Merge branch 'master' into speedup

This commit is contained in:
Jack Ha 2018-03-14 16:14:14 +01:00
commit 98b0559c9a
31 changed files with 727 additions and 323 deletions

View File

@ -91,6 +91,7 @@ from cura.Settings.UserChangesModel import UserChangesModel
from cura.Settings.ExtrudersModel import ExtrudersModel
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from cura.Settings.ContainerManager import ContainerManager
from cura.Settings.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel
from cura.ObjectsModel import ObjectsModel
@ -140,6 +141,7 @@ class CuraApplication(QtApplication):
MachineStack = Resources.UserType + 7
ExtruderStack = Resources.UserType + 8
DefinitionChangesContainer = Resources.UserType + 9
SettingVisibilityPreset = Resources.UserType + 10
Q_ENUMS(ResourceTypes)
@ -187,6 +189,7 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders")
Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes")
@ -378,19 +381,9 @@ class CuraApplication(QtApplication):
preferences.setDefault("local_file/last_used_type", "text/x-gcode")
setting_visibily_preset_names = self.getVisibilitySettingPresetTypes()
preferences.setDefault("general/visible_settings_preset", setting_visibily_preset_names)
default_visibility_profile = SettingVisibilityPresetsModel.getInstance().getItem(0)
preset_setting_visibility_choice = Preferences.getInstance().getValue("general/preset_setting_visibility_choice")
default_preset_visibility_group_name = "Basic"
if preset_setting_visibility_choice == "" or preset_setting_visibility_choice is None:
if preset_setting_visibility_choice not in setting_visibily_preset_names:
preset_setting_visibility_choice = default_preset_visibility_group_name
visible_settings = self.getVisibilitySettingPreset(settings_preset_name = preset_setting_visibility_choice)
preferences.setDefault("general/visible_settings", visible_settings)
preferences.setDefault("general/preset_setting_visibility_choice", preset_setting_visibility_choice)
preferences.setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"]))
self.applicationShuttingDown.connect(self.saveSettings)
self.engineCreatedSignal.connect(self._onEngineCreated)
@ -407,91 +400,6 @@ class CuraApplication(QtApplication):
CuraApplication.Created = True
@pyqtSlot(str, result = str)
def getVisibilitySettingPreset(self, settings_preset_name) -> str:
result = self._loadPresetSettingVisibilityGroup(settings_preset_name)
formatted_preset_settings = self._serializePresetSettingVisibilityData(result)
return formatted_preset_settings
## Serialise the given preset setting visibitlity group dictionary into a string which is concatenated by ";"
#
def _serializePresetSettingVisibilityData(self, settings_data: dict) -> str:
result_string = ""
for key in settings_data:
result_string += key + ";"
for value in settings_data[key]:
result_string += value + ";"
return result_string
## Load the preset setting visibility group with the given name
#
def _loadPresetSettingVisibilityGroup(self, visibility_preset_name) -> Dict[str, str]:
preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups)
result = {}
right_preset_found = False
for item in os.listdir(preset_dir):
file_path = os.path.join(preset_dir, item)
if not os.path.isfile(file_path):
continue
parser = ConfigParser(allow_no_value = True) # accept options without any value,
try:
parser.read([file_path])
if not parser.has_option("general", "name"):
continue
if parser["general"]["name"] == visibility_preset_name:
right_preset_found = True
for section in parser.sections():
if section == 'general':
continue
else:
section_settings = []
for option in parser[section].keys():
section_settings.append(option)
result[section] = section_settings
if right_preset_found:
break
except Exception as e:
Logger.log("e", "Failed to load setting visibility preset %s: %s", file_path, str(e))
return result
## Check visibility setting preset folder and returns available types
#
def getVisibilitySettingPresetTypes(self):
preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups)
result = {}
for item in os.listdir(preset_dir):
file_path = os.path.join(preset_dir, item)
if not os.path.isfile(file_path):
continue
parser = ConfigParser(allow_no_value=True) # accept options without any value,
try:
parser.read([file_path])
if not parser.has_option("general", "name") and not parser.has_option("general", "weight"):
continue
result[parser["general"]["weight"]] = parser["general"]["name"]
except Exception as e:
Logger.log("e", "Failed to load setting preset %s: %s", file_path, str(e))
return result
def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@ -991,6 +899,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager)
qmlRegisterSingletonType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel", SettingVisibilityPresetsModel.createSettingVisibilityPresetsModel)
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))

View File

@ -87,9 +87,11 @@ class QualitySettingsModel(ListModel):
if self._selected_position == self.GLOBAL_STACK_POSITION:
quality_node = quality_group.node_for_global
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()
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
# the settings in that quality_changes_group.
@ -97,7 +99,7 @@ class QualitySettingsModel(ListModel):
if self._selected_position == self.GLOBAL_STACK_POSITION:
quality_changes_node = quality_changes_group.node_for_global
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
try:
quality_containers.insert(0, quality_changes_node.getContainer())

