MaterialsView.qml 26 KB

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