MaterialsView.qml 24 KB

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