MaterialsView.qml 24 KB

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