meerk40t 0.9.3001__py2.py3-none-any.whl → 0.9.7020__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1194 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1858 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4595 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4315 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +934 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/testcases.py +105 -0
  66. meerk40t/core/elements/trace.py +651 -563
  67. meerk40t/core/elements/tree_commands.py +415 -409
  68. meerk40t/core/elements/undo_redo.py +116 -58
  69. meerk40t/core/elements/wordlist.py +319 -200
  70. meerk40t/core/exceptions.py +9 -9
  71. meerk40t/core/laserjob.py +220 -220
  72. meerk40t/core/logging.py +63 -63
  73. meerk40t/core/node/blobnode.py +83 -86
  74. meerk40t/core/node/bootstrap.py +105 -103
  75. meerk40t/core/node/branch_elems.py +40 -31
  76. meerk40t/core/node/branch_ops.py +45 -38
  77. meerk40t/core/node/branch_regmark.py +48 -41
  78. meerk40t/core/node/cutnode.py +29 -32
  79. meerk40t/core/node/effect_hatch.py +375 -257
  80. meerk40t/core/node/effect_warp.py +398 -0
  81. meerk40t/core/node/effect_wobble.py +441 -309
  82. meerk40t/core/node/elem_ellipse.py +404 -309
  83. meerk40t/core/node/elem_image.py +1082 -801
  84. meerk40t/core/node/elem_line.py +358 -292
  85. meerk40t/core/node/elem_path.py +259 -201
  86. meerk40t/core/node/elem_point.py +129 -102
  87. meerk40t/core/node/elem_polyline.py +310 -246
  88. meerk40t/core/node/elem_rect.py +376 -286
  89. meerk40t/core/node/elem_text.py +445 -418
  90. meerk40t/core/node/filenode.py +59 -40
  91. meerk40t/core/node/groupnode.py +138 -74
  92. meerk40t/core/node/image_processed.py +777 -766
  93. meerk40t/core/node/image_raster.py +156 -113
  94. meerk40t/core/node/layernode.py +31 -31
  95. meerk40t/core/node/mixins.py +135 -107
  96. meerk40t/core/node/node.py +1427 -1304
  97. meerk40t/core/node/nutils.py +117 -114
  98. meerk40t/core/node/op_cut.py +463 -335
  99. meerk40t/core/node/op_dots.py +296 -251
  100. meerk40t/core/node/op_engrave.py +414 -311
  101. meerk40t/core/node/op_image.py +755 -369
  102. meerk40t/core/node/op_raster.py +787 -522
  103. meerk40t/core/node/place_current.py +37 -40
  104. meerk40t/core/node/place_point.py +329 -126
  105. meerk40t/core/node/refnode.py +58 -47
  106. meerk40t/core/node/rootnode.py +225 -219
  107. meerk40t/core/node/util_console.py +48 -48
  108. meerk40t/core/node/util_goto.py +84 -65
  109. meerk40t/core/node/util_home.py +61 -61
  110. meerk40t/core/node/util_input.py +102 -102
  111. meerk40t/core/node/util_output.py +102 -102
  112. meerk40t/core/node/util_wait.py +65 -65
  113. meerk40t/core/parameters.py +709 -707
  114. meerk40t/core/planner.py +875 -785
  115. meerk40t/core/plotplanner.py +656 -652
  116. meerk40t/core/space.py +120 -113
  117. meerk40t/core/spoolers.py +706 -705
  118. meerk40t/core/svg_io.py +1836 -1549
  119. meerk40t/core/treeop.py +534 -445
  120. meerk40t/core/undos.py +278 -124
  121. meerk40t/core/units.py +784 -680
  122. meerk40t/core/view.py +393 -322
  123. meerk40t/core/webhelp.py +62 -62
  124. meerk40t/core/wordlist.py +513 -504
  125. meerk40t/cylinder/cylinder.py +247 -0
  126. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  127. meerk40t/cylinder/gui/gui.py +24 -0
  128. meerk40t/device/__init__.py +1 -1
  129. meerk40t/device/basedevice.py +322 -123
  130. meerk40t/device/devicechoices.py +50 -0
  131. meerk40t/device/dummydevice.py +163 -128
  132. meerk40t/device/gui/defaultactions.py +618 -602
  133. meerk40t/device/gui/effectspanel.py +114 -0
  134. meerk40t/device/gui/formatterpanel.py +253 -290
  135. meerk40t/device/gui/warningpanel.py +337 -260
  136. meerk40t/device/mixins.py +13 -13
  137. meerk40t/dxf/__init__.py +1 -1
  138. meerk40t/dxf/dxf_io.py +766 -554
  139. meerk40t/dxf/plugin.py +47 -35
  140. meerk40t/external_plugins.py +79 -79
  141. meerk40t/external_plugins_build.py +28 -28
  142. meerk40t/extra/cag.py +112 -116
  143. meerk40t/extra/coolant.py +403 -0
  144. meerk40t/extra/encode_detect.py +204 -0
  145. meerk40t/extra/ezd.py +1165 -1165
  146. meerk40t/extra/hershey.py +834 -340
  147. meerk40t/extra/imageactions.py +322 -316
  148. meerk40t/extra/inkscape.py +628 -622
  149. meerk40t/extra/lbrn.py +424 -424
  150. meerk40t/extra/outerworld.py +283 -0
  151. meerk40t/extra/param_functions.py +1542 -1556
  152. meerk40t/extra/potrace.py +257 -253
  153. meerk40t/extra/serial_exchange.py +118 -0
  154. meerk40t/extra/updater.py +602 -453
  155. meerk40t/extra/vectrace.py +147 -146
  156. meerk40t/extra/winsleep.py +83 -83
  157. meerk40t/extra/xcs_reader.py +597 -0
  158. meerk40t/fill/fills.py +781 -335
  159. meerk40t/fill/patternfill.py +1061 -1061
  160. meerk40t/fill/patterns.py +614 -567
  161. meerk40t/grbl/control.py +87 -87
  162. meerk40t/grbl/controller.py +990 -903
  163. meerk40t/grbl/device.py +1084 -768
  164. meerk40t/grbl/driver.py +989 -771
  165. meerk40t/grbl/emulator.py +532 -497
  166. meerk40t/grbl/gcodejob.py +783 -767
  167. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  168. meerk40t/grbl/gui/grblcontroller.py +485 -271
  169. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  170. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  171. meerk40t/grbl/gui/gui.py +147 -116
  172. meerk40t/grbl/interpreter.py +44 -44
  173. meerk40t/grbl/loader.py +22 -22
  174. meerk40t/grbl/mock_connection.py +56 -56
  175. meerk40t/grbl/plugin.py +294 -264
  176. meerk40t/grbl/serial_connection.py +93 -88
  177. meerk40t/grbl/tcp_connection.py +81 -79
  178. meerk40t/grbl/ws_connection.py +112 -0
  179. meerk40t/gui/__init__.py +1 -1
  180. meerk40t/gui/about.py +2042 -296
  181. meerk40t/gui/alignment.py +1644 -1608
  182. meerk40t/gui/autoexec.py +199 -0
  183. meerk40t/gui/basicops.py +791 -670
  184. meerk40t/gui/bufferview.py +77 -71
  185. meerk40t/gui/busy.py +232 -133
  186. meerk40t/gui/choicepropertypanel.py +1662 -1469
  187. meerk40t/gui/consolepanel.py +706 -542
  188. meerk40t/gui/devicepanel.py +687 -581
  189. meerk40t/gui/dialogoptions.py +110 -107
  190. meerk40t/gui/executejob.py +316 -306
  191. meerk40t/gui/fonts.py +90 -90
  192. meerk40t/gui/functionwrapper.py +252 -0
  193. meerk40t/gui/gui_mixins.py +729 -0
  194. meerk40t/gui/guicolors.py +205 -182
  195. meerk40t/gui/help_assets/help_assets.py +218 -201
  196. meerk40t/gui/helper.py +154 -0
  197. meerk40t/gui/hersheymanager.py +1440 -846
  198. meerk40t/gui/icons.py +3422 -2747
  199. meerk40t/gui/imagesplitter.py +555 -508
  200. meerk40t/gui/keymap.py +354 -344
  201. meerk40t/gui/laserpanel.py +897 -806
  202. meerk40t/gui/laserrender.py +1470 -1232
  203. meerk40t/gui/lasertoolpanel.py +805 -793
  204. meerk40t/gui/magnetoptions.py +436 -0
  205. meerk40t/gui/materialmanager.py +2944 -0
  206. meerk40t/gui/materialtest.py +1722 -1694
  207. meerk40t/gui/mkdebug.py +646 -359
  208. meerk40t/gui/mwindow.py +163 -140
  209. meerk40t/gui/navigationpanels.py +2605 -2467
  210. meerk40t/gui/notes.py +143 -142
  211. meerk40t/gui/opassignment.py +414 -410
  212. meerk40t/gui/operation_info.py +310 -299
  213. meerk40t/gui/plugin.py +500 -328
  214. meerk40t/gui/position.py +714 -669
  215. meerk40t/gui/preferences.py +901 -650
  216. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  217. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  218. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  219. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  220. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  221. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  222. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  223. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  224. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  225. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  226. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  227. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  228. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  229. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  230. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  231. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  232. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  233. meerk40t/gui/propertypanels/textproperty.py +770 -755
  234. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  235. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  236. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  237. meerk40t/gui/ribbon.py +2471 -2210
  238. meerk40t/gui/scene/scene.py +1100 -1051
  239. meerk40t/gui/scene/sceneconst.py +22 -22
  240. meerk40t/gui/scene/scenepanel.py +439 -349
  241. meerk40t/gui/scene/scenespacewidget.py +365 -365
  242. meerk40t/gui/scene/widget.py +518 -505
  243. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  244. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  245. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  246. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  247. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  248. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  249. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  250. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  251. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  252. meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
  253. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  254. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  255. meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
  256. meerk40t/gui/simpleui.py +362 -333
  257. meerk40t/gui/simulation.py +2451 -2094
  258. meerk40t/gui/snapoptions.py +208 -203
  259. meerk40t/gui/spoolerpanel.py +1227 -1180
  260. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  261. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  262. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  263. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  264. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  265. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  266. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  267. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  268. meerk40t/gui/themes.py +200 -78
  269. meerk40t/gui/tips.py +590 -0
  270. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  271. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  272. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  273. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  274. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  275. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  276. meerk40t/gui/toolwidgets/toolline.py +39 -144
  277. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  278. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  279. meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
  280. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  281. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  282. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  283. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  284. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  285. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  286. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  287. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  288. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  289. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  290. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  291. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  292. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  293. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  294. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  295. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  296. meerk40t/gui/usbconnect.py +98 -91
  297. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  298. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  299. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  300. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  301. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  302. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  303. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  304. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  305. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  306. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  307. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  308. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  309. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  310. meerk40t/gui/wordlisteditor.py +985 -931
  311. meerk40t/gui/wxmeerk40t.py +1447 -1169
  312. meerk40t/gui/wxmmain.py +5644 -4112
  313. meerk40t/gui/wxmribbon.py +1591 -1076
  314. meerk40t/gui/wxmscene.py +1631 -1453
  315. meerk40t/gui/wxmtree.py +2416 -2089
  316. meerk40t/gui/wxutils.py +1769 -1099
  317. meerk40t/gui/zmatrix.py +102 -102
  318. meerk40t/image/__init__.py +1 -1
  319. meerk40t/image/dither.py +429 -0
  320. meerk40t/image/imagetools.py +2793 -2269
  321. meerk40t/internal_plugins.py +150 -130
  322. meerk40t/kernel/__init__.py +63 -12
  323. meerk40t/kernel/channel.py +259 -212
  324. meerk40t/kernel/context.py +538 -538
  325. meerk40t/kernel/exceptions.py +41 -41
  326. meerk40t/kernel/functions.py +463 -414
  327. meerk40t/kernel/jobs.py +100 -100
  328. meerk40t/kernel/kernel.py +3828 -3571
  329. meerk40t/kernel/lifecycles.py +71 -71
  330. meerk40t/kernel/module.py +49 -49
  331. meerk40t/kernel/service.py +147 -147
  332. meerk40t/kernel/settings.py +383 -343
  333. meerk40t/lihuiyu/controller.py +883 -876
  334. meerk40t/lihuiyu/device.py +1181 -1069
  335. meerk40t/lihuiyu/driver.py +1466 -1372
  336. meerk40t/lihuiyu/gui/gui.py +127 -106
  337. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  338. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  339. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  340. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  341. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  342. meerk40t/lihuiyu/interpreter.py +53 -53
  343. meerk40t/lihuiyu/laserspeed.py +450 -450
  344. meerk40t/lihuiyu/loader.py +90 -90
  345. meerk40t/lihuiyu/parser.py +404 -404
  346. meerk40t/lihuiyu/plugin.py +101 -102
  347. meerk40t/lihuiyu/tcp_connection.py +111 -109
  348. meerk40t/main.py +231 -165
  349. meerk40t/moshi/builder.py +788 -781
  350. meerk40t/moshi/controller.py +505 -499
  351. meerk40t/moshi/device.py +495 -442
  352. meerk40t/moshi/driver.py +862 -696
  353. meerk40t/moshi/gui/gui.py +78 -76
  354. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  355. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  356. meerk40t/moshi/plugin.py +43 -43
  357. meerk40t/network/console_server.py +140 -57
  358. meerk40t/network/kernelserver.py +10 -9
  359. meerk40t/network/tcp_server.py +142 -140
  360. meerk40t/network/udp_server.py +103 -77
  361. meerk40t/network/web_server.py +404 -0
  362. meerk40t/newly/controller.py +1158 -1144
  363. meerk40t/newly/device.py +874 -732
  364. meerk40t/newly/driver.py +540 -412
  365. meerk40t/newly/gui/gui.py +219 -188
  366. meerk40t/newly/gui/newlyconfig.py +116 -101
  367. meerk40t/newly/gui/newlycontroller.py +193 -186
  368. meerk40t/newly/gui/operationproperties.py +51 -51
  369. meerk40t/newly/mock_connection.py +82 -82
  370. meerk40t/newly/newly_params.py +56 -56
  371. meerk40t/newly/plugin.py +1214 -1246
  372. meerk40t/newly/usb_connection.py +322 -322
  373. meerk40t/rotary/gui/gui.py +52 -46
  374. meerk40t/rotary/gui/rotarysettings.py +240 -232
  375. meerk40t/rotary/rotary.py +202 -98
  376. meerk40t/ruida/control.py +291 -91
  377. meerk40t/ruida/controller.py +138 -1088
  378. meerk40t/ruida/device.py +676 -231
  379. meerk40t/ruida/driver.py +534 -472
  380. meerk40t/ruida/emulator.py +1494 -1491
  381. meerk40t/ruida/exceptions.py +4 -4
  382. meerk40t/ruida/gui/gui.py +71 -76
  383. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  384. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  385. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  386. meerk40t/ruida/loader.py +54 -52
  387. meerk40t/ruida/mock_connection.py +57 -109
  388. meerk40t/ruida/plugin.py +124 -87
  389. meerk40t/ruida/rdjob.py +2084 -945
  390. meerk40t/ruida/serial_connection.py +116 -0
  391. meerk40t/ruida/tcp_connection.py +146 -0
  392. meerk40t/ruida/udp_connection.py +73 -0
  393. meerk40t/svgelements.py +9671 -9669
  394. meerk40t/tools/driver_to_path.py +584 -579
  395. meerk40t/tools/geomstr.py +5583 -4680
  396. meerk40t/tools/jhfparser.py +357 -292
  397. meerk40t/tools/kerftest.py +904 -890
  398. meerk40t/tools/livinghinges.py +1168 -1033
  399. meerk40t/tools/pathtools.py +987 -949
  400. meerk40t/tools/pmatrix.py +234 -0
  401. meerk40t/tools/pointfinder.py +942 -942
  402. meerk40t/tools/polybool.py +941 -940
  403. meerk40t/tools/rasterplotter.py +1660 -547
  404. meerk40t/tools/shxparser.py +1047 -901
  405. meerk40t/tools/ttfparser.py +726 -446
  406. meerk40t/tools/zinglplotter.py +595 -593
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
  408. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
  409. meerk40t-0.9.7020.dist-info/RECORD +446 -0
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
  412. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
  413. meerk40t/balormk/elementlightjob.py +0 -159
  414. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  415. test/bootstrap.py +0 -63
  416. test/test_cli.py +0 -12
  417. test/test_core_cutcode.py +0 -418
  418. test/test_core_elements.py +0 -144
  419. test/test_core_plotplanner.py +0 -397
  420. test/test_core_viewports.py +0 -312
  421. test/test_drivers_grbl.py +0 -108
  422. test/test_drivers_lihuiyu.py +0 -443
  423. test/test_drivers_newly.py +0 -113
  424. test/test_element_degenerate_points.py +0 -43
  425. test/test_elements_classify.py +0 -97
  426. test/test_elements_penbox.py +0 -22
  427. test/test_file_svg.py +0 -176
  428. test/test_fill.py +0 -155
  429. test/test_geomstr.py +0 -1523
  430. test/test_geomstr_nodes.py +0 -18
  431. test/test_imagetools_actualize.py +0 -306
  432. test/test_imagetools_wizard.py +0 -258
  433. test/test_kernel.py +0 -200
  434. test/test_laser_speeds.py +0 -3303
  435. test/test_length.py +0 -57
  436. test/test_lifecycle.py +0 -66
  437. test/test_operations.py +0 -251
  438. test/test_operations_hatch.py +0 -57
  439. test/test_ruida.py +0 -19
  440. test/test_spooler.py +0 -22
  441. test/test_tools_rasterplotter.py +0 -29
  442. test/test_wobble.py +0 -133
  443. test/test_zingl.py +0 -124
  444. {test → meerk40t/cylinder}/__init__.py +0 -0
  445. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  446. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
