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

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