From 2875e5f1876fe2083667415999ab62591ae9ea45 Mon Sep 17 00:00:00 2001 From: Iori Yanokura Date: Tue, 2 Jun 2026 20:34:46 +0900 Subject: [PATCH 1/3] Skip rendering in viewers when nothing changed PyrenderViewer and TrimeshSceneViewer re-rendered the scene on every refresh tick (e.g. 30 times per second) even when nothing had changed, keeping the CPU busy on a fully static view. Skip rendering entirely when self._redraw is False. Also force a redraw in TrimeshSceneViewer.save_image so the captured frame is freshly rendered rather than a potentially stale buffer. --- skrobot/viewers/_pyrender.py | 5 ++++- skrobot/viewers/_trimesh.py | 11 ++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/skrobot/viewers/_pyrender.py b/skrobot/viewers/_pyrender.py index 6273ae2c..1fd34f4e 100644 --- a/skrobot/viewers/_pyrender.py +++ b/skrobot/viewers/_pyrender.py @@ -279,7 +279,10 @@ def on_draw(self): with self._render_lock: if not self._redraw: - super(PyrenderViewer, self).on_draw() + # Nothing changed since the last frame, so skip rendering + # entirely. Without this, the scene is re-rendered on every + # refresh tick (e.g. 30 times per second), which keeps the + # CPU busy even when the view is completely static. return # apply latest angle-vector for link_id, (node, link) in self._visual_mesh_map.items(): diff --git a/skrobot/viewers/_trimesh.py b/skrobot/viewers/_trimesh.py index ee60b20b..a86eba5a 100644 --- a/skrobot/viewers/_trimesh.py +++ b/skrobot/viewers/_trimesh.py @@ -145,9 +145,10 @@ def on_update(self, dt): def on_draw(self): if not self._redraw: - with self.lock: - self._update_vertex_list() - super(TrimeshSceneViewer, self).on_draw() + # Nothing changed since the last frame, so skip rendering + # entirely. Without this, the scene is re-rendered on every + # refresh tick (e.g. 30 times per second), which keeps the + # CPU busy even when the view is completely static. return with self.lock: @@ -263,6 +264,10 @@ def set_camera(self, *args, **kwargs): ) def save_image(self, file_obj): + # Force a redraw so the freshly rendered frame is captured. on_draw + # skips rendering when self._redraw is False, so without this the + # buffer read back below could contain a stale frame. + self._redraw = True self.switch_to() self.dispatch_events() self.dispatch_event('on_draw') From 19bc845283241232010351d587a71e7026d6526c Mon Sep 17 00:00:00 2001 From: Iori Yanokura Date: Tue, 2 Jun 2026 20:41:02 +0900 Subject: [PATCH 2/3] Set default viewer update_interval back to 30Hz The default was lowered to 1Hz in #239 because rendering ran on every refresh tick, pushing idle CPU usage over 300%. Now that rendering is skipped when nothing changed, 30Hz no longer causes that, so restore the original 30Hz default for smooth interaction out of the box. --- skrobot/viewers/_pyrender.py | 4 ++-- skrobot/viewers/_trimesh.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/skrobot/viewers/_pyrender.py b/skrobot/viewers/_pyrender.py index 1fd34f4e..b751bada 100644 --- a/skrobot/viewers/_pyrender.py +++ b/skrobot/viewers/_pyrender.py @@ -99,7 +99,7 @@ class PyrenderViewer(pyrender.Viewer): The resolution of the viewer. Default is (640, 480). update_interval : float, optional The update interval (in seconds) for the viewer. Default is - 1.0 seconds. + 1 / 30 seconds (30 Hz). title : str, optional The title of the viewer window. Default is 'scikit-robot PyrenderViewer'. enable_collision_toggle : bool, optional @@ -123,7 +123,7 @@ class PyrenderViewer(pyrender.Viewer): # Class variable to hold the single instance of the class. _instance = None - def __init__(self, resolution=None, update_interval=1.0, + def __init__(self, resolution=None, update_interval=1 / 30.0, render_flags=None, title=None, enable_collision_toggle=True): if getattr(self, '_initialized', False): return diff --git a/skrobot/viewers/_trimesh.py b/skrobot/viewers/_trimesh.py index a86eba5a..e0811a93 100644 --- a/skrobot/viewers/_trimesh.py +++ b/skrobot/viewers/_trimesh.py @@ -55,7 +55,7 @@ class TrimeshSceneViewer(trimesh.viewer.SceneViewer): The resolution of the viewer. Default is (640, 480). update_interval : float, optional The update interval (in seconds) for the viewer. Default is - 1.0 seconds. + 1 / 30 seconds (30 Hz). title : str, optional The title of the viewer window. Default is 'scikit-robot TrimeshSceneViewer'. @@ -69,7 +69,7 @@ class TrimeshSceneViewer(trimesh.viewer.SceneViewer): _instance = None _version_warning_issued = False - def __init__(self, resolution=None, update_interval=1.0, title=None): + def __init__(self, resolution=None, update_interval=1 / 30.0, title=None): if getattr(self, '_initialized', False): return if resolution is None: From 239e92fff630bbd3e344741d7168ad7d4aeebb6d Mon Sep 17 00:00:00 2001 From: Iori Yanokura Date: Tue, 2 Jun 2026 20:42:52 +0900 Subject: [PATCH 3/3] docs: update viewer update_interval default to 30Hz Reflect the new 1/30 default and note that the viewer only re-renders when the scene changes, so 30Hz no longer keeps the CPU busy on a static view. --- docs/source/reference/viewers.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/source/reference/viewers.rst b/docs/source/reference/viewers.rst index a42aff9e..3085d030 100644 --- a/docs/source/reference/viewers.rst +++ b/docs/source/reference/viewers.rst @@ -70,7 +70,7 @@ PyrenderViewer The viewer is initialized with specified resolution, update interval, and rendering flags. Key parameters include: - ``resolution``: Window size (default: ``(640, 480)``) - - ``update_interval``: Update frequency in seconds (default: ``1.0``) + - ``update_interval``: Update frequency in seconds (default: ``1 / 30``, i.e. 30 Hz) - ``enable_collision_toggle``: Enable collision/visual mesh switching (default: ``True``) - ``title``: Window title (default: ``'scikit-robot PyrenderViewer'``) @@ -279,16 +279,17 @@ The following video demonstrates the interactive IK feature, where dragging the # Use viser viewer with visualize-urdf command (IK is enabled by default) skr visualize-urdf ~/.skrobot/pr2_description/pr2.urdf --viewer viser -.. caution:: +.. note:: - To speed up the rendering cycle in **TrimeshSceneViewer** and **PyrenderViewer**, adjust the ``update_interval`` to the reciprocal of the desired frequency. For example, to achieve updates at 30 Hz, set the ``update_interval`` to 1/30. This change will increase the frequency at which the ``redraw()`` function is called, making the rendering process faster. + Both **TrimeshSceneViewer** and **PyrenderViewer** update at 30 Hz by default (``update_interval=1/30``). The viewer only re-renders when the scene actually changes (e.g. after :func:`redraw`), so a static view stays cheap even at 30 Hz. The ``update_interval`` controls how often the ``redraw()`` request is polled: a smaller value gives a higher refresh rate (smoother interaction and animation) at the cost of more idle CPU, while a larger value lowers idle CPU usage. Example usage: .. code-block:: python - viewer = skrobot.viewers.TrimeshSceneViewer(resolution=(640, 480), update_interval=1.0/30) # Set update interval for 30 Hz - viewer = skrobot.viewers.PyrenderViewer(resolution=(640, 480), update_interval=1.0/30) # Same for PyrenderViewer + # 30 Hz is already the default; pass update_interval only to override it. + viewer = skrobot.viewers.TrimeshSceneViewer(resolution=(640, 480), update_interval=1.0/30) # 30 Hz (default) + viewer = skrobot.viewers.PyrenderViewer(resolution=(640, 480), update_interval=1.0) # 1 Hz, lower idle CPU Color Management