Merge branch 'ui_rework_4_0' into cura4.0_header

Conflicts:
plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml
resources/qml/Menus/ViewMenu.qml
resources/themes/cura-dark/theme.json
resources/themes/cura-light/theme.json
This commit is contained in:
Diego Prado Gesto 2018-10-29 11:10:25 +01:00
commit 5de367bcc4
107 changed files with 15797 additions and 14749 deletions

View file

@ -45,7 +45,7 @@ class Account(QObject):
CALLBACK_PORT=self._callback_port,
CALLBACK_URL="http://localhost:{}/callback".format(self._callback_port),
CLIENT_ID="um---------------ultimaker_cura_drive_plugin",
CLIENT_SCOPES="user.read drive.backups.read drive.backups.write",
CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download packages.rating.read packages.rating.write",
AUTH_DATA_PREFERENCE_KEY="general/ultimaker_auth_data",
AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(self._oauth_root),
AUTH_FAILED_REDIRECT="{}/app/auth-error".format(self._oauth_root)

View file

@ -4,7 +4,7 @@
import os
import sys
import time
from typing import cast, TYPE_CHECKING, Optional
from typing import cast, TYPE_CHECKING, Optional, Callable
import numpy
@ -13,6 +13,7 @@ from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
from UM.Application import Application
from UM.PluginError import PluginNotFoundError
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Camera import Camera
@ -114,12 +115,13 @@ from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions
from cura.ObjectsModel import ObjectsModel
from UM.FlameProfiler import pyqtSlot
from UM.Decorators import override
if TYPE_CHECKING:
from cura.Machines.MaterialManager import MaterialManager
from cura.Machines.QualityManager import QualityManager
from UM.Settings.EmptyInstanceContainer import EmptyInstanceContainer
from cura.Settings.GlobalStack import GlobalStack
numpy.seterr(all = "ignore")
@ -166,6 +168,8 @@ class CuraApplication(QtApplication):
self.default_theme = "cura-light"
self.change_log_url = "https://ultimaker.com/ultimaker-cura-latest-features"
self._boot_loading_time = time.time()
self._on_exit_callback_manager = OnExitCallbackManager(self)
@ -302,8 +306,6 @@ class CuraApplication(QtApplication):
self._machine_action_manager = MachineActionManager.MachineActionManager(self)
self._machine_action_manager.initialize()
self.change_log_url = "https://ultimaker.com/ultimaker-cura-latest-features"
def __sendCommandToSingleInstance(self):
self._single_instance = SingleInstance(self, self._files_to_open)
@ -419,7 +421,7 @@ class CuraApplication(QtApplication):
)
# Runs preparations that needs to be done before the starting process.
def startSplashWindowPhase(self):
def startSplashWindowPhase(self) -> None:
super().startSplashWindowPhase()
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
@ -525,15 +527,15 @@ class CuraApplication(QtApplication):
self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider())
@pyqtProperty(bool)
def needToShowUserAgreement(self):
def needToShowUserAgreement(self) -> bool:
return self._need_to_show_user_agreement
def setNeedToShowUserAgreement(self, set_value = True):
def setNeedToShowUserAgreement(self, set_value = True) -> None:
self._need_to_show_user_agreement = set_value
# DO NOT call this function to close the application, use checkAndExitApplication() instead which will perform
# pre-exit checks such as checking for in-progress USB printing, etc.
def closeApplication(self):
def closeApplication(self) -> None:
Logger.log("i", "Close application")
main_window = self.getMainWindow()
if main_window is not None:
@ -560,11 +562,11 @@ class CuraApplication(QtApplication):
showConfirmExitDialog = pyqtSignal(str, arguments = ["message"])
def setConfirmExitDialogCallback(self, callback):
def setConfirmExitDialogCallback(self, callback: Callable) -> None:
self._confirm_exit_dialog_callback = callback
@pyqtSlot(bool)
def callConfirmExitDialogCallback(self, yes_or_no: bool):
def callConfirmExitDialogCallback(self, yes_or_no: bool) -> None:
self._confirm_exit_dialog_callback(yes_or_no)
## Signal to connect preferences action in QML
@ -572,9 +574,17 @@ class CuraApplication(QtApplication):
## Show the preferences window
@pyqtSlot()
def showPreferences(self):
def showPreferences(self) -> None:
self.showPreferencesWindow.emit()
@override(Application)
def getGlobalContainerStack(self) -> Optional["GlobalStack"]:
return self._global_container_stack
@override(Application)
def setGlobalContainerStack(self, stack: "GlobalStack") -> None:
super().setGlobalContainerStack(stack)
## A reusable dialogbox
#
showMessageBox = pyqtSignal(str, str, str, str, int, int, arguments = ["title", "text", "informativeText", "detailedText", "buttons", "icon"])
@ -586,7 +596,7 @@ class CuraApplication(QtApplication):
showDiscardOrKeepProfileChanges = pyqtSignal()
def discardOrKeepProfileChanges(self):
def discardOrKeepProfileChanges(self) -> bool:
has_user_interaction = False
choice = self.getPreferences().getValue("cura/choice_on_profile_override")
if choice == "always_discard":
@ -602,7 +612,7 @@ class CuraApplication(QtApplication):
return has_user_interaction
@pyqtSlot(str)
def discardOrKeepProfileChangesClosed(self, option):
def discardOrKeepProfileChangesClosed(self, option: str) -> None:
global_stack = self.getGlobalContainerStack()
if option == "discard":
for extruder in global_stack.extruders.values():
@ -689,7 +699,7 @@ class CuraApplication(QtApplication):
self._quality_manager.initialize()
Logger.log("i", "Initializing machine manager")
self._machine_manager = MachineManager(self)
self._machine_manager = MachineManager(self, parent = self)
Logger.log("i", "Initializing container manager")
self._container_manager = ContainerManager(self)
@ -947,6 +957,9 @@ class CuraApplication(QtApplication):
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
from cura.PrinterOutput.CameraView import CameraView
qmlRegisterType(CameraView, "Cura", 1, 0, "CameraView")
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,

View file

