psychopy 2024.2.5__py3-none-any.whl → 2025.1.1__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.

Potentially problematic release.


This version of psychopy might be problematic. Click here for more details.

Files changed (863) hide show
  1. psychopy/CHANGELOG.txt +4 -4
  2. psychopy/GIT_SHA +1 -1
  3. psychopy/LICENSE.txt +1 -1
  4. psychopy/VERSION +1 -1
  5. psychopy/__init__.py +10 -7
  6. psychopy/alerts/__init__.py +1 -1
  7. psychopy/alerts/_alerts.py +53 -17
  8. psychopy/alerts/_errorHandler.py +3 -4
  9. psychopy/alerts/alertsCatalogue/3210.yaml +27 -0
  10. psychopy/alerts/alertsCatalogue/3610.yaml +19 -0
  11. psychopy/alerts/alertsCatalogue/4130.yaml +19 -0
  12. psychopy/alerts/alertsCatalogue/alertCategories.yaml +8 -1
  13. psychopy/alerts/alertsCatalogue/alertmsg.py +1 -1
  14. psychopy/alerts/alerttools.py +0 -16
  15. psychopy/app/Resources/betasplash.png +0 -0
  16. psychopy/app/Resources/betasplash@2x.png +0 -0
  17. psychopy/app/Resources/classic/case.png +0 -0
  18. psychopy/app/Resources/classic/case@2x.png +0 -0
  19. psychopy/app/Resources/classic/fileaudio.png +0 -0
  20. psychopy/app/Resources/classic/fileaudio@2x.png +0 -0
  21. psychopy/app/Resources/classic/filecss.png +0 -0
  22. psychopy/app/Resources/classic/filecss@2x.png +0 -0
  23. psychopy/app/Resources/classic/filecsv.png +0 -0
  24. psychopy/app/Resources/classic/filecsv@2x.png +0 -0
  25. psychopy/app/Resources/classic/filedesign.png +0 -0
  26. psychopy/app/Resources/classic/filedesign@2x.png +0 -0
  27. psychopy/app/Resources/classic/filefont.png +0 -0
  28. psychopy/app/Resources/classic/filefont@2x.png +0 -0
  29. psychopy/app/Resources/classic/filegit.png +0 -0
  30. psychopy/app/Resources/classic/filegit@2x.png +0 -0
  31. psychopy/app/Resources/classic/filehtml.png +0 -0
  32. psychopy/app/Resources/classic/filehtml@2x.png +0 -0
  33. psychopy/app/Resources/classic/fileimage.png +0 -0
  34. psychopy/app/Resources/classic/fileimage@2x.png +0 -0
  35. psychopy/app/Resources/classic/fileinfo.png +0 -0
  36. psychopy/app/Resources/classic/fileinfo@2x.png +0 -0
  37. psychopy/app/Resources/classic/filejs.png +0 -0
  38. psychopy/app/Resources/classic/filejs@2x.png +0 -0
  39. psychopy/app/Resources/classic/filejson.png +0 -0
  40. psychopy/app/Resources/classic/filejson@2x.png +0 -0
  41. psychopy/app/Resources/classic/filepkg.png +0 -0
  42. psychopy/app/Resources/classic/filepkg@2x.png +0 -0
  43. psychopy/app/Resources/classic/filepsyexp.png +0 -0
  44. psychopy/app/Resources/classic/filepsyexp@2x.png +0 -0
  45. psychopy/app/Resources/classic/filepy.png +0 -0
  46. psychopy/app/Resources/classic/filepy@2x.png +0 -0
  47. psychopy/app/Resources/classic/filetxt.png +0 -0
  48. psychopy/app/Resources/classic/filetxt@2x.png +0 -0
  49. psychopy/app/Resources/classic/fileunknown.png +0 -0
  50. psychopy/app/Resources/classic/fileunknown@2x.png +0 -0
  51. psychopy/app/Resources/classic/filevideo.png +0 -0
  52. psychopy/app/Resources/classic/filevideo@2x.png +0 -0
  53. psychopy/app/Resources/classic/find.png +0 -0
  54. psychopy/app/Resources/classic/find@2x.png +0 -0
  55. psychopy/app/Resources/classic/loop.png +0 -0
  56. psychopy/app/Resources/classic/loop@2x.png +0 -0
  57. psychopy/app/Resources/classic/regex.png +0 -0
  58. psychopy/app/Resources/classic/regex@2x.png +0 -0
  59. psychopy/app/Resources/dark/case.png +0 -0
  60. psychopy/app/Resources/dark/case@2x.png +0 -0
  61. psychopy/app/Resources/dark/fileaudio.png +0 -0
  62. psychopy/app/Resources/dark/fileaudio@2x.png +0 -0
  63. psychopy/app/Resources/dark/filecss.png +0 -0
  64. psychopy/app/Resources/dark/filecss@2x.png +0 -0
  65. psychopy/app/Resources/dark/filecsv.png +0 -0
  66. psychopy/app/Resources/dark/filecsv@2x.png +0 -0
  67. psychopy/app/Resources/dark/filedesign.png +0 -0
  68. psychopy/app/Resources/dark/filedesign@2x.png +0 -0
  69. psychopy/app/Resources/dark/filefont.png +0 -0
  70. psychopy/app/Resources/dark/filefont@2x.png +0 -0
  71. psychopy/app/Resources/dark/filegit.png +0 -0
  72. psychopy/app/Resources/dark/filegit@2x.png +0 -0
  73. psychopy/app/Resources/dark/filehtml.png +0 -0
  74. psychopy/app/Resources/dark/filehtml@2x.png +0 -0
  75. psychopy/app/Resources/dark/fileimage.png +0 -0
  76. psychopy/app/Resources/dark/fileimage@2x.png +0 -0
  77. psychopy/app/Resources/dark/fileinfo.png +0 -0
  78. psychopy/app/Resources/dark/fileinfo@2x.png +0 -0
  79. psychopy/app/Resources/dark/filejs.png +0 -0
  80. psychopy/app/Resources/dark/filejs@2x.png +0 -0
  81. psychopy/app/Resources/dark/filejson.png +0 -0
  82. psychopy/app/Resources/dark/filejson@2x.png +0 -0
  83. psychopy/app/Resources/dark/filepkg.png +0 -0
  84. psychopy/app/Resources/dark/filepkg@2x.png +0 -0
  85. psychopy/app/Resources/dark/filepsyexp.png +0 -0
  86. psychopy/app/Resources/dark/filepsyexp@2x.png +0 -0
  87. psychopy/app/Resources/dark/filepy.png +0 -0
  88. psychopy/app/Resources/dark/filepy@2x.png +0 -0
  89. psychopy/app/Resources/dark/filetxt.png +0 -0
  90. psychopy/app/Resources/dark/filetxt@2x.png +0 -0
  91. psychopy/app/Resources/dark/fileunknown.png +0 -0
  92. psychopy/app/Resources/dark/fileunknown@2x.png +0 -0
  93. psychopy/app/Resources/dark/filevideo.png +0 -0
  94. psychopy/app/Resources/dark/filevideo@2x.png +0 -0
  95. psychopy/app/Resources/dark/find.png +0 -0
  96. psychopy/app/Resources/dark/find@2x.png +0 -0
  97. psychopy/app/Resources/dark/loop.png +0 -0
  98. psychopy/app/Resources/dark/loop@2x.png +0 -0
  99. psychopy/app/Resources/dark/regex.png +0 -0
  100. psychopy/app/Resources/dark/regex@2x.png +0 -0
  101. psychopy/app/Resources/light/case.png +0 -0
  102. psychopy/app/Resources/light/case@2x.png +0 -0
  103. psychopy/app/Resources/light/fileaudio.png +0 -0
  104. psychopy/app/Resources/light/fileaudio@2x.png +0 -0
  105. psychopy/app/Resources/light/filecss.png +0 -0
  106. psychopy/app/Resources/light/filecss@2x.png +0 -0
  107. psychopy/app/Resources/light/filecsv.png +0 -0
  108. psychopy/app/Resources/light/filecsv@2x.png +0 -0
  109. psychopy/app/Resources/light/filedesign.png +0 -0
  110. psychopy/app/Resources/light/filedesign@2x.png +0 -0
  111. psychopy/app/Resources/light/filefont.png +0 -0
  112. psychopy/app/Resources/light/filefont@2x.png +0 -0
  113. psychopy/app/Resources/light/filegit.png +0 -0
  114. psychopy/app/Resources/light/filegit@2x.png +0 -0
  115. psychopy/app/Resources/light/filehtml.png +0 -0
  116. psychopy/app/Resources/light/filehtml@2x.png +0 -0
  117. psychopy/app/Resources/light/fileimage.png +0 -0
  118. psychopy/app/Resources/light/fileimage@2x.png +0 -0
  119. psychopy/app/Resources/light/fileinfo.png +0 -0
  120. psychopy/app/Resources/light/fileinfo@2x.png +0 -0
  121. psychopy/app/Resources/light/filejs.png +0 -0
  122. psychopy/app/Resources/light/filejs@2x.png +0 -0
  123. psychopy/app/Resources/light/filejson.png +0 -0
  124. psychopy/app/Resources/light/filejson@2x.png +0 -0
  125. psychopy/app/Resources/light/filepkg.png +0 -0
  126. psychopy/app/Resources/light/filepkg@2x.png +0 -0
  127. psychopy/app/Resources/light/filepsyexp.png +0 -0
  128. psychopy/app/Resources/light/filepsyexp@2x.png +0 -0
  129. psychopy/app/Resources/light/filepy.png +0 -0
  130. psychopy/app/Resources/light/filepy@2x.png +0 -0
  131. psychopy/app/Resources/light/filetxt.png +0 -0
  132. psychopy/app/Resources/light/filetxt@2x.png +0 -0
  133. psychopy/app/Resources/light/fileunknown.png +0 -0
  134. psychopy/app/Resources/light/fileunknown@2x.png +0 -0
  135. psychopy/app/Resources/light/filevideo.png +0 -0
  136. psychopy/app/Resources/light/filevideo@2x.png +0 -0
  137. psychopy/app/Resources/light/find.png +0 -0
  138. psychopy/app/Resources/light/find@2x.png +0 -0
  139. psychopy/app/Resources/light/loop.png +0 -0
  140. psychopy/app/Resources/light/loop@2x.png +0 -0
  141. psychopy/app/Resources/light/regex.png +0 -0
  142. psychopy/app/Resources/light/regex@2x.png +0 -0
  143. psychopy/app/Resources/routine_templates/Basic.psyexp +0 -1
  144. psychopy/app/Resources/routine_templates/Misc.psyexp +0 -1
  145. psychopy/app/Resources/routine_templates/Online.psyexp +0 -2
  146. psychopy/app/Resources/routine_templates/Trials.psyexp +0 -1
  147. psychopy/app/Resources/splash.png +0 -0
  148. psychopy/app/Resources/splash@2x.png +0 -0
  149. psychopy/app/__init__.py +49 -8
  150. psychopy/app/__main__.py +3 -0
  151. psychopy/app/_psychopyApp.py +134 -125
  152. psychopy/app/builder/builder.py +42 -22
  153. psychopy/app/builder/dialogs/__init__.py +44 -11
  154. psychopy/app/builder/dialogs/dlgsCode.py +1 -1
  155. psychopy/app/builder/dialogs/dlgsConditions.py +1 -1
  156. psychopy/app/builder/dialogs/findDlg.py +106 -20
  157. psychopy/app/builder/dialogs/paramCtrls.py +42 -1
  158. psychopy/app/builder/validators.py +1 -1
  159. psychopy/app/coder/codeEditorBase.py +8 -8
  160. psychopy/app/coder/coder.py +32 -29
  161. psychopy/app/coder/fileBrowser.py +68 -22
  162. psychopy/app/coder/folding.py +1 -1
  163. psychopy/app/coder/psychoParser.py +1 -1
  164. psychopy/app/coder/repl.py +1 -1
  165. psychopy/app/coder/sourceTree.py +1 -1
  166. psychopy/app/connections/__init__.py +1 -1
  167. psychopy/app/connections/news.py +1 -1
  168. psychopy/app/connections/sendusage.py +1 -1
  169. psychopy/app/connections/updates.py +1 -1
  170. psychopy/app/console.py +1 -1
  171. psychopy/app/errorDlg.py +1 -1
  172. psychopy/app/frametracker.py +1 -1
  173. psychopy/app/idle.py +1 -1
  174. psychopy/app/jobs.py +5 -2
  175. psychopy/app/linuxconfig/__init__.py +1 -1
  176. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  177. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4 -4
  178. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
  179. psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
  180. psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
  181. psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +3 -3
  182. psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
  183. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
  184. psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
  185. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  186. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +4 -4
  187. psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
  188. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +4 -4
  189. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  190. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +4 -4
  191. psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
  192. psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +2 -2
  193. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
  194. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +1 -1
  195. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
  196. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
  197. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +2 -2
  198. psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
  199. psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +2 -2
  200. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
  201. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +2 -2
  202. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
  203. psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
  204. psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +2 -2
  205. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  206. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +3421 -2396
  207. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
  208. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
  209. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +2 -2
  210. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
  211. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
  212. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
  213. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
  214. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +4 -4
  215. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
  216. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
  217. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
  218. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +2 -2
  219. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
  220. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +2 -2
  221. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
  222. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +3 -3
  223. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
  224. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +2 -2
  225. psychopy/app/{builder/localizedStrings.py → localizedStrings.py} +173 -26
  226. psychopy/app/pavlovia_ui/__init__.py +1 -1
  227. psychopy/app/pavlovia_ui/_base.py +1 -1
  228. psychopy/app/pavlovia_ui/functions.py +1 -1
  229. psychopy/app/pavlovia_ui/menu.py +1 -1
  230. psychopy/app/pavlovia_ui/project.py +1 -1
  231. psychopy/app/pavlovia_ui/search.py +1 -1
  232. psychopy/app/pavlovia_ui/sync.py +1 -1
  233. psychopy/app/pavlovia_ui/user.py +1 -1
  234. psychopy/app/plugin_manager/dialog.py +34 -96
  235. psychopy/app/plugin_manager/packages.py +10 -14
  236. psychopy/app/plugin_manager/plugins.py +64 -4
  237. psychopy/app/preferencesDlg.py +12 -37
  238. psychopy/app/psychopyApp.py +130 -44
  239. psychopy/app/ribbon.py +1 -0
  240. psychopy/app/runner/runner.py +19 -7
  241. psychopy/app/runner/scriptProcess.py +11 -6
  242. psychopy/app/stdout/stdOutRich.py +9 -2
  243. psychopy/app/themes/fonts.py +1 -1
  244. psychopy/app/themes/icons.py +2 -38
  245. psychopy/app/ui/__init__.py +1 -1
  246. psychopy/app/utils.py +3 -3
  247. psychopy/assets/default.mp3 +0 -0
  248. psychopy/assets/fonts/NotoSans-Bold.ttf +0 -0
  249. psychopy/assets/fonts/NotoSans-BoldItalic.ttf +0 -0
  250. psychopy/assets/fonts/NotoSans-Italic.ttf +0 -0
  251. psychopy/assets/fonts/NotoSans-Regular.ttf +0 -0
  252. psychopy/assets/voicekeyThresholdStim.wav +0 -0
  253. psychopy/clock.py +4 -1
  254. psychopy/colors.py +12 -5
  255. psychopy/core.py +1 -1
  256. psychopy/data/base.py +1 -1
  257. psychopy/data/experiment.py +151 -46
  258. psychopy/data/routine.py +27 -1
  259. psychopy/data/staircase.py +2 -1
  260. psychopy/data/trial.py +62 -14
  261. psychopy/data/utils.py +2 -2
  262. psychopy/demos/builder/Design Templates/branchedExperiment/branchedExperiment.psyexp +333 -218
  263. psychopy/demos/builder/Design Templates/psychophysicsStaircase/psychophysicsStaircase.psyexp +261 -239
  264. psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/psychophysicsStaircaseInterleaved.psyexp +319 -180
  265. psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks.psyexp +204 -116
  266. psychopy/demos/builder/Experiments/BART/assets/background.jpg +0 -0
  267. psychopy/demos/builder/Experiments/BART/assets/blueBalloon.png +0 -0
  268. psychopy/demos/builder/Experiments/BART/assets/greenBalloon.png +0 -0
  269. psychopy/demos/builder/Experiments/BART/assets/redBalloon.png +0 -0
  270. psychopy/demos/builder/Experiments/BART/bart.psyexp +779 -866
  271. psychopy/demos/builder/Experiments/BigFiveInventory/BFI.psyexp +242 -180
  272. psychopy/demos/builder/Experiments/GoNoGo/gng.psyexp +419 -406
  273. psychopy/demos/builder/Experiments/dragAndDrop/README.md +2 -37
  274. psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +460 -1204
  275. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/blank_grid.png +0 -0
  276. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/make_shapes.psyexp +221 -0
  277. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/readme.md +4 -0
  278. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_1.png +0 -0
  279. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_10.png +0 -0
  280. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_2.png +0 -0
  281. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_3.png +0 -0
  282. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_4.png +0 -0
  283. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_5.png +0 -0
  284. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_6.png +0 -0
  285. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_7.png +0 -0
  286. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_8.png +0 -0
  287. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_9.png +0 -0
  288. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
  289. psychopy/demos/builder/Experiments/mentalRotation/MentalRotation.psyexp +583 -542
  290. psychopy/demos/builder/Experiments/navon/NavonTask.psyexp +458 -427
  291. psychopy/demos/builder/Experiments/sternberg/sternberg.psyexp +588 -550
  292. psychopy/demos/builder/Experiments/stroop/stroop.psyexp +303 -207
  293. psychopy/demos/builder/Experiments/stroopExtended/stroop.psyexp +390 -215
  294. psychopy/demos/builder/Experiments/stroopExtended/stroopReverse.psyexp +390 -215
  295. psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +357 -331
  296. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -2
  297. psychopy/demos/builder/Feature Demos/counterbalance/counterbalance.psyexp +287 -277
  298. psychopy/demos/builder/Feature Demos/gratings/gratings.psyexp +370 -320
  299. psychopy/demos/builder/Feature Demos/noise/noise.psyexp +452 -399
  300. psychopy/demos/builder/Feature Demos/panorama/panorama.psyexp +168 -133
  301. psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +4 -3
  302. psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +420 -392
  303. psychopy/demos/builder/Feature Demos/sliders/sliders.psyexp +917 -871
  304. psychopy/demos/builder/Feature Demos/visualValidator/readme.md +7 -0
  305. psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +200 -0
  306. psychopy/demos/builder/Hardware/EEG_parallel_component/EEG_triggers_parallel_comp.psyexp +25 -9
  307. psychopy/demos/builder/Hardware/EEG_serial_code/EEG_triggers_serial_code.psyexp +372 -361
  308. psychopy/demos/builder/Hardware/EEG_serial_component/EEG_triggers_serial_comp.psyexp +25 -9
  309. psychopy/demos/builder/Hardware/EGI_netstation/stroop.psyexp +320 -235
  310. psychopy/demos/builder/Hardware/Eyetracking_visual_search/visualSearch.psyexp +790 -651
  311. psychopy/demos/builder/Hardware/camera/camera.psyexp +326 -246
  312. psychopy/demos/builder/Hardware/eyetracking/eyetracking.psyexp +432 -327
  313. psychopy/demos/builder/Hardware/eyetracking_custom_cal/eyetracking_custom_cal.psyexp +440 -321
  314. psychopy/demos/builder/Hardware/fMRI/fMRI_demo.psyexp +190 -181
  315. psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo.psyexp +323 -312
  316. psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy.psyexp +360 -0
  317. psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy_legacy.psyexp +312 -0
  318. psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy.psyexp +329 -273
  319. psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy.psyexp +360 -0
  320. psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy_legacy.psyexp +312 -0
  321. psychopy/demos/builder/Hardware/microphone/microphone.psyexp +450 -404
  322. psychopy/demos/builder/Hardware/pump/pump.psyexp +593 -329
  323. psychopy/demos/builder/Helper Tools/achorVSalignment/FlowCircular-Regular.ttf +0 -0
  324. psychopy/demos/builder/Helper Tools/achorVSalignment/anchorAlignment.psyexp +336 -274
  325. psychopy/demos/builder/Helper Tools/clockFace/clockFace.psyexp +200 -149
  326. psychopy/demos/builder/Helper Tools/colors/colors.psyexp +300 -279
  327. psychopy/demos/builder/Helper Tools/drawPolygon/drawPolygon.psyexp +677 -564
  328. psychopy/demos/builder/Helper Tools/keyNameFinder/keyNameFinder.psyexp +214 -158
  329. psychopy/demos/builder/Helper Tools/spatialUnits/unitDemo.psyexp +195 -146
  330. psychopy/demos/coder/experiment control/runtimeInfo.py +1 -1
  331. psychopy/devices/__init__.py +1 -1
  332. psychopy/event.py +1 -1
  333. psychopy/exceptions.py +1 -1
  334. psychopy/experiment/__init__.py +1 -1
  335. psychopy/experiment/_experiment.py +30 -9
  336. psychopy/experiment/components/__init__.py +1 -6
  337. psychopy/experiment/components/_base.py +44 -19
  338. psychopy/experiment/components/aperture/__init__.py +1 -1
  339. psychopy/experiment/components/brush/__init__.py +2 -2
  340. psychopy/experiment/components/button/__init__.py +24 -28
  341. psychopy/experiment/components/camera/__init__.py +38 -39
  342. psychopy/experiment/components/code/__init__.py +1 -1
  343. psychopy/experiment/components/dots/__init__.py +1 -1
  344. psychopy/experiment/components/eyetracker_record/__init__.py +7 -3
  345. psychopy/experiment/components/form/__init__.py +3 -3
  346. psychopy/experiment/components/grating/__init__.py +1 -1
  347. psychopy/experiment/components/image/__init__.py +1 -1
  348. psychopy/experiment/components/joyButtons/__init__.py +2 -2
  349. psychopy/experiment/components/joyButtons/virtualJoyButtons.py +1 -1
  350. psychopy/experiment/components/joystick/__init__.py +3 -3
  351. psychopy/experiment/components/joystick/virtualJoystick.py +1 -1
  352. psychopy/experiment/components/keyboard/__init__.py +98 -122
  353. psychopy/experiment/components/microphone/__init__.py +54 -98
  354. psychopy/experiment/components/mouse/__init__.py +92 -93
  355. psychopy/experiment/components/movie/__init__.py +28 -50
  356. psychopy/experiment/components/parallelOut/__init__.py +3 -3
  357. psychopy/experiment/components/polygon/__init__.py +2 -3
  358. psychopy/experiment/components/roi/__init__.py +2 -2
  359. psychopy/experiment/components/routineSettings/__init__.py +2 -0
  360. psychopy/experiment/components/serialOut/__init__.py +7 -7
  361. psychopy/experiment/components/settings/__init__.py +317 -313
  362. psychopy/experiment/components/settings/eyetracking.py +108 -0
  363. psychopy/experiment/components/slider/__init__.py +5 -5
  364. psychopy/experiment/components/sound/__init__.py +168 -78
  365. psychopy/experiment/components/soundsensor/__init__.py +361 -0
  366. psychopy/experiment/components/soundsensor/classic/soundsensor.png +0 -0
  367. psychopy/experiment/components/soundsensor/classic/soundsensor@2x.png +0 -0
  368. psychopy/experiment/components/soundsensor/dark/soundsensor.png +0 -0
  369. psychopy/experiment/components/soundsensor/dark/soundsensor@2x.png +0 -0
  370. psychopy/experiment/components/soundsensor/light/soundsensor.png +0 -0
  371. psychopy/experiment/components/soundsensor/light/soundsensor@2x.png +0 -0
  372. psychopy/experiment/components/static/__init__.py +59 -33
  373. psychopy/experiment/components/text/__init__.py +2 -6
  374. psychopy/experiment/components/textbox/__init__.py +3 -7
  375. psychopy/experiment/components/unknown/__init__.py +2 -0
  376. psychopy/experiment/components/unknownPlugin/__init__.py +2 -0
  377. psychopy/experiment/exports.py +1 -1
  378. psychopy/experiment/flow.py +2 -2
  379. psychopy/experiment/localization.py +1 -1
  380. psychopy/experiment/loops.py +43 -10
  381. psychopy/experiment/params.py +6 -4
  382. psychopy/experiment/plugins.py +8 -1
  383. psychopy/experiment/py2js.py +1 -1
  384. psychopy/experiment/py2js_transpiler.py +1 -1
  385. psychopy/experiment/routines/__init__.py +1 -1
  386. psychopy/experiment/routines/_base.py +23 -24
  387. psychopy/experiment/routines/audioValidator/__init__.py +343 -0
  388. psychopy/experiment/routines/audioValidator/classic/audio_validator.png +0 -0
  389. psychopy/experiment/routines/audioValidator/classic/audio_validator@2x.png +0 -0
  390. psychopy/experiment/routines/audioValidator/dark/audio_validator.png +0 -0
  391. psychopy/experiment/routines/audioValidator/dark/audio_validator@2x.png +0 -0
  392. psychopy/experiment/routines/audioValidator/light/audio_validator.png +0 -0
  393. psychopy/experiment/routines/audioValidator/light/audio_validator@2x.png +0 -0
  394. psychopy/experiment/routines/eyetracker_calibrate/__init__.py +76 -17
  395. psychopy/experiment/routines/eyetracker_validate/__init__.py +1 -1
  396. psychopy/experiment/routines/unknown/__init__.py +2 -0
  397. psychopy/experiment/routines/{photodiodeValidator → visualValidator}/__init__.py +89 -120
  398. psychopy/experiment/routines/visualValidator/classic/visual_validator.png +0 -0
  399. psychopy/experiment/routines/visualValidator/classic/visual_validator@2x.png +0 -0
  400. psychopy/experiment/routines/visualValidator/dark/visual_validator.png +0 -0
  401. psychopy/experiment/routines/visualValidator/dark/visual_validator@2x.png +0 -0
  402. psychopy/experiment/routines/visualValidator/light/visual_validator.png +0 -0
  403. psychopy/experiment/routines/visualValidator/light/visual_validator@2x.png +0 -0
  404. psychopy/experiment/utils.py +1 -1
  405. psychopy/gui/__init__.py +1 -1
  406. psychopy/gui/qtgui.py +15 -6
  407. psychopy/gui/wxgui.py +1 -1
  408. psychopy/hardware/__init__.py +0 -1
  409. psychopy/hardware/base.py +147 -16
  410. psychopy/hardware/bbtk/__init__.py +10 -16
  411. psychopy/hardware/brainproducts.py +7 -13
  412. psychopy/hardware/button.py +21 -2
  413. psychopy/hardware/buttonbox/__init__.py +1 -1
  414. psychopy/hardware/camera/__init__.py +18 -5
  415. psychopy/hardware/cedrus.py +5 -16
  416. psychopy/hardware/crs/__init__.py +1 -1
  417. psychopy/hardware/crs/bits.py +36 -33
  418. psychopy/hardware/crs/colorcal.py +8 -11
  419. psychopy/hardware/crs/optical.py +7 -10
  420. psychopy/hardware/crs/shaders.py +18 -5
  421. psychopy/hardware/emotiv.py +1 -1
  422. psychopy/hardware/emulator.py +11 -5
  423. psychopy/hardware/exceptions.py +86 -0
  424. psychopy/hardware/forp.py +18 -23
  425. psychopy/hardware/gammasci.py +8 -3
  426. psychopy/hardware/iolab.py +8 -19
  427. psychopy/hardware/joystick/__init__.py +865 -266
  428. psychopy/hardware/joystick/_base.py +251 -0
  429. psychopy/hardware/joystick/backend_glfw.py +306 -0
  430. psychopy/hardware/joystick/backend_pyglet.py +309 -0
  431. psychopy/hardware/joystick/mappings.py +287 -0
  432. psychopy/hardware/keyboard.py +4 -2
  433. psychopy/hardware/labhackers.py +1 -1
  434. psychopy/hardware/labjacks.py +9 -13
  435. psychopy/hardware/{photodiode.py → lightsensor.py} +146 -203
  436. psychopy/hardware/listener.py +9 -8
  437. psychopy/hardware/manager.py +24 -35
  438. psychopy/hardware/microphone.py +535 -155
  439. psychopy/hardware/minolta.py +14 -4
  440. psychopy/hardware/mouse/__init__.py +1 -1
  441. psychopy/hardware/photometer/__init__.py +2 -2
  442. psychopy/hardware/pr.py +14 -4
  443. psychopy/hardware/qmix.py +18 -27
  444. psychopy/hardware/serialdevice.py +43 -12
  445. psychopy/hardware/soundsensor.py +473 -0
  446. psychopy/hardware/spatial/__init__.py +231 -0
  447. psychopy/hardware/speaker.py +298 -36
  448. psychopy/hardware/triggerbox/__init__.py +1 -1
  449. psychopy/hardware/triggerbox/base.py +1 -1
  450. psychopy/hardware/triggerbox/parallel.py +1 -1
  451. psychopy/info.py +1 -1
  452. psychopy/iohub/devices/eyetracker/__init__.py +10 -18
  453. psychopy/iohub/devices/eyetracker/calibration/procedure.py +15 -33
  454. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +2 -2
  455. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -0
  456. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +10 -0
  457. psychopy/iohub/devices/eyetracker/hw/pupil_labs/neon/__init__.py +1 -0
  458. psychopy/iohub/devices/mouse/linux2.py +2 -1
  459. psychopy/layout.py +1 -1
  460. psychopy/liaison.py +91 -39
  461. psychopy/locale_setup.py +11 -1
  462. psychopy/localization/__init__.py +1 -1
  463. psychopy/localization/_localization.py +1 -1
  464. psychopy/localization/messages.pot +2 -2
  465. psychopy/logging.py +14 -12
  466. psychopy/microphone.py +4 -3
  467. psychopy/misc.py +1 -1
  468. psychopy/monitors/MonitorCenter.py +3 -3
  469. psychopy/monitors/__init__.py +1 -1
  470. psychopy/monitors/calibData.py +1 -1
  471. psychopy/monitors/calibTools.py +3 -2
  472. psychopy/platform_specific/__init__.py +1 -1
  473. psychopy/platform_specific/darwin.py +1 -1
  474. psychopy/platform_specific/linux.py +1 -1
  475. psychopy/platform_specific/win32.py +1 -1
  476. psychopy/plugins/__init__.py +26 -17
  477. psychopy/plugins/util.py +65 -0
  478. psychopy/preferences/Darwin.spec +11 -3
  479. psychopy/preferences/FreeBSD.spec +11 -3
  480. psychopy/preferences/Linux.spec +11 -3
  481. psychopy/preferences/Windows.spec +11 -3
  482. psychopy/preferences/__init__.py +1 -1
  483. psychopy/preferences/baseNoArch.spec +11 -3
  484. psychopy/preferences/hints.py +78 -61
  485. psychopy/preferences/preferences.py +82 -13
  486. psychopy/projects/__init__.py +1 -1
  487. psychopy/projects/pavlovia.py +29 -13
  488. psychopy/scripts/psyexpCompile.py +1 -1
  489. psychopy/session.py +81 -8
  490. psychopy/sound/__init__.py +25 -10
  491. psychopy/sound/_base.py +134 -34
  492. psychopy/sound/audioclip.py +38 -15
  493. psychopy/sound/audiodevice.py +1 -1
  494. psychopy/sound/backend_ptb.py +53 -321
  495. psychopy/sound/backend_pygame.py +7 -3
  496. psychopy/sound/backend_pyo.py +53 -22
  497. psychopy/sound/backend_pysound.py +10 -27
  498. psychopy/sound/backend_sounddevice.py +33 -21
  499. psychopy/sound/exceptions.py +1 -1
  500. psychopy/sound/microphone.py +83 -5
  501. psychopy/sound/transcribe.py +3 -3
  502. psychopy/tests/data/test_basic_run.py +1 -0
  503. psychopy/tests/data/test_sounds/default_16000.wav +0 -0
  504. psychopy/tests/data/test_sounds/default_192000.wav +0 -0
  505. psychopy/tests/data/test_sounds/default_22050.wav +0 -0
  506. psychopy/tests/data/test_sounds/default_32000.wav +0 -0
  507. psychopy/tests/data/test_sounds/default_44100.wav +0 -0
  508. psychopy/tests/data/test_sounds/default_48000.wav +0 -0
  509. psychopy/tests/data/test_sounds/default_8000.wav +0 -0
  510. psychopy/tests/data/test_sounds/default_96000.wav +0 -0
  511. psychopy/tests/test_alerts/test_alerttools.py +2 -1
  512. psychopy/tests/test_app/test_command_line.py +65 -0
  513. psychopy/tests/test_data/test_TrialHandler2.py +18 -7
  514. psychopy/tests/test_demos/test_builder_demos.py +1 -1
  515. psychopy/tests/test_experiment/needs_wx/componsTemplate.txt +5238 -4188
  516. psychopy/tests/test_experiment/needs_wx/test_components.py +1 -1
  517. psychopy/tests/test_experiment/test_components/test_RoutineSettingsComponent.py +16 -0
  518. psychopy/tests/test_experiment/test_components/test_all_components.py +1 -1
  519. psychopy/tests/test_experiment/test_components/test_base_components.py +26 -17
  520. psychopy/tests/test_experiment/test_loops.py +12 -4
  521. psychopy/tests/test_experiment/test_params.py +17 -4
  522. psychopy/tests/test_experiment/test_routines/test_PhotodiodeValidationRoutine.py +2 -2
  523. psychopy/tests/test_hardware/test_photodiode.py +66 -0
  524. psychopy/tests/test_liaison/test_Liaison.py +4 -4
  525. psychopy/tests/test_misc/test_clock.py +2 -0
  526. psychopy/tests/test_misc/test_color.py +2 -0
  527. psychopy/tests/test_misc/test_event.py +1 -0
  528. psychopy/tests/test_plugins/__init__.py +0 -0
  529. psychopy/tests/test_plugins/test_plugin_stubs.py +125 -0
  530. psychopy/tests/test_sound/test_sound.py +111 -40
  531. psychopy/tests/test_tools/test_colorspacetools.py +24 -23
  532. psychopy/tests/test_tools/test_mathtools.py +9 -9
  533. psychopy/tests/test_tools/test_viewtools.py +101 -0
  534. psychopy/tests/test_validators/__init__.py +0 -0
  535. psychopy/tests/test_validators/test_voicekeyValidator.py +95 -0
  536. psychopy/tests/test_visual/test_all_stimuli.py +2 -2
  537. psychopy/tests/test_visual/test_basevisual.py +37 -7
  538. psychopy/tests/test_visual/test_button.py +2 -2
  539. psychopy/tests/test_visual/test_circle.py +10 -2
  540. psychopy/tests/test_visual/test_dots.py +1 -1
  541. psychopy/tests/test_visual/test_form.py +13 -13
  542. psychopy/tests/test_visual/test_gamma.py +3 -3
  543. psychopy/tests/test_visual/test_image.py +2 -2
  544. psychopy/tests/test_visual/test_progress.py +2 -2
  545. psychopy/tests/test_visual/test_roi.py +8 -2
  546. psychopy/tests/test_visual/test_shape.py +2 -2
  547. psychopy/tests/test_visual/test_slider.py +2 -2
  548. psychopy/tests/test_visual/test_target.py +2 -2
  549. psychopy/tests/test_visual/test_textbox.py +6 -8
  550. psychopy/tests/test_visual/test_winScalePos.py +6 -5
  551. psychopy/tests/test_visual/test_window.py +17 -0
  552. psychopy/tests/utils.py +51 -1
  553. psychopy/tools/__init__.py +1 -1
  554. psychopy/tools/arraytools.py +2 -2
  555. psychopy/tools/attributetools.py +52 -1
  556. psychopy/tools/audiotools.py +4 -1
  557. psychopy/tools/colorspacetools.py +6 -4
  558. psychopy/tools/coordinatetools.py +1 -1
  559. psychopy/tools/fileerrortools.py +21 -9
  560. psychopy/tools/filetools.py +2 -2
  561. psychopy/tools/fontmanager.py +47 -28
  562. psychopy/tools/gltools.py +2967 -559
  563. psychopy/tools/imagetools.py +1 -1
  564. psychopy/tools/mathtools.py +997 -127
  565. psychopy/tools/monitorunittools.py +7 -1
  566. psychopy/tools/movietools.py +1 -2
  567. psychopy/tools/pkgtools.py +157 -127
  568. psychopy/tools/plottools.py +1 -1
  569. psychopy/tools/rifttools.py +1 -1
  570. psychopy/tools/stereotools.py +1 -1
  571. psychopy/tools/stimulustools.py +172 -2
  572. psychopy/tools/stringtools.py +22 -2
  573. psychopy/tools/systemtools.py +1 -1
  574. psychopy/tools/typetools.py +1 -1
  575. psychopy/tools/unittools.py +1 -1
  576. psychopy/tools/versionchooser.py +3 -1
  577. psychopy/tools/viewtools.py +54 -70
  578. psychopy/tools/wizard.py +2 -2
  579. psychopy/validation/__init__.py +6 -0
  580. psychopy/validation/audio.py +74 -0
  581. psychopy/validation/visual.py +115 -0
  582. psychopy/visual/__init__.py +1 -4
  583. psychopy/visual/aperture.py +9 -6
  584. psychopy/visual/backends/__init__.py +1 -1
  585. psychopy/visual/backends/_base.py +1 -1
  586. psychopy/visual/backends/gamma.py +1 -1
  587. psychopy/visual/backends/glfwbackend.py +8 -12
  588. psychopy/visual/backends/pygamebackend.py +1 -1
  589. psychopy/visual/backends/pygletbackend.py +32 -11
  590. psychopy/visual/basevisual.py +93 -13
  591. psychopy/visual/brush.py +1 -1
  592. psychopy/visual/bufferimage.py +90 -10
  593. psychopy/visual/button.py +1 -1
  594. psychopy/visual/circle.py +12 -20
  595. psychopy/visual/custommouse.py +1 -1
  596. psychopy/visual/dot.py +80 -14
  597. psychopy/visual/elementarray.py +84 -10
  598. psychopy/visual/filters.py +1 -1
  599. psychopy/visual/form.py +43 -28
  600. psychopy/visual/grating.py +105 -25
  601. psychopy/visual/helpers.py +4 -2
  602. psychopy/visual/image.py +98 -20
  603. psychopy/visual/line.py +15 -25
  604. psychopy/visual/movie.py +1 -1
  605. psychopy/visual/movie2.py +4 -3
  606. psychopy/visual/movie3.py +4 -3
  607. psychopy/visual/movies/__init__.py +1 -1
  608. psychopy/visual/movies/frame.py +1 -1
  609. psychopy/visual/movies/metadata.py +1 -1
  610. psychopy/visual/movies/players/__init__.py +1 -1
  611. psychopy/visual/movies/players/_base.py +1 -1
  612. psychopy/visual/movies/players/ffpyplayer_player.py +2 -3
  613. psychopy/visual/nnlvs.py +9 -6
  614. psychopy/visual/noise.py +4 -16
  615. psychopy/visual/patch.py +4 -3
  616. psychopy/visual/pie.py +3 -14
  617. psychopy/visual/polygon.py +21 -28
  618. psychopy/visual/radial.py +4 -18
  619. psychopy/visual/ratingscale.py +4 -3
  620. psychopy/visual/rect.py +16 -25
  621. psychopy/visual/rift.py +8 -2
  622. psychopy/visual/secondorder.py +4 -16
  623. psychopy/visual/shaders.py +620 -286
  624. psychopy/visual/shape.py +281 -90
  625. psychopy/visual/simpleimage.py +1 -1
  626. psychopy/visual/slider.py +78 -25
  627. psychopy/visual/stim3d.py +5 -608
  628. psychopy/visual/text.py +13 -3
  629. psychopy/visual/textbox2/textbox2.py +188 -56
  630. psychopy/visual/vlcmoviestim.py +1 -1
  631. psychopy/visual/window.py +374 -201
  632. psychopy/web.py +1 -1
  633. {psychopy-2024.2.5.dist-info → psychopy-2025.1.1.dist-info}/METADATA +13 -12
  634. {psychopy-2024.2.5.dist-info → psychopy-2025.1.1.dist-info}/RECORD +647 -624
  635. {psychopy-2024.2.5.dist-info → psychopy-2025.1.1.dist-info}/WHEEL +1 -1
  636. psychopy/.DS_Store +0 -0
  637. psychopy/__init__.py.orig +0 -65
  638. psychopy/app/.DS_Store +0 -0
  639. psychopy/app/Resources/.DS_Store +0 -0
  640. psychopy/app/Resources/classic/filecsv16.png +0 -0
  641. psychopy/app/Resources/classic/fileimage16.png +0 -0
  642. psychopy/app/Resources/classic/fileunknown16.png +0 -0
  643. psychopy/app/Resources/dark/filecsv16.png +0 -0
  644. psychopy/app/Resources/dark/filecsv16@2x.png +0 -0
  645. psychopy/app/Resources/dark/fileimage16.png +0 -0
  646. psychopy/app/Resources/dark/fileimage16@2x.png +0 -0
  647. psychopy/app/Resources/dark/fileunknown16.png +0 -0
  648. psychopy/app/Resources/dark/fileunknown16@2x.png +0 -0
  649. psychopy/app/Resources/fonts/OpenSans-Bold.ttf +0 -0
  650. psychopy/app/Resources/fonts/OpenSans-BoldItalic.ttf +0 -0
  651. psychopy/app/Resources/fonts/OpenSans-ExtraBold.ttf +0 -0
  652. psychopy/app/Resources/fonts/OpenSans-ExtraBoldItalic.ttf +0 -0
  653. psychopy/app/Resources/fonts/OpenSans-Italic.ttf +0 -0
  654. psychopy/app/Resources/fonts/OpenSans-Light.ttf +0 -0
  655. psychopy/app/Resources/fonts/OpenSans-LightItalic.ttf +0 -0
  656. psychopy/app/Resources/fonts/OpenSans-Regular.ttf +0 -0
  657. psychopy/app/Resources/fonts/OpenSans-SemiBold.ttf +0 -0
  658. psychopy/app/Resources/fonts/OpenSans-SemiBoldItalic.ttf +0 -0
  659. psychopy/app/Resources/light/filecsv16.png +0 -0
  660. psychopy/app/Resources/light/filecsv16@2x.png +0 -0
  661. psychopy/app/Resources/light/fileimage16.png +0 -0
  662. psychopy/app/Resources/light/fileimage16@2x.png +0 -0
  663. psychopy/app/Resources/light/fileunknown16.png +0 -0
  664. psychopy/app/Resources/light/fileunknown16@2x.png +0 -0
  665. psychopy/app/Resources/psychopySplash.png +0 -0
  666. psychopy/app/Resources/psychopySplash@2x.png +0 -0
  667. psychopy/app/builder/builder.py.orig +0 -3932
  668. psychopy/app/builder/dialogs/__init__.py.orig +0 -1679
  669. psychopy/app/builder/dialogs/paramCtrls.py.orig +0 -713
  670. psychopy/app/colorpicker/__init__.py.orig +0 -411
  671. psychopy/app/locale/ar_001/.DS_Store +0 -0
  672. psychopy/app/locale/ar_001/LC_MESSAGE/.DS_Store +0 -0
  673. psychopy/core.py.orig +0 -169
  674. psychopy/demos/builder/.DS_Store +0 -0
  675. psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks_lastrun.py +0 -330
  676. psychopy/demos/builder/Experiments/.DS_Store +0 -0
  677. psychopy/demos/builder/Experiments/dragAndDrop/archived_conditions.xlsx +0 -0
  678. psychopy/demos/builder/Experiments/dragAndDrop/draw grid stim.py +0 -61
  679. psychopy/demos/builder/Experiments/dragAndDrop/shapeMaker.psyexp +0 -91
  680. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_1.png +0 -0
  681. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_10.png +0 -0
  682. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_2.png +0 -0
  683. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_3.png +0 -0
  684. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_4.png +0 -0
  685. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_5.png +0 -0
  686. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_6.png +0 -0
  687. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_7.png +0 -0
  688. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_8.png +0 -0
  689. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_9.png +0 -0
  690. psychopy/demos/builder/Experiments/dragAndDrop/updated_conditions.xlsx +0 -0
  691. psychopy/demos/builder/Tools/.DS_Store +0 -0
  692. psychopy/demos/builder/Tools/gammaCalibration/.DS_Store +0 -0
  693. psychopy/demos/builder/Tools/gammaCalibration/data/.DS_Store +0 -0
  694. psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.csv +0 -38
  695. psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.log +0 -3418
  696. psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.psydat +0 -0
  697. psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.csv +0 -2
  698. psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.log +0 -15
  699. psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.psydat +0 -0
  700. psychopy/demos/builder/Tools/gammaCalibration/gamma_correction_visual_lastrun.py +0 -562
  701. psychopy/demos/coder/.DS_Store +0 -0
  702. psychopy/demos/coder/experiment control/info_gamma.pickle +0 -0
  703. psychopy/demos/coder/iohub/.iohpid +0 -1
  704. psychopy/demos/coder/iohub/eyetracking/.iohpid +0 -1
  705. psychopy/demos/coder/iohub/wintab/.DS_Store +0 -0
  706. psychopy/demos/coder/stimuli/.DS_Store +0 -0
  707. psychopy/experiment/_experiment.py.orig +0 -1032
  708. psychopy/experiment/components/.DS_Store +0 -0
  709. psychopy/experiment/components/_base.py.orig +0 -823
  710. psychopy/experiment/components/form/.DS_Store +0 -0
  711. psychopy/experiment/components/microphone/__init__.py.orig +0 -490
  712. psychopy/experiment/components/settings/__init__.py.orig +0 -1337
  713. psychopy/experiment/components/textbox/__init__.py.orig +0 -310
  714. psychopy/experiment/components/webcam/.DS_Store +0 -0
  715. psychopy/experiment/components/webcam/light/.DS_Store +0 -0
  716. psychopy/experiment/loops.py.orig +0 -829
  717. psychopy/experiment/params.py.orig +0 -408
  718. psychopy/experiment/routine.py.orig +0 -503
  719. psychopy/experiment/routines/photodiodeValidator/classic/photodiode_validator.png +0 -0
  720. psychopy/experiment/routines/photodiodeValidator/classic/photodiode_validator@2x.png +0 -0
  721. psychopy/experiment/routines/photodiodeValidator/dark/photodiode_validator.png +0 -0
  722. psychopy/experiment/routines/photodiodeValidator/dark/photodiode_validator@2x.png +0 -0
  723. psychopy/experiment/routines/photodiodeValidator/light/photodiode_validator.png +0 -0
  724. psychopy/experiment/routines/photodiodeValidator/light/photodiode_validator@2x.png +0 -0
  725. psychopy/hardware/.DS_Store +0 -0
  726. psychopy/hardware/brainproducts.py.orig +0 -680
  727. psychopy/hardware/iolab.py.orig +0 -238
  728. psychopy/iohub/datastore/__init__.py.orig +0 -443
  729. psychopy/iohub/datastore/util.py.orig +0 -692
  730. psychopy/iohub/devices/mouse/darwin.py.orig +0 -427
  731. psychopy/iohub/devices/mouse/linux2.py.orig +0 -198
  732. psychopy/preferences/.DS_Store +0 -0
  733. psychopy/projects/pavlovia.py.orig +0 -1295
  734. psychopy/tests/.DS_Store +0 -0
  735. psychopy/tests/data/.DS_Store +0 -0
  736. psychopy/tests/data/TestCircle_fill_local.png +0 -0
  737. psychopy/tests/data/aperture1_normHexbackground_local.png +0 -0
  738. psychopy/tests/data/aperture1_norm_local.png +0 -0
  739. psychopy/tests/data/aperture2_normHexbackground_local.png +0 -0
  740. psychopy/tests/data/beatandrcos_height_local.png +0 -0
  741. psychopy/tests/data/beatandrcos_normAddBlend_local.png +0 -0
  742. psychopy/tests/data/beatandrcos_normHexbackground_local.png +0 -0
  743. psychopy/tests/data/beatandrcos_norm_local.png +0 -0
  744. psychopy/tests/data/beatandrcos_stencil_local.png +0 -0
  745. psychopy/tests/data/blend_add_height_local.png +0 -0
  746. psychopy/tests/data/blend_add_normAddBlend_local.png +0 -0
  747. psychopy/tests/data/blend_add_normHexbackground_local.png +0 -0
  748. psychopy/tests/data/blend_add_normNoShade_local.png +0 -0
  749. psychopy/tests/data/blend_add_norm_local.png +0 -0
  750. psychopy/tests/data/blend_add_stencil_local.png +0 -0
  751. psychopy/tests/data/bufferimg_gabor_height_local.png +0 -0
  752. psychopy/tests/data/bufferimg_gabor_normAddBlend_local.png +0 -0
  753. psychopy/tests/data/bufferimg_gabor_normHexbackground_local.png +0 -0
  754. psychopy/tests/data/bufferimg_gabor_normNoShade_local.png +0 -0
  755. psychopy/tests/data/bufferimg_gabor_norm_local.png +0 -0
  756. psychopy/tests/data/bufferimg_gabor_stencil_local.png +0 -0
  757. psychopy/tests/data/circleHex_height_local.png +0 -0
  758. psychopy/tests/data/circleHex_normAddBlend_local.png +0 -0
  759. psychopy/tests/data/circleHex_normHexbackground_local.png +0 -0
  760. psychopy/tests/data/circleHex_normNoShade_local.png +0 -0
  761. psychopy/tests/data/circleHex_norm_local.png +0 -0
  762. psychopy/tests/data/circleHex_stencil_local.png +0 -0
  763. psychopy/tests/data/color_comparison_local.png +0 -0
  764. psychopy/tests/data/correctScript/.DS_Store +0 -0
  765. psychopy/tests/data/dots_height_local.png +0 -0
  766. psychopy/tests/data/dots_normAddBlend_local.png +0 -0
  767. psychopy/tests/data/dots_normHexbackground_local.png +0 -0
  768. psychopy/tests/data/dots_normNoShade_local.png +0 -0
  769. psychopy/tests/data/dots_norm_local.png +0 -0
  770. psychopy/tests/data/dots_stencil_local.png +0 -0
  771. psychopy/tests/data/elarray1_height_local.png +0 -0
  772. psychopy/tests/data/elarray1_normAddBlend_local.png +0 -0
  773. psychopy/tests/data/elarray1_normHexbackground_local.png +0 -0
  774. psychopy/tests/data/elarray1_norm_local.png +0 -0
  775. psychopy/tests/data/elarray1_stencil_local.png +0 -0
  776. psychopy/tests/data/envelopeandrcos_height_local.png +0 -0
  777. psychopy/tests/data/envelopeandrcos_normAddBlend_local.png +0 -0
  778. psychopy/tests/data/envelopeandrcos_normHexbackground_local.png +0 -0
  779. psychopy/tests/data/envelopeandrcos_norm_local.png +0 -0
  780. psychopy/tests/data/envelopeandrcos_stencil_local.png +0 -0
  781. psychopy/tests/data/envelopepowerandrcos_height_local.png +0 -0
  782. psychopy/tests/data/envelopepowerandrcos_normAddBlend_local.png +0 -0
  783. psychopy/tests/data/envelopepowerandrcos_normHexbackground_local.png +0 -0
  784. psychopy/tests/data/envelopepowerandrcos_norm_local.png +0 -0
  785. psychopy/tests/data/envelopepowerandrcos_stencil_local.png +0 -0
  786. psychopy/tests/data/gabor1_height_local.png +0 -0
  787. psychopy/tests/data/gabor1_normAddBlend_local.png +0 -0
  788. psychopy/tests/data/gabor1_normHexbackground_local.png +0 -0
  789. psychopy/tests/data/gabor1_normNoShade_local.png +0 -0
  790. psychopy/tests/data/gabor1_norm_local.png +0 -0
  791. psychopy/tests/data/gabor1_stencil_local.png +0 -0
  792. psychopy/tests/data/greyscale_normHexbackground_local.png +0 -0
  793. psychopy/tests/data/imageAndGauss_height_local.png +0 -0
  794. psychopy/tests/data/imageAndGauss_normAddBlend_local.png +0 -0
  795. psychopy/tests/data/imageAndGauss_normHexbackground_local.png +0 -0
  796. psychopy/tests/data/imageAndGauss_normNoShade_local.png +0 -0
  797. psychopy/tests/data/imageAndGauss_norm_local.png +0 -0
  798. psychopy/tests/data/imageAndGauss_stencil_local.png +0 -0
  799. psychopy/tests/data/movFrame1_stencil_local.png +0 -0
  800. psychopy/tests/data/noiseAndRcos_height_local.png +0 -0
  801. psychopy/tests/data/noiseAndRcos_normAddBlend_local.png +0 -0
  802. psychopy/tests/data/noiseAndRcos_normHexbackground_local.png +0 -0
  803. psychopy/tests/data/noiseAndRcos_normNoShade_local.png +0 -0
  804. psychopy/tests/data/noiseAndRcos_norm_local.png +0 -0
  805. psychopy/tests/data/noiseAndRcos_stencil_local.png +0 -0
  806. psychopy/tests/data/noiseFiltersAndRcos_height_local.png +0 -0
  807. psychopy/tests/data/noiseFiltersAndRcos_normAddBlend_local.png +0 -0
  808. psychopy/tests/data/noiseFiltersAndRcos_normHexbackground_local.png +0 -0
  809. psychopy/tests/data/noiseFiltersAndRcos_normNoShade_local.png +0 -0
  810. psychopy/tests/data/noiseFiltersAndRcos_norm_local.png +0 -0
  811. psychopy/tests/data/noiseFiltersAndRcos_stencil_local.png +0 -0
  812. psychopy/tests/data/numpyImage_height_local.png +0 -0
  813. psychopy/tests/data/numpyImage_normAddBlend_local.png +0 -0
  814. psychopy/tests/data/numpyImage_normHexbackground_local.png +0 -0
  815. psychopy/tests/data/numpyImage_normNoShade_local.png +0 -0
  816. psychopy/tests/data/numpyImage_norm_local.png +0 -0
  817. psychopy/tests/data/numpyImage_stencil_local.png +0 -0
  818. psychopy/tests/data/shape2_1_normAddBlend_local.png +0 -0
  819. psychopy/tests/data/shape2_1_normHexbackground_local.png +0 -0
  820. psychopy/tests/data/shape2_1_normNoShade_local.png +0 -0
  821. psychopy/tests/data/shape2_1_norm_local.png +0 -0
  822. psychopy/tests/data/shape2_1_stencil_local.png +0 -0
  823. psychopy/tests/data/text1_height_local.png +0 -0
  824. psychopy/tests/data/text1_normAddBlend_local.png +0 -0
  825. psychopy/tests/data/text1_normHexbackground_local.png +0 -0
  826. psychopy/tests/data/text1_norm_local.png +0 -0
  827. psychopy/tests/data/text1_stencil_local.png +0 -0
  828. psychopy/tests/data/wedge1_height_local.png +0 -0
  829. psychopy/tests/data/wedge1_normAddBlend_local.png +0 -0
  830. psychopy/tests/data/wedge1_normHexbackground_local.png +0 -0
  831. psychopy/tests/data/wedge1_normNoShade_local.png +0 -0
  832. psychopy/tests/data/wedge1_norm_local.png +0 -0
  833. psychopy/tests/data/wedge1_stencil_local.png +0 -0
  834. psychopy/tests/test_app/.DS_Store +0 -0
  835. psychopy/tests/test_app/test_builder/.DS_Store +0 -0
  836. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.log +0 -177
  837. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.psydat +0 -0
  838. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.xlsx +0 -0
  839. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.log +0 -168
  840. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.psydat +0 -0
  841. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.xlsx +0 -0
  842. psychopy/tests/test_data/.DS_Store +0 -0
  843. psychopy/tests/test_hardware/test_CRS_BitsSharp.py +0 -67
  844. psychopy/tests/test_hardware/test_CRS_BitsSharp.py.orig +0 -68
  845. psychopy/tests/test_hardware/test_CRS_bitsShaders.py +0 -110
  846. psychopy/tests/test_visual/test_image.py.orig +0 -219
  847. psychopy/visual/basevisual.py.orig +0 -1723
  848. psychopy/visual/form.py.orig +0 -1181
  849. psychopy/visual/text.py.orig +0 -752
  850. psychopy/visual/textbox2/textbox2.py.orig +0 -1315
  851. psychopy/visual/windowwarp.py.orig +0 -463
  852. /psychopy/{app/cortex.log → alerts/alertsCatalogue/3600.yaml} +0 -0
  853. /psychopy/{app/Resources → assets}/fonts/Arvo-Bold.ttf +0 -0
  854. /psychopy/{app/Resources → assets}/fonts/Arvo-BoldItalic.ttf +0 -0
  855. /psychopy/{app/Resources → assets}/fonts/Arvo-Italic.ttf +0 -0
  856. /psychopy/{app/Resources → assets}/fonts/Arvo-Regular.ttf +0 -0
  857. /psychopy/{app/Resources → assets}/fonts/DejaVuSerif.ttf +0 -0
  858. /psychopy/{app/Resources → assets}/fonts/IndieFlower-Regular.ttf +0 -0
  859. /psychopy/{app/Resources → assets}/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
  860. /psychopy/{app/Resources → assets}/fonts/JetBrainsMono-VariableFont_wght.ttf +0 -0
  861. /psychopy/demos/builder/Experiments/{GoNoGo → goNoGo}/readme.md +0 -0
  862. {psychopy-2024.2.5.dist-info → psychopy-2025.1.1.dist-info}/entry_points.txt +0 -0
  863. {psychopy-2024.2.5.dist-info → psychopy-2025.1.1.dist-info}/licenses/LICENSE +0 -0
