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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1194 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1858 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4595 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4315 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +934 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/testcases.py +105 -0
  66. meerk40t/core/elements/trace.py +651 -563
  67. meerk40t/core/elements/tree_commands.py +415 -409
  68. meerk40t/core/elements/undo_redo.py +116 -58
  69. meerk40t/core/elements/wordlist.py +319 -200
  70. meerk40t/core/exceptions.py +9 -9
  71. meerk40t/core/laserjob.py +220 -220
  72. meerk40t/core/logging.py +63 -63
  73. meerk40t/core/node/blobnode.py +83 -86
  74. meerk40t/core/node/bootstrap.py +105 -103
  75. meerk40t/core/node/branch_elems.py +40 -31
  76. meerk40t/core/node/branch_ops.py +45 -38
  77. meerk40t/core/node/branch_regmark.py +48 -41
  78. meerk40t/core/node/cutnode.py +29 -32
  79. meerk40t/core/node/effect_hatch.py +375 -257
  80. meerk40t/core/node/effect_warp.py +398 -0
  81. meerk40t/core/node/effect_wobble.py +441 -309
  82. meerk40t/core/node/elem_ellipse.py +404 -309
  83. meerk40t/core/node/elem_image.py +1082 -801
  84. meerk40t/core/node/elem_line.py +358 -292
  85. meerk40t/core/node/elem_path.py +259 -201
  86. meerk40t/core/node/elem_point.py +129 -102
  87. meerk40t/core/node/elem_polyline.py +310 -246
  88. meerk40t/core/node/elem_rect.py +376 -286
  89. meerk40t/core/node/elem_text.py +445 -418
  90. meerk40t/core/node/filenode.py +59 -40
  91. meerk40t/core/node/groupnode.py +138 -74
  92. meerk40t/core/node/image_processed.py +777 -766
  93. meerk40t/core/node/image_raster.py +156 -113
  94. meerk40t/core/node/layernode.py +31 -31
  95. meerk40t/core/node/mixins.py +135 -107
  96. meerk40t/core/node/node.py +1427 -1304
  97. meerk40t/core/node/nutils.py +117 -114
  98. meerk40t/core/node/op_cut.py +463 -335
  99. meerk40t/core/node/op_dots.py +296 -251
  100. meerk40t/core/node/op_engrave.py +414 -311
  101. meerk40t/core/node/op_image.py +755 -369
  102. meerk40t/core/node/op_raster.py +787 -522
  103. meerk40t/core/node/place_current.py +37 -40
  104. meerk40t/core/node/place_point.py +329 -126
  105. meerk40t/core/node/refnode.py +58 -47
  106. meerk40t/core/node/rootnode.py +225 -219
  107. meerk40t/core/node/util_console.py +48 -48
  108. meerk40t/core/node/util_goto.py +84 -65
  109. meerk40t/core/node/util_home.py +61 -61
  110. meerk40t/core/node/util_input.py +102 -102
  111. meerk40t/core/node/util_output.py +102 -102
  112. meerk40t/core/node/util_wait.py +65 -65
  113. meerk40t/core/parameters.py +709 -707
  114. meerk40t/core/planner.py +875 -785
  115. meerk40t/core/plotplanner.py +656 -652
  116. meerk40t/core/space.py +120 -113
  117. meerk40t/core/spoolers.py +706 -705
  118. meerk40t/core/svg_io.py +1836 -1549
  119. meerk40t/core/treeop.py +534 -445
  120. meerk40t/core/undos.py +278 -124
  121. meerk40t/core/units.py +784 -680
  122. meerk40t/core/view.py +393 -322
  123. meerk40t/core/webhelp.py +62 -62
  124. meerk40t/core/wordlist.py +513 -504
  125. meerk40t/cylinder/cylinder.py +247 -0
  126. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  127. meerk40t/cylinder/gui/gui.py +24 -0
  128. meerk40t/device/__init__.py +1 -1
  129. meerk40t/device/basedevice.py +322 -123
  130. meerk40t/device/devicechoices.py +50 -0
  131. meerk40t/device/dummydevice.py +163 -128
  132. meerk40t/device/gui/defaultactions.py +618 -602
  133. meerk40t/device/gui/effectspanel.py +114 -0
  134. meerk40t/device/gui/formatterpanel.py +253 -290
  135. meerk40t/device/gui/warningpanel.py +337 -260
  136. meerk40t/device/mixins.py +13 -13
  137. meerk40t/dxf/__init__.py +1 -1
  138. meerk40t/dxf/dxf_io.py +766 -554
  139. meerk40t/dxf/plugin.py +47 -35
  140. meerk40t/external_plugins.py +79 -79
  141. meerk40t/external_plugins_build.py +28 -28
  142. meerk40t/extra/cag.py +112 -116
  143. meerk40t/extra/coolant.py +403 -0
  144. meerk40t/extra/encode_detect.py +204 -0
  145. meerk40t/extra/ezd.py +1165 -1165
  146. meerk40t/extra/hershey.py +834 -340
  147. meerk40t/extra/imageactions.py +322 -316
  148. meerk40t/extra/inkscape.py +628 -622
  149. meerk40t/extra/lbrn.py +424 -424
  150. meerk40t/extra/outerworld.py +283 -0
  151. meerk40t/extra/param_functions.py +1542 -1556
  152. meerk40t/extra/potrace.py +257 -253
  153. meerk40t/extra/serial_exchange.py +118 -0
  154. meerk40t/extra/updater.py +602 -453
  155. meerk40t/extra/vectrace.py +147 -146
  156. meerk40t/extra/winsleep.py +83 -83
  157. meerk40t/extra/xcs_reader.py +597 -0
  158. meerk40t/fill/fills.py +781 -335
  159. meerk40t/fill/patternfill.py +1061 -1061
  160. meerk40t/fill/patterns.py +614 -567
  161. meerk40t/grbl/control.py +87 -87
  162. meerk40t/grbl/controller.py +990 -903
  163. meerk40t/grbl/device.py +1084 -768
  164. meerk40t/grbl/driver.py +989 -771
  165. meerk40t/grbl/emulator.py +532 -497
  166. meerk40t/grbl/gcodejob.py +783 -767
  167. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  168. meerk40t/grbl/gui/grblcontroller.py +485 -271
  169. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  170. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  171. meerk40t/grbl/gui/gui.py +147 -116
  172. meerk40t/grbl/interpreter.py +44 -44
  173. meerk40t/grbl/loader.py +22 -22
  174. meerk40t/grbl/mock_connection.py +56 -56
  175. meerk40t/grbl/plugin.py +294 -264
  176. meerk40t/grbl/serial_connection.py +93 -88
  177. meerk40t/grbl/tcp_connection.py +81 -79
  178. meerk40t/grbl/ws_connection.py +112 -0
  179. meerk40t/gui/__init__.py +1 -1
  180. meerk40t/gui/about.py +2042 -296
  181. meerk40t/gui/alignment.py +1644 -1608
  182. meerk40t/gui/autoexec.py +199 -0
  183. meerk40t/gui/basicops.py +791 -670
  184. meerk40t/gui/bufferview.py +77 -71
  185. meerk40t/gui/busy.py +232 -133
  186. meerk40t/gui/choicepropertypanel.py +1662 -1469
  187. meerk40t/gui/consolepanel.py +706 -542
  188. meerk40t/gui/devicepanel.py +687 -581
  189. meerk40t/gui/dialogoptions.py +110 -107
  190. meerk40t/gui/executejob.py +316 -306
  191. meerk40t/gui/fonts.py +90 -90
  192. meerk40t/gui/functionwrapper.py +252 -0
  193. meerk40t/gui/gui_mixins.py +729 -0
  194. meerk40t/gui/guicolors.py +205 -182
  195. meerk40t/gui/help_assets/help_assets.py +218 -201
  196. meerk40t/gui/helper.py +154 -0
  197. meerk40t/gui/hersheymanager.py +1440 -846
  198. meerk40t/gui/icons.py +3422 -2747
  199. meerk40t/gui/imagesplitter.py +555 -508
  200. meerk40t/gui/keymap.py +354 -344
  201. meerk40t/gui/laserpanel.py +897 -806
  202. meerk40t/gui/laserrender.py +1470 -1232
  203. meerk40t/gui/lasertoolpanel.py +805 -793
  204. meerk40t/gui/magnetoptions.py +436 -0
  205. meerk40t/gui/materialmanager.py +2944 -0
  206. meerk40t/gui/materialtest.py +1722 -1694
  207. meerk40t/gui/mkdebug.py +646 -359
  208. meerk40t/gui/mwindow.py +163 -140
  209. meerk40t/gui/navigationpanels.py +2605 -2467
  210. meerk40t/gui/notes.py +143 -142
  211. meerk40t/gui/opassignment.py +414 -410
  212. meerk40t/gui/operation_info.py +310 -299
  213. meerk40t/gui/plugin.py +500 -328
  214. meerk40t/gui/position.py +714 -669
  215. meerk40t/gui/preferences.py +901 -650
  216. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  217. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  218. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  219. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  220. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  221. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  222. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  223. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  224. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  225. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  226. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  227. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  228. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  229. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  230. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  231. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  232. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  233. meerk40t/gui/propertypanels/textproperty.py +770 -755
  234. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  235. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  236. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  237. meerk40t/gui/ribbon.py +2471 -2210
  238. meerk40t/gui/scene/scene.py +1100 -1051
  239. meerk40t/gui/scene/sceneconst.py +22 -22
  240. meerk40t/gui/scene/scenepanel.py +439 -349
  241. meerk40t/gui/scene/scenespacewidget.py +365 -365
  242. meerk40t/gui/scene/widget.py +518 -505
  243. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  244. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  245. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  246. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  247. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  248. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  249. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  250. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  251. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  252. meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
  253. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  254. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  255. meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
  256. meerk40t/gui/simpleui.py +362 -333
  257. meerk40t/gui/simulation.py +2451 -2094
  258. meerk40t/gui/snapoptions.py +208 -203
  259. meerk40t/gui/spoolerpanel.py +1227 -1180
  260. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  261. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  262. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  263. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  264. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  265. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  266. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  267. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  268. meerk40t/gui/themes.py +200 -78
  269. meerk40t/gui/tips.py +590 -0
  270. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  271. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  272. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  273. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  274. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  275. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  276. meerk40t/gui/toolwidgets/toolline.py +39 -144
  277. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  278. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  279. meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
  280. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  281. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  282. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  283. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  284. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  285. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  286. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  287. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  288. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  289. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  290. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  291. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  292. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  293. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  294. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  295. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  296. meerk40t/gui/usbconnect.py +98 -91
  297. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  298. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  299. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  300. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  301. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  302. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  303. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  304. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  305. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  306. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  307. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  308. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  309. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  310. meerk40t/gui/wordlisteditor.py +985 -931
  311. meerk40t/gui/wxmeerk40t.py +1447 -1169
  312. meerk40t/gui/wxmmain.py +5644 -4112
  313. meerk40t/gui/wxmribbon.py +1591 -1076
  314. meerk40t/gui/wxmscene.py +1631 -1453
  315. meerk40t/gui/wxmtree.py +2416 -2089
  316. meerk40t/gui/wxutils.py +1769 -1099
  317. meerk40t/gui/zmatrix.py +102 -102
  318. meerk40t/image/__init__.py +1 -1
  319. meerk40t/image/dither.py +429 -0
  320. meerk40t/image/imagetools.py +2793 -2269
  321. meerk40t/internal_plugins.py +150 -130
  322. meerk40t/kernel/__init__.py +63 -12
  323. meerk40t/kernel/channel.py +259 -212
  324. meerk40t/kernel/context.py +538 -538
  325. meerk40t/kernel/exceptions.py +41 -41
  326. meerk40t/kernel/functions.py +463 -414
  327. meerk40t/kernel/jobs.py +100 -100
  328. meerk40t/kernel/kernel.py +3828 -3571
  329. meerk40t/kernel/lifecycles.py +71 -71
  330. meerk40t/kernel/module.py +49 -49
  331. meerk40t/kernel/service.py +147 -147
  332. meerk40t/kernel/settings.py +383 -343
  333. meerk40t/lihuiyu/controller.py +883 -876
  334. meerk40t/lihuiyu/device.py +1181 -1069
  335. meerk40t/lihuiyu/driver.py +1466 -1372
  336. meerk40t/lihuiyu/gui/gui.py +127 -106
  337. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  338. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  339. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  340. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  341. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  342. meerk40t/lihuiyu/interpreter.py +53 -53
  343. meerk40t/lihuiyu/laserspeed.py +450 -450
  344. meerk40t/lihuiyu/loader.py +90 -90
  345. meerk40t/lihuiyu/parser.py +404 -404
  346. meerk40t/lihuiyu/plugin.py +101 -102
  347. meerk40t/lihuiyu/tcp_connection.py +111 -109
  348. meerk40t/main.py +231 -165
  349. meerk40t/moshi/builder.py +788 -781
  350. meerk40t/moshi/controller.py +505 -499
  351. meerk40t/moshi/device.py +495 -442
  352. meerk40t/moshi/driver.py +862 -696
  353. meerk40t/moshi/gui/gui.py +78 -76
  354. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  355. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  356. meerk40t/moshi/plugin.py +43 -43
  357. meerk40t/network/console_server.py +140 -57
  358. meerk40t/network/kernelserver.py +10 -9
  359. meerk40t/network/tcp_server.py +142 -140
  360. meerk40t/network/udp_server.py +103 -77
  361. meerk40t/network/web_server.py +404 -0
  362. meerk40t/newly/controller.py +1158 -1144
  363. meerk40t/newly/device.py +874 -732
  364. meerk40t/newly/driver.py +540 -412
  365. meerk40t/newly/gui/gui.py +219 -188
  366. meerk40t/newly/gui/newlyconfig.py +116 -101
  367. meerk40t/newly/gui/newlycontroller.py +193 -186
  368. meerk40t/newly/gui/operationproperties.py +51 -51
  369. meerk40t/newly/mock_connection.py +82 -82
  370. meerk40t/newly/newly_params.py +56 -56
  371. meerk40t/newly/plugin.py +1214 -1246
  372. meerk40t/newly/usb_connection.py +322 -322
  373. meerk40t/rotary/gui/gui.py +52 -46
  374. meerk40t/rotary/gui/rotarysettings.py +240 -232
  375. meerk40t/rotary/rotary.py +202 -98
  376. meerk40t/ruida/control.py +291 -91
  377. meerk40t/ruida/controller.py +138 -1088
  378. meerk40t/ruida/device.py +676 -231
  379. meerk40t/ruida/driver.py +534 -472
  380. meerk40t/ruida/emulator.py +1494 -1491
  381. meerk40t/ruida/exceptions.py +4 -4
  382. meerk40t/ruida/gui/gui.py +71 -76
  383. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  384. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  385. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  386. meerk40t/ruida/loader.py +54 -52
  387. meerk40t/ruida/mock_connection.py +57 -109
  388. meerk40t/ruida/plugin.py +124 -87
  389. meerk40t/ruida/rdjob.py +2084 -945
  390. meerk40t/ruida/serial_connection.py +116 -0
  391. meerk40t/ruida/tcp_connection.py +146 -0
  392. meerk40t/ruida/udp_connection.py +73 -0
  393. meerk40t/svgelements.py +9671 -9669
  394. meerk40t/tools/driver_to_path.py +584 -579
  395. meerk40t/tools/geomstr.py +5583 -4680
  396. meerk40t/tools/jhfparser.py +357 -292
  397. meerk40t/tools/kerftest.py +904 -890
  398. meerk40t/tools/livinghinges.py +1168 -1033
  399. meerk40t/tools/pathtools.py +987 -949
  400. meerk40t/tools/pmatrix.py +234 -0
  401. meerk40t/tools/pointfinder.py +942 -942
  402. meerk40t/tools/polybool.py +941 -940
  403. meerk40t/tools/rasterplotter.py +1660 -547
  404. meerk40t/tools/shxparser.py +1047 -901
  405. meerk40t/tools/ttfparser.py +726 -446
  406. meerk40t/tools/zinglplotter.py +595 -593
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
  408. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
  409. meerk40t-0.9.7020.dist-info/RECORD +446 -0
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
  412. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
  413. meerk40t/balormk/elementlightjob.py +0 -159
  414. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  415. test/bootstrap.py +0 -63
  416. test/test_cli.py +0 -12
  417. test/test_core_cutcode.py +0 -418
  418. test/test_core_elements.py +0 -144
  419. test/test_core_plotplanner.py +0 -397
  420. test/test_core_viewports.py +0 -312
  421. test/test_drivers_grbl.py +0 -108
  422. test/test_drivers_lihuiyu.py +0 -443
  423. test/test_drivers_newly.py +0 -113
  424. test/test_element_degenerate_points.py +0 -43
  425. test/test_elements_classify.py +0 -97
  426. test/test_elements_penbox.py +0 -22
  427. test/test_file_svg.py +0 -176
  428. test/test_fill.py +0 -155
  429. test/test_geomstr.py +0 -1523
  430. test/test_geomstr_nodes.py +0 -18
  431. test/test_imagetools_actualize.py +0 -306
  432. test/test_imagetools_wizard.py +0 -258
  433. test/test_kernel.py +0 -200
  434. test/test_laser_speeds.py +0 -3303
  435. test/test_length.py +0 -57
  436. test/test_lifecycle.py +0 -66
  437. test/test_operations.py +0 -251
  438. test/test_operations_hatch.py +0 -57
  439. test/test_ruida.py +0 -19
  440. test/test_spooler.py +0 -22
  441. test/test_tools_rasterplotter.py +0 -29
  442. test/test_wobble.py +0 -133
  443. test/test_zingl.py +0 -124
  444. {test → meerk40t/cylinder}/__init__.py +0 -0
  445. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  446. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
