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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (445) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1195 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1844 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4561 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4329 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +933 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/trace.py +651 -563
  66. meerk40t/core/elements/tree_commands.py +415 -409
  67. meerk40t/core/elements/undo_redo.py +116 -58
  68. meerk40t/core/elements/wordlist.py +319 -200
  69. meerk40t/core/exceptions.py +9 -9
  70. meerk40t/core/laserjob.py +220 -220
  71. meerk40t/core/logging.py +63 -63
  72. meerk40t/core/node/blobnode.py +83 -86
  73. meerk40t/core/node/bootstrap.py +105 -103
  74. meerk40t/core/node/branch_elems.py +40 -31
  75. meerk40t/core/node/branch_ops.py +45 -38
  76. meerk40t/core/node/branch_regmark.py +48 -41
  77. meerk40t/core/node/cutnode.py +29 -32
  78. meerk40t/core/node/effect_hatch.py +375 -257
  79. meerk40t/core/node/effect_warp.py +398 -0
  80. meerk40t/core/node/effect_wobble.py +441 -309
  81. meerk40t/core/node/elem_ellipse.py +404 -309
  82. meerk40t/core/node/elem_image.py +1082 -801
  83. meerk40t/core/node/elem_line.py +358 -292
  84. meerk40t/core/node/elem_path.py +259 -201
  85. meerk40t/core/node/elem_point.py +129 -102
  86. meerk40t/core/node/elem_polyline.py +310 -246
  87. meerk40t/core/node/elem_rect.py +376 -286
  88. meerk40t/core/node/elem_text.py +445 -418
  89. meerk40t/core/node/filenode.py +59 -40
  90. meerk40t/core/node/groupnode.py +138 -74
  91. meerk40t/core/node/image_processed.py +777 -766
  92. meerk40t/core/node/image_raster.py +156 -113
  93. meerk40t/core/node/layernode.py +31 -31
  94. meerk40t/core/node/mixins.py +135 -107
  95. meerk40t/core/node/node.py +1427 -1304
  96. meerk40t/core/node/nutils.py +117 -114
  97. meerk40t/core/node/op_cut.py +462 -335
  98. meerk40t/core/node/op_dots.py +296 -251
  99. meerk40t/core/node/op_engrave.py +414 -311
  100. meerk40t/core/node/op_image.py +755 -369
  101. meerk40t/core/node/op_raster.py +787 -522
  102. meerk40t/core/node/place_current.py +37 -40
  103. meerk40t/core/node/place_point.py +329 -126
  104. meerk40t/core/node/refnode.py +58 -47
  105. meerk40t/core/node/rootnode.py +225 -219
  106. meerk40t/core/node/util_console.py +48 -48
  107. meerk40t/core/node/util_goto.py +84 -65
  108. meerk40t/core/node/util_home.py +61 -61
  109. meerk40t/core/node/util_input.py +102 -102
  110. meerk40t/core/node/util_output.py +102 -102
  111. meerk40t/core/node/util_wait.py +65 -65
  112. meerk40t/core/parameters.py +709 -707
  113. meerk40t/core/planner.py +875 -785
  114. meerk40t/core/plotplanner.py +656 -652
  115. meerk40t/core/space.py +120 -113
  116. meerk40t/core/spoolers.py +706 -705
  117. meerk40t/core/svg_io.py +1836 -1549
  118. meerk40t/core/treeop.py +534 -445
  119. meerk40t/core/undos.py +278 -124
  120. meerk40t/core/units.py +784 -680
  121. meerk40t/core/view.py +393 -322
  122. meerk40t/core/webhelp.py +62 -62
  123. meerk40t/core/wordlist.py +513 -504
  124. meerk40t/cylinder/cylinder.py +247 -0
  125. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  126. meerk40t/cylinder/gui/gui.py +24 -0
  127. meerk40t/device/__init__.py +1 -1
  128. meerk40t/device/basedevice.py +322 -123
  129. meerk40t/device/devicechoices.py +50 -0
  130. meerk40t/device/dummydevice.py +163 -128
  131. meerk40t/device/gui/defaultactions.py +618 -602
  132. meerk40t/device/gui/effectspanel.py +114 -0
  133. meerk40t/device/gui/formatterpanel.py +253 -290
  134. meerk40t/device/gui/warningpanel.py +337 -260
  135. meerk40t/device/mixins.py +13 -13
  136. meerk40t/dxf/__init__.py +1 -1
  137. meerk40t/dxf/dxf_io.py +766 -554
  138. meerk40t/dxf/plugin.py +47 -35
  139. meerk40t/external_plugins.py +79 -79
  140. meerk40t/external_plugins_build.py +28 -28
  141. meerk40t/extra/cag.py +112 -116
  142. meerk40t/extra/coolant.py +403 -0
  143. meerk40t/extra/encode_detect.py +198 -0
  144. meerk40t/extra/ezd.py +1165 -1165
  145. meerk40t/extra/hershey.py +835 -340
  146. meerk40t/extra/imageactions.py +322 -316
  147. meerk40t/extra/inkscape.py +630 -622
  148. meerk40t/extra/lbrn.py +424 -424
  149. meerk40t/extra/outerworld.py +284 -0
  150. meerk40t/extra/param_functions.py +1542 -1556
  151. meerk40t/extra/potrace.py +257 -253
  152. meerk40t/extra/serial_exchange.py +118 -0
  153. meerk40t/extra/updater.py +602 -453
  154. meerk40t/extra/vectrace.py +147 -146
  155. meerk40t/extra/winsleep.py +83 -83
  156. meerk40t/extra/xcs_reader.py +597 -0
  157. meerk40t/fill/fills.py +781 -335
  158. meerk40t/fill/patternfill.py +1061 -1061
  159. meerk40t/fill/patterns.py +614 -567
  160. meerk40t/grbl/control.py +87 -87
  161. meerk40t/grbl/controller.py +990 -903
  162. meerk40t/grbl/device.py +1081 -768
  163. meerk40t/grbl/driver.py +989 -771
  164. meerk40t/grbl/emulator.py +532 -497
  165. meerk40t/grbl/gcodejob.py +783 -767
  166. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  167. meerk40t/grbl/gui/grblcontroller.py +485 -271
  168. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  169. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  170. meerk40t/grbl/gui/gui.py +147 -116
  171. meerk40t/grbl/interpreter.py +44 -44
  172. meerk40t/grbl/loader.py +22 -22
  173. meerk40t/grbl/mock_connection.py +56 -56
  174. meerk40t/grbl/plugin.py +294 -264
  175. meerk40t/grbl/serial_connection.py +93 -88
  176. meerk40t/grbl/tcp_connection.py +81 -79
  177. meerk40t/grbl/ws_connection.py +112 -0
  178. meerk40t/gui/__init__.py +1 -1
  179. meerk40t/gui/about.py +2042 -296
  180. meerk40t/gui/alignment.py +1644 -1608
  181. meerk40t/gui/autoexec.py +199 -0
  182. meerk40t/gui/basicops.py +791 -670
  183. meerk40t/gui/bufferview.py +77 -71
  184. meerk40t/gui/busy.py +170 -133
  185. meerk40t/gui/choicepropertypanel.py +1673 -1469
  186. meerk40t/gui/consolepanel.py +706 -542
  187. meerk40t/gui/devicepanel.py +687 -581
  188. meerk40t/gui/dialogoptions.py +110 -107
  189. meerk40t/gui/executejob.py +316 -306
  190. meerk40t/gui/fonts.py +90 -90
  191. meerk40t/gui/functionwrapper.py +252 -0
  192. meerk40t/gui/gui_mixins.py +729 -0
  193. meerk40t/gui/guicolors.py +205 -182
  194. meerk40t/gui/help_assets/help_assets.py +218 -201
  195. meerk40t/gui/helper.py +154 -0
  196. meerk40t/gui/hersheymanager.py +1430 -846
  197. meerk40t/gui/icons.py +3422 -2747
  198. meerk40t/gui/imagesplitter.py +555 -508
  199. meerk40t/gui/keymap.py +354 -344
  200. meerk40t/gui/laserpanel.py +892 -806
  201. meerk40t/gui/laserrender.py +1470 -1232
  202. meerk40t/gui/lasertoolpanel.py +805 -793
  203. meerk40t/gui/magnetoptions.py +436 -0
  204. meerk40t/gui/materialmanager.py +2917 -0
  205. meerk40t/gui/materialtest.py +1722 -1694
  206. meerk40t/gui/mkdebug.py +646 -359
  207. meerk40t/gui/mwindow.py +163 -140
  208. meerk40t/gui/navigationpanels.py +2605 -2467
  209. meerk40t/gui/notes.py +143 -142
  210. meerk40t/gui/opassignment.py +414 -410
  211. meerk40t/gui/operation_info.py +310 -299
  212. meerk40t/gui/plugin.py +494 -328
  213. meerk40t/gui/position.py +714 -669
  214. meerk40t/gui/preferences.py +901 -650
  215. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  216. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  217. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  218. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  219. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  220. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  221. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  222. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  223. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  224. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  225. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  226. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  227. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  228. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  229. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  230. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  231. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  232. meerk40t/gui/propertypanels/textproperty.py +770 -755
  233. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  234. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  235. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  236. meerk40t/gui/ribbon.py +2468 -2210
  237. meerk40t/gui/scene/scene.py +1100 -1051
  238. meerk40t/gui/scene/sceneconst.py +22 -22
  239. meerk40t/gui/scene/scenepanel.py +439 -349
  240. meerk40t/gui/scene/scenespacewidget.py +365 -365
  241. meerk40t/gui/scene/widget.py +518 -505
  242. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  243. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  244. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  245. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  246. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  247. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  248. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  249. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  250. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  251. meerk40t/gui/scenewidgets/rectselectwidget.py +589 -346
  252. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  253. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  254. meerk40t/gui/scenewidgets/selectionwidget.py +2952 -2756
  255. meerk40t/gui/simpleui.py +357 -333
  256. meerk40t/gui/simulation.py +2431 -2094
  257. meerk40t/gui/snapoptions.py +208 -203
  258. meerk40t/gui/spoolerpanel.py +1227 -1180
  259. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  260. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  261. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  262. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  263. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  264. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  265. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  266. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  267. meerk40t/gui/themes.py +200 -78
  268. meerk40t/gui/tips.py +591 -0
  269. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  270. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  271. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  272. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  273. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  274. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  275. meerk40t/gui/toolwidgets/toolline.py +39 -144
  276. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  277. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  278. meerk40t/gui/toolwidgets/toolmeasure.py +160 -216
  279. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  280. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  281. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  282. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  283. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  284. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  285. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  286. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  287. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  288. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  289. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  290. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  291. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  292. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  293. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  294. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  295. meerk40t/gui/usbconnect.py +98 -91
  296. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  297. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  298. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  299. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  300. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  301. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  302. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  303. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  304. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  305. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  306. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  307. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  308. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  309. meerk40t/gui/wordlisteditor.py +985 -931
  310. meerk40t/gui/wxmeerk40t.py +1444 -1169
  311. meerk40t/gui/wxmmain.py +5578 -4112
  312. meerk40t/gui/wxmribbon.py +1591 -1076
  313. meerk40t/gui/wxmscene.py +1635 -1453
  314. meerk40t/gui/wxmtree.py +2410 -2089
  315. meerk40t/gui/wxutils.py +1769 -1099
  316. meerk40t/gui/zmatrix.py +102 -102
  317. meerk40t/image/__init__.py +1 -1
  318. meerk40t/image/dither.py +429 -0
  319. meerk40t/image/imagetools.py +2778 -2269
  320. meerk40t/internal_plugins.py +150 -130
  321. meerk40t/kernel/__init__.py +63 -12
  322. meerk40t/kernel/channel.py +259 -212
  323. meerk40t/kernel/context.py +538 -538
  324. meerk40t/kernel/exceptions.py +41 -41
  325. meerk40t/kernel/functions.py +463 -414
  326. meerk40t/kernel/jobs.py +100 -100
  327. meerk40t/kernel/kernel.py +3809 -3571
  328. meerk40t/kernel/lifecycles.py +71 -71
  329. meerk40t/kernel/module.py +49 -49
  330. meerk40t/kernel/service.py +147 -147
  331. meerk40t/kernel/settings.py +383 -343
  332. meerk40t/lihuiyu/controller.py +883 -876
  333. meerk40t/lihuiyu/device.py +1181 -1069
  334. meerk40t/lihuiyu/driver.py +1466 -1372
  335. meerk40t/lihuiyu/gui/gui.py +127 -106
  336. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  337. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  338. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  339. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  340. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  341. meerk40t/lihuiyu/interpreter.py +53 -53
  342. meerk40t/lihuiyu/laserspeed.py +450 -450
  343. meerk40t/lihuiyu/loader.py +90 -90
  344. meerk40t/lihuiyu/parser.py +404 -404
  345. meerk40t/lihuiyu/plugin.py +101 -102
  346. meerk40t/lihuiyu/tcp_connection.py +111 -109
  347. meerk40t/main.py +231 -165
  348. meerk40t/moshi/builder.py +788 -781
  349. meerk40t/moshi/controller.py +505 -499
  350. meerk40t/moshi/device.py +495 -442
  351. meerk40t/moshi/driver.py +862 -696
  352. meerk40t/moshi/gui/gui.py +78 -76
  353. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  354. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  355. meerk40t/moshi/plugin.py +43 -43
  356. meerk40t/network/console_server.py +102 -57
  357. meerk40t/network/kernelserver.py +10 -9
  358. meerk40t/network/tcp_server.py +142 -140
  359. meerk40t/network/udp_server.py +103 -77
  360. meerk40t/network/web_server.py +390 -0
  361. meerk40t/newly/controller.py +1158 -1144
  362. meerk40t/newly/device.py +874 -732
  363. meerk40t/newly/driver.py +540 -412
  364. meerk40t/newly/gui/gui.py +219 -188
  365. meerk40t/newly/gui/newlyconfig.py +116 -101
  366. meerk40t/newly/gui/newlycontroller.py +193 -186
  367. meerk40t/newly/gui/operationproperties.py +51 -51
  368. meerk40t/newly/mock_connection.py +82 -82
  369. meerk40t/newly/newly_params.py +56 -56
  370. meerk40t/newly/plugin.py +1214 -1246
  371. meerk40t/newly/usb_connection.py +322 -322
  372. meerk40t/rotary/gui/gui.py +52 -46
  373. meerk40t/rotary/gui/rotarysettings.py +240 -232
  374. meerk40t/rotary/rotary.py +202 -98
  375. meerk40t/ruida/control.py +291 -91
  376. meerk40t/ruida/controller.py +138 -1088
  377. meerk40t/ruida/device.py +672 -231
  378. meerk40t/ruida/driver.py +534 -472
  379. meerk40t/ruida/emulator.py +1494 -1491
  380. meerk40t/ruida/exceptions.py +4 -4
  381. meerk40t/ruida/gui/gui.py +71 -76
  382. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  383. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  384. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  385. meerk40t/ruida/loader.py +54 -52
  386. meerk40t/ruida/mock_connection.py +57 -109
  387. meerk40t/ruida/plugin.py +124 -87
  388. meerk40t/ruida/rdjob.py +2084 -945
  389. meerk40t/ruida/serial_connection.py +116 -0
  390. meerk40t/ruida/tcp_connection.py +146 -0
  391. meerk40t/ruida/udp_connection.py +73 -0
  392. meerk40t/svgelements.py +9671 -9669
  393. meerk40t/tools/driver_to_path.py +584 -579
  394. meerk40t/tools/geomstr.py +5583 -4680
  395. meerk40t/tools/jhfparser.py +357 -292
  396. meerk40t/tools/kerftest.py +904 -890
  397. meerk40t/tools/livinghinges.py +1168 -1033
  398. meerk40t/tools/pathtools.py +987 -949
  399. meerk40t/tools/pmatrix.py +234 -0
  400. meerk40t/tools/pointfinder.py +942 -942
  401. meerk40t/tools/polybool.py +940 -940
  402. meerk40t/tools/rasterplotter.py +1660 -547
  403. meerk40t/tools/shxparser.py +989 -901
  404. meerk40t/tools/ttfparser.py +726 -446
  405. meerk40t/tools/zinglplotter.py +595 -593
  406. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/LICENSE +21 -21
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/METADATA +150 -139
  408. meerk40t-0.9.7010.dist-info/RECORD +445 -0
  409. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/WHEEL +1 -1
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/top_level.txt +0 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/zip-safe +1 -1
  412. meerk40t/balormk/elementlightjob.py +0 -159
  413. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  414. test/bootstrap.py +0 -63
  415. test/test_cli.py +0 -12
  416. test/test_core_cutcode.py +0 -418
  417. test/test_core_elements.py +0 -144
  418. test/test_core_plotplanner.py +0 -397
  419. test/test_core_viewports.py +0 -312
  420. test/test_drivers_grbl.py +0 -108
  421. test/test_drivers_lihuiyu.py +0 -443
  422. test/test_drivers_newly.py +0 -113
  423. test/test_element_degenerate_points.py +0 -43
  424. test/test_elements_classify.py +0 -97
  425. test/test_elements_penbox.py +0 -22
  426. test/test_file_svg.py +0 -176
  427. test/test_fill.py +0 -155
  428. test/test_geomstr.py +0 -1523
  429. test/test_geomstr_nodes.py +0 -18
  430. test/test_imagetools_actualize.py +0 -306
  431. test/test_imagetools_wizard.py +0 -258
  432. test/test_kernel.py +0 -200
  433. test/test_laser_speeds.py +0 -3303
  434. test/test_length.py +0 -57
  435. test/test_lifecycle.py +0 -66
  436. test/test_operations.py +0 -251
  437. test/test_operations_hatch.py +0 -57
  438. test/test_ruida.py +0 -19
  439. test/test_spooler.py +0 -22
  440. test/test_tools_rasterplotter.py +0 -29
  441. test/test_wobble.py +0 -133
  442. test/test_zingl.py +0 -124
  443. {test → meerk40t/cylinder}/__init__.py +0 -0
  444. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  445. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/entry_points.txt +0 -0
