MaterialsView.qml 24 KB

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