mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-11-02 20:52:20 -07:00
Merge pull request #3377 from Ultimaker/refactoring_machine_manager
CURA-4606 Refactoring MachineManager
This commit is contained in:
commit
91e3e6b50c
543 changed files with 5634 additions and 6057 deletions
|
|
@ -937,8 +937,8 @@ class BuildVolume(SceneNode):
|
|||
# stack.
|
||||
#
|
||||
# \return A sequence of setting values, one for each extruder.
|
||||
def _getSettingFromAllExtruders(self, setting_key, property = "value"):
|
||||
all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, property)
|
||||
def _getSettingFromAllExtruders(self, setting_key):
|
||||
all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value")
|
||||
all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
|
||||
for i in range(len(all_values)):
|
||||
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)
|
||||
# and travel avoid distance.
|
||||
def _getEdgeDisallowedSize(self):
|
||||
if not self._global_container_stack:
|
||||
if not self._global_container_stack or not self._global_container_stack.extruders:
|
||||
return 0
|
||||
|
||||
container_stack = self._global_container_stack
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
#Type hinting.
|
||||
from typing import Dict
|
||||
|
||||
from PyQt5.QtCore import QObject
|
||||
from PyQt5.QtNetwork import QLocalServer
|
||||
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.SettingFunction import SettingFunction
|
||||
from cura.Settings.MachineNameValidator import MachineNameValidator
|
||||
from cura.Settings.ProfilesModel import ProfilesModel
|
||||
from cura.Settings.MaterialsModel import MaterialsModel
|
||||
from cura.Settings.QualityAndUserProfilesModel import QualityAndUserProfilesModel
|
||||
|
||||
from cura.Machines.Models.BuildPlateModel import BuildPlateModel
|
||||
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.UserProfilesModel import UserProfilesModel
|
||||
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
|
||||
|
||||
from cura.Machines.VariantManager import VariantManager
|
||||
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
|
||||
|
||||
from . import PlatformPhysics
|
||||
from . import BuildVolume
|
||||
|
|
@ -71,17 +83,14 @@ from . import CameraImageProvider
|
|||
from . import MachineActionManager
|
||||
|
||||
from cura.Settings.MachineManager import MachineManager
|
||||
from cura.Settings.MaterialManager import MaterialManager
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.UserChangesModel import UserChangesModel
|
||||
from cura.Settings.ExtrudersModel import ExtrudersModel
|
||||
from cura.Settings.ContainerSettingsModel import ContainerSettingsModel
|
||||
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.ObjectsModel import ObjectsModel
|
||||
from cura.BuildPlateModel import BuildPlateModel
|
||||
|
||||
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
|
|
@ -216,6 +225,7 @@ class CuraApplication(QtApplication):
|
|||
self._material_manager = None
|
||||
self._object_manager = None
|
||||
self._build_plate_model = None
|
||||
self._multi_build_plate_model = None
|
||||
self._setting_inheritance_manager = None
|
||||
self._simple_mode_settings_manager = None
|
||||
self._cura_scene_controller = None
|
||||
|
|
@ -233,6 +243,8 @@ class CuraApplication(QtApplication):
|
|||
if kwargs["parsed_command_line"].get("trigger_early_crash", False):
|
||||
assert not "This crash is triggered by the trigger_early_crash command line argument."
|
||||
|
||||
self._variant_manager = None
|
||||
|
||||
self.default_theme = "cura-light"
|
||||
|
||||
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.
|
||||
# We need them to simplify the switching between materials.
|
||||
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||
self.empty_container = empty_container
|
||||
|
||||
empty_definition_changes_container = copy.deepcopy(empty_container)
|
||||
empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes")
|
||||
empty_definition_changes_container.addMetaDataEntry("type", "definition_changes")
|
||||
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.setMetaDataEntry("id", "empty_variant")
|
||||
empty_variant_container.addMetaDataEntry("type", "variant")
|
||||
ContainerRegistry.getInstance().addContainer(empty_variant_container)
|
||||
self.empty_variant_container = empty_variant_container
|
||||
|
||||
empty_material_container = copy.deepcopy(empty_container)
|
||||
empty_material_container.setMetaDataEntry("id", "empty_material")
|
||||
empty_material_container.addMetaDataEntry("type", "material")
|
||||
ContainerRegistry.getInstance().addContainer(empty_material_container)
|
||||
self.empty_material_container = empty_material_container
|
||||
|
||||
empty_quality_container = copy.deepcopy(empty_container)
|
||||
empty_quality_container.setMetaDataEntry("id", "empty_quality")
|
||||
|
|
@ -310,12 +326,14 @@ class CuraApplication(QtApplication):
|
|||
empty_quality_container.addMetaDataEntry("type", "quality")
|
||||
empty_quality_container.addMetaDataEntry("supported", False)
|
||||
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.setMetaDataEntry("id", "empty_quality_changes")
|
||||
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
||||
empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported")
|
||||
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
|
||||
self.empty_quality_changes_container = empty_quality_changes_container
|
||||
|
||||
with ContainerRegistry.getInstance().lockFile():
|
||||
ContainerRegistry.getInstance().loadAllMetadata()
|
||||
|
|
@ -381,6 +399,9 @@ class CuraApplication(QtApplication):
|
|||
|
||||
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
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
|
|
@ -523,8 +544,6 @@ class CuraApplication(QtApplication):
|
|||
has_user_interaction = True
|
||||
return has_user_interaction
|
||||
|
||||
onDiscardOrKeepProfileChangesClosed = pyqtSignal() # Used to notify other managers that the dialog was closed
|
||||
|
||||
@pyqtSlot(str)
|
||||
def discardOrKeepProfileChangesClosed(self, option):
|
||||
if option == "discard":
|
||||
|
|
@ -547,7 +566,6 @@ class CuraApplication(QtApplication):
|
|||
user_global_container.update()
|
||||
|
||||
# notify listeners that quality has changed (after user selected discard or keep)
|
||||
self.onDiscardOrKeepProfileChangesClosed.emit()
|
||||
self.getMachineManager().activeQualityChanged.emit()
|
||||
|
||||
@pyqtSlot(int)
|
||||
|
|
@ -723,6 +741,20 @@ class CuraApplication(QtApplication):
|
|||
def run(self):
|
||||
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
|
||||
self._setUpSingleInstanceServer()
|
||||
|
||||
|
|
@ -816,7 +848,7 @@ class CuraApplication(QtApplication):
|
|||
|
||||
def getMachineManager(self, *args) -> MachineManager:
|
||||
if self._machine_manager is None:
|
||||
self._machine_manager = MachineManager.createMachineManager()
|
||||
self._machine_manager = MachineManager(self)
|
||||
return self._machine_manager
|
||||
|
||||
def getExtruderManager(self, *args):
|
||||
|
|
@ -824,20 +856,32 @@ class CuraApplication(QtApplication):
|
|||
self._extruder_manager = ExtruderManager.createExtruderManager()
|
||||
return self._extruder_manager
|
||||
|
||||
def getVariantManager(self, *args):
|
||||
return self._variant_manager
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getMaterialManager(self, *args):
|
||||
if self._material_manager is None:
|
||||
self._material_manager = MaterialManager.createMaterialManager()
|
||||
return self._material_manager
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getQualityManager(self, *args):
|
||||
return self._quality_manager
|
||||
|
||||
def getObjectsModel(self, *args):
|
||||
if self._object_manager is None:
|
||||
self._object_manager = ObjectsModel.createObjectsModel()
|
||||
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):
|
||||
if self._build_plate_model is None:
|
||||
self._build_plate_model = BuildPlateModel.createBuildPlateModel()
|
||||
|
||||
self._build_plate_model = BuildPlateModel(self)
|
||||
return self._build_plate_model
|
||||
|
||||
def getCuraSceneController(self, *args):
|
||||
|
|
@ -875,6 +919,16 @@ class CuraApplication(QtApplication):
|
|||
def getPrintInformation(self):
|
||||
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.
|
||||
#
|
||||
# \param engine The QML engine.
|
||||
|
|
@ -889,27 +943,34 @@ class CuraApplication(QtApplication):
|
|||
|
||||
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(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
||||
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
|
||||
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(ObjectsModel, "Cura", 1, 2, "ObjectsModel", self.getObjectsModel)
|
||||
qmlRegisterSingletonType(BuildPlateModel, "Cura", 1, 2, "BuildPlateModel", self.getBuildPlateModel)
|
||||
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 0, "ObjectsModel", self.getObjectsModel)
|
||||
qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel")
|
||||
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
|
||||
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
||||
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
||||
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
|
||||
qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel)
|
||||
qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel")
|
||||
qmlRegisterType(QualityAndUserProfilesModel, "Cura", 1, 0, "QualityAndUserProfilesModel")
|
||||
qmlRegisterType(UserProfilesModel, "Cura", 1, 0, "UserProfilesModel")
|
||||
|
||||
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
||||
qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
|
||||
qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
|
||||
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(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
|
||||
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)
|
||||
|
||||
# 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
|
||||
scene_bounding_box = None
|
||||
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()):
|
||||
if (
|
||||
not issubclass(type(node), CuraSceneNode) or
|
||||
|
|
@ -1240,7 +1301,7 @@ class CuraApplication(QtApplication):
|
|||
@pyqtSlot()
|
||||
def arrangeAll(self):
|
||||
nodes = []
|
||||
active_build_plate = self.getBuildPlateModel().activeBuildPlate
|
||||
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
if not isinstance(node, SceneNode):
|
||||
continue
|
||||
|
|
@ -1389,7 +1450,7 @@ class CuraApplication(QtApplication):
|
|||
group_decorator = GroupDecorator()
|
||||
group_node.addDecorator(group_decorator)
|
||||
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.setSelectable(True)
|
||||
center = Selection.getSelectionCenter()
|
||||
|
|
@ -1534,7 +1595,7 @@ class CuraApplication(QtApplication):
|
|||
arrange_objects_on_load = (
|
||||
not Preferences.getInstance().getValue("cura/use_multi_build_plate") or
|
||||
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()
|
||||
fixed_nodes = []
|
||||
|
|
|
|||
49
cura/Machines/ContainerNode.py
Normal file
49
cura/Machines/ContainerNode.py
Normal 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"))
|
||||
26
cura/Machines/MaterialGroup.py
Normal file
26
cura/Machines/MaterialGroup.py
Normal 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)
|
||||
479
cura/Machines/MaterialManager.py
Normal file
479
cura/Machines/MaterialManager.py
Normal 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
|
||||
21
cura/Machines/MaterialNode.py
Normal file
21
cura/Machines/MaterialNode.py
Normal 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
|
||||
46
cura/Machines/Models/BaseMaterialsModel.py
Normal file
46
cura/Machines/Models/BaseMaterialsModel.py
Normal 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
|
||||
131
cura/Machines/Models/BrandMaterialsModel.py
Normal file
131
cura/Machines/Models/BrandMaterialsModel.py
Normal 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)
|
||||
49
cura/Machines/Models/BuildPlateModel.py
Normal file
49
cura/Machines/Models/BuildPlateModel.py
Normal 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)
|
||||
|
|
@ -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)
|
||||
60
cura/Machines/Models/GenericMaterialsModel.py
Normal file
60
cura/Machines/Models/GenericMaterialsModel.py
Normal 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)
|
||||
101
cura/Machines/Models/MaterialManagementModel.py
Normal file
101
cura/Machines/Models/MaterialManagementModel.py
Normal 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)
|
||||
|
|
@ -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.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()
|
||||
activeBuildPlateChanged = pyqtSignal()
|
||||
selectionChanged = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
|
||||
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
|
||||
|
||||
self._max_build_plate = 1 # default
|
||||
self._active_build_plate = -1
|
||||
self._selection_build_plates = []
|
||||
|
||||
def setMaxBuildPlate(self, max_build_plate):
|
||||
self._max_build_plate = max_build_plate
|
||||
|
|
@ -37,10 +45,6 @@ class BuildPlateModel(ListModel):
|
|||
def activeBuildPlate(self):
|
||||
return self._active_build_plate
|
||||
|
||||
@staticmethod
|
||||
def createBuildPlateModel():
|
||||
return BuildPlateModel()
|
||||
|
||||
def _updateSelectedObjectBuildPlateNumbers(self, *args):
|
||||
result = set()
|
||||
for node in Selection.getAllSelectedObjects():
|
||||
56
cura/Machines/Models/NozzleModel.py
Normal file
56
cura/Machines/Models/NozzleModel.py
Normal 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)
|
||||
122
cura/Machines/Models/QualityManagementModel.py
Normal file
122
cura/Machines/Models/QualityManagementModel.py
Normal 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
|
||||
106
cura/Machines/Models/QualityProfilesDropDownMenuModel.py
Normal file
106
cura/Machines/Models/QualityProfilesDropDownMenuModel.py
Normal 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)
|
||||
159
cura/Machines/Models/QualitySettingsModel.py
Normal file
159
cura/Machines/Models/QualitySettingsModel.py
Normal 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)
|
||||
0
cura/Machines/Models/__init__.py
Normal file
0
cura/Machines/Models/__init__.py
Normal file
53
cura/Machines/QualityChangesGroup.py
Normal file
53
cura/Machines/QualityChangesGroup.py
Normal 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)
|
||||
50
cura/Machines/QualityGroup.py
Normal file
50
cura/Machines/QualityGroup.py
Normal 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
|
||||
445
cura/Machines/QualityManager.py
Normal file
445
cura/Machines/QualityManager.py
Normal 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
|
||||
35
cura/Machines/QualityNode.py
Normal file
35
cura/Machines/QualityNode.py
Normal 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))
|
||||
111
cura/Machines/VariantManager.py
Normal file
111
cura/Machines/VariantManager.py
Normal 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
|
||||
0
cura/Machines/__init__.py
Normal file
0
cura/Machines/__init__.py
Normal file
|
|
@ -1,27 +1,22 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from typing import Dict
|
||||
import math
|
||||
import os.path
|
||||
import unicodedata
|
||||
import json
|
||||
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.Settings.ContainerRegistry import ContainerRegistry
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from typing import Dict
|
||||
|
||||
import math
|
||||
import os.path
|
||||
import unicodedata
|
||||
import json
|
||||
import re #To create abbreviations for printer names.
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
## 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._initVariablesWithBuildPlate(self._active_build_plate)
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._updateJobName)
|
||||
Application.getInstance().fileLoaded.connect(self.setBaseName)
|
||||
Application.getInstance().getBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
|
||||
Application.getInstance().workspaceLoaded.connect(self.setProjectName)
|
||||
self._application = Application.getInstance()
|
||||
self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
|
||||
|
||||
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)
|
||||
|
||||
self._active_material_container = None
|
||||
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._onActiveMaterialChanged)
|
||||
self._onActiveMaterialChanged()
|
||||
self._application.getMachineManager().rootMaterialChanged.connect(self._onActiveMaterialsChanged)
|
||||
self._onActiveMaterialsChanged()
|
||||
|
||||
self._material_amounts = []
|
||||
|
||||
|
|
@ -200,7 +198,8 @@ class PrintInformation(QObject):
|
|||
self._current_print_time[build_plate_number].setDuration(total_estimated_time)
|
||||
|
||||
def _calculateInformation(self, build_plate_number):
|
||||
if Application.getInstance().getGlobalContainerStack() is None:
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return
|
||||
|
||||
self._material_lengths[build_plate_number] = []
|
||||
|
|
@ -210,19 +209,17 @@ class PrintInformation(QObject):
|
|||
|
||||
material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings"))
|
||||
|
||||
extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(Application.getInstance().getGlobalContainerStack().getId()))
|
||||
for index, amount in enumerate(self._material_amounts):
|
||||
extruder_stacks = global_stack.extruders
|
||||
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
|
||||
# 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)
|
||||
material = extruder_stack.findContainer({"type": "material"})
|
||||
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
|
||||
density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0)
|
||||
material = extruder_stack.findContainer({"type": "material"})
|
||||
radius = extruder_stack.getProperty("material_diameter", "value") / 2
|
||||
|
||||
weight = float(amount) * float(density) / 1000
|
||||
cost = 0
|
||||
|
|
@ -260,25 +257,11 @@ class PrintInformation(QObject):
|
|||
if preference != "cura/material_settings":
|
||||
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)
|
||||
|
||||
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):
|
||||
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:
|
||||
self._active_build_plate = new_active_build_plate
|
||||
|
||||
|
|
@ -290,8 +273,8 @@ class PrintInformation(QObject):
|
|||
self.materialNamesChanged.emit()
|
||||
self.currentPrintTimeChanged.emit()
|
||||
|
||||
def _onMaterialMetaDataChanged(self, *args, **kwargs):
|
||||
for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1):
|
||||
def _onActiveMaterialsChanged(self, *args, **kwargs):
|
||||
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
|
||||
self._calculateInformation(build_plate_number)
|
||||
|
||||
@pyqtSlot(str)
|
||||
|
|
@ -396,7 +379,9 @@ class PrintInformation(QObject):
|
|||
return result
|
||||
|
||||
# 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
|
||||
temp_message = {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -68,7 +68,7 @@ class ConvexHullNode(SceneNode):
|
|||
ConvexHullNode.shader.setUniformValue("u_opacity", 0.6)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from PyQt5.QtCore import Qt, pyqtSlot, QObject
|
|||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from cura.ObjectsModel import ObjectsModel
|
||||
from cura.BuildPlateModel import BuildPlateModel
|
||||
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
|
|
@ -16,11 +16,11 @@ from UM.Signal import Signal
|
|||
class CuraSceneController(QObject):
|
||||
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__()
|
||||
|
||||
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._last_selected_index = 0
|
||||
|
|
@ -41,9 +41,9 @@ class CuraSceneController(QObject):
|
|||
self._max_build_plate = max_build_plate
|
||||
changed = True
|
||||
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)]
|
||||
self._build_plate_model.setItems(build_plates)
|
||||
self._multi_build_plate_model.setItems(build_plates)
|
||||
if self._active_build_plate > self._max_build_plate:
|
||||
build_plate_number = 0
|
||||
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
|
||||
Selection.clear()
|
||||
|
||||
self._build_plate_model.setActiveBuildPlate(nr)
|
||||
self._multi_build_plate_model.setActiveBuildPlate(nr)
|
||||
self._objects_model.setActiveBuildPlate(nr)
|
||||
self.activeBuildPlateChanged.emit()
|
||||
|
||||
@staticmethod
|
||||
def createCuraSceneController():
|
||||
objects_model = Application.getInstance().getObjectsModel()
|
||||
build_plate_model = Application.getInstance().getBuildPlateModel()
|
||||
return CuraSceneController(objects_model = objects_model, build_plate_model = build_plate_model)
|
||||
multi_build_plate_model = Application.getInstance().getMultiBuildPlateModel()
|
||||
return CuraSceneController(objects_model = objects_model, multi_build_plate_model = multi_build_plate_model)
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ class CuraSceneNode(SceneNode):
|
|||
return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
|
||||
|
||||
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:
|
||||
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
|
||||
# TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import copy
|
||||
import os.path
|
||||
import urllib.parse
|
||||
import uuid
|
||||
from typing import Any, Dict, List, Union
|
||||
from typing import Dict, Union
|
||||
|
||||
from PyQt5.QtCore import QObject, QUrl, QVariant
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
from UM.Util import parseBool
|
||||
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.SaveFile import SaveFile
|
||||
|
|
@ -22,7 +20,6 @@ from UM.Application import Application
|
|||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from cura.QualityManager import QualityManager
|
||||
|
||||
from UM.MimeTypeDatabase import MimeTypeNotFoundError
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
|
@ -30,9 +27,11 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
|||
from UM.i18n import i18nCatalog
|
||||
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
|
||||
catalog = i18nCatalog("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
|
||||
|
|
@ -42,154 +41,12 @@ class ContainerManager(QObject):
|
|||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._application = Application.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 = {}
|
||||
|
||||
## 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)
|
||||
def getContainerMetaDataEntry(self, container_id, entry_name):
|
||||
metadatas = self._container_registry.findContainersMetadata(id = container_id)
|
||||
|
|
@ -211,18 +68,15 @@ class ContainerManager(QObject):
|
|||
# \param entry_value The new value of the entry.
|
||||
#
|
||||
# \return True if successful, False if not.
|
||||
@pyqtSlot(str, str, str, result = bool)
|
||||
def setContainerMetaDataEntry(self, container_id, entry_name, entry_value):
|
||||
if self._container_registry.isReadOnly(container_id):
|
||||
Logger.log("w", "Cannot set metadata of read-only container %s.", container_id)
|
||||
# TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
|
||||
@pyqtSlot("QVariant", str, str)
|
||||
def setContainerMetaDataEntry(self, container_node, entry_name, entry_value):
|
||||
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
|
||||
|
||||
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.
|
||||
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]
|
||||
material_group = self._material_manager.getMaterialGroup(root_material_id)
|
||||
|
||||
entries = entry_name.split("/")
|
||||
entry_name = entries.pop()
|
||||
|
|
@ -230,7 +84,7 @@ class ContainerManager(QObject):
|
|||
sub_item_changed = False
|
||||
if entries:
|
||||
root_name = entries.pop(0)
|
||||
root = container.getMetaDataEntry(root_name)
|
||||
root = material_group.root_material_node.metadata.get(root_name)
|
||||
|
||||
item = root
|
||||
for _ in range(len(entries)):
|
||||
|
|
@ -243,12 +97,11 @@ class ContainerManager(QObject):
|
|||
entry_name = root_name
|
||||
entry_value = root
|
||||
|
||||
container = material_group.root_material_node.getContainer()
|
||||
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.
|
||||
container.metaDataChanged.emit(container)
|
||||
|
||||
return True
|
||||
|
||||
## Set a setting property of the specified 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)
|
||||
|
||||
## 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)
|
||||
def makeUniqueName(self, original_name):
|
||||
return self._container_registry.uniqueName(original_name)
|
||||
|
|
@ -399,7 +198,7 @@ class ContainerManager(QObject):
|
|||
@pyqtSlot(str, str, QUrl, result = "QVariantMap")
|
||||
def exportContainer(self, container_id: str, file_type: str, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
|
||||
if not container_id or not file_type or not file_url_or_string:
|
||||
return { "status": "error", "message": "Invalid arguments"}
|
||||
return {"status": "error", "message": "Invalid arguments"}
|
||||
|
||||
if isinstance(file_url_or_string, QUrl):
|
||||
file_url = file_url_or_string.toLocalFile()
|
||||
|
|
@ -407,20 +206,20 @@ class ContainerManager(QObject):
|
|||
file_url = file_url_or_string
|
||||
|
||||
if not file_url:
|
||||
return { "status": "error", "message": "Invalid path"}
|
||||
return {"status": "error", "message": "Invalid path"}
|
||||
|
||||
mime_type = None
|
||||
if not file_type in self._container_name_filters:
|
||||
if file_type not in self._container_name_filters:
|
||||
try:
|
||||
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
|
||||
except MimeTypeNotFoundError:
|
||||
return { "status": "error", "message": "Unknown File Type" }
|
||||
return {"status": "error", "message": "Unknown File Type"}
|
||||
else:
|
||||
mime_type = self._container_name_filters[file_type]["mime"]
|
||||
|
||||
containers = self._container_registry.findContainers(id = container_id)
|
||||
if not containers:
|
||||
return { "status": "error", "message": "Container not found"}
|
||||
return {"status": "error", "message": "Container not found"}
|
||||
container = containers[0]
|
||||
|
||||
if Platform.isOSX() and "." in file_url:
|
||||
|
|
@ -437,12 +236,12 @@ class ContainerManager(QObject):
|
|||
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
|
||||
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_url))
|
||||
if result == QMessageBox.No:
|
||||
return { "status": "cancelled", "message": "User cancelled"}
|
||||
return {"status": "cancelled", "message": "User cancelled"}
|
||||
|
||||
try:
|
||||
contents = container.serialize()
|
||||
except NotImplementedError:
|
||||
return { "status": "error", "message": "Unable to serialize container"}
|
||||
return {"status": "error", "message": "Unable to serialize container"}
|
||||
|
||||
if contents is None:
|
||||
return {"status": "error", "message": "Serialization returned None. Unable to write to file"}
|
||||
|
|
@ -450,7 +249,7 @@ class ContainerManager(QObject):
|
|||
with SaveFile(file_url, "w") as f:
|
||||
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
|
||||
#
|
||||
|
|
@ -461,7 +260,7 @@ class ContainerManager(QObject):
|
|||
@pyqtSlot(QUrl, result = "QVariantMap")
|
||||
def importMaterialContainer(self, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
|
||||
if not file_url_or_string:
|
||||
return { "status": "error", "message": "Invalid path"}
|
||||
return {"status": "error", "message": "Invalid path"}
|
||||
|
||||
if isinstance(file_url_or_string, QUrl):
|
||||
file_url = file_url_or_string.toLocalFile()
|
||||
|
|
@ -469,16 +268,16 @@ class ContainerManager(QObject):
|
|||
file_url = file_url_or_string
|
||||
|
||||
if not file_url or not os.path.exists(file_url):
|
||||
return { "status": "error", "message": "Invalid path" }
|
||||
return {"status": "error", "message": "Invalid path"}
|
||||
|
||||
try:
|
||||
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
|
||||
except MimeTypeNotFoundError:
|
||||
return { "status": "error", "message": "Could not determine mime type of file" }
|
||||
return {"status": "error", "message": "Could not determine mime type of file"}
|
||||
|
||||
container_type = self._container_registry.getContainerForMimeType(mime_type)
|
||||
if not container_type:
|
||||
return { "status": "error", "message": "Could not find a container to handle the specified file."}
|
||||
return {"status": "error", "message": "Could not find a container to handle the specified file."}
|
||||
|
||||
container_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_url)))
|
||||
container_id = self._container_registry.uniqueName(container_id)
|
||||
|
|
@ -489,7 +288,7 @@ class ContainerManager(QObject):
|
|||
with open(file_url, "rt", encoding = "utf-8") as f:
|
||||
container.deserialize(f.read())
|
||||
except PermissionError:
|
||||
return { "status": "error", "message": "Permission denied when trying to read the file"}
|
||||
return {"status": "error", "message": "Permission denied when trying to read the file"}
|
||||
except Exception as ex:
|
||||
return {"status": "error", "message": str(ex)}
|
||||
|
||||
|
|
@ -497,7 +296,7 @@ class ContainerManager(QObject):
|
|||
|
||||
self._container_registry.addContainer(container)
|
||||
|
||||
return { "status": "success", "message": "Successfully imported container {0}".format(container.getName()) }
|
||||
return {"status": "success", "message": "Successfully imported container {0}".format(container.getName())}
|
||||
|
||||
## Update the current active quality changes container with the settings from the user container.
|
||||
#
|
||||
|
|
@ -522,7 +321,7 @@ class ContainerManager(QObject):
|
|||
|
||||
self._performMerge(quality_changes, stack.getTop())
|
||||
|
||||
self._machine_manager.activeQualityChanged.emit()
|
||||
self._machine_manager.activeQualityChangesGroupChanged.emit()
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -547,18 +346,16 @@ class ContainerManager(QObject):
|
|||
# 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
|
||||
# stack and clear the user settings.
|
||||
#
|
||||
# \return \type{bool} True if the operation was successfully, False if not.
|
||||
@pyqtSlot(str, result = bool)
|
||||
@pyqtSlot(str)
|
||||
def createQualityChanges(self, base_name):
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
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 == "":
|
||||
Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
|
||||
return False
|
||||
return
|
||||
|
||||
self._machine_manager.blurSettings.emit()
|
||||
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.
|
||||
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
user_container = stack.getTop()
|
||||
user_container = stack.userChanges
|
||||
quality_container = stack.quality
|
||||
quality_changes_container = stack.qualityChanges
|
||||
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())
|
||||
continue
|
||||
|
||||
extruder_id = None if stack is global_stack else QualityManager.getInstance().getParentMachineDefinition(stack.getBottom()).getId()
|
||||
new_changes = self._createQualityChanges(quality_container, unique_name,
|
||||
Application.getInstance().getGlobalContainerStack().getBottom(),
|
||||
extruder_id)
|
||||
extruder_definition_id = None
|
||||
if isinstance(stack, ExtruderStack):
|
||||
extruder_definition_id = stack.definition.getId()
|
||||
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, user_container)
|
||||
|
||||
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
|
||||
#
|
||||
# \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
|
||||
@pyqtSlot(str, result = "QStringList")
|
||||
def getLinkedMaterials(self, material_id: str):
|
||||
containers = self._container_registry.findInstanceContainersMetadata(id = material_id)
|
||||
if not containers:
|
||||
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id)
|
||||
return []
|
||||
@pyqtSlot("QVariant", result = "QStringList")
|
||||
def getLinkedMaterials(self, material_node):
|
||||
guid = material_node.metadata["GUID"]
|
||||
|
||||
material_container = containers[0]
|
||||
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 []
|
||||
material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
|
||||
|
||||
containers = self._container_registry.findInstanceContainersMetadata(type = "material", GUID = material_guid)
|
||||
linked_material_names = []
|
||||
for container in containers:
|
||||
if container["id"] in [material_id, material_base_file] or container.get("base_file") != container["id"]:
|
||||
continue
|
||||
|
||||
linked_material_names.append(container["name"])
|
||||
if material_group_list:
|
||||
for material_group in material_group_list:
|
||||
linked_material_names.append(material_group.root_material_node.metadata["name"])
|
||||
return linked_material_names
|
||||
|
||||
## 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.
|
||||
@pyqtSlot(str)
|
||||
def unlinkMaterial(self, material_id: str):
|
||||
containers = self._container_registry.findInstanceContainers(id=material_id)
|
||||
if not containers:
|
||||
Logger.log("d", "Unable to make the material with id %s unique, because it doesn't exist.", material_id)
|
||||
return ""
|
||||
@pyqtSlot("QVariant")
|
||||
def unlinkMaterial(self, material_node):
|
||||
# Get the material group
|
||||
material_group = self._material_manager.getMaterialGroup(material_node.metadata["base_file"])
|
||||
|
||||
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.
|
||||
@classmethod
|
||||
|
|
@ -1015,81 +484,6 @@ class ContainerManager(QObject):
|
|||
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
|
||||
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
|
||||
@pyqtSlot(QUrl, result="QVariantMap")
|
||||
def importProfile(self, file_url):
|
||||
|
|
@ -1100,11 +494,13 @@ class ContainerManager(QObject):
|
|||
return
|
||||
return self._container_registry.importProfile(path)
|
||||
|
||||
@pyqtSlot("QVariantList", QUrl, str)
|
||||
def exportProfile(self, instance_id: str, file_url: QUrl, file_type: str) -> None:
|
||||
@pyqtSlot(QObject, QUrl, str)
|
||||
def exportQualityChangesGroup(self, quality_changes_group, file_url: QUrl, file_type: str):
|
||||
if not file_url.isValid():
|
||||
return
|
||||
path = file_url.toLocalFile()
|
||||
if not path:
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -25,14 +25,15 @@ from UM.Resources import Resources
|
|||
|
||||
from . import ExtruderStack
|
||||
from . import GlobalStack
|
||||
from .ContainerManager import ContainerManager
|
||||
from .ExtruderManager import ExtruderManager
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class CuraContainerRegistry(ContainerRegistry):
|
||||
def __init__(self, *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 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>)"
|
||||
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.
|
||||
# fileType has the format "<description> (*.<extension>)"
|
||||
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))
|
||||
if result == QMessageBox.No:
|
||||
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)
|
||||
|
||||
try:
|
||||
success = profile_writer.write(file_name, found_containers)
|
||||
success = profile_writer.write(file_name, container_list)
|
||||
except Exception as 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)),
|
||||
|
|
@ -289,7 +269,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
|
||||
elif profile_index < len(machine_extruders) + 1:
|
||||
# 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"):
|
||||
profile.addMetaDataEntry("extruder", extruder_id)
|
||||
else:
|
||||
|
|
@ -348,39 +328,16 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
|
||||
|
||||
quality_type_criteria = {"quality_type": quality_type}
|
||||
if self._machineHasOwnQualities():
|
||||
profile.setDefinition(self._activeQualityDefinition().getId())
|
||||
if self._machineHasOwnMaterials():
|
||||
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
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
definition_id = getMachineDefinitionIDForQualitySearch(global_stack)
|
||||
profile.setDefinition(definition_id)
|
||||
|
||||
# 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
|
||||
# successfully imported but then fail to show up.
|
||||
from cura.QualityManager import QualityManager
|
||||
qualities = QualityManager.getInstance()._getFilteredContainersForStack(machine_definition, materials, **quality_type_criteria)
|
||||
if not qualities:
|
||||
quality_manager = CuraApplication.getInstance()._quality_manager
|
||||
quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack)
|
||||
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)
|
||||
|
||||
ContainerRegistry.getInstance().addContainer(profile)
|
||||
|
|
@ -400,18 +357,6 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
result.append( (plugin_id, meta_data) )
|
||||
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
|
||||
# \return True if the current machine requires its own materials
|
||||
def _machineHasOwnMaterials(self):
|
||||
|
|
@ -507,8 +452,6 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
extruder_stack.setDefinition(extruder_definition)
|
||||
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
# 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_name = definition_changes_id
|
||||
|
|
@ -567,26 +510,28 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
self.addContainer(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"):
|
||||
variant_id = machine.variant.getId()
|
||||
variant = machine.variant
|
||||
else:
|
||||
variant_id = "empty_variant"
|
||||
extruder_stack.setVariantById(variant_id)
|
||||
variant = empty_variant
|
||||
extruder_stack.variant = variant
|
||||
|
||||
material_id = "default"
|
||||
if machine.material.getId() not in ("empty", "empty_material"):
|
||||
material_id = machine.material.getId()
|
||||
material = machine.material
|
||||
else:
|
||||
material_id = "empty_material"
|
||||
extruder_stack.setMaterialById(material_id)
|
||||
material = empty_material
|
||||
extruder_stack.material = material
|
||||
|
||||
quality_id = "default"
|
||||
if machine.quality.getId() not in ("empty", "empty_quality"):
|
||||
quality_id = machine.quality.getId()
|
||||
quality = machine.quality
|
||||
else:
|
||||
quality_id = "empty_quality"
|
||||
extruder_stack.setQualityById(quality_id)
|
||||
quality = empty_quality
|
||||
extruder_stack.quality = quality
|
||||
|
||||
machine_quality_changes = machine.qualityChanges
|
||||
if new_global_quality_changes is not None:
|
||||
|
|
@ -598,7 +543,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
extruder_quality_changes_container = extruder_quality_changes_container[0]
|
||||
|
||||
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:
|
||||
# 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
|
||||
|
|
@ -607,7 +552,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
if extruder_quality_changes_container:
|
||||
quality_changes_id = extruder_quality_changes_container.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:
|
||||
# if we still cannot find a quality changes container for the extruder, create a new one
|
||||
container_name = machine_quality_changes.getName()
|
||||
|
|
@ -642,7 +587,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
|
||||
machine_quality_changes.removeInstance(qc_setting_key, postpone_emit=True)
|
||||
else:
|
||||
extruder_stack.setQualityChangesById("empty_quality_changes")
|
||||
extruder_stack.qualityChanges = self.findInstanceContainers(id = "empty_quality_changes")[0]
|
||||
|
||||
self.addContainer(extruder_stack)
|
||||
|
||||
|
|
|
|||
|
|
@ -83,20 +83,6 @@ class CuraContainerStack(ContainerStack):
|
|||
def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None:
|
||||
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.
|
||||
#
|
||||
# \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:
|
||||
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.
|
||||
#
|
||||
# \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:
|
||||
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.
|
||||
#
|
||||
# \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:
|
||||
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.
|
||||
#
|
||||
# \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:
|
||||
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.
|
||||
#
|
||||
# \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:
|
||||
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.
|
||||
#
|
||||
# \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:
|
||||
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)
|
||||
|
||||
## Overridden from ContainerStack
|
||||
|
|
@ -391,243 +282,6 @@ class CuraContainerStack(ContainerStack):
|
|||
|
||||
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:
|
||||
|
||||
# Helper to make sure we emit a PyQt signal on container changes.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
from UM.Logger import Logger
|
||||
from typing import Optional
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
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 .ExtruderStack import ExtruderStack
|
||||
from typing import Optional
|
||||
|
||||
|
||||
## Contains helper functions to create new machines.
|
||||
|
|
@ -22,7 +25,13 @@ class CuraStackBuilder:
|
|||
# \return The new global stack or None if an error occurred.
|
||||
@classmethod
|
||||
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()
|
||||
|
||||
definitions = registry.findDefinitionContainers(id = definition_id)
|
||||
if not definitions:
|
||||
Logger.log("w", "Definition {definition} was not found!", definition = definition_id)
|
||||
|
|
@ -30,7 +39,21 @@ class CuraStackBuilder:
|
|||
|
||||
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
|
||||
# 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
|
||||
|
|
@ -40,49 +63,55 @@ class CuraStackBuilder:
|
|||
new_global_stack = cls.createGlobalStack(
|
||||
new_stack_id = generated_name,
|
||||
definition = machine_definition,
|
||||
quality = "default",
|
||||
material = "default",
|
||||
variant = "default",
|
||||
variant_container = global_variant_container,
|
||||
material_container = application.empty_material_container,
|
||||
quality_container = application.empty_quality_container,
|
||||
)
|
||||
|
||||
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 extruder stack for single extrusion machines that have no separate extruder definition files
|
||||
extruder_definition = registry.findDefinitionContainers(id = "fdmextruder")[0]
|
||||
new_extruder_id = registry.uniqueName(machine_definition.getName() + " " + extruder_definition.id)
|
||||
# Create ExtruderStacks
|
||||
extruder_dict = machine_definition.getMetaDataEntry("machine_extruder_trains")
|
||||
|
||||
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_id,
|
||||
definition = extruder_definition,
|
||||
machine_definition_id = machine_definition.getId(),
|
||||
quality = "default",
|
||||
material = "default",
|
||||
variant = "default",
|
||||
next_stack = new_global_stack
|
||||
extruder_definition = extruder_definition,
|
||||
machine_definition_id = definition_id,
|
||||
position = position,
|
||||
variant_container = extruder_variant_container,
|
||||
material_container = material_container,
|
||||
quality_container = application.empty_quality_container,
|
||||
global_stack = new_global_stack,
|
||||
)
|
||||
new_extruder.setNextStack(new_global_stack)
|
||||
new_global_stack.addExtruder(new_extruder)
|
||||
registry.addContainer(new_extruder)
|
||||
else:
|
||||
# create extruder stack for each found extruder definition
|
||||
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)
|
||||
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:
|
||||
extruder_stack.quality = application.empty_quality_container
|
||||
|
||||
# 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).
|
||||
|
|
@ -100,43 +129,27 @@ class CuraStackBuilder:
|
|||
#
|
||||
# \return A new Global stack instance with the specified parameters.
|
||||
@classmethod
|
||||
def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, machine_definition_id: str, **kwargs) -> ExtruderStack:
|
||||
stack = ExtruderStack(new_stack_id)
|
||||
stack.setName(definition.getName())
|
||||
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)
|
||||
def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str,
|
||||
position: int,
|
||||
variant_container, material_container, quality_container, global_stack) -> ExtruderStack:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
||||
user_container.setDefinition(machine_definition_id)
|
||||
application = CuraApplication.getInstance()
|
||||
|
||||
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
|
||||
# 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"))
|
||||
stack.addMetaDataEntry("position", position)
|
||||
|
||||
if "variant" in kwargs:
|
||||
stack.setVariantById(kwargs["variant"])
|
||||
user_container = cls.createUserChangesContainer(new_stack_id + "_user", machine_definition_id, new_stack_id,
|
||||
is_global_stack = False)
|
||||
|
||||
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"])
|
||||
stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings")
|
||||
stack.variant = variant_container
|
||||
stack.material = material_container
|
||||
stack.quality = quality_container
|
||||
stack.qualityChanges = application.empty_quality_changes_container
|
||||
stack.userChanges = user_container
|
||||
|
||||
# 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
|
||||
|
|
@ -153,44 +166,48 @@ class CuraStackBuilder:
|
|||
#
|
||||
# \return A new Global stack instance with the specified parameters.
|
||||
@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.setDefinition(definition)
|
||||
|
||||
user_container = InstanceContainer(new_stack_id + "_user")
|
||||
user_container.addMetaDataEntry("type", "user")
|
||||
user_container.addMetaDataEntry("machine", new_stack_id)
|
||||
from cura.CuraApplication import CuraApplication
|
||||
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
||||
user_container.setDefinition(definition.getId())
|
||||
# Create user container
|
||||
user_container = cls.createUserChangesContainer(new_stack_id + "_user", definition.getId(), new_stack_id,
|
||||
is_global_stack = True)
|
||||
|
||||
stack.setUserChanges(user_container)
|
||||
|
||||
# Important! The order here matters, because that allows the stack to
|
||||
# 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:
|
||||
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"])
|
||||
stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings")
|
||||
stack.variant = variant_container
|
||||
stack.material = material_container
|
||||
stack.quality = quality_container
|
||||
stack.qualityChanges = application.empty_quality_changes_container
|
||||
stack.userChanges = user_container
|
||||
|
||||
ContainerRegistry.getInstance().addContainer(user_container)
|
||||
|
||||
return stack
|
||||
|
||||
@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
|
||||
|
||||
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from UM.Scene.Selection import Selection
|
|||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Settings.SettingInstance import SettingInstance
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
|
||||
from typing import Optional, List, TYPE_CHECKING, Union
|
||||
|
|
@ -30,28 +31,22 @@ class ExtruderManager(QObject):
|
|||
def __init__(self, parent = None):
|
||||
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._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
|
||||
self._selected_object_extruders = []
|
||||
self._global_container_stack_definition_id = None
|
||||
self._addCurrentMachineExtruders()
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
|
||||
#Application.getInstance().globalContainerStackChanged.connect(self._globalContainerStackChanged)
|
||||
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
|
||||
|
||||
## Signal to notify other components when the list of extruders for a machine definition changes.
|
||||
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.
|
||||
activeExtruderChanged = pyqtSignal()
|
||||
|
||||
## The signal notifies subscribers if extruders are added
|
||||
extrudersAdded = pyqtSignal()
|
||||
|
||||
## Gets the unique identifier of the currently active extruder stack.
|
||||
#
|
||||
# The currently active extruder stack is the stack that is currently being
|
||||
|
|
@ -184,6 +179,7 @@ class ExtruderManager(QObject):
|
|||
self._selected_object_extruders = []
|
||||
self.selectedObjectExtrudersChanged.emit()
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
|
|
@ -371,12 +367,7 @@ class ExtruderManager(QObject):
|
|||
|
||||
return result[:machine_extruder_count]
|
||||
|
||||
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()
|
||||
|
||||
def _globalContainerStackChanged(self) -> None:
|
||||
# If the global container changed, the machine changed and might have extruders that were not registered yet
|
||||
self._addCurrentMachineExtruders()
|
||||
|
||||
|
|
@ -384,7 +375,7 @@ class ExtruderManager(QObject):
|
|||
|
||||
## Adds the extruders of the currently active machine.
|
||||
def _addCurrentMachineExtruders(self) -> None:
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = self._application.getGlobalContainerStack()
|
||||
extruders_changed = False
|
||||
|
||||
if global_stack:
|
||||
|
|
@ -404,20 +395,82 @@ class ExtruderManager(QObject):
|
|||
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. ???
|
||||
extruder_train.setParent(global_stack)
|
||||
extruder_train.setNextStack(global_stack)
|
||||
extruders_changed = True
|
||||
|
||||
# FIX: We have to remove those settings here because we know that those values have been copied to all
|
||||
# the extruders at this point.
|
||||
for key in ("material_diameter", "machine_nozzle_size"):
|
||||
if global_stack.definitionChanges.hasProperty(key, "value"):
|
||||
global_stack.definitionChanges.removeInstance(key, postpone_emit = True)
|
||||
|
||||
self._fixMaterialDiameterAndNozzleSize(global_stack, extruder_trains)
|
||||
if extruders_changed:
|
||||
self.extrudersChanged.emit(global_stack_id)
|
||||
self.extrudersAdded.emit()
|
||||
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.
|
||||
for key in keys_to_copy:
|
||||
if global_stack.definitionChanges.hasProperty(key, "value"):
|
||||
global_stack.definitionChanges.removeInstance(key, postpone_emit = True)
|
||||
|
||||
# Update material diameter for extruders
|
||||
for position in extruder_positions_to_update:
|
||||
self.updateMaterialForDiameter(position, global_stack = global_stack)
|
||||
|
||||
## Get all extruder values for a certain setting.
|
||||
#
|
||||
# This is exposed to SettingFunction so it can be used in value functions.
|
||||
|
|
@ -503,10 +556,11 @@ class ExtruderManager(QObject):
|
|||
return ExtruderManager.getExtruderValues(key)
|
||||
|
||||
## Updates the material container to a material that matches the material diameter set for the printer
|
||||
def updateMaterialForDiameter(self, extruder_position: int):
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
def updateMaterialForDiameter(self, extruder_position: int, global_stack = None):
|
||||
if not global_stack:
|
||||
return
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return
|
||||
|
||||
if not global_stack.getMetaDataEntry("has_materials", False):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@
|
|||
|
||||
from typing import Any, TYPE_CHECKING, Optional
|
||||
|
||||
from UM.Application import Application
|
||||
from PyQt5.QtCore import pyqtProperty
|
||||
|
||||
from UM.Decorators import override
|
||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
|
||||
from UM.Settings.SettingInstance import SettingInstance
|
||||
|
||||
from . import Exceptions
|
||||
from .CuraContainerStack import CuraContainerStack
|
||||
from .CuraContainerStack import CuraContainerStack, _ContainerIndexes
|
||||
from .ExtruderManager import ExtruderManager
|
||||
|
||||
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.
|
||||
@override(ContainerStack)
|
||||
def setNextStack(self, stack: ContainerStack) -> None:
|
||||
def setNextStack(self, stack: CuraContainerStack) -> None:
|
||||
super().setNextStack(stack)
|
||||
stack.addExtruder(self)
|
||||
self.addMetaDataEntry("machine", stack.id)
|
||||
|
|
@ -42,75 +42,6 @@ class ExtruderStack(CuraContainerStack):
|
|||
# For backward compatibility: Register the extruder with the Extruder Manager
|
||||
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)
|
||||
def getNextStack(self) -> Optional["GlobalStack"]:
|
||||
return super().getNextStack()
|
||||
|
|
@ -119,6 +50,29 @@ class ExtruderStack(CuraContainerStack):
|
|||
def getLoadingPriority(cls) -> int:
|
||||
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
|
||||
#
|
||||
# It will perform a few extra checks when trying to get properties.
|
||||
|
|
@ -187,11 +141,6 @@ class ExtruderStack(CuraContainerStack):
|
|||
if has_global_dependencies:
|
||||
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(
|
||||
name = "application/x-cura-extruderstack",
|
||||
|
|
|
|||
|
|
@ -125,21 +125,6 @@ class GlobalStack(CuraContainerStack):
|
|||
def setNextStack(self, next_stack: ContainerStack) -> None:
|
||||
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:
|
||||
|
||||
# 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
|
|
@ -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))
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import UM.Settings.Models.SettingVisibilityHandler
|
||||
|
||||
|
||||
class MaterialSettingsVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler.SettingVisibilityHandler):
|
||||
def __init__(self, parent = None, *args, **kwargs):
|
||||
super().__init__(parent = parent, *args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -4,26 +4,28 @@
|
|||
import os.path
|
||||
import zipfile
|
||||
|
||||
import numpy
|
||||
|
||||
import Savitar
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.Matrix import Matrix
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
from UM.Mesh.MeshReader import MeshReader
|
||||
from UM.Scene.GroupDecorator import GroupDecorator
|
||||
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
from UM.Application import Application
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.QualityManager import QualityManager
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
|
||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||
|
||||
MYPY = False
|
||||
|
||||
import Savitar
|
||||
import numpy
|
||||
|
||||
try:
|
||||
if not MYPY:
|
||||
import xml.etree.cElementTree as ET
|
||||
|
|
@ -77,7 +79,7 @@ class ThreeMFReader(MeshReader):
|
|||
self._object_count += 1
|
||||
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.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||
|
|
@ -120,8 +122,8 @@ class ThreeMFReader(MeshReader):
|
|||
um_node.callDecoration("setActiveExtruder", default_stack.getId())
|
||||
|
||||
# Get the definition & set it
|
||||
definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom())
|
||||
um_node.callDecoration("getStack").getTop().setDefinition(definition.getId())
|
||||
definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack)
|
||||
um_node.callDecoration("getStack").getTop().setDefinition(definition_id)
|
||||
|
||||
setting_container = um_node.callDecoration("getStack").getTop()
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ from cura.Settings.ExtruderManager import ExtruderManager
|
|||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from cura.Settings.CuraContainerStack import _ContainerIndexes
|
||||
from cura.QualityManager import QualityManager
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from configparser import ConfigParser
|
||||
|
|
@ -855,10 +854,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
if machine_extruder_count is not None:
|
||||
extruder_stacks_in_use = extruder_stacks[:machine_extruder_count]
|
||||
|
||||
available_quality = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_stack,
|
||||
extruder_stacks_in_use)
|
||||
quality_manager = CuraApplication.getInstance()._quality_manager
|
||||
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:
|
||||
has_not_supported = not available_quality
|
||||
has_not_supported = not available_quality_types
|
||||
|
||||
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.
|
||||
# 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".
|
||||
available_quality_types = [q.getMetaDataEntry("quality_type") for q in available_quality]
|
||||
|
||||
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,
|
||||
# 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()
|
||||
|
||||
# Actually change the active machine.
|
||||
Application.getInstance().setGlobalContainerStack(global_stack)
|
||||
|
||||
# Notify everything/one that is to notify about changes.
|
||||
global_stack.containersChanged.emit(global_stack.getTop())
|
||||
#
|
||||
# This is scheduled for later is because it depends on the Variant/Material/Qualitiy Managers to have the latest
|
||||
# data, but those managers will only update upon a container/container metadata changed signal. Because this
|
||||
# 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
|
||||
nodes = self._3mf_mesh_reader.read(file_name)
|
||||
|
|
@ -1100,6 +1101,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self.setWorkspaceName(base_file_name)
|
||||
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.
|
||||
# This function is used when the user chooses to resolve material conflicts by creating new ones.
|
||||
def _replaceStackMaterialWithNew(self, stack, old_new_material_dict):
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class ThreeMFWriter(MeshWriter):
|
|||
if not isinstance(um_node, SceneNode):
|
||||
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:
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
# Workaround to disable layer view processing if layer view is not active.
|
||||
self._layer_view_active = False
|
||||
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
|
||||
Application.getInstance().getBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged)
|
||||
Application.getInstance().getMultiBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged)
|
||||
self._onActiveViewChanged()
|
||||
self._stored_layer_data = []
|
||||
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
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||
Application.getInstance().getExtruderManager().extrudersAdded.connect(self._onGlobalStackChanged)
|
||||
self._onGlobalStackChanged()
|
||||
|
||||
Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished)
|
||||
|
|
@ -207,7 +206,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self._scene.gcode_dict = {}
|
||||
|
||||
# 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)
|
||||
Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
|
||||
num_objects = self._numObjects()
|
||||
|
|
@ -497,7 +496,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
node.getParent().removeChild(node)
|
||||
|
||||
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:
|
||||
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 )
|
||||
|
||||
# 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:
|
||||
self._startProcessSlicedLayersJob(active_build_plate)
|
||||
# self._onActiveViewChanged()
|
||||
|
|
@ -702,7 +701,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
application = Application.getInstance()
|
||||
view = application.getController().getActiveView()
|
||||
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.
|
||||
self._layer_view_active = True
|
||||
# There is data and we're not slicing at the moment
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ class ProcessGCodeLayerJob(Job):
|
|||
self._message = message
|
||||
|
||||
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.append(self._message.data.decode("utf-8", "replace"))
|
||||
|
|
|
|||
|
|
@ -437,7 +437,7 @@ class FlavorParser:
|
|||
scene_node.addDecorator(gcode_list_decorator)
|
||||
|
||||
# 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}
|
||||
Application.getInstance().getController().getScene().gcode_dict = gcode_dict
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class GCodeWriter(MeshWriter):
|
|||
Logger.log("e", "GCodeWriter does not support non-text mode.")
|
||||
return False
|
||||
|
||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
scene = Application.getInstance().getController().getScene()
|
||||
gcode_dict = getattr(scene, "gcode_dict")
|
||||
if not gcode_dict:
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ UM.TooltipArea
|
|||
if(checked)
|
||||
{
|
||||
addedSettingsModel.setVisible(model.key, checked);
|
||||
UM.ActiveTool.triggerAction("subscribeForSettingValidation", model.key)
|
||||
UM.ActiveTool.triggerActionWithData("subscribeForSettingValidation", model.key)
|
||||
}
|
||||
else
|
||||
{
|
||||
UM.ActiveTool.triggerAction("unsubscribeForSettingValidation", model.key)
|
||||
UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
|
||||
addedSettingsModel.setVisible(model.key, checked);
|
||||
}
|
||||
UM.ActiveTool.forceUpdate();
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ Item {
|
|||
|
||||
onClicked: {
|
||||
addedSettingsModel.setVisible(model.key, false)
|
||||
UM.ActiveTool.triggerAction("unsubscribeForSettingValidation", model.key)
|
||||
UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
|
||||
}
|
||||
|
||||
style: ButtonStyle
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class PostProcessingPlugin(QObject, Extension):
|
|||
return
|
||||
|
||||
# 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]
|
||||
if not gcode_list:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ class SimulationPass(RenderPass):
|
|||
self.bind()
|
||||
|
||||
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
|
||||
nozzle_node = None
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class SupportEraser(Tool):
|
|||
move_vector = Vector(0, 5, 0)
|
||||
node.setPosition(move_vector)
|
||||
|
||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
|
||||
node.addDecorator(SettingOverrideDecorator())
|
||||
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self.writeStarted.emit(self)
|
||||
|
||||
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]
|
||||
|
||||
if not gcode_list:
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self.writeStarted.emit(self)
|
||||
|
||||
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]
|
||||
|
||||
if not gcode_list:
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
Application.getInstance().getController().setActiveStage("MonitorStage")
|
||||
|
||||
# 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_list = gcode_dict[active_build_plate_id]
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,9 @@
|
|||
|
||||
import configparser #To parse preference files.
|
||||
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 cura.CuraApplication import CuraApplication
|
||||
|
||||
|
||||
# a list of all legacy "Not Supported" quality profiles
|
||||
_OLD_NOT_SUPPORTED_PROFILES = [
|
||||
|
|
@ -130,7 +125,6 @@ class VersionUpgrade30to31(VersionUpgrade):
|
|||
parser.write(output)
|
||||
return [filename], [output.getvalue()]
|
||||
|
||||
|
||||
## Upgrades a container stack from version 3.0 to 3.1.
|
||||
#
|
||||
# \param serialised The serialised form of a container stack.
|
||||
|
|
@ -172,71 +166,3 @@ class VersionUpgrade30to31(VersionUpgrade):
|
|||
output = io.StringIO()
|
||||
parser.write(output)
|
||||
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())
|
||||
|
|
|
|||
|
|
@ -48,18 +48,35 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
|
||||
## Overridden from InstanceContainer
|
||||
# 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()
|
||||
if registry.isReadOnly(self.getId()):
|
||||
return
|
||||
|
||||
super().setMetaDataEntry(key, value)
|
||||
# Prevent recursion
|
||||
if not apply_to_all:
|
||||
super().setMetaDataEntry(key, value)
|
||||
return
|
||||
|
||||
basefile = self.getMetaDataEntry("base_file", self.getId()) #if basefile is self.getId, this is a basefile.
|
||||
# Update all containers that share basefile
|
||||
for container in registry.findInstanceContainers(base_file = basefile):
|
||||
if container.getMetaDataEntry(key, None) != value: # Prevent recursion
|
||||
container.setMetaDataEntry(key, value)
|
||||
# Get the MaterialGroup
|
||||
material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
root_material_id = self.getMetaDataEntry("base_file") #if basefile is self.getId, this is a basefile.
|
||||
material_group = material_manager.getMaterialGroup(root_material_id)
|
||||
|
||||
# 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.
|
||||
# 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_nozzle_map = {}
|
||||
|
||||
variant_manager = CuraApplication.getInstance()._variant_manager
|
||||
|
||||
all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self.getId())
|
||||
for container in all_containers:
|
||||
definition_id = container.getDefinition().getId()
|
||||
|
|
@ -202,9 +221,10 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
if definition_id not in machine_nozzle_map:
|
||||
machine_nozzle_map[definition_id] = {}
|
||||
|
||||
variant = container.getMetaDataEntry("variant")
|
||||
if variant:
|
||||
machine_nozzle_map[definition_id][variant] = container
|
||||
variant_name = container.getMetaDataEntry("variant_name")
|
||||
if variant_name:
|
||||
machine_nozzle_map[definition_id][variant_name] = variant_manager.getVariantNode(definition_id,
|
||||
variant_name)
|
||||
continue
|
||||
|
||||
machine_container_map[definition_id] = container
|
||||
|
|
@ -236,16 +256,12 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
self._addSettingElement(builder, instance)
|
||||
|
||||
# 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():
|
||||
variant_containers = registry.findInstanceContainersMetadata(id = hotend.getMetaDataEntry("variant"))
|
||||
if not variant_containers:
|
||||
continue
|
||||
|
||||
for hotend_name, variant_node in machine_nozzle_map[definition_id].items():
|
||||
# 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 = hotend.getMetaDataEntry("compatible")
|
||||
compatible = variant_node.metadata.get("compatible")
|
||||
if compatible is not None:
|
||||
builder.start("setting", {"key": "hardware compatible"})
|
||||
if compatible:
|
||||
|
|
@ -254,7 +270,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
builder.data("no")
|
||||
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 the settings match that of the machine profile, just skip since we inherit the machine profile.
|
||||
continue
|
||||
|
|
@ -590,14 +606,10 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
if buildplate_id is None:
|
||||
continue
|
||||
|
||||
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(
|
||||
id = buildplate_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 = buildplate_id)
|
||||
|
||||
if not variant_containers:
|
||||
from cura.Machines.VariantManager import VariantType
|
||||
variant_manager = CuraApplication.getInstance().getVariantManager()
|
||||
variant_node = variant_manager.getVariantNode(machine_id, buildplate_id)
|
||||
if not variant_node:
|
||||
continue
|
||||
|
||||
buildplate_compatibility = machine_compatibility
|
||||
|
|
@ -618,16 +630,14 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
|
||||
hotends = machine.iterfind("./um:hotend", self.__namespaces)
|
||||
for hotend in hotends:
|
||||
hotend_id = hotend.get("id")
|
||||
if hotend_id is None:
|
||||
# The "id" field for hotends in material profiles are actually
|
||||
hotend_name = hotend.get("id")
|
||||
if hotend_name is None:
|
||||
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)
|
||||
|
||||
if not variant_containers:
|
||||
variant_manager = CuraApplication.getInstance().getVariantManager()
|
||||
variant_node = variant_manager.getVariantNode(machine_id, hotend_name)
|
||||
if not variant_node:
|
||||
continue
|
||||
|
||||
hotend_compatibility = machine_compatibility
|
||||
|
|
@ -643,20 +653,20 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
else:
|
||||
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
|
||||
if ContainerRegistry.getInstance().isLoaded(new_hotend_id):
|
||||
new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_id)[0]
|
||||
if ContainerRegistry.getInstance().isLoaded(new_hotend_specific_material_id):
|
||||
new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_specific_material_id)[0]
|
||||
is_new_material = False
|
||||
else:
|
||||
new_hotend_material = XmlMaterialProfile(new_hotend_id)
|
||||
new_hotend_material = XmlMaterialProfile(new_hotend_specific_material_id)
|
||||
is_new_material = True
|
||||
|
||||
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()["variant"] = variant_containers[0]["id"]
|
||||
new_hotend_material.getMetaData()["variant_name"] = hotend_name
|
||||
new_hotend_material.setDefinition(machine_id)
|
||||
# Don't use setMetadata, as that overrides it for all materials with same base file
|
||||
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
|
||||
|
|
@ -833,39 +843,30 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_map["buildplate_recommended"]
|
||||
|
||||
for hotend in machine.iterfind("./um:hotend", cls.__namespaces):
|
||||
hotend_id = hotend.get("id")
|
||||
if hotend_id is None:
|
||||
hotend_name = hotend.get("id")
|
||||
if hotend_name is None:
|
||||
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
|
||||
for entry in hotend.iterfind("./um:setting", cls.__namespaces):
|
||||
key = entry.get("key")
|
||||
if key == "hardware compatible":
|
||||
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
|
||||
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_id)
|
||||
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_specific_material_id)
|
||||
if found_materials:
|
||||
new_hotend_material_metadata = found_materials[0]
|
||||
else:
|
||||
new_hotend_material_metadata = {}
|
||||
|
||||
new_hotend_material_metadata.update(base_metadata)
|
||||
if variant_containers:
|
||||
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["variant_name"] = hotend_name
|
||||
new_hotend_material_metadata["compatible"] = hotend_compatibility
|
||||
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
|
||||
if 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 }
|
||||
def _tag_without_namespace(element):
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"platform": "anycubic_i3_mega_platform.stl",
|
||||
"has_materials": false,
|
||||
"has_machine_quality": true,
|
||||
"preferred_quality": "*normal*"
|
||||
"preferred_quality_type": "normal"
|
||||
},
|
||||
|
||||
"overrides":
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"platform": "builder_premium_platform.stl",
|
||||
"platform_offset": [-126, -36, 117],
|
||||
"has_machine_quality": true,
|
||||
"preferred_quality": "*Normal*",
|
||||
"preferred_quality_type": "normal",
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "builder_premium_large_rear",
|
||||
|
|
@ -20,8 +20,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Builder Premium Large" },
|
||||
"machine_heated_bed": { "default_value": true },
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"platform": "builder_premium_platform.stl",
|
||||
"platform_offset": [-126, -36, 117],
|
||||
"has_machine_quality": true,
|
||||
"preferred_quality": "*Normal*",
|
||||
"preferred_quality_type": "normal",
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "builder_premium_medium_rear",
|
||||
|
|
@ -20,8 +20,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Builder Premium Medium" },
|
||||
"machine_heated_bed": { "default_value": true },
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"platform": "builder_premium_platform.stl",
|
||||
"platform_offset": [-126, -36, 117],
|
||||
"has_machine_quality": true,
|
||||
"preferred_quality": "*Normal*",
|
||||
"preferred_quality_type": "normal",
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "builder_premium_small_rear",
|
||||
|
|
@ -19,8 +19,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Builder Premium Small" },
|
||||
"machine_heated_bed": { "default_value": true },
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@
|
|||
"has_variants": true,
|
||||
|
||||
"variants_name": "Tool",
|
||||
"preferred_variant": "*0.8*",
|
||||
"preferred_material": "*pla*",
|
||||
"preferred_quality": "*normal*",
|
||||
"preferred_variant_name": "0.8 mm",
|
||||
"preferred_material": "generic_pla",
|
||||
"preferred_quality_type": "normal",
|
||||
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"author": "Michael Wildermuth",
|
||||
"manufacturer": "Creality3D",
|
||||
"file_formats": "text/x-gcode",
|
||||
"preferred_quality": "*Draft*"
|
||||
"preferred_quality_type": "draft"
|
||||
},
|
||||
"overrides": {
|
||||
"machine_width": {
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
"has_machine_quality": true,
|
||||
"has_variants": true,
|
||||
"variants_name": "Head",
|
||||
"preferred_variant": "*lite04*",
|
||||
"preferred_material": "*fabtotum_pla*",
|
||||
"preferred_variant_name": "Lite 0.4 mm",
|
||||
"preferred_material": "fabtotum_pla",
|
||||
"supports_usb_connection": false
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g",
|
||||
"visible": false,
|
||||
"has_materials": true,
|
||||
"preferred_material": "*generic_pla*",
|
||||
"preferred_quality": "*normal*",
|
||||
"preferred_material": "generic_pla",
|
||||
"preferred_quality_type": "normal",
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "fdmextruder"
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@
|
|||
"category": "Other",
|
||||
"file_formats": "text/x-gcode",
|
||||
"platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl",
|
||||
"has_machine_quality": true,
|
||||
"has_variants": true,
|
||||
"variants_name": "Hotend",
|
||||
"preferred_variant": "*0.5mm E3D (Default)*"
|
||||
|
||||
|
||||
"preferred_variant_name": "0.5mm E3D (Default)"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@
|
|||
"file_formats": "text/x-gcode",
|
||||
"platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl",
|
||||
"has_variants": true,
|
||||
"has_machine_quality": true,
|
||||
"variants_name": "Hotend",
|
||||
"preferred_variant": "*0.5mm E3D (Default)*",
|
||||
"preferred_variant_name": "0.5mm E3D (Default)",
|
||||
"machine_extruder_trains": {
|
||||
"0": "gmax15plus_dual_extruder_0",
|
||||
"1": "gmax15plus_dual_extruder_1"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@
|
|||
"platform": "imade3d_jellybox_platform.stl",
|
||||
"platform_offset": [ 0, -0.3, 0],
|
||||
"file_formats": "text/x-gcode",
|
||||
"preferred_variant": "*0.4*",
|
||||
"preferred_material": "*generic_pla*",
|
||||
"preferred_quality": "*fast*",
|
||||
"preferred_variant_name": "0.4 mm",
|
||||
"preferred_quality_type": "fast",
|
||||
"has_materials": true,
|
||||
"has_variants": true,
|
||||
"has_machine_materials": true,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"platform": "malyan_m200_platform.stl",
|
||||
"has_machine_quality": true,
|
||||
"has_materials": true,
|
||||
"preferred_quality": "*normal*",
|
||||
"preferred_quality_type": "normal",
|
||||
"supports_usb_connection": true,
|
||||
"visible": true,
|
||||
"first_start_actions": ["MachineSettingsAction"],
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"file_formats": "text/x-gcode",
|
||||
"has_machine_quality": true,
|
||||
"has_materials": true,
|
||||
"preferred_quality": "*normal*",
|
||||
"preferred_quality_type": "normal",
|
||||
"visible": true
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"has_materials": false,
|
||||
"has_machine_quality": true,
|
||||
"platform": "tevo_blackwidow.stl",
|
||||
"preferred_quality": "*normal*"
|
||||
"preferred_quality_type": "normal"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"file_formats": "text/x-gcode",
|
||||
"platform": "ultimaker2_platform.obj",
|
||||
"platform_texture": "Ultimaker2Plusbackplate.png",
|
||||
"preferred_variant": "*0.4*",
|
||||
"preferred_variant_name": "0.4 mm",
|
||||
"has_variants": true,
|
||||
"has_materials": true,
|
||||
"has_machine_materials": true,
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
"has_machine_materials": true,
|
||||
"has_variant_materials": true,
|
||||
"has_variants": true,
|
||||
"preferred_variant": "*aa04*",
|
||||
"preferred_quality": "*Normal*",
|
||||
"preferred_variant_name": "AA 0.4",
|
||||
"preferred_quality_type": "normal",
|
||||
"variants_name": "Print core",
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
"has_variant_materials": true,
|
||||
"has_materials": true,
|
||||
"has_variants": true,
|
||||
"preferred_variant": "*aa04*",
|
||||
"preferred_variant_name": "AA 0.4",
|
||||
"variants_name": "Print core",
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ Item
|
|||
Action
|
||||
{
|
||||
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");
|
||||
onTriggered: Cura.ContainerManager.updateQualityChanges();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,24 +190,25 @@ UM.MainWindow
|
|||
model: Cura.ExtrudersModel { simpleNames: true }
|
||||
Menu {
|
||||
title: model.name
|
||||
visible: machineExtruderCount.properties.value > 1
|
||||
|
||||
NozzleMenu { title: Cura.MachineManager.activeDefinitionVariantsName; visible: Cura.MachineManager.hasVariants; 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)
|
||||
onObjectRemoved: settingsMenu.removeItem(object)
|
||||
}
|
||||
|
||||
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 }
|
||||
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 }
|
||||
ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); }
|
||||
|
||||
MenuSeparator { }
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import QtQuick.Dialogs 1.2
|
|||
import QtQuick.Window 2.1
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.1 as Cura
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
|
@ -12,28 +12,22 @@ Menu
|
|||
id: menu
|
||||
title: "Build plate"
|
||||
|
||||
property Cura.BuildPlateModel buildPlateModel: CuraApplication.getBuildPlateModel()
|
||||
|
||||
Instantiator
|
||||
{
|
||||
id: buildplateInstantiator
|
||||
model: UM.InstanceContainersModel
|
||||
{
|
||||
filter:
|
||||
{
|
||||
"type": "variant",
|
||||
"hardware_type": "buildplate",
|
||||
"definition": Cura.MachineManager.activeDefinitionId //Only show variants of this machine
|
||||
}
|
||||
}
|
||||
model: menu.buildPlateModel
|
||||
|
||||
MenuItem {
|
||||
text: model.name
|
||||
checkable: true
|
||||
checked: model.id == Cura.MachineManager.globalVariantId
|
||||
checked: model.name == Cura.MachineManager.globalVariantName
|
||||
exclusiveGroup: group
|
||||
onTriggered:
|
||||
{
|
||||
Cura.MachineManager.setActiveVariantBuildplate(model.id);
|
||||
onTriggered: {
|
||||
Cura.MachineManager.setGlobalVariant(model.container_node);
|
||||
}
|
||||
}
|
||||
|
||||
onObjectAdded: menu.insertItem(index, object)
|
||||
onObjectRemoved: menu.removeItem(object)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import QtQuick.Dialogs 1.2
|
|||
import QtQuick.Window 2.1
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.2 as Cura
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Menu
|
||||
{
|
||||
|
|
@ -15,6 +15,8 @@ Menu
|
|||
|
||||
property bool shouldShowExtruders: machineExtruderCount.properties.value > 1;
|
||||
|
||||
property Cura.MultiBuildPlateModel multiBuildPlateModel: CuraApplication.getMultiBuildPlateModel()
|
||||
|
||||
// Selection-related actions.
|
||||
MenuItem { action: Cura.Actions.centerSelection; }
|
||||
MenuItem { action: Cura.Actions.deleteSelection; }
|
||||
|
|
@ -45,13 +47,13 @@ Menu
|
|||
|
||||
Instantiator
|
||||
{
|
||||
model: Cura.BuildPlateModel
|
||||
model: base.multiBuildPlateModel
|
||||
MenuItem {
|
||||
enabled: UM.Selection.hasSelection
|
||||
text: Cura.BuildPlateModel.getItem(index).name;
|
||||
onTriggered: CuraActions.setBuildPlateForSelection(Cura.BuildPlateModel.getItem(index).buildPlateNumber);
|
||||
text: base.multiBuildPlateModel.getItem(index).name;
|
||||
onTriggered: CuraActions.setBuildPlateForSelection(base.multiBuildPlateModel.getItem(index).buildPlateNumber);
|
||||
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")
|
||||
}
|
||||
onObjectAdded: base.insertItem(index, object);
|
||||
|
|
@ -62,7 +64,7 @@ Menu
|
|||
enabled: UM.Selection.hasSelection
|
||||
text: "New build plate";
|
||||
onTriggered: {
|
||||
CuraActions.setBuildPlateForSelection(Cura.BuildPlateModel.maxBuildPlate + 1);
|
||||
CuraActions.setBuildPlateForSelection(base.multiBuildPlateModel.maxBuildPlate + 1);
|
||||
checked = false;
|
||||
}
|
||||
checkable: true
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
|
@ -13,64 +13,6 @@ Menu
|
|||
title: "Material"
|
||||
|
||||
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
|
||||
{
|
||||
|
|
@ -79,16 +21,11 @@ Menu
|
|||
{
|
||||
text: model.name
|
||||
checkable: true
|
||||
checked: model.id == Cura.MachineManager.allActiveMaterialIds[Cura.ExtruderManager.extruderIds[extruderIndex]]
|
||||
checked: model.root_material_id == Cura.MachineManager.currentRootMaterialId[extruderIndex]
|
||||
exclusiveGroup: group
|
||||
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;
|
||||
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex);
|
||||
Cura.MachineManager.setActiveMaterial(model.id);
|
||||
Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
|
||||
Cura.MachineManager.setMaterial(extruderIndex, model.container_node);
|
||||
}
|
||||
}
|
||||
onObjectAdded: menu.insertItem(index, object)
|
||||
|
|
@ -126,12 +63,8 @@ Menu
|
|||
exclusiveGroup: group
|
||||
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;
|
||||
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex);
|
||||
Cura.MachineManager.setActiveMaterial(model.id);
|
||||
Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
|
||||
Cura.MachineManager.setMaterial(activeExtruderIndex, model.container_node);
|
||||
}
|
||||
}
|
||||
onObjectAdded: brandMaterialsMenu.insertItem(index, object)
|
||||
|
|
@ -146,24 +79,16 @@ Menu
|
|||
onObjectRemoved: menu.removeItem(object)
|
||||
}
|
||||
|
||||
ListModel
|
||||
Cura.GenericMaterialsModel
|
||||
{
|
||||
id: genericMaterialsModel
|
||||
Component.onCompleted: populateMenuModels()
|
||||
extruderPosition: menu.extruderIndex
|
||||
}
|
||||
|
||||
ListModel
|
||||
Cura.BrandMaterialsModel
|
||||
{
|
||||
id: brandModel
|
||||
}
|
||||
|
||||
//: Model used to populate the brandModel
|
||||
Cura.MaterialsModel
|
||||
{
|
||||
id: materialsModel
|
||||
filter: materialFilter()
|
||||
onModelReset: populateMenuModels()
|
||||
onDataChanged: populateMenuModels()
|
||||
extruderPosition: menu.extruderIndex
|
||||
}
|
||||
|
||||
ExclusiveGroup { id: group }
|
||||
|
|
@ -171,80 +96,4 @@ Menu
|
|||
MenuSeparator { }
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
|
@ -13,89 +13,31 @@ Menu
|
|||
title: "Nozzle"
|
||||
|
||||
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
|
||||
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
|
||||
id: nozzleModel
|
||||
}
|
||||
|
||||
Instantiator
|
||||
{
|
||||
id: nozzleInstantiator
|
||||
model: UM.InstanceContainersModel
|
||||
{
|
||||
filter:
|
||||
{
|
||||
var filter_dict =
|
||||
{
|
||||
"type": "variant",
|
||||
"definition": Cura.MachineManager.activeQualityDefinitionId //Only show variants of this machine
|
||||
}
|
||||
if (Cura.MachineManager.hasVariantBuildplates)
|
||||
{
|
||||
filter_dict["hardware_type"] = "nozzle"
|
||||
}
|
||||
model: nozzleModel
|
||||
|
||||
return filter_dict
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
text: model.name
|
||||
MenuItem
|
||||
{
|
||||
text: model.hotend_name
|
||||
checkable: true
|
||||
checked: model.id == Cura.MachineManager.allActiveVariantIds[Cura.ExtruderManager.extruderIds[extruderIndex]]
|
||||
checked: {
|
||||
return Cura.MachineManager.activeVariantNames[extruderIndex] == model.hotend_name
|
||||
}
|
||||
exclusiveGroup: group
|
||||
onTriggered:
|
||||
{
|
||||
var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex;
|
||||
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex);
|
||||
Cura.MachineManager.setActiveVariant(model.id);
|
||||
Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
|
||||
onTriggered: {
|
||||
Cura.MachineManager.setVariantGroup(menu.extruderIndex, model.container_node);
|
||||
}
|
||||
}
|
||||
onObjectAdded: menu.insertItem(index, object)
|
||||
onObjectRemoved: menu.removeItem(object)
|
||||
|
||||
onObjectAdded: menu.insertItem(index, object);
|
||||
onObjectRemoved: menu.removeItem(object);
|
||||
}
|
||||
|
||||
ExclusiveGroup { id: group }
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
|
@ -13,15 +13,17 @@ Menu
|
|||
|
||||
Instantiator
|
||||
{
|
||||
model: Cura.ProfilesModel
|
||||
model: Cura.QualityProfilesDropDownMenuModel
|
||||
|
||||
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
|
||||
checked: Cura.MachineManager.activeQualityId == model.id
|
||||
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
|
||||
exclusiveGroup: group
|
||||
onTriggered: Cura.MachineManager.setActiveQuality(model.id)
|
||||
onTriggered: {
|
||||
Cura.MachineManager.setQualityGroup(model.quality_group)
|
||||
}
|
||||
visible: model.available
|
||||
}
|
||||
|
||||
|
|
@ -32,24 +34,27 @@ Menu
|
|||
MenuSeparator
|
||||
{
|
||||
id: customSeparator
|
||||
visible: Cura.UserProfilesModel.rowCount > 0
|
||||
visible: Cura.CustomQualityProfilesDropDownMenuModel.rowCount > 0
|
||||
}
|
||||
|
||||
Instantiator
|
||||
{
|
||||
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
|
||||
{
|
||||
text: model.name
|
||||
checkable: true
|
||||
checked: Cura.MachineManager.activeQualityChangesId == model.id
|
||||
checkable: model.available
|
||||
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
|
||||
exclusiveGroup: group
|
||||
onTriggered: Cura.MachineManager.setActiveQuality(model.id)
|
||||
onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group)
|
||||
}
|
||||
|
||||
onObjectAdded:
|
||||
|
|
@ -73,23 +78,4 @@ Menu
|
|||
MenuItem { action: Cura.Actions.resetProfile }
|
||||
MenuSeparator { }
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import QtQuick 2.2
|
|||
import QtQuick.Controls 1.1
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.2 as Cura
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Menu
|
||||
{
|
||||
|
|
@ -13,6 +13,8 @@ Menu
|
|||
id: base
|
||||
enabled: !PrintInformation.preSliced
|
||||
|
||||
property Cura.MultiBuildPlateModel multiBuildPlateModel: CuraApplication.getMultiBuildPlateModel()
|
||||
|
||||
// main views
|
||||
Instantiator
|
||||
{
|
||||
|
|
@ -53,12 +55,12 @@ Menu
|
|||
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
|
||||
Instantiator
|
||||
{
|
||||
model: Cura.BuildPlateModel
|
||||
model: base.multiBuildPlateModel
|
||||
MenuItem {
|
||||
text: Cura.BuildPlateModel.getItem(index).name;
|
||||
onTriggered: Cura.SceneController.setActiveBuildPlate(Cura.BuildPlateModel.getItem(index).buildPlateNumber);
|
||||
text: base.multiBuildPlateModel.getItem(index).name;
|
||||
onTriggered: Cura.SceneController.setActiveBuildPlate(base.multiBuildPlateModel.getItem(index).buildPlateNumber);
|
||||
checkable: true;
|
||||
checked: Cura.BuildPlateModel.getItem(index).buildPlateNumber == Cura.BuildPlateModel.activeBuildPlate;
|
||||
checked: base.multiBuildPlateModel.getItem(index).buildPlateNumber == base.multiBuildPlateModel.activeBuildPlate;
|
||||
exclusiveGroup: buildPlateGroup;
|
||||
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import QtQuick.Layouts 1.1
|
|||
import QtQuick.Dialogs 1.1
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.2 as Cura
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
import "Menus"
|
||||
|
||||
|
|
@ -33,6 +33,8 @@ Rectangle
|
|||
|
||||
property bool collapsed: true;
|
||||
|
||||
property Cura.MultiBuildPlateModel multiBuildPlateModel: CuraApplication.getMultiBuildPlateModel()
|
||||
|
||||
SystemPalette { id: palette }
|
||||
|
||||
Button {
|
||||
|
|
@ -67,7 +69,7 @@ Rectangle
|
|||
Rectangle
|
||||
{
|
||||
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
|
||||
Label
|
||||
{
|
||||
|
|
@ -75,8 +77,8 @@ Rectangle
|
|||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
width: parent.width - 2 * UM.Theme.getSize("default_margin").width - 30
|
||||
text: Cura.BuildPlateModel.getItem(index) ? Cura.BuildPlateModel.getItem(index).name : "";
|
||||
color: Cura.BuildPlateModel.activeBuildPlate == index ? palette.highlightedText : palette.text
|
||||
text: multiBuildPlateModel.getItem(index) ? multiBuildPlateModel.getItem(index).name : "";
|
||||
color: multiBuildPlateModel.activeBuildPlate == index ? palette.highlightedText : palette.text
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
|
|
@ -118,13 +120,12 @@ Rectangle
|
|||
ListView
|
||||
{
|
||||
id: buildPlateListView
|
||||
model: Cura.BuildPlateModel
|
||||
model: multiBuildPlateModel
|
||||
width: parent.width
|
||||
delegate: buildPlateDelegate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: objectDelegate
|
||||
Rectangle
|
||||
|
|
@ -200,7 +201,6 @@ Rectangle
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
CheckBox
|
||||
{
|
||||
id: filterBuildPlateCheckbox
|
||||
|
|
@ -260,6 +260,4 @@ Rectangle
|
|||
}
|
||||
action: Cura.Actions.arrangeAll;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
// Copyright (c) 2016 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Window 2.1
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
UM.ManagementPage
|
||||
{
|
||||
id: base;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick.Controls 1.3
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2
|
||||
|
||||
import UM 1.2 as UM
|
||||
|
|
@ -12,7 +12,10 @@ TabView
|
|||
{
|
||||
id: base
|
||||
|
||||
property QtObject properties;
|
||||
property QtObject materialManager: CuraApplication.getMaterialManager()
|
||||
|
||||
property QtObject properties
|
||||
property var currentMaterialNode: null
|
||||
|
||||
property bool editingEnabled: false;
|
||||
property string currency: UM.Preferences.getValue("cura/currency") ? UM.Preferences.getValue("cura/currency") : "€"
|
||||
|
|
@ -27,18 +30,23 @@ TabView
|
|||
property bool reevaluateLinkedMaterials: false
|
||||
property string linkedMaterialNames:
|
||||
{
|
||||
if (reevaluateLinkedMaterials)
|
||||
{
|
||||
if (reevaluateLinkedMaterials) {
|
||||
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 ""
|
||||
}
|
||||
var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.containerId);
|
||||
return linkedMaterials.join(", ");
|
||||
}
|
||||
|
||||
function getApproximateDiameter(diameter) {
|
||||
return Math.round(diameter);
|
||||
}
|
||||
|
||||
Tab
|
||||
{
|
||||
title: catalog.i18nc("@title", "Information")
|
||||
|
|
@ -65,6 +73,34 @@ TabView
|
|||
width: base.width
|
||||
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") }
|
||||
ReadOnlyTextField
|
||||
{
|
||||
|
|
@ -80,18 +116,18 @@ TabView
|
|||
{
|
||||
id: textField;
|
||||
width: scrollView.columnWidth;
|
||||
text: properties.supplier;
|
||||
text: properties.brand;
|
||||
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") }
|
||||
ReadOnlyTextField
|
||||
{
|
||||
width: scrollView.columnWidth;
|
||||
text: properties.material_type;
|
||||
text: properties.material;
|
||||
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") }
|
||||
|
|
@ -172,14 +208,20 @@ TabView
|
|||
// which derive from the same base_file
|
||||
var old_diameter = Cura.ContainerManager.getContainerProperty(base.containerId, "material_diameter", "value").toString();
|
||||
var old_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter");
|
||||
base.setMetaDataEntry("approximate_diameter", old_approximate_diameter, Math.round(value).toString());
|
||||
base.setMetaDataEntry("properties/diameter", properties.diameter, value);
|
||||
var new_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter");
|
||||
if (Cura.MachineManager.filterMaterialsByMachine && new_approximate_diameter != Cura.MachineManager.activeMachine.approximateMaterialDiameter)
|
||||
var new_approximate_diameter = getApproximateDiameter(value);
|
||||
if (Cura.MachineManager.filterMaterialsByMachine && new_approximate_diameter != Cura.ExtruderManager.getActiveExtruderStack().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);
|
||||
base.setMetaDataEntry("approximate_diameter", old_approximate_diameter, getApproximateDiameter(value).toString());
|
||||
base.setMetaDataEntry("properties/diameter", properties.diameter, value);
|
||||
}
|
||||
Cura.ContainerManager.setContainerProperty(base.containerId, "material_diameter", "value", value);
|
||||
}
|
||||
onValueChanged: updateCostPerMeter()
|
||||
}
|
||||
|
|
@ -251,7 +293,7 @@ TabView
|
|||
visible: base.linkedMaterialNames != ""
|
||||
onClicked:
|
||||
{
|
||||
Cura.ContainerManager.unlinkMaterial(base.containerId)
|
||||
Cura.ContainerManager.unlinkMaterial(base.currentMaterialNode)
|
||||
base.reevaluateLinkedMaterials = true
|
||||
}
|
||||
}
|
||||
|
|
@ -357,8 +399,20 @@ TabView
|
|||
onEditingFinished: materialPropertyProvider.setPropertyValue("value", value)
|
||||
}
|
||||
|
||||
UM.ContainerPropertyProvider { id: materialPropertyProvider; containerId: base.containerId; watchedProperties: [ "value" ]; key: model.key }
|
||||
UM.ContainerPropertyProvider { id: machinePropertyProvider; containerId: Cura.MachineManager.activeDefinitionId; watchedProperties: [ "value" ]; key: model.key }
|
||||
UM.ContainerPropertyProvider
|
||||
{
|
||||
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.
|
||||
function setMetaDataEntry(entry_name, 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
|
||||
// When the entry_name is something like properties/diameter, we take the last part of the entry_name
|
||||
var list = entry_name.split("/")
|
||||
|
|
@ -442,26 +496,25 @@ TabView
|
|||
|
||||
// update the display name of the material
|
||||
function updateMaterialDisplayName (old_name, new_name) {
|
||||
|
||||
// don't change when new name is the same
|
||||
if (old_name == new_name) {
|
||||
return
|
||||
}
|
||||
|
||||
// update the values
|
||||
Cura.ContainerManager.setContainerName(base.containerId, new_name)
|
||||
base.materialManager.setMaterialName(base.currentMaterialNode, new_name)
|
||||
materialProperties.name = new_name
|
||||
}
|
||||
|
||||
// update the type of the material
|
||||
function updateMaterialType (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
|
||||
function updateMaterialSupplier (old_supplier, new_supplier) {
|
||||
base.setMetaDataEntry("brand", old_supplier, new_supplier)
|
||||
materialProperties.supplier = new_supplier
|
||||
// update the brand of the material
|
||||
function updateMaterialBrand (old_brand, new_brand) {
|
||||
base.setMetaDataEntry("brand", old_brand, new_brand)
|
||||
materialProperties.brand = new_brand
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,412 +1,526 @@
|
|||
//Copyright (c) 2017 Ultimaker B.V.
|
||||
//Cura is released under the terms of the LGPLv3 or higher.
|
||||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Uranium is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Dialogs 1.2
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
UM.ManagementPage
|
||||
|
||||
Item
|
||||
{
|
||||
id: base;
|
||||
id: base
|
||||
|
||||
title: catalog.i18nc("@title:tab", "Materials");
|
||||
property QtObject materialManager: CuraApplication.getMaterialManager()
|
||||
property var resetEnabled: false // Keep PreferencesDialog happy
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
// Workaround to make sure all of the items are visible
|
||||
objectList.positionViewAtBeginning();
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
||||
Cura.MaterialManagementModel {
|
||||
id: materialsModel
|
||||
}
|
||||
|
||||
model: Cura.MaterialsModel
|
||||
{
|
||||
filter:
|
||||
{
|
||||
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
|
||||
Label {
|
||||
id: titleLabel
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: 5 * screenScaleFactor
|
||||
}
|
||||
|
||||
sectionProperty: "brand"
|
||||
font.pointSize: 18
|
||||
text: catalog.i18nc("@title:tab", "Materials")
|
||||
}
|
||||
|
||||
delegate: Rectangle
|
||||
{
|
||||
width: objectList.width;
|
||||
height: childrenRect.height;
|
||||
color: isCurrentItem ? palette.highlight : index % 2 ? palette.base : palette.alternateBase
|
||||
property bool isCurrentItem: ListView.isCurrentItem
|
||||
property var hasCurrentItem: materialListView.currentItem != null
|
||||
|
||||
Row
|
||||
property var currentItem:
|
||||
{ // is soon to be overwritten
|
||||
var current_index = materialListView.currentIndex;
|
||||
return materialsModel.getItem(current_index);
|
||||
}
|
||||
|
||||
property var isCurrentItemActivated:
|
||||
{
|
||||
const extruder_position = Cura.ExtruderManager.activeExtruderIndex;
|
||||
const root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position];
|
||||
return base.currentItem.root_material_id == root_material_id;
|
||||
}
|
||||
|
||||
Row // Button Row
|
||||
{
|
||||
id: buttonRow
|
||||
anchors
|
||||
{
|
||||
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
|
||||
Rectangle
|
||||
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:
|
||||
{
|
||||
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
|
||||
{
|
||||
width: Math.round((parent.width * 0.3))
|
||||
text: model.metadata.material
|
||||
elide: Text.ElideRight
|
||||
font.italic: model.id == activeId
|
||||
color: isCurrentItem ? palette.highlightedText : palette.text;
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: (model.name != model.metadata.material) ? model.name : ""
|
||||
elide: Text.ElideRight
|
||||
font.italic: model.id == activeId
|
||||
color: isCurrentItem ? palette.highlightedText : palette.text;
|
||||
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:
|
||||
{
|
||||
forceActiveFocus();
|
||||
if(!parent.ListView.isCurrentItem)
|
||||
{
|
||||
parent.ListView.view.currentIndex = index;
|
||||
base.itemActivated();
|
||||
}
|
||||
base.newRootMaterialIdToSwitchTo = base.materialManager.createMaterial();
|
||||
base.toActivateNewMaterial = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activeId: Cura.MachineManager.activeMaterialId
|
||||
activeIndex: getIndexById(activeId)
|
||||
function getIndexById(material_id)
|
||||
{
|
||||
for(var i = 0; i < model.rowCount(); i++) {
|
||||
if (model.getItem(i).id == material_id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
scrollviewCaption:
|
||||
{
|
||||
var caption = catalog.i18nc("@action:label", "Printer") + ": " + Cura.MachineManager.activeMachineName;
|
||||
if (Cura.MachineManager.hasVariants)
|
||||
{
|
||||
caption += ", " + Cura.MachineManager.activeDefinitionVariantsName + ": " + Cura.MachineManager.activeVariantName;
|
||||
}
|
||||
return caption;
|
||||
}
|
||||
detailsVisible: true
|
||||
|
||||
section.property: "section"
|
||||
section.delegate: Label
|
||||
{
|
||||
text: section
|
||||
font.bold: true
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: UM.Theme.getSize("default_lining").width;
|
||||
}
|
||||
|
||||
buttons: [
|
||||
|
||||
// Activate button
|
||||
Button {
|
||||
text: catalog.i18nc("@action:button", "Activate")
|
||||
iconName: "list-activate";
|
||||
enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId && Cura.MachineManager.hasMaterials
|
||||
onClicked: {
|
||||
forceActiveFocus()
|
||||
Cura.MachineManager.setActiveMaterial(base.currentItem.id)
|
||||
currentItem = base.model.getItem(base.objectList.currentIndex) // Refresh the current item.
|
||||
}
|
||||
},
|
||||
|
||||
// Create button
|
||||
Button {
|
||||
text: catalog.i18nc("@action:button", "Create")
|
||||
iconName: "list-add"
|
||||
onClicked: {
|
||||
forceActiveFocus()
|
||||
Cura.ContainerManager.createMaterial()
|
||||
}
|
||||
},
|
||||
|
||||
// Duplicate button
|
||||
Button {
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Duplicate");
|
||||
iconName: "list-add";
|
||||
enabled: base.currentItem != null
|
||||
onClicked: {
|
||||
forceActiveFocus()
|
||||
Cura.ContainerManager.duplicateOriginalMaterial(base.currentItem.id)
|
||||
iconName: "list-add"
|
||||
enabled: base.hasCurrentItem
|
||||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
base.newRootMaterialIdToSwitchTo = base.materialManager.duplicateMaterial(base.currentItem.container_node);
|
||||
base.toActivateNewMaterial = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Remove button
|
||||
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()
|
||||
enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated
|
||||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
confirmRemoveMaterialDialog.open();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Import button
|
||||
Button {
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Import")
|
||||
iconName: "document-import"
|
||||
onClicked: {
|
||||
forceActiveFocus()
|
||||
importDialog.open()
|
||||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
importMaterialDialog.open();
|
||||
}
|
||||
visible: true
|
||||
},
|
||||
}
|
||||
|
||||
// Export button
|
||||
Button {
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Export")
|
||||
iconName: "document-export"
|
||||
onClicked: {
|
||||
forceActiveFocus()
|
||||
exportDialog.open()
|
||||
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 {
|
||||
visible: base.currentItem != null
|
||||
anchors.fill: parent
|
||||
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;
|
||||
if (Cura.MachineManager.hasVariants)
|
||||
{
|
||||
caption += ", " + Cura.MachineManager.activeDefinitionVariantsName + ": " + Cura.MachineManager.activeVariantName;
|
||||
}
|
||||
return caption;
|
||||
}
|
||||
width: materialScrollView.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
ScrollView
|
||||
{
|
||||
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
|
||||
font.bold: true
|
||||
color: palette.text
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle
|
||||
{
|
||||
width: materialScrollView.width
|
||||
height: childrenRect.height
|
||||
color: ListView.isCurrentItem ? palette.highlight : (model.index % 2) ? palette.base : palette.alternateBase
|
||||
|
||||
Row
|
||||
{
|
||||
id: materialRow
|
||||
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
|
||||
|
||||
property bool isItemActivated:
|
||||
{
|
||||
const extruder_position = Cura.ExtruderManager.activeExtruderIndex;
|
||||
const root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position];
|
||||
return model.root_material_id == root_material_id;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
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
|
||||
{
|
||||
id: profileName
|
||||
id: detailsPanel
|
||||
|
||||
width: parent.width;
|
||||
height: childrenRect.height
|
||||
|
||||
Label { text: materialProperties.name; font: UM.Theme.getFont("large"); }
|
||||
}
|
||||
|
||||
MaterialView
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: profileName.bottom
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors {
|
||||
left: materialScrollView.right
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
editingEnabled: base.currentItem != null && !base.currentItem.readOnly
|
||||
|
||||
properties: materialProperties
|
||||
containerId: base.currentItem != null ? base.currentItem.id : ""
|
||||
|
||||
property alias pane: base
|
||||
}
|
||||
|
||||
QtObject
|
||||
{
|
||||
id: materialProperties
|
||||
|
||||
property string guid: "00000000-0000-0000-0000-000000000000"
|
||||
property string name: "Unknown";
|
||||
property string profile_type: "Unknown";
|
||||
property string supplier: "Unknown";
|
||||
property string material_type: "Unknown";
|
||||
|
||||
property string color_name: "Yellow";
|
||||
property color color_code: "yellow";
|
||||
|
||||
property real density: 0.0;
|
||||
property real diameter: 0.0;
|
||||
property string approximate_diameter: "0";
|
||||
|
||||
property real spool_cost: 0.0;
|
||||
property real spool_weight: 0.0;
|
||||
property real spool_length: 0.0;
|
||||
property real cost_per_meter: 0.0;
|
||||
|
||||
property string description: "";
|
||||
property string adhesion_info: "";
|
||||
}
|
||||
|
||||
UM.ConfirmRemoveDialog
|
||||
{
|
||||
id: confirmDialog
|
||||
object: base.currentItem != null ? base.currentItem.name : ""
|
||||
onYes:
|
||||
function updateMaterialPropertiesObject()
|
||||
{
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
var currentItem = materialsModel.getItem(materialListView.currentIndex);
|
||||
|
||||
FileDialog
|
||||
{
|
||||
id: importDialog;
|
||||
title: catalog.i18nc("@title:window", "Import Material");
|
||||
selectExisting: true;
|
||||
nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
|
||||
folder: CuraApplication.getDefaultPath("dialog_material_path")
|
||||
onAccepted:
|
||||
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
|
||||
{
|
||||
var result = Cura.ContainerManager.importMaterialContainer(fileUrl)
|
||||
anchors.fill: parent
|
||||
|
||||
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")
|
||||
Item // Material title Label
|
||||
{
|
||||
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)
|
||||
id: profileName
|
||||
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
Label {
|
||||
text: materialProperties.name
|
||||
font: UM.Theme.getFont("large")
|
||||
}
|
||||
}
|
||||
else if(result.status == "duplicate")
|
||||
|
||||
MaterialView // Material detailed information view below the title Label
|
||||
{
|
||||
messageDialog.icon = StandardIcon.Warning
|
||||
id: materialDetailsView
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: profileName.bottom
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
editingEnabled: base.currentItem != null && !base.currentItem.is_read_only
|
||||
|
||||
properties: materialProperties
|
||||
containerId: base.currentItem != null ? base.currentItem.container_id : ""
|
||||
currentMaterialNode: base.currentItem.container_node
|
||||
|
||||
property alias pane: base
|
||||
}
|
||||
else
|
||||
|
||||
QtObject
|
||||
{
|
||||
messageDialog.icon = StandardIcon.Critical
|
||||
id: materialProperties
|
||||
|
||||
property string guid: "00000000-0000-0000-0000-000000000000"
|
||||
property string name: "Unknown";
|
||||
property string profile_type: "Unknown";
|
||||
property string brand: "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 color color_code: "yellow";
|
||||
|
||||
property real density: 0.0;
|
||||
property real diameter: 0.0;
|
||||
property string approximate_diameter: "0";
|
||||
|
||||
property real spool_cost: 0.0;
|
||||
property real spool_weight: 0.0;
|
||||
property real spool_length: 0.0;
|
||||
property real cost_per_meter: 0.0;
|
||||
|
||||
property string description: "";
|
||||
property string adhesion_info: "";
|
||||
}
|
||||
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";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
|
@ -11,10 +11,8 @@ Tab
|
|||
{
|
||||
id: base
|
||||
|
||||
property string extruderId: "";
|
||||
property string extruderDefinition: "";
|
||||
property string quality: "";
|
||||
property string material: "";
|
||||
property string extruderPosition: ""
|
||||
property var qualityItem: null
|
||||
|
||||
TableView
|
||||
{
|
||||
|
|
@ -38,8 +36,8 @@ Tab
|
|||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
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.italic: setting.profile_value_source == "quality_changes" || (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" || (setting.user_value != "" && qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName)
|
||||
opacity: font.strikeout ? 0.5 : 1
|
||||
color: styleData.textColor
|
||||
elide: Text.ElideRight
|
||||
|
|
@ -65,7 +63,7 @@ Tab
|
|||
{
|
||||
role: "user_value"
|
||||
title: catalog.i18nc("@title:column", "Current");
|
||||
visible: quality == Cura.MachineManager.globalQualityId
|
||||
visible: qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName
|
||||
width: (parent.width * 0.18) | 0
|
||||
delegate: itemDelegate
|
||||
}
|
||||
|
|
@ -87,10 +85,8 @@ Tab
|
|||
model: Cura.QualitySettingsModel
|
||||
{
|
||||
id: qualitySettings
|
||||
extruderId: base.extruderId
|
||||
extruderDefinition: base.extruderDefinition
|
||||
quality: base.quality != null ? base.quality : ""
|
||||
material: base.material != null ? base.material : ""
|
||||
selectedPosition: base.extruderPosition
|
||||
selectedQualityItem: base.qualityItem
|
||||
}
|
||||
|
||||
SystemPalette { id: palette }
|
||||
|
|
|
|||
|
|
@ -1,355 +1,532 @@
|
|||
// Copyright (c) 2016 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Uranium is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Dialogs 1.2
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
UM.ManagementPage
|
||||
|
||||
Item
|
||||
{
|
||||
id: base;
|
||||
id: base
|
||||
|
||||
title: catalog.i18nc("@title:tab", "Profiles");
|
||||
property var extrudersModel: Cura.ExtrudersModel{}
|
||||
property QtObject qualityManager: CuraApplication.getQualityManager()
|
||||
property var resetEnabled: false // Keep PreferencesDialog happy
|
||||
property var extrudersModel: Cura.ExtrudersModel {}
|
||||
|
||||
model: Cura.QualityAndUserProfilesModel { }
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
||||
section.property: "readOnly"
|
||||
section.delegate: Rectangle
|
||||
Cura.QualityManagementModel {
|
||||
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;
|
||||
|
||||
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
|
||||
id: buttonRow
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: titleLabel.bottom
|
||||
}
|
||||
}
|
||||
height: childrenRect.height
|
||||
|
||||
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: [
|
||||
// Activate button
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Activate");
|
||||
iconName: "list-activate";
|
||||
enabled: base.currentItem != null ? base.currentItem.id != Cura.MachineManager.activeQualityId : false;
|
||||
onClicked:
|
||||
{
|
||||
Cura.MachineManager.setActiveQuality(base.currentItem.id)
|
||||
currentItem = base.model.getItem(base.objectList.currentIndex) // Refresh the current item.
|
||||
text: catalog.i18nc("@action:button", "Activate")
|
||||
iconName: "list-activate"
|
||||
enabled: !isCurrentItemActivated
|
||||
onClicked: {
|
||||
if (base.currentItem.is_read_only) {
|
||||
Cura.MachineManager.setQualityGroup(base.currentItem.quality_group);
|
||||
} else {
|
||||
Cura.MachineManager.setQualityChangesGroup(base.currentItem.quality_changes_group);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Create button
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@label", "Create")
|
||||
enabled: base.canCreateProfile() && !Cura.MachineManager.stacksHaveErrors
|
||||
visible: base.canCreateProfile()
|
||||
iconName: "list-add";
|
||||
iconName: "list-add"
|
||||
enabled: base.canCreateProfile && !Cura.MachineManager.stacksHaveErrors
|
||||
visible: base.canCreateProfile
|
||||
|
||||
onClicked:
|
||||
{
|
||||
newNameDialog.object = base.currentItem != null ? Cura.ContainerManager.makeUniqueName(base.currentItem.name) : "";
|
||||
newNameDialog.open();
|
||||
newNameDialog.selectText();
|
||||
onClicked: {
|
||||
createQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
|
||||
createQualityDialog.open();
|
||||
createQualityDialog.selectText();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Duplicate button
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@label", "Duplicate")
|
||||
enabled: ! base.canCreateProfile()
|
||||
visible: ! base.canCreateProfile()
|
||||
iconName: "list-add";
|
||||
iconName: "list-add"
|
||||
enabled: !base.canCreateProfile
|
||||
visible: !base.canCreateProfile
|
||||
|
||||
onClicked:
|
||||
{
|
||||
newDuplicateNameDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
|
||||
newDuplicateNameDialog.open();
|
||||
newDuplicateNameDialog.selectText();
|
||||
onClicked: {
|
||||
duplicateQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
|
||||
duplicateQualityDialog.open();
|
||||
duplicateQualityDialog.selectText();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// 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) : false;
|
||||
onClicked: confirmDialog.open();
|
||||
},
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Rename");
|
||||
iconName: "edit-rename";
|
||||
enabled: base.currentItem != null ? !base.currentItem.readOnly : false;
|
||||
onClicked:
|
||||
{
|
||||
renameDialog.open();
|
||||
renameDialog.selectText();
|
||||
text: catalog.i18nc("@action:button", "Remove")
|
||||
iconName: "list-remove"
|
||||
enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated
|
||||
onClicked: {
|
||||
forceActiveFocus();
|
||||
confirmRemoveQualityDialog.open();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Rename button
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Import");
|
||||
iconName: "document-import";
|
||||
onClicked: importDialog.open();
|
||||
},
|
||||
text: catalog.i18nc("@action:button", "Rename")
|
||||
iconName: "edit-rename"
|
||||
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
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Export")
|
||||
iconName: "document-export"
|
||||
onClicked: exportDialog.open()
|
||||
enabled: currentItem != null && !base.currentItem.readOnly
|
||||
enabled: base.hasCurrentItem && !base.currentItem.is_read_only
|
||||
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()
|
||||
onCreateProfile:
|
||||
{
|
||||
newNameDialog.object = base.currentItem != null ? Cura.ContainerManager.makeUniqueName(base.currentItem.name) : "";
|
||||
newNameDialog.open();
|
||||
newNameDialog.selectText();
|
||||
createQualityDialog.object = Cura.ContainerManager.makeUniqueName(Cura.MachineManager.activeQualityOrQualityChangesName);
|
||||
createQualityDialog.open();
|
||||
createQualityDialog.selectText();
|
||||
}
|
||||
|
||||
signal selectContainer(string name)
|
||||
onSelectContainer:
|
||||
// Dialog to request a name when creating a new profile
|
||||
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 {
|
||||
visible: base.currentItem != null
|
||||
anchors.fill: parent
|
||||
id: contentsItem
|
||||
|
||||
Label {
|
||||
id: profileName
|
||||
text: base.currentItem ? base.currentItem.name: ""
|
||||
font: UM.Theme.getFont("large")
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
anchors {
|
||||
top: titleLabel.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
margins: 5 * screenScaleFactor
|
||||
bottomMargin: 0
|
||||
}
|
||||
|
||||
Flow {
|
||||
id: currentSettingsActions
|
||||
visible: currentItem && currentItem.id == Cura.MachineManager.activeQualityId
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: profileName.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Button
|
||||
{
|
||||
text: {
|
||||
return catalog.i18nc("@action:button", "Update profile with current settings/overrides");
|
||||
}
|
||||
enabled: Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId)
|
||||
onClicked: Cura.ContainerManager.updateQualityChanges()
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Discard current changes");
|
||||
enabled: Cura.MachineManager.hasUserSettings
|
||||
onClicked: Cura.ContainerManager.clearUserContainers();
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: profileNotices
|
||||
anchors.top: currentSettingsActions.visible ? currentSettingsActions.bottom : currentSettingsActions.anchors.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Label {
|
||||
id: defaultsMessage
|
||||
visible: false
|
||||
text: catalog.i18nc("@action:label", "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below.")
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
Label {
|
||||
id: noCurrentSettingsMessage
|
||||
visible: currentItem && currentItem.id == Cura.MachineManager.activeQualityId && !Cura.MachineManager.hasUserSettings
|
||||
text: catalog.i18nc("@action:label", "Your current settings match the selected profile.")
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
TabView
|
||||
{
|
||||
anchors.left: parent.left
|
||||
anchors.top: profileNotices.visible ? profileNotices.bottom : profileNotices.anchors.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
currentIndex: Cura.ExtruderManager.extruderCount > 0 ? Cura.ExtruderManager.activeExtruderIndex + 1 : 0
|
||||
|
||||
ProfileTab
|
||||
{
|
||||
title: catalog.i18nc("@title:tab", "Global Settings");
|
||||
quality: base.currentItem != null ? base.currentItem.id : "";
|
||||
material: Cura.MachineManager.allActiveMaterialIds[Cura.MachineManager.activeMachineId]
|
||||
}
|
||||
|
||||
Repeater
|
||||
{
|
||||
model: base.extrudersModel
|
||||
|
||||
ProfileTab
|
||||
{
|
||||
title: model.name;
|
||||
extruderId: model.id;
|
||||
extruderDefinition: model.definition;
|
||||
quality: base.currentItem != null ? base.currentItem.id : "";
|
||||
material: Cura.MachineManager.allActiveMaterialIds[model.id]
|
||||
}
|
||||
}
|
||||
}
|
||||
clip: true
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
anchors {
|
||||
top: buttonRow.bottom
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
UM.ConfirmRemoveDialog
|
||||
SystemPalette { id: palette }
|
||||
|
||||
Label
|
||||
{
|
||||
id: confirmDialog
|
||||
object: base.currentItem != null ? base.currentItem.name : ""
|
||||
onYes:
|
||||
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
|
||||
{
|
||||
var name = base.currentItem.name;
|
||||
Cura.ContainerManager.removeQualityChanges(name)
|
||||
if(Cura.MachineManager.activeQualityName == name)
|
||||
id: qualityListView
|
||||
|
||||
model: qualitiesModel
|
||||
|
||||
section.property: "is_read_only"
|
||||
section.delegate: Rectangle
|
||||
{
|
||||
Cura.MachineManager.setActiveQuality(base.model.getItem(0).name)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
objectList.currentIndex = -1 //Reset selection.
|
||||
}
|
||||
}
|
||||
|
||||
UM.RenameDialog
|
||||
// details panel on the right
|
||||
Item
|
||||
{
|
||||
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.
|
||||
id: detailsPanel
|
||||
|
||||
anchors {
|
||||
left: profileScrollView.right
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
}
|
||||
}
|
||||
|
||||
// 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:
|
||||
Item
|
||||
{
|
||||
var selectedContainer = Cura.ContainerManager.createQualityChanges(newName);
|
||||
base.selectContainer(selectedContainer);
|
||||
objectList.currentIndex = -1 //Reset selection.
|
||||
}
|
||||
}
|
||||
anchors.fill: parent
|
||||
|
||||
// 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")
|
||||
Item // Profile title Label
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
id: profileName
|
||||
|
||||
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)
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
if(result && result.status == "error")
|
||||
{
|
||||
messageDialog.icon = StandardIcon.Critical
|
||||
messageDialog.text = result.message
|
||||
messageDialog.open()
|
||||
Label {
|
||||
text: base.currentItem.name
|
||||
font: UM.Theme.getFont("large")
|
||||
}
|
||||
}
|
||||
|
||||
// else pop-up Message thing from python code
|
||||
CuraApplication.setDefaultPath("dialog_profile_path", folder)
|
||||
Flow {
|
||||
id: currentSettingsActions
|
||||
visible: base.hasCurrentItem && base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: profileName.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Update profile with current settings/overrides")
|
||||
enabled: Cura.MachineManager.hasUserSettings && !base.currentItem.is_read_only
|
||||
onClicked: Cura.ContainerManager.updateQualityChanges()
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Discard current changes");
|
||||
enabled: Cura.MachineManager.hasUserSettings
|
||||
onClicked: Cura.ContainerManager.clearUserContainers();
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: profileNotices
|
||||
anchors.top: currentSettingsActions.visible ? currentSettingsActions.bottom : currentSettingsActions.anchors.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Label {
|
||||
id: defaultsMessage
|
||||
visible: false
|
||||
text: catalog.i18nc("@action:label", "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below.")
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
Label {
|
||||
id: noCurrentSettingsMessage
|
||||
visible: base.isCurrentItemActivated && !Cura.MachineManager.hasUserSettings
|
||||
text: catalog.i18nc("@action:label", "Your current settings match the selected profile.")
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TabView
|
||||
{
|
||||
anchors.left: parent.left
|
||||
anchors.top: profileNotices.visible ? profileNotices.bottom : profileNotices.anchors.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
currentIndex: 0
|
||||
|
||||
ProfileTab
|
||||
{
|
||||
title: catalog.i18nc("@title:tab", "Global Settings")
|
||||
qualityItem: base.currentItem
|
||||
}
|
||||
|
||||
Repeater
|
||||
{
|
||||
model: base.extrudersModel
|
||||
|
||||
ProfileTab
|
||||
{
|
||||
title: model.name
|
||||
extruderPosition: model.index
|
||||
qualityItem: base.currentItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,13 +57,13 @@ Item
|
|||
height: UM.Theme.getSize("setting_control").height
|
||||
anchors.left: globalProfileLabel.right
|
||||
anchors.right: parent.right
|
||||
tooltip: Cura.MachineManager.activeQualityName
|
||||
tooltip: Cura.MachineManager.activeQualityOrQualityChangesName
|
||||
style: UM.Theme.styles.sidebar_header_button
|
||||
activeFocusOnPress: true
|
||||
menu: ProfileMenu { }
|
||||
|
||||
function generateActiveQualityText () {
|
||||
var result = Cura.MachineManager.activeQualityName;
|
||||
var result = Cura.MachineManager.activeQualityOrQualityChangesName;
|
||||
|
||||
if (Cura.MachineManager.isActiveQualitySupported) {
|
||||
if (Cura.MachineManager.activeQualityLayerHeight > 0) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015 Ultimaker B.V.
|
||||
// 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 "Settings"
|
||||
|
|
|
|||
|
|
@ -252,10 +252,20 @@ Column
|
|||
{
|
||||
id: materialSelection
|
||||
|
||||
text: Cura.MachineManager.activeMaterialName
|
||||
tooltip: Cura.MachineManager.activeMaterialName
|
||||
property var currentRootMaterialName:
|
||||
{
|
||||
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
|
||||
enabled: !extrudersList.visible || base.currentExtruderIndex > -1
|
||||
enabled: !extrudersList.visible || base.currentExtruderIndex > -1
|
||||
height: UM.Theme.getSize("setting_control").height
|
||||
width: Math.round(parent.width * 0.7) + UM.Theme.getSize("sidebar_margin").width
|
||||
anchors.right: parent.right
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.2 as Cura
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
|
|
@ -57,7 +57,10 @@ Item
|
|||
interval: 50
|
||||
running: 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()
|
||||
|
|
@ -102,14 +105,14 @@ Item
|
|||
var availableMin = -1
|
||||
var availableMax = -1
|
||||
|
||||
for (var i = 0; i < Cura.ProfilesModel.rowCount(); i++) {
|
||||
var qualityItem = Cura.ProfilesModel.getItem(i)
|
||||
for (var i = 0; i < Cura.QualityProfilesDropDownMenuModel.rowCount(); i++) {
|
||||
var qualityItem = Cura.QualityProfilesDropDownMenuModel.getItem(i)
|
||||
|
||||
// Add each quality item to the UI quality model
|
||||
qualityModel.append(qualityItem)
|
||||
|
||||
// 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
|
||||
if (Cura.SimpleModeSettingsManager.isProfileUserCreated) {
|
||||
|
|
@ -134,7 +137,7 @@ Item
|
|||
|
||||
// Set total available ticks for active slider part
|
||||
if (availableMin != -1) {
|
||||
qualityModel.availableTotalTicks = availableMax - availableMin
|
||||
qualityModel.availableTotalTicks = availableMax - availableMin + 1
|
||||
}
|
||||
|
||||
// Calculate slider values
|
||||
|
|
@ -161,11 +164,11 @@ Item
|
|||
|
||||
function reset () {
|
||||
qualityModel.clear()
|
||||
qualityModel.availableTotalTicks = -1
|
||||
qualityModel.availableTotalTicks = 0
|
||||
qualityModel.existingQualityProfile = 0
|
||||
|
||||
// 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.top: parent.top
|
||||
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:
|
||||
{
|
||||
var result = ""
|
||||
if(Cura.MachineManager.activeMachine != null)
|
||||
{
|
||||
result = Cura.ProfilesModel.getItem(index).layer_height_without_unit
|
||||
result = Cura.QualityProfilesDropDownMenuModel.getItem(index).layer_height
|
||||
|
||||
if(result == undefined)
|
||||
{
|
||||
|
|
@ -262,7 +265,7 @@ Item
|
|||
Rectangle
|
||||
{
|
||||
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
|
||||
height: 6 * screenScaleFactor
|
||||
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
|
||||
{
|
||||
id: qualitySlider
|
||||
height: UM.Theme.getSize("sidebar_margin").height
|
||||
anchors.bottom: speedSlider.bottom
|
||||
enabled: qualityModel.availableTotalTicks > 0 && !Cura.SimpleModeSettingsManager.isProfileCustomized
|
||||
visible: qualityModel.totalTicks > 0
|
||||
enabled: qualityModel.totalTicks > 0 && !Cura.SimpleModeSettingsManager.isProfileCustomized
|
||||
visible: qualityModel.availableTotalTicks > 0
|
||||
updateValueWhileDragging : false
|
||||
|
||||
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
|
||||
|
||||
value: qualityModel.qualitySliderActiveIndex
|
||||
|
||||
width: qualityModel.qualitySliderStepWidth * qualityModel.availableTotalTicks
|
||||
width: qualityModel.qualitySliderStepWidth * (qualityModel.availableTotalTicks - 1)
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: qualityModel.qualitySliderMarginRight
|
||||
|
|
@ -373,7 +368,7 @@ Item
|
|||
|
||||
text: catalog.i18nc("@label", "Slower")
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -384,7 +379,7 @@ Item
|
|||
|
||||
text: catalog.i18nc("@label", "Faster")
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -408,9 +403,10 @@ Item
|
|||
// if the current profile is user-created, switch to a built-in quality
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ UM.Dialog
|
|||
}
|
||||
Label
|
||||
{
|
||||
text: Cura.MachineManager.activeDefinitionName
|
||||
text: Cura.MachineManager.activeMachine.definition.name
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
}
|
||||
|
|
@ -148,7 +148,7 @@ UM.Dialog
|
|||
|
||||
Repeater
|
||||
{
|
||||
model: Cura.MachineManager.activeMaterialNames
|
||||
model: Cura.MachineManager.currentExtruderPositions
|
||||
delegate: Column
|
||||
{
|
||||
Item // Spacer
|
||||
|
|
@ -158,7 +158,7 @@ UM.Dialog
|
|||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Extruder %1").arg(index+1)
|
||||
text: catalog.i18nc("@action:label", "Extruder %1").arg(modelData)
|
||||
}
|
||||
height: childrenRect.height
|
||||
width: parent.width
|
||||
|
|
@ -173,7 +173,7 @@ UM.Dialog
|
|||
}
|
||||
Label
|
||||
{
|
||||
text: Cura.MachineManager.activeVariantNames[index] + ", " + modelData
|
||||
text: Cura.MachineManager.activeVariantNames[modelData] + ", " + Cura.MachineManager.currentRootMaterialName[modelData]
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
}
|
||||
|
|
@ -217,7 +217,7 @@ UM.Dialog
|
|||
}
|
||||
Label
|
||||
{
|
||||
text: Cura.MachineManager.activeQualityName
|
||||
text: Cura.MachineManager.activeQualityOrQualityChangesName
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ name = Fine
|
|||
definition = abax_pri3
|
||||
|
||||
[metadata]
|
||||
type = quality
|
||||
material = generic_pla
|
||||
weight = -1
|
||||
quality_type = normal
|
||||
setting_version = 4
|
||||
type = quality
|
||||
quality_type = normal
|
||||
weight = -1
|
||||
material = generic_pla
|
||||
|
||||
[values]
|
||||
layer_height = 0.2
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue