Merge pull request #6289 from Ultimaker/feature_intent_interface

Feature intent interface
This commit is contained in:
Lipu Fei 2019-09-09 16:53:37 +02:00 committed by GitHub
commit 3f3aac7ce5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 875 additions and 784 deletions

View file

@ -17,4 +17,4 @@ class IntentNode(ContainerNode):
def __init__(self, container_id: str, quality: "QualityNode") -> None: def __init__(self, container_id: str, quality: "QualityNode") -> None:
super().__init__(container_id) super().__init__(container_id)
self.quality = quality self.quality = quality
self.intent_category = ContainerRegistry.getInstance().findContainersMetadata(id = container_id)[0].get("intent_category", "default") self.intent_category = ContainerRegistry.getInstance().findContainersMetadata(id = container_id)[0].get("intent_category", "default")

View file

@ -104,6 +104,8 @@ class BaseMaterialsModel(ListModel):
# tree. This change may trigger an _update() call when the materials # tree. This change may trigger an _update() call when the materials
# changed for the configuration that this model is looking for. # changed for the configuration that this model is looking for.
def _materialsListChanged(self, material: MaterialNode) -> None: def _materialsListChanged(self, material: MaterialNode) -> None:
if self._extruder_stack is None:
return
if material.variant.container_id != self._extruder_stack.variant.getId(): if material.variant.container_id != self._extruder_stack.variant.getId():
return return
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()

View file

@ -5,9 +5,11 @@ from PyQt5.QtCore import Qt
import collections import collections
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from cura.Machines.Models.IntentModel import IntentModel
from cura.Settings.IntentManager import IntentManager from cura.Settings.IntentManager import IntentManager
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes. from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes.
from PyQt5.QtCore import pyqtProperty, pyqtSignal
if TYPE_CHECKING: if TYPE_CHECKING:
from UM.Settings.ContainerRegistry import ContainerInterface from UM.Settings.ContainerRegistry import ContainerInterface
@ -15,12 +17,14 @@ if TYPE_CHECKING:
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
## Lists the intent categories that are available for the current printer ## Lists the intent categories that are available for the current printer
# configuration. # configuration.
class IntentCategoryModel(ListModel): class IntentCategoryModel(ListModel):
NameRole = Qt.UserRole + 1 NameRole = Qt.UserRole + 1
IntentCategoryRole = Qt.UserRole + 2 IntentCategoryRole = Qt.UserRole + 2
WeightRole = Qt.UserRole + 3 WeightRole = Qt.UserRole + 3
QualitiesRole = Qt.UserRole + 4
#Translations to user-visible string. Ordered by weight. #Translations to user-visible string. Ordered by weight.
#TODO: Create a solution for this name and weight to be used dynamically. #TODO: Create a solution for this name and weight to be used dynamically.
@ -29,6 +33,8 @@ class IntentCategoryModel(ListModel):
name_translation["engineering"] = catalog.i18nc("@label", "Engineering") name_translation["engineering"] = catalog.i18nc("@label", "Engineering")
name_translation["smooth"] = catalog.i18nc("@label", "Smooth") name_translation["smooth"] = catalog.i18nc("@label", "Smooth")
modelUpdated = pyqtSignal()
## Creates a new model for a certain intent category. ## Creates a new model for a certain intent category.
# \param The category to list the intent profiles for. # \param The category to list the intent profiles for.
def __init__(self, intent_category: str) -> None: def __init__(self, intent_category: str) -> None:
@ -38,6 +44,7 @@ class IntentCategoryModel(ListModel):
self.addRoleName(self.NameRole, "name") self.addRoleName(self.NameRole, "name")
self.addRoleName(self.IntentCategoryRole, "intent_category") self.addRoleName(self.IntentCategoryRole, "intent_category")
self.addRoleName(self.WeightRole, "weight") self.addRoleName(self.WeightRole, "weight")
self.addRoleName(self.QualitiesRole, "qualities")
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange) ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange)
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange) ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange)
@ -55,9 +62,13 @@ class IntentCategoryModel(ListModel):
available_categories = IntentManager.getInstance().currentAvailableIntentCategories() available_categories = IntentManager.getInstance().currentAvailableIntentCategories()
result = [] result = []
for category in available_categories: for category in available_categories:
qualities = IntentModel()
qualities.setIntentCategory(category)
result.append({ result.append({
"name": self.name_translation.get(category, catalog.i18nc("@label", "Unknown")), "name": self.name_translation.get(category, catalog.i18nc("@label", "Unknown")),
"intent_category": category, "intent_category": category,
"weight": list(self.name_translation.keys()).index(category) "weight": list(self.name_translation.keys()).index(category),
"qualities": qualities
}) })
self.setItems(result) result.sort(key = lambda k: k["weight"])
self.setItems(result)

View file

