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,1304 +1,1427 @@
1
- """
2
- The 'elements' service stores all the element types in a bootstrapped tree. Specific node types added to the tree become
3
- particular class types and the interactions between these types and functions applied are registered in the kernel.
4
-
5
- Types:
6
- * root: Root Tree element
7
- * branch ops: Operation Branch
8
- * branch elems: Elements Branch
9
- * branch reg: Regmark Branch
10
- * reference: Element below op branch which stores specific data.
11
- * op: LayerOperation within Operation Branch.
12
- * elem: Element with Element Branch or subgroup.
13
- * file: File Group within Elements Branch
14
- * group: Group type within Branch Elems or refelem.
15
- * cutcode: CutCode type within Operation Branch and Element Branch.
16
-
17
- rasternode: theoretical: would store all the refelems to be rastered. Such that we could store rasters in images.
18
-
19
- Tree Functions are to be stored: tree/command/type. These store many functions like the commands.
20
- """
21
- import ast
22
- from copy import copy
23
- from enum import IntEnum
24
- from time import time
25
-
26
-
27
- # LINEJOIN
28
- # Value arcs | bevel |miter | miter-clip | round
29
- # Default value miter
30
- class Linejoin(IntEnum):
31
- JOIN_ARCS = 0
32
- JOIN_BEVEL = 1
33
- JOIN_MITER = 2
34
- JOIN_MITER_CLIP = 3
35
- JOIN_ROUND = 4
36
-
37
-
38
- # LINECAP
39
- # Value butt | round | square
40
- # Default value butt
41
- class Linecap(IntEnum):
42
- CAP_BUTT = 0
43
- CAP_ROUND = 1
44
- CAP_SQUARE = 2
45
-
46
-
47
- # FILL-RULE
48
- # Value nonzero | evenodd
49
- # Default value nonzero
50
- class Fillrule(IntEnum):
51
- FILLRULE_NONZERO = 0
52
- FILLRULE_EVENODD = 1
53
-
54
-
55
- class Node:
56
- """
57
- Nodes are elements within the tree which stores most of the objects in Elements.
58
-
59
- All nodes have children, root, parent, and reference links. The children are subnodes,
60
- the root points to the tree root, the parent points to the immediate parent, and references
61
- refers to nodes that point to this node type.
62
-
63
- All nodes have type, id, label, and lock values.
64
-
65
- Type is a string value of the given node type and is used to delineate nodes.
66
- Label is a string value that will often describe the node.
67
- `Id` is a string value, during saving, we make sure this is a unique id.
68
-
69
-
70
- Node bounds exist, but not all nodes are have geometric bounds.
71
- Node paint_bounds exists, not all nodes have painted area bounds.
72
-
73
- Nodes can be emphasized. This is selecting the given node.
74
- Nodes can be highlighted.
75
- Nodes can be targeted.
76
- """
77
-
78
- def __init__(self, *args, **kwargs):
79
- self.type = None
80
- self.id = None
81
- self.label = None
82
- self.lock = False
83
- self._can_emphasize = True
84
- self._can_highlight = True
85
- self._can_target = True
86
- self._can_move = True
87
- self._can_scale = True
88
- self._can_rotate = True
89
- self._can_skew = True
90
- self._can_modify = True
91
- self._can_alter = True
92
- self._can_update = True
93
- self._can_remove = True
94
- self._is_visible = True
95
- for k, v in kwargs.items():
96
- if k.startswith("_"):
97
- continue
98
- if isinstance(v, str) and k not in ("text", "id", "label"):
99
- try:
100
- v = ast.literal_eval(v)
101
- except (ValueError, SyntaxError):
102
- pass
103
- try:
104
- setattr(self, k, v)
105
- except AttributeError:
106
- # If this is already an attribute, just add it to the node dict.
107
- self.__dict__[k] = v
108
-
109
- self._children = list()
110
- self._root = None
111
- self._parent = None
112
- self._references = list()
113
- self._formatter = "{element_type}:{id}"
114
-
115
- self._points = list()
116
- self._points_dirty = True
117
-
118
- self._selected = False
119
- self._emphasized = False
120
- self._emphasized_time = None
121
- self._highlighted = False
122
- self._target = False
123
-
124
- self._opened = False
125
-
126
- self._bounds = None
127
- self._bounds_dirty = True
128
-
129
- self._paint_bounds = None
130
- self._paint_bounds_dirty = True
131
-
132
- self._item = None
133
- self._cache = None
134
- super().__init__()
135
-
136
- def __repr__(self):
137
- return f"{self.__class__.__name__}('{self.type}', {str(self._parent)})"
138
-
139
- def __copy__(self):
140
- return self.__class__(**self.node_dict)
141
-
142
- def __str__(self):
143
- text = self._formatter
144
- if text is None:
145
- text = "{element_type}"
146
- default_map = self.default_map()
147
- try:
148
- return text.format_map(default_map)
149
- except KeyError as e:
150
- raise KeyError(
151
- f"mapping '{text}' did not contain a required key in {default_map} for {self.__class__}"
152
- ) from e
153
-
154
- def __eq__(self, other):
155
- return other is self
156
-
157
- def __hash__(self):
158
- return id(self)
159
-
160
- @property
161
- def node_dict(self):
162
- nd = dict()
163
- for k, v in self.__dict__.items():
164
- if k.startswith("_"):
165
- continue
166
- if k == "type":
167
- continue
168
- nd[k] = v
169
- return nd
170
-
171
- @property
172
- def children(self):
173
- return self._children
174
-
175
- @property
176
- def references(self):
177
- return self._references
178
-
179
- @property
180
- def targeted(self):
181
- return self._target
182
-
183
- @targeted.setter
184
- def targeted(self, value):
185
- self._target = value
186
- self.notify_targeted(self)
187
-
188
- @property
189
- def highlighted(self):
190
- return self._highlighted
191
-
192
- @highlighted.setter
193
- def highlighted(self, value):
194
- self._highlighted = value
195
- self.notify_highlighted(self)
196
-
197
- @property
198
- def is_visible(self):
199
- result = True
200
- # is it an operation?
201
- if hasattr(self, "output"):
202
- if self.output:
203
- return True
204
- else:
205
- return self._is_visible
206
- if hasattr(self, "references"):
207
- valid = False
208
- flag = False
209
- for n in self.references:
210
- if hasattr(n.parent, "output"):
211
- valid = True
212
- if n.parent.output is None or n.parent.output:
213
- flag = True
214
- break
215
- if n.parent.is_visible:
216
- flag = True
217
- break
218
- # If there aren't any references then it is visible by default
219
- if valid:
220
- result = flag
221
- return result
222
-
223
- @is_visible.setter
224
- def is_visible(self, value):
225
- # is it an operation?
226
- if hasattr(self, "output"):
227
- if self.output:
228
- value = True
229
- else:
230
- value = True
231
- self._is_visible = value
232
-
233
- @property
234
- def emphasized(self):
235
- if self.is_visible:
236
- result = self._emphasized
237
- else:
238
- result = False
239
- return result
240
-
241
- @emphasized.setter
242
- def emphasized(self, value):
243
- if not self.is_visible:
244
- value = False
245
- if value != self._emphasized:
246
- self._emphasized = value
247
- self._emphasized_time = time() if value else None
248
- self.notify_emphasized(self)
249
-
250
- @property
251
- def emphasized_time(self):
252
- # we intentionally reduce the resolution to 1/100 sec.
253
- # to allow simultaneous assignments to return the same delta
254
- factor = 100
255
- if self._emphasized_time is None:
256
- # Insanely high
257
- result = float("inf")
258
- else:
259
- result = self._emphasized_time
260
- result = round(result * factor) / factor
261
- return result
262
-
263
- def emphasized_since(self, reftime=None, fullres=False):
264
- # we intentionally reduce the resolution to 1/100 sec.
265
- # to allow simultaneous assignments to return the same delta
266
- factor = 100
267
- if reftime is None:
268
- reftime = time()
269
- if self._emphasized_time is None:
270
- delta = 0
271
- else:
272
- delta = reftime - self._emphasized_time
273
- if not fullres:
274
- delta = round(delta * factor) / factor
275
- return delta
276
-
277
- @property
278
- def selected(self):
279
- return self._selected
280
-
281
- @selected.setter
282
- def selected(self, value):
283
- self._selected = value
284
- self.notify_selected(self)
285
-
286
- @property
287
- def parent(self):
288
- return self._parent
289
-
290
- @property
291
- def root(self):
292
- return self._root
293
-
294
- @property
295
- def can_emphasize(self):
296
- return self._can_emphasize
297
-
298
- @property
299
- def can_highlight(self):
300
- return self._can_highlight
301
-
302
- @property
303
- def can_target(self):
304
- return self._can_target
305
-
306
- def can_move(self, optional_permission=False):
307
- if not self._can_move:
308
- return False
309
- if optional_permission:
310
- return True
311
- return not self.lock
312
-
313
- @property
314
- def can_scale(self):
315
- return self._can_scale and not self.lock
316
-
317
- @property
318
- def can_rotate(self):
319
- return self._can_rotate and not self.lock
320
-
321
- @property
322
- def can_skew(self):
323
- return self._can_skew and not self.lock
324
-
325
- @property
326
- def can_modify(self):
327
- return self._can_modify and not self.lock
328
-
329
- @property
330
- def can_alter(self):
331
- return self._can_alter and not self.lock
332
-
333
- @property
334
- def can_update(self):
335
- return self._can_update and not self.lock
336
-
337
- @property
338
- def can_remove(self):
339
- return self._can_remove and not self.lock
340
-
341
- @property
342
- def bounds(self):
343
- if not self._bounds_dirty:
344
- return self._bounds
345
-
346
- try:
347
- self._bounds = self.bbox(with_stroke=False)
348
- except AttributeError:
349
- self._bounds = None
350
-
351
- if self._children:
352
- self._bounds = Node.union_bounds(self._children, bounds=self._bounds)
353
- self._bounds_dirty = False
354
- return self._bounds
355
-
356
- @property
357
- def paint_bounds(self):
358
- # Make sure that bounds is valid
359
- if not self._paint_bounds_dirty:
360
- return self._paint_bounds
361
-
362
- flag = True
363
- if hasattr(self, "stroke"):
364
- if self.stroke is None or self.stroke.argb is None:
365
- flag = False
366
- try:
367
- self._paint_bounds = self.bbox(with_stroke=flag)
368
- except AttributeError:
369
- self._paint_bounds = None
370
-
371
- if self._children:
372
- self._paint_bounds = Node.union_bounds(
373
- self._children, bounds=self._paint_bounds, attr="paint_bounds"
374
- )
375
- self._paint_bounds_dirty = False
376
- return self._paint_bounds
377
-
378
- def set_dirty_bounds(self):
379
- self._paint_bounds_dirty = True
380
- self._bounds_dirty = True
381
- self._points_dirty = True
382
-
383
- @property
384
- def formatter(self):
385
- return self._formatter
386
-
387
- @formatter.setter
388
- def formatter(self, formatter):
389
- self._formatter = formatter
390
-
391
- @property
392
- def points(self):
393
- """
394
- Returns the node points values
395
-
396
- @return: validated node point values, this is a list of lists of 3 elements.
397
- """
398
- if self._points_dirty:
399
- self.revalidate_points()
400
- self._points_dirty = False
401
- return self._points
402
-
403
- def restore_tree(self, tree_data):
404
- self._children.clear()
405
- self._children.extend(tree_data)
406
- self._validate_tree()
407
-
408
- def _validate_tree(self):
409
- for c in self._children:
410
- assert c._parent is self
411
- assert c._root is self._root
412
- assert c in c._parent._children
413
- for q in c._references:
414
- assert q.node is c
415
- if c.type == "reference":
416
- assert c in c.node._references
417
- c._validate_tree()
418
-
419
- def _build_copy_nodes(self, links=None):
420
- """
421
- Creates a copy of each node, linked to the ID of the original node. This will create
422
- a map between id of original node and copy node. Without any structure. The original
423
- root will link to `None` since root copies are in-effective.
424
-
425
- @param links:
426
- @return:
427
- """
428
- if links is None:
429
- links = {id(self): (self, None)}
430
- for c in self._children:
431
- c._build_copy_nodes(links=links)
432
- node_copy = copy(c)
433
- node_copy._root = self._root
434
- links[id(c)] = (c, node_copy)
435
- return links
436
-
437
- def backup_tree(self):
438
- """
439
- Creates structured copy of the branches of the tree at the current node.
440
-
441
- This creates copied nodes, relinks the structure and returns branches of
442
- the current node.
443
- @return:
444
- """
445
- links = self._build_copy_nodes()
446
-
447
- # Rebuild structure.
448
- for uid, n in links.items():
449
- node, node_copy = n
450
- if node._parent is None:
451
- # Root.
452
- continue
453
- # Find copy-parent of copy-node and link.
454
- original_parent, copied_parent = links[id(node._parent)]
455
- if copied_parent is None:
456
- # copy_parent should have been copied root, but roots don't copy
457
- node_copy._parent = self._root
458
- continue
459
- node_copy._parent = copied_parent
460
- copied_parent._children.append(node_copy)
461
- if node.type == "reference":
462
- original_referenced, copied_referenced = links[id(node.node)]
463
- node_copy.node = copied_referenced
464
- copied_referenced._references.append(node_copy)
465
- branches = [links[id(c)][1] for c in self._children]
466
- return branches
467
-
468
- def create_label(self, text=None):
469
- if text is None:
470
- text = "{element_type}:{id}"
471
- # Just for the optical impression (who understands what a "Rect: None" means),
472
- # lets replace some of the more obvious ones...
473
- mymap = self.default_map()
474
- for key in mymap:
475
- if hasattr(self, key) and mymap[key] == "None":
476
- if getattr(self, key) is None:
477
- mymap[key] = "-"
478
- # slist = text.split("{")
479
- # for item in slist:
480
- # idx = item.find("}")
481
- # if idx>0:
482
- # sitem = item[0:idx]
483
- # else:
484
- # sitem = item
485
- # try:
486
- # dummy = mymap[sitem]
487
- # except KeyError:
488
- # # Addit
489
- # mymap[sitem] = "??ERR??"
490
- try:
491
- result = text.format_map(mymap)
492
- except ValueError:
493
- result = "<invalid pattern>"
494
- return result
495
-
496
- def default_map(self, default_map=None):
497
- if default_map is None:
498
- default_map = dict()
499
- default_map["id"] = str(self.id) if self.id is not None else "-"
500
- default_map["label"] = self.label if self.label is not None else ""
501
- default_map["desc"] = (
502
- self.label
503
- if self.label is not None
504
- else str(self.id)
505
- if self.id is not None
506
- else "-"
507
- )
508
- default_map["element_type"] = "Node"
509
- default_map["node_type"] = self.type
510
- return default_map
511
-
512
- def valid_node_for_reference(self, node):
513
- return True
514
-
515
- def copy_children_as_references(self, obj):
516
- """
517
- Copy the children of the given object as direct references to those children.
518
- @param obj:
519
- @return:
520
- """
521
- for element in obj.children:
522
- self.add_reference(element)
523
-
524
- def copy_with_reified_tree(self):
525
- """
526
- Make a copy of the current node, and a copy of the sub-nodes dereferencing any reference nodes
527
- @return:
528
- """
529
- copy_c = copy(self)
530
- copy_c.copy_children_as_real(self)
531
- return copy_c
532
-
533
- def copy_children_as_real(self, copy_node):
534
- """
535
- Copy the children of copy_node to the current node, dereferencing any reference nodes.
536
- @param copy_node:
537
- @return:
538
- """
539
- for child in copy_node.children:
540
- child = child
541
- if child.type == "reference":
542
- child = child.node
543
- copy_child = copy(child)
544
- self.add_node(copy_child)
545
- copy_child.copy_children_as_real(child)
546
-
547
- def is_draggable(self):
548
- return True
549
-
550
- def drop(self, drag_node, modify=True):
551
- """
552
- Process drag and drop node values for tree reordering.
553
-
554
- @param drag_node:
555
- @param modify:
556
- @return:
557
- """
558
- return False
559
-
560
- def reverse(self):
561
- self._children.reverse()
562
- self.notify_reorder()
563
-
564
- def load(self, settings, section):
565
- """
566
- Default loading will read the persistence object, such that any values found in the given section of the
567
- settings file. Will load parse the file to the correct type and set the attributes on this node.
568
-
569
- @param settings:
570
- @param section:
571
- @return:
572
- """
573
- settings.read_persistent_object(section, self)
574
-
575
- def save(self, settings, section):
576
- """
577
- The default node saving to a settings will write the persistence dictionary of the instance dictionary. This
578
- will save to that section any non `_` attributes.
579
-
580
- @param settings:
581
- @param section:
582
- @return:
583
- """
584
- settings.write_persistent_dict(section, self.__dict__)
585
-
586
- def revalidate_points(self):
587
- """
588
- Ensure the points values for the node are valid with regard to the node's
589
- current state. By default, this calls bounds but valid nodes can be overloaded
590
- based on specific node type.
591
-
592
- Should be overloaded by subclasses.
593
-
594
- @return:
595
- """
596
- bounds = self.bounds
597
- if bounds is None:
598
- return
599
- if len(self._points) < 5:
600
- self._points.extend([None] * (5 - len(self._points)))
601
- self._points[0] = [bounds[0], bounds[1], "bounds top_left"]
602
- self._points[1] = [bounds[2], bounds[1], "bounds top_right"]
603
- self._points[2] = [bounds[0], bounds[3], "bounds bottom_left"]
604
- self._points[3] = [bounds[2], bounds[3], "bounds bottom_right"]
605
- cx = (bounds[0] + bounds[2]) / 2
606
- cy = (bounds[1] + bounds[3]) / 2
607
- self._points[4] = [cx, cy, "bounds center_center"]
608
-
609
- def update_point(self, index, point):
610
- """
611
- Attempt to update a node value, located at a specific index with the new
612
- point provided.
613
-
614
- Should be overloaded by subclasses.
615
-
616
- @param index: updating point index
617
- @param point: Point to be updated
618
- @return: Whether update was successful
619
- """
620
- return False
621
-
622
- def add_point(self, point, index=None):
623
- """
624
- Attempts to add a point into node.points.
625
-
626
- Should be overloaded by subclasses.
627
-
628
- @param point: point to be added
629
- @param index: index for point insertion
630
- @return: Whether append was successful
631
- """
632
- # return self._insert_point(point, index)
633
- return False
634
-
635
- def _insert_point(self, point, index=None):
636
- """
637
- Default implementation of inserting point into points.
638
-
639
- @param point:
640
- @param index:
641
- @return:
642
- """
643
- x = None
644
- y = None
645
- point_type = None
646
- try:
647
- x = point[0]
648
- y = point[1]
649
- point_type = point[3]
650
- except IndexError:
651
- pass
652
- if index is None:
653
- self._points.append([x, y, point_type])
654
- else:
655
- try:
656
- self._points.insert(index, [x, y, point_type])
657
- except IndexError:
658
- return False
659
- return True
660
-
661
- def notify_created(self, node=None, **kwargs):
662
- if self._parent is not None:
663
- if node is None:
664
- node = self
665
- self._parent.notify_created(node=node, **kwargs)
666
-
667
- def notify_destroyed(self, node=None, **kwargs):
668
- if self._parent is not None:
669
- if node is None:
670
- node = self
671
- self._parent.notify_destroyed(node=node, **kwargs)
672
-
673
- def notify_attached(self, node=None, **kwargs):
674
- if self._parent is not None:
675
- if node is None:
676
- node = self
677
- self._parent.notify_attached(node=node, **kwargs)
678
-
679
- def notify_detached(self, node=None, **kwargs):
680
- if self._parent is not None:
681
- if node is None:
682
- node = self
683
- self._parent.notify_detached(node=node, **kwargs)
684
-
685
- def notify_changed(self, node, **kwargs):
686
- if self._parent is not None:
687
- if node is None:
688
- node = self
689
- self._parent.notify_changed(node=node, **kwargs)
690
-
691
- def notify_selected(self, node=None, **kwargs):
692
- if self._parent is not None:
693
- if node is None:
694
- node = self
695
- self._parent.notify_selected(node=node, **kwargs)
696
-
697
- def notify_emphasized(self, node=None, **kwargs):
698
- if self._parent is not None:
699
- if node is None:
700
- node = self
701
- self._parent.notify_emphasized(node=node, **kwargs)
702
-
703
- def notify_targeted(self, node=None, **kwargs):
704
- if self._parent is not None:
705
- if node is None:
706
- node = self
707
- self._parent.notify_targeted(node=node, **kwargs)
708
-
709
- def notify_highlighted(self, node=None, **kwargs):
710
- if self._root is not None:
711
- if node is None:
712
- node = self
713
- self._root.notify_highlighted(node=node, **kwargs)
714
-
715
- def notify_modified(self, node=None, **kwargs):
716
- if self._parent is not None:
717
- if node is None:
718
- node = self
719
- self._parent.notify_modified(node=node, **kwargs)
720
-
721
- def notify_translated(self, node=None, dx=0, dy=0, **kwargs):
722
- if self._parent is not None:
723
- if node is None:
724
- node = self
725
- self._parent.notify_translated(node=node, dx=dx, dy=dy, **kwargs)
726
-
727
- def notify_scaled(self, node=None, sx=1, sy=1, ox=0, oy=0, **kwargs):
728
- if self._parent is not None:
729
- if node is None:
730
- node = self
731
- self._parent.notify_scaled(node=node, sx=sx, sy=sy, ox=ox, oy=oy, **kwargs)
732
-
733
- def notify_altered(self, node=None, **kwargs):
734
- if self._parent is not None:
735
- if node is None:
736
- node = self
737
- self._parent.notify_altered(node=node, **kwargs)
738
-
739
- def notify_expand(self, node=None, **kwargs):
740
- if self._parent is not None:
741
- if node is None:
742
- node = self
743
- self._parent.notify_expand(node=node, **kwargs)
744
-
745
- def notify_collapse(self, node=None, **kwargs):
746
- if self._parent is not None:
747
- if node is None:
748
- node = self
749
- self._parent.notify_collapse(node=node, **kwargs)
750
-
751
- def notify_reorder(self, node=None, **kwargs):
752
- if self._parent is not None:
753
- if node is None:
754
- node = self
755
- self._parent.notify_reorder(node=node, **kwargs)
756
-
757
- def notify_update(self, node=None, **kwargs):
758
- if self._parent is not None:
759
- if node is None:
760
- node = self
761
- self._parent.notify_update(node=node, **kwargs)
762
-
763
- def notify_focus(self, node=None, **kwargs):
764
- if self._parent is not None:
765
- if node is None:
766
- node = self
767
- self._parent.notify_focus(node=node, **kwargs)
768
-
769
- def focus(self):
770
- self.notify_focus(self)
771
-
772
- def invalidated_node(self):
773
- """
774
- Invalidation of the individual node.
775
- """
776
- self.set_dirty_bounds()
777
- self._bounds = None
778
- self._paint_bounds = None
779
-
780
- def invalidated(self):
781
- """
782
- Invalidation occurs when the underlying data is altered or modified. This propagates up from children to
783
- invalidate the entire parental line.
784
- """
785
- self.invalidated_node()
786
- if self._parent is not None:
787
- self._parent.invalidated()
788
-
789
- def updated(self):
790
- """
791
- The nodes display information may have changed but nothing about the matrix or the internal data is altered.
792
- """
793
- self.notify_update(self)
794
-
795
- def modified(self):
796
- """
797
- The matrix transformation was changed. The object is shaped
798
- differently but fundamentally the same structure of data.
799
- """
800
- self.invalidated()
801
- self.notify_modified(self)
802
-
803
- def translated(self, dx, dy):
804
- """
805
- This is a special case of the modified call, we are translating
806
- the node without fundamentally altering its properties
807
- """
808
- if self._bounds_dirty or self._bounds is None:
809
- # A pity but we need proper data
810
- self.modified()
811
- return
812
- self._bounds = [
813
- self._bounds[0] + dx,
814
- self._bounds[1] + dy,
815
- self._bounds[2] + dx,
816
- self._bounds[3] + dy,
817
- ]
818
- if self._paint_bounds_dirty or self._paint_bounds is None:
819
- # Nothing we can do...
820
- pass
821
- else:
822
- self._paint_bounds = [
823
- self._paint_bounds[0] + dx,
824
- self._paint_bounds[1] + dy,
825
- self._paint_bounds[2] + dx,
826
- self._paint_bounds[3] + dy,
827
- ]
828
- self._points_dirty = True
829
- # No need to translate it as we will apply the matrix later
830
- # self.translate_functional_parameter(dx, dy)
831
-
832
- # if self._points_dirty:
833
- # self.revalidate_points()
834
- # else:
835
- # for pt in self._points:
836
- # pt[0] += dx
837
- # pt[1] += dy
838
-
839
- self.notify_translated(self, dx=dx, dy=dy)
840
-
841
- def scaled(self, sx, sy, ox, oy):
842
- """
843
- This is a special case of the modified call, we are scaling
844
- the node without fundamentally altering its properties
845
- """
846
-
847
- def apply_it(box):
848
- x0, y0, x1, y1 = box
849
- if sx != 1.0:
850
- d1 = x0 - ox
851
- d2 = x1 - ox
852
- x0 = ox + sx * d1
853
- x1 = ox + sx * d2
854
- if sy != 1.0:
855
- d1 = y0 - oy
856
- d2 = y1 - oy
857
- y0 = oy + sy * d1
858
- y1 = oy + sy * d2
859
- return min(x0, x1), min(y0, y1), max(x0, x1), max(y0, y1)
860
-
861
- if self._bounds_dirty or self._bounds is None:
862
- # A pity but we need proper data
863
- self.modified()
864
- return
865
- self._bounds = apply_it(self._bounds)
866
- # self.scale_functional_parameter(sx, sy, ox, oy)
867
- # This may not really correct, we need the
868
- # implied stroke_width to add, so the inherited
869
- # element classes will need to overload it
870
- if self._paint_bounds is not None:
871
- self._paint_bounds = apply_it(self._paint_bounds)
872
- self._points_dirty = True
873
- self.notify_scaled(self, sx=sx, sy=sy, ox=ox, oy=oy)
874
-
875
- def altered(self):
876
- """
877
- The data structure was changed. Any assumptions about what this object is/was are void.
878
- """
879
- try:
880
- self._cache.UnGetNativePath(self._cache.NativePath)
881
- except AttributeError:
882
- pass
883
- try:
884
- del self._cache
885
- del self._cache_matrix
886
- except AttributeError:
887
- pass
888
- self._cache = None
889
- self.invalidated()
890
- self.notify_altered(self)
891
-
892
- def unregister_object(self):
893
- try:
894
- self._cache.UngetNativePath(self._cache.NativePath)
895
- except AttributeError:
896
- pass
897
- try:
898
- del self._cache
899
- del self._cache_matrix
900
- except AttributeError:
901
- pass
902
-
903
- def unregister(self):
904
- self.unregister_object()
905
- try:
906
- self.targeted = False
907
- self.emphasized = False
908
- self.highlighted = False
909
- except AttributeError:
910
- pass
911
-
912
- def add_reference(self, node=None, pos=None, **kwargs):
913
- """
914
- Add a new node bound to the data_object of the type to the current node.
915
- If the data_object itself is a node already it is merely attached.
916
-
917
- @param node:
918
- @param pos:
919
- @return:
920
- """
921
- if node is None:
922
- return
923
- if not self.valid_node_for_reference(node):
924
- # We could raise a ValueError but that will break things...
925
- return
926
- ref = self.add(node=node, type="reference", pos=pos, **kwargs)
927
- node._references.append(ref)
928
-
929
- def add_node(self, node, pos=None):
930
- """
931
- Attach an already created node to the tree.
932
-
933
- Requires that this node be validated to avoid loops.
934
-
935
- @param node:
936
- @param pos:
937
- @return:
938
- """
939
- if node is None:
940
- # This should not happen and is a sign that something is amiss,
941
- # so we inform at least abount it
942
- print("Tried to add an invalid node...")
943
- return
944
- if node._parent is not None:
945
- raise ValueError("Cannot reparent node on add.")
946
- node._parent = self
947
- node.set_root(self._root)
948
- if pos is None:
949
- self._children.append(node)
950
- else:
951
- self._children.insert(pos, node)
952
- node.notify_attached(node, parent=self, pos=pos)
953
- return node
954
-
955
- def create(self, type, **kwargs):
956
- """
957
- Create node of type with attributes via node bootstrapping. Apply node defaults to values with defaults.
958
-
959
- @param type:
960
- @param kwargs:
961
- @return:
962
- """
963
- from .bootstrap import bootstrap, defaults
964
-
965
- node_class = bootstrap.get(type, None)
966
- if node_class is None:
967
- raise ValueError("Attempted to create unbootstrapped node")
968
- node_defaults = defaults.get(type, {})
969
- nd = dict(node_defaults)
970
- nd.update(kwargs)
971
- node = node_class(**nd)
972
- if self._root is not None:
973
- self._root.notify_created(node)
974
- return node
975
-
976
- def add(self, type=None, pos=None, **kwargs):
977
- """
978
- Add a new node bound to the data_object of the type to the current node.
979
- If the data_object itself is a node already it is merely attached.
980
-
981
- @param type: Node type to be bootstrapped
982
- @param pos: Position within current node to add this node
983
- @return:
984
- """
985
- node = self.create(type=type, **kwargs)
986
- if node is not None:
987
- self.add_node(node, pos=pos)
988
- else:
989
- print(f"Did not produce a valid node for type '{type}'")
990
- return node
991
-
992
- def set_root(self, root):
993
- """
994
- Set the root for this and all descendant to the provided root
995
-
996
- @param root:
997
- @return:
998
- """
999
- self._root = root
1000
- for c in self._children:
1001
- c.set_root(root)
1002
-
1003
- def _flatten(self, node):
1004
- """
1005
- Yield this node and all descendants in a flat generation.
1006
-
1007
- @param node: starting node
1008
- @return:
1009
- """
1010
- yield node
1011
- yield from self._flatten_children(node)
1012
-
1013
- def _flatten_children(self, node):
1014
- """
1015
- Yield all descendants in a flat generation.
1016
-
1017
- @param node: starting node
1018
- @return:
1019
- """
1020
- for child in node.children:
1021
- yield child
1022
- yield from self._flatten_children(child)
1023
-
1024
- def flat(
1025
- self,
1026
- types=None,
1027
- cascade=True,
1028
- depth=None,
1029
- selected=None,
1030
- emphasized=None,
1031
- targeted=None,
1032
- highlighted=None,
1033
- lock=None,
1034
- ):
1035
- """
1036
- Returned flat list of matching nodes. If cascade is set then any matching group will give all the descendants
1037
- of the given type, even if those descendants are beyond the depth limit. The sub-elements do not need to match
1038
- the criteria with respect to either the depth or the emphases.
1039
-
1040
- @param types: types of nodes permitted to be returned
1041
- @param cascade: cascade all subitems if a group matches the criteria.
1042
- @param depth: depth to search within the tree.
1043
- @param selected: match only selected nodes
1044
- @param emphasized: match only emphasized nodes.
1045
- @param targeted: match only targeted nodes
1046
- @param highlighted: match only highlighted nodes
1047
- @return:
1048
- """
1049
- node = self
1050
- if (
1051
- (targeted is None or targeted == node.targeted)
1052
- and (emphasized is None or emphasized == node.emphasized)
1053
- and (selected is None or selected == node.selected)
1054
- and (highlighted is None or highlighted == node.highlighted)
1055
- and (lock is None or lock == node.lock)
1056
- ):
1057
- # Matches the emphases.
1058
- if cascade:
1059
- # Give every type-matched descendant.
1060
- for c in self._flatten(node):
1061
- if types is None or c.type in types:
1062
- yield c
1063
- # Do not recurse further. This node is end node.
1064
- return
1065
- else:
1066
- if types is None or node.type in types:
1067
- yield node
1068
- if depth is not None:
1069
- if depth <= 0:
1070
- # Depth limit reached. Do not evaluate children.
1071
- return
1072
- depth -= 1
1073
- # Check all children.
1074
- for c in node.children:
1075
- yield from c.flat(
1076
- types, cascade, depth, selected, emphasized, targeted, highlighted, lock
1077
- )
1078
-
1079
- def count_children(self):
1080
- return len(self._children)
1081
-
1082
- def append_child(self, new_child):
1083
- """
1084
- Moves the new_child node as the last child of the current node.
1085
- If the node exists elsewhere in the tree it will be removed from that location.
1086
-
1087
- """
1088
- new_parent = self
1089
- source_siblings = new_child.parent.children
1090
- destination_siblings = new_parent.children
1091
-
1092
- source_siblings.remove(new_child) # Remove child
1093
- new_child.notify_detached(new_child)
1094
-
1095
- destination_siblings.append(new_child) # Add child.
1096
- new_child._parent = new_parent
1097
- new_child.notify_attached(new_child)
1098
-
1099
- def insert_sibling(self, new_sibling):
1100
- """
1101
- Add the new_sibling node next to the current node.
1102
- If the node exists elsewhere in the tree it will be removed from that location.
1103
- """
1104
- reference_sibling = self
1105
- source_siblings = new_sibling.parent.children
1106
- destination_siblings = reference_sibling.parent.children
1107
-
1108
- reference_position = destination_siblings.index(reference_sibling)
1109
-
1110
- source_siblings.remove(new_sibling)
1111
-
1112
- new_sibling.notify_detached(new_sibling)
1113
- destination_siblings.insert(reference_position, new_sibling)
1114
- new_sibling._parent = reference_sibling._parent
1115
- new_sibling.notify_attached(new_sibling, pos=reference_position)
1116
-
1117
- def replace_node(self, keep_children=None, *args, **kwargs):
1118
- """
1119
- Replace this current node with a bootstrapped replacement node.
1120
- """
1121
- if keep_children is None:
1122
- keep_children = False
1123
- parent = self._parent
1124
- index = parent._children.index(self)
1125
- parent._children.remove(self)
1126
- self.notify_detached(self)
1127
- node = parent.add(*args, **kwargs, pos=index)
1128
- self.notify_destroyed()
1129
- for ref in list(self._references):
1130
- ref.remove_node()
1131
- if keep_children:
1132
- for ref in list(self._children):
1133
- node._children.append(ref)
1134
- ref._parent = node
1135
- # Don't call attach / detach, as the tree
1136
- # doesn't know about the new node yet...
1137
- self._item = None
1138
- self._parent = None
1139
- self._root = None
1140
- self.unregister()
1141
- return node
1142
-
1143
- def swap_node(self, node):
1144
- """
1145
- Swap nodes swaps the current node with the provided node in the other position in the same tree. All children
1146
- during a swap are kept in place structurally. This permits swapping nodes between two positions that may be
1147
- nested, without creating a loop.
1148
-
1149
- Special care is taken for both swaps being children of the same parent.
1150
-
1151
- @param node: Node already in the tree that should be swapped with the current node.
1152
- @return:
1153
- """
1154
- # Remove self from tree.
1155
- parent = self._parent
1156
- n_parent = node._parent
1157
-
1158
- index = parent._children.index(self)
1159
- n_index = n_parent._children.index(node)
1160
-
1161
- if index < n_index:
1162
- # N_index is greater.
1163
- del n_parent._children[n_index]
1164
- del parent._children[index]
1165
-
1166
- parent._children.insert(index, node)
1167
- n_parent._children.insert(n_index, self)
1168
- else:
1169
- # N_index is lesser, equal
1170
- del parent._children[index]
1171
- del n_parent._children[n_index]
1172
-
1173
- n_parent._children.insert(n_index, self)
1174
- parent._children.insert(index, node)
1175
-
1176
- node._parent = parent
1177
- self._parent = n_parent
1178
-
1179
- # Make a copy of children
1180
- n_children = list(node._children)
1181
- children = list(self._children)
1182
-
1183
- # Delete children.
1184
- node._children.clear()
1185
- self._children.clear()
1186
-
1187
- # Move children without call attach / detach.
1188
- node._children.extend(children)
1189
- self._children.extend(n_children)
1190
-
1191
- # Correct parent for all children.
1192
- for n in list(n_children):
1193
- n._parent = self
1194
- for n in list(children):
1195
- n._parent = node
1196
-
1197
- # self._root._validate_tree()
1198
- self._root.notify_reorder()
1199
-
1200
- def remove_node(self, children=True, references=True, fast=False, destroy=True):
1201
- """
1202
- Remove the current node from the tree.
1203
-
1204
- @param children: removes all the children of this node.
1205
- @param references: remove the references to this node.
1206
- @param fast: Do not send notifications of the detatches and destroys
1207
- @param destroy: Do not destroy the node.
1208
- @return:
1209
- """
1210
- if children:
1211
- self.remove_all_children(fast=fast)
1212
- if self._parent:
1213
- self._parent._children.remove(self)
1214
- self._parent.set_dirty_bounds()
1215
- if not fast:
1216
- self.notify_detached(self)
1217
- if destroy:
1218
- self.notify_destroyed(self)
1219
- if references:
1220
- for ref in list(self._references):
1221
- ref.remove_node(fast=fast)
1222
- self._item = None
1223
- self._parent = None
1224
- self._root = None
1225
- self.unregister()
1226
-
1227
- def remove_all_children(self, fast=False, destroy=True):
1228
- """
1229
- Recursively removes all children of the current node.
1230
- """
1231
- for child in list(self.children):
1232
- child.remove_all_children(fast=fast, destroy=destroy)
1233
- child.remove_node(fast=fast, destroy=destroy)
1234
-
1235
- def has_ancestor(self, type):
1236
- """
1237
- Return whether this node has an ancestor node that matches the given type, or matches the major type.
1238
-
1239
- @param type:
1240
- @return:
1241
- """
1242
- if self.parent is None:
1243
- return False
1244
-
1245
- if self.parent.type == type:
1246
- return True
1247
-
1248
- if " " not in type:
1249
- if self.parent.type.startswith(type):
1250
- return True
1251
-
1252
- return self.parent.has_ancestor(type=type)
1253
-
1254
- def get(self, type=None):
1255
- """
1256
- Recursive call for get to find first sub-nodes with the given type.
1257
- @param type:
1258
- @return:
1259
- """
1260
- if type is None or type == self.type:
1261
- return self
1262
- for n in self._children:
1263
- node = n.get(type)
1264
- if node is not None:
1265
- return node
1266
- return None
1267
-
1268
- def move(self, dest, pos=None):
1269
- self._parent.remove(self)
1270
- dest.insert_node(self, pos=pos)
1271
-
1272
- @staticmethod
1273
- def union_bounds(nodes, bounds=None, attr="bounds"):
1274
- """
1275
- Returns the union of the node list given, optionally unioned the given bounds value
1276
-
1277
- @return: union of all bounds within the iterable.
1278
- """
1279
- if bounds is None:
1280
- xmin = float("inf")
1281
- ymin = float("inf")
1282
- xmax = -xmin
1283
- ymax = -ymin
1284
- else:
1285
- xmin, ymin, xmax, ymax = bounds
1286
- for e in nodes:
1287
- if e.lock:
1288
- continue
1289
- box = getattr(e, attr, None)
1290
- if box is None:
1291
- continue
1292
- if box[0] < xmin:
1293
- xmin = box[0]
1294
- if box[2] > xmax:
1295
- xmax = box[2]
1296
- if box[1] < ymin:
1297
- ymin = box[1]
1298
- if box[3] > ymax:
1299
- ymax = box[3]
1300
- return xmin, ymin, xmax, ymax
1301
-
1302
- @property
1303
- def name(self):
1304
- return self.__str__()
1
+ """
2
+ The 'elements' service stores all the element types in a bootstrapped tree. Specific node types added to the tree become
3
+ particular class types and the interactions between these types and functions applied are registered in the kernel.
4
+
5
+ Types:
6
+ * root: Root Tree element
7
+ * branch ops: Operation Branch
8
+ * branch elems: Elements Branch
9
+ * branch reg: Regmark Branch
10
+ * reference: Element below op branch which stores specific data.
11
+ * op: LayerOperation within Operation Branch.
12
+ * elem: Element with Element Branch or subgroup.
13
+ * file: File Group within Elements Branch
14
+ * group: Group type within Branch Elems or refelem.
15
+ * cutcode: CutCode type within Operation Branch and Element Branch.
16
+
17
+ rasternode: theoretical: would store all the refelems to be rastered. Such that we could store rasters in images.
18
+
19
+ Tree Functions are to be stored: tree/command/type. These store many functions like the commands.
20
+ """
21
+
22
+ import ast
23
+ from copy import copy
24
+ from enum import IntEnum
25
+ from time import time
26
+
27
+
28
+ # LINEJOIN
29
+ # Value arcs | bevel |miter | miter-clip | round
30
+ # Default value miter
31
+ class Linejoin(IntEnum):
32
+ JOIN_ARCS = 0
33
+ JOIN_BEVEL = 1
34
+ JOIN_MITER = 2
35
+ JOIN_MITER_CLIP = 3
36
+ JOIN_ROUND = 4
37
+
38
+
39
+ # LINECAP
40
+ # Value butt | round | square
41
+ # Default value butt
42
+ class Linecap(IntEnum):
43
+ CAP_BUTT = 0
44
+ CAP_ROUND = 1
45
+ CAP_SQUARE = 2
46
+
47
+
48
+ # FILL-RULE
49
+ # Value nonzero | evenodd
50
+ # Default value nonzero
51
+ class Fillrule(IntEnum):
52
+ FILLRULE_NONZERO = 0
53
+ FILLRULE_EVENODD = 1
54
+
55
+
56
+ class Node:
57
+ """
58
+ Nodes are elements within the tree which stores most of the objects in Elements.
59
+
60
+ All nodes have children, root, parent, and reference links. The children are subnodes,
61
+ the root points to the tree root, the parent points to the immediate parent, and references
62
+ refers to nodes that point to this node type.
63
+
64
+ All nodes have type, id, label, and lock values.
65
+
66
+ Type is a string value of the given node type and is used to delineate nodes.
67
+ Label is a string value that will often describe the node.
68
+ `Id` is a string value, during saving, we make sure this is a unique id.
69
+
70
+
71
+ Node bounds exist, but not all nodes are have geometric bounds.
72
+ Node paint_bounds exists, not all nodes have painted area bounds.
73
+
74
+ Nodes can be emphasized. This is selecting the given node.
75
+ Nodes can be highlighted.
76
+ Nodes can be targeted.
77
+ """
78
+
79
+ def __init__(self, *args, **kwargs):
80
+ self.type = None
81
+ self.id = None
82
+ self.label = None
83
+ self.lock = False
84
+ self._can_emphasize = True
85
+ self._can_highlight = True
86
+ self._can_target = True
87
+ self._can_move = True
88
+ self._can_scale = True
89
+ self._can_rotate = True
90
+ self._can_skew = True
91
+ self._can_modify = True
92
+ self._can_alter = True
93
+ self._can_update = True
94
+ self._can_remove = True
95
+ self._is_visible = True
96
+ self._expanded = False
97
+ self._children = list()
98
+ self._root = None
99
+ self._parent = None
100
+ self._references = list()
101
+ self._formatter = "{element_type}:{id}"
102
+
103
+ self._points = list()
104
+ self._points_dirty = True
105
+
106
+ self._selected = False
107
+ self._emphasized = False
108
+ self._emphasized_time = None
109
+ self._highlighted = False
110
+ self._target = False
111
+
112
+ self._opened = False
113
+
114
+ self._bounds = None
115
+ self._bounds_dirty = True
116
+
117
+ self._paint_bounds = None
118
+ self._paint_bounds_dirty = True
119
+
120
+ self._item = None
121
+ self._cache = None
122
+ self._default_map = dict()
123
+ if "expanded" in kwargs:
124
+ # print (f"Require expanded: {kwargs}")
125
+ exp_value = kwargs["expanded"]
126
+ self._expanded = exp_value
127
+ del kwargs["expanded"]
128
+
129
+ for k, v in kwargs.items():
130
+ if k.startswith("_"):
131
+ continue
132
+ if isinstance(v, str) and k not in ("text", "id", "label"):
133
+ try:
134
+ v = ast.literal_eval(v)
135
+ except (ValueError, SyntaxError):
136
+ pass
137
+ try:
138
+ setattr(self, k, v)
139
+ except AttributeError:
140
+ # If this is already an attribute, just add it to the node dict.
141
+ self.__dict__[k] = v
142
+
143
+ super().__init__()
144
+
145
+ def __repr__(self):
146
+ return f"{self.__class__.__name__}('{self.type}', {str(self._parent)})"
147
+
148
+ def __copy__(self):
149
+ return self.__class__(**self.node_dict)
150
+
151
+ def __str__(self):
152
+ text = self._formatter
153
+ if text is None:
154
+ text = "{element_type}"
155
+ default_map = self.default_map()
156
+ try:
157
+ return text.format_map(default_map)
158
+ except KeyError as e:
159
+ raise KeyError(
160
+ f"mapping '{text}' did not contain a required key in {default_map} for {self.__class__}"
161
+ ) from e
162
+
163
+ def __eq__(self, other):
164
+ return other is self
165
+
166
+ def __hash__(self):
167
+ return id(self)
168
+
169
+ @property
170
+ def node_dict(self):
171
+ nd = dict()
172
+ for k, v in self.__dict__.items():
173
+ if k.startswith("_"):
174
+ continue
175
+ if k == "type":
176
+ continue
177
+ nd[k] = v
178
+ return nd
179
+
180
+ @property
181
+ def children(self):
182
+ return self._children
183
+
184
+ @property
185
+ def references(self):
186
+ return self._references
187
+
188
+ @property
189
+ def targeted(self):
190
+ return self._target
191
+
192
+ @targeted.setter
193
+ def targeted(self, value):
194
+ self._target = value
195
+ self.notify_targeted(self)
196
+
197
+ @property
198
+ def expanded(self):
199
+ return self._expanded
200
+
201
+ @expanded.setter
202
+ def expanded(self, value):
203
+ self._expanded = value
204
+ # No use case for notify expand
205
+ # self.notify_expand(self)
206
+
207
+ @property
208
+ def highlighted(self):
209
+ return self._highlighted
210
+
211
+ @highlighted.setter
212
+ def highlighted(self, value):
213
+ self._highlighted = value
214
+ self.notify_highlighted(self)
215
+
216
+ @property
217
+ def is_visible(self):
218
+ result = True
219
+ # is it an operation?
220
+ if hasattr(self, "output"):
221
+ if self.output:
222
+ return True
223
+ else:
224
+ return self._is_visible
225
+ if hasattr(self, "references"):
226
+ valid = False
227
+ flag = False
228
+ for n in self.references:
229
+ if hasattr(n.parent, "output"):
230
+ valid = True
231
+ if n.parent.output is None or n.parent.output:
232
+ flag = True
233
+ break
234
+ if n.parent.is_visible:
235
+ flag = True
236
+ break
237
+ # If there aren't any references then it is visible by default
238
+ if valid:
239
+ result = flag
240
+ return result
241
+
242
+ @is_visible.setter
243
+ def is_visible(self, value):
244
+ # is it an operation?
245
+ if hasattr(self, "output"):
246
+ if self.output:
247
+ value = True
248
+ else:
249
+ value = True
250
+ self._is_visible = value
251
+
252
+ @property
253
+ def emphasized(self):
254
+ if self.is_visible:
255
+ result = self._emphasized
256
+ else:
257
+ result = False
258
+ return result
259
+
260
+ @emphasized.setter
261
+ def emphasized(self, value):
262
+ if not self.is_visible:
263
+ value = False
264
+ if value != self._emphasized:
265
+ self._emphasized = value
266
+ if value:
267
+ # Any value that is emphasiezd is automatically selected True
268
+ # This is not true for the inverse case, a node can be selected
269
+ # but not necessarily emphasized
270
+ self._selected = True
271
+ self._emphasized_time = time() if value else None
272
+ self.notify_emphasized(self)
273
+
274
+ @property
275
+ def emphasized_time(self):
276
+ # we intentionally reduce the resolution to 1/100 sec.
277
+ # to allow simultaneous assignments to return the same delta
278
+ factor = 100
279
+ if self._emphasized_time is None:
280
+ # Insanely high
281
+ result = float("inf")
282
+ else:
283
+ result = self._emphasized_time
284
+ result = round(result * factor) / factor
285
+ return result
286
+
287
+ def emphasized_since(self, reftime=None, fullres=False):
288
+ # we intentionally reduce the resolution to 1/100 sec.
289
+ # to allow simultaneous assignments to return the same delta
290
+ factor = 100
291
+ if reftime is None:
292
+ reftime = time()
293
+ if self._emphasized_time is None:
294
+ delta = 0
295
+ else:
296
+ delta = reftime - self._emphasized_time
297
+ if not fullres:
298
+ delta = round(delta * factor) / factor
299
+ return delta
300
+
301
+ @property
302
+ def selected(self):
303
+ return self._selected
304
+
305
+ @selected.setter
306
+ def selected(self, value):
307
+ self._selected = value
308
+ self.notify_selected(self)
309
+
310
+ @property
311
+ def parent(self):
312
+ return self._parent
313
+
314
+ @property
315
+ def root(self):
316
+ return self._root
317
+
318
+ @property
319
+ def can_emphasize(self):
320
+ return self._can_emphasize
321
+
322
+ @property
323
+ def can_highlight(self):
324
+ return self._can_highlight
325
+
326
+ @property
327
+ def can_target(self):
328
+ return self._can_target
329
+
330
+ def can_move(self, optional_permission=False):
331
+ if not self._can_move:
332
+ return False
333
+ if optional_permission:
334
+ return True
335
+ return not self.lock
336
+
337
+ @property
338
+ def can_scale(self):
339
+ return self._can_scale and not self.lock
340
+
341
+ @property
342
+ def can_rotate(self):
343
+ return self._can_rotate and not self.lock
344
+
345
+ @property
346
+ def can_skew(self):
347
+ return self._can_skew and not self.lock
348
+
349
+ @property
350
+ def can_modify(self):
351
+ return self._can_modify and not self.lock
352
+
353
+ @property
354
+ def can_alter(self):
355
+ return self._can_alter and not self.lock
356
+
357
+ @property
358
+ def can_update(self):
359
+ return self._can_update and not self.lock
360
+
361
+ @property
362
+ def can_remove(self):
363
+ return self._can_remove and not self.lock
364
+
365
+ @property
366
+ def bounds(self):
367
+ if not self._bounds_dirty:
368
+ return self._bounds
369
+
370
+ try:
371
+ self._bounds = self.bbox(with_stroke=False)
372
+ except AttributeError:
373
+ self._bounds = None
374
+
375
+ if self._children:
376
+ self._bounds = Node.union_bounds(self._children, bounds=self._bounds)
377
+ self._bounds_dirty = False
378
+ return self._bounds
379
+
380
+ @property
381
+ def paint_bounds(self):
382
+ # Make sure that bounds is valid
383
+ if not self._paint_bounds_dirty:
384
+ return self._paint_bounds
385
+
386
+ flag = True
387
+ if hasattr(self, "stroke"):
388
+ if self.stroke is None or self.stroke.argb is None:
389
+ flag = False
390
+ try:
391
+ self._paint_bounds = self.bbox(with_stroke=flag)
392
+ except AttributeError:
393
+ self._paint_bounds = None
394
+
395
+ if self._children:
396
+ self._paint_bounds = Node.union_bounds(
397
+ self._children, bounds=self._paint_bounds, attr="paint_bounds"
398
+ )
399
+ self._paint_bounds_dirty = False
400
+ return self._paint_bounds
401
+
402
+ def set_dirty_bounds(self):
403
+ self._paint_bounds_dirty = True
404
+ self._bounds_dirty = True
405
+ self._points_dirty = True
406
+
407
+ def set_dirty(self):
408
+ self.points_dirty = True
409
+ self.empty_cache()
410
+
411
+ @property
412
+ def formatter(self):
413
+ return self._formatter
414
+
415
+ @formatter.setter
416
+ def formatter(self, formatter):
417
+ self._formatter = formatter
418
+
419
+ def display_label(self):
420
+ x = self.label
421
+ if x is None:
422
+ return None
423
+ start = 0
424
+ default_map = self._default_map
425
+ while True:
426
+ i1 = x.find("{", start)
427
+ if i1 < 0:
428
+ break
429
+ i2 = x.find("}", i1)
430
+ if i2 < 0:
431
+ break
432
+ nd = x[i1 + 1 : i2]
433
+ nd_val = ""
434
+ if nd in default_map:
435
+ n_val = default_map[nd]
436
+ if n_val is not None:
437
+ nd_val = str(n_val)
438
+ elif hasattr(self, nd):
439
+ n_val = getattr(self, nd, "")
440
+ if n_val is not None:
441
+ nd_val = str(n_val)
442
+ x = x[:i1] + nd_val + x[i2 + 1 :]
443
+ start = i1 + len(nd_val)
444
+ return x
445
+
446
+ @property
447
+ def points(self):
448
+ """
449
+ Returns the node points values
450
+
451
+ @return: validated node point values, this is a list of lists of 3 elements.
452
+ """
453
+ if self._points_dirty:
454
+ self.revalidate_points()
455
+ self._points_dirty = False
456
+ return self._points
457
+
458
+ def restore_tree(self, tree_data):
459
+ # Takes a backup and reapplies it again to the tree
460
+ # Caveat: we can't just simply take the backup and load it into the tree,
461
+ # although it is already a perfectly independent copy.
462
+ # self._children.extend(tree_data)
463
+ # If loaded directly as above then this stored state will be used
464
+ # as the basis for further modifications consequently changing the
465
+ # original data (as it is still the original structure) used in the undostack.
466
+ # tree_data contains the copied branch nodes
467
+
468
+ self._children.clear()
469
+ links = {id(self): (self, None)}
470
+ attrib_list = ("_selected", "_emphasized", "_emphasized_time", "_highlighted", "_expanded")
471
+ for c in tree_data:
472
+ c._build_copy_nodes(links=links)
473
+ node_copy = copy(c)
474
+ for att in attrib_list:
475
+ if getattr(node_copy, att) != getattr(c, att):
476
+ # print (f"Strange {att} not identical, fixing")
477
+ setattr(node_copy, att, getattr(c, att))
478
+ node_copy._root = self._root
479
+ links[id(c)] = (c, node_copy)
480
+
481
+ # Rebuild structure.
482
+ self._validate_links(links)
483
+ branches = [links[id(c)][1] for c in tree_data]
484
+ self._children.extend(branches)
485
+ self._validate_tree()
486
+
487
+ def _validate_links(self, links):
488
+ for uid, n in links.items():
489
+ node, node_copy = n
490
+ if node._parent is None:
491
+ # Root.
492
+ continue
493
+ # Find copy-parent of copy-node and link.
494
+ original_parent, copied_parent = links[id(node._parent)]
495
+ if copied_parent is None:
496
+ # copy_parent should have been copied root, but roots don't copy
497
+ node_copy._parent = self._root
498
+ continue
499
+ node_copy._parent = copied_parent
500
+ copied_parent._children.append(node_copy)
501
+ if node.type == "reference":
502
+ original_referenced, copied_referenced = links[id(node.node)]
503
+ node_copy.node = copied_referenced
504
+ copied_referenced._references.append(node_copy)
505
+
506
+ def _validate_tree(self):
507
+ for c in self._children:
508
+ assert c._parent is self
509
+ assert c._root is self._root
510
+ assert c in c._parent._children
511
+ for q in c._references:
512
+ assert q.node is c
513
+ if c.type == "reference":
514
+ assert c in c.node._references
515
+ c._validate_tree()
516
+
517
+ def _build_copy_nodes(self, links=None):
518
+ """
519
+ Creates a copy of each node, linked to the ID of the original node. This will create
520
+ a map between id of original node and copy node. Without any structure. The original
521
+ root will link to `None` since root copies are in-effective.
522
+
523
+ @param links:
524
+ @return:
525
+ """
526
+ if links is None:
527
+ links = {id(self): (self, None)}
528
+ attrib_list = ("_selected", "_emphasized", "_emphasized_time", "_highlighted", "_expanded")
529
+ for c in self._children:
530
+ c._build_copy_nodes(links=links)
531
+ node_copy = copy(c)
532
+ for att in attrib_list:
533
+ if getattr(node_copy, att) != getattr(c, att):
534
+ # print (f"Strange {att} not identical, fixing")
535
+ setattr(node_copy, att, getattr(c, att))
536
+ node_copy._root = self._root
537
+ links[id(c)] = (c, node_copy)
538
+ return links
539
+
540
+ def backup_tree(self):
541
+ """
542
+ Creates structured copy of the branches of the tree at the current node.
543
+
544
+ This creates copied nodes, relinks the structure and returns branches of
545
+ the current node.
546
+ @return:
547
+ """
548
+ links = self._build_copy_nodes()
549
+
550
+ # Rebuild structure.
551
+ self._validate_links(links)
552
+ branches = [links[id(c)][1] for c in self._children]
553
+ return branches
554
+
555
+ def create_label(self, text=None):
556
+ if text is None:
557
+ text = "{element_type}:{id}"
558
+ # Just for the optical impression (who understands what a "Rect: None" means),
559
+ # let's replace some of the more obvious ones...
560
+ mymap = self.default_map()
561
+ for key in mymap:
562
+ if hasattr(self, key) and mymap[key] == "None":
563
+ if getattr(self, key) is None:
564
+ mymap[key] = "-"
565
+ # slist = text.split("{")
566
+ # for item in slist:
567
+ # idx = item.find("}")
568
+ # if idx>0:
569
+ # sitem = item[0:idx]
570
+ # else:
571
+ # sitem = item
572
+ # try:
573
+ # dummy = mymap[sitem]
574
+ # except KeyError:
575
+ # # Addit
576
+ # mymap[sitem] = "??ERR??"
577
+ try:
578
+ result = text.format_map(mymap)
579
+ except ValueError:
580
+ result = "<invalid pattern>"
581
+ return result
582
+
583
+ def default_map(self, default_map=None): # , skip_label=False
584
+ if default_map is None:
585
+ default_map = self._default_map
586
+ default_map["id"] = str(self.id) if self.id is not None else "-"
587
+ lbl = self.display_label()
588
+ default_map["label"] = lbl if lbl is not None else ""
589
+ default_map["desc"] = (
590
+ lbl if lbl is not None else str(self.id) if self.id is not None else "-"
591
+ )
592
+ default_map["element_type"] = "Node"
593
+ default_map["node_type"] = self.type
594
+ return default_map
595
+
596
+ def valid_node_for_reference(self, node):
597
+ return True
598
+
599
+ def copy_children_as_references(self, obj):
600
+ """
601
+ Copy the children of the given object as direct references to those children.
602
+ @param obj:
603
+ @return:
604
+ """
605
+ for element in obj.children:
606
+ self.add_reference(element)
607
+
608
+ def copy_with_reified_tree(self):
609
+ """
610
+ Make a copy of the current node, and a copy of the sub-nodes dereferencing any reference nodes
611
+ @return:
612
+ """
613
+ copy_c = copy(self)
614
+ copy_c.copy_children_as_real(self)
615
+ return copy_c
616
+
617
+ def copy_children_as_real(self, copy_node):
618
+ """
619
+ Copy the children of copy_node to the current node, dereferencing any reference nodes.
620
+ @param copy_node:
621
+ @return:
622
+ """
623
+ for child in copy_node.children:
624
+ child = child
625
+ if child.type == "reference":
626
+ child = child.node
627
+ copy_child = copy(child)
628
+ self.add_node(copy_child)
629
+ copy_child.copy_children_as_real(child)
630
+
631
+ def is_draggable(self):
632
+ return True
633
+
634
+ def can_drop(self, drag_node):
635
+ return False
636
+
637
+ def would_accept_drop(self, drag_nodes):
638
+ # drag_nodes can be a single node or a list of nodes
639
+ # drag_nodes can be a single node or a list of nodes
640
+ if isinstance(drag_nodes, (list, tuple)):
641
+ data = drag_nodes
642
+ else:
643
+ data = list(drag_nodes)
644
+ return any(self.can_drop(node) for node in data)
645
+
646
+ def drop(self, drag_node, modify=True, flag=False):
647
+ """
648
+ Process drag and drop node values for tree reordering.
649
+
650
+ @param drag_node:
651
+ @param modify:
652
+ @return:
653
+ """
654
+ return False
655
+
656
+ def reverse(self):
657
+ self._children.reverse()
658
+ self.notify_reorder()
659
+
660
+ def load(self, settings, section):
661
+ """
662
+ Default loading will read the persistence object, such that any values found in the given section of the
663
+ settings file. Will load parse the file to the correct type and set the attributes on this node.
664
+
665
+ @param settings:
666
+ @param section:
667
+ @return:
668
+ """
669
+ settings.read_persistent_object(section, self)
670
+
671
+ def save(self, settings, section):
672
+ """
673
+ The default node saving to a settings will write the persistence dictionary of the instance dictionary. This
674
+ will save to that section any non `_` attributes.
675
+
676
+ @param settings:
677
+ @param section:
678
+ @return:
679
+ """
680
+ settings.write_persistent_dict(section, self.__dict__)
681
+
682
+ def revalidate_points(self):
683
+ """
684
+ Ensure the points values for the node are valid with regard to the node's
685
+ current state. By default, this calls bounds but valid nodes can be overloaded
686
+ based on specific node type.
687
+
688
+ Should be overloaded by subclasses.
689
+
690
+ @return:
691
+ """
692
+ bounds = self.bounds
693
+ if bounds is None:
694
+ return
695
+ if len(self._points) < 5:
696
+ self._points.extend([None] * (5 - len(self._points)))
697
+ self._points[0] = [bounds[0], bounds[1], "bounds top_left"]
698
+ self._points[1] = [bounds[2], bounds[1], "bounds top_right"]
699
+ self._points[2] = [bounds[0], bounds[3], "bounds bottom_left"]
700
+ self._points[3] = [bounds[2], bounds[3], "bounds bottom_right"]
701
+ cx = (bounds[0] + bounds[2]) / 2
702
+ cy = (bounds[1] + bounds[3]) / 2
703
+ self._points[4] = [cx, cy, "bounds center_center"]
704
+
705
+ def update_point(self, index, point):
706
+ """
707
+ Attempt to update a node value, located at a specific index with the new
708
+ point provided.
709
+
710
+ Should be overloaded by subclasses.
711
+
712
+ @param index: updating point index
713
+ @param point: Point to be updated
714
+ @return: Whether update was successful
715
+ """
716
+ return False
717
+
718
+ def add_point(self, point, index=None):
719
+ """
720
+ Attempts to add a point into node.points.
721
+
722
+ Should be overloaded by subclasses.
723
+
724
+ @param point: point to be added
725
+ @param index: index for point insertion
726
+ @return: Whether append was successful
727
+ """
728
+ # return self._insert_point(point, index)
729
+ return False
730
+
731
+ def _insert_point(self, point, index=None):
732
+ """
733
+ Default implementation of inserting point into points.
734
+
735
+ @param point:
736
+ @param index:
737
+ @return:
738
+ """
739
+ x = None
740
+ y = None
741
+ point_type = None
742
+ try:
743
+ x = point[0]
744
+ y = point[1]
745
+ point_type = point[3]
746
+ except IndexError:
747
+ pass
748
+ if index is None:
749
+ self._points.append([x, y, point_type])
750
+ else:
751
+ try:
752
+ self._points.insert(index, [x, y, point_type])
753
+ except IndexError:
754
+ return False
755
+ return True
756
+
757
+ def notify_created(self, node=None, **kwargs):
758
+ if self._parent is not None:
759
+ if node is None:
760
+ node = self
761
+ self._parent.notify_created(node=node, **kwargs)
762
+
763
+ def notify_destroyed(self, node=None, **kwargs):
764
+ if self._parent is not None:
765
+ if node is None:
766
+ node = self
767
+ self._parent.notify_destroyed(node=node, **kwargs)
768
+
769
+ def notify_attached(self, node=None, **kwargs):
770
+ if self._parent is not None:
771
+ if node is None:
772
+ node = self
773
+ self._parent.notify_attached(node=node, **kwargs)
774
+
775
+ def notify_detached(self, node=None, **kwargs):
776
+ if self._parent is not None:
777
+ if node is None:
778
+ node = self
779
+ self._parent.notify_detached(node=node, **kwargs)
780
+
781
+ def notify_changed(self, node, **kwargs):
782
+ if self._parent is not None:
783
+ if node is None:
784
+ node = self
785
+ self._parent.notify_changed(node=node, **kwargs)
786
+
787
+ def notify_selected(self, node=None, **kwargs):
788
+ if self._parent is not None:
789
+ if node is None:
790
+ node = self
791
+ self._parent.notify_selected(node=node, **kwargs)
792
+
793
+ def notify_emphasized(self, node=None, **kwargs):
794
+ if self._parent is not None:
795
+ if node is None:
796
+ node = self
797
+ self._parent.notify_emphasized(node=node, **kwargs)
798
+
799
+ def notify_targeted(self, node=None, **kwargs):
800
+ if self._parent is not None:
801
+ if node is None:
802
+ node = self
803
+ self._parent.notify_targeted(node=node, **kwargs)
804
+
805
+ def notify_highlighted(self, node=None, **kwargs):
806
+ if self._root is not None:
807
+ if node is None:
808
+ node = self
809
+ self._root.notify_highlighted(node=node, **kwargs)
810
+
811
+ def notify_modified(self, node=None, **kwargs):
812
+ if self._parent is not None:
813
+ if node is None:
814
+ node = self
815
+ self._parent.notify_modified(node=node, **kwargs)
816
+
817
+ def notify_translated(self, node=None, dx=0, dy=0, invalidate=False, interim=False, **kwargs):
818
+ if invalidate:
819
+ self.set_dirty_bounds()
820
+ if self._parent is not None:
821
+ if node is None:
822
+ node = self
823
+ # Any change to position / size needs a recalculation of the bounds
824
+ self._parent.notify_translated(
825
+ node=node, dx=dx, dy=dy, invalidate=True, interim=interim, **kwargs
826
+ )
827
+
828
+ def notify_scaled(
829
+ self, node=None, sx=1, sy=1, ox=0, oy=0, invalidate=False, interim=False, **kwargs
830
+ ):
831
+ if invalidate:
832
+ self.set_dirty_bounds()
833
+ if self._parent is not None:
834
+ if node is None:
835
+ node = self
836
+ # Any change to position / size needs a recalculation of the bounds
837
+ self._parent.notify_scaled(
838
+ node=node, sx=sx, sy=sy, ox=ox, oy=oy, invalidate=True, interim=interim, **kwargs
839
+ )
840
+
841
+ def notify_altered(self, node=None, **kwargs):
842
+ if self._parent is not None:
843
+ if node is None:
844
+ node = self
845
+ self._parent.notify_altered(node=node, **kwargs)
846
+
847
+ def notify_expand(self, node=None, **kwargs):
848
+ if self._parent is not None:
849
+ if node is None:
850
+ node = self
851
+ self._parent.notify_expand(node=node, **kwargs)
852
+
853
+ def notify_collapse(self, node=None, **kwargs):
854
+ if self._parent is not None:
855
+ if node is None:
856
+ node = self
857
+ self._parent.notify_collapse(node=node, **kwargs)
858
+
859
+ def notify_reorder(self, node=None, **kwargs):
860
+ if self._parent is not None:
861
+ if node is None:
862
+ node = self
863
+ self._parent.notify_reorder(node=node, **kwargs)
864
+
865
+ def notify_update(self, node=None, **kwargs):
866
+ if self._parent is not None:
867
+ if node is None:
868
+ node = self
869
+ self._parent.notify_update(node=node, **kwargs)
870
+
871
+ def notify_focus(self, node=None, **kwargs):
872
+ if self._parent is not None:
873
+ if node is None:
874
+ node = self
875
+ self._parent.notify_focus(node=node, **kwargs)
876
+
877
+ def focus(self):
878
+ self.notify_focus(self)
879
+
880
+ def invalidated_node(self):
881
+ """
882
+ Invalidation of the individual node.
883
+ """
884
+ self.set_dirty_bounds()
885
+ self._bounds = None
886
+ self._paint_bounds = None
887
+
888
+ def invalidated(self):
889
+ """
890
+ Invalidation occurs when the underlying data is altered or modified. This propagates up from children to
891
+ invalidate the entire parental line.
892
+ """
893
+ self.invalidated_node()
894
+ if self._parent is not None:
895
+ self._parent.invalidated()
896
+
897
+ def updated(self):
898
+ """
899
+ The nodes display information may have changed but nothing about the matrix or the internal data is altered.
900
+ """
901
+ self.notify_update(self)
902
+
903
+ def modified(self):
904
+ """
905
+ The matrix transformation was changed. The object is shaped
906
+ differently but fundamentally the same structure of data.
907
+ """
908
+ self.invalidated()
909
+ self.notify_modified(self)
910
+
911
+ def translated(self, dx, dy, interim=False):
912
+ """
913
+ This is a special case of the modified call, we are translating
914
+ the node without fundamentally altering its properties
915
+ """
916
+ if self._bounds_dirty or self._bounds is None:
917
+ # A pity but we need proper data
918
+ self.modified()
919
+ return
920
+ self._bounds = [
921
+ self._bounds[0] + dx,
922
+ self._bounds[1] + dy,
923
+ self._bounds[2] + dx,
924
+ self._bounds[3] + dy,
925
+ ]
926
+ if self._paint_bounds_dirty or self._paint_bounds is None:
927
+ # Nothing we can do...
928
+ pass
929
+ else:
930
+ self._paint_bounds = [
931
+ self._paint_bounds[0] + dx,
932
+ self._paint_bounds[1] + dy,
933
+ self._paint_bounds[2] + dx,
934
+ self._paint_bounds[3] + dy,
935
+ ]
936
+ # self.set_dirty()
937
+ # No need to translate it as we will apply the matrix later
938
+ # self.translate_functional_parameter(dx, dy)
939
+
940
+ # if self._points_dirty:
941
+ # self.revalidate_points()
942
+ # else:
943
+ # for pt in self._points:
944
+ # pt[0] += dx
945
+ # pt[1] += dy
946
+ self.notify_translated(self, dx=dx, dy=dy, interim=interim)
947
+
948
+ def scaled(self, sx, sy, ox, oy, interim=False):
949
+ """
950
+ This is a special case of the modified call, we are scaling
951
+ the node without fundamentally altering its properties
952
+ """
953
+ def apply_it(box):
954
+ x0, y0, x1, y1 = box
955
+ if sx != 1.0:
956
+ d1 = x0 - ox
957
+ d2 = x1 - ox
958
+ x0 = ox + sx * d1
959
+ x1 = ox + sx * d2
960
+ if sy != 1.0:
961
+ d1 = y0 - oy
962
+ d2 = y1 - oy
963
+ y0 = oy + sy * d1
964
+ y1 = oy + sy * d2
965
+ return min(x0, x1), min(y0, y1), max(x0, x1), max(y0, y1)
966
+
967
+ if self._bounds_dirty or self._bounds is None:
968
+ # A pity but we need proper data
969
+ self.modified()
970
+ return
971
+ self._bounds = apply_it(self._bounds)
972
+ # self.scale_functional_parameter(sx, sy, ox, oy)
973
+ # This may not really correct, we need the
974
+ # implied stroke_width to add, so the inherited
975
+ # element classes will need to overload it
976
+ if self._paint_bounds is not None:
977
+ self._paint_bounds = apply_it(self._paint_bounds)
978
+ self.set_dirty()
979
+ self.notify_scaled(self, sx=sx, sy=sy, ox=ox, oy=oy, interim=interim)
980
+
981
+ def empty_cache(self):
982
+ # Remove cached artifacts
983
+ try:
984
+ self._cache.UnGetNativePath(self._cache.NativePath)
985
+ except AttributeError:
986
+ pass
987
+ try:
988
+ del self._cache
989
+ del self._cache_matrix
990
+ except AttributeError:
991
+ pass
992
+ self._cache = None
993
+
994
+ def altered(self, *args, **kwargs):
995
+ """
996
+ The data structure was changed. Any assumptions about what this object is/was are void.
997
+ """
998
+ self.empty_cache()
999
+ self.invalidated()
1000
+ self.notify_altered(self)
1001
+
1002
+ def unregister_object(self):
1003
+ self.empty_cache()
1004
+
1005
+ def unregister(self):
1006
+ self.unregister_object()
1007
+ try:
1008
+ self.targeted = False
1009
+ self.emphasized = False
1010
+ self.highlighted = False
1011
+ except AttributeError:
1012
+ pass
1013
+
1014
+ def add_reference(self, node=None, pos=None, **kwargs):
1015
+ """
1016
+ Add a new node bound to the data_object of the type to the current node.
1017
+ If the data_object itself is a node already it is merely attached.
1018
+
1019
+ @param node:
1020
+ @param pos:
1021
+ @return:
1022
+ """
1023
+ if node is None:
1024
+ return
1025
+ if not self.valid_node_for_reference(node):
1026
+ # We could raise a ValueError but that will break things...
1027
+ return
1028
+ ref = self.add(node=node, type="reference", pos=pos, **kwargs)
1029
+ node._references.append(ref)
1030
+
1031
+ def add_node(self, node, pos=None):
1032
+ """
1033
+ Attach an already created node to the tree.
1034
+
1035
+ Requires that this node be validated to avoid loops.
1036
+
1037
+ @param node:
1038
+ @param pos:
1039
+ @return:
1040
+ """
1041
+ if node is None:
1042
+ # This should not happen and is a sign that something is amiss,
1043
+ # so we inform at least abount it
1044
+ print("Tried to add an invalid node...")
1045
+ return
1046
+ if node._parent is not None:
1047
+ raise ValueError("Cannot reparent node on add.")
1048
+ node._parent = self
1049
+ node.set_root(self._root)
1050
+ if pos is None:
1051
+ self._children.append(node)
1052
+ else:
1053
+ self._children.insert(pos, node)
1054
+ node.notify_attached(node, parent=self, pos=pos)
1055
+ return node
1056
+
1057
+ def create(self, type, **kwargs):
1058
+ """
1059
+ Create node of type with attributes via node bootstrapping. Apply node defaults to values with defaults.
1060
+
1061
+ @param type:
1062
+ @param kwargs:
1063
+ @return:
1064
+ """
1065
+ from .bootstrap import bootstrap, defaults
1066
+
1067
+ node_class = bootstrap.get(type, None)
1068
+ if node_class is None:
1069
+ raise ValueError("Attempted to create unbootstrapped node")
1070
+ node_defaults = defaults.get(type, {})
1071
+ nd = dict(node_defaults)
1072
+ nd.update(kwargs)
1073
+ node = node_class(**nd)
1074
+ if self._root is not None:
1075
+ self._root.notify_created(node)
1076
+ return node
1077
+
1078
+ def add(self, type=None, pos=None, **kwargs):
1079
+ """
1080
+ Add a new node bound to the data_object of the type to the current node.
1081
+ If the data_object itself is a node already it is merely attached.
1082
+
1083
+ @param type: Node type to be bootstrapped
1084
+ @param pos: Position within current node to add this node
1085
+ @return:
1086
+ """
1087
+ node = self.create(type=type, **kwargs)
1088
+ if node is not None:
1089
+ self.add_node(node, pos=pos)
1090
+ else:
1091
+ print(f"Did not produce a valid node for type '{type}'")
1092
+ return node
1093
+
1094
+ def set_root(self, root):
1095
+ """
1096
+ Set the root for this and all descendant to the provided root
1097
+
1098
+ @param root:
1099
+ @return:
1100
+ """
1101
+ self._root = root
1102
+ for c in self._children:
1103
+ c.set_root(root)
1104
+
1105
+ def _flatten(self, node):
1106
+ """
1107
+ Yield this node and all descendants in a flat generation.
1108
+
1109
+ @param node: starting node
1110
+ @return:
1111
+ """
1112
+ yield node
1113
+ yield from self._flatten_children(node)
1114
+
1115
+ def _flatten_children(self, node):
1116
+ """
1117
+ Yield all descendants in a flat generation.
1118
+
1119
+ @param node: starting node
1120
+ @return:
1121
+ """
1122
+ for child in node.children:
1123
+ yield child
1124
+ yield from self._flatten_children(child)
1125
+
1126
+ def flat(
1127
+ self,
1128
+ types=None,
1129
+ cascade=True,
1130
+ depth=None,
1131
+ selected=None,
1132
+ emphasized=None,
1133
+ targeted=None,
1134
+ highlighted=None,
1135
+ lock=None,
1136
+ ):
1137
+ """
1138
+ Returned flat list of matching nodes. If cascade is set then any matching group will give all the descendants
1139
+ of the given type, even if those descendants are beyond the depth limit. The sub-elements do not need to match
1140
+ the criteria with respect to either the depth or the emphases.
1141
+
1142
+ @param types: types of nodes permitted to be returned
1143
+ @param cascade: cascade all subitems if a group matches the criteria.
1144
+ @param depth: depth to search within the tree.
1145
+ @param selected: match only selected nodes
1146
+ @param emphasized: match only emphasized nodes.
1147
+ @param targeted: match only targeted nodes
1148
+ @param highlighted: match only highlighted nodes
1149
+ @param lock: match locked nodes
1150
+ @return:
1151
+ """
1152
+ node = self
1153
+ if (
1154
+ (targeted is None or targeted == node.targeted)
1155
+ and (emphasized is None or emphasized == node.emphasized)
1156
+ and (selected is None or selected == node.selected)
1157
+ and (highlighted is None or highlighted == node.highlighted)
1158
+ and (lock is None or lock == node.lock)
1159
+ ):
1160
+ # Matches the emphases.
1161
+ if cascade:
1162
+ # Give every type-matched descendant.
1163
+ for c in self._flatten(node):
1164
+ if types is None or c.type in types:
1165
+ yield c
1166
+ # Do not recurse further. This node is end node.
1167
+ return
1168
+ else:
1169
+ if types is None or node.type in types:
1170
+ yield node
1171
+ if depth is not None:
1172
+ if depth <= 0:
1173
+ # Depth limit reached. Do not evaluate children.
1174
+ return
1175
+ depth -= 1
1176
+ # Check all children.
1177
+ for c in node.children:
1178
+ yield from c.flat(
1179
+ types, cascade, depth, selected, emphasized, targeted, highlighted, lock
1180
+ )
1181
+
1182
+ def count_children(self):
1183
+ return len(self._children)
1184
+
1185
+ def append_child(self, new_child):
1186
+ """
1187
+ Moves the new_child node as the last child of the current node.
1188
+ If the node exists elsewhere in the tree it will be removed from that location.
1189
+
1190
+ """
1191
+ if new_child is None:
1192
+ return
1193
+ new_parent = self
1194
+ belonged_to_me = bool(new_child.parent is self)
1195
+ if new_child.parent is not None:
1196
+ source_siblings = new_child.parent.children
1197
+ if belonged_to_me and source_siblings.index(new_child) == 0:
1198
+ # The very first will be moved to the end
1199
+ belonged_to_me = False
1200
+ source_siblings.remove(new_child) # Remove child
1201
+ destination_siblings = new_parent.children
1202
+
1203
+ new_child.notify_detached(new_child)
1204
+
1205
+ if belonged_to_me:
1206
+ destination_siblings.insert(0, new_child)
1207
+ new_child._parent = new_parent
1208
+ new_child.notify_attached(new_child, pos=0)
1209
+ else:
1210
+ destination_siblings.append(new_child) # Add child.
1211
+ new_child._parent = new_parent
1212
+ new_child.notify_attached(new_child)
1213
+
1214
+ def insert_sibling(self, new_sibling, below=True):
1215
+ """
1216
+ Add the new_sibling node next to the current node.
1217
+ If the node exists elsewhere in the tree it will be removed from that location.
1218
+ """
1219
+ reference_sibling = self
1220
+ source_siblings = None if new_sibling.parent is None else new_sibling.parent.children
1221
+ destination_siblings = reference_sibling.parent.children
1222
+
1223
+ if source_siblings:
1224
+ source_siblings.remove(new_sibling)
1225
+ try:
1226
+ reference_position = destination_siblings.index(reference_sibling)
1227
+ if below:
1228
+ reference_position += 1
1229
+ except ValueError:
1230
+ # Not in list, we could have just removed it...
1231
+ reference_position = 0
1232
+
1233
+ new_sibling.notify_detached(new_sibling)
1234
+ destination_siblings.insert(reference_position, new_sibling)
1235
+ new_sibling._parent = reference_sibling._parent
1236
+ new_sibling.notify_attached(new_sibling, pos=reference_position)
1237
+
1238
+ def replace_node(self, keep_children=None, *args, **kwargs):
1239
+ """
1240
+ Replace this current node with a bootstrapped replacement node.
1241
+ """
1242
+ if keep_children is None:
1243
+ keep_children = False
1244
+ parent = self._parent
1245
+ index = parent._children.index(self)
1246
+ parent._children.remove(self)
1247
+ self.notify_detached(self)
1248
+ node = parent.add(*args, **kwargs, pos=index)
1249
+ self.notify_destroyed()
1250
+ for ref in list(self._references):
1251
+ ref.remove_node()
1252
+ if keep_children:
1253
+ for ref in list(self._children):
1254
+ node._children.append(ref)
1255
+ ref._parent = node
1256
+ # Don't call attach / detach, as the tree
1257
+ # doesn't know about the new node yet...
1258
+ self._item = None
1259
+ self._parent = None
1260
+ self._root = None
1261
+ self.unregister()
1262
+ return node
1263
+
1264
+ def swap_node(self, node):
1265
+ """
1266
+ Swap nodes swaps the current node with the provided node in the other position in the same tree. All children
1267
+ during a swap are kept in place structurally. This permits swapping nodes between two positions that may be
1268
+ nested, without creating a loop.
1269
+
1270
+ Special care is taken for both swaps being children of the same parent.
1271
+
1272
+ @param node: Node already in the tree that should be swapped with the current node.
1273
+ @return:
1274
+ """
1275
+ # Remove self from tree.
1276
+ parent = self._parent
1277
+ n_parent = node._parent
1278
+
1279
+ index = parent._children.index(self)
1280
+ n_index = n_parent._children.index(node)
1281
+
1282
+ if index < n_index:
1283
+ # N_index is greater.
1284
+ del n_parent._children[n_index]
1285
+ del parent._children[index]
1286
+
1287
+ parent._children.insert(index, node)
1288
+ n_parent._children.insert(n_index, self)
1289
+ else:
1290
+ # N_index is lesser, equal
1291
+ del parent._children[index]
1292
+ del n_parent._children[n_index]
1293
+
1294
+ n_parent._children.insert(n_index, self)
1295
+ parent._children.insert(index, node)
1296
+
1297
+ node._parent = parent
1298
+ self._parent = n_parent
1299
+
1300
+ # Make a copy of children
1301
+ n_children = list(node._children)
1302
+ children = list(self._children)
1303
+
1304
+ # Delete children.
1305
+ node._children.clear()
1306
+ self._children.clear()
1307
+
1308
+ # Move children without call attach / detach.
1309
+ node._children.extend(children)
1310
+ self._children.extend(n_children)
1311
+
1312
+ # Correct parent for all children.
1313
+ for n in list(n_children):
1314
+ n._parent = self
1315
+ for n in list(children):
1316
+ n._parent = node
1317
+
1318
+ # self._root._validate_tree()
1319
+ self._root.notify_reorder()
1320
+
1321
+ def remove_node(self, children=True, references=True, fast=False, destroy=True):
1322
+ """
1323
+ Remove the current node from the tree.
1324
+
1325
+ @param children: removes all the children of this node.
1326
+ @param references: remove the references to this node.
1327
+ @param fast: Do not send notifications of the detatches and destroys
1328
+ @param destroy: Do not destroy the node.
1329
+ @return:
1330
+ """
1331
+ if children:
1332
+ self.remove_all_children(fast=fast)
1333
+ if self._parent:
1334
+ self._parent._children.remove(self)
1335
+ self._parent.set_dirty_bounds()
1336
+ if not fast:
1337
+ self.notify_detached(self)
1338
+ if destroy:
1339
+ self.notify_destroyed(self)
1340
+ if references:
1341
+ for ref in list(self._references):
1342
+ ref.remove_node(fast=fast)
1343
+ self._item = None
1344
+ self._parent = None
1345
+ self._root = None
1346
+ self.unregister()
1347
+
1348
+ def remove_all_children(self, fast=False, destroy=True):
1349
+ """
1350
+ Recursively removes all children of the current node.
1351
+ """
1352
+ for child in list(self.children):
1353
+ child.remove_all_children(fast=fast, destroy=destroy)
1354
+ child.remove_node(fast=fast, destroy=destroy)
1355
+
1356
+ def has_ancestor(self, type):
1357
+ """
1358
+ Return whether this node has an ancestor node that matches the given type, or matches the major type.
1359
+
1360
+ @param type:
1361
+ @return:
1362
+ """
1363
+ if self.parent is None:
1364
+ return False
1365
+
1366
+ if self.parent.type == type:
1367
+ return True
1368
+
1369
+ if " " not in type:
1370
+ if self.parent.type.startswith(type):
1371
+ return True
1372
+
1373
+ return self.parent.has_ancestor(type=type)
1374
+
1375
+ def get(self, type=None):
1376
+ """
1377
+ Recursive call for get to find first sub-nodes with the given type.
1378
+ @param type:
1379
+ @return:
1380
+ """
1381
+ if type is None or type == self.type:
1382
+ return self
1383
+ for n in self._children:
1384
+ node = n.get(type)
1385
+ if node is not None:
1386
+ return node
1387
+ return None
1388
+
1389
+ def move(self, dest, pos=None):
1390
+ self._parent.remove(self)
1391
+ dest.insert_node(self, pos=pos)
1392
+
1393
+ @staticmethod
1394
+ def union_bounds(nodes, bounds=None, attr="bounds", ignore_locked=True, ignore_hidden=False):
1395
+ """
1396
+ Returns the union of the node list given, optionally unioned the given bounds value
1397
+
1398
+ @return: union of all bounds within the iterable.
1399
+ """
1400
+ if bounds is None:
1401
+ xmin = float("inf")
1402
+ ymin = float("inf")
1403
+ xmax = -xmin
1404
+ ymax = -ymin
1405
+ else:
1406
+ xmin, ymin, xmax, ymax = bounds
1407
+ for e in nodes:
1408
+ if ignore_locked and e.lock:
1409
+ continue
1410
+ if ignore_hidden and getattr(e, "hidden", False):
1411
+ continue
1412
+ box = getattr(e, attr, None)
1413
+ if box is None:
1414
+ continue
1415
+ if box[0] < xmin:
1416
+ xmin = box[0]
1417
+ if box[2] > xmax:
1418
+ xmax = box[2]
1419
+ if box[1] < ymin:
1420
+ ymin = box[1]
1421
+ if box[3] > ymax:
1422
+ ymax = box[3]
1423
+ return xmin, ymin, xmax, ymax
1424
+
1425
+ @property
1426
+ def name(self):
1427
+ return self.__str__()