@@ -1,1556 +1,1542 @@
1
- """
2
- This module exposes a couple of routines to create shapes,
3
- that have additional functional parameters set to allow
4
- parametric editing
5
- """
6
- import copy
7
- import math
8
-
9
- from meerk40t.core.units import Length
10
- from meerk40t.kernel import CommandSyntaxError
11
- from meerk40t.svgelements import Angle, Point
12
- from meerk40t.tools.geomstr import Geomstr
13
-
14
-
15
- def plugin(kernel, lifecycle):
16
- if lifecycle == "register":
17
- _ = kernel.translation
18
- context = kernel.root
19
- self = context.elements
20
- classify_new = self.post_classify
21
-
22
- def getit(param, idx, default):
23
- if idx >= len(param):
24
- return default
25
- return param[idx]
26
-
27
- def create_copied_grid(start_pt, node, cols, rows, sdx, sdy):
28
- # print(
29
- # f"Create a {cols}x{rows} grid, gap={sdx:.0f} x {sdy:.0f} at ({start_pt.x:.0f}, {start_pt.y:.0f})..."
30
- # )
31
- geom = Geomstr()
32
- try:
33
- if isinstance(node, Geomstr):
34
- # That may be the case if we have lost the connection to the original node.
35
- # In that case we are getting passed the first geometry subpath to reuse
36
- orggeom = node
37
- else:
38
- orggeom = node.as_geometry()
39
- bb = orggeom.bbox()
40
- width = bb[2] - bb[0]
41
- height = bb[3] - bb[1]
42
- except AttributeError:
43
- return geom
44
- x = start_pt.x
45
- for scol in range(cols):
46
- y = start_pt.y
47
- for srow in range(rows):
48
- tempgeom = Geomstr(orggeom)
49
- dx = x - bb[0]
50
- dy = y - bb[1]
51
- # print(f"{scol}x{srow}: translate by {dx:.0f}x{dy:.0f}")
52
- tempgeom.translate(dx, dy)
53
- geom.append(tempgeom)
54
- y += sdy + height
55
- x += sdx + width
56
- return geom
57
-
58
- @self.console_argument("sx", type=Length)
59
- @self.console_argument("sy", type=Length)
60
- @self.console_argument("cols", type=int)
61
- @self.console_argument("rows", type=int)
62
- @self.console_argument("id", type=str)
63
- @self.console_option("dx", "x", type=Length, help=_("Horizontal delta"))
64
- @self.console_option("dy", "y", type=Length, help=_("Vertical delta"))
65
- @context.console_command("pgrid", help=_("pgrid sx, sy, cols, rows, id"))
66
- def param_grid(
67
- command,
68
- channel,
69
- _,
70
- sx=None,
71
- sy=None,
72
- cols=None,
73
- rows=None,
74
- id=None,
75
- dx=None,
76
- dy=None,
77
- clone=None,
78
- post=None,
79
- **kwargs,
80
- ):
81
- try:
82
- if sx is None:
83
- sx = Length("0cm")
84
- ssx = float(sx)
85
- if sy is None:
86
- sy = Length("0cm")
87
- ssy = float(sy)
88
- if cols is None:
89
- cols = 2
90
- if rows is None:
91
- rows = 2
92
- if dx is None:
93
- dx = Length("0mm")
94
- sdx = float(dx)
95
- if dy is None:
96
- dy = Length("0mm")
97
- sdy = float(dy)
98
- except ValueError:
99
- channel("Invalid data provided")
100
- return
101
- self.validate_ids()
102
- found_node = None
103
- if id is not None:
104
- # We look for such a node, both in elements and regmarks.
105
- for node in self.elems():
106
- if node.id == id and hasattr(node, "as_geometry"):
107
- found_node = node
108
- break
109
- if found_node is None:
110
- for node in self.regmarks():
111
- if node.id == id and hasattr(node, "as_geometry"):
112
- found_node = node
113
- break
114
- if found_node is None:
115
- # We try the first selected element
116
- for node in self.elems(emphasized=True):
117
- if hasattr(node, "as_geometry"):
118
- id = node.id
119
- found_node = node
120
- break
121
- if found_node is None:
122
- channel("No matching element found.")
123
- start_pt = Point(ssx, ssy)
124
- geom = create_copied_grid(start_pt, found_node, cols, rows, sdx, sdy)
125
- node = self.elem_branch.add(type="elem path", geometry=geom)
126
- bb = geom.bbox()
127
- width = bb[2] - bb[0]
128
- # height = bb[3] - bb[1]
129
- if hasattr(found_node, "stroke"):
130
- node.stroke = found_node.stroke
131
- else:
132
- node.stroke = self.default_stroke
133
- node.stroke_width = self.default_strokewidth
134
- node.altered()
135
- node.functional_parameter = (
136
- "grid",
137
- 3,
138
- id,
139
- 0,
140
- start_pt.x + sdx,
141
- start_pt.y,
142
- 0,
143
- start_pt.x + width,
144
- start_pt.y + sdy,
145
- 1,
146
- cols,
147
- 1,
148
- rows,
149
- )
150
- # Newly created! Classification needed?
151
- data = [node]
152
- post.append(classify_new(data))
153
- return "elements", data
154
-
155
- def update_node_grid(node):
156
- my_id = "grid"
157
- valid = True
158
- try:
159
- param = node.functional_parameter
160
- if param is None or param[0] != my_id:
161
- valid = False
162
- except (AttributeError, IndexError):
163
- valid = False
164
- if not valid:
165
- # Not for me...
166
- return
167
- node_id = getit(param, 2, None)
168
- if node_id is None:
169
- return
170
- found_node = None
171
- for e in self.elems():
172
- if e.id == node_id and hasattr(e, "as_geometry"):
173
- found_node = e
174
- break
175
- if found_node is None:
176
- for e in self.regmarks():
177
- if e.id == node_id and hasattr(e, "as_geometry"):
178
- found_node = e
179
- break
180
- if found_node is None:
181
- return
182
- geom = node.as_geometry()
183
- # That has already the matrix applied, so we need to reverse that
184
- # Use ~ inversion operator to create an inversed copy
185
- geom.transform(~node.matrix)
186
- bb = geom.bbox()
187
- start_pt = Point(bb[0], bb[1])
188
-
189
- sdx = getit(param, 4, bb[0])
190
- sdy = getit(param, 8, bb[1])
191
- sdx = sdx - bb[0]
192
- sdy = sdy - bb[1]
193
- cols = getit(param, 10, 1)
194
- rows = getit(param, 12, 1)
195
-
196
- geom = create_copied_grid(start_pt, found_node, cols, rows, sdx, sdy)
197
- node.geometry = geom
198
- bb = geom.bbox()
199
- node.functional_parameter = (
200
- "grid",
201
- 3,
202
- node_id,
203
- 0,
204
- bb[0] + sdx,
205
- bb[1],
206
- 0,
207
- bb[2],
208
- bb[1] + sdy,
209
- 1,
210
- cols,
211
- 1,
212
- rows,
213
- )
214
- node.altered()
215
-
216
- # --- Fractal Tree
217
- def create_fractal_tree(first_pt, second_pt, iterations, ratio):
218
- def tree_fractal(geom, startpt, endpt, depth, ratio):
219
- #
220
- # create a line from startpt to endpt and add it to the geometry
221
- if depth < 0:
222
- return
223
- angle = startpt.angle_to(endpt)
224
- dist = startpt.distance_to(endpt)
225
- delta = math.tau / 8
226
- # print(
227
- # f"{depth}: ({startpt.x:.0f}, {startpt.y:.0f}) - ({endpt.x:.0f}, {endpt.y:.0f}) : {dist:.0f}"
228
- # )
229
- geom.line(complex(startpt.x, startpt.y), complex(endpt.x, endpt.y))
230
- newstart = Point(endpt)
231
- newend = Point.polar(endpt, angle - delta, dist * ratio)
232
- tree_fractal(geom, newstart, newend, depth - 1, ratio)
233
- newend = Point.polar(endpt, angle + delta, dist * ratio)
234
- tree_fractal(geom, newstart, newend, depth - 1, ratio)
235
-
236
- geometry = Geomstr()
237
- # print("create fractal", first_pt, second_pt)
238
- tree_fractal(geometry, first_pt, second_pt, iterations, ratio)
239
- return geometry
240
-
241
- @self.console_argument(
242
- "turtle", type=str, help="turtle-path for the fractal seed"
243
- )
244
- @self.console_argument("n", type=int, default=4, help="Angle divisors")
245
- @self.console_argument(
246
- "iterations",
247
- type=int,
248
- default=5,
249
- help="Number of fractal iterations to add",
250
- )
251
- @self.console_option(
252
- "base", "b", type=str, help="turtle-path for the fractal base"
253
- )
254
- @context.console_command(
255
- "tfractal",
256
- help=_("tfractal iterations"),
257
- output_type="geometry",
258
- hidden=True,
259
- )
260
- def fractal(command, channel, turtle, n, iterations, base=None, **kwargs):
261
- """
262
- Add a turtle fractal to the scene. All fractals are geometry outputs.
263
- F - Forward
264
- f - Forward (y-flipped)
265
- B - Forward (walking backwards)
266
- b - Forward(walking backwards, y-flipped).
267
- D - Set new distance (units in sqrt of specified value)
268
- d - Set new distance (units given)
269
- + - Turn right (also R).
270
- - - Turn left (also L).
271
-
272
- For example:
273
- tfractal F+++D2Fd1-B 8 7 node
274
-
275
- Would produce the Dragon of Eve fractal (iteration=7)
276
- """
277
- if turtle is None:
278
- channel("No turtle definition found")
279
- return
280
- seed = Geomstr.turtle(turtle, n)
281
- pattern_base = base
282
- pattern_repeat = 1
283
- if base is None:
284
- base = Geomstr.svg("M0,0 H65535")
285
- else:
286
- base = Geomstr.turtle(base, n, d=65535)
287
- if pattern_base.startswith("n"):
288
- idx = 1
289
- pattern = ""
290
- while pattern_base[idx] in "0123456789":
291
- pattern += pattern_base[idx]
292
- idx += 1
293
- try:
294
- pattern_repeat = int(pattern)
295
- except ValueError:
296
- pass
297
-
298
- for i in range(iterations):
299
- base.fractal(seed)
300
-
301
- base.parameter_store = (
302
- "tfractal",
303
- 3,
304
- turtle,
305
- 3,
306
- pattern_base,
307
- 1,
308
- n,
309
- 1,
310
- iterations,
311
- 1,
312
- pattern_repeat,
313
- 1,
314
- 0, # line connector
315
- )
316
- return "geometry", base
317
-
318
- @self.console_option(
319
- "amount", "a", type=float, help="corner rounding amount", default=0.2
320
- )
321
- @self.console_command(
322
- "round_corners", input_type="geometry", output_type="geometry"
323
- )
324
- def round_corners(command, channel, data, amount=0.2, **kwargs):
325
- data.round_corners(amount)
326
- if hasattr(data, "parameter_store"):
327
- param = list(data.parameter_store) # make it editable
328
- if len(param) > 12:
329
- # Round corners
330
- param[12] = 1
331
- data.parameter_store = param
332
- return "geometry", data
333
-
334
- @self.console_option(
335
- "amount", "a", type=float, help="corner-bezier amount", default=0.2
336
- )
337
- @self.console_command(
338
- "quad_corners", input_type="geometry", output_type="geometry"
339
- )
340
- def round_corners(command, channel, data, amount=0.2, **kwargs):
341
- data.bezier_corners(amount)
342
- if hasattr(data, "parameter_store"):
343
- param = list(data.parameter_store) # make it editable
344
- if len(param) > 12:
345
- # Bezier corners
346
- param[12] = 2
347
- data.parameter_store = param
348
- return "geometry", data
349
-
350
- def update_node_tfractal(node):
351
- my_id = "tfractal"
352
- valid = True
353
- try:
354
- param = node.functional_parameter
355
- if param is None or param[0] != my_id:
356
- valid = False
357
- except (AttributeError, IndexError):
358
- valid = False
359
- if not valid:
360
- # Not for me...
361
- return
362
- turtle = getit(param, 2, "")
363
- basepattern = getit(param, 4, "")
364
- if basepattern == "":
365
- basepattern = None
366
- n = getit(param, 6, 1)
367
- iterations = getit(param, 8, 1)
368
- pattern_repeat = getit(param, 10, 1)
369
- connector = getit(param, 12, 0)
370
- if valid:
371
- seed = Geomstr.turtle(turtle, n)
372
- # Let's see whether we need to reconstruct the base
373
- # Store it
374
- targetpattern = basepattern
375
- if basepattern is not None:
376
- if basepattern.startswith("n"):
377
- basepattern = basepattern[1:]
378
- previous = ""
379
- while len(basepattern) and basepattern[0] in "0123456789":
380
- previous += basepattern[0]
381
- basepattern = basepattern[1:]
382
- prev_len = 1
383
- if previous:
384
- try:
385
- prev_len = int(previous)
386
- except ValueError:
387
- prev_len = 1
388
- # Need add 1 to the length as the connector symbol
389
- # is not present after the last repetition
390
- if (len(basepattern) + 1) % prev_len == 0:
391
- old = basepattern
392
- basepattern = basepattern[
393
- 0 : int(len(basepattern) / prev_len)
394
- ]
395
- else:
396
- # that's wrong...
397
- pattern_repeat = 1
398
-
399
- if pattern_repeat > 1:
400
- targetpattern = f"n{pattern_repeat}"
401
- for idx in range(pattern_repeat):
402
- if idx > 0:
403
- targetpattern += "+"
404
- targetpattern += basepattern
405
- else:
406
- targetpattern = basepattern
407
-
408
- if targetpattern == "":
409
- targetpattern = None
410
-
411
- if targetpattern is None:
412
- base = Geomstr.svg("M0,0 H65535")
413
- else:
414
- base = Geomstr.turtle(targetpattern, n, d=65535)
415
-
416
- for i in range(iterations):
417
- base.fractal(seed)
418
- if connector == 2:
419
- amount = 0.2
420
- base.bezier_corners(amount)
421
- elif connector == 1:
422
- amount = 0.2
423
- base.round_corners(amount)
424
- node.geometry = base
425
- node.altered()
426
- # Rewrite the functional_parameter
427
- node.functional_parameter = (
428
- "tfractal",
429
- 3,
430
- turtle,
431
- 3,
432
- targetpattern,
433
- 1,
434
- n,
435
- 1,
436
- iterations,
437
- 1,
438
- pattern_repeat,
439
- 1,
440
- connector, # line connector
441
- )
442
-
443
- @self.console_argument("svg_path", type=str)
444
- @self.console_argument("iterations", type=int)
445
- @self.console_argument("inversions", nargs="*", type=int)
446
- @context.console_command(
447
- "ffractal",
448
- help=_("ffractal iterations"),
449
- output_type="geometry",
450
- hidden=True,
451
- )
452
- def fractal(command, channel, svg_path, iterations, inversions, **kwargs):
453
- seed = Geomstr.svg(svg_path)
454
- segments = seed.segments
455
- for i, q in enumerate(inversions):
456
- if len(segments) > i:
457
- segments[i][1] = q
458
- segments[i][3] = q
459
- base = Geomstr.svg("M0,0 H65535")
460
- for i in range(iterations):
461
- base.fractal(seed)
462
- return "geometry", base
463
-
464
- @self.console_argument("sx", type=Length)
465
- @self.console_argument("sy", type=Length)
466
- @self.console_argument("branch", type=Length)
467
- @self.console_argument("iterations", type=int)
468
- @context.console_command(
469
- "fractal_tree", help=_("fractal_tree sx, sy, branch, iterations")
470
- )
471
- def fractal_tree(
472
- command,
473
- channel,
474
- _,
475
- sx=None,
476
- sy=None,
477
- branch=None,
478
- iterations=None,
479
- data=None,
480
- post=None,
481
- **kwargs,
482
- ):
483
- ratio = 0.75
484
- try:
485
- if sx is None:
486
- sx = Length("5cm")
487
- ssx = float(sx)
488
- if sy is None:
489
- sy = Length("5cm")
490
- ssy = float(sy)
491
- if branch is None:
492
- branch = Length("4cm")
493
- blen = float(branch)
494
- if iterations is None:
495
- iterations = 10
496
- iterations = int(iterations)
497
- except ValueError:
498
- channel("Invalid data provided")
499
- return
500
- start_pt = Point(ssx, ssy)
501
- end_pt = Point.polar(start_pt, 0, blen)
502
- geom = create_fractal_tree(start_pt, end_pt, iterations, ratio)
503
- node = self.elem_branch.add(type="elem path", geometry=geom)
504
- node.stroke = self.default_stroke
505
- node.stroke_width = self.default_strokewidth
506
- node.altered()
507
- node.functional_parameter = (
508
- "fractaltree",
509
- 0,
510
- start_pt.x,
511
- start_pt.y,
512
- 0,
513
- end_pt.x,
514
- end_pt.y,
515
- 1,
516
- iterations,
517
- 2,
518
- ratio,
519
- )
520
- # Newly created! Classification needed?
521
- data = [node]
522
- post.append(classify_new(data))
523
- return "elements", data
524
-
525
- def update_node_fractaltree(node):
526
- my_id = "fractaltree"
527
- point_a = None
528
- point_b = None
529
- iterations = 5
530
- ratio = 0.75
531
- valid = True
532
- try:
533
- param = node.functional_parameter
534
- if param is None or param[0] != my_id:
535
- valid = False
536
- except (AttributeError, IndexError):
537
- valid = False
538
- if not valid:
539
- # Not for me...
540
- return
541
- try:
542
- if param[1] == 0:
543
- point_a = Point(param[2], param[3])
544
- if param[4] == 0:
545
- point_b = Point(param[5], param[6])
546
- if param[7] == 1:
547
- iterations = param[8]
548
- if param[9] == 2:
549
- ratio = param[10]
550
- except IndexError:
551
- valid = False
552
- if point_a is None or point_b is None:
553
- valid = False
554
- if valid:
555
- geom = create_fractal_tree(point_a, point_b, iterations, ratio)
556
- node.geometry = geom
557
- node.altered()
558
-
559
- # --- Fractal Dragon - now superceded by generic tfractal routine
560
- # def create_fractal_dragon(first_pt, second_pt, iterations):
561
- #
562
- # # Code based on https://github.com/GuidoDipietro/python_art
563
- # def rotate(t):
564
- # t[0], t[1] = -1 * t[1], t[0]
565
- # return t
566
- #
567
- # def turtle_lines(coords, x, y):
568
- # turtle_coords = []
569
- # for i, point in enumerate(coords):
570
- # line_coords = [(x, y), (x + point[0], y + point[1])]
571
- # x, y = x + point[0], y + point[1]
572
- # turtle_coords.append(line_coords)
573
- # return turtle_coords
574
- #
575
- # step = first_pt.distance_to(second_pt)
576
- # origin_x = first_pt.x
577
- # origin_y = first_pt.y
578
- #
579
- # # list of moves in x,y axes. Start with (1,0) (straight line to right)
580
- # moves = [[step, 0]]
581
- #
582
- # for i in range(0, iterations):
583
- # copied = list(map(rotate, copy.deepcopy(moves)[::-1]))
584
- # moves += copied
585
- #
586
- # dragon_coords = turtle_lines(moves, origin_x, origin_y)
587
- #
588
- # geometry = Geomstr()
589
- # for c in dragon_coords:
590
- # geometry.line(c[0][0] + 1j * c[0][1], c[1][0] + 1j * c[1][1])
591
- # return geometry
592
- #
593
- # @self.console_argument("sx", type=Length)
594
- # @self.console_argument("sy", type=Length)
595
- # @self.console_argument("step", type=Length)
596
- # @self.console_argument("iterations", type=int)
597
- # @context.console_command(
598
- # "fractal_dragon", help=_("fractal_dragon sx, sy, step, iterations")
599
- # )
600
- # def fractal_dragon(
601
- # command,
602
- # channel,
603
- # _,
604
- # sx=None,
605
- # sy=None,
606
- # step=None,
607
- # iterations=None,
608
- # data=None,
609
- # post=None,
610
- # **kwargs,
611
- # ):
612
- # ratio = 0.75
613
- # try:
614
- # if sx is None:
615
- # sx = Length("5cm")
616
- # ssx = float(sx)
617
- # if sy is None:
618
- # sy = Length("5cm")
619
- # ssy = float(sy)
620
- # if step is None:
621
- # step = Length("0.5cm")
622
- # blen = float(step)
623
- # if iterations is None:
624
- # iterations = 10
625
- # iterations = int(iterations)
626
- # except ValueError:
627
- # channel("Invalid data provided")
628
- # return
629
- # start_pt = Point(ssx, ssy)
630
- # end_pt = Point.polar(start_pt, 0, blen)
631
- # geom = create_fractal_dragon(start_pt, end_pt, iterations)
632
- # node = self.elem_branch.add(type="elem path", geometry=geom)
633
- # node.stroke = self.default_stroke
634
- # node.stroke_width = self.default_strokewidth
635
- # node.altered()
636
- # node.functional_parameter = (
637
- # "fractaldragon",
638
- # 0,
639
- # start_pt.x,
640
- # start_pt.y,
641
- # 0,
642
- # end_pt.x,
643
- # end_pt.y,
644
- # 1,
645
- # iterations,
646
- # )
647
- # # Newly created! Classification needed?
648
- # data = [node]
649
- # post.append(classify_new(data))
650
- # return "elements", data
651
- #
652
- # def update_node_fractaldragon(node):
653
- # my_id = "fractaldragon"
654
- # point_a = None
655
- # point_b = None
656
- # iterations = 5
657
- # valid = True
658
- # try:
659
- # param = node.functional_parameter
660
- # if param is None or param[0] != my_id:
661
- # valid = False
662
- # except (AttributeError, IndexError):
663
- # valid = False
664
- # if not valid:
665
- # # Not for me...
666
- # return
667
- # try:
668
- # if param[1] == 0:
669
- # point_a = Point(param[2], param[3])
670
- # if param[4] == 0:
671
- # point_b = Point(param[5], param[6])
672
- # if param[7] == 1:
673
- # iterations = param[8]
674
- # except IndexError:
675
- # valid = False
676
- # if point_a is None or point_b is None:
677
- # valid = False
678
- # if valid:
679
- # geom = create_fractal_dragon(point_a, point_b, iterations)
680
- # node.geometry = geom
681
- # node.altered()
682
- # dist = point_a.distance_to(point_b)
683
- # # Put second pt at right spot
684
- # point_b = Point(point_a.x + dist, point_a.y)
685
- #
686
- # node.functional_parameter = (
687
- # "fractaldragon",
688
- # 0,
689
- # point_a.x,
690
- # point_a.y,
691
- # 0,
692
- # point_b.x,
693
- # point_b.y,
694
- # 1,
695
- # iterations,
696
- # )
697
-
698
- # Cycloid Shape
699
- def create_cycloid_shape(cx, cy, r_major, r_minor, rotations):
700
- series = []
701
- degree_step = 1
702
- if rotations == 0:
703
- rotations = 20
704
-
705
- radian_step = math.radians(degree_step)
706
- t = 0
707
- m = math.tau * rotations
708
- if r_minor == 0:
709
- r_minor = 1
710
- count = 0
711
- offset = 0
712
- while t < m:
713
- px = (r_minor + r_major) * math.cos(t) - (r_minor + offset) * math.cos(
714
- ((r_major + r_minor) / r_minor) * t
715
- )
716
- py = (r_minor + r_major) * math.sin(t) - (r_minor + offset) * math.sin(
717
- ((r_major + r_minor) / r_minor) * t
718
- )
719
- series.append((px + cx, py + cy))
720
- t += radian_step
721
- count += 1
722
- # print(
723
- # f"Done: {count} steps, major={r_major:.0f}, minor={r_minor:.0f}, offset={offset:.0f}, rot={rotations}"
724
- # )
725
- geometry = Geomstr()
726
- last = None
727
- for m in series:
728
- if last is not None:
729
- geometry.line(last[0] + 1j * last[1], m[0] + 1j * m[1])
730
- last = m
731
- return geometry
732
-
733
- @self.console_argument("sx", type=Length)
734
- @self.console_argument("sy", type=Length)
735
- @self.console_argument("r_major", type=Length)
736
- @self.console_argument("r_minor", type=Length)
737
- @self.console_argument("iterations", type=int)
738
- @context.console_command(
739
- "cycloid", help=_("cycloid sx sy r_major r_minor iterations")
740
- )
741
- def cycloid(
742
- command,
743
- channel,
744
- _,
745
- sx=None,
746
- sy=None,
747
- r_major=None,
748
- r_minor=None,
749
- iterations=None,
750
- data=None,
751
- post=None,
752
- **kwargs,
753
- ):
754
- try:
755
- if sx is None:
756
- sx = Length("5cm")
757
- ssx = float(sx)
758
- if sy is None:
759
- sy = Length("5cm")
760
- ssy = float(sy)
761
- if r_major is None:
762
- r_major = Length("5cm")
763
- radius_major = float(r_major)
764
- if r_minor is None:
765
- r_minor = Length("3cm")
766
- radius_minor = float(r_minor)
767
- if iterations is None:
768
- iterations = 20
769
- iterations = int(iterations)
770
- except ValueError:
771
- channel("Invalid data provided")
772
- return
773
- start_pt = Point(ssx, ssy)
774
- point_major = Point(ssx + radius_major, ssy)
775
- point_minor = Point(ssx, ssy - radius_minor)
776
- geom = create_cycloid_shape(
777
- ssx, ssy, radius_major, radius_minor, iterations
778
- )
779
- node = self.elem_branch.add(type="elem path", geometry=geom)
780
- node.label = f"Cycloid {iterations} iterations"
781
- node.stroke = self.default_stroke
782
- node.stroke_width = self.default_strokewidth
783
- node.altered()
784
- node.functional_parameter = (
785
- "cycloid",
786
- 0,
787
- start_pt.x,
788
- start_pt.y,
789
- 0,
790
- point_major.x,
791
- point_major.y,
792
- 0,
793
- point_minor.x,
794
- point_minor.y,
795
- 1,
796
- iterations,
797
- )
798
- # Newly created! Classification needed?
799
- data = [node]
800
- post.append(classify_new(data))
801
- return "elements", data
802
-
803
- def update_node_cycloid(node):
804
- my_id = "cycloid"
805
- valid = True
806
- try:
807
- param = node.functional_parameter
808
- if param is None or param[0] != my_id:
809
- valid = False
810
- except (AttributeError, IndexError):
811
- valid = False
812
- if not valid:
813
- # Not for me...
814
- return
815
- point_c = None
816
- point_major = None
817
- point_minor = None
818
- try:
819
- if param[1] == 0:
820
- point_c = Point(param[2], param[3])
821
- if param[4] == 0:
822
- point_major = Point(param[5], param[6])
823
- if param[7] == 0:
824
- point_minor = Point(param[8], param[9])
825
- if param[10] == 1:
826
- iterations = param[11]
827
- except IndexError:
828
- valid = False
829
- if point_c is None or point_major is None or point_minor is None:
830
- valid = False
831
- if valid:
832
- radius_major = point_c.distance_to(point_major)
833
- radius_minor = point_c.distance_to(point_minor)
834
- ssx = point_c.x
835
- ssy = point_c.y
836
- geom = create_cycloid_shape(
837
- ssx, ssy, radius_major, radius_minor, iterations
838
- )
839
- node.geometry = geom
840
- node.altered()
841
- point_major = Point(ssx + radius_major, ssy)
842
- point_minor = Point(ssx, ssy - radius_minor)
843
- node.functional_parameter = (
844
- "cycloid",
845
- 0,
846
- point_c.x,
847
- point_c.y,
848
- 0,
849
- point_major.x,
850
- point_major.y,
851
- 0,
852
- point_minor.x,
853
- point_minor.y,
854
- 1,
855
- iterations,
856
- )
857
-
858
- # --- Star like shapes
859
- def create_star_shape(
860
- cx,
861
- cy,
862
- corners,
863
- startangle,
864
- radius,
865
- radius_inner,
866
- alternate_seq,
867
- density,
868
- ):
869
- geom = Geomstr()
870
- center = Point(cx, cy)
871
- if startangle is None:
872
- startangle = 0
873
-
874
- if corners <= 2:
875
- if corners == 1:
876
- geom.point(cx + 1j * cy)
877
- if corners == 2:
878
- x = cx + math.cos(startangle) * radius
879
- y = cy + math.sin(startangle) * radius
880
- geom.line(cx + 1j * cy, x + 1j * y)
881
- else:
882
- i_angle = startangle
883
- delta_angle = math.tau / corners
884
- ct = 0
885
- pts = []
886
- for j in range(corners):
887
- if ct < alternate_seq:
888
- r = radius
889
- # dbg = "outer"
890
- else:
891
- r = radius_inner
892
- # dbg = "inner"
893
- thisx = cx + r * math.cos(i_angle)
894
- thisy = cy + r * math.sin(i_angle)
895
- # tpoint = center.polar_to(i_angle, r)
896
- # thisx = tpoint.x
897
- # thisy = tpoint.y
898
- # print(
899
- # "pt %d, Angle=%.1f: %s radius=%.1f: (%.1f, %.1f)"
900
- # % (j, i_angle / math.pi * 180, dbg, r, thisx, thisy)
901
- # )
902
- ct += 1
903
- if ct >= 2 * alternate_seq:
904
- ct = 0
905
- if j == 0:
906
- firstx = thisx
907
- firsty = thisy
908
- i_angle += delta_angle
909
- thispt = thisx + 1j * thisy
910
- pts.append(thispt)
911
- # Close the path
912
- thispt = firstx + 1j * firsty
913
- pts.append(thispt)
914
-
915
- if len(pts) > 0:
916
- star_points = [pts[0]]
917
- idx = density
918
- hitted = []
919
- while idx != 0:
920
- if idx in hitted:
921
- break
922
- hitted.append(idx)
923
- star_points.append(pts[idx])
924
- idx += density
925
- if idx >= corners:
926
- idx -= corners
927
- for idx in range(1, len(star_points)):
928
- geom.line(star_points[idx - 1], star_points[idx])
929
- geom.line(star_points[-1], star_points[0])
930
- # print(f"Created geometry from {len(pts) / 2} pts: {geom.capacity}")
931
- return geom
932
-
933
- # Shape (ie star) routine
934
- @self.console_argument(
935
- "corners", type=int, help=_("Number of corners/vertices")
936
- )
937
- @self.console_argument(
938
- "cx", type=self.length_x, help=_("X-Value of polygon's center")
939
- )
940
- @self.console_argument(
941
- "cy", type=self.length_y, help=_("Y-Value of polygon's center")
942
- )
943
- @self.console_argument(
944
- "radius",
945
- type=self.length_x,
946
- help=_("Radius (length of side if --side_length is used)"),
947
- )
948
- @self.console_option("startangle", "s", type=Angle.parse, help=_("Start-Angle"))
949
- @self.console_option(
950
- "inscribed",
951
- "i",
952
- type=bool,
953
- action="store_true",
954
- help=_("Shall the polygon touch the inscribing circle?"),
955
- )
956
- @self.console_option(
957
- "side_length",
958
- "l",
959
- type=bool,
960
- action="store_true",
961
- help=_(
962
- "Do you want to treat the length value for radius as the length of one edge instead?"
963
- ),
964
- )
965
- @self.console_option(
966
- "radius_inner",
967
- "r",
968
- type=str,
969
- help=_("Alternating radius for every other vertex"),
970
- )
971
- @self.console_option(
972
- "alternate_seq",
973
- "a",
974
- type=int,
975
- help=_(
976
- "Length of alternating sequence (1 for starlike figures, >=2 for more gear-like patterns)"
977
- ),
978
- )
979
- @self.console_option(
980
- "density", "d", type=int, help=_("Amount of vertices to skip")
981
- )
982
- @self.console_command(
983
- "shape",
984
- help=_(
985
- "shape <corners> <x> <y> <r> <startangle> <inscribed> or shape <corners> <r>"
986
- ),
987
- input_type=("elements", None),
988
- output_type="elements",
989
- )
990
- def element_shape(
991
- command,
992
- channel,
993
- _,
994
- corners,
995
- cx,
996
- cy,
997
- radius,
998
- startangle=None,
999
- inscribed=None,
1000
- side_length=None,
1001
- radius_inner=None,
1002
- alternate_seq=None,
1003
- density=None,
1004
- data=None,
1005
- post=None,
1006
- **kwargs,
1007
- ):
1008
- if corners is None:
1009
- raise CommandSyntaxError
1010
-
1011
- if cx is None:
1012
- if corners <= 2:
1013
- raise CommandSyntaxError(
1014
- _(
1015
- "Please provide at least one additional value (which will act as radius then)"
1016
- )
1017
- )
1018
- cx = 0
1019
- if cy is None:
1020
- cy = 0
1021
- if radius is None:
1022
- radius = 0
1023
- sangle = float(startangle)
1024
- if corners <= 2:
1025
- # No need to look at side_length parameter as we are considering the radius value as an edge anyway...
1026
- geom = create_star_shape(
1027
- cx,
1028
- cy,
1029
- corners,
1030
- sangle,
1031
- radius,
1032
- radius_inner,
1033
- alternate_seq,
1034
- density,
1035
- )
1036
- else:
1037
- # do we have something like 'shape 3 4cm' ? If yes, reassign the parameters
1038
- if radius is None:
1039
- radius = cx
1040
- cx = 0
1041
- cy = 0
1042
- if startangle is None:
1043
- startangle = Angle.parse("0deg")
1044
-
1045
- sangle = float(startangle)
1046
- if alternate_seq is None:
1047
- if radius_inner is None:
1048
- alternate_seq = 0
1049
- else:
1050
- alternate_seq = 1
1051
-
1052
- if density is None:
1053
- density = 1
1054
- if density < 1 or density > corners:
1055
- density = 1
1056
-
1057
- # Do we have to consider the radius value as the length of one corner?
1058
- if side_length is not None:
1059
- # Let's recalculate the radius then...
1060
- # d_oc = s * csc( math.pi / n)
1061
- radius = 0.5 * radius / math.sin(math.pi / corners)
1062
-
1063
- if radius_inner is None:
1064
- radius_inner = radius
1065
- else:
1066
- try:
1067
- radius_inner = float(
1068
- Length(radius_inner, relative_length=radius)
1069
- )
1070
- except ValueError:
1071
- raise CommandSyntaxError
1072
-
1073
- if inscribed:
1074
- if side_length is None:
1075
- radius = radius / math.cos(math.pi / corners)
1076
- else:
1077
- channel(
1078
- _(
1079
- "You have as well provided the --side_length parameter, this takes precedence, so --inscribed is ignored"
1080
- )
1081
- )
1082
-
1083
- if alternate_seq < 1:
1084
- radius_inner = radius
1085
-
1086
- # print(
1087
- # "Your parameters are:\n cx=%.1f, cy=%.1f\n radius=%.1f, inner=%.1f\n corners=%d, density=%d\n seq=%d, angle=%.1f"
1088
- # % (cx, cy, radius, radius_inner, corners, density, alternate_seq, startangle)
1089
- # )
1090
- geom = create_star_shape(
1091
- cx,
1092
- cy,
1093
- corners,
1094
- sangle,
1095
- radius,
1096
- radius_inner,
1097
- alternate_seq,
1098
- density,
1099
- )
1100
- pts = list(geom.as_points())
1101
- if len(pts) < corners:
1102
- ct = 0
1103
- possible_combinations = ""
1104
- for i in range(corners - 1):
1105
- j = i + 2
1106
- if math.gcd(j, corners) == 1:
1107
- if ct % 3 == 0:
1108
- possible_combinations += (
1109
- f"\n shape {corners} ... -d {j}"
1110
- )
1111
- else:
1112
- possible_combinations += (
1113
- f", shape {corners} ... -d {j} "
1114
- )
1115
- ct += 1
1116
- channel(_("Just for info: we have missed a couple of vertices..."))
1117
- channel(
1118
- _(
1119
- "To hit all, the density parameters should be e.g. {combinations}"
1120
- ).format(combinations=possible_combinations)
1121
- )
1122
-
1123
- node = self.elem_branch.add(type="elem path", geometry=geom)
1124
- node.stroke = self.default_stroke
1125
- node.stroke_width = self.default_strokewidth
1126
- node.fill = self.default_fill
1127
- node.altered()
1128
- node.emphasized = True
1129
- node.focus()
1130
-
1131
- data = [node]
1132
- # Newly created! Classification needed?
1133
- post.append(classify_new(data))
1134
-
1135
- center = Point(cx, cy)
1136
- sangle = float(startangle)
1137
- opposing_angle = sangle + math.tau / 2
1138
- while opposing_angle < 0:
1139
- opposing_angle += math.tau
1140
- while opposing_angle >= math.tau:
1141
- opposing_angle -= math.tau
1142
- first_point = Point.polar(center, sangle, radius)
1143
- second_point = Point.polar(center, opposing_angle, radius_inner)
1144
- node.functional_parameter = (
1145
- "star",
1146
- 0,
1147
- cx,
1148
- cy,
1149
- 0,
1150
- first_point.x,
1151
- first_point.y,
1152
- 0,
1153
- second_point.x,
1154
- second_point.y,
1155
- 1,
1156
- corners,
1157
- 1,
1158
- alternate_seq,
1159
- 1,
1160
- density,
1161
- )
1162
-
1163
- return "elements", data
1164
-
1165
- # --- end of node update routines
1166
- def update_node_star_shape(node):
1167
- my_id = "star"
1168
- valid = True
1169
- try:
1170
- param = node.functional_parameter
1171
- if param is None or param[0] != my_id:
1172
- valid = False
1173
- except (AttributeError, IndexError):
1174
- valid = False
1175
- if not valid:
1176
- # Not for me...
1177
- return
1178
- param = node.functional_parameter
1179
- if param is None:
1180
- return
1181
- cx = getit(param, 2, 0)
1182
- cy = getit(param, 3, 0)
1183
- p1x = getit(param, 5, 0)
1184
- p1y = getit(param, 6, 0)
1185
- p2x = getit(param, 8, 0)
1186
- p2y = getit(param, 9, 0)
1187
- corners = getit(param, 11, 3)
1188
- alternate_seq = getit(param, 13, 0)
1189
- density = getit(param, 15, 1)
1190
- if density < 1 or density > corners:
1191
- density = 1
1192
- center = Point(cx, cy)
1193
- pt1 = Point(p1x, p1y)
1194
- pt2 = Point(p2x, p2y)
1195
- startangle = center.angle_to(pt1)
1196
- radius = center.distance_to(pt1)
1197
- radius_inner = center.distance_to(pt2)
1198
- if alternate_seq < 1:
1199
- radius_inner = radius
1200
- if radius_inner == radius:
1201
- alternate_seq = 0
1202
- if radius == 0:
1203
- valid = False
1204
- if corners <= 0:
1205
- valid = False
1206
- if alternate_seq < 0:
1207
- valid = False
1208
- if valid:
1209
- geom = create_star_shape(
1210
- cx,
1211
- cy,
1212
- corners,
1213
- startangle,
1214
- radius,
1215
- radius_inner,
1216
- alternate_seq,
1217
- density,
1218
- )
1219
- node.geometry = geom
1220
- node.altered()
1221
- center = Point(cx, cy)
1222
- opposing_angle = startangle + math.tau / 2
1223
- while opposing_angle < 0:
1224
- opposing_angle += math.tau
1225
- while opposing_angle >= math.tau:
1226
- opposing_angle -= math.tau
1227
- first_point = Point.polar(center, startangle, radius)
1228
- second_point = Point.polar(center, opposing_angle, radius_inner)
1229
- node.functional_parameter = (
1230
- "star",
1231
- 0,
1232
- cx,
1233
- cy,
1234
- 0,
1235
- first_point.x,
1236
- first_point.y,
1237
- 0,
1238
- second_point.x,
1239
- second_point.y,
1240
- 1,
1241
- corners,
1242
- 1,
1243
- alternate_seq,
1244
- 1,
1245
- density,
1246
- )
1247
-
1248
- def create_growing_shape(
1249
- cx, cy, sides, iterations, ratio_in_percent, sidelen, startangle, gap
1250
- ):
1251
- geom = Geomstr()
1252
- if sides < 3:
1253
- sides = 3
1254
- shape_angle = math.tau / sides
1255
- myangle = startangle
1256
- sidelength = sidelen
1257
- sidedelta = sidelength * ratio_in_percent / 100.0
1258
- pt1 = cx + 1j * cy
1259
- for idx in range(iterations):
1260
- for side in range(sides):
1261
- while myangle < 0:
1262
- myangle += math.tau
1263
- while myangle > math.tau:
1264
- myangle -= math.tau
1265
-
1266
- pt2 = geom.polar(pt1, myangle, sidelength)
1267
- geom.line(pt1, pt2)
1268
- pt1 = pt2
1269
- sidelength += sidedelta
1270
- # sidedelta = sidelength * ratio_in_percent
1271
- myangle += shape_angle
1272
- myangle += gap
1273
- return geom
1274
-
1275
- def update_node_growing_shape(node):
1276
- my_id = "growingshape"
1277
- valid = True
1278
- try:
1279
- param = node.functional_parameter
1280
- if param is None or param[0] != my_id:
1281
- valid = False
1282
- except (AttributeError, IndexError):
1283
- valid = False
1284
- if not valid:
1285
- # Not for me...
1286
- return
1287
- param = node.functional_parameter
1288
- if param is None:
1289
- return
1290
- pt0x = getit(param, 2, 0)
1291
- pt0y = getit(param, 3, 0)
1292
- pt1x = getit(param, 5, 0)
1293
- pt1y = getit(param, 6, 0)
1294
- ratio_in_percent = getit(param, 8, 0)
1295
- sides = getit(param, 10, 0)
1296
- iterations = getit(param, 12, 0)
1297
- igap = getit(param, 14, 0)
1298
- pt0 = Point(pt0x, pt0y)
1299
- pt1 = Point(pt1x, pt1y)
1300
- sidelen = pt0.distance_to(pt1)
1301
- startangle = pt0.angle_to(pt1)
1302
- gap = igap / 360 * math.tau
1303
- geom = create_growing_shape(
1304
- pt0x,
1305
- pt0y,
1306
- sides,
1307
- iterations,
1308
- ratio_in_percent,
1309
- sidelen,
1310
- startangle,
1311
- gap,
1312
- )
1313
- node.geometry = geom
1314
- node.altered()
1315
- pt0 = None
1316
- pt1 = None
1317
- for pt in geom.as_points():
1318
- if pt0 is None:
1319
- pt0 = Point(pt.real, pt.imag)
1320
- elif pt1 is None:
1321
- pt1 = Point(pt.real, pt.imag)
1322
- else:
1323
- break
1324
- opposite_angle = math.tau / 2 + gap
1325
- while opposite_angle > math.tau:
1326
- opposite_angle -= math.tau
1327
- while opposite_angle < 0:
1328
- opposite_angle += math.tau
1329
- pt2 = Point.polar(pt0, opposite_angle, sidelen * ratio_in_percent)
1330
- node.functional_parameter = (
1331
- "growingshape",
1332
- 0,
1333
- pt0.x,
1334
- pt0.y,
1335
- 0,
1336
- pt1.x,
1337
- pt1.y,
1338
- 1,
1339
- ratio_in_percent,
1340
- 1,
1341
- sides,
1342
- 1,
1343
- iterations,
1344
- 1,
1345
- igap,
1346
- )
1347
-
1348
- @self.console_argument("sx", type=Length)
1349
- @self.console_argument("sy", type=Length)
1350
- @self.console_argument("sides", type=int)
1351
- @self.console_argument("iterations", type=int)
1352
- @self.console_argument("firstlength", type=Length)
1353
- @self.console_option("ratio", "r", type=int, help=_("Growth in %"))
1354
- @self.console_option("angle", "a", type=Angle, help=_("Start angle"))
1355
- @self.console_option(
1356
- "gap", "g", type=int, help=_("Delta angle between moves in degrees")
1357
- )
1358
- @context.console_command(
1359
- "growingshape", help=_("growingshape sx sy sides iterations")
1360
- )
1361
- def growing_shape(
1362
- command,
1363
- channel,
1364
- _,
1365
- sx=None,
1366
- sy=None,
1367
- sides=None,
1368
- iterations=None,
1369
- firstlength=None,
1370
- ratio=None,
1371
- angle=None,
1372
- gap=None,
1373
- data=None,
1374
- post=None,
1375
- **kwargs,
1376
- ):
1377
- try:
1378
- if sx is None:
1379
- sx = Length("0cm")
1380
- ssx = float(sx)
1381
- if sy is None:
1382
- sy = Length("0cm")
1383
- ssy = float(sy)
1384
- if sides is None or sides < 3:
1385
- sides = 3
1386
- if iterations is None:
1387
- iterations = 5
1388
- if iterations < 1:
1389
- iterations = 1
1390
- if angle is None:
1391
- angle = Angle.parse("0deg")
1392
- startangle = float(angle)
1393
- if firstlength is None:
1394
- firstlength = Length("0.5cm")
1395
- sidelen = float(firstlength)
1396
- if ratio is None:
1397
- ratio = 2 # 2% growth
1398
- if gap is None:
1399
- gap = 0
1400
- except ValueError:
1401
- channel("Invalid data provided")
1402
- return
1403
- gap_angle = gap / 360 * math.tau
1404
- geom = create_growing_shape(
1405
- ssx,
1406
- ssy,
1407
- sides,
1408
- iterations,
1409
- ratio,
1410
- sidelen,
1411
- startangle,
1412
- gap_angle,
1413
- )
1414
- node = self.elem_branch.add(type="elem path", geometry=geom)
1415
- node.label = f"Growing Polygon w. {sides} sides"
1416
- node.stroke = self.default_stroke
1417
- node.stroke_width = 1000 # self.default_strokewidth
1418
- node.altered()
1419
- pt0 = None
1420
- pt1 = None
1421
- for pt in geom.as_points():
1422
- if pt0 is None:
1423
- pt0 = Point(pt.real, pt.imag)
1424
- elif pt1 is None:
1425
- pt1 = Point(pt.real, pt.imag)
1426
- else:
1427
- break
1428
- node.functional_parameter = (
1429
- "growingshape",
1430
- 0,
1431
- pt0.x,
1432
- pt0.y,
1433
- 0,
1434
- pt1.x,
1435
- pt1.y,
1436
- 1,
1437
- ratio,
1438
- 1,
1439
- sides,
1440
- 1,
1441
- iterations,
1442
- 1,
1443
- gap,
1444
- )
1445
- # Newly created! Classification needed?
1446
- data = [node]
1447
- post.append(classify_new(data))
1448
- return "elements", data
1449
-
1450
- # Let's register them
1451
- # The info tuple contains three entries
1452
- # 1) The function to be called to update the node after a parameter change
1453
- # 2) A dict with information how to read/display the different parameter entries
1454
- # 3) A boolean parameter that indicates whether this is a routine that needs
1455
- # to be automatically called after a change of a related source node
1456
- # This needs the id of the related node to be in the very 'first' parameter
1457
- # of the functional_parameter structure, so something like
1458
- # node.functional_parameter = ("grid", 3, source_node.id, ....)
1459
- info = (
1460
- update_node_grid,
1461
- {
1462
- "0": ("ID",),
1463
- "1": ("Horizontal gap",),
1464
- "2": ("Vertical gap",),
1465
- "3": ("Columns", 1, 25),
1466
- "4": ("Rows", 1, 25),
1467
- },
1468
- True, # Yes this something that needs to be updated on source changes
1469
- )
1470
- kernel.register("element_update/grid", info)
1471
-
1472
- info = (
1473
- update_node_fractaltree,
1474
- {
1475
- "0": ("Startpoint",),
1476
- "1": ("End of base stem",),
1477
- "2": ("Iterations", 2, 13),
1478
- "3": ("Branch length",),
1479
- },
1480
- False,
1481
- )
1482
- kernel.register("element_update/fractaltree", info)
1483
-
1484
- # info = (
1485
- # update_node_fractaldragon,
1486
- # {
1487
- # "0": ("Startpoint",),
1488
- # "1": ("End of base stem",),
1489
- # "2": ("Iterations", 2, 20),
1490
- # },
1491
- # )
1492
- # kernel.register("element_update/fractaldragon", info)
1493
-
1494
- info = (
1495
- update_node_cycloid,
1496
- {
1497
- "0": ("Startpoint",),
1498
- "1": ("Major axis",),
1499
- "2": ("Minor axis",),
1500
- "3": ("Iterations", 2, 30),
1501
- },
1502
- False,
1503
- )
1504
- kernel.register("element_update/cycloid", info)
1505
-
1506
- max_corner_gui = 50
1507
- info = (
1508
- update_node_star_shape,
1509
- {
1510
- "0": ("Center",),
1511
- "1": ("Outer radius",),
1512
- "2": ("Inner radius",),
1513
- "3": ("Corners", 3, max_corner_gui),
1514
- "4": ("Alternation", 0, 10),
1515
- "5": ("Density", 1, max_corner_gui - 1),
1516
- },
1517
- False,
1518
- )
1519
- kernel.register("element_update/star", info)
1520
-
1521
- info = (
1522
- update_node_tfractal,
1523
- {
1524
- "0": ("Turtle code",),
1525
- "1": ("Base code",),
1526
- "2": ("Segmentation", 1, 20),
1527
- "3": ("Iterations", 1, 20),
1528
- "4": ("Shaping", 1, 20),
1529
- "5": ("Cornertype", 0, 2),
1530
- },
1531
- False,
1532
- )
1533
- kernel.register("element_update/tfractal", info)
1534
-
1535
- info = (
1536
- None, # Let the node deal with it
1537
- {
1538
- "0": ("Rounded corner",),
1539
- },
1540
- False,
1541
- )
1542
- kernel.register("element_update/rect", info)
1543
-
1544
- info = (
1545
- update_node_growing_shape,
1546
- {
1547
- "0": ("First point",),
1548
- "1": ("First edge",),
1549
- "2": ("Growth Ratio", 0, 100),
1550
- "3": ("Sides", 3, 12),
1551
- "4": ("Iterations", 1, 45),
1552
- "5": ("Gap", 0, 15),
1553
- },
1554
- False,
1555
- )
1556
- kernel.register("element_update/growingshape", info)
1
+ """
2
+ This module exposes a couple of routines to create shapes,
3
+ that have additional functional parameters set to allow
4
+ parametric editing
5
+ """
6
+ import math
7
+
8
+ from meerk40t.core.units import Angle, Length
9
+ from meerk40t.kernel import CommandSyntaxError
10
+ from meerk40t.svgelements import Point
11
+ from meerk40t.tools.geomstr import Geomstr
12
+
13
+
14
+ def plugin(kernel, lifecycle):
15
+ if lifecycle == "register":
16
+ _ = kernel.translation
17
+ context = kernel.root
18
+ self = context.elements
19
+ classify_new = self.post_classify
20
+
21
+ def getit(param, idx, default):
22
+ if idx >= len(param):
23
+ return default
24
+ return param[idx]
25
+
26
+ def create_copied_grid(start_pt, node, cols, rows, sdx, sdy):
27
+ # print(
28
+ # f"Create a {cols}x{rows} grid, gap={sdx:.0f} x {sdy:.0f} at ({start_pt.x:.0f}, {start_pt.y:.0f})..."
29
+ # )
30
+ geom = Geomstr()
31
+ try:
32
+ if isinstance(node, Geomstr):
33
+ # That may be the case if we have lost the connection to the original node.
34
+ # In that case we are getting passed the first geometry subpath to reuse
35
+ orggeom = node
36
+ else:
37
+ orggeom = node.as_geometry()
38
+ bb = orggeom.bbox()
39
+ width = bb[2] - bb[0]
40
+ height = bb[3] - bb[1]
41
+ except AttributeError:
42
+ return geom
43
+ x = start_pt.x
44
+ for scol in range(cols):
45
+ y = start_pt.y
46
+ for srow in range(rows):
47
+ tempgeom = Geomstr(orggeom)
48
+ dx = x - bb[0]
49
+ dy = y - bb[1]
50
+ # print(f"{scol}x{srow}: translate by {dx:.0f}x{dy:.0f}")
51
+ tempgeom.translate(dx, dy)
52
+ geom.append(tempgeom)
53
+ y += sdy + height
54
+ x += sdx + width
55
+ return geom
56
+
57
+ @self.console_argument("sx", type=Length)
58
+ @self.console_argument("sy", type=Length)
59
+ @self.console_argument("cols", type=int)
60
+ @self.console_argument("rows", type=int)
61
+ @self.console_argument("id", type=str)
62
+ @self.console_option("dx", "x", type=Length, help=_("Horizontal delta"))
63
+ @self.console_option("dy", "y", type=Length, help=_("Vertical delta"))
64
+ @context.console_command("pgrid", help=_("pgrid sx, sy, cols, rows, id"))
65
+ def param_grid(
66
+ command,
67
+ channel,
68
+ _,
69
+ sx=None,
70
+ sy=None,
71
+ cols=None,
72
+ rows=None,
73
+ id=None,
74
+ dx=None,
75
+ dy=None,
76
+ clone=None,
77
+ post=None,
78
+ **kwargs,
79
+ ):
80
+ try:
81
+ if sx is None:
82
+ sx = Length("0cm")
83
+ ssx = float(sx)
84
+ if sy is None:
85
+ sy = Length("0cm")
86
+ ssy = float(sy)
87
+ if cols is None:
88
+ cols = 2
89
+ if rows is None:
90
+ rows = 2
91
+ if dx is None:
92
+ dx = Length("0mm")
93
+ sdx = float(dx)
94
+ if dy is None:
95
+ dy = Length("0mm")
96
+ sdy = float(dy)
97
+ except ValueError:
98
+ channel("Invalid data provided")
99
+ return
100
+ self.validate_ids()
101
+ found_node = None
102
+ if id is not None:
103
+ # We look for such a node, both in elements and regmarks.
104
+ for node in self.elems():
105
+ if node.id == id and hasattr(node, "as_geometry"):
106
+ found_node = node
107
+ break
108
+ if found_node is None:
109
+ for node in self.regmarks():
110
+ if node.id == id and hasattr(node, "as_geometry"):
111
+ found_node = node
112
+ break
113
+ if found_node is None:
114
+ # We try the first selected element
115
+ for node in self.elems(emphasized=True):
116
+ if hasattr(node, "as_geometry"):
117
+ id = node.id
118
+ found_node = node
119
+ break
120
+ if found_node is None:
121
+ channel("No matching element found.")
122
+ start_pt = Point(ssx, ssy)
123
+ geom = create_copied_grid(start_pt, found_node, cols, rows, sdx, sdy)
124
+ node = self.elem_branch.add(type="elem path", geometry=geom)
125
+ bb = geom.bbox()
126
+ width = bb[2] - bb[0]
127
+ # height = bb[3] - bb[1]
128
+ if hasattr(found_node, "stroke"):
129
+ node.stroke = found_node.stroke
130
+ else:
131
+ node.stroke = self.default_stroke
132
+ node.stroke_width = self.default_strokewidth
133
+ node.altered()
134
+ node.functional_parameter = (
135
+ "grid",
136
+ 3,
137
+ id,
138
+ 0,
139
+ start_pt.x + sdx,
140
+ start_pt.y,
141
+ 0,
142
+ start_pt.x + width,
143
+ start_pt.y + sdy,
144
+ 1,
145
+ cols,
146
+ 1,
147
+ rows,
148
+ )
149
+ # Newly created! Classification needed?
150
+ data = [node]
151
+ node.emphasized = True
152
+ post.append(classify_new(data))
153
+ return "elements", data
154
+
155
+ def update_node_grid(node):
156
+ my_id = "grid"
157
+ try:
158
+ param = node.functional_parameter
159
+ if param is None or param[0] != my_id:
160
+ return
161
+ except (AttributeError, IndexError):
162
+ return
163
+ node_id = getit(param, 2, None)
164
+ if node_id is None:
165
+ return
166
+ found_node = None
167
+ for e in self.elems():
168
+ if e.id == node_id and hasattr(e, "as_geometry"):
169
+ found_node = e
170
+ break
171
+ if found_node is None:
172
+ for e in self.regmarks():
173
+ if e.id == node_id and hasattr(e, "as_geometry"):
174
+ found_node = e
175
+ break
176
+ if found_node is None:
177
+ return
178
+ geom = node.as_geometry()
179
+ # That has already the matrix applied, so we need to reverse that
180
+ # Use ~ inversion operator to create an inversed copy
181
+ geom.transform(~node.matrix)
182
+ bb = geom.bbox()
183
+ start_pt = Point(bb[0], bb[1])
184
+
185
+ sdx = getit(param, 4, bb[0])
186
+ sdy = getit(param, 8, bb[1])
187
+ sdx = sdx - bb[0]
188
+ sdy = sdy - bb[1]
189
+ cols = getit(param, 10, 1)
190
+ rows = getit(param, 12, 1)
191
+
192
+ geom = create_copied_grid(start_pt, found_node, cols, rows, sdx, sdy)
193
+ node.geometry = geom
194
+ bb = geom.bbox()
195
+ node.functional_parameter = (
196
+ "grid",
197
+ 3,
198
+ node_id,
199
+ 0,
200
+ bb[0] + sdx,
201
+ bb[1],
202
+ 0,
203
+ bb[2],
204
+ bb[1] + sdy,
205
+ 1,
206
+ cols,
207
+ 1,
208
+ rows,
209
+ )
210
+ node.altered()
211
+
212
+ # --- Fractal Tree
213
+ def create_fractal_tree(first_pt, second_pt, iterations, ratio):
214
+ def tree_fractal(geom, startpt, endpt, depth, ratio):
215
+ #
216
+ # create a line from startpt to endpt and add it to the geometry
217
+ if depth < 0:
218
+ return
219
+ angle = startpt.angle_to(endpt)
220
+ dist = startpt.distance_to(endpt)
221
+ delta = math.tau / 8
222
+ # print(
223
+ # f"{depth}: ({startpt.x:.0f}, {startpt.y:.0f}) - ({endpt.x:.0f}, {endpt.y:.0f}) : {dist:.0f}"
224
+ # )
225
+ geom.line(complex(startpt.x, startpt.y), complex(endpt.x, endpt.y))
226
+ newstart = Point(endpt)
227
+ newend = Point.polar(endpt, angle - delta, dist * ratio)
228
+ tree_fractal(geom, newstart, newend, depth - 1, ratio)
229
+ newend = Point.polar(endpt, angle + delta, dist * ratio)
230
+ tree_fractal(geom, newstart, newend, depth - 1, ratio)
231
+
232
+ geometry = Geomstr()
233
+ # print("create fractal", first_pt, second_pt)
234
+ tree_fractal(geometry, first_pt, second_pt, iterations, ratio)
235
+ return geometry
236
+
237
+ @self.console_argument(
238
+ "turtle", type=str, help="turtle-path for the fractal seed"
239
+ )
240
+ @self.console_argument("n", type=int, default=4, help="Angle divisors")
241
+ @self.console_argument(
242
+ "iterations",
243
+ type=int,
244
+ default=5,
245
+ help="Number of fractal iterations to add",
246
+ )
247
+ @self.console_option(
248
+ "base", "b", type=str, help="turtle-path for the fractal base"
249
+ )
250
+ @context.console_command(
251
+ "tfractal",
252
+ help=_("tfractal iterations"),
253
+ output_type="geometry",
254
+ hidden=True,
255
+ )
256
+ def fractal_t(command, channel, turtle, n, iterations, base=None, **kwargs):
257
+ """
258
+ Add a turtle fractal to the scene. All fractals are geometry outputs.
259
+ F - Forward
260
+ f - Forward (y-flipped)
261
+ B - Forward (walking backwards)
262
+ b - Forward(walking backwards, y-flipped).
263
+ D - Set new distance (units in sqrt of specified value)
264
+ d - Set new distance (units given)
265
+ + - Turn right (also R).
266
+ - - Turn left (also L).
267
+
268
+ For example:
269
+ tfractal F+++D2Fd1-B 8 7 node
270
+
271
+ Would produce the Dragon of Eve fractal (iteration=7)
272
+ """
273
+ if turtle is None:
274
+ channel("No turtle definition found")
275
+ return
276
+ seed = Geomstr.turtle(turtle, n)
277
+ pattern_base = base
278
+ pattern_repeat = 1
279
+ if base is None:
280
+ base = Geomstr.svg("M0,0 H65535")
281
+ else:
282
+ base = Geomstr.turtle(base, n, d=65535)
283
+ if pattern_base.startswith("n"):
284
+ idx = 1
285
+ pattern = ""
286
+ while pattern_base[idx] in "0123456789":
287
+ pattern += pattern_base[idx]
288
+ idx += 1
289
+ try:
290
+ pattern_repeat = int(pattern)
291
+ except ValueError:
292
+ pass
293
+
294
+ for i in range(iterations):
295
+ base.fractal(seed)
296
+
297
+ base.parameter_store = (
298
+ "tfractal",
299
+ 3,
300
+ turtle,
301
+ 3,
302
+ pattern_base,
303
+ 1,
304
+ n,
305
+ 1,
306
+ iterations,
307
+ 1,
308
+ pattern_repeat,
309
+ 1,
310
+ 0, # line connector
311
+ )
312
+ return "geometry", base
313
+
314
+ @self.console_option(
315
+ "amount", "a", type=float, help="corner rounding amount", default=0.2
316
+ )
317
+ @self.console_command(
318
+ "round_corners", input_type="geometry", output_type="geometry"
319
+ )
320
+ def round_corners(command, channel, data, amount=0.2, **kwargs):
321
+ data.round_corners(amount)
322
+ if hasattr(data, "parameter_store"):
323
+ param = list(data.parameter_store) # make it editable
324
+ if len(param) > 12:
325
+ # Round corners
326
+ param[12] = 1
327
+ data.parameter_store = param
328
+ return "geometry", data
329
+
330
+ @self.console_option(
331
+ "amount", "a", type=float, help="corner-bezier amount", default=0.2
332
+ )
333
+ @self.console_command(
334
+ "quad_corners", input_type="geometry", output_type="geometry"
335
+ )
336
+ def quad_corners(command, channel, data, amount=0.2, **kwargs):
337
+ data.bezier_corners(amount)
338
+ if hasattr(data, "parameter_store"):
339
+ param = list(data.parameter_store) # make it editable
340
+ if len(param) > 12:
341
+ # Bezier corners
342
+ param[12] = 2
343
+ data.parameter_store = param
344
+ return "geometry", data
345
+
346
+ def update_node_tfractal(node):
347
+ my_id = "tfractal"
348
+ try:
349
+ param = node.functional_parameter
350
+ if param is None or param[0] != my_id:
351
+ return
352
+ except (AttributeError, IndexError):
353
+ return
354
+ turtle = getit(param, 2, "")
355
+ basepattern = getit(param, 4, "")
356
+ if basepattern == "":
357
+ basepattern = None
358
+ n = getit(param, 6, 1)
359
+ iterations = getit(param, 8, 1)
360
+ pattern_repeat = getit(param, 10, 1)
361
+ connector = getit(param, 12, 0)
362
+ seed = Geomstr.turtle(turtle, n)
363
+ # Let's see whether we need to reconstruct the base
364
+ # Store it
365
+ targetpattern = basepattern
366
+ if basepattern is not None:
367
+ if basepattern.startswith("n"):
368
+ basepattern = basepattern[1:]
369
+ previous = ""
370
+ while len(basepattern) and basepattern[0] in "0123456789":
371
+ previous += basepattern[0]
372
+ basepattern = basepattern[1:]
373
+ if previous:
374
+ try:
375
+ prev_len = int(previous)
376
+ except ValueError:
377
+ prev_len = 1
378
+ # Need add 1 to the length as the connector symbol
379
+ # is not present after the last repetition
380
+ if (len(basepattern) + 1) % prev_len == 0:
381
+ # old = basepattern
382
+ basepattern = basepattern[
383
+ 0 : int(len(basepattern) / prev_len)
384
+ ]
385
+ else:
386
+ # that's wrong...
387
+ pattern_repeat = 1
388
+
389
+ if pattern_repeat > 1:
390
+ targetpattern = f"n{pattern_repeat}"
391
+ for idx in range(pattern_repeat):
392
+ if idx > 0:
393
+ targetpattern += "+"
394
+ targetpattern += basepattern
395
+ else:
396
+ targetpattern = basepattern
397
+
398
+ if targetpattern == "":
399
+ targetpattern = None
400
+
401
+ if targetpattern is None:
402
+ base = Geomstr.svg("M0,0 H65535")
403
+ else:
404
+ base = Geomstr.turtle(targetpattern, n, d=65535)
405
+
406
+ for i in range(iterations):
407
+ base.fractal(seed)
408
+ if connector == 2:
409
+ amount = 0.2
410
+ base.bezier_corners(amount)
411
+ elif connector == 1:
412
+ amount = 0.2
413
+ base.round_corners(amount)
414
+ node.geometry = base
415
+ node.altered()
416
+ # Rewrite the functional_parameter
417
+ node.functional_parameter = (
418
+ "tfractal",
419
+ 3,
420
+ turtle,
421
+ 3,
422
+ targetpattern,
423
+ 1,
424
+ n,
425
+ 1,
426
+ iterations,
427
+ 1,
428
+ pattern_repeat,
429
+ 1,
430
+ connector, # line connector
431
+ )
432
+
433
+ @self.console_argument("svg_path", type=str)
434
+ @self.console_argument("iterations", type=int)
435
+ @self.console_argument("inversions", nargs="*", type=int)
436
+ @context.console_command(
437
+ "ffractal",
438
+ help=_("ffractal iterations"),
439
+ output_type="geometry",
440
+ hidden=True,
441
+ )
442
+ def fractal_f(command, channel, svg_path, iterations, inversions, **kwargs):
443
+ seed = Geomstr.svg(svg_path)
444
+ segments = seed.segments
445
+ for i, q in enumerate(inversions):
446
+ if len(segments) > i:
447
+ segments[i][1] = q
448
+ segments[i][3] = q
449
+ base = Geomstr.svg("M0,0 H65535")
450
+ for i in range(iterations):
451
+ base.fractal(seed)
452
+ return "geometry", base
453
+
454
+ @self.console_argument("sx", type=Length)
455
+ @self.console_argument("sy", type=Length)
456
+ @self.console_argument("branch", type=Length)
457
+ @self.console_argument("iterations", type=int)
458
+ @context.console_command(
459
+ "fractal_tree", help=_("fractal_tree sx, sy, branch, iterations")
460
+ )
461
+ def fractal_tree(
462
+ command,
463
+ channel,
464
+ _,
465
+ sx=None,
466
+ sy=None,
467
+ branch=None,
468
+ iterations=None,
469
+ data=None,
470
+ post=None,
471
+ **kwargs,
472
+ ):
473
+ ratio = 0.75
474
+ try:
475
+ if sx is None:
476
+ sx = Length("5cm")
477
+ ssx = float(sx)
478
+ if sy is None:
479
+ sy = Length("5cm")
480
+ ssy = float(sy)
481
+ if branch is None:
482
+ branch = Length("4cm")
483
+ blen = float(branch)
484
+ if iterations is None:
485
+ iterations = 10
486
+ iterations = int(iterations)
487
+ except ValueError:
488
+ channel("Invalid data provided")
489
+ return
490
+ start_pt = Point(ssx, ssy)
491
+ end_pt = Point.polar(start_pt, 0, blen)
492
+ geom = create_fractal_tree(start_pt, end_pt, iterations, ratio)
493
+ node = self.elem_branch.add(type="elem path", geometry=geom)
494
+ node.stroke = self.default_stroke
495
+ node.stroke_width = self.default_strokewidth
496
+ node.altered()
497
+ node.functional_parameter = (
498
+ "fractaltree",
499
+ 0,
500
+ start_pt.x,
501
+ start_pt.y,
502
+ 0,
503
+ end_pt.x,
504
+ end_pt.y,
505
+ 1,
506
+ iterations,
507
+ 2,
508
+ ratio,
509
+ )
510
+ # Newly created! Classification needed?
511
+ data = [node]
512
+ node.emphasized = True
513
+ post.append(classify_new(data))
514
+ return "elements", data
515
+
516
+ def update_node_fractaltree(node):
517
+ my_id = "fractaltree"
518
+ try:
519
+ param = node.functional_parameter
520
+ if param is None or param[0] != my_id:
521
+ return
522
+ except (AttributeError, IndexError):
523
+ return
524
+ point_a = None
525
+ point_b = None
526
+ iterations = 5
527
+ ratio = 0.75
528
+ try:
529
+ if param[1] == 0:
530
+ point_a = Point(param[2], param[3])
531
+ if param[4] == 0:
532
+ point_b = Point(param[5], param[6])
533
+ if param[7] == 1:
534
+ iterations = param[8]
535
+ if param[9] == 2:
536
+ ratio = param[10]
537
+ except IndexError:
538
+ return
539
+ if point_a is None or point_b is None:
540
+ return
541
+ geom = create_fractal_tree(point_a, point_b, iterations, ratio)
542
+ node.geometry = geom
543
+ node.altered()
544
+
545
+ # --- Fractal Dragon - now superceded by generic tfractal routine
546
+ # def create_fractal_dragon(first_pt, second_pt, iterations):
547
+ #
548
+ # # Code based on https://github.com/GuidoDipietro/python_art
549
+ # def rotate(t):
550
+ # t[0], t[1] = -1 * t[1], t[0]
551
+ # return t
552
+ #
553
+ # def turtle_lines(coords, x, y):
554
+ # turtle_coords = []
555
+ # for i, point in enumerate(coords):
556
+ # line_coords = [(x, y), (x + point[0], y + point[1])]
557
+ # x, y = x + point[0], y + point[1]
558
+ # turtle_coords.append(line_coords)
559
+ # return turtle_coords
560
+ #
561
+ # step = first_pt.distance_to(second_pt)
562
+ # origin_x = first_pt.x
563
+ # origin_y = first_pt.y
564
+ #
565
+ # # list of moves in x,y axes. Start with (1,0) (straight line to right)
566
+ # moves = [[step, 0]]
567
+ #
568
+ # for i in range(0, iterations):
569
+ # copied = list(map(rotate, copy.deepcopy(moves)[::-1]))
570
+ # moves += copied
571
+ #
572
+ # dragon_coords = turtle_lines(moves, origin_x, origin_y)
573
+ #
574
+ # geometry = Geomstr()
575
+ # for c in dragon_coords:
576
+ # geometry.line(c[0][0] + 1j * c[0][1], c[1][0] + 1j * c[1][1])
577
+ # return geometry
578
+ #
579
+ # @self.console_argument("sx", type=Length)
580
+ # @self.console_argument("sy", type=Length)
581
+ # @self.console_argument("step", type=Length)
582
+ # @self.console_argument("iterations", type=int)
583
+ # @context.console_command(
584
+ # "fractal_dragon", help=_("fractal_dragon sx, sy, step, iterations")
585
+ # )
586
+ # def fractal_dragon(
587
+ # command,
588
+ # channel,
589
+ # _,
590
+ # sx=None,
591
+ # sy=None,
592
+ # step=None,
593
+ # iterations=None,
594
+ # data=None,
595
+ # post=None,
596
+ # **kwargs,
597
+ # ):
598
+ # ratio = 0.75
599
+ # try:
600
+ # if sx is None:
601
+ # sx = Length("5cm")
602
+ # ssx = float(sx)
603
+ # if sy is None:
604
+ # sy = Length("5cm")
605
+ # ssy = float(sy)
606
+ # if step is None:
607
+ # step = Length("0.5cm")
608
+ # blen = float(step)
609
+ # if iterations is None:
610
+ # iterations = 10
611
+ # iterations = int(iterations)
612
+ # except ValueError:
613
+ # channel("Invalid data provided")
614
+ # return
615
+ # start_pt = Point(ssx, ssy)
616
+ # end_pt = Point.polar(start_pt, 0, blen)
617
+ # geom = create_fractal_dragon(start_pt, end_pt, iterations)
618
+ # node = self.elem_branch.add(type="elem path", geometry=geom)
619
+ # node.stroke = self.default_stroke
620
+ # node.stroke_width = self.default_strokewidth
621
+ # node.altered()
622
+ # node.functional_parameter = (
623
+ # "fractaldragon",
624
+ # 0,
625
+ # start_pt.x,
626
+ # start_pt.y,
627
+ # 0,
628
+ # end_pt.x,
629
+ # end_pt.y,
630
+ # 1,
631
+ # iterations,
632
+ # )
633
+ # # Newly created! Classification needed?
634
+ # data = [node]
635
+ # post.append(classify_new(data))
636
+ # return "elements", data
637
+ #
638
+ # def update_node_fractaldragon(node):
639
+ # my_id = "fractaldragon"
640
+ # point_a = None
641
+ # point_b = None
642
+ # iterations = 5
643
+ # valid = True
644
+ # try:
645
+ # param = node.functional_parameter
646
+ # if param is None or param[0] != my_id:
647
+ # valid = False
648
+ # except (AttributeError, IndexError):
649
+ # valid = False
650
+ # if not valid:
651
+ # # Not for me...
652
+ # return
653
+ # try:
654
+ # if param[1] == 0:
655
+ # point_a = Point(param[2], param[3])
656
+ # if param[4] == 0:
657
+ # point_b = Point(param[5], param[6])
658
+ # if param[7] == 1:
659
+ # iterations = param[8]
660
+ # except IndexError:
661
+ # valid = False
662
+ # if point_a is None or point_b is None:
663
+ # valid = False
664
+ # if valid:
665
+ # geom = create_fractal_dragon(point_a, point_b, iterations)
666
+ # node.geometry = geom
667
+ # node.altered()
668
+ # dist = point_a.distance_to(point_b)
669
+ # # Put second pt at right spot
670
+ # point_b = Point(point_a.x + dist, point_a.y)
671
+ #
672
+ # node.functional_parameter = (
673
+ # "fractaldragon",
674
+ # 0,
675
+ # point_a.x,
676
+ # point_a.y,
677
+ # 0,
678
+ # point_b.x,
679
+ # point_b.y,
680
+ # 1,
681
+ # iterations,
682
+ # )
683
+
684
+ # Cycloid Shape
685
+ def create_cycloid_shape(cx, cy, r_major, r_minor, rotations):
686
+ series = []
687
+ degree_step = 1
688
+ if rotations == 0:
689
+ rotations = 20
690
+
691
+ radian_step = math.radians(degree_step)
692
+ t = 0
693
+ m = math.tau * rotations
694
+ if r_minor == 0:
695
+ r_minor = 1
696
+ count = 0
697
+ offset = 0
698
+ while t < m:
699
+ px = (r_minor + r_major) * math.cos(t) - (r_minor + offset) * math.cos(
700
+ ((r_major + r_minor) / r_minor) * t
701
+ )
702
+ py = (r_minor + r_major) * math.sin(t) - (r_minor + offset) * math.sin(
703
+ ((r_major + r_minor) / r_minor) * t
704
+ )
705
+ series.append((px + cx, py + cy))
706
+ t += radian_step
707
+ count += 1
708
+ # print(
709
+ # f"Done: {count} steps, major={r_major:.0f}, minor={r_minor:.0f}, offset={offset:.0f}, rot={rotations}"
710
+ # )
711
+ geometry = Geomstr()
712
+ last = None
713
+ for m in series:
714
+ if last is not None:
715
+ geometry.line(last[0] + 1j * last[1], m[0] + 1j * m[1])
716
+ last = m
717
+ return geometry
718
+
719
+ @self.console_argument("sx", type=Length)
720
+ @self.console_argument("sy", type=Length)
721
+ @self.console_argument("r_major", type=Length)
722
+ @self.console_argument("r_minor", type=Length)
723
+ @self.console_argument("iterations", type=int)
724
+ @context.console_command(
725
+ "cycloid", help=_("cycloid sx sy r_major r_minor iterations")
726
+ )
727
+ def cycloid(
728
+ command,
729
+ channel,
730
+ _,
731
+ sx=None,
732
+ sy=None,
733
+ r_major=None,
734
+ r_minor=None,
735
+ iterations=None,
736
+ data=None,
737
+ post=None,
738
+ **kwargs,
739
+ ):
740
+ try:
741
+ if sx is None:
742
+ sx = Length("5cm")
743
+ ssx = float(sx)
744
+ if sy is None:
745
+ sy = Length("5cm")
746
+ ssy = float(sy)
747
+ if r_major is None:
748
+ r_major = Length("5cm")
749
+ radius_major = float(r_major)
750
+ if r_minor is None:
751
+ r_minor = Length("3cm")
752
+ radius_minor = float(r_minor)
753
+ if iterations is None:
754
+ iterations = 20
755
+ iterations = int(iterations)
756
+ except ValueError:
757
+ channel("Invalid data provided")
758
+ return
759
+ start_pt = Point(ssx, ssy)
760
+ point_major = Point(ssx + radius_major, ssy)
761
+ point_minor = Point(ssx, ssy - radius_minor)
762
+ geom = create_cycloid_shape(
763
+ ssx, ssy, radius_major, radius_minor, iterations
764
+ )
765
+ node = self.elem_branch.add(type="elem path", geometry=geom)
766
+ node.label = f"Cycloid {iterations} iterations"
767
+ node.stroke = self.default_stroke
768
+ node.stroke_width = self.default_strokewidth
769
+ node.altered()
770
+ node.functional_parameter = (
771
+ "cycloid",
772
+ 0,
773
+ start_pt.x,
774
+ start_pt.y,
775
+ 0,
776
+ point_major.x,
777
+ point_major.y,
778
+ 0,
779
+ point_minor.x,
780
+ point_minor.y,
781
+ 1,
782
+ iterations,
783
+ )
784
+ # Newly created! Classification needed?
785
+ data = [node]
786
+ node.emphasized = True
787
+ post.append(classify_new(data))
788
+ return "elements", data
789
+
790
+ def update_node_cycloid(node):
791
+ my_id = "cycloid"
792
+ try:
793
+ param = node.functional_parameter
794
+ if param is None or param[0] != my_id:
795
+ return
796
+ except (AttributeError, IndexError):
797
+ return
798
+ point_c = None
799
+ point_major = None
800
+ point_minor = None
801
+ try:
802
+ if param[1] == 0:
803
+ point_c = Point(param[2], param[3])
804
+ if param[4] == 0:
805
+ point_major = Point(param[5], param[6])
806
+ if param[7] == 0:
807
+ point_minor = Point(param[8], param[9])
808
+ if param[10] == 1:
809
+ iterations = param[11]
810
+ except IndexError:
811
+ return
812
+ if point_c is None or point_major is None or point_minor is None:
813
+ return
814
+ radius_major = point_c.distance_to(point_major)
815
+ radius_minor = point_c.distance_to(point_minor)
816
+ ssx = point_c.x
817
+ ssy = point_c.y
818
+ geom = create_cycloid_shape(
819
+ ssx, ssy, radius_major, radius_minor, iterations
820
+ )
821
+ node.geometry = geom
822
+ node.altered()
823
+ point_major = Point(ssx + radius_major, ssy)
824
+ point_minor = Point(ssx, ssy - radius_minor)
825
+ node.functional_parameter = (
826
+ "cycloid",
827
+ 0,
828
+ point_c.x,
829
+ point_c.y,
830
+ 0,
831
+ point_major.x,
832
+ point_major.y,
833
+ 0,
834
+ point_minor.x,
835
+ point_minor.y,
836
+ 1,
837
+ iterations,
838
+ )
839
+
840
+ # --- Star like shapes
841
+ def create_star_shape(
842
+ cx,
843
+ cy,
844
+ corners,
845
+ startangle,
846
+ radius,
847
+ radius_inner,
848
+ alternate_seq,
849
+ density,
850
+ ):
851
+ geom = Geomstr()
852
+ # center = Point(cx, cy)
853
+ if startangle is None:
854
+ startangle = 0
855
+
856
+ if corners <= 2:
857
+ if corners == 1:
858
+ geom.point(cx + 1j * cy)
859
+ if corners == 2:
860
+ x = cx + math.cos(startangle) * radius
861
+ y = cy + math.sin(startangle) * radius
862
+ geom.line(cx + 1j * cy, x + 1j * y)
863
+ else:
864
+ i_angle = startangle
865
+ delta_angle = math.tau / corners
866
+ ct = 0
867
+ pts = []
868
+ for j in range(corners):
869
+ if ct < alternate_seq:
870
+ r = radius
871
+ # dbg = "outer"
872
+ else:
873
+ r = radius_inner
874
+ # dbg = "inner"
875
+ thisx = cx + r * math.cos(i_angle)
876
+ thisy = cy + r * math.sin(i_angle)
877
+ # tpoint = center.polar_to(i_angle, r)
878
+ # thisx = tpoint.x
879
+ # thisy = tpoint.y
880
+ # print(
881
+ # "pt %d, Angle=%.1f: %s radius=%.1f: (%.1f, %.1f)"
882
+ # % (j, i_angle / math.pi * 180, dbg, r, thisx, thisy)
883
+ # )
884
+ ct += 1
885
+ if ct >= 2 * alternate_seq:
886
+ ct = 0
887
+ if j == 0:
888
+ firstx = thisx
889
+ firsty = thisy
890
+ i_angle += delta_angle
891
+ thispt = thisx + 1j * thisy
892
+ pts.append(thispt)
893
+ # Close the path
894
+ thispt = firstx + 1j * firsty
895
+ pts.append(thispt)
896
+
897
+ if len(pts) > 0:
898
+ star_points = [pts[0]]
899
+ idx = density
900
+ hitted = []
901
+ while idx != 0:
902
+ if idx in hitted:
903
+ break
904
+ hitted.append(idx)
905
+ star_points.append(pts[idx])
906
+ idx += density
907
+ if idx >= corners:
908
+ idx -= corners
909
+ for idx in range(1, len(star_points)):
910
+ geom.line(star_points[idx - 1], star_points[idx])
911
+ geom.line(star_points[-1], star_points[0])
912
+ # print(f"Created geometry from {len(pts) / 2} pts: {geom.capacity}")
913
+ return geom
914
+
915
+ # Shape (i.e. star) routine
916
+ @self.console_argument(
917
+ "corners", type=int, help=_("Number of corners/vertices")
918
+ )
919
+ @self.console_argument(
920
+ "cx", type=self.length_x, help=_("X-Value of polygon's center")
921
+ )
922
+ @self.console_argument(
923
+ "cy", type=self.length_y, help=_("Y-Value of polygon's center")
924
+ )
925
+ @self.console_argument(
926
+ "radius",
927
+ type=self.length_x,
928
+ help=_("Radius (length of side if --side_length is used)"),
929
+ )
930
+ @self.console_option("startangle", "s", type=Angle, help=_("Start-Angle"))
931
+ @self.console_option(
932
+ "inscribed",
933
+ "i",
934
+ type=bool,
935
+ action="store_true",
936
+ help=_("Shall the polygon touch the inscribing circle?"),
937
+ )
938
+ @self.console_option(
939
+ "side_length",
940
+ "l",
941
+ type=bool,
942
+ action="store_true",
943
+ help=_(
944
+ "Do you want to treat the length value for radius as the length of one edge instead?"
945
+ ),
946
+ )
947
+ @self.console_option(
948
+ "radius_inner",
949
+ "r",
950
+ type=str,
951
+ help=_("Alternating radius for every other vertex"),
952
+ )
953
+ @self.console_option(
954
+ "alternate_seq",
955
+ "a",
956
+ type=int,
957
+ help=_(
958
+ "Length of alternating sequence (1 for starlike figures, >=2 for more gear-like patterns)"
959
+ ),
960
+ )
961
+ @self.console_option(
962
+ "density", "d", type=int, help=_("Amount of vertices to skip")
963
+ )
964
+ @self.console_command(
965
+ "shape",
966
+ help=_(
967
+ "shape <corners> <x> <y> <r> <startangle> <inscribed> or shape <corners> <r>"
968
+ ),
969
+ input_type=("elements", None),
970
+ output_type="elements",
971
+ )
972
+ def element_shape(
973
+ command,
974
+ channel,
975
+ _,
976
+ corners,
977
+ cx,
978
+ cy,
979
+ radius,
980
+ startangle=None,
981
+ inscribed=None,
982
+ side_length=None,
983
+ radius_inner=None,
984
+ alternate_seq=None,
985
+ density=None,
986
+ data=None,
987
+ post=None,
988
+ **kwargs,
989
+ ):
990
+ if corners is None:
991
+ raise CommandSyntaxError
992
+
993
+ if cx is None:
994
+ if corners <= 2:
995
+ raise CommandSyntaxError(
996
+ _(
997
+ "Please provide at least one additional value (which will act as radius then)"
998
+ )
999
+ )
1000
+ cx = 0
1001
+ if cy is None:
1002
+ cy = 0
1003
+ if radius is None:
1004
+ radius = 0
1005
+ sangle = float(startangle)
1006
+ if corners <= 2:
1007
+ # No need to look at side_length parameter as we are considering the radius value as an edge anyway...
1008
+ geom = create_star_shape(
1009
+ cx,
1010
+ cy,
1011
+ corners,
1012
+ sangle,
1013
+ radius,
1014
+ radius_inner,
1015
+ alternate_seq,
1016
+ density,
1017
+ )
1018
+ else:
1019
+ # do we have something like 'shape 3 4cm' ? If yes, reassign the parameters
1020
+ if radius is None:
1021
+ radius = cx
1022
+ cx = 0
1023
+ cy = 0
1024
+ if startangle is None:
1025
+ startangle = Angle("0deg")
1026
+
1027
+ sangle = float(startangle)
1028
+ if alternate_seq is None:
1029
+ if radius_inner is None:
1030
+ alternate_seq = 0
1031
+ else:
1032
+ alternate_seq = 1
1033
+
1034
+ if density is None:
1035
+ density = 1
1036
+ if density < 1 or density > corners:
1037
+ density = 1
1038
+
1039
+ # Do we have to consider the radius value as the length of one corner?
1040
+ if side_length is not None:
1041
+ # Let's recalculate the radius then...
1042
+ # d_oc = s * csc( math.pi / n)
1043
+ radius = 0.5 * radius / math.sin(math.pi / corners)
1044
+
1045
+ if radius_inner is None:
1046
+ radius_inner = radius
1047
+ else:
1048
+ try:
1049
+ radius_inner = float(
1050
+ Length(radius_inner, relative_length=radius)
1051
+ )
1052
+ except ValueError:
1053
+ raise CommandSyntaxError
1054
+
1055
+ if inscribed:
1056
+ if side_length is None:
1057
+ radius = radius / math.cos(math.pi / corners)
1058
+ else:
1059
+ channel(
1060
+ _(
1061
+ "You have as well provided the --side_length parameter, this takes precedence, so --inscribed is ignored"
1062
+ )
1063
+ )
1064
+
1065
+ if alternate_seq < 1:
1066
+ radius_inner = radius
1067
+
1068
+ # print(
1069
+ # "Your parameters are:\n cx=%.1f, cy=%.1f\n radius=%.1f, inner=%.1f\n corners=%d, density=%d\n seq=%d, angle=%.1f"
1070
+ # % (cx, cy, radius, radius_inner, corners, density, alternate_seq, startangle)
1071
+ # )
1072
+ geom = create_star_shape(
1073
+ cx,
1074
+ cy,
1075
+ corners,
1076
+ sangle,
1077
+ radius,
1078
+ radius_inner,
1079
+ alternate_seq,
1080
+ density,
1081
+ )
1082
+ pts = list(geom.as_points())
1083
+ if len(pts) < corners:
1084
+ ct = 0
1085
+ possible_combinations = ""
1086
+ for i in range(corners - 1):
1087
+ j = i + 2
1088
+ if math.gcd(j, corners) == 1:
1089
+ if ct % 3 == 0:
1090
+ possible_combinations += (
1091
+ f"\n shape {corners} ... -d {j}"
1092
+ )
1093
+ else:
1094
+ possible_combinations += (
1095
+ f", shape {corners} ... -d {j} "
1096
+ )
1097
+ ct += 1
1098
+ channel(_("Just for info: we have missed a couple of vertices..."))
1099
+ channel(
1100
+ _(
1101
+ "To hit all, the density parameters should be e.g. {combinations}"
1102
+ ).format(combinations=possible_combinations)
1103
+ )
1104
+ # _("Create shape")
1105
+ with self.undoscope("Create shape"):
1106
+ node = self.elem_branch.add(type="elem path", geometry=geom)
1107
+ node.stroke = self.default_stroke
1108
+ node.stroke_width = self.default_strokewidth
1109
+ node.fill = self.default_fill
1110
+ node.altered()
1111
+ self.set_emphasis([node])
1112
+ node.focus()
1113
+
1114
+ data = [node]
1115
+ # Newly created! Classification needed?
1116
+ post.append(classify_new(data))
1117
+
1118
+ center = Point(cx, cy)
1119
+ sangle = float(startangle)
1120
+ opposing_angle = sangle + math.tau / 2
1121
+ while opposing_angle < 0:
1122
+ opposing_angle += math.tau
1123
+ while opposing_angle >= math.tau:
1124
+ opposing_angle -= math.tau
1125
+ first_point = Point.polar(center, sangle, radius)
1126
+ second_point = Point.polar(center, opposing_angle, radius_inner)
1127
+ node.functional_parameter = (
1128
+ "star",
1129
+ 0,
1130
+ cx,
1131
+ cy,
1132
+ 0,
1133
+ first_point.x,
1134
+ first_point.y,
1135
+ 0,
1136
+ second_point.x,
1137
+ second_point.y,
1138
+ 1,
1139
+ corners,
1140
+ 1,
1141
+ alternate_seq,
1142
+ 1,
1143
+ density,
1144
+ )
1145
+
1146
+ return "elements", data
1147
+
1148
+ # --- end of node update routines
1149
+ def update_node_star_shape(node):
1150
+ my_id = "star"
1151
+ valid = True
1152
+ try:
1153
+ param = node.functional_parameter
1154
+ if param is None or param[0] != my_id:
1155
+ valid = False
1156
+ except (AttributeError, IndexError):
1157
+ valid = False
1158
+ if not valid:
1159
+ # Not for me...
1160
+ return
1161
+ param = node.functional_parameter
1162
+ if param is None:
1163
+ return
1164
+ cx = getit(param, 2, 0)
1165
+ cy = getit(param, 3, 0)
1166
+ p1x = getit(param, 5, 0)
1167
+ p1y = getit(param, 6, 0)
1168
+ p2x = getit(param, 8, 0)
1169
+ p2y = getit(param, 9, 0)
1170
+ corners = getit(param, 11, 3)
1171
+ alternate_seq = getit(param, 13, 0)
1172
+ density = getit(param, 15, 1)
1173
+ if density < 1 or density > corners:
1174
+ density = 1
1175
+ center = Point(cx, cy)
1176
+ pt1 = Point(p1x, p1y)
1177
+ pt2 = Point(p2x, p2y)
1178
+ startangle = center.angle_to(pt1)
1179
+ radius = center.distance_to(pt1)
1180
+ radius_inner = center.distance_to(pt2)
1181
+ if alternate_seq < 1:
1182
+ radius_inner = radius
1183
+ if radius_inner == radius:
1184
+ alternate_seq = 0
1185
+ if radius == 0:
1186
+ valid = False
1187
+ if corners <= 0:
1188
+ valid = False
1189
+ if alternate_seq < 0:
1190
+ valid = False
1191
+ if valid:
1192
+ geom = create_star_shape(
1193
+ cx,
1194
+ cy,
1195
+ corners,
1196
+ startangle,
1197
+ radius,
1198
+ radius_inner,
1199
+ alternate_seq,
1200
+ density,
1201
+ )
1202
+ node.geometry = geom
1203
+ node.altered()
1204
+ center = Point(cx, cy)
1205
+ opposing_angle = startangle + math.tau / 2
1206
+ while opposing_angle < 0:
1207
+ opposing_angle += math.tau
1208
+ while opposing_angle >= math.tau:
1209
+ opposing_angle -= math.tau
1210
+ first_point = Point.polar(center, startangle, radius)
1211
+ second_point = Point.polar(center, opposing_angle, radius_inner)
1212
+ node.functional_parameter = (
1213
+ "star",
1214
+ 0,
1215
+ cx,
1216
+ cy,
1217
+ 0,
1218
+ first_point.x,
1219
+ first_point.y,
1220
+ 0,
1221
+ second_point.x,
1222
+ second_point.y,
1223
+ 1,
1224
+ corners,
1225
+ 1,
1226
+ alternate_seq,
1227
+ 1,
1228
+ density,
1229
+ )
1230
+
1231
+ def create_growing_shape(
1232
+ cx, cy, sides, iterations, ratio_in_percent, sidelen, startangle, gap
1233
+ ):
1234
+ geom = Geomstr()
1235
+ if sides < 3:
1236
+ sides = 3
1237
+ shape_angle = math.tau / sides
1238
+ myangle = startangle
1239
+ sidelength = sidelen
1240
+ sidedelta = sidelength * ratio_in_percent / 100.0
1241
+ pt1 = cx + 1j * cy
1242
+ for idx in range(iterations):
1243
+ for side in range(sides):
1244
+ while myangle < 0:
1245
+ myangle += math.tau
1246
+ while myangle > math.tau:
1247
+ myangle -= math.tau
1248
+
1249
+ pt2 = geom.polar(pt1, myangle, sidelength)
1250
+ geom.line(pt1, pt2)
1251
+ pt1 = pt2
1252
+ sidelength += sidedelta
1253
+ # sidedelta = sidelength * ratio_in_percent
1254
+ myangle += shape_angle
1255
+ myangle += gap
1256
+ return geom
1257
+
1258
+ def update_node_growing_shape(node):
1259
+ my_id = "growingshape"
1260
+ valid = True
1261
+ try:
1262
+ param = node.functional_parameter
1263
+ if param is None or param[0] != my_id:
1264
+ valid = False
1265
+ except (AttributeError, IndexError):
1266
+ valid = False
1267
+ if not valid:
1268
+ # Not for me...
1269
+ return
1270
+ param = node.functional_parameter
1271
+ if param is None:
1272
+ return
1273
+ pt0x = getit(param, 2, 0)
1274
+ pt0y = getit(param, 3, 0)
1275
+ pt1x = getit(param, 5, 0)
1276
+ pt1y = getit(param, 6, 0)
1277
+ ratio_in_percent = getit(param, 8, 0)
1278
+ sides = getit(param, 10, 0)
1279
+ iterations = getit(param, 12, 0)
1280
+ igap = getit(param, 14, 0)
1281
+ pt0 = Point(pt0x, pt0y)
1282
+ pt1 = Point(pt1x, pt1y)
1283
+ sidelen = pt0.distance_to(pt1)
1284
+ startangle = pt0.angle_to(pt1)
1285
+ gap = igap / 360 * math.tau
1286
+ geom = create_growing_shape(
1287
+ pt0x,
1288
+ pt0y,
1289
+ sides,
1290
+ iterations,
1291
+ ratio_in_percent,
1292
+ sidelen,
1293
+ startangle,
1294
+ gap,
1295
+ )
1296
+ node.geometry = geom
1297
+ node.altered()
1298
+ pt0 = None
1299
+ pt1 = None
1300
+ for pt in geom.as_points():
1301
+ if pt0 is None:
1302
+ pt0 = Point(pt.real, pt.imag)
1303
+ elif pt1 is None:
1304
+ pt1 = Point(pt.real, pt.imag)
1305
+ else:
1306
+ break
1307
+ opposite_angle = math.tau / 2 + gap
1308
+ while opposite_angle > math.tau:
1309
+ opposite_angle -= math.tau
1310
+ while opposite_angle < 0:
1311
+ opposite_angle += math.tau
1312
+ # pt2 = Point.polar(pt0, opposite_angle, sidelen * ratio_in_percent)
1313
+ node.functional_parameter = (
1314
+ "growingshape",
1315
+ 0,
1316
+ pt0.x,
1317
+ pt0.y,
1318
+ 0,
1319
+ pt1.x,
1320
+ pt1.y,
1321
+ 1,
1322
+ ratio_in_percent,
1323
+ 1,
1324
+ sides,
1325
+ 1,
1326
+ iterations,
1327
+ 1,
1328
+ igap,
1329
+ )
1330
+
1331
+ @self.console_argument("sx", type=Length)
1332
+ @self.console_argument("sy", type=Length)
1333
+ @self.console_argument("sides", type=int)
1334
+ @self.console_argument("iterations", type=int)
1335
+ @self.console_argument("firstlength", type=Length)
1336
+ @self.console_option("ratio", "r", type=int, help=_("Growth in %"))
1337
+ @self.console_option("angle", "a", type=Angle, help=_("Start angle"))
1338
+ @self.console_option(
1339
+ "gap", "g", type=int, help=_("Delta angle between moves in degrees")
1340
+ )
1341
+ @context.console_command(
1342
+ "growingshape", help=_("growingshape sx sy sides iterations")
1343
+ )
1344
+ def growing_shape(
1345
+ command,
1346
+ channel,
1347
+ _,
1348
+ sx=None,
1349
+ sy=None,
1350
+ sides=None,
1351
+ iterations=None,
1352
+ firstlength=None,
1353
+ ratio=None,
1354
+ angle=None,
1355
+ gap=None,
1356
+ data=None,
1357
+ post=None,
1358
+ **kwargs,
1359
+ ):
1360
+ try:
1361
+ if sx is None:
1362
+ sx = Length("0cm")
1363
+ ssx = float(sx)
1364
+ if sy is None:
1365
+ sy = Length("0cm")
1366
+ ssy = float(sy)
1367
+ if sides is None or sides < 3:
1368
+ sides = 3
1369
+ if iterations is None:
1370
+ iterations = 5
1371
+ if iterations < 1:
1372
+ iterations = 1
1373
+ if angle is None:
1374
+ angle = Angle("0deg")
1375
+ startangle = float(angle)
1376
+ if firstlength is None:
1377
+ firstlength = Length("0.5cm")
1378
+ sidelen = float(firstlength)
1379
+ if ratio is None:
1380
+ ratio = 2 # 2% growth
1381
+ if gap is None:
1382
+ gap = 0
1383
+ except ValueError:
1384
+ channel("Invalid data provided")
1385
+ return
1386
+ gap_angle = gap / 360 * math.tau
1387
+ geom = create_growing_shape(
1388
+ ssx,
1389
+ ssy,
1390
+ sides,
1391
+ iterations,
1392
+ ratio,
1393
+ sidelen,
1394
+ startangle,
1395
+ gap_angle,
1396
+ )
1397
+ # _("Create shape")
1398
+ with self.undoscope("Create shape"):
1399
+ node = self.elem_branch.add(type="elem path", geometry=geom)
1400
+ node.label = f"Growing Polygon w. {sides} sides"
1401
+ node.stroke = self.default_stroke
1402
+ node.stroke_width = 1000 # self.default_strokewidth
1403
+ node.altered()
1404
+ pt0 = None
1405
+ pt1 = None
1406
+ for pt in geom.as_points():
1407
+ if pt0 is None:
1408
+ pt0 = Point(pt.real, pt.imag)
1409
+ elif pt1 is None:
1410
+ pt1 = Point(pt.real, pt.imag)
1411
+ else:
1412
+ break
1413
+ node.functional_parameter = (
1414
+ "growingshape",
1415
+ 0,
1416
+ pt0.x,
1417
+ pt0.y,
1418
+ 0,
1419
+ pt1.x,
1420
+ pt1.y,
1421
+ 1,
1422
+ ratio,
1423
+ 1,
1424
+ sides,
1425
+ 1,
1426
+ iterations,
1427
+ 1,
1428
+ gap,
1429
+ )
1430
+ # Newly created! Classification needed?
1431
+ data = [node]
1432
+ node.emphasized = True
1433
+ post.append(classify_new(data))
1434
+ return "elements", data
1435
+
1436
+ # Let's register them
1437
+ # The info tuple contains three entries
1438
+ # 1) The function to be called to update the node after a parameter change
1439
+ # 2) A dict with information how to read/display the different parameter entries
1440
+ # 3) A boolean parameter that indicates whether this is a routine that needs
1441
+ # to be automatically called after a change of a related source node
1442
+ # This needs the id of the related node to be in the very 'first' parameter
1443
+ # of the functional_parameter structure, so something like
1444
+ # node.functional_parameter = ("grid", 3, source_node.id, ....)
1445
+ info = (
1446
+ update_node_grid,
1447
+ {
1448
+ "0": ("ID",),
1449
+ "1": ("Horizontal gap",),
1450
+ "2": ("Vertical gap",),
1451
+ "3": ("Columns", 1, 25),
1452
+ "4": ("Rows", 1, 25),
1453
+ },
1454
+ True, # Yes this something that needs to be updated on source changes
1455
+ )
1456
+ kernel.register("element_update/grid", info)
1457
+
1458
+ info = (
1459
+ update_node_fractaltree,
1460
+ {
1461
+ "0": ("Startpoint",),
1462
+ "1": ("End of base stem",),
1463
+ "2": ("Iterations", 2, 13),
1464
+ "3": ("Branch length",),
1465
+ },
1466
+ False,
1467
+ )
1468
+ kernel.register("element_update/fractaltree", info)
1469
+
1470
+ # info = (
1471
+ # update_node_fractaldragon,
1472
+ # {
1473
+ # "0": ("Startpoint",),
1474
+ # "1": ("End of base stem",),
1475
+ # "2": ("Iterations", 2, 20),
1476
+ # },
1477
+ # )
1478
+ # kernel.register("element_update/fractaldragon", info)
1479
+
1480
+ info = (
1481
+ update_node_cycloid,
1482
+ {
1483
+ "0": ("Startpoint",),
1484
+ "1": ("Major axis",),
1485
+ "2": ("Minor axis",),
1486
+ "3": ("Iterations", 2, 30),
1487
+ },
1488
+ False,
1489
+ )
1490
+ kernel.register("element_update/cycloid", info)
1491
+
1492
+ max_corner_gui = 32
1493
+ info = (
1494
+ update_node_star_shape,
1495
+ {
1496
+ "0": ("Center",),
1497
+ "1": ("Outer radius",),
1498
+ "2": ("Inner radius",),
1499
+ "3": ("Corners", 3, max_corner_gui),
1500
+ "4": ("Alternation", 0, 10),
1501
+ "5": ("Density", 1, max_corner_gui - 1),
1502
+ },
1503
+ False,
1504
+ )
1505
+ kernel.register("element_update/star", info)
1506
+
1507
+ info = (
1508
+ update_node_tfractal,
1509
+ {
1510
+ "0": ("Turtle code",),
1511
+ "1": ("Base code",),
1512
+ "2": ("Segmentation", 1, 20),
1513
+ "3": ("Iterations", 1, 20),
1514
+ "4": ("Shaping", 1, 20),
1515
+ "5": ("Cornertype", 0, 2),
1516
+ },
1517
+ False,
1518
+ )
1519
+ kernel.register("element_update/tfractal", info)
1520
+
1521
+ info = (
1522
+ None, # Let the node deal with it
1523
+ {
1524
+ "0": ("Rounded corner",),
1525
+ },
1526
+ False,
1527
+ )
1528
+ kernel.register("element_update/rect", info)
1529
+
1530
+ info = (
1531
+ update_node_growing_shape,
1532
+ {
1533
+ "0": ("First point",),
1534
+ "1": ("First edge",),
1535
+ "2": ("Growth Ratio", 0, 100),
1536
+ "3": ("Sides", 3, 12),
1537
+ "4": ("Iterations", 1, 45),
1538
+ "5": ("Gap", 0, 15),
1539
+ },
1540
+ False,
1541
+ )
1542
+ kernel.register("element_update/growingshape", info)