@ -6,8 +6,11 @@ from typing import Optional, List, Dict, Any
from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction
from cura.Machines.ContainerTree import ContainerTree from cura.Machines.ContainerTree import ContainerTree
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.IntentManager import IntentManager from cura.Settings.IntentManager import IntentManager
import cura.CuraApplication import cura.CuraApplication
@ -15,18 +18,26 @@ import cura.CuraApplication
class IntentModel(ListModel): class IntentModel(ListModel):
NameRole = Qt.UserRole + 1 NameRole = Qt.UserRole + 1
QualityTypeRole = Qt.UserRole + 2 QualityTypeRole = Qt.UserRole + 2
LayerHeightRole = Qt.UserRole + 3
AvailableRole = Qt.UserRole + 4
IntentRole = Qt.UserRole + 5
def __init__(self, parent: Optional[QObject] = None) -> None: def __init__(self, parent: Optional[QObject] = None) -> None:
super().__init__(parent) super().__init__(parent)
self.addRoleName(self.NameRole, "name") self.addRoleName(self.NameRole, "name")
self.addRoleName(self.QualityTypeRole, "quality_type") self.addRoleName(self.QualityTypeRole, "quality_type")
self.addRoleName(self.LayerHeightRole, "layer_height")
self.addRoleName(self.AvailableRole, "available")
self.addRoleName(self.IntentRole, "intent_category")
self._intent_category = "engineering" self._intent_category = "engineering"
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager() machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
machine_manager.globalContainerChanged.connect(self._update) machine_manager.globalContainerChanged.connect(self._update)
machine_manager.activeStackChanged.connect(self._update) ContainerRegistry.getInstance().containerAdded.connect(self._onChanged)
ContainerRegistry.getInstance().containerRemoved.connect(self._onChanged)
self._layer_height_unit = "" # This is cached
self._update() self._update()
intentCategoryChanged = pyqtSignal() intentCategoryChanged = pyqtSignal()
@ -41,6 +52,10 @@ class IntentModel(ListModel):
def intentCategory(self) -> str: def intentCategory(self) -> str:
return self._intent_category return self._intent_category
def _onChanged(self, container):
if container.getMetaDataEntry("type") == "intent":
self._update()
def _update(self) -> None: def _update(self) -> None:
new_items = [] # type: List[Dict[str, Any]] new_items = [] # type: List[Dict[str, Any]]
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
@ -49,11 +64,76 @@ class IntentModel(ListModel):
return return
quality_groups = ContainerTree.getInstance().getCurrentQualityGroups() quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
for intent_category, quality_type in IntentManager.getInstance().getCurrentAvailableIntents(): container_tree = ContainerTree.getInstance()
if intent_category == self._intent_category: machine_node = container_tree.machines[global_stack.definition.getId()]
new_items.append({"name": quality_groups[quality_type].name, "quality_type": quality_type}) active_extruder = ExtruderManager.getInstance().getActiveExtruderStack()
if self._intent_category == "default": #For Default we always list all quality types. We can't filter on available profiles since the empty intent is not a specific quality type. if not active_extruder:
for quality_type in quality_groups.keys(): return
new_items.append({"name": quality_groups[quality_type].name, "quality_type": quality_type}) active_variant_name = active_extruder.variant.getMetaDataEntry("name")
active_variant_node = machine_node.variants[active_variant_name]
active_material_node = active_variant_node.materials[active_extruder.material.getMetaDataEntry("base_file")]
layer_heights_added = []
for quality_id, quality_node in active_material_node.qualities.items():
quality_group = quality_groups[quality_node.quality_type]
layer_height = self._fetchLayerHeight(quality_group)
for intent_id, intent_node in quality_node.intents.items():
if intent_node.intent_category != self._intent_category:
continue
layer_heights_added.append(layer_height)
new_items.append({"name": quality_group.name,
"quality_type": quality_group.quality_type,
"layer_height": layer_height,
"available": quality_group.is_available,
"intent_category": self._intent_category
})
# Now that we added all intents that we found something for, ensure that we set add ticks (and layer_heights)
# for all groups that we don't have anything for (and set it to not available)
for quality_tuple, quality_group in quality_groups.items():
# Add the intents that are of the correct category
if quality_tuple[0] != self._intent_category:
layer_height = self._fetchLayerHeight(quality_group)
if layer_height not in layer_heights_added:
new_items.append({"name": "Unavailable",
"quality_type": "",
"layer_height": layer_height,
"intent_category": self._intent_category,
"available": False})
layer_heights_added.append(layer_height)
new_items = sorted(new_items, key=lambda x: x["layer_height"])
self.setItems(new_items) self.setItems(new_items)
#TODO: Copied this from QualityProfilesDropdownMenuModel for the moment. This code duplication should be fixed.
def _fetchLayerHeight(self, quality_group) -> float:
global_stack = cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeMachine
if not self._layer_height_unit:
unit = global_stack.definition.getProperty("layer_height", "unit")
if not unit:
unit = ""
self._layer_height_unit = unit
default_layer_height = global_stack.definition.getProperty("layer_height", "value")
# Get layer_height from the quality profile for the GlobalStack
if quality_group.node_for_global is None:
return float(default_layer_height)
container = quality_group.node_for_global.getContainer()
layer_height = default_layer_height
if container and container.hasProperty("layer_height", "value"):
layer_height = container.getProperty("layer_height", "value")
else:
# Look for layer_height in the GlobalStack from material -> definition
container = global_stack.definition
if container and container.hasProperty("layer_height", "value"):
layer_height = container.getProperty("layer_height", "value")
if isinstance(layer_height, SettingFunction):
layer_height = layer_height(global_stack)
return float(layer_height)
def __repr__(self):
return str(self.items)

View file

@ -31,8 +31,8 @@ class QualityNode(ContainerNode):
# Find all intent profiles that fit the current configuration. # Find all intent profiles that fit the current configuration.
from cura.Machines.MachineNode import MachineNode from cura.Machines.MachineNode import MachineNode
if not isinstance(self.parent, MachineNode): # Not a global profile. if not isinstance(self.parent, MachineNode): # Not a global profile.
for intent in container_registry.findInstanceContainersMetadata(type = "intent", definition = self.parent.variant.machine.quality_definition, variant = self.parent.variant.variant_name, material = self.parent.base_file): for intent in container_registry.findInstanceContainersMetadata(type = "intent", definition = self.parent.variant.machine.quality_definition, variant = self.parent.variant.variant_name, material = self.parent.base_file, quality_type = self.quality_type):
self.intents[intent["id"]] = IntentNode(intent["id"], quality = self) self.intents[intent["id"]] = IntentNode(intent["id"], quality = self)
if not self.intents:
self.intents["empty_intent"] = IntentNode("empty_intent", quality = self) self.intents["empty_intent"] = IntentNode("empty_intent", quality = self)
# Otherwise, there are no intents for global profiles. # Otherwise, there are no intents for global profiles.

View file

@ -160,6 +160,7 @@ class CuraStackBuilder:
stack.variant = variant_container stack.variant = variant_container
stack.material = material_container stack.material = material_container
stack.quality = quality_container stack.quality = quality_container
stack.intent = application.empty_intent_container
stack.qualityChanges = application.empty_quality_changes_container stack.qualityChanges = application.empty_quality_changes_container
stack.userChanges = user_container stack.userChanges = user_container
@ -208,6 +209,7 @@ class CuraStackBuilder:
stack.variant = variant_container stack.variant = variant_container
stack.material = material_container stack.material = material_container
stack.quality = quality_container stack.quality = quality_container
stack.intent = application.empty_intent_container
stack.qualityChanges = application.empty_quality_changes_container stack.qualityChanges = application.empty_quality_changes_container
stack.userChanges = user_container stack.userChanges = user_container

View file

@ -132,6 +132,7 @@ class MachineManager(QObject):
activeMaterialChanged = pyqtSignal() activeMaterialChanged = pyqtSignal()
activeVariantChanged = pyqtSignal() activeVariantChanged = pyqtSignal()
activeQualityChanged = pyqtSignal() activeQualityChanged = pyqtSignal()
activeIntentChanged = pyqtSignal()
activeStackChanged = pyqtSignal() # Emitted whenever the active stack is changed (ie: when changing between extruders, changing a profile, but not when changing a value) activeStackChanged = pyqtSignal() # Emitted whenever the active stack is changed (ie: when changing between extruders, changing a profile, but not when changing a value)
extruderChanged = pyqtSignal() extruderChanged = pyqtSignal()
@ -270,6 +271,7 @@ class MachineManager(QObject):
self.activeQualityChanged.emit() self.activeQualityChanged.emit()
self.activeVariantChanged.emit() self.activeVariantChanged.emit()
self.activeMaterialChanged.emit() self.activeMaterialChanged.emit()
self.activeIntentChanged.emit()
self.rootMaterialChanged.emit() self.rootMaterialChanged.emit()
self.numberExtrudersEnabledChanged.emit() self.numberExtrudersEnabledChanged.emit()
@ -609,6 +611,14 @@ class MachineManager(QObject):
return False return False
return Util.parseBool(global_container_stack.quality.getMetaDataEntry("is_experimental", False)) return Util.parseBool(global_container_stack.quality.getMetaDataEntry("is_experimental", False))
@pyqtProperty(str, notify=activeIntentChanged)
def activeIntentCategory(self):
if not self._active_container_stack:
return ""
intent_category = self._active_container_stack.intent.getMetaDataEntry("intent_category")
return intent_category
## Returns whether there is anything unsupported in the current set-up. ## Returns whether there is anything unsupported in the current set-up.
# #
# The current set-up signifies the global stack and all extruder stacks, # The current set-up signifies the global stack and all extruder stacks,

View file