View File

@ -16,6 +16,7 @@ from .QualityGroup import QualityGroup
from .QualityNode import QualityNode
if TYPE_CHECKING:
from UM.Settings.DefinitionContainer import DefinitionContainer
from cura.Settings.GlobalStack import GlobalStack
from .QualityChangesGroup import QualityChangesGroup
@ -178,7 +179,7 @@ class QualityManager(QObject):
# Returns a dict of "custom profile name" -> QualityChangesGroup
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)
if not machine_node:
@ -206,7 +207,7 @@ class QualityManager(QObject):
# For more details, see QualityGroup.
#
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
has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False))
@ -315,7 +316,7 @@ class QualityManager(QObject):
return quality_group_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:
# (1) the machine-specific node
@ -460,7 +461,7 @@ class QualityManager(QObject):
quality_changes.addMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
# 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.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
# 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
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.
machine_definition_id = machine.getMetaDataEntry("quality_definition")
machine_definition_id = machine_definition.getMetaDataEntry("quality_definition")
if machine_definition_id is None:
machine_definition_id = machine.definition.getId()
machine_definition_id = machine_definition.getId()
return machine_definition_id

View File

@ -204,7 +204,7 @@ class CuraContainerRegistry(ContainerRegistry):
global_profile = profile_or_list[0]
else:
for profile in profile_or_list:
if not profile.getMetaDataEntry("extruder"):
if not profile.getMetaDataEntry("position"):
global_profile = profile
break
if not global_profile:
@ -212,16 +212,34 @@ class CuraContainerRegistry(ContainerRegistry):
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)}
profile_definition = global_profile.getMetaDataEntry("definition")
expected_machine_definition = "fdmprinter"
if parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", "False")):
expected_machine_definition = global_container_stack.getMetaDataEntry("quality_definition")
if not expected_machine_definition:
expected_machine_definition = global_container_stack.definition.getId()
if expected_machine_definition is not None and profile_definition is not None and profile_definition != expected_machine_definition:
# Make sure we have a profile_definition in the file:
if profile_definition is None:
break
machine_definition = self.findDefinitionContainers(id = profile_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_container_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)
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)}
# Fix the global quality profile's definition field in case it's not correct
global_profile.setMetaDataEntry("definition", expected_machine_definition)
quality_name = global_profile.getName()
quality_type = global_profile.getMetaDataEntry("quality_type")
name_seed = os.path.splitext(os.path.basename(file_name))[0]
new_name = self.uniqueName(name_seed)
@ -236,11 +254,11 @@ class CuraContainerRegistry(ContainerRegistry):
for idx, extruder in enumerate(global_container_stack.extruders.values()):
profile_id = ContainerRegistry.getInstance().uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1))
profile = InstanceContainer(profile_id)
profile.setName(global_profile.getName())
profile.setName(quality_name)
profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
profile.addMetaDataEntry("type", "quality_changes")
profile.addMetaDataEntry("definition", global_profile.getMetaDataEntry("definition"))
profile.addMetaDataEntry("quality_type", global_profile.getMetaDataEntry("quality_type"))
profile.addMetaDataEntry("definition", expected_machine_definition)
profile.addMetaDataEntry("quality_type", quality_type)
profile.addMetaDataEntry("position", "0")
profile.setDirty(True)
if idx == 0:
@ -283,7 +301,7 @@ class CuraContainerRegistry(ContainerRegistry):
else: #More extruders in the imported file than in the machine.
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:
return {"status": "error", "message": catalog.i18nc(
"@info:status Don't translate the XML tags <filename> or <message>!",
@ -311,7 +329,7 @@ class CuraContainerRegistry(ContainerRegistry):
# \param new_name The new name for the profile.
#
# \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
new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
@ -321,6 +339,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
# It also solves an issue with importing profiles from G-Codes
profile.setMetaDataEntry("id", new_id)
profile.setMetaDataEntry("definition", machine_definition_id)
if "type" in profile.getMetaData():
profile.setMetaDataEntry("type", "quality_changes")
@ -331,9 +350,8 @@ class CuraContainerRegistry(ContainerRegistry):
if not 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()
definition_id = getMachineDefinitionIDForQualitySearch(global_stack)
definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition)
profile.setDefinition(definition_id)
# Check to make sure the imported profile actually makes sense in context of the current configuration.

View File

@ -628,7 +628,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = globalContainerChanged)
def activeQualityDefinitionId(self) -> str:
if self._global_container_stack:
return getMachineDefinitionIDForQualitySearch(self._global_container_stack)
return getMachineDefinitionIDForQualitySearch(self._global_container_stack.definition)
return ""
## Gets how the active definition calls variants
@ -882,7 +882,7 @@ class MachineManager(QObject):
@pyqtSlot()
def forceUpdateAllSettings(self):
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
property_names = ["value", "resolve"]
property_names = ["value", "resolve", "validationState"]
for container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
for setting_key in container.getAllKeys():
container.propertiesChanged.emit(setting_key, property_names)

