diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 3ffa4291ba..087f081ae9 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -5,6 +5,7 @@ import copy import os import sys import time +from typing import cast, TYPE_CHECKING, Optional import numpy @@ -13,8 +14,6 @@ from PyQt5.QtGui import QColor, QIcon from PyQt5.QtWidgets import QMessageBox from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType -from typing import cast, TYPE_CHECKING - from UM.Scene.SceneNode import SceneNode from UM.Scene.Camera import Camera from UM.Math.Vector import Vector @@ -97,6 +96,8 @@ from . import CuraSplashScreen from . import CameraImageProvider from . import MachineActionManager +from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager + from cura.Settings.MachineManager import MachineManager from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.UserChangesModel import UserChangesModel @@ -158,6 +159,8 @@ class CuraApplication(QtApplication): self._boot_loading_time = time.time() + self._on_exit_callback_manager = OnExitCallbackManager(self) + # Variables set from CLI self._files_to_open = [] self._use_single_instance = False @@ -279,6 +282,8 @@ 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) @@ -520,8 +525,8 @@ class CuraApplication(QtApplication): def setNeedToShowUserAgreement(self, set_value = True): self._need_to_show_user_agreement = set_value - ## The "Quit" button click event handler. - @pyqtSlot() + # 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): Logger.log("i", "Close application") main_window = self.getMainWindow() @@ -530,6 +535,32 @@ class CuraApplication(QtApplication): else: self.exit(0) + # This function first performs all upon-exit checks such as USB printing that is in progress. + # Use this to close the application. + @pyqtSlot() + def checkAndExitApplication(self) -> None: + self._on_exit_callback_manager.resetCurrentState() + self._on_exit_callback_manager.triggerNextCallback() + + @pyqtSlot(result = bool) + def getIsAllChecksPassed(self) -> bool: + return self._on_exit_callback_manager.getIsAllChecksPassed() + + def getOnExitCallbackManager(self) -> "OnExitCallbackManager": + return self._on_exit_callback_manager + + def triggerNextExitCheck(self) -> None: + self._on_exit_callback_manager.triggerNextCallback() + + showConfirmExitDialog = pyqtSignal(str, arguments = ["message"]) + + def setConfirmExitDialogCallback(self, callback): + self._confirm_exit_dialog_callback = callback + + @pyqtSlot(bool) + def callConfirmExitDialogCallback(self, yes_or_no: bool): + self._confirm_exit_dialog_callback(yes_or_no) + ## Signal to connect preferences action in QML showPreferencesWindow = pyqtSignal() @@ -1701,22 +1732,3 @@ class CuraApplication(QtApplication): @pyqtSlot() def showMoreInformationDialogForAnonymousDataCollection(self): cast(SliceInfo, self._plugin_registry.getPluginObject("SliceInfoPlugin")).showMoreInfoDialog() - - ## Signal to check whether the application can be closed or not - checkCuraCloseChange = pyqtSignal() - - # This variable is necessary to ensure that all methods that were subscribed for the checkCuraCloseChange event - # have been passed checks - _isCuraCanBeClosed = True - - def setCuraCanBeClosed(self, value: bool): - self._isCuraCanBeClosed = value - - @pyqtSlot(result=bool) - def preCloseEventHandler(self)-> bool: - - # If any of checks failed then then _isCuraCanBeClosed should be set to False and Cura will not be closed - # after clicking the quit button - self.checkCuraCloseChange.emit() - - return self._isCuraCanBeClosed diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 216637db21..2b8ff4a234 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -46,19 +46,21 @@ class ContainerManager(QObject): self._quality_manager = self._application.getQualityManager() self._container_name_filters = {} # type: Dict[str, Dict[str, Any]] - @pyqtSlot(str, str, str, result=str) - def getContainerMetaDataEntry(self, container_id, entry_name, sub_entry: Optional[str] = None): + @pyqtSlot(str, str, result=str) + def getContainerMetaDataEntry(self, container_id: str, entry_names: str) -> str: metadatas = self._container_registry.findContainersMetadata(id = container_id) if not metadatas: Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id) return "" - sub_data = metadatas[0].get(entry_name, "") - result = str(sub_data) - if sub_entry: - result = str(sub_data.get(sub_entry, "")) - - return result + entries = entry_names.split("/") + result = metadatas[0] + while entries: + entry = entries.pop(0) + result = result.get(entry, {}) + if not result: + return "" + return str(result) ## Set a metadata entry of the specified container. # @@ -73,6 +75,7 @@ class ContainerManager(QObject): # # \return True if successful, False if not. # TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this. + # Update: In order for QML to use objects and sub objects, those (sub) objects must all be QObject. Is that what we want? @pyqtSlot("QVariant", str, str) def setContainerMetaDataEntry(self, container_node, entry_name, entry_value): root_material_id = container_node.metadata["base_file"] @@ -107,63 +110,6 @@ class ContainerManager(QObject): 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) - ## Set a setting property of the specified container. - # - # This will set the specified property of the specified setting of the container - # and all containers that share the same base_file (if any). The latter only - # happens for material containers. - # - # \param container_id \type{str} The ID of the container to change. - # \param setting_key \type{str} The key of the setting. - # \param property_name \type{str} The name of the property, eg "value". - # \param property_value \type{str} The new value of the property. - # - # \return True if successful, False if not. - @pyqtSlot(str, str, str, str, result = bool) - def setContainerProperty(self, container_id, setting_key, property_name, property_value): - if self._container_registry.isReadOnly(container_id): - Logger.log("w", "Cannot set properties of read-only container %s.", container_id) - return False - - containers = self._container_registry.findContainers(id = container_id) - if not containers: - Logger.log("w", "Could not set properties of container %s because it was not found.", container_id) - return False - - container = containers[0] - - container.setProperty(setting_key, property_name, property_value) - - basefile = container.getMetaDataEntry("base_file", container_id) - for sibbling_container in self._container_registry.findInstanceContainers(base_file = basefile): - if sibbling_container != container: - sibbling_container.setProperty(setting_key, property_name, property_value) - - return True - - ## Get a setting property of the specified container. - # - # This will get the specified property of the specified setting of the - # specified container. - # - # \param container_id The ID of the container to get the setting property - # of. - # \param setting_key The key of the setting to get the property of. - # \param property_name The property to obtain. - # \return The value of the specified property. The type of this property - # value depends on the type of the property. For instance, the "value" - # property of an integer setting will be a Python int, but the "value" - # property of an enum setting will be a Python str. - @pyqtSlot(str, str, str, result = QVariant) - def getContainerProperty(self, container_id: str, setting_key: str, property_name: str): - containers = self._container_registry.findContainers(id = container_id) - if not containers: - Logger.log("w", "Could not get properties of container %s because it was not found.", container_id) - return "" - container = containers[0] - - return container.getProperty(setting_key, property_name) - @pyqtSlot(str, result = str) def makeUniqueName(self, original_name): return self._container_registry.uniqueName(original_name) diff --git a/cura/TaskManagement/OnExitCallbackManager.py b/cura/TaskManagement/OnExitCallbackManager.py new file mode 100644 index 0000000000..2e8e42595b --- /dev/null +++ b/cura/TaskManagement/OnExitCallbackManager.py @@ -0,0 +1,69 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import TYPE_CHECKING, Callable, List + +from UM.Logger import Logger + +if TYPE_CHECKING: + from cura.CuraApplication import CuraApplication + + +# +# This class manages a all registered upon-exit checks that need to be perform when the application tries to exit. +# For example, to show a confirmation dialog when there is USB printing in progress, etc. All callbacks will be called +# in the order of when they got registered. If all callbacks "passes", that is, for example, if the user clicks "yes" +# on the exit confirmation dialog or nothing that's blocking the exit, then the application will quit after that. +# +class OnExitCallbackManager: + + def __init__(self, application: "CuraApplication") -> None: + self._application = application + self._on_exit_callback_list = list() # type: List[Callable] + self._current_callback_idx = 0 + self._is_all_checks_passed = False + + def addCallback(self, callback: Callable) -> None: + self._on_exit_callback_list.append(callback) + Logger.log("d", "on-app-exit callback [%s] added.", callback) + + # Reset the current state so the next time it will call all the callbacks again. + def resetCurrentState(self) -> None: + self._current_callback_idx = 0 + self._is_all_checks_passed = False + + def getIsAllChecksPassed(self) -> bool: + return self._is_all_checks_passed + + # Trigger the next callback if available. If not, it means that all callbacks have "passed", which means we should + # not block the application to quit, and it will call the application to actually quit. + def triggerNextCallback(self) -> None: + # Get the next callback and schedule that if + this_callback = None + if self._current_callback_idx < len(self._on_exit_callback_list): + this_callback = self._on_exit_callback_list[self._current_callback_idx] + self._current_callback_idx += 1 + + if this_callback is not None: + Logger.log("d", "Scheduled the next on-app-exit callback [%s]", this_callback) + self._application.callLater(this_callback) + else: + Logger.log("d", "No more on-app-exit callbacks to process. Tell the app to exit.") + + self._is_all_checks_passed = True + + # Tell the application to exit + self._application.callLater(self._application.closeApplication) + + # This is the callback function which an on-exit callback should call when it finishes, it should provide the + # "should_proceed" flag indicating whether this check has "passed", or in other words, whether quiting the + # application should be blocked. If the last on-exit callback doesn't block the quiting, it will call the next + # registered on-exit callback if available. + def onCurrentCallbackFinished(self, should_proceed: bool = True) -> None: + if not should_proceed: + Logger.log("d", "on-app-exit callback finished and we should not proceed.") + # Reset the state + self.resetCurrentState() + return + + self.triggerNextCallback() diff --git a/cura/TaskManagement/__init__.py b/cura/TaskManagement/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index b588e7d9d5..9aff42a9e1 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -457,7 +457,8 @@ class CuraEngineBackend(QObject, Backend): # Only count sliceable objects if node.callDecoration("isSliceable"): build_plate_number = node.callDecoration("getBuildPlateNumber") - num_objects[build_plate_number] += 1 + if build_plate_number is not None: + num_objects[build_plate_number] += 1 return num_objects ## Listener for when the scene has changed. @@ -490,7 +491,9 @@ class CuraEngineBackend(QObject, Backend): if mesh_data and mesh_data.getVertices() is None: return - build_plate_changed.add(source_build_plate_number) + # There are some SceneNodes that do not have any build plate associated, then do not add to the list. + if source_build_plate_number is not None: + build_plate_changed.add(source_build_plate_number) if not build_plate_changed: return diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index fc9558056b..109888a15d 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -88,6 +88,25 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._command_received = Event() self._command_received.set() + CuraApplication.getInstance().getOnExitCallbackManager().addCallback(self._checkActivePrintingUponAppExit) + + # This is a callback function that checks if there is any printing in progress via USB when the application tries + # to exit. If so, it will show a confirmation before + def _checkActivePrintingUponAppExit(self) -> None: + application = CuraApplication.getInstance() + if not self._is_printing: + # This USB printer is not printing, so we have nothing to do. Call the next callback if exists. + application.triggerNextExitCheck() + return + + application.setConfirmExitDialogCallback(self._onConfirmExitDialogResult) + application.showConfirmExitDialog.emit(catalog.i18nc("@label", "A USB print is in progress, closing Cura will stop this print. Are you sure?")) + + def _onConfirmExitDialogResult(self, result: bool) -> None: + if result: + application = CuraApplication.getInstance() + application.triggerNextExitCheck() + ## Reset USB device settings # def resetDeviceSettings(self): @@ -435,9 +454,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._gcode_position += 1 - def getIsPrinting(self)-> bool: - return self._is_printing - class FirmwareUpdateState(IntEnum): idle = 0 diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py index b2dc24480c..2ee85187ee 100644 --- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py +++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py @@ -6,8 +6,7 @@ import platform import time import serial.tools.list_ports -from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QCoreApplication -from PyQt5.QtWidgets import QMessageBox +from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal from UM.Logger import Logger from UM.Resources import Resources @@ -51,11 +50,6 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin): self._application.globalContainerStackChanged.connect(self.updateUSBPrinterOutputDevices) - self._application.checkCuraCloseChange.connect(self.checkWheterUSBIsActiveOrNot) - - self._lock = threading.Lock() - self._confirm_dialog_visible = False - # The method updates/reset the USB settings for all connected USB devices def updateUSBPrinterOutputDevices(self): for key, device in self._usb_output_devices.items(): @@ -190,51 +184,3 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin): @classmethod def getInstance(cls, *args, **kwargs) -> "USBPrinterOutputDeviceManager": return cls.__instance - - # The method checks whether a printer is printing via USB or not before closing cura. If the printer is printing then pop up a - # dialog to confirm stop printing - def checkWheterUSBIsActiveOrNot(self)-> None: - - is_printing = False - for key, device in self._usb_output_devices.items(): - if type(device) is USBPrinterOutputDevice.USBPrinterOutputDevice: - if device.getIsPrinting(): - is_printing = True - break - - if is_printing: - if threading.current_thread() != threading.main_thread(): - self._lock.acquire() - self._confirm_dialog_visible = True - - CuraApplication.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Confirm stop printing"), - i18n_catalog.i18nc("@window:message","A USB print is in progress, closing Cura will stop this print. Are you sure?"), - buttons=QMessageBox.Yes + QMessageBox.No, - icon=QMessageBox.Question, - callback=self._messageBoxCallback) - # Wait for dialog result - self.waitForClose() - - ## Block thread until the dialog is closed. - def waitForClose(self)-> None: - if self._confirm_dialog_visible: - if threading.current_thread() != threading.main_thread(): - self._lock.acquire() - self._lock.release() - else: - # If this is not run from a separate thread, we need to ensure that the events are still processed. - while self._confirm_dialog_visible: - time.sleep(1 / 50) - QCoreApplication.processEvents() # Ensure that the GUI does not freeze. - - def _messageBoxCallback(self, button): - if button == QMessageBox.Yes: - self._application.setCuraCanBeClosed(True) - else: - self._application.setCuraCanBeClosed(False) - - self._confirm_dialog_visible = False - try: - self._lock.release() - except: - pass \ No newline at end of file diff --git a/resources/definitions/prusa_i3.def.json b/resources/definitions/prusa_i3.def.json index a3759eb6c0..c676f7fe96 100644 --- a/resources/definitions/prusa_i3.def.json +++ b/resources/definitions/prusa_i3.def.json @@ -40,6 +40,14 @@ [18, -18] ] }, + "machine_head_with_fans_polygon": { + "default_value": [ + [-75, -18], + [-75, 35], + [18, 35], + [18, -18] + ] + }, "gantry_height": { "default_value": 55 }, diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 20638e8850..61d2c9f655 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -1,11 +1,11 @@ // 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.Controls.Styles 1.1 +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.1 -import QtQuick.Dialogs 1.1 +import QtQuick.Dialogs 1.2 import UM 1.3 as UM import Cura 1.0 as Cura @@ -22,12 +22,6 @@ UM.MainWindow backgroundColor: UM.Theme.getColor("viewport_background") - // Event which does the check before closing the window - onPreCloseChange: - { - event.accepted = CuraApplication.preCloseEventHandler() - } - // This connection is here to support legacy printer output devices that use the showPrintMonitor signal on Application to switch to the monitor stage // It should be phased out in newer plugin versions. Connections @@ -120,31 +114,10 @@ UM.MainWindow RecentFilesMenu { } - MenuSeparator { } - - MenuItem - { - text: catalog.i18nc("@action:inmenu menubar:file", "&Save Selection to File"); - enabled: UM.Selection.hasSelection; - iconName: "document-save-as"; - onTriggered: UM.OutputDeviceManager.requestWriteSelectionToDevice("local_file", PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"}); - } - - MenuItem - { - id: saveAsMenu - text: catalog.i18nc("@title:menu menubar:file", "Save &As...") - onTriggered: - { - var localDeviceId = "local_file"; - UM.OutputDeviceManager.requestWriteToDevice(localDeviceId, PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"}); - } - } - MenuItem { id: saveWorkspaceMenu - text: catalog.i18nc("@title:menu menubar:file","Save &Project...") + text: catalog.i18nc("@title:menu menubar:file","&Save...") onTriggered: { var args = { "filter_by_machine": false, "file_type": "workspace", "preferred_mimetype": "application/x-curaproject+xml" }; @@ -160,6 +133,29 @@ UM.MainWindow } } + MenuSeparator { } + + MenuItem + { + id: saveAsMenu + text: catalog.i18nc("@title:menu menubar:file", "&Export...") + onTriggered: + { + var localDeviceId = "local_file"; + UM.OutputDeviceManager.requestWriteToDevice(localDeviceId, PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"}); + } + } + + MenuItem + { + text: catalog.i18nc("@action:inmenu menubar:file", "Export Selection..."); + enabled: UM.Selection.hasSelection; + iconName: "document-save-as"; + onTriggered: UM.OutputDeviceManager.requestWriteSelectionToDevice("local_file", PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"}); + } + + MenuSeparator { } + MenuItem { action: Cura.Actions.reloadAll; } MenuSeparator { } @@ -704,10 +700,50 @@ UM.MainWindow id: contextMenu } + onPreClosing: + { + close.accepted = CuraApplication.getIsAllChecksPassed(); + if (!close.accepted) + { + CuraApplication.checkAndExitApplication(); + } + } + + MessageDialog + { + id: exitConfirmationDialog + title: catalog.i18nc("@title:window", "Closing Cura") + text: catalog.i18nc("@label", "Are you sure you want to exit Cura?") + icon: StandardIcon.Question + modality: Qt.ApplicationModal + standardButtons: StandardButton.Yes | StandardButton.No + onYes: CuraApplication.callConfirmExitDialogCallback(true) + onNo: CuraApplication.callConfirmExitDialogCallback(false) + onRejected: CuraApplication.callConfirmExitDialogCallback(false) + onVisibilityChanged: + { + if (!visible) + { + // reset the text to default because other modules may change the message text. + text = catalog.i18nc("@label", "Are you sure you want to exit Cura?"); + } + } + } + + Connections + { + target: CuraApplication + onShowConfirmExitDialog: + { + exitConfirmationDialog.text = message; + exitConfirmationDialog.open(); + } + } + Connections { target: Cura.Actions.quit - onTriggered: CuraApplication.closeApplication(); + onTriggered: CuraApplication.exitApplication(); } Connections diff --git a/resources/qml/JobSpecs.qml b/resources/qml/JobSpecs.qml index 3b10cf59d5..20ec8ce289 100644 --- a/resources/qml/JobSpecs.qml +++ b/resources/qml/JobSpecs.qml @@ -81,7 +81,7 @@ Item { text: PrintInformation.jobName horizontalAlignment: TextInput.AlignRight onEditingFinished: { - var new_name = text == "" ? "unnamed" : text; + var new_name = text == "" ? catalog.i18nc("@text Print job name", "unnamed") : text; PrintInformation.setJobName(new_name, true); printJobTextfield.focus = false; } diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index bab0b703d2..0929f1790a 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -103,7 +103,6 @@ TabView 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); } @@ -230,7 +229,7 @@ TabView { // This does not use a SettingPropertyProvider, because we need to make the change to all containers // which derive from the same base_file - var old_diameter = Cura.ContainerManager.getContainerProperty(base.containerId, "material_diameter", "value").toString(); + var old_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "properties/diameter"); var old_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter"); var new_approximate_diameter = getApproximateDiameter(value); if (new_approximate_diameter != Cura.ExtruderManager.getActiveExtruderStack().approximateMaterialDiameter) @@ -242,7 +241,6 @@ TabView 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); } @@ -271,7 +269,7 @@ TabView { id: spoolWeightSpinBox width: scrollView.columnWidth - value: base.getMaterialPreferenceValue(properties.guid, "spool_weight") + value: base.getMaterialPreferenceValue(properties.guid, "spool_weight", Cura.ContainerManager.getContainerMetaDataEntry(properties.container_id, "properties/weight")) suffix: " g" stepSize: 100 decimals: 0 @@ -468,7 +466,7 @@ TabView } if(!spoolWeight) { - spoolWeight = base.getMaterialPreferenceValue(properties.guid, "spool_weight"); + spoolWeight = base.getMaterialPreferenceValue(properties.guid, "spool_weight", Cura.ContainerManager.getContainerMetaDataEntry(properties.container_id, "properties/weight")); } if (diameter == 0 || density == 0 || spoolWeight == 0) @@ -517,21 +515,34 @@ TabView // value has not changed return; } - materialPreferenceValues[material_guid][entry_name] = new_value; + if (entry_name in materialPreferenceValues[material_guid] && new_value.toString() == 0) + { + // no need to store a 0, that's the default, so remove it + materialPreferenceValues[material_guid].delete(entry_name); + if (!(materialPreferenceValues[material_guid])) + { + // remove empty map + materialPreferenceValues.delete(material_guid); + } + } + if (new_value.toString() != 0) + { + // store new value + materialPreferenceValues[material_guid][entry_name] = new_value; + } // store preference UM.Preferences.setValue("cura/material_settings", JSON.stringify(materialPreferenceValues)); } - function getMaterialPreferenceValue(material_guid, entry_name) + function getMaterialPreferenceValue(material_guid, entry_name, default_value) { if(material_guid in materialPreferenceValues && entry_name in materialPreferenceValues[material_guid]) { return materialPreferenceValues[material_guid][entry_name]; } - - var material_weight = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "properties", "weight"); - return material_weight || 0; + default_value = default_value | 0; + return default_value; } // update the display name of the material diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index fb3623569c..e2e3edec2f 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -486,6 +486,7 @@ Item materialProperties.name = currentItem.name ? currentItem.name : "Unknown"; materialProperties.guid = currentItem.guid; + materialProperties.container_id = currentItem.container_id; materialProperties.brand = currentItem.brand ? currentItem.brand : "Unknown"; materialProperties.material = currentItem.material ? currentItem.material : "Unknown"; @@ -543,6 +544,7 @@ Item id: materialProperties property string guid: "00000000-0000-0000-0000-000000000000" + property string container_id: "Unknown"; property string name: "Unknown"; property string profile_type: "Unknown"; property string brand: "Unknown";