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

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