View File

@ -0,0 +1,136 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
import urllib
from configparser import ConfigParser
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot, QUrl
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Preferences import Preferences
from UM.Resources import Resources
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
import cura.CuraApplication
class SettingVisibilityPresetsModel(ListModel):
IdRole = Qt.UserRole + 1
NameRole = Qt.UserRole + 2
SettingsRole = Qt.UserRole + 4
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.SettingsRole, "settings")
self._populate()
self._preferences = Preferences.getInstance()
self._preferences.addPreference("cura/active_setting_visibility_preset", "custom") # Preference to store which preset is currently selected
self._preferences.addPreference("cura/custom_visible_settings", "") # Preference that stores the "custom" set so it can always be restored (even after a restart)
self._preferences.preferenceChanged.connect(self._onPreferencesChanged)
self._active_preset = self._preferences.getValue("cura/active_setting_visibility_preset")
if self.find("id", self._active_preset) < 0:
self._active_preset = "custom"
self.activePresetChanged.emit()
def _populate(self):
items = []
for item in Resources.getAllResourcesOfType(cura.CuraApplication.CuraApplication.ResourceTypes.SettingVisibilityPreset):
try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(item)
except MimeTypeNotFoundError:
Logger.log("e", "Could not determine mime type of file %s", item)
continue
id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(item)))
if not os.path.isfile(item):
continue
parser = ConfigParser(allow_no_value=True) # accept options without any value,
try:
parser.read([item])
if not parser.has_option("general", "name") and not parser.has_option("general", "weight"):
continue
settings = []
for section in parser.sections():
if section == 'general':
continue
settings.append(section)
for option in parser[section].keys():
settings.append(option)
items.append({
"id": id,
"name": parser["general"]["name"],
"weight": parser["general"]["weight"],
"settings": settings
})
except Exception as e:
Logger.log("e", "Failed to load setting preset %s: %s", file_path, str(e))
items.sort(key = lambda k: (k["weight"], k["id"]))
self.setItems(items)
@pyqtSlot(str)
def setActivePreset(self, preset_id):
if preset_id != "custom" and self.find("id", preset_id) == -1:
Logger.log("w", "Tried to set active preset to unknown id %s", preset_id)
return
if preset_id == "custom" and self._active_preset == "custom":
# Copy current visibility set to custom visibility set preference so it can be restored later
visibility_string = self._preferences.getValue("general/visible_settings")
self._preferences.setValue("cura/custom_visible_settings", visibility_string)
self._preferences.setValue("cura/active_setting_visibility_preset", preset_id)
self._active_preset = preset_id
self.activePresetChanged.emit()
activePresetChanged = pyqtSignal()
@pyqtProperty(str, notify = activePresetChanged)
def activePreset(self):
return self._active_preset
def _onPreferencesChanged(self, name):
if name != "general/visible_settings":
return
if self._active_preset != "custom":
return
# Copy current visibility set to custom visibility set preference so it can be restored later
visibility_string = self._preferences.getValue("general/visible_settings")
self._preferences.setValue("cura/custom_visible_settings", visibility_string)
# Factory function, used by QML
@staticmethod
def createSettingVisibilityPresetsModel(engine, js_engine):
return SettingVisibilityPresetsModel.getInstance()
## Get the singleton instance for this class.
@classmethod
def getInstance(cls) -> "SettingVisibilityPresetsModel":
# Note: Explicit use of class name to prevent issues with inheritance.
if not SettingVisibilityPresetsModel.__instance:
SettingVisibilityPresetsModel.__instance = cls()
return SettingVisibilityPresetsModel.__instance
__instance = None # type: "SettingVisibilityPresetsModel"

View File

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

View File

@ -122,7 +122,7 @@ class ThreeMFReader(MeshReader):
um_node.callDecoration("setActiveExtruder", default_stack.getId())
# 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)
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")
else:
global_preferences.setValue("general/visible_settings", visible_settings)
global_preferences.setValue("general/preset_setting_visibility_choice", "Custom")
global_preferences.setValue("cura/active_setting_visibility_preset", "custom")
categories_expanded = temp_preferences.getValue("cura/categories_expanded")
if categories_expanded is None:
@ -719,7 +719,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Get the correct extruder definition IDs for quality changes
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]
quality_changes_info = self._machine_info.quality_changes_info

View File

