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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1194 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1858 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4595 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4315 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +934 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/testcases.py +105 -0
  66. meerk40t/core/elements/trace.py +651 -563
  67. meerk40t/core/elements/tree_commands.py +415 -409
  68. meerk40t/core/elements/undo_redo.py +116 -58
  69. meerk40t/core/elements/wordlist.py +319 -200
  70. meerk40t/core/exceptions.py +9 -9
  71. meerk40t/core/laserjob.py +220 -220
  72. meerk40t/core/logging.py +63 -63
  73. meerk40t/core/node/blobnode.py +83 -86
  74. meerk40t/core/node/bootstrap.py +105 -103
  75. meerk40t/core/node/branch_elems.py +40 -31
  76. meerk40t/core/node/branch_ops.py +45 -38
  77. meerk40t/core/node/branch_regmark.py +48 -41
  78. meerk40t/core/node/cutnode.py +29 -32
  79. meerk40t/core/node/effect_hatch.py +375 -257
  80. meerk40t/core/node/effect_warp.py +398 -0
  81. meerk40t/core/node/effect_wobble.py +441 -309
  82. meerk40t/core/node/elem_ellipse.py +404 -309
  83. meerk40t/core/node/elem_image.py +1082 -801
  84. meerk40t/core/node/elem_line.py +358 -292
  85. meerk40t/core/node/elem_path.py +259 -201
  86. meerk40t/core/node/elem_point.py +129 -102
  87. meerk40t/core/node/elem_polyline.py +310 -246
  88. meerk40t/core/node/elem_rect.py +376 -286
  89. meerk40t/core/node/elem_text.py +445 -418
  90. meerk40t/core/node/filenode.py +59 -40
  91. meerk40t/core/node/groupnode.py +138 -74
  92. meerk40t/core/node/image_processed.py +777 -766
  93. meerk40t/core/node/image_raster.py +156 -113
  94. meerk40t/core/node/layernode.py +31 -31
  95. meerk40t/core/node/mixins.py +135 -107
  96. meerk40t/core/node/node.py +1427 -1304
  97. meerk40t/core/node/nutils.py +117 -114
  98. meerk40t/core/node/op_cut.py +463 -335
  99. meerk40t/core/node/op_dots.py +296 -251
  100. meerk40t/core/node/op_engrave.py +414 -311
  101. meerk40t/core/node/op_image.py +755 -369
  102. meerk40t/core/node/op_raster.py +787 -522
  103. meerk40t/core/node/place_current.py +37 -40
  104. meerk40t/core/node/place_point.py +329 -126
  105. meerk40t/core/node/refnode.py +58 -47
  106. meerk40t/core/node/rootnode.py +225 -219
  107. meerk40t/core/node/util_console.py +48 -48
  108. meerk40t/core/node/util_goto.py +84 -65
  109. meerk40t/core/node/util_home.py +61 -61
  110. meerk40t/core/node/util_input.py +102 -102
  111. meerk40t/core/node/util_output.py +102 -102
  112. meerk40t/core/node/util_wait.py +65 -65
  113. meerk40t/core/parameters.py +709 -707
  114. meerk40t/core/planner.py +875 -785
  115. meerk40t/core/plotplanner.py +656 -652
  116. meerk40t/core/space.py +120 -113
  117. meerk40t/core/spoolers.py +706 -705
  118. meerk40t/core/svg_io.py +1836 -1549
  119. meerk40t/core/treeop.py +534 -445
  120. meerk40t/core/undos.py +278 -124
  121. meerk40t/core/units.py +784 -680
  122. meerk40t/core/view.py +393 -322
  123. meerk40t/core/webhelp.py +62 -62
  124. meerk40t/core/wordlist.py +513 -504
  125. meerk40t/cylinder/cylinder.py +247 -0
  126. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  127. meerk40t/cylinder/gui/gui.py +24 -0
  128. meerk40t/device/__init__.py +1 -1
  129. meerk40t/device/basedevice.py +322 -123
  130. meerk40t/device/devicechoices.py +50 -0
  131. meerk40t/device/dummydevice.py +163 -128
  132. meerk40t/device/gui/defaultactions.py +618 -602
  133. meerk40t/device/gui/effectspanel.py +114 -0
  134. meerk40t/device/gui/formatterpanel.py +253 -290
  135. meerk40t/device/gui/warningpanel.py +337 -260
  136. meerk40t/device/mixins.py +13 -13
  137. meerk40t/dxf/__init__.py +1 -1
  138. meerk40t/dxf/dxf_io.py +766 -554
  139. meerk40t/dxf/plugin.py +47 -35
  140. meerk40t/external_plugins.py +79 -79
  141. meerk40t/external_plugins_build.py +28 -28
  142. meerk40t/extra/cag.py +112 -116
  143. meerk40t/extra/coolant.py +403 -0
  144. meerk40t/extra/encode_detect.py +204 -0
  145. meerk40t/extra/ezd.py +1165 -1165
  146. meerk40t/extra/hershey.py +834 -340
  147. meerk40t/extra/imageactions.py +322 -316
  148. meerk40t/extra/inkscape.py +628 -622
  149. meerk40t/extra/lbrn.py +424 -424
  150. meerk40t/extra/outerworld.py +283 -0
  151. meerk40t/extra/param_functions.py +1542 -1556
  152. meerk40t/extra/potrace.py +257 -253
  153. meerk40t/extra/serial_exchange.py +118 -0
  154. meerk40t/extra/updater.py +602 -453
  155. meerk40t/extra/vectrace.py +147 -146
  156. meerk40t/extra/winsleep.py +83 -83
  157. meerk40t/extra/xcs_reader.py +597 -0
  158. meerk40t/fill/fills.py +781 -335
  159. meerk40t/fill/patternfill.py +1061 -1061
  160. meerk40t/fill/patterns.py +614 -567
  161. meerk40t/grbl/control.py +87 -87
  162. meerk40t/grbl/controller.py +990 -903
  163. meerk40t/grbl/device.py +1084 -768
  164. meerk40t/grbl/driver.py +989 -771
  165. meerk40t/grbl/emulator.py +532 -497
  166. meerk40t/grbl/gcodejob.py +783 -767
  167. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  168. meerk40t/grbl/gui/grblcontroller.py +485 -271
  169. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  170. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  171. meerk40t/grbl/gui/gui.py +147 -116
  172. meerk40t/grbl/interpreter.py +44 -44
  173. meerk40t/grbl/loader.py +22 -22
  174. meerk40t/grbl/mock_connection.py +56 -56
  175. meerk40t/grbl/plugin.py +294 -264
  176. meerk40t/grbl/serial_connection.py +93 -88
  177. meerk40t/grbl/tcp_connection.py +81 -79
  178. meerk40t/grbl/ws_connection.py +112 -0
  179. meerk40t/gui/__init__.py +1 -1
  180. meerk40t/gui/about.py +2042 -296
  181. meerk40t/gui/alignment.py +1644 -1608
  182. meerk40t/gui/autoexec.py +199 -0
  183. meerk40t/gui/basicops.py +791 -670
  184. meerk40t/gui/bufferview.py +77 -71
  185. meerk40t/gui/busy.py +232 -133
  186. meerk40t/gui/choicepropertypanel.py +1662 -1469
  187. meerk40t/gui/consolepanel.py +706 -542
  188. meerk40t/gui/devicepanel.py +687 -581
  189. meerk40t/gui/dialogoptions.py +110 -107
  190. meerk40t/gui/executejob.py +316 -306
  191. meerk40t/gui/fonts.py +90 -90
  192. meerk40t/gui/functionwrapper.py +252 -0
  193. meerk40t/gui/gui_mixins.py +729 -0
  194. meerk40t/gui/guicolors.py +205 -182
  195. meerk40t/gui/help_assets/help_assets.py +218 -201
  196. meerk40t/gui/helper.py +154 -0
  197. meerk40t/gui/hersheymanager.py +1440 -846
  198. meerk40t/gui/icons.py +3422 -2747
  199. meerk40t/gui/imagesplitter.py +555 -508
  200. meerk40t/gui/keymap.py +354 -344
  201. meerk40t/gui/laserpanel.py +897 -806
  202. meerk40t/gui/laserrender.py +1470 -1232
  203. meerk40t/gui/lasertoolpanel.py +805 -793
  204. meerk40t/gui/magnetoptions.py +436 -0
  205. meerk40t/gui/materialmanager.py +2944 -0
  206. meerk40t/gui/materialtest.py +1722 -1694
  207. meerk40t/gui/mkdebug.py +646 -359
  208. meerk40t/gui/mwindow.py +163 -140
  209. meerk40t/gui/navigationpanels.py +2605 -2467
  210. meerk40t/gui/notes.py +143 -142
  211. meerk40t/gui/opassignment.py +414 -410
  212. meerk40t/gui/operation_info.py +310 -299
  213. meerk40t/gui/plugin.py +500 -328
  214. meerk40t/gui/position.py +714 -669
  215. meerk40t/gui/preferences.py +901 -650
  216. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  217. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  218. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  219. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  220. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  221. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  222. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  223. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  224. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  225. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  226. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  227. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  228. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  229. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  230. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  231. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  232. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  233. meerk40t/gui/propertypanels/textproperty.py +770 -755
  234. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  235. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  236. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  237. meerk40t/gui/ribbon.py +2471 -2210
  238. meerk40t/gui/scene/scene.py +1100 -1051
  239. meerk40t/gui/scene/sceneconst.py +22 -22
  240. meerk40t/gui/scene/scenepanel.py +439 -349
  241. meerk40t/gui/scene/scenespacewidget.py +365 -365
  242. meerk40t/gui/scene/widget.py +518 -505
  243. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  244. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  245. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  246. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  247. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  248. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  249. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  250. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  251. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  252. meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
  253. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  254. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  255. meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
  256. meerk40t/gui/simpleui.py +362 -333
  257. meerk40t/gui/simulation.py +2451 -2094
  258. meerk40t/gui/snapoptions.py +208 -203
  259. meerk40t/gui/spoolerpanel.py +1227 -1180
  260. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  261. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  262. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  263. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  264. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  265. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  266. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  267. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  268. meerk40t/gui/themes.py +200 -78
  269. meerk40t/gui/tips.py +590 -0
  270. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  271. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  272. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  273. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  274. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  275. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  276. meerk40t/gui/toolwidgets/toolline.py +39 -144
  277. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  278. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  279. meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
  280. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  281. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  282. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  283. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  284. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  285. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  286. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  287. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  288. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  289. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  290. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  291. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  292. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  293. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  294. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  295. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  296. meerk40t/gui/usbconnect.py +98 -91
  297. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  298. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  299. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  300. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  301. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  302. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  303. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  304. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  305. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  306. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  307. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  308. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  309. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  310. meerk40t/gui/wordlisteditor.py +985 -931
  311. meerk40t/gui/wxmeerk40t.py +1447 -1169
  312. meerk40t/gui/wxmmain.py +5644 -4112
  313. meerk40t/gui/wxmribbon.py +1591 -1076
  314. meerk40t/gui/wxmscene.py +1631 -1453
  315. meerk40t/gui/wxmtree.py +2416 -2089
  316. meerk40t/gui/wxutils.py +1769 -1099
  317. meerk40t/gui/zmatrix.py +102 -102
  318. meerk40t/image/__init__.py +1 -1
  319. meerk40t/image/dither.py +429 -0
  320. meerk40t/image/imagetools.py +2793 -2269
  321. meerk40t/internal_plugins.py +150 -130
  322. meerk40t/kernel/__init__.py +63 -12
  323. meerk40t/kernel/channel.py +259 -212
  324. meerk40t/kernel/context.py +538 -538
  325. meerk40t/kernel/exceptions.py +41 -41
  326. meerk40t/kernel/functions.py +463 -414
  327. meerk40t/kernel/jobs.py +100 -100
  328. meerk40t/kernel/kernel.py +3828 -3571
  329. meerk40t/kernel/lifecycles.py +71 -71
  330. meerk40t/kernel/module.py +49 -49
  331. meerk40t/kernel/service.py +147 -147
  332. meerk40t/kernel/settings.py +383 -343
  333. meerk40t/lihuiyu/controller.py +883 -876
  334. meerk40t/lihuiyu/device.py +1181 -1069
  335. meerk40t/lihuiyu/driver.py +1466 -1372
  336. meerk40t/lihuiyu/gui/gui.py +127 -106
  337. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  338. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  339. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  340. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  341. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  342. meerk40t/lihuiyu/interpreter.py +53 -53
  343. meerk40t/lihuiyu/laserspeed.py +450 -450
  344. meerk40t/lihuiyu/loader.py +90 -90
  345. meerk40t/lihuiyu/parser.py +404 -404
  346. meerk40t/lihuiyu/plugin.py +101 -102
  347. meerk40t/lihuiyu/tcp_connection.py +111 -109
  348. meerk40t/main.py +231 -165
  349. meerk40t/moshi/builder.py +788 -781
  350. meerk40t/moshi/controller.py +505 -499
  351. meerk40t/moshi/device.py +495 -442
  352. meerk40t/moshi/driver.py +862 -696
  353. meerk40t/moshi/gui/gui.py +78 -76
  354. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  355. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  356. meerk40t/moshi/plugin.py +43 -43
  357. meerk40t/network/console_server.py +140 -57
  358. meerk40t/network/kernelserver.py +10 -9
  359. meerk40t/network/tcp_server.py +142 -140
  360. meerk40t/network/udp_server.py +103 -77
  361. meerk40t/network/web_server.py +404 -0
  362. meerk40t/newly/controller.py +1158 -1144
  363. meerk40t/newly/device.py +874 -732
  364. meerk40t/newly/driver.py +540 -412
  365. meerk40t/newly/gui/gui.py +219 -188
  366. meerk40t/newly/gui/newlyconfig.py +116 -101
  367. meerk40t/newly/gui/newlycontroller.py +193 -186
  368. meerk40t/newly/gui/operationproperties.py +51 -51
  369. meerk40t/newly/mock_connection.py +82 -82
  370. meerk40t/newly/newly_params.py +56 -56
  371. meerk40t/newly/plugin.py +1214 -1246
  372. meerk40t/newly/usb_connection.py +322 -322
  373. meerk40t/rotary/gui/gui.py +52 -46
  374. meerk40t/rotary/gui/rotarysettings.py +240 -232
  375. meerk40t/rotary/rotary.py +202 -98
  376. meerk40t/ruida/control.py +291 -91
  377. meerk40t/ruida/controller.py +138 -1088
  378. meerk40t/ruida/device.py +676 -231
  379. meerk40t/ruida/driver.py +534 -472
  380. meerk40t/ruida/emulator.py +1494 -1491
  381. meerk40t/ruida/exceptions.py +4 -4
  382. meerk40t/ruida/gui/gui.py +71 -76
  383. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  384. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  385. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  386. meerk40t/ruida/loader.py +54 -52
  387. meerk40t/ruida/mock_connection.py +57 -109
  388. meerk40t/ruida/plugin.py +124 -87
  389. meerk40t/ruida/rdjob.py +2084 -945
  390. meerk40t/ruida/serial_connection.py +116 -0
  391. meerk40t/ruida/tcp_connection.py +146 -0
  392. meerk40t/ruida/udp_connection.py +73 -0
  393. meerk40t/svgelements.py +9671 -9669
  394. meerk40t/tools/driver_to_path.py +584 -579
  395. meerk40t/tools/geomstr.py +5583 -4680
  396. meerk40t/tools/jhfparser.py +357 -292
  397. meerk40t/tools/kerftest.py +904 -890
  398. meerk40t/tools/livinghinges.py +1168 -1033
  399. meerk40t/tools/pathtools.py +987 -949
  400. meerk40t/tools/pmatrix.py +234 -0
  401. meerk40t/tools/pointfinder.py +942 -942
  402. meerk40t/tools/polybool.py +941 -940
  403. meerk40t/tools/rasterplotter.py +1660 -547
  404. meerk40t/tools/shxparser.py +1047 -901
  405. meerk40t/tools/ttfparser.py +726 -446
  406. meerk40t/tools/zinglplotter.py +595 -593
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
  408. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
  409. meerk40t-0.9.7020.dist-info/RECORD +446 -0
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
  412. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
  413. meerk40t/balormk/elementlightjob.py +0 -159
  414. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  415. test/bootstrap.py +0 -63
  416. test/test_cli.py +0 -12
  417. test/test_core_cutcode.py +0 -418
  418. test/test_core_elements.py +0 -144
  419. test/test_core_plotplanner.py +0 -397
  420. test/test_core_viewports.py +0 -312
  421. test/test_drivers_grbl.py +0 -108
  422. test/test_drivers_lihuiyu.py +0 -443
  423. test/test_drivers_newly.py +0 -113
  424. test/test_element_degenerate_points.py +0 -43
  425. test/test_elements_classify.py +0 -97
  426. test/test_elements_penbox.py +0 -22
  427. test/test_file_svg.py +0 -176
  428. test/test_fill.py +0 -155
  429. test/test_geomstr.py +0 -1523
  430. test/test_geomstr_nodes.py +0 -18
  431. test/test_imagetools_actualize.py +0 -306
  432. test/test_imagetools_wizard.py +0 -258
  433. test/test_kernel.py +0 -200
  434. test/test_laser_speeds.py +0 -3303
  435. test/test_length.py +0 -57
  436. test/test_lifecycle.py +0 -66
  437. test/test_operations.py +0 -251
  438. test/test_operations_hatch.py +0 -57
  439. test/test_ruida.py +0 -19
  440. test/test_spooler.py +0 -22
  441. test/test_tools_rasterplotter.py +0 -29
  442. test/test_wobble.py +0 -133
  443. test/test_zingl.py +0 -124
  444. {test → meerk40t/cylinder}/__init__.py +0 -0
  445. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  446. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
