diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index b7731c5c8c..6056745c75 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1,9 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -#Type hinting. -from typing import Dict - from PyQt5.QtCore import QObject, QTimer from PyQt5.QtNetwork import QLocalServer from PyQt5.QtNetwork import QLocalSocket @@ -68,6 +65,8 @@ from cura.Machines.Models.QualityManagementModel import QualityManagementModel from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel from cura.Machines.Models.MachineManagementModel import MachineManagementModel +from cura.Machines.Models.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel + from cura.Machines.MachineErrorChecker import MachineErrorChecker from cura.Settings.SettingInheritanceManager import SettingInheritanceManager @@ -91,7 +90,6 @@ from cura.Settings.UserChangesModel import UserChangesModel from cura.Settings.ExtrudersModel import ExtrudersModel from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from cura.Settings.ContainerManager import ContainerManager -from cura.Settings.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel from cura.ObjectsModel import ObjectsModel @@ -101,7 +99,6 @@ from PyQt5.QtGui import QColor, QIcon from PyQt5.QtWidgets import QMessageBox from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType -from configparser import ConfigParser import sys import os.path import numpy @@ -226,6 +223,7 @@ class CuraApplication(QtApplication): self._object_manager = None self._build_plate_model = None self._multi_build_plate_model = None + self._setting_visibility_presets_model = None self._setting_inheritance_manager = None self._simple_mode_settings_manager = None self._cura_scene_controller = None @@ -381,10 +379,6 @@ class CuraApplication(QtApplication): preferences.setDefault("local_file/last_used_type", "text/x-gcode") - default_visibility_profile = SettingVisibilityPresetsModel.getInstance().getItem(0) - - preferences.setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"])) - self.applicationShuttingDown.connect(self.saveSettings) self.engineCreatedSignal.connect(self._onEngineCreated) @@ -457,27 +451,18 @@ class CuraApplication(QtApplication): @pyqtSlot(str) def discardOrKeepProfileChangesClosed(self, option): + global_stack = self.getGlobalContainerStack() if option == "discard": - global_stack = self.getGlobalContainerStack() - for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()): - extruder.getTop().clear() - global_stack.getTop().clear() + for extruder in global_stack.extruders.values(): + extruder.userChanges.clear() + global_stack.userChanges.clear() # if the user decided to keep settings then the user settings should be re-calculated and validated for errors # before slicing. To ensure that slicer uses right settings values elif option == "keep": - global_stack = self.getGlobalContainerStack() - for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()): - user_extruder_container = extruder.getTop() - if user_extruder_container: - user_extruder_container.update() - - user_global_container = global_stack.getTop() - if user_global_container: - user_global_container.update() - - # notify listeners that quality has changed (after user selected discard or keep) - self.getMachineManager().activeQualityChanged.emit() + for extruder in global_stack.extruders.values(): + extruder.userChanges.update() + global_stack.userChanges.update() @pyqtSlot(int) def messageBoxClosed(self, button): @@ -687,6 +672,11 @@ class CuraApplication(QtApplication): self._print_information = PrintInformation.PrintInformation() self._cura_actions = CuraActions.CuraActions(self) + # Initialize setting visibility presets model + self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self) + default_visibility_profile = self._setting_visibility_presets_model.getItem(0) + Preferences.getInstance().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"])) + # Detect in which mode to run and execute that mode if self.getCommandLineOption("headless", False): self.runWithoutGUI() @@ -769,6 +759,10 @@ class CuraApplication(QtApplication): def hasGui(self): return self._use_gui + @pyqtSlot(result = QObject) + def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel: + return self._setting_visibility_presets_model + def getMachineErrorChecker(self, *args) -> MachineErrorChecker: return self._machine_error_checker @@ -895,11 +889,11 @@ class CuraApplication(QtApplication): qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel") qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler") + qmlRegisterType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel") qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel") qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) - qmlRegisterSingletonType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel", SettingVisibilityPresetsModel.createSettingVisibilityPresetsModel) # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) diff --git a/cura/Machines/Models/SettingVisibilityPresetsModel.py b/cura/Machines/Models/SettingVisibilityPresetsModel.py new file mode 100644 index 0000000000..e281d81c39 --- /dev/null +++ b/cura/Machines/Models/SettingVisibilityPresetsModel.py @@ -0,0 +1,176 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import Optional +import os +import urllib.parse +from configparser import ConfigParser + +from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot + +from UM.Logger import Logger +from UM.Qt.ListModel import ListModel +from UM.Preferences import Preferences +from UM.Resources import Resources +from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + + +class SettingVisibilityPresetsModel(ListModel): + IdRole = Qt.UserRole + 1 + NameRole = Qt.UserRole + 2 + SettingsRole = Qt.UserRole + 3 + + def __init__(self, parent = None): + super().__init__(parent) + self.addRoleName(self.IdRole, "id") + self.addRoleName(self.NameRole, "name") + self.addRoleName(self.SettingsRole, "settings") + + self._populate() + basic_item = self.items[1] + basic_visibile_settings = ";".join(basic_item["settings"]) + + self._preferences = Preferences.getInstance() + # Preference to store which preset is currently selected + self._preferences.addPreference("cura/active_setting_visibility_preset", "basic") + # Preference that stores the "custom" set so it can always be restored (even after a restart) + self._preferences.addPreference("cura/custom_visible_settings", basic_visibile_settings) + self._preferences.preferenceChanged.connect(self._onPreferencesChanged) + + self._active_preset_item = self._getItem(self._preferences.getValue("cura/active_setting_visibility_preset")) + # Initialize visible settings if it is not done yet + visible_settings = self._preferences.getValue("general/visible_settings") + if not visible_settings: + self._preferences.setValue("general/visible_settings", ";".join(self._active_preset_item["settings"])) + + self.activePresetChanged.emit() + + def _getItem(self, item_id: str) -> Optional[dict]: + result = None + for item in self.items: + if item["id"] == item_id: + result = item + break + return result + + def _populate(self): + from cura.CuraApplication import CuraApplication + items = [] + for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset): + try: + mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path) + except MimeTypeNotFoundError: + Logger.log("e", "Could not determine mime type of file %s", file_path) + continue + + item_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_path))) + if not os.path.isfile(file_path): + Logger.log("e", "[%s] is not a file", file_path) + continue + + parser = ConfigParser(allow_no_value = True) # accept options without any value, + try: + parser.read([file_path]) + if not parser.has_option("general", "name") or not parser.has_option("general", "weight"): + continue + + settings = [] + for section in parser.sections(): + if section == 'general': + continue + + settings.append(section) + for option in parser[section].keys(): + settings.append(option) + + items.append({ + "id": item_id, + "name": catalog.i18nc("@action:inmenu", parser["general"]["name"]), + "weight": parser["general"]["weight"], + "settings": settings, + }) + + except Exception: + Logger.logException("e", "Failed to load setting preset %s", file_path) + + items.sort(key = lambda k: (int(k["weight"]), k["id"])) + # Put "custom" at the top + items.insert(0, {"id": "custom", + "name": "Custom selection", + "weight": -100, + "settings": []}) + + self.setItems(items) + + @pyqtSlot(str) + def setActivePreset(self, preset_id: str): + if preset_id == self._active_preset_item["id"]: + Logger.log("d", "Same setting visibility preset [%s] selected, do nothing.", preset_id) + return + + preset_item = None + for item in self.items: + if item["id"] == preset_id: + preset_item = item + break + if preset_item is None: + Logger.log("w", "Tried to set active preset to unknown id [%s]", preset_id) + return + + need_to_save_to_custom = self._active_preset_item["id"] == "custom" and preset_id != "custom" + if need_to_save_to_custom: + # Save the current visibility settings to custom + current_visibility_string = self._preferences.getValue("general/visible_settings") + if current_visibility_string: + self._preferences.setValue("cura/custom_visible_settings", current_visibility_string) + + new_visibility_string = ";".join(preset_item["settings"]) + if preset_id == "custom": + # Get settings from the stored custom data + new_visibility_string = self._preferences.getValue("cura/custom_visible_settings") + if new_visibility_string is None: + new_visibility_string = self._preferences.getValue("general/visible_settings") + self._preferences.setValue("general/visible_settings", new_visibility_string) + + self._preferences.setValue("cura/active_setting_visibility_preset", preset_id) + self._active_preset_item = preset_item + self.activePresetChanged.emit() + + activePresetChanged = pyqtSignal() + + @pyqtProperty(str, notify = activePresetChanged) + def activePreset(self) -> str: + return self._active_preset_item["id"] + + def _onPreferencesChanged(self, name: str): + if name != "general/visible_settings": + return + + # Find the preset that matches with the current visible settings setup + visibility_string = self._preferences.getValue("general/visible_settings") + if not visibility_string: + return + + visibility_set = set(visibility_string.split(";")) + matching_preset_item = None + for item in self.items: + if item["id"] == "custom": + continue + if set(item["settings"]) == visibility_set: + matching_preset_item = item + break + + if matching_preset_item is None: + # The new visibility setup is "custom" should be custom + if self._active_preset_item["id"] == "custom": + # We are already in custom, just save the settings + self._preferences.setValue("cura/custom_visible_settings", visibility_string) + else: + self._active_preset_item = self.items[0] # 0 is custom + self.activePresetChanged.emit() + else: + self._active_preset_item = matching_preset_item + self.activePresetChanged.emit() diff --git a/cura/PrinterOutput/ExtruderOutputModel.py b/cura/PrinterOutput/ExtruderOutputModel.py index e4c7f1608e..75b9cc98ac 100644 --- a/cura/PrinterOutput/ExtruderOutputModel.py +++ b/cura/PrinterOutput/ExtruderOutputModel.py @@ -18,6 +18,7 @@ class ExtruderOutputModel(QObject): hotendTemperatureChanged = pyqtSignal() activeMaterialChanged = pyqtSignal() extruderConfigurationChanged = pyqtSignal() + isPreheatingChanged = pyqtSignal() def __init__(self, printer: "PrinterOutputModel", position, parent=None): super().__init__(parent) @@ -30,6 +31,21 @@ class ExtruderOutputModel(QObject): self._extruder_configuration = ExtruderConfigurationModel() self._extruder_configuration.position = self._position + self._is_preheating = False + + def getPrinter(self): + return self._printer + + def getPosition(self): + return self._position + + # Does the printer support pre-heating the bed at all + @pyqtProperty(bool, constant=True) + def canPreHeatHotends(self): + if self._printer: + return self._printer.canPreHeatHotends + return False + @pyqtProperty(QObject, notify = activeMaterialChanged) def activeMaterial(self) -> "MaterialOutputModel": return self._active_material @@ -82,3 +98,25 @@ class ExtruderOutputModel(QObject): if self._extruder_configuration.isValid(): return self._extruder_configuration return None + + def updateIsPreheating(self, pre_heating): + if self._is_preheating != pre_heating: + self._is_preheating = pre_heating + self.isPreheatingChanged.emit() + + @pyqtProperty(bool, notify=isPreheatingChanged) + def isPreheating(self): + return self._is_preheating + + ## Pre-heats the extruder before printer. + # + # \param temperature The temperature to heat the extruder to, in degrees + # Celsius. + # \param duration How long the bed should stay warm, in seconds. + @pyqtSlot(float, float) + def preheatHotend(self, temperature, duration): + self._printer._controller.preheatHotend(self, temperature, duration) + + @pyqtSlot() + def cancelPreheatHotend(self): + self._printer._controller.cancelPreheatHotend(self) \ No newline at end of file diff --git a/cura/PrinterOutput/GenericOutputController.py b/cura/PrinterOutput/GenericOutputController.py new file mode 100644 index 0000000000..a21425af92 --- /dev/null +++ b/cura/PrinterOutput/GenericOutputController.py @@ -0,0 +1,151 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from cura.PrinterOutput.PrinterOutputController import PrinterOutputController +from PyQt5.QtCore import QTimer + +MYPY = False +if MYPY: + from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel + from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel + + +class GenericOutputController(PrinterOutputController): + def __init__(self, output_device): + super().__init__(output_device) + + self._preheat_bed_timer = QTimer() + self._preheat_bed_timer.setSingleShot(True) + self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished) + self._preheat_printer = None + + self._preheat_hotends_timer = QTimer() + self._preheat_hotends_timer.setSingleShot(True) + self._preheat_hotends_timer.timeout.connect(self._onPreheatHotendsTimerFinished) + self._preheat_hotends = set() + + self._output_device.printersChanged.connect(self._onPrintersChanged) + self._active_printer = None + + def _onPrintersChanged(self): + if self._active_printer: + self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged) + self._active_printer.targetBedTemperatureChanged.disconnect(self._onTargetBedTemperatureChanged) + for extruder in self._active_printer.extruders: + extruder.targetHotendTemperatureChanged.disconnect(self._onTargetHotendTemperatureChanged) + + self._active_printer = self._output_device.activePrinter + if self._active_printer: + self._active_printer.stateChanged.connect(self._onPrinterStateChanged) + self._active_printer.targetBedTemperatureChanged.connect(self._onTargetBedTemperatureChanged) + for extruder in self._active_printer.extruders: + extruder.targetHotendTemperatureChanged.connect(self._onTargetHotendTemperatureChanged) + + def _onPrinterStateChanged(self): + if self._active_printer.state != "idle": + if self._preheat_bed_timer.isActive(): + self._preheat_bed_timer.stop() + self._preheat_printer.updateIsPreheating(False) + if self._preheat_hotends_timer.isActive(): + self._preheat_hotends_timer.stop() + for extruder in self._preheat_hotends: + extruder.updateIsPreheating(False) + self._preheat_hotends = set() + + def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed): + self._output_device.sendCommand("G91") + self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed)) + self._output_device.sendCommand("G90") + + def homeHead(self, printer): + self._output_device.sendCommand("G28 X") + self._output_device.sendCommand("G28 Y") + + def homeBed(self, printer): + self._output_device.sendCommand("G28 Z") + + def setJobState(self, job: "PrintJobOutputModel", state: str): + if state == "pause": + self._output_device.pausePrint() + job.updateState("paused") + elif state == "print": + self._output_device.resumePrint() + job.updateState("printing") + elif state == "abort": + self._output_device.cancelPrint() + pass + + def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int): + self._output_device.sendCommand("M140 S%s" % temperature) + + def _onTargetBedTemperatureChanged(self): + if self._preheat_bed_timer.isActive() and self._preheat_printer.targetBedTemperature == 0: + self._preheat_bed_timer.stop() + self._preheat_printer.updateIsPreheating(False) + + def preheatBed(self, printer: "PrinterOutputModel", temperature, duration): + try: + temperature = round(temperature) # The API doesn't allow floating point. + duration = round(duration) + except ValueError: + return # Got invalid values, can't pre-heat. + + self.setTargetBedTemperature(printer, temperature=temperature) + self._preheat_bed_timer.setInterval(duration * 1000) + self._preheat_bed_timer.start() + self._preheat_printer = printer + printer.updateIsPreheating(True) + + def cancelPreheatBed(self, printer: "PrinterOutputModel"): + self.setTargetBedTemperature(printer, temperature=0) + self._preheat_bed_timer.stop() + printer.updateIsPreheating(False) + + def _onPreheatBedTimerFinished(self): + self.setTargetBedTemperature(self._preheat_printer, 0) + self._preheat_printer.updateIsPreheating(False) + + def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: int): + self._output_device.sendCommand("M104 S%s T%s" % (temperature, position)) + + def _onTargetHotendTemperatureChanged(self): + if not self._preheat_hotends_timer.isActive(): + return + + for extruder in self._active_printer.extruders: + if extruder in self._preheat_hotends and extruder.targetHotendTemperature == 0: + extruder.updateIsPreheating(False) + self._preheat_hotends.remove(extruder) + if not self._preheat_hotends: + self._preheat_hotends_timer.stop() + + def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration): + position = extruder.getPosition() + number_of_extruders = len(extruder.getPrinter().extruders) + if position >= number_of_extruders: + return # Got invalid extruder nr, can't pre-heat. + + try: + temperature = round(temperature) # The API doesn't allow floating point. + duration = round(duration) + except ValueError: + return # Got invalid values, can't pre-heat. + + self.setTargetHotendTemperature(extruder.getPrinter(), position, temperature=temperature) + self._preheat_hotends_timer.setInterval(duration * 1000) + self._preheat_hotends_timer.start() + self._preheat_hotends.add(extruder) + extruder.updateIsPreheating(True) + + def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"): + self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0) + if extruder in self._preheat_hotends: + extruder.updateIsPreheating(False) + self._preheat_hotends.remove(extruder) + if not self._preheat_hotends and self._preheat_hotends_timer.isActive(): + self._preheat_hotends_timer.stop() + + def _onPreheatHotendsTimerFinished(self): + for extruder in self._preheat_hotends: + self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0) + self._preheat_hotends = set() diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index eefbd9ae12..1537d51919 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -3,6 +3,8 @@ from UM.Application import Application from UM.Logger import Logger +from UM.Settings.ContainerRegistry import ContainerRegistry +from cura.CuraApplication import CuraApplication from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState @@ -254,6 +256,9 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): self._last_manager_create_time = time() self._manager.authenticationRequired.connect(self._onAuthenticationRequired) + machine_manager = CuraApplication.getInstance().getMachineManager() + machine_manager.checkCorrectGroupName(self.getId(), self.name) + def _registerOnFinishedCallback(self, reply: QNetworkReply, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None: if onFinished is not None: self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished diff --git a/cura/PrinterOutput/PrinterOutputController.py b/cura/PrinterOutput/PrinterOutputController.py index 1d658e79be..b5b0dd0be3 100644 --- a/cura/PrinterOutput/PrinterOutputController.py +++ b/cura/PrinterOutput/PrinterOutputController.py @@ -15,6 +15,7 @@ class PrinterOutputController: self.can_pause = True self.can_abort = True self.can_pre_heat_bed = True + self.can_pre_heat_hotends = True self.can_control_manually = True self._output_device = output_device @@ -33,6 +34,12 @@ class PrinterOutputController: def preheatBed(self, printer: "PrinterOutputModel", temperature, duration): Logger.log("w", "Preheat bed not implemented in controller") + def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"): + Logger.log("w", "Cancel preheat hotend not implemented in controller") + + def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration): + Logger.log("w", "Preheat hotend not implemented in controller") + def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed): Logger.log("w", "Set head position not implemented in controller") diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 712f9b5b1e..e936ace196 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -238,6 +238,13 @@ class PrinterOutputModel(QObject): return self._controller.can_pre_heat_bed return False + # Does the printer support pre-heating the bed at all + @pyqtProperty(bool, constant=True) + def canPreHeatHotends(self): + if self._controller: + return self._controller.can_pre_heat_hotends + return False + # Does the printer support pause at all @pyqtProperty(bool, constant=True) def canPause(self): diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 0cf1c7399f..ab48eaddd2 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -173,12 +173,13 @@ class CuraContainerRegistry(ContainerRegistry): plugin_registry = PluginRegistry.getInstance() extension = file_name.split(".")[-1] - global_container_stack = Application.getInstance().getGlobalContainerStack() - if not global_container_stack: + global_stack = Application.getInstance().getGlobalContainerStack() + if not global_stack: return - machine_extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId())) - machine_extruders.sort(key = lambda k: k.getMetaDataEntry("position")) + machine_extruders = [] + for position in sorted(global_stack.extruders): + machine_extruders.append(global_stack.extruders[position]) for plugin_id, meta_data in self._getIOPlugins("profile_reader"): if meta_data["profile_reader"][0]["extension"] != extension: @@ -200,13 +201,18 @@ class CuraContainerRegistry(ContainerRegistry): # First check if this profile is suitable for this machine global_profile = None + extruder_profiles = [] if len(profile_or_list) == 1: global_profile = profile_or_list[0] else: for profile in profile_or_list: if not profile.getMetaDataEntry("position"): global_profile = profile - break + else: + extruder_profiles.append(profile) + extruder_profiles = sorted(extruder_profiles, key = lambda x: int(x.getMetaDataEntry("position"))) + profile_or_list = [global_profile] + extruder_profiles + if not global_profile: Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name) return { "status": "error", @@ -227,7 +233,7 @@ class CuraContainerRegistry(ContainerRegistry): # Get the expected machine definition. # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode... profile_definition = getMachineDefinitionIDForQualitySearch(machine_definition) - expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_container_stack.definition) + expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition) # And check if the profile_definition matches either one (showing error if not): if profile_definition != expected_machine_definition: @@ -251,8 +257,8 @@ class CuraContainerRegistry(ContainerRegistry): if len(profile_or_list) == 1: global_profile = profile_or_list[0] extruder_profiles = [] - for idx, extruder in enumerate(global_container_stack.extruders.values()): - profile_id = ContainerRegistry.getInstance().uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1)) + for idx, extruder in enumerate(global_stack.extruders.values()): + profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1)) profile = InstanceContainer(profile_id) profile.setName(quality_name) profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) @@ -264,12 +270,12 @@ class CuraContainerRegistry(ContainerRegistry): if idx == 0: # move all per-extruder settings to the first extruder's quality_changes for qc_setting_key in global_profile.getAllKeys(): - settable_per_extruder = global_container_stack.getProperty(qc_setting_key, + settable_per_extruder = global_stack.getProperty(qc_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = global_profile.getProperty(qc_setting_key, "value") - setting_definition = global_container_stack.getSettingDefinition(qc_setting_key) + setting_definition = global_stack.getSettingDefinition(qc_setting_key) new_instance = SettingInstance(setting_definition, profile) new_instance.setProperty("value", setting_value) new_instance.resetState() # Ensure that the state is not seen as a user state. @@ -286,7 +292,7 @@ class CuraContainerRegistry(ContainerRegistry): for profile_index, profile in enumerate(profile_or_list): if profile_index == 0: # This is assumed to be the global profile - profile_id = (global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_") + profile_id = (global_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_") elif profile_index < len(machine_extruders) + 1: # This is assumed to be an extruder profile diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index 4ee5ab3c3b..f179dabd5a 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -210,6 +210,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): item = { "id": "", "name": catalog.i18nc("@menuitem", "Not overridden"), + "enabled": True, "color": "#ffffff", "index": -1, "definition": "" diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index c79d352dcb..b69d91c0d4 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -10,7 +10,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Signal import Signal from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer -import UM.FlameProfiler from UM.FlameProfiler import pyqtSlot from UM import Util @@ -24,7 +23,6 @@ from UM.Settings.SettingFunction import SettingFunction from UM.Signal import postponeSignals, CompressTechnique from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch -from cura.Machines.VariantManager import VariantType from cura.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutput.ConfigurationModel import ConfigurationModel from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel @@ -147,6 +145,7 @@ class MachineManager(QObject): activeStackValueChanged = pyqtSignal() # Emitted whenever a value inside the active stack is changed. activeStackValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed stacksValidationChanged = pyqtSignal() # Emitted whenever a validation is changed + numberExtrudersEnabledChanged = pyqtSignal() # Emitted when the number of extruders that are enabled changed blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly @@ -470,13 +469,13 @@ class MachineManager(QObject): @pyqtProperty(str, notify = outputDevicesChanged) def activeMachineNetworkKey(self) -> str: if self._global_container_stack: - return self._global_container_stack.getMetaDataEntry("um_network_key") + return self._global_container_stack.getMetaDataEntry("um_network_key", "") return "" @pyqtProperty(str, notify = outputDevicesChanged) def activeMachineNetworkGroupName(self) -> str: if self._global_container_stack: - return self._global_container_stack.getMetaDataEntry("connect_group_name") + return self._global_container_stack.getMetaDataEntry("connect_group_name", "") return "" @pyqtProperty(QObject, notify = globalContainerChanged) @@ -662,12 +661,22 @@ class MachineManager(QObject): if other_machine_stacks: self.setActiveMachine(other_machine_stacks[0]["id"]) + metadata = ContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0] + network_key = metadata["um_network_key"] if "um_network_key" in metadata else None ExtruderManager.getInstance().removeMachineExtruders(machine_id) containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) for container in containers: ContainerRegistry.getInstance().removeContainer(container["id"]) ContainerRegistry.getInstance().removeContainer(machine_id) + # If the printer that is being removed is a network printer, the hidden printers have to be also removed + if network_key: + metadata_filter = {"um_network_key": network_key} + hidden_containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) + if hidden_containers: + # This reuses the method and remove all printers recursively + self.removeMachine(hidden_containers[0].getId()) + @pyqtProperty(bool, notify = globalContainerChanged) def hasMaterials(self) -> bool: if self._global_container_stack: @@ -872,7 +881,13 @@ class MachineManager(QObject): for position, extruder in self._global_container_stack.extruders.items(): if extruder.isEnabled: extruder_count += 1 - definition_changes_container.setProperty("extruders_enabled_count", "value", extruder_count) + if self.numberExtrudersEnabled != extruder_count: + definition_changes_container.setProperty("extruders_enabled_count", "value", extruder_count) + self.numberExtrudersEnabledChanged.emit() + + @pyqtProperty(int, notify = numberExtrudersEnabledChanged) + def numberExtrudersEnabled(self): + return self._global_container_stack.definitionChanges.getProperty("extruders_enabled_count", "value") @pyqtProperty(str, notify = extruderChanged) def defaultExtruderPosition(self): @@ -1193,6 +1208,26 @@ class MachineManager(QObject): if machine.getMetaDataEntry(key) == value: machine.setMetaDataEntry(key, new_value) + ## This method checks if the name of the group stored in the definition container is correct. + # After updating from 3.2 to 3.3 some group names may be temporary. If there is a mismatch in the name of the group + # then all the container stacks are updated, both the current and the hidden ones. + def checkCorrectGroupName(self, device_id: str, group_name: str): + if self._global_container_stack and device_id == self.activeMachineNetworkKey: + # Check if the connect_group_name is correct. If not, update all the containers connected to the same printer + if self.activeMachineNetworkGroupName != group_name: + metadata_filter = {"um_network_key": self.activeMachineNetworkKey} + hidden_containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) + for container in hidden_containers: + container.setMetaDataEntry("connect_group_name", group_name) + + ## This method checks if there is an instance connected to the given network_key + def existNetworkInstances(self, network_key: str) -> bool: + metadata_filter = {"um_network_key": network_key} + containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) + if containers: + return True + return False + @pyqtSlot("QVariant") def setGlobalVariant(self, container_node): self.blurSettings.emit() @@ -1242,6 +1277,13 @@ class MachineManager(QObject): if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: self._application.discardOrKeepProfileChanges() + @pyqtSlot() + def resetToUseDefaultQuality(self): + with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): + self._setQualityGroup(self._current_quality_group) + for stack in [self._global_container_stack] + list(self._global_container_stack.extruders.values()): + stack.userChanges.clear() + @pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged) def activeQualityChangesGroup(self): return self._current_quality_changes_group diff --git a/cura/Settings/SettingVisibilityPresetsModel.py b/cura/Settings/SettingVisibilityPresetsModel.py deleted file mode 100644 index e5a2e24412..0000000000 --- a/cura/Settings/SettingVisibilityPresetsModel.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -import os -import urllib -from configparser import ConfigParser - -from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot, QUrl - -from UM.Logger import Logger -from UM.Qt.ListModel import ListModel -from UM.Preferences import Preferences -from UM.Resources import Resources -from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError - -import cura.CuraApplication - - -class SettingVisibilityPresetsModel(ListModel): - IdRole = Qt.UserRole + 1 - NameRole = Qt.UserRole + 2 - SettingsRole = Qt.UserRole + 4 - - def __init__(self, parent = None): - super().__init__(parent) - self.addRoleName(self.IdRole, "id") - self.addRoleName(self.NameRole, "name") - self.addRoleName(self.SettingsRole, "settings") - - self._populate() - - self._preferences = Preferences.getInstance() - self._preferences.addPreference("cura/active_setting_visibility_preset", "custom") # Preference to store which preset is currently selected - self._preferences.addPreference("cura/custom_visible_settings", "") # Preference that stores the "custom" set so it can always be restored (even after a restart) - self._preferences.preferenceChanged.connect(self._onPreferencesChanged) - - self._active_preset = self._preferences.getValue("cura/active_setting_visibility_preset") - if self.find("id", self._active_preset) < 0: - self._active_preset = "custom" - - self.activePresetChanged.emit() - - - def _populate(self): - items = [] - for item in Resources.getAllResourcesOfType(cura.CuraApplication.CuraApplication.ResourceTypes.SettingVisibilityPreset): - try: - mime_type = MimeTypeDatabase.getMimeTypeForFile(item) - except MimeTypeNotFoundError: - Logger.log("e", "Could not determine mime type of file %s", item) - continue - - id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(item))) - - if not os.path.isfile(item): - continue - - parser = ConfigParser(allow_no_value=True) # accept options without any value, - - try: - parser.read([item]) - - if not parser.has_option("general", "name") and not parser.has_option("general", "weight"): - continue - - settings = [] - for section in parser.sections(): - if section == 'general': - continue - - settings.append(section) - for option in parser[section].keys(): - settings.append(option) - - items.append({ - "id": id, - "name": parser["general"]["name"], - "weight": parser["general"]["weight"], - "settings": settings - }) - - except Exception as e: - Logger.log("e", "Failed to load setting preset %s: %s", file_path, str(e)) - - - items.sort(key = lambda k: (k["weight"], k["id"])) - self.setItems(items) - - @pyqtSlot(str) - def setActivePreset(self, preset_id): - if preset_id != "custom" and self.find("id", preset_id) == -1: - Logger.log("w", "Tried to set active preset to unknown id %s", preset_id) - return - - if preset_id == "custom" and self._active_preset == "custom": - # Copy current visibility set to custom visibility set preference so it can be restored later - visibility_string = self._preferences.getValue("general/visible_settings") - self._preferences.setValue("cura/custom_visible_settings", visibility_string) - - self._preferences.setValue("cura/active_setting_visibility_preset", preset_id) - - self._active_preset = preset_id - self.activePresetChanged.emit() - - activePresetChanged = pyqtSignal() - - @pyqtProperty(str, notify = activePresetChanged) - def activePreset(self): - return self._active_preset - - def _onPreferencesChanged(self, name): - if name != "general/visible_settings": - return - - if self._active_preset != "custom": - return - - # Copy current visibility set to custom visibility set preference so it can be restored later - visibility_string = self._preferences.getValue("general/visible_settings") - self._preferences.setValue("cura/custom_visible_settings", visibility_string) - - - # Factory function, used by QML - @staticmethod - def createSettingVisibilityPresetsModel(engine, js_engine): - return SettingVisibilityPresetsModel.getInstance() - - ## Get the singleton instance for this class. - @classmethod - def getInstance(cls) -> "SettingVisibilityPresetsModel": - # Note: Explicit use of class name to prevent issues with inheritance. - if not SettingVisibilityPresetsModel.__instance: - SettingVisibilityPresetsModel.__instance = cls() - return SettingVisibilityPresetsModel.__instance - - __instance = None # type: "SettingVisibilityPresetsModel" \ No newline at end of file diff --git a/cura/Settings/SimpleModeSettingsManager.py b/cura/Settings/SimpleModeSettingsManager.py index 867a21702c..a337d8b04e 100644 --- a/cura/Settings/SimpleModeSettingsManager.py +++ b/cura/Settings/SimpleModeSettingsManager.py @@ -16,7 +16,8 @@ class SimpleModeSettingsManager(QObject): self._is_profile_user_created = False # True when profile was custom created by user self._machine_manager.activeStackValueChanged.connect(self._updateIsProfileCustomized) - self._machine_manager.activeQualityChanged.connect(self._updateIsProfileUserCreated) + self._machine_manager.activeQualityGroupChanged.connect(self._updateIsProfileUserCreated) + self._machine_manager.activeQualityChangesGroupChanged.connect(self._updateIsProfileUserCreated) # update on create as the activeQualityChanged signal is emitted before this manager is created when Cura starts self._updateIsProfileCustomized() diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index 1b3b7264a1..4b78a2a72a 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -1,17 +1,17 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import re # For escaping characters in the settings. +import json +import copy + from UM.Mesh.MeshWriter import MeshWriter from UM.Logger import Logger from UM.Application import Application from UM.Settings.InstanceContainer import InstanceContainer -from UM.Util import parseBool -from cura.Settings.ExtruderManager import ExtruderManager +from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch -import re #For escaping characters in the settings. -import json -import copy ## Writes g-code to a file. # @@ -45,6 +45,8 @@ class GCodeWriter(MeshWriter): def __init__(self): super().__init__() + self._application = Application.getInstance() + ## Writes the g-code for the entire scene to a stream. # # Note that even though the function accepts a collection of nodes, the @@ -94,7 +96,6 @@ class GCodeWriter(MeshWriter): return flat_container - ## Serialises a container stack to prepare it for writing at the end of the # g-code. # @@ -104,15 +105,21 @@ class GCodeWriter(MeshWriter): # \param settings A container stack to serialise. # \return A serialised string of the settings. def _serialiseSettings(self, stack): + container_registry = self._application.getContainerRegistry() + quality_manager = self._application.getQualityManager() + prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line. prefix_length = len(prefix) + quality_name = stack.qualityChanges.getName() + quality_type = stack.quality.getMetaDataEntry("quality_type") container_with_profile = stack.qualityChanges if container_with_profile.getId() == "empty_quality_changes": - Logger.log("e", "No valid quality profile found, not writing settings to g-code!") - return "" + # If the global quality changes is empty, create a new one + quality_name = container_registry.uniqueName(stack.quality.getName()) + container_with_profile = quality_manager._createQualityChanges(quality_type, quality_name, stack, None) - flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile) + flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile) # If the quality changes is not set, we need to set type manually if flat_global_container.getMetaDataEntry("type", None) is None: flat_global_container.addMetaDataEntry("type", "quality_changes") @@ -121,41 +128,47 @@ class GCodeWriter(MeshWriter): if flat_global_container.getMetaDataEntry("quality_type", None) is None: flat_global_container.addMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal")) - # Change the default defintion - default_machine_definition = "fdmprinter" - if parseBool(stack.getMetaDataEntry("has_machine_quality", "False")): - default_machine_definition = stack.getMetaDataEntry("quality_definition") - if not default_machine_definition: - default_machine_definition = stack.definition.getId() - flat_global_container.setMetaDataEntry("definition", default_machine_definition) + # Get the machine definition ID for quality profiles + machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition) + flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality) serialized = flat_global_container.serialize() data = {"global_quality": serialized} - for extruder in sorted(stack.extruders.values(), key = lambda k: k.getMetaDataEntry("position")): + all_setting_keys = set(flat_global_container.getAllKeys()) + for extruder in sorted(stack.extruders.values(), key = lambda k: int(k.getMetaDataEntry("position"))): extruder_quality = extruder.qualityChanges if extruder_quality.getId() == "empty_quality_changes": - Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId()) - continue - flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality) + # Same story, if quality changes is empty, create a new one + quality_name = container_registry.uniqueName(stack.quality.getName()) + extruder_quality = quality_manager._createQualityChanges(quality_type, quality_name, stack, None) + + flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality) # If the quality changes is not set, we need to set type manually if flat_extruder_quality.getMetaDataEntry("type", None) is None: flat_extruder_quality.addMetaDataEntry("type", "quality_changes") # Ensure that extruder is set. (Can happen if we have empty quality changes). - if flat_extruder_quality.getMetaDataEntry("extruder", None) is None: - flat_extruder_quality.addMetaDataEntry("extruder", extruder.getBottom().getId()) + if flat_extruder_quality.getMetaDataEntry("position", None) is None: + flat_extruder_quality.addMetaDataEntry("position", extruder.getMetaDataEntry("position")) # Ensure that quality_type is set. (Can happen if we have empty quality changes). if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None: flat_extruder_quality.addMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal")) - # Change the default defintion - flat_extruder_quality.setMetaDataEntry("definition", default_machine_definition) + # Change the default definition + flat_extruder_quality.setMetaDataEntry("definition", machine_definition_id_for_quality) extruder_serialized = flat_extruder_quality.serialize() data.setdefault("extruder_quality", []).append(extruder_serialized) + all_setting_keys.update(set(flat_extruder_quality.getAllKeys())) + + # Check if there is any profiles + if not all_setting_keys: + Logger.log("i", "No custom settings found, not writing settings to g-code.") + return "" + json_string = json.dumps(data) # Escape characters that have a special meaning in g-code comments. @@ -169,5 +182,5 @@ class GCodeWriter(MeshWriter): # Lines have 80 characters, so the payload of each line is 80 - prefix. for pos in range(0, len(escaped_string), 80 - prefix_length): - result += prefix + escaped_string[pos : pos + 80 - prefix_length] + "\n" + result += prefix + escaped_string[pos: pos + 80 - prefix_length] + "\n" return result diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml index 03a2ce1bf4..a2790dcf08 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -163,7 +163,16 @@ Item { id: addedSettingsModel; containerId: Cura.MachineManager.activeDefinitionId expanded: [ "*" ] - exclude: { + filter: + { + if (printSequencePropertyProvider.properties.value == "one_at_a_time") + { + return {"settable_per_meshgroup": true}; + } + return {"settable_per_mesh": true}; + } + exclude: + { var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]; if(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh") @@ -375,7 +384,6 @@ Item { title: catalog.i18nc("@title:window", "Select Settings to Customize for this model") width: screenScaleFactor * 360 - property string labelFilter: "" property var additional_excluded_settings onVisibilityChanged: @@ -386,11 +394,33 @@ Item { // Set skip setting, it will prevent from resetting selected mesh_type contents.model.visibilityHandler.addSkipResetSetting(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type) listview.model.forceUpdate() + + updateFilter() } } + function updateFilter() + { + var new_filter = {}; + if (printSequencePropertyProvider.properties.value == "one_at_a_time") + { + new_filter["settable_per_meshgroup"] = true; + } + else + { + new_filter["settable_per_mesh"] = true; + } + + if(filterInput.text != "") + { + new_filter["i18n_label"] = "*" + filterInput.text; + } + + listview.model.filter = new_filter; + } + TextField { - id: filter + id: filterInput anchors { top: parent.top @@ -401,17 +431,7 @@ Item { placeholderText: catalog.i18nc("@label:textbox", "Filter..."); - onTextChanged: - { - if(text != "") - { - listview.model.filter = {"settable_per_mesh": true, "i18n_label": "*" + text} - } - else - { - listview.model.filter = {"settable_per_mesh": true} - } - } + onTextChanged: settingPickDialog.updateFilter() } CheckBox @@ -437,7 +457,7 @@ Item { anchors { - top: filter.bottom; + top: filterInput.bottom; left: parent.left; right: parent.right; bottom: parent.bottom; @@ -449,10 +469,6 @@ Item { { id: definitionsModel; containerId: Cura.MachineManager.activeDefinitionId - filter: - { - "settable_per_mesh": true - } visibilityHandler: UM.SettingPreferenceVisibilityHandler {} expanded: [ "*" ] exclude: @@ -484,6 +500,7 @@ Item { } } } + Component.onCompleted: settingPickDialog.updateFilter() } } @@ -507,6 +524,16 @@ Item { storeIndex: 0 } + UM.SettingPropertyProvider + { + id: printSequencePropertyProvider + + containerStackId: Cura.MachineManager.activeMachineId + key: "print_sequence" + watchedProperties: [ "value" ] + storeIndex: 0 + } + SystemPalette { id: palette; } Component diff --git a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py index ff930e2c31..1bfdbd4117 100644 --- a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py +++ b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py @@ -60,7 +60,7 @@ class RemovableDriveOutputDevice(OutputDevice): if len(file_formats) == 0: Logger.log("e", "There are no file formats available to write with!") - raise OutputDeviceError.WriteRequestFailedError(catalog.i18nc("There are no file formats available to write with!")) + raise OutputDeviceError.WriteRequestFailedError(catalog.i18nc("@info:status", "There are no file formats available to write with!")) # Just take the first file format available. if file_handler is not None: diff --git a/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py b/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py index 4615cd62dc..076c4584af 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py @@ -13,6 +13,7 @@ class ClusterUM3PrinterOutputController(PrinterOutputController): def __init__(self, output_device): super().__init__(output_device) self.can_pre_heat_bed = False + self.can_pre_heat_hotends = False self.can_control_manually = False def setJobState(self, job: "PrintJobOutputModel", state: str): diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py index 76e8721fdd..0b8d6e9f53 100644 --- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py +++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py @@ -147,6 +147,10 @@ class DiscoverUM3Action(MachineAction): return "" + @pyqtSlot(str, result = bool) + def existsKey(self, key) -> bool: + return Application.getInstance().getMachineManager().existNetworkInstances(network_key = key) + @pyqtSlot() def loadConfigurationFromPrinter(self): machine_manager = Application.getInstance().getMachineManager() diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml index 079e5dcdd3..e7df22b546 100644 --- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml +++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml @@ -5,6 +5,7 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.1 +import QtQuick.Dialogs 1.2 Cura.MachineAction { @@ -33,15 +34,34 @@ Cura.MachineAction { var printerKey = base.selectedDevice.key var printerName = base.selectedDevice.name // TODO To change when the groups have a name - if(manager.getStoredKey() != printerKey) + if (manager.getStoredKey() != printerKey) { - manager.setKey(printerKey) - manager.setGroupName(printerName) // TODO To change when the groups have a name - completed() + // Check if there is another instance with the same key + if (!manager.existsKey(printerKey)) + { + manager.setKey(printerKey) + manager.setGroupName(printerName) // TODO To change when the groups have a name + completed() + } + else + { + existingConnectionDialog.open() + } } } } + MessageDialog + { + id: existingConnectionDialog + title: catalog.i18nc("@window:title", "Existing Connection") + icon: StandardIcon.Information + text: catalog.i18nc("@message:text", "There is an instance already connected to this group") + detailedText: catalog.i18nc("@message:description", "You can't connect two instances to the same group. Please use the other instance or connect to another group.") + standardButtons: StandardButton.Ok + modality: Qt.ApplicationModal + } + Column { anchors.fill: parent; diff --git a/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py index 5ff5eb9e3e..089b9038f7 100644 --- a/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py @@ -82,6 +82,9 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._zero_conf_browser.cancel() self._zero_conf_browser = None # Force the old ServiceBrowser to be destroyed. + for instance_name in list(self._discovered_devices): + self._onRemoveDevice(instance_name) + self._zero_conf = Zeroconf() self._zero_conf_browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._appendServiceChangedRequest]) diff --git a/plugins/USBPrinting/USBPrinterOutputController.py b/plugins/USBPrinting/USBPrinterOutputController.py deleted file mode 100644 index f189ed5876..0000000000 --- a/plugins/USBPrinting/USBPrinterOutputController.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright (c) 2017 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from cura.PrinterOutput.PrinterOutputController import PrinterOutputController -from PyQt5.QtCore import QTimer - -MYPY = False -if MYPY: - from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel - from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel - - -class USBPrinterOutputController(PrinterOutputController): - def __init__(self, output_device): - super().__init__(output_device) - - self._preheat_bed_timer = QTimer() - self._preheat_bed_timer.setSingleShot(True) - self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished) - self._preheat_printer = None - - def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed): - self._output_device.sendCommand("G91") - self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed)) - self._output_device.sendCommand("G90") - - def homeHead(self, printer): - self._output_device.sendCommand("G28 X") - self._output_device.sendCommand("G28 Y") - - def homeBed(self, printer): - self._output_device.sendCommand("G28 Z") - - def setJobState(self, job: "PrintJobOutputModel", state: str): - if state == "pause": - self._output_device.pausePrint() - job.updateState("paused") - elif state == "print": - self._output_device.resumePrint() - job.updateState("printing") - elif state == "abort": - self._output_device.cancelPrint() - pass - - def preheatBed(self, printer: "PrinterOutputModel", temperature, duration): - try: - temperature = round(temperature) # The API doesn't allow floating point. - duration = round(duration) - except ValueError: - return # Got invalid values, can't pre-heat. - - self.setTargetBedTemperature(printer, temperature=temperature) - self._preheat_bed_timer.setInterval(duration * 1000) - self._preheat_bed_timer.start() - self._preheat_printer = printer - printer.updateIsPreheating(True) - - def cancelPreheatBed(self, printer: "PrinterOutputModel"): - self.preheatBed(printer, temperature=0, duration=0) - self._preheat_bed_timer.stop() - printer.updateIsPreheating(False) - - def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int): - self._output_device.sendCommand("M140 S%s" % temperature) - - def _onPreheatBedTimerFinished(self): - self.setTargetBedTemperature(self._preheat_printer, 0) - self._preheat_printer.updateIsPreheating(False) \ No newline at end of file diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 14098b66f8..24feedd628 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -10,9 +10,9 @@ from UM.PluginRegistry import PluginRegistry from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel +from cura.PrinterOutput.GenericOutputController import GenericOutputController from .AutoDetectBaudJob import AutoDetectBaudJob -from .USBPrinterOutputController import USBPrinterOutputController from .avr_isp import stk500v2, intelHex from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty @@ -240,7 +240,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): container_stack = Application.getInstance().getGlobalContainerStack() num_extruders = container_stack.getProperty("machine_extruder_count", "value") # Ensure that a printer is created. - self._printers = [PrinterOutputModel(output_controller=USBPrinterOutputController(self), number_of_extruders=num_extruders)] + self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)] self._printers[0].updateName(container_stack.getName()) self.setConnectionState(ConnectionState.connected) self._update_thread.start() @@ -372,7 +372,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): elapsed_time = int(time() - self._print_start_time) print_job = self._printers[0].activePrintJob if print_job is None: - print_job = PrintJobOutputModel(output_controller = USBPrinterOutputController(self), name= Application.getInstance().getPrintInformation().jobName) + print_job = PrintJobOutputModel(output_controller = GenericOutputController(self), name= Application.getInstance().getPrintInformation().jobName) print_job.updateState("printing") self._printers[0].updateActivePrintJob(print_job) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 8b17721794..5ff6838373 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -208,14 +208,9 @@ class XmlMaterialProfile(InstanceContainer): machine_variant_map = {} variant_manager = CuraApplication.getInstance().getVariantManager() - 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) - - all_containers = [] - for node in [material_group.root_material_node] + material_group.derived_material_node_list: - all_containers.append(node.getContainer()) + all_containers = registry.findInstanceContainers(base_file = root_material_id) for container in all_containers: definition_id = container.getMetaDataEntry("definition") @@ -242,7 +237,7 @@ class XmlMaterialProfile(InstanceContainer): for definition_id, container in machine_container_map.items(): definition_id = container.getMetaDataEntry("definition") - definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = definition_id)[0] + definition_metadata = registry.findDefinitionContainersMetadata(id = definition_id)[0] product = definition_id for product_name, product_id_list in product_id_map.items(): diff --git a/resources/definitions/bq_hephestos_xl.def.json b/resources/definitions/bq_hephestos_xl.def.json index 75b756c71e..08be4b8d34 100644 --- a/resources/definitions/bq_hephestos_xl.def.json +++ b/resources/definitions/bq_hephestos_xl.def.json @@ -6,7 +6,7 @@ "visible": true, "manufacturer": "BQ", "author": "BQ", - "file_formats": "text/x-code", + "file_formats": "text/x-gcode", "platform": "bq_hephestos_platform.stl", "platform_offset": [ 0, -82, 0] }, diff --git a/resources/definitions/seemecnc_artemis.def.json b/resources/definitions/seemecnc_artemis.def.json index 88a1b28de6..0b31abfa41 100644 --- a/resources/definitions/seemecnc_artemis.def.json +++ b/resources/definitions/seemecnc_artemis.def.json @@ -25,6 +25,7 @@ "machine_nozzle_size": { "default_value": 0.5 }, "machine_shape": { "default_value": "elliptic" }, "machine_width": { "default_value": 290 }, + "material_diameter": { "default_value": 1.75 }, "relative_extrusion": { "default_value": false }, "retraction_amount": { "default_value": 3.2 }, "retraction_combing": { "default_value": "off" }, diff --git a/resources/definitions/seemecnc_v32.def.json b/resources/definitions/seemecnc_v32.def.json index 5932403bc5..3f46c1540a 100644 --- a/resources/definitions/seemecnc_v32.def.json +++ b/resources/definitions/seemecnc_v32.def.json @@ -25,6 +25,7 @@ "machine_nozzle_size": { "default_value": 0.5 }, "machine_shape": { "default_value": "elliptic" }, "machine_width": { "default_value": 265 }, + "material_diameter": { "default_value": 1.75 }, "relative_extrusion": { "default_value": false }, "retraction_amount": { "default_value": 3.2 }, "retraction_combing": { "default_value": "off" }, diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json index 57cfbe960f..ef41686752 100644 --- a/resources/definitions/ultimaker3.def.json +++ b/resources/definitions/ultimaker3.def.json @@ -110,9 +110,9 @@ "material_bed_temperature": { "maximum_value": "115" }, "material_bed_temperature_layer_0": { "maximum_value": "115" }, "material_standby_temperature": { "value": "100" }, - "meshfix_maximum_resolution": { "value": "0.04" }, + "meshfix_maximum_resolution": { "value": "0.04" }, "multiple_mesh_overlap": { "value": "0" }, - "optimize_wall_printing_order": { "value": "True" }, + "optimize_wall_printing_order": { "value": "True" }, "prime_tower_enable": { "default_value": true }, "raft_airgap": { "value": "0" }, "raft_base_thickness": { "value": "0.3" }, diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index cff4399073..c4ebb790e8 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -217,6 +217,7 @@ UM.MainWindow text: catalog.i18nc("@action:inmenu", "Disable Extruder") onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false) visible: Cura.MachineManager.getExtruder(model.index).isEnabled + enabled: Cura.MachineManager.numberExtrudersEnabled > 1 } } diff --git a/resources/qml/MachineSelection.qml b/resources/qml/MachineSelection.qml index b959e20bb7..d075486eb2 100644 --- a/resources/qml/MachineSelection.qml +++ b/resources/qml/MachineSelection.qml @@ -71,8 +71,8 @@ ToolButton color: UM.Theme.getColor("sidebar_header_text_active") text: control.text; elide: Text.ElideRight; - anchors.left: isNetworkPrinter ? printerStatusIcon.right : parent.left; - anchors.leftMargin: isNetworkPrinter ? UM.Theme.getSize("sidebar_lining").width : UM.Theme.getSize("sidebar_margin").width + anchors.left: printerStatusIcon.visible ? printerStatusIcon.right : parent.left; + anchors.leftMargin: printerStatusIcon.visible ? UM.Theme.getSize("sidebar_lining").width : UM.Theme.getSize("sidebar_margin").width anchors.right: downArrow.left; anchors.rightMargin: control.rightMargin; anchors.verticalCenter: parent.verticalCenter; diff --git a/resources/qml/Menus/ContextMenu.qml b/resources/qml/Menus/ContextMenu.qml index 83302f9463..e35aef5f20 100644 --- a/resources/qml/Menus/ContextMenu.qml +++ b/resources/qml/Menus/ContextMenu.qml @@ -31,7 +31,7 @@ Menu MenuItem { text: "%1: %2 - %3".arg(model.name).arg(model.material).arg(model.variant) visible: base.shouldShowExtruders - enabled: UM.Selection.hasSelection + enabled: UM.Selection.hasSelection && model.enabled checkable: true checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1 onTriggered: CuraActions.setExtruderForSelection(model.id) diff --git a/resources/qml/Menus/SettingVisibilityPresetsMenu.qml b/resources/qml/Menus/SettingVisibilityPresetsMenu.qml index 19c36e6118..0753c83b17 100644 --- a/resources/qml/Menus/SettingVisibilityPresetsMenu.qml +++ b/resources/qml/Menus/SettingVisibilityPresetsMenu.qml @@ -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.7 +import QtQuick.Controls 1.4 import UM 1.2 as UM import Cura 1.0 as Cura @@ -12,44 +12,26 @@ Menu id: menu title: catalog.i18nc("@action:inmenu", "Visible Settings") + property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel() property bool showingSearchResults property bool showingAllSettings signal showAllSettings() signal showSettingVisibilityProfile() - MenuItem - { - text: catalog.i18nc("@action:inmenu", "Custom selection") - checkable: true - checked: !showingSearchResults && !showingAllSettings && Cura.SettingVisibilityPresetsModel.activePreset == "custom" - exclusiveGroup: group - onTriggered: - { - Cura.SettingVisibilityPresetsModel.setActivePreset("custom"); - // Restore custom set from preference - UM.Preferences.setValue("general/visible_settings", UM.Preferences.getValue("cura/custom_visible_settings")); - showSettingVisibilityProfile(); - } - } - MenuSeparator { } - Instantiator { - model: Cura.SettingVisibilityPresetsModel + model: settingVisibilityPresetsModel MenuItem { text: model.name checkable: true - checked: model.id == Cura.SettingVisibilityPresetsModel.activePreset + checked: model.id == settingVisibilityPresetsModel.activePreset exclusiveGroup: group onTriggered: { - Cura.SettingVisibilityPresetsModel.setActivePreset(model.id); - - UM.Preferences.setValue("general/visible_settings", model.settings.join(";")); - + settingVisibilityPresetsModel.setActivePreset(model.id); showSettingVisibilityProfile(); } } diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index 7f06ffecde..042bd09828 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -364,6 +364,7 @@ Item } width: true ? (parent.width * 0.4) | 0 : parent.width + frameVisible: true ListView { diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index ff35e27eeb..1726087e97 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -369,6 +369,7 @@ Item } width: true ? (parent.width * 0.4) | 0 : parent.width + frameVisible: true ListView { diff --git a/resources/qml/Preferences/SettingVisibilityPage.qml b/resources/qml/Preferences/SettingVisibilityPage.qml index f0c24e2cbe..b6b1c133ed 100644 --- a/resources/qml/Preferences/SettingVisibilityPage.qml +++ b/resources/qml/Preferences/SettingVisibilityPage.qml @@ -13,6 +13,8 @@ UM.PreferencesPage { title: catalog.i18nc("@title:tab", "Setting Visibility"); + property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel() + property int scrollToIndex: 0 signal scrollToSection( string key ) @@ -27,8 +29,7 @@ UM.PreferencesPage // After calling this function update Setting visibility preset combobox. // Reset should set default setting preset ("Basic") - visibilityPreset.setDefaultPreset() - + visibilityPreset.currentIndex = 1 } resetEnabled: true; @@ -37,8 +38,6 @@ UM.PreferencesPage id: base; anchors.fill: parent; - property bool inhibitSwitchToCustom: false - CheckBox { id: toggleVisibleSettings @@ -112,11 +111,6 @@ UM.PreferencesPage ComboBox { - function setDefaultPreset() - { - visibilityPreset.currentIndex = 0 - } - id: visibilityPreset width: 150 * screenScaleFactor anchors @@ -125,51 +119,25 @@ UM.PreferencesPage right: parent.right } - model: ListModel - { - id: visibilityPresetsModel - Component.onCompleted: - { - visibilityPresetsModel.append({text: catalog.i18nc("@action:inmenu", "Custom selection"), id: "custom"}); - - var presets = Cura.SettingVisibilityPresetsModel; - for(var i = 0; i < presets.rowCount(); i++) - { - visibilityPresetsModel.append({text: presets.getItem(i)["name"], id: presets.getItem(i)["id"]}); - } - } - } + model: settingVisibilityPresetsModel + textRole: "name" currentIndex: { // Load previously selected preset. - var index = Cura.SettingVisibilityPresetsModel.find("id", Cura.SettingVisibilityPresetsModel.activePreset); - if(index == -1) + var index = settingVisibilityPresetsModel.find("id", settingVisibilityPresetsModel.activePreset) + if (index == -1) { - return 0; + return 0 } - return index + 1; // "Custom selection" entry is added in front, so index is off by 1 + return index } onActivated: { - base.inhibitSwitchToCustom = true; - var preset_id = visibilityPresetsModel.get(index).id; - Cura.SettingVisibilityPresetsModel.setActivePreset(preset_id); - - UM.Preferences.setValue("cura/active_setting_visibility_preset", preset_id); - if (preset_id != "custom") - { - UM.Preferences.setValue("general/visible_settings", Cura.SettingVisibilityPresetsModel.getItem(index - 1).settings.join(";")); - // "Custom selection" entry is added in front, so index is off by 1 - } - else - { - // Restore custom set from preference - UM.Preferences.setValue("general/visible_settings", UM.Preferences.getValue("cura/custom_visible_settings")); - } - base.inhibitSwitchToCustom = false; + var preset_id = settingVisibilityPresetsModel.getItem(index).id; + settingVisibilityPresetsModel.setActivePreset(preset_id); } } @@ -199,16 +167,7 @@ UM.PreferencesPage exclude: ["machine_settings", "command_line_settings"] showAncestors: true expanded: ["*"] - visibilityHandler: UM.SettingPreferenceVisibilityHandler - { - onVisibilityChanged: - { - if(Cura.SettingVisibilityPresetsModel.activePreset != "" && !base.inhibitSwitchToCustom) - { - Cura.SettingVisibilityPresetsModel.setActivePreset("custom"); - } - } - } + visibilityHandler: UM.SettingPreferenceVisibilityHandler {} } delegate: Loader diff --git a/resources/qml/PrinterOutput/ExtruderBox.qml b/resources/qml/PrinterOutput/ExtruderBox.qml index a7141262a9..56c86f1034 100644 --- a/resources/qml/PrinterOutput/ExtruderBox.qml +++ b/resources/qml/PrinterOutput/ExtruderBox.qml @@ -12,9 +12,20 @@ Item property alias color: background.color property var extruderModel property var position: index - //width: index == machineExtruderCount.properties.value - 1 && index % 2 == 0 ? extrudersGrid.width : Math.floor(extrudersGrid.width / 2 - UM.Theme.getSize("sidebar_lining_thin").width / 2) implicitWidth: parent.width implicitHeight: UM.Theme.getSize("sidebar_extruder_box").height + + UM.SettingPropertyProvider + { + id: extruderTemperature + containerStackId: Cura.ExtruderManager.extruderIds[position] + key: "material_print_temperature" + watchedProperties: ["value", "minimum_value", "maximum_value", "resolve"] + storeIndex: 0 + + property var resolve: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId ? properties.resolve : "None" + } + Rectangle { id: background @@ -34,12 +45,11 @@ Item { id: extruderTargetTemperature text: Math.round(extruderModel.targetHotendTemperature) + "°C" - //text: (connectedPrinter != null && connectedPrinter.hotendIds[index] != null && connectedPrinter.targetHotendTemperatures[index] != null) ? Math.round(connectedPrinter.targetHotendTemperatures[index]) + "°C" : "" font: UM.Theme.getFont("small") color: UM.Theme.getColor("text_inactive") anchors.right: parent.right anchors.rightMargin: UM.Theme.getSize("default_margin").width - anchors.bottom: extruderTemperature.bottom + anchors.bottom: extruderCurrentTemperature.bottom MouseArea //For tooltip. { @@ -52,7 +62,7 @@ Item { base.showTooltip( base, - {x: 0, y: extruderTargetTemperature.mapToItem(base, 0, -parent.height / 4).y}, + {x: 0, y: extruderTargetTemperature.mapToItem(base, 0, Math.floor(-parent.height / 4)).y}, catalog.i18nc("@tooltip", "The target temperature of the hotend. The hotend will heat up or cool down towards this temperature. If this is 0, the hotend heating is turned off.") ); } @@ -65,9 +75,8 @@ Item } Label //Temperature indication. { - id: extruderTemperature + id: extruderCurrentTemperature text: Math.round(extruderModel.hotendTemperature) + "°C" - //text: (connectedPrinter != null && connectedPrinter.hotendIds[index] != null && connectedPrinter.hotendTemperatures[index] != null) ? Math.round(connectedPrinter.hotendTemperatures[index]) + "°C" : "" color: UM.Theme.getColor("text") font: UM.Theme.getFont("large") anchors.right: extruderTargetTemperature.left @@ -76,7 +85,7 @@ Item MouseArea //For tooltip. { - id: extruderTemperatureTooltipArea + id: extruderCurrentTemperatureTooltipArea hoverEnabled: true anchors.fill: parent onHoveredChanged: @@ -85,8 +94,8 @@ Item { base.showTooltip( base, - {x: 0, y: parent.mapToItem(base, 0, -parent.height / 4).y}, - catalog.i18nc("@tooltip", "The current temperature of this extruder.") + {x: 0, y: parent.mapToItem(base, 0, Math.floor(-parent.height / 4)).y}, + catalog.i18nc("@tooltip", "The current temperature of this hotend.") ); } else @@ -97,6 +106,272 @@ Item } } + Rectangle //Input field for pre-heat temperature. + { + id: preheatTemperatureControl + color: !enabled ? UM.Theme.getColor("setting_control_disabled") : showError ? UM.Theme.getColor("setting_validation_error_background") : UM.Theme.getColor("setting_validation_ok") + property var showError: + { + if(extruderTemperature.properties.maximum_value != "None" && extruderTemperature.properties.maximum_value < Math.floor(preheatTemperatureInput.text)) + { + return true; + } else + { + return false; + } + } + enabled: + { + if (extruderModel == null) + { + return false; //Can't preheat if not connected. + } + if (!connectedPrinter.acceptsCommands) + { + return false; //Not allowed to do anything. + } + if (connectedPrinter.activePrinter && connectedPrinter.activePrinter.activePrintJob) + { + if((["printing", "pre_print", "resuming", "pausing", "paused", "error", "offline"]).indexOf(connectedPrinter.activePrinter.activePrintJob.state) != -1) + { + return false; //Printer is in a state where it can't react to pre-heating. + } + } + return true; + } + border.width: UM.Theme.getSize("default_lining").width + border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : preheatTemperatureInputMouseArea.containsMouse ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border") + anchors.right: preheatButton.left + anchors.rightMargin: UM.Theme.getSize("default_margin").width + anchors.bottom: parent.bottom + anchors.bottomMargin: UM.Theme.getSize("default_margin").height + width: UM.Theme.getSize("monitor_preheat_temperature_control").width + height: UM.Theme.getSize("monitor_preheat_temperature_control").height + visible: extruderModel != null ? enabled && extruderModel.canPreHeatHotends && !extruderModel.isPreheating : true + Rectangle //Highlight of input field. + { + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_lining").width + color: UM.Theme.getColor("setting_control_highlight") + opacity: preheatTemperatureControl.hovered ? 1.0 : 0 + } + MouseArea //Change cursor on hovering. + { + id: preheatTemperatureInputMouseArea + hoverEnabled: true + anchors.fill: parent + cursorShape: Qt.IBeamCursor + + onHoveredChanged: + { + if (containsMouse) + { + base.showTooltip( + base, + {x: 0, y: preheatTemperatureInputMouseArea.mapToItem(base, 0, 0).y}, + catalog.i18nc("@tooltip of temperature input", "The temperature to pre-heat the hotend to.") + ); + } + else + { + base.hideTooltip(); + } + } + } + Label + { + id: unit + anchors.right: parent.right + anchors.rightMargin: UM.Theme.getSize("setting_unit_margin").width + anchors.verticalCenter: parent.verticalCenter + + text: "°C"; + color: UM.Theme.getColor("setting_unit") + font: UM.Theme.getFont("default") + } + TextInput + { + id: preheatTemperatureInput + font: UM.Theme.getFont("default") + color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") + selectByMouse: true + maximumLength: 5 + enabled: parent.enabled + validator: RegExpValidator { regExp: /^-?[0-9]{0,9}[.,]?[0-9]{0,10}$/ } //Floating point regex. + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width + anchors.right: unit.left + anchors.verticalCenter: parent.verticalCenter + renderType: Text.NativeRendering + + Component.onCompleted: + { + if (!extruderTemperature.properties.value) + { + text = ""; + } + else + { + text = extruderTemperature.properties.value; + } + } + } + } + + Button //The pre-heat button. + { + id: preheatButton + height: UM.Theme.getSize("setting_control").height + visible: extruderModel != null ? extruderModel.canPreHeatHotends: true + enabled: + { + if (!preheatTemperatureControl.enabled) + { + return false; //Not connected, not authenticated or printer is busy. + } + if (extruderModel.isPreheating) + { + return true; + } + if (extruderTemperature.properties.minimum_value != "None" && Math.floor(preheatTemperatureInput.text) < Math.floor(extruderTemperature.properties.minimum_value)) + { + return false; //Target temperature too low. + } + if (extruderTemperature.properties.maximum_value != "None" && Math.floor(preheatTemperatureInput.text) > Math.floor(extruderTemperature.properties.maximum_value)) + { + return false; //Target temperature too high. + } + if (Math.floor(preheatTemperatureInput.text) == 0) + { + return false; //Setting the temperature to 0 is not allowed (since that cancels the pre-heating). + } + return true; //Preconditions are met. + } + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: UM.Theme.getSize("default_margin").width + style: ButtonStyle { + background: Rectangle + { + border.width: UM.Theme.getSize("default_lining").width + implicitWidth: actualLabel.contentWidth + (UM.Theme.getSize("default_margin").width * 2) + border.color: + { + if(!control.enabled) + { + return UM.Theme.getColor("action_button_disabled_border"); + } + else if(control.pressed) + { + return UM.Theme.getColor("action_button_active_border"); + } + else if(control.hovered) + { + return UM.Theme.getColor("action_button_hovered_border"); + } + else + { + return UM.Theme.getColor("action_button_border"); + } + } + color: + { + if(!control.enabled) + { + return UM.Theme.getColor("action_button_disabled"); + } + else if(control.pressed) + { + return UM.Theme.getColor("action_button_active"); + } + else if(control.hovered) + { + return UM.Theme.getColor("action_button_hovered"); + } + else + { + return UM.Theme.getColor("action_button"); + } + } + Behavior on color + { + ColorAnimation + { + duration: 50 + } + } + + Label + { + id: actualLabel + anchors.centerIn: parent + color: + { + if(!control.enabled) + { + return UM.Theme.getColor("action_button_disabled_text"); + } + else if(control.pressed) + { + return UM.Theme.getColor("action_button_active_text"); + } + else if(control.hovered) + { + return UM.Theme.getColor("action_button_hovered_text"); + } + else + { + return UM.Theme.getColor("action_button_text"); + } + } + font: UM.Theme.getFont("action_button") + text: + { + if(extruderModel == null) + { + return "" + } + if(extruderModel.isPreheating ) + { + return catalog.i18nc("@button Cancel pre-heating", "Cancel") + } else + { + return catalog.i18nc("@button", "Pre-heat") + } + } + } + } + } + + onClicked: + { + if (!extruderModel.isPreheating) + { + extruderModel.preheatHotend(preheatTemperatureInput.text, 900); + } + else + { + extruderModel.cancelPreheatHotend(); + } + } + + onHoveredChanged: + { + if (hovered) + { + base.showTooltip( + base, + {x: 0, y: preheatButton.mapToItem(base, 0, 0).y}, + catalog.i18nc("@tooltip of pre-heat", "Heat the hotend in advance before printing. You can continue adjusting your print while it is heating, and you won't have to wait for the hotend to heat up when you're ready to print.") + ); + } + else + { + base.hideTooltip(); + } + } + } + Rectangle //Material colour indication. { id: materialColor diff --git a/resources/qml/PrinterOutput/HeatedBedBox.qml b/resources/qml/PrinterOutput/HeatedBedBox.qml index bc89da2251..385977141c 100644 --- a/resources/qml/PrinterOutput/HeatedBedBox.qml +++ b/resources/qml/PrinterOutput/HeatedBedBox.qml @@ -114,21 +114,24 @@ Item { return false; //Not allowed to do anything. } - if (connectedPrinter.jobState == "printing" || connectedPrinter.jobState == "pre_print" || connectedPrinter.jobState == "resuming" || connectedPrinter.jobState == "pausing" || connectedPrinter.jobState == "paused" || connectedPrinter.jobState == "error" || connectedPrinter.jobState == "offline") + if (connectedPrinter.activePrinter && connectedPrinter.activePrinter.activePrintJob) { - return false; //Printer is in a state where it can't react to pre-heating. + if((["printing", "pre_print", "resuming", "pausing", "paused", "error", "offline"]).indexOf(connectedPrinter.activePrinter.activePrintJob.state) != -1) + { + return false; //Printer is in a state where it can't react to pre-heating. + } } return true; } border.width: UM.Theme.getSize("default_lining").width border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : preheatTemperatureInputMouseArea.containsMouse ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border") - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.right: preheatButton.left + anchors.rightMargin: UM.Theme.getSize("default_margin").width anchors.bottom: parent.bottom anchors.bottomMargin: UM.Theme.getSize("default_margin").height - width: UM.Theme.getSize("setting_control").width - height: UM.Theme.getSize("setting_control").height - visible: printerModel != null ? printerModel.canPreHeatBed: true + width: UM.Theme.getSize("monitor_preheat_temperature_control").width + height: UM.Theme.getSize("monitor_preheat_temperature_control").height + visible: printerModel != null ? enabled && printerModel.canPreHeatBed && !printerModel.isPreheating : true Rectangle //Highlight of input field. { anchors.fill: parent @@ -159,18 +162,29 @@ Item } } } + Label + { + id: unit + anchors.right: parent.right + anchors.rightMargin: UM.Theme.getSize("setting_unit_margin").width + anchors.verticalCenter: parent.verticalCenter + + text: "°C"; + color: UM.Theme.getColor("setting_unit") + font: UM.Theme.getFont("default") + } TextInput { id: preheatTemperatureInput font: UM.Theme.getFont("default") color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") selectByMouse: true - maximumLength: 10 + maximumLength: 5 enabled: parent.enabled validator: RegExpValidator { regExp: /^-?[0-9]{0,9}[.,]?[0-9]{0,10}$/ } //Floating point regex. anchors.left: parent.left anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width - anchors.right: parent.right + anchors.right: unit.left anchors.verticalCenter: parent.verticalCenter renderType: Text.NativeRendering diff --git a/resources/qml/Settings/SettingExtruder.qml b/resources/qml/Settings/SettingExtruder.qml index 2ddbb135c7..38b1c2cab0 100644 --- a/resources/qml/Settings/SettingExtruder.qml +++ b/resources/qml/Settings/SettingExtruder.qml @@ -215,7 +215,8 @@ SettingItem { text: model.name renderType: Text.NativeRendering - color: { + color: + { if (model.enabled) { UM.Theme.getColor("setting_control_text") } else { diff --git a/resources/qml/Settings/SettingOptionalExtruder.qml b/resources/qml/Settings/SettingOptionalExtruder.qml index f49b7035d7..a370ec6259 100644 --- a/resources/qml/Settings/SettingOptionalExtruder.qml +++ b/resources/qml/Settings/SettingOptionalExtruder.qml @@ -27,8 +27,19 @@ SettingItem onActivated: { - forceActiveFocus(); - propertyProvider.setPropertyValue("value", model.getItem(index).index); + if (model.getItem(index).enabled) + { + forceActiveFocus(); + propertyProvider.setPropertyValue("value", model.getItem(index).index); + } else + { + if (propertyProvider.properties.value == -1) + { + control.currentIndex = model.rowCount() - 1; // we know the last item is "Not overriden" + } else { + control.currentIndex = propertyProvider.properties.value; // revert to the old value + } + } } onActiveFocusChanged: @@ -192,7 +203,14 @@ SettingItem { text: model.name renderType: Text.NativeRendering - color: UM.Theme.getColor("setting_control_text") + color: + { + if (model.enabled) { + UM.Theme.getColor("setting_control_text") + } else { + UM.Theme.getColor("action_button_disabled_text"); + } + } font: UM.Theme.getFont("default") elide: Text.ElideRight verticalAlignment: Text.AlignVCenter diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 235dfac91a..cf9697210b 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -15,6 +15,7 @@ Item { id: base; + property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel() property Action configureSettings property bool findingSettings property bool showingAllSettings @@ -439,6 +440,7 @@ Item key: model.key ? model.key : "" watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder", "resolve" ] storeIndex: 0 + removeUnusedValue: model.resolve == undefined } Connections @@ -562,9 +564,9 @@ Item { definitionsModel.hide(contextMenu.key); // visible settings have changed, so we're no longer showing a preset - if (Cura.SettingVisibilityPresetsModel.activePreset != "" && !showingAllSettings) + if (settingVisibilityPresetsModel.activePreset != "" && !showingAllSettings) { - Cura.SettingVisibilityPresetsModel.setActivePreset("custom"); + settingVisibilityPresetsModel.setActivePreset("custom"); } } } @@ -594,16 +596,16 @@ Item definitionsModel.show(contextMenu.key); } // visible settings have changed, so we're no longer showing a preset - if (Cura.SettingVisibilityPresetsModel.activePreset != "" && !showingAllSettings) + if (settingVisibilityPresetsModel.activePreset != "" && !showingAllSettings) { - Cura.SettingVisibilityPresetsModel.setActivePreset("custom"); + settingVisibilityPresetsModel.setActivePreset("custom"); } } } MenuItem { //: Settings context menu action - text: catalog.i18nc("@action:menu", "Configure setting visiblity..."); + text: catalog.i18nc("@action:menu", "Configure setting visibility..."); onTriggered: Cura.Actions.configureSettingVisibility.trigger(contextMenu); } diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index 5cd0446b36..7baf13ca97 100644 --- a/resources/qml/SidebarHeader.qml +++ b/resources/qml/SidebarHeader.qml @@ -178,6 +178,7 @@ Column text: catalog.i18nc("@action:inmenu", "Disable Extruder") onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false) visible: extruder_enabled + enabled: Cura.MachineManager.numberExtrudersEnabled > 1 } } @@ -185,22 +186,34 @@ Column { background: Item { - Rectangle + function buttonBackgroundColor(index) { - anchors.fill: parent - border.width: control.checked ? UM.Theme.getSize("default_lining").width * 2 : UM.Theme.getSize("default_lining").width - border.color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_border") : - control.hovered ? UM.Theme.getColor("action_button_hovered_border") : - UM.Theme.getColor("action_button_border") - color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active") : - control.hovered ? UM.Theme.getColor("action_button_hovered") : - UM.Theme.getColor("action_button") - Behavior on color { ColorAnimation { duration: 50; } } + var extruder = Cura.MachineManager.getExtruder(index) + if (extruder.isEnabled) { + return (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active") : + control.hovered ? UM.Theme.getColor("action_button_hovered") : + UM.Theme.getColor("action_button") + } else { + return UM.Theme.getColor("action_button_disabled") + } + } + + function buttonBorderColor(index) + { + var extruder = Cura.MachineManager.getExtruder(index) + if (extruder.isEnabled) { + return (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_border") : + control.hovered ? UM.Theme.getColor("action_button_hovered_border") : + UM.Theme.getColor("action_button_border") + } else { + return UM.Theme.getColor("action_button_disabled_border") + } } function buttonColor(index) { var extruder = Cura.MachineManager.getExtruder(index); - if (extruder.isEnabled) { + if (extruder.isEnabled) + { return ( control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_text") : control.hovered ? UM.Theme.getColor("action_button_hovered_text") : @@ -210,10 +223,20 @@ Column } } + Rectangle + { + anchors.fill: parent + border.width: control.checked ? UM.Theme.getSize("default_lining").width * 2 : UM.Theme.getSize("default_lining").width + border.color: buttonBorderColor(index) + color: buttonBackgroundColor(index) + Behavior on color { ColorAnimation { duration: 50; } } + } + Item { id: extruderButtonFace anchors.centerIn: parent + width: { var extruderTextWidth = extruderStaticText.visible ? extruderStaticText.width : 0; var iconWidth = extruderIconItem.width; diff --git a/resources/qml/SidebarSimple.qml b/resources/qml/SidebarSimple.qml index 41ecb529eb..aa6f3ce1de 100644 --- a/resources/qml/SidebarSimple.qml +++ b/resources/qml/SidebarSimple.qml @@ -19,7 +19,7 @@ Item property Action configureSettings; property variant minimumPrintTime: PrintInformation.minimumPrintTime; property variant maximumPrintTime: PrintInformation.maximumPrintTime; - property bool settingsEnabled: Cura.ExtruderManager.activeExtruderStackId || extrudersEnabledCount.properties.value == 1 + property bool settingsEnabled: extrudersEnabledCount.properties.value == 1 Component.onCompleted: PrintInformation.enabled = true Component.onDestruction: PrintInformation.enabled = false @@ -111,7 +111,6 @@ Item // Set selected value 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) { qualityModel.qualitySliderActiveIndex = -1 @@ -474,18 +473,7 @@ Item onClicked: { // if the current profile is user-created, switch to a built-in quality - if (Cura.SimpleModeSettingsManager.isProfileUserCreated) - { - if (Cura.QualityProfilesDropDownMenuModel.rowCount() > 0) - { - var item = Cura.QualityProfilesDropDownMenuModel.getItem(0); - Cura.MachineManager.activeQualityGroup = item.quality_group; - } - } - if (Cura.SimpleModeSettingsManager.isProfileCustomized) - { - discardOrKeepProfileChangesDialog.show() - } + Cura.MachineManager.resetToUseDefaultQuality() } onEntered: { @@ -594,7 +582,9 @@ Item // Update value only if the Recomended mode is Active, // Otherwise if I change the value in the Custom mode the Recomended view will try to repeat // same operation - if (UM.Preferences.getValue("cura/active_mode") == 0) { + var active_mode = UM.Preferences.getValue("cura/active_mode") + + if (active_mode == 0 || active_mode == "simple") { Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", roundedSliderValue) } } diff --git a/resources/themes/cura-dark/theme.json b/resources/themes/cura-dark/theme.json index 80a5eec09c..5fbe36fcdb 100644 --- a/resources/themes/cura-dark/theme.json +++ b/resources/themes/cura-dark/theme.json @@ -84,16 +84,16 @@ "tab_background": [39, 44, 48, 255], "action_button": [39, 44, 48, 255], - "action_button_text": [255, 255, 255, 101], + "action_button_text": [255, 255, 255, 200], "action_button_border": [255, 255, 255, 30], "action_button_hovered": [39, 44, 48, 255], "action_button_hovered_text": [255, 255, 255, 255], "action_button_hovered_border": [255, 255, 255, 30], "action_button_active": [39, 44, 48, 30], "action_button_active_text": [255, 255, 255, 255], - "action_button_active_border": [255, 255, 255, 30], + "action_button_active_border": [255, 255, 255, 100], "action_button_disabled": [39, 44, 48, 255], - "action_button_disabled_text": [255, 255, 255, 101], + "action_button_disabled_text": [255, 255, 255, 80], "action_button_disabled_border": [255, 255, 255, 30], "scrollbar_background": [39, 44, 48, 0], diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index c0b71ac618..4f4b2306a8 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -411,6 +411,8 @@ "save_button_save_to_button": [0.3, 2.7], "save_button_specs_icons": [1.4, 1.4], + "monitor_preheat_temperature_control": [4.5, 2.0], + "modal_window_minimum": [60.0, 45], "license_window_minimum": [45, 45], "wizard_progress": [10.0, 0.0],