MaterialView.qml 24 KB

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