psychopy/tools/gltools.py CHANGED
@@ -5,15 +5,31 @@
5
5
  """
6
6
 
7
7
  # Part of the PsychoPy library
8
- # Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2024 Open Science Tools Ltd.
8
+ # Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2025 Open Science Tools Ltd.
9
9
  # Distributed under the terms of the GNU General Public License (GPL).
10
10
 
11
11
  __all__ = [
12
+ 'VERTEX_ATTRIB_POSITION',
13
+ 'VERTEX_ATTRIB_NORMAL',
14
+ 'VERTEX_ATTRIB_COLOR',
15
+ 'VERTEX_ATTRIB_SECONADRY_COLOR',
16
+ 'VERTEX_ATTRIB_COLOR2',
17
+ 'VERTEX_ATTRIB_FOGCOORD',
18
+ 'VERTEX_ATTRIB_MULTITEXCOORD0',
19
+ 'VERTEX_ATTRIB_MULTITEXCOORD1',
20
+ 'VERTEX_ATTRIB_MULTITEXCOORD2',
21
+ 'VERTEX_ATTRIB_MULTITEXCOORD3',
22
+ 'VERTEX_ATTRIB_MULTITEXCOORD4',
23
+ 'VERTEX_ATTRIB_MULTITEXCOORD5',
24
+ 'VERTEX_ATTRIB_MULTITEXCOORD6',
25
+ 'VERTEX_ATTRIB_MULTITEXCOORD7',
12
26
  'createProgram',
13
27
  'createProgramObjectARB',
14
28
  'compileShader',
15
29
  'compileShaderObjectARB',
16
30
  'embedShaderSourceDefs',
31
+ 'deleteShader',
32
+ 'deleteProgram',
17
33
  'deleteObject',
18
34
  'deleteObjectARB',
19
35
  'attachShader',
@@ -27,6 +43,9 @@ __all__ = [
27
43
  'useProgram',
28
44
  'useProgramObjectARB',
29
45
  'getInfoLog',
46
+ 'setUniformValue',
47
+ 'setUniformMatrix',
48
+ 'getUniformLocation',
30
49
  'getUniformLocations',
31
50
  'getAttribLocations',
32
51
  'createQueryObject',
@@ -35,12 +54,20 @@ __all__ = [
35
54
  'endQuery',
36
55
  'getQuery',
37
56
  'getAbsTimeGPU',
57
+ 'FramebufferInfo',
38
58
  'createFBO',
39
- 'attach',
40
- 'isComplete',
59
+ 'attachImage',
60
+ 'isFramebufferComplete',
41
61
  'deleteFBO',
42
62
  'blitFBO',
43
63
  'useFBO',
64
+ 'bindFBO',
65
+ 'unbindFBO',
66
+ 'deleteFBO',
67
+ 'clearFramebuffer',
68
+ 'setDrawBuffer',
69
+ 'setReadBuffer',
70
+ 'RenderbufferInfo',
44
71
  'createRenderbuffer',
45
72
  'deleteRenderbuffer',
46
73
  'createTexImage2D',
@@ -50,12 +77,15 @@ __all__ = [
50
77
  'createVAO',
51
78
  'drawVAO',
52
79
  'deleteVAO',
80
+ 'drawClientArrays',
53
81
  'VertexBufferInfo',
54
82
  'createVBO',
55
83
  'bindVBO',
56
84
  'unbindVBO',
57
85
  'mapBuffer',
58
86
  'unmapBuffer',
87
+ 'mappedBuffer',
88
+ 'updateVBO',
59
89
  'deleteVBO',
60
90
  'setVertexAttribPointer',
61
91
  'enableVertexAttribArray',
@@ -73,8 +103,17 @@ __all__ = [
73
103
  'createMeshGridFromArrays',
74
104
  'createMeshGrid',
75
105
  'createBox',
106
+ 'createDisc',
107
+ 'createAnnulus',
108
+ 'createCylinder',
76
109
  'transformMeshPosOri',
77
110
  'calculateVertexNormals',
111
+ 'mergeVerticies',
112
+ 'smoothCreases',
113
+ 'flipFaces',
114
+ 'interleaveAttributes',
115
+ 'generateTexCoords',
116
+ 'tesselate',
78
117
  'getIntegerv',
79
118
  'getFloatv',
80
119
  'getString',
@@ -82,7 +121,6 @@ __all__ = [
82
121
  'createTexImage2D',
83
122
  'createTexImage2dFromFile',
84
123
  'bindTexture',
85
- 'unbindTexture',
86
124
  'createCubeMap',
87
125
  'TexCubeMap',
88
126
  'getModelViewMatrix',
@@ -106,33 +144,272 @@ from psychopy.visual.helpers import setColor, findImageFile
106
144
  _thisPlatform = platform.system()
107
145
 
108
146
  # create a query counter to get absolute GPU time
109
-
110
147
  QUERY_COUNTER = None # prevent genQueries from being called
111
148
 
149
+ # vertex attribute locations
150
+ VERTEX_ATTRIB_POSITION = 0 # gl_Vertex
151
+ VERTEX_ATTRIB_NORMAL = 2 # gl_Normal
152
+ VERTEX_ATTRIB_COLOR = 3 # gl_Color
153
+ VERTEX_ATTRIB_SECONADRY_COLOR = VERTEX_ATTRIB_COLOR2 = 4 # gl_SecondaryColor
154
+ VERTEX_ATTRIB_FOGCOORD = 5 # gl_FogCoord
155
+ VERTEX_ATTRIB_MULTITEXCOORD0 = 8 # gl_MultiTexCoord0
156
+ VERTEX_ATTRIB_MULTITEXCOORD1 = 9 # gl_MultiTexCoord1
157
+ VERTEX_ATTRIB_MULTITEXCOORD2 = 10 # gl_MultiTexCoord2
158
+ VERTEX_ATTRIB_MULTITEXCOORD3 = 11 # gl_MultiTexCoord3
159
+ VERTEX_ATTRIB_MULTITEXCOORD4 = 12 # gl_MultiTexCoord4
160
+ VERTEX_ATTRIB_MULTITEXCOORD5 = 13 # gl_MultiTexCoord5
161
+ VERTEX_ATTRIB_MULTITEXCOORD6 = 14 # gl_MultiTexCoord6
162
+ VERTEX_ATTRIB_MULTITEXCOORD7 = 15 # gl_MultiTexCoord7
163
+
164
+ # mapping human-readable names to the typical attribute locations
165
+ VERTEX_ATTRIBS = {
166
+ 'gl_Vertex': VERTEX_ATTRIB_POSITION,
167
+ 'gl_Normal': VERTEX_ATTRIB_NORMAL,
168
+ 'gl_Color': VERTEX_ATTRIB_COLOR,
169
+ 'gl_SecondaryColor': VERTEX_ATTRIB_COLOR2,
170
+ 'gl_FogCoord': VERTEX_ATTRIB_FOGCOORD,
171
+ 'gl_MultiTexCoord0': VERTEX_ATTRIB_MULTITEXCOORD0,
172
+ 'gl_MultiTexCoord1': VERTEX_ATTRIB_MULTITEXCOORD1,
173
+ 'gl_MultiTexCoord2': VERTEX_ATTRIB_MULTITEXCOORD2,
174
+ 'gl_MultiTexCoord3': VERTEX_ATTRIB_MULTITEXCOORD3,
175
+ 'gl_MultiTexCoord4': VERTEX_ATTRIB_MULTITEXCOORD4,
176
+ 'gl_MultiTexCoord5': VERTEX_ATTRIB_MULTITEXCOORD5,
177
+ 'gl_MultiTexCoord6': VERTEX_ATTRIB_MULTITEXCOORD6,
178
+ 'gl_MultiTexCoord7': VERTEX_ATTRIB_MULTITEXCOORD7
179
+ }
112
180
 
113
- # compatible Numpy and OpenGL types for common GL type enums
114
- GL_COMPAT_TYPES = {
115
- GL.GL_FLOAT: (np.float32, GL.GLfloat),
116
- GL.GL_DOUBLE: (np.float64, GL.GLdouble),
117
- GL.GL_UNSIGNED_SHORT: (np.uint16, GL.GLushort),
118
- GL.GL_UNSIGNED_INT: (np.uint32, GL.GLuint),
119
- GL.GL_INT: (np.int32, GL.GLint),
120
- GL.GL_SHORT: (np.int16, GL.GLshort),
121
- GL.GL_HALF_FLOAT: (np.float16, GL.GLhalfARB),
122
- GL.GL_UNSIGNED_BYTE: (np.uint8, GL.GLubyte),
123
- GL.GL_BYTE: (np.int8, GL.GLbyte),
124
- np.float32: (GL.GL_FLOAT, GL.GLfloat),
125
- np.float64: (GL.GL_DOUBLE, GL.GLdouble),
126
- np.uint16: (GL.GL_UNSIGNED_SHORT, GL.GLushort),
127
- np.uint32: (GL.GL_UNSIGNED_INT, GL.GLuint),
128
- np.int32: (GL.GL_INT, GL.GLint),
129
- np.int16: (GL.GL_SHORT, GL.GLshort),
130
- np.float16: (GL.GL_HALF_FLOAT, GL.GLhalfARB),
131
- np.uint8: (GL.GL_UNSIGNED_BYTE, GL.GLubyte),
132
- np.int8: (GL.GL_BYTE, GL.GLbyte)
181
+ # Mappings between Python/Numpy and OpenGL data types for arrays. Duplication
182
+ # simplifies the lookup process when used in functions. Some types are not
183
+ # supported by OpenGL, so they are remapped to the closest compatible type.
184
+ ARRAY_TYPES = {
185
+ 'float32': (GL.GL_FLOAT, GL.GLfloat, np.float32),
186
+ 'float': (GL.GL_FLOAT, GL.GLfloat, np.float32),
187
+ 'double': (GL.GL_DOUBLE, GL.GLdouble, float),
188
+ 'float64': (GL.GL_DOUBLE, GL.GLdouble, float),
189
+ 'uint16': (GL.GL_UNSIGNED_SHORT, GL.GLushort, np.uint16),
190
+ 'unsigned_short': (GL.GL_UNSIGNED_SHORT, GL.GLushort, np.uint16),
191
+ 'uint32': (GL.GL_UNSIGNED_INT, GL.GLuint, np.uint32),
192
+ 'unsigned_int': (GL.GL_UNSIGNED_INT, GL.GLuint, np.uint32),
193
+ 'int': (GL.GL_INT, GL.GLint, np.int32), # remapped to int32 from int64
194
+ 'int32': (GL.GL_INT, GL.GLint, np.int32),
195
+ 'int16': (GL.GL_SHORT, GL.GLshort, np.int16),
196
+ 'short': (GL.GL_SHORT, GL.GLshort, np.int16),
197
+ 'uint8': (GL.GL_UNSIGNED_BYTE, GL.GLubyte, np.uint8),
198
+ 'unsigned_byte': (GL.GL_UNSIGNED_BYTE, GL.GLubyte, np.uint8),
199
+ 'int8': (GL.GL_BYTE, GL.GLbyte, np.int8),
200
+ 'byte': (GL.GL_BYTE, GL.GLbyte, np.int8),
201
+ np.float32: (GL.GL_FLOAT, GL.GLfloat, np.float32),
202
+ float: (GL.GL_DOUBLE, GL.GLdouble, float),
203
+ np.float64: (GL.GL_DOUBLE, GL.GLdouble, np.float64),
204
+ np.uint16: (GL.GL_UNSIGNED_SHORT, GL.GLushort, np.uint16),
205
+ np.uint32: (GL.GL_UNSIGNED_INT, GL.GLuint, np.uint32),
206
+ int: (GL.GL_INT, GL.GLint, np.int32), # remapped to int32
207
+ np.int32: (GL.GL_INT, GL.GLint, np.int32),
208
+ np.int16: (GL.GL_SHORT, GL.GLshort, np.int16),
209
+ np.uint8: (GL.GL_UNSIGNED_BYTE, GL.GLubyte, np.uint8),
210
+ np.int8: (GL.GL_BYTE, GL.GLbyte, np.int8),
211
+ GL.GL_FLOAT: (GL.GL_FLOAT, GL.GLfloat, np.float32),
212
+ GL.GL_DOUBLE: (GL.GL_DOUBLE, GL.GLdouble, float), # python float is 64-bit
213
+ GL.GL_UNSIGNED_SHORT: (GL.GL_UNSIGNED_SHORT, GL.GLushort, np.uint16),
214
+ GL.GL_UNSIGNED_INT: (GL.GL_UNSIGNED_INT, GL.GLuint, np.uint32),
215
+ GL.GL_INT: (GL.GL_INT, GL.GLint, np.int32),
216
+ GL.GL_SHORT: (GL.GL_SHORT, GL.GLshort, np.int16),
217
+ GL.GL_UNSIGNED_BYTE: (GL.GL_UNSIGNED_BYTE, GL.GLubyte, np.uint8),
218
+ GL.GL_BYTE: (GL.GL_BYTE, GL.GLbyte, np.int8),
133
219
  }
134
220
 
135
221
 
222
+ def _getGLEnum(*args):
223
+ """Get the OpenGL enum value from a string or GLEnum.
224
+
225
+ Parameters
226
+ ----------
227
+ args : str, GLEnum, int or None
228
+ OpenGL enum value(s) to retrieve. If `None`, `None` is returned.
229
+
230
+ Returns
231
+ -------
232
+ int or list
233
+ OpenGL enum value(s) in the order they were passed.
234
+
235
+ Examples
236
+ --------
237
+ Get the OpenGL enum value for a single string::
238
+
239
+ _getGLEnum('points') # returns GL.GL_POINTS
240
+
241
+ """
242
+ if args is None:
243
+ return None
244
+ elif len(args) == 1:
245
+ return getattr(GL, args[0]) if isinstance(args[0], str) else args[0]
246
+ else:
247
+ return [getattr(GL, i) if isinstance(i, str) else i for i in args]
248
+
249
+
250
+ def getIntegerv(parName):
251
+ """Get a single integer parameter value, return it as a Python integer.
252
+
253
+ Parameters
254
+ ----------
255
+ pName : int
256
+ OpenGL property enum to query (e.g. GL_MAJOR_VERSION).
257
+
258
+ Returns
259
+ -------
260
+ int
261
+
262
+ """
263
+ val = GL.GLint()
264
+ GL.glGetIntegerv(parName, val)
265
+
266
+ return int(val.value)
267
+
268
+
269
+ def getFloatv(parName):
270
+ """Get a single float parameter value, return it as a Python float.
271
+
272
+ Parameters
273
+ ----------
274
+ pName : float
275
+ OpenGL property enum to query.
276
+
277
+ Returns
278
+ -------
279
+ float
280
+
281
+ """
282
+ val = GL.GLfloat()
283
+ GL.glGetFloatv(parName, val)
284
+
285
+ return float(val.value)
286
+
287
+
288
+ def getString(parName):
289
+ """Get a single string parameter value, return it as a Python UTF-8 string.
290
+
291
+ Parameters
292
+ ----------
293
+ pName : int
294
+ OpenGL property enum to query (e.g. GL_VENDOR).
295
+
296
+ Returns
297
+ -------
298
+ str
299
+
300
+ """
301
+ val = ctypes.cast(GL.glGetString(parName), ctypes.c_char_p).value
302
+ return val.decode('UTF-8')
303
+
304
+
305
+ class OpenGLInfo:
306
+ """OpenGL information class.
307
+
308
+ This class is used to store information about the OpenGL implementation on
309
+ the current machine. It provides a consistent means of querying the OpenGL
310
+ implementation regardless of the OpenGL interface being used.
311
+
312
+ Attributes
313
+ ----------
314
+ vendor : str
315
+ The name of the company responsible for the OpenGL implementation.
316
+ renderer : str
317
+ The name of the renderer.
318
+ version : str
319
+ The version of the OpenGL implementation.
320
+ majorVersion : int
321
+ The major version number of the OpenGL implementation.
322
+ minorVersion : int
323
+ The minor version number of the OpenGL implementation.
324
+ shaderVersion : str
325
+ The version of the GLSL implementation.
326
+ doubleBuffer : int
327
+ Indicates if the OpenGL implementation is double buffered.
328
+ maxTextureSize : int
329
+ The maximum texture size supported by the OpenGL implementation.
330
+ stereo : int
331
+ Indicates if the OpenGL implementation supports stereo rendering.
332
+ maxSamples : int
333
+ The maximum number of samples supported by the OpenGL implementation.
334
+ extensions : list
335
+ A list of supported OpenGL extensions.
336
+
337
+ """
338
+ __slots__ = [
339
+ 'vendor', 'renderer', 'version', 'shaderVersion', 'doubleBuffer',
340
+ 'maxTextureSize', 'maxTextureUnits', 'stereo', 'maxSamples',
341
+ 'extensions']
342
+
343
+ # singleton
344
+ _instance = None
345
+
346
+ def __init__(self):
347
+ self.vendor = getString(GL.GL_VENDOR)
348
+ self.renderer = getString(GL.GL_RENDERER)
349
+ self.version = getString(GL.GL_VERSION)
350
+ self.shaderVersion = getString(GL.GL_SHADING_LANGUAGE_VERSION)
351
+ # self.majorVersion = getIntegerv(GL.GL_MAJOR_VERSION)
352
+ # self.minorVersion = getIntegerv(GL.GL_MINOR_VERSION)
353
+ self.doubleBuffer = getIntegerv(GL.GL_DOUBLEBUFFER)
354
+ self.maxTextureSize = getIntegerv(GL.GL_MAX_TEXTURE_SIZE)
355
+ self.maxTextureUnits = getIntegerv(GL.GL_MAX_TEXTURE_IMAGE_UNITS)
356
+ self.stereo = getIntegerv(GL.GL_STEREO)
357
+ self.maxSamples = getIntegerv(GL.GL_MAX_SAMPLES)
358
+ self.extensions = \
359
+ [i for i in getString(GL.GL_EXTENSIONS).split(' ') if i not in ('', ' ')]
360
+
361
+ def __new__(cls):
362
+ if cls._instance is None:
363
+ cls._instance = super(OpenGLInfo, cls).__new__(cls)
364
+
365
+ return cls._instance
366
+
367
+ def hasExtension(self, extName):
368
+ """Check if the OpenGL implementation supports an extension.
369
+
370
+ Parameters
371
+ ----------
372
+ extName : str
373
+ The name of the extension to check for.
374
+
375
+ Returns
376
+ -------
377
+ bool
378
+ True if the extension is supported, False otherwise.
379
+
380
+ """
381
+ return extName in self.extensions
382
+
383
+
384
+ def getOpenGLInfo():
385
+ """Get general information about the OpenGL implementation on this machine.
386
+ This should provide a consistent means of doing so regardless of the OpenGL
387
+ interface we are using.
388
+
389
+ Returns are dictionary with the following fields::
390
+
391
+ vendor, renderer, version, majorVersion, minorVersion, doubleBuffer,
392
+ maxTextureSize, stereo, maxSamples, extensions
393
+
394
+ Supported extensions are returned as a list in the 'extensions' field. You
395
+ can check if a platform supports an extension by checking the membership of
396
+ the extension name in that list.
397
+
398
+ Returns
399
+ -------
400
+ OpenGLInfo
401
+
402
+ """
403
+ return OpenGLInfo()
404
+
405
+
406
+ # OpenGL limits for this system
407
+ try:
408
+ MAX_TEXTURE_UNITS = getOpenGLInfo().maxTextureUnits
409
+ except:
410
+ MAX_TEXTURE_UNITS = 32
411
+
412
+
136
413
  # -------------------------------
137
414
  # Shader Program Helper Functions
138
415
  # -------------------------------
@@ -302,7 +579,7 @@ def compileShader(shaderSrc, shaderType):
302
579
  shaderId, GL.GL_COMPILE_STATUS, ctypes.byref(result))
303
580
 
304
581
  if result.value == GL.GL_FALSE: # failed to compile for whatever reason
305
- sys.stderr.write(getInfoLog(shaderId) + '\n')
582
+ print(getInfoLog(shaderId) + '\n')
306
583
  deleteObject(shaderId)
307
584
  raise RuntimeError("Shader compilation failed, check log output.")
308
585
 
@@ -477,6 +754,32 @@ def embedShaderSourceDefs(shaderSrc, defs):
477
754
  return srcOut
478
755
 
479
756
 
757
+ def deleteShader(shader):
758
+ """Delete a shader object.
759
+
760
+ Parameters
761
+ ----------
762
+ shader : int
763
+ Shader object handle to delete. Must have originated from a
764
+ :func:`compileShader` or `glCreateShader` call.
765
+
766
+ """
767
+ GL.glDeleteShader(shader)
768
+
769
+
770
+ def deleteProgram(program):
771
+ """Delete a shader program object.
772
+
773
+ Parameters
774
+ ----------
775
+ program : int
776
+ Program object handle to delete. Must have originated from a
777
+ :func:`createProgram` or `glCreateProgram` call.
778
+
779
+ """
780
+ GL.glDeleteProgram(program)
781
+
782
+
480
783
  def deleteObject(obj):
481
784
  """Delete a shader or program object.