@ -22,7 +22,7 @@ from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
from time import time
from datetime import datetime
from typing import Optional
from typing import Optional, Dict, List
import json
import os
@ -79,7 +79,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._latest_reply_handler = None
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
self.writeStarted.emit(self)
@ -116,7 +115,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
@pyqtSlot()
@pyqtSlot(str)
def sendPrintJob(self, target_printer = ""):
def sendPrintJob(self, target_printer: str = ""):
Logger.log("i", "Sending print job to printer.")
if self._sending_gcode:
self._error_message = Message(
@ -157,11 +156,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return True
@pyqtProperty(QObject, notify=activePrinterChanged)
def activePrinter(self) -> Optional["PrinterOutputModel"]:
def activePrinter(self) -> Optional[PrinterOutputModel]:
return self._active_printer
@pyqtSlot(QObject)
def setActivePrinter(self, printer):
def setActivePrinter(self, printer: Optional[PrinterOutputModel]):
if self._active_printer != printer:
if self._active_printer and self._active_printer.camera:
self._active_printer.camera.stop()
@ -173,7 +172,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._compressing_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:
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
@ -186,7 +185,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._progress_message.setProgress(0)
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":
Logger.log("d", "User aborted sending print to remote.")
self._progress_message.hide()
@ -202,29 +201,29 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
@pyqtSlot()
def openPrintJobControlPanel(self):
def openPrintJobControlPanel(self) -> None:
Logger.log("d", "Opening print job control panel...")
QDesktopServices.openUrl(QUrl("http://" + self._address + "/print_jobs"))
@pyqtSlot()
def openPrinterControlPanel(self):
def openPrinterControlPanel(self) -> None:
Logger.log("d", "Opening printer control panel...")
QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers"))
@pyqtProperty("QVariantList", notify=printJobsChanged)
def printJobs(self):
def printJobs(self)-> List[PrintJobOutputModel] :
return self._print_jobs
@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"]
@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"]
@pyqtProperty("QVariantList", notify=clusterPrintersChanged)
def connectedPrintersTypeCount(self):
def connectedPrintersTypeCount(self) -> List[PrinterOutputModel]:
printer_count = {}
for printer in self._printers:
if printer.type in printer_count:
@ -237,22 +236,22 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return result
@pyqtSlot(int, result=str)
def formatDuration(self, seconds):
def formatDuration(self, seconds: int) -> str:
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
@pyqtSlot(int, result=str)
def getTimeCompleted(self, time_remaining):
def getTimeCompleted(self, time_remaining: int) -> str:
current_time = time()
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute)
@pyqtSlot(int, result=str)
def getDateCompleted(self, time_remaining):
def getDateCompleted(self, time_remaining: int) -> str:
current_time = time()
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
def _printJobStateChanged(self):
def _printJobStateChanged(self) -> None:
username = self._getUserName()
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.
self._finished_jobs = finished_jobs
def _update(self):
def _update(self) -> None:
if not super()._update():
return
self.get("printers/", onFinished=self._onGetPrintersDataFinished)
self.get("print_jobs/", onFinished=self._onGetPrintJobsFinished)
def _onGetPrintJobsFinished(self, reply: QNetworkReply):
def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
if not checkValidGetReply(reply):
return
@ -323,7 +322,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if job_list_changed:
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):
return
@ -352,31 +351,37 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if removed_printers or printer_list_changed:
self.printersChanged.emit()
def _createPrinterModel(self, data):
def _createPrinterModel(self, data: Dict) -> PrinterOutputModel:
printer = PrinterOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
number_of_extruders=self._number_of_extruders)
printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream"))
self._printers.append(printer)
return printer
def _createPrintJobModel(self, data):
def _createPrintJobModel(self, data: Dict) -> PrintJobOutputModel:
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
key=data["uuid"], name= data["name"])
print_job.stateChanged.connect(self._printJobStateChanged)
self._print_jobs.append(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.updateTimeElapsed(data["time_elapsed"])
print_job.updateState(data["status"])
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.
# 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"]
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.updateKey(data["uuid"])
@ -421,7 +426,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
brand=brand, color=color, name=name)
extruder.updateActiveMaterial(material)
def _removeJob(self, job):
def _removeJob(self, job: PrintJobOutputModel):
if job not in self._print_jobs:
return False
@ -432,7 +437,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return True
def _removePrinter(self, printer):
def _removePrinter(self, printer: PrinterOutputModel):
self._printers.remove(printer)
if self._active_printer == printer:
self._active_printer = None

View File

