// Copyright (c) 2017 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 import UM 1.2 as UM import Cura 1.0 as Cura import ".." // Access to ReadOnlyTextArea.qml TabView { 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) { return "" } var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode, true); if (linkedMaterials.length == 0) { return "" } return linkedMaterials; } 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; } } Tab { title: catalog.i18nc("@title", "Information") anchors.margins: UM.Theme.getSize("default_margin").width ScrollView { id: scrollView anchors.fill: parent horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff flickableItem.flickableDirection: Flickable.VerticalFlick frameVisible: true property real columnWidth: (viewport.width * 0.5 - UM.Theme.getSize("default_margin").width) | 0 Flow { id: containerGrid x: UM.Theme.getSize("default_margin").width y: UM.Theme.getSize("default_lining").height width: base.width property real rowHeight: brandTextField.height + UM.Theme.getSize("default_lining").height MessageDialog { id: confirmDiameterChangeDialog icon: StandardIcon.Question; 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: StandardButton.Yes | StandardButton.No modality: Qt.ApplicationModal property var new_diameter_value: null; property var old_diameter_value: null; property var old_approximate_diameter_value: null; onYes: { 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() } onNo: { base.properties.diameter = old_diameter_value; diameterSpinBox.value = Qt.binding(function() { return base.properties.diameter }) } onRejected: no() } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Display Name") } ReadOnlyTextField { id: displayNameTextField; width: scrollView.columnWidth; text: properties.name; readOnly: !base.editingEnabled; onEditingFinished: base.updateMaterialDisplayName(properties.name, text) } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Brand") } ReadOnlyTextField { id: brandTextField; width: scrollView.columnWidth; text: properties.brand; readOnly: !base.editingEnabled; onEditingFinished: base.updateMaterialBrand(properties.brand, text) } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Material Type") } ReadOnlyTextField { id: materialTypeField; width: scrollView.columnWidth; text: properties.material; readOnly: !base.editingEnabled; onEditingFinished: base.updateMaterialType(properties.material, text) } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") } Row { width: scrollView.columnWidth height: parent.rowHeight spacing: Math.round(UM.Theme.getSize("default_margin").width / 2) // color indicator square Rectangle { id: colorSelector color: properties.color_code width: Math.round(colorLabel.height * 0.75) height: Math.round(colorLabel.height * 0.75) border.width: UM.Theme.getSize("default_lining").height anchors.verticalCenter: parent.verticalCenter // open the color selection dialog on click MouseArea { anchors.fill: parent onClicked: colorDialog.open() enabled: base.editingEnabled } } // pretty color name text field ReadOnlyTextField { id: colorLabel; width: parent.width - colorSelector.width - parent.spacing text: properties.color_name; readOnly: !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 ColorDialog { id: colorDialog color: properties.color_code onAccepted: base.setMetaDataEntry("color_code", properties.color_code, color) } } Item { width: parent.width; height: UM.Theme.getSize("default_margin").height } Label { width: parent.width; height: parent.rowHeight; font.bold: true; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Properties") } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Density") } ReadOnlySpinBox { id: densitySpinBox width: scrollView.columnWidth value: properties.density decimals: 2 suffix: " g/cm³" stepSize: 0.01 readOnly: !base.editingEnabled onEditingFinished: base.setMetaDataEntry("properties/density", properties.density, value) onValueChanged: updateCostPerMeter() } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Diameter") } ReadOnlySpinBox { id: diameterSpinBox width: scrollView.columnWidth value: properties.diameter decimals: 2 suffix: " mm" stepSize: 0.01 readOnly: !base.editingEnabled 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() } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament Cost") } SpinBox { id: spoolCostSpinBox width: scrollView.columnWidth value: base.getMaterialPreferenceValue(properties.guid, "spool_cost") prefix: base.currency + " " decimals: 2 maximumValue: 100000000 onValueChanged: { base.setMaterialPreferenceValue(properties.guid, "spool_cost", parseFloat(value)) updateCostPerMeter() } } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament weight") } SpinBox { id: spoolWeightSpinBox width: scrollView.columnWidth value: base.getMaterialPreferenceValue(properties.guid, "spool_weight", Cura.ContainerManager.getContainerMetaDataEntry(properties.container_id, "properties/weight")) suffix: " g" stepSize: 100 decimals: 0 maximumValue: 10000 onValueChanged: { base.setMaterialPreferenceValue(properties.guid, "spool_weight", parseFloat(value)) updateCostPerMeter() } } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament length") } Label { width: scrollView.columnWidth text: "~ %1 m".arg(Math.round(base.spoolLength)) verticalAlignment: Qt.AlignVCenter height: parent.rowHeight } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Cost per Meter") } Label { width: scrollView.columnWidth text: "~ %1 %2/m".arg(base.costPerMeter.toFixed(2)).arg(base.currency) verticalAlignment: Qt.AlignVCenter height: parent.rowHeight } Item { width: parent.width; height: UM.Theme.getSize("default_margin").height; visible: unlinkMaterialButton.visible } Label { width: 2 * scrollView.columnWidth verticalAlignment: Qt.AlignVCenter 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 } Button { id: unlinkMaterialButton text: catalog.i18nc("@label", "Unlink Material") visible: base.linkedMaterialNames != "" onClicked: { Cura.ContainerManager.unlinkMaterial(base.currentMaterialNode) base.reevaluateLinkedMaterials = true } } Item { width: parent.width; height: UM.Theme.getSize("default_margin").height } Label { width: parent.width; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Description") } ReadOnlyTextArea { text: properties.description; width: 2 * scrollView.columnWidth wrapMode: Text.WordWrap readOnly: !base.editingEnabled; onEditingFinished: base.setMetaDataEntry("description", properties.description, text) } Label { width: parent.width; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Adhesion Information") } ReadOnlyTextArea { text: properties.adhesion_info; width: 2 * scrollView.columnWidth wrapMode: Text.WordWrap readOnly: !base.editingEnabled; onEditingFinished: base.setMetaDataEntry("adhesion_info", properties.adhesion_info, text) } Item { width: parent.width; height: UM.Theme.getSize("default_margin").height } } function updateCostPerMeter() { base.spoolLength = calculateSpoolLength(diameterSpinBox.value, densitySpinBox.value, spoolWeightSpinBox.value); base.costPerMeter = calculateCostPerMeter(spoolCostSpinBox.value); } } } Tab { title: catalog.i18nc("@label", "Print settings") anchors { leftMargin: UM.Theme.getSize("default_margin").width topMargin: UM.Theme.getSize("default_margin").height bottomMargin: UM.Theme.getSize("default_margin").height rightMargin: 0 } ScrollView { anchors.fill: parent; ListView { 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 text: model.description Label { id: label width: base.firstColumnWidth; height: spinBox.height + UM.Theme.getSize("default_lining").height text: model.label elide: Text.ElideRight verticalAlignment: Qt.AlignVCenter } ReadOnlySpinBox { 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 readOnly: !base.editingEnabled suffix: " " + model.unit maximumValue: 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 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 } }