482
785
 
@@ -828,6 +1131,322 @@ def getInfoLog(obj):
828
1131
  return logBuffer.value.decode('UTF-8')
829
1132
 
830
1133
 
1134
+ def getUniformLocation(program, name, error=True):
1135
+ """Get the location of a uniform variable in a shader program.
1136
+
1137
+ Parameters
1138
+ ----------
1139
+ program : int
1140
+ Handle of program to retrieve uniform location. Must have originated
1141
+ from a :func:`createProgram`, :func:`createProgramObjectARB`,
1142
+ `glCreateProgram` or `glCreateProgramObjectARB` call.
1143
+ name : str
1144
+ Name of the uniform variable to retrieve the location of.
1145
+ error : bool, optional
1146
+ Raise an error if the uniform is not found. Default is `True`.
1147
+
1148
+ Returns
1149
+ -------
1150
+ int
1151
+ Location of the uniform variable in the program. If the uniform is not
1152
+ found, `-1` is returned.
1153
+
1154
+ """
1155
+ if not GL.glIsProgram(program):
1156
+ raise ValueError(
1157
+ "Specified value of `program` is not a program object handle.")
1158
+
1159
+ if type(name) is not bytes:
1160
+ name = bytes(name, 'utf-8')
1161
+
1162
+ loc = GL.glGetUniformLocation(program, name)
1163
+
1164
+ if error:
1165
+ if loc == -1:
1166
+ raise ValueError(
1167
+ "Uniform not found in program, it may not be defined or has "
1168
+ "been optimized out by the GLSL compiler.")
1169
+ raise ValueError("Uniform '{}' not found in program.".format(name))
1170
+
1171
+ return loc
1172
+
1173
+
1174
+ # functions for setting uniform values
1175
+ _unifValueFuncs = {
1176
+ 'float': {
1177
+ 1: GL.glUniform1f,
1178
+ 2: GL.glUniform2f,
1179
+ 3: GL.glUniform3f,
1180
+ 4: GL.glUniform4f
1181
+ },
1182
+ 'int': {
1183
+ 1: GL.glUniform1i,
1184
+ 2: GL.glUniform2i,
1185
+ 3: GL.glUniform3i,
1186
+ 4: GL.glUniform4i
1187
+ },
1188
+ 'uint': {
1189
+ 1: GL.glUniform1ui,
1190
+ 2: GL.glUniform2ui,
1191
+ 3: GL.glUniform3ui,
1192
+ 4: GL.glUniform4ui
1193
+ }
1194
+ }
1195
+
1196
+
1197
+ def setUniformValue(program, loc, value, unifType='float',
1198
+ ignoreNotDefined=False):
1199
+ """Set a uniform variable value in a shader program.
1200
+
1201
+ This function sets the value of a uniform variable in a shader program to
1202
+ the specified value. The type of the value must match the type of the
1203
+ uniform variable in the shader program. The location of the uniform variable
1204
+ can be specified as an integer or string name. If the uniform variable is
1205
+ not found in the program, a `ValueError` is raised.
1206
+
1207
+ Parameters
1208
+ ----------
1209
+ program : int
1210
+ Handle of program to set the uniform value. Must have originated from a
1211
+ :func:`createProgram`, :func:`createProgramObjectARB`, `glCreateProgram`
1212
+ or `glCreateProgramObjectARB` call.
1213
+ loc : str or int
1214
+ Location of the uniform variable in the program obtained from a
1215
+ :func:`getUniformLocation` call. You may also specify the name of the
1216
+ uniform variable as a string to look-up the location before setting.
1217
+ value : int, float, list, tuple, numpy.ndarray
1218
+ Value to set the uniform to. The type of the value must match the type
1219
+ of the uniform variable in the shader program.
1220
+ unifType : str, optional
1221
+ Type of the uniform value elements. This is used to determine the
1222
+ appropritate setter function. Must be one of 'float', 'int' or 'uint'.
1223
+ ignoreNotDefined : bool, optional
1224
+ Do not raise an error if the uniform is not found in the program.
1225
+ Default is `False`.
1226
+
1227
+ Notes
1228
+ -----
1229
+ * If you get a "Invalid operation. The specified operation is not allowed in
1230
+ the current state" error, it is likely that the shader program is not
1231
+ currently in use or the data is not the correct type or length. Make sure
1232
+ the `unifType` matches the type of the uniform variable in the shader and
1233
+ the length of the data matches the dimensions of the uniform variable.
1234
+
1235
+ Examples
1236
+ --------
1237
+ Set a single float uniform value::
1238
+
1239
+ # define uniform in shader as `uniform float myFloat;`
1240
+ setUniformValue(myProgram, 'myFloat', 0.5)
1241
+
1242
+ Set a 3-element float vector uniform value::
1243
+
1244
+ # define uniform in shader as `uniform vec3 myVec3;`
1245
+ setUniformValue(myProgram, 'myVec3', [0.5, 0.2, 0.8])
1246
+
1247
+ Set an integer uniform value (eg. texture unit index)::
1248
+
1249
+ # define uniform in shader as `uniform sampler2d colorTexture;`
1250
+ setUniformValue(myProgram, 'colorTexture', 0, unifType='int')
1251
+
1252
+ """
1253
+ if not GL.glIsProgram(program):
1254
+ raise ValueError(
1255
+ "Specified value of `program` is not a program object handle.")
1256
+
1257
+ if isinstance(loc, bytes):
1258
+ loc = GL.glGetUniformLocation(program, loc)
1259
+ elif isinstance(loc, str):
1260
+ loc = GL.glGetUniformLocation(program, bytes(loc, 'utf-8'))
1261
+ else:
1262
+ if not isinstance(loc, int):
1263
+ raise ValueError("Invalid type for uniform location.")
1264
+
1265
+ if loc == -1:
1266
+ if ignoreNotDefined:
1267
+ return # ignore if not found
1268
+ raise ValueError("Uniform '{}' not found in program.".format(loc))
1269
+
1270
+ # handle scalar values
1271
+ if isinstance(value, (float, int)):
1272
+ if unifType == 'float':
1273
+ GL.glUniform1f(loc, float(value))
1274
+ elif unifType == 'int':
1275
+ GL.glUniform1i(loc, int(value))
1276
+ elif unifType == 'uint':
1277
+ if value < 0:
1278
+ raise ValueError(
1279
+ "Invalid unsigned integer value, must be >= 0.")
1280
+ GL.glUniform1ui(loc, int(value))
1281
+ else:
1282
+ raise ValueError("Invalid uniform value type.")
1283
+ return
1284
+
1285
+ # passed as list, tuple or numpy array
1286
+ unifLen = len(value)
1287
+ if unifLen > 4:
1288
+ raise ValueError("Invalid uniform data length, must be 1-4 elements.")
1289
+
1290
+ unifSetFunc = _unifValueFuncs[unifType].get(unifLen, None)
1291
+ if unifSetFunc is None:
1292
+ raise ValueError("Invalid uniform data length.")
1293
+
1294
+ unifSetFunc(loc, *value)
1295
+
1296
+
1297
+ def setUniformSampler2D(program, loc, unit, ignoreNotDefined=False):
1298
+ """Set a 2D texture sampler uniform value in a shader program.
1299
+
1300
+ Parameters
1301
+ ----------
1302
+ program : int
1303
+ Handle of program to set the uniform value. Must have originated from a
1304
+ :func:`createProgram`, :func:`createProgramObjectARB`, `glCreateProgram`
1305
+ or `glCreateProgramObjectARB` call.
1306
+ loc : str or int
1307
+ Location of the uniform variable in the program obtained from a
1308
+ :func:`getUniformLocation` call. You may also specify the name of the
1309
+ uniform variable as a string to look-up the location before setting.
1310
+ unit : int
1311
+ Texture unit index to bind to the sampler uniform.
1312
+ ignoreNotDefined : bool, optional
1313
+ Do not raise an error if the uniform is not found in the program.
1314
+ Default is `False`.
1315
+
1316
+ Examples
1317
+ --------
1318
+ Set a 2D texture sampler uniform value::
1319
+
1320
+ # define uniform in shader as `uniform sampler2D colorTexture;`
1321
+ setUniformSampler2D(myProgram, 'colorTexture', 0)
1322
+
1323
+ Use the enum value for the texture unit::
1324
+
1325
+ setUniformSampler2D(myProgram, 'colorTexture', GL.GL_TEXTURE0)
1326
+ # or ...
1327
+ setUniformSampler2D(myProgram, 'colorTexture', 'GL_TEXTURE0')
1328
+
1329
+ """
1330
+ if not GL.glIsProgram(program):
1331
+ raise ValueError(
1332
+ "Specified value of `program` is not a program object handle.")
1333
+
1334
+ if isinstance(loc, bytes):
1335
+ loc = GL.glGetUniformLocation(program, loc)
1336
+ elif isinstance(loc, str):
1337
+ loc = GL.glGetUniformLocation(program, bytes(loc, 'utf-8'))
1338
+ else:
1339
+ if not isinstance(loc, int):
1340
+ raise ValueError("Invalid type for uniform location.")
1341
+
1342
+ if loc == -1:
1343
+ if ignoreNotDefined:
1344
+ return # ignore if not found
1345
+ raise ValueError("Uniform '{}' not found in program.".format(loc))
1346
+
1347
+ if isinstance(unit, str):
1348
+ unit = getattr(GL, unit)
1349
+
1350
+ if unit >= GL.GL_TEXTURE0: # got enum value
1351
+ unit = unit - GL.GL_TEXTURE0
1352
+
1353
+ GL.glUniform1i(loc, unit)
1354
+
1355
+
1356
+ # lookup table for matrix uniform setter functions, the keys are hashable
1357
+ # tuples of the matrix dimensions
1358
+ _unifMatrixFuncs = {
1359
+ (2, 2): GL.glUniformMatrix2fv,
1360
+ (3, 3): GL.glUniformMatrix3fv,
1361
+ (4, 4): GL.glUniformMatrix4fv,
1362
+ (2, 3): GL.glUniformMatrix2x3fv,
1363
+ (3, 2): GL.glUniformMatrix3x2fv,
1364
+ (2, 4): GL.glUniformMatrix2x4fv,
1365
+ (4, 2): GL.glUniformMatrix4x2fv,
1366
+ (3, 4): GL.glUniformMatrix3x4fv,
1367
+ (4, 3): GL.glUniformMatrix4x3fv
1368
+ }
1369
+
1370
+
1371
+ def setUniformMatrix(program, loc, value, transpose=False,
1372
+ ignoreNotDefined=False):
1373
+ """Set the value of a matrix uniform variable in a shader program.
1374
+
1375
+ Arrays are converted to contiguous arrays with 'float32' data type before
1376
+ setting.
1377
+
1378
+ Parameters
1379
+ ----------
1380
+ program : int
1381
+ Handle of program to set the uniform value. Must have originated from a
1382
+ :func:`createProgram`, :func:`createProgramObjectARB`, `glCreateProgram`
1383
+ or `glCreateProgramObjectARB` call.
1384
+ loc : str or int
1385
+ Location of the uniform variable in the program obtained from a
1386
+ :func:`getUniformLocation` call. You may also specify the name of the
1387
+ uniform variable as a string to look-up the location before setting.
1388
+ value : numpy.ndarray
1389
+ Matrix value to set the uniform to. The shape of the matrix must match
1390
+ the dimensions of the uniform variable in the shader program. The data
1391
+ type of the matrix must be `float32` or else it will be cast to
1392
+ `float32`.
1393
+ transpose : bool, optional
1394
+ Transpose the matrix before setting. Default is `False`.
1395
+ ignoreNotDefined : bool, optional
1396
+ Do not raise an error if the uniform is not found in the program.
1397
+ Default is `False`.
1398
+
1399
+ Notes
1400
+ -----
1401
+ * If you get a "Invalid operation. The specified operation is not allowed in
1402
+ the current state" error, it is likely that the shader program is not
1403
+ currently in use or the data is not the correct type or length. Make sure
1404
+ the `unifType` matches the type of the uniform variable in the shader and
1405
+ the length of the data matches the dimensions of the uniform variable.
1406
+
1407
+ Examples
1408
+ --------
1409
+ Set a 4x4 matrix uniform value::
1410
+
1411
+ # define uniform in shader as `uniform mat4 projectionMatrix;`
1412
+ setUniformMatrix(myProgram, 'projectionMatrix', np.eye(4))
1413
+
1414
+ """
1415
+ if not GL.glIsProgram(program):
1416
+ raise ValueError(
1417
+ "Specified value of `program` is not a program object handle.")
1418
+
1419
+ locInput = loc # for error message
1420
+ if isinstance(loc, bytes):
1421
+ loc = GL.glGetUniformLocation(program, loc)
1422
+ elif isinstance(loc, str):
1423
+ loc = GL.glGetUniformLocation(program, bytes(loc, 'utf-8'))
1424
+ else:
1425
+ if not isinstance(loc, int):
1426
+ raise ValueError("Invalid type for uniform location.")
1427
+
1428
+ if loc == -1:
1429
+ if ignoreNotDefined:
1430
+ return # ignore if not found
1431
+ raise ValueError((
1432
+ "Uniform '{}' not found in program. It is either not defined "
1433
+ "or it is unused.").format(locInput))
1434
+
1435
+ # convert to contiguous array
1436
+ value = np.ascontiguousarray(value, dtype=np.float32)
1437
+
1438
+ # handle scalar values
1439
+ matrixFunc = _unifMatrixFuncs.get(value.shape, None)
1440
+ if matrixFunc is None:
1441
+ raise ValueError("Invalid matrix dimensions")
1442
+
1443
+ transpose = GL.GL_TRUE if transpose else GL.GL_FALSE
1444
+
1445
+ # recast as pointer
1446
+ matrixFunc(loc, 1, transpose, value.ctypes.data_as(
1447
+ ctypes.POINTER(GL.GLfloat)))
1448
+
1449
+
831
1450
  def getUniformLocations(program, builtins=False):
