meerk40t 0.9.3001__py2.py3-none-any.whl → 0.9.7010__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 (445) 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 +1195 -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 +1844 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4561 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4329 -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 +933 -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/trace.py +651 -563
  66. meerk40t/core/elements/tree_commands.py +415 -409
  67. meerk40t/core/elements/undo_redo.py +116 -58
  68. meerk40t/core/elements/wordlist.py +319 -200
  69. meerk40t/core/exceptions.py +9 -9
  70. meerk40t/core/laserjob.py +220 -220
  71. meerk40t/core/logging.py +63 -63
  72. meerk40t/core/node/blobnode.py +83 -86
  73. meerk40t/core/node/bootstrap.py +105 -103
  74. meerk40t/core/node/branch_elems.py +40 -31
  75. meerk40t/core/node/branch_ops.py +45 -38
  76. meerk40t/core/node/branch_regmark.py +48 -41
  77. meerk40t/core/node/cutnode.py +29 -32
  78. meerk40t/core/node/effect_hatch.py +375 -257
  79. meerk40t/core/node/effect_warp.py +398 -0
  80. meerk40t/core/node/effect_wobble.py +441 -309
  81. meerk40t/core/node/elem_ellipse.py +404 -309
  82. meerk40t/core/node/elem_image.py +1082 -801
  83. meerk40t/core/node/elem_line.py +358 -292
  84. meerk40t/core/node/elem_path.py +259 -201
  85. meerk40t/core/node/elem_point.py +129 -102
  86. meerk40t/core/node/elem_polyline.py +310 -246
  87. meerk40t/core/node/elem_rect.py +376 -286
  88. meerk40t/core/node/elem_text.py +445 -418
  89. meerk40t/core/node/filenode.py +59 -40
  90. meerk40t/core/node/groupnode.py +138 -74
  91. meerk40t/core/node/image_processed.py +777 -766
  92. meerk40t/core/node/image_raster.py +156 -113
  93. meerk40t/core/node/layernode.py +31 -31
  94. meerk40t/core/node/mixins.py +135 -107
  95. meerk40t/core/node/node.py +1427 -1304
  96. meerk40t/core/node/nutils.py +117 -114
  97. meerk40t/core/node/op_cut.py +462 -335
  98. meerk40t/core/node/op_dots.py +296 -251
  99. meerk40t/core/node/op_engrave.py +414 -311
  100. meerk40t/core/node/op_image.py +755 -369
  101. meerk40t/core/node/op_raster.py +787 -522
  102. meerk40t/core/node/place_current.py +37 -40
  103. meerk40t/core/node/place_point.py +329 -126
  104. meerk40t/core/node/refnode.py +58 -47
  105. meerk40t/core/node/rootnode.py +225 -219
  106. meerk40t/core/node/util_console.py +48 -48
  107. meerk40t/core/node/util_goto.py +84 -65
  108. meerk40t/core/node/util_home.py +61 -61
  109. meerk40t/core/node/util_input.py +102 -102
  110. meerk40t/core/node/util_output.py +102 -102
  111. meerk40t/core/node/util_wait.py +65 -65
  112. meerk40t/core/parameters.py +709 -707
  113. meerk40t/core/planner.py +875 -785
  114. meerk40t/core/plotplanner.py +656 -652
  115. meerk40t/core/space.py +120 -113
  116. meerk40t/core/spoolers.py +706 -705
  117. meerk40t/core/svg_io.py +1836 -1549
  118. meerk40t/core/treeop.py +534 -445
  119. meerk40t/core/undos.py +278 -124
  120. meerk40t/core/units.py +784 -680
  121. meerk40t/core/view.py +393 -322
  122. meerk40t/core/webhelp.py +62 -62
  123. meerk40t/core/wordlist.py +513 -504
  124. meerk40t/cylinder/cylinder.py +247 -0
  125. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  126. meerk40t/cylinder/gui/gui.py +24 -0
  127. meerk40t/device/__init__.py +1 -1
  128. meerk40t/device/basedevice.py +322 -123
  129. meerk40t/device/devicechoices.py +50 -0
  130. meerk40t/device/dummydevice.py +163 -128
  131. meerk40t/device/gui/defaultactions.py +618 -602
  132. meerk40t/device/gui/effectspanel.py +114 -0
  133. meerk40t/device/gui/formatterpanel.py +253 -290
  134. meerk40t/device/gui/warningpanel.py +337 -260
  135. meerk40t/device/mixins.py +13 -13
  136. meerk40t/dxf/__init__.py +1 -1
  137. meerk40t/dxf/dxf_io.py +766 -554
  138. meerk40t/dxf/plugin.py +47 -35
  139. meerk40t/external_plugins.py +79 -79
  140. meerk40t/external_plugins_build.py +28 -28
  141. meerk40t/extra/cag.py +112 -116
  142. meerk40t/extra/coolant.py +403 -0
  143. meerk40t/extra/encode_detect.py +198 -0
  144. meerk40t/extra/ezd.py +1165 -1165
  145. meerk40t/extra/hershey.py +835 -340
  146. meerk40t/extra/imageactions.py +322 -316
  147. meerk40t/extra/inkscape.py +630 -622
  148. meerk40t/extra/lbrn.py +424 -424
  149. meerk40t/extra/outerworld.py +284 -0
  150. meerk40t/extra/param_functions.py +1542 -1556
  151. meerk40t/extra/potrace.py +257 -253
  152. meerk40t/extra/serial_exchange.py +118 -0
  153. meerk40t/extra/updater.py +602 -453
  154. meerk40t/extra/vectrace.py +147 -146
  155. meerk40t/extra/winsleep.py +83 -83
  156. meerk40t/extra/xcs_reader.py +597 -0
  157. meerk40t/fill/fills.py +781 -335
  158. meerk40t/fill/patternfill.py +1061 -1061
  159. meerk40t/fill/patterns.py +614 -567
  160. meerk40t/grbl/control.py +87 -87
  161. meerk40t/grbl/controller.py +990 -903
  162. meerk40t/grbl/device.py +1081 -768
  163. meerk40t/grbl/driver.py +989 -771
  164. meerk40t/grbl/emulator.py +532 -497
  165. meerk40t/grbl/gcodejob.py +783 -767
  166. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  167. meerk40t/grbl/gui/grblcontroller.py +485 -271
  168. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  169. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  170. meerk40t/grbl/gui/gui.py +147 -116
  171. meerk40t/grbl/interpreter.py +44 -44
  172. meerk40t/grbl/loader.py +22 -22
  173. meerk40t/grbl/mock_connection.py +56 -56
  174. meerk40t/grbl/plugin.py +294 -264
  175. meerk40t/grbl/serial_connection.py +93 -88
  176. meerk40t/grbl/tcp_connection.py +81 -79
  177. meerk40t/grbl/ws_connection.py +112 -0
  178. meerk40t/gui/__init__.py +1 -1
  179. meerk40t/gui/about.py +2042 -296
  180. meerk40t/gui/alignment.py +1644 -1608
  181. meerk40t/gui/autoexec.py +199 -0
  182. meerk40t/gui/basicops.py +791 -670
  183. meerk40t/gui/bufferview.py +77 -71
  184. meerk40t/gui/busy.py +170 -133
  185. meerk40t/gui/choicepropertypanel.py +1673 -1469
  186. meerk40t/gui/consolepanel.py +706 -542
  187. meerk40t/gui/devicepanel.py +687 -581
  188. meerk40t/gui/dialogoptions.py +110 -107
  189. meerk40t/gui/executejob.py +316 -306
  190. meerk40t/gui/fonts.py +90 -90
  191. meerk40t/gui/functionwrapper.py +252 -0
  192. meerk40t/gui/gui_mixins.py +729 -0
  193. meerk40t/gui/guicolors.py +205 -182
  194. meerk40t/gui/help_assets/help_assets.py +218 -201
  195. meerk40t/gui/helper.py +154 -0
  196. meerk40t/gui/hersheymanager.py +1430 -846
  197. meerk40t/gui/icons.py +3422 -2747
  198. meerk40t/gui/imagesplitter.py +555 -508
  199. meerk40t/gui/keymap.py +354 -344
  200. meerk40t/gui/laserpanel.py +892 -806
  201. meerk40t/gui/laserrender.py +1470 -1232
  202. meerk40t/gui/lasertoolpanel.py +805 -793
  203. meerk40t/gui/magnetoptions.py +436 -0
  204. meerk40t/gui/materialmanager.py +2917 -0
  205. meerk40t/gui/materialtest.py +1722 -1694
  206. meerk40t/gui/mkdebug.py +646 -359
  207. meerk40t/gui/mwindow.py +163 -140
  208. meerk40t/gui/navigationpanels.py +2605 -2467
  209. meerk40t/gui/notes.py +143 -142
  210. meerk40t/gui/opassignment.py +414 -410
  211. meerk40t/gui/operation_info.py +310 -299
  212. meerk40t/gui/plugin.py +494 -328
  213. meerk40t/gui/position.py +714 -669
  214. meerk40t/gui/preferences.py +901 -650
  215. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  216. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  217. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  218. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  219. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  220. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  221. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  222. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  223. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  224. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  225. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  226. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  227. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  228. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  229. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  230. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  231. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  232. meerk40t/gui/propertypanels/textproperty.py +770 -755
  233. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  234. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  235. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  236. meerk40t/gui/ribbon.py +2468 -2210
  237. meerk40t/gui/scene/scene.py +1100 -1051
  238. meerk40t/gui/scene/sceneconst.py +22 -22
  239. meerk40t/gui/scene/scenepanel.py +439 -349
  240. meerk40t/gui/scene/scenespacewidget.py +365 -365
  241. meerk40t/gui/scene/widget.py +518 -505
  242. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  243. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  244. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  245. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  246. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  247. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  248. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  249. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  250. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  251. meerk40t/gui/scenewidgets/rectselectwidget.py +589 -346
  252. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  253. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  254. meerk40t/gui/scenewidgets/selectionwidget.py +2952 -2756
  255. meerk40t/gui/simpleui.py +357 -333
  256. meerk40t/gui/simulation.py +2431 -2094
  257. meerk40t/gui/snapoptions.py +208 -203
  258. meerk40t/gui/spoolerpanel.py +1227 -1180
  259. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  260. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  261. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  262. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  263. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  264. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  265. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  266. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  267. meerk40t/gui/themes.py +200 -78
  268. meerk40t/gui/tips.py +591 -0
  269. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  270. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  271. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  272. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  273. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  274. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  275. meerk40t/gui/toolwidgets/toolline.py +39 -144
  276. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  277. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  278. meerk40t/gui/toolwidgets/toolmeasure.py +160 -216
  279. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  280. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  281. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  282. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  283. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  284. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  285. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  286. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  287. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  288. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  289. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  290. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  291. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  292. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  293. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  294. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  295. meerk40t/gui/usbconnect.py +98 -91
  296. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  297. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  298. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  299. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  300. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  301. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  302. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  303. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  304. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  305. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  306. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  307. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  308. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  309. meerk40t/gui/wordlisteditor.py +985 -931
  310. meerk40t/gui/wxmeerk40t.py +1444 -1169
  311. meerk40t/gui/wxmmain.py +5578 -4112
  312. meerk40t/gui/wxmribbon.py +1591 -1076
  313. meerk40t/gui/wxmscene.py +1635 -1453
  314. meerk40t/gui/wxmtree.py +2410 -2089
  315. meerk40t/gui/wxutils.py +1769 -1099
  316. meerk40t/gui/zmatrix.py +102 -102
  317. meerk40t/image/__init__.py +1 -1
  318. meerk40t/image/dither.py +429 -0
  319. meerk40t/image/imagetools.py +2778 -2269
  320. meerk40t/internal_plugins.py +150 -130
  321. meerk40t/kernel/__init__.py +63 -12
  322. meerk40t/kernel/channel.py +259 -212
  323. meerk40t/kernel/context.py +538 -538
  324. meerk40t/kernel/exceptions.py +41 -41
  325. meerk40t/kernel/functions.py +463 -414
  326. meerk40t/kernel/jobs.py +100 -100
  327. meerk40t/kernel/kernel.py +3809 -3571
  328. meerk40t/kernel/lifecycles.py +71 -71
  329. meerk40t/kernel/module.py +49 -49
  330. meerk40t/kernel/service.py +147 -147
  331. meerk40t/kernel/settings.py +383 -343
  332. meerk40t/lihuiyu/controller.py +883 -876
  333. meerk40t/lihuiyu/device.py +1181 -1069
  334. meerk40t/lihuiyu/driver.py +1466 -1372
  335. meerk40t/lihuiyu/gui/gui.py +127 -106
  336. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  337. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  338. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  339. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  340. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  341. meerk40t/lihuiyu/interpreter.py +53 -53
  342. meerk40t/lihuiyu/laserspeed.py +450 -450
  343. meerk40t/lihuiyu/loader.py +90 -90
  344. meerk40t/lihuiyu/parser.py +404 -404
  345. meerk40t/lihuiyu/plugin.py +101 -102
  346. meerk40t/lihuiyu/tcp_connection.py +111 -109
  347. meerk40t/main.py +231 -165
  348. meerk40t/moshi/builder.py +788 -781
  349. meerk40t/moshi/controller.py +505 -499
  350. meerk40t/moshi/device.py +495 -442
  351. meerk40t/moshi/driver.py +862 -696
  352. meerk40t/moshi/gui/gui.py +78 -76
  353. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  354. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  355. meerk40t/moshi/plugin.py +43 -43
  356. meerk40t/network/console_server.py +102 -57
  357. meerk40t/network/kernelserver.py +10 -9
  358. meerk40t/network/tcp_server.py +142 -140
  359. meerk40t/network/udp_server.py +103 -77
  360. meerk40t/network/web_server.py +390 -0
  361. meerk40t/newly/controller.py +1158 -1144
  362. meerk40t/newly/device.py +874 -732
  363. meerk40t/newly/driver.py +540 -412
  364. meerk40t/newly/gui/gui.py +219 -188
  365. meerk40t/newly/gui/newlyconfig.py +116 -101
  366. meerk40t/newly/gui/newlycontroller.py +193 -186
  367. meerk40t/newly/gui/operationproperties.py +51 -51
  368. meerk40t/newly/mock_connection.py +82 -82
  369. meerk40t/newly/newly_params.py +56 -56
  370. meerk40t/newly/plugin.py +1214 -1246
  371. meerk40t/newly/usb_connection.py +322 -322
  372. meerk40t/rotary/gui/gui.py +52 -46
  373. meerk40t/rotary/gui/rotarysettings.py +240 -232
  374. meerk40t/rotary/rotary.py +202 -98
  375. meerk40t/ruida/control.py +291 -91
  376. meerk40t/ruida/controller.py +138 -1088
  377. meerk40t/ruida/device.py +672 -231
  378. meerk40t/ruida/driver.py +534 -472
  379. meerk40t/ruida/emulator.py +1494 -1491
  380. meerk40t/ruida/exceptions.py +4 -4
  381. meerk40t/ruida/gui/gui.py +71 -76
  382. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  383. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  384. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  385. meerk40t/ruida/loader.py +54 -52
  386. meerk40t/ruida/mock_connection.py +57 -109
  387. meerk40t/ruida/plugin.py +124 -87
  388. meerk40t/ruida/rdjob.py +2084 -945
  389. meerk40t/ruida/serial_connection.py +116 -0
  390. meerk40t/ruida/tcp_connection.py +146 -0
  391. meerk40t/ruida/udp_connection.py +73 -0
  392. meerk40t/svgelements.py +9671 -9669
  393. meerk40t/tools/driver_to_path.py +584 -579
  394. meerk40t/tools/geomstr.py +5583 -4680
  395. meerk40t/tools/jhfparser.py +357 -292
  396. meerk40t/tools/kerftest.py +904 -890
  397. meerk40t/tools/livinghinges.py +1168 -1033
  398. meerk40t/tools/pathtools.py +987 -949
  399. meerk40t/tools/pmatrix.py +234 -0
  400. meerk40t/tools/pointfinder.py +942 -942
  401. meerk40t/tools/polybool.py +940 -940
  402. meerk40t/tools/rasterplotter.py +1660 -547
  403. meerk40t/tools/shxparser.py +989 -901
  404. meerk40t/tools/ttfparser.py +726 -446
  405. meerk40t/tools/zinglplotter.py +595 -593
  406. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/LICENSE +21 -21
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/METADATA +150 -139
  408. meerk40t-0.9.7010.dist-info/RECORD +445 -0
  409. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/WHEEL +1 -1
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/top_level.txt +0 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/zip-safe +1 -1
  412. meerk40t/balormk/elementlightjob.py +0 -159
  413. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  414. test/bootstrap.py +0 -63
  415. test/test_cli.py +0 -12
  416. test/test_core_cutcode.py +0 -418
  417. test/test_core_elements.py +0 -144
  418. test/test_core_plotplanner.py +0 -397
  419. test/test_core_viewports.py +0 -312
  420. test/test_drivers_grbl.py +0 -108
  421. test/test_drivers_lihuiyu.py +0 -443
  422. test/test_drivers_newly.py +0 -113
  423. test/test_element_degenerate_points.py +0 -43
  424. test/test_elements_classify.py +0 -97
  425. test/test_elements_penbox.py +0 -22
  426. test/test_file_svg.py +0 -176
  427. test/test_fill.py +0 -155
  428. test/test_geomstr.py +0 -1523
  429. test/test_geomstr_nodes.py +0 -18
  430. test/test_imagetools_actualize.py +0 -306
  431. test/test_imagetools_wizard.py +0 -258
  432. test/test_kernel.py +0 -200
  433. test/test_laser_speeds.py +0 -3303
  434. test/test_length.py +0 -57
  435. test/test_lifecycle.py +0 -66
  436. test/test_operations.py +0 -251
  437. test/test_operations_hatch.py +0 -57
  438. test/test_ruida.py +0 -19
  439. test/test_spooler.py +0 -22
  440. test/test_tools_rasterplotter.py +0 -29
  441. test/test_wobble.py +0 -133
  442. test/test_zingl.py +0 -124
  443. {test → meerk40t/cylinder}/__init__.py +0 -0
  444. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  445. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/entry_points.txt +0 -0
