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
@@ -0,0 +1,2944 @@
1
+ """
2
+ GUI to manage material library entries.
3
+ In essence a material library setting is a persistent list of operations.
4
+ They are stored in the operations.cfg file in the meerk40t working directory
5
+ """
6
+
7
+ import os
8
+ import xml.etree.ElementTree as ET
9
+ from platform import system
10
+
11
+ import wx
12
+
13
+ from meerk40t.core.node.node import Node
14
+ from meerk40t.gui.icons import (
15
+ icon_hatch,
16
+ icon_library,
17
+ icon_points,
18
+ icons8_caret_down,
19
+ icons8_caret_up,
20
+ icons8_console,
21
+ icons8_direction,
22
+ icons8_image,
23
+ icons8_laser_beam,
24
+ icons8_laserbeam_weak,
25
+ )
26
+ from meerk40t.gui.mwindow import MWindow
27
+ from meerk40t.gui.wxutils import (
28
+ EditableListCtrl,
29
+ ScrolledPanel,
30
+ StaticBoxSizer,
31
+ TextCtrl,
32
+ dip_size,
33
+ wxButton,
34
+ wxCheckBox,
35
+ wxComboBox,
36
+ wxStaticText,
37
+ wxTreeCtrl,
38
+ )
39
+ from meerk40t.kernel.settings import Settings
40
+ from meerk40t.svgelements import Color
41
+
42
+ _ = wx.GetTranslation
43
+
44
+
45
+ class ImportDialog(wx.Dialog):
46
+ def __init__(self, *args, context=None, filename=None, **kwds):
47
+ kwds["style"] = (
48
+ kwds.get("style", 0) | wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
49
+ )
50
+ wx.Dialog.__init__(self, *args, **kwds)
51
+ self.context = context
52
+ self.context.themes.set_window_colors(self)
53
+ self.txt_filename = TextCtrl(self, wx.ID_ANY)
54
+ self.btn_file = wxButton(self, wx.ID_ANY, "...")
55
+ self.check_consolidate = wxCheckBox(
56
+ self, wx.ID_ANY, _("Consolidate same thickness for material")
57
+ )
58
+ self.check_lens = wxCheckBox(self, wx.ID_ANY, _("Compensate Lens-Sizes"))
59
+ self.txt_lens_old = TextCtrl(self, wx.ID_ANY)
60
+ self.txt_lens_new = TextCtrl(self, wx.ID_ANY)
61
+ self.check_wattage = wxCheckBox(self, wx.ID_ANY, _("Compensate Power-Levels"))
62
+ self.txt_wattage_old = TextCtrl(self, wx.ID_ANY)
63
+ self.txt_wattage_new = TextCtrl(self, wx.ID_ANY)
64
+ self.btn_ok = wxButton(self, wx.ID_OK, _("OK"))
65
+ self.btn_cancel = wxButton(self, wx.ID_CANCEL, _("Cancel"))
66
+
67
+ self._define_layout()
68
+ self.validate(None)
69
+ self.on_check(None)
70
+ self.check_consolidate.SetValue(True)
71
+ self._define_logic()
72
+ if filename is not None:
73
+ self.txt_filename.SetValue(filename)
74
+
75
+ def _define_layout(self):
76
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
77
+ file_sizer = StaticBoxSizer(self, wx.ID_ANY, _("File to import"), wx.VERTICAL)
78
+
79
+ file_box = wx.BoxSizer(wx.HORIZONTAL)
80
+
81
+ file_box.Add(self.txt_filename, 1, wx.ALIGN_CENTER_VERTICAL, 0)
82
+ file_box.Add(self.btn_file, 0, wx.ALIGN_CENTER_VERTICAL, 0)
83
+ file_sizer.Add(file_box, 0, wx.EXPAND, 0)
84
+
85
+ file_sizer.Add(self.check_consolidate, 0, 0, 0)
86
+
87
+ main_sizer.Add(file_sizer, 0, wx.EXPAND, 0)
88
+
89
+ lens_sizer = StaticBoxSizer(
90
+ self, wx.ID_ANY, _("Different Lens-Size"), wx.VERTICAL
91
+ )
92
+
93
+ lens_param_sizer = wx.BoxSizer(wx.HORIZONTAL)
94
+ label_old = wxStaticText(self, wx.ID_ANY, _("Old:"))
95
+ unit_old = wxStaticText(self, wx.ID_ANY, "mm")
96
+ label_new = wxStaticText(self, wx.ID_ANY, _("New:"))
97
+ unit_new = wxStaticText(self, wx.ID_ANY, "mm")
98
+ lens_param_sizer.Add(label_old, 0, wx.ALIGN_CENTER_VERTICAL, 0)
99
+ lens_param_sizer.Add(self.txt_lens_old, 0, 0, 0)
100
+ lens_param_sizer.Add(unit_old, 0, wx.ALIGN_CENTER_VERTICAL, 0)
101
+
102
+ lens_param_sizer.AddSpacer(25)
103
+
104
+ lens_param_sizer.Add(label_new, 0, wx.ALIGN_CENTER_VERTICAL, 0)
105
+ lens_param_sizer.Add(self.txt_lens_new, 0, 0, 0)
106
+ lens_param_sizer.Add(unit_new, 0, wx.ALIGN_CENTER_VERTICAL, 0)
107
+
108
+ lens_sizer.Add(self.check_lens, 0, 0, 0)
109
+ lens_sizer.Add(lens_param_sizer, 0, 0, 0)
110
+ main_sizer.Add(lens_sizer, 0, wx.EXPAND, 0)
111
+
112
+ wattage_sizer = StaticBoxSizer(
113
+ self, wx.ID_ANY, _("Different Laser-Power"), wx.VERTICAL
114
+ )
115
+
116
+ wattage_param_sizer = wx.BoxSizer(wx.HORIZONTAL)
117
+ label_old = wxStaticText(self, wx.ID_ANY, _("Old:"))
118
+ unit_old = wxStaticText(self, wx.ID_ANY, "W")
119
+ label_new = wxStaticText(self, wx.ID_ANY, _("New:"))
120
+ unit_new = wxStaticText(self, wx.ID_ANY, "W")
121
+ wattage_param_sizer.Add(label_old, 0, wx.ALIGN_CENTER_VERTICAL, 0)
122
+ wattage_param_sizer.Add(self.txt_wattage_old, 0, 0, 0)
123
+ wattage_param_sizer.Add(unit_old, 0, wx.ALIGN_CENTER_VERTICAL, 0)
124
+
125
+ wattage_param_sizer.AddSpacer(25)
126
+
127
+ wattage_param_sizer.Add(label_new, 0, wx.ALIGN_CENTER_VERTICAL, 0)
128
+ wattage_param_sizer.Add(self.txt_wattage_new, 0, 0, 0)
129
+ wattage_param_sizer.Add(unit_new, 0, wx.ALIGN_CENTER_VERTICAL, 0)
130
+
131
+ wattage_sizer.Add(self.check_wattage, 0, 0, 0)
132
+ wattage_sizer.Add(wattage_param_sizer, 0, 0, 0)
133
+ main_sizer.Add(wattage_sizer, 0, wx.EXPAND, 0)
134
+
135
+ box_sizer = wx.BoxSizer(wx.HORIZONTAL)
136
+ box_sizer.Add(self.btn_ok, 0, 0, 0)
137
+ box_sizer.Add(self.btn_cancel, 0, 0, 0)
138
+ main_sizer.Add(box_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
139
+
140
+ self.SetSizer(main_sizer)
141
+ self.Layout()
142
+ main_sizer.Fit(self)
143
+ self.txt_filename.SetToolTip(
144
+ _("Provide the full filename for material library")
145
+ )
146
+ self.btn_file.SetToolTip(_("Click to select files"))
147
+ self.check_consolidate.SetToolTip(
148
+ _("This will group entries with the same material description together")
149
+ )
150
+ self.check_lens.SetToolTip(
151
+ _(
152
+ "If active, power and speed values will be adjusted,\nto accommodate different lens-sizes"
153
+ )
154
+ )
155
+ self.check_wattage.SetToolTip(
156
+ _(
157
+ "If active, power and speed values will be adjusted,\nto accommodate different laser power"
158
+ )
159
+ )
160
+
161
+ def _define_logic(self):
162
+ self.Bind(wx.EVT_TEXT, self.validate, self.txt_filename)
163
+ self.Bind(wx.EVT_BUTTON, self.on_file, self.btn_file)
164
+ self.Bind(wx.EVT_CHECKBOX, self.on_check, self.check_lens)
165
+ self.Bind(wx.EVT_CHECKBOX, self.on_check, self.check_wattage)
166
+
167
+ def on_file(self, event):
168
+ mydlg = wx.FileDialog(
169
+ self,
170
+ message=_("Choose a library-file"),
171
+ wildcard="Supported files|*.lib;*.ini;*.clb;*.cfg|EZcad files (*.lib;*.ini)|*.lib;*.ini|Lightburn files (*.clb)|*.clb|MeerK40t operations (*.cfg)|*.cfg|All files (*.*)|*.*",
172
+ style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_PREVIEW,
173
+ )
174
+ if mydlg.ShowModal() == wx.ID_OK:
175
+ # This returns a Python list of files that were selected.
176
+ self.txt_filename.SetValue(mydlg.GetPath())
177
+ self.validate()
178
+ mydlg.Destroy()
179
+
180
+ def on_check(self, event):
181
+ flag = self.check_lens.GetValue()
182
+ self.txt_lens_old.Enable(flag)
183
+ self.txt_lens_new.Enable(flag)
184
+ flag = self.check_wattage.GetValue()
185
+ self.txt_wattage_old.Enable(flag)
186
+ self.txt_wattage_new.Enable(flag)
187
+
188
+ def validate(self, *args):
189
+ flag = True
190
+ fname = self.txt_filename.GetValue()
191
+ if fname == "" or not os.path.exists(fname):
192
+ flag = False
193
+ if flag and fname.endswith(".clb"):
194
+ self.check_consolidate.Enable(True)
195
+ else:
196
+ self.check_consolidate.Enable(False)
197
+
198
+ self.btn_ok.Enable(flag)
199
+
200
+ def result(self):
201
+ old_lens = None
202
+ new_lens = None
203
+ factor_from_lens = 1.0
204
+ if self.check_lens.GetValue():
205
+ a_s = self.txt_lens_old.GetValue()
206
+ b_s = self.txt_lens_new.GetValue()
207
+ try:
208
+ a = float(a_s)
209
+ b = float(b_s)
210
+ if a != 0 and b != 0:
211
+ old_lens = a_s
212
+ new_lens = b_s
213
+ factor_from_lens = b / a
214
+ except ValueError:
215
+ pass
216
+
217
+ old_power = None
218
+ new_power = None
219
+ factor_from_power = 1.0
220
+ if self.check_wattage.GetValue():
221
+ a_s = self.txt_wattage_old.GetValue()
222
+ b_s = self.txt_wattage_new.GetValue()
223
+ try:
224
+ a = float(a_s)
225
+ b = float(b_s)
226
+ if a != 0 and b != 0:
227
+ old_power = a_s
228
+ new_power = b_s
229
+ factor_from_power = a / b
230
+ except ValueError:
231
+ pass
232
+
233
+ fname = self.txt_filename.GetValue()
234
+ if fname == "" or not os.path.exists(fname):
235
+ fname = None
236
+
237
+ consolidate = self.check_consolidate.GetValue()
238
+ if not self.check_consolidate.Enabled:
239
+ consolidate = False
240
+ factor = factor_from_lens * factor_from_power
241
+ info = (
242
+ fname,
243
+ old_lens,
244
+ new_lens,
245
+ old_power,
246
+ new_power,
247
+ factor_from_lens,
248
+ factor_from_power,
249
+ factor,
250
+ consolidate,
251
+ )
252
+ return info
253
+
254
+
255
+ class MaterialPanel(ScrolledPanel):
256
+ """
257
+ Panel to modify material library settings.
258
+ In essence a material library setting is a persistent list of operations.
259
+ They are stored in the operations.cfg file in the meerk40t working directory
260
+
261
+ Internal development note:
262
+ I have tried the dataview TreeListCtrl to self.display_list the different entries:
263
+ this was crashing consistently, so I stopped following this path
264
+ """
265
+
266
+ def __init__(self, *args, context=None, **kwds):
267
+ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
268
+ ScrolledPanel.__init__(self, *args, **kwds)
269
+ self.context = context
270
+ self.context.themes.set_window_colors(self)
271
+ self.op_data = self.context.elements.op_data
272
+ self.SetHelpText("materialmanager")
273
+ self.parent_panel = None
274
+ self.current_item = None
275
+ self._active_material = None
276
+ self._active_operation = None
277
+ self.no_reload = False
278
+ self.share_ready = False
279
+ self.state_images = wx.ImageList()
280
+ # Categorisation
281
+ # 0 = Material (thickness), 1 = Lasertype (Material), 2 = Thickness (Material)
282
+ self.categorisation = 0
283
+ # Intentionally not translated, to allow data exchange
284
+ materials = [
285
+ "Plywood",
286
+ "Solid wood",
287
+ "Acrylic",
288
+ "Foam",
289
+ "Leather",
290
+ "Cardboard",
291
+ "Cork",
292
+ "Textiles",
293
+ "Slate",
294
+ "Paper",
295
+ "Aluminium",
296
+ "Steel",
297
+ "Copper",
298
+ "Silver",
299
+ "Gold",
300
+ "Zinc",
301
+ "Metal",
302
+ ]
303
+ # Dictionary with key=Materialname, entry=Description (Name, Lasertype, entries)
304
+ self.material_list = dict()
305
+ self.operation_list = dict()
306
+ self.display_list = list()
307
+ self.deletion_methods = dict()
308
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
309
+ filter_box = StaticBoxSizer(
310
+ self, wx.ID_ANY, _("Filter Materials"), wx.HORIZONTAL
311
+ )
312
+ label_1 = wxStaticText(self, wx.ID_ANY, _("Material"))
313
+ filter_box.Add(label_1, 0, wx.ALIGN_CENTER_VERTICAL, 0)
314
+
315
+ self.txt_material = wxComboBox(
316
+ self, wx.ID_ANY, choices=materials, style=wx.CB_SORT
317
+ )
318
+ # self.txt_material = TextCtrl(self, wx.ID_ANY, "", limited=True)
319
+
320
+ filter_box.Add(self.txt_material, 1, wx.ALIGN_CENTER_VERTICAL, 0)
321
+
322
+ label_2 = wxStaticText(self, wx.ID_ANY, _("Thickness"))
323
+ filter_box.Add(label_2, 0, wx.ALIGN_CENTER_VERTICAL, 0)
324
+
325
+ self.txt_thickness = TextCtrl(self, wx.ID_ANY, "", limited=True)
326
+ filter_box.Add(self.txt_thickness, 1, wx.ALIGN_CENTER_VERTICAL, 0)
327
+
328
+ label_3 = wxStaticText(self, wx.ID_ANY, _("Laser"))
329
+ filter_box.Add(label_3, 0, wx.ALIGN_CENTER_VERTICAL, 0)
330
+
331
+ self.laser_choices = [
332
+ _("<All Lasertypes>"),
333
+ ]
334
+ dev_infos = list(self.context.find("provider/friendly"))
335
+ # Gets a list of tuples (description, key, path)
336
+ dev_infos.sort(key=lambda e: e[0][1])
337
+ for e in dev_infos:
338
+ self.laser_choices.append(e[0][0])
339
+
340
+ self.combo_lasertype = wxComboBox(
341
+ self,
342
+ wx.ID_ANY,
343
+ choices=self.laser_choices,
344
+ style=wx.CB_DROPDOWN | wx.CB_READONLY,
345
+ )
346
+ self.combo_lasertype.SetMaxSize(dip_size(self, 110, -1))
347
+
348
+ filter_box.Add(self.combo_lasertype, 1, wx.ALIGN_CENTER_VERTICAL, 0)
349
+
350
+ self.btn_reset = wxButton(self, wx.ID_ANY, _("Reset Filter"))
351
+ filter_box.Add(self.btn_reset, 0, wx.ALIGN_CENTER_VERTICAL, 0)
352
+ main_sizer.Add(filter_box, 0, wx.EXPAND, 0)
353
+ result_box = StaticBoxSizer(
354
+ self, wx.ID_ANY, _("Matching library entries"), wx.VERTICAL
355
+ )
356
+ self.tree_library = wxTreeCtrl(
357
+ self,
358
+ wx.ID_ANY,
359
+ style=wx.BORDER_SUNKEN | wx.TR_HAS_BUTTONS
360
+ # | wx.TR_HIDE_ROOT
361
+ | wx.TR_ROW_LINES | wx.TR_SINGLE,
362
+ )
363
+ self.tree_library.SetToolTip(_("Click to select / Right click for actions"))
364
+
365
+ self.list_preview = EditableListCtrl(
366
+ self,
367
+ wx.ID_ANY,
368
+ style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES | wx.LC_SINGLE_SEL,
369
+ context=self.context, list_name="list_materialmanager"
370
+ )
371
+
372
+ self.list_preview.AppendColumn(_("#"), format=wx.LIST_FORMAT_LEFT, width=55)
373
+ self.list_preview.AppendColumn(
374
+ _("Operation"),
375
+ format=wx.LIST_FORMAT_LEFT,
376
+ width=60,
377
+ )
378
+ self.list_preview.AppendColumn(_("Id"), format=wx.LIST_FORMAT_LEFT, width=60)
379
+ self.list_preview.AppendColumn(
380
+ _("Label"), format=wx.LIST_FORMAT_LEFT, width=100
381
+ )
382
+ self.list_preview.AppendColumn(
383
+ _("Power") + " [ppi]", format=wx.LIST_FORMAT_LEFT, width=50
384
+ )
385
+ self.list_preview.AppendColumn(
386
+ _("Speed") + " [mm/s]", format=wx.LIST_FORMAT_LEFT, width=50
387
+ )
388
+ self.list_preview.AppendColumn(
389
+ _("Frequency") + " [kHz]", format=wx.LIST_FORMAT_LEFT, width=50
390
+ )
391
+ self.list_preview.AppendColumn(
392
+ _("Passes"), format=wx.LIST_FORMAT_LEFT, width=50
393
+ )
394
+ self.list_preview.resize_columns()
395
+ self.list_preview.SetToolTip(_("Click to select / Right click for actions"))
396
+ self.opinfo = {
397
+ "op cut": ("Cut", icons8_laser_beam),
398
+ "op raster": ("Raster", icons8_direction),
399
+ "op image": ("Image", icons8_image),
400
+ "op engrave": ("Engrave", icons8_laserbeam_weak),
401
+ "op dots": ("Dots", icon_points),
402
+ "op hatch": ("Hatch", icon_hatch),
403
+ "generic": ("Generic", icons8_console),
404
+ }
405
+
406
+ param_box = StaticBoxSizer(self, wx.ID_ANY, _("Information"), wx.VERTICAL)
407
+
408
+ box1 = wx.BoxSizer(wx.HORIZONTAL)
409
+ box2 = wx.BoxSizer(wx.HORIZONTAL)
410
+ box3 = wx.BoxSizer(wx.HORIZONTAL)
411
+ box4 = wx.BoxSizer(wx.HORIZONTAL)
412
+ self.box_minimal = wx.BoxSizer(wx.VERTICAL)
413
+ self.box_extended = wx.BoxSizer(wx.VERTICAL)
414
+
415
+ param_box.Add(self.box_minimal, 0, wx.EXPAND, 0)
416
+ param_box.Add(self.box_extended, 0, wx.EXPAND, 0)
417
+
418
+ self.box_minimal.Add(box1, 0, wx.EXPAND, 0)
419
+ self.box_minimal.Add(box2, 0, wx.EXPAND, 0)
420
+ self.box_extended.Add(box3, 0, wx.EXPAND, 0)
421
+ self.box_extended.Add(box4, 0, wx.EXPAND, 0)
422
+
423
+ def size_it(ctrl, minsize, maxsize):
424
+ ctrl.SetMinSize(dip_size(self, minsize, -1))
425
+ ctrl.SetMaxSize(dip_size(self, maxsize, -1))
426
+
427
+ label = wxStaticText(self, wx.ID_ANY, _("Title"))
428
+ # size_it(label, 60, 100)
429
+ box1.Add(label, 0, wx.ALIGN_CENTER_VERTICAL, 0)
430
+ self.txt_entry_title = TextCtrl(self, wx.ID_ANY, "")
431
+ box1.Add(self.txt_entry_title, 1, wx.ALIGN_CENTER_VERTICAL, 0)
432
+
433
+ self.btn_set = wxButton(self, wx.ID_ANY, _("Set"))
434
+ self.btn_set.SetToolTip(
435
+ _(
436
+ "Change the name / lasertype of the current entry\nRight-Click: assign lasertype to all visible entries"
437
+ )
438
+ )
439
+
440
+ box1.Add(self.btn_set, 0, wx.ALIGN_CENTER_VERTICAL, 0)
441
+ self.btn_expand = wxButton(
442
+ self,
443
+ wx.ID_ANY,
444
+ )
445
+ self.btn_expand.SetSize(dip_size(self, 25, 25))
446
+ self.btn_expand.SetMinSize(dip_size(self, 25, 25))
447
+ self.btn_expand.SetMaxSize(dip_size(self, 25, 25))
448
+ box1.Add(self.btn_expand, 0, wx.ALIGN_CENTER_VERTICAL, 0)
449
+
450
+ label = wxStaticText(self, wx.ID_ANY, _("Material"))
451
+ size_it(label, 60, 100)
452
+ box2.Add(label, 0, wx.ALIGN_CENTER_VERTICAL, 0)
453
+ # self.txt_entry_material = TextCtrl(self, wx.ID_ANY, "")
454
+ self.txt_entry_material = wxComboBox(
455
+ self, wx.ID_ANY, choices=materials, style=wx.CB_SORT
456
+ )
457
+
458
+ box2.Add(self.txt_entry_material, 1, wx.ALIGN_CENTER_VERTICAL, 0)
459
+
460
+ label = wxStaticText(self, wx.ID_ANY, _("Thickness"))
461
+ size_it(label, 60, 100)
462
+ box2.Add(label, 0, wx.ALIGN_CENTER_VERTICAL, 0)
463
+ self.txt_entry_thickness = TextCtrl(self, wx.ID_ANY, "", limited=True)
464
+ box2.Add(self.txt_entry_thickness, 1, wx.ALIGN_CENTER_VERTICAL, 0)
465
+
466
+ label = wxStaticText(self, wx.ID_ANY, _("Laser"))
467
+ size_it(label, 60, 100)
468
+ box3.Add(label, 0, wx.ALIGN_CENTER_VERTICAL, 0)
469
+
470
+ choices = self.laser_choices # [1:]
471
+ self.combo_entry_type = wxComboBox(
472
+ self, wx.ID_ANY, choices=choices, style=wx.CB_DROPDOWN | wx.CB_READONLY
473
+ )
474
+ self.combo_entry_type.SetMaxSize(dip_size(self, 110, -1))
475
+
476
+ box3.Add(self.combo_entry_type, 1, wx.ALIGN_CENTER_VERTICAL, 0)
477
+ box3.AddSpacer(20)
478
+
479
+ label = wxStaticText(self, wx.ID_ANY, _("Id"))
480
+ size_it(label, 60, 100)
481
+ box3.Add(label, 0, wx.ALIGN_CENTER_VERTICAL, 0)
482
+ self.txt_entry_section = TextCtrl(
483
+ self,
484
+ wx.ID_ANY,
485
+ "",
486
+ limited=True,
487
+ check="empty",
488
+ )
489
+ box3.Add(self.txt_entry_section, 1, wx.ALIGN_CENTER_VERTICAL, 0)
490
+
491
+ label = wxStaticText(self, wx.ID_ANY, _("Power"))
492
+ size_it(label, 60, 100)
493
+ box4.Add(label, 0, wx.ALIGN_CENTER_VERTICAL, 0)
494
+ self.txt_entry_power = TextCtrl(
495
+ self,
496
+ wx.ID_ANY,
497
+ "",
498
+ limited=True,
499
+ )
500
+ unit = wxStaticText(self, wx.ID_ANY, _("W"))
501
+ box4.Add(self.txt_entry_power, 1, wx.ALIGN_CENTER_VERTICAL, 0)
502
+ box4.Add(unit, 0, wx.ALIGN_CENTER_VERTICAL, 0)
503
+ box4.AddSpacer(20)
504
+
505
+ label = wxStaticText(self, wx.ID_ANY, _("Lens-Size"))
506
+ size_it(label, 60, 100)
507
+ box4.Add(label, 0, wx.ALIGN_CENTER_VERTICAL, 0)
508
+ self.txt_entry_lens = TextCtrl(
509
+ self,
510
+ wx.ID_ANY,
511
+ "",
512
+ limited=True,
513
+ )
514
+ unit = wxStaticText(self, wx.ID_ANY, _("mm"))
515
+ box4.Add(self.txt_entry_lens, 1, wx.ALIGN_CENTER_VERTICAL, 0)
516
+ box4.Add(unit, 0, wx.ALIGN_CENTER_VERTICAL, 0)
517
+
518
+ self.txt_entry_note = TextCtrl(self, wx.ID_ANY, "", style=wx.TE_MULTILINE)
519
+ self.txt_entry_note.SetMinSize(dip_size(self, -1, 2 * 23))
520
+ self.box_extended.Add(self.txt_entry_note, 0, wx.EXPAND, 0)
521
+
522
+ result_box.Add(self.tree_library, 1, wx.EXPAND, 0)
523
+ result_box.Add(param_box, 0, wx.EXPAND, 0)
524
+ result_box.Add(self.list_preview, 1, wx.EXPAND, 0)
525
+
526
+ self.txt_material.SetToolTip(_("Filter entries with a certain title."))
527
+ self.txt_thickness.SetToolTip(
528
+ _("Filter entries with a certain material thickness.")
529
+ )
530
+ self.combo_lasertype.SetToolTip(_("Filter entries of a certain laser type"))
531
+
532
+ self.txt_entry_section.SetToolTip(_("Internal name of the library entry."))
533
+ self.txt_entry_material.SetToolTip(_("Name of the library entry."))
534
+ self.txt_entry_thickness.SetToolTip(_("Thickness of the material."))
535
+ self.combo_entry_type.SetToolTip(
536
+ _("Is this entry specific for a certain laser?")
537
+ )
538
+ self.txt_entry_note.SetToolTip(_("You can add additional information here."))
539
+
540
+ button_box = wx.BoxSizer(wx.VERTICAL)
541
+
542
+ self.btn_new = wxButton(self, wx.ID_ANY, _("Add new"))
543
+ self.btn_new.SetToolTip(_("Add a new library entry"))
544
+ self.btn_use_current = wxButton(self, wx.ID_ANY, _("Get current"))
545
+ self.btn_use_current.SetToolTip(_("Use the currently defined operations"))
546
+ self.btn_apply = wxButton(self, wx.ID_ANY, _("Load into Tree"))
547
+ self.btn_apply.SetToolTip(
548
+ _("Apply the current library entry to the operations branch")
549
+ )
550
+ self.btn_simple_apply = wxButton(self, wx.ID_ANY, _("Use for statusbar"))
551
+ self.btn_simple_apply.SetToolTip(
552
+ _("Use the current library entry for the statusbar icons")
553
+ )
554
+ self.btn_delete = wxButton(self, wx.ID_ANY, _("Delete"))
555
+ self.btn_delete.SetToolTip(_("Delete the current library entry"))
556
+ self.btn_duplicate = wxButton(self, wx.ID_ANY, _("Duplicate"))
557
+ self.btn_duplicate.SetToolTip(_("Duplicate the current library entry"))
558
+ self.btn_import = wxButton(self, wx.ID_ANY, _("Import"))
559
+ self.btn_import.SetToolTip(
560
+ _("Import a material library from ezcad or LightBurn")
561
+ )
562
+ self.btn_share = wxButton(self, wx.ID_ANY, _("Share"))
563
+ self.btn_share.SetToolTip(
564
+ _("Share the current library entry with the MeerK40t community")
565
+ )
566
+
567
+ button_box.Add(self.btn_new, 0, wx.EXPAND, 0)
568
+ button_box.Add(self.btn_use_current, 0, wx.EXPAND, 0)
569
+ button_box.Add(self.btn_apply, 0, wx.EXPAND, 0)
570
+ button_box.Add(self.btn_simple_apply, 0, wx.EXPAND, 0)
571
+ button_box.Add(self.btn_delete, 0, wx.EXPAND, 0)
572
+ button_box.Add(self.btn_duplicate, 0, wx.EXPAND, 0)
573
+ button_box.AddSpacer(self.btn_duplicate.Size[1])
574
+ button_box.Add(self.btn_import, 0, wx.EXPAND, 0)
575
+ button_box.Add(self.btn_share, 0, wx.EXPAND, 0)
576
+ outer_box = wx.BoxSizer(wx.HORIZONTAL)
577
+ outer_box.Add(result_box, 1, wx.EXPAND, 0)
578
+ outer_box.Add(button_box, 0, wx.EXPAND, 0)
579
+ main_sizer.Add(outer_box, 1, wx.EXPAND, 0)
580
+
581
+ self.SetSizer(main_sizer)
582
+ self.btn_reset.Bind(wx.EVT_BUTTON, self.on_reset)
583
+ self.combo_lasertype.Bind(wx.EVT_COMBOBOX, self.update_list)
584
+ self.txt_material.Bind(wx.EVT_TEXT, self.update_list)
585
+ self.txt_thickness.Bind(wx.EVT_TEXT, self.update_list)
586
+ self.btn_new.Bind(wx.EVT_BUTTON, self.on_new)
587
+ self.btn_use_current.Bind(wx.EVT_BUTTON, self.on_use_current)
588
+ self.btn_apply.Bind(wx.EVT_BUTTON, self.on_apply_tree)
589
+ self.btn_simple_apply.Bind(wx.EVT_BUTTON, self.on_apply_statusbar)
590
+ self.btn_delete.Bind(wx.EVT_BUTTON, self.on_delete)
591
+ self.btn_duplicate.Bind(wx.EVT_BUTTON, self.on_duplicate)
592
+ self.btn_import.Bind(wx.EVT_BUTTON, self.on_import)
593
+ self.btn_share.Bind(wx.EVT_BUTTON, self.on_share)
594
+ self.btn_expand.Bind(wx.EVT_BUTTON, self.toggle_extended)
595
+ self.btn_set.Bind(wx.EVT_BUTTON, self.update_entry)
596
+ self.btn_set.Bind(wx.EVT_RIGHT_DOWN, self.update_lasertype_for_all)
597
+ self.tree_library.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_list_selection)
598
+ self.list_preview.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_preview_selection)
599
+ self.list_preview.Bind(
600
+ wx.EVT_LIST_BEGIN_LABEL_EDIT, self.before_operation_update
601
+ )
602
+ self.list_preview.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.on_operation_update)
603
+
604
+ self.tree_library.Bind(
605
+ wx.EVT_RIGHT_DOWN,
606
+ self.on_library_rightclick,
607
+ )
608
+ self.Bind(
609
+ wx.EVT_LIST_ITEM_RIGHT_CLICK, self.on_preview_rightclick, self.list_preview
610
+ )
611
+ self.Bind(
612
+ wx.EVT_LIST_COL_RIGHT_CLICK, self.on_preview_rightclick, self.list_preview
613
+ )
614
+ self.Bind(wx.EVT_SIZE, self.on_resize)
615
+ self.SetupScrolling()
616
+ # Hide not-yet-supported functions
617
+ self.btn_share.Show(self.share_ready)
618
+ self.active_material = None
619
+ self.expanded_info = False
620
+ self.Layout()
621
+ self.on_resize(None)
622
+ self.on_reset(None)
623
+
624
+ @property
625
+ def expanded_info(self):
626
+ return self._expanded_info
627
+
628
+ @expanded_info.setter
629
+ def expanded_info(self, newvalue):
630
+ self._expanded_info = newvalue
631
+ info = dip_size(self, 20, 20)
632
+ icon_size = info[0] * self.context.root.bitmap_correction_scale
633
+ if self._expanded_info:
634
+ self.btn_expand.SetBitmap(icons8_caret_up.GetBitmap(resize=icon_size))
635
+ self.btn_expand.SetToolTip(_("Click to hide extended infos"))
636
+ else:
637
+ self.btn_expand.SetBitmap(icons8_caret_down.GetBitmap(resize=icon_size))
638
+ self.btn_expand.SetToolTip(_("Click to show extended infos"))
639
+ self.box_extended.ShowItems(self._expanded_info)
640
+ self.Layout()
641
+
642
+ def toggle_extended(self, event):
643
+ self.expanded_info = not self.expanded_info
644
+
645
+ @property
646
+ def active_material(self):
647
+ return self._active_material
648
+
649
+ @active_material.setter
650
+ def active_material(self, newvalue):
651
+ self._active_material = newvalue
652
+ active = bool(newvalue is not None)
653
+ self.btn_apply.Enable(active)
654
+ self.btn_simple_apply.Enable(active)
655
+ self.btn_delete.Enable(active)
656
+ self.btn_duplicate.Enable(active)
657
+ self.txt_entry_section.Enable(active)
658
+ self.txt_entry_material.Enable(active)
659
+ self.txt_entry_lens.Enable(active)
660
+ self.txt_entry_power.Enable(active)
661
+ self.txt_entry_title.Enable(active)
662
+ self.txt_entry_thickness.Enable(active)
663
+ self.txt_entry_note.Enable(active)
664
+ self.combo_entry_type.Enable(active)
665
+ self.btn_set.Enable(active)
666
+ self.list_preview.Enable(active)
667
+ self.fill_preview()
668
+
669
+ @property
670
+ def is_balor(self):
671
+ if self.active_material is None:
672
+ return False
673
+ # a) laser-settings are set to fibre = 3
674
+ # b) laser-settings are set to general and we have a defined fibre laser
675
+ # Will be updated in fill_preview
676
+ return self._balor
677
+
678
+ def _add_deletion_method(self, level=0, keyprimary=None, primaryvalue=None, keysecondary=None, secondaryvalue=None)->int:
679
+ index = -1
680
+ while index in self.deletion_methods:
681
+ index -= 1
682
+ self.deletion_methods[index] = (level, keyprimary, primaryvalue, keysecondary, secondaryvalue)
683
+ return index
684
+
685
+ def retrieve_material_list(
686
+ self,
687
+ filtername=None,
688
+ filterlaser=None,
689
+ filterthickness=None,
690
+ reload=True,
691
+ setter=None,
692
+ ):
693
+ if reload:
694
+ self.material_list.clear()
695
+ self.deletion_methods = dict()
696
+ for section in self.op_data.section_set():
697
+ if section == "previous":
698
+ continue
699
+ count = 0
700
+ secname = section
701
+ secdesc = ""
702
+ sectitle = ""
703
+ thick = ""
704
+ ltype = 0 # All lasers
705
+ note = ""
706
+ for subsection in self.op_data.derivable(secname):
707
+ if subsection.endswith(" info"):
708
+ secdesc = self.op_data.read_persistent(
709
+ str, subsection, "material", ""
710
+ )
711
+ sectitle = self.op_data.read_persistent(
712
+ str, subsection, "title", ""
713
+ )
714
+ thick = self.op_data.read_persistent(
715
+ str, subsection, "thickness", ""
716
+ )
717
+ ltype = self.op_data.read_persistent(
718
+ int, subsection, "laser", 0
719
+ )
720
+ note = self.op_data.read_persistent(str, subsection, "note", "")
721
+ else:
722
+ count += 1
723
+ if not sectitle:
724
+ sectitle = secname.replace("_", " ")
725
+ entry = {
726
+ "section": secname,
727
+ "material": secdesc,
728
+ "title": sectitle,
729
+ "laser": ltype,
730
+ "thickness": thick,
731
+ "note": note,
732
+ "opcount": count,
733
+ }
734
+ # entry = [secname, secdesc, count, ltype, thick, note]
735
+ self.material_list[secname] = entry
736
+ listidx = -1
737
+ self.display_list.clear()
738
+ display = []
739
+
740
+ if self.categorisation == 1:
741
+ # lasertype
742
+ sort_key_primary = "laser" # 3
743
+ sort_key_secondary = "material" # 1
744
+ sort_key_tertiary = "thickness" # 4
745
+ elif self.categorisation == 2:
746
+ # thickness
747
+ sort_key_primary = "thickness" # 4
748
+ sort_key_secondary = "material"
749
+ sort_key_tertiary = "laser" # 3
750
+ else:
751
+ # material
752
+ sort_key_primary = "material"
753
+ sort_key_secondary = "thickness" # 4
754
+ sort_key_tertiary = "laser" # 3
755
+ for key, entry in self.material_list.items():
756
+ listidx += 1
757
+ display.append((entry, listidx))
758
+ display.sort(
759
+ key=lambda e: (
760
+ e[0][sort_key_primary],
761
+ e[0][sort_key_secondary],
762
+ e[0][sort_key_tertiary],
763
+ )
764
+ )
765
+
766
+ busy = wx.BusyCursor()
767
+ tree = self.tree_library
768
+ tree.Freeze()
769
+ tree.DeleteAllItems()
770
+ tree_root = tree.AddRoot(_("Materials"))
771
+ # Save a delete all...
772
+ data_idx = self._add_deletion_method(0, None, None, None, None)
773
+
774
+ tree.SetItemData(tree_root, data_idx)
775
+ idx_primary = 0
776
+ idx_secondary = 0
777
+ newvalue = None
778
+ selected = None
779
+ first_item = None
780
+ selected_parent = None
781
+ last_category_primary = None
782
+ last_category_secondary = None
783
+ tree_primary = tree_root
784
+ tree_secondary = tree_root
785
+ visible_count = [0, 0] # All, subsections
786
+ for content in display:
787
+ entry = content[0]
788
+ listidx = content[1]
789
+ ltype = entry["laser"]
790
+ if ltype is None:
791
+ ltype = 0
792
+ if 0 <= ltype < len(self.laser_choices):
793
+ info = self.laser_choices[ltype]
794
+ else:
795
+ info = "???"
796
+ if sort_key_primary == "laser": # laser
797
+ this_category_primary = info
798
+ else:
799
+ this_category_primary = entry[sort_key_primary].replace("_", " ")
800
+ if sort_key_secondary == 3: # laser
801
+ this_category_secondary = info
802
+ else:
803
+ this_category_secondary = entry[sort_key_secondary].replace("_", " ")
804
+ if not this_category_primary:
805
+ # _("No laser")
806
+ # _("No material")
807
+ # _("No thickness")
808
+ this_category_primary = _("No " + sort_key_primary)
809
+ key = entry["section"]
810
+ if (
811
+ filtername is not None
812
+ and filtername.lower() not in entry["material"].lower()
813
+ ):
814
+ continue
815
+ if filterthickness is not None and not entry[
816
+ "thickness"
817
+ ].lower().startswith(filterthickness.lower()):
818
+ continue
819
+ if filterlaser is not None:
820
+ if filterlaser not in (0, entry["laser"]):
821
+ continue
822
+ self.display_list.append(entry)
823
+ visible_count[0] += 1
824
+ if last_category_primary != this_category_primary:
825
+ # New item
826
+ last_category_secondary = ""
827
+ idx_primary += 1
828
+ idx_secondary = 0
829
+ tree_primary = tree.AppendItem(tree_root, this_category_primary)
830
+ data_idx = self._add_deletion_method(1, sort_key_primary, this_category_primary, sort_key_secondary, "")
831
+ tree.SetItemData(tree_primary, data_idx)
832
+
833
+ tree_secondary = tree_primary
834
+ if last_category_secondary != this_category_secondary:
835
+ # new subitem
836
+ tree_secondary = tree.AppendItem(tree_primary, this_category_secondary)
837
+ data_idx = self._add_deletion_method(2, sort_key_primary, this_category_primary, sort_key_secondary, this_category_secondary)
838
+ tree.SetItemData(tree_secondary, data_idx)
839
+ visible_count[1] += 1
840
+ idx_secondary += 1
841
+
842
+ description = f"#{idx_primary}.{idx_secondary} - {entry['title']}, {entry['thickness']} ({info}, {entry['opcount']} ops)"
843
+ tree_id = tree.AppendItem(tree_secondary, description)
844
+ tree.SetItemData(tree_id, listidx)
845
+ if first_item is None:
846
+ first_item = tree_id
847
+ if key == setter:
848
+ newvalue = key
849
+ selected = tree_id
850
+ selected_parent = tree_primary
851
+
852
+ last_category_primary = this_category_primary
853
+ last_category_secondary = this_category_secondary
854
+
855
+ self.active_material = newvalue
856
+ tree.Expand(tree_root)
857
+ # if visible_count[0] <= 10:
858
+ # tree.ExpandAllChildren(tree_root)
859
+ if visible_count[1] == 1:
860
+ tree.ExpandAllChildren(tree_root)
861
+ elif visible_count[1] <= 10:
862
+ child, cookie = tree.GetFirstChild(tree_root)
863
+ while child.IsOk():
864
+ tree.Expand(child)
865
+ child, cookie = tree.GetNextChild(tree_root, cookie)
866
+
867
+ if selected is None:
868
+ if visible_count[0] == 1: # Just one, why don't we select it
869
+ self.tree_library.SelectItem(first_item)
870
+ else:
871
+ tree.ExpandAllChildren(selected_parent)
872
+ self.tree_library.SelectItem(selected)
873
+ tree.Thaw()
874
+ tree.Refresh()
875
+ del busy
876
+
877
+ @staticmethod
878
+ def get_nth_dict_entry(dictionary: dict, n=0):
879
+ if n < 0:
880
+ n += len(dictionary)
881
+ for i, key in enumerate(dictionary.keys()):
882
+ if i == n:
883
+ return key
884
+ return None
885
+
886
+ def get_nth_material(self, n=0):
887
+ return self.get_nth_dict_entry(self.material_list, n)
888
+
889
+ def get_nth_operation(self, n=0):
890
+ return self.get_nth_dict_entry(self.operation_list, n)
891
+
892
+ def on_share(self, event):
893
+ if self.active_material is None:
894
+ return
895
+
896
+ self.context.setting(str, "author", "")
897
+ last_author = self.context.author
898
+ if last_author is None:
899
+ last_author = ""
900
+ dlg = wx.TextEntryDialog(
901
+ self,
902
+ _(
903
+ "Thank you for your willingness to share your material setting with the MeerK40t community.\n"
904
+ + "Please provide a name to honor your authorship."
905
+ ),
906
+ caption=_("Share material setting"),
907
+ value=last_author,
908
+ )
909
+ dlg.SetValue(last_author)
910
+ res = dlg.ShowModal()
911
+ last_author = dlg.GetValue()
912
+ dlg.Destroy()
913
+ if res == wx.ID_CANCEL:
914
+ return
915
+ self.context.author = last_author
916
+ # We will store the relevant section in a separate file
917
+ oplist, opinfo = self.context.elements.load_persistent_op_list(
918
+ self.active_material,
919
+ use_settings=self.op_data,
920
+ )
921
+ if len(oplist) == 0:
922
+ return
923
+ opinfo["author"] = last_author
924
+ directory = self.context.kernel.os_information["WORKDIR"]
925
+ local_file = os.path.join(directory, "op_export.cfg")
926
+ if os.path.exists(local_file):
927
+ try:
928
+ os.remove(local_file)
929
+ except (OSError, PermissionError):
930
+ return
931
+ settings = Settings(
932
+ self.context.kernel.name, "op_export.cfg", ignore_settings=False
933
+ )
934
+ opsection = f"{self.active_material} info"
935
+ for key, value in opinfo.items():
936
+ settings.write_persistent(opsection, key, value)
937
+
938
+ def _save_tree(name, op_list):
939
+ for i, op in enumerate(op_list):
940
+ if hasattr(op, "allow_save"):
941
+ if not op.allow_save():
942
+ continue
943
+ if op.type == "reference":
944
+ # We do not save references.
945
+ continue
946
+ section = f"{name} {i:06d}"
947
+ settings.write_persistent(section, "type", op.type)
948
+ op.save(settings, section)
949
+ try:
950
+ _save_tree(section, op.children)
951
+ except AttributeError:
952
+ pass
953
+
954
+ _save_tree(opsection, oplist)
955
+ settings.write_configuration()
956
+ # print ("Sharing")
957
+ try:
958
+ with open(local_file, "r") as f:
959
+ data = f.read()
960
+ except (OSError, RuntimeError, PermissionError, FileNotFoundError):
961
+ return
962
+ self.send_data_to_community(local_file, data)
963
+
964
+ def send_data_to_community(self, filename, data):
965
+ """
966
+ Sends the material setting to a server using rfc1341 7.2 The multipart Content-Type
967
+ https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
968
+
969
+ @param filename: filename to use when sending file
970
+ @param data: data to send
971
+ @return:
972
+ """
973
+ import socket
974
+
975
+ MEERK40T_HOST = "dev.meerk40t.com"
976
+
977
+ host = MEERK40T_HOST # Replace with the actual host
978
+ port = 80 # Replace with the actual port
979
+
980
+ # Construct the HTTP request
981
+ boundary = "----------------meerk40t-material"
982
+ body = (
983
+ f"--{boundary}\r\n"
984
+ f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'
985
+ f"Content-Type: text/plain\r\n"
986
+ "\r\n"
987
+ f"{data}\r\n"
988
+ f"--{boundary}--\r\n"
989
+ )
990
+
991
+ headers = (
992
+ f"POST /upload HTTP/1.1\r\n"
993
+ f"Host: {host}\r\n"
994
+ "User-Agent: meerk40t/1.0.0\r\n"
995
+ f"Content-Type: multipart/form-data; boundary={boundary}\r\n"
996
+ f"Content-Length: {len(body)}\r\n"
997
+ "\r\n"
998
+ )
999
+
1000
+ try:
1001
+ # Create a socket connection
1002
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
1003
+ client_socket.connect((host, port))
1004
+
1005
+ # Send the request
1006
+ request = f"{headers}{body}"
1007
+ client_socket.sendall(request.encode())
1008
+
1009
+ # Receive and print the response
1010
+ response = client_socket.recv(4096)
1011
+ response = response.decode("utf-8", errors="ignore")
1012
+ except Exception:
1013
+ response = ""
1014
+
1015
+ response_lines = response.split("\n")
1016
+ http_code = response_lines[0]
1017
+
1018
+ # print(response)
1019
+
1020
+ if http_code.startswith("HTTP/1.1 200 OK"):
1021
+ message = response_lines[-1]
1022
+ dlg = wx.MessageDialog(
1023
+ None,
1024
+ _("We got your file. Thank you for helping\n\n") + message,
1025
+ _("Thanks"),
1026
+ wx.OK,
1027
+ )
1028
+ dlg.ShowModal()
1029
+ dlg.Destroy()
1030
+ else:
1031
+ # print(response)
1032
+ dlg = wx.MessageDialog(
1033
+ None,
1034
+ _("We're sorry, that didn't work.\n\n") + "\n\n" + str(http_code),
1035
+ _("Thanks"),
1036
+ wx.OK,
1037
+ )
1038
+ dlg.ShowModal()
1039
+ dlg.Destroy()
1040
+
1041
+ def on_duplicate(self, event):
1042
+ if self.active_material is None:
1043
+ return
1044
+ op_list, op_info = self.context.elements.load_persistent_op_list(
1045
+ self.active_material,
1046
+ use_settings=self.op_data,
1047
+ )
1048
+ if len(op_list) == 0:
1049
+ return
1050
+ oldsection = self.active_material
1051
+ if oldsection.endswith(")"):
1052
+ idx = oldsection.rfind("(")
1053
+ if idx >= 0:
1054
+ oldsection = oldsection[:idx]
1055
+ if oldsection.endswith("_"):
1056
+ oldsection = oldsection[:-1]
1057
+
1058
+ counter = 0
1059
+ while True:
1060
+ counter += 1
1061
+ newsection = f"{oldsection}_({counter})"
1062
+ if newsection not in self.material_list:
1063
+ break
1064
+
1065
+ oldname = oldsection
1066
+ if "title" in op_info:
1067
+ oldname = op_info["title"]
1068
+ if oldname.endswith(")"):
1069
+ idx = oldname.rfind("(")
1070
+ if idx >= 0:
1071
+ oldname = oldname[:idx]
1072
+ newname = f"{oldname} ({counter})"
1073
+ op_info["title"] = newname
1074
+ self.context.elements.save_persistent_operations_list(
1075
+ newsection,
1076
+ oplist=op_list,
1077
+ opinfo=op_info,
1078
+ inform=False,
1079
+ use_settings=self.op_data,
1080
+ )
1081
+ self.op_data.write_configuration()
1082
+ self.retrieve_material_list(reload=True, setter=newsection)
1083
+
1084
+ def on_delete(self, event):
1085
+ if self.active_material is None:
1086
+ return
1087
+ if self.context.kernel.yesno(
1088
+ _("Do you really want to delete this entry? This can't be undone.")
1089
+ ):
1090
+ self.context.elements.clear_persistent_operations(
1091
+ self.active_material,
1092
+ use_settings=self.op_data,
1093
+ )
1094
+ self.op_data.write_configuration()
1095
+ self.retrieve_material_list(reload=True)
1096
+
1097
+ def on_delete_all(self, event):
1098
+ self._delete_according_to_key(keytype=0, primary="", secondary="")
1099
+
1100
+ def on_delete_category(self, keytype:int, primary:any, secondary:any):
1101
+ def handler(event):
1102
+ self._delete_according_to_key(keytype=keytype, primary=primary, secondary=secondary)
1103
+ return handler
1104
+
1105
+ def _delete_according_to_key(self, keytype: int, primary:str, secondary:str):
1106
+ if self.categorisation == 1:
1107
+ # lasertype
1108
+ sort_key_primary = "laser" # 3
1109
+ sort_key_secondary = "material" # 1
1110
+ elif self.categorisation == 2:
1111
+ # thickness
1112
+ sort_key_primary = "thickness" # 4
1113
+ sort_key_secondary = "material"
1114
+ else:
1115
+ # material
1116
+ sort_key_primary = "material"
1117
+ sort_key_secondary = "thickness" # 4
1118
+ # Establish the amount of to be deleted entries
1119
+ amount = 0
1120
+ for entry in self.display_list:
1121
+ to_delete = False
1122
+ if keytype == 0:
1123
+ to_delete = True
1124
+ elif (
1125
+ keytype == 1 and
1126
+ entry[sort_key_primary].replace("_", " ") == primary
1127
+ ):
1128
+ to_delete = True
1129
+ elif (
1130
+ keytype == 2 and
1131
+ entry[sort_key_primary].replace("_", " ") == primary and
1132
+ entry[sort_key_secondary].replace("_", " ") == secondary
1133
+ ):
1134
+ to_delete = True
1135
+ if to_delete:
1136
+ amount += 1
1137
+
1138
+ if keytype == 0:
1139
+ question = _("Do you really want to delete all {num} visible entries? This can't be undone.").format(num=str(amount))
1140
+ else:
1141
+ criteria = f"{sort_key_primary}={'<empty>' if primary is None else primary}"
1142
+ if secondary is not None:
1143
+ criteria = criteria + f" & {sort_key_secondary}='{secondary}'"
1144
+ question = _("Do you really want to delete all {num} entries with {data}? This can't be undone.").format(data=criteria, num=str(amount))
1145
+ if self.context.kernel.yesno(question):
1146
+ busy = self.context.kernel.busyinfo
1147
+ busy.start(msg=_("Deleting data"))
1148
+ for idx, entry in enumerate(self.display_list):
1149
+ busy.change(msg=f"{idx+1}/{len(self.display_list)}", keep=1)
1150
+
1151
+ to_delete = False
1152
+ prim_key = entry[sort_key_primary].replace("_", " ") if entry[sort_key_primary] else _("No " + sort_key_primary)
1153
+ if keytype == 0:
1154
+ to_delete = True
1155
+ elif (
1156
+ keytype == 1 and
1157
+ prim_key == primary
1158
+ ):
1159
+ to_delete = True
1160
+ elif (
1161
+ keytype == 2 and
1162
+ prim_key == primary and
1163
+ prim_key == secondary
1164
+ ):
1165
+ to_delete = True
1166
+
1167
+ # print (f"Keytype={keytype}, primary: {prim_key} vs {primary}, secondary: {entry[sort_key_secondary].replace('_', ' ')} vs {secondary} -> {to_delete}")
1168
+
1169
+ if to_delete:
1170
+ material = entry["section"]
1171
+ self.context.elements.clear_persistent_operations(
1172
+ material, use_settings=self.op_data, flush=False
1173
+ )
1174
+ self.op_data.write_configuration()
1175
+ busy.end()
1176
+ self.on_reset(None)
1177
+
1178
+
1179
+ def invalid_file(self, filename):
1180
+ dlg = wx.MessageDialog(
1181
+ self,
1182
+ _("Unrecognized format in file {info}".format(info=filename)),
1183
+ _("Invalid file"),
1184
+ wx.OK | wx.ICON_WARNING,
1185
+ )
1186
+ dlg.ShowModal()
1187
+ dlg.Destroy()
1188
+
1189
+ def import_lightburn(self, info):
1190
+ # info = (fname, old_lens, new_lens, old_power, new_power, factor_from_lens, factor_from_power, factor, consolidate)
1191
+ filename = info[0]
1192
+ factor = info[7]
1193
+ join_entries = info[8]
1194
+ lens_info = info[2]
1195
+ if lens_info is None:
1196
+ lens_info = ""
1197
+ power_info = info[4]
1198
+ if power_info is None:
1199
+ power_info = ""
1200
+
1201
+ if not os.path.exists(filename):
1202
+ return False
1203
+ added = False
1204
+ try:
1205
+ tree = ET.parse(filename)
1206
+ except ET.ParseError:
1207
+ self.invalid_file(filename)
1208
+ return False
1209
+
1210
+ root = tree.getroot()
1211
+ if root.tag.lower() != "lightburnlibrary":
1212
+ self.invalid_file(filename)
1213
+ return False
1214
+
1215
+ # We need to have a new id...
1216
+ new_import_id = 0
1217
+ pattern = "import_"
1218
+ for section in self.op_data.section_set():
1219
+ if section.startswith(pattern):
1220
+ s = section[len(pattern) :]
1221
+ try:
1222
+ number = int(s)
1223
+ except ValueError:
1224
+ number = 0
1225
+ if number > new_import_id:
1226
+ new_import_id = number
1227
+
1228
+ # def traverse(node):
1229
+ # print(f"Node: {node.tag}")
1230
+ # for attr in node.attrib:
1231
+ # print(f"Attribute: {attr} = {node.attrib[attr]}")
1232
+ # for child in node:
1233
+ # traverse(child)
1234
+ # traverse(root)
1235
+ operation_ids = dict()
1236
+
1237
+ for material_node in root:
1238
+ material = material_node.attrib["name"]
1239
+ last_thickness = None
1240
+ for entry_node in material_node:
1241
+ thickness = entry_node.attrib.get("Thickness", "-1")
1242
+ try:
1243
+ thickness_value = float(thickness)
1244
+ except ValueError:
1245
+ thickness_value = -1
1246
+ if thickness_value < 0:
1247
+ thickness = ""
1248
+ desc = entry_node.attrib.get("Desc", "")
1249
+ title = entry_node.attrib.get("NoThickTitle", "")
1250
+ label = desc
1251
+ if last_thickness == thickness and join_entries:
1252
+ # We keep those together
1253
+ pass
1254
+ else:
1255
+ operation_ids.clear()
1256
+ operation_ids["op engrave"] = ["E", 0]
1257
+ operation_ids["op raster"] = ["R", 0]
1258
+ operation_ids["op cut"] = ["C", 0]
1259
+ operation_ids["op image"] = ["I", 0]
1260
+ new_import_id += 1
1261
+ sect_num = -1
1262
+ sect = f"{pattern}{new_import_id:0>4}"
1263
+ info_section_name = f"{sect} info"
1264
+ self.op_data.write_persistent(info_section_name, "title", title)
1265
+ self.op_data.write_persistent(
1266
+ info_section_name, "material", material
1267
+ )
1268
+ self.op_data.write_persistent(info_section_name, "laser", 0)
1269
+ self.op_data.write_persistent(
1270
+ info_section_name, "thickness", thickness
1271
+ )
1272
+ self.op_data.write_persistent(
1273
+ info_section_name, "power", power_info
1274
+ )
1275
+ self.op_data.write_persistent(info_section_name, "lens", lens_info)
1276
+ note = label
1277
+ last_thickness = thickness
1278
+ added = True
1279
+ for cutsetting_node in entry_node:
1280
+ powerval = None
1281
+ speedval = None
1282
+ sect_num += 1
1283
+ section_name = f"{sect} {sect_num:0>6}"
1284
+ cut_type = cutsetting_node.attrib.get("type", "Scan")
1285
+ if cut_type.lower() == "cut":
1286
+ op_type = "op engrave"
1287
+ elif cut_type.lower() == "scan":
1288
+ op_type = "op raster"
1289
+ elif cut_type.lower() == "image":
1290
+ op_type = "op image"
1291
+ else:
1292
+ op_type = "op engrave"
1293
+ if op_type in operation_ids:
1294
+ operation_ids[op_type][1] += 1
1295
+ else:
1296
+ operation_ids[op_type] = [op_type[3].upper(), 1]
1297
+
1298
+ self.op_data.write_persistent(section_name, "type", op_type)
1299
+ self.op_data.write_persistent(
1300
+ section_name,
1301
+ "id",
1302
+ f"{operation_ids[op_type][0]}{operation_ids[op_type][1]}",
1303
+ )
1304
+ self.op_data.write_persistent(section_name, "label", label)
1305
+
1306
+ numeric_value = 0
1307
+ for param_node in cutsetting_node:
1308
+ param = param_node.tag.lower()
1309
+ value = param_node.attrib.get("Value", "")
1310
+ if not value:
1311
+ continue
1312
+ try:
1313
+ numeric_value = float(value)
1314
+ except ValueError:
1315
+ continue
1316
+
1317
+ if param == "numpasses":
1318
+ if numeric_value != 0:
1319
+ self.op_data.write_persistent(
1320
+ section_name, "passes", numeric_value
1321
+ )
1322
+ self.op_data.write_persistent(
1323
+ section_name, "passes_custom", True
1324
+ )
1325
+ elif param == "speed":
1326
+ if numeric_value != 0:
1327
+ speedval = numeric_value
1328
+ elif param == "maxpower":
1329
+ if numeric_value != 0:
1330
+ powerval = numeric_value * 10
1331
+ elif param == "frequency":
1332
+ # khz
1333
+ if numeric_value != 0:
1334
+ self.op_data.write_persistent(
1335
+ section_name, "frequency", numeric_value / 1000.0
1336
+ )
1337
+ elif param == "jumpspeed":
1338
+ if numeric_value != 0:
1339
+ self.op_data.write_persistent(
1340
+ section_name, "rapid_enabled", True
1341
+ )
1342
+ self.op_data.write_persistent(
1343
+ section_name, "rapid_speed", numeric_value
1344
+ )
1345
+ else:
1346
+ # note += f"\\n{param} = {numeric_value}"
1347
+ pass
1348
+ # Ready, let's write power and speed
1349
+ if factor != 1:
1350
+ old_l = info[1]
1351
+ new_l = info[2]
1352
+ factor_l = info[5]
1353
+ if old_l is not None:
1354
+ note += f"\\n({cut_type}) Converted lens-size {old_l}mm -> {new_l}mm: {factor_l:.2}"
1355
+ old_l = info[3]
1356
+ new_l = info[4]
1357
+ factor_l = info[6]
1358
+ if old_l is not None:
1359
+ note += f"\\n({cut_type}) Converted power {old_l}W -> {new_l}W: {factor_l:.2}"
1360
+ if powerval * factor > 1000:
1361
+ # Too much, let's reduce speed instead
1362
+ if speedval:
1363
+ note += f"\\n({cut_type}) Needed to reduce speed {speedval:.1}mm/s -> {speedval / factor:.2}mm/s"
1364
+ speedval *= 1 / factor
1365
+ else:
1366
+ powerval *= factor
1367
+ self.op_data.write_persistent(section_name, "speed", speedval)
1368
+ self.op_data.write_persistent(section_name, "power", powerval)
1369
+ self.op_data.write_persistent(info_section_name, "note", note)
1370
+
1371
+ return added
1372
+
1373
+ def import_meerk40t(self, info):
1374
+ filename = info[0]
1375
+ factor = info[7]
1376
+ lens_info = info[2]
1377
+ if lens_info is None:
1378
+ lens_info = ""
1379
+ power_info = info[4]
1380
+ if power_info is None:
1381
+ power_info = ""
1382
+ if not os.path.exists(filename):
1383
+ return False
1384
+ elems = self.context.elements
1385
+ settings = Settings(None, filename, create_backup=False)
1386
+ added = False
1387
+
1388
+ # Load operation list from file and adjust power/speed if needed
1389
+ for section in settings.section_set():
1390
+ if section == "previous":
1391
+ continue
1392
+ target_section = section
1393
+ idx = 0
1394
+ ex_list = list(self.op_data.section_set())
1395
+ while target_section in ex_list:
1396
+ idx += 1
1397
+ target_section = f"{section}-{idx}"
1398
+ # Remember existing ids in operations....
1399
+ uid = {}
1400
+ oplist, opinfo = elems.load_persistent_op_list(
1401
+ section, use_settings=settings
1402
+ )
1403
+ note = ""
1404
+ for op in oplist:
1405
+ added = True
1406
+ powerval = 1000.0
1407
+ speedval = 10.0
1408
+ if hasattr(op, "power") and op.power is not None:
1409
+ try:
1410
+ powerval = float(op.power)
1411
+ except ValueError as e:
1412
+ if str(op.power).endswith("%"):
1413
+ try:
1414
+ powerval = 10.0 * float(str(op.power)[:-1])
1415
+ except ValueError:
1416
+ pass
1417
+ if hasattr(op, "speed") and op.speed is not None:
1418
+ try:
1419
+ speedval = float(op.speed)
1420
+ except ValueError:
1421
+ pass
1422
+ if factor != 1:
1423
+ old_l = info[1]
1424
+ new_l = info[2]
1425
+ factor_l = info[5]
1426
+ if old_l is not None:
1427
+ note += f"\\nConverted lens-size {old_l}mm -> {new_l}mm: {factor_l:.2}"
1428
+ old_l = info[3]
1429
+ new_l = info[4]
1430
+ factor_l = info[6]
1431
+ if old_l is not None:
1432
+ note += (
1433
+ f"\\nConverted power {old_l}W -> {new_l}W: {factor_l:.2}"
1434
+ )
1435
+ if powerval and powerval * factor > 1000:
1436
+ # Too much, let's reduce speed instead
1437
+ if speedval:
1438
+ note += f"\\nNeeded to reduce speed {speedval:.1}mm/s -> {speedval / factor:.2}mm/s"
1439
+ speedval *= 1 / factor
1440
+ else:
1441
+ if powerval:
1442
+ powerval *= factor
1443
+ if powerval:
1444
+ op.power = powerval
1445
+ if speedval:
1446
+ op.speed = speedval
1447
+ # op.note = note
1448
+ # Do we have a duplicate id?
1449
+ if op.id in uid or op.id is None or op.id == "":
1450
+ idx = 1
1451
+ pattern = op.type[3].upper()
1452
+ while f"{pattern}{idx}" in uid:
1453
+ idx += 1
1454
+ op.id = f"{pattern}{idx}"
1455
+
1456
+ # Add id to list of existing ids
1457
+ uid[op.id] = op
1458
+ elems.save_persistent_operations_list(
1459
+ target_section,
1460
+ oplist=oplist,
1461
+ opinfo=opinfo,
1462
+ inform=False,
1463
+ use_settings=self.op_data,
1464
+ )
1465
+
1466
+ return added
1467
+
1468
+ def import_ezcad(self, info):
1469
+ # info = (fname, old_lens, new_lens, old_power, new_power, factor_from_lens, factor_from_power, factor, consolidate)
1470
+ filename = info[0]
1471
+ factor = info[7]
1472
+ lens_info = info[2]
1473
+ if lens_info is None:
1474
+ lens_info = ""
1475
+ power_info = info[4]
1476
+ if power_info is None:
1477
+ power_info = ""
1478
+ if not os.path.exists(filename):
1479
+ return False
1480
+ added = False
1481
+ # We can safely assume this a fibre laser...
1482
+ laser_type = 0
1483
+ for idx, desc in enumerate(self.laser_choices):
1484
+ if "fibre" in desc.lower():
1485
+ laser_type = idx
1486
+ break
1487
+ try:
1488
+ with open(filename, "r") as f:
1489
+ # We need to have a new id...
1490
+ new_import_id = 0
1491
+ pattern = "import_"
1492
+ for section in self.op_data.section_set():
1493
+ if section.startswith(pattern):
1494
+ s = section[len(pattern) :]
1495
+ try:
1496
+ number = int(s)
1497
+ except ValueError:
1498
+ number = 0
1499
+ if number > new_import_id:
1500
+ new_import_id = number
1501
+ section_name = ""
1502
+ info_section_name = ""
1503
+ info_box = ""
1504
+ powerval = None
1505
+ speedval = None
1506
+ numeric_value = 0
1507
+
1508
+ while True:
1509
+ line = f.readline()
1510
+ if not line:
1511
+ break
1512
+ line = line.strip()
1513
+ if line.startswith("["):
1514
+ if info_box and info_section_name:
1515
+ self.op_data.write_persistent(
1516
+ info_section_name, "note", info_box
1517
+ )
1518
+ if powerval and section_name:
1519
+ if factor != 1:
1520
+ old_l = info[1]
1521
+ new_l = info[2]
1522
+ factor_l = info[5]
1523
+ if old_l is not None:
1524
+ if info_box:
1525
+ info_box += "\\n"
1526
+ info_box += f"Converted lens-size {old_l}mm -> {new_l}mm: {factor_l:.2}"
1527
+ old_l = info[3]
1528
+ new_l = info[4]
1529
+ factor_l = info[6]
1530
+ if old_l is not None:
1531
+ if info_box:
1532
+ info_box += "\\n"
1533
+ info_box += f"Converted power {old_l}W -> {new_l}W: {factor_l:.2}"
1534
+ if powerval * factor > 1000:
1535
+ # Too much, let's reduce speed instead
1536
+ if speedval:
1537
+ if info_box:
1538
+ info_box += "\\n"
1539
+ info_box += f"Needed to reduce speed {speedval:.1}mm/s -> {speedval / factor:.2}mm/s"
1540
+ speedval *= 1 / factor
1541
+ else:
1542
+ powerval *= factor
1543
+ self.op_data.write_persistent(
1544
+ section_name, "speed", speedval
1545
+ )
1546
+ self.op_data.write_persistent(
1547
+ section_name, "power", powerval
1548
+ )
1549
+ powerval = None
1550
+ speedval = None
1551
+ info_box = ""
1552
+ new_import_id += 1
1553
+ sect = f"{pattern}{new_import_id:0>4}"
1554
+ info_section_name = f"{sect} info"
1555
+ section_name = f"{sect} {0:0>6}"
1556
+ matname = line[1:-1]
1557
+ if matname.startswith("F "):
1558
+ matname = matname[2:]
1559
+ self.op_data.write_persistent(
1560
+ info_section_name, "material", matname
1561
+ )
1562
+ title = matname
1563
+ if power_info:
1564
+ title += f" {power_info}W"
1565
+ if lens_info:
1566
+ title += f" {lens_info}mm"
1567
+ self.op_data.write_persistent(info_section_name, "title", title)
1568
+ self.op_data.write_persistent(
1569
+ info_section_name, "power", power_info
1570
+ )
1571
+ self.op_data.write_persistent(
1572
+ info_section_name, "lens", lens_info
1573
+ )
1574
+ self.op_data.write_persistent(
1575
+ info_section_name, "laser", laser_type
1576
+ )
1577
+ self.op_data.write_persistent(
1578
+ section_name, "type", "op engrave"
1579
+ )
1580
+ self.op_data.write_persistent(section_name, "id", "F1")
1581
+ speed_factor = 1.0
1582
+ added = True
1583
+ else:
1584
+ if not section_name:
1585
+ continue
1586
+ idx = line.find("=")
1587
+ if idx <= 0:
1588
+ continue
1589
+ param = line[:idx].lower()
1590
+ value = line[idx + 1 :]
1591
+ try:
1592
+ numeric_value = float(value)
1593
+ except ValueError:
1594
+ numeric_value = 0
1595
+ if param == "loop":
1596
+ if numeric_value != 0:
1597
+ self.op_data.write_persistent(
1598
+ section_name, "passes", numeric_value
1599
+ )
1600
+ self.op_data.write_persistent(
1601
+ section_name, "passes_custom", True
1602
+ )
1603
+ elif param == "markspeed":
1604
+ if numeric_value != 0:
1605
+ speedval = numeric_value
1606
+ elif param == "powerratio":
1607
+ if numeric_value != 0:
1608
+ powerval = numeric_value * 10
1609
+ elif param == "freq":
1610
+ # khz
1611
+ if numeric_value != 0:
1612
+ self.op_data.write_persistent(
1613
+ section_name, "frequency", numeric_value / 1000.0
1614
+ )
1615
+ elif param == "jumpspeed":
1616
+ if numeric_value != 0:
1617
+ self.op_data.write_persistent(
1618
+ section_name, "rapid_enabled", True
1619
+ )
1620
+ self.op_data.write_persistent(
1621
+ section_name, "rapid_speed", numeric_value
1622
+ )
1623
+ elif param == "starttc":
1624
+ if numeric_value != 0:
1625
+ self.op_data.write_persistent(
1626
+ section_name, "timing_enabled", True
1627
+ )
1628
+ self.op_data.write_persistent(
1629
+ section_name, "delay_laser_on", numeric_value
1630
+ )
1631
+ elif param == "laserofftc":
1632
+ if numeric_value != 0:
1633
+ self.op_data.write_persistent(
1634
+ section_name, "timing_enabled", True
1635
+ )
1636
+ self.op_data.write_persistent(
1637
+ section_name, "delay_laser_off", numeric_value
1638
+ )
1639
+ elif param == "polytc":
1640
+ if numeric_value != 0:
1641
+ self.op_data.write_persistent(
1642
+ section_name, "timing_enabled", True
1643
+ )
1644
+ self.op_data.write_persistent(
1645
+ section_name, "delay_polygon", numeric_value
1646
+ )
1647
+ elif param == "qpulsewidth":
1648
+ if numeric_value != 0:
1649
+ self.op_data.write_persistent(
1650
+ section_name, "pulse_width_enabled", True
1651
+ )
1652
+ self.op_data.write_persistent(
1653
+ section_name, "pulse_width", numeric_value
1654
+ )
1655
+ else:
1656
+ # Unknown / unsupported - a significant amount of the parameters
1657
+ # would require the addition of different things like hatches,
1658
+ # wobbles or device specific settings
1659
+ if numeric_value != 0:
1660
+ if info_box:
1661
+ info_box += "\\n"
1662
+ info_box += f"{param} = {numeric_value}"
1663
+ # Residual information available?
1664
+ if powerval and section_name:
1665
+ if factor != 1:
1666
+ old_l = info[1]
1667
+ new_l = info[2]
1668
+ factor_l = info[5]
1669
+ if old_l is not None:
1670
+ if info_box:
1671
+ info_box += "\\n"
1672
+ info_box += f"Converted lens-size {old_l}mm -> {new_l}mm: {factor_l:.2}"
1673
+ old_l = info[3]
1674
+ new_l = info[4]
1675
+ factor_l = info[6]
1676
+ if old_l is not None:
1677
+ if info_box:
1678
+ info_box += "\\n"
1679
+ info_box += (
1680
+ f"Converted power {old_l}W -> {new_l}W: {factor_l:.2}"
1681
+ )
1682
+ if powerval * factor > 1000:
1683
+ # Too much, let's reduce speed instead
1684
+ if speedval:
1685
+ if info_box:
1686
+ info_box += "\\n"
1687
+ info_box += f"Needed to reduce speed {numeric_value:.1}mm/s -> {numeric_value * speed_factor:.2}mm/s"
1688
+ speedval *= 1 / factor
1689
+ else:
1690
+ powerval *= factor
1691
+ self.op_data.write_persistent(section_name, "speed", speedval)
1692
+ self.op_data.write_persistent(section_name, "power", powerval)
1693
+ if info_box and info_section_name:
1694
+ self.op_data.write_persistent(info_section_name, "note", info_box)
1695
+
1696
+ except (OSError, RuntimeError, PermissionError, FileNotFoundError):
1697
+ return False
1698
+ if added:
1699
+ self.op_data.write_configuration()
1700
+ return added
1701
+
1702
+ def on_import(self, event, filename=None):
1703
+ #
1704
+ info = None
1705
+ mydlg = ImportDialog(
1706
+ None, id=wx.ID_ANY, context=self.context, filename=filename
1707
+ )
1708
+ if mydlg.ShowModal() == wx.ID_OK:
1709
+ # This returns a Python list of files that were selected.
1710
+ info = mydlg.result()
1711
+ mydlg.Destroy()
1712
+ if info is None:
1713
+ return
1714
+ added = False
1715
+ myfile = info[0]
1716
+ if myfile.endswith(".clb"):
1717
+ added = self.import_lightburn(info)
1718
+ elif myfile.endswith(".lib") or myfile.endswith(".ini"):
1719
+ added = self.import_ezcad(info)
1720
+ elif myfile.endswith(".cfg"):
1721
+ added = self.import_meerk40t(info)
1722
+ else:
1723
+ self.invalid_file(myfile)
1724
+
1725
+ if added:
1726
+ self.on_reset(None)
1727
+
1728
+ def on_apply_statusbar(self, event):
1729
+ if self.active_material is None:
1730
+ return
1731
+ op_list, op_info = self.context.elements.load_persistent_op_list(
1732
+ self.active_material,
1733
+ use_settings=self.op_data,
1734
+ )
1735
+ if len(op_list) == 0:
1736
+ return
1737
+ self.context.elements.default_operations = list(op_list)
1738
+ self.context.signal("default_operations")
1739
+
1740
+ def on_apply_tree(self, event):
1741
+ if self.active_material is None:
1742
+ return
1743
+ op_list, op_info = self.context.elements.load_persistent_op_list(
1744
+ self.active_material,
1745
+ use_settings=self.op_data,
1746
+ )
1747
+ if len(op_list) == 0:
1748
+ return
1749
+ response = self.context.kernel.yesno(
1750
+ _("Do you want to remove all existing operations before loading this set?"),
1751
+ caption=_("Clear Operation-List"),
1752
+ )
1753
+ self.context.elements.load_persistent_operations(
1754
+ self.active_material, clear=response
1755
+ )
1756
+ self.context.signal("rebuild_tree")
1757
+
1758
+ def on_new(self, event):
1759
+ entry_txt = self.txt_material.GetValue()
1760
+ if entry_txt == "":
1761
+ entry_txt = "material"
1762
+ if entry_txt in self.material_list:
1763
+ idx = 0
1764
+ while True:
1765
+ idx += 1
1766
+ pattern = f"{entry_txt}_({idx})"
1767
+ if pattern not in self.material_list:
1768
+ break
1769
+ entry_txt = pattern
1770
+ # entry_type = self.combo_entry_type.GetSelection()
1771
+ # if entry_type < 0:
1772
+ # entry_type = 0
1773
+ # We need to create a new one...
1774
+ op_info = dict()
1775
+ op_info["material"] = _("New material")
1776
+ op_info["laser"] = 0
1777
+ op_info["thickness"] = "4mm"
1778
+ op_info["note"] = _("You can put additional operation instructions here.")
1779
+ section = entry_txt
1780
+
1781
+ if len(list(self.context.elements.ops())) == 0:
1782
+ op_list = self.context.elements.default_operations
1783
+ if len(op_list) == 0:
1784
+ return
1785
+ # op_list = None save op_branch
1786
+ self.context.elements.save_persistent_operations_list(
1787
+ section,
1788
+ oplist=op_list,
1789
+ opinfo=op_info,
1790
+ inform=False,
1791
+ use_settings=self.op_data,
1792
+ )
1793
+ else:
1794
+ # op_list = None save op_branch
1795
+ self.context.elements.save_persistent_operations_list(
1796
+ section,
1797
+ oplist=None,
1798
+ opinfo=op_info,
1799
+ inform=False,
1800
+ use_settings=self.op_data,
1801
+ )
1802
+ self.op_data.write_configuration()
1803
+ self.retrieve_material_list(reload=True, setter=section)
1804
+
1805
+ def on_use_current(self, event):
1806
+ section = None
1807
+ op_info = None
1808
+ if self.active_material is not None:
1809
+ if self.context.kernel.yesno(
1810
+ _(
1811
+ "Do you want to use the operations in the tree for the current entry?"
1812
+ )
1813
+ ):
1814
+ section = self.active_material
1815
+ op_info = self.context.elements.load_persistent_op_info(
1816
+ self.active_material,
1817
+ use_settings=self.op_data,
1818
+ )
1819
+ if "material" not in op_info:
1820
+ op_info["material"] = "Operations List"
1821
+ if "laser" not in op_info:
1822
+ op_info["laser"] = 0
1823
+
1824
+ if section is None:
1825
+ entry_txt = self.txt_material.GetValue()
1826
+ if entry_txt == "":
1827
+ entry_txt = "material"
1828
+ if entry_txt in self.material_list:
1829
+ idx = 0
1830
+ while True:
1831
+ idx += 1
1832
+ pattern = f"{entry_txt}_({idx})"
1833
+ if pattern not in self.material_list:
1834
+ break
1835
+ entry_txt = pattern
1836
+ # entry_type = self.combo_entry_type.GetSelection()
1837
+ # if entry_type < 0:
1838
+ # entry_type = 0
1839
+ # We need to create a new one...
1840
+ op_info = dict()
1841
+ op_info["material"] = _("New material")
1842
+ op_info["laser"] = 0
1843
+ section = entry_txt
1844
+
1845
+ # op_list = None save op_branch
1846
+ self.context.elements.save_persistent_operations_list(
1847
+ section,
1848
+ oplist=None,
1849
+ opinfo=op_info,
1850
+ inform=False,
1851
+ use_settings=self.op_data,
1852
+ )
1853
+ self.op_data.write_configuration()
1854
+ self.retrieve_material_list(reload=True, setter=section)
1855
+
1856
+ def on_reset(self, event):
1857
+ self.no_reload = True
1858
+ self.txt_material.SetValue("")
1859
+ self.txt_thickness.SetValue("")
1860
+ self.combo_lasertype.SetSelection(0)
1861
+ self.no_reload = False
1862
+ self.update_list(reload=True)
1863
+
1864
+ def update_list(self, *args, **kwargs):
1865
+ if self.no_reload:
1866
+ return
1867
+ filter_txt = self.txt_material.GetValue()
1868
+ filter_thickness = self.txt_thickness.GetValue()
1869
+ filter_type = self.combo_lasertype.GetSelection()
1870
+ if filter_txt == "":
1871
+ filter_txt = None
1872
+ if filter_thickness == "":
1873
+ filter_thickness = None
1874
+ if filter_type < 0:
1875
+ filter_type = None
1876
+ reload = False
1877
+ if "reload" in kwargs:
1878
+ reload = kwargs["reload"]
1879
+ self.retrieve_material_list(
1880
+ filtername=filter_txt,
1881
+ filterlaser=filter_type,
1882
+ filterthickness=filter_thickness,
1883
+ reload=reload,
1884
+ setter=self.active_material,
1885
+ )
1886
+
1887
+ def update_lasertype_for_all(self, event):
1888
+ op_ltype = self.combo_entry_type.GetSelection()
1889
+ if op_ltype < 0:
1890
+ return
1891
+ changes = False
1892
+ for entry in self.display_list:
1893
+ material = entry["section"]
1894
+ section = f"{material} info"
1895
+ self.op_data.write_persistent(section, "laser", op_ltype)
1896
+ changes = True
1897
+ if changes:
1898
+ self.op_data.write_configuration()
1899
+ self.on_reset(None)
1900
+
1901
+ def update_entry(self, event):
1902
+ if self.active_material is None:
1903
+ return
1904
+ op_section = self.txt_entry_section.GetValue()
1905
+ for forbidden in " []":
1906
+ op_section = op_section.replace(forbidden, "_")
1907
+ ctrls = (
1908
+ self.txt_entry_title,
1909
+ self.txt_entry_material,
1910
+ self.txt_entry_thickness,
1911
+ self.txt_entry_power,
1912
+ self.txt_entry_lens,
1913
+ )
1914
+ fields = (
1915
+ "title",
1916
+ "material",
1917
+ "thickness",
1918
+ "power",
1919
+ "lens",
1920
+ )
1921
+ data = [(field, ctrl.GetValue()) for field, ctrl in zip(fields, ctrls)]
1922
+
1923
+ op_ltype = self.combo_entry_type.GetSelection()
1924
+ if op_ltype < 0:
1925
+ op_ltype = 0
1926
+ data.append(("laser", op_ltype))
1927
+
1928
+ # Note, convert linebreaks
1929
+ op_note = self.txt_entry_note.GetValue().replace("\n", "\\n")
1930
+ data.append(("note", op_note))
1931
+
1932
+ op_list, op_info = self.context.elements.load_persistent_op_list(
1933
+ self.active_material,
1934
+ use_settings=self.op_data,
1935
+ )
1936
+ if len(op_list) == 0:
1937
+ return
1938
+ to_save = False
1939
+ for entry in data:
1940
+ field, value = entry
1941
+ stored_value = op_info.get(field, "")
1942
+ if value != stored_value:
1943
+ op_info[field] = value
1944
+ to_save = True
1945
+ if to_save:
1946
+ if self.active_material != op_section:
1947
+ self.context.elements.clear_persistent_operations(
1948
+ self.active_material,
1949
+ use_settings=self.op_data,
1950
+ )
1951
+ self.active_material = op_section
1952
+ self.context.elements.save_persistent_operations_list(
1953
+ self.active_material,
1954
+ oplist=op_list,
1955
+ opinfo=op_info,
1956
+ inform=False,
1957
+ use_settings=self.op_data,
1958
+ )
1959
+ self.op_data.write_configuration()
1960
+ self.retrieve_material_list(reload=True, setter=self.active_material)
1961
+
1962
+ def on_list_selection(self, event):
1963
+ try:
1964
+ item = event.GetItem()
1965
+ if item and item.IsOk():
1966
+ listidx = self.tree_library.GetItemData(item)
1967
+ if listidx >= 0:
1968
+ info = self.get_nth_material(listidx)
1969
+ self.active_material = info
1970
+ else:
1971
+ self.active_material = None
1972
+ except RuntimeError:
1973
+ return
1974
+
1975
+ def fill_preview(self):
1976
+
1977
+ def get_key(op_type, op_color):
1978
+ return f"{op_type}-{str(op_color)}"
1979
+
1980
+ def populate_images() -> dict:
1981
+ COLORFUL_BACKGROUND = True
1982
+ iconsize = 30
1983
+ self.state_images.Destroy()
1984
+ self.state_images = wx.ImageList()
1985
+ self.state_images.Create(width=iconsize, height=iconsize)
1986
+ image_dict = {}
1987
+ if self.active_material is not None:
1988
+ for subsection in self.op_data.derivable(self.active_material):
1989
+ optype = self.op_data.read_persistent(str, subsection, "type", "")
1990
+ if optype is None or optype == "":
1991
+ continue
1992
+ opcolor = self.op_data.read_persistent(str, subsection, "color", "")
1993
+ if opcolor:
1994
+ opc = Color(opcolor)
1995
+ else:
1996
+ opc = None
1997
+ key = get_key(optype, opc)
1998
+ if key in image_dict:
1999
+ continue
2000
+ try:
2001
+ info = self.opinfo[optype]
2002
+ except KeyError:
2003
+ info = self.opinfo["generic"]
2004
+ if COLORFUL_BACKGROUND:
2005
+ if opc is None:
2006
+ opc = Color("black")
2007
+ fgcol = wx.BLACK if Color.distance(opc, "black") > Color.distance(opc, "white") else wx.WHITE
2008
+ forced_bg = (opc.red, opc.green, opc.blue, opc.alpha)
2009
+ bmap = info[1].GetBitmap(resize=(iconsize, iconsize), noadjustment=True, color=fgcol, forced_background=forced_bg)
2010
+ else:
2011
+ bmap = info[1].GetBitmap(resize=(iconsize, iconsize), noadjustment=True, color=opc)
2012
+ image_id = self.state_images.Add(bitmap=bmap)
2013
+ image_dict[key] = image_id
2014
+
2015
+ self.list_preview.AssignImageList(self.state_images, wx.IMAGE_LIST_SMALL)
2016
+ return image_dict
2017
+
2018
+ self._balor = False
2019
+ for obj, name, sname in self.context.find("dev_info"):
2020
+ if obj is not None and "balor" in sname.lower():
2021
+ self._balor = True
2022
+ break
2023
+
2024
+ self.list_preview.Freeze()
2025
+ self.list_preview.DeleteAllItems()
2026
+ icon_dict = populate_images()
2027
+ self.operation_list.clear()
2028
+ secdesc = ""
2029
+ thickness = ""
2030
+ info_power = ""
2031
+ info_lens = ""
2032
+ info_title = ""
2033
+ note = ""
2034
+ ltype = 0
2035
+ if self.active_material is not None:
2036
+ secdesc = ""
2037
+ idx = 0
2038
+ for subsection in self.op_data.derivable(self.active_material):
2039
+ if subsection.endswith(" info"):
2040
+ info_title = self.op_data.read_persistent(
2041
+ str, subsection, "title", ""
2042
+ )
2043
+ info_power = self.op_data.read_persistent(
2044
+ str, subsection, "power", ""
2045
+ )
2046
+ info_lens = self.op_data.read_persistent(
2047
+ str, subsection, "lens", ""
2048
+ )
2049
+ secdesc = self.op_data.read_persistent(
2050
+ str, subsection, "material", ""
2051
+ )
2052
+ thickness = self.op_data.read_persistent(
2053
+ str, subsection, "thickness", ""
2054
+ )
2055
+ ltype = self.op_data.read_persistent(int, subsection, "laser", 0)
2056
+ note = self.op_data.read_persistent(str, subsection, "note", "")
2057
+ # We need to replace stored linebreaks with real linebreaks
2058
+ note = note.replace("\\n", "\n")
2059
+ if ltype == 3:
2060
+ self._balor = True
2061
+ elif ltype != 0:
2062
+ self._balor = False
2063
+ continue
2064
+ optype = self.op_data.read_persistent(str, subsection, "type", "")
2065
+ if optype is None or optype == "":
2066
+ continue
2067
+ opcolor = self.op_data.read_persistent(str, subsection, "color", "")
2068
+ if opcolor:
2069
+ opc = Color(opcolor)
2070
+ else:
2071
+ opc = None
2072
+ idx += 1
2073
+ opid = self.op_data.read_persistent(str, subsection, "id", "")
2074
+ oplabel = self.op_data.read_persistent(str, subsection, "label", "")
2075
+ speed = self.op_data.read_persistent(str, subsection, "speed", "")
2076
+ power = self.op_data.read_persistent(str, subsection, "power", "")
2077
+ passes = self.op_data.read_persistent(str, subsection, "passes", "")
2078
+ frequency = self.op_data.read_persistent(
2079
+ str, subsection, "frequency", ""
2080
+ )
2081
+ if not self.is_balor:
2082
+ frequency = ""
2083
+ command = self.op_data.read_persistent(str, subsection, "command", "")
2084
+ if power == "" and optype.startswith("op "):
2085
+ power = "1000"
2086
+ if passes == "" and optype.startswith("op "):
2087
+ passes = "1"
2088
+ list_id = self.list_preview.InsertItem(
2089
+ self.list_preview.GetItemCount(), f"#{idx}"
2090
+ )
2091
+ try:
2092
+ info = self.opinfo[optype]
2093
+ except KeyError:
2094
+ info = self.opinfo["generic"]
2095
+ if command:
2096
+ if oplabel:
2097
+ oplabel += " "
2098
+ else:
2099
+ oplabel = ""
2100
+ oplabel += f"({command})"
2101
+ self.list_preview.SetItem(list_id, 1, info[0])
2102
+ self.list_preview.SetItem(list_id, 2, opid)
2103
+ self.list_preview.SetItem(list_id, 3, oplabel)
2104
+ self.list_preview.SetItem(list_id, 4, power)
2105
+ self.list_preview.SetItem(list_id, 5, speed)
2106
+ self.list_preview.SetItem(list_id, 6, frequency)
2107
+ self.list_preview.SetItem(list_id, 7, passes)
2108
+ key = get_key(optype, opc)
2109
+ if key in icon_dict:
2110
+ imgid = icon_dict[key]
2111
+ self.list_preview.SetItemImage(list_id, imgid)
2112
+ self.list_preview.SetItemData(list_id, idx - 1)
2113
+ self.operation_list[subsection] = (optype, opid, oplabel, power, speed)
2114
+ self.list_preview.Thaw()
2115
+ self.list_preview.Refresh()
2116
+ if self.active_material is None:
2117
+ actval = ""
2118
+ else:
2119
+ actval = self.active_material
2120
+ # print (f"id: '{actval}'\ntitle: '{info_title}'\nmaterial: '{secdesc}'")
2121
+ if not info_title:
2122
+ info_title = actval.replace("_", " ")
2123
+ self.txt_entry_section.SetValue(actval)
2124
+ self.txt_entry_material.SetValue(secdesc)
2125
+ self.txt_entry_thickness.SetValue(thickness)
2126
+ self.txt_entry_title.SetValue(info_title)
2127
+ self.txt_entry_power.SetValue(info_power)
2128
+ self.txt_entry_lens.SetValue(info_lens)
2129
+ self.txt_entry_note.SetValue(note)
2130
+ self.combo_entry_type.SetSelection(ltype)
2131
+ self.list_preview.resize_columns()
2132
+
2133
+ def on_preview_selection(self, event):
2134
+ event.Skip()
2135
+
2136
+ def on_library_rightclick(self, event):
2137
+ event.Skip()
2138
+ menu = wx.Menu()
2139
+ item = menu.Append(wx.ID_ANY, _("Add new"), "", wx.ITEM_NORMAL)
2140
+ self.Bind(wx.EVT_MENU, self.on_new, item)
2141
+
2142
+ item = menu.Append(wx.ID_ANY, _("Get current"), "", wx.ITEM_NORMAL)
2143
+ self.Bind(wx.EVT_MENU, self.on_use_current, item)
2144
+
2145
+ item = menu.Append(wx.ID_ANY, _("Load into Tree"), "", wx.ITEM_NORMAL)
2146
+ menu.Enable(item.GetId(), bool(self.active_material is not None))
2147
+ self.Bind(wx.EVT_MENU, self.on_apply_tree, item)
2148
+
2149
+ item = menu.Append(wx.ID_ANY, _("Use for statusbar"), "", wx.ITEM_NORMAL)
2150
+ menu.Enable(item.GetId(), bool(self.active_material is not None))
2151
+ self.Bind(wx.EVT_MENU, self.on_apply_statusbar, item)
2152
+
2153
+ item = menu.Append(wx.ID_ANY, _("Duplicate"), "", wx.ITEM_NORMAL)
2154
+ menu.Enable(item.GetId(), bool(self.active_material is not None))
2155
+ self.Bind(wx.EVT_MENU, self.on_duplicate, item)
2156
+
2157
+ # We delete all entries of the same kind.
2158
+ # mat_list_entry = self.tree_library.GetSelection()
2159
+
2160
+ if self.share_ready:
2161
+ menu.AppendSeparator()
2162
+ item = menu.Append(wx.ID_ANY, _("Share"), "", wx.ITEM_NORMAL)
2163
+ menu.Enable(item.GetId(), bool(self.active_material is not None))
2164
+ self.Bind(wx.EVT_MENU, self.on_share, item)
2165
+
2166
+ def create_minimal(event):
2167
+ section = "minimal"
2168
+ oplist = self.context.elements.create_minimal_op_list()
2169
+ opinfo = {"material": "Minimal list", "laser": 0}
2170
+ self.context.elements.save_persistent_operations_list(
2171
+ section,
2172
+ oplist,
2173
+ opinfo,
2174
+ False,
2175
+ use_settings=self.op_data,
2176
+ )
2177
+ self.op_data.write_configuration()
2178
+ self.retrieve_material_list(reload=True, setter=section)
2179
+
2180
+ def create_basic(event):
2181
+ section = "basic"
2182
+ oplist = self.context.elements.create_basic_op_list()
2183
+ opinfo = {"material": "Basic list", "laser": 0}
2184
+ self.context.elements.save_persistent_operations_list(
2185
+ section,
2186
+ oplist,
2187
+ opinfo,
2188
+ False,
2189
+ use_settings=self.op_data,
2190
+ )
2191
+ self.op_data.write_configuration()
2192
+ self.retrieve_material_list(reload=True, setter=section)
2193
+
2194
+ menu.AppendSeparator()
2195
+ item = menu.Append(wx.ID_ANY, _("Create minimal"), "", wx.ITEM_NORMAL)
2196
+ self.Bind(wx.EVT_MENU, create_minimal, item)
2197
+ item = menu.Append(wx.ID_ANY, _("Create basic"), "", wx.ITEM_NORMAL)
2198
+ self.Bind(wx.EVT_MENU, create_basic, item)
2199
+ menu.AppendSeparator()
2200
+ tree_item = self.tree_library.GetSelection()
2201
+ if tree_item.IsOk():
2202
+ listidx = self.tree_library.GetItemData(tree_item)
2203
+ if listidx >= 0:
2204
+ item = menu.Append(wx.ID_ANY, _("Delete"), "", wx.ITEM_NORMAL)
2205
+ menu.Enable(item.GetId(), bool(self.active_material is not None))
2206
+ self.Bind(wx.EVT_MENU, self.on_delete, item)
2207
+ else:
2208
+ deletion_info = self.deletion_methods[listidx]
2209
+ deletion_level, key1, value1, key2, value2 = deletion_info
2210
+ if deletion_level > 0: # First or second category
2211
+ criteria = f"{key1}='{value1}'"
2212
+ if deletion_level == 2:
2213
+ criteria += f" + {key2}='{value2}'"
2214
+ info = _("Delete all with {data}").format(data=criteria)
2215
+ item = menu.Append(wx.ID_ANY, info, "", wx.ITEM_NORMAL)
2216
+ self.Bind(wx.EVT_MENU, self.on_delete_category(deletion_level, value1, value2), item)
2217
+
2218
+ item = menu.Append(wx.ID_ANY, _("Delete all"), "", wx.ITEM_NORMAL)
2219
+ self.Bind(wx.EVT_MENU, self.on_delete_all, item)
2220
+
2221
+ menu.AppendSeparator()
2222
+ item = menu.Append(wx.ID_ANY, _("Sort by..."), "", wx.ITEM_NORMAL)
2223
+ menu.Enable(item.GetId(), False)
2224
+
2225
+ def set_sort_key(sortvalue):
2226
+ local_value = sortvalue
2227
+
2228
+ def sort_handler(event):
2229
+ self.categorisation = local_value
2230
+ self.retrieve_material_list(reload=False, setter=self.active_material)
2231
+
2232
+ return sort_handler
2233
+
2234
+ item = menu.Append(wx.ID_ANY, _("Material"), "", wx.ITEM_RADIO)
2235
+ item.Check(bool(self.categorisation == 0))
2236
+ self.Bind(wx.EVT_MENU, set_sort_key(0), item)
2237
+ item = menu.Append(wx.ID_ANY, _("Laser"), "", wx.ITEM_RADIO)
2238
+ item.Check(bool(self.categorisation == 1))
2239
+ self.Bind(wx.EVT_MENU, set_sort_key(1), item)
2240
+ item = menu.Append(wx.ID_ANY, _("Thickness"), "", wx.ITEM_RADIO)
2241
+ item.Check(bool(self.categorisation == 2))
2242
+ self.Bind(wx.EVT_MENU, set_sort_key(2), item)
2243
+ menu.AppendSeparator()
2244
+
2245
+ def on_expand(flag):
2246
+ def exp_handler(event):
2247
+ tree = self.tree_library
2248
+ tree_root = tree.GetRootItem()
2249
+ child, cookie = tree.GetFirstChild(tree_root)
2250
+ while child.IsOk():
2251
+ if local_flag:
2252
+ tree.ExpandAllChildren(child)
2253
+ else:
2254
+ tree.CollapseAllChildren(child)
2255
+ child, cookie = tree.GetNextChild(tree_root, cookie)
2256
+
2257
+ local_flag = flag
2258
+ return exp_handler
2259
+
2260
+ item = menu.Append(wx.ID_ANY, _("Expand all"), "", wx.ITEM_NORMAL)
2261
+ self.Bind(wx.EVT_MENU, on_expand(True), item)
2262
+ item = menu.Append(wx.ID_ANY, _("Collapse all"), "", wx.ITEM_NORMAL)
2263
+ self.Bind(wx.EVT_MENU, on_expand(False), item)
2264
+
2265
+ self.PopupMenu(menu)
2266
+ menu.Destroy()
2267
+
2268
+ def on_preview_rightclick(self, event):
2269
+ # A couple of basic operations
2270
+ def max_keynum(secname):
2271
+ maxfound = 0
2272
+ for subsection in self.op_data.derivable(secname):
2273
+ parts = subsection.split(" ")
2274
+ number = parts[-1]
2275
+ try:
2276
+ nr = int(number)
2277
+ if nr > maxfound:
2278
+ maxfound = nr
2279
+ except ValueError:
2280
+ pass
2281
+ return maxfound
2282
+
2283
+ def newkey():
2284
+ sect = self.active_material
2285
+ # fetch all section names...
2286
+ sect_num = max_keynum(sect) + 1
2287
+ section_name = f"{sect} {sect_num:0>6}"
2288
+ return section_name
2289
+
2290
+ event.Skip()
2291
+ if self.active_material is None:
2292
+ return
2293
+ key = None
2294
+ try:
2295
+ # main click
2296
+ listindex = event.Index
2297
+ if listindex >= 0:
2298
+ index = self.list_preview.GetItemData(listindex)
2299
+ key = self.get_nth_operation(index)
2300
+ except AttributeError:
2301
+ # Column click
2302
+ pass
2303
+
2304
+ menu = wx.Menu()
2305
+
2306
+ def on_menu_popup_recolor(coloroption, op_section):
2307
+ def color_handler(*args):
2308
+ def next_color(primary, secondary, tertiary, delta=32):
2309
+ r = primary
2310
+ b = secondary
2311
+ g = tertiary
2312
+
2313
+ b += delta
2314
+ if b > 255:
2315
+ b = 0
2316
+ r -= delta
2317
+ if r < 0:
2318
+ r = 255
2319
+ g += delta
2320
+ if g > 255:
2321
+ g = 0
2322
+ return r, b, g
2323
+
2324
+ colors = [0, 0, 0]
2325
+ primary = 0
2326
+ secondary = 1
2327
+ tertiary = 2
2328
+ if coloropt == "red":
2329
+ colors[0] = 255
2330
+ primary = 0
2331
+ secondary = 1
2332
+ tertiary = 2
2333
+ if coloropt == "blue":
2334
+ colors[1] = 255
2335
+ primary = 1
2336
+ secondary = 2
2337
+ tertiary = 0
2338
+ if coloropt == "green":
2339
+ colors[2] = 255
2340
+ primary = 2
2341
+ secondary = 1
2342
+ tertiary = 0
2343
+ if coloropt == "black":
2344
+ colors = [0, 0, 0]
2345
+ primary = 0
2346
+ secondary = 1
2347
+ tertiary = 2
2348
+ settings = self.op_data
2349
+ target_type = settings.read_persistent(str, key, "type", "")
2350
+ idx = 0
2351
+ for subsection in settings.derivable(self.active_material):
2352
+ if subsection.endswith(" info"):
2353
+ continue
2354
+ optype = settings.read_persistent(str, subsection, "type", "")
2355
+ if optype is None or optype != target_type:
2356
+ continue
2357
+ idx += 1
2358
+ opcolor = Color(red=colors[0], green=colors[2], blue=colors[1])
2359
+ settings.write_persistent(subsection, "color", str(opcolor))
2360
+ if coloropt=="black":
2361
+ colors[primary] += 32
2362
+ if colors[primary] > 255:
2363
+ colors[primary] = 0
2364
+ colors[secondary] = colors[primary]
2365
+ colors[tertiary] = colors[primary]
2366
+ else:
2367
+ colors[primary], colors[secondary], colors[tertiary] = next_color(colors[primary], colors[secondary], colors[tertiary], delta=64)
2368
+
2369
+ settings.write_configuration()
2370
+ self.fill_preview()
2371
+
2372
+ coloropt = coloroption.lower()
2373
+ key = op_section
2374
+ return color_handler
2375
+
2376
+ def on_menu_popup_delete(op_section):
2377
+ def remove_handler(*args):
2378
+ settings = self.op_data
2379
+ # print (f"Remove {sect}")
2380
+ settings.clear_persistent(sect)
2381
+ self.fill_preview()
2382
+
2383
+ sect = op_section
2384
+ return remove_handler
2385
+
2386
+ def on_menu_popup_duplicate(op_section):
2387
+ def dup_handler(*args):
2388
+ settings = self.op_data
2389
+ # print (f"Remove {sect}")
2390
+ nkey = newkey()
2391
+ for info in settings.keylist(sect):
2392
+ secdesc = settings.read_persistent(str, sect, info, "")
2393
+ if info == "id":
2394
+ continue
2395
+ if info == "label":
2396
+ idx = 0
2397
+ if secdesc.endswith(")") and secdesc[-2] in (
2398
+ str(i) for i in range(0, 10)
2399
+ ):
2400
+ i = secdesc.rfind("(")
2401
+ if i >= 0:
2402
+ try:
2403
+ s = secdesc[i + 1 :]
2404
+ t = secdesc[:i]
2405
+ idx = int(s[:-1])
2406
+ secdesc = t
2407
+ except ValueError:
2408
+ pass
2409
+ secdesc += f"({idx + 1})"
2410
+ settings.write_persistent(nkey, info, secdesc)
2411
+
2412
+ on_menu_popup_missing()
2413
+ settings.write_configuration()
2414
+ self.fill_preview()
2415
+
2416
+ sect = op_section
2417
+ return dup_handler
2418
+
2419
+ def on_menu_popup_newop(op_dict):
2420
+ def add_handler(*args):
2421
+ settings = self.op_data
2422
+ # print (f"Remove {sect}")
2423
+ nkey = newkey()
2424
+ for key, value in opd.items():
2425
+ settings.write_persistent(nkey, key, value)
2426
+
2427
+ on_menu_popup_missing()
2428
+ settings.write_configuration()
2429
+ self.fill_preview()
2430
+
2431
+ opd = op_dict
2432
+ return add_handler
2433
+
2434
+ def on_menu_popup_apply_to_tree(op_section):
2435
+ def apply_to_tree_handler(*args):
2436
+ settings = self.op_data
2437
+ op_type = settings.read_persistent(str, sect, "type")
2438
+ op_attr = dict()
2439
+ for key in settings.keylist(sect):
2440
+ if key == "type":
2441
+ # We need to ignore it to avoid double attribute issues.
2442
+ continue
2443
+ content = settings.read_persistent(str, sect, key)
2444
+ op_attr[key] = content
2445
+ try:
2446
+ targetop = Node().create(type=op_type, **op_attr)
2447
+ except ValueError:
2448
+ # Attempted to create a non-bootstrapped node type.
2449
+ return
2450
+ op_id = targetop.id
2451
+ if op_id is None:
2452
+ # WTF, that should not be the case
2453
+ op_list = [targetop]
2454
+ self.context.elements.validate_ids(nodelist=op_list, generic=False)
2455
+ newone = True
2456
+ for op in self.context.elements.ops():
2457
+ # Already existing?
2458
+ if op.id == targetop.id:
2459
+ newone = False
2460
+ op_attr["type"] = targetop.type
2461
+ op.replace_node(keep_children=True, **op_attr)
2462
+ break
2463
+ if newone:
2464
+ try:
2465
+ self.context.elements.op_branch.add_node(targetop)
2466
+ except ValueError:
2467
+ # This happens when he have somehow lost sync with the node,
2468
+ # and we try to add a node that is already added...
2469
+ # In principle this should be covered by the check
2470
+ # above, but you never know
2471
+ pass
2472
+ self.context.signal("rebuild_tree")
2473
+
2474
+ sect = op_section
2475
+ return apply_to_tree_handler
2476
+
2477
+ def on_menu_popup_apply_to_statusbar(op_section):
2478
+ def apply_to_tree_handler(*args):
2479
+ settings = self.op_data
2480
+ op_type = settings.read_persistent(str, sect, "type")
2481
+ op_attr = dict()
2482
+ for key in settings.keylist(sect):
2483
+ if key == "type":
2484
+ # We need to ignore it to avoid double attribute issues.
2485
+ continue
2486
+ content = settings.read_persistent(str, sect, key)
2487
+ op_attr[key] = content
2488
+ try:
2489
+ targetop = Node().create(type=op_type, **op_attr)
2490
+ except ValueError:
2491
+ # Attempted to create a non-bootstrapped node type.
2492
+ return
2493
+ op_id = targetop.id
2494
+ if op_id is None:
2495
+ # WTF, that should not be the case
2496
+ op_list = [targetop]
2497
+ self.context.elements.validate_ids(nodelist=op_list, generic=False)
2498
+ newone = True
2499
+ for idx, op in enumerate(self.context.elements.default_operations):
2500
+ # Already existing?
2501
+ if op.id == targetop.id:
2502
+ newone = False
2503
+ self.context.elements.default_operations[idx] = targetop
2504
+ break
2505
+ if newone:
2506
+ self.context.elements.default_operations.append(targetop)
2507
+ self.context.signal("default_operations")
2508
+
2509
+ sect = op_section
2510
+ return apply_to_tree_handler
2511
+
2512
+ def on_menu_popup_missing(*args):
2513
+ if self.active_material is None:
2514
+ return
2515
+ op_list, op_info = self.context.elements.load_persistent_op_list(
2516
+ self.active_material,
2517
+ use_settings=self.op_data,
2518
+ )
2519
+ if len(op_list) == 0:
2520
+ return
2521
+ changes = False
2522
+ # Which ones do we have already?
2523
+ replace_mk_pattern = True
2524
+ mkpattern = "meerk40t:"
2525
+ uid = list()
2526
+ for op in op_list:
2527
+ if not hasattr(op, "id"):
2528
+ continue
2529
+ if not op.id:
2530
+ continue
2531
+ if replace_mk_pattern and op.id.startswith(mkpattern):
2532
+ continue
2533
+ uid.append(op.id)
2534
+ for op in op_list:
2535
+ if hasattr(op, "label") and op.label is None:
2536
+ pattern = op.type
2537
+ if pattern.startswith("op "):
2538
+ pattern = pattern[3].upper() + pattern[4:]
2539
+ s1 = ""
2540
+ s2 = ""
2541
+ if hasattr(op, "power"):
2542
+ s1 = "{percent}"
2543
+ if hasattr(op, "speed") and op.speed is not None:
2544
+ s2 = "{speed}mm/s"
2545
+ if s1 or s2:
2546
+ pattern += f" ({s1}{', ' if s1 and s2 else ''}{s2})"
2547
+ op.label = pattern
2548
+ changes = True
2549
+ replace_id = True
2550
+ if not hasattr(op, "id"):
2551
+ # oldid = "unknown"
2552
+ replace_id = False
2553
+ else:
2554
+ # oldid = op.id
2555
+ if op.id and not (
2556
+ replace_mk_pattern and op.id.startswith(mkpattern)
2557
+ ):
2558
+ replace_id = False
2559
+ # print (oldid, replace_id)
2560
+ if replace_id:
2561
+ # oldid = op.id
2562
+ changes = True
2563
+ if op.type.startswith("op "):
2564
+ pattern = op.type[3].upper()
2565
+ else:
2566
+ pattern = op.type[0:2].upper()
2567
+ idx = 1
2568
+ while f"{pattern}{idx}" in uid:
2569
+ idx += 1
2570
+ op.id = f"{pattern}{idx}"
2571
+ # print (f"{oldid} -> {op.id}")
2572
+ uid.append(op.id)
2573
+
2574
+ if changes:
2575
+ self.context.elements.save_persistent_operations_list(
2576
+ self.active_material,
2577
+ op_list,
2578
+ op_info,
2579
+ flush=False,
2580
+ use_settings=self.op_data,
2581
+ )
2582
+ self.op_data.write_configuration()
2583
+ self.fill_preview()
2584
+
2585
+ op_dict = {
2586
+ "type": "op raster",
2587
+ "speed": "300",
2588
+ "power": "1000",
2589
+ "label": "Raster ({percent}, {speed}mm/s)",
2590
+ "color": "#000000",
2591
+ "passes": "1",
2592
+ }
2593
+ if self.is_balor:
2594
+ op_dict["frequency"] = "35"
2595
+ item = menu.Append(wx.ID_ANY, _("Add Raster"), "", wx.ITEM_NORMAL)
2596
+ self.Bind(wx.EVT_MENU, on_menu_popup_newop(op_dict), item)
2597
+
2598
+ op_dict = {
2599
+ "type": "op image",
2600
+ "speed": "300",
2601
+ "power": "1000",
2602
+ "label": "Image ({percent}, {speed}mm/s)",
2603
+ "color": "#000000",
2604
+ }
2605
+ item = menu.Append(wx.ID_ANY, _("Add Image"), "", wx.ITEM_NORMAL)
2606
+ self.Bind(wx.EVT_MENU, on_menu_popup_newop(op_dict), item)
2607
+
2608
+ op_dict = {
2609
+ "type": "op engrave",
2610
+ "speed": "50",
2611
+ "power": "1000",
2612
+ "label": "Engrave ({percent}, {speed}mm/s)",
2613
+ "color": "#0000FF",
2614
+ }
2615
+ if self.is_balor:
2616
+ op_dict["frequency"] = "35"
2617
+ item = menu.Append(wx.ID_ANY, _("Add Engrave"), "", wx.ITEM_NORMAL)
2618
+ self.Bind(wx.EVT_MENU, on_menu_popup_newop(op_dict), item)
2619
+ op_dict = {
2620
+ "type": "op cut",
2621
+ "speed": "5",
2622
+ "power": "1000",
2623
+ "label": "Cut ({percent}, {speed}mm/s)",
2624
+ "color": "#FF0000",
2625
+ }
2626
+ if self.is_balor:
2627
+ op_dict["frequency"] = "35"
2628
+ item = menu.Append(wx.ID_ANY, _("Add Cut"), "", wx.ITEM_NORMAL)
2629
+ self.Bind(wx.EVT_MENU, on_menu_popup_newop(op_dict), item)
2630
+
2631
+ if key:
2632
+ menu.AppendSeparator()
2633
+ item = menu.Append(wx.ID_ANY, _("Duplicate"), "", wx.ITEM_NORMAL)
2634
+ self.Bind(wx.EVT_MENU, on_menu_popup_duplicate(key), item)
2635
+ item = menu.Append(wx.ID_ANY, _("Delete"), "", wx.ITEM_NORMAL)
2636
+ self.Bind(wx.EVT_MENU, on_menu_popup_delete(key), item)
2637
+
2638
+ menu.AppendSeparator()
2639
+
2640
+ item = menu.Append(wx.ID_ANY, _("Load into Tree"), "", wx.ITEM_NORMAL)
2641
+ self.Bind(wx.EVT_MENU, on_menu_popup_apply_to_tree(key), item)
2642
+
2643
+ settings = self.op_data
2644
+ op_type = settings.read_persistent(str, key, "type")
2645
+ if op_type.startswith("op "):
2646
+ item = menu.Append(
2647
+ wx.ID_ANY, _("Use for statusbar"), "", wx.ITEM_NORMAL
2648
+ )
2649
+ menu.Enable(item.GetId(), bool(self.active_material is not None))
2650
+ self.Bind(wx.EVT_MENU, on_menu_popup_apply_to_statusbar(key), item)
2651
+ try:
2652
+ info = self.opinfo[op_type]
2653
+ except KeyError:
2654
+ info = self.opinfo["generic"]
2655
+ submenu = wx.Menu()
2656
+ for coloroption in ("Red", "Blue", "Green", "Black"):
2657
+ sitem = submenu.Append(wx.ID_ANY, _(coloroption), "", wx.ITEM_NORMAL)
2658
+ self.Bind(wx.EVT_MENU, on_menu_popup_recolor(coloroption, key), sitem)
2659
+ menu.AppendSubMenu(submenu, _("Color all {type}").format(type=info[0]))
2660
+
2661
+
2662
+ if self.list_preview.GetItemCount() > 0:
2663
+ menu.AppendSeparator()
2664
+
2665
+ item = menu.Append(
2666
+ wx.ID_ANY, _("Fill missing ids/label"), "", wx.ITEM_NORMAL
2667
+ )
2668
+ self.Bind(wx.EVT_MENU, on_menu_popup_missing, item)
2669
+
2670
+ self.PopupMenu(menu)
2671
+ menu.Destroy()
2672
+
2673
+ def on_resize(self, event):
2674
+ size = self.GetClientSize()
2675
+ if size[0] != 0 and size[1] != 0:
2676
+ self.tree_library.SetMaxSize(wx.Size(-1, int(0.4 * size[1])))
2677
+ self.list_preview.SetMaxSize(wx.Size(-1, int(0.4 * size[1])))
2678
+
2679
+ # Resize the columns in the listctrl
2680
+ size = self.list_preview.GetSize()
2681
+ if size[0] == 0 or size[1] == 0:
2682
+ return
2683
+ remaining = size[0] * 0.8
2684
+ # 0 "#"
2685
+ # 1 "Operation"
2686
+ # 2 "Id"
2687
+ # 3 "Label"
2688
+ # 4 "Power"
2689
+ # 5 "Speed"
2690
+ # 6 "Frequency"
2691
+ # 7 "Passes"
2692
+ if self.is_balor:
2693
+ p1 = 0.15
2694
+ p2 = 0.35
2695
+ p3 = (1.0 - p1 - p2) / 5
2696
+ p4 = p3
2697
+ else:
2698
+ p1 = 0.15
2699
+ p2 = 0.40
2700
+ p3 = (1.0 - p1 - p2) / 4
2701
+ p4 = 0
2702
+ self.list_preview.SetColumnWidth(0, int(p3 * remaining))
2703
+ self.list_preview.SetColumnWidth(1, int(p1 * remaining))
2704
+ self.list_preview.SetColumnWidth(2, int(p1 * remaining))
2705
+ self.list_preview.SetColumnWidth(3, int(p2 * remaining))
2706
+ self.list_preview.SetColumnWidth(4, int(p3 * remaining))
2707
+ self.list_preview.SetColumnWidth(5, int(p3 * remaining))
2708
+ self.list_preview.SetColumnWidth(6, int(p4 * remaining))
2709
+ self.list_preview.SetColumnWidth(7, int(p3 * remaining))
2710
+
2711
+ def before_operation_update(self, event):
2712
+ list_id = event.GetIndex() # Get the current row
2713
+ if list_id < 0:
2714
+ event.Veto()
2715
+ return
2716
+ col_id = event.GetColumn() # Get the current column
2717
+ ok = True
2718
+ try:
2719
+ index = self.list_preview.GetItemData(list_id)
2720
+ key = self.get_nth_operation(index)
2721
+ entry = self.operation_list[key]
2722
+ if not entry[0].startswith("op "):
2723
+ ok = False
2724
+ except (AttributeError, KeyError):
2725
+ ok = False
2726
+ if col_id not in range(2, 7 + 1):
2727
+ ok = False
2728
+ if col_id == 6 and not self.is_balor:
2729
+ ok = False
2730
+ if ok:
2731
+ event.Allow()
2732
+ else:
2733
+ event.Veto()
2734
+
2735
+ def on_operation_update(self, event):
2736
+ list_id = event.GetIndex() # Get the current row
2737
+ col_id = event.GetColumn() # Get the current column
2738
+ new_data = event.GetLabel() # Get the changed data
2739
+ index = self.list_preview.GetItemData(list_id)
2740
+ key = self.get_nth_operation(index)
2741
+
2742
+ if list_id >= 0 and col_id in range(2, 7 + 1):
2743
+ if col_id == 2:
2744
+ # id
2745
+ self.op_data.write_persistent(key, "id", new_data)
2746
+ elif col_id == 3:
2747
+ # label
2748
+ self.op_data.write_persistent(key, "label", new_data)
2749
+ elif col_id == 4:
2750
+ # power
2751
+ try:
2752
+ if new_data.endswith("%"):
2753
+ new_data = float(new_data[:-1]) * 10.0
2754
+ else:
2755
+ new_data = float(new_data)
2756
+ self.op_data.write_persistent(key, "power", new_data)
2757
+ new_data = f"{new_data:.0f}"
2758
+ except ValueError:
2759
+ event.Veto()
2760
+ return
2761
+ elif col_id == 5:
2762
+ # speed
2763
+ try:
2764
+ new_data = float(new_data)
2765
+ self.op_data.write_persistent(key, "speed", new_data)
2766
+ new_data = f"{new_data:.1f}"
2767
+ except ValueError:
2768
+ event.Veto()
2769
+ return
2770
+ elif col_id == 6:
2771
+ # frequency
2772
+ try:
2773
+ new_data = float(new_data)
2774
+ self.op_data.write_persistent(key, "frequency", new_data)
2775
+ new_data = f"{new_data:.0f}"
2776
+ except ValueError:
2777
+ event.Veto()
2778
+ return
2779
+ elif col_id == 7:
2780
+ # Passes
2781
+ try:
2782
+ new_data = int(new_data)
2783
+ if new_data < 1:
2784
+ new_data = 1
2785
+ self.op_data.write_persistent(key, "passes_custom", bool(new_data != 1))
2786
+ self.op_data.write_persistent(key, "passes", new_data)
2787
+ new_data = f"{new_data}"
2788
+ except ValueError:
2789
+ event.Veto()
2790
+ return
2791
+ # Set the new data in the listctrl
2792
+ self.op_data.write_configuration()
2793
+ self.list_preview.SetItem(list_id, col_id, new_data)
2794
+
2795
+ def set_parent(self, par_panel):
2796
+ self.parent_panel = par_panel
2797
+
2798
+ def pane_show(self):
2799
+ self.update_list(reload=True)
2800
+
2801
+ def pane_hide(self):
2802
+ pass
2803
+
2804
+
2805
+ class ImportPanel(wx.Panel):
2806
+ """
2807
+ Displays a how-to summary
2808
+ """
2809
+
2810
+ def __init__(self, *args, context=None, **kwds):
2811
+ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
2812
+ wx.Panel.__init__(self, *args, **kwds)
2813
+ self.context = context
2814
+ self.context.themes.set_window_colors(self)
2815
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
2816
+ label = wxStaticText(self, wx.ID_ANY, "UNDER CONSTRUCTION")
2817
+ main_sizer.Add(label, 0, wx.EXPAND, 0)
2818
+ self.SetSizer(main_sizer)
2819
+
2820
+
2821
+ class AboutPanel(wx.Panel):
2822
+ """
2823
+ Displays a how-to summary
2824
+ """
2825
+
2826
+ def __init__(self, *args, context=None, **kwds):
2827
+ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
2828
+ wx.Panel.__init__(self, *args, **kwds)
2829
+ self.context = context
2830
+ self.context.themes.set_window_colors(self)
2831
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
2832
+ info_box = StaticBoxSizer(self, wx.ID_ANY, _("How to use..."), wx.VERTICAL)
2833
+ self.parent_panel = None
2834
+ s = self.context.asset("material_howto")
2835
+ info_label = TextCtrl(
2836
+ self, wx.ID_ANY, value=s, style=wx.TE_READONLY | wx.TE_MULTILINE
2837
+ )
2838
+ fsize = 16 if system() == "Darwin" else 10
2839
+ font = wx.Font(
2840
+ fsize,
2841
+ wx.FONTFAMILY_DEFAULT,
2842
+ wx.FONTSTYLE_NORMAL,
2843
+ wx.FONTWEIGHT_NORMAL,
2844
+ )
2845
+ info_label.SetFont(font)
2846
+ info_label.SetBackgroundColour(self.GetBackgroundColour())
2847
+ info_box.Add(info_label, 1, wx.EXPAND, 0)
2848
+ main_sizer.Add(info_box, 1, wx.EXPAND, 0)
2849
+ self.SetSizer(main_sizer)
2850
+ self.Layout()
2851
+
2852
+ def set_parent(self, par_panel):
2853
+ self.parent_panel = par_panel
2854
+
2855
+
2856
+ class MaterialManager(MWindow):
2857
+ def __init__(self, *args, **kwds):
2858
+ super().__init__(860, 800, *args, **kwds)
2859
+
2860
+ self.panel_library = MaterialPanel(self, wx.ID_ANY, context=self.context)
2861
+ # self.panel_import = ImportPanel(self, wx.ID_ANY, context=self.context)
2862
+ self.panel_about = AboutPanel(self, wx.ID_ANY, context=self.context)
2863
+
2864
+ self.panel_library.set_parent(self)
2865
+ # self.panel_import.set_parent(self)
2866
+ self.panel_about.set_parent(self)
2867
+
2868
+ _icon = wx.NullIcon
2869
+ _icon.CopyFromBitmap(icon_library.GetBitmap())
2870
+ self.SetIcon(_icon)
2871
+ self.notebook_main = wx.aui.AuiNotebook(
2872
+ self,
2873
+ -1,
2874
+ style=wx.aui.AUI_NB_TAB_EXTERNAL_MOVE
2875
+ | wx.aui.AUI_NB_SCROLL_BUTTONS
2876
+ | wx.aui.AUI_NB_TAB_SPLIT
2877
+ | wx.aui.AUI_NB_TAB_MOVE,
2878
+ )
2879
+ # ARGGH, the color setting via the ArtProvider does only work
2880
+ # if you set the tabs to the bottom! wx.aui.AUI_NB_BOTTOM
2881
+
2882
+ self.window_context.themes.set_window_colors(self.notebook_main)
2883
+ bg_std = self.window_context.themes.get("win_bg")
2884
+ bg_active = self.window_context.themes.get("highlight")
2885
+ self.notebook_main.GetArtProvider().SetColour(bg_std)
2886
+ self.notebook_main.GetArtProvider().SetActiveColour(bg_active)
2887
+
2888
+ self.sizer.Add(self.notebook_main, 1, wx.EXPAND, 0)
2889
+ self.notebook_main.AddPage(self.panel_library, _("Library"))
2890
+ # self.notebook_main.AddPage(self.panel_import, _("Import"))
2891
+ self.notebook_main.AddPage(self.panel_about, _("How to use"))
2892
+ # begin wxGlade: Keymap.__set_properties
2893
+ self.DragAcceptFiles(True)
2894
+ self.Bind(wx.EVT_DROP_FILES, self.on_drop_file)
2895
+ self.SetTitle(_("Material Library"))
2896
+ self.restore_aspect(honor_initial_values=True)
2897
+
2898
+ def on_drop_file(self, event):
2899
+ """
2900
+ Drop file handler
2901
+ Accepts only a single file drop.
2902
+ """
2903
+ for pathname in event.GetFiles():
2904
+ self.panel_library.on_import(None, filename=pathname)
2905
+ break
2906
+
2907
+ def delegates(self):
2908
+ yield self.panel_library
2909
+ # yield self.panel_import
2910
+ yield self.panel_about
2911
+
2912
+ @staticmethod
2913
+ def sub_register(kernel):
2914
+ kernel.register(
2915
+ "button/config/Material",
2916
+ {
2917
+ "label": _("Material Library"),
2918
+ "icon": icon_library,
2919
+ "tip": _("Manages Material Settings"),
2920
+ "help": "materialmanager",
2921
+ "action": lambda v: kernel.console("window toggle MatManager\n"),
2922
+ },
2923
+ )
2924
+
2925
+ def window_open(self):
2926
+ pass
2927
+
2928
+ def window_close(self):
2929
+ pass
2930
+
2931
+ def edit_message(self, msg):
2932
+ self.panel_library.edit_message(msg)
2933
+
2934
+ def populate_gui(self):
2935
+ self.panel_library.populate_gui()
2936
+
2937
+ @staticmethod
2938
+ def submenu():
2939
+ # Suppress to avoid double menu-appearance
2940
+ return "", "Material Library", True
2941
+
2942
+ @staticmethod
2943
+ def helptext():
2944
+ return _("Manage and choose material specific settings")