diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea5d4f86f..cc71539e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,10 +42,16 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.9 + - name: Install llvmpipe and lavapipe for offscreen canvas + run: | + sudo apt-get update -y -qq + sudo add-apt-repository ppa:oibaf/graphics-drivers -y + sudo apt-get update -y -qq + sudo apt-get install --no-install-recommends -y libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - name: Install dev dependencies run: | python -m pip install --upgrade pip - pip install -U -r docs/docs_requirements.txt + pip install -U -e .[examples,docs] - name: Build docs run: | cd docs @@ -96,7 +102,7 @@ jobs: sudo apt-get update -y -qq sudo add-apt-repository ppa:oibaf/graphics-drivers -y sudo apt-get update -y -qq - sudo apt install --no-install-recommends -y libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers + sudo apt-get install --no-install-recommends -y libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - name: Install dev dependencies run: | python -m pip install --upgrade pip diff --git a/.gitignore b/.gitignore index 90b03461d..62958aea3 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ instance/ # Sphinx documentation docs/_build/ +docs/_gallery # PyBuilder target/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..c742035f4 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,27 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.9" + apt_packages: + - libegl1-mesa + - libgl1-mesa-dri + - libxcb-xfixes0-dev + - mesa-vulkan-drivers + +sphinx: + configuration: docs/conf.py + fail_on_warning: true + +# Optionally declare the Python requirements required to build your docs +python: + install: + - method: pip + path: . + extra_requirements: + - docs + - examples diff --git a/docs/conf.py b/docs/conf.py index 3738da210..b0c760255 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,11 +2,27 @@ import os import sys +from sphinx_gallery.sorting import ExplicitOrder +import wgpu.gui.offscreen ROOT_DIR = os.path.abspath(os.path.join(__file__, "..", "..")) sys.path.insert(0, ROOT_DIR) +# -- Sphix Gallery Hackz ----------------------------------------------------- +# When building the gallery, render offscreen and don't process +# the event loop while parsing the example + + +def _ignore_offscreen_run(): + wgpu.gui.offscreen.run = lambda: None + + +os.environ["WGPU_FORCE_OFFSCREEN"] = "true" +_ignore_offscreen_run() + +# ---------------------------------------------------------------------------- + import pygfx # noqa: E402 @@ -53,6 +69,7 @@ def _check_objects_are_documented(): extensions = [ "sphinx.ext.autodoc", "sphinx.ext.napoleon", + "sphinx_gallery.gen_gallery", # "nbsphinx", ] @@ -64,6 +81,23 @@ def _check_objects_are_documented(): # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] +sphinx_gallery_conf = { + "examples_dirs": "../examples", + "gallery_dirs": "_gallery", + "backreferences_dir": "_gallery/backreferences", + "doc_module": ("pygfx",), + "image_scrapers": ("pygfx",), + "subsection_order": ExplicitOrder( + [ + "../examples/introductory", + "../examples/feature_demo", + "../examples/validation", + "../examples/other", + ] + ), + "remove_config_comments": True, +} + # -- Options for HTML output ------------------------------------------------- diff --git a/docs/docs_requirements.txt b/docs/docs_requirements.txt deleted file mode 100644 index 12dda3be0..000000000 --- a/docs/docs_requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -sphinx -numpy -wgpu -jinja2 diff --git a/docs/index.rst b/docs/index.rst index 9cbba8db3..3ac84b6f5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,6 +11,7 @@ Contents :maxdepth: 2 Guide + Gallery <_gallery/index.rst> Reference Writing shaders diff --git a/examples/README.rst b/examples/README.rst new file mode 100644 index 000000000..b3250789a --- /dev/null +++ b/examples/README.rst @@ -0,0 +1,2 @@ +Examples that use pygfx +======================= diff --git a/examples/feature_demo/README.rst b/examples/feature_demo/README.rst new file mode 100644 index 000000000..147d11c11 --- /dev/null +++ b/examples/feature_demo/README.rst @@ -0,0 +1 @@ +.. rubric:: Feature Demos diff --git a/examples/colormap_channels.py b/examples/feature_demo/plot_colormap_channels.py similarity index 96% rename from examples/colormap_channels.py rename to examples/feature_demo/plot_colormap_channels.py index 00e1a28e7..e21485ce7 100644 --- a/examples/colormap_channels.py +++ b/examples/feature_demo/plot_colormap_channels.py @@ -1,7 +1,12 @@ """ +Colormap Channels +================= + Example demonstrating colormaps in 4 modes: grayscale, gray+alpha, RGB, RGBA. """ +# sphinx_gallery_pygfx_render = True + import numpy as np from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/colormap_image.py b/examples/feature_demo/plot_colormap_image.py similarity index 97% rename from examples/colormap_image.py rename to examples/feature_demo/plot_colormap_image.py index 6a5ab0988..7dd17ce34 100644 --- a/examples/colormap_image.py +++ b/examples/feature_demo/plot_colormap_image.py @@ -1,7 +1,12 @@ """ +Colormap Image +============== + Example demonstrating different colormap dimensions on an image. """ +# sphinx_gallery_pygfx_render = True + import numpy as np import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/colormap_mesh.py b/examples/feature_demo/plot_colormap_mesh.py similarity index 98% rename from examples/colormap_mesh.py rename to examples/feature_demo/plot_colormap_mesh.py index 1a7197bde..3835547c7 100644 --- a/examples/colormap_mesh.py +++ b/examples/feature_demo/plot_colormap_mesh.py @@ -1,4 +1,7 @@ """ +Colormap Mesh +============= + Example demonstrating different colormap dimensions on a mesh, and per-vertex colors as a bonus. @@ -6,6 +9,8 @@ this can also be applied for points and lines. """ +# sphinx_gallery_pygfx_render = True + import numpy as np import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/custom_object1.py b/examples/feature_demo/plot_custom_object1.py similarity index 97% rename from examples/custom_object1.py rename to examples/feature_demo/plot_custom_object1.py index cd3d5aa28..c3e79dff4 100644 --- a/examples/custom_object1.py +++ b/examples/feature_demo/plot_custom_object1.py @@ -1,4 +1,7 @@ """ +Minimal Custom Object +===================== + Example that implements a minimal custom object and renders it. This example simply draws a triangle at the bottomleft of the screen. @@ -93,6 +96,7 @@ def code_fragment(self): # %% Setup scene +# sphinx_gallery_pygfx_render = True renderer = gfx.WgpuRenderer(WgpuCanvas()) camera = gfx.NDCCamera() # This material does not actually use the camera diff --git a/examples/custom_object2.py b/examples/feature_demo/plot_custom_object2.py similarity index 98% rename from examples/custom_object2.py rename to examples/feature_demo/plot_custom_object2.py index 74dc75367..680727ffd 100644 --- a/examples/custom_object2.py +++ b/examples/feature_demo/plot_custom_object2.py @@ -1,4 +1,7 @@ """ +Simple Custom Object +==================== + Example that implements a simple custom object and renders it. This example draws a triangle at the appropriate position; the object's @@ -125,6 +128,8 @@ def code_fragment(self): # %% Setup scene +# sphinx_gallery_pygfx_render = True + renderer = gfx.WgpuRenderer(WgpuCanvas()) camera = gfx.OrthographicCamera(10, 10) diff --git a/examples/custom_object3.py b/examples/feature_demo/plot_custom_object3.py similarity index 98% rename from examples/custom_object3.py rename to examples/feature_demo/plot_custom_object3.py index 074a8acfb..e0b960a8f 100644 --- a/examples/custom_object3.py +++ b/examples/feature_demo/plot_custom_object3.py @@ -1,4 +1,7 @@ """ +Custom Object +============= + Example that implements a custom object and renders it. This example draws multiple triangles. This is more or a full-fledged object. @@ -165,6 +168,8 @@ def code_fragment(self): # %% Setup scene +# sphinx_gallery_pygfx_render = True + renderer = gfx.WgpuRenderer(WgpuCanvas()) camera = gfx.OrthographicCamera(10, 10) diff --git a/examples/cylinder.py b/examples/feature_demo/plot_cylinder.py similarity index 96% rename from examples/cylinder.py rename to examples/feature_demo/plot_cylinder.py index 99bb18a94..e1d99b6ff 100644 --- a/examples/cylinder.py +++ b/examples/feature_demo/plot_cylinder.py @@ -1,7 +1,12 @@ """ +Types of Cylinders +================== + Example showing different types of geometric cylinders. """ +# sphinx_gallery_pygfx_render = True + import numpy as np from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/dynamic_env_map.py b/examples/feature_demo/plot_dynamic_env_map.py similarity index 96% rename from examples/dynamic_env_map.py rename to examples/feature_demo/plot_dynamic_env_map.py index 03c943be2..aaf30f076 100644 --- a/examples/dynamic_env_map.py +++ b/examples/feature_demo/plot_dynamic_env_map.py @@ -1,8 +1,13 @@ """ +Dynamic Environment Map +======================= + This example shows a dynamic environment map used for MeshStandardMaterial. The environment map is automatically be updated from the scene by a CubeCamera. """ +# sphinx_gallery_pygfx_render = True + import time import math diff --git a/examples/flat_shaded_torus.py b/examples/feature_demo/plot_flat_shaded_torus.py similarity index 92% rename from examples/flat_shaded_torus.py rename to examples/feature_demo/plot_flat_shaded_torus.py index 508d2afd0..b80b8884a 100644 --- a/examples/flat_shaded_torus.py +++ b/examples/feature_demo/plot_flat_shaded_torus.py @@ -1,7 +1,12 @@ """ +Flat Shading +============ + Example showing a Torus knot, using flat shading. """ +# sphinx_gallery_pygfx_render = True + from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/geometry_cubes.py b/examples/feature_demo/plot_geometry_cubes.py similarity index 94% rename from examples/geometry_cubes.py rename to examples/feature_demo/plot_geometry_cubes.py index 398948cf7..b09239815 100644 --- a/examples/geometry_cubes.py +++ b/examples/feature_demo/plot_geometry_cubes.py @@ -1,7 +1,12 @@ """ +Geometry Cubes +============== + Example showing multiple rotating cubes. This also tests the depth buffer. """ +# sphinx_gallery_pygfx_render = True + import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/geometry_image.py b/examples/feature_demo/plot_geometry_image.py similarity index 89% rename from examples/geometry_image.py rename to examples/feature_demo/plot_geometry_image.py index 8142f38ac..ee38035ee 100644 --- a/examples/geometry_image.py +++ b/examples/feature_demo/plot_geometry_image.py @@ -1,7 +1,12 @@ """ +Geometry Image +============== + Show an image. """ +# sphinx_gallery_pygfx_render = True + import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/geometry_klein_bottle.py b/examples/feature_demo/plot_geometry_klein_bottle.py similarity index 91% rename from examples/geometry_klein_bottle.py rename to examples/feature_demo/plot_geometry_klein_bottle.py index b44d79904..ead86bb0f 100644 --- a/examples/geometry_klein_bottle.py +++ b/examples/feature_demo/plot_geometry_klein_bottle.py @@ -1,7 +1,12 @@ """ +Klein Bottle Geometry +===================== + Example showing a Klein Bottle. """ +# sphinx_gallery_pygfx_render = True + from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/geometry_plane.py b/examples/feature_demo/plot_geometry_plane.py similarity index 93% rename from examples/geometry_plane.py rename to examples/feature_demo/plot_geometry_plane.py index cc9de4de0..6fa0b6dd2 100644 --- a/examples/geometry_plane.py +++ b/examples/feature_demo/plot_geometry_plane.py @@ -1,7 +1,12 @@ """ +Geometry Plane +============== + Use a plane geometry to show a texture, which is continuously updated to show video. """ +# sphinx_gallery_pygfx_render = True + import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/geometry_polyhedron.py b/examples/feature_demo/plot_geometry_polyhedron.py similarity index 93% rename from examples/geometry_polyhedron.py rename to examples/feature_demo/plot_geometry_polyhedron.py index f6d436881..5adccb0ee 100644 --- a/examples/geometry_polyhedron.py +++ b/examples/feature_demo/plot_geometry_polyhedron.py @@ -1,6 +1,10 @@ """ +Rotating Polyhedra +================== + Example showing multiple rotating polyhedrons. """ +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/geometry_polyhedron_subdivisions.py b/examples/feature_demo/plot_geometry_polyhedron_subdivisions.py similarity index 74% rename from examples/geometry_polyhedron_subdivisions.py rename to examples/feature_demo/plot_geometry_polyhedron_subdivisions.py index 7e38b5754..0c980d817 100644 --- a/examples/geometry_polyhedron_subdivisions.py +++ b/examples/feature_demo/plot_geometry_polyhedron_subdivisions.py @@ -1,7 +1,13 @@ """ +Subdivision +=========== + Example showing subdivided polyhedrons. """ +# sphinx_gallery_pygfx_render = True +# sphinx_gallery_pygfx_target_name = "disp" + import pygfx as gfx @@ -20,4 +26,5 @@ if __name__ == "__main__": - gfx.show(scene, up=gfx.linalg.Vector3(0, 0, 1)) + disp = gfx.Display() + disp.show(scene, up=gfx.linalg.Vector3(0, 0, 1)) diff --git a/examples/geometry_torus_knot.py b/examples/feature_demo/plot_geometry_torus_knot.py similarity index 94% rename from examples/geometry_torus_knot.py rename to examples/feature_demo/plot_geometry_torus_knot.py index 52d4d2a21..72d33110c 100644 --- a/examples/geometry_torus_knot.py +++ b/examples/feature_demo/plot_geometry_torus_knot.py @@ -1,6 +1,10 @@ """ +Torus knot +========== + Example showing a Torus knot, with a texture and lighting. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/helpers_gizmo.py b/examples/feature_demo/plot_helpers_gizmo.py similarity index 91% rename from examples/helpers_gizmo.py rename to examples/feature_demo/plot_helpers_gizmo.py index e42154d14..06edc44f2 100644 --- a/examples/helpers_gizmo.py +++ b/examples/feature_demo/plot_helpers_gizmo.py @@ -1,7 +1,11 @@ """ +Gizmo to transform world objects +================================ + Example to demonstrate the Gizmo that can be used to transform world objects. Click the center sphere to toggle between object-space, world-space, and screen-space. """ +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/image_click_events.py b/examples/feature_demo/plot_image_click_events.py similarity index 96% rename from examples/image_click_events.py rename to examples/feature_demo/plot_image_click_events.py index b00cf70bf..b89355382 100644 --- a/examples/image_click_events.py +++ b/examples/feature_demo/plot_image_click_events.py @@ -1,4 +1,7 @@ """ +Image Click Events +================== + Show an image and print the x, y image data coordinates for click events. """ @@ -13,6 +16,8 @@ # %% add image +# sphinx_gallery_pygfx_render = True + im = iio.imread("imageio:astronaut.png") image = gfx.Image( diff --git a/examples/image_plus_points.py b/examples/feature_demo/plot_image_plus_points.py similarity index 91% rename from examples/image_plus_points.py rename to examples/feature_demo/plot_image_plus_points.py index c402beae2..55c6ab7c9 100644 --- a/examples/image_plus_points.py +++ b/examples/feature_demo/plot_image_plus_points.py @@ -1,4 +1,7 @@ """ +Image with Points Overlaid +========================== + Show an image with points overlaid. """ @@ -22,6 +25,8 @@ # %% add points +# sphinx_gallery_pygfx_render = True + xx = [182, 180, 161, 153, 191, 237, 293, 300, 272, 267, 254] yy = [145, 131, 112, 59, 29, 14, 48, 91, 136, 137, 172] diff --git a/examples/instancing_mesh.py b/examples/feature_demo/plot_instancing_mesh.py similarity index 95% rename from examples/instancing_mesh.py rename to examples/feature_demo/plot_instancing_mesh.py index 60e478625..7356fcf31 100644 --- a/examples/instancing_mesh.py +++ b/examples/feature_demo/plot_instancing_mesh.py @@ -1,6 +1,10 @@ """ +Instancing +========== + Example rendering the same mesh object multiple times, using instancing. """ +# sphinx_gallery_pygfx_render = True import numpy as np import imageio.v3 as iio diff --git a/examples/light_directional_shadow.py b/examples/feature_demo/plot_light_directional_shadow.py similarity index 81% rename from examples/light_directional_shadow.py rename to examples/feature_demo/plot_light_directional_shadow.py index 7f3ba138f..9279fb275 100644 --- a/examples/light_directional_shadow.py +++ b/examples/feature_demo/plot_light_directional_shadow.py @@ -1,9 +1,14 @@ """ -This example demonstrates directional light shadows and their helper. -The cubes within the view frustum of the shadow camera have complete shadows, -the cubes at the edge of the view frustum of the camera have partial shadows, -while the cubes outside the view frustum of the camera will not cast shadows. + +Directional Shadow 1 +==================== + +This example demonstrates directional light shadows and their helper. The cubes +within the view frustum of the shadow camera have complete shadows, the cubes at +the edge of the view frustum of the camera have partial shadows, while the cubes +outside the view frustum of the camera will not cast shadows. """ +# sphinx_gallery_pygfx_render = True import math diff --git a/examples/light_shadow.py b/examples/feature_demo/plot_light_shadow.py similarity index 93% rename from examples/light_shadow.py rename to examples/feature_demo/plot_light_shadow.py index c6ccebf9d..f56ff9461 100644 --- a/examples/light_shadow.py +++ b/examples/feature_demo/plot_light_shadow.py @@ -1,7 +1,12 @@ """ -This example demonstrates the effects of directional light shadows (from DirectionalLight) -and omnidirectional shadows (from PointLight). + +Directional Shadow 2 +==================== + +This example demonstrates the effects of directional light shadows (from +DirectionalLight) and omnidirectional shadows (from PointLight). """ +# sphinx_gallery_pygfx_render = True import time import math diff --git a/examples/light_spotlight_shadows.py b/examples/feature_demo/plot_light_spotlight_shadows.py similarity index 97% rename from examples/light_spotlight_shadows.py rename to examples/feature_demo/plot_light_spotlight_shadows.py index afc11f707..54519d991 100644 --- a/examples/light_spotlight_shadows.py +++ b/examples/feature_demo/plot_light_spotlight_shadows.py @@ -1,6 +1,12 @@ """ + +Spotlight Shadow +================ + + Spotlights and shadows examnple """ +# sphinx_gallery_pygfx_render = True import time import math @@ -155,8 +161,9 @@ def animate(): renderer.request_draw() renderer.request_draw(animate) + return renderer if __name__ == "__main__": - init_scene() + renderer = init_scene() run() diff --git a/examples/light_spotlights.py b/examples/feature_demo/plot_light_spotlights.py similarity index 97% rename from examples/light_spotlights.py rename to examples/feature_demo/plot_light_spotlights.py index 75ab6a255..55104b844 100644 --- a/examples/light_spotlights.py +++ b/examples/feature_demo/plot_light_spotlights.py @@ -1,6 +1,12 @@ """ + +Spotlights +========== + + Spotlights example. """ +# sphinx_gallery_pygfx_render = True import time import math @@ -142,8 +148,9 @@ def animate(): renderer.request_draw() renderer.request_draw(animate) + return renderer if __name__ == "__main__": - init_scene() + renderer = init_scene() run() diff --git a/examples/line_basic.py b/examples/feature_demo/plot_line_basic.py similarity index 91% rename from examples/line_basic.py rename to examples/feature_demo/plot_line_basic.py index ad6fd19fa..b1bfb6608 100644 --- a/examples/line_basic.py +++ b/examples/feature_demo/plot_line_basic.py @@ -1,6 +1,12 @@ """ +Line Drawing +============ + + Some basic line drawing. """ +# sphinx_gallery_pygfx_render = True +# sphinx_gallery_pygfx_target_name = "canvas" import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/line_performance.py b/examples/feature_demo/plot_line_performance.py similarity index 89% rename from examples/line_performance.py rename to examples/feature_demo/plot_line_performance.py index 9ee549c30..79d2fb741 100644 --- a/examples/line_performance.py +++ b/examples/feature_demo/plot_line_performance.py @@ -1,6 +1,10 @@ """ +Line Drawing Performance +======================== + Display a line depicting a noisy signal consisting of a lot of points. """ +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/line_segments.py b/examples/feature_demo/plot_line_segments.py similarity index 92% rename from examples/line_segments.py rename to examples/feature_demo/plot_line_segments.py index ae451c28c..59c7a105d 100644 --- a/examples/line_segments.py +++ b/examples/feature_demo/plot_line_segments.py @@ -1,6 +1,11 @@ """ + +Line Segments +============= + Display line segments. Can be useful e.g. for visializing vector fields. """ +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/line_thick.py b/examples/feature_demo/plot_line_thick.py similarity index 94% rename from examples/line_thick.py rename to examples/feature_demo/plot_line_thick.py index 6de6f3063..2bc3c25b2 100644 --- a/examples/line_thick.py +++ b/examples/feature_demo/plot_line_thick.py @@ -1,6 +1,10 @@ """ +Thick Lines +=========== + Display very thick lines to show how lines stay pretty on large scales. """ +# sphinx_gallery_pygfx_render = True import random from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/manual_matrix_update.py b/examples/feature_demo/plot_manual_matrix_update.py similarity index 92% rename from examples/manual_matrix_update.py rename to examples/feature_demo/plot_manual_matrix_update.py index e30ff7e8d..ed8210a61 100644 --- a/examples/manual_matrix_update.py +++ b/examples/feature_demo/plot_manual_matrix_update.py @@ -1,6 +1,11 @@ """ +Transform Control without Matrix Updating +========================================= + + Example showing transform control flow without matrix auto updating. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/mesh_depth_material.py b/examples/feature_demo/plot_mesh_depth_material.py similarity index 98% rename from examples/mesh_depth_material.py rename to examples/feature_demo/plot_mesh_depth_material.py index 0944b03eb..cd9a1b1fd 100644 --- a/examples/mesh_depth_material.py +++ b/examples/feature_demo/plot_mesh_depth_material.py @@ -1,4 +1,7 @@ """ +Depth Material +============== + A custom material for drawing geometry by depth """ @@ -101,6 +104,8 @@ def code_fragment(self): # %% Setup scene +# sphinx_gallery_pygfx_render = True + renderer = gfx.WgpuRenderer(WgpuCanvas(size=(640, 480))) camera = gfx.PerspectiveCamera(45, 640 / 480, 8, 12) camera.position.z = 10 diff --git a/examples/mesh_slice.py b/examples/feature_demo/plot_mesh_slice.py similarity index 94% rename from examples/mesh_slice.py rename to examples/feature_demo/plot_mesh_slice.py index 2231e8e85..88b4b0008 100644 --- a/examples/mesh_slice.py +++ b/examples/feature_demo/plot_mesh_slice.py @@ -1,6 +1,10 @@ """ +Mesh Slice Material +=================== + Example showing off the mesh slice material. """ +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/multi_select.py b/examples/feature_demo/plot_multi_select.py similarity index 97% rename from examples/multi_select.py rename to examples/feature_demo/plot_multi_select.py index f4e147991..ec5f5580c 100644 --- a/examples/multi_select.py +++ b/examples/feature_demo/plot_multi_select.py @@ -1,4 +1,8 @@ """ +Multi-Object Selection +====================== + + Example demonstrating multi object selection using mouse events. Hovering the mouse over a cube will highlight it with a bounding box. @@ -6,6 +10,7 @@ all the items from that group (because the group has a double-click event handler). Holding shift will add to the selection. """ +# sphinx_gallery_pygfx_render = True from functools import partial from random import randint, random diff --git a/examples/multi_slice1.py b/examples/feature_demo/plot_multi_slice1.py similarity index 96% rename from examples/multi_slice1.py rename to examples/feature_demo/plot_multi_slice1.py index 25c0ed9d2..0b3277ada 100644 --- a/examples/multi_slice1.py +++ b/examples/feature_demo/plot_multi_slice1.py @@ -1,8 +1,12 @@ """ +Volume and Mesh Slicing 1 +========================= + Slice a volume and a mesh through the three primary planes (XY, XZ, YZ). This example uses a mesh object with custom texture coordinates. This is a generic approach. See multi_slice2.py for a simpler way. """ +# sphinx_gallery_pygfx_render = True from time import time diff --git a/examples/multi_slice2.py b/examples/feature_demo/plot_multi_slice2.py similarity index 95% rename from examples/multi_slice2.py rename to examples/feature_demo/plot_multi_slice2.py index 51c772876..b24a97833 100644 --- a/examples/multi_slice2.py +++ b/examples/feature_demo/plot_multi_slice2.py @@ -1,9 +1,14 @@ """ +Mesh and Volume Slicing 2 +========================= + + Slice a volume and a mesh through the three primary planes (XY, XZ, YZ). This example uses Volume object with a VolumeSliceMaterial, which produces an implicit geometry defined by the volume data. See multi_slice1.py for a more generic approach. """ +# sphinx_gallery_pygfx_render = True from time import time diff --git a/examples/panzoom_camera.py b/examples/feature_demo/plot_panzoom_camera.py similarity index 95% rename from examples/panzoom_camera.py rename to examples/feature_demo/plot_panzoom_camera.py index f805308d3..607aa7809 100644 --- a/examples/panzoom_camera.py +++ b/examples/feature_demo/plot_panzoom_camera.py @@ -1,9 +1,13 @@ """ +Panzoom Camera +============== + Example showing orbit camera controller. Press 's' to save the state, and press 'l' to load the last saved state. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/pbr.py b/examples/feature_demo/plot_pbr.py similarity index 63% rename from examples/pbr.py rename to examples/feature_demo/plot_pbr.py index c3592710f..5ba93db5e 100644 --- a/examples/pbr.py +++ b/examples/feature_demo/plot_pbr.py @@ -1,10 +1,37 @@ """ +PBR Rendering 1 +=============== + + This example shows a complete PBR rendering effect. The cubemap of skybox is also the environment cubemap of the helmet. """ +################################################################################ +# .. warning:: +# An external model is needed to run this example. +# +# To run this example, you need a model from the source repo's example +# folder. If you are running this example from a local copy of the code (dev +# install) no further actions are needed. Otherwise, you may have to replace +# the path below to point to the location of the model. + +import os from pathlib import Path +try: + # modify this line if your model is located elsewhere + model_dir = Path(__file__).parents[1] / "models" +except NameError: + # compatibility with sphinx-gallery + model_dir = Path(os.getcwd()).parent / "models" + + +################################################################################ +# Once the path is set correctly, you can use the model as follows: + +# sphinx_gallery_pygfx_render = True + import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx @@ -40,9 +67,7 @@ # Load meshes, and apply env map # Note that this lits the helmet already -gltf_path = ( - Path(__file__).parent / "models" / "DamagedHelmet" / "glTF" / "DamagedHelmet.gltf" -) +gltf_path = model_dir / "DamagedHelmet" / "glTF" / "DamagedHelmet.gltf" meshes = gfx.load_scene(gltf_path) scene.add(*meshes) m = meshes[0] # this example has just one mesh diff --git a/examples/pbr2.py b/examples/feature_demo/plot_pbr2.py similarity index 94% rename from examples/pbr2.py rename to examples/feature_demo/plot_pbr2.py index 40e5fd18c..25c35c95e 100644 --- a/examples/pbr2.py +++ b/examples/feature_demo/plot_pbr2.py @@ -1,8 +1,13 @@ """ -This example shows the lighting rendering affect of materials with different metalness and roughness. -Every second sphere has an IBL environment map on it. + +PBR Rendering 2 +=============== + +This example shows the lighting rendering affect of materials with different +metalness and roughness. Every second sphere has an IBL environment map on it. """ # run_example = false +# sphinx_gallery_pygfx_render = True import time import math diff --git a/examples/physical_color.py b/examples/feature_demo/plot_physical_color.py similarity index 95% rename from examples/physical_color.py rename to examples/feature_demo/plot_physical_color.py index 2a5829f4e..ca9a314af 100644 --- a/examples/physical_color.py +++ b/examples/feature_demo/plot_physical_color.py @@ -1,4 +1,8 @@ """ + +Physical Color +============== + PyGfx by default assumes that all colors are in the sRGB colorspace. This example shows how you can also provide colors in physical colorspace (a.k.a. linear-srgb). This example shows 3 images: @@ -6,6 +10,7 @@ * An image in physical colorspace, shows up too dark. * An image in physical colorspace, rendered correctly. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/picking_color.py b/examples/feature_demo/plot_picking_color.py similarity index 95% rename from examples/picking_color.py rename to examples/feature_demo/plot_picking_color.py index 7cf810d90..5beae1984 100644 --- a/examples/picking_color.py +++ b/examples/feature_demo/plot_picking_color.py @@ -1,7 +1,11 @@ """ +Color Picking +============= + Example showing picking the color from the scene. Depending on the object being clicked, more detailed picking info is available. """ +# sphinx_gallery_pygfx_render = True import numpy as np import imageio.v3 as iio diff --git a/examples/picking_mesh.py b/examples/feature_demo/plot_picking_mesh.py similarity index 97% rename from examples/picking_mesh.py rename to examples/feature_demo/plot_picking_mesh.py index ddb87cf20..c29f26923 100644 --- a/examples/picking_mesh.py +++ b/examples/feature_demo/plot_picking_mesh.py @@ -1,7 +1,11 @@ """ +Mesh Picking +============ + Example showing picking a mesh. Showing two meshes that can be clicked on. Upon clicking, the vertex closest to the pick location is moved. """ +# sphinx_gallery_pygfx_render = True # todo: if we have per-vertex coloring, we can paint on the mesh instead :D diff --git a/examples/picking_points.py b/examples/feature_demo/plot_picking_points.py similarity index 94% rename from examples/picking_points.py rename to examples/feature_demo/plot_picking_points.py index 9409c59fc..034c33077 100644 --- a/examples/picking_points.py +++ b/examples/feature_demo/plot_picking_points.py @@ -1,7 +1,11 @@ """ +Point Picking +============= + Example showing picking points. When clicking on a point, it's location is changed. With a small change, a line is shown instead. """ +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/scene_in_a_scene.py b/examples/feature_demo/plot_scene_in_a_scene.py similarity index 94% rename from examples/scene_in_a_scene.py rename to examples/feature_demo/plot_scene_in_a_scene.py index f8ba7d453..a07d0bb1e 100644 --- a/examples/scene_in_a_scene.py +++ b/examples/feature_demo/plot_scene_in_a_scene.py @@ -1,4 +1,7 @@ """ +Nested Scenes +============= + Example of a scene rendered to a texture, which is shown in another scene. Inception style. Since both the outer and inner scenes are lit, this @@ -8,6 +11,8 @@ The sub-scene is rendered to a texture, and that texture is used for the surface of the cube in the outer scene. """ +# sphinx_gallery_pygfx_render = True +# sphinx_gallery_pygfx_target_name = "renderer2" import numpy as np import imageio.v3 as iio diff --git a/examples/scene_overlay.py b/examples/feature_demo/plot_scene_overlay.py similarity index 95% rename from examples/scene_overlay.py rename to examples/feature_demo/plot_scene_overlay.py index 1dad71bc9..4b80b01fd 100644 --- a/examples/scene_overlay.py +++ b/examples/feature_demo/plot_scene_overlay.py @@ -1,9 +1,13 @@ """ +Scene Overlay +============= + Example showing a 3D scene with a 2D overlay. The idea is to render both scenes, but clear the depth before rendering the overlay, so that it's always on top. """ +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/scene_subplots1.py b/examples/feature_demo/plot_scene_subplots1.py similarity index 96% rename from examples/scene_subplots1.py rename to examples/feature_demo/plot_scene_subplots1.py index 1a6a1f638..fb708fc30 100644 --- a/examples/scene_subplots1.py +++ b/examples/feature_demo/plot_scene_subplots1.py @@ -1,10 +1,14 @@ """ +Subplots 1 +========== + Example showing how to render to a subregion of a canvas. This is a feature necessary to implement e.g. subplots. This example uses a low-level approach without using the Viewport object. See scene_subplot2.py for a slightly higher-level approach. """ +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/scene_subplots2.py b/examples/feature_demo/plot_scene_subplots2.py similarity index 97% rename from examples/scene_subplots2.py rename to examples/feature_demo/plot_scene_subplots2.py index 6892ad374..d6e7afcbc 100644 --- a/examples/scene_subplots2.py +++ b/examples/feature_demo/plot_scene_subplots2.py @@ -1,6 +1,10 @@ """ +Subplots 2 +========== + Like scene_side_by_side, but now with a more plot-like idea, and mouse interaction. """ +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/scene_subplots_video.py b/examples/feature_demo/plot_scene_subplots_video.py similarity index 97% rename from examples/scene_subplots_video.py rename to examples/feature_demo/plot_scene_subplots_video.py index 3397d57ef..a9db90f27 100644 --- a/examples/scene_subplots_video.py +++ b/examples/feature_demo/plot_scene_subplots_video.py @@ -1,7 +1,11 @@ """ +Subplots Video +============== + An example combining `synced_video.py` with subplots. Double click to re-center the images. """ +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/show_image.py b/examples/feature_demo/plot_show_image.py similarity index 91% rename from examples/show_image.py rename to examples/feature_demo/plot_show_image.py index c947740ea..c5bacdf36 100644 --- a/examples/show_image.py +++ b/examples/feature_demo/plot_show_image.py @@ -1,6 +1,10 @@ """ +Show Image +========== + Use camera.show_object to ensure the Image is in view. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/show_scene.py b/examples/feature_demo/plot_show_scene.py similarity index 75% rename from examples/show_scene.py rename to examples/feature_demo/plot_show_scene.py index f4c9a8117..837693c41 100644 --- a/examples/show_scene.py +++ b/examples/feature_demo/plot_show_scene.py @@ -1,6 +1,11 @@ """ +Use gfx.show to show a Scene +============================ + Demonstrates show utility for a scene """ +# sphinx_gallery_pygfx_render = True +# sphinx_gallery_pygfx_target_name = "disp" import imageio.v3 as iio import pygfx as gfx @@ -21,4 +26,5 @@ scene.add(background) if __name__ == "__main__": - gfx.show(scene) + disp = gfx.Display() + disp.show(scene) diff --git a/examples/feature_demo/plot_show_stl.py b/examples/feature_demo/plot_show_stl.py new file mode 100644 index 000000000..0f8491df8 --- /dev/null +++ b/examples/feature_demo/plot_show_stl.py @@ -0,0 +1,49 @@ +""" +Show STL File via gfx.show +========================== + +Demonstrates show utility with an STL file +""" + +################################################################################ +# .. warning:: +# An external model is needed to run this example. +# +# To run this example, you need a model from the source repo's example +# folder. If you are running this example from a local copy of the code (dev +# install) no further actions are needed. Otherwise, you may have to replace +# the path below to point to the location of the model. + +import os +from pathlib import Path + +try: + # modify this line if your model is located elsewhere + model_dir = Path(__file__).parents[1] / "models" +except NameError: + # compatibility with sphinx-gallery + model_dir = Path(os.getcwd()).parent / "models" + + +################################################################################ +# Once the path is set correctly, you can use the model as follows: + +# sphinx_gallery_pygfx_render = True +# sphinx_gallery_pygfx_target_name = "disp" + +import trimesh +import pygfx as gfx + + +TEAPOT = model_dir / "teapot.stl" + +teapot = trimesh.load(TEAPOT) + +mesh = gfx.Mesh( + gfx.trimesh_geometry(teapot), + gfx.MeshPhongMaterial(), +) + +if __name__ == "__main__": + disp = gfx.Display() + disp.show(mesh, up=gfx.linalg.Vector3(0, 0, 1)) diff --git a/examples/skybox.py b/examples/feature_demo/plot_skybox.py similarity index 96% rename from examples/skybox.py rename to examples/feature_demo/plot_skybox.py index 6b06edb4f..9b9cec30e 100644 --- a/examples/skybox.py +++ b/examples/feature_demo/plot_skybox.py @@ -1,8 +1,12 @@ """ +Use a Skybox +============ + Example with a skybox background. Inspired by https://github.com/gfx-rs/wgpu-rs/blob/master/examples/skybox/main.rs """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/spheres.py b/examples/feature_demo/plot_spheres.py similarity index 95% rename from examples/spheres.py rename to examples/feature_demo/plot_spheres.py index 29b7c4df8..f175f1d67 100644 --- a/examples/spheres.py +++ b/examples/feature_demo/plot_spheres.py @@ -1,6 +1,10 @@ """ +Sphere Geometry +=============== + Example showing different types of geometric cylinders. """ +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/synced_video.py b/examples/feature_demo/plot_synced_video.py similarity index 93% rename from examples/synced_video.py rename to examples/feature_demo/plot_synced_video.py index 45964b982..f45a4a6b6 100644 --- a/examples/synced_video.py +++ b/examples/feature_demo/plot_synced_video.py @@ -1,6 +1,10 @@ """ +Synced Video Rendering +====================== + Example demonstrating synced video rendering """ +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/transparency1.py b/examples/feature_demo/plot_transparency1.py similarity index 95% rename from examples/transparency1.py rename to examples/feature_demo/plot_transparency1.py index 57d9df6bc..9c6c1cee6 100644 --- a/examples/transparency1.py +++ b/examples/feature_demo/plot_transparency1.py @@ -1,8 +1,12 @@ """ +Transparency 1 +============== + Example showing transparency using three overlapping planes. Press space to toggle the order of the planes. Press 1-6 to select the blend mode. """ +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/transparency2.py b/examples/feature_demo/plot_transparency2.py similarity index 96% rename from examples/transparency2.py rename to examples/feature_demo/plot_transparency2.py index 2cf928d45..6e620d9e3 100644 --- a/examples/transparency2.py +++ b/examples/feature_demo/plot_transparency2.py @@ -1,8 +1,12 @@ """ +Transparency 2 +============== + Example showing transparency using three orthogonal planes. Press space to toggle the order of the planes. Press 1-6 to select the blend mode. """ +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/volume_render1.py b/examples/feature_demo/plot_volume_render1.py similarity index 95% rename from examples/volume_render1.py rename to examples/feature_demo/plot_volume_render1.py index be3371d2a..1946afa89 100644 --- a/examples/volume_render1.py +++ b/examples/feature_demo/plot_volume_render1.py @@ -1,6 +1,10 @@ """ +Volume Rendering 1 +================== + Render a volume. Shift-click to draw white blobs inside the volume. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio import numpy as np diff --git a/examples/volume_render2.py b/examples/feature_demo/plot_volume_render2.py similarity index 94% rename from examples/volume_render2.py rename to examples/feature_demo/plot_volume_render2.py index 7fa3cd181..325b0a901 100644 --- a/examples/volume_render2.py +++ b/examples/feature_demo/plot_volume_render2.py @@ -1,6 +1,10 @@ """ +Volume Rendering 2 +================== + Render three volumes using different world transforms. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio import numpy as np diff --git a/examples/volume_slice1.py b/examples/feature_demo/plot_volume_slice1.py similarity index 93% rename from examples/volume_slice1.py rename to examples/feature_demo/plot_volume_slice1.py index 9404daa35..b8d227f34 100644 --- a/examples/volume_slice1.py +++ b/examples/feature_demo/plot_volume_slice1.py @@ -1,7 +1,11 @@ """ +Volume Slice 1 +============== + Render slices through a volume, by uploading to a 2D texture. Simple and ... slow. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/volume_slice2.py b/examples/feature_demo/plot_volume_slice2.py similarity index 94% rename from examples/volume_slice2.py rename to examples/feature_demo/plot_volume_slice2.py index c2f3a84eb..22c6c6e63 100644 --- a/examples/volume_slice2.py +++ b/examples/feature_demo/plot_volume_slice2.py @@ -1,7 +1,11 @@ """ +Volume Slice 2 +============== + Render slices through a volume, by creating a 3D texture, with 2D views. Simple and relatively fast, but no subslices. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/volume_slice3.py b/examples/feature_demo/plot_volume_slice3.py similarity index 94% rename from examples/volume_slice3.py rename to examples/feature_demo/plot_volume_slice3.py index 7579bf7e9..0c97c5a6a 100644 --- a/examples/volume_slice3.py +++ b/examples/feature_demo/plot_volume_slice3.py @@ -1,7 +1,11 @@ """ +Volume Slice 3 +============== + Render slices through a volume, by creating a 3D texture, and sampling onto a plane geometry. Simple, fast and subpixel! """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio import numpy as np diff --git a/examples/volume_slice4.py b/examples/feature_demo/plot_volume_slice4.py similarity index 94% rename from examples/volume_slice4.py rename to examples/feature_demo/plot_volume_slice4.py index 3c7d6b629..70247368a 100644 --- a/examples/volume_slice4.py +++ b/examples/feature_demo/plot_volume_slice4.py @@ -1,7 +1,11 @@ """ +Volume Slice 4 +============== + Render slices through a volume, by creating a 3D texture, and viewing it with a VolumeSliceMaterial. Easy because we can just define the view plane. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/wireframe1.py b/examples/feature_demo/plot_wireframe1.py similarity index 94% rename from examples/wireframe1.py rename to examples/feature_demo/plot_wireframe1.py index f81474655..8bd6af9be 100644 --- a/examples/wireframe1.py +++ b/examples/feature_demo/plot_wireframe1.py @@ -1,10 +1,14 @@ """ +Wireframe 1 +=========== + Example showing a Torus knot, with a wireframe overlay. In this case the wireframe is lit while the solid mesh is not, producing a look of a metalic frame around a soft tube. """ +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/wireframe2.py b/examples/feature_demo/plot_wireframe2.py similarity index 94% rename from examples/wireframe2.py rename to examples/feature_demo/plot_wireframe2.py index b139911e5..14d6f64c5 100644 --- a/examples/wireframe2.py +++ b/examples/feature_demo/plot_wireframe2.py @@ -1,8 +1,12 @@ """ +Wireframe 2 +=========== + Example showing a Torus knot, as a wireframe. We create two wireframes, one for the front, bright blue and lit, and one for the back, unlit and gray. """ +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/wireframe_material.py b/examples/feature_demo/plot_wireframe_material.py similarity index 98% rename from examples/wireframe_material.py rename to examples/feature_demo/plot_wireframe_material.py index 328de1b51..0d9e653f4 100644 --- a/examples/wireframe_material.py +++ b/examples/feature_demo/plot_wireframe_material.py @@ -1,4 +1,7 @@ """ +Wireframe Material +================== + Example showing a custom wireframe material, together with the builtin option to render a wireframe. Notice that in the former the wires are not of equal thickness. @@ -147,6 +150,8 @@ def code_fragment(self): # %% Setup scene +# sphinx_gallery_pygfx_render = True + renderer = gfx.WgpuRenderer(WgpuCanvas(size=(640, 480))) camera = gfx.PerspectiveCamera(45, 640 / 480, 0.1, 100) camera.position.z = 10 diff --git a/examples/world_bounding_box.py b/examples/feature_demo/plot_world_bounding_box.py similarity index 93% rename from examples/world_bounding_box.py rename to examples/feature_demo/plot_world_bounding_box.py index 52d29fc59..7679bbcac 100644 --- a/examples/world_bounding_box.py +++ b/examples/feature_demo/plot_world_bounding_box.py @@ -1,8 +1,12 @@ """ +Bounding Box Coordinates +======================== + Render two volumes using different world transforms. Prints the world bounding box of the scene which used to trigger an Exception. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio import numpy as np diff --git a/examples/introductory/README.rst b/examples/introductory/README.rst new file mode 100644 index 000000000..dbbeed995 --- /dev/null +++ b/examples/introductory/README.rst @@ -0,0 +1 @@ +.. rubric:: Introductory Examples diff --git a/examples/clipping_planes.py b/examples/introductory/plot_clipping_planes.py similarity index 95% rename from examples/clipping_planes.py rename to examples/introductory/plot_clipping_planes.py index 1bff200bb..cd5d72c4a 100644 --- a/examples/clipping_planes.py +++ b/examples/introductory/plot_clipping_planes.py @@ -1,6 +1,10 @@ """ +Clipping Planes +=============== + Example demonstrating clipping planes on a mesh. """ +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/cube.py b/examples/introductory/plot_cube.py similarity index 64% rename from examples/cube.py rename to examples/introductory/plot_cube.py index 67f158f55..4826caa3e 100644 --- a/examples/cube.py +++ b/examples/introductory/plot_cube.py @@ -1,6 +1,13 @@ """ -Example showing a single geometric cube. +Hello World +=========== + +In this example shows how to do the rendering world's hello world: Show a 3D +Cube on screen. + """ +# sphinx_gallery_pygfx_animate = True +# sphinx_gallery_pygfx_target_name = "disp" import pygfx as gfx diff --git a/examples/hello_triangle.py b/examples/introductory/plot_hello_triangle.py similarity index 88% rename from examples/hello_triangle.py rename to examples/introductory/plot_hello_triangle.py index 68e63f188..faf20a6b3 100644 --- a/examples/hello_triangle.py +++ b/examples/introductory/plot_hello_triangle.py @@ -1,6 +1,10 @@ """ +Render a Triangle +================= + Replicating the WGPU triangle example, but with about 10x less code. """ +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/light_basic.py b/examples/introductory/plot_light_basic.py similarity index 96% rename from examples/light_basic.py rename to examples/introductory/plot_light_basic.py index 6e54a9e95..49d73c62c 100644 --- a/examples/light_basic.py +++ b/examples/introductory/plot_light_basic.py @@ -1,7 +1,12 @@ """ +Lights +====== + + Simple light example. This example shows a cube with MeshPhongMaterial illuminated by a point light and a directional light. """ +# sphinx_gallery_pygfx_render = True import time import math diff --git a/examples/introductory/plot_object_bounding_box.py b/examples/introductory/plot_object_bounding_box.py new file mode 100644 index 000000000..cf28e0a74 --- /dev/null +++ b/examples/introductory/plot_object_bounding_box.py @@ -0,0 +1,61 @@ +""" +Boundary Boxes +============== + +Demonstrates visualizing object bounding boxes +""" + +################################################################################ +# .. warning:: +# An external model is needed to run this example. +# +# To run this example, you need a model from the source repo's example +# folder. If you are running this example from a local copy of the code (dev +# install) no further actions are needed. Otherwise, you may have to replace +# the path below to point to the location of the model. + +import os +from pathlib import Path + +try: + # modify this line if your model is located elsewhere + model_dir = Path(__file__).parents[1] / "models" +except NameError: + # compatibility with sphinx-gallery + model_dir = Path(os.getcwd()).parent / "models" + + +################################################################################ +# Once the path is set correctly, you can use the model as follows: + +# sphinx_gallery_pygfx_render = True +# sphinx_gallery_pygfx_target_name = "disp" + +import trimesh +import pygfx as gfx + + +teapot = trimesh.load(model_dir / "teapot.stl") + +scene = gfx.Scene() +scene.add(gfx.AmbientLight(), gfx.DirectionalLight()) + +mesh = gfx.Mesh( + gfx.trimesh_geometry(teapot), + gfx.MeshPhongMaterial(), +) +mesh.rotation.set_from_euler(gfx.linalg.Euler(0.71, 0.91)) +scene.add(mesh) + +box_world = gfx.BoxHelper(color="red") +box_world.set_transform_by_object(mesh) +scene.add(box_world) + +box_local = gfx.BoxHelper(thickness=2, color="green") +box_local.set_transform_by_object(mesh, space="local") +mesh.add(box_local) # note that the parent is `mesh` here, not `scene` + + +if __name__ == "__main__": + disp = gfx.Display() + disp.show(scene, up=gfx.linalg.Vector3(0, 0, 1)) diff --git a/examples/offscreen.py b/examples/introductory/plot_offscreen.py similarity index 95% rename from examples/offscreen.py rename to examples/introductory/plot_offscreen.py index 9242ff683..ef4019f32 100644 --- a/examples/offscreen.py +++ b/examples/introductory/plot_offscreen.py @@ -1,10 +1,14 @@ """ +Offscreen Rendering +=================== + Example demonstrating off-screen rendering. This uses wgpu's offscreen canvas to obtain the frames as a numpy array. Note that one can also render to a ``pygfx.Texture`` and use that texture to decorate an object in another scene. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio import pygfx as gfx diff --git a/examples/orbit_camera.py b/examples/introductory/plot_orbit_camera.py similarity index 96% rename from examples/orbit_camera.py rename to examples/introductory/plot_orbit_camera.py index 4644ce8c8..e38516ef7 100644 --- a/examples/orbit_camera.py +++ b/examples/introductory/plot_orbit_camera.py @@ -1,9 +1,13 @@ """ +Orbit Camera +============ + Example showing orbit camera controller. Press 's' to save the state, and press 'l' to load the last saved state. """ +# sphinx_gallery_pygfx_render = True import imageio.v3 as iio from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/points_basic.py b/examples/introductory/plot_points_basic.py similarity index 88% rename from examples/points_basic.py rename to examples/introductory/plot_points_basic.py index e3fcb0850..069de76c6 100644 --- a/examples/points_basic.py +++ b/examples/introductory/plot_points_basic.py @@ -1,3 +1,13 @@ +""" +Rendering Points +================ + + +Render Points +""" +# sphinx_gallery_pygfx_render = True + + import numpy as np from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/show_util.py b/examples/introductory/plot_show_util.py similarity index 54% rename from examples/show_util.py rename to examples/introductory/plot_show_util.py index 53bae9fca..633bb72a2 100644 --- a/examples/show_util.py +++ b/examples/introductory/plot_show_util.py @@ -1,6 +1,11 @@ """ +Use gfx.show +============ + Demonstrates show utility """ +# sphinx_gallery_pygfx_render = True +# sphinx_gallery_pygfx_target_name = "disp" import pygfx as gfx @@ -10,4 +15,5 @@ ) if __name__ == "__main__": - gfx.show(cube) + disp = gfx.Display() + disp.show(cube) diff --git a/examples/object_bounding_box.py b/examples/object_bounding_box.py deleted file mode 100644 index 84e969bce..000000000 --- a/examples/object_bounding_box.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Demonstrates visualizing object bounding boxes -""" -from pathlib import Path - -import trimesh - -import pygfx as gfx - - -teapot = trimesh.load(Path(__file__).parent / "models" / "teapot.stl") - -scene = gfx.Scene() - -mesh = gfx.Mesh( - gfx.trimesh_geometry(teapot), - gfx.MeshPhongMaterial(), -) -mesh.rotation.set_from_euler(gfx.linalg.Euler(0.71, 0.91)) -scene.add(mesh) - -box_world = gfx.BoxHelper(color="red") -box_world.set_transform_by_object(mesh) -scene.add(box_world) - -box_local = gfx.BoxHelper(thickness=2, color="green") -box_local.set_transform_by_object(mesh, space="local") -mesh.add(box_local) # note that the parent is `mesh` here, not `scene` - - -if __name__ == "__main__": - gfx.show(scene, up=gfx.linalg.Vector3(0, 0, 1)) diff --git a/examples/other/README.rst b/examples/other/README.rst new file mode 100644 index 000000000..cc20b3cfa --- /dev/null +++ b/examples/other/README.rst @@ -0,0 +1,5 @@ +.. rubric:: Other Examples + +.. note:: + The examples in this section are not rendered. Typically because they use a + backend that is not available on the CI runner. diff --git a/examples/collection_line.py b/examples/other/collection_line.py similarity index 94% rename from examples/collection_line.py rename to examples/other/collection_line.py index 7178e8397..092f9a68e 100644 --- a/examples/collection_line.py +++ b/examples/other/collection_line.py @@ -1,9 +1,14 @@ """ +Peformance of drawing many Lines +================================ + Display a lot of line objects. Because of the architecture of wgpu, this is still performant. """ # run_example = false - because it takes too long and times out +# sphinx_gallery_pygfx_render = True + import time # noqa import numpy as np diff --git a/examples/cube_qt.py b/examples/other/cube_qt.py similarity index 95% rename from examples/cube_qt.py rename to examples/other/cube_qt.py index 44ad21686..a00845269 100644 --- a/examples/cube_qt.py +++ b/examples/other/cube_qt.py @@ -1,4 +1,7 @@ """ +Simple Cube with Qt +=================== + Example showing a single geometric cube. """ # run_example = false diff --git a/examples/cube_wx.py b/examples/other/cube_wx.py similarity index 95% rename from examples/cube_wx.py rename to examples/other/cube_wx.py index 88bf40f3e..40cf5574e 100644 --- a/examples/cube_wx.py +++ b/examples/other/cube_wx.py @@ -1,4 +1,7 @@ """ +Simple Cube with WX +=================== + Example showing a single geometric cube. """ # run_example = false diff --git a/examples/integration_qt.py b/examples/other/integration_qt.py similarity index 96% rename from examples/integration_qt.py rename to examples/other/integration_qt.py index a9fc8e27a..712d3645b 100644 --- a/examples/integration_qt.py +++ b/examples/other/integration_qt.py @@ -1,5 +1,10 @@ # run_example = false +""" +Integrate pygfx in Qt +===================== +""" + import random from PySide6 import QtWidgets diff --git a/examples/light_view.py b/examples/other/light_view.py similarity index 99% rename from examples/light_view.py rename to examples/other/light_view.py index 20f130de3..2a346be64 100644 --- a/examples/light_view.py +++ b/examples/other/light_view.py @@ -1,4 +1,7 @@ """ +Light Effects +============= + Lighting effect demonstration examples with adjustable parameters """ diff --git a/examples/post_processing1.py b/examples/other/post_processing1.py similarity index 98% rename from examples/post_processing1.py rename to examples/other/post_processing1.py index 0cf915b14..b62d43360 100644 --- a/examples/post_processing1.py +++ b/examples/other/post_processing1.py @@ -1,4 +1,8 @@ """ +Full-Screen Post Processing 1 +============================= + + Example full-screen post processing. The idea is to render a scene to a texture, and then rendering diff --git a/examples/post_processing2.py b/examples/other/post_processing2.py similarity index 96% rename from examples/post_processing2.py rename to examples/other/post_processing2.py index 71faaa15a..ba9a9cc84 100644 --- a/examples/post_processing2.py +++ b/examples/other/post_processing2.py @@ -1,4 +1,7 @@ """ +Fullscreen Postprocessing 2 +=========================== + Example showing post-processing effects by modifying the flusher object. This example is a placeholder for how post-processing *could* work if diff --git a/examples/sponza_scene.py b/examples/other/sponza_scene.py similarity index 98% rename from examples/sponza_scene.py rename to examples/other/sponza_scene.py index 5ec110626..11fdc9a64 100644 --- a/examples/sponza_scene.py +++ b/examples/other/sponza_scene.py @@ -1,4 +1,7 @@ """ +Sponza Scene +============ + This example shows how to load the Sponza scene. """ # run_example = false - because it depends on external files diff --git a/examples/text_with_qt.py b/examples/other/text_with_qt.py similarity index 99% rename from examples/text_with_qt.py rename to examples/other/text_with_qt.py index 73e2945ce..ad6f28dbc 100644 --- a/examples/text_with_qt.py +++ b/examples/other/text_with_qt.py @@ -1,5 +1,10 @@ # run_example = false +""" +Render Text with Qt +=================== +""" + import numpy as np import pygfx as gfx diff --git a/examples/two_canvases.py b/examples/other/two_canvases.py similarity index 98% rename from examples/two_canvases.py rename to examples/other/two_canvases.py index 3d2783b97..89498ab5f 100644 --- a/examples/two_canvases.py +++ b/examples/other/two_canvases.py @@ -1,4 +1,7 @@ """ +Two Canvases +============ + Example demonstrating rendering the same scene into two different canvases. """ diff --git a/examples/screenshots/validate_box.png b/examples/screenshots/plot_validate_box.png similarity index 100% rename from examples/screenshots/validate_box.png rename to examples/screenshots/plot_validate_box.png diff --git a/examples/screenshots/validate_color.png b/examples/screenshots/plot_validate_color.png similarity index 100% rename from examples/screenshots/validate_color.png rename to examples/screenshots/plot_validate_color.png diff --git a/examples/screenshots/validate_culling.png b/examples/screenshots/plot_validate_culling.png similarity index 100% rename from examples/screenshots/validate_culling.png rename to examples/screenshots/plot_validate_culling.png diff --git a/examples/screenshots/validate_depth_clipping.png b/examples/screenshots/plot_validate_depth_clipping.png similarity index 100% rename from examples/screenshots/validate_depth_clipping.png rename to examples/screenshots/plot_validate_depth_clipping.png diff --git a/examples/screenshots/validate_helpers1.png b/examples/screenshots/plot_validate_helpers1.png similarity index 100% rename from examples/screenshots/validate_helpers1.png rename to examples/screenshots/plot_validate_helpers1.png diff --git a/examples/screenshots/validate_helpers2.png b/examples/screenshots/plot_validate_helpers2.png similarity index 100% rename from examples/screenshots/validate_helpers2.png rename to examples/screenshots/plot_validate_helpers2.png diff --git a/examples/screenshots/validate_image1.png b/examples/screenshots/plot_validate_image1.png similarity index 100% rename from examples/screenshots/validate_image1.png rename to examples/screenshots/plot_validate_image1.png diff --git a/examples/screenshots/validate_image2.png b/examples/screenshots/plot_validate_image2.png similarity index 100% rename from examples/screenshots/validate_image2.png rename to examples/screenshots/plot_validate_image2.png diff --git a/examples/screenshots/validate_image_colormap.png b/examples/screenshots/plot_validate_image_colormap.png similarity index 100% rename from examples/screenshots/validate_image_colormap.png rename to examples/screenshots/plot_validate_image_colormap.png diff --git a/examples/screenshots/validate_light_shadow.png b/examples/screenshots/plot_validate_light_shadow.png similarity index 100% rename from examples/screenshots/validate_light_shadow.png rename to examples/screenshots/plot_validate_light_shadow.png diff --git a/examples/screenshots/validate_mesh_colormap.png b/examples/screenshots/plot_validate_mesh_colormap.png similarity index 100% rename from examples/screenshots/validate_mesh_colormap.png rename to examples/screenshots/plot_validate_mesh_colormap.png diff --git a/examples/screenshots/validate_ndc.png b/examples/screenshots/plot_validate_ndc.png similarity index 100% rename from examples/screenshots/validate_ndc.png rename to examples/screenshots/plot_validate_ndc.png diff --git a/examples/screenshots/validate_volume.png b/examples/screenshots/plot_validate_volume.png similarity index 100% rename from examples/screenshots/validate_volume.png rename to examples/screenshots/plot_validate_volume.png diff --git a/examples/show_stl.py b/examples/show_stl.py deleted file mode 100644 index d5635e0a3..000000000 --- a/examples/show_stl.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Demonstrates show utility with an STL file -""" -from pathlib import Path - -import trimesh - -import pygfx as gfx - - -TEAPOT = Path(__file__).parent / "models" / "teapot.stl" - -teapot = trimesh.load(TEAPOT) - -mesh = gfx.Mesh( - gfx.trimesh_geometry(teapot), - gfx.MeshPhongMaterial(), -) - -if __name__ == "__main__": - gfx.show(mesh, up=gfx.linalg.Vector3(0, 0, 1)) diff --git a/examples/tests/test_examples.py b/examples/tests/test_examples.py index eaaffba4e..19e06001c 100644 --- a/examples/tests/test_examples.py +++ b/examples/tests/test_examples.py @@ -24,20 +24,22 @@ # run all tests unless they opt-out -examples_to_run = find_examples( - negative_query="# run_example = false", return_stems=True -) +examples_to_run = find_examples(negative_query="# run_example = false") # only test output of examples that opt-in -examples_to_test = find_examples(query="# test_example = true", return_stems=True) +examples_to_test = find_examples(query="# test_example = true") -@pytest.mark.parametrize("module", examples_to_run) +@pytest.mark.parametrize("module", examples_to_run, ids=lambda x: x.stem) def test_examples_run(module, force_offscreen, disable_call_later_after_run): """Run every example marked to see if they can run without error.""" # use runpy so the module is not actually imported (and can be gc'd) # but also to be able to run the code in the __main__ block - runpy.run_module(f"examples.{module}", run_name="__main__") + + # (relative) module name from project root + module_name = module.relative_to(ROOT).with_suffix("").as_posix().replace("/", ".") + + runpy.run_module(module_name, run_name="__main__") @pytest.fixture @@ -95,14 +97,16 @@ def mock_time(): yield -@pytest.mark.parametrize("module", examples_to_test) +@pytest.mark.parametrize("module", examples_to_test, ids=lambda x: x.stem) def test_examples_screenshots( module, pytestconfig, force_offscreen, mock_time, request ): """Run every example marked for testing.""" + # (relative) module name from project root + module_name = module.relative_to(ROOT).with_suffix("").as_posix().replace("/", ".") + # import the example module - module_name = f"examples.{module}" example = importlib.import_module(module_name) # ensure it is unloaded after the test @@ -127,7 +131,7 @@ def unload_module(): pytest.skip("screenshot comparisons are only done when using lavapipe") # regenerate screenshot if requested - screenshot_path = screenshots_dir / f"{module}.png" + screenshot_path = screenshots_dir / f"{module.stem}.png" if pytestconfig.getoption("regenerate_screenshots"): iio.imwrite(screenshot_path, img) @@ -138,9 +142,9 @@ def unload_module(): stored_img = iio.imread(screenshot_path) # assert similarity is_similar = np.allclose(img, stored_img, atol=1) - update_diffs(module, is_similar, img, stored_img) + update_diffs(module.stem, is_similar, img, stored_img) assert is_similar, ( - f"rendered image for example {module} changed, see " + f"rendered image for example {module.stem} changed, see " f"the {diffs_dir.relative_to(ROOT).as_posix()} folder" " for visual diffs (you can download this folder from" " CI build artifacts as well)" diff --git a/examples/tests/testutils.py b/examples/tests/testutils.py index 06d3eb0f8..1f452e007 100644 --- a/examples/tests/testutils.py +++ b/examples/tests/testutils.py @@ -5,13 +5,17 @@ from pathlib import Path import subprocess import sys +from itertools import chain -ROOT = Path(__file__).parent.parent.parent # repo root +ROOT = Path(__file__).parents[2] # repo root examples_dir = ROOT / "examples" screenshots_dir = examples_dir / "screenshots" diffs_dir = screenshots_dir / "diffs" +# examples live in themed sub-folders +example_globs = ["*.py", "introductory/*.py", "feature_demo/*.py", "validation/*.py"] + def wgpu_backend_endswith(query): """ @@ -40,7 +44,7 @@ def wgpu_backend_endswith(query): def find_examples(query=None, negative_query=None, return_stems=False): result = [] - for example_path in examples_dir.glob("*.py"): + for example_path in chain(*(examples_dir.glob(x) for x in example_globs)): example_code = example_path.read_text() query_match = query is None or query in example_code negative_query_match = ( diff --git a/examples/validation/README.rst b/examples/validation/README.rst new file mode 100644 index 000000000..d3a206c11 --- /dev/null +++ b/examples/validation/README.rst @@ -0,0 +1 @@ +.. rubric:: Validation Examples diff --git a/examples/validate_box.py b/examples/validation/plot_validate_box.py similarity index 91% rename from examples/validate_box.py rename to examples/validation/plot_validate_box.py index 05f8a7a66..03141665c 100644 --- a/examples/validate_box.py +++ b/examples/validation/plot_validate_box.py @@ -1,7 +1,11 @@ """ +Box Geometry +============ + Example showing the box geometry. """ # test_example = true +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/validate_color.py b/examples/validation/plot_validate_color.py similarity index 95% rename from examples/validate_color.py rename to examples/validation/plot_validate_color.py index 5e7270a45..1193a666c 100644 --- a/examples/validate_color.py +++ b/examples/validation/plot_validate_color.py @@ -1,8 +1,12 @@ """ +Reference Color +=============== + This example draws squares of reference colors. These can be compared to similar output from e.g. Matplotlib. """ # test_example = true +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/validate_culling.py b/examples/validation/plot_validate_culling.py similarity index 97% rename from examples/validate_culling.py rename to examples/validation/plot_validate_culling.py index ed1dd545d..706f31682 100644 --- a/examples/validate_culling.py +++ b/examples/validation/plot_validate_culling.py @@ -1,4 +1,7 @@ """ +Culling +======= + Example test to validate winding and culling. * The top red knot should look normal and well lit. @@ -7,6 +10,7 @@ """ # test_example = true +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/validate_depth_clipping.py b/examples/validation/plot_validate_depth_clipping.py similarity index 96% rename from examples/validate_depth_clipping.py rename to examples/validation/plot_validate_depth_clipping.py index fc8ce08eb..4dd0adf11 100644 --- a/examples/validate_depth_clipping.py +++ b/examples/validation/plot_validate_depth_clipping.py @@ -1,4 +1,7 @@ """ +Depth Clipping +============== + Example (and test) for camera depth clipping planes. This draws four rectangles near the near and far clipping planes. @@ -34,6 +37,8 @@ # %% Create four planes near the z-clipping planes +# sphinx_gallery_pygfx_render = True + geometry = gfx.plane_geometry(1, 1) green_material = gfx.MeshBasicMaterial(color=(0, 0.8, 0.2, 1)) greener_material = gfx.MeshBasicMaterial(color=(0, 1, 0, 1)) diff --git a/examples/validate_helpers1.py b/examples/validation/plot_validate_helpers1.py similarity index 89% rename from examples/validate_helpers1.py rename to examples/validation/plot_validate_helpers1.py index b67274df5..13b00a8fd 100644 --- a/examples/validate_helpers1.py +++ b/examples/validation/plot_validate_helpers1.py @@ -1,4 +1,7 @@ """ +Axes Helper 1 +============= + Example showing the axes helper. * The axes must be centered in the middle. @@ -7,6 +10,7 @@ * The blue axes (z) is not visible. """ # test_example = true +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/validate_helpers2.py b/examples/validation/plot_validate_helpers2.py similarity index 93% rename from examples/validate_helpers2.py rename to examples/validation/plot_validate_helpers2.py index a5d541357..8504c4dd7 100644 --- a/examples/validate_helpers2.py +++ b/examples/validation/plot_validate_helpers2.py @@ -1,4 +1,7 @@ """ +Axis Helper 2 +============= + Example showing the axes and grid helpers with a perspective camera. * The grid spans the x-z plane (red and blue axis). @@ -6,6 +9,7 @@ * The red box fits snugly around the grid. """ # test_example = true +# sphinx_gallery_pygfx_render = True from wgpu.gui.auto import WgpuCanvas, run import pygfx as gfx diff --git a/examples/validate_image1.py b/examples/validation/plot_validate_image1.py similarity index 93% rename from examples/validate_image1.py rename to examples/validation/plot_validate_image1.py index d88e7f409..f7fbd3d3e 100644 --- a/examples/validate_image1.py +++ b/examples/validation/plot_validate_image1.py @@ -1,4 +1,7 @@ """ +Image on Plane Geometry 1 +========================= + Show an image using a plane geometry. For historic reasons, image data (usually) has the first rows representing the top of the image. But the plane gemeometry is such that it is reversed again. @@ -8,6 +11,7 @@ * The darker corner is in the top left. """ # test_example = true +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/validate_image2.py b/examples/validation/plot_validate_image2.py similarity index 93% rename from examples/validate_image2.py rename to examples/validation/plot_validate_image2.py index 5e063d46a..379e31892 100644 --- a/examples/validate_image2.py +++ b/examples/validation/plot_validate_image2.py @@ -1,4 +1,7 @@ """ +Image Material +============== + Show an image displayed the correct way. * The green dots should be at the corners that are darker/brighter. @@ -6,6 +9,7 @@ * The darker corner is in the bottom left. """ # test_example = true +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/validate_image_colormap.py b/examples/validation/plot_validate_image_colormap.py similarity index 92% rename from examples/validate_image_colormap.py rename to examples/validation/plot_validate_image_colormap.py index 6682fbd35..d7dfad345 100644 --- a/examples/validate_image_colormap.py +++ b/examples/validation/plot_validate_image_colormap.py @@ -1,10 +1,14 @@ """ +Simple Colormap +=============== + Show an image with a simple colormap. * You should see a square image with 3 equally sized vertical bands. * The bands should be red, green, and blue. """ # test_example = true +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/validate_light_shadow.py b/examples/validation/plot_validate_light_shadow.py similarity index 96% rename from examples/validate_light_shadow.py rename to examples/validation/plot_validate_light_shadow.py index b0a0094bf..37d1d615f 100644 --- a/examples/validate_light_shadow.py +++ b/examples/validation/plot_validate_light_shadow.py @@ -1,9 +1,13 @@ """ +Light and Shadow +================ + This example combines MeshPhongMaterial and MeshStandardMaterial with PointLight, AmbientLight, SpotLight and DirectionalLight to check that all combinations are working properly. """ # test_example = true +# sphinx_gallery_pygfx_render = True import math diff --git a/examples/validate_mesh_colormap.py b/examples/validation/plot_validate_mesh_colormap.py similarity index 97% rename from examples/validate_mesh_colormap.py rename to examples/validation/plot_validate_mesh_colormap.py index 1696ce65a..b42e562b0 100644 --- a/examples/validate_mesh_colormap.py +++ b/examples/validation/plot_validate_mesh_colormap.py @@ -1,10 +1,14 @@ """ +Mesh Colormaps +============== + Show meshes with 1D, 2D, and 3D colormaps, and per-vertex colors too. * You should see four cylinders with block-pattern colors. * The right-most cylinder is smoothly colored matching its normal. """ # test_example = true +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/examples/validate_ndc.py b/examples/validation/plot_validate_ndc.py similarity index 97% rename from examples/validate_ndc.py rename to examples/validation/plot_validate_ndc.py index 2b0e59659..8c1a7c134 100644 --- a/examples/validate_ndc.py +++ b/examples/validation/plot_validate_ndc.py @@ -1,4 +1,7 @@ """ +NDC Coordinates +=============== + Example (and test) for the NDC coordinates. Draws a square that falls partly out of visible range. * The scene should show a band from the bottom left to the upper right. @@ -81,6 +84,8 @@ def code_fragment(self): # %% Setup scene +# sphinx_gallery_pygfx_render = True + canvas = WgpuCanvas() renderer = gfx.WgpuRenderer(canvas) diff --git a/examples/validate_volume.py b/examples/validation/plot_validate_volume.py similarity index 95% rename from examples/validate_volume.py rename to examples/validation/plot_validate_volume.py index 3f184d764..459a455f4 100644 --- a/examples/validate_volume.py +++ b/examples/validation/plot_validate_volume.py @@ -1,10 +1,14 @@ """ +Volume and Volume Slice Rendering +================================= + Render a volume and volume slices. You should see: * On the left: a raycasted volume fit snugly inside a red box. * On the right: three orthogonal slices inside - and through the middle of - a green box. * The volume has its corners darker and its very center is brighter. """ # test_example = true +# sphinx_gallery_pygfx_render = True import numpy as np from wgpu.gui.auto import WgpuCanvas, run diff --git a/pygfx/__init__.py b/pygfx/__init__.py index 02f043359..9eafd76fa 100644 --- a/pygfx/__init__.py +++ b/pygfx/__init__.py @@ -23,6 +23,16 @@ version_info = tuple(map(int, __version__.split("."))) +def _get_sg_image_scraper(): + import sphinx_gallery.scrapers + from .utils.gallery_scraper import pygfx_scraper + + # add webp as supported extension + sphinx_gallery.scrapers._KNOWN_IMG_EXTS += ("webp",) + + return pygfx_scraper + + # Elements of this library are derived from three.js, original license # at time of writing copied here: # --- diff --git a/pygfx/utils/gallery_scraper.py b/pygfx/utils/gallery_scraper.py new file mode 100644 index 000000000..0e2a318ac --- /dev/null +++ b/pygfx/utils/gallery_scraper.py @@ -0,0 +1,102 @@ +from pathlib import Path + +import imageio.v3 as iio +from sphinx_gallery.scrapers import figure_rst +from wgpu.gui import WgpuCanvasBase + +from ..renderers import Renderer +from .show import Display + +# The scraper's default configuration. An example code-block +# may overwrite these values by setting comments of the form +# +# # sphinx_gallery_pygfx_ = +# +# inside the code block. These comments will not be shown in the generated +# gallery example and will be reset after each code block. +default_config = { + "render": False, # if True, render an image + "animate": False, # if True, render a GIF (TODO) + "target_name": "renderer", # the display to use + # GIF settings + "duration": 3, # how many seconds to record + "loop": 0, # loop forever +} + + +def pygfx_scraper(block, block_vars, gallery_conf, **kwargs): + """Scrape pygfx images and animations + + Parameters + ---------- + block : tuple + A tuple containing the (label, content, line_number) of the block. + block_vars : dict + Dict of block variables. + gallery_conf : dict + Contains the configuration of Sphinx-Gallery + **kwargs : dict + Additional keyword arguments to pass to + :meth:`~matplotlib.figure.Figure.savefig`, e.g. ``format='svg'``. + The ``format`` kwarg in particular is used to set the file extension + of the output file (currently only 'png', 'jpg', and 'svg' are + supported). + + Returns + ------- + rst : str + The ReSTructuredText that will be rendered to HTML containing + the images. This is often produced by :func:`figure_rst`. + """ + + # parse block-level config + scraper_config = default_config.copy() + config_prefix = "# sphinx_gallery_pygfx_" + for line in block[1].split("\n"): + if not line.startswith(config_prefix): + continue + + name, value = line[len(config_prefix) :].split(" = ") + scraper_config[name] = eval(value) + + if not scraper_config["render"] and not scraper_config["animate"]: + return "" # nothing to do + + target = block_vars["example_globals"][scraper_config["target_name"]] + if isinstance(target, Display): + canvas = target.canvas + elif isinstance(target, Renderer): + canvas = target.target + elif isinstance(target, WgpuCanvasBase): + canvas = target + else: + raise ValueError("`target` must be a Display, Renderer, or Canvas.") + + images = [] + + if scraper_config["render"]: + path_generator = block_vars["image_path_iterator"] + img_path = next(path_generator) + iio.imwrite(img_path, canvas.draw()) + images.append(img_path) + + if scraper_config["animate"]: + frames = [] + + # by default videos are rendered at ~ 30 FPS + n_frames = scraper_config["duration"] * 30 + for _ in range(n_frames): + frames.append(canvas.draw()) + + path_generator = block_vars["image_path_iterator"] + img_path = Path(next(path_generator)).with_suffix(".webp") + iio.imwrite( + img_path, + frames, + duration=33, + loop=scraper_config["loop"], + lossless=True, + ) + images.append(img_path) + + return figure_rst(images, gallery_conf["src_dir"]) diff --git a/setup.cfg b/setup.cfg index 90a56ddfc..66cb43f7a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [flake8] max_line_length = 88 extend-ignore = E203, E231, E501, F541, D -exclude = build,dist,*.egg-info,.venv +exclude = build,dist,*.egg-info,.venv,docs/_gallery per-file-ignores = pygfx/linalg/*: F821 diff --git a/setup.py b/setup.py index 65db91c05..f56d339aa 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,15 @@ "scikit-image", "trimesh", ], + "docs": [ + "sphinx", + "numpy", + "wgpu", + "jinja2", + "sphinx-gallery", + "imageio", + "matplotlib", + ], }