@ -47,6 +47,7 @@ EMPTY_INTENT_CONTAINER_ID = "empty_intent"
empty_intent_container = copy.deepcopy(empty_container) empty_intent_container = copy.deepcopy(empty_container)
empty_intent_container.setMetaDataEntry("id", EMPTY_INTENT_CONTAINER_ID) empty_intent_container.setMetaDataEntry("id", EMPTY_INTENT_CONTAINER_ID)
empty_intent_container.setMetaDataEntry("type", "intent") empty_intent_container.setMetaDataEntry("type", "intent")
empty_intent_container.setMetaDataEntry("intent_category", "default")
empty_intent_container.setName(catalog.i18nc("@info:No intent profile selected", "Default")) empty_intent_container.setName(catalog.i18nc("@info:No intent profile selected", "Default"))

View file

@ -4,7 +4,7 @@ name = Smooth (TEST INTENT)
definition = ultimaker3 definition = ultimaker3
[metadata] [metadata]
setting_version = 8 setting_version = 9
type = intent type = intent
intent_category = smooth intent_category = smooth
quality_type = draft quality_type = draft

View file

@ -4,7 +4,7 @@ name = Strong (TEST INTENT)
definition = ultimaker3 definition = ultimaker3
[metadata] [metadata]
setting_version = 8 setting_version = 9
type = intent type = intent
intent_category = engineering intent_category = engineering
quality_type = draft quality_type = draft

View file

@ -0,0 +1,65 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import UM 1.2 as UM
// The labelBar shows a set of labels that are evenly spaced from oneother.
// The first item is aligned to the left, the last is aligned to the right.
// It's intended to be used together with RadioCheckBar. As such, it needs
// to know what the used itemSize is, so it can ensure the labels are aligned correctly.
Item
{
id: base
property var model: null
property string modelKey: ""
property int itemSize: 14
height: childrenRect.height
RowLayout
{
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
Repeater
{
id: repeater
model: base.model
Item
{
Layout.fillWidth: true
Layout.maximumWidth: Math.round(index + 1 === repeater.count || repeater.count <= 1 ? itemSize : base.width / (repeater.count - 1))
height: label.height
Label
{
id: label
text: model[modelKey]
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
height: contentHeight
anchors
{
// Some magic to ensure that the items are aligned properly.
// We want the following:
// First item should be aligned to the left, no margin.
// Last item should be aligned to the right, no margin.
// The middle item(s) should be aligned to the center of the "item" it's showing (hence half the itemsize as offset).
// We want the center of the label to align with the center of the item, so we negatively offset by half the contentWidth
right: index + 1 === repeater.count ? parent.right: undefined
left: index + 1 === repeater.count || index === 0 ? undefined: parent.left
leftMargin: Math.round((itemSize - contentWidth) * 0.5)
// For some reason, the last label in the row gets misaligned with Qt 5.10. This lines seems to
// fix it.
verticalCenter: parent.verticalCenter
}
}
}
}
}
}

View file

