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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1194 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1858 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4595 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4315 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +934 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/testcases.py +105 -0
  66. meerk40t/core/elements/trace.py +651 -563
  67. meerk40t/core/elements/tree_commands.py +415 -409
  68. meerk40t/core/elements/undo_redo.py +116 -58
  69. meerk40t/core/elements/wordlist.py +319 -200
  70. meerk40t/core/exceptions.py +9 -9
  71. meerk40t/core/laserjob.py +220 -220
  72. meerk40t/core/logging.py +63 -63
  73. meerk40t/core/node/blobnode.py +83 -86
  74. meerk40t/core/node/bootstrap.py +105 -103
  75. meerk40t/core/node/branch_elems.py +40 -31
  76. meerk40t/core/node/branch_ops.py +45 -38
  77. meerk40t/core/node/branch_regmark.py +48 -41
  78. meerk40t/core/node/cutnode.py +29 -32
  79. meerk40t/core/node/effect_hatch.py +375 -257
  80. meerk40t/core/node/effect_warp.py +398 -0
  81. meerk40t/core/node/effect_wobble.py +441 -309
  82. meerk40t/core/node/elem_ellipse.py +404 -309
  83. meerk40t/core/node/elem_image.py +1082 -801
  84. meerk40t/core/node/elem_line.py +358 -292
  85. meerk40t/core/node/elem_path.py +259 -201
  86. meerk40t/core/node/elem_point.py +129 -102
  87. meerk40t/core/node/elem_polyline.py +310 -246
  88. meerk40t/core/node/elem_rect.py +376 -286
  89. meerk40t/core/node/elem_text.py +445 -418
  90. meerk40t/core/node/filenode.py +59 -40
  91. meerk40t/core/node/groupnode.py +138 -74
  92. meerk40t/core/node/image_processed.py +777 -766
  93. meerk40t/core/node/image_raster.py +156 -113
  94. meerk40t/core/node/layernode.py +31 -31
  95. meerk40t/core/node/mixins.py +135 -107
  96. meerk40t/core/node/node.py +1427 -1304
  97. meerk40t/core/node/nutils.py +117 -114
  98. meerk40t/core/node/op_cut.py +463 -335
  99. meerk40t/core/node/op_dots.py +296 -251
  100. meerk40t/core/node/op_engrave.py +414 -311
  101. meerk40t/core/node/op_image.py +755 -369
  102. meerk40t/core/node/op_raster.py +787 -522
  103. meerk40t/core/node/place_current.py +37 -40
  104. meerk40t/core/node/place_point.py +329 -126
  105. meerk40t/core/node/refnode.py +58 -47
  106. meerk40t/core/node/rootnode.py +225 -219
  107. meerk40t/core/node/util_console.py +48 -48
  108. meerk40t/core/node/util_goto.py +84 -65
  109. meerk40t/core/node/util_home.py +61 -61
  110. meerk40t/core/node/util_input.py +102 -102
  111. meerk40t/core/node/util_output.py +102 -102
  112. meerk40t/core/node/util_wait.py +65 -65
  113. meerk40t/core/parameters.py +709 -707
  114. meerk40t/core/planner.py +875 -785
  115. meerk40t/core/plotplanner.py +656 -652
  116. meerk40t/core/space.py +120 -113
  117. meerk40t/core/spoolers.py +706 -705
  118. meerk40t/core/svg_io.py +1836 -1549
  119. meerk40t/core/treeop.py +534 -445
  120. meerk40t/core/undos.py +278 -124
  121. meerk40t/core/units.py +784 -680
  122. meerk40t/core/view.py +393 -322
  123. meerk40t/core/webhelp.py +62 -62
  124. meerk40t/core/wordlist.py +513 -504
  125. meerk40t/cylinder/cylinder.py +247 -0
  126. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  127. meerk40t/cylinder/gui/gui.py +24 -0
  128. meerk40t/device/__init__.py +1 -1
  129. meerk40t/device/basedevice.py +322 -123
  130. meerk40t/device/devicechoices.py +50 -0
  131. meerk40t/device/dummydevice.py +163 -128
  132. meerk40t/device/gui/defaultactions.py +618 -602
  133. meerk40t/device/gui/effectspanel.py +114 -0
  134. meerk40t/device/gui/formatterpanel.py +253 -290
  135. meerk40t/device/gui/warningpanel.py +337 -260
  136. meerk40t/device/mixins.py +13 -13
  137. meerk40t/dxf/__init__.py +1 -1
  138. meerk40t/dxf/dxf_io.py +766 -554
  139. meerk40t/dxf/plugin.py +47 -35
  140. meerk40t/external_plugins.py +79 -79
  141. meerk40t/external_plugins_build.py +28 -28
  142. meerk40t/extra/cag.py +112 -116
  143. meerk40t/extra/coolant.py +403 -0
  144. meerk40t/extra/encode_detect.py +204 -0
  145. meerk40t/extra/ezd.py +1165 -1165
  146. meerk40t/extra/hershey.py +834 -340
  147. meerk40t/extra/imageactions.py +322 -316
  148. meerk40t/extra/inkscape.py +628 -622
  149. meerk40t/extra/lbrn.py +424 -424
  150. meerk40t/extra/outerworld.py +283 -0
  151. meerk40t/extra/param_functions.py +1542 -1556
  152. meerk40t/extra/potrace.py +257 -253
  153. meerk40t/extra/serial_exchange.py +118 -0
  154. meerk40t/extra/updater.py +602 -453
  155. meerk40t/extra/vectrace.py +147 -146
  156. meerk40t/extra/winsleep.py +83 -83
  157. meerk40t/extra/xcs_reader.py +597 -0
  158. meerk40t/fill/fills.py +781 -335
  159. meerk40t/fill/patternfill.py +1061 -1061
  160. meerk40t/fill/patterns.py +614 -567
  161. meerk40t/grbl/control.py +87 -87
  162. meerk40t/grbl/controller.py +990 -903
  163. meerk40t/grbl/device.py +1084 -768
  164. meerk40t/grbl/driver.py +989 -771
  165. meerk40t/grbl/emulator.py +532 -497
  166. meerk40t/grbl/gcodejob.py +783 -767
  167. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  168. meerk40t/grbl/gui/grblcontroller.py +485 -271
  169. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  170. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  171. meerk40t/grbl/gui/gui.py +147 -116
  172. meerk40t/grbl/interpreter.py +44 -44
  173. meerk40t/grbl/loader.py +22 -22
  174. meerk40t/grbl/mock_connection.py +56 -56
  175. meerk40t/grbl/plugin.py +294 -264
  176. meerk40t/grbl/serial_connection.py +93 -88
  177. meerk40t/grbl/tcp_connection.py +81 -79
  178. meerk40t/grbl/ws_connection.py +112 -0
  179. meerk40t/gui/__init__.py +1 -1
  180. meerk40t/gui/about.py +2042 -296
  181. meerk40t/gui/alignment.py +1644 -1608
  182. meerk40t/gui/autoexec.py +199 -0
  183. meerk40t/gui/basicops.py +791 -670
  184. meerk40t/gui/bufferview.py +77 -71
  185. meerk40t/gui/busy.py +232 -133
  186. meerk40t/gui/choicepropertypanel.py +1662 -1469
  187. meerk40t/gui/consolepanel.py +706 -542
  188. meerk40t/gui/devicepanel.py +687 -581
  189. meerk40t/gui/dialogoptions.py +110 -107
  190. meerk40t/gui/executejob.py +316 -306
  191. meerk40t/gui/fonts.py +90 -90
  192. meerk40t/gui/functionwrapper.py +252 -0
  193. meerk40t/gui/gui_mixins.py +729 -0
  194. meerk40t/gui/guicolors.py +205 -182
  195. meerk40t/gui/help_assets/help_assets.py +218 -201
  196. meerk40t/gui/helper.py +154 -0
  197. meerk40t/gui/hersheymanager.py +1440 -846
  198. meerk40t/gui/icons.py +3422 -2747
  199. meerk40t/gui/imagesplitter.py +555 -508
  200. meerk40t/gui/keymap.py +354 -344
  201. meerk40t/gui/laserpanel.py +897 -806
  202. meerk40t/gui/laserrender.py +1470 -1232
  203. meerk40t/gui/lasertoolpanel.py +805 -793
  204. meerk40t/gui/magnetoptions.py +436 -0
  205. meerk40t/gui/materialmanager.py +2944 -0
  206. meerk40t/gui/materialtest.py +1722 -1694
  207. meerk40t/gui/mkdebug.py +646 -359
  208. meerk40t/gui/mwindow.py +163 -140
  209. meerk40t/gui/navigationpanels.py +2605 -2467
  210. meerk40t/gui/notes.py +143 -142
  211. meerk40t/gui/opassignment.py +414 -410
  212. meerk40t/gui/operation_info.py +310 -299
  213. meerk40t/gui/plugin.py +500 -328
  214. meerk40t/gui/position.py +714 -669
  215. meerk40t/gui/preferences.py +901 -650
  216. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  217. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  218. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  219. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  220. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  221. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  222. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  223. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  224. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  225. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  226. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  227. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  228. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  229. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  230. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  231. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  232. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  233. meerk40t/gui/propertypanels/textproperty.py +770 -755
  234. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  235. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  236. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  237. meerk40t/gui/ribbon.py +2471 -2210
  238. meerk40t/gui/scene/scene.py +1100 -1051
  239. meerk40t/gui/scene/sceneconst.py +22 -22
  240. meerk40t/gui/scene/scenepanel.py +439 -349
  241. meerk40t/gui/scene/scenespacewidget.py +365 -365
  242. meerk40t/gui/scene/widget.py +518 -505
  243. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  244. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  245. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  246. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  247. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  248. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  249. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  250. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  251. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  252. meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
  253. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  254. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  255. meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
  256. meerk40t/gui/simpleui.py +362 -333
  257. meerk40t/gui/simulation.py +2451 -2094
  258. meerk40t/gui/snapoptions.py +208 -203
  259. meerk40t/gui/spoolerpanel.py +1227 -1180
  260. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  261. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  262. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  263. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  264. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  265. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  266. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  267. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  268. meerk40t/gui/themes.py +200 -78
  269. meerk40t/gui/tips.py +590 -0
  270. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  271. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  272. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  273. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  274. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  275. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  276. meerk40t/gui/toolwidgets/toolline.py +39 -144
  277. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  278. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  279. meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
  280. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  281. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  282. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  283. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  284. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  285. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  286. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  287. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  288. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  289. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  290. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  291. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  292. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  293. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  294. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  295. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  296. meerk40t/gui/usbconnect.py +98 -91
  297. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  298. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  299. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  300. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  301. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  302. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  303. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  304. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  305. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  306. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  307. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  308. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  309. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  310. meerk40t/gui/wordlisteditor.py +985 -931
  311. meerk40t/gui/wxmeerk40t.py +1447 -1169
  312. meerk40t/gui/wxmmain.py +5644 -4112
  313. meerk40t/gui/wxmribbon.py +1591 -1076
  314. meerk40t/gui/wxmscene.py +1631 -1453
  315. meerk40t/gui/wxmtree.py +2416 -2089
  316. meerk40t/gui/wxutils.py +1769 -1099
  317. meerk40t/gui/zmatrix.py +102 -102
  318. meerk40t/image/__init__.py +1 -1
  319. meerk40t/image/dither.py +429 -0
  320. meerk40t/image/imagetools.py +2793 -2269
  321. meerk40t/internal_plugins.py +150 -130
  322. meerk40t/kernel/__init__.py +63 -12
  323. meerk40t/kernel/channel.py +259 -212
  324. meerk40t/kernel/context.py +538 -538
  325. meerk40t/kernel/exceptions.py +41 -41
  326. meerk40t/kernel/functions.py +463 -414
  327. meerk40t/kernel/jobs.py +100 -100
  328. meerk40t/kernel/kernel.py +3828 -3571
  329. meerk40t/kernel/lifecycles.py +71 -71
  330. meerk40t/kernel/module.py +49 -49
  331. meerk40t/kernel/service.py +147 -147
  332. meerk40t/kernel/settings.py +383 -343
  333. meerk40t/lihuiyu/controller.py +883 -876
  334. meerk40t/lihuiyu/device.py +1181 -1069
  335. meerk40t/lihuiyu/driver.py +1466 -1372
  336. meerk40t/lihuiyu/gui/gui.py +127 -106
  337. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  338. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  339. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  340. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  341. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  342. meerk40t/lihuiyu/interpreter.py +53 -53
  343. meerk40t/lihuiyu/laserspeed.py +450 -450
  344. meerk40t/lihuiyu/loader.py +90 -90
  345. meerk40t/lihuiyu/parser.py +404 -404
  346. meerk40t/lihuiyu/plugin.py +101 -102
  347. meerk40t/lihuiyu/tcp_connection.py +111 -109
  348. meerk40t/main.py +231 -165
  349. meerk40t/moshi/builder.py +788 -781
  350. meerk40t/moshi/controller.py +505 -499
  351. meerk40t/moshi/device.py +495 -442
  352. meerk40t/moshi/driver.py +862 -696
  353. meerk40t/moshi/gui/gui.py +78 -76
  354. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  355. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  356. meerk40t/moshi/plugin.py +43 -43
  357. meerk40t/network/console_server.py +140 -57
  358. meerk40t/network/kernelserver.py +10 -9
  359. meerk40t/network/tcp_server.py +142 -140
  360. meerk40t/network/udp_server.py +103 -77
  361. meerk40t/network/web_server.py +404 -0
  362. meerk40t/newly/controller.py +1158 -1144
  363. meerk40t/newly/device.py +874 -732
  364. meerk40t/newly/driver.py +540 -412
  365. meerk40t/newly/gui/gui.py +219 -188
  366. meerk40t/newly/gui/newlyconfig.py +116 -101
  367. meerk40t/newly/gui/newlycontroller.py +193 -186
  368. meerk40t/newly/gui/operationproperties.py +51 -51
  369. meerk40t/newly/mock_connection.py +82 -82
  370. meerk40t/newly/newly_params.py +56 -56
  371. meerk40t/newly/plugin.py +1214 -1246
  372. meerk40t/newly/usb_connection.py +322 -322
  373. meerk40t/rotary/gui/gui.py +52 -46
  374. meerk40t/rotary/gui/rotarysettings.py +240 -232
  375. meerk40t/rotary/rotary.py +202 -98
  376. meerk40t/ruida/control.py +291 -91
  377. meerk40t/ruida/controller.py +138 -1088
  378. meerk40t/ruida/device.py +676 -231
  379. meerk40t/ruida/driver.py +534 -472
  380. meerk40t/ruida/emulator.py +1494 -1491
  381. meerk40t/ruida/exceptions.py +4 -4
  382. meerk40t/ruida/gui/gui.py +71 -76
  383. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  384. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  385. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  386. meerk40t/ruida/loader.py +54 -52
  387. meerk40t/ruida/mock_connection.py +57 -109
  388. meerk40t/ruida/plugin.py +124 -87
  389. meerk40t/ruida/rdjob.py +2084 -945
  390. meerk40t/ruida/serial_connection.py +116 -0
  391. meerk40t/ruida/tcp_connection.py +146 -0
  392. meerk40t/ruida/udp_connection.py +73 -0
  393. meerk40t/svgelements.py +9671 -9669
  394. meerk40t/tools/driver_to_path.py +584 -579
  395. meerk40t/tools/geomstr.py +5583 -4680
  396. meerk40t/tools/jhfparser.py +357 -292
  397. meerk40t/tools/kerftest.py +904 -890
  398. meerk40t/tools/livinghinges.py +1168 -1033
  399. meerk40t/tools/pathtools.py +987 -949
  400. meerk40t/tools/pmatrix.py +234 -0
  401. meerk40t/tools/pointfinder.py +942 -942
  402. meerk40t/tools/polybool.py +941 -940
  403. meerk40t/tools/rasterplotter.py +1660 -547
  404. meerk40t/tools/shxparser.py +1047 -901
  405. meerk40t/tools/ttfparser.py +726 -446
  406. meerk40t/tools/zinglplotter.py +595 -593
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
  408. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
  409. meerk40t-0.9.7020.dist-info/RECORD +446 -0
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
  412. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
  413. meerk40t/balormk/elementlightjob.py +0 -159
  414. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  415. test/bootstrap.py +0 -63
  416. test/test_cli.py +0 -12
  417. test/test_core_cutcode.py +0 -418
  418. test/test_core_elements.py +0 -144
  419. test/test_core_plotplanner.py +0 -397
  420. test/test_core_viewports.py +0 -312
  421. test/test_drivers_grbl.py +0 -108
  422. test/test_drivers_lihuiyu.py +0 -443
  423. test/test_drivers_newly.py +0 -113
  424. test/test_element_degenerate_points.py +0 -43
  425. test/test_elements_classify.py +0 -97
  426. test/test_elements_penbox.py +0 -22
  427. test/test_file_svg.py +0 -176
  428. test/test_fill.py +0 -155
  429. test/test_geomstr.py +0 -1523
  430. test/test_geomstr_nodes.py +0 -18
  431. test/test_imagetools_actualize.py +0 -306
  432. test/test_imagetools_wizard.py +0 -258
  433. test/test_kernel.py +0 -200
  434. test/test_laser_speeds.py +0 -3303
  435. test/test_length.py +0 -57
  436. test/test_lifecycle.py +0 -66
  437. test/test_operations.py +0 -251
  438. test/test_operations_hatch.py +0 -57
  439. test/test_ruida.py +0 -19
  440. test/test_spooler.py +0 -22
  441. test/test_tools_rasterplotter.py +0 -29
  442. test/test_wobble.py +0 -133
  443. test/test_zingl.py +0 -124
  444. {test → meerk40t/cylinder}/__init__.py +0 -0
  445. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  446. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
