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

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