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

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