Merge branch 'master' into feature_custom_gcode_commands

This commit is contained in:
ChrisTerBeke 2018-03-15 16:40:42 +01:00
commit 5ee30e0c99
51 changed files with 1064 additions and 446 deletions

View File

@ -136,6 +136,7 @@ class BuildVolume(SceneNode):
if active_extruder_changed is not None: if active_extruder_changed is not None:
node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild) node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild)
node.decoratorsChanged.disconnect(self._updateNodeListeners) node.decoratorsChanged.disconnect(self._updateNodeListeners)
self._updateDisallowedAreasAndRebuild() # make sure we didn't miss anything before we updated the node listeners
self._scene_objects = new_scene_objects self._scene_objects = new_scene_objects
self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered. self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered.
@ -150,7 +151,6 @@ class BuildVolume(SceneNode):
active_extruder_changed = node.callDecoration("getActiveExtruderChangedSignal") active_extruder_changed = node.callDecoration("getActiveExtruderChangedSignal")
if active_extruder_changed is not None: if active_extruder_changed is not None:
active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild) active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild)
self._updateDisallowedAreasAndRebuild()
def setWidth(self, width): def setWidth(self, width):
if width is not None: if width is not None:

View File

@ -109,10 +109,6 @@ class CuraActions(QObject):
nodes_to_change = [] nodes_to_change = []
for node in Selection.getAllSelectedObjects(): for node in Selection.getAllSelectedObjects():
# Do not change any nodes that already have the right extruder set.
if node.callDecoration("getActiveExtruder") == extruder_id:
continue
# If the node is a group, apply the active extruder to all children of the group. # If the node is a group, apply the active extruder to all children of the group.
if node.callDecoration("isGroup"): if node.callDecoration("isGroup"):
for grouped_node in BreadthFirstIterator(node): for grouped_node in BreadthFirstIterator(node):
@ -125,6 +121,10 @@ class CuraActions(QObject):
nodes_to_change.append(grouped_node) nodes_to_change.append(grouped_node)
continue continue
# Do not change any nodes that already have the right extruder set.
if node.callDecoration("getActiveExtruder") == extruder_id:
continue
nodes_to_change.append(node) nodes_to_change.append(node)
if not nodes_to_change: if not nodes_to_change:

View File

@ -1,10 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
#Type hinting. from PyQt5.QtCore import QObject, QTimer
from typing import Dict
from PyQt5.QtCore import QObject
from PyQt5.QtNetwork import QLocalServer from PyQt5.QtNetwork import QLocalServer
from PyQt5.QtNetwork import QLocalSocket 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.QualitySettingsModel import QualitySettingsModel
from cura.Machines.Models.MachineManagementModel import MachineManagementModel from cura.Machines.Models.MachineManagementModel import MachineManagementModel
from cura.Machines.Models.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel
from cura.Machines.MachineErrorChecker import MachineErrorChecker from cura.Machines.MachineErrorChecker import MachineErrorChecker
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
@ -100,7 +99,6 @@ from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
from configparser import ConfigParser
import sys import sys
import os.path import os.path
import numpy import numpy
@ -140,6 +138,7 @@ class CuraApplication(QtApplication):
MachineStack = Resources.UserType + 7 MachineStack = Resources.UserType + 7
ExtruderStack = Resources.UserType + 8 ExtruderStack = Resources.UserType + 8
DefinitionChangesContainer = Resources.UserType + 9 DefinitionChangesContainer = Resources.UserType + 9
SettingVisibilityPreset = Resources.UserType + 10
Q_ENUMS(ResourceTypes) Q_ENUMS(ResourceTypes)
@ -187,6 +186,7 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders") Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders")
Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances") Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") 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")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes") ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes")
@ -223,6 +223,7 @@ class CuraApplication(QtApplication):
self._object_manager = None self._object_manager = None
self._build_plate_model = None self._build_plate_model = None
self._multi_build_plate_model = None self._multi_build_plate_model = None
self._setting_visibility_presets_model = None
self._setting_inheritance_manager = None self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None self._simple_mode_settings_manager = None
self._cura_scene_controller = None self._cura_scene_controller = None
@ -283,10 +284,15 @@ class CuraApplication(QtApplication):
self._preferred_mimetype = "" self._preferred_mimetype = ""
self._i18n_catalog = i18nCatalog("cura") self._i18n_catalog = i18nCatalog("cura")
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity) self._update_platform_activity_timer = QTimer()
self._update_platform_activity_timer.setInterval(500)
self._update_platform_activity_timer.setSingleShot(True)
self._update_platform_activity_timer.timeout.connect(self.updatePlatformActivity)
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivityDelayed)
self.getController().toolOperationStopped.connect(self._onToolOperationStopped) self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
self.getController().contextMenuRequested.connect(self._onContextMenuRequested) self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivity) self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivityDelayed)
Resources.addType(self.ResourceTypes.QmlFiles, "qml") Resources.addType(self.ResourceTypes.QmlFiles, "qml")
Resources.addType(self.ResourceTypes.Firmware, "firmware") Resources.addType(self.ResourceTypes.Firmware, "firmware")
@ -373,20 +379,6 @@ class CuraApplication(QtApplication):
preferences.setDefault("local_file/last_used_type", "text/x-gcode") 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)
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)
self.applicationShuttingDown.connect(self.saveSettings) self.applicationShuttingDown.connect(self.saveSettings)
self.engineCreatedSignal.connect(self._onEngineCreated) self.engineCreatedSignal.connect(self._onEngineCreated)
@ -402,91 +394,6 @@ class CuraApplication(QtApplication):
CuraApplication.Created = True 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): def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@ -774,6 +681,11 @@ class CuraApplication(QtApplication):
self._print_information = PrintInformation.PrintInformation() self._print_information = PrintInformation.PrintInformation()
self._cura_actions = CuraActions.CuraActions(self) 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 # Detect in which mode to run and execute that mode
if self.getCommandLineOption("headless", False): if self.getCommandLineOption("headless", False):
self.runWithoutGUI() self.runWithoutGUI()
@ -856,6 +768,10 @@ class CuraApplication(QtApplication):
def hasGui(self): def hasGui(self):
return self._use_gui return self._use_gui
@pyqtSlot(result = QObject)
def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel:
return self._setting_visibility_presets_model
def getMachineErrorChecker(self, *args) -> MachineErrorChecker: def getMachineErrorChecker(self, *args) -> MachineErrorChecker:
return self._machine_error_checker return self._machine_error_checker
@ -982,6 +898,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel") qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler") qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
qmlRegisterType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel")
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel") qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
@ -1061,6 +978,10 @@ class CuraApplication(QtApplication):
def getSceneBoundingBoxString(self): def getSceneBoundingBoxString(self):
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()} return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
def updatePlatformActivityDelayed(self, node = None):
if node is not None and node.getMeshData() is not None:
self._update_platform_activity_timer.start()
## Update scene bounding box for current build plate ## Update scene bounding box for current build plate
def updatePlatformActivity(self, node = None): def updatePlatformActivity(self, node = None):
count = 0 count = 0

View File

@ -3,7 +3,7 @@
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt, pyqtSignal from PyQt5.QtCore import Qt
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
@ -11,6 +11,7 @@ from UM.Settings.ContainerStack import ContainerStack
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
# #
# This the QML model for the quality management page. # This the QML model for the quality management page.
# #
@ -39,7 +40,7 @@ class MachineManagementModel(ListModel):
## Handler for container added/removed events from registry ## Handler for container added/removed events from registry
def _onContainerChanged(self, container): def _onContainerChanged(self, container):
# We only need to update when the added / removed container is a stack. # We only need to update when the added / removed container is a stack.
if isinstance(container, ContainerStack): if isinstance(container, ContainerStack) and container.getMetaDataEntry("type") == "machine":
self._update() self._update()
## Private convenience function to reset & repopulate the model. ## Private convenience function to reset & repopulate the model.
@ -47,7 +48,9 @@ class MachineManagementModel(ListModel):
items = [] items = []
# Get first the network enabled printers # Get first the network enabled printers
network_filter_printers = {"type": "machine", "um_network_key": "*", "hidden": "False"} network_filter_printers = {"type": "machine",
"um_network_key": "*",
"hidden": "False"}
self._network_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**network_filter_printers) self._network_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**network_filter_printers)
self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("connect_group_name")) self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("connect_group_name"))
@ -61,7 +64,7 @@ class MachineManagementModel(ListModel):
"metadata": metadata, "metadata": metadata,
"group": catalog.i18nc("@info:title", "Network enabled printers")}) "group": catalog.i18nc("@info:title", "Network enabled printers")})
# Get now the local printes # Get now the local printers
local_filter_printers = {"type": "machine", "um_network_key": None} local_filter_printers = {"type": "machine", "um_network_key": None}
self._local_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**local_filter_printers) self._local_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**local_filter_printers)
self._local_container_stacks.sort(key = lambda i: i.getName()) self._local_container_stacks.sort(key = lambda i: i.getName())

View File

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty from PyQt5.QtCore import QTimer, pyqtSignal, pyqtProperty
from UM.Application import Application from UM.Application import Application
from UM.Scene.Selection import Selection from UM.Scene.Selection import Selection
@ -21,8 +21,13 @@ class MultiBuildPlateModel(ListModel):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
self._update_timer = QTimer()
self._update_timer.setInterval(100)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._updateSelectedObjectBuildPlateNumbers)
self._application = Application.getInstance() self._application = Application.getInstance()
self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers) self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbersDelayed)
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers) Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
self._max_build_plate = 1 # default self._max_build_plate = 1 # default
@ -45,6 +50,9 @@ class MultiBuildPlateModel(ListModel):
def activeBuildPlate(self): def activeBuildPlate(self):
return self._active_build_plate return self._active_build_plate
def _updateSelectedObjectBuildPlateNumbersDelayed(self, *args):
self._update_timer.start()
def _updateSelectedObjectBuildPlateNumbers(self, *args): def _updateSelectedObjectBuildPlateNumbers(self, *args):
result = set() result = set()
for node in Selection.getAllSelectedObjects(): for node in Selection.getAllSelectedObjects():

