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,1372 +1,1466 @@
1
- """
2
- Lihuiyu Driver
3
-
4
- Governs the generic commands issued by laserjob and spooler and converts that into regular LHYMicro-GL output.
5
-
6
- This generated data is then sent to the controller, which could be a network connection, usb, or mock depending on the
7
- selected output.
8
- """
9
-
10
- import math
11
- import time
12
-
13
- from meerk40t.tools.zinglplotter import ZinglPlotter
14
-
15
- from ..core.cutcode.dwellcut import DwellCut
16
- from ..core.cutcode.gotocut import GotoCut
17
- from ..core.cutcode.homecut import HomeCut
18
- from ..core.cutcode.inputcut import InputCut
19
- from ..core.cutcode.outputcut import OutputCut
20
- from ..core.cutcode.plotcut import PlotCut
21
- from ..core.cutcode.waitcut import WaitCut
22
- from ..core.parameters import Parameters
23
- from ..core.plotplanner import PlotPlanner, grouped
24
- from ..device.basedevice import (
25
- DRIVER_STATE_FINISH,
26
- DRIVER_STATE_MODECHANGE,
27
- DRIVER_STATE_PROGRAM,
28
- DRIVER_STATE_RAPID,
29
- DRIVER_STATE_RASTER,
30
- PLOT_AXIS,
31
- PLOT_DIRECTION,
32
- PLOT_FINISH,
33
- PLOT_JOG,
34
- PLOT_RAPID,
35
- PLOT_SETTING,
36
- )
37
- from .laserspeed import LaserSpeed
38
-
39
- distance_lookup = [
40
- b"",
41
- b"a",
42
- b"b",
43
- b"c",
44
- b"d",
45
- b"e",
46
- b"f",
47
- b"g",
48
- b"h",
49
- b"i",
50
- b"j",
51
- b"k",
52
- b"l",
53
- b"m",
54
- b"n",
55
- b"o",
56
- b"p",
57
- b"q",
58
- b"r",
59
- b"s",
60
- b"t",
61
- b"u",
62
- b"v",
63
- b"w",
64
- b"x",
65
- b"y",
66
- b"|a",
67
- b"|b",
68
- b"|c",
69
- b"|d",
70
- b"|e",
71
- b"|f",
72
- b"|g",
73
- b"|h",
74
- b"|i",
75
- b"|j",
76
- b"|k",
77
- b"|l",
78
- b"|m",
79
- b"|n",
80
- b"|o",
81
- b"|p",
82
- b"|q",
83
- b"|r",
84
- b"|s",
85
- b"|t",
86
- b"|u",
87
- b"|v",
88
- b"|w",
89
- b"|x",
90
- b"|y",
91
- b"|z",
92
- ]
93
-
94
-
95
- def lhymicro_distance(v):
96
- if v < 0:
97
- raise ValueError("Cannot permit negative values.")
98
- dist = b""
99
- if v >= 255:
100
- zs = int(v / 255)
101
- v %= 255
102
- dist += b"z" * zs
103
- if v >= 52:
104
- return dist + b"%03d" % v
105
- return dist + distance_lookup[v]
106
-
107
-
108
- class LihuiyuDriver(Parameters):
109
- """
110
- LihuiyuDriver provides Lihuiyu specific coding for elements and sends it to the backend
111
- to write to the usb.
112
- """
113
-
114
- def __init__(self, service, *args, **kwargs):
115
- super().__init__()
116
- self.service = service
117
- self.name = str(self.service)
118
- self._topward = False
119
- self._leftward = False
120
- self._x_engaged = False
121
- self._y_engaged = False
122
- self._horizontal_major = False
123
-
124
- self._request_leftward = None
125
- self._request_topward = None
126
- self._request_horizontal_major = None
127
-
128
- self.out_pipe = None
129
-
130
- self.process_item = None
131
- self.spooled_item = None
132
- self.holds = []
133
- self.temp_holds = []
134
-
135
- self.native_x = 0
136
- self.native_y = 0
137
-
138
- self.plot_planner = PlotPlanner(self.settings)
139
- self.plot_attribute_update()
140
-
141
- self.plot_data = None
142
-
143
- self.state = DRIVER_STATE_RAPID
144
- self.properties = 0
145
- self.is_relative = False
146
- self.laser = False
147
- self._thread = None
148
- self._shutdown = False
149
- self.last_fetch = None
150
-
151
- self.CODE_RIGHT = b"B"
152
- self.CODE_LEFT = b"T"
153
- self.CODE_TOP = b"L"
154
- self.CODE_BOTTOM = b"R"
155
- self.CODE_ANGLE = b"M"
156
- self.CODE_LASER_ON = b"D"
157
- self.CODE_LASER_OFF = b"U"
158
-
159
- self.paused = False
160
-
161
- def primary_hold():
162
- if self.out_pipe is None:
163
- return True
164
- if hasattr(self.out_pipe, "is_shutdown") and self.out_pipe.is_shutdown:
165
- raise ConnectionAbortedError("Cannot hold for a shutdown pipe.")
166
- try:
167
- buffer = len(self.out_pipe)
168
- except TypeError:
169
- buffer = 0
170
- return self.service.buffer_limit and buffer > self.service.buffer_max
171
-
172
- self.holds.append(primary_hold)
173
-
174
- # Step amount expected of the current operation
175
- self._raster_step_float = 0
176
-
177
- # Step amount is the current correctly set step amount in the controller.
178
- self._raster_step_g_value = 0
179
-
180
- # Step index of the current step taken for unidirectional
181
- self._raster_step_swing_index = 0
182
-
183
- # Step total the count for fractional step amounts
184
- self._raster_step_fractional_remainder = 0.0
185
-
186
- def __repr__(self):
187
- return f"LihuiyuDriver({self.name})"
188
-
189
- def __call__(self, e):
190
- self.out_pipe.write(e)
191
-
192
- def plot_attribute_update(self):
193
- self.plot_planner.force_shift = self.service.plot_shift
194
- self.plot_planner.phase_type = self.service.plot_phase_type
195
- self.plot_planner.phase_value = self.service.plot_phase_value
196
-
197
- def hold_work(self, priority):
198
- """
199
- Holds are criteria to use to pause the data interpretation. These halt the production of new data until the
200
- criteria is met. A hold is constant and will always halt the data while true. A temp_hold will be removed
201
- as soon as it does not hold the data.
202
-
203
- @return: Whether data interpretation should hold.
204
- """
205
- if priority > 0:
206
- # Don't hold realtime work.
207
- return False
208
-
209
- temp_hold = False
210
- fail_hold = False
211
- for i, hold in enumerate(self.temp_holds):
212
- if not hold():
213
- self.temp_holds[i] = None
214
- fail_hold = True
215
- else:
216
- temp_hold = True
217
- if fail_hold:
218
- self.temp_holds = [hold for hold in self.temp_holds if hold is not None]
219
- if temp_hold:
220
- return True
221
- for hold in self.holds:
222
- if hold():
223
- return True
224
- return False
225
-
226
- def get(self, key, default=None):
227
- """
228
- Required.
229
-
230
- @param key: Key to get.
231
- @param default: Default value to use.
232
- @return:
233
- """
234
- return self.settings.get(key, default=default)
235
-
236
- def set(self, key, value):
237
- """
238
- Required.
239
-
240
- Sets a laser parameter this could be speed, power, wobble, number_of_unicorns, or any unknown parameters for
241
- yet to be written drivers.
242
-
243
- @param key:
244
- @param value:
245
- @return:
246
- """
247
- if key == "power":
248
- self._set_power(value)
249
- elif key == "ppi":
250
- self._set_power(value)
251
- elif key == "pwm":
252
- self._set_power(value)
253
- elif key == "overscan":
254
- self._set_overscan(value)
255
- elif key == "acceleration":
256
- self._set_acceleration(value)
257
- elif key == "relative":
258
- self.is_relative = value
259
- elif key == "d_ratio":
260
- self._set_d_ratio(value)
261
- elif key == "step":
262
- self._set_step(*value)
263
- else:
264
- self.settings[key] = value
265
-
266
- def status(self):
267
- """
268
- Wants a status report of what the driver is doing.
269
- @return:
270
- """
271
- state_major = "idle"
272
- state_minor = "idle"
273
- if self.state == DRIVER_STATE_RAPID:
274
- state_major = "idle"
275
- state_minor = "idle"
276
- elif self.state == DRIVER_STATE_FINISH:
277
- state_major = "idle"
278
- state_minor = "finished"
279
- elif self.state == DRIVER_STATE_PROGRAM:
280
- state_major = "busy"
281
- state_minor = "program"
282
- elif self.state == DRIVER_STATE_RASTER:
283
- state_major = "busy"
284
- state_minor = "raster"
285
- elif self.state == DRIVER_STATE_MODECHANGE:
286
- state_major = "busy"
287
- state_minor = "changing"
288
- return (self.native_x, self.native_y), state_major, state_minor
289
-
290
- def pause(self, *values):
291
- """
292
- Asks that the laser be paused.
293
-
294
- @return:
295
- """
296
- self(b"~PN!\n~")
297
- self.paused = True
298
- self.service.signal("pause")
299
-
300
- def resume(self, *values):
301
- """
302
- Asks that the laser be resumed.
303
-
304
- To work this command should usually be put into the realtime work queue for the laser, without that it will
305
- be paused and unable to process the resume.
306
-
307
- @param values:
308
- @return:
309
- """
310
- self(b"~PN&\n~")
311
- self.paused = False
312
- self.service.signal("pause")
313
-
314
- def reset(self):
315
- """
316
- This command asks that this device be emergency stopped and reset. Usually that queue data from the spooler be
317
- deleted.
318
-
319
- Asks that the device resets, and clears all current work.
320
-
321
- @return:
322
- """
323
- self.service.spooler.clear_queue()
324
- self.plot_planner.clear()
325
- self.spooled_item = None
326
- self.temp_holds.clear()
327
-
328
- self.service.signal("pipe;buffer", 0)
329
- self(b"~I*\n~")
330
- self._reset_modes()
331
- self.state = DRIVER_STATE_RAPID
332
- self.paused = False
333
- self.service.signal("pause")
334
-
335
- def abort(self):
336
- self(b"I\n")
337
-
338
- def blob(self, blob_type, data):
339
- """
340
- Blob sends a data blob. This is native code data of the give type. For example in a ruida device it might be a
341
- bunch of .rd code, or Lihuiyu device it could be .egv code. It's a method of sending pre-chewed data to the
342
- device.
343
-
344
- @param blob_type:
345
- @param data:
346
- @return:
347
- """
348
- if blob_type == "egv":
349
- self(data)
350
-
351
- def move_abs(self, x, y):
352
- """
353
- Requests laser move to absolute position x, y in physical units
354
-
355
- @param x:
356
- @param y:
357
- @return:
358
- """
359
- x, y = self.service.view.position(x, y)
360
- self.rapid_mode()
361
- self._move_absolute(int(round(x)), int(round(y)))
362
-
363
- def move_rel(self, dx, dy):
364
- """
365
- Requests laser move relative position dx, dy in physical units
366
-
367
- @param dx:
368
- @param dy:
369
- @return:
370
- """
371
- unit_dx, unit_dy = self.service.view.position(dx, dy, vector=True)
372
- self.rapid_mode()
373
- self._move_relative(unit_dx, unit_dy)
374
-
375
- def dwell(self, time_in_ms):
376
- """
377
- Requests that the laser fire in place for the given time period. This could be done in a series of commands,
378
- move to a location, turn laser on, wait, turn laser off. However, some drivers have specific laser-in-place
379
- commands so calling dwell is preferred.
380
-
381
- @param time_in_ms:
382
- @return:
383
- """
384
- self.rapid_mode()
385
- self.wait_finish()
386
- self.laser_on() # This can't be sent early since these are timed operations.
387
- self.wait(time_in_ms)
388
- self.laser_off()
389
-
390
- def laser_off(self):
391
- """
392
- Turn laser off in place.
393
-
394
- @return:
395
- """
396
- if not self.laser:
397
- return False
398
- if self.state == DRIVER_STATE_RAPID:
399
- self(b"I")
400
- self(self.CODE_LASER_OFF)
401
- self(b"S1P\n")
402
- if not self.service.autolock:
403
- self(b"IS2P\n")
404
- elif self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
405
- self(self.CODE_LASER_OFF)
406
- elif self.state == DRIVER_STATE_FINISH:
407
- self(self.CODE_LASER_OFF)
408
- self(b"N")
409
- self.laser = False
410
- return True
411
-
412
- def laser_on(self):
413
- """
414
- Turn laser on in place.
415
-
416
- @return:
417
- """
418
- if self.laser:
419
- return False
420
- if self.state == DRIVER_STATE_RAPID:
421
- self(b"I")
422
- self(self.CODE_LASER_ON)
423
- self(b"S1P\n")
424
- if not self.service.autolock:
425
- self(b"IS2P\n")
426
- elif self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
427
- self(self.CODE_LASER_ON)
428
- elif self.state == DRIVER_STATE_FINISH:
429
- self(self.CODE_LASER_ON)
430
- self(b"N")
431
- self.laser = True
432
- return True
433
-
434
- def rapid_mode(self, *values):
435
- """
436
- Rapid mode sets the laser to rapid state. This is usually moving the laser around without it executing a large
437
- batch of commands.
438
-
439
- @param values:
440
- @return:
441
- """
442
- if self.state == DRIVER_STATE_RAPID:
443
- return
444
- if self.state == DRIVER_STATE_FINISH:
445
- self(b"S1P\n")
446
- if not self.service.autolock:
447
- self(b"IS2P\n")
448
- elif self.state in (
449
- DRIVER_STATE_PROGRAM,
450
- DRIVER_STATE_RASTER,
451
- DRIVER_STATE_MODECHANGE,
452
- ):
453
- self(b"FNSE-\n")
454
- self.laser = False
455
- self.state = DRIVER_STATE_RAPID
456
-
457
- def finished_mode(self, *values):
458
- """
459
- Finished mode is after a large batch of jobs is done. A transition to finished may require the laser process
460
- all the data in the buffer.
461
-
462
- @param values:
463
- @return:
464
- """
465
- if self.state == DRIVER_STATE_FINISH:
466
- return
467
- if self.state in (
468
- DRIVER_STATE_PROGRAM,
469
- DRIVER_STATE_RASTER,
470
- DRIVER_STATE_MODECHANGE,
471
- ):
472
- self(b"@NSE")
473
- self.laser = False
474
- elif self.state == DRIVER_STATE_RAPID:
475
- self(b"I")
476
- self.state = DRIVER_STATE_FINISH
477
-
478
- def raster_mode(self, *values, dx=0, dy=0):
479
- """
480
- Raster mode runs in either `G0xx` stepping mode. It is only intended to move horizontal or
481
- vertical rastering, usually at a high speed. Accel twitches are required for this mode.
482
-
483
- @param values:
484
- @param dx: movement during raster mode switch
485
- @param dy: movement during raster mode switch
486
- @return:
487
- """
488
- if self.raster_step_y == 0 and self.raster_step_x == 0:
489
- # This is not properly set raster mode.
490
- self.program_mode(*values, dx=dx, dy=dy)
491
- return
492
- if self.state == DRIVER_STATE_RASTER:
493
- return
494
- self.finished_mode()
495
-
496
- horizontal = self.raster_step_y != 0
497
- self._request_horizontal_major = horizontal
498
-
499
- self._raster_step_swing_index = 0
500
- self._raster_step_float = (
501
- self.raster_step_y if horizontal else self.raster_step_x
502
- )
503
- if self._raster_step_float is None:
504
- self._raster_step_float = 1
505
- self._raster_step_g_value = int(math.floor(self._raster_step_float))
506
-
507
- if self._request_leftward is not None:
508
- self._leftward = self._request_leftward
509
- self._request_leftward = None
510
- if self._request_topward is not None:
511
- self._topward = self._request_topward
512
- self._request_topward = None
513
- if self._request_horizontal_major is not None:
514
- self._horizontal_major = self._request_horizontal_major
515
- self._request_horizontal_major = None
516
-
517
- if self.service.strict:
518
- # Override requested or current values only use core initial values.
519
- self._leftward = False
520
- self._topward = False
521
- self._horizontal_major = False
522
- if self.bidirectional:
523
- # Bidirectional (step on forward/back swing - rasters both directions)
524
- raster_step_value = self._raster_step_g_value
525
- else:
526
- # Unidirectional (step on forward swing - rasters only going forward)
527
- raster_step_value = self._raster_step_g_value, 0
528
- speed_code = LaserSpeed(
529
- self.service.board,
530
- self.speed,
531
- raster_step=raster_step_value,
532
- d_ratio=self.implicit_d_ratio,
533
- acceleration=self.implicit_accel,
534
- fix_limit=True,
535
- fix_lows=True,
536
- suffix_c=False,
537
- fix_speeds=self.service.fix_speeds,
538
- raster_horizontal=horizontal,
539
- ).speedcode
540
- speed_code = bytes(speed_code, "utf8")
541
- self(speed_code)
542
- self._goto_xy(dx, dy)
543
- self(b"N")
544
- self(self._code_declare_directions())
545
- self(b"S1E")
546
- self.state = DRIVER_STATE_RASTER
547
-
548
- def program_mode(self, *values, dx=0, dy=0):
549
- """
550
- Vector Mode implies but doesn't discount rastering. Twitches are used if twitches is set to True.
551
-
552
- @param values: passed information from the driver command
553
- @param dx: change in dx that should be made while switching to program mode.
554
- @param dy: change in dy that should be made while switching to program mode.
555
- @return:
556
- """
557
- if self.state == DRIVER_STATE_PROGRAM:
558
- return
559
- self.finished_mode()
560
-
561
- self._raster_step_swing_index = 0
562
- self._raster_step_float = 0
563
- self._raster_step_g_value = 0
564
-
565
- suffix_c = None
566
- if (
567
- not self.service.twitches or self.settings.get("_force_twitchless", False)
568
- ) and not self._raster_step_float:
569
- suffix_c = True
570
- if self._request_leftward is not None:
571
- self._leftward = self._request_leftward
572
- self._request_leftward = None
573
- if self._request_topward is not None:
574
- self._topward = self._request_topward
575
- self._request_topward = None
576
- if self._request_horizontal_major is not None:
577
- self._horizontal_major = self._request_horizontal_major
578
- self._request_horizontal_major = None
579
- if self.service.strict:
580
- # Override requested or current values only use core initial values.
581
- self._leftward = False
582
- self._topward = False
583
- self._horizontal_major = False
584
-
585
- speed_code = LaserSpeed(
586
- self.service.board,
587
- self.speed,
588
- raster_step=0,
589
- d_ratio=self.implicit_d_ratio,
590
- acceleration=self.implicit_accel,
591
- fix_limit=True,
592
- fix_lows=True,
593
- suffix_c=suffix_c,
594
- fix_speeds=self.service.fix_speeds,
595
- raster_horizontal=self._horizontal_major,
596
- ).speedcode
597
- speed_code = bytes(speed_code, "utf8")
598
- self(speed_code)
599
- self._goto_xy(dx, dy)
600
- self(b"N")
601
- self(self._code_declare_directions())
602
- self(b"S1E")
603
- if self._raster_step_float:
604
- self.state = DRIVER_STATE_RASTER
605
- else:
606
- self.state = DRIVER_STATE_PROGRAM
607
-
608
- def home(self, *values):
609
- """
610
- Home the laser.
611
-
612
- @param values:
613
- @return:
614
- """
615
- if self.service.rotary_active and self.service.rotary_supress_home:
616
- return
617
- self.rapid_mode()
618
- self(b"IPP\n")
619
- old_current = self.service.current
620
- self.native_x = 0
621
- self.native_y = 0
622
- self._reset_modes()
623
- self.state = DRIVER_STATE_RAPID
624
-
625
- new_current = self.service.current
626
- self.service.signal(
627
- "driver;position",
628
- (old_current[0], old_current[1], new_current[0], new_current[1]),
629
- )
630
-
631
- def physical_home(self):
632
- """ "
633
- This would be the command to go to a real physical home position (ie hitting endstops)
634
- """
635
- self.home()
636
-
637
- def lock_rail(self):
638
- """
639
- For plotter-style lasers this should prevent the laser bar from moving.
640
-
641
- @return:
642
- """
643
- self.rapid_mode()
644
- self(b"IS1P\n")
645
-
646
- def unlock_rail(self, abort=False):
647
- """
648
- For plotter-style jobs this should free the laser head to be movable by the user.
649
-
650
- @return:
651
- """
652
- self.rapid_mode()
653
- self(b"IS2P\n")
654
-
655
- def laser_disable(self, *values):
656
- self.laser_enabled = False
657
-
658
- def laser_enable(self, *values):
659
- self.laser_enabled = True
660
-
661
- def plot(self, plot):
662
- """
663
- Gives the driver cutcode that should be plotted/performed.
664
-
665
- @param plot:
666
- @return:
667
- """
668
- if isinstance(plot, InputCut):
669
- self.plot_start()
670
- self.wait_finish()
671
- # We do not have any GPIO-output abilities
672
- elif isinstance(plot, OutputCut):
673
- self.plot_start()
674
- self.wait_finish()
675
- # We do not have any GPIO-input abilities
676
- elif isinstance(plot, DwellCut):
677
- self.plot_start()
678
- self.rapid_mode()
679
- start = plot.start
680
- self.move_abs(start[0], start[1])
681
- self.wait_finish()
682
- self.dwell(plot.dwell_time)
683
- elif isinstance(plot, WaitCut):
684
- self.plot_start()
685
- self.wait_finish()
686
- self.wait(plot.dwell_time)
687
- elif isinstance(plot, HomeCut):
688
- self.plot_start()
689
- self.wait_finish()
690
- self.home()
691
- elif isinstance(plot, GotoCut):
692
- self.plot_start()
693
- start = plot.start
694
- self.wait_finish()
695
- self._move_absolute(start[0], start[1])
696
- else:
697
- # LineCut, QuadCut, CubicCut, PlotCut, RasterCut
698
- if isinstance(plot, PlotCut):
699
- plot.check_if_rasterable()
700
- self.plot_planner.push(plot)
701
-
702
- def plot_start(self):
703
- """
704
- Called at the end of plot commands to ensure the driver can deal with them all cutcode as a group, if this
705
- is needed by the driver.
706
-
707
- @return:
708
- """
709
- if self.plot_data is None:
710
- self.plot_data = self.plot_planner.gen()
711
- self._plotplanner_process()
712
-
713
- def wait(self, time_in_ms):
714
- """
715
- Wait asks that the work be stalled or current process held for the time time_in_ms in ms. If wait_finished is
716
- called first this will attempt to stall the machine while performing no work. If the driver in question permits
717
- waits to be placed within code this should insert waits into the current job. Returning instantly rather than
718
- holding the processes.
719
-
720
- @param time_in_ms:
721
- @return:
722
- """
723
- time.sleep(time_in_ms / 1000.0)
724
-
725
- def wait_finish(self, *values):
726
- """
727
- Wait finish should ensure that no additional commands be processed until the current buffer is completed. This
728
- does not necessarily imply a change in mode as "finished_mode" would require. Just that the buffer be completed
729
- before moving on.
730
-
731
- @param values:
732
- @return:
733
- """
734
-
735
- def temp_hold():
736
- try:
737
- return len(self.out_pipe) != 0
738
- except TypeError:
739
- return False
740
-
741
- self.temp_holds.append(temp_hold)
742
-
743
- def function(self, function):
744
- """
745
- This command asks that this function be executed at the appropriate time within the spooled cycle.
746
-
747
- @param function:
748
- @return:
749
- """
750
- function()
751
-
752
- def beep(self):
753
- """
754
- This command asks that a beep be executed at the appropriate time within the spooled cycle.
755
-
756
- @return:
757
- """
758
- self.service("beep\n")
759
-
760
- def console(self, value):
761
- """
762
- This asks that the console command be executed at the appropriate time within the spooled cycle.
763
-
764
- @param value: console command
765
- @return:
766
- """
767
- self.service(value)
768
-
769
- def signal(self, signal, *args):
770
- """
771
- This asks that this signal be broadcast.
772
-
773
- @param signal:
774
- @param args:
775
- @return:
776
- """
777
- self.service.signal(signal, *args)
778
-
779
- ######################
780
- # Property IO
781
- ######################
782
-
783
- @property
784
- def is_left(self):
785
- return self._x_engaged and not self._y_engaged and self._leftward
786
-
787
- @property
788
- def is_right(self):
789
- return self._x_engaged and not self._y_engaged and not self._leftward
790
-
791
- @property
792
- def is_top(self):
793
- return not self._x_engaged and self._y_engaged and self._topward
794
-
795
- @property
796
- def is_bottom(self):
797
- return not self._x_engaged and self._y_engaged and not self._topward
798
-
799
- @property
800
- def is_angle(self):
801
- return self._y_engaged and self._x_engaged
802
-
803
- def set_prop(self, mask):
804
- self.properties |= mask
805
-
806
- def unset_prop(self, mask):
807
- self.properties &= ~mask
808
-
809
- def is_prop(self, mask):
810
- return bool(self.properties & mask)
811
-
812
- def toggle_prop(self, mask):
813
- if self.is_prop(mask):
814
- self.unset_prop(mask)
815
- else:
816
- self.set_prop(mask)
817
-
818
- ######################
819
- # PROTECTED DRIVER CODE
820
- ######################
821
-
822
- def _plotplanner_process(self):
823
- """
824
- Processes any data in the plot planner. Getting all relevant (x,y,on) plot values and performing the cardinal
825
- movements. Or updating the laser state based on the settings of the cutcode.
826
-
827
- @return:
828
- """
829
- if self.plot_data is None:
830
- return False
831
- for x, y, on in self.plot_data:
832
- while self.hold_work(0):
833
- time.sleep(0.05)
834
- sx = self.native_x
835
- sy = self.native_y
836
- # print("x: %s, y: %s -- c: %s, %s" % (str(x), str(y), str(sx), str(sy)))
837
- on = int(on)
838
- if on > 1:
839
- # Special Command.
840
- if on & PLOT_FINISH: # Plot planner is ending.
841
- self.rapid_mode()
842
- break
843
- elif on & PLOT_SETTING: # Plot planner settings have changed.
844
- p_set = Parameters(self.plot_planner.settings)
845
- if p_set.power != self.power:
846
- self._set_power(p_set.power)
847
- if (
848
- p_set.raster_step_x != self.raster_step_x
849
- or p_set.raster_step_y != self.raster_step_y
850
- or p_set.speed != self.speed
851
- or self.implicit_d_ratio != p_set.implicit_d_ratio
852
- or self.implicit_accel != p_set.implicit_accel
853
- ):
854
- self._set_speed(p_set.speed)
855
- self._set_step(p_set.raster_step_x, p_set.raster_step_y)
856
- self._set_acceleration(p_set.implicit_accel)
857
- self._set_d_ratio(p_set.implicit_d_ratio)
858
- self.settings.update(p_set.settings)
859
- elif on & PLOT_AXIS: # Major Axis.
860
- # 0 means X Major / Horizontal.
861
- # 1 means Y Major / Vertical
862
- self._request_horizontal_major = bool(x == 0)
863
- elif on & PLOT_DIRECTION:
864
- # -1: Moving Left -x
865
- # 1: Moving Right. +x
866
- self._request_leftward = bool(x != 1)
867
- # -1: Moving Bottom +y
868
- # 1: Moving Top. -y
869
- self._request_topward = bool(y != 1)
870
- elif on & (
871
- PLOT_RAPID | PLOT_JOG
872
- ): # Plot planner requests position change.
873
- if (
874
- on & PLOT_RAPID
875
- or self.state != DRIVER_STATE_PROGRAM
876
- or self.service.rapid_override
877
- ):
878
- # Perform a rapid position change. Always perform this for raster moves.
879
- # DRIVER_STATE_RASTER should call this code as well.
880
- self.rapid_mode()
881
- self._move_absolute(x, y)
882
- else:
883
- # Jog is performable and requested. # We have not flagged our direction or state.
884
- self._jog_absolute(x, y, mode=self.service.opt_jog_mode)
885
- continue
886
- dx = x - sx
887
- dy = y - sy
888
- step_x = self.raster_step_x
889
- step_y = self.raster_step_y
890
- if step_x == 0 and step_y == 0:
891
- # vector mode
892
- self.program_mode()
893
- else:
894
- self.raster_mode()
895
- if self._horizontal_major:
896
- # Horizontal Rastering.
897
- if dy != 0:
898
- self._h_switch_g(dy)
899
- else:
900
- # Vertical Rastering.
901
- if dx != 0:
902
- self._v_switch_g(dx)
903
- # Update dx, dy (if changed by switches)
904
- dx = x - self.native_x
905
- dy = y - self.native_y
906
- self._goto_octent(dx, dy, on & 1)
907
- self.plot_data = None
908
- return False
909
-
910
- def _set_speed(self, speed=None):
911
- if self.speed != speed:
912
- self.speed = speed
913
- if self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
914
- self.state = DRIVER_STATE_MODECHANGE
915
-
916
- def _set_d_ratio(self, d_ratio=None):
917
- if self.dratio != d_ratio:
918
- self.dratio = d_ratio
919
- if self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
920
- self.state = DRIVER_STATE_MODECHANGE
921
-
922
- def _set_acceleration(self, accel=None):
923
- if self.acceleration != accel:
924
- self.acceleration = accel
925
- if self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
926
- self.state = DRIVER_STATE_MODECHANGE
927
-
928
- def _set_step(self, step_x=None, step_y=None):
929
- if self.raster_step_x != step_x or self.raster_step_y != step_y:
930
- self.raster_step_x = step_x
931
- self.raster_step_y = step_y
932
- if self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
933
- self.state = DRIVER_STATE_MODECHANGE
934
-
935
- def _set_power(self, power=1000.0):
936
- self.power = power
937
- if self.power > 1000.0:
938
- self.power = 1000.0
939
- if self.power <= 0:
940
- self.power = 0.0
941
-
942
- def _set_ppi(self, power=1000.0):
943
- self.power = power
944
- if self.power > 1000.0:
945
- self.power = 1000.0
946
- if self.power <= 0:
947
- self.power = 0.0
948
-
949
- def _set_pwm(self, power=1000.0):
950
- self.power = power
951
- if self.power > 1000.0:
952
- self.power = 1000.0
953
- if self.power <= 0:
954
- self.power = 0.0
955
-
956
- def _set_overscan(self, overscan=None):
957
- self.overscan = overscan
958
-
959
- def _cut(self, x, y):
960
- self._goto(x, y, True)
961
-
962
- def _jog(self, x, y, **kwargs):
963
- if self.is_relative:
964
- self._jog_relative(x, y, **kwargs)
965
- else:
966
- self._jog_absolute(x, y, **kwargs)
967
-
968
- def _jog_absolute(self, x, y, **kwargs):
969
- self._jog_relative(x - self.native_x, y - self.native_y, **kwargs)
970
-
971
- def _jog_relative(self, dx, dy, mode=0):
972
- self.laser_off()
973
- dx = int(round(dx))
974
- dy = int(round(dy))
975
- if mode == 0:
976
- self._nse_jog_event(dx, dy)
977
- elif mode == 1:
978
- self._mode_shift_on_the_fly(dx, dy)
979
- else:
980
- # Finish-out Jog
981
- self.rapid_mode()
982
- self._move_relative(dx, dy)
983
- self.program_mode()
984
-
985
- def _nse_jog_event(self, dx=0, dy=0, speed=None):
986
- """
987
- NSE Jog events are performed from program or raster mode and skip out to rapid mode to perform
988
- a single jog command. This jog effect varies based on the horizontal vertical major setting and
989
- needs to counteract the jogged head according to those settings.
990
-
991
- NSE jogs will not change the underlying mode even though they temporarily switch into
992
- rapid mode. nse jogs are not done in raster mode.
993
- """
994
- dx = int(round(dx))
995
- dy = int(round(dy))
996
- original_state = self.state
997
- self.state = DRIVER_STATE_RAPID
998
- self.laser = False
999
- if self._horizontal_major:
1000
- if not self.is_left and dx >= 0:
1001
- self(self.CODE_LEFT)
1002
- if not self.is_right and dx <= 0:
1003
- self(self.CODE_RIGHT)
1004
- else:
1005
- if not self.is_top and dy >= 0:
1006
- self(self.CODE_TOP)
1007
- if not self.is_bottom and dy <= 0:
1008
- self(self.CODE_BOTTOM)
1009
- self(b"N")
1010
- self._goto_xy(dx, dy)
1011
- self(b"SE")
1012
- self(self._code_declare_directions())
1013
- self.state = original_state
1014
-
1015
- def _move(self, x, y):
1016
- self._goto(x, y, False)
1017
-
1018
- def _move_absolute(self, x, y):
1019
- self._goto_absolute(x, y, False)
1020
-
1021
- def _move_relative(self, x, y):
1022
- self._goto_relative(x, y, False)
1023
-
1024
- def _goto(self, x, y, cut):
1025
- """
1026
- Goto a position within a cut.
1027
-
1028
- This depends on whether is_relative is set.
1029
-
1030
- @param x:
1031
- @param y:
1032
- @param cut:
1033
- @return:
1034
- """
1035
- if self.is_relative:
1036
- self._goto_relative(x, y, cut)
1037
- else:
1038
- self._goto_absolute(x, y, cut)
1039
-
1040
- def _goto_absolute(self, x, y, cut):
1041
- """
1042
- Goto absolute x and y. With cut set or not set.
1043
-
1044
- @param x:
1045
- @param y:
1046
- @param cut:
1047
- @return:
1048
- """
1049
- self._goto_relative(x - self.native_x, y - self.native_y, cut)
1050
-
1051
- def _move_override_speed(self, dx, dy, x_speed, y_speed=None):
1052
- """
1053
- Rapid movement override. Should make programmed jogs.
1054
-
1055
- @param dx: change in x
1056
- @param dy: change in y
1057
- @param x_speed: max allowed speed in x direction
1058
- @param y_speed: max allowed speed in y direction
1059
- @return:
1060
- """
1061
- if y_speed is None:
1062
- y_speed = x_speed
1063
- original_accel = self.acceleration
1064
- original_speed = self.speed
1065
- original_steps = self.raster_step_x, self.raster_step_y
1066
-
1067
- # Do not allow custom accel codes, or raster steps.
1068
- self._set_acceleration(None)
1069
- self._set_step(0, 0)
1070
- # We know we will travel dx, dy. Set the initial direction requests.
1071
- self._request_leftward = dx < 0
1072
- self._request_topward = dy < 0
1073
- self._request_horizontal_major = abs(dx) > abs(dy)
1074
- if y_speed <= x_speed and abs(dy) >= abs(dx):
1075
- # y_speed is slowest and dy is larger than dx. The y-shift will take longer than x-shift. Combine.
1076
- self._set_speed(y_speed)
1077
- self.program_mode()
1078
- dy_m = int(
1079
- math.copysign(dx, dy)
1080
- ) # magnitude of shorter in the direction of longer.
1081
- self._goto_octent(dx, dy_m, on=False)
1082
- self._goto_octent(0, dy - dy_m, on=False)
1083
- elif x_speed <= y_speed and abs(dx) >= abs(dy):
1084
- # x_speed is slowest and dx is larger than dy. The x-shift will take longer than y-shift. Combine.
1085
- self._set_speed(x_speed)
1086
- self.program_mode()
1087
- dx_m = int(
1088
- math.copysign(dy, dx)
1089
- ) # magnitude of shorter in the direction of longer.
1090
- self._goto_octent(dx_m, dy, on=False)
1091
- self._goto_octent(dx - dx_m, 0, on=False)
1092
- else:
1093
- # The faster speed is going longer. The slower speed is going shorter. Full zig.
1094
- if dx != 0:
1095
- self._set_speed(x_speed)
1096
- self.program_mode()
1097
- self._goto_octent(dx, 0, on=False)
1098
- if dy != 0:
1099
- self._set_speed(y_speed)
1100
- self.program_mode()
1101
- self._goto_octent(0, dy, on=False)
1102
- self.rapid_mode()
1103
-
1104
- # We always restore the original settings.
1105
- self._set_acceleration(original_accel)
1106
- self._set_speed(original_speed)
1107
- self._set_step(*original_steps)
1108
-
1109
- def _move_in_rapid_mode(self, dx, dy, cut):
1110
- if self.service.rapid_override and (dx != 0 or dy != 0):
1111
- y_speed = self.service.rapid_override_speed_y
1112
- x_speed = self.service.rapid_override_speed_x
1113
- self._move_override_speed(dx, dy, x_speed, y_speed)
1114
- return
1115
- self(b"I")
1116
- self._goto_xy(dx, dy)
1117
- self(b"S1P\n")
1118
- if not self.service.autolock:
1119
- self(b"IS2P\n")
1120
-
1121
- def _goto_relative(self, dx, dy, cut):
1122
- """
1123
- Goto relative dx, dy. With cut set or not set.
1124
-
1125
- @param dx:
1126
- @param dy:
1127
- @param cut:
1128
- @return:
1129
- """
1130
- if abs(dx) == 0 and abs(dy) == 0:
1131
- return
1132
- dx = int(round(dx))
1133
- dy = int(round(dy))
1134
- old_current = self.service.current
1135
- if self.state == DRIVER_STATE_RAPID:
1136
- self._move_in_rapid_mode(dx, dy, cut)
1137
- elif self.state == DRIVER_STATE_RASTER:
1138
- # goto in raster, switches to program to recall this function.
1139
- self.program_mode()
1140
- self._goto_relative(dx, dy, cut)
1141
- return
1142
- elif self.state == DRIVER_STATE_PROGRAM:
1143
- mx = 0
1144
- my = 0
1145
- line = list(grouped(ZinglPlotter.plot_line(0, 0, dx, dy)))
1146
- for x, y in line:
1147
- self._goto_octent(x - mx, y - my, cut)
1148
- mx = x
1149
- my = y
1150
- elif self.state == DRIVER_STATE_FINISH:
1151
- self._goto_xy(dx, dy)
1152
- self(b"N")
1153
- elif self.state == DRIVER_STATE_MODECHANGE:
1154
- self._mode_shift_on_the_fly(dx, dy)
1155
-
1156
- new_current = self.service.current
1157
- self.service.signal(
1158
- "driver;position",
1159
- (old_current[0], old_current[1], new_current[0], new_current[1]),
1160
- )
1161
-
1162
- def _mode_shift_on_the_fly(self, dx=0, dy=0):
1163
- """
1164
- Mode-shift on the fly changes the current modes while in programmed or raster mode
1165
- this exits with a @ command that resets the modes. A movement operation can be added after
1166
- the speed code and before the return to into programmed or raster mode.
1167
-
1168
- This switch is often avoided because testing revealed some chance of a runaway during reset
1169
- switching.
1170
-
1171
- If the raster step has been changed from zero this can result in shifting from program to raster mode
1172
- """
1173
- dx = int(round(dx))
1174
- dy = int(round(dy))
1175
- self(b"@NSE")
1176
- self.laser = False
1177
- self.state = DRIVER_STATE_RAPID
1178
- self.program_mode(dx, dy)
1179
-
1180
- def _h_switch_g(self, dy: float):
1181
- """
1182
- Horizontal switch with a Gvalue set. The board will automatically step according to the step_value_set.
1183
-
1184
- @return:
1185
- """
1186
- set_step = self._raster_step_g_value
1187
- if isinstance(set_step, tuple):
1188
- set_step = set_step[self._raster_step_swing_index % len(set_step)]
1189
-
1190
- # correct for fractional stepping
1191
- self._raster_step_fractional_remainder += dy
1192
- delta = math.trunc(self._raster_step_fractional_remainder)
1193
- self._raster_step_fractional_remainder -= delta
1194
-
1195
- step_amount = -abs(set_step) if self._topward else abs(set_step)
1196
- remaining = delta - step_amount
1197
- if (
1198
- remaining > 0
1199
- and self._topward
1200
- or remaining < 0
1201
- and not self._topward
1202
- or abs(remaining) > 15
1203
- ):
1204
- # Remaining value is in the wrong direction, abort and move.
1205
- self.finished_mode()
1206
- self._move_relative(0, remaining)
1207
- self.raster_mode()
1208
- remaining = 0
1209
- if remaining:
1210
- self._goto_octent(
1211
- -abs(remaining) if self._leftward else abs(remaining), remaining, False
1212
- )
1213
- self._x_engaged = True
1214
- self._y_engaged = False
1215
- # We reverse direction and step.
1216
- if self._leftward:
1217
- self(self.CODE_RIGHT)
1218
- self._leftward = False
1219
- else:
1220
- self(self.CODE_LEFT)
1221
- self._leftward = True
1222
- self.native_y += step_amount
1223
- self.laser = False
1224
- self._raster_step_swing_index += 1
1225
-
1226
- def _v_switch_g(self, dx: float):
1227
- """
1228
- Vertical switch with a Gvalue set. The board will automatically step according to the step_value_set.
1229
-
1230
- @return:
1231
- """
1232
- set_step = self._raster_step_g_value
1233
- if isinstance(set_step, tuple):
1234
- set_step = set_step[self._raster_step_swing_index % len(set_step)]
1235
-
1236
- # correct for fractional stepping
1237
- self._raster_step_fractional_remainder += dx
1238
- delta = math.trunc(self._raster_step_fractional_remainder)
1239
- self._raster_step_fractional_remainder -= delta
1240
-
1241
- step_amount = -set_step if self._leftward else set_step
1242
- remaining = delta - step_amount
1243
- if (
1244
- remaining > 0
1245
- and self._leftward
1246
- or remaining < 0
1247
- and not self._leftward
1248
- or abs(remaining) > 15
1249
- ):
1250
- # Remaining value is in the wrong direction, abort and move.
1251
- self.finished_mode()
1252
- self._move_relative(remaining, 0)
1253
- self.raster_mode()
1254
- remaining = 0
1255
- if remaining:
1256
- self._goto_octent(
1257
- remaining, -abs(remaining) if self._topward else abs(remaining), False
1258
- )
1259
- self._y_engaged = True
1260
- self._x_engaged = False
1261
- # We reverse direction and step.
1262
- if self._topward:
1263
- self(self.CODE_BOTTOM)
1264
- self._topward = False
1265
- else:
1266
- self(self.CODE_TOP)
1267
- self._topward = True
1268
- self.native_x += step_amount
1269
- self.laser = False
1270
- self._raster_step_swing_index += 1
1271
-
1272
- def _reset_modes(self):
1273
- self.laser = False
1274
- self._request_leftward = None
1275
- self._request_topward = None
1276
- self._request_horizontal_major = None
1277
- self._topward = False
1278
- self._leftward = False
1279
- self._x_engaged = False
1280
- self._y_engaged = False
1281
- self._horizontal_major = False
1282
-
1283
- def _goto_xy(self, dx, dy, on=None):
1284
- rapid = self.state not in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER)
1285
- if dx != 0:
1286
- self.native_x += dx
1287
- if dx > 0: # Moving right
1288
- if not self.is_right or rapid:
1289
- self(self.CODE_RIGHT)
1290
- self._leftward = False
1291
- else: # Moving left
1292
- if not self.is_left or rapid:
1293
- self(self.CODE_LEFT)
1294
- self._leftward = True
1295
- self._x_engaged = True
1296
- self._y_engaged = False
1297
- if on is not None:
1298
- if on:
1299
- self.laser_on()
1300
- else:
1301
- self.laser_off()
1302
- self(lhymicro_distance(abs(dx)))
1303
- if dy != 0:
1304
- self.native_y += dy
1305
- if dy > 0: # Moving bottom
1306
- if not self.is_bottom or rapid:
1307
- self(self.CODE_BOTTOM)
1308
- self._topward = False
1309
- else: # Moving top
1310
- if not self.is_top or rapid:
1311
- self(self.CODE_TOP)
1312
- self._topward = True
1313
- self._x_engaged = False
1314
- self._y_engaged = True
1315
- if on is not None:
1316
- if on:
1317
- self.laser_on()
1318
- else:
1319
- self.laser_off()
1320
- self(lhymicro_distance(abs(dy)))
1321
-
1322
- def _goto_octent(self, dx, dy, on):
1323
- old_current = self.service.current
1324
- if dx == 0 and dy == 0:
1325
- return
1326
- if abs(dx) == abs(dy):
1327
- self._x_engaged = True # Set both on
1328
- self._y_engaged = True
1329
- if dx > 0: # Moving right
1330
- if self._leftward:
1331
- self(self.CODE_RIGHT)
1332
- self._leftward = False
1333
- else: # Moving left
1334
- if not self._leftward:
1335
- self(self.CODE_LEFT)
1336
- self._leftward = True
1337
- if dy > 0: # Moving bottom
1338
- if self._topward:
1339
- self(self.CODE_BOTTOM)
1340
- self._topward = False
1341
- else: # Moving top
1342
- if not self._topward:
1343
- self(self.CODE_TOP)
1344
- self._topward = True
1345
- self.native_x += dx
1346
- self.native_y += dy
1347
- self(self.CODE_ANGLE)
1348
- if on:
1349
- self.laser_on()
1350
- else:
1351
- self.laser_off()
1352
- self(lhymicro_distance(abs(dy)))
1353
- else:
1354
- self._goto_xy(dx, dy, on=on)
1355
-
1356
- new_current = self.service.current
1357
- self.service.signal(
1358
- "driver;position",
1359
- (old_current[0], old_current[1], new_current[0], new_current[1]),
1360
- )
1361
-
1362
- def _code_declare_directions(self):
1363
- x_dir = self.CODE_LEFT if self._leftward else self.CODE_RIGHT
1364
- y_dir = self.CODE_TOP if self._topward else self.CODE_BOTTOM
1365
- if self._horizontal_major:
1366
- self._x_engaged = True
1367
- self._y_engaged = False
1368
- return y_dir + x_dir
1369
- else:
1370
- self._x_engaged = False
1371
- self._y_engaged = True
1372
- return x_dir + y_dir
1
+ """
2
+ Lihuiyu Driver
3
+
4
+ Governs the generic commands issued by laserjob and spooler and converts that into regular LHYMicro-GL output.
5
+
6
+ This generated data is then sent to the controller, which could be a network connection, usb, or mock depending on the
7
+ selected output.
8
+ """
9
+
10
+ import math
11
+ import time
12
+
13
+ from meerk40t.tools.zinglplotter import ZinglPlotter
14
+
15
+ from ..core.cutcode.dwellcut import DwellCut
16
+ from ..core.cutcode.gotocut import GotoCut
17
+ from ..core.cutcode.homecut import HomeCut
18
+ from ..core.cutcode.inputcut import InputCut
19
+ from ..core.cutcode.outputcut import OutputCut
20
+ from ..core.cutcode.plotcut import PlotCut
21
+ from ..core.cutcode.waitcut import WaitCut
22
+ from ..core.parameters import Parameters
23
+ from ..core.plotplanner import PlotPlanner, grouped
24
+ from ..device.basedevice import (
25
+ DRIVER_STATE_FINISH,
26
+ DRIVER_STATE_MODECHANGE,
27
+ DRIVER_STATE_PROGRAM,
28
+ DRIVER_STATE_RAPID,
29
+ DRIVER_STATE_RASTER,
30
+ PLOT_AXIS,
31
+ PLOT_DIRECTION,
32
+ PLOT_FINISH,
33
+ PLOT_JOG,
34
+ PLOT_RAPID,
35
+ PLOT_SETTING,
36
+ )
37
+ from ..tools.geomstr import Geomstr
38
+ from .laserspeed import LaserSpeed
39
+
40
+ distance_lookup = [
41
+ b"",
42
+ b"a",
43
+ b"b",
44
+ b"c",
45
+ b"d",
46
+ b"e",
47
+ b"f",
48
+ b"g",
49
+ b"h",
50
+ b"i",
51
+ b"j",
52
+ b"k",
53
+ b"l",
54
+ b"m",
55
+ b"n",
56
+ b"o",
57
+ b"p",
58
+ b"q",
59
+ b"r",
60
+ b"s",
61
+ b"t",
62
+ b"u",
63
+ b"v",
64
+ b"w",
65
+ b"x",
66
+ b"y",
67
+ b"|a",
68
+ b"|b",
69
+ b"|c",
70
+ b"|d",
71
+ b"|e",
72
+ b"|f",
73
+ b"|g",
74
+ b"|h",
75
+ b"|i",
76
+ b"|j",
77
+ b"|k",
78
+ b"|l",
79
+ b"|m",
80
+ b"|n",
81
+ b"|o",
82
+ b"|p",
83
+ b"|q",
84
+ b"|r",
85
+ b"|s",
86
+ b"|t",
87
+ b"|u",
88
+ b"|v",
89
+ b"|w",
90
+ b"|x",
91
+ b"|y",
92
+ b"|z",
93
+ ]
94
+
95
+
96
+ def lhymicro_distance(v):
97
+ if v < 0:
98
+ raise ValueError("Cannot permit negative values.")
99
+ dist = b""
100
+ if v >= 255:
101
+ zs = int(v / 255)
102
+ v %= 255
103
+ dist += b"z" * zs
104
+ if v >= 52:
105
+ return dist + b"%03d" % v
106
+ return dist + distance_lookup[v]
107
+
108
+
109
+ class LihuiyuDriver(Parameters):
110
+ """
111
+ LihuiyuDriver provides Lihuiyu specific coding for elements and sends it to the backend
112
+ to write to the usb.
113
+ """
114
+
115
+ def __init__(self, service, *args, **kwargs):
116
+ super().__init__()
117
+ self.service = service
118
+ self.name = str(self.service)
119
+ self._topward = False
120
+ self._leftward = False
121
+ self._x_engaged = False
122
+ self._y_engaged = False
123
+ self._horizontal_major = False
124
+
125
+ self._request_leftward = None
126
+ self._request_topward = None
127
+ self._request_horizontal_major = None
128
+
129
+ self.out_pipe = None
130
+
131
+ self.process_item = None
132
+ self.spooled_item = None
133
+ self.holds = []
134
+ self.temp_holds = []
135
+
136
+ self.native_x = 0
137
+ self.native_y = 0
138
+
139
+ self.plot_planner = PlotPlanner(self.settings)
140
+ self.plot_attribute_update()
141
+
142
+ self.plot_data = None
143
+ self._queue_current = 0
144
+ self._queue_total = 0
145
+
146
+ self.state = DRIVER_STATE_RAPID
147
+ self.properties = 0
148
+ self.is_relative = False
149
+ self.laser = False
150
+ self._thread = None
151
+ self._shutdown = False
152
+ self.last_fetch = None
153
+
154
+ self.CODE_RIGHT = b"B"
155
+ self.CODE_LEFT = b"T"
156
+ self.CODE_TOP = b"L"
157
+ self.CODE_BOTTOM = b"R"
158
+ self.CODE_ANGLE = b"M"
159
+ self.CODE_LASER_ON = b"D"
160
+ self.CODE_LASER_OFF = b"U"
161
+
162
+ self._signal_updates = self.service.setting(bool, "signal_updates", True)
163
+
164
+ self.paused = False
165
+
166
+ def primary_hold():
167
+ if self.out_pipe is None:
168
+ return True
169
+ if hasattr(self.out_pipe, "is_shutdown") and self.out_pipe.is_shutdown:
170
+ raise ConnectionAbortedError("Cannot hold for a shutdown pipe.")
171
+ try:
172
+ buffer = len(self.out_pipe)
173
+ except TypeError:
174
+ buffer = 0
175
+ return self.service.buffer_limit and buffer > self.service.buffer_max
176
+
177
+ self.holds.append(primary_hold)
178
+
179
+ # Step amount expected of the current operation
180
+ self._raster_step_float = 0
181
+
182
+ # Step amount is the current correctly set step amount in the controller.
183
+ self._raster_step_g_value = 0
184
+
185
+ # Step index of the current step taken for unidirectional
186
+ self._raster_step_swing_index = 0
187
+
188
+ # Step total the count for fractional step amounts
189
+ self._raster_step_fractional_remainder = 0.0
190
+
191
+ def __repr__(self):
192
+ return f"LihuiyuDriver({self.name})"
193
+
194
+ def __call__(self, e):
195
+ self.out_pipe.write(e)
196
+
197
+ def get_internal_queue_status(self):
198
+ return self._queue_current, self._queue_total
199
+
200
+ def _set_queue_status(self, current, total):
201
+ self._queue_current = current
202
+ self._queue_total = total
203
+
204
+ def plot_attribute_update(self):
205
+ self.plot_planner.force_shift = self.service.plot_shift
206
+ self.plot_planner.phase_type = self.service.plot_phase_type
207
+ self.plot_planner.phase_value = self.service.plot_phase_value
208
+
209
+ def hold_work(self, priority):
210
+ """
211
+ Holds are criteria to use to pause the data interpretation. These halt the production of new data until the
212
+ criteria is met. A hold is constant and will always halt the data while true. A temp_hold will be removed
213
+ as soon as it does not hold the data.
214
+
215
+ @return: Whether data interpretation should hold.
216
+ """
217
+ if priority > 0:
218
+ # Don't hold realtime work.
219
+ return False
220
+
221
+ temp_hold = False
222
+ fail_hold = False
223
+ for i, hold in enumerate(self.temp_holds):
224
+ if not hold():
225
+ self.temp_holds[i] = None
226
+ fail_hold = True
227
+ else:
228
+ temp_hold = True
229
+ if fail_hold:
230
+ self.temp_holds = [hold for hold in self.temp_holds if hold is not None]
231
+ if temp_hold:
232
+ return True
233
+ for hold in self.holds:
234
+ if hold():
235
+ return True
236
+ return False
237
+
238
+ def get(self, key, default=None):
239
+ """
240
+ Required.
241
+
242
+ @param key: Key to get.
243
+ @param default: Default value to use.
244
+ @return:
245
+ """
246
+ return self.settings.get(key, default=default)
247
+
248
+ def set(self, key, value):
249
+ """
250
+ Required.
251
+
252
+ Sets a laser parameter this could be speed, power, wobble, number_of_unicorns, or any unknown parameters for
253
+ yet to be written drivers.
254
+
255
+ @param key:
256
+ @param value:
257
+ @return:
258
+ """
259
+ if key == "power":
260
+ self._set_power(value)
261
+ elif key == "ppi":
262
+ self._set_power(value)
263
+ elif key == "pwm":
264
+ self._set_power(value)
265
+ elif key == "overscan":
266
+ self._set_overscan(value)
267
+ elif key == "acceleration":
268
+ self._set_acceleration(value)
269
+ elif key == "relative":
270
+ self.is_relative = value
271
+ elif key == "d_ratio":
272
+ self._set_d_ratio(value)
273
+ elif key == "step":
274
+ self._set_step(*value)
275
+ else:
276
+ self.settings[key] = value
277
+
278
+ def status(self):
279
+ """
280
+ Wants a status report of what the driver is doing.
281
+ @return:
282
+ """
283
+ state_major = "idle"
284
+ state_minor = "idle"
285
+ if self.state == DRIVER_STATE_RAPID:
286
+ state_major = "idle"
287
+ state_minor = "idle"
288
+ elif self.state == DRIVER_STATE_FINISH:
289
+ state_major = "idle"
290
+ state_minor = "finished"
291
+ elif self.state == DRIVER_STATE_PROGRAM:
292
+ state_major = "busy"
293
+ state_minor = "program"
294
+ elif self.state == DRIVER_STATE_RASTER:
295
+ state_major = "busy"
296
+ state_minor = "raster"
297
+ elif self.state == DRIVER_STATE_MODECHANGE:
298
+ state_major = "busy"
299
+ state_minor = "changing"
300
+ return (self.native_x, self.native_y), state_major, state_minor
301
+
302
+ def pause(self, *values):
303
+ """
304
+ Asks that the laser be paused.
305
+
306
+ @return:
307
+ """
308
+ self(b"~PN!\n~")
309
+ self.paused = True
310
+ self.service.signal("pause")
311
+
312
+ def resume(self, *values):
313
+ """
314
+ Asks that the laser be resumed.
315
+
316
+ To work this command should usually be put into the realtime work queue for the laser, without that it will
317
+ be paused and unable to process the resume.
318
+
319
+ @param values:
320
+ @return:
321
+ """
322
+ self(b"~PN&\n~")
323
+ self.paused = False
324
+ self.service.signal("pause")
325
+
326
+ def reset(self):
327
+ """
328
+ This command asks that this device be emergency stopped and reset. Usually that queue data from the spooler be
329
+ deleted.
330
+
331
+ Asks that the device resets, and clears all current work.
332
+
333
+ @return:
334
+ """
335
+ self.service.spooler.clear_queue()
336
+ self.plot_planner.clear()
337
+ self.spooled_item = None
338
+ self.temp_holds.clear()
339
+
340
+ self.service.signal("pipe;buffer", 0)
341
+ self(b"~I*\n~")
342
+ self._reset_modes()
343
+ self.state = DRIVER_STATE_RAPID
344
+ self.paused = False
345
+ self.service.signal("pause")
346
+
347
+ def abort(self):
348
+ self(b"I\n")
349
+
350
+ def blob(self, blob_type, data):
351
+ """
352
+ Blob sends a data blob. This is native code data of the give type. For example in a ruida device it might be a
353
+ bunch of .rd code, or Lihuiyu device it could be .egv code. It's a method of sending pre-chewed data to the
354
+ device.
355
+
356
+ @param blob_type:
357
+ @param data:
358
+ @return:
359
+ """
360
+ if blob_type == "egv":
361
+ self(data)
362
+
363
+ def move_abs(self, x, y):
364
+ """
365
+ Requests laser move to absolute position x, y in physical units
366
+
367
+ @param x:
368
+ @param y:
369
+ @return:
370
+ """
371
+ x, y = self.service.view.position(x, y)
372
+ self.rapid_mode()
373
+ self._move_absolute(int(round(x)), int(round(y)))
374
+
375
+ def move_rel(self, dx, dy):
376
+ """
377
+ Requests laser move relative position dx, dy in physical units
378
+
379
+ @param dx:
380
+ @param dy:
381
+ @return:
382
+ """
383
+ unit_dx, unit_dy = self.service.view.position(dx, dy, vector=True)
384
+ self.rapid_mode()
385
+ self._move_relative(unit_dx, unit_dy)
386
+
387
+ def dwell(self, time_in_ms):
388
+ """
389
+ Requests that the laser fire in place for the given time period. This could be done in a series of commands,
390
+ move to a location, turn laser on, wait, turn laser off. However, some drivers have specific laser-in-place
391
+ commands so calling dwell is preferred.
392
+
393
+ @param time_in_ms:
394
+ @return:
395
+ """
396
+ self.rapid_mode()
397
+ self.wait_finish()
398
+ self.laser_on() # This can't be sent early since these are timed operations.
399
+ self.wait(time_in_ms)
400
+ self.laser_off()
401
+
402
+ def laser_off(self):
403
+ """
404
+ Turn laser off in place.
405
+
406
+ @return:
407
+ """
408
+ if not self.laser:
409
+ return False
410
+ if self.state == DRIVER_STATE_RAPID:
411
+ self(b"I")
412
+ self(self.CODE_LASER_OFF)
413
+ self(b"S1P\n")
414
+ if not self.service.autolock:
415
+ self(b"IS2P\n")
416
+ elif self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
417
+ self(self.CODE_LASER_OFF)
418
+ elif self.state == DRIVER_STATE_FINISH:
419
+ self(self.CODE_LASER_OFF)
420
+ self(b"N")
421
+ self.laser = False
422
+ return True
423
+
424
+ def laser_on(self):
425
+ """
426
+ Turn laser on in place.
427
+
428
+ @return:
429
+ """
430
+ if self.laser:
431
+ return False
432
+ if self.state == DRIVER_STATE_RAPID:
433
+ self(b"I")
434
+ self(self.CODE_LASER_ON)
435
+ self(b"S1P\n")
436
+ if not self.service.autolock:
437
+ self(b"IS2P\n")
438
+ elif self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
439
+ self(self.CODE_LASER_ON)
440
+ elif self.state == DRIVER_STATE_FINISH:
441
+ self(self.CODE_LASER_ON)
442
+ self(b"N")
443
+ self.laser = True
444
+ return True
445
+
446
+ def rapid_mode(self, *values):
447
+ """
448
+ Rapid mode sets the laser to rapid state. This is usually moving the laser around without it executing a large
449
+ batch of commands.
450
+
451
+ @param values:
452
+ @return:
453
+ """
454
+ if self.state == DRIVER_STATE_RAPID:
455
+ return
456
+ if self.state == DRIVER_STATE_FINISH:
457
+ self(b"S1P\n")
458
+ if not self.service.autolock:
459
+ self(b"IS2P\n")
460
+ elif self.state in (
461
+ DRIVER_STATE_PROGRAM,
462
+ DRIVER_STATE_RASTER,
463
+ DRIVER_STATE_MODECHANGE,
464
+ ):
465
+ self(b"FNSE-\n")
466
+ self.laser = False
467
+ self.state = DRIVER_STATE_RAPID
468
+
469
+ def finished_mode(self, *values):
470
+ """
471
+ Finished mode is after a large batch of jobs is done. A transition to finished may require the laser process
472
+ all the data in the buffer.
473
+
474
+ @param values:
475
+ @return:
476
+ """
477
+ if self.state == DRIVER_STATE_FINISH:
478
+ return
479
+ if self.state in (
480
+ DRIVER_STATE_PROGRAM,
481
+ DRIVER_STATE_RASTER,
482
+ DRIVER_STATE_MODECHANGE,
483
+ ):
484
+ self(b"@NSE")
485
+ self.laser = False
486
+ elif self.state == DRIVER_STATE_RAPID:
487
+ self(b"I")
488
+ self.state = DRIVER_STATE_FINISH
489
+
490
+ def raster_mode(self, *values, dx=0, dy=0):
491
+ """
492
+ Raster mode runs in either `G0xx` stepping mode. It is only intended to move horizontal or
493
+ vertical rastering, usually at a high speed. Accel twitches are required for this mode.
494
+
495
+ @param values:
496
+ @param dx: movement during raster mode switch
497
+ @param dy: movement during raster mode switch
498
+ @return:
499
+ """
500
+ if self.raster_step_y == 0 and self.raster_step_x == 0:
501
+ # This is not properly set raster mode.
502
+ self.program_mode(*values, dx=dx, dy=dy)
503
+ return
504
+ if self.state == DRIVER_STATE_RASTER:
505
+ return
506
+ self.finished_mode()
507
+
508
+ horizontal = self.raster_step_y != 0
509
+ self._request_horizontal_major = horizontal
510
+
511
+ self._raster_step_swing_index = 0
512
+ self._raster_step_float = (
513
+ self.raster_step_y if horizontal else self.raster_step_x
514
+ )
515
+ if self._raster_step_float is None:
516
+ self._raster_step_float = 1
517
+ self._raster_step_g_value = int(math.floor(self._raster_step_float))
518
+
519
+ if self._request_leftward is not None:
520
+ self._leftward = self._request_leftward
521
+ self._request_leftward = None
522
+ if self._request_topward is not None:
523
+ self._topward = self._request_topward
524
+ self._request_topward = None
525
+ if self._request_horizontal_major is not None:
526
+ self._horizontal_major = self._request_horizontal_major
527
+ self._request_horizontal_major = None
528
+
529
+ if self.service.strict:
530
+ # Override requested or current values only use core initial values.
531
+ self._leftward = False
532
+ self._topward = False
533
+ self._horizontal_major = False
534
+ if self.bidirectional:
535
+ # Bidirectional (step on forward/back swing - rasters both directions)
536
+ raster_step_value = self._raster_step_g_value
537
+ else:
538
+ # Unidirectional (step on forward swing - rasters only going forward)
539
+ raster_step_value = self._raster_step_g_value, 0
540
+ speed_code = LaserSpeed(
541
+ self.service.board,
542
+ self.speed,
543
+ raster_step=raster_step_value,
544
+ d_ratio=self.implicit_d_ratio,
545
+ acceleration=self.implicit_accel,
546
+ fix_limit=True,
547
+ fix_lows=True,
548
+ suffix_c=False,
549
+ fix_speeds=self.service.fix_speeds,
550
+ raster_horizontal=horizontal,
551
+ ).speedcode
552
+ speed_code = bytes(speed_code, "utf8")
553
+ self(speed_code)
554
+ self._goto_xy(dx, dy)
555
+ self(b"N")
556
+ self(self._code_declare_directions())
557
+ self(b"S1E")
558
+ self.state = DRIVER_STATE_RASTER
559
+
560
+ def program_mode(self, *values, dx=0, dy=0):
561
+ """
562
+ Vector Mode implies but doesn't discount rastering. Twitches are used if twitches is set to True.
563
+
564
+ @param values: passed information from the driver command
565
+ @param dx: change in dx that should be made while switching to program mode.
566
+ @param dy: change in dy that should be made while switching to program mode.
567
+ @return:
568
+ """
569
+ if self.state == DRIVER_STATE_PROGRAM:
570
+ return
571
+ self.finished_mode()
572
+
573
+ self._raster_step_swing_index = 0
574
+ self._raster_step_float = 0
575
+ self._raster_step_g_value = 0
576
+
577
+ suffix_c = None
578
+ if (
579
+ not self.service.twitches or self.settings.get("_force_twitchless", False)
580
+ ) and not self._raster_step_float:
581
+ suffix_c = True
582
+ if self._request_leftward is not None:
583
+ self._leftward = self._request_leftward
584
+ self._request_leftward = None
585
+ if self._request_topward is not None:
586
+ self._topward = self._request_topward
587
+ self._request_topward = None
588
+ if self._request_horizontal_major is not None:
589
+ self._horizontal_major = self._request_horizontal_major
590
+ self._request_horizontal_major = None
591
+ if self.service.strict:
592
+ # Override requested or current values only use core initial values.
593
+ self._leftward = False
594
+ self._topward = False
595
+ self._horizontal_major = False
596
+
597
+ speed_code = LaserSpeed(
598
+ self.service.board,
599
+ self.speed,
600
+ raster_step=0,
601
+ d_ratio=self.implicit_d_ratio,
602
+ acceleration=self.implicit_accel,
603
+ fix_limit=True,
604
+ fix_lows=True,
605
+ suffix_c=suffix_c,
606
+ fix_speeds=self.service.fix_speeds,
607
+ raster_horizontal=self._horizontal_major,
608
+ ).speedcode
609
+ speed_code = bytes(speed_code, "utf8")
610
+ self(speed_code)
611
+ self._goto_xy(dx, dy)
612
+ self(b"N")
613
+ self(self._code_declare_directions())
614
+ self(b"S1E")
615
+ if self._raster_step_float:
616
+ self.state = DRIVER_STATE_RASTER
617
+ else:
618
+ self.state = DRIVER_STATE_PROGRAM
619
+
620
+ def home(self, *values):
621
+ """
622
+ Home the laser.
623
+
624
+ @param values:
625
+ @return:
626
+ """
627
+ if self.service.rotary.active and self.service.rotary.suppress_home:
628
+ return
629
+ self.rapid_mode()
630
+ self(b"IPP\n")
631
+ old_current = self.service.current
632
+ self.native_x = 0
633
+ self.native_y = 0
634
+ self._reset_modes()
635
+ self.state = DRIVER_STATE_RAPID
636
+
637
+ new_current = self.service.current
638
+ if self._signal_updates:
639
+ self.service.signal(
640
+ "driver;position",
641
+ (old_current[0], old_current[1], new_current[0], new_current[1]),
642
+ )
643
+
644
+ def physical_home(self):
645
+ """ "
646
+ This would be the command to go to a real physical home position (i.e. hitting endstops)
647
+ """
648
+ self.home()
649
+
650
+ def lock_rail(self):
651
+ """
652
+ For plotter-style lasers this should prevent the laser bar from moving.
653
+
654
+ @return:
655
+ """
656
+ self.rapid_mode()
657
+ self(b"IS1P\n")
658
+
659
+ def unlock_rail(self, abort=False):
660
+ """
661
+ For plotter-style jobs this should free the laser head to be movable by the user.
662
+
663
+ @return:
664
+ """
665
+ self.rapid_mode()
666
+ self(b"IS2P\n")
667
+
668
+ def laser_disable(self, *values):
669
+ self.laser_enabled = False
670
+
671
+ def laser_enable(self, *values):
672
+ self.laser_enabled = True
673
+
674
+ def geometry(self, geom):
675
+ """
676
+ Driver command to deal with `geometry` driver call.
677
+
678
+ @return:
679
+ """
680
+ for segment_type, start, c1, c2, end, sets in geom.as_lines():
681
+ if segment_type == "line":
682
+ self.plot_planner.push(plot)
683
+ elif segment_type == "end":
684
+ pass
685
+ elif segment_type == "quad":
686
+ self.plot_planner.push(plot)
687
+ elif segment_type == "cubic":
688
+ self.plot_planner.push(plot)
689
+ elif segment_type == "arc":
690
+ interp = 50
691
+ g = Geomstr()
692
+ g.clear()
693
+ g.arc(start, c1, end)
694
+ last = start
695
+ for p in list(g.as_equal_interpolated_points(distance=interp))[1:]:
696
+ self.plot_planner.push((last, p))
697
+ last = p
698
+ elif segment_type == "point":
699
+ function = sets.get("function")
700
+ if function == "dwell":
701
+ self.plot_start()
702
+ self.rapid_mode()
703
+ self._move_absolute(start.real, start.imag)
704
+ self.wait_finish()
705
+ self.dwell(sets.get("dwell_time"))
706
+ elif function == "wait":
707
+ self.plot_start()
708
+ self.wait_finish()
709
+ self.wait(sets.get("dwell_time"))
710
+ elif function == "home":
711
+ self.plot_start()
712
+ self.wait_finish()
713
+ self.home()
714
+ elif function == "goto":
715
+ self.plot_start()
716
+ self.wait_finish()
717
+ self._move_absolute(start.real, start.imag)
718
+ elif function == "input":
719
+ self.plot_start()
720
+ self.wait_finish()
721
+ elif function == "output":
722
+ self.plot_start()
723
+ self.wait_finish()
724
+ if self.plot_data is None:
725
+ self.plot_data = self.plot_planner.gen()
726
+ self._plotplanner_process()
727
+
728
+ def plot(self, plot):
729
+ """
730
+ Gives the driver cutcode that should be plotted/performed.
731
+
732
+ @param plot:
733
+ @return:
734
+ """
735
+ if isinstance(plot, InputCut):
736
+ self.plot_start()
737
+ self.wait_finish()
738
+ # We do not have any GPIO-output abilities
739
+ elif isinstance(plot, OutputCut):
740
+ self.plot_start()
741
+ self.wait_finish()
742
+ # We do not have any GPIO-input abilities
743
+ elif isinstance(plot, DwellCut):
744
+ self.plot_start()
745
+ self.rapid_mode()
746
+ start = plot.start
747
+ self._move_absolute(start[0], start[1])
748
+ self.wait_finish()
749
+ self.dwell(plot.dwell_time)
750
+ elif isinstance(plot, WaitCut):
751
+ self.plot_start()
752
+ self.wait_finish()
753
+ self.wait(plot.dwell_time)
754
+ elif isinstance(plot, HomeCut):
755
+ self.plot_start()
756
+ self.wait_finish()
757
+ self.home()
758
+ elif isinstance(plot, GotoCut):
759
+ self.plot_start()
760
+ start = plot.start
761
+ self.wait_finish()
762
+ self._move_absolute(start[0], start[1])
763
+ else:
764
+ # LineCut, QuadCut, CubicCut, PlotCut, RasterCut
765
+ if isinstance(plot, PlotCut):
766
+ plot.check_if_rasterable()
767
+ self.plot_planner.push(plot)
768
+
769
+ def plot_start(self):
770
+ """
771
+ Called at the end of plot commands to ensure the driver can deal with them all cutcode as a group, if this
772
+ is needed by the driver.
773
+
774
+ @return:
775
+ """
776
+ if self.plot_data is None:
777
+ self.plot_data = self.plot_planner.gen()
778
+ self._plotplanner_process()
779
+
780
+ def wait(self, time_in_ms):
781
+ """
782
+ Wait asks that the work be stalled or current process held for the time time_in_ms in ms. If wait_finished is
783
+ called first this will attempt to stall the machine while performing no work. If the driver in question permits
784
+ waits to be placed within code this should insert waits into the current job. Returning instantly rather than
785
+ holding the processes.
786
+
787
+ @param time_in_ms:
788
+ @return:
789
+ """
790
+ self.wait_finish()
791
+ while self.hold_work(0):
792
+ time.sleep(0.05)
793
+ time.sleep(time_in_ms / 1000.0)
794
+
795
+ def wait_finish(self, *values):
796
+ """
797
+ Wait finish should ensure that no additional commands be processed until the current buffer is completed. This
798
+ does not necessarily imply a change in mode as "finished_mode" would require. Just that the buffer be completed
799
+ before moving on.
800
+
801
+ @param values:
802
+ @return:
803
+ """
804
+
805
+ def temp_hold():
806
+ try:
807
+ return (
808
+ len(self.out_pipe) != 0 or self.service.controller.state == "wait"
809
+ )
810
+ except TypeError:
811
+ return False
812
+
813
+ self.temp_holds.append(temp_hold)
814
+
815
+ def function(self, function):
816
+ """
817
+ This command asks that this function be executed at the appropriate time within the spooled cycle.
818
+
819
+ @param function:
820
+ @return:
821
+ """
822
+ self.wait_finish()
823
+ while self.hold_work(0):
824
+ time.sleep(0.05)
825
+ function()
826
+
827
+ def beep(self):
828
+ """
829
+ This command asks that a beep be executed at the appropriate time within the spooled cycle.
830
+
831
+ @return:
832
+ """
833
+ self.wait_finish()
834
+ while self.hold_work(0):
835
+ time.sleep(0.05)
836
+ self.service("beep\n")
837
+
838
+ def console(self, value):
839
+ """
840
+ This asks that the console command be executed at the appropriate time within the spooled cycle.
841
+
842
+ @param value: console command
843
+ @return:
844
+ """
845
+ self.wait_finish()
846
+ while self.hold_work(0):
847
+ time.sleep(0.05)
848
+ self.service(value)
849
+
850
+ def signal(self, signal, *args):
851
+ """
852
+ This asks that this signal be broadcast.
853
+
854
+ @param signal:
855
+ @param args:
856
+ @return:
857
+ """
858
+ self.wait_finish()
859
+ while self.hold_work(0):
860
+ time.sleep(0.05)
861
+ self.service.signal(signal, *args)
862
+
863
+ ######################
864
+ # Property IO
865
+ ######################
866
+
867
+ @property
868
+ def is_left(self):
869
+ return self._x_engaged and not self._y_engaged and self._leftward
870
+
871
+ @property
872
+ def is_right(self):
873
+ return self._x_engaged and not self._y_engaged and not self._leftward
874
+
875
+ @property
876
+ def is_top(self):
877
+ return not self._x_engaged and self._y_engaged and self._topward
878
+
879
+ @property
880
+ def is_bottom(self):
881
+ return not self._x_engaged and self._y_engaged and not self._topward
882
+
883
+ @property
884
+ def is_angle(self):
885
+ return self._y_engaged and self._x_engaged
886
+
887
+ def set_prop(self, mask):
888
+ self.properties |= mask
889
+
890
+ def unset_prop(self, mask):
891
+ self.properties &= ~mask
892
+
893
+ def is_prop(self, mask):
894
+ return bool(self.properties & mask)
895
+
896
+ def toggle_prop(self, mask):
897
+ if self.is_prop(mask):
898
+ self.unset_prop(mask)
899
+ else:
900
+ self.set_prop(mask)
901
+
902
+ ######################
903
+ # PROTECTED DRIVER CODE
904
+ ######################
905
+
906
+ def _plotplanner_process(self):
907
+ """
908
+ Processes any data in the plot planner. Getting all relevant (x,y,on) plot values and performing the cardinal
909
+ movements. Or updating the laser state based on the settings of the cutcode.
910
+
911
+ @return:
912
+ """
913
+ if self.plot_data is None:
914
+ return False
915
+ # We don't know the length of a generator object
916
+ total = 0
917
+ current = 0
918
+ for x, y, on in self.plot_data:
919
+ current += 1
920
+ total = current
921
+ self._set_queue_status(current, total)
922
+ while self.hold_work(0):
923
+ time.sleep(0.05)
924
+ sx = self.native_x
925
+ sy = self.native_y
926
+ # print("x: %s, y: %s -- c: %s, %s" % (str(x), str(y), str(sx), str(sy)))
927
+ on = int(on)
928
+ if on > 1:
929
+ # Special Command.
930
+ if on & PLOT_FINISH: # Plot planner is ending.
931
+ self.rapid_mode()
932
+ break
933
+ elif on & PLOT_SETTING: # Plot planner settings have changed.
934
+ p_set = Parameters(self.plot_planner.settings)
935
+ if p_set.power != self.power:
936
+ self._set_power(p_set.power)
937
+ if (
938
+ p_set.raster_step_x != self.raster_step_x
939
+ or p_set.raster_step_y != self.raster_step_y
940
+ or p_set.speed != self.speed
941
+ or self.implicit_d_ratio != p_set.implicit_d_ratio
942
+ or self.implicit_accel != p_set.implicit_accel
943
+ ):
944
+ self._set_speed(p_set.speed)
945
+ self._set_step(p_set.raster_step_x, p_set.raster_step_y)
946
+ self._set_acceleration(p_set.implicit_accel)
947
+ self._set_d_ratio(p_set.implicit_d_ratio)
948
+ self.settings.update(p_set.settings)
949
+ elif on & PLOT_AXIS: # Major Axis.
950
+ # 0 means X Major / Horizontal.
951
+ # 1 means Y Major / Vertical
952
+ self._request_horizontal_major = bool(x == 0)
953
+ elif on & PLOT_DIRECTION:
954
+ # -1: Moving Left -x
955
+ # 1: Moving Right. +x
956
+ self._request_leftward = bool(x != 1)
957
+ # -1: Moving Bottom +y
958
+ # 1: Moving Top. -y
959
+ self._request_topward = bool(y != 1)
960
+ elif on & (
961
+ PLOT_RAPID | PLOT_JOG
962
+ ): # Plot planner requests position change.
963
+ if (
964
+ on & PLOT_RAPID
965
+ or self.state != DRIVER_STATE_PROGRAM
966
+ or self.service.rapid_override
967
+ ):
968
+ # Perform a rapid position change. Always perform this for raster moves.
969
+ # DRIVER_STATE_RASTER should call this code as well.
970
+ self.rapid_mode()
971
+ self._move_absolute(x, y)
972
+ else:
973
+ # Jog is performable and requested. # We have not flagged our direction or state.
974
+ self._jog_absolute(x, y, mode=self.service.opt_jog_mode)
975
+ continue
976
+ dx = x - sx
977
+ dy = y - sy
978
+ step_x = self.raster_step_x
979
+ step_y = self.raster_step_y
980
+ if step_x == 0 and step_y == 0:
981
+ # vector mode
982
+ self.program_mode()
983
+ else:
984
+ self.raster_mode()
985
+ if self._horizontal_major:
986
+ # Horizontal Rastering.
987
+ if dy != 0:
988
+ self._h_switch_g(dy)
989
+ else:
990
+ # Vertical Rastering.
991
+ if dx != 0:
992
+ self._v_switch_g(dx)
993
+ # Update dx, dy (if changed by switches)
994
+ dx = x - self.native_x
995
+ dy = y - self.native_y
996
+ self._goto_octent(dx, dy, on & 1)
997
+ self.plot_data = None
998
+ self._set_queue_status(0, 0)
999
+ return False
1000
+
1001
+ def _set_speed(self, speed=None):
1002
+ if self.speed != speed:
1003
+ self.speed = speed
1004
+ if self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
1005
+ self.state = DRIVER_STATE_MODECHANGE
1006
+
1007
+ def _set_d_ratio(self, d_ratio=None):
1008
+ if self.dratio != d_ratio:
1009
+ self.dratio = d_ratio
1010
+ if self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
1011
+ self.state = DRIVER_STATE_MODECHANGE
1012
+
1013
+ def _set_acceleration(self, accel=None):
1014
+ if self.acceleration != accel:
1015
+ self.acceleration = accel
1016
+ if self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
1017
+ self.state = DRIVER_STATE_MODECHANGE
1018
+
1019
+ def _set_step(self, step_x=None, step_y=None):
1020
+ if self.raster_step_x != step_x or self.raster_step_y != step_y:
1021
+ self.raster_step_x = step_x
1022
+ self.raster_step_y = step_y
1023
+ if self.state in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER):
1024
+ self.state = DRIVER_STATE_MODECHANGE
1025
+
1026
+ def _set_power(self, power=1000.0):
1027
+ self.power = power
1028
+ if self.power > 1000.0:
1029
+ self.power = 1000.0
1030
+ if self.power <= 0:
1031
+ self.power = 0.0
1032
+
1033
+ def _set_ppi(self, power=1000.0):
1034
+ self.power = power
1035
+ if self.power > 1000.0:
1036
+ self.power = 1000.0
1037
+ if self.power <= 0:
1038
+ self.power = 0.0
1039
+
1040
+ def _set_pwm(self, power=1000.0):
1041
+ self.power = power
1042
+ if self.power > 1000.0:
1043
+ self.power = 1000.0
1044
+ if self.power <= 0:
1045
+ self.power = 0.0
1046
+
1047
+ def _set_overscan(self, overscan=None):
1048
+ self.overscan = overscan
1049
+
1050
+ def _cut(self, x, y):
1051
+ self._goto(x, y, True)
1052
+
1053
+ def _jog(self, x, y, **kwargs):
1054
+ if self.is_relative:
1055
+ self._jog_relative(x, y, **kwargs)
1056
+ else:
1057
+ self._jog_absolute(x, y, **kwargs)
1058
+
1059
+ def _jog_absolute(self, x, y, **kwargs):
1060
+ self._jog_relative(x - self.native_x, y - self.native_y, **kwargs)
1061
+
1062
+ def _jog_relative(self, dx, dy, mode=0):
1063
+ self.laser_off()
1064
+ dx = int(round(dx))
1065
+ dy = int(round(dy))
1066
+ if mode == 0:
1067
+ self._nse_jog_event(dx, dy)
1068
+ elif mode == 1:
1069
+ self._mode_shift_on_the_fly(dx, dy)
1070
+ else:
1071
+ # Finish-out Jog
1072
+ self.rapid_mode()
1073
+ self._move_relative(dx, dy)
1074
+ self.program_mode()
1075
+
1076
+ def _nse_jog_event(self, dx=0, dy=0, speed=None):
1077
+ """
1078
+ NSE Jog events are performed from program or raster mode and skip out to rapid mode to perform
1079
+ a single jog command. This jog effect varies based on the horizontal vertical major setting and
1080
+ needs to counteract the jogged head according to those settings.
1081
+
1082
+ NSE jogs will not change the underlying mode even though they temporarily switch into
1083
+ rapid mode. nse jogs are not done in raster mode.
1084
+ """
1085
+ dx = int(round(dx))
1086
+ dy = int(round(dy))
1087
+ original_state = self.state
1088
+ self.state = DRIVER_STATE_RAPID
1089
+ self.laser = False
1090
+ if self._horizontal_major:
1091
+ if not self.is_left and dx >= 0:
1092
+ self(self.CODE_LEFT)
1093
+ if not self.is_right and dx <= 0:
1094
+ self(self.CODE_RIGHT)
1095
+ else:
1096
+ if not self.is_top and dy >= 0:
1097
+ self(self.CODE_TOP)
1098
+ if not self.is_bottom and dy <= 0:
1099
+ self(self.CODE_BOTTOM)
1100
+ self(b"N")
1101
+ self._goto_xy(dx, dy)
1102
+ self(b"SE")
1103
+ self(self._code_declare_directions())
1104
+ self.state = original_state
1105
+
1106
+ def _move(self, x, y):
1107
+ self._goto(x, y, False)
1108
+
1109
+ def _move_absolute(self, x, y):
1110
+ self._goto_absolute(x, y, False)
1111
+
1112
+ def _move_relative(self, x, y):
1113
+ self._goto_relative(x, y, False)
1114
+
1115
+ def _goto(self, x, y, cut):
1116
+ """
1117
+ Goto a position within a cut.
1118
+
1119
+ This depends on whether is_relative is set.
1120
+
1121
+ @param x:
1122
+ @param y:
1123
+ @param cut:
1124
+ @return:
1125
+ """
1126
+ if self.is_relative:
1127
+ self._goto_relative(x, y, cut)
1128
+ else:
1129
+ self._goto_absolute(x, y, cut)
1130
+
1131
+ def _goto_absolute(self, x, y, cut):
1132
+ """
1133
+ Goto absolute x and y. With cut set or not set.
1134
+
1135
+ @param x:
1136
+ @param y:
1137
+ @param cut:
1138
+ @return:
1139
+ """
1140
+ self._goto_relative(x - self.native_x, y - self.native_y, cut)
1141
+
1142
+ def _move_override_speed(self, dx, dy, x_speed, y_speed=None):
1143
+ """
1144
+ Rapid movement override. Should make programmed jogs.
1145
+
1146
+ @param dx: change in x
1147
+ @param dy: change in y
1148
+ @param x_speed: max allowed speed in x direction
1149
+ @param y_speed: max allowed speed in y direction
1150
+ @return:
1151
+ """
1152
+ if y_speed is None:
1153
+ y_speed = x_speed
1154
+ original_accel = self.acceleration
1155
+ original_speed = self.speed
1156
+ original_steps = self.raster_step_x, self.raster_step_y
1157
+
1158
+ # Do not allow custom accel codes, or raster steps.
1159
+ self._set_acceleration(None)
1160
+ self._set_step(0, 0)
1161
+ # We know we will travel dx, dy. Set the initial direction requests.
1162
+ self._request_leftward = dx < 0
1163
+ self._request_topward = dy < 0
1164
+ self._request_horizontal_major = abs(dx) > abs(dy)
1165
+ if y_speed <= x_speed and abs(dy) >= abs(dx):
1166
+ # y_speed is slowest and dy is larger than dx. The y-shift will take longer than x-shift. Combine.
1167
+ self._set_speed(y_speed)
1168
+ self.program_mode()
1169
+ dy_m = int(
1170
+ math.copysign(dx, dy)
1171
+ ) # magnitude of shorter in the direction of longer.
1172
+ self._goto_octent(dx, dy_m, on=False)
1173
+ self._goto_octent(0, dy - dy_m, on=False)
1174
+ elif x_speed <= y_speed and abs(dx) >= abs(dy):
1175
+ # x_speed is slowest and dx is larger than dy. The x-shift will take longer than y-shift. Combine.
1176
+ self._set_speed(x_speed)
1177
+ self.program_mode()
1178
+ dx_m = int(
1179
+ math.copysign(dy, dx)
1180
+ ) # magnitude of shorter in the direction of longer.
1181
+ self._goto_octent(dx_m, dy, on=False)
1182
+ self._goto_octent(dx - dx_m, 0, on=False)
1183
+ else:
1184
+ # The faster speed is going longer. The slower speed is going shorter. Full zig.
1185
+ if dx != 0:
1186
+ self._set_speed(x_speed)
1187
+ self.program_mode()
1188
+ self._goto_octent(dx, 0, on=False)
1189
+ if dy != 0:
1190
+ self._set_speed(y_speed)
1191
+ self.program_mode()
1192
+ self._goto_octent(0, dy, on=False)
1193
+ self.rapid_mode()
1194
+
1195
+ # We always restore the original settings.
1196
+ self._set_acceleration(original_accel)
1197
+ self._set_speed(original_speed)
1198
+ self._set_step(*original_steps)
1199
+
1200
+ def _move_in_rapid_mode(self, dx, dy, cut):
1201
+ if self.service.rapid_override and (dx != 0 or dy != 0):
1202
+ y_speed = self.service.rapid_override_speed_y
1203
+ x_speed = self.service.rapid_override_speed_x
1204
+ self._move_override_speed(dx, dy, x_speed, y_speed)
1205
+ return
1206
+ self(b"I")
1207
+ self._goto_xy(dx, dy)
1208
+ self(b"S1P\n")
1209
+ if not self.service.autolock:
1210
+ self(b"IS2P\n")
1211
+
1212
+ def _goto_relative(self, dx, dy, cut):
1213
+ """
1214
+ Goto relative dx, dy. With cut set or not set.
1215
+
1216
+ @param dx:
1217
+ @param dy:
1218
+ @param cut:
1219
+ @return:
1220
+ """
1221
+ if abs(dx) == 0 and abs(dy) == 0:
1222
+ return
1223
+ dx = int(round(dx))
1224
+ dy = int(round(dy))
1225
+ old_current = self.service.current
1226
+ if self.state == DRIVER_STATE_RAPID:
1227
+ self._move_in_rapid_mode(dx, dy, cut)
1228
+ elif self.state == DRIVER_STATE_RASTER:
1229
+ # goto in raster, switches to program to recall this function.
1230
+ self.program_mode()
1231
+ self._goto_relative(dx, dy, cut)
1232
+ return
1233
+ elif self.state == DRIVER_STATE_PROGRAM:
1234
+ mx = 0
1235
+ my = 0
1236
+ line = list(grouped(ZinglPlotter.plot_line(0, 0, dx, dy)))
1237
+ for x, y in line:
1238
+ self._goto_octent(x - mx, y - my, cut)
1239
+ mx = x
1240
+ my = y
1241
+ elif self.state == DRIVER_STATE_FINISH:
1242
+ self._goto_xy(dx, dy)
1243
+ self(b"N")
1244
+ elif self.state == DRIVER_STATE_MODECHANGE:
1245
+ self._mode_shift_on_the_fly(dx, dy)
1246
+
1247
+ new_current = self.service.current
1248
+ if self._signal_updates:
1249
+ self.service.signal(
1250
+ "driver;position",
1251
+ (old_current[0], old_current[1], new_current[0], new_current[1]),
1252
+ )
1253
+
1254
+ def _mode_shift_on_the_fly(self, dx=0, dy=0):
1255
+ """
1256
+ Mode-shift on the fly changes the current modes while in programmed or raster mode
1257
+ this exits with a @ command that resets the modes. A movement operation can be added after
1258
+ the speed code and before the return to into programmed or raster mode.
1259
+
1260
+ This switch is often avoided because testing revealed some chance of a runaway during reset
1261
+ switching.
1262
+
1263
+ If the raster step has been changed from zero this can result in shifting from program to raster mode
1264
+ """
1265
+ dx = int(round(dx))
1266
+ dy = int(round(dy))
1267
+ self(b"@NSE")
1268
+ self.laser = False
1269
+ self.state = DRIVER_STATE_RAPID
1270
+ self.program_mode(dx, dy)
1271
+
1272
+ def _h_switch_g(self, dy: float):
1273
+ """
1274
+ Horizontal switch with a Gvalue set. The board will automatically step according to the step_value_set.
1275
+
1276
+ @return:
1277
+ """
1278
+ set_step = self._raster_step_g_value
1279
+ if isinstance(set_step, tuple):
1280
+ set_step = set_step[self._raster_step_swing_index % len(set_step)]
1281
+
1282
+ # correct for fractional stepping
1283
+ self._raster_step_fractional_remainder += dy
1284
+ delta = math.trunc(self._raster_step_fractional_remainder)
1285
+ self._raster_step_fractional_remainder -= delta
1286
+
1287
+ step_amount = -abs(set_step) if self._topward else abs(set_step)
1288
+ remaining = delta - step_amount
1289
+ if (
1290
+ remaining > 0
1291
+ and self._topward
1292
+ or remaining < 0
1293
+ and not self._topward
1294
+ or abs(remaining) > 15
1295
+ ):
1296
+ # Remaining value is in the wrong direction, abort and move.
1297
+ self.finished_mode()
1298
+ self._move_relative(0, remaining)
1299
+ self.raster_mode()
1300
+ remaining = 0
1301
+ if remaining:
1302
+ self._goto_octent(
1303
+ -abs(remaining) if self._leftward else abs(remaining), remaining, False
1304
+ )
1305
+ self._x_engaged = True
1306
+ self._y_engaged = False
1307
+ # We reverse direction and step.
1308
+ if self._leftward:
1309
+ self(self.CODE_RIGHT)
1310
+ self._leftward = False
1311
+ else:
1312
+ self(self.CODE_LEFT)
1313
+ self._leftward = True
1314
+ self.native_y += step_amount
1315
+ self.laser = False
1316
+ self._raster_step_swing_index += 1
1317
+
1318
+ def _v_switch_g(self, dx: float):
1319
+ """
1320
+ Vertical switch with a Gvalue set. The board will automatically step according to the step_value_set.
1321
+
1322
+ @return:
1323
+ """
1324
+ set_step = self._raster_step_g_value
1325
+ if isinstance(set_step, tuple):
1326
+ set_step = set_step[self._raster_step_swing_index % len(set_step)]
1327
+
1328
+ # correct for fractional stepping
1329
+ self._raster_step_fractional_remainder += dx
1330
+ delta = math.trunc(self._raster_step_fractional_remainder)
1331
+ self._raster_step_fractional_remainder -= delta
1332
+
1333
+ step_amount = -set_step if self._leftward else set_step
1334
+ remaining = delta - step_amount
1335
+ if (
1336
+ remaining > 0
1337
+ and self._leftward
1338
+ or remaining < 0
1339
+ and not self._leftward
1340
+ or abs(remaining) > 15
1341
+ ):
1342
+ # Remaining value is in the wrong direction, abort and move.
1343
+ self.finished_mode()
1344
+ self._move_relative(remaining, 0)
1345
+ self.raster_mode()
1346
+ remaining = 0
1347
+ if remaining:
1348
+ self._goto_octent(
1349
+ remaining, -abs(remaining) if self._topward else abs(remaining), False
1350
+ )
1351
+ self._y_engaged = True
1352
+ self._x_engaged = False
1353
+ # We reverse direction and step.
1354
+ if self._topward:
1355
+ self(self.CODE_BOTTOM)
1356
+ self._topward = False
1357
+ else:
1358
+ self(self.CODE_TOP)
1359
+ self._topward = True
1360
+ self.native_x += step_amount
1361
+ self.laser = False
1362
+ self._raster_step_swing_index += 1
1363
+
1364
+ def _reset_modes(self):
1365
+ self.laser = False
1366
+ self._request_leftward = None
1367
+ self._request_topward = None
1368
+ self._request_horizontal_major = None
1369
+ self._topward = False
1370
+ self._leftward = False
1371
+ self._x_engaged = False
1372
+ self._y_engaged = False
1373
+ self._horizontal_major = False
1374
+
1375
+ def _goto_xy(self, dx, dy, on=None):
1376
+ rapid = self.state not in (DRIVER_STATE_PROGRAM, DRIVER_STATE_RASTER)
1377
+ if dx != 0:
1378
+ self.native_x += dx
1379
+ if dx > 0: # Moving right
1380
+ if not self.is_right or rapid:
1381
+ self(self.CODE_RIGHT)
1382
+ self._leftward = False
1383
+ else: # Moving left
1384
+ if not self.is_left or rapid:
1385
+ self(self.CODE_LEFT)
1386
+ self._leftward = True
1387
+ self._x_engaged = True
1388
+ self._y_engaged = False
1389
+ if on is not None:
1390
+ if on:
1391
+ self.laser_on()
1392
+ else:
1393
+ self.laser_off()
1394
+ self(lhymicro_distance(abs(dx)))
1395
+ if dy != 0:
1396
+ self.native_y += dy
1397
+ if dy > 0: # Moving bottom
1398
+ if not self.is_bottom or rapid:
1399
+ self(self.CODE_BOTTOM)
1400
+ self._topward = False
1401
+ else: # Moving top
1402
+ if not self.is_top or rapid:
1403
+ self(self.CODE_TOP)
1404
+ self._topward = True
1405
+ self._x_engaged = False
1406
+ self._y_engaged = True
1407
+ if on is not None:
1408
+ if on:
1409
+ self.laser_on()
1410
+ else:
1411
+ self.laser_off()
1412
+ self(lhymicro_distance(abs(dy)))
1413
+
1414
+ def _goto_octent(self, dx, dy, on):
1415
+ old_current = self.service.current
1416
+ if dx == 0 and dy == 0:
1417
+ return
1418
+ if abs(dx) == abs(dy):
1419
+ self._x_engaged = True # Set both on
1420
+ self._y_engaged = True
1421
+ if dx > 0: # Moving right
1422
+ if self._leftward:
1423
+ self(self.CODE_RIGHT)
1424
+ self._leftward = False
1425
+ else: # Moving left
1426
+ if not self._leftward:
1427
+ self(self.CODE_LEFT)
1428
+ self._leftward = True
1429
+ if dy > 0: # Moving bottom
1430
+ if self._topward:
1431
+ self(self.CODE_BOTTOM)
1432
+ self._topward = False
1433
+ else: # Moving top
1434
+ if not self._topward:
1435
+ self(self.CODE_TOP)
1436
+ self._topward = True
1437
+ self.native_x += dx
1438
+ self.native_y += dy
1439
+ self(self.CODE_ANGLE)
1440
+ if on:
1441
+ self.laser_on()
1442
+ else:
1443
+ self.laser_off()
1444
+ self(lhymicro_distance(abs(dy)))
1445
+ else:
1446
+ self._goto_xy(dx, dy, on=on)
1447
+
1448
+ new_current = self.service.current
1449
+ if self._signal_updates:
1450
+ self.service.signal(
1451
+ "driver;position",
1452
+ (old_current[0], old_current[1], new_current[0], new_current[1]),
1453
+ )
1454
+
1455
+ def _code_declare_directions(self):
1456
+ x_dir = self.CODE_LEFT if self._leftward else self.CODE_RIGHT
1457
+ y_dir = self.CODE_TOP if self._topward else self.CODE_BOTTOM
1458
+ if self._horizontal_major:
1459
+ self._x_engaged = True
1460
+ self._y_engaged = False
1461
+ return y_dir + x_dir
1462
+ else:
1463
+ self._x_engaged = False
1464
+ self._y_engaged = True
1465
+ return x_dir + y_dir
1466
+