Source code for isaaclab.envs.ui.viewport_camera_controller
# Copyright (c) 2022-2025, The Isaac Lab Project Developers.# All rights reserved.## SPDX-License-Identifier: BSD-3-Clausefrom__future__importannotationsimportcopyimportnumpyasnpimporttorchimportweakreffromcollections.abcimportSequencefromtypingimportTYPE_CHECKINGimportomni.kit.appimportomni.timelinefromisaaclab.assets.articulation.articulationimportArticulationifTYPE_CHECKING:fromisaaclab.envsimportDirectRLEnv,ManagerBasedEnv,ViewerCfg
[docs]classViewportCameraController:"""This class handles controlling the camera associated with a viewport in the simulator. It can be used to set the viewpoint camera to track different origin types: - **world**: the center of the world (static) - **env**: the center of an environment (static) - **asset_root**: the root of an asset in the scene (e.g. tracking a robot moving in the scene) On creation, the camera is set to track the origin type specified in the configuration. For the :attr:`asset_root` origin type, the camera is updated at each rendering step to track the asset's root position. For this, it registers a callback to the post update event stream from the simulation app. """
[docs]def__init__(self,env:ManagerBasedEnv|DirectRLEnv,cfg:ViewerCfg):"""Initialize the ViewportCameraController. Args: env: The environment. cfg: The configuration for the viewport camera controller. Raises: ValueError: If origin type is configured to be "env" but :attr:`cfg.env_index` is out of bounds. ValueError: If origin type is configured to be "asset_root" but :attr:`cfg.asset_name` is unset. """# store inputsself._env=envself._cfg=copy.deepcopy(cfg)# cast viewer eye and look-at to numpy arraysself.default_cam_eye=np.array(self._cfg.eye)self.default_cam_lookat=np.array(self._cfg.lookat)# set the camera originsifself.cfg.origin_type=="env":# check that the env_index is within boundsself.set_view_env_index(self.cfg.env_index)# set the camera origin to the center of the environmentself.update_view_to_env()elifself.cfg.origin_type=="asset_root"orself.cfg.origin_type=="asset_body":# note: we do not yet update camera for tracking an asset origin, as the asset may not yet be# in the scene when this is called. Instead, we subscribe to the post update event to update the camera# at each rendering step.ifself.cfg.asset_nameisNone:raiseValueError(f"No asset name provided for viewer with origin type: '{self.cfg.origin_type}'.")ifself.cfg.origin_type=="asset_body":ifself.cfg.body_nameisNone:raiseValueError(f"No body name provided for viewer with origin type: '{self.cfg.origin_type}'.")else:# set the camera origin to the center of the worldself.update_view_to_world()# subscribe to post update event so that camera view can be updated at each rendering stepapp_interface=omni.kit.app.get_app_interface()app_event_stream=app_interface.get_post_update_event_stream()self._viewport_camera_update_handle=app_event_stream.create_subscription_to_pop(lambdaevent,obj=weakref.proxy(self):obj._update_tracking_callback(event))
def__del__(self):"""Unsubscribe from the callback."""# use hasattr to handle case where __init__ has not completed before __del__ is calledifhasattr(self,"_viewport_camera_update_handle")andself._viewport_camera_update_handleisnotNone:self._viewport_camera_update_handle.unsubscribe()self._viewport_camera_update_handle=None""" Properties """@propertydefcfg(self)->ViewerCfg:"""The configuration for the viewer."""returnself._cfg""" Public Functions """
[docs]defset_view_env_index(self,env_index:int):"""Sets the environment index for the camera view. Args: env_index: The index of the environment to set the camera view to. Raises: ValueError: If the environment index is out of bounds. It should be between 0 and num_envs - 1. """# check that the env_index is within boundsifenv_index<0orenv_index>=self._env.num_envs:raiseValueError(f"Out of range value for attribute 'env_index': {env_index}."f" Expected a value between 0 and {self._env.num_envs-1} for the current environment.")# update the environment indexself.cfg.env_index=env_index# update the camera view if the origin is set to env type (since, the camera view is static)# note: for assets, the camera view is updated at each rendering stepifself.cfg.origin_type=="env":self.update_view_to_env()
[docs]defupdate_view_to_world(self):"""Updates the viewer's origin to the origin of the world which is (0, 0, 0)."""# set origin type to worldself.cfg.origin_type="world"# update the camera originsself.viewer_origin=torch.zeros(3)# update the camera viewself.update_view_location()
[docs]defupdate_view_to_env(self):"""Updates the viewer's origin to the origin of the selected environment."""# set origin type to worldself.cfg.origin_type="env"# update the camera originsself.viewer_origin=self._env.scene.env_origins[self.cfg.env_index]# update the camera viewself.update_view_location()
[docs]defupdate_view_to_asset_root(self,asset_name:str):"""Updates the viewer's origin based upon the root of an asset in the scene. Args: asset_name: The name of the asset in the scene. The name should match the name of the asset in the scene. Raises: ValueError: If the asset is not in the scene. """# check if the asset is in the sceneifself.cfg.asset_name!=asset_name:asset_entities=[*self._env.scene.rigid_objects.keys(),*self._env.scene.articulations.keys()]ifasset_namenotinasset_entities:raiseValueError(f"Asset '{asset_name}' is not in the scene. Available entities: {asset_entities}.")# update the asset nameself.cfg.asset_name=asset_name# set origin type to asset_rootself.cfg.origin_type="asset_root"# update the camera originsself.viewer_origin=self._env.scene[self.cfg.asset_name].data.root_pos_w[self.cfg.env_index]# update the camera viewself.update_view_location()
[docs]defupdate_view_to_asset_body(self,asset_name:str,body_name:str):"""Updates the viewer's origin based upon the body of an asset in the scene. Args: asset_name: The name of the asset in the scene. The name should match the name of the asset in the scene. body_name: The name of the body in the asset. Raises: ValueError: If the asset is not in the scene or the body is not valid. """# check if the asset is in the sceneifself.cfg.asset_name!=asset_name:asset_entities=[*self._env.scene.rigid_objects.keys(),*self._env.scene.articulations.keys()]ifasset_namenotinasset_entities:raiseValueError(f"Asset '{asset_name}' is not in the scene. Available entities: {asset_entities}.")# check if the body is in the assetasset:Articulation=self._env.scene[asset_name]ifbody_namenotinasset.body_names:raiseValueError(f"'{body_name}' is not a body of Asset '{asset_name}'. Available bodies: {asset.body_names}.")# get the body indexbody_id,_=asset.find_bodies(body_name)# update the asset nameself.cfg.asset_name=asset_name# set origin type to asset_bodyself.cfg.origin_type="asset_body"# update the camera originsself.viewer_origin=self._env.scene[self.cfg.asset_name].data.body_pos_w[self.cfg.env_index,body_id].view(3)# update the camera viewself.update_view_location()
[docs]defupdate_view_location(self,eye:Sequence[float]|None=None,lookat:Sequence[float]|None=None):"""Updates the camera view pose based on the current viewer origin and the eye and lookat positions. Args: eye: The eye position of the camera. If None, the current eye position is used. lookat: The lookat position of the camera. If None, the current lookat position is used. """# store the camera view pose for later useifeyeisnotNone:self.default_cam_eye=np.asarray(eye)iflookatisnotNone:self.default_cam_lookat=np.asarray(lookat)# set the camera locationsviewer_origin=self.viewer_origin.detach().cpu().numpy()cam_eye=viewer_origin+self.default_cam_eyecam_target=viewer_origin+self.default_cam_lookat# set the camera viewself._env.sim.set_camera_view(eye=cam_eye,target=cam_target)
""" Private Functions """def_update_tracking_callback(self,event):"""Updates the camera view at each rendering step."""# update the camera view if the origin is set to asset_root# in other cases, the camera view is static and does not need to be updated continuouslyifself.cfg.origin_type=="asset_root"andself.cfg.asset_nameisnotNone:self.update_view_to_asset_root(self.cfg.asset_name)ifself.cfg.origin_type=="asset_body"andself.cfg.asset_nameisnotNoneandself.cfg.body_nameisnotNone:self.update_view_to_asset_body(self.cfg.asset_name,self.cfg.body_name)