View File

@ -87,9 +87,11 @@ class QualitySettingsModel(ListModel):
if self._selected_position == self.GLOBAL_STACK_POSITION: if self._selected_position == self.GLOBAL_STACK_POSITION:
quality_node = quality_group.node_for_global quality_node = quality_group.node_for_global
else: else:
quality_node = quality_group.nodes_for_extruders.get(self._selected_position) quality_node = quality_group.nodes_for_extruders.get(str(self._selected_position))
settings_keys = quality_group.getAllKeys() settings_keys = quality_group.getAllKeys()
quality_containers = [quality_node.getContainer()] quality_containers = []
if quality_node is not None:
quality_containers.append(quality_node.getContainer())
# Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch # Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch
# the settings in that quality_changes_group. # the settings in that quality_changes_group.
@ -97,7 +99,7 @@ class QualitySettingsModel(ListModel):
if self._selected_position == self.GLOBAL_STACK_POSITION: if self._selected_position == self.GLOBAL_STACK_POSITION:
quality_changes_node = quality_changes_group.node_for_global quality_changes_node = quality_changes_group.node_for_global
else: else:
quality_changes_node = quality_changes_group.nodes_for_extruders.get(self._selected_position) quality_changes_node = quality_changes_group.nodes_for_extruders.get(str(self._selected_position))
if quality_changes_node is not None: # it can be None if number of extruders are changed during runtime if quality_changes_node is not None: # it can be None if number of extruders are changed during runtime
try: try:
quality_containers.insert(0, quality_changes_node.getContainer()) quality_containers.insert(0, quality_changes_node.getContainer())

View File

@ -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()

View File

@ -16,6 +16,7 @@ from .QualityGroup import QualityGroup
from .QualityNode import QualityNode from .QualityNode import QualityNode
if TYPE_CHECKING: if TYPE_CHECKING:
from UM.Settings.DefinitionContainer import DefinitionContainer
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from .QualityChangesGroup import QualityChangesGroup from .QualityChangesGroup import QualityChangesGroup
@ -178,7 +179,7 @@ class QualityManager(QObject):
# Returns a dict of "custom profile name" -> QualityChangesGroup # Returns a dict of "custom profile name" -> QualityChangesGroup
def getQualityChangesGroups(self, machine: "GlobalStack") -> dict: def getQualityChangesGroups(self, machine: "GlobalStack") -> dict:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine) machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id) machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id)
if not machine_node: if not machine_node:
@ -206,7 +207,7 @@ class QualityManager(QObject):
# For more details, see QualityGroup. # For more details, see QualityGroup.
# #
def getQualityGroups(self, machine: "GlobalStack") -> dict: def getQualityGroups(self, machine: "GlobalStack") -> dict:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine) machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
# This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks # This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False)) has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False))
@ -315,7 +316,7 @@ class QualityManager(QObject):
return quality_group_dict return quality_group_dict
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict: def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine) machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
# To find the quality container for the GlobalStack, check in the following fall-back manner: # To find the quality container for the GlobalStack, check in the following fall-back manner:
# (1) the machine-specific node # (1) the machine-specific node
@ -460,7 +461,7 @@ class QualityManager(QObject):
quality_changes.addMetaDataEntry("position", extruder_stack.getMetaDataEntry("position")) quality_changes.addMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
# If the machine specifies qualities should be filtered, ensure we match the current criteria. # If the machine specifies qualities should be filtered, ensure we match the current criteria.
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine) machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
quality_changes.setDefinition(machine_definition_id) quality_changes.setDefinition(machine_definition_id)
quality_changes.addMetaDataEntry("setting_version", self._application.SettingVersion) quality_changes.addMetaDataEntry("setting_version", self._application.SettingVersion)
@ -480,12 +481,13 @@ class QualityManager(QObject):
# Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended # Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended
# shares the same set of qualities profiles as Ultimaker 3. # shares the same set of qualities profiles as Ultimaker 3.
# #
def getMachineDefinitionIDForQualitySearch(machine: "GlobalStack", default_definition_id: str = "fdmprinter") -> str: def getMachineDefinitionIDForQualitySearch(machine_definition: "DefinitionContainer",
default_definition_id: str = "fdmprinter") -> str:
machine_definition_id = default_definition_id machine_definition_id = default_definition_id
if parseBool(machine.getMetaDataEntry("has_machine_quality", False)): if parseBool(machine_definition.getMetaDataEntry("has_machine_quality", False)):
# Only use the machine's own quality definition ID if this machine has machine quality. # Only use the machine's own quality definition ID if this machine has machine quality.
machine_definition_id = machine.getMetaDataEntry("quality_definition") machine_definition_id = machine_definition.getMetaDataEntry("quality_definition")
if machine_definition_id is None: if machine_definition_id is None:
machine_definition_id = machine.definition.getId() machine_definition_id = machine_definition.getId()
return machine_definition_id return machine_definition_id

View File

@ -1,3 +1,8 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QTimer
from UM.Application import Application from UM.Application import Application
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@ -14,8 +19,13 @@ class ObjectsModel(ListModel):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
Application.getInstance().getController().getScene().sceneChanged.connect(self._update) Application.getInstance().getController().getScene().sceneChanged.connect(self._updateDelayed)
Preferences.getInstance().preferenceChanged.connect(self._update) Preferences.getInstance().preferenceChanged.connect(self._updateDelayed)
self._update_timer = QTimer()
self._update_timer.setInterval(100)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._update)
self._build_plate_number = -1 self._build_plate_number = -1
@ -23,6 +33,9 @@ class ObjectsModel(ListModel):
self._build_plate_number = nr self._build_plate_number = nr
self._update() self._update()
def _updateDelayed(self, *args):
self._update_timer.start()
def _update(self, *args): def _update(self, *args):
nodes = [] nodes = []
filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate") filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate")

View File

