OpenGLContext 3.0.0a1__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.
- OpenGLContext/__init__.py +151 -0
- OpenGLContext/arrays.py +2 -0
- OpenGLContext/atlas.py +300 -0
- OpenGLContext/audio/__init__.py +32 -0
- OpenGLContext/audio/scene.py +130 -0
- OpenGLContext/audio/settings.py +82 -0
- OpenGLContext/bin/__init__.py +0 -0
- OpenGLContext/bin/choosecontext.py +224 -0
- OpenGLContext/bin/choosefonts.py +209 -0
- OpenGLContext/bin/gltest.py +121 -0
- OpenGLContext/bin/gltf_demo.py +492 -0
- OpenGLContext/bin/gltf_regression.py +645 -0
- OpenGLContext/bin/gltf_view.py +30 -0
- OpenGLContext/bin/keyboardevents.py +93 -0
- OpenGLContext/bin/lorentz.py +109 -0
- OpenGLContext/bin/physics_cook.py +81 -0
- OpenGLContext/bin/profile_view.py +38 -0
- OpenGLContext/bin/terrain_view.py +581 -0
- OpenGLContext/bin/tiles_view.py +39 -0
- OpenGLContext/bin/ui_demo.py +228 -0
- OpenGLContext/bin/view.py +341 -0
- OpenGLContext/bin/vrml_view.py +42 -0
- OpenGLContext/capture.py +125 -0
- OpenGLContext/context.py +1253 -0
- OpenGLContext/contextconfig.py +177 -0
- OpenGLContext/contextdefinition.py +275 -0
- OpenGLContext/debug/__init__.py +6 -0
- OpenGLContext/debug/bufferimage.py +80 -0
- OpenGLContext/debug/leaks.py +13 -0
- OpenGLContext/debug/logcontext.py +9 -0
- OpenGLContext/debug/logs.py +15 -0
- OpenGLContext/debug/state.py +329 -0
- OpenGLContext/displaylist.py +69 -0
- OpenGLContext/doinchildmatrix.py +34 -0
- OpenGLContext/drawcube.py +58 -0
- OpenGLContext/events/__init__.py +10 -0
- OpenGLContext/events/default_config.py +29 -0
- OpenGLContext/events/event.py +65 -0
- OpenGLContext/events/eventhandlermixin.py +214 -0
- OpenGLContext/events/eventmanager.py +269 -0
- OpenGLContext/events/fxevents.py +90 -0
- OpenGLContext/events/glfwevents.py +424 -0
- OpenGLContext/events/glutevents.py +184 -0
- OpenGLContext/events/inputstate.py +129 -0
- OpenGLContext/events/internaltime.py +161 -0
- OpenGLContext/events/keyboardevents.py +153 -0
- OpenGLContext/events/mouseevents.py +473 -0
- OpenGLContext/events/pygameevents.py +162 -0
- OpenGLContext/events/systemtime.py +12 -0
- OpenGLContext/events/timeeventgeneratormanager.py +40 -0
- OpenGLContext/events/timeevents.py +143 -0
- OpenGLContext/events/timer.py +170 -0
- OpenGLContext/events/tkevents.py +172 -0
- OpenGLContext/events/wxevents.py +185 -0
- OpenGLContext/extensionmanager.py +158 -0
- OpenGLContext/framecounter.py +74 -0
- OpenGLContext/frustum.py +135 -0
- OpenGLContext/glfwcontext.py +395 -0
- OpenGLContext/glfwinteractivecontext.py +34 -0
- OpenGLContext/glfwvrmlcontext.py +11 -0
- OpenGLContext/glutcontext.py +224 -0
- OpenGLContext/glutinteractivecontext.py +24 -0
- OpenGLContext/gluttestingcontext.py +11 -0
- OpenGLContext/glutvrmlcontext.py +25 -0
- OpenGLContext/glutvrmltestingcontext.py +48 -0
- OpenGLContext/hud.py +404 -0
- OpenGLContext/interactivecontext.py +29 -0
- OpenGLContext/loaders/__init__.py +1 -0
- OpenGLContext/loaders/base.py +84 -0
- OpenGLContext/loaders/cc0.py +137 -0
- OpenGLContext/loaders/gltf/__init__.py +71 -0
- OpenGLContext/loaders/gltf/accessors.py +255 -0
- OpenGLContext/loaders/gltf/animation.py +611 -0
- OpenGLContext/loaders/gltf/draco.py +166 -0
- OpenGLContext/loaders/gltf/environment_sky.py +416 -0
- OpenGLContext/loaders/gltf/loader.py +80 -0
- OpenGLContext/loaders/gltf/materials.py +298 -0
- OpenGLContext/loaders/gltf/meshes.py +292 -0
- OpenGLContext/loaders/gltf/samples.py +162 -0
- OpenGLContext/loaders/gltf/scene.py +673 -0
- OpenGLContext/loaders/gltf/specular_glossiness.py +154 -0
- OpenGLContext/loaders/gltf/textures.py +155 -0
- OpenGLContext/loaders/gltf/transforms.py +261 -0
- OpenGLContext/loaders/gltf_demos.py +511 -0
- OpenGLContext/loaders/hdr.py +228 -0
- OpenGLContext/loaders/hdri.py +83 -0
- OpenGLContext/loaders/loader.py +255 -0
- OpenGLContext/loaders/obj.py +299 -0
- OpenGLContext/loaders/resolver.py +536 -0
- OpenGLContext/loaders/tiles3d/__init__.py +9 -0
- OpenGLContext/loaders/tiles3d/boundingvolume.py +120 -0
- OpenGLContext/loaders/tiles3d/dem.py +71 -0
- OpenGLContext/loaders/tiles3d/fetch.py +102 -0
- OpenGLContext/loaders/tiles3d/foliage.py +786 -0
- OpenGLContext/loaders/tiles3d/frustum.py +70 -0
- OpenGLContext/loaders/tiles3d/geomorph.py +57 -0
- OpenGLContext/loaders/tiles3d/gltf_uploader.py +246 -0
- OpenGLContext/loaders/tiles3d/loadmanager.py +121 -0
- OpenGLContext/loaders/tiles3d/physics_colliders.py +78 -0
- OpenGLContext/loaders/tiles3d/procedural.py +355 -0
- OpenGLContext/loaders/tiles3d/residency.py +102 -0
- OpenGLContext/loaders/tiles3d/runtime.py +168 -0
- OpenGLContext/loaders/tiles3d/sample.py +212 -0
- OpenGLContext/loaders/tiles3d/scatter.py +77 -0
- OpenGLContext/loaders/tiles3d/screenspaceerror.py +31 -0
- OpenGLContext/loaders/tiles3d/tileset.py +390 -0
- OpenGLContext/loaders/tiles3d/traversal.py +108 -0
- OpenGLContext/loaders/tiles3d/vegetation.py +361 -0
- OpenGLContext/loaders/vrml97.py +133 -0
- OpenGLContext/looptrace.py +307 -0
- OpenGLContext/move/__init__.py +0 -0
- OpenGLContext/move/bindingstore.py +232 -0
- OpenGLContext/move/direct.py +156 -0
- OpenGLContext/move/dragwatcher.py +42 -0
- OpenGLContext/move/examinemanager.py +100 -0
- OpenGLContext/move/followcam.py +121 -0
- OpenGLContext/move/modes.py +522 -0
- OpenGLContext/move/movementmanager.py +74 -0
- OpenGLContext/move/navigation.py +137 -0
- OpenGLContext/move/physicsplatform.py +203 -0
- OpenGLContext/move/physicswalk.py +469 -0
- OpenGLContext/move/smooth.py +68 -0
- OpenGLContext/move/terrainwalk.py +451 -0
- OpenGLContext/move/trackball.py +92 -0
- OpenGLContext/move/viewplatform.py +283 -0
- OpenGLContext/move/viewplatformmixin.py +335 -0
- OpenGLContext/nav/__init__.py +13 -0
- OpenGLContext/nav/navmesh.py +476 -0
- OpenGLContext/passes/__init__.py +0 -0
- OpenGLContext/passes/_flat.py +1317 -0
- OpenGLContext/passes/asyncpick.py +216 -0
- OpenGLContext/passes/bloom.py +257 -0
- OpenGLContext/passes/flatcompat.py +246 -0
- OpenGLContext/passes/flatcore.py +198 -0
- OpenGLContext/passes/flateffects.py +372 -0
- OpenGLContext/passes/ibl.py +711 -0
- OpenGLContext/passes/instancing.py +775 -0
- OpenGLContext/passes/pbrpass.py +829 -0
- OpenGLContext/passes/renderpass.py +79 -0
- OpenGLContext/passes/renderstats.py +55 -0
- OpenGLContext/passes/rendervisitor.py +60 -0
- OpenGLContext/passes/selection.py +450 -0
- OpenGLContext/passes/selectionbuffers.py +442 -0
- OpenGLContext/passes/shaderpass.py +1018 -0
- OpenGLContext/passes/shaderpass_shadow.py +197 -0
- OpenGLContext/passes/shadersource.py +121 -0
- OpenGLContext/passes/shadowcaps.py +182 -0
- OpenGLContext/passes/shadowmap.py +330 -0
- OpenGLContext/passes/shadowmath.py +290 -0
- OpenGLContext/passes/shadowmixin.py +906 -0
- OpenGLContext/passes/shadowpool.py +186 -0
- OpenGLContext/passes/transmission.py +124 -0
- OpenGLContext/physics/__init__.py +16 -0
- OpenGLContext/physics/debugdraw.py +370 -0
- OpenGLContext/physics/demo.py +157 -0
- OpenGLContext/physics/gltf_world.py +185 -0
- OpenGLContext/physics/manager.py +84 -0
- OpenGLContext/physics/threaded.py +93 -0
- OpenGLContext/plugins.py +61 -0
- OpenGLContext/pygamecontext.py +178 -0
- OpenGLContext/pygameinteractivecontext.py +36 -0
- OpenGLContext/pygametestingcontext.py +10 -0
- OpenGLContext/pygamevrmlcontext.py +13 -0
- OpenGLContext/quaternion.py +190 -0
- OpenGLContext/renderoptions.py +280 -0
- OpenGLContext/resources/__init__.py +36 -0
- OpenGLContext/resources/available_contexts.txt +4 -0
- OpenGLContext/resources/available_contexts_txt.py +9 -0
- OpenGLContext/resources/context_icon_png.py +46 -0
- OpenGLContext/resources/context_icon_small_png.py +21 -0
- OpenGLContext/resources/disk_icon_hi_png.py +14 -0
- OpenGLContext/resources/disk_icon_png.py +15 -0
- OpenGLContext/resources/environment/pimbackground_BK.jpg +0 -0
- OpenGLContext/resources/environment/pimbackground_DN.jpg +0 -0
- OpenGLContext/resources/environment/pimbackground_FR.jpg +0 -0
- OpenGLContext/resources/environment/pimbackground_LF.jpg +0 -0
- OpenGLContext/resources/environment/pimbackground_RT.jpg +0 -0
- OpenGLContext/resources/environment/pimbackground_UP.jpg +0 -0
- OpenGLContext/resources/environment/studio_BK.jpg +0 -0
- OpenGLContext/resources/environment/studio_DN.jpg +0 -0
- OpenGLContext/resources/environment/studio_FR.jpg +0 -0
- OpenGLContext/resources/environment/studio_LF.jpg +0 -0
- OpenGLContext/resources/environment/studio_RT.jpg +0 -0
- OpenGLContext/resources/environment/studio_UP.jpg +0 -0
- OpenGLContext/resources/environment/studiobright_BK.jpg +0 -0
- OpenGLContext/resources/environment/studiobright_DN.jpg +0 -0
- OpenGLContext/resources/environment/studiobright_FR.jpg +0 -0
- OpenGLContext/resources/environment/studiobright_LF.jpg +0 -0
- OpenGLContext/resources/environment/studiobright_RT.jpg +0 -0
- OpenGLContext/resources/environment/studiobright_UP.jpg +0 -0
- OpenGLContext/resources/green_arrow_right_png.py +47 -0
- OpenGLContext/resources/legacy_lighting.vert.txt +37 -0
- OpenGLContext/resources/legacy_lighting_vert_txt.py +18 -0
- OpenGLContext/resources/lights.vert.txt +107 -0
- OpenGLContext/resources/lights_vert_txt.py +23 -0
- OpenGLContext/resources/phongprecalc_vert.py +30 -0
- OpenGLContext/resources/phongweights_frag.py +47 -0
- OpenGLContext/resources/pygame_icon_png.py +47 -0
- OpenGLContext/resources/red_arrow_right_png.py +50 -0
- OpenGLContext/resources/simpleshader.frag.txt +3 -0
- OpenGLContext/resources/simpleshader.vert.txt +3 -0
- OpenGLContext/resources/simpleshader_frag_txt.py +9 -0
- OpenGLContext/resources/simpleshader_vert_txt.py +10 -0
- OpenGLContext/scenegraph/__init__.py +19 -0
- OpenGLContext/scenegraph/appearance.py +107 -0
- OpenGLContext/scenegraph/arraygeometry.py +238 -0
- OpenGLContext/scenegraph/audio.py +632 -0
- OpenGLContext/scenegraph/background.py +108 -0
- OpenGLContext/scenegraph/basenodes.py +50 -0
- OpenGLContext/scenegraph/billboard.py +14 -0
- OpenGLContext/scenegraph/boundingvolume.py +494 -0
- OpenGLContext/scenegraph/box.py +180 -0
- OpenGLContext/scenegraph/collision.py +14 -0
- OpenGLContext/scenegraph/coordinate.py +20 -0
- OpenGLContext/scenegraph/coordinatebounded.py +18 -0
- OpenGLContext/scenegraph/cubebackground.py +207 -0
- OpenGLContext/scenegraph/extrusions.py +275 -0
- OpenGLContext/scenegraph/fog.py +123 -0
- OpenGLContext/scenegraph/gear.py +323 -0
- OpenGLContext/scenegraph/group.py +20 -0
- OpenGLContext/scenegraph/grouping.py +140 -0
- OpenGLContext/scenegraph/hdrbackground.py +384 -0
- OpenGLContext/scenegraph/ifscompiler.py +633 -0
- OpenGLContext/scenegraph/imagetexture.py +365 -0
- OpenGLContext/scenegraph/indexedfaceset.py +238 -0
- OpenGLContext/scenegraph/indexedlineset.py +418 -0
- OpenGLContext/scenegraph/indexedpolygons.py +422 -0
- OpenGLContext/scenegraph/inline.py +79 -0
- OpenGLContext/scenegraph/instancedgl.py +187 -0
- OpenGLContext/scenegraph/interpolators.py +122 -0
- OpenGLContext/scenegraph/light.py +213 -0
- OpenGLContext/scenegraph/lod.py +22 -0
- OpenGLContext/scenegraph/material.py +96 -0
- OpenGLContext/scenegraph/material_fields.py +50 -0
- OpenGLContext/scenegraph/mouseover.py +43 -0
- OpenGLContext/scenegraph/nodepath.py +52 -0
- OpenGLContext/scenegraph/nurbs.py +637 -0
- OpenGLContext/scenegraph/nurbssampling.py +101 -0
- OpenGLContext/scenegraph/nurbstess.py +316 -0
- OpenGLContext/scenegraph/nurbstrim.py +54 -0
- OpenGLContext/scenegraph/particles.py +786 -0
- OpenGLContext/scenegraph/pbrmaterial.py +229 -0
- OpenGLContext/scenegraph/pbrmesh.py +558 -0
- OpenGLContext/scenegraph/physicsbody.py +72 -0
- OpenGLContext/scenegraph/pointset.py +257 -0
- OpenGLContext/scenegraph/polygon.py +166 -0
- OpenGLContext/scenegraph/polygonsort.py +119 -0
- OpenGLContext/scenegraph/polygontessellator.py +213 -0
- OpenGLContext/scenegraph/quadrics.py +494 -0
- OpenGLContext/scenegraph/scenegraph.py +21 -0
- OpenGLContext/scenegraph/shadergeometry.py +403 -0
- OpenGLContext/scenegraph/shaders.py +812 -0
- OpenGLContext/scenegraph/shape.py +243 -0
- OpenGLContext/scenegraph/simplebackground.py +34 -0
- OpenGLContext/scenegraph/spherebackground.py +462 -0
- OpenGLContext/scenegraph/switch.py +75 -0
- OpenGLContext/scenegraph/teapot.py +439 -0
- OpenGLContext/scenegraph/teapot_nurbs.py +291 -0
- OpenGLContext/scenegraph/teapot_nurbs_data.py +384 -0
- OpenGLContext/scenegraph/terrain/__init__.py +12 -0
- OpenGLContext/scenegraph/terrain/heightfield.py +172 -0
- OpenGLContext/scenegraph/terrain/splat.py +224 -0
- OpenGLContext/scenegraph/tessellationlod.py +97 -0
- OpenGLContext/scenegraph/text/__init__.py +1 -0
- OpenGLContext/scenegraph/text/_toolsfont.py +200 -0
- OpenGLContext/scenegraph/text/font.py +452 -0
- OpenGLContext/scenegraph/text/fontprovider.py +193 -0
- OpenGLContext/scenegraph/text/fonts/__init__.py +75 -0
- OpenGLContext/scenegraph/text/fonts/font_atlas_10.py +133 -0
- OpenGLContext/scenegraph/text/fonts/font_atlas_12.py +158 -0
- OpenGLContext/scenegraph/text/fonts/font_atlas_14.py +176 -0
- OpenGLContext/scenegraph/text/fonts/font_atlas_16.py +204 -0
- OpenGLContext/scenegraph/text/fonts/font_atlas_18.py +222 -0
- OpenGLContext/scenegraph/text/fonts/font_atlas_20.py +244 -0
- OpenGLContext/scenegraph/text/fonts/font_atlas_24.py +294 -0
- OpenGLContext/scenegraph/text/fonts/font_atlas_28.py +339 -0
- OpenGLContext/scenegraph/text/fonts/font_atlas_32.py +390 -0
- OpenGLContext/scenegraph/text/fontstyle3d.py +62 -0
- OpenGLContext/scenegraph/text/glutfont.py +186 -0
- OpenGLContext/scenegraph/text/pygamefont.py +213 -0
- OpenGLContext/scenegraph/text/shaderfont.py +340 -0
- OpenGLContext/scenegraph/text/shadertext.py +433 -0
- OpenGLContext/scenegraph/text/text.py +75 -0
- OpenGLContext/scenegraph/text/toolsfont.py +819 -0
- OpenGLContext/scenegraph/text/ttfregistry.py +135 -0
- OpenGLContext/scenegraph/text/wglfont.py +228 -0
- OpenGLContext/scenegraph/text/wglfontprovider.py +37 -0
- OpenGLContext/scenegraph/text/wxfont.py +314 -0
- OpenGLContext/scenegraph/texturetransform.py +66 -0
- OpenGLContext/scenegraph/tilesterrain.py +95 -0
- OpenGLContext/scenegraph/timesensor.py +51 -0
- OpenGLContext/scenegraph/transform.py +180 -0
- OpenGLContext/scenegraph/vegetation/__init__.py +20 -0
- OpenGLContext/scenegraph/vegetation/base.py +96 -0
- OpenGLContext/scenegraph/vegetation/billboards.py +145 -0
- OpenGLContext/scenegraph/vegetation/clumps.py +260 -0
- OpenGLContext/scenegraph/vegetation/grid.py +112 -0
- OpenGLContext/scenegraph/vegetation/nearmesh.py +185 -0
- OpenGLContext/scenegraph/vertex.py +54 -0
- OpenGLContext/scenegraph/viewpoint.py +42 -0
- OpenGLContext/scenegraph/winding.py +49 -0
- OpenGLContext/shaders/_brdf_inc.glsl +124 -0
- OpenGLContext/shaders/_common_inc.glsl +24 -0
- OpenGLContext/shaders/_cubemap_inc.glsl +26 -0
- OpenGLContext/shaders/_lights_inc.glsl +32 -0
- OpenGLContext/shaders/_objectid_inc.glsl +14 -0
- OpenGLContext/shaders/_shadow_inc.glsl +254 -0
- OpenGLContext/shaders/_vrml97_lighting_inc.glsl +78 -0
- OpenGLContext/shaders/hdr_background.frag +32 -0
- OpenGLContext/shaders/hdr_background.vert +15 -0
- OpenGLContext/shaders/ibl_brdf.frag +78 -0
- OpenGLContext/shaders/ibl_env.frag +53 -0
- OpenGLContext/shaders/ibl_equirect.frag +19 -0
- OpenGLContext/shaders/ibl_fullscreen.vert +11 -0
- OpenGLContext/shaders/ibl_irradiance.frag +40 -0
- OpenGLContext/shaders/ibl_prefilter.frag +56 -0
- OpenGLContext/shaders/particle.frag +42 -0
- OpenGLContext/shaders/particle.vert +52 -0
- OpenGLContext/shaders/pbr.frag +906 -0
- OpenGLContext/shaders/pbr.vert +70 -0
- OpenGLContext/shaders/shadow_depth.frag +4 -0
- OpenGLContext/shaders/shadow_depth.vert +18 -0
- OpenGLContext/shaders/terrain_splat.frag +80 -0
- OpenGLContext/shaders/terrain_splat.vert +16 -0
- OpenGLContext/shaders/veg_billboard.frag +35 -0
- OpenGLContext/shaders/veg_billboard.vert +17 -0
- OpenGLContext/shaders/veg_clump.frag +27 -0
- OpenGLContext/shaders/veg_mesh.frag +26 -0
- OpenGLContext/shaders/veg_mesh.vert +17 -0
- OpenGLContext/shaders/vrml97_background.frag +17 -0
- OpenGLContext/shaders/vrml97_background.vert +24 -0
- OpenGLContext/shaders/vrml97_lighting.frag +111 -0
- OpenGLContext/shaders/vrml97_lighting.vert +50 -0
- OpenGLContext/shaders/vrml97_line.frag +23 -0
- OpenGLContext/shaders/vrml97_line.vert +24 -0
- OpenGLContext/shaders/vrml97_point.frag +36 -0
- OpenGLContext/shaders/vrml97_point.vert +21 -0
- OpenGLContext/shaders/vrml97_unlit.frag +59 -0
- OpenGLContext/shaders/vrml97_unlit.vert +18 -0
- OpenGLContext/shaders/vrml97_vertex_color.frag +85 -0
- OpenGLContext/shaders/vrml97_vertex_color.vert +34 -0
- OpenGLContext/stalltrace.py +637 -0
- OpenGLContext/testing/__init__.py +67 -0
- OpenGLContext/testing/display.py +30 -0
- OpenGLContext/testing/event_injector.py +564 -0
- OpenGLContext/testing/framebuffer_comparison.py +842 -0
- OpenGLContext/testing/gl_env.py +39 -0
- OpenGLContext/testing/paths.py +23 -0
- OpenGLContext/testing/process_exit.py +30 -0
- OpenGLContext/testing/report_generator.py +594 -0
- OpenGLContext/testing/subprocess_runner.py +411 -0
- OpenGLContext/testingcontext.py +36 -0
- OpenGLContext/tests/test_atlas.py +62 -0
- OpenGLContext/tests/test_configs.py +27 -0
- OpenGLContext/tests/test_polygonsort.py +45 -0
- OpenGLContext/tests/test_utilities.py +84 -0
- OpenGLContext/tests/test_viewplatform.py +39 -0
- OpenGLContext/texture.py +449 -0
- OpenGLContext/texturecache.py +37 -0
- OpenGLContext/triangleutilities.py +89 -0
- OpenGLContext/ui/__init__.py +39 -0
- OpenGLContext/ui/bindings.py +243 -0
- OpenGLContext/ui/console.py +339 -0
- OpenGLContext/ui/debugoverlay.py +519 -0
- OpenGLContext/ui/dialogs.py +124 -0
- OpenGLContext/ui/draw.py +617 -0
- OpenGLContext/ui/gallery.py +425 -0
- OpenGLContext/ui/generate.py +195 -0
- OpenGLContext/ui/geometry.py +88 -0
- OpenGLContext/ui/hudwidgets.py +1053 -0
- OpenGLContext/ui/layout.py +263 -0
- OpenGLContext/ui/metrics.py +218 -0
- OpenGLContext/ui/overlay.py +412 -0
- OpenGLContext/ui/panel.py +459 -0
- OpenGLContext/ui/pictures.py +261 -0
- OpenGLContext/ui/screen.py +177 -0
- OpenGLContext/ui/scroll.py +297 -0
- OpenGLContext/ui/session.py +329 -0
- OpenGLContext/ui/settings.py +273 -0
- OpenGLContext/ui/skin.py +330 -0
- OpenGLContext/ui/widgets.py +1199 -0
- OpenGLContext/userpaths.py +48 -0
- OpenGLContext/utilities.py +94 -0
- OpenGLContext/vectorutilities.py +101 -0
- OpenGLContext/viewer/__init__.py +60 -0
- OpenGLContext/viewer/adapters/__init__.py +150 -0
- OpenGLContext/viewer/adapters/base.py +157 -0
- OpenGLContext/viewer/adapters/gltf.py +31 -0
- OpenGLContext/viewer/adapters/obj.py +20 -0
- OpenGLContext/viewer/adapters/scenegraph.py +71 -0
- OpenGLContext/viewer/adapters/tiles.py +296 -0
- OpenGLContext/viewer/adapters/vrml.py +25 -0
- OpenGLContext/viewer/asyncscene.py +117 -0
- OpenGLContext/viewer/caption.py +126 -0
- OpenGLContext/viewer/capture.py +77 -0
- OpenGLContext/viewer/debug.py +78 -0
- OpenGLContext/viewer/environment.py +131 -0
- OpenGLContext/viewer/framing.py +111 -0
- OpenGLContext/viewer/library.py +235 -0
- OpenGLContext/viewer/menu.py +218 -0
- OpenGLContext/viewer/options.py +150 -0
- OpenGLContext/viewer/overlay.py +63 -0
- OpenGLContext/viewer/sceneviewer.py +1056 -0
- OpenGLContext/viewer/screens.py +172 -0
- OpenGLContext/viewer/source.py +56 -0
- OpenGLContext/visitor.py +68 -0
- OpenGLContext/vrmlcontext.py +72 -0
- OpenGLContext/wxcontext.py +424 -0
- OpenGLContext/wxinteractivecontext.py +25 -0
- OpenGLContext/wxtestingcontext.py +9 -0
- OpenGLContext/wxvrmlcontext.py +13 -0
- openglcontext-3.0.0a1.dist-info/METADATA +650 -0
- openglcontext-3.0.0a1.dist-info/RECORD +417 -0
- openglcontext-3.0.0a1.dist-info/WHEEL +5 -0
- openglcontext-3.0.0a1.dist-info/entry_points.txt +11 -0
- openglcontext-3.0.0a1.dist-info/licenses/license.txt +89 -0
- openglcontext-3.0.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
"""The developer overlay: everything a player should never see, in one place.
|
|
2
|
+
|
|
3
|
+
**One panel, fed by registered providers.** A provider is a callable that
|
|
4
|
+
returns name/value pairs and knows nothing about drawing; a subsystem appears in
|
|
5
|
+
the overlay by registering one, not by anybody editing this module. That is the
|
|
6
|
+
whole reason this is engine machinery rather than a game's own screen: the
|
|
7
|
+
renderer, the physics world, the navigation mesh and the application each own
|
|
8
|
+
their numbers, and each hands them over the same way::
|
|
9
|
+
|
|
10
|
+
context.debugOverlay.register('Map', lambda: [
|
|
11
|
+
('name', loaded.name), ('family', loaded.family),
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
Values are formatted here, so a provider returns whatever it has -- a float, a
|
|
15
|
+
flag, a vector, ``None`` -- and never a pre-rendered string.
|
|
16
|
+
|
|
17
|
+
**It is a HUD layer, not a panel.** It takes no input, blocks nothing and is
|
|
18
|
+
drawn under any screen that is open, so it can be left up while playing. A
|
|
19
|
+
provider that raises turns into an ``error`` row rather than taking the frame
|
|
20
|
+
down with it: this is diagnostic equipment, and a diagnostic that breaks the
|
|
21
|
+
thing it is measuring is worse than none.
|
|
22
|
+
|
|
23
|
+
This is where the frame rate is drawn.
|
|
24
|
+
:class:`~OpenGLContext.framecounter.FrameCounter` measures it and does not draw
|
|
25
|
+
it: doing that would mean fixed-function calls, which mean nothing in a
|
|
26
|
+
core-profile context. ``OPENGLCONTEXT_DISABLE_FPS_DISPLAY`` starts the overlay
|
|
27
|
+
hidden, so a captured frame has nothing over it.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
import os
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
from typing import (
|
|
36
|
+
Any, Callable, Dict, Iterable, List, NamedTuple, Optional, Tuple,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
from vrml import field
|
|
40
|
+
|
|
41
|
+
from OpenGLContext.ui.geometry import Rect
|
|
42
|
+
from OpenGLContext.ui.hudwidgets import HUDLayer, HUDWidget
|
|
43
|
+
from OpenGLContext.ui.metrics import FontMetrics
|
|
44
|
+
|
|
45
|
+
log = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
'DebugOverlay', 'DebugPanel', 'DebugSection', 'Fixed', 'LaidOutRow',
|
|
49
|
+
'audio_provider', 'format_value', 'frame_provider', 'loop_provider',
|
|
50
|
+
'render_provider', 'platform_provider', 'physics_provider',
|
|
51
|
+
'simulation_provider', 'install_default_providers',
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
#: What a provider returns: pairs of name and value, or a mapping of the same.
|
|
55
|
+
Rows = Iterable[Tuple[str, Any]]
|
|
56
|
+
Provider = Callable[[], Any]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DebugSection(NamedTuple):
|
|
60
|
+
"""One labelled group of rows, as a provider last answered it."""
|
|
61
|
+
|
|
62
|
+
title: str
|
|
63
|
+
rows: List[Tuple[str, str]]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class LaidOutRow(NamedTuple):
|
|
67
|
+
"""One line of the overlay, placed."""
|
|
68
|
+
|
|
69
|
+
rect: Rect
|
|
70
|
+
label: str
|
|
71
|
+
value: str
|
|
72
|
+
heading: bool
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class Fixed:
|
|
77
|
+
"""A number that keeps its decimals however round it happens to be.
|
|
78
|
+
|
|
79
|
+
For the rows that change every frame. :func:`_number` drops trailing
|
|
80
|
+
zeros, which is right for a position — ``512.00`` is three characters of
|
|
81
|
+
nothing on a crowded line — and wrong for a frame rate: 60, 59.97 and 60.1
|
|
82
|
+
are three different widths in three consecutive frames, and a number that
|
|
83
|
+
changes width sixty times a second is a number nobody can read.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
value: float
|
|
87
|
+
decimals: int = 2
|
|
88
|
+
|
|
89
|
+
def __str__(self) -> str:
|
|
90
|
+
return '%.*f' % (self.decimals, self.value)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def format_value(value: Any) -> str:
|
|
94
|
+
"""A provider's value as the one line that will be drawn for it.
|
|
95
|
+
|
|
96
|
+
Formatting here rather than in the provider is what keeps the providers
|
|
97
|
+
trivial -- a provider hands over the number it has -- and what keeps the
|
|
98
|
+
overlay's columns reading consistently when a dozen unrelated subsystems
|
|
99
|
+
write into them.
|
|
100
|
+
"""
|
|
101
|
+
if value is None:
|
|
102
|
+
return '-'
|
|
103
|
+
if isinstance(value, Fixed):
|
|
104
|
+
return str(value)
|
|
105
|
+
if isinstance(value, bool):
|
|
106
|
+
return 'yes' if value else 'no'
|
|
107
|
+
if isinstance(value, float):
|
|
108
|
+
return _number(value)
|
|
109
|
+
if isinstance(value, int):
|
|
110
|
+
return str(value)
|
|
111
|
+
if isinstance(value, str):
|
|
112
|
+
return value
|
|
113
|
+
if isinstance(value, (tuple, list)) or hasattr(value, '__len__'):
|
|
114
|
+
try:
|
|
115
|
+
return ', '.join(_number(float(component)) for component in value)
|
|
116
|
+
except (TypeError, ValueError):
|
|
117
|
+
pass
|
|
118
|
+
return str(value)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _number(value: float) -> str:
|
|
122
|
+
"""A float with two decimals, and none at all when it is whole.
|
|
123
|
+
|
|
124
|
+
Trailing zeros on a frame time are noise, and a position that reads
|
|
125
|
+
``512.00`` is three characters of nothing on a line where width is scarce.
|
|
126
|
+
"""
|
|
127
|
+
text = '%.2f' % (value,)
|
|
128
|
+
if text.endswith('.00'):
|
|
129
|
+
return text[:-3]
|
|
130
|
+
return text.rstrip('0').rstrip('.') if '.' in text else text
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class DebugPanel(HUDWidget):
|
|
134
|
+
"""The plate the sections are drawn on: two columns, one line per row.
|
|
135
|
+
|
|
136
|
+
Its size follows its contents, which change every frame, so it is measured
|
|
137
|
+
and placed each time the overlay is drawn rather than once at layout.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
PROTO = 'DebugPanel'
|
|
141
|
+
anchor = field.newField('anchor', 'SFString', 1, 'top-left')
|
|
142
|
+
|
|
143
|
+
def __init__(self, **named: Any) -> None:
|
|
144
|
+
super(DebugPanel, self).__init__(**named)
|
|
145
|
+
#: What the providers last said. Not a field: it is this frame's
|
|
146
|
+
#: reading, not authored data.
|
|
147
|
+
self.sections: List[DebugSection] = []
|
|
148
|
+
|
|
149
|
+
# -- measurement ------------------------------------------------------
|
|
150
|
+
def columnWidths(self, metrics: FontMetrics) -> Tuple[int, int]:
|
|
151
|
+
"""Pixels for the name column and for the value column.
|
|
152
|
+
|
|
153
|
+
One pair for the whole overlay rather than per section, so the values
|
|
154
|
+
of every subsystem line up in one column and the eye runs down it.
|
|
155
|
+
"""
|
|
156
|
+
labels = 0
|
|
157
|
+
values = 0
|
|
158
|
+
for section in self.sections:
|
|
159
|
+
labels = max(labels, metrics.text_width(section.title))
|
|
160
|
+
for name, value in section.rows:
|
|
161
|
+
labels = max(labels, metrics.text_width(name))
|
|
162
|
+
values = max(values, metrics.text_width(value))
|
|
163
|
+
return (labels, values)
|
|
164
|
+
|
|
165
|
+
def lineCount(self) -> int:
|
|
166
|
+
"""Headings and rows together -- how many lines will be drawn."""
|
|
167
|
+
return sum(1 + len(section.rows) for section in self.sections)
|
|
168
|
+
|
|
169
|
+
def content_size(self, metrics: FontMetrics,
|
|
170
|
+
available: Optional[int] = None) -> Tuple[int, int]:
|
|
171
|
+
lines = self.lineCount()
|
|
172
|
+
if not lines:
|
|
173
|
+
return (0, 0)
|
|
174
|
+
skin = self.activeSkin()
|
|
175
|
+
padding = int(skin.hudPadding)
|
|
176
|
+
labels, values = self.columnWidths(metrics)
|
|
177
|
+
gap = int(skin.hudSpacing) if values else 0
|
|
178
|
+
return (labels + gap + values + padding * 2,
|
|
179
|
+
lines * metrics.line_height + padding * 2)
|
|
180
|
+
|
|
181
|
+
def laidOutRows(self, metrics: FontMetrics) -> List[LaidOutRow]:
|
|
182
|
+
"""Every heading and row, placed inside this plate, top down.
|
|
183
|
+
|
|
184
|
+
The whole of the overlay's layout, and pure arithmetic: hand it a font
|
|
185
|
+
and a set of sections and the rectangles come back, with no window
|
|
186
|
+
anywhere in sight.
|
|
187
|
+
"""
|
|
188
|
+
if not self.sections or self.rect.empty:
|
|
189
|
+
return []
|
|
190
|
+
padding = int(self.activeSkin().hudPadding)
|
|
191
|
+
inner = self.rect.inset(padding)
|
|
192
|
+
line_height = metrics.line_height
|
|
193
|
+
cursor = inner.top - metrics.char_height
|
|
194
|
+
laid: List[LaidOutRow] = []
|
|
195
|
+
for section in self.sections:
|
|
196
|
+
laid.append(LaidOutRow(
|
|
197
|
+
Rect(inner.x, cursor, inner.width, metrics.char_height),
|
|
198
|
+
section.title, '', True))
|
|
199
|
+
cursor -= line_height
|
|
200
|
+
for name, value in section.rows:
|
|
201
|
+
laid.append(LaidOutRow(
|
|
202
|
+
Rect(inner.x, cursor, inner.width, metrics.char_height),
|
|
203
|
+
name, value, False))
|
|
204
|
+
cursor -= line_height
|
|
205
|
+
return laid
|
|
206
|
+
|
|
207
|
+
# -- drawing ----------------------------------------------------------
|
|
208
|
+
def paint(self, renderer: Any) -> None:
|
|
209
|
+
if not self.sections:
|
|
210
|
+
return
|
|
211
|
+
metrics = renderer.metrics
|
|
212
|
+
skin = renderer.skin
|
|
213
|
+
renderer.rect(self.rect, self.tinted(skin.debugFill))
|
|
214
|
+
for row in self.laidOutRows(metrics):
|
|
215
|
+
if row.heading:
|
|
216
|
+
renderer.textIn(row.rect, row.label, skin.debugTitle)
|
|
217
|
+
continue
|
|
218
|
+
renderer.textIn(row.rect, row.label, skin.debugLabel)
|
|
219
|
+
renderer.textIn(row.rect, row.value, skin.debugValue,
|
|
220
|
+
align='right')
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class DebugOverlay(HUDLayer):
|
|
224
|
+
"""The overlay itself: a registry of providers and the plate they fill.
|
|
225
|
+
|
|
226
|
+
Held by a context (``context.debugOverlay``) and drawn with the rest of its
|
|
227
|
+
HUD. Registration is by title, so registering the same title twice
|
|
228
|
+
replaces the provider rather than growing a second section with the same
|
|
229
|
+
heading -- which is what a reloaded subsystem would otherwise do.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
PROTO = 'DebugOverlay'
|
|
233
|
+
margin = field.newField('margin', 'SFFloat', 1, 12.0)
|
|
234
|
+
|
|
235
|
+
def __init__(self, **named: Any) -> None:
|
|
236
|
+
super(DebugOverlay, self).__init__(**named)
|
|
237
|
+
self.panel = DebugPanel()
|
|
238
|
+
self.children = [self.panel]
|
|
239
|
+
#: Title to (order, provider), in registration order within an order.
|
|
240
|
+
self._providers: Dict[str, Tuple[int, Provider]] = {}
|
|
241
|
+
self._sequence = 0
|
|
242
|
+
|
|
243
|
+
# -- the registry -----------------------------------------------------
|
|
244
|
+
def register(self, title: str, provider: Provider,
|
|
245
|
+
order: int = 50) -> Provider:
|
|
246
|
+
"""Add (or replace) a section, and hand the provider back.
|
|
247
|
+
|
|
248
|
+
``order`` sorts the sections: low numbers first, and providers given
|
|
249
|
+
the same order keep the order they registered in, so a subsystem that
|
|
250
|
+
does not care where it lands simply lands after whatever was there.
|
|
251
|
+
"""
|
|
252
|
+
self._sequence += 1
|
|
253
|
+
self._providers[title] = (order * 1000 + self._sequence, provider)
|
|
254
|
+
return provider
|
|
255
|
+
|
|
256
|
+
def unregister(self, title: str) -> None:
|
|
257
|
+
"""Take a section away -- a subsystem that has been shut down."""
|
|
258
|
+
self._providers.pop(title, None)
|
|
259
|
+
|
|
260
|
+
def sections(self) -> List[DebugSection]:
|
|
261
|
+
"""Ask every provider, in order, for the rows it has right now.
|
|
262
|
+
|
|
263
|
+
A provider with nothing to say is left out entirely rather than
|
|
264
|
+
appearing as an empty heading: a physics section on a map with no
|
|
265
|
+
physics is noise on a screen that is short of room.
|
|
266
|
+
"""
|
|
267
|
+
found: List[DebugSection] = []
|
|
268
|
+
for title, (_order, provider) in sorted(self._providers.items(),
|
|
269
|
+
key=lambda item: item[1][0]):
|
|
270
|
+
rows = self._rowsFrom(title, provider)
|
|
271
|
+
if rows:
|
|
272
|
+
found.append(DebugSection(title, rows))
|
|
273
|
+
return found
|
|
274
|
+
|
|
275
|
+
@staticmethod
|
|
276
|
+
def _rowsFrom(title: str, provider: Provider) -> List[Tuple[str, str]]:
|
|
277
|
+
"""One provider's rows, formatted, with a failure shown rather than raised."""
|
|
278
|
+
try:
|
|
279
|
+
answer = provider()
|
|
280
|
+
except Exception as error: # noqa: BLE001 - diagnostic
|
|
281
|
+
log.debug('debug overlay provider %s failed', title, exc_info=True)
|
|
282
|
+
return [('error', str(error) or type(error).__name__)]
|
|
283
|
+
if hasattr(answer, 'items'):
|
|
284
|
+
answer = list(answer.items())
|
|
285
|
+
return [(str(name), format_value(value)) for name, value in answer]
|
|
286
|
+
|
|
287
|
+
# -- the frame --------------------------------------------------------
|
|
288
|
+
def refresh(self) -> None:
|
|
289
|
+
"""Read every provider into the plate, ready to be measured and drawn."""
|
|
290
|
+
self.panel.sections = self.sections()
|
|
291
|
+
|
|
292
|
+
def tick(self, now: Optional[float] = None) -> None:
|
|
293
|
+
"""Refresh from the providers; the numbers are this frame's, not last's."""
|
|
294
|
+
self.refresh()
|
|
295
|
+
super(DebugOverlay, self).tick(now)
|
|
296
|
+
|
|
297
|
+
# -- visibility -------------------------------------------------------
|
|
298
|
+
@staticmethod
|
|
299
|
+
def startsVisible() -> bool:
|
|
300
|
+
"""Whether a new overlay should be on screen straight away.
|
|
301
|
+
|
|
302
|
+
``OPENGLCONTEXT_DISABLE_FPS_DISPLAY`` is what the capture harness sets
|
|
303
|
+
to get a clean frame, and it has always meant "no numbers over my
|
|
304
|
+
screenshot"; here it means the overlay starts hidden. A key still
|
|
305
|
+
brings it up, because a capture run is not the only thing that sets it.
|
|
306
|
+
"""
|
|
307
|
+
return not os.environ.get('OPENGLCONTEXT_DISABLE_FPS_DISPLAY')
|
|
308
|
+
|
|
309
|
+
def toggle(self) -> bool:
|
|
310
|
+
"""Show it if it is hidden, hide it if it is up; True if now shown."""
|
|
311
|
+
self.visible = not self.visible
|
|
312
|
+
return bool(self.visible)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# -- the providers shipped with it ----------------------------------------
|
|
316
|
+
# Each takes what it needs and returns a callable, so a context registers one
|
|
317
|
+
# by naming it rather than by writing a lambda that closes over itself.
|
|
318
|
+
|
|
319
|
+
def frame_provider(context: Any) -> Provider:
|
|
320
|
+
"""Frame rate, frame time and the window this is all being drawn in.
|
|
321
|
+
|
|
322
|
+
The rate is the **windowed median**, not the lifetime average: a cumulative
|
|
323
|
+
average bakes in the first frame's shader compile and every synchronous
|
|
324
|
+
load after it, and then reads permanently below what the renderer is
|
|
325
|
+
actually doing.
|
|
326
|
+
"""
|
|
327
|
+
def rows() -> Rows:
|
|
328
|
+
counter = getattr(context, 'frameCounter', None)
|
|
329
|
+
width, height = context.getViewPort()
|
|
330
|
+
if counter is None:
|
|
331
|
+
return [('fps', 0), ('viewport', '%dx%d' % (width, height))]
|
|
332
|
+
count, _average, last = counter.summary()
|
|
333
|
+
return [
|
|
334
|
+
# Fixed decimals: these two change every frame, and a value that
|
|
335
|
+
# changes *width* every frame twitches on the screen.
|
|
336
|
+
('fps', Fixed(counter.recentFps())),
|
|
337
|
+
('frame ms', Fixed(last * 1000.0)),
|
|
338
|
+
('frames', count),
|
|
339
|
+
('viewport', '%dx%d' % (width, height)),
|
|
340
|
+
]
|
|
341
|
+
return rows
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def loop_provider(context: Any) -> Provider:
|
|
345
|
+
"""What the whole main loop costs, and which part of it costs that.
|
|
346
|
+
|
|
347
|
+
The section to read when the Frame section looks healthy and the game does
|
|
348
|
+
not. ``fps`` above measures the inside of ``OnDraw`` and only for frames
|
|
349
|
+
that changed something; ``loop fps`` here measures iterations of wall
|
|
350
|
+
clock, which is the rate a player's hands feel. The two disagreeing is the
|
|
351
|
+
diagnosis: the renderer is keeping up and something outside it is not.
|
|
352
|
+
|
|
353
|
+
``loop ms`` beside ``worst ms`` says the same thing about a single hitch
|
|
354
|
+
that a median cannot -- a healthy median next to a worst ten times larger
|
|
355
|
+
is a loop that stutters -- and the phase rows below say where the worst one
|
|
356
|
+
went. Phases divide the iteration rather than overlapping it, so they add
|
|
357
|
+
up to ``loop ms`` and the largest is the culprit by construction.
|
|
358
|
+
|
|
359
|
+
Left out entirely by a backend that runs its own loop and never opens an
|
|
360
|
+
iteration: rows of zeroes would read as a loop doing nothing at all, which
|
|
361
|
+
is a worse answer than no rows.
|
|
362
|
+
"""
|
|
363
|
+
def rows() -> Rows:
|
|
364
|
+
trace = getattr(context, 'loopTrace', None)
|
|
365
|
+
if trace is None:
|
|
366
|
+
return []
|
|
367
|
+
summary = trace.summary()
|
|
368
|
+
if not summary['iterations']:
|
|
369
|
+
return []
|
|
370
|
+
found: List[Tuple[str, Any]] = [
|
|
371
|
+
('loop fps', Fixed(summary['rate'])),
|
|
372
|
+
('loop ms', Fixed(summary['median_ms'])),
|
|
373
|
+
('worst ms', Fixed(summary['worst_ms'])),
|
|
374
|
+
('stalls', summary['stalls']),
|
|
375
|
+
]
|
|
376
|
+
culprit = trace.worst_phase()
|
|
377
|
+
if culprit is not None:
|
|
378
|
+
found.append(('last stall', '%s %.0fms' % culprit))
|
|
379
|
+
# Worst first, which `phases_ms` already orders them by: a panel that
|
|
380
|
+
# reads top-down as most-to-least expensive answers "where did it go"
|
|
381
|
+
# without the reader comparing every row against every other.
|
|
382
|
+
found.extend((name, Fixed(milliseconds))
|
|
383
|
+
for name, milliseconds in trace.phases_ms().items())
|
|
384
|
+
return found
|
|
385
|
+
return rows
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def render_provider(context: Any) -> Provider:
|
|
389
|
+
"""Which renderer features are on, and what the last frame cost in draws."""
|
|
390
|
+
def rows() -> Rows:
|
|
391
|
+
definition = getattr(context, 'contextDefinition', None)
|
|
392
|
+
found: List[Tuple[str, Any]] = [
|
|
393
|
+
('profile', 'core' if getattr(context, 'coreProfile', False)
|
|
394
|
+
else 'compatibility'),
|
|
395
|
+
]
|
|
396
|
+
for name in ('shadows', 'ibl', 'bloom', 'transmission', 'instancing',
|
|
397
|
+
'vsync'):
|
|
398
|
+
if definition is not None and hasattr(definition, name):
|
|
399
|
+
found.append((name, bool(getattr(definition, name))))
|
|
400
|
+
stats = getattr(context, 'renderStats', None)
|
|
401
|
+
if stats is not None:
|
|
402
|
+
found.extend([
|
|
403
|
+
('shapes', stats.shapes),
|
|
404
|
+
('draws', stats.draws),
|
|
405
|
+
('instanced', '%d in %d groups' % (stats.instances,
|
|
406
|
+
stats.instanceGroups)),
|
|
407
|
+
])
|
|
408
|
+
return found
|
|
409
|
+
return rows
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def platform_provider(context: Any) -> Provider:
|
|
413
|
+
"""Where the camera is, and what the character controller under it is doing.
|
|
414
|
+
|
|
415
|
+
Everything is read defensively: a context may have no view platform yet, a
|
|
416
|
+
platform may be a plain camera with no controller, and neither is a reason
|
|
417
|
+
for the overlay to stop reporting the rest.
|
|
418
|
+
"""
|
|
419
|
+
def rows() -> Rows:
|
|
420
|
+
platform = context.getViewPlatform()
|
|
421
|
+
if platform is None:
|
|
422
|
+
return []
|
|
423
|
+
found: List[Tuple[str, Any]] = [
|
|
424
|
+
('position', tuple(float(value)
|
|
425
|
+
for value in platform.position[:3])),
|
|
426
|
+
]
|
|
427
|
+
controller = getattr(platform, 'controller', None)
|
|
428
|
+
if controller is not None:
|
|
429
|
+
for name, label in (('grounded', 'grounded'),
|
|
430
|
+
('velocity', 'velocity'),
|
|
431
|
+
('mode', 'mode')):
|
|
432
|
+
if hasattr(controller, name):
|
|
433
|
+
found.append((label, getattr(controller, name)))
|
|
434
|
+
return found
|
|
435
|
+
return rows
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def physics_provider(world: Callable[[], Any]) -> Provider:
|
|
439
|
+
"""Body and contact counts from a physics world, fetched when asked for.
|
|
440
|
+
|
|
441
|
+
Takes a callable rather than the world itself because a world is built when
|
|
442
|
+
a level loads and replaced when the next one does; a provider holding the
|
|
443
|
+
first would report on it forever.
|
|
444
|
+
"""
|
|
445
|
+
def rows() -> Rows:
|
|
446
|
+
found_world = world()
|
|
447
|
+
if found_world is None:
|
|
448
|
+
return []
|
|
449
|
+
found: List[Tuple[str, Any]] = []
|
|
450
|
+
for name, label in (('bodies', 'bodies'), ('contacts', 'contacts')):
|
|
451
|
+
collection = getattr(found_world, name, None)
|
|
452
|
+
if collection is not None:
|
|
453
|
+
found.append((label, len(collection)))
|
|
454
|
+
return found
|
|
455
|
+
return rows
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def simulation_provider(simulation: Callable[[], Any]) -> Provider:
|
|
459
|
+
"""Whether a background physics thread is getting the turns it asked for.
|
|
460
|
+
|
|
461
|
+
The companion to :func:`physics_provider`, which reports on the *world*;
|
|
462
|
+
this reports on the thread stepping it. A simulation that falls behind
|
|
463
|
+
resets its schedule rather than running the missed ticks back to back --
|
|
464
|
+
the only alternative is a spiral -- so being starved costs simulated time
|
|
465
|
+
silently, and everything in the world moves slowly. That is
|
|
466
|
+
indistinguishable from wrong gravity, wrong units or a wrong timestep until
|
|
467
|
+
``sim hz`` is on the panel beside the rate that was asked for.
|
|
468
|
+
|
|
469
|
+
Takes a callable for the same reason :func:`physics_provider` does: a
|
|
470
|
+
simulation is built when a level loads and replaced when the next one does.
|
|
471
|
+
"""
|
|
472
|
+
def rows() -> Rows:
|
|
473
|
+
found_sim = simulation()
|
|
474
|
+
if found_sim is None:
|
|
475
|
+
return []
|
|
476
|
+
found: List[Tuple[str, Any]] = [
|
|
477
|
+
('sim hz', Fixed(found_sim.rate())),
|
|
478
|
+
('asked', Fixed(found_sim.sim_hz)),
|
|
479
|
+
('steps', found_sim.steps),
|
|
480
|
+
]
|
|
481
|
+
# Zero is the healthy answer and a healthy row is a row not worth the
|
|
482
|
+
# space; a non-zero one is the whole reason the section exists.
|
|
483
|
+
dropped = found_sim.dropped
|
|
484
|
+
if dropped:
|
|
485
|
+
found.append(('dropped', dropped))
|
|
486
|
+
return found
|
|
487
|
+
return rows
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def audio_provider(context: Any) -> Provider:
|
|
491
|
+
"""What this context's sound is doing, and whether it is doing it at all.
|
|
492
|
+
|
|
493
|
+
The one subsystem whose state cannot be seen, which is exactly why it wants
|
|
494
|
+
a place on the overlay: a sound that is not audible may be off, may have no
|
|
495
|
+
device, may have lost its voice to a louder one, or may simply be too far
|
|
496
|
+
away, and no amount of listening tells those four apart.
|
|
497
|
+
|
|
498
|
+
Reported even when there is no engine, because "audio: idle" -- nothing in
|
|
499
|
+
this scene has asked to make a noise -- is itself the answer to the most
|
|
500
|
+
common question.
|
|
501
|
+
"""
|
|
502
|
+
def rows() -> Rows:
|
|
503
|
+
from OpenGLContext.audio import scene as audioscene
|
|
504
|
+
if getattr(context, 'contextDefinition', None) is None:
|
|
505
|
+
return []
|
|
506
|
+
return list(audioscene.describe(context).items())
|
|
507
|
+
return rows
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def install_default_providers(overlay: DebugOverlay, context: Any) -> None:
|
|
511
|
+
"""Register the sections every context can answer for itself."""
|
|
512
|
+
overlay.register('Frame', frame_provider(context), order=10)
|
|
513
|
+
# Directly under Frame, because the two are read against each other: a
|
|
514
|
+
# healthy `fps` over a poor `loop fps` is the whole diagnosis, and a reader
|
|
515
|
+
# who has to hunt down the panel for the second number will not compare it.
|
|
516
|
+
overlay.register('Loop', loop_provider(context), order=15)
|
|
517
|
+
overlay.register('Render', render_provider(context), order=20)
|
|
518
|
+
overlay.register('View', platform_provider(context), order=30)
|
|
519
|
+
overlay.register('Audio', audio_provider(context), order=35)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Ready-made dialogs: a question, a message, and a wall of text.
|
|
2
|
+
|
|
3
|
+
These are the shapes an application asks for often enough that building them
|
|
4
|
+
out of widgets every time would be busywork. Each returns a
|
|
5
|
+
:class:`~OpenGLContext.ui.panel.Panel` for the caller to push onto a context's
|
|
6
|
+
overlay stack::
|
|
7
|
+
|
|
8
|
+
context.pushOverlay(dialogs.confirm(
|
|
9
|
+
'Download the core textures?',
|
|
10
|
+
detail='About 40MB, cached for next time.',
|
|
11
|
+
on_answer=self.texturesAnswered))
|
|
12
|
+
|
|
13
|
+
The buttons are **real measured rectangles**, hovered and clicked with the
|
|
14
|
+
pointer; the keys are accelerators for anyone who prefers them, not the only
|
|
15
|
+
way in. A question that can only be dismissed by guessing a key is a bad
|
|
16
|
+
question.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from gettext import gettext as _
|
|
22
|
+
from typing import Callable, Optional, Sequence
|
|
23
|
+
|
|
24
|
+
from OpenGLContext.ui.layout import Column, Row
|
|
25
|
+
from OpenGLContext.ui.panel import Panel
|
|
26
|
+
from OpenGLContext.ui.widgets import Button, Label, Spacer, DANGER, PRIMARY
|
|
27
|
+
|
|
28
|
+
__all__ = ['confirm', 'message', 'notice']
|
|
29
|
+
|
|
30
|
+
#: Content width, in characters, that a text dialog aims for. Roughly a
|
|
31
|
+
#: newspaper column: long enough not to hyphenate, short enough to read.
|
|
32
|
+
DIALOG_COLUMNS = 56
|
|
33
|
+
#: Keys that answer a question, unless the caller names others.
|
|
34
|
+
YES_KEYS = ('y',)
|
|
35
|
+
NO_KEYS = ('n',)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def confirm(question: str, detail: str = '',
|
|
39
|
+
on_answer: Optional[Callable[[bool], None]] = None,
|
|
40
|
+
yes: Optional[str] = None, no: Optional[str] = None,
|
|
41
|
+
title: str = '',
|
|
42
|
+
danger: bool = False,
|
|
43
|
+
yes_keys: Sequence[str] = YES_KEYS,
|
|
44
|
+
no_keys: Sequence[str] = NO_KEYS) -> Panel:
|
|
45
|
+
"""A modal yes/no question.
|
|
46
|
+
|
|
47
|
+
``on_answer`` is called once, with True or False; Escape and the panel's
|
|
48
|
+
Cancel both answer False, because a question dismissed without an answer is
|
|
49
|
+
a No in every case this is used for. ``danger`` marks the affirmative as
|
|
50
|
+
the destructive one -- resetting bindings, discarding changes.
|
|
51
|
+
"""
|
|
52
|
+
yes_button = Button(text=yes or _('Yes'), name='yes',
|
|
53
|
+
role=DANGER if danger else PRIMARY,
|
|
54
|
+
accelerator=yes_keys[0] if yes_keys else '')
|
|
55
|
+
no_button = Button(text=no or _('No'), name='no',
|
|
56
|
+
accelerator=no_keys[0] if no_keys else '')
|
|
57
|
+
body = [Label(text=question, wrap=True, name='question')]
|
|
58
|
+
if detail:
|
|
59
|
+
body.append(Label(text=detail, wrap=True, name='detail', top=4))
|
|
60
|
+
body.append(Row(children=[Spacer(), no_button, yes_button], spacing=8,
|
|
61
|
+
top=8, name='buttons'))
|
|
62
|
+
panel = Panel(title=title, scrim=True, modal=True,
|
|
63
|
+
preferredColumns=DIALOG_COLUMNS,
|
|
64
|
+
children=[Column(children=body, spacing=4)])
|
|
65
|
+
answered: list = []
|
|
66
|
+
|
|
67
|
+
def answer(value: bool) -> None:
|
|
68
|
+
if answered:
|
|
69
|
+
return
|
|
70
|
+
answered.append(value)
|
|
71
|
+
panel.close(value)
|
|
72
|
+
if on_answer is not None:
|
|
73
|
+
on_answer(value)
|
|
74
|
+
|
|
75
|
+
yes_button.on_activate = lambda widget: answer(True)
|
|
76
|
+
no_button.on_activate = lambda widget: answer(False)
|
|
77
|
+
# Escape is a No, so a prompt closed any other way still reports one answer.
|
|
78
|
+
panel.on_close = lambda closing: answer(False)
|
|
79
|
+
for extra in yes_keys[1:]:
|
|
80
|
+
panel.accelerators[extra] = lambda dialog: answer(True)
|
|
81
|
+
for extra in no_keys[1:]:
|
|
82
|
+
panel.accelerators[extra] = lambda dialog: answer(False)
|
|
83
|
+
return panel
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def message(text: str, title: str = '', button: Optional[str] = None,
|
|
87
|
+
on_close: Optional[Callable[[Panel], None]] = None) -> Panel:
|
|
88
|
+
"""Something the user has to acknowledge, with one button."""
|
|
89
|
+
ok = Button(text=button or _('OK'), role=PRIMARY, name='ok')
|
|
90
|
+
panel = Panel(title=title, scrim=True, modal=True,
|
|
91
|
+
preferredColumns=DIALOG_COLUMNS,
|
|
92
|
+
children=[Column(children=[
|
|
93
|
+
Label(text=text, wrap=True, name='message'),
|
|
94
|
+
Row(children=[Spacer(), ok], top=8),
|
|
95
|
+
], spacing=4)])
|
|
96
|
+
ok.on_activate = lambda widget: panel.close(True)
|
|
97
|
+
if on_close is not None:
|
|
98
|
+
panel.on_close = on_close
|
|
99
|
+
return panel
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def notice(title: str, text: str, columns: int = 78,
|
|
103
|
+
on_close: Optional[Callable[[Panel], None]] = None) -> Panel:
|
|
104
|
+
"""A wall of text nobody can style away -- a copyright or licence notice.
|
|
105
|
+
|
|
106
|
+
Filling the window and scrolling rather than sizing to the text: these run
|
|
107
|
+
to thousands of words, and a panel that grew to fit one would be taller
|
|
108
|
+
than any screen.
|
|
109
|
+
"""
|
|
110
|
+
from OpenGLContext.ui.scroll import ScrollViewport
|
|
111
|
+
|
|
112
|
+
close = Button(text=_('Close'), role=PRIMARY, name='close')
|
|
113
|
+
body = ScrollViewport(name='body', flex=1, children=[
|
|
114
|
+
Label(text=text, wrap=True, name='text')])
|
|
115
|
+
panel = Panel(title=title, modal=True, scrim=True, fill=True,
|
|
116
|
+
preferredColumns=columns,
|
|
117
|
+
children=[Column(children=[
|
|
118
|
+
body,
|
|
119
|
+
Row(children=[Spacer(), close], top=8),
|
|
120
|
+
], spacing=4)])
|
|
121
|
+
close.on_activate = lambda widget: panel.close(True)
|
|
122
|
+
if on_close is not None:
|
|
123
|
+
panel.on_close = on_close
|
|
124
|
+
return panel
|