@ -1,13 +1,13 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Logger import Logger
from UM.PluginObject import PluginObject
from UM.PluginRegistry import PluginRegistry
from UM.Application import Application
import os
## Machine actions are actions that are added to a specific machine type. Examples of such actions are
@ -19,7 +19,7 @@ class MachineAction(QObject, PluginObject):
## Create a new Machine action.
# \param key unique key of the machine action
# \param label Human readable label used to identify the machine action.
def __init__(self, key, label = ""):
def __init__(self, key: str, label: str = "") -> None:
super().__init__()
self._key = key
self._label = label
@ -30,14 +30,14 @@ class MachineAction(QObject, PluginObject):
labelChanged = pyqtSignal()
onFinished = pyqtSignal()
def getKey(self):
def getKey(self) -> str:
return self._key
@pyqtProperty(str, notify = labelChanged)
def label(self):
def label(self) -> str:
return self._label
def setLabel(self, label):
def setLabel(self, label: str) -> None:
if self._label != label:
self._label = label
self.labelChanged.emit()
@ -46,29 +46,35 @@ class MachineAction(QObject, PluginObject):
# This should not be re-implemented by child classes, instead re-implement _reset.
# /sa _reset
@pyqtSlot()
def reset(self):
def reset(self) -> None:
self._finished = False
self._reset()
## Protected implementation of reset.
# /sa reset()
def _reset(self):
def _reset(self) -> None:
pass
@pyqtSlot()
def setFinished(self):
def setFinished(self) -> None:
self._finished = True
self._reset()
self.onFinished.emit()
@pyqtProperty(bool, notify = onFinished)
def finished(self):
def finished(self) -> bool:
return self._finished
## Protected helper to create a view object based on provided QML.
def _createViewFromQML(self):
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url)
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
def _createViewFromQML(self) -> None:
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if plugin_path is None:
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())
return
path = os.path.join(plugin_path, self._qml_url)
from cura.CuraApplication import CuraApplication
self._view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
@pyqtProperty(QObject, constant = True)
def displayItem(self):

View file

@ -1,12 +1,18 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING, Optional, List, Set, Dict
from PyQt5.QtCore import QObject
from UM.FlameProfiler import pyqtSlot
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry # So MachineAction can be added as plugin type
from UM.Settings.DefinitionContainer import DefinitionContainer
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
from cura.Settings.GlobalStack import GlobalStack
from .MachineAction import MachineAction
## Raised when trying to add an unknown machine action as a required action
@ -20,46 +26,54 @@ class NotUniqueMachineActionError(Exception):
class MachineActionManager(QObject):
def __init__(self, application, parent = None):
super().__init__(parent)
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
super().__init__(parent = parent)
self._application = application
self._container_registry = self._application.getContainerRegistry()
self._machine_actions = {} # Dict of all known machine actions
self._required_actions = {} # Dict of all required actions by definition ID
self._supported_actions = {} # Dict of all supported actions by definition ID
self._first_start_actions = {} # Dict of all actions that need to be done when first added by definition ID
# Keeps track of which machines have already been processed so we don't do that again.
self._definition_ids_with_default_actions_added = set() # type: Set[str]
# Dict of all known machine actions
self._machine_actions = {} # type: Dict[str, MachineAction]
# Dict of all required actions by definition ID
self._required_actions = {} # type: Dict[str, List[MachineAction]]
# Dict of all supported actions by definition ID
self._supported_actions = {} # type: Dict[str, List[MachineAction]]
# Dict of all actions that need to be done when first added by definition ID
self._first_start_actions = {} # type: Dict[str, List[MachineAction]]
def initialize(self):
container_registry = self._application.getContainerRegistry()
# Add machine_action as plugin type
PluginRegistry.addType("machine_action", self.addMachineAction)
# Ensure that all containers that were registered before creation of this registry are also handled.
# This should not have any effect, but it makes it safer if we ever refactor the order of things.
for container in container_registry.findDefinitionContainers():
self._onContainerAdded(container)
# Adds all default machine actions that are defined in the machine definition for the given machine.
def addDefaultMachineActions(self, global_stack: "GlobalStack") -> None:
definition_id = global_stack.definition.getId()
container_registry.containerAdded.connect(self._onContainerAdded)
if definition_id in self._definition_ids_with_default_actions_added:
Logger.log("i", "Default machine actions have been added for machine definition [%s], do nothing.",
definition_id)
return
def _onContainerAdded(self, container):
## Ensure that the actions are added to this manager
if isinstance(container, DefinitionContainer):
supported_actions = container.getMetaDataEntry("supported_actions", [])
for action in supported_actions:
self.addSupportedAction(container.getId(), action)
supported_actions = global_stack.getMetaDataEntry("supported_actions", [])
for action_key in supported_actions:
self.addSupportedAction(definition_id, action_key)
required_actions = container.getMetaDataEntry("required_actions", [])
for action in required_actions:
self.addRequiredAction(container.getId(), action)
required_actions = global_stack.getMetaDataEntry("required_actions", [])
for action_key in required_actions:
self.addRequiredAction(definition_id, action_key)
first_start_actions = container.getMetaDataEntry("first_start_actions", [])
for action in first_start_actions:
self.addFirstStartAction(container.getId(), action)
first_start_actions = global_stack.getMetaDataEntry("first_start_actions", [])
for action_key in first_start_actions:
self.addFirstStartAction(definition_id, action_key)
self._definition_ids_with_default_actions_added.add(definition_id)
Logger.log("i", "Default machine actions added for machine definition [%s]", definition_id)
## Add a required action to a machine
# Raises an exception when the action is not recognised.
def addRequiredAction(self, definition_id, action_key):
def addRequiredAction(self, definition_id: str, action_key: str) -> None:
if action_key in self._machine_actions:
if definition_id in self._required_actions:
if self._machine_actions[action_key] not in self._required_actions[definition_id]:
@ -70,7 +84,7 @@ class MachineActionManager(QObject):
raise UnknownMachineActionError("Action %s, which is required for %s is not known." % (action_key, definition_id))
## Add a supported action to a machine.
def addSupportedAction(self, definition_id, action_key):
def addSupportedAction(self, definition_id: str, action_key: str) -> None:
if action_key in self._machine_actions:
if definition_id in self._supported_actions:
if self._machine_actions[action_key] not in self._supported_actions[definition_id]:
@ -81,13 +95,10 @@ class MachineActionManager(QObject):
Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id)
## Add an action to the first start list of a machine.
def addFirstStartAction(self, definition_id, action_key, index = None):
def addFirstStartAction(self, definition_id: str, action_key: str) -> None:
if action_key in self._machine_actions:
if definition_id in self._first_start_actions:
if index is not None:
self._first_start_actions[definition_id].insert(index, self._machine_actions[action_key])
else:
self._first_start_actions[definition_id].append(self._machine_actions[action_key])
self._first_start_actions[definition_id].append(self._machine_actions[action_key])
else:
self._first_start_actions[definition_id] = [self._machine_actions[action_key]]
else:
@ -95,7 +106,7 @@ class MachineActionManager(QObject):
## Add a (unique) MachineAction
# if the Key of the action is not unique, an exception is raised.
def addMachineAction(self, action):
def addMachineAction(self, action: "MachineAction") -> None:
if action.getKey() not in self._machine_actions:
self._machine_actions[action.getKey()] = action
else:
@ -105,7 +116,7 @@ class MachineActionManager(QObject):
# \param definition_id The ID of the definition you want the supported actions of
# \returns set of supported actions.
@pyqtSlot(str, result = "QVariantList")
def getSupportedActions(self, definition_id):
def getSupportedActions(self, definition_id: str) -> List["MachineAction"]:
if definition_id in self._supported_actions:
return list(self._supported_actions[definition_id])
else:
@ -114,11 +125,11 @@ class MachineActionManager(QObject):
## Get all actions required by given machine
# \param definition_id The ID of the definition you want the required actions of
# \returns set of required actions.
def getRequiredActions(self, definition_id):
def getRequiredActions(self, definition_id: str) -> List["MachineAction"]:
if definition_id in self._required_actions:
return self._required_actions[definition_id]
else:
return set()
return list()
## Get all actions that need to be performed upon first start of a given machine.
# Note that contrary to required / supported actions a list is returned (as it could be required to run the same
@ -126,7 +137,7 @@ class MachineActionManager(QObject):
# \param definition_id The ID of the definition that you want to get the "on added" actions for.
# \returns List of actions.
@pyqtSlot(str, result="QVariantList")
def getFirstStartActions(self, definition_id):
def getFirstStartActions(self, definition_id: str) -> List["MachineAction"]:
if definition_id in self._first_start_actions:
return self._first_start_actions[definition_id]
else:
@ -134,7 +145,7 @@ class MachineActionManager(QObject):
## Remove Machine action from manager
# \param action to remove
def removeMachineAction(self, action):
def removeMachineAction(self, action: "MachineAction") -> None:
try:
del self._machine_actions[action.getKey()]
except KeyError:
@ -143,7 +154,7 @@ class MachineActionManager(QObject):
## Get MachineAction by key
# \param key String of key to select
# \return Machine action if found, None otherwise
def getMachineAction(self, key):
def getMachineAction(self, key: str) -> Optional["MachineAction"]:
if key in self._machine_actions:
return self._machine_actions[key]
else:

