2424from __future__ import annotations
2525
2626import logging
27+ import threading
28+ import time
2729from typing import TYPE_CHECKING
2830
2931import mujoco
@@ -251,12 +253,8 @@ def _on_gizmo_update(event) -> None:
251253 [2 * (x * z - w * y ), 2 * (y * z + w * x ), 1 - 2 * (x * x + y * y )],
252254 ])
253255 pose [:3 , 3 ] = event .target .position
256+ # Only buffer the target — stepping happens in _teleop_loop thread
254257 self ._controller .set_target_pose (pose )
255- # Step immediately — no continuous sync loop in IPython mode
256- state = self ._controller .step ()
257- # Sync viewer so the arm movement is visible
258- if self ._viewer is not None :
259- self ._viewer .sync ()
260258
261259 # GUI controls
262260 self ._activate_btn = gui .add_button (
@@ -334,15 +332,20 @@ def _activate_teleop(self) -> None:
334332 self ._gizmo .visible = True
335333
336334 if self ._ghost is not None :
337- # Ghost is a child of gizmo — just make it visible
338335 self ._ghost .set_visible (True )
339336
340337 self ._activate_btn .name = f"Deactivate Teleop ({ self ._arm_label } )"
341338 self ._activate_btn .color = "red"
342339
340+ # Start background loop that steps the controller + syncs viewer
341+ self ._teleop_thread = threading .Thread (
342+ target = self ._teleop_loop , daemon = True ,
343+ )
344+ self ._teleop_thread .start ()
345+
343346 def _deactivate_teleop (self ) -> None :
347+ self ._is_teleop_active = False # signals thread to stop
344348 self ._controller .deactivate ()
345- self ._is_teleop_active = False
346349
347350 if self ._gizmo is not None :
348351 self ._gizmo .visible = False
@@ -351,3 +354,20 @@ def _deactivate_teleop(self) -> None:
351354
352355 self ._activate_btn .name = f"Activate Teleop ({ self ._arm_label } )"
353356 self ._activate_btn .color = "green"
357+
358+ def _teleop_loop (self ) -> None :
359+ """Background loop: step controller + sync viewer at ~30 Hz."""
360+ dt = 1.0 / 30.0
361+ while self ._is_teleop_active :
362+ t0 = time .monotonic ()
363+ try :
364+ self ._controller .step ()
365+ if self ._viewer is not None :
366+ self ._viewer .sync ()
367+ except Exception as e :
368+ logger .warning ("Teleop step error: %s" , e )
369+ break
370+ elapsed = time .monotonic () - t0
371+ sleep = dt - elapsed
372+ if sleep > 0 :
373+ time .sleep (sleep )
0 commit comments