diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 84ec787cd7..5d5d04b716 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -91,6 +91,7 @@ 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 @@ -140,6 +141,7 @@ class CuraApplication(QtApplication): MachineStack = Resources.UserType + 7 ExtruderStack = Resources.UserType + 8 DefinitionChangesContainer = Resources.UserType + 9 + SettingVisibilityPreset = Resources.UserType + 10 Q_ENUMS(ResourceTypes) @@ -187,6 +189,7 @@ class CuraApplication(QtApplication): Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders") Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances") Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") + Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility") ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality") ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes") @@ -373,19 +376,9 @@ class CuraApplication(QtApplication): preferences.setDefault("local_file/last_used_type", "text/x-gcode") - setting_visibily_preset_names = self.getVisibilitySettingPresetTypes() - preferences.setDefault("general/visible_settings_preset", setting_visibily_preset_names) + default_visibility_profile = SettingVisibilityPresetsModel.getInstance().getItem(0) - preset_setting_visibility_choice = Preferences.getInstance().getValue("general/preset_setting_visibility_choice") - - default_preset_visibility_group_name = "Basic" - if preset_setting_visibility_choice == "" or preset_setting_visibility_choice is None: - if preset_setting_visibility_choice not in setting_visibily_preset_names: - preset_setting_visibility_choice = default_preset_visibility_group_name - - visible_settings = self.getVisibilitySettingPreset(settings_preset_name = preset_setting_visibility_choice) - preferences.setDefault("general/visible_settings", visible_settings) - preferences.setDefault("general/preset_setting_visibility_choice", preset_setting_visibility_choice) + preferences.setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"])) self.applicationShuttingDown.connect(self.saveSettings) self.engineCreatedSignal.connect(self._onEngineCreated) @@ -402,91 +395,6 @@ class CuraApplication(QtApplication): CuraApplication.Created = True - @pyqtSlot(str, result = str) - def getVisibilitySettingPreset(self, settings_preset_name) -> str: - result = self._loadPresetSettingVisibilityGroup(settings_preset_name) - formatted_preset_settings = self._serializePresetSettingVisibilityData(result) - - return formatted_preset_settings - - ## Serialise the given preset setting visibitlity group dictionary into a string which is concatenated by ";" - # - def _serializePresetSettingVisibilityData(self, settings_data: dict) -> str: - result_string = "" - - for key in settings_data: - result_string += key + ";" - for value in settings_data[key]: - result_string += value + ";" - - return result_string - - ## Load the preset setting visibility group with the given name - # - def _loadPresetSettingVisibilityGroup(self, visibility_preset_name) -> Dict[str, str]: - preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups) - - result = {} - right_preset_found = False - - for item in os.listdir(preset_dir): - file_path = os.path.join(preset_dir, item) - if not os.path.isfile(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"): - continue - - if parser["general"]["name"] == visibility_preset_name: - right_preset_found = True - for section in parser.sections(): - if section == 'general': - continue - else: - section_settings = [] - for option in parser[section].keys(): - section_settings.append(option) - - result[section] = section_settings - - if right_preset_found: - break - - except Exception as e: - Logger.log("e", "Failed to load setting visibility preset %s: %s", file_path, str(e)) - - return result - - ## Check visibility setting preset folder and returns available types - # - def getVisibilitySettingPresetTypes(self): - preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups) - result = {} - - for item in os.listdir(preset_dir): - file_path = os.path.join(preset_dir, item) - if not os.path.isfile(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") and not parser.has_option("general", "weight"): - continue - - result[parser["general"]["weight"]] = parser["general"]["name"] - - except Exception as e: - Logger.log("e", "Failed to load setting preset %s: %s", file_path, str(e)) - - return result def _onEngineCreated(self): self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) @@ -986,6 +894,7 @@ class CuraApplication(QtApplication): 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/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index a4934b7f74..eefbd9ae12 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -56,12 +56,15 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): self._connection_state_before_timeout = None # type: Optional[ConnectionState] printer_type = self._properties.get(b"machine", b"").decode("utf-8") - if printer_type.startswith("9511"): - self._printer_type = "ultimaker3_extended" - elif printer_type.startswith("9066"): - self._printer_type = "ultimaker3" - else: - self._printer_type = "unknown" + printer_type_identifiers = { + "9066": "ultimaker3", + "9511": "ultimaker3_extended" + } + self._printer_type = "Unknown" + for key, value in printer_type_identifiers.items(): + if printer_type.startswith(key): + self._printer_type = value + break def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs) -> None: raise NotImplementedError("requestWrite needs to be implemented") diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 81cbabc0c9..4e8ece0504 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -212,16 +212,29 @@ class CuraContainerRegistry(ContainerRegistry): return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "This profile {0} contains incorrect data, could not import it.", file_name)} profile_definition = global_profile.getMetaDataEntry("definition") - expected_machine_definition = "fdmprinter" - if parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", "False")): - expected_machine_definition = global_container_stack.getMetaDataEntry("quality_definition") - if not expected_machine_definition: - expected_machine_definition = global_container_stack.definition.getId() - if expected_machine_definition is not None and profile_definition is not None and profile_definition != expected_machine_definition: + + # Make sure we have a profile_definition in the file: + if profile_definition is None: + break + + # Get the expected machine definition. + # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode... + expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_container_stack.definition) + + # ...but that's not always the case for Cura 3.1 and older, so also get the current machine: + current_machine_definition = global_container_stack.definition.getId() + + # And check if the profile_definition matches either one (showing error if not): + if profile_definition not in (expected_machine_definition, current_machine_definition): Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition) return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)} + # Fix the global quality profile's definition field in case it's not correct + global_profile.setMetaDataEntry("definition", expected_machine_definition) + quality_name = global_profile.getName() + quality_type = global_profile.getMetaDataEntry("quality_type") + name_seed = os.path.splitext(os.path.basename(file_name))[0] new_name = self.uniqueName(name_seed) @@ -236,11 +249,11 @@ class CuraContainerRegistry(ContainerRegistry): for idx, extruder in enumerate(global_container_stack.extruders.values()): profile_id = ContainerRegistry.getInstance().uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1)) profile = InstanceContainer(profile_id) - profile.setName(global_profile.getName()) + profile.setName(quality_name) profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) profile.addMetaDataEntry("type", "quality_changes") - profile.addMetaDataEntry("definition", global_profile.getMetaDataEntry("definition")) - profile.addMetaDataEntry("quality_type", global_profile.getMetaDataEntry("quality_type")) + profile.addMetaDataEntry("definition", expected_machine_definition) + profile.addMetaDataEntry("quality_type", quality_type) profile.addMetaDataEntry("position", "0") profile.setDirty(True) if idx == 0: diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index b41cdc9799..019354ad40 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -882,7 +882,7 @@ class MachineManager(QObject): @pyqtSlot() def forceUpdateAllSettings(self): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): - property_names = ["value", "resolve"] + property_names = ["value", "resolve", "validationState"] for container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()): for setting_key in container.getAllKeys(): container.propertiesChanged.emit(setting_key, property_names) diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py index 0d4cd02cdb..e317b20f68 100644 --- a/cura/Settings/SettingInheritanceManager.py +++ b/cura/Settings/SettingInheritanceManager.py @@ -1,7 +1,7 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal +from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal from UM.FlameProfiler import pyqtSlot from UM.Application import Application from UM.Logger import Logger @@ -30,6 +30,11 @@ class SettingInheritanceManager(QObject): ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) self._onActiveExtruderChanged() + self._update_timer = QTimer() + self._update_timer.setInterval(500) + self._update_timer.setSingleShot(True) + self._update_timer.timeout.connect(self._update) + settingsWithIntheritanceChanged = pyqtSignal() ## Get the keys of all children settings with an override. @@ -226,9 +231,7 @@ class SettingInheritanceManager(QObject): self._onActiveExtruderChanged() def _onContainersChanged(self, container): - # TODO: Multiple container changes in sequence now cause quite a few recalculations. - # This isn't that big of an issue, but it could be in the future. - self._update() + self._update_timer.start() @staticmethod def createSettingInheritanceManager(engine=None, script_engine=None): diff --git a/cura/Settings/SettingVisibilityPresetsModel.py b/cura/Settings/SettingVisibilityPresetsModel.py new file mode 100644 index 0000000000..e5a2e24412 --- /dev/null +++ b/cura/Settings/SettingVisibilityPresetsModel.py @@ -0,0 +1,136 @@ +# 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/Snapshot.py b/cura/Snapshot.py index afc8818116..1f2a24aecd 100644 --- a/cura/Snapshot.py +++ b/cura/Snapshot.py @@ -66,7 +66,7 @@ class Snapshot: size = max(bbox.width, bbox.height, bbox.depth * 0.5) # Looking from this direction (x, y, z) in OGL coordinates - looking_from_offset = Vector(1, 1, -2) + looking_from_offset = Vector(-1, 1, 2) if size > 0: # determine the watch distance depending on the size looking_from_offset = looking_from_offset * size * 1.3 diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 19e6b89f07..f20b9ab181 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -594,7 +594,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged") else: global_preferences.setValue("general/visible_settings", visible_settings) - global_preferences.setValue("general/preset_setting_visibility_choice", "Custom") + global_preferences.setValue("cura/active_setting_visibility_preset", "custom") categories_expanded = temp_preferences.getValue("cura/categories_expanded") if categories_expanded is None: diff --git a/plugins/USBPrinting/AutoDetectBaudJob.py b/plugins/USBPrinting/AutoDetectBaudJob.py index 72f4f20262..50bb831ba8 100644 --- a/plugins/USBPrinting/AutoDetectBaudJob.py +++ b/plugins/USBPrinting/AutoDetectBaudJob.py @@ -22,6 +22,7 @@ class AutoDetectBaudJob(Job): def run(self): Logger.log("d", "Auto detect baud rate started.") timeout = 3 + tries = 2 programmer = Stk500v2() serial = None @@ -31,36 +32,38 @@ class AutoDetectBaudJob(Job): except: programmer.close() - for baud_rate in self._all_baud_rates: - Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate)) + for retry in range(tries): + for baud_rate in self._all_baud_rates: + Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate)) - if serial is None: - try: - serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout) - except SerialException as e: - Logger.logException("w", "Unable to create serial") - continue - else: - # We already have a serial connection, just change the baud rate. - try: - serial.baudrate = baud_rate - except: - continue - sleep(1.5) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number - successful_responses = 0 - - serial.write(b"\n") # Ensure we clear out previous responses - serial.write(b"M105\n") - - timeout_time = time() + timeout - - while timeout_time > time(): - line = serial.readline() - if b"ok T:" in line: - successful_responses += 1 - if successful_responses >= 3: - self.setResult(baud_rate) - return + if serial is None: + try: + serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout) + except SerialException as e: + Logger.logException("w", "Unable to create serial") + continue + else: + # We already have a serial connection, just change the baud rate. + try: + serial.baudrate = baud_rate + except: + continue + sleep(1.5) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number + successful_responses = 0 + serial.write(b"\n") # Ensure we clear out previous responses serial.write(b"M105\n") + + timeout_time = time() + timeout + + while timeout_time > time(): + line = serial.readline() + if b"ok T:" in line: + successful_responses += 1 + if successful_responses >= 3: + self.setResult(baud_rate) + return + + serial.write(b"M105\n") + sleep(15) # Give the printer some time to init and try again. self.setResult(None) # Unable to detect the correct baudrate. diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 11cc7bf472..5d99f61a51 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -198,7 +198,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice): # Reset line number. If this is not done, first line is sometimes ignored self._gcode.insert(0, "M110") self._gcode_position = 0 - self._is_printing = True self._print_start_time = time() self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds)) @@ -206,6 +205,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): for i in range(0, 4): # Push first 4 entries before accepting other inputs self._sendNextGcodeLine() + self._is_printing = True self.writeFinished.emit(self) def _autoDetectFinished(self, job: AutoDetectBaudJob): @@ -267,7 +267,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice): if not command.endswith(b"\n"): command += b"\n" try: - self._serial.write(b"\n") self._serial.write(command) except SerialTimeoutException: Logger.log("w", "Timeout when sending command to printer via USB.") @@ -284,7 +283,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self.sendCommand("M105") self._last_temperature_request = time() - if b"ok T:" in line or line.startswith(b"T:"): # Temperature message + if b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line) # Update all temperature values for match, extruder in zip(extruder_temperature_matches, self._printers[0].extruders): @@ -302,6 +301,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._printers[0].updateTargetBedTemperature(float(match[1])) if self._is_printing: + if line.startswith(b'!!'): + Logger.log('e', "Printer signals fatal error. Cancelling print. {}".format(line)) + self.cancelPrint() if b"ok" in line: if not self._command_queue.empty(): self._sendCommand(self._command_queue.get()) diff --git a/resources/definitions/printrbot_simple_makers_kit.def.json b/resources/definitions/printrbot_simple_makers_kit.def.json new file mode 100644 index 0000000000..e2afd57826 --- /dev/null +++ b/resources/definitions/printrbot_simple_makers_kit.def.json @@ -0,0 +1,38 @@ +{ + "version": 2, + "name": "Printrbot Simple Maker's Kit (1405)", + "inherits": "fdmprinter", + "metadata": { + "visible": true, + "author": "Timur Tabi", + "manufacturer": "Printrbot", + "file_formats": "text/x-gcode" + }, + + "overrides": { + "machine_name": { "default_value": "Printrbot Simple Maker's Kit (1405)" }, + "machine_heated_bed": { "default_value": false }, + "machine_width": { "default_value": 100 }, + "machine_depth": { "default_value": 100 }, + "machine_height": { "default_value": 115 }, + "material_diameter": { "default_value": 1.75 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "machine_head_with_fans_polygon": { + "default_value": [ + [-40, 1000], + [-40, -10], + [60, 1000], + [60, -10] + ] + }, + "gantry_height": { "default_value": 1000 }, + "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, + + "machine_start_gcode": { + "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;home X/Y\nG28 Z0 ;home Z\nG92 E0 ;zero the extruded length\nG29 ;initiate auto bed leveling sequence" + }, + "machine_end_gcode": { + "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nM106 S0 ;fan off\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit\nG1 Z+1 E-5 F9000 ;move Z up a bit and retract even more\nG28 X0 Y0 ;home X/Y, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning" + } + } +} diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index c81281f0d7..cff4399073 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -653,7 +653,10 @@ UM.MainWindow { preferences.visible = true; preferences.setPage(1); - preferences.getCurrentItem().scrollToSection(source.key); + if(source && source.key) + { + preferences.getCurrentItem().scrollToSection(source.key); + } } } diff --git a/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml b/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml index 4a2d4cd062..52fd0e6556 100644 --- a/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml +++ b/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml @@ -59,7 +59,7 @@ Column section.criteria: ViewSection.FullString section.delegate: sectionHeading - model: (ouputDevice != null) ? outputDevice.uniqueConfigurations : [] + model: (outputDevice != null) ? outputDevice.uniqueConfigurations : [] delegate: ConfigurationItem { width: parent.width - UM.Theme.getSize("default_margin").width diff --git a/resources/qml/Menus/SettingVisibilityPresetsMenu.qml b/resources/qml/Menus/SettingVisibilityPresetsMenu.qml new file mode 100644 index 0000000000..19c36e6118 --- /dev/null +++ b/resources/qml/Menus/SettingVisibilityPresetsMenu.qml @@ -0,0 +1,82 @@ +// 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 UM 1.2 as UM +import Cura 1.0 as Cura + +Menu +{ + id: menu + title: catalog.i18nc("@action:inmenu", "Visible Settings") + + 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 + + MenuItem + { + text: model.name + checkable: true + checked: model.id == Cura.SettingVisibilityPresetsModel.activePreset + exclusiveGroup: group + onTriggered: + { + Cura.SettingVisibilityPresetsModel.setActivePreset(model.id); + + UM.Preferences.setValue("general/visible_settings", model.settings.join(";")); + + showSettingVisibilityProfile(); + } + } + + onObjectAdded: menu.insertItem(index, object) + onObjectRemoved: menu.removeItem(object) + } + + MenuSeparator {} + MenuItem + { + text: catalog.i18nc("@action:inmenu", "All Settings") + checkable: true + checked: showingAllSettings + exclusiveGroup: group + onTriggered: + { + showAllSettings(); + } + } + MenuSeparator {} + MenuItem + { + text: catalog.i18nc("@action:inmenu", "Manage Setting Visibility...") + iconName: "configure" + onTriggered: Cura.Actions.configureSettingVisibility.trigger() + } + + ExclusiveGroup { id: group } +} diff --git a/resources/qml/Preferences/SettingVisibilityPage.qml b/resources/qml/Preferences/SettingVisibilityPage.qml index 0e3069d194..f0c24e2cbe 100644 --- a/resources/qml/Preferences/SettingVisibilityPage.qml +++ b/resources/qml/Preferences/SettingVisibilityPage.qml @@ -26,8 +26,8 @@ UM.PreferencesPage UM.Preferences.resetPreference("general/visible_settings") // After calling this function update Setting visibility preset combobox. - // Reset should set "Basic" setting preset - visibilityPreset.setBasicPreset() + // Reset should set default setting preset ("Basic") + visibilityPreset.setDefaultPreset() } resetEnabled: true; @@ -37,6 +37,8 @@ UM.PreferencesPage id: base; anchors.fill: parent; + property bool inhibitSwitchToCustom: false + CheckBox { id: toggleVisibleSettings @@ -84,7 +86,7 @@ UM.PreferencesPage if (visibilityPreset.currentIndex != visibilityPreset.model.count - 1) { visibilityPreset.currentIndex = visibilityPreset.model.count - 1 - UM.Preferences.setValue("general/preset_setting_visibility_choice", visibilityPreset.model.get(visibilityPreset.currentIndex).text) + UM.Preferences.setValue("cura/active_setting_visibility_preset", visibilityPreset.model.getItem(visibilityPreset.currentIndex).id) } } } @@ -110,25 +112,13 @@ UM.PreferencesPage ComboBox { - property int customOptionValue: 100 - - function setBasicPreset() + function setDefaultPreset() { - var index = 0 - for(var i = 0; i < presetNamesList.count; ++i) - { - if(model.get(i).text == "Basic") - { - index = i; - break; - } - } - - visibilityPreset.currentIndex = index + visibilityPreset.currentIndex = 0 } id: visibilityPreset - width: 150 + width: 150 * screenScaleFactor anchors { top: parent.top @@ -137,56 +127,49 @@ UM.PreferencesPage model: ListModel { - id: presetNamesList + id: visibilityPresetsModel Component.onCompleted: { - // returned value is Dictionary (Ex: {1:"Basic"}, The number 1 is the weight and sort by weight) - var itemsDict = UM.Preferences.getValue("general/visible_settings_preset") - var sorted = []; - for(var key in itemsDict) { - sorted[sorted.length] = key; - } + visibilityPresetsModel.append({text: catalog.i18nc("@action:inmenu", "Custom selection"), id: "custom"}); - sorted.sort(); - for(var i = 0; i < sorted.length; i++) { - presetNamesList.append({text: itemsDict[sorted[i]], value: i}); + var presets = Cura.SettingVisibilityPresetsModel; + for(var i = 0; i < presets.rowCount(); i++) + { + visibilityPresetsModel.append({text: presets.getItem(i)["name"], id: presets.getItem(i)["id"]}); } - - // By agreement lets "Custom" option will have value 100 - presetNamesList.append({text: "Custom", value: visibilityPreset.customOptionValue}); } } currentIndex: { // Load previously selected preset. - var text = UM.Preferences.getValue("general/preset_setting_visibility_choice"); - - - - var index = 0; - for(var i = 0; i < presetNamesList.count; ++i) + var index = Cura.SettingVisibilityPresetsModel.find("id", Cura.SettingVisibilityPresetsModel.activePreset); + if(index == -1) { - if(model.get(i).text == text) - { - index = i; - break; - } + return 0; } - return index; + + return index + 1; // "Custom selection" entry is added in front, so index is off by 1 } onActivated: { - // TODO What to do if user is selected "Custom from Combobox" ? - if (model.get(index).text == "Custom"){ - UM.Preferences.setValue("general/preset_setting_visibility_choice", model.get(index).text) - return - } + base.inhibitSwitchToCustom = true; + var preset_id = visibilityPresetsModel.get(index).id; + Cura.SettingVisibilityPresetsModel.setActivePreset(preset_id); - var newVisibleSettings = CuraApplication.getVisibilitySettingPreset(model.get(index).text) - UM.Preferences.setValue("general/visible_settings", newVisibleSettings) - UM.Preferences.setValue("general/preset_setting_visibility_choice", model.get(index).text) + 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; } } @@ -216,7 +199,16 @@ UM.PreferencesPage exclude: ["machine_settings", "command_line_settings"] showAncestors: true expanded: ["*"] - visibilityHandler: UM.SettingPreferenceVisibilityHandler { } + visibilityHandler: UM.SettingPreferenceVisibilityHandler + { + onVisibilityChanged: + { + if(Cura.SettingVisibilityPresetsModel.activePreset != "" && !base.inhibitSwitchToCustom) + { + Cura.SettingVisibilityPresetsModel.setActivePreset("custom"); + } + } + } } delegate: Loader @@ -259,19 +251,7 @@ UM.PreferencesPage { id: settingVisibilityItem; - UM.SettingVisibilityItem { - - // after changing any visibility of settings, set the preset to the "Custom" option - visibilityChangeCallback : function() - { - // If already "Custom" then don't do nothing - if (visibilityPreset.currentIndex != visibilityPreset.model.count - 1) - { - visibilityPreset.currentIndex = visibilityPreset.model.count - 1 - UM.Preferences.setValue("general/preset_setting_visibility_choice", visibilityPreset.model.get(visibilityPreset.currentIndex).text) - } - } - } + UM.SettingVisibilityItem { } } } } \ No newline at end of file diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 7a967e211c..235dfac91a 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -15,10 +15,11 @@ Item { id: base; - property Action configureSettings; - property bool findingSettings; - signal showTooltip(Item item, point location, string text); - signal hideTooltip(); + property Action configureSettings + property bool findingSettings + property bool showingAllSettings + signal showTooltip(Item item, point location, string text) + signal hideTooltip() Item { @@ -107,6 +108,57 @@ Item } } + ToolButton + { + id: settingVisibilityMenu + + width: height + height: UM.Theme.getSize("setting_control").height + anchors + { + top: globalProfileRow.bottom + topMargin: UM.Theme.getSize("sidebar_margin").height + right: parent.right + rightMargin: UM.Theme.getSize("sidebar_margin").width + } + style: ButtonStyle + { + background: Item { + UM.RecolorImage { + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + width: UM.Theme.getSize("standard_arrow").width + height: UM.Theme.getSize("standard_arrow").height + sourceSize.width: width + sourceSize.height: width + color: control.enabled ? UM.Theme.getColor("setting_category_text") : UM.Theme.getColor("setting_category_disabled_text") + source: UM.Theme.getIcon("menu") + } + } + label: Label{} + } + menu: SettingVisibilityPresetsMenu + { + showingSearchResults: findingSettings + showingAllSettings: showingAllSettings + + onShowAllSettings: + { + base.showingAllSettings = true; + base.findingSettings = false; + filter.text = ""; + filter.updateDefinitionModel(); + } + onShowSettingVisibilityProfile: + { + base.showingAllSettings = false; + base.findingSettings = false; + filter.text = ""; + filter.updateDefinitionModel(); + } + } + } + Rectangle { id: filterContainer @@ -132,9 +184,9 @@ Item top: globalProfileRow.bottom topMargin: UM.Theme.getSize("sidebar_margin").height left: parent.left - leftMargin: Math.round(UM.Theme.getSize("sidebar_margin").width) - right: parent.right - rightMargin: Math.round(UM.Theme.getSize("sidebar_margin").width) + leftMargin: UM.Theme.getSize("sidebar_margin").width + right: settingVisibilityMenu.left + rightMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2) } height: visible ? UM.Theme.getSize("setting_control").height : 0 Behavior on height { NumberAnimation { duration: 100 } } @@ -168,17 +220,9 @@ Item { if(findingSettings) { - expandedCategories = definitionsModel.expanded.slice(); - definitionsModel.expanded = ["*"]; - definitionsModel.showAncestors = true; - definitionsModel.showAll = true; - } - else - { - definitionsModel.expanded = expandedCategories; - definitionsModel.showAncestors = false; - definitionsModel.showAll = false; + showingAllSettings = false; } + updateDefinitionModel(); lastFindingSettings = findingSettings; } } @@ -187,6 +231,27 @@ Item { filter.text = ""; } + + function updateDefinitionModel() + { + if(findingSettings || showingAllSettings) + { + expandedCategories = definitionsModel.expanded.slice(); + definitionsModel.expanded = [""]; // keep categories closed while to prevent render while making settings visible one by one + definitionsModel.showAncestors = true; + definitionsModel.showAll = true; + definitionsModel.expanded = ["*"]; + } + else + { + if(expandedCategories) + { + definitionsModel.expanded = expandedCategories; + } + definitionsModel.showAncestors = false; + definitionsModel.showAll = false; + } + } } MouseArea @@ -209,7 +274,7 @@ Item anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right - anchors.rightMargin: Math.round(UM.Theme.getSize("sidebar_margin").width) + anchors.rightMargin: UM.Theme.getSize("default_margin").width color: UM.Theme.getColor("setting_control_button") hoverColor: UM.Theme.getColor("setting_control_button_hover") @@ -491,9 +556,17 @@ Item MenuItem { //: Settings context menu action - visible: !findingSettings; + visible: !(findingSettings || showingAllSettings); text: catalog.i18nc("@action:menu", "Hide this setting"); - onTriggered: definitionsModel.hide(contextMenu.key); + onTriggered: + { + definitionsModel.hide(contextMenu.key); + // visible settings have changed, so we're no longer showing a preset + if (Cura.SettingVisibilityPresetsModel.activePreset != "" && !showingAllSettings) + { + Cura.SettingVisibilityPresetsModel.setActivePreset("custom"); + } + } } MenuItem { @@ -509,7 +582,7 @@ Item return catalog.i18nc("@action:menu", "Keep this setting visible"); } } - visible: findingSettings; + visible: (findingSettings || showingAllSettings); onTriggered: { if (contextMenu.settingVisible) @@ -520,6 +593,11 @@ Item { definitionsModel.show(contextMenu.key); } + // visible settings have changed, so we're no longer showing a preset + if (Cura.SettingVisibilityPresetsModel.activePreset != "" && !showingAllSettings) + { + Cura.SettingVisibilityPresetsModel.setActivePreset("custom"); + } } } MenuItem diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index 5591f49797..5cd0446b36 100644 --- a/resources/qml/SidebarHeader.qml +++ b/resources/qml/SidebarHeader.qml @@ -347,7 +347,8 @@ Column id: materialSelection property var activeExtruder: Cura.MachineManager.activeStack - property var currentRootMaterialName: activeExtruder.material.name + property var hasActiveExtruder: activeExtruder != null + property var currentRootMaterialName: hasActiveExtruder ? activeExtruder.material.name : "" text: currentRootMaterialName tooltip: currentRootMaterialName @@ -366,6 +367,10 @@ Column property var valueWarning: ! Cura.MachineManager.isActiveQualitySupported function isMaterialSupported () { + if (!hasActiveExtruder) + { + return false; + } return Cura.ContainerManager.getContainerMetaDataEntry(activeExtruder.material.id, "compatible") == "True" } } diff --git a/resources/qml/SidebarSimple.qml b/resources/qml/SidebarSimple.qml index a7c8a1b8c5..41ecb529eb 100644 --- a/resources/qml/SidebarSimple.qml +++ b/resources/qml/SidebarSimple.qml @@ -243,6 +243,81 @@ Item anchors.top: parent.top anchors.topMargin: UM.Theme.getSize("sidebar_margin").height + // This Item is used only for tooltip, for slider area which is unavailable + Item + { + function showTooltip (showTooltip) + { + if (showTooltip) { + var content = catalog.i18nc("@tooltip", "This quality profile is not available for you current material and nozzle configuration. Please change these to enable this quality profile") + base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("sidebar_margin").width, customisedSettings.height), content) + } + else { + base.hideTooltip() + } + } + + id: unavailableLineToolTip + height: 20 // hovered area height + z: parent.z + 1 // should be higher, otherwise the area can be hovered + x: 0 + anchors.verticalCenter: qualitySlider.verticalCenter + + Rectangle + { + id: leftArea + width: + { + if (qualityModel.availableTotalTicks == 0) { + return qualityModel.qualitySliderStepWidth * qualityModel.totalTicks + } + return qualityModel.qualitySliderStepWidth * qualityModel.qualitySliderAvailableMin - 10 + } + height: parent.height + color: "transparent" + + MouseArea + { + anchors.fill: parent + hoverEnabled: true + enabled: Cura.SimpleModeSettingsManager.isProfileUserCreated == false + onEntered: unavailableLineToolTip.showTooltip(true) + onExited: unavailableLineToolTip.showTooltip(false) + } + } + + Rectangle + { + id: rightArea + width: { + if(qualityModel.availableTotalTicks == 0) + return 0 + + return qualityModel.qualitySliderMarginRight - 10 + } + height: parent.height + color: "transparent" + x: { + if (qualityModel.availableTotalTicks == 0) { + return 0 + } + + var leftUnavailableArea = qualityModel.qualitySliderStepWidth * qualityModel.qualitySliderAvailableMin + var totalGap = qualityModel.qualitySliderStepWidth * (qualityModel.availableTotalTicks -1) + leftUnavailableArea + 10 + + return totalGap + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + enabled: Cura.SimpleModeSettingsManager.isProfileUserCreated == false + onEntered: unavailableLineToolTip.showTooltip(true) + onExited: unavailableLineToolTip.showTooltip(false) + } + } + } + // Draw Unavailable line Rectangle { diff --git a/resources/qml/WorkspaceSummaryDialog.qml b/resources/qml/WorkspaceSummaryDialog.qml index 12eba13dd9..cf19e45fdd 100644 --- a/resources/qml/WorkspaceSummaryDialog.qml +++ b/resources/qml/WorkspaceSummaryDialog.qml @@ -101,7 +101,7 @@ UM.Dialog } Label { - text: Cura.MachineManager.activeMachine.definition.name + text: (Cura.MachineManager.activeMachine == null) ? "" : Cura.MachineManager.activeMachine.definition.name width: (parent.width / 3) | 0 } } diff --git a/resources/preset_setting_visibility_groups/advanced.cfg b/resources/setting_visibility/advanced.cfg similarity index 100% rename from resources/preset_setting_visibility_groups/advanced.cfg rename to resources/setting_visibility/advanced.cfg diff --git a/resources/preset_setting_visibility_groups/basic.cfg b/resources/setting_visibility/basic.cfg similarity index 100% rename from resources/preset_setting_visibility_groups/basic.cfg rename to resources/setting_visibility/basic.cfg diff --git a/resources/preset_setting_visibility_groups/expert.cfg b/resources/setting_visibility/expert.cfg similarity index 100% rename from resources/preset_setting_visibility_groups/expert.cfg rename to resources/setting_visibility/expert.cfg diff --git a/resources/themes/cura-light/icons/menu.svg b/resources/themes/cura-light/icons/menu.svg new file mode 100644 index 0000000000..85fbfb072c --- /dev/null +++ b/resources/themes/cura-light/icons/menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/variants/ultimaker3_bb0.8.inst.cfg b/resources/variants/ultimaker3_bb0.8.inst.cfg index 41c6419ec1..ef6dc625ac 100644 --- a/resources/variants/ultimaker3_bb0.8.inst.cfg +++ b/resources/variants/ultimaker3_bb0.8.inst.cfg @@ -56,7 +56,6 @@ retraction_amount = 4.5 retraction_count_max = 15 retraction_extrusion_window = =retraction_amount retraction_hop = 2 -retraction_hop_enabled = True retraction_hop_only_when_collides = True retraction_min_travel = 5 retraction_prime_speed = 15 diff --git a/tests/TestProfileRequirements.py b/tests/TestProfileRequirements.py index a91a08172c..edeec909f2 100644 --- a/tests/TestProfileRequirements.py +++ b/tests/TestProfileRequirements.py @@ -22,4 +22,4 @@ def test_ultimaker3extended_variants(um3_file, um3e_file): um3.read_file(open(os.path.join(directory, um3_file))) um3e = configparser.ConfigParser() um3e.read_file(open(os.path.join(directory, um3e_file))) - assert um3["values"] == um3e["values"] \ No newline at end of file + assert um3["values"] == um3e["values"]