View file

@ -365,7 +365,7 @@ class MaterialManager(QObject):
nozzle_name = None
if extruder_stack.variant.getId() != "empty_variant":
nozzle_name = extruder_stack.variant.getName()
diameter = extruder_stack.approximateMaterialDiameter
diameter = extruder_stack.getApproximateMaterialDiameter()
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
return self.getAvailableMaterials(machine.definition, nozzle_name, buildplate_name, diameter)
@ -478,12 +478,22 @@ class MaterialManager(QObject):
buildplate_name = global_stack.getBuildplateName()
machine_definition = global_stack.definition
if extruder_definition is None:
extruder_definition = global_stack.extruders[position].definition
if extruder_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)):
# At this point the extruder_definition is not None
material_diameter = extruder_definition.getProperty("material_diameter", "value")
# The extruder-compatible material diameter in the extruder definition may not be the correct value because
# the user can change it in the definition_changes container.
if extruder_definition is None:
extruder_stack_or_definition = global_stack.extruders[position]
is_extruder_stack = True
else:
extruder_stack_or_definition = extruder_definition
is_extruder_stack = False
if extruder_stack_or_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)):
if is_extruder_stack:
material_diameter = extruder_stack_or_definition.getCompatibleMaterialDiameter()
else:
material_diameter = extruder_stack_or_definition.getProperty("material_diameter", "value")
if isinstance(material_diameter, SettingFunction):
material_diameter = material_diameter(global_stack)
approximate_material_diameter = str(round(material_diameter))

View file

@ -64,9 +64,11 @@ class BaseMaterialsModel(ListModel):
if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.disconnect(self._update)
self._extruder_stack.approximateMaterialDiameterChanged.disconnect(self._update)
self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.connect(self._update)
self._extruder_stack.approximateMaterialDiameterChanged.connect(self._update)
# Force update the model when the extruder stack changes
self._update()

View file