@ -3,6 +3,8 @@
from UM.Application import Application from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.CuraApplication import CuraApplication
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
@ -56,12 +58,15 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._connection_state_before_timeout = None # type: Optional[ConnectionState] self._connection_state_before_timeout = None # type: Optional[ConnectionState]
printer_type = self._properties.get(b"machine", b"").decode("utf-8") printer_type = self._properties.get(b"machine", b"").decode("utf-8")
if printer_type.startswith("9511"): printer_type_identifiers = {
self._printer_type = "ultimaker3_extended" "9066": "ultimaker3",
elif printer_type.startswith("9066"): "9511": "ultimaker3_extended"
self._printer_type = "ultimaker3" }
else: self._printer_type = "Unknown"
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: def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs) -> None:
raise NotImplementedError("requestWrite needs to be implemented") raise NotImplementedError("requestWrite needs to be implemented")
@ -251,6 +256,9 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._last_manager_create_time = time() self._last_manager_create_time = time()
self._manager.authenticationRequired.connect(self._onAuthenticationRequired) 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: def _registerOnFinishedCallback(self, reply: QNetworkReply, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None:
if onFinished is not None: if onFinished is not None:
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished

View File

@ -1,6 +1,8 @@
# Copyright (c) 2016 Ultimaker B.V. # Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QTimer
from UM.Application import Application from UM.Application import Application
from UM.Math.Polygon import Polygon from UM.Math.Polygon import Polygon
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
@ -22,6 +24,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._global_stack = None self._global_stack = None
# Make sure the timer is created on the main thread
self._recompute_convex_hull_timer = None
Application.getInstance().callLater(self.createRecomputeConvexHullTimer)
self._raft_thickness = 0.0 self._raft_thickness = 0.0
# For raft thickness, DRY # For raft thickness, DRY
self._build_volume = Application.getInstance().getBuildVolume() self._build_volume = Application.getInstance().getBuildVolume()
@ -33,6 +39,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._onGlobalStackChanged() self._onGlobalStackChanged()
def createRecomputeConvexHullTimer(self):
self._recompute_convex_hull_timer = QTimer()
self._recompute_convex_hull_timer.setInterval(200)
self._recompute_convex_hull_timer.setSingleShot(True)
self._recompute_convex_hull_timer.timeout.connect(self.recomputeConvexHull)
def setNode(self, node): def setNode(self, node):
previous_node = self._node previous_node = self._node
# Disconnect from previous node signals # Disconnect from previous node signals
@ -99,6 +111,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
return self._compute2DConvexHull() return self._compute2DConvexHull()
return None return None
def recomputeConvexHullDelayed(self):
if self._recompute_convex_hull_timer is not None:
self._recompute_convex_hull_timer.start()
else:
self.recomputeConvexHull()
def recomputeConvexHull(self): def recomputeConvexHull(self):
controller = Application.getInstance().getController() controller = Application.getInstance().getController()
root = controller.getScene().getRoot() root = controller.getScene().getRoot()
@ -279,7 +297,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
def _onChanged(self, *args): def _onChanged(self, *args):
self._raft_thickness = self._build_volume.getRaftThickness() self._raft_thickness = self._build_volume.getRaftThickness()
self.recomputeConvexHull() if not args or args[0] == self._node:
self.recomputeConvexHullDelayed()
def _onGlobalStackChanged(self): def _onGlobalStackChanged(self):
if self._global_stack: if self._global_stack:

View File

@ -173,12 +173,13 @@ class CuraContainerRegistry(ContainerRegistry):
plugin_registry = PluginRegistry.getInstance() plugin_registry = PluginRegistry.getInstance()
extension = file_name.split(".")[-1] extension = file_name.split(".")[-1]
global_container_stack = Application.getInstance().getGlobalContainerStack() global_stack = Application.getInstance().getGlobalContainerStack()
if not global_container_stack: if not global_stack:
return return
machine_extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId())) machine_extruders = []
machine_extruders.sort(key = lambda k: k.getMetaDataEntry("position")) for position in sorted(global_stack.extruders):
machine_extruders.append(global_stack.extruders[position])
for plugin_id, meta_data in self._getIOPlugins("profile_reader"): for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
if meta_data["profile_reader"][0]["extension"] != extension: if meta_data["profile_reader"][0]["extension"] != extension:
@ -200,28 +201,51 @@ class CuraContainerRegistry(ContainerRegistry):
# First check if this profile is suitable for this machine # First check if this profile is suitable for this machine
global_profile = None global_profile = None
extruder_profiles = []
if len(profile_or_list) == 1: if len(profile_or_list) == 1:
global_profile = profile_or_list[0] global_profile = profile_or_list[0]
else: else:
for profile in profile_or_list: for profile in profile_or_list:
if not profile.getMetaDataEntry("extruder"): if not profile.getMetaDataEntry("position"):
global_profile = profile 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: if not global_profile:
Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name) Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name)
return { "status": "error", return { "status": "error",
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)} "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)}
profile_definition = global_profile.getMetaDataEntry("definition") profile_definition = global_profile.getMetaDataEntry("definition")
expected_machine_definition = "fdmprinter"
if parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", "False")): # Make sure we have a profile_definition in the file:
expected_machine_definition = global_container_stack.getMetaDataEntry("quality_definition") if profile_definition is None:
if not expected_machine_definition: break
expected_machine_definition = global_container_stack.definition.getId() machine_definition = self.findDefinitionContainers(id = profile_definition)
if expected_machine_definition is not None and profile_definition is not None and profile_definition != expected_machine_definition: if not machine_definition:
Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition)
return {"status": "error",
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)
}
machine_definition = machine_definition[0]
# 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_stack.definition)
# And check if the profile_definition matches either one (showing error if not):
if profile_definition != expected_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) 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", return { "status": "error",
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)} "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "The machine defined in profile <filename>{0}</filename> ({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] name_seed = os.path.splitext(os.path.basename(file_name))[0]
new_name = self.uniqueName(name_seed) new_name = self.uniqueName(name_seed)
@ -233,25 +257,25 @@ class CuraContainerRegistry(ContainerRegistry):
if len(profile_or_list) == 1: if len(profile_or_list) == 1:
global_profile = profile_or_list[0] global_profile = profile_or_list[0]
extruder_profiles = [] extruder_profiles = []
for idx, extruder in enumerate(global_container_stack.extruders.values()): for idx, extruder in enumerate(global_stack.extruders.values()):
profile_id = ContainerRegistry.getInstance().uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1)) profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1))
profile = InstanceContainer(profile_id) profile = InstanceContainer(profile_id)
profile.setName(global_profile.getName()) profile.setName(quality_name)
profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
profile.addMetaDataEntry("type", "quality_changes") profile.addMetaDataEntry("type", "quality_changes")
profile.addMetaDataEntry("definition", global_profile.getMetaDataEntry("definition")) profile.addMetaDataEntry("definition", expected_machine_definition)
profile.addMetaDataEntry("quality_type", global_profile.getMetaDataEntry("quality_type")) profile.addMetaDataEntry("quality_type", quality_type)
profile.addMetaDataEntry("position", "0") profile.addMetaDataEntry("position", "0")
profile.setDirty(True) profile.setDirty(True)
if idx == 0: if idx == 0:
# move all per-extruder settings to the first extruder's quality_changes # move all per-extruder settings to the first extruder's quality_changes
for qc_setting_key in global_profile.getAllKeys(): 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") "settable_per_extruder")
if settable_per_extruder: if settable_per_extruder:
setting_value = global_profile.getProperty(qc_setting_key, "value") 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 = SettingInstance(setting_definition, profile)
new_instance.setProperty("value", setting_value) new_instance.setProperty("value", setting_value)
new_instance.resetState() # Ensure that the state is not seen as a user state. new_instance.resetState() # Ensure that the state is not seen as a user state.
@ -268,7 +292,7 @@ class CuraContainerRegistry(ContainerRegistry):
for profile_index, profile in enumerate(profile_or_list): for profile_index, profile in enumerate(profile_or_list):
if profile_index == 0: if profile_index == 0:
# This is assumed to be the global profile # 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: elif profile_index < len(machine_extruders) + 1:
# This is assumed to be an extruder profile # This is assumed to be an extruder profile
@ -283,7 +307,7 @@ class CuraContainerRegistry(ContainerRegistry):
else: #More extruders in the imported file than in the machine. else: #More extruders in the imported file than in the machine.
continue #Delete the additional profiles. continue #Delete the additional profiles.
result = self._configureProfile(profile, profile_id, new_name) result = self._configureProfile(profile, profile_id, new_name, expected_machine_definition)
if result is not None: if result is not None:
return {"status": "error", "message": catalog.i18nc( return {"status": "error", "message": catalog.i18nc(
"@info:status Don't translate the XML tags <filename> or <message>!", "@info:status Don't translate the XML tags <filename> or <message>!",
@ -311,7 +335,7 @@ class CuraContainerRegistry(ContainerRegistry):
# \param new_name The new name for the profile. # \param new_name The new name for the profile.
# #
# \return None if configuring was successful or an error message if an error occurred. # \return None if configuring was successful or an error message if an error occurred.
def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]: def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str, machine_definition_id: str) -> Optional[str]:
profile.setDirty(True) # Ensure the profiles are correctly saved profile.setDirty(True) # Ensure the profiles are correctly saved
new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile")) new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
@ -321,6 +345,7 @@ class CuraContainerRegistry(ContainerRegistry):
# Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile # Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile
# It also solves an issue with importing profiles from G-Codes # It also solves an issue with importing profiles from G-Codes
profile.setMetaDataEntry("id", new_id) profile.setMetaDataEntry("id", new_id)
profile.setMetaDataEntry("definition", machine_definition_id)
if "type" in profile.getMetaData(): if "type" in profile.getMetaData():
profile.setMetaDataEntry("type", "quality_changes") profile.setMetaDataEntry("type", "quality_changes")
@ -331,9 +356,8 @@ class CuraContainerRegistry(ContainerRegistry):
if not quality_type: if not quality_type:
return catalog.i18nc("@info:status", "Profile is missing a quality type.") return catalog.i18nc("@info:status", "Profile is missing a quality type.")
quality_type_criteria = {"quality_type": quality_type}
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = Application.getInstance().getGlobalContainerStack()
definition_id = getMachineDefinitionIDForQualitySearch(global_stack) definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition)
profile.setDefinition(definition_id) profile.setDefinition(definition_id)
# Check to make sure the imported profile actually makes sense in context of the current configuration. # Check to make sure the imported profile actually makes sense in context of the current configuration.

View File

@ -210,6 +210,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
item = { item = {
"id": "", "id": "",
"name": catalog.i18nc("@menuitem", "Not overridden"), "name": catalog.i18nc("@menuitem", "Not overridden"),
"enabled": True,
"color": "#ffffff", "color": "#ffffff",
"index": -1, "index": -1,
"definition": "" "definition": ""

View File

@ -10,7 +10,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Signal import Signal from UM.Signal import Signal
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
import UM.FlameProfiler
from UM.FlameProfiler import pyqtSlot from UM.FlameProfiler import pyqtSlot
from UM import Util from UM import Util
@ -24,7 +23,6 @@ from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique from UM.Signal import postponeSignals, CompressTechnique
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.Machines.VariantManager import VariantType
from cura.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutputDevice import PrinterOutputDevice
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel 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. activeStackValueChanged = pyqtSignal() # Emitted whenever a value inside the active stack is changed.
activeStackValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed activeStackValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed
stacksValidationChanged = pyqtSignal() # Emitted whenever a validation 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 blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
@ -467,16 +466,16 @@ class MachineManager(QObject):
return self._global_container_stack.getId() return self._global_container_stack.getId()
return "" return ""
@pyqtProperty(str, notify = globalContainerChanged) @pyqtProperty(str, notify = outputDevicesChanged)
def activeMachineNetworkKey(self) -> str: def activeMachineNetworkKey(self) -> str:
if self._global_container_stack: if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("um_network_key") return self._global_container_stack.getMetaDataEntry("um_network_key", "")
return "" return ""
@pyqtProperty(str, notify = globalContainerChanged) @pyqtProperty(str, notify = outputDevicesChanged)
def activeMachineNetworkGroupName(self) -> str: def activeMachineNetworkGroupName(self) -> str:
if self._global_container_stack: if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("connect_group_name") return self._global_container_stack.getMetaDataEntry("connect_group_name", "")
return "" return ""
@pyqtProperty(QObject, notify = globalContainerChanged) @pyqtProperty(QObject, notify = globalContainerChanged)
@ -628,7 +627,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = globalContainerChanged) @pyqtProperty(str, notify = globalContainerChanged)
def activeQualityDefinitionId(self) -> str: def activeQualityDefinitionId(self) -> str:
if self._global_container_stack: if self._global_container_stack:
return getMachineDefinitionIDForQualitySearch(self._global_container_stack) return getMachineDefinitionIDForQualitySearch(self._global_container_stack.definition)
return "" return ""
## Gets how the active definition calls variants ## Gets how the active definition calls variants
@ -662,12 +661,22 @@ class MachineManager(QObject):
if other_machine_stacks: if other_machine_stacks:
self.setActiveMachine(other_machine_stacks[0]["id"]) 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) ExtruderManager.getInstance().removeMachineExtruders(machine_id)
containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
for container in containers: for container in containers:
ContainerRegistry.getInstance().removeContainer(container["id"]) ContainerRegistry.getInstance().removeContainer(container["id"])
ContainerRegistry.getInstance().removeContainer(machine_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) @pyqtProperty(bool, notify = globalContainerChanged)
def hasMaterials(self) -> bool: def hasMaterials(self) -> bool:
if self._global_container_stack: if self._global_container_stack:
@ -872,7 +881,13 @@ class MachineManager(QObject):
for position, extruder in self._global_container_stack.extruders.items(): for position, extruder in self._global_container_stack.extruders.items():
if extruder.isEnabled: if extruder.isEnabled:
extruder_count += 1 extruder_count += 1
if self.numberExtrudersEnabled != extruder_count:
definition_changes_container.setProperty("extruders_enabled_count", "value", 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) @pyqtProperty(str, notify = extruderChanged)
def defaultExtruderPosition(self): def defaultExtruderPosition(self):
@ -882,7 +897,7 @@ class MachineManager(QObject):
@pyqtSlot() @pyqtSlot()
def forceUpdateAllSettings(self): def forceUpdateAllSettings(self):
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): 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 container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
for setting_key in container.getAllKeys(): for setting_key in container.getAllKeys():
container.propertiesChanged.emit(setting_key, property_names) container.propertiesChanged.emit(setting_key, property_names)
@ -1193,6 +1208,18 @@ class MachineManager(QObject):
if machine.getMetaDataEntry(key) == value: if machine.getMetaDataEntry(key) == value:
machine.setMetaDataEntry(key, new_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)
@pyqtSlot("QVariant") @pyqtSlot("QVariant")
def setGlobalVariant(self, container_node): def setGlobalVariant(self, container_node):
self.blurSettings.emit() self.blurSettings.emit()

