1818import time
1919
2020import numpy as np
21+ import numpy .typing as npt
2122import warp as wp
2223
2324import newton as nt
@@ -53,7 +54,7 @@ class ViewerGL(ViewerBase):
5354 - Extensible logging of meshes, lines, points, and arrays for custom visualization.
5455 """
5556
56- def __init__ (self , width = 1920 , height = 1080 , vsync = False , headless = False ):
57+ def __init__ (self , width = 1920 , height = 1080 , vsync = False , headless = False , save_frame : bool = False ):
5758 """
5859 Initialize the OpenGL viewer and UI.
5960
@@ -62,6 +63,7 @@ def __init__(self, width=1920, height=1080, vsync=False, headless=False):
6263 height (int): Window height in pixels.
6364 vsync (bool): Enable vertical sync.
6465 headless (bool): Run in headless mode (no window).
66+ save_frame (bool): If True, store internally the last rendered frame.
6567 """
6668 super ().__init__ ()
6769
@@ -127,6 +129,10 @@ def __init__(self, width=1920, height=1080, vsync=False, headless=False):
127129 # positions: "side", "stats", "free"
128130 self ._ui_callbacks = {"side" : [], "stats" : [], "free" : []}
129131
132+ # Buffer to store the last rendered frame
133+ self ._save_frame = save_frame
134+ self ._frame = np .empty (0 , dtype = np .uint8 )
135+
130136 self .set_model (None )
131137
132138 def register_ui_callback (self , callback , position = "side" ):
@@ -425,7 +431,7 @@ def end_frame(self):
425431 destroyed results in a crash (access violation). Therefore we check
426432 whether an exit was requested and early-out before touching GL if so.
427433 """
428- self ._update ()
434+ return self ._update ()
429435
430436 @override
431437 def apply_forces (self , state ):
@@ -459,7 +465,10 @@ def _update(self):
459465 return
460466
461467 # Render the scene and present it
462- self .renderer .render (self .camera , self .objects , self .lines )
468+ frame = self .renderer .render (self .camera , self .objects , self .lines , return_frame = self ._save_frame )
469+
470+ if self ._save_frame :
471+ self ._frame = frame
463472
464473 # Always update FPS tracking, even if UI is hidden
465474 self ._update_fps ()
@@ -475,6 +484,19 @@ def _update(self):
475484
476485 self .renderer .present ()
477486
487+ def get_frame (self ) -> npt .NDArray [np .uint8 ]:
488+ """
489+ Get the last rendered frame as a numpy array.
490+
491+ Returns:
492+ np.ndarray: The last rendered frame in RGBA format.
493+ """
494+
495+ if not self ._save_frame :
496+ raise RuntimeError ("Frame saving is not enabled. Initialize with `save_frame=True`." )
497+
498+ return self ._frame
499+
478500 @override
479501 def is_running (self ) -> bool :
480502 """
0 commit comments