@ -22,6 +22,7 @@ class AutoDetectBaudJob(Job):
def run(self):
Logger.log("d", "Auto detect baud rate started.")
timeout = 3
tries = 2
programmer = Stk500v2()
serial = None
@ -31,36 +32,38 @@ class AutoDetectBaudJob(Job):
except:
programmer.close()
for baud_rate in self._all_baud_rates:
Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate))
for retry in range(tries):
for baud_rate in self._all_baud_rates:
Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate))
if serial is None:
try:
serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout)
except SerialException as e:
Logger.logException("w", "Unable to create serial")
continue
else:
# We already have a serial connection, just change the baud rate.
try:
serial.baudrate = baud_rate
except:
continue
sleep(1.5) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number
successful_responses = 0
serial.write(b"\n") # Ensure we clear out previous responses
serial.write(b"M105\n")
timeout_time = time() + timeout
while timeout_time > time():
line = serial.readline()
if b"ok T:" in line:
successful_responses += 1
if successful_responses >= 3:
self.setResult(baud_rate)
return
if serial is None:
try:
serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout)
except SerialException as e:
Logger.logException("w", "Unable to create serial")
continue
else:
# We already have a serial connection, just change the baud rate.
try:
serial.baudrate = baud_rate
except:
continue
sleep(1.5) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number
successful_responses = 0
serial.write(b"\n") # Ensure we clear out previous responses
serial.write(b"M105\n")
timeout_time = time() + timeout
while timeout_time > time():
line = serial.readline()
if b"ok T:" in line:
successful_responses += 1
if successful_responses >= 3:
self.setResult(baud_rate)
return
serial.write(b"M105\n")
sleep(15) # Give the printer some time to init and try again.
self.setResult(None) # Unable to detect the correct baudrate.

View File

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

View File

