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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1194 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1858 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4595 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4315 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +934 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/testcases.py +105 -0
  66. meerk40t/core/elements/trace.py +651 -563
  67. meerk40t/core/elements/tree_commands.py +415 -409
  68. meerk40t/core/elements/undo_redo.py +116 -58
  69. meerk40t/core/elements/wordlist.py +319 -200
  70. meerk40t/core/exceptions.py +9 -9
  71. meerk40t/core/laserjob.py +220 -220
  72. meerk40t/core/logging.py +63 -63
  73. meerk40t/core/node/blobnode.py +83 -86
  74. meerk40t/core/node/bootstrap.py +105 -103
  75. meerk40t/core/node/branch_elems.py +40 -31
  76. meerk40t/core/node/branch_ops.py +45 -38
  77. meerk40t/core/node/branch_regmark.py +48 -41
  78. meerk40t/core/node/cutnode.py +29 -32
  79. meerk40t/core/node/effect_hatch.py +375 -257
  80. meerk40t/core/node/effect_warp.py +398 -0
  81. meerk40t/core/node/effect_wobble.py +441 -309
  82. meerk40t/core/node/elem_ellipse.py +404 -309
  83. meerk40t/core/node/elem_image.py +1082 -801
  84. meerk40t/core/node/elem_line.py +358 -292
  85. meerk40t/core/node/elem_path.py +259 -201
  86. meerk40t/core/node/elem_point.py +129 -102
  87. meerk40t/core/node/elem_polyline.py +310 -246
  88. meerk40t/core/node/elem_rect.py +376 -286
  89. meerk40t/core/node/elem_text.py +445 -418
  90. meerk40t/core/node/filenode.py +59 -40
  91. meerk40t/core/node/groupnode.py +138 -74
  92. meerk40t/core/node/image_processed.py +777 -766
  93. meerk40t/core/node/image_raster.py +156 -113
  94. meerk40t/core/node/layernode.py +31 -31
  95. meerk40t/core/node/mixins.py +135 -107
  96. meerk40t/core/node/node.py +1427 -1304
  97. meerk40t/core/node/nutils.py +117 -114
  98. meerk40t/core/node/op_cut.py +463 -335
  99. meerk40t/core/node/op_dots.py +296 -251
  100. meerk40t/core/node/op_engrave.py +414 -311
  101. meerk40t/core/node/op_image.py +755 -369
  102. meerk40t/core/node/op_raster.py +787 -522
  103. meerk40t/core/node/place_current.py +37 -40
  104. meerk40t/core/node/place_point.py +329 -126
  105. meerk40t/core/node/refnode.py +58 -47
  106. meerk40t/core/node/rootnode.py +225 -219
  107. meerk40t/core/node/util_console.py +48 -48
  108. meerk40t/core/node/util_goto.py +84 -65
  109. meerk40t/core/node/util_home.py +61 -61
  110. meerk40t/core/node/util_input.py +102 -102
  111. meerk40t/core/node/util_output.py +102 -102
  112. meerk40t/core/node/util_wait.py +65 -65
  113. meerk40t/core/parameters.py +709 -707
  114. meerk40t/core/planner.py +875 -785
  115. meerk40t/core/plotplanner.py +656 -652
  116. meerk40t/core/space.py +120 -113
  117. meerk40t/core/spoolers.py +706 -705
  118. meerk40t/core/svg_io.py +1836 -1549
  119. meerk40t/core/treeop.py +534 -445
  120. meerk40t/core/undos.py +278 -124
  121. meerk40t/core/units.py +784 -680
  122. meerk40t/core/view.py +393 -322
  123. meerk40t/core/webhelp.py +62 -62
  124. meerk40t/core/wordlist.py +513 -504
  125. meerk40t/cylinder/cylinder.py +247 -0
  126. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  127. meerk40t/cylinder/gui/gui.py +24 -0
  128. meerk40t/device/__init__.py +1 -1
  129. meerk40t/device/basedevice.py +322 -123
  130. meerk40t/device/devicechoices.py +50 -0
  131. meerk40t/device/dummydevice.py +163 -128
  132. meerk40t/device/gui/defaultactions.py +618 -602
  133. meerk40t/device/gui/effectspanel.py +114 -0
  134. meerk40t/device/gui/formatterpanel.py +253 -290
  135. meerk40t/device/gui/warningpanel.py +337 -260
  136. meerk40t/device/mixins.py +13 -13
  137. meerk40t/dxf/__init__.py +1 -1
  138. meerk40t/dxf/dxf_io.py +766 -554
  139. meerk40t/dxf/plugin.py +47 -35
  140. meerk40t/external_plugins.py +79 -79
  141. meerk40t/external_plugins_build.py +28 -28
  142. meerk40t/extra/cag.py +112 -116
  143. meerk40t/extra/coolant.py +403 -0
  144. meerk40t/extra/encode_detect.py +204 -0
  145. meerk40t/extra/ezd.py +1165 -1165
  146. meerk40t/extra/hershey.py +834 -340
  147. meerk40t/extra/imageactions.py +322 -316
  148. meerk40t/extra/inkscape.py +628 -622
  149. meerk40t/extra/lbrn.py +424 -424
  150. meerk40t/extra/outerworld.py +283 -0
  151. meerk40t/extra/param_functions.py +1542 -1556
  152. meerk40t/extra/potrace.py +257 -253
  153. meerk40t/extra/serial_exchange.py +118 -0
  154. meerk40t/extra/updater.py +602 -453
  155. meerk40t/extra/vectrace.py +147 -146
  156. meerk40t/extra/winsleep.py +83 -83
  157. meerk40t/extra/xcs_reader.py +597 -0
  158. meerk40t/fill/fills.py +781 -335
  159. meerk40t/fill/patternfill.py +1061 -1061
  160. meerk40t/fill/patterns.py +614 -567
  161. meerk40t/grbl/control.py +87 -87
  162. meerk40t/grbl/controller.py +990 -903
  163. meerk40t/grbl/device.py +1084 -768
  164. meerk40t/grbl/driver.py +989 -771
  165. meerk40t/grbl/emulator.py +532 -497
  166. meerk40t/grbl/gcodejob.py +783 -767
  167. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  168. meerk40t/grbl/gui/grblcontroller.py +485 -271
  169. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  170. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  171. meerk40t/grbl/gui/gui.py +147 -116
  172. meerk40t/grbl/interpreter.py +44 -44
  173. meerk40t/grbl/loader.py +22 -22
  174. meerk40t/grbl/mock_connection.py +56 -56
  175. meerk40t/grbl/plugin.py +294 -264
  176. meerk40t/grbl/serial_connection.py +93 -88
  177. meerk40t/grbl/tcp_connection.py +81 -79
  178. meerk40t/grbl/ws_connection.py +112 -0
  179. meerk40t/gui/__init__.py +1 -1
  180. meerk40t/gui/about.py +2042 -296
  181. meerk40t/gui/alignment.py +1644 -1608
  182. meerk40t/gui/autoexec.py +199 -0
  183. meerk40t/gui/basicops.py +791 -670
  184. meerk40t/gui/bufferview.py +77 -71
  185. meerk40t/gui/busy.py +232 -133
  186. meerk40t/gui/choicepropertypanel.py +1662 -1469
  187. meerk40t/gui/consolepanel.py +706 -542
  188. meerk40t/gui/devicepanel.py +687 -581
  189. meerk40t/gui/dialogoptions.py +110 -107
  190. meerk40t/gui/executejob.py +316 -306
  191. meerk40t/gui/fonts.py +90 -90
  192. meerk40t/gui/functionwrapper.py +252 -0
  193. meerk40t/gui/gui_mixins.py +729 -0
  194. meerk40t/gui/guicolors.py +205 -182
  195. meerk40t/gui/help_assets/help_assets.py +218 -201
  196. meerk40t/gui/helper.py +154 -0
  197. meerk40t/gui/hersheymanager.py +1440 -846
  198. meerk40t/gui/icons.py +3422 -2747
  199. meerk40t/gui/imagesplitter.py +555 -508
  200. meerk40t/gui/keymap.py +354 -344
  201. meerk40t/gui/laserpanel.py +897 -806
  202. meerk40t/gui/laserrender.py +1470 -1232
  203. meerk40t/gui/lasertoolpanel.py +805 -793
  204. meerk40t/gui/magnetoptions.py +436 -0
  205. meerk40t/gui/materialmanager.py +2944 -0
  206. meerk40t/gui/materialtest.py +1722 -1694
  207. meerk40t/gui/mkdebug.py +646 -359
  208. meerk40t/gui/mwindow.py +163 -140
  209. meerk40t/gui/navigationpanels.py +2605 -2467
  210. meerk40t/gui/notes.py +143 -142
  211. meerk40t/gui/opassignment.py +414 -410
  212. meerk40t/gui/operation_info.py +310 -299
  213. meerk40t/gui/plugin.py +500 -328
  214. meerk40t/gui/position.py +714 -669
  215. meerk40t/gui/preferences.py +901 -650
  216. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  217. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  218. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  219. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  220. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  221. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  222. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  223. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  224. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  225. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  226. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  227. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  228. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  229. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  230. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  231. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  232. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  233. meerk40t/gui/propertypanels/textproperty.py +770 -755
  234. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  235. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  236. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  237. meerk40t/gui/ribbon.py +2471 -2210
  238. meerk40t/gui/scene/scene.py +1100 -1051
  239. meerk40t/gui/scene/sceneconst.py +22 -22
  240. meerk40t/gui/scene/scenepanel.py +439 -349
  241. meerk40t/gui/scene/scenespacewidget.py +365 -365
  242. meerk40t/gui/scene/widget.py +518 -505
  243. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  244. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  245. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  246. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  247. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  248. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  249. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  250. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  251. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  252. meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
  253. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  254. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  255. meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
  256. meerk40t/gui/simpleui.py +362 -333
  257. meerk40t/gui/simulation.py +2451 -2094
  258. meerk40t/gui/snapoptions.py +208 -203
  259. meerk40t/gui/spoolerpanel.py +1227 -1180
  260. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  261. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  262. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  263. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  264. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  265. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  266. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  267. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  268. meerk40t/gui/themes.py +200 -78
  269. meerk40t/gui/tips.py +590 -0
  270. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  271. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  272. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  273. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  274. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  275. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  276. meerk40t/gui/toolwidgets/toolline.py +39 -144
  277. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  278. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  279. meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
  280. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  281. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  282. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  283. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  284. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  285. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  286. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  287. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  288. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  289. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  290. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  291. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  292. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  293. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  294. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  295. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  296. meerk40t/gui/usbconnect.py +98 -91
  297. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  298. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  299. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  300. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  301. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  302. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  303. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  304. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  305. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  306. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  307. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  308. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  309. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  310. meerk40t/gui/wordlisteditor.py +985 -931
  311. meerk40t/gui/wxmeerk40t.py +1447 -1169
  312. meerk40t/gui/wxmmain.py +5644 -4112
  313. meerk40t/gui/wxmribbon.py +1591 -1076
  314. meerk40t/gui/wxmscene.py +1631 -1453
  315. meerk40t/gui/wxmtree.py +2416 -2089
  316. meerk40t/gui/wxutils.py +1769 -1099
  317. meerk40t/gui/zmatrix.py +102 -102
  318. meerk40t/image/__init__.py +1 -1
  319. meerk40t/image/dither.py +429 -0
  320. meerk40t/image/imagetools.py +2793 -2269
  321. meerk40t/internal_plugins.py +150 -130
  322. meerk40t/kernel/__init__.py +63 -12
  323. meerk40t/kernel/channel.py +259 -212
  324. meerk40t/kernel/context.py +538 -538
  325. meerk40t/kernel/exceptions.py +41 -41
  326. meerk40t/kernel/functions.py +463 -414
  327. meerk40t/kernel/jobs.py +100 -100
  328. meerk40t/kernel/kernel.py +3828 -3571
  329. meerk40t/kernel/lifecycles.py +71 -71
  330. meerk40t/kernel/module.py +49 -49
  331. meerk40t/kernel/service.py +147 -147
  332. meerk40t/kernel/settings.py +383 -343
  333. meerk40t/lihuiyu/controller.py +883 -876
  334. meerk40t/lihuiyu/device.py +1181 -1069
  335. meerk40t/lihuiyu/driver.py +1466 -1372
  336. meerk40t/lihuiyu/gui/gui.py +127 -106
  337. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  338. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  339. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  340. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  341. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  342. meerk40t/lihuiyu/interpreter.py +53 -53
  343. meerk40t/lihuiyu/laserspeed.py +450 -450
  344. meerk40t/lihuiyu/loader.py +90 -90
  345. meerk40t/lihuiyu/parser.py +404 -404
  346. meerk40t/lihuiyu/plugin.py +101 -102
  347. meerk40t/lihuiyu/tcp_connection.py +111 -109
  348. meerk40t/main.py +231 -165
  349. meerk40t/moshi/builder.py +788 -781
  350. meerk40t/moshi/controller.py +505 -499
  351. meerk40t/moshi/device.py +495 -442
  352. meerk40t/moshi/driver.py +862 -696
  353. meerk40t/moshi/gui/gui.py +78 -76
  354. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  355. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  356. meerk40t/moshi/plugin.py +43 -43
  357. meerk40t/network/console_server.py +140 -57
  358. meerk40t/network/kernelserver.py +10 -9
  359. meerk40t/network/tcp_server.py +142 -140
  360. meerk40t/network/udp_server.py +103 -77
  361. meerk40t/network/web_server.py +404 -0
  362. meerk40t/newly/controller.py +1158 -1144
  363. meerk40t/newly/device.py +874 -732
  364. meerk40t/newly/driver.py +540 -412
  365. meerk40t/newly/gui/gui.py +219 -188
  366. meerk40t/newly/gui/newlyconfig.py +116 -101
  367. meerk40t/newly/gui/newlycontroller.py +193 -186
  368. meerk40t/newly/gui/operationproperties.py +51 -51
  369. meerk40t/newly/mock_connection.py +82 -82
  370. meerk40t/newly/newly_params.py +56 -56
  371. meerk40t/newly/plugin.py +1214 -1246
  372. meerk40t/newly/usb_connection.py +322 -322
  373. meerk40t/rotary/gui/gui.py +52 -46
  374. meerk40t/rotary/gui/rotarysettings.py +240 -232
  375. meerk40t/rotary/rotary.py +202 -98
  376. meerk40t/ruida/control.py +291 -91
  377. meerk40t/ruida/controller.py +138 -1088
  378. meerk40t/ruida/device.py +676 -231
  379. meerk40t/ruida/driver.py +534 -472
  380. meerk40t/ruida/emulator.py +1494 -1491
  381. meerk40t/ruida/exceptions.py +4 -4
  382. meerk40t/ruida/gui/gui.py +71 -76
  383. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  384. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  385. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  386. meerk40t/ruida/loader.py +54 -52
  387. meerk40t/ruida/mock_connection.py +57 -109
  388. meerk40t/ruida/plugin.py +124 -87
  389. meerk40t/ruida/rdjob.py +2084 -945
  390. meerk40t/ruida/serial_connection.py +116 -0
  391. meerk40t/ruida/tcp_connection.py +146 -0
  392. meerk40t/ruida/udp_connection.py +73 -0
  393. meerk40t/svgelements.py +9671 -9669
  394. meerk40t/tools/driver_to_path.py +584 -579
  395. meerk40t/tools/geomstr.py +5583 -4680
  396. meerk40t/tools/jhfparser.py +357 -292
  397. meerk40t/tools/kerftest.py +904 -890
  398. meerk40t/tools/livinghinges.py +1168 -1033
  399. meerk40t/tools/pathtools.py +987 -949
  400. meerk40t/tools/pmatrix.py +234 -0
  401. meerk40t/tools/pointfinder.py +942 -942
  402. meerk40t/tools/polybool.py +941 -940
  403. meerk40t/tools/rasterplotter.py +1660 -547
  404. meerk40t/tools/shxparser.py +1047 -901
  405. meerk40t/tools/ttfparser.py +726 -446
  406. meerk40t/tools/zinglplotter.py +595 -593
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
  408. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
  409. meerk40t-0.9.7020.dist-info/RECORD +446 -0
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
  412. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
  413. meerk40t/balormk/elementlightjob.py +0 -159
  414. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  415. test/bootstrap.py +0 -63
  416. test/test_cli.py +0 -12
  417. test/test_core_cutcode.py +0 -418
  418. test/test_core_elements.py +0 -144
  419. test/test_core_plotplanner.py +0 -397
  420. test/test_core_viewports.py +0 -312
  421. test/test_drivers_grbl.py +0 -108
  422. test/test_drivers_lihuiyu.py +0 -443
  423. test/test_drivers_newly.py +0 -113
  424. test/test_element_degenerate_points.py +0 -43
  425. test/test_elements_classify.py +0 -97
  426. test/test_elements_penbox.py +0 -22
  427. test/test_file_svg.py +0 -176
  428. test/test_fill.py +0 -155
  429. test/test_geomstr.py +0 -1523
  430. test/test_geomstr_nodes.py +0 -18
  431. test/test_imagetools_actualize.py +0 -306
  432. test/test_imagetools_wizard.py +0 -258
  433. test/test_kernel.py +0 -200
  434. test/test_laser_speeds.py +0 -3303
  435. test/test_length.py +0 -57
  436. test/test_lifecycle.py +0 -66
  437. test/test_operations.py +0 -251
  438. test/test_operations_hatch.py +0 -57
  439. test/test_ruida.py +0 -19
  440. test/test_spooler.py +0 -22
  441. test/test_tools_rasterplotter.py +0 -29
  442. test/test_wobble.py +0 -133
  443. test/test_zingl.py +0 -124
  444. {test → meerk40t/cylinder}/__init__.py +0 -0
  445. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  446. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
