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

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