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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (445) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1195 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1844 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4561 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4329 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +933 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/trace.py +651 -563
  66. meerk40t/core/elements/tree_commands.py +415 -409
  67. meerk40t/core/elements/undo_redo.py +116 -58
  68. meerk40t/core/elements/wordlist.py +319 -200
  69. meerk40t/core/exceptions.py +9 -9
  70. meerk40t/core/laserjob.py +220 -220
  71. meerk40t/core/logging.py +63 -63
  72. meerk40t/core/node/blobnode.py +83 -86
  73. meerk40t/core/node/bootstrap.py +105 -103
  74. meerk40t/core/node/branch_elems.py +40 -31
  75. meerk40t/core/node/branch_ops.py +45 -38
  76. meerk40t/core/node/branch_regmark.py +48 -41
  77. meerk40t/core/node/cutnode.py +29 -32
  78. meerk40t/core/node/effect_hatch.py +375 -257
  79. meerk40t/core/node/effect_warp.py +398 -0
  80. meerk40t/core/node/effect_wobble.py +441 -309
  81. meerk40t/core/node/elem_ellipse.py +404 -309
  82. meerk40t/core/node/elem_image.py +1082 -801
  83. meerk40t/core/node/elem_line.py +358 -292
  84. meerk40t/core/node/elem_path.py +259 -201
  85. meerk40t/core/node/elem_point.py +129 -102
  86. meerk40t/core/node/elem_polyline.py +310 -246
  87. meerk40t/core/node/elem_rect.py +376 -286
  88. meerk40t/core/node/elem_text.py +445 -418
  89. meerk40t/core/node/filenode.py +59 -40
  90. meerk40t/core/node/groupnode.py +138 -74
  91. meerk40t/core/node/image_processed.py +777 -766
  92. meerk40t/core/node/image_raster.py +156 -113
  93. meerk40t/core/node/layernode.py +31 -31
  94. meerk40t/core/node/mixins.py +135 -107
  95. meerk40t/core/node/node.py +1427 -1304
  96. meerk40t/core/node/nutils.py +117 -114
  97. meerk40t/core/node/op_cut.py +462 -335
  98. meerk40t/core/node/op_dots.py +296 -251
  99. meerk40t/core/node/op_engrave.py +414 -311
  100. meerk40t/core/node/op_image.py +755 -369
  101. meerk40t/core/node/op_raster.py +787 -522
  102. meerk40t/core/node/place_current.py +37 -40
  103. meerk40t/core/node/place_point.py +329 -126
  104. meerk40t/core/node/refnode.py +58 -47
  105. meerk40t/core/node/rootnode.py +225 -219
  106. meerk40t/core/node/util_console.py +48 -48
  107. meerk40t/core/node/util_goto.py +84 -65
  108. meerk40t/core/node/util_home.py +61 -61
  109. meerk40t/core/node/util_input.py +102 -102
  110. meerk40t/core/node/util_output.py +102 -102
  111. meerk40t/core/node/util_wait.py +65 -65
  112. meerk40t/core/parameters.py +709 -707
  113. meerk40t/core/planner.py +875 -785
  114. meerk40t/core/plotplanner.py +656 -652
  115. meerk40t/core/space.py +120 -113
  116. meerk40t/core/spoolers.py +706 -705
  117. meerk40t/core/svg_io.py +1836 -1549
  118. meerk40t/core/treeop.py +534 -445
  119. meerk40t/core/undos.py +278 -124
  120. meerk40t/core/units.py +784 -680
  121. meerk40t/core/view.py +393 -322
  122. meerk40t/core/webhelp.py +62 -62
  123. meerk40t/core/wordlist.py +513 -504
  124. meerk40t/cylinder/cylinder.py +247 -0
  125. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  126. meerk40t/cylinder/gui/gui.py +24 -0
  127. meerk40t/device/__init__.py +1 -1
  128. meerk40t/device/basedevice.py +322 -123
  129. meerk40t/device/devicechoices.py +50 -0
  130. meerk40t/device/dummydevice.py +163 -128
  131. meerk40t/device/gui/defaultactions.py +618 -602
  132. meerk40t/device/gui/effectspanel.py +114 -0
  133. meerk40t/device/gui/formatterpanel.py +253 -290
  134. meerk40t/device/gui/warningpanel.py +337 -260
  135. meerk40t/device/mixins.py +13 -13
  136. meerk40t/dxf/__init__.py +1 -1
  137. meerk40t/dxf/dxf_io.py +766 -554
  138. meerk40t/dxf/plugin.py +47 -35
  139. meerk40t/external_plugins.py +79 -79
  140. meerk40t/external_plugins_build.py +28 -28
  141. meerk40t/extra/cag.py +112 -116
  142. meerk40t/extra/coolant.py +403 -0
  143. meerk40t/extra/encode_detect.py +198 -0
  144. meerk40t/extra/ezd.py +1165 -1165
  145. meerk40t/extra/hershey.py +835 -340
  146. meerk40t/extra/imageactions.py +322 -316
  147. meerk40t/extra/inkscape.py +630 -622
  148. meerk40t/extra/lbrn.py +424 -424
  149. meerk40t/extra/outerworld.py +284 -0
  150. meerk40t/extra/param_functions.py +1542 -1556
  151. meerk40t/extra/potrace.py +257 -253
  152. meerk40t/extra/serial_exchange.py +118 -0
  153. meerk40t/extra/updater.py +602 -453
  154. meerk40t/extra/vectrace.py +147 -146
  155. meerk40t/extra/winsleep.py +83 -83
  156. meerk40t/extra/xcs_reader.py +597 -0
  157. meerk40t/fill/fills.py +781 -335
  158. meerk40t/fill/patternfill.py +1061 -1061
  159. meerk40t/fill/patterns.py +614 -567
  160. meerk40t/grbl/control.py +87 -87
  161. meerk40t/grbl/controller.py +990 -903
  162. meerk40t/grbl/device.py +1081 -768
  163. meerk40t/grbl/driver.py +989 -771
  164. meerk40t/grbl/emulator.py +532 -497
  165. meerk40t/grbl/gcodejob.py +783 -767
  166. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  167. meerk40t/grbl/gui/grblcontroller.py +485 -271
  168. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  169. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  170. meerk40t/grbl/gui/gui.py +147 -116
  171. meerk40t/grbl/interpreter.py +44 -44
  172. meerk40t/grbl/loader.py +22 -22
  173. meerk40t/grbl/mock_connection.py +56 -56
  174. meerk40t/grbl/plugin.py +294 -264
  175. meerk40t/grbl/serial_connection.py +93 -88
  176. meerk40t/grbl/tcp_connection.py +81 -79
  177. meerk40t/grbl/ws_connection.py +112 -0
  178. meerk40t/gui/__init__.py +1 -1
  179. meerk40t/gui/about.py +2042 -296
  180. meerk40t/gui/alignment.py +1644 -1608
  181. meerk40t/gui/autoexec.py +199 -0
  182. meerk40t/gui/basicops.py +791 -670
  183. meerk40t/gui/bufferview.py +77 -71
  184. meerk40t/gui/busy.py +170 -133
  185. meerk40t/gui/choicepropertypanel.py +1673 -1469
  186. meerk40t/gui/consolepanel.py +706 -542
  187. meerk40t/gui/devicepanel.py +687 -581
  188. meerk40t/gui/dialogoptions.py +110 -107
  189. meerk40t/gui/executejob.py +316 -306
  190. meerk40t/gui/fonts.py +90 -90
  191. meerk40t/gui/functionwrapper.py +252 -0
  192. meerk40t/gui/gui_mixins.py +729 -0
  193. meerk40t/gui/guicolors.py +205 -182
  194. meerk40t/gui/help_assets/help_assets.py +218 -201
  195. meerk40t/gui/helper.py +154 -0
  196. meerk40t/gui/hersheymanager.py +1430 -846
  197. meerk40t/gui/icons.py +3422 -2747
  198. meerk40t/gui/imagesplitter.py +555 -508
  199. meerk40t/gui/keymap.py +354 -344
  200. meerk40t/gui/laserpanel.py +892 -806
  201. meerk40t/gui/laserrender.py +1470 -1232
  202. meerk40t/gui/lasertoolpanel.py +805 -793
  203. meerk40t/gui/magnetoptions.py +436 -0
  204. meerk40t/gui/materialmanager.py +2917 -0
  205. meerk40t/gui/materialtest.py +1722 -1694
  206. meerk40t/gui/mkdebug.py +646 -359
  207. meerk40t/gui/mwindow.py +163 -140
  208. meerk40t/gui/navigationpanels.py +2605 -2467
  209. meerk40t/gui/notes.py +143 -142
  210. meerk40t/gui/opassignment.py +414 -410
  211. meerk40t/gui/operation_info.py +310 -299
  212. meerk40t/gui/plugin.py +494 -328
  213. meerk40t/gui/position.py +714 -669
  214. meerk40t/gui/preferences.py +901 -650
  215. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  216. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  217. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  218. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  219. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  220. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  221. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  222. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  223. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  224. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  225. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  226. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  227. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  228. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  229. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  230. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  231. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  232. meerk40t/gui/propertypanels/textproperty.py +770 -755
  233. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  234. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  235. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  236. meerk40t/gui/ribbon.py +2468 -2210
  237. meerk40t/gui/scene/scene.py +1100 -1051
  238. meerk40t/gui/scene/sceneconst.py +22 -22
  239. meerk40t/gui/scene/scenepanel.py +439 -349
  240. meerk40t/gui/scene/scenespacewidget.py +365 -365
  241. meerk40t/gui/scene/widget.py +518 -505
  242. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  243. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  244. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  245. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  246. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  247. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  248. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  249. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  250. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  251. meerk40t/gui/scenewidgets/rectselectwidget.py +589 -346
  252. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  253. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  254. meerk40t/gui/scenewidgets/selectionwidget.py +2952 -2756
  255. meerk40t/gui/simpleui.py +357 -333
  256. meerk40t/gui/simulation.py +2431 -2094
  257. meerk40t/gui/snapoptions.py +208 -203
  258. meerk40t/gui/spoolerpanel.py +1227 -1180
  259. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  260. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  261. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  262. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  263. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  264. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  265. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  266. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  267. meerk40t/gui/themes.py +200 -78
  268. meerk40t/gui/tips.py +591 -0
  269. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  270. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  271. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  272. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  273. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  274. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  275. meerk40t/gui/toolwidgets/toolline.py +39 -144
  276. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  277. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  278. meerk40t/gui/toolwidgets/toolmeasure.py +160 -216
  279. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  280. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  281. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  282. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  283. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  284. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  285. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  286. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  287. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  288. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  289. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  290. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  291. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  292. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  293. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  294. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  295. meerk40t/gui/usbconnect.py +98 -91
  296. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  297. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  298. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  299. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  300. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  301. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  302. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  303. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  304. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  305. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  306. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  307. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  308. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  309. meerk40t/gui/wordlisteditor.py +985 -931
  310. meerk40t/gui/wxmeerk40t.py +1444 -1169
  311. meerk40t/gui/wxmmain.py +5578 -4112
  312. meerk40t/gui/wxmribbon.py +1591 -1076
  313. meerk40t/gui/wxmscene.py +1635 -1453
  314. meerk40t/gui/wxmtree.py +2410 -2089
  315. meerk40t/gui/wxutils.py +1769 -1099
  316. meerk40t/gui/zmatrix.py +102 -102
  317. meerk40t/image/__init__.py +1 -1
  318. meerk40t/image/dither.py +429 -0
  319. meerk40t/image/imagetools.py +2778 -2269
  320. meerk40t/internal_plugins.py +150 -130
  321. meerk40t/kernel/__init__.py +63 -12
  322. meerk40t/kernel/channel.py +259 -212
  323. meerk40t/kernel/context.py +538 -538
  324. meerk40t/kernel/exceptions.py +41 -41
  325. meerk40t/kernel/functions.py +463 -414
  326. meerk40t/kernel/jobs.py +100 -100
  327. meerk40t/kernel/kernel.py +3809 -3571
  328. meerk40t/kernel/lifecycles.py +71 -71
  329. meerk40t/kernel/module.py +49 -49
  330. meerk40t/kernel/service.py +147 -147
  331. meerk40t/kernel/settings.py +383 -343
  332. meerk40t/lihuiyu/controller.py +883 -876
  333. meerk40t/lihuiyu/device.py +1181 -1069
  334. meerk40t/lihuiyu/driver.py +1466 -1372
  335. meerk40t/lihuiyu/gui/gui.py +127 -106
  336. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  337. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  338. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  339. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  340. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  341. meerk40t/lihuiyu/interpreter.py +53 -53
  342. meerk40t/lihuiyu/laserspeed.py +450 -450
  343. meerk40t/lihuiyu/loader.py +90 -90
  344. meerk40t/lihuiyu/parser.py +404 -404
  345. meerk40t/lihuiyu/plugin.py +101 -102
  346. meerk40t/lihuiyu/tcp_connection.py +111 -109
  347. meerk40t/main.py +231 -165
  348. meerk40t/moshi/builder.py +788 -781
  349. meerk40t/moshi/controller.py +505 -499
  350. meerk40t/moshi/device.py +495 -442
  351. meerk40t/moshi/driver.py +862 -696
  352. meerk40t/moshi/gui/gui.py +78 -76
  353. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  354. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  355. meerk40t/moshi/plugin.py +43 -43
  356. meerk40t/network/console_server.py +102 -57
  357. meerk40t/network/kernelserver.py +10 -9
  358. meerk40t/network/tcp_server.py +142 -140
  359. meerk40t/network/udp_server.py +103 -77
  360. meerk40t/network/web_server.py +390 -0
  361. meerk40t/newly/controller.py +1158 -1144
  362. meerk40t/newly/device.py +874 -732
  363. meerk40t/newly/driver.py +540 -412
  364. meerk40t/newly/gui/gui.py +219 -188
  365. meerk40t/newly/gui/newlyconfig.py +116 -101
  366. meerk40t/newly/gui/newlycontroller.py +193 -186
  367. meerk40t/newly/gui/operationproperties.py +51 -51
  368. meerk40t/newly/mock_connection.py +82 -82
  369. meerk40t/newly/newly_params.py +56 -56
  370. meerk40t/newly/plugin.py +1214 -1246
  371. meerk40t/newly/usb_connection.py +322 -322
  372. meerk40t/rotary/gui/gui.py +52 -46
  373. meerk40t/rotary/gui/rotarysettings.py +240 -232
  374. meerk40t/rotary/rotary.py +202 -98
  375. meerk40t/ruida/control.py +291 -91
  376. meerk40t/ruida/controller.py +138 -1088
  377. meerk40t/ruida/device.py +672 -231
  378. meerk40t/ruida/driver.py +534 -472
  379. meerk40t/ruida/emulator.py +1494 -1491
  380. meerk40t/ruida/exceptions.py +4 -4
  381. meerk40t/ruida/gui/gui.py +71 -76
  382. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  383. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  384. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  385. meerk40t/ruida/loader.py +54 -52
  386. meerk40t/ruida/mock_connection.py +57 -109
  387. meerk40t/ruida/plugin.py +124 -87
  388. meerk40t/ruida/rdjob.py +2084 -945
  389. meerk40t/ruida/serial_connection.py +116 -0
  390. meerk40t/ruida/tcp_connection.py +146 -0
  391. meerk40t/ruida/udp_connection.py +73 -0
  392. meerk40t/svgelements.py +9671 -9669
  393. meerk40t/tools/driver_to_path.py +584 -579
  394. meerk40t/tools/geomstr.py +5583 -4680
  395. meerk40t/tools/jhfparser.py +357 -292
  396. meerk40t/tools/kerftest.py +904 -890
  397. meerk40t/tools/livinghinges.py +1168 -1033
  398. meerk40t/tools/pathtools.py +987 -949
  399. meerk40t/tools/pmatrix.py +234 -0
  400. meerk40t/tools/pointfinder.py +942 -942
  401. meerk40t/tools/polybool.py +940 -940
  402. meerk40t/tools/rasterplotter.py +1660 -547
  403. meerk40t/tools/shxparser.py +989 -901
  404. meerk40t/tools/ttfparser.py +726 -446
  405. meerk40t/tools/zinglplotter.py +595 -593
  406. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/LICENSE +21 -21
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/METADATA +150 -139
  408. meerk40t-0.9.7010.dist-info/RECORD +445 -0
  409. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/WHEEL +1 -1
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/top_level.txt +0 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/zip-safe +1 -1
  412. meerk40t/balormk/elementlightjob.py +0 -159
  413. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  414. test/bootstrap.py +0 -63
  415. test/test_cli.py +0 -12
  416. test/test_core_cutcode.py +0 -418
  417. test/test_core_elements.py +0 -144
  418. test/test_core_plotplanner.py +0 -397
  419. test/test_core_viewports.py +0 -312
  420. test/test_drivers_grbl.py +0 -108
  421. test/test_drivers_lihuiyu.py +0 -443
  422. test/test_drivers_newly.py +0 -113
  423. test/test_element_degenerate_points.py +0 -43
  424. test/test_elements_classify.py +0 -97
  425. test/test_elements_penbox.py +0 -22
  426. test/test_file_svg.py +0 -176
  427. test/test_fill.py +0 -155
  428. test/test_geomstr.py +0 -1523
  429. test/test_geomstr_nodes.py +0 -18
  430. test/test_imagetools_actualize.py +0 -306
  431. test/test_imagetools_wizard.py +0 -258
  432. test/test_kernel.py +0 -200
  433. test/test_laser_speeds.py +0 -3303
  434. test/test_length.py +0 -57
  435. test/test_lifecycle.py +0 -66
  436. test/test_operations.py +0 -251
  437. test/test_operations_hatch.py +0 -57
  438. test/test_ruida.py +0 -19
  439. test/test_spooler.py +0 -22
  440. test/test_tools_rasterplotter.py +0 -29
  441. test/test_wobble.py +0 -133
  442. test/test_zingl.py +0 -124
  443. {test → meerk40t/cylinder}/__init__.py +0 -0
  444. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  445. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/entry_points.txt +0 -0
