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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (445) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1195 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1844 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4561 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4329 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +933 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/trace.py +651 -563
  66. meerk40t/core/elements/tree_commands.py +415 -409
  67. meerk40t/core/elements/undo_redo.py +116 -58
  68. meerk40t/core/elements/wordlist.py +319 -200
  69. meerk40t/core/exceptions.py +9 -9
  70. meerk40t/core/laserjob.py +220 -220
  71. meerk40t/core/logging.py +63 -63
  72. meerk40t/core/node/blobnode.py +83 -86
  73. meerk40t/core/node/bootstrap.py +105 -103
  74. meerk40t/core/node/branch_elems.py +40 -31
  75. meerk40t/core/node/branch_ops.py +45 -38
  76. meerk40t/core/node/branch_regmark.py +48 -41
  77. meerk40t/core/node/cutnode.py +29 -32
  78. meerk40t/core/node/effect_hatch.py +375 -257
  79. meerk40t/core/node/effect_warp.py +398 -0
  80. meerk40t/core/node/effect_wobble.py +441 -309
  81. meerk40t/core/node/elem_ellipse.py +404 -309
  82. meerk40t/core/node/elem_image.py +1082 -801
  83. meerk40t/core/node/elem_line.py +358 -292
  84. meerk40t/core/node/elem_path.py +259 -201
  85. meerk40t/core/node/elem_point.py +129 -102
  86. meerk40t/core/node/elem_polyline.py +310 -246
  87. meerk40t/core/node/elem_rect.py +376 -286
  88. meerk40t/core/node/elem_text.py +445 -418
  89. meerk40t/core/node/filenode.py +59 -40
  90. meerk40t/core/node/groupnode.py +138 -74
  91. meerk40t/core/node/image_processed.py +777 -766
  92. meerk40t/core/node/image_raster.py +156 -113
  93. meerk40t/core/node/layernode.py +31 -31
  94. meerk40t/core/node/mixins.py +135 -107
  95. meerk40t/core/node/node.py +1427 -1304
  96. meerk40t/core/node/nutils.py +117 -114
  97. meerk40t/core/node/op_cut.py +462 -335
  98. meerk40t/core/node/op_dots.py +296 -251
  99. meerk40t/core/node/op_engrave.py +414 -311
  100. meerk40t/core/node/op_image.py +755 -369
  101. meerk40t/core/node/op_raster.py +787 -522
  102. meerk40t/core/node/place_current.py +37 -40
  103. meerk40t/core/node/place_point.py +329 -126
  104. meerk40t/core/node/refnode.py +58 -47
  105. meerk40t/core/node/rootnode.py +225 -219
  106. meerk40t/core/node/util_console.py +48 -48
  107. meerk40t/core/node/util_goto.py +84 -65
  108. meerk40t/core/node/util_home.py +61 -61
  109. meerk40t/core/node/util_input.py +102 -102
  110. meerk40t/core/node/util_output.py +102 -102
  111. meerk40t/core/node/util_wait.py +65 -65
  112. meerk40t/core/parameters.py +709 -707
  113. meerk40t/core/planner.py +875 -785
  114. meerk40t/core/plotplanner.py +656 -652
  115. meerk40t/core/space.py +120 -113
  116. meerk40t/core/spoolers.py +706 -705
  117. meerk40t/core/svg_io.py +1836 -1549
  118. meerk40t/core/treeop.py +534 -445
  119. meerk40t/core/undos.py +278 -124
  120. meerk40t/core/units.py +784 -680
  121. meerk40t/core/view.py +393 -322
  122. meerk40t/core/webhelp.py +62 -62
  123. meerk40t/core/wordlist.py +513 -504
  124. meerk40t/cylinder/cylinder.py +247 -0
  125. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  126. meerk40t/cylinder/gui/gui.py +24 -0
  127. meerk40t/device/__init__.py +1 -1
  128. meerk40t/device/basedevice.py +322 -123
  129. meerk40t/device/devicechoices.py +50 -0
  130. meerk40t/device/dummydevice.py +163 -128
  131. meerk40t/device/gui/defaultactions.py +618 -602
  132. meerk40t/device/gui/effectspanel.py +114 -0
  133. meerk40t/device/gui/formatterpanel.py +253 -290
  134. meerk40t/device/gui/warningpanel.py +337 -260
  135. meerk40t/device/mixins.py +13 -13
  136. meerk40t/dxf/__init__.py +1 -1
  137. meerk40t/dxf/dxf_io.py +766 -554
  138. meerk40t/dxf/plugin.py +47 -35
  139. meerk40t/external_plugins.py +79 -79
  140. meerk40t/external_plugins_build.py +28 -28
  141. meerk40t/extra/cag.py +112 -116
  142. meerk40t/extra/coolant.py +403 -0
  143. meerk40t/extra/encode_detect.py +198 -0
  144. meerk40t/extra/ezd.py +1165 -1165
  145. meerk40t/extra/hershey.py +835 -340
  146. meerk40t/extra/imageactions.py +322 -316
  147. meerk40t/extra/inkscape.py +630 -622
  148. meerk40t/extra/lbrn.py +424 -424
  149. meerk40t/extra/outerworld.py +284 -0
  150. meerk40t/extra/param_functions.py +1542 -1556
  151. meerk40t/extra/potrace.py +257 -253
  152. meerk40t/extra/serial_exchange.py +118 -0
  153. meerk40t/extra/updater.py +602 -453
  154. meerk40t/extra/vectrace.py +147 -146
  155. meerk40t/extra/winsleep.py +83 -83
  156. meerk40t/extra/xcs_reader.py +597 -0
  157. meerk40t/fill/fills.py +781 -335
  158. meerk40t/fill/patternfill.py +1061 -1061
  159. meerk40t/fill/patterns.py +614 -567
  160. meerk40t/grbl/control.py +87 -87
  161. meerk40t/grbl/controller.py +990 -903
  162. meerk40t/grbl/device.py +1081 -768
  163. meerk40t/grbl/driver.py +989 -771
  164. meerk40t/grbl/emulator.py +532 -497
  165. meerk40t/grbl/gcodejob.py +783 -767
  166. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  167. meerk40t/grbl/gui/grblcontroller.py +485 -271
  168. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  169. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  170. meerk40t/grbl/gui/gui.py +147 -116
  171. meerk40t/grbl/interpreter.py +44 -44
  172. meerk40t/grbl/loader.py +22 -22
  173. meerk40t/grbl/mock_connection.py +56 -56
  174. meerk40t/grbl/plugin.py +294 -264
  175. meerk40t/grbl/serial_connection.py +93 -88
  176. meerk40t/grbl/tcp_connection.py +81 -79
  177. meerk40t/grbl/ws_connection.py +112 -0
  178. meerk40t/gui/__init__.py +1 -1
  179. meerk40t/gui/about.py +2042 -296
  180. meerk40t/gui/alignment.py +1644 -1608
  181. meerk40t/gui/autoexec.py +199 -0
  182. meerk40t/gui/basicops.py +791 -670
  183. meerk40t/gui/bufferview.py +77 -71
  184. meerk40t/gui/busy.py +170 -133
  185. meerk40t/gui/choicepropertypanel.py +1673 -1469
  186. meerk40t/gui/consolepanel.py +706 -542
  187. meerk40t/gui/devicepanel.py +687 -581
  188. meerk40t/gui/dialogoptions.py +110 -107
  189. meerk40t/gui/executejob.py +316 -306
  190. meerk40t/gui/fonts.py +90 -90
  191. meerk40t/gui/functionwrapper.py +252 -0
  192. meerk40t/gui/gui_mixins.py +729 -0
  193. meerk40t/gui/guicolors.py +205 -182
  194. meerk40t/gui/help_assets/help_assets.py +218 -201
  195. meerk40t/gui/helper.py +154 -0
  196. meerk40t/gui/hersheymanager.py +1430 -846
  197. meerk40t/gui/icons.py +3422 -2747
  198. meerk40t/gui/imagesplitter.py +555 -508
  199. meerk40t/gui/keymap.py +354 -344
  200. meerk40t/gui/laserpanel.py +892 -806
  201. meerk40t/gui/laserrender.py +1470 -1232
  202. meerk40t/gui/lasertoolpanel.py +805 -793
  203. meerk40t/gui/magnetoptions.py +436 -0
  204. meerk40t/gui/materialmanager.py +2917 -0
  205. meerk40t/gui/materialtest.py +1722 -1694
  206. meerk40t/gui/mkdebug.py +646 -359
  207. meerk40t/gui/mwindow.py +163 -140
  208. meerk40t/gui/navigationpanels.py +2605 -2467
  209. meerk40t/gui/notes.py +143 -142
  210. meerk40t/gui/opassignment.py +414 -410
  211. meerk40t/gui/operation_info.py +310 -299
  212. meerk40t/gui/plugin.py +494 -328
  213. meerk40t/gui/position.py +714 -669
  214. meerk40t/gui/preferences.py +901 -650
  215. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  216. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  217. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  218. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  219. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  220. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  221. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  222. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  223. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  224. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  225. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  226. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  227. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  228. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  229. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  230. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  231. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  232. meerk40t/gui/propertypanels/textproperty.py +770 -755
  233. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  234. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  235. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  236. meerk40t/gui/ribbon.py +2468 -2210
  237. meerk40t/gui/scene/scene.py +1100 -1051
  238. meerk40t/gui/scene/sceneconst.py +22 -22
  239. meerk40t/gui/scene/scenepanel.py +439 -349
  240. meerk40t/gui/scene/scenespacewidget.py +365 -365
  241. meerk40t/gui/scene/widget.py +518 -505
  242. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  243. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  244. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  245. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  246. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  247. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  248. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  249. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  250. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  251. meerk40t/gui/scenewidgets/rectselectwidget.py +589 -346
  252. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  253. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  254. meerk40t/gui/scenewidgets/selectionwidget.py +2952 -2756
  255. meerk40t/gui/simpleui.py +357 -333
  256. meerk40t/gui/simulation.py +2431 -2094
  257. meerk40t/gui/snapoptions.py +208 -203
  258. meerk40t/gui/spoolerpanel.py +1227 -1180
  259. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  260. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  261. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  262. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  263. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  264. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  265. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  266. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  267. meerk40t/gui/themes.py +200 -78
  268. meerk40t/gui/tips.py +591 -0
  269. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  270. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  271. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  272. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  273. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  274. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  275. meerk40t/gui/toolwidgets/toolline.py +39 -144
  276. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  277. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  278. meerk40t/gui/toolwidgets/toolmeasure.py +160 -216
  279. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  280. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  281. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  282. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  283. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  284. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  285. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  286. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  287. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  288. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  289. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  290. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  291. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  292. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  293. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  294. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  295. meerk40t/gui/usbconnect.py +98 -91
  296. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  297. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  298. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  299. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  300. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  301. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  302. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  303. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  304. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  305. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  306. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  307. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  308. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  309. meerk40t/gui/wordlisteditor.py +985 -931
  310. meerk40t/gui/wxmeerk40t.py +1444 -1169
  311. meerk40t/gui/wxmmain.py +5578 -4112
  312. meerk40t/gui/wxmribbon.py +1591 -1076
  313. meerk40t/gui/wxmscene.py +1635 -1453
  314. meerk40t/gui/wxmtree.py +2410 -2089
  315. meerk40t/gui/wxutils.py +1769 -1099
  316. meerk40t/gui/zmatrix.py +102 -102
  317. meerk40t/image/__init__.py +1 -1
  318. meerk40t/image/dither.py +429 -0
  319. meerk40t/image/imagetools.py +2778 -2269
  320. meerk40t/internal_plugins.py +150 -130
  321. meerk40t/kernel/__init__.py +63 -12
  322. meerk40t/kernel/channel.py +259 -212
  323. meerk40t/kernel/context.py +538 -538
  324. meerk40t/kernel/exceptions.py +41 -41
  325. meerk40t/kernel/functions.py +463 -414
  326. meerk40t/kernel/jobs.py +100 -100
  327. meerk40t/kernel/kernel.py +3809 -3571
  328. meerk40t/kernel/lifecycles.py +71 -71
  329. meerk40t/kernel/module.py +49 -49
  330. meerk40t/kernel/service.py +147 -147
  331. meerk40t/kernel/settings.py +383 -343
  332. meerk40t/lihuiyu/controller.py +883 -876
  333. meerk40t/lihuiyu/device.py +1181 -1069
  334. meerk40t/lihuiyu/driver.py +1466 -1372
  335. meerk40t/lihuiyu/gui/gui.py +127 -106
  336. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  337. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  338. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  339. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  340. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  341. meerk40t/lihuiyu/interpreter.py +53 -53
  342. meerk40t/lihuiyu/laserspeed.py +450 -450
  343. meerk40t/lihuiyu/loader.py +90 -90
  344. meerk40t/lihuiyu/parser.py +404 -404
  345. meerk40t/lihuiyu/plugin.py +101 -102
  346. meerk40t/lihuiyu/tcp_connection.py +111 -109
  347. meerk40t/main.py +231 -165
  348. meerk40t/moshi/builder.py +788 -781
  349. meerk40t/moshi/controller.py +505 -499
  350. meerk40t/moshi/device.py +495 -442
  351. meerk40t/moshi/driver.py +862 -696
  352. meerk40t/moshi/gui/gui.py +78 -76
  353. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  354. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  355. meerk40t/moshi/plugin.py +43 -43
  356. meerk40t/network/console_server.py +102 -57
  357. meerk40t/network/kernelserver.py +10 -9
  358. meerk40t/network/tcp_server.py +142 -140
  359. meerk40t/network/udp_server.py +103 -77
  360. meerk40t/network/web_server.py +390 -0
  361. meerk40t/newly/controller.py +1158 -1144
  362. meerk40t/newly/device.py +874 -732
  363. meerk40t/newly/driver.py +540 -412
  364. meerk40t/newly/gui/gui.py +219 -188
  365. meerk40t/newly/gui/newlyconfig.py +116 -101
  366. meerk40t/newly/gui/newlycontroller.py +193 -186
  367. meerk40t/newly/gui/operationproperties.py +51 -51
  368. meerk40t/newly/mock_connection.py +82 -82
  369. meerk40t/newly/newly_params.py +56 -56
  370. meerk40t/newly/plugin.py +1214 -1246
  371. meerk40t/newly/usb_connection.py +322 -322
  372. meerk40t/rotary/gui/gui.py +52 -46
  373. meerk40t/rotary/gui/rotarysettings.py +240 -232
  374. meerk40t/rotary/rotary.py +202 -98
  375. meerk40t/ruida/control.py +291 -91
  376. meerk40t/ruida/controller.py +138 -1088
  377. meerk40t/ruida/device.py +672 -231
  378. meerk40t/ruida/driver.py +534 -472
  379. meerk40t/ruida/emulator.py +1494 -1491
  380. meerk40t/ruida/exceptions.py +4 -4
  381. meerk40t/ruida/gui/gui.py +71 -76
  382. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  383. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  384. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  385. meerk40t/ruida/loader.py +54 -52
  386. meerk40t/ruida/mock_connection.py +57 -109
  387. meerk40t/ruida/plugin.py +124 -87
  388. meerk40t/ruida/rdjob.py +2084 -945
  389. meerk40t/ruida/serial_connection.py +116 -0
  390. meerk40t/ruida/tcp_connection.py +146 -0
  391. meerk40t/ruida/udp_connection.py +73 -0
  392. meerk40t/svgelements.py +9671 -9669
  393. meerk40t/tools/driver_to_path.py +584 -579
  394. meerk40t/tools/geomstr.py +5583 -4680
  395. meerk40t/tools/jhfparser.py +357 -292
  396. meerk40t/tools/kerftest.py +904 -890
  397. meerk40t/tools/livinghinges.py +1168 -1033
  398. meerk40t/tools/pathtools.py +987 -949
  399. meerk40t/tools/pmatrix.py +234 -0
  400. meerk40t/tools/pointfinder.py +942 -942
  401. meerk40t/tools/polybool.py +940 -940
  402. meerk40t/tools/rasterplotter.py +1660 -547
  403. meerk40t/tools/shxparser.py +989 -901
  404. meerk40t/tools/ttfparser.py +726 -446
  405. meerk40t/tools/zinglplotter.py +595 -593
  406. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/LICENSE +21 -21
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/METADATA +150 -139
  408. meerk40t-0.9.7010.dist-info/RECORD +445 -0
  409. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/WHEEL +1 -1
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/top_level.txt +0 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/zip-safe +1 -1
  412. meerk40t/balormk/elementlightjob.py +0 -159
  413. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  414. test/bootstrap.py +0 -63
  415. test/test_cli.py +0 -12
  416. test/test_core_cutcode.py +0 -418
  417. test/test_core_elements.py +0 -144
  418. test/test_core_plotplanner.py +0 -397
  419. test/test_core_viewports.py +0 -312
  420. test/test_drivers_grbl.py +0 -108
  421. test/test_drivers_lihuiyu.py +0 -443
  422. test/test_drivers_newly.py +0 -113
  423. test/test_element_degenerate_points.py +0 -43
  424. test/test_elements_classify.py +0 -97
  425. test/test_elements_penbox.py +0 -22
  426. test/test_file_svg.py +0 -176
  427. test/test_fill.py +0 -155
  428. test/test_geomstr.py +0 -1523
  429. test/test_geomstr_nodes.py +0 -18
  430. test/test_imagetools_actualize.py +0 -306
  431. test/test_imagetools_wizard.py +0 -258
  432. test/test_kernel.py +0 -200
  433. test/test_laser_speeds.py +0 -3303
  434. test/test_length.py +0 -57
  435. test/test_lifecycle.py +0 -66
  436. test/test_operations.py +0 -251
  437. test/test_operations_hatch.py +0 -57
  438. test/test_ruida.py +0 -19
  439. test/test_spooler.py +0 -22
  440. test/test_tools_rasterplotter.py +0 -29
  441. test/test_wobble.py +0 -133
  442. test/test_zingl.py +0 -124
  443. {test → meerk40t/cylinder}/__init__.py +0 -0
  444. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  445. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/entry_points.txt +0 -0