meerk40t/extra/ezd.py CHANGED
@@ -1,1165 +1,1165 @@
1
- """
2
- Parser for .ezd files.
3
-
4
- These are a working type file produced by EZCad2™. They contain several pens and different settings that were used by
5
- the program when the file was saved. The vector objects consist of a series of laser-ready commands/shapes which refer
6
- to the required pen. Some modification objects like hatch and spiral work like a group containing other sub-elements
7
- and also contain the cached curve/path data. The image objects contain a standard 24 bit bitmap image. All elements
8
- are coordinated relative to the center of the working area and, it is much more common to be given the center point than
9
- a specific corner. Nearly all coordinates are in mm, and denote the deviation from the center point.
10
-
11
- """
12
- import math
13
- import struct
14
- from io import BytesIO
15
-
16
- from meerk40t.core.exceptions import BadFileError
17
- from meerk40t.core.units import UNITS_PER_INCH, UNITS_PER_MM
18
- from meerk40t.svgelements import Color, Matrix, Path, Polygon
19
-
20
-
21
- def plugin(kernel, lifecycle):
22
- if lifecycle == "boot":
23
- context = kernel.root
24
- elif lifecycle == "register":
25
- kernel.register("load/EZDLoader", EZDLoader)
26
- pass
27
- elif lifecycle == "shutdown":
28
- pass
29
-
30
-
31
- def _parse_struct(file):
32
- """
33
- Parses a generic structure for ezd files. These are a count of objects. Then for each data entry int32le:length
34
- followed by data of that length.
35
-
36
- @param file:
37
- @return:
38
- """
39
- p = list()
40
- count = struct.unpack("<i", file.read(4))[0]
41
- for i in range(count):
42
- b = file.read(4)
43
- if len(b) != 4:
44
- return p
45
- (length,) = struct.unpack("<i", b)
46
- if length == -1:
47
- return p
48
- b = file.read(length)
49
- if len(b) != length:
50
- return p
51
- p.append(b)
52
- return p
53
-
54
-
55
- def _interpret(data, index, type):
56
- """
57
- Provide a specific hint for how to interpret a chunk of bytes. There are cases where 16 bytes could be a point,
58
- consisting of two floating points, but could also be a string. This is used to force the typing to use the correct
59
- method.
60
-
61
- @param data:
62
- @param index:
63
- @param type:
64
- @return:
65
- """
66
- if type == str:
67
- data[index] = data[index].decode("utf_16").strip("\x00")
68
- elif type == "point":
69
- data[index] = struct.unpack("2d", data[index])
70
- elif type == "short":
71
- (data[index],) = struct.unpack("<H", data[index])
72
- elif type == int:
73
- (data[index],) = struct.unpack("<i", data[index])
74
- elif type == float:
75
- (data[index],) = struct.unpack("d", data[index])
76
- elif type == "matrix":
77
- data[index] = struct.unpack("9d", data[index])
78
-
79
-
80
- def _construct(data):
81
- """
82
- For each element of data (that is a bytes object), interpret them as their most common type.
83
-
84
- @param data:
85
- @return:
86
- """
87
- for i in range(len(data)):
88
- b = data[i]
89
- length = len(b)
90
- if not isinstance(b, (bytes, bytearray)):
91
- continue
92
- if length == 2:
93
- _interpret(data, i, "short")
94
- elif length == 4:
95
- _interpret(data, i, int)
96
- elif length == 8:
97
- _interpret(data, i, float)
98
- elif length == 16:
99
- _interpret(data, i, "point")
100
- elif length == 60:
101
- _interpret(data, i, str)
102
- elif length == 72:
103
- _interpret(data, i, "matrix")
104
- elif length == 0:
105
- data[i] = None
106
- return data
107
-
108
-
109
- def _huffman_decode_python(file, uncompressed_length):
110
- """
111
- Python fallback for huffman decoding of the vector table.
112
-
113
- @param file:
114
- @param uncompressed_length:
115
- @return:
116
- """
117
- huffman_dict = {}
118
- table_length = struct.unpack("<H", file.read(2))[0]
119
- for i in range(table_length):
120
- character, bb, length = struct.unpack("<BIH", file.read(7))
121
- bits = "{:032b}".format(bb)[-length:]
122
- huffman_dict[bits] = character
123
- data = file.read()
124
-
125
- def bit_generator():
126
- for d in data:
127
- yield from "{:08b}".format(d)
128
-
129
- q = bytearray()
130
- c = ""
131
- for b in bit_generator():
132
- c += b
133
- m = huffman_dict.get(c)
134
- if m is not None:
135
- q.append(m)
136
- c = ""
137
- if len(q) >= uncompressed_length:
138
- return q
139
- return q
140
-
141
-
142
- def _huffman_decode_bitarray(file, uncompressed_length):
143
- """
144
- Bitarray decoding of huffman table found in the vector table section.
145
-
146
- @param file:
147
- @param uncompressed_length:
148
- @return:
149
- """
150
- from bitarray import bitarray
151
-
152
- huffman_dict = {}
153
- table_length = struct.unpack("<H", file.read(2))[0]
154
- for i in range(table_length):
155
- character, bb, length = struct.unpack("<BIH", file.read(7))
156
- bits = bitarray("{:032b}".format(bb)[-length:])
157
- huffman_dict[character] = bits
158
- a = bitarray()
159
- a.frombytes(file.read())
160
- while True:
161
- try:
162
- return bytearray(a.decode(huffman_dict))
163
- except ValueError:
164
- a = a[:-1]
165
-
166
-
167
- class Pen:
168
- def __init__(self, file):
169
- """
170
- Parse pen with the given file.
171
- """
172
- args = _parse_struct(file)
173
- _interpret(args, 1, str)
174
- _construct(args)
175
-
176
- self.color = Color(bgr=args[0])
177
- self.label = args[1]
178
- self.mark_enable = args[2]
179
- self.passes = args[4] # Loop Count
180
- if self.passes >= 1:
181
- self.passes_custom = True
182
- self.speed = args[5]
183
- self.power = args[6] * 10.0
184
- self.frequency = args[7] / 1000.0
185
- self.start_tc = args[9]
186
- self.end_tc = args[10]
187
- self.polygon_tc = args[11]
188
- self.jump_speed = args[12]
189
- self.jump_min_delay = args[13]
190
- self.jump_max_delay = args[14]
191
- self.opt_start_length = args[16]
192
- self.opt_end_length = args[15]
193
- self.time_per_point = args[17]
194
- self.pulse_per_point = args[21]
195
- self.laser_off_tc = args[23]
196
- self.wobble_enable = args[26]
197
- self.wobble_diameter = args[27]
198
- self.wobble_distance = args[28]
199
-
200
- try:
201
- self.add_endpoints = args[29]
202
- self.add_endpoint_distance = args[30]
203
- self.add_endpoint_time_per_point = args[32]
204
- self.add_endpoint_point_distance = args[31]
205
- self.add_endpoints_point_cycles = args[33]
206
- self.opt_enable = args[40]
207
- self.break_angle = args[41]
208
-
209
- self.jump_min_jump_delay2 = args[37]
210
- self.jump_max_delay2 = args[38]
211
- self.jump_speed_max_limit = args[39]
212
- except IndexError:
213
- pass
214
-
215
-
216
- class EZCFile:
217
- """
218
- Parse the EZCFile given file as a stream.
219
- """
220
-
221
- def __init__(self, file):
222
- self._locations = {}
223
- self.pens = []
224
- self.objects = []
225
- self.fonts = []
226
- self._preview_bitmap = list()
227
- self._prevector = None
228
- self.parse_header(file)
229
- self.parse_seektable(file)
230
- self.parse_unknown_nontable(file)
231
- self.parse_tables(file)
232
-
233
- def parse_header(self, file):
234
- """
235
- Parse file header.
236
-
237
- @param file:
238
- @return:
239
- """
240
- magic_number = file.read(16)
241
- header = magic_number.decode("utf_16")
242
- if header != "EZCADUNI":
243
- return False
244
- v0 = struct.unpack("<i", file.read(4)) # 0
245
- v1 = struct.unpack("<i", file.read(4)) # 2001
246
- s1 = file.read(60)
247
- s1 = s1.decode("utf-16")
248
- s2 = file.read(60)
249
- s2 = s2.decode("utf-16")
250
- s3 = file.read(60)
251
- s3 = s3.decode("utf-16")
252
- s4 = file.read(140)
253
-
254
- def parse_seektable(self, file):
255
- """
256
- The second item in the file after the header is the seek table lookup. This provides the location in absolute
257
- position in the file of the table locations.
258
- @param file:
259
- @return:
260
- """
261
- self._locations["preview"] = struct.unpack("<i", file.read(4))[0]
262
- self._locations["v1"] = struct.unpack("<i", file.read(4))[0]
263
- self._locations["pens"] = struct.unpack("<i", file.read(4))[0]
264
- self._locations["font"] = struct.unpack("<i", file.read(4))[0]
265
- self._locations["v4"] = struct.unpack("<i", file.read(4))[0]
266
- self._locations["vectors"] = struct.unpack("<i", file.read(4))[0]
267
- self._locations["prevectors"] = struct.unpack("<i", file.read(4))[0]
268
-
269
- def parse_unknown_nontable(self, file):
270
- """
271
- This is a non-table section. It could be padding for future seek tables entries or have some unknown meaning.
272
-
273
- @param file:
274
- @return:
275
- """
276
- unknown_table = struct.unpack("<24I", file.read(96))
277
-
278
- def parse_tables(self, file):
279
- """
280
- Parses all the different tables found in the file.
281
-
282
- @param file:
283
- @return:
284
- """
285
- self.parse_preview(file)
286
- self.parse_v1(file)
287
- self.parse_pens(file)
288
- self.parse_font(file)
289
- self.parse_v4(file)
290
- self.parse_vectors(file)
291
- self.parse_prevectors(file)
292
-
293
- def parse_v1(self, file):
294
- """
295
- Unknown table location. Usually absent.
296
-
297
- @param file:
298
- @return:
299
- """
300
- seek = self._locations.get("v1", 0)
301
- if seek == 0:
302
- return
303
- file.seek(seek, 0)
304
-
305
- def parse_v4(self, file):
306
- """
307
- Unknown table location usually contains 96 bytes.
308
-
309
- @param file:
310
- @return:
311
- """
312
- seek = self._locations.get("v4", 0)
313
- if seek == 0:
314
- return
315
- file.seek(seek, 0)
316
- unknown_table = struct.unpack("<24I", file.read(96))
317
-
318
- def parse_preview(self, file):
319
- """
320
- Contains a preview image of the file.
321
-
322
- @param file:
323
- @return:
324
- """
325
- seek = self._locations.get("preview", 0)
326
- if seek == 0:
327
- return
328
- file.seek(seek, 0)
329
- unknown = struct.unpack("<i", file.read(4))[0]
330
- width = struct.unpack("<i", file.read(4))[0]
331
- height = struct.unpack("<i", file.read(4))[0]
332
- v3 = struct.unpack("<3i", file.read(12))
333
- # 800, 0x200002
334
-
335
- # RGB0
336
- self._preview_bitmap.extend(
337
- struct.unpack(f"<{int(width*height)}I", file.read(4 * width * height))
338
- )
339
-
340
- def parse_font(self, file):
341
- """
342
- Font table. This usually consists of "Arial" with no other data and only exists if a font is used.
343
-
344
- @param file:
345
- @return:
346
- """
347
- seek = self._locations.get("font", 0)
348
- if seek == 0:
349
- return
350
- file.seek(seek, 0)
351
- font_count = struct.unpack("<i", file.read(4))[0]
352
-
353
- for i in range(font_count):
354
- f = file.read(100)
355
- self.fonts.append(f.decode("utf_16").strip("\x00"))
356
-
357
- def parse_pens(self, file):
358
- """
359
- Contains all the pens used at the time of the saving of the file. This is 256 pens.
360
-
361
- @param file:
362
- @return:
363
- """
364
- seek = self._locations.get("pens", 0)
365
- if seek == 0:
366
- return
367
- file.seek(seek, 0)
368
-
369
- parameter_count = struct.unpack("<i", file.read(4))[0]
370
- seek = struct.unpack("<i", file.read(4))[0]
371
- file.seek(seek, 0)
372
- for c in range(parameter_count):
373
- self.pens.append(Pen(file))
374
-
375
- def parse_prevectors(self, file):
376
- """
377
- Pre-vectors are usually 400 bytes with no values.
378
-
379
- @param file:
380
- @return:
381
- """
382
- seek = self._locations.get("prevectors", 0)
383
- if seek == 0:
384
- return
385
- file.seek(seek, 0)
386
-
387
- # 400 bytes of 00, 100 bytes of int
388
- self._prevector = struct.unpack("<400B", file.read(400))
389
-
390
- def parse_vectors(self, file):
391
- """
392
- Vectors contain the bulk of the files. This is a compressed file of huffman encoded data. The first section
393
- contains the huffman table, followed by the compressed data.
394
-
395
- @param file:
396
- @return:
397
- """
398
- seek = self._locations.get("vectors", 0)
399
- if seek == 0:
400
- return
401
- file.seek(seek, 0)
402
- uncompressed_length, unknown2, unknown3, data_start, unknown5 = struct.unpack(
403
- "<IIIII", file.read(20)
404
- )
405
- try:
406
- q = _huffman_decode_bitarray(file, uncompressed_length)
407
- except ImportError:
408
- q = _huffman_decode_python(file, uncompressed_length)
409
- data = BytesIO(q)
410
- while parse_object(data, self.objects):
411
- pass
412
-
413
-
414
- class EZObject:
415
- """
416
- Every object contains the same 15 pieces of data.
417
- If this object type contains children, the count of children and the children are given exactly following the
418
- header. Any information specific to the class of object is read after the header and children.
419
- """
420
-
421
- def __init__(self, file):
422
- header = _parse_struct(file)
423
- _interpret(header, 3, str)
424
- _construct(header)
425
-
426
- self.pen = header[0]
427
- self.type = header[1]
428
- self.state = header[2]
429
-
430
- # Selected 0x02, Hidden 0x01, Locked 0x10
431
- self.selected = bool(self.state & 0x02)
432
- self.hidden = bool(self.state & 0x01)
433
- self.locked = bool(self.state & 0x10)
434
-
435
- self.label = header[3]
436
- self.unknown2 = header[4]
437
- self.unknown3 = header[5]
438
- self.unknown4 = header[6]
439
- self.input_port_bits = header[7]
440
- self.array_state = header[8]
441
- self.array_bidirectional = bool(self.array_state & 0x2)
442
- self.array_vertical = bool(self.array_state & 0x1)
443
- self.array_count_x = header[9]
444
- self.array_count_y = header[10]
445
- self.array_step_x = header[11]
446
- self.array_step_y = header[12]
447
- self.position = header[13]
448
- self.z_pos = header[14]
449
- if isinstance(self, list):
450
- (count,) = struct.unpack("<i", file.read(4))
451
- for c in range(count):
452
- parse_object(file, self)
453
-
454
-
455
- class EZCombine(list, EZObject):
456
- """
457
- This is a series of related contours.
458
- """
459
-
460
- def __init__(self, file):
461
- list.__init__(self)
462
- EZObject.__init__(self, file)
463
-
464
-
465
- class EZGroup(list, EZObject):
466
- """
467
- Grouped data appears both when objects are grouped but also in groups within vector file objects like svgs.
468
- """
469
-
470
- def __init__(self, file):
471
- list.__init__(self)
472
- EZObject.__init__(self, file)
473
-
474
-
475
- class EZVectorFile(list, EZObject):
476
- """
477
- Vector file object.
478
- """
479
-
480
- def __init__(self, file):
481
- list.__init__(self)
482
- EZObject.__init__(self, file)
483
- data1 = _parse_struct(file)
484
- _interpret(data1, 0, str)
485
- _construct(data1)
486
-
487
- self.path = data1[0]
488
- self.args = data1
489
-
490
-
491
- class EZCurve(EZObject):
492
- """
493
- Curves are some number of curve-type (usually 1 or 3) contours.
494
- """
495
-
496
- def __init__(self, file):
497
- super().__init__(file)
498
- pts = []
499
- (count, closed) = struct.unpack("<2I", file.read(8))
500
- for i in range(count):
501
- (unk1, curve_type, unk2, unk3) = struct.unpack("<BB2H", file.read(6))
502
- # Unk1 is 2 for a weird node. with t equal 0.
503
- if curve_type == 0:
504
- d = struct.unpack(f"<5d", file.read(40))
505
- # print(d)
506
- continue
507
- (pt_count,) = struct.unpack("<i", file.read(4))
508
- # print(unk1, curve_type, unk2, unk2, pt_count)
509
- pts.append(
510
- (
511
- curve_type,
512
- closed,
513
- struct.unpack(f"<{pt_count * 2}d", file.read(16 * pt_count)),
514
- )
515
- )
516
-
517
- self.points = pts
518
-
519
-
520
- class EZRect(EZObject):
521
- """
522
- Rectangles have optional each corner curved edges.
523
- """
524
-
525
- def __init__(self, file):
526
- EZObject.__init__(self, file)
527
- args = _parse_struct(file)
528
- _construct(args)
529
- self.min_pos = args[0]
530
- self.max_pos = args[1]
531
- self.corner_upper_left = args[0]
532
- self.corner_bottom_right = args[1]
533
- self.round_c1 = args[2]
534
- self.round_c2 = args[3]
535
- self.round_c3 = args[4]
536
- self.round_c4 = args[5]
537
- self.unknown5 = args[6]
538
- self.matrix = args[7]
539
-
540
-
541
- class EZCircle(EZObject):
542
- """
543
- Circles are center followed by their radius. The angles are given in radians.
544
- """
545
-
546
- def __init__(self, file):
547
- EZObject.__init__(self, file)
548
- args = _parse_struct(file)
549
- _construct(args)
550
- self.center = args[0]
551
- self.radius = args[1]
552
- self.start_angle = args[2]
553
- self.cw = args[3]
554
- self.circle_prop0 = args[4]
555
- self.matrix = args[5]
556
-
557
-
558
- class EZEllipse(EZObject):
559
- """
560
- Ellipses are a rectangle like structures, the start and end angles create a pie-slice like geometric shape when
561
- these are set.
562
- """
563
-
564
- def __init__(self, file):
565
- EZObject.__init__(self, file)
566
- args = _parse_struct(file)
567
- _construct(args)
568
- self.corner_upper_left = args[1]
569
- self.corner_bottom_right = args[2]
570
- self.start_angle = args[3]
571
- self.end_angle = args[4]
572
- self.matrix = args[6]
573
-
574
-
575
- class EZSpiral(list, EZObject):
576
- """
577
- Spirals are a modification group of the items contained by the spiral. These also contain a cached-group of the
578
- output produced by the spiral.
579
- """
580
-
581
- def __init__(self, file):
582
- list.__init__(self)
583
- EZObject.__init__(self, file)
584
- args = _parse_struct(file)
585
- _construct(args)
586
- self.spiral_pen = args[0]
587
- self.spiral_type = args[1]
588
- self.min_radius = args[5]
589
- self.min_spiral_pitch = args[2]
590
- self.max_spiral_pitch = args[3]
591
- self.max_spiral_increment = args[4]
592
- self.outer_edge_loops = args[6]
593
- self.inner_edge_loops = args[7]
594
- self.spiral_out = args[8]
595
- self.group = EZGroup(file)
596
-
597
-
598
- class EZPolygon(EZObject):
599
- """
600
- Polygons are either regular or star-like. No control is given over the minor or major phase.
601
- """
602
-
603
- def __init__(self, file):
604
- EZObject.__init__(self, file)
605
- args = _parse_struct(file)
606
- _construct(args)
607
- self.polygon_type = args[0]
608
- self.corner_upper_left = args[1]
609
- self.corner_bottom_right = args[2]
610
- self.sides = args[7]
611
- self.matrix = args[9]
612
-
613
-
614
- class EZTimer(EZObject):
615
- """
616
- Timers are wait commands. These are given a time and simply send the wait command to the laser.
617
- """
618
-
619
- def __init__(self, file):
620
- EZObject.__init__(self, file)
621
- args = _parse_struct(file)
622
- _construct(args)
623
- self.wait_time = args[1]
624
-
625
-
626
- class EZInput(EZObject):
627
- """
628
- Input commands wait on the IO of the laser to trigger to the next item within the operations list.
629
- """
630
-
631
- def __init__(self, file):
632
- EZObject.__init__(self, file)
633
- args = _parse_struct(file)
634
- _interpret(args, 1, str)
635
- _construct(args)
636
- self.message_enabled = bool(args[0])
637
- self.message = args[1]
638
-
639
-
640
- class EZOutput(EZObject):
641
- """
642
- Output list sends IO out to the laser, this is used to trigger things like rotary, GPIO, or light.
643
- """
644
-
645
- def __init__(self, file):
646
- EZObject.__init__(self, file)
647
- args = _parse_struct(file)
648
- _construct(args)
649
- self.output_bit = args[0]
650
- self.low_to_high = bool(args[1]) # 1
651
- self.timed_high = bool(args[2]) # 0
652
- self.wait_time = args[4] # args[18] is int value
653
- self.all_out_mode = bool(args[5])
654
- self.all_out_bits = args[6]
655
-
656
-
657
- class EZEncoderDistance(EZObject):
658
- """
659
- This is for testing on-the-fly movement.
660
- """
661
-
662
- def __init__(self, file):
663
- EZObject.__init__(self, file)
664
- args = _parse_struct(file)
665
- _construct(args)
666
- self.distance = args[0]
667
-
668
-
669
- class EZExtendAxis(EZObject):
670
- """
671
- This is for testing on-the-fly movement.
672
- """
673
-
674
- def __init__(self, file):
675
- EZObject.__init__(self, file)
676
- args = _parse_struct(file)
677
- _construct(args)
678
- self.axis_go_zero = bool(args[0])
679
- self.only_once_origin = bool(args[1])
680
- self.relative = bool(args[2])
681
- self.unit_type = args[3] # Pulse (0), MM (1), Degree(2).
682
- self.pulse_per_mm = args[4]
683
- self.move_pulse = args[5]
684
- self.max_speed = args[6]
685
- self.min_speed = args[7]
686
- self.acceleration_time = args[8]
687
-
688
-
689
- class EZText(EZObject):
690
- """
691
- Text objects.
692
- """
693
-
694
- def __init__(self, file):
695
- EZObject.__init__(self, file)
696
- args = _parse_struct(file)
697
- _interpret(args, 10, str)
698
- _interpret(args, 18, str)
699
- _interpret(args, 44, str)
700
- _interpret(args, 54, str)
701
- _construct(args)
702
- self.font_angle = args[0] # Font angle in Text.
703
- self.height = args[1] # Height in MM
704
- self.text_space_setting = args[5] # 0 auto, 1 between, 2 center
705
- self.text_space = args[12]
706
- self.char_space = args[13]
707
- self.line_space = args[14]
708
- self.font = args[18] # Arial, JSF Font, etc
709
- self.font2 = args[44]
710
- self.x, self.y = args[7]
711
- self.text = args[10]
712
- self.hatch_loop_distance = args[21]
713
- self.circle_text_enable = args[48]
714
- self.circle_text_diameter = args[49]
715
- self.circle_text_base_angle = args[50]
716
- self.circle_text_range_limit_enable = args[51]
717
- self.circle_text_range_limit_angle = args[52]
718
- self.save_options = args[53] # 3 boolean values
719
- self.save_filename = args[54]
720
- self.circle_text_button_flags = args[
721
- 85
722
- ] # 2 is first button, 1 is right to left.
723
- (count,) = struct.unpack("<i", file.read(4))
724
- for i in range(count):
725
- (type,) = struct.unpack("<H", file.read(2))
726
- # type, 7 file. 1 Text. 2 Serial
727
- extradata = _parse_struct(file)
728
- _construct(extradata)
729
- extradata2 = _parse_struct(file)
730
- _construct(extradata2)
731
- (unk,) = struct.unpack("<i", file.read(4))
732
-
733
-
734
- class EZImage(EZObject):
735
- """
736
- Image objects consist of a lot of properties to control the encoding of the image and a 24-bit bitmap.
737
- """
738
-
739
- def __init__(self, file):
740
- EZObject.__init__(self, file)
741
- args = _parse_struct(file)
742
- _construct(args)
743
-
744
- image_bytes = bytearray(file.read(2)) # BM
745
- image_length = file.read(4) # int32le
746
- (size,) = struct.unpack("<i", image_length)
747
- image_bytes += image_length
748
- image_bytes += file.read(size - 6)
749
-
750
- from PIL import Image
751
-
752
- image = Image.open(BytesIO(image_bytes))
753
-
754
- self.image_path = args[0]
755
- self.width = args[5]
756
- self.height = args[4]
757
- self.fixed_dpi_x = args[9]
758
- self.fixed_dpi_y = args[333 - 15]
759
- self.image = image
760
- self.powermap = args[74 - 15 : 330 - 15]
761
- self.scan_line_increment = args[29 - 15]
762
- self.scan_line_increment_value = args[30 - 15]
763
- self.disable_mark_low_gray_point = args[31 - 15]
764
- self.disable_mark_low_gray_point_value = args[32 - 15]
765
- self.acc_distance_mm = args[331 - 15]
766
- self.dec_distance_mm = args[332 - 15]
767
- self.all_offset_mm = args[334 - 15]
768
- self.bidirectional_offset = args[330 - 15]
769
- self.status_bits = args[25 - 15]
770
- self.mirror_x = bool(self.status_bits & 0x20)
771
- self.mirror_y = bool(self.status_bits & 0x40)
772
-
773
-
774
- class EZHatch(list, EZObject):
775
- """
776
- Hatch is a modification group. All three hatch elements are given properties for each hatch. The hatch contains
777
- the actual elements that were to be given a hatch. As well as a cache-group of curve items that actually are the
778
- given hatch properly rendered.
779
- """
780
-
781
- def __init__(self, file):
782
- list.__init__(self)
783
- EZObject.__init__(self, file)
784
- args = _parse_struct(file)
785
- _construct(args)
786
- self.mark_contours = args[0]
787
- self.mark_contours_type = args[41]
788
-
789
- self.hatch1_enabled = args[1]
790
- self.hatch1_type = args[3]
791
- # Includes average distribute line, allcalc, follow edge, crosshatch
792
- # spiral = 0x50
793
- self.hatch1_type_all_calc = self.hatch1_type & 0x1
794
- self.hatch1_type_follow_edge = self.hatch1_type & 0x2
795
- self.hatch1_type_crosshatch = self.hatch1_type & 0x400
796
- self.hatch1_angle = args[8]
797
- self.hatch1_pen = args[2]
798
- self.hatch1_count = args[42]
799
- self.hatch1_line_space = args[5]
800
- self.hatch1_edge_offset = args[4]
801
- self.hatch1_start_offset = args[6]
802
- self.hatch1_end_offset = args[7]
803
- self.hatch1_line_reduction = args[29]
804
- self.hatch1_number_of_loops = args[32]
805
- self.hatch1_loop_distance = args[35]
806
- self.hatch1_angle_inc = args[18]
807
-
808
- self.hatch2_enabled = args[9]
809
- self.hatch2_type = args[11]
810
- self.hatch2_angle = args[16]
811
- self.hatch2_pen = args[10]
812
- self.hatch2_count = args[43]
813
- self.hatch2_line_space = args[13]
814
- self.hatch2_edge_offset = args[12]
815
- self.hatch2_start_offset = args[14]
816
- self.hatch2_end_offset = args[15]
817
- self.hatch2_line_reduction = args[30]
818
- self.hatch2_number_of_loops = args[33]
819
- self.hatch2_loop_distance = args[36]
820
- self.hatch2_angle_inc = args[19]
821
-
822
- self.hatch3_enabled = args[20]
823
- self.hatch3_type = args[22]
824
- self.hatch3_angle = args[27]
825
- self.hatch3_pen = args[21]
826
- self.hatch3_count = args[44]
827
- self.hatch3_line_space = args[24]
828
- self.hatch3_edge_offset = args[23]
829
- self.hatch3_start_offset = args[25]
830
- self.hatch3_end_offset = args[26]
831
- self.hatch3_line_reduction = args[31]
832
- self.hatch3_number_of_loops = args[34]
833
- self.hatch3_loop_distance = args[37]
834
- self.hatch3_angle_inc = args[28]
835
- tell = file.tell()
836
- (check,) = struct.unpack("<i", file.read(4))
837
- file.seek(tell, 0)
838
- if check == 15:
839
- self.group = EZGroup(file)
840
- else:
841
- self.group = None
842
-
843
-
844
- object_map = {
845
- 1: EZCurve,
846
- 3: EZRect,
847
- 4: EZCircle,
848
- 5: EZEllipse,
849
- 6: EZPolygon,
850
- 0x30: EZCombine,
851
- 0x40: EZImage,
852
- 0x60: EZSpiral,
853
- 0x6000: EZEncoderDistance,
854
- 0x5000: EZExtendAxis,
855
- 0x4000: EZOutput,
856
- 0x3000: EZInput,
857
- 0x2000: EZTimer,
858
- 0x800: EZText,
859
- 0x10: EZGroup,
860
- 0x50: EZVectorFile,
861
- 0x20: EZHatch,
862
- }
863
-
864
-
865
- def parse_object(file, objects):
866
- object_type = struct.unpack("<i", file.read(4))[0] # 0
867
- if object_type == 0:
868
- return False
869
- ez_class = object_map.get(object_type)
870
- assert ez_class
871
- objects.append(ez_class(file))
872
- return True
873
-
874
-
875
- class EZDLoader:
876
- @staticmethod
877
- def load_types():
878
- yield "EZCad2 Files", ("ezd",), "application/x-ezd"
879
-
880
- @staticmethod
881
- def load(context, elements_service, pathname, **kwargs):
882
- try:
883
- with open(pathname, "br") as file:
884
- ezfile = EZCFile(file)
885
- except (IOError, IndexError) as e:
886
- raise BadFileError(str(e)) from e
887
- except struct.error:
888
- raise BadFileError(
889
- "Unseen sequence, object, or formatting.\n"
890
- "File format was only partially unrecognized.\n"
891
- "Please raise an github issue and submit this file for review.\n"
892
- )
893
-
894
- ez_processor = EZProcessor(elements_service)
895
- ez_processor.process(ezfile, pathname)
896
- return True
897
-
898
-
899
- class EZProcessor:
900
- def __init__(self, elements):
901
- self.elements = elements
902
- self.element_list = list()
903
- self.regmark_list = list()
904
- self.pathname = None
905
- self.regmark = self.elements.reg_branch
906
- self.op_branch = elements.op_branch
907
- self.elem_branch = elements.elem_branch
908
-
909
- self.width = elements.device.view.unit_width
910
- self.height = elements.device.view.unit_height
911
- self.cx = self.width / 2.0
912
- self.cy = self.height / 2.0
913
- self.matrix = Matrix.scale(UNITS_PER_MM, -UNITS_PER_MM)
914
- self.matrix.post_translate(self.cx, self.cy)
915
-
916
- def process(self, ez, pathname):
917
- self.op_branch.remove_all_children()
918
- self.elem_branch.remove_all_children()
919
- self.pathname = pathname
920
- file_node = self.elem_branch.add(type="file", filepath=pathname)
921
- file_node.focus()
922
- for f in ez.objects:
923
- self.parse(ez, f, file_node, self.op_branch)
924
-
925
- def parse(self, ez, element, elem, op, op_add=None, path=None):
926
- """
927
- Parse ez structure into MK specific tree structure and objects.
928
-
929
- @param ez: EZFile object
930
- @param element: ezobject being parsed.
931
- @param elem: element context.
932
- @param op: operation context
933
- @param op_add: Operation we should add to rather than create.
934
- @param path: Path we should append to rather than create.
935
- @return:
936
- """
937
- if isinstance(element, EZText):
938
- node = elem.add(type="elem text", text=element.text, transform=self.matrix)
939
- p = ez.pens[element.pen]
940
- if op_add is None:
941
- op_add = op.add(type="op engrave", **p.__dict__)
942
- op_add.add_reference(node)
943
- elif isinstance(element, EZCurve):
944
- points = element.points
945
- if len(points) == 0:
946
- return
947
- if path is None:
948
- append_path = False
949
- path = Path(stroke="black", transform=self.matrix)
950
- else:
951
- append_path = True
952
-
953
- last_end = None
954
- for t, closed, contour in points:
955
- cpt = [
956
- complex(contour[i], contour[i + 1])
957
- for i in range(0, len(contour), 2)
958
- ]
959
- if last_end != cpt[0]:
960
- path.move(cpt[0])
961
- if t == 1:
962
- path.line(*cpt[1:])
963
- elif t == 2:
964
- path.quad(*cpt[1:])
965
- elif t == 3:
966
- path.cubic(*cpt[1:])
967
- last_end = cpt[-1]
968
- if points[-1][1]:
969
- # Path is closed.
970
- path.closed()
971
- if append_path:
972
- return
973
- node = elem.add(
974
- type="elem path",
975
- path=path,
976
- stroke_width=self.elements.default_strokewidth,
977
- )
978
- p = ez.pens[element.pen]
979
- if op_add is None:
980
- op_add = op.add(type="op engrave", **p.__dict__)
981
- op_add.add_reference(node)
982
- elif isinstance(element, EZPolygon):
983
- m = element.matrix
984
- mx = Matrix(m[0], m[1], m[3], m[4], m[6], m[7])
985
- mx *= self.matrix
986
- x0, y0 = element.corner_upper_left
987
- x1, y1 = element.corner_bottom_right
988
- step = math.tau / element.sides
989
- cx, cy = (x0 + x1) / 2.0, (y0 + y1) / 2.0
990
- rx = (x1 - x0) / 2.0
991
- ry = (y1 - y0) / 2.0
992
- pts = []
993
- theta = step / 2.0
994
- for i in range(element.sides):
995
- pts.append((cx + math.cos(theta) * rx, cy + math.sin(theta) * ry))
996
- theta += step
997
- polyline = Polygon(points=pts, transform=mx, stroke="black")
998
- node = elem.add(
999
- type="elem polyline",
1000
- shape=polyline,
1001
- stroke_width=self.elements.default_strokewidth,
1002
- )
1003
- p = ez.pens[element.pen]
1004
- if op_add is None:
1005
- op_add = op.add(type="op engrave", **p.__dict__)
1006
- op_add.add_reference(node)
1007
- elif isinstance(element, EZCircle):
1008
- m = element.matrix
1009
- mx = Matrix(m[0], m[1], m[3], m[4], m[6], m[7])
1010
- mx *= self.matrix
1011
- node = elem.add(
1012
- cx=element.center[0],
1013
- cy=element.center[1],
1014
- rx=element.radius,
1015
- ry=element.radius,
1016
- stroke=Color("black"),
1017
- matrix=mx,
1018
- stroke_width=self.elements.default_strokewidth,
1019
- type="elem ellipse",
1020
- )
1021
- p = ez.pens[element.pen]
1022
- if op_add is None:
1023
- op_add = op.add(type="op engrave", **p.__dict__)
1024
- op_add.add_reference(node)
1025
- elif isinstance(element, EZEllipse):
1026
- m = element.matrix
1027
- mx = Matrix(m[0], m[1], m[3], m[4], m[6], m[7])
1028
- mx *= self.matrix
1029
- x0, y0 = element.corner_upper_left
1030
- x1, y1 = element.corner_bottom_right
1031
- node = elem.add(
1032
- cx=(x0 + x1) / 2.0,
1033
- cy=(y0 + y1) / 2.0,
1034
- rx=(x1 - x0) / 2.0,
1035
- ry=(y1 - y0) / 2.0,
1036
- matrix=mx,
1037
- stroke=Color("black"),
1038
- stroke_width=self.elements.default_strokewidth,
1039
- type="elem ellipse",
1040
- )
1041
- p = ez.pens[element.pen]
1042
- if op_add is None:
1043
- op_add = op.add(type="op engrave", **p.__dict__)
1044
- op_add.add_reference(node)
1045
- elif isinstance(element, EZRect):
1046
- m = element.matrix
1047
- mx = Matrix(m[0], m[1], m[3], m[4], m[6], m[7])
1048
- mx *= self.matrix
1049
- x0, y0 = element.corner_upper_left
1050
- x1, y1 = element.corner_bottom_right
1051
- node = elem.add(
1052
- x=x0,
1053
- y=y0,
1054
- width=x1 - x0,
1055
- height=y1 - y0,
1056
- matrix=mx,
1057
- stroke=Color("black"),
1058
- stroke_width=self.elements.default_strokewidth,
1059
- type="elem rect",
1060
- )
1061
- p = ez.pens[element.pen]
1062
- if op_add is None:
1063
- op_add = op.add(type="op engrave", **p.__dict__)
1064
- op_add.add_reference(node)
1065
- elif isinstance(element, EZTimer):
1066
- op.add(type="util wait", wait=element.wait_time / 1000.0)
1067
- elif isinstance(element, EZOutput):
1068
- mask = 1 << element.output_bit
1069
- bits = mask if element.low_to_high else 0
1070
-
1071
- op.add(
1072
- type="util output",
1073
- output_value=bits,
1074
- output_mask=mask,
1075
- )
1076
- if element.timed_high:
1077
- op.add(type="util wait", wait=element.wait_time / 1000.0)
1078
- op.add(
1079
- type="util output",
1080
- output_value=~bits,
1081
- output_mask=mask,
1082
- )
1083
- elif isinstance(element, EZInput):
1084
- op.add(
1085
- type="util input",
1086
- input_message=element.message,
1087
- input_value=element.input_port_bits,
1088
- input_mask=element.input_port_bits,
1089
- )
1090
- elif isinstance(element, EZImage):
1091
- image = element.image
1092
- left, top = self.matrix.point_in_matrix_space(
1093
- (
1094
- element.position[0] - (element.width / 2.0),
1095
- element.position[1] + element.height / 2.0,
1096
- )
1097
- )
1098
- w, h = image.size
1099
- unit_width = element.width * UNITS_PER_MM
1100
- unit_height = element.height * UNITS_PER_MM
1101
- matrix = Matrix.scale(
1102
- (unit_width / w),
1103
- (unit_height / h),
1104
- )
1105
- _dpi = int(
1106
- round(
1107
- (
1108
- float((w * UNITS_PER_INCH) / unit_width)
1109
- + float((h * UNITS_PER_INCH) / unit_height)
1110
- )
1111
- / 2.0,
1112
- )
1113
- )
1114
- matrix.post_translate(left, top)
1115
- node = elem.add(type="elem image", image=image, matrix=matrix, dpi=_dpi)
1116
- p = ez.pens[element.pen]
1117
- if op_add is None:
1118
- op_add = op.add(type="op image", **p.__dict__)
1119
- op_add.add_reference(node)
1120
- elif isinstance(element, EZVectorFile):
1121
- elem = elem.add(type="group", label=element.label)
1122
- for child in element:
1123
- # (self, ez, element, elem, op)
1124
- self.parse(ez, child, elem, op, op_add=op_add, path=path)
1125
- elif isinstance(element, EZHatch):
1126
- p = dict(ez.pens[element.pen].__dict__)
1127
-
1128
- op_add = op.add(type="op engrave", **p)
1129
- if "label" in p:
1130
- # Both pen and hatch have a label, we shall use the hatch-label for hatch; pen for op.
1131
- del p["label"]
1132
- op_add.add(type="effect hatch", **p, label=element.label)
1133
- for child in element:
1134
- # Operands for the hatch.
1135
- self.parse(ez, child, elem, op, op_add=op_add)
1136
-
1137
- op_add = op.add(type="op engrave", **p)
1138
- if element.group:
1139
- path = Path(stroke="black", transform=self.matrix)
1140
- for child in element.group:
1141
- # Per-completed hatch elements.
1142
- self.parse(ez, child, elem, op, op_add=op_add, path=path)
1143
-
1144
- # All path elements are added, should add it to the tree.
1145
- node = elem.add(
1146
- type="elem path",
1147
- path=path,
1148
- stroke_width=self.elements.default_strokewidth,
1149
- )
1150
- p = ez.pens[element.pen]
1151
- if op_add is None:
1152
- op_add = op.add(type="op engrave", **p.__dict__)
1153
- op_add.add_reference(node)
1154
- elif isinstance(element, (EZGroup, EZCombine)):
1155
- elem = elem.add(type="group", label=element.label)
1156
- # recurse to children
1157
- for child in element:
1158
- self.parse(ez, child, elem, op, op_add=op_add, path=path)
1159
- elif isinstance(element, EZSpiral):
1160
- elem = elem.add(type="group", label=element.label)
1161
- # recurse to children
1162
- for child in element:
1163
- self.parse(ez, child, elem, op)
1164
- for child in element.group:
1165
- self.parse(ez, child, elem, op)
1
+ """
2
+ Parser for .ezd files.
3
+
4
+ These are a working type file produced by EZCad2™. They contain several pens and different settings that were used by
5
+ the program when the file was saved. The vector objects consist of a series of laser-ready commands/shapes which refer
6
+ to the required pen. Some modification objects like hatch and spiral work like a group containing other sub-elements
7
+ and also contain the cached curve/path data. The image objects contain a standard 24 bit bitmap image. All elements
8
+ are coordinated relative to the center of the working area and, it is much more common to be given the center point than
9
+ a specific corner. Nearly all coordinates are in mm, and denote the deviation from the center point.
10
+
11
+ """
12
+ import math
13
+ import struct
14
+ from io import BytesIO
15
+
16
+ from meerk40t.core.exceptions import BadFileError
17
+ from meerk40t.core.units import UNITS_PER_INCH, UNITS_PER_MM
18
+ from meerk40t.svgelements import Color, Matrix, Path, Polygon
19
+
20
+
21
+ def plugin(kernel, lifecycle):
22
+ if lifecycle == "register":
23
+ kernel.register("load/EZDLoader", EZDLoader)
24
+
25
+
26
+ def _parse_struct(file):
27
+ """
28
+ Parses a generic structure for ezd files. These are a count of objects. Then for each data entry int32le:length
29
+ followed by data of that length.
30
+
31
+ @param file:
32
+ @return:
33
+ """
34
+ p = list()
35
+ count = struct.unpack("<i", file.read(4))[0]
36
+ for i in range(count):
37
+ b = file.read(4)
38
+ if len(b) != 4:
39
+ return p
40
+ (length,) = struct.unpack("<i", b)
41
+ if length == -1:
42
+ return p
43
+ b = file.read(length)
44
+ if len(b) != length:
45
+ return p
46
+ p.append(b)
47
+ return p
48
+
49
+
50
+ def _interpret(data, index, type):
51
+ """
52
+ Provide a specific hint for how to interpret a chunk of bytes. There are cases where 16 bytes could be a point,
53
+ consisting of two floating points, but could also be a string. This is used to force the typing to use the correct
54
+ method.
55
+
56
+ @param data:
57
+ @param index:
58
+ @param type:
59
+ @return:
60
+ """
61
+ if type == str:
62
+ data[index] = data[index].decode("utf_16").strip("\x00")
63
+ elif type == "point":
64
+ data[index] = struct.unpack("2d", data[index])
65
+ elif type == "short":
66
+ (data[index],) = struct.unpack("<H", data[index])
67
+ elif type == int:
68
+ (data[index],) = struct.unpack("<i", data[index])
69
+ elif type == float:
70
+ (data[index],) = struct.unpack("d", data[index])
71
+ elif type == "matrix":
72
+ data[index] = struct.unpack("9d", data[index])
73
+
74
+
75
+ def _construct(data):
76
+ """
77
+ For each element of data (that is a bytes object), interpret them as their most common type.
78
+
79
+ @param data:
80
+ @return:
81
+ """
82
+ for i in range(len(data)):
83
+ b = data[i]
84
+ length = len(b)
85
+ if not isinstance(b, (bytes, bytearray)):
86
+ continue
87
+ if length == 2:
88
+ _interpret(data, i, "short")
89
+ elif length == 4:
90
+ _interpret(data, i, int)
91
+ elif length == 8:
92
+ _interpret(data, i, float)
93
+ elif length == 16:
94
+ _interpret(data, i, "point")
95
+ elif length == 60:
96
+ _interpret(data, i, str)
97
+ elif length == 72:
98
+ _interpret(data, i, "matrix")
99
+ elif length == 0:
100
+ data[i] = None
101
+ return data
102
+
103
+
104
+ def _huffman_decode_python(file, uncompressed_length):
105
+ """
106
+ Python fallback for huffman decoding of the vector table.
107
+
108
+ @param file:
109
+ @param uncompressed_length:
110
+ @return:
111
+ """
112
+ huffman_dict = {}
113
+ table_length = struct.unpack("<H", file.read(2))[0]
114
+ for i in range(table_length):
115
+ character, bb, length = struct.unpack("<BIH", file.read(7))
116
+ bits = "{:032b}".format(bb)[-length:]
117
+ huffman_dict[bits] = character
118
+ data = file.read()
119
+
120
+ def bit_generator():
121
+ for d in data:
122
+ yield from "{:08b}".format(d)
123
+
124
+ q = bytearray()
125
+ c = ""
126
+ for b in bit_generator():
127
+ c += b
128
+ m = huffman_dict.get(c)
129
+ if m is not None:
130
+ q.append(m)
131
+ c = ""
132
+ if len(q) >= uncompressed_length:
133
+ return q
134
+ return q
135
+
136
+
137
+ def _huffman_decode_bitarray(file, uncompressed_length):
138
+ """
139
+ Bitarray decoding of huffman table found in the vector table section.
140
+
141
+ @param file:
142
+ @param uncompressed_length:
143
+ @return:
144
+ """
145
+ from bitarray import bitarray
146
+
147
+ huffman_dict = {}
148
+ table_length = struct.unpack("<H", file.read(2))[0]
149
+ for i in range(table_length):
150
+ character, bb, length = struct.unpack("<BIH", file.read(7))
151
+ bits = bitarray("{:032b}".format(bb)[-length:])
152
+ huffman_dict[character] = bits
153
+ a = bitarray()
154
+ a.frombytes(file.read())
155
+ while True:
156
+ try:
157
+ return bytearray(a.decode(huffman_dict))
158
+ except ValueError:
159
+ a = a[:-1]
160
+
161
+
162
+ class Pen:
163
+ def __init__(self, file):
164
+ """
165
+ Parse pen with the given file.
166
+ """
167
+ args = _parse_struct(file)
168
+ _interpret(args, 1, str)
169
+ _construct(args)
170
+
171
+ self.color = Color(bgr=args[0])
172
+ self.label = args[1]
173
+ self.mark_enable = args[2]
174
+ self.passes = args[4] # Loop Count
175
+ if self.passes >= 1:
176
+ self.passes_custom = True
177
+ self.speed = args[5]
178
+ self.power = args[6] * 10.0
179
+ self.frequency = args[7] / 1000.0
180
+ self.start_tc = args[9]
181
+ self.end_tc = args[10]
182
+ self.polygon_tc = args[11]
183
+ self.jump_speed = args[12]
184
+ self.jump_min_delay = args[13]
185
+ self.jump_max_delay = args[14]
186
+ self.opt_start_length = args[16]
187
+ self.opt_end_length = args[15]
188
+ self.time_per_point = args[17]
189
+ self.pulse_per_point = args[21]
190
+ self.laser_off_tc = args[23]
191
+ self.wobble_enable = args[26]
192
+ self.wobble_diameter = args[27]
193
+ self.wobble_distance = args[28]
194
+
195
+ try:
196
+ self.add_endpoints = args[29]
197
+ self.add_endpoint_distance = args[30]
198
+ self.add_endpoint_time_per_point = args[32]
199
+ self.add_endpoint_point_distance = args[31]
200
+ self.add_endpoints_point_cycles = args[33]
201
+ self.opt_enable = args[40]
202
+ self.break_angle = args[41]
203
+
204
+ self.jump_min_jump_delay2 = args[37]
205
+ self.jump_max_delay2 = args[38]
206
+ self.jump_speed_max_limit = args[39]
207
+ except IndexError:
208
+ pass
209
+
210
+
211
+ class EZCFile:
212
+ """
213
+ Parse the EZCFile given file as a stream.
214
+ """
215
+
216
+ def __init__(self, file):
217
+ self._locations = {}
218
+ self.pens = []
219
+ self.objects = []
220
+ self.fonts = []
221
+ self._preview_bitmap = list()
222
+ self._prevector = None
223
+ self.parse_header(file)
224
+ self.parse_seektable(file)
225
+ self.parse_unknown_nontable(file)
226
+ self.parse_tables(file)
227
+
228
+ def parse_header(self, file):
229
+ """
230
+ Parse file header.
231
+
232
+ @param file:
233
+ @return:
234
+ """
235
+ magic_number = file.read(16)
236
+ header = magic_number.decode("utf_16")
237
+ if header != "EZCADUNI":
238
+ return False
239
+ v0 = struct.unpack("<i", file.read(4)) # 0
240
+ v1 = struct.unpack("<i", file.read(4)) # 2001
241
+ s1 = file.read(60)
242
+ s1 = s1.decode("utf-16")
243
+ s2 = file.read(60)
244
+ s2 = s2.decode("utf-16")
245
+ s3 = file.read(60)
246
+ s3 = s3.decode("utf-16")
247
+ s4 = file.read(140)
248
+
249
+ def parse_seektable(self, file):
250
+ """
251
+ The second item in the file after the header is the seek table lookup. This provides the location in absolute
252
+ position in the file of the table locations.
253
+ @param file:
254
+ @return:
255
+ """
256
+ self._locations["preview"] = struct.unpack("<i", file.read(4))[0]
257
+ self._locations["v1"] = struct.unpack("<i", file.read(4))[0]
258
+ self._locations["pens"] = struct.unpack("<i", file.read(4))[0]
259
+ self._locations["font"] = struct.unpack("<i", file.read(4))[0]
260
+ self._locations["v4"] = struct.unpack("<i", file.read(4))[0]
261
+ self._locations["vectors"] = struct.unpack("<i", file.read(4))[0]
262
+ self._locations["prevectors"] = struct.unpack("<i", file.read(4))[0]
263
+
264
+ def parse_unknown_nontable(self, file):
265
+ """
266
+ This is a non-table section. It could be padding for future seek tables entries or have some unknown meaning.
267
+
268
+ @param file:
269
+ @return:
270
+ """
271
+ unknown_table = struct.unpack("<24I", file.read(96))
272
+
273
+ def parse_tables(self, file):
274
+ """
275
+ Parses all the different tables found in the file.
276
+
277
+ @param file:
278
+ @return:
279
+ """
280
+ self.parse_preview(file)
281
+ self.parse_v1(file)
282
+ self.parse_pens(file)
283
+ self.parse_font(file)
284
+ self.parse_v4(file)
285
+ self.parse_vectors(file)
286
+ self.parse_prevectors(file)
287
+
288
+ def parse_v1(self, file):
289
+ """
290
+ Unknown table location. Usually absent.
291
+
292
+ @param file:
293
+ @return:
294
+ """
295
+ seek = self._locations.get("v1", 0)
296
+ if seek == 0:
297
+ return
298
+ file.seek(seek, 0)
299
+
300
+ def parse_v4(self, file):
301
+ """
302
+ Unknown table location usually contains 96 bytes.
303
+
304
+ @param file:
305
+ @return:
306
+ """
307
+ seek = self._locations.get("v4", 0)
308
+ if seek == 0:
309
+ return
310
+ file.seek(seek, 0)
311
+ unknown_table = struct.unpack("<24I", file.read(96))
312
+
313
+ def parse_preview(self, file):
314
+ """
315
+ Contains a preview image of the file.
316
+
317
+ @param file:
318
+ @return:
319
+ """
320
+ seek = self._locations.get("preview", 0)
321
+ if seek == 0:
322
+ return
323
+ file.seek(seek, 0)
324
+ unknown = struct.unpack("<i", file.read(4))[0]
325
+ width = struct.unpack("<i", file.read(4))[0]
326
+ height = struct.unpack("<i", file.read(4))[0]
327
+ v3 = struct.unpack("<3i", file.read(12))
328
+ # 800, 0x200002
329
+
330
+ # RGB0
331
+ self._preview_bitmap.extend(
332
+ struct.unpack(f"<{int(width*height)}I", file.read(4 * width * height))
333
+ )
334
+
335
+ def parse_font(self, file):
336
+ """
337
+ Font table. This usually consists of "Arial" with no other data and only exists if a font is used.
338
+
339
+ @param file:
340
+ @return:
341
+ """
342
+ seek = self._locations.get("font", 0)
343
+ if seek == 0:
344
+ return
345
+ file.seek(seek, 0)
346
+ font_count = struct.unpack("<i", file.read(4))[0]
347
+
348
+ for i in range(font_count):
349
+ f = file.read(100)
350
+ self.fonts.append(f.decode("utf_16").strip("\x00"))
351
+
352
+ def parse_pens(self, file):
353
+ """
354
+ Contains all the pens used at the time of the saving of the file. This is 256 pens.
355
+
356
+ @param file:
357
+ @return:
358
+ """
359
+ seek = self._locations.get("pens", 0)
360
+ if seek == 0:
361
+ return
362
+ file.seek(seek, 0)
363
+
364
+ parameter_count = struct.unpack("<i", file.read(4))[0]
365
+ seek = struct.unpack("<i", file.read(4))[0]
366
+ file.seek(seek, 0)
367
+ for c in range(parameter_count):
368
+ self.pens.append(Pen(file))
369
+
370
+ def parse_prevectors(self, file):
371
+ """
372
+ Pre-vectors are usually 400 bytes with no values.
373
+
374
+ @param file:
375
+ @return:
376
+ """
377
+ seek = self._locations.get("prevectors", 0)
378
+ if seek == 0:
379
+ return
380
+ file.seek(seek, 0)
381
+
382
+ # 400 bytes of 00, 100 bytes of int
383
+ self._prevector = struct.unpack("<400B", file.read(400))
384
+
385
+ def parse_vectors(self, file):
386
+ """
387
+ Vectors contain the bulk of the files. This is a compressed file of huffman encoded data. The first section
388
+ contains the huffman table, followed by the compressed data.
389
+
390
+ @param file:
391
+ @return:
392
+ """
393
+ seek = self._locations.get("vectors", 0)
394
+ if seek == 0:
395
+ return
396
+ file.seek(seek, 0)
397
+ uncompressed_length, unknown2, unknown3, data_start, unknown5 = struct.unpack(
398
+ "<IIIII", file.read(20)
399
+ )
400
+ try:
401
+ q = _huffman_decode_bitarray(file, uncompressed_length)
402
+ except ImportError:
403
+ q = _huffman_decode_python(file, uncompressed_length)
404
+ data = BytesIO(q)
405
+ while parse_object(data, self.objects):
406
+ pass
407
+
408
+
409
+ class EZObject:
410
+ """
411
+ Every object contains the same 15 pieces of data.
412
+ If this object type contains children, the count of children and the children are given exactly following the
413
+ header. Any information specific to the class of object is read after the header and children.
414
+ """
415
+
416
+ def __init__(self, file):
417
+ header = _parse_struct(file)
418
+ _interpret(header, 3, str)
419
+ _construct(header)
420
+
421
+ self.pen = header[0]
422
+ self.type = header[1]
423
+ self.state = header[2]
424
+
425
+ # Selected 0x02, Hidden 0x01, Locked 0x10
426
+ self.selected = bool(self.state & 0x02)
427
+ self.hidden = bool(self.state & 0x01)
428
+ self.locked = bool(self.state & 0x10)
429
+
430
+ self.label = header[3]
431
+ self.unknown2 = header[4]
432
+ self.unknown3 = header[5]
433
+ self.unknown4 = header[6]
434
+ self.input_port_bits = header[7]
435
+ self.array_state = header[8]
436
+ self.array_bidirectional = bool(self.array_state & 0x2)
437
+ self.array_vertical = bool(self.array_state & 0x1)
438
+ self.array_count_x = header[9]
439
+ self.array_count_y = header[10]
440
+ self.array_step_x = header[11]
441
+ self.array_step_y = header[12]
442
+ self.position = header[13]
443
+ self.z_pos = header[14]
444
+ if isinstance(self, list):
445
+ (count,) = struct.unpack("<i", file.read(4))
446
+ for c in range(count):
447
+ parse_object(file, self)
448
+
449
+
450
+ class EZCombine(list, EZObject):
451
+ """
452
+ This is a series of related contours.
453
+ """
454
+
455
+ def __init__(self, file):
456
+ list.__init__(self)
457
+ EZObject.__init__(self, file)
458
+
459
+
460
+ class EZGroup(list, EZObject):
461
+ """
462
+ Grouped data appears both when objects are grouped but also in groups within vector file objects like svgs.
463
+ """
464
+
465
+ def __init__(self, file):
466
+ list.__init__(self)
467
+ EZObject.__init__(self, file)
468
+
469
+
470
+ class EZVectorFile(list, EZObject):
471
+ """
472
+ Vector file object.
473
+ """
474
+
475
+ def __init__(self, file):
476
+ list.__init__(self)
477
+ EZObject.__init__(self, file)
478
+ data1 = _parse_struct(file)
479
+ _interpret(data1, 0, str)
480
+ _construct(data1)
481
+
482
+ self.path = data1[0]
483
+ self.args = data1
484
+
485
+
486
+ class EZCurve(EZObject):
487
+ """
488
+ Curves are some number of curve-type (usually 1 or 3) contours.
489
+ """
490
+
491
+ def __init__(self, file):
492
+ super().__init__(file)
493
+ pts = []
494
+ (count, closed) = struct.unpack("<2I", file.read(8))
495
+ for i in range(count):
496
+ (unk1, curve_type, unk2, unk3) = struct.unpack("<BB2H", file.read(6))
497
+ # Unk1 is 2 for a weird node. with t equal 0.
498
+ if curve_type == 0:
499
+ d = struct.unpack(f"<5d", file.read(40))
500
+ # print(d)
501
+ continue
502
+ (pt_count,) = struct.unpack("<i", file.read(4))
503
+ # print(unk1, curve_type, unk2, unk2, pt_count)
504
+ pts.append(
505
+ (
506
+ curve_type,
507
+ closed,
508
+ struct.unpack(f"<{pt_count * 2}d", file.read(16 * pt_count)),
509
+ )
510
+ )
511
+
512
+ self.points = pts
513
+
514
+
515
+ class EZRect(EZObject):
516
+ """
517
+ Rectangles have optional each corner curved edges.
518
+ """
519
+
520
+ def __init__(self, file):
521
+ EZObject.__init__(self, file)
522
+ args = _parse_struct(file)
523
+ _construct(args)
524
+ self.min_pos = args[0]
525
+ self.max_pos = args[1]
526
+ self.corner_upper_left = args[0]
527
+ self.corner_bottom_right = args[1]
528
+ self.round_c1 = args[2]
529
+ self.round_c2 = args[3]
530
+ self.round_c3 = args[4]
531
+ self.round_c4 = args[5]
532
+ self.unknown5 = args[6]
533
+ self.matrix = args[7]
534
+
535
+
536
+ class EZCircle(EZObject):
537
+ """
538
+ Circles are center followed by their radius. The angles are given in radians.
539
+ """
540
+
541
+ def __init__(self, file):
542
+ EZObject.__init__(self, file)
543
+ args = _parse_struct(file)
544
+ _construct(args)
545
+ self.center = args[0]
546
+ self.radius = args[1]
547
+ self.start_angle = args[2]
548
+ self.cw = args[3]
549
+ self.circle_prop0 = args[4]
550
+ self.matrix = args[5]
551
+
552
+
553
+ class EZEllipse(EZObject):
554
+ """
555
+ Ellipses are a rectangle like structures, the start and end angles create a pie-slice like geometric shape when
556
+ these are set.
557
+ """
558
+
559
+ def __init__(self, file):
560
+ EZObject.__init__(self, file)
561
+ args = _parse_struct(file)
562
+ _construct(args)
563
+ self.corner_upper_left = args[1]
564
+ self.corner_bottom_right = args[2]
565
+ self.start_angle = args[3]
566
+ self.end_angle = args[4]
567
+ self.matrix = args[6]
568
+
569
+
570
+ class EZSpiral(list, EZObject):
571
+ """
572
+ Spirals are a modification group of the items contained by the spiral. These also contain a cached-group of the
573
+ output produced by the spiral.
574
+ """
575
+
576
+ def __init__(self, file):
577
+ list.__init__(self)
578
+ EZObject.__init__(self, file)
579
+ args = _parse_struct(file)
580
+ _construct(args)
581
+ self.spiral_pen = args[0]
582
+ self.spiral_type = args[1]
583
+ self.min_radius = args[5]
584
+ self.min_spiral_pitch = args[2]
585
+ self.max_spiral_pitch = args[3]
586
+ self.max_spiral_increment = args[4]
587
+ self.outer_edge_loops = args[6]
588
+ self.inner_edge_loops = args[7]
589
+ self.spiral_out = args[8]
590
+ self.group = EZGroup(file)
591
+
592
+
593
+ class EZPolygon(EZObject):
594
+ """
595
+ Polygons are either regular or star-like. No control is given over the minor or major phase.
596
+ """
597
+
598
+ def __init__(self, file):
599
+ EZObject.__init__(self, file)
600
+ args = _parse_struct(file)
601
+ _construct(args)
602
+ self.polygon_type = args[0]
603
+ self.corner_upper_left = args[1]
604
+ self.corner_bottom_right = args[2]
605
+ self.sides = args[7]
606
+ self.matrix = args[9]
607
+
608
+
609
+ class EZTimer(EZObject):
610
+ """
611
+ Timers are wait commands. These are given a time and simply send the wait command to the laser.
612
+ """
613
+
614
+ def __init__(self, file):
615
+ EZObject.__init__(self, file)
616
+ args = _parse_struct(file)
617
+ _construct(args)
618
+ self.wait_time = args[1]
619
+
620
+
621
+ class EZInput(EZObject):
622
+ """
623
+ Input commands wait on the IO of the laser to trigger to the next item within the operations list.
624
+ """
625
+
626
+ def __init__(self, file):
627
+ EZObject.__init__(self, file)
628
+ args = _parse_struct(file)
629
+ _interpret(args, 1, str)
630
+ _construct(args)
631
+ self.message_enabled = bool(args[0])
632
+ self.message = args[1]
633
+
634
+
635
+ class EZOutput(EZObject):
636
+ """
637
+ Output list sends IO out to the laser, this is used to trigger things like rotary, GPIO, or light.
638
+ """
639
+
640
+ def __init__(self, file):
641
+ EZObject.__init__(self, file)
642
+ args = _parse_struct(file)
643
+ _construct(args)
644
+ self.output_bit = args[0]
645
+ self.low_to_high = bool(args[1]) # 1
646
+ self.timed_high = bool(args[2]) # 0
647
+ self.wait_time = args[4] # args[18] is int value
648
+ self.all_out_mode = bool(args[5])
649
+ self.all_out_bits = args[6]
650
+
651
+
652
+ class EZEncoderDistance(EZObject):
653
+ """
654
+ This is for testing on-the-fly movement.
655
+ """
656
+
657
+ def __init__(self, file):
658
+ EZObject.__init__(self, file)
659
+ args = _parse_struct(file)
660
+ _construct(args)
661
+ self.distance = args[0]
662
+
663
+
664
+ class EZExtendAxis(EZObject):
665
+ """
666
+ This is for testing on-the-fly movement.
667
+ """
668
+
669
+ def __init__(self, file):
670
+ EZObject.__init__(self, file)
671
+ args = _parse_struct(file)
672
+ _construct(args)
673
+ self.axis_go_zero = bool(args[0])
674
+ self.only_once_origin = bool(args[1])
675
+ self.relative = bool(args[2])
676
+ self.unit_type = args[3] # Pulse (0), MM (1), Degree(2).
677
+ self.pulse_per_mm = args[4]
678
+ self.move_pulse = args[5]
679
+ self.max_speed = args[6]
680
+ self.min_speed = args[7]
681
+ self.acceleration_time = args[8]
682
+
683
+
684
+ class EZText(EZObject):
685
+ """
686
+ Text objects.
687
+ """
688
+
689
+ def __init__(self, file):
690
+ EZObject.__init__(self, file)
691
+ args = _parse_struct(file)
692
+ _interpret(args, 10, str)
693
+ _interpret(args, 18, str)
694
+ _interpret(args, 44, str)
695
+ _interpret(args, 54, str)
696
+ _construct(args)
697
+ self.font_angle = args[0] # Font angle in Text.
698
+ self.height = args[1] # Height in MM
699
+ self.text_space_setting = args[5] # 0 auto, 1 between, 2 center
700
+ self.text_space = args[12]
701
+ self.char_space = args[13]
702
+ self.line_space = args[14]
703
+ self.font = args[18] # Arial, JSF Font, etc
704
+ self.font2 = args[44]
705
+ self.x, self.y = args[7]
706
+ self.text = args[10]
707
+ self.hatch_loop_distance = args[21]
708
+ self.circle_text_enable = args[48]
709
+ self.circle_text_diameter = args[49]
710
+ self.circle_text_base_angle = args[50]
711
+ self.circle_text_range_limit_enable = args[51]
712
+ self.circle_text_range_limit_angle = args[52]
713
+ self.save_options = args[53] # 3 boolean values
714
+ self.save_filename = args[54]
715
+ self.circle_text_button_flags = args[
716
+ 85
717
+ ] # 2 is first button, 1 is right to left.
718
+ (count,) = struct.unpack("<i", file.read(4))
719
+ for i in range(count):
720
+ (_type,) = struct.unpack("<H", file.read(2))
721
+ # type, 7 file. 1 Text. 2 Serial
722
+ extradata = _parse_struct(file)
723
+ _construct(extradata)
724
+ extradata2 = _parse_struct(file)
725
+ _construct(extradata2)
726
+ (unk,) = struct.unpack("<i", file.read(4))
727
+
728
+
729
+ class EZImage(EZObject):
730
+ """
731
+ Image objects consist of a lot of properties to control the encoding of the image and a 24-bit bitmap.
732
+ """
733
+
734
+ def __init__(self, file):
735
+ EZObject.__init__(self, file)
736
+ args = _parse_struct(file)
737
+ _construct(args)
738
+
739
+ image_bytes = bytearray(file.read(2)) # BM
740
+ image_length = file.read(4) # int32le
741
+ (size,) = struct.unpack("<i", image_length)
742
+ image_bytes += image_length
743
+ image_bytes += file.read(size - 6)
744
+
745
+ from PIL import Image
746
+
747
+ image = Image.open(BytesIO(image_bytes))
748
+
749
+ self.image_path = args[0]
750
+ self.width = args[5]
751
+ self.height = args[4]
752
+ self.fixed_dpi_x = args[9]
753
+ self.fixed_dpi_y = args[333 - 15]
754
+ self.image = image
755
+ self.powermap = args[74 - 15 : 330 - 15]
756
+ self.scan_line_increment = args[29 - 15]
757
+ self.scan_line_increment_value = args[30 - 15]
758
+ self.disable_mark_low_gray_point = args[31 - 15]
759
+ self.disable_mark_low_gray_point_value = args[32 - 15]
760
+ self.acc_distance_mm = args[331 - 15]
761
+ self.dec_distance_mm = args[332 - 15]
762
+ self.all_offset_mm = args[334 - 15]
763
+ self.bidirectional_offset = args[330 - 15]
764
+ self.status_bits = args[25 - 15]
765
+ self.mirror_x = bool(self.status_bits & 0x20)
766
+ self.mirror_y = bool(self.status_bits & 0x40)
767
+
768
+
769
+ class EZHatch(list, EZObject):
770
+ """
771
+ Hatch is a modification group. All three hatch elements are given properties for each hatch. The hatch contains
772
+ the actual elements that were to be given a hatch. As well as a cache-group of curve items that actually are the
773
+ given hatch properly rendered.
774
+ """
775
+
776
+ def __init__(self, file):
777
+ list.__init__(self)
778
+ EZObject.__init__(self, file)
779
+ args = _parse_struct(file)
780
+ _construct(args)
781
+ self.mark_contours = args[0]
782
+ self.mark_contours_type = args[41]
783
+
784
+ self.hatch1_enabled = args[1]
785
+ self.hatch1_type = args[3]
786
+ # Includes average distribute line, allcalc, follow edge, crosshatch
787
+ # spiral = 0x50
788
+ self.hatch1_type_all_calc = self.hatch1_type & 0x1
789
+ self.hatch1_type_follow_edge = self.hatch1_type & 0x2
790
+ self.hatch1_type_crosshatch = self.hatch1_type & 0x400
791
+ self.hatch1_angle = args[8]
792
+ self.hatch1_pen = args[2]
793
+ self.hatch1_line_space = args[5]
794
+ self.hatch1_edge_offset = args[4]
795
+ self.hatch1_start_offset = args[6]
796
+ self.hatch1_end_offset = args[7]
797
+ self.hatch1_line_reduction = args[29]
798
+ self.hatch1_number_of_loops = args[32]
799
+ self.hatch1_loop_distance = args[35]
800
+ self.hatch1_angle_inc = args[18]
801
+
802
+ self.hatch2_enabled = args[9]
803
+ self.hatch2_type = args[11]
804
+ self.hatch2_angle = args[16]
805
+ self.hatch2_pen = args[10]
806
+ self.hatch2_line_space = args[13]
807
+ self.hatch2_edge_offset = args[12]
808
+ self.hatch2_start_offset = args[14]
809
+ self.hatch2_end_offset = args[15]
810
+ self.hatch2_line_reduction = args[30]
811
+ self.hatch2_number_of_loops = args[33]
812
+ self.hatch2_loop_distance = args[36]
813
+ self.hatch2_angle_inc = args[19]
814
+
815
+ self.hatch3_enabled = args[20]
816
+ self.hatch3_type = args[22]
817
+ self.hatch3_angle = args[27]
818
+ self.hatch3_pen = args[21]
819
+ self.hatch3_line_space = args[24]
820
+ self.hatch3_edge_offset = args[23]
821
+ self.hatch3_start_offset = args[25]
822
+ self.hatch3_end_offset = args[26]
823
+ self.hatch3_line_reduction = args[31]
824
+ self.hatch3_number_of_loops = args[34]
825
+ self.hatch3_loop_distance = args[37]
826
+ self.hatch3_angle_inc = args[28]
827
+ try:
828
+ self.hatch1_count = args[42]
829
+ self.hatch2_count = args[43]
830
+ self.hatch3_count = args[44]
831
+ except IndexError:
832
+ # Older Version without count values.
833
+ pass
834
+ tell = file.tell()
835
+ (check,) = struct.unpack("<i", file.read(4))
836
+ file.seek(tell, 0)
837
+ if check == 15:
838
+ self.group = EZGroup(file)
839
+ else:
840
+ self.group = None
841
+
842
+
843
+ object_map = {
844
+ 1: EZCurve,
845
+ 3: EZRect,
846
+ 4: EZCircle,
847
+ 5: EZEllipse,
848
+ 6: EZPolygon,
849
+ 0x30: EZCombine,
850
+ 0x40: EZImage,
851
+ 0x60: EZSpiral,
852
+ 0x6000: EZEncoderDistance,
853
+ 0x5000: EZExtendAxis,
854
+ 0x4000: EZOutput,
855
+ 0x3000: EZInput,
856
+ 0x2000: EZTimer,
857
+ 0x800: EZText,
858
+ 0x10: EZGroup,
859
+ 0x50: EZVectorFile,
860
+ 0x20: EZHatch,
861
+ }
862
+
863
+
864
+ def parse_object(file, objects):
865
+ object_type = struct.unpack("<i", file.read(4))[0] # 0
866
+ if object_type == 0:
867
+ return False
868
+ ez_class = object_map.get(object_type)
869
+ assert ez_class
870
+ objects.append(ez_class(file))
871
+ return True
872
+
873
+
874
+ class EZDLoader:
875
+ @staticmethod
876
+ def load_types():
877
+ yield "EZCad2 Files", ("ezd",), "application/x-ezd"
878
+
879
+ @staticmethod
880
+ def load(context, elements_service, pathname, **kwargs):
881
+ try:
882
+ with open(pathname, "br") as file:
883
+ ezfile = EZCFile(file)
884
+ except (IOError, IndexError) as e:
885
+ raise BadFileError(str(e)) from e
886
+ except struct.error:
887
+ raise BadFileError(
888
+ "Unseen sequence, object, or formatting.\n"
889
+ "File format was only partially unrecognized.\n"
890
+ "Please raise an github issue and submit this file for review.\n"
891
+ )
892
+ elements_service._loading_cleared = True
893
+
894
+ ez_processor = EZProcessor(elements_service)
895
+ ez_processor.process(ezfile, pathname)
896
+ return True
897
+
898
+
899
+ class EZProcessor:
900
+ def __init__(self, elements):
901
+ self.elements = elements
902
+ self.element_list = list()
903
+ self.regmark_list = list()
904
+ self.pathname = None
905
+ self.regmark = self.elements.reg_branch
906
+ self.op_branch = elements.op_branch
907
+ self.elem_branch = elements.elem_branch
908
+
909
+ self.width = elements.device.view.unit_width
910
+ self.height = elements.device.view.unit_height
911
+ self.cx = self.width / 2.0
912
+ self.cy = self.height / 2.0
913
+ self.matrix = Matrix.scale(UNITS_PER_MM, -UNITS_PER_MM)
914
+ self.matrix.post_translate(self.cx, self.cy)
915
+
916
+ def process(self, ez, pathname):
917
+ self.op_branch.remove_all_children()
918
+ self.elem_branch.remove_all_children()
919
+ self.pathname = pathname
920
+ file_node = self.elem_branch.add(type="file", filepath=pathname)
921
+ file_node.focus()
922
+ for f in ez.objects:
923
+ self.parse(ez, f, file_node, self.op_branch)
924
+
925
+ def parse(self, ez, element, elem, op, op_add=None, path=None):
926
+ """
927
+ Parse ez structure into MK specific tree structure and objects.
928
+
929
+ @param ez: EZFile object
930
+ @param element: ezobject being parsed.
931
+ @param elem: element context.
932
+ @param op: operation context
933
+ @param op_add: Operation we should add to rather than create.
934
+ @param path: Path we should append to rather than create.
935
+ @return:
936
+ """
937
+ if isinstance(element, EZText):
938
+ node = elem.add(type="elem text", text=element.text, transform=self.matrix)
939
+ p = ez.pens[element.pen]
940
+ if op_add is None:
941
+ op_add = op.add(type="op engrave", **p.__dict__)
942
+ op_add.add_reference(node)
943
+ elif isinstance(element, EZCurve):
944
+ points = element.points
945
+ if len(points) == 0:
946
+ return
947
+ if path is None:
948
+ append_path = False
949
+ path = Path(stroke="black", transform=self.matrix)
950
+ else:
951
+ append_path = True
952
+
953
+ last_end = None
954
+ for t, closed, contour in points:
955
+ cpt = [
956
+ complex(contour[i], contour[i + 1])
957
+ for i in range(0, len(contour), 2)
958
+ ]
959
+ if last_end != cpt[0]:
960
+ path.move(cpt[0])
961
+ if t == 1:
962
+ path.line(*cpt[1:])
963
+ elif t == 2:
964
+ path.quad(*cpt[1:])
965
+ elif t == 3:
966
+ path.cubic(*cpt[1:])
967
+ last_end = cpt[-1]
968
+ if points[-1][1]:
969
+ # Path is closed.
970
+ path.closed()
971
+ if append_path:
972
+ return
973
+ node = elem.add(
974
+ type="elem path",
975
+ path=path,
976
+ stroke_width=self.elements.default_strokewidth,
977
+ )
978
+ p = ez.pens[element.pen]
979
+ if op_add is None:
980
+ op_add = op.add(type="op engrave", **p.__dict__)
981
+ op_add.add_reference(node)
982
+ elif isinstance(element, EZPolygon):
983
+ m = element.matrix
984
+ mx = Matrix(m[0], m[1], m[3], m[4], m[6], m[7])
985
+ mx *= self.matrix
986
+ x0, y0 = element.corner_upper_left
987
+ x1, y1 = element.corner_bottom_right
988
+ step = math.tau / element.sides
989
+ cx, cy = (x0 + x1) / 2.0, (y0 + y1) / 2.0
990
+ rx = (x1 - x0) / 2.0
991
+ ry = (y1 - y0) / 2.0
992
+ pts = []
993
+ theta = step / 2.0
994
+ for i in range(element.sides):
995
+ pts.append((cx + math.cos(theta) * rx, cy + math.sin(theta) * ry))
996
+ theta += step
997
+ polyline = Polygon(points=pts, transform=mx, stroke="black")
998
+ node = elem.add(
999
+ type="elem polyline",
1000
+ shape=polyline,
1001
+ stroke_width=self.elements.default_strokewidth,
1002
+ )
1003
+ p = ez.pens[element.pen]
1004
+ if op_add is None:
1005
+ op_add = op.add(type="op engrave", **p.__dict__)
1006
+ op_add.add_reference(node)
1007
+ elif isinstance(element, EZCircle):
1008
+ m = element.matrix
1009
+ mx = Matrix(m[0], m[1], m[3], m[4], m[6], m[7])
1010
+ mx *= self.matrix
1011
+ node = elem.add(
1012
+ cx=element.center[0],
1013
+ cy=element.center[1],
1014
+ rx=element.radius,
1015
+ ry=element.radius,
1016
+ stroke=Color("black"),
1017
+ matrix=mx,
1018
+ stroke_width=self.elements.default_strokewidth,
1019
+ type="elem ellipse",
1020
+ )
1021
+ p = ez.pens[element.pen]
1022
+ if op_add is None:
1023
+ op_add = op.add(type="op engrave", **p.__dict__)
1024
+ op_add.add_reference(node)
1025
+ elif isinstance(element, EZEllipse):
1026
+ m = element.matrix
1027
+ mx = Matrix(m[0], m[1], m[3], m[4], m[6], m[7])
1028
+ mx *= self.matrix
1029
+ x0, y0 = element.corner_upper_left
1030
+ x1, y1 = element.corner_bottom_right
1031
+ node = elem.add(
1032
+ cx=(x0 + x1) / 2.0,
1033
+ cy=(y0 + y1) / 2.0,
1034
+ rx=(x1 - x0) / 2.0,
1035
+ ry=(y1 - y0) / 2.0,
1036
+ matrix=mx,
1037
+ stroke=Color("black"),
1038
+ stroke_width=self.elements.default_strokewidth,
1039
+ type="elem ellipse",
1040
+ )
1041
+ p = ez.pens[element.pen]
1042
+ if op_add is None:
1043
+ op_add = op.add(type="op engrave", **p.__dict__)
1044
+ op_add.add_reference(node)
1045
+ elif isinstance(element, EZRect):
1046
+ m = element.matrix
1047
+ mx = Matrix(m[0], m[1], m[3], m[4], m[6], m[7])
1048
+ mx *= self.matrix
1049
+ x0, y0 = element.corner_upper_left
1050
+ x1, y1 = element.corner_bottom_right
1051
+ node = elem.add(
1052
+ x=x0,
1053
+ y=y0,
1054
+ width=x1 - x0,
1055
+ height=y1 - y0,
1056
+ matrix=mx,
1057
+ stroke=Color("black"),
1058
+ stroke_width=self.elements.default_strokewidth,
1059
+ type="elem rect",
1060
+ )
1061
+ p = ez.pens[element.pen]
1062
+ if op_add is None:
1063
+ op_add = op.add(type="op engrave", **p.__dict__)
1064
+ op_add.add_reference(node)
1065
+ elif isinstance(element, EZTimer):
1066
+ op.add(type="util wait", wait=element.wait_time / 1000.0)
1067
+ elif isinstance(element, EZOutput):
1068
+ mask = 1 << element.output_bit
1069
+ bits = mask if element.low_to_high else 0
1070
+
1071
+ op.add(
1072
+ type="util output",
1073
+ output_value=bits,
1074
+ output_mask=mask,
1075
+ )
1076
+ if element.timed_high:
1077
+ op.add(type="util wait", wait=element.wait_time / 1000.0)
1078
+ op.add(
1079
+ type="util output",
1080
+ output_value=~bits,
1081
+ output_mask=mask,
1082
+ )
1083
+ elif isinstance(element, EZInput):
1084
+ op.add(
1085
+ type="util input",
1086
+ input_message=element.message,
1087
+ input_value=element.input_port_bits,
1088
+ input_mask=element.input_port_bits,
1089
+ )
1090
+ elif isinstance(element, EZImage):
1091
+ image = element.image
1092
+ left, top = self.matrix.point_in_matrix_space(
1093
+ (
1094
+ element.position[0] - (element.width / 2.0),
1095
+ element.position[1] + element.height / 2.0,
1096
+ )
1097
+ )
1098
+ w, h = image.size
1099
+ unit_width = element.width * UNITS_PER_MM
1100
+ unit_height = element.height * UNITS_PER_MM
1101
+ matrix = Matrix.scale(
1102
+ (unit_width / w),
1103
+ (unit_height / h),
1104
+ )
1105
+ _dpi = int(
1106
+ round(
1107
+ (
1108
+ float((w * UNITS_PER_INCH) / unit_width)
1109
+ + float((h * UNITS_PER_INCH) / unit_height)
1110
+ )
1111
+ / 2.0,
1112
+ )
1113
+ )
1114
+ matrix.post_translate(left, top)
1115
+ node = elem.add(type="elem image", image=image, matrix=matrix, dpi=_dpi)
1116
+ p = ez.pens[element.pen]
1117
+ if op_add is None:
1118
+ op_add = op.add(type="op image", **p.__dict__)
1119
+ op_add.add_reference(node)
1120
+ elif isinstance(element, EZVectorFile):
1121
+ elem = elem.add(type="group", label=element.label)
1122
+ for child in element:
1123
+ # (self, ez, element, elem, op)
1124
+ self.parse(ez, child, elem, op, op_add=op_add, path=path)
1125
+ elif isinstance(element, EZHatch):
1126
+ p = dict(ez.pens[element.pen].__dict__)
1127
+
1128
+ op_add = op.add(type="op engrave", **p)
1129
+ if "label" in p:
1130
+ # Both pen and hatch have a label, we shall use the hatch-label for hatch; pen for op.
1131
+ del p["label"]
1132
+ op_add.add(type="effect hatch", **p, label=element.label)
1133
+ for child in element:
1134
+ # Operands for the hatch.
1135
+ self.parse(ez, child, elem, op, op_add=op_add)
1136
+
1137
+ op_add = op.add(type="op engrave", **p)
1138
+ if element.group:
1139
+ path = Path(stroke="black", transform=self.matrix)
1140
+ for child in element.group:
1141
+ # Per-completed hatch elements.
1142
+ self.parse(ez, child, elem, op, op_add=op_add, path=path)
1143
+
1144
+ # All path elements are added, should add it to the tree.
1145
+ node = elem.add(
1146
+ type="elem path",
1147
+ path=path,
1148
+ stroke_width=self.elements.default_strokewidth,
1149
+ )
1150
+ p = ez.pens[element.pen]
1151
+ if op_add is None:
1152
+ op_add = op.add(type="op engrave", **p.__dict__)
1153
+ op_add.add_reference(node)
1154
+ elif isinstance(element, (EZGroup, EZCombine)):
1155
+ elem = elem.add(type="group", label=element.label)
1156
+ # recurse to children
1157
+ for child in element:
1158
+ self.parse(ez, child, elem, op, op_add=op_add, path=path)
1159
+ elif isinstance(element, EZSpiral):
1160
+ elem = elem.add(type="group", label=element.label)
1161
+ # recurse to children
1162
+ for child in element:
1163
+ self.parse(ez, child, elem, op)
1164
+ for child in element.group:
1165
+ self.parse(ez, child, elem, op)