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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1194 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1858 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4595 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4315 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +934 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/testcases.py +105 -0
  66. meerk40t/core/elements/trace.py +651 -563
  67. meerk40t/core/elements/tree_commands.py +415 -409
  68. meerk40t/core/elements/undo_redo.py +116 -58
  69. meerk40t/core/elements/wordlist.py +319 -200
  70. meerk40t/core/exceptions.py +9 -9
  71. meerk40t/core/laserjob.py +220 -220
  72. meerk40t/core/logging.py +63 -63
  73. meerk40t/core/node/blobnode.py +83 -86
  74. meerk40t/core/node/bootstrap.py +105 -103
  75. meerk40t/core/node/branch_elems.py +40 -31
  76. meerk40t/core/node/branch_ops.py +45 -38
  77. meerk40t/core/node/branch_regmark.py +48 -41
  78. meerk40t/core/node/cutnode.py +29 -32
  79. meerk40t/core/node/effect_hatch.py +375 -257
  80. meerk40t/core/node/effect_warp.py +398 -0
  81. meerk40t/core/node/effect_wobble.py +441 -309
  82. meerk40t/core/node/elem_ellipse.py +404 -309
  83. meerk40t/core/node/elem_image.py +1082 -801
  84. meerk40t/core/node/elem_line.py +358 -292
  85. meerk40t/core/node/elem_path.py +259 -201
  86. meerk40t/core/node/elem_point.py +129 -102
  87. meerk40t/core/node/elem_polyline.py +310 -246
  88. meerk40t/core/node/elem_rect.py +376 -286
  89. meerk40t/core/node/elem_text.py +445 -418
  90. meerk40t/core/node/filenode.py +59 -40
  91. meerk40t/core/node/groupnode.py +138 -74
  92. meerk40t/core/node/image_processed.py +777 -766
  93. meerk40t/core/node/image_raster.py +156 -113
  94. meerk40t/core/node/layernode.py +31 -31
  95. meerk40t/core/node/mixins.py +135 -107
  96. meerk40t/core/node/node.py +1427 -1304
  97. meerk40t/core/node/nutils.py +117 -114
  98. meerk40t/core/node/op_cut.py +463 -335
  99. meerk40t/core/node/op_dots.py +296 -251
  100. meerk40t/core/node/op_engrave.py +414 -311
  101. meerk40t/core/node/op_image.py +755 -369
  102. meerk40t/core/node/op_raster.py +787 -522
  103. meerk40t/core/node/place_current.py +37 -40
  104. meerk40t/core/node/place_point.py +329 -126
  105. meerk40t/core/node/refnode.py +58 -47
  106. meerk40t/core/node/rootnode.py +225 -219
  107. meerk40t/core/node/util_console.py +48 -48
  108. meerk40t/core/node/util_goto.py +84 -65
  109. meerk40t/core/node/util_home.py +61 -61
  110. meerk40t/core/node/util_input.py +102 -102
  111. meerk40t/core/node/util_output.py +102 -102
  112. meerk40t/core/node/util_wait.py +65 -65
  113. meerk40t/core/parameters.py +709 -707
  114. meerk40t/core/planner.py +875 -785
  115. meerk40t/core/plotplanner.py +656 -652
  116. meerk40t/core/space.py +120 -113
  117. meerk40t/core/spoolers.py +706 -705
  118. meerk40t/core/svg_io.py +1836 -1549
  119. meerk40t/core/treeop.py +534 -445
  120. meerk40t/core/undos.py +278 -124
  121. meerk40t/core/units.py +784 -680
  122. meerk40t/core/view.py +393 -322
  123. meerk40t/core/webhelp.py +62 -62
  124. meerk40t/core/wordlist.py +513 -504
  125. meerk40t/cylinder/cylinder.py +247 -0
  126. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  127. meerk40t/cylinder/gui/gui.py +24 -0
  128. meerk40t/device/__init__.py +1 -1
  129. meerk40t/device/basedevice.py +322 -123
  130. meerk40t/device/devicechoices.py +50 -0
  131. meerk40t/device/dummydevice.py +163 -128
  132. meerk40t/device/gui/defaultactions.py +618 -602
  133. meerk40t/device/gui/effectspanel.py +114 -0
  134. meerk40t/device/gui/formatterpanel.py +253 -290
  135. meerk40t/device/gui/warningpanel.py +337 -260
  136. meerk40t/device/mixins.py +13 -13
  137. meerk40t/dxf/__init__.py +1 -1
  138. meerk40t/dxf/dxf_io.py +766 -554
  139. meerk40t/dxf/plugin.py +47 -35
  140. meerk40t/external_plugins.py +79 -79
  141. meerk40t/external_plugins_build.py +28 -28
  142. meerk40t/extra/cag.py +112 -116
  143. meerk40t/extra/coolant.py +403 -0
  144. meerk40t/extra/encode_detect.py +204 -0
  145. meerk40t/extra/ezd.py +1165 -1165
  146. meerk40t/extra/hershey.py +834 -340
  147. meerk40t/extra/imageactions.py +322 -316
  148. meerk40t/extra/inkscape.py +628 -622
  149. meerk40t/extra/lbrn.py +424 -424
  150. meerk40t/extra/outerworld.py +283 -0
  151. meerk40t/extra/param_functions.py +1542 -1556
  152. meerk40t/extra/potrace.py +257 -253
  153. meerk40t/extra/serial_exchange.py +118 -0
  154. meerk40t/extra/updater.py +602 -453
  155. meerk40t/extra/vectrace.py +147 -146
  156. meerk40t/extra/winsleep.py +83 -83
  157. meerk40t/extra/xcs_reader.py +597 -0
  158. meerk40t/fill/fills.py +781 -335
  159. meerk40t/fill/patternfill.py +1061 -1061
  160. meerk40t/fill/patterns.py +614 -567
  161. meerk40t/grbl/control.py +87 -87
  162. meerk40t/grbl/controller.py +990 -903
  163. meerk40t/grbl/device.py +1084 -768
  164. meerk40t/grbl/driver.py +989 -771
  165. meerk40t/grbl/emulator.py +532 -497
  166. meerk40t/grbl/gcodejob.py +783 -767
  167. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  168. meerk40t/grbl/gui/grblcontroller.py +485 -271
  169. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  170. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  171. meerk40t/grbl/gui/gui.py +147 -116
  172. meerk40t/grbl/interpreter.py +44 -44
  173. meerk40t/grbl/loader.py +22 -22
  174. meerk40t/grbl/mock_connection.py +56 -56
  175. meerk40t/grbl/plugin.py +294 -264
  176. meerk40t/grbl/serial_connection.py +93 -88
  177. meerk40t/grbl/tcp_connection.py +81 -79
  178. meerk40t/grbl/ws_connection.py +112 -0
  179. meerk40t/gui/__init__.py +1 -1
  180. meerk40t/gui/about.py +2042 -296
  181. meerk40t/gui/alignment.py +1644 -1608
  182. meerk40t/gui/autoexec.py +199 -0
  183. meerk40t/gui/basicops.py +791 -670
  184. meerk40t/gui/bufferview.py +77 -71
  185. meerk40t/gui/busy.py +232 -133
  186. meerk40t/gui/choicepropertypanel.py +1662 -1469
  187. meerk40t/gui/consolepanel.py +706 -542
  188. meerk40t/gui/devicepanel.py +687 -581
  189. meerk40t/gui/dialogoptions.py +110 -107
  190. meerk40t/gui/executejob.py +316 -306
  191. meerk40t/gui/fonts.py +90 -90
  192. meerk40t/gui/functionwrapper.py +252 -0
  193. meerk40t/gui/gui_mixins.py +729 -0
  194. meerk40t/gui/guicolors.py +205 -182
  195. meerk40t/gui/help_assets/help_assets.py +218 -201
  196. meerk40t/gui/helper.py +154 -0
  197. meerk40t/gui/hersheymanager.py +1440 -846
  198. meerk40t/gui/icons.py +3422 -2747
  199. meerk40t/gui/imagesplitter.py +555 -508
  200. meerk40t/gui/keymap.py +354 -344
  201. meerk40t/gui/laserpanel.py +897 -806
  202. meerk40t/gui/laserrender.py +1470 -1232
  203. meerk40t/gui/lasertoolpanel.py +805 -793
  204. meerk40t/gui/magnetoptions.py +436 -0
  205. meerk40t/gui/materialmanager.py +2944 -0
  206. meerk40t/gui/materialtest.py +1722 -1694
  207. meerk40t/gui/mkdebug.py +646 -359
  208. meerk40t/gui/mwindow.py +163 -140
  209. meerk40t/gui/navigationpanels.py +2605 -2467
  210. meerk40t/gui/notes.py +143 -142
  211. meerk40t/gui/opassignment.py +414 -410
  212. meerk40t/gui/operation_info.py +310 -299
  213. meerk40t/gui/plugin.py +500 -328
  214. meerk40t/gui/position.py +714 -669
  215. meerk40t/gui/preferences.py +901 -650
  216. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  217. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  218. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  219. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  220. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  221. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  222. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  223. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  224. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  225. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  226. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  227. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  228. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  229. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  230. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  231. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  232. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  233. meerk40t/gui/propertypanels/textproperty.py +770 -755
  234. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  235. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  236. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  237. meerk40t/gui/ribbon.py +2471 -2210
  238. meerk40t/gui/scene/scene.py +1100 -1051
  239. meerk40t/gui/scene/sceneconst.py +22 -22
  240. meerk40t/gui/scene/scenepanel.py +439 -349
  241. meerk40t/gui/scene/scenespacewidget.py +365 -365
  242. meerk40t/gui/scene/widget.py +518 -505
  243. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  244. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  245. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  246. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  247. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  248. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  249. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  250. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  251. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  252. meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
  253. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  254. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  255. meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
  256. meerk40t/gui/simpleui.py +362 -333
  257. meerk40t/gui/simulation.py +2451 -2094
  258. meerk40t/gui/snapoptions.py +208 -203
  259. meerk40t/gui/spoolerpanel.py +1227 -1180
  260. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  261. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  262. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  263. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  264. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  265. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  266. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  267. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  268. meerk40t/gui/themes.py +200 -78
  269. meerk40t/gui/tips.py +590 -0
  270. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  271. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  272. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  273. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  274. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  275. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  276. meerk40t/gui/toolwidgets/toolline.py +39 -144
  277. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  278. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  279. meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
  280. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  281. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  282. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  283. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  284. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  285. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  286. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  287. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  288. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  289. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  290. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  291. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  292. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  293. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  294. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  295. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  296. meerk40t/gui/usbconnect.py +98 -91
  297. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  298. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  299. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  300. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  301. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  302. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  303. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  304. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  305. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  306. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  307. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  308. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  309. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  310. meerk40t/gui/wordlisteditor.py +985 -931
  311. meerk40t/gui/wxmeerk40t.py +1447 -1169
  312. meerk40t/gui/wxmmain.py +5644 -4112
  313. meerk40t/gui/wxmribbon.py +1591 -1076
  314. meerk40t/gui/wxmscene.py +1631 -1453
  315. meerk40t/gui/wxmtree.py +2416 -2089
  316. meerk40t/gui/wxutils.py +1769 -1099
  317. meerk40t/gui/zmatrix.py +102 -102
  318. meerk40t/image/__init__.py +1 -1
  319. meerk40t/image/dither.py +429 -0
  320. meerk40t/image/imagetools.py +2793 -2269
  321. meerk40t/internal_plugins.py +150 -130
  322. meerk40t/kernel/__init__.py +63 -12
  323. meerk40t/kernel/channel.py +259 -212
  324. meerk40t/kernel/context.py +538 -538
  325. meerk40t/kernel/exceptions.py +41 -41
  326. meerk40t/kernel/functions.py +463 -414
  327. meerk40t/kernel/jobs.py +100 -100
  328. meerk40t/kernel/kernel.py +3828 -3571
  329. meerk40t/kernel/lifecycles.py +71 -71
  330. meerk40t/kernel/module.py +49 -49
  331. meerk40t/kernel/service.py +147 -147
  332. meerk40t/kernel/settings.py +383 -343
  333. meerk40t/lihuiyu/controller.py +883 -876
  334. meerk40t/lihuiyu/device.py +1181 -1069
  335. meerk40t/lihuiyu/driver.py +1466 -1372
  336. meerk40t/lihuiyu/gui/gui.py +127 -106
  337. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  338. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  339. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  340. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  341. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  342. meerk40t/lihuiyu/interpreter.py +53 -53
  343. meerk40t/lihuiyu/laserspeed.py +450 -450
  344. meerk40t/lihuiyu/loader.py +90 -90
  345. meerk40t/lihuiyu/parser.py +404 -404
  346. meerk40t/lihuiyu/plugin.py +101 -102
  347. meerk40t/lihuiyu/tcp_connection.py +111 -109
  348. meerk40t/main.py +231 -165
  349. meerk40t/moshi/builder.py +788 -781
  350. meerk40t/moshi/controller.py +505 -499
  351. meerk40t/moshi/device.py +495 -442
  352. meerk40t/moshi/driver.py +862 -696
  353. meerk40t/moshi/gui/gui.py +78 -76
  354. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  355. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  356. meerk40t/moshi/plugin.py +43 -43
  357. meerk40t/network/console_server.py +140 -57
  358. meerk40t/network/kernelserver.py +10 -9
  359. meerk40t/network/tcp_server.py +142 -140
  360. meerk40t/network/udp_server.py +103 -77
  361. meerk40t/network/web_server.py +404 -0
  362. meerk40t/newly/controller.py +1158 -1144
  363. meerk40t/newly/device.py +874 -732
  364. meerk40t/newly/driver.py +540 -412
  365. meerk40t/newly/gui/gui.py +219 -188
  366. meerk40t/newly/gui/newlyconfig.py +116 -101
  367. meerk40t/newly/gui/newlycontroller.py +193 -186
  368. meerk40t/newly/gui/operationproperties.py +51 -51
  369. meerk40t/newly/mock_connection.py +82 -82
  370. meerk40t/newly/newly_params.py +56 -56
  371. meerk40t/newly/plugin.py +1214 -1246
  372. meerk40t/newly/usb_connection.py +322 -322
  373. meerk40t/rotary/gui/gui.py +52 -46
  374. meerk40t/rotary/gui/rotarysettings.py +240 -232
  375. meerk40t/rotary/rotary.py +202 -98
  376. meerk40t/ruida/control.py +291 -91
  377. meerk40t/ruida/controller.py +138 -1088
  378. meerk40t/ruida/device.py +676 -231
  379. meerk40t/ruida/driver.py +534 -472
  380. meerk40t/ruida/emulator.py +1494 -1491
  381. meerk40t/ruida/exceptions.py +4 -4
  382. meerk40t/ruida/gui/gui.py +71 -76
  383. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  384. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  385. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  386. meerk40t/ruida/loader.py +54 -52
  387. meerk40t/ruida/mock_connection.py +57 -109
  388. meerk40t/ruida/plugin.py +124 -87
  389. meerk40t/ruida/rdjob.py +2084 -945
  390. meerk40t/ruida/serial_connection.py +116 -0
  391. meerk40t/ruida/tcp_connection.py +146 -0
  392. meerk40t/ruida/udp_connection.py +73 -0
  393. meerk40t/svgelements.py +9671 -9669
  394. meerk40t/tools/driver_to_path.py +584 -579
  395. meerk40t/tools/geomstr.py +5583 -4680
  396. meerk40t/tools/jhfparser.py +357 -292
  397. meerk40t/tools/kerftest.py +904 -890
  398. meerk40t/tools/livinghinges.py +1168 -1033
  399. meerk40t/tools/pathtools.py +987 -949
  400. meerk40t/tools/pmatrix.py +234 -0
  401. meerk40t/tools/pointfinder.py +942 -942
  402. meerk40t/tools/polybool.py +941 -940
  403. meerk40t/tools/rasterplotter.py +1660 -547
  404. meerk40t/tools/shxparser.py +1047 -901
  405. meerk40t/tools/ttfparser.py +726 -446
  406. meerk40t/tools/zinglplotter.py +595 -593
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
  408. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
  409. meerk40t-0.9.7020.dist-info/RECORD +446 -0
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
  412. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
  413. meerk40t/balormk/elementlightjob.py +0 -159
  414. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  415. test/bootstrap.py +0 -63
  416. test/test_cli.py +0 -12
  417. test/test_core_cutcode.py +0 -418
  418. test/test_core_elements.py +0 -144
  419. test/test_core_plotplanner.py +0 -397
  420. test/test_core_viewports.py +0 -312
  421. test/test_drivers_grbl.py +0 -108
  422. test/test_drivers_lihuiyu.py +0 -443
  423. test/test_drivers_newly.py +0 -113
  424. test/test_element_degenerate_points.py +0 -43
  425. test/test_elements_classify.py +0 -97
  426. test/test_elements_penbox.py +0 -22
  427. test/test_file_svg.py +0 -176
  428. test/test_fill.py +0 -155
  429. test/test_geomstr.py +0 -1523
  430. test/test_geomstr_nodes.py +0 -18
  431. test/test_imagetools_actualize.py +0 -306
  432. test/test_imagetools_wizard.py +0 -258
  433. test/test_kernel.py +0 -200
  434. test/test_laser_speeds.py +0 -3303
  435. test/test_length.py +0 -57
  436. test/test_lifecycle.py +0 -66
  437. test/test_operations.py +0 -251
  438. test/test_operations_hatch.py +0 -57
  439. test/test_ruida.py +0 -19
  440. test/test_spooler.py +0 -22
  441. test/test_tools_rasterplotter.py +0 -29
  442. test/test_wobble.py +0 -133
  443. test/test_zingl.py +0 -124
  444. {test → meerk40t/cylinder}/__init__.py +0 -0
  445. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  446. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
@@ -1,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