Rendering from command line without UI

Hi, I’m running a script from powershell trying to render a usd file using a specific camera to file. How can I do it headlessly?

  1. I’m using CaptureExtension for that and I’ve encountered an issue when trying to start the render because there’s no viewport to speak of since the script doesn’t launch USD Composer with all the UI. It stops on self._capture_instance.start():
import carb
import asyncio 
import carb.settings
from pathlib import Path
import omni.usd
from pxr import Usd, UsdGeom

from omni.kit.capture.viewport import CaptureExtension
from omni.kit.capture.viewport.capture_options import CaptureOptions


class HeadlessRenderer:
    
    def __init__(self) -> None:
        self.task = None
        self._capture_instance = CaptureExtension.get_instance() 
        self._options = CaptureOptions()
        self._settings = carb.settings.get_settings()
        self.stage = Usd.Stage.Open(self._settings.get("/stageToOpen"))
        self._CameraList = [x for x in self.stage.Traverse() if x.IsA(UsdGeom.Camera)]
        self._is_frame_rendered = False
        self._isRendering = False


    def OnRenderFinished(self):
        self._isRendering = False        


    async def _single_render(self):
        SECONDS_TO_WAIT = 5 
        SECONDS_EACH_TIME = 1

        self._options.camera = self._CameraList[0].GetPath()
        self._options.file_type = ".png"
        self._options.path_trace_spp = 16
        self._options.ptmb_subframes_per_frame = 1
        self._options.file_name = "TEST"
        self._options.output_folder = "C:\\Projects\\Renders" 
        self._options.res_height = 1080
        self._options.res_width = 1920
        self._options.render_product = ''

        path = self._options.output_folder + "/" + self._options.file_name
        self._capture_instance.options = self._options

        self._capture_instance.capture_finished_fn = self.OnRenderFinished

        self._capture_instance._frame_path = str(Path(path))

        '''I've tried creating an instance of a viewport but it doesn't work as such'''
        # viewport_interface = omni.kit.viewport.get_viewport_interface()
        # viewport_handle = viewport_interface.create_instance()
        # viewport = viewport_interface.get_viewport_window(viewport_handle)
        # viewport.set_active_camera(self._CameraList[0].GetPath())

        await omni.kit.app.get_app().next_update_async()

        carb.log_error("________________1")

        '''the script stops at this: '''
        self._capture_instance.start()

        carb.log_error("________________2")

        self._capture_instance._frame_pattern_prefix = str(Path(path))

        while seconds_tried < SECONDS_TO_WAIT: 
            await asyncio.sleep(SECONDS_EACH_TIME)
            seconds_tried += SECONDS_EACH_TIME

        return True

    
    def Render(self):
        loop = asyncio.get_event_loop()
        self.task = loop.create_task(
            self._single_render()) 
        
renderer = HeadlessRenderer()
renderer.Render()

So this is all it prints:

  1. I’ve tried using omni.kit.renderer.capture to circumvent having to access viewport, but it seems it still needs it, and thus the script stops on viewport_ldr_rp as well in that case:
import omni.kit.renderer.capture

        render_capture = omni.renderer_capture.acquire_renderer_capture_interface()

        viewport_ldr_rp = omni.kit.viewport.get_viewport_interface().get_viewport_window(None).get_drawable_ldr_resource()