@@ -1,1638 +1,1890 @@
1
- import wx
2
-
3
- from meerk40t.gui.wxutils import ScrolledPanel, StaticBoxSizer, dip_size
4
- from meerk40t.kernel import lookup_listener, signal_listener
5
-
6
- from ...core.units import UNITS_PER_MM, Length
7
- from ...svgelements import Angle, Color, Matrix
8
- from ..laserrender import swizzlecolor
9
- from ..wxutils import TextCtrl, set_ctrl_value
10
- from .attributes import IdPanel
11
-
12
- _ = wx.GetTranslation
13
-
14
- # OPERATION_TYPE_TOOLTIP = _(
15
- # """Operation Type
16
-
17
- # Cut & Engrave are vector operations, Raster and Image are raster operations.
18
-
19
- # Cut and Engrave operations are essentially the same except that for a Cut operation with Cut Outer Paths last, only closed Paths in Cut operations are considered as being Outer-most."""
20
- # )
21
-
22
-
23
- class LayerSettingPanel(wx.Panel):
24
- def __init__(self, *args, context=None, node=None, **kwds):
25
- # begin wxGlade: LayerSettingPanel.__init__
26
- kwds["style"] = kwds.get("style", 0)
27
- wx.Panel.__init__(self, *args, **kwds)
28
- self.context = context
29
- self.operation = node
30
-
31
- layer_sizer = StaticBoxSizer(self, wx.ID_ANY, _("Layer:"), wx.HORIZONTAL)
32
-
33
- self.button_layer_color = wx.Button(self, wx.ID_ANY, "")
34
- self.button_layer_color.SetBackgroundColour(wx.Colour(0, 0, 0))
35
- COLOR_TOOLTIP = _(
36
- "Change/View color of this layer. When Meerk40t classifies elements to operations,"
37
- ) + _("this exact color is used to match elements to this operation.")
38
-
39
- self.button_layer_color.SetToolTip(COLOR_TOOLTIP)
40
- layer_sizer.Add(self.button_layer_color, 0, wx.EXPAND, 0)
41
- h_classify_sizer = StaticBoxSizer(
42
- self, wx.ID_ANY, _("Restrict classification"), wx.HORIZONTAL
43
- )
44
- h_property_sizer = StaticBoxSizer(
45
- self, wx.ID_ANY, _("Properties"), wx.HORIZONTAL
46
- )
47
- rastertooltip = ""
48
- if self.operation.type == "op raster":
49
- rastertooltip = (
50
- "\n"
51
- + _("If neither stroke nor fill are checked, then the raster op")
52
- + "\n"
53
- + _("will classify all elements that have a fill")
54
- + "\n"
55
- + _("or stroke that are either black or white.")
56
- )
57
- try:
58
- self.has_stroke = self.operation.has_color_attribute("stroke")
59
- self.checkbox_stroke = wx.CheckBox(self, wx.ID_ANY, _("Stroke"))
60
- self.checkbox_stroke.SetToolTip(
61
- _("Look at the stroke color to restrict classification.")
62
- + rastertooltip
63
- )
64
- self.checkbox_stroke.SetValue(1 if self.has_stroke else 0)
65
- h_classify_sizer.Add(self.checkbox_stroke, 1, 0, 0)
66
- self.Bind(wx.EVT_CHECKBOX, self.on_check_stroke, self.checkbox_stroke)
67
- except AttributeError:
68
- self.has_stroke = None
69
-
70
- try:
71
- self.has_fill = self.operation.has_color_attribute("fill")
72
- self.checkbox_fill = wx.CheckBox(self, wx.ID_ANY, _("Fill"))
73
- self.checkbox_fill.SetToolTip(
74
- _("Look at the fill color to restrict classification.") + rastertooltip
75
- )
76
- self.checkbox_fill.SetValue(1 if self.has_fill else 0)
77
- h_classify_sizer.Add(self.checkbox_fill, 1, 0, 0)
78
- self.Bind(wx.EVT_CHECKBOX, self.on_check_fill, self.checkbox_fill)
79
- except AttributeError:
80
- self.has_fill = None
81
-
82
- self.checkbox_stop = wx.CheckBox(self, wx.ID_ANY, _("Stop"))
83
- self.checkbox_stop.SetToolTip(
84
- _("If active, then this op will prevent further classification")
85
- + "\n"
86
- + _("from other ops if it could classify an element by itself.")
87
- )
88
- h_classify_sizer.Add(self.checkbox_stop, 1, 0, 0)
89
- self.Bind(wx.EVT_CHECKBOX, self.on_check_stop, self.checkbox_stop)
90
- self.has_stop = hasattr(self.operation, "stopop")
91
- if not self.has_stop:
92
- self.checkbox_stop.SetValue(False)
93
- self.checkbox_stop.Enable(False)
94
- else:
95
- self.checkbox_stop.SetValue(self.operation.stopop)
96
- self.checkbox_stop.Enable(True)
97
-
98
- if (
99
- self.has_fill is not None
100
- or self.has_stroke is not None
101
- or self.has_stop is not None
102
- ):
103
- layer_sizer.Add(h_classify_sizer, 1, wx.EXPAND, 0)
104
-
105
- # self.combo_type = wx.ComboBox(
106
- # self,
107
- # wx.ID_ANY,
108
- # choices=["Engrave", "Cut", "Raster", "Image", "Hatch", "Dots"],
109
- # style=wx.CB_DROPDOWN,
110
- # )
111
- # self.combo_type.SetToolTip(OPERATION_TYPE_TOOLTIP)
112
- # self.combo_type.SetSelection(0)
113
- # layer_sizer.Add(self.combo_type, 1, 0, 0)
114
-
115
- self.checkbox_output = wx.CheckBox(self, wx.ID_ANY, _("Enable"))
116
- self.checkbox_output.SetToolTip(
117
- _("Enable this operation for inclusion in Execute Job.")
118
- )
119
- self.checkbox_output.SetValue(1)
120
- h_property_sizer.Add(self.checkbox_output, 1, 0, 0)
121
-
122
- self.checkbox_visible = wx.CheckBox(self, wx.ID_ANY, _("Visible"))
123
- self.checkbox_visible.SetToolTip(
124
- _("Hide all contained elements on scene if not set.")
125
- )
126
- self.checkbox_visible.SetValue(1)
127
- self.checkbox_visible.Enable(False)
128
- h_property_sizer.Add(self.checkbox_visible, 1, 0, 0)
129
-
130
- self.checkbox_default = wx.CheckBox(self, wx.ID_ANY, _("Default"))
131
- OPERATION_DEFAULT_TOOLTIP = (
132
- _(
133
- "When classifying elements, Default operations gain all appropriate elements "
134
- )
135
- + _("not matched to an existing operation of the same colour, ")
136
- + _("rather than a new operation of that color being created.")
137
- )
138
-
139
- self.checkbox_default.SetToolTip(OPERATION_DEFAULT_TOOLTIP)
140
- self.checkbox_default.SetValue(1)
141
- h_property_sizer.Add(self.checkbox_default, 1, 0, 0)
142
-
143
- layer_sizer.Add(h_property_sizer, 1, wx.EXPAND, 0)
144
-
145
- self.SetSizer(layer_sizer)
146
-
147
- self.Layout()
148
-
149
- self.Bind(wx.EVT_BUTTON, self.on_button_layer, self.button_layer_color)
150
- # self.Bind(wx.EVT_COMBOBOX, self.on_combo_operation, self.combo_type)
151
- self.Bind(wx.EVT_CHECKBOX, self.on_check_output, self.checkbox_output)
152
- self.Bind(wx.EVT_CHECKBOX, self.on_check_visible, self.checkbox_visible)
153
- self.Bind(wx.EVT_CHECKBOX, self.on_check_default, self.checkbox_default)
154
- # end wxGlade
155
-
156
- def pane_hide(self):
157
- pass
158
-
159
- def pane_show(self):
160
- pass
161
-
162
- def accepts(self, node):
163
- return node.type in (
164
- "op cut",
165
- "op engrave",
166
- "op raster",
167
- "op image",
168
- "op dots",
169
- )
170
-
171
- def set_widgets(self, node):
172
- self.operation = node
173
- if self.operation is None or not self.accepts(node):
174
- self.Hide()
175
- return
176
- self.button_layer_color.SetBackgroundColour(
177
- wx.Colour(swizzlecolor(self.operation.color))
178
- )
179
- if self.operation.output is not None:
180
- self.checkbox_output.SetValue(self.operation.output)
181
- flag_set = True
182
- flag_enabled = False
183
- if self.operation.output is not None:
184
- if not self.operation.output:
185
- flag_enabled = True
186
- flag_set = self.operation.is_visible
187
- self.checkbox_visible.SetValue(flag_set)
188
- self.checkbox_visible.Enable(flag_enabled)
189
- if self.operation.default is not None:
190
- self.checkbox_default.SetValue(self.operation.default)
191
- try:
192
- if self.has_fill:
193
- self.checkbox_fill.SetValue(
194
- 1 if self.operation.has_color_attribute("fill") else 0
195
- )
196
- except AttributeError:
197
- pass
198
- try:
199
- if self.has_stroke:
200
- self.checkbox_stroke.SetValue(
201
- 1 if self.operation.has_color_attribute("stroke") else 0
202
- )
203
- except AttributeError:
204
- pass
205
- self.has_stop = hasattr(self.operation, "stopop")
206
- if not self.has_stop:
207
- self.checkbox_stop.SetValue(False)
208
- self.checkbox_stop.Enable(False)
209
- else:
210
- self.checkbox_stop.SetValue(self.operation.stopop)
211
- self.checkbox_stop.Enable(True)
212
-
213
- self.Layout()
214
- self.Show()
215
-
216
- def on_button_layer(self, event=None): # wxGlade: OperationProperty.<event_handler>
217
- data = wx.ColourData()
218
- if self.operation.color is not None and self.operation.color != "none":
219
- data.SetColour(wx.Colour(swizzlecolor(self.operation.color)))
220
- dlg = wx.ColourDialog(self, data)
221
- if dlg.ShowModal() == wx.ID_OK:
222
- data = dlg.GetColourData()
223
- color = data.GetColour()
224
- rgb = color.GetRGB()
225
- color = swizzlecolor(rgb)
226
- self.operation.color = Color(color, 1.0)
227
- try:
228
- self.button_layer_color.SetBackgroundColour(
229
- wx.Colour(swizzlecolor(self.operation.color))
230
- )
231
- except RuntimeError:
232
- return
233
- # Ask the user if she/he wants to assign the color of the contained objects
234
- try:
235
- candidate_stroke = bool(self.checkbox_stroke.GetValue())
236
- except AttributeError:
237
- candidate_stroke = False
238
- try:
239
- candidate_fill = bool(self.checkbox_fill.GetValue())
240
- except AttributeError:
241
- candidate_fill = False
242
- if (
243
- self.operation.type in ("op engrave", "op cut")
244
- and len(self.operation.children) > 0
245
- and (candidate_fill or candidate_stroke)
246
- ):
247
- dlg = wx.MessageDialog(
248
- None,
249
- message=_(
250
- "Do you want to change the color of the contained elements too?"
251
- ),
252
- caption=_("Update Colors?"),
253
- style=wx.YES_NO | wx.ICON_QUESTION,
254
- )
255
- response = dlg.ShowModal()
256
- dlg.Destroy()
257
- if response == wx.ID_YES:
258
- changed = []
259
- for refnode in self.operation.children:
260
- cnode = refnode.node
261
- add_to_change = False
262
- if candidate_stroke and hasattr(cnode, "stroke"):
263
- cnode.stroke = self.operation.color
264
- add_to_change = True
265
-
266
- if candidate_fill and hasattr(cnode, "fill"):
267
- cnode.fill = self.operation.color
268
- add_to_change = True
269
-
270
- if add_to_change:
271
- changed.append(cnode)
272
- if len(changed) > 0:
273
- self.context.elements.signal("element_property_update", changed)
274
- self.context.elements.signal("refresh_scene", "Scene")
275
-
276
- self.context.elements.signal(
277
- "element_property_reload", self.operation, "button_layer"
278
- )
279
-
280
- def on_check_output(self, event=None): # wxGlade: OperationProperty.<event_handler>
281
- if self.operation.output != bool(self.checkbox_output.GetValue()):
282
- self.operation.output = bool(self.checkbox_output.GetValue())
283
- self.context.elements.signal(
284
- "element_property_reload", self.operation, "check_output"
285
- )
286
- self.checkbox_visible.Enable(not bool(self.checkbox_output.GetValue()))
287
-
288
- def on_check_visible(self, event=None):
289
- if self.operation.is_visible != bool(self.checkbox_visible.GetValue()):
290
- self.operation.is_visible = bool(self.checkbox_visible.GetValue())
291
- self.context.elements.validate_selected_area()
292
- self.context.elements.signal("element_property_update", self.operation)
293
- self.context.elements.signal("refresh_scene", "Scene")
294
-
295
- def on_check_default(self, event=None):
296
- if self.operation.default != bool(self.checkbox_default.GetValue()):
297
- self.operation.default = bool(self.checkbox_default.GetValue())
298
- self.context.elements.signal(
299
- "element_property_reload", self.operation, "check_default"
300
- )
301
-
302
- def on_check_fill(self, event=None):
303
- if self.checkbox_fill.GetValue():
304
- self.operation.add_color_attribute("fill")
305
- else:
306
- self.operation.remove_color_attribute("fill")
307
- self.context.elements.signal(
308
- "element_property_reload", self.operation, "check_fill"
309
- )
310
- event.Skip()
311
-
312
- def on_check_stroke(self, event=None):
313
- if self.checkbox_stroke.GetValue():
314
- self.operation.add_color_attribute("stroke")
315
- else:
316
- self.operation.remove_color_attribute("stroke")
317
- self.context.elements.signal(
318
- "element_property_reload", self.operation, "check_stroke"
319
- )
320
- event.Skip()
321
-
322
- def on_check_stop(self, event=None):
323
- if self.checkbox_stop.GetValue():
324
- self.operation.stopop = True
325
- else:
326
- self.operation.stopop = False
327
- self.context.elements.signal(
328
- "element_property_reload", self.operation, "check_stopop"
329
- )
330
- event.Skip()
331
-
332
-
333
- # end of class LayerSettingPanel
334
-
335
-
336
- class SpeedPpiPanel(wx.Panel):
337
- def __init__(self, *args, context=None, node=None, **kwds):
338
- # begin wxGlade: SpeedPpiPanel.__init__
339
- kwds["style"] = kwds.get("style", 0)
340
- wx.Panel.__init__(self, *args, **kwds)
341
- self.context = context
342
- self.operation = node
343
-
344
- self.context.device.setting(bool, "use_percent_for_power_display", False)
345
- self.use_percent = self.context.device.use_percent_for_power_display
346
- self.context.device.setting(bool, "use_mm_min_for_speed_display", False)
347
- self.use_mm_min = self.context.device.use_mm_min_for_speed_display
348
-
349
- speed_power_sizer = wx.BoxSizer(wx.HORIZONTAL)
350
- if self.use_mm_min:
351
- speed_fact = 60
352
- else:
353
- speed_fact = 1
354
-
355
- speed_sizer = StaticBoxSizer(self, wx.ID_ANY, _("Speed"), wx.HORIZONTAL)
356
- speed_power_sizer.Add(speed_sizer, 1, wx.EXPAND, 0)
357
-
358
- self.text_speed = TextCtrl(
359
- self,
360
- wx.ID_ANY,
361
- f"{20 * speed_fact:.0f}",
362
- limited=True,
363
- check="float",
364
- style=wx.TE_PROCESS_ENTER,
365
- nonzero=True,
366
- )
367
- self.trailer_speed = wx.StaticText(self, id=wx.ID_ANY)
368
- speed_sizer.Add(self.text_speed, 1, wx.EXPAND, 0)
369
- speed_sizer.Add(self.trailer_speed, 0, wx.ALIGN_CENTER_VERTICAL, 0)
370
-
371
- self.power_sizer = StaticBoxSizer(
372
- self, wx.ID_ANY, _("Power (ppi)"), wx.HORIZONTAL
373
- )
374
- speed_power_sizer.Add(self.power_sizer, 1, wx.EXPAND, 0)
375
-
376
- self.text_power = TextCtrl(
377
- self,
378
- wx.ID_ANY,
379
- "1000.0",
380
- limited=True,
381
- check="float",
382
- style=wx.TE_PROCESS_ENTER,
383
- )
384
- self.trailer_power = wx.StaticText(self, id=wx.ID_ANY, label=_("/1000"))
385
- self.power_sizer.Add(self.text_power, 1, wx.EXPAND, 0)
386
- self.power_sizer.Add(self.trailer_power, 0, wx.ALIGN_CENTER_VERTICAL, 0)
387
-
388
- self.update_power_speed_properties()
389
-
390
- freq = self.context.device.lookup("frequency")
391
- if freq:
392
- frequency_sizer = StaticBoxSizer(
393
- self, wx.ID_ANY, _("Frequency (kHz)"), wx.HORIZONTAL
394
- )
395
- speed_power_sizer.Add(frequency_sizer, 1, wx.EXPAND, 0)
396
-
397
- self.text_frequency = TextCtrl(
398
- self,
399
- wx.ID_ANY,
400
- "20.0",
401
- limited=True,
402
- check="float",
403
- style=wx.TE_PROCESS_ENTER,
404
- )
405
- OPERATION_FREQUENCY_TOOLTIP = (
406
- _("Laser frequency in kHz.")
407
- + "\n"
408
- + _("For lasers with frequencies that can be set.")
409
- )
410
- self.text_frequency.SetToolTip(OPERATION_FREQUENCY_TOOLTIP)
411
- self.text_frequency.set_warn_level(*freq)
412
- frequency_sizer.Add(self.text_frequency, 1, wx.EXPAND, 0)
413
- else:
414
- self.text_frequency = None
415
-
416
- self.SetSizer(speed_power_sizer)
417
-
418
- self.Layout()
419
-
420
- self.text_speed.SetActionRoutine(self.on_text_speed)
421
- self.text_power.SetActionRoutine(self.on_text_power)
422
-
423
- if self.text_frequency:
424
- self.text_frequency.SetActionRoutine(self.on_text_frequency)
425
-
426
- # end wxGlade
427
-
428
- def pane_hide(self):
429
- pass
430
-
431
- def pane_show(self):
432
- self.update_power_speed_properties()
433
-
434
- def on_device_update(self):
435
- try:
436
- self.update_power_speed_properties()
437
- except RuntimeError:
438
- # Pane was already destroyed
439
- pass
440
- self.set_widgets(self.operation)
441
-
442
- def update_power_speed_properties(self):
443
- speed_min = None
444
- speed_max = None
445
- power_min = None
446
- power_max = None
447
-
448
- op = self.operation.type
449
- if op.startswith("op "): # Should, shouldn't it?
450
- op = op[3:]
451
- else:
452
- op = ""
453
- if op != "":
454
- label = "dangerlevel_op_" + op
455
- warning = [False, 0, False, 0, False, 0, False, 0]
456
- if hasattr(self.context.device, label):
457
- dummy = getattr(self.context.device, label)
458
- if isinstance(dummy, (tuple, list)) and len(dummy) == len(warning):
459
- warning = dummy
460
- if warning[0]:
461
- power_min = warning[1]
462
- if warning[2]:
463
- power_max = warning[3]
464
- if warning[4]:
465
- speed_min = warning[5]
466
- if warning[6]:
467
- speed_max = warning[7]
468
- self.use_mm_min = self.context.device.use_mm_min_for_speed_display
469
- if self.use_mm_min:
470
- if speed_min is not None:
471
- speed_min *= 60
472
- if speed_max is not None:
473
- speed_max *= 60
474
- speed_unit = "mm/min"
475
- else:
476
- speed_unit = "mm/s"
477
- self.trailer_speed.SetLabel(speed_unit)
478
- OPERATION_SPEED_TOOLTIP = (
479
- _("Speed at which the head moves in {unit}.").format(unit=speed_unit)
480
- + "\n"
481
- + _(
482
- "For Cut/Engrave vector operations, this is the speed of the head regardless of direction i.e. the separate x/y speeds vary according to the direction."
483
- )
484
- + "\n"
485
- + _(
486
- "For Raster/Image operations, this is the speed of the head as it sweeps backwards and forwards."
487
- )
488
- )
489
- self.text_speed.SetToolTip(OPERATION_SPEED_TOOLTIP)
490
- self.text_speed.set_error_level(0, None)
491
- self.text_speed.set_warn_level(speed_min, speed_max)
492
-
493
- self.use_percent = self.context.device.use_percent_for_power_display
494
- if self.use_percent:
495
- self.trailer_power.SetLabel("%")
496
- self.text_power._check = "percent"
497
- if power_min is not None:
498
- power_min /= 10
499
- if power_max is not None:
500
- power_max /= 10
501
- self.text_power.set_range(0, 100)
502
- self.text_power.set_warn_level(power_min, power_max)
503
- OPERATION_POWER_TOOLTIP = _(
504
- "% of maximum power - This is a percentage of the maximum power of the laser."
505
- )
506
- self.power_sizer.SetLabel(_("Power"))
507
- else:
508
- self.trailer_power.SetLabel(_("/1000"))
509
- self.text_power._check = "float"
510
- self.text_power.set_range(0, 1000)
511
- self.text_power.set_warn_level(power_min, power_max)
512
- OPERATION_POWER_TOOLTIP = _(
513
- _("Pulses Per Inch - This is software created laser power control.")
514
- + "\n"
515
- + _("1000 is always on, 500 is half power (fire every other step).")
516
- + "\n"
517
- + _(
518
- 'Values of 100 or have pulses > 1/10" and are generally used only for dotted or perforated lines.'
519
- )
520
- )
521
- self.power_sizer.SetLabel(_("Power (ppi)"))
522
- self.text_power.SetToolTip(OPERATION_POWER_TOOLTIP)
523
-
524
- def accepts(self, node):
525
- return node.type in (
526
- "op cut",
527
- "op engrave",
528
- "op raster",
529
- "op image",
530
- "op dots",
531
- )
532
-
533
- def set_widgets(self, node):
534
- self.operation = node
535
- if self.operation is None or not self.accepts(node):
536
- self.Hide()
537
- return
538
- if self.operation.speed is not None:
539
- if self.use_mm_min:
540
- set_ctrl_value(self.text_speed, str(self.operation.speed * 60))
541
- else:
542
- set_ctrl_value(self.text_speed, str(self.operation.speed))
543
- if self.operation.power is not None:
544
- if self.use_percent:
545
- set_ctrl_value(self.text_power, f"{self.operation.power / 10.0:.0f}")
546
- else:
547
- set_ctrl_value(self.text_power, f"{self.operation.power:.0f}")
548
- self.update_power_label()
549
- if self.operation.frequency is not None and self.text_frequency:
550
- set_ctrl_value(self.text_frequency, str(self.operation.frequency))
551
- self.Show()
552
-
553
- def on_text_speed(self): # wxGlade: OperationProperty.<event_handler>
554
- try:
555
- value = float(self.text_speed.GetValue())
556
- if self.use_mm_min:
557
- value /= 60
558
- if self.operation.speed != value:
559
- self.operation.speed = value
560
- self.context.elements.signal(
561
- "element_property_reload", self.operation, "text_speed"
562
- )
563
- except ValueError:
564
- pass
565
-
566
- def on_text_frequency(self):
567
- try:
568
- value = float(self.text_frequency.GetValue())
569
- if self.operation.frequency != value:
570
- self.operation.frequency = value
571
- self.context.elements.signal(
572
- "element_property_reload", self.operation, "text_frquency"
573
- )
574
- except ValueError:
575
- pass
576
-
577
- def update_power_label(self):
578
- # if self.operation.power <= 100:
579
- # self.power_label.SetLabel(_("Power (ppi):") + "⚠️")
580
- # else:
581
- # self.power_label.SetLabel(_("Power (ppi):"))
582
- if self.use_percent:
583
- return
584
- try:
585
- value = float(self.text_power.GetValue())
586
- self.power_sizer.SetLabel(_("Power (ppi)") + f" ({value/10:.1f}%)")
587
- except ValueError:
588
- return
589
-
590
- def on_text_power(self):
591
- try:
592
- value = float(self.text_power.GetValue())
593
- if self.use_percent:
594
- value *= 10
595
- if self.operation.power != value:
596
- self.operation.power = value
597
- self.update_power_label()
598
- self.context.elements.signal(
599
- "element_property_reload", self.operation, "text_power"
600
- )
601
- except ValueError:
602
- return
603
-
604
-
605
- # end of class SpeedPpiPanel
606
-
607
-
608
- class PassesPanel(wx.Panel):
609
- def __init__(self, *args, context=None, node=None, **kwds):
610
- # begin wxGlade: PassesPanel.__init__
611
- kwds["style"] = kwds.get("style", 0)
612
- wx.Panel.__init__(self, *args, **kwds)
613
- self.context = context
614
- self.operation = node
615
- self.has_kerf = False
616
-
617
- sizer_main = wx.BoxSizer(wx.HORIZONTAL)
618
-
619
- self.sizer_kerf = StaticBoxSizer(
620
- self, wx.ID_ANY, _("Kerf compensation:"), wx.HORIZONTAL
621
- )
622
- self.text_kerf = TextCtrl(
623
- self,
624
- wx.ID_ANY,
625
- "0",
626
- limited=True,
627
- check="length",
628
- style=wx.TE_PROCESS_ENTER,
629
- )
630
- self.text_kerf.SetToolTip(
631
- _(
632
- "Enter half the width of your laserbeam (kerf)\n"
633
- + "if you want to have a shape with an exact size.\n"
634
- + "Use the negative value if you are using the cutout\n"
635
- + "as a placeholder for another part (eg inlays)."
636
- )
637
- )
638
- self.kerf_label = wx.StaticText(self, wx.ID_ANY, "")
639
- self.sizer_kerf.Add(self.text_kerf, 1, wx.EXPAND, 0)
640
- self.sizer_kerf.Add(self.kerf_label, 0, wx.ALIGN_CENTER_VERTICAL, 0)
641
-
642
- sizer_passes = StaticBoxSizer(self, wx.ID_ANY, _("Passes:"), wx.HORIZONTAL)
643
-
644
- self.check_passes = wx.CheckBox(self, wx.ID_ANY, _("Passes"))
645
- self.check_passes.SetToolTip(_("Enable Operation Passes"))
646
- sizer_passes.Add(self.check_passes, 0, wx.EXPAND, 0)
647
-
648
- self.text_passes = TextCtrl(
649
- self, wx.ID_ANY, "1", limited=True, check="int", style=wx.TE_PROCESS_ENTER
650
- )
651
- OPERATION_PASSES_TOOLTIP = (
652
- _("How many times to repeat this operation?")
653
- + "\n"
654
- + _(
655
- "Setting e.g. passes to 2 is essentially equivalent to Duplicating the operation, "
656
- )
657
- + _(
658
- "creating a second identical operation with the same settings and same elements."
659
- )
660
- + "\n"
661
- + _("The number of Operation Passes can be changed extremely easily, ")
662
- + _("but you cannot change any of the other settings.")
663
- + "\n"
664
- + _(
665
- "Duplicating the Operation gives more flexibility for changing settings, "
666
- )
667
- + _("but is far more cumbersome to change the number of duplications ")
668
- + _("because you need to add and delete the duplicates one by one.")
669
- )
670
-
671
- self.text_passes.SetToolTip(OPERATION_PASSES_TOOLTIP)
672
- sizer_passes.Add(self.text_passes, 1, wx.EXPAND, 0)
673
-
674
- sizer_main.Add(sizer_passes, 1, wx.EXPAND, 0)
675
- sizer_main.Add(self.sizer_kerf, 1, wx.EXPAND, 0)
676
-
677
- self.SetSizer(sizer_main)
678
-
679
- self.Layout()
680
-
681
- self.Bind(wx.EVT_CHECKBOX, self.on_check_passes, self.check_passes)
682
-
683
- self.text_passes.SetActionRoutine(self.on_text_passes)
684
- self.text_kerf.SetActionRoutine(self.on_text_kerf)
685
- # end wxGlade
686
-
687
- def pane_hide(self):
688
- pass
689
-
690
- def pane_show(self):
691
- pass
692
-
693
- def accepts(self, node):
694
- return node.type in (
695
- "op cut",
696
- "op engrave",
697
- "op raster",
698
- "op image",
699
- "op dots",
700
- )
701
-
702
- def set_widgets(self, node):
703
- self.operation = node
704
- if self.operation is None or not self.accepts(node):
705
- self.Hide()
706
- return
707
- self.has_kerf = bool(self.operation.type == "op cut")
708
- if self.has_kerf:
709
- s_label = ""
710
- s_kerf = "0"
711
- try:
712
- ll = Length(self.operation.kerf, digits=2, preferred_units="mm")
713
- kval = float(ll)
714
- s_kerf = ll.preferred_length
715
- if kval < 0:
716
- s_label = " " + _("Inward")
717
- elif kval > 0:
718
- s_label = " " + _("Outward")
719
- else:
720
- s_kerf = "0"
721
-
722
- except ValueError:
723
- pass
724
- self.text_kerf.SetValue(s_kerf)
725
- self.kerf_label.SetLabel(s_label)
726
- if self.operation.passes_custom is not None:
727
- self.check_passes.SetValue(self.operation.passes_custom)
728
- if self.operation.passes is not None:
729
- set_ctrl_value(self.text_passes, str(self.operation.passes))
730
- on = self.check_passes.GetValue()
731
- self.text_passes.Enable(on)
732
- self.sizer_kerf.ShowItems(self.has_kerf)
733
- self.sizer_kerf.Show(self.has_kerf)
734
- self.Layout()
735
- self.Show()
736
-
737
- def on_check_passes(self, event=None): # wxGlade: OperationProperty.<event_handler>
738
- on = self.check_passes.GetValue()
739
- self.text_passes.Enable(on)
740
- self.operation.passes_custom = bool(on)
741
- self.context.elements.signal(
742
- "element_property_reload", self.operation, "check_passes"
743
- )
744
- event.Skip()
745
-
746
- def on_text_passes(self):
747
- try:
748
- value = int(self.text_passes.GetValue())
749
- if self.operation.passes != value:
750
- self.operation.passes = value
751
- self.context.elements.signal(
752
- "element_property_reload", self.operation, "text_Passes"
753
- )
754
- except ValueError:
755
- pass
756
-
757
- def on_text_kerf(self):
758
- try:
759
- value = float(Length(self.text_kerf.GetValue()))
760
- if self.operation.kerf != value:
761
- self.operation.kerf = value
762
- self.context.elements.signal(
763
- "element_property_reload", self.operation, "text_kerf"
764
- )
765
- except ValueError:
766
- pass
767
-
768
-
769
- # end of class PassesPanel
770
-
771
-
772
- class InfoPanel(wx.Panel):
773
- def __init__(self, *args, context=None, node=None, **kwds):
774
- # begin wxGlade: PassesPanel.__init__
775
- kwds["style"] = kwds.get("style", 0)
776
- wx.Panel.__init__(self, *args, **kwds)
777
- self.context = context
778
- self.operation = node
779
-
780
- sizer_info = StaticBoxSizer(self, wx.ID_ANY, _("Info:"), wx.HORIZONTAL)
781
-
782
- sizer_children = StaticBoxSizer(self, wx.ID_ANY, _("Children:"), wx.HORIZONTAL)
783
- sizer_time = StaticBoxSizer(
784
- self, wx.ID_ANY, _("Est. burn-time:"), wx.HORIZONTAL
785
- )
786
-
787
- self.text_children = wx.TextCtrl(self, wx.ID_ANY, "0", style=wx.TE_READONLY)
788
- self.text_children.SetMinSize(dip_size(self, 25, -1))
789
- self.text_children.SetMaxSize(dip_size(self, 55, -1))
790
- self.text_time = wx.TextCtrl(self, wx.ID_ANY, "---", style=wx.TE_READONLY)
791
- self.text_time.SetMinSize(dip_size(self, 55, -1))
792
- self.text_time.SetMaxSize(dip_size(self, 100, -1))
793
- self.text_children.SetToolTip(
794
- _("How many elements does this operation contain")
795
- )
796
- self.text_time.SetToolTip(_("Estimated time for execution (hh:mm:ss)"))
797
- self.btn_update = wx.Button(self, wx.ID_ANY, _("Calculate"))
798
- self.btn_update.Bind(wx.EVT_BUTTON, self.on_button_calculate)
799
-
800
- self.btn_recalc = wx.Button(self, wx.ID_ANY, _("Re-Classify"))
801
- self.btn_recalc.Bind(wx.EVT_BUTTON, self.on_button_refresh)
802
-
803
- sizer_children.Add(self.text_children, 1, wx.EXPAND, 0)
804
- sizer_time.Add(self.text_time, 1, wx.EXPAND, 0)
805
- sizer_time.Add(self.btn_update, 0, wx.EXPAND, 0)
806
- sizer_time.AddSpacer(20)
807
- sizer_time.Add(self.btn_recalc, 0, wx.EXPAND, 0)
808
-
809
- sizer_info.Add(sizer_children, 1, wx.EXPAND, 0)
810
- sizer_info.Add(sizer_time, 2, wx.EXPAND, 0)
811
-
812
- self.SetSizer(sizer_info)
813
-
814
- self.Layout()
815
-
816
- # end wxGlade
817
-
818
- def pane_hide(self):
819
- pass
820
-
821
- def pane_show(self):
822
- pass
823
-
824
- def on_button_refresh(self, event):
825
- if not hasattr(self.operation, "classify"):
826
- return
827
-
828
- infotxt = (
829
- _("Do you really want to reassign elements to this operation?")
830
- + "\n"
831
- + _("Atttention: This will delete all existing assignments!")
832
- )
833
- dlg = wx.MessageDialog(
834
- None, infotxt, "Re-Classify", wx.YES_NO | wx.ICON_WARNING
835
- )
836
- result = dlg.ShowModal()
837
- dlg.Destroy()
838
- if result == wx.ID_YES:
839
- myop = self.operation
840
- myop.remove_all_children()
841
- data = list(self.context.elements.elems())
842
- reverse = self.context.elements.classify_reverse
843
- fuzzy = self.context.elements.classify_fuzzy
844
- fuzzydistance = self.context.elements.classify_fuzzydistance
845
- if reverse:
846
- data = reversed(data)
847
- for node in data:
848
- # result is a tuple containing classified, should_break, feedback
849
- result = myop.classify(
850
- node,
851
- fuzzy=fuzzy,
852
- fuzzydistance=fuzzydistance,
853
- usedefault=False,
854
- )
855
- # Probably moot as the next command will move the focus away...
856
- self.refresh_display()
857
- self.context.signal("tree_changed")
858
- self.context.signal("activate_single_node", myop)
859
-
860
- def on_button_calculate(self, event):
861
- self.text_time.SetValue(self.operation.time_estimate())
862
-
863
- def refresh_display(self):
864
- childs = len(self.operation.children)
865
- self.text_time.SetValue("---")
866
- self.text_children.SetValue(str(childs))
867
-
868
- def accepts(self, node):
869
- return node.type in (
870
- "op cut",
871
- "op engrave",
872
- "op raster",
873
- "op image",
874
- "op dots",
875
- )
876
-
877
- def set_widgets(self, node):
878
- self.operation = node
879
- if self.operation is None or not self.accepts(node):
880
- self.Hide()
881
- return
882
- self.refresh_display()
883
- self.Show()
884
-
885
-
886
- # end of class InfoPanel
887
-
888
-
889
- class PanelStartPreference(wx.Panel):
890
- def __init__(self, *args, context=None, node=None, **kwds):
891
- # begin wxGlade: PanelStartPreference.__init__
892
- kwds["style"] = kwds.get("style", 0)
893
- wx.Panel.__init__(self, *args, **kwds)
894
- self.context = context
895
- self.operation = node
896
-
897
- sizer_2 = StaticBoxSizer(self, wx.ID_ANY, _("Start Preference:"), wx.VERTICAL)
898
-
899
- self.slider_top = wx.Slider(self, wx.ID_ANY, 1, 0, 2)
900
- sizer_2.Add(self.slider_top, 0, wx.EXPAND, 0)
901
-
902
- sizer_7 = wx.BoxSizer(wx.HORIZONTAL)
903
- sizer_2.Add(sizer_7, 0, wx.EXPAND, 0)
904
-
905
- self.slider_left = wx.Slider(self, wx.ID_ANY, 1, 0, 2, style=wx.SL_VERTICAL)
906
- sizer_7.Add(self.slider_left, 0, wx.EXPAND, 0)
907
-
908
- self.display_panel = wx.Panel(self, wx.ID_ANY)
909
- sizer_7.Add(self.display_panel, 1, wx.EXPAND, 0)
910
-
911
- self.slider_right = wx.Slider(self, wx.ID_ANY, 1, 0, 2, style=wx.SL_VERTICAL)
912
- sizer_7.Add(self.slider_right, 0, 0, 0)
913
-
914
- self.slider_bottom = wx.Slider(self, wx.ID_ANY, 1, 0, 2)
915
- sizer_2.Add(self.slider_bottom, 0, wx.EXPAND, 0)
916
-
917
- self.SetSizer(sizer_2)
918
-
919
- self.Layout()
920
-
921
- self.Bind(wx.EVT_SLIDER, self.on_slider_top, self.slider_top)
922
- self.Bind(wx.EVT_SLIDER, self.on_slider_left, self.slider_left)
923
- self.Bind(wx.EVT_SLIDER, self.on_slider_right, self.slider_right)
924
- self.Bind(wx.EVT_SLIDER, self.on_slider_bottom, self.slider_bottom)
925
- # end wxGlade
926
- self.Bind(wx.EVT_SIZE, self.on_size)
927
- self.display_panel.Bind(wx.EVT_PAINT, self.on_display_paint)
928
- self.display_panel.Bind(wx.EVT_ERASE_BACKGROUND, self.on_display_erase)
929
-
930
- self.raster_pen = wx.Pen()
931
- self.raster_pen.SetColour(wx.BLACK)
932
- self.raster_pen.SetWidth(2)
933
-
934
- self.travel_pen = wx.Pen()
935
- self.travel_pen.SetColour(wx.Colour(255, 127, 255, 127))
936
- self.travel_pen.SetWidth(2)
937
-
938
- self.direction_pen = wx.Pen()
939
- self.direction_pen.SetColour(wx.Colour(127, 127, 255))
940
- self.direction_pen.SetWidth(2)
941
-
942
- self.raster_lines = None
943
- self.travel_lines = None
944
- self.direction_lines = None
945
-
946
- self._Buffer = None
947
-
948
- self.context.setting(bool, "developer_mode", False)
949
- if not self.context.developer_mode:
950
- # 0.6.1 freeze, drops.
951
- self.slider_top.Enable(False)
952
- self.slider_right.Enable(False)
953
- self.slider_left.Enable(False)
954
- self.slider_bottom.Enable(False)
955
- self.toggle_sliders = False
956
- else:
957
- self.toggle_sliders = True
958
- self._toggle_sliders()
959
-
960
- def pane_hide(self):
961
- pass
962
-
963
- def pane_show(self):
964
- pass
965
-
966
- # @signal_listener("element_property_reload")
967
- def on_element_property_reload(self, *args):
968
- self._toggle_sliders()
969
- self.raster_lines = None
970
- self.travel_lines = None
971
- self.refresh_display()
972
-
973
- def accepts(self, node):
974
- return node.type in (
975
- "op raster",
976
- "op image",
977
- )
978
-
979
- def set_widgets(self, node):
980
- self.operation = node
981
- if self.operation is None or not self.accepts(node):
982
- self.Hide()
983
- return
984
- if self.operation.raster_preference_top is not None:
985
- self.slider_top.SetValue(self.operation.raster_preference_top + 1)
986
- if self.operation.raster_preference_left is not None:
987
- self.slider_left.SetValue(self.operation.raster_preference_left + 1)
988
- if self.operation.raster_preference_right is not None:
989
- self.slider_right.SetValue(self.operation.raster_preference_right + 1)
990
- if self.operation.raster_preference_bottom is not None:
991
- self.slider_bottom.SetValue(self.operation.raster_preference_bottom + 1)
992
- self.Show()
993
-
994
- def on_display_paint(self, event=None):
995
- try:
996
- wx.BufferedPaintDC(self.display_panel, self._Buffer)
997
- except RuntimeError:
998
- pass
999
-
1000
- def on_display_erase(self, event=None):
1001
- pass
1002
-
1003
- def set_buffer(self):
1004
- width, height = self.display_panel.Size
1005
- if width <= 0:
1006
- width = 1
1007
- if height <= 0:
1008
- height = 1
1009
- self._Buffer = wx.Bitmap(width, height)
1010
-
1011
- def on_size(self, event=None):
1012
- self.Layout()
1013
- self.set_buffer()
1014
- wx.CallAfter(self.refresh_in_ui)
1015
-
1016
- def refresh_display(self):
1017
- if not wx.IsMainThread():
1018
- wx.CallAfter(self.refresh_in_ui)
1019
- else:
1020
- self.refresh_in_ui()
1021
-
1022
- def calculate_raster_lines(self):
1023
- w, h = self._Buffer.Size
1024
-
1025
- right = True
1026
- top = True
1027
-
1028
- last = None
1029
- direction = self.operation.raster_direction
1030
- r_start = list()
1031
- r_end = list()
1032
- t_start = list()
1033
- t_end = list()
1034
- d_start = list()
1035
- d_end = list()
1036
- factor = 3
1037
- bidirectional = self.operation.bidirectional
1038
- dpi = self.operation.dpi
1039
- if dpi <= 1:
1040
- dpi = 1
1041
-
1042
- if direction == 0 or direction == 1 or direction == 4:
1043
- # Direction Line
1044
- d_start.append((w * 0.05, h * 0.05))
1045
- d_end.append((w * 0.05, h * 0.95))
1046
- if direction == 1:
1047
- # Bottom to Top
1048
- if self.operation.raster_preference_bottom > 0:
1049
- # if bottom preference is left
1050
- right = False
1051
- # Direction Arrow
1052
- d_start.append((w * 0.05, h * 0.05))
1053
- d_end.append((w * 0.05 + 4, h * 0.05 + 4))
1054
- d_start.append((w * 0.05, h * 0.05))
1055
- d_end.append((w * 0.05 - 4, h * 0.05 + 4))
1056
- start = int(h * 0.9)
1057
- end = int(h * 0.1)
1058
- step = -1000 / dpi * factor
1059
- else:
1060
- # Top to Bottom or Crosshatch
1061
- if self.operation.raster_preference_top > 0:
1062
- # if top preference is left
1063
- right = False
1064
- d_start.append((w * 0.05, h * 0.95))
1065
- d_end.append((w * 0.05 + 4, h * 0.95 - 4))
1066
- d_start.append((w * 0.05, h * 0.95))
1067
- d_end.append((w * 0.05 - 4, h * 0.95 - 4))
1068
- start = int(h * 0.1)
1069
- end = int(h * 0.9)
1070
- step = 1000 / dpi * factor
1071
- pos = start
1072
- while min(start, end) <= pos <= max(start, end):
1073
- # Primary Line Horizontal Raster
1074
- r_start.append((w * 0.1, pos))
1075
- r_end.append((w * 0.9, pos))
1076
-
1077
- # Arrow Segment
1078
- if last is not None:
1079
- # Travel Lines
1080
- t_start.append((last[0], last[1]))
1081
- t_end.append((w * 0.1 if right else w * 0.9, pos))
1082
-
1083
- r_start.append((w * 0.9 if right else w * 0.1, pos))
1084
- r_end.append((w * 0.9 - 2 if right else w * 0.1 + 2, pos - 2))
1085
- last = r_start[-1]
1086
- if bidirectional:
1087
- right = not right
1088
- pos += step
1089
- if direction == 2 or direction == 3 or direction == 4:
1090
- # Direction Line
1091
- d_start.append((w * 0.05, h * 0.05))
1092
- d_end.append((w * 0.95, h * 0.05))
1093
- if direction == 2:
1094
- # Right to Left
1095
- if self.operation.raster_preference_right > 0:
1096
- # if right preference is bottom
1097
- top = False
1098
- # Direction Arrow
1099
- d_start.append((w * 0.05, h * 0.05))
1100
- d_end.append((w * 0.05 + 4, h * 0.05 + 4))
1101
- d_start.append((w * 0.05, h * 0.05))
1102
- d_end.append((w * 0.05 + 4, h * 0.05 - 4))
1103
- start = int(w * 0.9)
1104
- end = int(w * 0.1)
1105
- step = -1000 / dpi * factor
1106
- else:
1107
- # Left to Right or Crosshatch
1108
- if self.operation.raster_preference_left > 0:
1109
- # if left preference is bottom
1110
- top = False
1111
- d_start.append((w * 0.95, h * 0.05))
1112
- d_end.append((w * 0.95 - 4, h * 0.05 + 4))
1113
- d_start.append((w * 0.95, h * 0.05))
1114
- d_end.append((w * 0.95 - 4, h * 0.05 - 4))
1115
- start = int(w * 0.1)
1116
- end = int(w * 0.9)
1117
- step = 1000 / dpi * factor
1118
- pos = start
1119
- while min(start, end) <= pos <= max(start, end):
1120
- # Primary Line Vertical Raster.
1121
- r_start.append((pos, h * 0.1))
1122
- r_end.append((pos, h * 0.9))
1123
-
1124
- # Arrow Segment
1125
- if last is not None:
1126
- # Travel Lines
1127
- t_start.append((last[0], last[1]))
1128
- t_end.append((pos, h * 0.1 if top else h * 0.9))
1129
- r_start.append((pos, h * 0.9 if top else h * 0.1))
1130
- r_end.append((pos - 2, (h * 0.9) - 2 if top else (h * 0.1) + 2))
1131
-
1132
- last = r_start[-1]
1133
- if bidirectional:
1134
- top = not top
1135
- pos += step
1136
- self.raster_lines = r_start, r_end
1137
- self.travel_lines = t_start, t_end
1138
- self.direction_lines = d_start, d_end
1139
-
1140
- def refresh_in_ui(self):
1141
- """Performs redrawing of the data in the UI thread."""
1142
- try:
1143
- visible = self.Shown
1144
- except RuntimeError:
1145
- # May have already been deleted....
1146
- return
1147
- dc = wx.MemoryDC()
1148
- dc.SelectObject(self._Buffer)
1149
- dc.SetBackground(wx.WHITE_BRUSH)
1150
- dc.Clear()
1151
- gc = wx.GraphicsContext.Create(dc)
1152
- if visible:
1153
- if self.raster_lines is None:
1154
- self.calculate_raster_lines()
1155
- if self.raster_lines is not None:
1156
- starts, ends = self.raster_lines
1157
- if len(starts):
1158
- gc.SetPen(self.raster_pen)
1159
- gc.StrokeLineSegments(starts, ends)
1160
- if self.travel_lines is not None:
1161
- starts, ends = self.travel_lines
1162
- if len(starts):
1163
- gc.SetPen(self.travel_pen)
1164
- gc.StrokeLineSegments(starts, ends)
1165
- if self.direction_lines is not None:
1166
- starts, ends = self.direction_lines
1167
- if len(starts):
1168
- gc.SetPen(self.direction_pen)
1169
- gc.StrokeLineSegments(starts, ends)
1170
- gc.Destroy()
1171
- dc.SelectObject(wx.NullBitmap)
1172
- del dc
1173
- self.display_panel.Refresh()
1174
- self.display_panel.Update()
1175
-
1176
- def _toggle_sliders(self):
1177
- if self.toggle_sliders:
1178
- direction = self.operation.raster_direction
1179
- self.slider_top.Enable(False)
1180
- self.slider_right.Enable(False)
1181
- self.slider_left.Enable(False)
1182
- self.slider_bottom.Enable(False)
1183
- if direction == 0:
1184
- self.slider_top.Enable(True)
1185
- elif direction == 1:
1186
- self.slider_bottom.Enable(True)
1187
- elif direction == 2:
1188
- self.slider_right.Enable(True)
1189
- elif direction == 3:
1190
- self.slider_left.Enable(True)
1191
- elif direction == 4:
1192
- self.slider_top.Enable(True)
1193
- self.slider_left.Enable(True)
1194
-
1195
- def on_slider_top(self, event=None): # wxGlade: OperationProperty.<event_handler>
1196
- if self.operation.raster_preference_top != self.slider_top.GetValue() - 1:
1197
- self.operation.raster_preference_top = self.slider_top.GetValue() - 1
1198
- self.context.elements.signal(
1199
- "element_property_reload", self.operation, "slider_top"
1200
- )
1201
-
1202
- def on_slider_left(self, event=None): # wxGlade: OperationProperty.<event_handler>
1203
- if self.operation.raster_preference_left != self.slider_left.GetValue() - 1:
1204
- self.operation.raster_preference_left = self.slider_left.GetValue() - 1
1205
- self.context.elements.signal(
1206
- "element_property_reload", self.operation, "slider_left"
1207
- )
1208
-
1209
- def on_slider_right(self, event=None): # wxGlade: OperationProperty.<event_handler>
1210
- if self.operation.raster_preference_right != self.slider_right.GetValue() - 1:
1211
- self.operation.raster_preference_right = self.slider_right.GetValue() - 1
1212
- self.context.elements.signal(
1213
- "element_property_reload", self.operation, "slider_right"
1214
- )
1215
-
1216
- def on_slider_bottom(
1217
- self, event=None
1218
- ): # wxGlade: OperationProperty.<event_handler>
1219
- if self.operation.raster_preference_bottom != self.slider_bottom.GetValue() - 1:
1220
- self.operation.raster_preference_bottom = self.slider_bottom.GetValue() - 1
1221
- self.context.elements.signal(
1222
- "element_property_reload", self.operation, "slider_bottom"
1223
- )
1224
-
1225
-
1226
- # end of class PanelStartPreference
1227
-
1228
-
1229
- class RasterSettingsPanel(wx.Panel):
1230
- def __init__(self, *args, context=None, node=None, **kwds):
1231
- # begin wxGlade: RasterSettingsPanel.__init__
1232
- kwds["style"] = kwds.get("style", 0)
1233
- wx.Panel.__init__(self, *args, **kwds)
1234
- self.context = context
1235
- self.operation = node
1236
-
1237
- raster_sizer = StaticBoxSizer(self, wx.ID_ANY, _("Raster:"), wx.VERTICAL)
1238
- param_sizer = wx.BoxSizer(wx.HORIZONTAL)
1239
- sizer_3 = StaticBoxSizer(self, wx.ID_ANY, _("DPI:"), wx.HORIZONTAL)
1240
- param_sizer.Add(sizer_3, 1, wx.EXPAND, 0)
1241
-
1242
- self.text_dpi = TextCtrl(
1243
- self,
1244
- wx.ID_ANY,
1245
- "500",
1246
- limited=True,
1247
- check="float",
1248
- style=wx.TE_PROCESS_ENTER,
1249
- )
1250
- self.text_dpi.set_error_level(1, 100000)
1251
- OPERATION_DPI_TOOLTIP = (
1252
- _(
1253
- 'In a raster engrave, the step size is the distance between raster lines in 1/1000" '
1254
- )
1255
- + _("and also the number of raster dots that get combined together.")
1256
- + "\n"
1257
- + _(
1258
- 'Because the laser dot is >> 1/1000" in diameter, at step 1 the raster lines overlap a lot, '
1259
- )
1260
- + _(
1261
- "and consequently you can raster with steps > 1 without leaving gaps between the lines."
1262
- )
1263
- + "\n"
1264
- + _(
1265
- "The step size before you get gaps will depend on your focus and the size of your laser dot."
1266
- )
1267
- + "\n"
1268
- + _(
1269
- "Step size > 1 reduces the laser energy delivered by the same factor, so you may need to increase "
1270
- )
1271
- + _(
1272
- "power equivalently with a higher front-panel power, a higher PPI or by rastering at a slower speed."
1273
- )
1274
- + "\n"
1275
- + _(
1276
- "Step size > 1 also turns the laser on and off fewer times, and combined with a slower speed"
1277
- )
1278
- + _("this can prevent your laser from stuttering.")
1279
- )
1280
- self.text_dpi.SetToolTip(OPERATION_DPI_TOOLTIP)
1281
- sizer_3.Add(self.text_dpi, 1, wx.EXPAND, 0)
1282
-
1283
- sizer_6 = StaticBoxSizer(self, wx.ID_ANY, _("Overscan:"), wx.HORIZONTAL)
1284
- param_sizer.Add(sizer_6, 1, wx.EXPAND, 0)
1285
-
1286
- raster_sizer.Add(param_sizer, 0, wx.EXPAND, 0)
1287
-
1288
- self.text_overscan = TextCtrl(
1289
- self,
1290
- wx.ID_ANY,
1291
- "1mm",
1292
- limited=True,
1293
- check="length",
1294
- style=wx.TE_PROCESS_ENTER,
1295
- )
1296
- self.text_overscan.SetToolTip(_("Overscan amount"))
1297
- sizer_6.Add(self.text_overscan, 1, wx.EXPAND, 0)
1298
-
1299
- sizer_4 = StaticBoxSizer(self, wx.ID_ANY, _("Direction:"), wx.HORIZONTAL)
1300
- raster_sizer.Add(sizer_4, 0, wx.EXPAND, 0)
1301
-
1302
- self.combo_raster_direction = wx.ComboBox(
1303
- self,
1304
- wx.ID_ANY,
1305
- choices=[
1306
- "Top To Bottom",
1307
- "Bottom To Top",
1308
- "Right To Left",
1309
- "Left To Right",
1310
- "Crosshatch",
1311
- ],
1312
- style=wx.CB_DROPDOWN | wx.CB_READONLY,
1313
- )
1314
- OPERATION_RASTERDIRECTION_TOOLTIP = (
1315
- _("Direction to perform a raster")
1316
- + "\n"
1317
- + _(
1318
- "Normally you would raster in an X-direction and select Top-to-Bottom (T2B) or Bottom-to-Top (B2T)."
1319
- )
1320
- + "\n"
1321
- + _(
1322
- "This is because rastering in the X-direction involve moving only the laser head which is relatively low mass."
1323
- )
1324
- + "\n"
1325
- + _(
1326
- "Rastering in the Y-direction (Left-to-Right or Right-to-Left) involves moving not only the laser head "
1327
- )
1328
- + _(
1329
- "but additionally the entire x-axis gantry assembly including the stepper motor, mirror and the gantry itself."
1330
- )
1331
- + "\n"
1332
- + _(
1333
- "This total mass is much greater, acceleration therefore needs to be much slower, "
1334
- )
1335
- + _(
1336
- "and allow for space at each end of the raster to reverse direction the speed has to be much slower."
1337
- )
1338
- )
1339
-
1340
- self.combo_raster_direction.SetToolTip(OPERATION_RASTERDIRECTION_TOOLTIP)
1341
- self.combo_raster_direction.SetSelection(0)
1342
- sizer_4.Add(self.combo_raster_direction, 1, wx.ALIGN_CENTER_VERTICAL, 0)
1343
-
1344
- self.radio_raster_swing = wx.RadioBox(
1345
- self,
1346
- wx.ID_ANY,
1347
- _("Directional Raster:"),
1348
- choices=[_("Unidirectional"), _("Bidirectional")],
1349
- majorDimension=1,
1350
- style=wx.RA_SPECIFY_ROWS,
1351
- )
1352
- OPERATION_RASTERSWING_TOOLTIP = (
1353
- _("Raster on forward and backswing or only forward swing?")
1354
- + "\n"
1355
- + _(
1356
- "Rastering only on forward swings will double the time required to complete the raster."
1357
- )
1358
- + "\n"
1359
- + _(
1360
- "It seems doubtful that there will be significant quality benefits from rastering in one direction."
1361
- )
1362
- )
1363
- self.radio_raster_swing.SetToolTip(OPERATION_RASTERSWING_TOOLTIP)
1364
- self.radio_raster_swing.SetSelection(0)
1365
- raster_sizer.Add(self.radio_raster_swing, 0, wx.EXPAND, 0)
1366
-
1367
- self.panel_start = PanelStartPreference(
1368
- self, wx.ID_ANY, context=context, node=node
1369
- )
1370
- raster_sizer.Add(self.panel_start, 0, wx.EXPAND, 0)
1371
-
1372
- self.SetSizer(raster_sizer)
1373
-
1374
- self.Layout()
1375
-
1376
- self.text_dpi.SetActionRoutine(self.on_text_dpi)
1377
- self.text_overscan.SetActionRoutine(self.on_text_overscan)
1378
-
1379
- self.Bind(
1380
- wx.EVT_COMBOBOX, self.on_combo_raster_direction, self.combo_raster_direction
1381
- )
1382
- self.Bind(wx.EVT_RADIOBOX, self.on_radio_directional, self.radio_raster_swing)
1383
-
1384
- def pane_hide(self):
1385
- self.panel_start.pane_hide()
1386
-
1387
- def pane_show(self):
1388
- self.panel_start.pane_show()
1389
-
1390
- def accepts(self, node):
1391
- return node.type in (
1392
- "op raster",
1393
- "op image",
1394
- )
1395
-
1396
- def set_widgets(self, node):
1397
- self.operation = node
1398
- if self.operation is None or not self.accepts(node):
1399
- self.Hide()
1400
- return
1401
- if self.operation.dpi is not None:
1402
- set_ctrl_value(self.text_dpi, str(self.operation.dpi))
1403
- if self.operation.overscan is not None:
1404
- set_ctrl_value(self.text_overscan, str(self.operation.overscan))
1405
- if self.operation.raster_direction is not None:
1406
- self.combo_raster_direction.SetSelection(self.operation.raster_direction)
1407
- if self.operation.bidirectional is not None:
1408
- self.radio_raster_swing.SetSelection(self.operation.bidirectional)
1409
- self.Show()
1410
-
1411
- def on_text_dpi(self):
1412
- try:
1413
- value = int(self.text_dpi.GetValue())
1414
- if self.operation.dpi != value:
1415
- self.operation.dpi = value
1416
- self.context.signal(
1417
- "element_property_reload", self.operation, "text_dpi"
1418
- )
1419
- except ValueError:
1420
- pass
1421
-
1422
- def on_text_overscan(self):
1423
- start_text = self.text_overscan.GetValue()
1424
- try:
1425
- v = Length(
1426
- self.text_overscan.GetValue(),
1427
- unitless=UNITS_PER_MM,
1428
- preferred_units="mm",
1429
- digits=4,
1430
- )
1431
- except ValueError:
1432
- return
1433
- # print ("Start overscan=%s - target=%s" % (start_text, str(v.preferred_length)))
1434
- value = v.preferred_length
1435
- if v._amount < 0.0000000001:
1436
- value = 0
1437
- if self.operation.overscan != value:
1438
- self.operation.overscan = value
1439
- self.context.elements.signal(
1440
- "element_property_reload", self.operation, "text_overscan"
1441
- )
1442
-
1443
- def on_combo_raster_direction(self, event=None):
1444
- if (
1445
- self.operation.raster_direction
1446
- != self.combo_raster_direction.GetSelection()
1447
- ):
1448
- self.operation.raster_direction = self.combo_raster_direction.GetSelection()
1449
- self.context.raster_direction = self.operation.raster_direction
1450
- self.context.elements.signal(
1451
- "element_property_reload", self.operation, "combo_raster"
1452
- )
1453
- event.Skip()
1454
-
1455
- def on_radio_directional(self, event=None):
1456
- self.operation.bidirectional = bool(self.radio_raster_swing.GetSelection())
1457
- self.context.elements.signal(
1458
- "element_property_reload", self.operation, "radio_direct"
1459
- )
1460
- event.Skip()
1461
-
1462
-
1463
- # end of class RasterSettingsPanel
1464
-
1465
-
1466
- class DwellSettingsPanel(wx.Panel):
1467
- def __init__(self, *args, context=None, node=None, **kwds):
1468
- # begin wxGlade: PassesPanel.__init__
1469
- kwds["style"] = kwds.get("style", 0)
1470
- wx.Panel.__init__(self, *args, **kwds)
1471
- self.context = context
1472
- self.operation = node
1473
-
1474
- sizer_passes = StaticBoxSizer(
1475
- self, wx.ID_ANY, _("Dwell Time: (ms)"), wx.HORIZONTAL
1476
- )
1477
-
1478
- self.text_dwelltime = TextCtrl(
1479
- self,
1480
- wx.ID_ANY,
1481
- "1.0",
1482
- limited=True,
1483
- style=wx.TE_PROCESS_ENTER,
1484
- check="float",
1485
- )
1486
- self.text_dwelltime.SetToolTip(
1487
- _("Dwell time (ms) at each location in the sequence")
1488
- )
1489
- sizer_passes.Add(self.text_dwelltime, 1, wx.EXPAND, 0)
1490
-
1491
- self.SetSizer(sizer_passes)
1492
-
1493
- self.Layout()
1494
-
1495
- self.text_dwelltime.SetActionRoutine(self.on_text_dwelltime)
1496
- # end wxGlade
1497
-
1498
- def pane_hide(self):
1499
- pass
1500
-
1501
- def pane_show(self):
1502
- pass
1503
-
1504
- def accepts(self, node):
1505
- return node.type in ("op dots",)
1506
-
1507
- def set_widgets(self, node):
1508
- self.operation = node
1509
- if self.operation is None or not self.accepts(node):
1510
- self.Hide()
1511
- return
1512
- set_ctrl_value(self.text_dwelltime, str(self.operation.dwell_time))
1513
- self.Show()
1514
-
1515
- def on_text_dwelltime(self):
1516
- try:
1517
- value = float(self.text_dwelltime.GetValue())
1518
- if self.operation.dwell_time != value:
1519
- self.operation.dwell_time = value
1520
- self.context.elements.signal(
1521
- "element_property_reload", self.operation, "text_dwell"
1522
- )
1523
- except ValueError:
1524
- pass
1525
-
1526
-
1527
- # end of class PassesPanel
1528
-
1529
-
1530
- class ParameterPanel(ScrolledPanel):
1531
- name = _("Properties")
1532
- priority = -1
1533
-
1534
- def __init__(self, *args, context=None, node=None, **kwds):
1535
- # begin wxGlade: ParameterPanel.__init__
1536
- kwds["style"] = kwds.get("style", 0)
1537
- ScrolledPanel.__init__(self, *args, **kwds)
1538
- self.context = context
1539
- self.operation = node
1540
- self.panels = []
1541
-
1542
- param_sizer = wx.BoxSizer(wx.VERTICAL)
1543
-
1544
- self.id_panel = IdPanel(
1545
- self,
1546
- wx.ID_ANY,
1547
- context=context,
1548
- node=node,
1549
- showid=True,
1550
- )
1551
- param_sizer.Add(self.id_panel, 0, wx.EXPAND, 0)
1552
- self.panels.append(self.id_panel)
1553
-
1554
- self.layer_panel = LayerSettingPanel(
1555
- self, wx.ID_ANY, context=context, node=node
1556
- )
1557
- param_sizer.Add(self.layer_panel, 0, wx.EXPAND, 0)
1558
- self.panels.append(self.layer_panel)
1559
-
1560
- self.speedppi_panel = SpeedPpiPanel(self, wx.ID_ANY, context=context, node=node)
1561
- param_sizer.Add(self.speedppi_panel, 0, wx.EXPAND, 0)
1562
- self.panels.append(self.speedppi_panel)
1563
-
1564
- self.passes_panel = PassesPanel(self, wx.ID_ANY, context=context, node=node)
1565
- param_sizer.Add(self.passes_panel, 0, wx.EXPAND, 0)
1566
- self.panels.append(self.passes_panel)
1567
-
1568
- self.raster_panel = RasterSettingsPanel(
1569
- self, wx.ID_ANY, context=context, node=node
1570
- )
1571
- param_sizer.Add(self.raster_panel, 0, wx.EXPAND, 0)
1572
- self.panels.append(self.raster_panel)
1573
-
1574
- self.dwell_panel = DwellSettingsPanel(
1575
- self, wx.ID_ANY, context=context, node=node
1576
- )
1577
- param_sizer.Add(self.dwell_panel, 0, wx.EXPAND, 0)
1578
- self.panels.append(self.dwell_panel)
1579
-
1580
- self.info_panel = InfoPanel(self, wx.ID_ANY, context=context, node=node)
1581
- param_sizer.Add(self.info_panel, 0, wx.EXPAND, 0)
1582
- self.panels.append(self.info_panel)
1583
-
1584
- self.SetSizer(param_sizer)
1585
-
1586
- self.Layout()
1587
- # end wxGlade
1588
-
1589
- @signal_listener("power_percent")
1590
- @signal_listener("speed_min")
1591
- @lookup_listener("service/device/active")
1592
- def on_device_update(self, *args):
1593
- self.speedppi_panel.on_device_update()
1594
-
1595
- @signal_listener("element_property_reload")
1596
- def on_element_property_reload(self, origin=None, *args):
1597
- # Is this something I should care about?
1598
- # element_property_reload provides a list of nodes that are affected
1599
- # if self.operation isn't one of them, then we just let it slip
1600
- for_me = False
1601
- if len(args) > 0:
1602
- element = args[0]
1603
- if isinstance(element, (tuple, list)):
1604
- for node in element:
1605
- if node == self.operation:
1606
- for_me = True
1607
- break
1608
- elif self.operation == element:
1609
- for_me = True
1610
- if not for_me:
1611
- return
1612
-
1613
- # if origin is None:
1614
- # print ("EPR called with no origin")
1615
- # else:
1616
- # print ("EPR called from:", args)
1617
- try:
1618
- self.raster_panel.panel_start.on_element_property_reload(*args)
1619
- except AttributeError:
1620
- pass
1621
- self.set_widgets(self.operation)
1622
- self.Layout()
1623
-
1624
- def set_widgets(self, node):
1625
- self.operation = node
1626
- for panel in self.panels:
1627
- panel.set_widgets(node)
1628
-
1629
- def pane_hide(self):
1630
- for panel in self.panels:
1631
- panel.pane_hide()
1632
-
1633
- def pane_show(self):
1634
- for panel in self.panels:
1635
- panel.pane_show()
1636
-
1637
-
1638
- # end of class ParameterPanel
1
+ import wx
2
+
3
+ from meerk40t.gui.wxutils import (
4
+ ScrolledPanel,
5
+ StaticBoxSizer,
6
+ TextCtrl,
7
+ dip_size,
8
+ set_ctrl_value,
9
+ wxButton,
10
+ wxCheckBox,
11
+ wxComboBox,
12
+ wxRadioBox,
13
+ wxStaticBitmap,
14
+ wxStaticText,
15
+ )
16
+ from meerk40t.constants import (
17
+ RASTER_T2B,
18
+ RASTER_B2T,
19
+ RASTER_R2L,
20
+ RASTER_L2R,
21
+ RASTER_HATCH,
22
+ RASTER_GREEDY_H,
23
+ RASTER_GREEDY_V,
24
+ RASTER_CROSSOVER,
25
+ RASTER_SPIRAL,
26
+ )
27
+ from meerk40t.kernel import lookup_listener, signal_listener
28
+ from meerk40t.gui.icons import icon_letter_h
29
+ from ...core.units import UNITS_PER_MM, Length
30
+ from ...svgelements import Color
31
+ from ..laserrender import swizzlecolor
32
+ from .attributes import IdPanel
33
+
34
+ _ = wx.GetTranslation
35
+
36
+ # OPERATION_TYPE_TOOLTIP = _(
37
+ # """Operation Type
38
+
39
+ # Cut & Engrave are vector operations, Raster and Image are raster operations.
40
+
41
+ # Cut and Engrave operations are essentially the same except that for a Cut operation with Cut Outer Paths last, only closed Paths in Cut operations are considered as being Outer-most."""
42
+ # )
43
+
44
+
45
+ def validate_raster_settings(node):
46
+ # Make sure things are properly set...
47
+ if node.raster_direction in (RASTER_T2B, ):
48
+ node.raster_preference_top = True
49
+ elif node.raster_direction in (RASTER_B2T, ):
50
+ node.raster_preference_top = False
51
+ elif node.raster_direction in (RASTER_R2L, ):
52
+ node.raster_preference_left = True
53
+ elif node.raster_direction in (RASTER_L2R, ):
54
+ node.raster_preference_left = False
55
+ elif node.raster_direction in (RASTER_HATCH, RASTER_CROSSOVER):
56
+ node.raster_preference_top = True
57
+ node.raster_preference_left = True
58
+ if node.raster_direction in (RASTER_CROSSOVER, RASTER_GREEDY_H, RASTER_GREEDY_V, RASTER_SPIRAL):
59
+ node.bidirectional = True
60
+
61
+ class LayerSettingPanel(wx.Panel):
62
+ def __init__(self, *args, context=None, node=None, **kwds):
63
+ # begin wxGlade: LayerSettingPanel.__init__
64
+ kwds["style"] = kwds.get("style", 0)
65
+ wx.Panel.__init__(self, *args, **kwds)
66
+ self.context = context
67
+ self.context.themes.set_window_colors(self)
68
+ self.operation = node
69
+
70
+ layer_sizer = StaticBoxSizer(self, wx.ID_ANY, _("Layer:"), wx.HORIZONTAL)
71
+
72
+ self.button_layer_color = wxButton(self, wx.ID_ANY, "")
73
+ self.button_layer_color.SetBackgroundColour(wx.Colour(0, 0, 0))
74
+ COLOR_TOOLTIP = _(
75
+ "Change/View color of this layer. When Meerk40t classifies elements to operations,"
76
+ ) + _("this exact color is used to match elements to this operation.")
77
+
78
+ self.button_layer_color.SetToolTip(COLOR_TOOLTIP)
79
+ layer_sizer.Add(self.button_layer_color, 0, wx.EXPAND, 0)
80
+ h_classify_sizer = StaticBoxSizer(
81
+ self, wx.ID_ANY, _("Restrict classification"), wx.HORIZONTAL
82
+ )
83
+ h_property_sizer = StaticBoxSizer(
84
+ self, wx.ID_ANY, _("Properties"), wx.HORIZONTAL
85
+ )
86
+ rastertooltip = ""
87
+ if self.operation.type == "op raster":
88
+ rastertooltip = (
89
+ "\n"
90
+ + _("If neither stroke nor fill are checked, then the raster op")
91
+ + "\n"
92
+ + _("will classify all elements that have a fill")
93
+ + "\n"
94
+ + _("or stroke that are either black or white.")
95
+ )
96
+ try:
97
+ self.has_stroke = self.operation.has_color_attribute("stroke")
98
+ self.checkbox_stroke = wxCheckBox(self, wx.ID_ANY, _("Stroke"))
99
+ self.checkbox_stroke.SetToolTip(
100
+ _("Look at the stroke color to restrict classification.")
101
+ + rastertooltip
102
+ )
103
+ self.checkbox_stroke.SetValue(1 if self.has_stroke else 0)
104
+ h_classify_sizer.Add(self.checkbox_stroke, 1, 0, 0)
105
+ self.Bind(wx.EVT_CHECKBOX, self.on_check_stroke, self.checkbox_stroke)
106
+ except AttributeError:
107
+ self.has_stroke = None
108
+
109
+ try:
110
+ self.has_fill = self.operation.has_color_attribute("fill")
111
+ self.checkbox_fill = wxCheckBox(self, wx.ID_ANY, _("Fill"))
112
+ self.checkbox_fill.SetToolTip(
113
+ _("Look at the fill color to restrict classification.") + rastertooltip
114
+ )
115
+ self.checkbox_fill.SetValue(1 if self.has_fill else 0)
116
+ h_classify_sizer.Add(self.checkbox_fill, 1, 0, 0)
117
+ self.Bind(wx.EVT_CHECKBOX, self.on_check_fill, self.checkbox_fill)
118
+ except AttributeError:
119
+ self.has_fill = None
120
+
121
+ self.checkbox_stop = wxCheckBox(self, wx.ID_ANY, _("Stop"))
122
+ self.checkbox_stop.SetToolTip(
123
+ _("If active, then this op will prevent further classification")
124
+ + "\n"
125
+ + _("from other ops if it could classify an element by itself.")
126
+ )
127
+ h_classify_sizer.Add(self.checkbox_stop, 1, 0, 0)
128
+ self.Bind(wx.EVT_CHECKBOX, self.on_check_stop, self.checkbox_stop)
129
+ self.has_stop = hasattr(self.operation, "stopop")
130
+ if not self.has_stop:
131
+ self.checkbox_stop.SetValue(False)
132
+ self.checkbox_stop.Enable(False)
133
+ else:
134
+ self.checkbox_stop.SetValue(self.operation.stopop)
135
+ self.checkbox_stop.Enable(True)
136
+
137
+ if (
138
+ self.has_fill is not None
139
+ or self.has_stroke is not None
140
+ or self.has_stop is not None
141
+ ):
142
+ layer_sizer.Add(h_classify_sizer, 1, wx.EXPAND, 0)
143
+
144
+ # self.combo_type = wxComboBox(
145
+ # self,
146
+ # wx.ID_ANY,
147
+ # choices=["Engrave", "Cut", "Raster", "Image", "Hatch", "Dots"],
148
+ # style=wx.CB_DROPDOWN,
149
+ # )
150
+ # self.combo_type.SetToolTip(OPERATION_TYPE_TOOLTIP)
151
+ # self.combo_type.SetSelection(0)
152
+ # layer_sizer.Add(self.combo_type, 1, 0, 0)
153
+
154
+ self.checkbox_output = wxCheckBox(self, wx.ID_ANY, _("Enable"))
155
+ self.checkbox_output.SetToolTip(
156
+ _("Enable this operation for inclusion in Execute Job.")
157
+ )
158
+ self.checkbox_output.SetValue(1)
159
+ h_property_sizer.Add(self.checkbox_output, 1, 0, 0)
160
+
161
+ self.checkbox_visible = wxCheckBox(self, wx.ID_ANY, _("Visible"))
162
+ self.checkbox_visible.SetToolTip(
163
+ _("Hide all contained elements on scene if not set.")
164
+ )
165
+ self.checkbox_visible.SetValue(1)
166
+ self.checkbox_visible.Enable(False)
167
+ h_property_sizer.Add(self.checkbox_visible, 1, 0, 0)
168
+
169
+ self.checkbox_default = wxCheckBox(self, wx.ID_ANY, _("Default"))
170
+ OPERATION_DEFAULT_TOOLTIP = (
171
+ _(
172
+ "When classifying elements, Default operations gain all appropriate elements "
173
+ )
174
+ + _("not matched to an existing operation of the same colour, ")
175
+ + _("rather than a new operation of that color being created.")
176
+ )
177
+
178
+ self.checkbox_default.SetToolTip(OPERATION_DEFAULT_TOOLTIP)
179
+ self.checkbox_default.SetValue(1)
180
+ h_property_sizer.Add(self.checkbox_default, 1, 0, 0)
181
+
182
+ layer_sizer.Add(h_property_sizer, 1, wx.EXPAND, 0)
183
+
184
+ self.SetSizer(layer_sizer)
185
+
186
+ self.Layout()
187
+
188
+ self.Bind(wx.EVT_BUTTON, self.on_button_layer, self.button_layer_color)
189
+ # self.Bind(wx.EVT_COMBOBOX, self.on_combo_operation, self.combo_type)
190
+ self.Bind(wx.EVT_CHECKBOX, self.on_check_output, self.checkbox_output)
191
+ self.Bind(wx.EVT_CHECKBOX, self.on_check_visible, self.checkbox_visible)
192
+ self.Bind(wx.EVT_CHECKBOX, self.on_check_default, self.checkbox_default)
193
+ # end wxGlade
194
+
195
+ def pane_hide(self):
196
+ pass
197
+
198
+ def pane_show(self):
199
+ pass
200
+
201
+ def accepts(self, node):
202
+ return node.type in (
203
+ "op cut",
204
+ "op engrave",
205
+ "op raster",
206
+ "op image",
207
+ "op dots",
208
+ )
209
+
210
+ def set_widgets(self, node):
211
+ self.operation = node
212
+ if self.operation is None or not self.accepts(node):
213
+ self.Hide()
214
+ return
215
+ self.button_layer_color.SetBackgroundColour(
216
+ wx.Colour(swizzlecolor(self.operation.color))
217
+ )
218
+ if self.operation.output is not None:
219
+ self.checkbox_output.SetValue(self.operation.output)
220
+ flag_set = True
221
+ flag_enabled = False
222
+ if self.operation.output is not None:
223
+ if not self.operation.output:
224
+ flag_enabled = True
225
+ flag_set = self.operation.is_visible
226
+ self.checkbox_visible.SetValue(flag_set)
227
+ self.checkbox_visible.Enable(flag_enabled)
228
+ if self.operation.default is not None:
229
+ self.checkbox_default.SetValue(self.operation.default)
230
+ try:
231
+ if self.has_fill:
232
+ self.checkbox_fill.SetValue(
233
+ 1 if self.operation.has_color_attribute("fill") else 0
234
+ )
235
+ except AttributeError:
236
+ pass
237
+ try:
238
+ if self.has_stroke:
239
+ self.checkbox_stroke.SetValue(
240
+ 1 if self.operation.has_color_attribute("stroke") else 0
241
+ )
242
+ except AttributeError:
243
+ pass
244
+ self.has_stop = hasattr(self.operation, "stopop")
245
+ if not self.has_stop:
246
+ self.checkbox_stop.SetValue(False)
247
+ self.checkbox_stop.Enable(False)
248
+ else:
249
+ self.checkbox_stop.SetValue(self.operation.stopop)
250
+ self.checkbox_stop.Enable(True)
251
+
252
+ self.Layout()
253
+ self.Show()
254
+
255
+ def on_button_layer(self, event=None): # wxGlade: OperationProperty.<event_handler>
256
+ data = wx.ColourData()
257
+ if self.operation.color is not None and self.operation.color != "none":
258
+ data.SetColour(wx.Colour(swizzlecolor(self.operation.color)))
259
+ dlg = wx.ColourDialog(self, data)
260
+ if dlg.ShowModal() == wx.ID_OK:
261
+ data = dlg.GetColourData()
262
+ color = data.GetColour()
263
+ rgb = color.GetRGB()
264
+ color = swizzlecolor(rgb)
265
+ self.operation.color = Color(color, 1.0)
266
+ try:
267
+ self.button_layer_color.SetBackgroundColour(
268
+ wx.Colour(swizzlecolor(self.operation.color))
269
+ )
270
+ except RuntimeError:
271
+ return
272
+ # Ask the user if she/he wants to assign the color of the contained objects
273
+ try:
274
+ candidate_stroke = bool(self.checkbox_stroke.GetValue())
275
+ except AttributeError:
276
+ candidate_stroke = False
277
+ try:
278
+ candidate_fill = bool(self.checkbox_fill.GetValue())
279
+ except AttributeError:
280
+ candidate_fill = False
281
+ if (
282
+ self.operation.type in ("op engrave", "op cut")
283
+ and len(self.operation.children) > 0
284
+ and (candidate_fill or candidate_stroke)
285
+ ):
286
+ changed = []
287
+ for e in self.operation.children:
288
+ if e.type.startswith("effect "):
289
+ e.stroke = self.operation.color
290
+ changed.append(e)
291
+ dlg = wx.MessageDialog(
292
+ None,
293
+ message=_(
294
+ "Do you want to change the color of the contained elements too?"
295
+ ),
296
+ caption=_("Update Colors?"),
297
+ style=wx.YES_NO | wx.ICON_QUESTION,
298
+ )
299
+ response = dlg.ShowModal()
300
+ dlg.Destroy()
301
+ if response == wx.ID_YES:
302
+ for refnode in self.operation.children:
303
+ if refnode in changed:
304
+ continue
305
+ if hasattr(refnode, "node"):
306
+ cnode = refnode.node
307
+ else:
308
+ cnode = refnode
309
+ add_to_change = False
310
+ if candidate_stroke and hasattr(cnode, "stroke"):
311
+ cnode.stroke = self.operation.color
312
+ add_to_change = True
313
+
314
+ if candidate_fill and hasattr(cnode, "fill"):
315
+ cnode.fill = self.operation.color
316
+ add_to_change = True
317
+
318
+ if add_to_change:
319
+ changed.append(cnode)
320
+ if len(changed) > 0:
321
+ self.context.elements.signal("element_property_update", changed)
322
+ self.context.elements.signal("refresh_scene", "Scene")
323
+
324
+ self.context.elements.signal(
325
+ "element_property_reload", self.operation, "button_layer"
326
+ )
327
+ self.context.elements.signal("updateop_tree")
328
+
329
+ def on_check_output(self, event=None): # wxGlade: OperationProperty.<event_handler>
330
+ if self.operation.output != bool(self.checkbox_output.GetValue()):
331
+ self.operation.output = bool(self.checkbox_output.GetValue())
332
+ self.context.elements.signal(
333
+ "element_property_reload", self.operation, "check_output"
334
+ )
335
+ self.context.elements.signal("updateop_tree")
336
+ self.checkbox_visible.Enable(not bool(self.checkbox_output.GetValue()))
337
+
338
+ def on_check_visible(self, event=None):
339
+ if self.operation.is_visible != bool(self.checkbox_visible.GetValue()):
340
+ self.operation.is_visible = bool(self.checkbox_visible.GetValue())
341
+ self.context.elements.validate_selected_area()
342
+ self.context.elements.signal("element_property_update", self.operation)
343
+ self.context.elements.signal("refresh_scene", "Scene")
344
+
345
+ def on_check_default(self, event=None):
346
+ if self.operation.default != bool(self.checkbox_default.GetValue()):
347
+ self.operation.default = bool(self.checkbox_default.GetValue())
348
+ self.context.elements.signal(
349
+ "element_property_reload", self.operation, "check_default"
350
+ )
351
+
352
+ def on_check_fill(self, event=None):
353
+ if self.checkbox_fill.GetValue():
354
+ self.operation.add_color_attribute("fill")
355
+ else:
356
+ self.operation.remove_color_attribute("fill")
357
+ self.context.elements.signal(
358
+ "element_property_reload", self.operation, "check_fill"
359
+ )
360
+ event.Skip()
361
+
362
+ def on_check_stroke(self, event=None):
363
+ if self.checkbox_stroke.GetValue():
364
+ self.operation.add_color_attribute("stroke")
365
+ else:
366
+ self.operation.remove_color_attribute("stroke")
367
+ self.context.elements.signal(
368
+ "element_property_reload", self.operation, "check_stroke"
369
+ )
370
+ event.Skip()
371
+
372
+ def on_check_stop(self, event=None):
373
+ if self.checkbox_stop.GetValue():
374
+ self.operation.stopop = True
375
+ else:
376
+ self.operation.stopop = False
377
+ self.context.elements.signal(
378
+ "element_property_reload", self.operation, "check_stopop"
379
+ )
380
+ event.Skip()
381
+
382
+
383
+ # end of class LayerSettingPanel
384
+
385
+
386
+ class SpeedPpiPanel(wx.Panel):
387
+ def __init__(self, *args, context=None, node=None, **kwds):
388
+ # begin wxGlade: SpeedPpiPanel.__init__
389
+ kwds["style"] = kwds.get("style", 0)
390
+ wx.Panel.__init__(self, *args, **kwds)
391
+ self.context = context
392
+ self.context.themes.set_window_colors(self)
393
+ self.operation = node
394
+
395
+ self.context.device.setting(bool, "use_percent_for_power_display", False)
396
+ self.use_percent = self.context.device.use_percent_for_power_display
397
+ self.context.device.setting(bool, "use_mm_min_for_speed_display", False)
398
+ self.use_mm_min = self.context.device.use_mm_min_for_speed_display
399
+
400
+ speed_power_sizer = wx.BoxSizer(wx.HORIZONTAL)
401
+ if self.use_mm_min:
402
+ speed_fact = 60
403
+ else:
404
+ speed_fact = 1
405
+
406
+ speed_sizer = StaticBoxSizer(self, wx.ID_ANY, _("Speed"), wx.HORIZONTAL)
407
+ speed_power_sizer.Add(speed_sizer, 1, wx.EXPAND, 0)
408
+
409
+ self.text_speed = TextCtrl(
410
+ self,
411
+ wx.ID_ANY,
412
+ f"{20 * speed_fact:.0f}",
413
+ limited=True,
414
+ check="float",
415
+ style=wx.TE_PROCESS_ENTER,
416
+ nonzero=True,
417
+ )
418
+ self.trailer_speed = wxStaticText(self, id=wx.ID_ANY)
419
+ speed_sizer.Add(self.text_speed, 1, wx.EXPAND, 0)
420
+ speed_sizer.Add(self.trailer_speed, 0, wx.ALIGN_CENTER_VERTICAL, 0)
421
+
422
+ self.power_sizer = StaticBoxSizer(
423
+ self, wx.ID_ANY, _("Power (ppi)"), wx.HORIZONTAL
424
+ )
425
+ speed_power_sizer.Add(self.power_sizer, 1, wx.EXPAND, 0)
426
+
427
+ self.text_power = TextCtrl(
428
+ self,
429
+ wx.ID_ANY,
430
+ "1000.0",
431
+ limited=True,
432
+ check="float",
433
+ style=wx.TE_PROCESS_ENTER,
434
+ )
435
+ self.trailer_power = wxStaticText(self, id=wx.ID_ANY, label=_("/1000"))
436
+ self.power_sizer.Add(self.text_power, 1, wx.EXPAND, 0)
437
+ self.power_sizer.Add(self.trailer_power, 0, wx.ALIGN_CENTER_VERTICAL, 0)
438
+
439
+ self.update_power_speed_properties()
440
+
441
+ freq = self.context.device.lookup("frequency")
442
+ if freq:
443
+ frequency_sizer = StaticBoxSizer(
444
+ self, wx.ID_ANY, _("Frequency (kHz)"), wx.HORIZONTAL
445
+ )
446
+ speed_power_sizer.Add(frequency_sizer, 1, wx.EXPAND, 0)
447
+
448
+ self.text_frequency = TextCtrl(
449
+ self,
450
+ wx.ID_ANY,
451
+ "20.0",
452
+ limited=True,
453
+ check="float",
454
+ style=wx.TE_PROCESS_ENTER,
455
+ )
456
+ OPERATION_FREQUENCY_TOOLTIP = (
457
+ _("Laser frequency in kHz.")
458
+ + "\n"
459
+ + _("For lasers with frequencies that can be set.")
460
+ )
461
+ self.text_frequency.SetToolTip(OPERATION_FREQUENCY_TOOLTIP)
462
+ self.text_frequency.set_warn_level(*freq)
463
+ frequency_sizer.Add(self.text_frequency, 1, wx.EXPAND, 0)
464
+ else:
465
+ self.text_frequency = None
466
+
467
+ self.SetSizer(speed_power_sizer)
468
+
469
+ self.Layout()
470
+
471
+ self.text_speed.SetActionRoutine(self.on_text_speed)
472
+ self.text_power.SetActionRoutine(self.on_text_power)
473
+
474
+ if self.text_frequency:
475
+ self.text_frequency.SetActionRoutine(self.on_text_frequency)
476
+
477
+ # end wxGlade
478
+
479
+ def pane_hide(self):
480
+ pass
481
+
482
+ def pane_show(self):
483
+ self.update_power_speed_properties()
484
+
485
+ def on_device_update(self):
486
+ try:
487
+ self.update_power_speed_properties()
488
+ except RuntimeError:
489
+ # Pane was already destroyed
490
+ return
491
+ self.set_widgets(self.operation)
492
+
493
+ def update_power_speed_properties(self):
494
+ speed_min = None
495
+ speed_max = None
496
+ power_min = None
497
+ power_max = None
498
+
499
+ op = self.operation.type
500
+ if op.startswith("op "): # Should, shouldn't it?
501
+ op = op[3:]
502
+ else:
503
+ op = ""
504
+ if op != "":
505
+ label = "dangerlevel_op_" + op
506
+ warning = [False, 0, False, 0, False, 0, False, 0]
507
+ if hasattr(self.context.device, label):
508
+ dummy = getattr(self.context.device, label)
509
+ if isinstance(dummy, (tuple, list)) and len(dummy) == len(warning):
510
+ warning = dummy
511
+ if warning[0]:
512
+ power_min = warning[1]
513
+ if warning[2]:
514
+ power_max = warning[3]
515
+ if warning[4]:
516
+ speed_min = warning[5]
517
+ if warning[6]:
518
+ speed_max = warning[7]
519
+ self.use_mm_min = self.context.device.use_mm_min_for_speed_display
520
+ if self.use_mm_min:
521
+ if speed_min is not None:
522
+ speed_min *= 60
523
+ if speed_max is not None:
524
+ speed_max *= 60
525
+ speed_unit = "mm/min"
526
+ else:
527
+ speed_unit = "mm/s"
528
+ self.trailer_speed.SetLabel(speed_unit)
529
+ OPERATION_SPEED_TOOLTIP = (
530
+ _("Speed at which the head moves in {unit}.").format(unit=speed_unit)
531
+ + "\n"
532
+ + _(
533
+ "For Cut/Engrave vector operations, this is the speed of the head regardless of direction i.e. the separate x/y speeds vary according to the direction."
534
+ )
535
+ + "\n"
536
+ + _(
537
+ "For Raster/Image operations, this is the speed of the head as it sweeps backwards and forwards."
538
+ )
539
+ )
540
+ self.text_speed.SetToolTip(OPERATION_SPEED_TOOLTIP)
541
+ self.text_speed.set_error_level(0, None)
542
+ self.text_speed.set_warn_level(speed_min, speed_max)
543
+
544
+ self.use_percent = self.context.device.use_percent_for_power_display
545
+ if self.use_percent:
546
+ self.trailer_power.SetLabel("%")
547
+ self.text_power._check = "percent"
548
+ if power_min is not None:
549
+ power_min /= 10
550
+ if power_max is not None:
551
+ power_max /= 10
552
+ self.text_power.set_range(0, 100)
553
+ self.text_power.set_warn_level(power_min, power_max)
554
+ OPERATION_POWER_TOOLTIP = _(
555
+ "% of maximum power - This is a percentage of the maximum power of the laser."
556
+ )
557
+ self.power_sizer.SetLabel(_("Power"))
558
+ else:
559
+ self.trailer_power.SetLabel(_("/1000"))
560
+ self.text_power._check = "float"
561
+ self.text_power.set_range(0, 1000)
562
+ self.text_power.set_warn_level(power_min, power_max)
563
+ OPERATION_POWER_TOOLTIP = _(
564
+ _("Pulses Per Inch - This is software created laser power control.")
565
+ + "\n"
566
+ + _("1000 is always on, 500 is half power (fire every other step).")
567
+ + "\n"
568
+ + _(
569
+ 'Values of 100 or have pulses > 1/10" and are generally used only for dotted or perforated lines.'
570
+ )
571
+ )
572
+ self.power_sizer.SetLabel(_("Power (ppi)"))
573
+ self.text_power.SetToolTip(OPERATION_POWER_TOOLTIP)
574
+
575
+ def accepts(self, node):
576
+ return node.type in (
577
+ "op cut",
578
+ "op engrave",
579
+ "op raster",
580
+ "op image",
581
+ "op dots",
582
+ )
583
+
584
+ def set_widgets(self, node):
585
+ self.operation = node
586
+ if self.operation is None or not self.accepts(node):
587
+ self.Hide()
588
+ return
589
+ if self.operation.speed is not None:
590
+ if self.use_mm_min:
591
+ set_ctrl_value(self.text_speed, str(self.operation.speed * 60))
592
+ else:
593
+ set_ctrl_value(self.text_speed, str(self.operation.speed))
594
+ if self.operation.power is not None:
595
+ if self.use_percent:
596
+ set_ctrl_value(self.text_power, f"{self.operation.power / 10.0:.0f}")
597
+ else:
598
+ set_ctrl_value(self.text_power, f"{self.operation.power:.0f}")
599
+ self.update_power_label()
600
+ if self.operation.frequency is not None and self.text_frequency:
601
+ set_ctrl_value(self.text_frequency, str(self.operation.frequency))
602
+ self.Show()
603
+
604
+ def on_text_speed(self): # wxGlade: OperationProperty.<event_handler>
605
+ try:
606
+ value = float(self.text_speed.GetValue())
607
+ if self.use_mm_min:
608
+ value /= 60
609
+ if self.operation.speed != value:
610
+ self.operation.speed = value
611
+ self.context.elements.signal(
612
+ "element_property_reload", self.operation, "text_speed"
613
+ )
614
+ self.context.elements.signal("updateop_tree")
615
+ except ValueError:
616
+ pass
617
+
618
+ def on_text_frequency(self):
619
+ try:
620
+ value = float(self.text_frequency.GetValue())
621
+ if self.operation.frequency != value:
622
+ self.operation.frequency = value
623
+ self.context.elements.signal(
624
+ "element_property_reload", self.operation, "text_frquency"
625
+ )
626
+ except ValueError:
627
+ pass
628
+
629
+ def update_power_label(self):
630
+ # if self.operation.power <= 100:
631
+ # self.power_label.SetLabel(_("Power (ppi):") + "⚠️")
632
+ # else:
633
+ # self.power_label.SetLabel(_("Power (ppi):"))
634
+ if self.use_percent:
635
+ return
636
+ try:
637
+ value = float(self.text_power.GetValue())
638
+ self.power_sizer.SetLabel(_("Power (ppi)") + f" ({value/10:.1f}%)")
639
+ except ValueError:
640
+ return
641
+
642
+ def on_text_power(self):
643
+ try:
644
+ value = float(self.text_power.GetValue())
645
+ if self.use_percent:
646
+ value *= 10
647
+ if self.operation.power != value:
648
+ self.operation.power = value
649
+ self.update_power_label()
650
+ self.context.elements.signal(
651
+ "element_property_reload", self.operation, "text_power"
652
+ )
653
+ self.context.elements.signal("updateop_tree")
654
+ except ValueError:
655
+ return
656
+
657
+
658
+ # end of class SpeedPpiPanel
659
+
660
+
661
+ class PassesPanel(wx.Panel):
662
+ def __init__(self, *args, context=None, node=None, **kwds):
663
+ # begin wxGlade: PassesPanel.__init__
664
+ kwds["style"] = kwds.get("style", 0)
665
+ wx.Panel.__init__(self, *args, **kwds)
666
+ self.context = context
667
+ self.context.themes.set_window_colors(self)
668
+ self.operation = node
669
+ self.has_kerf = False
670
+
671
+ sizer_main = wx.BoxSizer(wx.HORIZONTAL)
672
+
673
+ self.sizer_kerf = StaticBoxSizer(
674
+ self, wx.ID_ANY, _("Kerf compensation:"), wx.HORIZONTAL
675
+ )
676
+ self.text_kerf = TextCtrl(
677
+ self,
678
+ wx.ID_ANY,
679
+ "0",
680
+ limited=True,
681
+ check="length",
682
+ style=wx.TE_PROCESS_ENTER,
683
+ )
684
+ self.text_kerf.SetToolTip(
685
+ _(
686
+ "Enter half the width of your laserbeam (kerf)\n"
687
+ + "if you want to have a shape with an exact size.\n"
688
+ + "Use the negative value if you are using the cutout\n"
689
+ + "as a placeholder for another part (eg inlays)."
690
+ )
691
+ )
692
+ self.kerf_label = wxStaticText(self, wx.ID_ANY, "")
693
+ self.sizer_kerf.Add(self.text_kerf, 1, wx.EXPAND, 0)
694
+ self.sizer_kerf.Add(self.kerf_label, 0, wx.ALIGN_CENTER_VERTICAL, 0)
695
+
696
+ self.sizer_coolant = StaticBoxSizer(
697
+ self, wx.ID_ANY, _("Coolant:"), wx.HORIZONTAL
698
+ )
699
+ cool_choices = [_("No changes"), _("Turn on"), _("Turn off")]
700
+ self.combo_coolant = wxComboBox(
701
+ self,
702
+ wx.ID_ANY,
703
+ choices=cool_choices,
704
+ style=wx.CB_DROPDOWN | wx.CB_READONLY,
705
+ )
706
+ self.combo_coolant.SetToolTip(
707
+ _(
708
+ "Define whether coolant support shall be explicitly turned on or off at the start of the operation, or whether it should be left at its current state."
709
+ )
710
+ )
711
+ self.sizer_coolant.Add(self.combo_coolant, 1, wx.EXPAND, 0)
712
+
713
+ sizer_passes = StaticBoxSizer(self, wx.ID_ANY, _("Passes:"), wx.HORIZONTAL)
714
+
715
+ self.check_passes = wxCheckBox(self, wx.ID_ANY, _("Passes"))
716
+ self.check_passes.SetToolTip(_("Enable Operation Passes"))
717
+ sizer_passes.Add(self.check_passes, 0, wx.EXPAND, 0)
718
+
719
+ self.text_passes = TextCtrl(
720
+ self, wx.ID_ANY, "1", limited=True, check="int", style=wx.TE_PROCESS_ENTER
721
+ )
722
+ OPERATION_PASSES_TOOLTIP = (
723
+ _("How many times to repeat this operation?")
724
+ + "\n"
725
+ + _(
726
+ "Setting e.g. passes to 2 is essentially equivalent to Duplicating the operation, "
727
+ )
728
+ + _(
729
+ "creating a second identical operation with the same settings and same elements."
730
+ )
731
+ + "\n"
732
+ + _("The number of Operation Passes can be changed extremely easily, ")
733
+ + _("but you cannot change any of the other settings.")
734
+ + "\n"
735
+ + _(
736
+ "Duplicating the Operation gives more flexibility for changing settings, "
737
+ )
738
+ + _("but is far more cumbersome to change the number of duplications ")
739
+ + _("because you need to add and delete the duplicates one by one.")
740
+ )
741
+
742
+ self.text_passes.SetToolTip(OPERATION_PASSES_TOOLTIP)
743
+ sizer_passes.Add(self.text_passes, 1, wx.EXPAND, 0)
744
+
745
+ sizer_main.Add(sizer_passes, 1, wx.EXPAND, 0)
746
+ sizer_main.Add(self.sizer_kerf, 1, wx.EXPAND, 0)
747
+ sizer_main.Add(self.sizer_coolant, 1, wx.EXPAND, 0)
748
+
749
+ self.SetSizer(sizer_main)
750
+
751
+ self.Layout()
752
+
753
+ self.Bind(wx.EVT_CHECKBOX, self.on_check_passes, self.check_passes)
754
+ self.Bind(wx.EVT_COMBOBOX, self.on_combo_coolant, self.combo_coolant)
755
+
756
+ self.text_passes.SetActionRoutine(self.on_text_passes)
757
+ self.text_kerf.SetActionRoutine(self.on_text_kerf)
758
+ # end wxGlade
759
+
760
+ def pane_hide(self):
761
+ pass
762
+
763
+ def pane_show(self):
764
+ pass
765
+
766
+ def accepts(self, node):
767
+ return node.type in (
768
+ "op cut",
769
+ "op engrave",
770
+ "op raster",
771
+ "op image",
772
+ "op dots",
773
+ )
774
+
775
+ def set_widgets(self, node):
776
+ self.operation = node
777
+ if self.operation is None or not self.accepts(node):
778
+ self.Hide()
779
+ return
780
+ self.has_kerf = bool(self.operation.type == "op cut")
781
+ if self.has_kerf:
782
+ s_label = ""
783
+ s_kerf = "0"
784
+ try:
785
+ ll = Length(self.operation.kerf, digits=2, preferred_units="mm")
786
+ kval = float(ll)
787
+ s_kerf = ll.preferred_length
788
+ if kval < 0:
789
+ s_label = " " + _("Inward")
790
+ elif kval > 0:
791
+ s_label = " " + _("Outward")
792
+ else:
793
+ s_kerf = "0"
794
+
795
+ except ValueError:
796
+ pass
797
+ self.text_kerf.SetValue(s_kerf)
798
+ self.kerf_label.SetLabel(s_label)
799
+ if self.operation.passes_custom is not None:
800
+ self.check_passes.SetValue(self.operation.passes_custom)
801
+ if self.operation.passes is not None:
802
+ set_ctrl_value(self.text_passes, str(self.operation.passes))
803
+ on = self.check_passes.GetValue()
804
+ self.text_passes.Enable(on)
805
+ self.sizer_kerf.ShowItems(self.has_kerf)
806
+ self.sizer_kerf.Show(self.has_kerf)
807
+ if hasattr(self.operation, "coolant"):
808
+ show_cool = True
809
+ value = self.operation.coolant
810
+ if value is None:
811
+ value = 0
812
+ self.combo_coolant.SetSelection(value)
813
+ else:
814
+ show_cool = False
815
+ if hasattr(self.context.device, "device_coolant"):
816
+ enable_cool = True
817
+ if self.context.device.device_coolant is None:
818
+ enable_cool = False
819
+ else:
820
+ enable_cool = False
821
+ self.combo_coolant.Enable(enable_cool)
822
+ self.sizer_coolant.ShowItems(show_cool)
823
+ self.sizer_coolant.Show(show_cool)
824
+ self.Layout()
825
+ self.Show()
826
+
827
+ def on_combo_coolant(self, event=None):
828
+ value = self.combo_coolant.GetSelection()
829
+ if value < 0:
830
+ value = 0
831
+ self.operation.coolant = value
832
+ self.context.elements.signal(
833
+ "element_property_reload", self.operation, "coolant"
834
+ )
835
+ event.Skip()
836
+
837
+ def on_check_passes(self, event=None): # wxGlade: OperationProperty.<event_handler>
838
+ on = self.check_passes.GetValue()
839
+ self.text_passes.Enable(on)
840
+ self.operation.passes_custom = bool(on)
841
+ self.context.elements.signal(
842
+ "element_property_reload", self.operation, "check_passes"
843
+ )
844
+ event.Skip()
845
+
846
+ def on_text_passes(self):
847
+ try:
848
+ value = int(self.text_passes.GetValue())
849
+ if self.operation.passes != value:
850
+ self.operation.passes = value
851
+ self.context.elements.signal(
852
+ "element_property_reload", self.operation, "text_Passes"
853
+ )
854
+ except ValueError:
855
+ pass
856
+
857
+ def on_text_kerf(self):
858
+ try:
859
+ value = float(Length(self.text_kerf.GetValue()))
860
+ if self.operation.kerf != value:
861
+ self.operation.kerf = value
862
+ self.context.elements.signal(
863
+ "element_property_reload", self.operation, "text_kerf"
864
+ )
865
+ except ValueError:
866
+ pass
867
+
868
+
869
+ # end of class PassesPanel
870
+
871
+
872
+ class InfoPanel(wx.Panel):
873
+ def __init__(self, *args, context=None, node=None, **kwds):
874
+ # begin wxGlade: PassesPanel.__init__
875
+ kwds["style"] = kwds.get("style", 0)
876
+ wx.Panel.__init__(self, *args, **kwds)
877
+ self.context = context
878
+ self.operation = node
879
+
880
+ sizer_info = StaticBoxSizer(self, wx.ID_ANY, _("Info:"), wx.HORIZONTAL)
881
+
882
+ sizer_children = StaticBoxSizer(self, wx.ID_ANY, _("Children:"), wx.HORIZONTAL)
883
+ sizer_time = StaticBoxSizer(
884
+ self, wx.ID_ANY, _("Est. burn-time:"), wx.HORIZONTAL
885
+ )
886
+
887
+ self.text_children = TextCtrl(self, wx.ID_ANY, "0", style=wx.TE_READONLY)
888
+ self.text_children.SetMinSize(dip_size(self, 25, -1))
889
+ self.text_children.SetMaxSize(dip_size(self, 55, -1))
890
+ self.text_time = TextCtrl(self, wx.ID_ANY, "---", style=wx.TE_READONLY)
891
+ self.text_time.SetMinSize(dip_size(self, 55, -1))
892
+ self.text_time.SetMaxSize(dip_size(self, 100, -1))
893
+ self.text_children.SetToolTip(
894
+ _("How many elements does this operation contain")
895
+ )
896
+ self.text_time.SetToolTip(_("Estimated time for execution (hh:mm:ss)"))
897
+ self.btn_update = wxButton(self, wx.ID_ANY, _("Calculate"))
898
+ self.btn_update.Bind(wx.EVT_BUTTON, self.on_button_calculate)
899
+
900
+ self.btn_recalc = wxButton(self, wx.ID_ANY, _("Re-Classify"))
901
+ self.btn_recalc.Bind(wx.EVT_BUTTON, self.on_button_refresh)
902
+
903
+ sizer_children.Add(self.text_children, 1, wx.EXPAND, 0)
904
+ sizer_time.Add(self.text_time, 1, wx.EXPAND, 0)
905
+ sizer_time.Add(self.btn_update, 0, wx.EXPAND, 0)
906
+ sizer_time.AddSpacer(20)
907
+ sizer_time.Add(self.btn_recalc, 0, wx.EXPAND, 0)
908
+
909
+ sizer_info.Add(sizer_children, 1, wx.EXPAND, 0)
910
+ sizer_info.Add(sizer_time, 2, wx.EXPAND, 0)
911
+
912
+ self.SetSizer(sizer_info)
913
+
914
+ self.Layout()
915
+
916
+ # end wxGlade
917
+
918
+ def pane_hide(self):
919
+ pass
920
+
921
+ def pane_show(self):
922
+ pass
923
+
924
+ def on_button_refresh(self, event):
925
+ if not hasattr(self.operation, "classify"):
926
+ return
927
+
928
+ infotxt = (
929
+ _("Do you really want to reassign elements to this operation?")
930
+ + "\n"
931
+ + _("Atttention: This will delete all existing assignments!")
932
+ )
933
+ dlg = wx.MessageDialog(
934
+ None, infotxt, "Re-Classify", wx.YES_NO | wx.ICON_WARNING
935
+ )
936
+ result = dlg.ShowModal()
937
+ dlg.Destroy()
938
+ if result == wx.ID_YES:
939
+ with self.context.elements.undoscope("Re-Classify"):
940
+ myop = self.operation
941
+ myop.remove_all_children()
942
+ data = list(self.context.elements.elems())
943
+ reverse = self.context.elements.classify_reverse
944
+ fuzzy = self.context.elements.classify_fuzzy
945
+ fuzzydistance = self.context.elements.classify_fuzzydistance
946
+ if reverse:
947
+ data = reversed(data)
948
+ for node in data:
949
+ # result is a tuple containing classified, should_break, feedback
950
+ result = myop.classify(
951
+ node,
952
+ fuzzy=fuzzy,
953
+ fuzzydistance=fuzzydistance,
954
+ usedefault=False,
955
+ )
956
+ # Probably moot as the next command will move the focus away...
957
+ self.refresh_display()
958
+ self.context.signal("tree_changed")
959
+ self.context.signal("activate_single_node", myop)
960
+
961
+ def on_button_calculate(self, event):
962
+ self.text_time.SetValue(self.operation.time_estimate())
963
+
964
+ def refresh_display(self):
965
+ childs = len(self.operation.children)
966
+ self.text_time.SetValue("---")
967
+ self.text_children.SetValue(str(childs))
968
+
969
+ def accepts(self, node):
970
+ return node.type in (
971
+ "op cut",
972
+ "op engrave",
973
+ "op raster",
974
+ "op image",
975
+ "op dots",
976
+ )
977
+
978
+ def set_widgets(self, node):
979
+ self.operation = node
980
+ if self.operation is None or not self.accepts(node):
981
+ self.Hide()
982
+ return
983
+ self.refresh_display()
984
+ self.Show()
985
+
986
+
987
+ # end of class InfoPanel
988
+
989
+
990
+ class PanelStartPreference(wx.Panel):
991
+ def __init__(self, *args, context=None, node=None, **kwds):
992
+ # begin wxGlade: PanelStartPreference.__init__
993
+ kwds["style"] = kwds.get("style", 0)
994
+ wx.Panel.__init__(self, *args, **kwds)
995
+ self.context = context
996
+ self.operation = node
997
+
998
+ sizer_main = StaticBoxSizer(self, wx.ID_ANY, _("Start Preference:"), wx.VERTICAL)
999
+
1000
+ self.slider_pref_left = wx.Slider(self, wx.ID_ANY, 1, 0, 1)
1001
+ sizer_main.Add(self.slider_pref_left, 0, wx.EXPAND, 0)
1002
+
1003
+ sizer_top_display = wx.BoxSizer(wx.HORIZONTAL)
1004
+ sizer_main.Add(sizer_top_display, 1, wx.EXPAND, 0)
1005
+
1006
+ self.slider_pref_top = wx.Slider(self, wx.ID_ANY, 1, 0, 1, style=wx.SL_VERTICAL)
1007
+ sizer_top_display.Add(self.slider_pref_top, 0, wx.EXPAND, 0)
1008
+
1009
+ self.display_panel = wx.Panel(self, wx.ID_ANY)
1010
+ sizer_top_display.Add(self.display_panel, 1, wx.EXPAND, 0)
1011
+
1012
+ self.SetSizer(sizer_main)
1013
+
1014
+ self.Layout()
1015
+
1016
+ self.Bind(wx.EVT_SLIDER, self.on_slider_pref_left, self.slider_pref_left)
1017
+ self.Bind(wx.EVT_SLIDER, self.on_slider_pref_top, self.slider_pref_top)
1018
+ # end wxGlade
1019
+ self.Bind(wx.EVT_SIZE, self.on_size)
1020
+ self.display_panel.Bind(wx.EVT_PAINT, self.on_display_paint)
1021
+ self.display_panel.Bind(wx.EVT_ERASE_BACKGROUND, self.on_display_erase)
1022
+
1023
+ self.raster_pen = wx.Pen()
1024
+ self.raster_pen.SetColour(wx.BLACK)
1025
+ self.raster_pen.SetWidth(2)
1026
+
1027
+ self.travel_pen = wx.Pen()
1028
+ self.travel_pen.SetColour(wx.Colour(255, 127, 255, 127))
1029
+ self.travel_pen.SetWidth(2)
1030
+
1031
+ self.direction_pen = wx.Pen()
1032
+ self.direction_pen.SetColour(wx.Colour(127, 127, 255))
1033
+ self.direction_pen.SetWidth(2)
1034
+
1035
+ self.raster_lines = None
1036
+ self.travel_lines = None
1037
+ self.direction_lines = None
1038
+
1039
+ self._Buffer = None
1040
+
1041
+ def pane_hide(self):
1042
+ pass
1043
+
1044
+ def pane_show(self):
1045
+ pass
1046
+
1047
+ # @signal_listener("element_property_reload")
1048
+ def on_element_property_reload(self, *args):
1049
+ self.set_widgets(self.operation)
1050
+ self._reload_display()
1051
+
1052
+ def accepts(self, node):
1053
+ return node.type in (
1054
+ "op raster",
1055
+ "op image",
1056
+ )
1057
+
1058
+ def set_widgets(self, node):
1059
+ self.operation = node
1060
+ if self.operation is None or not self.accepts(node):
1061
+ self.Hide()
1062
+ return
1063
+ if self.operation.raster_direction in (
1064
+ RASTER_CROSSOVER, RASTER_SPIRAL,
1065
+ ): # Crossover
1066
+ self.Hide()
1067
+ return
1068
+ validate_raster_settings(self.operation)
1069
+ self._toggle_sliders()
1070
+ self.refresh_display()
1071
+ self.Show()
1072
+
1073
+ def on_display_paint(self, event=None):
1074
+ try:
1075
+ wx.BufferedPaintDC(self.display_panel, self._Buffer)
1076
+ except RuntimeError:
1077
+ pass
1078
+
1079
+ def on_display_erase(self, event=None):
1080
+ pass
1081
+
1082
+ def set_buffer(self):
1083
+ width, height = self.display_panel.Size
1084
+ if width <= 0:
1085
+ width = 1
1086
+ if height <= 0:
1087
+ height = 1
1088
+ self._Buffer = wx.Bitmap(width, height)
1089
+
1090
+ def on_size(self, event=None):
1091
+ self.Layout()
1092
+ self.set_buffer()
1093
+ self.raster_lines = None
1094
+ self.travel_lines = None
1095
+ self.direction_lines = None
1096
+ wx.CallAfter(self.refresh_in_ui)
1097
+
1098
+ def refresh_display(self):
1099
+ if self._Buffer is None:
1100
+ self.set_buffer()
1101
+
1102
+ if not wx.IsMainThread():
1103
+ wx.CallAfter(self.refresh_in_ui)
1104
+ else:
1105
+ self.refresh_in_ui()
1106
+
1107
+ def calculate_raster_lines(self):
1108
+ w, h = self._Buffer.Size
1109
+ if w<10 or h<10: # Ini initialisation phase and too small anyway...
1110
+ return
1111
+
1112
+ from_left = self.operation.raster_preference_left
1113
+ from_top = self.operation.raster_preference_top
1114
+
1115
+ last = None
1116
+ direction = self.operation.raster_direction
1117
+ # Rasterline Indicator
1118
+ r_start = []
1119
+ r_end = []
1120
+ # Travel Indicator
1121
+ t_start = []
1122
+ t_end = []
1123
+ # Direction Indicator
1124
+ d_start = []
1125
+ d_end = []
1126
+ factor = 3
1127
+ bidirectional = self.operation.bidirectional
1128
+ dpi = max(1, self.operation.dpi)
1129
+
1130
+ def dir_arrow_up_down(up: bool):
1131
+ d_start.append((w * 0.05, h * 0.05))
1132
+ d_end.append((w * 0.05, h * 0.95))
1133
+ # Direction Arrow
1134
+ d_start.append((w * 0.05, (h * 0.95) if up else (h * 0.05)))
1135
+ d_end.append((w * 0.05 + 4, (h * 0.95 - 4) if up else (h * 0.05 + 4)))
1136
+ d_start.append((w * 0.05, (h * 0.95) if up else (h * 0.05)))
1137
+ d_end.append((w * 0.05 - 4, (h * 0.95 - 4) if up else (h * 0.05 + 4)))
1138
+
1139
+ def dir_arrow_left_right(left: bool):
1140
+ # Direction Line
1141
+ d_start.append((w * 0.05, h * 0.05))
1142
+ d_end.append((w * 0.95, h * 0.05))
1143
+ # Direction Arrow
1144
+ d_start.append(((w * 0.95) if left else (w * 0.05), h * 0.05))
1145
+ d_end.append(((w * 0.95 - 4) if left else (w * 0.05 + 4), h * 0.05 - 4))
1146
+ d_start.append(((w * 0.95) if left else (w * 0.05), h * 0.05))
1147
+ d_end.append(((w * 0.95 - 4) if left else (w * 0.05 + 4), h * 0.05 + 4))
1148
+
1149
+ if direction in (RASTER_T2B, RASTER_B2T, RASTER_HATCH, RASTER_GREEDY_H, RASTER_CROSSOVER): # Horizontal mode, raster will be built from top to bottom (or vice versa)
1150
+ # Direction Line
1151
+ if direction in (RASTER_B2T, ):
1152
+ # Bottom to Top
1153
+ dir_arrow_up_down(False)
1154
+ start = int(h * 0.9)
1155
+ end = int(h * 0.1)
1156
+ step = -1000 / dpi * factor
1157
+ else:
1158
+ # Top to Bottom or Crosshatch
1159
+ dir_arrow_up_down(True)
1160
+ start = int(h * 0.1)
1161
+ end = int(h * 0.9)
1162
+ step = 1000 / dpi * factor
1163
+ if step == 0:
1164
+ step = abs(start-end) / 10
1165
+ while abs(step) > abs(start - end):
1166
+ step /= 2
1167
+ while abs(start - end) / abs(step) > 100:
1168
+ step *= 2
1169
+
1170
+ pos = start
1171
+ while min(start, end) <= pos <= max(start, end):
1172
+ # Primary Line Horizontal Raster, no need to be fancy and be directional here...
1173
+ r_start.append((w * 0.1, pos))
1174
+ r_end.append((w * 0.9, pos))
1175
+
1176
+ # Travel segment
1177
+ if last is not None:
1178
+ # Travel Lines, from end of last line to next
1179
+ t_start.append((last[0], last[1]))
1180
+ t_end.append((w * 0.1 if from_left else w * 0.9, pos))
1181
+
1182
+ # Arrow segment
1183
+ r_start.append((w * 0.9 if from_left else w * 0.1, pos))
1184
+ r_end.append((w * 0.9 - 2 if from_left else w * 0.1 + 2, pos - 2))
1185
+ last = r_start[-1]
1186
+ if bidirectional:
1187
+ from_left = not from_left
1188
+ pos += step
1189
+ if direction in (RASTER_R2L, RASTER_L2R, RASTER_HATCH, RASTER_GREEDY_V, RASTER_CROSSOVER): # Vertical mode, raster will be built from left to right (or vice versa)
1190
+ if direction in (RASTER_R2L, ):
1191
+ # Right to Left
1192
+ dir_arrow_left_right(False)
1193
+ start = int(w * 0.9)
1194
+ end = int(w * 0.1)
1195
+ step = -1000 / dpi * factor
1196
+ else:
1197
+ # Left to Right or Crosshatch
1198
+ dir_arrow_left_right(True)
1199
+ start = int(w * 0.1)
1200
+ end = int(w * 0.9)
1201
+ step = 1000 / dpi * factor
1202
+ if step == 0:
1203
+ step = abs(start-end) / 10
1204
+ while abs(step) > abs(start - end):
1205
+ step /= 2
1206
+ while abs(start - end) / abs(step) > 100:
1207
+ step *= 2
1208
+
1209
+ pos = start
1210
+ while min(start, end) <= pos <= max(start, end):
1211
+ # Primary Line Vertical Raster.
1212
+ r_start.append((pos, h * 0.1))
1213
+ r_end.append((pos, h * 0.9))
1214
+
1215
+ # Travel Segment
1216
+ if last is not None:
1217
+ # Travel Lines
1218
+ t_start.append((last[0], last[1]))
1219
+ t_end.append((pos, h * 0.1 if from_top else h * 0.9))
1220
+
1221
+ # Arrow Segment
1222
+ r_start.append((pos, h * 0.9 if from_top else h * 0.1))
1223
+ r_end.append((pos - 2, (h * 0.9) - 2 if from_top else (h * 0.1) + 2))
1224
+
1225
+ last = r_start[-1]
1226
+ if bidirectional:
1227
+ from_top = not from_top
1228
+ pos += step
1229
+ self.raster_lines = r_start, r_end
1230
+ self.travel_lines = t_start, t_end
1231
+ self.direction_lines = d_start, d_end
1232
+
1233
+ def refresh_in_ui(self):
1234
+ """Performs redrawing of the data in the UI thread."""
1235
+ try:
1236
+ visible = self.Shown
1237
+ except RuntimeError:
1238
+ # May have already been deleted....
1239
+ return
1240
+ dc = wx.MemoryDC()
1241
+ dc.SelectObject(self._Buffer)
1242
+ dc.SetBackground(wx.WHITE_BRUSH)
1243
+ dc.Clear()
1244
+ gc = wx.GraphicsContext.Create(dc)
1245
+ if visible:
1246
+ if self.raster_lines is None:
1247
+ self.calculate_raster_lines()
1248
+ if self.raster_lines is not None:
1249
+ starts, ends = self.raster_lines
1250
+ if len(starts):
1251
+ gc.SetPen(self.raster_pen)
1252
+ gc.StrokeLineSegments(starts, ends)
1253
+ if self.travel_lines is not None:
1254
+ starts, ends = self.travel_lines
1255
+ if len(starts):
1256
+ gc.SetPen(self.travel_pen)
1257
+ gc.StrokeLineSegments(starts, ends)
1258
+ if self.direction_lines is not None:
1259
+ starts, ends = self.direction_lines
1260
+ if len(starts):
1261
+ gc.SetPen(self.direction_pen)
1262
+ gc.StrokeLineSegments(starts, ends)
1263
+ gc.Destroy()
1264
+ dc.SelectObject(wx.NullBitmap)
1265
+ del dc
1266
+ self.display_panel.Refresh()
1267
+ self.display_panel.Update()
1268
+
1269
+ def _toggle_sliders(self):
1270
+ direction = self.operation.raster_direction
1271
+ prefer_min_y = self.operation.raster_preference_top
1272
+ prefer_min_x = self.operation.raster_preference_left
1273
+
1274
+ self.slider_pref_left.Enable(direction in (RASTER_T2B, RASTER_B2T, RASTER_GREEDY_H, RASTER_GREEDY_V))
1275
+ self.slider_pref_top.Enable(direction in (RASTER_L2R, RASTER_R2L, RASTER_GREEDY_H, RASTER_GREEDY_V))
1276
+ self.slider_pref_left.SetValue(0 if prefer_min_x else 1)
1277
+ self.slider_pref_top.SetValue(0 if prefer_min_y else 1)
1278
+
1279
+
1280
+ def _reload_display(self):
1281
+ self.raster_lines = None
1282
+ self.travel_lines = None
1283
+ self.refresh_display()
1284
+
1285
+ def on_slider_pref_left(self, event=None): # wxGlade: OperationProperty.<event_handler>
1286
+ value = self.slider_pref_left.GetValue() == 0
1287
+ if self.operation.raster_preference_left != value:
1288
+ self.operation.raster_preference_left = value
1289
+ self._reload_display()
1290
+ self.context.elements.signal(
1291
+ "element_property_update", self.operation, "slider_top"
1292
+ )
1293
+
1294
+ def on_slider_pref_top(self, event=None): # wxGlade: OperationProperty.<event_handler>
1295
+ value = self.slider_pref_top.GetValue() == 0
1296
+ if self.operation.raster_preference_top != value:
1297
+ self.operation.raster_preference_top = value
1298
+ self._reload_display()
1299
+ self.context.elements.signal(
1300
+ "element_property_update", self.operation, "slider_left"
1301
+ )
1302
+ # end of class PanelStartPreference
1303
+
1304
+
1305
+ class RasterSettingsPanel(wx.Panel):
1306
+ def __init__(self, *args, context=None, node=None, **kwds):
1307
+ # begin wxGlade: RasterSettingsPanel.__init__
1308
+ kwds["style"] = kwds.get("style", 0)
1309
+ wx.Panel.__init__(self, *args, **kwds)
1310
+ self.context = context
1311
+ self.operation = node
1312
+ iconsize = dip_size(self, 30, 20)
1313
+ bmpsize = min(iconsize[0], iconsize[1]) * self.context.root.bitmap_correction_scale
1314
+
1315
+ raster_sizer = StaticBoxSizer(self, wx.ID_ANY, _("Raster:"), wx.VERTICAL)
1316
+ param_sizer = wx.BoxSizer(wx.HORIZONTAL)
1317
+
1318
+ sizer_dpi = StaticBoxSizer(self, wx.ID_ANY, _("DPI:"), wx.HORIZONTAL)
1319
+ param_sizer.Add(sizer_dpi, 1, wx.EXPAND, 0)
1320
+ self.check_overrule_dpi = wxCheckBox(self, wx.ID_ANY)
1321
+ self.check_overrule_dpi.SetToolTip(_("Overrules image dpi settings and uses this value instead"))
1322
+ self.text_dpi = TextCtrl(
1323
+ self,
1324
+ wx.ID_ANY,
1325
+ "500",
1326
+ limited=True,
1327
+ check="int",
1328
+ style=wx.TE_PROCESS_ENTER,
1329
+ )
1330
+ self.text_dpi.set_default_values(
1331
+ [
1332
+ (str(dpi), _("Set DPI to {value}").format(value=str(dpi)))
1333
+ for dpi in self.context.device.view.get_sensible_dpi_values()
1334
+ ]
1335
+ )
1336
+ self.text_dpi.set_error_level(1, 100000)
1337
+ OPERATION_DPI_TOOLTIP = (
1338
+ _('In a raster engrave, the step size is the distance between raster lines in 1/1000" ') +
1339
+ _("and also the number of raster dots that get combined together.") + "\n" +
1340
+ _('Because the laser dot is >> 1/1000" in diameter, at step 1 the raster lines overlap a lot, ') +
1341
+ _("and consequently you can raster with steps > 1 without leaving gaps between the lines.") + "\n" +
1342
+ _("The step size before you get gaps will depend on your focus and the size of your laser dot.") + "\n"+
1343
+ _("Step size > 1 reduces the laser energy delivered by the same factor, so you may need to increase ") +
1344
+ _("power equivalently with a higher front-panel power, a higher PPI or by rastering at a slower speed.") + "\n" +
1345
+ _("Step size > 1 also turns the laser on and off fewer times, and combined with a slower speed") +
1346
+ _("this can prevent your laser from stuttering.")
1347
+ )
1348
+ self.text_dpi.SetToolTip(OPERATION_DPI_TOOLTIP)
1349
+ sizer_dpi.Add(self.check_overrule_dpi, 0, wx.EXPAND, 0)
1350
+ sizer_dpi.Add(self.text_dpi, 1, wx.EXPAND, 0)
1351
+
1352
+ sizer_optimize = StaticBoxSizer(self, wx.ID_ANY, _("Optimize movement:"), wx.HORIZONTAL)
1353
+ self.check_laserdot = wxCheckBox(self, wx.ID_ANY, _("Consider laserdot") )
1354
+ self.check_laserdot.SetToolTip(
1355
+ _("A laser dot has a certain diameter, so for high dpi values, lines will overlap a lot.") + "\n" +
1356
+ _("Active: don't burn pixels already overlapped") + "\n" +
1357
+ _("Inactive: burn all pixels regardless of a possible overlap.")
1358
+ )
1359
+ sizer_optimize.Add(self.check_laserdot, 1, wx.EXPAND, 0)
1360
+ param_sizer.Add(sizer_optimize, 1, wx.EXPAND, 0)
1361
+
1362
+ self.sizer_grayscale = StaticBoxSizer(self, wx.ID_ANY, _("Override black/white image:"), wx.HORIZONTAL)
1363
+ param_sizer.Add(self.sizer_grayscale, 1, wx.EXPAND, 0)
1364
+ self.check_grayscale = wxCheckBox(self, wx.ID_ANY, _("Use grayscale instead") )
1365
+ self.check_grayscale.SetToolTip(
1366
+ _("Usually a raster will be created as a grayscale picture and the burn-power of a pixel will depend on its darkness.") + "\n" +
1367
+ _("If you uncheck this value then every non-white pixel (even very light ones) will become black and will be burned at full power.")
1368
+ )
1369
+ self.sizer_grayscale.Add(self.check_grayscale, 1, wx.EXPAND, 0)
1370
+
1371
+ sizer_overscan = StaticBoxSizer(self, wx.ID_ANY, _("Overscan:"), wx.HORIZONTAL)
1372
+ param_sizer.Add(sizer_overscan, 1, wx.EXPAND, 0)
1373
+ self.text_overscan = TextCtrl(
1374
+ self,
1375
+ wx.ID_ANY,
1376
+ "0mm",
1377
+ limited=True,
1378
+ check="length",
1379
+ style=wx.TE_PROCESS_ENTER,
1380
+ )
1381
+ self.text_overscan.SetToolTip(_("Padding that will be added at the end of a scanline to allow the laser to slow down"))
1382
+ sizer_overscan.Add(self.text_overscan, 1, wx.EXPAND, 0)
1383
+
1384
+ raster_sizer.Add(param_sizer, 0, wx.EXPAND, 0)
1385
+
1386
+ sizer_4 = StaticBoxSizer(self, wx.ID_ANY, _("Direction:"), wx.HORIZONTAL)
1387
+ raster_sizer.Add(sizer_4, 0, wx.EXPAND, 0)
1388
+ self.raster_terms = [
1389
+ (RASTER_T2B, "Top To Bottom"),
1390
+ (RASTER_B2T, "Bottom To Top"),
1391
+ (RASTER_R2L, "Right To Left"),
1392
+ (RASTER_L2R, "Left To Right"),
1393
+ (RASTER_HATCH, "Crosshatch"),
1394
+ (RASTER_GREEDY_H, "Greedy horizontal"),
1395
+ (RASTER_GREEDY_V, "Greedy vertical"),
1396
+ (RASTER_CROSSOVER, "Crossover"),
1397
+ (RASTER_SPIRAL, "Spiral"),
1398
+ ]
1399
+ # Look for registered raster (image) preprocessors,
1400
+ # these are routines that take one image as parameter
1401
+ # and deliver a set of (result image, method (aka raster_direction) )
1402
+ # that will be dealt with independently
1403
+ # The registered datastructure is (rasterid, description, method)
1404
+ self.raster_terms.extend(
1405
+ (key, description)
1406
+ for key, description, method in self.context.kernel.lookup_all(
1407
+ "raster_preprocessor/.*"
1408
+ )
1409
+ )
1410
+ # Add a couple of testcases
1411
+ # test_methods = (
1412
+ # (-1, "Test: Horizontal Rectangle"),
1413
+ # (-2, "Test: Vertical Rectangle"),
1414
+ # (-3, "Test: Horizontal Snake"),
1415
+ # (-4, "Test: Vertical Snake"),
1416
+ # (-5, "Test: Spiral"),
1417
+ # )
1418
+ # self.raster_terms.extend(test_methods)
1419
+
1420
+ self.raster_methods = [ key for key, info in self.raster_terms ]
1421
+
1422
+ self.combo_raster_direction = wxComboBox(
1423
+ self,
1424
+ wx.ID_ANY,
1425
+ style=wx.CB_DROPDOWN | wx.CB_READONLY,
1426
+ )
1427
+ OPERATION_RASTERDIRECTION_TOOLTIP = (
1428
+ _("Direction to perform a raster")
1429
+ + "\n"
1430
+ + _(
1431
+ "Normally you would raster in an X-direction and select Top-to-Bottom (T2B) or Bottom-to-Top (B2T)."
1432
+ )
1433
+ + "\n"
1434
+ + _(
1435
+ "This is because rastering in the X-direction involve moving only the laser head which is relatively low mass."
1436
+ )
1437
+ + "\n"
1438
+ + _(
1439
+ "Rastering in the Y-direction (Left-to-Right or Right-to-Left) involves moving not only the laser head "
1440
+ )
1441
+ + _(
1442
+ "but additionally the entire x-axis gantry assembly including the stepper motor, mirror and the gantry itself."
1443
+ )
1444
+ + "\n"
1445
+ + _(
1446
+ "This total mass is much greater, acceleration therefore needs to be much slower, "
1447
+ )
1448
+ + _(
1449
+ "and allow for space at each end of the raster to reverse direction the speed has to be much slower."
1450
+ )
1451
+ )
1452
+
1453
+ self.combo_raster_direction.SetToolTip(OPERATION_RASTERDIRECTION_TOOLTIP)
1454
+ # OSX needs an early population, as events might occur before pane_show has been called
1455
+ self.fill_raster_combo()
1456
+ self.combo_raster_direction.SetSelection(0)
1457
+ sizer_4.Add(self.combo_raster_direction, 1, wx.ALIGN_CENTER_VERTICAL, 0)
1458
+ self.btn_instruction = wxStaticBitmap(self, wx.ID_ANY)
1459
+ self.btn_instruction.SetBitmap(icon_letter_h.GetBitmap(resize=bmpsize))
1460
+ self.btn_instruction.SetToolTip(_("Quick info about the available modes"))
1461
+ sizer_4.Add(self.btn_instruction, 0, wx.ALIGN_CENTER_VERTICAL, 0)
1462
+
1463
+ self.radio_raster_swing = wxRadioBox(
1464
+ self,
1465
+ wx.ID_ANY,
1466
+ _("Directional Raster:"),
1467
+ choices=[_("Unidirectional"), _("Bidirectional")],
1468
+ majorDimension=1,
1469
+ style=wx.RA_SPECIFY_ROWS,
1470
+ )
1471
+ OPERATION_RASTERSWING_TOOLTIP = (
1472
+ _("Raster on forward and backswing or only forward swing?")
1473
+ + "\n"
1474
+ + _(
1475
+ "Rastering only on forward swings will double the time required to complete the raster."
1476
+ )
1477
+ + "\n"
1478
+ + _(
1479
+ "It seems doubtful that there will be significant quality benefits from rastering in one direction."
1480
+ )
1481
+ )
1482
+ self.radio_raster_swing.SetToolTip(OPERATION_RASTERSWING_TOOLTIP)
1483
+ self.radio_raster_swing.SetSelection(0)
1484
+ raster_sizer.Add(self.radio_raster_swing, 0, wx.EXPAND, 0)
1485
+
1486
+ self.panel_start = PanelStartPreference(
1487
+ self, wx.ID_ANY, context=context, node=node
1488
+ )
1489
+ raster_sizer.Add(self.panel_start, 1, wx.EXPAND, 0)
1490
+
1491
+ self.SetSizer(raster_sizer)
1492
+
1493
+ self.Layout()
1494
+ self.Bind(wx.EVT_CHECKBOX, self.on_overrule, self.check_overrule_dpi)
1495
+ self.Bind(wx.EVT_CHECKBOX, self.on_check_grayscale, self.check_grayscale)
1496
+ self.Bind(wx.EVT_CHECKBOX, self.on_check_laserdot, self.check_laserdot)
1497
+ self.text_dpi.SetActionRoutine(self.on_text_dpi)
1498
+ self.text_overscan.SetActionRoutine(self.on_text_overscan)
1499
+
1500
+ self.Bind(
1501
+ wx.EVT_COMBOBOX, self.on_combo_raster_direction, self.combo_raster_direction
1502
+ )
1503
+ self.Bind(wx.EVT_RADIOBOX, self.on_radio_directional, self.radio_raster_swing)
1504
+ self.btn_instruction.Bind(wx.EVT_LEFT_DOWN, self.on_raster_help)
1505
+
1506
+ def fill_raster_combo(self):
1507
+ unsupported = ()
1508
+ if hasattr(self.context.device, "get_raster_instructions"):
1509
+ instructions = self.context.device.get_raster_instructions()
1510
+ unsupported = instructions.get("unsupported_opt", ())
1511
+ # print (f"fill raster called: {unsupported}")
1512
+ self.raster_methods = [ key for key, info in self.raster_terms if key not in unsupported ]
1513
+ choices = [ info for key, info in self.raster_terms if key not in unsupported ]
1514
+ self.combo_raster_direction.Clear()
1515
+ self.combo_raster_direction.SetItems(choices)
1516
+ if self.operation is not None:
1517
+ self.set_raster_combo()
1518
+
1519
+ @lookup_listener("service/device/active")
1520
+ def on_device_update(self, *args):
1521
+ if self.Shown():
1522
+ self.fill_raster_combo()
1523
+
1524
+ def pane_hide(self):
1525
+ self.panel_start.pane_hide()
1526
+
1527
+ def pane_show(self):
1528
+ self.fill_raster_combo()
1529
+ self.panel_start.pane_show()
1530
+
1531
+ def accepts(self, node):
1532
+ return node.type in (
1533
+ "op raster",
1534
+ "op image",
1535
+ )
1536
+
1537
+ def set_widgets(self, node):
1538
+ self.operation = node
1539
+ if self.operation is None or not self.accepts(node):
1540
+ self.Hide()
1541
+ return
1542
+ if self.operation.dpi is not None:
1543
+ dpi = int(self.operation.dpi)
1544
+ set_ctrl_value(self.text_dpi, str(dpi))
1545
+ if hasattr(self.operation, "use_grayscale"):
1546
+ self.sizer_grayscale.ShowItems(True)
1547
+
1548
+ self.check_grayscale.SetValue(self.operation.use_grayscale)
1549
+ else:
1550
+ self.sizer_grayscale.ShowItems(False)
1551
+ if hasattr(self.operation, "overrule_dpi"):
1552
+ self.check_overrule_dpi.Show(True)
1553
+ overrule = self.operation.overrule_dpi
1554
+ if overrule is None:
1555
+ overrule = False
1556
+ self.check_overrule_dpi.SetValue(overrule)
1557
+ self.text_dpi.Enable(overrule)
1558
+ else:
1559
+ self.check_overrule_dpi.Show(False)
1560
+ self.text_dpi.Enable(True)
1561
+ if self.operation.overscan is not None:
1562
+ set_ctrl_value(self.text_overscan, str(self.operation.overscan))
1563
+ self.set_raster_combo()
1564
+ if self.operation.bidirectional is not None:
1565
+ self.radio_raster_swing.SetSelection(self.operation.bidirectional)
1566
+ self.check_laserdot.SetValue(self.operation.consider_laserspot)
1567
+ # Hide it for now...
1568
+ # self.check_laserdot.Show(False)
1569
+ self.allow_controls_according_to_optimization()
1570
+ self.Show()
1571
+
1572
+ def allow_controls_according_to_optimization(self):
1573
+ direction = self.operation.raster_direction
1574
+ overscan_okay = direction not in (RASTER_GREEDY_H, RASTER_GREEDY_V)
1575
+ swing_okay = direction in (RASTER_B2T, RASTER_T2B, RASTER_R2L, RASTER_L2R, RASTER_HATCH)
1576
+ self.text_overscan.Enable(overscan_okay)
1577
+ self.radio_raster_swing.Enable(swing_okay)
1578
+ if not swing_okay:
1579
+ self.radio_raster_swing.SetSelection(True)
1580
+
1581
+ def on_overrule(self, event):
1582
+ if self.operation is None or not hasattr(self.operation, "overrule_dpi"):
1583
+ return
1584
+ b = self.check_overrule_dpi.GetValue()
1585
+ self.text_dpi.Enable(b)
1586
+ if self.operation.overrule_dpi != b:
1587
+ self.operation.overrule_dpi = b
1588
+ self.context.signal(
1589
+ "element_property_reload", self.operation, "text_dpi"
1590
+ )
1591
+ self.context.signal("warn_state_update")
1592
+
1593
+ def on_text_dpi(self):
1594
+ try:
1595
+ value = int(self.text_dpi.GetValue())
1596
+ except ValueError as e:
1597
+ # print (e)
1598
+ return
1599
+ if self.operation.dpi != value:
1600
+ self.operation.dpi = value
1601
+ self.context.signal(
1602
+ "element_property_reload", self.operation, "text_dpi"
1603
+ )
1604
+ self.context.signal("warn_state_update")
1605
+
1606
+ def on_check_grayscale(self, event):
1607
+ value = self.check_grayscale.GetValue()
1608
+ if self.operation.use_grayscale != value:
1609
+ self.operation.use_grayscale = value
1610
+ self.context.elements.signal("element_property_reload", self.operation)
1611
+
1612
+ def on_check_laserdot(self, event):
1613
+ value = self.check_laserdot.GetValue()
1614
+ if self.operation.consider_laserspot != value:
1615
+ self.operation.consider_laserspot = value
1616
+ self.context.elements.signal("element_property_reload", self.operation)
1617
+
1618
+ def on_text_overscan(self):
1619
+ try:
1620
+ v = Length(
1621
+ self.text_overscan.GetValue(),
1622
+ unitless=UNITS_PER_MM,
1623
+ preferred_units="mm",
1624
+ digits=4,
1625
+ )
1626
+ except ValueError:
1627
+ return
1628
+ # print ("Start overscan=%s - target=%s" % (start_text, str(v.preferred_length)))
1629
+ value = v.preferred_length
1630
+ if v._amount < 1e-10:
1631
+ value = 0
1632
+ if self.operation.overscan != value:
1633
+ self.operation.overscan = value
1634
+ self.context.elements.signal(
1635
+ "element_property_reload", self.operation, "text_overscan"
1636
+ )
1637
+
1638
+ def on_combo_raster_direction(self, event=None):
1639
+ idx = self.combo_raster_direction.GetSelection()
1640
+ if idx < 0:
1641
+ return
1642
+ value = self.raster_methods[idx]
1643
+
1644
+ if (self.operation.raster_direction != value ):
1645
+ self.operation.raster_direction = value
1646
+ validate_raster_settings(self.operation)
1647
+
1648
+ self.context.raster_direction = self.operation.raster_direction
1649
+ self.context.elements.signal(
1650
+ "element_property_reload", self.operation, "combo_raster"
1651
+ )
1652
+ event.Skip()
1653
+
1654
+ def on_radio_directional(self, event=None):
1655
+ self.operation.bidirectional = bool(self.radio_raster_swing.GetSelection())
1656
+ self.context.elements.signal(
1657
+ "element_property_reload", self.operation, "radio_direct"
1658
+ )
1659
+ event.Skip()
1660
+
1661
+ def set_raster_combo(self):
1662
+ idx = -1
1663
+ if self.operation is not None and self.operation.raster_direction is not None:
1664
+ try:
1665
+ idx = self.raster_methods.index(self.operation.raster_direction)
1666
+ except ValueError:
1667
+ idx = 0
1668
+ self.combo_raster_direction.SetSelection(idx)
1669
+
1670
+ def on_raster_help(self, event):
1671
+ unsupported = ()
1672
+ if hasattr(self.context.device, "get_raster_instructions"):
1673
+ instructions = self.context.device.get_raster_instructions()
1674
+ unsupported = instructions.get("unsupported_opt", ())
1675
+
1676
+ inform = {
1677
+ RASTER_T2B: _("- Top To Bottom: follows the picture line by line starting at the top"),
1678
+ RASTER_B2T: _("- Bottom To Top: follows the picture line by line starting at the bottom"),
1679
+ RASTER_R2L: _("- Right To Left: follows the picture column by column starting at the right side"),
1680
+ RASTER_L2R: _("- Left To Right: follows the picture column by column starting at the left side"),
1681
+ RASTER_HATCH: _("- Crosshatch: Makes two passes: one horizontally then another one vertically"),
1682
+ RASTER_GREEDY_H:
1683
+ _("- Greedy horizontal: Instead of following a complete line,") + "\n" +
1684
+ _(" this will choose the nearest to be drawn segment,") + "\n" +
1685
+ _(" lasering these segments with vertical lines.") + "\n" +
1686
+ _(" Usually much faster on an image with a lot of white pixels."),
1687
+ RASTER_GREEDY_V:
1688
+ _("- Greedy vertical: Instead of following a complete line,") + "\n" +
1689
+ _(" this will choose the nearest to be drawn segment,") + "\n" +
1690
+ _(" lasering these segments with vertical lines.") + "\n" +
1691
+ _(" Usually much faster on an image with a lot of white pixels."),
1692
+ RASTER_CROSSOVER:
1693
+ _("- Crossover: Sweeping over the image drawing first all lines") + "\n" +
1694
+ _(" with a majority of black pixels and then drawing the columns") + "\n" +
1695
+ _(" where we have a majority.") + "\n" +
1696
+ _(" Usually much faster on an image with a lot of white pixels."),
1697
+ RASTER_SPIRAL:
1698
+ _("- Spiral: Starting in the center spiralling outwards"),
1699
+ }
1700
+ lines = [_("You can choose from the following modes to laser an image:")]
1701
+ lines.extend( [info for key, info in inform.items() if key not in unsupported] )
1702
+
1703
+ message = "\n".join(lines)
1704
+ wx.MessageBox(
1705
+ caption=_("Help"),
1706
+ message=message,
1707
+ style=wx.OK|wx.ICON_INFORMATION
1708
+ )
1709
+
1710
+ # end of class RasterSettingsPanel
1711
+
1712
+
1713
+ class DwellSettingsPanel(wx.Panel):
1714
+ def __init__(self, *args, context=None, node=None, **kwds):
1715
+ # begin wxGlade: PassesPanel.__init__
1716
+ kwds["style"] = kwds.get("style", 0)
1717
+ wx.Panel.__init__(self, *args, **kwds)
1718
+ self.context = context
1719
+ self.operation = node
1720
+
1721
+ sizer_passes = StaticBoxSizer(
1722
+ self, wx.ID_ANY, _("Dwell Time: (ms)"), wx.HORIZONTAL
1723
+ )
1724
+
1725
+ self.text_dwelltime = TextCtrl(
1726
+ self,
1727
+ wx.ID_ANY,
1728
+ "1.0",
1729
+ limited=True,
1730
+ style=wx.TE_PROCESS_ENTER,
1731
+ check="float",
1732
+ )
1733
+ self.text_dwelltime.SetToolTip(
1734
+ _("Dwell time (ms) at each location in the sequence")
1735
+ )
1736
+ sizer_passes.Add(self.text_dwelltime, 1, wx.EXPAND, 0)
1737
+
1738
+ self.SetSizer(sizer_passes)
1739
+
1740
+ self.Layout()
1741
+
1742
+ self.text_dwelltime.SetActionRoutine(self.on_text_dwelltime)
1743
+ # end wxGlade
1744
+
1745
+ def pane_hide(self):
1746
+ pass
1747
+
1748
+ def pane_show(self):
1749
+ pass
1750
+
1751
+ def accepts(self, node):
1752
+ return node.type in ("op dots",)
1753
+
1754
+ def set_widgets(self, node):
1755
+ self.operation = node
1756
+ if self.operation is None or not self.accepts(node):
1757
+ self.Hide()
1758
+ return
1759
+ set_ctrl_value(self.text_dwelltime, str(self.operation.dwell_time))
1760
+ self.Show()
1761
+
1762
+ def on_text_dwelltime(self):
1763
+ try:
1764
+ value = float(self.text_dwelltime.GetValue())
1765
+ if self.operation.dwell_time != value:
1766
+ self.operation.dwell_time = value
1767
+ self.context.elements.signal(
1768
+ "element_property_reload", self.operation, "text_dwell"
1769
+ )
1770
+ except ValueError:
1771
+ pass
1772
+
1773
+
1774
+ # end of class PassesPanel
1775
+
1776
+
1777
+ class ParameterPanel(ScrolledPanel):
1778
+ name = _("Properties")
1779
+ priority = -1
1780
+
1781
+ def __init__(self, *args, context=None, node=None, **kwds):
1782
+ # begin wxGlade: ParameterPanel.__init__
1783
+ kwds["style"] = kwds.get("style", 0)
1784
+ ScrolledPanel.__init__(self, *args, **kwds)
1785
+ self.context = context
1786
+ self.operation = node
1787
+ self.panels = []
1788
+ self.SetHelpText("operationproperty")
1789
+
1790
+ param_sizer = wx.BoxSizer(wx.VERTICAL)
1791
+
1792
+ self.id_panel = IdPanel(
1793
+ self,
1794
+ wx.ID_ANY,
1795
+ context=context,
1796
+ node=node,
1797
+ showid=True,
1798
+ )
1799
+ param_sizer.Add(self.id_panel, 0, wx.EXPAND, 0)
1800
+ self.panels.append(self.id_panel)
1801
+
1802
+ self.layer_panel = LayerSettingPanel(
1803
+ self, wx.ID_ANY, context=context, node=node
1804
+ )
1805
+ param_sizer.Add(self.layer_panel, 0, wx.EXPAND, 0)
1806
+ self.panels.append(self.layer_panel)
1807
+
1808
+ self.speedppi_panel = SpeedPpiPanel(self, wx.ID_ANY, context=context, node=node)
1809
+ param_sizer.Add(self.speedppi_panel, 0, wx.EXPAND, 0)
1810
+ self.panels.append(self.speedppi_panel)
1811
+
1812
+ self.passes_panel = PassesPanel(self, wx.ID_ANY, context=context, node=node)
1813
+ param_sizer.Add(self.passes_panel, 0, wx.EXPAND, 0)
1814
+ self.panels.append(self.passes_panel)
1815
+
1816
+ self.raster_panel = RasterSettingsPanel(
1817
+ self, wx.ID_ANY, context=context, node=node
1818
+ )
1819
+ param_sizer.Add(self.raster_panel, 1, wx.EXPAND, 0)
1820
+ self.panels.append(self.raster_panel)
1821
+
1822
+ self.dwell_panel = DwellSettingsPanel(
1823
+ self, wx.ID_ANY, context=context, node=node
1824
+ )
1825
+ param_sizer.Add(self.dwell_panel, 0, wx.EXPAND, 0)
1826
+ self.panels.append(self.dwell_panel)
1827
+
1828
+ self.info_panel = InfoPanel(self, wx.ID_ANY, context=context, node=node)
1829
+ param_sizer.Add(self.info_panel, 0, wx.EXPAND, 0)
1830
+ self.panels.append(self.info_panel)
1831
+
1832
+ self.SetSizer(param_sizer)
1833
+
1834
+ self.Layout()
1835
+ # end wxGlade
1836
+
1837
+ @signal_listener("power_percent")
1838
+ @signal_listener("speed_min")
1839
+ @lookup_listener("service/device/active")
1840
+ def on_device_update(self, *args):
1841
+ self.speedppi_panel.on_device_update()
1842
+
1843
+ @signal_listener("element_property_reload")
1844
+ def on_element_property_reload(self, origin=None, *args):
1845
+ # Is this something I should care about?
1846
+ # element_property_reload provides a list of nodes that are affected
1847
+ # if self.operation isn't one of them, then we just let it slip
1848
+ for_me = False
1849
+ if len(args) > 0:
1850
+ element = args[0]
1851
+ if isinstance(element, (tuple, list)):
1852
+ for node in element:
1853
+ if node == self.operation:
1854
+ for_me = True
1855
+ break
1856
+ elif self.operation == element:
1857
+ for_me = True
1858
+ if not for_me:
1859
+ return
1860
+
1861
+ # if origin is None:
1862
+ # print ("EPR called with no origin")
1863
+ # else:
1864
+ # print ("EPR called from:", args)
1865
+ try:
1866
+ self.raster_panel.panel_start.on_element_property_reload(*args)
1867
+ except AttributeError:
1868
+ pass
1869
+ self.set_widgets(self.operation)
1870
+ self.Layout()
1871
+
1872
+ def set_widgets(self, node):
1873
+ try:
1874
+ self.operation = node
1875
+ for panel in self.panels:
1876
+ panel.set_widgets(node)
1877
+ except RuntimeError:
1878
+ # Already deleted
1879
+ return
1880
+
1881
+ def pane_hide(self):
1882
+ for panel in self.panels:
1883
+ panel.pane_hide()
1884
+
1885
+ def pane_show(self):
1886
+ for panel in self.panels:
1887
+ panel.pane_show()
1888
+
1889
+
1890
+ # end of class ParameterPanel