1// Copyright (c) 2017 Ultimaker B.V. 2// Cura is released under the terms of the LGPLv3 or higher. 3 4import QtQuick 2.7 5import QtQuick.Controls 1.4 6import QtQuick.Dialogs 1.2 7 8import UM 1.2 as UM 9import Cura 1.0 as Cura 10 11import ".." // Access to ReadOnlyTextArea.qml 12 13TabView 14{ 15 id: base 16 17 property QtObject properties 18 property var currentMaterialNode: null 19 20 property bool editingEnabled: false 21 property string currency: UM.Preferences.getValue("cura/currency") ? UM.Preferences.getValue("cura/currency") : "€" 22 property real firstColumnWidth: (width * 0.50) | 0 23 property real secondColumnWidth: (width * 0.40) | 0 24 property string containerId: "" 25 property var materialPreferenceValues: UM.Preferences.getValue("cura/material_settings") ? JSON.parse(UM.Preferences.getValue("cura/material_settings")) : {} 26 property var materialManagementModel: CuraApplication.getMaterialManagementModel() 27 28 property double spoolLength: calculateSpoolLength() 29 property real costPerMeter: calculateCostPerMeter() 30 31 signal resetSelectedMaterial() 32 33 property bool reevaluateLinkedMaterials: false 34 property string linkedMaterialNames: 35 { 36 if (reevaluateLinkedMaterials) 37 { 38 reevaluateLinkedMaterials = false; 39 } 40 if (!base.containerId || !base.editingEnabled || !base.currentMaterialNode) 41 { 42 return "" 43 } 44 var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode, true); 45 if (linkedMaterials.length == 0) 46 { 47 return "" 48 } 49 return linkedMaterials; 50 } 51 52 function getApproximateDiameter(diameter) 53 { 54 return Math.round(diameter); 55 } 56 57 // This trick makes sure to make all fields lose focus so their onEditingFinished will be triggered 58 // and modified values will be saved. This can happen when a user changes a value and then closes the 59 // dialog directly. 60 // 61 // Please note that somehow this callback is ONLY triggered when visible is false. 62 onVisibleChanged: 63 { 64 if (!visible) 65 { 66 base.focus = false; 67 } 68 } 69 70 Tab 71 { 72 title: catalog.i18nc("@title", "Information") 73 74 anchors.margins: UM.Theme.getSize("default_margin").width 75 76 ScrollView 77 { 78 id: scrollView 79 anchors.fill: parent 80 horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff 81 flickableItem.flickableDirection: Flickable.VerticalFlick 82 frameVisible: true 83 84 property real columnWidth: (viewport.width * 0.5 - UM.Theme.getSize("default_margin").width) | 0 85 86 Flow 87 { 88 id: containerGrid 89 90 x: UM.Theme.getSize("default_margin").width 91 y: UM.Theme.getSize("default_lining").height 92 93 width: base.width 94 property real rowHeight: brandTextField.height + UM.Theme.getSize("default_lining").height 95 96 MessageDialog 97 { 98 id: confirmDiameterChangeDialog 99 100 icon: StandardIcon.Question; 101 title: catalog.i18nc("@title:window", "Confirm Diameter Change") 102 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)) 103 standardButtons: StandardButton.Yes | StandardButton.No 104 modality: Qt.ApplicationModal 105 106 property var new_diameter_value: null; 107 property var old_diameter_value: null; 108 property var old_approximate_diameter_value: null; 109 110 onYes: 111 { 112 base.setMetaDataEntry("approximate_diameter", old_approximate_diameter_value, getApproximateDiameter(new_diameter_value).toString()); 113 base.setMetaDataEntry("properties/diameter", properties.diameter, new_diameter_value); 114 // CURA-6868 Make sure to update the extruder to user a diameter-compatible material. 115 Cura.MachineManager.updateMaterialWithVariant() 116 base.resetSelectedMaterial() 117 } 118 119 onNo: 120 { 121 base.properties.diameter = old_diameter_value; 122 diameterSpinBox.value = Qt.binding(function() { return base.properties.diameter }) 123 } 124 125 onRejected: no() 126 } 127 128 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Display Name") } 129 ReadOnlyTextField 130 { 131 id: displayNameTextField; 132 width: scrollView.columnWidth; 133 text: properties.name; 134 readOnly: !base.editingEnabled; 135 onEditingFinished: base.updateMaterialDisplayName(properties.name, text) 136 } 137 138 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Brand") } 139 ReadOnlyTextField 140 { 141 id: brandTextField; 142 width: scrollView.columnWidth; 143 text: properties.brand; 144 readOnly: !base.editingEnabled; 145 onEditingFinished: base.updateMaterialBrand(properties.brand, text) 146 } 147 148 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Material Type") } 149 ReadOnlyTextField 150 { 151 id: materialTypeField; 152 width: scrollView.columnWidth; 153 text: properties.material; 154 readOnly: !base.editingEnabled; 155 onEditingFinished: base.updateMaterialType(properties.material, text) 156 } 157 158 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") } 159 Row 160 { 161 width: scrollView.columnWidth 162 height: parent.rowHeight 163 spacing: Math.round(UM.Theme.getSize("default_margin").width / 2) 164 165 // color indicator square 166 Rectangle 167 { 168 id: colorSelector 169 color: properties.color_code 170 171 width: Math.round(colorLabel.height * 0.75) 172 height: Math.round(colorLabel.height * 0.75) 173 border.width: UM.Theme.getSize("default_lining").height 174 175 anchors.verticalCenter: parent.verticalCenter 176 177 // open the color selection dialog on click 178 MouseArea 179 { 180 anchors.fill: parent 181 onClicked: colorDialog.open() 182 enabled: base.editingEnabled 183 } 184 } 185 186 // pretty color name text field 187 ReadOnlyTextField 188 { 189 id: colorLabel; 190 width: parent.width - colorSelector.width - parent.spacing 191 text: properties.color_name; 192 readOnly: !base.editingEnabled 193 onEditingFinished: base.setMetaDataEntry("color_name", properties.color_name, text) 194 } 195 196 // popup dialog to select a new color 197 // if successful it sets the properties.color_code value to the new color 198 ColorDialog 199 { 200 id: colorDialog 201 color: properties.color_code 202 onAccepted: base.setMetaDataEntry("color_code", properties.color_code, color) 203 } 204 } 205 206 Item { width: parent.width; height: UM.Theme.getSize("default_margin").height } 207 208 Label { width: parent.width; height: parent.rowHeight; font.bold: true; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Properties") } 209 210 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Density") } 211 ReadOnlySpinBox 212 { 213 id: densitySpinBox 214 width: scrollView.columnWidth 215 value: properties.density 216 decimals: 2 217 suffix: " g/cm³" 218 stepSize: 0.01 219 readOnly: !base.editingEnabled 220 221 onEditingFinished: base.setMetaDataEntry("properties/density", properties.density, value) 222 onValueChanged: updateCostPerMeter() 223 } 224 225 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Diameter") } 226 ReadOnlySpinBox 227 { 228 id: diameterSpinBox 229 width: scrollView.columnWidth 230 value: properties.diameter 231 decimals: 2 232 suffix: " mm" 233 stepSize: 0.01 234 readOnly: !base.editingEnabled 235 236 onEditingFinished: 237 { 238 // This does not use a SettingPropertyProvider, because we need to make the change to all containers 239 // which derive from the same base_file 240 var old_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "properties/diameter"); 241 var old_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter"); 242 var new_approximate_diameter = getApproximateDiameter(value); 243 if (new_approximate_diameter != Cura.ExtruderManager.getActiveExtruderStack().approximateMaterialDiameter) 244 { 245 confirmDiameterChangeDialog.old_diameter_value = old_diameter; 246 confirmDiameterChangeDialog.new_diameter_value = value; 247 confirmDiameterChangeDialog.old_approximate_diameter_value = old_approximate_diameter; 248 249 confirmDiameterChangeDialog.open() 250 } 251 else { 252 base.setMetaDataEntry("approximate_diameter", old_approximate_diameter, getApproximateDiameter(value).toString()); 253 base.setMetaDataEntry("properties/diameter", properties.diameter, value); 254 } 255 } 256 onValueChanged: updateCostPerMeter() 257 } 258 259 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament Cost") } 260 SpinBox 261 { 262 id: spoolCostSpinBox 263 width: scrollView.columnWidth 264 value: base.getMaterialPreferenceValue(properties.guid, "spool_cost") 265 prefix: base.currency + " " 266 decimals: 2 267 maximumValue: 100000000 268 269 onValueChanged: 270 { 271 base.setMaterialPreferenceValue(properties.guid, "spool_cost", parseFloat(value)) 272 updateCostPerMeter() 273 } 274 } 275 276 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament weight") } 277 SpinBox 278 { 279 id: spoolWeightSpinBox 280 width: scrollView.columnWidth 281 value: base.getMaterialPreferenceValue(properties.guid, "spool_weight", Cura.ContainerManager.getContainerMetaDataEntry(properties.container_id, "properties/weight")) 282 suffix: " g" 283 stepSize: 100 284 decimals: 0 285 maximumValue: 10000 286 287 onValueChanged: 288 { 289 base.setMaterialPreferenceValue(properties.guid, "spool_weight", parseFloat(value)) 290 updateCostPerMeter() 291 } 292 } 293 294 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament length") } 295 Label 296 { 297 width: scrollView.columnWidth 298 text: "~ %1 m".arg(Math.round(base.spoolLength)) 299 verticalAlignment: Qt.AlignVCenter 300 height: parent.rowHeight 301 } 302 303 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Cost per Meter") } 304 Label 305 { 306 width: scrollView.columnWidth 307 text: "~ %1 %2/m".arg(base.costPerMeter.toFixed(2)).arg(base.currency) 308 verticalAlignment: Qt.AlignVCenter 309 height: parent.rowHeight 310 } 311 312 Item { width: parent.width; height: UM.Theme.getSize("default_margin").height; visible: unlinkMaterialButton.visible } 313 Label 314 { 315 width: 2 * scrollView.columnWidth 316 verticalAlignment: Qt.AlignVCenter 317 text: catalog.i18nc("@label", "This material is linked to %1 and shares some of its properties.").arg(base.linkedMaterialNames) 318 wrapMode: Text.WordWrap 319 visible: unlinkMaterialButton.visible 320 } 321 Button 322 { 323 id: unlinkMaterialButton 324 text: catalog.i18nc("@label", "Unlink Material") 325 visible: base.linkedMaterialNames != "" 326 onClicked: 327 { 328 Cura.ContainerManager.unlinkMaterial(base.currentMaterialNode) 329 base.reevaluateLinkedMaterials = true 330 } 331 } 332 333 Item { width: parent.width; height: UM.Theme.getSize("default_margin").height } 334 335 Label { width: parent.width; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Description") } 336 337 ReadOnlyTextArea 338 { 339 text: properties.description; 340 width: 2 * scrollView.columnWidth 341 wrapMode: Text.WordWrap 342 343 readOnly: !base.editingEnabled; 344 345 onEditingFinished: base.setMetaDataEntry("description", properties.description, text) 346 } 347 348 Label { width: parent.width; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Adhesion Information") } 349 350 ReadOnlyTextArea 351 { 352 text: properties.adhesion_info; 353 width: 2 * scrollView.columnWidth 354 wrapMode: Text.WordWrap 355 356 readOnly: !base.editingEnabled; 357 358 onEditingFinished: base.setMetaDataEntry("adhesion_info", properties.adhesion_info, text) 359 } 360 361 Item { width: parent.width; height: UM.Theme.getSize("default_margin").height } 362 } 363 364 function updateCostPerMeter() 365 { 366 base.spoolLength = calculateSpoolLength(diameterSpinBox.value, densitySpinBox.value, spoolWeightSpinBox.value); 367 base.costPerMeter = calculateCostPerMeter(spoolCostSpinBox.value); 368 } 369 } 370 } 371 372 Tab 373 { 374 title: catalog.i18nc("@label", "Print settings") 375 anchors 376 { 377 leftMargin: UM.Theme.getSize("default_margin").width 378 topMargin: UM.Theme.getSize("default_margin").height 379 bottomMargin: UM.Theme.getSize("default_margin").height 380 rightMargin: 0 381 } 382 383 ScrollView 384 { 385 anchors.fill: parent; 386 387 ListView 388 { 389 model: UM.SettingDefinitionsModel 390 { 391 containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: "" 392 visibilityHandler: Cura.MaterialSettingsVisibilityHandler { } 393 expanded: ["*"] 394 } 395 396 delegate: UM.TooltipArea 397 { 398 width: childrenRect.width 399 height: childrenRect.height 400 text: model.description 401 Label 402 { 403 id: label 404 width: base.firstColumnWidth; 405 height: spinBox.height + UM.Theme.getSize("default_lining").height 406 text: model.label 407 elide: Text.ElideRight 408 verticalAlignment: Qt.AlignVCenter 409 } 410 ReadOnlySpinBox 411 { 412 id: spinBox 413 anchors.left: label.right 414 value: 415 { 416 // In case the setting is not in the material... 417 if (!isNaN(parseFloat(materialPropertyProvider.properties.value))) 418 { 419 return parseFloat(materialPropertyProvider.properties.value); 420 } 421 // ... we search in the variant, and if it is not there... 422 if (!isNaN(parseFloat(variantPropertyProvider.properties.value))) 423 { 424 return parseFloat(variantPropertyProvider.properties.value); 425 } 426 // ... then look in the definition container. 427 if (!isNaN(parseFloat(machinePropertyProvider.properties.value))) 428 { 429 return parseFloat(machinePropertyProvider.properties.value); 430 } 431 return 0; 432 } 433 width: base.secondColumnWidth 434 readOnly: !base.editingEnabled 435 suffix: " " + model.unit 436 maximumValue: 99999 437 decimals: model.unit == "mm" ? 2 : 0 438 439 onEditingFinished: materialPropertyProvider.setPropertyValue("value", value) 440 } 441 442 UM.ContainerPropertyProvider 443 { 444 id: materialPropertyProvider 445 containerId: base.containerId 446 watchedProperties: [ "value" ] 447 key: model.key 448 } 449 UM.ContainerPropertyProvider 450 { 451 id: variantPropertyProvider 452 containerId: Cura.MachineManager.activeStack.variant.id 453 watchedProperties: [ "value" ] 454 key: model.key 455 } 456 UM.ContainerPropertyProvider 457 { 458 id: machinePropertyProvider 459 containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: "" 460 watchedProperties: [ "value" ] 461 key: model.key 462 } 463 } 464 } 465 } 466 } 467 468 function calculateSpoolLength(diameter, density, spoolWeight) 469 { 470 if(!diameter) 471 { 472 diameter = properties.diameter; 473 } 474 if(!density) 475 { 476 density = properties.density; 477 } 478 if(!spoolWeight) 479 { 480 spoolWeight = base.getMaterialPreferenceValue(properties.guid, "spool_weight", Cura.ContainerManager.getContainerMetaDataEntry(properties.container_id, "properties/weight")); 481 } 482 483 if (diameter == 0 || density == 0 || spoolWeight == 0) 484 { 485 return 0; 486 } 487 var area = Math.PI * Math.pow(diameter / 2, 2); // in mm2 488 var volume = (spoolWeight / density); // in cm3 489 return volume / area; // in m 490 } 491 492 function calculateCostPerMeter(spoolCost) 493 { 494 if(!spoolCost) 495 { 496 spoolCost = base.getMaterialPreferenceValue(properties.guid, "spool_cost"); 497 } 498 499 if (spoolLength == 0) 500 { 501 return 0; 502 } 503 return spoolCost / spoolLength; 504 } 505 506 // Tiny convenience function to check if a value really changed before trying to set it. 507 function setMetaDataEntry(entry_name, old_value, new_value) 508 { 509 if (old_value != new_value) 510 { 511 Cura.ContainerManager.setContainerMetaDataEntry(base.currentMaterialNode, entry_name, new_value) 512 // make sure the UI properties are updated as well since we don't re-fetch the entire model here 513 // When the entry_name is something like properties/diameter, we take the last part of the entry_name 514 var list = entry_name.split("/") 515 var key = list[list.length - 1] 516 properties[key] = new_value 517 } 518 } 519 520 function setMaterialPreferenceValue(material_guid, entry_name, new_value) 521 { 522 if(!(material_guid in materialPreferenceValues)) 523 { 524 materialPreferenceValues[material_guid] = {}; 525 } 526 if(entry_name in materialPreferenceValues[material_guid] && materialPreferenceValues[material_guid][entry_name] == new_value) 527 { 528 // value has not changed 529 return; 530 } 531 if (entry_name in materialPreferenceValues[material_guid] && new_value.toString() == 0) 532 { 533 // no need to store a 0, that's the default, so remove it 534 materialPreferenceValues[material_guid].delete(entry_name); 535 if (!(materialPreferenceValues[material_guid])) 536 { 537 // remove empty map 538 materialPreferenceValues.delete(material_guid); 539 } 540 } 541 if (new_value.toString() != 0) 542 { 543 // store new value 544 materialPreferenceValues[material_guid][entry_name] = new_value; 545 } 546 547 // store preference 548 UM.Preferences.setValue("cura/material_settings", JSON.stringify(materialPreferenceValues)); 549 } 550 551 function getMaterialPreferenceValue(material_guid, entry_name, default_value) 552 { 553 if(material_guid in materialPreferenceValues && entry_name in materialPreferenceValues[material_guid]) 554 { 555 return materialPreferenceValues[material_guid][entry_name]; 556 } 557 default_value = default_value | 0; 558 return default_value; 559 } 560 561 // update the display name of the material 562 function updateMaterialDisplayName(old_name, new_name) 563 { 564 // don't change when new name is the same 565 if (old_name == new_name) 566 { 567 return 568 } 569 570 // update the values 571 base.materialManagementModel.setMaterialName(base.currentMaterialNode, new_name) 572 properties.name = new_name 573 } 574 575 // update the type of the material 576 function updateMaterialType(old_type, new_type) 577 { 578 base.setMetaDataEntry("material", old_type, new_type) 579 properties.material = new_type 580 } 581 582 // update the brand of the material 583 function updateMaterialBrand(old_brand, new_brand) 584 { 585 base.setMetaDataEntry("brand", old_brand, new_brand) 586 properties.brand = new_brand 587 } 588} 589