@@ -338,6 +338,57 @@ def view(filename, collision, rotate, scale=1.0, show_link_frame=False):
338338 gui_process .join ()
339339
340340
341+ def play (filename = None , collision = False , scale = 1.0 ):
342+ import time
343+
344+ gs .init ()
345+
346+ scene = gs .Scene (
347+ viewer_options = gs .options .ViewerOptions (
348+ camera_pos = (2.0 , 2.0 , 1.5 ),
349+ camera_lookat = (0.0 , 0.0 , 0.5 ),
350+ ),
351+ show_viewer = True ,
352+ )
353+
354+ if filename is None :
355+ scene .add_entity (gs .morphs .Plane ())
356+ scene .add_entity (gs .morphs .MJCF (file = "xml/franka_emika_panda/panda.xml" ))
357+ else :
358+ filename_lower = filename .lower ()
359+ morphs = gs .options .morphs
360+ surface = gs .surfaces .Default (vis_mode = "visual" if not collision else "collision" )
361+
362+ if filename_lower .endswith (morphs .USD_FORMATS ):
363+ scene .add_stage (
364+ morph = gs .morphs .USD (file = filename , collision = collision , scale = scale ),
365+ vis_mode = surface .vis_mode ,
366+ )
367+ elif filename_lower .endswith (morphs .URDF_FORMAT ):
368+ scene .add_entity (gs .morphs .URDF (file = filename , collision = collision , scale = scale ), surface = surface )
369+ elif filename_lower .endswith (morphs .MJCF_FORMAT ):
370+ scene .add_entity (gs .morphs .MJCF (file = filename , collision = collision , scale = scale ), surface = surface )
371+ elif filename_lower .endswith (morphs .MESH_FORMATS ):
372+ scene .add_entity (gs .morphs .Mesh (file = filename , collision = collision , scale = scale ), surface = surface )
373+ else :
374+ gs .raise_exception (
375+ f"Unsupported file format for 'gs play'. Expected { morphs .URDF_FORMAT } , "
376+ f"{ morphs .MJCF_FORMAT } , { morphs .MESH_FORMATS } , or { morphs .USD_FORMATS } ."
377+ )
378+
379+ scene .build ()
380+
381+ from genesis .ext .pyrender .overlay import ImGuiOverlayPlugin
382+
383+ plugin = ImGuiOverlayPlugin ()
384+ scene .viewer .add_plugin (plugin )
385+
386+ while scene .viewer .is_alive ():
387+ if plugin .should_step ():
388+ scene .step ()
389+ time .sleep (0.01 )
390+
391+
341392def animate (filename_pattern , fps ):
342393 import glob
343394
@@ -365,6 +416,19 @@ def main():
365416 parser_view .add_argument ("-s" , "--scale" , type = float , default = 1.0 , help = "Scale of the entity" )
366417 parser_view .add_argument ("-l" , "--link_frame" , action = "store_true" , default = False , help = "Show link frame" )
367418
419+ parser_play = subparsers .add_parser ("play" , help = "Interactive viewer with ImGui joint controls and simulation" )
420+ parser_play .add_argument (
421+ "filename" ,
422+ type = str ,
423+ nargs = "?" ,
424+ default = None ,
425+ help = "Optional asset file (URDF/MJCF/Mesh/USD). Defaults to a demo scene." ,
426+ )
427+ parser_play .add_argument (
428+ "-c" , "--collision" , action = "store_true" , default = False , help = "Visualize collision geometry"
429+ )
430+ parser_play .add_argument ("-s" , "--scale" , type = float , default = 1.0 , help = "Scale of the entity" )
431+
368432 parser_animate = subparsers .add_parser ("animate" , help = "Compile a list of image files into a video" )
369433 parser_animate .add_argument ("filename_pattern" , type = str , help = "Image files, via glob pattern" )
370434 parser_animate .add_argument ("--fps" , type = int , default = 30 , help = "FPS of the output video" )
@@ -373,6 +437,8 @@ def main():
373437
374438 if args .command == "view" :
375439 view (args .filename , args .collision , args .rotate , args .scale , args .link_frame )
440+ elif args .command == "play" :
441+ play (args .filename , args .collision , args .scale )
376442 elif args .command == "animate" :
377443 animate (args .filename_pattern , args .fps )
378444 elif args .command is None :
0 commit comments