@@ -1,1095 +1,1241 @@
1
- import platform
2
-
3
- import wx
4
- from wx import aui
5
-
6
- from meerk40t.gui.icons import (
7
- get_default_icon_size,
8
- icons8_camera,
9
- icons8_connected,
10
- icons8_detective,
11
- icons8_image_in_frame,
12
- )
13
- from meerk40t.gui.mwindow import MWindow
14
- from meerk40t.gui.scene.sceneconst import (
15
- HITCHAIN_HIT,
16
- RESPONSE_ABORT,
17
- RESPONSE_CHAIN,
18
- RESPONSE_CONSUME,
19
- )
20
- from meerk40t.gui.scene.scenepanel import ScenePanel
21
- from meerk40t.gui.scene.widget import Widget
22
- from meerk40t.kernel import Job, signal_listener
23
- from meerk40t.svgelements import Color
24
-
25
- _ = wx.GetTranslation
26
-
27
- CORNER_SIZE = 25
28
-
29
-
30
- def register_panel_camera(window, context):
31
- for index in range(5):
32
- panel = CameraPanel(
33
- window, wx.ID_ANY, context=context, gui=window, index=index, pane=True
34
- )
35
- pane = (
36
- aui.AuiPaneInfo()
37
- .Left()
38
- .MinSize(200, 150)
39
- .FloatingSize(640, 480)
40
- .Caption(_("Camera {index}").format(index=index))
41
- .Name(f"camera{index}")
42
- .CaptionVisible(not context.pane_lock)
43
- .Hide()
44
- )
45
- pane.dock_proportion = 200
46
- pane.control = panel
47
- pane.submenu = "_60_" + _("Camera")
48
- window.on_pane_create(pane)
49
- context.register(f"pane/camera{index}", pane)
50
-
51
-
52
- class CameraPanel(wx.Panel, Job):
53
- def __init__(
54
- self, *args, context=None, gui=None, index: int = 0, pane=False, **kwds
55
- ):
56
- kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
57
- wx.Panel.__init__(self, *args, **kwds)
58
- self.gui = gui
59
- self.context = context
60
- self.index = index
61
- self.pane = pane
62
-
63
- if pane:
64
- job_name = f"CamPane{self.index}"
65
- else:
66
- job_name = f"Camera{self.index}"
67
- Job.__init__(self, job_name=job_name)
68
- self.process = self.update_camera_frame
69
- self.run_main = True
70
-
71
- self.context(f"camera{self.index}\n") # command activates Camera service
72
- self.camera = self.context.get_context(f"camera/{self.index}")
73
- self.camera.setting(int, "frames_per_second", 30)
74
- # camera service location.
75
- self.last_frame_index = -1
76
-
77
- if not pane:
78
- self.button_update = wx.BitmapButton(
79
- self, wx.ID_ANY, icons8_camera.GetBitmap(resize=get_default_icon_size())
80
- )
81
- self.button_export = wx.BitmapButton(
82
- self,
83
- wx.ID_ANY,
84
- icons8_image_in_frame.GetBitmap(resize=get_default_icon_size()),
85
- )
86
- self.button_reconnect = wx.BitmapButton(
87
- self,
88
- wx.ID_ANY,
89
- icons8_connected.GetBitmap(resize=get_default_icon_size()),
90
- )
91
- self.check_fisheye = wx.CheckBox(self, wx.ID_ANY, _("Correct Fisheye"))
92
- self.check_perspective = wx.CheckBox(
93
- self, wx.ID_ANY, _("Correct Perspective")
94
- )
95
- self.slider_fps = wx.Slider(
96
- self,
97
- wx.ID_ANY,
98
- 30,
99
- 0,
100
- 120,
101
- style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS,
102
- )
103
- self.button_detect = wx.BitmapButton(
104
- self,
105
- wx.ID_ANY,
106
- icons8_detective.GetBitmap(resize=get_default_icon_size()),
107
- )
108
- scene_name = f"Camera{self.index}"
109
- else:
110
- scene_name = f"CamPaneScene{self.index}"
111
-
112
- self.display_camera = ScenePanel(
113
- self.camera,
114
- self,
115
- scene_name=scene_name,
116
- style=wx.EXPAND | wx.WANTS_CHARS,
117
- )
118
- self.widget_scene = self.display_camera.scene
119
- # Allow Scene update from now on (are suppressed by default during startup phase)
120
- self.widget_scene.suppress_changes = False
121
-
122
- # end wxGlade
123
- sizer_main = wx.BoxSizer(wx.VERTICAL)
124
- if not pane:
125
- self.button_update.SetToolTip(_("Update Image"))
126
- self.button_update.SetSize(self.button_update.GetBestSize())
127
- self.button_export.SetToolTip(_("Export Snapshot"))
128
- self.button_export.SetSize(self.button_export.GetBestSize())
129
- self.button_reconnect.SetToolTip(_("Reconnect Camera"))
130
- self.button_reconnect.SetSize(self.button_reconnect.GetBestSize())
131
- self.button_detect.SetToolTip(
132
- _(
133
- "Detect Distortions/Calibration\n"
134
- "You need to print a 9x6 checkerboard pattern from OpenCV\n"
135
- "It should be flat and visible in the camera."
136
- )
137
- )
138
- self.button_detect.SetSize(self.button_detect.GetBestSize())
139
- self.slider_fps.SetToolTip(
140
- _(
141
- "Set the camera frames per second. A value of 0 means a frame every 5 seconds."
142
- )
143
- )
144
- self.check_fisheye.SetToolTip(
145
- _("Corrects Fisheye lensing, must be trained with checkerboard image.")
146
- )
147
- self.check_perspective.SetToolTip(
148
- _(
149
- "The four marker locations (in scene when unchecked) are transformed into corners of a regular square shape."
150
- )
151
- )
152
-
153
- sizer_controls = wx.BoxSizer(wx.HORIZONTAL)
154
- sizer_checkboxes = wx.BoxSizer(wx.VERTICAL)
155
- sizer_controls.Add(self.button_update, 0, 0, 0)
156
- sizer_controls.Add(self.button_export, 0, 0, 0)
157
- sizer_controls.Add(self.button_reconnect, 0, 0, 0)
158
- sizer_checkboxes.Add(self.check_fisheye, 0, 0, 0)
159
- sizer_checkboxes.Add(self.check_perspective, 0, 0, 0)
160
- sizer_controls.Add(sizer_checkboxes, 1, wx.EXPAND, 0)
161
- sizer_controls.Add(self.slider_fps, 1, wx.EXPAND, 0)
162
- sizer_controls.Add(self.button_detect, 0, 0, 0)
163
- sizer_main.Add(sizer_controls, 1, wx.EXPAND, 0)
164
- self.Bind(wx.EVT_BUTTON, self.on_button_update, self.button_update)
165
- self.Bind(wx.EVT_BUTTON, self.on_button_export, self.button_export)
166
- self.Bind(wx.EVT_BUTTON, self.on_button_reconnect, self.button_reconnect)
167
- self.Bind(wx.EVT_CHECKBOX, self.on_check_fisheye, self.check_fisheye)
168
- self.Bind(
169
- wx.EVT_CHECKBOX, self.on_check_perspective, self.check_perspective
170
- )
171
- self.Bind(wx.EVT_SLIDER, self.on_slider_fps, self.slider_fps)
172
- self.Bind(wx.EVT_BUTTON, self.on_button_detect, self.button_detect)
173
- sizer_main.Add(self.display_camera, 10, wx.EXPAND, 0)
174
- self.SetSizer(sizer_main)
175
- self.Layout()
176
-
177
- self.image_width = -1
178
- self.image_height = -1
179
- self.frame_bitmap = None
180
-
181
- self.SetDoubleBuffered(True)
182
- # end wxGlade
183
-
184
- if not pane:
185
- self.check_fisheye.SetValue(self.camera.correction_fisheye)
186
- self.check_perspective.SetValue(self.camera.correction_perspective)
187
- self.slider_fps.SetValue(self.camera.frames_per_second)
188
-
189
- self.on_fps_change(self.camera.path)
190
-
191
- self.widget_scene.widget_root.set_aspect(self.camera.aspect)
192
-
193
- self.widget_scene.background_brush = wx.Brush(wx.WHITE)
194
- self.widget_scene.add_scenewidget(CamSceneWidget(self.widget_scene, self))
195
- self.widget_scene.add_scenewidget(CamImageWidget(self.widget_scene, self))
196
- self.widget_scene.add_interfacewidget(
197
- CamInterfaceWidget(self.widget_scene, self)
198
- )
199
- # Allow Scene update from now on (are suppressed by default during startup phase)
200
- self.widget_scene.suppress_changes = False
201
-
202
- def pane_show(self, *args):
203
- if platform.system() == "Darwin" and not hasattr(self.camera, "_first"):
204
- self.camera(f"camera{self.index} start -t 1\n")
205
- self.camera._first = False
206
- else:
207
- self.camera(f"camera{self.index} start\n")
208
- self.camera.schedule(self)
209
- self.display_camera.start_scene()
210
- # This listener is because you can have frames and windows and both need to care about the slider.
211
- self.camera.listen("camera;fps", self.on_fps_change)
212
- self.camera.listen("camera;stopped", self.on_camera_stop)
213
- self.camera.gui = self
214
- self.camera("camera focus -5% -5% 105% 105%\n")
215
-
216
- def pane_hide(self, *args):
217
- self.camera(f"camera{self.index} stop\n")
218
- self.camera.unschedule(self)
219
- self.display_camera.stop_scene()
220
- if not self.pane:
221
- self.camera.close(f"Camera{str(self.index)}")
222
- self.camera.unlisten("camera;fps", self.on_fps_change)
223
- self.camera.unlisten("camera;stopped", self.on_camera_stop)
224
- self.camera.signal("camera;stopped", self.index)
225
- self.camera.gui = None
226
-
227
- def on_camera_stop(self, origin, index):
228
- if index == self.index:
229
- self.camera(f"camera{self.index} start\n")
230
-
231
- def on_fps_change(self, origin, *args):
232
- # Set the camera fps.
233
- if origin != self.camera.path:
234
- # Not this window.
235
- return
236
- camera_fps = self.camera.frames_per_second
237
- if camera_fps == 0:
238
- tick = 5
239
- else:
240
- tick = 1.0 / camera_fps
241
- self.interval = tick
242
- # Set the scene fps if it's needed to support the camera.
243
- scene_fps = self.camera.frames_per_second
244
- if scene_fps < 30:
245
- scene_fps = 30
246
- if self.camera.fps != scene_fps:
247
- self.display_camera.scene.set_fps(scene_fps)
248
-
249
- @signal_listener("refresh_scene")
250
- def on_refresh_scene(self, origin, *args):
251
- self.widget_scene.request_refresh(*args)
252
-
253
- def update_camera_frame(self, event=None):
254
- if self.camera is None:
255
- return
256
-
257
- if self.camera.frame_index == self.last_frame_index:
258
- return
259
- else:
260
- self.last_frame_index = self.camera.frame_index
261
-
262
- frame = self.camera.get_frame()
263
- if frame is None:
264
- return
265
-
266
- self.image_height, self.image_width = frame.shape[:2]
267
- if not self.frame_bitmap:
268
- # Initial set.
269
- self.widget_scene.widget_root.set_view(
270
- 0, 0, self.image_width, self.image_height, self.camera.preserve_aspect
271
- )
272
- self.frame_bitmap = wx.Bitmap.FromBuffer(
273
- self.image_width, self.image_height, frame
274
- )
275
- if self.camera.correction_perspective:
276
- if (
277
- self.camera.width != self.image_width
278
- or self.camera.height != self.image_height
279
- ):
280
- self.image_width = self.camera.width
281
- self.image_height = self.camera.height
282
-
283
- self.widget_scene.request_refresh()
284
-
285
- def reset_perspective(self, event=None):
286
- """
287
- Reset the perspective settings.
288
-
289
- @param event:
290
- @return:
291
- """
292
- self.camera(f"camera{self.index} perspective reset\n")
293
-
294
- def reset_fisheye(self, event=None):
295
- """
296
- Reset the fisheye settings.
297
-
298
- @param event:
299
- @return:
300
- """
301
- self.camera(f"camera{self.index} fisheye reset\n")
302
-
303
- def on_check_perspective(self, event=None):
304
- """
305
- Perspective checked. Turns on/off
306
- @param event:
307
- @return:
308
- """
309
- self.camera.correction_perspective = self.check_perspective.GetValue()
310
-
311
- def on_check_fisheye(self, event=None):
312
- """
313
- Fisheye checked. Turns on/off.
314
- @param event:
315
- @return:
316
- """
317
- self.camera.correction_fisheye = self.check_fisheye.GetValue()
318
-
319
- def on_button_update(self, event=None): # wxGlade: CameraInterface.<event_handler>
320
- """
321
- Button update.
322
-
323
- Sets image background to main scene.
324
-
325
- @param event:
326
- @return:
327
- """
328
- self.camera(f"camera{self.index} background\n")
329
-
330
- def on_button_export(self, event=None): # wxGlade: CameraInterface.<event_handler>
331
- """
332
- Button export.
333
-
334
- Sends an image to the scene as an exported object.
335
- @param event:
336
- @return:
337
- """
338
- self.camera.console(f"camera{self.index} export\n")
339
-
340
- def on_button_reconnect(
341
- self, event=None
342
- ): # wxGlade: CameraInterface.<event_handler>
343
- self.camera.console(f"camera{self.index} stop start\n")
344
-
345
- def on_slider_fps(self, event=None): # wxGlade: CameraInterface.<event_handler>
346
- """
347
- Adjusts the camera FPS.
348
-
349
- If set to 0, this will be a frame each 5 seconds.
350
-
351
- @param event:
352
- @return:
353
- """
354
- self.camera.frames_per_second = self.slider_fps.GetValue()
355
- self.camera.signal("camera;fps", self.camera.frames_per_second)
356
-
357
- def on_button_detect(self, event=None): # wxGlade: CameraInterface.<event_handler>
358
- """
359
- Attempts to locate 6x9 checkerboard pattern for OpenCV to correct the fisheye pattern.
360
-
361
- @param event:
362
- @return:
363
- """
364
- self.camera.console(f"camera{self.index} fisheye capture\n")
365
-
366
- def swap_camera(self, uri):
367
- def swap(event=None):
368
- self.camera(f"camera{self.index} --uri {str(uri)} stop start\n")
369
- self.frame_bitmap = None
370
-
371
- return swap
372
-
373
-
374
- class CamInterfaceWidget(Widget):
375
- def __init__(self, scene, camera):
376
- Widget.__init__(self, scene, all=True)
377
- self.cam = camera
378
-
379
- def process_draw(self, gc: wx.GraphicsContext):
380
- if self.cam.frame_bitmap is None:
381
- font = wx.Font(
382
- 14, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD
383
- )
384
- gc.SetFont(font, wx.BLACK)
385
- if self.cam.camera is None:
386
- gc.DrawText(
387
- _("Camera backend failure...\nCannot attempt camera connection."),
388
- 0,
389
- 0,
390
- )
391
- else:
392
- gc.DrawText(
393
- _("Fetching URI:{uri} Frame...").format(uri=self.cam.camera.uri),
394
- 0,
395
- 0,
396
- )
397
-
398
- def hit(self):
399
- return HITCHAIN_HIT
400
-
401
- def event(self, window_pos=None, space_pos=None, event_type=None, **kwargs):
402
- if event_type == "rightdown":
403
-
404
- def enable_aspect(*args):
405
- self.cam.camera.aspect = not self.cam.camera.aspect
406
- self.scene.widget_root.set_aspect(self.cam.camera.aspect)
407
- self.scene.widget_root.set_view(
408
- 0,
409
- 0,
410
- self.cam.image_width,
411
- self.cam.image_height,
412
- self.cam.camera.preserve_aspect,
413
- )
414
-
415
- def set_aspect(aspect):
416
- def asp(event=None):
417
- self.cam.camera.preserve_aspect = aspect
418
- self.scene.widget_root.set_aspect(self.cam.camera.aspect)
419
- self.scene.widget_root.set_view(
420
- 0,
421
- 0,
422
- self.cam.image_width,
423
- self.cam.image_height,
424
- self.cam.camera.preserve_aspect,
425
- )
426
-
427
- return asp
428
-
429
- menu = wx.Menu()
430
-
431
- item = menu.Append(wx.ID_ANY, _("Update Background"), "")
432
- self.cam.Bind(
433
- wx.EVT_MENU,
434
- lambda e: self.cam.context(f"camera{self.cam.index} background\n"),
435
- id=item.GetId(),
436
- )
437
-
438
- def live_view(c_frames, c_sec):
439
- def runcam(event=None):
440
- ratio = c_sec / c_frames
441
- self.cam.context(
442
- f"timer.updatebg 0 {ratio} camera{self.cam.index} background\n"
443
- )
444
- return
445
-
446
- return runcam
447
-
448
- def live_stop():
449
- self.cam.context("timer.updatebg --off\n")
450
-
451
- submenu = wx.Menu()
452
- menu.AppendSubMenu(submenu, _("...refresh"))
453
- rates = ((2, 1), (1, 1), (1, 2), (1, 5), (1, 10))
454
- for myrate in rates:
455
- rate_frame = myrate[0]
456
- rate_sec = myrate[1]
457
- item = submenu.Append(wx.ID_ANY, f"{rate_frame}x / {rate_sec}sec")
458
- self.cam.Bind(
459
- wx.EVT_MENU,
460
- live_view(rate_frame, rate_sec),
461
- id=item.GetId(),
462
- )
463
- submenu.AppendSeparator()
464
- item = submenu.Append(wx.ID_ANY, "Disable")
465
- self.cam.Bind(
466
- wx.EVT_MENU,
467
- lambda e: live_stop(),
468
- id=item.GetId(),
469
- )
470
- menu.AppendSeparator()
471
- item = menu.Append(wx.ID_ANY, _("Export Snapshot"), "")
472
- self.cam.Bind(
473
- wx.EVT_MENU,
474
- lambda e: self.cam.context(f"camera{self.cam.index} export\n"),
475
- id=item.GetId(),
476
- )
477
-
478
- item = menu.Append(wx.ID_ANY, _("Reconnect Camera"), "")
479
- self.cam.Bind(
480
- wx.EVT_MENU,
481
- lambda e: self.cam.context(f"camera{self.cam.index} stop start\n"),
482
- id=item.GetId(),
483
- )
484
-
485
- item = menu.Append(wx.ID_ANY, _("Stop Camera"), "")
486
- self.cam.Bind(
487
- wx.EVT_MENU,
488
- lambda e: self.cam.context(f"camera{self.cam.index} stop\n"),
489
- id=item.GetId(),
490
- )
491
-
492
- item = menu.Append(wx.ID_ANY, _("Open CameraInterface"), "")
493
- self.cam.Bind(
494
- wx.EVT_MENU,
495
- lambda e: self.cam.context(f"camwin {self.cam.index}\n"),
496
- id=item.GetId(),
497
- )
498
-
499
- menu.AppendSeparator()
500
-
501
- sub_menu = wx.Menu()
502
- center = menu.Append(wx.ID_ANY, _("Aspect"), "", wx.ITEM_CHECK)
503
- if self.cam.camera.aspect:
504
- center.Check(True)
505
- self.cam.Bind(wx.EVT_MENU, enable_aspect, center)
506
- self.cam.Bind(
507
- wx.EVT_MENU,
508
- set_aspect("xMinYMin meet"),
509
- sub_menu.Append(wx.ID_ANY, "xMinYMin meet", "", wx.ITEM_NORMAL),
510
- )
511
- self.cam.Bind(
512
- wx.EVT_MENU,
513
- set_aspect("xMidYMid meet"),
514
- sub_menu.Append(wx.ID_ANY, "xMidYMid meet", "", wx.ITEM_NORMAL),
515
- )
516
- self.cam.Bind(
517
- wx.EVT_MENU,
518
- set_aspect("xMidYMid slice"),
519
- sub_menu.Append(wx.ID_ANY, "xMidYMid slice", "", wx.ITEM_NORMAL),
520
- )
521
- self.cam.Bind(
522
- wx.EVT_MENU,
523
- set_aspect("none"),
524
- sub_menu.Append(wx.ID_ANY, "none", "", wx.ITEM_NORMAL),
525
- )
526
-
527
- menu.Append(
528
- wx.ID_ANY,
529
- _("Preserve: {aspect}").format(aspect=self.cam.camera.preserve_aspect),
530
- sub_menu,
531
- )
532
- menu.AppendSeparator()
533
-
534
- fisheye = menu.Append(wx.ID_ANY, _("Correct Fisheye"), "", wx.ITEM_CHECK)
535
- fisheye.Check(self.cam.camera.correction_fisheye)
536
- self.cam.camera.correction_fisheye = fisheye.IsChecked()
537
-
538
- def check_fisheye(event=None):
539
- self.cam.camera.correction_fisheye = fisheye.IsChecked()
540
-
541
- self.cam.Bind(wx.EVT_MENU, check_fisheye, fisheye)
542
-
543
- perspect = menu.Append(
544
- wx.ID_ANY, _("Correct Perspective"), "", wx.ITEM_CHECK
545
- )
546
- perspect.Check(self.cam.camera.correction_perspective)
547
- self.cam.camera.correction_perspective = perspect.IsChecked()
548
-
549
- def check_perspect(event=None):
550
- self.cam.camera.correction_perspective = perspect.IsChecked()
551
-
552
- self.cam.Bind(wx.EVT_MENU, check_perspect, perspect)
553
- menu.AppendSeparator()
554
- item = menu.Append(wx.ID_ANY, _("Reset Perspective"), "")
555
- self.cam.Bind(
556
- wx.EVT_MENU,
557
- lambda e: self.cam.camera(
558
- f"camera{self.cam.index} perspective reset\n"
559
- ),
560
- id=item.GetId(),
561
- )
562
- item = menu.Append(wx.ID_ANY, _("Reset Fisheye"), "")
563
- self.cam.Bind(
564
- wx.EVT_MENU,
565
- lambda e: self.cam.camera(f"camera{self.cam.index} fisheye reset\n"),
566
- id=item.GetId(),
567
- )
568
- menu.AppendSeparator()
569
-
570
- sub_menu = wx.Menu()
571
- item = sub_menu.Append(wx.ID_ANY, _("URI Manager"), "")
572
- self.cam.Bind(
573
- wx.EVT_MENU,
574
- lambda e: self.cam.context.open(
575
- "window/CameraURI", self.cam, index=self.cam.index
576
- ),
577
- id=item.GetId(),
578
- )
579
-
580
- camera_context = self.cam.context.get_context("camera")
581
- uris = camera_context.setting(list, "uris", [])
582
- for uri in uris:
583
- item = sub_menu.Append(wx.ID_ANY, _("URI: {uri}").format(uri=uri), "")
584
- self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(uri), id=item.GetId())
585
- if sub_menu.MenuItemCount:
586
- sub_menu.AppendSeparator()
587
-
588
- item = sub_menu.Append(
589
- wx.ID_ANY, _("USB {usb_index}").format(usb_index=0), ""
590
- )
591
- self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(0), id=item.GetId())
592
- item = sub_menu.Append(
593
- wx.ID_ANY, _("USB {usb_index}").format(usb_index=1), ""
594
- )
595
- self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(1), id=item.GetId())
596
- item = sub_menu.Append(
597
- wx.ID_ANY, _("USB {usb_index}").format(usb_index=2), ""
598
- )
599
- self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(2), id=item.GetId())
600
- item = sub_menu.Append(
601
- wx.ID_ANY, _("USB {usb_index}").format(usb_index=3), ""
602
- )
603
- self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(3), id=item.GetId())
604
- item = sub_menu.Append(
605
- wx.ID_ANY, _("USB {usb_index}").format(usb_index=4), ""
606
- )
607
- self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(4), id=item.GetId())
608
-
609
- menu.Append(
610
- wx.ID_ANY,
611
- _("Manage URIs"),
612
- sub_menu,
613
- )
614
- if menu.MenuItemCount != 0:
615
- self.cam.PopupMenu(menu)
616
- menu.Destroy()
617
- return RESPONSE_ABORT
618
- if event_type == "doubleclick":
619
- self.cam.context(f"camera{self.cam.index} background\n")
620
- return RESPONSE_CHAIN
621
-
622
-
623
- class CamPerspectiveWidget(Widget):
624
- def __init__(self, scene, camera, index, mid=False):
625
- self.cam = camera
626
- self.mid = mid
627
- self.index = index
628
- half = CORNER_SIZE / 2.0
629
- Widget.__init__(self, scene, -half, -half, half, half)
630
- self.update()
631
- c = Color.distinct(self.index + 2)
632
- self.pen = wx.Pen(wx.Colour(c.red, c.green, c.blue))
633
-
634
- def update(self):
635
- half = CORNER_SIZE / 2.0
636
- pos_x, pos_y = self.cam.camera.perspective[self.index]
637
- self.set_position(pos_x - half, pos_y - half)
638
-
639
- def hit(self):
640
- return HITCHAIN_HIT
641
-
642
- def process_draw(self, gc):
643
- if not self.cam.camera.correction_perspective and not self.cam.camera.aspect:
644
- gc.SetPen(self.pen)
645
- gc.SetBrush(wx.TRANSPARENT_BRUSH)
646
- gc.StrokeLine(
647
- self.left,
648
- self.top + self.height / 2.0,
649
- self.right,
650
- self.bottom - self.height / 2.0,
651
- )
652
- gc.StrokeLine(
653
- self.left + self.width / 2.0,
654
- self.top,
655
- self.right - self.width / 2.0,
656
- self.bottom,
657
- )
658
- gc.DrawEllipse(self.left, self.top, self.width, self.height)
659
-
660
- def event(self, window_pos=None, space_pos=None, event_type=None, **kwargs):
661
- if event_type == "leftdown":
662
- return RESPONSE_CONSUME
663
- if event_type == "move":
664
- self.cam.camera.perspective[self.index][0] += space_pos[4]
665
- self.cam.camera.perspective[self.index][1] += space_pos[5]
666
- if self.parent is not None:
667
- for w in self.parent:
668
- if isinstance(w, CamPerspectiveWidget):
669
- w.update()
670
- self.cam.context.signal("refresh_scene", self.scene.name)
671
- return RESPONSE_CONSUME
672
-
673
-
674
- class CamSceneWidget(Widget):
675
- def __init__(self, scene, camera):
676
- Widget.__init__(self, scene, all=True)
677
- self.cam = camera
678
-
679
- def process_draw(self, gc):
680
- if not self.cam.camera.correction_perspective and not self.cam.camera.aspect:
681
- if self.cam.camera.perspective is not None:
682
- if not len(self):
683
- for i in range(len(self.cam.camera.perspective)):
684
- self.add_widget(
685
- -1, CamPerspectiveWidget(self.scene, self.cam, i, False)
686
- )
687
- gc.SetPen(wx.BLACK_DASHED_PEN)
688
- lines = list(self.cam.camera.perspective)
689
- lines.append(lines[0])
690
- gc.StrokeLines(lines)
691
- else:
692
- if len(self):
693
- self.remove_all_widgets()
694
-
695
-
696
- class CamImageWidget(Widget):
697
- def __init__(self, scene, camera):
698
- Widget.__init__(self, scene, all=False)
699
- self.cam = camera
700
-
701
- def process_draw(self, gc):
702
- if self.cam.frame_bitmap is None:
703
- return
704
- gc.DrawBitmap(
705
- self.cam.frame_bitmap, 0, 0, self.cam.image_width, self.cam.image_height
706
- )
707
-
708
-
709
- class CameraInterface(MWindow):
710
- def __init__(self, context, path, parent, index=0, **kwds):
711
- if isinstance(index, str):
712
- try:
713
- index = int(index)
714
- except ValueError:
715
- pass
716
- if index is None:
717
- index = 0
718
- super().__init__(640, 480, context, path, parent, **kwds)
719
- self.camera = self.context.get_context(f"camera/{index}")
720
- self.panel = CameraPanel(self, wx.ID_ANY, context=self.camera, index=index)
721
- self.add_module_delegate(self.panel)
722
-
723
- # ==========
724
- # MENU BAR
725
- # ==========
726
- if platform.system() != "Darwin":
727
- self.CameraInterface_menubar = wx.MenuBar()
728
- self.create_menu(self.CameraInterface_menubar.Append)
729
- self.SetMenuBar(self.CameraInterface_menubar)
730
- # ==========
731
- # MENUBAR END
732
- # ==========
733
-
734
- _icon = wx.NullIcon
735
- _icon.CopyFromBitmap(icons8_camera.GetBitmap())
736
- self.SetIcon(_icon)
737
- self.SetTitle(_("CameraInterface {index}").format(index=index))
738
- self.Layout()
739
-
740
- def create_menu(self, append):
741
- def identify_cameras(event=None):
742
- self.context("camdetect\n")
743
-
744
- wxglade_tmp_menu = wx.Menu()
745
- item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Reset Fisheye"), "")
746
- self.Bind(wx.EVT_MENU, self.panel.reset_fisheye, id=item.GetId())
747
- item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Reset Perspective"), "")
748
- self.Bind(wx.EVT_MENU, self.panel.reset_perspective, id=item.GetId())
749
- wxglade_tmp_menu.AppendSeparator()
750
-
751
- item = wxglade_tmp_menu.Append(wx.ID_ANY, _("URI Manager"), "")
752
- self.Bind(
753
- wx.EVT_MENU,
754
- lambda e: self.camera.open(
755
- "window/CameraURI", self, index=self.panel.index
756
- ),
757
- id=item.GetId(),
758
- )
759
- camera_root = self.context.get_context("camera")
760
- uris = camera_root.setting(list, "uris", [])
761
- camera_root.setting(int, "search_range", 5)
762
- for uri in uris:
763
- menu_text = _("URI: {usb_index}").format(usb_index=uri)
764
- if isinstance(uri, int):
765
- menu_text = _("Detected USB {usb_index}").format(usb_index=uri)
766
- item = wxglade_tmp_menu.Append(wx.ID_ANY, menu_text, "")
767
- self.Bind(wx.EVT_MENU, self.panel.swap_camera(uri), id=item.GetId())
768
-
769
- for i in range(camera_root.search_range):
770
- if i in uris:
771
- continue
772
- item = wxglade_tmp_menu.Append(
773
- wx.ID_ANY, _("USB {usb_index}").format(usb_index=i), ""
774
- )
775
- self.Bind(wx.EVT_MENU, self.panel.swap_camera(i), id=item.GetId())
776
- wxglade_tmp_menu.AppendSeparator()
777
- item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Identify cameras"), "")
778
- self.Bind(wx.EVT_MENU, identify_cameras, id=item.GetId())
779
-
780
- append(wxglade_tmp_menu, _("Camera"))
781
-
782
- def window_open(self):
783
- self.panel.pane_show()
784
-
785
- def window_close(self):
786
- self.panel.pane_hide()
787
-
788
- @staticmethod
789
- def sub_register(kernel):
790
- camera = kernel.get_context("camera")
791
-
792
- def camera_click(index=None):
793
- s = index
794
-
795
- def specific(event=None):
796
- index = s
797
- camera.default = index
798
- v = camera.default
799
- camera(f"window toggle -m {v} CameraInterface {v}\n")
800
-
801
- return specific
802
-
803
- def detect_usb_cameras(event=None):
804
- camera("camdetect\n")
805
-
806
- kernel.register(
807
- "button/preparation/Camera",
808
- {
809
- "label": _("Camera"),
810
- "icon": icons8_camera,
811
- "tip": _("Opens Camera Window"),
812
- "identifier": "camera_id",
813
- "action": camera_click(),
814
- "priority": 3,
815
- "multi": [
816
- {
817
- "identifier": "cam0",
818
- "label": _("Camera {index}").format(index=0),
819
- "action": camera_click(0),
820
- "signal": "camset0",
821
- },
822
- {
823
- "identifier": "cam1",
824
- "label": _("Camera {index}").format(index=1),
825
- "action": camera_click(1),
826
- "signal": "camset1",
827
- },
828
- {
829
- "identifier": "cam2",
830
- "label": _("Camera {index}").format(index=2),
831
- "action": camera_click(2),
832
- "signal": "camset2",
833
- },
834
- {
835
- "identifier": "cam3",
836
- "label": _("Camera {index}").format(index=3),
837
- "action": camera_click(3),
838
- "signal": "camset3",
839
- },
840
- {
841
- "identifier": "cam4",
842
- "label": _("Camera {index}").format(index=4),
843
- "action": camera_click(4),
844
- "signal": "camset4",
845
- },
846
- {
847
- "identifier": "id_cam",
848
- "label": _("Identify cameras"),
849
- "action": detect_usb_cameras,
850
- },
851
- ],
852
- },
853
- )
854
- kernel.register("window/CameraURI", CameraURI)
855
-
856
- @kernel.console_argument("index", type=int)
857
- @kernel.console_command(
858
- "camwin", help=_("camwin <index>: Open camera window at index")
859
- )
860
- def camera_win(index=None, **kwargs):
861
- kernel.console(f"window open -m {index} CameraInterface {index}\n")
862
-
863
- @kernel.console_command(
864
- "camdetect", help=_("camdetect: Tries to detect cameras on the system")
865
- )
866
- def cam_detect(**kwargs):
867
- try:
868
- import cv2
869
- except ImportError:
870
- return
871
-
872
- # Max range to look at
873
- camera = kernel.get_context("camera")
874
- camera.setting(int, "search_range", 5)
875
- camera.setting(list, "uris", [])
876
- # Reset stuff...
877
- for _index in range(5):
878
- if _index in camera.uris:
879
- camera.uris.remove(_index)
880
-
881
- max_range = camera.search_range
882
- if max_range is None or max_range < 1:
883
- max_range = 5
884
- found = 0
885
- found_camera_string = _("Cameras found: {count}")
886
- progress = wx.ProgressDialog(
887
- _("Looking for Cameras (Range={max_range})").format(
888
- max_range=max_range
889
- ),
890
- found_camera_string.format(count=found),
891
- maximum=max_range,
892
- parent=None,
893
- style=wx.PD_APP_MODAL | wx.PD_CAN_ABORT,
894
- )
895
- # checks for cameras in the first x USB ports
896
- first_found = -1
897
- index = 0
898
- keepgoing = True
899
- while index < max_range and keepgoing:
900
- try:
901
- cap = cv2.VideoCapture(index)
902
- if cap.read()[0]:
903
- if first_found < 0:
904
- first_found = index
905
- camera.uris.append(index)
906
- cap.release()
907
- found += 1
908
- except:
909
- pass
910
- keepgoing = progress.Update(
911
- index + 1, found_camera_string.format(count=found)
912
- )
913
- index += 1
914
- progress.Destroy()
915
- if first_found >= 0:
916
- kernel.signal(f"camset{first_found}", "camera", (first_found, found))
917
-
918
- @staticmethod
919
- def submenu():
920
- return "Camera", "Camera"
921
-
922
-
923
- class CameraURIPanel(wx.Panel):
924
- def __init__(self, *args, context=None, index=None, **kwds):
925
- kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
926
- wx.Panel.__init__(self, *args, **kwds)
927
- self.context = context.get_context("camera")
928
- if index is None:
929
- index = 0
930
- self.index = index
931
- assert isinstance(self.index, int)
932
- self.context.setting(list, "uris", [])
933
- self.list_uri = wx.ListCtrl(
934
- self, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES
935
- )
936
- self.button_add = wx.Button(self, wx.ID_ANY, _("Add URI"))
937
- self.text_uri = wx.TextCtrl(self, wx.ID_ANY, "")
938
-
939
- self.__set_properties()
940
- self.__do_layout()
941
-
942
- self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_list_activated, self.list_uri)
943
- self.Bind(
944
- wx.EVT_LIST_ITEM_RIGHT_CLICK, self.on_list_right_clicked, self.list_uri
945
- )
946
- self.Bind(wx.EVT_BUTTON, self.on_button_add_uri, self.button_add)
947
- self.Bind(wx.EVT_TEXT, self.on_text_uri, self.text_uri)
948
- # end wxGlade
949
- self.changed = False
950
-
951
- def __set_properties(self):
952
- self.list_uri.SetToolTip(_("Displays a list of registered camera URIs"))
953
- self.list_uri.AppendColumn(_("Index"), format=wx.LIST_FORMAT_LEFT, width=69)
954
- self.list_uri.AppendColumn(_("URI"), format=wx.LIST_FORMAT_LEFT, width=348)
955
- self.button_add.SetToolTip(_("Add a new URL"))
956
- # end wxGlade
957
-
958
- def __do_layout(self):
959
- # begin wxGlade: CameraURI.__do_layout
960
- sizer_1 = wx.BoxSizer(wx.VERTICAL)
961
- sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
962
- sizer_1.Add(self.list_uri, 1, wx.EXPAND, 0)
963
- sizer_2.Add(self.button_add, 0, 0, 0)
964
- sizer_2.Add(self.text_uri, 2, 0, 0)
965
- sizer_1.Add(sizer_2, 0, wx.EXPAND, 0)
966
- self.SetSizer(sizer_1)
967
- self.Layout()
968
- # end wxGlade
969
-
970
- def pane_show(self):
971
- self.on_list_refresh()
972
-
973
- def pane_hide(self):
974
- self.commit()
975
-
976
- def commit(self):
977
- if not self.changed:
978
- return
979
- self.context.signal("camera_uri_changed", True)
980
-
981
- def on_list_refresh(self):
982
- self.list_uri.DeleteAllItems()
983
- for i, uri in enumerate(self.context.uris):
984
- m = self.list_uri.InsertItem(i, str(i))
985
- if m != -1:
986
- self.list_uri.SetItem(m, 1, str(uri))
987
-
988
- def on_list_activated(self, event): # wxGlade: CameraURI.<event_handler>
989
- index = event.GetIndex()
990
- new_uri = self.context.uris[index]
991
- self.context.console(f"camera{self.index} --uri {new_uri} stop start\n")
992
- try:
993
- self.GetParent().Close()
994
- except (TypeError, AttributeError):
995
- pass
996
-
997
- def on_list_right_clicked(self, event): # wxGlade: CameraURI.<event_handler>
998
- index = event.GetIndex()
999
- element = event.Text
1000
- menu = wx.Menu()
1001
- convert = menu.Append(
1002
- wx.ID_ANY,
1003
- _("Remove {name}").format(name=str(element)[:16]),
1004
- "",
1005
- wx.ITEM_NORMAL,
1006
- )
1007
- self.Bind(wx.EVT_MENU, self.on_tree_popup_delete(index), convert)
1008
- convert = menu.Append(wx.ID_ANY, _("Duplicate"), "", wx.ITEM_NORMAL)
1009
- self.Bind(wx.EVT_MENU, self.on_tree_popup_duplicate(index), convert)
1010
- convert = menu.Append(wx.ID_ANY, _("Edit"), "", wx.ITEM_NORMAL)
1011
- self.Bind(wx.EVT_MENU, self.on_tree_popup_edit(index), convert)
1012
- convert = menu.Append(wx.ID_ANY, _("Clear All"), "", wx.ITEM_NORMAL)
1013
- self.Bind(wx.EVT_MENU, self.on_tree_popup_clear(index), convert)
1014
- self.PopupMenu(menu)
1015
- menu.Destroy()
1016
-
1017
- def on_tree_popup_delete(self, index):
1018
- def delete(event=None):
1019
- try:
1020
- del self.context.uris[index]
1021
- except KeyError:
1022
- pass
1023
- self.changed = True
1024
- self.on_list_refresh()
1025
-
1026
- return delete
1027
-
1028
- def on_tree_popup_duplicate(self, index):
1029
- def duplicate(event=None):
1030
- self.context.uris.insert(index, self.context.uris[index])
1031
- self.changed = True
1032
- self.on_list_refresh()
1033
-
1034
- return duplicate
1035
-
1036
- def on_tree_popup_edit(self, index):
1037
- def edit(event=None):
1038
- dlg = wx.TextEntryDialog(
1039
- self,
1040
- _("Edit"),
1041
- _("Camera URI"),
1042
- "",
1043
- )
1044
- try:
1045
- dlg.SetValue(self.context.uris[index])
1046
- except IndexError:
1047
- return
1048
- if dlg.ShowModal() == wx.ID_OK:
1049
- self.context.uris[index] = dlg.GetValue()
1050
- self.changed = True
1051
- self.on_list_refresh()
1052
-
1053
- return edit
1054
-
1055
- def on_tree_popup_clear(self, index):
1056
- def delete(event):
1057
- self.context.uris.clear()
1058
- self.changed = True
1059
- self.on_list_refresh()
1060
-
1061
- return delete
1062
-
1063
- def on_button_add_uri(self, event=None): # wxGlade: CameraURI.<event_handler>
1064
- uri = self.text_uri.GetValue()
1065
- if uri is None or uri == "":
1066
- return
1067
- self.context.uris.append(uri)
1068
- self.text_uri.SetValue("")
1069
- self.changed = True
1070
- self.on_list_refresh()
1071
-
1072
- def on_text_uri(self, event): # wxGlade: CameraURI.<event_handler>
1073
- pass
1074
-
1075
-
1076
- class CameraURI(MWindow):
1077
- def __init__(self, *args, index=None, **kwds):
1078
- super().__init__(437, 530, *args, **kwds)
1079
-
1080
- self.panel = CameraURIPanel(self, wx.ID_ANY, context=self.context, index=index)
1081
- _icon = wx.NullIcon
1082
- _icon.CopyFromBitmap(icons8_camera.GetBitmap())
1083
- self.SetIcon(_icon)
1084
- # begin wxGlade: CameraURI.__set_properties
1085
- self.SetTitle(_("URI Manager"))
1086
-
1087
- def window_open(self):
1088
- self.panel.pane_show()
1089
-
1090
- def window_close(self):
1091
- self.panel.pane_hide()
1092
-
1093
- @staticmethod
1094
- def submenu():
1095
- return "Camera", "Sources"
1
+ import platform
2
+
3
+ import wx
4
+ from wx import aui
5
+
6
+ from meerk40t.gui.icons import (
7
+ get_default_icon_size,
8
+ icons8_camera,
9
+ icons8_connected,
10
+ icons8_detective,
11
+ icons8_image_in_frame,
12
+ )
13
+ from meerk40t.gui.mwindow import MWindow
14
+ from meerk40t.gui.scene.sceneconst import (
15
+ HITCHAIN_HIT,
16
+ RESPONSE_ABORT,
17
+ RESPONSE_CHAIN,
18
+ RESPONSE_CONSUME,
19
+ )
20
+ from meerk40t.gui.scene.scenepanel import ScenePanel
21
+ from meerk40t.gui.scene.widget import Widget
22
+ from meerk40t.gui.wxutils import TextCtrl, wxButton, wxBitmapButton, wxCheckBox, wxListCtrl
23
+ from meerk40t.kernel import Job, signal_listener
24
+ from meerk40t.svgelements import Color
25
+
26
+ _ = wx.GetTranslation
27
+
28
+ CORNER_SIZE = 25
29
+
30
+
31
+ def register_panel_camera(window, context):
32
+ for index in range(5):
33
+ panel = CameraPanel(
34
+ window, wx.ID_ANY, context=context, gui=window, index=index, pane=True
35
+ )
36
+ pane = (
37
+ aui.AuiPaneInfo()
38
+ .Left()
39
+ .MinSize(200, 150)
40
+ .FloatingSize(640, 480)
41
+ .Caption(_("Camera {index}").format(index=index))
42
+ .Name(f"camera{index}")
43
+ .CaptionVisible(not context.pane_lock)
44
+ .Hide()
45
+ )
46
+ pane.dock_proportion = 200
47
+ pane.control = panel
48
+ pane.submenu = "_60_" + _("Camera")
49
+ pane.helptext = _("Show camera capture panel")
50
+ window.on_pane_create(pane)
51
+ context.register(f"pane/camera{index}", pane)
52
+
53
+
54
+ class CameraPanel(wx.Panel, Job):
55
+ def __init__(
56
+ self, *args, context=None, gui=None, index: int = 0, pane=False, **kwds
57
+ ):
58
+ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
59
+ wx.Panel.__init__(self, *args, **kwds)
60
+ self.gui = gui
61
+ self.context = context
62
+ self.context.themes.set_window_colors(self)
63
+ self.SetHelpText("camera")
64
+ self.index = index
65
+ self.cam_device_link = {}
66
+ self.pane = pane
67
+
68
+ if pane:
69
+ job_name = f"CamPane{self.index}"
70
+ else:
71
+ job_name = f"Camera{self.index}"
72
+ Job.__init__(self, job_name=job_name)
73
+ self.process = self.update_camera_frame
74
+ self.run_main = True
75
+
76
+ self.context(f"camera{self.index}\n") # command activates Camera service
77
+ self.camera = self.context.get_context(f"camera/{self.index}")
78
+ self.camera.setting(int, "frames_per_second", 30)
79
+ self._remembered = -1
80
+ self.available_resolutions = []
81
+ # camera service location.
82
+ self.last_frame_index = -1
83
+
84
+ if not pane:
85
+ self.button_update = wxBitmapButton(
86
+ self, wx.ID_ANY, icons8_camera.GetBitmap(resize=get_default_icon_size(self.context))
87
+ )
88
+ self.button_export = wxBitmapButton(
89
+ self,
90
+ wx.ID_ANY,
91
+ icons8_image_in_frame.GetBitmap(resize=get_default_icon_size(self.context)),
92
+ )
93
+ self.button_reconnect = wxBitmapButton(
94
+ self,
95
+ wx.ID_ANY,
96
+ icons8_connected.GetBitmap(resize=get_default_icon_size(self.context)),
97
+ )
98
+ self.check_fisheye = wxCheckBox(self, wx.ID_ANY, _("Correct Fisheye"))
99
+ self.check_perspective = wxCheckBox(
100
+ self, wx.ID_ANY, _("Correct Perspective")
101
+ )
102
+ self.slider_fps = wx.Slider(
103
+ self,
104
+ wx.ID_ANY,
105
+ 30,
106
+ 0,
107
+ 120,
108
+ style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS,
109
+ )
110
+ self.button_detect = wxBitmapButton(
111
+ self,
112
+ wx.ID_ANY,
113
+ icons8_detective.GetBitmap(resize=get_default_icon_size(self.context)),
114
+ )
115
+ scene_name = f"Camera{self.index}"
116
+ else:
117
+ scene_name = f"CamPaneScene{self.index}"
118
+
119
+ self.display_camera = ScenePanel(
120
+ self.camera,
121
+ self,
122
+ scene_name=scene_name,
123
+ style=wx.EXPAND | wx.WANTS_CHARS,
124
+ )
125
+ self.widget_scene = self.display_camera.scene
126
+ # Allow Scene update from now on (are suppressed by default during startup phase)
127
+ self.widget_scene.suppress_changes = False
128
+
129
+ # end wxGlade
130
+ sizer_main = wx.BoxSizer(wx.VERTICAL)
131
+ if not pane:
132
+ self.button_update.SetToolTip(_("Update Image"))
133
+ self.button_update.SetSize(self.button_update.GetBestSize())
134
+ self.button_export.SetToolTip(_("Export Snapshot"))
135
+ self.button_export.SetSize(self.button_export.GetBestSize())
136
+ self.button_reconnect.SetToolTip(_("Reconnect Camera"))
137
+ self.button_reconnect.SetSize(self.button_reconnect.GetBestSize())
138
+ self.button_detect.SetToolTip(
139
+ _(
140
+ "Detect Distortions/Calibration\n"
141
+ "You need to print a 9x6 checkerboard pattern from OpenCV\n"
142
+ "It should be flat and visible in the camera."
143
+ )
144
+ )
145
+ self.button_detect.SetSize(self.button_detect.GetBestSize())
146
+ self.slider_fps.SetToolTip(
147
+ _(
148
+ "Set the camera frames per second. A value of 0 means a frame every 5 seconds."
149
+ )
150
+ )
151
+ self.check_fisheye.SetToolTip(
152
+ _("Corrects Fisheye lensing, must be trained with checkerboard image.")
153
+ )
154
+ self.check_perspective.SetToolTip(
155
+ _(
156
+ "The four marker locations (in scene when unchecked) are transformed into corners of a regular square shape."
157
+ )
158
+ )
159
+
160
+ sizer_controls = wx.BoxSizer(wx.HORIZONTAL)
161
+ sizer_checkboxes = wx.BoxSizer(wx.VERTICAL)
162
+ sizer_controls.Add(self.button_update, 0, 0, 0)
163
+ sizer_controls.Add(self.button_export, 0, 0, 0)
164
+ sizer_controls.Add(self.button_reconnect, 0, 0, 0)
165
+ sizer_checkboxes.Add(self.check_fisheye, 0, 0, 0)
166
+ sizer_checkboxes.Add(self.check_perspective, 0, 0, 0)
167
+ sizer_controls.Add(sizer_checkboxes, 1, wx.EXPAND, 0)
168
+ sizer_controls.Add(self.slider_fps, 1, wx.EXPAND, 0)
169
+ sizer_controls.Add(self.button_detect, 0, 0, 0)
170
+ sizer_main.Add(sizer_controls, 1, wx.EXPAND, 0)
171
+ self.Bind(wx.EVT_BUTTON, self.on_button_update, self.button_update)
172
+ self.Bind(wx.EVT_BUTTON, self.on_button_export, self.button_export)
173
+ self.Bind(wx.EVT_BUTTON, self.on_button_reconnect, self.button_reconnect)
174
+ self.Bind(wx.EVT_CHECKBOX, self.on_check_fisheye, self.check_fisheye)
175
+ self.Bind(
176
+ wx.EVT_CHECKBOX, self.on_check_perspective, self.check_perspective
177
+ )
178
+ self.Bind(wx.EVT_SLIDER, self.on_slider_fps, self.slider_fps)
179
+ self.Bind(wx.EVT_BUTTON, self.on_button_detect, self.button_detect)
180
+ sizer_main.Add(self.display_camera, 10, wx.EXPAND, 0)
181
+ self.SetSizer(sizer_main)
182
+ self.Layout()
183
+
184
+ self.image_width = -1
185
+ self.image_height = -1
186
+ self.frame_bitmap = None
187
+
188
+ self.SetDoubleBuffered(True)
189
+ # end wxGlade
190
+
191
+ if not pane:
192
+ self.check_fisheye.SetValue(self.camera.correction_fisheye)
193
+ self.check_perspective.SetValue(self.camera.correction_perspective)
194
+ self.slider_fps.SetValue(self.camera.frames_per_second)
195
+
196
+ self.on_fps_change(self.camera.path)
197
+
198
+ self.widget_scene.widget_root.set_aspect(self.camera.aspect)
199
+
200
+ self.widget_scene.background_brush = wx.Brush(wx.WHITE)
201
+ self.widget_scene.add_scenewidget(CamSceneWidget(self.widget_scene, self))
202
+ self.widget_scene.add_scenewidget(CamImageWidget(self.widget_scene, self))
203
+ self.widget_scene.add_interfacewidget(
204
+ CamInterfaceWidget(self.widget_scene, self)
205
+ )
206
+ # Allow Scene update from now on (are suppressed by default during startup phase)
207
+ self.widget_scene.suppress_changes = False
208
+
209
+ def pane_show(self, *args):
210
+ if self.index != self._remembered:
211
+ self._remembered = self.index
212
+ self.available_resolutions = []
213
+ if self.camera.is_physical:
214
+ self.available_resolutions = self.camera.guess_supported_resolutions()
215
+ if platform.system() == "Darwin" and not hasattr(self.camera, "_first"):
216
+ self.camera(f"camera{self.index} start -t 1\n")
217
+ self.camera._first = False
218
+ else:
219
+ self.camera(f"camera{self.index} start\n")
220
+ self.camera.schedule(self)
221
+ self.display_camera.start_scene()
222
+ # This listener is because you can have frames and windows and both need to care about the slider.
223
+ self.camera.listen("camera;fps", self.on_fps_change)
224
+ self.camera.listen("camera;stopped", self.on_camera_stop)
225
+ self.camera.gui = self
226
+ self.camera("camera focus -5% -5% 105% 105%\n")
227
+
228
+ def pane_hide(self, *args):
229
+ self.camera(f"camera{self.index} stop\n")
230
+ self.camera.unschedule(self)
231
+ self.display_camera.stop_scene()
232
+ if not self.pane:
233
+ self.camera.close(f"Camera{str(self.index)}")
234
+ self.camera.unlisten("camera;fps", self.on_fps_change)
235
+ self.camera.unlisten("camera;stopped", self.on_camera_stop)
236
+ self.camera.signal("camera;stopped", self.index)
237
+ self.camera.gui = None
238
+
239
+ def on_camera_stop(self, origin, index):
240
+ if index == self.index:
241
+ self.camera(f"camera{self.index} start\n")
242
+
243
+ def on_fps_change(self, origin, *args):
244
+ # Set the camera fps.
245
+ if origin != self.camera.path:
246
+ # Not this window.
247
+ return
248
+ camera_fps = self.camera.frames_per_second
249
+ if camera_fps == 0:
250
+ tick = 5
251
+ else:
252
+ tick = 1.0 / camera_fps
253
+ self.interval = tick
254
+ # Set the scene fps if it's needed to support the camera.
255
+ scene_fps = self.camera.frames_per_second
256
+ if scene_fps < 30:
257
+ scene_fps = 30
258
+ if self.camera.fps != scene_fps:
259
+ self.display_camera.scene.set_fps(scene_fps)
260
+
261
+ @signal_listener("refresh_scene")
262
+ def on_refresh_scene(self, origin, *args):
263
+ self.widget_scene.request_refresh(*args)
264
+
265
+ def update_camera_frame(self, event=None):
266
+ if self.camera is None:
267
+ return
268
+
269
+ if self.camera.frame_index == self.last_frame_index:
270
+ return
271
+ else:
272
+ self.last_frame_index = self.camera.frame_index
273
+
274
+ frame = self.camera.get_frame()
275
+ if frame is None:
276
+ return
277
+
278
+ self.image_height, self.image_width = frame.shape[:2]
279
+ if not self.frame_bitmap:
280
+ # Initial set.
281
+ self.widget_scene.widget_root.set_view(
282
+ 0, 0, self.image_width, self.image_height, self.camera.preserve_aspect
283
+ )
284
+ self.frame_bitmap = wx.Bitmap.FromBuffer(
285
+ self.image_width, self.image_height, frame
286
+ )
287
+ if self.camera.correction_perspective:
288
+ if (
289
+ self.camera.width != self.image_width
290
+ or self.camera.height != self.image_height
291
+ ):
292
+ self.image_width = self.camera.width
293
+ self.image_height = self.camera.height
294
+
295
+ self.widget_scene.request_refresh()
296
+
297
+ def reset_perspective(self, event=None):
298
+ """
299
+ Reset the perspective settings.
300
+
301
+ @param event:
302
+ @return:
303
+ """
304
+ self.camera(f"camera{self.index} perspective reset\n")
305
+ if self.camera.perspective is None:
306
+ width = self.camera.width
307
+ height = self.camera.height
308
+ self.camera.perspective = [
309
+ [0, 0],
310
+ [width, 0],
311
+ [width, height],
312
+ [0, height],
313
+ ]
314
+ for v in self.widget_scene.widget_root.scene_widget:
315
+ if hasattr(v, "update"):
316
+ v.update()
317
+ self.widget_scene.request_refresh()
318
+
319
+ def reset_fisheye(self, event=None):
320
+ """
321
+ Reset the fisheye settings.
322
+
323
+ @param event:
324
+ @return:
325
+ """
326
+ self.camera(f"camera{self.index} fisheye reset\n")
327
+ self.widget_scene.request_refresh()
328
+
329
+ def on_check_perspective(self, event=None):
330
+ """
331
+ Perspective checked. Turns on/off
332
+ @param event:
333
+ @return:
334
+ """
335
+ self.camera.correction_perspective = self.check_perspective.GetValue()
336
+
337
+ def on_check_fisheye(self, event=None):
338
+ """
339
+ Fisheye checked. Turns on/off.
340
+ @param event:
341
+ @return:
342
+ """
343
+ self.camera.correction_fisheye = self.check_fisheye.GetValue()
344
+
345
+ def on_button_update(self, event=None): # wxGlade: CameraInterface.<event_handler>
346
+ """
347
+ Button update.
348
+
349
+ Sets image background to main scene.
350
+
351
+ @param event:
352
+ @return:
353
+ """
354
+ self.camera(f"camera{self.index} background\n")
355
+
356
+ def on_button_export(self, event=None): # wxGlade: CameraInterface.<event_handler>
357
+ """
358
+ Button export.
359
+
360
+ Sends an image to the scene as an exported object.
361
+ @param event:
362
+ @return:
363
+ """
364
+ self.camera.console(f"camera{self.index} export\n")
365
+
366
+ def on_button_reconnect(
367
+ self, event=None
368
+ ): # wxGlade: CameraInterface.<event_handler>
369
+ self.camera.console(f"camera{self.index} stop start\n")
370
+
371
+ def on_slider_fps(self, event=None): # wxGlade: CameraInterface.<event_handler>
372
+ """
373
+ Adjusts the camera FPS.
374
+
375
+ If set to 0, this will be a frame each 5 seconds.
376
+
377
+ @param event:
378
+ @return:
379
+ """
380
+ self.camera.frames_per_second = self.slider_fps.GetValue()
381
+ self.camera.signal("camera;fps", self.camera.frames_per_second)
382
+
383
+ def on_button_detect(self, event=None): # wxGlade: CameraInterface.<event_handler>
384
+ """
385
+ Attempts to locate 6x9 checkerboard pattern for OpenCV to correct the fisheye pattern.
386
+
387
+ @param event:
388
+ @return:
389
+ """
390
+ self.camera.console(f"camera{self.index} fisheye capture\n")
391
+
392
+ def swap_camera(self, uri):
393
+ def swap(event=None):
394
+ self.camera(f"camera{self.index} --uri {str(uri)} stop start\n")
395
+ self.frame_bitmap = None
396
+ self._remembered = uri
397
+ self.available_resolutions = []
398
+ if self.camera.is_physical:
399
+ self.available_resolutions = self.camera.guess_supported_resolutions()
400
+
401
+ return swap
402
+
403
+ def set_resolution(self, w, h):
404
+ self.camera.set_resolution(w, h)
405
+ # restart camera
406
+ uri = self.camera.uri
407
+ self.camera(f"camera{self.index} --uri {str(uri)} stop start\n")
408
+ self.frame_bitmap = None
409
+
410
+ class CamInterfaceWidget(Widget):
411
+ def __init__(self, scene, camera):
412
+ Widget.__init__(self, scene, all=True)
413
+ self.cam = camera
414
+
415
+ def process_draw(self, gc: wx.GraphicsContext):
416
+ if self.cam.frame_bitmap is None:
417
+ font = wx.Font(
418
+ 14, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD
419
+ )
420
+ gc.SetFont(font, wx.BLACK)
421
+ if self.cam.camera is None:
422
+ gc.DrawText(
423
+ _("Camera backend failure...\nCannot attempt camera connection."),
424
+ 0,
425
+ 0,
426
+ )
427
+ else:
428
+ gc.DrawText(
429
+ _("Fetching URI:{uri} Frame...").format(uri=self.cam.camera.uri),
430
+ 0,
431
+ 0,
432
+ )
433
+
434
+ def hit(self):
435
+ return HITCHAIN_HIT
436
+
437
+ def event(self, window_pos=None, space_pos=None, event_type=None, **kwargs):
438
+ if event_type == "rightdown":
439
+ def set_resolution(width, height):
440
+ def handler(*args):
441
+ self.cam.set_resolution(this_w, this_h)
442
+
443
+ this_w = width
444
+ this_h = height
445
+ return handler
446
+
447
+ def enable_aspect(*args):
448
+ self.cam.camera.aspect = not self.cam.camera.aspect
449
+ self.scene.widget_root.set_aspect(self.cam.camera.aspect)
450
+ self.scene.widget_root.set_view(
451
+ 0,
452
+ 0,
453
+ self.cam.image_width,
454
+ self.cam.image_height,
455
+ self.cam.camera.preserve_aspect,
456
+ )
457
+
458
+ def set_aspect(aspect):
459
+ def asp(event=None):
460
+ self.cam.camera.preserve_aspect = aspect
461
+ self.scene.widget_root.set_aspect(self.cam.camera.aspect)
462
+ self.scene.widget_root.set_view(
463
+ 0,
464
+ 0,
465
+ self.cam.image_width,
466
+ self.cam.image_height,
467
+ self.cam.camera.preserve_aspect,
468
+ )
469
+
470
+ return asp
471
+
472
+ def get_device_link():
473
+ if self.cam.index in self.cam.cam_device_link:
474
+ return self.cam.cam_device_link[self.cam.index]
475
+ return ""
476
+
477
+ def set_device_link(devlabel):
478
+ if devlabel:
479
+ self.cam.cam_device_link[self.cam.index] = devlabel
480
+ else: # Empty or None
481
+ self.cam.cam_device_link.pop(self.cam.index, None)
482
+
483
+ menu = wx.Menu()
484
+
485
+ item = menu.Append(wx.ID_ANY, _("Update Background"), "")
486
+ self.cam.Bind(
487
+ wx.EVT_MENU,
488
+ lambda e: self.cam.context(f"camera{self.cam.index} background\n"),
489
+ id=item.GetId(),
490
+ )
491
+
492
+ def live_view(c_frames, c_sec):
493
+ def runcam(event=None):
494
+ ratio = c_sec / c_frames
495
+ dev_label = get_device_link()
496
+ self.cam.context(
497
+ f"timer.updatebg{self.cam.index} 0 {ratio} .camera{self.cam.index} background {dev_label}\n"
498
+ )
499
+ return
500
+
501
+ return runcam
502
+
503
+ def live_stop():
504
+ self.cam.context(f"timer.updatebg{self.cam.index} --off\n")
505
+
506
+ submenu = wx.Menu()
507
+ menu.AppendSubMenu(submenu, _("...refresh"))
508
+ rates = ((2, 1), (1, 1), (1, 2), (1, 5), (1, 10))
509
+ for myrate in rates:
510
+ rate_frame = myrate[0]
511
+ rate_sec = myrate[1]
512
+ item = submenu.Append(wx.ID_ANY, f"{rate_frame}x / {rate_sec}sec")
513
+ self.cam.Bind(
514
+ wx.EVT_MENU,
515
+ live_view(rate_frame, rate_sec),
516
+ id=item.GetId(),
517
+ )
518
+
519
+ def has_live_job():
520
+ we_have_a_job = False
521
+ try:
522
+ obj = self.cam.context.kernel.jobs[f"timer.updatebg{self.cam.index}"]
523
+ if obj is not None:
524
+ we_have_a_job = True
525
+ except KeyError:
526
+ pass
527
+ return we_have_a_job
528
+
529
+ if has_live_job():
530
+ submenu.AppendSeparator()
531
+ item = submenu.Append(wx.ID_ANY, "Disable")
532
+ self.cam.Bind(
533
+ wx.EVT_MENU,
534
+ lambda e: live_stop(),
535
+ id=item.GetId(),
536
+ )
537
+ submenu.AppendSeparator()
538
+
539
+ def set_link(devlabel):
540
+ def handler(event):
541
+ set_device_link(devlabel)
542
+ return handler
543
+
544
+ def unset_link(devlabel):
545
+ def handler(event):
546
+ set_device_link("")
547
+ return handler
548
+
549
+ link = get_device_link()
550
+ item = submenu.Append(wx.ID_ANY, _("Device independent"), kind=wx.ITEM_RADIO)
551
+ if link == "":
552
+ item.Check(True)
553
+ self.cam.Bind(wx.EVT_MENU, unset_link(""), id=item.GetId())
554
+ else:
555
+ self.cam.Bind(wx.EVT_MENU, set_link(""), id=item.GetId())
556
+ available_devices = self.cam.context.kernel.services("device")
557
+ for i, spool in enumerate(available_devices):
558
+ item = submenu.Append(wx.ID_ANY, spool.label, kind=wx.ITEM_RADIO)
559
+ if link == spool.label:
560
+ item.Check(True)
561
+ self.cam.Bind(wx.EVT_MENU, unset_link(spool.label), id=item.GetId())
562
+ else:
563
+ self.cam.Bind(wx.EVT_MENU, set_link(spool.label), id=item.GetId())
564
+
565
+ menu.AppendSeparator()
566
+ item = menu.Append(wx.ID_ANY, _("Export Snapshot"), "")
567
+ self.cam.Bind(
568
+ wx.EVT_MENU,
569
+ lambda e: self.cam.context(f"camera{self.cam.index} export\n"),
570
+ id=item.GetId(),
571
+ )
572
+
573
+ item = menu.Append(wx.ID_ANY, _("Reconnect Camera"), "")
574
+ self.cam.Bind(
575
+ wx.EVT_MENU,
576
+ lambda e: self.cam.context(f"camera{self.cam.index} stop start\n"),
577
+ id=item.GetId(),
578
+ )
579
+
580
+ item = menu.Append(wx.ID_ANY, _("Stop Camera"), "")
581
+ self.cam.Bind(
582
+ wx.EVT_MENU,
583
+ lambda e: self.cam.context(f"camera{self.cam.index} stop\n"),
584
+ id=item.GetId(),
585
+ )
586
+ if len(self.cam.available_resolutions):
587
+ cam_w, cam_h = self.cam.camera.get_resolution()
588
+ resmen = wx.Menu()
589
+ for res_w, res_h, res_desc in self.cam.available_resolutions:
590
+ item = resmen.Append(wx.ID_ANY, f"{res_w}x{res_h} - {res_desc}", kind=wx.ITEM_RADIO)
591
+ if res_h == cam_h and res_w == cam_w:
592
+ item.Check(True)
593
+ self.cam.Bind(
594
+ wx.EVT_MENU,
595
+ set_resolution(res_w, res_h),
596
+ id=item.GetId(),
597
+ )
598
+ menu.AppendSubMenu(resmen, _("Set camera resolution..."))
599
+
600
+
601
+ item = menu.Append(wx.ID_ANY, _("Open CameraInterface"), "")
602
+ self.cam.Bind(
603
+ wx.EVT_MENU,
604
+ lambda e: self.cam.context(f"camwin {self.cam.index}\n"),
605
+ id=item.GetId(),
606
+ )
607
+
608
+ menu.AppendSeparator()
609
+
610
+ sub_menu = wx.Menu()
611
+ center = menu.Append(wx.ID_ANY, _("Aspect"), "", wx.ITEM_CHECK)
612
+ if self.cam.camera.aspect:
613
+ center.Check(True)
614
+ self.cam.Bind(wx.EVT_MENU, enable_aspect, center)
615
+ self.cam.Bind(
616
+ wx.EVT_MENU,
617
+ set_aspect("xMinYMin meet"),
618
+ sub_menu.Append(wx.ID_ANY, "xMinYMin meet", "", wx.ITEM_NORMAL),
619
+ )
620
+ self.cam.Bind(
621
+ wx.EVT_MENU,
622
+ set_aspect("xMidYMid meet"),
623
+ sub_menu.Append(wx.ID_ANY, "xMidYMid meet", "", wx.ITEM_NORMAL),
624
+ )
625
+ self.cam.Bind(
626
+ wx.EVT_MENU,
627
+ set_aspect("xMidYMid slice"),
628
+ sub_menu.Append(wx.ID_ANY, "xMidYMid slice", "", wx.ITEM_NORMAL),
629
+ )
630
+ self.cam.Bind(
631
+ wx.EVT_MENU,
632
+ set_aspect("none"),
633
+ sub_menu.Append(wx.ID_ANY, "none", "", wx.ITEM_NORMAL),
634
+ )
635
+
636
+ menu.Append(
637
+ wx.ID_ANY,
638
+ _("Preserve: {aspect}").format(aspect=self.cam.camera.preserve_aspect),
639
+ sub_menu,
640
+ )
641
+ menu.AppendSeparator()
642
+
643
+ fisheye = menu.Append(wx.ID_ANY, _("Correct Fisheye"), "", wx.ITEM_CHECK)
644
+ fisheye.Check(self.cam.camera.correction_fisheye)
645
+ self.cam.camera.correction_fisheye = fisheye.IsChecked()
646
+
647
+ def check_fisheye(event=None):
648
+ self.cam.camera.correction_fisheye = fisheye.IsChecked()
649
+
650
+ self.cam.Bind(wx.EVT_MENU, check_fisheye, fisheye)
651
+
652
+ perspect = menu.Append(
653
+ wx.ID_ANY, _("Correct Perspective"), "", wx.ITEM_CHECK
654
+ )
655
+ perspect.Check(self.cam.camera.correction_perspective)
656
+ self.cam.camera.correction_perspective = perspect.IsChecked()
657
+
658
+ def check_perspect(event=None):
659
+ self.cam.camera.correction_perspective = perspect.IsChecked()
660
+
661
+ def reset_perspect(event=None):
662
+ self.cam.camera(f"camera{self.cam.index} perspective reset\n")
663
+ if self.cam.camera.perspective is None:
664
+ width = self.cam.camera.width
665
+ height = self.cam.camera.height
666
+ self.cam.camera.perspective = [
667
+ [0, 0],
668
+ [width, 0],
669
+ [width, height],
670
+ [0, height],
671
+ ]
672
+ for v in self.scene.widget_root.scene_widget:
673
+ if hasattr(v, "update"):
674
+ v.update()
675
+
676
+ self.cam.Bind(wx.EVT_MENU, check_perspect, perspect)
677
+ menu.AppendSeparator()
678
+ item = menu.Append(wx.ID_ANY, _("Reset Perspective"), "")
679
+ self.cam.Bind(
680
+ wx.EVT_MENU,
681
+ reset_perspect,
682
+ id=item.GetId(),
683
+ )
684
+ item = menu.Append(wx.ID_ANY, _("Reset Fisheye"), "")
685
+ self.cam.Bind(
686
+ wx.EVT_MENU,
687
+ lambda e: self.cam.camera(f"camera{self.cam.index} fisheye reset\n"),
688
+ id=item.GetId(),
689
+ )
690
+ menu.AppendSeparator()
691
+
692
+ sub_menu = wx.Menu()
693
+ item = sub_menu.Append(wx.ID_ANY, _("URI Manager"), "")
694
+ self.cam.Bind(
695
+ wx.EVT_MENU,
696
+ lambda e: self.cam.context.open(
697
+ "window/CameraURI", self.cam, index=self.cam.index
698
+ ),
699
+ id=item.GetId(),
700
+ )
701
+
702
+ camera_context = self.cam.context.get_context("camera")
703
+ uris = camera_context.setting(list, "uris", [])
704
+ for uri in uris:
705
+ item = sub_menu.Append(wx.ID_ANY, _("URI: {uri}").format(uri=uri), "")
706
+ self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(uri), id=item.GetId())
707
+ if sub_menu.MenuItemCount:
708
+ sub_menu.AppendSeparator()
709
+
710
+ item = sub_menu.Append(
711
+ wx.ID_ANY, _("USB {usb_index}").format(usb_index=0), ""
712
+ )
713
+ self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(0), id=item.GetId())
714
+ item = sub_menu.Append(
715
+ wx.ID_ANY, _("USB {usb_index}").format(usb_index=1), ""
716
+ )
717
+ self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(1), id=item.GetId())
718
+ item = sub_menu.Append(
719
+ wx.ID_ANY, _("USB {usb_index}").format(usb_index=2), ""
720
+ )
721
+ self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(2), id=item.GetId())
722
+ item = sub_menu.Append(
723
+ wx.ID_ANY, _("USB {usb_index}").format(usb_index=3), ""
724
+ )
725
+ self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(3), id=item.GetId())
726
+ item = sub_menu.Append(
727
+ wx.ID_ANY, _("USB {usb_index}").format(usb_index=4), ""
728
+ )
729
+ self.cam.Bind(wx.EVT_MENU, self.cam.swap_camera(4), id=item.GetId())
730
+
731
+ menu.Append(
732
+ wx.ID_ANY,
733
+ _("Manage URIs"),
734
+ sub_menu,
735
+ )
736
+ if menu.MenuItemCount != 0:
737
+ self.cam.PopupMenu(menu)
738
+ menu.Destroy()
739
+ return RESPONSE_ABORT
740
+ if event_type == "doubleclick":
741
+ self.cam.context(f"camera{self.cam.index} background\n")
742
+ return RESPONSE_CHAIN
743
+
744
+
745
+ class CamPerspectiveWidget(Widget):
746
+ def __init__(self, scene, camera, index, mid=False):
747
+ self.cam = camera
748
+ self.mid = mid
749
+ self.index = index
750
+ half = CORNER_SIZE / 2.0
751
+ Widget.__init__(self, scene, -half, -half, half, half)
752
+ self.update()
753
+ c = Color.distinct(self.index + 2)
754
+ self.pen = wx.Pen(wx.Colour(c.red, c.green, c.blue))
755
+
756
+ def update(self):
757
+ half = CORNER_SIZE / 2.0
758
+ pos_x, pos_y = self.cam.camera.perspective[self.index]
759
+ self.set_position(pos_x - half, pos_y - half)
760
+
761
+ def hit(self):
762
+ return HITCHAIN_HIT
763
+
764
+ def process_draw(self, gc):
765
+ if not self.cam.camera.correction_perspective and not self.cam.camera.aspect:
766
+ gc.SetPen(self.pen)
767
+ gc.SetBrush(wx.TRANSPARENT_BRUSH)
768
+ gc.StrokeLine(
769
+ self.left,
770
+ self.top + self.height / 2.0,
771
+ self.right,
772
+ self.bottom - self.height / 2.0,
773
+ )
774
+ gc.StrokeLine(
775
+ self.left + self.width / 2.0,
776
+ self.top,
777
+ self.right - self.width / 2.0,
778
+ self.bottom,
779
+ )
780
+ gc.DrawEllipse(self.left, self.top, self.width, self.height)
781
+
782
+ def event(self, window_pos=None, space_pos=None, event_type=None, **kwargs):
783
+ if event_type == "leftdown":
784
+ return RESPONSE_CONSUME
785
+ if event_type == "move":
786
+ self.cam.camera.perspective[self.index][0] += space_pos[4]
787
+ self.cam.camera.perspective[self.index][1] += space_pos[5]
788
+ if self.parent is not None:
789
+ for w in self.parent:
790
+ if isinstance(w, CamPerspectiveWidget):
791
+ w.update()
792
+ self.cam.context.signal("refresh_scene", self.scene.name)
793
+ return RESPONSE_CONSUME
794
+
795
+
796
+ class CamSceneWidget(Widget):
797
+ def __init__(self, scene, camera):
798
+ Widget.__init__(self, scene, all=True)
799
+ self.cam = camera
800
+
801
+ def process_draw(self, gc):
802
+ if not self.cam.camera.correction_perspective and not self.cam.camera.aspect:
803
+ if self.cam.camera.perspective is not None:
804
+ if not len(self):
805
+ for i in range(len(self.cam.camera.perspective)):
806
+ self.add_widget(
807
+ -1, CamPerspectiveWidget(self.scene, self.cam, i, False)
808
+ )
809
+ gc.SetPen(wx.BLACK_DASHED_PEN)
810
+ lines = list(self.cam.camera.perspective)
811
+ lines.append(lines[0])
812
+ gc.StrokeLines(lines)
813
+ else:
814
+ if len(self):
815
+ self.remove_all_widgets()
816
+
817
+ def update(self):
818
+ for v in self:
819
+ if hasattr(v, "update"):
820
+ v.update()
821
+
822
+
823
+ class CamImageWidget(Widget):
824
+ def __init__(self, scene, camera):
825
+ Widget.__init__(self, scene, all=False)
826
+ self.cam = camera
827
+
828
+ def process_draw(self, gc):
829
+ if self.cam.frame_bitmap is None:
830
+ return
831
+ gc.DrawBitmap(
832
+ self.cam.frame_bitmap, 0, 0, self.cam.image_width, self.cam.image_height
833
+ )
834
+
835
+
836
+ class CameraInterface(MWindow):
837
+ def __init__(self, context, path, parent, index=0, **kwds):
838
+ if isinstance(index, str):
839
+ try:
840
+ index = int(index)
841
+ except ValueError:
842
+ pass
843
+ if index is None:
844
+ index = 0
845
+ self.index = index
846
+ super().__init__(640, 480, context, path, parent, **kwds)
847
+ self.camera = self.context.get_context(f"camera/{self.index}")
848
+ self.panel = CameraPanel(self, wx.ID_ANY, context=self.camera, index=self.index)
849
+ self.sizer.Add(self.panel, 1, wx.EXPAND, 0)
850
+ self.add_module_delegate(self.panel)
851
+
852
+ # ==========
853
+ # MENU BAR
854
+ # ==========
855
+ if platform.system() != "Darwin":
856
+ self.CameraInterface_menubar = wx.MenuBar()
857
+ self.create_menu(self.CameraInterface_menubar.Append)
858
+ self.SetMenuBar(self.CameraInterface_menubar)
859
+ # ==========
860
+ # MENUBAR END
861
+ # ==========
862
+
863
+ _icon = wx.NullIcon
864
+ _icon.CopyFromBitmap(icons8_camera.GetBitmap())
865
+ self.SetIcon(_icon)
866
+ self.SetTitle(_("CameraInterface {index}").format(index=self.index))
867
+ self.Layout()
868
+ self.restore_aspect()
869
+
870
+ def create_menu(self, append):
871
+ def identify_cameras(event=None):
872
+ self.context("camdetect\n")
873
+
874
+ wxglade_tmp_menu = wx.Menu()
875
+ item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Reset Fisheye"), "")
876
+ self.Bind(wx.EVT_MENU, self.panel.reset_fisheye, id=item.GetId())
877
+ item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Reset Perspective"), "")
878
+ self.Bind(wx.EVT_MENU, self.panel.reset_perspective, id=item.GetId())
879
+ wxglade_tmp_menu.AppendSeparator()
880
+
881
+ item = wxglade_tmp_menu.Append(wx.ID_ANY, _("URI Manager"), "")
882
+ self.Bind(
883
+ wx.EVT_MENU,
884
+ lambda e: self.camera.open(
885
+ "window/CameraURI", self, index=self.panel.index
886
+ ),
887
+ id=item.GetId(),
888
+ )
889
+ camera_root = self.context.get_context("camera")
890
+ uris = camera_root.setting(list, "uris", [])
891
+ camera_root.setting(int, "search_range", 5)
892
+ for uri in uris:
893
+ menu_text = _("URI: {usb_index}").format(usb_index=uri)
894
+ if isinstance(uri, int):
895
+ menu_text = _("Detected USB {usb_index}").format(usb_index=uri)
896
+ item = wxglade_tmp_menu.Append(wx.ID_ANY, menu_text, "")
897
+ self.Bind(wx.EVT_MENU, self.panel.swap_camera(uri), id=item.GetId())
898
+
899
+ for i in range(camera_root.search_range):
900
+ if i in uris:
901
+ continue
902
+ item = wxglade_tmp_menu.Append(
903
+ wx.ID_ANY, _("USB {usb_index}").format(usb_index=i), ""
904
+ )
905
+ self.Bind(wx.EVT_MENU, self.panel.swap_camera(i), id=item.GetId())
906
+ wxglade_tmp_menu.AppendSeparator()
907
+ item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Identify cameras"), "")
908
+ self.Bind(wx.EVT_MENU, identify_cameras, id=item.GetId())
909
+
910
+ append(wxglade_tmp_menu, _("Camera"))
911
+
912
+ def window_open(self):
913
+ self.panel.pane_show()
914
+
915
+ def window_close(self):
916
+ self.panel.pane_hide()
917
+
918
+ @staticmethod
919
+ def sub_register(kernel):
920
+ camera = kernel.get_context("camera")
921
+
922
+ def camera_click(index=None):
923
+ def specific(event=None):
924
+ camera.default = index
925
+ v = camera.default
926
+ if v is None:
927
+ v = 0
928
+ camera(f"window toggle -m {v} CameraInterface {v}\n")
929
+
930
+ return specific
931
+
932
+ def detect_usb_cameras(event=None):
933
+ camera("camdetect\n")
934
+
935
+ kernel.register(
936
+ "button/preparation/Camera",
937
+ {
938
+ "label": _("Camera"),
939
+ "icon": icons8_camera,
940
+ "tip": _("Opens Camera Window"),
941
+ "identifier": "camera_id",
942
+ "action": camera_click(),
943
+ "priority": 3,
944
+ "multi": [
945
+ {
946
+ "identifier": "cam0",
947
+ "label": _("Camera {index}").format(index=0),
948
+ "action": camera_click(0),
949
+ "signal": "camset0",
950
+ },
951
+ {
952
+ "identifier": "cam1",
953
+ "label": _("Camera {index}").format(index=1),
954
+ "action": camera_click(1),
955
+ "signal": "camset1",
956
+ },
957
+ {
958
+ "identifier": "cam2",
959
+ "label": _("Camera {index}").format(index=2),
960
+ "action": camera_click(2),
961
+ "signal": "camset2",
962
+ },
963
+ {
964
+ "identifier": "cam3",
965
+ "label": _("Camera {index}").format(index=3),
966
+ "action": camera_click(3),
967
+ "signal": "camset3",
968
+ },
969
+ {
970
+ "identifier": "cam4",
971
+ "label": _("Camera {index}").format(index=4),
972
+ "action": camera_click(4),
973
+ "signal": "camset4",
974
+ },
975
+ {
976
+ "identifier": "id_cam",
977
+ "label": _("Identify cameras"),
978
+ "action": detect_usb_cameras,
979
+ },
980
+ ],
981
+ },
982
+ )
983
+ kernel.register("window/CameraURI", CameraURI)
984
+
985
+ @kernel.console_argument("index", type=int)
986
+ @kernel.console_command(
987
+ "camwin", help=_("camwin <index>: Open camera window at index")
988
+ )
989
+ def camera_win(index=None, **kwargs):
990
+ kernel.console(f"window open -m {index} CameraInterface {index}\n")
991
+
992
+ @kernel.console_command(
993
+ "camdetect", help=_("camdetect: Tries to detect cameras on the system")
994
+ )
995
+ def cam_detect(**kwargs):
996
+ try:
997
+ import cv2
998
+ except ImportError:
999
+ return
1000
+
1001
+ # Max range to look at
1002
+ cam_context = kernel.get_context("camera")
1003
+ cam_context.setting(int, "search_range", 5)
1004
+ cam_context.setting(list, "uris", [])
1005
+ # Reset stuff...
1006
+ for _index in range(5):
1007
+ if _index in cam_context.uris:
1008
+ cam_context.uris.remove(_index)
1009
+
1010
+ max_range = cam_context.search_range
1011
+ if max_range is None or max_range < 1:
1012
+ max_range = 5
1013
+ found = 0
1014
+ found_camera_string = _("Cameras found: {count}")
1015
+ progress = wx.ProgressDialog(
1016
+ _("Looking for Cameras (Range={max_range})").format(
1017
+ max_range=max_range
1018
+ ),
1019
+ found_camera_string.format(count=found),
1020
+ maximum=max_range,
1021
+ parent=None,
1022
+ style=wx.PD_APP_MODAL | wx.PD_CAN_ABORT,
1023
+ )
1024
+ # checks for cameras in the first x USB ports
1025
+ first_found = -1
1026
+ index = 0
1027
+ keepgoing = True
1028
+ while index < max_range and keepgoing:
1029
+ try:
1030
+ cap = cv2.VideoCapture(index)
1031
+ if cap.read()[0]:
1032
+ if first_found < 0:
1033
+ first_found = index
1034
+ cam_context.uris.append(index)
1035
+ cap.release()
1036
+ found += 1
1037
+ except:
1038
+ pass
1039
+ keepgoing = progress.Update(
1040
+ index + 1, found_camera_string.format(count=found)
1041
+ )
1042
+ index += 1
1043
+ progress.Destroy()
1044
+ if first_found >= 0:
1045
+ kernel.signal(f"camset{first_found}", "camera", (first_found, found))
1046
+
1047
+ @staticmethod
1048
+ def submenu():
1049
+ return "Camera", "Camera"
1050
+
1051
+ @staticmethod
1052
+ def helptext():
1053
+ return _("Display the camera window")
1054
+
1055
+ class CameraURIPanel(wx.Panel):
1056
+ def __init__(self, *args, context=None, index=None, **kwds):
1057
+ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
1058
+ wx.Panel.__init__(self, *args, **kwds)
1059
+ self.context = context.get_context("camera")
1060
+ context.themes.set_window_colors(self)
1061
+ if index is None:
1062
+ index = 0
1063
+ self.index = index
1064
+ assert isinstance(self.index, int)
1065
+ self.context.setting(list, "uris", [])
1066
+ self.list_uri = wxListCtrl(
1067
+ self, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES,
1068
+ context=self.context, list_name="list_camerauri",
1069
+ )
1070
+ self.button_add = wxButton(self, wx.ID_ANY, _("Add URI"))
1071
+ self.text_uri = TextCtrl(self, wx.ID_ANY, "")
1072
+
1073
+ self.__set_properties()
1074
+ self.__do_layout()
1075
+
1076
+ self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_list_activated, self.list_uri)
1077
+ self.Bind(
1078
+ wx.EVT_LIST_ITEM_RIGHT_CLICK, self.on_list_right_clicked, self.list_uri
1079
+ )
1080
+ self.Bind(wx.EVT_BUTTON, self.on_button_add_uri, self.button_add)
1081
+ self.Bind(wx.EVT_TEXT, self.on_text_uri, self.text_uri)
1082
+ # end wxGlade
1083
+ self.changed = False
1084
+
1085
+ def __set_properties(self):
1086
+ self.list_uri.SetToolTip(_("Displays a list of registered camera URIs"))
1087
+ self.list_uri.AppendColumn(_("Index"), format=wx.LIST_FORMAT_LEFT, width=69)
1088
+ self.list_uri.AppendColumn(_("URI"), format=wx.LIST_FORMAT_LEFT, width=348)
1089
+ self.list_uri.resize_columns()
1090
+ self.button_add.SetToolTip(_("Add a new URL"))
1091
+ # end wxGlade
1092
+
1093
+ def __do_layout(self):
1094
+ # begin wxGlade: CameraURI.__do_layout
1095
+ sizer_1 = wx.BoxSizer(wx.VERTICAL)
1096
+ sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
1097
+ sizer_1.Add(self.list_uri, 1, wx.EXPAND, 0)
1098
+ sizer_2.Add(self.button_add, 0, 0, 0)
1099
+ sizer_2.Add(self.text_uri, 2, 0, 0)
1100
+ sizer_1.Add(sizer_2, 0, wx.EXPAND, 0)
1101
+ self.SetSizer(sizer_1)
1102
+ self.Layout()
1103
+ # end wxGlade
1104
+
1105
+ def pane_show(self):
1106
+ self.on_list_refresh()
1107
+ self.list_uri.load_column_widths()
1108
+
1109
+ def pane_hide(self):
1110
+ self.commit()
1111
+ self.list_uri.save_column_widths()
1112
+
1113
+ def commit(self):
1114
+ if not self.changed:
1115
+ return
1116
+ self.context.signal("camera_uri_changed", True)
1117
+
1118
+ def on_list_refresh(self):
1119
+ self.list_uri.DeleteAllItems()
1120
+ for i, uri in enumerate(self.context.uris):
1121
+ m = self.list_uri.InsertItem(i, str(i))
1122
+ if m != -1:
1123
+ self.list_uri.SetItem(m, 1, str(uri))
1124
+
1125
+ def on_list_activated(self, event): # wxGlade: CameraURI.<event_handler>
1126
+ index = event.GetIndex()
1127
+ new_uri = self.context.uris[index]
1128
+ self.context.console(f"camera{self.index} --uri {new_uri} stop start\n")
1129
+ try:
1130
+ self.GetParent().Close()
1131
+ except (TypeError, AttributeError):
1132
+ pass
1133
+
1134
+ def on_list_right_clicked(self, event): # wxGlade: CameraURI.<event_handler>
1135
+ index = event.GetIndex()
1136
+ element = event.Text
1137
+ menu = wx.Menu()
1138
+ convert = menu.Append(
1139
+ wx.ID_ANY,
1140
+ _("Remove {name}").format(name=str(element)[:16]),
1141
+ "",
1142
+ wx.ITEM_NORMAL,
1143
+ )
1144
+ self.Bind(wx.EVT_MENU, self.on_tree_popup_delete(index), convert)
1145
+ convert = menu.Append(wx.ID_ANY, _("Duplicate"), "", wx.ITEM_NORMAL)
1146
+ self.Bind(wx.EVT_MENU, self.on_tree_popup_duplicate(index), convert)
1147
+ convert = menu.Append(wx.ID_ANY, _("Edit"), "", wx.ITEM_NORMAL)
1148
+ self.Bind(wx.EVT_MENU, self.on_tree_popup_edit(index), convert)
1149
+ convert = menu.Append(wx.ID_ANY, _("Clear All"), "", wx.ITEM_NORMAL)
1150
+ self.Bind(wx.EVT_MENU, self.on_tree_popup_clear(index), convert)
1151
+ self.PopupMenu(menu)
1152
+ menu.Destroy()
1153
+
1154
+ def on_tree_popup_delete(self, index):
1155
+ def delete(event=None):
1156
+ try:
1157
+ del self.context.uris[index]
1158
+ except KeyError:
1159
+ pass
1160
+ self.changed = True
1161
+ self.on_list_refresh()
1162
+
1163
+ return delete
1164
+
1165
+ def on_tree_popup_duplicate(self, index):
1166
+ def duplicate(event=None):
1167
+ self.context.uris.insert(index, self.context.uris[index])
1168
+ self.changed = True
1169
+ self.on_list_refresh()
1170
+
1171
+ return duplicate
1172
+
1173
+ def on_tree_popup_edit(self, index):
1174
+ def edit(event=None):
1175
+ dlg = wx.TextEntryDialog(
1176
+ self,
1177
+ _("Edit"),
1178
+ _("Camera URI"),
1179
+ "",
1180
+ )
1181
+ try:
1182
+ dlg.SetValue(self.context.uris[index])
1183
+ except IndexError:
1184
+ return
1185
+ if dlg.ShowModal() == wx.ID_OK:
1186
+ self.context.uris[index] = dlg.GetValue()
1187
+ self.changed = True
1188
+ self.on_list_refresh()
1189
+
1190
+ return edit
1191
+
1192
+ def on_tree_popup_clear(self, index):
1193
+ def delete(event):
1194
+ if self.context.kernel.yesno(
1195
+ _("Do you really want to delete all entries?"),
1196
+ caption=_("URI-Manager")
1197
+ ):
1198
+ self.context.uris.clear()
1199
+ self.changed = True
1200
+ self.on_list_refresh()
1201
+
1202
+ return delete
1203
+
1204
+ def on_button_add_uri(self, event=None): # wxGlade: CameraURI.<event_handler>
1205
+ uri = self.text_uri.GetValue()
1206
+ if uri is None or uri == "":
1207
+ return
1208
+ self.context.uris.append(uri)
1209
+ self.text_uri.SetValue("")
1210
+ self.changed = True
1211
+ self.on_list_refresh()
1212
+
1213
+ def on_text_uri(self, event): # wxGlade: CameraURI.<event_handler>
1214
+ pass
1215
+
1216
+
1217
+ class CameraURI(MWindow):
1218
+ def __init__(self, *args, index=None, **kwds):
1219
+ super().__init__(437, 530, *args, **kwds)
1220
+ self.panel = CameraURIPanel(self, wx.ID_ANY, context=self.context, index=index)
1221
+ self.sizer.Add(self.panel, 1, wx.EXPAND, 0)
1222
+ _icon = wx.NullIcon
1223
+ _icon.CopyFromBitmap(icons8_camera.GetBitmap())
1224
+ self.SetIcon(_icon)
1225
+ # begin wxGlade: CameraURI.__set_properties
1226
+ self.SetTitle(_("URI Manager"))
1227
+ self.restore_aspect()
1228
+
1229
+ def window_open(self):
1230
+ self.panel.pane_show()
1231
+
1232
+ def window_close(self):
1233
+ self.panel.pane_hide()
1234
+
1235
+ @staticmethod
1236
+ def submenu():
1237
+ return "Camera", "Sources"
1238
+
1239
+ @staticmethod
1240
+ def helptext():
1241
+ return _("Edit camera sources")