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

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