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

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