@@ -1,1507 +1,1844 @@
1
- """
2
- This is a giant list of console commands that deal with and often implement the elements system in the program.
3
- """
4
-
5
- import re
6
- from copy import copy
7
-
8
- from meerk40t.core.node.effect_hatch import HatchEffectNode
9
- from meerk40t.core.node.op_cut import CutOpNode
10
- from meerk40t.core.node.op_dots import DotsOpNode
11
- from meerk40t.core.node.op_engrave import EngraveOpNode
12
- from meerk40t.core.node.op_image import ImageOpNode
13
- from meerk40t.core.node.op_raster import RasterOpNode
14
- from meerk40t.core.node.util_input import InputOperation
15
- from meerk40t.core.node.util_output import OutputOperation
16
- from meerk40t.core.node.util_wait import WaitOperation
17
- from meerk40t.core.units import Length
18
- from meerk40t.kernel import CommandSyntaxError
19
- from meerk40t.svgelements import Angle, Color, Matrix
20
-
21
-
22
- def plugin(kernel, lifecycle=None):
23
- _ = kernel.translation
24
- if lifecycle == "postboot":
25
- init_commands(kernel)
26
-
27
-
28
- def init_commands(kernel):
29
- self = kernel.elements
30
-
31
- _ = kernel.translation
32
-
33
- classify_new = self.post_classify
34
-
35
- @self.console_argument("filename")
36
- @self.console_command(
37
- "load",
38
- help=_("loads file from working directory"),
39
- input_type=None,
40
- output_type="file",
41
- )
42
- def load(channel, _, filename=None, **kwargs):
43
- import os
44
-
45
- if filename is None:
46
- channel(_("No file specified."))
47
- return
48
- new_file = os.path.join(self.kernel.current_directory, filename)
49
- if not os.path.exists(new_file):
50
- channel(_("No such file."))
51
- return
52
- try:
53
- channel(_("loading..."))
54
- result = self.load(new_file)
55
- if result:
56
- channel(_("Done."))
57
- except AttributeError:
58
- raise CommandSyntaxError(_("Loading files was not defined"))
59
- return "file", new_file
60
-
61
- # ==========
62
- # OPERATION BASE
63
- # ==========
64
-
65
- @self.console_command("operations", help=_("Show information about operations"))
66
- def element(**kwargs):
67
- self(".operation* list\n")
68
-
69
- @self.console_command(
70
- "operation.*", help=_("operation.*: selected operations"), output_type="ops"
71
- )
72
- def operation_select(**kwargs):
73
- return "ops", list(self.ops(emphasized=True))
74
-
75
- @self.console_command(
76
- "operation*", help=_("operation*: all operations"), output_type="ops"
77
- )
78
- def operation_all(**kwargs):
79
- return "ops", list(self.ops())
80
-
81
- @self.console_command(
82
- "operation~",
83
- help=_("operation~: non selected operations."),
84
- output_type="ops",
85
- )
86
- def operation_invert(**kwargs):
87
- return "ops", list(self.ops(emphasized=False))
88
-
89
- @self.console_command(
90
- "operation", help=_("operation: selected operations."), output_type="ops"
91
- )
92
- def operation_base(**kwargs):
93
- return "ops", list(self.ops(emphasized=True))
94
-
95
- @self.console_command(
96
- r"operation([0-9]+,?)+",
97
- help=_("operation0,2: operation #0 and #2"),
98
- regex=True,
99
- output_type="ops",
100
- )
101
- def operation_re(command, channel, _, **kwargs):
102
- arg = command[9:]
103
- op_values = []
104
- for value in arg.split(","):
105
- try:
106
- value = int(value)
107
- except ValueError:
108
- continue
109
- try:
110
- op = self.get_op(value)
111
- op_values.append(op)
112
- except IndexError:
113
- channel(_("index {index} out of range").format(index=value))
114
- return "ops", op_values
115
-
116
- @self.console_command(
117
- "select",
118
- help=_("Set these values as the selection."),
119
- input_type="ops",
120
- output_type="ops",
121
- )
122
- def operation_select_emphasis(data=None, **kwargs):
123
- self.set_emphasis(data)
124
- return "ops", data
125
-
126
- @self.console_command(
127
- "select+",
128
- help=_("Add the input to the selection"),
129
- input_type="ops",
130
- output_type="ops",
131
- )
132
- def operation_select_plus(data=None, **kwargs):
133
- ops = list(self.ops(emphasized=True))
134
- ops.extend(data)
135
- self.set_emphasis(ops)
136
- return "ops", ops
137
-
138
- @self.console_command(
139
- "select-",
140
- help=_("Remove the input data from the selection"),
141
- input_type="ops",
142
- output_type="ops",
143
- )
144
- def operation_select_minus(data=None, **kwargs):
145
- ops = list(self.ops(emphasized=True))
146
- for e in data:
147
- try:
148
- ops.remove(e)
149
- except ValueError:
150
- pass
151
- self.set_emphasis(ops)
152
- return "ops", ops
153
-
154
- @self.console_command(
155
- "select^",
156
- help=_("Toggle the input data in the selection"),
157
- input_type="ops",
158
- output_type="ops",
159
- )
160
- def operation_select_xor(data=None, **kwargs):
161
- ops = list(self.ops(emphasized=True))
162
- for e in data:
163
- try:
164
- ops.remove(e)
165
- except ValueError:
166
- ops.append(e)
167
- self.set_emphasis(ops)
168
- return "ops", ops
169
-
170
- @self.console_argument("start", type=int, help=_("start"))
171
- @self.console_argument("end", type=int, help=_("end"))
172
- @self.console_option("step", "s", type=int, default=1, help=_("step"))
173
- @self.console_command(
174
- "range",
175
- help=_("Subset existing selection by begin and end indices and step"),
176
- input_type=("ops", "elements"),
177
- output_type=("ops", "elements"),
178
- )
179
- def opelem_select_range(
180
- data=None, data_type=None, start=None, end=None, step=1, **kwargs
181
- ):
182
- sublist = list()
183
- for e in range(start, end, step):
184
- try:
185
- sublist.append(data[e])
186
- except IndexError:
187
- pass
188
- self.set_emphasis(sublist)
189
- return data_type, sublist
190
-
191
- @self.console_argument("filter", type=str, help=_("Filter to apply"))
192
- @self.console_command(
193
- "filter",
194
- help=_("Filter data by given value"),
195
- input_type=("ops", "elements"),
196
- output_type=("ops", "elements"),
197
- )
198
- def opelem_filter(channel=None, data=None, data_type=None, filter=None, **kwargs):
199
- """
200
- Apply a filter string to a filter particular operations from the current data.
201
- Operations or elements are evaluated in an infix prioritized stack format without spaces.
202
- Qualified values for all node types are: id, label, len, type
203
- Qualified element values are stroke, fill, dpi, elem
204
- Qualified operation values are speed, power, frequency, dpi, acceleration, op, passes, color, overscan
205
- Valid operators are >, >=, <, <=, =, ==, +, -, *, /, &, &&, |, and ||
206
- Valid string operators are startswith, endswith, contains.
207
- String values require single-quotes ', because the console interface requires double-quotes.
208
- e.g. filter speed>=10, filter speed=5+5, filter speed>power/10, filter speed==2*4+2
209
- e.g. filter engrave=op&speed=35|cut=op&speed=10
210
- e.g. filter len=0
211
- e.g. operation* filter "type='op image'" list
212
- e.g. element* filter "id startswith 'p'" list
213
- """
214
- sublist = list()
215
- _filter_parse = [
216
- ("STR", r"'([^']*)'"),
217
- ("SKIP", r"[ ,\t\n\x09\x0A\x0C\x0D]+"),
218
- ("OP20", r"(\*|/)"),
219
- ("OP15", r"(\+|-)"),
220
- ("OP11", r"(<=|>=|==|!=|startswith|endswith|contains)"),
221
- ("OP10", r"(<|>|=)"),
222
- ("OP5", r"(&&)"),
223
- ("OP4", r"(&)"),
224
- ("OP3", r"(\|\|)"),
225
- ("OP2", r"(\|)"),
226
- ("NUM", r"([-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"),
227
- (
228
- "COLOR",
229
- r"(#[0123456789abcdefABCDEF]{6}|#[0123456789abcdefABCDEF]{3})",
230
- ),
231
- (
232
- "TYPE",
233
- r"(raster|image|cut|engrave|dots|blob|rect|path|ellipse|point|image|line|polyline)",
234
- ),
235
- (
236
- "VAL",
237
- r"(type|op|speed|power|frequency|dpi|passes|color|overscan|len|elem|stroke|fill|id|label)",
238
- ),
239
- ]
240
- filter_re = re.compile("|".join("(?P<%s>%s)" % pair for pair in _filter_parse))
241
- operator = list()
242
- operand = list()
243
-
244
- def filter_parser(text: str):
245
- p = 0
246
- limit = len(text)
247
- while p < limit:
248
- match = filter_re.match(text, p)
249
- if match is None:
250
- break # No more matches.
251
- _kind = match.lastgroup
252
- _start = p
253
- p = match.end()
254
- if _kind == "SKIP":
255
- continue
256
- _value = match.group()
257
- yield _kind, _value, _start, p
258
-
259
- def solve_to(order: int):
260
- try:
261
- while len(operator) and operator[0][0] >= order:
262
- _p, op = operator.pop()
263
- v2 = operand.pop()
264
- v1 = operand.pop()
265
- try:
266
- if op == "==" or op == "=":
267
- operand.append(v1 == v2)
268
- elif op == "!=":
269
- operand.append(v1 != v2)
270
- elif op == ">":
271
- operand.append(v1 > v2)
272
- elif op == "<":
273
- operand.append(v1 < v2)
274
- elif op == "<=":
275
- operand.append(v1 <= v2)
276
- elif op == ">=":
277
- operand.append(v1 >= v2)
278
- elif op == "&&" or op == "&":
279
- operand.append(v1 and v2)
280
- elif op == "||" or op == "|":
281
- operand.append(v1 or v2)
282
- elif op == "*":
283
- operand.append(v1 * v2)
284
- elif op == "/":
285
- operand.append(v1 / v2)
286
- elif op == "+":
287
- operand.append(v1 + v2)
288
- elif op == "-":
289
- operand.append(v1 - v2)
290
- elif op == "startswith":
291
- operand.append(str(v1).startswith(str(v2)))
292
- elif op == "endswith":
293
- operand.append(str(v1).endswith(str(v2)))
294
- elif op == "contains":
295
- operand.append(str(v2) in (str(v1)))
296
- except TypeError:
297
- raise CommandSyntaxError("Cannot evaluate expression")
298
- except ZeroDivisionError:
299
- operand.append(float("inf"))
300
- except IndexError:
301
- pass
302
-
303
- for e in data:
304
- for kind, value, start, pos in filter_parser(filter):
305
- if kind == "COLOR":
306
- operand.append(Color(value))
307
- elif kind == "VAL":
308
- try:
309
- if value == "type":
310
- operand.append(e.type)
311
- elif value == "op":
312
- if e.type.startswith("op"):
313
- operand.append(e.type.replace("op", "").strip())
314
- else:
315
- operand.append(None)
316
- elif value == "speed":
317
- operand.append(e.speed)
318
- elif value == "power":
319
- operand.append(e.power)
320
- elif value == "frequency":
321
- operand.append(e.frequency)
322
- elif value == "dpi":
323
- operand.append(e.dpi)
324
- elif value == "passes":
325
- operand.append(e.passes)
326
- elif value == "color":
327
- operand.append(e.color)
328
- elif value == "len":
329
- try:
330
- operand.append(len(e.children))
331
- except AttributeError:
332
- operand.append(0)
333
- elif value == "elem":
334
- if e.type.startswith("elem"):
335
- operand.append(e.type.replace("elem", "").strip())
336
- else:
337
- operand.append(None)
338
- elif value == "stroke":
339
- operand.append(e.stroke)
340
- elif value == "fill":
341
- operand.append(e.fill)
342
- elif value == "stroke_width":
343
- operand.append(e.stroke_width)
344
- elif value == "id":
345
- operand.append(e.id)
346
- elif value == "label":
347
- operand.append(e.label)
348
- else:
349
- operand.append(e.settings.get(value))
350
- except AttributeError:
351
- operand.append(None)
352
- elif kind == "NUM":
353
- operand.append(float(value))
354
- elif kind == "TYPE":
355
- operand.append(value)
356
- elif kind == "STR":
357
- operand.append(value[1:-1])
358
- elif kind.startswith("OP"):
359
- precedence = int(kind[2:])
360
- solve_to(precedence)
361
- operator.append((precedence, value))
362
- solve_to(0)
363
- if len(operand) == 1:
364
- if operand.pop():
365
- sublist.append(e)
366
- else:
367
- raise CommandSyntaxError(_("Filter parse failed"))
368
-
369
- self.set_emphasis(sublist)
370
- return data_type, sublist
371
-
372
- @self.console_argument(
373
- "id",
374
- type=str,
375
- help=_("new id to set values to"),
376
- )
377
- @self.console_command(
378
- "id",
379
- help=_("id <id>"),
380
- input_type=("ops", "elements"),
381
- output_type=("elements", "ops"),
382
- )
383
- def opelem_id(command, channel, _, id=None, data=None, data_type=None, **kwargs):
384
- if id is None:
385
- # Display data about id.
386
- channel("----------")
387
- channel(_("ID Values:"))
388
- for i, e in enumerate(data):
389
- name = str(e)
390
- channel(
391
- _("{index}: {name} - id = {id}").format(index=i, name=name, id=e.id)
392
- )
393
- channel("----------")
394
- return
395
-
396
- if len(data) == 0:
397
- channel(_("No selected nodes"))
398
- return
399
- for e in data:
400
- e.id = id
401
- self.validate_ids()
402
- self.signal("element_property_update", data)
403
- self.signal("refresh_scene", "Scene")
404
- return data_type, data
405
-
406
- @self.console_argument(
407
- "label",
408
- type=str,
409
- help=_("new label to set values to"),
410
- )
411
- @self.console_command(
412
- "label",
413
- help=_("label <label>"),
414
- input_type=("ops", "elements"),
415
- output_type=("elements", "ops"),
416
- )
417
- def opelem_label(
418
- command, channel, _, label=None, data=None, data_type=None, **kwargs
419
- ):
420
- if label is None:
421
- # Display data about id.
422
- channel("----------")
423
- channel(_("Label Values:"))
424
- for i, e in enumerate(data):
425
- name = str(e)
426
- channel(
427
- _("{index}: {name} - label = {label}").format(
428
- index=i, name=name, label=e.label
429
- )
430
- )
431
- channel("----------")
432
- return
433
-
434
- if len(data) == 0:
435
- channel(_("No selected nodes"))
436
- return
437
- for e in data:
438
- e.label = label
439
- self.signal("element_property_update", data)
440
- self.signal("refresh_scene", "Scene")
441
- return data_type, data
442
-
443
- @self.console_command(
444
- "list",
445
- help=_("Show information about the chained data"),
446
- input_type="ops",
447
- output_type="ops",
448
- )
449
- def operation_list(channel, _, data=None, **kwargs):
450
- channel("----------")
451
- channel(_("Operations:"))
452
- index_ops = list(self.ops())
453
- for op_obj in data:
454
- i = index_ops.index(op_obj)
455
- select_piece = "*" if op_obj.emphasized else " "
456
- name = f"{select_piece} {i}: {str(op_obj)}"
457
- channel(name)
458
- if isinstance(op_obj, list):
459
- for q, oe in enumerate(op_obj):
460
- stroke_piece = (
461
- "None"
462
- if (not hasattr(oe, "stroke") or oe.stroke) is None
463
- else oe.stroke.hex
464
- )
465
- fill_piece = (
466
- "None"
467
- if (not hasattr(oe, "stroke") or oe.fill) is None
468
- else oe.fill.hex
469
- )
470
- ident_piece = str(oe.id)
471
- name = f"{''.ljust(5)}{q}: {str(type(oe).__name__)}-{ident_piece} s:{stroke_piece} f:{fill_piece}"
472
- channel(name)
473
- channel("----------")
474
-
475
- @self.console_option("color", "c", type=Color)
476
- @self.console_option("default", "D", type=bool)
477
- @self.console_option("speed", "s", type=float)
478
- @self.console_option("power", "p", type=float)
479
- @self.console_option("dpi", "d", type=int)
480
- @self.console_option("overscan", "o", type=self.length)
481
- @self.console_option("passes", "x", type=int)
482
- @self.console_option(
483
- "parallel",
484
- "P",
485
- type=bool,
486
- help=_("Creates a new operation for each element given"),
487
- action="store_true",
488
- )
489
- @self.console_option(
490
- "stroke",
491
- "K",
492
- type=bool,
493
- action="store_true",
494
- help=_(
495
- "Set the operation color based on the stroke if the first stroked item added to this operation"
496
- ),
497
- )
498
- @self.console_option(
499
- "fill",
500
- "F",
501
- type=bool,
502
- action="store_true",
503
- help=_(
504
- "Set the operation color based on the fill if the first filled item added to this operation"
505
- ),
506
- )
507
- @self.console_command(
508
- ("cut", "engrave", "raster", "imageop", "dots", "hatch"),
509
- help=_(
510
- "<cut/engrave/raster/imageop/dots/hatch> - group the elements into this operation"
511
- ),
512
- input_type=(None, "elements"),
513
- output_type="ops",
514
- )
515
- def makeop(
516
- command,
517
- data=None,
518
- color=None,
519
- default=None,
520
- speed=None,
521
- power=None,
522
- dpi=None,
523
- overscan=None,
524
- passes=None,
525
- parallel=False,
526
- stroke=False,
527
- fill=False,
528
- **kwargs,
529
- ):
530
- op_list = []
531
-
532
- def make_op():
533
- if command == "cut":
534
- return CutOpNode()
535
- elif command == "engrave":
536
- return EngraveOpNode()
537
- elif command == "raster":
538
- return RasterOpNode()
539
- elif command == "imageop":
540
- return ImageOpNode()
541
- elif command == "dots":
542
- return DotsOpNode()
543
- elif command == "hatch":
544
- parent_node = EngraveOpNode()
545
- parent_node.add_node(HatchEffectNode())
546
- return parent_node
547
- elif command == "waitop":
548
- return WaitOperation()
549
- elif command == "outputop":
550
- return OutputOperation()
551
- elif command == "inputop":
552
- return InputOperation()
553
- else:
554
- raise ValueError
555
-
556
- if parallel:
557
- if data is None:
558
- return "op", []
559
- for item in data:
560
- op = make_op()
561
- if color is not None:
562
- op.color = color
563
- elif fill:
564
- try:
565
- op.color = item.fill
566
- except AttributeError:
567
- continue
568
- elif stroke:
569
- try:
570
- op.color = item.stroke
571
- except AttributeError:
572
- continue
573
- if default is not None:
574
- op.default = default
575
- if speed is not None:
576
- op.speed = speed
577
- if power is not None:
578
- op.power = power
579
- if passes is not None:
580
- op.passes_custom = True
581
- op.passes = passes
582
- if dpi is not None:
583
- op.dpi = dpi
584
- if overscan is not None:
585
- op.overscan = overscan
586
- self.add_op(op)
587
- op.add_reference(item)
588
- op_list.append(op)
589
- else:
590
- op = make_op()
591
- if color is not None:
592
- op.color = color
593
- elif fill:
594
- try:
595
- op.color = data[0].fill
596
- except (AttributeError, IndexError):
597
- pass
598
- elif stroke:
599
- try:
600
- op.color = data[0].stroke
601
- except (AttributeError, IndexError):
602
- pass
603
- if default is not None:
604
- op.default = default
605
- if speed is not None:
606
- op.speed = speed
607
- if power is not None:
608
- op.power = power
609
- if passes is not None:
610
- op.passes_custom = True
611
- op.passes = passes
612
- if dpi is not None:
613
- op.dpi = dpi
614
- if overscan is not None:
615
- op.overscan = overscan
616
- self.add_op(op)
617
- if data is not None:
618
- for item in data:
619
- op.add_reference(item)
620
- op_list.append(op)
621
- return "ops", op_list
622
-
623
- @self.console_argument(
624
- "time",
625
- type=float,
626
- default=5,
627
- help=_("Time for the given wait operation."),
628
- )
629
- @self.console_command(
630
- "waitop",
631
- help=_("<waitop> - Create new utility operation"),
632
- input_type=None,
633
- output_type="ops",
634
- )
635
- def waitop(
636
- command,
637
- time=None,
638
- **kwargs,
639
- ):
640
- op = self.op_branch.add(type="util wait", wait=time)
641
- return "ops", [op]
642
-
643
- @self.console_argument(
644
- "mask",
645
- type=int,
646
- default=0,
647
- help=_("binary input/output mask"),
648
- )
649
- @self.console_argument(
650
- "value",
651
- type=int,
652
- default=0,
653
- help=_("binary input/output value"),
654
- )
655
- @self.console_command(
656
- ("outputop", "inputop"),
657
- help=_("<outputop, inputop> - Create new utility operation"),
658
- input_type=None,
659
- output_type="ops",
660
- )
661
- def io_op(
662
- command,
663
- mask=None,
664
- value=None,
665
- **kwargs,
666
- ):
667
- if command == "inputop":
668
- op = self.op_branch.add(
669
- type="util input", input_mask=mask, input_value=value
670
- )
671
- else:
672
- op = self.op_branch.add(
673
- type="util output", output_mask=mask, output_value=value
674
- )
675
- return "ops", [op]
676
-
677
- @self.console_command(
678
- "consoleop",
679
- help=_("<consoleop> - Create new utility operation"),
680
- )
681
- def consoleop(
682
- command,
683
- remainder=None,
684
- **kwargs,
685
- ):
686
- if remainder is not None:
687
- op = self.op_branch.add(type="util console", command=remainder)
688
- return "ops", [op]
689
-
690
- @self.console_argument("dpi", type=int, help=_("raster dpi"))
691
- @self.console_command("dpi", help=_("dpi <raster-dpi>"), input_type="ops")
692
- def op_dpi(command, channel, _, data, dpi=None, **kwrgs):
693
- if dpi is None:
694
- found = False
695
- for op in data:
696
- if op.type in ("op raster", "op image"):
697
- dpi = op.dpi
698
- channel(
699
- _("Step for {name} is currently: {dpi}").format(
700
- name=str(op), dpi=dpi
701
- )
702
- )
703
- found = True
704
- if not found:
705
- channel(_("No raster operations selected."))
706
- return
707
- for op in data:
708
- if op.type in ("op raster", "op image"):
709
- op.dpi = dpi
710
- op.updated()
711
- return "ops", data
712
-
713
- @self.console_option(
714
- "difference",
715
- "d",
716
- type=bool,
717
- action="store_true",
718
- help=_("Change speed by this amount."),
719
- )
720
- @self.console_option(
721
- "progress",
722
- "p",
723
- type=bool,
724
- action="store_true",
725
- help=_("Change speed for each item in order"),
726
- )
727
- @self.console_argument("speed", type=str, help=_("operation speed in mm/s"))
728
- @self.console_command(
729
- "speed", help=_("speed <speed>"), input_type="ops", output_type="ops"
730
- )
731
- def op_speed(
732
- command,
733
- channel,
734
- _,
735
- speed=None,
736
- difference=False,
737
- progress=False,
738
- data=None,
739
- **kwrgs,
740
- ):
741
- if speed is None:
742
- for op in data:
743
- old = op.speed
744
- channel(
745
- _("Speed for '{name}' is currently: {speed}").format(
746
- name=str(op), speed=old
747
- )
748
- )
749
- return
750
- if speed.endswith("%"):
751
- speed = speed[:-1]
752
- percent = True
753
- else:
754
- percent = False
755
-
756
- try:
757
- new_speed = float(speed)
758
- except ValueError:
759
- channel(_("Not a valid speed or percent."))
760
- return
761
- delta = 0
762
- for op in data:
763
- old = op.speed
764
- if percent and difference:
765
- s = old + old * (new_speed / 100.0)
766
- elif difference:
767
- s = old + new_speed
768
- elif percent:
769
- s = old * (new_speed / 100.0)
770
- elif progress:
771
- s = old + delta
772
- delta += new_speed
773
- else:
774
- s = new_speed
775
- if s < 0:
776
- s = 0
777
- op.speed = s
778
- channel(
779
- _("Speed for '{name}' updated {old_speed} -> {speed}").format(
780
- name=str(op), old_speed=old, speed=s
781
- )
782
- )
783
- op.updated()
784
- return "ops", data
785
-
786
- @self.console_argument(
787
- "power", type=int, help=_("power in pulses per inch (ppi, 1000=max)")
788
- )
789
- @self.console_option(
790
- "difference",
791
- "d",
792
- type=bool,
793
- action="store_true",
794
- help=_("Change power by this amount."),
795
- )
796
- @self.console_option(
797
- "progress",
798
- "p",
799
- type=bool,
800
- action="store_true",
801
- help=_("Change power for each item in order"),
802
- )
803
- @self.console_command(
804
- "power", help=_("power <ppi>"), input_type="ops", output_type="ops"
805
- )
806
- def op_power(
807
- command,
808
- channel,
809
- _,
810
- power=None,
811
- difference=False,
812
- progress=False,
813
- data=None,
814
- **kwrgs,
815
- ):
816
- if power is None:
817
- for op in data:
818
- old = op.power
819
- channel(
820
- _("Power for '{name}' is currently: {power}").format(
821
- name=str(op), power=old
822
- )
823
- )
824
- return
825
- delta = 0
826
- for op in data:
827
- old = op.power
828
- if progress:
829
- s = old + delta
830
- delta += power
831
- elif difference:
832
- s = old + power
833
- else:
834
- s = power
835
- if s > 1000:
836
- s = 1000
837
- if s < 0:
838
- s = 0
839
- op.power = s
840
- channel(
841
- _("Power for '{name}' updated {old_power} -> {power}").format(
842
- name=str(op), old_power=old, power=s
843
- )
844
- )
845
- op.updated()
846
- return "ops", data
847
-
848
- @self.console_argument(
849
- "frequency", type=float, help=_("frequency set for operation")
850
- )
851
- @self.console_option(
852
- "difference",
853
- "d",
854
- type=bool,
855
- action="store_true",
856
- help=_("Change speed by this amount."),
857
- )
858
- @self.console_option(
859
- "progress",
860
- "p",
861
- type=bool,
862
- action="store_true",
863
- help=_("Change speed for each item in order"),
864
- )
865
- @self.console_command(
866
- "frequency", help=_("frequency <kHz>"), input_type="ops", output_type="ops"
867
- )
868
- def op_frequency(
869
- command,
870
- channel,
871
- _,
872
- frequency=None,
873
- difference=False,
874
- progress=False,
875
- data=None,
876
- **kwrgs,
877
- ):
878
- if frequency is None:
879
- for op in data:
880
- old = op.frequency
881
- channel(
882
- _("Frequency for '{name}' is currently: {frequency}").format(
883
- name=str(op), frequency=old
884
- )
885
- )
886
- return
887
- delta = 0
888
- for op in data:
889
- old = op.frequency
890
- if progress:
891
- s = old + delta
892
- delta += frequency
893
- elif difference:
894
- s = old + frequency
895
- else:
896
- s = frequency
897
- if s < 0:
898
- s = 0
899
- op.frequency = s
900
- channel(
901
- _(
902
- "Frequency for '{name}' updated {old_frequency} -> {frequency}"
903
- ).format(name=str(op), old_frequency=old, frequency=s)
904
- )
905
- op.updated()
906
- return "ops", data
907
-
908
- @self.console_argument("passes", type=int, help=_("Set operation passes"))
909
- @self.console_command(
910
- "passes", help=_("passes <passes>"), input_type="ops", output_type="ops"
911
- )
912
- def op_passes(command, channel, _, passes=None, data=None, **kwrgs):
913
- if passes is None:
914
- for op in data:
915
- old_passes = op.passes
916
- channel(
917
- _("Passes for '{name}' is currently: {passes}").format(
918
- name=str(op), passes=old_passes
919
- )
920
- )
921
- return
922
- for op in data:
923
- old_passes = op.passes
924
- op.passes = passes
925
- if passes >= 1:
926
- op.passes_custom = True
927
- channel(
928
- _("Passes for '{name}' updated {old_passes} -> {passes}").format(
929
- name=str(op), old_passes=old_passes, passes=passes
930
- )
931
- )
932
- op.updated()
933
- return "ops", data
934
-
935
- @self.console_argument(
936
- "distance", type=Length, help=_("Set hatch-distance of operations")
937
- )
938
- @self.console_option(
939
- "difference",
940
- "d",
941
- type=bool,
942
- action="store_true",
943
- help=_("Change hatch-distance by this amount."),
944
- )
945
- @self.console_option(
946
- "progress",
947
- "p",
948
- type=bool,
949
- action="store_true",
950
- help=_("Change hatch-distance for each item in order"),
951
- )
952
- @self.console_command(
953
- "hatch-distance",
954
- help=_("hatch-distance <distance>"),
955
- input_type="ops",
956
- output_type="ops",
957
- )
958
- def op_hatch_distance(
959
- command,
960
- channel,
961
- _,
962
- distance=None,
963
- difference=False,
964
- progress=False,
965
- data=None,
966
- **kwrgs,
967
- ):
968
- if distance is None:
969
- for op in data:
970
- old = op.hatch_distance
971
- channel(
972
- _("Hatch Distance for '{name}' is currently: {distance}").format(
973
- name=str(op), distance=old
974
- )
975
- )
976
- return
977
- delta = 0
978
- for op in data:
979
- old = Length(op.hatch_distance)
980
- if progress:
981
- s = float(old) + delta
982
- delta += float(distance)
983
- elif difference:
984
- s = float(old) + float(distance)
985
- else:
986
- s = float(distance)
987
- if s < 0:
988
- s = 0
989
- op.hatch_distance = Length(amount=s).length_mm
990
- channel(
991
- _(
992
- "Hatch Distance for '{name}' updated {old_distance} -> {distance}"
993
- ).format(name=str(op), old_distance=old, distance=op.hatch_distance)
994
- )
995
- op.updated()
996
- return "ops", data
997
-
998
- @self.console_argument(
999
- "angle", type=Angle.parse, help=_("Set hatch-angle of operations")
1000
- )
1001
- @self.console_option(
1002
- "difference",
1003
- "d",
1004
- type=bool,
1005
- action="store_true",
1006
- help=_("Change hatch-distance by this amount."),
1007
- )
1008
- @self.console_option(
1009
- "progress",
1010
- "p",
1011
- type=bool,
1012
- action="store_true",
1013
- help=_("Change hatch-distance for each item in order"),
1014
- )
1015
- @self.console_command(
1016
- "hatch-angle",
1017
- help=_("hatch-angle <angle>"),
1018
- input_type="ops",
1019
- output_type="ops",
1020
- )
1021
- def op_hatch_angle(
1022
- command,
1023
- channel,
1024
- _,
1025
- angle=None,
1026
- difference=False,
1027
- progress=False,
1028
- data=None,
1029
- **kwrgs,
1030
- ):
1031
- if angle is None:
1032
- for op in data:
1033
- old = f"{Angle.parse(op.hatch_angle).as_turns:.4f}turn"
1034
- old_hatch_angle_deg = f"{Angle.parse(op.hatch_angle).as_degrees:.4f}deg"
1035
- channel(
1036
- _(
1037
- "Hatch Angle for '{name}' is currently: {angle} ({angle_degree})"
1038
- ).format(name=str(op), angle=old, angle_degree=old_hatch_angle_deg)
1039
- )
1040
- return
1041
- delta = 0
1042
- for op in data:
1043
- old = Angle.parse(op.hatch_angle)
1044
- if progress:
1045
- s = old + delta
1046
- delta += angle
1047
- elif difference:
1048
- s = old + angle
1049
- else:
1050
- s = angle
1051
- s = Angle.radians(float(s))
1052
- op.hatch_angle = f"{s.as_turns}turn"
1053
- new_hatch_angle_turn = f"{s.as_turns:.4f}turn"
1054
- new_hatch_angle_deg = f"{s.as_degrees:.4f}deg"
1055
-
1056
- channel(
1057
- _(
1058
- "Hatch Angle for '{name}' updated {old_angle} -> {angle} ({angle_degree})"
1059
- ).format(
1060
- name=str(op),
1061
- old_angle=f"{old.as_turns:.4f}turn",
1062
- angle=new_hatch_angle_turn,
1063
- angle_degree=new_hatch_angle_deg,
1064
- )
1065
- )
1066
- op.updated()
1067
- return "ops", data
1068
-
1069
- @self.console_command(
1070
- "disable",
1071
- help=_("Disable the given operations"),
1072
- input_type="ops",
1073
- output_type="ops",
1074
- )
1075
- def op_disable(command, channel, _, data=None, **kwrgs):
1076
- for op in data:
1077
- no_op = True
1078
- if hasattr(op, "output"):
1079
- try:
1080
- op.output = False
1081
- channel(_("Operation '{name}' disabled.").format(name=str(op)))
1082
- op.updated()
1083
- no_op = False
1084
- except AttributeError:
1085
- pass
1086
- if no_op:
1087
- channel(_("Operation '{name}' can't be disabled.").format(name=str(op)))
1088
- return "ops", data
1089
-
1090
- @self.console_command(
1091
- "enable",
1092
- help=_("Enable the given operations"),
1093
- input_type="ops",
1094
- output_type="ops",
1095
- )
1096
- def op_enable(command, channel, _, data=None, **kwrgs):
1097
- for op in data:
1098
- no_op = True
1099
- if hasattr(op, "output"):
1100
- try:
1101
- op.output = True
1102
- channel(_("Operation '{name}' enabled.").format(name=str(op)))
1103
- op.updated()
1104
- no_op = False
1105
- except AttributeError:
1106
- pass
1107
- if no_op:
1108
- channel(_("Operation '{name}' can't be enabled.").format(name=str(op)))
1109
- return "ops", data
1110
-
1111
- # ==========
1112
- # ELEMENT/OPERATION SUBCOMMANDS
1113
- # ==========
1114
- @self.console_command(
1115
- "lock",
1116
- help=_("Lock element (protect from manipulation)"),
1117
- input_type="elements",
1118
- output_type="elements",
1119
- )
1120
- def e_lock(data=None, **kwargs):
1121
- if data is None:
1122
- data = list(self.elems(emphasized=True))
1123
- for e in data:
1124
- e.lock = True
1125
- self.signal("element_property_update", data)
1126
- self.signal("refresh_scene", "Scene")
1127
- return "elements", data
1128
-
1129
- @self.console_command(
1130
- "unlock",
1131
- help=_("Unlock element (allow manipulation)"),
1132
- input_type="elements",
1133
- output_type="elements",
1134
- )
1135
- def e_unlock(data=None, **kwargs):
1136
- if data is None:
1137
- data = list(self.elems(emphasized=True))
1138
- for e in data:
1139
- if hasattr(e, "lock"):
1140
- e.lock = False
1141
- self.signal("element_property_update", data)
1142
- self.signal("refresh_scene", "Scene")
1143
- return "elements", data
1144
-
1145
- @self.console_option(
1146
- "dx", "x", help=_("copy offset x (for elems)"), type=Length, default=0
1147
- )
1148
- @self.console_option(
1149
- "dy", "y", help=_("copy offset y (for elems)"), type=Length, default=0
1150
- )
1151
- @self.console_command(
1152
- "copy",
1153
- help=_("Duplicate elements"),
1154
- input_type=("elements", "ops"),
1155
- output_type=("elements", "ops"),
1156
- )
1157
- def e_copy(data=None, data_type=None, post=None, dx=None, dy=None, **kwargs):
1158
- if data is None:
1159
- # Take tree selection for ops, scene selection for elements
1160
- if data_type == "ops":
1161
- data = list(self.ops(selected=True))
1162
- else:
1163
- data = list(self.elems(emphasized=True))
1164
-
1165
- if data_type == "ops":
1166
- add_elem = list(map(copy, data))
1167
- self.add_ops(add_elem)
1168
- return "ops", add_elem
1169
- else:
1170
- if dx is None:
1171
- x_pos = 0
1172
- else:
1173
- x_pos = dx
1174
- if dy is None:
1175
- y_pos = 0
1176
- else:
1177
- y_pos = dy
1178
- add_elem = list(map(copy, data))
1179
- matrix = None
1180
- if x_pos != 0 or y_pos != 0:
1181
- matrix = Matrix.translate(dx, dy)
1182
- delta_wordlist = 1
1183
- for e in add_elem:
1184
- if matrix:
1185
- e.matrix *= matrix
1186
- newnode = self.elem_branch.add_node(e)
1187
- if self.copy_increases_wordlist_references and hasattr(newnode, "text"):
1188
- newnode.text = self.wordlist_delta(newnode.text, delta_wordlist)
1189
- elif self.copy_increases_wordlist_references and hasattr(
1190
- newnode, "mktext"
1191
- ):
1192
- newnode.mktext = self.wordlist_delta(newnode.mktext, delta_wordlist)
1193
- for property_op in self.kernel.lookup_all("path_updater/.*"):
1194
- property_op(self.kernel.root, newnode)
1195
- # Newly created! Classification needed?
1196
- post.append(classify_new(add_elem))
1197
- self.signal("refresh_scene", "Scene")
1198
- return "elements", add_elem
1199
-
1200
- @self.console_command(
1201
- "delete", help=_("Delete elements"), input_type=("elements", "ops")
1202
- )
1203
- def e_delete(command, channel, _, data=None, data_type=None, **kwargs):
1204
- channel(_("Deleting…"))
1205
- with self.static("e_delete"):
1206
- if data_type == "elements":
1207
- self.remove_elements(data)
1208
- else:
1209
- self.remove_operations(data)
1210
-
1211
- # ==========
1212
- # ELEMENT BASE
1213
- # ==========
1214
-
1215
- @self.console_command(
1216
- "elements",
1217
- help=_("Show information about elements"),
1218
- )
1219
- def elements(**kwargs):
1220
- self(".element* list\n")
1221
-
1222
- @self.console_command(
1223
- "element*",
1224
- help=_("element*, all elements"),
1225
- output_type="elements",
1226
- )
1227
- def element_star(**kwargs):
1228
- return "elements", list(self.elems())
1229
-
1230
- @self.console_command(
1231
- "element~",
1232
- help=_("element~, all non-selected elements"),
1233
- output_type="elements",
1234
- )
1235
- def element_not(**kwargs):
1236
- return "elements", list(self.elems(emphasized=False))
1237
-
1238
- @self.console_command(
1239
- "element",
1240
- help=_("element, selected elements"),
1241
- output_type="elements",
1242
- )
1243
- def element_base(**kwargs):
1244
- return "elements", list(self.elems(emphasized=True))
1245
-
1246
- @self.console_command(
1247
- r"element([0-9]+,?)+",
1248
- help=_("element0,3,4,5: chain a list of specific elements"),
1249
- regex=True,
1250
- output_type="elements",
1251
- )
1252
- def element_chain(command, channel, _, **kwargs):
1253
- arg = command[7:]
1254
- elements_list = []
1255
- for value in arg.split(","):
1256
- try:
1257
- value = int(value)
1258
- except ValueError:
1259
- continue
1260
- try:
1261
- e = self.get_elem(value)
1262
- elements_list.append(e)
1263
- except IndexError:
1264
- channel(_("index {index} out of range").format(index=value))
1265
- return "elements", elements_list
1266
-
1267
- # ==========
1268
- # REGMARK COMMANDS
1269
- # ==========
1270
- def move_nodes_to(target, nodes):
1271
- for elem in nodes:
1272
- target.drop(elem)
1273
-
1274
- @self.console_argument("cmd", type=str, help=_("free, clear, add"))
1275
- @self.console_command(
1276
- "regmark",
1277
- help=_("regmark cmd"),
1278
- input_type=(None, "elements"),
1279
- output_type="elements",
1280
- all_arguments_required=True,
1281
- )
1282
- def regmark(command, channel, _, data, cmd=None, **kwargs):
1283
- # Move regmarks into the regular element tree and vice versa
1284
- with self.static("regmark"):
1285
- if cmd == "free":
1286
- target = self.elem_branch
1287
- else:
1288
- target = self.reg_branch
1289
-
1290
- if data is None:
1291
- data = list()
1292
- if cmd == "free":
1293
- for item in list(self.regmarks()):
1294
- data.append(item)
1295
- else:
1296
- for item in list(self.elems(emphasized=True)):
1297
- data.append(item)
1298
- if cmd in ("free", "add"):
1299
- if len(data) == 0:
1300
- channel(_("No elements to transfer"))
1301
- else:
1302
- move_nodes_to(target, data)
1303
- if cmd == "free" and self.classify_new:
1304
- self.classify(data)
1305
- elif cmd == "clear":
1306
- self.clear_regmarks()
1307
- data = None
1308
- else:
1309
- # Unknown command
1310
- channel(_("Invalid command, use one of add, free, clear"))
1311
- data = None
1312
- return "elements", data
1313
-
1314
- # ==========
1315
- # ELEMENT SUBCOMMANDS
1316
- # ==========
1317
-
1318
- # @self.console_argument("step_size", type=int, help=_("element step size"))
1319
- # @self.console_command(
1320
- # "step",
1321
- # help=_("step <element step-size>"),
1322
- # input_type="elements",
1323
- # output_type="elements",
1324
- # )
1325
- # def step_command(command, channel, _, data, step_size=None, **kwrgs):
1326
- # if step_size is None:
1327
- # found = False
1328
- # for element in data:
1329
- # if isinstance(element, SVGImage):
1330
- # try:
1331
- # step = element.values["raster_step"]
1332
- # except KeyError:
1333
- # step = 1
1334
- # channel(
1335
- # _("Image step for %s is currently: %s")
1336
- # % (str(element), step)
1337
- # )
1338
- # found = True
1339
- # if not found:
1340
- # channel(_("No image element selected."))
1341
- # return
1342
- # for element in data:
1343
- # element.values["raster_step"] = str(step_size)
1344
- # m = element.transform
1345
- # tx = m.e
1346
- # ty = m.f
1347
- # element.transform = Matrix.scale(float(step_size), float(step_size))
1348
- # element.transform.post_translate(tx, ty)
1349
- # if hasattr(element, "node"):
1350
- # element.node.modified()
1351
- # self.signal("element_property_reload", element)
1352
- # return ("elements",)
1353
-
1354
- @self.console_command(
1355
- "select",
1356
- help=_("Set these values as the selection."),
1357
- input_type="elements",
1358
- output_type="elements",
1359
- )
1360
- def element_select_base(data=None, **kwargs):
1361
- self.set_emphasis(data)
1362
- return "elements", data
1363
-
1364
- @self.console_command(
1365
- "select+",
1366
- help=_("Add the input to the selection"),
1367
- input_type="elements",
1368
- output_type="elements",
1369
- )
1370
- def element_select_plus(data=None, **kwargs):
1371
- elems = list(self.elems(emphasized=True))
1372
- elems.extend(data)
1373
- self.set_emphasis(elems)
1374
- return "elements", elems
1375
-
1376
- @self.console_command(
1377
- "select-",
1378
- help=_("Remove the input data from the selection"),
1379
- input_type="elements",
1380
- output_type="elements",
1381
- )
1382
- def element_select_minus(data=None, **kwargs):
1383
- elems = list(self.elems(emphasized=True))
1384
- for e in data:
1385
- try:
1386
- elems.remove(e)
1387
- except ValueError:
1388
- pass
1389
- self.set_emphasis(elems)
1390
- return "elements", elems
1391
-
1392
- @self.console_command(
1393
- "select^",
1394
- help=_("Toggle the input data in the selection"),
1395
- input_type="elements",
1396
- output_type="elements",
1397
- )
1398
- def element_select_xor(data=None, **kwargs):
1399
- elems = list(self.elems(emphasized=True))
1400
- for e in data:
1401
- try:
1402
- elems.remove(e)
1403
- except ValueError:
1404
- elems.append(e)
1405
- self.set_emphasis(elems)
1406
- return "elements", elems
1407
-
1408
- @self.console_command(
1409
- "list",
1410
- help=_("Show information about the chained data"),
1411
- input_type="elements",
1412
- output_type="elements",
1413
- )
1414
- def element_list(command, channel, _, data=None, **kwargs):
1415
- channel("----------")
1416
- channel(_("Graphical Elements:"))
1417
- index_list = list(self.elems())
1418
- for e in data:
1419
- i = index_list.index(e)
1420
- name = str(e)
1421
- if len(name) > 50:
1422
- name = name[:50] + "…"
1423
- if e.emphasized:
1424
- channel(f"{i}: * {name}")
1425
- else:
1426
- channel(f"{i}: {name}")
1427
- channel("----------")
1428
- return "elements", data
1429
-
1430
- @self.console_command(
1431
- "merge",
1432
- help=_("merge elements"),
1433
- input_type="elements",
1434
- output_type="elements",
1435
- )
1436
- def element_merge(data=None, post=None, **kwargs):
1437
- """
1438
- Merge combines the geometries of the inputs. This matters in some cases where fills are used. Such that two
1439
- nested circles forms a toroid rather two independent circles.
1440
- """
1441
- node = self.elem_branch.add(type="elem path")
1442
- for e in data:
1443
- try:
1444
- path = e.as_geometry()
1445
- except AttributeError:
1446
- continue
1447
- try:
1448
- if node.stroke is None:
1449
- node.stroke = e.stroke
1450
- except AttributeError:
1451
- pass
1452
- try:
1453
- if node.fill is None:
1454
- node.fill = e.fill
1455
- except AttributeError:
1456
- pass
1457
- try:
1458
- if node.stroke_width is None:
1459
- node.stroke_width = e.stroke_width
1460
- except AttributeError:
1461
- pass
1462
- node.geometry.append(path)
1463
- self.remove_elements(data)
1464
- self.set_node_emphasis(node, True)
1465
- # Newly created! Classification needed?
1466
- data = [node]
1467
- post.append(classify_new(data))
1468
- return "elements", data
1469
-
1470
- @self.console_command(
1471
- "subpath",
1472
- help=_("break elements"),
1473
- input_type="elements",
1474
- output_type="elements",
1475
- )
1476
- def element_subpath(data=None, post=None, **kwargs):
1477
- """
1478
- Subpath is the opposite of merge. It divides non-attached paths into different node objects.
1479
- """
1480
- if not isinstance(data, list):
1481
- data = list(data)
1482
- elements_nodes = []
1483
- elements = []
1484
- for node in data:
1485
- node_attributes = []
1486
- for attrib in ("stroke", "fill", "stroke_width", "stroke_scaled"):
1487
- if hasattr(node, attrib):
1488
- oldval = getattr(node, attrib, None)
1489
- node_attributes.append([attrib, oldval])
1490
- group_node = node.replace_node(type="group", label=node.label)
1491
-
1492
- try:
1493
- geometry = node.as_geometry()
1494
- geometry.ensure_proper_subpaths()
1495
- except AttributeError:
1496
- continue
1497
-
1498
- for subpath in geometry.as_subpaths():
1499
- subnode = group_node.add(geometry=subpath, type="elem path")
1500
- for item in node_attributes:
1501
- setattr(subnode, item[0], item[1])
1502
- elements.append(subnode)
1503
- elements_nodes.append(group_node)
1504
- post.append(classify_new(elements))
1505
- return "elements", elements_nodes
1506
-
1507
- # --------------------------- END COMMANDS ------------------------------
1
+ """
2
+ This module provides a set of console commands for managing branches and operations within the application.
3
+ These commands allow users to load files, manage operations, and manipulate elements in various ways.
4
+
5
+ Functions:
6
+ - plugin(kernel, lifecycle=None): Initializes the plugin and sets up branch commands.
7
+ - init_commands(kernel): Initializes the branch commands and defines the associated operations.
8
+ - load(channel, _, filename=None, **kwargs): Loads a file from the working directory and adds its contents to the application.
9
+ Args:
10
+ channel: The communication channel for messages.
11
+ filename: The name of the file to load.
12
+ Returns:
13
+ A tuple containing the type of the file and its path.
14
+ - element(command, **kwargs): Displays information about operations in the system.
15
+ Args:
16
+ command: The command context.
17
+ Returns:
18
+ None
19
+ - operation_select(**kwargs): Selects the currently emphasized operations.
20
+ Args:
21
+ command: The command context.
22
+ Returns:
23
+ A tuple containing the type of operations and the selected operations.
24
+ - operation_all(**kwargs): Selects all operations in the system.
25
+ Args:
26
+ command: The command context.
27
+ Returns:
28
+ A tuple containing the type of operations and all operations.
29
+ - operation_invert(**kwargs): Selects all non-emphasized operations.
30
+ Args:
31
+ command: The command context.
32
+ Returns:
33
+ A tuple containing the type of operations and the non-selected operations.
34
+ - operation_base(**kwargs): Selects the currently emphasized operations.
35
+ Args:
36
+ command: The command context.
37
+ Returns:
38
+ A tuple containing the type of operations and the emphasized operations.
39
+ - operation_re(command, channel, _, **kwargs): Selects operations based on specified indices.
40
+ Args:
41
+ command: The command context.
42
+ channel: The communication channel for messages.
43
+ Returns:
44
+ A tuple containing the type of operations and the selected operations.
45
+ - operation_select_emphasis(data=None, **kwargs): Sets the specified operations as the current selection.
46
+ Args:
47
+ data: The operations to select.
48
+ Returns:
49
+ A tuple containing the type of operations and the selected operations.
50
+ - operation_select_plus(data=None, **kwargs): Adds the specified operations to the current selection.
51
+ Args:
52
+ data: The operations to add.
53
+ Returns:
54
+ A tuple containing the type of operations and the updated selection.
55
+ - operation_select_minus(data=None, **kwargs): Removes the specified operations from the current selection.
56
+ Args:
57
+ data: The operations to remove.
58
+ Returns:
59
+ A tuple containing the type of operations and the updated selection.
60
+ - operation_select_xor(data=None, **kwargs): Toggles the specified operations in the current selection.
61
+ Args:
62
+ data: The operations to toggle.
63
+ Returns:
64
+ A tuple containing the type of operations and the updated selection.
65
+ - opelem_select_range(data=None, data_type=None, start=None, end=None, step=1, **kwargs): Subsets the current selection based on specified start, end, and step indices.
66
+ Args:
67
+ data: The elements to subset.
68
+ data_type: The type of data being processed.
69
+ start: The starting index for the subset.
70
+ end: The ending index for the subset.
71
+ step: The step size for the subset.
72
+ Returns:
73
+ A tuple containing the type of data and the subsetted elements.
74
+ - opelem_filter(channel=None, data=None, data_type=None, filter=None, **kwargs): Filters the current selection based on the provided filter string.
75
+ Args:
76
+ channel: The communication channel for messages.
77
+ data: The elements to filter.
78
+ data_type: The type of data being processed.
79
+ filter: The filter string to apply.
80
+ Returns:
81
+ A tuple containing the type of data and the filtered elements.
82
+ - opelem_id(command, channel, _, id=None, data=None, data_type=None, **kwargs): Sets or retrieves the ID of the specified elements.
83
+ Args:
84
+ command: The command context.
85
+ channel: The communication channel for messages.
86
+ id: The new ID to set.
87
+ data: The elements to modify.
88
+ data_type: The type of data being processed.
89
+ Returns:
90
+ A tuple containing the type of data and the modified elements.
91
+ - opelem_label(command, channel, _, label=None, data=None, data_type=None, **kwargs): Sets or retrieves the label of the specified elements.
92
+ Args:
93
+ command: The command context.
94
+ channel: The communication channel for messages.
95
+ label: The new label to set.
96
+ data: The elements to modify.
97
+ data_type: The type of data being processed.
98
+ Returns:
99
+ A tuple containing the type of data and the modified elements.
100
+ - operation_empty(channel, _, data=None, data_type=None, **kwargs): Removes all elements from the specified operations.
101
+ Args:
102
+ channel: The communication channel for messages.
103
+ data: The operations to clear.
104
+ data_type: The type of data being processed.
105
+ Returns:
106
+ A tuple containing the type of data and the cleared operations.
107
+ - operation_list(command, channel, _, data=None, **kwargs): Lists information about the specified operations.
108
+ Args:
109
+ command: The command context.
110
+ channel: The communication channel for messages.
111
+ data: The operations to list.
112
+ Returns:
113
+ A tuple containing the type of data and the listed operations.
114
+ - element_lock(data=None, **kwargs): Locks the specified elements to prevent manipulation.
115
+ Args:
116
+ data: The elements to lock.
117
+ Returns:
118
+ A tuple containing the type of data and the locked elements.
119
+ - element_unlock(data=None, **kwargs): Unlocks the specified elements to allow manipulation.
120
+ Args:
121
+ data: The elements to unlock.
122
+ Returns:
123
+ A tuple containing the type of data and the unlocked elements.
124
+ - e_copy(data=None, data_type=None, post=None, dx=None, dy=None, copies=None, **kwargs): Duplicates the specified elements a given number of times with optional offsets.
125
+ Args:
126
+ data: The elements to copy.
127
+ data_type: The type of data being processed.
128
+ post: Additional processing information.
129
+ dx: The x-offset for the copies.
130
+ dy: The y-offset for the copies.
131
+ copies: The number of copies to create.
132
+ Returns:
133
+ A tuple containing the type of data and the copied elements.
134
+ - e_delete(command, channel, _, data=None, data_type=None, **kwargs): Deletes the specified elements or operations.
135
+ Args:
136
+ command: The command context.
137
+ channel: The communication channel for messages.
138
+ data: The elements or operations to delete.
139
+ data_type: The type of data being processed.
140
+ Returns:
141
+ A tuple containing the type of data and the deleted elements.
142
+ """
143
+
144
+ import re
145
+ from copy import copy
146
+
147
+ from meerk40t.core.node.effect_hatch import HatchEffectNode
148
+ from meerk40t.core.node.op_cut import CutOpNode
149
+ from meerk40t.core.node.op_dots import DotsOpNode
150
+ from meerk40t.core.node.op_engrave import EngraveOpNode
151
+ from meerk40t.core.node.op_image import ImageOpNode
152
+ from meerk40t.core.node.op_raster import RasterOpNode
153
+ from meerk40t.core.units import Angle, Length
154
+ from meerk40t.kernel import CommandSyntaxError
155
+ from meerk40t.svgelements import Color, Matrix
156
+ from meerk40t.core.elements.element_types import op_nodes
157
+ from meerk40t.tools.geomstr import NON_GEOMETRY_TYPES
158
+ def plugin(kernel, lifecycle=None):
159
+ _ = kernel.translation
160
+ if lifecycle == "postboot":
161
+ init_commands(kernel)
162
+
163
+
164
+ def init_commands(kernel):
165
+ self = kernel.elements
166
+
167
+ _ = kernel.translation
168
+
169
+ classify_new = self.post_classify
170
+
171
+ @self.console_argument("filename")
172
+ @self.console_command(
173
+ "load",
174
+ help=_("loads file from working directory"),
175
+ input_type=None,
176
+ output_type="file",
177
+ )
178
+ def load(channel, _, filename=None, **kwargs):
179
+ import os
180
+
181
+ if filename is None:
182
+ channel(_("No file specified."))
183
+ return
184
+ new_file = os.path.join(self.kernel.current_directory, filename)
185
+ if not os.path.exists(new_file):
186
+ channel(_("No such file."))
187
+ return
188
+ try:
189
+ channel(_("loading..."))
190
+ result = self.load(new_file)
191
+ if result:
192
+ channel(_("Done."))
193
+ except AttributeError:
194
+ raise CommandSyntaxError(_("Loading files was not defined"))
195
+ return "file", new_file
196
+
197
+ # ==========
198
+ # OPERATION BASE
199
+ # ==========
200
+
201
+ @self.console_command("operations", help=_("Show information about operations"))
202
+ def element(**kwargs):
203
+ self(".operation* list\n")
204
+
205
+ @self.console_command(
206
+ "operation.*", help=_("operation.*: selected operations"), output_type="ops"
207
+ )
208
+ def operation_select(**kwargs):
209
+ return "ops", list(self.ops(emphasized=True))
210
+
211
+ @self.console_command(
212
+ "operation*", help=_("operation*: all operations"), output_type="ops"
213
+ )
214
+ def operation_all(**kwargs):
215
+ return "ops", list(self.ops())
216
+
217
+ @self.console_command(
218
+ "operation~",
219
+ help=_("operation~: non selected operations."),
220
+ output_type="ops",
221
+ )
222
+ def operation_invert(**kwargs):
223
+ return "ops", list(self.ops(emphasized=False))
224
+
225
+ @self.console_command(
226
+ "operation", help=_("operation: selected operations."), output_type="ops"
227
+ )
228
+ def operation_base(**kwargs):
229
+ return "ops", list(self.ops(emphasized=True))
230
+
231
+ @self.console_command(
232
+ r"operation([0-9]+,?)+",
233
+ help=_("operation0,2: operation #0 and #2"),
234
+ regex=True,
235
+ output_type="ops",
236
+ )
237
+ def operation_re(command, channel, _, **kwargs):
238
+ arg = command[9:]
239
+ op_values = []
240
+ for value in arg.split(","):
241
+ try:
242
+ value = int(value)
243
+ except ValueError:
244
+ continue
245
+ try:
246
+ op = self.get_op(value)
247
+ op_values.append(op)
248
+ except IndexError:
249
+ channel(_("index {index} out of range").format(index=value))
250
+ return "ops", op_values
251
+
252
+ @self.console_command(
253
+ "select",
254
+ help=_("Set these values as the selection."),
255
+ input_type="ops",
256
+ output_type="ops",
257
+ )
258
+ def operation_select_emphasis(data=None, **kwargs):
259
+ self.set_emphasis(data)
260
+ return "ops", data
261
+
262
+ @self.console_command(
263
+ "select+",
264
+ help=_("Add the input to the selection"),
265
+ input_type="ops",
266
+ output_type="ops",
267
+ )
268
+ def operation_select_plus(data=None, **kwargs):
269
+ ops = list(self.ops(emphasized=True))
270
+ ops.extend(data)
271
+ self.set_emphasis(ops)
272
+ return "ops", ops
273
+
274
+ @self.console_command(
275
+ "select-",
276
+ help=_("Remove the input data from the selection"),
277
+ input_type="ops",
278
+ output_type="ops",
279
+ )
280
+ def operation_select_minus(data=None, **kwargs):
281
+ ops = list(self.ops(emphasized=True))
282
+ for e in data:
283
+ try:
284
+ ops.remove(e)
285
+ except ValueError:
286
+ pass
287
+ self.set_emphasis(ops)
288
+ return "ops", ops
289
+
290
+ @self.console_command(
291
+ "select^",
292
+ help=_("Toggle the input data in the selection"),
293
+ input_type="ops",
294
+ output_type="ops",
295
+ )
296
+ def operation_select_xor(data=None, **kwargs):
297
+ ops = list(self.ops(emphasized=True))
298
+ for e in data:
299
+ try:
300
+ ops.remove(e)
301
+ except ValueError:
302
+ ops.append(e)
303
+ self.set_emphasis(ops)
304
+ return "ops", ops
305
+
306
+ @self.console_argument("start", type=int, help=_("start"))
307
+ @self.console_argument("end", type=int, help=_("end"))
308
+ @self.console_option("step", "s", type=int, default=1, help=_("step"))
309
+ @self.console_command(
310
+ "range",
311
+ help=_("Subset existing selection by begin and end indices and step"),
312
+ input_type=("ops", "elements"),
313
+ output_type=("ops", "elements"),
314
+ )
315
+ def opelem_select_range(
316
+ data=None, data_type=None, start=None, end=None, step=1, **kwargs
317
+ ):
318
+ sublist = list()
319
+ for e in range(start, end, step):
320
+ try:
321
+ sublist.append(data[e])
322
+ except IndexError:
323
+ pass
324
+ self.set_emphasis(sublist)
325
+ return data_type, sublist
326
+
327
+ @self.console_argument("filter", type=str, help=_("Filter to apply"))
328
+ @self.console_command(
329
+ "filter",
330
+ help=_("Filter data by given value"),
331
+ input_type=("ops", "elements"),
332
+ output_type=("ops", "elements"),
333
+ )
334
+ def opelem_filter(channel=None, data=None, data_type=None, filter=None, **kwargs):
335
+ """
336
+ Apply a filter string to a filter particular operations from the current data.
337
+ Operations or elements are evaluated in an infix prioritized stack format without spaces.
338
+ Qualified values for all node types are: id, label, len, type
339
+ Qualified element values are stroke, fill, dpi, elem
340
+ Qualified operation values are speed, power, frequency, dpi, acceleration, op, passes, color, overscan
341
+ Valid operators are >, >=, <, <=, =, ==, +, -, *, /, &, &&, |, and ||
342
+ Valid string operators are startswith, endswith, contains.
343
+ String values require single-quotes ', because the console interface requires double-quotes.
344
+ e.g. filter speed>=10, filter speed=5+5, filter speed>power/10, filter speed==2*4+2
345
+ e.g. filter engrave=op&speed=35|cut=op&speed=10
346
+ e.g. filter len=0
347
+ e.g. operation* filter "type='op image'" list
348
+ e.g. element* filter "id startswith 'p'" list
349
+ """
350
+ sublist = list()
351
+ _filter_parse = [
352
+ ("STR", r"'([^']*)'"),
353
+ ("SKIP", r"[ ,\t\n\x09\x0A\x0C\x0D]+"),
354
+ ("OP20", r"(\*|/)"),
355
+ ("OP15", r"(\+|-)"),
356
+ ("OP11", r"(<=|>=|==|!=|startswith|endswith|contains)"),
357
+ ("OP10", r"(<|>|=)"),
358
+ ("OP5", r"(&&)"),
359
+ ("OP4", r"(&)"),
360
+ ("OP3", r"(\|\|)"),
361
+ ("OP2", r"(\|)"),
362
+ ("NUM", r"([-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"),
363
+ (
364
+ "COLOR",
365
+ r"(#[0123456789abcdefABCDEF]{6}|#[0123456789abcdefABCDEF]{3})",
366
+ ),
367
+ (
368
+ "TYPE",
369
+ r"(raster|image|cut|engrave|dots|blob|rect|path|ellipse|point|image|line|polyline)",
370
+ ),
371
+ (
372
+ "VAL",
373
+ r"(type|op|speed|power|frequency|dpi|passes|color|overscan|len|elem|stroke|fill|id|label)",
374
+ ),
375
+ ]
376
+ filter_re = re.compile("|".join("(?P<%s>%s)" % pair for pair in _filter_parse))
377
+ operator = list()
378
+ operand = list()
379
+
380
+ def filter_parser(text: str):
381
+ p = 0
382
+ limit = len(text)
383
+ while p < limit:
384
+ match = filter_re.match(text, p)
385
+ if match is None:
386
+ break # No more matches.
387
+ _kind = match.lastgroup
388
+ _start = p
389
+ p = match.end()
390
+ if _kind == "SKIP":
391
+ continue
392
+ _value = match.group()
393
+ yield _kind, _value, _start, p
394
+
395
+ def solve_to(order: int):
396
+ try:
397
+ while len(operator) and operator[0][0] >= order:
398
+ _p, op = operator.pop()
399
+ v2 = operand.pop()
400
+ v1 = operand.pop()
401
+ try:
402
+ if op == "==" or op == "=":
403
+ operand.append(v1 == v2)
404
+ elif op == "!=":
405
+ operand.append(v1 != v2)
406
+ elif op == ">":
407
+ operand.append(v1 > v2)
408
+ elif op == "<":
409
+ operand.append(v1 < v2)
410
+ elif op == "<=":
411
+ operand.append(v1 <= v2)
412
+ elif op == ">=":
413
+ operand.append(v1 >= v2)
414
+ elif op == "&&" or op == "&":
415
+ operand.append(v1 and v2)
416
+ elif op == "||" or op == "|":
417
+ operand.append(v1 or v2)
418
+ elif op == "*":
419
+ operand.append(v1 * v2)
420
+ elif op == "/":
421
+ operand.append(v1 / v2)
422
+ elif op == "+":
423
+ operand.append(v1 + v2)
424
+ elif op == "-":
425
+ operand.append(v1 - v2)
426
+ elif op == "startswith":
427
+ operand.append(str(v1).startswith(str(v2)))
428
+ elif op == "endswith":
429
+ operand.append(str(v1).endswith(str(v2)))
430
+ elif op == "contains":
431
+ operand.append(str(v2) in (str(v1)))
432
+ except TypeError:
433
+ raise CommandSyntaxError("Cannot evaluate expression")
434
+ except ZeroDivisionError:
435
+ operand.append(float("inf"))
436
+ except IndexError:
437
+ pass
438
+
439
+ for e in data:
440
+ for kind, value, start, pos in filter_parser(filter):
441
+ if kind == "COLOR":
442
+ operand.append(Color(value))
443
+ elif kind == "VAL":
444
+ try:
445
+ if value == "type":
446
+ operand.append(e.type)
447
+ elif value == "op":
448
+ if e.type.startswith("op"):
449
+ operand.append(e.type.replace("op", "").strip())
450
+ else:
451
+ operand.append(None)
452
+ elif value == "speed":
453
+ operand.append(e.speed)
454
+ elif value == "power":
455
+ operand.append(e.power)
456
+ elif value == "frequency":
457
+ operand.append(e.frequency)
458
+ elif value == "dpi":
459
+ operand.append(e.dpi)
460
+ elif value == "passes":
461
+ operand.append(e.passes)
462
+ elif value == "color":
463
+ operand.append(e.color)
464
+ elif value == "len":
465
+ try:
466
+ operand.append(len(e.children))
467
+ except AttributeError:
468
+ operand.append(0)
469
+ elif value == "elem":
470
+ if e.type.startswith("elem"):
471
+ operand.append(e.type.replace("elem", "").strip())
472
+ else:
473
+ operand.append(None)
474
+ elif value == "stroke":
475
+ operand.append(e.stroke)
476
+ elif value == "fill":
477
+ operand.append(e.fill)
478
+ elif value == "stroke_width":
479
+ operand.append(e.stroke_width)
480
+ elif value == "id":
481
+ operand.append(e.id)
482
+ elif value == "label":
483
+ operand.append(e.label)
484
+ else:
485
+ operand.append(e.settings.get(value))
486
+ except AttributeError:
487
+ operand.append(None)
488
+ elif kind == "NUM":
489
+ operand.append(float(value))
490
+ elif kind == "TYPE":
491
+ operand.append(value)
492
+ elif kind == "STR":
493
+ operand.append(value[1:-1])
494
+ elif kind.startswith("OP"):
495
+ precedence = int(kind[2:])
496
+ solve_to(precedence)
497
+ operator.append((precedence, value))
498
+ solve_to(0)
499
+ if len(operand) == 1:
500
+ if operand.pop():
501
+ sublist.append(e)
502
+ else:
503
+ raise CommandSyntaxError(_("Filter parse failed"))
504
+
505
+ self.set_emphasis(sublist)
506
+ return data_type, sublist
507
+
508
+ @self.console_argument(
509
+ "id",
510
+ type=str,
511
+ help=_("new id to set values to"),
512
+ )
513
+ @self.console_command(
514
+ "id",
515
+ help=_("id <id>"),
516
+ input_type=("ops", "elements"),
517
+ output_type=("elements", "ops"),
518
+ )
519
+ def opelem_id(command, channel, _, id=None, data=None, data_type=None, **kwargs):
520
+ if id is None:
521
+ # Display data about id.
522
+ channel("----------")
523
+ channel(_("ID Values:"))
524
+ for i, e in enumerate(data):
525
+ name = str(e)
526
+ channel(
527
+ _("{index}: {name} - id = {id}").format(index=i, name=name, id=e.id)
528
+ )
529
+ channel("----------")
530
+ return
531
+
532
+ if len(data) == 0:
533
+ channel(_("No selected nodes"))
534
+ return
535
+ for e in data:
536
+ e.id = id
537
+ self.validate_ids()
538
+ self.signal("element_property_update", data)
539
+ self.signal("refresh_scene", "Scene")
540
+ return data_type, data
541
+
542
+ @self.console_argument(
543
+ "label",
544
+ type=str,
545
+ help=_("new label to set values to"),
546
+ )
547
+ @self.console_command(
548
+ "label",
549
+ help=_("label <label>"),
550
+ input_type=("ops", "elements"),
551
+ output_type=("elements", "ops"),
552
+ )
553
+ def opelem_label(
554
+ command, channel, _, label=None, data=None, data_type=None, **kwargs
555
+ ):
556
+ if label is None:
557
+ # Display data about id.
558
+ channel("----------")
559
+ channel(_("Label Values:"))
560
+ for i, e in enumerate(data):
561
+ name = str(e)
562
+ channel(
563
+ _("{index}: {name} - label = {label}").format(
564
+ index=i, name=name, label=e.label
565
+ )
566
+ )
567
+ channel("----------")
568
+ return
569
+
570
+ if len(data) == 0:
571
+ channel(_("No selected nodes"))
572
+ return
573
+ for e in data:
574
+ e.label = label
575
+ self.signal("element_property_update", data)
576
+ self.signal("refresh_scene", "Scene")
577
+ return data_type, data
578
+
579
+
580
+ @self.console_command(
581
+ "empty",
582
+ help=_("Remove all elements from provided operations"),
583
+ input_type="ops",
584
+ output_type="ops",
585
+ )
586
+ def operation_empty(channel, _, data=None, **kwargs):
587
+ if data is None:
588
+ data = list()
589
+ for item in list(self.flat(selected=True, cascade=False, types=op_nodes)):
590
+ data.append(item)
591
+ # _("Clear operations")
592
+ with self.undoscope("Clear operations"):
593
+ index_ops = list(self.ops())
594
+ for item in data:
595
+ i = index_ops.index(item)
596
+ select_piece = "*" if item.emphasized else " "
597
+ name = f"{select_piece} {i}: {str(item)}"
598
+ channel(f"{name}: {len(item.children)}")
599
+ item.remove_all_children()
600
+ self.signal("rebuild_tree", "operations")
601
+
602
+ @self.console_command(
603
+ "list",
604
+ help=_("Show information about the chained data"),
605
+ input_type="ops",
606
+ output_type="ops",
607
+ )
608
+ def operation_list(channel, _, data=None, **kwargs):
609
+ channel("----------")
610
+ channel(_("Operations:"))
611
+ index_ops = list(self.ops())
612
+ for op_obj in data:
613
+ i = index_ops.index(op_obj)
614
+ select_piece = "*" if op_obj.emphasized else " "
615
+ name = f"{select_piece} {i}: {str(op_obj)}"
616
+ channel(name)
617
+ if isinstance(op_obj, list):
618
+ for q, oe in enumerate(op_obj):
619
+ stroke_piece = (
620
+ "None"
621
+ if (not hasattr(oe, "stroke") or oe.stroke) is None
622
+ else oe.stroke.hex
623
+ )
624
+ fill_piece = (
625
+ "None"
626
+ if (not hasattr(oe, "stroke") or oe.fill) is None
627
+ else oe.fill.hex
628
+ )
629
+ ident_piece = str(oe.id)
630
+ name = f"{''.ljust(5)}{q}: {str(type(oe).__name__)}-{ident_piece} s:{stroke_piece} f:{fill_piece}"
631
+ channel(name)
632
+ channel("----------")
633
+
634
+ @self.console_option("color", "c", type=Color)
635
+ @self.console_option("default", "D", type=bool)
636
+ @self.console_option("speed", "s", type=float)
637
+ @self.console_option("power", "p", type=float)
638
+ @self.console_option("dpi", "d", type=int)
639
+ @self.console_option("overscan", "o", type=self.length)
640
+ @self.console_option("passes", "x", type=int)
641
+ @self.console_option(
642
+ "parallel",
643
+ "P",
644
+ type=bool,
645
+ help=_("Creates a new operation for each element given"),
646
+ action="store_true",
647
+ )
648
+ @self.console_option(
649
+ "stroke",
650
+ "K",
651
+ type=bool,
652
+ action="store_true",
653
+ help=_(
654
+ "Set the operation color based on the stroke if the first stroked item added to this operation"
655
+ ),
656
+ )
657
+ @self.console_option(
658
+ "fill",
659
+ "F",
660
+ type=bool,
661
+ action="store_true",
662
+ help=_(
663
+ "Set the operation color based on the fill if the first filled item added to this operation"
664
+ ),
665
+ )
666
+ @self.console_command(
667
+ ("cut", "engrave", "raster", "imageop", "dots", "hatch"),
668
+ help=_(
669
+ "<cut/engrave/raster/imageop/dots/hatch> - group the elements into this operation"
670
+ ),
671
+ input_type=(None, "elements"),
672
+ output_type="ops",
673
+ )
674
+ def makeop(
675
+ command,
676
+ data=None,
677
+ color=None,
678
+ default=None,
679
+ speed=None,
680
+ power=None,
681
+ dpi=None,
682
+ overscan=None,
683
+ passes=None,
684
+ parallel=False,
685
+ stroke=False,
686
+ fill=False,
687
+ **kwargs,
688
+ ):
689
+ op_list = []
690
+
691
+ def make_op():
692
+ if command == "cut":
693
+ return CutOpNode()
694
+ elif command == "engrave":
695
+ return EngraveOpNode()
696
+ elif command == "raster":
697
+ return RasterOpNode()
698
+ elif command == "imageop":
699
+ return ImageOpNode()
700
+ elif command == "dots":
701
+ return DotsOpNode()
702
+ elif command == "hatch":
703
+ parent_node = EngraveOpNode()
704
+ parent_node.add_node(HatchEffectNode())
705
+ return parent_node
706
+ else:
707
+ raise ValueError
708
+
709
+ if parallel:
710
+ if data is None:
711
+ return "op", []
712
+ for item in data:
713
+ op = make_op()
714
+ if color is not None:
715
+ op.color = color
716
+ elif fill:
717
+ try:
718
+ op.color = item.fill
719
+ except AttributeError:
720
+ continue
721
+ elif stroke:
722
+ try:
723
+ op.color = item.stroke
724
+ except AttributeError:
725
+ continue
726
+ if default is not None:
727
+ op.default = default
728
+ if speed is not None:
729
+ op.speed = speed
730
+ if power is not None:
731
+ op.power = power
732
+ if passes is not None:
733
+ op.passes_custom = True
734
+ op.passes = passes
735
+ if dpi is not None:
736
+ op.dpi = dpi
737
+ if overscan is not None:
738
+ op.overscan = overscan
739
+ self.add_op(op)
740
+ op.add_reference(item)
741
+ op_list.append(op)
742
+ else:
743
+ op = make_op()
744
+ if color is not None:
745
+ op.color = color
746
+ elif fill:
747
+ try:
748
+ op.color = data[0].fill
749
+ except (AttributeError, IndexError):
750
+ pass
751
+ elif stroke:
752
+ try:
753
+ op.color = data[0].stroke
754
+ except (AttributeError, IndexError):
755
+ pass
756
+ if default is not None:
757
+ op.default = default
758
+ if speed is not None:
759
+ op.speed = speed
760
+ if power is not None:
761
+ op.power = power
762
+ if passes is not None:
763
+ op.passes_custom = True
764
+ op.passes = passes
765
+ if dpi is not None:
766
+ op.dpi = dpi
767
+ if overscan is not None:
768
+ op.overscan = overscan
769
+ self.add_op(op)
770
+ if data is not None:
771
+ for item in data:
772
+ op.add_reference(item)
773
+ op_list.append(op)
774
+ return "ops", op_list
775
+
776
+ @self.console_argument(
777
+ "time",
778
+ type=float,
779
+ default=5,
780
+ help=_("Time for the given wait operation."),
781
+ )
782
+ @self.console_command(
783
+ "waitop",
784
+ help=_("<waitop> - Create new utility operation"),
785
+ input_type=None,
786
+ output_type="ops",
787
+ )
788
+ def waitop(
789
+ command,
790
+ time=None,
791
+ **kwargs,
792
+ ):
793
+ op = self.op_branch.add(type="util wait", wait=time)
794
+ return "ops", [op]
795
+
796
+ @self.console_argument(
797
+ "mask",
798
+ type=int,
799
+ default=0,
800
+ help=_("binary input/output mask"),
801
+ )
802
+ @self.console_argument(
803
+ "value",
804
+ type=int,
805
+ default=0,
806
+ help=_("binary input/output value"),
807
+ )
808
+ @self.console_command(
809
+ ("outputop", "inputop"),
810
+ help=_("<outputop, inputop> - Create new utility operation"),
811
+ input_type=None,
812
+ output_type="ops",
813
+ )
814
+ def io_op(
815
+ command,
816
+ mask=None,
817
+ value=None,
818
+ **kwargs,
819
+ ):
820
+ if command == "inputop":
821
+ op = self.op_branch.add(
822
+ type="util input", input_mask=mask, input_value=value
823
+ )
824
+ else:
825
+ op = self.op_branch.add(
826
+ type="util output", output_mask=mask, output_value=value
827
+ )
828
+ return "ops", [op]
829
+
830
+ @self.console_argument(
831
+ "x",
832
+ type=Length,
833
+ default=0,
834
+ help=_("X-Coordinate of Goto?"),
835
+ )
836
+ @self.console_argument(
837
+ "y",
838
+ type=Length,
839
+ default=0,
840
+ help=_("Y-Coordinate of Goto?"),
841
+ )
842
+ @self.console_command(
843
+ "gotoop",
844
+ help=_("<gotoop> <x> <y> - Create new utility operation"),
845
+ input_type=None,
846
+ output_type="ops",
847
+ )
848
+ def gotoop(
849
+ command,
850
+ x=0,
851
+ y=0,
852
+ **kwargs,
853
+ ):
854
+ op = self.op_branch.add(type="util goto", x=str(x), y=str(y))
855
+ return "ops", [op]
856
+
857
+ @self.console_command(
858
+ "consoleop",
859
+ help=_("<consoleop> - Create new utility operation"),
860
+ )
861
+ def consoleop(
862
+ command,
863
+ remainder=None,
864
+ **kwargs,
865
+ ):
866
+ if remainder is not None:
867
+ op = self.op_branch.add(type="util console", command=remainder)
868
+ return "ops", [op]
869
+
870
+ @self.console_argument("dpi", type=int, help=_("raster dpi"))
871
+ @self.console_command("dpi", help=_("dpi <raster-dpi>"), input_type="ops")
872
+ def op_dpi(command, channel, _, data, dpi=None, **kwrgs):
873
+ if dpi is None:
874
+ found = False
875
+ for op in data:
876
+ if op.type in ("op raster", "op image"):
877
+ dpi = op.dpi
878
+ channel(
879
+ _("Step for {name} is currently: {dpi}").format(
880
+ name=str(op), dpi=dpi
881
+ )
882
+ )
883
+ found = True
884
+ if not found:
885
+ channel(_("No raster operations selected."))
886
+ return
887
+ for op in data:
888
+ if op.type in ("op raster", "op image"):
889
+ op.dpi = dpi
890
+ op.updated()
891
+ return "ops", data
892
+
893
+ @self.console_option(
894
+ "difference",
895
+ "d",
896
+ type=bool,
897
+ action="store_true",
898
+ help=_("Change speed by this amount."),
899
+ )
900
+ @self.console_option(
901
+ "progress",
902
+ "p",
903
+ type=bool,
904
+ action="store_true",
905
+ help=_("Change speed for each item in order"),
906
+ )
907
+ @self.console_argument("speed", type=str, help=_("operation speed in mm/s"))
908
+ @self.console_command(
909
+ "speed", help=_("speed <speed>"), input_type="ops", output_type="ops"
910
+ )
911
+ def op_speed(
912
+ command,
913
+ channel,
914
+ _,
915
+ speed=None,
916
+ difference=False,
917
+ progress=False,
918
+ data=None,
919
+ **kwrgs,
920
+ ):
921
+ if speed is None:
922
+ for op in data:
923
+ old = op.speed
924
+ channel(
925
+ _("Speed for '{name}' is currently: {speed}").format(
926
+ name=str(op), speed=old
927
+ )
928
+ )
929
+ return
930
+ if speed.endswith("%"):
931
+ speed = speed[:-1]
932
+ percent = True
933
+ else:
934
+ percent = False
935
+
936
+ try:
937
+ new_speed = float(speed)
938
+ except ValueError:
939
+ channel(_("Not a valid speed or percent."))
940
+ return
941
+ delta = 0
942
+ for op in data:
943
+ old = op.speed
944
+ if percent and difference:
945
+ s = old + old * (new_speed / 100.0)
946
+ elif difference:
947
+ s = old + new_speed
948
+ elif percent:
949
+ s = old * (new_speed / 100.0)
950
+ elif progress:
951
+ s = old + delta
952
+ delta += new_speed
953
+ else:
954
+ s = new_speed
955
+ if s < 0:
956
+ s = 0
957
+ op.speed = s
958
+ channel(
959
+ _("Speed for '{name}' updated {old_speed} -> {speed}").format(
960
+ name=str(op), old_speed=old, speed=s
961
+ )
962
+ )
963
+ op.updated()
964
+ return "ops", data
965
+
966
+ @self.console_argument(
967
+ "power", type=int, help=_("power in pulses per inch (ppi, 1000=max)")
968
+ )
969
+ @self.console_option(
970
+ "difference",
971
+ "d",
972
+ type=bool,
973
+ action="store_true",
974
+ help=_("Change power by this amount."),
975
+ )
976
+ @self.console_option(
977
+ "progress",
978
+ "p",
979
+ type=bool,
980
+ action="store_true",
981
+ help=_("Change power for each item in order"),
982
+ )
983
+ @self.console_command(
984
+ "power", help=_("power <ppi>"), input_type="ops", output_type="ops"
985
+ )
986
+ def op_power(
987
+ command,
988
+ channel,
989
+ _,
990
+ power=None,
991
+ difference=False,
992
+ progress=False,
993
+ data=None,
994
+ **kwrgs,
995
+ ):
996
+ if power is None:
997
+ for op in data:
998
+ old = op.power
999
+ channel(
1000
+ _("Power for '{name}' is currently: {power}").format(
1001
+ name=str(op), power=old
1002
+ )
1003
+ )
1004
+ return
1005
+ delta = 0
1006
+ for op in data:
1007
+ old = op.power
1008
+ if progress:
1009
+ s = old + delta
1010
+ delta += power
1011
+ elif difference:
1012
+ s = old + power
1013
+ else:
1014
+ s = power
1015
+ if s > 1000:
1016
+ s = 1000
1017
+ if s < 0:
1018
+ s = 0
1019
+ op.power = s
1020
+ channel(
1021
+ _("Power for '{name}' updated {old_power} -> {power}").format(
1022
+ name=str(op), old_power=old, power=s
1023
+ )
1024
+ )
1025
+ op.updated()
1026
+ return "ops", data
1027
+
1028
+ @self.console_argument(
1029
+ "frequency", type=float, help=_("frequency set for operation")
1030
+ )
1031
+ @self.console_option(
1032
+ "difference",
1033
+ "d",
1034
+ type=bool,
1035
+ action="store_true",
1036
+ help=_("Change speed by this amount."),
1037
+ )
1038
+ @self.console_option(
1039
+ "progress",
1040
+ "p",
1041
+ type=bool,
1042
+ action="store_true",
1043
+ help=_("Change speed for each item in order"),
1044
+ )
1045
+ @self.console_command(
1046
+ "frequency", help=_("frequency <kHz>"), input_type="ops", output_type="ops"
1047
+ )
1048
+ def op_frequency(
1049
+ command,
1050
+ channel,
1051
+ _,
1052
+ frequency=None,
1053
+ difference=False,
1054
+ progress=False,
1055
+ data=None,
1056
+ **kwrgs,
1057
+ ):
1058
+ if frequency is None:
1059
+ for op in data:
1060
+ old = op.frequency
1061
+ channel(
1062
+ _("Frequency for '{name}' is currently: {frequency}").format(
1063
+ name=str(op), frequency=old
1064
+ )
1065
+ )
1066
+ return
1067
+ delta = 0
1068
+ for op in data:
1069
+ old = op.frequency
1070
+ if progress:
1071
+ s = old + delta
1072
+ delta += frequency
1073
+ elif difference:
1074
+ s = old + frequency
1075
+ else:
1076
+ s = frequency
1077
+ if s < 0:
1078
+ s = 0
1079
+ op.frequency = s
1080
+ channel(
1081
+ _(
1082
+ "Frequency for '{name}' updated {old_frequency} -> {frequency}"
1083
+ ).format(name=str(op), old_frequency=old, frequency=s)
1084
+ )
1085
+ op.updated()
1086
+ return "ops", data
1087
+
1088
+ @self.console_argument("passes", type=int, help=_("Set operation passes"))
1089
+ @self.console_command(
1090
+ "passes", help=_("passes <passes>"), input_type="ops", output_type="ops"
1091
+ )
1092
+ def op_passes(command, channel, _, passes=None, data=None, **kwrgs):
1093
+ if passes is None:
1094
+ for op in data:
1095
+ old_passes = op.passes
1096
+ channel(
1097
+ _("Passes for '{name}' is currently: {passes}").format(
1098
+ name=str(op), passes=old_passes
1099
+ )
1100
+ )
1101
+ return
1102
+ for op in data:
1103
+ old_passes = op.passes
1104
+ op.passes = passes
1105
+ if passes >= 1:
1106
+ op.passes_custom = True
1107
+ channel(
1108
+ _("Passes for '{name}' updated {old_passes} -> {passes}").format(
1109
+ name=str(op), old_passes=old_passes, passes=passes
1110
+ )
1111
+ )
1112
+ op.updated()
1113
+ return "ops", data
1114
+
1115
+ @self.console_argument(
1116
+ "distance", type=Length, help=_("Set hatch-distance of operations")
1117
+ )
1118
+ @self.console_option(
1119
+ "difference",
1120
+ "d",
1121
+ type=bool,
1122
+ action="store_true",
1123
+ help=_("Change hatch-distance by this amount."),
1124
+ )
1125
+ @self.console_option(
1126
+ "progress",
1127
+ "p",
1128
+ type=bool,
1129
+ action="store_true",
1130
+ help=_("Change hatch-distance for each item in order"),
1131
+ )
1132
+ @self.console_command(
1133
+ "hatch-distance",
1134
+ help=_("hatch-distance <distance>"),
1135
+ input_type="ops",
1136
+ output_type="ops",
1137
+ )
1138
+ def op_hatch_distance(
1139
+ command,
1140
+ channel,
1141
+ _,
1142
+ distance=None,
1143
+ difference=False,
1144
+ progress=False,
1145
+ data=None,
1146
+ **kwrgs,
1147
+ ):
1148
+ if distance is None:
1149
+ for op in data:
1150
+ old = op.hatch_distance
1151
+ channel(
1152
+ _("Hatch Distance for '{name}' is currently: {distance}").format(
1153
+ name=str(op), distance=old
1154
+ )
1155
+ )
1156
+ return
1157
+ delta = 0
1158
+ for op in data:
1159
+ old = Length(op.hatch_distance)
1160
+ if progress:
1161
+ s = float(old) + delta
1162
+ delta += float(distance)
1163
+ elif difference:
1164
+ s = float(old) + float(distance)
1165
+ else:
1166
+ s = float(distance)
1167
+ if s < 0:
1168
+ s = 0
1169
+ op.hatch_distance = Length(amount=s).length_mm
1170
+ channel(
1171
+ _(
1172
+ "Hatch Distance for '{name}' updated {old_distance} -> {distance}"
1173
+ ).format(name=str(op), old_distance=old, distance=op.hatch_distance)
1174
+ )
1175
+ op.updated()
1176
+ return "ops", data
1177
+
1178
+ @self.console_argument("angle", type=Angle, help=_("Set hatch-angle of operations"))
1179
+ @self.console_option(
1180
+ "difference",
1181
+ "d",
1182
+ type=bool,
1183
+ action="store_true",
1184
+ help=_("Change hatch-angle by this amount."),
1185
+ )
1186
+ @self.console_option(
1187
+ "progress",
1188
+ "p",
1189
+ type=bool,
1190
+ action="store_true",
1191
+ help=_("Change hatch-angle for each item in order"),
1192
+ )
1193
+ @self.console_command(
1194
+ "hatch-angle",
1195
+ help=_("hatch-angle <angle>"),
1196
+ input_type="ops",
1197
+ output_type="ops",
1198
+ )
1199
+ def op_hatch_angle(
1200
+ command,
1201
+ channel,
1202
+ _,
1203
+ angle=None,
1204
+ difference=False,
1205
+ progress=False,
1206
+ data=None,
1207
+ **kwrgs,
1208
+ ):
1209
+ if angle is None:
1210
+ for op in data:
1211
+ old = Angle(op.hatch_angle, digits=4).angle_turns
1212
+ old_hatch_angle_deg = Angle(op.hatch_angle, digits=4).angle_degrees
1213
+ channel(
1214
+ _(
1215
+ "Hatch Angle for '{name}' is currently: {angle} ({angle_degree})"
1216
+ ).format(name=str(op), angle=old, angle_degree=old_hatch_angle_deg)
1217
+ )
1218
+ return
1219
+ delta = 0
1220
+ for op in data:
1221
+ try:
1222
+ old = Angle(op.hatch_angle)
1223
+ except AttributeError:
1224
+ # Console-Op or other non-angled op.
1225
+ continue
1226
+ if progress:
1227
+ s = old + delta
1228
+ delta += angle
1229
+ elif difference:
1230
+ s = old + angle
1231
+ else:
1232
+ s = angle
1233
+ s = Angle.from_radians(float(s))
1234
+ op.hatch_angle = s.angle_turns
1235
+ new_hatch_angle_turn = s.angle_turns
1236
+ new_hatch_angle_deg = s.angle_degrees
1237
+
1238
+ channel(
1239
+ _(
1240
+ "Hatch Angle for '{name}' updated {old_angle} -> {angle} ({angle_degree})"
1241
+ ).format(
1242
+ name=str(op),
1243
+ old_angle=old.angle_turns,
1244
+ angle=new_hatch_angle_turn,
1245
+ angle_degree=new_hatch_angle_deg,
1246
+ )
1247
+ )
1248
+ op.updated()
1249
+ return "ops", data
1250
+
1251
+ @self.console_command(
1252
+ "disable",
1253
+ help=_("Disable the given operations"),
1254
+ input_type="ops",
1255
+ output_type="ops",
1256
+ )
1257
+ def op_disable(command, channel, _, data=None, **kwrgs):
1258
+ for op in data:
1259
+ no_op = True
1260
+ if hasattr(op, "output"):
1261
+ try:
1262
+ op.output = False
1263
+ channel(_("Operation '{name}' disabled.").format(name=str(op)))
1264
+ op.updated()
1265
+ no_op = False
1266
+ except AttributeError:
1267
+ pass
1268
+ if no_op:
1269
+ channel(_("Operation '{name}' can't be disabled.").format(name=str(op)))
1270
+ return "ops", data
1271
+
1272
+ @self.console_command(
1273
+ "enable",
1274
+ help=_("Enable the given operations"),
1275
+ input_type="ops",
1276
+ output_type="ops",
1277
+ )
1278
+ def op_enable(command, channel, _, data=None, **kwrgs):
1279
+ for op in data:
1280
+ no_op = True
1281
+ if hasattr(op, "output"):
1282
+ try:
1283
+ op.output = True
1284
+ channel(_("Operation '{name}' enabled.").format(name=str(op)))
1285
+ op.updated()
1286
+ no_op = False
1287
+ except AttributeError:
1288
+ pass
1289
+ if no_op:
1290
+ channel(_("Operation '{name}' can't be enabled.").format(name=str(op)))
1291
+ return "ops", data
1292
+
1293
+ # ==========
1294
+ # ELEMENT/OPERATION SUBCOMMANDS
1295
+ # ==========
1296
+ @self.console_command(
1297
+ "lock",
1298
+ help=_("Lock element (protect from manipulation)"),
1299
+ input_type="elements",
1300
+ output_type="elements",
1301
+ )
1302
+ def e_lock(data=None, **kwargs):
1303
+ if data is None:
1304
+ data = list(self.elems(emphasized=True))
1305
+ for e in data:
1306
+ e.lock = True
1307
+ self.signal("element_property_update", data)
1308
+ self.signal("refresh_scene", "Scene")
1309
+ return "elements", data
1310
+
1311
+ @self.console_command(
1312
+ "unlock",
1313
+ help=_("Unlock element (allow manipulation)"),
1314
+ input_type="elements",
1315
+ output_type="elements",
1316
+ )
1317
+ def e_unlock(data=None, **kwargs):
1318
+ if data is None:
1319
+ data = list(self.elems(emphasized=True))
1320
+ for e in data:
1321
+ if hasattr(e, "lock"):
1322
+ e.lock = False
1323
+ self.signal("element_property_update", data)
1324
+ self.signal("refresh_scene", "Scene")
1325
+ return "elements", data
1326
+
1327
+ @self.console_option(
1328
+ "dx", "x", help=_("copy offset x (for elems)"), type=Length, default=0
1329
+ )
1330
+ @self.console_option(
1331
+ "dy", "y", help=_("copy offset y (for elems)"), type=Length, default=0
1332
+ )
1333
+ @self.console_option(
1334
+ "copies", "c", help=_("amount of copies to be created"), type=int, default=1
1335
+ )
1336
+ @self.console_command(
1337
+ "copy",
1338
+ help=_("Duplicate elements"),
1339
+ input_type=("elements", "ops"),
1340
+ output_type=("elements", "ops"),
1341
+ )
1342
+ def e_copy(data=None, data_type=None, post=None, dx=None, dy=None, copies=None, **kwargs):
1343
+ if data is None:
1344
+ # Take tree selection for ops, scene selection for elements
1345
+ if data_type == "ops":
1346
+ data = list(self.ops(selected=True))
1347
+ else:
1348
+ data = list(self.elems(emphasized=True))
1349
+ if copies is None:
1350
+ copies = 1
1351
+ if copies < 1:
1352
+ copies = 1
1353
+
1354
+ if data_type == "ops":
1355
+ add_ops = list()
1356
+ for idx in range(copies):
1357
+ add_ops.extend(list(map(copy, data)))
1358
+ # print (f"Add ops contains now: {len(add_ops)} operations")
1359
+ self.add_ops(add_ops)
1360
+ return "ops", add_ops
1361
+ else:
1362
+ if dx is None:
1363
+ x_pos = 0
1364
+ else:
1365
+ x_pos = float(dx)
1366
+ if dy is None:
1367
+ y_pos = 0
1368
+ else:
1369
+ y_pos = float(dy)
1370
+ add_elem = list()
1371
+ shift = list()
1372
+ tx = 0
1373
+ ty = 0
1374
+ for idx in range(copies):
1375
+ tx += x_pos
1376
+ ty += y_pos
1377
+ this_shift = [(tx, ty)] * len(data)
1378
+ add_elem.extend(list(map(copy, data)))
1379
+ shift.extend(this_shift)
1380
+ # print (f"Add elem contains now: {len(add_elem)} elements")
1381
+ delta_wordlist = 1
1382
+ for e, delta in zip(add_elem, shift):
1383
+ tx, ty = delta
1384
+ if tx != 0 or ty != 0:
1385
+ matrix = Matrix.translate(tx, ty)
1386
+ e.matrix *= matrix
1387
+ newnode = self.elem_branch.add_node(e)
1388
+ if self.copy_increases_wordlist_references and hasattr(newnode, "text"):
1389
+ newnode.text = self.wordlist_delta(newnode.text, delta_wordlist)
1390
+ elif self.copy_increases_wordlist_references and hasattr(
1391
+ newnode, "mktext"
1392
+ ):
1393
+ newnode.mktext = self.wordlist_delta(newnode.mktext, delta_wordlist)
1394
+ for property_op in self.kernel.lookup_all("path_updater/.*"):
1395
+ property_op(self.kernel.root, newnode)
1396
+ # Newly created! Classification needed?
1397
+ post.append(classify_new(add_elem))
1398
+ self.signal("refresh_scene", "Scene")
1399
+ return "elements", add_elem
1400
+
1401
+ @self.console_command(
1402
+ "delete", help=_("Delete elements"), input_type=("elements", "ops")
1403
+ )
1404
+ def e_delete(command, channel, _, data=None, data_type=None, **kwargs):
1405
+ channel(_("Deleting…"))
1406
+ with self.undoscope("Deleting"):
1407
+ if data_type == "elements":
1408
+ self.remove_elements(data)
1409
+ else:
1410
+ self.remove_operations(data)
1411
+ self.signal("update_group_labels")
1412
+
1413
+ # ==========
1414
+ # ELEMENT BASE
1415
+ # ==========
1416
+
1417
+ @self.console_command(
1418
+ "elements",
1419
+ help=_("Show information about elements"),
1420
+ )
1421
+ def elements(**kwargs):
1422
+ self(".element* list\n")
1423
+
1424
+ @self.console_command(
1425
+ "element*",
1426
+ help=_("element*, all elements"),
1427
+ output_type="elements",
1428
+ )
1429
+ def element_star(**kwargs):
1430
+ return "elements", list(self.elems())
1431
+
1432
+ @self.console_command(
1433
+ "element~",
1434
+ help=_("element~, all non-selected elements"),
1435
+ output_type="elements",
1436
+ )
1437
+ def element_not(**kwargs):
1438
+ return "elements", list(self.elems(emphasized=False))
1439
+
1440
+ @self.console_command(
1441
+ "element",
1442
+ help=_("element, selected elements"),
1443
+ output_type="elements",
1444
+ )
1445
+ def element_base(**kwargs):
1446
+ return "elements", list(self.elems(emphasized=True))
1447
+
1448
+ @self.console_command(
1449
+ r"element([0-9]+,?)+",
1450
+ help=_("element0,3,4,5: chain a list of specific elements"),
1451
+ regex=True,
1452
+ output_type="elements",
1453
+ )
1454
+ def element_chain(command, channel, _, **kwargs):
1455
+ arg = command[7:]
1456
+ elements_list = []
1457
+ for value in arg.split(","):
1458
+ try:
1459
+ value = int(value)
1460
+ except ValueError:
1461
+ continue
1462
+ try:
1463
+ e = self.get_elem(value)
1464
+ elements_list.append(e)
1465
+ except IndexError:
1466
+ channel(_("index {index} out of range").format(index=value))
1467
+ return "elements", elements_list
1468
+
1469
+ # ==========
1470
+ # REGMARK COMMANDS
1471
+ # ==========
1472
+ def move_nodes_to(target, nodes):
1473
+ for elem in nodes:
1474
+ target.drop(elem)
1475
+
1476
+ @self.console_argument("cmd", type=str, help=_("free, clear, add"))
1477
+ @self.console_command(
1478
+ "regmark",
1479
+ help=_("regmark cmd"),
1480
+ input_type=(None, "elements"),
1481
+ output_type="elements",
1482
+ all_arguments_required=True,
1483
+ )
1484
+ def regmark(command, channel, _, data, cmd=None, **kwargs):
1485
+ # Move regmarks into the regular element tree and vice versa
1486
+ # _("Regmarks -> Elements") + _("Elements -> Regmarks")
1487
+ if cmd == "free":
1488
+ target = self.elem_branch
1489
+ scope = "Regmarks -> Elements"
1490
+ else:
1491
+ target = self.reg_branch
1492
+ scope = "Elements -> Regmarks"
1493
+
1494
+ with self.undoscope(scope):
1495
+ if data is None:
1496
+ data = list()
1497
+ if cmd == "free":
1498
+ for item in list(self.regmarks()):
1499
+ data.append(item)
1500
+ else:
1501
+ for item in list(self.elems(emphasized=True)):
1502
+ data.append(item)
1503
+ if cmd in ("free", "add"):
1504
+ if len(data) == 0:
1505
+ channel(_("No elements to transfer"))
1506
+ else:
1507
+ move_nodes_to(target, data)
1508
+ if cmd == "free" and self.classify_new:
1509
+ self.classify(data)
1510
+ elif cmd == "clear":
1511
+ self.clear_regmarks()
1512
+ data = None
1513
+ else:
1514
+ # Unknown command
1515
+ channel(_("Invalid command, use one of add, free, clear"))
1516
+ data = None
1517
+ return "elements", data
1518
+
1519
+ # ==========
1520
+ # ELEMENT SUBCOMMANDS
1521
+ # ==========
1522
+
1523
+ # @self.console_argument("step_size", type=int, help=_("element step size"))
1524
+ # @self.console_command(
1525
+ # "step",
1526
+ # help=_("step <element step-size>"),
1527
+ # input_type="elements",
1528
+ # output_type="elements",
1529
+ # )
1530
+ # def step_command(command, channel, _, data, step_size=None, **kwrgs):
1531
+ # if step_size is None:
1532
+ # found = False
1533
+ # for element in data:
1534
+ # if isinstance(element, SVGImage):
1535
+ # try:
1536
+ # step = element.values["raster_step"]
1537
+ # except KeyError:
1538
+ # step = 1
1539
+ # channel(
1540
+ # _("Image step for %s is currently: %s")
1541
+ # % (str(element), step)
1542
+ # )
1543
+ # found = True
1544
+ # if not found:
1545
+ # channel(_("No image element selected."))
1546
+ # return
1547
+ # for element in data:
1548
+ # element.values["raster_step"] = str(step_size)
1549
+ # m = element.transform
1550
+ # tx = m.e
1551
+ # ty = m.f
1552
+ # element.transform = Matrix.scale(float(step_size), float(step_size))
1553
+ # element.transform.post_translate(tx, ty)
1554
+ # if hasattr(element, "node"):
1555
+ # element.node.modified()
1556
+ # self.signal("element_property_reload", element)
1557
+ # return ("elements",)
1558
+
1559
+ @self.console_command(
1560
+ "select",
1561
+ help=_("Set these values as the selection."),
1562
+ input_type="elements",
1563
+ output_type="elements",
1564
+ )
1565
+ def element_select_base(data=None, **kwargs):
1566
+ self.set_emphasis(data)
1567
+ return "elements", data
1568
+
1569
+ @self.console_command(
1570
+ "select+",
1571
+ help=_("Add the input to the selection"),
1572
+ input_type="elements",
1573
+ output_type="elements",
1574
+ )
1575
+ def element_select_plus(data=None, **kwargs):
1576
+ elems = list(self.elems(emphasized=True))
1577
+ elems.extend(data)
1578
+ self.set_emphasis(elems)
1579
+ return "elements", elems
1580
+
1581
+ @self.console_command(
1582
+ "select-",
1583
+ help=_("Remove the input data from the selection"),
1584
+ input_type="elements",
1585
+ output_type="elements",
1586
+ )
1587
+ def element_select_minus(data=None, **kwargs):
1588
+ elems = list(self.elems(emphasized=True))
1589
+ for e in data:
1590
+ try:
1591
+ elems.remove(e)
1592
+ except ValueError:
1593
+ pass
1594
+ self.set_emphasis(elems)
1595
+ return "elements", elems
1596
+
1597
+ @self.console_command(
1598
+ "select^",
1599
+ help=_("Toggle the input data in the selection"),
1600
+ input_type="elements",
1601
+ output_type="elements",
1602
+ )
1603
+ def element_select_xor(data=None, **kwargs):
1604
+ elems = list(self.elems(emphasized=True))
1605
+ for e in data:
1606
+ try:
1607
+ elems.remove(e)
1608
+ except ValueError:
1609
+ elems.append(e)
1610
+ self.set_emphasis(elems)
1611
+ return "elements", elems
1612
+
1613
+ @self.console_command(
1614
+ "list",
1615
+ help=_("Show information about the chained data"),
1616
+ input_type="elements",
1617
+ output_type="elements",
1618
+ )
1619
+ def element_list(command, channel, _, data=None, **kwargs):
1620
+ channel("----------")
1621
+ channel(_("Graphical Elements:"))
1622
+ index_list = list(self.elems())
1623
+ for e in data:
1624
+ i = index_list.index(e)
1625
+ name = str(e)
1626
+ if len(name) > 50:
1627
+ name = name[:50] + "…"
1628
+ if e.emphasized:
1629
+ channel(f"{i}: * {name}")
1630
+ else:
1631
+ channel(f"{i}: {name}")
1632
+ channel("----------")
1633
+ return "elements", data
1634
+
1635
+ @kernel.console_option("stitchtolerance", "s", type=Length, help=_("By default elements will be stitched together if they have common end/start points, this option allows to set a tolerance"))
1636
+ @kernel.console_option("nostitch", "n", type=bool, action="store_true", help=_("By default elements will be stitched together if they have a common end/start point, this option prevents that and real subpaths will be created"))
1637
+ @self.console_command(
1638
+ "merge",
1639
+ help=_("merge elements"),
1640
+ input_type="elements",
1641
+ output_type="elements",
1642
+ )
1643
+ def element_merge(command, channel, _, data=None, post=None, nostitch=None, stitchtolerance=None, **kwargs):
1644
+ """
1645
+ Merge combines the geometries of the inputs. This matters in some cases where fills are used. Such that two
1646
+ nested circles forms a toroid rather two independent circles.
1647
+ """
1648
+ def set_nonset_attributes(node, e):
1649
+ try:
1650
+ if node.stroke is None:
1651
+ node.stroke = e.stroke
1652
+ except AttributeError:
1653
+ pass
1654
+ try:
1655
+ if node.fill is None:
1656
+ node.fill = e.fill
1657
+ except AttributeError:
1658
+ pass
1659
+ try:
1660
+ if node.stroke_width is None:
1661
+ node.stroke_width = e.stroke_width
1662
+ except AttributeError:
1663
+ pass
1664
+
1665
+ def merge_paths(other, path, nocase, tolerance):
1666
+ def segments_can_stitch(aseg, bseg, starta, startb):
1667
+ def segtype(info):
1668
+ return int(info[2].real) & 0xFF
1669
+
1670
+ if segtype(aseg) not in NON_GEOMETRY_TYPES and segtype(bseg) not in NON_GEOMETRY_TYPES:
1671
+ s1, _dummy2, _dummy3, _dummy4, e1 = aseg
1672
+ s2, _dummy2, _dummy3, _dummy4, e2 = bseg
1673
+ c1 = s1 if starta else e1
1674
+ c2 = s2 if startb else e2
1675
+ if abs(c1 - c2) <= tolerance + 1E-6:
1676
+ return True
1677
+ return False
1678
+
1679
+
1680
+ seg1_start = other.segments[0]
1681
+ seg1_end = other.segments[other.index - 1]
1682
+ seg2_start = path.segments[0]
1683
+ seg2_end = path.segments[path.index - 1]
1684
+ # We have six cases: forbidden, s1.end=s2.start, s1.start=s2.end, s1.start=s2.start, s2.end=s1.end, anything else
1685
+
1686
+ if nocase:
1687
+ # ignore, path after other, proper orientation, disjoint
1688
+ separate = True
1689
+ orientation = True
1690
+ path_before = False
1691
+ to_set_seg, to_set_idx, from_seg, from_idx = None, None, None, None
1692
+ elif segments_can_stitch(seg1_end, seg2_start, False, True):
1693
+ # s1.end = s2.start, path after other, proper orientation
1694
+ orientation = True
1695
+ path_before = False
1696
+ separate = False
1697
+ to_set_seg, to_set_idx, from_seg, from_idx = 0, 0, other.index - 1, 4
1698
+ elif segments_can_stitch(seg1_start, seg2_end, True, False):
1699
+ # s1.start = s2.end, path before other, proper orientation, joint
1700
+ orientation = True
1701
+ path_before = True
1702
+ separate = False
1703
+ to_set_seg, to_set_idx, from_seg, from_idx = path.index - 1, 4, 0, 0
1704
+ elif segments_can_stitch(seg1_start, seg2_start, True, True):
1705
+ # s1.start = s2.start, path before other, wrong orientation, joint
1706
+ orientation = False
1707
+ path_before = True
1708
+ separate = False
1709
+ to_set_seg, to_set_idx, from_seg, from_idx = 0, 0, 0, 0
1710
+ elif segments_can_stitch(seg1_end, seg2_end, False, False):
1711
+ # s1.end = s2. end, path after other, wrong orientation, joint
1712
+ orientation = False
1713
+ path_before = False
1714
+ separate = False
1715
+ to_set_seg, to_set_idx, from_seg, from_idx = path.index - 1, 4, other.index - 1, 4
1716
+ else:
1717
+ # ignore, path after other, proper orientation, disjoint
1718
+ separate = True
1719
+ orientation = True
1720
+ path_before = False
1721
+ to_set_seg, to_set_idx, from_seg, from_idx = None, None, None, None
1722
+
1723
+ return separate, orientation, path_before, to_set_seg, to_set_idx, from_seg, from_idx
1724
+
1725
+
1726
+ if nostitch is None:
1727
+ nostitch = False
1728
+ tolerance = 0
1729
+ if stitchtolerance is not None:
1730
+ try:
1731
+ tolerance = float(Length(stitchtolerance))
1732
+ except ValueError:
1733
+ channel(_("Invalid tolerance distance provided"))
1734
+ return
1735
+ if data is None:
1736
+ data = list(self.elems(emphasized=True))
1737
+ if len(data) == 0:
1738
+ channel(_("No item selected."))
1739
+ return
1740
+ node_label = None
1741
+ for e in data:
1742
+ if e.label is not None:
1743
+ el = e.label
1744
+ idx = el.rfind("-")
1745
+ if idx > 0:
1746
+ el = el[:idx]
1747
+ node_label = el
1748
+ break
1749
+ node = self.elem_branch.add(type="elem path", label=node_label)
1750
+ first = True
1751
+ for e in data:
1752
+ try:
1753
+ if hasattr(e, "final_geometry"):
1754
+ path = e.final_geometry()
1755
+ else:
1756
+ path = e.as_geometry()
1757
+ except AttributeError:
1758
+ continue
1759
+
1760
+ set_nonset_attributes(node, e)
1761
+
1762
+ if first:
1763
+ node.geometry = path
1764
+ first = False
1765
+ else:
1766
+ other = node.geometry
1767
+
1768
+ separate, orientation, path_before, to_set_seg, to_set_idx, from_seg, from_idx = merge_paths(other, path, nostitch, tolerance)
1769
+ if to_set_seg is not None:
1770
+ path.segments[to_set_seg, to_set_idx] = other.segments[from_seg, from_idx]
1771
+ actionstr = 'Insert' if path_before else 'Append'
1772
+ typestr = 'regular' if orientation else 'reversed'
1773
+ channel(f"{actionstr} a {typestr} path - separate: {separate}")
1774
+ if not orientation:
1775
+ path.reverse()
1776
+ if path_before:
1777
+ node.geometry.insert(0, path.segments[:path.index])
1778
+ else:
1779
+ node.geometry.append(path, end=separate)
1780
+
1781
+ self.remove_elements(data)
1782
+ self.set_node_emphasis(node, True)
1783
+ # Newly created! Classification needed?
1784
+ data = [node]
1785
+ post.append(classify_new(data))
1786
+ return "elements", data
1787
+
1788
+ @self.console_command(
1789
+ "subpath",
1790
+ help=_("break elements"),
1791
+ input_type="elements",
1792
+ output_type="elements",
1793
+ )
1794
+ def element_subpath(data=None, post=None, **kwargs):
1795
+ """
1796
+ Subpath is the opposite of merge. It divides non-attached paths into different node objects.
1797
+ """
1798
+ if not isinstance(data, list):
1799
+ data = list(data)
1800
+ if not data:
1801
+ return
1802
+ elements_nodes = []
1803
+ elems = []
1804
+ groups= []
1805
+ # _("Break elements")
1806
+ with self.undoscope("Break elements"):
1807
+ for node in data:
1808
+ node_label = node.label
1809
+ node_attributes = {}
1810
+ for attrib in ("stroke", "fill", "stroke_width", "stroke_scaled"):
1811
+ if hasattr(node, attrib):
1812
+ oldval = getattr(node, attrib, None)
1813
+ node_attributes[attrib] = oldval
1814
+ group_node = node.replace_node(type="group", label=node_label, expanded=True)
1815
+ groups.append(group_node)
1816
+ try:
1817
+ if hasattr(node, "final_geometry"):
1818
+ geometry = node.final_geometry()
1819
+ else:
1820
+ geometry = node.as_geometry()
1821
+ geometry.ensure_proper_subpaths()
1822
+ except AttributeError:
1823
+ continue
1824
+ idx = 0
1825
+ for subpath in geometry.as_subpaths():
1826
+ subpath.ensure_proper_subpaths()
1827
+ idx += 1
1828
+ subnode = group_node.add(
1829
+ geometry=subpath,
1830
+ type="elem path",
1831
+ label=f"{node_label}-{idx}",
1832
+ stroke=node_attributes.get("stroke", None),
1833
+ fill=node_attributes.get("fill", None),
1834
+ )
1835
+ for key, value in node_attributes.items():
1836
+ setattr(subnode, key, value)
1837
+
1838
+ elems.append(subnode)
1839
+ elements_nodes.append(group_node)
1840
+ post.append(classify_new(elems))
1841
+ self.signal("element_property_reload", groups)
1842
+ return "elements", elements_nodes
1843
+
1844
+ # --------------------------- END COMMANDS ------------------------------