View File

@ -1,7 +1,7 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # 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.FlameProfiler import pyqtSlot
from UM.Application import Application from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
@ -30,6 +30,11 @@ class SettingInheritanceManager(QObject):
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
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() settingsWithIntheritanceChanged = pyqtSignal()
## Get the keys of all children settings with an override. ## Get the keys of all children settings with an override.
@ -226,9 +231,7 @@ class SettingInheritanceManager(QObject):
self._onActiveExtruderChanged() self._onActiveExtruderChanged()
def _onContainersChanged(self, container): def _onContainersChanged(self, container):
# TODO: Multiple container changes in sequence now cause quite a few recalculations. self._update_timer.start()
# This isn't that big of an issue, but it could be in the future.
self._update()
@staticmethod @staticmethod
def createSettingInheritanceManager(engine=None, script_engine=None): def createSettingInheritanceManager(engine=None, script_engine=None):

View File

@ -66,7 +66,7 @@ class Snapshot:
size = max(bbox.width, bbox.height, bbox.depth * 0.5) size = max(bbox.width, bbox.height, bbox.depth * 0.5)
# Looking from this direction (x, y, z) in OGL coordinates # 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: if size > 0:
# determine the watch distance depending on the size # determine the watch distance depending on the size
looking_from_offset = looking_from_offset * size * 1.3 looking_from_offset = looking_from_offset * size * 1.3

View File

@ -122,7 +122,7 @@ class ThreeMFReader(MeshReader):
um_node.callDecoration("setActiveExtruder", default_stack.getId()) um_node.callDecoration("setActiveExtruder", default_stack.getId())
# Get the definition & set it # Get the definition & set it
definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack) definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack.definition)
um_node.callDecoration("getStack").getTop().setDefinition(definition_id) um_node.callDecoration("getStack").getTop().setDefinition(definition_id)
setting_container = um_node.callDecoration("getStack").getTop() setting_container = um_node.callDecoration("getStack").getTop()

View File

@ -594,7 +594,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged") Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged")
else: else:
global_preferences.setValue("general/visible_settings", visible_settings) 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") categories_expanded = temp_preferences.getValue("cura/categories_expanded")
if categories_expanded is None: if categories_expanded is None:
@ -719,7 +719,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Get the correct extruder definition IDs for quality changes # Get the correct extruder definition IDs for quality changes
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(global_stack) machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(global_stack.definition)
machine_definition_for_quality = self._container_registry.findDefinitionContainers(id = machine_definition_id_for_quality)[0] machine_definition_for_quality = self._container_registry.findDefinitionContainers(id = machine_definition_id_for_quality)[0]
quality_changes_info = self._machine_info.quality_changes_info quality_changes_info = self._machine_info.quality_changes_info

View File

@ -158,6 +158,7 @@ class SimulationView(View):
return self._nozzle_node return self._nozzle_node
def _onSceneChanged(self, node): def _onSceneChanged(self, node):
if node.getMeshData() is not None:
self.setActivity(False) self.setActivity(False)
self.calculateMaxLayers() self.calculateMaxLayers()
self.calculateMaxPathsOnLayer(self._current_layer_num) self.calculateMaxPathsOnLayer(self._current_layer_num)

View File

@ -22,7 +22,7 @@ from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
from time import time from time import time
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional, Dict, List
import json import json
import os import os
@ -79,7 +79,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._latest_reply_handler = None self._latest_reply_handler = None
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs): def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
self.writeStarted.emit(self) self.writeStarted.emit(self)
@ -116,7 +115,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
@pyqtSlot() @pyqtSlot()
@pyqtSlot(str) @pyqtSlot(str)
def sendPrintJob(self, target_printer = ""): def sendPrintJob(self, target_printer: str = ""):
Logger.log("i", "Sending print job to printer.") Logger.log("i", "Sending print job to printer.")
if self._sending_gcode: if self._sending_gcode:
self._error_message = Message( self._error_message = Message(
@ -157,11 +156,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return True return True
@pyqtProperty(QObject, notify=activePrinterChanged) @pyqtProperty(QObject, notify=activePrinterChanged)
def activePrinter(self) -> Optional["PrinterOutputModel"]: def activePrinter(self) -> Optional[PrinterOutputModel]:
return self._active_printer return self._active_printer
@pyqtSlot(QObject) @pyqtSlot(QObject)
def setActivePrinter(self, printer): def setActivePrinter(self, printer: Optional[PrinterOutputModel]):
if self._active_printer != printer: if self._active_printer != printer:
if self._active_printer and self._active_printer.camera: if self._active_printer and self._active_printer.camera:
self._active_printer.camera.stop() self._active_printer.camera.stop()
@ -173,7 +172,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._compressing_gcode = False self._compressing_gcode = False
self._sending_gcode = False self._sending_gcode = False
def _onUploadPrintJobProgress(self, bytes_sent, bytes_total): def _onUploadPrintJobProgress(self, bytes_sent:int, bytes_total:int):
if bytes_total > 0: if bytes_total > 0:
new_progress = bytes_sent / bytes_total * 100 new_progress = bytes_sent / bytes_total * 100
# Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
@ -186,7 +185,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._progress_message.setProgress(0) self._progress_message.setProgress(0)
self._progress_message.hide() self._progress_message.hide()
def _progressMessageActionTriggered(self, message_id=None, action_id=None): def _progressMessageActionTriggered(self, message_id: Optional[str]=None, action_id: Optional[str]=None) -> None:
if action_id == "Abort": if action_id == "Abort":
Logger.log("d", "User aborted sending print to remote.") Logger.log("d", "User aborted sending print to remote.")
self._progress_message.hide() self._progress_message.hide()
@ -202,29 +201,29 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
@pyqtSlot() @pyqtSlot()
def openPrintJobControlPanel(self): def openPrintJobControlPanel(self) -> None:
Logger.log("d", "Opening print job control panel...") Logger.log("d", "Opening print job control panel...")
QDesktopServices.openUrl(QUrl("http://" + self._address + "/print_jobs")) QDesktopServices.openUrl(QUrl("http://" + self._address + "/print_jobs"))
@pyqtSlot() @pyqtSlot()
def openPrinterControlPanel(self): def openPrinterControlPanel(self) -> None:
Logger.log("d", "Opening printer control panel...") Logger.log("d", "Opening printer control panel...")
QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers")) QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers"))
@pyqtProperty("QVariantList", notify=printJobsChanged) @pyqtProperty("QVariantList", notify=printJobsChanged)
def printJobs(self): def printJobs(self)-> List[PrintJobOutputModel] :
return self._print_jobs return self._print_jobs
@pyqtProperty("QVariantList", notify=printJobsChanged) @pyqtProperty("QVariantList", notify=printJobsChanged)
def queuedPrintJobs(self): def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is None or print_job.state == "queued"] return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is None or print_job.state == "queued"]
@pyqtProperty("QVariantList", notify=printJobsChanged) @pyqtProperty("QVariantList", notify=printJobsChanged)
def activePrintJobs(self): def activePrintJobs(self) -> List[PrintJobOutputModel]:
return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"] return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"]
@pyqtProperty("QVariantList", notify=clusterPrintersChanged) @pyqtProperty("QVariantList", notify=clusterPrintersChanged)
def connectedPrintersTypeCount(self): def connectedPrintersTypeCount(self) -> List[PrinterOutputModel]:
printer_count = {} printer_count = {}
for printer in self._printers: for printer in self._printers:
if printer.type in printer_count: if printer.type in printer_count:
@ -237,22 +236,22 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return result return result
@pyqtSlot(int, result=str) @pyqtSlot(int, result=str)
def formatDuration(self, seconds): def formatDuration(self, seconds: int) -> str:
return Duration(seconds).getDisplayString(DurationFormat.Format.Short) return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
@pyqtSlot(int, result=str) @pyqtSlot(int, result=str)
def getTimeCompleted(self, time_remaining): def getTimeCompleted(self, time_remaining: int) -> str:
current_time = time() current_time = time()
datetime_completed = datetime.fromtimestamp(current_time + time_remaining) datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute) return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute)
@pyqtSlot(int, result=str) @pyqtSlot(int, result=str)
def getDateCompleted(self, time_remaining): def getDateCompleted(self, time_remaining: int) -> str:
current_time = time() current_time = time()
datetime_completed = datetime.fromtimestamp(current_time + time_remaining) datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper() return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
def _printJobStateChanged(self): def _printJobStateChanged(self) -> None:
username = self._getUserName() username = self._getUserName()
if username is None: if username is None:
@ -275,13 +274,13 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# Keep a list of all completed jobs so we know if something changed next time. # Keep a list of all completed jobs so we know if something changed next time.
self._finished_jobs = finished_jobs self._finished_jobs = finished_jobs
def _update(self): def _update(self) -> None:
if not super()._update(): if not super()._update():
return return
self.get("printers/", onFinished=self._onGetPrintersDataFinished) self.get("printers/", onFinished=self._onGetPrintersDataFinished)
self.get("print_jobs/", onFinished=self._onGetPrintJobsFinished) self.get("print_jobs/", onFinished=self._onGetPrintJobsFinished)
def _onGetPrintJobsFinished(self, reply: QNetworkReply): def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
if not checkValidGetReply(reply): if not checkValidGetReply(reply):
return return
@ -323,7 +322,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if job_list_changed: if job_list_changed:
self.printJobsChanged.emit() # Do a single emit for all print job changes. self.printJobsChanged.emit() # Do a single emit for all print job changes.
def _onGetPrintersDataFinished(self, reply: QNetworkReply): def _onGetPrintersDataFinished(self, reply: QNetworkReply) -> None:
if not checkValidGetReply(reply): if not checkValidGetReply(reply):
return return
@ -352,31 +351,37 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if removed_printers or printer_list_changed: if removed_printers or printer_list_changed:
self.printersChanged.emit() self.printersChanged.emit()
def _createPrinterModel(self, data): def _createPrinterModel(self, data: Dict) -> PrinterOutputModel:
printer = PrinterOutputModel(output_controller=ClusterUM3PrinterOutputController(self), printer = PrinterOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
number_of_extruders=self._number_of_extruders) number_of_extruders=self._number_of_extruders)
printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream")) printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream"))
self._printers.append(printer) self._printers.append(printer)
return printer return printer
def _createPrintJobModel(self, data): def _createPrintJobModel(self, data: Dict) -> PrintJobOutputModel:
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self), print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
key=data["uuid"], name= data["name"]) key=data["uuid"], name= data["name"])
print_job.stateChanged.connect(self._printJobStateChanged) print_job.stateChanged.connect(self._printJobStateChanged)
self._print_jobs.append(print_job) self._print_jobs.append(print_job)
return print_job return print_job
def _updatePrintJob(self, print_job, data): def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict) -> None:
print_job.updateTimeTotal(data["time_total"]) print_job.updateTimeTotal(data["time_total"])
print_job.updateTimeElapsed(data["time_elapsed"]) print_job.updateTimeElapsed(data["time_elapsed"])
print_job.updateState(data["status"]) print_job.updateState(data["status"])
print_job.updateOwner(data["owner"]) print_job.updateOwner(data["owner"])
def _updatePrinter(self, printer, data): def _updatePrinter(self, printer: PrinterOutputModel, data: Dict) -> None:
# For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer. # For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer.
# Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping. # Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping.
self._printer_uuid_to_unique_name_mapping[data["uuid"]] = data["unique_name"] self._printer_uuid_to_unique_name_mapping[data["uuid"]] = data["unique_name"]
machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(name = data["machine_variant"])[0]
definitions = ContainerRegistry.getInstance().findDefinitionContainers(name = data["machine_variant"])
if not definitions:
Logger.log("w", "Unable to find definition for machine variant %s", data["machine_variant"])
return
machine_definition = definitions[0]
printer.updateName(data["friendly_name"]) printer.updateName(data["friendly_name"])
printer.updateKey(data["uuid"]) printer.updateKey(data["uuid"])
@ -421,7 +426,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
brand=brand, color=color, name=name) brand=brand, color=color, name=name)
extruder.updateActiveMaterial(material) extruder.updateActiveMaterial(material)
def _removeJob(self, job): def _removeJob(self, job: PrintJobOutputModel):
if job not in self._print_jobs: if job not in self._print_jobs:
return False return False
@ -432,7 +437,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return True return True
def _removePrinter(self, printer): def _removePrinter(self, printer: PrinterOutputModel):
self._printers.remove(printer) self._printers.remove(printer)
if self._active_printer == printer: if self._active_printer == printer:
self._active_printer = None self._active_printer = None