@ -1,36 +0,0 @@
// 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: "Build plate"
property var buildPlateModel: CuraApplication.getBuildPlateModel()
Instantiator
{
model: menu.buildPlateModel
MenuItem {
text: model.name
checkable: true
checked: model.name == Cura.MachineManager.globalVariantName
exclusiveGroup: group
onTriggered: {
Cura.MachineManager.setGlobalVariant(model.container_node);
}
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
ExclusiveGroup { id: group }
}

View file

@ -1,8 +1,8 @@
// 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.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 2.0 import QtQuick.Controls 2.3
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM import UM 1.2 as UM
@ -99,12 +99,14 @@ Cura.ExpandablePopup
left: extruderIcon.right left: extruderIcon.right
leftMargin: UM.Theme.getSize("default_margin").width leftMargin: UM.Theme.getSize("default_margin").width
top: typeAndBrandNameLabel.bottom top: typeAndBrandNameLabel.bottom
right: parent.right
rightMargin: UM.Theme.getSize("default_margin").width
} }
} }
} }
} }
//Placeholder text if there is a configuration to select but no materials (so we can't show the materials per extruder). // Placeholder text if there is a configuration to select but no materials (so we can't show the materials per extruder).
Label Label
{ {
text: catalog.i18nc("@label", "Select configuration") text: catalog.i18nc("@label", "Select configuration")

View file

@ -1,64 +0,0 @@
// Copyright (c) 2019 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.6 as Cura
Menu
{
id: menu
title: "Intent"
property int extruderIndex: 0
Cura.IntentCategoryModel
{
id: intentCategoryModel
}
Instantiator
{
model: intentCategoryModel
MenuItem //Section header.
{
text: model.name
enabled: false
checked: false
property var per_category_intents: Cura.IntentModel
{
id: intentModel
intentCategory: model.intent_category
}
property var intent_instantiator: Instantiator
{
model: intentModel
MenuItem
{
text: model.name
checkable: true
checked: false
Binding on checked
{
when: Cura.MachineManager.activeStack != null
value: Cura.MachineManager.activeStack.intent.metaData["intent_category"] == intentModel.intentCategory && Cura.MachineManager.activeStack.quality.metaData["quality_type"] == model.quality_type
}
exclusiveGroup: group
onTriggered: Cura.IntentManager.selectIntent(intentModel.intentCategory, model.quality_type)
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
ExclusiveGroup { id: group }
}

View file

@ -1,84 +0,0 @@
// 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
Instantiator
{
model: Cura.QualityProfilesDropDownMenuModel
MenuItem
{
text:
{
var full_text = (model.layer_height != "") ? model.name + " - " + model.layer_height + model.layer_height_unit : model.name
full_text += model.is_experimental ? " - " + catalog.i18nc("@label", "Experimental") : ""
return full_text
}
checkable: true
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
exclusiveGroup: group
onTriggered: Cura.MachineManager.setQualityGroup(model.quality_group)
visible: model.available
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
MenuSeparator
{
id: customSeparator
visible: Cura.CustomQualityProfilesDropDownMenuModel.count > 0
}
Instantiator
{
id: customProfileInstantiator
model: Cura.CustomQualityProfilesDropDownMenuModel
Connections
{
target: Cura.CustomQualityProfilesDropDownMenuModel
onModelReset: customSeparator.visible = Cura.CustomQualityProfilesDropDownMenuModel.count > 0
}
MenuItem
{
text: model.name
checkable: true
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
exclusiveGroup: group
onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group)
}
onObjectAdded:
{
customSeparator.visible = model.count > 0;
menu.insertItem(index, object);
}
onObjectRemoved:
{
customSeparator.visible = model.count > 0;
menu.removeItem(object);
}
}
ExclusiveGroup { id: group; }
MenuSeparator { id: profileMenuSeparator }
MenuItem { action: Cura.Actions.addProfile }
MenuItem { action: Cura.Actions.updateProfile }
MenuItem { action: Cura.Actions.resetProfile }
MenuSeparator { }
MenuItem { action: Cura.Actions.manageProfiles }
}

View file

@ -57,14 +57,6 @@ Menu
onObjectRemoved: base.removeItem(object) onObjectRemoved: base.removeItem(object)
} }
// TODO Only show in dev mode. Remove check when feature ready
BuildplateMenu
{
title: catalog.i18nc("@title:menu", "&Build plate")
visible: CuraSDKVersion == "dev" && Cura.MachineManager.hasVariantBuildplates
}
ProfileMenu { title: catalog.i18nc("@title:settings", "&Profile") }
MenuSeparator { } MenuSeparator { }
MenuItem { action: Cura.Actions.configureSettingVisibility } MenuItem { action: Cura.Actions.configureSettingVisibility }

View file

@ -1,12 +1,12 @@
// 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.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 2.0 import QtQuick.Controls 2.3
import QtQuick.Controls 1.1 as OldControls import QtQuick.Controls 1.4 as OldControls
import UM 1.3 as UM import UM 1.3 as UM
import Cura 1.0 as Cura import Cura 1.6 as Cura
Item Item
@ -18,18 +18,6 @@ Item
property var extrudersModel: CuraApplication.getExtrudersModel() property var extrudersModel: CuraApplication.getExtrudersModel()
// Profile selector row
GlobalProfileSelector
{
id: globalProfileRow
anchors
{
top: parent.top
left: parent.left
right: parent.right
margins: parent.padding
}
}
Item Item
{ {
id: intent id: intent
@ -37,7 +25,7 @@ Item
anchors anchors
{ {
top: globalProfileRow.bottom top: parent.top
topMargin: UM.Theme.getSize("default_margin").height topMargin: UM.Theme.getSize("default_margin").height
left: parent.left left: parent.left
leftMargin: parent.padding leftMargin: parent.padding
@ -47,7 +35,7 @@ Item
Label Label
{ {
id: intentLabel id: profileLabel
anchors anchors
{ {
top: parent.top top: parent.top
@ -55,25 +43,130 @@ Item
left: parent.left left: parent.left
right: intentSelection.left right: intentSelection.left
} }
text: catalog.i18nc("@label", "Intent") text: catalog.i18nc("@label", "Profile")
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
OldControls.ToolButton
Button
{ {
id: intentSelection id: intentSelection
text: Cura.MachineManager.activeStack != null ? Cura.MachineManager.activeStack.intent.name : "" onClicked: menu.opened ? menu.close() : menu.open()
tooltip: text text: generateActiveQualityText()
height: UM.Theme.getSize("print_setup_big_item").height
width: UM.Theme.getSize("print_setup_big_item").width
anchors.right: parent.right
style: UM.Theme.styles.print_setup_header_button
activeFocusOnPress: true
menu: Cura.IntentMenu { extruderIndex: Cura.ExtruderManager.activeExtruderIndex } anchors.right: parent.right
width: UM.Theme.getSize("print_setup_big_item").width
height: textLabel.contentHeight + 2 * UM.Theme.getSize("narrow_margin").height
hoverEnabled: true
baselineOffset: null // If we don't do this, there is a binding loop. WHich is a bit weird, since we override the contentItem anyway...
contentItem: Label
{
id: textLabel
text: intentSelection.text
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: intentSelection.verticalCenter
height: contentHeight
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
}
background: Rectangle
{
id: backgroundItem
border.color: intentSelection.hovered ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border")
border.width: UM.Theme.getSize("default_lining").width
radius: UM.Theme.getSize("default_radius").width
color: UM.Theme.getColor("main_background")
}
function generateActiveQualityText()
{
var result = ""
if(Cura.MachineManager.activeIntentCategory != "default")
{
result += Cura.MachineManager.activeIntentCategory + " - "
}
result += Cura.MachineManager.activeQualityOrQualityChangesName
if (Cura.MachineManager.isActiveQualityExperimental)
{
result += " (Experimental)"
}
if (Cura.MachineManager.isActiveQualitySupported)
{
if (Cura.MachineManager.activeQualityLayerHeight > 0)
{
result += " <font color=\"" + UM.Theme.getColor("text_detail") + "\">"
result += " - "
result += Cura.MachineManager.activeQualityLayerHeight + "mm"
result += "</font>"
}
}
return result
}
UM.SimpleButton
{
id: customisedSettings
visible: Cura.MachineManager.hasUserSettings
width: UM.Theme.getSize("print_setup_icon").width
height: UM.Theme.getSize("print_setup_icon").height
anchors.verticalCenter: parent.verticalCenter
anchors.right: downArrow.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button");
iconSource: UM.Theme.getIcon("star")
onClicked:
{
forceActiveFocus();
Cura.Actions.manageProfiles.trigger()
}
onEntered:
{
var content = catalog.i18nc("@tooltip", "Some setting/override values are different from the values stored in the profile.\n\nClick to open the profile manager.")
base.showTooltip(intent, Qt.point(-UM.Theme.getSize("default_margin").width, 0), content)
}
onExited: base.hideTooltip()
}
UM.RecolorImage
{
id: downArrow
source: UM.Theme.getIcon("arrow_bottom")
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
anchors
{
right: parent.right
verticalCenter: parent.verticalCenter
rightMargin: UM.Theme.getSize("default_margin").width
}
color: UM.Theme.getColor("setting_control_button")
}
} }
QualitiesWithIntentMenu
{
id: menu
y: intentSelection.y + intentSelection.height
x: intentSelection.x
width: intentSelection.width
}
} }
UM.TabRow UM.TabRow
@ -143,7 +236,7 @@ Item
{ {
anchors anchors
{ {
top: tabBar.visible ? tabBar.bottom : globalProfileRow.bottom top: tabBar.visible ? tabBar.bottom : intent.bottom
topMargin: -UM.Theme.getSize("default_lining").width topMargin: -UM.Theme.getSize("default_lining").width
left: parent.left left: parent.left
leftMargin: parent.padding leftMargin: parent.padding

View file

@ -1,100 +0,0 @@
// 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.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.2
import UM 1.2 as UM
import Cura 1.0 as Cura
Item
{
id: globalProfileRow
height: childrenRect.height
Label
{
id: globalProfileLabel
anchors
{
top: parent.top
bottom: parent.bottom
left: parent.left
right: globalProfileSelection.left
}
text: catalog.i18nc("@label", "Profile")
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
}
ToolButton
{
id: globalProfileSelection
text: generateActiveQualityText()
width: UM.Theme.getSize("print_setup_big_item").width
height: UM.Theme.getSize("print_setup_big_item").height
anchors
{
top: parent.top
right: parent.right
}
tooltip: Cura.MachineManager.activeQualityOrQualityChangesName
style: UM.Theme.styles.print_setup_header_button
activeFocusOnPress: true
menu: Cura.ProfileMenu { }
function generateActiveQualityText()
{
var result = Cura.MachineManager.activeQualityOrQualityChangesName
if (Cura.MachineManager.isActiveQualityExperimental)
{
result += " (Experimental)"
}
if (Cura.MachineManager.isActiveQualitySupported)
{
if (Cura.MachineManager.activeQualityLayerHeight > 0)
{
result += " <font color=\"" + UM.Theme.getColor("text_detail") + "\">"
result += " - "
result += Cura.MachineManager.activeQualityLayerHeight + "mm"
result += "</font>"
}
}
return result
}
UM.SimpleButton
{
id: customisedSettings
visible: Cura.MachineManager.hasUserSettings
width: UM.Theme.getSize("print_setup_icon").width
height: UM.Theme.getSize("print_setup_icon").height
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Math.round(UM.Theme.getSize("setting_preferences_button_margin").width - UM.Theme.getSize("thick_margin").width)
color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button");
iconSource: UM.Theme.getIcon("star")
onClicked:
{
forceActiveFocus();
Cura.Actions.manageProfiles.trigger()
}
onEntered:
{
var content = catalog.i18nc("@tooltip","Some setting/override values are different from the values stored in the profile.\n\nClick to open the profile manager.")
base.showTooltip(globalProfileRow, Qt.point(-UM.Theme.getSize("default_margin").width, 0), content)
}
onExited: base.hideTooltip()
}
}
}

View file

@ -0,0 +1,52 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import UM 1.2 as UM
import Cura 1.6 as Cura
Button
{
// This is a work around for a qml issue. Since the default button uses a private implementation for contentItem
// (the so called IconText), which handles the mnemonic conversion (aka; ensuring that &Button) text property
// is rendered with the B underlined. Since we're also forced to mix controls 1.0 and 2.0 actions together,
// we need a special property for the text of the label if we do want it to be rendered correclty, but don't want
// another shortcut to be added (which will cause for "QQuickAction::event: Ambiguous shortcut overload: " to
// happen.
property string labelText: ""
id: button
hoverEnabled: true
background: Rectangle
{
id: backgroundRectangle
border.width: 1
border.color: button.checked ? UM.Theme.getColor("setting_control_border_highlight") : "transparent"
color: button.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent"
radius: UM.Theme.getSize("action_button_radius").width
}
// Workarround to ensure that the mnemonic highlighting happens correctly
function replaceText(txt)
{
var index = txt.indexOf("&")
if(index >= 0)
{
txt = txt.replace(txt.substr(index, 2), ("<u>" + txt.substr(index + 1, 1) + "</u>"))
}
return txt
}
contentItem: Label
{
id: textLabel
text: button.text != "" ? replaceText(button.text) : replaceText(button.labelText)
height: contentHeight
verticalAlignment: Text.AlignVCenter
anchors.left: button.left
anchors.leftMargin: UM.Theme.getSize("wide_margin").width
renderType: Text.NativeRendering
}
}

View file

@ -0,0 +1,228 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import UM 1.2 as UM
import Cura 1.6 as Cura
Popup
{
id: popup
implicitWidth: 400
property var dataModel: Cura.IntentCategoryModel {}
property int defaultMargin: UM.Theme.getSize("default_margin").width
property color backgroundColor: UM.Theme.getColor("main_background")
property color borderColor: UM.Theme.getColor("lining")
topPadding: UM.Theme.getSize("narrow_margin").height
rightPadding: UM.Theme.getSize("default_lining").width
leftPadding: UM.Theme.getSize("default_lining").width
padding: 0
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
background: Cura.RoundedRectangle
{
color: backgroundColor
border.width: UM.Theme.getSize("default_lining").width
border.color: borderColor
cornerSide: Cura.RoundedRectangle.Direction.Down
}
ButtonGroup
{
id: buttonGroup
exclusive: true
onClicked: popup.visible = false
}
contentItem: Column
{
// This repeater adds the intent labels
Repeater
{
model: dataModel
delegate: Item
{
// We need to set it like that, otherwise we'd have to set the sub model with model: model.qualities
// Which obviously won't work due to naming conflicts.
property variant subItemModel: model.qualities
height: childrenRect.height
anchors
{
left: parent.left
right: parent.right
}
Label
{
id: headerLabel
text: model.name
renderType: Text.NativeRendering
height: visible ? contentHeight: 0
enabled: false
visible: qualitiesList.visibleChildren.length > 0
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
}
Column
{
id: qualitiesList
anchors.top: headerLabel.bottom
anchors.left: parent.left
anchors.right: parent.right
// We set it by means of a binding, since then we can use the when condition, which we need to
// prevent a binding loop.
Binding
{
target: parent
property: "height"
value: parent.childrenRect.height
when: parent.visibleChildren.length > 0
}
// Add the qualities that belong to the intent
Repeater
{
visible: false
model: subItemModel
MenuButton
{
id: button
onClicked: Cura.IntentManager.selectIntent(model.intent_category, model.quality_type)
width: parent.width
checkable: true
visible: model.available
text: model.name + " - " + model.layer_height + " mm"
checked:
{
if(Cura.MachineManager.hasCustomQuality)
{
// When user created profile is active, no quality tickbox should be active.
return false
}
return Cura.MachineManager.activeQualityType == model.quality_type && Cura.MachineManager.activeIntentCategory == model.intent_category
}
ButtonGroup.group: buttonGroup
}
}
}
}
}
Rectangle
{
height: 1
anchors.left: parent.left
anchors.right: parent.right
color: borderColor
}
MenuButton
{
labelText: Cura.Actions.addProfile.text
anchors.left: parent.left
anchors.right: parent.right
enabled: Cura.Actions.addProfile.enabled
onClicked:
{
Cura.Actions.addProfile.trigger()
popup.visible = false
}
}
MenuButton
{
labelText: Cura.Actions.updateProfile.text
anchors.left: parent.left
anchors.right: parent.right
enabled: Cura.Actions.updateProfile.enabled
onClicked:
{
popup.visible = false
Cura.Actions.updateProfile.trigger()
}
}
MenuButton
{
text: catalog.i18nc("@action:button", "Discard current changes")
anchors.left: parent.left
anchors.right: parent.right
enabled: Cura.MachineManager.hasUserSettings
onClicked:
{
popup.visible = false
Cura.ContainerManager.clearUserContainers()
}
}
Rectangle
{
height: 1
anchors.left: parent.left
anchors.right: parent.right
color: borderColor
}
MenuButton
{
id: manageProfilesButton
text: Cura.Actions.manageProfiles.text
anchors
{
left: parent.left
right: parent.right
}
height: textLabel.contentHeight + 2 * UM.Theme.getSize("narrow_margin").height
contentItem: Item
{
width: manageProfilesButton.width
Label
{
id: textLabel
text: manageProfilesButton.text
height: contentHeight
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("narrow_margin").width
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
}
Label
{
id: shortcutLabel
text: Cura.Actions.manageProfiles.shortcut
height: contentHeight
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
}
}
onClicked:
{
popup.visible = false
Cura.Actions.manageProfiles.trigger()
}
}
// spacer
Item
{
width: 2
height: UM.Theme.getSize("default_radius").width
}
}
}

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.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -20,10 +20,16 @@ RowLayout
{ {
if (Cura.MachineManager.activeStack) if (Cura.MachineManager.activeStack)
{ {
var text = Cura.MachineManager.activeQualityOrQualityChangesName var text = ""
if(Cura.MachineManager.activeIntentCategory != "default")
{
text += Cura.MachineManager.activeIntentCategory + " - "
}
text += Cura.MachineManager.activeQualityOrQualityChangesName
if (!Cura.MachineManager.hasNotSupportedQuality) if (!Cura.MachineManager.hasNotSupportedQuality)
{ {
text += " " + layerHeight.properties.value + "mm" text += " - " + layerHeight.properties.value + "mm"
text += Cura.MachineManager.isActiveQualityExperimental ? " - " + catalog.i18nc("@label", "Experimental") : "" text += Cura.MachineManager.isActiveQualityExperimental ? " - " + catalog.i18nc("@label", "Experimental") : ""
} }
return text return text

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.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
@ -27,7 +27,6 @@ Item
Column Column
{ {
width: parent.width - 2 * parent.padding
spacing: UM.Theme.getSize("wide_margin").height spacing: UM.Theme.getSize("wide_margin").height
anchors anchors

View file

@ -1,17 +1,14 @@
// 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.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls 2.3 as Controls2
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.6 as Cura
//
// Quality profile
//
Item Item
{ {
id: qualityRow id: qualityRow
@ -20,436 +17,111 @@ Item
property real labelColumnWidth: Math.round(width / 3) property real labelColumnWidth: Math.round(width / 3)
property real settingsColumnWidth: width - labelColumnWidth property real settingsColumnWidth: width - labelColumnWidth
Timer
{
id: qualitySliderChangeTimer
interval: 50
running: false
repeat: false
onTriggered:
{
var item = Cura.QualityProfilesDropDownMenuModel.getItem(qualitySlider.value);
Cura.MachineManager.activeQualityGroup = item.quality_group;
}
}
Component.onCompleted: qualityModel.update()
Connections
{
target: Cura.QualityProfilesDropDownMenuModel
onItemsChanged: qualityModel.update()
}
Connections {
target: base
onVisibleChanged:
{
// update needs to be called when the widgets are visible, otherwise the step width calculation
// will fail because the width of an invisible item is 0.
if (visible)
{
qualityModel.update();
}
}
}
ListModel
{
id: qualityModel
property var totalTicks: 0
property var availableTotalTicks: 0
property var existingQualityProfile: 0
property var qualitySliderActiveIndex: 0
property var qualitySliderStepWidth: 0
property var qualitySliderAvailableMin: 0
property var qualitySliderAvailableMax: 0
property var qualitySliderMarginRight: 0
function update ()
{
reset()
var availableMin = -1
var availableMax = -1
for (var i = 0; i < Cura.QualityProfilesDropDownMenuModel.rowCount(); i++)
{
var qualityItem = Cura.QualityProfilesDropDownMenuModel.getItem(i)
// Add each quality item to the UI quality model
qualityModel.append(qualityItem)
// Set selected value
if (Cura.MachineManager.activeQualityType == qualityItem.quality_type)
{
// set to -1 when switching to user created profile so all ticks are clickable
if (Cura.MachineManager.hasCustomQuality)
{
qualityModel.qualitySliderActiveIndex = -1
}
else
{
qualityModel.qualitySliderActiveIndex = i
}
qualityModel.existingQualityProfile = 1
}
// Set min available
if (qualityItem.available && availableMin == -1)
{
availableMin = i
}
// Set max available
if (qualityItem.available)
{
availableMax = i
}
}
// Set total available ticks for active slider part
if (availableMin != -1)
{
qualityModel.availableTotalTicks = availableMax - availableMin + 1
}
// Calculate slider values
calculateSliderStepWidth(qualityModel.totalTicks)
calculateSliderMargins(availableMin, availableMax, qualityModel.totalTicks)
qualityModel.qualitySliderAvailableMin = availableMin
qualityModel.qualitySliderAvailableMax = availableMax
}
function calculateSliderStepWidth (totalTicks)
{
// Do not use Math.round otherwise the tickmarks won't be aligned
qualityModel.qualitySliderStepWidth = totalTicks != 0 ?
((settingsColumnWidth - UM.Theme.getSize("print_setup_slider_handle").width) / (totalTicks)) : 0
}
function calculateSliderMargins (availableMin, availableMax, totalTicks)
{
if (availableMin == -1 || (availableMin == 0 && availableMax == 0))
{
// Do not use Math.round otherwise the tickmarks won't be aligned
qualityModel.qualitySliderMarginRight = settingsColumnWidth / 2
}
else if (availableMin == availableMax)
{
// Do not use Math.round otherwise the tickmarks won't be aligned
qualityModel.qualitySliderMarginRight = (totalTicks - availableMin) * qualitySliderStepWidth
}
else
{
// Do not use Math.round otherwise the tickmarks won't be aligned
qualityModel.qualitySliderMarginRight = (totalTicks - availableMax) * qualitySliderStepWidth
}
}
function reset () {
qualityModel.clear()
qualityModel.availableTotalTicks = 0
qualityModel.existingQualityProfile = 0
// check, the ticks count cannot be less than zero
qualityModel.totalTicks = Math.max(0, Cura.QualityProfilesDropDownMenuModel.rowCount() - 1)
}
}
// Here are the elements that are shown in the left column // Here are the elements that are shown in the left column
Item
{
id: titleRow
width: labelColumnWidth
height: childrenRect.height
Cura.IconWithText Column
{
anchors
{ {
id: qualityRowTitle left: parent.left
source: UM.Theme.getIcon("category_layer_height") right: parent.right
text: catalog.i18nc("@label", "Layer Height")
font: UM.Theme.getFont("medium")
anchors.left: parent.left
anchors.right: customisedSettings.left
} }
UM.SimpleButton spacing: UM.Theme.getSize("default_margin").height
{
id: customisedSettings
visible: Cura.SimpleModeSettingsManager.isProfileCustomized || Cura.MachineManager.hasCustomQuality Controls2.ButtonGroup
height: visible ? UM.Theme.getSize("print_setup_icon").height : 0 {
width: height id: activeProfileButtonGroup
exclusive: true
onClicked: Cura.IntentManager.selectIntent(button.modelData.intent_category, button.modelData.quality_type)
}
Item
{
height: childrenRect.height
anchors anchors
{ {
left: parent.left
right: parent.right right: parent.right
rightMargin: UM.Theme.getSize("default_margin").width }
leftMargin: UM.Theme.getSize("default_margin").width Cura.IconWithText
verticalCenter: parent.verticalCenter {
id: profileLabel
source: UM.Theme.getIcon("category_layer_height")
text: catalog.i18nc("@label", "Profiles")
font: UM.Theme.getFont("medium")
width: labelColumnWidth
} }
color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button") Cura.LabelBar
iconSource: UM.Theme.getIcon("reset") {
id: labelbar
anchors
{
left: profileLabel.right
right: parent.right
}
onClicked: model: Cura.QualityProfilesDropDownMenuModel
{ modelKey: "layer_height"
// if the current profile is user-created, switch to a built-in quality
Cura.MachineManager.resetToUseDefaultQuality()
} }
onEntered:
{
var tooltipContent = catalog.i18nc("@tooltip","You have modified some profile settings. If you want to change these go to custom mode.")
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, 0), tooltipContent)
}
onExited: base.hideTooltip()
} }
}
// Show titles for the each quality slider ticks
Item
{
anchors.left: speedSlider.left
anchors.top: speedSlider.bottom
height: childrenRect.height
Repeater Repeater
{ {
model: qualityModel model: Cura.IntentCategoryModel {}
Item
Label
{ {
anchors.verticalCenter: parent.verticalCenter anchors
anchors.top: parent.top
// The height has to be set manually, otherwise it's not automatically calculated in the repeater
height: UM.Theme.getSize("default_margin").height
color: (Cura.MachineManager.activeMachine != null && Cura.QualityProfilesDropDownMenuModel.getItem(index).available) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
text:
{ {
var result = "" left: parent.left
if(Cura.MachineManager.activeMachine != null) right: parent.right
{ }
result = Cura.QualityProfilesDropDownMenuModel.getItem(index).layer_height height: intentCategoryLabel.height
if(result == undefined) Label
{
id: intentCategoryLabel
text: model.name
width: labelColumnWidth - UM.Theme.getSize("section_icon").width
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("section_icon").width + UM.Theme.getSize("narrow_margin").width
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
elide: Text.ElideRight
}
Cura.RadioCheckbar
{
anchors
{
left: intentCategoryLabel.right
right: parent.right
}
dataModel: model["qualities"]
buttonGroup: activeProfileButtonGroup
function checkedFunction(modelItem)
{
if(Cura.MachineManager.hasCustomQuality)
{ {
result = ""; // When user created profile is active, no quality tickbox should be active.
return false
} }
else
if(modelItem === null)
{ {
result = Number(Math.round(result + "e+2") + "e-2"); //Round to 2 decimals. Javascript makes this difficult... return false
if (result == undefined || result != result) //Parse failure.
{
result = "";
}
} }
return Cura.MachineManager.activeQualityType == modelItem.quality_type && Cura.MachineManager.activeIntentCategory == modelItem.intent_category
} }
return result
}
x: isCheckedFunction: checkedFunction
{
// Make sure the text aligns correctly with each tick
if (qualityModel.totalTicks == 0)
{
// If there is only one tick, align it centrally
return Math.round(((settingsColumnWidth) - width) / 2)
}
else if (index == 0)
{
return Math.round(settingsColumnWidth / qualityModel.totalTicks) * index
}
else if (index == qualityModel.totalTicks)
{
return Math.round(settingsColumnWidth / qualityModel.totalTicks) * index - width
}
else
{
return Math.round((settingsColumnWidth / qualityModel.totalTicks) * index - (width / 2))
}
}
font: UM.Theme.getFont("default")
}
}
}
// Print speed slider
// Two sliders are created, one at the bottom with the unavailable qualities
// and the other at the top with the available quality profiles and so the handle to select them.
Item
{
id: speedSlider
height: childrenRect.height
anchors
{
left: titleRow.right
right: parent.right
verticalCenter: titleRow.verticalCenter
}
// Draw unavailable slider
Slider
{
id: unavailableSlider
width: parent.width
height: qualitySlider.height // Same height as the slider that is on top
updateValueWhileDragging : false
tickmarksEnabled: true
minimumValue: 0
// maximumValue must be greater than minimumValue to be able to see the handle. While the value is strictly
// speaking not always correct, it seems to have the correct behavior (switching from 0 available to 1 available)
maximumValue: qualityModel.totalTicks
stepSize: 1
style: SliderStyle
{
//Draw Unvailable line
groove: Item
{
Rectangle
{
height: UM.Theme.getSize("print_setup_slider_groove").height
width: control.width - UM.Theme.getSize("print_setup_slider_handle").width
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color: UM.Theme.getColor("quality_slider_unavailable")
}
}
handle: Item {}
tickmarks: Repeater
{
id: qualityRepeater
model: qualityModel.totalTicks > 0 ? qualityModel : 0
Rectangle
{
color: Cura.QualityProfilesDropDownMenuModel.getItem(index).available ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
implicitWidth: UM.Theme.getSize("print_setup_slider_tickmarks").width
implicitHeight: UM.Theme.getSize("print_setup_slider_tickmarks").height
anchors.verticalCenter: parent.verticalCenter
// Do not use Math.round otherwise the tickmarks won't be aligned
x: ((UM.Theme.getSize("print_setup_slider_handle").width / 2) - (implicitWidth / 2) + (qualityModel.qualitySliderStepWidth * index))
radius: Math.round(implicitWidth / 2)
}
} }
} }
// Create a mouse area on top of the unavailable profiles to show a specific tooltip
MouseArea
{
anchors.fill: parent
hoverEnabled: true
enabled: !Cura.MachineManager.hasCustomQuality
onEntered:
{
var tooltipContent = catalog.i18nc("@tooltip", "This quality profile is not available for your current material and nozzle configuration. Please change these to enable this quality profile.")
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, customisedSettings.height), tooltipContent)
}
onExited: base.hideTooltip()
}
}
// Draw available slider
Slider
{
id: qualitySlider
width: qualityModel.qualitySliderStepWidth * (qualityModel.availableTotalTicks - 1) + UM.Theme.getSize("print_setup_slider_handle").width
height: UM.Theme.getSize("print_setup_slider_handle").height // The handle is the widest element of the slider
enabled: qualityModel.totalTicks > 0 && !Cura.SimpleModeSettingsManager.isProfileCustomized
visible: qualityModel.availableTotalTicks > 0
updateValueWhileDragging : false
anchors
{
right: parent.right
rightMargin: qualityModel.qualitySliderMarginRight
}
minimumValue: qualityModel.qualitySliderAvailableMin >= 0 ? qualityModel.qualitySliderAvailableMin : 0
// maximumValue must be greater than minimumValue to be able to see the handle. While the value is strictly
// speaking not always correct, it seems to have the correct behavior (switching from 0 available to 1 available)
maximumValue: qualityModel.qualitySliderAvailableMax >= 1 ? qualityModel.qualitySliderAvailableMax : 1
stepSize: 1
value: qualityModel.qualitySliderActiveIndex
style: SliderStyle
{
// Draw Available line
groove: Item
{
Rectangle
{
height: UM.Theme.getSize("print_setup_slider_groove").height
width: control.width - UM.Theme.getSize("print_setup_slider_handle").width
anchors.verticalCenter: parent.verticalCenter
// Do not use Math.round otherwise the tickmarks won't be aligned
x: UM.Theme.getSize("print_setup_slider_handle").width / 2
color: UM.Theme.getColor("quality_slider_available")
}
}
handle: Rectangle
{
id: qualityhandleButton
color: UM.Theme.getColor("primary")
implicitWidth: UM.Theme.getSize("print_setup_slider_handle").width
implicitHeight: implicitWidth
radius: Math.round(implicitWidth / 2)
visible: !Cura.SimpleModeSettingsManager.isProfileCustomized && !Cura.MachineManager.hasCustomQuality && qualityModel.existingQualityProfile
}
}
onValueChanged:
{
// only change if an active machine is set and the slider is visible at all.
if (Cura.MachineManager.activeMachine != null && visible)
{
// prevent updating during view initializing. Trigger only if the value changed by user
if (qualitySlider.value != qualityModel.qualitySliderActiveIndex && qualityModel.qualitySliderActiveIndex != -1)
{
// start updating with short delay
qualitySliderChangeTimer.start()
}
}
}
// This mouse area is only used to capture the onHover state and don't propagate it to the unavailable mouse area
MouseArea
{
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
enabled: !Cura.MachineManager.hasCustomQuality
}
}
// This mouse area will only take the mouse events and show a tooltip when the profile in use is
// a user created profile
MouseArea
{
anchors.fill: parent
hoverEnabled: true
visible: Cura.MachineManager.hasCustomQuality
onEntered:
{
var tooltipContent = catalog.i18nc("@tooltip", "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab")
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, customisedSettings.height), tooltipContent)
}
onExited: base.hideTooltip()
} }
} }
} }

View file

@ -0,0 +1,153 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import UM 1.1 as UM
Item
{
id: base
property ButtonGroup buttonGroup: null
property color activeColor: UM.Theme.getColor("primary")
property color inactiveColor: UM.Theme.getColor("slider_groove")
property color defaultItemColor: UM.Theme.getColor("small_button_active")
property int checkboxSize: UM.Theme.getSize("radio_button").height * 0.75
property int inactiveMarkerSize: 2 * barSize
property int barSize: UM.Theme.getSize("slider_groove_radius").height
property var isCheckedFunction // Function that accepts the modelItem and returns if the item should be active.
implicitWidth: 200
implicitHeight: checkboxSize
property var dataModel: null
// The horizontal inactive bar that sits behind the buttons
Rectangle
{
id: inactiveLine
color: inactiveColor
height: barSize
anchors
{
left: buttonBar.left
right: buttonBar.right
leftMargin: (checkboxSize - inactiveMarkerSize) / 2
rightMargin: (checkboxSize - inactiveMarkerSize) / 2
verticalCenter: parent.verticalCenter
}
}
RowLayout
{
id: buttonBar
anchors.top: parent.top
height: checkboxSize
width: parent.width
spacing: 0
Repeater
{
id: repeater
model: base.dataModel
height: checkboxSize
Item
{
Layout.fillWidth: true
Layout.fillHeight: true
// The last item of the repeater needs to be shorter, as we don't need another part to fit
// the horizontal bar. The others should essentially not be limited.
Layout.maximumWidth: index + 1 === repeater.count ? activeComponent.width: 200000000
property bool isEnabled: model.available
// The horizontal bar between the checkable options.
// Note that the horizontal bar points towards the previous item.
Rectangle
{
property Item previousItem: repeater.itemAt(index - 1)
height: barSize
width: buttonBar.width / (repeater.count - 1) - activeComponent.width - 2
color: defaultItemColor
anchors
{
right: activeComponent.left
verticalCenter: parent.verticalCenter
}
visible: previousItem !== null && previousItem.isEnabled && isEnabled
}
Loader
{
id: activeComponent
sourceComponent: isEnabled? checkboxComponent : disabledComponent
width: checkboxSize
property var modelItem: model
}
}
}
}
Component
{
id: disabledComponent
Item
{
height: checkboxSize
width: checkboxSize
Rectangle
{
// This can (and should) be done wiht a verticalCenter. For some reason it does work in QtCreator
// but not when using the exact same QML in Cura.
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
height: inactiveMarkerSize
width: inactiveMarkerSize
radius: width / 2
color: inactiveColor
}
}
}
Component
{
id: checkboxComponent
CheckBox
{
id: checkbox
ButtonGroup.group: buttonGroup
width: checkboxSize
height: checkboxSize
property var modelData: modelItem
checked: isCheckedFunction(modelItem)
indicator: Rectangle
{
height: checkboxSize
width: checkboxSize
radius: width / 2
border.color: defaultItemColor
Rectangle
{
anchors
{
margins: 3
fill: parent
}
radius: width / 2
color: activeColor
visible: checkbox.checked
}
}
}
}
}

View file

@ -20,7 +20,6 @@ SettingItem
textRole: "value" textRole: "value"
anchors.fill: parent anchors.fill: parent
highlighted: base.hovered
onActivated: onActivated:
{ {

View file

@ -14,40 +14,34 @@ import Cura 1.1 as Cura
ComboBox ComboBox
{ {
id: control id: control
property bool highlighted: False
states: [
State
{
name: "disabled"
when: !control.enabled
PropertyChanges { target: backgroundRectangle.border; color: UM.Theme.getColor("setting_control_disabled_border")}
PropertyChanges { target: backgroundRectangle; color: UM.Theme.getColor("setting_control_disabled")}
PropertyChanges { target: contentLabel; color: UM.Theme.getColor("setting_control_disabled_text")}
},
State
{
name: "highlighted"
when: control.hovered || control.activeFocus
PropertyChanges { target: backgroundRectangle.border; color: UM.Theme.getColor("setting_control_border_highlight") }
PropertyChanges { target: backgroundRectangle; color: UM.Theme.getColor("setting_control_highlight")}
}
]
background: Rectangle background: Rectangle
{ {
color: id: backgroundRectangle
{ color: UM.Theme.getColor("setting_control")
if (!enabled)
{
return UM.Theme.getColor("setting_control_disabled")
}
if (control.hovered || control.activeFocus || control.highlighted)
{
return UM.Theme.getColor("setting_control_highlight")
}
return UM.Theme.getColor("setting_control")
}
radius: UM.Theme.getSize("setting_control_radius").width radius: UM.Theme.getSize("setting_control_radius").width
border.width: UM.Theme.getSize("default_lining").width border.width: UM.Theme.getSize("default_lining").width
border.color: border.color: UM.Theme.getColor("setting_control_border")
{
if (!enabled)
{
return UM.Theme.getColor("setting_control_disabled_border")
}
if (control.hovered || control.activeFocus || control.highlighted)
{
return UM.Theme.getColor("setting_control_border_highlight")
}
return UM.Theme.getColor("setting_control_border")
}
} }
indicator: UM.RecolorImage indicator: UM.RecolorImage
@ -67,6 +61,7 @@ ComboBox
contentItem: Label contentItem: Label
{ {
id: contentLabel
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -76,7 +71,7 @@ ComboBox
textFormat: Text.PlainText textFormat: Text.PlainText
renderType: Text.NativeRendering renderType: Text.NativeRendering
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") color: UM.Theme.getColor("setting_control_text")
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }

View file

@ -44,6 +44,7 @@ def test_qualityNode_machine_1(container_registry):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)): with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
node = QualityNode("quality_1", material_node) node = QualityNode("quality_1", material_node)
assert len(node.intents) == 2 assert len(node.intents) == 3
assert "intent_3" in node.intents assert "intent_3" in node.intents
assert "intent_4" in node.intents assert "intent_4" in node.intents
assert "empty_intent" in node.intents

View file

@ -28,6 +28,14 @@ def quality_container():
return container return container
@pytest.fixture
def intent_container():
container = InstanceContainer(container_id="intent container")
container.setMetaDataEntry("type", "intent")
return container
@pytest.fixture @pytest.fixture
def quality_changes_container(): def quality_changes_container():
container = InstanceContainer(container_id="quality changes container") container = InstanceContainer(container_id="quality changes container")
@ -44,7 +52,8 @@ def test_createMachineWithUnknownDefinition(application, container_registry):
assert mocked_config_error.addFaultyContainers.called_with("NOPE") assert mocked_config_error.addFaultyContainers.called_with("NOPE")
def test_createMachine(application, container_registry, definition_container, global_variant, material_instance_container, quality_container, quality_changes_container): def test_createMachine(application, container_registry, definition_container, global_variant, material_instance_container,
quality_container, intent_container, quality_changes_container):
variant_manager = MagicMock(name = "Variant Manager") variant_manager = MagicMock(name = "Variant Manager")
quality_manager = MagicMock(name = "Quality Manager") quality_manager = MagicMock(name = "Quality Manager")
global_variant_node = MagicMock( name = "global variant node") global_variant_node = MagicMock( name = "global variant node")
@ -61,6 +70,7 @@ def test_createMachine(application, container_registry, definition_container, gl
application.getQualityManager = MagicMock(return_value = quality_manager) application.getQualityManager = MagicMock(return_value = quality_manager)
application.empty_material_container = material_instance_container application.empty_material_container = material_instance_container
application.empty_quality_container = quality_container application.empty_quality_container = quality_container
application.empty_intent_container = intent_container
application.empty_quality_changes_container = quality_changes_container application.empty_quality_changes_container = quality_changes_container
application.empty_variant_container = global_variant application.empty_variant_container = global_variant
@ -83,9 +93,11 @@ def test_createMachine(application, container_registry, definition_container, gl
assert machine.variant == global_variant assert machine.variant == global_variant
def test_createExtruderStack(application, definition_container, global_variant, material_instance_container, quality_container, quality_changes_container): def test_createExtruderStack(application, definition_container, global_variant, material_instance_container,
quality_container, intent_container, quality_changes_container):
application.empty_material_container = material_instance_container application.empty_material_container = material_instance_container
application.empty_quality_container = quality_container application.empty_quality_container = quality_container
application.empty_intent_container = intent_container
application.empty_quality_changes_container = quality_changes_container application.empty_quality_changes_container = quality_changes_container
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value=application)): with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value=application)):
extruder_stack = CuraStackBuilder.createExtruderStack("Whatever", definition_container, "meh", 0, global_variant, material_instance_container, quality_container) extruder_stack = CuraStackBuilder.createExtruderStack("Whatever", definition_container, "meh", 0, global_variant, material_instance_container, quality_container)