832
1451
  """Get uniform names and locations from a given shader program object.
833
1452
 
@@ -1139,28 +1758,92 @@ Framebuffer = namedtuple(
1139
1758
  )
1140
1759
 
1141
1760
 
1142
- def createFBO(attachments=()):
1143
- """Create a Framebuffer Object.
1761
+ class FramebufferInfo:
1762
+ """Framebuffer object (VBO) descriptor.
1763
+
1764
+ This class only stores information about the FBO it refers to, it does not
1765
+ contain any actual array data associated with the FBO. Calling
1766
+ :func:`createFBO` returns instances of this class.
1767
+
1768
+ It is recommended to use `gltools` functions :func:`bindFBO`,
1769
+ :func:`unbindFBO` and :func:`deleteFBO` to manage FBOs.
1144
1770
 
1145
1771
  Parameters
1146
1772
  ----------
1147
- attachments : :obj:`list` or :obj:`tuple` of :obj:`tuple`
1148
- Optional attachments to initialize the Framebuffer with. Attachments are
1149
- specified as a list of tuples. Each tuple must contain an attachment
1150
- point (e.g. GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, etc.) and a
1151
- buffer descriptor type (Renderbuffer or TexImage2D). If using a combined
1152
- depth/stencil format such as GL_DEPTH24_STENCIL8, GL_DEPTH_ATTACHMENT
1153
- and GL_STENCIL_ATTACHMENT must be passed the same buffer. Alternatively,
1154
- one can use GL_DEPTH_STENCIL_ATTACHMENT instead. If using multisample
1155
- buffers, all attachment images must use the same number of samples!. As
1156
- an example, one may specify attachments as 'attachments=((
1157
- GL.GL_COLOR_ATTACHMENT0, frameTexture), (GL.GL_DEPTH_STENCIL_ATTACHMENT,
1158
- depthRenderBuffer))'.
1159
-
1160
- Returns
1161
- -------
1162
- Framebuffer
1163
- Framebuffer descriptor.
1773
+ name : int
1774
+ Handle of the FBO.
1775
+ target : int
1776
+ Target of the FBO.
1777
+ attachments : dict, optional
1778
+ Dictionary of attachments associated with the FBO. The keys are
1779
+ attachment points (e.g. GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, etc.)
1780
+ and the values are buffer descriptors (`RenderbufferInfo` or
1781
+ `TexImage2DInfo`).
1782
+ sizeHint : tuple, optional
1783
+ Size hint for the FBO. This is used to specify the dimensions of logical
1784
+ buffers attached to the FBO. The size hint is a tuple of two integers
1785
+ (width, height).
1786
+ userData : dict, optional
1787
+ User-defined data associated with the FBO.
1788
+
1789
+ """
1790
+ __slots__ = [
1791
+ 'name', 'target', '_attachments', 'sizeHint', 'userData', '_lastBound']
1792
+
1793
+ def __init__(self, name, target=GL.GL_FRAMEBUFFER, attachments=None,
1794
+ sizeHint=None, userData=None):
1795
+ self.name = name
1796
+ self.target = target
1797
+ self._attachments = {} if attachments is None else attachments
1798
+ self.sizeHint = sizeHint
1799
+ self.userData = userData
1800
+
1801
+ self._lastBound = GL.GLint()
1802
+
1803
+ def __enter__(self):
1804
+ GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, ctypes.byref(self.lastBound))
1805
+ GL.glBindFramebuffer(self.fbo.target, self.fbo.id)
1806
+
1807
+ def __exit__(self, exc_type, exc_val, exc_tb):
1808
+ GL.glBindFramebuffer(self.fbo.target, self.lastBound.value)
1809
+
1810
+ @property
1811
+ def attachments(self):
1812
+ """Image buffer attachments associated with the FBO (`dict`).
1813
+
1814
+ Do not modify this dictionary directly. Use :func:`attachImage` to
1815
+ attach images to the FBO.
1816
+
1817
+ """
1818
+ return self._attachments
1819
+
1820
+
1821
+ def createFBO(attachments=(), sizeHint=None):
1822
+ """Create a Framebuffer Object.
1823
+
1824
+ Parameters
1825
+ ----------
1826
+ attachments : :obj:`list` or :obj:`tuple` of :obj:`tuple`
1827
+ Optional attachments to initialize the Framebuffer with. Attachments are
1828
+ specified as a list of tuples. Each tuple must contain an attachment
1829
+ point (e.g. GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, etc.) and a
1830
+ buffer descriptor type (RenderbufferInfo or TexImage2DInfo). If using a
1831
+ combined depth/stencil format such as GL_DEPTH24_STENCIL8,
1832
+ GL_DEPTH_ATTACHMENT and GL_STENCIL_ATTACHMENT must be passed the same
1833
+ buffer. Alternatively, one can use GL_DEPTH_STENCIL_ATTACHMENT instead.
1834
+ If using multisample buffers, all attachment images must use the same
1835
+ number of samples!. As an example, one may specify attachments as
1836
+ 'attachments=((GL.GL_COLOR_ATTACHMENT0, frameTexture),
1837
+ (GL.GL_DEPTH_STENCIL_ATTACHMENT, depthRenderBuffer))'.
1838
+ sizeHint : :obj:`tuple`, optional
1839
+ Size hint for the FBO. This is used to specify the dimensions of logical
1840
+ buffers attached to the FBO. The size hint is a tuple of two integers
1841
+ (width, height).
1842
+
1843
+ Returns
1844
+ -------
1845
+ FramebufferInfo
1846
+ Framebuffer descriptor.
1164
1847
 
1165
1848
  Notes
1166
1849
  -----
@@ -1177,22 +1860,18 @@ def createFBO(attachments=()):
1177
1860
 
1178
1861
  Create a render target with multiple color texture attachments::
1179
1862
 
1180
- colorTex = createTexImage2D(1024,1024) # empty texture
1181
- depthRb = createRenderbuffer(800,600,internalFormat=GL.GL_DEPTH24_STENCIL8)
1182
-
1183
- # attach images
1184
- GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, fbo.id)
1185
- attach(GL.GL_COLOR_ATTACHMENT0, colorTex)
1186
- attach(GL.GL_DEPTH_ATTACHMENT, depthRb)
1187
- attach(GL.GL_STENCIL_ATTACHMENT, depthRb)
1188
- # or attach(GL.GL_DEPTH_STENCIL_ATTACHMENT, depthRb)
1189
- GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0)
1190
-
1191
- # above is the same as
1192
- with useFBO(fbo):
1193
- attach(GL.GL_COLOR_ATTACHMENT0, colorTex)
1194
- attach(GL.GL_DEPTH_ATTACHMENT, depthRb)
1195
- attach(GL.GL_STENCIL_ATTACHMENT, depthRb)
1863
+ fbo = gt.createFBO(sizeHint=(512, 512))
1864
+ gt.bindFBO(fbo)
1865
+ gt.attachImage( # color
1866
+ fbo, GL.GL_COLOR_ATTACHMENT0, gt.createTexImage2D(512, 512))
1867
+ gt.attachImage( # normal map
1868
+ fbo, GL.GL_COLOR_ATTACHMENT1, gt.createTexImage2D(512, 512))
1869
+ gt.attachImage( # depth/stencil
1870
+ fbo,
1871
+ GL.GL_DEPTH_STENCIL_ATTACHMENT,
1872
+ gt.createRenderbuffer(512, 512, GL.GL_DEPTH24_STENCIL8))
1873
+ print(gt.isFramebufferComplete(fbo)) # True
1874
+ gt.unbindFBO(None)
1196
1875
 
1197
1876
  Examples of userData some custom function might access::
1198
1877
 
@@ -1213,26 +1892,34 @@ def createFBO(attachments=()):
1213
1892
  GL.glGenFramebuffers(1, ctypes.byref(fboId))
1214
1893
 
1215
1894
  # create a framebuffer descriptor
1216
- fboDesc = Framebuffer(fboId, GL.GL_FRAMEBUFFER, dict())
1895
+ fboDesc = FramebufferInfo(
1896
+ fboId,
1897
+ GL.GL_FRAMEBUFFER,
1898
+ attachments=None, # set later
1899
+ sizeHint=sizeHint,
1900
+ userData=dict())
1217
1901
 
1218
1902
  # initial attachments for this framebuffer
1219
1903
  if attachments:
1220
- with useFBO(fboDesc):
1221
- for attachPoint, imageBuffer in attachments:
1222
- attach(attachPoint, imageBuffer)
1904
+ bindFBO(fboDesc)
1905
+ for attachPoint, imageBuffer in attachments:
1906
+ attachImage(fboDesc, attachPoint, imageBuffer)
1907
+ unbindFBO()
1223
1908
 
1224
1909
  return fboDesc
1225
1910
 
1226
1911
 
1227
- def attach(attachPoint, imageBuffer):
1912
+ def attachImage(fbo, attachPoint, imageBuffer):
1228
1913
  """Attach an image to a specified attachment point on the presently bound
1229
1914
  FBO.
1230
1915
 
1231
1916
  Parameters
1232
1917
  ----------
1918
+ fbo : :obj:`FramebufferInfo`
1919
+ Framebuffer descriptor to attach buffer to.
1233
1920
  attachPoint :obj:`int`
1234
1921
  Attachment point for 'imageBuffer' (e.g. GL.GL_COLOR_ATTACHMENT0).
1235
- imageBuffer : :obj:`TexImage2D` or :obj:`Renderbuffer`
1922
+ imageBuffer : :obj:`TexImage2D` or :obj:`RenderbufferInfo`
1236
1923
  Framebuffer-attachable buffer descriptor.
1237
1924
 
1238
1925
  Examples
@@ -1240,55 +1927,131 @@ def attach(attachPoint, imageBuffer):
1240
1927
  Attach an image to attachment points on the framebuffer::
1241
1928
 
1242
1929
  GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, fbo)
1243
- attach(GL.GL_COLOR_ATTACHMENT0, colorTex)
1244
- attach(GL.GL_DEPTH_STENCIL_ATTACHMENT, depthRb)
1930
+ attachImage(GL.GL_COLOR_ATTACHMENT0, colorTex)
1931
+ attachImage(GL.GL_DEPTH_STENCIL_ATTACHMENT, depthRb)
1245
1932
  GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, lastBoundFbo)
1246
1933
 
1247
1934
  # same as above, but using a context manager
1248
1935
  with useFBO(fbo):
1249
- attach(GL.GL_COLOR_ATTACHMENT0, colorTex)
1250
- attach(GL.GL_DEPTH_STENCIL_ATTACHMENT, depthRb)
1936
+ attachImage(GL.GL_COLOR_ATTACHMENT0, colorTex)
1937
+ attachImage(GL.GL_DEPTH_STENCIL_ATTACHMENT, depthRb)
1251
1938
 
1252
1939
  """
1940
+ if not isinstance(fbo, FramebufferInfo):
1941
+ raise ValueError("Invalid type for `fbo`, must be `FramebufferInfo`.")
1942
+
1943
+ if isinstance(attachPoint, str):
1944
+ attachPoint = getattr(GL, attachPoint)
1945
+
1946
+ # get the last framebuffer bound
1947
+ lastBound = GL.GLint()
1948
+ GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, ctypes.byref(lastBound))
1949
+
1950
+ # bind the framebuffer
1951
+ changeBinding = fbo.name != lastBound.value
1952
+ if changeBinding:
1953
+ bindFBO(fbo)
1954
+
1955
+ if fbo.sizeHint is not None:
1956
+ if (fbo.sizeHint[0] != imageBuffer.width or
1957
+ fbo.sizeHint[1] != imageBuffer.height):
1958
+ raise ValueError(
1959
+ "Imagebuffer dimensions do not match FBO size hint. Expected "
1960
+ "({}, {}), got ({}, {}).".format(
1961
+ fbo.sizeHint[0], fbo.sizeHint[1],
1962
+ imageBuffer.width, imageBuffer.height))
1963
+
1253
1964
  # We should also support binding GL names specified as integers. Right now
1254
1965
  # you need as descriptor which contains the target and name for the buffer.
1255
1966
  #
1256
- if isinstance(imageBuffer, (TexImage2D, TexImage2DMultisample)):
1967
+ if isinstance(imageBuffer, (TexImage2DInfo, TexImage2DMultisampleInfo)):
1257
1968
  GL.glFramebufferTexture2D(
1258
- GL.GL_FRAMEBUFFER,
1969
+ fbo.target,
1259
1970
  attachPoint,
1260
1971
  imageBuffer.target,
1261
- imageBuffer.id, 0)
1262
- elif isinstance(imageBuffer, Renderbuffer):
1972
+ imageBuffer.name, 0)
1973
+ elif isinstance(imageBuffer, RenderbufferInfo):
1263
1974
  GL.glFramebufferRenderbuffer(
1264
- GL.GL_FRAMEBUFFER,
1975
+ fbo.target,
1265
1976
  attachPoint,
1266
1977
  imageBuffer.target,
1267
- imageBuffer.id)
1978
+ imageBuffer.name)
1979
+
1980
+ fbo.attachments[attachPoint] = imageBuffer
1981
+
1982
+ # restore the last framebuffer
1983
+ if changeBinding:
1984
+ GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, lastBound.value)
1985
+
1986
+
1987
+ # legacy
1988
+ attach = attachImage
1268
1989
 
1269
1990
 
1270
- def isComplete():
1991
+ def isFramebufferComplete(fbo):
1271
1992
  """Check if the currently bound framebuffer is complete.
1272
1993
 
1994
+ Parameters
1995
+ ----------
1996
+ fbo : :obj:`FramebufferInfo`
1997
+ Framebuffer descriptor to check for completeness.
1998
+
1273
1999
  Returns
1274
2000
  -------
1275
2001
  bool
1276
2002
  `True` if the presently bound FBO is complete.
1277
2003
 
1278
2004
  """
1279
- return GL.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER) == \
2005
+ # get the last framebuffer bound
2006
+ lastBound = GL.GLint()
2007
+ GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, ctypes.byref(lastBound))
2008
+
2009
+ # bind the framebuffer
2010
+ changeBinding = fbo.name != lastBound.value
2011
+ if changeBinding:
2012
+ bindFBO(fbo)
2013
+
2014
+ status = GL.glCheckFramebufferStatus(fbo.target) == \
1280
2015
  GL.GL_FRAMEBUFFER_COMPLETE
2016
+
2017
+ # restore the last framebuffer
2018
+ if changeBinding:
2019
+ GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, lastBound.value)
2020
+
2021
+ return status
1281
2022
 
1282
2023
 
1283
- def deleteFBO(fbo):
2024
+ def deleteFBO(fbo, deleteAttachments=True):
1284
2025
  """Delete a framebuffer.
1285
2026
 
2027
+ Parameters
2028
+ ----------
2029
+ fbo : :obj:`FramebufferInfo` or :obj:`int`
2030
+ Framebuffer descriptor or name to delete.
2031
+ deleteAttachments : bool, optional
2032
+ Delete attachments associated with the framebuffer. Default is `True`.
2033
+
1286
2034
  """
1287
- GL.glDeleteFramebuffers(
1288
- 1, fbo.id if isinstance(fbo, Framebuffer) else int(fbo))
2035
+ if not isinstance(fbo, FramebufferInfo):
2036
+ raise ValueError("Invalid type for `fbo`, must be `FramebufferInfo`.")
2037
+
2038
+ if deleteAttachments:
2039
+ for attachment in fbo.attachments.values():
2040
+ if isinstance(attachment, RenderbufferInfo):
2041
+ deleteRenderbuffer(attachment)
2042
+ elif isinstance(attachment,
2043
+ (TexImage2DInfo, TexImage2DMultisampleInfo)):
2044
+ deleteTexture(attachment)
2045
+
2046
+ GL.glDeleteFramebuffers(1, fbo.name)
1289
2047
 
2048
+ # invalidate the descriptor
2049
+ fbo.name = 0
2050
+ fbo.target = 0
2051
+ fbo.attachments.clear()
1290
2052
 
1291
- def blitFBO(srcRect, dstRect=None, filter=GL.GL_LINEAR):
2053
+
2054
+ def blitFBO(srcRect, dstRect=None, filter=GL.GL_LINEAR, mask=GL.GL_COLOR_BUFFER_BIT):
1292
2055
  """Copy a block of pixels between framebuffers via blitting. Read and draw
1293
2056
  framebuffers must be bound prior to calling this function. Beware, the
1294
2057
  scissor box and viewport are changed when this is called to dstRect.
@@ -1302,43 +2065,181 @@ def blitFBO(srcRect, dstRect=None, filter=GL.GL_LINEAR):
1302
2065
  List specifying the top-left and bottom-right coordinates of the region
1303
2066
  to copy to (<X0>, <Y0>, <X1>, <Y1>). If None, srcRect is used for
1304
2067
  dstRect.
1305
- filter : :obj:`int`
2068
+ filter : :obj:`int` or :obj:`str`
1306
2069
  Interpolation method to use if the image is stretched, default is
1307
2070
  GL_LINEAR, but can also be GL_NEAREST.
1308
-
1309
- Returns
1310
- -------
1311
- None
2071
+ mask : :obj:`int` or :obj:`str`
2072
+ Bitmask specifying which buffers to copy. Default is GL_COLOR_BUFFER_BIT.
1312
2073
 
1313
2074
  Examples
1314
2075
  --------
1315
2076
  Blitting pixels from on FBO to another::
1316
2077
 
1317
- # bind framebuffer to read pixels from
1318
- GL.glBindFramebuffer(GL.GL_READ_FRAMEBUFFER, srcFbo)
1319
-
1320
- # bind framebuffer to draw pixels to
1321
- GL.glBindFramebuffer(GL.GL_DRAW_FRAMEBUFFER, dstFbo)
1322
-
1323
- gltools.blitFBO((0,0,800,600), (0,0,800,600))
2078
+ # set buffers for reading and drawing
2079
+ gt.setReadBuffer(fbo, 'GL_COLOR_ATTACHMENT0')
2080
+ gt.setDrawBuffer(None, 'GL_BACK') # default back buffer for window
2081
+ gt.blitFBO((0 ,0, 512, 512), (0, 0, 512, 512))
2082
+
2083
+ """
2084
+ if isinstance(mask, str):
2085
+ mask = getattr(GL, mask)
1324
2086
 
1325
- # unbind both read and draw buffers
1326
- GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0)
2087
+ if isinstance(filter, str):
2088
+ filter = getattr(GL, filter)
1327
2089
 
1328
- """
1329
2090
  # in most cases srcRect and dstRect will be the same.
1330
2091
  if dstRect is None:
1331
2092
  dstRect = srcRect
1332
2093
 
1333
- # GL.glViewport(*dstRect)
1334
- # GL.glEnable(GL.GL_SCISSOR_TEST)
1335
- # GL.glScissor(*dstRect)
1336
2094
  GL.glBlitFramebuffer(srcRect[0], srcRect[1], srcRect[2], srcRect[3],
1337
2095
  dstRect[0], dstRect[1], dstRect[2], dstRect[3],
1338
- GL.GL_COLOR_BUFFER_BIT, # colors only for now
2096
+ mask, # colors only for now
1339
2097
  filter)
2098
+
2099
+
2100
+ def setReadBuffer(fbo, mode):
2101
+ """Set the read buffer for the framebuffer.
1340
2102
 
1341
- # GL.glDisable(GL.GL_SCISSOR_TEST)
2103
+ Parameters
2104
+ ----------
2105
+ fbo : :obj:`FramebufferInfo` or `None`
2106
+ Framebuffer descriptor. If `None`, the framebuffer with target
2107
+ `GL_FRAMEBUFFER` is bound to `0` (default framebuffer).
2108
+ mode : :obj:`int`, :obj:`GLenum` or :obj:`str`
2109
+ Buffer mode to set as the read buffer. If `fbo` is not `None`, this
2110
+ value may be `GL_COLOR_ATTACHMENT(i)` where `i` is the color attachment
2111
+ index. If `fbo` is `None`, this value may be one of `GL_FRONT_LEFT`,
2112
+ `GL_FRONT_RIGHT`, `GL_BACK_LEFT`, `GL_BACK_RIGHT`, `GL_FRONT`,
2113
+ `GL_BACK`, `GL_LEFT`, `GL_RIGHT`.
2114
+
2115
+ Examples
2116
+ --------
2117
+ Set the read buffer for a framebuffer::
2118
+
2119
+ setReadBuffer(fbo, GL_COLOR_ATTACHMENT0)
2120
+
2121
+ """
2122
+ if isinstance(mode, str):
2123
+ mode = getattr(GL, mode)
2124
+
2125
+ if fbo is None:
2126
+ GL.glBindFramebuffer(GL.GL_READ_FRAMEBUFFER, 0)
2127
+ GL.glReadBuffer(mode)
2128
+ return
2129
+
2130
+ if not isinstance(fbo, FramebufferInfo):
2131
+ raise ValueError(
2132
+ "Invalid type for `fbo`, must be `FramebufferInfo`.")
2133
+
2134
+ GL.glBindFramebuffer(GL.GL_READ_FRAMEBUFFER, fbo.name)
2135
+ GL.glReadBuffer(mode)
2136
+
2137
+
2138
+ def setDrawBuffer(fbo, mode):
2139
+ """Set the draw buffer for the framebuffer.
2140
+
2141
+ Parameters
2142
+ ----------
2143
+ fbo : :obj:`FramebufferInfo` or `None`
2144
+ Framebuffer descriptor. If `None`, the framebuffer with target
2145
+ `GL_FRAMEBUFFER` is bound to `0` (default framebuffer).
2146
+ mode : :obj:`int`, :obj:`GLenum` or :obj:`str`
2147
+ Buffer mode to set as the draw buffer. If `fbo` is not `None`, this
2148
+ value may be `GL_COLOR_ATTACHMENT(i)` where `i` is the color attachment
2149
+ index. If `fbo` is `None`, this value may be one of `GL_FRONT_LEFT`,
2150
+ `GL_FRONT_RIGHT`, `GL_BACK_LEFT`, `GL_BACK_RIGHT`, `GL_FRONT`,
2151
+ `GL_BACK`, `GL_LEFT`, `GL_RIGHT`.
2152
+
2153
+ """
2154
+ if isinstance(mode, str):
2155
+ mode = getattr(GL, mode)
2156
+
2157
+ if fbo is None:
2158
+ GL.glBindFramebuffer(GL.GL_DRAW_FRAMEBUFFER, 0)
2159
+ GL.glDrawBuffer(mode)
2160
+ return
2161
+
2162
+ if not isinstance(fbo, FramebufferInfo):
2163
+ raise ValueError(
2164
+ "Invalid type for `fbo`, must be `FramebufferInfo`.")
2165
+
2166
+ GL.glBindFramebuffer(GL.GL_DRAW_FRAMEBUFFER, fbo.name)
2167
+ GL.glDrawBuffer(mode)
2168
+
2169
+
2170
+ def clearFramebuffer(color=(0.0, 0.0, 0.0, 1.0), depth=None, stencil=None):
2171
+ """Clear the presently bound draw buffer.
2172
+
2173
+ Parameters
2174
+ ----------
2175
+ color : :obj:`tuple`, optional
2176
+ Color to clear the framebuffer to. Default is (0.0, 0.0, 0.0, 1.0).
2177
+ depth : :obj:`float`, optional
2178
+ Depth value to clear the framebuffer to. Default is 1.0.
2179
+ stencil : :obj:`int`, optional
2180
+ Stencil value to clear the framebuffer to. Default is 0.
2181
+
2182
+ Examples
2183
+ --------
2184
+ Clear the framebuffer to green::
2185
+
2186
+ setDrawBuffer(fbo, GL_COLOR_ATTACHMENT0)
2187
+ clearFramebuffer(color=(0.0, 1.0, 0.0, 1.0))
2188
+
2189
+ Clear the window back buffer to black::
2190
+
2191
+ setDrawBuffer(None, GL_BACK)
2192
+ clearFramebuffer()
2193
+
2194
+ """
2195
+ clearFlags = 0
2196
+ if color is not None:
2197
+ GL.glClearColor(*color)
2198
+ clearFlags |= GL.GL_COLOR_BUFFER_BIT
2199
+ if depth is not None:
2200
+ GL.glClearDepth(depth)
2201
+ clearFlags |= GL.GL_DEPTH_BUFFER_BIT
2202
+ if stencil is not None:
2203
+ GL.glClearStencil(stencil)
2204
+ clearFlags |= GL.GL_STENCIL_BUFFER_BIT
2205
+
2206
+ GL.glClear(clearFlags)
2207
+
2208
+
2209
+ def setViewport(x, y, width, height):
2210
+ """Set the viewport for the current render target.
2211
+
2212
+ Parameters
2213
+ ----------
2214
+ x : :obj:`int`
2215
+ X-coordinate of the lower-left corner of the viewport.
2216
+ y : :obj:`int`
2217
+ Y-coordinate of the lower-left corner of the viewport.
2218
+ width : :obj:`int`
2219
+ Width of the viewport.
2220
+ height : :obj:`int`
2221
+ Height of the viewport.
2222
+
2223
+ """
2224
+ GL.glViewport(int(x), int(y), int(width), int(height))
2225
+
2226
+
2227
+ def setScissor(x, y, width, height):
2228
+ """Set the scissor box for the current render target.
2229
+
2230
+ Parameters
2231
+ ----------
2232
+ x : :obj:`int`
2233
+ X-coordinate of the lower-left corner of the scissor box.
2234
+ y : :obj:`int`
2235
+ Y-coordinate of the lower-left corner of the scissor box.
2236
+ width : :obj:`int`
2237
+ Width of the scissor box.
2238
+ height : :obj:`int`
2239
+ Height of the scissor box.
2240
+
2241
+ """
2242
+ GL.glScissor(int(x), int(y), int(width), int(height))
1342
2243
 
1343
2244
 
1344
2245
  @contextmanager
@@ -1380,7 +2281,7 @@ def useFBO(fbo):
1380
2281
  """
1381
2282
  prevFBO = GL.GLint()
1382
2283
  GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, ctypes.byref(prevFBO))
1383
- toBind = fbo.id if isinstance(fbo, Framebuffer) else int(fbo)
2284
+ toBind = fbo.id if isinstance(fbo, FramebufferInfo) else int(fbo)
1384
2285
  GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, toBind)
1385
2286
  try:
1386
2287
  yield toBind
@@ -1388,6 +2289,80 @@ def useFBO(fbo):
1388
2289
  GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, prevFBO.value)
1389
2290
 
1390
2291
 
2292
+ def bindFBO(fbo, target=None):
2293
+ """Bind a Framebuffer Object (FBO).
2294
+
2295
+ Parameters
2296
+ ----------
2297
+ fbo :obj:`FramebufferInfo`
2298
+ OpenGL Framebuffer Object name/ID or descriptor.
2299
+ target :obj:`int`, :obj:`GLenum`, :obj:`str` or `None`
2300
+ Target to bind the framebuffer to. If `None`, the target of the
2301
+ framebuffer descriptor is used. Valid targets are `GL_FRAMEBUFFER`,
2302
+ `GL_DRAW_FRAMEBUFFER` and `GL_READ_FRAMEBUFFER`.
2303
+
2304
+ Examples
2305
+ --------
2306
+ Bind a framebuffer::
2307
+
2308
+ bindFBO(fbo)
2309
+
2310
+ """
2311
+ if target is None:
2312
+ target = fbo.target if isinstance(fbo, FramebufferInfo) else GL.GL_FRAMEBUFFER
2313
+
2314
+ if isinstance(target, str):
2315
+ target = getattr(GL, target)
2316
+
2317
+ if fbo is None:
2318
+ GL.glBindFramebuffer(target, 0)
2319
+ return
2320
+
2321
+ if not isinstance(fbo, FramebufferInfo):
2322
+ raise ValueError("Invalid type for `fbo`, must be `FramebufferInfo`.")
2323
+
2324
+ GL.glBindFramebuffer(fbo.target, fbo.name)
2325
+
2326
+
2327
+ def unbindFBO(fbo, target=None):
2328
+ """Unbind a Framebuffer Object (FBO).
2329
+
2330
+ While the last FBO does not have to be unbound explicitly, passing the
2331
+ descriptor of the FBO to this function will ensure the target is unbound.
2332
+
2333
+ Parameters
2334
+ ----------
2335
+ fbo :obj:`FramebufferInfo` or `None`
2336
+ OpenGL Framebuffer Object name/ID or descriptor. If `None`, the
2337
+ frambuffer with target `GL_FRAMEBUFFER` is bound to `0`. Values are
2338
+ `GL_FRAMEBUFFER`, `GL_DRAW_FRAMEBUFFER` and `GL_READ_FRAMEBUFFER`.
2339
+ target :obj:`int`, :obj:`GLenum`, :obj:`str` or `None`
2340
+ Target to unbind the framebuffer from. If `None`, the target of the
2341
+ framebuffer descriptor is used.
2342
+
2343
+ Examples
2344
+ --------
2345
+ Unbind a framebuffer::
2346
+
2347
+ unbindFBO(fbo)
2348
+
2349
+ """
2350
+ if target is None:
2351
+ target = fbo.target if isinstance(fbo, FramebufferInfo) else GL.GL_FRAMEBUFFER
2352
+
2353
+ if isinstance(target, str):
2354
+ target = getattr(GL, target)
2355
+
2356
+ if fbo is None:
2357
+ GL.glBindFramebuffer(target, 0)
2358
+ return
2359
+
2360
+ if not isinstance(fbo, FramebufferInfo):
2361
+ raise ValueError("Invalid type for `fbo`, must be `FramebufferInfo`.")
2362
+
2363
+ GL.glBindFramebuffer(fbo.target, 0)
2364
+
2365
+
1391
2366
  # ------------------------------
1392
2367
  # Renderbuffer Objects Functions
1393
2368
  # ------------------------------
@@ -1410,6 +2385,52 @@ Renderbuffer = namedtuple(
1410
2385
  )
1411
2386
 
1412
2387
 
2388
+ class RenderbufferInfo:
2389
+ """Renderbuffer object descriptor.
2390
+
2391
+ This class only stores information about the Renderbuffer it refers to, it
2392
+ does not contain any actual array data associated with the Renderbuffer.
2393
+ Calling :func:`createRenderbuffer` returns instances of this class.
2394
+
2395
+ It is recommended to use `gltools` functions :func:`bindRenderbuffer`,
2396
+ :func:`unbindRenderbuffer` and :func:`deleteRenderbuffer` to manage
2397
+ Renderbuffers.
2398
+
2399
+ Parameters
2400
+ ----------
2401
+ name : int
2402
+ Handle of the Renderbuffer.
2403
+ target : int
2404
+ Target of the Renderbuffer.
2405
+ width : int
2406
+ Width of the Renderbuffer.
2407
+ height : int
2408
+ Height of the Renderbuffer.
2409
+ internalFormat : int
2410
+ Internal format of the Renderbuffer.
2411
+ samples : int
2412
+ Number of samples for multi-sampling.
2413
+ multiSample : bool
2414
+ True if the Renderbuffer is multi-sampled.
2415
+ userData : dict, optional
2416
+ User-defined data associated with the Renderbuffer.
2417
+
2418
+ """
2419
+ __slots__ = ['name', 'target', 'width', 'height', 'internalFormat',
2420
+ 'samples', 'multiSample', 'userData']
2421
+
2422
+ def __init__(self, name, target, width, height, internalFormat, samples,
2423
+ multiSample, userData=None):
2424
+ self.name = name
2425
+ self.target = target
2426
+ self.width = width
2427
+ self.height = height
2428
+ self.internalFormat = internalFormat
2429
+ self.samples = samples
2430
+ self.multiSample = multiSample
2431
+ self.userData = userData
2432
+
2433
+
1413
2434
  def createRenderbuffer(width, height, internalFormat=GL.GL_RGBA8, samples=1):
1414
2435
  """Create a new Renderbuffer Object with a specified internal format. A