View File

@ -82,6 +82,9 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._zero_conf_browser.cancel() self._zero_conf_browser.cancel()
self._zero_conf_browser = None # Force the old ServiceBrowser to be destroyed. 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 = Zeroconf()
self._zero_conf_browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', self._zero_conf_browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.',
[self._appendServiceChangedRequest]) [self._appendServiceChangedRequest])

View File

@ -22,6 +22,7 @@ class AutoDetectBaudJob(Job):
def run(self): def run(self):
Logger.log("d", "Auto detect baud rate started.") Logger.log("d", "Auto detect baud rate started.")
timeout = 3 timeout = 3
tries = 2
programmer = Stk500v2() programmer = Stk500v2()
serial = None serial = None
@ -31,6 +32,7 @@ class AutoDetectBaudJob(Job):
except: except:
programmer.close() programmer.close()
for retry in range(tries):
for baud_rate in self._all_baud_rates: 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)) Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate))
@ -63,4 +65,5 @@ class AutoDetectBaudJob(Job):
return return
serial.write(b"M105\n") 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. self.setResult(None) # Unable to detect the correct baudrate.

View File

@ -116,7 +116,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
@pyqtSlot(str) @pyqtSlot(str)
def updateFirmware(self, file): def updateFirmware(self, file):
self._firmware_location = file # the file path is qurl encoded.
self._firmware_location = file.replace("file://", "")
self.showFirmwareInterface() self.showFirmwareInterface()
self.setFirmwareUpdateState(FirmwareUpdateState.updating) self.setFirmwareUpdateState(FirmwareUpdateState.updating)
self._update_firmware_thread.start() self._update_firmware_thread.start()
@ -126,9 +127,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if self._connection_state != ConnectionState.closed: if self._connection_state != ConnectionState.closed:
self.close() self.close()
try:
hex_file = intelHex.readHex(self._firmware_location) hex_file = intelHex.readHex(self._firmware_location)
if len(hex_file) == 0: assert len(hex_file) > 0
Logger.log("e", "Unable to read provided hex file. Could not update firmware") except (FileNotFoundError, AssertionError):
Logger.log("e", "Unable to read provided hex file. Could not update firmware.")
self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error) self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error)
return return
@ -198,7 +201,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
# Reset line number. If this is not done, first line is sometimes ignored # Reset line number. If this is not done, first line is sometimes ignored
self._gcode.insert(0, "M110") self._gcode.insert(0, "M110")
self._gcode_position = 0 self._gcode_position = 0
self._is_printing = True
self._print_start_time = time() self._print_start_time = time()
self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds)) self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))
@ -206,6 +208,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
for i in range(0, 4): # Push first 4 entries before accepting other inputs for i in range(0, 4): # Push first 4 entries before accepting other inputs
self._sendNextGcodeLine() self._sendNextGcodeLine()
self._is_printing = True
self.writeFinished.emit(self) self.writeFinished.emit(self)
def _autoDetectFinished(self, job: AutoDetectBaudJob): def _autoDetectFinished(self, job: AutoDetectBaudJob):
@ -267,7 +270,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if not command.endswith(b"\n"): if not command.endswith(b"\n"):
command += b"\n" command += b"\n"
try: try:
self._serial.write(b"\n")
self._serial.write(command) self._serial.write(command)
except SerialTimeoutException: except SerialTimeoutException:
Logger.log("w", "Timeout when sending command to printer via USB.") Logger.log("w", "Timeout when sending command to printer via USB.")
@ -284,7 +286,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self.sendCommand("M105") self.sendCommand("M105")
self._last_temperature_request = time() 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) extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
# Update all temperature values # Update all temperature values
for match, extruder in zip(extruder_temperature_matches, self._printers[0].extruders): for match, extruder in zip(extruder_temperature_matches, self._printers[0].extruders):
@ -302,6 +304,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._printers[0].updateTargetBedTemperature(float(match[1])) self._printers[0].updateTargetBedTemperature(float(match[1]))
if self._is_printing: 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 b"ok" in line:
if not self._command_queue.empty(): if not self._command_queue.empty():
self._sendCommand(self._command_queue.get()) self._sendCommand(self._command_queue.get())

View File

