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

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