From 48a31bd4bb54492213c665d2a1a2f074492907e6 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Thu, 18 Jun 2026 21:39:37 -0500 Subject: [PATCH 1/4] De-indent docstrings code examples Necessary for proper formatting in Zensical docs...for now.. --- src/scenex/adaptors/__init__.py | 28 +++---- src/scenex/adaptors/_auto.py | 15 ++-- src/scenex/app/__init__.py | 26 +++--- src/scenex/app/_auto.py | 36 ++++---- src/scenex/app/events/_events.py | 64 +++++++------- src/scenex/imgui/_controls.py | 26 +++--- src/scenex/model/__init__.py | 24 +++--- src/scenex/model/_canvas.py | 9 +- src/scenex/model/_color.py | 23 +++--- src/scenex/model/_nodes/__init__.py | 36 ++++---- src/scenex/model/_nodes/camera.py | 124 +++++++++++++++------------- src/scenex/model/_nodes/image.py | 24 +++--- src/scenex/model/_nodes/line.py | 33 ++++---- src/scenex/model/_nodes/mesh.py | 48 +++++------ src/scenex/model/_nodes/points.py | 50 +++++------ src/scenex/model/_nodes/scene.py | 44 +++++----- src/scenex/model/_nodes/text.py | 3 +- src/scenex/model/_nodes/volume.py | 20 +++-- src/scenex/model/_transform.py | 40 +++++---- src/scenex/model/_view.py | 42 ++++++---- 20 files changed, 387 insertions(+), 328 deletions(-) diff --git a/src/scenex/adaptors/__init__.py b/src/scenex/adaptors/__init__.py index 41a337c8..d3519644 100644 --- a/src/scenex/adaptors/__init__.py +++ b/src/scenex/adaptors/__init__.py @@ -38,25 +38,25 @@ Usage ----- Adaptors are NOT intended for manual instantiation; they are instead created -automatically by `scenex.show()`:: +automatically by `scenex.show()`: - >>> import scenex as snx - >>> import numpy as np +>>> import scenex as snx +>>> import numpy as np - >>> # Create model - >>> my_array = np.random.rand(100, 100).astype(np.float32) - >>> scene = snx.Scene(children=[snx.Image(data=my_array)]) +>>> # Create model +>>> my_array = np.random.rand(100, 100).astype(np.float32) +>>> scene = snx.Scene(children=[snx.Image(data=my_array)]) - >>> # This creates adaptors automatically - >>> snx.show(scene) - Canvas(...) - >>> snx.run() +>>> # This creates adaptors automatically +>>> snx.show(scene) +Canvas(...) +>>> snx.run() -To select a particular backend, use `scenex.use()`:: +To select a particular backend, use `scenex.use()`: - >>> snx.use("pygfx") # doctest: +SKIP - >>> snx.show(scene) - Canvas(...) +>>> snx.use("pygfx") # doctest: +SKIP +>>> snx.show(scene) +Canvas(...) See Also -------- diff --git a/src/scenex/adaptors/_auto.py b/src/scenex/adaptors/_auto.py index 4f8ddb56..60f71fd4 100644 --- a/src/scenex/adaptors/_auto.py +++ b/src/scenex/adaptors/_auto.py @@ -100,16 +100,19 @@ def use(backend: KnownBackend | None = None) -> None: Examples -------- Use pygfx backend explicitly: - >>> import scenex as snx - >>> snx.use("pygfx") # doctest: +SKIP - >>> canvas = snx.show(snx.View()) + + >>> import scenex as snx + >>> snx.use("pygfx") # doctest: +SKIP + >>> canvas = snx.show(snx.View()) Use vispy backend: - >>> snx.use("vispy") # doctest: +SKIP - >>> canvas = snx.show(snx.Scene()) + + >>> snx.use("vispy") # doctest: +SKIP + >>> canvas = snx.show(snx.Scene()) Reset to auto-detection: - >>> snx.use(None) + + >>> snx.use(None) Notes ----- diff --git a/src/scenex/app/__init__.py b/src/scenex/app/__init__.py index d2f9d865..1320d156 100644 --- a/src/scenex/app/__init__.py +++ b/src/scenex/app/__init__.py @@ -24,26 +24,26 @@ Usage ----- -The app is typically created automatically by `scenex.show()` and/or `scenex.run()`:: +The app is typically created automatically by `scenex.show()` and/or `scenex.run()`: - >>> import scenex as snx - >>> import numpy as np +>>> import scenex as snx +>>> import numpy as np - >>> # Create a scenex scene - >>> my_array = np.random.rand(100, 100).astype(np.float32) - >>> my_scene = snx.Scene(children=[snx.Image(data=my_array)]) +>>> # Create a scenex scene +>>> my_array = np.random.rand(100, 100).astype(np.float32) +>>> my_scene = snx.Scene(children=[snx.Image(data=my_array)]) - >>> # Showing the scene creates the app if needed - >>> snx.show(my_scene) - Canvas(...) - >>> snx.run() # Starts the event loop +>>> # Showing the scene creates the app if needed +>>> snx.show(my_scene) +Canvas(...) +>>> snx.run() # Starts the event loop But it CAN be useful to access the app instance directly. For example, it can be useful -to ask the app to process any pending events:: +to ask the app to process any pending events: - >>> from scenex.app import app +>>> from scenex.app import app - >>> app().process_events() +>>> app().process_events() Notes ----- diff --git a/src/scenex/app/_auto.py b/src/scenex/app/_auto.py index ca668318..d1f12b05 100644 --- a/src/scenex/app/_auto.py +++ b/src/scenex/app/_auto.py @@ -93,12 +93,14 @@ class CursorType(Enum): Examples -------- Set a crosshair cursor during drawing mode: - >>> import scenex as snx - >>> canvas = snx.Canvas() - >>> snx.set_cursor(canvas, CursorType.CROSS) # doctest: +SKIP + + >>> import scenex as snx + >>> canvas = snx.Canvas() + >>> snx.set_cursor(canvas, CursorType.CROSS) # doctest: +SKIP Restore default cursor after operation: - >>> snx.set_cursor(canvas, CursorType.DEFAULT) # doctest: +SKIP + + >>> snx.set_cursor(canvas, CursorType.DEFAULT) # doctest: +SKIP See Also -------- @@ -451,12 +453,13 @@ def ensure_main_thread(func: Callable[P, T]) -> Callable[P, Future[T]]: Examples -------- Ensure a GUI operation runs on the main thread: - >>> @ensure_main_thread - ... def update_widget(value: int) -> None: - ... # Update some GUI widget with the given value - ... pass - >>> future = update_widget(42) # Returns immediately with Future - >>> result = future.result() # Block until completion if needed + + >>> @ensure_main_thread + ... def update_widget(value: int) -> None: + ... # Update some GUI widget with the given value + ... pass + >>> future = update_widget(42) # Returns immediately with Future + >>> result = future.result() # Block until completion if needed See Also -------- @@ -499,12 +502,14 @@ def determine_app() -> GuiFrontend: Examples -------- Let the function auto-detect the backend: - >>> backend = determine_app() + + >>> backend = determine_app() Force a specific backend via environment variable: - >>> import os - >>> os.environ["SCENEX_APP_BACKEND"] = "qt" # doctest: +SKIP - >>> backend = determine_app() # Will use Qt + + >>> import os + >>> os.environ["SCENEX_APP_BACKEND"] = "qt" # doctest: +SKIP + >>> backend = determine_app() # Will use Qt See Also -------- @@ -562,7 +567,8 @@ def app() -> App: Examples -------- Get the app and run the event loop: - >>> app().run() + + >>> app().run() See Also -------- diff --git a/src/scenex/app/events/_events.py b/src/scenex/app/events/_events.py index 5324304a..ea6f9b8c 100644 --- a/src/scenex/app/events/_events.py +++ b/src/scenex/app/events/_events.py @@ -42,23 +42,26 @@ class MouseButton(IntFlag): Examples -------- Check if left button is pressed: - >>> event = MousePressEvent( - ... pos=(100, 150), - ... buttons=MouseButton.LEFT | MouseButton.RIGHT, - ... ) - >>> if event.buttons & MouseButton.LEFT: - ... print("Left button is down") - Left button is down + + >>> event = MousePressEvent( + ... pos=(100, 150), + ... buttons=MouseButton.LEFT | MouseButton.RIGHT, + ... ) + >>> if event.buttons & MouseButton.LEFT: + ... print("Left button is down") + Left button is down Check for specific button combination: - >>> if event.buttons == (MouseButton.LEFT | MouseButton.RIGHT): - ... print("Both left and right buttons pressed") - Both left and right buttons pressed + + >>> if event.buttons == (MouseButton.LEFT | MouseButton.RIGHT): + ... print("Both left and right buttons pressed") + Both left and right buttons pressed Check if any button is pressed: - >>> if event.buttons != MouseButton.NONE: - ... print("Some button is pressed") - Some button is pressed + + >>> if event.buttons != MouseButton.NONE: + ... print("Some button is pressed") + Some button is pressed """ NONE = 0 @@ -99,23 +102,24 @@ class Ray(NamedTuple): Examples -------- Find all intersections with a scene: - >>> import numpy as np - >>> import scenex as snx - >>> view = snx.View( - ... scene=snx.Scene( - ... children=[ - ... snx.Image(data=np.random.rand(100, 100)), - ... snx.Points( - ... vertices=np.asarray([[0, 0, 0], [1, 1, 0]]), - ... size=5, - ... edge_width=0, - ... ), - ... ] - ... ) - ... ) - >>> ray = Ray(origin=(1, 1, 10), direction=(0, 0, -1), source=view) - >>> ray.intersections(view.scene) - [(Points(...), 7.5), (Image(...), 10.0)] + + >>> import numpy as np + >>> import scenex as snx + >>> view = snx.View( + ... scene=snx.Scene( + ... children=[ + ... snx.Image(data=np.random.rand(100, 100)), + ... snx.Points( + ... vertices=np.asarray([[0, 0, 0], [1, 1, 0]]), + ... size=5, + ... edge_width=0, + ... ), + ... ] + ... ) + ... ) + >>> ray = Ray(origin=(1, 1, 10), direction=(0, 0, -1), source=view) + >>> ray.intersections(view.scene) + [(Points(...), 7.5), (Image(...), 10.0)] See Also -------- diff --git a/src/scenex/imgui/_controls.py b/src/scenex/imgui/_controls.py index 5da672d6..b72968fa 100644 --- a/src/scenex/imgui/_controls.py +++ b/src/scenex/imgui/_controls.py @@ -90,19 +90,19 @@ def add_imgui_controls(view: View) -> None: Examples -------- - Basic usage with an image:: - - >>> import scenex as snx - >>> import numpy as np - >>> from scenex.imgui import add_imgui_controls - - >>> my_array = np.random.rand(100, 100).astype(np.float32) - >>> image = snx.Image(data=my_array) - >>> view = snx.View(scene=snx.Scene(children=[image])) - >>> snx.show(view) - Canvas(...) - >>> add_imgui_controls(view) - >>> snx.run() + Basic usage with an image: + + >>> import scenex as snx + >>> import numpy as np + >>> from scenex.imgui import add_imgui_controls + + >>> my_array = np.random.rand(100, 100).astype(np.float32) + >>> image = snx.Image(data=my_array) + >>> view = snx.View(scene=snx.Scene(children=[image])) + >>> snx.show(view) + Canvas(...) + >>> add_imgui_controls(view) + >>> snx.run() The control panel will show sections for: - View properties (camera, layout, etc.) diff --git a/src/scenex/model/__init__.py b/src/scenex/model/__init__.py index d7ee5fe4..0d42b15e 100644 --- a/src/scenex/model/__init__.py +++ b/src/scenex/model/__init__.py @@ -49,23 +49,23 @@ Examples -------- -Build a simple scene:: +Build a simple scene: - >>> from scenex.model import Scene, Image, Points - >>> import numpy as np +>>> from scenex.model import Scene, Image, Points +>>> import numpy as np - >>> scene = Scene( - ... children=[ - ... Image(data=np.random.rand(100, 100)), - ... Points(vertices=np.random.rand(50, 2) * 100), - ... ] - ... ) +>>> scene = Scene( +... children=[ +... Image(data=np.random.rand(100, 100)), +... Points(vertices=np.random.rand(50, 2) * 100), +... ] +... ) -Create a view with interactive camera:: +Create a view with interactive camera: - >>> from scenex.model import View, Camera, PanZoom +>>> from scenex.model import View, Camera, PanZoom - >>> view = View(scene=scene, camera=Camera(controller=PanZoom(), interactive=True)) +>>> view = View(scene=scene, camera=Camera(controller=PanZoom(), interactive=True)) Notes ----- diff --git a/src/scenex/model/_canvas.py b/src/scenex/model/_canvas.py index 1f81764f..0d88e05c 100644 --- a/src/scenex/model/_canvas.py +++ b/src/scenex/model/_canvas.py @@ -52,13 +52,16 @@ class Canvas(EventedBase): Examples -------- Create a simple canvas with default settings: - >>> canvas = Canvas() + + >>> canvas = Canvas() Create a canvas with custom size and title: - >>> canvas = Canvas(width=800, height=600, title="My Visualization") + + >>> canvas = Canvas(width=800, height=600, title="My Visualization") Create a canvas with multiple views side-by-side: - >>> canvas = Canvas(width=800, height=400, views=[View(), View()]) + + >>> canvas = Canvas(width=800, height=400, views=[View(), View()]) """ width: int = Field(default=600, description="The width of the canvas in pixels") diff --git a/src/scenex/model/_color.py b/src/scenex/model/_color.py index af91bedb..d08e455a 100644 --- a/src/scenex/model/_color.py +++ b/src/scenex/model/_color.py @@ -36,9 +36,10 @@ class UniformColor(ColorModel): Examples -------- Uniform coloring: - >>> from cmap import Color - >>> from scenex import UniformColor - >>> model = UniformColor(color=Color("red")) + + >>> from cmap import Color + >>> from scenex import UniformColor + >>> model = UniformColor(color=Color("red")) """ color: Color = Field(default_factory=lambda: Color("white")) @@ -54,9 +55,10 @@ class FaceColors(ColorModel): Examples -------- Per-face coloring: - >>> from cmap import Color - >>> from scenex import FaceColors - >>> model = FaceColors(color=[Color("red"), Color("blue"), Color("green")]) + + >>> from cmap import Color + >>> from scenex import FaceColors + >>> model = FaceColors(color=[Color("red"), Color("blue"), Color("green")]) """ color: Sequence[Color] @@ -72,11 +74,10 @@ class VertexColors(ColorModel): Examples -------- Per-vertex coloring: - >>> from cmap import Color - >>> from scenex import VertexColors - >>> model = VertexColors( - ... color=[Color("yellow"), Color("purple"), Color("cyan")] - ... ) + + >>> from cmap import Color + >>> from scenex import VertexColors + >>> model = VertexColors(color=[Color("yellow"), Color("purple"), Color("cyan")]) """ color: Sequence[Color] diff --git a/src/scenex/model/_nodes/__init__.py b/src/scenex/model/_nodes/__init__.py index 85b060d9..f7276ad8 100644 --- a/src/scenex/model/_nodes/__init__.py +++ b/src/scenex/model/_nodes/__init__.py @@ -36,29 +36,29 @@ Examples -------- -Create a simple image node:: +Create a simple image node: - >>> import numpy as np - >>> from scenex.model._nodes import Image - >>> img = Image(data=np.random.rand(100, 100)) +>>> import numpy as np +>>> from scenex.model._nodes import Image +>>> img = Image(data=np.random.rand(100, 100)) -Create a hierarchy with transforms:: +Create a hierarchy with transforms: - >>> from scenex.model._nodes import Scene, Points - >>> from scenex.model import Transform +>>> from scenex.model._nodes import Scene, Points +>>> from scenex.model import Transform - >>> # Parent node with transform - >>> parent_points = Points( - ... vertices=np.random.rand(50, 3), - ... transform=Transform().translated((10, 0, 0)), - ... ) - >>> # Child node - >>> child_img = Image(data=np.random.rand(100, 100)) +>>> # Parent node with transform +>>> parent_points = Points( +... vertices=np.random.rand(50, 3), +... transform=Transform().translated((10, 0, 0)), +... ) +>>> # Child node +>>> child_img = Image(data=np.random.rand(100, 100)) - >>> # Add to scene - >>> scene = Scene() - >>> parent_points.parent = scene - >>> child_img.parent = parent_points +>>> # Add to scene +>>> scene = Scene() +>>> parent_points.parent = scene +>>> child_img.parent = parent_points See Also -------- diff --git a/src/scenex/model/_nodes/camera.py b/src/scenex/model/_nodes/camera.py index 6528db8b..c8c7f432 100644 --- a/src/scenex/model/_nodes/camera.py +++ b/src/scenex/model/_nodes/camera.py @@ -55,19 +55,23 @@ class Camera(Node): Examples -------- Create a camera with pan-zoom controller: - >>> camera = Camera(controller=PanZoom(), interactive=True) + + >>> camera = Camera(controller=PanZoom(), interactive=True) Create a camera with orbit controller: - >>> camera = Camera(controller=Orbit(center=(0, 0, 0)), interactive=True) + + >>> camera = Camera(controller=Orbit(center=(0, 0, 0)), interactive=True) Position a camera and point it at a target: - >>> camera = Camera() - >>> camera.transform = Transform().translated((10, 0, 0)) - >>> camera.look_at((0, 0, 0), up=(0, 0, 1)) + + >>> camera = Camera() + >>> camera.transform = Transform().translated((10, 0, 0)) + >>> camera.look_at((0, 0, 0), up=(0, 0, 1)) Create a perspective camera: - >>> from scenex.utils.projections import perspective - >>> camera = Camera(projection=perspective(fov=70, near=0.1, far=100)) + + >>> from scenex.utils.projections import perspective + >>> camera = Camera(projection=perspective(fov=70, near=0.1, far=100)) """ node_type: Literal["camera"] = "camera" @@ -190,10 +194,12 @@ class CameraController(BaseModel): Examples -------- Create a camera with pan/zoom controller: - >>> camera = Camera(controller=PanZoom(), interactive=True) + + >>> camera = Camera(controller=PanZoom(), interactive=True) Create a camera with orbit controller: - >>> camera = Camera(controller=Orbit(center=(0, 0, 0)), interactive=True) + + >>> camera = Camera(controller=Orbit(center=(0, 0, 0)), interactive=True) See Also -------- @@ -265,24 +271,27 @@ class PanZoom(CameraController): Examples -------- Standard 2D pan and zoom: - >>> camera = Camera(controller=PanZoom(), interactive=True) + + >>> camera = Camera(controller=PanZoom(), interactive=True) Lock horizontal movement for vertical scrolling only: - >>> camera = Camera(controller=PanZoom(lock_x=True), interactive=True) + + >>> camera = Camera(controller=PanZoom(lock_x=True), interactive=True) Create an image viewer with pan/zoom: - >>> import numpy as np - >>> import scenex as snx - >>> from scenex.utils import projections - >>> my_data = np.random.rand(512, 512).astype(np.float32) - >>> view = snx.View( - ... scene=snx.Scene(children=[snx.Image(data=my_data)]), - ... camera=snx.Camera( - ... controller=snx.PanZoom(), - ... interactive=True, - ... ), - ... ) - >>> projections.zoom_to_fit(view=view, type="orthographic") + + >>> import numpy as np + >>> import scenex as snx + >>> from scenex.utils import projections + >>> my_data = np.random.rand(512, 512).astype(np.float32) + >>> view = snx.View( + ... scene=snx.Scene(children=[snx.Image(data=my_data)]), + ... camera=snx.Camera( + ... controller=snx.PanZoom(), + ... interactive=True, + ... ), + ... ) + >>> projections.zoom_to_fit(view=view, type="orthographic") See Also -------- @@ -408,44 +417,45 @@ class Orbit(CameraController): Examples -------- Orbit around the origin: - >>> import scenex as snx - >>> from scenex.utils import projections - >>> # Create a perspective camera... - >>> camera = snx.Camera( - ... interactive=True, - ... projection=projections.perspective(fov=70, near=1, far=1000), - ... ) - >>> # ...positioned along the X axis... - >>> camera.transform = snx.Transform().translated((100, 0, 0)) - >>> # ...looking at the origin... - >>> camera.look_at((0, 0, 0), up=(0, 0, 1)) - >>> # ...that orbits around the origin - >>> camera.controller = snx.Orbit(center=(0, 0, 0)) + + >>> import scenex as snx + >>> from scenex.utils import projections + >>> # Create a perspective camera... + >>> camera = snx.Camera( + ... interactive=True, + ... projection=projections.perspective(fov=70, near=1, far=1000), + ... ) + >>> # ...positioned along the X axis... + >>> camera.transform = snx.Transform().translated((100, 0, 0)) + >>> # ...looking at the origin... + >>> camera.look_at((0, 0, 0), up=(0, 0, 1)) + >>> # ...that orbits around the origin + >>> camera.controller = snx.Orbit(center=(0, 0, 0)) Orbit around a data volume's center: - >>> import numpy as np - >>> my_data = np.random.rand(100, 100, 100).astype(np.float32) - >>> volume = snx.Volume(data=my_data) - >>> center = np.mean(volume.bounding_box, axis=0) - >>> # Create a perspective camera... - >>> camera = snx.Camera( - ... interactive=True, - ... projection=projections.perspective(fov=70, near=1, far=1000), - ... ) - >>> # ...positioned along the X axis from the volume center... - >>> camera.transform = ( - ... snx.Transform().translated(center).translated((100, 0, 0)) - ... ) - >>> # ...looking at the center... - >>> camera.look_at(center, up=(0, 0, 1)) - >>> # ...that orbits around the center - >>> camera.controller = snx.Orbit(center=center) + + >>> import numpy as np + >>> my_data = np.random.rand(100, 100, 100).astype(np.float32) + >>> volume = snx.Volume(data=my_data) + >>> center = np.mean(volume.bounding_box, axis=0) + >>> # Create a perspective camera... + >>> camera = snx.Camera( + ... interactive=True, + ... projection=projections.perspective(fov=70, near=1, far=1000), + ... ) + >>> # ...positioned along the X axis from the volume center... + >>> camera.transform = snx.Transform().translated(center).translated((100, 0, 0)) + >>> # ...looking at the center... + >>> camera.look_at(center, up=(0, 0, 1)) + >>> # ...that orbits around the center + >>> camera.controller = snx.Orbit(center=center) Custom polar axis for Y-up scenes: - >>> camera = snx.Camera( - ... controller=snx.Orbit(center=(0, 0, 0), polar_axis=(0, 1, 0)), - ... interactive=True, - ... ) + + >>> camera = snx.Camera( + ... controller=snx.Orbit(center=(0, 0, 0), polar_axis=(0, 1, 0)), + ... interactive=True, + ... ) Interactions ------------ diff --git a/src/scenex/model/_nodes/image.py b/src/scenex/model/_nodes/image.py index 27b78a5a..beeae26a 100644 --- a/src/scenex/model/_nodes/image.py +++ b/src/scenex/model/_nodes/image.py @@ -30,22 +30,26 @@ class Image(Node): Examples -------- Create a simple grayscale image: - >>> import numpy as np - >>> data = np.random.rand(100, 100) - >>> img = Image(data=data) + + >>> import numpy as np + >>> data = np.random.rand(100, 100) + >>> img = Image(data=data) Create an image with custom colormap and intensity range: - >>> img = Image(data=data, cmap=Colormap("viridis"), clims=(0, 255)) + + >>> img = Image(data=data, cmap=Colormap("viridis"), clims=(0, 255)) Create a transformed and semi-transparent image: - >>> img = Image( - ... data=data, - ... transform=Transform().translated((10, 20)).scaled((2, 2)), - ... opacity=0.7, - ... ) + + >>> img = Image( + ... data=data, + ... transform=Transform().translated((10, 20)).scaled((2, 2)), + ... opacity=0.7, + ... ) Apply gamma correction to brighten dark images: - >>> img = Image(data=data, gamma=0.5) + + >>> img = Image(data=data, gamma=0.5) """ node_type: Literal["image"] = Field(default="image", repr=False) diff --git a/src/scenex/model/_nodes/line.py b/src/scenex/model/_nodes/line.py index 7910b935..efc8039b 100644 --- a/src/scenex/model/_nodes/line.py +++ b/src/scenex/model/_nodes/line.py @@ -29,25 +29,28 @@ class Line(Node): Examples -------- Create a simple line connecting several points: - >>> import numpy as np - >>> vertices = np.array([[0, 0], [10, 5], [20, 0]]) - >>> line = Line( - ... vertices=vertices, - ... color=UniformColor(color=Color("red")), - ... ) + + >>> import numpy as np + >>> vertices = np.array([[0, 0], [10, 5], [20, 0]]) + >>> line = Line( + ... vertices=vertices, + ... color=UniformColor(color=Color("red")), + ... ) Create a line with per-vertex colors: - >>> vertices = np.array([[0, 0], [10, 10], [20, 0]]) - >>> colors = [Color("red"), Color("green"), Color("blue")] - >>> line = Line( - ... vertices=vertices, - ... color=VertexColors(color=colors), - ... width=2.0, - ... ) + + >>> vertices = np.array([[0, 0], [10, 10], [20, 0]]) + >>> colors = [Color("red"), Color("green"), Color("blue")] + >>> line = Line( + ... vertices=vertices, + ... color=VertexColors(color=colors), + ... width=2.0, + ... ) Create a 3D line: - >>> vertices = np.array([[0, 0, 0], [10, 5, 3], [20, 0, 6]]) - >>> line = Line(vertices=vertices, width=3.0) + + >>> vertices = np.array([[0, 0, 0], [10, 5, 3], [20, 0, 6]]) + >>> line = Line(vertices=vertices, width=3.0) """ node_type: Literal["line"] = "line" diff --git a/src/scenex/model/_nodes/mesh.py b/src/scenex/model/_nodes/mesh.py index 76d81f5f..cf7b960d 100644 --- a/src/scenex/model/_nodes/mesh.py +++ b/src/scenex/model/_nodes/mesh.py @@ -28,31 +28,33 @@ class Mesh(Node): Examples -------- Create a simple triangle mesh: - >>> import numpy as np - >>> vertices = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0]]) - >>> faces = np.array([[0, 1, 2]]) - >>> mesh = Mesh( - ... vertices=vertices, - ... faces=faces, - ... color=UniformColor(color=Color("blue")), - ... ) + + >>> import numpy as np + >>> vertices = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0]]) + >>> faces = np.array([[0, 1, 2]]) + >>> mesh = Mesh( + ... vertices=vertices, + ... faces=faces, + ... color=UniformColor(color=Color("blue")), + ... ) Create a square made of two triangles: - >>> vertices = np.array( - ... [ - ... [0, 0, 0], # bottom-left - ... [1, 0, 0], # bottom-right - ... [1, 1, 0], # top-right - ... [0, 1, 0], # top-left - ... ] - ... ) - >>> faces = np.array( - ... [ - ... [0, 1, 2], # first triangle - ... [0, 2, 3], # second triangle - ... ] - ... ) - >>> mesh = Mesh(vertices=vertices, faces=faces) + + >>> vertices = np.array( + ... [ + ... [0, 0, 0], # bottom-left + ... [1, 0, 0], # bottom-right + ... [1, 1, 0], # top-right + ... [0, 1, 0], # top-left + ... ] + ... ) + >>> faces = np.array( + ... [ + ... [0, 1, 2], # first triangle + ... [0, 2, 3], # second triangle + ... ] + ... ) + >>> mesh = Mesh(vertices=vertices, faces=faces) Notes ----- diff --git a/src/scenex/model/_nodes/points.py b/src/scenex/model/_nodes/points.py index b72a7273..347df932 100644 --- a/src/scenex/model/_nodes/points.py +++ b/src/scenex/model/_nodes/points.py @@ -46,35 +46,39 @@ class Points(Node): Examples -------- Create simple point markers: - >>> import numpy as np - >>> vertices = np.random.rand(100, 2) * 100 - >>> points = Points( - ... vertices=vertices, - ... size=5, - ... face_color=UniformColor(color=Color("red")), - ... ) + + >>> import numpy as np + >>> vertices = np.random.rand(100, 2) * 100 + >>> points = Points( + ... vertices=vertices, + ... size=5, + ... face_color=UniformColor(color=Color("red")), + ... ) Create points with custom symbols and styling: - >>> points = Points( - ... vertices=vertices, - ... symbol="star", - ... size=20, - ... face_color=UniformColor(color=Color("yellow")), - ... edge_color=UniformColor(color=Color("orange")), - ... edge_width=2, - ... ) + + >>> points = Points( + ... vertices=vertices, + ... symbol="star", + ... size=20, + ... face_color=UniformColor(color=Color("yellow")), + ... edge_color=UniformColor(color=Color("orange")), + ... edge_width=2, + ... ) Create fixed-size points that don't scale with zoom: - >>> points = Points( - ... vertices=vertices, - ... size=10, - ... scaling="fixed", - ... face_color=UniformColor(color=Color("blue")), - ... ) + + >>> points = Points( + ... vertices=vertices, + ... size=10, + ... scaling="fixed", + ... face_color=UniformColor(color=Color("blue")), + ... ) Create 3D points: - >>> vertices_3d = np.random.rand(50, 3) * 100 - >>> points = Points(vertices=vertices_3d, symbol="diamond", size=15) + + >>> vertices_3d = np.random.rand(50, 3) * 100 + >>> points = Points(vertices=vertices_3d, symbol="diamond", size=15) """ node_type: Literal["points"] = "points" diff --git a/src/scenex/model/_nodes/scene.py b/src/scenex/model/_nodes/scene.py index c0b66d7a..6f6a3b67 100644 --- a/src/scenex/model/_nodes/scene.py +++ b/src/scenex/model/_nodes/scene.py @@ -25,32 +25,36 @@ class Scene(Node): Examples -------- Create a scene with visual elements: - >>> import numpy as np - >>> my_image = np.random.rand(100, 100).astype(np.float32) - >>> my_points = np.random.rand(100, 3).astype(np.float32) - >>> scene = Scene( - ... children=[ - ... Image(data=my_image), - ... Points( - ... vertices=my_points, - ... face_color=UniformColor(color=Color("red")), - ... ), - ... ] - ... ) + + >>> import numpy as np + >>> my_image = np.random.rand(100, 100).astype(np.float32) + >>> my_points = np.random.rand(100, 3).astype(np.float32) + >>> scene = Scene( + ... children=[ + ... Image(data=my_image), + ... Points( + ... vertices=my_points, + ... face_color=UniformColor(color=Color("red")), + ... ), + ... ] + ... ) Create an empty scene and later add children: - >>> scene = Scene() - >>> scene.add_child(Image(data=my_image)) - >>> scene.add_child(Points(vertices=my_points)) + + >>> scene = Scene() + >>> scene.add_child(Image(data=my_image)) + >>> scene.add_child(Points(vertices=my_points)) Create a hierarchical scene with nested nodes: - >>> grandchild = Image(data=my_image) - >>> parent = Points(vertices=my_points) - >>> scene = Scene(children=[parent]) + + >>> grandchild = Image(data=my_image) + >>> parent = Points(vertices=my_points) + >>> scene = Scene(children=[parent]) Use a scene with a view: - >>> view = View(scene=scene, camera=Camera()) - >>> canvas = Canvas(views=[view]) + + >>> view = View(scene=scene, camera=Camera()) + >>> canvas = Canvas(views=[view]) Notes ----- diff --git a/src/scenex/model/_nodes/text.py b/src/scenex/model/_nodes/text.py index 7edfeb80..78575b36 100644 --- a/src/scenex/model/_nodes/text.py +++ b/src/scenex/model/_nodes/text.py @@ -18,7 +18,8 @@ class Text(Node): Examples -------- Create a simple text label: - >>> text = Text(text="Hello World", color=Color("white"), size=14) + + >>> text = Text(text="Hello World", color=Color("white"), size=14) Notes ----- diff --git a/src/scenex/model/_nodes/volume.py b/src/scenex/model/_nodes/volume.py index 28b5d75c..0e07687c 100644 --- a/src/scenex/model/_nodes/volume.py +++ b/src/scenex/model/_nodes/volume.py @@ -30,17 +30,19 @@ class Volume(Image): Examples -------- Create a volume with MIP rendering: - >>> import numpy as np - >>> data = np.random.rand(50, 100, 100) # ZYX dimensions - >>> volume = Volume(data=data, render_mode="mip") + + >>> import numpy as np + >>> data = np.random.rand(50, 100, 100) # ZYX dimensions + >>> volume = Volume(data=data, render_mode="mip") Create a volume with custom colormap and intensity range: - >>> volume = Volume( - ... data=data, - ... cmap=Colormap("viridis"), - ... clims=(0, 1), - ... render_mode="iso", - ... ) + + >>> volume = Volume( + ... data=data, + ... cmap=Colormap("viridis"), + ... clims=(0, 1), + ... render_mode="iso", + ... ) Notes ----- diff --git a/src/scenex/model/_transform.py b/src/scenex/model/_transform.py index 308e0cf5..1fa1b081 100644 --- a/src/scenex/model/_transform.py +++ b/src/scenex/model/_transform.py @@ -99,39 +99,45 @@ class Transform(RootModel): Examples -------- Create an identity transform: - >>> transform = Transform() + + >>> transform = Transform() Translate an object: - >>> transform = Transform().translated((10, 20, 30)) + + >>> transform = Transform().translated((10, 20, 30)) Rotate 45 degrees around the z-axis: - >>> transform = Transform().rotated(45, axis=(0, 0, 1)) + + >>> transform = Transform().rotated(45, axis=(0, 0, 1)) Scale uniformly by 2x: - >>> transform = Transform().scaled((2, 2, 2)) + + >>> transform = Transform().scaled((2, 2, 2)) Chain multiple transformations: - >>> transform = ( - ... Transform() - ... .translated((10, 0, 0)) - ... .rotated(45, (0, 0, 1)) - ... .scaled((2, 2, 2)) - ... ) + + >>> transform = ( + ... Transform().translated((10, 0, 0)).rotated(45, (0, 0, 1)).scaled((2, 2, 2)) + ... ) Rotate around a specific point: - >>> transform = Transform().rotated(90, axis=(0, 0, 1), about=(10, 10, 0)) + + >>> transform = Transform().rotated(90, axis=(0, 0, 1), about=(10, 10, 0)) Transform coordinates: - >>> points = np.array([[0, 0, 0], [1, 1, 1]]) - >>> transformed = transform.map(points) + + >>> points = np.array([[0, 0, 0], [1, 1, 1]]) + >>> transformed = transform.map(points) Combine two transforms: - >>> transform1 = Transform().translated((5, 0, 0)) - >>> transform2 = Transform().scaled((2, 2, 2)) - >>> combined = transform1 @ transform2 + + >>> transform1 = Transform().translated((5, 0, 0)) + >>> transform2 = Transform().scaled((2, 2, 2)) + >>> combined = transform1 @ transform2 Invert a transform: - >>> inverse = transform.inv() + + >>> inverse = transform.inv() Notes ----- diff --git a/src/scenex/model/_view.py b/src/scenex/model/_view.py index cb959500..f8be94b4 100644 --- a/src/scenex/model/_view.py +++ b/src/scenex/model/_view.py @@ -43,21 +43,24 @@ class View(EventedBase): Examples -------- Create a view with a scene containing an image: - >>> import numpy as np - >>> my_array = np.random.rand(100, 100).astype(np.float32) - >>> scene = Scene(children=[Image(data=my_array)]) - >>> view = View(scene=scene, camera=Camera()) + + >>> import numpy as np + >>> my_array = np.random.rand(100, 100).astype(np.float32) + >>> scene = Scene(children=[Image(data=my_array)]) + >>> view = View(scene=scene, camera=Camera()) Create a view with interactive camera and letterbox resizing: - >>> view = View( - ... scene=scene, - ... camera=Camera(controller=PanZoom(), interactive=True), - ... on_resize=Letterbox(), - ... ) + + >>> view = View( + ... scene=scene, + ... camera=Camera(controller=PanZoom(), interactive=True), + ... on_resize=Letterbox(), + ... ) Add a view to a canvas: - >>> canvas = Canvas() - >>> canvas.views.append(view) + + >>> canvas = Canvas() + >>> canvas.views.append(view) """ scene: Scene = Field( @@ -294,10 +297,12 @@ class ResizePolicy(EventedBase): Examples -------- Maintain aspect ratio when view resizes: - >>> view = View(camera=Camera(), on_resize=Letterbox()) + + >>> view = View(camera=Camera(), on_resize=Letterbox()) No resize behavior (omit the on_resize parameter): - >>> view = View(camera=Camera()) + + >>> view = View(camera=Camera()) See Also -------- @@ -343,11 +348,12 @@ class Letterbox(ResizePolicy): Examples -------- Create a view with letterbox resizing: - >>> from scenex.utils.projections import orthographic - >>> view = View( - ... camera=Camera(projection=orthographic(100, 100, 100)), - ... on_resize=Letterbox(), - ... ) + + >>> from scenex.utils.projections import orthographic + >>> view = View( + ... camera=Camera(projection=orthographic(100, 100, 100)), + ... on_resize=Letterbox(), + ... ) When view is resized to 200x100 pixels, the projection expands horizontally to maintain the 1:1 aspect ratio, showing more content on the sides rather From 264b144726aced47c1b6c3a3d096b20d37a158c8 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Fri, 19 Jun 2026 10:42:11 -0500 Subject: [PATCH 2/4] Drop adaptors module usage section This is a strange section, which amounts to "Don't use this!" None of the code examples actually pertain to usage OF THIS MODULE :) Let's just get rid of it then? --- src/scenex/adaptors/__init__.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/scenex/adaptors/__init__.py b/src/scenex/adaptors/__init__.py index d3519644..a2a6997a 100644 --- a/src/scenex/adaptors/__init__.py +++ b/src/scenex/adaptors/__init__.py @@ -35,29 +35,6 @@ **vispy** (OpenGL-based): - Mature, widely-supported OpenGL renderer -Usage ------ -Adaptors are NOT intended for manual instantiation; they are instead created -automatically by `scenex.show()`: - ->>> import scenex as snx ->>> import numpy as np - ->>> # Create model ->>> my_array = np.random.rand(100, 100).astype(np.float32) ->>> scene = snx.Scene(children=[snx.Image(data=my_array)]) - ->>> # This creates adaptors automatically ->>> snx.show(scene) -Canvas(...) ->>> snx.run() - -To select a particular backend, use `scenex.use()`: - ->>> snx.use("pygfx") # doctest: +SKIP ->>> snx.show(scene) -Canvas(...) - See Also -------- scenex.model : Declarative model classes From 41c2ecdb8cdfc01d2239f05a5c65a47d2d6f2a56 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Fri, 19 Jun 2026 10:53:26 -0500 Subject: [PATCH 3/4] Add scenex.app.events reference page Not sure how I missed it before --- docs/references/app/events.md | 7 +++++++ docs/references/{app.md => app/index.md} | 0 zensical.toml | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 docs/references/app/events.md rename docs/references/{app.md => app/index.md} (100%) diff --git a/docs/references/app/events.md b/docs/references/app/events.md new file mode 100644 index 00000000..3cbcdafa --- /dev/null +++ b/docs/references/app/events.md @@ -0,0 +1,7 @@ +--- +icon: lucide/code +--- + +# scenex.app.events + +:::scenex.app.events diff --git a/docs/references/app.md b/docs/references/app/index.md similarity index 100% rename from docs/references/app.md rename to docs/references/app/index.md diff --git a/zensical.toml b/zensical.toml index caac489f..3abe83b5 100644 --- a/zensical.toml +++ b/zensical.toml @@ -13,7 +13,7 @@ nav = [ {References = [ "references/index.md", "references/model.md", - "references/app.md", + {"scenex.app" = ["references/app/index.md", "references/app/events.md"]}, "references/adaptors.md", "references/imgui.md", {"scenex.utils" = ["references/utils/index.md", "references/utils/projections.md"]}]}, From a758185a7ca0eb1bb1f62950dfce0a923725d722 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Fri, 19 Jun 2026 10:56:49 -0500 Subject: [PATCH 4/4] Rename Usage section to Examples --- src/scenex/app/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scenex/app/__init__.py b/src/scenex/app/__init__.py index 1320d156..44399360 100644 --- a/src/scenex/app/__init__.py +++ b/src/scenex/app/__init__.py @@ -22,8 +22,8 @@ **WxPython** **Jupyter** -Usage ------ +Examples +-------- The app is typically created automatically by `scenex.show()` and/or `scenex.run()`: >>> import scenex as snx