meerk40t/gui/wxmscene.py CHANGED
@@ -1,1453 +1,1635 @@
1
- import math
2
- import platform
3
- import random
4
- import time
5
-
6
- import wx
7
- from wx import aui
8
-
9
- from meerk40t.core.elements.element_types import elem_nodes
10
- from meerk40t.core.units import UNITS_PER_PIXEL, Length
11
- from meerk40t.gui.icons import (
12
- STD_ICON_SIZE,
13
- icon_meerk40t,
14
- icons8_menu,
15
- icons8_r_white,
16
- icons8_text,
17
- )
18
- from meerk40t.gui.laserrender import DRAW_MODE_BACKGROUND, DRAW_MODE_GUIDES, LaserRender
19
- from meerk40t.gui.mwindow import MWindow
20
- from meerk40t.gui.scene.scenepanel import ScenePanel
21
- from meerk40t.gui.scenewidgets.affinemover import AffineMover
22
- from meerk40t.gui.scenewidgets.attractionwidget import AttractionWidget
23
- from meerk40t.gui.scenewidgets.bedwidget import BedWidget
24
- from meerk40t.gui.scenewidgets.elementswidget import ElementsWidget
25
- from meerk40t.gui.scenewidgets.gridwidget import GridWidget
26
- from meerk40t.gui.scenewidgets.guidewidget import GuideWidget
27
- from meerk40t.gui.scenewidgets.laserpathwidget import LaserPathWidget
28
- from meerk40t.gui.scenewidgets.machineoriginwidget import MachineOriginWidget
29
- from meerk40t.gui.scenewidgets.nodeselector import NodeSelector
30
- from meerk40t.gui.scenewidgets.rectselectwidget import RectSelectWidget
31
- from meerk40t.gui.scenewidgets.reticlewidget import ReticleWidget
32
- from meerk40t.gui.scenewidgets.selectionwidget import SelectionWidget
33
- from meerk40t.gui.toolwidgets.toolcircle import CircleTool
34
- from meerk40t.gui.toolwidgets.toolcontainer import ToolContainer
35
- from meerk40t.gui.toolwidgets.tooldraw import DrawTool
36
- from meerk40t.gui.toolwidgets.toolellipse import EllipseTool
37
- from meerk40t.gui.toolwidgets.toolimagecut import ImageCutTool
38
- from meerk40t.gui.toolwidgets.toolline import LineTool
39
- from meerk40t.gui.toolwidgets.toollinetext import LineTextTool
40
- from meerk40t.gui.toolwidgets.toolmeasure import MeasureTool
41
- from meerk40t.gui.toolwidgets.toolnodeedit import EditTool
42
- from meerk40t.gui.toolwidgets.toolnodemove import NodeMoveTool
43
- from meerk40t.gui.toolwidgets.toolparameter import ParameterTool
44
- from meerk40t.gui.toolwidgets.toolplacement import PlacementTool
45
- from meerk40t.gui.toolwidgets.toolpoint import PointTool
46
- from meerk40t.gui.toolwidgets.toolpolygon import PolygonTool
47
- from meerk40t.gui.toolwidgets.toolpolyline import PolylineTool
48
- from meerk40t.gui.toolwidgets.toolrect import RectTool
49
- from meerk40t.gui.toolwidgets.toolrelocate import RelocateTool
50
- from meerk40t.gui.toolwidgets.toolribbon import RibbonTool
51
- from meerk40t.gui.toolwidgets.tooltext import TextTool
52
- from meerk40t.gui.toolwidgets.toolvector import VectorTool
53
- from meerk40t.gui.utilitywidgets.checkboxwidget import CheckboxWidget
54
- from meerk40t.gui.utilitywidgets.cyclocycloidwidget import CyclocycloidWidget
55
- from meerk40t.gui.utilitywidgets.harmonograph import HarmonographWidget
56
- from meerk40t.gui.utilitywidgets.seekbarwidget import SeekbarWidget
57
- from meerk40t.gui.utilitywidgets.togglewidget import ToggleWidget
58
- from meerk40t.gui.wxutils import get_key_name, is_navigation_key
59
- from meerk40t.kernel import CommandSyntaxError, signal_listener
60
- from meerk40t.svgelements import Angle, Color
61
-
62
- _ = wx.GetTranslation
63
-
64
-
65
- def register_panel_scene(window, context):
66
- # control = wx.aui.AuiNotebook(window, -1, size=(200, 150))
67
- # panel1 = MeerK40tScenePanel(window, wx.ID_ANY, context=context, index=1)
68
- # control.AddPage(panel1, "scene1")
69
- # panel2 = MeerK40tScenePanel(window, wx.ID_ANY, context=context, index=2)
70
- # control.AddPage(panel2, "scene2")
71
-
72
- control = MeerK40tScenePanel(window, wx.ID_ANY, context=context)
73
- pane = aui.AuiPaneInfo().CenterPane().MinSize(200, 200).Name("scene")
74
- pane.dock_proportion = 600
75
- pane.control = control
76
- pane.hide_menu = True
77
-
78
- # def on_note_page_change(event=None):
79
- # if control.GetPageText(control.GetSelection()) == "scene1":
80
- # context.kernel.activate_service_path('elements', 'elements')
81
- # else:
82
- # context.kernel.activate_service_path('elements', "elements1")
83
- # context("refresh\n")
84
- # control.Bind(aui.EVT_AUINOTEBOOK_PAGE_CHANGED, on_note_page_change, control)
85
-
86
- window.on_pane_create(pane)
87
- context.register("pane/scene", pane)
88
-
89
-
90
- class MeerK40tScenePanel(wx.Panel):
91
- def __init__(self, *args, context=None, index=None, **kwargs):
92
- # begin wxGlade: ConsolePanel.__init__
93
- kwargs["style"] = kwargs.get("style", 0) | wx.TAB_TRAVERSAL
94
- wx.Panel.__init__(self, *args, **kwargs)
95
- self.context = context
96
- self.scene = ScenePanel(
97
- self.context,
98
- self,
99
- scene_name="Scene" if index is None else f"Scene{index}",
100
- style=wx.EXPAND | wx.WANTS_CHARS,
101
- )
102
- self.scene.start_scene()
103
- self.widget_scene = self.scene.scene
104
-
105
- self.tool_active = False
106
- self.modif_active = False
107
- self.suppress_selection = False
108
- self._reference = None # Reference Object
109
-
110
- # Stuff for magnet-lines
111
- self.magnet_x = []
112
- self.magnet_y = []
113
- self._magnet_attraction = 2
114
- # 0 off, `1..x` increasing strength (quadratic behaviour)
115
- self.magnet_attract_x = True # Shall the X-Axis be affected
116
- self.magnet_attract_y = True # Shall the Y-Axis be affected
117
- self.magnet_attract_c = True # Shall the center be affected
118
-
119
- self.context.setting(bool, "clear_magnets", True)
120
-
121
- # Save / Load the content of magnets
122
- from os.path import join, realpath
123
-
124
- from meerk40t.kernel.functions import get_safe_path
125
-
126
- self._magnet_file = join(
127
- realpath(get_safe_path(self.context.kernel.name)), "magnets.cfg"
128
- )
129
- self.load_magnets()
130
- # Add a plugin routine to be called at the time of a full new start
131
- context.kernel.register(
132
- "reset_routines/magnets", self.clear_magnets_conditionally
133
- )
134
-
135
- self.active_tool = "none"
136
-
137
- self._last_snap_position = None
138
- self._last_snap_ts = 0
139
-
140
- context = self.context
141
- # Add in snap-to-grid functionality.
142
- self.widget_scene.add_scenewidget(AttractionWidget(self.widget_scene))
143
-
144
- # Tool container - Widget to hold tools.
145
- self.tool_container = ToolContainer(self.widget_scene)
146
- self.widget_scene.add_scenewidget(self.tool_container)
147
-
148
- # Rectangular selection.
149
- self.widget_scene.add_scenewidget(RectSelectWidget(self.widget_scene))
150
-
151
- # Laser-Path blue-line drawer.
152
- self.laserpath_widget = LaserPathWidget(self.widget_scene)
153
- self.widget_scene.add_scenewidget(self.laserpath_widget)
154
-
155
- # Draw elements in scene.
156
- self.widget_scene.add_scenewidget(
157
- ElementsWidget(self.widget_scene, LaserRender(context))
158
- )
159
-
160
- # Draw Machine Origin widget.
161
- self.widget_scene.add_scenewidget(MachineOriginWidget(self.widget_scene))
162
-
163
- # Draw Grid.
164
- self.grid = GridWidget(self.widget_scene)
165
- self.widget_scene.add_scenewidget(self.grid)
166
-
167
- # Draw Bed
168
- self.widget_scene.add_scenewidget(BedWidget(self.widget_scene))
169
-
170
- # Draw Interface Guide.
171
- self.widget_scene.add_interfacewidget(GuideWidget(self.widget_scene))
172
-
173
- # Draw Interface Laser-Position
174
- self.widget_scene.add_interfacewidget(ReticleWidget(self.widget_scene))
175
-
176
- sizer_2 = wx.BoxSizer(wx.VERTICAL)
177
- sizer_2.Add(self.scene, 20, wx.EXPAND, 0)
178
- self.SetSizer(sizer_2)
179
- sizer_2.Fit(self)
180
- self.Layout()
181
-
182
- # Allow Scene update from now on (are suppressed by default during startup phase)
183
- self.widget_scene.suppress_changes = False
184
- self._keybind_channel = self.context.channel("keybinds")
185
-
186
- if platform.system() == "Windows":
187
-
188
- def charhook(event):
189
- keyvalue = get_key_name(event)
190
- if is_navigation_key(keyvalue):
191
- if self._keybind_channel:
192
- self._keybind_channel(
193
- f"Scene, char_hook used for key_down: {keyvalue}"
194
- )
195
- self.on_key_down(event)
196
- event.Skip()
197
- else:
198
- event.DoAllowNextEvent()
199
-
200
- self.scene.Bind(wx.EVT_CHAR_HOOK, charhook)
201
- self.scene.Bind(wx.EVT_KEY_UP, self.on_key_up)
202
- self.scene.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
203
- self.scene.scene_panel.Bind(wx.EVT_KEY_UP, self.on_key_up)
204
- self.scene.scene_panel.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
205
-
206
- self.Bind(wx.EVT_SIZE, self.on_size)
207
-
208
- self._tool_widget = None
209
-
210
- context.register("tool/draw", DrawTool)
211
- context.register("tool/rect", RectTool)
212
- context.register("tool/line", LineTool)
213
- context.register("tool/polyline", PolylineTool)
214
- context.register("tool/polygon", PolygonTool)
215
- context.register("tool/point", PointTool)
216
- context.register("tool/circle", CircleTool)
217
- context.register("tool/ellipse", EllipseTool)
218
- context.register("tool/relocate", RelocateTool)
219
- context.register("tool/text", TextTool)
220
- context.register("tool/vector", VectorTool)
221
- context.register("tool/measure", MeasureTool)
222
- context.register("tool/ribbon", RibbonTool)
223
- context.register("tool/linetext", LineTextTool)
224
- context.register("tool/edit", EditTool)
225
- context.register("tool/placement", PlacementTool)
226
- context.register("tool/nodemove", NodeMoveTool)
227
- context.register("tool/parameter", ParameterTool)
228
- context.register("tool/imagecut", ImageCutTool)
229
-
230
- bsize_normal = STD_ICON_SIZE
231
-
232
- def proxy_linetext():
233
- from meerk40t.extra.hershey import have_hershey_fonts
234
-
235
- if have_hershey_fonts(context):
236
- context.kernel.elements("tool linetext\n")
237
- else:
238
- context.kernel.elements("window open HersheyFontManager\n")
239
-
240
- context.kernel.register(
241
- "button/tools/Linetext",
242
- {
243
- "label": _("Vector Text"),
244
- "icon": icons8_text,
245
- "tip": _("Add a vector text element"),
246
- "action": lambda v: proxy_linetext(),
247
- "group": "tool",
248
- "size": bsize_normal,
249
- "identifier": "linetext",
250
- },
251
- )
252
-
253
- context.kernel.register(
254
- "button/align/refob",
255
- {
256
- "label": _("Ref. Obj."),
257
- "icon": icons8_r_white,
258
- "tip": _("Toggle Reference Object Status"),
259
- "action": lambda v: self.toggle_ref_obj(),
260
- "size": bsize_normal,
261
- "identifier": "refobj",
262
- "rule_enabled": lambda cond: len(
263
- list(context.kernel.elements.elems(emphasized=True))
264
- )
265
- == 1,
266
- },
267
- )
268
-
269
- # Provide a reference to current scene in root context
270
- setattr(self.context.root, "mainscene", self.widget_scene)
271
-
272
- @context.console_command("dialog_fps", hidden=True)
273
- def dialog_fps(**kwgs):
274
- dlg = wx.TextEntryDialog(
275
- None, _("Enter FPS Limit"), _("FPS Limit Entry"), ""
276
- )
277
- dlg.SetValue("")
278
-
279
- if dlg.ShowModal() == wx.ID_OK:
280
- fps = dlg.GetValue()
281
- try:
282
- self.widget_scene.set_fps(int(fps))
283
- except ValueError:
284
- pass
285
- dlg.Destroy()
286
-
287
- # @context.console_command("tool_menu", hidden=True)
288
- # def tool_menu(channel, _, **kwgs):
289
- # orgx = 5
290
- # orgy = 5
291
- # # Are guides drawn?
292
- # if self.context.draw_mode & DRAW_MODE_GUIDES == 0:
293
- # orgx += 25
294
- # orgy += 25
295
- # if self._tool_widget is not None:
296
- # visible = self._tool_widget.visible
297
- # self._tool_widget.show(not visible)
298
- # self.widget_scene.request_refresh()
299
-
300
- # if self._tool_widget is None:
301
- # self._tool_widget = ToggleWidget(
302
- # self.widget_scene,
303
- # orgx,
304
- # orgy,
305
- # orgx + 25,
306
- # orgy + 25,
307
- # icons8_menu.GetBitmap(use_theme=False),
308
- # "button/tool",
309
- # )
310
- # self.widget_scene.widget_root.interface_widget.add_widget(
311
- # -1,
312
- # self._tool_widget,
313
- # )
314
- # channel(_("Added tool widget to interface"))
315
-
316
- @context.console_command("seek_bar", hidden=True)
317
- def seek_bar(channel, _, **kwgs):
318
- def changed(values, seeker):
319
- print(values)
320
-
321
- widget = SeekbarWidget(
322
- self.widget_scene, 25, 25, 200, 25, 0, 1000.0, changed
323
- )
324
-
325
- def clicked(values, seeker):
326
- self.widget_scene.widget_root.interface_widget.remove_widget(widget)
327
- self.widget_scene.request_refresh()
328
-
329
- widget.add_value(500.0)
330
- widget.add_value(250.0)
331
- widget.clicked = clicked
332
- self.widget_scene.widget_root.interface_widget.add_widget(-1, widget)
333
-
334
- channel(_("Added example_seekbar to interface"))
335
- self.widget_scene.request_refresh()
336
-
337
- @context.console_command("checkbox", hidden=True)
338
- def checkbox(channel, _, **kwgs):
339
- def checked(value):
340
- print(value)
341
-
342
- widget = CheckboxWidget(
343
- self.widget_scene,
344
- 25,
345
- 25,
346
- text="Example",
347
- tool_tip="Example's tool tip",
348
- checked=checked,
349
- )
350
- self.widget_scene.widget_root.interface_widget.add_widget(-1, widget)
351
- channel(_("Added example_checkbox to interface"))
352
- self.widget_scene.request_refresh()
353
-
354
- @context.console_command("cyclocycloid", hidden=True)
355
- def cyclocycloid(channel, _, **kwgs):
356
- self.widget_scene.widget_root.scene_widget.add_widget(
357
- 0, CyclocycloidWidget(self.widget_scene)
358
- )
359
- channel(_("Added cyclocycloid widget to scene."))
360
-
361
- @context.console_command("harmonograph", hidden=True)
362
- def harmonograph(channel, _, **kwgs):
363
- self.widget_scene.widget_root.scene_widget.add_widget(
364
- 0, HarmonographWidget(self.widget_scene)
365
- )
366
- self.widget_scene.request_refresh()
367
- channel(_("Added harmonograph widget to scene."))
368
-
369
- @context.console_command("toast", hidden=True)
370
- def toast_scene(remainder, **kwgs):
371
- self.widget_scene.toast(remainder)
372
-
373
- @context.console_argument("tool", help=_("tool to use."))
374
- @context.console_command("tool", help=_("sets a particular tool for the scene"))
375
- def tool_base(command, channel, _, tool=None, **kwgs):
376
- if tool is None:
377
- channel(_("Tools:"))
378
- channel("none")
379
- for t in context.match("tool/", suffix=True):
380
- channel(t)
381
- channel(_("-------"))
382
- return
383
- toolbar = context.lookup("ribbonbar/tools")
384
- # Reset the edit toolbar
385
- if toolbar is not None:
386
- toolbar.remove_page("toolcontainer")
387
- for pages in toolbar.pages:
388
- pages.visible = True
389
- toolbar.validate_current_page()
390
- toolbar.apply_enable_rules()
391
- toolbar.modified()
392
- try:
393
- if tool == "none":
394
- success, response = self.tool_container.set_tool(None)
395
- channel(response)
396
- if not success:
397
- return
398
- else:
399
- success, response = self.tool_container.set_tool(tool.lower())
400
- channel(response)
401
- if not success:
402
- return
403
- # Reset the edit toolbar
404
- if toolbar is not None:
405
- toolbar.remove_page("toolcontainer")
406
- tool_values = list(
407
- context.find(f"button/secondarytool_{tool}/.*")
408
- )
409
- # print(f"button/secondarytool_{tool}/.*\n{tool_values}")
410
- if tool_values is not None and len(tool_values) > 0:
411
- for pages in toolbar.pages:
412
- pages.visible = False
413
- newpage = toolbar.add_page(
414
- "toolcontainer",
415
- "toolcontainer",
416
- "Select",
417
- None,
418
- )
419
-
420
- select_panel = toolbar.add_panel(
421
- "toolback",
422
- newpage,
423
- "toolback",
424
- "Select",
425
- None,
426
- )
427
- select_values = (
428
- (
429
- context.lookup("button/select/Scene"),
430
- "button/select/Scene",
431
- "Select",
432
- ),
433
- )
434
- select_panel.set_buttons(select_values)
435
-
436
- tool_panel = toolbar.add_panel(
437
- "toolutil",
438
- newpage,
439
- "toolutil",
440
- "Tools",
441
- None,
442
- )
443
- tool_panel.set_buttons(tool_values)
444
- newpage.visible = True
445
- toolbar.validate_current_page()
446
- toolbar.apply_enable_rules()
447
-
448
- toolbar.modified()
449
-
450
- except (KeyError, AttributeError):
451
- raise CommandSyntaxError
452
-
453
- @context.console_argument("page", help=_("page to use."))
454
- @context.console_command(
455
- "page", help=_("Switches to a particular page in the ribbonbar")
456
- )
457
- def page_base(command, channel, _, page=None, **kwgs):
458
- # No need to store it beyond
459
- context = self.context.root
460
- context.setting(str, "_active_page", "")
461
- if page is None:
462
- channel(_("Active Page: {page}").format(page=context._active_page))
463
- return
464
- else:
465
- page = page.lower()
466
- if page == "none":
467
- page = "home"
468
- context._active_page = page
469
- self.context.signal("page", page)
470
-
471
- @context.console_command("laserpath_clear", hidden=True)
472
- def clear_laser_path(**kwgs):
473
- self.laserpath_widget.clear_laserpath()
474
- self.request_refresh()
475
-
476
- @self.context.console_command("scene", output_type="scene")
477
- def scene(command, _, channel, **kwgs):
478
- channel(f"scene: {str(self.widget_scene)}")
479
- return "scene", self.widget_scene
480
-
481
- @self.context.console_argument(
482
- "aspect", type=str, help="aspect of the scene to color"
483
- )
484
- @self.context.console_argument(
485
- "color", type=str, help="color to apply to scene"
486
- )
487
- @self.context.console_command("color", input_type="scene")
488
- def scene_color(command, _, channel, data, aspect=None, color=None, **kwgs):
489
- """
490
- Sets the scene colors. This is usually done with `scene color <aspect> <color>` which
491
- sets the aspect to the color specified. `scene color unset` unsets all colors and returns
492
- them to the default settings. `scene color random` changes all colors to random.
493
- """
494
- if aspect is None:
495
- for key in dir(self.context):
496
- if key.startswith("color_"):
497
- channel(key[6:])
498
- else:
499
- color_key = f"color_{aspect}"
500
- if aspect == "unset": # reset all
501
- self.widget_scene.colors.set_default_colors()
502
- self.context.signal("theme", True)
503
- return "scene", data
504
- if aspect == "random": # reset all
505
- self.widget_scene.colors.set_random_colors()
506
- self.context.signal("theme", True)
507
- return "scene", data
508
- if color == "unset": # reset one
509
- setattr(self.context, color_key, "default")
510
- self.context.signal("theme", True)
511
- return "scene", data
512
- if color == "random": # randomize one
513
- random_color = (
514
- f"#"
515
- f"{random.randint(0, 255):02X}"
516
- f"{random.randint(0, 255):02X}"
517
- f"{random.randint(0, 255):02X}"
518
- )
519
- setattr(self.context, color_key, random_color)
520
- self.context.signal("theme", True)
521
- return "scene", data
522
-
523
- if color is None:
524
- channel(
525
- _(
526
- "No color given! Please provide one like 'green', '#RRBBGGAA' (i.e. #FF000080 for semitransparent red)"
527
- )
528
- )
529
- return "scene", data
530
- color = Color(color)
531
- if hasattr(self.context, color_key):
532
- setattr(self.context, color_key, color.hexa)
533
- channel(_("Scene aspect color is set."))
534
- self.context.signal("theme", False)
535
- else:
536
- channel(
537
- _("{name} is not a known scene color command").format(
538
- name=aspect
539
- )
540
- )
541
-
542
- return "scene", data
543
-
544
- @self.context.console_argument(
545
- "zoom_x", type=float, help="zoom amount from current"
546
- )
547
- @self.context.console_argument(
548
- "zoom_y", type=float, help="zoom amount from current"
549
- )
550
- @self.context.console_command("aspect", input_type="scene")
551
- def scene_aspect(command, _, channel, data, zoom_x=1.0, zoom_y=1.0, **kwgs):
552
- if zoom_x is None or zoom_y is None:
553
- raise CommandSyntaxError
554
- matrix = data.widget_root.scene_widget.matrix
555
- matrix.post_scale(zoom_x, zoom_y)
556
- data.request_refresh()
557
- channel(str(matrix))
558
- return "scene", data
559
-
560
- @self.context.console_argument(
561
- "zoomfactor", type=float, help="zoom amount from current"
562
- )
563
- @self.context.console_command("zoom", input_type="scene")
564
- def scene_zoomfactor(command, _, channel, data, zoomfactor=1.0, **kwgs):
565
- matrix = data.widget_root.scene_widget.matrix
566
- if zoomfactor is None:
567
- zoomfactor = 1.0
568
- matrix.post_scale(zoomfactor)
569
- data.request_refresh()
570
- channel(str(matrix))
571
- return "scene", data
572
-
573
- @self.context.console_argument(
574
- "pan_x", type=float, default=0, help="pan from current position x"
575
- )
576
- @self.context.console_argument(
577
- "pan_y", type=float, default=0, help="pan from current position y"
578
- )
579
- @self.context.console_command("pan", input_type="scene")
580
- def scene_pan(command, _, channel, data, pan_x, pan_y, **kwgs):
581
- matrix = data.widget_root.scene_widget.matrix
582
- if pan_x is None or pan_y is None:
583
- return
584
- matrix.post_translate(pan_x, pan_y)
585
- data.request_refresh()
586
- channel(str(matrix))
587
- return "scene", data
588
-
589
- @self.context.console_argument(
590
- "angle", type=Angle.parse, default=0, help="Rotate scene"
591
- )
592
- @self.context.console_command("rotate", input_type="scene")
593
- def scene_rotate(command, _, channel, data, angle, **kwgs):
594
- matrix = data.widget_root.scene_widget.matrix
595
- if angle is not None:
596
- matrix.post_rotate(angle)
597
- data.request_refresh()
598
- channel(str(matrix))
599
- return "scene", data
600
-
601
- @self.context.console_command("reset", input_type="scene")
602
- def scene_reset(command, _, channel, data, **kwgs):
603
- matrix = data.widget_root.scene_widget.matrix
604
- matrix.reset()
605
- data.request_refresh()
606
- channel(str(matrix))
607
- return "scene", data
608
-
609
- @self.context.console_argument("x", type=str, help="x position")
610
- @self.context.console_argument("y", type=str, help="y position")
611
- @self.context.console_argument("width", type=str, help="width of view")
612
- @self.context.console_argument("height", type=str, help="height of view")
613
- @self.context.console_option(
614
- "animate",
615
- "a",
616
- type=bool,
617
- action="store_true",
618
- help="perform focus with animation",
619
- )
620
- @self.context.console_command("focus", input_type="scene")
621
- def scene_focus(
622
- command, _, channel, data, x, y, width, height, animate=False, **kwgs
623
- ):
624
- if height is None:
625
- raise CommandSyntaxError("x, y, width, height not specified")
626
- try:
627
- x = Length(
628
- x,
629
- relative_length=self.context.device.view.width,
630
- unitless=UNITS_PER_PIXEL,
631
- )
632
- y = Length(
633
- y,
634
- relative_length=self.context.device.view.height,
635
- unitless=UNITS_PER_PIXEL,
636
- )
637
- width = Length(
638
- width,
639
- relative_length=self.context.device.view.width,
640
- unitless=UNITS_PER_PIXEL,
641
- )
642
- height = Length(
643
- height,
644
- relative_length=self.context.device.view.height,
645
- unitless=UNITS_PER_PIXEL,
646
- )
647
- except ValueError:
648
- raise CommandSyntaxError("Not a valid length.")
649
- bbox = (x, y, width, height)
650
- matrix = data.widget_root.scene_widget.matrix
651
- data.widget_root.focus_viewport_scene(bbox, self.Size, animate=animate)
652
- data.request_refresh()
653
- channel(str(matrix))
654
- return "scene", data
655
-
656
- @context.console_command("feature_request")
657
- def send_developer_feature(remainder="", **kwgs):
658
- from .wxmeerk40t import send_data_to_developers
659
-
660
- send_data_to_developers("feature_request.txt", remainder)
661
-
662
- @context.console_command("bug")
663
- def send_developer_bug(remainder="", **kwgs):
664
- from .wxmeerk40t import send_data_to_developers
665
-
666
- send_data_to_developers("bug.txt", remainder)
667
-
668
- @context.console_command("reference")
669
- def make_reference(**kwgs):
670
- # Take first emphasized element
671
- for e in self.context.elements.flat(types=elem_nodes, emphasized=True):
672
- self.reference_object = e
673
- break
674
- self.context.signal("reference")
675
-
676
- # Establishes commands
677
- @context.console_argument(
678
- "target", type=str, help=_("Target (one of primary, secondary, circular")
679
- )
680
- @context.console_argument("ox", type=str, help=_("X-Position of origin"))
681
- @context.console_argument("oy", type=str, help=_("Y-Position of origin"))
682
- @context.console_argument(
683
- "scalex", type=str, help=_("Scaling of X-Axis for secondary")
684
- )
685
- @context.console_argument(
686
- "scaley", type=str, help=_("Scaling of Y-Axis for secondary")
687
- )
688
- @context.console_command(
689
- "grid",
690
- help=_("grid <target> <rows> <x_distance> <y_distance> <origin>"),
691
- input_type="scene",
692
- )
693
- def show_grid(
694
- command,
695
- channel,
696
- _,
697
- target=None,
698
- ox=None,
699
- oy=None,
700
- scalex=None,
701
- scaley=None,
702
- **kwgs,
703
- ):
704
- if target is None:
705
- channel(_("Grid-Parameters:"))
706
- p_state = _("On") if self.grid.draw_grid_primary else _("Off")
707
- channel(f"Primary: {p_state}")
708
- if self.grid.draw_grid_secondary:
709
- channel(f"Secondary: {_('On')}")
710
- if self.grid.grid_secondary_cx is not None:
711
- channel(
712
- f" cx: {Length(amount=self.grid.grid_secondary_cx).length_mm}"
713
- )
714
- if self.grid.grid_secondary_cy is not None:
715
- channel(
716
- f" cy: {Length(amount=self.grid.grid_secondary_cy).length_mm}"
717
- )
718
- if self.grid.grid_secondary_scale_x is not None:
719
- channel(f" scale-x: {self.grid.grid_secondary_scale_x:.2f}")
720
- if self.grid.grid_secondary_scale_y is not None:
721
- channel(f" scale-y: {self.grid.grid_secondary_scale_y:.2f}")
722
- else:
723
- channel(f"Secondary: {_('Off')}")
724
- if self.grid.draw_grid_circular:
725
- channel(f"Circular: {_('On')}")
726
- if self.grid.grid_circular_cx is not None:
727
- channel(
728
- f" cx: {Length(amount=self.grid.grid_circular_cx).length_mm}"
729
- )
730
- if self.grid.grid_circular_cy is not None:
731
- channel(
732
- f" cy: {Length(amount=self.grid.grid_circular_cy).length_mm}"
733
- )
734
- else:
735
- channel(f"Circular: {_('Off')}")
736
- return
737
- else:
738
- target = target.lower()
739
- if target[0] == "p":
740
- self.grid.draw_grid_primary = not self.grid.draw_grid_primary
741
- channel(
742
- _("Turned primary grid on")
743
- if self.grid.draw_grid_primary
744
- else _("Turned primary grid off")
745
- )
746
- self.scene.signal("guide")
747
- self.scene.signal("grid")
748
- self.request_refresh()
749
- elif target[0] == "s":
750
- self.grid.draw_grid_secondary = not self.grid.draw_grid_secondary
751
- if self.grid.draw_grid_secondary:
752
- if ox is None:
753
- self.grid.grid_secondary_cx = None
754
- self.grid.grid_secondary_cy = None
755
- scalex = None
756
- scaley = None
757
- else:
758
- if oy is None:
759
- oy = ox
760
- self.grid.grid_secondary_cx = float(
761
- Length(
762
- ox, relative_length=self.context.device.view.width
763
- )
764
- )
765
- self.grid.grid_secondary_cy = float(
766
- Length(
767
- oy, relative_length=self.context.device.view.height
768
- )
769
- )
770
- if scalex is None:
771
- rot = self.scene.context.rotary
772
- if rot.rotary_enabled:
773
- scalex = rot.scale_x
774
- scaley = rot.scale_y
775
- else:
776
- scalex = 1.0
777
- scaley = 1.0
778
- else:
779
- scalex = float(scalex)
780
- if scaley is None:
781
- scaley = scalex
782
- else:
783
- scaley = float(scaley)
784
- self.grid.grid_secondary_scale_x = scalex
785
- self.grid.grid_secondary_scale_y = scaley
786
- channel(
787
- _(
788
- "Turned secondary grid on"
789
- if self.grid.draw_grid_secondary
790
- else "Turned secondary grid off"
791
- )
792
- )
793
- self.scene.signal("guide")
794
- self.scene.signal("grid")
795
- self.request_refresh()
796
- elif target[0] == "c":
797
- self.grid.draw_grid_circular = not self.grid.draw_grid_circular
798
- if self.grid.draw_grid_circular:
799
- if ox is None:
800
- self.grid.grid_circular_cx = None
801
- self.grid.grid_circular_cy = None
802
- else:
803
- if oy is None:
804
- oy = ox
805
- self.grid.grid_circular_cx = float(
806
- Length(
807
- ox, relative_length=self.context.device.view.width
808
- )
809
- )
810
- self.grid.grid_circular_cy = float(
811
- Length(
812
- oy, relative_length=self.context.device.view.height
813
- )
814
- )
815
- channel(
816
- _(
817
- "Turned circular grid on"
818
- if self.grid.draw_grid_circular
819
- else "Turned circular grid off"
820
- )
821
- )
822
- self.scene.signal("guide")
823
- self.scene.signal("grid")
824
- self.request_refresh()
825
- else:
826
- channel(_("Target needs to be one of primary, secondary, circular"))
827
-
828
- def toggle_ref_obj(self):
829
- for e in self.scene.context.elements.flat(types=elem_nodes, emphasized=True):
830
- if self.reference_object == e:
831
- self.reference_object = None
832
- else:
833
- self.reference_object = e
834
- break
835
- self.context.signal("reference")
836
- self.request_refresh()
837
-
838
- def validate_reference(self):
839
- """
840
- Check whether the reference is still valid
841
- """
842
- found = False
843
- if self._reference:
844
- for e in self.context.elements.flat(types=elem_nodes):
845
- # Here we ignore the lock-status of an element
846
- if e is self._reference:
847
- found = True
848
- break
849
- if not found:
850
- self._reference = None
851
-
852
- @property
853
- def reference_object(self):
854
- return self._reference
855
-
856
- @reference_object.setter
857
- def reference_object(self, ref_object):
858
- prev = self._reference
859
- self._reference = ref_object
860
- self.scene.reference_object = self._reference
861
- dlist = []
862
- if prev is not None:
863
- dlist.append(prev)
864
- if self._reference is not None:
865
- dlist.append(self._reference)
866
- if len(dlist) > 0:
867
- self.context.signal("element_property_update", dlist)
868
-
869
- ##########
870
- # MAGNETS
871
- ##########
872
-
873
- @property
874
- def magnet_attraction(self):
875
- return self._magnet_attraction
876
-
877
- @magnet_attraction.setter
878
- def magnet_attraction(self, value):
879
- if 0 <= value <= 5:
880
- self._magnet_attraction = value
881
- self.save_magnets()
882
-
883
- def save_magnets(self):
884
- try:
885
- with open(self._magnet_file, "w") as f:
886
- f.write(f"a={self.magnet_attraction}\n")
887
- for x in self.magnet_x:
888
- f.write(f"x={Length(x, preferred_units='mm').preferred_length}\n")
889
- for y in self.magnet_y:
890
- f.write(f"y={Length(y, preferred_units='mm').preferred_length}\n")
891
- except ValueError: # ( PermissionError, OSError, FileNotFoundError ):
892
- return
893
-
894
- def load_magnets(self):
895
- self.magnet_x = []
896
- self.magnet_y = []
897
- try:
898
- with open(self._magnet_file, "r") as f:
899
- for line in f:
900
- cline = line.strip()
901
- if cline != "":
902
- subs = cline.split("=")
903
- if len(subs) > 1:
904
- try:
905
- if subs[0] in ("a", "A"):
906
- # Attraction strength
907
- value = int(subs[1])
908
- if value < 0:
909
- value = 0
910
- if value > 5:
911
- value = 5
912
- self._magnet_attraction = value
913
- elif subs[0] in ("x", "X"):
914
- dimens = Length(subs[1])
915
- value = float(dimens)
916
- if value not in self.magnet_x:
917
- self.magnet_x.append(value)
918
- elif subs[0] in ("y", "Y"):
919
- dimens = Length(subs[1])
920
- value = float(dimens)
921
- if value not in self.magnet_y:
922
- self.magnet_y.append(value)
923
- except ValueError:
924
- pass
925
- except (PermissionError, OSError, FileNotFoundError):
926
- return
927
-
928
- def clear_magnets(self):
929
- self.magnet_x = []
930
- self.magnet_y = []
931
- self.save_magnets()
932
-
933
- def clear_magnets_conditionally(self):
934
- # Depending on setting
935
- if self.context.clear_magnets:
936
- self.clear_magnets()
937
-
938
- def toggle_x_magnet(self, x_value):
939
- if x_value in self.magnet_x:
940
- self.magnet_x.remove(x_value)
941
- else:
942
- self.magnet_x += [x_value]
943
-
944
- def toggle_y_magnet(self, y_value):
945
- if y_value in self.magnet_y:
946
- self.magnet_y.remove(y_value)
947
- else:
948
- self.magnet_y += [y_value]
949
-
950
- def magnet_attracted_x(self, x_value, useit):
951
- delta = float("inf")
952
- x_val = None
953
- if useit:
954
- for mag_x in self.magnet_x:
955
- if abs(x_value - mag_x) < delta:
956
- delta = abs(x_value - mag_x)
957
- x_val = mag_x
958
- return delta, x_val
959
-
960
- def magnet_attracted_y(self, y_value, useit):
961
- delta = float("inf")
962
- y_val = None
963
- if useit:
964
- for mag_y in self.magnet_y:
965
- if abs(y_value - mag_y) < delta:
966
- delta = abs(y_value - mag_y)
967
- y_val = mag_y
968
- return delta, y_val
969
-
970
- def revised_magnet_bound(self, bounds=None):
971
- dx = 0
972
- dy = 0
973
- if self.has_magnets() and self._magnet_attraction > 0:
974
- if self.grid.tick_distance > 0:
975
- s = f"{self.grid.tick_distance}{self.context.units_name}"
976
- len_tick = float(Length(s))
977
- # Attraction length is 1/3, 4/3, 9/3 of a grid-unit
978
- # fmt: off
979
- attraction_len = 1 / 3 * self._magnet_attraction * self._magnet_attraction * len_tick
980
-
981
- # print("Attraction len=%s, attract=%d, alen=%.1f, tlen=%.1f, factor=%.1f" % (s, self._magnet_attraction, attraction_len, len_tick, attraction_len / len_tick ))
982
- # fmt: on
983
- else:
984
- attraction_len = float(Length("1mm"))
985
-
986
- delta_x1, x1 = self.magnet_attracted_x(bounds[0], self.magnet_attract_x)
987
- delta_x2, x2 = self.magnet_attracted_x(bounds[2], self.magnet_attract_x)
988
- delta_x3, x3 = self.magnet_attracted_x(
989
- (bounds[0] + bounds[2]) / 2, self.magnet_attract_c
990
- )
991
- delta_y1, y1 = self.magnet_attracted_y(bounds[1], self.magnet_attract_y)
992
- delta_y2, y2 = self.magnet_attracted_y(bounds[3], self.magnet_attract_y)
993
- delta_y3, y3 = self.magnet_attracted_y(
994
- (bounds[1] + bounds[3]) / 2, self.magnet_attract_c
995
- )
996
- if delta_x3 < delta_x1 and delta_x3 < delta_x2:
997
- if delta_x3 < attraction_len:
998
- if x3 is not None:
999
- dx = x3 - (bounds[0] + bounds[2]) / 2
1000
- # print("X Take center , x=%.1f, dx=%.1f" % ((bounds[0] + bounds[2]) / 2, dx)
1001
- elif delta_x1 < delta_x2 and delta_x1 < delta_x3:
1002
- if delta_x1 < attraction_len:
1003
- if x1 is not None:
1004
- dx = x1 - bounds[0]
1005
- # print("X Take left side, x=%.1f, dx=%.1f" % (bounds[0], dx))
1006
- elif delta_x2 < delta_x1 and delta_x2 < delta_x3:
1007
- if delta_x2 < attraction_len:
1008
- if x2 is not None:
1009
- dx = x2 - bounds[2]
1010
- # print("X Take right side, x=%.1f, dx=%.1f" % (bounds[2], dx))
1011
- if delta_y3 < delta_y1 and delta_y3 < delta_y2:
1012
- if delta_y3 < attraction_len:
1013
- if y3 is not None:
1014
- dy = y3 - (bounds[1] + bounds[3]) / 2
1015
- # print("Y Take center , x=%.1f, dx=%.1f" % ((bounds[1] + bounds[3]) / 2, dy))
1016
- elif delta_y1 < delta_y2 and delta_y1 < delta_y3:
1017
- if delta_y1 < attraction_len:
1018
- if y1 is not None:
1019
- dy = y1 - bounds[1]
1020
- # print("Y Take top side, y=%.1f, dy=%.1f" % (bounds[1], dy))
1021
- elif delta_y2 < delta_y1 and delta_y2 < delta_y3:
1022
- if delta_y2 < attraction_len:
1023
- if y2 is not None:
1024
- dy = y2 - bounds[3]
1025
- # print("Y Take bottom side, y=%.1f, dy=%.1f" % (bounds[3], dy))
1026
-
1027
- return dx, dy
1028
-
1029
- def has_magnets(self):
1030
- return len(self.magnet_x) + len(self.magnet_y) > 0
1031
-
1032
- ##############
1033
- # SNAPS
1034
- ##############
1035
-
1036
- @property
1037
- def last_snap(self):
1038
- result = self._last_snap_position
1039
- # Too old? Discard
1040
- if (time.time() - self._last_snap_ts) > 0.5:
1041
- result = None
1042
- return result
1043
-
1044
- @last_snap.setter
1045
- def last_snap(self, value):
1046
- self._last_snap_position = value
1047
- if value is None:
1048
- self._last_snap_ts = 0
1049
- else:
1050
- self._last_snap_ts = time.time()
1051
-
1052
- @signal_listener("make_reference")
1053
- def listen_make_ref(self, origin, *args):
1054
- node = args[0]
1055
- self.reference_object = node
1056
- self.context.signal("reference")
1057
-
1058
- @signal_listener("draw_mode")
1059
- def on_draw_mode(self, origin, *args):
1060
- if self._tool_widget is not None:
1061
- orgx = 5
1062
- orgy = 5
1063
- # Are guides drawn?
1064
- if self.context.draw_mode & DRAW_MODE_GUIDES == 0:
1065
- orgx += 25
1066
- orgy += 25
1067
- self._tool_widget.set_position(orgx, orgy)
1068
-
1069
- @signal_listener("scene_right_click")
1070
- def on_scene_right(self, origin, *args):
1071
- def zoom_to_bed(event=None):
1072
- zoom = self.context.zoom_margin
1073
- self.context(f"scene focus -a {-zoom}% {-zoom}% {zoom+100}% {zoom+100}%\n")
1074
-
1075
- def zoom_to_selected(event=None):
1076
- bbox = self.context.elements.selected_area()
1077
- if bbox is None:
1078
- zoom_to_bed(event=event)
1079
- else:
1080
- zfact = self.context.zoom_margin / 100.0
1081
-
1082
- x_delta = (bbox[2] - bbox[0]) * zfact
1083
- y_delta = (bbox[3] - bbox[1]) * zfact
1084
- x0 = Length(
1085
- amount=bbox[0] - x_delta,
1086
- relative_length=self.context.device.view.width,
1087
- ).length_mm
1088
- y0 = Length(
1089
- amount=bbox[1] - y_delta,
1090
- relative_length=self.context.device.view.height,
1091
- ).length_mm
1092
- x1 = Length(
1093
- amount=bbox[2] + x_delta,
1094
- relative_length=self.context.device.view.width,
1095
- ).length_mm
1096
- y1 = Length(
1097
- amount=bbox[3] + y_delta,
1098
- relative_length=self.context.device.view.height,
1099
- ).length_mm
1100
- self.context(f"scene focus -a {x0} {y0} {x1} {y1}\n")
1101
-
1102
- def toggle_background(event=None):
1103
- """
1104
- Toggle the draw mode for the background
1105
- """
1106
- self.widget_scene.context.draw_mode ^= DRAW_MODE_BACKGROUND
1107
- self.widget_scene.request_refresh()
1108
-
1109
- def toggle_grid(gridtype):
1110
- if gridtype == "primary":
1111
- self.grid.draw_grid_primary = not self.grid.draw_grid_primary
1112
- elif gridtype == "secondary":
1113
- self.grid.draw_grid_secondary = not self.grid.draw_grid_secondary
1114
- elif gridtype == "circular":
1115
- self.grid.draw_grid_circular = not self.grid.draw_grid_circular
1116
- self.request_refresh()
1117
-
1118
- def toggle_grid_p(event=None):
1119
- toggle_grid("primary")
1120
-
1121
- def toggle_grid_s(event=None):
1122
- toggle_grid("secondary")
1123
-
1124
- def toggle_grid_c(event=None):
1125
- toggle_grid("circular")
1126
-
1127
- def remove_background(event=None):
1128
- self.widget_scene._signal_widget(
1129
- self.widget_scene.widget_root, "background", None
1130
- )
1131
- self.widget_scene.request_refresh()
1132
-
1133
- def stop_auto_update(event=None):
1134
- self.context("timer.updatebg --off\n")
1135
-
1136
- gui = self
1137
- menu = wx.Menu()
1138
- id1 = menu.Append(
1139
- wx.ID_ANY,
1140
- _("Show Background"),
1141
- _("Display the background picture in the scene"),
1142
- wx.ITEM_CHECK,
1143
- )
1144
- self.Bind(wx.EVT_MENU, toggle_background, id=id1.GetId())
1145
- menu.Check(
1146
- id1.GetId(),
1147
- (self.widget_scene.context.draw_mode & DRAW_MODE_BACKGROUND == 0),
1148
- )
1149
- id2 = menu.Append(
1150
- wx.ID_ANY,
1151
- _("Show Primary Grid"),
1152
- _("Display the primary grid in the scene"),
1153
- wx.ITEM_CHECK,
1154
- )
1155
- self.Bind(wx.EVT_MENU, toggle_grid_p, id=id2.GetId())
1156
- menu.Check(id2.GetId(), self.grid.draw_grid_primary)
1157
- id3 = menu.Append(
1158
- wx.ID_ANY,
1159
- _("Show Secondary Grid"),
1160
- _("Display the secondary grid in the scene"),
1161
- wx.ITEM_CHECK,
1162
- )
1163
- self.Bind(wx.EVT_MENU, toggle_grid_s, id=id3.GetId())
1164
- menu.Check(id3.GetId(), self.grid.draw_grid_secondary)
1165
- id4 = menu.Append(
1166
- wx.ID_ANY,
1167
- _("Show Circular Grid"),
1168
- _("Display the circular grid in the scene"),
1169
- wx.ITEM_CHECK,
1170
- )
1171
- self.Bind(wx.EVT_MENU, toggle_grid_c, id=id4.GetId())
1172
- menu.Check(id4.GetId(), self.grid.draw_grid_circular)
1173
- if self.widget_scene.has_background:
1174
- menu.AppendSeparator()
1175
- id5 = menu.Append(wx.ID_ANY, _("Remove Background"), "")
1176
- self.Bind(wx.EVT_MENU, remove_background, id=id5.GetId())
1177
- # Do we have a timer called .updatebg?
1178
- we_have_a_job = False
1179
- try:
1180
- obj = self.context.kernel.jobs["timer.updatebg"]
1181
- if obj is not None:
1182
- we_have_a_job = True
1183
- except KeyError:
1184
- pass
1185
- if we_have_a_job:
1186
- self.Bind(
1187
- wx.EVT_MENU,
1188
- lambda e: stop_auto_update(),
1189
- menu.Append(
1190
- wx.ID_ANY,
1191
- _("Stop autoupdate"),
1192
- _("Stop automatic refresh of background image"),
1193
- ),
1194
- )
1195
- menu.AppendSeparator()
1196
- self.Bind(
1197
- wx.EVT_MENU,
1198
- lambda e: zoom_to_bed(),
1199
- menu.Append(
1200
- wx.ID_ANY,
1201
- _("&Zoom to Bed"),
1202
- _("View the whole laser bed"),
1203
- ),
1204
- )
1205
- if self.context.elements.has_emphasis():
1206
- self.Bind(
1207
- wx.EVT_MENU,
1208
- lambda e: zoom_to_selected(),
1209
- menu.Append(
1210
- wx.ID_ANY,
1211
- _("Zoom to &Selected"),
1212
- _("Fill the scene area with the selected elements"),
1213
- ),
1214
- )
1215
-
1216
- if menu.MenuItemCount != 0:
1217
- gui.PopupMenu(menu)
1218
- menu.Destroy()
1219
-
1220
- @signal_listener("refresh_scene")
1221
- def on_refresh_scene(self, origin, scene_name=None, *args):
1222
- """
1223
- Called by 'refresh_scene' change. To refresh tree.
1224
-
1225
- @param origin: the path of the originating signal
1226
- @param scene_name: Scene to refresh on if matching
1227
- @param args:
1228
- @return:
1229
- """
1230
- if scene_name == "Scene":
1231
- self.request_refresh()
1232
-
1233
- @signal_listener("bedsize")
1234
- def on_bedsize_simple(self, origin, nocmd=None, *args):
1235
- # The next two are more or less the same, so we remove the direct invocation...
1236
- # self.context.device.realize()
1237
- issue_command = True
1238
- if nocmd is not None and nocmd:
1239
- issue_command = False
1240
- if issue_command:
1241
- self.context("viewport_update\n")
1242
- self.scene.signal("guide")
1243
- self.scene.signal("grid")
1244
- self.request_refresh(origin)
1245
-
1246
- @signal_listener("magnet-attraction")
1247
- def on_magnet_attract(self, origin, strength, *args):
1248
- strength = int(strength)
1249
- if strength < 0:
1250
- strength = 0
1251
- self.magnet_attraction = strength
1252
-
1253
- @signal_listener("magnet_gen")
1254
- def on_magnet_generate(self, origin, *args):
1255
- candidate = args[0]
1256
- if candidate is None:
1257
- return
1258
- if not isinstance(candidate, (tuple, list)) or len(candidate) < 2:
1259
- return
1260
- method = candidate[0]
1261
- node = candidate[1]
1262
- bb = node.bounds
1263
- if method == "outer":
1264
- self.toggle_x_magnet(bb[0])
1265
- self.toggle_x_magnet(bb[2])
1266
- self.toggle_y_magnet(bb[1])
1267
- self.toggle_y_magnet(bb[3])
1268
- elif method == "center":
1269
- self.toggle_x_magnet((bb[0] + bb[2]) / 2)
1270
- self.toggle_y_magnet((bb[1] + bb[3]) / 2)
1271
- self.save_magnets()
1272
- self.request_refresh()
1273
-
1274
- def pane_show(self, *args):
1275
- zl = self.context.zoom_margin
1276
- self.context(f"scene focus -{zl}% -{zl}% {100 + zl}% {100 + zl}%\n")
1277
-
1278
- def pane_hide(self, *args):
1279
- pass
1280
-
1281
- @signal_listener("activate;device")
1282
- def on_activate_device(self, origin, device):
1283
- self.scene.signal("grid")
1284
- self.request_refresh()
1285
-
1286
- def on_size(self, event):
1287
- if self.context is None:
1288
- return
1289
- self.Layout()
1290
- # Refresh not needed as scenepanel already does it...
1291
- # self.scene.signal("guide")
1292
- # self.request_refresh()
1293
-
1294
- def on_close(self, event):
1295
- self.save_magnets()
1296
-
1297
- @signal_listener("pause")
1298
- @signal_listener("pipe;running")
1299
- def on_driver_mode(self, origin, *args):
1300
- # pipe running has (state) as args
1301
- new_color = None
1302
- try:
1303
- if self.context.device.driver.paused:
1304
- new_color = self.context.themes.get("pause_bg")
1305
- elif self.context.device.laser_status == "active":
1306
- new_color = self.context.themes.get("stop_bg")
1307
- except AttributeError:
1308
- pass
1309
- self.widget_scene.overrule_background = new_color
1310
- self.widget_scene.request_refresh_for_animation()
1311
-
1312
- @signal_listener("background")
1313
- def on_background_signal(self, origin, background):
1314
- background = wx.Bitmap.FromBuffer(*background)
1315
- self.scene.signal("background", background)
1316
- self.request_refresh()
1317
-
1318
- @signal_listener("units")
1319
- def space_changed(self, origin, *args):
1320
- self.scene.signal("guide")
1321
- self.scene.signal("grid")
1322
- self.request_refresh(origin)
1323
-
1324
- @signal_listener("bed_size")
1325
- def bed_changed(self, origin, *args):
1326
- self.scene.signal("grid")
1327
- # self.scene.signal('guide')
1328
- self.request_refresh(origin)
1329
-
1330
- @signal_listener("tool_modified")
1331
- def on_modification_by_tool(self, origin, *args):
1332
- self.scene.signal("tool_modified")
1333
-
1334
- @signal_listener("emphasized")
1335
- def on_emphasized_elements_changed(self, origin, *args):
1336
- self.scene.signal("emphasized")
1337
- self.laserpath_widget.clear_laserpath()
1338
- self.request_refresh(origin)
1339
-
1340
- def request_refresh(self, *args):
1341
- self.widget_scene.request_refresh(*args)
1342
-
1343
- @signal_listener("altered")
1344
- @signal_listener("modified")
1345
- def on_element_modified(self, *args):
1346
- self.scene.signal("modified")
1347
- self.widget_scene.request_refresh(*args)
1348
-
1349
- @signal_listener("linetext")
1350
- def on_signal_linetext(self, origin, *args):
1351
- if len(args) == 1:
1352
- self.scene.signal("linetext", args[0])
1353
- elif len(args) > 1:
1354
- self.scene.signal("linetext", args[0], args[1])
1355
-
1356
- @signal_listener("nodeedit")
1357
- def on_signal_nodeedit(self, origin, *args):
1358
- if len(args) == 1:
1359
- self.scene.signal("nodeedit", args[0])
1360
- elif len(args) > 1:
1361
- self.scene.signal("nodeedit", args[0], args[1])
1362
-
1363
- @signal_listener("element_added")
1364
- @signal_listener("tree_changed")
1365
- def on_elements_added(self, origin, nodes=None, *args):
1366
- self.scene.signal("element_added", nodes)
1367
- # There may be a smarter way to eliminate unnecessary rebuilds, but it's doing the job...
1368
- # self.context.signal("rebuild_tree")
1369
- self.context.signal("refresh_tree", nodes)
1370
- self.widget_scene.request_refresh()
1371
-
1372
- @signal_listener("rebuild_tree")
1373
- def on_rebuild_tree(self, origin, *args):
1374
- self.widget_scene._signal_widget(
1375
- self.widget_scene.widget_root, "rebuild_tree", None
1376
- )
1377
-
1378
- @signal_listener("theme")
1379
- def on_theme_change(self, origin, theme=None):
1380
- self.scene.signal("theme", theme)
1381
- self.request_refresh(origin)
1382
-
1383
- @signal_listener("selstroke")
1384
- def on_selstroke(self, origin, rgb, *args):
1385
- # print (origin, rgb, args)
1386
- if rgb[0] == 255 and rgb[1] == 255 and rgb[2] == 255:
1387
- color = None
1388
- else:
1389
- color = Color(rgb[0], rgb[1], rgb[2])
1390
- self.widget_scene.context.elements.default_stroke = color
1391
-
1392
- @signal_listener("selfill")
1393
- def on_selfill(self, origin, rgb, *args):
1394
- # print (origin, rgb, args)
1395
- if rgb[0] == 255 and rgb[1] == 255 and rgb[2] == 255:
1396
- color = None
1397
- else:
1398
- color = Color(rgb[0], rgb[1], rgb[2])
1399
- self.widget_scene.context.elements.default_fill = color
1400
-
1401
- def on_key_down(self, event):
1402
- keyvalue = get_key_name(event)
1403
- ignore = self.tool_active
1404
- if self._keybind_channel:
1405
- self._keybind_channel(f"Scene key_down: {keyvalue}.")
1406
- if not ignore and self.context.bind.trigger(keyvalue):
1407
- if self._keybind_channel:
1408
- self._keybind_channel(f"Scene key_down: {keyvalue} executed.")
1409
- else:
1410
- if self._keybind_channel:
1411
- if ignore:
1412
- self._keybind_channel(
1413
- f"Scene key_down: {keyvalue} was ignored as tool active."
1414
- )
1415
- else:
1416
- self._keybind_channel(f"Scene key_down: {keyvalue} unfound.")
1417
- event.Skip()
1418
-
1419
- def on_key_up(self, event, log=True):
1420
- keyvalue = get_key_name(event)
1421
- ignore = self.tool_active
1422
- if self._keybind_channel:
1423
- self._keybind_channel(f"Scene key_up: {keyvalue}.")
1424
- if not ignore and self.context.bind.untrigger(keyvalue):
1425
- if self._keybind_channel:
1426
- self._keybind_channel(f"Scene key_up: {keyvalue} executed.")
1427
- else:
1428
- if self._keybind_channel:
1429
- if ignore:
1430
- self._keybind_channel(
1431
- f"Scene key_up: {keyvalue} was ignored as tool active."
1432
- )
1433
- else:
1434
- self._keybind_channel(f"Scene key_up: {keyvalue} unfound.")
1435
- event.Skip()
1436
-
1437
-
1438
- class SceneWindow(MWindow):
1439
- def __init__(self, *args, **kwds):
1440
- super().__init__(1280, 800, *args, **kwds)
1441
- self.panel = MeerK40tScenePanel(self, wx.ID_ANY, context=self.context)
1442
- self.add_module_delegate(self.panel)
1443
- _icon = wx.NullIcon
1444
- _icon.CopyFromBitmap(icon_meerk40t.GetBitmap())
1445
- self.SetIcon(_icon)
1446
- self.SetTitle(_("Scene"))
1447
- self.Layout()
1448
-
1449
- def window_open(self):
1450
- self.panel.pane_show()
1451
-
1452
- def window_close(self):
1453
- self.panel.pane_hide()
1
+ import platform
2
+ import random
3
+ import time
4
+
5
+ import wx
6
+ from PIL import Image
7
+ from wx import aui
8
+
9
+ from meerk40t.core.elements.element_types import elem_nodes
10
+ from meerk40t.core.node.elem_image import ImageNode
11
+ from meerk40t.core.units import UNITS_PER_PIXEL, Angle, Length
12
+ from meerk40t.gui.icons import STD_ICON_SIZE, icon_meerk40t, icons8_r_white, icons8_text
13
+ from meerk40t.gui.laserrender import DRAW_MODE_BACKGROUND, DRAW_MODE_GUIDES, LaserRender
14
+ from meerk40t.gui.mwindow import MWindow
15
+ from meerk40t.gui.propertypanels.imageproperty import ContourPanel
16
+ from meerk40t.gui.scene.scenepanel import ScenePanel
17
+
18
+ # from meerk40t.gui.scenewidgets.affinemover import AffineMover
19
+ from meerk40t.gui.scenewidgets.attractionwidget import AttractionWidget
20
+ from meerk40t.gui.scenewidgets.bedwidget import BedWidget
21
+ from meerk40t.gui.scenewidgets.elementswidget import ElementsWidget
22
+ from meerk40t.gui.scenewidgets.gridwidget import GridWidget
23
+ from meerk40t.gui.scenewidgets.guidewidget import GuideWidget
24
+ from meerk40t.gui.scenewidgets.laserpathwidget import LaserPathWidget
25
+ from meerk40t.gui.scenewidgets.machineoriginwidget import MachineOriginWidget
26
+
27
+ # from meerk40t.gui.scenewidgets.nodeselector import NodeSelector
28
+ from meerk40t.gui.scenewidgets.rectselectwidget import RectSelectWidget
29
+ from meerk40t.gui.scenewidgets.reticlewidget import ReticleWidget
30
+
31
+ # from meerk40t.gui.scenewidgets.selectionwidget import SelectionWidget
32
+ from meerk40t.gui.toolwidgets.toolcircle import CircleTool
33
+ from meerk40t.gui.toolwidgets.toolcontainer import ToolContainer
34
+ from meerk40t.gui.toolwidgets.tooldraw import DrawTool
35
+ from meerk40t.gui.toolwidgets.toolellipse import EllipseTool
36
+ from meerk40t.gui.toolwidgets.toolimagecut import ImageCutTool
37
+ from meerk40t.gui.toolwidgets.toolline import LineTool
38
+ from meerk40t.gui.toolwidgets.toollinetext import LineTextTool
39
+ from meerk40t.gui.toolwidgets.toolmeasure import MeasureTool
40
+ from meerk40t.gui.toolwidgets.toolnodeedit import EditTool
41
+ from meerk40t.gui.toolwidgets.toolnodemove import NodeMoveTool
42
+ from meerk40t.gui.toolwidgets.toolparameter import ParameterTool
43
+ from meerk40t.gui.toolwidgets.toolplacement import PlacementTool
44
+ from meerk40t.gui.toolwidgets.toolpoint import PointTool
45
+ from meerk40t.gui.toolwidgets.toolpointmove import PointMoveTool
46
+ from meerk40t.gui.toolwidgets.toolpolygon import PolygonTool
47
+ from meerk40t.gui.toolwidgets.toolpolyline import PolylineTool
48
+ from meerk40t.gui.toolwidgets.toolrect import RectTool
49
+ from meerk40t.gui.toolwidgets.toolrelocate import RelocateTool
50
+ from meerk40t.gui.toolwidgets.toolribbon import RibbonTool
51
+ from meerk40t.gui.toolwidgets.tooltabedit import TabEditTool
52
+ from meerk40t.gui.toolwidgets.tooltext import TextTool
53
+ from meerk40t.gui.toolwidgets.toolvector import VectorTool
54
+ from meerk40t.gui.utilitywidgets.checkboxwidget import CheckboxWidget
55
+ from meerk40t.gui.utilitywidgets.cyclocycloidwidget import CyclocycloidWidget
56
+ from meerk40t.gui.utilitywidgets.harmonograph import HarmonographWidget
57
+ from meerk40t.gui.utilitywidgets.seekbarwidget import SeekbarWidget
58
+ from meerk40t.gui.wxutils import get_key_name, is_navigation_key
59
+ from meerk40t.kernel import CommandSyntaxError, signal_listener
60
+ from meerk40t.svgelements import Color, Matrix
61
+
62
+ _ = wx.GetTranslation
63
+
64
+
65
+ def register_panel_scene(window, context):
66
+ # control = wx.aui.AuiNotebook(window, -1, size=(200, 150))
67
+ # panel1 = MeerK40tScenePanel(window, wx.ID_ANY, context=context, index=1)
68
+ # control.AddPage(panel1, "scene1")
69
+ # panel2 = MeerK40tScenePanel(window, wx.ID_ANY, context=context, index=2)
70
+ # control.AddPage(panel2, "scene2")
71
+
72
+ control = MeerK40tScenePanel(window, wx.ID_ANY, context=context)
73
+ pane = aui.AuiPaneInfo().CenterPane().MinSize(200, 200).Name("scene")
74
+ pane.dock_proportion = 600
75
+ pane.control = control
76
+ pane.hide_menu = True
77
+
78
+ # def on_note_page_change(event=None):
79
+ # if control.GetPageText(control.GetSelection()) == "scene1":
80
+ # context.kernel.activate_service_path('elements', 'elements')
81
+ # else:
82
+ # context.kernel.activate_service_path('elements', "elements1")
83
+ # context("refresh\n")
84
+ # control.Bind(aui.EVT_AUINOTEBOOK_PAGE_CHANGED, on_note_page_change, control)
85
+
86
+ window.on_pane_create(pane)
87
+ context.register("pane/scene", pane)
88
+
89
+
90
+ class ContourDetectionDialog(wx.Dialog):
91
+ def __init__(self, parent, context, node):
92
+ super().__init__(
93
+ parent, wx.ID_ANY, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
94
+ )
95
+ self.context = context
96
+ self._init_ui(node)
97
+ self._start_dialog()
98
+
99
+ def _init_ui(self, node):
100
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
101
+ panel = ContourPanel(
102
+ self, wx.ID_ANY, context=self.context, node=node, simplified=True, direct_mode=True,
103
+ )
104
+ main_sizer.Add(panel, 1, wx.EXPAND, 0)
105
+ buttons = self.CreateStdDialogButtonSizer(wx.OK)
106
+ main_sizer.Add(buttons, 0, wx.EXPAND, 0)
107
+ panel.pane_active()
108
+ self.SetSizer(main_sizer)
109
+ self.Layout()
110
+
111
+ def _start_dialog(self):
112
+ win_wd = self.context.setting(int, "win_bgcontour_width", 700)
113
+ win_ht = self.context.setting(int, "win_bgcontour_height", 500)
114
+ self.SetSize(win_wd, win_ht)
115
+ self.CenterOnParent()
116
+
117
+ def end_dialog(self):
118
+ # Save window size for next time
119
+ win_wd, win_ht = self.GetSize()
120
+ self.context.win_bgcontour_width = win_wd
121
+ self.context.win_bgcontour_height = win_ht
122
+ self.context.signal("refresh_scene", "Scene")
123
+
124
+
125
+ class MeerK40tScenePanel(wx.Panel):
126
+ def __init__(self, *args, context=None, index=None, **kwargs):
127
+ # begin wxGlade: ConsolePanel.__init__
128
+ kwargs["style"] = kwargs.get("style", 0) | wx.TAB_TRAVERSAL
129
+ wx.Panel.__init__(self, *args, **kwargs)
130
+ self.context = context
131
+ self.context.themes.set_window_colors(self)
132
+ self.scene = ScenePanel(
133
+ self.context,
134
+ self,
135
+ scene_name="Scene" if index is None else f"Scene{index}",
136
+ style=wx.EXPAND | wx.WANTS_CHARS,
137
+ )
138
+ self.scene.start_scene()
139
+ self.widget_scene = self.scene.scene
140
+
141
+ self.tool_active = False
142
+ self.modif_active = False
143
+ self.ignore_snap = False
144
+ self.suppress_selection = False
145
+ self._reference = None # Reference Object
146
+
147
+ # Stuff for magnet-lines
148
+ self.magnet_x = []
149
+ self.magnet_y = []
150
+ self._magnet_attraction = 2
151
+ # 0 off, `1..x` increasing strength (quadratic behaviour)
152
+ self.magnet_attract_x = True # Shall the X-Axis be affected
153
+ self.magnet_attract_y = True # Shall the Y-Axis be affected
154
+ self.magnet_attract_c = True # Shall the center be affected
155
+
156
+ self.context.setting(bool, "clear_magnets", True)
157
+
158
+ # Save / Load the content of magnets
159
+ from os.path import join, realpath
160
+
161
+ from meerk40t.kernel.functions import get_safe_path
162
+
163
+ self._magnet_file = join(
164
+ realpath(get_safe_path(self.context.kernel.name)), "magnets.cfg"
165
+ )
166
+ self.load_magnets()
167
+ # Add a plugin routine to be called at the time of a full new start
168
+ context.kernel.register(
169
+ "reset_routines/magnets", self.clear_magnets_conditionally
170
+ )
171
+
172
+ self.active_tool = "none"
173
+
174
+ self._last_snap_position = None
175
+ self._last_snap_ts = 0
176
+
177
+ context = self.context
178
+ # Add in snap-to-grid functionality.
179
+ self.widget_scene.add_scenewidget(AttractionWidget(self.widget_scene))
180
+
181
+ # Tool container - Widget to hold tools.
182
+ self.tool_container = ToolContainer(self.widget_scene)
183
+ self.widget_scene.add_scenewidget(self.tool_container)
184
+
185
+ # Rectangular selection.
186
+ self.widget_scene.add_scenewidget(RectSelectWidget(self.widget_scene))
187
+
188
+ # Laser-Path blue-line drawer.
189
+ self.laserpath_widget = LaserPathWidget(self.widget_scene)
190
+ self.widget_scene.add_scenewidget(self.laserpath_widget)
191
+
192
+ # Draw elements in scene.
193
+ self.widget_scene.add_scenewidget(
194
+ ElementsWidget(self.widget_scene, LaserRender(context))
195
+ )
196
+
197
+ # Draw Machine Origin widget.
198
+ self.widget_scene.add_scenewidget(MachineOriginWidget(self.widget_scene))
199
+
200
+ # Draw Grid.
201
+ self.grid = GridWidget(self.widget_scene)
202
+ self.widget_scene.add_scenewidget(self.grid)
203
+
204
+ # Draw Bed
205
+ self.widget_scene.add_scenewidget(BedWidget(self.widget_scene))
206
+
207
+ # Draw Interface Guide.
208
+ self.widget_scene.add_interfacewidget(GuideWidget(self.widget_scene))
209
+
210
+ # Draw Interface Laser-Position
211
+ self.widget_scene.add_interfacewidget(ReticleWidget(self.widget_scene))
212
+
213
+ sizer_2 = wx.BoxSizer(wx.VERTICAL)
214
+ sizer_2.Add(self.scene, 20, wx.EXPAND, 0)
215
+ self.SetSizer(sizer_2)
216
+ sizer_2.Fit(self)
217
+ self.Layout()
218
+
219
+ # Allow Scene update from now on (are suppressed by default during startup phase)
220
+ self.widget_scene.suppress_changes = False
221
+ self._keybind_channel = self.context.channel("keybinds")
222
+
223
+ if platform.system() == "Windows":
224
+
225
+ def charhook(event):
226
+ keyvalue = get_key_name(event)
227
+ if is_navigation_key(keyvalue):
228
+ if self._keybind_channel:
229
+ self._keybind_channel(
230
+ f"Scene, char_hook used for key_down: {keyvalue}"
231
+ )
232
+ self.on_key_down(event)
233
+ event.Skip()
234
+ else:
235
+ event.DoAllowNextEvent()
236
+
237
+ self.scene.Bind(wx.EVT_CHAR_HOOK, charhook)
238
+ self.scene.Bind(wx.EVT_KEY_UP, self.on_key_up)
239
+ self.scene.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
240
+
241
+ self.Bind(wx.EVT_SIZE, self.on_size)
242
+
243
+ self._tool_widget = None
244
+
245
+ context.register("tool/draw", DrawTool)
246
+ context.register("tool/rect", RectTool)
247
+ context.register("tool/line", LineTool)
248
+ context.register("tool/polyline", PolylineTool)
249
+ context.register("tool/polygon", PolygonTool)
250
+ context.register("tool/point", PointTool)
251
+ context.register("tool/circle", CircleTool)
252
+ context.register("tool/ellipse", EllipseTool)
253
+ context.register("tool/relocate", RelocateTool)
254
+ context.register("tool/text", TextTool)
255
+ context.register("tool/vector", VectorTool)
256
+ context.register("tool/measure", MeasureTool)
257
+ context.register("tool/ribbon", RibbonTool)
258
+ context.register("tool/linetext", LineTextTool)
259
+ context.register("tool/edit", EditTool)
260
+ context.register("tool/placement", PlacementTool)
261
+ context.register("tool/nodemove", NodeMoveTool)
262
+ context.register("tool/pointmove", PointMoveTool)
263
+ context.register("tool/parameter", ParameterTool)
264
+ context.register("tool/imagecut", ImageCutTool)
265
+ context.register("tool/tabedit", TabEditTool)
266
+
267
+ bsize_normal = STD_ICON_SIZE
268
+
269
+ def proxy_linetext():
270
+ if context.fonts.have_hershey_fonts():
271
+ context.kernel.elements("tool linetext\n")
272
+ else:
273
+ context.kernel.elements("window open HersheyFontManager\n")
274
+
275
+ context.kernel.register(
276
+ "button/tools/Linetext",
277
+ {
278
+ "label": _("Vector Text"),
279
+ "icon": icons8_text,
280
+ "tip": _("Add a vector text element"),
281
+ "action": lambda v: proxy_linetext(),
282
+ "group": "tool",
283
+ "size": bsize_normal,
284
+ "identifier": "linetext",
285
+ },
286
+ )
287
+
288
+ context.kernel.register(
289
+ "button/align/refob",
290
+ {
291
+ "label": _("Ref. Obj."),
292
+ "icon": icons8_r_white,
293
+ "tip": _("Toggle Reference Object Status"),
294
+ "action": lambda v: self.toggle_ref_obj(),
295
+ "size": bsize_normal,
296
+ "identifier": "refobj",
297
+ "rule_enabled": lambda cond: len(
298
+ list(context.kernel.elements.elems(emphasized=True))
299
+ )
300
+ == 1,
301
+ },
302
+ )
303
+
304
+ # Provide a reference to current scene in root context
305
+ setattr(self.context.root, "mainscene", self.widget_scene)
306
+
307
+ @context.console_command("dialog_fps", hidden=True)
308
+ def dialog_fps(**kwgs):
309
+ dlg = wx.TextEntryDialog(
310
+ None, _("Enter FPS Limit"), _("FPS Limit Entry"), ""
311
+ )
312
+ dlg.SetValue("")
313
+
314
+ if dlg.ShowModal() == wx.ID_OK:
315
+ fps = dlg.GetValue()
316
+ try:
317
+ self.widget_scene.set_fps(int(fps))
318
+ except ValueError:
319
+ pass
320
+ dlg.Destroy()
321
+
322
+ # @context.console_command("tool_menu", hidden=True)
323
+ # def tool_menu(channel, _, **kwgs):
324
+ # orgx = 5
325
+ # orgy = 5
326
+ # # Are guides drawn?
327
+ # if self.context.draw_mode & DRAW_MODE_GUIDES == 0:
328
+ # orgx += 25
329
+ # orgy += 25
330
+ # if self._tool_widget is not None:
331
+ # visible = self._tool_widget.visible
332
+ # self._tool_widget.show(not visible)
333
+ # self.widget_scene.request_refresh()
334
+
335
+ # if self._tool_widget is None:
336
+ # self._tool_widget = ToggleWidget(
337
+ # self.widget_scene,
338
+ # orgx,
339
+ # orgy,
340
+ # orgx + 25,
341
+ # orgy + 25,
342
+ # icons8_menu.GetBitmap(use_theme=False),
343
+ # "button/tool",
344
+ # )
345
+ # self.widget_scene.widget_root.interface_widget.add_widget(
346
+ # -1,
347
+ # self._tool_widget,
348
+ # )
349
+ # channel(_("Added tool widget to interface"))
350
+
351
+ @context.console_command("seek_bar", hidden=True)
352
+ def seek_bar(channel, _, **kwgs):
353
+ def changed(values, seeker):
354
+ print(values)
355
+
356
+ widget = SeekbarWidget(
357
+ self.widget_scene, 25, 25, 200, 25, 0, 1000.0, changed
358
+ )
359
+
360
+ def clicked(values, seeker):
361
+ self.widget_scene.widget_root.interface_widget.remove_widget(widget)
362
+ self.widget_scene.request_refresh()
363
+
364
+ widget.add_value(500.0)
365
+ widget.add_value(250.0)
366
+ widget.clicked = clicked
367
+ self.widget_scene.widget_root.interface_widget.add_widget(-1, widget)
368
+
369
+ channel(_("Added example_seekbar to interface"))
370
+ self.widget_scene.request_refresh()
371
+
372
+ @context.console_command("checkbox", hidden=True)
373
+ def checkbox(channel, _, **kwgs):
374
+ def checked(value):
375
+ print(value)
376
+
377
+ widget = CheckboxWidget(
378
+ self.widget_scene,
379
+ 25,
380
+ 25,
381
+ text="Example",
382
+ tool_tip="Example's tool tip",
383
+ checked=checked,
384
+ )
385
+ self.widget_scene.widget_root.interface_widget.add_widget(-1, widget)
386
+ channel(_("Added example_checkbox to interface"))
387
+ self.widget_scene.request_refresh()
388
+
389
+ @context.console_command("cyclocycloid", hidden=True)
390
+ def cyclocycloid(channel, _, **kwgs):
391
+ self.widget_scene.widget_root.scene_widget.add_widget(
392
+ 0, CyclocycloidWidget(self.widget_scene)
393
+ )
394
+ channel(_("Added cyclocycloid widget to scene."))
395
+
396
+ @context.console_command("harmonograph", hidden=True)
397
+ def harmonograph(channel, _, **kwgs):
398
+ self.widget_scene.widget_root.scene_widget.add_widget(
399
+ 0, HarmonographWidget(self.widget_scene)
400
+ )
401
+ self.widget_scene.request_refresh()
402
+ channel(_("Added harmonograph widget to scene."))
403
+
404
+ @context.console_command("toast", hidden=True)
405
+ def toast_scene(remainder, **kwgs):
406
+ self.widget_scene.toast(remainder)
407
+
408
+ @context.console_argument("tool", help=_("tool to use."))
409
+ @context.console_command("tool", help=_("sets a particular tool for the scene"))
410
+ def tool_base(command, channel, _, tool=None, remainder=None, **kwgs):
411
+ if tool is None:
412
+ channel(_("Tools:"))
413
+ channel("none")
414
+ for t in context.match("tool/", suffix=True):
415
+ channel(t)
416
+ channel(_("-------"))
417
+ return
418
+ toolbar = context.lookup("ribbonbar/tools")
419
+ # Reset the edit toolbar
420
+ if toolbar is not None:
421
+ toolbar.remove_page("toolcontainer")
422
+ for pages in toolbar.pages:
423
+ pages.visible = True
424
+ toolbar.validate_current_page()
425
+ toolbar.apply_enable_rules()
426
+ toolbar.modified()
427
+ try:
428
+ if tool == "none":
429
+ success, response = self.tool_container.set_tool(None, remainder)
430
+ channel(response)
431
+ if not success:
432
+ return
433
+ else:
434
+ success, response = self.tool_container.set_tool(
435
+ tool.lower(), remainder
436
+ )
437
+ channel(response)
438
+ if not success:
439
+ return
440
+ # Reset the edit toolbar
441
+ if toolbar is not None:
442
+ toolbar.remove_page("toolcontainer")
443
+ tool_values = list(
444
+ context.find(f"button/secondarytool_{tool}/.*")
445
+ )
446
+ # print(f"button/secondarytool_{tool}/.*\n{tool_values}")
447
+ if tool_values is not None and len(tool_values) > 0:
448
+ for pages in toolbar.pages:
449
+ pages.visible = False
450
+ newpage = toolbar.add_page(
451
+ "toolcontainer",
452
+ "toolcontainer",
453
+ "Select",
454
+ None,
455
+ )
456
+
457
+ select_panel = toolbar.add_panel(
458
+ "toolback",
459
+ newpage,
460
+ "toolback",
461
+ "Select",
462
+ None,
463
+ )
464
+ select_values = (
465
+ (
466
+ context.lookup("button/select/Scene"),
467
+ "button/select/Scene",
468
+ "Select",
469
+ ),
470
+ )
471
+ select_panel.set_buttons(select_values)
472
+
473
+ tool_panel = toolbar.add_panel(
474
+ "toolutil",
475
+ newpage,
476
+ "toolutil",
477
+ "Tools",
478
+ None,
479
+ )
480
+ tool_panel.set_buttons(tool_values)
481
+ newpage.visible = True
482
+ toolbar.validate_current_page()
483
+ toolbar.apply_enable_rules()
484
+
485
+ toolbar.modified()
486
+
487
+ except (KeyError, AttributeError):
488
+ raise CommandSyntaxError
489
+
490
+ @context.console_argument("page", help=_("page to use."))
491
+ @context.console_command(
492
+ "page", help=_("Switches to a particular page in the ribbonbar")
493
+ )
494
+ def page_base(command, channel, _, page=None, **kwgs):
495
+ # No need to store it beyond
496
+ context = self.context.root
497
+ context.setting(str, "_active_page", "")
498
+ if page is None:
499
+ channel(_("Active Page: {page}").format(page=context._active_page))
500
+ return
501
+ else:
502
+ page = page.lower()
503
+ if page == "none":
504
+ page = "home"
505
+ context._active_page = page
506
+ self.context.signal("page", page)
507
+
508
+ @context.console_command("laserpath_clear", hidden=True)
509
+ def clear_laser_path(**kwgs):
510
+ self.laserpath_widget.clear_laserpath()
511
+ self.request_refresh()
512
+
513
+ @self.context.console_command("scene", output_type="scene")
514
+ def scene(command, _, channel, **kwgs):
515
+ channel(f"scene: {str(self.widget_scene)}")
516
+ return "scene", self.widget_scene
517
+
518
+ @self.context.console_argument(
519
+ "aspect", type=str, help="aspect of the scene to color"
520
+ )
521
+ @self.context.console_argument(
522
+ "color", type=str, help="color to apply to scene"
523
+ )
524
+ @self.context.console_command("color", input_type="scene")
525
+ def scene_color(command, _, channel, data, aspect=None, color=None, **kwgs):
526
+ """
527
+ Sets the scene colors. This is usually done with `scene color <aspect> <color>` which
528
+ sets the aspect to the color specified. `scene color unset` unsets all colors and returns
529
+ them to the default settings. `scene color random` changes all colors to random.
530
+ """
531
+ if aspect is None:
532
+ for key in dir(self.context):
533
+ if key.startswith("color_"):
534
+ channel(key[6:])
535
+ else:
536
+ color_key = f"color_{aspect}"
537
+ if aspect == "unset": # reset all
538
+ self.widget_scene.colors.set_default_colors()
539
+ self.context.signal("theme", True)
540
+ return "scene", data
541
+ if aspect == "unsetbright": # reset all
542
+ self.widget_scene.colors.set_default_colors(brighter=True)
543
+ self.context.signal("theme", True)
544
+ return "scene", data
545
+ if aspect == "random": # reset all
546
+ self.widget_scene.colors.set_random_colors()
547
+ self.context.signal("theme", True)
548
+ return "scene", data
549
+ if color == "unset": # reset one
550
+ setattr(self.context, color_key, "default")
551
+ self.context.signal("theme", True)
552
+ return "scene", data
553
+ if color == "random": # randomize one
554
+ random_color = (
555
+ f"#"
556
+ f"{random.randint(0, 255):02X}"
557
+ f"{random.randint(0, 255):02X}"
558
+ f"{random.randint(0, 255):02X}"
559
+ )
560
+ setattr(self.context, color_key, random_color)
561
+ self.context.signal("theme", True)
562
+ return "scene", data
563
+
564
+ if color is None:
565
+ channel(
566
+ _(
567
+ "No color given! Please provide one like 'green', '#RRBBGGAA' (i.e. #FF000080 for semitransparent red)"
568
+ )
569
+ )
570
+ return "scene", data
571
+ color = Color(color)
572
+ if hasattr(self.context, color_key):
573
+ setattr(self.context, color_key, color.hexa)
574
+ channel(_("Scene aspect color is set."))
575
+ self.context.signal("theme", False)
576
+ else:
577
+ channel(
578
+ _("{name} is not a known scene color command").format(
579
+ name=aspect
580
+ )
581
+ )
582
+
583
+ return "scene", data
584
+
585
+ @self.context.console_argument(
586
+ "zoom_x", type=float, help="zoom amount from current"
587
+ )
588
+ @self.context.console_argument(
589
+ "zoom_y", type=float, help="zoom amount from current"
590
+ )
591
+ @self.context.console_command("aspect", input_type="scene")
592
+ def scene_aspect(command, _, channel, data, zoom_x=1.0, zoom_y=1.0, **kwgs):
593
+ if zoom_x is None or zoom_y is None:
594
+ raise CommandSyntaxError
595
+ matrix = data.widget_root.scene_widget.matrix
596
+ matrix.post_scale(zoom_x, zoom_y)
597
+ data.request_refresh()
598
+ channel(str(matrix))
599
+ return "scene", data
600
+
601
+ @self.context.console_argument(
602
+ "zoomfactor", type=float, help="zoom amount from current"
603
+ )
604
+ @self.context.console_command("zoom", input_type="scene")
605
+ def scene_zoomfactor(command, _, channel, data, zoomfactor=1.0, **kwgs):
606
+ matrix = data.widget_root.scene_widget.matrix
607
+ if zoomfactor is None:
608
+ zoomfactor = 1.0
609
+ matrix.post_scale(zoomfactor)
610
+ data.request_refresh()
611
+ channel(str(matrix))
612
+ return "scene", data
613
+
614
+ @self.context.console_argument(
615
+ "pan_x", type=float, default=0, help="pan from current position x"
616
+ )
617
+ @self.context.console_argument(
618
+ "pan_y", type=float, default=0, help="pan from current position y"
619
+ )
620
+ @self.context.console_command("pan", input_type="scene")
621
+ def scene_pan(command, _, channel, data, pan_x, pan_y, **kwgs):
622
+ matrix = data.widget_root.scene_widget.matrix
623
+ if pan_x is None or pan_y is None:
624
+ return
625
+ matrix.post_translate(pan_x, pan_y)
626
+ data.request_refresh()
627
+ channel(str(matrix))
628
+ return "scene", data
629
+
630
+ @self.context.console_argument(
631
+ "angle", type=Angle, default=0, help="Rotate scene"
632
+ )
633
+ @self.context.console_command("rotate", input_type="scene")
634
+ def scene_rotate(command, _, channel, data, angle, **kwgs):
635
+ matrix = data.widget_root.scene_widget.matrix
636
+ if angle is not None:
637
+ matrix.post_rotate(angle)
638
+ data.request_refresh()
639
+ channel(str(matrix))
640
+ return "scene", data
641
+
642
+ @self.context.console_command("reset", input_type="scene")
643
+ def scene_reset(command, _, channel, data, **kwgs):
644
+ matrix = data.widget_root.scene_widget.matrix
645
+ matrix.reset()
646
+ data.request_refresh()
647
+ channel(str(matrix))
648
+ return "scene", data
649
+
650
+ @self.context.console_argument("x", type=str, help="x position")
651
+ @self.context.console_argument("y", type=str, help="y position")
652
+ @self.context.console_argument("width", type=str, help="width of view")
653
+ @self.context.console_argument("height", type=str, help="height of view")
654
+ @self.context.console_option(
655
+ "animate",
656
+ "a",
657
+ type=bool,
658
+ action="store_true",
659
+ help="perform focus with animation",
660
+ )
661
+ @self.context.console_command("focus", input_type="scene")
662
+ def scene_focus(
663
+ command, _, channel, data, x, y, width, height, animate=False, **kwgs
664
+ ):
665
+ if animate:
666
+ overrule = self.context.setting(bool, "suppress_focus_animation", False)
667
+ if overrule:
668
+ animate = False
669
+
670
+ if height is None:
671
+ raise CommandSyntaxError("x, y, width, height not specified")
672
+ try:
673
+ x = Length(
674
+ x,
675
+ relative_length=self.context.device.view.width,
676
+ unitless=UNITS_PER_PIXEL,
677
+ )
678
+ y = Length(
679
+ y,
680
+ relative_length=self.context.device.view.height,
681
+ unitless=UNITS_PER_PIXEL,
682
+ )
683
+ width = Length(
684
+ width,
685
+ relative_length=self.context.device.view.width,
686
+ unitless=UNITS_PER_PIXEL,
687
+ )
688
+ height = Length(
689
+ height,
690
+ relative_length=self.context.device.view.height,
691
+ unitless=UNITS_PER_PIXEL,
692
+ )
693
+ except ValueError:
694
+ raise CommandSyntaxError("Not a valid length.")
695
+ bbox = (x, y, width, height)
696
+ matrix = data.widget_root.scene_widget.matrix
697
+ data.widget_root.focus_viewport_scene(bbox, self.Size, animate=animate)
698
+ data.request_refresh()
699
+ channel(str(matrix))
700
+ return "scene", data
701
+
702
+ @context.console_command("feature_request")
703
+ def send_developer_feature(remainder="", **kwgs):
704
+ from .wxmeerk40t import send_data_to_developers
705
+
706
+ send_data_to_developers("feature_request.txt", remainder)
707
+
708
+ @context.console_command("bug")
709
+ def send_developer_bug(remainder="", **kwgs):
710
+ from .wxmeerk40t import send_data_to_developers
711
+
712
+ send_data_to_developers("bug.txt", remainder)
713
+
714
+ @context.console_command("reference")
715
+ def make_reference(**kwgs):
716
+ # Take first emphasized element
717
+ for e in self.context.elements.flat(types=elem_nodes, emphasized=True):
718
+ self.reference_object = e
719
+ break
720
+ self.context.signal("reference")
721
+
722
+ # Establishes commands
723
+ @context.console_argument(
724
+ "target", type=str, help=_("Target (one of primary, secondary, circular")
725
+ )
726
+ @context.console_argument("ox", type=str, help=_("X-Position of origin"))
727
+ @context.console_argument("oy", type=str, help=_("Y-Position of origin"))
728
+ @context.console_argument(
729
+ "scalex", type=str, help=_("Scaling of X-Axis for secondary")
730
+ )
731
+ @context.console_argument(
732
+ "scaley", type=str, help=_("Scaling of Y-Axis for secondary")
733
+ )
734
+ @context.console_command(
735
+ "grid",
736
+ help=_("grid <target> <rows> <x_distance> <y_distance> <origin>"),
737
+ input_type="scene",
738
+ )
739
+ def show_grid(
740
+ command,
741
+ channel,
742
+ _,
743
+ target=None,
744
+ ox=None,
745
+ oy=None,
746
+ scalex=None,
747
+ scaley=None,
748
+ **kwgs,
749
+ ):
750
+ if target is None:
751
+ channel(_("Grid-Parameters:"))
752
+ p_state = _("On") if self.grid.draw_grid_primary else _("Off")
753
+ channel(f"Primary: {p_state}")
754
+ if self.grid.draw_grid_secondary:
755
+ channel(f"Secondary: {_('On')}")
756
+ if self.grid.grid_secondary_cx is not None:
757
+ channel(
758
+ f" cx: {Length(amount=self.grid.grid_secondary_cx).length_mm}"
759
+ )
760
+ if self.grid.grid_secondary_cy is not None:
761
+ channel(
762
+ f" cy: {Length(amount=self.grid.grid_secondary_cy).length_mm}"
763
+ )
764
+ if self.grid.grid_secondary_scale_x is not None:
765
+ channel(f" scale-x: {self.grid.grid_secondary_scale_x:.2f}")
766
+ if self.grid.grid_secondary_scale_y is not None:
767
+ channel(f" scale-y: {self.grid.grid_secondary_scale_y:.2f}")
768
+ else:
769
+ channel(f"Secondary: {_('Off')}")
770
+ if self.grid.draw_grid_circular:
771
+ channel(f"Circular: {_('On')}")
772
+ if self.grid.grid_circular_cx is not None:
773
+ channel(
774
+ f" cx: {Length(amount=self.grid.grid_circular_cx).length_mm}"
775
+ )
776
+ if self.grid.grid_circular_cy is not None:
777
+ channel(
778
+ f" cy: {Length(amount=self.grid.grid_circular_cy).length_mm}"
779
+ )
780
+ else:
781
+ channel(f"Circular: {_('Off')}")
782
+ return
783
+ else:
784
+ target = target.lower()
785
+ if target[0] == "p":
786
+ self.grid.draw_grid_primary = not self.grid.draw_grid_primary
787
+ channel(
788
+ _("Turned primary grid on")
789
+ if self.grid.draw_grid_primary
790
+ else _("Turned primary grid off")
791
+ )
792
+ self.scene.signal("guide")
793
+ self.scene.signal("grid")
794
+ self.widget_scene.reset_snap_attraction()
795
+ self.request_refresh()
796
+ elif target[0] == "s":
797
+ self.grid.draw_grid_secondary = not self.grid.draw_grid_secondary
798
+ if self.grid.draw_grid_secondary:
799
+ if ox is None:
800
+ self.grid.grid_secondary_cx = None
801
+ self.grid.grid_secondary_cy = None
802
+ scalex = None
803
+ scaley = None
804
+ else:
805
+ if oy is None:
806
+ oy = ox
807
+ self.grid.grid_secondary_cx = float(
808
+ Length(
809
+ ox, relative_length=self.context.device.view.width
810
+ )
811
+ )
812
+ self.grid.grid_secondary_cy = float(
813
+ Length(
814
+ oy, relative_length=self.context.device.view.height
815
+ )
816
+ )
817
+ if scalex is None:
818
+ rot = self.scene.context.device.rotary
819
+ if rot.active:
820
+ scalex = rot.scale_x
821
+ scaley = rot.scale_y
822
+ else:
823
+ scalex = 1.0
824
+ scaley = 1.0
825
+ else:
826
+ scalex = float(scalex)
827
+ if scaley is None:
828
+ scaley = scalex
829
+ else:
830
+ scaley = float(scaley)
831
+ self.grid.grid_secondary_scale_x = scalex
832
+ self.grid.grid_secondary_scale_y = scaley
833
+ channel(
834
+ _(
835
+ "Turned secondary grid on"
836
+ if self.grid.draw_grid_secondary
837
+ else "Turned secondary grid off"
838
+ )
839
+ )
840
+ self.scene.signal("guide")
841
+ self.scene.signal("grid")
842
+ self.request_refresh()
843
+ elif target[0] == "c":
844
+ self.grid.draw_grid_circular = not self.grid.draw_grid_circular
845
+ if self.grid.draw_grid_circular:
846
+ if ox is None:
847
+ self.grid.grid_circular_cx = None
848
+ self.grid.grid_circular_cy = None
849
+ else:
850
+ if oy is None:
851
+ oy = ox
852
+ self.grid.grid_circular_cx = float(
853
+ Length(
854
+ ox, relative_length=self.context.device.view.width
855
+ )
856
+ )
857
+ self.grid.grid_circular_cy = float(
858
+ Length(
859
+ oy, relative_length=self.context.device.view.height
860
+ )
861
+ )
862
+ channel(
863
+ _(
864
+ "Turned circular grid on"
865
+ if self.grid.draw_grid_circular
866
+ else "Turned circular grid off"
867
+ )
868
+ )
869
+ self.scene.signal("guide")
870
+ self.scene.signal("grid")
871
+ self.request_refresh()
872
+ else:
873
+ channel(_("Target needs to be one of primary, secondary, circular"))
874
+
875
+ def toggle_ref_obj(self):
876
+ for e in self.scene.context.elements.flat(types=elem_nodes, emphasized=True):
877
+ if self.reference_object == e:
878
+ self.reference_object = None
879
+ else:
880
+ self.reference_object = e
881
+ break
882
+ self.context.signal("reference")
883
+ self.request_refresh()
884
+
885
+ def validate_reference(self):
886
+ """
887
+ Check whether the reference is still valid
888
+ """
889
+ found = False
890
+ if self._reference:
891
+ for e in self.context.elements.flat(types=elem_nodes):
892
+ # Here we ignore the lock-status of an element
893
+ if e is self._reference:
894
+ found = True
895
+ break
896
+ if not found:
897
+ self._reference = None
898
+
899
+ @property
900
+ def reference_object(self):
901
+ return self._reference
902
+
903
+ @reference_object.setter
904
+ def reference_object(self, ref_object):
905
+ prev = self._reference
906
+ self._reference = ref_object
907
+ self.scene.reference_object = self._reference
908
+ dlist = []
909
+ if prev is not None:
910
+ dlist.append(prev)
911
+ if self._reference is not None:
912
+ dlist.append(self._reference)
913
+ if len(dlist) > 0:
914
+ self.context.signal("element_property_update", dlist)
915
+
916
+ ##########
917
+ # MAGNETS
918
+ ##########
919
+
920
+ @property
921
+ def magnet_attraction(self):
922
+ return self._magnet_attraction
923
+
924
+ @magnet_attraction.setter
925
+ def magnet_attraction(self, value):
926
+ if 0 <= value <= 5:
927
+ self._magnet_attraction = value
928
+ self.save_magnets()
929
+
930
+ def save_magnets(self):
931
+ try:
932
+ with open(self._magnet_file, "w") as f:
933
+ f.write(f"a={self.magnet_attraction}\n")
934
+ for x in self.magnet_x:
935
+ f.write(f"x={Length(x, preferred_units='mm').preferred_length}\n")
936
+ for y in self.magnet_y:
937
+ f.write(f"y={Length(y, preferred_units='mm').preferred_length}\n")
938
+ except (ValueError, PermissionError, OSError, FileNotFoundError):
939
+ return
940
+
941
+ def load_magnets(self):
942
+ self.magnet_x = []
943
+ self.magnet_y = []
944
+ try:
945
+ with open(self._magnet_file, "r") as f:
946
+ for line in f:
947
+ cline = line.strip()
948
+ if cline != "":
949
+ subs = cline.split("=")
950
+ if len(subs) > 1:
951
+ try:
952
+ if subs[0] in ("a", "A"):
953
+ # Attraction strength
954
+ value = int(subs[1])
955
+ if value < 0:
956
+ value = 0
957
+ if value > 5:
958
+ value = 5
959
+ self._magnet_attraction = value
960
+ elif subs[0] in ("x", "X"):
961
+ dimens = Length(subs[1])
962
+ value = float(dimens)
963
+ if value not in self.magnet_x:
964
+ self.magnet_x.append(value)
965
+ elif subs[0] in ("y", "Y"):
966
+ dimens = Length(subs[1])
967
+ value = float(dimens)
968
+ if value not in self.magnet_y:
969
+ self.magnet_y.append(value)
970
+ except ValueError:
971
+ pass
972
+ except (PermissionError, OSError, FileNotFoundError):
973
+ return
974
+
975
+ def clear_magnets(self):
976
+ self.magnet_x = []
977
+ self.magnet_y = []
978
+ self.save_magnets()
979
+
980
+ def clear_magnets_conditionally(self):
981
+ # Depending on setting
982
+ if self.context.clear_magnets:
983
+ self.clear_magnets()
984
+
985
+ def toggle_x_magnet(self, x_value):
986
+ if x_value in self.magnet_x:
987
+ self.magnet_x.remove(x_value)
988
+ else:
989
+ self.magnet_x += [x_value]
990
+
991
+ def toggle_y_magnet(self, y_value):
992
+ if y_value in self.magnet_y:
993
+ self.magnet_y.remove(y_value)
994
+ else:
995
+ self.magnet_y += [y_value]
996
+
997
+ def magnet_attracted_x(self, x_value, useit):
998
+ delta = float("inf")
999
+ x_val = None
1000
+ if useit:
1001
+ for mag_x in self.magnet_x:
1002
+ if abs(x_value - mag_x) < delta:
1003
+ delta = abs(x_value - mag_x)
1004
+ x_val = mag_x
1005
+ return delta, x_val
1006
+
1007
+ def magnet_attracted_y(self, y_value, useit):
1008
+ delta = float("inf")
1009
+ y_val = None
1010
+ if useit:
1011
+ for mag_y in self.magnet_y:
1012
+ if abs(y_value - mag_y) < delta:
1013
+ delta = abs(y_value - mag_y)
1014
+ y_val = mag_y
1015
+ return delta, y_val
1016
+
1017
+ def revised_magnet_bound(self, bounds=None):
1018
+ dx = 0
1019
+ dy = 0
1020
+ if self.has_magnets() and self._magnet_attraction > 0:
1021
+ if self.grid.tick_distance > 0:
1022
+ s = f"{self.grid.tick_distance}{self.context.units_name}"
1023
+ len_tick = float(Length(s))
1024
+ # Attraction length is 1/3, 4/3, 9/3 of a grid-unit
1025
+ # fmt: off
1026
+ attraction_len = 1 / 3 * self._magnet_attraction * self._magnet_attraction * len_tick
1027
+
1028
+ # print("Attraction len=%s, attract=%d, alen=%.1f, tlen=%.1f, factor=%.1f" % (s, self._magnet_attraction, attraction_len, len_tick, attraction_len / len_tick ))
1029
+ # fmt: on
1030
+ else:
1031
+ attraction_len = float(Length("1mm"))
1032
+
1033
+ delta_x1, x1 = self.magnet_attracted_x(bounds[0], self.magnet_attract_x)
1034
+ delta_x2, x2 = self.magnet_attracted_x(bounds[2], self.magnet_attract_x)
1035
+ delta_x3, x3 = self.magnet_attracted_x(
1036
+ (bounds[0] + bounds[2]) / 2, self.magnet_attract_c
1037
+ )
1038
+ delta_y1, y1 = self.magnet_attracted_y(bounds[1], self.magnet_attract_y)
1039
+ delta_y2, y2 = self.magnet_attracted_y(bounds[3], self.magnet_attract_y)
1040
+ delta_y3, y3 = self.magnet_attracted_y(
1041
+ (bounds[1] + bounds[3]) / 2, self.magnet_attract_c
1042
+ )
1043
+ if delta_x3 < delta_x1 and delta_x3 < delta_x2:
1044
+ if delta_x3 < attraction_len:
1045
+ if x3 is not None:
1046
+ dx = x3 - (bounds[0] + bounds[2]) / 2
1047
+ # print("X Take center , x=%.1f, dx=%.1f" % ((bounds[0] + bounds[2]) / 2, dx)
1048
+ elif delta_x1 < delta_x2 and delta_x1 < delta_x3:
1049
+ if delta_x1 < attraction_len:
1050
+ if x1 is not None:
1051
+ dx = x1 - bounds[0]
1052
+ # print("X Take left side, x=%.1f, dx=%.1f" % (bounds[0], dx))
1053
+ elif delta_x2 < delta_x1 and delta_x2 < delta_x3:
1054
+ if delta_x2 < attraction_len:
1055
+ if x2 is not None:
1056
+ dx = x2 - bounds[2]
1057
+ # print("X Take right side, x=%.1f, dx=%.1f" % (bounds[2], dx))
1058
+ if delta_y3 < delta_y1 and delta_y3 < delta_y2:
1059
+ if delta_y3 < attraction_len:
1060
+ if y3 is not None:
1061
+ dy = y3 - (bounds[1] + bounds[3]) / 2
1062
+ # print("Y Take center , x=%.1f, dx=%.1f" % ((bounds[1] + bounds[3]) / 2, dy))
1063
+ elif delta_y1 < delta_y2 and delta_y1 < delta_y3:
1064
+ if delta_y1 < attraction_len:
1065
+ if y1 is not None:
1066
+ dy = y1 - bounds[1]
1067
+ # print("Y Take top side, y=%.1f, dy=%.1f" % (bounds[1], dy))
1068
+ elif delta_y2 < delta_y1 and delta_y2 < delta_y3:
1069
+ if delta_y2 < attraction_len:
1070
+ if y2 is not None:
1071
+ dy = y2 - bounds[3]
1072
+ # print("Y Take bottom side, y=%.1f, dy=%.1f" % (bounds[3], dy))
1073
+
1074
+ return dx, dy
1075
+
1076
+ def has_magnets(self):
1077
+ return len(self.magnet_x) + len(self.magnet_y) > 0
1078
+
1079
+ ##############
1080
+ # SNAPS
1081
+ ##############
1082
+
1083
+ @property
1084
+ def last_snap(self):
1085
+ result = self._last_snap_position
1086
+ # Too old? Discard
1087
+ if (time.time() - self._last_snap_ts) > 0.5:
1088
+ result = None
1089
+ return result
1090
+
1091
+ @last_snap.setter
1092
+ def last_snap(self, value):
1093
+ self._last_snap_position = value
1094
+ if value is None:
1095
+ self._last_snap_ts = 0
1096
+ else:
1097
+ self._last_snap_ts = time.time()
1098
+
1099
+ @signal_listener("make_reference")
1100
+ def listen_make_ref(self, origin, *args):
1101
+ node = args[0]
1102
+ self.reference_object = node
1103
+ self.context.signal("reference")
1104
+
1105
+ @signal_listener("create_magnets")
1106
+ def listen_magnet_creation(self, origin, creation_list, *args):
1107
+ for (info, value) in creation_list:
1108
+ if info == "x":
1109
+ self.toggle_x_magnet(value)
1110
+ else:
1111
+ self.toggle_y_magnet(value)
1112
+ self.save_magnets()
1113
+ self.request_refresh()
1114
+
1115
+ @signal_listener("draw_mode")
1116
+ def on_draw_mode(self, origin, *args):
1117
+ if self._tool_widget is not None:
1118
+ orgx = 5
1119
+ orgy = 5
1120
+ # Are guides drawn?
1121
+ if self.context.draw_mode & DRAW_MODE_GUIDES == 0:
1122
+ orgx += 25
1123
+ orgy += 25
1124
+ self._tool_widget.set_position(orgx, orgy)
1125
+
1126
+ @signal_listener("scene_right_click")
1127
+ def on_scene_right(self, origin, *args):
1128
+ def zoom_in(event=None):
1129
+ self.context(f"scene zoom {1.5 / 1.0}\n")
1130
+
1131
+ def zoom_out(event=None):
1132
+ self.context(f"scene zoom {1.0 / 1.5}\n")
1133
+
1134
+ def zoom_to_bed(event=None):
1135
+ zoom = self.context.zoom_margin
1136
+ self.context(f"scene focus -a {-zoom}% {-zoom}% {zoom+100}% {zoom+100}%\n")
1137
+
1138
+ def zoom_to_selected(event=None):
1139
+ bbox = self.context.elements.selected_area()
1140
+ if bbox is None:
1141
+ zoom_to_bed(event=event)
1142
+ else:
1143
+ zfact = self.context.zoom_margin / 100.0
1144
+
1145
+ x_delta = (bbox[2] - bbox[0]) * zfact
1146
+ y_delta = (bbox[3] - bbox[1]) * zfact
1147
+ x0 = Length(
1148
+ amount=bbox[0] - x_delta,
1149
+ relative_length=self.context.device.view.width,
1150
+ ).length_mm
1151
+ y0 = Length(
1152
+ amount=bbox[1] - y_delta,
1153
+ relative_length=self.context.device.view.height,
1154
+ ).length_mm
1155
+ x1 = Length(
1156
+ amount=bbox[2] + x_delta,
1157
+ relative_length=self.context.device.view.width,
1158
+ ).length_mm
1159
+ y1 = Length(
1160
+ amount=bbox[3] + y_delta,
1161
+ relative_length=self.context.device.view.height,
1162
+ ).length_mm
1163
+ self.context(f"scene focus -a {x0} {y0} {x1} {y1}\n")
1164
+
1165
+ def toggle_background(event=None):
1166
+ """
1167
+ Toggle the draw mode for the background
1168
+ """
1169
+ self.widget_scene.context.draw_mode ^= DRAW_MODE_BACKGROUND
1170
+ self.widget_scene.request_refresh()
1171
+
1172
+ def toggle_grid(gridtype):
1173
+ if gridtype == "primary":
1174
+ self.grid.draw_grid_primary = not self.grid.draw_grid_primary
1175
+ elif gridtype == "secondary":
1176
+ self.grid.draw_grid_secondary = not self.grid.draw_grid_secondary
1177
+ elif gridtype == "circular":
1178
+ self.grid.draw_grid_circular = not self.grid.draw_grid_circular
1179
+ elif gridtype == "offset":
1180
+ self.grid.draw_offset_lines = not self.grid.draw_offset_lines
1181
+ self.scene.signal("guide")
1182
+ self.scene.signal("grid")
1183
+ self.widget_scene.reset_snap_attraction()
1184
+ self.request_refresh()
1185
+
1186
+ def toggle_grid_p(event=None):
1187
+ toggle_grid("primary")
1188
+
1189
+ def toggle_grid_s(event=None):
1190
+ toggle_grid("secondary")
1191
+
1192
+ def toggle_grid_c(event=None):
1193
+ toggle_grid("circular")
1194
+
1195
+ def toggle_grid_o(event=None):
1196
+ toggle_grid("offset")
1197
+
1198
+ def remove_background(event=None):
1199
+ self.widget_scene._signal_widget(
1200
+ self.widget_scene.widget_root, "background", None
1201
+ )
1202
+ self.widget_scene.request_refresh()
1203
+
1204
+ def recognize_background_contours(event=None):
1205
+ def image_from_bitmap(myBitmap):
1206
+ wx_image = myBitmap.ConvertToImage()
1207
+ myPilImage = Image.new(
1208
+ "RGB", (wx_image.GetWidth(), wx_image.GetHeight())
1209
+ )
1210
+ myPilImage.frombytes(wx_image.GetData())
1211
+ return myPilImage
1212
+
1213
+ if not self.widget_scene.has_background:
1214
+ return
1215
+ # We build a dummy imageNode, so we fetch the background,
1216
+ # calculate the required transformation matrix and pass
1217
+ # it on to one of the standard image node property dialogs
1218
+
1219
+ background = self.widget_scene.active_background
1220
+ if background is None:
1221
+ return
1222
+ background_image = image_from_bitmap(background)
1223
+
1224
+ # Calculate scaling matrix
1225
+ sx = float(Length(self.context.device.view.width)) / background_image.width
1226
+ sy = (
1227
+ float(Length(self.context.device.view.height)) / background_image.height
1228
+ )
1229
+ matrix = Matrix(f"scale({sx},{sy})")
1230
+ # print (f"Image dimension: {background_image.width} x {background_image.height} pixel")
1231
+ # print (f"View-Size: {float(Length(self.context.device.view.width))} x {float(Length(self.context.device.view.height))}")
1232
+ # print (f"Matrix: {matrix}")
1233
+
1234
+ node = ImageNode(
1235
+ image=background_image,
1236
+ matrix=matrix,
1237
+ dither=False,
1238
+ prevent_crop=True,
1239
+ dpi=500,
1240
+ )
1241
+ # print (f"Node-Dimensions: {node.bbox()}")
1242
+ dlg = ContourDetectionDialog(self, self.context, node)
1243
+ dlg.ShowModal()
1244
+ dlg.end_dialog()
1245
+ dlg.Destroy()
1246
+
1247
+ def stop_auto_update(event=None):
1248
+ devlabel = self.context.device.label
1249
+ to_stop = []
1250
+ for job, content in self.context.kernel.jobs.items():
1251
+ if job is not None and job.startswith("timer.updatebg"):
1252
+ cmd = str(content).strip()
1253
+ if cmd.endswith("background") or cmd.endswith(devlabel):
1254
+ to_stop.append(job)
1255
+ for job in to_stop:
1256
+ self.context(f"{job} --off\n")
1257
+
1258
+ gui = self
1259
+ menu = wx.Menu()
1260
+ id1 = menu.Append(
1261
+ wx.ID_ANY,
1262
+ _("Show Background"),
1263
+ _("Display the background picture in the scene"),
1264
+ wx.ITEM_CHECK,
1265
+ )
1266
+ self.Bind(wx.EVT_MENU, toggle_background, id=id1.GetId())
1267
+ menu.Check(
1268
+ id1.GetId(),
1269
+ (self.widget_scene.context.draw_mode & DRAW_MODE_BACKGROUND == 0),
1270
+ )
1271
+ id2 = menu.Append(
1272
+ wx.ID_ANY,
1273
+ _("Show Primary Grid"),
1274
+ _("Display the primary grid in the scene"),
1275
+ wx.ITEM_CHECK,
1276
+ )
1277
+ self.Bind(wx.EVT_MENU, toggle_grid_p, id=id2.GetId())
1278
+ menu.Check(id2.GetId(), self.grid.draw_grid_primary)
1279
+ id3 = menu.Append(
1280
+ wx.ID_ANY,
1281
+ _("Show Secondary Grid"),
1282
+ _("Display the secondary grid in the scene"),
1283
+ wx.ITEM_CHECK,
1284
+ )
1285
+ self.Bind(wx.EVT_MENU, toggle_grid_s, id=id3.GetId())
1286
+ menu.Check(id3.GetId(), self.grid.draw_grid_secondary)
1287
+ id4 = menu.Append(
1288
+ wx.ID_ANY,
1289
+ _("Show Circular Grid"),
1290
+ _("Display the circular grid in the scene"),
1291
+ wx.ITEM_CHECK,
1292
+ )
1293
+ self.Bind(wx.EVT_MENU, toggle_grid_c, id=id4.GetId())
1294
+ menu.Check(id4.GetId(), self.grid.draw_grid_circular)
1295
+ try:
1296
+ mx = float(Length(self.context.device.view.margin_x))
1297
+ my = float(Length(self.context.device.view.margin_y))
1298
+ except ValueError:
1299
+ mx = 0
1300
+ my = 0
1301
+ # print(self.context.device.view.margin_x, self.context.device.view.margin_y)
1302
+ if mx != 0.0 or my != 0.0:
1303
+ menu.AppendSeparator()
1304
+ id4b = menu.Append(
1305
+ wx.ID_ANY,
1306
+ _("Show physical dimensions"),
1307
+ _("Display the physical dimensions"),
1308
+ wx.ITEM_CHECK,
1309
+ )
1310
+ self.Bind(wx.EVT_MENU, toggle_grid_o, id=id4b.GetId())
1311
+ menu.Check(id4b.GetId(), self.grid.draw_offset_lines)
1312
+
1313
+ if self.widget_scene.has_background:
1314
+ menu.AppendSeparator()
1315
+ id5 = menu.Append(wx.ID_ANY, _("Remove Background"), "")
1316
+ self.Bind(wx.EVT_MENU, remove_background, id=id5.GetId())
1317
+
1318
+ id6 = menu.Append(wx.ID_ANY, _("Detect contours on background"), "")
1319
+ self.Bind(wx.EVT_MENU, recognize_background_contours, id=id6.GetId())
1320
+ # Do we have a timer called .updatebg?
1321
+ devlabel = self.context.device.label
1322
+ we_have_a_job = False
1323
+ for job, content in self.context.kernel.jobs.items():
1324
+ if job is not None and job.startswith("timer.updatebg"):
1325
+ cmd = str(content).strip()
1326
+ if cmd.endswith("background") or cmd.endswith(devlabel):
1327
+ we_have_a_job = True
1328
+ break
1329
+
1330
+ if we_have_a_job:
1331
+ self.Bind(
1332
+ wx.EVT_MENU,
1333
+ lambda e: stop_auto_update(),
1334
+ menu.Append(
1335
+ wx.ID_ANY,
1336
+ _("Stop autoupdate"),
1337
+ _("Stop automatic refresh of background image"),
1338
+ ),
1339
+ )
1340
+ menu.AppendSeparator()
1341
+ self.Bind(
1342
+ wx.EVT_MENU,
1343
+ lambda e: zoom_out(),
1344
+ menu.Append(
1345
+ wx.ID_ANY,
1346
+ _("Zoom Out"),
1347
+ _("Make the scene smaller"),
1348
+ ),
1349
+ )
1350
+ self.Bind(
1351
+ wx.EVT_MENU,
1352
+ lambda e: zoom_in(),
1353
+ menu.Append(
1354
+ wx.ID_ANY,
1355
+ _("Zoom In"),
1356
+ _("Make the scene larger"),
1357
+ ),
1358
+ )
1359
+ self.Bind(
1360
+ wx.EVT_MENU,
1361
+ lambda e: zoom_to_bed(),
1362
+ menu.Append(
1363
+ wx.ID_ANY,
1364
+ _("&Zoom to Bed"),
1365
+ _("View the whole laser bed"),
1366
+ ),
1367
+ )
1368
+ if self.context.elements.has_emphasis():
1369
+ self.Bind(
1370
+ wx.EVT_MENU,
1371
+ lambda e: zoom_to_selected(),
1372
+ menu.Append(
1373
+ wx.ID_ANY,
1374
+ _("Zoom to &Selected"),
1375
+ _("Fill the scene area with the selected elements"),
1376
+ ),
1377
+ )
1378
+
1379
+ if menu.MenuItemCount != 0:
1380
+ gui.PopupMenu(menu)
1381
+ menu.Destroy()
1382
+
1383
+ @signal_listener("refresh_scene")
1384
+ def on_refresh_scene(self, origin, scene_name=None, *args):
1385
+ """
1386
+ Called by 'refresh_scene' change. To refresh tree.
1387
+
1388
+ @param origin: the path of the originating signal
1389
+ @param scene_name: Scene to refresh on if matching
1390
+ @param args:
1391
+ @return:
1392
+ """
1393
+ if scene_name == "Scene":
1394
+ self.request_refresh()
1395
+
1396
+ @signal_listener("coolant_changed")
1397
+ def on_coolant_changed(self, origin, *args):
1398
+ if hasattr(self.context.device, "coolant"):
1399
+ coolid = self.context.device.device_coolant
1400
+ if coolid == "":
1401
+ coolid = None
1402
+ cool = self.context.kernel.root.coolant
1403
+ cool.claim_coolant(self.context.device, coolid)
1404
+
1405
+ @signal_listener("view;realized")
1406
+ def on_bedsize_simple(self, origin=None, nocmd=None, *args):
1407
+ self.scene.signal("guide")
1408
+ self.scene.signal("grid")
1409
+ self.request_refresh(origin)
1410
+
1411
+ @signal_listener("magnet-attraction")
1412
+ def on_magnet_attract(self, origin, strength, *args):
1413
+ strength = int(strength)
1414
+ if strength < 0:
1415
+ strength = 0
1416
+ self.magnet_attraction = strength
1417
+
1418
+ @signal_listener("magnet_gen")
1419
+ def on_magnet_generate(self, origin, *args):
1420
+ candidate = args[0]
1421
+ if candidate is None:
1422
+ return
1423
+ if not isinstance(candidate, (tuple, list)) or len(candidate) < 2:
1424
+ return
1425
+ method = candidate[0]
1426
+ node = candidate[1]
1427
+ bb = node.bounds
1428
+ if method == "outer":
1429
+ self.toggle_x_magnet(bb[0])
1430
+ self.toggle_x_magnet(bb[2])
1431
+ self.toggle_y_magnet(bb[1])
1432
+ self.toggle_y_magnet(bb[3])
1433
+ elif method == "center":
1434
+ self.toggle_x_magnet((bb[0] + bb[2]) / 2)
1435
+ self.toggle_y_magnet((bb[1] + bb[3]) / 2)
1436
+ self.save_magnets()
1437
+ self.request_refresh()
1438
+
1439
+ def pane_show(self, *args):
1440
+ zl = self.context.zoom_margin
1441
+ self.context(f"scene focus -{zl}% -{zl}% {100 + zl}% {100 + zl}%\n")
1442
+
1443
+ def pane_hide(self, *args):
1444
+ pass
1445
+
1446
+ @signal_listener("activate;device")
1447
+ def on_activate_device(self, origin, device):
1448
+ self.scene.signal("grid")
1449
+ self.request_refresh()
1450
+
1451
+ def on_size(self, event):
1452
+ if self.context is None:
1453
+ return
1454
+ self.Layout()
1455
+ # Refresh not needed as scenepanel already does it...
1456
+ # self.scene.signal("guide")
1457
+ # self.request_refresh()
1458
+
1459
+ def on_close(self, event):
1460
+ self.save_magnets()
1461
+
1462
+ @signal_listener("pause")
1463
+ @signal_listener("pipe;running")
1464
+ def on_driver_mode(self, origin, *args):
1465
+ # pipe running has (state) as args
1466
+ new_color = None
1467
+ try:
1468
+ if self.context.device.driver.paused:
1469
+ new_color = self.context.themes.get("pause_bg")
1470
+ elif self.context.device.laser_status == "active":
1471
+ new_color = self.context.themes.get("stop_bg")
1472
+ except AttributeError:
1473
+ pass
1474
+ self.widget_scene.overrule_background = new_color
1475
+ self.widget_scene.request_refresh_for_animation()
1476
+
1477
+ @signal_listener("background")
1478
+ def on_background_signal(self, origin, background):
1479
+ background = wx.Bitmap.FromBuffer(*background)
1480
+ self.scene.signal("background", background)
1481
+ self.request_refresh()
1482
+
1483
+ @signal_listener("units")
1484
+ def space_changed(self, origin, *args):
1485
+ self.scene.signal("guide")
1486
+ self.scene.signal("grid")
1487
+ self.request_refresh(origin)
1488
+
1489
+ @signal_listener("bed_size")
1490
+ def bed_changed(self, origin, *args):
1491
+ self.scene.signal("grid")
1492
+ # self.scene.signal('guide')
1493
+ self.request_refresh(origin)
1494
+
1495
+ @signal_listener("modified_by_tool")
1496
+ def on_modification_by_tool(self, origin, *args):
1497
+ self.context.elements.process_keyhole_updates(self.context)
1498
+ self.scene.signal("modified_by_tool")
1499
+
1500
+ @signal_listener("tabs_updated")
1501
+ def on_tabs_update(self, origin, *args):
1502
+ # Pass on to scene widgets
1503
+ self.scene.signal("tabs_updated")
1504
+
1505
+ @signal_listener("emphasized")
1506
+ def on_emphasized_elements_changed(self, origin, *args):
1507
+ self.scene.context.elements.set_start_time("Emphasis wxmscene")
1508
+ self.scene.signal("emphasized")
1509
+ self.laserpath_widget.clear_laserpath()
1510
+ self.request_refresh(origin)
1511
+ self.scene.context.elements.set_end_time("Emphasis wxmscene")
1512
+
1513
+ def request_refresh(self, *args):
1514
+ self.widget_scene.request_refresh(*args)
1515
+
1516
+ @signal_listener("altered")
1517
+ @signal_listener("modified")
1518
+ def on_element_modified(self, *args):
1519
+ self.scene.signal("modified")
1520
+ self.widget_scene.request_refresh(*args)
1521
+
1522
+ @signal_listener("linetext")
1523
+ def on_signal_linetext(self, origin, *args):
1524
+ if len(args) == 1:
1525
+ self.scene.signal("linetext", args[0])
1526
+ elif len(args) > 1:
1527
+ self.scene.signal("linetext", args[0], args[1])
1528
+
1529
+ @signal_listener("nodeedit")
1530
+ def on_signal_nodeedit(self, origin, *args):
1531
+ if len(args) == 1:
1532
+ self.scene.signal("nodeedit", args[0])
1533
+ elif len(args) > 1:
1534
+ self.scene.signal("nodeedit", args[0], args[1])
1535
+
1536
+ @signal_listener("element_added")
1537
+ @signal_listener("tree_changed")
1538
+ def on_elements_added(self, origin, nodes=None, *args):
1539
+ self.scene.signal("element_added", nodes)
1540
+ # There may be a smarter way to eliminate unnecessary rebuilds, but it's doing the job...
1541
+ # self.context.signal("rebuild_tree")
1542
+ self.context.signal("refresh_tree", nodes)
1543
+ self.widget_scene.request_refresh()
1544
+
1545
+ @signal_listener("rebuild_tree")
1546
+ def on_rebuild_tree(self, origin, *args):
1547
+ self.widget_scene._signal_widget(
1548
+ self.widget_scene.widget_root, "rebuild_tree", None
1549
+ )
1550
+
1551
+ @signal_listener("theme")
1552
+ def on_theme_change(self, origin, theme=None):
1553
+ self.scene.signal("theme", theme)
1554
+ self.request_refresh(origin)
1555
+
1556
+ @signal_listener("selstroke")
1557
+ def on_selstroke(self, origin, rgb, *args):
1558
+ # print (origin, rgb, args)
1559
+ if rgb[0] == 255 and rgb[1] == 255 and rgb[2] == 255:
1560
+ color = None
1561
+ else:
1562
+ color = Color(rgb[0], rgb[1], rgb[2])
1563
+ self.widget_scene.context.elements.default_stroke = color
1564
+
1565
+ @signal_listener("selfill")
1566
+ def on_selfill(self, origin, rgb, *args):
1567
+ # print (origin, rgb, args)
1568
+ if rgb[0] == 255 and rgb[1] == 255 and rgb[2] == 255:
1569
+ color = None
1570
+ else:
1571
+ color = Color(rgb[0], rgb[1], rgb[2])
1572
+ self.widget_scene.context.elements.default_fill = color
1573
+
1574
+ @signal_listener("scene_deactivated")
1575
+ def on_scene_deactived(self, origin, *args):
1576
+ if not self.context.setting(bool, "auto_tool_reset", True):
1577
+ return
1578
+ if self.active_tool != "none":
1579
+ self.context(".tool none\n")
1580
+
1581
+ def on_key_down(self, event):
1582
+ keyvalue = get_key_name(event)
1583
+ ignore = self.tool_active
1584
+ if self._keybind_channel:
1585
+ self._keybind_channel(f"Scene key_down: {keyvalue}.")
1586
+ if not ignore and self.context.bind.trigger(keyvalue):
1587
+ if self._keybind_channel:
1588
+ self._keybind_channel(f"Scene key_down: {keyvalue} executed.")
1589
+ else:
1590
+ if self._keybind_channel:
1591
+ if ignore:
1592
+ self._keybind_channel(
1593
+ f"Scene key_down: {keyvalue} was ignored as tool active."
1594
+ )
1595
+ else:
1596
+ self._keybind_channel(f"Scene key_down: {keyvalue} unfound.")
1597
+ event.Skip()
1598
+
1599
+ def on_key_up(self, event, log=True):
1600
+ keyvalue = get_key_name(event)
1601
+ ignore = self.tool_active
1602
+ if self._keybind_channel:
1603
+ self._keybind_channel(f"Scene key_up: {keyvalue}.")
1604
+ if not ignore and self.context.bind.untrigger(keyvalue):
1605
+ if self._keybind_channel:
1606
+ self._keybind_channel(f"Scene key_up: {keyvalue} executed.")
1607
+ else:
1608
+ if self._keybind_channel:
1609
+ if ignore:
1610
+ self._keybind_channel(
1611
+ f"Scene key_up: {keyvalue} was ignored as tool active."
1612
+ )
1613
+ else:
1614
+ self._keybind_channel(f"Scene key_up: {keyvalue} unfound.")
1615
+ event.Skip()
1616
+
1617
+
1618
+ class SceneWindow(MWindow):
1619
+ def __init__(self, *args, **kwds):
1620
+ super().__init__(1280, 800, *args, **kwds)
1621
+ self.panel = MeerK40tScenePanel(self, wx.ID_ANY, context=self.context)
1622
+ self.sizer.Add(self.panel, 1, wx.EXPAND, 0)
1623
+ self.add_module_delegate(self.panel)
1624
+ _icon = wx.NullIcon
1625
+ _icon.CopyFromBitmap(icon_meerk40t.GetBitmap())
1626
+ self.SetIcon(_icon)
1627
+ self.SetTitle(_("Scene"))
1628
+ self.Layout()
1629
+ self.restore_aspect()
1630
+
1631
+ def window_open(self):
1632
+ self.panel.pane_show()
1633
+
1634
+ def window_close(self):
1635
+ self.panel.pane_hide()