Pygfx Examples
These examples demonstrate 3D scene rendering with Pygfx. Run them with:
Interactive Sphere
A sphere that changes color on hover:
pygfx-component.cgx
<group>
<ambient-light />
<directional-light />
<mesh
:material="material()"
:geometry="sphere"
@pointer_enter="lambda ev: hover(True)"
@pointer_leave="lambda ev: hover(False)"
/>
</group>
<script>
import pygfx as gfx
import collagraph as cg
sphere = gfx.sphere_geometry(radius=3)
red = gfx.MeshPhongMaterial(color=(1, 0.2, 0.2), pick_write=True)
blue = gfx.MeshPhongMaterial(color=(0.2, 0.4, 1.0), pick_write=True)
class Ball(cg.Component):
def material(self):
hovered = self.state.get("hovered", False)
return red if hovered else blue
def hover(self, hover):
self.state["hovered"] = hover
</script>
Point Cloud
A dynamic point cloud with selection and hover state. Uses v-for to render multiple 3D objects and accepts props via CLI --state:
point_cloud.cgx
<ambient-light />
<point-light />
<group>
<Point
v-for="idx, position in enumerate(positions)"
:position="position"
:material="'selected' if idx == selected else 'hovered' if idx == hovered else 'default'"
:index="idx"
@selected="set_selected"
@hovered="set_hovered"
/>
</group>
<script>
import random
import pygfx as gfx
import collagraph
from observ import watch
from point import Point
class PointCloud(collagraph.Component):
def init(self):
self.state["positions"] = []
self.state["hovered"] = -1
self.state["selected"] = -1
self.watchers = {}
self.watchers["count"] = watch(
lambda: self.props["count"],
self.update_positions,
immediate=True,
)
def update_positions(self):
new_count = self.props["count"]
old_count = len(self.state["positions"])
if new_count > old_count:
self.state["positions"].extend(
[(random.randint(-20, 20), random.randint(-20, 20), random.randint(-20, 20))
for _ in range(new_count - old_count)]
)
elif new_count < old_count:
del self.state["positions"][0:old_count - new_count]
def set_hovered(self, index):
self.state["hovered"] = index
def set_selected(self, index):
if self.state["selected"] == index:
self.state["selected"] = -1
else:
self.state["selected"] = index
</script>
Landmarks with Text Labels
Random 3D landmarks with labels rendered as text content inside <text> elements, using {{ }} interpolation. Click a landmark sphere to increment and rename its label:
landmarks.cgx (excerpt)
<group v-for="idx, landmark in enumerate(landmarks)">
<group :local.position="landmark['position']">
<mesh
:geometry="sphere_geometry"
:material="selected_sphere_material if idx == selected else sphere_material"
@click="lambda ev: click_landmark(idx)"
/>
<text
anchor="bottom-center"
:font_size="14"
:local.position="(0, 0.75, 0)"
:material="selected_label_material if idx == selected else label_material"
screen_space
>
{{landmark['name']}}
</text>
</group>
</group>
Combined PySide + Pygfx
You can embed a Pygfx canvas inside a PySide6 application. See examples/pygfx/combined-example.py for a full example of rendering 3D content within a Qt widget layout.
More Examples
See the examples/pygfx/ directory for:
point.cgx-- Individual point component with hover/selectbutton.cgx/numberpad.cgx-- Reusable 3D UI componentscomponent-example.py-- Pygfx with timer-based interactionrender_widget.cgx-- Wrapping PygfxRenderer inside a PySide widget