meerk40t/gui/wxmtree.py CHANGED
@@ -1,2089 +1,2410 @@
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, 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
+ parent = node.parent
1434
+ parent_item = parent._item
1435
+ if parent_item is None:
1436
+ # We are appending items in tree before registration.
1437
+ return
1438
+ tree = self.wxtree
1439
+ if pos is None:
1440
+ node._item = tree.AppendItem(parent_item, self.name)
1441
+ else:
1442
+ node._item = tree.InsertItem(parent_item, pos, self.name)
1443
+ tree.SetItemData(node._item, node)
1444
+ self.update_decorations(node, False)
1445
+ wxcolor = self.wxtree.GetForegroundColour()
1446
+ attribute_to_try = "fill" if node.type == "elem text" else "stroke"
1447
+ if hasattr(node, attribute_to_try):
1448
+ wxcolor = self.safe_color(getattr(node, attribute_to_try))
1449
+ elif hasattr(node, "color"):
1450
+ wxcolor = self.safe_color(node.color)
1451
+ else:
1452
+ back_color = self.wxtree.GetBackgroundColour()
1453
+ rgb = back_color.Get()
1454
+ background = Color(rgb[0], rgb[1], rgb[2])
1455
+ if background is not None:
1456
+ c1 = Color("Black")
1457
+ c2 = Color("White")
1458
+ if Color.distance(background, c1) > Color.distance(background, c2):
1459
+ textcolor = c1
1460
+ else:
1461
+ textcolor = c2
1462
+ wxcolor = wx.Colour(swizzlecolor(textcolor))
1463
+ if self.context.root.tree_colored:
1464
+ try:
1465
+ tree.SetItemTextColour(node._item, wxcolor)
1466
+ except (AttributeError, KeyError, TypeError):
1467
+ pass
1468
+ # We might need to update the decorations for all parent objects
1469
+ if not self._freeze:
1470
+ informed = []
1471
+ parent = node._parent
1472
+ while parent is not None and not parent.type.startswith("branch "):
1473
+ informed.append(parent)
1474
+ parent = parent._parent
1475
+ if informed:
1476
+ self.context.signal("element_property_update", informed)
1477
+
1478
+ # self.context.signal("update_group_labels")
1479
+
1480
+ def set_enhancements(self, node):
1481
+ """
1482
+ Node in the tree is drawn special based on nodes current setting.
1483
+ @param node:
1484
+ @return:
1485
+ """
1486
+ tree = self.wxtree
1487
+ node_item = node._item
1488
+ if node_item is None:
1489
+ return
1490
+ if self._freeze or self.context.elements.suppress_updates:
1491
+ return
1492
+ tree.SetItemBackgroundColour(node_item, None)
1493
+ try:
1494
+ if node.highlighted:
1495
+ tree.SetItemBackgroundColour(node_item, wx.LIGHT_GREY)
1496
+ elif node.emphasized:
1497
+ tree.SetItemBackgroundColour(node_item, wx.Colour(0x80A0A0))
1498
+ elif node.targeted:
1499
+ tree.SetItemBackgroundColour(node_item, wx.Colour(0xA080A0))
1500
+ except AttributeError:
1501
+ pass
1502
+
1503
+ def set_color(self, node, color=None):
1504
+ """
1505
+ Node color is set.
1506
+
1507
+ @param node: Not to be colored
1508
+ @param color: Color to be set.
1509
+ @return:
1510
+ """
1511
+ if not self.context.root.tree_colored:
1512
+ return
1513
+ item = node._item
1514
+ if item is None:
1515
+ return
1516
+ if self._freeze or self.context.elements.suppress_updates:
1517
+ return
1518
+ tree = self.wxtree
1519
+ wxcolor = self.safe_color(color)
1520
+ tree.SetItemTextColour(item, wxcolor)
1521
+
1522
+ def create_image_from_node(self, node):
1523
+ image = None
1524
+ mini_icon = self.context.root.mini_icon and not self._too_big
1525
+ c = None
1526
+ self.cache_requests += 1
1527
+ cached_id = -1
1528
+ # Do we have a standard representation?
1529
+ defaultcolor = Color("black")
1530
+ if mini_icon:
1531
+ if node.type == "elem image":
1532
+ try:
1533
+ image = self.renderer.make_thumbnail(
1534
+ node.active_image, width=self.iconsize, height=self.iconsize
1535
+ )
1536
+ except (MemoryError, RuntimeError):
1537
+ image = None
1538
+ else:
1539
+ # Establish colors (and some images)
1540
+ if node.type.startswith("op ") or node.type.startswith("util "):
1541
+ if (
1542
+ hasattr(node, "color")
1543
+ and node.color is not None
1544
+ and node.color.argb is not None
1545
+ ):
1546
+ c = node.color
1547
+ elif node.type == "reference":
1548
+ c, image, cached_id = self.create_image_from_node(node.node)
1549
+ elif node.type.startswith("elem "):
1550
+ if (
1551
+ hasattr(node, "stroke")
1552
+ and node.stroke is not None
1553
+ and node.stroke.argb is not None
1554
+ ):
1555
+ c = node.stroke
1556
+ if node.type.startswith("elem ") and node.type != "elem point":
1557
+ image = self.renderer.make_raster(
1558
+ node,
1559
+ node.paint_bounds,
1560
+ width=self.iconsize,
1561
+ height=self.iconsize,
1562
+ bitmap=True,
1563
+ keep_ratio=True,
1564
+ )
1565
+ else:
1566
+ # Establish at least colors (and an image for a reference)
1567
+ if node.type.startswith("op ") or node.type.startswith("util "):
1568
+ if (
1569
+ hasattr(node, "color")
1570
+ and node.color is not None
1571
+ and node.color.argb is not None
1572
+ ):
1573
+ c = node.color
1574
+ elif node.type == "reference":
1575
+ c, image, cached_id = self.create_image_from_node(node.node)
1576
+ elif node.type == "elem text":
1577
+ if (
1578
+ hasattr(node, "fill")
1579
+ and node.fill is not None
1580
+ and node.fill.argb is not None
1581
+ ):
1582
+ c = node.fill
1583
+ elif node.type.startswith("elem ") or node.type.startswith("effect "):
1584
+ if (
1585
+ hasattr(node, "stroke")
1586
+ and node.stroke is not None
1587
+ and node.stroke.argb is not None
1588
+ ):
1589
+ c = node.stroke
1590
+
1591
+ # Have we already established an image, if no let's use the default
1592
+ if image is None:
1593
+ found = ""
1594
+ tofind = node.type
1595
+ if tofind == "util console":
1596
+ # Let's see whether we find the keyword...
1597
+ for key in self.default_images:
1598
+ if key.startswith("console "):
1599
+ skey = key[8:]
1600
+ if node.command is not None and skey in node.command:
1601
+ found = key
1602
+ break
1603
+
1604
+ if not found and tofind in self.default_images:
1605
+ # print (f"Wasn't found use {tofind}")
1606
+ found = tofind
1607
+
1608
+ if found:
1609
+ for stored_key, stored_color, img_obj, c_id in self.image_cache:
1610
+ if stored_key == found and stored_color == c:
1611
+ image = img_obj
1612
+ cached_id = c_id
1613
+ self.cache_hits += 1
1614
+ # print (f"Restore id {cached_id} for {c} - {found}")
1615
+ break
1616
+ if image is None:
1617
+ # has not been found yet...
1618
+ img_obj = self.default_images[found]
1619
+ image = img_obj.GetBitmap(
1620
+ color=c,
1621
+ resize=(self.iconsize, self.iconsize),
1622
+ noadjustment=True,
1623
+ buffer=1,
1624
+ )
1625
+ cached_id = self.tree_images.Add(bitmap=image)
1626
+ # print(f"Store id {cached_id} for {c} - {found}")
1627
+ self.image_cache.append((found, c, image, cached_id))
1628
+
1629
+ if c is None:
1630
+ c = defaultcolor
1631
+ # print (f"Icon gives color: {c} and cached-id={cached_id}")
1632
+ return c, image, cached_id
1633
+
1634
+ def set_icon(self, node, icon=None, force=False):
1635
+ """
1636
+ Node icon to be created and applied
1637
+
1638
+ @param node: Node to have the icon set.
1639
+ @param icon: overriding icon to be forcibly set, rather than a default.
1640
+ @param force: force the icon setting
1641
+ @return: item_id if newly created / update
1642
+ """
1643
+ root = self
1644
+ drawmode = self.elements.root.draw_mode
1645
+ if drawmode & DRAW_MODE_ICONS != 0:
1646
+ return
1647
+ # if self._freeze or self.context.elements.suppress_updates:
1648
+ # return
1649
+ if node is None:
1650
+ return
1651
+ try:
1652
+ item = node._item
1653
+ except AttributeError:
1654
+ return # Node.item can be none if launched from ExecuteJob where the nodes are not part of the tree.
1655
+ if node._item is None:
1656
+ return
1657
+ tree = root.wxtree
1658
+ if icon is None:
1659
+ if force is None:
1660
+ force = False
1661
+ image_id = tree.GetItemImage(item)
1662
+ if image_id >= self.tree_images.ImageCount:
1663
+ image_id = -1
1664
+ if image_id >= 0 and not force:
1665
+ # Don't do it twice
1666
+ return image_id
1667
+
1668
+ # print ("Default size for iconsize, tree_images", self.iconsize, self.tree_images.GetSize())
1669
+ c, image, cached_id = self.create_image_from_node(node)
1670
+
1671
+ if image is not None:
1672
+ if cached_id >= 0:
1673
+ image_id = cached_id
1674
+ elif image_id < 0:
1675
+ image_id = self.tree_images.Add(bitmap=image)
1676
+ else:
1677
+ self.tree_images.Replace(index=image_id, bitmap=image)
1678
+ tree.SetItemImage(item, image=image_id)
1679
+ # Let's have a look at all references....
1680
+ for subnode in node.references:
1681
+ try:
1682
+ subitem = subnode._item
1683
+ except AttributeError:
1684
+ subitem = None
1685
+ if subitem is None:
1686
+ continue
1687
+ tree.SetItemImage(subitem, image=image_id)
1688
+
1689
+ if c is not None:
1690
+ self.set_color(node, c)
1691
+
1692
+ else:
1693
+ image_id = tree.GetItemImage(item)
1694
+ if image_id >= self.tree_images.ImageCount:
1695
+ image_id = -1
1696
+ # Reset Image Node in List
1697
+ if image_id < 0:
1698
+ image_id = self.tree_images.Add(bitmap=icon)
1699
+ else:
1700
+ self.tree_images.Replace(index=image_id, bitmap=icon)
1701
+
1702
+ tree.SetItemImage(item, image=image_id)
1703
+ return image_id
1704
+
1705
+ def update_op_labels(self):
1706
+ startnode = self.elements.get(type="branch ops")._item
1707
+ if startnode is None:
1708
+ # Branch op never populated the tree, we cannot update sublayer.
1709
+ return
1710
+ child, cookie = self.wxtree.GetFirstChild(startnode)
1711
+ while child.IsOk():
1712
+ node = self.wxtree.GetItemData(child) # Make sure the map is updated...
1713
+ self.update_decorations(node=node, force=True)
1714
+ child, cookie = self.wxtree.GetNextChild(startnode, cookie)
1715
+
1716
+ def update_group_labels(self, src):
1717
+ # print(f"group_labels: {src}")
1718
+ stop_updates = not self._freeze
1719
+ if stop_updates:
1720
+ self.freeze_tree(True)
1721
+ for e in self.context.elements.elems_nodes():
1722
+ if e.type == "group":
1723
+ self.update_decorations(e)
1724
+ for e in self.context.elements.regmarks_nodes():
1725
+ if e.type == "group":
1726
+ self.update_decorations(e)
1727
+ if stop_updates:
1728
+ self.freeze_tree(False)
1729
+
1730
+ def reset_formatter_cache(self):
1731
+ self.formatter_cache.clear()
1732
+
1733
+ def update_decorations(self, node, force=False):
1734
+ """
1735
+ Updates the decorations for a particular node/tree item
1736
+
1737
+ @param node:
1738
+ @param force: force updating decorations
1739
+ @return:
1740
+ """
1741
+
1742
+ def my_create_label(node, text=None):
1743
+ if text is None:
1744
+ try:
1745
+ text = node._formatter
1746
+ except AttributeError:
1747
+ text = "{element_type}:{id}"
1748
+ # Just for the optical impression (who understands what a "Rect: None" means),
1749
+ # let's replace some of the more obvious ones...
1750
+ mymap = node.default_map()
1751
+ # We change power to either ppi or percent
1752
+ if "power" in mymap and "ppi" in mymap and "percent" in mymap:
1753
+ self.context.device.setting(
1754
+ bool, "use_percent_for_power_display", False
1755
+ )
1756
+ if self.context.device.use_percent_for_power_display:
1757
+ mymap["power"] = mymap["percent"]
1758
+ if "speed" in mymap and "speed_mm_min" in mymap:
1759
+ self.context.device.setting(bool, "use_mm_min_for_speed_display", False)
1760
+ if self.context.device.use_mm_min_for_speed_display:
1761
+ text = text.replace("mm/s", "mm/min")
1762
+ mymap["speed"] = mymap["speed_mm_min"]
1763
+ mymap["speed_unit"] = "mm/min"
1764
+ else:
1765
+ mymap["speed_unit"] = "mm/s"
1766
+ for key in mymap:
1767
+ if hasattr(node, key) and key in mymap and mymap[key] == "None":
1768
+ if getattr(node, key) is None:
1769
+ mymap[key] = "-"
1770
+ # There are a couple of translatable entries,
1771
+ # to make sure we don't get an unwanted translation we add
1772
+ # a special pattern to it
1773
+ translatable = (
1774
+ "element_type",
1775
+ "enabled",
1776
+ )
1777
+ pattern = "_TREE_"
1778
+ for key in mymap:
1779
+ if key in translatable:
1780
+ # Original value
1781
+ std = mymap[key]
1782
+ value = _(pattern + std)
1783
+ if not value.startswith(pattern):
1784
+ mymap[key] = value
1785
+ try:
1786
+ res = text.format_map(mymap)
1787
+ except (ValueError, KeyError):
1788
+ res = text
1789
+ return res
1790
+
1791
+ def get_formatter(nodetype):
1792
+ if nodetype not in self.formatter_cache:
1793
+ default = self.context.elements.lookup(f"format/{nodetype}")
1794
+ lbl = nodetype.replace(" ", "_")
1795
+ check_string = f"formatter_{lbl}_active"
1796
+ pattern_string = f"formatter_{lbl}"
1797
+ self.context.device.setting(bool, check_string, False)
1798
+ self.context.device.setting(str, pattern_string, default)
1799
+ bespoke = getattr(self.context.device, check_string, False)
1800
+ pattern = getattr(self.context.device, pattern_string, "")
1801
+ if bespoke and pattern is not None and pattern != "":
1802
+ default = pattern
1803
+ self.formatter_cache[nodetype] = default
1804
+ return self.formatter_cache[nodetype]
1805
+
1806
+ if force is None:
1807
+ force = False
1808
+ if node._item is None:
1809
+ # This node is not registered the tree has desynced.
1810
+ self.rebuild_tree(source="desync", target="all")
1811
+ return
1812
+
1813
+ self.set_icon(node, force=force)
1814
+ if hasattr(node, "node") and node.node is not None:
1815
+ formatter = get_formatter(node.node.type)
1816
+ if node.node.type.startswith("op "):
1817
+ if not self.context.elements.op_show_default:
1818
+ if hasattr(node.node, "speed"):
1819
+ node.node.speed = node.node.speed
1820
+ if hasattr(node.node, "power"):
1821
+ node.node.power = node.node.power
1822
+ if hasattr(node.node, "dwell_time"):
1823
+ node.node.dwell_time = node.node.dwell_time
1824
+
1825
+ checker = f"dangerlevel_{node.type.replace(' ', '_')}"
1826
+ if hasattr(self.context.device, checker):
1827
+ maxspeed_minpower = getattr(self.context.device, checker)
1828
+ if (
1829
+ isinstance(maxspeed_minpower, (tuple, list))
1830
+ and len(maxspeed_minpower) == 8
1831
+ ):
1832
+ # minpower, maxposer, minspeed, maxspeed
1833
+ # print ("Yes: ", checker, maxspeed_minpower)
1834
+ danger = False
1835
+ if hasattr(node.node, "power"):
1836
+ value = node.node.power
1837
+ if maxspeed_minpower[0] and value < maxspeed_minpower[1]:
1838
+ danger = True
1839
+ if maxspeed_minpower[2] and value > maxspeed_minpower[3]:
1840
+ danger = True
1841
+ if hasattr(node.node, "speed"):
1842
+ value = node.node.speed
1843
+ if maxspeed_minpower[4] and value < maxspeed_minpower[5]:
1844
+ danger = True
1845
+ if maxspeed_minpower[6] and value > maxspeed_minpower[7]:
1846
+ danger = True
1847
+ if hasattr(node.node, "dangerous"):
1848
+ node.node.dangerous = danger
1849
+ else:
1850
+ setattr(self.context.device, checker, [False, 0] * 4)
1851
+ print(
1852
+ f"That's strange {checker}: {type(maxspeed_minpower).__name__}"
1853
+ )
1854
+ # node.node.is_dangerous(maxspeed, minpower)
1855
+ # label = "*" + node.node.create_label(formatter)
1856
+ label = f"*{my_create_label(node.node, formatter)}"
1857
+ else:
1858
+ formatter = get_formatter(node.type)
1859
+ if node.type.startswith("op "):
1860
+ # Not too elegant... op nodes should have a property default_speed, default_power
1861
+ if not self.context.elements.op_show_default:
1862
+ if hasattr(node, "speed"):
1863
+ node.speed = node.speed
1864
+ if hasattr(node, "power"):
1865
+ node.power = node.power
1866
+ if hasattr(node, "dwell_time"):
1867
+ node.dwell_time = node.dwell_time
1868
+ checker = f"dangerlevel_{node.type.replace(' ', '_')}"
1869
+ if hasattr(self.context.device, checker):
1870
+ maxspeed_minpower = getattr(self.context.device, checker)
1871
+ if (
1872
+ isinstance(maxspeed_minpower, (tuple, list))
1873
+ and len(maxspeed_minpower) == 8
1874
+ ):
1875
+ # minpower, maxposer, minspeed, maxspeed
1876
+ # print ("Yes: ", checker, maxspeed_minpower)
1877
+ danger = False
1878
+ if hasattr(node, "power"):
1879
+ value = float(node.power)
1880
+ if maxspeed_minpower[0] and value < maxspeed_minpower[1]:
1881
+ danger = True
1882
+ if maxspeed_minpower[2] and value > maxspeed_minpower[3]:
1883
+ danger = True
1884
+ if hasattr(node, "speed"):
1885
+ value = float(node.speed)
1886
+ if maxspeed_minpower[4] and value < maxspeed_minpower[5]:
1887
+ danger = True
1888
+ if maxspeed_minpower[6] and value > maxspeed_minpower[7]:
1889
+ danger = True
1890
+ if hasattr(node, "dangerous"):
1891
+ node.dangerous = danger
1892
+ else:
1893
+ setattr(self.context.device, checker, [False, 0] * 4)
1894
+ print(
1895
+ f"That's strange {checker}: {type(maxspeed_minpower).__name__}"
1896
+ )
1897
+ # label = node.create_label(formatter)
1898
+ label = my_create_label(node, formatter)
1899
+
1900
+ self.wxtree.SetItemText(node._item, label)
1901
+ attribute_to_try = "fill" if node.type == "elem text" else "stroke"
1902
+ wxcolor = None
1903
+ if hasattr(node, attribute_to_try):
1904
+ wxcolor = self.safe_color(getattr(node, attribute_to_try))
1905
+ elif hasattr(node, "color"):
1906
+ wxcolor = self.safe_color(node.color)
1907
+ else:
1908
+ back_color = self.wxtree.GetBackgroundColour()
1909
+ rgb = back_color.Get()
1910
+ background = Color(rgb[0], rgb[1], rgb[2])
1911
+ if background is not None:
1912
+ c1 = Color("Black")
1913
+ c2 = Color("White")
1914
+ if Color.distance(background, c1) > Color.distance(background, c2):
1915
+ textcolor = c1
1916
+ else:
1917
+ textcolor = c2
1918
+ wxcolor = wx.Colour(swizzlecolor(textcolor))
1919
+ if self.context.root.tree_colored:
1920
+ try:
1921
+ self.wxtree.SetItemTextColour(node._item, wxcolor)
1922
+ except (AttributeError, KeyError, TypeError):
1923
+ pass
1924
+
1925
+ state_num = -1
1926
+ if node is self.elements.get(type="branch ops"):
1927
+ unassigned, unburnt = self.elements.have_unburnable_elements()
1928
+ if unassigned or unburnt:
1929
+ state_num = self.iconstates["warning"]
1930
+ else:
1931
+ # Has the node a lock attribute?
1932
+ lockit = node.lock if hasattr(node, "lock") else False
1933
+ if lockit:
1934
+ state_num = self.iconstates["lock"]
1935
+ scene = getattr(self.context.root, "mainscene", None)
1936
+ if scene is not None and node == scene.pane.reference_object:
1937
+ state_num = self.iconstates["refobject"]
1938
+ if state_num < 0:
1939
+ state_num = wx.TREE_ITEMSTATE_NONE
1940
+ if (
1941
+ node.type in op_nodes
1942
+ and hasattr(node, "is_visible")
1943
+ and not node.is_visible
1944
+ ) or (
1945
+ node.type in elem_nodes and hasattr(node, "hidden") and node.hidden
1946
+ ) or (
1947
+ hasattr(node, "node") and hasattr(node.node, "hidden") and node.node.hidden
1948
+ ):
1949
+ state_num = self.iconstates["ghost"]
1950
+ self.wxtree.SetItemState(node._item, state_num)
1951
+
1952
+ def on_drag_begin_handler(self, event):
1953
+ """
1954
+ Drag handler begin for the tree.
1955
+
1956
+ @param event:
1957
+ @return:
1958
+ """
1959
+
1960
+ def typefamily(typename):
1961
+ # Combine similar nodetypes
1962
+ if typename.startswith("op "):
1963
+ result = "op"
1964
+ elif typename.startswith("elem "):
1965
+ result = "elem"
1966
+ elif typename.startswith("group"):
1967
+ result = "elem"
1968
+ elif typename.startswith("file"):
1969
+ result = "elem"
1970
+ else:
1971
+ result = typename
1972
+ return result
1973
+
1974
+ self.dragging_nodes = None
1975
+
1976
+ pt = event.GetPoint()
1977
+ drag_item, _ = self.wxtree.HitTest(pt)
1978
+
1979
+ if drag_item is None or drag_item.ID is None or not drag_item.IsOk():
1980
+ # errmsg = ""
1981
+ # if drag_item is None:
1982
+ # errmsg = "item was none"
1983
+ # elif drag_item.ID is None:
1984
+ # errmsg = "id was none"
1985
+ # elif not drag_item.IsOk():
1986
+ # errmsg = "IsOk was false"
1987
+ # print (f"Drag item was wrong: {errmsg}")
1988
+ event.Skip()
1989
+ return
1990
+
1991
+ self.dragging_nodes = []
1992
+ for item in self.wxtree.GetSelections():
1993
+ node = self.wxtree.GetItemData(item)
1994
+ if node is not None and node.is_draggable():
1995
+ self.dragging_nodes.append(node)
1996
+
1997
+ if not self.dragging_nodes:
1998
+ # print ("Dragging_nodes was empty")
1999
+ event.Skip()
2000
+ return
2001
+
2002
+ t = typefamily(self.dragging_nodes[0].type)
2003
+ for n in self.dragging_nodes:
2004
+ tt = typefamily(n.type)
2005
+ if t != tt:
2006
+ # Different typefamilies
2007
+ # print ("Different typefamilies")
2008
+ event.Skip()
2009
+ return
2010
+ if not n.is_draggable():
2011
+ # print ("Element was not draggable")
2012
+ event.Skip()
2013
+ return
2014
+ event.Allow()
2015
+
2016
+ def on_drag_end_handler(self, event):
2017
+ """
2018
+ Drag end handler for the tree
2019
+
2020
+ @param event:
2021
+ @return:
2022
+ """
2023
+ if self.dragging_nodes is None:
2024
+ event.Skip()
2025
+ return
2026
+ self.wxtree.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
2027
+ drop_item = event.GetItem()
2028
+ if drop_item is None or drop_item.ID is None:
2029
+ event.Skip()
2030
+ return
2031
+ drop_node = self.wxtree.GetItemData(drop_item)
2032
+ if drop_node is None:
2033
+ event.Skip()
2034
+ return
2035
+ # Is the node expanded? If yes regular dnd applies, if not we will add the node to the end...
2036
+ closed_leaf = (self.wxtree.ItemHasChildren(drop_item) and not self.wxtree.IsExpanded(drop_item))
2037
+ # We extend the logic by calling the appropriate elems routine
2038
+ skip = not self.elements.drag_and_drop(self.dragging_nodes, drop_node, flag=closed_leaf)
2039
+ if skip:
2040
+ event.Skip()
2041
+ self.dragging_nodes = None
2042
+ return
2043
+ event.Allow()
2044
+ # Make sure that the drop node is visible
2045
+ self.wxtree.Expand(drop_item)
2046
+ self.wxtree.EnsureVisible(drop_item)
2047
+ self.refresh_tree(source="drag end")
2048
+ # Do the dragging_nodes contain an operation?
2049
+ # Let's give an indication of that, as this may
2050
+ # have led to the creation of a new reference
2051
+ # node. For whatever reason this is not recognised
2052
+ # otherwise...
2053
+ if not self.dragging_nodes:
2054
+ # Dragging nodes were cleared (we must have rebuilt the entire tree)
2055
+ return
2056
+ for node in self.dragging_nodes:
2057
+ if node.type.startswith("op"):
2058
+ self.context.signal("tree_changed")
2059
+ break
2060
+ # self.rebuild_tree()
2061
+ self.reset_dragging()
2062
+
2063
+ def on_mouse_over(self, event):
2064
+ # establish the item we are over...
2065
+ event.Skip()
2066
+ ttip = ""
2067
+ pt = event.GetPosition()
2068
+ item, flags = self.wxtree.HitTest(pt)
2069
+ if self._last_hover_item is item:
2070
+ return
2071
+ if item:
2072
+ state = self.wxtree.GetItemState(item)
2073
+ node = self.wxtree.GetItemData(item)
2074
+ if node is not None:
2075
+ # Lets check the dragging status
2076
+ if self.dragging_nodes:
2077
+ if hasattr(node, "would_accept_drop"):
2078
+ would_drop = node.would_accept_drop(self.dragging_nodes)
2079
+ else:
2080
+ would_drop = False
2081
+ if would_drop:
2082
+ self.wxtree.SetCursor(wx.Cursor(wx.CURSOR_HAND))
2083
+ else:
2084
+ self.wxtree.SetCursor(wx.Cursor(wx.CURSOR_NO_ENTRY))
2085
+ else:
2086
+ self.wxtree.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
2087
+
2088
+ if hasattr(node, "_tooltip"):
2089
+ # That has precedence and will be displayed in all cases
2090
+ ttip = node._tooltip
2091
+ elif not self.context.disable_tree_tool_tips:
2092
+ if node.type == "blob":
2093
+ ttip = _(
2094
+ "This is binary data imported or generated\n"
2095
+ + "that will be sent directly to the laser.\n"
2096
+ + "Double-click to view."
2097
+ )
2098
+ elif node.type == "op cut":
2099
+ ttip = _(
2100
+ "This will engrave/cut the elements contained,\n"
2101
+ + "following the vector-paths of the data.\n"
2102
+ + "(Usually done last)"
2103
+ )
2104
+ elif node.type == "op engrave":
2105
+ ttip = _(
2106
+ "This will engrave the elements contained,\n"
2107
+ + "following the vector-paths of the data."
2108
+ )
2109
+ elif node.type == "op image":
2110
+ ttip = _(
2111
+ "This engraves already created images pixel by pixel,\n"
2112
+ + "applying the settings to the individual pictures"
2113
+ )
2114
+ elif node.type == "op raster":
2115
+ ttip = _(
2116
+ "This will render all contained elements\n"
2117
+ + "into an intermediary image which then will be\n"
2118
+ + "engraved pixel by pixel."
2119
+ )
2120
+ elif node.type == "op dots":
2121
+ ttip = _(
2122
+ "This will engrave a single point for a given period of time"
2123
+ )
2124
+ elif node.type == "util console":
2125
+ ttip = _(
2126
+ "This allows to execute an arbitrary command during the engrave process"
2127
+ )
2128
+ elif node.type == "util goto":
2129
+ ttip = _(
2130
+ "This will send the laser back to its logical start position"
2131
+ )
2132
+ elif node.type == "util home":
2133
+ ttip = _(
2134
+ "This will send the laser back to its physical start position"
2135
+ )
2136
+ elif node.type == "util input":
2137
+ ttip = _(
2138
+ "This will wait for active IO bits on the laser (mainly fibre laser for now)"
2139
+ )
2140
+ elif node.type == "util output":
2141
+ ttip = _(
2142
+ "This will set some IO bits on the laser (mainly fibre laser for now)"
2143
+ )
2144
+ elif node.type == "util wait":
2145
+ ttip = _(
2146
+ "This will pause the engrave process for a given period"
2147
+ )
2148
+ elif node.type == "branch reg":
2149
+ ttip = _(
2150
+ "The elements under this section will not be engraved,\n"
2151
+ + "they can serve as a template or registration marks."
2152
+ )
2153
+ elif node.type == "elem line":
2154
+ bb = node.bounds
2155
+ if bb is not None:
2156
+ ww = Length(amount=bb[2] - bb[0], digits=1)
2157
+ hh = Length(amount=bb[3] - bb[1], digits=1)
2158
+ ll = Length(amount=node.length(), digits=1)
2159
+ ttip = f"{ww.length_mm} x {hh.length_mm}, L={ll.length_mm}"
2160
+ elif node.type == "elem rect":
2161
+ bb = node.bounds
2162
+ if bb is not None:
2163
+ ww = Length(amount=bb[2] - bb[0], digits=1)
2164
+ hh = Length(amount=bb[3] - bb[1], digits=1)
2165
+ ll = Length(amount=node.length(), digits=1)
2166
+ ttip = f"{ww.length_mm} x {hh.length_mm}, L={ll.length_mm}"
2167
+ elif node.type == "elem polyline":
2168
+ bb = node.bounds
2169
+ if bb is not None:
2170
+ ww = Length(amount=bb[2] - bb[0], digits=1)
2171
+ hh = Length(amount=bb[3] - bb[1], digits=1)
2172
+ ll = Length(amount=node.length(), digits=1)
2173
+ ttip = f"{ww.length_mm} x {hh.length_mm}, L={ll.length_mm}"
2174
+ ttip += f"\n{len(node)} pts"
2175
+ elif node.type == "elem ellipse":
2176
+ bb = node.bounds
2177
+ if bb is not None:
2178
+ ww = Length(amount=bb[2] - bb[0], digits=1)
2179
+ hh = Length(amount=bb[3] - bb[1], digits=1)
2180
+ ttip = f"{ww.length_mm} x {hh.length_mm}"
2181
+ elif node.type == "elem path":
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
+ ttip += f"\n{len(node.path)} segments"
2188
+ elif node.type == "elem text":
2189
+ bb = node.bounds
2190
+ if bb is not None:
2191
+ ww = Length(amount=bb[2] - bb[0], digits=1)
2192
+ hh = Length(amount=bb[3] - bb[1], digits=1)
2193
+ ttip = f"{ww.length_mm} x {hh.length_mm}"
2194
+ # ttip += f"\n{node.font}"
2195
+ elif node.type == "place current":
2196
+ ttip = _(
2197
+ "This is a placeholder for the 'place current' operation"
2198
+ )
2199
+ elif node.type == "place point":
2200
+ ttip = _(
2201
+ "This will define an origin from where all the elements in this scene\n"
2202
+ + "will be plotted. You can have multiple such job start points"
2203
+ )
2204
+ elif node.type == "effect hatch":
2205
+ ttip = _(
2206
+ "This is a special node that will consume any other closed path\n"
2207
+ + "you drag onto it and will fill the shape with a line pattern.\n"
2208
+ + "To activate / deactivate this effect please use the context menu."
2209
+ )
2210
+ if node.type in op_nodes:
2211
+ if hasattr(node, "label") and node.label is not None:
2212
+ ttip += f"\n{node.id + ': ' if node.id is not None else ''}{node.display_label()}"
2213
+ ps_info = ""
2214
+ if hasattr(node, "power") and node.power is not None:
2215
+ try:
2216
+ p = float(node.power)
2217
+ if self.context.device.use_percent_for_power_display:
2218
+ ps_info += f"{', ' if ps_info else ''}{p / 10:.1f}%"
2219
+ else:
2220
+ ps_info += f"{', ' if ps_info else ''}{p:.0f}ppi"
2221
+ except ValueError:
2222
+ pass
2223
+
2224
+ if hasattr(node, "speed") and node.speed is not None:
2225
+ try:
2226
+ p = float(node.speed)
2227
+ if self.context.device.use_mm_min_for_speed_display:
2228
+ ps_info += (
2229
+ f"{', ' if ps_info else ''}{p * 60.0:.0f}mm/min"
2230
+ )
2231
+ else:
2232
+ ps_info += f"{', ' if ps_info else ''}{p:.0f}mm/s"
2233
+ except ValueError:
2234
+ pass
2235
+
2236
+ if (
2237
+ hasattr(self.context.device, "default_frequency")
2238
+ and hasattr(node, "frequency")
2239
+ and node.frequency is not None
2240
+ ):
2241
+ try:
2242
+ p = float(node.frequency)
2243
+ ps_info += f"{', ' if ps_info else ''}{p:.0f}kHz"
2244
+ except ValueError:
2245
+ pass
2246
+
2247
+ if ps_info:
2248
+ ttip += f"\n{ps_info}"
2249
+ if state == self.iconstates["ghost"]:
2250
+ ttip = _("HIDDEN: ") + ttip
2251
+ self._last_hover_item = item
2252
+ if ttip != self.wxtree.GetToolTipText():
2253
+ self.wxtree.SetToolTip(ttip)
2254
+
2255
+ def on_item_right_click(self, event):
2256
+ """
2257
+ Right click of element in tree.
2258
+
2259
+ @param event:
2260
+ @return:
2261
+ """
2262
+ item = event.GetItem()
2263
+ if item is None:
2264
+ return
2265
+ node = self.wxtree.GetItemData(item)
2266
+
2267
+ create_menu(self.gui, node, self.elements)
2268
+
2269
+ def on_item_activated(self, event):
2270
+ """
2271
+ Tree item is double-clicked. Launches PropertyWindow associated with that object.
2272
+
2273
+ @param event:
2274
+ @return:
2275
+ """
2276
+ item = event.GetItem()
2277
+ node = self.wxtree.GetItemData(item)
2278
+ activate = self.elements.lookup("function/open_property_window_for_node")
2279
+ if activate is not None:
2280
+ activate(node)
2281
+
2282
+ def activate_selected_node(self, *args):
2283
+ """
2284
+ Call activated on the first emphasized node.
2285
+
2286
+ @param args:
2287
+ @return:
2288
+ """
2289
+ first_element = self.elements.first_element(emphasized=True)
2290
+ if first_element is None:
2291
+ first_element = self.elements.first_element(selected=True)
2292
+ if first_element is None:
2293
+ return
2294
+ if hasattr(first_element, "node"):
2295
+ # Reference
2296
+ first_element = first_element.node
2297
+ activate = self.elements.lookup("function/open_property_window_for_node")
2298
+ if activate is not None:
2299
+ activate(first_element)
2300
+
2301
+ def on_collapse(self, event):
2302
+ if self.do_not_select:
2303
+ # Do not select is part of a linux correction where moving nodes around in a drag and drop fashion could
2304
+ # cause them to appear to drop invalid nodes.
2305
+ return
2306
+ item = event.GetItem()
2307
+ if not item:
2308
+ return
2309
+ node = self.wxtree.GetItemData(item)
2310
+ node.expanded = False
2311
+
2312
+ def on_expand(self, event):
2313
+ if self.do_not_select:
2314
+ # Do not select is part of a linux correction where moving nodes around in a drag and drop fashion could
2315
+ # cause them to appear to drop invalid nodes.
2316
+ return
2317
+ item = event.GetItem()
2318
+ if not item:
2319
+ return
2320
+ node = self.wxtree.GetItemData(item)
2321
+ node.expanded = True
2322
+
2323
+ def on_state_icon(self, event):
2324
+ if self.do_not_select:
2325
+ # Do not select is part of a linux correction where moving nodes around in a drag and drop fashion could
2326
+ # cause them to appear to drop invalid nodes.
2327
+ return
2328
+ item = event.GetItem()
2329
+ if not item:
2330
+ return
2331
+ node = self.wxtree.GetItemData(item)
2332
+ if hasattr(node, "hidden"):
2333
+ node.hidden = False
2334
+ self.context.elements.set_emphasis([node])
2335
+ # self.context.signal("refresh_scene", "Scene")
2336
+ self.update_decorations(node)
2337
+
2338
+ def on_item_selection_changed(self, event):
2339
+ """
2340
+ Tree menu item is changed. Modify the selection.
2341
+
2342
+ @param event:
2343
+ @return:
2344
+ """
2345
+ if self.do_not_select:
2346
+ # Do not select is part of a linux correction where moving nodes around in a drag and drop fashion could
2347
+ # cause them to appear to drop invalid nodes.
2348
+ return
2349
+ # 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()}")
2350
+ its_me = self.wxtree.FindFocus() is self.wxtree
2351
+ # Just out of curiosity, is there no image set? Then just do it again.
2352
+ item = event.GetItem()
2353
+ if item:
2354
+ image_id = self.wxtree.GetItemImage(item)
2355
+ if image_id >= self.tree_images.ImageCount:
2356
+ image_id = -1
2357
+ if image_id < 0:
2358
+ node = self.wxtree.GetItemData(item)
2359
+ if node is not None:
2360
+ self.set_icon(node, force=True)
2361
+
2362
+ selected = [
2363
+ self.wxtree.GetItemData(item) for item in self.wxtree.GetSelections()
2364
+ ]
2365
+
2366
+ emphasized = list(selected)
2367
+ for i in range(len(emphasized)):
2368
+ node = emphasized[i]
2369
+ if node is None or node.type is None:
2370
+ # Rare issue seen building the tree during materials test and the node type didn't exist.
2371
+ return
2372
+ if node.type == "reference":
2373
+ emphasized[i] = node.node
2374
+ elif node.type.startswith("op"):
2375
+ for n in node.flat(types=("reference",), cascade=False):
2376
+ try:
2377
+ emphasized.append(n.node)
2378
+ except Exception:
2379
+ pass
2380
+ self.elements.set_emphasis(emphasized)
2381
+ self.elements.set_selected(selected)
2382
+ # self.refresh_tree(source="on_item_selection")
2383
+ event.Allow()
2384
+
2385
+ # We seem to lose focus, so lets reclaim it
2386
+ if its_me:
2387
+ def restore_focus():
2388
+ self.wxtree.SetFocus()
2389
+ wx.CallAfter(restore_focus)
2390
+
2391
+ def select_in_tree_by_emphasis(self, origin, *args):
2392
+ """
2393
+ Selected the actual `wx.tree` control those items which are currently emphasized.
2394
+
2395
+ @return:
2396
+ """
2397
+ self.do_not_select = True
2398
+ self.wxtree.UnselectAll()
2399
+ require_rebuild = False
2400
+ for e in self.elements.elems_nodes(emphasized=True):
2401
+ if e._item:
2402
+ self.wxtree.SelectItem(e._item, True)
2403
+ else:
2404
+ # That should not happen, apparently we have a not fully built tree
2405
+ require_rebuild = True
2406
+ break
2407
+ if require_rebuild:
2408
+ self.context.signal("rebuild_tree", "all")
2409
+
2410
+ self.do_not_select = False