@ -6,7 +6,7 @@ import math
import os
import unicodedata
import re # To create abbreviations for printer names.
from typing import Dict
from typing import Dict, List, Optional
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
@ -16,55 +16,41 @@ from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog
from UM.MimeTypeDatabase import MimeTypeDatabase
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
catalog = i18nCatalog("cura")
## A class for processing and calculating minimum, current and maximum print time as well as managing the job name
#
# This class contains all the logic relating to calculation and slicing for the
# time/quality slider concept. It is a rather tricky combination of event handling
# and state management. The logic behind this is as follows:
#
# - A scene change or setting change event happens.
# We track what the source was of the change, either a scene change, a setting change, an active machine change or something else.
# - This triggers a new slice with the current settings - this is the "current settings pass".
# - When the slice is done, we update the current print time and material amount.
# - If the source of the slice was not a Setting change, we start the second slice pass, the "low quality settings pass". Otherwise we stop here.
# - When that is done, we update the minimum print time and start the final slice pass, the "Extra Fine settings pass".
# - When the Extra Fine pass is done, we update the maximum print time.
## A class for processing and the print times per build plate as well as managing the job name
#
# This class also mangles the current machine name and the filename of the first loaded mesh into a job name.
# This job name is requested by the JobSpecs qml file.
class PrintInformation(QObject):
class SlicePass:
CurrentSettings = 1
LowQualitySettings = 2
HighQualitySettings = 3
class SliceReason:
SceneChanged = 1
SettingChanged = 2
ActiveMachineChanged = 3
Other = 4
UNTITLED_JOB_NAME = "Untitled"
def __init__(self, application, parent = None):
def __init__(self, application: "CuraApplication", parent = None) -> None:
super().__init__(parent)
self._application = application
self.UNTITLED_JOB_NAME = "Untitled"
self.initializeCuraMessagePrintTimeProperties()
self._material_lengths = {} # indexed by build plate number
self._material_weights = {}
self._material_costs = {}
self._material_names = {}
# Indexed by build plate number
self._material_lengths = {} # type: Dict[int, List[float]]
self._material_weights = {} # type: Dict[int, List[float]]
self._material_costs = {} # type: Dict[int, List[float]]
self._material_names = {} # type: Dict[int, List[str]]
self._pre_sliced = False
self._backend = self._application.getBackend()
if self._backend:
self._backend.printDurationMessage.connect(self._onPrintDurationMessage)
self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged)
self._is_user_specified_job_name = False
@ -72,29 +58,25 @@ class PrintInformation(QObject):
self._abbr_machine = ""
self._job_name = ""
self._active_build_plate = 0
self._initVariablesWithBuildPlate(self._active_build_plate)
self._initVariablesByBuildPlate(self._active_build_plate)
self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
ss = self._multi_build_plate_model.maxBuildPlate
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)
self._application.getMachineManager().rootMaterialChanged.connect(self._onActiveMaterialsChanged)
self._application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
self._application.getMachineManager().rootMaterialChanged.connect(self._onActiveMaterialsChanged)
self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
self._onActiveMaterialsChanged()
self._material_amounts = []
self._material_amounts = [] # type: List[float]
# Crate cura message translations and using translation keys initialize empty time Duration object for total time
# and time for each feature
def initializeCuraMessagePrintTimeProperties(self):
self._current_print_time = {} # Duration(None, self)
def initializeCuraMessagePrintTimeProperties(self) -> None:
self._current_print_time = {} # type: Dict[int, Duration]
self._print_time_message_translations = {
"inset_0": catalog.i18nc("@tooltip", "Outer Wall"),
@ -110,17 +92,17 @@ class PrintInformation(QObject):
"none": catalog.i18nc("@tooltip", "Other")
}
self._print_time_message_values = {}
self._print_times_per_feature = {} # type: Dict[int, Dict[str, Duration]]
def _initPrintTimeMessageValues(self, build_plate_number):
def _initPrintTimesPerFeature(self, build_plate_number: int) -> None:
# Full fill message values using keys from _print_time_message_translations
self._print_time_message_values[build_plate_number] = {}
self._print_times_per_feature[build_plate_number] = {}
for key in self._print_time_message_translations.keys():
self._print_time_message_values[build_plate_number][key] = Duration(None, self)
self._print_times_per_feature[build_plate_number][key] = Duration(None, self)
def _initVariablesWithBuildPlate(self, build_plate_number):
if build_plate_number not in self._print_time_message_values:
self._initPrintTimeMessageValues(build_plate_number)
def _initVariablesByBuildPlate(self, build_plate_number: int) -> None:
if build_plate_number not in self._print_times_per_feature:
self._initPrintTimesPerFeature(build_plate_number)
if self._active_build_plate not in self._material_lengths:
self._material_lengths[self._active_build_plate] = []
if self._active_build_plate not in self._material_weights:
@ -130,23 +112,24 @@ class PrintInformation(QObject):
if self._active_build_plate not in self._material_names:
self._material_names[self._active_build_plate] = []
if self._active_build_plate not in self._current_print_time:
self._current_print_time[self._active_build_plate] = Duration(None, self)
self._current_print_time[self._active_build_plate] = Duration(parent = self)
currentPrintTimeChanged = pyqtSignal()
preSlicedChanged = pyqtSignal()
@pyqtProperty(bool, notify=preSlicedChanged)
def preSliced(self):
def preSliced(self) -> bool:
return self._pre_sliced
def setPreSliced(self, pre_sliced):
self._pre_sliced = pre_sliced
self._updateJobName()
self.preSlicedChanged.emit()
def setPreSliced(self, pre_sliced: bool) -> None:
if self._pre_sliced != pre_sliced:
self._pre_sliced = pre_sliced
self._updateJobName()
self.preSlicedChanged.emit()
@pyqtProperty(Duration, notify = currentPrintTimeChanged)
def currentPrintTime(self):
def currentPrintTime(self) -> Duration:
return self._current_print_time[self._active_build_plate]
materialLengthsChanged = pyqtSignal()
@ -173,36 +156,41 @@ class PrintInformation(QObject):
def materialNames(self):
return self._material_names[self._active_build_plate]
def printTimes(self):
return self._print_time_message_values[self._active_build_plate]
# Get all print times (by feature) of the active buildplate.
def printTimes(self) -> Dict[str, Duration]:
return self._print_times_per_feature[self._active_build_plate]
def _onPrintDurationMessage(self, build_plate_number, print_time: Dict[str, int], material_amounts: list):
self._updateTotalPrintTimePerFeature(build_plate_number, print_time)
def _onPrintDurationMessage(self, build_plate_number: int, print_times_per_feature: Dict[str, int], material_amounts: List[float]) -> None:
self._updateTotalPrintTimePerFeature(build_plate_number, print_times_per_feature)
self.currentPrintTimeChanged.emit()
self._material_amounts = material_amounts
self._calculateInformation(build_plate_number)
def _updateTotalPrintTimePerFeature(self, build_plate_number, print_time: Dict[str, int]):
def _updateTotalPrintTimePerFeature(self, build_plate_number: int, print_times_per_feature: Dict[str, int]) -> None:
total_estimated_time = 0
if build_plate_number not in self._print_time_message_values:
self._initPrintTimeMessageValues(build_plate_number)
if build_plate_number not in self._print_times_per_feature:
self._initPrintTimesPerFeature(build_plate_number)
for feature, time in print_times_per_feature.items():
if feature not in self._print_times_per_feature[build_plate_number]:
self._print_times_per_feature[build_plate_number][feature] = Duration(parent=self)
duration = self._print_times_per_feature[build_plate_number][feature]
for feature, time in print_time.items():
if time != time: # Check for NaN. Engine can sometimes give us weird values.
self._print_time_message_values[build_plate_number].get(feature).setDuration(0)
duration.setDuration(0)
Logger.log("w", "Received NaN for print duration message")
continue
total_estimated_time += time
self._print_time_message_values[build_plate_number].get(feature).setDuration(time)
duration.setDuration(time)
if build_plate_number not in self._current_print_time:
self._current_print_time[build_plate_number] = Duration(None, self)
self._current_print_time[build_plate_number].setDuration(total_estimated_time)
def _calculateInformation(self, build_plate_number):
def _calculateInformation(self, build_plate_number: int) -> None:
global_stack = self._application.getGlobalContainerStack()
if global_stack is None:
return
@ -215,39 +203,45 @@ class PrintInformation(QObject):
material_preference_values = json.loads(self._application.getInstance().getPreferences().getValue("cura/material_settings"))
extruder_stacks = global_stack.extruders
for position, extruder_stack in extruder_stacks.items():
for position in extruder_stacks:
extruder_stack = extruder_stacks[position]
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.
# 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.
density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0)
material = extruder_stack.findContainer({"type": "material"})
material = extruder_stack.material
radius = extruder_stack.getProperty("material_diameter", "value") / 2
weight = float(amount) * float(density) / 1000
cost = 0
material_name = catalog.i18nc("@label unknown material", "Unknown")
if material:
material_guid = material.getMetaDataEntry("GUID")
material_name = material.getName()
if material_guid in material_preference_values:
material_values = material_preference_values[material_guid]
cost = 0.
weight_per_spool = float(material_values["spool_weight"] if material_values and "spool_weight" in material_values else 0)
cost_per_spool = float(material_values["spool_cost"] if material_values and "spool_cost" in material_values else 0)
material_guid = material.getMetaDataEntry("GUID")
material_name = material.getName()
if material_guid in material_preference_values:
material_values = material_preference_values[material_guid]
if weight_per_spool != 0:
cost = cost_per_spool * weight / weight_per_spool
else:
cost = 0
if material_values and "spool_weight" in material_values:
weight_per_spool = float(material_values["spool_weight"])
else:
weight_per_spool = float(extruder_stack.getMetaDataEntry("properties", {}).get("weight", 0))
cost_per_spool = float(material_values["spool_cost"] if material_values and "spool_cost" in material_values else 0)
if weight_per_spool != 0:
cost = cost_per_spool * weight / weight_per_spool
else:
cost = 0
# Material amount is sent as an amount of mm^3, so calculate length from that
if radius != 0:
length = round((amount / (math.pi * radius ** 2)) / 1000, 2)
else:
length = 0
self._material_weights[build_plate_number].append(weight)
self._material_lengths[build_plate_number].append(length)
self._material_costs[build_plate_number].append(cost)
@ -258,20 +252,20 @@ class PrintInformation(QObject):
self.materialCostsChanged.emit()
self.materialNamesChanged.emit()
def _onPreferencesChanged(self, preference):
def _onPreferencesChanged(self, preference: str) -> None:
if preference != "cura/material_settings":
return
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
self._calculateInformation(build_plate_number)
def _onActiveBuildPlateChanged(self):
def _onActiveBuildPlateChanged(self) -> None:
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
self._updateJobName()
self._initVariablesWithBuildPlate(self._active_build_plate)
self._initVariablesByBuildPlate(self._active_build_plate)
self.materialLengthsChanged.emit()
self.materialWeightsChanged.emit()
@ -279,14 +273,14 @@ class PrintInformation(QObject):
self.materialNamesChanged.emit()
self.currentPrintTimeChanged.emit()
def _onActiveMaterialsChanged(self, *args, **kwargs):
def _onActiveMaterialsChanged(self, *args, **kwargs) -> None:
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
self._calculateInformation(build_plate_number)
# Manual override of job name should also set the base name so that when the printer prefix is updated, it the
# prefix can be added to the manually added name, not the old base name
@pyqtSlot(str, bool)
def setJobName(self, name, is_user_specified_job_name = False):
def setJobName(self, name: str, is_user_specified_job_name = False) -> None:
self._is_user_specified_job_name = is_user_specified_job_name
self._job_name = name
self._base_name = name.replace(self._abbr_machine + "_", "")
@ -300,7 +294,7 @@ class PrintInformation(QObject):
def jobName(self):
return self._job_name
def _updateJobName(self):
def _updateJobName(self) -> None:
if self._base_name == "":
self._job_name = self.UNTITLED_JOB_NAME
self._is_user_specified_job_name = False
@ -335,12 +329,12 @@ class PrintInformation(QObject):
self.jobNameChanged.emit()
@pyqtSlot(str)
def setProjectName(self, name):
def setProjectName(self, name: str) -> None:
self.setBaseName(name, is_project_file = True)
baseNameChanged = pyqtSignal()
def setBaseName(self, base_name: str, is_project_file: bool = False):
def setBaseName(self, base_name: str, is_project_file: bool = False) -> None:
self._is_user_specified_job_name = False
# Ensure that we don't use entire path but only filename
@ -384,7 +378,7 @@ class PrintInformation(QObject):
## Created an acronym-like abbreviated machine name from the currently
# active machine name.
# Called each time the global stack is switched.
def _defineAbbreviatedMachineName(self):
def _defineAbbreviatedMachineName(self) -> None:
global_container_stack = self._application.getGlobalContainerStack()
if not global_container_stack:
self._abbr_machine = ""
@ -408,15 +402,15 @@ class PrintInformation(QObject):
self._abbr_machine = abbr_machine
## Utility method that strips accents from characters (eg: â -> a)
def _stripAccents(self, str):
return ''.join(char for char in unicodedata.normalize('NFD', str) if unicodedata.category(char) != 'Mn')
def _stripAccents(self, to_strip: str) -> str:
return ''.join(char for char in unicodedata.normalize('NFD', to_strip) if unicodedata.category(char) != 'Mn')
@pyqtSlot(result = "QVariantMap")
def getFeaturePrintTimes(self):
result = {}
if self._active_build_plate not in self._print_time_message_values:
self._initPrintTimeMessageValues(self._active_build_plate)
for feature, time in self._print_time_message_values[self._active_build_plate].items():
if self._active_build_plate not in self._print_times_per_feature:
self._initPrintTimesPerFeature(self._active_build_plate)
for feature, time in self._print_times_per_feature[self._active_build_plate].items():
if feature in self._print_time_message_translations:
result[self._print_time_message_translations[feature]] = time
else:
@ -424,22 +418,22 @@ class PrintInformation(QObject):
return result
# Simulate message with zero time duration
def setToZeroPrintInformation(self, build_plate = None):
def setToZeroPrintInformation(self, build_plate: Optional[int] = None) -> None:
if build_plate is None:
build_plate = self._active_build_plate
# Construct the 0-time message
temp_message = {}
if build_plate not in self._print_time_message_values:
self._print_time_message_values[build_plate] = {}
for key in self._print_time_message_values[build_plate].keys():
if build_plate not in self._print_times_per_feature:
self._print_times_per_feature[build_plate] = {}
for key in self._print_times_per_feature[build_plate].keys():
temp_message[key] = 0
temp_material_amounts = [0]
temp_material_amounts = [0.]
self._onPrintDurationMessage(build_plate, temp_message, temp_material_amounts)
## Listen to scene changes to check if we need to reset the print information
def _onSceneChanged(self, scene_node):
def _onSceneChanged(self, scene_node: SceneNode) -> None:
# Ignore any changes that are not related to sliceable objects
if not isinstance(scene_node, SceneNode)\
or not scene_node.callDecoration("isSliceable")\

