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,903 +1,990 @@
1
- """
2
- GRBL Controller
3
-
4
- Tasked with sending data to the different connection.
5
-
6
- Validation Stages.
7
- Stage 0, we are disconnected and invalid.
8
- Stage 1, we are connected and need to check if we are GRBL send $
9
- Stage 2, we parsed $ and need to try $$ $G
10
- Stage 3, we successfully parsed $$
11
- Stage 4, we successfully parsed $G, send ?
12
- Stage 5, we successfully parsed ?
13
- """
14
- import re
15
- import threading
16
- import time
17
-
18
- from meerk40t.kernel import signal_listener
19
-
20
- SETTINGS_MESSAGE = re.compile(r"^\$([0-9]+)=(.*)")
21
-
22
-
23
- def hardware_settings(code):
24
- """
25
- Given a $# code returns the parameter and the units.
26
-
27
- @param code: $$ code.
28
- @return: parameter, units
29
- """
30
- if code == 0:
31
- return 10, "step pulse time", "microseconds"
32
- if code == 1:
33
- return 25, "step idle delay", "milliseconds"
34
- if code == 2:
35
- return 0, "step pulse invert", "bitmask"
36
- if code == 3:
37
- return 0, "step direction invert", "bitmask"
38
- if code == 4:
39
- return 0, "invert step enable pin", "boolean"
40
- if code == 5:
41
- return 0, "invert limit pins", "boolean"
42
- if code == 6:
43
- return 0, "invert probe pin", "boolean"
44
- if code == 10:
45
- return 255, "status report options", "bitmask"
46
- if code == 11:
47
- return 0.010, "Junction deviation", "mm"
48
- if code == 12:
49
- return 0.002, "arc tolerance", "mm"
50
- if code == 13:
51
- return 0, "Report in inches", "boolean"
52
- if code == 20:
53
- return 0, "Soft limits enabled", "boolean"
54
- if code == 21:
55
- return 0, "hard limits enabled", "boolean"
56
- if code == 22:
57
- return 0, "Homing cycle enable", "boolean"
58
- if code == 23:
59
- return 0, "Homing direction invert", "bitmask"
60
- if code == 24:
61
- return 25.000, "Homing locate feed rate", "mm/min"
62
- if code == 25:
63
- return 500.000, "Homing search seek rate", "mm/min"
64
- if code == 26:
65
- return 250, "Homing switch debounce delay", "ms"
66
- if code == 27:
67
- return 1.000, "Homing switch pull-off distance", "mm"
68
- if code == 30:
69
- return 1000, "Maximum spindle speed", "RPM"
70
- if code == 31:
71
- return 0, "Minimum spindle speed", "RPM"
72
- if code == 32:
73
- return 1, "Laser mode enable", "boolean"
74
- if code == 100:
75
- return 250.000, "X-axis steps per millimeter", "steps"
76
- if code == 101:
77
- return 250.000, "Y-axis steps per millimeter", "steps"
78
- if code == 102:
79
- return 250.000, "Z-axis steps per millimeter", "steps"
80
- if code == 110:
81
- return 500.000, "X-axis max rate", "mm/min"
82
- if code == 111:
83
- return 500.000, "Y-axis max rate", "mm/min"
84
- if code == 112:
85
- return 500.000, "Z-axis max rate", "mm/min"
86
- if code == 120:
87
- return 10.000, "X-axis acceleration", "mm/s^2"
88
- if code == 121:
89
- return 10.000, "Y-axis acceleration", "mm/s^2"
90
- if code == 122:
91
- return 10.000, "Z-axis acceleration", "mm/s^2"
92
- if code == 130:
93
- return 200.000, "X-axis max travel", "mm"
94
- if code == 131:
95
- return 200.000, "Y-axis max travel", "mm"
96
- if code == 132:
97
- return 200.000, "Z-axis max travel", "mm"
98
-
99
-
100
- def grbl_error_code(code):
101
- long = ""
102
- short = f"Error #{code}"
103
- if code == 1:
104
- long = "GCode Command letter was not found."
105
- elif code == 2:
106
- long = "GCode Command value invalid or missing."
107
- elif code == 3:
108
- long = "Grbl '$' not recognized or supported."
109
- elif code == 4:
110
- long = "Negative value for an expected positive value."
111
- elif code == 5:
112
- long = "Homing fail. Homing not enabled in settings."
113
- elif code == 6:
114
- long = "Min step pulse must be greater than 3usec."
115
- elif code == 7:
116
- long = "EEPROM read failed. Default values used."
117
- elif code == 8:
118
- long = "Grbl '$' command Only valid when Idle."
119
- elif code == 9:
120
- long = "GCode commands invalid in alarm or jog state."
121
- elif code == 10:
122
- long = "Soft limits require homing to be enabled."
123
- elif code == 11:
124
- long = "Max characters per line exceeded. Ignored."
125
- elif code == 12:
126
- long = "Grbl '$' setting exceeds the maximum step rate."
127
- elif code == 13:
128
- long = "Safety door opened and door state initiated."
129
- elif code == 14:
130
- long = "Build info or start-up line > EEPROM line length"
131
- elif code == 15:
132
- long = "Jog target exceeds machine travel, ignored."
133
- elif code == 16:
134
- long = "Jog Cmd missing '=' or has prohibited GCode."
135
- elif code == 17:
136
- long = "Laser mode requires PWM output."
137
- elif code == 20:
138
- long = "Unsupported or invalid GCode command."
139
- elif code == 21:
140
- long = "> 1 GCode command in a modal group in block."
141
- elif code == 22:
142
- long = "Feed rate has not yet been set or is undefined."
143
- elif code == 23:
144
- long = "GCode command requires an integer value."
145
- elif code == 24:
146
- long = "> 1 GCode command using axis words found."
147
- elif code == 25:
148
- long = "Repeated GCode word found in block."
149
- elif code == 26:
150
- long = "No axis words found in command block."
151
- elif code == 27:
152
- long = "Line number value is invalid."
153
- elif code == 28:
154
- long = "GCode Cmd missing a required value word."
155
- elif code == 29:
156
- long = "G59.x WCS are not supported."
157
- elif code == 30:
158
- long = "G53 only valid with G0 and G1 motion modes."
159
- elif code == 31:
160
- long = "Unneeded Axis words found in block."
161
- elif code == 32:
162
- long = "G2/G3 arcs need >= 1 in-plane axis word."
163
- elif code == 33:
164
- long = "Motion command target is invalid."
165
- elif code == 34:
166
- long = "Arc radius value is invalid."
167
- elif code == 35:
168
- long = "G2/G3 arcs need >= 1 in-plane offset word."
169
- elif code == 36:
170
- long = "Unused value words found in block."
171
- elif code == 37:
172
- long = "G43.1 offset not assigned to tool length axis."
173
- elif code == 38:
174
- long = "Tool number greater than max value."
175
- else:
176
- long = f"Unrecodgnised error code #{code}"
177
- return short, long
178
-
179
-
180
- def grbl_alarm_message(code):
181
- if code == 1:
182
- short = "Hard limit"
183
- long = (
184
- "Hard limit has been triggered."
185
- + " Machine position is likely lost due to sudden halt."
186
- + " Re-homing is highly recommended."
187
- )
188
- elif code == 2:
189
- short = "Soft limit"
190
- long = (
191
- "Soft limit alarm. G-code motion target exceeds machine travel."
192
- + " Machine position retained. Alarm may be safely unlocked."
193
- )
194
- elif code == 3:
195
- short = "Abort during cycle"
196
- long = (
197
- "Reset while in motion. Machine position is likely lost due to sudden halt."
198
- + " Re-homing is highly recommended. May be due to issuing g-code"
199
- + " commands that exceed the limit of the machine."
200
- )
201
- elif code == 4:
202
- short = "Probe fail"
203
- long = (
204
- "Probe fail. Probe is not in the expected initial state before"
205
- + " starting probe cycle when G38.2 and G38.3 is not triggered"
206
- + " and G38.4 and G38.5 is triggered."
207
- )
208
- elif code == 5:
209
- short = "Probe fail"
210
- long = (
211
- "Probe fail. Probe did not contact the workpiece within the programmed"
212
- + " travel for G38.2 and G38.4."
213
- )
214
- elif code == 6:
215
- short = "Homing fail"
216
- long = "Homing fail. The active homing cycle was reset."
217
- elif code == 7:
218
- short = "Homing fail"
219
- long = "Homing fail. Safety door was opened during homing cycle."
220
- elif code == 8:
221
- short = "Homing fail"
222
- long = (
223
- "Homing fail. Pull off travel failed to clear limit switch."
224
- + " Try increasing pull-off setting or check wiring."
225
- )
226
- elif code == 9:
227
- short = "Homing fail"
228
- long = (
229
- "Homing fail. Could not find limit switch within search distances."
230
- + " Try increasing max travel, decreasing pull-off distance,"
231
- + " or check wiring."
232
- )
233
- else:
234
- short = f"Alarm #{code}"
235
- long = "Unknow alarm status"
236
- long += "\nTry to clear the alarm status."
237
- return short, long
238
-
239
-
240
- class GrblController:
241
- def __init__(self, context):
242
- self.service = context
243
- self.connection = None
244
- self._validation_stage = 0
245
-
246
- self.update_connection()
247
-
248
- self.driver = self.service.driver
249
-
250
- # Welcome message into, indicates the device is initialized.
251
- self.welcome = self.service.setting(str, "welcome", "Grbl")
252
-
253
- # Sending variables.
254
- self._sending_thread = None
255
- self._recving_thread = None
256
-
257
- self._forward_lock = threading.Lock()
258
- self._sending_lock = threading.Lock()
259
- self._realtime_lock = threading.Lock()
260
- self._loop_cond = threading.Condition()
261
- self._sending_queue = []
262
- self._realtime_queue = []
263
- # buffer for feedback...
264
- self._assembled_response = []
265
- self._forward_buffer = bytearray()
266
- self._device_buffer_size = self.service.planning_buffer_size
267
- self._log = None
268
-
269
- self._paused = False
270
- self._watchers = []
271
- self.is_shutdown = False
272
-
273
- def __repr__(self):
274
- return f"GRBLController('{self.service.location()}')"
275
-
276
- def __len__(self):
277
- return (
278
- len(self._sending_queue)
279
- + len(self._realtime_queue)
280
- + len(self._forward_buffer)
281
- )
282
-
283
- @property
284
- def _length_of_next_line(self):
285
- """
286
- Lookahead and provide length of the next line.
287
- @return:
288
- """
289
- if not self._sending_queue:
290
- return 0
291
- return len(self._sending_queue[0])
292
-
293
- @property
294
- def _index_of_forward_line(self):
295
- try:
296
- r = self._forward_buffer.index(b"\r")
297
- except ValueError:
298
- r = -1
299
- try:
300
- n = self._forward_buffer.index(b"\n")
301
- except ValueError:
302
- n = -1
303
-
304
- if n != -1:
305
- return min(n, r) if r != -1 else n
306
- else:
307
- return r
308
-
309
- @signal_listener("update_interface")
310
- def update_connection(self, origin=None, *args):
311
- if self.service.permit_serial and self.service.interface == "serial":
312
- try:
313
- from .serial_connection import SerialConnection
314
-
315
- self.connection = SerialConnection(self.service, self)
316
- except ImportError:
317
- pass
318
- elif self.service.permit_tcp and self.service.interface == "tcp":
319
- from meerk40t.grbl.tcp_connection import TCPOutput
320
-
321
- self.connection = TCPOutput(self.service, self)
322
- else:
323
- # Mock
324
- from .mock_connection import MockConnection
325
-
326
- self.connection = MockConnection(self.service, self)
327
-
328
- def add_watcher(self, watcher):
329
- self._watchers.append(watcher)
330
-
331
- def remove_watcher(self, watcher):
332
- self._watchers.remove(watcher)
333
-
334
- def log(self, data, type):
335
- for w in self._watchers:
336
- w(data, type=type)
337
-
338
- def _channel_log(self, data, type=None):
339
- if type == "send":
340
- if not hasattr(self, "_grbl_send"):
341
- self._grbl_send = self.service.channel(
342
- f"send-{self.service.label}", pure=True
343
- )
344
- self._grbl_send(data)
345
- elif type == "recv":
346
- if not hasattr(self, "_grbl_recv"):
347
- self._grbl_recv = self.service.channel(
348
- f"recv-{self.service.label}", pure=True
349
- )
350
- self._grbl_recv(data)
351
- elif type == "event":
352
- if not hasattr(self, "_grbl_events"):
353
- self._grbl_events = self.service.channel(f"events-{self.service.label}")
354
- self._grbl_events(data)
355
-
356
- def open(self):
357
- """
358
- Opens the connection calling connection.connect.
359
-
360
- Reads the first line this should be GRBL version and information.
361
- @return:
362
- """
363
- if self.connection.connected:
364
- return
365
- self.connection.connect()
366
- if not self.connection.connected:
367
- self.log("Could not connect.", type="event")
368
- return
369
- self.log("Connecting to GRBL...", type="event")
370
- self._validation_stage = 1
371
- self.realtime("$\r\n")
372
-
373
- def close(self):
374
- """
375
- Close the GRBL connection.
376
-
377
- @return:
378
- """
379
- if not self.connection.connected:
380
- return
381
- self.connection.disconnect()
382
- self.log("Disconnecting from GRBL...", type="event")
383
- self._validation_stage = 0
384
-
385
- def write(self, data):
386
- """
387
- Write data to the sending queue.
388
-
389
- @param data:
390
- @return:
391
- """
392
- self.start()
393
- self.service.signal("grbl;write", data)
394
- with self._sending_lock:
395
- self._sending_queue.append(data)
396
- self.service.signal(
397
- "grbl;buffer", len(self._sending_queue) + len(self._realtime_queue)
398
- )
399
- self._send_resume()
400
-
401
- def realtime(self, data):
402
- """
403
- Write data to the realtime queue.
404
-
405
- The realtime queue should preemt the regular dataqueue.
406
-
407
- @param data:
408
- @return:
409
- """
410
- self.start()
411
- self.service.signal("grbl;write", data)
412
- with self._realtime_lock:
413
- self._realtime_queue.append(data)
414
- if "\x18" in data:
415
- with self._sending_lock:
416
- self._sending_queue.clear()
417
- self.service.signal(
418
- "grbl;buffer", len(self._sending_queue) + len(self._realtime_queue)
419
- )
420
- self._send_resume()
421
-
422
- ####################
423
- # Control GRBL Sender
424
- ####################
425
-
426
- def start(self):
427
- """
428
- Starts the driver thread.
429
-
430
- @return:
431
- """
432
- self.open()
433
- if self._channel_log not in self._watchers:
434
- self.add_watcher(self._channel_log)
435
-
436
- if self._sending_thread is None:
437
- self._sending_thread = True # Avoid race condition.
438
- self._sending_thread = self.service.threaded(
439
- self._sending,
440
- thread_name=f"sender-{self.service.location()}",
441
- result=self.stop,
442
- daemon=True,
443
- )
444
- if self._recving_thread is None:
445
- self._recving_thread = True # Avoid race condition.
446
- self._recving_thread = self.service.threaded(
447
- self._recving,
448
- thread_name=f"recver-{self.service.location()}",
449
- result=self._rstop,
450
- daemon=True,
451
- )
452
-
453
- def shutdown(self):
454
- self.is_shutdown = True
455
- self._forward_buffer.clear()
456
-
457
- def _rstop(self, *args):
458
- self._recving_thread = None
459
-
460
- def stop(self, *args):
461
- """
462
- Processes the stopping of the sending queue.
463
-
464
- @param args:
465
- @return:
466
- """
467
- self._sending_thread = None
468
- self.close()
469
- self._send_resume()
470
-
471
- try:
472
- self.remove_watcher(self._channel_log)
473
- except (AttributeError, ValueError):
474
- pass
475
-
476
- ####################
477
- # GRBL SEND ROUTINES
478
- ####################
479
-
480
- def _send(self, line):
481
- """
482
- Write the line to the connection, announce it to the send channel, and add it to the forward buffer.
483
-
484
- @param line:
485
- @return:
486
- """
487
- with self._forward_lock:
488
- self._forward_buffer += bytes(line, encoding="latin-1")
489
- self.connection.write(line)
490
- self.log(line, type="send")
491
-
492
- def _sending_realtime(self):
493
- """
494
- Send one line of realtime queue.
495
-
496
- @return:
497
- """
498
- with self._realtime_lock:
499
- line = self._realtime_queue.pop(0)
500
- if "!" in line:
501
- self._paused = True
502
- if "~" in line:
503
- self._paused = False
504
- if line is not None:
505
- self._send(line)
506
- if "\x18" in line:
507
- self._paused = False
508
- with self._forward_lock:
509
- self._forward_buffer.clear()
510
-
511
- def _sending_single_line(self):
512
- """
513
- Send one line of sending queue.
514
-
515
- @return:
516
- """
517
- with self._sending_lock:
518
- line = self._sending_queue.pop(0)
519
- if line:
520
- self._send(line)
521
- self.service.signal("grbl;buffer", len(self._sending_queue))
522
- return True
523
-
524
- def _send_halt(self):
525
- """
526
- This is called internally in the _sending command.
527
- @return:
528
- """
529
- with self._loop_cond:
530
- self._loop_cond.wait()
531
-
532
- def _send_resume(self):
533
- """
534
- Other threads are expected to call this routine to permit _sending to resume.
535
-
536
- @return:
537
- """
538
- with self._loop_cond:
539
- self._loop_cond.notify()
540
-
541
- def _sending(self):
542
- """
543
- Generic sender, delegate the function according to the desired mode.
544
-
545
- This function is only run with the self.sending_thread
546
- @return:
547
-
548
- """
549
- while self.connection.connected:
550
- if self._realtime_queue:
551
- # Send realtime data.
552
- self._sending_realtime()
553
- continue
554
- if self._paused or not self.fully_validated():
555
- # We are paused or invalid. We do not send anything other than realtime commands.
556
- time.sleep(0.05)
557
- continue
558
- if not self._sending_queue:
559
- # There is nothing to write/realtime
560
- self.service.laser_status = "idle"
561
- self._send_halt()
562
- continue
563
- buffer = len(self._forward_buffer)
564
- if buffer:
565
- self.service.laser_status = "active"
566
-
567
- if self.service.buffer_mode == "sync":
568
- if buffer:
569
- # Any buffer is too much buffer. Halt.
570
- self._send_halt()
571
- continue
572
- else:
573
- # Buffered
574
- if self._device_buffer_size <= buffer + self._length_of_next_line:
575
- # Stop sending when buffer is the size of permitted buffer size.
576
- self._send_halt()
577
- continue
578
- # Go for send_line
579
- self._sending_single_line()
580
- self.service.laser_status = "idle"
581
-
582
- ####################
583
- # GRBL RECV ROUTINES
584
- ####################
585
-
586
- def get_forward_command(self):
587
- """
588
- Gets the forward command from the front of the forward buffer. This was the oldest command that the controller
589
- has not processed.
590
-
591
- @return:
592
- """
593
- q = self._index_of_forward_line
594
- if q == -1:
595
- raise ValueError("No forward command exists.")
596
- with self._forward_lock:
597
- cmd_issued = self._forward_buffer[: q + 1]
598
- self._forward_buffer = self._forward_buffer[q + 1 :]
599
- return cmd_issued
600
-
601
- def _recving(self):
602
- """
603
- Generic recver, delegate the function according to the desired mode.
604
-
605
- Read and process response from grbl.
606
-
607
- This function is only run with the self.recver_thread
608
- @return:
609
- """
610
- while self.connection.connected:
611
- # reading responses.
612
- response = None
613
- while not response:
614
- try:
615
- response = self.connection.read()
616
- except (ConnectionAbortedError, AttributeError):
617
- return
618
- if not response:
619
- time.sleep(0.01)
620
- if self.is_shutdown:
621
- return
622
- self.service.signal("grbl;response", response)
623
- self.log(response, type="recv")
624
- if response == "ok":
625
- # Indicates that the command line received was parsed and executed (or set to be executed).
626
- try:
627
- cmd_issued = self.get_forward_command()
628
- cmd_issued = cmd_issued.decode(encoding="latin-1")
629
- except ValueError as e:
630
- # We got an ok. But, had not sent anything.
631
- self.log(
632
- f"Response: {response}, but this was unexpected", type="event"
633
- )
634
- self._assembled_response = []
635
- continue
636
- # raise ConnectionAbortedError from e
637
- self.log(
638
- f"{response} / {len(self._forward_buffer)} -- {cmd_issued}",
639
- type="recv",
640
- )
641
- self.service.signal(
642
- "grbl;response", cmd_issued, self._assembled_response
643
- )
644
- self._assembled_response = []
645
- self._send_resume()
646
- continue
647
- elif response.startswith("error"):
648
- # Indicates that the command line received contained an error, with an error code x, and was purged.
649
- try:
650
- cmd_issued = self.get_forward_command()
651
- cmd_issued = cmd_issued.decode(encoding="latin-1")
652
- except ValueError as e:
653
- cmd_issued = ""
654
- try:
655
- error_num = int(response[6:])
656
- except ValueError:
657
- error_num = -1
658
- short, long = grbl_error_code(error_num)
659
- error_desc = f"#{error_num} '{cmd_issued}' {short}\n{long}"
660
- self.service.signal("grbl;error", f"GRBL: {error_desc}", response, 4)
661
- self.log(f"ERROR {error_desc}", type="recv")
662
- self._assembled_response = []
663
- self._send_resume()
664
- continue
665
- elif response.startswith("<"):
666
- self._process_status_message(response)
667
- elif response.startswith("["):
668
- self._process_feedback_message(response)
669
- continue
670
- elif response.startswith("$"):
671
- if self._validation_stage == 2:
672
- self.log("Stage 3: $$ was successfully parsed.", type="event")
673
- self._validation_stage = 3
674
- self._process_settings_message(response)
675
- continue
676
- elif response.startswith("ALARM"):
677
- try:
678
- error_num = int(response[6:])
679
- except ValueError:
680
- error_num = -1
681
- short, long = grbl_alarm_message(error_num)
682
- alarm_desc = f"#{error_num}, {short}\n{long}"
683
- self.service.signal("warning", f"GRBL: {alarm_desc}", response, 4)
684
- self.log(f"Alarm {alarm_desc}", type="recv")
685
- self._assembled_response = []
686
- elif response.startswith(">"):
687
- self.log(f"STARTUP: {response}", type="event")
688
- elif response.startswith(self.welcome):
689
- self.log("Device Reset, revalidation required", type="event")
690
- if self.fully_validated():
691
- self._validation_stage = 1
692
- self.realtime("$\r\n")
693
- else:
694
- self._assembled_response.append(response)
695
-
696
- def fully_validated(self):
697
- return self._validation_stage == 5
698
-
699
- def _process_status_message(self, response):
700
- message = response[1:-1]
701
- data = list(message.split("|"))
702
- self.service.signal("grbl:state", data[0])
703
- for datum in data[1:]:
704
- # While valid some grbl replies might violate the parsing convention.
705
- try:
706
- name, info = datum.split(":")
707
- except ValueError:
708
- continue
709
- if name == "F":
710
- self.service.signal("grbl:speed", float(info))
711
- elif name == "S":
712
- self.service.signal("grbl:power", float(info))
713
- elif name == "FS":
714
- f, s = info.split(",")
715
- self.service.signal("grbl:speed", float(f))
716
- self.service.signal("grbl:power", float(s))
717
- elif name == "MPos":
718
- coords = info.split(",")
719
- try:
720
- nx = float(coords[0])
721
- ny = float(coords[1])
722
-
723
- if not self.fully_validated():
724
- # During validation, we declare positions.
725
- self.driver.declare_position(nx, ny)
726
- ox = self.driver.mpos_x
727
- oy = self.driver.mpos_y
728
-
729
- x, y = self.service.view_mm.position(f"{nx}mm", f"{ny}mm")
730
-
731
- (
732
- self.driver.mpos_x,
733
- self.driver.mpos_y,
734
- ) = self.service.view_mm.scene_position(f"{x}mm", f"{y}mm")
735
-
736
- if len(coords) >= 3:
737
- self.driver.mpos_z = float(coords[2])
738
- self.service.signal(
739
- "status;position",
740
- (ox, oy, self.driver.mpos_x, self.driver.mpos_y),
741
- )
742
- except ValueError:
743
- pass
744
- elif name == "WPos":
745
- coords = info.split(",")
746
- self.driver.wpos_x = coords[0]
747
- self.driver.wpos_y = coords[1]
748
- if len(coords) >= 3:
749
- self.driver.wpos_z = coords[2]
750
- # See: https://github.com/grbl/grbl/blob/master/grbl/report.c#L421
751
- # MPos: Coord values. Machine Position.
752
- # WPos: MPos but with applied work coordinates. Work Position.
753
- # RX: serial rx buffer count.
754
- # Buf: plan block buffer count.
755
- # Ln: line number.
756
- # Lim: limits states
757
- # Ctl: control pins and mask (binary).
758
- self.service.signal(f"grbl:status:{name}", info)
759
- if self._validation_stage in (2, 3, 4):
760
- self.log("Connection Confirmed.", type="event")
761
- self._validation_stage = 5
762
-
763
- def _process_feedback_message(self, response):
764
- if response.startswith("[MSG:"):
765
- message = response[5:-1]
766
- self.log(message, type="event")
767
- self.service.channel("console")(message)
768
- elif response.startswith("[GC:"):
769
- # Parsing $G
770
- message = response[4:-1]
771
- states = list(message.split(" "))
772
- if not self.fully_validated():
773
- self.log("Stage 4: $G was successfully parsed.", type="event")
774
- self.driver.declare_modals(states)
775
- self._validation_stage = 4
776
- self.realtime("?\r\n")
777
- self.log(message, type="event")
778
- self.service.signal("grbl:states", states)
779
- elif response.startswith("[HLP:"):
780
- # Parsing $
781
- message = response[5:-1]
782
- if self._validation_stage == 1:
783
- self.log("Stage 2: $ was successfully parsed.", type="event")
784
- self._validation_stage = 2
785
- if "$$" in message:
786
- self.realtime("$$\r\n")
787
- if "$G" in message:
788
- self.realtime("$G\r\n")
789
- elif "?" in message:
790
- # No $G just request status.
791
- self.realtime("?\r\n")
792
- self.log(message, type="event")
793
- elif response.startswith("[G54:"):
794
- message = response[5:-1]
795
- self.service.signal("grbl:g54", message)
796
- elif response.startswith("[G55:"):
797
- message = response[5:-1]
798
- self.service.signal("grbl:g55", message)
799
- elif response.startswith("[G56:"):
800
- message = response[5:-1]
801
- self.service.signal("grbl:g56", message)
802
- elif response.startswith("[G57:"):
803
- message = response[5:-1]
804
- self.service.signal("grbl:g57", message)
805
- elif response.startswith("[G58:"):
806
- message = response[5:-1]
807
- self.service.signal("grbl:g58", message)
808
- elif response.startswith("[G59:"):
809
- message = response[5:-1]
810
- self.service.signal("grbl:g59", message)
811
- elif response.startswith("[G28:"):
812
- message = response[5:-1]
813
- self.service.signal("grbl:g28", message)
814
- elif response.startswith("[G30:"):
815
- message = response[5:-1]
816
- self.service.signal("grbl:g30", message)
817
- elif response.startswith("[G92:"):
818
- message = response[5:-1]
819
- self.service.signal("grbl:g92", message)
820
- elif response.startswith("[TLO:"):
821
- message = response[5:-1]
822
- self.service.signal("grbl:tlo", message)
823
- elif response.startswith("[PRB:"):
824
- message = response[5:-1]
825
- self.service.signal("grbl:prb", message)
826
- elif response.startswith("[VER:"):
827
- message = response[5:-1]
828
- self.service.signal("grbl:ver", message)
829
- elif response.startswith("[OPT:"):
830
- message = response[5:-1]
831
- codes, block_buffer_size, rx_buffer_size = message.split(",")
832
- self.log(f"codes: {codes}", type="event")
833
- if "V" in codes:
834
- # Variable spindle enabled
835
- pass
836
- if "N" in codes:
837
- # Line numbers enabled
838
- pass
839
-
840
- if "M" in codes:
841
- # Mist coolant enabled
842
- pass
843
- if "C" in codes:
844
- # CoreXY enabled
845
- pass
846
- if "P" in codes:
847
- # Parking motion enabled
848
- pass
849
- if "Z" in codes:
850
- # Homing force origin enabled
851
- pass
852
- if "H" in codes:
853
- # Homing single axis enabled
854
- pass
855
- if "T" in codes:
856
- # Two limit switches on axis enabled
857
- pass
858
- if "A" in codes:
859
- # Allow feed rate overrides in probe cycles
860
- pass
861
- if "*" in codes:
862
- # Restore all EEPROM disabled
863
- pass
864
- if "$" in codes:
865
- # Restore EEPROM $ settings disabled
866
- pass
867
- if "#" in codes:
868
- # Restore EEPROM parameter data disabled
869
- pass
870
- if "I" in codes:
871
- # Build info write user string disabled
872
- pass
873
- if "E" in codes:
874
- # Force sync upon EEPROM write disabled
875
- pass
876
- if "W" in codes:
877
- # Force sync upon work coordinate offset change disabled
878
- pass
879
- if "L" in codes:
880
- # Homing init lock sets Grbl into an alarm state upon power up
881
- pass
882
- if "2" in codes:
883
- # Dual axis motors with self-squaring enabled
884
- pass
885
- self.log(f"blockBufferSize: {block_buffer_size}", type="event")
886
- self.log(f"rxBufferSize: {rx_buffer_size}", type="event")
887
- self.service.signal("grbl:block_buffer", int(block_buffer_size))
888
- self.service.signal("grbl:rx_buffer", int(rx_buffer_size))
889
- self.service.signal("grbl:opt", message)
890
- elif response.startswith("[echo:"):
891
- message = response[6:-1]
892
- self.service.channel("console")(message)
893
-
894
- def _process_settings_message(self, response):
895
- match = SETTINGS_MESSAGE.match(response)
896
- if match:
897
- try:
898
- key = int(match.group(1))
899
- value = float(match.group(2))
900
- self.service.hardware_config[key] = value
901
- self.service.signal(f"grbl:hwsettings", key, value)
902
- except ValueError:
903
- pass
1
+ """
2
+ GRBL Controller
3
+
4
+ Tasked with sending data to the different connection.
5
+
6
+ Validation Stages.
7
+ Stage 0, we are disconnected and invalid.
8
+ Stage 1, we are connected and need to check if we are GRBL send $
9
+ Stage 2, we parsed $ and need to try $$ $G
10
+ Stage 3, we successfully parsed $$
11
+ Stage 4, we successfully parsed $G, send ?
12
+ Stage 5, we successfully parsed ?
13
+ """
14
+ import ast
15
+ import re
16
+ import threading
17
+ import time
18
+
19
+ from meerk40t.kernel import signal_listener
20
+
21
+ SETTINGS_MESSAGE = re.compile(r"^\$([0-9]+)=(.*)")
22
+
23
+
24
+ def hardware_settings(code):
25
+ """
26
+ Given a $# code returns the parameter and the units.
27
+
28
+ @param code: $$ code.
29
+ @return: parameter, units
30
+ """
31
+ if code == 0:
32
+ return 10, "step pulse time", "microseconds", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#0--step-pulse-microseconds"
33
+ if code == 1:
34
+ return 25, "step idle delay", "milliseconds", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#1---step-idle-delay-milliseconds"
35
+ if code == 2:
36
+ return 0, "step pulse invert", "bitmask", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#2--step-port-invert-mask"
37
+ if code == 3:
38
+ return 0, "step direction invert", "bitmask", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#3--direction-port-invert-mask"
39
+ if code == 4:
40
+ return 0, "invert step enable pin", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#4---step-enable-invert-boolean"
41
+ if code == 5:
42
+ return 0, "invert limit pins", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#5----limit-pins-invert-boolean"
43
+ if code == 6:
44
+ return 0, "invert probe pin", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#6----probe-pin-invert-boolean"
45
+ if code == 10:
46
+ return 255, "status report options", "bitmask", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#10---status-report-mask"
47
+ if code == 11:
48
+ return 0.010, "Junction deviation", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#11---junction-deviation-mm"
49
+ if code == 12:
50
+ return 0.002, "arc tolerance", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#12--arc-tolerance-mm"
51
+ if code == 13:
52
+ return 0, "Report in inches", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#13---report-inches-boolean"
53
+ if code == 20:
54
+ return 0, "Soft limits enabled", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#20---soft-limits-boolean"
55
+ if code == 21:
56
+ return 0, "hard limits enabled", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#21---hard-limits-boolean"
57
+ if code == 22:
58
+ return 0, "Homing cycle enable", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#22---homing-cycle-boolean"
59
+ if code == 23:
60
+ return 0, "Homing direction invert", "bitmask", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#23---homing-dir-invert-mask"
61
+ if code == 24:
62
+ return 25.000, "Homing locate feed rate", "mm/min", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#24---homing-feed-mmmin"
63
+ if code == 25:
64
+ return 500.000, "Homing search seek rate", "mm/min", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#25---homing-seek-mmmin"
65
+ if code == 26:
66
+ return 250, "Homing switch debounce delay", "ms", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#26---homing-debounce-milliseconds"
67
+ if code == 27:
68
+ return 1.000, "Homing switch pull-off distance", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#27---homing-pull-off-mm"
69
+ if code == 30:
70
+ return 1000, "Maximum spindle speed", "RPM", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#30---max-spindle-speed-rpm"
71
+ if code == 31:
72
+ return 0, "Minimum spindle speed", "RPM", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#31---min-spindle-speed-rpm"
73
+ if code == 32:
74
+ return 1, "Laser mode enable", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#32---laser-mode-boolean"
75
+ if code == 100:
76
+ return 250.000, "X-axis steps per millimeter", "steps", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#100-101-and-102--xyz-stepsmm"
77
+ if code == 101:
78
+ return 250.000, "Y-axis steps per millimeter", "steps", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#100-101-and-102--xyz-stepsmm"
79
+ if code == 102:
80
+ return 250.000, "Z-axis steps per millimeter", "steps", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#100-101-and-102--xyz-stepsmm"
81
+ if code == 110:
82
+ return 500.000, "X-axis max rate", "mm/min", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#110-111-and-112--xyz-max-rate-mmmin"
83
+ if code == 111:
84
+ return 500.000, "Y-axis max rate", "mm/min", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#110-111-and-112--xyz-max-rate-mmmin"
85
+ if code == 112:
86
+ return 500.000, "Z-axis max rate", "mm/min", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#110-111-and-112--xyz-max-rate-mmmin"
87
+ if code == 120:
88
+ return 10.000, "X-axis acceleration", "mm/s^2", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#120-121-122--xyz-acceleration-mmsec2"
89
+ if code == 121:
90
+ return 10.000, "Y-axis acceleration", "mm/s^2", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#120-121-122--xyz-acceleration-mmsec2"
91
+ if code == 122:
92
+ return 10.000, "Z-axis acceleration", "mm/s^2", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#120-121-122--xyz-acceleration-mmsec2"
93
+ if code == 130:
94
+ return 200.000, "X-axis max travel", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#130-131-132--xyz-max-travel-mm"
95
+ if code == 131:
96
+ return 200.000, "Y-axis max travel", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#130-131-132--xyz-max-travel-mm"
97
+ if code == 132:
98
+ return 200.000, "Z-axis max travel", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#130-131-132--xyz-max-travel-mm"
99
+
100
+
101
+ def grbl_error_code(code):
102
+ short = f"Error #{code}"
103
+ if code == 1:
104
+ long = "GCode Command letter was not found."
105
+ elif code == 2:
106
+ long = "GCode Command value invalid or missing."
107
+ elif code == 3:
108
+ long = "Grbl '$' not recognized or supported."
109
+ elif code == 4:
110
+ long = "Negative value for an expected positive value."
111
+ elif code == 5:
112
+ long = "Homing fail. Homing not enabled in settings."
113
+ elif code == 6:
114
+ long = "Min step pulse must be greater than 3usec."
115
+ elif code == 7:
116
+ long = "EEPROM read failed. Default values used."
117
+ elif code == 8:
118
+ long = "Grbl '$' command Only valid when Idle."
119
+ elif code == 9:
120
+ long = "GCode commands invalid in alarm or jog state."
121
+ elif code == 10:
122
+ long = "Soft limits require homing to be enabled."
123
+ elif code == 11:
124
+ long = "Max characters per line exceeded. Ignored."
125
+ elif code == 12:
126
+ long = "Grbl '$' setting exceeds the maximum step rate."
127
+ elif code == 13:
128
+ long = "Safety door opened and door state initiated."
129
+ elif code == 14:
130
+ long = "Build info or start-up line > EEPROM line length"
131
+ elif code == 15:
132
+ long = "Jog target exceeds machine travel, ignored."
133
+ elif code == 16:
134
+ long = "Jog Cmd missing '=' or has prohibited GCode."
135
+ elif code == 17:
136
+ long = "Laser mode requires PWM output."
137
+ elif code == 20:
138
+ long = "Unsupported or invalid GCode command."
139
+ elif code == 21:
140
+ long = "> 1 GCode command in a modal group in block."
141
+ elif code == 22:
142
+ long = "Feed rate has not yet been set or is undefined."
143
+ elif code == 23:
144
+ long = "GCode command requires an integer value."
145
+ elif code == 24:
146
+ long = "> 1 GCode command using axis words found."
147
+ elif code == 25:
148
+ long = "Repeated GCode word found in block."
149
+ elif code == 26:
150
+ long = "No axis words found in command block."
151
+ elif code == 27:
152
+ long = "Line number value is invalid."
153
+ elif code == 28:
154
+ long = "GCode Cmd missing a required value word."
155
+ elif code == 29:
156
+ long = "G59.x WCS are not supported."
157
+ elif code == 30:
158
+ long = "G53 only valid with G0 and G1 motion modes."
159
+ elif code == 31:
160
+ long = "Unneeded Axis words found in block."
161
+ elif code == 32:
162
+ long = "G2/G3 arcs need >= 1 in-plane axis word."
163
+ elif code == 33:
164
+ long = "Motion command target is invalid."
165
+ elif code == 34:
166
+ long = "Arc radius value is invalid."
167
+ elif code == 35:
168
+ long = "G2/G3 arcs need >= 1 in-plane offset word."
169
+ elif code == 36:
170
+ long = "Unused value words found in block."
171
+ elif code == 37:
172
+ long = "G43.1 offset not assigned to tool length axis."
173
+ elif code == 38:
174
+ long = "Tool number greater than max value."
175
+ else:
176
+ long = f"Unrecognised error code #{code}"
177
+ return short, long
178
+
179
+
180
+ def grbl_alarm_message(code):
181
+ if code == 1:
182
+ short = "Hard limit"
183
+ long = (
184
+ "Hard limit has been triggered."
185
+ + " Machine position is likely lost due to sudden halt."
186
+ + " Re-homing is highly recommended."
187
+ )
188
+ elif code == 2:
189
+ short = "Soft limit"
190
+ long = (
191
+ "Soft limit alarm. G-code motion target exceeds machine travel."
192
+ + " Machine position retained. Alarm may be safely unlocked."
193
+ )
194
+ elif code == 3:
195
+ short = "Abort during cycle"
196
+ long = (
197
+ "Reset while in motion. Machine position is likely lost due to sudden halt."
198
+ + " Re-homing is highly recommended. May be due to issuing g-code"
199
+ + " commands that exceed the limit of the machine."
200
+ )
201
+ elif code == 4:
202
+ short = "Probe fail"
203
+ long = (
204
+ "Probe fail. Probe is not in the expected initial state before"
205
+ + " starting probe cycle when G38.2 and G38.3 is not triggered"
206
+ + " and G38.4 and G38.5 is triggered."
207
+ )
208
+ elif code == 5:
209
+ short = "Probe fail"
210
+ long = (
211
+ "Probe fail. Probe did not contact the workpiece within the programmed"
212
+ + " travel for G38.2 and G38.4."
213
+ )
214
+ elif code == 6:
215
+ short = "Homing fail"
216
+ long = "Homing fail. The active homing cycle was reset."
217
+ elif code == 7:
218
+ short = "Homing fail"
219
+ long = "Homing fail. Safety door was opened during homing cycle."
220
+ elif code == 8:
221
+ short = "Homing fail"
222
+ long = (
223
+ "Homing fail. Pull off travel failed to clear limit switch."
224
+ + " Try increasing pull-off setting or check wiring."
225
+ )
226
+ elif code == 9:
227
+ short = "Homing fail"
228
+ long = (
229
+ "Homing fail. Could not find limit switch within search distances."
230
+ + " Try increasing max travel, decreasing pull-off distance,"
231
+ + " or check wiring."
232
+ )
233
+ else:
234
+ short = f"Alarm #{code}"
235
+ long = "Unknow alarm status"
236
+ long += "\nTry to clear the alarm status."
237
+ return short, long
238
+
239
+
240
+ class GrblController:
241
+ def __init__(self, context):
242
+ self.service = context
243
+ self.connection = None
244
+ self._validation_stage = 0
245
+
246
+ self.update_connection()
247
+
248
+ self.driver = self.service.driver
249
+
250
+ # Sending variables.
251
+ self._sending_thread = None
252
+ self._recving_thread = None
253
+
254
+ self._forward_lock = threading.Lock()
255
+ self._sending_lock = threading.Lock()
256
+ self._realtime_lock = threading.Lock()
257
+ self._loop_cond = threading.Condition()
258
+ self._sending_queue = []
259
+ self._realtime_queue = []
260
+ # buffer for feedback...
261
+ self._assembled_response = []
262
+ self._forward_buffer = bytearray()
263
+ self._device_buffer_size = self.service.planning_buffer_size
264
+ self._log = None
265
+
266
+ self._paused = False
267
+ self._watchers = []
268
+ self.is_shutdown = False
269
+
270
+ def __repr__(self):
271
+ return f"GRBLController('{self.service.location()}')"
272
+
273
+ def __len__(self):
274
+ return (
275
+ len(self._sending_queue)
276
+ + len(self._realtime_queue)
277
+ + len(self._forward_buffer)
278
+ )
279
+
280
+ @property
281
+ def _length_of_next_line(self):
282
+ """
283
+ Lookahead and provide length of the next line.
284
+ @return:
285
+ """
286
+ if not self._sending_queue:
287
+ return 0
288
+ return len(self._sending_queue[0])
289
+
290
+ @property
291
+ def _index_of_forward_line(self):
292
+ try:
293
+ r = self._forward_buffer.index(b"\r")
294
+ except ValueError:
295
+ r = -1
296
+ try:
297
+ n = self._forward_buffer.index(b"\n")
298
+ except ValueError:
299
+ n = -1
300
+
301
+ if n != -1:
302
+ return min(n, r) if r != -1 else n
303
+ else:
304
+ return r
305
+
306
+ @signal_listener("update_interface")
307
+ def update_connection(self, origin=None, *args):
308
+ if self.service.permit_serial and self.service.interface == "serial":
309
+ try:
310
+ from .serial_connection import SerialConnection
311
+
312
+ self.connection = SerialConnection(self.service, self)
313
+ except ImportError:
314
+ pass
315
+ elif self.service.permit_tcp and self.service.interface == "tcp":
316
+ from meerk40t.grbl.tcp_connection import TCPOutput
317
+
318
+ self.connection = TCPOutput(self.service, self)
319
+ elif self.service.permit_ws and self.service.interface == "ws":
320
+ from meerk40t.grbl.ws_connection import WSOutput
321
+ try:
322
+ self.connection = WSOutput(self.service, self)
323
+ except ModuleNotFoundError:
324
+ response = self.service.kernel.prompt(str, "Could not open websocket-connection (websocket installed?)")
325
+ else:
326
+ # Mock
327
+ from .mock_connection import MockConnection
328
+
329
+ self.connection = MockConnection(self.service, self)
330
+
331
+ def add_watcher(self, watcher):
332
+ self._watchers.append(watcher)
333
+
334
+ def remove_watcher(self, watcher):
335
+ self._watchers.remove(watcher)
336
+
337
+ def log(self, data, type):
338
+ for w in self._watchers:
339
+ w(data, type=type)
340
+
341
+ def _channel_log(self, data, type=None):
342
+ name = self.service.safe_label
343
+ if type == "send":
344
+ if not hasattr(self, "_grbl_send"):
345
+ self._grbl_send = self.service.channel(f"send-{name}", pure=True)
346
+ self._grbl_send(data)
347
+ elif type == "recv":
348
+ if not hasattr(self, "_grbl_recv"):
349
+ self._grbl_recv = self.service.channel(f"recv-{name}", pure=True)
350
+ self._grbl_recv(data)
351
+ elif type == "event":
352
+ if not hasattr(self, "_grbl_events"):
353
+ self._grbl_events = self.service.channel(f"events-{name}")
354
+ self._grbl_events(data)
355
+
356
+ def open(self):
357
+ """
358
+ Opens the connection calling connection.connect.
359
+
360
+ Reads the first line this should be GRBL version and information.
361
+ @return:
362
+ """
363
+ if self.connection.connected:
364
+ return
365
+ self.connection.connect()
366
+ if not self.connection.connected:
367
+ self.log("Could not connect.", type="event")
368
+ return
369
+ self.log("Connecting to GRBL...", type="event")
370
+ if self.service.reset_on_connect:
371
+ self.driver.reset()
372
+ if not self.service.require_validator:
373
+ # We are required to wait for the validation.
374
+ if self.service.boot_connect_sequence:
375
+ self._validation_stage = 1
376
+ self.validate_start("$")
377
+ else:
378
+ self._validation_stage = 5
379
+ if self.service.startup_commands:
380
+ self.log("Queue startup commands", type="event")
381
+ lines = self.service.startup_commands.split("\n")
382
+ line_end = self.service.driver.line_end
383
+ for line in lines:
384
+ if line.startswith("#"):
385
+ self.log(f"Startup: {line}", type="event")
386
+ else:
387
+ self.service.driver(f"{line}{line_end}")
388
+
389
+ def close(self):
390
+ """
391
+ Close the GRBL connection.
392
+
393
+ @return:
394
+ """
395
+ if not self.connection.connected:
396
+ return
397
+ self.connection.disconnect()
398
+ self.log("Disconnecting from GRBL...", type="event")
399
+ self.validate_stop("*")
400
+ self._validation_stage = 0
401
+
402
+ def write(self, data):
403
+ """
404
+ Write data to the sending queue.
405
+
406
+ @param data:
407
+ @return:
408
+ """
409
+ self.start()
410
+ self.service.signal("grbl;write", data)
411
+ with self._sending_lock:
412
+ self._sending_queue.append(data)
413
+ self.service.signal(
414
+ "grbl;buffer", len(self._sending_queue) + len(self._realtime_queue)
415
+ )
416
+ self._send_resume()
417
+
418
+ def realtime(self, data):
419
+ """
420
+ Write data to the realtime queue.
421
+
422
+ The realtime queue should preemt the regular dataqueue.
423
+
424
+ @param data:
425
+ @return:
426
+ """
427
+ self.start()
428
+ self.service.signal("grbl;write", data)
429
+ with self._realtime_lock:
430
+ self._realtime_queue.append(data)
431
+ if "\x18" in data:
432
+ with self._sending_lock:
433
+ self._sending_queue.clear()
434
+ self.service.signal(
435
+ "grbl;buffer", len(self._sending_queue) + len(self._realtime_queue)
436
+ )
437
+ self._send_resume()
438
+
439
+ ####################
440
+ # Control GRBL Sender
441
+ ####################
442
+
443
+ def start(self):
444
+ """
445
+ Starts the driver thread.
446
+
447
+ @return:
448
+ """
449
+ self.open()
450
+ if self._channel_log not in self._watchers:
451
+ self.add_watcher(self._channel_log)
452
+
453
+ if self._sending_thread is None or (
454
+ self._sending_thread != True and not self._sending_thread.is_alive()
455
+ ):
456
+ self._sending_thread = True # Avoid race condition.
457
+ self._sending_thread = self.service.threaded(
458
+ self._sending,
459
+ thread_name=f"sender-{self.service.location()}",
460
+ result=self.stop,
461
+ daemon=True,
462
+ )
463
+ if self._recving_thread is None or (
464
+ self._recving_thread != True and not self._recving_thread.is_alive()
465
+ ):
466
+ self._recving_thread = True # Avoid race condition.
467
+ self._recving_thread = self.service.threaded(
468
+ self._recving,
469
+ thread_name=f"recver-{self.service.location()}",
470
+ result=self._rstop,
471
+ daemon=True,
472
+ )
473
+
474
+ def shutdown(self):
475
+ self.is_shutdown = True
476
+ self._forward_buffer.clear()
477
+
478
+ def validate_start(self, cmd):
479
+ if cmd == "$":
480
+ delay = self.service.connect_delay / 1000
481
+ else:
482
+ delay = 0
483
+ name = self.service.safe_label
484
+ if delay:
485
+ self.service(f".timer 1 {delay} .gcode_realtime {cmd}")
486
+ self.service(
487
+ f".timer-{name}{cmd} 1 {delay} .timer-{name}{cmd} 0 1 gcode_realtime {cmd}"
488
+ )
489
+ else:
490
+ self.service(f".gcode_realtime {cmd}")
491
+ self.service(f".timer-{name}{cmd} 0 1 gcode_realtime {cmd}")
492
+
493
+ def validate_stop(self, cmd):
494
+ name = self.service.safe_label
495
+ if cmd == "*":
496
+ self.service(f".timer-{name}* -q --off")
497
+ return
498
+ self.service(f".timer-{name}{cmd} -q --off")
499
+ if cmd == "$":
500
+ if len(self._forward_buffer) > 3:
501
+ # If the forward planning buffer is longer than 3 it must have filled with failed attempts.
502
+ with self._forward_lock:
503
+ self._forward_buffer.clear()
504
+
505
+ def _rstop(self, *args):
506
+ self._recving_thread = None
507
+
508
+ def stop(self, *args):
509
+ """
510
+ Processes the stopping of the sending queue.
511
+
512
+ @param args:
513
+ @return:
514
+ """
515
+ self._sending_thread = None
516
+ self.close()
517
+ self._send_resume()
518
+
519
+ try:
520
+ self.remove_watcher(self._channel_log)
521
+ except (AttributeError, ValueError):
522
+ pass
523
+
524
+ ####################
525
+ # GRBL SEND ROUTINES
526
+ ####################
527
+
528
+ def _send(self, line):
529
+ """
530
+ Write the line to the connection, announce it to the send channel, and add it to the forward buffer.
531
+
532
+ @param line:
533
+ @return:
534
+ """
535
+ with self._forward_lock:
536
+ self._forward_buffer += bytes(line, encoding="latin-1")
537
+ self.connection.write(line)
538
+ self.log(line, type="send")
539
+
540
+ def _sending_realtime(self):
541
+ """
542
+ Send one line of realtime queue.
543
+
544
+ @return:
545
+ """
546
+ with self._realtime_lock:
547
+ line = self._realtime_queue.pop(0)
548
+ if "!" in line:
549
+ self._paused = True
550
+ if "~" in line:
551
+ self._paused = False
552
+ if line is not None:
553
+ self._send(line)
554
+ if "\x18" in line:
555
+ self._paused = False
556
+ with self._forward_lock:
557
+ self._forward_buffer.clear()
558
+
559
+ def _sending_single_line(self):
560
+ """
561
+ Send one line of sending queue.
562
+
563
+ @return:
564
+ """
565
+ with self._sending_lock:
566
+ line = self._sending_queue.pop(0)
567
+ if line:
568
+ self._send(line)
569
+ self.service.signal("grbl;buffer", len(self._sending_queue))
570
+ return True
571
+
572
+ def _send_halt(self):
573
+ """
574
+ This is called internally in the _sending command.
575
+ @return:
576
+ """
577
+ with self._loop_cond:
578
+ self._loop_cond.wait()
579
+
580
+ def _send_resume(self):
581
+ """
582
+ Other threads are expected to call this routine to permit _sending to resume.
583
+
584
+ @return:
585
+ """
586
+ with self._loop_cond:
587
+ self._loop_cond.notify()
588
+
589
+ def _sending(self):
590
+ """
591
+ Generic sender, delegate the function according to the desired mode.
592
+
593
+ This function is only run with the self.sending_thread
594
+ @return:
595
+
596
+ """
597
+ while self.connection.connected:
598
+ if self._realtime_queue:
599
+ # Send realtime data.
600
+ self._sending_realtime()
601
+ continue
602
+ if self._paused or not self.fully_validated():
603
+ # We are paused or invalid. We do not send anything other than realtime commands.
604
+ time.sleep(0.05)
605
+ continue
606
+ if not self._sending_queue:
607
+ # There is nothing to write/realtime
608
+ self.service.laser_status = "idle"
609
+ self._send_halt()
610
+ continue
611
+ buffer = len(self._forward_buffer)
612
+ if buffer:
613
+ self.service.laser_status = "active"
614
+
615
+ if self.service.buffer_mode == "sync":
616
+ if buffer:
617
+ # Any buffer is too much buffer. Halt.
618
+ self._send_halt()
619
+ continue
620
+ else:
621
+ # Buffered
622
+ if self._device_buffer_size <= buffer + self._length_of_next_line:
623
+ # Stop sending when buffer is the size of permitted buffer size.
624
+ self._send_halt()
625
+ continue
626
+ # Go for send_line
627
+ self._sending_single_line()
628
+ self.service.laser_status = "idle"
629
+
630
+ ####################
631
+ # GRBL RECV ROUTINES
632
+ ####################
633
+
634
+ def get_forward_command(self):
635
+ """
636
+ Gets the forward command from the front of the forward buffer. This was the oldest command that the controller
637
+ has not processed.
638
+
639
+ @return:
640
+ """
641
+ q = self._index_of_forward_line
642
+ if q == -1:
643
+ raise ValueError("No forward command exists.")
644
+ with self._forward_lock:
645
+ cmd_issued = self._forward_buffer[: q + 1]
646
+ self._forward_buffer = self._forward_buffer[q + 1 :]
647
+ return cmd_issued
648
+
649
+ def _recving(self):
650
+ """
651
+ Generic recver, delegate the function according to the desired mode.
652
+
653
+ Read and process response from grbl.
654
+
655
+ This function is only run with the self.recver_thread
656
+ @return:
657
+ """
658
+ while self.connection.connected:
659
+ # reading responses.
660
+ response = None
661
+ while not response:
662
+ try:
663
+ response = self.connection.read()
664
+ except (ConnectionAbortedError, AttributeError):
665
+ return
666
+ if not response:
667
+ time.sleep(0.01)
668
+ if self.is_shutdown:
669
+ return
670
+ self.service.signal("grbl;response", response)
671
+ self.log(response, type="recv")
672
+ if response == "ok":
673
+ # Indicates that the command line received was parsed and executed (or set to be executed).
674
+ try:
675
+ cmd_issued = self.get_forward_command()
676
+ cmd_issued = cmd_issued.decode(encoding="latin-1")
677
+ except ValueError:
678
+ # We got an ok. But, had not sent anything.
679
+ self.log(
680
+ f"Response: {response}, but this was unexpected", type="event"
681
+ )
682
+ self._assembled_response = []
683
+ continue
684
+ # raise ConnectionAbortedError from e
685
+ self.log(
686
+ f"{response} / {len(self._forward_buffer)} -- {cmd_issued}",
687
+ type="recv",
688
+ )
689
+ self.service.signal(
690
+ "grbl;response", cmd_issued, self._assembled_response
691
+ )
692
+ self._assembled_response = []
693
+ self._send_resume()
694
+ elif response.startswith("error"):
695
+ # Indicates that the command line received contained an error, with an error code x, and was purged.
696
+ try:
697
+ cmd_issued = self.get_forward_command()
698
+ cmd_issued = cmd_issued.decode(encoding="latin-1")
699
+ except ValueError:
700
+ cmd_issued = ""
701
+ try:
702
+ error_num = int(response[6:])
703
+ except ValueError:
704
+ error_num = -1
705
+ short, long = grbl_error_code(error_num)
706
+ error_desc = f"#{error_num} '{cmd_issued}' {short}\n{long}"
707
+ self.service.signal("grbl;error", f"GRBL: {error_desc}", response, 4)
708
+ self.log(f"ERROR {error_desc}", type="recv")
709
+ self._assembled_response = []
710
+ self._send_resume()
711
+ continue
712
+ elif response.startswith("<"):
713
+ self._process_status_message(response)
714
+ elif response.startswith("["):
715
+ self._process_feedback_message(response)
716
+ continue
717
+ elif response.startswith("$"):
718
+ if self._validation_stage == 2:
719
+ self.log("Stage 3: $$ was successfully parsed.", type="event")
720
+ self.validate_stop("$$")
721
+ self._validation_stage = 3
722
+ self._process_settings_message(response)
723
+ elif response.startswith("Alarm|"):
724
+ # There's no errorcode
725
+ error_num = 1
726
+ short, long = grbl_alarm_message(error_num)
727
+ alarm_desc = f"#{error_num}, {short}\n{long}"
728
+ self.service.signal("warning", f"GRBL: {alarm_desc}", response, 4)
729
+ self.log(f"Alarm {alarm_desc}", type="recv")
730
+ self._assembled_response = []
731
+
732
+ elif response.startswith("ALARM"):
733
+ try:
734
+ error_num = int(response[6:])
735
+ except ValueError:
736
+ error_num = -1
737
+ short, long = grbl_alarm_message(error_num)
738
+ alarm_desc = f"#{error_num}, {short}\n{long}"
739
+ self.service.signal("warning", f"GRBL: {alarm_desc}", response, 4)
740
+ self.log(f"Alarm {alarm_desc}", type="recv")
741
+ self._assembled_response = []
742
+ elif response.startswith(">"):
743
+ self.log(f"STARTUP: {response}", type="event")
744
+ elif response.startswith(self.service.welcome):
745
+ if not self.service.require_validator:
746
+ # Validation is not required, we reboot.
747
+ if self.fully_validated():
748
+ if self.service.boot_connect_sequence:
749
+ # Boot sequence is required. Restart sequence.
750
+ self.log(
751
+ "Device Reset, revalidation required", type="event"
752
+ )
753
+ self._validation_stage = 1
754
+ self.validate_start("$")
755
+ else:
756
+ # Validation is required. This was stage 0.
757
+ if self.service.boot_connect_sequence:
758
+ # Boot sequence is required. Restart sequence.
759
+ self._validation_stage = 1
760
+ self.validate_start("$")
761
+ else:
762
+ # No boot sequence required. Declare fully connected.
763
+ self._validation_stage = 5
764
+ else:
765
+ self._assembled_response.append(response)
766
+
767
+ def fully_validated(self):
768
+ return self._validation_stage == 5
769
+
770
+ def force_validate(self):
771
+ self._validation_stage = 5
772
+ self.validate_stop("*")
773
+
774
+ def _process_status_message(self, response):
775
+ message = response[1:-1]
776
+ data = list(message.split("|"))
777
+ self.service.signal("grbl:state", data[0])
778
+ for datum in data[1:]:
779
+ # While valid some grbl replies might violate the parsing convention.
780
+ try:
781
+ name, info = datum.split(":")
782
+ except ValueError:
783
+ continue
784
+ if name == "F":
785
+ self.service.signal("grbl:speed", float(info))
786
+ elif name == "S":
787
+ self.service.signal("grbl:power", float(info))
788
+ elif name == "FS":
789
+ f, s = info.split(",")
790
+ self.service.signal("grbl:speed", float(f))
791
+ self.service.signal("grbl:power", float(s))
792
+ elif name == "MPos":
793
+ coords = info.split(",")
794
+ try:
795
+ nx = float(coords[0])
796
+ ny = float(coords[1])
797
+
798
+ if not self.fully_validated():
799
+ # During validation, we declare positions.
800
+ self.driver.declare_position(nx, ny)
801
+ ox = self.driver.mpos_x
802
+ oy = self.driver.mpos_y
803
+
804
+ x, y = self.service.view_mm.position(f"{nx}mm", f"{ny}mm")
805
+
806
+ (
807
+ self.driver.mpos_x,
808
+ self.driver.mpos_y,
809
+ ) = self.service.view_mm.scene_position(f"{x}mm", f"{y}mm")
810
+
811
+ if len(coords) >= 3:
812
+ self.driver.mpos_z = float(coords[2])
813
+ self.service.signal(
814
+ "status;position",
815
+ (ox, oy, self.driver.mpos_x, self.driver.mpos_y),
816
+ )
817
+ except ValueError:
818
+ pass
819
+ elif name == "WPos":
820
+ coords = info.split(",")
821
+ self.driver.wpos_x = coords[0]
822
+ self.driver.wpos_y = coords[1]
823
+ if len(coords) >= 3:
824
+ self.driver.wpos_z = coords[2]
825
+ # See: https://github.com/grbl/grbl/blob/master/grbl/report.c#L421
826
+ # MPos: Coord values. Machine Position.
827
+ # WPos: MPos but with applied work coordinates. Work Position.
828
+ # RX: serial rx buffer count.
829
+ # Buf: plan block buffer count.
830
+ # Ln: line number.
831
+ # Lim: limits states
832
+ # Ctl: control pins and mask (binary).
833
+ self.service.signal(f"grbl:status:{name}", info)
834
+ if self._validation_stage in (2, 3, 4):
835
+ self.log("Connection Confirmed.", type="event")
836
+ self._validation_stage = 5
837
+ self.validate_stop("*")
838
+
839
+ def _process_feedback_message(self, response):
840
+ if response.startswith("[MSG:"):
841
+ message = response[5:-1]
842
+ self.log(message, type="event")
843
+ self.service.channel("console")(message)
844
+ elif response.startswith("[GC:"):
845
+ # Parsing $G
846
+ message = response[4:-1]
847
+ states = list(message.split(" "))
848
+ if not self.fully_validated():
849
+ self.log("Stage 4: $G was successfully parsed.", type="event")
850
+ self.driver.declare_modals(states)
851
+ self._validation_stage = 4
852
+ self.validate_stop("$G")
853
+ self.validate_start("?")
854
+ self.log(message, type="event")
855
+ self.service.signal("grbl:states", states)
856
+ elif response.startswith("[HLP:"):
857
+ # Parsing $
858
+ message = response[5:-1]
859
+ if self._validation_stage == 1:
860
+ self.log("Stage 2: $ was successfully parsed.", type="event")
861
+ self._validation_stage = 2
862
+ self.validate_stop("$")
863
+ if "$$" in message:
864
+ self.validate_start("$$")
865
+ if "$G" in message:
866
+ self.validate_start("$G")
867
+ elif "?" in message:
868
+ # No $G just request status.
869
+ self.validate_start("?")
870
+ self.log(message, type="event")
871
+ elif response.startswith("[G54:"):
872
+ message = response[5:-1]
873
+ self.service.signal("grbl:g54", message)
874
+ elif response.startswith("[G55:"):
875
+ message = response[5:-1]
876
+ self.service.signal("grbl:g55", message)
877
+ elif response.startswith("[G56:"):
878
+ message = response[5:-1]
879
+ self.service.signal("grbl:g56", message)
880
+ elif response.startswith("[G57:"):
881
+ message = response[5:-1]
882
+ self.service.signal("grbl:g57", message)
883
+ elif response.startswith("[G58:"):
884
+ message = response[5:-1]
885
+ self.service.signal("grbl:g58", message)
886
+ elif response.startswith("[G59:"):
887
+ message = response[5:-1]
888
+ self.service.signal("grbl:g59", message)
889
+ elif response.startswith("[G28:"):
890
+ message = response[5:-1]
891
+ self.service.signal("grbl:g28", message)
892
+ elif response.startswith("[G30:"):
893
+ message = response[5:-1]
894
+ self.service.signal("grbl:g30", message)
895
+ elif response.startswith("[G92:"):
896
+ message = response[5:-1]
897
+ self.service.signal("grbl:g92", message)
898
+ elif response.startswith("[TLO:"):
899
+ message = response[5:-1]
900
+ self.service.signal("grbl:tlo", message)
901
+ elif response.startswith("[PRB:"):
902
+ message = response[5:-1]
903
+ self.service.signal("grbl:prb", message)
904
+ elif response.startswith("[VER:"):
905
+ message = response[5:-1]
906
+ self.service.signal("grbl:ver", message)
907
+ elif response.startswith("[OPT:"):
908
+ message = response[5:-1]
909
+ opts = list(message.split(","))
910
+ codes = opts[0]
911
+ block_buffer_size = opts[1]
912
+ rx_buffer_size = opts[2]
913
+ self.log(f"codes: {codes}", type="event")
914
+ if "V" in codes:
915
+ # Variable spindle enabled
916
+ pass
917
+ if "N" in codes:
918
+ # Line numbers enabled
919
+ pass
920
+
921
+ if "M" in codes:
922
+ # Mist coolant enabled
923
+ pass
924
+ if "C" in codes:
925
+ # CoreXY enabled
926
+ pass
927
+ if "P" in codes:
928
+ # Parking motion enabled
929
+ pass
930
+ if "Z" in codes:
931
+ # Homing force origin enabled
932
+ pass
933
+ if "H" in codes:
934
+ # Homing single axis enabled
935
+ pass
936
+ if "T" in codes:
937
+ # Two limit switches on axis enabled
938
+ pass
939
+ if "A" in codes:
940
+ # Allow feed rate overrides in probe cycles
941
+ pass
942
+ if "*" in codes:
943
+ # Restore all EEPROM disabled
944
+ pass
945
+ if "$" in codes:
946
+ # Restore EEPROM $ settings disabled
947
+ pass
948
+ if "#" in codes:
949
+ # Restore EEPROM parameter data disabled
950
+ pass
951
+ if "I" in codes:
952
+ # Build info write user string disabled
953
+ pass
954
+ if "E" in codes:
955
+ # Force sync upon EEPROM write disabled
956
+ pass
957
+ if "W" in codes:
958
+ # Force sync upon work coordinate offset change disabled
959
+ pass
960
+ if "L" in codes:
961
+ # Homing init lock sets Grbl into an alarm state upon power up
962
+ pass
963
+ if "2" in codes:
964
+ # Dual axis motors with self-squaring enabled
965
+ pass
966
+ self.log(f"blockBufferSize: {block_buffer_size}", type="event")
967
+ self.log(f"rxBufferSize: {rx_buffer_size}", type="event")
968
+ self.service.signal("grbl:block_buffer", int(block_buffer_size))
969
+ self.service.signal("grbl:rx_buffer", int(rx_buffer_size))
970
+ self.service.signal("grbl:opt", message)
971
+ elif response.startswith("[echo:"):
972
+ message = response[6:-1]
973
+ self.service.channel("console")(message)
974
+
975
+ def _process_settings_message(self, response):
976
+ match = SETTINGS_MESSAGE.match(response)
977
+ if match:
978
+ try:
979
+ key = int(match.group(1))
980
+ value = match.group(2)
981
+ try:
982
+ value = ast.literal_eval(value)
983
+ except SyntaxError:
984
+ # GRBLHal can have things like "", and "Grbl" and "192.168.1.39" in the settings.
985
+ pass
986
+
987
+ self.service.hardware_config[key] = value
988
+ self.service.signal("grbl:hwsettings", key, value)
989
+ except ValueError:
990
+ pass