Cannot programatically open USD files in a recursive loop in Create


I have a scenario where i need to do hundreds of renders, each render with a different series of usd layers. To individually load and render out each scene takes about a week of work, and i am writing an extension to automate the render process, but am having difficulties.

The first step is to recursively loop through my variables and save the persistent usd’s, ready to be sent to Farm. I have a more complex version of the program i am pasting here, but i have recreated a minimal version of the recursion to simulate the bug or problem i am having.

On the first loop, everything works as expected, the initial persistent usd loads, i am able to programatically add layers to it, programatically save it, then call the second recursive loop. This is where it stumbles, it is able to load the persistent level again, but when calling the event function, if you do some logging, you see that it loads 5 events on opening the USD and then crashes to the desktop just before it gets to the ASSETS_LOADED event. And so my automated tool crashes here and i cannot get the recursion to continue and save all the USD’s i need.

The code for the minimal replication is here:

import threading

import omni.ext

import omni.ui as ui

import omni.usd

import omni.kit

import asyncio

import json

import sys

import getpass

import carb

import time

import as _services_client

# Any class derived from `omni.ext.IExt` in top level module (defined in `python.modules` of `extension.toml`) will be

# instantiated when extension gets enabled and `on_startup(ext_id)` will be called. Later when extension gets disabled

# on_shutdown() is called.

class MyExtension(omni.ext.IExt):

    persistentLevel = "E:/Programming/IKHNucleus/Omni IMKH/2400mm/IMK_Foliage.usd"

    layer1 = "E:/Programming/IKHNucleus/Omni IMKH/2400mm/IMK_Rocks.usd"

    layer2 = "E:/Programming/IKHNucleus/Omni IMKH/2400mm/IMK_Ecohome_24_Slab.usd"

    layer3Loop1 = "E:/Programming/IKHNucleus/Omni IMKH/2400mm/IMK_Z_HardieFlex24.usd"

    layer3Loop2 = "E:/Programming/IKHNucleus/Omni IMKH/2400mm/IMK_Z_Primeline24.usd"

    saveLocationLoop1 = "E:/Programming/IKHNucleus/Omni IMKH/Save/O1_0-O2_0-O3_0.usd"

    saveLocationLoop2 = "E:/Programming/IKHNucleus/Omni IMKH/Save/O1_0-O2_0-O3_1.usd"

    simulateLoop = 0

    assetsLoaded = 0

    inst = ''


    def on_stage_event(event):

            # On both loops, this event is called multiple times until finally the assets are loaded for the root layer

            # On the second recursive loop, all the events are called execpt for ASSETS_LOADED, this is where it crashes to desktop

            if event.type == int(omni.usd.StageEventType.ASSETS_LOADED):

                stage = omni.usd.get_context().get_stage()

                root_layer = stage.GetRootLayer()

                if MyExtension.assetsLoaded == 0:


                    MyExtension.assetsLoaded = 1

                elif MyExtension.assetsLoaded == 1:  


                    MyExtension.assetsLoaded = 2

                elif MyExtension.assetsLoaded == 2:  

                    if MyExtension.simulateLoop == 0:


                    elif MyExtension.simulateLoop == 1:


                    MyExtension.assetsLoaded = 3

                elif MyExtension.assetsLoaded == 3:

                    if MyExtension.simulateLoop == 0:


                    elif MyExtension.simulateLoop == 1:


            if event.type == int(omni.usd.StageEventType.SAVED):

                MyExtension.simulateLoop += 1

                MyExtension.assetsLoaded = 0

                if MyExtension.simulateLoop <= 1:

                    #Call render loop recursively



    def renderLoop():

            # Begin recursive function

            event_stream = omni.usd.get_context().get_stage_event_stream()

            MyExtension.stage_event_sub = event_stream.create_subscription_to_pop(MyExtension.on_stage_event)        

            if MyExtension.simulateLoop == 0:


            elif MyExtension.simulateLoop == 1:



    # ext_id is current extension id. It can be used with extension manager to query additional information, like where

    # this extension is located on filesystem.

    def on_startup(self, ext_id):

        print("[] MyExtension startup")

        self._window_title = "ConceptV Render Automation"

        self._menu_path = "Window/ConceptV Render"

        self._window = None


            self._menu = omni.kit.ui.get_editor_menu().add_item(

                self._menu_path, self.show_window, toggle=True, value=False


        except Exception as e:

            self._menu = None


    def on_shutdown(self):

        print("[] MyExtension shutdown")

        if self._window:


        self._window = None

        self._menu = None

    def build_window(self):

        self._window = ui.Window(self._window_title, width=300, height=300, visible=True)


        with self._window.frame:

            with ui.VStack():

                ui.Label("Load layers and begin render")

                def load_layers():



                ui.Button("Load Layers", clicked_fn=lambda: load_layers())

    def show_window(self, menu, value):

        if not self._window:


        elif self._window:

            self._window.visible = value


    def _visibility_changed_fn(self, value):

        if self._menu:

            omni.kit.ui.get_editor_menu().set_value(self._menu_path, value)

Hello @michaelbaggott! I reached out to the development team for some more help!

Thank you Wendy :)

Hi @michaelbaggott. Because it sounds like you’re just preparing stages to then render as a second step. I think it’s much easier to use vanilla USD for this. Note that you won’t see content load into the viewport and stage outliner, but this is probably a benefit in this case. I’ve adapted your example, but hopefully it’s still similar in essence to what you’re trying to do:

from pxr import Usd

base_layer = "omniverse://localhost/Projects/sublayer_test/base.usd"
layer_configs = [
		"inputs": [
		"output": "omniverse://localhost/Projects/sublayer_test/merge1.usd"
		"inputs": [
		"output": "omniverse://localhost/Projects/sublayer_test/merge2.usd"

for config in layer_configs:
	stage = Usd.Stage.Open(base_layer)
	root_layer = stage.GetRootLayer()
	for sublayer in config["inputs"]:
	# Using reload here to clear the changes for the next loop

Thank you for your time Mati,

Yes we don’t care about showing the changes in the viewport, we are doing our level editing in another application.

I will give this a try.