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

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