@ -56,6 +56,8 @@ _EXTRUDER_TO_POSITION = {
## Upgrades configurations from the state they were in at version 3.2 to the ## Upgrades configurations from the state they were in at version 3.2 to the
# state they should be in at version 3.3. # state they should be in at version 3.3.
class VersionUpgrade32to33(VersionUpgrade): class VersionUpgrade32to33(VersionUpgrade):
temporary_group_name_counter = 1
## Gets the version number from a CFG file in Uranium's 3.2 format. ## Gets the version number from a CFG file in Uranium's 3.2 format.
# #
# Since the format may change, this is implemented for the 3.2 format only # Since the format may change, this is implemented for the 3.2 format only
@ -74,6 +76,28 @@ class VersionUpgrade32to33(VersionUpgrade):
setting_version = int(parser.get("metadata", "setting_version", fallback = 0)) setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
return format_version * 1000000 + setting_version return format_version * 1000000 + setting_version
## Upgrades a container stack from version 3.2 to 3.3.
#
# \param serialised The serialised form of a container stack.
# \param filename The name of the file to upgrade.
def upgradeStack(self, serialized, filename):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
if "metadata" in parser and "um_network_key" in parser["metadata"]:
if "hidden" not in parser["metadata"]:
parser["metadata"]["hidden"] = "False"
if "connect_group_name" not in parser["metadata"]:
parser["metadata"]["connect_group_name"] = "Temporary group name #" + str(self.temporary_group_name_counter)
self.temporary_group_name_counter += 1
#Update version number.
parser["general"]["version"] = "4"
result = io.StringIO()
parser.write(result)
return [filename], [result.getvalue()]
## Upgrades non-quality-changes instance containers to have the new version ## Upgrades non-quality-changes instance containers to have the new version
# number. # number.
def upgradeInstanceContainer(self, serialized, filename): def upgradeInstanceContainer(self, serialized, filename):

View File

@ -9,11 +9,22 @@ def getMetaData():
return { return {
"version_upgrade": { "version_upgrade": {
# From To Upgrade function # From To Upgrade function
("machine_stack", 3000004): ("machine_stack", 4000004, upgrade.upgradeStack),
("extruder_train", 3000004): ("extruder_train", 4000004, upgrade.upgradeStack),
("definition_changes", 2000004): ("definition_changes", 3000004, upgrade.upgradeInstanceContainer), ("definition_changes", 2000004): ("definition_changes", 3000004, upgrade.upgradeInstanceContainer),
("quality_changes", 2000004): ("quality_changes", 3000004, upgrade.upgradeQualityChanges), ("quality_changes", 2000004): ("quality_changes", 3000004, upgrade.upgradeQualityChanges),
("user", 2000004): ("user", 3000004, upgrade.upgradeInstanceContainer) ("user", 2000004): ("user", 3000004, upgrade.upgradeInstanceContainer)
}, },
"sources": { "sources": {
"machine_stack": {
"get_version": upgrade.getCfgVersion,
"location": {"./machine_instances"}
},
"extruder_train": {
"get_version": upgrade.getCfgVersion,
"location": {"./extruders"}
},
"definition_changes": { "definition_changes": {
"get_version": upgrade.getCfgVersion, "get_version": upgrade.getCfgVersion,
"location": {"./definition_changes"} "location": {"./definition_changes"}

View File

@ -208,14 +208,9 @@ class XmlMaterialProfile(InstanceContainer):
machine_variant_map = {} machine_variant_map = {}
variant_manager = CuraApplication.getInstance().getVariantManager() 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. 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 = registry.findInstanceContainers(base_file = root_material_id)
all_containers = []
for node in [material_group.root_material_node] + material_group.derived_material_node_list:
all_containers.append(node.getContainer())
for container in all_containers: for container in all_containers:
definition_id = container.getMetaDataEntry("definition") definition_id = container.getMetaDataEntry("definition")
@ -242,7 +237,7 @@ class XmlMaterialProfile(InstanceContainer):
for definition_id, container in machine_container_map.items(): for definition_id, container in machine_container_map.items():
definition_id = container.getMetaDataEntry("definition") 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 product = definition_id
for product_name, product_id_list in product_id_map.items(): for product_name, product_id_list in product_id_map.items():

View File

@ -632,6 +632,73 @@
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": false "settable_per_meshgroup": false
}, },
"machine_steps_per_mm_x":
{
"label": "Steps per Millimeter (X)",
"description": "How many steps of the stepper motor will result in one millimeter of movement in the X direction.",
"type": "int",
"default_value": 50,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_steps_per_mm_y":
{
"label": "Steps per Millimeter (Y)",
"description": "How many steps of the stepper motor will result in one millimeter of movement in the Y direction.",
"type": "int",
"default_value": 50,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_steps_per_mm_z":
{
"label": "Steps per Millimeter (Z)",
"description": "How many steps of the stepper motor will result in one millimeter of movement in the Z direction.",
"type": "int",
"default_value": 50,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_steps_per_mm_e":
{
"label": "Steps per Millimeter (E)",
"description": "How many steps of the stepper motors will result in one millimeter of extrusion.",
"type": "int",
"default_value": 1600,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_endstop_positive_direction_x":
{
"label": "X Endstop in Positive Direction",
"description": "Whether the endstop of the X axis is in the positive direction (high X coordinate) or negative (low X coordinate).",
"type": "bool",
"default_value": false,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_endstop_positive_direction_y":
{
"label": "Y Endstop in Positive Direction",
"description": "Whether the endstop of the Y axis is in the positive direction (high Y coordinate) or negative (low Y coordinate).",
"type": "bool",
"default_value": false,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_endstop_positive_direction_z":
{
"label": "Z Endstop in Positive Direction",
"description": "Whether the endstop of the Z axis is in the positive direction (high Z coordinate) or negative (low Z coordinate).",
"type": "bool",
"default_value": true,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_minimum_feedrate": "machine_minimum_feedrate":
{ {
"label": "Minimum Feedrate", "label": "Minimum Feedrate",
@ -642,6 +709,16 @@
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": false "settable_per_meshgroup": false
},
"machine_feeder_wheel_diameter":
{
"label": "Feeder Wheel Diameter",
"description": "The diameter of the wheel that drives the material in the feeder.",
"unit": "mm",
"type": "float",
"default_value": 10.0,
"settable_per_mesh": false,
"settable_per_extruder": true
} }
} }
}, },

View File

@ -25,8 +25,7 @@
"default_value": true "default_value": true
}, },
"machine_nozzle_size": { "machine_nozzle_size": {
"default_value": 0.4, "default_value": 0.4
"minimum_value": "0.001"
}, },
"machine_head_with_fans_polygon": { "machine_head_with_fans_polygon": {
"default_value": [ "default_value": [
@ -36,6 +35,21 @@
[ 18, 35 ] [ 18, 35 ]
] ]
}, },
"machine_max_feedrate_z": {
"default_value": 400
},
"machine_steps_per_mm_x": {
"default_value": 93
},
"machine_steps_per_mm_y": {
"default_value": 93
},
"machine_steps_per_mm_z": {
"default_value": 1600
},
"machine_steps_per_mm_e": {
"default_value": 92
},
"gantry_height": { "gantry_height": {
"default_value": 55 "default_value": 55
}, },

View File

@ -217,6 +217,7 @@ UM.MainWindow
text: catalog.i18nc("@action:inmenu", "Disable Extruder") text: catalog.i18nc("@action:inmenu", "Disable Extruder")
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false) onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false)
visible: Cura.MachineManager.getExtruder(model.index).isEnabled visible: Cura.MachineManager.getExtruder(model.index).isEnabled
enabled: Cura.MachineManager.numberExtrudersEnabled > 1
} }
} }
@ -653,9 +654,12 @@ UM.MainWindow
{ {
preferences.visible = true; preferences.visible = true;
preferences.setPage(1); preferences.setPage(1);
if(source && source.key)
{
preferences.getCurrentItem().scrollToSection(source.key); preferences.getCurrentItem().scrollToSection(source.key);
} }
} }
}
UM.ExtensionModel { UM.ExtensionModel {
id: curaExtensions id: curaExtensions

View File

@ -10,17 +10,22 @@ import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
import "Menus" import "Menus"
ToolButton { ToolButton
{
id: base id: base
property var isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != "" property bool isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != ""
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property var printerStatus: Cura.MachineManager.printerOutputDevices.length != 0 ? "connected" : "disconnected" property var printerStatus: Cura.MachineManager.printerOutputDevices.length != 0 ? "connected" : "disconnected"
text: isNetworkPrinter ? Cura.MachineManager.activeMachineNetworkGroupName : Cura.MachineManager.activeMachineName text: isNetworkPrinter ? Cura.MachineManager.activeMachineNetworkGroupName : Cura.MachineManager.activeMachineName
tooltip: Cura.MachineManager.activeMachineName tooltip: Cura.MachineManager.activeMachineName
style: ButtonStyle { style: ButtonStyle
background: Rectangle { {
color: { background: Rectangle
{
color:
{
if (control.pressed) { if (control.pressed) {
return UM.Theme.getColor("sidebar_header_active"); return UM.Theme.getColor("sidebar_header_active");
} }
@ -33,7 +38,8 @@ ToolButton {
} }
Behavior on color { ColorAnimation { duration: 50; } } Behavior on color { ColorAnimation { duration: 50; } }
UM.RecolorImage { UM.RecolorImage
{
id: downArrow id: downArrow
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right
@ -46,24 +52,27 @@ ToolButton {
source: UM.Theme.getIcon("arrow_bottom") source: UM.Theme.getIcon("arrow_bottom")
} }
PrinterStatusIcon { PrinterStatusIcon
{
id: printerStatusIcon id: printerStatusIcon
visible: isNetworkPrinter visible: printerConnected || isNetworkPrinter
status: printerStatus status: printerStatus
anchors { anchors
{
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
left: parent.left left: parent.left
leftMargin: UM.Theme.getSize("sidebar_margin").width leftMargin: UM.Theme.getSize("sidebar_margin").width
} }
} }
Label { Label
{
id: sidebarComboBoxLabel id: sidebarComboBoxLabel
color: UM.Theme.getColor("sidebar_header_text_active") color: UM.Theme.getColor("sidebar_header_text_active")
text: control.text; text: control.text;
elide: Text.ElideRight; elide: Text.ElideRight;
anchors.left: isNetworkPrinter ? printerStatusIcon.right : parent.left; anchors.left: printerStatusIcon.visible ? printerStatusIcon.right : parent.left;
anchors.leftMargin: isNetworkPrinter ? UM.Theme.getSize("sidebar_lining").width : UM.Theme.getSize("sidebar_margin").width anchors.leftMargin: printerStatusIcon.visible ? UM.Theme.getSize("sidebar_lining").width : UM.Theme.getSize("sidebar_margin").width
anchors.right: downArrow.left; anchors.right: downArrow.left;
anchors.rightMargin: control.rightMargin; anchors.rightMargin: control.rightMargin;
anchors.verticalCenter: parent.verticalCenter; anchors.verticalCenter: parent.verticalCenter;
@ -74,14 +83,4 @@ ToolButton {
} }
menu: PrinterMenu { } menu: PrinterMenu { }
// Make the toolbutton react when the outputdevice changes
Connections
{
target: Cura.MachineManager
onOutputDevicesChanged:
{
base.isNetworkPrinter = Cura.MachineManager.activeMachineNetworkKey != ""
}
}
} }

View File

@ -59,13 +59,14 @@ Column
section.criteria: ViewSection.FullString section.criteria: ViewSection.FullString
section.delegate: sectionHeading section.delegate: sectionHeading
model: (ouputDevice != null) ? outputDevice.uniqueConfigurations : [] model: (outputDevice != null) ? outputDevice.uniqueConfigurations : []
delegate: ConfigurationItem delegate: ConfigurationItem
{ {
width: parent.width - UM.Theme.getSize("default_margin").width width: parent.width - UM.Theme.getSize("default_margin").width
configuration: modelData configuration: modelData
onActivateConfiguration: onActivateConfiguration:
{ {
switchPopupState()
Cura.MachineManager.applyRemoteConfiguration(configuration) Cura.MachineManager.applyRemoteConfiguration(configuration)
} }
} }

View File

@ -13,54 +13,53 @@ Item
id: configurationSelector id: configurationSelector
property var connectedDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null property var connectedDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
property var panelWidth: control.width property var panelWidth: control.width
property var panelVisible: false
SyncButton { function switchPopupState()
onClicked: configurationSelector.state == "open" ? configurationSelector.state = "closed" : configurationSelector.state = "open" {
popup.visible ? popup.close() : popup.open()
}
SyncButton
{
id: syncButton
onClicked: switchPopupState()
outputDevice: connectedDevice outputDevice: connectedDevice
} }
Popup { Popup
{
// TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
id: popup id: popup
clip: true clip: true
closePolicy: Popup.CloseOnPressOutsideParent
y: configurationSelector.height - UM.Theme.getSize("default_lining").height y: configurationSelector.height - UM.Theme.getSize("default_lining").height
x: configurationSelector.width - width x: configurationSelector.width - width
width: panelWidth width: panelWidth
visible: panelVisible && connectedDevice != null visible: false
padding: UM.Theme.getSize("default_lining").width padding: UM.Theme.getSize("default_lining").width
contentItem: ConfigurationListView { transformOrigin: Popup.Top
contentItem: ConfigurationListView
{
id: configList id: configList
width: panelWidth - 2 * popup.padding width: panelWidth - 2 * popup.padding
outputDevice: connectedDevice outputDevice: connectedDevice
} }
background: Rectangle { background: Rectangle
{
color: UM.Theme.getColor("setting_control") color: UM.Theme.getColor("setting_control")
border.color: UM.Theme.getColor("setting_control_border") border.color: UM.Theme.getColor("setting_control_border")
} }
} exit: Transition
{
states: [
// This adds a second state to the container where the rectangle is farther to the right
State {
name: "open"
PropertyChanges {
target: popup
height: configList.computedHeight
}
},
State {
name: "closed"
PropertyChanges {
target: popup
height: 0
}
}
]
transitions: [
// This adds a transition that defaults to applying to all state changes
Transition {
// This applies a default NumberAnimation to any changes a state change makes to x or y properties // This applies a default NumberAnimation to any changes a state change makes to x or y properties
NumberAnimation { properties: "height"; duration: 200; easing.type: Easing.InOutQuad; } NumberAnimation { property: "visible"; duration: 75; }
}
enter: Transition
{
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
NumberAnimation { property: "visible"; duration: 75; }
}
onClosed: visible = false
onOpened: visible = true
} }
]
} }

View File

@ -17,11 +17,15 @@ Button
width: parent.width width: parent.width
height: parent.height height: parent.height
function updateOnSync() { function updateOnSync()
if (outputDevice != undefined) { {
for (var index in outputDevice.uniqueConfigurations) { if (outputDevice != undefined)
{
for (var index in outputDevice.uniqueConfigurations)
{
var configuration = outputDevice.uniqueConfigurations[index] var configuration = outputDevice.uniqueConfigurations[index]
if (Cura.MachineManager.matchesConfiguration(configuration)) { if (Cura.MachineManager.matchesConfiguration(configuration))
{
base.matched = true; base.matched = true;
return; return;
} }
@ -82,11 +86,6 @@ Button
label: Label {} label: Label {}
} }
onClicked:
{
panelVisible = !panelVisible
}
Connections { Connections {
target: outputDevice target: outputDevice
onUniqueConfigurationsChanged: { onUniqueConfigurationsChanged: {

View File

@ -31,7 +31,7 @@ Menu
MenuItem { MenuItem {
text: "%1: %2 - %3".arg(model.name).arg(model.material).arg(model.variant) text: "%1: %2 - %3".arg(model.name).arg(model.material).arg(model.variant)
visible: base.shouldShowExtruders visible: base.shouldShowExtruders
enabled: UM.Selection.hasSelection enabled: UM.Selection.hasSelection && model.enabled
checkable: true checkable: true
checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1 checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1
onTriggered: CuraActions.setExtruderForSelection(model.id) onTriggered: CuraActions.setExtruderForSelection(model.id)

View File

@ -0,0 +1,64 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 1.4
import UM 1.2 as UM
import Cura 1.0 as Cura
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()
Instantiator
{
model: settingVisibilityPresetsModel
MenuItem
{
text: model.name
checkable: true
checked: model.id == settingVisibilityPresetsModel.activePreset
exclusiveGroup: group
onTriggered:
{
settingVisibilityPresetsModel.setActivePreset(model.id);
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 }
}

View File

@ -13,6 +13,8 @@ UM.PreferencesPage
{ {
title: catalog.i18nc("@title:tab", "Setting Visibility"); title: catalog.i18nc("@title:tab", "Setting Visibility");
property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
property int scrollToIndex: 0 property int scrollToIndex: 0
signal scrollToSection( string key ) signal scrollToSection( string key )
@ -26,9 +28,8 @@ UM.PreferencesPage
UM.Preferences.resetPreference("general/visible_settings") UM.Preferences.resetPreference("general/visible_settings")
// After calling this function update Setting visibility preset combobox. // After calling this function update Setting visibility preset combobox.
// Reset should set "Basic" setting preset // Reset should set default setting preset ("Basic")
visibilityPreset.setBasicPreset() visibilityPreset.currentIndex = 1
} }
resetEnabled: true; resetEnabled: true;
@ -84,7 +85,7 @@ UM.PreferencesPage
if (visibilityPreset.currentIndex != visibilityPreset.model.count - 1) if (visibilityPreset.currentIndex != visibilityPreset.model.count - 1)
{ {
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,83 +111,33 @@ UM.PreferencesPage
ComboBox ComboBox
{ {
property int customOptionValue: 100
function setBasicPreset()
{
var index = 0
for(var i = 0; i < presetNamesList.count; ++i)
{
if(model.get(i).text == "Basic")
{
index = i;
break;
}
}
visibilityPreset.currentIndex = index
}
id: visibilityPreset id: visibilityPreset
width: 150 width: 150 * screenScaleFactor
anchors anchors
{ {
top: parent.top top: parent.top
right: parent.right right: parent.right
} }
model: ListModel model: settingVisibilityPresetsModel
{ textRole: "name"
id: presetNamesList
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;
}
sorted.sort();
for(var i = 0; i < sorted.length; i++) {
presetNamesList.append({text: itemsDict[sorted[i]], value: i});
}
// By agreement lets "Custom" option will have value 100
presetNamesList.append({text: "Custom", value: visibilityPreset.customOptionValue});
}
}
currentIndex: currentIndex:
{ {
// Load previously selected preset. // Load previously selected preset.
var text = UM.Preferences.getValue("general/preset_setting_visibility_choice"); var index = settingVisibilityPresetsModel.find("id", settingVisibilityPresetsModel.activePreset)
if (index == -1)
var index = 0;
for(var i = 0; i < presetNamesList.count; ++i)
{ {
if(model.get(i).text == text) return 0
{
index = i;
break;
} }
}
return index; return index
} }
onActivated: onActivated:
{ {
// TODO What to do if user is selected "Custom from Combobox" ? var preset_id = settingVisibilityPresetsModel.getItem(index).id;
if (model.get(index).text == "Custom"){ settingVisibilityPresetsModel.setActivePreset(preset_id);
UM.Preferences.setValue("general/preset_setting_visibility_choice", model.get(index).text)
return
}
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)
} }
} }
@ -216,7 +167,7 @@ UM.PreferencesPage
exclude: ["machine_settings", "command_line_settings"] exclude: ["machine_settings", "command_line_settings"]
showAncestors: true showAncestors: true
expanded: ["*"] expanded: ["*"]
visibilityHandler: UM.SettingPreferenceVisibilityHandler { } visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
} }
delegate: Loader delegate: Loader
@ -259,19 +210,7 @@ UM.PreferencesPage
{ {
id: settingVisibilityItem; id: settingVisibilityItem;
UM.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)
}
}
}
} }
} }
} }

View File

@ -215,7 +215,8 @@ SettingItem
{ {
text: model.name text: model.name
renderType: Text.NativeRendering renderType: Text.NativeRendering
color: { color:
{
if (model.enabled) { if (model.enabled) {
UM.Theme.getColor("setting_control_text") UM.Theme.getColor("setting_control_text")
} else { } else {

View File

@ -26,9 +26,20 @@ SettingItem
textRole: "name" textRole: "name"
onActivated: onActivated:
{
if (model.getItem(index).enabled)
{ {
forceActiveFocus(); forceActiveFocus();
propertyProvider.setPropertyValue("value", model.getItem(index).index); 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: onActiveFocusChanged:
@ -192,7 +203,14 @@ SettingItem
{ {
text: model.name text: model.name
renderType: Text.NativeRendering 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") font: UM.Theme.getFont("default")
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter

View File

@ -13,12 +13,18 @@ SettingItem
property string textBeforeEdit property string textBeforeEdit
property bool textHasChanged property bool textHasChanged
property bool focusGainedByClick: false
onFocusReceived: onFocusReceived:
{ {
textHasChanged = false; textHasChanged = false;
textBeforeEdit = focusItem.text; textBeforeEdit = focusItem.text;
if(!focusGainedByClick)
{
// select all text when tabbing through fields (but not when selecting a field with the mouse)
focusItem.selectAll(); focusItem.selectAll();
} }
}
contents: Rectangle contents: Rectangle
{ {
@ -93,14 +99,6 @@ SettingItem
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
} }
MouseArea
{
id: mouseArea
anchors.fill: parent;
//hoverEnabled: true;
cursorShape: Qt.IBeamCursor
}
TextInput TextInput
{ {
id: input id: input
@ -142,6 +140,7 @@ SettingItem
{ {
base.focusReceived(); base.focusReceived();
} }
base.focusGainedByClick = false;
} }
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
@ -178,6 +177,22 @@ SettingItem
} }
when: !input.activeFocus when: !input.activeFocus
} }
MouseArea
{
id: mouseArea
anchors.fill: parent;
cursorShape: Qt.IBeamCursor
onPressed: {
if(!input.activeFocus) {
base.focusGainedByClick = true;
input.forceActiveFocus();
}
mouse.accepted = false;
}
}
} }
} }
} }

View File

@ -15,10 +15,12 @@ Item
{ {
id: base; id: base;
property Action configureSettings; property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
property bool findingSettings; property Action configureSettings
signal showTooltip(Item item, point location, string text); property bool findingSettings
signal hideTooltip(); property bool showingAllSettings
signal showTooltip(Item item, point location, string text)
signal hideTooltip()
Item Item
{ {
@ -107,6 +109,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 Rectangle
{ {
id: filterContainer id: filterContainer
@ -132,9 +185,9 @@ Item
top: globalProfileRow.bottom top: globalProfileRow.bottom
topMargin: UM.Theme.getSize("sidebar_margin").height topMargin: UM.Theme.getSize("sidebar_margin").height
left: parent.left left: parent.left
leftMargin: Math.round(UM.Theme.getSize("sidebar_margin").width) leftMargin: UM.Theme.getSize("sidebar_margin").width
right: parent.right right: settingVisibilityMenu.left
rightMargin: Math.round(UM.Theme.getSize("sidebar_margin").width) rightMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
} }
height: visible ? UM.Theme.getSize("setting_control").height : 0 height: visible ? UM.Theme.getSize("setting_control").height : 0
Behavior on height { NumberAnimation { duration: 100 } } Behavior on height { NumberAnimation { duration: 100 } }
@ -168,17 +221,9 @@ Item
{ {
if(findingSettings) if(findingSettings)
{ {
expandedCategories = definitionsModel.expanded.slice(); showingAllSettings = false;
definitionsModel.expanded = ["*"];
definitionsModel.showAncestors = true;
definitionsModel.showAll = true;
}
else
{
definitionsModel.expanded = expandedCategories;
definitionsModel.showAncestors = false;
definitionsModel.showAll = false;
} }
updateDefinitionModel();
lastFindingSettings = findingSettings; lastFindingSettings = findingSettings;
} }
} }
@ -187,6 +232,27 @@ Item
{ {
filter.text = ""; 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 MouseArea
@ -209,7 +275,7 @@ Item
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right 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") color: UM.Theme.getColor("setting_control_button")
hoverColor: UM.Theme.getColor("setting_control_button_hover") hoverColor: UM.Theme.getColor("setting_control_button_hover")
@ -374,6 +440,7 @@ Item
key: model.key ? model.key : "" key: model.key ? model.key : ""
watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder", "resolve" ] watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder", "resolve" ]
storeIndex: 0 storeIndex: 0
removeUnusedValue: model.resolve == undefined
} }
Connections Connections
@ -491,9 +558,17 @@ Item
MenuItem MenuItem
{ {
//: Settings context menu action //: Settings context menu action
visible: !findingSettings; visible: !(findingSettings || showingAllSettings);
text: catalog.i18nc("@action:menu", "Hide this setting"); 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 (settingVisibilityPresetsModel.activePreset != "" && !showingAllSettings)
{
settingVisibilityPresetsModel.setActivePreset("custom");
}
}
} }
MenuItem MenuItem
{ {
@ -509,7 +584,7 @@ Item
return catalog.i18nc("@action:menu", "Keep this setting visible"); return catalog.i18nc("@action:menu", "Keep this setting visible");
} }
} }
visible: findingSettings; visible: (findingSettings || showingAllSettings);
onTriggered: onTriggered:
{ {
if (contextMenu.settingVisible) if (contextMenu.settingVisible)
@ -520,12 +595,17 @@ Item
{ {
definitionsModel.show(contextMenu.key); definitionsModel.show(contextMenu.key);
} }
// visible settings have changed, so we're no longer showing a preset
if (settingVisibilityPresetsModel.activePreset != "" && !showingAllSettings)
{
settingVisibilityPresetsModel.setActivePreset("custom");
}
} }
} }
MenuItem MenuItem
{ {
//: Settings context menu action //: 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); onTriggered: Cura.Actions.configureSettingVisibility.trigger(contextMenu);
} }

View File

@ -19,6 +19,7 @@ Rectangle
property bool hideView: Cura.MachineManager.activeMachineName == "" property bool hideView: Cura.MachineManager.activeMachineName == ""
// Is there an output device for this printer? // Is there an output device for this printer?
property bool isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != ""
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
@ -86,7 +87,8 @@ Rectangle
} }
} }
MachineSelection { MachineSelection
{
id: machineSelection id: machineSelection
width: base.width - configSelection.width - separator.width width: base.width - configSelection.width - separator.width
height: UM.Theme.getSize("sidebar_header").height height: UM.Theme.getSize("sidebar_header").height
@ -104,9 +106,10 @@ Rectangle
anchors.left: machineSelection.right anchors.left: machineSelection.right
} }
ConfigurationSelection { ConfigurationSelection
{
id: configSelection id: configSelection
visible: printerConnected && !sidebar.monitoringPrint && !sidebar.hideSettings visible: isNetworkPrinter
width: visible ? Math.round(base.width * 0.15) : 0 width: visible ? Math.round(base.width * 0.15) : 0
height: UM.Theme.getSize("sidebar_header").height height: UM.Theme.getSize("sidebar_header").height
anchors.top: base.top anchors.top: base.top

View File

@ -178,6 +178,7 @@ Column
text: catalog.i18nc("@action:inmenu", "Disable Extruder") text: catalog.i18nc("@action:inmenu", "Disable Extruder")
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false) onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false)
visible: extruder_enabled visible: extruder_enabled
enabled: Cura.MachineManager.numberExtrudersEnabled > 1
} }
} }
@ -347,7 +348,8 @@ Column
id: materialSelection id: materialSelection
property var activeExtruder: Cura.MachineManager.activeStack 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 text: currentRootMaterialName
tooltip: currentRootMaterialName tooltip: currentRootMaterialName
@ -366,6 +368,10 @@ Column
property var valueWarning: ! Cura.MachineManager.isActiveQualitySupported property var valueWarning: ! Cura.MachineManager.isActiveQualitySupported
function isMaterialSupported () { function isMaterialSupported () {
if (!hasActiveExtruder)
{
return false;
}
return Cura.ContainerManager.getContainerMetaDataEntry(activeExtruder.material.id, "compatible") == "True" return Cura.ContainerManager.getContainerMetaDataEntry(activeExtruder.material.id, "compatible") == "True"
} }
} }
@ -468,6 +474,74 @@ Column
} }
} }
// Material info row
Item
{
id: materialInfoRow
height: Math.round(UM.Theme.getSize("sidebar_setup").height / 2)
visible: (Cura.MachineManager.hasVariants || Cura.MachineManager.hasMaterials) && !sidebar.monitoringPrint && !sidebar.hideSettings
anchors
{
left: parent.left
leftMargin: UM.Theme.getSize("sidebar_margin").width
right: parent.right
rightMargin: UM.Theme.getSize("sidebar_margin").width
}
Item {
height: UM.Theme.getSize("sidebar_setup").height
anchors.right: parent.right
width: Math.round(parent.width * 0.7 + UM.Theme.getSize("sidebar_margin").width)
UM.RecolorImage {
id: warningImage
anchors.right: materialInfoLabel.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.Bottom
source: UM.Theme.getIcon("warning")
width: UM.Theme.getSize("section_icon").width
height: UM.Theme.getSize("section_icon").height
color: UM.Theme.getColor("material_compatibility_warning")
visible: !Cura.MachineManager.isCurrentSetupSupported
}
Label {
id: materialInfoLabel
wrapMode: Text.WordWrap
text: "<a href='%1'>" + catalog.i18nc("@label", "Check compatibility") + "</a>"
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
verticalAlignment: Text.AlignTop
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: parent.bottom
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
// open the material URL with web browser
var version = UM.Application.version;
var machineName = Cura.MachineManager.activeMachine.definition.id;
var url = "https://ultimaker.com/materialcompatibility/" + version + "/" + machineName + "?utm_source=cura&utm_medium=software&utm_campaign=resources";
Qt.openUrlExternally(url);
}
onEntered: {
var content = catalog.i18nc("@tooltip", "Click to check the material compatibility on Ultimaker.com.");
base.showTooltip(
materialInfoRow,
Qt.point(-UM.Theme.getSize("sidebar_margin").width, 0),
catalog.i18nc("@tooltip", content)
);
}
onExited: base.hideTooltip();
}
}
}
}
UM.SettingPropertyProvider UM.SettingPropertyProvider
{ {
id: machineExtruderCount id: machineExtruderCount

View File

@ -243,6 +243,81 @@ Item
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height 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 // Draw Unavailable line
Rectangle Rectangle
{ {

View File

@ -101,7 +101,7 @@ UM.Dialog
} }
Label Label
{ {
text: Cura.MachineManager.activeMachine.definition.name text: (Cura.MachineManager.activeMachine == null) ? "" : Cura.MachineManager.activeMachine.definition.name
width: (parent.width / 3) | 0 width: (parent.width / 3) | 0
} }
} }

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path d="m 30,23.75 v 2.5 q 0,0.50781 -0.37109,0.87891 Q 29.25781,27.5 28.75,27.5 H 1.25 Q 0.74219,27.5 0.37109,27.12891 0,26.75781 0,26.25 v -2.5 Q 0,23.24219 0.37109,22.87109 0.74219,22.5 1.25,22.5 h 27.5 q 0.50781,0 0.87891,0.37109 Q 30,23.24219 30,23.75 Z m 0,-10 v 2.5 q 0,0.50781 -0.37109,0.87891 Q 29.25781,17.5 28.75,17.5 H 1.25 Q 0.74219,17.5 0.37109,17.12891 0,16.75781 0,16.25 v -2.5 Q 0,13.24219 0.37109,12.87109 0.74219,12.5 1.25,12.5 h 27.5 q 0.50781,0 0.87891,0.37109 Q 30,13.24219 30,13.75 Z m 0,-10 v 2.5 Q 30,6.75781 29.62891,7.12891 29.25781,7.5 28.75,7.5 H 1.25 Q 0.74219,7.5 0.37109,7.12891 0,6.75781 0,6.25 V 3.75 Q 0,3.24219 0.37109,2.87109 0.74219,2.5 1.25,2.5 h 27.5 q 0.50781,0 0.87891,0.37109 Q 30,3.24219 30,3.75 Z" />
</svg>

After

Width:  |  Height:  |  Size: 817 B

View File

@ -56,7 +56,6 @@ retraction_amount = 4.5
retraction_count_max = 15 retraction_count_max = 15
retraction_extrusion_window = =retraction_amount retraction_extrusion_window = =retraction_amount
retraction_hop = 2 retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = 5 retraction_min_travel = 5
retraction_prime_speed = 15 retraction_prime_speed = 15