MaterialsView.qml 24 KB

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