1415
2436
  multisample storage buffer is created if samples > 1.
@@ -1428,7 +2449,7 @@ def createRenderbuffer(width, height, internalFormat=GL.GL_RGBA8, samples=1):
1428
2449
  Format for renderbuffer data (e.g. GL_RGBA8, GL_DEPTH24_STENCIL8).
1429
2450
  samples : :obj:`int`
1430
2451
  Number of samples for multi-sampling, should be >1 and power-of-two.
1431
- Work with one sample, but will raise a warning.
2452
+ If samples == 1, a single sample buffer is created.
1432
2453
 
1433
2454
  Returns
1434
2455
  -------
@@ -1441,6 +2462,8 @@ def createRenderbuffer(width, height, internalFormat=GL.GL_RGBA8, samples=1):
1441
2462
  be used to store arbitrary data associated with the buffer.
1442
2463
 
1443
2464
  """
2465
+ internalFormat = _getGLEnum(internalFormat)
2466
+
1444
2467
  width = int(width)
1445
2468
  height = int(height)
1446
2469
 
@@ -1476,14 +2499,14 @@ def createRenderbuffer(width, height, internalFormat=GL.GL_RGBA8, samples=1):
1476
2499
  # done, unbind it
1477
2500
  GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0)
1478
2501
 
1479
- return Renderbuffer(rbId,
1480
- GL.GL_RENDERBUFFER,
1481
- width,
1482
- height,
1483
- internalFormat,
1484
- samples,
1485
- samples > 1,
1486
- dict())
2502
+ return RenderbufferInfo(rbId,
2503
+ GL.GL_RENDERBUFFER,
2504
+ width,
2505
+ height,
2506
+ internalFormat,
2507
+ samples,
2508
+ samples > 1,
2509
+ dict())
1487
2510
 
1488
2511
 
1489
2512
  def deleteRenderbuffer(renderBuffer):
@@ -1491,7 +2514,10 @@ def deleteRenderbuffer(renderBuffer):
1491
2514
  renderbuffer's ID.
1492
2515
 
1493
2516
  """
1494
- GL.glDeleteRenderbuffers(1, renderBuffer.id)
2517
+ GL.glDeleteRenderbuffers(1, renderBuffer.name)
2518
+
2519
+ # invalidate the descriptor
2520
+ renderBuffer.name = 0
1495
2521
 
1496
2522
 
1497
2523
  # -----------------
@@ -1502,7 +2528,7 @@ def deleteRenderbuffer(renderBuffer):
1502
2528
  # use them with functions that require that type as input.
1503
2529
  #
1504
2530
 
1505
- class TexImage2D:
2531
+ class TexImage2DInfo:
1506
2532
  """Descriptor for a 2D texture.
1507
2533
 
1508
2534
  This class is used for bookkeeping 2D textures stored in video memory.
@@ -1656,8 +2682,8 @@ def createTexImage2D(width, height, target=GL.GL_TEXTURE_2D, level=0,
1656
2682
 
1657
2683
  Returns
1658
2684
  -------
1659
- TexImage2D
1660
- A `TexImage2D` descriptor.
2685
+ TexImage2DInfo
2686
+ A `TexImage2DInfo` descriptor.
1661
2687
 
1662
2688
  Notes
1663
2689
  -----
@@ -1699,6 +2725,9 @@ def createTexImage2D(width, height, target=GL.GL_TEXTURE_2D, level=0,
1699
2725
  GL.glBindTexture(GL.GL_TEXTURE_2D, textureDesc.id)
1700
2726
 
1701
2727
  """
2728
+ target, internalFormat, pixelFormat, dataType = _getGLEnum(
2729
+ target, internalFormat, pixelFormat, dataType)
2730
+
1702
2731
  width = int(width)
1703
2732
  height = int(height)
1704
2733
 
@@ -1715,11 +2744,12 @@ def createTexImage2D(width, height, target=GL.GL_TEXTURE_2D, level=0,
1715
2744
  texId = GL.GLuint()
1716
2745
  GL.glGenTextures(1, ctypes.byref(texId))
1717
2746
 
1718
- GL.glBindTexture(target, texId)
2747
+ GL.glBindTexture(target, texId.value)
1719
2748
  GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, int(unpackAlignment))
1720
2749
  GL.glTexImage2D(target, level, internalFormat,
1721
2750
  width, height, 0,
1722
2751
  pixelFormat, dataType, data)
2752
+ GL.glGenerateMipmap(target)
1723
2753
 
1724
2754
  # apply texture parameters
1725
2755
  if texParams is not None:
@@ -1727,16 +2757,17 @@ def createTexImage2D(width, height, target=GL.GL_TEXTURE_2D, level=0,
1727
2757
  GL.glTexParameteri(target, pname, param)
1728
2758
 
1729
2759
  # new texture descriptor
1730
- tex = TexImage2D(name=texId,
1731
- target=target,
1732
- width=width,
1733
- height=height,
1734
- internalFormat=internalFormat,
1735
- level=level,
1736
- pixelFormat=pixelFormat,
1737
- dataType=dataType,
1738
- unpackAlignment=unpackAlignment,
1739
- texParams=texParams)
2760
+ tex = TexImage2DInfo(
2761
+ name=texId.value,
2762
+ target=target,
2763
+ width=width,
2764
+ height=height,
2765
+ internalFormat=internalFormat,
2766
+ level=level,
2767
+ pixelFormat=pixelFormat,
2768
+ dataType=dataType,
2769
+ unpackAlignment=unpackAlignment,
2770
+ texParams=texParams)
1740
2771
 
1741
2772
  tex._texParamsNeedUpdate = False
1742
2773
 
@@ -1762,7 +2793,7 @@ def createTexImage2dFromFile(imgFile, transpose=True):
1762
2793
 
1763
2794
  Returns
1764
2795
  -------
1765
- TexImage2D
2796
+ TexImage2DInfo
1766
2797
  Texture descriptor.
1767
2798
 
1768
2799
  """
@@ -1978,62 +3009,62 @@ def createCubeMap(width, height, target=GL.GL_TEXTURE_CUBE_MAP, level=0,
1978
3009
  return tex
1979
3010
 
1980
3011
 
1981
- def bindTexture(texture, unit=None, enable=True):
1982
- """Bind a texture.
3012
+ # def bindTexture(texture, unit=None, enable=True):
3013
+ # """Bind a texture.
1983
3014
 
1984
- Function binds `texture` to `unit` (if specified). If `unit` is `None`, the
1985
- texture will be bound but not assigned to a texture unit.
3015
+ # Function binds `texture` to `unit` (if specified). If `unit` is `None`, the
3016
+ # texture will be bound but not assigned to a texture unit.
1986
3017
 
1987
- Parameters
1988
- ----------
1989
- texture : TexImage2D
1990
- Texture descriptor to bind.
1991
- unit : int, optional
1992
- Texture unit to associated the texture with.
1993
- enable : bool
1994
- Enable textures upon binding.
3018
+ # Parameters
3019
+ # ----------
3020
+ # texture : TexImage2DInfo
3021
+ # Texture descriptor to bind.
3022
+ # unit : int, optional
3023
+ # Texture unit to associated the texture with.
3024
+ # enable : bool
3025
+ # Enable textures upon binding.
1995
3026
 
1996
- """
1997
- if not texture._isBound:
1998
- if enable:
1999
- GL.glEnable(texture.target)
3027
+ # """
3028
+ # if not texture._isBound:
3029
+ # if enable:
3030
+ # GL.glEnable(texture.target)
2000
3031
 
2001
- GL.glBindTexture(texture.target, texture.name)
2002
- texture._isBound = True
3032
+ # GL.glBindTexture(texture.target, texture.name)
3033
+ # texture._isBound = True
2003
3034
 
2004
- if unit is not None:
2005
- texture._unit = unit
2006
- GL.glActiveTexture(GL.GL_TEXTURE0 + unit)
3035
+ # if unit is not None:
3036
+ # texture._unit = unit
3037
+ # GL.glActiveTexture(GL.GL_TEXTURE0 + unit)
2007
3038
 
2008
- # update texture parameters if they have been accessed (changed?)
2009
- if texture._texParamsNeedUpdate:
2010
- for pname, param in texture._texParams.items():
2011
- GL.glTexParameteri(texture.target, pname, param)
2012
- texture._texParamsNeedUpdate = False
3039
+ # # update texture parameters if they have been accessed (changed?)
3040
+ # if texture._texParamsNeedUpdate:
3041
+ # for pname, param in texture._texParams.items():
3042
+ # GL.glTexParameteri(texture.target, pname, param)
3043
+ # texture._texParamsNeedUpdate = False
2013
3044
 
2014
3045
 
2015
- def unbindTexture(texture=None):
2016
- """Unbind a texture.
3046
+ # def unbindTexture(texture=None):
3047
+ # """Unbind a texture.
2017
3048
 
2018
- Parameters
2019
- ----------
2020
- texture : TexImage2D
2021
- Texture descriptor to unbind.
3049
+ # Parameters
3050
+ # ----------
3051
+ # texture : TexImage2DInfo
3052
+ # Texture descriptor to unbind.
2022
3053
 
2023
- """
2024
- if texture._isBound:
2025
- # set the texture unit
2026
- if texture._unit is not None:
2027
- GL.glActiveTexture(GL.GL_TEXTURE0 + texture._unit)
2028
- texture._unit = None
3054
+ # """
3055
+ # if texture._isBound:
3056
+ # # set the texture unit
3057
+ # if texture._unit is not None:
3058
+ # GL.glActiveTexture(GL.GL_TEXTURE0 + texture._unit)
3059
+ # texture._unit = None
2029
3060
 
2030
- GL.glBindTexture(texture.target, 0)
2031
- texture._isBound = False
3061
+ # GL.glBindTexture(texture.target, 0)
3062
+ # texture._isBound = False
2032
3063
 
2033
- GL.glDisable(texture.target)
2034
- else:
2035
- raise RuntimeError('Trying to unbind a texture that was not previously'
2036
- 'bound.')
3064
+ # GL.glDisable(texture.target)
3065
+ # else:
3066
+ # raise RuntimeError('Trying to unbind a texture that was not previously'
3067
+ # 'bound.')
2037
3068
 
2038
3069
 
2039
3070
  # Descriptor for 2D mutlisampled texture
@@ -2049,6 +3080,72 @@ TexImage2DMultisample = namedtuple(
2049
3080
  'userData'])
2050
3081
 
2051
3082
 
3083
+ class TexImage2DMultisampleInfo:
3084
+ """Descriptor for a 2D multisampled texture.
3085
+
3086
+ This class is used for bookkeeping 2D multisampled textures stored in video
3087
+ memory. Information about the texture (eg. `width` and `height`) is
3088
+ available via class attributes. Attributes should never be modified
3089
+ directly.
3090
+
3091
+ """
3092
+ __slots__ = ['width',
3093
+ 'height',
3094
+ 'target',
3095
+ '_name',
3096
+ 'internalFormat',
3097
+ 'samples',
3098
+ 'multisample',
3099
+ 'userData']
3100
+
3101
+ def __init__(self,
3102
+ name=0,
3103
+ target=GL.GL_TEXTURE_2D_MULTISAMPLE,
3104
+ width=64,
3105
+ height=64,
3106
+ internalFormat=GL.GL_RGBA8,
3107
+ samples=1,
3108
+ multisample=True,
3109
+ userData=None):
3110
+ """
3111
+ Parameters
3112
+ ----------
3113
+ name : `int` or `GLuint`
3114
+ OpenGL handle for texture. Is `0` if uninitialized.
3115
+ target : :obj:`int`
3116
+ The target texture should only be `GL_TEXTURE_2D_MULTISAMPLE`.
3117
+ width : :obj:`int`
3118
+ Texture width in pixels.
3119
+ height : :obj:`int`
3120
+ Texture height in pixels.
3121
+ internalFormat : :obj:`int`
3122
+ Internal format for texture data (e.g. GL_RGBA8, GL_R11F_G11F_B10F).
3123
+ samples : :obj:`int`
3124
+ Number of samples for multi-sampling, should be >1 and power-of-two.
3125
+ Work with one sample, but will raise a warning.
3126
+ multisample : :obj:`bool`
3127
+ True if the texture is multi-sampled.
3128
+ userData : :obj:`dict`
3129
+ User-defined data associated with the texture.
3130
+
3131
+ """
3132
+ self.name = name
3133
+ self.width = width
3134
+ self.height = height
3135
+ self.target = target
3136
+ self.internalFormat = internalFormat
3137
+ self.samples = samples
3138
+ self.multisample = multisample
3139
+
3140
+ if userData is None:
3141
+ self.userData = {}
3142
+ elif isinstance(userData, dict):
3143
+ self.userData = userData
3144
+ else:
3145
+ raise TypeError('Invalid type for `userData`.')
3146
+
3147
+
3148
+
2052
3149
  def createTexImage2DMultisample(width, height,
2053
3150
  target=GL.GL_TEXTURE_2D_MULTISAMPLE, samples=1,
2054
3151
  internalFormat=GL.GL_RGBA8, texParameters=()):