meerk40t/gui/wxmtree.py CHANGED
@@ -1,2089 +1,2416 @@
1
- import wx
2
- from wx import aui
3
-
4
- from meerk40t.core.elements.element_types import op_nodes
5
-
6
- from ..core.units import Length
7
- from ..kernel import signal_listener
8
- from ..svgelements import Color
9
- from .basicops import BasicOpPanel
10
- from .icons import (
11
- get_default_scale_factor,
12
- icon_bell,
13
- icon_bmap_text,
14
- icon_canvas,
15
- icon_close_window,
16
- icon_console,
17
- icon_effect_hatch,
18
- icon_effect_wobble,
19
- icon_external,
20
- icon_internal,
21
- icon_line,
22
- icon_meerk40t,
23
- icon_mk_ellipse,
24
- icon_mk_polyline,
25
- icon_mk_rectangular,
26
- icon_path,
27
- icon_points,
28
- icon_regmarks,
29
- icon_return,
30
- icon_round_stop,
31
- icon_timer,
32
- icon_tree,
33
- icon_warning,
34
- icons8_direction,
35
- icons8_file,
36
- icons8_ghost,
37
- icons8_group_objects,
38
- icons8_home_filled,
39
- icons8_image,
40
- icons8_laser_beam,
41
- icons8_laserbeam_weak,
42
- icons8_lock,
43
- icons8_r_white,
44
- )
45
- from .laserrender import DRAW_MODE_ICONS, LaserRender, swizzlecolor
46
- from .mwindow import MWindow
47
- from .wxutils import (
48
- StaticBoxSizer,
49
- create_menu,
50
- dip_size,
51
- get_key_name,
52
- is_navigation_key,
53
- )
54
-
55
- _ = wx.GetTranslation
56
-
57
-
58
- def register_panel_tree(window, context):
59
- context.root.setting(int, "tree_panel_page", 0)
60
- lastpage = getattr(context.root, "tree_panel_page", 0)
61
- if lastpage is None or lastpage < 0 or lastpage > 2:
62
- lastpage = 0
63
-
64
- def on_panel_change(context):
65
- def handler(event):
66
- mycontext.root.setting(int, "tree_panel_page", 0)
67
- pagenum = notetab.GetSelection()
68
- setattr(mycontext.root, "tree_panel_page", pagenum)
69
- return
70
-
71
- mycontext = context
72
- return handler
73
-
74
- notetab = wx.aui.AuiNotebook(
75
- window,
76
- wx.ID_ANY,
77
- style=wx.aui.AUI_NB_TAB_EXTERNAL_MOVE
78
- | wx.aui.AUI_NB_SCROLL_BUTTONS
79
- | wx.aui.AUI_NB_TAB_SPLIT
80
- | wx.aui.AUI_NB_TAB_MOVE,
81
- )
82
-
83
- basic_op = BasicOpPanel(window, wx.ID_ANY, context=context)
84
- wxtree = TreePanel(window, wx.ID_ANY, context=context)
85
- pane = (
86
- aui.AuiPaneInfo()
87
- .Name("tree")
88
- .Right()
89
- .MinSize(200, 180)
90
- .BestSize(300, 270)
91
- .FloatingSize(300, 270)
92
- .LeftDockable()
93
- .RightDockable()
94
- .BottomDockable(False)
95
- .Caption(_("Tree"))
96
- .CaptionVisible(not context.pane_lock)
97
- .TopDockable(False)
98
- )
99
-
100
- notetab.AddPage(basic_op, _("Burn-Operation"))
101
- notetab.AddPage(wxtree, _("Details"))
102
- notetab.SetSelection(lastpage)
103
- notetab.Bind(aui.EVT_AUINOTEBOOK_PAGE_CHANGED, on_panel_change(context))
104
- pane.dock_proportion = 500
105
- pane.control = notetab
106
- window.on_pane_create(pane)
107
- context.register("pane/tree", pane)
108
-
109
-
110
- class TreePanel(wx.Panel):
111
- def __init__(self, *args, context=None, **kwds):
112
- kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
113
- wx.Panel.__init__(self, *args, **kwds)
114
- self.context = context
115
- # Define Tree
116
- self.wxtree = wx.TreeCtrl(
117
- self,
118
- wx.ID_ANY,
119
- style=wx.TR_MULTIPLE
120
- | wx.TR_HAS_BUTTONS
121
- | wx.TR_HIDE_ROOT
122
- | wx.TR_LINES_AT_ROOT,
123
- )
124
- # try:
125
- # res = wx.SystemSettings().GetAppearance().IsDark()
126
- # except AttributeError:
127
- # res = wx.SystemSettings().GetColour(wx.SYS_COLOUR_WINDOW)[0] < 127
128
- res = wx.SystemSettings().GetColour(wx.SYS_COLOUR_WINDOW)[0] < 127
129
- if res:
130
- self.wxtree.SetBackgroundColour(wx.Colour(50, 50, 50))
131
-
132
- self.setup_warn_panel()
133
-
134
- main_sizer = wx.BoxSizer(wx.VERTICAL)
135
- main_sizer.Add(self.wxtree, 1, wx.EXPAND, 0)
136
- main_sizer.Add(self.warn_panel, 0, wx.EXPAND, 0)
137
- self.SetSizer(main_sizer)
138
- self.__set_tree()
139
- self.wxtree.Bind(wx.EVT_KEY_UP, self.on_key_up)
140
- self.wxtree.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
141
- self._keybind_channel = self.context.channel("keybinds")
142
-
143
- self.context.signal("rebuild_tree")
144
-
145
- def setup_warn_panel(self):
146
- def fix_unassigned_create(event):
147
- previous = self.context.elements.classify_autogenerate
148
- self.context.elements.classify_autogenerate = True
149
- target_list = list(self.context.elements.unassigned_elements())
150
- self.context.elements.classify(target_list)
151
- self.context.elements.classify_autogenerate = previous
152
- self.context.elements.signal("refresh_tree")
153
-
154
- def fix_unassigned_used(event):
155
- previous = self.context.elements.classify_autogenerate
156
- self.context.elements.classify_autogenerate = False
157
- target_list = list(self.context.elements.unassigned_elements())
158
- self.context.elements.classify(target_list)
159
- self.context.elements.classify_autogenerate = previous
160
- self.context.elements.signal("refresh_tree")
161
-
162
- def fix_unburnt(event):
163
- for node in self.context.elements.elems():
164
- will_be_burnt = False
165
- first_op = None
166
- for refnode in node._references:
167
- op = refnode.parent
168
- if op is not None:
169
- try:
170
- if op.output:
171
- will_be_burnt = True
172
- break
173
- else:
174
- if first_op is None:
175
- first_op = op
176
- except AttributeError:
177
- pass
178
- if not will_be_burnt and first_op is not None:
179
- try:
180
- first_op.output = True
181
- self.context.elements.signal(
182
- "element_property_update", first_op
183
- )
184
- self.context.elements.signal("warn_state_update")
185
- except AttributeError:
186
- pass
187
-
188
- self.warn_panel = wx.BoxSizer(wx.HORIZONTAL)
189
- unassigned_frame = StaticBoxSizer(self, wx.ID_ANY, "Unassigned", wx.HORIZONTAL)
190
- unburnt_frame = StaticBoxSizer(self, wx.ID_ANY, "Non-burnt", wx.HORIZONTAL)
191
- self.btn_fix_assign_create = wx.Button(self, wx.ID_ANY, "Assign (+new)")
192
- self.btn_fix_assign_existing = wx.Button(self, wx.ID_ANY, "Assign")
193
- self.btn_fix_unburnt = wx.Button(self, wx.ID_ANY, "Enable")
194
- self.btn_fix_assign_create.SetToolTip(
195
- _("Classify unassigned elements and create operations if necessary")
196
- )
197
- self.btn_fix_assign_existing.SetToolTip(
198
- _("Classify unassigned elements and use only existing operations")
199
- )
200
- self.btn_fix_unburnt.SetToolTip(
201
- _("Reactivate disabled operations that prevent elements from being burnt")
202
- )
203
-
204
- unassigned_frame.Add(self.btn_fix_assign_create, 0, wx.EXPAND, 0)
205
- unassigned_frame.Add(self.btn_fix_assign_existing, 0, wx.EXPAND, 0)
206
- unburnt_frame.Add(self.btn_fix_unburnt, 0, wx.EXPAND, 0)
207
- self.warn_panel.Add(unassigned_frame, 1, wx.EXPAND, 0)
208
- self.warn_panel.Add(unburnt_frame, 1, wx.EXPAND, 0)
209
- self._last_issue = None
210
- self.warn_panel.Show(False)
211
- self.warn_panel.ShowItems(False)
212
- self.Bind(wx.EVT_BUTTON, fix_unassigned_create, self.btn_fix_assign_create)
213
- self.Bind(wx.EVT_BUTTON, fix_unassigned_used, self.btn_fix_assign_existing)
214
- self.Bind(wx.EVT_BUTTON, fix_unburnt, self.btn_fix_unburnt)
215
- # self.Show(False)
216
-
217
- def check_for_issues(self):
218
- non_assigned, non_burn = self.context.elements.have_unburnable_elements()
219
- self.btn_fix_assign_create.Enable(non_assigned)
220
- self.btn_fix_assign_existing.Enable(non_assigned)
221
- self.btn_fix_unburnt.Enable(non_burn)
222
- new_issue = non_assigned or non_burn
223
- if self._last_issue == new_issue:
224
- return
225
- self._last_issue = new_issue
226
- if new_issue:
227
- self.warn_panel.Show(True)
228
- self.warn_panel.ShowItems(True)
229
- else:
230
- self.warn_panel.Show(False)
231
- self.warn_panel.ShowItems(False)
232
- self.Layout()
233
-
234
- def __set_tree(self):
235
- self.shadow_tree = ShadowTree(
236
- self.context.elements, self.GetParent(), self.wxtree, self.context
237
- )
238
-
239
- # self.Bind(
240
- # wx.EVT_TREE_BEGIN_DRAG, self.shadow_tree.on_drag_begin_handler, self.wxtree
241
- # )
242
- self.shadow_tree.wxtree.Bind(
243
- wx.EVT_TREE_BEGIN_DRAG, self.shadow_tree.on_drag_begin_handler
244
- )
245
- self.Bind(
246
- wx.EVT_TREE_END_DRAG, self.shadow_tree.on_drag_end_handler, self.wxtree
247
- )
248
- self.Bind(
249
- wx.EVT_TREE_ITEM_ACTIVATED, self.shadow_tree.on_item_activated, self.wxtree
250
- )
251
- self.Bind(
252
- wx.EVT_TREE_SEL_CHANGED,
253
- self.shadow_tree.on_item_selection_changed,
254
- self.wxtree,
255
- )
256
- self.Bind(
257
- wx.EVT_TREE_ITEM_RIGHT_CLICK,
258
- self.shadow_tree.on_item_right_click,
259
- self.wxtree,
260
- )
261
- self.wxtree.Bind(wx.EVT_MOTION, self.shadow_tree.on_mouse_over)
262
-
263
- def on_key_down(self, event):
264
- """
265
- Keydown for the tree does not execute navigation keys. These are only executed by the scene since they do
266
- useful work for the tree.
267
-
268
- Make sure the treectl can work on standard keys...
269
-
270
- @param event:
271
- @return:
272
- """
273
- event.Skip()
274
- keyvalue = get_key_name(event)
275
- if is_navigation_key(keyvalue):
276
- if self._keybind_channel:
277
- self._keybind_channel(
278
- f"Tree key_down: {keyvalue} is a navigation key. Not processed."
279
- )
280
- return
281
- if self.context.bind.trigger(keyvalue):
282
- if self._keybind_channel:
283
- self._keybind_channel(f"Tree key_down: {keyvalue} is executed.")
284
- else:
285
- if self._keybind_channel:
286
- self._keybind_channel(f"Tree key_down: {keyvalue} was unbound.")
287
-
288
- def on_key_up(self, event):
289
- """
290
- Keyup for the tree does not execute navigation keys. These are only executed by the scene.
291
-
292
- Make sure the treectl can work on standard keys...
293
-
294
- @param event:
295
- @return:
296
- """
297
- event.Skip()
298
- keyvalue = get_key_name(event)
299
- if is_navigation_key(keyvalue):
300
- if self._keybind_channel:
301
- self._keybind_channel(
302
- f"Tree key_up: {keyvalue} is a navigation key. Not processed."
303
- )
304
- return
305
- if self.context.bind.untrigger(keyvalue):
306
- if self._keybind_channel:
307
- self._keybind_channel(f"Tree key_up: {keyvalue} is executed.")
308
- else:
309
- if self._keybind_channel:
310
- self._keybind_channel(f"Tree key_up: {keyvalue} was unbound.")
311
-
312
- def pane_show(self):
313
- pass
314
-
315
- def pane_hide(self):
316
- pass
317
-
318
- @signal_listener("warn_state_update")
319
- def on_warn_state_update(self, origin, *args):
320
- # Updates the warning state, using signal to avoid unnecessary calls
321
- self.shadow_tree.update_warn_sign()
322
- self.check_for_issues()
323
-
324
- @signal_listener("select_emphasized_tree")
325
- def on_shadow_select_emphasized_tree(self, origin, *args):
326
- self.shadow_tree.select_in_tree_by_emphasis(origin, *args)
327
-
328
- @signal_listener("activate_selected_nodes")
329
- def on_shadow_select_activate_tree(self, origin, *args):
330
- self.shadow_tree.activate_selected_node(origin, *args)
331
-
332
- @signal_listener("activate_single_node")
333
- def on_shadow_select_activate_single_tree(self, origin, node=None, *args):
334
- if node is not None:
335
- node.selected = True
336
- # self.shadow_tree.activate_selected_node(origin, *args)
337
-
338
- @signal_listener("element_property_update")
339
- def on_element_update(self, origin, *args):
340
- """
341
- Called by 'element_property_update' when the properties of an element are changed.
342
-
343
- @param origin: the path of the originating signal
344
- @param args:
345
- @return:
346
- """
347
- if self.shadow_tree is not None:
348
- self.shadow_tree.on_element_update(*args)
349
-
350
- @signal_listener("element_property_reload")
351
- def on_force_element_update(self, origin, *args):
352
- """
353
- Called by 'element_property_reload' when the properties of an element are changed.
354
-
355
- @param origin: the path of the originating signal
356
- @param args:
357
- @return:
358
- """
359
- if self.shadow_tree is not None:
360
- self.shadow_tree.on_force_element_update(*args)
361
-
362
- @signal_listener("activate;device")
363
- @signal_listener("rebuild_tree")
364
- def on_rebuild_tree_signal(self, origin, target=None, *args):
365
- """
366
- Called by 'rebuild_tree' signal. To rebuild the tree directly
367
-
368
- @param origin: the path of the originating signal
369
- @param target: target device
370
- @param args:
371
- @return:
372
- """
373
- # if target is not None:
374
- # if target == "elements":
375
- # startnode = self.shadow_tree.elements.get(type="branch elems").item
376
- # elif target == "operations":
377
- # startnode = self.shadow_tree.elements.get(type="branch ops").item
378
- # elif target == "regmarks":
379
- # startnode = self.shadow_tree.elements.get(type="branch reg").item
380
- # print ("Current content of branch %s" % target)
381
- # idx = 0
382
- # child, cookie = self.shadow_tree.wxtree.GetFirstChild(startnode)
383
- # while child.IsOk():
384
- # # child_node = self.wxtree.GetItemData(child)
385
- # lbl = self.shadow_tree.wxtree.GetItemText(child)
386
- # print ("Node #%d - content: %s" % (idx, lbl))
387
- # child, cookie = self.shadow_tree.wxtree.GetNextChild(startnode, cookie)
388
- # idx += 1
389
- # self.shadow_tree.wxtree.Expand(startnode)
390
- # else:
391
- # self.shadow_tree.rebuild_tree()
392
- self.shadow_tree.rebuild_tree()
393
-
394
- @signal_listener("refresh_tree")
395
- def on_refresh_tree_signal(self, origin, nodes=None, *args):
396
- """
397
- Called by 'refresh_tree' signal. To refresh tree directly
398
-
399
- @param origin: the path of the originating signal
400
- @param nodes: which nodes were added.
401
- @param args:
402
- @return:
403
- """
404
- self.shadow_tree.cache_hits = 0
405
- self.shadow_tree.cache_requests = 0
406
- self.shadow_tree.refresh_tree(source=f"signal_{origin}")
407
- if nodes is not None:
408
- if isinstance(nodes, (tuple, list)):
409
- # All Standard nodes first
410
- for node in nodes:
411
- if node is None or node._item is None:
412
- pass
413
- else:
414
- if node.type.startswith("elem "):
415
- self.shadow_tree.set_icon(node, force=True)
416
- # Then all others
417
- for node in nodes:
418
- if node is None or node._item is None:
419
- pass
420
- else:
421
- if not node.type.startswith("elem "):
422
- self.shadow_tree.set_icon(node, force=True)
423
- # Show the first node, but if that's the root node then ignore stuff
424
- if len(nodes) > 0:
425
- node = nodes[0]
426
- else:
427
- node = None
428
- else:
429
- node = nodes
430
- self.shadow_tree.set_icon(node, force=True)
431
- rootitem = self.shadow_tree.wxtree.GetRootItem()
432
- if not node is None and not node._item is None and node._item != rootitem:
433
- self.shadow_tree.wxtree.EnsureVisible(node._item)
434
-
435
- @signal_listener("freeze_tree")
436
- def on_freeze_tree_signal(self, origin, status=None, *args):
437
- """
438
- Called by 'rebuild_tree' signal. Halts any updates like set_decorations and others
439
-
440
- @param origin: the path of the originating signal
441
- @param status: true, false (evident what they do), None: to toggle
442
- @param args:
443
- @return:
444
- """
445
- self.shadow_tree.freeze_tree(status)
446
-
447
- @signal_listener("updateop_tree")
448
- def on_update_op_labels_tree(self, origin, *args):
449
- self.shadow_tree.update_op_labels()
450
- opitem = self.context.elements.get(type="branch ops")._item
451
- if opitem is None:
452
- return
453
- tree = self.shadow_tree.wxtree
454
- tree.Expand(opitem)
455
-
456
- @signal_listener("updateelem_tree")
457
- def on_update_elem_tree(self, origin, *args):
458
- elitem = self.context.elements.get(type="branch elems")._item
459
- if elitem is None:
460
- return
461
- tree = self.shadow_tree.wxtree
462
- tree.Expand(elitem)
463
-
464
-
465
- class ElementsTree(MWindow):
466
- def __init__(self, *args, **kwds):
467
- super().__init__(423, 131, *args, **kwds)
468
-
469
- self.panel = TreePanel(self, wx.ID_ANY, context=self.context)
470
- self.add_module_delegate(self.panel)
471
- _icon = wx.NullIcon
472
- _icon.CopyFromBitmap(icon_tree.GetBitmap())
473
- self.SetIcon(_icon)
474
- self.SetTitle(_("Tree"))
475
-
476
- def window_open(self):
477
- try:
478
- self.panel.pane_show()
479
- except AttributeError:
480
- pass
481
-
482
- def window_close(self):
483
- try:
484
- self.panel.pane_hide()
485
- except AttributeError:
486
- pass
487
-
488
-
489
- class ShadowTree:
490
- """
491
- The shadowTree creates a 'wx.Tree' structure from the 'elements.tree' structure. It listens to updates to the
492
- elements tree and updates the GUI version accordingly. This tree does not permit alterations to it, rather it sends
493
- any requested alterations to the 'elements.tree' or the 'elements.elements' or 'elements.operations' and when those
494
- are reflected in the tree, the shadow tree is updated accordingly.
495
- """
496
-
497
- def __init__(self, service, gui, wxtree, context):
498
- self.elements = service
499
- self.context = context
500
- self.gui = gui
501
- self.wxtree = wxtree
502
- self.renderer = LaserRender(service.root)
503
- self.dragging_nodes = None
504
- self.tree_images = None
505
- self.name = "Project"
506
- self._freeze = False
507
- testsize = dip_size(self, 20, 20)
508
- self.iconsize = testsize[1]
509
- self.iconstates = {}
510
- self.last_call = 0
511
-
512
- # fact = get_default_scale_factor()
513
- # if fact > 1.0:
514
- # self.iconsize = int(self.iconsize * fact)
515
-
516
- self.do_not_select = False
517
- self.was_already_expanded = []
518
- service.add_service_delegate(self)
519
- self.setup_state_images()
520
- self.default_images = {
521
- "console home -f": icons8_home_filled,
522
- "console move_abs": icon_return,
523
- "console beep": icon_bell,
524
- "console interrupt": icon_round_stop,
525
- "console quit": icon_close_window,
526
- "util wait": icon_timer,
527
- "util home": icons8_home_filled,
528
- "util goto": icon_return,
529
- "util output": icon_external,
530
- "util input": icon_internal,
531
- "util console": icon_console,
532
- "op engrave": icons8_laserbeam_weak,
533
- "op cut": icons8_laser_beam,
534
- "op image": icons8_image,
535
- "op raster": icons8_direction,
536
- "op dots": icon_points,
537
- "effect hatch": icon_effect_hatch,
538
- "effect wobble": icon_effect_wobble,
539
- "place current": icons8_home_filled,
540
- "place point": icons8_home_filled,
541
- "elem point": icon_points,
542
- "file": icons8_file,
543
- "group": icons8_group_objects,
544
- "elem rect": icon_mk_rectangular,
545
- "elem ellipse": icon_mk_ellipse,
546
- "elem image": icons8_image,
547
- "elem path": icon_path,
548
- "elem line": icon_line,
549
- "elem polyline": icon_mk_polyline,
550
- "elem text": icon_bmap_text,
551
- "blob": icons8_file,
552
- }
553
- self.image_cache = []
554
- self.cache_hits = 0
555
- self.cache_requests = 0
556
- self._too_big = False
557
- self.refresh_tree_counter = 0
558
- self._last_hover_item = None
559
-
560
- def service_attach(self, *args):
561
- self.elements.listen_tree(self)
562
-
563
- def service_detach(self, *args):
564
- self.elements.unlisten_tree(self)
565
-
566
- def setup_state_images(self):
567
- self.state_images = wx.ImageList()
568
- self.iconstates = {}
569
- self.state_images.Create(width=self.iconsize, height=self.iconsize)
570
- image = icons8_lock.GetBitmap(
571
- resize=(self.iconsize, self.iconsize),
572
- noadjustment=True,
573
- buffer=1,
574
- )
575
- image_id = self.state_images.Add(bitmap=image)
576
- self.iconstates["lock"] = image_id
577
- image = icons8_r_white.GetBitmap(
578
- resize=(self.iconsize, self.iconsize),
579
- noadjustment=True,
580
- buffer=1,
581
- )
582
- image_id = self.state_images.Add(bitmap=image)
583
- self.iconstates["refobject"] = image_id
584
- image = icon_warning.GetBitmap(
585
- resize=(self.iconsize, self.iconsize),
586
- noadjustment=True,
587
- buffer=1,
588
- )
589
- image_id = self.state_images.Add(bitmap=image)
590
- self.iconstates["warning"] = image_id
591
- image = icons8_ghost.GetBitmap(
592
- resize=(self.iconsize, self.iconsize),
593
- noadjustment=True,
594
- buffer=1,
595
- )
596
- image_id = self.state_images.Add(bitmap=image)
597
- self.iconstates["ghost"] = image_id
598
- self.wxtree.SetStateImageList(self.state_images)
599
-
600
- def node_created(self, node, **kwargs):
601
- """
602
- Notified that this node has been created.
603
- @param node: Node that was created.
604
- @param kwargs:
605
- @return:
606
- """
607
- if self._freeze or self.context.elements.suppress_updates:
608
- return
609
- self.elements.signal("modified")
610
-
611
- def node_destroyed(self, node, **kwargs):
612
- """
613
- Notified that this node has been destroyed.
614
- @param node: Node that was destroyed.
615
- @param kwargs:
616
- @return:
617
- """
618
- if self._freeze or self.context.elements.suppress_updates:
619
- return
620
- self.elements.signal("modified")
621
- self.elements.signal("warn_state_update")
622
-
623
- def node_detached(self, node, **kwargs):
624
- """
625
- Notified that this node has been detached from the tree.
626
- @param node: Node that was detached.
627
- @param kwargs:
628
- @return:
629
- """
630
- self.unregister_children(node)
631
- self.node_unregister(node, **kwargs)
632
-
633
- def node_attached(self, node, **kwargs):
634
- """
635
- Notified that this node has been attached to the tree.
636
- @param node: Node that was attached.
637
- @param kwargs:
638
- @return:
639
- """
640
- self.node_register(node, **kwargs)
641
- self.register_children(node)
642
-
643
- def node_changed(self, node):
644
- """
645
- Notified that this node has been changed.
646
- @param node: Node that was changed.
647
- @return:
648
- """
649
- if self._freeze or self.context.elements.suppress_updates:
650
- return
651
- item = node._item
652
- self.check_validity(item)
653
- try:
654
- self.update_decorations(node, force=True)
655
- except RuntimeError:
656
- # A timer can update after the tree closes.
657
- return
658
-
659
- def check_validity(self, item):
660
- if item is None or not item.IsOk():
661
- # raise ValueError("Bad Item")
662
- self.rebuild_tree()
663
- self.elements.signal("refresh_scene", "Scene")
664
- return
665
-
666
- def selected(self, node):
667
- """
668
- Notified that this node was selected.
669
-
670
- Directly selected within the tree, specifically selected within the treectrl
671
- @param node:
672
- @return:
673
- """
674
- if self._freeze or self.context.elements.suppress_updates:
675
- return
676
- item = node._item
677
- self.check_validity(item)
678
- # self.update_decorations(node)
679
- self.set_enhancements(node)
680
- self.elements.signal("selected", node)
681
-
682
- def emphasized(self, node):
683
- """
684
- Notified that this node was emphasized.
685
-
686
- Item is selected by being emphasized this is treated like a soft selection throughout
687
- @param node:
688
- @return:
689
- """
690
- if self._freeze or self.context.elements.suppress_updates:
691
- return
692
- item = node._item
693
- self.check_validity(item)
694
- # self.update_decorations(node)
695
- self.set_enhancements(node)
696
- self.elements.signal("emphasized", node)
697
-
698
- def targeted(self, node):
699
- """
700
- Notified that this node was targeted.
701
-
702
- If any element is emphasized, all operations containing that element are targeted.
703
- @param node:
704
- @return:
705
- """
706
- if self._freeze or self.context.elements.suppress_updates:
707
- return
708
- item = node._item
709
- self.check_validity(item)
710
- self.update_decorations(node)
711
- self.set_enhancements(node)
712
- self.elements.signal("targeted", node)
713
-
714
- def highlighted(self, node):
715
- """
716
- Notified that this node was highlighted.
717
-
718
- If any operation is selected, all sub-operations are highlighted.
719
- If any element is emphasized, all copies are highlighted.
720
- @param node:
721
- @return:
722
- """
723
- if self._freeze or self.context.elements.suppress_updates:
724
- return
725
- item = node._item
726
- self.check_validity(item)
727
- # self.update_decorations(node)
728
- self.set_enhancements(node)
729
- self.elements.signal("highlighted", node)
730
-
731
- def translated(self, node, dx=0, dy=0, *args):
732
- """
733
- This node was moved
734
- """
735
- return
736
-
737
- def scaled(self, node, sx=1, sy=1, ox=0, oy=0, *args):
738
- """
739
- This node was scaled
740
- """
741
- return
742
-
743
- def modified(self, node):
744
- """
745
- Notified that this node was modified.
746
- This node position values were changed, but nothing about the core data was altered.
747
- @param node:
748
- @return:
749
- """
750
- if self._freeze or self.context.elements.suppress_updates:
751
- return
752
- okay = False
753
- item = None
754
- if node is not None and hasattr(node, "_item"):
755
- item = node._item
756
- if item is not None and item.IsOk():
757
- okay = True
758
- # print (f"Modified: {node}\nItem: {item}, Status={okay}")
759
- if not okay:
760
- return
761
- try:
762
- self.update_decorations(node, force=True)
763
- except RuntimeError:
764
- # A timer can update after the tree closes.
765
- return
766
- try:
767
- c = node.color
768
- self.set_color(node, c)
769
- except AttributeError:
770
- pass
771
- self.elements.signal("modified", node)
772
-
773
- def altered(self, node):
774
- """
775
- Notified that this node was altered.
776
- This node was changed in fundamental ways and nothing about this node remains trusted.
777
- @param node:
778
- @return:
779
- """
780
- if self._freeze or self.context.elements.suppress_updates:
781
- return
782
- item = node._item
783
- self.check_validity(item)
784
- try:
785
- self.update_decorations(node, force=True)
786
- except RuntimeError:
787
- # A timer can update after the tree closes.
788
- return
789
- try:
790
- c = node.color
791
- self.set_color(node, c)
792
- except AttributeError:
793
- pass
794
- self.elements.signal("altered", node)
795
-
796
- def expand(self, node):
797
- """
798
- Notified that this node was expanded.
799
-
800
- @param node:
801
- @return:
802
- """
803
- if self._freeze or self.context.elements.suppress_updates:
804
- return
805
- item = node._item
806
- self.check_validity(item)
807
- self.wxtree.ExpandAllChildren(item)
808
- self.set_expanded(item, 1)
809
-
810
- def collapse_within(self, node):
811
- # Tries to collapse children first, if there were any open,
812
- # return TRUE, if all were already collapsed, return FALSE
813
- result = False
814
- startnode = node._item
815
- try:
816
- pnode, cookie = self.wxtree.GetFirstChild(startnode)
817
- except:
818
- return
819
- were_expanded = []
820
- while pnode.IsOk():
821
- state = self.wxtree.IsExpanded(pnode)
822
- if state:
823
- result = True
824
- were_expanded.append(pnode)
825
- pnode, cookie = self.wxtree.GetNextChild(startnode, cookie)
826
- for pnode in were_expanded:
827
- cnode = self.wxtree.GetItemData(pnode)
828
- cnode.notify_collapse()
829
- return result
830
-
831
- def collapse(self, node):
832
- """
833
- Notified that this node was collapsed.
834
-
835
- @param node:
836
- @return:
837
- """
838
- if node is None:
839
- return
840
- item = node._item
841
- if item is None:
842
- return
843
- self.check_validity(item)
844
- # Special treatment for branches, they only collapse fully,
845
- # if all their childrens were collapsed already
846
- if node.type.startswith("branch"):
847
- if self.collapse_within(node):
848
- return
849
- self.wxtree.CollapseAllChildren(item)
850
- if (
851
- item is self.wxtree.GetRootItem()
852
- or self.wxtree.GetItemParent(item) is self.wxtree.GetRootItem()
853
- ):
854
- self.wxtree.Expand(self.elements.get(type="branch ops")._item)
855
- self.wxtree.Expand(self.elements.get(type="branch elems")._item)
856
- self.wxtree.Expand(self.elements.get(type="branch reg")._item)
857
-
858
- def reorder(self, node):
859
- """
860
- Notified that this node was reordered.
861
-
862
- Tree is rebuilt.
863
-
864
- @param node:
865
- @return:
866
- """
867
- self.rebuild_tree()
868
-
869
- def update(self, node):
870
- """
871
- Notified that this node has been updated.
872
- @param node:
873
- @return:
874
- """
875
- if self._freeze or self.context.elements.suppress_updates:
876
- return
877
- item = node._item
878
- if item is None:
879
- # Could be a faulty refresh during an undo.
880
- return
881
- self.check_validity(item)
882
- self.set_icon(node, force=False)
883
- self.on_force_element_update(node)
884
-
885
- def focus(self, node):
886
- """
887
- Notified that this node has been focused.
888
-
889
- It must be seen in the tree.
890
- @param node:
891
- @return:
892
- """
893
- if self._freeze or self.context.elements.suppress_updates:
894
- return
895
- item = node._item
896
- self.check_validity(item)
897
- self.wxtree.EnsureVisible(item)
898
- for s in self.wxtree.GetSelections():
899
- self.wxtree.SelectItem(s, False)
900
- self.wxtree.SelectItem(item)
901
- self.wxtree.ScrollTo(item)
902
-
903
- def on_force_element_update(self, *args):
904
- """
905
- Called by signal "element_property_reload"
906
- @param args:
907
- @return:
908
- """
909
- element = args[0]
910
- if isinstance(element, (tuple, list)):
911
- for node in element:
912
- if hasattr(node, "node"):
913
- node = node.node
914
- try:
915
- self.update_decorations(node, force=True)
916
- for refnode in node.references:
917
- self.update_decorations(refnode, force=True)
918
- except RuntimeError:
919
- # A timer can update after the tree closes.
920
- return
921
- else:
922
- try:
923
- self.update_decorations(element, force=True)
924
- for refnode in element.references:
925
- self.update_decorations(refnode, force=True)
926
- except RuntimeError:
927
- # A timer can update after the tree closes.
928
- return
929
-
930
- def on_element_update(self, *args):
931
- """
932
- Called by signal "element_property_update"
933
- @param args:
934
- @return:
935
- """
936
- element = args[0]
937
- if isinstance(element, (tuple, list)):
938
- for node in element:
939
- if hasattr(node, "node"):
940
- node = node.node
941
- try:
942
- self.update_decorations(node, force=True)
943
- except RuntimeError:
944
- # A timer can update after the tree closes.
945
- return
946
- else:
947
- try:
948
- self.update_decorations(element, force=True)
949
- except RuntimeError:
950
- # A timer can update after the tree closes.
951
- return
952
-
953
- def refresh_tree(self, node=None, level=0, source=""):
954
- """
955
- This no longer has any relevance, as the updates are properly done outside...
956
- """
957
- # if node is None:
958
- # self.context.elements.set_start_time("refresh_tree")
959
- # self.refresh_tree_counter = 0
960
- # elemtree = self.elements._tree
961
- # node = elemtree._item
962
- # level = 0
963
- # else:
964
- # self.refresh_tree_counter += 1
965
-
966
- # if node is None:
967
- # return
968
- self.update_op_labels()
969
- if node is not None:
970
- if isinstance(node, (tuple, list)):
971
- for enode in node:
972
- if hasattr(enode, "node"):
973
- enode = enode.node
974
- try:
975
- self.update_decorations(enode, force=True)
976
- except RuntimeError:
977
- # A timer can update after the tree closes.
978
- return
979
- else:
980
- try:
981
- self.update_decorations(node, force=True)
982
- except RuntimeError:
983
- # A timer can update after the tree closes.
984
- return
985
-
986
- self.wxtree._freeze = False
987
- branch_elems_item = self.elements.get(type="branch elems")._item
988
- if branch_elems_item:
989
- self.wxtree.Expand(branch_elems_item)
990
- branch_reg_item = self.elements.get(type="branch reg")._item
991
- if branch_reg_item:
992
- self.wxtree.Expand(branch_reg_item)
993
- self.context.elements.signal("warn_state_update")
994
- self.context.elements.set_end_time("full_load", display=True, delete=True)
995
-
996
- def update_warn_sign(self):
997
- # from time import perf_counter
998
- # this_call = perf_counter()
999
- # print (f"Update warn was called, time since last: {this_call-self.last_call:.3f}sec")
1000
- # self.last_call = this_call
1001
- op_node = self.elements.get(type="branch ops")
1002
- if op_node is None:
1003
- return
1004
- op_item = op_node._item
1005
-
1006
- status = ""
1007
- if op_item is None:
1008
- return
1009
-
1010
- self.wxtree.Expand(op_item)
1011
- unassigned, unburnt = self.elements.have_unburnable_elements()
1012
- if unassigned or unburnt:
1013
- self.wxtree.SetItemState(op_item, self.iconstates["warning"])
1014
- s1 = _("You have elements in disabled operations, that won't be burned")
1015
- s2 = _("You have unassigned elements, that won't be burned")
1016
- if unassigned and unburnt:
1017
- status = s1 + "\n" + s2
1018
- elif unburnt:
1019
- status = s1
1020
- elif unassigned:
1021
- status = s2
1022
- else:
1023
- self.wxtree.SetItemState(op_item, wx.TREE_ITEMSTATE_NONE)
1024
- status = ""
1025
- op_node._tooltip = status
1026
- op_node._tooltip_translated = True
1027
-
1028
- def freeze_tree(self, status=None):
1029
- if status is None:
1030
- status = not self._freeze
1031
- self._freeze = status
1032
- self.wxtree.Enable(not self._freeze)
1033
-
1034
- def was_expanded(self, node, level):
1035
- txt = self.wxtree.GetItemText(node)
1036
- chk = f"{level}-{txt}"
1037
- for elem in self.was_already_expanded:
1038
- if chk == elem:
1039
- return True
1040
- return False
1041
-
1042
- def set_expanded(self, node, level):
1043
- txt = self.wxtree.GetItemText(node)
1044
- chk = f"{level}-{txt}"
1045
- result = self.was_expanded(node, level)
1046
- if not result:
1047
- self.was_already_expanded.append(chk)
1048
-
1049
- def parse_tree(self, startnode, level):
1050
- if startnode is None:
1051
- return
1052
- cookie = 0
1053
- try:
1054
- pnode, cookie = self.wxtree.GetFirstChild(startnode)
1055
- except:
1056
- return
1057
- while pnode.IsOk():
1058
- txt = self.wxtree.GetItemText(pnode)
1059
- # That is not working as advertised...
1060
- state = self.wxtree.IsExpanded(pnode)
1061
- state = False # otherwise every thing gets expanded...
1062
- if state:
1063
- self.was_already_expanded.append(f"{level}-{txt}")
1064
- self.parse_tree(pnode, level + 1)
1065
- pnode, cookie = self.wxtree.GetNextChild(startnode, cookie)
1066
-
1067
- def restore_tree(self, startnode, level):
1068
- if startnode is None:
1069
- return
1070
- cookie = 0
1071
- try:
1072
- pnode, cookie = self.wxtree.GetFirstChild(startnode)
1073
- except:
1074
- return
1075
- while pnode.IsOk():
1076
- txt = self.wxtree.GetItemText(pnode)
1077
- chk = f"{level}-{txt}"
1078
- for elem in self.was_already_expanded:
1079
- if chk == elem:
1080
- self.wxtree.ExpandAllChildren(pnode)
1081
- break
1082
- self.parse_tree(pnode, level + 1)
1083
- pnode, cookie = self.wxtree.GetNextChild(startnode, cookie)
1084
-
1085
- def reset_expanded(self):
1086
- self.was_already_expanded = []
1087
-
1088
- def rebuild_tree(self):
1089
- """
1090
- Tree requires being deleted and completely rebuilt.
1091
-
1092
- @return:
1093
- """
1094
- # let's try to remember which branches were expanded:
1095
- self._freeze = True
1096
- self.reset_expanded()
1097
- # Safety net - if we have too many elements it will
1098
- # take too long to create all preview icons...
1099
- count = self.elements.count_elems() + self.elements.count_op()
1100
- self._too_big = bool(count > 1000)
1101
- # print(f"Was too big?! {count} -> {self._too_big}")
1102
-
1103
- self.parse_tree(self.wxtree.GetRootItem(), 0)
1104
- # Rebuild tree destroys the emphasis, so let's store it...
1105
- emphasized_list = list(self.elements.elems(emphasized=True))
1106
- elemtree = self.elements._tree
1107
- self.dragging_nodes = None
1108
- self.wxtree.DeleteAllItems()
1109
- if self.tree_images is not None:
1110
- self.tree_images.Destroy()
1111
- self.image_cache = []
1112
-
1113
- self.tree_images = wx.ImageList()
1114
- self.tree_images.Create(width=self.iconsize, height=self.iconsize)
1115
-
1116
- self.wxtree.SetImageList(self.tree_images)
1117
- elemtree._item = self.wxtree.AddRoot(self.name)
1118
-
1119
- self.wxtree.SetItemData(elemtree._item, elemtree)
1120
-
1121
- self.set_icon(
1122
- elemtree,
1123
- icon_meerk40t.GetBitmap(
1124
- False,
1125
- resize=(self.iconsize, self.iconsize),
1126
- noadjustment=True,
1127
- buffer=1,
1128
- ),
1129
- )
1130
- self.register_children(elemtree)
1131
-
1132
- node_operations = elemtree.get(type="branch ops")
1133
- self.set_icon(
1134
- node_operations,
1135
- icons8_laser_beam.GetBitmap(
1136
- resize=(self.iconsize, self.iconsize),
1137
- noadjustment=True,
1138
- buffer=1,
1139
- ),
1140
- )
1141
-
1142
- for n in node_operations.children:
1143
- self.set_icon(n, force=True)
1144
-
1145
- node_elements = elemtree.get(type="branch elems")
1146
- self.set_icon(
1147
- node_elements,
1148
- icon_canvas.GetBitmap(
1149
- resize=(self.iconsize, self.iconsize),
1150
- noadjustment=True,
1151
- buffer=1,
1152
- ),
1153
- )
1154
-
1155
- node_registration = elemtree.get(type="branch reg")
1156
- self.set_icon(
1157
- node_registration,
1158
- icon_regmarks.GetBitmap(
1159
- resize=(self.iconsize, self.iconsize),
1160
- noadjustment=True,
1161
- buffer=1,
1162
- ),
1163
- )
1164
- self.update_op_labels()
1165
- # Expand Ops, Element, and Regmarks nodes only
1166
- self.wxtree.CollapseAll()
1167
- self.wxtree.Expand(node_operations._item)
1168
- self.wxtree.Expand(node_elements._item)
1169
- self.wxtree.Expand(node_registration._item)
1170
- self.elements.signal("warn_state_update")
1171
-
1172
- # Restore emphasis
1173
- for e in emphasized_list:
1174
- e.emphasized = True
1175
- self.restore_tree(self.wxtree.GetRootItem(), 0)
1176
- self._freeze = False
1177
-
1178
- def register_children(self, node):
1179
- """
1180
- All children of this node are registered.
1181
-
1182
- @param node:
1183
- @return:
1184
- """
1185
- for child in node.children:
1186
- self.node_register(child)
1187
- self.register_children(child)
1188
- if node.type in ("group", "file"):
1189
- self.update_decorations(node, force=True)
1190
-
1191
- def unregister_children(self, node):
1192
- """
1193
- All children of this node are unregistered.
1194
- @param node:
1195
- @return:
1196
- """
1197
- for child in node.children:
1198
- self.unregister_children(child)
1199
- self.node_unregister(child)
1200
-
1201
- def node_unregister(self, node, **kwargs):
1202
- """
1203
- Node object is unregistered and item is deleted.
1204
-
1205
- @param node:
1206
- @param kwargs:
1207
- @return:
1208
- """
1209
- item = node._item
1210
- if item is None:
1211
- raise ValueError("Item was None for node " + repr(node))
1212
- self.check_validity(item)
1213
- # We might need to update the decorations for all parent objects
1214
- e = node.parent
1215
- while e is not None:
1216
- if e.type in ("group", "file"):
1217
- self.update_decorations(e, force=True)
1218
- else:
1219
- break
1220
- e = e.parent
1221
-
1222
- node.unregister_object()
1223
- self.wxtree.Delete(node._item)
1224
- for i in self.wxtree.GetSelections():
1225
- self.wxtree.SelectItem(i, False)
1226
-
1227
- def safe_color(self, color_to_set):
1228
- back_color = self.wxtree.GetBackgroundColour()
1229
- rgb = back_color.Get()
1230
- default_color = wx.Colour(
1231
- red=255 - rgb[0], green=255 - rgb[1], blue=255 - rgb[2], alpha=128
1232
- )
1233
- if color_to_set is not None and color_to_set.argb is not None:
1234
- mycolor = wx.Colour(swizzlecolor(color_to_set.argb))
1235
- if mycolor.Get() == rgb:
1236
- mycolor = default_color
1237
- else:
1238
- mycolor = default_color
1239
- return mycolor
1240
-
1241
- def node_register(self, node, pos=None, **kwargs):
1242
- """
1243
- Node.item is added/inserted. Label is updated and values are set. Icon is set.
1244
-
1245
- @param node:
1246
- @param pos:
1247
- @param kwargs:
1248
- @return:
1249
- """
1250
- parent = node.parent
1251
- parent_item = parent._item
1252
- if parent_item is None:
1253
- # We are appending items in tree before registration.
1254
- return
1255
- tree = self.wxtree
1256
- if pos is None:
1257
- node._item = tree.AppendItem(parent_item, self.name)
1258
- else:
1259
- node._item = tree.InsertItem(parent_item, pos, self.name)
1260
- tree.SetItemData(node._item, node)
1261
- self.update_decorations(node, False)
1262
- wxcolor = self.wxtree.GetForegroundColour()
1263
- if node.type == "elem text":
1264
- attribute_to_try = "fill"
1265
- else:
1266
- attribute_to_try = "stroke"
1267
- if hasattr(node, attribute_to_try):
1268
- wxcolor = self.safe_color(getattr(node, attribute_to_try))
1269
- elif hasattr(node, "color"):
1270
- wxcolor = self.safe_color(node.color)
1271
- else:
1272
- back_color = self.wxtree.GetBackgroundColour()
1273
- rgb = back_color.Get()
1274
- background = Color(rgb[0], rgb[1], rgb[2])
1275
- if background is not None:
1276
- c1 = Color("Black")
1277
- c2 = Color("White")
1278
- if Color.distance(background, c1) > Color.distance(background, c2):
1279
- textcolor = c1
1280
- else:
1281
- textcolor = c2
1282
- wxcolor = wx.Colour(swizzlecolor(textcolor))
1283
- if self.context.root.tree_colored:
1284
- try:
1285
- tree.SetItemTextColour(node._item, wxcolor)
1286
- except (AttributeError, KeyError, TypeError):
1287
- pass
1288
- # We might need to update the decorations for all parent objects
1289
- e = node.parent
1290
- while e is not None:
1291
- if e.type in ("group", "file"):
1292
- self.update_decorations(e, force=True)
1293
- else:
1294
- break
1295
- e = e.parent
1296
-
1297
- def set_enhancements(self, node):
1298
- """
1299
- Node in the tree is drawn special based on nodes current setting.
1300
- @param node:
1301
- @return:
1302
- """
1303
- tree = self.wxtree
1304
- node_item = node._item
1305
- if node_item is None:
1306
- return
1307
- if self._freeze or self.context.elements.suppress_updates:
1308
- return
1309
- tree.SetItemBackgroundColour(node_item, None)
1310
- try:
1311
- if node.highlighted:
1312
- tree.SetItemBackgroundColour(node_item, wx.LIGHT_GREY)
1313
- elif node.emphasized:
1314
- tree.SetItemBackgroundColour(node_item, wx.Colour(0x80A0A0))
1315
- elif node.targeted:
1316
- tree.SetItemBackgroundColour(node_item, wx.Colour(0xA080A0))
1317
- except AttributeError:
1318
- pass
1319
-
1320
- def set_color(self, node, color=None):
1321
- """
1322
- Node color is set.
1323
-
1324
- @param node: Not to be colored
1325
- @param color: Color to be set.
1326
- @return:
1327
- """
1328
- if not self.context.root.tree_colored:
1329
- return
1330
- item = node._item
1331
- if item is None:
1332
- return
1333
- if self._freeze or self.context.elements.suppress_updates:
1334
- return
1335
- tree = self.wxtree
1336
- wxcolor = self.safe_color(color)
1337
- tree.SetItemTextColour(item, wxcolor)
1338
-
1339
- def create_image_from_node(self, node):
1340
- image = None
1341
- mini_icon = self.context.root.mini_icon and not self._too_big
1342
- c = None
1343
- self.cache_requests += 1
1344
- cached_id = -1
1345
- # Do we have a standard representation?
1346
- defaultcolor = Color("black")
1347
- if mini_icon:
1348
- if node.type == "elem image":
1349
- image = self.renderer.make_thumbnail(
1350
- node.active_image, width=self.iconsize, height=self.iconsize
1351
- )
1352
- else:
1353
- # Establish colors (and some images)
1354
- if node.type.startswith("op ") or node.type.startswith("util "):
1355
- if (
1356
- hasattr(node, "color")
1357
- and node.color is not None
1358
- and node.color.argb is not None
1359
- ):
1360
- c = node.color
1361
- elif node.type == "reference":
1362
- c, image, cached_id = self.create_image_from_node(node.node)
1363
- elif node.type.startswith("elem "):
1364
- if (
1365
- hasattr(node, "stroke")
1366
- and node.stroke is not None
1367
- and node.stroke.argb is not None
1368
- ):
1369
- c = node.stroke
1370
- if node.type.startswith("elem ") and node.type != "elem point":
1371
- image = self.renderer.make_raster(
1372
- node,
1373
- node.paint_bounds,
1374
- width=self.iconsize,
1375
- height=self.iconsize,
1376
- bitmap=True,
1377
- keep_ratio=True,
1378
- )
1379
- else:
1380
- # Establish at least colors (and an image for a reference)
1381
- if node.type.startswith("op ") or node.type.startswith("util "):
1382
- if (
1383
- hasattr(node, "color")
1384
- and node.color is not None
1385
- and node.color.argb is not None
1386
- ):
1387
- c = node.color
1388
- elif node.type == "reference":
1389
- c, image, cached_id = self.create_image_from_node(node.node)
1390
- elif node.type == "elem text":
1391
- if (
1392
- hasattr(node, "fill")
1393
- and node.fill is not None
1394
- and node.fill.argb is not None
1395
- ):
1396
- c = node.fill
1397
- elif node.type.startswith("elem ") or node.type.startswith("effect "):
1398
- if (
1399
- hasattr(node, "stroke")
1400
- and node.stroke is not None
1401
- and node.stroke.argb is not None
1402
- ):
1403
- c = node.stroke
1404
-
1405
- # Have we already established an image, if no let's use the default
1406
- if image is None:
1407
- img_obj = None
1408
- found = ""
1409
- tofind = node.type
1410
- if tofind == "util console":
1411
- # Let's see whether we find the keyword...
1412
- for key in self.default_images:
1413
- if key.startswith("console "):
1414
- skey = key[8:]
1415
- if node.command is not None and skey in node.command:
1416
- found = key
1417
- break
1418
-
1419
- if not found and tofind in self.default_images:
1420
- # print (f"Wasn't found use {tofind}")
1421
- found = tofind
1422
-
1423
- if found:
1424
- for stored_key, stored_color, img_obj, c_id in self.image_cache:
1425
- if stored_key == found and stored_color == c:
1426
- image = img_obj
1427
- cached_id = c_id
1428
- self.cache_hits += 1
1429
- # print (f"Restore id {cached_id} for {c} - {found}")
1430
- break
1431
- if image is None:
1432
- # has not been found yet...
1433
- img_obj = self.default_images[found]
1434
- image = img_obj.GetBitmap(
1435
- color=c,
1436
- resize=(self.iconsize, self.iconsize),
1437
- noadjustment=True,
1438
- buffer=1,
1439
- )
1440
- cached_id = self.tree_images.Add(bitmap=image)
1441
- # print(f"Store id {cached_id} for {c} - {found}")
1442
- self.image_cache.append((found, c, image, cached_id))
1443
-
1444
- if c is None:
1445
- c = defaultcolor
1446
- # print (f"Icon gives color: {c} and cached-id={cached_id}")
1447
- return c, image, cached_id
1448
-
1449
- def set_icon(self, node, icon=None, force=False):
1450
- """
1451
- Node icon to be created and applied
1452
-
1453
- @param node: Node to have the icon set.
1454
- @param icon: overriding icon to be forcibly set, rather than a default.
1455
- @param force: force the icon setting
1456
- @return: item_id if newly created / update
1457
- """
1458
- root = self
1459
- drawmode = self.elements.root.draw_mode
1460
- if drawmode & DRAW_MODE_ICONS != 0:
1461
- return
1462
- # if self._freeze or self.context.elements.suppress_updates:
1463
- # return
1464
- if node is None:
1465
- return
1466
- try:
1467
- item = node._item
1468
- except AttributeError:
1469
- return # Node.item can be none if launched from ExecuteJob where the nodes are not part of the tree.
1470
- if node._item is None:
1471
- return
1472
- tree = root.wxtree
1473
- if icon is None:
1474
- if force is None:
1475
- force = False
1476
- image_id = tree.GetItemImage(item)
1477
- if image_id >= self.tree_images.ImageCount:
1478
- image_id = -1
1479
- if image_id >= 0 and not force:
1480
- # Don't do it twice
1481
- return image_id
1482
-
1483
- # print ("Default size for iconsize, tree_images", self.iconsize, self.tree_images.GetSize())
1484
- c, image, cached_id = self.create_image_from_node(node)
1485
-
1486
- if image is not None:
1487
- if cached_id >= 0:
1488
- image_id = cached_id
1489
- elif image_id < 0:
1490
- image_id = self.tree_images.Add(bitmap=image)
1491
- else:
1492
- self.tree_images.Replace(index=image_id, bitmap=image)
1493
- tree.SetItemImage(item, image=image_id)
1494
- # Let's have a look at all references....
1495
- for subnode in node.references:
1496
- try:
1497
- subitem = subnode._item
1498
- except AttributeError:
1499
- subitem = None
1500
- if subitem is None:
1501
- continue
1502
- tree.SetItemImage(subitem, image=image_id)
1503
-
1504
- if c is not None:
1505
- self.set_color(node, c)
1506
-
1507
- else:
1508
- image_id = tree.GetItemImage(item)
1509
- if image_id >= self.tree_images.ImageCount:
1510
- image_id = -1
1511
- # Reset Image Node in List
1512
- if image_id < 0:
1513
- image_id = self.tree_images.Add(bitmap=icon)
1514
- else:
1515
- self.tree_images.Replace(index=image_id, bitmap=icon)
1516
-
1517
- tree.SetItemImage(item, image=image_id)
1518
- return image_id
1519
-
1520
- def update_op_labels(self):
1521
- startnode = self.elements.get(type="branch ops")._item
1522
- if startnode is None:
1523
- # Branch op never populated the tree, we cannot update sublayer.
1524
- return
1525
- child, cookie = self.wxtree.GetFirstChild(startnode)
1526
- while child.IsOk():
1527
- node = self.wxtree.GetItemData(child) # Make sure the map is updated...
1528
- self.update_decorations(node=node, force=True)
1529
- child, cookie = self.wxtree.GetNextChild(startnode, cookie)
1530
-
1531
- def update_decorations(self, node, force=False):
1532
- """
1533
- Updates the decorations for a particular node/tree item
1534
-
1535
- @param node:
1536
- @param force: force updating decorations
1537
- @return:
1538
- """
1539
-
1540
- def my_create_label(node, text=None):
1541
- if text is None:
1542
- try:
1543
- text = node._formatter
1544
- except AttributeError:
1545
- text = "{element_type}:{id}"
1546
- # Just for the optical impression (who understands what a "Rect: None" means),
1547
- # lets replace some of the more obvious ones...
1548
- mymap = node.default_map()
1549
- # We change power to either ppi or percent
1550
- if "power" in mymap and "ppi" in mymap and "percent" in mymap:
1551
- self.context.device.setting(
1552
- bool, "use_percent_for_power_display", False
1553
- )
1554
- if self.context.device.use_percent_for_power_display:
1555
- mymap["power"] = mymap["percent"]
1556
- if "speed" in mymap and "speed_mm_min" in mymap:
1557
- self.context.device.setting(bool, "use_mm_min_for_speed_display", False)
1558
- if self.context.device.use_mm_min_for_speed_display:
1559
- text = text.replace("mm/s", "mm/min")
1560
- mymap["speed"] = mymap["speed_mm_min"]
1561
- mymap["speed_unit"] = "mm/min"
1562
- else:
1563
- mymap["speed_unit"] = "mm/s"
1564
- for key in mymap:
1565
- if hasattr(node, key) and key in mymap and mymap[key] == "None":
1566
- if getattr(node, key) is None:
1567
- mymap[key] = "-"
1568
- # There are a couple of translatable entries,
1569
- # to make sure we don't get an unwanted translation we add
1570
- # a special pattern to it
1571
- translatable = (
1572
- "element_type",
1573
- "enabled",
1574
- )
1575
- pattern = "_TREE_"
1576
- for key in mymap:
1577
- if key in translatable:
1578
- # Original value
1579
- std = mymap[key]
1580
- value = _(pattern + std)
1581
- if not value.startswith(pattern):
1582
- mymap[key] = value
1583
- try:
1584
- res = text.format_map(mymap)
1585
- except KeyError:
1586
- res = text
1587
- return res
1588
-
1589
- def get_formatter(nodetype):
1590
- default = self.context.elements.lookup(f"format/{nodetype}")
1591
- lbl = nodetype.replace(" ", "_")
1592
- check_string = f"formatter_{lbl}_active"
1593
- pattern_string = f"formatter_{lbl}"
1594
- self.context.device.setting(bool, check_string, False)
1595
- self.context.device.setting(str, pattern_string, default)
1596
- bespoke = getattr(self.context.device, check_string, False)
1597
- pattern = getattr(self.context.device, pattern_string, "")
1598
- if bespoke and pattern is not None and pattern != "":
1599
- default = pattern
1600
- return default
1601
-
1602
- if force is None:
1603
- force = False
1604
- if node._item is None:
1605
- # This node is not registered the tree has desynced.
1606
- self.rebuild_tree()
1607
- return
1608
-
1609
- self.set_icon(node, force=force)
1610
- if hasattr(node, "node") and node.node is not None:
1611
- formatter = get_formatter(node.node.type)
1612
- if node.node.type.startswith("op "):
1613
- if not self.context.elements.op_show_default:
1614
- if hasattr(node.node, "speed"):
1615
- node.node.speed = node.node.speed
1616
- if hasattr(node.node, "power"):
1617
- node.node.power = node.node.power
1618
- if hasattr(node.node, "dwell_time"):
1619
- node.node.dwell_time = node.node.dwell_time
1620
-
1621
- checker = f"dangerlevel_{node.type.replace(' ', '_')}"
1622
- if hasattr(self.context.device, checker):
1623
- maxspeed_minpower = getattr(self.context.device, checker)
1624
- if (
1625
- isinstance(maxspeed_minpower, (tuple, list))
1626
- and len(maxspeed_minpower) == 8
1627
- ):
1628
- # minpower, maxposer, minspeed, maxspeed
1629
- # print ("Yes: ", checker, maxspeed_minpower)
1630
- danger = False
1631
- if hasattr(node.node, "power"):
1632
- value = node.node.power
1633
- if maxspeed_minpower[0] and value < maxspeed_minpower[1]:
1634
- danger = True
1635
- if maxspeed_minpower[2] and value > maxspeed_minpower[3]:
1636
- danger = True
1637
- if hasattr(node.node, "speed"):
1638
- value = node.node.speed
1639
- if maxspeed_minpower[4] and value < maxspeed_minpower[5]:
1640
- danger = True
1641
- if maxspeed_minpower[6] and value > maxspeed_minpower[7]:
1642
- danger = True
1643
- if hasattr(node.node, "dangerous"):
1644
- node.node.dangerous = danger
1645
- else:
1646
- setattr(self.context.device, checker, [False, 0] * 4)
1647
- print(
1648
- f"That's strange {checker}: {type(maxspeed_minpower).__name__}"
1649
- )
1650
- # node.node.is_dangerous(maxspeed, minpower)
1651
- # label = "*" + node.node.create_label(formatter)
1652
- label = "*" + my_create_label(node.node, formatter)
1653
- else:
1654
- formatter = get_formatter(node.type)
1655
- if node.type.startswith("op "):
1656
- # Not too elegant... op nodes should have a property default_speed, default_power
1657
- if not self.context.elements.op_show_default:
1658
- if hasattr(node, "speed"):
1659
- node.speed = node.speed
1660
- if hasattr(node, "power"):
1661
- node.power = node.power
1662
- if hasattr(node, "dwell_time"):
1663
- node.dwell_time = node.dwell_time
1664
- checker = f"dangerlevel_{node.type.replace(' ', '_')}"
1665
- if hasattr(self.context.device, checker):
1666
- maxspeed_minpower = getattr(self.context.device, checker)
1667
- if (
1668
- isinstance(maxspeed_minpower, (tuple, list))
1669
- and len(maxspeed_minpower) == 8
1670
- ):
1671
- # minpower, maxposer, minspeed, maxspeed
1672
- # print ("Yes: ", checker, maxspeed_minpower)
1673
- danger = False
1674
- if hasattr(node, "power"):
1675
- value = float(node.power)
1676
- if maxspeed_minpower[0] and value < maxspeed_minpower[1]:
1677
- danger = True
1678
- if maxspeed_minpower[2] and value > maxspeed_minpower[3]:
1679
- danger = True
1680
- if hasattr(node, "speed"):
1681
- value = float(node.speed)
1682
- if maxspeed_minpower[4] and value < maxspeed_minpower[5]:
1683
- danger = True
1684
- if maxspeed_minpower[6] and value > maxspeed_minpower[7]:
1685
- danger = True
1686
- if hasattr(node, "dangerous"):
1687
- node.dangerous = danger
1688
- else:
1689
- setattr(self.context.device, checker, [False, 0] * 4)
1690
- print(
1691
- f"That's strange {checker}: {type(maxspeed_minpower).__name__}"
1692
- )
1693
- # label = node.create_label(formatter)
1694
- label = my_create_label(node, formatter)
1695
-
1696
- self.wxtree.SetItemText(node._item, label)
1697
- if node.type == "elem text":
1698
- attribute_to_try = "fill"
1699
- else:
1700
- attribute_to_try = "stroke"
1701
- wxcolor = None
1702
- if hasattr(node, attribute_to_try):
1703
- wxcolor = self.safe_color(getattr(node, attribute_to_try))
1704
- elif hasattr(node, "color"):
1705
- wxcolor = self.safe_color(node.color)
1706
- else:
1707
- back_color = self.wxtree.GetBackgroundColour()
1708
- rgb = back_color.Get()
1709
- background = Color(rgb[0], rgb[1], rgb[2])
1710
- if background is not None:
1711
- c1 = Color("Black")
1712
- c2 = Color("White")
1713
- if Color.distance(background, c1) > Color.distance(background, c2):
1714
- textcolor = c1
1715
- else:
1716
- textcolor = c2
1717
- wxcolor = wx.Colour(swizzlecolor(textcolor))
1718
- if self.context.root.tree_colored:
1719
- try:
1720
- self.wxtree.SetItemTextColour(node._item, wxcolor)
1721
- except (AttributeError, KeyError, TypeError):
1722
- pass
1723
-
1724
- state_num = -1
1725
- if node is self.elements.get(type="branch ops"):
1726
- unassigned, unburnt = self.elements.have_unburnable_elements()
1727
- if unassigned or unburnt:
1728
- state_num = self.iconstates["warning"]
1729
- else:
1730
- # Has the node a lock attribute?
1731
- if hasattr(node, "lock"):
1732
- lockit = node.lock
1733
- else:
1734
- lockit = False
1735
- if lockit:
1736
- state_num = self.iconstates["lock"]
1737
- scene = getattr(self.context.root, "mainscene", None)
1738
- if scene is not None:
1739
- if node == scene.pane.reference_object:
1740
- state_num = self.iconstates["refobject"]
1741
- if state_num < 0:
1742
- state_num = wx.TREE_ITEMSTATE_NONE
1743
- if (
1744
- node.type in op_nodes
1745
- and hasattr(node, "is_visible")
1746
- and not node.is_visible
1747
- ):
1748
- state_num = self.iconstates["ghost"]
1749
- self.wxtree.SetItemState(node._item, state_num)
1750
-
1751
- def on_drag_begin_handler(self, event):
1752
- """
1753
- Drag handler begin for the tree.
1754
-
1755
- @param event:
1756
- @return:
1757
- """
1758
-
1759
- def typefamily(typename):
1760
- # Combine similar nodetypes
1761
- if typename.startswith("op "):
1762
- result = "op"
1763
- elif typename.startswith("elem "):
1764
- result = "elem"
1765
- else:
1766
- result = typename
1767
- return result
1768
-
1769
- self.dragging_nodes = None
1770
-
1771
- pt = event.GetPoint()
1772
- drag_item, _ = self.wxtree.HitTest(pt)
1773
-
1774
- if drag_item is None or drag_item.ID is None or not drag_item.IsOk():
1775
- # errmsg = ""
1776
- # if drag_item is None:
1777
- # errmsg = "item was none"
1778
- # elif drag_item.ID is None:
1779
- # errmsg = "id was none"
1780
- # elif not drag_item.IsOk():
1781
- # errmsg = "IsOk was false"
1782
- # print (f"Drag item was wrong: {errmsg}")
1783
- event.Skip()
1784
- return
1785
-
1786
- self.dragging_nodes = [
1787
- self.wxtree.GetItemData(item) for item in self.wxtree.GetSelections()
1788
- ]
1789
- if len(self.dragging_nodes) == 0:
1790
- # print ("Dragging_nodes was empty")
1791
- event.Skip()
1792
- return
1793
-
1794
- t = typefamily(self.dragging_nodes[0].type)
1795
- for n in self.dragging_nodes:
1796
- tt = typefamily(n.type)
1797
- if t != tt:
1798
- # Different typefamilies
1799
- # print ("Different typefamilies")
1800
- event.Skip()
1801
- return
1802
- if not n.is_draggable():
1803
- # print ("Element was not draggable")
1804
- event.Skip()
1805
- return
1806
- event.Allow()
1807
-
1808
- def on_drag_end_handler(self, event):
1809
- """
1810
- Drag end handler for the tree
1811
-
1812
- @param event:
1813
- @return:
1814
- """
1815
- if self.dragging_nodes is None:
1816
- event.Skip()
1817
- return
1818
-
1819
- drop_item = event.GetItem()
1820
- if drop_item is None or drop_item.ID is None:
1821
- event.Skip()
1822
- return
1823
- drop_node = self.wxtree.GetItemData(drop_item)
1824
- if drop_node is None:
1825
- event.Skip()
1826
- return
1827
- skip = True
1828
- # We extend the logic by calling the appropriate elems routine
1829
- skip = not self.elements.drag_and_drop(self.dragging_nodes, drop_node)
1830
- if skip:
1831
- event.Skip()
1832
- else:
1833
- event.Allow()
1834
- # Make sure that the drop node is visible
1835
- self.wxtree.Expand(drop_item)
1836
- self.wxtree.EnsureVisible(drop_item)
1837
- self.refresh_tree(source="drag end")
1838
- # Do the dragging_nodes contain an operation?
1839
- # Let's give an indication of that, as this may
1840
- # have led to the creation of a new reference
1841
- # node. For whatever reason this is not recognised
1842
- # otherwise...
1843
- if not self.dragging_nodes:
1844
- # Dragging nodes were cleared (we must have rebuilt the entire tree)
1845
- return
1846
- for node in self.dragging_nodes:
1847
- if node.type.startswith("op"):
1848
- self.context.signal("tree_changed")
1849
- break
1850
- # self.rebuild_tree()
1851
- self.dragging_nodes = None
1852
-
1853
- def on_mouse_over(self, event):
1854
- # establish the item we are over...
1855
- event.Skip()
1856
- ttip = ""
1857
- pt = event.GetPosition()
1858
- item, flags = self.wxtree.HitTest(pt)
1859
- if self._last_hover_item is item:
1860
- return
1861
- if item:
1862
- node = self.wxtree.GetItemData(item)
1863
- if node is not None:
1864
- if hasattr(node, "_tooltip"):
1865
- # That has precedence and will be displayed in all cases
1866
- ttip = node._tooltip
1867
- elif not self.context.disable_tree_tool_tips:
1868
- if node.type == "blob":
1869
- ttip = _(
1870
- "This is binary data imported or generated\n"
1871
- + "that will be sent directly to the laser.\n"
1872
- + "Double-click to view."
1873
- )
1874
- elif node.type == "op cut":
1875
- ttip = _(
1876
- "This will engrave/cut the elements contained,\n"
1877
- + "following the vector-paths of the data.\n"
1878
- + "(Usually done last)"
1879
- )
1880
- elif node.type == "op engrave":
1881
- ttip = _(
1882
- "This will engrave the elements contained,\n"
1883
- + "following the vector-paths of the data."
1884
- )
1885
- elif node.type == "op image":
1886
- ttip = _(
1887
- "This engraves already created images pixel by pixel,\n"
1888
- + "applying the settings to the individual pictures"
1889
- )
1890
- elif node.type == "op raster":
1891
- ttip = _(
1892
- "This will render all contained elements\n"
1893
- + "into an intermediary image which then will be\n"
1894
- + "engraved pixel by pixel."
1895
- )
1896
- elif node.type == "op dots":
1897
- ttip = _(
1898
- "This will engrave a single point for a given period of time"
1899
- )
1900
- elif node.type == "util console":
1901
- ttip = _(
1902
- "This allows to execute an arbitrary command during the engrave process"
1903
- )
1904
- elif node.type == "util goto":
1905
- ttip = _(
1906
- "This will send the laser back to its logical start position"
1907
- )
1908
- elif node.type == "util home":
1909
- ttip = _(
1910
- "This will send the laser back to its physical start position"
1911
- )
1912
- elif node.type == "util input":
1913
- ttip = _(
1914
- "This will wait for active IO bits on the laser (mainly fibre laser for now)"
1915
- )
1916
- elif node.type == "util output":
1917
- ttip = _(
1918
- "This will set some IO bits on the laser (mainly fibre laser for now)"
1919
- )
1920
- elif node.type == "util wait":
1921
- ttip = _(
1922
- "This will pause the engrave process for a given period"
1923
- )
1924
- elif node.type == "branch reg":
1925
- ttip = _(
1926
- "The elements under this section will not be engraved,\n"
1927
- + "they can serve as a template or registration marks."
1928
- )
1929
- elif node.type == "elem line":
1930
- bb = node.bounds
1931
- if bb is not None:
1932
- ww = Length(amount=bb[2] - bb[0], digits=1)
1933
- hh = Length(amount=bb[3] - bb[1], digits=1)
1934
- ll = Length(amount=node.shape.length(), digits=1)
1935
- ttip = f"{ww.length_mm} x {hh.length_mm}, L={ll.length_mm}"
1936
- elif node.type == "elem rect":
1937
- bb = node.bounds
1938
- if bb is not None:
1939
- ww = Length(amount=bb[2] - bb[0], digits=1)
1940
- hh = Length(amount=bb[3] - bb[1], digits=1)
1941
- ll = Length(amount=node.shape.length(), digits=1)
1942
- ttip = f"{ww.length_mm} x {hh.length_mm}, L={ll.length_mm}"
1943
- elif node.type == "elem polyline":
1944
- bb = node.bounds
1945
- if bb is not None:
1946
- ww = Length(amount=bb[2] - bb[0], digits=1)
1947
- hh = Length(amount=bb[3] - bb[1], digits=1)
1948
- ll = Length(amount=node.shape.length(), digits=1)
1949
- ttip = f"{ww.length_mm} x {hh.length_mm}, L={ll.length_mm}"
1950
- ttip += f"\n{len(node.shape.points)} pts"
1951
- elif node.type == "elem ellipse":
1952
- bb = node.bounds
1953
- if bb is not None:
1954
- ww = Length(amount=bb[2] - bb[0], digits=1)
1955
- hh = Length(amount=bb[3] - bb[1], digits=1)
1956
- ttip = f"{ww.length_mm} x {hh.length_mm}"
1957
- elif node.type == "elem path":
1958
- bb = node.bounds
1959
- if bb is not None:
1960
- ww = Length(amount=bb[2] - bb[0], digits=1)
1961
- hh = Length(amount=bb[3] - bb[1], digits=1)
1962
- ttip = f"{ww.length_mm} x {hh.length_mm}"
1963
- ttip += f"\n{len(node.path)} segments"
1964
- elif node.type == "elem text":
1965
- bb = node.bounds
1966
- if bb is not None:
1967
- ww = Length(amount=bb[2] - bb[0], digits=1)
1968
- hh = Length(amount=bb[3] - bb[1], digits=1)
1969
- ttip = f"{ww.length_mm} x {hh.length_mm}"
1970
- # ttip += f"\n{node.font}"
1971
- elif node.type == "place current":
1972
- ttip = _(
1973
- "This is a placeholder for the 'place current' operation"
1974
- )
1975
- elif node.type == "place point":
1976
- ttip = _(
1977
- "This will define an origin from where all the elements in this scene\n"
1978
- + "will be plotted. You can have multiple such job start points"
1979
- )
1980
- elif node.type == "effect hatch":
1981
- ttip = _(
1982
- "This is a special node that will consume any other closed path\n"
1983
- + "you drag onto it and will fill the shape with a line pattern.\n"
1984
- + "To activate / deactivate this effect please use the context menu."
1985
- )
1986
- self._last_hover_item = item
1987
- if ttip != self.wxtree.GetToolTipText():
1988
- self.wxtree.SetToolTip(ttip)
1989
-
1990
- def on_item_right_click(self, event):
1991
- """
1992
- Right click of element in tree.
1993
-
1994
- @param event:
1995
- @return:
1996
- """
1997
- item = event.GetItem()
1998
- if item is None:
1999
- return
2000
- node = self.wxtree.GetItemData(item)
2001
-
2002
- create_menu(self.gui, node, self.elements)
2003
-
2004
- def on_item_activated(self, event):
2005
- """
2006
- Tree item is double-clicked. Launches PropertyWindow associated with that object.
2007
-
2008
- @param event:
2009
- @return:
2010
- """
2011
- item = event.GetItem()
2012
- node = self.wxtree.GetItemData(item)
2013
- activate = self.elements.lookup("function/open_property_window_for_node")
2014
- if activate is not None:
2015
- activate(node)
2016
-
2017
- def activate_selected_node(self, *args):
2018
- """
2019
- Call activated on the first emphasized node.
2020
-
2021
- @param args:
2022
- @return:
2023
- """
2024
- first_element = self.elements.first_element(emphasized=True)
2025
- if hasattr(first_element, "node"):
2026
- # Reference
2027
- first_element = first_element.node
2028
- activate = self.elements.lookup("function/open_property_window_for_node")
2029
- if activate is not None:
2030
- activate(first_element)
2031
-
2032
- def on_item_selection_changed(self, event):
2033
- """
2034
- Tree menu item is changed. Modify the selection.
2035
-
2036
- @param event:
2037
- @return:
2038
- """
2039
- if self.do_not_select:
2040
- # Do not select is part of a linux correction where moving nodes around in a drag and drop fashion could
2041
- # cause them to appear to drop invalid nodes.
2042
- return
2043
-
2044
- # Just out of curiosity, is there no image set? Then just do it again.
2045
- item = event.GetItem()
2046
- if item:
2047
- image_id = self.wxtree.GetItemImage(item)
2048
- if image_id >= self.tree_images.ImageCount:
2049
- image_id = -1
2050
- if image_id < 0:
2051
- node = self.wxtree.GetItemData(item)
2052
- if node is not None:
2053
- self.set_icon(node, force=True)
2054
-
2055
- selected = [
2056
- self.wxtree.GetItemData(item) for item in self.wxtree.GetSelections()
2057
- ]
2058
-
2059
- emphasized = list(selected)
2060
- for i in range(len(emphasized)):
2061
- node = emphasized[i]
2062
- if node is None or node.type is None:
2063
- # Rare issue seen building the tree during materials test and the node type didn't exist.
2064
- return
2065
- if node.type == "reference":
2066
- emphasized[i] = node.node
2067
- elif node.type.startswith("op"):
2068
- for n in node.flat(types=("reference",), cascade=False):
2069
- try:
2070
- emphasized.append(n.node)
2071
- except Exception:
2072
- pass
2073
- self.elements.set_emphasis(emphasized)
2074
- self.elements.set_selected(selected)
2075
- # self.refresh_tree(source="on_item_selection")
2076
- event.Allow()
2077
-
2078
- def select_in_tree_by_emphasis(self, origin, *args):
2079
- """
2080
- Selected the actual `wx.tree` control those items which are currently emphasized.
2081
-
2082
- @return:
2083
- """
2084
- self.do_not_select = True
2085
- self.wxtree.UnselectAll()
2086
-
2087
- for e in self.elements.elems_nodes(emphasized=True):
2088
- self.wxtree.SelectItem(e._item, True)
2089
- self.do_not_select = False
1
+ import wx
2
+ from wx import aui
3
+
4
+ from meerk40t.core.elements.element_types import op_nodes, elem_nodes
5
+
6
+ from ..core.units import Length
7
+ from ..kernel import signal_listener
8
+ from ..svgelements import Color
9
+ from .basicops import BasicOpPanel
10
+ from .icons import (
11
+ icon_bell,
12
+ icon_bmap_text,
13
+ icon_canvas,
14
+ icon_close_window,
15
+ icon_console,
16
+ icon_distort,
17
+ icon_effect_hatch,
18
+ icon_effect_wobble,
19
+ icon_external,
20
+ icon_internal,
21
+ icon_line,
22
+ icon_meerk40t,
23
+ icon_mk_ellipse,
24
+ icon_mk_polyline,
25
+ icon_mk_rectangular,
26
+ icon_path,
27
+ icon_points,
28
+ icon_regmarks,
29
+ icon_return,
30
+ icon_round_stop,
31
+ icon_timer,
32
+ icon_tree,
33
+ icon_warning,
34
+ icons8_direction,
35
+ icons8_file,
36
+ icons8_ghost,
37
+ icons8_group_objects,
38
+ icons8_home_filled,
39
+ icons8_image,
40
+ icons8_laser_beam,
41
+ icons8_laserbeam_weak,
42
+ icons8_lock,
43
+ icons8_r_white,
44
+ )
45
+ from .laserrender import DRAW_MODE_ICONS, LaserRender, swizzlecolor
46
+ from .mwindow import MWindow
47
+ from .wxutils import (
48
+ StaticBoxSizer,
49
+ create_menu,
50
+ dip_size,
51
+ get_key_name,
52
+ is_navigation_key,
53
+ wxButton,
54
+ wxTreeCtrl,
55
+ )
56
+
57
+ _ = wx.GetTranslation
58
+
59
+
60
+ def register_panel_tree(window, context):
61
+ lastpage = context.root.setting(int, "tree_panel_page", 1)
62
+ if lastpage is None or lastpage < 0 or lastpage > 2:
63
+ lastpage = 0
64
+
65
+ basic_op = BasicOpPanel(window, wx.ID_ANY, context=context)
66
+ wxtree = TreePanel(window, wx.ID_ANY, context=context)
67
+
68
+ def on_panel_change(context):
69
+ def handler(event):
70
+ mycontext.root.setting(int, "tree_panel_page", 1)
71
+ pagenum = notetab.GetSelection()
72
+ setattr(mycontext.root, "tree_panel_page", pagenum)
73
+ if pagenum == 0:
74
+ basic_op.pane_show()
75
+ wxtree.pane_hide()
76
+ else:
77
+ basic_op.pane_hide()
78
+ wxtree.pane_show()
79
+
80
+ mycontext = context
81
+ return handler
82
+
83
+ # ARGGH, the color setting via the ArtProvider does only work
84
+ # if you set the tabs to the bottom! wx.aui.AUI_NB_BOTTOM
85
+ notetab = wx.aui.AuiNotebook(
86
+ window,
87
+ wx.ID_ANY,
88
+ style=wx.aui.AUI_NB_TAB_EXTERNAL_MOVE
89
+ | wx.aui.AUI_NB_SCROLL_BUTTONS
90
+ | wx.aui.AUI_NB_TAB_SPLIT
91
+ | wx.aui.AUI_NB_TAB_MOVE
92
+ | wx.aui.AUI_NB_BOTTOM,
93
+ )
94
+ context.themes.set_window_colors(notetab)
95
+ bg_std = context.themes.get("win_bg")
96
+ bg_active = context.themes.get("highlight")
97
+ notetab.GetArtProvider().SetColour(bg_std)
98
+ notetab.GetArtProvider().SetActiveColour(bg_active)
99
+
100
+ pane = (
101
+ aui.AuiPaneInfo()
102
+ .Name("tree")
103
+ .Right()
104
+ .MinSize(200, 180)
105
+ .BestSize(300, 270)
106
+ .FloatingSize(300, 270)
107
+ .LeftDockable()
108
+ .RightDockable()
109
+ .BottomDockable(False)
110
+ .Caption(_("Tree"))
111
+ .CaptionVisible(not context.pane_lock)
112
+ .TopDockable(False)
113
+ )
114
+ pane.helptext = _("Tree containing all objects")
115
+ notetab.AddPage(basic_op, _("Burn-Operation"))
116
+ notetab.AddPage(wxtree, _("Details"))
117
+ notetab.SetSelection(lastpage)
118
+ notetab.Bind(aui.EVT_AUINOTEBOOK_PAGE_CHANGED, on_panel_change(context))
119
+ pane.dock_proportion = 500
120
+ pane.control = notetab
121
+ window.on_pane_create(pane)
122
+ context.register("pane/tree", pane)
123
+
124
+
125
+ class TreePanel(wx.Panel):
126
+ def __init__(self, *args, context=None, **kwds):
127
+ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
128
+ wx.Panel.__init__(self, *args, **kwds)
129
+ self.context = context
130
+ self.context.themes.set_window_colors(self)
131
+ # Define Tree
132
+ self.wxtree = wxTreeCtrl(
133
+ self,
134
+ wx.ID_ANY,
135
+ style=wx.TR_MULTIPLE
136
+ | wx.TR_HAS_BUTTONS
137
+ | wx.TR_HIDE_ROOT
138
+ | wx.TR_LINES_AT_ROOT,
139
+ )
140
+ # try:
141
+ # res = wx.SystemSettings().GetAppearance().IsDark()
142
+ # except AttributeError:
143
+ # res = wx.SystemSettings().GetColour(wx.SYS_COLOUR_WINDOW)[0] < 127
144
+ self.SetHelpText(
145
+ "tree"
146
+ ) # That will be used for all controls in this window, unless stated differently
147
+
148
+ self.setup_warn_panel()
149
+
150
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
151
+ main_sizer.Add(self.wxtree, 1, wx.EXPAND, 0)
152
+ main_sizer.Add(self.warn_panel, 0, wx.EXPAND, 0)
153
+ self.SetSizer(main_sizer)
154
+ self.__set_tree()
155
+ self.wxtree.Bind(wx.EVT_KEY_UP, self.on_key_up)
156
+ self.wxtree.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
157
+ self._keybind_channel = self.context.channel("keybinds")
158
+
159
+ self.context.signal("rebuild_tree", "all")
160
+
161
+ def setup_warn_panel(self):
162
+ def fix_unassigned_create(event):
163
+ previous = self.context.elements.classify_autogenerate
164
+ self.context.elements.classify_autogenerate = True
165
+ target_list = list(self.context.elements.unassigned_elements())
166
+ self.context.elements.classify(target_list)
167
+ self.context.elements.classify_autogenerate = previous
168
+ self.context.elements.signal("refresh_tree")
169
+
170
+ def fix_unassigned_used(event):
171
+ previous = self.context.elements.classify_autogenerate
172
+ self.context.elements.classify_autogenerate = False
173
+ target_list = list(self.context.elements.unassigned_elements())
174
+ self.context.elements.classify(target_list)
175
+ self.context.elements.classify_autogenerate = previous
176
+ self.context.elements.signal("refresh_tree")
177
+
178
+ def fix_unburnt(event):
179
+ to_reload = []
180
+ for node in self.context.elements.elems():
181
+ will_be_burnt = False
182
+ first_op = None
183
+ for refnode in node._references:
184
+ op = refnode.parent
185
+ if op is not None:
186
+ try:
187
+ if op.output:
188
+ will_be_burnt = True
189
+ break
190
+ else:
191
+ if first_op is None:
192
+ first_op = op
193
+ except AttributeError:
194
+ pass
195
+ if not will_be_burnt and first_op is not None:
196
+ try:
197
+ first_op.output = True
198
+ to_reload.append(first_op)
199
+ except AttributeError:
200
+ pass
201
+ if to_reload:
202
+ self.context.elements.signal(
203
+ "element_property_reload", to_reload
204
+ )
205
+ self.context.elements.signal("warn_state_update")
206
+
207
+ self.warn_panel = wx.BoxSizer(wx.HORIZONTAL)
208
+ unassigned_frame = StaticBoxSizer(self, wx.ID_ANY, "Unassigned", wx.HORIZONTAL)
209
+ unburnt_frame = StaticBoxSizer(self, wx.ID_ANY, "Non-burnt", wx.HORIZONTAL)
210
+ self.btn_fix_assign_create = wxButton(self, wx.ID_ANY, "Assign (+new)")
211
+ self.btn_fix_assign_existing = wxButton(self, wx.ID_ANY, "Assign")
212
+ self.btn_fix_unburnt = wxButton(self, wx.ID_ANY, "Enable")
213
+ self.btn_fix_assign_create.SetToolTip(
214
+ _("Classify unassigned elements and create operations if necessary")
215
+ )
216
+ self.btn_fix_assign_existing.SetToolTip(
217
+ _("Classify unassigned elements and use only existing operations")
218
+ )
219
+ self.btn_fix_unburnt.SetToolTip(
220
+ _("Reactivate disabled operations that prevent elements from being burnt")
221
+ )
222
+
223
+ unassigned_frame.Add(self.btn_fix_assign_create, 0, wx.EXPAND, 0)
224
+ unassigned_frame.Add(self.btn_fix_assign_existing, 0, wx.EXPAND, 0)
225
+ unburnt_frame.Add(self.btn_fix_unburnt, 0, wx.EXPAND, 0)
226
+ self.warn_panel.Add(unassigned_frame, 1, wx.EXPAND, 0)
227
+ self.warn_panel.Add(unburnt_frame, 1, wx.EXPAND, 0)
228
+ self._last_issue = None
229
+ self.warn_panel.Show(False)
230
+ self.warn_panel.ShowItems(False)
231
+ self.Bind(wx.EVT_BUTTON, fix_unassigned_create, self.btn_fix_assign_create)
232
+ self.Bind(wx.EVT_BUTTON, fix_unassigned_used, self.btn_fix_assign_existing)
233
+ self.Bind(wx.EVT_BUTTON, fix_unburnt, self.btn_fix_unburnt)
234
+ # self.Show(False)
235
+
236
+ def check_for_issues(self):
237
+ needs_showing = False
238
+ non_assigned, non_burn = self.context.elements.have_unburnable_elements()
239
+ warn_level = self.context.setting(int, "concern_level", 1)
240
+ if non_assigned and warn_level <= 2:
241
+ needs_showing = True
242
+ if non_burn and warn_level <= 1:
243
+ needs_showing = True
244
+ self.btn_fix_assign_create.Enable(non_assigned)
245
+ self.btn_fix_assign_existing.Enable(non_assigned)
246
+ self.btn_fix_unburnt.Enable(non_burn)
247
+ new_issue = non_assigned or non_burn
248
+ if (self._last_issue == new_issue) and (needs_showing == self.btn_fix_unburnt.IsShown()):
249
+ # no changes
250
+ return
251
+ self._last_issue = new_issue
252
+ if new_issue and needs_showing:
253
+ self.warn_panel.Show(True)
254
+ self.warn_panel.ShowItems(True)
255
+ else:
256
+ self.warn_panel.Show(False)
257
+ self.warn_panel.ShowItems(False)
258
+ self.Layout()
259
+
260
+ def __set_tree(self):
261
+ self.shadow_tree = ShadowTree(
262
+ self.context.elements, self.GetParent(), self.wxtree, self.context
263
+ )
264
+
265
+ # self.Bind(
266
+ # wx.EVT_TREE_BEGIN_DRAG, self.shadow_tree.on_drag_begin_handler, self.wxtree
267
+ # )
268
+ self.shadow_tree.wxtree.Bind(
269
+ wx.EVT_TREE_BEGIN_DRAG, self.shadow_tree.on_drag_begin_handler
270
+ )
271
+ self.Bind(
272
+ wx.EVT_TREE_END_DRAG, self.shadow_tree.on_drag_end_handler, self.wxtree
273
+ )
274
+ self.Bind(
275
+ wx.EVT_TREE_ITEM_ACTIVATED, self.shadow_tree.on_item_activated, self.wxtree
276
+ )
277
+ self.Bind(
278
+ wx.EVT_TREE_SEL_CHANGED,
279
+ self.shadow_tree.on_item_selection_changed,
280
+ self.wxtree,
281
+ )
282
+ self.Bind(
283
+ wx.EVT_TREE_ITEM_RIGHT_CLICK,
284
+ self.shadow_tree.on_item_right_click,
285
+ self.wxtree,
286
+ )
287
+ self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.shadow_tree.on_collapse, self.wxtree)
288
+ self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.shadow_tree.on_expand, self.wxtree)
289
+ self.Bind(wx.EVT_TREE_STATE_IMAGE_CLICK, self.shadow_tree.on_state_icon, self.wxtree)
290
+
291
+ self.wxtree.Bind(wx.EVT_MOTION, self.shadow_tree.on_mouse_over)
292
+ self.wxtree.Bind(wx.EVT_LEAVE_WINDOW, self.on_lost_focus, self.wxtree)
293
+
294
+ def on_lost_focus(self, event):
295
+ self.wxtree.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
296
+
297
+ def on_key_down(self, event):
298
+ """
299
+ Keydown for the tree does not execute navigation keys. These are only executed by the scene since they do
300
+ useful work for the tree.
301
+
302
+ Make sure the treectl can work on standard keys...
303
+
304
+ @param event:
305
+ @return:
306
+ """
307
+ event.Skip()
308
+ keyvalue = get_key_name(event)
309
+ if is_navigation_key(keyvalue):
310
+ if self._keybind_channel:
311
+ self._keybind_channel(
312
+ f"Tree key_down: {keyvalue} is a navigation key. Not processed."
313
+ )
314
+ return
315
+ if self.context.bind.trigger(keyvalue):
316
+ if self._keybind_channel:
317
+ self._keybind_channel(f"Tree key_down: {keyvalue} is executed.")
318
+ else:
319
+ if self._keybind_channel:
320
+ self._keybind_channel(f"Tree key_down: {keyvalue} was unbound.")
321
+
322
+ def on_key_up(self, event):
323
+ """
324
+ Keyup for the tree does not execute navigation keys. These are only executed by the scene.
325
+
326
+ Make sure the treectl can work on standard keys...
327
+
328
+ @param event:
329
+ @return:
330
+ """
331
+ keyvalue = get_key_name(event)
332
+ # There is a menu entry in wxmain that should catch all 'delete' keys
333
+ # but that is not consistently so, every other key seems to slip through
334
+ # probably there is an issue there, but we use the opportunity not
335
+ # only to catch these but to establish a forced delete via ctrl-delete as well
336
+
337
+ if keyvalue == "delete":
338
+ self.context("tree selected delete\n")
339
+ return
340
+ if keyvalue == "ctrl+delete":
341
+ self.context("tree selected remove\n")
342
+ return
343
+ event.Skip()
344
+ if is_navigation_key(keyvalue):
345
+ if self._keybind_channel:
346
+ self._keybind_channel(
347
+ f"Tree key_up: {keyvalue} is a navigation key. Not processed."
348
+ )
349
+ return
350
+ if self.context.bind.untrigger(keyvalue):
351
+ if self._keybind_channel:
352
+ self._keybind_channel(f"Tree key_up: {keyvalue} is executed.")
353
+ else:
354
+ if self._keybind_channel:
355
+ self._keybind_channel(f"Tree key_up: {keyvalue} was unbound.")
356
+
357
+ def pane_show(self):
358
+ pass
359
+
360
+ def pane_hide(self):
361
+ pass
362
+
363
+ @signal_listener("updateop_tree")
364
+ @signal_listener("warn_state_update")
365
+ def on_warn_state_update(self, origin, *args):
366
+ # Updates the warning state, using signal to avoid unnecessary calls
367
+ self.shadow_tree.update_warn_sign()
368
+ self.check_for_issues()
369
+
370
+ @signal_listener("update_group_labels")
371
+ def on_group_update(self, origin, *args):
372
+ self.shadow_tree.update_group_labels("signal")
373
+
374
+ @signal_listener("select_emphasized_tree")
375
+ def on_shadow_select_emphasized_tree(self, origin, *args):
376
+ self.shadow_tree.select_in_tree_by_emphasis(origin, *args)
377
+
378
+ @signal_listener("activate_selected_nodes")
379
+ def on_shadow_select_activate_tree(self, origin, *args):
380
+ self.shadow_tree.activate_selected_node(origin, *args)
381
+
382
+ @signal_listener("activate_single_node")
383
+ def on_shadow_select_activate_single_tree(self, origin, node=None, *args):
384
+ if node is not None:
385
+ node.selected = True
386
+ # self.shadow_tree.activate_selected_node(origin, *args)
387
+
388
+ @signal_listener("element_property_update")
389
+ def on_element_update(self, origin, *args):
390
+ """
391
+ Called by 'element_property_update' when the properties of an element are changed.
392
+
393
+ @param origin: the path of the originating signal
394
+ @param args:
395
+ @return:
396
+ """
397
+ if self.shadow_tree is not None:
398
+ stop_updates = not self.shadow_tree._freeze
399
+ if stop_updates:
400
+ self.shadow_tree.freeze_tree(True)
401
+ self.shadow_tree.on_element_update(*args)
402
+ if stop_updates:
403
+ self.shadow_tree.freeze_tree(False)
404
+
405
+ @signal_listener("element_property_reload")
406
+ def on_force_element_update(self, origin, *args):
407
+ """
408
+ Called by 'element_property_reload' when the properties of an element are changed.
409
+
410
+ @param origin: the path of the originating signal
411
+ @param args:
412
+ @return:
413
+ """
414
+ if self.shadow_tree is not None:
415
+ stop_updates = not self.shadow_tree._freeze
416
+ if stop_updates:
417
+ self.shadow_tree.freeze_tree(True)
418
+ self.shadow_tree.on_force_element_update(*args)
419
+ if stop_updates:
420
+ self.shadow_tree.freeze_tree(False)
421
+
422
+ @signal_listener("activate;device")
423
+ def on_activate_device(self, origin, target=None, *args):
424
+ self.shadow_tree.reset_formatter_cache()
425
+ self.shadow_tree.refresh_tree(source="device")
426
+
427
+ @signal_listener("reset_formatter")
428
+ def on_reset_formatter(self, origin, target=None, *args):
429
+ self.shadow_tree.reset_formatter_cache()
430
+
431
+ @signal_listener("sync_expansion")
432
+ def on_sync_expansion(self, origin, target=None, *args):
433
+ self.shadow_tree.sync_expansion()
434
+
435
+ @signal_listener("rebuild_tree")
436
+ def on_rebuild_tree_signal(self, origin, target=None, *args):
437
+ """
438
+ Called by 'rebuild_tree' signal. To rebuild the tree directly
439
+
440
+ @param origin: the path of the originating signal
441
+ @param target: target device
442
+ @param args:
443
+ @return:
444
+ """
445
+ # if target is not None:
446
+ # if target == "elements":
447
+ # startnode = self.shadow_tree.elements.get(type="branch elems").item
448
+ # elif target == "operations":
449
+ # startnode = self.shadow_tree.elements.get(type="branch ops").item
450
+ # elif target == "regmarks":
451
+ # startnode = self.shadow_tree.elements.get(type="branch reg").item
452
+ # print ("Current content of branch %s" % target)
453
+ # idx = 0
454
+ # child, cookie = self.shadow_tree.wxtree.GetFirstChild(startnode)
455
+ # while child.IsOk():
456
+ # # child_node = self.wxtree.GetItemData(child)
457
+ # lbl = self.shadow_tree.wxtree.GetItemText(child)
458
+ # print ("Node #%d - content: %s" % (idx, lbl))
459
+ # child, cookie = self.shadow_tree.wxtree.GetNextChild(startnode, cookie)
460
+ # idx += 1
461
+ # self.shadow_tree.wxtree.Expand(startnode)
462
+ # else:
463
+ # self.shadow_tree.rebuild_tree()
464
+ if target is None:
465
+ target = "all"
466
+ self.shadow_tree.rebuild_tree(source="signal", target=target)
467
+
468
+ @signal_listener("refresh_tree")
469
+ def on_refresh_tree_signal(self, origin, nodes=None, *args):
470
+ """
471
+ Called by 'refresh_tree' signal. To refresh tree directly
472
+
473
+ @param origin: the path of the originating signal
474
+ @param nodes: which nodes were added.
475
+ @param args:
476
+ @return:
477
+ """
478
+ self.shadow_tree.cache_hits = 0
479
+ self.shadow_tree.cache_requests = 0
480
+ self.shadow_tree.refresh_tree(source=f"signal_{origin}")
481
+ if nodes is not None:
482
+ if isinstance(nodes, (tuple, list)):
483
+ # All Standard nodes first
484
+ for node in nodes:
485
+ if node is not None and node._item is not None and node.type.startswith("elem "):
486
+ self.shadow_tree.set_icon(node, force=True)
487
+ # Then all others
488
+ for node in nodes:
489
+ if node is not None and node._item is not None and not node.type.startswith("elem "):
490
+ self.shadow_tree.set_icon(node, force=True)
491
+ # Show the first node, but if that's the root node then ignore stuff
492
+ node = nodes[0] if len(nodes) > 0 else None
493
+ else:
494
+ node = nodes
495
+ self.shadow_tree.set_icon(node, force=True)
496
+ rootitem = self.shadow_tree.wxtree.GetRootItem()
497
+ if (
498
+ node is not None
499
+ and node._item is not None
500
+ and node._item != rootitem
501
+ ):
502
+ self.shadow_tree.wxtree.EnsureVisible(node._item)
503
+
504
+ @signal_listener("freeze_tree")
505
+ def on_freeze_tree_signal(self, origin, status=None, *args):
506
+ """
507
+ Called by 'rebuild_tree' signal. Halts any updates like set_decorations and others
508
+
509
+ @param origin: the path of the originating signal
510
+ @param status: true, false (evident what they do), None: to toggle
511
+ @param args:
512
+ @return:
513
+ """
514
+ self.shadow_tree.freeze_tree(status)
515
+
516
+ @signal_listener("updateop_tree")
517
+ def on_update_op_labels_tree(self, origin, *args):
518
+ stop_updates = not self.shadow_tree._freeze
519
+ if stop_updates:
520
+ self.shadow_tree.freeze_tree(True)
521
+ self.shadow_tree.update_op_labels()
522
+ opitem = self.context.elements.get(type="branch ops")._item
523
+ if opitem is None:
524
+ return
525
+ tree = self.shadow_tree.wxtree
526
+ tree.Expand(opitem)
527
+ if stop_updates:
528
+ self.shadow_tree.freeze_tree(False)
529
+
530
+ @signal_listener("updateelem_tree")
531
+ def on_update_elem_tree(self, origin, *args):
532
+ elitem = self.context.elements.get(type="branch elems")._item
533
+ if elitem is None:
534
+ return
535
+ tree = self.shadow_tree.wxtree
536
+ tree.Expand(elitem)
537
+
538
+
539
+ class ElementsTree(MWindow):
540
+ def __init__(self, *args, **kwds):
541
+ super().__init__(423, 131, *args, **kwds)
542
+
543
+ self.panel = TreePanel(self, wx.ID_ANY, context=self.context)
544
+ self.sizer.Add(self.panel, 1, wx.EXPAND, 0)
545
+ self.add_module_delegate(self.panel)
546
+ _icon = wx.NullIcon
547
+ _icon.CopyFromBitmap(icon_tree.GetBitmap())
548
+ self.SetIcon(_icon)
549
+ self.SetTitle(_("Tree"))
550
+ self.restore_aspect()
551
+
552
+ def window_open(self):
553
+ try:
554
+ self.panel.pane_show()
555
+ except AttributeError:
556
+ pass
557
+
558
+ def window_close(self):
559
+ try:
560
+ self.panel.pane_hide()
561
+ except AttributeError:
562
+ pass
563
+
564
+
565
+ class ShadowTree:
566
+ """
567
+ The shadowTree creates a 'wx.Tree' structure from the 'elements.tree' structure. It listens to updates to the
568
+ elements tree and updates the GUI version accordingly. This tree does not permit alterations to it, rather it sends
569
+ any requested alterations to the 'elements.tree' or the 'elements.elements' or 'elements.operations' and when those
570
+ are reflected in the tree, the shadow tree is updated accordingly.
571
+ """
572
+
573
+ def __init__(self, service, gui, wxtree, context):
574
+ self.elements = service
575
+ self.context = context
576
+ self.gui = gui
577
+ self.wxtree = wxtree
578
+ self.renderer = LaserRender(service.root)
579
+ self.dragging_nodes = None
580
+ self.tree_images = None
581
+ self.name = "Project"
582
+ self._freeze = False
583
+ testsize = dip_size(self.wxtree, 20, 20)
584
+ self.iconsize = testsize[1]
585
+ self.iconstates = {}
586
+ self.last_call = 0
587
+ self._nodes_to_expand = []
588
+
589
+ # fact = get_default_scale_factor()
590
+ # if fact > 1.0:
591
+ # self.iconsize = int(self.iconsize * fact)
592
+
593
+ self.do_not_select = False
594
+ self.was_already_expanded = []
595
+ service.add_service_delegate(self)
596
+ self.setup_state_images()
597
+ self.default_images = {
598
+ "console home -f": icons8_home_filled,
599
+ "console move_abs": icon_return,
600
+ "console beep": icon_bell,
601
+ "console interrupt": icon_round_stop,
602
+ "console quit": icon_close_window,
603
+ "util wait": icon_timer,
604
+ "util home": icons8_home_filled,
605
+ "util goto": icon_return,
606
+ "util output": icon_external,
607
+ "util input": icon_internal,
608
+ "util console": icon_console,
609
+ "op engrave": icons8_laserbeam_weak,
610
+ "op cut": icons8_laser_beam,
611
+ "op image": icons8_image,
612
+ "op raster": icons8_direction,
613
+ "op dots": icon_points,
614
+ "effect hatch": icon_effect_hatch,
615
+ "effect wobble": icon_effect_wobble,
616
+ "effect warp": icon_distort,
617
+ "place current": icons8_home_filled,
618
+ "place point": icons8_home_filled,
619
+ "elem point": icon_points,
620
+ "file": icons8_file,
621
+ "group": icons8_group_objects,
622
+ "elem rect": icon_mk_rectangular,
623
+ "elem ellipse": icon_mk_ellipse,
624
+ "elem image": icons8_image,
625
+ "elem path": icon_path,
626
+ "elem line": icon_line,
627
+ "elem polyline": icon_mk_polyline,
628
+ "elem text": icon_bmap_text,
629
+ "image raster": icons8_image,
630
+ "blob": icons8_file,
631
+ }
632
+ self.image_cache = []
633
+ self.cache_hits = 0
634
+ self.cache_requests = 0
635
+ self.color_cache = {}
636
+ self.formatter_cache = {}
637
+ self._too_big = False
638
+ self.refresh_tree_counter = 0
639
+ self._last_hover_item = None
640
+
641
+ def service_attach(self, *args):
642
+ self.elements.listen_tree(self)
643
+
644
+ def service_detach(self, *args):
645
+ self.elements.unlisten_tree(self)
646
+
647
+ def setup_state_images(self):
648
+ self.state_images = wx.ImageList()
649
+ self.iconstates = {}
650
+ self.state_images.Create(width=self.iconsize, height=self.iconsize)
651
+ image = icons8_lock.GetBitmap(
652
+ resize=(self.iconsize, self.iconsize),
653
+ noadjustment=True,
654
+ buffer=1,
655
+ )
656
+ image_id = self.state_images.Add(bitmap=image)
657
+ self.iconstates["lock"] = image_id
658
+ image = icons8_r_white.GetBitmap(
659
+ resize=(self.iconsize, self.iconsize),
660
+ noadjustment=True,
661
+ buffer=1,
662
+ )
663
+ image_id = self.state_images.Add(bitmap=image)
664
+ self.iconstates["refobject"] = image_id
665
+ image = icon_warning.GetBitmap(
666
+ resize=(self.iconsize, self.iconsize),
667
+ noadjustment=True,
668
+ buffer=1,
669
+ )
670
+ image_id = self.state_images.Add(bitmap=image)
671
+ self.iconstates["warning"] = image_id
672
+ image = icons8_ghost.GetBitmap(
673
+ resize=(self.iconsize, self.iconsize),
674
+ noadjustment=True,
675
+ buffer=1,
676
+ )
677
+ image_id = self.state_images.Add(bitmap=image)
678
+ self.iconstates["ghost"] = image_id
679
+ self.wxtree.SetStateImageList(self.state_images)
680
+
681
+ def node_created(self, node, **kwargs):
682
+ """
683
+ Notified that this node has been created.
684
+ @param node: Node that was created.
685
+ @param kwargs:
686
+ @return:
687
+ """
688
+ if self._freeze or self.context.elements.suppress_updates:
689
+ return
690
+ self.elements.signal("modified")
691
+
692
+ def node_destroyed(self, node, **kwargs):
693
+ """
694
+ Notified that this node has been destroyed.
695
+ @param node: Node that was destroyed.
696
+ @param kwargs:
697
+ @return:
698
+ """
699
+ if self._freeze or self.context.elements.suppress_updates:
700
+ return
701
+ self.elements.signal("modified")
702
+ self.elements.signal("warn_state_update")
703
+
704
+ def node_detached(self, node, **kwargs):
705
+ """
706
+ Notified that this node has been detached from the tree.
707
+ @param node: Node that was detached.
708
+ @param kwargs:
709
+ @return:
710
+ """
711
+ self.unregister_children(node)
712
+ self.node_unregister(node, **kwargs)
713
+
714
+ def node_attached(self, node, **kwargs):
715
+ """
716
+ Notified that this node has been attached to the tree.
717
+ @param node: Node that was attached.
718
+ @param kwargs:
719
+ @return:
720
+ """
721
+ self.node_register(node, **kwargs)
722
+ self.register_children(node)
723
+ if node.expanded:
724
+ # Needs to be done later...
725
+ self._nodes_to_expand.append(node)
726
+ if not self.context.elements.suppress_signalling:
727
+ self.context.elements.signal("sync_expansion")
728
+
729
+ def sync_expansion(self):
730
+ for node in self._nodes_to_expand:
731
+ item = node._item
732
+ if item is None or not item.IsOk():
733
+ continue
734
+ if node.expanded:
735
+ self.wxtree.Expand(item)
736
+ else:
737
+ self.wxtree.Collapse(item)
738
+ self._nodes_to_expand.clear()
739
+
740
+ def node_changed(self, node):
741
+ """
742
+ Notified that this node has been changed.
743
+ @param node: Node that was changed.
744
+ @return:
745
+ """
746
+ if self._freeze or self.context.elements.suppress_updates:
747
+ return
748
+ item = node._item
749
+ self.check_validity(item)
750
+ try:
751
+ self.update_decorations(node, force=True)
752
+ except RuntimeError:
753
+ # A timer can update after the tree closes.
754
+ return
755
+
756
+ def check_validity(self, item):
757
+ if item is None or not item.IsOk():
758
+ # raise ValueError("Bad Item")
759
+ self.rebuild_tree(source="validity", target="all")
760
+ self.elements.signal("refresh_scene", "Scene")
761
+ return False
762
+ return True
763
+
764
+ def selected(self, node):
765
+ """
766
+ Notified that this node was selected.
767
+
768
+ Directly selected within the tree, specifically selected within the treectrl
769
+ @param node:
770
+ @return:
771
+ """
772
+ if self._freeze or self.context.elements.suppress_updates:
773
+ return
774
+ item = node._item
775
+ self.check_validity(item)
776
+ # self.update_decorations(node)
777
+ self.set_enhancements(node)
778
+ if not self.context.elements.suppress_signalling:
779
+ self.elements.signal("selected", node)
780
+
781
+ def emphasized(self, node):
782
+ """
783
+ Notified that this node was emphasized.
784
+
785
+ Item is selected by being emphasized this is treated like a soft selection throughout
786
+ @param node:
787
+ @return:
788
+ """
789
+ if self._freeze or self.context.elements.suppress_updates:
790
+ return
791
+ item = node._item
792
+ self.check_validity(item)
793
+ # self.update_decorations(node)
794
+ self.set_enhancements(node)
795
+ if not self.context.elements.suppress_signalling:
796
+ self.elements.signal("emphasized", node)
797
+
798
+ def targeted(self, node):
799
+ """
800
+ Notified that this node was targeted.
801
+
802
+ If any element is emphasized, all operations containing that element are targeted.
803
+ @param node:
804
+ @return:
805
+ """
806
+ if self._freeze or self.context.elements.suppress_updates:
807
+ return
808
+ item = node._item
809
+ self.check_validity(item)
810
+ self.update_decorations(node)
811
+ self.set_enhancements(node)
812
+ if not self.context.elements.suppress_signalling:
813
+ self.elements.signal("targeted", node)
814
+
815
+ def highlighted(self, node):
816
+ """
817
+ Notified that this node was highlighted.
818
+
819
+ If any operation is selected, all sub-operations are highlighted.
820
+ If any element is emphasized, all copies are highlighted.
821
+ @param node:
822
+ @return:
823
+ """
824
+ if self._freeze or self.context.elements.suppress_updates:
825
+ return
826
+ item = node._item
827
+ self.check_validity(item)
828
+ # self.update_decorations(node)
829
+ self.set_enhancements(node)
830
+ if not self.context.elements.suppress_signalling:
831
+ self.elements.signal("highlighted", node)
832
+
833
+ def translated(self, node, dx=0, dy=0, interim=False, *args):
834
+ """
835
+ This node was moved
836
+ """
837
+ return
838
+
839
+ def scaled(self, node, sx=1, sy=1, ox=0, oy=0, interim=False, *args):
840
+ """
841
+ This node was scaled
842
+ """
843
+ return
844
+
845
+ def modified(self, node):
846
+ """
847
+ Notified that this node was modified.
848
+ This node position values were changed, but nothing about the core data was altered.
849
+ @param node:
850
+ @return:
851
+ """
852
+ if self._freeze or self.context.elements.suppress_updates:
853
+ return
854
+
855
+ if node is None or not hasattr(node, "_item"):
856
+ return
857
+
858
+ item = node._item
859
+ if item is None or not item.IsOk():
860
+ return
861
+
862
+ try:
863
+ self.update_decorations(node, force=True)
864
+ except RuntimeError:
865
+ # A timer can update after the tree closes.
866
+ return
867
+
868
+ try:
869
+ c = node.color
870
+ self.set_color(node, c)
871
+ except AttributeError:
872
+ pass
873
+ self.elements.signal("modified", node)
874
+
875
+ def altered(self, node, *args, **kwargs):
876
+ """
877
+ Notified that this node was altered.
878
+ This node was changed in fundamental ways and nothing about this node remains trusted.
879
+ @param node:
880
+ @return:
881
+ """
882
+ if self._freeze or self.context.elements.suppress_updates:
883
+ return
884
+ item = node._item
885
+ self.check_validity(item)
886
+ try:
887
+ self.update_decorations(node, force=True)
888
+ except RuntimeError:
889
+ # A timer can update after the tree closes.
890
+ return
891
+ try:
892
+ c = node.color
893
+ self.set_color(node, c)
894
+ except AttributeError:
895
+ pass
896
+ self.elements.signal("altered", node)
897
+
898
+ def expand(self, node):
899
+ """
900
+ Notified that this node was expanded.
901
+
902
+ @param node:
903
+ @return:
904
+ """
905
+ if self._freeze or self.context.elements.suppress_updates:
906
+ return
907
+ node.expanded = True
908
+ item = node._item
909
+ self.check_validity(item)
910
+ self.wxtree.ExpandAllChildren(item)
911
+ self.set_expanded(item, 1)
912
+
913
+ def collapse_within(self, node):
914
+ # Tries to collapse children first, if there were any open,
915
+ # return TRUE, if all were already collapsed, return FALSE
916
+ result = False
917
+ startnode = node._item
918
+ try:
919
+ pnode, cookie = self.wxtree.GetFirstChild(startnode)
920
+ except:
921
+ return
922
+ were_expanded = []
923
+ while pnode.IsOk():
924
+ if self.wxtree.IsExpanded(pnode):
925
+ result = True
926
+ were_expanded.append(pnode)
927
+ pnode, cookie = self.wxtree.GetNextChild(startnode, cookie)
928
+ for pnode in were_expanded:
929
+ cnode = self.wxtree.GetItemData(pnode)
930
+ cnode.notify_collapse()
931
+ return result
932
+
933
+ def collapse(self, node):
934
+ """
935
+ Notified that this node was collapsed.
936
+
937
+ @param node:
938
+ @return:
939
+ """
940
+ if node is None:
941
+ return
942
+ node.expanded = False
943
+ item = node._item
944
+ if item is None:
945
+ return
946
+ self.check_validity(item)
947
+ # Special treatment for branches, they only collapse fully,
948
+ # if all their childrens were collapsed already
949
+ if node.type.startswith("branch") and self.collapse_within(node):
950
+ return
951
+ self.wxtree.CollapseAllChildren(item)
952
+ if (
953
+ item is self.wxtree.GetRootItem()
954
+ or self.wxtree.GetItemParent(item) is self.wxtree.GetRootItem()
955
+ ):
956
+ self.wxtree.Expand(self.elements.get(type="branch ops")._item)
957
+ self.wxtree.Expand(self.elements.get(type="branch elems")._item)
958
+ self.wxtree.Expand(self.elements.get(type="branch reg")._item)
959
+
960
+ def reorder(self, node):
961
+ """
962
+ Notified that this node was reordered.
963
+
964
+ Tree is rebuilt.
965
+
966
+ @param node:
967
+ @return:
968
+ """
969
+ target = "all"
970
+ while node.parent is not None:
971
+ if node.parent.type == "branch reg":
972
+ target = "regmarks"
973
+ break
974
+ if node.parent.type == "branch elem":
975
+ target = "elements"
976
+ break
977
+ if node.parent.type == "branch ops":
978
+ target = "operations"
979
+ break
980
+ node = node.parent
981
+
982
+ self.rebuild_tree("reorder", target=target)
983
+
984
+ def update(self, node):
985
+ """
986
+ Notified that this node has been updated.
987
+ @param node:
988
+ @return:
989
+ """
990
+ if self._freeze or self.context.elements.suppress_updates:
991
+ return
992
+ item = node._item
993
+ if item is None:
994
+ # Could be a faulty refresh during an undo.
995
+ return
996
+ self.check_validity(item)
997
+ self.set_icon(node, force=False)
998
+ self.on_force_element_update(node)
999
+
1000
+ def focus(self, node):
1001
+ """
1002
+ Notified that this node has been focused.
1003
+
1004
+ It must be seen in the tree.
1005
+ @param node:
1006
+ @return:
1007
+ """
1008
+ if self._freeze or self.context.elements.suppress_updates:
1009
+ return
1010
+ item = node._item
1011
+ self.check_validity(item)
1012
+ self.wxtree.EnsureVisible(item)
1013
+ self.wxtree.ScrollTo(item)
1014
+ # self.wxtree.SetFocusedItem(item)
1015
+
1016
+ def on_force_element_update(self, *args):
1017
+ """
1018
+ Called by signal "element_property_reload"
1019
+ @param args:
1020
+ @return:
1021
+ """
1022
+ element = args[0]
1023
+ if isinstance(element, (tuple, list)):
1024
+ for node in element:
1025
+ if hasattr(node, "node"):
1026
+ node = node.node
1027
+ try:
1028
+ self.update_decorations(node, force=True)
1029
+ for refnode in node.references:
1030
+ self.update_decorations(refnode, force=True)
1031
+ except RuntimeError:
1032
+ # A timer can update after the tree closes.
1033
+ return
1034
+ else:
1035
+ try:
1036
+ self.update_decorations(element, force=True)
1037
+ for refnode in element.references:
1038
+ self.update_decorations(refnode, force=True)
1039
+ except RuntimeError:
1040
+ # A timer can update after the tree closes.
1041
+ return
1042
+
1043
+ def on_element_update(self, *args):
1044
+ """
1045
+ Called by signal "element_property_update"
1046
+ @param args:
1047
+ @return:
1048
+ """
1049
+ element = args[0]
1050
+ if isinstance(element, (tuple, list)):
1051
+ for node in element:
1052
+ if hasattr(node, "node"):
1053
+ node = node.node
1054
+ try:
1055
+ self.update_decorations(node, force=True)
1056
+ except RuntimeError:
1057
+ # A timer can update after the tree closes.
1058
+ return
1059
+ else:
1060
+ try:
1061
+ self.update_decorations(element, force=True)
1062
+ except RuntimeError:
1063
+ # A timer can update after the tree closes.
1064
+ return
1065
+
1066
+ def refresh_tree(self, node=None, level=0, source=""):
1067
+ """
1068
+ This no longer has any relevance, as the updates are properly done outside...
1069
+ """
1070
+ # if node is None:
1071
+ # self.context.elements.set_start_time("refresh_tree")
1072
+ # self.refresh_tree_counter = 0
1073
+ # elemtree = self.elements._tree
1074
+ # node = elemtree._item
1075
+ # level = 0
1076
+ # else:
1077
+ # self.refresh_tree_counter += 1
1078
+
1079
+ # if node is None:
1080
+ # return
1081
+ self.context.elements.set_start_time("refresh_tree")
1082
+ self.freeze_tree(True)
1083
+ self.update_op_labels()
1084
+ if node is not None:
1085
+ if isinstance(node, (tuple, list)):
1086
+ for enode in node:
1087
+ if hasattr(enode, "node"):
1088
+ enode = enode.node
1089
+ try:
1090
+ self.update_decorations(enode, force=True)
1091
+ except RuntimeError:
1092
+ # A timer can update after the tree closes.
1093
+ return
1094
+ else:
1095
+ try:
1096
+ self.update_decorations(node, force=True)
1097
+ except RuntimeError:
1098
+ # A timer can update after the tree closes.
1099
+ return
1100
+
1101
+ branch_elems_item = self.elements.get(type="branch elems")._item
1102
+ if branch_elems_item:
1103
+ self.wxtree.Expand(branch_elems_item)
1104
+ branch_reg_item = self.elements.get(type="branch reg")._item
1105
+ if branch_reg_item:
1106
+ self.wxtree.Expand(branch_reg_item)
1107
+ self.context.elements.signal("warn_state_update")
1108
+ self.freeze_tree(False)
1109
+ self.context.elements.set_end_time("full_load", display=True, delete=True)
1110
+ self.context.elements.set_end_time("refresh_tree", display=True)
1111
+
1112
+ def update_warn_sign(self):
1113
+ # from time import perf_counter
1114
+ # this_call = perf_counter()
1115
+ # print (f"Update warn was called, time since last: {this_call-self.last_call:.3f}sec")
1116
+ # self.last_call = this_call
1117
+ op_node = self.elements.get(type="branch ops")
1118
+ if op_node is None:
1119
+ return
1120
+ op_item = op_node._item
1121
+
1122
+ status = ""
1123
+ if op_item is None:
1124
+ return
1125
+
1126
+ self.wxtree.Expand(op_item)
1127
+ unassigned, unburnt = self.elements.have_unburnable_elements()
1128
+ needs_showing = False
1129
+ warn_level = self.context.setting(int, "concern_level", 1)
1130
+ messages = []
1131
+ if unassigned and warn_level <= 2:
1132
+ needs_showing = True
1133
+ messages.append( _("You have unassigned elements, that won't be burned") )
1134
+ if unburnt and warn_level <= 1:
1135
+ needs_showing = True
1136
+ messages.append( _("You have elements in disabled operations, that won't be burned") )
1137
+
1138
+ if needs_showing:
1139
+ self.wxtree.SetItemState(op_item, self.iconstates["warning"])
1140
+ status = "\n".join(messages)
1141
+ else:
1142
+ self.wxtree.SetItemState(op_item, wx.TREE_ITEMSTATE_NONE)
1143
+ status = ""
1144
+ op_node._tooltip = status
1145
+ op_node._tooltip_translated = True
1146
+
1147
+ def freeze_tree(self, status=None):
1148
+ if status is None:
1149
+ status = not self._freeze
1150
+ if self._freeze != status:
1151
+ self._freeze = status
1152
+ self.wxtree.Enable(not self._freeze)
1153
+ if status:
1154
+ self.wxtree.Freeze()
1155
+ else:
1156
+ self.wxtree.Thaw()
1157
+ self.wxtree.Refresh()
1158
+
1159
+ def frozen(self, status):
1160
+ self.wxtree.Enable(not status)
1161
+ if status:
1162
+ self.wxtree.Freeze()
1163
+ else:
1164
+ self.wxtree.Thaw()
1165
+ self.wxtree.Refresh()
1166
+
1167
+ def was_expanded(self, node, level):
1168
+ txt = self.wxtree.GetItemText(node)
1169
+ chk = f"{level}-{txt}"
1170
+ return any(chk == elem for elem in self.was_already_expanded)
1171
+
1172
+ def set_expanded(self, node, level):
1173
+ txt = self.wxtree.GetItemText(node)
1174
+ chk = f"{level}-{txt}"
1175
+ result = self.was_expanded(node, level)
1176
+ if not result:
1177
+ self.was_already_expanded.append(chk)
1178
+
1179
+ # These routines were supposed to save and restore the expanded state of the tree
1180
+ # But that did not work out as intended....
1181
+ #
1182
+ # def parse_tree(self, startnode, level):
1183
+ # if startnode is None:
1184
+ # return
1185
+ # cookie = 0
1186
+ # try:
1187
+ # pnode, cookie = self.wxtree.GetFirstChild(startnode)
1188
+ # except:
1189
+ # return
1190
+ # while pnode.IsOk():
1191
+ # txt = self.wxtree.GetItemText(pnode)
1192
+ # # That is not working as advertised...
1193
+ # state = self.wxtree.IsExpanded(pnode)
1194
+ # if state:
1195
+ # self.was_already_expanded.append(f"{level}-{txt}")
1196
+ # self.parse_tree(pnode, level + 1)
1197
+ # pnode, cookie = self.wxtree.GetNextChild(startnode, cookie)
1198
+
1199
+ # def restore_tree(self, startnode, level):
1200
+ # if startnode is None:
1201
+ # return
1202
+ # cookie = 0
1203
+ # try:
1204
+ # pnode, cookie = self.wxtree.GetFirstChild(startnode)
1205
+ # except:
1206
+ # return
1207
+ # while pnode.IsOk():
1208
+ # txt = self.wxtree.GetItemText(pnode)
1209
+ # chk = f"{level}-{txt}"
1210
+ # for elem in self.was_already_expanded:
1211
+ # if chk == elem:
1212
+ # self.wxtree.ExpandAllChildren(pnode)
1213
+ # break
1214
+ # self.parse_tree(pnode, level + 1)
1215
+ # pnode, cookie = self.wxtree.GetNextChild(startnode, cookie)
1216
+ #
1217
+ # def reset_expanded(self):
1218
+ # self.was_already_expanded = []
1219
+
1220
+ def reset_dragging(self):
1221
+ self.dragging_nodes = None
1222
+ self.wxtree.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
1223
+
1224
+ def rebuild_tree(self, source:str, target:str="all" ):
1225
+ """
1226
+ Tree requires being deleted and completely rebuilt.
1227
+
1228
+ @return:
1229
+ """
1230
+ # print (f"Rebuild called from {source}")
1231
+ # let's try to remember which branches were expanded:
1232
+ busy = wx.BusyCursor()
1233
+ self.context.elements.set_start_time(f"rebuild_tree_{target}")
1234
+ self.freeze_tree(True)
1235
+
1236
+ # self.reset_expanded()
1237
+
1238
+ # Safety net - if we have too many elements it will
1239
+ # take too long to create all preview icons...
1240
+ count = self.elements.count_elems() + self.elements.count_op()
1241
+ self._too_big = count > 1000
1242
+ # print(f"Was too big?! {count} -> {self._too_big}")
1243
+
1244
+ # self.parse_tree(self.wxtree.GetRootItem(), 0)
1245
+ # Rebuild tree destroys the emphasis, so let's store it...
1246
+ def delete_items(target):
1247
+ if target == "regmarks":
1248
+ node = self.elements.reg_branch
1249
+ item = node._item
1250
+ if item is not None:
1251
+ self.wxtree.DeleteChildren(item)
1252
+ elif target == "operations":
1253
+ node = self.elements.op_branch
1254
+ item = node._item
1255
+ if item is not None:
1256
+ self.wxtree.DeleteChildren(item)
1257
+ elif target == "elements":
1258
+ node = self.elements.elem_branch
1259
+ item = node._item
1260
+ if item is not None:
1261
+ self.wxtree.DeleteChildren(item)
1262
+ else:
1263
+ self.wxtree.DeleteAllItems()
1264
+
1265
+ def rebuild_items(target):
1266
+ if target == "all":
1267
+ if self.tree_images is not None:
1268
+ self.tree_images.Destroy()
1269
+ self.image_cache = []
1270
+ self.tree_images = wx.ImageList()
1271
+ self.tree_images.Create(width=self.iconsize, height=self.iconsize)
1272
+
1273
+ self.wxtree.SetImageList(self.tree_images)
1274
+ if target == "regmarks":
1275
+ elemtree = self.elements.reg_branch
1276
+ elif target == "operations":
1277
+ elemtree = self.elements.op_branch
1278
+ elif target == "elements":
1279
+ elemtree = self.elements.elem_branch
1280
+ else:
1281
+ elemtree = self.elements._tree
1282
+ elemtree._item = self.wxtree.AddRoot(self.name)
1283
+ self.wxtree.SetItemData(elemtree._item, elemtree)
1284
+ self.set_icon(
1285
+ elemtree,
1286
+ icon_meerk40t.GetBitmap(
1287
+ False,
1288
+ resize=(self.iconsize, self.iconsize),
1289
+ noadjustment=True,
1290
+ buffer=1,
1291
+ ),
1292
+ )
1293
+ self.register_children(elemtree)
1294
+ branch_list = (
1295
+ ("branch ops", "operations", icons8_laser_beam),
1296
+ ("branch reg", "regmarks", icon_regmarks),
1297
+ ("branch elems", "elements", icon_canvas),
1298
+ )
1299
+ for branch_name, branch_type, icon in branch_list:
1300
+ if target not in ("all", branch_type):
1301
+ continue
1302
+ node_branch = elemtree.get(type=branch_name)
1303
+ self.set_icon(
1304
+ node_branch,
1305
+ icon.GetBitmap(
1306
+ resize=(self.iconsize, self.iconsize),
1307
+ noadjustment=True,
1308
+ buffer=1,
1309
+ ),
1310
+ )
1311
+ for n in node_branch.children:
1312
+ self.set_icon(n, force=True)
1313
+ if target in {"all", "operations"}:
1314
+ self.update_op_labels()
1315
+ if target in {"all", "elements"}:
1316
+ self.update_group_labels("rebuild_tree")
1317
+
1318
+ emphasized_list = list(self.elements.elems(emphasized=True))
1319
+
1320
+ delete_items(target)
1321
+ rebuild_items(target)
1322
+
1323
+ # Expand Ops, Element, and Regmarks nodes only
1324
+ # self.wxtree.CollapseAll()
1325
+ self.wxtree.Expand(self.elements.op_branch._item)
1326
+ self.wxtree.Expand(self.elements.elem_branch._item)
1327
+ self.wxtree.Expand(self.elements.reg_branch._item)
1328
+ startnode = self.elements._tree._item
1329
+
1330
+ def expand_leaf(snode):
1331
+ child, cookie = self.wxtree.GetFirstChild(snode)
1332
+ while child.IsOk():
1333
+ node = self.wxtree.GetItemData(child)
1334
+ if node.expanded:
1335
+ self.wxtree.Expand(child)
1336
+ expand_leaf(child)
1337
+ child, cookie = self.wxtree.GetNextChild(snode, cookie)
1338
+
1339
+ expand_leaf(startnode)
1340
+ self.elements.signal("warn_state_update")
1341
+
1342
+ # Restore emphasis
1343
+ for e in emphasized_list:
1344
+ e.emphasized = True
1345
+ # self.restore_tree(self.wxtree.GetRootItem(), 0)
1346
+ self.freeze_tree(False)
1347
+ self.context.elements.set_end_time(f"rebuild_tree_{target}", display=True)
1348
+ # print(f"Rebuild done for {source}")
1349
+ del busy
1350
+
1351
+ def register_children(self, node):
1352
+ """
1353
+ All children of this node are registered.
1354
+
1355
+ @param node:
1356
+ @return:
1357
+ """
1358
+ for child in node.children:
1359
+ self.node_register(child)
1360
+ self.register_children(child)
1361
+ if node.type in ("group", "file"):
1362
+ self.update_decorations(node, force=True)
1363
+
1364
+ def unregister_children(self, node):
1365
+ """
1366
+ All children of this node are unregistered.
1367
+ @param node:
1368
+ @return:
1369
+ """
1370
+ for child in node.children:
1371
+ self.unregister_children(child)
1372
+ self.node_unregister(child)
1373
+
1374
+ def node_unregister(self, node, **kwargs):
1375
+ """
1376
+ Node object is unregistered and item is deleted.
1377
+
1378
+ @param node:
1379
+ @param kwargs:
1380
+ @return:
1381
+ """
1382
+ self.do_not_select = True
1383
+
1384
+ item = node._item
1385
+ if item is None:
1386
+ raise ValueError(f"Item was None for node {repr(node)}")
1387
+ self.check_validity(item)
1388
+ # We might need to update the decorations for all parent objects
1389
+ informed = []
1390
+ if not self._freeze:
1391
+ parent = node._parent
1392
+ while parent is not None and not parent.type.startswith("branch "):
1393
+ informed.append(parent)
1394
+ parent = parent._parent
1395
+
1396
+ node.unregister_object()
1397
+ self.wxtree.Delete(node._item)
1398
+ if informed:
1399
+ self.context.signal("element_property_update", informed)
1400
+ for i in self.wxtree.GetSelections():
1401
+ self.wxtree.SelectItem(i, False)
1402
+
1403
+ self.do_not_select = False
1404
+
1405
+ def safe_color(self, color_to_set):
1406
+ _hash = str(color_to_set)
1407
+ if _hash not in self.color_cache:
1408
+ back_color = self.wxtree.GetBackgroundColour()
1409
+ rgb = back_color.Get()
1410
+ default_color = wx.Colour(
1411
+ red=255 - rgb[0], green=255 - rgb[1], blue=255 - rgb[2], alpha=128
1412
+ )
1413
+ if color_to_set is not None and color_to_set.argb is not None:
1414
+ mycolor = wx.Colour(swizzlecolor(color_to_set.argb))
1415
+ if mycolor.Get() == rgb:
1416
+ mycolor = default_color
1417
+ else:
1418
+ mycolor = default_color
1419
+ self.color_cache[_hash] = mycolor
1420
+ else:
1421
+ mycolor = self.color_cache[_hash]
1422
+ return mycolor
1423
+
1424
+ def node_register(self, node, pos=None, **kwargs):
1425
+ """
1426
+ Node.item is added/inserted. Label is updated and values are set. Icon is set.
1427
+
1428
+ @param node:
1429
+ @param pos:
1430
+ @param kwargs:
1431
+ @return:
1432
+ """
1433
+ try:
1434
+ parent = node.parent
1435
+ parent_item = parent._item
1436
+ if parent_item is None:
1437
+ # We are appending items in tree before registration.
1438
+ return
1439
+ tree = self.wxtree
1440
+ if pos is None:
1441
+ node._item = tree.AppendItem(parent_item, self.name)
1442
+ else:
1443
+ node._item = tree.InsertItem(parent_item, pos, self.name)
1444
+ tree.SetItemData(node._item, node)
1445
+ except Exception as e:
1446
+ # Invalid tree?
1447
+ self.context.signal("rebuild_tree", "all")
1448
+ print (f"We encountered an error at node registration: {e}")
1449
+ return
1450
+ self.update_decorations(node, False)
1451
+ wxcolor = self.wxtree.GetForegroundColour()
1452
+ attribute_to_try = "fill" if node.type == "elem text" else "stroke"
1453
+ if hasattr(node, attribute_to_try):
1454
+ wxcolor = self.safe_color(getattr(node, attribute_to_try))
1455
+ elif hasattr(node, "color"):
1456
+ wxcolor = self.safe_color(node.color)
1457
+ else:
1458
+ back_color = self.wxtree.GetBackgroundColour()
1459
+ rgb = back_color.Get()
1460
+ background = Color(rgb[0], rgb[1], rgb[2])
1461
+ if background is not None:
1462
+ c1 = Color("Black")
1463
+ c2 = Color("White")
1464
+ if Color.distance(background, c1) > Color.distance(background, c2):
1465
+ textcolor = c1
1466
+ else:
1467
+ textcolor = c2
1468
+ wxcolor = wx.Colour(swizzlecolor(textcolor))
1469
+ if self.context.root.tree_colored:
1470
+ try:
1471
+ tree.SetItemTextColour(node._item, wxcolor)
1472
+ except (AttributeError, KeyError, TypeError):
1473
+ pass
1474
+ # We might need to update the decorations for all parent objects
1475
+ if not self._freeze:
1476
+ informed = []
1477
+ parent = node._parent
1478
+ while parent is not None and not parent.type.startswith("branch "):
1479
+ informed.append(parent)
1480
+ parent = parent._parent
1481
+ if informed:
1482
+ self.context.signal("element_property_update", informed)
1483
+
1484
+ # self.context.signal("update_group_labels")
1485
+
1486
+ def set_enhancements(self, node):
1487
+ """
1488
+ Node in the tree is drawn special based on nodes current setting.
1489
+ @param node:
1490
+ @return:
1491
+ """
1492
+ tree = self.wxtree
1493
+ node_item = node._item
1494
+ if node_item is None:
1495
+ return
1496
+ if self._freeze or self.context.elements.suppress_updates:
1497
+ return
1498
+ tree.SetItemBackgroundColour(node_item, None)
1499
+ try:
1500
+ if node.highlighted:
1501
+ tree.SetItemBackgroundColour(node_item, wx.LIGHT_GREY)
1502
+ elif node.emphasized:
1503
+ tree.SetItemBackgroundColour(node_item, wx.Colour(0x80A0A0))
1504
+ elif node.targeted:
1505
+ tree.SetItemBackgroundColour(node_item, wx.Colour(0xA080A0))
1506
+ except AttributeError:
1507
+ pass
1508
+
1509
+ def set_color(self, node, color=None):
1510
+ """
1511
+ Node color is set.
1512
+
1513
+ @param node: Not to be colored
1514
+ @param color: Color to be set.
1515
+ @return:
1516
+ """
1517
+ if not self.context.root.tree_colored:
1518
+ return
1519
+ item = node._item
1520
+ if item is None:
1521
+ return
1522
+ if self._freeze or self.context.elements.suppress_updates:
1523
+ return
1524
+ tree = self.wxtree
1525
+ wxcolor = self.safe_color(color)
1526
+ tree.SetItemTextColour(item, wxcolor)
1527
+
1528
+ def create_image_from_node(self, node):
1529
+ image = None
1530
+ mini_icon = self.context.root.mini_icon and not self._too_big
1531
+ c = None
1532
+ self.cache_requests += 1
1533
+ cached_id = -1
1534
+ # Do we have a standard representation?
1535
+ defaultcolor = Color("black")
1536
+ if mini_icon:
1537
+ if node.type == "elem image":
1538
+ try:
1539
+ image = self.renderer.make_thumbnail(
1540
+ node.active_image, width=self.iconsize, height=self.iconsize
1541
+ )
1542
+ except (MemoryError, RuntimeError):
1543
+ image = None
1544
+ else:
1545
+ # Establish colors (and some images)
1546
+ if node.type.startswith("op ") or node.type.startswith("util "):
1547
+ if (
1548
+ hasattr(node, "color")
1549
+ and node.color is not None
1550
+ and node.color.argb is not None
1551
+ ):
1552
+ c = node.color
1553
+ elif node.type == "reference":
1554
+ c, image, cached_id = self.create_image_from_node(node.node)
1555
+ elif node.type.startswith("elem "):
1556
+ if (
1557
+ hasattr(node, "stroke")
1558
+ and node.stroke is not None
1559
+ and node.stroke.argb is not None
1560
+ ):
1561
+ c = node.stroke
1562
+ if node.type.startswith("elem ") and node.type != "elem point":
1563
+ image = self.renderer.make_raster(
1564
+ node,
1565
+ node.paint_bounds,
1566
+ width=self.iconsize,
1567
+ height=self.iconsize,
1568
+ bitmap=True,
1569
+ keep_ratio=True,
1570
+ )
1571
+ else:
1572
+ # Establish at least colors (and an image for a reference)
1573
+ if node.type.startswith("op ") or node.type.startswith("util "):
1574
+ if (
1575
+ hasattr(node, "color")
1576
+ and node.color is not None
1577
+ and node.color.argb is not None
1578
+ ):
1579
+ c = node.color
1580
+ elif node.type == "reference":
1581
+ c, image, cached_id = self.create_image_from_node(node.node)
1582
+ elif node.type == "elem text":
1583
+ if (
1584
+ hasattr(node, "fill")
1585
+ and node.fill is not None
1586
+ and node.fill.argb is not None
1587
+ ):
1588
+ c = node.fill
1589
+ elif node.type.startswith("elem ") or node.type.startswith("effect "):
1590
+ if (
1591
+ hasattr(node, "stroke")
1592
+ and node.stroke is not None
1593
+ and node.stroke.argb is not None
1594
+ ):
1595
+ c = node.stroke
1596
+
1597
+ # Have we already established an image, if no let's use the default
1598
+ if image is None:
1599
+ found = ""
1600
+ tofind = node.type
1601
+ if tofind == "util console":
1602
+ # Let's see whether we find the keyword...
1603
+ for key in self.default_images:
1604
+ if key.startswith("console "):
1605
+ skey = key[8:]
1606
+ if node.command is not None and skey in node.command:
1607
+ found = key
1608
+ break
1609
+
1610
+ if not found and tofind in self.default_images:
1611
+ # print (f"Wasn't found use {tofind}")
1612
+ found = tofind
1613
+
1614
+ if found:
1615
+ for stored_key, stored_color, img_obj, c_id in self.image_cache:
1616
+ if stored_key == found and stored_color == c:
1617
+ image = img_obj
1618
+ cached_id = c_id
1619
+ self.cache_hits += 1
1620
+ # print (f"Restore id {cached_id} for {c} - {found}")
1621
+ break
1622
+ if image is None:
1623
+ # has not been found yet...
1624
+ img_obj = self.default_images[found]
1625
+ image = img_obj.GetBitmap(
1626
+ color=c,
1627
+ resize=(self.iconsize, self.iconsize),
1628
+ noadjustment=True,
1629
+ buffer=1,
1630
+ )
1631
+ cached_id = self.tree_images.Add(bitmap=image)
1632
+ # print(f"Store id {cached_id} for {c} - {found}")
1633
+ self.image_cache.append((found, c, image, cached_id))
1634
+
1635
+ if c is None:
1636
+ c = defaultcolor
1637
+ # print (f"Icon gives color: {c} and cached-id={cached_id}")
1638
+ return c, image, cached_id
1639
+
1640
+ def set_icon(self, node, icon=None, force=False):
1641
+ """
1642
+ Node icon to be created and applied
1643
+
1644
+ @param node: Node to have the icon set.
1645
+ @param icon: overriding icon to be forcibly set, rather than a default.
1646
+ @param force: force the icon setting
1647
+ @return: item_id if newly created / update
1648
+ """
1649
+ root = self
1650
+ drawmode = self.elements.root.draw_mode
1651
+ if drawmode & DRAW_MODE_ICONS != 0:
1652
+ return
1653
+ # if self._freeze or self.context.elements.suppress_updates:
1654
+ # return
1655
+ if node is None:
1656
+ return
1657
+ try:
1658
+ item = node._item
1659
+ except AttributeError:
1660
+ return # Node.item can be none if launched from ExecuteJob where the nodes are not part of the tree.
1661
+ if node._item is None:
1662
+ return
1663
+ tree = root.wxtree
1664
+ if icon is None:
1665
+ if force is None:
1666
+ force = False
1667
+ image_id = tree.GetItemImage(item)
1668
+ if image_id >= self.tree_images.ImageCount:
1669
+ image_id = -1
1670
+ if image_id >= 0 and not force:
1671
+ # Don't do it twice
1672
+ return image_id
1673
+
1674
+ # print ("Default size for iconsize, tree_images", self.iconsize, self.tree_images.GetSize())
1675
+ c, image, cached_id = self.create_image_from_node(node)
1676
+
1677
+ if image is not None:
1678
+ if cached_id >= 0:
1679
+ image_id = cached_id
1680
+ elif image_id < 0:
1681
+ image_id = self.tree_images.Add(bitmap=image)
1682
+ else:
1683
+ self.tree_images.Replace(index=image_id, bitmap=image)
1684
+ tree.SetItemImage(item, image=image_id)
1685
+ # Let's have a look at all references....
1686
+ for subnode in node.references:
1687
+ try:
1688
+ subitem = subnode._item
1689
+ except AttributeError:
1690
+ subitem = None
1691
+ if subitem is None:
1692
+ continue
1693
+ tree.SetItemImage(subitem, image=image_id)
1694
+
1695
+ if c is not None:
1696
+ self.set_color(node, c)
1697
+
1698
+ else:
1699
+ image_id = tree.GetItemImage(item)
1700
+ if image_id >= self.tree_images.ImageCount:
1701
+ image_id = -1
1702
+ # Reset Image Node in List
1703
+ if image_id < 0:
1704
+ image_id = self.tree_images.Add(bitmap=icon)
1705
+ else:
1706
+ self.tree_images.Replace(index=image_id, bitmap=icon)
1707
+
1708
+ tree.SetItemImage(item, image=image_id)
1709
+ return image_id
1710
+
1711
+ def update_op_labels(self):
1712
+ startnode = self.elements.get(type="branch ops")._item
1713
+ if startnode is None:
1714
+ # Branch op never populated the tree, we cannot update sublayer.
1715
+ return
1716
+ child, cookie = self.wxtree.GetFirstChild(startnode)
1717
+ while child.IsOk():
1718
+ node = self.wxtree.GetItemData(child) # Make sure the map is updated...
1719
+ self.update_decorations(node=node, force=True)
1720
+ child, cookie = self.wxtree.GetNextChild(startnode, cookie)
1721
+
1722
+ def update_group_labels(self, src):
1723
+ # print(f"group_labels: {src}")
1724
+ stop_updates = not self._freeze
1725
+ if stop_updates:
1726
+ self.freeze_tree(True)
1727
+ for e in self.context.elements.elems_nodes():
1728
+ if e.type == "group":
1729
+ self.update_decorations(e)
1730
+ for e in self.context.elements.regmarks_nodes():
1731
+ if e.type == "group":
1732
+ self.update_decorations(e)
1733
+ if stop_updates:
1734
+ self.freeze_tree(False)
1735
+
1736
+ def reset_formatter_cache(self):
1737
+ self.formatter_cache.clear()
1738
+
1739
+ def update_decorations(self, node, force=False):
1740
+ """
1741
+ Updates the decorations for a particular node/tree item
1742
+
1743
+ @param node:
1744
+ @param force: force updating decorations
1745
+ @return:
1746
+ """
1747
+
1748
+ def my_create_label(node, text=None):
1749
+ if text is None:
1750
+ try:
1751
+ text = node._formatter
1752
+ except AttributeError:
1753
+ text = "{element_type}:{id}"
1754
+ # Just for the optical impression (who understands what a "Rect: None" means),
1755
+ # let's replace some of the more obvious ones...
1756
+ mymap = node.default_map()
1757
+ # We change power to either ppi or percent
1758
+ if "power" in mymap and "ppi" in mymap and "percent" in mymap:
1759
+ self.context.device.setting(
1760
+ bool, "use_percent_for_power_display", False
1761
+ )
1762
+ if self.context.device.use_percent_for_power_display:
1763
+ mymap["power"] = mymap["percent"]
1764
+ if "speed" in mymap and "speed_mm_min" in mymap:
1765
+ self.context.device.setting(bool, "use_mm_min_for_speed_display", False)
1766
+ if self.context.device.use_mm_min_for_speed_display:
1767
+ text = text.replace("mm/s", "mm/min")
1768
+ mymap["speed"] = mymap["speed_mm_min"]
1769
+ mymap["speed_unit"] = "mm/min"
1770
+ else:
1771
+ mymap["speed_unit"] = "mm/s"
1772
+ for key in mymap:
1773
+ if hasattr(node, key) and key in mymap and mymap[key] == "None":
1774
+ if getattr(node, key) is None:
1775
+ mymap[key] = "-"
1776
+ # There are a couple of translatable entries,
1777
+ # to make sure we don't get an unwanted translation we add
1778
+ # a special pattern to it
1779
+ translatable = (
1780
+ "element_type",
1781
+ "enabled",
1782
+ )
1783
+ pattern = "_TREE_"
1784
+ for key in mymap:
1785
+ if key in translatable:
1786
+ # Original value
1787
+ std = mymap[key]
1788
+ value = _(pattern + std)
1789
+ if not value.startswith(pattern):
1790
+ mymap[key] = value
1791
+ try:
1792
+ res = text.format_map(mymap)
1793
+ except (ValueError, KeyError):
1794
+ res = text
1795
+ return res
1796
+
1797
+ def get_formatter(nodetype):
1798
+ if nodetype not in self.formatter_cache:
1799
+ default = self.context.elements.lookup(f"format/{nodetype}")
1800
+ lbl = nodetype.replace(" ", "_")
1801
+ check_string = f"formatter_{lbl}_active"
1802
+ pattern_string = f"formatter_{lbl}"
1803
+ self.context.device.setting(bool, check_string, False)
1804
+ self.context.device.setting(str, pattern_string, default)
1805
+ bespoke = getattr(self.context.device, check_string, False)
1806
+ pattern = getattr(self.context.device, pattern_string, "")
1807
+ if bespoke and pattern is not None and pattern != "":
1808
+ default = pattern
1809
+ self.formatter_cache[nodetype] = default
1810
+ return self.formatter_cache[nodetype]
1811
+
1812
+ if force is None:
1813
+ force = False
1814
+ if node._item is None:
1815
+ # This node is not registered the tree has desynced.
1816
+ self.rebuild_tree(source="desync", target="all")
1817
+ return
1818
+
1819
+ self.set_icon(node, force=force)
1820
+ if hasattr(node, "node") and node.node is not None:
1821
+ formatter = get_formatter(node.node.type)
1822
+ if node.node.type.startswith("op "):
1823
+ if not self.context.elements.op_show_default:
1824
+ if hasattr(node.node, "speed"):
1825
+ node.node.speed = node.node.speed
1826
+ if hasattr(node.node, "power"):
1827
+ node.node.power = node.node.power
1828
+ if hasattr(node.node, "dwell_time"):
1829
+ node.node.dwell_time = node.node.dwell_time
1830
+
1831
+ checker = f"dangerlevel_{node.type.replace(' ', '_')}"
1832
+ if hasattr(self.context.device, checker):
1833
+ maxspeed_minpower = getattr(self.context.device, checker)
1834
+ if (
1835
+ isinstance(maxspeed_minpower, (tuple, list))
1836
+ and len(maxspeed_minpower) == 8
1837
+ ):
1838
+ # minpower, maxposer, minspeed, maxspeed
1839
+ # print ("Yes: ", checker, maxspeed_minpower)
1840
+ danger = False
1841
+ if hasattr(node.node, "power"):
1842
+ value = node.node.power
1843
+ if maxspeed_minpower[0] and value < maxspeed_minpower[1]:
1844
+ danger = True
1845
+ if maxspeed_minpower[2] and value > maxspeed_minpower[3]:
1846
+ danger = True
1847
+ if hasattr(node.node, "speed"):
1848
+ value = node.node.speed
1849
+ if maxspeed_minpower[4] and value < maxspeed_minpower[5]:
1850
+ danger = True
1851
+ if maxspeed_minpower[6] and value > maxspeed_minpower[7]:
1852
+ danger = True
1853
+ if hasattr(node.node, "dangerous"):
1854
+ node.node.dangerous = danger
1855
+ else:
1856
+ setattr(self.context.device, checker, [False, 0] * 4)
1857
+ print(
1858
+ f"That's strange {checker}: {type(maxspeed_minpower).__name__}"
1859
+ )
1860
+ # node.node.is_dangerous(maxspeed, minpower)
1861
+ # label = "*" + node.node.create_label(formatter)
1862
+ label = f"*{my_create_label(node.node, formatter)}"
1863
+ else:
1864
+ formatter = get_formatter(node.type)
1865
+ if node.type.startswith("op "):
1866
+ # Not too elegant... op nodes should have a property default_speed, default_power
1867
+ if not self.context.elements.op_show_default:
1868
+ if hasattr(node, "speed"):
1869
+ node.speed = node.speed
1870
+ if hasattr(node, "power"):
1871
+ node.power = node.power
1872
+ if hasattr(node, "dwell_time"):
1873
+ node.dwell_time = node.dwell_time
1874
+ checker = f"dangerlevel_{node.type.replace(' ', '_')}"
1875
+ if hasattr(self.context.device, checker):
1876
+ maxspeed_minpower = getattr(self.context.device, checker)
1877
+ if (
1878
+ isinstance(maxspeed_minpower, (tuple, list))
1879
+ and len(maxspeed_minpower) == 8
1880
+ ):
1881
+ # minpower, maxposer, minspeed, maxspeed
1882
+ # print ("Yes: ", checker, maxspeed_minpower)
1883
+ danger = False
1884
+ if hasattr(node, "power"):
1885
+ value = float(node.power)
1886
+ if maxspeed_minpower[0] and value < maxspeed_minpower[1]:
1887
+ danger = True
1888
+ if maxspeed_minpower[2] and value > maxspeed_minpower[3]:
1889
+ danger = True
1890
+ if hasattr(node, "speed"):
1891
+ value = float(node.speed)
1892
+ if maxspeed_minpower[4] and value < maxspeed_minpower[5]:
1893
+ danger = True
1894
+ if maxspeed_minpower[6] and value > maxspeed_minpower[7]:
1895
+ danger = True
1896
+ if hasattr(node, "dangerous"):
1897
+ node.dangerous = danger
1898
+ else:
1899
+ setattr(self.context.device, checker, [False, 0] * 4)
1900
+ print(
1901
+ f"That's strange {checker}: {type(maxspeed_minpower).__name__}"
1902
+ )
1903
+ # label = node.create_label(formatter)
1904
+ label = my_create_label(node, formatter)
1905
+
1906
+ self.wxtree.SetItemText(node._item, label)
1907
+ attribute_to_try = "fill" if node.type == "elem text" else "stroke"
1908
+ wxcolor = None
1909
+ if hasattr(node, attribute_to_try):
1910
+ wxcolor = self.safe_color(getattr(node, attribute_to_try))
1911
+ elif hasattr(node, "color"):
1912
+ wxcolor = self.safe_color(node.color)
1913
+ else:
1914
+ back_color = self.wxtree.GetBackgroundColour()
1915
+ rgb = back_color.Get()
1916
+ background = Color(rgb[0], rgb[1], rgb[2])
1917
+ if background is not None:
1918
+ c1 = Color("Black")
1919
+ c2 = Color("White")
1920
+ if Color.distance(background, c1) > Color.distance(background, c2):
1921
+ textcolor = c1
1922
+ else:
1923
+ textcolor = c2
1924
+ wxcolor = wx.Colour(swizzlecolor(textcolor))
1925
+ if self.context.root.tree_colored:
1926
+ try:
1927
+ self.wxtree.SetItemTextColour(node._item, wxcolor)
1928
+ except (AttributeError, KeyError, TypeError):
1929
+ pass
1930
+
1931
+ state_num = -1
1932
+ if node is self.elements.get(type="branch ops"):
1933
+ unassigned, unburnt = self.elements.have_unburnable_elements()
1934
+ if unassigned or unburnt:
1935
+ state_num = self.iconstates["warning"]
1936
+ else:
1937
+ # Has the node a lock attribute?
1938
+ lockit = node.lock if hasattr(node, "lock") else False
1939
+ if lockit:
1940
+ state_num = self.iconstates["lock"]
1941
+ scene = getattr(self.context.root, "mainscene", None)
1942
+ if scene is not None and node == scene.pane.reference_object:
1943
+ state_num = self.iconstates["refobject"]
1944
+ if state_num < 0:
1945
+ state_num = wx.TREE_ITEMSTATE_NONE
1946
+ if (
1947
+ node.type in op_nodes
1948
+ and hasattr(node, "is_visible")
1949
+ and not node.is_visible
1950
+ ) or (
1951
+ node.type in elem_nodes and hasattr(node, "hidden") and node.hidden
1952
+ ) or (
1953
+ hasattr(node, "node") and hasattr(node.node, "hidden") and node.node.hidden
1954
+ ):
1955
+ state_num = self.iconstates["ghost"]
1956
+ self.wxtree.SetItemState(node._item, state_num)
1957
+
1958
+ def on_drag_begin_handler(self, event):
1959
+ """
1960
+ Drag handler begin for the tree.
1961
+
1962
+ @param event:
1963
+ @return:
1964
+ """
1965
+
1966
+ def typefamily(typename):
1967
+ # Combine similar nodetypes
1968
+ if typename.startswith("op "):
1969
+ result = "op"
1970
+ elif typename.startswith("elem "):
1971
+ result = "elem"
1972
+ elif typename.startswith("group"):
1973
+ result = "elem"
1974
+ elif typename.startswith("file"):
1975
+ result = "elem"
1976
+ else:
1977
+ result = typename
1978
+ return result
1979
+
1980
+ self.dragging_nodes = None
1981
+
1982
+ pt = event.GetPoint()
1983
+ drag_item, _ = self.wxtree.HitTest(pt)
1984
+
1985
+ if drag_item is None or drag_item.ID is None or not drag_item.IsOk():
1986
+ # errmsg = ""
1987
+ # if drag_item is None:
1988
+ # errmsg = "item was none"
1989
+ # elif drag_item.ID is None:
1990
+ # errmsg = "id was none"
1991
+ # elif not drag_item.IsOk():
1992
+ # errmsg = "IsOk was false"
1993
+ # print (f"Drag item was wrong: {errmsg}")
1994
+ event.Skip()
1995
+ return
1996
+
1997
+ self.dragging_nodes = []
1998
+ for item in self.wxtree.GetSelections():
1999
+ node = self.wxtree.GetItemData(item)
2000
+ if node is not None and node.is_draggable():
2001
+ self.dragging_nodes.append(node)
2002
+
2003
+ if not self.dragging_nodes:
2004
+ # print ("Dragging_nodes was empty")
2005
+ event.Skip()
2006
+ return
2007
+
2008
+ t = typefamily(self.dragging_nodes[0].type)
2009
+ for n in self.dragging_nodes:
2010
+ tt = typefamily(n.type)
2011
+ if t != tt:
2012
+ # Different typefamilies
2013
+ # print ("Different typefamilies")
2014
+ event.Skip()
2015
+ return
2016
+ if not n.is_draggable():
2017
+ # print ("Element was not draggable")
2018
+ event.Skip()
2019
+ return
2020
+ event.Allow()
2021
+
2022
+ def on_drag_end_handler(self, event):
2023
+ """
2024
+ Drag end handler for the tree
2025
+
2026
+ @param event:
2027
+ @return:
2028
+ """
2029
+ if self.dragging_nodes is None:
2030
+ event.Skip()
2031
+ return
2032
+ self.wxtree.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
2033
+ drop_item = event.GetItem()
2034
+ if drop_item is None or drop_item.ID is None:
2035
+ event.Skip()
2036
+ return
2037
+ drop_node = self.wxtree.GetItemData(drop_item)
2038
+ if drop_node is None:
2039
+ event.Skip()
2040
+ return
2041
+ # Is the node expanded? If yes regular dnd applies, if not we will add the node to the end...
2042
+ closed_leaf = (self.wxtree.ItemHasChildren(drop_item) and not self.wxtree.IsExpanded(drop_item))
2043
+ # We extend the logic by calling the appropriate elems routine
2044
+ skip = not self.elements.drag_and_drop(self.dragging_nodes, drop_node, flag=closed_leaf)
2045
+ if skip:
2046
+ event.Skip()
2047
+ self.dragging_nodes = None
2048
+ return
2049
+ event.Allow()
2050
+ # Make sure that the drop node is visible
2051
+ self.wxtree.Expand(drop_item)
2052
+ self.wxtree.EnsureVisible(drop_item)
2053
+ self.refresh_tree(source="drag end")
2054
+ # Do the dragging_nodes contain an operation?
2055
+ # Let's give an indication of that, as this may
2056
+ # have led to the creation of a new reference
2057
+ # node. For whatever reason this is not recognised
2058
+ # otherwise...
2059
+ if not self.dragging_nodes:
2060
+ # Dragging nodes were cleared (we must have rebuilt the entire tree)
2061
+ return
2062
+ for node in self.dragging_nodes:
2063
+ if node.type.startswith("op"):
2064
+ self.context.signal("tree_changed")
2065
+ break
2066
+ # self.rebuild_tree()
2067
+ self.reset_dragging()
2068
+
2069
+ def on_mouse_over(self, event):
2070
+ # establish the item we are over...
2071
+ event.Skip()
2072
+ ttip = ""
2073
+ pt = event.GetPosition()
2074
+ item, flags = self.wxtree.HitTest(pt)
2075
+ if self._last_hover_item is item:
2076
+ return
2077
+ if item:
2078
+ state = self.wxtree.GetItemState(item)
2079
+ node = self.wxtree.GetItemData(item)
2080
+ if node is not None:
2081
+ # Lets check the dragging status
2082
+ if self.dragging_nodes:
2083
+ if hasattr(node, "would_accept_drop"):
2084
+ would_drop = node.would_accept_drop(self.dragging_nodes)
2085
+ else:
2086
+ would_drop = False
2087
+ if would_drop:
2088
+ self.wxtree.SetCursor(wx.Cursor(wx.CURSOR_HAND))
2089
+ else:
2090
+ self.wxtree.SetCursor(wx.Cursor(wx.CURSOR_NO_ENTRY))
2091
+ else:
2092
+ self.wxtree.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
2093
+
2094
+ if hasattr(node, "_tooltip"):
2095
+ # That has precedence and will be displayed in all cases
2096
+ ttip = node._tooltip
2097
+ elif not self.context.disable_tree_tool_tips:
2098
+ if node.type == "blob":
2099
+ ttip = _(
2100
+ "This is binary data imported or generated\n"
2101
+ + "that will be sent directly to the laser.\n"
2102
+ + "Double-click to view."
2103
+ )
2104
+ elif node.type == "op cut":
2105
+ ttip = _(
2106
+ "This will engrave/cut the elements contained,\n"
2107
+ + "following the vector-paths of the data.\n"
2108
+ + "(Usually done last)"
2109
+ )
2110
+ elif node.type == "op engrave":
2111
+ ttip = _(
2112
+ "This will engrave the elements contained,\n"
2113
+ + "following the vector-paths of the data."
2114
+ )
2115
+ elif node.type == "op image":
2116
+ ttip = _(
2117
+ "This engraves already created images pixel by pixel,\n"
2118
+ + "applying the settings to the individual pictures"
2119
+ )
2120
+ elif node.type == "op raster":
2121
+ ttip = _(
2122
+ "This will render all contained elements\n"
2123
+ + "into an intermediary image which then will be\n"
2124
+ + "engraved pixel by pixel."
2125
+ )
2126
+ elif node.type == "op dots":
2127
+ ttip = _(
2128
+ "This will engrave a single point for a given period of time"
2129
+ )
2130
+ elif node.type == "util console":
2131
+ ttip = _(
2132
+ "This allows to execute an arbitrary command during the engrave process"
2133
+ )
2134
+ elif node.type == "util goto":
2135
+ ttip = _(
2136
+ "This will send the laser back to its logical start position"
2137
+ )
2138
+ elif node.type == "util home":
2139
+ ttip = _(
2140
+ "This will send the laser back to its physical start position"
2141
+ )
2142
+ elif node.type == "util input":
2143
+ ttip = _(
2144
+ "This will wait for active IO bits on the laser (mainly fibre laser for now)"
2145
+ )
2146
+ elif node.type == "util output":
2147
+ ttip = _(
2148
+ "This will set some IO bits on the laser (mainly fibre laser for now)"
2149
+ )
2150
+ elif node.type == "util wait":
2151
+ ttip = _(
2152
+ "This will pause the engrave process for a given period"
2153
+ )
2154
+ elif node.type == "branch reg":
2155
+ ttip = _(
2156
+ "The elements under this section will not be engraved,\n"
2157
+ + "they can serve as a template or registration marks."
2158
+ )
2159
+ elif node.type == "elem line":
2160
+ bb = node.bounds
2161
+ if bb is not None:
2162
+ ww = Length(amount=bb[2] - bb[0], digits=1)
2163
+ hh = Length(amount=bb[3] - bb[1], digits=1)
2164
+ ll = Length(amount=node.length(), digits=1)
2165
+ ttip = f"{ww.length_mm} x {hh.length_mm}, L={ll.length_mm}"
2166
+ elif node.type == "elem rect":
2167
+ bb = node.bounds
2168
+ if bb is not None:
2169
+ ww = Length(amount=bb[2] - bb[0], digits=1)
2170
+ hh = Length(amount=bb[3] - bb[1], digits=1)
2171
+ ll = Length(amount=node.length(), digits=1)
2172
+ ttip = f"{ww.length_mm} x {hh.length_mm}, L={ll.length_mm}"
2173
+ elif node.type == "elem polyline":
2174
+ bb = node.bounds
2175
+ if bb is not None:
2176
+ ww = Length(amount=bb[2] - bb[0], digits=1)
2177
+ hh = Length(amount=bb[3] - bb[1], digits=1)
2178
+ ll = Length(amount=node.length(), digits=1)
2179
+ ttip = f"{ww.length_mm} x {hh.length_mm}, L={ll.length_mm}"
2180
+ ttip += f"\n{len(node)} pts"
2181
+ elif node.type == "elem ellipse":
2182
+ bb = node.bounds
2183
+ if bb is not None:
2184
+ ww = Length(amount=bb[2] - bb[0], digits=1)
2185
+ hh = Length(amount=bb[3] - bb[1], digits=1)
2186
+ ttip = f"{ww.length_mm} x {hh.length_mm}"
2187
+ elif node.type == "elem path":
2188
+ bb = node.bounds
2189
+ if bb is not None:
2190
+ ww = Length(amount=bb[2] - bb[0], digits=1)
2191
+ hh = Length(amount=bb[3] - bb[1], digits=1)
2192
+ ttip = f"{ww.length_mm} x {hh.length_mm}"
2193
+ ttip += f"\n{len(node.path)} segments"
2194
+ elif node.type == "elem text":
2195
+ bb = node.bounds
2196
+ if bb is not None:
2197
+ ww = Length(amount=bb[2] - bb[0], digits=1)
2198
+ hh = Length(amount=bb[3] - bb[1], digits=1)
2199
+ ttip = f"{ww.length_mm} x {hh.length_mm}"
2200
+ # ttip += f"\n{node.font}"
2201
+ elif node.type == "place current":
2202
+ ttip = _(
2203
+ "This is a placeholder for the 'place current' operation"
2204
+ )
2205
+ elif node.type == "place point":
2206
+ ttip = _(
2207
+ "This will define an origin from where all the elements in this scene\n"
2208
+ + "will be plotted. You can have multiple such job start points"
2209
+ )
2210
+ elif node.type == "effect hatch":
2211
+ ttip = _(
2212
+ "This is a special node that will consume any other closed path\n"
2213
+ + "you drag onto it and will fill the shape with a line pattern.\n"
2214
+ + "To activate / deactivate this effect please use the context menu."
2215
+ )
2216
+ if node.type in op_nodes:
2217
+ if hasattr(node, "label") and node.label is not None:
2218
+ ttip += f"\n{node.id + ': ' if node.id is not None else ''}{node.display_label()}"
2219
+ ps_info = ""
2220
+ if hasattr(node, "power") and node.power is not None:
2221
+ try:
2222
+ p = float(node.power)
2223
+ if self.context.device.use_percent_for_power_display:
2224
+ ps_info += f"{', ' if ps_info else ''}{p / 10:.1f}%"
2225
+ else:
2226
+ ps_info += f"{', ' if ps_info else ''}{p:.0f}ppi"
2227
+ except ValueError:
2228
+ pass
2229
+
2230
+ if hasattr(node, "speed") and node.speed is not None:
2231
+ try:
2232
+ p = float(node.speed)
2233
+ if self.context.device.use_mm_min_for_speed_display:
2234
+ ps_info += (
2235
+ f"{', ' if ps_info else ''}{p * 60.0:.0f}mm/min"
2236
+ )
2237
+ else:
2238
+ ps_info += f"{', ' if ps_info else ''}{p:.0f}mm/s"
2239
+ except ValueError:
2240
+ pass
2241
+
2242
+ if (
2243
+ hasattr(self.context.device, "default_frequency")
2244
+ and hasattr(node, "frequency")
2245
+ and node.frequency is not None
2246
+ ):
2247
+ try:
2248
+ p = float(node.frequency)
2249
+ ps_info += f"{', ' if ps_info else ''}{p:.0f}kHz"
2250
+ except ValueError:
2251
+ pass
2252
+
2253
+ if ps_info:
2254
+ ttip += f"\n{ps_info}"
2255
+ if state == self.iconstates["ghost"]:
2256
+ ttip = _("HIDDEN: ") + ttip
2257
+ self._last_hover_item = item
2258
+ if ttip != self.wxtree.GetToolTipText():
2259
+ self.wxtree.SetToolTip(ttip)
2260
+
2261
+ def on_item_right_click(self, event):
2262
+ """
2263
+ Right click of element in tree.
2264
+
2265
+ @param event:
2266
+ @return:
2267
+ """
2268
+ item = event.GetItem()
2269
+ if item is None:
2270
+ return
2271
+ node = self.wxtree.GetItemData(item)
2272
+
2273
+ create_menu(self.gui, node, self.elements)
2274
+
2275
+ def on_item_activated(self, event):
2276
+ """
2277
+ Tree item is double-clicked. Launches PropertyWindow associated with that object.
2278
+
2279
+ @param event:
2280
+ @return:
2281
+ """
2282
+ item = event.GetItem()
2283
+ node = self.wxtree.GetItemData(item)
2284
+ activate = self.elements.lookup("function/open_property_window_for_node")
2285
+ if activate is not None:
2286
+ activate(node)
2287
+
2288
+ def activate_selected_node(self, *args):
2289
+ """
2290
+ Call activated on the first emphasized node.
2291
+
2292
+ @param args:
2293
+ @return:
2294
+ """
2295
+ first_element = self.elements.first_element(emphasized=True)
2296
+ if first_element is None:
2297
+ first_element = self.elements.first_element(selected=True)
2298
+ if first_element is None:
2299
+ return
2300
+ if hasattr(first_element, "node"):
2301
+ # Reference
2302
+ first_element = first_element.node
2303
+ activate = self.elements.lookup("function/open_property_window_for_node")
2304
+ if activate is not None:
2305
+ activate(first_element)
2306
+
2307
+ def on_collapse(self, event):
2308
+ if self.do_not_select:
2309
+ # Do not select is part of a linux correction where moving nodes around in a drag and drop fashion could
2310
+ # cause them to appear to drop invalid nodes.
2311
+ return
2312
+ item = event.GetItem()
2313
+ if not item:
2314
+ return
2315
+ node = self.wxtree.GetItemData(item)
2316
+ node.expanded = False
2317
+
2318
+ def on_expand(self, event):
2319
+ if self.do_not_select:
2320
+ # Do not select is part of a linux correction where moving nodes around in a drag and drop fashion could
2321
+ # cause them to appear to drop invalid nodes.
2322
+ return
2323
+ item = event.GetItem()
2324
+ if not item:
2325
+ return
2326
+ node = self.wxtree.GetItemData(item)
2327
+ node.expanded = True
2328
+
2329
+ def on_state_icon(self, event):
2330
+ if self.do_not_select:
2331
+ # Do not select is part of a linux correction where moving nodes around in a drag and drop fashion could
2332
+ # cause them to appear to drop invalid nodes.
2333
+ return
2334
+ item = event.GetItem()
2335
+ if not item:
2336
+ return
2337
+ node = self.wxtree.GetItemData(item)
2338
+ if hasattr(node, "hidden"):
2339
+ node.hidden = False
2340
+ self.context.elements.set_emphasis([node])
2341
+ # self.context.signal("refresh_scene", "Scene")
2342
+ self.update_decorations(node)
2343
+
2344
+ def on_item_selection_changed(self, event):
2345
+ """
2346
+ Tree menu item is changed. Modify the selection.
2347
+
2348
+ @param event:
2349
+ @return:
2350
+ """
2351
+ if self.do_not_select:
2352
+ # Do not select is part of a linux correction where moving nodes around in a drag and drop fashion could
2353
+ # cause them to appear to drop invalid nodes.
2354
+ return
2355
+ # print (f"tree claims: {self.wxtree.FindFocus().GetId()}, parent claims: {self.wxtree.GetParent().FindFocus().GetId()}, toplevel claims: {self.wxtree.GetTopLevelParent().FindFocus().GetId()}, tree-id={self.wxtree.GetId()}")
2356
+ its_me = self.wxtree.FindFocus() is self.wxtree
2357
+ # Just out of curiosity, is there no image set? Then just do it again.
2358
+ item = event.GetItem()
2359
+ if item:
2360
+ image_id = self.wxtree.GetItemImage(item)
2361
+ if image_id >= self.tree_images.ImageCount:
2362
+ image_id = -1
2363
+ if image_id < 0:
2364
+ node = self.wxtree.GetItemData(item)
2365
+ if node is not None:
2366
+ self.set_icon(node, force=True)
2367
+
2368
+ selected = [
2369
+ self.wxtree.GetItemData(item) for item in self.wxtree.GetSelections()
2370
+ ]
2371
+
2372
+ emphasized = list(selected)
2373
+ for i in range(len(emphasized)):
2374
+ node = emphasized[i]
2375
+ if node is None or node.type is None:
2376
+ # Rare issue seen building the tree during materials test and the node type didn't exist.
2377
+ return
2378
+ if node.type == "reference":
2379
+ emphasized[i] = node.node
2380
+ elif node.type.startswith("op"):
2381
+ for n in node.flat(types=("reference",), cascade=False):
2382
+ try:
2383
+ emphasized.append(n.node)
2384
+ except Exception:
2385
+ pass
2386
+ self.elements.set_emphasis(emphasized)
2387
+ self.elements.set_selected(selected)
2388
+ # self.refresh_tree(source="on_item_selection")
2389
+ event.Allow()
2390
+
2391
+ # We seem to lose focus, so lets reclaim it
2392
+ if its_me:
2393
+ def restore_focus():
2394
+ self.wxtree.SetFocus()
2395
+ wx.CallAfter(restore_focus)
2396
+
2397
+ def select_in_tree_by_emphasis(self, origin, *args):
2398
+ """
2399
+ Selected the actual `wx.tree` control those items which are currently emphasized.
2400
+
2401
+ @return:
2402
+ """
2403
+ self.do_not_select = True
2404
+ self.wxtree.UnselectAll()
2405
+ require_rebuild = False
2406
+ for e in self.elements.elems_nodes(emphasized=True):
2407
+ if e._item:
2408
+ self.wxtree.SelectItem(e._item, True)
2409
+ else:
2410
+ # That should not happen, apparently we have a not fully built tree
2411
+ require_rebuild = True
2412
+ break
2413
+ if require_rebuild:
2414
+ self.context.signal("rebuild_tree", "all")
2415
+
2416
+ self.do_not_select = False