@ -56,6 +56,8 @@ _EXTRUDER_TO_POSITION = {
## Upgrades configurations from the state they were in at version 3.2 to the
# state they should be in at version 3.3.
class VersionUpgrade32to33(VersionUpgrade):
temporary_group_name_counter = 1
## 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
@ -74,6 +76,28 @@ class VersionUpgrade32to33(VersionUpgrade):
setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
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
# number.
def upgradeInstanceContainer(self, serialized, filename):

View File

@ -9,11 +9,22 @@ def getMetaData():
return {
"version_upgrade": {
# 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),
("quality_changes", 2000004): ("quality_changes", 3000004, upgrade.upgradeQualityChanges),
("user", 2000004): ("user", 3000004, upgrade.upgradeInstanceContainer)
},
"sources": {
"machine_stack": {
"get_version": upgrade.getCfgVersion,
"location": {"./machine_instances"}
},
"extruder_train": {
"get_version": upgrade.getCfgVersion,
"location": {"./extruders"}
},
"definition_changes": {
"get_version": upgrade.getCfgVersion,
"location": {"./definition_changes"}

View File

@ -653,7 +653,10 @@ UM.MainWindow
{
preferences.visible = true;
preferences.setPage(1);
preferences.getCurrentItem().scrollToSection(source.key);
if(source && source.key)
{
preferences.getCurrentItem().scrollToSection(source.key);
}
}
}

View File

@ -12,7 +12,7 @@ import "Menus"
ToolButton {
id: base
property var isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != ""
property bool isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != ""
property var printerStatus: Cura.MachineManager.printerOutputDevices.length != 0 ? "connected" : "disconnected"
text: isNetworkPrinter ? Cura.MachineManager.activeMachineNetworkGroupName : Cura.MachineManager.activeMachineName

View File

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

View File

@ -13,54 +13,54 @@ Item
id: configurationSelector
property var connectedDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
property var panelWidth: control.width
property var panelVisible: false
SyncButton {
onClicked: configurationSelector.state == "open" ? configurationSelector.state = "closed" : configurationSelector.state = "open"
function switchPopupState()
{
popup.opened ? popup.close() : popup.open()
}
SyncButton
{
id: syncButton
onClicked: switchPopupState()
outputDevice: connectedDevice
}
Popup {
Popup
{
// TODO Change once updating to Qt5.10 - This property is already in 5.10 but is manually implemented until upgrade
property bool opened: false
id: popup
clip: true
closePolicy: Popup.CloseOnPressOutsideParent
y: configurationSelector.height - UM.Theme.getSize("default_lining").height
x: configurationSelector.width - width
width: panelWidth
visible: panelVisible && connectedDevice != null
visible: opened
padding: UM.Theme.getSize("default_lining").width
contentItem: ConfigurationListView {
transformOrigin: Popup.Top
contentItem: ConfigurationListView
{
id: configList
width: panelWidth - 2 * popup.padding
outputDevice: connectedDevice
}
background: Rectangle {
background: Rectangle
{
color: UM.Theme.getColor("setting_control")
border.color: UM.Theme.getColor("setting_control_border")
}
}
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 {
exit: Transition
{
// 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: opened = false
onOpened: opened = true
}
}

View File

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

View File

@ -0,0 +1,82 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import UM 1.2 as UM
import Cura 1.0 as Cura
Menu
{
id: menu
title: catalog.i18nc("@action:inmenu", "Visible Settings")
property bool showingSearchResults
property bool showingAllSettings
signal showAllSettings()
signal showSettingVisibilityProfile()
MenuItem
{
text: catalog.i18nc("@action:inmenu", "Custom selection")
checkable: true
checked: !showingSearchResults && !showingAllSettings && Cura.SettingVisibilityPresetsModel.activePreset == "custom"
exclusiveGroup: group
onTriggered:
{
Cura.SettingVisibilityPresetsModel.setActivePreset("custom");
// Restore custom set from preference
UM.Preferences.setValue("general/visible_settings", UM.Preferences.getValue("cura/custom_visible_settings"));
showSettingVisibilityProfile();
}
}
MenuSeparator { }
Instantiator
{
model: Cura.SettingVisibilityPresetsModel
MenuItem
{
text: model.name
checkable: true
checked: model.id == Cura.SettingVisibilityPresetsModel.activePreset
exclusiveGroup: group
onTriggered:
{
Cura.SettingVisibilityPresetsModel.setActivePreset(model.id);
UM.Preferences.setValue("general/visible_settings", model.settings.join(";"));
showSettingVisibilityProfile();
}
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
MenuSeparator {}
MenuItem
{
text: catalog.i18nc("@action:inmenu", "All Settings")
checkable: true
checked: showingAllSettings
exclusiveGroup: group
onTriggered:
{
showAllSettings();
}
}
MenuSeparator {}
MenuItem
{
text: catalog.i18nc("@action:inmenu", "Manage Setting Visibility...")
iconName: "configure"
onTriggered: Cura.Actions.configureSettingVisibility.trigger()
}
ExclusiveGroup { id: group }
}

View File

@ -26,8 +26,8 @@ UM.PreferencesPage
UM.Preferences.resetPreference("general/visible_settings")
// After calling this function update Setting visibility preset combobox.
// Reset should set "Basic" setting preset
visibilityPreset.setBasicPreset()
// Reset should set default setting preset ("Basic")
visibilityPreset.setDefaultPreset()
}
resetEnabled: true;
@ -37,6 +37,8 @@ UM.PreferencesPage
id: base;
anchors.fill: parent;
property bool inhibitSwitchToCustom: false
CheckBox
{
id: toggleVisibleSettings
@ -84,7 +86,7 @@ UM.PreferencesPage
if (visibilityPreset.currentIndex != visibilityPreset.model.count - 1)
{
visibilityPreset.currentIndex = visibilityPreset.model.count - 1
UM.Preferences.setValue("general/preset_setting_visibility_choice", visibilityPreset.model.get(visibilityPreset.currentIndex).text)
UM.Preferences.setValue("cura/active_setting_visibility_preset", visibilityPreset.model.getItem(visibilityPreset.currentIndex).id)
}
}
}
@ -110,25 +112,13 @@ UM.PreferencesPage
ComboBox
{
property int customOptionValue: 100
function setBasicPreset()
function setDefaultPreset()
{
var index = 0
for(var i = 0; i < presetNamesList.count; ++i)
{
if(model.get(i).text == "Basic")
{
index = i;
break;
}
}
visibilityPreset.currentIndex = index
visibilityPreset.currentIndex = 0
}
id: visibilityPreset
width: 150
width: 150 * screenScaleFactor
anchors
{
top: parent.top
@ -137,56 +127,49 @@ UM.PreferencesPage
model: ListModel
{
id: presetNamesList
id: visibilityPresetsModel
Component.onCompleted:
{
// returned value is Dictionary (Ex: {1:"Basic"}, The number 1 is the weight and sort by weight)
var itemsDict = UM.Preferences.getValue("general/visible_settings_preset")
var sorted = [];
for(var key in itemsDict) {
sorted[sorted.length] = key;
}
visibilityPresetsModel.append({text: catalog.i18nc("@action:inmenu", "Custom selection"), id: "custom"});
sorted.sort();
for(var i = 0; i < sorted.length; i++) {
presetNamesList.append({text: itemsDict[sorted[i]], value: i});
var presets = Cura.SettingVisibilityPresetsModel;
for(var i = 0; i < presets.rowCount(); i++)
{
visibilityPresetsModel.append({text: presets.getItem(i)["name"], id: presets.getItem(i)["id"]});
}
// By agreement lets "Custom" option will have value 100
presetNamesList.append({text: "Custom", value: visibilityPreset.customOptionValue});
}
}
currentIndex:
{
// Load previously selected preset.
var text = UM.Preferences.getValue("general/preset_setting_visibility_choice");
var index = 0;
for(var i = 0; i < presetNamesList.count; ++i)
var index = Cura.SettingVisibilityPresetsModel.find("id", Cura.SettingVisibilityPresetsModel.activePreset);
if(index == -1)
{
if(model.get(i).text == text)
{
index = i;
break;
}
return 0;
}
return index;
return index + 1; // "Custom selection" entry is added in front, so index is off by 1
}
onActivated:
{
// TODO What to do if user is selected "Custom from Combobox" ?
if (model.get(index).text == "Custom"){
UM.Preferences.setValue("general/preset_setting_visibility_choice", model.get(index).text)
return
}
base.inhibitSwitchToCustom = true;
var preset_id = visibilityPresetsModel.get(index).id;
Cura.SettingVisibilityPresetsModel.setActivePreset(preset_id);
var newVisibleSettings = CuraApplication.getVisibilitySettingPreset(model.get(index).text)
UM.Preferences.setValue("general/visible_settings", newVisibleSettings)
UM.Preferences.setValue("general/preset_setting_visibility_choice", model.get(index).text)
UM.Preferences.setValue("cura/active_setting_visibility_preset", preset_id);
if (preset_id != "custom")
{
UM.Preferences.setValue("general/visible_settings", Cura.SettingVisibilityPresetsModel.getItem(index - 1).settings.join(";"));
// "Custom selection" entry is added in front, so index is off by 1
}
else
{
// Restore custom set from preference
UM.Preferences.setValue("general/visible_settings", UM.Preferences.getValue("cura/custom_visible_settings"));
}
base.inhibitSwitchToCustom = false;
}
}
@ -216,7 +199,16 @@ UM.PreferencesPage
exclude: ["machine_settings", "command_line_settings"]
showAncestors: true
expanded: ["*"]
visibilityHandler: UM.SettingPreferenceVisibilityHandler { }
visibilityHandler: UM.SettingPreferenceVisibilityHandler
{
onVisibilityChanged:
{
if(Cura.SettingVisibilityPresetsModel.activePreset != "" && !base.inhibitSwitchToCustom)
{
Cura.SettingVisibilityPresetsModel.setActivePreset("custom");
}
}
}
}
delegate: Loader
@ -259,19 +251,7 @@ UM.PreferencesPage
{
id: settingVisibilityItem;
UM.SettingVisibilityItem {
// after changing any visibility of settings, set the preset to the "Custom" option
visibilityChangeCallback : function()
{
// If already "Custom" then don't do nothing
if (visibilityPreset.currentIndex != visibilityPreset.model.count - 1)
{
visibilityPreset.currentIndex = visibilityPreset.model.count - 1
UM.Preferences.setValue("general/preset_setting_visibility_choice", visibilityPreset.model.get(visibilityPreset.currentIndex).text)
}
}
}
UM.SettingVisibilityItem { }
}
}
}

View File

@ -15,10 +15,11 @@ Item
{
id: base;
property Action configureSettings;
property bool findingSettings;
signal showTooltip(Item item, point location, string text);
signal hideTooltip();
property Action configureSettings
property bool findingSettings
property bool showingAllSettings
signal showTooltip(Item item, point location, string text)
signal hideTooltip()
Item
{
@ -107,6 +108,57 @@ Item
}
}
ToolButton
{
id: settingVisibilityMenu
width: height
height: UM.Theme.getSize("setting_control").height
anchors
{
top: globalProfileRow.bottom
topMargin: UM.Theme.getSize("sidebar_margin").height
right: parent.right
rightMargin: UM.Theme.getSize("sidebar_margin").width
}
style: ButtonStyle
{
background: Item {
UM.RecolorImage {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width
sourceSize.height: width
color: control.enabled ? UM.Theme.getColor("setting_category_text") : UM.Theme.getColor("setting_category_disabled_text")
source: UM.Theme.getIcon("menu")
}
}
label: Label{}
}
menu: SettingVisibilityPresetsMenu
{
showingSearchResults: findingSettings
showingAllSettings: showingAllSettings
onShowAllSettings:
{
base.showingAllSettings = true;
base.findingSettings = false;
filter.text = "";
filter.updateDefinitionModel();
}
onShowSettingVisibilityProfile:
{
base.showingAllSettings = false;
base.findingSettings = false;
filter.text = "";
filter.updateDefinitionModel();
}
}
}
Rectangle
{
id: filterContainer
@ -132,9 +184,9 @@ Item
top: globalProfileRow.bottom
topMargin: UM.Theme.getSize("sidebar_margin").height
left: parent.left
leftMargin: Math.round(UM.Theme.getSize("sidebar_margin").width)
right: parent.right
rightMargin: Math.round(UM.Theme.getSize("sidebar_margin").width)
leftMargin: UM.Theme.getSize("sidebar_margin").width
right: settingVisibilityMenu.left
rightMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
}
height: visible ? UM.Theme.getSize("setting_control").height : 0
Behavior on height { NumberAnimation { duration: 100 } }
@ -168,17 +220,9 @@ Item
{
if(findingSettings)
{
expandedCategories = definitionsModel.expanded.slice();
definitionsModel.expanded = ["*"];
definitionsModel.showAncestors = true;
definitionsModel.showAll = true;
}
else
{
definitionsModel.expanded = expandedCategories;
definitionsModel.showAncestors = false;
definitionsModel.showAll = false;
showingAllSettings = false;
}
updateDefinitionModel();
lastFindingSettings = findingSettings;
}
}
@ -187,6 +231,27 @@ Item
{
filter.text = "";
}
function updateDefinitionModel()
{
if(findingSettings || showingAllSettings)
{
expandedCategories = definitionsModel.expanded.slice();
definitionsModel.expanded = [""]; // keep categories closed while to prevent render while making settings visible one by one
definitionsModel.showAncestors = true;
definitionsModel.showAll = true;
definitionsModel.expanded = ["*"];
}
else
{
if(expandedCategories)
{
definitionsModel.expanded = expandedCategories;
}
definitionsModel.showAncestors = false;
definitionsModel.showAll = false;
}
}
}
MouseArea
@ -209,7 +274,7 @@ Item
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Math.round(UM.Theme.getSize("sidebar_margin").width)
anchors.rightMargin: UM.Theme.getSize("default_margin").width
color: UM.Theme.getColor("setting_control_button")
hoverColor: UM.Theme.getColor("setting_control_button_hover")
@ -491,9 +556,17 @@ Item
MenuItem
{
//: Settings context menu action
visible: !findingSettings;
visible: !(findingSettings || showingAllSettings);
text: catalog.i18nc("@action:menu", "Hide this setting");
onTriggered: definitionsModel.hide(contextMenu.key);
onTriggered:
{
definitionsModel.hide(contextMenu.key);
// visible settings have changed, so we're no longer showing a preset
if (Cura.SettingVisibilityPresetsModel.activePreset != "" && !showingAllSettings)
{
Cura.SettingVisibilityPresetsModel.setActivePreset("custom");
}
}
}
MenuItem
{
@ -509,7 +582,7 @@ Item
return catalog.i18nc("@action:menu", "Keep this setting visible");
}
}
visible: findingSettings;
visible: (findingSettings || showingAllSettings);
onTriggered:
{
if (contextMenu.settingVisible)
@ -520,6 +593,11 @@ Item
{
definitionsModel.show(contextMenu.key);
}
// visible settings have changed, so we're no longer showing a preset
if (Cura.SettingVisibilityPresetsModel.activePreset != "" && !showingAllSettings)
{
Cura.SettingVisibilityPresetsModel.setActivePreset("custom");
}
}
}
MenuItem

View File

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

View File

@ -473,6 +473,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
{
id: machineExtruderCount

View File

@ -243,6 +243,81 @@ Item
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height
// This Item is used only for tooltip, for slider area which is unavailable
Item
{
function showTooltip (showTooltip)
{
if (showTooltip) {
var content = catalog.i18nc("@tooltip", "This quality profile is not available for you current material and nozzle configuration. Please change these to enable this quality profile")
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("sidebar_margin").width, customisedSettings.height), content)
}
else {
base.hideTooltip()
}
}
id: unavailableLineToolTip
height: 20 // hovered area height
z: parent.z + 1 // should be higher, otherwise the area can be hovered
x: 0
anchors.verticalCenter: qualitySlider.verticalCenter
Rectangle
{
id: leftArea
width:
{
if (qualityModel.availableTotalTicks == 0) {
return qualityModel.qualitySliderStepWidth * qualityModel.totalTicks
}
return qualityModel.qualitySliderStepWidth * qualityModel.qualitySliderAvailableMin - 10
}
height: parent.height
color: "transparent"
MouseArea
{
anchors.fill: parent
hoverEnabled: true
enabled: Cura.SimpleModeSettingsManager.isProfileUserCreated == false
onEntered: unavailableLineToolTip.showTooltip(true)
onExited: unavailableLineToolTip.showTooltip(false)
}
}
Rectangle
{
id: rightArea
width: {
if(qualityModel.availableTotalTicks == 0)
return 0
return qualityModel.qualitySliderMarginRight - 10
}
height: parent.height
color: "transparent"
x: {
if (qualityModel.availableTotalTicks == 0) {
return 0
}
var leftUnavailableArea = qualityModel.qualitySliderStepWidth * qualityModel.qualitySliderAvailableMin
var totalGap = qualityModel.qualitySliderStepWidth * (qualityModel.availableTotalTicks -1) + leftUnavailableArea + 10
return totalGap
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
enabled: Cura.SimpleModeSettingsManager.isProfileUserCreated == false
onEntered: unavailableLineToolTip.showTooltip(true)
onExited: unavailableLineToolTip.showTooltip(false)
}
}
}
// Draw Unavailable line
Rectangle
{

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_extrusion_window = =retraction_amount
retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 5
retraction_prime_speed = 15

View File

@ -22,4 +22,4 @@ def test_ultimaker3extended_variants(um3_file, um3e_file):
um3.read_file(open(os.path.join(directory, um3_file)))
um3e = configparser.ConfigParser()
um3e.read_file(open(os.path.join(directory, um3e_file)))
assert um3["values"] == um3e["values"]
assert um3["values"] == um3e["values"]