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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (445) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1195 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1844 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4561 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4329 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +933 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/trace.py +651 -563
  66. meerk40t/core/elements/tree_commands.py +415 -409
  67. meerk40t/core/elements/undo_redo.py +116 -58
  68. meerk40t/core/elements/wordlist.py +319 -200
  69. meerk40t/core/exceptions.py +9 -9
  70. meerk40t/core/laserjob.py +220 -220
  71. meerk40t/core/logging.py +63 -63
  72. meerk40t/core/node/blobnode.py +83 -86
  73. meerk40t/core/node/bootstrap.py +105 -103
  74. meerk40t/core/node/branch_elems.py +40 -31
  75. meerk40t/core/node/branch_ops.py +45 -38
  76. meerk40t/core/node/branch_regmark.py +48 -41
  77. meerk40t/core/node/cutnode.py +29 -32
  78. meerk40t/core/node/effect_hatch.py +375 -257
  79. meerk40t/core/node/effect_warp.py +398 -0
  80. meerk40t/core/node/effect_wobble.py +441 -309
  81. meerk40t/core/node/elem_ellipse.py +404 -309
  82. meerk40t/core/node/elem_image.py +1082 -801
  83. meerk40t/core/node/elem_line.py +358 -292
  84. meerk40t/core/node/elem_path.py +259 -201
  85. meerk40t/core/node/elem_point.py +129 -102
  86. meerk40t/core/node/elem_polyline.py +310 -246
  87. meerk40t/core/node/elem_rect.py +376 -286
  88. meerk40t/core/node/elem_text.py +445 -418
  89. meerk40t/core/node/filenode.py +59 -40
  90. meerk40t/core/node/groupnode.py +138 -74
  91. meerk40t/core/node/image_processed.py +777 -766
  92. meerk40t/core/node/image_raster.py +156 -113
  93. meerk40t/core/node/layernode.py +31 -31
  94. meerk40t/core/node/mixins.py +135 -107
  95. meerk40t/core/node/node.py +1427 -1304
  96. meerk40t/core/node/nutils.py +117 -114
  97. meerk40t/core/node/op_cut.py +462 -335
  98. meerk40t/core/node/op_dots.py +296 -251
  99. meerk40t/core/node/op_engrave.py +414 -311
  100. meerk40t/core/node/op_image.py +755 -369
  101. meerk40t/core/node/op_raster.py +787 -522
  102. meerk40t/core/node/place_current.py +37 -40
  103. meerk40t/core/node/place_point.py +329 -126
  104. meerk40t/core/node/refnode.py +58 -47
  105. meerk40t/core/node/rootnode.py +225 -219
  106. meerk40t/core/node/util_console.py +48 -48
  107. meerk40t/core/node/util_goto.py +84 -65
  108. meerk40t/core/node/util_home.py +61 -61
  109. meerk40t/core/node/util_input.py +102 -102
  110. meerk40t/core/node/util_output.py +102 -102
  111. meerk40t/core/node/util_wait.py +65 -65
  112. meerk40t/core/parameters.py +709 -707
  113. meerk40t/core/planner.py +875 -785
  114. meerk40t/core/plotplanner.py +656 -652
  115. meerk40t/core/space.py +120 -113
  116. meerk40t/core/spoolers.py +706 -705
  117. meerk40t/core/svg_io.py +1836 -1549
  118. meerk40t/core/treeop.py +534 -445
  119. meerk40t/core/undos.py +278 -124
  120. meerk40t/core/units.py +784 -680
  121. meerk40t/core/view.py +393 -322
  122. meerk40t/core/webhelp.py +62 -62
  123. meerk40t/core/wordlist.py +513 -504
  124. meerk40t/cylinder/cylinder.py +247 -0
  125. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  126. meerk40t/cylinder/gui/gui.py +24 -0
  127. meerk40t/device/__init__.py +1 -1
  128. meerk40t/device/basedevice.py +322 -123
  129. meerk40t/device/devicechoices.py +50 -0
  130. meerk40t/device/dummydevice.py +163 -128
  131. meerk40t/device/gui/defaultactions.py +618 -602
  132. meerk40t/device/gui/effectspanel.py +114 -0
  133. meerk40t/device/gui/formatterpanel.py +253 -290
  134. meerk40t/device/gui/warningpanel.py +337 -260
  135. meerk40t/device/mixins.py +13 -13
  136. meerk40t/dxf/__init__.py +1 -1
  137. meerk40t/dxf/dxf_io.py +766 -554
  138. meerk40t/dxf/plugin.py +47 -35
  139. meerk40t/external_plugins.py +79 -79
  140. meerk40t/external_plugins_build.py +28 -28
  141. meerk40t/extra/cag.py +112 -116
  142. meerk40t/extra/coolant.py +403 -0
  143. meerk40t/extra/encode_detect.py +198 -0
  144. meerk40t/extra/ezd.py +1165 -1165
  145. meerk40t/extra/hershey.py +835 -340
  146. meerk40t/extra/imageactions.py +322 -316
  147. meerk40t/extra/inkscape.py +630 -622
  148. meerk40t/extra/lbrn.py +424 -424
  149. meerk40t/extra/outerworld.py +284 -0
  150. meerk40t/extra/param_functions.py +1542 -1556
  151. meerk40t/extra/potrace.py +257 -253
  152. meerk40t/extra/serial_exchange.py +118 -0
  153. meerk40t/extra/updater.py +602 -453
  154. meerk40t/extra/vectrace.py +147 -146
  155. meerk40t/extra/winsleep.py +83 -83
  156. meerk40t/extra/xcs_reader.py +597 -0
  157. meerk40t/fill/fills.py +781 -335
  158. meerk40t/fill/patternfill.py +1061 -1061
  159. meerk40t/fill/patterns.py +614 -567
  160. meerk40t/grbl/control.py +87 -87
  161. meerk40t/grbl/controller.py +990 -903
  162. meerk40t/grbl/device.py +1081 -768
  163. meerk40t/grbl/driver.py +989 -771
  164. meerk40t/grbl/emulator.py +532 -497
  165. meerk40t/grbl/gcodejob.py +783 -767
  166. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  167. meerk40t/grbl/gui/grblcontroller.py +485 -271
  168. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  169. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  170. meerk40t/grbl/gui/gui.py +147 -116
  171. meerk40t/grbl/interpreter.py +44 -44
  172. meerk40t/grbl/loader.py +22 -22
  173. meerk40t/grbl/mock_connection.py +56 -56
  174. meerk40t/grbl/plugin.py +294 -264
  175. meerk40t/grbl/serial_connection.py +93 -88
  176. meerk40t/grbl/tcp_connection.py +81 -79
  177. meerk40t/grbl/ws_connection.py +112 -0
  178. meerk40t/gui/__init__.py +1 -1
  179. meerk40t/gui/about.py +2042 -296
  180. meerk40t/gui/alignment.py +1644 -1608
  181. meerk40t/gui/autoexec.py +199 -0
  182. meerk40t/gui/basicops.py +791 -670
  183. meerk40t/gui/bufferview.py +77 -71
  184. meerk40t/gui/busy.py +170 -133
  185. meerk40t/gui/choicepropertypanel.py +1673 -1469
  186. meerk40t/gui/consolepanel.py +706 -542
  187. meerk40t/gui/devicepanel.py +687 -581
  188. meerk40t/gui/dialogoptions.py +110 -107
  189. meerk40t/gui/executejob.py +316 -306
  190. meerk40t/gui/fonts.py +90 -90
  191. meerk40t/gui/functionwrapper.py +252 -0
  192. meerk40t/gui/gui_mixins.py +729 -0
  193. meerk40t/gui/guicolors.py +205 -182
  194. meerk40t/gui/help_assets/help_assets.py +218 -201
  195. meerk40t/gui/helper.py +154 -0
  196. meerk40t/gui/hersheymanager.py +1430 -846
  197. meerk40t/gui/icons.py +3422 -2747
  198. meerk40t/gui/imagesplitter.py +555 -508
  199. meerk40t/gui/keymap.py +354 -344
  200. meerk40t/gui/laserpanel.py +892 -806
  201. meerk40t/gui/laserrender.py +1470 -1232
  202. meerk40t/gui/lasertoolpanel.py +805 -793
  203. meerk40t/gui/magnetoptions.py +436 -0
  204. meerk40t/gui/materialmanager.py +2917 -0
  205. meerk40t/gui/materialtest.py +1722 -1694
  206. meerk40t/gui/mkdebug.py +646 -359
  207. meerk40t/gui/mwindow.py +163 -140
  208. meerk40t/gui/navigationpanels.py +2605 -2467
  209. meerk40t/gui/notes.py +143 -142
  210. meerk40t/gui/opassignment.py +414 -410
  211. meerk40t/gui/operation_info.py +310 -299
  212. meerk40t/gui/plugin.py +494 -328
  213. meerk40t/gui/position.py +714 -669
  214. meerk40t/gui/preferences.py +901 -650
  215. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  216. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  217. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  218. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  219. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  220. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  221. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  222. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  223. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  224. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  225. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  226. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  227. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  228. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  229. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  230. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  231. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  232. meerk40t/gui/propertypanels/textproperty.py +770 -755
  233. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  234. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  235. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  236. meerk40t/gui/ribbon.py +2468 -2210
  237. meerk40t/gui/scene/scene.py +1100 -1051
  238. meerk40t/gui/scene/sceneconst.py +22 -22
  239. meerk40t/gui/scene/scenepanel.py +439 -349
  240. meerk40t/gui/scene/scenespacewidget.py +365 -365
  241. meerk40t/gui/scene/widget.py +518 -505
  242. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  243. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  244. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  245. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  246. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  247. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  248. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  249. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  250. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  251. meerk40t/gui/scenewidgets/rectselectwidget.py +589 -346
  252. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  253. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  254. meerk40t/gui/scenewidgets/selectionwidget.py +2952 -2756
  255. meerk40t/gui/simpleui.py +357 -333
  256. meerk40t/gui/simulation.py +2431 -2094
  257. meerk40t/gui/snapoptions.py +208 -203
  258. meerk40t/gui/spoolerpanel.py +1227 -1180
  259. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  260. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  261. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  262. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  263. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  264. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  265. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  266. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  267. meerk40t/gui/themes.py +200 -78
  268. meerk40t/gui/tips.py +591 -0
  269. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  270. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  271. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  272. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  273. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  274. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  275. meerk40t/gui/toolwidgets/toolline.py +39 -144
  276. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  277. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  278. meerk40t/gui/toolwidgets/toolmeasure.py +160 -216
  279. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  280. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  281. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  282. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  283. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  284. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  285. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  286. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  287. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  288. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  289. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  290. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  291. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  292. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  293. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  294. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  295. meerk40t/gui/usbconnect.py +98 -91
  296. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  297. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  298. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  299. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  300. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  301. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  302. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  303. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  304. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  305. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  306. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  307. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  308. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  309. meerk40t/gui/wordlisteditor.py +985 -931
  310. meerk40t/gui/wxmeerk40t.py +1444 -1169
  311. meerk40t/gui/wxmmain.py +5578 -4112
  312. meerk40t/gui/wxmribbon.py +1591 -1076
  313. meerk40t/gui/wxmscene.py +1635 -1453
  314. meerk40t/gui/wxmtree.py +2410 -2089
  315. meerk40t/gui/wxutils.py +1769 -1099
  316. meerk40t/gui/zmatrix.py +102 -102
  317. meerk40t/image/__init__.py +1 -1
  318. meerk40t/image/dither.py +429 -0
  319. meerk40t/image/imagetools.py +2778 -2269
  320. meerk40t/internal_plugins.py +150 -130
  321. meerk40t/kernel/__init__.py +63 -12
  322. meerk40t/kernel/channel.py +259 -212
  323. meerk40t/kernel/context.py +538 -538
  324. meerk40t/kernel/exceptions.py +41 -41
  325. meerk40t/kernel/functions.py +463 -414
  326. meerk40t/kernel/jobs.py +100 -100
  327. meerk40t/kernel/kernel.py +3809 -3571
  328. meerk40t/kernel/lifecycles.py +71 -71
  329. meerk40t/kernel/module.py +49 -49
  330. meerk40t/kernel/service.py +147 -147
  331. meerk40t/kernel/settings.py +383 -343
  332. meerk40t/lihuiyu/controller.py +883 -876
  333. meerk40t/lihuiyu/device.py +1181 -1069
  334. meerk40t/lihuiyu/driver.py +1466 -1372
  335. meerk40t/lihuiyu/gui/gui.py +127 -106
  336. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  337. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  338. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  339. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  340. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  341. meerk40t/lihuiyu/interpreter.py +53 -53
  342. meerk40t/lihuiyu/laserspeed.py +450 -450
  343. meerk40t/lihuiyu/loader.py +90 -90
  344. meerk40t/lihuiyu/parser.py +404 -404
  345. meerk40t/lihuiyu/plugin.py +101 -102
  346. meerk40t/lihuiyu/tcp_connection.py +111 -109
  347. meerk40t/main.py +231 -165
  348. meerk40t/moshi/builder.py +788 -781
  349. meerk40t/moshi/controller.py +505 -499
  350. meerk40t/moshi/device.py +495 -442
  351. meerk40t/moshi/driver.py +862 -696
  352. meerk40t/moshi/gui/gui.py +78 -76
  353. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  354. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  355. meerk40t/moshi/plugin.py +43 -43
  356. meerk40t/network/console_server.py +102 -57
  357. meerk40t/network/kernelserver.py +10 -9
  358. meerk40t/network/tcp_server.py +142 -140
  359. meerk40t/network/udp_server.py +103 -77
  360. meerk40t/network/web_server.py +390 -0
  361. meerk40t/newly/controller.py +1158 -1144
  362. meerk40t/newly/device.py +874 -732
  363. meerk40t/newly/driver.py +540 -412
  364. meerk40t/newly/gui/gui.py +219 -188
  365. meerk40t/newly/gui/newlyconfig.py +116 -101
  366. meerk40t/newly/gui/newlycontroller.py +193 -186
  367. meerk40t/newly/gui/operationproperties.py +51 -51
  368. meerk40t/newly/mock_connection.py +82 -82
  369. meerk40t/newly/newly_params.py +56 -56
  370. meerk40t/newly/plugin.py +1214 -1246
  371. meerk40t/newly/usb_connection.py +322 -322
  372. meerk40t/rotary/gui/gui.py +52 -46
  373. meerk40t/rotary/gui/rotarysettings.py +240 -232
  374. meerk40t/rotary/rotary.py +202 -98
  375. meerk40t/ruida/control.py +291 -91
  376. meerk40t/ruida/controller.py +138 -1088
  377. meerk40t/ruida/device.py +672 -231
  378. meerk40t/ruida/driver.py +534 -472
  379. meerk40t/ruida/emulator.py +1494 -1491
  380. meerk40t/ruida/exceptions.py +4 -4
  381. meerk40t/ruida/gui/gui.py +71 -76
  382. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  383. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  384. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  385. meerk40t/ruida/loader.py +54 -52
  386. meerk40t/ruida/mock_connection.py +57 -109
  387. meerk40t/ruida/plugin.py +124 -87
  388. meerk40t/ruida/rdjob.py +2084 -945
  389. meerk40t/ruida/serial_connection.py +116 -0
  390. meerk40t/ruida/tcp_connection.py +146 -0
  391. meerk40t/ruida/udp_connection.py +73 -0
  392. meerk40t/svgelements.py +9671 -9669
  393. meerk40t/tools/driver_to_path.py +584 -579
  394. meerk40t/tools/geomstr.py +5583 -4680
  395. meerk40t/tools/jhfparser.py +357 -292
  396. meerk40t/tools/kerftest.py +904 -890
  397. meerk40t/tools/livinghinges.py +1168 -1033
  398. meerk40t/tools/pathtools.py +987 -949
  399. meerk40t/tools/pmatrix.py +234 -0
  400. meerk40t/tools/pointfinder.py +942 -942
  401. meerk40t/tools/polybool.py +940 -940
  402. meerk40t/tools/rasterplotter.py +1660 -547
  403. meerk40t/tools/shxparser.py +989 -901
  404. meerk40t/tools/ttfparser.py +726 -446
  405. meerk40t/tools/zinglplotter.py +595 -593
  406. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/LICENSE +21 -21
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/METADATA +150 -139
  408. meerk40t-0.9.7010.dist-info/RECORD +445 -0
  409. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/WHEEL +1 -1
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/top_level.txt +0 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/zip-safe +1 -1
  412. meerk40t/balormk/elementlightjob.py +0 -159
  413. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  414. test/bootstrap.py +0 -63
  415. test/test_cli.py +0 -12
  416. test/test_core_cutcode.py +0 -418
  417. test/test_core_elements.py +0 -144
  418. test/test_core_plotplanner.py +0 -397
  419. test/test_core_viewports.py +0 -312
  420. test/test_drivers_grbl.py +0 -108
  421. test/test_drivers_lihuiyu.py +0 -443
  422. test/test_drivers_newly.py +0 -113
  423. test/test_element_degenerate_points.py +0 -43
  424. test/test_elements_classify.py +0 -97
  425. test/test_elements_penbox.py +0 -22
  426. test/test_file_svg.py +0 -176
  427. test/test_fill.py +0 -155
  428. test/test_geomstr.py +0 -1523
  429. test/test_geomstr_nodes.py +0 -18
  430. test/test_imagetools_actualize.py +0 -306
  431. test/test_imagetools_wizard.py +0 -258
  432. test/test_kernel.py +0 -200
  433. test/test_laser_speeds.py +0 -3303
  434. test/test_length.py +0 -57
  435. test/test_lifecycle.py +0 -66
  436. test/test_operations.py +0 -251
  437. test/test_operations_hatch.py +0 -57
  438. test/test_ruida.py +0 -19
  439. test/test_spooler.py +0 -22
  440. test/test_tools_rasterplotter.py +0 -29
  441. test/test_wobble.py +0 -133
  442. test/test_zingl.py +0 -124
  443. {test → meerk40t/cylinder}/__init__.py +0 -0
  444. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  445. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/entry_points.txt +0 -0