View file

@ -0,0 +1,41 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal
from PyQt5.QtGui import QImage
from PyQt5.QtQuick import QQuickPaintedItem
#
# A custom camera view that uses QQuickPaintedItem to present (or "paint") the image frames from a printer's
# network camera feed.
#
class CameraView(QQuickPaintedItem):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._image = QImage()
imageChanged = pyqtSignal()
def setImage(self, image: "QImage") -> None:
self._image = image
self.imageChanged.emit()
self.update()
def getImage(self) -> "QImage":
return self._image
image = pyqtProperty(QImage, fget = getImage, fset = setImage, notify = imageChanged)
@pyqtProperty(int, notify = imageChanged)
def imageWidth(self) -> int:
return self._image.width()
@pyqtProperty(int, notify = imageChanged)
def imageHeight(self) -> int:
return self._image.height()
def paint(self, painter):
painter.drawImage(self.contentsBoundingRect(), self._image)

View file

@ -16,7 +16,6 @@ class NetworkCamera(QObject):
self._image_request = None
self._image_reply = None
self._image = QImage()
self._image_id = 0
self._target = target
self._started = False
@ -33,15 +32,9 @@ class NetworkCamera(QObject):
if restart_required:
self.start()
@pyqtProperty(QUrl, notify=newImage)
@pyqtProperty(QImage, notify=newImage)
def latestImage(self):
self._image_id += 1
# There is an image provider that is called "camera". In order to ensure that the image qml object, that
# requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
# as new (instead of relying on cached version and thus forces an update.
temp = "image://camera/" + str(self._image_id)
return QUrl(temp, QUrl.TolerantMode)
return self._image
@pyqtSlot()
def start(self):
@ -116,4 +109,4 @@ class NetworkCamera(QObject):
self._stream_buffer_start_index = -1
self._image.loadFromData(jpg_data)
self.newImage.emit()
self.newImage.emit()