@@ -1,1169 +1,1447 @@
1
- import os
2
- import platform
3
- import sys
4
- import traceback
5
- from datetime import datetime
6
-
7
- import wx
8
- from wx import aui
9
-
10
- # try:
11
- # # According to https://docs.wxpython.org/wx.richtext.1moduleindex.html
12
- # # richtext needs to be imported before wx.App i.e. wxMeerK40t is instantiated
13
- # # so, we are doing it here even though we do not refer to it in this file
14
- # # richtext is used for the Console panel.
15
- # from wx import richtext
16
- # except ImportError:
17
- # pass
18
- from meerk40t.gui.consolepanel import Console
19
- from meerk40t.gui.navigationpanels import Navigation
20
- from meerk40t.gui.spoolerpanel import JobSpooler
21
- from meerk40t.gui.wxmscene import SceneWindow
22
- from meerk40t.kernel import CommandSyntaxError, ConsoleFunction, Module, get_safe_path
23
- from meerk40t.kernel.kernel import Job
24
-
25
- from ..main import APPLICATION_NAME, APPLICATION_VERSION
26
- from ..tools.kerftest import KerfTool
27
- from ..tools.livinghinges import LivingHingeTool
28
- from .about import About
29
- from .alignment import Alignment
30
- from .bufferview import BufferView
31
- from .devicepanel import DeviceManager
32
- from .executejob import ExecuteJob
33
- from .hersheymanager import (
34
- HersheyFontManager,
35
- HersheyFontSelector,
36
- register_hershey_stuff,
37
- )
38
- from .icons import (
39
- DARKMODE,
40
- icons8_emergency_stop_button,
41
- icons8_gas_industry,
42
- icons8_home_filled,
43
- icons8_pause,
44
- )
45
- from .imagesplitter import RenderSplit
46
- from .keymap import Keymap
47
- from .lasertoolpanel import LaserTool
48
- from .materialtest import TemplateTool
49
- from .notes import Notes
50
- from .operation_info import OperationInformation
51
- from .preferences import Preferences
52
- from .propertypanels.blobproperty import BlobPropertyPanel
53
- from .propertypanels.consoleproperty import ConsolePropertiesPanel
54
- from .propertypanels.groupproperties import FilePropertiesPanel, GroupPropertiesPanel
55
- from .propertypanels.hatchproperty import HatchPropertyPanel
56
- from .propertypanels.imageproperty import (
57
- ImageModificationPanel,
58
- ImagePropertyPanel,
59
- ImageVectorisationPanel,
60
- )
61
- from .propertypanels.inputproperty import InputPropertyPanel
62
- from .propertypanels.opbranchproperties import OpBranchPanel
63
- from .propertypanels.operationpropertymain import ParameterPanel
64
- from .propertypanels.outputproperty import OutputPropertyPanel
65
- from .propertypanels.pathproperty import PathPropertyPanel
66
- from .propertypanels.placementproperty import PlacementParameterPanel
67
- from .propertypanels.pointproperty import PointPropertyPanel
68
- from .propertypanels.propertywindow import PropertyWindow
69
- from .propertypanels.rasterwizardpanels import (
70
- AutoContrastPanel,
71
- ContrastPanel,
72
- EdgePanel,
73
- GammaPanel,
74
- HalftonePanel,
75
- SharpenPanel,
76
- ToneCurvePanel,
77
- )
78
- from .propertypanels.textproperty import TextPropertyPanel
79
- from .propertypanels.waitproperty import WaitPropertyPanel
80
- from .propertypanels.wobbleproperty import WobblePropertyPanel
81
- from .simpleui import SimpleUI
82
- from .simulation import Simulation
83
- from .wordlisteditor import WordlistEditor
84
- from .wxmmain import MeerK40t
85
-
86
- """
87
- Laser software for the Stock-LIHUIYU laserboard.
88
-
89
- MeerK40t (pronounced MeerKat) is a built-from-the-ground-up MIT licensed
90
- open-source laser cutting software. See https://github.com/meerk40t/meerk40t
91
- for full details.
92
-
93
- wxMeerK40t is the primary gui addon for MeerK40t. It requires wxPython for the interface.
94
- The Transformations work in Windows/OSX/Linux for wxPython 4.0+ (and likely before)
95
-
96
- """
97
-
98
- _ = wx.GetTranslation
99
-
100
-
101
- class ActionPanel(wx.Panel):
102
- def __init__(
103
- self,
104
- *args,
105
- context=None,
106
- action=None,
107
- action_right=None,
108
- fgcolor=None,
109
- bgcolor=None,
110
- icon=None,
111
- tooltip="",
112
- **kwds,
113
- ):
114
- kwds["style"] = kwds.get("style", 0)
115
- wx.Panel.__init__(self, *args, **kwds)
116
-
117
- self.context = context
118
- self.button_go = wx.Button(self, wx.ID_ANY)
119
- self.icon = icon
120
- self.fgcolor = fgcolor
121
- if bgcolor is not None:
122
- self.button_go.SetBackgroundColour(bgcolor)
123
- self.button_go.SetToolTip(tooltip)
124
- # self.button_go.SetBitmapMargins(0, 0)
125
- self.action = action
126
- self.action_right = action_right
127
-
128
- main_sizer = wx.BoxSizer(wx.HORIZONTAL)
129
- main_sizer.Add(self.button_go, 1, wx.EXPAND, 0)
130
- self.SetSizer(main_sizer)
131
- main_sizer.Fit(self)
132
- self.button_go.Bind(wx.EVT_BUTTON, self.on_button_go_click)
133
- if self.action_right is not None:
134
- self.button_go.Bind(wx.EVT_RIGHT_DOWN, self.on_button_go_click_right)
135
-
136
- self.button_go.Bind(wx.EVT_SIZE, self.on_button_resize)
137
- # Initial resize
138
- self.resize_button()
139
- self.resize_job = Job(
140
- process=self.resize_button,
141
- job_name=f"_resize_actionpanel_{self.Id}",
142
- interval=0.1,
143
- times=1,
144
- run_main=True,
145
- )
146
-
147
- def on_button_go_click(self, event):
148
- if self.action is not None:
149
- self.action()
150
-
151
- def on_button_go_click_right(self, event):
152
- if self.action_right is not None:
153
- self.action_right()
154
-
155
- def resize_button(self):
156
- size = self.button_go.Size
157
- minsize = min(size[0], size[1])
158
- # Leave some room at the edges,
159
- # for every 25 pixel 1 pixel at each side
160
- room = int(minsize / 25) * 2
161
- best_size = minsize - room
162
- # At least 20 px high
163
- best_size = max(best_size, 20)
164
- border = 2
165
- bmp = self.icon.GetBitmap(color=self.fgcolor, resize=best_size, buffer=border)
166
- # s = bmp.Size
167
- # print(f"Was asking for {best_size}x{best_size}, got {s[0]}x{s[1]}")
168
- self.button_go.SetBitmap(bmp)
169
- bmp = self.icon.GetBitmap(resize=best_size, buffer=border)
170
- self.button_go.SetBitmapFocus(bmp)
171
-
172
- def on_button_resize(self, event):
173
- self.context.schedule(self.resize_job)
174
- event.Skip()
175
-
176
-
177
- class GoPanel(ActionPanel):
178
- def __init__(self, *args, context=None, **kwds):
179
- # begin wxGlade: PassesPanel.__init__
180
- kwds["style"] = kwds.get("style", 0)
181
- fgcol = context.themes.get("start_fg")
182
- bgcol = context.themes.get("start_bg")
183
- ActionPanel.__init__(
184
- self,
185
- context=context,
186
- action=None,
187
- fgcolor=fgcol,
188
- bgcolor=bgcol,
189
- icon=icons8_gas_industry,
190
- tooltip=_("One Touch: Send Job To Laser "),
191
- *args,
192
- **kwds,
193
- )
194
- self.click_time = 0
195
- self.was_mouse = False
196
- self.button_go.Bind(wx.EVT_BUTTON, self.on_button_go_click)
197
- self.button_go.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
198
-
199
- def on_mouse_down(self, event):
200
- self.was_mouse = True
201
- event.Skip()
202
-
203
- def on_button_go_click(self, event):
204
- from time import perf_counter
205
-
206
- this_time = perf_counter()
207
- if this_time - self.click_time < 0.5:
208
- return
209
- if not self.was_mouse:
210
- channel = self.context.kernel.channel("console")
211
- channel(
212
- _(
213
- "We intentionally ignored a request to start a job via the keyboard.\n"
214
- + "You need to make your intent clear by a deliberate mouse-click"
215
- )
216
- )
217
- return
218
- if not self.button_go.Enabled:
219
- return
220
-
221
- self.button_go.Enable(False)
222
- self.context.kernel.busyinfo.start(msg=_("Processing and sending..."))
223
- self.context(
224
- "plan clear copy preprocess validate blob preopt optimize spool\nplan clear\n"
225
- )
226
- self.context.kernel.busyinfo.end()
227
- self.button_go.Enable(True)
228
- # Reset...
229
- # Deliberately at the end, as clicks queue...
230
- self.click_time = perf_counter()
231
- self.was_mouse = False
232
-
233
-
234
- def register_panel_go(window, context):
235
- pane = (
236
- aui.AuiPaneInfo()
237
- .Bottom()
238
- .Caption(_("Go"))
239
- .MinSize(40, 40)
240
- .FloatingSize(98, 98)
241
- .Name("go")
242
- .CaptionVisible(not context.pane_lock)
243
- .Hide()
244
- )
245
- pane.submenu = "_10_" + _("Laser")
246
- pane.dock_proportion = 98
247
- panel = GoPanel(window, wx.ID_ANY, context=context)
248
- pane.control = panel
249
-
250
- window.on_pane_create(pane)
251
- context.register("pane/go", pane)
252
-
253
-
254
- def register_panel_stop(window, context):
255
- # Define Stop.
256
- def action():
257
- context("estop\n")
258
-
259
- pane = (
260
- aui.AuiPaneInfo()
261
- .Bottom()
262
- .Caption(_("Stop"))
263
- .MinSize(40, 40)
264
- .FloatingSize(98, 98)
265
- .Name("stop")
266
- .Hide()
267
- .CaptionVisible(not context.pane_lock)
268
- )
269
- pane.submenu = "_10_" + _("Laser")
270
- pane.dock_proportion = 98
271
- fgcol = context.themes.get("stop_fg")
272
- bgcol = context.themes.get("stop_bg")
273
- panel = ActionPanel(
274
- window,
275
- wx.ID_ANY,
276
- context=context,
277
- action=action,
278
- fgcolor=fgcol,
279
- bgcolor=bgcol,
280
- icon=icons8_emergency_stop_button,
281
- tooltip=_("Emergency stop/reset the controller."),
282
- )
283
- pane.control = panel
284
- window.on_pane_create(pane)
285
- context.register("pane/stop", pane)
286
-
287
-
288
- def register_panel_home(window, context):
289
- # Define Home.
290
- def action():
291
- context("home\n")
292
-
293
- def action_right():
294
- context("physical_home\n")
295
-
296
- pane = (
297
- aui.AuiPaneInfo()
298
- .Bottom()
299
- .Caption(_("Home"))
300
- .MinSize(40, 40)
301
- .FloatingSize(98, 98)
302
- .Name("home")
303
- .Hide()
304
- .CaptionVisible(not context.pane_lock)
305
- )
306
- pane.submenu = "_10_" + _("Laser")
307
- pane.dock_proportion = 98
308
-
309
- fgcol = None
310
- bgcol = None
311
- panel = ActionPanel(
312
- window,
313
- wx.ID_ANY,
314
- context=context,
315
- action=action,
316
- action_right=action_right,
317
- fgcolor=fgcol,
318
- bgcolor=bgcol,
319
- icon=icons8_home_filled,
320
- tooltip=_("Send laser to home position"),
321
- )
322
- pane.control = panel
323
- window.on_pane_create(pane)
324
- context.register("pane/home", pane)
325
-
326
-
327
- def register_panel_pause(window, context):
328
- # Define Pause.
329
- def action():
330
- context("pause\n")
331
-
332
- pane = (
333
- aui.AuiPaneInfo()
334
- .Caption(_("Pause"))
335
- .Bottom()
336
- .MinSize(40, 40)
337
- .FloatingSize(98, 98)
338
- .Name("pause")
339
- .Hide()
340
- .CaptionVisible(not context.pane_lock)
341
- )
342
- pane.submenu = "_10_" + _("Laser")
343
- pane.dock_proportion = 98
344
-
345
- bgcol = context.themes.get("pause_bg")
346
- fgcol = None
347
- panel = ActionPanel(
348
- window,
349
- wx.ID_ANY,
350
- context=context,
351
- action=action,
352
- fgcolor=fgcol,
353
- bgcolor=bgcol,
354
- icon=icons8_pause,
355
- tooltip=_("Pause/Resume the controller"),
356
- )
357
- pane.control = panel
358
- window.on_pane_create(pane)
359
- context.register("pane/pause", pane)
360
-
361
-
362
- supported_languages = (
363
- ("en", "English", wx.LANGUAGE_ENGLISH),
364
- ("it", "italiano", wx.LANGUAGE_ITALIAN),
365
- ("fr", "français", wx.LANGUAGE_FRENCH),
366
- ("de", "Deutsch", wx.LANGUAGE_GERMAN),
367
- ("es", "español", wx.LANGUAGE_SPANISH),
368
- ("zh", "中文", wx.LANGUAGE_CHINESE),
369
- ("hu", "Magyar", wx.LANGUAGE_HUNGARIAN),
370
- ("pt_PT", "português", wx.LANGUAGE_PORTUGUESE),
371
- ("pt_BR", "português brasileiro", wx.LANGUAGE_PORTUGUESE_BRAZILIAN),
372
- ("ja", "日本", wx.LANGUAGE_JAPANESE),
373
- ("nl", "Nederlands", wx.LANGUAGE_DUTCH),
374
- )
375
-
376
-
377
- def resource_path(relative_path):
378
- """Get absolute path to resource, works for dev and for PyInstaller"""
379
- base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
380
- return os.path.join(base_path, relative_path)
381
-
382
-
383
- class wxMeerK40t(wx.App, Module):
384
- """
385
- wxMeerK40t is the wx.App main class and a qualified Module for the MeerK40t kernel.
386
- Running MeerK40t without the wxMeerK40t gui is both possible and reasonable. This should not change the way the
387
- underlying code runs. It should just be a series of frames held together with the kernel.
388
- """
389
-
390
- def __init__(self, context, path):
391
- wx.App.__init__(self, 0)
392
- # Is this a Windows machine? If yes:
393
- # Turn on high-DPI awareness to make sure rendering is sharp on big
394
- # monitors with font scaling enabled.
395
-
396
- high_dpi = context.setting(bool, "high_dpi", True)
397
- if platform.system() == "Windows" and high_dpi:
398
- try:
399
- # https://discuss.wxpython.org/t/support-for-high-dpi-on-windows-10/32925
400
- from ctypes import OleDLL
401
-
402
- OleDLL("shcore").SetProcessDpiAwareness(1)
403
- except (AttributeError, ImportError):
404
- # We're on a non-Windows box.
405
- pass
406
- except OSError:
407
- # Potential access denied.
408
- pass
409
- self.supported_languages = supported_languages
410
- import meerk40t.gui.icons as icons
411
-
412
- self.timer = wx.Timer(self, id=wx.ID_ANY)
413
- self.Bind(wx.EVT_TIMER, context._kernel.scheduler_main, self.timer)
414
- context._kernel.scheduler_handles_main_thread_jobs = False
415
- self.timer.Start(50)
416
- # try:
417
- # res = wx.SystemSettings().GetAppearance().IsDark()
418
- # except AttributeError:
419
- # res = wx.SystemSettings().GetColour(wx.SYS_COLOUR_WINDOW)[0] < 127
420
- res = wx.SystemSettings().GetColour(wx.SYS_COLOUR_WINDOW)[0] < 127
421
- icons.DARKMODE = res
422
- icons.icon_r = 230
423
- icons.icon_g = 230
424
- icons.icon_b = 230
425
- Module.__init__(self, context, path)
426
- self.locale = None
427
- self.Bind(wx.EVT_CLOSE, self.on_app_close)
428
- self.Bind(wx.EVT_QUERY_END_SESSION, self.on_app_close) # MAC DOCK QUIT.
429
- self.Bind(wx.EVT_END_SESSION, self.on_app_close)
430
- self.Bind(wx.EVT_END_PROCESS, self.on_app_close)
431
- # This catches events when the app is asked to activate by some other process
432
- self.Bind(wx.EVT_ACTIVATE_APP, self.OnActivate)
433
-
434
- # App started add the except hook
435
- sys.excepthook = handleGUIException
436
-
437
- # Monkey patch for pycharm excepthook override issue. https://youtrack.jetbrains.com/issue/PY-39723
438
- try:
439
- import importlib
440
-
441
- pydevd = importlib.import_module("_pydevd_bundle.pydevd_breakpoints")
442
- except ImportError:
443
- pass
444
- else:
445
- pydevd._fallback_excepthook = sys.excepthook
446
-
447
- # Set the delay after which the tooltip disappears or how long a tooltip remains visible.
448
- self.context.setting(int, "tooltip_autopop", 10000)
449
- # Set the delay after which the tooltip appears.
450
- self.context.setting(int, "tooltip_delay", 100)
451
- autopop_ms = self.context.tooltip_autopop
452
- delay_ms = self.context.tooltip_delay
453
- wx.ToolTip.SetAutoPop(autopop_ms)
454
- wx.ToolTip.SetDelay(delay_ms)
455
- wx.ToolTip.SetReshow(0)
456
-
457
- def on_app_close(self, event=None):
458
- try:
459
- if self.context is not None:
460
- self.context("quit\n")
461
- except AttributeError:
462
- pass
463
-
464
- def OnInit(self):
465
- return True
466
-
467
- def InitLocale(self):
468
- import sys
469
-
470
- if sys.platform.startswith("win") and sys.version_info > (3, 8):
471
- # This hack is needed to deal with a new Python 3.8 behaviour to
472
- # set the locale at runtime. wxpython assumes it can do with the
473
- # locale objects whatever it wants, so we need to bring it back to
474
- # a defined default
475
-
476
- import locale
477
-
478
- locale.setlocale(locale.LC_ALL, "C")
479
-
480
- def BringWindowToFront(self):
481
- try: # it's possible for this event to come when the frame is closed
482
- self.GetTopWindow().Raise()
483
- except Exception:
484
- pass
485
-
486
- def OnActivate(self, event):
487
- # if this is an activate event, rather than something else, like iconize.
488
- if event.GetActive():
489
- self.BringWindowToFront()
490
- event.Skip()
491
-
492
- def MacReopenApp(self):
493
- """Called when the doc icon is clicked, and ???"""
494
- self.BringWindowToFront()
495
-
496
- def MacNewFile(self):
497
- try:
498
- if self.context is not None:
499
- self.context.elements.clear_all()
500
- except AttributeError:
501
- pass
502
-
503
- def MacPrintFile(self, file_path):
504
- pass
505
-
506
- def MacOpenFile(self, filename):
507
- try:
508
- if self.context is not None:
509
- self.context.elements.load(os.path.realpath(filename))
510
- except AttributeError:
511
- pass
512
-
513
- def MacOpenFiles(self, filenames):
514
- try:
515
- if self.context is not None:
516
- for filename in filenames:
517
- self.context.elements.load(os.path.realpath(filename))
518
- except AttributeError:
519
- pass
520
-
521
- @staticmethod
522
- def sub_register(kernel):
523
- #################
524
- # WINDOW COMMANDS
525
- #################
526
-
527
- @kernel.console_option(
528
- "path",
529
- "p",
530
- type=str,
531
- default="/",
532
- help=_("Context Path at which to open the window"),
533
- )
534
- @kernel.console_command(
535
- "window", output_type="window", help=_("Base window command")
536
- )
537
- def window_base(channel, _, path=None, remainder=None, **kwargs):
538
- """
539
- Opens a MeerK40t window or provides information. This command is restricted to use with the wxMeerK40t gui.
540
- This also allows use of a -p flag that sets the context path for this window to operate at. This should
541
- often be restricted to where the windows are typically opened since their function and settings usually
542
- depend on the context used. Windows often cannot open multiple copies of the same window at the same context
543
- The default root path is "/". Eg. "window -p / open Preferences"
544
- """
545
- context = kernel.root
546
- if path is None:
547
- path = context
548
- else:
549
- path = kernel.get_context(path)
550
-
551
- if remainder is None:
552
- channel(
553
- _("Loaded Windows in Context {name}:").format(
554
- name=str(context.path)
555
- )
556
- )
557
- for i, name in enumerate(context.opened):
558
- if not name.startswith("window"):
559
- continue
560
- module = context.opened[name]
561
- channel(
562
- _("{index}: {name} as type of {type}").format(
563
- index=i + 1, name=name, type=type(module)
564
- )
565
- )
566
-
567
- channel("----------")
568
- if path is context:
569
- return "window", path
570
- channel(_("Loaded Windows in Path {path}:").format(path=str(path.path)))
571
- for i, name in enumerate(path.opened):
572
- if not name.startswith("window"):
573
- continue
574
- module = path.opened[name]
575
- channel(
576
- _("{index}: {name} as type of {type}").format(
577
- index=i + 1, name=name, type=type(module)
578
- )
579
- )
580
- channel("----------")
581
- return "window", path
582
-
583
- @kernel.console_command(
584
- "list",
585
- input_type="window",
586
- output_type="window",
587
- help=_("List available windows."),
588
- )
589
- def window_list(channel, _, data, **kwargs):
590
- channel(_("----------"))
591
- channel(_("Windows Registered:"))
592
- context = kernel.root
593
- for i, name in enumerate(context.match("window")):
594
- name = name[7:]
595
- channel(f"{i + 1}: {name}")
596
- return "window", data
597
-
598
- @kernel.console_command(
599
- "displays",
600
- input_type="window",
601
- output_type="window",
602
- help=_("Give display info for the current opened windows"),
603
- )
604
- def displays(channel, _, data, **kwargs):
605
- for idx in range(wx.Display.GetCount()):
606
- d = wx.Display(idx)
607
- channel(f"{idx} Primary: {d.IsPrimary()} {d.GetGeometry()}")
608
- channel(_("----------"))
609
- path = data
610
- for opened in path.opened:
611
- if opened.startswith("window/"):
612
- window = path.opened[opened]
613
- display = wx.Display.GetFromWindow(window)
614
- if display == wx.NOT_FOUND:
615
- display = "Display Not Found"
616
- channel(
617
- f"Window {opened} with bounds {window.GetRect()} is located on display: {display})"
618
- )
619
- return "window", data
620
-
621
- @kernel.console_option(
622
- "multi",
623
- "m",
624
- type=int,
625
- help=_("Multi window flag for launching multiple copies of this window."),
626
- )
627
- @kernel.console_argument("window", type=str, help=_("window to be opened"))
628
- @kernel.console_command(
629
- ("open", "toggle"),
630
- input_type="window",
631
- help=_("open/toggle the supplied window"),
632
- )
633
- def window_open(
634
- command, channel, _, data, multi=None, window=None, args=(), **kwargs
635
- ):
636
- context = kernel.root
637
- path = data
638
- try:
639
- parent = context.gui
640
- except AttributeError:
641
- parent = None
642
- window_uri = f"window/{window}"
643
- window_class = context.lookup(window_uri)
644
- if isinstance(window_class, str):
645
- window_uri = window_class
646
- window_class = context.lookup(window_uri)
647
-
648
- new_path = context.lookup(f"winpath/{window}")
649
- if new_path:
650
- path = new_path
651
- else:
652
- path = context
653
-
654
- window_name = f"{window_uri}:{multi}" if multi is not None else window_uri
655
-
656
- def window_open(*a, **k):
657
- path.open_as(window_uri, window_name, parent, *args)
658
- channel(_("Window opened: {window}").format(window=window))
659
-
660
- def window_close(*a, **k):
661
- path.close(window_name, *args)
662
- channel(_("Window closed: {window}").format(window=window))
663
-
664
- if command == "open":
665
- if path.lookup(window_uri) is not None:
666
- if wx.IsMainThread():
667
- window_open(None)
668
- else:
669
- wx.CallAfter(window_open, None)
670
- # kernel.run_later(window_open, None)
671
- else:
672
- channel(_("No such window as {window}").format(window=window))
673
- raise CommandSyntaxError
674
- else: # Toggle.
675
- if window_class is not None:
676
- if window_name in path.opened:
677
- if wx.IsMainThread():
678
- window_close(None)
679
- else:
680
- wx.CallAfter(window_close, None)
681
- # kernel.run_later(window_close, None)
682
- else:
683
- if wx.IsMainThread():
684
- window_open(None)
685
- else:
686
- wx.CallAfter(window_open, None)
687
- # kernel.run_later(window_open, None)
688
- else:
689
- channel(_("No such window as {name}").format(name=window))
690
- raise CommandSyntaxError
691
-
692
- @kernel.console_argument("window", type=str, help=_("window to be closed"))
693
- @kernel.console_command(
694
- "close",
695
- input_type="window",
696
- output_type="window",
697
- help=_("close the supplied window"),
698
- )
699
- def window_close(channel, _, data, window=None, args=(), **kwargs):
700
- path = data
701
- context = kernel.root
702
- try:
703
- parent = context.gui if hasattr(context, "gui") else None
704
- if wx.IsMainThread():
705
- path.close(f"window/{window}", parent, *args)
706
- else:
707
- wx.CallAfter(path.close(f"window/{window}", parent, *args), None)
708
- channel(_("Window closed."))
709
- except (KeyError, ValueError):
710
- channel(_("No such window as {window}").format(window=window))
711
- except IndexError:
712
- raise CommandSyntaxError
713
-
714
- @kernel.console_argument("window", type=str, help=_("window to be reset"))
715
- @kernel.console_command(
716
- "reset",
717
- input_type="window",
718
- output_type="window",
719
- help=_("reset the supplied window, or '*' for all windows"),
720
- )
721
- def window_reset(channel, _, data, window=None, **kwargs):
722
- for section in list(kernel.section_startswith("window/")):
723
- kernel.clear_persistent(section)
724
- try:
725
- del kernel.contexts[section]
726
- except KeyError:
727
- pass # No open context for that window, nothing will save out.
728
-
729
- @kernel.console_command("refresh", help=_("Refresh the main wxMeerK40 window"))
730
- def scene_refresh(command, channel, _, **kwargs):
731
- context = kernel.root
732
- context.signal("refresh_scene", "Scene")
733
- context.signal("rebuild_tree")
734
- channel(_("Refreshed."))
735
-
736
- @kernel.console_command("tooltips_enable", hidden=True)
737
- def tooltip_enable(command, channel, _, **kwargs):
738
- context = kernel.root
739
- context.setting(bool, "disable_tool_tips", False)
740
- context.disable_tool_tips = False
741
- wx.ToolTip.Enable(not context.disable_tool_tips)
742
-
743
- @kernel.console_command("tooltips_disable", hidden=True)
744
- def tooltip_disable(command, channel, _, **kwargs):
745
- context = kernel.root
746
- context.setting(bool, "disable_tool_tips", False)
747
- context.disable_tool_tips = True
748
- wx.ToolTip.Enable(not context.disable_tool_tips)
749
-
750
- def module_open(self, *args, **kwargs):
751
- context = self.context
752
- kernel = context.kernel
753
-
754
- try: # pyinstaller internal location
755
- # pylint: disable=no-member
756
- _resource_path = os.path.join(sys._MEIPASS, "locale")
757
- wx.Locale.AddCatalogLookupPathPrefix(_resource_path)
758
- except Exception:
759
- pass
760
-
761
- try: # Mac py2app resource
762
- _resource_path = os.path.join(os.environ["RESOURCEPATH"], "locale")
763
- wx.Locale.AddCatalogLookupPathPrefix(_resource_path)
764
- except Exception:
765
- pass
766
-
767
- wx.Locale.AddCatalogLookupPathPrefix("locale")
768
-
769
- # Default Locale, prepended. Check this first.
770
- basepath = os.path.abspath(os.path.dirname(sys.argv[0]))
771
- localedir = os.path.join(basepath, "locale")
772
- wx.Locale.AddCatalogLookupPathPrefix(localedir)
773
-
774
- kernel.translation = wx.GetTranslation
775
-
776
- context.app = self # Registers self as kernel.app
777
-
778
- context.setting(int, "language", None)
779
- language = context.language
780
- from meerk40t.gui.help_assets.help_assets import asset
781
-
782
- def get_asset(asset_name):
783
- return asset(context, asset_name)
784
-
785
- context.asset = get_asset
786
- if language is not None and language != 0:
787
- self.update_language(language)
788
-
789
- kernel.register("window/MeerK40t", MeerK40t)
790
-
791
- kernel.register("window/Properties", PropertyWindow)
792
- kernel.register("property/RasterOpNode/OpMain", ParameterPanel)
793
- kernel.register("property/CutOpNode/OpMain", ParameterPanel)
794
- kernel.register("property/EngraveOpNode/OpMain", ParameterPanel)
795
- kernel.register("property/ImageOpNode/OpMain", ParameterPanel)
796
- kernel.register("property/DotsOpNode/OpMain", ParameterPanel)
797
- kernel.register("property/PlaceCurrentNode/OpMain", PlacementParameterPanel)
798
- kernel.register("property/PlacePointNode/OpMain", PlacementParameterPanel)
799
-
800
- kernel.register("property/ConsoleOperation/Property", ConsolePropertiesPanel)
801
- kernel.register("property/FileNode/Property", FilePropertiesPanel)
802
- kernel.register("property/GroupNode/Property", GroupPropertiesPanel)
803
- kernel.register("property/EllipseNode/PathProperty", PathPropertyPanel)
804
- kernel.register("property/PathNode/PathProperty", PathPropertyPanel)
805
- kernel.register("property/LineNode/PathProperty", PathPropertyPanel)
806
- kernel.register("property/PolylineNode/PathProperty", PathPropertyPanel)
807
- kernel.register("property/RectNode/PathProperty", PathPropertyPanel)
808
- kernel.register("property/HatchEffectNode/HatchProperty", HatchPropertyPanel)
809
- kernel.register("property/WobbleEffectNode/WobbleProperty", WobblePropertyPanel)
810
- kernel.register("property/PointNode/PointProperty", PointPropertyPanel)
811
- kernel.register("property/TextNode/TextProperty", TextPropertyPanel)
812
- kernel.register("property/BlobNode/BlobProperty", BlobPropertyPanel)
813
- kernel.register("property/WaitOperation/WaitProperty", WaitPropertyPanel)
814
- kernel.register("property/InputOperation/InputProperty", InputPropertyPanel)
815
- kernel.register("property/BranchOperationsNode/LoopProperty", OpBranchPanel)
816
- kernel.register("property/OutputOperation/OutputProperty", OutputPropertyPanel)
817
- kernel.register("property/ImageNode/ImageProperty", ImagePropertyPanel)
818
-
819
- kernel.register("property/ImageNode/SharpenProperty", SharpenPanel)
820
- kernel.register("property/ImageNode/ContrastProperty", ContrastPanel)
821
- kernel.register("property/ImageNode/ToneCurveProperty", ToneCurvePanel)
822
- kernel.register("property/ImageNode/HalftoneProperty", HalftonePanel)
823
- kernel.register("property/ImageNode/GammaProperty", GammaPanel)
824
- kernel.register("property/ImageNode/EdgeProperty", EdgePanel)
825
- kernel.register("property/ImageNode/AutoContrastProperty", AutoContrastPanel)
826
-
827
- kernel.register("property/ImageNode/ImageModification", ImageModificationPanel)
828
- kernel.register(
829
- "property/ImageNode/ImageVectorisation", ImageVectorisationPanel
830
- )
831
-
832
- kernel.register("window/Console", Console)
833
- kernel.register("window/Preferences", Preferences)
834
- kernel.register("window/About", About)
835
- kernel.register("window/Keymap", Keymap)
836
- kernel.register("window/Wordlist", WordlistEditor)
837
- kernel.register("window/Navigation", Navigation)
838
- kernel.register("window/Notes", Notes)
839
- kernel.register("window/JobSpooler", JobSpooler)
840
- kernel.register("window/Simulation", Simulation)
841
- kernel.register("window/ExecuteJob", ExecuteJob)
842
- kernel.register("window/BufferView", BufferView)
843
- kernel.register("window/Scene", SceneWindow)
844
- kernel.register("window/DeviceManager", DeviceManager)
845
- kernel.register("window/Alignment", Alignment)
846
- kernel.register("window/HersheyFontManager", HersheyFontManager)
847
- kernel.register("window/HersheyFontSelector", HersheyFontSelector)
848
- kernel.register("window/SplitImage", RenderSplit)
849
- kernel.register("window/OperationInfo", OperationInformation)
850
- kernel.register("window/Lasertool", LaserTool)
851
- kernel.register("window/Templatetool", TemplateTool)
852
- kernel.register("window/Hingetool", LivingHingeTool)
853
- kernel.register("window/Kerftest", KerfTool)
854
- kernel.register("window/SimpleUI", SimpleUI)
855
- # Hershey Manager stuff
856
- register_hershey_stuff(kernel)
857
-
858
- from meerk40t.gui.wxmribbon import register_panel_ribbon
859
-
860
- kernel.register("wxpane/Ribbon", register_panel_ribbon)
861
-
862
- from meerk40t.gui.wxmscene import register_panel_scene
863
-
864
- kernel.register("wxpane/ScenePane", register_panel_scene)
865
-
866
- from meerk40t.gui.wxmtree import register_panel_tree
867
-
868
- kernel.register("wxpane/Tree", register_panel_tree)
869
-
870
- from meerk40t.gui.laserpanel import register_panel_laser
871
-
872
- kernel.register("wxpane/LaserPanel", register_panel_laser)
873
-
874
- from meerk40t.gui.position import register_panel_position
875
-
876
- kernel.register("wxpane/Position", register_panel_position)
877
-
878
- from meerk40t.gui.opassignment import register_panel_operation_assign
879
-
880
- kernel.register("wxpane/opassign", register_panel_operation_assign)
881
-
882
- from meerk40t.gui.snapoptions import register_panel_snapoptions
883
-
884
- kernel.register("wxpane/Snap", register_panel_snapoptions)
885
-
886
- from meerk40t.gui.wordlisteditor import register_panel_wordlist
887
-
888
- kernel.register("wxpane/wordlist", register_panel_wordlist)
889
-
890
- # from meerk40t.gui.auitoolbars import register_toolbars
891
-
892
- # kernel.register("wxpane/Toolbars", register_toolbars)
893
-
894
- kernel.register("wxpane/Go", register_panel_go)
895
- kernel.register("wxpane/Stop", register_panel_stop)
896
- kernel.register("wxpane/Home", register_panel_home)
897
- kernel.register("wxpane/Pause", register_panel_pause)
898
-
899
- from meerk40t.gui.dialogoptions import DialogOptions
900
-
901
- kernel.register("dialog/options", DialogOptions)
902
-
903
- context = kernel.root
904
-
905
- context.setting(bool, "developer_mode", False)
906
- context.setting(bool, "debug_mode", False)
907
- if context.debug_mode:
908
- from meerk40t.gui.mkdebug import (
909
- register_panel_color,
910
- register_panel_debugger,
911
- register_panel_icon,
912
- )
913
-
914
- kernel.register("wxpane/debug_tree", register_panel_debugger)
915
- kernel.register("wxpane/debug_color", register_panel_color)
916
- kernel.register("wxpane/debug_icons", register_panel_icon)
917
-
918
- @context.console_argument("sure", type=str, help="Are you sure? 'yes'?")
919
- @context.console_command("nuke_settings", hidden=True)
920
- def nuke_settings(command, channel, _, sure=None, **kwargs):
921
- if sure == "yes":
922
- kernel = self.context.kernel
923
- kernel.delete_all_persistent()
924
- kernel.shutdown()
925
- else:
926
- channel(
927
- 'Argument "sure" is required. Requires typing: "nuke_settings yes"'
928
- )
929
-
930
- def update_language(self, lang):
931
- """
932
- Update language to the requested language.
933
- """
934
- context = self.context
935
- try:
936
- language_code, language_name, language_index = supported_languages[lang]
937
- except (IndexError, ValueError):
938
- return
939
- context.language = lang
940
-
941
- if self.locale:
942
- assert sys.getrefcount(self.locale) <= 2
943
- del self.locale
944
- self.locale = wx.Locale(language_index)
945
- # wxWidgets is broken. IsOk()==false and pops up error dialog, but it translates fine!
946
- if self.locale.IsOk() or platform.system() == "Linux":
947
- self.locale.AddCatalog("meerk40t")
948
- else:
949
- self.locale = None
950
- context.signal("language", (lang, language_code, language_name, language_index))
951
-
952
-
953
- # end of class MeerK40tGui
954
-
955
- MEERK40T_HOST = "dev.meerk40t.com"
956
-
957
-
958
- def send_file_to_developers(filename):
959
- """
960
- Loads a file to send data to the developers.
961
-
962
- @param filename: file to send
963
- @return:
964
- """
965
- try:
966
- with open(filename) as f:
967
- data = f.read()
968
- except:
969
- return # There is no file, there is no data.
970
- send_data_to_developers(filename, data)
971
-
972
-
973
- def send_data_to_developers(filename, data):
974
- """
975
- Sends crash log to a server using rfc1341 7.2 The multipart Content-Type
976
- https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
977
-
978
- @param filename: filename to use when sending file
979
- @param data: data to send
980
- @return:
981
- """
982
- import socket
983
-
984
- host = MEERK40T_HOST # Replace with the actual host
985
- port = 80 # Replace with the actual port
986
-
987
- # Construct the HTTP request
988
- boundary = "----------------meerk40t-boundary"
989
- body = (
990
- f"--{boundary}\r\n"
991
- f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'
992
- f"Content-Type: text/plain\r\n"
993
- "\r\n"
994
- f"{data}\r\n"
995
- f"--{boundary}--\r\n"
996
- )
997
-
998
- headers = (
999
- f"POST /upload HTTP/1.1\r\n"
1000
- f"Host: {host}\r\n"
1001
- "User-Agent: meerk40t/1.0.0\r\n"
1002
- f"Content-Type: multipart/form-data; boundary={boundary}\r\n"
1003
- f"Content-Length: {len(body)}\r\n"
1004
- "\r\n"
1005
- )
1006
-
1007
- try:
1008
- # Create a socket connection
1009
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
1010
- client_socket.connect((host, port))
1011
-
1012
- # Send the request
1013
- request = f"{headers}{body}"
1014
- client_socket.sendall(request.encode())
1015
-
1016
- # Receive and print the response
1017
- response = client_socket.recv(4096)
1018
- response = response.decode("utf-8")
1019
- except Exception:
1020
- response = ""
1021
-
1022
- response_lines = response.split("\n")
1023
- http_code = response_lines[0]
1024
-
1025
- print(response)
1026
-
1027
- if http_code.startswith("HTTP/1.1 200 OK"):
1028
- message = response_lines[-1]
1029
- dlg = wx.MessageDialog(
1030
- None,
1031
- _("We got your message. Thank you for helping\n\n") + message,
1032
- _("Thanks"),
1033
- wx.OK,
1034
- )
1035
- dlg.ShowModal()
1036
- dlg.Destroy()
1037
- else:
1038
- # print(response)
1039
- MEERK40T_ISSUES = "https://github.com/meerk40t/meerk40t/issues"
1040
- dlg = wx.MessageDialog(
1041
- None,
1042
- _(
1043
- "We're sorry, that didn't work. Raise an issue on the github please.\n\n "
1044
- "The log file will be in your working directory.\n"
1045
- )
1046
- + MEERK40T_ISSUES
1047
- + "\n\n"
1048
- + str(http_code),
1049
- _("Thanks"),
1050
- wx.OK,
1051
- )
1052
- dlg.ShowModal()
1053
- dlg.Destroy()
1054
-
1055
-
1056
- def handleGUIException(exc_type, exc_value, exc_traceback):
1057
- """
1058
- Handler for errors. Save error to a file, and create dialog.
1059
-
1060
- @param exc_type:
1061
- @param exc_value:
1062
- @param exc_traceback:
1063
- @return:
1064
- """
1065
-
1066
- def _extended_dialog(caption, header, body):
1067
- dlg = wx.Dialog(
1068
- None,
1069
- wx.ID_ANY,
1070
- title=caption,
1071
- size=wx.DefaultSize,
1072
- pos=wx.DefaultPosition,
1073
- style=wx.DEFAULT_DIALOG_STYLE,
1074
- )
1075
- # contents
1076
- sizer = wx.BoxSizer(wx.VERTICAL)
1077
-
1078
- label = wx.StaticText(dlg, wx.ID_ANY, header)
1079
- sizer.Add(label, 1, wx.EXPAND, 0)
1080
- info = wx.TextCtrl(dlg, wx.ID_ANY, style=wx.TE_MULTILINE | wx.TE_READONLY)
1081
- info.SetValue(body)
1082
- sizer.Add(info, 5, wx.EXPAND, 0)
1083
- btnsizer = wx.StdDialogButtonSizer()
1084
- btn = wx.Button(dlg, wx.ID_OK)
1085
- btn.SetDefault()
1086
- btnsizer.AddButton(btn)
1087
- btn = wx.Button(dlg, wx.ID_CANCEL)
1088
- btnsizer.AddButton(btn)
1089
- btnsizer.Realize()
1090
- sizer.Add(btnsizer, 0, wx.EXPAND, 0)
1091
- dlg.SetSizer(sizer)
1092
- sizer.Fit(dlg)
1093
- dlg.CenterOnScreen()
1094
- return dlg
1095
-
1096
- def _variable_summary(vars, indent: int = 0):
1097
- info = ""
1098
- for name, value in vars.items():
1099
- label = f'{" " * indent}{name} : '
1100
- total_indent = len(label)
1101
- formatted = str(value)
1102
- formatted = formatted.replace("\n", "\n" + " " * total_indent)
1103
- info += f"{label}{formatted}\n"
1104
- return info
1105
-
1106
- wxversion = "wx"
1107
- try:
1108
- wxversion = wx.version()
1109
- except:
1110
- pass
1111
-
1112
- error_log = (
1113
- f"MeerK40t crash log. Version: {APPLICATION_VERSION} on {platform.system()}: "
1114
- f"Python {platform.python_version()}: {platform.machine()} - wxPython: {wxversion}\n"
1115
- )
1116
- error_log += "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
1117
- variable_info = ""
1118
- try:
1119
- frame = exc_traceback.tb_frame
1120
- variable_info = "\nLocal variables:\n"
1121
- variable_info += _variable_summary(frame.f_locals)
1122
- except Exception:
1123
- pass
1124
- try:
1125
- filename = f"MeerK40t-{datetime.now():%Y-%m-%d_%H_%M_%S}.txt"
1126
- except Exception: # I already crashed once, if there's another here just ignore it.
1127
- filename = "MeerK40t-Crash.txt"
1128
-
1129
- try:
1130
- try:
1131
- with open(filename, "w", encoding="utf8") as file:
1132
- file.write(error_log)
1133
- if variable_info:
1134
- file.write(variable_info)
1135
- print(error_log)
1136
- except PermissionError:
1137
- filename = get_safe_path(APPLICATION_NAME).joinpath(filename)
1138
- with open(filename, "w", encoding="utf8") as file:
1139
- file.write(error_log)
1140
- if variable_info:
1141
- file.write(variable_info)
1142
- print(error_log)
1143
- except Exception:
1144
- # I already crashed once, if there's another here just ignore it.
1145
- pass
1146
-
1147
- # Ask to send file.
1148
- message = _(
1149
- """The bad news is that MeerK40t encountered a crash, and the developers apologise for this bug!
1150
-
1151
- The good news is that you can help us fix this bug by anonymously sending us the crash details."""
1152
- )
1153
- message += "\n" + _(
1154
- "Only the crash details below are sent. No data from your MeerK40t project is sent. No "
1155
- + "personal information is sent either.\n"
1156
- + "Send the following data to the MeerK40t team?"
1157
- )
1158
- caption = _("Crash Detected! Send Log?")
1159
- data = error_log
1160
- if variable_info:
1161
- data += "\n" + variable_info
1162
- try:
1163
- dlg = _extended_dialog(caption, message, data)
1164
- answer = dlg.ShowModal()
1165
- dlg.Destroy()
1166
- except Exception:
1167
- answer = wx.ID_NO
1168
- if answer in (wx.YES, wx.ID_YES, wx.ID_OK):
1169
- send_data_to_developers(filename, data)
1
+ import os
2
+ import platform
3
+ import sys
4
+ import traceback
5
+ from datetime import datetime
6
+
7
+ import wx
8
+ from wx import aui
9
+
10
+ from meerk40t.gui.consolepanel import Console
11
+ from meerk40t.gui.navigationpanels import Navigation
12
+ from meerk40t.gui.spoolerpanel import JobSpooler
13
+
14
+ # try:
15
+ # # According to https://docs.wxpython.org/wx.richtext.1moduleindex.html
16
+ # # richtext needs to be imported before wx.App i.e. wxMeerK40t is instantiated
17
+ # # so, we are doing it here even though we do not refer to it in this file
18
+ # # richtext is used for the Console panel.
19
+ # from wx import richtext
20
+ # except ImportError:
21
+ # pass
22
+ from meerk40t.gui.themes import Themes
23
+ from meerk40t.gui.wxmscene import SceneWindow
24
+ from meerk40t.gui.wxutils import TextCtrl, wxButton, wxStaticText
25
+ from meerk40t.kernel import CommandSyntaxError, Module, get_safe_path
26
+ from meerk40t.kernel.kernel import Job
27
+
28
+ from ..main import APPLICATION_NAME, APPLICATION_VERSION
29
+ from ..tools.kerftest import KerfTool
30
+ from ..tools.livinghinges import LivingHingeTool
31
+ from .about import About
32
+ from .alignment import Alignment
33
+ from .autoexec import AutoExec
34
+ from .bufferview import BufferView
35
+ from .devicepanel import DeviceManager
36
+ from .executejob import ExecuteJob
37
+ from .hersheymanager import (
38
+ HersheyFontManager,
39
+ HersheyFontSelector,
40
+ register_hershey_stuff,
41
+ )
42
+ from .icons import (
43
+ icons8_emergency_stop_button,
44
+ icons8_gas_industry,
45
+ icons8_home_filled,
46
+ icons8_pause,
47
+ )
48
+ from .imagesplitter import RenderSplit
49
+ from .keymap import Keymap
50
+ from .lasertoolpanel import LaserTool
51
+ from .materialmanager import MaterialManager
52
+ from .materialtest import TemplateTool
53
+ from .notes import Notes
54
+ from .operation_info import OperationInformation
55
+ from .preferences import Preferences
56
+ from .propertypanels.blobproperty import BlobPropertyPanel
57
+ from .propertypanels.consoleproperty import ConsolePropertiesPanel
58
+ from .propertypanels.gotoproperty import GotoPropertyPanel
59
+ from .propertypanels.groupproperties import FilePropertiesPanel, GroupPropertiesPanel
60
+ from .propertypanels.hatchproperty import HatchPropertyPanel
61
+ from .propertypanels.imageproperty import (
62
+ ContourPanel,
63
+ ImageModificationPanel,
64
+ ImagePropertyPanel,
65
+ ImageVectorisationPanel,
66
+ )
67
+ from .propertypanels.inputproperty import InputPropertyPanel
68
+ from .propertypanels.opbranchproperties import OpBranchPanel
69
+ from .propertypanels.operationpropertymain import ParameterPanel
70
+ from .propertypanels.outputproperty import OutputPropertyPanel
71
+ from .propertypanels.pathproperty import PathPropertyPanel
72
+ from .propertypanels.placementproperty import PlacementParameterPanel
73
+ from .propertypanels.pointproperty import PointPropertyPanel
74
+ from .propertypanels.propertywindow import PropertyWindow
75
+ from .propertypanels.rasterwizardpanels import (
76
+ AutoContrastPanel,
77
+ ContrastPanel,
78
+ EdgePanel,
79
+ GammaPanel,
80
+ HalftonePanel,
81
+ SharpenPanel,
82
+ ToneCurvePanel,
83
+ )
84
+ from .propertypanels.regbranchproperties import RegBranchPanel
85
+ from .propertypanels.textproperty import TextPropertyPanel
86
+ from .propertypanels.waitproperty import WaitPropertyPanel
87
+ from .propertypanels.warpproperty import WarpPropertyPanel
88
+ from .propertypanels.wobbleproperty import WobblePropertyPanel
89
+ from .simpleui import SimpleUI
90
+ from .simulation import Simulation
91
+ from .tips import Tips
92
+ from .functionwrapper import ConsoleCommandUI
93
+ from .wordlisteditor import WordlistEditor
94
+ from .wxmmain import MeerK40t
95
+
96
+ """
97
+ Laser software for the Stock-LIHUIYU laserboard.
98
+
99
+ MeerK40t (pronounced MeerKat) is a built-from-the-ground-up MIT licensed
100
+ open-source laser cutting software. See https://github.com/meerk40t/meerk40t
101
+ for full details.
102
+
103
+ wxMeerK40t is the primary gui addon for MeerK40t. It requires wxPython for the interface.
104
+ The Transformations work in Windows/OSX/Linux for wxPython 4.0+ (and likely before)
105
+
106
+ """
107
+
108
+ _ = wx.GetTranslation
109
+
110
+ class ActionPanel(wx.Panel):
111
+ def __init__(
112
+ self,
113
+ *args,
114
+ context=None,
115
+ action=None,
116
+ action_right=None,
117
+ fgcolor=None,
118
+ bgcolor=None,
119
+ icon=None,
120
+ tooltip="",
121
+ **kwds,
122
+ ):
123
+ kwds["style"] = kwds.get("style", 0)
124
+ wx.Panel.__init__(self, *args, **kwds)
125
+
126
+ self.context = context
127
+ self.context.themes.set_window_colors(self)
128
+ self.button_go = wxButton(self, wx.ID_ANY)
129
+ self.icon = icon
130
+ self.fgcolor = fgcolor
131
+ self.resize_job = Job(
132
+ process=self.resize_button,
133
+ job_name=f"_resize_actionpanel_{self.Id}",
134
+ interval=0.1,
135
+ times=1,
136
+ run_main=True,
137
+ )
138
+ if bgcolor is not None:
139
+ self.button_go.SetBackgroundColour(bgcolor)
140
+ self.button_go.SetToolTip(tooltip)
141
+ # self.button_go.SetBitmapMargins(0, 0)
142
+ self.action = action
143
+ self.action_right = action_right
144
+
145
+ main_sizer = wx.BoxSizer(wx.HORIZONTAL)
146
+ main_sizer.Add(self.button_go, 1, wx.EXPAND, 0)
147
+ self.SetSizer(main_sizer)
148
+ main_sizer.Fit(self)
149
+ self.button_go.Bind(wx.EVT_BUTTON, self.on_button_go_click)
150
+ if self.action_right is not None:
151
+ self.button_go.Bind(wx.EVT_RIGHT_DOWN, self.on_button_go_click_right)
152
+
153
+ self.button_go.Bind(wx.EVT_SIZE, self.on_button_resize)
154
+ # Initial resize
155
+ self.resize_button()
156
+
157
+ def on_button_go_click(self, event):
158
+ if self.action is not None:
159
+ self.action()
160
+
161
+ def on_button_go_click_right(self, event):
162
+ if self.action_right is not None:
163
+ self.action_right()
164
+
165
+ def resize_button(self):
166
+ # The job might be called when the window is already destroyed
167
+ if self.context.kernel.is_shutdown:
168
+ return
169
+ try:
170
+ size = self.button_go.Size
171
+ except RuntimeError:
172
+ return
173
+ minsize = min(size[0], size[1])
174
+ # Leave some room at the edges,
175
+ # for every 25 pixel 1 pixel at each side
176
+ room = int(minsize / 25) * 2
177
+ best_size = minsize - room
178
+ # At least 20 px high
179
+ best_size = max(best_size, 20)
180
+ border = 2
181
+ bmp = self.icon.GetBitmap(color=self.fgcolor, resize=best_size, buffer=border)
182
+ s = bmp.Size
183
+ self.button_go.SetBitmap(bmp)
184
+ bmp = self.icon.GetBitmap(resize=best_size, buffer=border)
185
+ self.button_go.SetBitmapFocus(bmp)
186
+
187
+ t = self.button_go.GetBitmap().Size
188
+ # print(f"Was asking for {best_size}x{best_size}, got {s[0]}x{s[1]}, button has {t[0]}x{t[1]}")
189
+ scale_x = s[0] / t[0]
190
+ scale_y = s[1] / t[1]
191
+ if abs(1 - scale_x) > 1e-2 or abs(1 - scale_y) > 1e-2:
192
+ # print(f"Scale factors: {scale_x:.2f}, {scale_y:.2f}")
193
+ # This is a bug within wxPython! It seems to appear only here at very high scale factors under windows
194
+ bmp = self.icon.GetBitmap(
195
+ color=self.fgcolor,
196
+ resize=(best_size * scale_x, best_size * scale_y),
197
+ buffer=border,
198
+ )
199
+ self.button_go.SetBitmap(bmp)
200
+ bmp = self.icon.GetBitmap(
201
+ resize=(best_size * scale_x, best_size * scale_y), buffer=border
202
+ )
203
+ self.button_go.SetBitmapFocus(bmp)
204
+
205
+ def on_button_resize(self, event):
206
+ event.Skip()
207
+ self.context.schedule(self.resize_job)
208
+
209
+
210
+ class GoPanel(ActionPanel):
211
+ def __init__(self, *args, context=None, **kwds):
212
+ # begin wxGlade: PassesPanel.__init__
213
+ kwds["style"] = kwds.get("style", 0)
214
+ fgcol = context.themes.get("start_fg")
215
+ bgcol = context.themes.get("start_bg")
216
+ ActionPanel.__init__(
217
+ self,
218
+ context=context,
219
+ action=None,
220
+ fgcolor=fgcol,
221
+ bgcolor=bgcol,
222
+ icon=icons8_gas_industry,
223
+ tooltip=_("One Touch: Send Job To Laser "),
224
+ *args,
225
+ **kwds,
226
+ )
227
+ self.context.themes.set_window_colors(self)
228
+ self.click_time = 0
229
+ self.was_mouse = False
230
+ self.button_go.Bind(wx.EVT_BUTTON, self.on_button_go_click)
231
+ self.button_go.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
232
+
233
+ def on_mouse_down(self, event):
234
+ self.was_mouse = True
235
+ event.Skip()
236
+
237
+ def on_button_go_click(self, event):
238
+ from time import perf_counter
239
+
240
+ this_time = perf_counter()
241
+ if this_time - self.click_time < 0.5:
242
+ return
243
+ if not self.was_mouse:
244
+ channel = self.context.kernel.channel("console")
245
+ channel(
246
+ _(
247
+ "We intentionally ignored a request to start a job via the keyboard.\n"
248
+ + "You need to make your intent clear by a deliberate mouse-click"
249
+ )
250
+ )
251
+ return
252
+ if not self.button_go.Enabled:
253
+ return
254
+
255
+ self.button_go.Enable(False)
256
+ self.context.kernel.busyinfo.start(msg=_("Processing and sending..."))
257
+ self.context(
258
+ "planz clear copy preprocess validate blob preopt optimize spool\n"
259
+ )
260
+ self.context.kernel.busyinfo.end()
261
+ self.button_go.Enable(True)
262
+ # Reset...
263
+ # Deliberately at the end, as clicks queue...
264
+ self.click_time = perf_counter()
265
+ self.was_mouse = False
266
+
267
+
268
+ def register_panel_go(window, context):
269
+ pane = (
270
+ aui.AuiPaneInfo()
271
+ .Bottom()
272
+ .Caption(_("Go"))
273
+ .MinSize(40, 40)
274
+ .FloatingSize(98, 98)
275
+ .Name("go")
276
+ .CaptionVisible(not context.pane_lock)
277
+ .Hide()
278
+ )
279
+ pane.submenu = "_10_" + _("Laser")
280
+ pane.helptext = _("Display a laser start button")
281
+ pane.dock_proportion = 98
282
+ panel = GoPanel(window, wx.ID_ANY, context=context)
283
+ pane.control = panel
284
+
285
+ window.on_pane_create(pane)
286
+ context.register("pane/go", pane)
287
+
288
+
289
+ def register_panel_stop(window, context):
290
+ # Define Stop.
291
+ def action():
292
+ context("estop\n")
293
+
294
+ pane = (
295
+ aui.AuiPaneInfo()
296
+ .Bottom()
297
+ .Caption(_("Stop"))
298
+ .MinSize(40, 40)
299
+ .FloatingSize(98, 98)
300
+ .Name("stop")
301
+ .Hide()
302
+ .CaptionVisible(not context.pane_lock)
303
+ )
304
+ pane.submenu = "_10_" + _("Laser")
305
+ pane.helptext = _("Display a job abort button")
306
+ pane.dock_proportion = 98
307
+ fgcol = context.themes.get("stop_fg")
308
+ bgcol = context.themes.get("stop_bg")
309
+ panel = ActionPanel(
310
+ window,
311
+ wx.ID_ANY,
312
+ context=context,
313
+ action=action,
314
+ fgcolor=fgcol,
315
+ bgcolor=bgcol,
316
+ icon=icons8_emergency_stop_button,
317
+ tooltip=_("Emergency stop/reset the controller."),
318
+ )
319
+ pane.control = panel
320
+ window.on_pane_create(pane)
321
+ context.register("pane/stop", pane)
322
+
323
+
324
+ def register_panel_home(window, context):
325
+ # Define Home.
326
+ def action():
327
+ context("home\n")
328
+
329
+ def action_right():
330
+ context("physical_home\n")
331
+
332
+ pane = (
333
+ aui.AuiPaneInfo()
334
+ .Bottom()
335
+ .Caption(_("Home"))
336
+ .MinSize(40, 40)
337
+ .FloatingSize(98, 98)
338
+ .Name("home")
339
+ .Hide()
340
+ .CaptionVisible(not context.pane_lock)
341
+ )
342
+ pane.submenu = "_10_" + _("Laser")
343
+ pane.helptext = _("Display a laser homing button")
344
+ pane.dock_proportion = 98
345
+
346
+ fgcol = None
347
+ bgcol = None
348
+ panel = ActionPanel(
349
+ window,
350
+ wx.ID_ANY,
351
+ context=context,
352
+ action=action,
353
+ action_right=action_right,
354
+ fgcolor=fgcol,
355
+ bgcolor=bgcol,
356
+ icon=icons8_home_filled,
357
+ tooltip=_("Send laser to home position"),
358
+ )
359
+ pane.control = panel
360
+ window.on_pane_create(pane)
361
+ context.register("pane/home", pane)
362
+
363
+
364
+ def register_panel_pause(window, context):
365
+ # Define Pause.
366
+ def action():
367
+ context("pause\n")
368
+
369
+ pane = (
370
+ aui.AuiPaneInfo()
371
+ .Caption(_("Pause"))
372
+ .Bottom()
373
+ .MinSize(40, 40)
374
+ .FloatingSize(98, 98)
375
+ .Name("pause")
376
+ .Hide()
377
+ .CaptionVisible(not context.pane_lock)
378
+ )
379
+ pane.submenu = "_10_" + _("Laser")
380
+ pane.helptext = _("Display a job pause button")
381
+ pane.dock_proportion = 98
382
+
383
+ bgcol = context.themes.get("pause_bg")
384
+ fgcol = None
385
+ panel = ActionPanel(
386
+ window,
387
+ wx.ID_ANY,
388
+ context=context,
389
+ action=action,
390
+ fgcolor=fgcol,
391
+ bgcolor=bgcol,
392
+ icon=icons8_pause,
393
+ tooltip=_("Pause/Resume the controller"),
394
+ )
395
+ pane.control = panel
396
+ window.on_pane_create(pane)
397
+ context.register("pane/pause", pane)
398
+
399
+
400
+ supported_languages = (
401
+ ("en", "English", wx.LANGUAGE_ENGLISH),
402
+ ("it", "italiano", wx.LANGUAGE_ITALIAN),
403
+ ("fr", "français", wx.LANGUAGE_FRENCH),
404
+ ("de", "Deutsch", wx.LANGUAGE_GERMAN),
405
+ ("es", "español", wx.LANGUAGE_SPANISH),
406
+ ("zh", "中文", wx.LANGUAGE_CHINESE),
407
+ ("hu", "Magyar", wx.LANGUAGE_HUNGARIAN),
408
+ ("pt_PT", "português", wx.LANGUAGE_PORTUGUESE),
409
+ ("pt_BR", "português brasileiro", wx.LANGUAGE_PORTUGUESE_BRAZILIAN),
410
+ ("ja", "日本", wx.LANGUAGE_JAPANESE),
411
+ ("nl", "Nederlands", wx.LANGUAGE_DUTCH),
412
+ )
413
+
414
+
415
+ def resource_path(relative_path):
416
+ """Get absolute path to resource, works for dev and for PyInstaller"""
417
+ base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
418
+ return os.path.join(base_path, relative_path)
419
+
420
+
421
+ class wxMeerK40t(wx.App, Module):
422
+ """
423
+ wxMeerK40t is the wx.App main class and a qualified Module for the MeerK40t kernel.
424
+ Running MeerK40t without the wxMeerK40t gui is both possible and reasonable. This should not change the way the
425
+ underlying code runs. It should just be a series of frames held together with the kernel.
426
+ """
427
+
428
+ def __init__(self, context, path):
429
+ self.context = context
430
+ wx.App.__init__(self, 0)
431
+ # Is this a Windows machine? If yes:
432
+ # Turn on high-DPI awareness to make sure rendering is sharp on big
433
+ # monitors with font scaling enabled.
434
+
435
+ high_dpi = context.setting(bool, "high_dpi", True)
436
+ if platform.system() == "Windows" and high_dpi:
437
+ try:
438
+ # https://discuss.wxpython.org/t/support-for-high-dpi-on-windows-10/32925
439
+ from ctypes import OleDLL
440
+
441
+ OleDLL("shcore").SetProcessDpiAwareness(1)
442
+ except (AttributeError, ImportError):
443
+ # We're on a non-Windows box.
444
+ pass
445
+ except OSError:
446
+ # Potential access denied.
447
+ pass
448
+ self.supported_languages = supported_languages
449
+ import meerk40t.gui.icons as icons
450
+
451
+ self.timer = wx.Timer(self, id=wx.ID_ANY)
452
+ self.Bind(wx.EVT_TIMER, context._kernel.scheduler_main, self.timer)
453
+ context._kernel.scheduler_handles_main_thread_jobs = False
454
+ self.timer.Start(50)
455
+ # try:
456
+ # res = wx.SystemSettings().GetAppearance().IsDark()
457
+ # except AttributeError:
458
+ # res = wx.SystemSettings().GetColour(wx.SYS_COLOUR_WINDOW)[0] < 127
459
+ Module.__init__(self, context, path)
460
+ theme = Themes(kernel=context._kernel)
461
+ icons.DARKMODE = theme.dark
462
+ self.locale = None
463
+ self.Bind(wx.EVT_CLOSE, self.on_app_close)
464
+ self.Bind(wx.EVT_QUERY_END_SESSION, self.on_app_close) # MAC DOCK QUIT.
465
+ self.Bind(wx.EVT_END_SESSION, self.on_app_close)
466
+ self.Bind(wx.EVT_END_PROCESS, self.on_app_close)
467
+ # This catches events when the app is asked to activate by some other process
468
+ self.Bind(wx.EVT_ACTIVATE_APP, self.OnActivate)
469
+ provider = wx.SimpleHelpProvider()
470
+ wx.HelpProvider.Set(provider)
471
+ # App started add the except hook
472
+ sys.excepthook = handleGUIException
473
+
474
+ # Monkey patch for pycharm excepthook override issue. https://youtrack.jetbrains.com/issue/PY-39723
475
+ try:
476
+ import importlib
477
+
478
+ pydevd = importlib.import_module("_pydevd_bundle.pydevd_breakpoints")
479
+ except ImportError:
480
+ pass
481
+ else:
482
+ pydevd._fallback_excepthook = sys.excepthook
483
+
484
+ # Set the delay after which the tooltip disappears or how long a tooltip remains visible.
485
+ self.context.setting(int, "tooltip_autopop", 10000)
486
+ # Set the delay after which the tooltip appears.
487
+ self.context.setting(int, "tooltip_delay", 100)
488
+ autopop_ms = self.context.tooltip_autopop
489
+ delay_ms = self.context.tooltip_delay
490
+ wx.ToolTip.SetAutoPop(autopop_ms)
491
+ wx.ToolTip.SetDelay(delay_ms)
492
+ wx.ToolTip.SetReshow(0)
493
+
494
+ def on_app_close(self, event=None):
495
+ try:
496
+ if self.context is not None:
497
+ self.context("quit\n")
498
+ except AttributeError:
499
+ pass
500
+
501
+ def OnInit(self):
502
+ self.name = f"MeerK40t-{wx.GetUserId()}"
503
+ mkdir = self.context.kernel.os_information["OS_TEMPDIR"]
504
+ self.instance = wx.SingleInstanceChecker(self.name, path=mkdir)
505
+ self.context.setting(bool, "single_instance_only", True)
506
+ if self.context.kernel._was_restarted:
507
+ return True
508
+ if self.context.single_instance_only and self.instance.IsAnotherRunning():
509
+ dlg = wx.MessageDialog(
510
+ None,
511
+ "Another instance is running!\nDo you want to run another copy of the app?",
512
+ "ERROR",
513
+ wx.YES_NO | wx.ICON_QUESTION | wx.NO_DEFAULT,
514
+ )
515
+ result = dlg.ShowModal() == wx.ID_YES
516
+ dlg.Destroy()
517
+ if not result:
518
+ return False
519
+ return True
520
+
521
+ def InitLocale(self):
522
+ import sys
523
+
524
+ if sys.platform.startswith("win") and sys.version_info > (3, 8):
525
+ # This hack is needed to deal with a new Python 3.8 behaviour to
526
+ # set the locale at runtime. wxpython assumes it can do with the
527
+ # locale objects whatever it wants, so we need to bring it back to
528
+ # a defined default
529
+
530
+ import locale
531
+
532
+ locale.setlocale(locale.LC_ALL, "C")
533
+
534
+ def BringWindowToFront(self):
535
+ try: # it's possible for this event to come when the frame is closed
536
+ self.GetTopWindow().Raise()
537
+ except Exception:
538
+ pass
539
+
540
+ def OnActivate(self, event):
541
+ # if this is an activate event, rather than something else, like iconize.
542
+ if event.GetActive():
543
+ self.BringWindowToFront()
544
+ event.Skip()
545
+
546
+ def MacReopenApp(self):
547
+ """Called when the doc icon is clicked, and ???"""
548
+ self.BringWindowToFront()
549
+
550
+ def MacNewFile(self):
551
+ try:
552
+ if self.context is not None:
553
+ with self.context.elements.undoscope("New"):
554
+ self.context.elements.clear_all()
555
+ except AttributeError:
556
+ pass
557
+
558
+ def MacPrintFile(self, file_path):
559
+ pass
560
+
561
+ def MacOpenFile(self, filename):
562
+ try:
563
+ if self.context is not None:
564
+ channel = self.context.kernel.channel("console")
565
+ self.context.elements.load(os.path.realpath(filename), svg_ppi=self.context.elements.svg_ppi, channel=channel)
566
+ except AttributeError:
567
+ pass
568
+
569
+ def MacOpenFiles(self, filenames):
570
+ try:
571
+ if self.context is not None:
572
+ channel = self.context.kernel.channel("console")
573
+ for filename in filenames:
574
+ self.context.elements.load(os.path.realpath(filename), svg_ppi=self.context.elements.svg_ppi, channel=channel)
575
+ except AttributeError:
576
+ pass
577
+
578
+ @staticmethod
579
+ def sub_register(kernel):
580
+ #################
581
+ # WINDOW COMMANDS
582
+ #################
583
+
584
+ @kernel.console_option(
585
+ "path",
586
+ "p",
587
+ type=str,
588
+ default="/",
589
+ help=_("Context Path at which to open the window"),
590
+ )
591
+ @kernel.console_command(
592
+ "window", output_type="window", help=_("Base window command")
593
+ )
594
+ def window_base(channel, _, path=None, remainder=None, **kwargs):
595
+ """
596
+ Opens a MeerK40t window or provides information. This command is restricted to use with the wxMeerK40t gui.
597
+ This also allows use of a -p flag that sets the context path for this window to operate at. This should
598
+ often be restricted to where the windows are typically opened since their function and settings usually
599
+ depend on the context used. Windows often cannot open multiple copies of the same window at the same context
600
+ The default root path is "/". E.g. "window -p / open Preferences"
601
+ """
602
+ context = kernel.root
603
+ if path is None:
604
+ path = context
605
+ else:
606
+ path = kernel.get_context(path)
607
+
608
+ if remainder is None:
609
+ channel(
610
+ _("Loaded Windows in Context {name}:").format(
611
+ name=str(context.path)
612
+ )
613
+ )
614
+ for i, name in enumerate(context.opened):
615
+ if not name.startswith("window"):
616
+ continue
617
+ module = context.opened[name]
618
+ channel(
619
+ _("{index}: {name} as type of {type}").format(
620
+ index=i + 1, name=name, type=type(module)
621
+ )
622
+ )
623
+
624
+ channel("----------")
625
+ if path is context:
626
+ return "window", path
627
+ channel(_("Loaded Windows in Path {path}:").format(path=str(path.path)))
628
+ for i, name in enumerate(path.opened):
629
+ if not name.startswith("window"):
630
+ continue
631
+ module = path.opened[name]
632
+ channel(
633
+ _("{index}: {name} as type of {type}").format(
634
+ index=i + 1, name=name, type=type(module)
635
+ )
636
+ )
637
+ channel("----------")
638
+ return "window", path
639
+
640
+ @kernel.console_command(
641
+ "list",
642
+ input_type="window",
643
+ output_type="window",
644
+ help=_("List available windows."),
645
+ )
646
+ def window_list(channel, _, data, **kwargs):
647
+ channel(_("----------"))
648
+ channel(_("Windows Registered:"))
649
+ context = kernel.root
650
+ for i, name in enumerate(context.match("window")):
651
+ name = name[7:]
652
+ channel(f"{i + 1}: {name}")
653
+ return "window", data
654
+
655
+ @kernel.console_command(
656
+ "displays",
657
+ input_type="window",
658
+ output_type="window",
659
+ help=_("Give display info for the current opened windows"),
660
+ )
661
+ def displays(channel, _, data, **kwargs):
662
+ for idx in range(wx.Display.GetCount()):
663
+ d = wx.Display(idx)
664
+ channel(f"{idx} Primary: {d.IsPrimary()} {d.GetGeometry()}")
665
+ channel(_("----------"))
666
+ path = data
667
+ for opened in path.opened:
668
+ if opened.startswith("window/"):
669
+ window = path.opened[opened]
670
+ display = wx.Display.GetFromWindow(window)
671
+ if display == wx.NOT_FOUND:
672
+ display = "Display Not Found"
673
+ channel(
674
+ f"Window {opened} with bounds {window.GetRect()} is located on display: {display})"
675
+ )
676
+ return "window", data
677
+
678
+ @kernel.console_option(
679
+ "multi",
680
+ "m",
681
+ type=int,
682
+ help=_("Multi window flag for launching multiple copies of this window."),
683
+ )
684
+ @kernel.console_argument("window", type=str, help=_("window to be opened"))
685
+ @kernel.console_command(
686
+ ("open", "toggle"),
687
+ input_type="window",
688
+ help=_("open/toggle the supplied window"),
689
+ )
690
+ def window_open(
691
+ command, channel, _, data, multi=None, window=None, args=(), **kwargs
692
+ ):
693
+ context = kernel.root
694
+ path = data
695
+ try:
696
+ parent = context.gui
697
+ except AttributeError:
698
+ parent = None
699
+ window_uri = f"window/{window}"
700
+ window_class = context.lookup(window_uri)
701
+ if isinstance(window_class, str):
702
+ window_uri = window_class
703
+ window_class = context.lookup(window_uri)
704
+
705
+ new_path = context.lookup(f"winpath/{window}")
706
+ if new_path:
707
+ path = new_path
708
+ else:
709
+ path = context
710
+
711
+ window_name = f"{window_uri}:{multi}" if multi is not None else window_uri
712
+
713
+ def window_open(*a, **k):
714
+ path.open_as(window_uri, window_name, parent, *args)
715
+ channel(_("Window opened: {window}").format(window=window))
716
+
717
+ def window_close(*a, **k):
718
+ path.close(window_name, *args)
719
+ channel(_("Window closed: {window}").format(window=window))
720
+
721
+ if command == "open":
722
+ if path.lookup(window_uri) is not None:
723
+ if wx.IsMainThread():
724
+ with wx.BusyCursor():
725
+ window_open(None)
726
+ else:
727
+ wx.CallAfter(window_open, None)
728
+ # kernel.run_later(window_open, None)
729
+ else:
730
+ channel(_("No such window as {window}").format(window=window))
731
+ raise CommandSyntaxError
732
+ else: # Toggle.
733
+ if window_class is not None:
734
+ to_be_closed = bool(window_name in path.opened)
735
+ if to_be_closed:
736
+ win = path.opened[window_name]
737
+ if hasattr(win, "IsIconized") and win.IsIconized():
738
+ # Minimized windows will reappear first
739
+ to_be_closed = False
740
+
741
+ if to_be_closed:
742
+ if wx.IsMainThread():
743
+ window_close(None)
744
+ else:
745
+ wx.CallAfter(window_close, None)
746
+ # kernel.run_later(window_close, None)
747
+ else:
748
+ if wx.IsMainThread():
749
+ with wx.BusyCursor():
750
+ window_open(None)
751
+ else:
752
+ wx.CallAfter(window_open, None)
753
+ # kernel.run_later(window_open, None)
754
+ else:
755
+ channel(_("No such window as {name}").format(name=window))
756
+ raise CommandSyntaxError
757
+
758
+ @kernel.console_argument("window", type=str, help=_("window to be closed"))
759
+ @kernel.console_command(
760
+ "close",
761
+ input_type="window",
762
+ output_type="window",
763
+ help=_("close the supplied window"),
764
+ )
765
+ def window_close(channel, _, data, window=None, args=(), **kwargs):
766
+ path = data
767
+ context = kernel.root
768
+ try:
769
+ parent = context.gui if hasattr(context, "gui") else None
770
+ if wx.IsMainThread():
771
+ path.close(f"window/{window}", parent, *args)
772
+ else:
773
+ wx.CallAfter(path.close(f"window/{window}", parent, *args), None)
774
+ channel(_("Window closed."))
775
+ except (KeyError, ValueError):
776
+ channel(_("No such window as {window}").format(window=window))
777
+ except IndexError:
778
+ raise CommandSyntaxError
779
+
780
+ @kernel.console_argument("window", type=str, help=_("window to be reset"))
781
+ @kernel.console_command(
782
+ "reset",
783
+ input_type="window",
784
+ output_type="window",
785
+ help=_("reset the supplied window, or '*' for all windows"),
786
+ )
787
+ def window_reset(channel, _, data, window=None, **kwargs):
788
+ for section in list(kernel.section_startswith("window/")):
789
+ kernel.clear_persistent(section)
790
+ try:
791
+ del kernel.contexts[section]
792
+ except KeyError:
793
+ pass # No open context for that window, nothing will save out.
794
+
795
+ @kernel.console_command("refresh", help=_("Refresh the main wxMeerK40 window"))
796
+ def scene_refresh(command, channel, _, **kwargs):
797
+ context = kernel.root
798
+ context.signal("refresh_scene", "Scene")
799
+ context.signal("rebuild_tree")
800
+ channel(_("Refreshed."))
801
+
802
+ @kernel.console_command("tooltips_enable", hidden=True)
803
+ def tooltip_enable(command, channel, _, **kwargs):
804
+ context = kernel.root
805
+ context.setting(bool, "disable_tool_tips", False)
806
+ context.disable_tool_tips = False
807
+ wx.ToolTip.Enable(not context.disable_tool_tips)
808
+
809
+ @kernel.console_command("tooltips_disable", hidden=True)
810
+ def tooltip_disable(command, channel, _, **kwargs):
811
+ context = kernel.root
812
+ context.setting(bool, "disable_tool_tips", False)
813
+ context.disable_tool_tips = True
814
+ wx.ToolTip.Enable(not context.disable_tool_tips)
815
+
816
+ @kernel.console_argument("func", type=str, help=_("Function to call interactively"))
817
+ @kernel.console_command("gui", help=_("Provides a GUI wrapper around a console command"))
818
+ def gui_func(command, channel, _, func=None, **kwargs):
819
+ if func is None:
820
+ channel(_("You need to provide a function name"))
821
+ return
822
+ if func in ("gui", "help", "?", "??", "quit", "shutdown", "exit"):
823
+ channel (_("It does not make sense, to run '{command}' in a GUI").format(command=func))
824
+ return
825
+ context = kernel.root
826
+ try:
827
+ parent = context.gui
828
+ except AttributeError:
829
+ parent = None
830
+ dialog: wx.Dialog = ConsoleCommandUI(parent, wx.ID_ANY, title=_("Command {command}").format(command=func), context=context, command_string=func)
831
+ res = dialog.ShowModal()
832
+ if res == wx.ID_OK:
833
+ dialog.accept_it()
834
+ else:
835
+ dialog.cancel_it()
836
+ dialog.Destroy()
837
+
838
+ def module_open(self, *args, **kwargs):
839
+ context = self.context
840
+ kernel = context.kernel
841
+
842
+ try: # pyinstaller internal location
843
+ # pylint: disable=no-member
844
+ _resource_path = os.path.join(sys._MEIPASS, "locale")
845
+ wx.Locale.AddCatalogLookupPathPrefix(_resource_path)
846
+ except Exception:
847
+ pass
848
+
849
+ try: # Mac py2app resource
850
+ _resource_path = os.path.join(os.environ["RESOURCEPATH"], "locale")
851
+ wx.Locale.AddCatalogLookupPathPrefix(_resource_path)
852
+ except Exception:
853
+ pass
854
+
855
+ wx.Locale.AddCatalogLookupPathPrefix("locale")
856
+
857
+ # Default Locale, prepended. Check this first.
858
+ basepath = os.path.abspath(os.path.dirname(sys.argv[0]))
859
+ localedir = os.path.join(basepath, "locale")
860
+ wx.Locale.AddCatalogLookupPathPrefix(localedir)
861
+
862
+ kernel.translation = wx.GetTranslation
863
+
864
+ context.app = self # Registers self as kernel.app
865
+
866
+ context.setting(int, "language", None)
867
+ language = context.language
868
+ # print (f"Language according to settings: {language}")
869
+ tlang = getattr(kernel.args, "language", "undefined")
870
+ for idx, content in enumerate(supported_languages):
871
+ if content[0] == tlang:
872
+ language = idx
873
+ break
874
+ # print (f"Language after cmdline-test: {language}")
875
+
876
+ # See issue #2103
877
+ # context.setting(str, "i18n", "en")
878
+ # language = context.i18n
879
+ # self.update_language_kernel(language)
880
+
881
+ from meerk40t.gui.help_assets.help_assets import asset
882
+
883
+ def get_asset(asset_name):
884
+ return asset(context, asset_name)
885
+
886
+ context.asset = get_asset
887
+ if language is not None and language != 0:
888
+ self.update_language(language)
889
+
890
+ kernel.register("window/MeerK40t", MeerK40t)
891
+
892
+ kernel.register("window/Properties", PropertyWindow)
893
+ kernel.register("property/RasterOpNode/OpMain", ParameterPanel)
894
+ kernel.register("property/CutOpNode/OpMain", ParameterPanel)
895
+ kernel.register("property/EngraveOpNode/OpMain", ParameterPanel)
896
+ kernel.register("property/ImageOpNode/OpMain", ParameterPanel)
897
+ kernel.register("property/DotsOpNode/OpMain", ParameterPanel)
898
+ kernel.register("property/PlaceCurrentNode/OpMain", PlacementParameterPanel)
899
+ kernel.register("property/PlacePointNode/OpMain", PlacementParameterPanel)
900
+
901
+ kernel.register("property/ConsoleOperation/Property", ConsolePropertiesPanel)
902
+ kernel.register("property/FileNode/Property", FilePropertiesPanel)
903
+ kernel.register("property/GroupNode/Property", GroupPropertiesPanel)
904
+ kernel.register("property/EllipseNode/PathProperty", PathPropertyPanel)
905
+ kernel.register("property/PathNode/PathProperty", PathPropertyPanel)
906
+ kernel.register("property/LineNode/PathProperty", PathPropertyPanel)
907
+ kernel.register("property/PolylineNode/PathProperty", PathPropertyPanel)
908
+ kernel.register("property/RectNode/PathProperty", PathPropertyPanel)
909
+ kernel.register("property/HatchEffectNode/HatchProperty", HatchPropertyPanel)
910
+ kernel.register("property/WobbleEffectNode/WobbleProperty", WobblePropertyPanel)
911
+ kernel.register("property/WarpEffectNode/WarpProperty", WarpPropertyPanel)
912
+ kernel.register("property/PointNode/PointProperty", PointPropertyPanel)
913
+ kernel.register("property/TextNode/TextProperty", TextPropertyPanel)
914
+ kernel.register("property/BlobNode/BlobProperty", BlobPropertyPanel)
915
+ kernel.register("property/WaitOperation/WaitProperty", WaitPropertyPanel)
916
+ kernel.register("property/GotoOperation/GotoProperty", GotoPropertyPanel)
917
+ kernel.register("property/InputOperation/InputProperty", InputPropertyPanel)
918
+ kernel.register("property/BranchOperationsNode/LoopProperty", OpBranchPanel)
919
+ kernel.register("property/BranchRegmarkNode/RegmarkProperty", RegBranchPanel)
920
+ kernel.register("property/OutputOperation/OutputProperty", OutputPropertyPanel)
921
+ kernel.register("property/ImageNode/ImageProperty", ImagePropertyPanel)
922
+
923
+ kernel.register("property/ImageNode/SharpenProperty", SharpenPanel)
924
+ kernel.register("property/ImageNode/ContrastProperty", ContrastPanel)
925
+ kernel.register("property/ImageNode/ToneCurveProperty", ToneCurvePanel)
926
+ kernel.register("property/ImageNode/HalftoneProperty", HalftonePanel)
927
+ kernel.register("property/ImageNode/GammaProperty", GammaPanel)
928
+ kernel.register("property/ImageNode/EdgeProperty", EdgePanel)
929
+ kernel.register("property/ImageNode/AutoContrastProperty", AutoContrastPanel)
930
+
931
+ kernel.register("property/ImageNode/ImageModification", ImageModificationPanel)
932
+ kernel.register(
933
+ "property/ImageNode/ImageVectorisation", ImageVectorisationPanel
934
+ )
935
+ kernel.register(
936
+ "property/ImageNode/ImageContour", ContourPanel
937
+ )
938
+
939
+ kernel.register("window/Console", Console)
940
+ if (
941
+ hasattr(kernel.args, "lock_general_config")
942
+ and kernel.args.lock_general_config
943
+ ):
944
+ pass
945
+ else:
946
+ kernel.register("window/Preferences", Preferences)
947
+ kernel.register("window/About", About)
948
+ kernel.register("window/Keymap", Keymap)
949
+ kernel.register("window/Wordlist", WordlistEditor)
950
+ kernel.register("window/MatManager", MaterialManager)
951
+ kernel.register("window/Navigation", Navigation)
952
+ kernel.register("window/Notes", Notes)
953
+ kernel.register("window/AutoExec", AutoExec)
954
+ kernel.register("window/JobSpooler", JobSpooler)
955
+ kernel.register("window/Simulation", Simulation)
956
+ kernel.register("window/Tips", Tips)
957
+ kernel.register("window/ExecuteJob", ExecuteJob)
958
+ kernel.register("window/BufferView", BufferView)
959
+ kernel.register("window/Scene", SceneWindow)
960
+ if not (
961
+ hasattr(kernel.args, "lock_device_config")
962
+ and kernel.args.lock_device_config
963
+ ):
964
+ kernel.register("window/DeviceManager", DeviceManager)
965
+ kernel.register("window/Alignment", Alignment)
966
+ kernel.register("window/HersheyFontManager", HersheyFontManager)
967
+ kernel.register("window/HersheyFontSelector", HersheyFontSelector)
968
+ kernel.register("window/SplitImage", RenderSplit)
969
+ kernel.register("window/OperationInfo", OperationInformation)
970
+ kernel.register("window/Lasertool", LaserTool)
971
+ kernel.register("window/Templatetool", TemplateTool)
972
+ kernel.register("window/Hingetool", LivingHingeTool)
973
+ kernel.register("window/Kerftest", KerfTool)
974
+ kernel.register("window/SimpleUI", SimpleUI)
975
+ # Hershey Manager stuff
976
+ register_hershey_stuff(kernel)
977
+
978
+ from meerk40t.gui.helper import register_panel_helper
979
+
980
+ kernel.register("wxpane/helper", register_panel_helper)
981
+
982
+ from meerk40t.gui.wxmribbon import register_panel_ribbon
983
+
984
+ kernel.register("wxpane/Ribbon", register_panel_ribbon)
985
+
986
+ from meerk40t.gui.wxmscene import register_panel_scene
987
+
988
+ kernel.register("wxpane/ScenePane", register_panel_scene)
989
+
990
+ from meerk40t.gui.wxmtree import register_panel_tree
991
+
992
+ kernel.register("wxpane/Tree", register_panel_tree)
993
+
994
+ from meerk40t.gui.laserpanel import register_panel_laser
995
+
996
+ kernel.register("wxpane/LaserPanel", register_panel_laser)
997
+
998
+ from meerk40t.gui.position import register_panel_position
999
+
1000
+ kernel.register("wxpane/Position", register_panel_position)
1001
+
1002
+ from meerk40t.gui.opassignment import register_panel_operation_assign
1003
+
1004
+ kernel.register("wxpane/opassign", register_panel_operation_assign)
1005
+
1006
+ from meerk40t.gui.snapoptions import register_panel_snapoptions
1007
+
1008
+ kernel.register("wxpane/Snap", register_panel_snapoptions)
1009
+
1010
+ from meerk40t.gui.magnetoptions import register_panel_magnetoptions
1011
+
1012
+ kernel.register("wxpane/magnet", register_panel_magnetoptions)
1013
+
1014
+ from meerk40t.gui.wordlisteditor import register_panel_wordlist
1015
+
1016
+ kernel.register("wxpane/wordlist", register_panel_wordlist)
1017
+
1018
+ # from meerk40t.gui.auitoolbars import register_toolbars
1019
+
1020
+ # kernel.register("wxpane/Toolbars", register_toolbars)
1021
+
1022
+ kernel.register("wxpane/Go", register_panel_go)
1023
+ kernel.register("wxpane/Stop", register_panel_stop)
1024
+ kernel.register("wxpane/Home", register_panel_home)
1025
+ kernel.register("wxpane/Pause", register_panel_pause)
1026
+
1027
+ from meerk40t.gui.dialogoptions import DialogOptions
1028
+
1029
+ kernel.register("dialog/options", DialogOptions)
1030
+
1031
+ context = kernel.root
1032
+
1033
+ context.setting(bool, "developer_mode", False)
1034
+ context.setting(bool, "debug_mode", False)
1035
+ if context.debug_mode:
1036
+ from meerk40t.gui.mkdebug import (
1037
+ register_panel_color,
1038
+ register_panel_crash,
1039
+ register_panel_debugger,
1040
+ register_panel_icon,
1041
+ register_panel_window,
1042
+ )
1043
+
1044
+ kernel.register("wxpane/debug_tree", register_panel_debugger)
1045
+ kernel.register("wxpane/debug_color", register_panel_color)
1046
+ kernel.register("wxpane/debug_icons", register_panel_icon)
1047
+ kernel.register("wxpane/debug_shutdown", register_panel_crash)
1048
+ kernel.register("wxpane/debug_window", register_panel_window)
1049
+
1050
+ from meerk40t.gui.utilitywidgets.debugwidgets import register_widget_icon
1051
+
1052
+ register_widget_icon(kernel.root)
1053
+
1054
+ wildcard = "Sound-Files|*.wav;*.mp3;*.ogg|All files|*.*"
1055
+ OS_NAME = platform.system()
1056
+ addon = ""
1057
+ if OS_NAME == "Darwin":
1058
+ wildcard = f"System-Sounds|*.aiff|{wildcard}"
1059
+ elif OS_NAME == "Linux":
1060
+ wildcard = f"System-Sounds|*.oga;*.wav;*.mp3|{wildcard}"
1061
+ addon = "\n" + _("This uses the 'play' command that comes with the sox package,\nso you might need to install it with 'sudo apt install sox' first.")
1062
+ system_sound = {
1063
+ "Windows": r"c:\Windows\Media\Alarm01.wav",
1064
+ "Darwin": "/System/Library/Sounds/Ping.aiff",
1065
+ "Linux": "/usr/share/sounds/freedesktop/stereo/phone-incoming-call.oga",
1066
+ }
1067
+ default_snd = system_sound.get(OS_NAME, "")
1068
+
1069
+ choices = [
1070
+ {
1071
+ "attr": "single_instance_only",
1072
+ "object": context.root,
1073
+ "default": True,
1074
+ "type": bool,
1075
+ "label": _("Single Instance"),
1076
+ "tip": _("Allow only a single instance of MeerK40t."),
1077
+ "page": "Start",
1078
+ },
1079
+ {
1080
+ "attr": "beep_soundfile",
1081
+ "object": context.root,
1082
+ "type": str,
1083
+ "default": default_snd,
1084
+ "style": "file",
1085
+ "wildcard": wildcard,
1086
+ "label": _("Soundfile"),
1087
+ "tip": _("Define the soundfile MeerK40t will play when the 'beep' command is issued") + addon,
1088
+ "page": "Start",
1089
+ }
1090
+ ]
1091
+ kernel.register_choices("preferences", choices)
1092
+
1093
+ @context.console_argument("sure", type=str, help="Are you sure? 'yes'?")
1094
+ @context.console_command("nuke_settings", hidden=True)
1095
+ def nuke_settings(command, channel, _, sure=None, **kwargs):
1096
+ if sure == "yes":
1097
+ kernel = self.context.kernel
1098
+ kernel.delete_all_persistent()
1099
+ kernel.shutdown()
1100
+ else:
1101
+ channel(
1102
+ 'Argument "sure" is required. Requires typing: "nuke_settings yes"'
1103
+ )
1104
+
1105
+ @context.console_argument("crashtype", type=str)
1106
+ @context.console_command("crash_me_if_you_can", hidden=True)
1107
+ def crash_mk(command, channel, _, crashtype=None, **kwargs):
1108
+ def crash_divide(x, y):
1109
+ return x / y
1110
+
1111
+ def crash_key(variable, index):
1112
+ l = variable
1113
+ return l[index]
1114
+
1115
+ def crash_index(variable, index):
1116
+ l = variable
1117
+ return l[index]
1118
+
1119
+ def crash_value(variable, dtype):
1120
+ return dtype(variable)
1121
+
1122
+ if crashtype is None:
1123
+ crashtype = "dividebyzero"
1124
+ crashtype = crashtype.lower()
1125
+ if crashtype == "dividebyzero":
1126
+ a = 0
1127
+ b = 0
1128
+ c = crash_divide(a, b)
1129
+ return
1130
+ if crashtype == "key":
1131
+ d = {"a": 0}
1132
+ b = crash_key(d, "b")
1133
+ return
1134
+ if crashtype == "index":
1135
+ a = (0, 1, 2)
1136
+ b = crash_index(a, 5)
1137
+ return
1138
+ if crashtype == "value":
1139
+ a = "an invalid number 1"
1140
+ b = crash_value(a, float)
1141
+ return
1142
+
1143
+ def update_language(self, lang):
1144
+ """
1145
+ Update language to the requested language.
1146
+ """
1147
+ context = self.context
1148
+ try:
1149
+ language_code, language_name, language_index = supported_languages[lang]
1150
+ except (IndexError, ValueError):
1151
+ return
1152
+ context.language = lang
1153
+ # We need to remove the command-line argument now:
1154
+ if hasattr(context.kernel.args, "language"):
1155
+ delattr(context.kernel.args, "language")
1156
+
1157
+ if self.locale:
1158
+ assert sys.getrefcount(self.locale) <= 2
1159
+ del self.locale
1160
+ self.locale = wx.Locale(language_index)
1161
+ # wxWidgets is broken. IsOk()==false and pops up error dialog, but it translates fine!
1162
+ if self.locale.IsOk() or platform.system() == "Linux":
1163
+ self.locale.AddCatalog("meerk40t")
1164
+ else:
1165
+ self.locale = None
1166
+ context.signal("language", (lang, language_code, language_name, language_index))
1167
+
1168
+ def update_language_kernel(self, lang_code):
1169
+ context = self.context
1170
+ for i, suplang in enumerate(supported_languages):
1171
+ language_code, language_name, language_index = suplang
1172
+ if language_code != lang_code:
1173
+ continue
1174
+ context.i18n = language_code
1175
+ self.context.kernel.set_language(language_code)
1176
+ if self.locale:
1177
+ assert sys.getrefcount(self.locale) <= 2
1178
+ del self.locale
1179
+ self.locale = wx.Locale(language_index)
1180
+ # wxWidgets is broken. IsOk()==false and pops up error dialog, but it translates fine!
1181
+ if self.locale.IsOk() or platform.system() == "Linux":
1182
+ self.locale.AddCatalog("meerk40t")
1183
+ else:
1184
+ self.locale = None
1185
+ context.signal(
1186
+ "language", (i, language_code, language_name, language_index)
1187
+ )
1188
+
1189
+
1190
+ # end of class MeerK40tGui
1191
+
1192
+ MEERK40T_HOST = "dev.meerk40t.com"
1193
+
1194
+
1195
+ def send_file_to_developers(filename):
1196
+ """
1197
+ Loads a file to send data to the developers.
1198
+
1199
+ @param filename: file to send
1200
+ @return:
1201
+ """
1202
+ try:
1203
+ with open(filename) as f:
1204
+ data = f.read()
1205
+ except:
1206
+ return # There is no file, there is no data.
1207
+ send_data_to_developers(filename, data)
1208
+
1209
+
1210
+ def send_data_to_developers(filename, data):
1211
+ """
1212
+ Sends crash log to a server using rfc1341 7.2 The multipart Content-Type
1213
+ https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
1214
+
1215
+ @param filename: filename to use when sending file
1216
+ @param data: data to send
1217
+ @return:
1218
+ """
1219
+ import socket
1220
+
1221
+ host = MEERK40T_HOST # Replace with the actual host
1222
+ port = 80 # Replace with the actual port
1223
+
1224
+ # Construct the HTTP request
1225
+ boundary = "----------------meerk40t-boundary"
1226
+ body = (
1227
+ f"--{boundary}\r\n"
1228
+ f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'
1229
+ f"Content-Type: text/plain\r\n"
1230
+ "\r\n"
1231
+ f"{data}\r\n"
1232
+ f"--{boundary}--\r\n"
1233
+ )
1234
+
1235
+ headers = (
1236
+ f"POST /upload HTTP/1.1\r\n"
1237
+ f"Host: {host}\r\n"
1238
+ "User-Agent: meerk40t/1.0.0\r\n"
1239
+ f"Content-Type: multipart/form-data; boundary={boundary}\r\n"
1240
+ f"Content-Length: {len(body)}\r\n"
1241
+ "\r\n"
1242
+ )
1243
+
1244
+ try:
1245
+ # Create a socket connection
1246
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
1247
+ client_socket.connect((host, port))
1248
+
1249
+ # Send the request
1250
+ request = f"{headers}{body}"
1251
+ client_socket.sendall(request.encode())
1252
+
1253
+ # Receive and print the response
1254
+ response = client_socket.recv(4096)
1255
+ response = response.decode("utf-8", errors="ignore")
1256
+ except Exception:
1257
+ response = ""
1258
+
1259
+ response_lines = response.split("\n")
1260
+ http_code = response_lines[0]
1261
+
1262
+ print(response)
1263
+
1264
+ if http_code.startswith("HTTP/1.1 200 OK"):
1265
+ message = response_lines[-1]
1266
+ dlg = wx.MessageDialog(
1267
+ None,
1268
+ _("We got your message. Thank you for helping\n\n") + message,
1269
+ _("Thanks"),
1270
+ wx.OK,
1271
+ )
1272
+ dlg.ShowModal()
1273
+ dlg.Destroy()
1274
+ else:
1275
+ # print(response)
1276
+ MEERK40T_ISSUES = "https://github.com/meerk40t/meerk40t/issues"
1277
+ dlg = wx.MessageDialog(
1278
+ None,
1279
+ _(
1280
+ "We're sorry, that didn't work. Raise an issue on the github please.\n\n "
1281
+ "The log file will be in your working directory.\n"
1282
+ )
1283
+ + MEERK40T_ISSUES
1284
+ + "\n\n"
1285
+ + str(http_code),
1286
+ _("Thanks"),
1287
+ wx.OK,
1288
+ )
1289
+ dlg.ShowModal()
1290
+ dlg.Destroy()
1291
+
1292
+
1293
+ in_error_dialog = False
1294
+
1295
+
1296
+ def handleGUIException(exc_type, exc_value, exc_traceback):
1297
+ """
1298
+ Handler for errors. Save error to a file, and create dialog.
1299
+
1300
+ @param exc_type:
1301
+ @param exc_value:
1302
+ @param exc_traceback:
1303
+ @return:
1304
+ """
1305
+
1306
+ def _extended_dialog(caption, header, body):
1307
+ dlg = wx.Dialog(
1308
+ None,
1309
+ wx.ID_ANY,
1310
+ title=caption,
1311
+ size=wx.DefaultSize,
1312
+ pos=wx.DefaultPosition,
1313
+ style=wx.DEFAULT_DIALOG_STYLE,
1314
+ )
1315
+ # contents
1316
+ sizer = wx.BoxSizer(wx.VERTICAL)
1317
+
1318
+ label = wxStaticText(dlg, wx.ID_ANY, header)
1319
+ sizer.Add(label, 1, wx.EXPAND, 0)
1320
+ info = TextCtrl(dlg, wx.ID_ANY, style=wx.TE_MULTILINE | wx.TE_READONLY)
1321
+ info.SetValue(body)
1322
+ sizer.Add(info, 5, wx.EXPAND, 0)
1323
+ btnsizer = wx.StdDialogButtonSizer()
1324
+ btn_yes = wxButton(dlg, wx.ID_YES)
1325
+ btn_yes.SetDefault()
1326
+ btnsizer.AddButton(btn_yes)
1327
+ btn_no = wxButton(dlg, wx.ID_NO)
1328
+ btnsizer.AddButton(btn_no)
1329
+ btn_cancel = wxButton(dlg, wx.ID_CANCEL, _("Quit"))
1330
+ btnsizer.AddButton(btn_cancel)
1331
+ btnsizer.Realize()
1332
+ sizer.Add(btnsizer, 0, wx.EXPAND, 0)
1333
+ btnsizer.SetAffirmativeButton(btn_yes)
1334
+ btnsizer.SetNegativeButton(btn_no)
1335
+ btnsizer.SetCancelButton(btn_cancel)
1336
+
1337
+ def close_yes(event):
1338
+ dlg.EndModal(wx.ID_YES)
1339
+
1340
+ def close_no(event):
1341
+ dlg.EndModal(wx.ID_NO)
1342
+
1343
+ def close_cancel(event):
1344
+ wx.Abort()
1345
+
1346
+ dlg.Bind(wx.EVT_BUTTON, close_yes, btn_yes)
1347
+ dlg.Bind(wx.EVT_BUTTON, close_no, btn_no)
1348
+ dlg.Bind(wx.EVT_BUTTON, close_cancel, btn_cancel)
1349
+ dlg.SetSizer(sizer)
1350
+ sizer.Fit(dlg)
1351
+ dlg.CenterOnScreen()
1352
+ return dlg
1353
+
1354
+ def _variable_summary(vars, indent: int = 0):
1355
+ info = ""
1356
+ for name, value in vars.items():
1357
+ label = f'{" " * indent}{name} : '
1358
+ total_indent = len(label)
1359
+ formatted = str(value)
1360
+ formatted = formatted.replace("\n", "\n" + " " * total_indent)
1361
+ info += f"{label}{formatted}\n"
1362
+ return info
1363
+
1364
+ global in_error_dialog
1365
+ if in_error_dialog:
1366
+ return
1367
+ in_error_dialog = True
1368
+
1369
+ wxversion = "wx"
1370
+ try:
1371
+ wxversion = wx.version()
1372
+ except:
1373
+ pass
1374
+ filename = os.path.join(get_safe_path(APPLICATION_NAME), "_crash")
1375
+ try:
1376
+ with open(filename, "w") as file:
1377
+ file.write("MeerK40 crash indicator - you may ignore or delete it.")
1378
+ except Exception as e:
1379
+ pass
1380
+
1381
+ error_log = (
1382
+ f"MeerK40t crash log. Version: {APPLICATION_VERSION} on {platform.system()}: "
1383
+ f"Python {platform.python_version()}: {platform.machine()} - wxPython: {wxversion}\n"
1384
+ )
1385
+ error_log += "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
1386
+ variable_info = ""
1387
+ try:
1388
+ variable_info = "\nLocal variables:\n"
1389
+ tb = exc_traceback
1390
+ while tb:
1391
+ frame = tb.tb_frame
1392
+ code = frame.f_code
1393
+ source = f"{code.co_filename}:{tb.tb_lineno}, in {code.co_name}"
1394
+ variable_info += f"[{source}]:\n" + _variable_summary(frame.f_locals)
1395
+ tb = tb.tb_next
1396
+ except Exception:
1397
+ pass
1398
+ try:
1399
+ filename = f"MeerK40t-{datetime.now():%Y-%m-%d_%H_%M_%S}.txt"
1400
+ except Exception: # I already crashed once, if there's another here just ignore it.
1401
+ filename = "MeerK40t-Crash.txt"
1402
+
1403
+ try:
1404
+ try:
1405
+ with open(filename, "w", encoding="utf8") as file:
1406
+ file.write(error_log)
1407
+ if variable_info:
1408
+ file.write(variable_info)
1409
+ print(error_log)
1410
+ except PermissionError:
1411
+ filename = os.path.join(get_safe_path(APPLICATION_NAME), filename)
1412
+ with open(filename, "w", encoding="utf8") as file:
1413
+ file.write(error_log)
1414
+ if variable_info:
1415
+ file.write(variable_info)
1416
+ print(error_log)
1417
+ except Exception:
1418
+ # I already crashed once, if there's another here just ignore it.
1419
+ pass
1420
+
1421
+ # Ask to send file.
1422
+ message = _(
1423
+ """The bad news is that MeerK40t encountered a crash, and the developers apologise for this bug!
1424
+
1425
+ The good news is that you can help us fix this bug by anonymously sending us the crash details."""
1426
+ )
1427
+ message += "\n" + _(
1428
+ "Only the crash details below are sent. No data from your MeerK40t project is sent. No "
1429
+ + "personal information is sent either.\n"
1430
+ + "Send the following data to the MeerK40t team?"
1431
+ )
1432
+ caption = _("Crash Detected! Send Log?")
1433
+ data = error_log
1434
+ if variable_info:
1435
+ data += "\n" + variable_info
1436
+ try:
1437
+ dlg = _extended_dialog(caption, message, data)
1438
+ answer = dlg.ShowModal()
1439
+ dlg.Destroy()
1440
+ except Exception as e:
1441
+ answer = wx.ID_NO
1442
+ # print (answer)
1443
+ in_error_dialog = False
1444
+ if answer in (wx.ID_YES, wx.ID_OK, wx.ID_CLOSE):
1445
+ send_data_to_developers(filename, data)
1446
+ if answer == wx.ID_CANCEL:
1447
+ wx.Abort()