# Copyright (c) 2022-2025, The Isaac Lab Project Developers.# All rights reserved.## SPDX-License-Identifier: BSD-3-Clausefrom__future__importannotationsimportasyncioimportosimportweakreffromdatetimeimportdatetimefromtypingimportTYPE_CHECKINGimportisaacsimimportomni.kit.appimportomni.kit.commandsimportomni.usdfrompxrimportPhysxSchema,Sdf,Usd,UsdGeom,UsdPhysicsfromisaaclab.ui.widgetsimportManagerLiveVisualizerifTYPE_CHECKING:importomni.uifrom..manager_based_envimportManagerBasedEnv
[docs]classBaseEnvWindow:"""Window manager for the basic environment. This class creates a window that is used to control the environment. The window contains controls for rendering, debug visualization, and other environment-specific UI elements. Users can add their own UI elements to the window by using the `with` context manager. This can be done either be inheriting the class or by using the `env.window` object directly from the standalone execution script. Example for adding a UI element from the standalone execution script: >>> with env.window.ui_window_elements["main_vstack"]: >>> ui.Label("My UI element") """
[docs]def__init__(self,env:ManagerBasedEnv,window_name:str="IsaacLab"):"""Initialize the window. Args: env: The environment object. window_name: The name of the window. Defaults to "IsaacLab". """# store inputsself.env=env# prepare the list of assets that can be followed by the viewport camera# note that the first two options are "World" and "Env" which are special casesself._viewer_assets_options=["World","Env",*self.env.scene.rigid_objects.keys(),*self.env.scene.articulations.keys(),]# Listeners for environment selection changesself._ui_listeners:list[ManagerLiveVisualizer]=[]print("Creating window for environment.")# create window for UIself.ui_window=omni.ui.Window(window_name,width=400,height=500,visible=True,dock_preference=omni.ui.DockPreference.RIGHT_TOP)# dock next to properties windowasyncio.ensure_future(self._dock_window(window_title=self.ui_window.title))# keep a dictionary of stacks so that child environments can add their own UI elements# this can be done by using the `with` context managerself.ui_window_elements=dict()# create main frameself.ui_window_elements["main_frame"]=self.ui_window.framewithself.ui_window_elements["main_frame"]:# create main stackself.ui_window_elements["main_vstack"]=omni.ui.VStack(spacing=5,height=0)withself.ui_window_elements["main_vstack"]:# create collapsable frame for simulationself._build_sim_frame()# create collapsable frame for viewerself._build_viewer_frame()# create collapsable frame for debug visualizationself._build_debug_vis_frame()withself.ui_window_elements["debug_frame"]:withself.ui_window_elements["debug_vstack"]:self._visualize_manager(title="Actions",class_name="action_manager")self._visualize_manager(title="Observations",class_name="observation_manager")
def__del__(self):"""Destructor for the window."""# destroy the windowifself.ui_windowisnotNone:self.ui_window.visible=Falseself.ui_window.destroy()self.ui_window=None""" Build sub-sections of the UI. """def_build_sim_frame(self):"""Builds the sim-related controls frame for the UI."""# create collapsable frame for controlsself.ui_window_elements["sim_frame"]=omni.ui.CollapsableFrame(title="Simulation Settings",width=omni.ui.Fraction(1),height=0,collapsed=False,style=isaacsim.gui.components.ui_utils.get_style(),horizontal_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_AS_NEEDED,vertical_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON,)withself.ui_window_elements["sim_frame"]:# create stack for controlsself.ui_window_elements["sim_vstack"]=omni.ui.VStack(spacing=5,height=0)withself.ui_window_elements["sim_vstack"]:# create rendering mode dropdownrender_mode_cfg={"label":"Rendering Mode","type":"dropdown","default_val":self.env.sim.render_mode.value,"items":[member.nameformemberinself.env.sim.RenderModeifmember.value>=0],"tooltip":"Select a rendering mode\n"+self.env.sim.RenderMode.__doc__,"on_clicked_fn":lambdavalue:self.env.sim.set_render_mode(self.env.sim.RenderMode[value]),}self.ui_window_elements["render_dropdown"]=isaacsim.gui.components.ui_utils.dropdown_builder(**render_mode_cfg)# create animation recording boxrecord_animate_cfg={"label":"Record Animation","type":"state_button","a_text":"START","b_text":"STOP","tooltip":"Record the animation of the scene. Only effective if fabric is disabled.","on_clicked_fn":lambdavalue:self._toggle_recording_animation_fn(value),}self.ui_window_elements["record_animation"]=isaacsim.gui.components.ui_utils.state_btn_builder(**record_animate_cfg)# disable the button if fabric is not enabledself.ui_window_elements["record_animation"].enabled=notself.env.sim.is_fabric_enabled()def_build_viewer_frame(self):"""Build the viewer-related control frame for the UI."""# create collapsable frame for viewerself.ui_window_elements["viewer_frame"]=omni.ui.CollapsableFrame(title="Viewer Settings",width=omni.ui.Fraction(1),height=0,collapsed=False,style=isaacsim.gui.components.ui_utils.get_style(),horizontal_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_AS_NEEDED,vertical_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON,)withself.ui_window_elements["viewer_frame"]:# create stack for controlsself.ui_window_elements["viewer_vstack"]=omni.ui.VStack(spacing=5,height=0)withself.ui_window_elements["viewer_vstack"]:# create a number slider to move to environment origin# NOTE: slider is 1-indexed, whereas the env index is 0-indexedviewport_origin_cfg={"label":"Environment Index","type":"button","default_val":self.env.cfg.viewer.env_index+1,"min":1,"max":self.env.num_envs,"tooltip":"The environment index to follow. Only effective if follow mode is not 'World'.",}self.ui_window_elements["viewer_env_index"]=isaacsim.gui.components.ui_utils.int_builder(**viewport_origin_cfg)# create a number slider to move to environment originself.ui_window_elements["viewer_env_index"].add_value_changed_fn(self._set_viewer_env_index_fn)# create a tracker for the camera locationviewer_follow_cfg={"label":"Follow Mode","type":"dropdown","default_val":0,"items":[name.replace("_"," ").title()fornameinself._viewer_assets_options],"tooltip":"Select the viewport camera following mode.","on_clicked_fn":self._set_viewer_origin_type_fn,}self.ui_window_elements["viewer_follow"]=isaacsim.gui.components.ui_utils.dropdown_builder(**viewer_follow_cfg)# add viewer default eye and lookat locationsself.ui_window_elements["viewer_eye"]=isaacsim.gui.components.ui_utils.xyz_builder(label="Camera Eye",tooltip="Modify the XYZ location of the viewer eye.",default_val=self.env.cfg.viewer.eye,step=0.1,on_value_changed_fn=[self._set_viewer_location_fn]*3,)self.ui_window_elements["viewer_lookat"]=isaacsim.gui.components.ui_utils.xyz_builder(label="Camera Target",tooltip="Modify the XYZ location of the viewer target.",default_val=self.env.cfg.viewer.lookat,step=0.1,on_value_changed_fn=[self._set_viewer_location_fn]*3,)def_build_debug_vis_frame(self):"""Builds the debug visualization frame for various scene elements. This function inquires the scene for all elements that have a debug visualization implemented and creates a checkbox to toggle the debug visualization for each element that has it implemented. If the element does not have a debug visualization implemented, a label is created instead. """# create collapsable frame for debug visualizationself.ui_window_elements["debug_frame"]=omni.ui.CollapsableFrame(title="Scene Debug Visualization",width=omni.ui.Fraction(1),height=0,collapsed=False,style=isaacsim.gui.components.ui_utils.get_style(),horizontal_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_AS_NEEDED,vertical_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON,)withself.ui_window_elements["debug_frame"]:# create stack for debug visualizationself.ui_window_elements["debug_vstack"]=omni.ui.VStack(spacing=5,height=0)withself.ui_window_elements["debug_vstack"]:elements=[self.env.scene.terrain,*self.env.scene.rigid_objects.values(),*self.env.scene.articulations.values(),*self.env.scene.sensors.values(),]names=["terrain",*self.env.scene.rigid_objects.keys(),*self.env.scene.articulations.keys(),*self.env.scene.sensors.keys(),]# create one for the terrainforelem,nameinzip(elements,names):ifelemisnotNone:self._create_debug_vis_ui_element(name,elem)def_visualize_manager(self,title:str,class_name:str)->None:"""Checks if the attribute with the name 'class_name' can be visualized. If yes, create vis interface. Args: title: The title of the manager visualization frame. class_name: The name of the manager to visualize. """ifhasattr(self.env,class_name)andclass_nameinself.env.manager_visualizers:manager=self.env.manager_visualizers[class_name]ifhasattr(manager,"has_debug_vis_implementation"):self._create_debug_vis_ui_element(title,manager)else:print(f"ManagerLiveVisualizer cannot be created for manager: {class_name}, has_debug_vis_implementation"" does not exist")else:print(f"ManagerLiveVisualizer cannot be created for manager: {class_name}, Manager does not exist")""" Custom callbacks for UI elements. """def_toggle_recording_animation_fn(self,value:bool):"""Toggles the animation recording."""ifvalue:# log directory to save the recordingifnothasattr(self,"animation_log_dir"):# create a new log directorylog_dir=datetime.now().strftime("%Y-%m-%d_%H-%M-%S")self.animation_log_dir=os.path.join(os.getcwd(),"recordings",log_dir)# start the recording_=omni.kit.commands.execute("StartRecording",target_paths=[("/World",True)],live_mode=True,use_frame_range=False,start_frame=0,end_frame=0,use_preroll=False,preroll_frame=0,record_to="FILE",fps=0,apply_root_anim=False,increment_name=True,record_folder=self.animation_log_dir,take_name="TimeSample",)else:# stop the recording_=omni.kit.commands.execute("StopRecording")# save the current stagestage=omni.usd.get_context().get_stage()source_layer=stage.GetRootLayer()# output the stage to a filestage_usd_path=os.path.join(self.animation_log_dir,"Stage.usd")source_prim_path="/"# creates empty anon layertemp_layer=Sdf.Find(stage_usd_path)iftemp_layerisNone:temp_layer=Sdf.Layer.CreateNew(stage_usd_path)temp_stage=Usd.Stage.Open(temp_layer)# update stage dataUsdGeom.SetStageUpAxis(temp_stage,UsdGeom.GetStageUpAxis(stage))UsdGeom.SetStageMetersPerUnit(temp_stage,UsdGeom.GetStageMetersPerUnit(stage))# copy the primSdf.CreatePrimInLayer(temp_layer,source_prim_path)Sdf.CopySpec(source_layer,source_prim_path,temp_layer,source_prim_path)# set the default primtemp_layer.defaultPrim=Sdf.Path(source_prim_path).name# remove all physics from the stageforprimintemp_stage.TraverseAll():# skip if the prim is an instanceifprim.IsInstanceable():continue# if prim has articulation then disable itifprim.HasAPI(UsdPhysics.ArticulationRootAPI):prim.RemoveAPI(UsdPhysics.ArticulationRootAPI)prim.RemoveAPI(PhysxSchema.PhysxArticulationAPI)# if prim has rigid body then disable itifprim.HasAPI(UsdPhysics.RigidBodyAPI):prim.RemoveAPI(UsdPhysics.RigidBodyAPI)prim.RemoveAPI(PhysxSchema.PhysxRigidBodyAPI)# if prim is a joint type then disable itifprim.IsA(UsdPhysics.Joint):prim.GetAttribute("physics:jointEnabled").Set(False)# resolve all paths relative to layer pathomni.usd.resolve_paths(source_layer.identifier,temp_layer.identifier)# save the stagetemp_layer.Save()# print the path to the saved stageprint("Recording completed.")print(f"\tSaved recorded stage to : {stage_usd_path}")print(f"\tSaved recorded animation to: {os.path.join(self.animation_log_dir,'TimeSample_tk001.usd')}")print("\nTo play the animation, check the instructions in the following link:")print("\thttps://docs.omniverse.nvidia.com/extensions/latest/ext_animation_stage-recorder.html#using-the-captured-timesamples")print("\n")# reset the log directoryself.animation_log_dir=Nonedef_set_viewer_origin_type_fn(self,value:str):"""Sets the origin of the viewport's camera. This is based on the drop-down menu in the UI."""# Extract the viewport camera controller from environmentvcc=self.env.viewport_camera_controllerifvccisNone:raiseValueError("Viewport camera controller is not initialized! Please check the rendering mode.")# Based on origin type, update the camera viewifvalue=="World":vcc.update_view_to_world()elifvalue=="Env":vcc.update_view_to_env()else:# find which index the asset isfancy_names=[name.replace("_"," ").title()fornameinself._viewer_assets_options]# store the desired env indexviewer_asset_name=self._viewer_assets_options[fancy_names.index(value)]# update the camera viewvcc.update_view_to_asset_root(viewer_asset_name)def_set_viewer_location_fn(self,model:omni.ui.SimpleFloatModel):"""Sets the viewport camera location based on the UI."""# access the viewport camera controller (for brevity)vcc=self.env.viewport_camera_controllerifvccisNone:raiseValueError("Viewport camera controller is not initialized! Please check the rendering mode.")# obtain the camera locations and set them in the viewpoint camera controllereye=[self.ui_window_elements["viewer_eye"][i].get_value_as_float()foriinrange(3)]lookat=[self.ui_window_elements["viewer_lookat"][i].get_value_as_float()foriinrange(3)]# update the camera viewvcc.update_view_location(eye,lookat)def_set_viewer_env_index_fn(self,model:omni.ui.SimpleIntModel):"""Sets the environment index and updates the camera if in 'env' origin mode."""# access the viewport camera controller (for brevity)vcc=self.env.viewport_camera_controllerifvccisNone:raiseValueError("Viewport camera controller is not initialized! Please check the rendering mode.")# store the desired env index, UI is 1-indexedvcc.set_view_env_index(model.as_int-1)# notify additional listenersforlistenerinself._ui_listeners:listener.set_env_selection(model.as_int-1)""" Helper functions - UI building. """def_create_debug_vis_ui_element(self,name:str,elem:object):"""Create a checkbox for toggling debug visualization for the given element."""fromomni.kit.window.extensionsimportSimpleCheckBoxwithomni.ui.HStack():# create the UI elementtext=("Toggle debug visualization."ifelem.has_debug_vis_implementationelse"Debug visualization not implemented.")omni.ui.Label(name.replace("_"," ").title(),width=isaacsim.gui.components.ui_utils.LABEL_WIDTH-12,alignment=omni.ui.Alignment.LEFT_CENTER,tooltip=text,)has_cfg=hasattr(elem,"cfg")andelem.cfgisnotNoneis_checked=Falseifhas_cfg:is_checked=(hasattr(elem.cfg,"debug_vis")andelem.cfg.debug_vis)or(hasattr(elem,"debug_vis")andelem.debug_vis)self.ui_window_elements[f"{name}_cb"]=SimpleCheckBox(model=omni.ui.SimpleBoolModel(),enabled=elem.has_debug_vis_implementation,checked=is_checked,on_checked_fn=lambdavalue,e=weakref.proxy(elem):e.set_debug_vis(value),)isaacsim.gui.components.ui_utils.add_line_rect_flourish()# Create a panel for the debug visualizationifisinstance(elem,ManagerLiveVisualizer):self.ui_window_elements[f"{name}_panel"]=omni.ui.Frame(width=omni.ui.Fraction(1))ifnotelem.set_vis_frame(self.ui_window_elements[f"{name}_panel"]):print(f"Frame failed to set for ManagerLiveVisualizer: {name}")# Add listener for environment selection changesifisinstance(elem,ManagerLiveVisualizer):self._ui_listeners.append(elem)asyncdef_dock_window(self,window_title:str):"""Docks the custom UI window to the property window."""# wait for the window to be createdfor_inrange(5):ifomni.ui.Workspace.get_window(window_title):breakawaitself.env.sim.app.next_update_async()# dock next to properties windowcustom_window=omni.ui.Workspace.get_window(window_title)property_window=omni.ui.Workspace.get_window("Property")ifcustom_windowandproperty_window:custom_window.dock_in(property_window,omni.ui.DockPosition.SAME,1.0)custom_window.focus()