View file

@ -1,4 +1,4 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
@ -12,7 +12,6 @@ if TYPE_CHECKING:
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
class PrintJobOutputModel(QObject):
stateChanged = pyqtSignal()
timeTotalChanged = pyqtSignal()
@ -147,4 +146,4 @@ class PrintJobOutputModel(QObject):
@pyqtSlot(str)
def setState(self, state):
self._output_controller.setJobState(self, state)
self._output_controller.setJobState(self, state)

View file

@ -50,7 +50,7 @@ class PrinterOutputModel(QObject):
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
self._extruders]
self._camera = None
self._camera = None # type: Optional[NetworkCamera]
@pyqtProperty(str, constant = True)
def firmwareVersion(self) -> str:

View file

@ -1,9 +1,12 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
class BlockSlicingDecorator(SceneNodeDecorator):
def __init__(self):
def __init__(self) -> None:
super().__init__()
def isBlockSlicing(self):
def isBlockSlicing(self) -> bool:
return True

View file

@ -145,13 +145,11 @@ class CuraContainerStack(ContainerStack):
def setDefinition(self, new_definition: DefinitionContainerInterface) -> None:
self.replaceContainer(_ContainerIndexes.Definition, new_definition)
## Get the definition container.
#
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@pyqtProperty(QObject, fset = setDefinition, notify = pyqtContainersChanged)
def definition(self) -> DefinitionContainer:
def getDefinition(self) -> "DefinitionContainer":
return cast(DefinitionContainer, self._containers[_ContainerIndexes.Definition])
definition = pyqtProperty(QObject, fget = getDefinition, fset = setDefinition, notify = pyqtContainersChanged)
@override(ContainerStack)
def getBottom(self) -> "DefinitionContainer":
return self.definition

View file

@ -129,7 +129,7 @@ class CuraStackBuilder:
# get material container for extruders
material_container = application.empty_material_container
material_node = material_manager.getDefaultMaterial(global_stack, extruder_position, extruder_variant_name,
material_node = material_manager.getDefaultMaterial(global_stack, str(extruder_position), extruder_variant_name,
extruder_definition = extruder_definition)
if material_node and material_node.getContainer():
material_container = material_node.getContainer()
@ -145,7 +145,6 @@ class CuraStackBuilder:
quality_container = application.empty_quality_container
)
new_extruder.setNextStack(global_stack)
global_stack.addExtruder(new_extruder)
registry.addContainer(new_extruder)

View file

@ -374,8 +374,6 @@ class ExtruderManager(QObject):
extruder_definition = container_registry.findDefinitionContainers(id = expected_extruder_definition_0_id)[0]
extruder_stack_0.definition = extruder_definition
extruder_stack_0.setNextStack(global_stack)
## Get all extruder values for a certain setting.
#
# This is exposed to qml for display purposes

View file

@ -65,16 +65,33 @@ class ExtruderStack(CuraContainerStack):
def getLoadingPriority(cls) -> int:
return 3
compatibleMaterialDiameterChanged = pyqtSignal()
## 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:
def getCompatibleMaterialDiameter(self) -> float:
context = PropertyEvaluationContext(self)
context.context["evaluate_from_container_index"] = _ContainerIndexes.Variant
return self.getProperty("material_diameter", "value", context = context)
return float(self.getProperty("material_diameter", "value", context = context))
def setCompatibleMaterialDiameter(self, value: float) -> None:
old_approximate_diameter = self.getApproximateMaterialDiameter()
if self.getCompatibleMaterialDiameter() != value:
self.definitionChanges.setProperty("material_diameter", "value", value)
self.compatibleMaterialDiameterChanged.emit()
# Emit approximate diameter changed signal if needed
if old_approximate_diameter != self.getApproximateMaterialDiameter():
self.approximateMaterialDiameterChanged.emit()
compatibleMaterialDiameter = pyqtProperty(float, fset = setCompatibleMaterialDiameter,
fget = getCompatibleMaterialDiameter,
notify = compatibleMaterialDiameterChanged)
approximateMaterialDiameterChanged = pyqtSignal()
## Return the approximate filament diameter that the machine requires.
#
@ -84,9 +101,11 @@ class ExtruderStack(CuraContainerStack):
# 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))
def getApproximateMaterialDiameter(self) -> float:
return round(self.getCompatibleMaterialDiameter())
approximateMaterialDiameter = pyqtProperty(float, fget = getApproximateMaterialDiameter,
notify = approximateMaterialDiameterChanged)
## Overridden from ContainerStack
#

View file