@@ -1,1694 +1,1722 @@
1
- from copy import copy
2
- from math import tau
3
-
4
- import wx
5
- from wx import aui
6
-
7
- from meerk40t.core.node.effect_hatch import HatchEffectNode
8
- from meerk40t.core.node.op_cut import CutOpNode
9
- from meerk40t.core.node.op_engrave import EngraveOpNode
10
- from meerk40t.core.node.op_image import ImageOpNode
11
- from meerk40t.core.node.op_raster import RasterOpNode
12
- from meerk40t.core.units import UNITS_PER_PIXEL, Angle, Length
13
- from meerk40t.gui.icons import STD_ICON_SIZE, get_default_icon_size, icons8_detective
14
- from meerk40t.gui.mwindow import MWindow
15
- from meerk40t.gui.wxutils import StaticBoxSizer, TextCtrl, dip_size
16
- from meerk40t.kernel import Settings, lookup_listener, signal_listener
17
- from meerk40t.svgelements import Color, Matrix
18
-
19
- _ = wx.GetTranslation
20
-
21
-
22
- class SaveLoadPanel(wx.Panel):
23
- """
24
- Provides the scaffold for saving and loading of parameter sets.
25
- Does not know a lot about the underlying structure of data as it
26
- blindly interacts with the parent via the callback routine
27
- (could hence work as a generic way to save / load data)
28
- """
29
-
30
- def __init__(self, *args, context=None, **kwds):
31
- kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
32
- wx.Panel.__init__(self, *args, **kwds)
33
- self.context = context
34
- self.callback = None
35
- sizer_main = wx.BoxSizer(wx.VERTICAL)
36
- self.SetSizer(sizer_main)
37
- sizer_name = wx.BoxSizer(wx.HORIZONTAL)
38
- lbl_info = wx.StaticText(self, wx.ID_ANY, _("Template-Name"))
39
- self.txt_name = wx.TextCtrl(self, wx.ID_ANY, "")
40
- self.btn_save = wx.Button(self, wx.ID_ANY, _("Save"))
41
- self.btn_load = wx.Button(self, wx.ID_ANY, _("Load"))
42
- self.btn_delete = wx.Button(self, wx.ID_ANY, _("Delete"))
43
- self.btn_load.Enable(False)
44
- self.btn_save.Enable(False)
45
- self.btn_delete.Enable(False)
46
- sizer_name.Add(lbl_info, 0, wx.ALIGN_CENTER_VERTICAL, 0)
47
- sizer_name.Add(self.txt_name, 1, wx.ALIGN_CENTER_VERTICAL, 0)
48
- sizer_name.Add(self.btn_save, 0, wx.EXPAND, 0)
49
- sizer_name.Add(self.btn_load, 0, wx.EXPAND, 0)
50
- sizer_name.Add(self.btn_delete, 0, wx.EXPAND, 0)
51
-
52
- self.choices = []
53
- self.list_slots = wx.ListBox(
54
- self, wx.ID_ANY, choices=self.choices, style=wx.LB_SINGLE
55
- )
56
- self.list_slots.SetToolTip(_("Select an entry to reload"))
57
- sizer_main.Add(sizer_name, 0, wx.EXPAND, 0)
58
- sizer_main.Add(self.list_slots, 1, wx.EXPAND, 0)
59
- self.Layout()
60
- self.Bind(wx.EVT_TEXT, self.on_text_change, self.txt_name)
61
- self.Bind(wx.EVT_BUTTON, self.on_btn_load, self.btn_load)
62
- self.Bind(wx.EVT_BUTTON, self.on_btn_save, self.btn_save)
63
- self.Bind(wx.EVT_BUTTON, self.on_btn_delete, self.btn_delete)
64
- self.list_slots.Bind(wx.EVT_LISTBOX, self.on_listbox_click)
65
- self.list_slots.Bind(wx.EVT_LISTBOX_DCLICK, self.on_listbox_double_click)
66
-
67
- def set_callback(self, routine):
68
- self.callback = routine
69
- self.fill_choices("")
70
-
71
- def standardize(self, text):
72
- text = text.lower()
73
- for invalid in (
74
- "=",
75
- ":",
76
- ):
77
- text = text.replace(invalid, "_")
78
- return text
79
-
80
- def fill_choices(self, txt):
81
- self.choices = []
82
- if self.callback is not None:
83
- self.choices = self.callback("get", "")
84
-
85
- self.list_slots.Clear()
86
- self.list_slots.SetItems(self.choices)
87
- if txt:
88
- try:
89
- idx = self.choices.index(txt)
90
- except ValueError:
91
- idx = -1
92
- if idx >= 0:
93
- self.list_slots.SetSelection(idx)
94
- self.list_slots.Refresh()
95
-
96
- def on_text_change(self, event):
97
- info = self.txt_name.GetValue()
98
- flag1 = False
99
- flag2 = False
100
- if info:
101
- info = self.standardize(info)
102
- flag2 = True
103
- try:
104
- idx = self.choices.index(info)
105
- except ValueError:
106
- idx = -1
107
- flag1 = idx >= 0
108
- self.btn_load.Enable(flag1)
109
- self.btn_delete.Enable(flag1)
110
- self.btn_save.Enable(flag2)
111
-
112
- def on_btn_load(self, event):
113
- info = self.txt_name.GetValue()
114
- if self.callback is None or not info:
115
- return
116
- info = self.standardize(info)
117
- __ = self.callback("load", info)
118
-
119
- def on_btn_delete(self, event):
120
- info = self.txt_name.GetValue()
121
- if self.callback is None or not info:
122
- return
123
- info = self.standardize(info)
124
- __ = self.callback("delete", info)
125
- self.fill_choices("")
126
-
127
- def on_btn_save(self, event):
128
- info = self.txt_name.GetValue()
129
- if self.callback is None or not info:
130
- return
131
- info = self.standardize(info)
132
- __ = self.callback("save", info)
133
- self.fill_choices(info)
134
- self.on_text_change(None)
135
-
136
- def on_listbox_click(self, event):
137
- info = ""
138
- idx = self.list_slots.GetSelection()
139
- # print (f"Click with {idx}")
140
- if idx >= 0:
141
- info = self.choices[idx]
142
- self.txt_name.SetValue(info)
143
- self.on_text_change(None)
144
-
145
- def on_listbox_double_click(self, event):
146
- info = ""
147
- idx = self.list_slots.GetSelection()
148
- # print (f"DClick with {idx}")
149
- if idx >= 0:
150
- info = self.choices[idx]
151
- self.txt_name.SetValue(info)
152
- self.on_btn_load(None)
153
- self.on_text_change(None)
154
-
155
-
156
- class TemplatePanel(wx.Panel):
157
- """
158
- Responsible for the generation of testpatterns and the user interface
159
- params:
160
- context - the current context
161
- storage - an instance of kernel.Settings to store/load parameter sets
162
- """
163
-
164
- def __init__(self, *args, context=None, storage=None, **kwds):
165
- def size_it(ctrl, value):
166
- ctrl.SetMaxSize(dip_size(self, int(value), -1))
167
- ctrl.SetMinSize(dip_size(self, int(value * 0.75), -1))
168
- ctrl.SetSize(dip_size(self, value, -1))
169
-
170
- # begin wxGlade: clsLasertools.__init__
171
- kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
172
- wx.Panel.__init__(self, *args, **kwds)
173
- self.context = context
174
- self.storage = storage
175
- self.callback = None
176
- self.current_op = None
177
- opchoices = [_("Cut"), _("Engrave"), _("Raster"), _("Image"), _("Hatch")]
178
- # Setup 5 Op nodes - they aren't saved yet
179
- self.default_op = []
180
- # A tuple defining whether a free color-selection scheme is allowed, linked to default_op
181
- self.color_scheme_free = []
182
- self.default_op.append(CutOpNode())
183
- self.color_scheme_free.append(True)
184
- self.default_op.append(EngraveOpNode())
185
- self.color_scheme_free.append(True)
186
- self.default_op.append(RasterOpNode())
187
- self.color_scheme_free.append(False)
188
- self.default_op.append(ImageOpNode())
189
- self.color_scheme_free.append(True)
190
- op = EngraveOpNode()
191
- op.add_node(HatchEffectNode())
192
- self.default_op.append(op)
193
- self.color_scheme_free.append(True)
194
-
195
- self.use_image = [False] * len(self.default_op)
196
- self.use_image[3] = True
197
-
198
- self._freecolor = True
199
-
200
- self.parameters = []
201
- color_choices = [_("Red"), _("Green"), _("Blue")]
202
-
203
- self.combo_ops = wx.ComboBox(
204
- self, id=wx.ID_ANY, choices=opchoices, style=wx.CB_DROPDOWN | wx.CB_READONLY
205
- )
206
- self.images = []
207
- self.image_labels = []
208
- self.image_labels.append(_("Choose image..."))
209
-
210
- for node in self.context.elements.elems():
211
- if node.type == "elem image":
212
- imagenode = copy(node)
213
- bb = imagenode.bounds
214
- if bb is not None:
215
- # Put it back on origin
216
- imagenode.matrix.post_translate(-bb[0], -bb[1])
217
- self.images.append(imagenode)
218
- w, h = imagenode.active_image.size
219
- label = f"{w} x {h} Pixel"
220
- if node.label:
221
- label += "(" + node.label + ")"
222
- self.image_labels.append(label)
223
-
224
- self.combo_images = wx.ComboBox(
225
- self,
226
- id=wx.ID_ANY,
227
- choices=self.image_labels,
228
- style=wx.CB_DROPDOWN | wx.CB_READONLY,
229
- )
230
- self.combo_images.SetToolTip(
231
- _(
232
- "Choose from one of the existing images on your canvas to use as the test template"
233
- )
234
- )
235
- self.combo_images.SetSelection(0)
236
- self.check_labels = wx.CheckBox(self, wx.ID_ANY, _("Labels"))
237
- self.check_values = wx.CheckBox(self, wx.ID_ANY, _("Values"))
238
-
239
- self.combo_param_1 = wx.ComboBox(
240
- self, id=wx.ID_ANY, style=wx.CB_DROPDOWN | wx.CB_READONLY
241
- )
242
- self.spin_count_1 = wx.SpinCtrl(self, wx.ID_ANY, initial=5, min=1, max=100)
243
- self.text_min_1 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
244
- self.text_max_1 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
245
- self.text_dim_1 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
246
- self.text_dim_1.set_range(0, 50)
247
- self.text_delta_1 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
248
- self.text_delta_1.set_range(0, 50)
249
- self.unit_param_1a = wx.StaticText(self, wx.ID_ANY, "")
250
- self.unit_param_1b = wx.StaticText(self, wx.ID_ANY, "")
251
- size_it(self.unit_param_1a, 85)
252
- size_it(self.unit_param_1b, 85)
253
-
254
- self.combo_color_1 = wx.ComboBox(
255
- self,
256
- wx.ID_ANY,
257
- choices=color_choices,
258
- style=wx.CB_DROPDOWN | wx.CB_READONLY,
259
- )
260
- self.check_color_direction_1 = wx.CheckBox(self, wx.ID_ANY, _("Growing"))
261
-
262
- self.combo_param_2 = wx.ComboBox(
263
- self, id=wx.ID_ANY, style=wx.CB_DROPDOWN | wx.CB_READONLY
264
- )
265
- self.spin_count_2 = wx.SpinCtrl(self, wx.ID_ANY, initial=5, min=1, max=100)
266
- self.text_min_2 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
267
- self.text_max_2 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
268
- self.text_dim_2 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
269
- self.text_dim_2.set_range(0, 50)
270
- self.text_delta_2 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
271
- self.text_delta_2.set_range(0, 50)
272
- self.unit_param_2a = wx.StaticText(self, wx.ID_ANY, "")
273
- self.unit_param_2b = wx.StaticText(self, wx.ID_ANY, "")
274
- size_it(self.unit_param_2a, 85)
275
- size_it(self.unit_param_2b, 85)
276
-
277
- self.combo_color_2 = wx.ComboBox(
278
- self,
279
- wx.ID_ANY,
280
- choices=color_choices,
281
- style=wx.CB_DROPDOWN | wx.CB_READONLY,
282
- )
283
- self.check_color_direction_2 = wx.CheckBox(self, wx.ID_ANY, _("Growing"))
284
-
285
- self.button_create = wx.Button(self, wx.ID_ANY, _("Create Pattern"))
286
- self.button_create.SetBitmap(
287
- icons8_detective.GetBitmap(resize=0.5 * get_default_icon_size())
288
- )
289
-
290
- sizer_main = wx.BoxSizer(wx.VERTICAL)
291
- sizer_param_optype = wx.BoxSizer(wx.HORIZONTAL)
292
-
293
- self.sizer_param_op = StaticBoxSizer(
294
- self, wx.ID_ANY, _("Operation to test"), wx.VERTICAL
295
- )
296
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Operation:"))
297
- size_it(mylbl, 85)
298
- h1 = wx.BoxSizer(wx.HORIZONTAL)
299
- h1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
300
- h1.Add(self.combo_ops, 1, wx.EXPAND, 0)
301
- self.sizer_param_op.Add(h1, 0, wx.EXPAND, 0)
302
- self.sizer_param_op.Add(self.combo_images, 0, wx.EXPAND, 0)
303
-
304
- sizer_param_check = StaticBoxSizer(
305
- self, wx.ID_ANY, _("Show Labels / Values"), wx.HORIZONTAL
306
- )
307
- sizer_param_check.Add(self.check_labels, 1, wx.ALIGN_CENTER_VERTICAL, 0)
308
- sizer_param_check.Add(self.check_values, 1, wx.ALIGN_CENTER_VERTICAL, 0)
309
-
310
- sizer_param_optype.Add(self.sizer_param_op, 1, wx.EXPAND, 0)
311
- sizer_param_optype.Add(sizer_param_check, 1, wx.EXPAND, 0)
312
-
313
- sizer_param_xy = wx.BoxSizer(wx.HORIZONTAL)
314
- sizer_param_x = StaticBoxSizer(
315
- self, wx.ID_ANY, _("First parameter (X-Axis)"), wx.VERTICAL
316
- )
317
-
318
- hline_param_1 = wx.BoxSizer(wx.HORIZONTAL)
319
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Parameter:"))
320
- size_it(mylbl, 85)
321
- hline_param_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
322
- hline_param_1.Add(self.combo_param_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
323
-
324
- hline_count_1 = wx.BoxSizer(wx.HORIZONTAL)
325
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Count:"))
326
- size_it(mylbl, 85)
327
- self.info_delta_1 = wx.StaticText(self, wx.ID_ANY, "")
328
-
329
- hline_count_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
330
- hline_count_1.Add(self.spin_count_1, 0, wx.ALIGN_CENTER_VERTICAL, 0)
331
- hline_count_1.Add(self.info_delta_1, 0, wx.ALIGN_CENTER_VERTICAL, 0)
332
-
333
- hline_min_1 = wx.BoxSizer(wx.HORIZONTAL)
334
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Minimum:"))
335
- size_it(mylbl, 85)
336
- hline_min_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
337
- hline_min_1.Add(self.text_min_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
338
- hline_min_1.Add(self.unit_param_1a, 0, wx.ALIGN_CENTER_VERTICAL, 0)
339
-
340
- hline_max_1 = wx.BoxSizer(wx.HORIZONTAL)
341
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Maximum:"))
342
- size_it(mylbl, 85)
343
- hline_max_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
344
- hline_max_1.Add(self.text_max_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
345
- hline_max_1.Add(self.unit_param_1b, 0, wx.ALIGN_CENTER_VERTICAL, 0)
346
-
347
- hline_dim_1 = wx.BoxSizer(wx.HORIZONTAL)
348
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Width:"))
349
- size_it(mylbl, 85)
350
- hline_dim_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
351
- hline_dim_1.Add(self.text_dim_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
352
- mylbl = wx.StaticText(self, wx.ID_ANY, "mm")
353
- hline_dim_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
354
-
355
- hline_delta_1 = wx.BoxSizer(wx.HORIZONTAL)
356
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Delta:"))
357
- size_it(mylbl, 85)
358
- hline_delta_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
359
- hline_delta_1.Add(self.text_delta_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
360
- mylbl = wx.StaticText(self, wx.ID_ANY, "mm")
361
- hline_delta_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
362
-
363
- hline_color_1 = wx.BoxSizer(wx.HORIZONTAL)
364
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Color:"))
365
- size_it(mylbl, 85)
366
- hline_color_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
367
- hline_color_1.Add(self.combo_color_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
368
- hline_color_1.Add(self.check_color_direction_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
369
-
370
- sizer_param_x.Add(hline_param_1, 0, wx.EXPAND, 0)
371
- sizer_param_x.Add(hline_count_1, 0, wx.EXPAND, 0)
372
- sizer_param_x.Add(hline_min_1, 0, wx.EXPAND, 0)
373
- sizer_param_x.Add(hline_max_1, 0, wx.EXPAND, 0)
374
- sizer_param_x.Add(hline_dim_1, 0, wx.EXPAND, 0)
375
- sizer_param_x.Add(hline_delta_1, 0, wx.EXPAND, 0)
376
- sizer_param_x.Add(hline_color_1, 0, wx.EXPAND, 0)
377
-
378
- sizer_param_y = StaticBoxSizer(
379
- self, wx.ID_ANY, _("Second parameter (Y-Axis)"), wx.VERTICAL
380
- )
381
-
382
- hline_param_2 = wx.BoxSizer(wx.HORIZONTAL)
383
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Parameter:"))
384
- size_it(mylbl, 85)
385
- hline_param_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
386
- hline_param_2.Add(self.combo_param_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
387
-
388
- hline_count_2 = wx.BoxSizer(wx.HORIZONTAL)
389
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Count:"))
390
- size_it(mylbl, 85)
391
- self.info_delta_2 = wx.StaticText(self, wx.ID_ANY, "")
392
- hline_count_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
393
- hline_count_2.Add(self.spin_count_2, 0, wx.ALIGN_CENTER_VERTICAL, 0)
394
- hline_count_2.Add(self.info_delta_2, 0, wx.ALIGN_CENTER_VERTICAL, 0)
395
-
396
- hline_min_2 = wx.BoxSizer(wx.HORIZONTAL)
397
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Minimum:"))
398
- size_it(mylbl, 85)
399
- hline_min_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
400
- hline_min_2.Add(self.text_min_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
401
- hline_min_2.Add(self.unit_param_2a, 0, wx.ALIGN_CENTER_VERTICAL, 0)
402
-
403
- hline_max_2 = wx.BoxSizer(wx.HORIZONTAL)
404
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Maximum:"))
405
- size_it(mylbl, 85)
406
- hline_max_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
407
- hline_max_2.Add(self.text_max_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
408
- hline_max_2.Add(self.unit_param_2b, 0, wx.ALIGN_CENTER_VERTICAL, 0)
409
-
410
- hline_dim_2 = wx.BoxSizer(wx.HORIZONTAL)
411
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Height:"))
412
- size_it(mylbl, 85)
413
- hline_dim_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
414
- hline_dim_2.Add(self.text_dim_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
415
- mylbl = wx.StaticText(self, wx.ID_ANY, "mm")
416
- hline_dim_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
417
-
418
- hline_delta_2 = wx.BoxSizer(wx.HORIZONTAL)
419
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Delta:"))
420
- size_it(mylbl, 85)
421
- hline_delta_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
422
- hline_delta_2.Add(self.text_delta_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
423
- mylbl = wx.StaticText(self, wx.ID_ANY, "mm")
424
- hline_delta_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
425
-
426
- hline_color_2 = wx.BoxSizer(wx.HORIZONTAL)
427
- mylbl = wx.StaticText(self, wx.ID_ANY, _("Color:"))
428
- size_it(mylbl, 85)
429
- hline_color_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
430
- hline_color_2.Add(self.combo_color_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
431
- hline_color_2.Add(self.check_color_direction_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
432
-
433
- sizer_param_y.Add(hline_param_2, 0, wx.EXPAND, 0)
434
- sizer_param_y.Add(hline_count_2, 0, wx.EXPAND, 0)
435
- sizer_param_y.Add(hline_min_2, 0, wx.EXPAND, 0)
436
- sizer_param_y.Add(hline_max_2, 0, wx.EXPAND, 0)
437
- sizer_param_y.Add(hline_dim_2, 0, wx.EXPAND, 0)
438
- sizer_param_y.Add(hline_delta_2, 0, wx.EXPAND, 0)
439
- sizer_param_y.Add(hline_color_2, 0, wx.EXPAND, 0)
440
-
441
- sizer_param_xy.Add(sizer_param_x, 1, wx.EXPAND, 0)
442
- sizer_param_xy.Add(sizer_param_y, 1, wx.EXPAND, 0)
443
-
444
- sizer_main.Add(sizer_param_optype, 0, wx.EXPAND, 0)
445
- sizer_main.Add(sizer_param_xy, 0, wx.EXPAND, 0)
446
- sizer_main.Add(self.button_create, 0, wx.EXPAND, 0)
447
-
448
- sizer_info = StaticBoxSizer(self, wx.ID_ANY, _("How to use it"), wx.VERTICAL)
449
- infomsg = _("To provide the best burning results, the parameters of operations")
450
- infomsg += " " + _(
451
- "need to be adjusted according to *YOUR* laser and the specific material"
452
- )
453
- infomsg += " " + _(
454
- "you want to work with (e.g. one batch of poplar plywood from one supplier"
455
- )
456
- infomsg += " " + _(
457
- "may respond completely different to a batch of another supplier despite"
458
- )
459
- infomsg += " " + _("having the very same specifications on paper).")
460
- infomsg += "\n" + _(
461
- "E.g. for a regular CO2 laser you want to optimize the burn speed"
462
- )
463
- infomsg += " " + _(
464
- "for a given power to reduce burn marks or decrease execution time."
465
- )
466
- infomsg += "\n" + _(
467
- "Meerk40t simplifies this task to find out the optimal settings"
468
- )
469
- infomsg += " " + _(
470
- "by creating a testpattern that varies two different parameters."
471
- )
472
-
473
- info_label = wx.TextCtrl(
474
- self, wx.ID_ANY, value=infomsg, style=wx.TE_READONLY | wx.TE_MULTILINE
475
- )
476
- info_label.SetBackgroundColour(self.GetBackgroundColour())
477
- sizer_info.Add(info_label, 1, wx.EXPAND, 0)
478
- sizer_main.Add(sizer_info, 1, wx.EXPAND, 0)
479
-
480
- self.button_create.SetToolTip(_("Create a grid with your values"))
481
- s = _("Operation type for which the testpattern will be generated")
482
- s += "\n" + _(
483
- "You can define the common parameters for this operation in the other tabs on top of this window"
484
- )
485
- self.combo_ops.SetToolTip(s)
486
- self.combo_param_1.SetToolTip(
487
- _("Choose the first parameter that you want to be tested")
488
- )
489
- self.combo_param_2.SetToolTip(
490
- _("Choose the second parameter that you want to be tested")
491
- )
492
- self.combo_color_1.SetToolTip(
493
- _(
494
- "Choose the color aspect for the second parameter. NB: the colors for both parameters will be combined"
495
- )
496
- )
497
- self.combo_color_2.SetToolTip(
498
- _(
499
- "Choose the color aspect for the second parameter. NB: the colors for both parameters will be combined"
500
- )
501
- )
502
- self.check_color_direction_1.SetToolTip(
503
- _(
504
- "If checked, then the color aspect will grow from min to max values, if not then shrink"
505
- )
506
- )
507
- self.check_color_direction_2.SetToolTip(
508
- _(
509
- "If checked, then the color aspect will grow from min to max values, if not then shrink"
510
- )
511
- )
512
- self.spin_count_1.SetToolTip(
513
- _(
514
- "Define how many values you want to test in the interval between min and max"
515
- )
516
- )
517
- self.spin_count_2.SetToolTip(
518
- _(
519
- "Define how many values you want to test in the interval between min and max"
520
- )
521
- )
522
- self.check_labels.SetToolTip(
523
- _("Will create a descriptive label at the sides of the grid")
524
- )
525
- self.check_values.SetToolTip(
526
- _("Will create the corresponding values as labels at the sides of the grid")
527
- )
528
- self.text_min_1.SetToolTip(_("Minimum value for 1st parameter"))
529
- self.text_max_1.SetToolTip(_("Maximum value for 1st parameter"))
530
- self.text_min_2.SetToolTip(_("Minimum value for 2nd parameter"))
531
- self.text_max_2.SetToolTip(_("Maximum value for 2nd parameter"))
532
- self.text_dim_1.SetToolTip(_("Width of the to be created pattern"))
533
- self.text_dim_2.SetToolTip(_("Height of the to be created pattern"))
534
- self.text_delta_1.SetToolTip(_("Horizontal gap between patterns"))
535
- self.text_delta_2.SetToolTip(_("Vertical gap between patterns"))
536
-
537
- self.button_create.Bind(wx.EVT_BUTTON, self.on_button_create_pattern)
538
- self.combo_ops.Bind(wx.EVT_COMBOBOX, self.set_param_according_to_op)
539
- self.text_min_1.Bind(wx.EVT_TEXT, self.validate_input)
540
- self.text_max_1.Bind(wx.EVT_TEXT, self.validate_input)
541
- self.text_min_2.Bind(wx.EVT_TEXT, self.validate_input)
542
- self.text_max_2.Bind(wx.EVT_TEXT, self.validate_input)
543
- self.text_dim_1.Bind(wx.EVT_TEXT, self.validate_input)
544
- self.text_delta_1.Bind(wx.EVT_TEXT, self.validate_input)
545
- self.text_dim_2.Bind(wx.EVT_TEXT, self.validate_input)
546
- self.text_delta_2.Bind(wx.EVT_TEXT, self.validate_input)
547
- self.combo_param_1.Bind(wx.EVT_COMBOBOX, self.on_combo_1)
548
- self.combo_param_2.Bind(wx.EVT_COMBOBOX, self.on_combo_2)
549
- self.combo_images.Bind(wx.EVT_COMBOBOX, self.on_combo_image)
550
- self.spin_count_1.Bind(wx.EVT_SPINCTRL, self.validate_input)
551
- self.spin_count_2.Bind(wx.EVT_SPINCTRL, self.validate_input)
552
-
553
- self.SetSizer(sizer_main)
554
- self.Layout()
555
- self.setup_settings()
556
- self.combo_ops.SetSelection(0)
557
- self.restore_settings()
558
- self.sync_fields()
559
-
560
- def shortened(self, value, digits):
561
- result = str(round(value, digits))
562
- if "." in result:
563
- while result.endswith("0"):
564
- result = result[:-1]
565
- if result.endswith("."):
566
- if result == ".":
567
- result = "0"
568
- else:
569
- result = result[:-1]
570
- return result
571
-
572
- def on_combo_image(self, event):
573
- op = self.combo_ops.GetSelection()
574
- if op != 3: # No Image?
575
- return
576
- idx = self.combo_images.GetSelection() - 1
577
- if 0 <= idx < len(self.images):
578
- bb = self.images[idx].bounds
579
- if bb is not None:
580
- wd = Length(amount=bb[2] - bb[0], preferred_units="mm")
581
- ht = Length(amount=bb[3] - bb[1], preferred_units="mm")
582
- self.text_dim_1.SetValue(f"{wd.mm:.1f}")
583
- self.text_dim_2.SetValue(f"{ht.mm:.1f}")
584
-
585
- def set_callback(self, routine):
586
- self.callback = routine
587
- idx = self.combo_ops.GetSelection()
588
- if self.callback is not None and idx >= 0:
589
- self.callback(self.default_op[idx])
590
-
591
- def use_percent(self):
592
- self.context.device.setting(bool, "use_percent_for_power_display", False)
593
- return self.context.device.use_percent_for_power_display
594
-
595
- def use_mm_min(self):
596
- self.context.device.setting(bool, "use_mm_min_for_speed_display", False)
597
- return self.context.device.use_mm_min_for_speed_display
598
-
599
- def set_param_according_to_op(self, event):
600
- def preset_passes(node=None):
601
- # Will be called ahead of the modification of the passes variable
602
- node.passes_custom = True
603
-
604
- def preset_balor_wobble(node=None):
605
- # Will be called ahead of the modification of a wobble variable
606
- # to copy the device defaults
607
- if node is None or "balor" not in self.context.device.path:
608
- return
609
- node.settings["wobble_enabled"] = True
610
-
611
- def preset_balor_rapid(node=None):
612
- # Will be called ahead of the modification of a rapid variable
613
- # to copy the device defaults
614
- if node is None or "balor" not in self.context.device.path:
615
- return
616
- node.settings["rapid_enabled"] = True
617
-
618
- def preset_balor_pulse(node=None):
619
- # Will be called ahead of the modification of a pulse variable
620
- # to copy the device defaults
621
- if node is None or "balor" not in self.context.device.path:
622
- return
623
- node.settings["pulse_width_enabled"] = True
624
-
625
- def preset_balor_timings(node=None):
626
- # Will be called ahead of the modification of a timing variable
627
- # to copy the device defaults
628
- if node is None or "balor" not in self.context.device.path:
629
- return
630
- if not node.settings["timing_enabled"]:
631
- node.settings["timing_enabled"] = True
632
- node.settings["delay_laser_on"] = self.context.device.delay_laser_on
633
- node.settings["delay_laser_off"] = self.context.device.delay_laser_off
634
- node.settings["delay_polygon"] = self.context.device.delay_polygon
635
-
636
- opidx = self.combo_ops.GetSelection()
637
- if self.current_op == opidx:
638
- return
639
- self.current_op = opidx
640
-
641
- self.Freeze()
642
- if opidx < 0:
643
- opnode = None
644
- self._freecolor = True
645
- self.combo_images.Show(False)
646
- self.text_dim_1.Enable(True)
647
- self.text_dim_2.Enable(True)
648
- else:
649
- opnode = self.default_op[opidx]
650
- self._freecolor = self.color_scheme_free[opidx]
651
- self.combo_images.Show(self.use_image[opidx])
652
- self.text_dim_1.Enable(not self.use_image[opidx])
653
- self.text_dim_2.Enable(not self.use_image[opidx])
654
- self.sizer_param_op.Layout()
655
- if self.callback is not None:
656
- self.callback(opnode)
657
- self.combo_color_1.Enable(self._freecolor)
658
- self.combo_color_2.Enable(self._freecolor)
659
- self.check_color_direction_1.Enable(self._freecolor)
660
- self.check_color_direction_2.Enable(self._freecolor)
661
-
662
- # (internal_attribute, secondary_attribute, Label, unit, keep_unit, needs_to_be_positive)
663
- if self.use_percent():
664
- ppi = "%"
665
- else:
666
- ppi = "ppi"
667
- if self.use_mm_min():
668
- speed_unit = "mm/min"
669
- else:
670
- speed_unit = "mm/s"
671
- self.parameters = [
672
- ("speed", None, _("Speed"), speed_unit, False, True),
673
- ("power", None, _("Power"), ppi, False, True),
674
- ("passes", preset_passes, _("Passes"), "x", False, True),
675
- ]
676
-
677
- if opidx == 0:
678
- # Cut
679
- # (internal_attribute, secondary_attribute, Label, unit, keep_unit, needs_to_be_positive)
680
- self.parameters = [
681
- ("speed", None, _("Speed"), speed_unit, False, True),
682
- ("power", None, _("Power"), ppi, False, True),
683
- ("passes", preset_passes, _("Passes"), "x", False, True),
684
- ]
685
- elif opidx == 1:
686
- # Engrave
687
- self.parameters = [
688
- ("speed", None, _("Speed"), speed_unit, False, True),
689
- ("power", None, _("Power"), ppi, False, True),
690
- ("passes", preset_passes, _("Passes"), "x", False, True),
691
- ]
692
- elif opidx == 2:
693
- # Raster
694
- self.parameters = [
695
- ("speed", None, _("Speed"), speed_unit, False, True),
696
- ("power", None, _("Power"), ppi, False, True),
697
- ("passes", preset_passes, _("Passes"), "x", False, True),
698
- ("dpi", None, _("DPI"), "dpi", False, True),
699
- ("overscan", None, _("Overscan"), "mm", False, True),
700
- ]
701
- elif opidx == 3:
702
- # Image
703
- self.parameters = [
704
- ("speed", None, _("Speed"), speed_unit, False, True),
705
- ("power", None, _("Power"), ppi, False, True),
706
- ("passes", preset_passes, _("Passes"), "x", False, True),
707
- ("dpi", None, _("DPI"), "dpi", False, True),
708
- ("overscan", None, _("Overscan"), "mm", False, True),
709
- ]
710
- elif opidx == 4:
711
- # Hatch
712
- self.parameters = [
713
- ("speed", None, _("Speed"), speed_unit, False, True),
714
- ("power", None, _("Power"), ppi, False, True),
715
- ("passes", preset_passes, _("Passes"), "x", False, True),
716
- ("hatch_distance", None, _("Hatch Distance"), "mm", False, True),
717
- ("hatch_angle", None, _("Hatch Angle"), "deg", False, True),
718
- ]
719
-
720
- if "balor" in self.context.device.path:
721
- balor_choices = [
722
- ("frequency", None, _("Frequency"), "kHz", False, True),
723
- (
724
- "rapid_speed",
725
- preset_balor_rapid,
726
- _("Rapid Speed"),
727
- "mm/s",
728
- False,
729
- True,
730
- ),
731
- (
732
- "delay_laser_on",
733
- preset_balor_timings,
734
- _("Laser On Delay"),
735
- "µs",
736
- False,
737
- False,
738
- ),
739
- (
740
- "delay_laser_off",
741
- preset_balor_timings,
742
- _("Laser Off Delay"),
743
- "µs",
744
- False,
745
- False,
746
- ),
747
- (
748
- "delay_polygon",
749
- preset_balor_timings,
750
- _("Polygon Delay"),
751
- "µs",
752
- False,
753
- False,
754
- ),
755
- (
756
- "wobble_radius",
757
- preset_balor_wobble,
758
- _("Wobble Radius"),
759
- "mm",
760
- True,
761
- True,
762
- ),
763
- (
764
- "wobble_interval",
765
- preset_balor_wobble,
766
- _("Wobble Interval"),
767
- "mm",
768
- True,
769
- True,
770
- ),
771
- (
772
- "wobble_speed",
773
- preset_balor_wobble,
774
- _("Wobble Speed Multiplier"),
775
- "x",
776
- False,
777
- True,
778
- ),
779
- ]
780
- if self.context.device.pulse_width_enabled:
781
- balor_choices.append(
782
- (
783
- "pulse_width",
784
- preset_balor_pulse,
785
- _("Pulse Width"),
786
- "ns",
787
- False,
788
- True,
789
- )
790
- )
791
-
792
- for entry in balor_choices:
793
- self.parameters.append(entry)
794
- choices = []
795
- for entry in self.parameters:
796
- choices.append(entry[2])
797
- self.combo_param_1.Clear()
798
- self.combo_param_1.Set(choices)
799
- self.combo_param_2.Clear()
800
- self.combo_param_2.Set(choices)
801
- idx1 = -1
802
- idx2 = -1
803
- if len(self.parameters) > 0:
804
- idx1 = 0
805
- idx2 = 0
806
- if len(self.parameters) > 1:
807
- idx2 = 1
808
- self.combo_param_1.SetSelection(idx1)
809
- self.on_combo_1(None)
810
- self.combo_param_2.SetSelection(idx2)
811
- self.on_combo_2(None)
812
- self.Layout()
813
- self.Thaw()
814
-
815
- def on_combo_1(self, input):
816
- s_unit = ""
817
- b_positive = True
818
- idx = self.combo_param_1.GetSelection()
819
- # 0 = internal_attribute, 1 = secondary_attribute,
820
- # 2 = Label, 3 = unit,
821
- # 4 = keep_unit, 5 = needs_to_be_positive)
822
- if 0 <= idx < len(self.parameters):
823
- s_unit = self.parameters[idx][3]
824
- b_positive = self.parameters[idx][5]
825
- self.unit_param_1a.SetLabel(s_unit)
826
- self.unit_param_1b.SetLabel(s_unit)
827
- # And now enter validation...
828
- self.validate_input(None)
829
-
830
- def on_combo_2(self, input):
831
- s_unit = ""
832
- idx = self.combo_param_2.GetSelection()
833
- # 0 = internal_attribute, 1 = secondary_attribute,
834
- # 2 = Label, 3 = unit,
835
- # 4 = keep_unit, 5 = needs_to_be_positive)
836
- if 0 <= idx < len(self.parameters):
837
- s_unit = self.parameters[idx][3]
838
- self.unit_param_2a.SetLabel(s_unit)
839
- self.unit_param_2b.SetLabel(s_unit)
840
- # And now enter validation...
841
- self.validate_input(None)
842
-
843
- def validate_input(self, event):
844
- def valid_float(ctrl):
845
- result = True
846
- if ctrl.GetValue() == "":
847
- result = False
848
- else:
849
- try:
850
- value = float(ctrl.GetValue())
851
- except ValueError:
852
- result = False
853
- return result
854
-
855
- active = True
856
- valid_interval_1 = True
857
- valid_interval_2 = True
858
- optype = self.combo_ops.GetSelection()
859
- if optype < 0:
860
- active = False
861
- if (
862
- optype == 3 and self.combo_images.GetSelection() < 1
863
- ): # image and no valid image chosen
864
- active = False
865
- idx1 = self.combo_param_1.GetSelection()
866
- if idx1 < 0:
867
- active = False
868
- idx2 = self.combo_param_2.GetSelection()
869
- if idx2 < 0:
870
- active = False
871
- if idx1 == idx2:
872
- active = False
873
- if not valid_float(self.text_min_1):
874
- active = False
875
- valid_interval_1 = False
876
- if not valid_float(self.text_max_1):
877
- active = False
878
- valid_interval_1 = False
879
- if not valid_float(self.text_min_2):
880
- active = False
881
- valid_interval_2 = False
882
- if not valid_float(self.text_max_2):
883
- active = False
884
- valid_interval_2 = False
885
- if not valid_float(self.text_dim_1):
886
- active = False
887
- if not valid_float(self.text_delta_1):
888
- active = False
889
- if not valid_float(self.text_dim_2):
890
- active = False
891
- if not valid_float(self.text_delta_2):
892
- active = False
893
- if valid_interval_1:
894
- minv = float(self.text_min_1.GetValue())
895
- maxv = float(self.text_max_1.GetValue())
896
- count = self.spin_count_1.GetValue()
897
- delta = maxv - minv
898
- if count > 1:
899
- delta /= count - 1
900
- s_unit = ""
901
- idx = self.combo_param_1.GetSelection()
902
- # 0 = internal_attribute, 1 = secondary_attribute,
903
- # 2 = Label, 3 = unit,
904
- # 4 = keep_unit, 5 = needs_to_be_positive)
905
- if 0 <= idx < len(self.parameters):
906
- s_unit = self.parameters[idx][3]
907
- self.info_delta_1.SetLabel(
908
- _("Every {dist}").format(dist=self.shortened(delta, 3) + s_unit)
909
- )
910
- else:
911
- self.info_delta_1.SetLabel("---")
912
- if valid_interval_2:
913
- minv = float(self.text_min_2.GetValue())
914
- maxv = float(self.text_max_2.GetValue())
915
- count = self.spin_count_2.GetValue()
916
- delta = maxv - minv
917
- if count > 1:
918
- delta /= count - 1
919
- s_unit = ""
920
- idx = self.combo_param_2.GetSelection()
921
- # 0 = internal_attribute, 1 = secondary_attribute,
922
- # 2 = Label, 3 = unit,
923
- # 4 = keep_unit, 5 = needs_to_be_positive)
924
- if 0 <= idx < len(self.parameters):
925
- s_unit = self.parameters[idx][3]
926
- self.info_delta_2.SetLabel(
927
- _("Every {dist}").format(dist=self.shortened(delta, 3) + s_unit)
928
- )
929
- else:
930
- self.info_delta_2.SetLabel("---")
931
-
932
- self.button_create.Enable(active)
933
-
934
- def on_device_update(self):
935
- self.current_op = None
936
- self.set_param_according_to_op(None)
937
- # self.on_combo_1(None)
938
- # self.on_combo_2(None)
939
-
940
- def on_button_create_pattern(self, event):
941
- def make_color(idx1, max1, idx2, max2, aspect1, growing1, aspect2, growing2):
942
- if self._freecolor:
943
- r = 0
944
- g = 0
945
- b = 0
946
-
947
- rel = max1 - 1
948
- if rel < 1:
949
- rel = 1
950
- if growing1:
951
- val1 = int(idx1 / rel * 255.0)
952
- else:
953
- val1 = 255 - int(idx1 / rel * 255.0)
954
-
955
- rel = max2 - 1
956
- if rel < 1:
957
- rel = 1
958
- if growing2:
959
- val2 = int(idx2 / rel * 255.0)
960
- else:
961
- val2 = 255 - int(idx2 / rel * 255.0)
962
- if aspect1 == 1:
963
- g = val1
964
- elif aspect1 == 2:
965
- b = val1
966
- else:
967
- r = val1
968
- if aspect2 == 1:
969
- g = val1
970
- elif aspect2 == 2:
971
- b = val2
972
- else:
973
- r = val2
974
- else:
975
- r = 0
976
- g = 0
977
- b = 0
978
- mycolor = Color(r, g, b)
979
- return mycolor
980
-
981
- def clear_all():
982
- self.context.elements.clear_operations(fast=True)
983
- self.context.elements.clear_elements(fast=True)
984
-
985
- def create_operations():
986
- # opchoices = [_("Cut"), _("Engrave"), _("Raster"), _("Image"), _("Hatch")]
987
- display_labels = self.check_labels.GetValue()
988
- display_values = self.check_values.GetValue()
989
- color_aspect_1 = max(0, self.combo_color_1.GetSelection())
990
- color_aspect_2 = max(0, self.combo_color_2.GetSelection())
991
- color_growing_1 = self.check_color_direction_1.GetValue()
992
- color_growing_2 = self.check_color_direction_2.GetValue()
993
-
994
- if optype < 0 or optype > 4:
995
- return
996
- if optype == 3:
997
- shapetype = "image"
998
- else:
999
- shapetype = "rect"
1000
- size_x = float(Length(f"{dimension_1}mm"))
1001
- size_y = float(Length(f"{dimension_2}mm"))
1002
- gap_x = float(Length(f"{gap_1}mm"))
1003
- gap_y = float(Length(f"{gap_2}mm"))
1004
- expected_width = count_1 * size_x + (count_1 - 1) * gap_x
1005
- expected_height = count_2 * size_y + (count_2 - 1) * gap_y
1006
- # Need to be adjusted to allow for centering
1007
- start_x = (
1008
- float(Length(self.context.device.view.width)) - expected_width
1009
- ) / 2
1010
- start_y = (
1011
- float(Length(self.context.device.view.height)) - expected_height
1012
- ) / 2
1013
- operation_branch = self.context.elements._tree.get(type="branch ops")
1014
- element_branch = self.context.elements._tree.get(type="branch elems")
1015
-
1016
- text_scale_x = min(1.0, size_y / float(Length("20mm")))
1017
- text_scale_y = min(1.0, size_x / float(Length("20mm")))
1018
-
1019
- # Make one op for text
1020
- if display_labels or display_values:
1021
- text_op_x = RasterOpNode()
1022
- text_op_x.color = Color("black")
1023
- text_op_x.label = "Descriptions X-Axis"
1024
- text_op_y = RasterOpNode()
1025
- text_op_y.color = Color("black")
1026
- text_op_y.label = "Descriptions Y-Axis"
1027
- operation_branch.add_node(text_op_x)
1028
- operation_branch.add_node(text_op_y)
1029
- if display_labels:
1030
- text_x = start_x + expected_width / 2
1031
- text_y = start_y - min(float(Length("10mm")), 3 * gap_y)
1032
- node = self.context.elements.elem_branch.add(
1033
- text=f"{param_name_1} [{param_unit_1}]",
1034
- matrix=Matrix(
1035
- f"translate({text_x}, {text_y}) scale({2 * max(text_scale_x, text_scale_y) * UNITS_PER_PIXEL})"
1036
- ),
1037
- anchor="middle",
1038
- fill=Color("black"),
1039
- type="elem text",
1040
- )
1041
- text_op_x.add_reference(node, 0)
1042
-
1043
- text_x = start_x - min(float(Length("10mm")), 3 * gap_x)
1044
- text_y = start_y + expected_height / 2
1045
- node = self.context.elements.elem_branch.add(
1046
- text=f"{param_name_2} [{param_unit_2}]",
1047
- matrix=Matrix(
1048
- f"translate({text_x}, {text_y}) scale({2 * max(text_scale_x, text_scale_y) * UNITS_PER_PIXEL})"
1049
- ),
1050
- anchor="middle",
1051
- fill=Color("black"),
1052
- type="elem text",
1053
- )
1054
- node.matrix.post_rotate(tau * 3 / 4, text_x, text_y)
1055
- node.modified()
1056
- text_op_y.add_reference(node, 0)
1057
-
1058
- p_value_1 = min_value_1
1059
-
1060
- xx = start_x
1061
- for idx1 in range(count_1):
1062
- pval1 = self.shortened(p_value_1, 3)
1063
-
1064
- p_value_2 = min_value_2
1065
- yy = start_y
1066
-
1067
- if display_values:
1068
- # Add a text above for each column
1069
- text_x = xx + 0.5 * size_x
1070
- text_y = yy - min(float(Length("5mm")), 1.5 * gap_y)
1071
- node = self.context.elements.elem_branch.add(
1072
- text=f"{pval1}",
1073
- matrix=Matrix(
1074
- f"translate({text_x}, {text_y}) scale({text_scale_x * UNITS_PER_PIXEL})"
1075
- ),
1076
- anchor="middle",
1077
- fill=Color("black"),
1078
- type="elem text",
1079
- )
1080
- # node.matrix.post_rotate(tau / 4, text_x, text_y)
1081
- node.modified()
1082
- text_op_x.add_reference(node, 0)
1083
-
1084
- for idx2 in range(count_2):
1085
- pval2 = self.shortened(p_value_2, 3)
1086
- s_lbl = f"{param_type_1}={pval1}{param_unit_1}"
1087
- s_lbl += f"- {param_type_2}={pval2}{param_unit_2}"
1088
- if display_values and idx1 == 0: # first row, so add a text above
1089
- text_x = xx - min(float(Length("5mm")), 1.5 * gap_x)
1090
- text_y = yy + 0.5 * size_y
1091
- node = self.context.elements.elem_branch.add(
1092
- text=f"{pval2}",
1093
- matrix=Matrix(
1094
- f"translate({text_x}, {text_y}) scale({text_scale_y * UNITS_PER_PIXEL})"
1095
- ),
1096
- anchor="middle",
1097
- fill=Color("black"),
1098
- type="elem text",
1099
- )
1100
- node.matrix.post_rotate(tau * 3 / 4, text_x, text_y)
1101
- text_op_y.add_reference(node, 0)
1102
- if optype == 0: # Cut
1103
- this_op = copy(self.default_op[optype])
1104
- usefill = False
1105
- elif optype == 1: # Engrave
1106
- this_op = copy(self.default_op[optype])
1107
- usefill = False
1108
- elif optype == 2: # Raster
1109
- this_op = copy(self.default_op[optype])
1110
- usefill = True
1111
- elif optype == 3: # Image
1112
- this_op = copy(self.default_op[optype])
1113
- usefill = False
1114
- elif optype == 4: # Hatch
1115
- this_op = copy(self.default_op[optype])
1116
- usefill = True
1117
- else:
1118
- return
1119
- this_op.label = s_lbl
1120
-
1121
- # Do we need to prep the op?
1122
- if param_prepper_1 is not None:
1123
- param_prepper_1(this_op)
1124
-
1125
- if param_keep_unit_1:
1126
- value = str(p_value_1) + param_unit_1
1127
- else:
1128
- value = p_value_1
1129
- if param_type_1 == "power" and self.use_percent():
1130
- value *= 10.0
1131
- if param_type_1 == "speed" and self.use_mm_min():
1132
- value /= 60.0
1133
- if hasattr(this_op, param_type_1):
1134
- # quick and dirty
1135
- if param_type_1 == "passes":
1136
- value = int(value)
1137
- if param_type_1 == "hatch_distance":
1138
- value = f"{value}mm"
1139
- setattr(this_op, param_type_1, value)
1140
- else: # Try setting
1141
- this_op.settings[param_type_1] = value
1142
-
1143
- # Do we need to prep the op?
1144
- if param_prepper_2 is not None:
1145
- param_prepper_2(this_op)
1146
-
1147
- if param_keep_unit_2:
1148
- value = str(p_value_2) + param_unit_2
1149
- else:
1150
- value = p_value_2
1151
- if param_type_2 == "power" and self.use_percent():
1152
- value *= 10.0
1153
- if param_type_2 == "speed" and self.use_mm_min():
1154
- value /= 60.0
1155
- if hasattr(this_op, param_type_2):
1156
- if param_type_2 == "passes":
1157
- value = int(value)
1158
- if param_type_2 == "hatch_distance":
1159
- value = f"{value}mm"
1160
- setattr(this_op, param_type_2, value)
1161
- else: # Try setting
1162
- this_op.settings[param_type_2] = value
1163
-
1164
- set_color = make_color(
1165
- idx1,
1166
- count_1,
1167
- idx2,
1168
- count_2,
1169
- color_aspect_1,
1170
- color_growing_1,
1171
- color_aspect_2,
1172
- color_growing_2,
1173
- )
1174
- this_op.color = set_color
1175
- # Add op to tree.
1176
- operation_branch.add_node(this_op)
1177
- # Now add a rectangle to the scene and assign it to the newly created op
1178
- if usefill:
1179
- fill_color = set_color
1180
- else:
1181
- fill_color = None
1182
- if shapetype == "image":
1183
- idx = self.combo_images.GetSelection() - 1
1184
- if 0 <= idx < len(self.images):
1185
- elemnode = copy(self.images[idx])
1186
- elemnode.matrix.post_translate(xx, yy)
1187
- elemnode.modified()
1188
- self.context.elements.elem_branch.add_node(elemnode)
1189
- elif shapetype == "rect":
1190
- elemnode = self.context.elements.elem_branch.add(
1191
- x=xx,
1192
- y=yy,
1193
- width=size_x,
1194
- height=size_y,
1195
- stroke=set_color,
1196
- fill=fill_color,
1197
- type="elem rect",
1198
- )
1199
- elif shapetype == "circle":
1200
- elemnode = self.context.elements.elem_branch.add(
1201
- cx=xx + size_x / 2,
1202
- cy=yy + size_y / 2,
1203
- rx=size_x / 2,
1204
- ry=size_y / 2,
1205
- stroke=set_color,
1206
- fill=fill_color,
1207
- type="elem ellipse",
1208
- )
1209
- elemnode.label = s_lbl
1210
- this_op.add_reference(elemnode, 0)
1211
- p_value_2 += delta_2
1212
- yy = yy + gap_y + size_y
1213
- p_value_1 += delta_1
1214
- xx = xx + gap_x + size_x
1215
-
1216
- # Read the parameters and user input
1217
- optype = self.combo_ops.GetSelection()
1218
- if optype < 0:
1219
- return
1220
- idx = self.combo_param_1.GetSelection()
1221
- if idx < 0:
1222
- return
1223
- # 0 = internal_attribute, 1 = secondary_attribute,
1224
- # 2 = Label, 3 = unit,
1225
- # 4 = keep_unit, 5 = needs_to_be_positive)
1226
- param_name_1 = self.parameters[idx][2]
1227
- param_type_1 = self.parameters[idx][0]
1228
- param_prepper_1 = self.parameters[idx][1]
1229
- if param_prepper_1 == "":
1230
- param_prepper_1 = None
1231
- param_unit_1 = self.parameters[idx][3]
1232
- param_keep_unit_1 = self.parameters[idx][4]
1233
- param_positive_1 = self.parameters[idx][5]
1234
-
1235
- idx = self.combo_param_2.GetSelection()
1236
- if idx < 0:
1237
- return
1238
- param_name_2 = self.parameters[idx][2]
1239
- param_type_2 = self.parameters[idx][0]
1240
- param_prepper_2 = self.parameters[idx][1]
1241
- if param_prepper_2 == "":
1242
- param_prepper_2 = None
1243
- param_unit_2 = self.parameters[idx][3]
1244
- param_keep_unit_2 = self.parameters[idx][4]
1245
- param_positive_2 = self.parameters[idx][5]
1246
- if param_type_1 == param_type_2:
1247
- return
1248
- if self.text_min_1.GetValue() == "":
1249
- return
1250
- try:
1251
- min_value_1 = float(self.text_min_1.GetValue())
1252
- except ValueError:
1253
- return
1254
- if self.text_min_2.GetValue() == "":
1255
- return
1256
- try:
1257
- min_value_2 = float(self.text_min_2.GetValue())
1258
- except ValueError:
1259
- return
1260
- if self.text_max_1.GetValue() == "":
1261
- return
1262
- try:
1263
- max_value_1 = float(self.text_max_1.GetValue())
1264
- except ValueError:
1265
- return
1266
- if self.text_max_2.GetValue() == "":
1267
- return
1268
- try:
1269
- max_value_2 = float(self.text_max_2.GetValue())
1270
- except ValueError:
1271
- return
1272
-
1273
- if param_unit_1 == "deg":
1274
- min_value_1 = Angle(self.text_min_1.GetValue()).degrees
1275
- max_value_1 = Angle(self.text_max_1.GetValue()).degrees
1276
- elif param_unit_1 == "ppi":
1277
- min_value_1 = max(min_value_1, 0)
1278
- max_value_1 = min(max_value_1, 1000)
1279
- elif param_unit_1 == "%":
1280
- min_value_1 = max(min_value_1, 0)
1281
- max_value_1 = min(max_value_1, 100)
1282
- else:
1283
- # > 0
1284
- if param_positive_1:
1285
- min_value_1 = max(min_value_1, 0)
1286
- max_value_1 = max(max_value_1, 0)
1287
-
1288
- if param_unit_2 == "deg":
1289
- min_value_2 = Angle(self.text_min_2.GetValue()).degrees
1290
- max_value_2 = Angle(self.text_max_2.GetValue()).degrees
1291
- elif param_unit_2 == "ppi":
1292
- min_value_2 = max(min_value_2, 0)
1293
- max_value_2 = min(max_value_2, 1000)
1294
- elif param_unit_1 == "%":
1295
- min_value_2 = max(min_value_2, 0)
1296
- max_value_2 = min(max_value_2, 100)
1297
- else:
1298
- # > 0
1299
- if param_positive_2:
1300
- min_value_2 = max(min_value_2, 0)
1301
- max_value_2 = max(max_value_2, 0)
1302
-
1303
- count_1 = int(self.spin_count_1.GetValue())
1304
- count_2 = int(self.spin_count_2.GetValue())
1305
- if count_1 > 1:
1306
- delta_1 = (max_value_1 - min_value_1) / (count_1 - 1)
1307
- else:
1308
- delta_1 = 0
1309
- if count_2 > 1:
1310
- delta_2 = (max_value_2 - min_value_2) / (count_2 - 1)
1311
- else:
1312
- delta_2 = 0
1313
- try:
1314
- dimension_1 = float(self.text_dim_1.GetValue())
1315
- except ValueError:
1316
- dimension_1 = -1
1317
- try:
1318
- dimension_2 = float(self.text_dim_2.GetValue())
1319
- except ValueError:
1320
- dimension_2 = -1
1321
- if dimension_1 <= 0:
1322
- dimension_1 = 5
1323
- if dimension_2 <= 0:
1324
- dimension_2 = 5
1325
-
1326
- try:
1327
- gap_1 = float(self.text_delta_1.GetValue())
1328
- except ValueError:
1329
- gap_1 = -1
1330
- try:
1331
- gap_2 = float(self.text_delta_2.GetValue())
1332
- except ValueError:
1333
- gap_2 = -1
1334
-
1335
- if gap_1 < 0:
1336
- gap_1 = 0
1337
- if gap_2 < 0:
1338
- gap_2 = 5
1339
-
1340
- message = _("This will delete all existing operations and elements") + "\n"
1341
- message += (
1342
- _("and replace them by the test-pattern! Are you really sure?") + "\n"
1343
- )
1344
- message += _("(Yes=Empty and Create, No=Keep existing)")
1345
- caption = _("Create Test-Pattern")
1346
- dlg = wx.MessageDialog(
1347
- self,
1348
- message,
1349
- caption,
1350
- wx.YES_NO | wx.CANCEL | wx.ICON_WARNING,
1351
- )
1352
- result = dlg.ShowModal()
1353
- dlg.Destroy()
1354
- if result == wx.ID_YES:
1355
- clear_all()
1356
- elif result == wx.ID_CANCEL:
1357
- return
1358
-
1359
- create_operations()
1360
-
1361
- self.context.signal("rebuild_tree")
1362
- self.context.signal("refresh_scene", "Scene")
1363
- self.save_settings()
1364
-
1365
- def setup_settings(self):
1366
- self.context.setting(int, "template_optype", 0)
1367
- self.context.setting(int, "template_param1", 0)
1368
- self.context.setting(int, "template_param2", 1)
1369
- self.context.setting(str, "template_min1", "")
1370
- self.context.setting(str, "template_max1", "")
1371
- self.context.setting(str, "template_min2", "")
1372
- self.context.setting(str, "template_max2", "")
1373
- self.context.setting(int, "template_count1", 5)
1374
- self.context.setting(int, "template_count2", 5)
1375
- self.context.setting(str, "template_dim_1", "10")
1376
- self.context.setting(str, "template_dim_2", "10")
1377
- self.context.setting(str, "template_gap_1", "5")
1378
- self.context.setting(str, "template_gap_2", "5")
1379
- self.context.setting(bool, "template_show_labels", True)
1380
- self.context.setting(bool, "template_show_values", True)
1381
- self.context.setting(int, "template_color1", 0)
1382
- self.context.setting(int, "template_color2", 2)
1383
- self.context.setting(bool, "template_coldir1", False)
1384
- self.context.setting(bool, "template_coldir2", False)
1385
-
1386
- def _set_settings(self, templatename):
1387
- info_field = (
1388
- self.context.template_show_values,
1389
- self.context.template_show_labels,
1390
- self.context.template_optype,
1391
- self.context.template_param1,
1392
- self.context.template_param2,
1393
- self.context.template_min1,
1394
- self.context.template_max1,
1395
- self.context.template_min2,
1396
- self.context.template_max2,
1397
- self.context.template_count1,
1398
- self.context.template_count2,
1399
- self.context.template_dim_1,
1400
- self.context.template_dim_2,
1401
- self.context.template_gap_1,
1402
- self.context.template_gap_2,
1403
- self.context.template_color1,
1404
- self.context.template_color2,
1405
- self.context.template_coldir1,
1406
- self.context.template_coldir2,
1407
- )
1408
- # print (f"Save data to {templatename}, infofield-len={len(info_field)}")
1409
- key = f"{templatename}"
1410
- self.storage.write_persistent("materialtest", key, info_field)
1411
- self.storage.write_configuration()
1412
-
1413
- def _get_settings(self, templatename):
1414
- key = f"{templatename}"
1415
- info_field = self.storage.read_persistent(tuple, "materialtest", key, None)
1416
- if (
1417
- info_field is not None
1418
- and isinstance(info_field, (tuple, list))
1419
- and len(info_field) == 19
1420
- ):
1421
- # print (f"Load data from {templatename}")
1422
- self.context.template_show_values = info_field[0]
1423
- self.context.template_show_labels = info_field[1]
1424
- self.context.template_optype = info_field[2]
1425
- self.context.template_param1 = info_field[3]
1426
- self.context.template_param2 = info_field[4]
1427
- self.context.template_min1 = info_field[5]
1428
- self.context.template_max1 = info_field[6]
1429
- self.context.template_min2 = info_field[7]
1430
- self.context.template_max2 = info_field[8]
1431
- self.context.template_count1 = info_field[9]
1432
- self.context.template_count2 = info_field[10]
1433
- self.context.template_dim_1 = info_field[11]
1434
- self.context.template_dim_2 = info_field[12]
1435
- self.context.template_gap_1 = info_field[13]
1436
- self.context.template_gap_2 = info_field[14]
1437
- self.context.template_color1 = info_field[15]
1438
- self.context.template_color2 = info_field[16]
1439
- self.context.template_coldir1 = info_field[17]
1440
- self.context.template_coldir2 = info_field[18]
1441
-
1442
- def save_settings(self, templatename=None):
1443
- self.context.template_show_values = self.check_values.GetValue()
1444
- self.context.template_show_labels = self.check_labels.GetValue()
1445
- self.context.template_optype = self.combo_ops.GetSelection()
1446
- self.context.template_param1 = self.combo_param_1.GetSelection()
1447
- self.context.template_param2 = self.combo_param_2.GetSelection()
1448
- self.context.template_min1 = self.text_min_1.GetValue()
1449
- self.context.template_max1 = self.text_max_1.GetValue()
1450
- self.context.template_min2 = self.text_min_2.GetValue()
1451
- self.context.template_max2 = self.text_max_2.GetValue()
1452
- self.context.template_count1 = self.spin_count_1.GetValue()
1453
- self.context.template_count2 = self.spin_count_2.GetValue()
1454
- self.context.template_dim_1 = self.text_dim_1.GetValue()
1455
- self.context.template_dim_2 = self.text_dim_2.GetValue()
1456
- self.context.template_gap_1 = self.text_delta_1.GetValue()
1457
- self.context.template_gap_2 = self.text_delta_2.GetValue()
1458
- self.context.template_color1 = self.combo_color_1.GetSelection()
1459
- self.context.template_color2 = self.combo_color_2.GetSelection()
1460
- self.context.template_coldir1 = self.check_color_direction_1.GetValue()
1461
- self.context.template_coldir2 = self.check_color_direction_2.GetValue()
1462
- if templatename:
1463
- # let's try to restore the settings
1464
- self._set_settings(templatename)
1465
-
1466
- def restore_settings(self, templatename=None):
1467
- if templatename:
1468
- # let's try to restore the settings
1469
- self._get_settings(templatename)
1470
- try:
1471
- self.check_color_direction_1.SetValue(self.context.template_coldir1)
1472
- self.check_color_direction_2.SetValue(self.context.template_coldir2)
1473
- self.combo_color_1.SetSelection(
1474
- min(self.context.template_color1, self.combo_color_1.GetCount() - 1)
1475
- )
1476
- self.combo_color_2.SetSelection(
1477
- min(self.context.template_color2, self.combo_color_2.GetCount() - 1)
1478
- )
1479
- self.check_values.SetValue(self.context.template_show_values)
1480
- self.check_labels.SetValue(self.context.template_show_labels)
1481
- self.combo_ops.SetSelection(
1482
- min(self.context.template_optype, self.combo_ops.GetCount() - 1)
1483
- )
1484
- self.combo_param_1.SetSelection(
1485
- min(self.context.template_param1, self.combo_param_1.GetCount() - 1)
1486
- )
1487
- self.combo_param_2.SetSelection(
1488
- min(self.context.template_param2, self.combo_param_2.GetCount() - 1)
1489
- )
1490
- self.text_min_1.SetValue(self.context.template_min1)
1491
- self.text_max_1.SetValue(self.context.template_max1)
1492
- self.text_min_2.SetValue(self.context.template_min2)
1493
- self.text_max_2.SetValue(self.context.template_max2)
1494
- self.spin_count_1.SetValue(self.context.template_count1)
1495
- self.spin_count_2.SetValue(self.context.template_count2)
1496
- self.text_dim_1.SetValue(self.context.template_dim_1)
1497
- self.text_dim_2.SetValue(self.context.template_dim_2)
1498
- self.text_delta_1.SetValue(self.context.template_gap_1)
1499
- self.text_delta_2.SetValue(self.context.template_gap_2)
1500
- except (AttributeError, ValueError):
1501
- pass
1502
-
1503
- def sync_fields(self):
1504
- # Repopulate combos
1505
- self.set_param_according_to_op(None)
1506
- # And then setting it back to the defaults...
1507
- self.combo_param_1.SetSelection(
1508
- min(self.context.template_param1, self.combo_param_1.GetCount() - 1)
1509
- )
1510
- # Make sure units appear properly
1511
- self.on_combo_1(None)
1512
- self.combo_param_2.SetSelection(
1513
- min(self.context.template_param2, self.combo_param_2.GetCount() - 1)
1514
- )
1515
- # Make sure units appear properly
1516
- self.on_combo_2(None)
1517
-
1518
- @signal_listener("activate;device")
1519
- def on_activate_device(self, origin, device):
1520
- self.set_param_according_to_op(None)
1521
-
1522
-
1523
- class TemplateTool(MWindow):
1524
- """
1525
- Material-/Parameter Test routines
1526
- """
1527
-
1528
- def __init__(self, *args, **kwds):
1529
- super().__init__(720, 750, submenu="Laser-Tools", *args, **kwds)
1530
-
1531
- self.storage = Settings(self.context.kernel.name, "templates.cfg")
1532
- self.storage.read_configuration()
1533
- self.panel_instances = list()
1534
- self.panel_template = TemplatePanel(
1535
- self,
1536
- wx.ID_ANY,
1537
- context=self.context,
1538
- storage=self.storage,
1539
- )
1540
-
1541
- self.panel_saveload = SaveLoadPanel(
1542
- self,
1543
- wx.ID_ANY,
1544
- context=self.context,
1545
- )
1546
-
1547
- self.notebook_main = aui.AuiNotebook(
1548
- self,
1549
- -1,
1550
- style=aui.AUI_NB_TAB_EXTERNAL_MOVE
1551
- | aui.AUI_NB_SCROLL_BUTTONS
1552
- | aui.AUI_NB_TAB_SPLIT
1553
- | aui.AUI_NB_TAB_MOVE,
1554
- )
1555
-
1556
- self.notebook_main.AddPage(self.panel_template, _("Generator"))
1557
-
1558
- self.panel_template.set_callback(self.set_node)
1559
- self.add_module_delegate(self.panel_template)
1560
-
1561
- self.notebook_main.AddPage(self.panel_saveload, _("Templates"))
1562
- self.panel_saveload.set_callback(self.callback_templates)
1563
- self.add_module_delegate(self.panel_saveload)
1564
-
1565
- _icon = wx.NullIcon
1566
- _icon.CopyFromBitmap(icons8_detective.GetBitmap())
1567
- self.SetIcon(_icon)
1568
- self.SetTitle(_("Parameter-Test"))
1569
-
1570
- def callback_templates(self, command, param):
1571
- # print (f"callback called with {command}, {param}")
1572
- if command == "load":
1573
- if param:
1574
- self.panel_template.restore_settings(param)
1575
- self.panel_template.sync_fields()
1576
- return True
1577
- elif command == "save":
1578
- if param:
1579
- self.panel_template.save_settings(param)
1580
- return True
1581
- elif command == "delete":
1582
- if param:
1583
- key = f"{param}"
1584
- self.storage.delete_persistent("materialtest", key)
1585
- self.storage.write_configuration()
1586
- return True
1587
- elif command == "get":
1588
- choices = []
1589
- for section in list(self.storage.keylist("materialtest")):
1590
- choices.append(section)
1591
- return choices
1592
-
1593
- return None
1594
-
1595
- def set_node(self, node):
1596
- def sort_priority(prop):
1597
- prop_sheet, node = prop
1598
- return (
1599
- getattr(prop_sheet, "priority")
1600
- if hasattr(prop_sheet, "priority")
1601
- else 0
1602
- )
1603
-
1604
- if node is None:
1605
- return
1606
- self.Freeze()
1607
- pages_to_instance = []
1608
- pages_in_node = []
1609
- found = False
1610
- for property_sheet in self.context.lookup_all(
1611
- f"property/{node.__class__.__name__}/.*"
1612
- ):
1613
- if not hasattr(property_sheet, "accepts") or property_sheet.accepts(node):
1614
- pages_in_node.append((property_sheet, node))
1615
- found = True
1616
- # If we did not have any hits and the node is a reference
1617
- # then we fall back to the master. So if in the future we
1618
- # would have a property panel dealing with reference-nodes
1619
- # then this would no longer apply.
1620
- if node.type == "reference" and not found:
1621
- snode = node.node
1622
- found = False
1623
- for property_sheet in self.context.lookup_all(
1624
- f"property/{snode.__class__.__name__}/.*"
1625
- ):
1626
- if not hasattr(property_sheet, "accepts") or property_sheet.accepts(
1627
- snode
1628
- ):
1629
- pages_in_node.append((property_sheet, snode))
1630
- found = True
1631
-
1632
- pages_in_node.sort(key=sort_priority, reverse=True)
1633
- pages_to_instance.extend(pages_in_node)
1634
-
1635
- for p in self.panel_instances:
1636
- try:
1637
- p.pane_hide()
1638
- except AttributeError:
1639
- pass
1640
- self.remove_module_delegate(p)
1641
-
1642
- # Delete all but the first and last page...
1643
- while self.notebook_main.GetPageCount() > 2:
1644
- self.notebook_main.DeletePage(1)
1645
- for prop_sheet, instance in pages_to_instance:
1646
- page_panel = prop_sheet(
1647
- self.notebook_main, wx.ID_ANY, context=self.context, node=instance
1648
- )
1649
- try:
1650
- name = prop_sheet.name
1651
- except AttributeError:
1652
- name = instance.__class__.__name__
1653
-
1654
- self.notebook_main.InsertPage(1, page_panel, _(name))
1655
- try:
1656
- page_panel.set_widgets(instance)
1657
- except AttributeError:
1658
- pass
1659
- self.add_module_delegate(page_panel)
1660
- self.panel_instances.append(page_panel)
1661
- try:
1662
- page_panel.pane_show()
1663
- except AttributeError:
1664
- pass
1665
- page_panel.Layout()
1666
- try:
1667
- page_panel.SetupScrolling()
1668
- except AttributeError:
1669
- pass
1670
-
1671
- self.Layout()
1672
- self.Thaw()
1673
-
1674
- def window_open(self):
1675
- pass
1676
-
1677
- def window_close(self):
1678
- for p in self.panel_instances:
1679
- try:
1680
- p.pane_hide()
1681
- except AttributeError:
1682
- pass
1683
- # We do not remove the delegates, they will detach with the closing of the module.
1684
- self.panel_instances.clear()
1685
-
1686
- @signal_listener("power_percent")
1687
- @signal_listener("speed_min")
1688
- @lookup_listener("service/device/active")
1689
- def on_device_update(self, *args):
1690
- self.panel_template.on_device_update()
1691
-
1692
- @staticmethod
1693
- def submenu():
1694
- return "Laser-Tools", "Parameter-Test"
1
+ from copy import copy
2
+ from math import tau
3
+
4
+ import wx
5
+ from wx import aui
6
+
7
+ from meerk40t.core.node.effect_hatch import HatchEffectNode
8
+ from meerk40t.core.node.op_cut import CutOpNode
9
+ from meerk40t.core.node.op_engrave import EngraveOpNode
10
+ from meerk40t.core.node.op_image import ImageOpNode
11
+ from meerk40t.core.node.op_raster import RasterOpNode
12
+ from meerk40t.core.units import UNITS_PER_PIXEL, Angle, Length
13
+ from meerk40t.gui.icons import get_default_icon_size, icons8_detective
14
+ from meerk40t.gui.mwindow import MWindow
15
+ from meerk40t.gui.wxutils import (
16
+ StaticBoxSizer,
17
+ TextCtrl,
18
+ dip_size,
19
+ wxButton,
20
+ wxCheckBox,
21
+ wxComboBox,
22
+ wxListBox,
23
+ wxStaticText,
24
+ )
25
+ from meerk40t.kernel import Settings, lookup_listener, signal_listener
26
+ from meerk40t.svgelements import Color, Matrix
27
+
28
+ _ = wx.GetTranslation
29
+
30
+
31
+ class SaveLoadPanel(wx.Panel):
32
+ """
33
+ Provides the scaffold for saving and loading of parameter sets.
34
+ Does not know a lot about the underlying structure of data as it
35
+ blindly interacts with the parent via the callback routine
36
+ (could hence work as a generic way to save / load data)
37
+ """
38
+
39
+ def __init__(self, *args, context=None, **kwds):
40
+ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
41
+ wx.Panel.__init__(self, *args, **kwds)
42
+ self.context = context
43
+ self.context.themes.set_window_colors(self)
44
+ self.callback = None
45
+ sizer_main = wx.BoxSizer(wx.VERTICAL)
46
+ self.SetSizer(sizer_main)
47
+ sizer_name = wx.BoxSizer(wx.HORIZONTAL)
48
+ lbl_info = wxStaticText(self, wx.ID_ANY, _("Template-Name"))
49
+ self.txt_name = TextCtrl(self, wx.ID_ANY, "")
50
+ self.btn_save = wxButton(self, wx.ID_ANY, _("Save"))
51
+ self.btn_load = wxButton(self, wx.ID_ANY, _("Load"))
52
+ self.btn_delete = wxButton(self, wx.ID_ANY, _("Delete"))
53
+ self.btn_load.Enable(False)
54
+ self.btn_save.Enable(False)
55
+ self.btn_delete.Enable(False)
56
+ sizer_name.Add(lbl_info, 0, wx.ALIGN_CENTER_VERTICAL, 0)
57
+ sizer_name.Add(self.txt_name, 1, wx.ALIGN_CENTER_VERTICAL, 0)
58
+ sizer_name.Add(self.btn_save, 0, wx.EXPAND, 0)
59
+ sizer_name.Add(self.btn_load, 0, wx.EXPAND, 0)
60
+ sizer_name.Add(self.btn_delete, 0, wx.EXPAND, 0)
61
+
62
+ self.choices = []
63
+ self.list_slots = wxListBox(
64
+ self, wx.ID_ANY, choices=self.choices, style=wx.LB_SINGLE
65
+ )
66
+ self.list_slots.SetToolTip(_("Select an entry to reload"))
67
+ sizer_main.Add(sizer_name, 0, wx.EXPAND, 0)
68
+ sizer_main.Add(self.list_slots, 1, wx.EXPAND, 0)
69
+ self.Layout()
70
+ self.Bind(wx.EVT_TEXT, self.on_text_change, self.txt_name)
71
+ self.Bind(wx.EVT_BUTTON, self.on_btn_load, self.btn_load)
72
+ self.Bind(wx.EVT_BUTTON, self.on_btn_save, self.btn_save)
73
+ self.Bind(wx.EVT_BUTTON, self.on_btn_delete, self.btn_delete)
74
+ self.list_slots.Bind(wx.EVT_LISTBOX, self.on_listbox_click)
75
+ self.list_slots.Bind(wx.EVT_LISTBOX_DCLICK, self.on_listbox_double_click)
76
+
77
+ def set_callback(self, routine):
78
+ self.callback = routine
79
+ self.fill_choices("")
80
+
81
+ def standardize(self, text):
82
+ text = text.lower()
83
+ for invalid in (
84
+ "=",
85
+ ":",
86
+ ):
87
+ text = text.replace(invalid, "_")
88
+ return text
89
+
90
+ def fill_choices(self, txt):
91
+ self.choices = []
92
+ if self.callback is not None:
93
+ self.choices = self.callback("get", "")
94
+
95
+ self.list_slots.Clear()
96
+ self.list_slots.SetItems(self.choices)
97
+ if txt:
98
+ try:
99
+ idx = self.choices.index(txt)
100
+ except ValueError:
101
+ idx = -1
102
+ if idx >= 0:
103
+ self.list_slots.SetSelection(idx)
104
+ self.list_slots.Refresh()
105
+
106
+ def on_text_change(self, event):
107
+ info = self.txt_name.GetValue()
108
+ flag1 = False
109
+ flag2 = False
110
+ if info:
111
+ info = self.standardize(info)
112
+ flag2 = True
113
+ try:
114
+ idx = self.choices.index(info)
115
+ except ValueError:
116
+ idx = -1
117
+ flag1 = idx >= 0
118
+ self.btn_load.Enable(flag1)
119
+ self.btn_delete.Enable(flag1)
120
+ self.btn_save.Enable(flag2)
121
+
122
+ def on_btn_load(self, event):
123
+ info = self.txt_name.GetValue()
124
+ if self.callback is None or not info:
125
+ return
126
+ info = self.standardize(info)
127
+ __ = self.callback("load", info)
128
+
129
+ def on_btn_delete(self, event):
130
+ info = self.txt_name.GetValue()
131
+ if self.callback is None or not info:
132
+ return
133
+ info = self.standardize(info)
134
+ __ = self.callback("delete", info)
135
+ self.fill_choices("")
136
+
137
+ def on_btn_save(self, event):
138
+ info = self.txt_name.GetValue()
139
+ if self.callback is None or not info:
140
+ return
141
+ info = self.standardize(info)
142
+ __ = self.callback("save", info)
143
+ self.fill_choices(info)
144
+ self.on_text_change(None)
145
+
146
+ def on_listbox_click(self, event):
147
+ idx = self.list_slots.GetSelection()
148
+ # print (f"Click with {idx}")
149
+ if idx >= 0:
150
+ info = self.choices[idx]
151
+ self.txt_name.SetValue(info)
152
+ self.on_text_change(None)
153
+
154
+ def on_listbox_double_click(self, event):
155
+ idx = self.list_slots.GetSelection()
156
+ # print (f"DClick with {idx}")
157
+ if idx >= 0:
158
+ info = self.choices[idx]
159
+ self.txt_name.SetValue(info)
160
+ self.on_btn_load(None)
161
+ self.on_text_change(None)
162
+
163
+
164
+ class TemplatePanel(wx.Panel):
165
+ """
166
+ Responsible for the generation of testpatterns and the user interface
167
+ params:
168
+ context - the current context
169
+ storage - an instance of kernel.Settings to store/load parameter sets
170
+ """
171
+
172
+ def __init__(self, *args, context=None, storage=None, **kwds):
173
+ def size_it(ctrl, value):
174
+ ctrl.SetMaxSize(dip_size(self, int(value), -1))
175
+ ctrl.SetMinSize(dip_size(self, int(value * 0.75), -1))
176
+ ctrl.SetSize(dip_size(self, value, -1))
177
+
178
+ # begin wxGlade: clsLasertools.__init__
179
+ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
180
+ wx.Panel.__init__(self, *args, **kwds)
181
+ self.context = context
182
+ self.context.themes.set_window_colors(self)
183
+ self.SetHelpText("testpattern")
184
+ self.storage = storage
185
+ self.callback = None
186
+ self.current_op = None
187
+ opchoices = [_("Cut"), _("Engrave"), _("Raster"), _("Image"), _("Hatch")]
188
+ # Setup 5 Op nodes - they aren't saved yet
189
+ self.default_op = []
190
+ # A tuple defining whether a free color-selection scheme is allowed, linked to default_op
191
+ self.color_scheme_free = []
192
+ self.default_op.append(CutOpNode())
193
+ self.color_scheme_free.append(True)
194
+ self.default_op.append(EngraveOpNode())
195
+ self.color_scheme_free.append(True)
196
+ self.default_op.append(RasterOpNode())
197
+ self.color_scheme_free.append(False)
198
+ self.default_op.append(ImageOpNode())
199
+ self.color_scheme_free.append(True)
200
+ op = EngraveOpNode()
201
+ self.default_op.append(op)
202
+ self.color_scheme_free.append(True)
203
+
204
+ self.use_image = [False] * len(self.default_op)
205
+ self.use_image[3] = True
206
+
207
+ self._freecolor = True
208
+
209
+ self.parameters = []
210
+ color_choices = [_("Red"), _("Green"), _("Blue")]
211
+
212
+ LABEL_WIDTH = 115
213
+
214
+ self.combo_ops = wxComboBox(
215
+ self, id=wx.ID_ANY, choices=opchoices, style=wx.CB_DROPDOWN | wx.CB_READONLY
216
+ )
217
+ self.images = []
218
+ self.image_labels = []
219
+ self.image_labels.append(_("Choose image..."))
220
+
221
+ for node in self.context.elements.elems():
222
+ if node.type == "elem image":
223
+ imagenode = copy(node)
224
+ bb = imagenode.bounds
225
+ if bb is not None:
226
+ # Put it back on origin
227
+ imagenode.matrix.post_translate(-bb[0], -bb[1])
228
+ self.images.append(imagenode)
229
+ w, h = imagenode.active_image.size
230
+ label = f"{w} x {h} Pixel"
231
+ if node.label:
232
+ label += "(" + node.display_label() + ")"
233
+ self.image_labels.append(label)
234
+
235
+ self.combo_images = wxComboBox(
236
+ self,
237
+ id=wx.ID_ANY,
238
+ choices=self.image_labels,
239
+ style=wx.CB_DROPDOWN | wx.CB_READONLY,
240
+ )
241
+ self.combo_images.SetToolTip(
242
+ _(
243
+ "Choose from one of the existing images on your canvas to use as the test template"
244
+ )
245
+ )
246
+ self.combo_images.SetSelection(0)
247
+ self.check_labels = wxCheckBox(self, wx.ID_ANY, _("Labels"))
248
+ self.check_values = wxCheckBox(self, wx.ID_ANY, _("Values"))
249
+
250
+ self.combo_param_1 = wxComboBox(
251
+ self, id=wx.ID_ANY, style=wx.CB_DROPDOWN | wx.CB_READONLY
252
+ )
253
+ self.spin_count_1 = wx.SpinCtrl(self, wx.ID_ANY, initial=5, min=1, max=100)
254
+ self.text_min_1 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
255
+ self.text_max_1 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
256
+ self.text_dim_1 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
257
+ self.text_dim_1.set_range(0, 50)
258
+ self.text_delta_1 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
259
+ self.text_delta_1.set_range(0, 50)
260
+ self.unit_param_1a = wxStaticText(self, wx.ID_ANY, "")
261
+ self.unit_param_1b = wxStaticText(self, wx.ID_ANY, "")
262
+
263
+ self.combo_color_1 = wxComboBox(
264
+ self,
265
+ wx.ID_ANY,
266
+ choices=color_choices,
267
+ style=wx.CB_DROPDOWN | wx.CB_READONLY,
268
+ )
269
+ self.check_color_direction_1 = wxCheckBox(self, wx.ID_ANY, _("Growing"))
270
+
271
+ self.combo_param_2 = wxComboBox(
272
+ self, id=wx.ID_ANY, style=wx.CB_DROPDOWN | wx.CB_READONLY
273
+ )
274
+ self.spin_count_2 = wx.SpinCtrl(self, wx.ID_ANY, initial=5, min=1, max=100)
275
+ self.text_min_2 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
276
+ self.text_max_2 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
277
+ self.text_dim_2 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
278
+ self.text_dim_2.set_range(0, 50)
279
+ self.text_delta_2 = TextCtrl(self, wx.ID_ANY, limited=True, check="float")
280
+ self.text_delta_2.set_range(0, 50)
281
+ self.unit_param_2a = wxStaticText(self, wx.ID_ANY, "")
282
+ self.unit_param_2b = wxStaticText(self, wx.ID_ANY, "")
283
+
284
+ self.combo_color_2 = wxComboBox(
285
+ self,
286
+ wx.ID_ANY,
287
+ choices=color_choices,
288
+ style=wx.CB_DROPDOWN | wx.CB_READONLY,
289
+ )
290
+ self.check_color_direction_2 = wxCheckBox(self, wx.ID_ANY, _("Growing"))
291
+
292
+ self.button_create = wxButton(self, wx.ID_ANY, _("Create Pattern"))
293
+ self.button_create.SetBitmap(
294
+ icons8_detective.GetBitmap(resize=0.5 * get_default_icon_size(self.context))
295
+ )
296
+
297
+ sizer_main = wx.BoxSizer(wx.VERTICAL)
298
+ sizer_param_optype = wx.BoxSizer(wx.HORIZONTAL)
299
+
300
+ self.sizer_param_op = StaticBoxSizer(
301
+ self, wx.ID_ANY, _("Operation to test"), wx.VERTICAL
302
+ )
303
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Operation:"))
304
+ size_it(mylbl, LABEL_WIDTH)
305
+ h1 = wx.BoxSizer(wx.HORIZONTAL)
306
+ h1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
307
+ h1.Add(self.combo_ops, 1, wx.EXPAND, 0)
308
+ self.sizer_param_op.Add(h1, 0, wx.EXPAND, 0)
309
+ self.sizer_param_op.Add(self.combo_images, 0, wx.EXPAND, 0)
310
+
311
+ sizer_param_check = StaticBoxSizer(
312
+ self, wx.ID_ANY, _("Show Labels / Values"), wx.HORIZONTAL
313
+ )
314
+ sizer_param_check.Add(self.check_labels, 1, wx.ALIGN_CENTER_VERTICAL, 0)
315
+ sizer_param_check.Add(self.check_values, 1, wx.ALIGN_CENTER_VERTICAL, 0)
316
+
317
+ sizer_param_optype.Add(self.sizer_param_op, 1, wx.EXPAND, 0)
318
+ sizer_param_optype.Add(sizer_param_check, 1, wx.EXPAND, 0)
319
+
320
+ sizer_param_xy = wx.BoxSizer(wx.HORIZONTAL)
321
+ sizer_param_x = StaticBoxSizer(
322
+ self, wx.ID_ANY, _("First parameter (X-Axis)"), wx.VERTICAL
323
+ )
324
+
325
+ hline_param_1 = wx.BoxSizer(wx.HORIZONTAL)
326
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Parameter:"))
327
+ size_it(mylbl, LABEL_WIDTH)
328
+ hline_param_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
329
+ hline_param_1.Add(self.combo_param_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
330
+
331
+ hline_count_1 = wx.BoxSizer(wx.HORIZONTAL)
332
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Count:"))
333
+ size_it(mylbl, LABEL_WIDTH)
334
+ self.info_delta_1 = wxStaticText(self, wx.ID_ANY, "")
335
+
336
+ hline_count_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
337
+ hline_count_1.Add(self.spin_count_1, 0, wx.ALIGN_CENTER_VERTICAL, 0)
338
+ hline_count_1.Add(self.info_delta_1, 0, wx.ALIGN_CENTER_VERTICAL, 0)
339
+
340
+ hline_min_1 = wx.BoxSizer(wx.HORIZONTAL)
341
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Minimum:"))
342
+ size_it(mylbl, LABEL_WIDTH)
343
+ hline_min_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
344
+ hline_min_1.Add(self.text_min_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
345
+ hline_min_1.Add(self.unit_param_1a, 0, wx.ALIGN_CENTER_VERTICAL, 0)
346
+
347
+ hline_max_1 = wx.BoxSizer(wx.HORIZONTAL)
348
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Maximum:"))
349
+ size_it(mylbl, LABEL_WIDTH)
350
+ hline_max_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
351
+ hline_max_1.Add(self.text_max_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
352
+ hline_max_1.Add(self.unit_param_1b, 0, wx.ALIGN_CENTER_VERTICAL, 0)
353
+
354
+ hline_dim_1 = wx.BoxSizer(wx.HORIZONTAL)
355
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Width:"))
356
+ size_it(mylbl, LABEL_WIDTH)
357
+ hline_dim_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
358
+ hline_dim_1.Add(self.text_dim_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
359
+ mylbl = wxStaticText(self, wx.ID_ANY, "mm")
360
+ hline_dim_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
361
+
362
+ hline_delta_1 = wx.BoxSizer(wx.HORIZONTAL)
363
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Delta:"))
364
+ size_it(mylbl, LABEL_WIDTH)
365
+ hline_delta_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
366
+ hline_delta_1.Add(self.text_delta_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
367
+ mylbl = wxStaticText(self, wx.ID_ANY, "mm")
368
+ hline_delta_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
369
+
370
+ hline_color_1 = wx.BoxSizer(wx.HORIZONTAL)
371
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Color:"))
372
+ size_it(mylbl, LABEL_WIDTH)
373
+ hline_color_1.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
374
+ hline_color_1.Add(self.combo_color_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
375
+ hline_color_1.Add(self.check_color_direction_1, 1, wx.ALIGN_CENTER_VERTICAL, 0)
376
+
377
+ sizer_param_x.Add(hline_param_1, 0, wx.EXPAND, 0)
378
+ sizer_param_x.Add(hline_count_1, 0, wx.EXPAND, 0)
379
+ sizer_param_x.Add(hline_min_1, 0, wx.EXPAND, 0)
380
+ sizer_param_x.Add(hline_max_1, 0, wx.EXPAND, 0)
381
+ sizer_param_x.Add(hline_dim_1, 0, wx.EXPAND, 0)
382
+ sizer_param_x.Add(hline_delta_1, 0, wx.EXPAND, 0)
383
+ sizer_param_x.Add(hline_color_1, 0, wx.EXPAND, 0)
384
+
385
+ sizer_param_y = StaticBoxSizer(
386
+ self, wx.ID_ANY, _("Second parameter (Y-Axis)"), wx.VERTICAL
387
+ )
388
+
389
+ hline_param_2 = wx.BoxSizer(wx.HORIZONTAL)
390
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Parameter:"))
391
+ size_it(mylbl, LABEL_WIDTH)
392
+ hline_param_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
393
+ hline_param_2.Add(self.combo_param_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
394
+
395
+ hline_count_2 = wx.BoxSizer(wx.HORIZONTAL)
396
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Count:"))
397
+ size_it(mylbl, LABEL_WIDTH)
398
+ self.info_delta_2 = wxStaticText(self, wx.ID_ANY, "")
399
+ hline_count_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
400
+ hline_count_2.Add(self.spin_count_2, 0, wx.ALIGN_CENTER_VERTICAL, 0)
401
+ hline_count_2.Add(self.info_delta_2, 0, wx.ALIGN_CENTER_VERTICAL, 0)
402
+
403
+ hline_min_2 = wx.BoxSizer(wx.HORIZONTAL)
404
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Minimum:"))
405
+ size_it(mylbl, LABEL_WIDTH)
406
+ hline_min_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
407
+ hline_min_2.Add(self.text_min_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
408
+ hline_min_2.Add(self.unit_param_2a, 0, wx.ALIGN_CENTER_VERTICAL, 0)
409
+
410
+ hline_max_2 = wx.BoxSizer(wx.HORIZONTAL)
411
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Maximum:"))
412
+ size_it(mylbl, LABEL_WIDTH)
413
+ hline_max_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
414
+ hline_max_2.Add(self.text_max_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
415
+ hline_max_2.Add(self.unit_param_2b, 0, wx.ALIGN_CENTER_VERTICAL, 0)
416
+
417
+ hline_dim_2 = wx.BoxSizer(wx.HORIZONTAL)
418
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Height:"))
419
+ size_it(mylbl, LABEL_WIDTH)
420
+ hline_dim_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
421
+ hline_dim_2.Add(self.text_dim_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
422
+ mylbl = wxStaticText(self, wx.ID_ANY, "mm")
423
+ hline_dim_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
424
+
425
+ hline_delta_2 = wx.BoxSizer(wx.HORIZONTAL)
426
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Delta:"))
427
+ size_it(mylbl, LABEL_WIDTH)
428
+ hline_delta_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
429
+ hline_delta_2.Add(self.text_delta_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
430
+ mylbl = wxStaticText(self, wx.ID_ANY, "mm")
431
+ hline_delta_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
432
+
433
+ hline_color_2 = wx.BoxSizer(wx.HORIZONTAL)
434
+ mylbl = wxStaticText(self, wx.ID_ANY, _("Color:"))
435
+ size_it(mylbl, LABEL_WIDTH)
436
+ hline_color_2.Add(mylbl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
437
+ hline_color_2.Add(self.combo_color_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
438
+ hline_color_2.Add(self.check_color_direction_2, 1, wx.ALIGN_CENTER_VERTICAL, 0)
439
+
440
+ sizer_param_y.Add(hline_param_2, 0, wx.EXPAND, 0)
441
+ sizer_param_y.Add(hline_count_2, 0, wx.EXPAND, 0)
442
+ sizer_param_y.Add(hline_min_2, 0, wx.EXPAND, 0)
443
+ sizer_param_y.Add(hline_max_2, 0, wx.EXPAND, 0)
444
+ sizer_param_y.Add(hline_dim_2, 0, wx.EXPAND, 0)
445
+ sizer_param_y.Add(hline_delta_2, 0, wx.EXPAND, 0)
446
+ sizer_param_y.Add(hline_color_2, 0, wx.EXPAND, 0)
447
+
448
+ sizer_param_xy.Add(sizer_param_x, 1, wx.EXPAND, 0)
449
+ sizer_param_xy.Add(sizer_param_y, 1, wx.EXPAND, 0)
450
+
451
+ sizer_main.Add(sizer_param_optype, 0, wx.EXPAND, 0)
452
+ sizer_main.Add(sizer_param_xy, 0, wx.EXPAND, 0)
453
+ sizer_main.Add(self.button_create, 0, wx.EXPAND, 0)
454
+
455
+ sizer_info = StaticBoxSizer(self, wx.ID_ANY, _("How to use it"), wx.VERTICAL)
456
+ infomsg = _("To provide the best burning results, the parameters of operations")
457
+ infomsg += " " + _(
458
+ "need to be adjusted according to *YOUR* laser and the specific material"
459
+ )
460
+ infomsg += " " + _(
461
+ "you want to work with (e.g. one batch of poplar plywood from one supplier"
462
+ )
463
+ infomsg += " " + _(
464
+ "may respond completely different to a batch of another supplier despite"
465
+ )
466
+ infomsg += " " + _("having the very same specifications on paper).")
467
+ infomsg += "\n" + _(
468
+ "E.g. for a regular CO2 laser you want to optimize the burn speed"
469
+ )
470
+ infomsg += " " + _(
471
+ "for a given power to reduce burn marks or decrease execution time."
472
+ )
473
+ infomsg += "\n" + _(
474
+ "Meerk40t simplifies this task to find out the optimal settings"
475
+ )
476
+ infomsg += " " + _(
477
+ "by creating a testpattern that varies two different parameters."
478
+ )
479
+
480
+ info_label = TextCtrl(
481
+ self, wx.ID_ANY, value=infomsg, style=wx.TE_READONLY | wx.TE_MULTILINE
482
+ )
483
+ info_label.SetBackgroundColour(self.GetBackgroundColour())
484
+ sizer_info.Add(info_label, 1, wx.EXPAND, 0)
485
+ sizer_main.Add(sizer_info, 1, wx.EXPAND, 0)
486
+
487
+ self.button_create.SetToolTip(_("Create a grid with your values"))
488
+ s = _("Operation type for which the testpattern will be generated")
489
+ s += "\n" + _(
490
+ "You can define the common parameters for this operation in the other tabs on top of this window"
491
+ )
492
+ self.combo_ops.SetToolTip(s)
493
+ self.combo_param_1.SetToolTip(
494
+ _("Choose the first parameter that you want to be tested")
495
+ )
496
+ self.combo_param_2.SetToolTip(
497
+ _("Choose the second parameter that you want to be tested")
498
+ )
499
+ self.combo_color_1.SetToolTip(
500
+ _(
501
+ "Choose the color aspect for the first parameter. NB: the colors for both parameters will be combined"
502
+ )
503
+ )
504
+ self.combo_color_2.SetToolTip(
505
+ _(
506
+ "Choose the color aspect for the second parameter. NB: the colors for both parameters will be combined"
507
+ )
508
+ )
509
+ self.check_color_direction_1.SetToolTip(
510
+ _(
511
+ "If checked, then the color aspect will grow from min to max values, if not then shrink"
512
+ )
513
+ )
514
+ self.check_color_direction_2.SetToolTip(
515
+ _(
516
+ "If checked, then the color aspect will grow from min to max values, if not then shrink"
517
+ )
518
+ )
519
+ self.spin_count_1.SetToolTip(
520
+ _(
521
+ "Define how many values you want to test in the interval between min and max"
522
+ )
523
+ )
524
+ self.spin_count_2.SetToolTip(
525
+ _(
526
+ "Define how many values you want to test in the interval between min and max"
527
+ )
528
+ )
529
+ self.check_labels.SetToolTip(
530
+ _("Will create a descriptive label at the sides of the grid")
531
+ )
532
+ self.check_values.SetToolTip(
533
+ _("Will create the corresponding values as labels at the sides of the grid")
534
+ )
535
+ self.text_min_1.SetToolTip(_("Minimum value for 1st parameter"))
536
+ self.text_max_1.SetToolTip(_("Maximum value for 1st parameter"))
537
+ self.text_min_2.SetToolTip(_("Minimum value for 2nd parameter"))
538
+ self.text_max_2.SetToolTip(_("Maximum value for 2nd parameter"))
539
+ self.text_dim_1.SetToolTip(_("Width of the to be created pattern"))
540
+ self.text_dim_2.SetToolTip(_("Height of the to be created pattern"))
541
+ self.text_delta_1.SetToolTip(_("Horizontal gap between patterns"))
542
+ self.text_delta_2.SetToolTip(_("Vertical gap between patterns"))
543
+
544
+ self.button_create.Bind(wx.EVT_BUTTON, self.on_button_create_pattern)
545
+ self.combo_ops.Bind(wx.EVT_COMBOBOX, self.set_param_according_to_op)
546
+ self.text_min_1.Bind(wx.EVT_TEXT, self.validate_input)
547
+ self.text_max_1.Bind(wx.EVT_TEXT, self.validate_input)
548
+ self.text_min_2.Bind(wx.EVT_TEXT, self.validate_input)
549
+ self.text_max_2.Bind(wx.EVT_TEXT, self.validate_input)
550
+ self.text_dim_1.Bind(wx.EVT_TEXT, self.validate_input)
551
+ self.text_delta_1.Bind(wx.EVT_TEXT, self.validate_input)
552
+ self.text_dim_2.Bind(wx.EVT_TEXT, self.validate_input)
553
+ self.text_delta_2.Bind(wx.EVT_TEXT, self.validate_input)
554
+ self.combo_param_1.Bind(wx.EVT_COMBOBOX, self.on_combo_1)
555
+ self.combo_param_2.Bind(wx.EVT_COMBOBOX, self.on_combo_2)
556
+ self.combo_images.Bind(wx.EVT_COMBOBOX, self.on_combo_image)
557
+ self.spin_count_1.Bind(wx.EVT_SPINCTRL, self.validate_input)
558
+ self.spin_count_2.Bind(wx.EVT_SPINCTRL, self.validate_input)
559
+
560
+ self.SetSizer(sizer_main)
561
+ self.Layout()
562
+ self.setup_settings()
563
+ self.combo_ops.SetSelection(0)
564
+ self.restore_settings()
565
+ self.sync_fields()
566
+
567
+ def shortened(self, value, digits):
568
+ result = str(round(value, digits))
569
+ if "." in result:
570
+ while result.endswith("0"):
571
+ result = result[:-1]
572
+ if result.endswith("."):
573
+ if result == ".":
574
+ result = "0"
575
+ else:
576
+ result = result[:-1]
577
+ return result
578
+
579
+ def on_combo_image(self, event):
580
+ op = self.combo_ops.GetSelection()
581
+ if op != 3: # No Image?
582
+ return
583
+ idx = self.combo_images.GetSelection() - 1
584
+ if 0 <= idx < len(self.images):
585
+ bb = self.images[idx].bounds
586
+ if bb is not None:
587
+ wd = Length(amount=bb[2] - bb[0], preferred_units="mm")
588
+ ht = Length(amount=bb[3] - bb[1], preferred_units="mm")
589
+ self.text_dim_1.SetValue(f"{wd.mm:.1f}")
590
+ self.text_dim_2.SetValue(f"{ht.mm:.1f}")
591
+
592
+ def set_callback(self, routine):
593
+ self.callback = routine
594
+ idx = self.combo_ops.GetSelection()
595
+ if self.callback is not None and idx >= 0:
596
+ self.callback(self.default_op[idx])
597
+
598
+ def use_percent(self):
599
+ self.context.device.setting(bool, "use_percent_for_power_display", False)
600
+ return self.context.device.use_percent_for_power_display
601
+
602
+ def use_mm_min(self):
603
+ self.context.device.setting(bool, "use_mm_min_for_speed_display", False)
604
+ return self.context.device.use_mm_min_for_speed_display
605
+
606
+ def set_param_according_to_op(self, event):
607
+ def preset_image_dpi(node=None):
608
+ # Will be called ahead of the modification of the 'op image' dpi variable
609
+ node.overrule_dpi = True
610
+
611
+ def preset_passes(node=None):
612
+ # Will be called ahead of the modification of the passes variable
613
+ node.passes_custom = True
614
+
615
+ def preset_balor_wobble(node=None):
616
+ # Will be called ahead of the modification of a wobble variable
617
+ # to copy the device defaults
618
+ if node is None or "balor" not in self.context.device.path:
619
+ return
620
+ node.settings["wobble_enabled"] = True
621
+
622
+ def preset_balor_rapid(node=None):
623
+ # Will be called ahead of the modification of a rapid variable
624
+ # to copy the device defaults
625
+ if node is None or "balor" not in self.context.device.path:
626
+ return
627
+ node.settings["rapid_enabled"] = True
628
+
629
+ def preset_balor_pulse(node=None):
630
+ # Will be called ahead of the modification of a pulse variable
631
+ # to copy the device defaults
632
+ if node is None or "balor" not in self.context.device.path:
633
+ return
634
+ node.settings["pulse_width_enabled"] = True
635
+
636
+ def preset_balor_timings(node=None):
637
+ # Will be called ahead of the modification of a timing variable
638
+ # to copy the device defaults
639
+ if node is None or "balor" not in self.context.device.path:
640
+ return
641
+ if not node.settings["timing_enabled"]:
642
+ node.settings["timing_enabled"] = True
643
+ node.settings["delay_laser_on"] = self.context.device.delay_laser_on
644
+ node.settings["delay_laser_off"] = self.context.device.delay_laser_off
645
+ node.settings["delay_polygon"] = self.context.device.delay_polygon
646
+
647
+ opidx = self.combo_ops.GetSelection()
648
+ if self.current_op == opidx:
649
+ return
650
+ self.current_op = opidx
651
+
652
+ busy = wx.BusyCursor()
653
+ self.Freeze()
654
+ if opidx < 0:
655
+ opnode = None
656
+ self._freecolor = True
657
+ self.combo_images.Show(False)
658
+ self.text_dim_1.Enable(True)
659
+ self.text_dim_2.Enable(True)
660
+ else:
661
+ opnode = self.default_op[opidx]
662
+ self._freecolor = self.color_scheme_free[opidx]
663
+ self.combo_images.Show(self.use_image[opidx])
664
+ self.text_dim_1.Enable(not self.use_image[opidx])
665
+ self.text_dim_2.Enable(not self.use_image[opidx])
666
+ self.sizer_param_op.Layout()
667
+ if self.callback is not None:
668
+ self.callback(opnode)
669
+ self.combo_color_1.Enable(self._freecolor)
670
+ self.combo_color_2.Enable(self._freecolor)
671
+ self.check_color_direction_1.Enable(self._freecolor)
672
+ self.check_color_direction_2.Enable(self._freecolor)
673
+
674
+ # (internal_attribute, secondary_attribute, Label, unit, keep_unit, needs_to_be_positive)
675
+ if self.use_percent():
676
+ ppi = "%"
677
+ else:
678
+ ppi = "ppi"
679
+ if self.use_mm_min():
680
+ speed_unit = "mm/min"
681
+ else:
682
+ speed_unit = "mm/s"
683
+ self.parameters = [
684
+ ("speed", None, _("Speed"), speed_unit, False, True),
685
+ ("power", None, _("Power"), ppi, False, True),
686
+ ("passes", preset_passes, _("Passes"), "x", False, True),
687
+ ]
688
+
689
+ if opidx == 0:
690
+ # Cut
691
+ # (internal_attribute, secondary_attribute, Label, unit, keep_unit, needs_to_be_positive, type)
692
+ self.parameters = [
693
+ ("speed", None, _("Speed"), speed_unit, False, True, None),
694
+ ("power", None, _("Power"), ppi, False, True, None),
695
+ ("passes", preset_passes, _("Passes"), "x", False, True, int),
696
+ ]
697
+ elif opidx == 1:
698
+ # Engrave
699
+ self.parameters = [
700
+ ("speed", None, _("Speed"), speed_unit, False, True, None),
701
+ ("power", None, _("Power"), ppi, False, True, None),
702
+ ("passes", preset_passes, _("Passes"), "x", False, True, int),
703
+ ]
704
+ elif opidx == 2:
705
+ # Raster
706
+ self.parameters = [
707
+ ("speed", None, _("Speed"), speed_unit, False, True, None),
708
+ ("power", None, _("Power"), ppi, False, True, None),
709
+ ("passes", preset_passes, _("Passes"), "x", False, True, int),
710
+ ("dpi", None, _("DPI"), "dpi", False, True, int),
711
+ ("overscan", None, _("Overscan"), "mm", False, True, None),
712
+ ]
713
+ elif opidx == 3:
714
+ # Image
715
+ self.parameters = [
716
+ ("speed", None, _("Speed"), speed_unit, False, True, None),
717
+ ("power", None, _("Power"), ppi, False, True, None),
718
+ ("passes", preset_passes, _("Passes"), "x", False, True, int),
719
+ ("dpi", preset_image_dpi, _("DPI"), "dpi", False, True, int),
720
+ ("overscan", None, _("Overscan"), "mm", False, True, None),
721
+ ]
722
+ elif opidx == 4:
723
+ # Hatch
724
+ self.parameters = [
725
+ ("speed", None, _("Speed"), speed_unit, False, True, None),
726
+ ("power", None, _("Power"), ppi, False, True, None),
727
+ ("passes", preset_passes, _("Passes"), "x", False, True, int),
728
+ ("hatch_distance", None, _("Hatch Distance"), "mm", False, True, None),
729
+ ("hatch_angle", None, _("Hatch Angle"), "deg", False, True, None),
730
+ ]
731
+
732
+ if "balor" in self.context.device.path:
733
+ balor_choices = [
734
+ ("frequency", None, _("Frequency"), "kHz", False, True, None),
735
+ ("rapid_speed", preset_balor_rapid, _("Rapid Speed"), "mm/s", False, True, None,),
736
+ ("delay_laser_on", preset_balor_timings, _("Laser On Delay"), "µs", False, False, None,),
737
+ ("delay_laser_off", preset_balor_timings, _("Laser Off Delay"), "µs", False, False, None,),
738
+ ("delay_polygon", preset_balor_timings, _("Polygon Delay"), "µs", False, False, None,),
739
+ ("wobble_radius", preset_balor_wobble, _("Wobble Radius"), "mm", True, True, None,),
740
+ ("wobble_interval", preset_balor_wobble, _("Wobble Interval"), "mm", True, True, None,),
741
+ ("wobble_speed", preset_balor_wobble, _("Wobble Speed Multiplier"), "x", False, True, None,),
742
+ ]
743
+ if self.context.device.pulse_width_enabled:
744
+ balor_choices.append(
745
+ (
746
+ "pulse_width",
747
+ preset_balor_pulse,
748
+ _("Pulse Width"),
749
+ "ns",
750
+ False,
751
+ True,
752
+ None,
753
+ )
754
+ )
755
+
756
+ for entry in balor_choices:
757
+ self.parameters.append(entry)
758
+ # for p in self.parameters:
759
+ # if len(p) != 7:
760
+ # print (f"No good: {p}")
761
+ choices = []
762
+ for entry in self.parameters:
763
+ choices.append(entry[2])
764
+ self.combo_param_1.Clear()
765
+ self.combo_param_1.Set(choices)
766
+ self.combo_param_2.Clear()
767
+ self.combo_param_2.Set(choices)
768
+ idx1 = -1
769
+ idx2 = -1
770
+ if len(self.parameters) > 0:
771
+ idx1 = 0
772
+ idx2 = 0
773
+ if len(self.parameters) > 1:
774
+ idx2 = 1
775
+ self.combo_param_1.SetSelection(idx1)
776
+ self.on_combo_1(None)
777
+ self.combo_param_2.SetSelection(idx2)
778
+ self.on_combo_2(None)
779
+ self.Layout()
780
+ self.Thaw()
781
+ del busy
782
+
783
+ def on_combo_1(self, input):
784
+ s_unit = ""
785
+ b_positive = True
786
+ idx = self.combo_param_1.GetSelection()
787
+ # 0 = internal_attribute, 1 = secondary_attribute,
788
+ # 2 = Label, 3 = unit,
789
+ # 4 = keep_unit, 5 = needs_to_be_positive)
790
+ if 0 <= idx < len(self.parameters):
791
+ s_unit = self.parameters[idx][3]
792
+ b_positive = self.parameters[idx][5]
793
+ self.unit_param_1a.SetLabel(s_unit)
794
+ self.unit_param_1b.SetLabel(s_unit)
795
+ # And now enter validation...
796
+ self.validate_input(None)
797
+
798
+ def on_combo_2(self, input):
799
+ s_unit = ""
800
+ idx = self.combo_param_2.GetSelection()
801
+ # 0 = internal_attribute, 1 = secondary_attribute,
802
+ # 2 = Label, 3 = unit,
803
+ # 4 = keep_unit, 5 = needs_to_be_positive)
804
+ if 0 <= idx < len(self.parameters):
805
+ s_unit = self.parameters[idx][3]
806
+ self.unit_param_2a.SetLabel(s_unit)
807
+ self.unit_param_2b.SetLabel(s_unit)
808
+ # And now enter validation...
809
+ self.validate_input(None)
810
+
811
+ def validate_input(self, event):
812
+ def valid_float(ctrl):
813
+ result = True
814
+ if ctrl.GetValue() == "":
815
+ result = False
816
+ else:
817
+ try:
818
+ value = float(ctrl.GetValue())
819
+ except ValueError:
820
+ result = False
821
+ return result
822
+
823
+ active = True
824
+ valid_interval_1 = True
825
+ valid_interval_2 = True
826
+ optype = self.combo_ops.GetSelection()
827
+ if optype < 0:
828
+ active = False
829
+ if (
830
+ optype == 3 and self.combo_images.GetSelection() < 1
831
+ ): # image and no valid image chosen
832
+ active = False
833
+ idx1 = self.combo_param_1.GetSelection()
834
+ if idx1 < 0:
835
+ active = False
836
+ idx2 = self.combo_param_2.GetSelection()
837
+ if idx2 < 0:
838
+ active = False
839
+ if idx1 == idx2:
840
+ active = False
841
+ if not valid_float(self.text_min_1):
842
+ active = False
843
+ valid_interval_1 = False
844
+ if not valid_float(self.text_max_1):
845
+ active = False
846
+ valid_interval_1 = False
847
+ if not valid_float(self.text_min_2):
848
+ active = False
849
+ valid_interval_2 = False
850
+ if not valid_float(self.text_max_2):
851
+ active = False
852
+ valid_interval_2 = False
853
+ if not valid_float(self.text_dim_1):
854
+ active = False
855
+ if not valid_float(self.text_delta_1):
856
+ active = False
857
+ if not valid_float(self.text_dim_2):
858
+ active = False
859
+ if not valid_float(self.text_delta_2):
860
+ active = False
861
+ if valid_interval_1:
862
+ minv = float(self.text_min_1.GetValue())
863
+ maxv = float(self.text_max_1.GetValue())
864
+ count = self.spin_count_1.GetValue()
865
+ delta = maxv - minv
866
+ if count > 1:
867
+ delta /= count - 1
868
+ s_unit = ""
869
+ idx = self.combo_param_1.GetSelection()
870
+ # 0 = internal_attribute, 1 = secondary_attribute,
871
+ # 2 = Label, 3 = unit,
872
+ # 4 = keep_unit, 5 = needs_to_be_positive)
873
+ if 0 <= idx < len(self.parameters):
874
+ s_unit = self.parameters[idx][3]
875
+ self.info_delta_1.SetLabel(
876
+ _("Every {dist}").format(dist=self.shortened(delta, 3) + s_unit)
877
+ )
878
+ else:
879
+ self.info_delta_1.SetLabel("---")
880
+ if valid_interval_2:
881
+ minv = float(self.text_min_2.GetValue())
882
+ maxv = float(self.text_max_2.GetValue())
883
+ count = self.spin_count_2.GetValue()
884
+ delta = maxv - minv
885
+ if count > 1:
886
+ delta /= count - 1
887
+ s_unit = ""
888
+ idx = self.combo_param_2.GetSelection()
889
+ # 0 = internal_attribute, 1 = secondary_attribute,
890
+ # 2 = Label, 3 = unit,
891
+ # 4 = keep_unit, 5 = needs_to_be_positive)
892
+ if 0 <= idx < len(self.parameters):
893
+ s_unit = self.parameters[idx][3]
894
+ self.info_delta_2.SetLabel(
895
+ _("Every {dist}").format(dist=self.shortened(delta, 3) + s_unit)
896
+ )
897
+ else:
898
+ self.info_delta_2.SetLabel("---")
899
+
900
+ self.button_create.Enable(active)
901
+
902
+ def on_device_update(self):
903
+ self.current_op = None
904
+ self.set_param_according_to_op(None)
905
+ # self.on_combo_1(None)
906
+ # self.on_combo_2(None)
907
+
908
+ def on_button_create_pattern(self, event):
909
+ def make_color(idx1, max1, idx2, max2, aspect1, growing1, aspect2, growing2):
910
+ if self._freecolor:
911
+ r = 0
912
+ g = 0
913
+ b = 0
914
+
915
+ rel = max1 - 1
916
+ if rel < 1:
917
+ rel = 1
918
+ if growing1:
919
+ val1 = int(idx1 / rel * 255.0)
920
+ else:
921
+ val1 = 255 - int(idx1 / rel * 255.0)
922
+
923
+ rel = max2 - 1
924
+ if rel < 1:
925
+ rel = 1
926
+ if growing2:
927
+ val2 = int(idx2 / rel * 255.0)
928
+ else:
929
+ val2 = 255 - int(idx2 / rel * 255.0)
930
+ if aspect1 == 1:
931
+ g = val1
932
+ elif aspect1 == 2:
933
+ b = val1
934
+ else:
935
+ r = val1
936
+ if aspect2 == 1:
937
+ g = val1
938
+ elif aspect2 == 2:
939
+ b = val2
940
+ else:
941
+ r = val2
942
+ else:
943
+ r = 0
944
+ g = 0
945
+ b = 0
946
+ mycolor = Color(r, g, b)
947
+ return mycolor
948
+
949
+ def clear_all():
950
+ self.context.elements.clear_operations(fast=True)
951
+ self.context.elements.clear_elements(fast=True)
952
+
953
+ def create_operations():
954
+ # opchoices = [_("Cut"), _("Engrave"), _("Raster"), _("Image"), _("Hatch")]
955
+ display_labels = self.check_labels.GetValue()
956
+ display_values = self.check_values.GetValue()
957
+ color_aspect_1 = max(0, self.combo_color_1.GetSelection())
958
+ color_aspect_2 = max(0, self.combo_color_2.GetSelection())
959
+ color_growing_1 = self.check_color_direction_1.GetValue()
960
+ color_growing_2 = self.check_color_direction_2.GetValue()
961
+
962
+ if optype < 0 or optype > 4:
963
+ return
964
+ if optype == 3:
965
+ shapetype = "image"
966
+ else:
967
+ shapetype = "rect"
968
+ size_x = float(Length(f"{dimension_1}mm"))
969
+ size_y = float(Length(f"{dimension_2}mm"))
970
+ gap_x = float(Length(f"{gap_1}mm"))
971
+ gap_y = float(Length(f"{gap_2}mm"))
972
+ expected_width = count_1 * size_x + (count_1 - 1) * gap_x
973
+ expected_height = count_2 * size_y + (count_2 - 1) * gap_y
974
+ # Need to be adjusted to allow for centering
975
+ start_x = (
976
+ float(Length(self.context.device.view.width)) - expected_width
977
+ ) / 2
978
+ start_y = (
979
+ float(Length(self.context.device.view.height)) - expected_height
980
+ ) / 2
981
+ operation_branch = self.context.elements._tree.get(type="branch ops")
982
+ element_branch = self.context.elements._tree.get(type="branch elems")
983
+
984
+ text_scale_x = min(1.0, size_y / float(Length("20mm")))
985
+ text_scale_y = min(1.0, size_x / float(Length("20mm")))
986
+
987
+ # Make one op for text
988
+ if display_labels or display_values:
989
+ text_op_x = RasterOpNode()
990
+ text_op_x.color = Color("black")
991
+ text_op_x.label = "Descriptions X-Axis"
992
+ text_op_y = RasterOpNode()
993
+ text_op_y.color = Color("black")
994
+ text_op_y.label = "Descriptions Y-Axis"
995
+ operation_branch.add_node(text_op_x)
996
+ operation_branch.add_node(text_op_y)
997
+ if display_labels:
998
+ text_x = start_x + expected_width / 2
999
+ text_y = start_y - min(float(Length("10mm")), 3 * gap_y)
1000
+ node = self.context.elements.elem_branch.add(
1001
+ text=f"{param_name_1} [{param_unit_1}]",
1002
+ matrix=Matrix(
1003
+ f"translate({text_x}, {text_y}) scale({2 * max(text_scale_x, text_scale_y) * UNITS_PER_PIXEL})"
1004
+ ),
1005
+ anchor="middle",
1006
+ fill=Color("black"),
1007
+ type="elem text",
1008
+ )
1009
+ text_op_x.add_reference(node, 0)
1010
+
1011
+ text_x = start_x - min(float(Length("10mm")), 3 * gap_x)
1012
+ text_y = start_y + expected_height / 2
1013
+ node = self.context.elements.elem_branch.add(
1014
+ text=f"{param_name_2} [{param_unit_2}]",
1015
+ matrix=Matrix(
1016
+ f"translate({text_x}, {text_y}) scale({2 * max(text_scale_x, text_scale_y) * UNITS_PER_PIXEL})"
1017
+ ),
1018
+ anchor="middle",
1019
+ fill=Color("black"),
1020
+ type="elem text",
1021
+ )
1022
+ node.matrix.post_rotate(tau * 3 / 4, text_x, text_y)
1023
+ node.modified()
1024
+ text_op_y.add_reference(node, 0)
1025
+
1026
+ _p_value_1 = min_value_1
1027
+
1028
+ xx = start_x
1029
+ for idx1 in range(count_1):
1030
+ p_value_1 = _p_value_1
1031
+ if param_value_type_1 is not None:
1032
+ try:
1033
+ _pp = param_value_type_1(_p_value_1)
1034
+ p_value_1 = _pp
1035
+ except ValueError:
1036
+ pass
1037
+ pval1 = self.shortened(p_value_1, 3)
1038
+
1039
+ _p_value_2 = min_value_2
1040
+ yy = start_y
1041
+
1042
+ if display_values:
1043
+ # Add a text above for each column
1044
+ text_x = xx + 0.5 * size_x
1045
+ text_y = yy - min(float(Length("5mm")), 1.5 * gap_y)
1046
+ node = self.context.elements.elem_branch.add(
1047
+ text=f"{pval1}",
1048
+ matrix=Matrix(
1049
+ f"translate({text_x}, {text_y}) scale({text_scale_x * UNITS_PER_PIXEL})"
1050
+ ),
1051
+ anchor="middle",
1052
+ fill=Color("black"),
1053
+ type="elem text",
1054
+ )
1055
+ # node.matrix.post_rotate(tau / 4, text_x, text_y)
1056
+ node.modified()
1057
+ text_op_x.add_reference(node, 0)
1058
+
1059
+ for idx2 in range(count_2):
1060
+ p_value_2 = _p_value_2
1061
+ if param_value_type_2 is not None:
1062
+ try:
1063
+ _pp = param_value_type_2(_p_value_2)
1064
+ p_value_2 = _pp
1065
+ except ValueError:
1066
+ pass
1067
+
1068
+ pval2 = self.shortened(p_value_2, 3)
1069
+ s_lbl = f"{param_type_1}={pval1}{param_unit_1}"
1070
+ s_lbl += f"- {param_type_2}={pval2}{param_unit_2}"
1071
+ if display_values and idx1 == 0: # first row, so add a text above
1072
+ text_x = xx - min(float(Length("5mm")), 1.5 * gap_x)
1073
+ text_y = yy + 0.5 * size_y
1074
+ node = self.context.elements.elem_branch.add(
1075
+ text=f"{pval2}",
1076
+ matrix=Matrix(
1077
+ f"translate({text_x}, {text_y}) scale({text_scale_y * UNITS_PER_PIXEL})"
1078
+ ),
1079
+ anchor="middle",
1080
+ fill=Color("black"),
1081
+ type="elem text",
1082
+ )
1083
+ node.matrix.post_rotate(tau * 3 / 4, text_x, text_y)
1084
+ text_op_y.add_reference(node, 0)
1085
+ if optype == 0: # Cut
1086
+ this_op = copy(self.default_op[optype])
1087
+ master_op = this_op
1088
+ usefill = False
1089
+ elif optype == 1: # Engrave
1090
+ this_op = copy(self.default_op[optype])
1091
+ master_op = this_op
1092
+ usefill = False
1093
+ elif optype == 2: # Raster
1094
+ this_op = copy(self.default_op[optype])
1095
+ master_op = this_op
1096
+ usefill = True
1097
+ elif optype == 3: # Image
1098
+ this_op = copy(self.default_op[optype])
1099
+ master_op = this_op
1100
+ usefill = False
1101
+ elif optype == 4: # Hatch
1102
+ master_op = copy(self.default_op[optype])
1103
+ this_op = HatchEffectNode()
1104
+ master_op.add_node(this_op)
1105
+
1106
+ # We need to add a hatch node and make this the target for parameter application
1107
+ usefill = False
1108
+ else:
1109
+ return
1110
+ this_op.label = s_lbl
1111
+
1112
+ # Do we need to prep the op?
1113
+ if param_prepper_1 is not None:
1114
+ param_prepper_1(this_op)
1115
+
1116
+ if param_keep_unit_1:
1117
+ value = str(p_value_1) + param_unit_1
1118
+ else:
1119
+ value = p_value_1
1120
+ if param_type_1 == "power" and self.use_percent():
1121
+ value *= 10.0
1122
+ if param_type_1 == "speed" and self.use_mm_min():
1123
+ value /= 60.0
1124
+ if hasattr(master_op, param_type_1):
1125
+ # quick and dirty
1126
+ if param_type_1 == "passes":
1127
+ value = int(value)
1128
+ if param_type_1 == "hatch_distance":
1129
+ if not str(value).endswith("mm"):
1130
+ value = f"{value}mm"
1131
+ setattr(master_op, param_type_1, value)
1132
+ # else: # Try setting
1133
+ # master_op.settings[param_type_1] = value
1134
+ if hasattr(this_op, param_type_1):
1135
+ # quick and dirty
1136
+ if param_type_1 == "passes":
1137
+ value = int(value)
1138
+ if param_type_1 == "hatch_distance":
1139
+ if not str(value).endswith("mm"):
1140
+ value = f"{value}mm"
1141
+ setattr(this_op, param_type_1, value)
1142
+ elif hasattr(this_op, "settings"): # Try setting
1143
+ this_op.settings[param_type_1] = value
1144
+
1145
+ # Do we need to prep the op?
1146
+ if param_prepper_2 is not None:
1147
+ param_prepper_2(this_op)
1148
+
1149
+ if param_keep_unit_2:
1150
+ value = str(p_value_2) + param_unit_2
1151
+ else:
1152
+ value = p_value_2
1153
+ if param_type_2 == "power" and self.use_percent():
1154
+ value *= 10.0
1155
+ if param_type_2 == "speed" and self.use_mm_min():
1156
+ value /= 60.0
1157
+ if hasattr(master_op, param_type_2):
1158
+ # quick and dirty
1159
+ if param_type_2 == "passes":
1160
+ value = int(value)
1161
+ if param_type_2 == "hatch_distance":
1162
+ if not str(value).endswith("mm"):
1163
+ value = f"{value}mm"
1164
+ setattr(master_op, param_type_2, value)
1165
+ if hasattr(this_op, param_type_2):
1166
+ if param_type_2 == "passes":
1167
+ value = int(value)
1168
+ if param_type_2 == "hatch_distance":
1169
+ if not str(value).endswith("mm"):
1170
+ value = f"{value}mm"
1171
+ setattr(this_op, param_type_2, value)
1172
+ elif hasattr(this_op, "settings"): # Try setting
1173
+ this_op.settings[param_type_2] = value
1174
+
1175
+ set_color = make_color(
1176
+ idx1,
1177
+ count_1,
1178
+ idx2,
1179
+ count_2,
1180
+ color_aspect_1,
1181
+ color_growing_1,
1182
+ color_aspect_2,
1183
+ color_growing_2,
1184
+ )
1185
+ this_op.color = set_color
1186
+ # Add op to tree.
1187
+ operation_branch.add_node(master_op)
1188
+ # Now add a rectangle to the scene and assign it to the newly created op
1189
+ if usefill:
1190
+ fill_color = set_color
1191
+ else:
1192
+ fill_color = None
1193
+ if shapetype == "image":
1194
+ idx = self.combo_images.GetSelection() - 1
1195
+ if 0 <= idx < len(self.images):
1196
+ elemnode = copy(self.images[idx])
1197
+ elemnode.matrix.post_translate(xx, yy)
1198
+ elemnode.modified()
1199
+ self.context.elements.elem_branch.add_node(elemnode)
1200
+ elif shapetype == "rect":
1201
+ elemnode = self.context.elements.elem_branch.add(
1202
+ x=xx,
1203
+ y=yy,
1204
+ width=size_x,
1205
+ height=size_y,
1206
+ stroke=set_color,
1207
+ fill=fill_color,
1208
+ type="elem rect",
1209
+ )
1210
+ elif shapetype == "circle":
1211
+ elemnode = self.context.elements.elem_branch.add(
1212
+ cx=xx + size_x / 2,
1213
+ cy=yy + size_y / 2,
1214
+ rx=size_x / 2,
1215
+ ry=size_y / 2,
1216
+ stroke=set_color,
1217
+ fill=fill_color,
1218
+ type="elem ellipse",
1219
+ )
1220
+ elemnode.label = s_lbl
1221
+ this_op.add_reference(elemnode, 0)
1222
+ _p_value_2 += delta_2
1223
+ yy = yy + gap_y + size_y
1224
+ _p_value_1 += delta_1
1225
+ xx = xx + gap_x + size_x
1226
+
1227
+ # Read the parameters and user input
1228
+ optype = self.combo_ops.GetSelection()
1229
+ if optype < 0:
1230
+ return
1231
+ idx = self.combo_param_1.GetSelection()
1232
+ if idx < 0:
1233
+ return
1234
+ # 0 = internal_attribute, 1 = secondary_attribute,
1235
+ # 2 = Label, 3 = unit,
1236
+ # 4 = keep_unit, 5 = needs_to_be_positive)
1237
+ param_name_1 = self.parameters[idx][2]
1238
+ param_type_1 = self.parameters[idx][0]
1239
+ param_value_type_1 = self.parameters[idx][6]
1240
+ param_prepper_1 = self.parameters[idx][1]
1241
+ if param_prepper_1 == "":
1242
+ param_prepper_1 = None
1243
+ param_unit_1 = self.parameters[idx][3]
1244
+ param_keep_unit_1 = self.parameters[idx][4]
1245
+ param_positive_1 = self.parameters[idx][5]
1246
+
1247
+ idx = self.combo_param_2.GetSelection()
1248
+ if idx < 0:
1249
+ return
1250
+ param_name_2 = self.parameters[idx][2]
1251
+ param_type_2 = self.parameters[idx][0]
1252
+ param_value_type_2 = self.parameters[idx][6]
1253
+ param_prepper_2 = self.parameters[idx][1]
1254
+ if param_prepper_2 == "":
1255
+ param_prepper_2 = None
1256
+ param_unit_2 = self.parameters[idx][3]
1257
+ param_keep_unit_2 = self.parameters[idx][4]
1258
+ param_positive_2 = self.parameters[idx][5]
1259
+ if param_type_1 == param_type_2:
1260
+ return
1261
+ if self.text_min_1.GetValue() == "":
1262
+ return
1263
+ try:
1264
+ min_value_1 = float(self.text_min_1.GetValue())
1265
+ except ValueError:
1266
+ return
1267
+ if self.text_min_2.GetValue() == "":
1268
+ return
1269
+ try:
1270
+ min_value_2 = float(self.text_min_2.GetValue())
1271
+ except ValueError:
1272
+ return
1273
+ if self.text_max_1.GetValue() == "":
1274
+ return
1275
+ try:
1276
+ max_value_1 = float(self.text_max_1.GetValue())
1277
+ except ValueError:
1278
+ return
1279
+ if self.text_max_2.GetValue() == "":
1280
+ return
1281
+ try:
1282
+ max_value_2 = float(self.text_max_2.GetValue())
1283
+ except ValueError:
1284
+ return
1285
+
1286
+ if param_unit_1 == "deg":
1287
+ min_value_1 = Angle(self.text_min_1.GetValue()).degrees
1288
+ max_value_1 = Angle(self.text_max_1.GetValue()).degrees
1289
+ elif param_unit_1 == "ppi":
1290
+ min_value_1 = max(min_value_1, 0)
1291
+ max_value_1 = min(max_value_1, 1000)
1292
+ elif param_unit_1 == "%":
1293
+ min_value_1 = max(min_value_1, 0)
1294
+ max_value_1 = min(max_value_1, 100)
1295
+ else:
1296
+ # > 0
1297
+ if param_positive_1:
1298
+ min_value_1 = max(min_value_1, 0)
1299
+ max_value_1 = max(max_value_1, 0)
1300
+
1301
+ if param_unit_2 == "deg":
1302
+ min_value_2 = Angle(self.text_min_2.GetValue()).degrees
1303
+ max_value_2 = Angle(self.text_max_2.GetValue()).degrees
1304
+ elif param_unit_2 == "ppi":
1305
+ min_value_2 = max(min_value_2, 0)
1306
+ max_value_2 = min(max_value_2, 1000)
1307
+ elif param_unit_2 == "%":
1308
+ min_value_2 = max(min_value_2, 0)
1309
+ max_value_2 = min(max_value_2, 100)
1310
+ else:
1311
+ # > 0
1312
+ if param_positive_2:
1313
+ min_value_2 = max(min_value_2, 0)
1314
+ max_value_2 = max(max_value_2, 0)
1315
+
1316
+ count_1 = int(self.spin_count_1.GetValue())
1317
+ count_2 = int(self.spin_count_2.GetValue())
1318
+ if count_1 > 1:
1319
+ delta_1 = (max_value_1 - min_value_1) / (count_1 - 1)
1320
+ else:
1321
+ delta_1 = 0
1322
+ if count_2 > 1:
1323
+ delta_2 = (max_value_2 - min_value_2) / (count_2 - 1)
1324
+ else:
1325
+ delta_2 = 0
1326
+ try:
1327
+ dimension_1 = float(self.text_dim_1.GetValue())
1328
+ except ValueError:
1329
+ dimension_1 = -1
1330
+ try:
1331
+ dimension_2 = float(self.text_dim_2.GetValue())
1332
+ except ValueError:
1333
+ dimension_2 = -1
1334
+ if dimension_1 <= 0:
1335
+ dimension_1 = 5
1336
+ if dimension_2 <= 0:
1337
+ dimension_2 = 5
1338
+
1339
+ try:
1340
+ gap_1 = float(self.text_delta_1.GetValue())
1341
+ except ValueError:
1342
+ gap_1 = -1
1343
+ try:
1344
+ gap_2 = float(self.text_delta_2.GetValue())
1345
+ except ValueError:
1346
+ gap_2 = -1
1347
+
1348
+ if gap_1 < 0:
1349
+ gap_1 = 0
1350
+ if gap_2 < 0:
1351
+ gap_2 = 5
1352
+
1353
+ message = _("This will delete all existing operations and elements") + "\n"
1354
+ message += (
1355
+ _("and replace them by the test-pattern! Are you really sure?") + "\n"
1356
+ )
1357
+ message += _("(Yes=Empty and Create, No=Keep existing)")
1358
+ caption = _("Create Test-Pattern")
1359
+ dlg = wx.MessageDialog(
1360
+ self,
1361
+ message,
1362
+ caption,
1363
+ wx.YES_NO | wx.CANCEL | wx.ICON_WARNING,
1364
+ )
1365
+ result = dlg.ShowModal()
1366
+ dlg.Destroy()
1367
+ if result == wx.ID_YES:
1368
+ clear_all()
1369
+ elif result == wx.ID_CANCEL:
1370
+ return
1371
+
1372
+ create_operations()
1373
+
1374
+ self.context.signal("rebuild_tree")
1375
+ self.context.signal("refresh_scene", "Scene")
1376
+ self.save_settings()
1377
+
1378
+ def setup_settings(self):
1379
+ self.context.setting(int, "template_optype", 0)
1380
+ self.context.setting(int, "template_param1", 0)
1381
+ self.context.setting(int, "template_param2", 1)
1382
+ self.context.setting(str, "template_min1", "")
1383
+ self.context.setting(str, "template_max1", "")
1384
+ self.context.setting(str, "template_min2", "")
1385
+ self.context.setting(str, "template_max2", "")
1386
+ self.context.setting(int, "template_count1", 5)
1387
+ self.context.setting(int, "template_count2", 5)
1388
+ self.context.setting(str, "template_dim_1", "10")
1389
+ self.context.setting(str, "template_dim_2", "10")
1390
+ self.context.setting(str, "template_gap_1", "5")
1391
+ self.context.setting(str, "template_gap_2", "5")
1392
+ self.context.setting(bool, "template_show_labels", True)
1393
+ self.context.setting(bool, "template_show_values", True)
1394
+ self.context.setting(int, "template_color1", 0)
1395
+ self.context.setting(int, "template_color2", 2)
1396
+ self.context.setting(bool, "template_coldir1", False)
1397
+ self.context.setting(bool, "template_coldir2", False)
1398
+
1399
+ def _set_settings(self, templatename):
1400
+ info_field = (
1401
+ self.context.template_show_values,
1402
+ self.context.template_show_labels,
1403
+ self.context.template_optype,
1404
+ self.context.template_param1,
1405
+ self.context.template_param2,
1406
+ self.context.template_min1,
1407
+ self.context.template_max1,
1408
+ self.context.template_min2,
1409
+ self.context.template_max2,
1410
+ self.context.template_count1,
1411
+ self.context.template_count2,
1412
+ self.context.template_dim_1,
1413
+ self.context.template_dim_2,
1414
+ self.context.template_gap_1,
1415
+ self.context.template_gap_2,
1416
+ self.context.template_color1,
1417
+ self.context.template_color2,
1418
+ self.context.template_coldir1,
1419
+ self.context.template_coldir2,
1420
+ )
1421
+ # print (f"Save data to {templatename}, infofield-len={len(info_field)}")
1422
+ key = f"{templatename}"
1423
+ self.storage.write_persistent("materialtest", key, info_field)
1424
+ self.storage.write_configuration()
1425
+
1426
+ def _get_settings(self, templatename):
1427
+ key = f"{templatename}"
1428
+ info_field = self.storage.read_persistent(tuple, "materialtest", key, None)
1429
+ if (
1430
+ info_field is not None
1431
+ and isinstance(info_field, (tuple, list))
1432
+ and len(info_field) == 19
1433
+ ):
1434
+ # print (f"Load data from {templatename}")
1435
+ self.context.template_show_values = info_field[0]
1436
+ self.context.template_show_labels = info_field[1]
1437
+ self.context.template_optype = info_field[2]
1438
+ self.context.template_param1 = info_field[3]
1439
+ self.context.template_param2 = info_field[4]
1440
+ self.context.template_min1 = info_field[5]
1441
+ self.context.template_max1 = info_field[6]
1442
+ self.context.template_min2 = info_field[7]
1443
+ self.context.template_max2 = info_field[8]
1444
+ self.context.template_count1 = info_field[9]
1445
+ self.context.template_count2 = info_field[10]
1446
+ self.context.template_dim_1 = info_field[11]
1447
+ self.context.template_dim_2 = info_field[12]
1448
+ self.context.template_gap_1 = info_field[13]
1449
+ self.context.template_gap_2 = info_field[14]
1450
+ self.context.template_color1 = info_field[15]
1451
+ self.context.template_color2 = info_field[16]
1452
+ self.context.template_coldir1 = info_field[17]
1453
+ self.context.template_coldir2 = info_field[18]
1454
+
1455
+ def save_settings(self, templatename=None):
1456
+ self.context.template_show_values = self.check_values.GetValue()
1457
+ self.context.template_show_labels = self.check_labels.GetValue()
1458
+ self.context.template_optype = self.combo_ops.GetSelection()
1459
+ self.context.template_param1 = self.combo_param_1.GetSelection()
1460
+ self.context.template_param2 = self.combo_param_2.GetSelection()
1461
+ self.context.template_min1 = self.text_min_1.GetValue()
1462
+ self.context.template_max1 = self.text_max_1.GetValue()
1463
+ self.context.template_min2 = self.text_min_2.GetValue()
1464
+ self.context.template_max2 = self.text_max_2.GetValue()
1465
+ self.context.template_count1 = self.spin_count_1.GetValue()
1466
+ self.context.template_count2 = self.spin_count_2.GetValue()
1467
+ self.context.template_dim_1 = self.text_dim_1.GetValue()
1468
+ self.context.template_dim_2 = self.text_dim_2.GetValue()
1469
+ self.context.template_gap_1 = self.text_delta_1.GetValue()
1470
+ self.context.template_gap_2 = self.text_delta_2.GetValue()
1471
+ self.context.template_color1 = self.combo_color_1.GetSelection()
1472
+ self.context.template_color2 = self.combo_color_2.GetSelection()
1473
+ self.context.template_coldir1 = self.check_color_direction_1.GetValue()
1474
+ self.context.template_coldir2 = self.check_color_direction_2.GetValue()
1475
+ if templatename:
1476
+ # let's try to restore the settings
1477
+ self._set_settings(templatename)
1478
+
1479
+ def restore_settings(self, templatename=None):
1480
+ if templatename:
1481
+ # let's try to restore the settings
1482
+ self._get_settings(templatename)
1483
+ try:
1484
+ self.check_color_direction_1.SetValue(self.context.template_coldir1)
1485
+ self.check_color_direction_2.SetValue(self.context.template_coldir2)
1486
+ self.combo_color_1.SetSelection(
1487
+ min(self.context.template_color1, self.combo_color_1.GetCount() - 1)
1488
+ )
1489
+ self.combo_color_2.SetSelection(
1490
+ min(self.context.template_color2, self.combo_color_2.GetCount() - 1)
1491
+ )
1492
+ self.check_values.SetValue(self.context.template_show_values)
1493
+ self.check_labels.SetValue(self.context.template_show_labels)
1494
+ self.combo_ops.SetSelection(
1495
+ min(self.context.template_optype, self.combo_ops.GetCount() - 1)
1496
+ )
1497
+ self.combo_param_1.SetSelection(
1498
+ min(self.context.template_param1, self.combo_param_1.GetCount() - 1)
1499
+ )
1500
+ self.combo_param_2.SetSelection(
1501
+ min(self.context.template_param2, self.combo_param_2.GetCount() - 1)
1502
+ )
1503
+ self.text_min_1.SetValue(self.context.template_min1)
1504
+ self.text_max_1.SetValue(self.context.template_max1)
1505
+ self.text_min_2.SetValue(self.context.template_min2)
1506
+ self.text_max_2.SetValue(self.context.template_max2)
1507
+ self.spin_count_1.SetValue(self.context.template_count1)
1508
+ self.spin_count_2.SetValue(self.context.template_count2)
1509
+ self.text_dim_1.SetValue(self.context.template_dim_1)
1510
+ self.text_dim_2.SetValue(self.context.template_dim_2)
1511
+ self.text_delta_1.SetValue(self.context.template_gap_1)
1512
+ self.text_delta_2.SetValue(self.context.template_gap_2)
1513
+ except (AttributeError, ValueError):
1514
+ pass
1515
+
1516
+ def sync_fields(self):
1517
+ # Repopulate combos
1518
+ self.set_param_according_to_op(None)
1519
+ # And then setting it back to the defaults...
1520
+ self.combo_param_1.SetSelection(
1521
+ min(self.context.template_param1, self.combo_param_1.GetCount() - 1)
1522
+ )
1523
+ # Make sure units appear properly
1524
+ self.on_combo_1(None)
1525
+ self.combo_param_2.SetSelection(
1526
+ min(self.context.template_param2, self.combo_param_2.GetCount() - 1)
1527
+ )
1528
+ # Make sure units appear properly
1529
+ self.on_combo_2(None)
1530
+
1531
+ @signal_listener("activate;device")
1532
+ def on_activate_device(self, origin, device):
1533
+ self.set_param_according_to_op(None)
1534
+
1535
+
1536
+ class TemplateTool(MWindow):
1537
+ """
1538
+ Material-/Parameter Test routines
1539
+ """
1540
+
1541
+ def __init__(self, *args, **kwds):
1542
+ super().__init__(720, 750, submenu="Laser-Tools", *args, **kwds)
1543
+
1544
+ self.storage = Settings(self.context.kernel.name, "templates.cfg")
1545
+ self.storage.read_configuration()
1546
+ self.panel_instances = list()
1547
+ self.panel_template = TemplatePanel(
1548
+ self,
1549
+ wx.ID_ANY,
1550
+ context=self.context,
1551
+ storage=self.storage,
1552
+ )
1553
+
1554
+ self.panel_saveload = SaveLoadPanel(
1555
+ self,
1556
+ wx.ID_ANY,
1557
+ context=self.context,
1558
+ )
1559
+
1560
+ self.notebook_main = aui.AuiNotebook(
1561
+ self,
1562
+ -1,
1563
+ style=aui.AUI_NB_TAB_EXTERNAL_MOVE
1564
+ | aui.AUI_NB_SCROLL_BUTTONS
1565
+ | aui.AUI_NB_TAB_SPLIT
1566
+ | aui.AUI_NB_TAB_MOVE
1567
+ | aui.AUI_NB_BOTTOM,
1568
+ )
1569
+ # ARGGH, the color setting via the ArtProvider does only work
1570
+ # if you set the tabs to the bottom! wx.aui.AUI_NB_BOTTOM
1571
+ self.window_context.themes.set_window_colors(self.notebook_main)
1572
+ bg_std = self.window_context.themes.get("win_bg")
1573
+ bg_active = self.window_context.themes.get("highlight")
1574
+ self.notebook_main.GetArtProvider().SetColour(bg_std)
1575
+ self.notebook_main.GetArtProvider().SetActiveColour(bg_active)
1576
+ self.sizer.Add(self.notebook_main, 1, wx.EXPAND, 0)
1577
+ self.notebook_main.AddPage(self.panel_template, _("Generator"))
1578
+
1579
+ self.panel_template.set_callback(self.set_node)
1580
+ self.add_module_delegate(self.panel_template)
1581
+
1582
+ self.notebook_main.AddPage(self.panel_saveload, _("Templates"))
1583
+ self.panel_saveload.set_callback(self.callback_templates)
1584
+ self.add_module_delegate(self.panel_saveload)
1585
+
1586
+ _icon = wx.NullIcon
1587
+ _icon.CopyFromBitmap(icons8_detective.GetBitmap())
1588
+ self.SetIcon(_icon)
1589
+ self.SetTitle(_("Parameter-Test"))
1590
+ self.restore_aspect()
1591
+
1592
+ def callback_templates(self, command, param):
1593
+ # print (f"callback called with {command}, {param}")
1594
+ if command == "load":
1595
+ if param:
1596
+ self.panel_template.restore_settings(param)
1597
+ self.panel_template.sync_fields()
1598
+ return True
1599
+ elif command == "save":
1600
+ if param:
1601
+ self.panel_template.save_settings(param)
1602
+ return True
1603
+ elif command == "delete":
1604
+ if param:
1605
+ key = f"{param}"
1606
+ self.storage.delete_persistent("materialtest", key)
1607
+ self.storage.write_configuration()
1608
+ return True
1609
+ elif command == "get":
1610
+ choices = []
1611
+ for section in list(self.storage.keylist("materialtest")):
1612
+ choices.append(section)
1613
+ return choices
1614
+
1615
+ return None
1616
+
1617
+ def set_node(self, node):
1618
+ def sort_priority(prop):
1619
+ prop_sheet, node = prop
1620
+ return (
1621
+ getattr(prop_sheet, "priority")
1622
+ if hasattr(prop_sheet, "priority")
1623
+ else 0
1624
+ )
1625
+
1626
+ if node is None:
1627
+ return
1628
+ busy = wx.BusyCursor()
1629
+ self.Freeze()
1630
+ pages_to_instance = []
1631
+ pages_in_node = []
1632
+ found = False
1633
+ for property_sheet in self.context.lookup_all(
1634
+ f"property/{node.__class__.__name__}/.*"
1635
+ ):
1636
+ if not hasattr(property_sheet, "accepts") or property_sheet.accepts(node):
1637
+ pages_in_node.append((property_sheet, node))
1638
+ found = True
1639
+ # If we did not have any hits and the node is a reference
1640
+ # then we fall back to the master. So if in the future we
1641
+ # would have a property panel dealing with reference-nodes
1642
+ # then this would no longer apply.
1643
+ if node.type == "reference" and not found:
1644
+ snode = node.node
1645
+ found = False
1646
+ for property_sheet in self.context.lookup_all(
1647
+ f"property/{snode.__class__.__name__}/.*"
1648
+ ):
1649
+ if not hasattr(property_sheet, "accepts") or property_sheet.accepts(
1650
+ snode
1651
+ ):
1652
+ pages_in_node.append((property_sheet, snode))
1653
+ found = True
1654
+
1655
+ pages_in_node.sort(key=sort_priority, reverse=True)
1656
+ pages_to_instance.extend(pages_in_node)
1657
+
1658
+ for p in self.panel_instances:
1659
+ try:
1660
+ p.pane_hide()
1661
+ except AttributeError:
1662
+ pass
1663
+ self.remove_module_delegate(p)
1664
+
1665
+ # Delete all but the first and last page...
1666
+ while self.notebook_main.GetPageCount() > 2:
1667
+ self.notebook_main.DeletePage(1)
1668
+ for prop_sheet, instance in pages_to_instance:
1669
+ page_panel = prop_sheet(
1670
+ self.notebook_main, wx.ID_ANY, context=self.context, node=instance
1671
+ )
1672
+ try:
1673
+ name = prop_sheet.name
1674
+ except AttributeError:
1675
+ name = instance.__class__.__name__
1676
+
1677
+ self.notebook_main.InsertPage(1, page_panel, _(name))
1678
+ try:
1679
+ page_panel.set_widgets(instance)
1680
+ except AttributeError:
1681
+ pass
1682
+ self.add_module_delegate(page_panel)
1683
+ self.panel_instances.append(page_panel)
1684
+ try:
1685
+ page_panel.pane_show()
1686
+ except AttributeError:
1687
+ pass
1688
+ page_panel.Layout()
1689
+ try:
1690
+ page_panel.SetupScrolling()
1691
+ except AttributeError:
1692
+ pass
1693
+
1694
+ self.Layout()
1695
+ self.Thaw()
1696
+ del busy
1697
+
1698
+ def window_open(self):
1699
+ pass
1700
+
1701
+ def window_close(self):
1702
+ for p in self.panel_instances:
1703
+ try:
1704
+ p.pane_hide()
1705
+ except AttributeError:
1706
+ pass
1707
+ # We do not remove the delegates, they will detach with the closing of the module.
1708
+ self.panel_instances.clear()
1709
+
1710
+ @signal_listener("power_percent")
1711
+ @signal_listener("speed_min")
1712
+ @lookup_listener("service/device/active")
1713
+ def on_device_update(self, *args):
1714
+ self.panel_template.on_device_update()
1715
+
1716
+ @staticmethod
1717
+ def submenu():
1718
+ return "Laser-Tools", "Parameter-Test"
1719
+
1720
+ @staticmethod
1721
+ def helptext():
1722
+ return _("Figure out the right settings for your material")