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,687 +1,785 @@
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
- from copy import copy
6
- from math import isinf
7
-
8
- from meerk40t.core.node.elem_image import ImageNode
9
- from meerk40t.core.node.elem_path import PathNode
10
- from meerk40t.core.node.node import Fillrule, Linejoin, Node
11
- from meerk40t.core.units import UNITS_PER_INCH, UNITS_PER_PIXEL, Length
12
- from meerk40t.svgelements import Color, Matrix, Path
13
-
14
-
15
- def plugin(kernel, lifecycle=None):
16
- _ = kernel.translation
17
- if lifecycle == "postboot":
18
- init_commands(kernel)
19
-
20
-
21
- def init_commands(kernel):
22
- self = kernel.elements
23
-
24
- _ = kernel.translation
25
-
26
- classify_new = self.post_classify
27
-
28
- @self.console_option("dpi", "d", default=500, type=float)
29
- @self.console_command(
30
- "render",
31
- help=_("Create a raster image from the given elements"),
32
- input_type=(None, "elements"),
33
- output_type="image",
34
- )
35
- def render_elements(command, channel, _, dpi=500.0, data=None, post=None, **kwargs):
36
- if data is None:
37
- data = list(self.elems(emphasized=True))
38
- reverse = self.classify_reverse
39
- if reverse:
40
- data = list(reversed(data))
41
- make_raster = self.lookup("render-op/make_raster")
42
- if not make_raster:
43
- channel(_("No renderer is registered to perform render."))
44
- return
45
- bounds = Node.union_bounds(data, attr="paint_bounds")
46
- # bounds_regular = Node.union_bounds(data)
47
- # for idx in range(4):
48
- # print (f"Bounds[{idx}] = {bounds_regular[idx]:.2f} vs {bounds_regular[idx]:.2f}")
49
- if bounds is None:
50
- return
51
- xmin, ymin, xmax, ymax = bounds
52
- if isinf(xmin):
53
- channel(_("No bounds for selected elements."))
54
- return
55
- width = xmax - xmin
56
- height = ymax - ymin
57
-
58
- dots_per_units = dpi / UNITS_PER_INCH
59
- new_width = width * dots_per_units
60
- new_height = height * dots_per_units
61
- new_height = max(new_height, 1)
62
- new_width = max(new_width, 1)
63
-
64
- image = make_raster(
65
- data,
66
- bounds=bounds,
67
- width=new_width,
68
- height=new_height,
69
- )
70
- matrix = Matrix.scale(width / new_width, height / new_height)
71
- matrix.post_translate(bounds[0], bounds[1])
72
-
73
- image_node = ImageNode(image=image, matrix=matrix, dpi=dpi)
74
- self.elem_branch.add_node(image_node)
75
- self.signal("refresh_scene", "Scene")
76
- data = [image_node]
77
- # Newly created! Classification needed?
78
- post.append(classify_new(data))
79
- return "image", [image_node]
80
-
81
- @self.console_option(
82
- "dpi", "d", help=_("interim image resolution"), default=500, type=float
83
- )
84
- @self.console_option(
85
- "turnpolicy",
86
- "z",
87
- type=str,
88
- default="minority",
89
- help=_("how to resolve ambiguities in path decomposition"),
90
- )
91
- @self.console_option(
92
- "turdsize",
93
- "t",
94
- type=int,
95
- default=2,
96
- help=_("suppress speckles of up to this size (default 2)"),
97
- )
98
- @self.console_option(
99
- "alphamax", "a", type=float, default=1, help=_("corner threshold parameter")
100
- )
101
- @self.console_option(
102
- "opticurve",
103
- "n",
104
- type=bool,
105
- action="store_true",
106
- help=_("turn off curve optimization"),
107
- )
108
- @self.console_option(
109
- "opttolerance",
110
- "O",
111
- type=float,
112
- help=_("curve optimization tolerance"),
113
- default=0.2,
114
- )
115
- @self.console_option(
116
- "color",
117
- "C",
118
- type=Color,
119
- help=_("set foreground color (default Black)"),
120
- )
121
- @self.console_option(
122
- "invert",
123
- "i",
124
- type=bool,
125
- action="store_true",
126
- help=_("invert bitmap"),
127
- )
128
- @self.console_option(
129
- "blacklevel",
130
- "k",
131
- type=float,
132
- default=0.5,
133
- help=_("blacklevel?!"),
134
- )
135
- @self.console_command(
136
- "vectorize",
137
- help=_("Convert given elements to a path"),
138
- input_type=(None, "elements"),
139
- output_type="elements",
140
- )
141
- def vectorize_elements(
142
- command,
143
- channel,
144
- _,
145
- dpi=500.0,
146
- turnpolicy=None,
147
- turdsize=None,
148
- alphamax=None,
149
- opticurve=None,
150
- opttolerance=None,
151
- color=None,
152
- invert=None,
153
- blacklevel=None,
154
- data=None,
155
- post=None,
156
- **kwargs,
157
- ):
158
- if data is None:
159
- data = list(self.elems(emphasized=True))
160
- reverse = self.classify_reverse
161
- if reverse:
162
- data = list(reversed(data))
163
- make_raster = self.lookup("render-op/make_raster")
164
- make_vector = self.lookup("render-op/make_vector")
165
- if not make_raster:
166
- channel(_("No renderer is registered to perform render."))
167
- return
168
- if not make_vector:
169
- channel(_("No vectorization engine could be found."))
170
- return
171
-
172
- policies = {
173
- "black": 0, # POTRACE_TURNPOLICY_BLACK
174
- "white": 1, # POTRACE_TURNPOLICY_WHITE
175
- "left": 2, # POTRACE_TURNPOLICY_LEFT
176
- "right": 3, # POTRACE_TURNPOLICY_RIGHT
177
- "minority": 4, # POTRACE_TURNPOLICY_MINORITY
178
- "majority": 5, # POTRACE_TURNPOLICY_MAJORITY
179
- "random": 6, # POTRACE_TURNPOLICY_RANDOM
180
- }
181
-
182
- if turnpolicy not in policies:
183
- turnpolicy = "minority"
184
- ipolicy = policies[turnpolicy]
185
-
186
- if turdsize is None:
187
- turdsize = 2
188
- if alphamax is None:
189
- alphamax = 1
190
- if opticurve is None:
191
- opticurve = True
192
- if opttolerance is None:
193
- opttolerance = 0.2
194
- if color is None:
195
- color = Color("black")
196
- if invert is None:
197
- invert = False
198
- if blacklevel is None:
199
- blacklevel = 0.5
200
-
201
- bounds = Node.union_bounds(data, attr="paint_bounds")
202
- if bounds is None:
203
- return
204
- xmin, ymin, xmax, ymax = bounds
205
- if isinf(xmin):
206
- channel(_("No bounds for selected elements."))
207
- return
208
- width = xmax - xmin
209
- height = ymax - ymin
210
-
211
- dots_per_units = dpi / UNITS_PER_INCH
212
- new_width = width * dots_per_units
213
- new_height = height * dots_per_units
214
- new_height = max(new_height, 1)
215
- new_width = max(new_width, 1)
216
-
217
- image = make_raster(
218
- data,
219
- bounds=bounds,
220
- width=new_width,
221
- height=new_height,
222
- )
223
- path = make_vector(
224
- image,
225
- interpolationpolicy=ipolicy,
226
- invert=invert,
227
- turdsize=turdsize,
228
- alphamax=alphamax,
229
- opticurve=opticurve,
230
- opttolerance=opttolerance,
231
- color=color,
232
- blacklevel=blacklevel,
233
- )
234
- matrix = Matrix.scale(width / new_width, height / new_height)
235
- matrix.post_translate(bounds[0], bounds[1])
236
- path.transform *= Matrix(matrix)
237
- node = self.elem_branch.add(
238
- path=abs(path),
239
- stroke_width=0,
240
- stroke_scaled=False,
241
- type="elem path",
242
- fillrule=Fillrule.FILLRULE_NONZERO,
243
- linejoin=Linejoin.JOIN_ROUND,
244
- )
245
- # Newly created! Classification needed?
246
- data_out = [node]
247
- post.append(classify_new(data_out))
248
- self.signal("refresh_scene", "Scene")
249
-
250
- return "elements", data_out
251
-
252
- @self.console_option(
253
- "dpi", "d", help=_("interim image resolution"), default=500, type=float
254
- )
255
- @self.console_option(
256
- "turnpolicy",
257
- "z",
258
- type=str,
259
- default="minority",
260
- help=_("how to resolve ambiguities in path decomposition"),
261
- )
262
- @self.console_option(
263
- "turdsize",
264
- "t",
265
- type=int,
266
- default=2,
267
- help=_("suppress speckles of up to this size (default 2)"),
268
- )
269
- @self.console_option(
270
- "alphamax", "a", type=float, default=1, help=_("corner threshold parameter")
271
- )
272
- @self.console_option(
273
- "opticurve",
274
- "n",
275
- type=bool,
276
- action="store_true",
277
- help=_("turn off curve optimization"),
278
- )
279
- @self.console_option(
280
- "opttolerance",
281
- "O",
282
- type=float,
283
- help=_("curve optimization tolerance"),
284
- default=0.2,
285
- )
286
- @self.console_option(
287
- "color",
288
- "C",
289
- type=Color,
290
- help=_("set foreground color (default Black)"),
291
- )
292
- @self.console_option(
293
- "invert",
294
- "i",
295
- type=bool,
296
- action="store_true",
297
- help=_("invert bitmap"),
298
- )
299
- @self.console_option(
300
- "blacklevel",
301
- "k",
302
- type=float,
303
- default=0.5,
304
- help=_("blacklevel?!"),
305
- )
306
- @self.console_option(
307
- "outer",
308
- "u",
309
- type=bool,
310
- action="store_true",
311
- help=_("Only outer line"),
312
- )
313
- @self.console_option(
314
- "steps",
315
- "x",
316
- type=int,
317
- default=1,
318
- help=_("How many offsetlines (default 1)"),
319
- )
320
- @self.console_option(
321
- "debug",
322
- "d",
323
- type=bool,
324
- action="store_true",
325
- help=_("Preserve intermediary objects"),
326
- )
327
- @self.console_argument("offset", type=Length, help="Offset distance")
328
- @self.console_command(
329
- "outline",
330
- help=_("Create an outline path at the inner and outer side of a path"),
331
- input_type=(None, "elements"),
332
- output_type="elements",
333
- )
334
- def element_outline(
335
- command,
336
- channel,
337
- _,
338
- offset=None,
339
- dpi=500.0,
340
- turnpolicy=None,
341
- turdsize=None,
342
- alphamax=None,
343
- opticurve=None,
344
- opttolerance=None,
345
- color=None,
346
- invert=None,
347
- blacklevel=None,
348
- outer=None,
349
- steps=None,
350
- debug=False,
351
- data=None,
352
- post=None,
353
- **kwargs,
354
- ):
355
- """
356
- Phase 1: We create a rendered image of the data, then we vectorize
357
- this representation
358
- Phase 2: This path will then be adjusted by applying
359
- altered stroke-widths and rendered and vectorized again.
360
-
361
- This two phase approach is required as not all nodes have
362
- a proper stroke-width that can be adjusted (eg text or images...)
363
-
364
- The subvariant --outer requires one additional pass where we disassemble
365
- the first outline and fill the subpaths, This will effectively deal with
366
- donut-type shapes
367
-
368
- The need for --inner wasn't high on my priority list, as it is somwhat
369
- difficult to implement. --outer just uses a clever hack to deal with
370
- topology edge cases. So if we are in need of inner we need to create
371
- the outline shape, break it in subpaths and delete the outer shapes
372
- manually. Sorry.
373
- """
374
- if data is None:
375
- data = list(self.elems(emphasized=True))
376
- if data is None or len(data) == 0:
377
- channel(_("No elements to outline."))
378
- return
379
- if debug is None:
380
- debug = False
381
- reverse = self.classify_reverse
382
- if reverse:
383
- data = list(reversed(data))
384
- make_raster = self.lookup("render-op/make_raster")
385
- make_vector = self.lookup("render-op/make_vector")
386
- if not make_raster:
387
- channel(_("No renderer is registered to perform render."))
388
- return
389
- if not make_vector:
390
- channel(_("No vectorization engine could be found."))
391
- return
392
-
393
- policies = {
394
- "black": 0, # POTRACE_TURNPOLICY_BLACK
395
- "white": 1, # POTRACE_TURNPOLICY_WHITE
396
- "left": 2, # POTRACE_TURNPOLICY_LEFT
397
- "right": 3, # POTRACE_TURNPOLICY_RIGHT
398
- "minority": 4, # POTRACE_TURNPOLICY_MINORITY
399
- "majority": 5, # POTRACE_TURNPOLICY_MAJORITY
400
- "random": 6, # POTRACE_TURNPOLICY_RANDOM
401
- }
402
-
403
- if turnpolicy not in policies:
404
- turnpolicy = "minority"
405
- ipolicy = policies[turnpolicy]
406
-
407
- if turdsize is None:
408
- turdsize = 2
409
- if alphamax is None:
410
- alphamax = 1
411
- if opticurve is None:
412
- opticurve = True
413
- if opttolerance is None:
414
- opttolerance = 0.2
415
- if color is None:
416
- pathcolor = Color("blue")
417
- else:
418
- pathcolor = color
419
- if invert is None:
420
- invert = False
421
- if blacklevel is None:
422
- blacklevel = 0.5
423
- if offset is None:
424
- offset = self.length("5mm")
425
- else:
426
- offset = self.length(offset)
427
- if steps is None or steps < 1:
428
- steps = 1
429
- if outer is None:
430
- outer = False
431
- outputdata = []
432
- mydata = []
433
- for node in data:
434
- if outer and hasattr(node, "fill"):
435
- e = copy(node)
436
- e.fill = Color("black")
437
- if hasattr(e, "stroke"):
438
- e.stroke = Color("black")
439
- if hasattr(e, "stroke_width") and e.stroke_width == 0:
440
- e.stroke_width = UNITS_PER_PIXEL
441
- if hasattr(e, "fillrule"):
442
- e.fillrule = 0
443
- mydata.append(e)
444
- else:
445
- e = copy(node)
446
- if hasattr(e, "stroke_width") and e.stroke_width == 0:
447
- e.stroke_width = UNITS_PER_PIXEL
448
- mydata.append(e)
449
- if debug:
450
- for node in mydata:
451
- node.label = "Phase 0: Initial copy"
452
- self.elem_branch.add_node(node)
453
-
454
- ###############################################
455
- # Phase 1: render and vectorize first outline
456
- ###############################################
457
- bounds = Node.union_bounds(mydata, attr="paint_bounds")
458
- # bounds_regular = Node.union_bounds(data)
459
- # for idx in range(4):
460
- # print (f"Bounds[{idx}] = {bounds_regular[idx]:.2f} vs {bounds_regular[idx]:.2f}")
461
- if bounds is None:
462
- return
463
- xmin, ymin, xmax, ymax = bounds
464
- if isinf(xmin):
465
- channel(_("No bounds for selected elements."))
466
- return
467
- width = xmax - xmin
468
- height = ymax - ymin
469
-
470
- dots_per_units = dpi / UNITS_PER_INCH
471
- new_width = width * dots_per_units
472
- new_height = height * dots_per_units
473
- new_height = max(new_height, 1)
474
- new_width = max(new_width, 1)
475
- dpi = 500
476
-
477
- data_image = make_raster(
478
- mydata,
479
- bounds=bounds,
480
- width=new_width,
481
- height=new_height,
482
- )
483
- matrix = Matrix.scale(width / new_width, height / new_height)
484
- matrix.post_translate(bounds[0], bounds[1])
485
- image_node_1 = ImageNode(
486
- image=data_image, matrix=matrix, dpi=dpi, label="Phase 1 render image"
487
- )
488
-
489
- path = make_vector(
490
- data_image,
491
- interpolationpolicy=ipolicy,
492
- invert=invert,
493
- turdsize=turdsize,
494
- alphamax=alphamax,
495
- opticurve=opticurve,
496
- opttolerance=opttolerance,
497
- color=color,
498
- blacklevel=blacklevel,
499
- )
500
- matrix = Matrix.scale(width / new_width, height / new_height)
501
- matrix.post_translate(bounds[0], bounds[1])
502
- path.transform *= Matrix(matrix)
503
- data_node = PathNode(
504
- path=abs(path),
505
- stroke_width=1,
506
- stroke=Color("black"),
507
- stroke_scaled=False,
508
- fill=None,
509
- # fillrule=Fillrule.FILLRULE_NONZERO,
510
- linejoin=Linejoin.JOIN_ROUND,
511
- label="Phase 1 Outline path",
512
- )
513
- data_node.fill = None
514
- # If you want to debug the phases then uncomment the following lines to
515
- # see the interim path and interim render image
516
- if debug:
517
- self.elem_branch.add_node(data_node)
518
- self.elem_branch.add_node(image_node_1)
519
-
520
- copy_data = [image_node_1, data_node]
521
-
522
- ################################################################
523
- # Phase 2: change outline witdh and render and vectorize again
524
- ################################################################
525
- for numidx in range(steps):
526
- data_node.stroke_width += 2 * offset
527
- data_node.set_dirty_bounds()
528
- pb = data_node.paint_bounds
529
- bounds = Node.union_bounds(copy_data, attr="paint_bounds")
530
- # print (f"{pb} - {bounds}")
531
- if bounds is None:
532
- return
533
- # bounds_regular = Node.union_bounds(copy_data)
534
- # for idx in range(4):
535
- # print (f"Bounds[{idx}] = {bounds_regular[idx]:.2f} vs {bounds[idx]:.2f}")
536
- xmin, ymin, xmax, ymax = bounds
537
- if isinf(xmin):
538
- channel(_("No bounds for selected elements."))
539
- return
540
- width = xmax - xmin
541
- height = ymax - ymin
542
-
543
- dots_per_units = dpi / UNITS_PER_INCH
544
- new_width = width * dots_per_units
545
- new_height = height * dots_per_units
546
- new_height = max(new_height, 1)
547
- new_width = max(new_width, 1)
548
- dpi = 500
549
-
550
- image_2 = make_raster(
551
- copy_data,
552
- bounds=bounds,
553
- width=new_width,
554
- height=new_height,
555
- )
556
- matrix = Matrix.scale(width / new_width, height / new_height)
557
- matrix.post_translate(bounds[0], bounds[1])
558
- image_node_2 = ImageNode(
559
- image=image_2, matrix=matrix, dpi=dpi, label="Phase 2 render image"
560
- )
561
-
562
- path_2 = make_vector(
563
- image_2,
564
- interpolationpolicy=ipolicy,
565
- invert=invert,
566
- turdsize=turdsize,
567
- alphamax=alphamax,
568
- opticurve=opticurve,
569
- opttolerance=opttolerance,
570
- color=color,
571
- blacklevel=blacklevel,
572
- )
573
- matrix = Matrix.scale(width / new_width, height / new_height)
574
- matrix.post_translate(bounds[0], bounds[1])
575
- path_2.transform *= Matrix(matrix)
576
- # That's our final path (or is it? Depends on outer...)
577
- path_final = path_2
578
- data_node_2 = PathNode(
579
- path=abs(path_2),
580
- stroke_width=1,
581
- stroke=Color("black"),
582
- stroke_scaled=False,
583
- fill=None,
584
- # fillrule=Fillrule.FILLRULE_NONZERO,
585
- linejoin=Linejoin.JOIN_ROUND,
586
- label="Phase 2 Outline path",
587
- )
588
- data_node_2.fill = None
589
-
590
- # If you want to debug the phases then uncomment the following line to
591
- # see the interim image
592
- if debug:
593
- self.elem_branch.add_node(image_node_2)
594
- self.elem_branch.add_node(data_node_2)
595
- #######################################################
596
- # Phase 3: render and vectorize last outline for outer
597
- #######################################################
598
- if outer:
599
- # Generate the outline, break it into subpaths
600
- copy_data = []
601
- # Now break it into subpaths...
602
- for pasp in path_final.as_subpaths():
603
- subpath = Path(pasp)
604
- data_node = PathNode(
605
- path=abs(subpath),
606
- stroke_width=1,
607
- stroke=Color("black"),
608
- stroke_scaled=False,
609
- fill=Color("black"),
610
- # fillrule=Fillrule.FILLRULE_NONZERO,
611
- linejoin=Linejoin.JOIN_ROUND,
612
- label="Phase 3 Outline subpath",
613
- )
614
- # This seems to be necessary to make sure the fill sticks
615
- data_node.fill = Color("black")
616
- copy_data.append(data_node)
617
- # If you want to debug the phases then uncomment the following lines to
618
- # see the interim path nodes
619
- if debug:
620
- self.elem_branch.add_node(data_node)
621
-
622
- bounds = Node.union_bounds(copy_data, attr="paint_bounds")
623
- # bounds_regular = Node.union_bounds(data)
624
- # for idx in range(4):
625
- # print (f"Bounds[{idx}] = {bounds_regular[idx]:.2f} vs {bounds_regular[idx]:.2f}")
626
- if bounds is None:
627
- return
628
- xmin, ymin, xmax, ymax = bounds
629
- if isinf(xmin):
630
- channel(_("No bounds for selected elements."))
631
- return
632
- width = xmax - xmin
633
- height = ymax - ymin
634
-
635
- dots_per_units = dpi / UNITS_PER_INCH
636
- new_width = width * dots_per_units
637
- new_height = height * dots_per_units
638
- new_height = max(new_height, 1)
639
- new_width = max(new_width, 1)
640
- dpi = 500
641
-
642
- data_image = make_raster(
643
- copy_data,
644
- bounds=bounds,
645
- width=new_width,
646
- height=new_height,
647
- )
648
- matrix = Matrix.scale(width / new_width, height / new_height)
649
- matrix.post_translate(bounds[0], bounds[1])
650
-
651
- path_final = make_vector(
652
- data_image,
653
- interpolationpolicy=ipolicy,
654
- invert=invert,
655
- turdsize=turdsize,
656
- alphamax=alphamax,
657
- opticurve=opticurve,
658
- opttolerance=opttolerance,
659
- color=color,
660
- blacklevel=blacklevel,
661
- )
662
- matrix = Matrix.scale(width / new_width, height / new_height)
663
- matrix.post_translate(bounds[0], bounds[1])
664
- path_final.transform *= Matrix(matrix)
665
-
666
- outline_node = self.elem_branch.add(
667
- path=abs(path_final),
668
- stroke_width=1,
669
- stroke_scaled=False,
670
- type="elem path",
671
- fill=None,
672
- stroke=pathcolor,
673
- # fillrule=Fillrule.FILLRULE_NONZERO,
674
- linejoin=Linejoin.JOIN_ROUND,
675
- label=f"Outline path #{numidx}",
676
- )
677
- outline_node.fill = None
678
- outputdata.append(outline_node)
679
-
680
- # Newly created! Classification needed?
681
- post.append(classify_new(outputdata))
682
- self.signal("refresh_scene", "Scene")
683
- if len(outputdata) > 0:
684
- self.signal("element_property_update", outputdata)
685
- return "elements", outputdata
686
-
687
- # --------------------------- END COMMANDS ------------------------------
1
+ """
2
+ This module contains console commands that interact with the elements system in the program.
3
+ It provides functionality for rendering, vectorizing, outlining, and managing keyhole elements for images.
4
+
5
+ Functions:
6
+ - plugin: Initializes console commands related to rendering and vectorization.
7
+ - init_commands: Sets up various console commands for rendering and manipulating elements.
8
+ - render_elements: Creates a raster image from the given elements.
9
+ - vectorize_elements: Converts given elements to a path.
10
+ - element_outline: Creates an outline path at the inner and outer side of a path.
11
+ - keyhole_elements: Sets a path-like element as a keyhole frame for selected images.
12
+ - remove_keyhole_elements: Removes keyhole frame for selected images.
13
+
14
+ """
15
+
16
+ from copy import copy
17
+ from math import isinf
18
+
19
+ from meerk40t.core.node.elem_image import ImageNode
20
+ from meerk40t.core.node.elem_path import PathNode
21
+ from meerk40t.core.node.node import Fillrule, Linejoin, Node
22
+ from meerk40t.core.units import UNITS_PER_INCH, UNITS_PER_PIXEL, Length
23
+ from meerk40t.svgelements import Color, Matrix, Path
24
+
25
+
26
+ def plugin(kernel, lifecycle=None):
27
+ _ = kernel.translation
28
+ if lifecycle == "postboot":
29
+ init_commands(kernel)
30
+
31
+
32
+ def init_commands(kernel):
33
+ self = kernel.elements
34
+
35
+ _ = kernel.translation
36
+
37
+ classify_new = self.post_classify
38
+
39
+ @self.console_option("dpi", "d", default=500, type=float)
40
+ @self.console_command(
41
+ "render",
42
+ help=_("Create a raster image from the given elements"),
43
+ input_type=(None, "elements"),
44
+ output_type="image",
45
+ )
46
+ def render_elements(command, channel, _, dpi=500.0, data=None, post=None, **kwargs):
47
+ if data is None:
48
+ data = list(self.elems(emphasized=True))
49
+ reverse = self.classify_reverse
50
+ if reverse:
51
+ data = list(reversed(data))
52
+ make_raster = self.lookup("render-op/make_raster")
53
+ if not make_raster:
54
+ channel(_("No renderer is registered to perform render."))
55
+ return
56
+ bounds = Node.union_bounds(data, attr="paint_bounds")
57
+ # bounds_regular = Node.union_bounds(data)
58
+ # for idx in range(4):
59
+ # print (f"Bounds[{idx}] = {bounds_regular[idx]:.2f} vs {bounds_regular[idx]:.2f}")
60
+ if bounds is None:
61
+ return
62
+ xmin, ymin, xmax, ymax = bounds
63
+ if isinf(xmin):
64
+ channel(_("No bounds for selected elements."))
65
+ return
66
+ width = xmax - xmin
67
+ height = ymax - ymin
68
+
69
+ dots_per_units = dpi / UNITS_PER_INCH
70
+ new_width = width * dots_per_units
71
+ new_height = height * dots_per_units
72
+ new_height = max(new_height, 1)
73
+ new_width = max(new_width, 1)
74
+ try:
75
+ image = make_raster(
76
+ data,
77
+ bounds=bounds,
78
+ width=new_width,
79
+ height=new_height,
80
+ )
81
+ except Exception:
82
+ channel(_("Too much memory required."))
83
+ return
84
+ matrix = Matrix.scale(width / new_width, height / new_height)
85
+ matrix.post_translate(bounds[0], bounds[1])
86
+
87
+ image_node = ImageNode(image=image, matrix=matrix, dpi=dpi)
88
+ self.elem_branch.add_node(image_node)
89
+ self.signal("refresh_scene", "Scene")
90
+ data = [image_node]
91
+ # Newly created! Classification needed?
92
+ post.append(classify_new(data))
93
+ return "image", [image_node]
94
+
95
+ @self.console_option(
96
+ "dpi", "d", help=_("interim image resolution"), default=500, type=float
97
+ )
98
+ @self.console_option(
99
+ "turnpolicy",
100
+ "z",
101
+ type=str,
102
+ default="minority",
103
+ help=_("how to resolve ambiguities in path decomposition"),
104
+ )
105
+ @self.console_option(
106
+ "turdsize",
107
+ "t",
108
+ type=int,
109
+ default=2,
110
+ help=_("suppress speckles of up to this size (default 2)"),
111
+ )
112
+ @self.console_option(
113
+ "alphamax", "a", type=float, default=1, help=_("corner threshold parameter")
114
+ )
115
+ @self.console_option(
116
+ "opticurve",
117
+ "n",
118
+ type=bool,
119
+ action="store_true",
120
+ help=_("turn off curve optimization"),
121
+ )
122
+ @self.console_option(
123
+ "opttolerance",
124
+ "O",
125
+ type=float,
126
+ help=_("curve optimization tolerance"),
127
+ default=0.2,
128
+ )
129
+ @self.console_option(
130
+ "color",
131
+ "C",
132
+ type=Color,
133
+ help=_("set foreground color (default Black)"),
134
+ )
135
+ @self.console_option(
136
+ "invert",
137
+ "i",
138
+ type=bool,
139
+ action="store_true",
140
+ help=_("invert bitmap"),
141
+ )
142
+ @self.console_option(
143
+ "blacklevel",
144
+ "k",
145
+ type=float,
146
+ default=0.5,
147
+ help=_("blacklevel?!"),
148
+ )
149
+ @self.console_command(
150
+ "vectorize",
151
+ help=_("Convert given elements to a path"),
152
+ input_type=(None, "elements"),
153
+ output_type="elements",
154
+ )
155
+ def vectorize_elements(
156
+ command,
157
+ channel,
158
+ _,
159
+ dpi=500.0,
160
+ turnpolicy=None,
161
+ turdsize=None,
162
+ alphamax=None,
163
+ opticurve=None,
164
+ opttolerance=None,
165
+ color=None,
166
+ invert=None,
167
+ blacklevel=None,
168
+ data=None,
169
+ post=None,
170
+ **kwargs,
171
+ ):
172
+ if data is None:
173
+ data = list(self.elems(emphasized=True))
174
+ reverse = self.classify_reverse
175
+ if reverse:
176
+ data = list(reversed(data))
177
+ make_raster = self.lookup("render-op/make_raster")
178
+ make_vector = self.lookup("render-op/make_vector")
179
+ if not make_raster:
180
+ channel(_("No renderer is registered to perform render."))
181
+ return
182
+ if not make_vector:
183
+ channel(_("No vectorization engine could be found."))
184
+ return
185
+
186
+ policies = {
187
+ "black": 0, # POTRACE_TURNPOLICY_BLACK
188
+ "white": 1, # POTRACE_TURNPOLICY_WHITE
189
+ "left": 2, # POTRACE_TURNPOLICY_LEFT
190
+ "right": 3, # POTRACE_TURNPOLICY_RIGHT
191
+ "minority": 4, # POTRACE_TURNPOLICY_MINORITY
192
+ "majority": 5, # POTRACE_TURNPOLICY_MAJORITY
193
+ "random": 6, # POTRACE_TURNPOLICY_RANDOM
194
+ }
195
+
196
+ if turnpolicy not in policies:
197
+ turnpolicy = "minority"
198
+ ipolicy = policies[turnpolicy]
199
+
200
+ if turdsize is None:
201
+ turdsize = 2
202
+ if alphamax is None:
203
+ alphamax = 1
204
+ if opticurve is None:
205
+ opticurve = True
206
+ if opttolerance is None:
207
+ opttolerance = 0.2
208
+ if color is None:
209
+ color = Color("black")
210
+ if invert is None:
211
+ invert = False
212
+ if blacklevel is None:
213
+ blacklevel = 0.5
214
+
215
+ bounds = Node.union_bounds(data, attr="paint_bounds")
216
+ if bounds is None:
217
+ return
218
+ xmin, ymin, xmax, ymax = bounds
219
+ if isinf(xmin):
220
+ channel(_("No bounds for selected elements."))
221
+ return
222
+ kernel.busyinfo.start(msg=_("Generating..."))
223
+ width = xmax - xmin
224
+ height = ymax - ymin
225
+
226
+ dots_per_units = dpi / UNITS_PER_INCH
227
+ new_width = width * dots_per_units
228
+ new_height = height * dots_per_units
229
+ new_height = max(new_height, 1)
230
+ new_width = max(new_width, 1)
231
+ try:
232
+ image = make_raster(
233
+ data,
234
+ bounds=bounds,
235
+ width=new_width,
236
+ height=new_height,
237
+ )
238
+ except Exception:
239
+ channel(_("Too much memory required."))
240
+ return
241
+ path = make_vector(
242
+ image,
243
+ interpolationpolicy=ipolicy,
244
+ invert=invert,
245
+ turdsize=turdsize,
246
+ alphamax=alphamax,
247
+ opticurve=opticurve,
248
+ opttolerance=opttolerance,
249
+ color=color,
250
+ blacklevel=blacklevel,
251
+ )
252
+ matrix = Matrix.scale(width / new_width, height / new_height)
253
+ matrix.post_translate(bounds[0], bounds[1])
254
+ path.transform *= Matrix(matrix)
255
+ node = self.elem_branch.add(
256
+ path=abs(path),
257
+ stroke_width=500,
258
+ stroke_scaled=False,
259
+ type="elem path",
260
+ fillrule=Fillrule.FILLRULE_NONZERO,
261
+ linejoin=Linejoin.JOIN_ROUND,
262
+ )
263
+ # Newly created! Classification needed?
264
+ data_out = [node]
265
+ post.append(classify_new(data_out))
266
+ self.signal("refresh_scene", "Scene")
267
+ kernel.busyinfo.end()
268
+ return "elements", data_out
269
+
270
+ @self.console_option(
271
+ "dpi", "d", help=_("interim image resolution"), default=500, type=float
272
+ )
273
+ @self.console_option(
274
+ "turnpolicy",
275
+ "z",
276
+ type=str,
277
+ default="minority",
278
+ help=_("how to resolve ambiguities in path decomposition"),
279
+ )
280
+ @self.console_option(
281
+ "turdsize",
282
+ "t",
283
+ type=int,
284
+ default=2,
285
+ help=_("suppress speckles of up to this size (default 2)"),
286
+ )
287
+ @self.console_option(
288
+ "alphamax", "a", type=float, default=1, help=_("corner threshold parameter")
289
+ )
290
+ @self.console_option(
291
+ "opticurve",
292
+ "n",
293
+ type=bool,
294
+ action="store_true",
295
+ help=_("turn off curve optimization"),
296
+ )
297
+ @self.console_option(
298
+ "opttolerance",
299
+ "O",
300
+ type=float,
301
+ help=_("curve optimization tolerance"),
302
+ default=0.2,
303
+ )
304
+ @self.console_option(
305
+ "color",
306
+ "C",
307
+ type=Color,
308
+ help=_("set foreground color (default Black)"),
309
+ )
310
+ @self.console_option(
311
+ "invert",
312
+ "i",
313
+ type=bool,
314
+ action="store_true",
315
+ help=_("invert bitmap"),
316
+ )
317
+ @self.console_option(
318
+ "blacklevel",
319
+ "k",
320
+ type=float,
321
+ default=0.5,
322
+ help=_("blacklevel?!"),
323
+ )
324
+ @self.console_option(
325
+ "outer",
326
+ "u",
327
+ type=bool,
328
+ action="store_true",
329
+ help=_("Only outer line"),
330
+ )
331
+ @self.console_option(
332
+ "steps",
333
+ "x",
334
+ type=int,
335
+ default=1,
336
+ help=_("How many offsetlines (default 1)"),
337
+ )
338
+ @self.console_option(
339
+ "debug",
340
+ "d",
341
+ type=bool,
342
+ action="store_true",
343
+ help=_("Preserve intermediary objects"),
344
+ )
345
+ @self.console_argument("offset", type=Length, help="Offset distance")
346
+ @self.console_command(
347
+ "outline",
348
+ help=_("Create an outline path at the inner and outer side of a path"),
349
+ input_type=(None, "elements"),
350
+ output_type="elements",
351
+ )
352
+ def element_outline(
353
+ command,
354
+ channel,
355
+ _,
356
+ offset=None,
357
+ dpi=500.0,
358
+ turnpolicy=None,
359
+ turdsize=None,
360
+ alphamax=None,
361
+ opticurve=None,
362
+ opttolerance=None,
363
+ color=None,
364
+ invert=None,
365
+ blacklevel=None,
366
+ outer=None,
367
+ steps=None,
368
+ debug=False,
369
+ data=None,
370
+ post=None,
371
+ **kwargs,
372
+ ):
373
+ """
374
+ Phase 1: We create a rendered image of the data, then we vectorize
375
+ this representation
376
+ Phase 2: This path will then be adjusted by applying
377
+ altered stroke-widths and rendered and vectorized again.
378
+
379
+ This two phase approach is required as not all nodes have
380
+ a proper stroke-width that can be adjusted (e.g. text or images...)
381
+
382
+ The subvariant --outer requires one additional pass where we disassemble
383
+ the first outline and fill the subpaths, This will effectively deal with
384
+ donut-type shapes
385
+
386
+ The need for --inner wasn't high on my priority list, as it is somwhat
387
+ difficult to implement. --outer just uses a clever hack to deal with
388
+ topology edge cases. So if we are in need of inner we need to create
389
+ the outline shape, break it in subpaths and delete the outer shapes
390
+ manually. Sorry.
391
+ """
392
+ if data is None:
393
+ data = list(self.elems(emphasized=True))
394
+ if data is None or len(data) == 0:
395
+ channel(_("No elements to outline."))
396
+ return
397
+ if debug is None:
398
+ debug = False
399
+ kernel.busyinfo.start(msg=_("Generating..."))
400
+ reverse = self.classify_reverse
401
+ if reverse:
402
+ data = list(reversed(data))
403
+ make_raster = self.lookup("render-op/make_raster")
404
+ make_vector = self.lookup("render-op/make_vector")
405
+ if not make_raster:
406
+ channel(_("No renderer is registered to perform render."))
407
+ return
408
+ if not make_vector:
409
+ channel(_("No vectorization engine could be found."))
410
+ return
411
+
412
+ policies = {
413
+ "black": 0, # POTRACE_TURNPOLICY_BLACK
414
+ "white": 1, # POTRACE_TURNPOLICY_WHITE
415
+ "left": 2, # POTRACE_TURNPOLICY_LEFT
416
+ "right": 3, # POTRACE_TURNPOLICY_RIGHT
417
+ "minority": 4, # POTRACE_TURNPOLICY_MINORITY
418
+ "majority": 5, # POTRACE_TURNPOLICY_MAJORITY
419
+ "random": 6, # POTRACE_TURNPOLICY_RANDOM
420
+ }
421
+
422
+ if turnpolicy not in policies:
423
+ turnpolicy = "minority"
424
+ ipolicy = policies[turnpolicy]
425
+
426
+ if turdsize is None:
427
+ turdsize = 2
428
+ if alphamax is None:
429
+ alphamax = 1
430
+ if opticurve is None:
431
+ opticurve = True
432
+ if opttolerance is None:
433
+ opttolerance = 0.2
434
+ pathcolor = Color("blue") if color is None else color
435
+ if invert is None:
436
+ invert = False
437
+ if blacklevel is None:
438
+ blacklevel = 0.5
439
+ if offset is None:
440
+ offset = self.length("5mm")
441
+ else:
442
+ offset = self.length(offset)
443
+ if steps is None or steps < 1:
444
+ steps = 1
445
+ if outer is None:
446
+ outer = False
447
+ outputdata = []
448
+ mydata = []
449
+ for node in data:
450
+ if outer and hasattr(node, "fill"):
451
+ e = copy(node)
452
+ e.fill = Color("black")
453
+ if hasattr(e, "stroke"):
454
+ e.stroke = Color("black")
455
+ if hasattr(e, "stroke_width") and e.stroke_width == 0:
456
+ e.stroke_width = UNITS_PER_PIXEL
457
+ if hasattr(e, "fillrule"):
458
+ e.fillrule = 0
459
+ mydata.append(e)
460
+ else:
461
+ e = copy(node)
462
+ if hasattr(e, "stroke_width") and e.stroke_width == 0:
463
+ e.stroke_width = UNITS_PER_PIXEL
464
+ mydata.append(e)
465
+ if debug:
466
+ for node in mydata:
467
+ node.label = "Phase 0: Initial copy"
468
+ self.elem_branch.add_node(node)
469
+
470
+ ###############################################
471
+ # Phase 1: render and vectorize first outline
472
+ ###############################################
473
+ bounds = Node.union_bounds(mydata, attr="paint_bounds")
474
+ # bounds_regular = Node.union_bounds(data)
475
+ # for idx in range(4):
476
+ # print (f"Bounds[{idx}] = {bounds_regular[idx]:.2f} vs {bounds_regular[idx]:.2f}")
477
+ if bounds is None:
478
+ return
479
+ xmin, ymin, xmax, ymax = bounds
480
+ if isinf(xmin):
481
+ channel(_("No bounds for selected elements."))
482
+ return
483
+ width = xmax - xmin
484
+ height = ymax - ymin
485
+
486
+ dots_per_units = dpi / UNITS_PER_INCH
487
+ new_width = width * dots_per_units
488
+ new_height = height * dots_per_units
489
+ new_height = max(new_height, 1)
490
+ new_width = max(new_width, 1)
491
+ dpi = 500
492
+ try:
493
+ data_image = make_raster(
494
+ mydata,
495
+ bounds=bounds,
496
+ width=new_width,
497
+ height=new_height,
498
+ )
499
+ except Exception:
500
+ channel(_("Too much memory required."))
501
+ return
502
+
503
+ matrix = Matrix.scale(width / new_width, height / new_height)
504
+ matrix.post_translate(bounds[0], bounds[1])
505
+ image_node_1 = ImageNode(
506
+ image=data_image, matrix=matrix, dpi=dpi, label="Phase 1 render image"
507
+ )
508
+
509
+ path = make_vector(
510
+ data_image,
511
+ interpolationpolicy=ipolicy,
512
+ invert=invert,
513
+ turdsize=turdsize,
514
+ alphamax=alphamax,
515
+ opticurve=opticurve,
516
+ opttolerance=opttolerance,
517
+ color=color,
518
+ blacklevel=blacklevel,
519
+ )
520
+ matrix = Matrix.scale(width / new_width, height / new_height)
521
+ matrix.post_translate(bounds[0], bounds[1])
522
+ path.transform *= Matrix(matrix)
523
+ data_node = PathNode(
524
+ path=abs(path),
525
+ stroke_width=500,
526
+ stroke=Color("black"),
527
+ stroke_scaled=False,
528
+ fill=None,
529
+ # fillrule=Fillrule.FILLRULE_NONZERO,
530
+ linejoin=Linejoin.JOIN_ROUND,
531
+ label="Phase 1 Outline path",
532
+ )
533
+ data_node.fill = None
534
+ # If you want to debug the phases then uncomment the following lines to
535
+ # see the interim path and interim render image
536
+ if debug:
537
+ self.elem_branch.add_node(data_node)
538
+ self.elem_branch.add_node(image_node_1)
539
+
540
+ copy_data = [image_node_1, data_node]
541
+
542
+ ################################################################
543
+ # Phase 2: change outline witdh and render and vectorize again
544
+ ################################################################
545
+ for numidx in range(steps):
546
+ data_node.stroke_width += 2 * offset
547
+ data_node.set_dirty_bounds()
548
+ pb = data_node.paint_bounds
549
+ bounds = Node.union_bounds(copy_data, attr="paint_bounds")
550
+ # print (f"{pb} - {bounds}")
551
+ if bounds is None:
552
+ return
553
+ # bounds_regular = Node.union_bounds(copy_data)
554
+ # for idx in range(4):
555
+ # print (f"Bounds[{idx}] = {bounds_regular[idx]:.2f} vs {bounds[idx]:.2f}")
556
+ xmin, ymin, xmax, ymax = bounds
557
+ if isinf(xmin):
558
+ channel(_("No bounds for selected elements."))
559
+ return
560
+ width = xmax - xmin
561
+ height = ymax - ymin
562
+
563
+ dots_per_units = dpi / UNITS_PER_INCH
564
+ new_width = width * dots_per_units
565
+ new_height = height * dots_per_units
566
+ new_height = max(new_height, 1)
567
+ new_width = max(new_width, 1)
568
+ dpi = 500
569
+ try:
570
+ image_2 = make_raster(
571
+ copy_data,
572
+ bounds=bounds,
573
+ width=new_width,
574
+ height=new_height,
575
+ )
576
+ except Exception:
577
+ channel(_("Too much memory required."))
578
+ return
579
+ matrix = Matrix.scale(width / new_width, height / new_height)
580
+ matrix.post_translate(bounds[0], bounds[1])
581
+ image_node_2 = ImageNode(
582
+ image=image_2, matrix=matrix, dpi=dpi, label="Phase 2 render image"
583
+ )
584
+
585
+ path_2 = make_vector(
586
+ image_2,
587
+ interpolationpolicy=ipolicy,
588
+ invert=invert,
589
+ turdsize=turdsize,
590
+ alphamax=alphamax,
591
+ opticurve=opticurve,
592
+ opttolerance=opttolerance,
593
+ color=color,
594
+ blacklevel=blacklevel,
595
+ )
596
+ matrix = Matrix.scale(width / new_width, height / new_height)
597
+ matrix.post_translate(bounds[0], bounds[1])
598
+ path_2.transform *= Matrix(matrix)
599
+ # That's our final path (or is it? Depends on outer...)
600
+ path_final = path_2
601
+ data_node_2 = PathNode(
602
+ path=abs(path_2),
603
+ stroke_width=500,
604
+ stroke=Color("black"),
605
+ stroke_scaled=False,
606
+ fill=None,
607
+ # fillrule=Fillrule.FILLRULE_NONZERO,
608
+ linejoin=Linejoin.JOIN_ROUND,
609
+ label="Phase 2 Outline path",
610
+ )
611
+ data_node_2.fill = None
612
+
613
+ # If you want to debug the phases then uncomment the following line to
614
+ # see the interim image
615
+ if debug:
616
+ self.elem_branch.add_node(image_node_2)
617
+ self.elem_branch.add_node(data_node_2)
618
+ #######################################################
619
+ # Phase 3: render and vectorize last outline for outer
620
+ #######################################################
621
+ if outer:
622
+ # Generate the outline, break it into subpaths
623
+ copy_data = []
624
+ # Now break it into subpaths...
625
+ for pasp in path_final.as_subpaths():
626
+ subpath = Path(pasp)
627
+ data_node = PathNode(
628
+ path=abs(subpath),
629
+ stroke_width=500,
630
+ stroke=Color("black"),
631
+ stroke_scaled=False,
632
+ fill=Color("black"),
633
+ # fillrule=Fillrule.FILLRULE_NONZERO,
634
+ linejoin=Linejoin.JOIN_ROUND,
635
+ label="Phase 3 Outline subpath",
636
+ )
637
+ # This seems to be necessary to make sure the fill sticks
638
+ data_node.fill = Color("black")
639
+ copy_data.append(data_node)
640
+ # If you want to debug the phases then uncomment the following lines to
641
+ # see the interim path nodes
642
+ if debug:
643
+ self.elem_branch.add_node(data_node)
644
+
645
+ bounds = Node.union_bounds(copy_data, attr="paint_bounds")
646
+ # bounds_regular = Node.union_bounds(data)
647
+ # for idx in range(4):
648
+ # print (f"Bounds[{idx}] = {bounds_regular[idx]:.2f} vs {bounds_regular[idx]:.2f}")
649
+ if bounds is None:
650
+ return
651
+ xmin, ymin, xmax, ymax = bounds
652
+ if isinf(xmin):
653
+ channel(_("No bounds for selected elements."))
654
+ return
655
+ width = xmax - xmin
656
+ height = ymax - ymin
657
+
658
+ dots_per_units = dpi / UNITS_PER_INCH
659
+ new_width = width * dots_per_units
660
+ new_height = height * dots_per_units
661
+ new_height = max(new_height, 1)
662
+ new_width = max(new_width, 1)
663
+ dpi = 500
664
+ try:
665
+ data_image = make_raster(
666
+ copy_data,
667
+ bounds=bounds,
668
+ width=new_width,
669
+ height=new_height,
670
+ )
671
+ except Exception:
672
+ channel(_("Too much memory required."))
673
+ return
674
+ matrix = Matrix.scale(width / new_width, height / new_height)
675
+ matrix.post_translate(bounds[0], bounds[1])
676
+
677
+ path_final = make_vector(
678
+ data_image,
679
+ interpolationpolicy=ipolicy,
680
+ invert=invert,
681
+ turdsize=turdsize,
682
+ alphamax=alphamax,
683
+ opticurve=opticurve,
684
+ opttolerance=opttolerance,
685
+ color=color,
686
+ blacklevel=blacklevel,
687
+ )
688
+ matrix = Matrix.scale(width / new_width, height / new_height)
689
+ matrix.post_translate(bounds[0], bounds[1])
690
+ path_final.transform *= Matrix(matrix)
691
+
692
+ outline_node = self.elem_branch.add(
693
+ path=abs(path_final),
694
+ stroke_width=500,
695
+ stroke_scaled=False,
696
+ type="elem path",
697
+ fill=None,
698
+ stroke=pathcolor,
699
+ # fillrule=Fillrule.FILLRULE_NONZERO,
700
+ linejoin=Linejoin.JOIN_ROUND,
701
+ label=f"Outline path #{numidx}",
702
+ )
703
+ outline_node.fill = None
704
+ outputdata.append(outline_node)
705
+
706
+ # Newly created! Classification needed?
707
+ post.append(classify_new(outputdata))
708
+ self.signal("refresh_scene", "Scene")
709
+ kernel.busyinfo.end()
710
+ if len(outputdata) > 0:
711
+ self.signal("element_property_update", outputdata)
712
+ return "elements", outputdata
713
+
714
+ @self.console_argument("refid", type=str, help=_("The id of the keyhole element"))
715
+ @self.console_command(
716
+ "keyhole",
717
+ help=_("Set a path-like element as keyhole frame for selected images"),
718
+ input_type=(None, "elements"),
719
+ output_type="elements",
720
+ )
721
+ def keyhole_elements(command, channel, _, refid=None, nohide=None, data=None, post=None, **kwargs):
722
+ if data is None:
723
+ data = list(self.elems(emphasized=True))
724
+
725
+ if nohide is None:
726
+ nohide = False
727
+ if refid is None:
728
+ # We do look for the very first occurence of a path like object and take this...
729
+ for node in data:
730
+ if node.id is not None and node.type in ("elem path", "elem ellipse", "elem rect", "elem polyline"):
731
+ refid = node.id
732
+ break
733
+
734
+ if refid is None:
735
+ channel(_("You need to provide an ID of an element to act as a keyhole"))
736
+ return
737
+ refnode = self.find_node(refid)
738
+ if refnode is None:
739
+ channel(_("A node with such an ID couldn't be found"))
740
+ return
741
+ if not hasattr(refnode, "as_geometry"):
742
+ channel(_("This node can not act as a keyhole: {nodetype}").format(nodetype=refnode.type))
743
+ return
744
+ images = list ( (e for e in data if e.type == "elem image") )
745
+ if len(images) == 0:
746
+ channel(_("No images selected/provided"))
747
+ return
748
+
749
+ for node in images:
750
+ rid = node.keyhole_reference
751
+ if rid is not None:
752
+ self.deregister_keyhole(rid, node)
753
+ try:
754
+ self.register_keyhole(refnode, node)
755
+ except ValueError as e:
756
+ channel(f"Could not register keyhole: {e}")
757
+ return
758
+ self.process_keyhole_updates(None)
759
+ self.signal("refresh_scene", "Scene")
760
+ return "elements", images
761
+
762
+ @self.console_command(
763
+ "remove_keyhole",
764
+ help=_("Removes keyhole frame for selected images"),
765
+ input_type=(None, "elements"),
766
+ output_type="elements",
767
+ )
768
+ def remove_keyhole_elements(command, channel, _, refid=None, nohide=None, data=None, post=None, **kwargs):
769
+ if data is None:
770
+ data = list(self.elems(emphasized=True))
771
+
772
+ images = list ( (e for e in data if e.type == "elem image") )
773
+ if len(images) == 0:
774
+ channel(_("No images selected/provided"))
775
+ return
776
+ for node in images:
777
+ rid = node.keyhole_reference
778
+ if rid is not None:
779
+ self.deregister_keyhole(rid, node)
780
+ self.process_keyhole_updates(None)
781
+
782
+ self.signal("refresh_scene", "Scene")
783
+ return "elements", images
784
+
785
+ # --------------------------- END COMMANDS ------------------------------