@@ -2075,8 +3172,8 @@ def createTexImage2DMultisample(width, height,
2075
3172
 
2076
3173
  Returns
2077
3174
  -------
2078
- TexImage2DMultisample
2079
- A TexImage2DMultisample descriptor.
3175
+ TexImage2DMultisampleInfo
3176
+ A TexImage2DMultisampleInfo descriptor.
2080
3177
 
2081
3178
  """
2082
3179
  width = int(width)
@@ -2107,14 +3204,15 @@ def createTexImage2DMultisample(width, height,
2107
3204
 
2108
3205
  GL.glBindTexture(target, 0)
2109
3206
 
2110
- return TexImage2DMultisample(colorTexId,
2111
- target,
2112
- width,
2113
- height,
2114
- internalFormat,
2115
- samples,
2116
- True,
2117
- dict())
3207
+ return TexImage2DMultisampleInfo(
3208
+ colorTexId,
3209
+ target,
3210
+ width,
3211
+ height,
3212
+ internalFormat,
3213
+ samples,
3214
+ True,
3215
+ dict())
2118
3216
 
2119
3217
 
2120
3218
  def deleteTexture(texture):
@@ -2300,6 +3398,11 @@ def createVAO(attribBuffers, indexBuffer=None, attribDivisors=None, legacy=False
2300
3398
  activeAttribs = {}
2301
3399
  bufferIndices = []
2302
3400
  for i, buffer in attribBuffers.items():
3401
+ if isinstance(i, str):
3402
+ i = VERTEX_ATTRIBS.get(i, None)
3403
+ if i is None:
3404
+ raise ValueError('Invalid attribute name specified.')
3405
+
2303
3406
  if isinstance(buffer, (list, tuple,)):
2304
3407
  if len(buffer) == 1:
2305
3408
  buffer = buffer[0] # size 1 tuple or list eg. (buffer,)
@@ -2370,6 +3473,13 @@ def createVAO(attribBuffers, indexBuffer=None, attribDivisors=None, legacy=False
2370
3473
  legacy)
2371
3474
 
2372
3475
 
3476
+ # use the appropriate VAO binding function for the platform
3477
+ if _thisPlatform != 'Darwin':
3478
+ _glBindVertexArray = GL.glBindVertexArray
3479
+ else:
3480
+ _glBindVertexArray = GL.glBindVertexArrayAPPLE
3481
+
3482
+
2373
3483
  def drawVAO(vao, mode=GL.GL_TRIANGLES, start=0, count=None, instanceCount=None,
2374
3484
  flush=False):
2375
3485
  """Draw a vertex array object. Uses `glDrawArrays` or `glDrawElements` if
@@ -2381,7 +3491,9 @@ def drawVAO(vao, mode=GL.GL_TRIANGLES, start=0, count=None, instanceCount=None,
2381
3491
  vao : VertexArrayObject
2382
3492
  Vertex Array Object (VAO) to draw.
2383
3493
  mode : int, optional
2384
- Drawing mode to use (e.g. GL_TRIANGLES, GL_QUADS, GL_POINTS, etc.)
3494
+ Drawing mode to use (e.g. GL_TRIANGLES, GL_QUADS, GL_POINTS, etc.) for
3495
+ rasterization. Default is `GL_TRIANGLES`. Strings can be used for
3496
+ convenience (e.g. 'GL_TRIANGLES', 'GL_QUADS', 'GL_POINTS').
2385
3497
  start : int, optional
2386
3498
  Starting index for array elements. Default is `0` which is the beginning
2387
3499
  of the array.
@@ -2402,11 +3514,13 @@ def drawVAO(vao, mode=GL.GL_TRIANGLES, start=0, count=None, instanceCount=None,
2402
3514
  drawVAO(vaoDesc, GL.GL_TRIANGLES)
2403
3515
 
2404
3516
  """
3517
+ if isinstance(mode, str):
3518
+ mode = getattr(GL, mode, None)
3519
+ if mode is None:
3520
+ raise ValueError('Invalid drawing mode specified.')
3521
+
2405
3522
  # draw the array
2406
- if _thisPlatform != 'Darwin':
2407
- GL.glBindVertexArray(vao.name)
2408
- else:
2409
- GL.glBindVertexArrayAPPLE(vao.name)
3523
+ _glBindVertexArray(vao.name)
2410
3524
 
2411
3525
  if count is None:
2412
3526
  count = vao.count
@@ -2432,10 +3546,7 @@ def drawVAO(vao, mode=GL.GL_TRIANGLES, start=0, count=None, instanceCount=None,
2432
3546
  GL.glFlush()
2433
3547
 
2434
3548
  # reset
2435
- if _thisPlatform != 'Darwin':
2436
- GL.glBindVertexArray(0)
2437
- else:
2438
- GL.glBindVertexArrayAPPLE(0)
3549
+ _glBindVertexArray(0)
2439
3550
 
2440
3551
 
2441
3552
  def deleteVAO(vao):
@@ -2449,25 +3560,185 @@ def deleteVAO(vao):
2449
3560
  reset.
2450
3561
 
2451
3562
  """
2452
- if isinstance(vao, VertexArrayInfo):
2453
- if vao.name:
2454
- GL.glDeleteVertexArrays(1, GL.GLuint(vao.name))
2455
- vao.name = 0
2456
- vao.isLegacy = False
2457
- vao.indexBuffer = None
2458
- vao.activeAttribs = {}
2459
- vao.count = 0
2460
-
3563
+ if not isinstance(vao, VertexArrayInfo):
3564
+ raise TypeError('Invalid type for `vao`, must be `VertexArrayInfo`.')
3565
+
3566
+ if vao.name:
3567
+ GL.glDeleteVertexArrays(1, GL.GLuint(vao.name))
3568
+ vao.name = 0
3569
+ vao.isLegacy = False
3570
+ vao.indexBuffer = None
3571
+ vao.activeAttribs = {}
3572
+ vao.count = 0
2461
3573
 
2462
- # ---------------------------
2463
- # Vertex Buffer Objects (VBO)
2464
- #
2465
3574
 
3575
+ def drawClientArrays(attribBuffers, mode=GL.GL_TRIANGLES, indexBuffer=None):
3576
+ """Draw vertex arrays using client-side arrays.
3577
+
3578
+ This is a convenience function for drawing vertex arrays by passing
3579
+ client-side (resident in CPU memory space) arrays to the driver directly.
3580
+ Performance may be suboptimal compared to using VBOs and VAOs, as data
3581
+ must be transferred to the GPU each time this function is called. This may
3582
+ also stall the rendering pipeline, preventing the GPU from processing
3583
+ commands in parallel.
2466
3584
 
2467
- class VertexBufferInfo:
2468
- """Vertex buffer object (VBO) descriptor.
3585
+ For best performance, use interleaved arrays for vertex attributes and an
3586
+ index buffer. Using an index buffer is optional, but it greatly reduces
3587
+ the amount of data that must be transferred to the GPU.
2469
3588
 
2470
- This class only stores information about the VBO it refers to, it does not
3589
+ Parameters
3590
+ ----------
3591
+ attribBuffers : dict
3592
+ Attributes and associated buffers to draw. Keys are vertex attribute
3593
+ pointer indices, values are buffer arrays. Values can be `tuples` where
3594
+ the first value is the buffer array, the second is the number of
3595
+ attribute components (`int`, either 2, 3 or 4), the third is the offset
3596
+ (`int`), and the last is whether to normalize the array (`bool`). Buffer
3597
+ arrays may be `numpy` arrays or lists. If lists, the arrays will be
3598
+ converted to `numpy` arrays with `float` (`GL_DOUBLE`) data type.
3599
+ mode : int
3600
+ Drawing mode to use (e.g. GL_TRIANGLES, GL_QUADS, GL_POINTS, etc.) for
3601
+ rasterization.
3602
+ indexBuffer : VertexBufferInfo
3603
+ Optional index buffer. If `None`, `glDrawArrays` is used, else
3604
+ `glDrawElements` is used. The index buffer must be a 2D array that is
3605
+ appropriately sized for the drawing mode. For example, if `mode` is
3606
+ `GL_TRIANGLES`, the index buffer must be a 2D array with 3 columns. The
3607
+ index array is always assumed to be of type `GL_UNSIGNED_INT`, it will
3608
+ be converted if necessary.
3609
+
3610
+ Examples
3611
+ --------
3612
+ Drawing vertex arrays using client-side arrays::
3613
+
3614
+ attribArrays = {
3615
+ 'gl_Vertex': vertexPos, # vertex positions
3616
+ 'gl_Color': vertexColors} # per vertex colors
3617
+
3618
+ drawClientArrays('triangles', attribArrays)
3619
+
3620
+ Drawing using index buffers::
3621
+
3622
+ vertex, normal, texCoord, faces = createAnnulus() # ring geometry
3623
+
3624
+ # bind and setup shader uniforms here...
3625
+
3626
+ attribs = {
3627
+ 'gl_Vertex': vertex,
3628
+ 'gl_Normal': normal,
3629
+ 'gl_MultiTexCoord0': texCoord}
3630
+
3631
+ drawClientArrays('triangles', attribs, indexBuffer=faces)
3632
+
3633
+ Using interleaved arrays is recommended for greater performance, all
3634
+ attributes are stored in a single array which reduces the number of
3635
+ binding calls::
3636
+
3637
+ vertex, normal, texCoord, faces = createPlane() # square
3638
+ interleaved, sizes, offsets = interleaveArrays(
3639
+ [vertex, normal, texCoord])
3640
+
3641
+ # interleaved array layout: 000111222
3642
+ attribs = {}
3643
+ for i, attrib in enumerate('gl_Vertex', 'gl_Normal', 'gl_TexCoord'):
3644
+ attribs[attrib] = (interleaved, sizes[i], offsets[i])
3645
+
3646
+ drawClientArrays('triangles', attribs, indexBuffer=faces)
3647
+
3648
+ """
3649
+ mode = _getGLEnum(mode)
3650
+ if mode is None:
3651
+ raise ValueError('Invalid drawing mode specified.')
3652
+
3653
+ GL.glEnable(GL.GL_VERTEX_ARRAY)
3654
+ GL.glBindBuffer(GL.GL_ARRAY_BUFFER, 0)
3655
+ GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0)
3656
+ GL.glBindVertexArray(0)
3657
+
3658
+ useIndexBuffer = indexBuffer is not None
3659
+ if useIndexBuffer:
3660
+ # always use unsigned int for index buffer
3661
+ indexBuffer = np.ascontiguousarray(indexBuffer, dtype=np.uint32)
3662
+ if indexBuffer.ndim != 2:
3663
+ raise ValueError('Index buffer must be 2D array.')
3664
+
3665
+ boundArrays = []
3666
+ for arrIdx, buffer in attribBuffers.items():
3667
+ if isinstance(arrIdx, str):
3668
+ arrIdx= VERTEX_ATTRIBS.get(arrIdx, None)
3669
+ if arrIdx is None:
3670
+ raise ValueError('Invalid attribute name specified.')
3671
+
3672
+ if isinstance(buffer, (list, tuple,)):
3673
+ normalize = False
3674
+ nVals = len(buffer)
3675
+ if nVals == 1:
3676
+ buffer = buffer[0] # size 1 tuple or list eg. (buffer,)
3677
+ size = buffer.shape[1]
3678
+ offset = 0
3679
+ elif nVals == 2:
3680
+ buffer, size = buffer
3681
+ offset = 0
3682
+ elif nVals == 3:
3683
+ buffer, size, offset = buffer
3684
+ elif nVals == 4:
3685
+ buffer, size, offset, normalize = buffer
3686
+ else:
3687
+ raise ValueError('Invalid attribute values.')
3688
+ else:
3689
+ size = buffer.shape[1]
3690
+ offset = 0
3691
+ normalize = False
3692
+
3693
+ # make sure the buffer is contiguous array
3694
+ if not isinstance(buffer, np.ndarray):
3695
+ buffer = np.ascontiguousarray(buffer, dtype=float)
3696
+
3697
+ if buffer.ndim != 2:
3698
+ raise ValueError(
3699
+ 'Buffer {} must be 2D array.'.format(arrIdx))
3700
+
3701
+ numVertices = buffer.shape[0]
3702
+
3703
+ # enable and set attribute pointers
3704
+ GL.glEnableVertexAttribArray(arrIdx)
3705
+
3706
+ arrayTypes = ARRAY_TYPES.get(buffer.dtype.type, None)
3707
+ if arrayTypes is None:
3708
+ raise ValueError('Unable to determine data type from buffer.')
3709
+
3710
+ GL.glVertexAttribPointer(
3711
+ arrIdx,
3712
+ size,
3713
+ arrayTypes[0],
3714
+ GL.GL_TRUE if normalize else GL.GL_FALSE,
3715
+ offset,
3716
+ buffer.ctypes)
3717
+
3718
+ boundArrays.append(arrIdx)
3719
+
3720
+ # use the appropriate draw function
3721
+ if useIndexBuffer:
3722
+ GL.glDrawElements(
3723
+ mode, indexBuffer.size, GL.GL_UNSIGNED_INT, indexBuffer.ctypes)
3724
+ else:
3725
+ GL.glDrawArrays(mode, 0, numVertices)
3726
+
3727
+ for arrIdx in boundArrays: # unbind arrays
3728
+ GL.glDisableVertexAttribArray(arrIdx)
3729
+
3730
+ GL.glDisable(GL.GL_VERTEX_ARRAY)
3731
+
3732
+
3733
+ # ---------------------------
3734
+ # Vertex Buffer Objects (VBO)
3735
+ #
3736
+
3737
+
3738
+ class VertexBufferInfo:
3739
+ """Vertex buffer object (VBO) descriptor.
3740
+
3741
+ This class only stores information about the VBO it refers to, it does not
2471
3742
  contain any actual array data associated with the VBO. Calling
2472
3743
  :func:`createVBO` returns instances of this class.
2473
3744
 
@@ -2573,7 +3844,7 @@ class VertexBufferInfo:
2573
3844
  return False
2574
3845
 
2575
3846
  if self.target == GL.GL_ARRAY_BUFFER:
2576
- bindTarget = GL.GL_VERTEX_ARRAY_BUFFER_BINDING
3847
+ bindTarget = GL.GL_VERTEX_ARRAY_BINDING
2577
3848
  elif self.target == GL.GL_ELEMENT_ARRAY_BUFFER:
2578
3849
  bindTarget = GL.GL_ELEMENT_ARRAY_BUFFER_BINDING
2579
3850
  else:
@@ -2609,7 +3880,7 @@ class VertexBufferInfo:
2609
3880
 
2610
3881
  def createVBO(data,
2611
3882
  target=GL.GL_ARRAY_BUFFER,
2612
- dataType=GL.GL_FLOAT,
3883
+ dataType=None,
2613
3884
  usage=GL.GL_STATIC_DRAW):
2614
3885
  """Create an array buffer object (VBO).
2615
3886
 
@@ -2621,15 +3892,21 @@ def createVBO(data,
2621
3892
  data : array_like
2622
3893
  A 2D array of values to write to the array buffer. The data type of the
2623
3894
  VBO is inferred by the type of the array. If the input is a Python
2624
- `list` or `tuple` type, the data type of the array will be `GL_FLOAT`.
2625
- target : :obj:`int`
3895
+ `list` or `tuple` type, the data type of the array will be `GL_DOUBLE`.
3896
+ target : :obj:`int` or :obj:`str`, optional
2626
3897
  Target used when binding the buffer (e.g. `GL_VERTEX_ARRAY` or
2627
- `GL_ELEMENT_ARRAY_BUFFER`). Default is `GL_VERTEX_ARRAY`.
2628
- dataType : Glenum, optional
3898
+ `GL_ELEMENT_ARRAY_BUFFER`). Default is `GL_VERTEX_ARRAY`. Strings may
3899
+ also be used to specify the target, where the following are valid:
3900
+ 'array' (for `GL_VERTEX_ARRAY`) or 'element_array' (for
3901
+ `GL_ELEMENT_ARRAY_BUFFER`).
3902
+ dataType : Glenum or None, optional
2629
3903
  Data type of array. Input data will be recast to an appropriate type if
2630
- necessary. Default is `GL_FLOAT`.
3904
+ necessary. Default is `None`. If `None`, the data type will be
3905
+ inferred from the input data.
2631
3906
  usage : GLenum or int, optional
2632
- Usage type for the array (i.e. `GL_STATIC_DRAW`).
3907
+ Usage hint for the array (i.e. `GL_STATIC_DRAW`). This will hint to the
3908
+ GL driver how the buffer will be used so it can optimize memory
3909
+ allocation and access. Default is `GL_STATIC_DRAW`.
2633
3910
 
2634
3911
  Returns
2635
3912
  -------
@@ -2682,10 +3959,33 @@ def createVBO(data,
2682
3959
  glFlush()
2683
3960
 
2684
3961
  """
2685
- # build input array
2686
- npType, glType = GL_COMPAT_TYPES[dataType]
2687
- data = np.asarray(data, dtype=npType)
2688
-
3962
+ target, dataType, usage = _getGLEnum(target, dataType, usage)
3963
+
3964
+ # try and infer the data type if not specified
3965
+ if dataType is None: # get data type from input
3966
+ if isinstance(data, (list, tuple)): # default for Python array types
3967
+ dataType = GL.GL_DOUBLE
3968
+ elif isinstance(data, np.ndarray): # numpy arrays
3969
+ dataType = data.dtype.type
3970
+ else:
3971
+ raise ValueError('Could not infer data type from input.')
3972
+
3973
+ # get the OpenGL data type and numpy type
3974
+ typeVals = ARRAY_TYPES.get(dataType, None)
3975
+ if typeVals is None:
3976
+ raise ValueError('Invalid data type specified.')
3977
+
3978
+ glEnum, glType, npType = typeVals
3979
+
3980
+ # get the usage hint if a string was passed
3981
+ if isinstance(usage, str):
3982
+ usage = GL_ENUMS.get(usage, None)
3983
+ if usage is None:
3984
+ raise ValueError('Invalid `usage` hint string.')
3985
+
3986
+ # create the input data array
3987
+ data = np.ascontiguousarray(data, dtype=npType)
3988
+
2689
3989
  # get buffer size and pointer
2690
3990
  bufferSize = data.size * ctypes.sizeof(glType)
2691
3991
  if data.ndim > 1:
@@ -2708,7 +4008,7 @@ def createVBO(data,
2708
4008
  bufferName,
2709
4009
  target,
2710
4010
  usage,
2711
- dataType,
4011
+ glEnum,
2712
4012
  bufferSize,
2713
4013
  bufferStride,
2714
4014
  data.shape) # leave userData empty
@@ -2734,7 +4034,7 @@ def bindVBO(vbo):
2734
4034
  if isinstance(vbo, VertexBufferInfo):
2735
4035
  GL.glBindBuffer(vbo.target, vbo.name)
2736
4036
  else:
2737
- raise TypeError('Specified `vbo` is not at `VertexBufferInfo`.')
4037
+ raise TypeError('Specified `vbo` is not a `VertexBufferInfo`.')
2738
4038
 
2739
4039
 
2740
4040
  def unbindVBO(vbo):
@@ -2749,7 +4049,7 @@ def unbindVBO(vbo):
2749
4049
  if isinstance(vbo, VertexBufferInfo):
2750
4050
  GL.glBindBuffer(vbo.target, 0)
2751
4051
  else:
2752
- raise TypeError('Specified `vbo` is not at `VertexBufferInfo`.')
4052
+ raise TypeError('Specified `vbo` is not a `VertexBufferInfo`.')
2753
4053
 
2754
4054
 
2755
4055
  def mapBuffer(vbo, start=0, length=None, read=True, write=True, noSync=False):
@@ -2813,7 +4113,7 @@ def mapBuffer(vbo, start=0, length=None, read=True, write=True, noSync=False):
2813
4113
  unmapBuffer(vbo)
2814
4114
 
2815
4115
  """
2816
- npType, glType = GL_COMPAT_TYPES[vbo.dataType]
4116
+ _, glType, npType = ARRAY_TYPES[vbo.dataType]
2817
4117
  start *= ctypes.sizeof(glType)
2818
4118
 
2819
4119
  if length is None:
@@ -2867,6 +4167,103 @@ def unmapBuffer(vbo):
2867
4167
  return GL.glUnmapBuffer(vbo.target) == GL.GL_TRUE
2868
4168
 
2869
4169
 
4170
+ @contextmanager
4171
+ def mappedBuffer(vbo, start=0, length=None, read=True, write=True, noSync=False):
4172
+ """Context manager for mapping and unmapping a buffer. This is a convenience
4173
+ function for using :func:`mapBuffer` and :func:`unmapBuffer` together.
4174
+
4175
+ Parameters
4176
+ ----------
4177
+ vbo : VertexBufferInfo
4178
+ Vertex buffer to map to client memory.
4179
+ start : int
4180
+ Initial index of the sub-range of the buffer to modify.
4181
+ length : int or None
4182
+ Number of elements of the sub-array to map from `offset`. If `None`, all
4183
+ elements to from `offset` to the end of the array are mapped.
4184
+ read : bool, optional
4185
+ Allow data to be read from the buffer (sets `GL_MAP_READ_BIT`). This is
4186
+ ignored if `noSync` is `True`.
4187
+ write : bool, optional
4188
+ Allow data to be written to the buffer (sets `GL_MAP_WRITE_BIT`).
4189
+ noSync : bool, optional
4190
+ If `True`, GL will not wait until the buffer is free (i.e. not being
4191
+ processed by the GPU) to map it (sets `GL_MAP_UNSYNCHRONIZED_BIT`). The
4192
+ contents of the previous storage buffer are discarded and the driver
4193
+ returns a new one. This prevents the CPU from stalling until the buffer
4194
+ is available
4195
+
4196
+ Yields
4197
+ ------
4198
+ ndarray
4199
+ View of the data. The type of the returned array is one which best
4200
+ matches the data type of the buffer.
4201
+
4202
+ Examples
4203
+ --------
4204
+ Using the context manager to map and unmap a buffer::
4205
+
4206
+ with mappedBuffer(vbo) as arr:
4207
+ arr[:, :] += 2.0
4208
+
4209
+ """
4210
+ arr = mapBuffer(vbo, start, length, read, write, noSync)
4211
+ yield arr
4212
+ unmapBuffer(vbo)
4213
+
4214
+
4215
+ def updateVBO(vbo, data, noSync=False):
4216
+ """Update the contents of a VBO with new data.
4217
+
4218
+ This is a convenience function for mapping a buffer, updating the data, and
4219
+ then unmapping it if the data shape matches the buffer shape.
4220
+
4221
+ Parameters
4222
+ ----------
4223
+ vbo : VertexBufferInfo
4224
+ Vertex buffer to update.
4225
+ data : array_like
4226
+ New data to write to the buffer. The shape of the data must match the
4227
+ shape of the buffer.
4228
+ noSync : bool, optional
4229
+ If `True`, GL will not wait until the buffer is free (i.e. not being
4230
+ processed by the GPU) to map it (sets `GL_MAP_UNSYNCHRONIZED_BIT`). The
4231
+ contents of the previous storage buffer are discarded and the driver
4232
+ returns a new one. This prevents the CPU from stalling until the buffer
4233
+ is available.
4234
+
4235
+ Returns
4236
+ -------
4237
+ bool
4238
+ `True` if the buffer has been successfully modified. If `False`, the
4239
+ data was corrupted for some reason and needs to be resubmitted.
4240
+
4241
+ Examples
4242
+ --------
4243
+ Update a VBO with new data::
4244
+
4245
+ # new vertices
4246
+ verts = [[ 1.0, 1.0, 0.0], # v0
4247
+ [ 0.0, -1.0, 0.0], # v1
4248
+ [-1.0, 1.0, 0.0]] # v2
4249
+
4250
+ # update the VBO
4251
+ updateVBO(vboDesc, verts)
4252
+
4253
+ """
4254
+ if not isinstance(data, np.ndarray): # allow lists, tuples, etc.
4255
+ data = np.ascontiguousarray(data)
4256
+
4257
+ if data.shape != vbo.shape:
4258
+ raise ValueError('Data shape does not match VBO shape, expected {} '
4259
+ 'but got {}.'.format(vbo.shape, data.shape))
4260
+
4261
+ mappedArray = mapBuffer(vbo, noSync=noSync)
4262
+ mappedArray[:, :] = data[:, :] # transfer data to GPU buffer array
4263
+
4264
+ return unmapBuffer(vbo)
4265
+
4266
+
2870
4267
  def deleteVBO(vbo):
2871
4268
  """Delete a vertex buffer object (VBO).
2872
4269
 
@@ -2876,9 +4273,12 @@ def deleteVBO(vbo):
2876
4273
  Descriptor of VBO to delete.
2877
4274
 
2878
4275
  """
4276
+ if not isinstance(vbo, VertexBufferInfo):
4277
+ raise TypeError('Invalid type for `vbo`, must be `VertexBufferInfo`.')
4278
+
2879
4279
  if GL.glIsBuffer(vbo.name):
2880
4280
  GL.glDeleteBuffers(1, vbo.name)
2881
- vbo.name = GL.GLuint(0)
4281
+ vbo.name = GL.GLuint(0) # reset the object to invalidate it
2882
4282
 
2883
4283
 
2884
4284
  def setVertexAttribPointer(index,
@@ -2911,7 +4311,7 @@ def setVertexAttribPointer(index,
2911
4311
  versions.
2912
4312
 
2913
4313
  On nVidia graphics drivers (and maybe others), the following attribute
2914
- pointers indices are aliased with reserved GLSL names:
4314
+ pointer indices are aliased with reserved GLSL names:
2915
4315
 
2916
4316
  * gl_Vertex - 0
2917
4317
  * gl_Normal - 2
@@ -3011,7 +4411,7 @@ def setVertexAttribPointer(index,
3011
4411
  if vbo.target != GL.GL_ARRAY_BUFFER:
3012
4412
  raise ValueError('VBO must have `target` type `GL_ARRAY_BUFFER`.')
3013
4413
 
3014
- _, glType = GL_COMPAT_TYPES[vbo.dataType]
4414
+ _, glType, _ = ARRAY_TYPES[vbo.dataType]
3015
4415
 
3016
4416
  if size is None:
3017
4417
  size = vbo.shape[1]
@@ -3094,120 +4494,506 @@ def disableVertexAttribArray(index, legacy=False):
3094
4494
  GL.glDisableClientState(index)
3095
4495
 
3096
4496
 
3097
- # -------------------------
3098
- # Material Helper Functions
3099
- # -------------------------
3100
- #
3101
- # Materials affect the appearance of rendered faces. These helper functions and
3102
- # datatypes simplify the creation of materials for rendering stimuli.
4497
+ # ---------------------------
4498
+ # Draw settings
3103
4499
  #
3104
4500
 
3105
- Material = namedtuple('Material', ['face', 'params', 'textures', 'userData'])
3106
-
3107
4501
 
3108
- def createMaterial(params=(), textures=(), face=GL.GL_FRONT_AND_BACK):
3109
- """Create a new material.
4502
+ def activeTexture(unit):
4503
+ """Set the active texture unit.
3110
4504
 
3111
4505
  Parameters
3112
4506
  ----------
4507
+ unit : GLenum, int or str
4508
+ Texture unit to activate. Values can be `GL_TEXTURE0`, `GL_TEXTURE1`,
4509
+ `GL_TEXTURE2`, etc.
3113
4510
 
3114
- params : :obj:`list` of :obj:`tuple`, optional
3115
- List of material modes and values. Each mode is assigned a value as
3116
- (mode, color). Modes can be GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR,
3117
- GL_EMISSION, GL_SHININESS or GL_AMBIENT_AND_DIFFUSE. Colors must be
3118
- a tuple of 4 floats which specify reflectance values for each RGBA
3119
- component. The value of GL_SHININESS should be a single float. If no
3120
- values are specified, an empty material will be created.
3121
- textures : :obj:`list` of :obj:`tuple`, optional
3122
- List of texture units and TexImage2D descriptors. These will be written
3123
- to the 'textures' field of the returned descriptor. For example,
3124
- [(GL.GL_TEXTURE0, texDesc0), (GL.GL_TEXTURE1, texDesc1)]. The number of
3125
- texture units per-material is GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.
3126
- face : :obj:`int`, optional
3127
- Faces to apply material to. Values can be GL_FRONT_AND_BACK, GL_FRONT
3128
- and GL_BACK. The default is GL_FRONT_AND_BACK.
4511
+ """
4512
+ if isinstance(unit, str):
4513
+ unit = _getGLEnum(unit)
4514
+ else:
4515
+ if unit < MAX_TEXTURE_UNITS:
4516
+ unit = GL.GL_TEXTURE0 + unit
3129
4517
 
3130
- Returns
3131
- -------
3132
- Material :
3133
- A descriptor with material properties.
4518
+ GL.glActiveTexture(unit)
3134
4519
 
3135
- Examples
3136
- --------
3137
- Creating a new material with given properties::
3138
4520
 
3139
- # The values for the material below can be found at
3140
- # http://devernay.free.fr/cours/opengl/materials.html
4521
+ def bindTexture(texture, target=None, unit=None):
4522
+ """Bind a texture to a target.
3141
4523
 
3142
- # create a gold material
3143
- gold = createMaterial([
3144
- (GL.GL_AMBIENT, (0.24725, 0.19950, 0.07450, 1.0)),
3145
- (GL.GL_DIFFUSE, (0.75164, 0.60648, 0.22648, 1.0)),
3146
- (GL.GL_SPECULAR, (0.628281, 0.555802, 0.366065, 1.0)),
3147
- (GL.GL_SHININESS, 0.4 * 128.0)])
4524
+ Parameters
4525
+ ----------
4526
+ texture : GLuint, int, None, TexImage2DInfo or TexImage2DMultisampleInfo
4527
+ Texture to bind. If `None`, the texture will be unbound.
4528
+ target : GLenum, int or str, optional
4529
+ Target to bind the texture to. Default is `None`. If `None`, and the
4530
+ texture is a `TexImage2DInfo` or `TexImage2DMultisampleInfo` object,
4531
+ the target will be inferred from the texture object. If specified, the
4532
+ value will override the target inferred from the texture. If `None` and
4533
+ the texture is an integer, the target will default to `GL_TEXTURE_2D`.
4534
+ unit : GLenum, int or None, optional
4535
+ Texture unit to bind the texture to, this will also set the active
4536
+ texture unit. Default is `None`. If `None`, the texture will be bound to
4537
+ the currently active texture unit.
3148
4538
 
3149
- Use the material when drawing::
4539
+ """
4540
+ if isinstance(target, str):
4541
+ target = getattr(GL, target, None)
4542
+ if target is None:
4543
+ raise ValueError('Invalid target string specified.')
4544
+
4545
+ if texture is None:
4546
+ texture = 0
4547
+ else:
4548
+ if isinstance(texture, (TexImage2DInfo, TexImage2DMultisampleInfo)):
4549
+ texture = texture.name
4550
+ if target is None:
4551
+ target = texture.target
4552
+ else:
4553
+ texture = int(texture)
4554
+ if target is None:
4555
+ target = GL.GL_TEXTURE_2D
3150
4556
 
3151
- useMaterial(gold)
3152
- drawVAO( ... ) # all meshes will be gold
3153
- useMaterial(None) # turn off material when done
4557
+ if unit is not None: # bind the texture to a specific unit
4558
+ activeTexture(unit)
4559
+
4560
+ GL.glBindTexture(target, texture)
3154
4561
 
3155
- Create a red plastic material, but define reflectance and shine later::
3156
4562
 
3157
- red_plastic = createMaterial()
4563
+ def setPolygonMode(face, mode):
4564
+ """Set the polygon rasterization mode for a face.
3158
4565
 
3159
- # you need to convert values to ctypes!
3160
- red_plastic.values[GL_AMBIENT] = (GLfloat * 4)(0.0, 0.0, 0.0, 1.0)
3161
- red_plastic.values[GL_DIFFUSE] = (GLfloat * 4)(0.5, 0.0, 0.0, 1.0)
3162
- red_plastic.values[GL_SPECULAR] = (GLfloat * 4)(0.7, 0.6, 0.6, 1.0)
3163
- red_plastic.values[GL_SHININESS] = 0.25 * 128.0
4566
+ Parameters
4567
+ ----------
4568
+ face : GLenum or str
4569
+ Face to set the polygon mode for. Values can be `GL_FRONT`, `GL_BACK`,
4570
+ or `GL_FRONT_AND_BACK`. Strings may also be used to specify the face,
4571
+ where the following are valid: 'front' (for `GL_FRONT`), 'back' (for
4572
+ `GL_BACK`), and 'front_and_back' (for `GL_FRONT_AND_BACK`).
4573
+ mode : GLenum or str
4574
+ Polygon rasterization mode. Values can be `GL_POINT`, `GL_LINE`, or
4575
+ `GL_FILL`. Strings may also be used to specify the mode, where the
4576
+ following are valid: 'point' (for `GL_POINT`), 'line' (for `GL_LINE`),
4577
+ and 'fill' (for `GL_FILL`).
3164
4578
 
3165
- # set and draw
3166
- useMaterial(red_plastic)
3167
- drawVertexbuffers( ... ) # all meshes will be red plastic
3168
- useMaterial(None)
4579
+ """
4580
+ if isinstance(face, str):
4581
+ face = getattr(GL, face, None)
4582
+ if face is None:
4583
+ raise ValueError(
4584
+ 'Invalid face string specified, got {}.'.format(face))
4585
+ if isinstance(mode, str):
4586
+ mode = getattr(GL, mode, None)
4587
+ if mode is None:
4588
+ raise ValueError(
4589
+ 'Invalid mode string specified, got {}.'.format(mode))
4590
+
4591
+ GL.glPolygonMode(face, mode)
4592
+
4593
+
4594
+ def setWireframeDraw(face=GL.GL_FRONT_AND_BACK):
4595
+ """Set the rasterization mode to wireframe.
4596
+
4597
+ Successive draw operations will render wireframe polygons (i.e. outlines).
4598
+
4599
+ Parameters
4600
+ ----------
4601
+ face : GLenum or int, optional
4602
+ Faces to apply wireframe to. Values can be `GL_FRONT_AND_BACK`,
4603
+ `GL_FRONT` and `GL_BACK`. The default is `GL_FRONT_AND_BACK`. Strings
4604
+ may also be used to specify the face, where the following are valid:
4605
+ 'front' (for `GL_FRONT`), 'back' (for `GL_BACK`), and 'front_and_back'
4606
+ (for `GL_FRONT_AND_BACK`).
3169
4607
 
3170
4608
  """
3171
- # setup material mode/value slots
3172
- matDesc = Material(
3173
- face,
3174
- {mode: None for mode in (
3175
- GL.GL_AMBIENT,
3176
- GL.GL_DIFFUSE,
3177
- GL.GL_SPECULAR,
3178
- GL.GL_EMISSION,
3179
- GL.GL_SHININESS)},
3180
- dict(),
3181
- dict())
3182
- if params:
3183
- for mode, param in params:
3184
- matDesc.params[mode] = \
3185
- (GL.GLfloat * 4)(*param) \
3186
- if mode != GL.GL_SHININESS else GL.GLfloat(param)
3187
- if textures:
3188
- maxTexUnits = getIntegerv(GL.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)
3189
- for unit, texDesc in textures:
3190
- if unit <= GL.GL_TEXTURE0 + (maxTexUnits - 1):
3191
- matDesc.textures[unit] = texDesc
3192
- else:
3193
- raise ValueError("Invalid texture unit enum.")
4609
+ if isinstance(face, str):
4610
+ face = getattr(GL, face, None)
4611
+ if face is None:
4612
+ raise ValueError(
4613
+ 'Invalid face string specified, got {}.'.format(face))
4614
+
4615
+ setPolygonMode(face, GL.GL_LINE)
3194
4616
 
3195
- return matDesc
3196
4617
 
4618
+ def setFillDraw(face=GL.GL_FRONT_AND_BACK):
4619
+ """Set the rasterization mode to fill polygons.
3197
4620
 
3198
- class SimpleMaterial:
3199
- """Class representing a simple material.
4621
+ Successive draw operations will render filled polygons.
4622
+
4623
+ Parameters
4624
+ ----------
4625
+ face : GLenum or int, optional
4626
+ Faces to apply fill to. Values can be `GL_FRONT_AND_BACK`, `GL_FRONT`
4627
+ and `GL_BACK`. The default is `GL_FRONT_AND_BACK`. Strings may also be
4628
+ used to specify the face, where the following are valid: 'front' (for
4629
+ `GL_FRONT`), 'back' (for `GL_BACK`), and 'front_and_back' (for
4630
+ `GL_FRONT_AND_BACK`).
3200
4631
 
3201
- This class stores material information to modify the appearance of drawn
3202
- primitives with respect to lighting, such as color (diffuse, specular,
3203
- ambient, and emission), shininess, and textures. Simple materials are
3204
- intended to work with features supported by the fixed-function OpenGL
3205
- pipeline.
4632
+ """
4633
+ if isinstance(face, str):
4634
+ face = getattr(GL, face, None)
4635
+ if face is None:
4636
+ raise ValueError(
4637
+ 'Invalid face string specified, got {}.'.format(face))
4638
+
4639
+ setPolygonMode(face, GL.GL_FILL)
4640
+
4641
+
4642
+ def setDepthTest(enable=True):
4643
+ """Enable or disable depth testing.
4644
+
4645
+ Parameters
4646
+ ----------
4647
+ enable : bool, optional
4648
+ Enable or disable depth testing. Default is `True`.
3206
4649
 
3207
4650
  """
3208
- def __init__(self,
3209
- win=None,
3210
- diffuseColor=(.5, .5, .5),
4651
+ if enable:
4652
+ GL.glEnable(GL.GL_DEPTH_TEST)
4653
+ else:
4654
+ GL.glDisable(GL.GL_DEPTH_TEST)
4655
+
4656
+
4657
+ def setDepthFunc(func):
4658
+ """Set the depth comparison function.
4659
+
4660
+ Parameters
4661
+ ----------
4662
+ func : GLenum or str
4663
+ Depth comparison function. Values can be `GL_NEVER`, `GL_LESS`,
4664
+ `GL_EQUAL`, `GL_LEQUAL`, `GL_GREATER`, `GL_NOTEQUAL`, `GL_GEQUAL`, or
4665
+ `GL_ALWAYS`. Strings may also be used to specify the function, where
4666
+ the following are valid: 'never' (for `GL_NEVER`), 'less' (for `GL_LESS`),
4667
+ 'equal' (for `GL_EQUAL`), 'lequal' (for `GL_LEQUAL`), 'greater' (for
4668
+ `GL_GREATER`), 'notequal' (for `GL_NOTEQUAL`), 'gequal' (for `GL_GEQUAL`),
4669
+ and 'always' (for `GL_ALWAYS`).
4670
+
4671
+ """
4672
+ func = _getGLEnum(func)
4673
+ GL.glDepthFunc(func)
4674
+
4675
+
4676
+ def setDepthMask(enable=True):
4677
+ """Enable or disable writing to the depth buffer.
4678
+
4679
+ Parameters
4680
+ ----------
4681
+ enable : bool, optional
4682
+ Enable or disable writing to the depth buffer. Default is `True`.
4683
+
4684
+ """
4685
+ if enable:
4686
+ GL.glDepthMask(GL.GL_TRUE)
4687
+ else:
4688
+ GL.glDepthMask(GL.GL_FALSE)
4689
+
4690
+
4691
+ def setDepthRange(near, far):
4692
+ """Set the range of depth values.
4693
+
4694
+ Parameters
4695
+ ----------
4696
+ near : float
4697
+ Near clipping plane.
4698
+ far : float
4699
+ Far clipping plane.
4700
+
4701
+ """
4702
+ GL.glDepthRange(near, far)
4703
+
4704
+
4705
+ def setClearColor(color):
4706
+ """Set the clear color for the color buffer.
4707
+
4708
+ Parameters
4709
+ ----------
4710
+ color : array_like
4711
+ Color to clear the color buffer with. The color should be in RGBA
4712
+ format, where each component is in the range [0, 1].
4713
+
4714
+ """
4715
+ GL.glClearColor(*color)
4716
+
4717
+
4718
+ def setClearDepth(depth):
4719
+ """Set the clear value for the depth buffer.
4720
+
4721
+ Parameters
4722
+ ----------
4723
+ depth : float
4724
+ Value to clear the depth buffer with.
4725
+
4726
+ """
4727
+ GL.glClearDepth(depth)
4728
+
4729
+
4730
+ def enable(enum):
4731
+ """Enable a GL capability.
4732
+
4733
+ Parameters
4734
+ ----------
4735
+ enum : GLenum, int or str
4736
+ Capability to enable.
4737
+
4738
+ """
4739
+ if isinstance(enum, str):
4740
+ enum = getattr(GL, enum, None)
4741
+
4742
+ if enum is None:
4743
+ raise ValueError('Invalid capability string specified.')
4744
+
4745
+ GL.glEnable(enum)
4746
+
4747
+
4748
+ def disable(enum):
4749
+ """Disable a GL capability.
4750
+
4751
+ Parameters
4752
+ ----------
4753
+ enum : GLenum, int or str
4754
+ Capability to disable.
4755
+
4756
+ """
4757
+ if isinstance(enum, str):
4758
+ enum = getattr(GL, enum, None)
4759
+
4760
+ if enum is None:
4761
+ raise ValueError('Invalid capability string specified.')
4762
+
4763
+ GL.glDisable(enum)
4764
+
4765
+
4766
+ def setLineWidth(width):
4767
+ """Set the width of rasterized lines.
4768
+
4769
+ Parameters
4770
+ ----------
4771
+ width : float
4772
+ Width of rasterized lines.
4773
+
4774
+ """
4775
+ GL.glLineWidth(width)
4776
+
4777
+
4778
+ def setLineSmooth(enable=True):
4779
+ """Enable or disable line antialiasing.
4780
+
4781
+ Parameters
4782
+ ----------
4783
+ enable : bool, optional
4784
+ Enable or disable line antialiasing. Default is `True`.
4785
+
4786
+ """
4787
+ if enable:
4788
+ GL.glEnable(GL.GL_LINE_SMOOTH)
4789
+ else:
4790
+ GL.glDisable(GL.GL_LINE_SMOOTH)
4791
+
4792
+
4793
+ def setPointSize(size):
4794
+ """Set the size of rasterized points.
4795
+
4796
+ Parameters
4797
+ ----------
4798
+ size : float
4799
+ Size of rasterized points.
4800
+
4801
+ """
4802
+ GL.glPointSize(size)
4803
+
4804
+
4805
+ def setPointSmooth(enable=True):
4806
+ """Enable or disable point antialiasing.
4807
+
4808
+ Parameters
4809
+ ----------
4810
+ enable : bool, optional
4811
+ Enable or disable point antialiasing. Default is `True`.
4812
+
4813
+ """
4814
+ if enable:
4815
+ GL.glEnable(GL.GL_POINT_SMOOTH)
4816
+ else:
4817
+ GL.glDisable(GL.GL_POINT_SMOOTH)
4818
+
4819
+
4820
+ def setMultiSample(enable=True):
4821
+ """Enable or disable multisample antialiasing.
4822
+
4823
+ Parameters
4824
+ ----------
4825
+ enable : bool, optional
4826
+ Enable or disable multisample antialiasing. Default is `True`.
4827
+
4828
+ """
4829
+ if enable:
4830
+ GL.glEnable(GL.GL_MULTISAMPLE)
4831
+ else:
4832
+ GL.glDisable(GL.GL_MULTISAMPLE)
4833
+
4834
+
4835
+ def setBlend(enable=True):
4836
+ """Enable or disable blending.
4837
+
4838
+ Parameters
4839
+ ----------
4840
+ enable : bool, optional
4841
+ Enable or disable blending. Default is `True`.
4842
+
4843
+ """
4844
+ if enable:
4845
+ GL.glEnable(GL.GL_BLEND)
4846
+ else:
4847
+ GL.glDisable(GL.GL_BLEND)
4848
+
4849
+
4850
+ def setBlendFunc(srcFactor, dstFactor):
4851
+ """Set the blending function for source and destination factors.
4852
+
4853
+ Parameters
4854
+ ----------
4855
+ srcFactor : GLenum or str
4856
+ Source blending factor. Eg. `GL_SRC_ALPHA`.
4857
+ dstFactor : GLenum or str
4858
+ Destination blending factor. Eg. `GL_ONE_MINUS_SRC_ALPHA`.
4859
+
4860
+ Examples
4861
+ --------
4862
+ Set the blending function to use source alpha and one minus source alpha::
4863
+
4864
+ setBlendFunc('GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA')
4865
+
4866
+ """
4867
+ if isinstance(srcFactor, str):
4868
+ srcFactor = getattr(GL, srcFactor, None)
4869
+ if srcFactor is None:
4870
+ raise ValueError(
4871
+ 'Invalid enum specified for source factor. Got {}.'.format(
4872
+ srcFactor))
4873
+ if isinstance(dstFactor, str):
4874
+ dstFactor = getattr(GL, dstFactor, None)
4875
+ if dstFactor is None:
4876
+ raise ValueError(
4877
+ 'Invalid enum specified for destination factor. Got {}.'.format(
4878
+ dstFactor))
4879
+
4880
+ GL.glBlendFunc(srcFactor, dstFactor)
4881
+
4882
+
4883
+ # -------------------------
4884
+ # Material Helper Functions
4885
+ # -------------------------
4886
+ #
4887
+ # Materials affect the appearance of rendered faces. These helper functions and
4888
+ # datatypes simplify the creation of materials for rendering stimuli.
4889
+ #
4890
+
4891
+ Material = namedtuple('Material', ['face', 'params', 'textures', 'userData'])
4892
+
4893
+
4894
+ def createMaterial(params=(), textures=(), face=GL.GL_FRONT_AND_BACK):
4895
+ """Create a new material.
4896
+
4897
+ Parameters
4898
+ ----------
4899
+
4900
+ params : :obj:`list` of :obj:`tuple`, optional
4901
+ List of material modes and values. Each mode is assigned a value as
4902
+ (mode, color). Modes can be GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR,
4903
+ GL_EMISSION, GL_SHININESS or GL_AMBIENT_AND_DIFFUSE. Colors must be
4904
+ a tuple of 4 floats which specify reflectance values for each RGBA
4905
+ component. The value of GL_SHININESS should be a single float. If no
4906
+ values are specified, an empty material will be created.
4907
+ textures : :obj:`list` of :obj:`tuple`, optional
4908
+ List of texture units and TexImage2D descriptors. These will be written
4909
+ to the 'textures' field of the returned descriptor. For example,
4910
+ [(GL.GL_TEXTURE0, texDesc0), (GL.GL_TEXTURE1, texDesc1)]. The number of
4911
+ texture units per-material is GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.
4912
+ face : :obj:`int`, optional
4913
+ Faces to apply material to. Values can be GL_FRONT_AND_BACK, GL_FRONT
4914
+ and GL_BACK. The default is GL_FRONT_AND_BACK.
4915
+
4916
+ Returns
4917
+ -------
4918
+ Material :
4919
+ A descriptor with material properties.
4920
+
4921
+ Examples
4922
+ --------
4923
+ Creating a new material with given properties::
4924
+
4925
+ # The values for the material below can be found at
4926
+ # http://devernay.free.fr/cours/opengl/materials.html
4927
+
4928
+ # create a gold material
4929
+ gold = createMaterial([
4930
+ (GL.GL_AMBIENT, (0.24725, 0.19950, 0.07450, 1.0)),
4931
+ (GL.GL_DIFFUSE, (0.75164, 0.60648, 0.22648, 1.0)),
4932
+ (GL.GL_SPECULAR, (0.628281, 0.555802, 0.366065, 1.0)),
4933
+ (GL.GL_SHININESS, 0.4 * 128.0)])
4934
+
4935
+ Use the material when drawing::
4936
+
4937
+ useMaterial(gold)
4938
+ drawVAO( ... ) # all meshes will be gold
4939
+ useMaterial(None) # turn off material when done
4940
+
4941
+ Create a red plastic material, but define reflectance and shine later::
4942
+
4943
+ red_plastic = createMaterial()
4944
+
4945
+ # you need to convert values to ctypes!
4946
+ red_plastic.values[GL_AMBIENT] = (GLfloat * 4)(0.0, 0.0, 0.0, 1.0)
4947
+ red_plastic.values[GL_DIFFUSE] = (GLfloat * 4)(0.5, 0.0, 0.0, 1.0)
4948
+ red_plastic.values[GL_SPECULAR] = (GLfloat * 4)(0.7, 0.6, 0.6, 1.0)
4949
+ red_plastic.values[GL_SHININESS] = 0.25 * 128.0
4950
+
4951
+ # set and draw
4952
+ useMaterial(red_plastic)
4953
+ drawVertexbuffers( ... ) # all meshes will be red plastic
4954
+ useMaterial(None)
4955
+
4956
+ """
4957
+ # setup material mode/value slots
4958
+ matDesc = Material(
4959
+ face,
4960
+ {mode: None for mode in (
4961
+ GL.GL_AMBIENT,
4962
+ GL.GL_DIFFUSE,
4963
+ GL.GL_SPECULAR,
4964
+ GL.GL_EMISSION,
4965
+ GL.GL_SHININESS)},
4966
+ dict(),
4967
+ dict())
4968
+ if params:
4969
+ for mode, param in params:
4970
+ matDesc.params[mode] = \
4971
+ (GL.GLfloat * 4)(*param) \
4972
+ if mode != GL.GL_SHININESS else GL.GLfloat(param)
4973
+ if textures:
4974
+ maxTexUnits = getIntegerv(GL.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)
4975
+ for unit, texDesc in textures:
4976
+ if unit <= GL.GL_TEXTURE0 + (maxTexUnits - 1):
4977
+ matDesc.textures[unit] = texDesc
4978
+ else:
4979
+ raise ValueError("Invalid texture unit enum.")
4980
+
4981
+ return matDesc
4982
+
4983
+
4984
+ class SimpleMaterial:
4985
+ """Class representing a simple material.
4986
+
4987
+ This class stores material information to modify the appearance of drawn
4988
+ primitives with respect to lighting, such as color (diffuse, specular,
4989
+ ambient, and emission), shininess, and textures. Simple materials are
4990
+ intended to work with features supported by the fixed-function OpenGL
4991
+ pipeline.
4992
+
4993
+ """
4994
+ def __init__(self,
4995
+ win=None,
4996
+ diffuseColor=(.5, .5, .5),
3211
4997
  specularColor=(-1., -1., -1.),
3212
4998
  ambientColor=(-1., -1., -1.),
3213
4999
  emissionColor=(-1., -1., -1.),
@@ -3239,8 +5025,8 @@ class SimpleMaterial:
3239
5025
  colorSpace : float
3240
5026
  Color space for `diffuseColor`, `specularColor`, `ambientColor`, and
3241
5027
  `emissionColor`.
3242
- diffuseTexture : TexImage2D
3243
- specularTexture : TexImage2D
5028
+ diffuseTexture : TexImage2DInfo
5029
+ specularTexture : TexImage2DInfo
3244
5030
  opacity : float
3245
5031
  Opacity of the material. Ranges from 0.0 to 1.0 where 1.0 is fully
3246
5032
  opaque.
@@ -4533,38 +6319,256 @@ def createBox(size=(1., 1., 1.), flipFaces=False):
4533
6319
  return vertices, texCoords, normals, faces
4534
6320
 
4535
6321
 
4536
- def transformMeshPosOri(vertices, normals, pos=(0., 0., 0.), ori=(0., 0., 0., 1.)):
4537
- """Transform a mesh.
6322
+ def createDisc(radius=1.0, edges=16):
6323
+ """Create a disc (filled circle) mesh.
4538
6324
 
4539
- Transform mesh vertices and normals to a new position and orientation using
4540
- a position coordinate and rotation quaternion. Values `vertices` and
4541
- `normals` must be the same shape. This is intended to be used when editing
4542
- raw vertex data prior to rendering. Do not use this to change the
4543
- configuration of an object while rendering.
6325
+ Generates a flat disc mesh with the specified radius and number of `edges`.
6326
+ The origin of the disc is located at the center. Textures coordinates will
6327
+ be mapped to a square which bounds the circle. Normals are perpendicular to
6328
+ the face of the circle.
4544
6329
 
4545
6330
  Parameters
4546
6331
  ----------
4547
- vertices : array_like
4548
- Nx3 array of vertices.
4549
- normals : array_like
4550
- Nx3 array of normals.
4551
- pos : array_like, optional
4552
- Position vector to transform mesh vertices. If Nx3, `vertices` will be
4553
- transformed by corresponding rows of `pos`.
4554
- ori : array_like, optional
4555
- Orientation quaternion in form [x, y, z, w]. If Nx4, `vertices` and
4556
- `normals` will be transformed by corresponding rows of `ori`.
6332
+ radius : float
6333
+ Radius of the disc in scene units.
6334
+ edges : int
6335
+ Number of segments to use to define the outer rim of the disc. Higher
6336
+ numbers will result in a smoother circle but will use more triangles.
4557
6337
 
4558
6338
  Returns
4559
6339
  -------
4560
6340
  tuple
4561
- Transformed vertices and normals.
6341
+ Vertex attribute arrays (position, texture coordinates, and normals) and
6342
+ triangle indices.
4562
6343
 
4563
6344
  Examples
4564
6345
  --------
4565
- Create and re-orient a plane to face upwards::
6346
+ Create a vertex array object to draw a disc::
4566
6347
 
4567
- vertices, textureCoords, normals, faces = createPlane()
6348
+ vertices, textureCoords, normals, faces = gltools.createDisc(edges=128)
6349
+ vertexVBO = gltools.createVBO(vertices)
6350
+ texCoordVBO = gltools.createVBO(textureCoords)
6351
+ normalsVBO = gltools.createVBO(normals)
6352
+ indexBuffer = gltools.createVBO(
6353
+ faces.flatten(),
6354
+ target=GL.GL_ELEMENT_ARRAY_BUFFER,
6355
+ dataType=GL.GL_UNSIGNED_INT)
6356
+
6357
+ vao = gltools.createVAO(
6358
+ {gltools.gl_Vertex: vertexVBO,
6359
+ gltools.gl_MultiTexCoord0: texCoordVBO,
6360
+ gltools.gl_Normal: normalsVBO},
6361
+ indexBuffer=indexBuffer)
6362
+
6363
+ """
6364
+ # get number of steps for vertices to get the number of edges we want
6365
+ nVerts = edges + 1
6366
+ steps = np.linspace(0, 2 * np.pi, num=nVerts, dtype=np.float32)
6367
+
6368
+ # offset since the first vertex is the centre
6369
+ vertices = np.zeros((nVerts + 1, 3), dtype=np.float32)
6370
+ vertices[1:, 0] = np.sin(steps)
6371
+ vertices[1:, 1] = np.cos(steps)
6372
+
6373
+ # compute the face indices
6374
+ faces = []
6375
+ for i in range(nVerts):
6376
+ faces.append([0, i + 1, i])
6377
+
6378
+ faces = np.ascontiguousarray(faces, dtype=np.uint32)
6379
+
6380
+ # compute the texture coordinates for each vertex
6381
+ normals = np.zeros_like(vertices, dtype=np.float32)
6382
+ normals[:, 2] = 1.
6383
+
6384
+ # compute texture coordinates
6385
+ texCoords = vertices.copy()
6386
+ texCoords[:, :] += 1.0
6387
+ texCoords[:, :] *= 0.5
6388
+
6389
+ # scale to specified radius
6390
+ vertices *= radius
6391
+
6392
+ return vertices, texCoords, normals, faces
6393
+
6394
+
6395
+ def createAnnulus(innerRadius=0.5, outerRadius=1.0, edges=16):
6396
+ """Create an annulus (ring) mesh.
6397
+
6398
+ Generates a flat ring mesh with the specified inner/outer radii and number
6399
+ of `edges`. The origin of the ring is located at the center. Textures
6400
+ coordinates will be mapped to a square which bounds the ring. Normals are
6401
+ perpendicular to the plane of the ring.
6402
+
6403
+ Parameters
6404
+ ----------
6405
+ innerRadius, outerRadius : float
6406
+ Radius of the inner and outer rims of the ring in scene units.
6407
+ edges : int
6408
+ Number of segments to use to define the band of the ring. The higher the
6409
+ value, the rounder the ring will look.
6410
+
6411
+ Returns
6412
+ -------
6413
+ tuple
6414
+ Vertex attribute arrays (position, texture coordinates, and normals) and
6415
+ triangle indices.
6416
+
6417
+ """
6418
+ # error checks
6419
+ if innerRadius >= outerRadius:
6420
+ raise ValueError("Inner radius must be less than outer.")
6421
+ elif outerRadius <= 0.:
6422
+ raise ValueError("Outer radius must be >0.")
6423
+ elif innerRadius < 0.:
6424
+ raise ValueError("Inner radius must be positive.")
6425
+ elif edges <= 2:
6426
+ raise ValueError("Number of edges must be >2.")
6427
+
6428
+ # generate inner and outer vertices
6429
+ nVerts = edges + 1
6430
+ steps = np.linspace(0, 2 * np.pi, num=nVerts, dtype=np.float32)
6431
+
6432
+ innerVerts = np.zeros((nVerts, 3), dtype=np.float32)
6433
+ outerVerts = np.zeros((nVerts, 3), dtype=np.float32)
6434
+ innerVerts[:, 0] = outerVerts[:, 0] = np.sin(steps)
6435
+ innerVerts[:, 1] = outerVerts[:, 1] = np.cos(steps)
6436
+
6437
+ # Keep the ring size between -1 and 1 to simplify computing texture
6438
+ # coordinates. We'll scale the vertices to the correct dimensions
6439
+ # afterwards.
6440
+ frac = innerRadius / float(outerRadius)
6441
+ innerVerts[:, :2] *= frac
6442
+
6443
+ # combine inner and outer vertex rings
6444
+ vertPos = np.vstack((innerVerts, outerVerts))
6445
+
6446
+ # generate faces
6447
+ faces = []
6448
+ for i in range(nVerts):
6449
+ faces.append([i, edges + i + 1, edges + i])
6450
+ faces.append([i, i + 1, edges + i + 1])
6451
+
6452
+ vertPos = np.ascontiguousarray(vertPos, dtype=np.float32)
6453
+ normals = np.zeros_like(vertPos, dtype=np.float32)
6454
+ normals[:, 2] = 1.0
6455
+ faces = np.ascontiguousarray(faces, dtype=np.uint32)
6456
+
6457
+ # compute texture coordinates
6458
+ texCoords = vertPos.copy()
6459
+ texCoords[:, :] += 1.0
6460
+ texCoords[:, :] *= 0.5
6461
+
6462
+ # scale to specified outer radius
6463
+ vertPos[:, :2] *= outerRadius
6464
+
6465
+ return vertPos, texCoords, normals, faces
6466
+
6467
+
6468
+ def createCylinder(radius=1.0, height=1.0, edges=16, stacks=1):
6469
+ """Create a cylinder mesh.
6470
+
6471
+ Generate a cylinder mesh with a given `height` and `radius`. The origin of
6472
+ the mesh will centered on it and offset to the base. Texture coordinates
6473
+ will be generated allowing a texture to wrap around it.
6474
+
6475
+ Parameters
6476
+ ----------
6477
+ radius : float
6478
+ Radius of the cylinder in scene units.
6479
+ height : float
6480
+ Height in scene units.
6481
+ edges : int
6482
+ Number of edges, the greater the number, the smoother the cylinder will
6483
+ appear when drawn.
6484
+ stacks : int
6485
+ Number of subdivisions along the height of cylinder to make. Setting to
6486
+ 1 will result in vertex data only being generated for the base and end
6487
+ of the cylinder.
6488
+
6489
+ Returns
6490
+ -------
6491
+ tuple
6492
+ Vertex attribute arrays (position, texture coordinates, and normals) and
6493
+ triangle indices.
6494
+
6495
+ """
6496
+ # generate vertex positions
6497
+ nEdgeVerts = edges + 1
6498
+ rings = stacks + 1
6499
+ steps = np.linspace(0, 2 * np.pi, num=nEdgeVerts)
6500
+ vertPos = np.zeros((nEdgeVerts, 3))
6501
+ vertPos[:, 0] = np.sin(steps)
6502
+ vertPos[:, 1] = np.cos(steps)
6503
+ vertPos = np.tile(vertPos, (rings, 1))
6504
+
6505
+ # apply offset in height for each stack
6506
+ stackHeight = np.linspace(0, height, num=rings)
6507
+ vertPos[:, 2] = np.repeat(stackHeight, nEdgeVerts)
6508
+
6509
+ # generate texture coordinates to they wrap around the cylinder
6510
+ u = np.linspace(0.0, 1.0, nEdgeVerts)
6511
+ v = np.linspace(1.0, 0.0, rings)
6512
+ uu, vv = np.meshgrid(u, v)
6513
+ texCoords = np.vstack([uu.ravel(), vv.ravel()]).T
6514
+
6515
+ # generate vertex normals, since our vertices all on a unit circle, we can
6516
+ # do a trick here
6517
+ normals = vertPos.copy()
6518
+ normals[:, 2] = 0.0
6519
+
6520
+ # create face indices
6521
+ faces = []
6522
+ for i in range(0, stacks):
6523
+ stackOffset = nEdgeVerts * i
6524
+ for j in range(nEdgeVerts):
6525
+ j = stackOffset + j
6526
+ faces.append([j, edges + j, edges + j + 1])
6527
+ faces.append([j, edges + j + 1, j + 1])
6528
+
6529
+ vertPos, texCoords, normals = [
6530
+ np.ascontiguousarray(i, dtype=np.float32) for i in (
6531
+ vertPos, texCoords, normals)]
6532
+ faces = np.ascontiguousarray(faces, dtype=np.uint32)
6533
+
6534
+ # scale the cylinder's radius and height to what the user specified
6535
+ vertPos[:, :2] *= radius
6536
+
6537
+ return vertPos, texCoords, normals, faces
6538
+
6539
+
6540
+ def transformMeshPosOri(vertices, normals, pos=(0., 0., 0.), ori=(0., 0., 0., 1.)):
6541
+ """Transform a mesh.
6542
+
6543
+ Transform mesh vertices and normals to a new position and orientation using
6544
+ a position coordinate and rotation quaternion. Values `vertices` and
6545
+ `normals` must be the same shape. This is intended to be used when editing
6546
+ raw vertex data prior to rendering. Do not use this to change the
6547
+ configuration of an object while rendering.
6548
+
6549
+ Parameters
6550
+ ----------
6551
+ vertices : array_like
6552
+ Nx3 array of vertices.
6553
+ normals : array_like
6554
+ Nx3 array of normals.
6555
+ pos : array_like, optional
6556
+ Position vector to transform mesh vertices. If Nx3, `vertices` will be
6557
+ transformed by corresponding rows of `pos`.
6558
+ ori : array_like, optional
6559
+ Orientation quaternion in form [x, y, z, w]. If Nx4, `vertices` and
6560
+ `normals` will be transformed by corresponding rows of `ori`.
6561
+
6562
+ Returns
6563
+ -------
6564
+ tuple
6565
+ Transformed vertices and normals.
6566
+
6567
+ Examples
6568
+ --------
6569
+ Create and re-orient a plane to face upwards::
6570
+
6571
+ vertices, textureCoords, normals, faces = createPlane()
4568
6572
 
4569
6573
  # rotation quaternion
4570
6574
  qr = quatFromAxisAngle((1., 0., 0.), -90.0) # -90 degrees about +X axis
@@ -4581,31 +6585,39 @@ def transformMeshPosOri(vertices, normals, pos=(0., 0., 0.), ori=(0., 0., 0., 1.
4581
6585
  normals = np.ascontiguousarray(normals)
4582
6586
 
4583
6587
  if not np.allclose(pos, [0., 0., 0.]):
4584
- vertices = mt.transform(pos, ori, vertices)
6588
+ vertices = mt.transform(pos, ori, vertices, dtype=np.float32)
4585
6589
 
4586
6590
  if not np.allclose(ori, [0., 0., 0., 1.]):
4587
- normals = mt.applyQuat(ori, normals)
6591
+ normals = mt.applyQuat(ori, normals, dtype=np.float32)
4588
6592
 
4589
6593
  return vertices, normals
4590
6594
 
4591
6595
 
6596
+ # ------------------------------
6597
+ # Mesh editing and cleanup tools
6598
+ # ------------------------------
6599
+ #
6600
+
4592
6601
  def calculateVertexNormals(vertices, faces, shading='smooth'):
4593
6602
  """Calculate vertex normals given vertices and triangle faces.
4594
6603
 
4595
6604
  Finds all faces sharing a vertex index and sets its normal to either
4596
6605
  the face normal if `shading='flat'` or the average normals of adjacent
4597
- faces if `shading='smooth'`. Flat shading only works correctly if each
6606
+ faces if `shading='smooth'`. Note, this function does not convert between
6607
+ flat and smooth shading. Flat shading only works correctly if each
4598
6608
  vertex belongs to exactly one face.
4599
6609
 
4600
6610
  The direction of the normals are determined by the winding order of
4601
- triangles, assumed counter clock-wise (OpenGL default). Most model
6611
+ triangles, assumed counter clock-wise (OpenGL default). Most 3D model
4602
6612
  editing software exports using this convention. If not, winding orders
4603
6613
  can be reversed by calling::
4604
6614
 
4605
- faces = np.fliplr(faces)
6615
+ faces = numpy.fliplr(faces)
4606
6616
 
4607
- In some case, creases may appear if vertices are at the same location,
4608
- but do not share the same index.
6617
+ In some case when using 'smooth', creases may appear if vertices are at the
6618
+ same location, but do not share the same index. This may be desired in some
6619
+ cases, however one may use the :func:`smoothCreases` function computing
6620
+ normals to smooth out creases.
4609
6621
 
4610
6622
  Parameters
4611
6623
  ----------
@@ -4615,7 +6627,8 @@ def calculateVertexNormals(vertices, faces, shading='smooth'):
4615
6627
  Nx3 vertex indices.
4616
6628
  shading : str, optional
4617
6629
  Shading mode. Options are 'smooth' and 'flat'. Flat only works with
4618
- meshes where no vertex index is shared across faces.
6630
+ meshes where no vertex index is shared across faces, if not, the
6631
+ returned normals will be invalid.
4619
6632
 
4620
6633
  Returns
4621
6634
  -------
@@ -4635,7 +6648,7 @@ def calculateVertexNormals(vertices, faces, shading='smooth'):
4635
6648
  # compute surface normals for all faces
4636
6649
  faceNormals = mt.surfaceNormal(vertices[faces])
4637
6650
 
4638
- normals = []
6651
+ normals = [] # new list of normals to return
4639
6652
  if shading == 'flat':
4640
6653
  for vertexIdx in np.unique(faces):
4641
6654
  match, _ = np.where(faces == vertexIdx)
@@ -4646,7 +6659,611 @@ def calculateVertexNormals(vertices, faces, shading='smooth'):
4646
6659
  match, _ = np.where(faces == vertexIdx)
4647
6660
  normals.append(mt.vertexNormal(faceNormals[match, :]))
4648
6661
 
4649
- return np.ascontiguousarray(normals) + 0.0
6662
+ return np.ascontiguousarray(np.vstack(normals), np.float32) + 0.0
6663
+
6664
+
6665
+ def mergeVertices(vertices, faces, textureCoords=None, vertDist=0.0001,
6666
+ texDist=0.0001):
6667
+ """Simplify a mesh by removing redundant vertices.
6668
+
6669
+ This function simplifies a mesh by merging overlapping (doubled) vertices,
6670
+ welding together adjacent faces and removing sharp creases that appear when
6671
+ rendering. This is useful in cases where a mesh's triangles do not share
6672
+ vertices and one wishes to have it appear smoothly shaded when rendered. One
6673
+ can also use this function reduce the detail of a mesh, however the quality
6674
+ of the results may vary.
6675
+
6676
+ The position of the new vertex after merging will be the average position
6677
+ of the adjacent vertices. Re-indexed faces and recalculated normals are also
6678
+ returned with the cleaned-up vertex data. If texture coordinates are
6679
+ supplied, adjacent vertices will not be removed if their is a distance in
6680
+ texel space is greater than `texDist`. This avoids discontinuities in the
6681
+ texture of the simplified mesh.
6682
+
6683
+ Parameters
6684
+ ----------
6685
+ vertices : ndarray
6686
+ Nx3 array of vertex positions.
6687
+ faces : ndarray
6688
+ Nx3 integer array of face vertex indices.
6689
+ textureCoords : ndarray
6690
+ Nx2 array of texture coordinates.
6691
+ vertDist : float
6692
+ Maximum distance between two adjacent vertices to merge in scene units.
6693
+ texDist : float
6694
+ Maximum distance between texels to permit merging of vertices. If a
6695
+ vertex is within merging distance, it will be moved instead of merged.
6696
+
6697
+ Returns
6698
+ -------
6699
+ tuple
6700
+ Tuple containing newly computed vertices, normals and face indices. If
6701
+ `textureCoords` was specified, a new array of texture coordinates will
6702
+ be returned too at the second index.
6703
+
6704
+ Notes
6705
+ -----
6706
+ * This function only works on meshes consisting of triangle faces.
6707
+
6708
+ Examples
6709
+ --------
6710
+ Remove redundant vertices from a sphere::
6711
+
6712
+ vertices, textureCoords, normals, faces = gltools.createUVSphere()
6713
+ vertices, textureCoords, normals, faces = gltools.removeDoubles(
6714
+ vertices, faces, textureCoords)
6715
+
6716
+ Same but no texture coordinates are specified::
6717
+
6718
+ vertices, normals, faces = gltools.removeDoubles(vertices, faces)
6719
+
6720
+ """
6721
+ # keep track of vertices that we merged
6722
+ vertsProcessed = np.zeros((vertices.shape[0],), dtype=np.bool)
6723
+
6724
+ faces = faces.flatten() # existing faces but flattened
6725
+ # new array of faces that will get updated
6726
+ newFaces = np.zeros_like(faces, dtype=np.uint32)
6727
+
6728
+ # loop over all vertices in the original mesh
6729
+ newVerts = []
6730
+ newTexCoords = []
6731
+ lastProcIdx = 0 # last index processed, used to reindex
6732
+ for i, vertex in enumerate(vertices):
6733
+ if vertsProcessed[i]: # don't do merge check if already processed
6734
+ continue
6735
+
6736
+ # get the distance to all other vertices in mesh
6737
+ vertDists = mt.distance(vertex, vertices)
6738
+
6739
+ # get vertices that fall with the threshold distance
6740
+ toProcess = np.where(vertDists <= vertDist)[0]
6741
+
6742
+ # if all adjacent vertices were processed, move on to the next
6743
+ if np.all(vertsProcessed[toProcess]):
6744
+ continue
6745
+
6746
+ # if we have close verts and they have not been processed, merge them
6747
+ if len(toProcess) > 1:
6748
+ # If we have texture coords, merge those whose texture coords are
6749
+ # close. Move the vertex to the new location for any that are not.
6750
+ if textureCoords is not None:
6751
+ # create a new vertex by averaging out positions
6752
+ texCoordDists = mt.distance(textureCoords[i, :],
6753
+ textureCoords[toProcess, :])
6754
+
6755
+ # get the vertices to merge or move
6756
+ toMerge = toProcess[texCoordDists <= texDist]
6757
+ toMove = toProcess[texCoordDists > texDist]
6758
+
6759
+ # compute mean positions
6760
+ newPos = np.mean(vertices[toMerge, :], axis=0)
6761
+ newTexCoord = np.mean(textureCoords[toMerge, :], axis=0)
6762
+
6763
+ newVerts.append(newPos)
6764
+ newTexCoords.append(newTexCoord)
6765
+ newFaces[np.in1d(faces, toMerge).nonzero()[0]] = lastProcIdx
6766
+
6767
+ # handle vertices that were moved
6768
+ for j, idx in enumerate(toMove):
6769
+ newVerts.append(newPos)
6770
+ newTexCoords.append(textureCoords[idx, :])
6771
+ newFaces[np.argwhere(faces == idx)] = lastProcIdx + j
6772
+
6773
+ lastProcIdx += len(toMove)
6774
+
6775
+ vertsProcessed[toProcess] = 1 # update verts we processed
6776
+
6777
+ else:
6778
+ newPos = np.mean(vertices[toProcess, :], axis=0)
6779
+ newVerts.append(newPos)
6780
+ newFaces[np.in1d(faces, toProcess).nonzero()[0]] = lastProcIdx
6781
+ vertsProcessed[toProcess] = 1 # update verts we processed
6782
+
6783
+ else:
6784
+ # single vertices need to be added too
6785
+ newVerts.append(vertex)
6786
+ if textureCoords is not None:
6787
+ newTexCoords.append(textureCoords[i, :])
6788
+ vertsProcessed[i] = 1 # update merged list
6789
+ newFaces[np.argwhere(faces == i)] = lastProcIdx
6790
+
6791
+ lastProcIdx += 1
6792
+
6793
+ # all vertices have been processed, exit loop early
6794
+ if np.all(vertsProcessed):
6795
+ break
6796
+
6797
+ # create new output arrays
6798
+ newVerts = np.ascontiguousarray(np.vstack(newVerts), dtype=np.float32)
6799
+ newFaces = np.ascontiguousarray(newFaces.reshape((-1, 3)), dtype=np.uint32)
6800
+ newNormals = calculateVertexNormals(newVerts, newFaces, 'smooth')
6801
+
6802
+ if textureCoords is not None:
6803
+ newTexCoords = np.ascontiguousarray(
6804
+ np.vstack(newTexCoords), dtype=np.float32)
6805
+ toReturn = (newVerts, newTexCoords, newNormals, newFaces)
6806
+ else:
6807
+ toReturn = (newVerts, newNormals, newFaces)
6808
+
6809
+ return toReturn
6810
+
6811
+
6812
+ def smoothCreases(vertices, normals, vertDist=0.0001):
6813
+ """Remove creases caused by misaligned surface normals.
6814
+
6815
+ A problem arises where surface normals are not correctly interpolated across
6816
+ the edge where two faces meet, resulting in a visible 'crease' or sharp
6817
+ discontinuity in shading. This is usually caused by the normals of the
6818
+ overlapping vertices forming the edge being mis-aligned (not pointing in the
6819
+ same direction).
6820
+
6821
+ If you notice these crease artifacts are present in your mesh *after*
6822
+ computing surface normals, you can use this function to smooth them out.
6823
+
6824
+ Parameters
6825
+ ----------
6826
+ vertices : ndarray
6827
+ Nx3 array of vertex coordinates.
6828
+ normals : ndarray
6829
+ Nx3 array of vertex normals.
6830
+ vertDist : float
6831
+ Maximum distance between vertices to average. Avoid using large numbers
6832
+ here, vertices to be smoothed should be overlapping. This distance
6833
+ should be as small as possible, just enough to account for numeric
6834
+ rounding errors between vertices intended which would otherwise be at
6835
+ the exact same location.
6836
+
6837
+ Returns
6838
+ -------
6839
+ ndarray
6840
+ Array of smoothed surface normals with the same shape as `normals`.
6841
+
6842
+ """
6843
+ newNormals = normals.copy()
6844
+
6845
+ # keep track of vertices that we processed
6846
+ vertsProcessed = np.zeros((vertices.shape[0],), dtype=np.bool)
6847
+
6848
+ for i, vertex in enumerate(vertices):
6849
+ if vertsProcessed[i]: # don't do merge check if already processed
6850
+ continue
6851
+
6852
+ # get the distance to all other vertices in mesh
6853
+ dist = mt.distance(vertex, vertices)
6854
+
6855
+ # get vertices that fall with the threshold distance
6856
+ adjacentIdx = list(np.where(dist <= vertDist)[0])
6857
+
6858
+ if np.all(vertsProcessed[adjacentIdx]):
6859
+ continue
6860
+
6861
+ # now get their normals and average them
6862
+ if len(adjacentIdx) > 1:
6863
+ toAverage = vertices[adjacentIdx, :]
6864
+ newNormal = np.mean(toAverage, axis=0)
6865
+
6866
+ # normalize
6867
+ newNormal = mt.normalize(newNormal, out=newNormal)
6868
+
6869
+ # overwrite the normals we used to compute this one
6870
+ newNormals[adjacentIdx, :] = newNormal
6871
+
6872
+ # flag these normals as used
6873
+ vertsProcessed[adjacentIdx] = 1
6874
+
6875
+ return newNormals
6876
+
6877
+
6878
+ def flipFaces(normals, faces):
6879
+ """Change the winding order of face indices.
6880
+
6881
+ OpenGL uses the winding order of face vertices to determine which side of
6882
+ the face is either the front and back. This function reverses the winding
6883
+ order of face indices and flips vertex normals so faces can be correctly
6884
+ shaded.
6885
+
6886
+ Parameters
6887
+ ----------
6888
+ normals : ndarray
6889
+ Nx3 array of surface normals.
6890
+ faces : ndarray
6891
+ Nx3 array of face indices.
6892
+
6893
+ Returns
6894
+ -------
6895
+ ndarray
6896
+ Face indices with winding order reversed.
6897
+
6898
+ Examples
6899
+ --------
6900
+ Flip faces and normals of a box mesh so it can be viewed and lit from the
6901
+ inside::
6902
+
6903
+ vertices, texCoords, normals, faces = createBox((5, 5, 5))
6904
+ faces = flipFaces(faces)
6905
+
6906
+ """
6907
+ normals = -normals # invert normal vectors
6908
+ faces = np.fliplr(faces)
6909
+
6910
+ return normals, faces
6911
+
6912
+
6913
+ def interleaveAttributes(attribArrays):
6914
+ """Interleave vertex attributes into a single array.
6915
+
6916
+ Interleave vertex attributes into a single array for use with OpenGL's
6917
+ vertex array objects. This function is useful when creating a VBO from
6918
+ separate arrays of vertex positions, normals, and texture coordinates.
6919
+
6920
+ Parameters
6921
+ ----------
6922
+ attribArrays : list of array_like
6923
+ List of arrays containing vertex attributes. Each array must have the
6924
+ same number of rows.
6925
+
6926
+ Returns
6927
+ -------
6928
+ tuple
6929
+ A tuple containing the interleaved vertex attribute array, a list of
6930
+ attribute sizes, and a list of attribute offsets.
6931
+
6932
+ Examples
6933
+ --------
6934
+ Interleave vertex attributes for use with a VAO::
6935
+
6936
+ vertices, textureCoords, normals, faces = createBox()
6937
+ interleaved, sizes, offsets = interleaveAttributes(
6938
+ [vertices, textureCoords, normals])
6939
+
6940
+ # create a VBO with interleaved attributes
6941
+ vboInterleaved = createVBO(interleaved)
6942
+
6943
+ # ... before rendering, set the attribute pointers
6944
+ GL.glBindBuffer(vboInterleaved.target, vboInterleaved.name)
6945
+ for i, attrib in enumerate([0, 8, 3]):
6946
+ gltools.setVertexAttribPointer(
6947
+ attrib, vboInterleaved, size=sizes[i], offset=offsets[i])
6948
+
6949
+ """
6950
+ # get the number of rows in the first array
6951
+ nRows = attribArrays[0].shape[0]
6952
+
6953
+ # check if all arrays have the same number of rows
6954
+ if any([i.shape[0] != nRows for i in attribArrays]):
6955
+ raise ValueError("All arrays must have the same number of rows.")
6956
+
6957
+ # get a list of attribute widths
6958
+ sizes = [i.shape[1] for i in attribArrays]
6959
+ offsets = [0] + [sum(sizes[:i]) for i in range(1, len(sizes))]
6960
+
6961
+ # combine all arrays horizontally
6962
+ toReturn = np.hstack(attribArrays)
6963
+
6964
+ return np.ascontiguousarray(toReturn, dtype=np.float32), sizes, offsets
6965
+
6966
+
6967
+ def generateTexCoords(vertices):
6968
+ """Generate texture coordinates for a mesh.
6969
+
6970
+ Generate texture coordinates for a mesh by normalizing the vertex positions
6971
+ to the bounding box of the mesh.
6972
+
6973
+ Parameters
6974
+ ----------
6975
+ vertices : ndarray
6976
+ Nx2 or Nx3 array of vertex positions.
6977
+
6978
+ Returns
6979
+ -------
6980
+ ndarray
6981
+ Nx2 array of normalized texture coordinates.
6982
+
6983
+ Examples
6984
+ --------
6985
+ Generate texture coordinates for a box mesh::
6986
+
6987
+ vertices, textureCoords, normals, faces = createBox()
6988
+ texCoords = generateTexCoords(vertices)
6989
+
6990
+ """
6991
+ # normalize the vertex positions to the bounding box
6992
+ texCoords = (vertices - np.min(vertices, axis=0)) / np.ptp(vertices, axis=0)
6993
+
6994
+ return np.ascontiguousarray(texCoords, dtype=np.float32)
6995
+
6996
+
6997
+ def tesselate(points, mode='triangle', config=None):
6998
+ """Tesselate (or fill) a 2D polygon edge loop.
6999
+
7000
+ Tesselate a 2D polygon defined by a set of vertices. This creates a 'filled'
7001
+ polygon where the vertices are connected by a set of triangles. The function
7002
+ will return a tesselated mesh with vertex positions, surface normals, and
7003
+ face indices. Texture coordinates are generated by normalizing the vertex
7004
+ positions.
7005
+
7006
+ Parameters
7007
+ ----------
7008
+ points : ndarray
7009
+ Nx2 array of vertex positions of the outer boundary.
7010
+ mode : str, optional
7011
+ Tesselation mode (algorithm) to use. Options are 'triangle', 'delaunay',
7012
+ 'fan' or 'simple'. Default is 'triangle' (recommended) which is the most
7013
+ robust method that can handle concave polygons and holes. Use 'fan' for
7014
+ equilateral polygons.
7015
+ config : dict, optional
7016
+ Configuration options for the tesselation. This can be used to specify
7017
+ additional options for the tesselation algorithm.
7018
+
7019
+ Returns
7020
+ -------
7021
+ tuple
7022
+ A tuple containing the vertex positions, surface normals, texture
7023
+ coordinates, and face indices of the tesselated mesh.
7024
+
7025
+ Examples
7026
+ --------
7027
+ Tesselate a simple square::
7028
+
7029
+ vertices = np.array([[0, 0], [1, 0], [1, 1], [0, 1]])
7030
+ vertices, normals, texCoords, faces = tesselate(points)
7031
+
7032
+ Notes
7033
+ -----
7034
+ * The 'triangle' mode uses the Triangle library (via MeshPy) to tesselate
7035
+ the polygon, see: https://www.cs.cmu.edu/~quake/triangle.html
7036
+ * The 'delaunay' mode uses the `scipy.spatial.Delaunay` for tesselation
7037
+ (using the Qhull library). However, it cannot handle holes or concave
7038
+ polygons.
7039
+ * The 'fan' mode creates a fan tesselation where the first vertex is the
7040
+ center of the fan and the rest are the outer boundary. This is useful for
7041
+ creating simple, nonconcave shapes like circles.
7042
+
7043
+ """
7044
+ config = config or dict() # ensure we have a config dictionary
7045
+
7046
+ nPoints = len(points)
7047
+ if nPoints < 3:
7048
+ raise ValueError(
7049
+ "At least 3 points are required to tesselate a polygon.")
7050
+ elif nPoints == 3:
7051
+ # if we only have 3 points, we can't tesselate the polygon
7052
+ vertices = np.array(points, dtype=np.float32)
7053
+ faces = np.array([[0, 1, 2]], dtype=np.uint32)
7054
+ normals = np.ascontiguousarray(
7055
+ np.tile([0., 0., -1.], (faces.shape[0], 1)), dtype=np.float32)
7056
+ texCoords = generateTexCoords(vertices)
7057
+
7058
+ return vertices, normals, texCoords, faces
7059
+
7060
+ # perform tesselation based on the mode
7061
+ if mode == 'triangle' or mode == 'meshpy':
7062
+ # trinagulation using meshpy (triangle)
7063
+ from meshpy.triangle import MeshInfo, build
7064
+
7065
+ # create a mesh info object
7066
+ meshInfo = MeshInfo()
7067
+ meshInfo.set_points(points)
7068
+
7069
+ # compute facets
7070
+ facetEnd = len(points) - 1
7071
+ facets = [(i, i + 1) for i in range(0, facetEnd)] + [(facetEnd, 0)]
7072
+ meshInfo.set_facets(facets)
7073
+
7074
+ # build the mesh
7075
+ mesh = build(meshInfo, **config)
7076
+
7077
+ # get the vertices and faces
7078
+ vertices = mesh.points
7079
+ faces = mesh.elements
7080
+
7081
+ elif mode == 'fan' or mode == 'equilateral':
7082
+ # This mode creates a fan tesselation where the first vertex is the
7083
+ # centroid of the polygon and the rest are the outer boundary. Works
7084
+ # well for equilateral polygons that have not concavities. This is
7085
+ # probably the fastest method to tesselate an equilateral polygon with
7086
+ # good results.
7087
+
7088
+ # find the centroid of the polygon
7089
+ centroid = np.mean(points, axis=0)
7090
+
7091
+ # add the centroid to the list of vertices
7092
+ vertices = np.vstack((centroid, points))
7093
+
7094
+ # generate faces
7095
+ faces = np.zeros((len(points), 3), dtype=np.uint32)
7096
+ faces[:, 0] = 0
7097
+ faces[:, 1] = np.arange(1, len(points) + 1)
7098
+ faces[:, 2] = np.roll(faces[:, 1], 1)
7099
+
7100
+ elif mode == 'delaunay' or mode == 'scipy':
7101
+ # Delaunay triangulation using scipy. This triangulates the polygon
7102
+ # using the Qhull library. This method is reasonably fast however it
7103
+ # outputs the convex hull of the polygon which may not be desired.
7104
+
7105
+ # do a delaunay triangulation
7106
+ from scipy.spatial import Delaunay
7107
+
7108
+ # create a delaunay triangulation
7109
+ result = Delaunay(points, **config)
7110
+
7111
+ # get the face indices
7112
+ vertices = result.points
7113
+ faces = result.simplices
7114
+
7115
+ elif mode == 'simple':
7116
+ # Simple tesselation mode where the vertices are connected in a simple
7117
+ # fan pattern from the first vertex in the provided array. This is
7118
+ # useful for simple shapes like circles or squares.
7119
+ vertices = np.vstack((points, points[0]))
7120
+ faces = np.array(
7121
+ [[0, i + 1, i + 2] for i in range(len(points) - 1)])
7122
+
7123
+ else:
7124
+ raise ValueError("Invalid mode '{}' specified.".format(mode))
7125
+
7126
+ # compute surface normals for all faces
7127
+ vertices = np.ascontiguousarray(vertices, dtype=np.float32)
7128
+ faces = np.ascontiguousarray(faces, dtype=np.uint32)
7129
+
7130
+ # generate normals for each vertex, facing outwards
7131
+ normals = np.ascontiguousarray(
7132
+ np.tile([0., 0., -1.], (faces.shape[0], 1)),
7133
+ dtype=np.float32)
7134
+
7135
+ # generate texture coordinates which are normalized vertex positions
7136
+ texCoords = generateTexCoords(vertices)
7137
+
7138
+ return vertices, normals, texCoords, faces
7139
+
7140
+
7141
+ def mergeMeshes(meshData):
7142
+ """Merge multiple meshes into a single mesh.
7143
+
7144
+ Merge multiple meshes into a single mesh by combining their vertex positions,
7145
+ normals, texture coordinates, and face indices.
7146
+
7147
+ Parameters
7148
+ ----------
7149
+ meshData : list of tuple
7150
+ List of tuples containing vertex positions, normals, texture coordinates,
7151
+ and face indices for each mesh to merge. Each tuple must contain the
7152
+ vertex positions, and may optionally contain the normals and texture
7153
+ coordinates. The face indices must be provided.
7154
+
7155
+ Returns
7156
+ -------
7157
+ tuple
7158
+ A tuple containing the merged vertex positions, normals, texture
7159
+ coordinates, and face indices. If `normals` or `texCoords` are not
7160
+ provided, they will be set to `None`.
7161
+
7162
+ Examples
7163
+ --------
7164
+ Merge two meshes into a single mesh::
7165
+
7166
+ vertices1, normals1, texCoords1, faces1 = createBox()
7167
+ vertices2, normals2, texCoords2, faces2 = createUVSphere()
7168
+
7169
+ # merge the two meshes
7170
+ vertices, normals, texCoords, faces = mergeMeshes([
7171
+ (vertices1, normals1, texCoords1, faces1),
7172
+ (vertices2, normals2, texCoords2, faces2)])
7173
+
7174
+ Create a tube using primatives::
7175
+
7176
+ tubeOuter = createCylinder(1.0, 1.0, 16, 1)
7177
+ tubeInner = createCylinder(0.5, 1.0, 16, 1)
7178
+
7179
+ # merge the tube parts
7180
+ vertices, normals, texCoords, faces = mergeMeshes(
7181
+ tubeOuter, tubeInner])
7182
+
7183
+ """
7184
+ vertices = np.ascontiguousarray(
7185
+ np.vstack([i[0] for i in meshData]), dtype=np.float32)
7186
+
7187
+ newFaces = []
7188
+ offset = 0
7189
+ for i in meshData:
7190
+ newFaces.append(i[3] + offset)
7191
+ offset += np.max(i[3]) + 1
7192
+
7193
+ faces = np.ascontiguousarray(np.vstack(newFaces), dtype=np.uint32)
7194
+
7195
+ # check if normals and texture coordinates are provided
7196
+ if all([i[1] is not None for i in meshData]):
7197
+ normals = np.ascontiguousarray(
7198
+ np.vstack([i[1] for i in meshData]), dtype=np.float32)
7199
+ else:
7200
+ normals = None
7201
+
7202
+ if all([i[2] is not None for i in meshData]):
7203
+ texCoords = np.ascontiguousarray(
7204
+ np.vstack([i[2] for i in meshData]), dtype=np.float32)
7205
+ else:
7206
+ texCoords = None
7207
+
7208
+ return vertices, normals, texCoords, faces
7209
+
7210
+
7211
+ def nextPowerOfTwo(value):
7212
+ """Compute the next power of two for a given value.
7213
+
7214
+ Parameters
7215
+ ----------
7216
+ value : int
7217
+ The value to compute the next power of two for.
7218
+
7219
+ Returns
7220
+ -------
7221
+ int
7222
+ The next power of two for the given value.
7223
+
7224
+ """
7225
+ return 2 ** int(np.ceil(np.log2(value)))
7226
+
7227
+
7228
+ def fitTextureToPowerOfTwo(width, height, square=False):
7229
+ """Determine the horizontal and vertical dimensions of a image buffer that
7230
+ can contain the specified width and height, fitted to the next power of two.
7231
+
7232
+ Parameters
7233
+ ----------
7234
+ width : int
7235
+ The width of the texture in pixels.
7236
+ height : int
7237
+ The height of the texture in pixels.
7238
+ square : bool, optional
7239
+ If True, the texture will be fitted to the next power of two in both
7240
+ dimensions. Default is False.
7241
+
7242
+ Returns
7243
+ -------
7244
+ tuple
7245
+ A tuple containing the width and height of the texture fitted to the
7246
+ next power of two.
7247
+
7248
+ Raises
7249
+ ------
7250
+ ValueError
7251
+ If the texture dimensions exceed the maximum texture size
7252
+ supported by the OpenGL implementation.
7253
+
7254
+ """
7255
+ maxTexSize = getOpenGLInfo().maxTextureSize
7256
+
7257
+ newWidth = nextPowerOfTwo(width)
7258
+ newHeight = nextPowerOfTwo(height)
7259
+
7260
+ if newWidth > maxTexSize or newHeight > maxTexSize:
7261
+ raise ValueError("Texture dimensions exceed maximum texture size.")
7262
+
7263
+ if square:
7264
+ newWidth = newHeight = max(newWidth, newHeight)
7265
+
7266
+ return newWidth, newHeight
4650
7267
 
4651
7268
 
4652
7269
  # -----------------------------
@@ -4718,7 +7335,7 @@ def getModelViewMatrix():
4718
7335
 
4719
7336
  """
4720
7337
  modelview = np.zeros((4, 4), dtype=np.float32)
4721
-
7338
+
4722
7339
  GL.glGetFloatv(GL.GL_MODELVIEW_MATRIX, modelview.ctypes.data_as(
4723
7340
  ctypes.POINTER(ctypes.c_float)))
4724
7341
 
@@ -4746,214 +7363,5 @@ def getProjectionMatrix():
4746
7363
  return proj
4747
7364
 
4748
7365
 
4749
- # OpenGL information type
4750
- OpenGLInfo = namedtuple(
4751
- 'OpenGLInfo',
4752
- ['vendor',
4753
- 'renderer',
4754
- 'version',
4755
- 'majorVersion',
4756
- 'minorVersion',
4757
- 'doubleBuffer',
4758
- 'maxTextureSize',
4759
- 'stereo',
4760
- 'maxSamples',
4761
- 'extensions',
4762
- 'userData'])
4763
-
4764
-
4765
- def getOpenGLInfo():
4766
- """Get general information about the OpenGL implementation on this machine.
4767
- This should provide a consistent means of doing so regardless of the OpenGL
4768
- interface we are using.
4769
-
4770
- Returns are dictionary with the following fields::
4771
-
4772
- vendor, renderer, version, majorVersion, minorVersion, doubleBuffer,
4773
- maxTextureSize, stereo, maxSamples, extensions
4774
-
4775
- Supported extensions are returned as a list in the 'extensions' field. You
4776
- can check if a platform supports an extension by checking the membership of
4777
- the extension name in that list.
4778
-
4779
- Returns
4780
- -------
4781
- OpenGLInfo
4782
-
4783
- """
4784
- return OpenGLInfo(getString(GL.GL_VENDOR),
4785
- getString(GL.GL_RENDERER),
4786
- getString(GL.GL_VERSION),
4787
- getIntegerv(GL.GL_MAJOR_VERSION),
4788
- getIntegerv(GL.GL_MINOR_VERSION),
4789
- getIntegerv(GL.GL_DOUBLEBUFFER),
4790
- getIntegerv(GL.GL_MAX_TEXTURE_SIZE),
4791
- getIntegerv(GL.GL_STEREO),
4792
- getIntegerv(GL.GL_MAX_SAMPLES),
4793
- [i for i in getString(GL.GL_EXTENSIONS).split(' ')],
4794
- dict())
4795
-
4796
-
4797
- # ---------------------
4798
- # OpenGL/VRML Materials
4799
- # ---------------------
4800
- #
4801
- # A collection of pre-defined materials for stimuli. Keep in mind that these
4802
- # materials only approximate real-world equivalents. Values were obtained from
4803
- # http://devernay.free.fr/cours/opengl/materials.html (08/24/18). There are four
4804
- # material libraries to use, where individual material descriptors are accessed
4805
- # via property names.
4806
- #
4807
- # Usage:
4808
- #
4809
- # useMaterial(metalMaterials.gold)
4810
- # drawVAO(myObject)
4811
- # ...
4812
- #
4813
- mineralMaterials = namedtuple(
4814
- 'mineralMaterials',
4815
- ['emerald', 'jade', 'obsidian', 'pearl', 'ruby', 'turquoise'])(
4816
- createMaterial(
4817
- [(GL.GL_AMBIENT, (0.0215, 0.1745, 0.0215, 1.0)),
4818
- (GL.GL_DIFFUSE, (0.07568, 0.61424, 0.07568, 1.0)),
4819
- (GL.GL_SPECULAR, (0.633, 0.727811, 0.633, 1.0)),
4820
- (GL.GL_SHININESS, 0.6 * 128.0)]),
4821
- createMaterial(
4822
- [(GL.GL_AMBIENT, (0.135, 0.2225, 0.1575, 1.0)),
4823
- (GL.GL_DIFFUSE, (0.54, 0.89, 0.63, 1.0)),
4824
- (GL.GL_SPECULAR, (0.316228, 0.316228, 0.316228, 1.0)),
4825
- (GL.GL_SHININESS, 0.1 * 128.0)]),
4826
- createMaterial(
4827
- [(GL.GL_AMBIENT, (0.05375, 0.05, 0.06625, 1.0)),
4828
- (GL.GL_DIFFUSE, (0.18275, 0.17, 0.22525, 1.0)),
4829
- (GL.GL_SPECULAR, (0.332741, 0.328634, 0.346435, 1.0)),
4830
- (GL.GL_SHININESS, 0.3 * 128.0)]),
4831
- createMaterial(
4832
- [(GL.GL_AMBIENT, (0.25, 0.20725, 0.20725, 1.0)),
4833
- (GL.GL_DIFFUSE, (1, 0.829, 0.829, 1.0)),
4834
- (GL.GL_SPECULAR, (0.296648, 0.296648, 0.296648, 1.0)),
4835
- (GL.GL_SHININESS, 0.088 * 128.0)]),
4836
- createMaterial(
4837
- [(GL.GL_AMBIENT, (0.1745, 0.01175, 0.01175, 1.0)),
4838
- (GL.GL_DIFFUSE, (0.61424, 0.04136, 0.04136, 1.0)),
4839
- (GL.GL_SPECULAR, (0.727811, 0.626959, 0.626959, 1.0)),
4840
- (GL.GL_SHININESS, 0.6 * 128.0)]),
4841
- createMaterial(
4842
- [(GL.GL_AMBIENT, (0.1, 0.18725, 0.1745, 1.0)),
4843
- (GL.GL_DIFFUSE, (0.396, 0.74151, 0.69102, 1.0)),
4844
- (GL.GL_SPECULAR, (0.297254, 0.30829, 0.306678, 1.0)),
4845
- (GL.GL_SHININESS, 0.1 * 128.0)])
4846
- )
4847
-
4848
- metalMaterials = namedtuple(
4849
- 'metalMaterials',
4850
- ['brass', 'bronze', 'chrome', 'copper', 'gold', 'silver'])(
4851
- createMaterial(
4852
- [(GL.GL_AMBIENT, (0.329412, 0.223529, 0.027451, 1.0)),
4853
- (GL.GL_DIFFUSE, (0.780392, 0.568627, 0.113725, 1.0)),
4854
- (GL.GL_SPECULAR, (0.992157, 0.941176, 0.807843, 1.0)),
4855
- (GL.GL_SHININESS, 0.21794872 * 128.0)]),
4856
- createMaterial(
4857
- [(GL.GL_AMBIENT, (0.2125, 0.1275, 0.054, 1.0)),
4858
- (GL.GL_DIFFUSE, (0.714, 0.4284, 0.18144, 1.0)),
4859
- (GL.GL_SPECULAR, (0.393548, 0.271906, 0.166721, 1.0)),
4860
- (GL.GL_SHININESS, 0.2 * 128.0)]),
4861
- createMaterial(
4862
- [(GL.GL_AMBIENT, (0.25, 0.25, 0.25, 1.0)),
4863
- (GL.GL_DIFFUSE, (0.4, 0.4, 0.4, 1.0)),
4864
- (GL.GL_SPECULAR, (0.774597, 0.774597, 0.774597, 1.0)),
4865
- (GL.GL_SHININESS, 0.6 * 128.0)]),
4866
- createMaterial(
4867
- [(GL.GL_AMBIENT, (0.19125, 0.0735, 0.0225, 1.0)),
4868
- (GL.GL_DIFFUSE, (0.7038, 0.27048, 0.0828, 1.0)),
4869
- (GL.GL_SPECULAR, (0.256777, 0.137622, 0.086014, 1.0)),
4870
- (GL.GL_SHININESS, 0.1 * 128.0)]),
4871
- createMaterial(
4872
- [(GL.GL_AMBIENT, (0.24725, 0.1995, 0.0745, 1.0)),
4873
- (GL.GL_DIFFUSE, (0.75164, 0.60648, 0.22648, 1.0)),
4874
- (GL.GL_SPECULAR, (0.628281, 0.555802, 0.366065, 1.0)),
4875
- (GL.GL_SHININESS, 0.4 * 128.0)]),
4876
- createMaterial(
4877
- [(GL.GL_AMBIENT, (0.19225, 0.19225, 0.19225, 1.0)),
4878
- (GL.GL_DIFFUSE, (0.50754, 0.50754, 0.50754, 1.0)),
4879
- (GL.GL_SPECULAR, (0.508273, 0.508273, 0.508273, 1.0)),
4880
- (GL.GL_SHININESS, 0.4 * 128.0)])
4881
- )
4882
-
4883
- plasticMaterials = namedtuple(
4884
- 'plasticMaterials',
4885
- ['black', 'cyan', 'green', 'red', 'white', 'yellow'])(
4886
- createMaterial(
4887
- [(GL.GL_AMBIENT, (0, 0, 0, 1.0)),
4888
- (GL.GL_DIFFUSE, (0.01, 0.01, 0.01, 1.0)),
4889
- (GL.GL_SPECULAR, (0.5, 0.5, 0.5, 1.0)),
4890
- (GL.GL_SHININESS, 0.25 * 128.0)]),
4891
- createMaterial(
4892
- [(GL.GL_AMBIENT, (0, 0.1, 0.06, 1.0)),
4893
- (GL.GL_DIFFUSE, (0.06, 0, 0.50980392, 1.0)),
4894
- (GL.GL_SPECULAR, (0.50196078, 0.50196078, 0.50196078, 1.0)),
4895
- (GL.GL_SHININESS, 0.25 * 128.0)]),
4896
- createMaterial(
4897
- [(GL.GL_AMBIENT, (0, 0, 0, 1.0)),
4898
- (GL.GL_DIFFUSE, (0.1, 0.35, 0.1, 1.0)),
4899
- (GL.GL_SPECULAR, (0.45, 0.55, 0.45, 1.0)),
4900
- (GL.GL_SHININESS, 0.25 * 128.0)]),
4901
- createMaterial(
4902
- [(GL.GL_AMBIENT, (0, 0, 0, 1.0)),
4903
- (GL.GL_DIFFUSE, (0.5, 0, 0, 1.0)),
4904
- (GL.GL_SPECULAR, (0.7, 0.6, 0.6, 1.0)),
4905
- (GL.GL_SHININESS, 0.25 * 128.0)]),
4906
- createMaterial(
4907
- [(GL.GL_AMBIENT, (0, 0, 0, 1.0)),
4908
- (GL.GL_DIFFUSE, (0.55, 0.55, 0.55, 1.0)),
4909
- (GL.GL_SPECULAR, (0.7, 0.7, 0.7, 1.0)),
4910
- (GL.GL_SHININESS, 0.25 * 128.0)]),
4911
- createMaterial(
4912
- [(GL.GL_AMBIENT, (0, 0, 0, 1.0)),
4913
- (GL.GL_DIFFUSE, (0.5, 0.5, 0, 1.0)),
4914
- (GL.GL_SPECULAR, (0.6, 0.6, 0.5, 1.0)),
4915
- (GL.GL_SHININESS, 0.25 * 128.0)])
4916
- )
4917
-
4918
- rubberMaterials = namedtuple(
4919
- 'rubberMaterials',
4920
- ['black', 'cyan', 'green', 'red', 'white', 'yellow'])(
4921
- createMaterial(
4922
- [(GL.GL_AMBIENT, (0.02, 0.02, 0.02, 1.0)),
4923
- (GL.GL_DIFFUSE, (0.01, 0.01, 0.01, 1.0)),
4924
- (GL.GL_SPECULAR, (0.4, 0.4, 0.4, 1.0)),
4925
- (GL.GL_SHININESS, 0.078125 * 128.0)]),
4926
- createMaterial(
4927
- [(GL.GL_AMBIENT, (0, 0.05, 0.05, 1.0)),
4928
- (GL.GL_DIFFUSE, (0.4, 0.5, 0.5, 1.0)),
4929
- (GL.GL_SPECULAR, (0.04, 0.7, 0.7, 1.0)),
4930
- (GL.GL_SHININESS, 0.078125 * 128.0)]),
4931
- createMaterial(
4932
- [(GL.GL_AMBIENT, (0, 0.05, 0, 1.0)),
4933
- (GL.GL_DIFFUSE, (0.4, 0.5, 0.4, 1.0)),
4934
- (GL.GL_SPECULAR, (0.04, 0.7, 0.04, 1.0)),
4935
- (GL.GL_SHININESS, 0.078125 * 128.0)]),
4936
- createMaterial(
4937
- [(GL.GL_AMBIENT, (0.05, 0, 0, 1.0)),
4938
- (GL.GL_DIFFUSE, (0.5, 0.4, 0.4, 1.0)),
4939
- (GL.GL_SPECULAR, (0.7, 0.04, 0.04, 1.0)),
4940
- (GL.GL_SHININESS, 0.078125 * 128.0)]),
4941
- createMaterial(
4942
- [(GL.GL_AMBIENT, (0.05, 0.05, 0.05, 1.0)),
4943
- (GL.GL_DIFFUSE, (0.5, 0.5, 0.5, 1.0)),
4944
- (GL.GL_SPECULAR, (0.7, 0.7, 0.7, 1.0)),
4945
- (GL.GL_SHININESS, 0.078125 * 128.0)]),
4946
- createMaterial(
4947
- [(GL.GL_AMBIENT, (0.05, 0.05, 0, 1.0)),
4948
- (GL.GL_DIFFUSE, (0.5, 0.5, 0.4, 1.0)),
4949
- (GL.GL_SPECULAR, (0.7, 0.7, 0.04, 1.0)),
4950
- (GL.GL_SHININESS, 0.078125 * 128.0)])
4951
- )
4952
-
4953
- # default material according to the OpenGL spec.
4954
- defaultMaterial = createMaterial(
4955
- [(GL.GL_AMBIENT, (0.2, 0.2, 0.2, 1.0)),
4956
- (GL.GL_DIFFUSE, (0.8, 0.8, 0.8, 1.0)),
4957
- (GL.GL_SPECULAR, (0.0, 0.0, 0.0, 1.0)),
4958
- (GL.GL_EMISSION, (0.0, 0.0, 0.0, 1.0)),
4959
- (GL.GL_SHININESS, 0)])
7366
+ if __name__ == "__main__":
7367
+ pass