LayerView.qml 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. // Copyright (c) 2015 Ultimaker B.V.
  2. // Cura is released under the terms of the AGPLv3 or higher.
  3. import QtQuick 2.2
  4. import QtQuick.Controls 1.2
  5. import QtQuick.Layouts 1.1
  6. import QtQuick.Controls.Styles 1.1
  7. import UM 1.0 as UM
  8. import Cura 1.0 as Cura
  9. Item
  10. {
  11. id: base
  12. width: {
  13. if (UM.LayerView.compatibilityMode) {
  14. return UM.Theme.getSize("layerview_menu_size_compatibility").width;
  15. } else {
  16. return UM.Theme.getSize("layerview_menu_size").width;
  17. }
  18. }
  19. height: {
  20. if (UM.LayerView.compatibilityMode) {
  21. return UM.Theme.getSize("layerview_menu_size_compatibility").height;
  22. } else {
  23. return UM.Theme.getSize("layerview_menu_size").height + UM.LayerView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
  24. }
  25. }
  26. property var buttonTarget: {
  27. var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
  28. return base.mapFromItem(parent.parent, parent.buttonTarget.x, parent.buttonTarget.y);
  29. }
  30. UM.PointingRectangle {
  31. id: layerViewMenu
  32. anchors.left: parent.left
  33. anchors.top: parent.top
  34. width: parent.width
  35. height: parent.height
  36. z: slider.z - 1
  37. color: UM.Theme.getColor("tool_panel_background")
  38. borderWidth: UM.Theme.getSize("default_lining").width
  39. borderColor: UM.Theme.getColor("lining")
  40. target: parent.buttonTarget
  41. arrowSize: UM.Theme.getSize("default_arrow").width
  42. ColumnLayout {
  43. id: view_settings
  44. property var extruder_opacities: UM.Preferences.getValue("layerview/extruder_opacities").split("|")
  45. property bool show_travel_moves: UM.Preferences.getValue("layerview/show_travel_moves")
  46. property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
  47. property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
  48. property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
  49. // if we are in compatibility mode, we only show the "line type"
  50. property bool show_legend: UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type") == 1
  51. property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
  52. property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
  53. anchors.top: parent.top
  54. anchors.topMargin: UM.Theme.getSize("default_margin").height
  55. anchors.left: parent.left
  56. anchors.leftMargin: UM.Theme.getSize("default_margin").width
  57. spacing: UM.Theme.getSize("layerview_row_spacing").height
  58. Label
  59. {
  60. id: layersLabel
  61. anchors.left: parent.left
  62. text: catalog.i18nc("@label","View Mode: Layers")
  63. font.bold: true
  64. color: UM.Theme.getColor("text")
  65. }
  66. Label
  67. {
  68. id: spaceLabel
  69. anchors.left: parent.left
  70. text: " "
  71. font.pointSize: 0.5
  72. }
  73. Label
  74. {
  75. id: layerViewTypesLabel
  76. anchors.left: parent.left
  77. text: catalog.i18nc("@label","Color scheme")
  78. visible: !UM.LayerView.compatibilityMode
  79. Layout.fillWidth: true
  80. color: UM.Theme.getColor("text")
  81. }
  82. ListModel // matches LayerView.py
  83. {
  84. id: layerViewTypes
  85. }
  86. Component.onCompleted:
  87. {
  88. layerViewTypes.append({
  89. text: catalog.i18nc("@label:listbox", "Material Color"),
  90. type_id: 0
  91. })
  92. layerViewTypes.append({
  93. text: catalog.i18nc("@label:listbox", "Line Type"),
  94. type_id: 1 // these ids match the switching in the shader
  95. })
  96. }
  97. ComboBox
  98. {
  99. id: layerTypeCombobox
  100. anchors.left: parent.left
  101. Layout.fillWidth: true
  102. Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
  103. model: layerViewTypes
  104. visible: !UM.LayerView.compatibilityMode
  105. style: UM.Theme.styles.combobox
  106. onActivated:
  107. {
  108. UM.Preferences.setValue("layerview/layer_view_type", index);
  109. }
  110. Component.onCompleted:
  111. {
  112. currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
  113. updateLegends(currentIndex);
  114. }
  115. function updateLegends(type_id)
  116. {
  117. // update visibility of legends
  118. view_settings.show_legend = UM.LayerView.compatibilityMode || (type_id == 1);
  119. }
  120. }
  121. Label
  122. {
  123. id: compatibilityModeLabel
  124. anchors.left: parent.left
  125. text: catalog.i18nc("@label","Compatibility Mode")
  126. visible: UM.LayerView.compatibilityMode
  127. Layout.fillWidth: true
  128. Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
  129. Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
  130. }
  131. Label
  132. {
  133. id: space2Label
  134. anchors.left: parent.left
  135. text: " "
  136. font.pointSize: 0.5
  137. }
  138. Connections {
  139. target: UM.Preferences
  140. onPreferenceChanged:
  141. {
  142. layerTypeCombobox.currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
  143. layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex);
  144. view_settings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
  145. view_settings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
  146. view_settings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
  147. view_settings.show_skin = UM.Preferences.getValue("layerview/show_skin");
  148. view_settings.show_infill = UM.Preferences.getValue("layerview/show_infill");
  149. view_settings.only_show_top_layers = UM.Preferences.getValue("view/only_show_top_layers");
  150. view_settings.top_layer_count = UM.Preferences.getValue("view/top_layer_count");
  151. }
  152. }
  153. Repeater {
  154. model: Cura.ExtrudersModel{}
  155. CheckBox {
  156. checked: view_settings.extruder_opacities[index] > 0.5 || view_settings.extruder_opacities[index] == undefined || view_settings.extruder_opacities[index] == ""
  157. onClicked: {
  158. view_settings.extruder_opacities[index] = checked ? 1.0 : 0.0
  159. UM.Preferences.setValue("layerview/extruder_opacities", view_settings.extruder_opacities.join("|"));
  160. }
  161. text: model.name
  162. visible: !UM.LayerView.compatibilityMode
  163. enabled: index + 1 <= 4
  164. Rectangle {
  165. anchors.verticalCenter: parent.verticalCenter
  166. anchors.right: parent.right
  167. width: UM.Theme.getSize("layerview_legend_size").width
  168. height: UM.Theme.getSize("layerview_legend_size").height
  169. color: model.color
  170. border.width: UM.Theme.getSize("default_lining").width
  171. border.color: UM.Theme.getColor("lining")
  172. visible: !view_settings.show_legend
  173. }
  174. Layout.fillWidth: true
  175. Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
  176. Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
  177. style: UM.Theme.styles.checkbox
  178. }
  179. }
  180. Repeater {
  181. model: ListModel {
  182. id: typesLegenModel
  183. Component.onCompleted:
  184. {
  185. typesLegenModel.append({
  186. label: catalog.i18nc("@label", "Show Travels"),
  187. initialValue: view_settings.show_travel_moves,
  188. preference: "layerview/show_travel_moves",
  189. colorId: "layerview_move_combing"
  190. });
  191. typesLegenModel.append({
  192. label: catalog.i18nc("@label", "Show Helpers"),
  193. initialValue: view_settings.show_helpers,
  194. preference: "layerview/show_helpers",
  195. colorId: "layerview_support"
  196. });
  197. typesLegenModel.append({
  198. label: catalog.i18nc("@label", "Show Shell"),
  199. initialValue: view_settings.show_skin,
  200. preference: "layerview/show_skin",
  201. colorId: "layerview_inset_0"
  202. });
  203. typesLegenModel.append({
  204. label: catalog.i18nc("@label", "Show Infill"),
  205. initialValue: view_settings.show_infill,
  206. preference: "layerview/show_infill",
  207. colorId: "layerview_infill"
  208. });
  209. }
  210. }
  211. CheckBox {
  212. checked: model.initialValue
  213. onClicked: {
  214. UM.Preferences.setValue(model.preference, checked);
  215. }
  216. text: label
  217. Rectangle {
  218. anchors.verticalCenter: parent.verticalCenter
  219. anchors.right: parent.right
  220. width: UM.Theme.getSize("layerview_legend_size").width
  221. height: UM.Theme.getSize("layerview_legend_size").height
  222. color: UM.Theme.getColor(model.colorId)
  223. border.width: UM.Theme.getSize("default_lining").width
  224. border.color: UM.Theme.getColor("lining")
  225. visible: view_settings.show_legend
  226. }
  227. Layout.fillWidth: true
  228. Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
  229. Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
  230. style: UM.Theme.styles.checkbox
  231. }
  232. }
  233. CheckBox {
  234. checked: view_settings.only_show_top_layers
  235. onClicked: {
  236. UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
  237. }
  238. text: catalog.i18nc("@label", "Only Show Top Layers")
  239. visible: UM.LayerView.compatibilityMode
  240. style: UM.Theme.styles.checkbox
  241. }
  242. CheckBox {
  243. checked: view_settings.top_layer_count == 5
  244. onClicked: {
  245. UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
  246. }
  247. text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
  248. visible: UM.LayerView.compatibilityMode
  249. style: UM.Theme.styles.checkbox
  250. }
  251. Repeater {
  252. model: ListModel {
  253. id: typesLegenModelNoCheck
  254. Component.onCompleted:
  255. {
  256. typesLegenModelNoCheck.append({
  257. label: catalog.i18nc("@label", "Top / Bottom"),
  258. colorId: "layerview_skin",
  259. });
  260. typesLegenModelNoCheck.append({
  261. label: catalog.i18nc("@label", "Inner Wall"),
  262. colorId: "layerview_inset_x",
  263. });
  264. }
  265. }
  266. Label {
  267. text: label
  268. visible: view_settings.show_legend
  269. Rectangle {
  270. anchors.verticalCenter: parent.verticalCenter
  271. anchors.right: parent.right
  272. width: UM.Theme.getSize("layerview_legend_size").width
  273. height: UM.Theme.getSize("layerview_legend_size").height
  274. color: UM.Theme.getColor(model.colorId)
  275. border.width: UM.Theme.getSize("default_lining").width
  276. border.color: UM.Theme.getColor("lining")
  277. visible: view_settings.show_legend
  278. }
  279. Layout.fillWidth: true
  280. Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
  281. Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
  282. color: UM.Theme.getColor("text")
  283. }
  284. }
  285. }
  286. Item
  287. {
  288. id: slider
  289. width: handleSize
  290. height: parent.height - 2*UM.Theme.getSize("slider_layerview_margin").height
  291. anchors.top: parent.top
  292. anchors.topMargin: UM.Theme.getSize("slider_layerview_margin").height
  293. anchors.right: layerViewMenu.right
  294. anchors.rightMargin: UM.Theme.getSize("slider_layerview_margin").width
  295. property real handleSize: UM.Theme.getSize("slider_handle").width
  296. property real handleRadius: handleSize / 2
  297. property real minimumRangeHandleSize: UM.Theme.getSize("slider_handle").width / 2
  298. property real trackThickness: UM.Theme.getSize("slider_groove").width
  299. property real trackRadius: trackThickness / 2
  300. property real trackBorderWidth: UM.Theme.getSize("default_lining").width
  301. property color upperHandleColor: UM.Theme.getColor("slider_handle")
  302. property color lowerHandleColor: UM.Theme.getColor("slider_handle")
  303. property color rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
  304. property color trackColor: UM.Theme.getColor("slider_groove")
  305. property color trackBorderColor: UM.Theme.getColor("slider_groove_border")
  306. property real maximumValue: UM.LayerView.numLayers
  307. property real minimumValue: 0
  308. property real minimumRange: 0
  309. property bool roundValues: true
  310. property var activeHandle: upperHandle
  311. property bool layersVisible: UM.LayerView.layerActivity && CuraApplication.platformActivity ? true : false
  312. function getUpperValueFromHandle()
  313. {
  314. var result = upperHandle.y / (height - (2 * handleSize + minimumRangeHandleSize));
  315. result = maximumValue + result * (minimumValue - (maximumValue - minimumRange));
  316. result = roundValues ? Math.round(result) | 0 : result;
  317. return result;
  318. }
  319. function getLowerValueFromHandle()
  320. {
  321. var result = (lowerHandle.y - (handleSize + minimumRangeHandleSize)) / (height - (2 * handleSize + minimumRangeHandleSize));
  322. result = maximumValue - minimumRange + result * (minimumValue - (maximumValue - minimumRange));
  323. result = roundValues ? Math.round(result) : result;
  324. return result;
  325. }
  326. function setUpperValue(value)
  327. {
  328. var value = (value - maximumValue) / (minimumValue - maximumValue);
  329. var new_upper_y = Math.round(value * (height - (2 * handleSize + minimumRangeHandleSize)));
  330. if(new_upper_y != upperHandle.y)
  331. {
  332. upperHandle.y = new_upper_y;
  333. }
  334. rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height);
  335. }
  336. function setLowerValue(value)
  337. {
  338. var value = (value - maximumValue) / (minimumValue - maximumValue);
  339. var new_lower_y = Math.round((handleSize + minimumRangeHandleSize) + value * (height - (2 * handleSize + minimumRangeHandleSize)));
  340. if(new_lower_y != lowerHandle.y)
  341. {
  342. lowerHandle.y = new_lower_y;
  343. }
  344. rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height);
  345. }
  346. Connections
  347. {
  348. target: UM.LayerView
  349. onMinimumLayerChanged: slider.setLowerValue(UM.LayerView.minimumLayer)
  350. onCurrentLayerChanged: slider.setUpperValue(UM.LayerView.currentLayer)
  351. }
  352. Rectangle {
  353. width: parent.trackThickness
  354. height: parent.height - parent.handleSize
  355. radius: parent.trackRadius
  356. anchors.centerIn: parent
  357. color: parent.trackColor
  358. border.width: parent.trackBorderWidth;
  359. border.color: parent.trackBorderColor;
  360. }
  361. Item {
  362. id: rangeHandle
  363. y: upperHandle.y + upperHandle.height
  364. width: parent.handleSize
  365. height: parent.minimumRangeHandleSize
  366. anchors.horizontalCenter: parent.horizontalCenter
  367. visible: slider.layersVisible
  368. property real value: UM.LayerView.currentLayer
  369. function setValue(value)
  370. {
  371. var range = upperHandle.value - lowerHandle.value;
  372. value = Math.min(value, slider.maximumValue);
  373. value = Math.max(value, slider.minimumValue + range);
  374. UM.LayerView.setCurrentLayer(value);
  375. UM.LayerView.setMinimumLayer(value - range);
  376. }
  377. Rectangle {
  378. anchors.centerIn: parent
  379. width: parent.parent.trackThickness - 2 * parent.parent.trackBorderWidth
  380. height: parent.height + parent.parent.handleSize
  381. color: parent.parent.rangeHandleColor
  382. }
  383. MouseArea {
  384. anchors.fill: parent
  385. drag.target: parent
  386. drag.axis: Drag.YAxis
  387. drag.minimumY: upperHandle.height
  388. drag.maximumY: parent.parent.height - (parent.height + lowerHandle.height)
  389. onPressed: parent.parent.activeHandle = rangeHandle
  390. onPositionChanged:
  391. {
  392. upperHandle.y = parent.y - upperHandle.height
  393. lowerHandle.y = parent.y + parent.height
  394. var upper_value = slider.getUpperValueFromHandle();
  395. var lower_value = upper_value - (upperHandle.value - lowerHandle.value);
  396. UM.LayerView.setCurrentLayer(upper_value);
  397. UM.LayerView.setMinimumLayer(lower_value);
  398. }
  399. }
  400. }
  401. Rectangle {
  402. id: upperHandle
  403. y: parent.height - (parent.minimumRangeHandleSize + 2 * parent.handleSize)
  404. width: parent.handleSize
  405. height: parent.handleSize
  406. anchors.horizontalCenter: parent.horizontalCenter
  407. radius: parent.handleRadius
  408. color: parent.upperHandleColor
  409. visible: slider.layersVisible
  410. property real value: UM.LayerView.currentLayer
  411. function setValue(value)
  412. {
  413. UM.LayerView.setCurrentLayer(value);
  414. }
  415. MouseArea {
  416. anchors.fill: parent
  417. drag.target: parent
  418. drag.axis: Drag.YAxis
  419. drag.minimumY: 0
  420. drag.maximumY: parent.parent.height - (2 * parent.parent.handleSize + parent.parent.minimumRangeHandleSize)
  421. onPressed: parent.parent.activeHandle = upperHandle
  422. onPositionChanged:
  423. {
  424. if(lowerHandle.y - (upperHandle.y + upperHandle.height) < parent.parent.minimumRangeHandleSize)
  425. {
  426. lowerHandle.y = upperHandle.y + upperHandle.height + parent.parent.minimumRangeHandleSize;
  427. }
  428. rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height);
  429. UM.LayerView.setCurrentLayer(slider.getUpperValueFromHandle());
  430. }
  431. }
  432. }
  433. Rectangle {
  434. id: lowerHandle
  435. y: parent.height - parent.handleSize
  436. width: parent.handleSize
  437. height: parent.handleSize
  438. anchors.horizontalCenter: parent.horizontalCenter
  439. radius: parent.handleRadius
  440. color: parent.lowerHandleColor
  441. visible: slider.layersVisible
  442. property real value: UM.LayerView.minimumLayer
  443. function setValue(value)
  444. {
  445. UM.LayerView.setMinimumLayer(value);
  446. }
  447. MouseArea {
  448. anchors.fill: parent
  449. drag.target: parent
  450. drag.axis: Drag.YAxis
  451. drag.minimumY: upperHandle.height + parent.parent.minimumRangeHandleSize
  452. drag.maximumY: parent.parent.height - parent.height
  453. onPressed: parent.parent.activeHandle = lowerHandle
  454. onPositionChanged:
  455. {
  456. if(lowerHandle.y - (upperHandle.y + upperHandle.height) < parent.parent.minimumRangeHandleSize)
  457. {
  458. upperHandle.y = lowerHandle.y - (upperHandle.height + parent.parent.minimumRangeHandleSize);
  459. }
  460. rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
  461. UM.LayerView.setMinimumLayer(slider.getLowerValueFromHandle());
  462. }
  463. }
  464. }
  465. UM.PointingRectangle
  466. {
  467. x: parent.width + UM.Theme.getSize("slider_layerview_background").width / 2;
  468. y: Math.floor(slider.activeHandle.y + slider.activeHandle.height / 2 - height / 2);
  469. target: Qt.point(0, slider.activeHandle.y + slider.activeHandle.height / 2)
  470. arrowSize: UM.Theme.getSize("default_arrow").width
  471. height: UM.Theme.getSize("slider_handle").height + UM.Theme.getSize("default_margin").height
  472. width: valueLabel.width + UM.Theme.getSize("default_margin").width
  473. Behavior on height { NumberAnimation { duration: 50; } }
  474. color: UM.Theme.getColor("tool_panel_background")
  475. borderColor: UM.Theme.getColor("lining")
  476. borderWidth: UM.Theme.getSize("default_lining").width
  477. visible: slider.layersVisible
  478. MouseArea //Catch all mouse events (so scene doesnt handle them)
  479. {
  480. anchors.fill: parent
  481. }
  482. TextField
  483. {
  484. id: valueLabel
  485. property string maxValue: slider.maximumValue + 1
  486. text: slider.activeHandle.value + 1
  487. horizontalAlignment: TextInput.AlignRight;
  488. onEditingFinished:
  489. {
  490. // Ensure that the cursor is at the first position. On some systems the text isn't fully visible
  491. // Seems to have to do something with different dpi densities that QML doesn't quite handle.
  492. // Another option would be to increase the size even further, but that gives pretty ugly results.
  493. cursorPosition = 0;
  494. if(valueLabel.text != '')
  495. {
  496. slider.activeHandle.setValue(valueLabel.text - 1);
  497. }
  498. }
  499. validator: IntValidator { bottom: 1; top: slider.maximumValue + 1; }
  500. anchors.left: parent.left;
  501. anchors.leftMargin: UM.Theme.getSize("default_margin").width / 2;
  502. anchors.verticalCenter: parent.verticalCenter;
  503. width: Math.max(UM.Theme.getSize("line").width * maxValue.length + 2, 20);
  504. style: TextFieldStyle
  505. {
  506. textColor: UM.Theme.getColor("setting_control_text");
  507. font: UM.Theme.getFont("default");
  508. background: Item { }
  509. }
  510. Keys.onUpPressed: slider.activeHandle.setValue(slider.activeHandle.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
  511. Keys.onDownPressed: slider.activeHandle.setValue(slider.activeHandle.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
  512. }
  513. BusyIndicator
  514. {
  515. id: busyIndicator;
  516. anchors.left: parent.right;
  517. anchors.leftMargin: UM.Theme.getSize("default_margin").width / 2;
  518. anchors.verticalCenter: parent.verticalCenter;
  519. width: UM.Theme.getSize("slider_handle").height;
  520. height: width;
  521. running: UM.LayerView.busy;
  522. visible: UM.LayerView.busy;
  523. }
  524. }
  525. }
  526. }
  527. }