Cura/resources/qml/Preferences/Materials/MaterialsView.qml
2022-03-10 22:13:32 +01:00

738 lines
29 KiB
QML

// Copyright (c) 2022 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.15
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.3
import UM 1.5 as UM
import Cura 1.0 as Cura
import ".." // Access to ReadOnlyTextArea.qml
Item
{
id: base
property QtObject properties
property var currentMaterialNode: null
property bool editingEnabled: false
property string currency: UM.Preferences.getValue("cura/currency") ? UM.Preferences.getValue("cura/currency") : "€"
property real firstColumnWidth: (width * 0.50) | 0
property real secondColumnWidth: (width * 0.40) | 0
property string containerId: ""
property var materialPreferenceValues: UM.Preferences.getValue("cura/material_settings") ? JSON.parse(UM.Preferences.getValue("cura/material_settings")) : {}
property var materialManagementModel: CuraApplication.getMaterialManagementModel()
property double spoolLength: calculateSpoolLength()
property real costPerMeter: calculateCostPerMeter()
signal resetSelectedMaterial()
property bool reevaluateLinkedMaterials: false
property string linkedMaterialNames:
{
if (reevaluateLinkedMaterials)
{
reevaluateLinkedMaterials = false;
}
if (!base.containerId || !base.editingEnabled || !base.currentMaterialNode)
{
return "";
}
var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode, true);
if (linkedMaterials.length == 0)
{
return "";
}
return linkedMaterials.join(", ");
}
function getApproximateDiameter(diameter)
{
return Math.round(diameter);
}
// This trick makes sure to make all fields lose focus so their onEditingFinished will be triggered
// and modified values will be saved. This can happen when a user changes a value and then closes the
// dialog directly.
//
// Please note that somehow this callback is ONLY triggered when visible is false.
onVisibleChanged:
{
if (!visible)
{
base.focus = false;
}
}
UM.TabRow
{
id: pageSelectorTabRow
UM.TabRowButton
{
text: catalog.i18nc("@title", "Information")
property string activeView: "information" //To determine which page gets displayed.
}
UM.TabRowButton
{
text: catalog.i18nc("@label", "Print settings")
property string activeView: "settings"
}
}
Rectangle
{
color: UM.Theme.getColor("main_background")
anchors
{
top: pageSelectorTabRow.bottom
topMargin: -UM.Theme.getSize("default_lining").width
left: parent.left
right: parent.right
bottom: parent.bottom
}
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("thick_lining")
visible: base.hasCurrentItem
ScrollView
{
id: informationPage
anchors
{
fill: parent
topMargin: UM.Theme.getSize("thin_margin").height
bottomMargin: UM.Theme.getSize("thin_margin").height
leftMargin: UM.Theme.getSize("thin_margin").width
rightMargin: UM.Theme.getSize("thin_margin").width
}
ScrollBar.vertical: UM.ScrollBar
{
parent: informationPage
anchors
{
top: parent.top
right: parent.right
bottom: parent.bottom
}
}
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
clip: true
visible: pageSelectorTabRow.currentItem.activeView === "information"
property real columnWidth: Math.floor(width / 2 - UM.Theme.getSize("narrow_margin").width)
property real rowHeight: UM.Theme.getSize("setting_control").height
Column
{
width: informationPage.width
spacing: UM.Theme.getSize("narrow_margin").height
Cura.MessageDialog
{
id: confirmDiameterChangeDialog
title: catalog.i18nc("@title:window", "Confirm Diameter Change")
text: catalog.i18nc("@label (%1 is a number)", "The new filament diameter is set to %1 mm, which is not compatible with the current extruder. Do you wish to continue?".arg(new_diameter_value))
standardButtons: Dialog.Yes | Dialog.No
property var new_diameter_value: null
property var old_diameter_value: null
property var old_approximate_diameter_value: null
onAccepted:
{
base.setMetaDataEntry("approximate_diameter", old_approximate_diameter_value, getApproximateDiameter(new_diameter_value).toString());
base.setMetaDataEntry("properties/diameter", properties.diameter, new_diameter_value);
// CURA-6868 Make sure to update the extruder to user a diameter-compatible material.
Cura.MachineManager.updateMaterialWithVariant()
base.resetSelectedMaterial()
}
onRejected:
{
base.properties.diameter = old_diameter_value;
diameterSpinBox.value = Qt.binding(function() { return base.properties.diameter })
}
}
Row
{
spacing: UM.Theme.getSize("narrow_margin").width
UM.Label
{
height: informationPage.rowHeight
width: informationPage.columnWidth
text: catalog.i18nc("@label", "Display Name")
}
Cura.TextField
{
id: displayNameTextField;
width: informationPage.columnWidth;
text: properties.name;
enabled: base.editingEnabled;
onEditingFinished: base.updateMaterialDisplayName(properties.name, text)
}
}
Row
{
spacing: UM.Theme.getSize("narrow_margin").width
UM.Label
{
height: informationPage.rowHeight
width: informationPage.columnWidth
text: catalog.i18nc("@label", "Brand")
}
Cura.TextField
{
id: brandTextField
width: informationPage.columnWidth
text: properties.brand
enabled: base.editingEnabled
onEditingFinished: base.updateMaterialBrand(properties.brand, text)
}
}
Row
{
spacing: UM.Theme.getSize("narrow_margin").width
UM.Label
{
height: informationPage.rowHeight
width: informationPage.columnWidth
text: catalog.i18nc("@label", "Material Type")
}
Cura.TextField
{
id: materialTypeField
width: informationPage.columnWidth
text: properties.material
enabled: base.editingEnabled
onEditingFinished: base.updateMaterialType(properties.material, text)
}
}
Row
{
spacing: UM.Theme.getSize("narrow_margin").width
UM.Label
{
height: informationPage.rowHeight
width: informationPage.columnWidth
verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color")
}
Row
{
width: informationPage.columnWidth
spacing: Math.round(UM.Theme.getSize("default_margin").width / 2)
// color indicator square
Item
{
id: colorSelector
anchors.verticalCenter: parent.verticalCenter
width: childrenRect.width + 2 * UM.Theme.getSize("narrow_margin").width
height: childrenRect.height + 2 * UM.Theme.getSize("narrow_margin").height
Rectangle
{
color: properties.color_code
width: UM.Theme.getSize("icon_indicator").width
height: UM.Theme.getSize("icon_indicator").height
radius: width / 2
anchors.centerIn: parent
}
// open the color selection dialog on click
MouseArea
{
anchors.fill: parent
onClicked: colorDialog.open()
enabled: base.editingEnabled
}
}
// pretty color name text field
Cura.TextField
{
id: colorLabel;
width: parent.width - colorSelector.width - parent.spacing
text: properties.color_name;
enabled: base.editingEnabled
onEditingFinished: base.setMetaDataEntry("color_name", properties.color_name, text)
}
// popup dialog to select a new color
// if successful it sets the properties.color_code value to the new color
Cura.ColorDialog
{
id: colorDialog
title: catalog.i18nc("@title", "Material color picker")
color: properties.color_code
onAccepted: base.setMetaDataEntry("color_code", properties.color_code, color)
}
}
}
UM.Label
{
width: parent.width
height: parent.rowHeight
font: UM.Theme.getFont("default_bold")
verticalAlignment: Qt.AlignVCenter
text: catalog.i18nc("@label", "Properties")
}
Row
{
height: parent.rowHeight
spacing: UM.Theme.getSize("narrow_margin").width
UM.Label
{
height: informationPage.rowHeight
width: informationPage.columnWidth
text: catalog.i18nc("@label", "Density")
}
Cura.SpinBox
{
enabled: base.editingEnabled
id: densitySpinBox
width: informationPage.columnWidth
value: properties.density
decimals: 2
suffix: " g/cm³"
stepSize: 0.01
onEditingFinished: base.setMetaDataEntry("properties/density", properties.density, value)
onValueChanged: updateCostPerMeter()
}
}
Row
{
height: parent.rowHeight
spacing: UM.Theme.getSize("narrow_margin").width
UM.Label
{
height: informationPage.rowHeight
width: informationPage.columnWidth
text: catalog.i18nc("@label", "Diameter")
}
Cura.SpinBox
{
enabled: base.editingEnabled
id: diameterSpinBox
width: informationPage.columnWidth
value: properties.diameter
decimals: 2
suffix: " mm"
stepSize: 0.01
onEditingFinished:
{
// This does not use a SettingPropertyProvider, because we need to make the change to all containers
// which derive from the same base_file
var old_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "properties/diameter");
var old_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter");
var new_approximate_diameter = getApproximateDiameter(value);
if (new_approximate_diameter != Cura.ExtruderManager.getActiveExtruderStack().approximateMaterialDiameter)
{
confirmDiameterChangeDialog.old_diameter_value = old_diameter;
confirmDiameterChangeDialog.new_diameter_value = value;
confirmDiameterChangeDialog.old_approximate_diameter_value = old_approximate_diameter;
confirmDiameterChangeDialog.open()
}
else {
base.setMetaDataEntry("approximate_diameter", old_approximate_diameter, getApproximateDiameter(value).toString());
base.setMetaDataEntry("properties/diameter", properties.diameter, value);
}
}
onValueChanged: updateCostPerMeter()
}
}
Row
{
height: parent.rowHeight
spacing: UM.Theme.getSize("narrow_margin").width
UM.Label
{
height: informationPage.rowHeight
width: informationPage.columnWidth
text: catalog.i18nc("@label", "Filament Cost")
}
Cura.SpinBox
{
id: spoolCostSpinBox
width: informationPage.columnWidth
value: base.getMaterialPreferenceValue(properties.guid, "spool_cost")
to: 100000000
editable: true
prefix: base.currency + " "
decimals: 2
onValueChanged:
{
base.setMaterialPreferenceValue(properties.guid, "spool_cost", parseFloat(value))
updateCostPerMeter()
}
}
}
Row
{
height: parent.rowHeight
spacing: UM.Theme.getSize("narrow_margin").width
UM.Label
{
height: informationPage.rowHeight
width: informationPage.columnWidth
text: catalog.i18nc("@label", "Filament weight")
}
Cura.SpinBox
{
id: spoolWeightSpinBox
width: informationPage.columnWidth
value: base.getMaterialPreferenceValue(properties.guid, "spool_weight", Cura.ContainerManager.getContainerMetaDataEntry(properties.container_id, "properties/weight"))
stepSize: 100
to: 10000
editable: true
suffix: " g"
onValueChanged:
{
base.setMaterialPreferenceValue(properties.guid, "spool_weight", parseFloat(value))
updateCostPerMeter()
}
}
}
Row
{
height: parent.rowHeight
spacing: UM.Theme.getSize("narrow_margin").width
UM.Label
{
height: informationPage.rowHeight
width: informationPage.columnWidth
text: catalog.i18nc("@label", "Filament length")
}
UM.Label
{
width: informationPage.columnWidth
text: "~ %1 m".arg(Math.round(base.spoolLength))
height: informationPage.rowHeight
}
}
Row
{
height: parent.rowHeight
spacing: UM.Theme.getSize("narrow_margin").width
UM.Label
{
height: informationPage.rowHeight
width: informationPage.columnWidth
text: catalog.i18nc("@label", "Cost per Meter")
}
UM.Label
{
height: informationPage.rowHeight
width: informationPage.columnWidth
text: "~ %1 %2/m".arg(base.costPerMeter.toFixed(2)).arg(base.currency)
}
}
UM.Label
{
height: parent.rowHeight
width: informationPage.width
text: catalog.i18nc("@label", "This material is linked to %1 and shares some of its properties.").arg(base.linkedMaterialNames)
wrapMode: Text.WordWrap
visible: unlinkMaterialButton.visible
}
Cura.SecondaryButton
{
id: unlinkMaterialButton
text: catalog.i18nc("@label", "Unlink Material")
visible: base.linkedMaterialNames != ""
onClicked:
{
Cura.ContainerManager.unlinkMaterial(base.currentMaterialNode)
base.reevaluateLinkedMaterials = true
}
}
UM.Label
{
width: informationPage.width
height: parent.rowHeight
text: catalog.i18nc("@label", "Description")
}
ReadOnlyTextArea
{
text: properties.description
width: informationPage.width
height: 0.4 * informationPage.width
wrapMode: Text.WordWrap
readOnly: !base.editingEnabled
onEditingFinished: base.setMetaDataEntry("description", properties.description, text)
}
UM.Label
{
width: informationPage.width
height: parent.rowHeight
text: catalog.i18nc("@label", "Adhesion Information")
}
ReadOnlyTextArea
{
text: properties.adhesion_info
width: informationPage.width
height: 0.4 * informationPage.width
wrapMode: Text.WordWrap
readOnly: !base.editingEnabled
onEditingFinished: base.setMetaDataEntry("adhesion_info", properties.adhesion_info, text)
}
}
}
Column
{
visible: pageSelectorTabRow.currentItem.activeView === "settings"
spacing: UM.Theme.getSize("narrow_margin").height
anchors.fill: parent
anchors.topMargin: UM.Theme.getSize("thin_margin").height
anchors.bottomMargin: UM.Theme.getSize("thin_margin").height
anchors.leftMargin: UM.Theme.getSize("thin_margin").width
anchors.rightMargin: UM.Theme.getSize("thin_margin").width
ScrollBar.vertical: UM.ScrollBar {}
clip: true
Repeater
{
model: UM.SettingDefinitionsModel
{
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
visibilityHandler: Cura.MaterialSettingsVisibilityHandler { }
expanded: ["*"]
}
delegate: UM.TooltipArea
{
width: childrenRect.width
height: childrenRect.height
UM.TooltipArea
{
anchors.fill: parent
text: model.description
}
UM.Label
{
id: label
width: base.firstColumnWidth;
height: spinBox.height + UM.Theme.getSize("default_lining").height
text: model.label
elide: Text.ElideRight
verticalAlignment: Qt.AlignVCenter
}
Cura.SpinBox
{
id: spinBox
anchors.left: label.right
value:
{
// In case the setting is not in the material...
if (!isNaN(parseFloat(materialPropertyProvider.properties.value)))
{
return parseFloat(materialPropertyProvider.properties.value);
}
// ... we search in the variant, and if it is not there...
if (!isNaN(parseFloat(variantPropertyProvider.properties.value)))
{
return parseFloat(variantPropertyProvider.properties.value);
}
// ... then look in the definition container.
if (!isNaN(parseFloat(machinePropertyProvider.properties.value)))
{
return parseFloat(machinePropertyProvider.properties.value);
}
return 0;
}
width: base.secondColumnWidth
suffix: " " + model.unit
to: 99999
decimals: model.unit == "mm" ? 2 : 0
onEditingFinished: materialPropertyProvider.setPropertyValue("value", value)
}
UM.ContainerPropertyProvider
{
id: materialPropertyProvider
containerId: base.containerId
watchedProperties: [ "value" ]
key: model.key
}
UM.ContainerPropertyProvider
{
id: variantPropertyProvider
containerId: Cura.MachineManager.activeStack.variant.id
watchedProperties: [ "value" ]
key: model.key
}
UM.ContainerPropertyProvider
{
id: machinePropertyProvider
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
watchedProperties: ["value"]
key: model.key
}
}
}
}
}
function updateCostPerMeter()
{
base.spoolLength = calculateSpoolLength(diameterSpinBox.value, densitySpinBox.value, spoolWeightSpinBox.value);
base.costPerMeter = calculateCostPerMeter(spoolCostSpinBox.value);
}
function calculateSpoolLength(diameter, density, spoolWeight)
{
if(!diameter)
{
diameter = properties.diameter;
}
if(!density)
{
density = properties.density;
}
if(!spoolWeight)
{
spoolWeight = base.getMaterialPreferenceValue(properties.guid, "spool_weight", Cura.ContainerManager.getContainerMetaDataEntry(properties.container_id, "properties/weight"));
}
if (diameter == 0 || density == 0 || spoolWeight == 0)
{
return 0;
}
var area = Math.PI * Math.pow(diameter / 2, 2); // in mm2
var volume = (spoolWeight / density); // in cm3
return volume / area; // in m
}
function calculateCostPerMeter(spoolCost)
{
if(!spoolCost)
{
spoolCost = base.getMaterialPreferenceValue(properties.guid, "spool_cost");
}
if (spoolLength == 0)
{
return 0;
}
return spoolCost / spoolLength;
}
// Tiny convenience function to check if a value really changed before trying to set it.
function setMetaDataEntry(entry_name, old_value, new_value)
{
if (old_value != new_value)
{
Cura.ContainerManager.setContainerMetaDataEntry(base.currentMaterialNode, entry_name, new_value)
// make sure the UI properties are updated as well since we don't re-fetch the entire model here
// When the entry_name is something like properties/diameter, we take the last part of the entry_name
var list = entry_name.split("/")
var key = list[list.length - 1]
properties[key] = new_value
}
}
function setMaterialPreferenceValue(material_guid, entry_name, new_value)
{
if(!(material_guid in materialPreferenceValues))
{
materialPreferenceValues[material_guid] = {};
}
if(entry_name in materialPreferenceValues[material_guid] && materialPreferenceValues[material_guid][entry_name] == new_value)
{
// value has not changed
return;
}
if (entry_name in materialPreferenceValues[material_guid] && new_value.toString() == 0)
{
// no need to store a 0, that's the default, so remove it
materialPreferenceValues[material_guid].delete(entry_name);
if (!(materialPreferenceValues[material_guid]))
{
// remove empty map
materialPreferenceValues.delete(material_guid);
}
}
if (new_value.toString() != 0)
{
// store new value
materialPreferenceValues[material_guid][entry_name] = new_value;
}
// store preference
UM.Preferences.setValue("cura/material_settings", JSON.stringify(materialPreferenceValues));
}
function getMaterialPreferenceValue(material_guid, entry_name, default_value)
{
if(material_guid in materialPreferenceValues && entry_name in materialPreferenceValues[material_guid])
{
return materialPreferenceValues[material_guid][entry_name];
}
default_value = default_value | 0;
return default_value;
}
// update the display name of the material
function updateMaterialDisplayName(old_name, new_name)
{
// don't change when new name is the same
if (old_name == new_name)
{
return
}
// update the values
base.materialManagementModel.setMaterialName(base.currentMaterialNode, new_name)
properties.name = new_name
}
// update the type of the material
function updateMaterialType(old_type, new_type)
{
base.setMetaDataEntry("material", old_type, new_type)
properties.material = new_type
}
// update the brand of the material
function updateMaterialBrand(old_brand, new_brand)
{
base.setMetaDataEntry("brand", old_brand, new_brand)
properties.brand = new_brand
}
}