@ -20,7 +20,6 @@ from UM.Message import Message
from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique
import cura.CuraApplication
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.PrinterOutputDevice import PrinterOutputDevice
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
@ -29,6 +28,9 @@ from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.cura_empty_instance_containers import (empty_definition_changes_container, empty_variant_container,
empty_material_container, empty_quality_container,
empty_quality_changes_container)
from .CuraStackBuilder import CuraStackBuilder
@ -36,6 +38,7 @@ from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
from cura.Settings.CuraContainerStack import CuraContainerStack
from cura.Settings.GlobalStack import GlobalStack
from cura.Machines.MaterialManager import MaterialManager
@ -47,7 +50,7 @@ if TYPE_CHECKING:
class MachineManager(QObject):
def __init__(self, parent: QObject = None) -> None:
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
super().__init__(parent)
self._active_container_stack = None # type: Optional[ExtruderStack]
@ -66,9 +69,10 @@ class MachineManager(QObject):
self._instance_container_timer.setSingleShot(True)
self._instance_container_timer.timeout.connect(self.__emitChangedSignals)
self._application = cura.CuraApplication.CuraApplication.getInstance() #type: cura.CuraApplication.CuraApplication
self._application = application
self._container_registry = self._application.getContainerRegistry()
self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
self._application.getContainerRegistry().containerLoadComplete.connect(self._onContainersChanged)
self._container_registry.containerLoadComplete.connect(self._onContainersChanged)
## When the global container is changed, active material probably needs to be updated.
self.globalContainerChanged.connect(self.activeMaterialChanged)
@ -80,13 +84,6 @@ class MachineManager(QObject):
self._stacks_have_errors = None # type: Optional[bool]
self._empty_container = CuraContainerRegistry.getInstance().getEmptyInstanceContainer() #type: InstanceContainer
self._empty_definition_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0] #type: InstanceContainer
self._empty_variant_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_variant")[0] #type: InstanceContainer
self._empty_material_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_material")[0] #type: InstanceContainer
self._empty_quality_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] #type: InstanceContainer
self._empty_quality_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0] #type: InstanceContainer
self._onGlobalContainerChanged()
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
@ -192,19 +189,21 @@ class MachineManager(QObject):
for extruder in self._global_container_stack.extruders.values():
extruder_configuration = ExtruderConfigurationModel()
# For compare just the GUID is needed at this moment
mat_type = extruder.material.getMetaDataEntry("material") if extruder.material != self._empty_material_container else None
mat_guid = extruder.material.getMetaDataEntry("GUID") if extruder.material != self._empty_material_container else None
mat_color = extruder.material.getMetaDataEntry("color_name") if extruder.material != self._empty_material_container else None
mat_brand = extruder.material.getMetaDataEntry("brand") if extruder.material != self._empty_material_container else None
mat_name = extruder.material.getMetaDataEntry("name") if extruder.material != self._empty_material_container else None
mat_type = extruder.material.getMetaDataEntry("material") if extruder.material != empty_material_container else None
mat_guid = extruder.material.getMetaDataEntry("GUID") if extruder.material != empty_material_container else None
mat_color = extruder.material.getMetaDataEntry("color_name") if extruder.material != empty_material_container else None
mat_brand = extruder.material.getMetaDataEntry("brand") if extruder.material != empty_material_container else None
mat_name = extruder.material.getMetaDataEntry("name") if extruder.material != empty_material_container else None
material_model = MaterialOutputModel(mat_guid, mat_type, mat_color, mat_brand, mat_name)
extruder_configuration.position = int(extruder.getMetaDataEntry("position"))
extruder_configuration.material = material_model
extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != self._empty_variant_container else None
extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != empty_variant_container else None
self._current_printer_configuration.extruderConfigurations.append(extruder_configuration)
self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != self._empty_variant_container else None
# an empty build plate configuration from the network printer is presented as an empty string, so use "" for an
# empty build plate.
self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != empty_variant_container else ""
self.currentConfigurationChanged.emit()
@pyqtSlot(QObject, result = bool)
@ -258,14 +257,14 @@ class MachineManager(QObject):
# Global stack can have only a variant if it is a buildplate
global_variant = self._global_container_stack.variant
if global_variant != self._empty_variant_container:
if global_variant != empty_variant_container:
if global_variant.getMetaDataEntry("hardware_type") != "buildplate":
self._global_container_stack.setVariant(self._empty_variant_container)
self._global_container_stack.setVariant(empty_variant_container)
# set the global material to empty as we now use the extruder stack at all times - CURA-4482
global_material = self._global_container_stack.material
if global_material != self._empty_material_container:
self._global_container_stack.setMaterial(self._empty_material_container)
if global_material != empty_material_container:
self._global_container_stack.setMaterial(empty_material_container)
# Listen for changes on all extruder stacks
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
@ -367,6 +366,10 @@ class MachineManager(QObject):
return
global_stack = containers[0]
# Make sure that the default machine actions for this machine have been added
self._application.getMachineActionManager().addDefaultMachineActions(global_stack)
ExtruderManager.getInstance()._fixSingleExtrusionMachineExtruderDefinition(global_stack)
if not global_stack.isValid():
# Mark global stack as invalid
@ -593,7 +596,7 @@ class MachineManager(QObject):
def globalVariantName(self) -> str:
if self._global_container_stack:
variant = self._global_container_stack.variant
if variant and not isinstance(variant, type(self._empty_variant_container)):
if variant and not isinstance(variant, type(empty_variant_container)):
return variant.getName()
return ""
@ -781,7 +784,7 @@ class MachineManager(QObject):
if not stack.isEnabled:
continue
material_container = stack.material
if material_container == self._empty_material_container:
if material_container == empty_material_container:
continue
if material_container.getMetaDataEntry("buildplate_compatible"):
buildplate_compatible = buildplate_compatible and material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName]
@ -803,7 +806,7 @@ class MachineManager(QObject):
extruder_stacks = self._global_container_stack.extruders.values()
for stack in extruder_stacks:
material_container = stack.material
if material_container == self._empty_material_container:
if material_container == empty_material_container:
continue
buildplate_compatible = material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_compatible") else True
buildplate_usable = material_container.getMetaDataEntry("buildplate_recommended")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_recommended") else True
@ -873,7 +876,7 @@ class MachineManager(QObject):
extruder_manager = self._application.getExtruderManager()
definition_changes_container = self._global_container_stack.definitionChanges
if not self._global_container_stack or definition_changes_container == self._empty_definition_changes_container:
if not self._global_container_stack or definition_changes_container == empty_definition_changes_container:
return
previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
@ -1072,7 +1075,7 @@ class MachineManager(QObject):
for stack in active_stacks:
variant_container = stack.variant
position = stack.getMetaDataEntry("position")
if variant_container and variant_container != self._empty_variant_container:
if variant_container and variant_container != empty_variant_container:
result[position] = variant_container.getName()
return result
@ -1086,11 +1089,11 @@ class MachineManager(QObject):
return
self._current_quality_group = None
self._current_quality_changes_group = None
self._global_container_stack.quality = self._empty_quality_container
self._global_container_stack.qualityChanges = self._empty_quality_changes_container
self._global_container_stack.quality = empty_quality_container
self._global_container_stack.qualityChanges = empty_quality_changes_container
for extruder in self._global_container_stack.extruders.values():
extruder.quality = self._empty_quality_container
extruder.qualityChanges = self._empty_quality_changes_container
extruder.quality = empty_quality_container
extruder.qualityChanges = empty_quality_changes_container
self.activeQualityGroupChanged.emit()
self.activeQualityChangesGroupChanged.emit()
@ -1115,13 +1118,13 @@ class MachineManager(QObject):
# Set quality and quality_changes for the GlobalStack
self._global_container_stack.quality = quality_group.node_for_global.getContainer()
if empty_quality_changes:
self._global_container_stack.qualityChanges = self._empty_quality_changes_container
self._global_container_stack.qualityChanges = empty_quality_changes_container
# Set quality and quality_changes for each ExtruderStack
for position, node in quality_group.nodes_for_extruders.items():
self._global_container_stack.extruders[str(position)].quality = node.getContainer()
if empty_quality_changes:
self._global_container_stack.extruders[str(position)].qualityChanges = self._empty_quality_changes_container
self._global_container_stack.extruders[str(position)].qualityChanges = empty_quality_changes_container
self.activeQualityGroupChanged.emit()
self.activeQualityChangesGroupChanged.emit()
@ -1147,8 +1150,8 @@ class MachineManager(QObject):
if quality_group is None:
self._fixQualityChangesGroupToNotSupported(quality_changes_group)
quality_changes_container = self._empty_quality_changes_container
quality_container = self._empty_quality_container # type: Optional[InstanceContainer]
quality_changes_container = empty_quality_changes_container
quality_container = empty_quality_container # type: Optional[InstanceContainer]
if quality_changes_group.node_for_global and quality_changes_group.node_for_global.getContainer():
quality_changes_container = cast(InstanceContainer, quality_changes_group.node_for_global.getContainer())
if quality_group is not None and quality_group.node_for_global and quality_group.node_for_global.getContainer():
@ -1163,8 +1166,8 @@ class MachineManager(QObject):
if quality_group is not None:
quality_node = quality_group.nodes_for_extruders.get(position)
quality_changes_container = self._empty_quality_changes_container
quality_container = self._empty_quality_container
quality_changes_container = empty_quality_changes_container
quality_container = empty_quality_container
if quality_changes_node and quality_changes_node.getContainer():
quality_changes_container = cast(InstanceContainer, quality_changes_node.getContainer())
if quality_node and quality_node.getContainer():
@ -1198,7 +1201,7 @@ class MachineManager(QObject):
self._global_container_stack.extruders[position].material = container_node.getContainer()
root_material_id = container_node.getMetaDataEntry("base_file", None)
else:
self._global_container_stack.extruders[position].material = self._empty_material_container
self._global_container_stack.extruders[position].material = empty_material_container
root_material_id = None
# The _current_root_material_id is used in the MaterialMenu to see which material is selected
if root_material_id != self._current_root_material_id[position]:
@ -1273,14 +1276,10 @@ class MachineManager(QObject):
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
current_nozzle_name = None
if extruder.variant.getId() != self._empty_variant_container.getId():
if extruder.variant.getId() != empty_variant_container.getId():
current_nozzle_name = extruder.variant.getMetaDataEntry("name")
from UM.Settings.Interfaces import PropertyEvaluationContext
from cura.Settings.CuraContainerStack import _ContainerIndexes
context = PropertyEvaluationContext(extruder)
context.context["evaluate_from_container_index"] = _ContainerIndexes.DefinitionChanges
material_diameter = extruder.getProperty("material_diameter", "value", context)
material_diameter = extruder.getCompatibleMaterialDiameter()
candidate_materials = self._material_manager.getAvailableMaterials(
self._global_container_stack.definition,
current_nozzle_name,
@ -1348,12 +1347,12 @@ class MachineManager(QObject):
if variant_container_node:
self._setVariantNode(position, variant_container_node)
else:
self._global_container_stack.extruders[position].variant = self._empty_variant_container
self._global_container_stack.extruders[position].variant = empty_variant_container
if material_container_node:
self._setMaterial(position, material_container_node)
else:
self._global_container_stack.extruders[position].material = self._empty_material_container
self._global_container_stack.extruders[position].material = empty_material_container
self.updateMaterialWithVariant(position)
if configuration.buildplateConfiguration is not None:
@ -1361,9 +1360,9 @@ class MachineManager(QObject):
if global_variant_container_node:
self._setGlobalVariant(global_variant_container_node)
else:
self._global_container_stack.variant = self._empty_variant_container
self._global_container_stack.variant = empty_variant_container
else:
self._global_container_stack.variant = self._empty_variant_container
self._global_container_stack.variant = empty_variant_container
self._updateQualityWithMaterial()
# See if we need to show the Discard or Keep changes screen
@ -1415,7 +1414,7 @@ class MachineManager(QObject):
position = str(position)
extruder_stack = self._global_container_stack.extruders[position]
nozzle_name = extruder_stack.variant.getName()
material_diameter = extruder_stack.approximateMaterialDiameter
material_diameter = extruder_stack.getApproximateMaterialDiameter()
material_node = self._material_manager.getMaterialNode(machine_definition_id, nozzle_name, buildplate_name,
material_diameter, root_material_id)
self.setMaterial(position, material_node)
@ -1481,7 +1480,7 @@ class MachineManager(QObject):
# This is not changing the quality for the active machine !!!!!!!!
global_stack.quality = quality_group.node_for_global.getContainer()
for extruder_nr, extruder_stack in global_stack.extruders.items():
quality_container = self._empty_quality_container
quality_container = empty_quality_container
if extruder_nr in quality_group.nodes_for_extruders:
container = quality_group.nodes_for_extruders[extruder_nr].getContainer()
quality_container = container if container is not None else quality_container
@ -1525,7 +1524,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = activeQualityGroupChanged)
def activeQualityOrQualityChangesName(self) -> str:
name = self._empty_quality_container.getName()
name = empty_quality_container.getName()
if self._current_quality_changes_group:
name = self._current_quality_changes_group.name
elif self._current_quality_group: