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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1194 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1858 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4595 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4315 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +934 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/testcases.py +105 -0
  66. meerk40t/core/elements/trace.py +651 -563
  67. meerk40t/core/elements/tree_commands.py +415 -409
  68. meerk40t/core/elements/undo_redo.py +116 -58
  69. meerk40t/core/elements/wordlist.py +319 -200
  70. meerk40t/core/exceptions.py +9 -9
  71. meerk40t/core/laserjob.py +220 -220
  72. meerk40t/core/logging.py +63 -63
  73. meerk40t/core/node/blobnode.py +83 -86
  74. meerk40t/core/node/bootstrap.py +105 -103
  75. meerk40t/core/node/branch_elems.py +40 -31
  76. meerk40t/core/node/branch_ops.py +45 -38
  77. meerk40t/core/node/branch_regmark.py +48 -41
  78. meerk40t/core/node/cutnode.py +29 -32
  79. meerk40t/core/node/effect_hatch.py +375 -257
  80. meerk40t/core/node/effect_warp.py +398 -0
  81. meerk40t/core/node/effect_wobble.py +441 -309
  82. meerk40t/core/node/elem_ellipse.py +404 -309
  83. meerk40t/core/node/elem_image.py +1082 -801
  84. meerk40t/core/node/elem_line.py +358 -292
  85. meerk40t/core/node/elem_path.py +259 -201
  86. meerk40t/core/node/elem_point.py +129 -102
  87. meerk40t/core/node/elem_polyline.py +310 -246
  88. meerk40t/core/node/elem_rect.py +376 -286
  89. meerk40t/core/node/elem_text.py +445 -418
  90. meerk40t/core/node/filenode.py +59 -40
  91. meerk40t/core/node/groupnode.py +138 -74
  92. meerk40t/core/node/image_processed.py +777 -766
  93. meerk40t/core/node/image_raster.py +156 -113
  94. meerk40t/core/node/layernode.py +31 -31
  95. meerk40t/core/node/mixins.py +135 -107
  96. meerk40t/core/node/node.py +1427 -1304
  97. meerk40t/core/node/nutils.py +117 -114
  98. meerk40t/core/node/op_cut.py +463 -335
  99. meerk40t/core/node/op_dots.py +296 -251
  100. meerk40t/core/node/op_engrave.py +414 -311
  101. meerk40t/core/node/op_image.py +755 -369
  102. meerk40t/core/node/op_raster.py +787 -522
  103. meerk40t/core/node/place_current.py +37 -40
  104. meerk40t/core/node/place_point.py +329 -126
  105. meerk40t/core/node/refnode.py +58 -47
  106. meerk40t/core/node/rootnode.py +225 -219
  107. meerk40t/core/node/util_console.py +48 -48
  108. meerk40t/core/node/util_goto.py +84 -65
  109. meerk40t/core/node/util_home.py +61 -61
  110. meerk40t/core/node/util_input.py +102 -102
  111. meerk40t/core/node/util_output.py +102 -102
  112. meerk40t/core/node/util_wait.py +65 -65
  113. meerk40t/core/parameters.py +709 -707
  114. meerk40t/core/planner.py +875 -785
  115. meerk40t/core/plotplanner.py +656 -652
  116. meerk40t/core/space.py +120 -113
  117. meerk40t/core/spoolers.py +706 -705
  118. meerk40t/core/svg_io.py +1836 -1549
  119. meerk40t/core/treeop.py +534 -445
  120. meerk40t/core/undos.py +278 -124
  121. meerk40t/core/units.py +784 -680
  122. meerk40t/core/view.py +393 -322
  123. meerk40t/core/webhelp.py +62 -62
  124. meerk40t/core/wordlist.py +513 -504
  125. meerk40t/cylinder/cylinder.py +247 -0
  126. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  127. meerk40t/cylinder/gui/gui.py +24 -0
  128. meerk40t/device/__init__.py +1 -1
  129. meerk40t/device/basedevice.py +322 -123
  130. meerk40t/device/devicechoices.py +50 -0
  131. meerk40t/device/dummydevice.py +163 -128
  132. meerk40t/device/gui/defaultactions.py +618 -602
  133. meerk40t/device/gui/effectspanel.py +114 -0
  134. meerk40t/device/gui/formatterpanel.py +253 -290
  135. meerk40t/device/gui/warningpanel.py +337 -260
  136. meerk40t/device/mixins.py +13 -13
  137. meerk40t/dxf/__init__.py +1 -1
  138. meerk40t/dxf/dxf_io.py +766 -554
  139. meerk40t/dxf/plugin.py +47 -35
  140. meerk40t/external_plugins.py +79 -79
  141. meerk40t/external_plugins_build.py +28 -28
  142. meerk40t/extra/cag.py +112 -116
  143. meerk40t/extra/coolant.py +403 -0
  144. meerk40t/extra/encode_detect.py +204 -0
  145. meerk40t/extra/ezd.py +1165 -1165
  146. meerk40t/extra/hershey.py +834 -340
  147. meerk40t/extra/imageactions.py +322 -316
  148. meerk40t/extra/inkscape.py +628 -622
  149. meerk40t/extra/lbrn.py +424 -424
  150. meerk40t/extra/outerworld.py +283 -0
  151. meerk40t/extra/param_functions.py +1542 -1556
  152. meerk40t/extra/potrace.py +257 -253
  153. meerk40t/extra/serial_exchange.py +118 -0
  154. meerk40t/extra/updater.py +602 -453
  155. meerk40t/extra/vectrace.py +147 -146
  156. meerk40t/extra/winsleep.py +83 -83
  157. meerk40t/extra/xcs_reader.py +597 -0
  158. meerk40t/fill/fills.py +781 -335
  159. meerk40t/fill/patternfill.py +1061 -1061
  160. meerk40t/fill/patterns.py +614 -567
  161. meerk40t/grbl/control.py +87 -87
  162. meerk40t/grbl/controller.py +990 -903
  163. meerk40t/grbl/device.py +1084 -768
  164. meerk40t/grbl/driver.py +989 -771
  165. meerk40t/grbl/emulator.py +532 -497
  166. meerk40t/grbl/gcodejob.py +783 -767
  167. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  168. meerk40t/grbl/gui/grblcontroller.py +485 -271
  169. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  170. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  171. meerk40t/grbl/gui/gui.py +147 -116
  172. meerk40t/grbl/interpreter.py +44 -44
  173. meerk40t/grbl/loader.py +22 -22
  174. meerk40t/grbl/mock_connection.py +56 -56
  175. meerk40t/grbl/plugin.py +294 -264
  176. meerk40t/grbl/serial_connection.py +93 -88
  177. meerk40t/grbl/tcp_connection.py +81 -79
  178. meerk40t/grbl/ws_connection.py +112 -0
  179. meerk40t/gui/__init__.py +1 -1
  180. meerk40t/gui/about.py +2042 -296
  181. meerk40t/gui/alignment.py +1644 -1608
  182. meerk40t/gui/autoexec.py +199 -0
  183. meerk40t/gui/basicops.py +791 -670
  184. meerk40t/gui/bufferview.py +77 -71
  185. meerk40t/gui/busy.py +232 -133
  186. meerk40t/gui/choicepropertypanel.py +1662 -1469
  187. meerk40t/gui/consolepanel.py +706 -542
  188. meerk40t/gui/devicepanel.py +687 -581
  189. meerk40t/gui/dialogoptions.py +110 -107
  190. meerk40t/gui/executejob.py +316 -306
  191. meerk40t/gui/fonts.py +90 -90
  192. meerk40t/gui/functionwrapper.py +252 -0
  193. meerk40t/gui/gui_mixins.py +729 -0
  194. meerk40t/gui/guicolors.py +205 -182
  195. meerk40t/gui/help_assets/help_assets.py +218 -201
  196. meerk40t/gui/helper.py +154 -0
  197. meerk40t/gui/hersheymanager.py +1440 -846
  198. meerk40t/gui/icons.py +3422 -2747
  199. meerk40t/gui/imagesplitter.py +555 -508
  200. meerk40t/gui/keymap.py +354 -344
  201. meerk40t/gui/laserpanel.py +897 -806
  202. meerk40t/gui/laserrender.py +1470 -1232
  203. meerk40t/gui/lasertoolpanel.py +805 -793
  204. meerk40t/gui/magnetoptions.py +436 -0
  205. meerk40t/gui/materialmanager.py +2944 -0
  206. meerk40t/gui/materialtest.py +1722 -1694
  207. meerk40t/gui/mkdebug.py +646 -359
  208. meerk40t/gui/mwindow.py +163 -140
  209. meerk40t/gui/navigationpanels.py +2605 -2467
  210. meerk40t/gui/notes.py +143 -142
  211. meerk40t/gui/opassignment.py +414 -410
  212. meerk40t/gui/operation_info.py +310 -299
  213. meerk40t/gui/plugin.py +500 -328
  214. meerk40t/gui/position.py +714 -669
  215. meerk40t/gui/preferences.py +901 -650
  216. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  217. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  218. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  219. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  220. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  221. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  222. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  223. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  224. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  225. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  226. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  227. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  228. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  229. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  230. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  231. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  232. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  233. meerk40t/gui/propertypanels/textproperty.py +770 -755
  234. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  235. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  236. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  237. meerk40t/gui/ribbon.py +2471 -2210
  238. meerk40t/gui/scene/scene.py +1100 -1051
  239. meerk40t/gui/scene/sceneconst.py +22 -22
  240. meerk40t/gui/scene/scenepanel.py +439 -349
  241. meerk40t/gui/scene/scenespacewidget.py +365 -365
  242. meerk40t/gui/scene/widget.py +518 -505
  243. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  244. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  245. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  246. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  247. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  248. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  249. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  250. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  251. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  252. meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
  253. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  254. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  255. meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
  256. meerk40t/gui/simpleui.py +362 -333
  257. meerk40t/gui/simulation.py +2451 -2094
  258. meerk40t/gui/snapoptions.py +208 -203
  259. meerk40t/gui/spoolerpanel.py +1227 -1180
  260. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  261. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  262. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  263. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  264. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  265. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  266. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  267. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  268. meerk40t/gui/themes.py +200 -78
  269. meerk40t/gui/tips.py +590 -0
  270. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  271. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  272. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  273. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  274. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  275. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  276. meerk40t/gui/toolwidgets/toolline.py +39 -144
  277. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  278. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  279. meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
  280. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  281. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  282. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  283. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  284. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  285. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  286. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  287. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  288. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  289. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  290. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  291. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  292. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  293. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  294. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  295. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  296. meerk40t/gui/usbconnect.py +98 -91
  297. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  298. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  299. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  300. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  301. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  302. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  303. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  304. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  305. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  306. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  307. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  308. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  309. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  310. meerk40t/gui/wordlisteditor.py +985 -931
  311. meerk40t/gui/wxmeerk40t.py +1447 -1169
  312. meerk40t/gui/wxmmain.py +5644 -4112
  313. meerk40t/gui/wxmribbon.py +1591 -1076
  314. meerk40t/gui/wxmscene.py +1631 -1453
  315. meerk40t/gui/wxmtree.py +2416 -2089
  316. meerk40t/gui/wxutils.py +1769 -1099
  317. meerk40t/gui/zmatrix.py +102 -102
  318. meerk40t/image/__init__.py +1 -1
  319. meerk40t/image/dither.py +429 -0
  320. meerk40t/image/imagetools.py +2793 -2269
  321. meerk40t/internal_plugins.py +150 -130
  322. meerk40t/kernel/__init__.py +63 -12
  323. meerk40t/kernel/channel.py +259 -212
  324. meerk40t/kernel/context.py +538 -538
  325. meerk40t/kernel/exceptions.py +41 -41
  326. meerk40t/kernel/functions.py +463 -414
  327. meerk40t/kernel/jobs.py +100 -100
  328. meerk40t/kernel/kernel.py +3828 -3571
  329. meerk40t/kernel/lifecycles.py +71 -71
  330. meerk40t/kernel/module.py +49 -49
  331. meerk40t/kernel/service.py +147 -147
  332. meerk40t/kernel/settings.py +383 -343
  333. meerk40t/lihuiyu/controller.py +883 -876
  334. meerk40t/lihuiyu/device.py +1181 -1069
  335. meerk40t/lihuiyu/driver.py +1466 -1372
  336. meerk40t/lihuiyu/gui/gui.py +127 -106
  337. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  338. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  339. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  340. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  341. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  342. meerk40t/lihuiyu/interpreter.py +53 -53
  343. meerk40t/lihuiyu/laserspeed.py +450 -450
  344. meerk40t/lihuiyu/loader.py +90 -90
  345. meerk40t/lihuiyu/parser.py +404 -404
  346. meerk40t/lihuiyu/plugin.py +101 -102
  347. meerk40t/lihuiyu/tcp_connection.py +111 -109
  348. meerk40t/main.py +231 -165
  349. meerk40t/moshi/builder.py +788 -781
  350. meerk40t/moshi/controller.py +505 -499
  351. meerk40t/moshi/device.py +495 -442
  352. meerk40t/moshi/driver.py +862 -696
  353. meerk40t/moshi/gui/gui.py +78 -76
  354. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  355. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  356. meerk40t/moshi/plugin.py +43 -43
  357. meerk40t/network/console_server.py +140 -57
  358. meerk40t/network/kernelserver.py +10 -9
  359. meerk40t/network/tcp_server.py +142 -140
  360. meerk40t/network/udp_server.py +103 -77
  361. meerk40t/network/web_server.py +404 -0
  362. meerk40t/newly/controller.py +1158 -1144
  363. meerk40t/newly/device.py +874 -732
  364. meerk40t/newly/driver.py +540 -412
  365. meerk40t/newly/gui/gui.py +219 -188
  366. meerk40t/newly/gui/newlyconfig.py +116 -101
  367. meerk40t/newly/gui/newlycontroller.py +193 -186
  368. meerk40t/newly/gui/operationproperties.py +51 -51
  369. meerk40t/newly/mock_connection.py +82 -82
  370. meerk40t/newly/newly_params.py +56 -56
  371. meerk40t/newly/plugin.py +1214 -1246
  372. meerk40t/newly/usb_connection.py +322 -322
  373. meerk40t/rotary/gui/gui.py +52 -46
  374. meerk40t/rotary/gui/rotarysettings.py +240 -232
  375. meerk40t/rotary/rotary.py +202 -98
  376. meerk40t/ruida/control.py +291 -91
  377. meerk40t/ruida/controller.py +138 -1088
  378. meerk40t/ruida/device.py +676 -231
  379. meerk40t/ruida/driver.py +534 -472
  380. meerk40t/ruida/emulator.py +1494 -1491
  381. meerk40t/ruida/exceptions.py +4 -4
  382. meerk40t/ruida/gui/gui.py +71 -76
  383. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  384. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  385. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  386. meerk40t/ruida/loader.py +54 -52
  387. meerk40t/ruida/mock_connection.py +57 -109
  388. meerk40t/ruida/plugin.py +124 -87
  389. meerk40t/ruida/rdjob.py +2084 -945
  390. meerk40t/ruida/serial_connection.py +116 -0
  391. meerk40t/ruida/tcp_connection.py +146 -0
  392. meerk40t/ruida/udp_connection.py +73 -0
  393. meerk40t/svgelements.py +9671 -9669
  394. meerk40t/tools/driver_to_path.py +584 -579
  395. meerk40t/tools/geomstr.py +5583 -4680
  396. meerk40t/tools/jhfparser.py +357 -292
  397. meerk40t/tools/kerftest.py +904 -890
  398. meerk40t/tools/livinghinges.py +1168 -1033
  399. meerk40t/tools/pathtools.py +987 -949
  400. meerk40t/tools/pmatrix.py +234 -0
  401. meerk40t/tools/pointfinder.py +942 -942
  402. meerk40t/tools/polybool.py +941 -940
  403. meerk40t/tools/rasterplotter.py +1660 -547
  404. meerk40t/tools/shxparser.py +1047 -901
  405. meerk40t/tools/ttfparser.py +726 -446
  406. meerk40t/tools/zinglplotter.py +595 -593
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
  408. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
  409. meerk40t-0.9.7020.dist-info/RECORD +446 -0
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
  412. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
  413. meerk40t/balormk/elementlightjob.py +0 -159
  414. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  415. test/bootstrap.py +0 -63
  416. test/test_cli.py +0 -12
  417. test/test_core_cutcode.py +0 -418
  418. test/test_core_elements.py +0 -144
  419. test/test_core_plotplanner.py +0 -397
  420. test/test_core_viewports.py +0 -312
  421. test/test_drivers_grbl.py +0 -108
  422. test/test_drivers_lihuiyu.py +0 -443
  423. test/test_drivers_newly.py +0 -113
  424. test/test_element_degenerate_points.py +0 -43
  425. test/test_elements_classify.py +0 -97
  426. test/test_elements_penbox.py +0 -22
  427. test/test_file_svg.py +0 -176
  428. test/test_fill.py +0 -155
  429. test/test_geomstr.py +0 -1523
  430. test/test_geomstr_nodes.py +0 -18
  431. test/test_imagetools_actualize.py +0 -306
  432. test/test_imagetools_wizard.py +0 -258
  433. test/test_kernel.py +0 -200
  434. test/test_laser_speeds.py +0 -3303
  435. test/test_length.py +0 -57
  436. test/test_lifecycle.py +0 -66
  437. test/test_operations.py +0 -251
  438. test/test_operations_hatch.py +0 -57
  439. test/test_ruida.py +0 -19
  440. test/test_spooler.py +0 -22
  441. test/test_tools_rasterplotter.py +0 -29
  442. test/test_wobble.py +0 -133
  443. test/test_zingl.py +0 -124
  444. {test → meerk40t/cylinder}/__init__.py +0 -0
  445. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  446. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
@@ -1,1507 +1,1858 @@
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, svg_ppi=self.svg_ppi)
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
+ @self.console_command(
1414
+ "clear_all", help=_("Clear all content"), input_type=("elements", "ops")
1415
+ )
1416
+ def e_clear(command, channel, _, data=None, data_type=None, **kwargs):
1417
+ channel(_("Deleting…"))
1418
+ fast = True
1419
+ with self.undoscope("Deleting"):
1420
+ if data_type == "elements":
1421
+ self.clear_elements(fast=fast)
1422
+ self.emphasized()
1423
+ else:
1424
+ self.clear_operations(fast=fast)
1425
+ self.signal("rebuild_tree", "all")
1426
+
1427
+ # ==========
1428
+ # ELEMENT BASE
1429
+ # ==========
1430
+
1431
+ @self.console_command(
1432
+ "elements",
1433
+ help=_("Show information about elements"),
1434
+ )
1435
+ def elements(**kwargs):
1436
+ self(".element* list\n")
1437
+
1438
+ @self.console_command(
1439
+ "element*",
1440
+ help=_("element*, all elements"),
1441
+ output_type="elements",
1442
+ )
1443
+ def element_star(**kwargs):
1444
+ return "elements", list(self.elems())
1445
+
1446
+ @self.console_command(
1447
+ "element~",
1448
+ help=_("element~, all non-selected elements"),
1449
+ output_type="elements",
1450
+ )
1451
+ def element_not(**kwargs):
1452
+ return "elements", list(self.elems(emphasized=False))
1453
+
1454
+ @self.console_command(
1455
+ "element",
1456
+ help=_("element, selected elements"),
1457
+ output_type="elements",
1458
+ )
1459
+ def element_base(**kwargs):
1460
+ return "elements", list(self.elems(emphasized=True))
1461
+
1462
+ @self.console_command(
1463
+ r"element([0-9]+,?)+",
1464
+ help=_("element0,3,4,5: chain a list of specific elements"),
1465
+ regex=True,
1466
+ output_type="elements",
1467
+ )
1468
+ def element_chain(command, channel, _, **kwargs):
1469
+ arg = command[7:]
1470
+ elements_list = []
1471
+ for value in arg.split(","):
1472
+ try:
1473
+ value = int(value)
1474
+ except ValueError:
1475
+ continue
1476
+ try:
1477
+ e = self.get_elem(value)
1478
+ elements_list.append(e)
1479
+ except IndexError:
1480
+ channel(_("index {index} out of range").format(index=value))
1481
+ return "elements", elements_list
1482
+
1483
+ # ==========
1484
+ # REGMARK COMMANDS
1485
+ # ==========
1486
+ def move_nodes_to(target, nodes):
1487
+ for elem in nodes:
1488
+ target.drop(elem)
1489
+
1490
+ @self.console_argument("cmd", type=str, help=_("free, clear, add"))
1491
+ @self.console_command(
1492
+ "regmark",
1493
+ help=_("regmark cmd"),
1494
+ input_type=(None, "elements"),
1495
+ output_type="elements",
1496
+ all_arguments_required=True,
1497
+ )
1498
+ def regmark(command, channel, _, data, cmd=None, **kwargs):
1499
+ # Move regmarks into the regular element tree and vice versa
1500
+ # _("Regmarks -> Elements") + _("Elements -> Regmarks")
1501
+ if cmd == "free":
1502
+ target = self.elem_branch
1503
+ scope = "Regmarks -> Elements"
1504
+ else:
1505
+ target = self.reg_branch
1506
+ scope = "Elements -> Regmarks"
1507
+
1508
+ with self.undoscope(scope):
1509
+ if data is None:
1510
+ data = list()
1511
+ if cmd == "free":
1512
+ for item in list(self.regmarks()):
1513
+ data.append(item)
1514
+ else:
1515
+ for item in list(self.elems(emphasized=True)):
1516
+ data.append(item)
1517
+ if cmd in ("free", "add"):
1518
+ if len(data) == 0:
1519
+ channel(_("No elements to transfer"))
1520
+ else:
1521
+ move_nodes_to(target, data)
1522
+ if cmd == "free" and self.classify_new:
1523
+ self.classify(data)
1524
+ elif cmd == "clear":
1525
+ self.clear_regmarks()
1526
+ data = None
1527
+ else:
1528
+ # Unknown command
1529
+ channel(_("Invalid command, use one of add, free, clear"))
1530
+ data = None
1531
+ return "elements", data
1532
+
1533
+ # ==========
1534
+ # ELEMENT SUBCOMMANDS
1535
+ # ==========
1536
+
1537
+ # @self.console_argument("step_size", type=int, help=_("element step size"))
1538
+ # @self.console_command(
1539
+ # "step",
1540
+ # help=_("step <element step-size>"),
1541
+ # input_type="elements",
1542
+ # output_type="elements",
1543
+ # )
1544
+ # def step_command(command, channel, _, data, step_size=None, **kwrgs):
1545
+ # if step_size is None:
1546
+ # found = False
1547
+ # for element in data:
1548
+ # if isinstance(element, SVGImage):
1549
+ # try:
1550
+ # step = element.values["raster_step"]
1551
+ # except KeyError:
1552
+ # step = 1
1553
+ # channel(
1554
+ # _("Image step for %s is currently: %s")
1555
+ # % (str(element), step)
1556
+ # )
1557
+ # found = True
1558
+ # if not found:
1559
+ # channel(_("No image element selected."))
1560
+ # return
1561
+ # for element in data:
1562
+ # element.values["raster_step"] = str(step_size)
1563
+ # m = element.transform
1564
+ # tx = m.e
1565
+ # ty = m.f
1566
+ # element.transform = Matrix.scale(float(step_size), float(step_size))
1567
+ # element.transform.post_translate(tx, ty)
1568
+ # if hasattr(element, "node"):
1569
+ # element.node.modified()
1570
+ # self.signal("element_property_reload", element)
1571
+ # return ("elements",)
1572
+
1573
+ @self.console_command(
1574
+ "select",
1575
+ help=_("Set these values as the selection."),
1576
+ input_type="elements",
1577
+ output_type="elements",
1578
+ )
1579
+ def element_select_base(data=None, **kwargs):
1580
+ self.set_emphasis(data)
1581
+ return "elements", data
1582
+
1583
+ @self.console_command(
1584
+ "select+",
1585
+ help=_("Add the input to the selection"),
1586
+ input_type="elements",
1587
+ output_type="elements",
1588
+ )
1589
+ def element_select_plus(data=None, **kwargs):
1590
+ elems = list(self.elems(emphasized=True))
1591
+ elems.extend(data)
1592
+ self.set_emphasis(elems)
1593
+ return "elements", elems
1594
+
1595
+ @self.console_command(
1596
+ "select-",
1597
+ help=_("Remove the input data from the selection"),
1598
+ input_type="elements",
1599
+ output_type="elements",
1600
+ )
1601
+ def element_select_minus(data=None, **kwargs):
1602
+ elems = list(self.elems(emphasized=True))
1603
+ for e in data:
1604
+ try:
1605
+ elems.remove(e)
1606
+ except ValueError:
1607
+ pass
1608
+ self.set_emphasis(elems)
1609
+ return "elements", elems
1610
+
1611
+ @self.console_command(
1612
+ "select^",
1613
+ help=_("Toggle the input data in the selection"),
1614
+ input_type="elements",
1615
+ output_type="elements",
1616
+ )
1617
+ def element_select_xor(data=None, **kwargs):
1618
+ elems = list(self.elems(emphasized=True))
1619
+ for e in data:
1620
+ try:
1621
+ elems.remove(e)
1622
+ except ValueError:
1623
+ elems.append(e)
1624
+ self.set_emphasis(elems)
1625
+ return "elements", elems
1626
+
1627
+ @self.console_command(
1628
+ "list",
1629
+ help=_("Show information about the chained data"),
1630
+ input_type="elements",
1631
+ output_type="elements",
1632
+ )
1633
+ def element_list(command, channel, _, data=None, **kwargs):
1634
+ channel("----------")
1635
+ channel(_("Graphical Elements:"))
1636
+ index_list = list(self.elems())
1637
+ for e in data:
1638
+ i = index_list.index(e)
1639
+ name = str(e)
1640
+ if len(name) > 50:
1641
+ name = name[:50] + "…"
1642
+ if e.emphasized:
1643
+ channel(f"{i}: * {name}")
1644
+ else:
1645
+ channel(f"{i}: {name}")
1646
+ channel("----------")
1647
+ return "elements", data
1648
+
1649
+ @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"))
1650
+ @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"))
1651
+ @self.console_command(
1652
+ "merge",
1653
+ help=_("merge elements"),
1654
+ input_type="elements",
1655
+ output_type="elements",
1656
+ )
1657
+ def element_merge(command, channel, _, data=None, post=None, nostitch=None, stitchtolerance=None, **kwargs):
1658
+ """
1659
+ Merge combines the geometries of the inputs. This matters in some cases where fills are used. Such that two
1660
+ nested circles forms a toroid rather two independent circles.
1661
+ """
1662
+ def set_nonset_attributes(node, e):
1663
+ try:
1664
+ if node.stroke is None:
1665
+ node.stroke = e.stroke
1666
+ except AttributeError:
1667
+ pass
1668
+ try:
1669
+ if node.fill is None:
1670
+ node.fill = e.fill
1671
+ except AttributeError:
1672
+ pass
1673
+ try:
1674
+ if node.stroke_width is None:
1675
+ node.stroke_width = e.stroke_width
1676
+ except AttributeError:
1677
+ pass
1678
+
1679
+ def merge_paths(other, path, nocase, tolerance):
1680
+ def segments_can_stitch(aseg, bseg, starta, startb):
1681
+ def segtype(info):
1682
+ return int(info[2].real) & 0xFF
1683
+
1684
+ if segtype(aseg) not in NON_GEOMETRY_TYPES and segtype(bseg) not in NON_GEOMETRY_TYPES:
1685
+ s1, _dummy2, _dummy3, _dummy4, e1 = aseg
1686
+ s2, _dummy2, _dummy3, _dummy4, e2 = bseg
1687
+ c1 = s1 if starta else e1
1688
+ c2 = s2 if startb else e2
1689
+ if abs(c1 - c2) <= tolerance + 1E-6:
1690
+ return True
1691
+ return False
1692
+
1693
+
1694
+ seg1_start = other.segments[0]
1695
+ seg1_end = other.segments[other.index - 1]
1696
+ seg2_start = path.segments[0]
1697
+ seg2_end = path.segments[path.index - 1]
1698
+ # We have six cases: forbidden, s1.end=s2.start, s1.start=s2.end, s1.start=s2.start, s2.end=s1.end, anything else
1699
+
1700
+ if nocase:
1701
+ # ignore, path after other, proper orientation, disjoint
1702
+ separate = True
1703
+ orientation = True
1704
+ path_before = False
1705
+ to_set_seg, to_set_idx, from_seg, from_idx = None, None, None, None
1706
+ elif segments_can_stitch(seg1_end, seg2_start, False, True):
1707
+ # s1.end = s2.start, path after other, proper orientation
1708
+ orientation = True
1709
+ path_before = False
1710
+ separate = False
1711
+ to_set_seg, to_set_idx, from_seg, from_idx = 0, 0, other.index - 1, 4
1712
+ elif segments_can_stitch(seg1_start, seg2_end, True, False):
1713
+ # s1.start = s2.end, path before other, proper orientation, joint
1714
+ orientation = True
1715
+ path_before = True
1716
+ separate = False
1717
+ to_set_seg, to_set_idx, from_seg, from_idx = path.index - 1, 4, 0, 0
1718
+ elif segments_can_stitch(seg1_start, seg2_start, True, True):
1719
+ # s1.start = s2.start, path before other, wrong orientation, joint
1720
+ orientation = False
1721
+ path_before = True
1722
+ separate = False
1723
+ to_set_seg, to_set_idx, from_seg, from_idx = 0, 0, 0, 0
1724
+ elif segments_can_stitch(seg1_end, seg2_end, False, False):
1725
+ # s1.end = s2. end, path after other, wrong orientation, joint
1726
+ orientation = False
1727
+ path_before = False
1728
+ separate = False
1729
+ to_set_seg, to_set_idx, from_seg, from_idx = path.index - 1, 4, other.index - 1, 4
1730
+ else:
1731
+ # ignore, path after other, proper orientation, disjoint
1732
+ separate = True
1733
+ orientation = True
1734
+ path_before = False
1735
+ to_set_seg, to_set_idx, from_seg, from_idx = None, None, None, None
1736
+
1737
+ return separate, orientation, path_before, to_set_seg, to_set_idx, from_seg, from_idx
1738
+
1739
+
1740
+ if nostitch is None:
1741
+ nostitch = False
1742
+ tolerance = 0
1743
+ if stitchtolerance is not None:
1744
+ try:
1745
+ tolerance = float(Length(stitchtolerance))
1746
+ except ValueError:
1747
+ channel(_("Invalid tolerance distance provided"))
1748
+ return
1749
+ if data is None:
1750
+ data = list(self.elems(emphasized=True))
1751
+ if len(data) == 0:
1752
+ channel(_("No item selected."))
1753
+ return
1754
+ node_label = None
1755
+ for e in data:
1756
+ if e.label is not None:
1757
+ el = e.label
1758
+ idx = el.rfind("-")
1759
+ if idx > 0:
1760
+ el = el[:idx]
1761
+ node_label = el
1762
+ break
1763
+ node = self.elem_branch.add(type="elem path", label=node_label)
1764
+ first = True
1765
+ for e in data:
1766
+ try:
1767
+ if hasattr(e, "final_geometry"):
1768
+ path = e.final_geometry()
1769
+ else:
1770
+ path = e.as_geometry()
1771
+ except AttributeError:
1772
+ continue
1773
+
1774
+ set_nonset_attributes(node, e)
1775
+
1776
+ if first:
1777
+ node.geometry = path
1778
+ first = False
1779
+ else:
1780
+ other = node.geometry
1781
+
1782
+ separate, orientation, path_before, to_set_seg, to_set_idx, from_seg, from_idx = merge_paths(other, path, nostitch, tolerance)
1783
+ if to_set_seg is not None:
1784
+ path.segments[to_set_seg, to_set_idx] = other.segments[from_seg, from_idx]
1785
+ actionstr = 'Insert' if path_before else 'Append'
1786
+ typestr = 'regular' if orientation else 'reversed'
1787
+ channel(f"{actionstr} a {typestr} path - separate: {separate}")
1788
+ if not orientation:
1789
+ path.reverse()
1790
+ if path_before:
1791
+ node.geometry.insert(0, path.segments[:path.index])
1792
+ else:
1793
+ node.geometry.append(path, end=separate)
1794
+
1795
+ self.remove_elements(data)
1796
+ self.set_node_emphasis(node, True)
1797
+ # Newly created! Classification needed?
1798
+ data = [node]
1799
+ post.append(classify_new(data))
1800
+ return "elements", data
1801
+
1802
+ @self.console_command(
1803
+ "subpath",
1804
+ help=_("break elements"),
1805
+ input_type="elements",
1806
+ output_type="elements",
1807
+ )
1808
+ def element_subpath(data=None, post=None, **kwargs):
1809
+ """
1810
+ Subpath is the opposite of merge. It divides non-attached paths into different node objects.
1811
+ """
1812
+ if not isinstance(data, list):
1813
+ data = list(data)
1814
+ if not data:
1815
+ return
1816
+ elements_nodes = []
1817
+ elems = []
1818
+ groups= []
1819
+ # _("Break elements")
1820
+ with self.undoscope("Break elements"):
1821
+ for node in data:
1822
+ node_label = node.label
1823
+ node_attributes = {}
1824
+ for attrib in ("stroke", "fill", "stroke_width", "stroke_scaled"):
1825
+ if hasattr(node, attrib):
1826
+ oldval = getattr(node, attrib, None)
1827
+ node_attributes[attrib] = oldval
1828
+ group_node = node.replace_node(type="group", label=node_label, expanded=True)
1829
+ groups.append(group_node)
1830
+ try:
1831
+ if hasattr(node, "final_geometry"):
1832
+ geometry = node.final_geometry()
1833
+ else:
1834
+ geometry = node.as_geometry()
1835
+ geometry.ensure_proper_subpaths()
1836
+ except AttributeError:
1837
+ continue
1838
+ idx = 0
1839
+ for subpath in geometry.as_subpaths():
1840
+ subpath.ensure_proper_subpaths()
1841
+ idx += 1
1842
+ subnode = group_node.add(
1843
+ geometry=subpath,
1844
+ type="elem path",
1845
+ label=f"{node_label}-{idx}",
1846
+ stroke=node_attributes.get("stroke", None),
1847
+ fill=node_attributes.get("fill", None),
1848
+ )
1849
+ for key, value in node_attributes.items():
1850
+ setattr(subnode, key, value)
1851
+
1852
+ elems.append(subnode)
1853
+ elements_nodes.append(group_node)
1854
+ post.append(classify_new(elems))
1855
+ self.signal("element_property_reload", groups)
1856
+ return "elements", elements_nodes
1857
+
1858
+ # --------------------------- END COMMANDS ------------------------------