HelloWorld Headless PNG from Scene Help

Hi,

I have been trying for sometime to generate a simple cube on a plane in a trivial scene in a headless setup, basically a “Hello World” to help me better understand the platform… A jumping off point to learn more… I have followed several tutorials and can now generate images, but they are always all WHITE, GRAY, or BLACK (depending on my lighting) but I never can see the cube. I am def. doing something wrong, but I can’t figure out what. It would be greatly appreciated if someone could take a look at this code, and even run it on their system, to see help me rule-out environment issues. Or failing that, point me to a better tutorial than the one’s I have been attempting to follow :)

Info: I am running headless on an RTX3060, by no means is this system a beast but it’s more than enough for this exercise. I am attempting to generate a few images (not 10’s or 100’s of thousands… the demo code generates 10). I am using the NVIDIA-supplied Docker base image (nvcr.io/nvidia/isaac-sim:2023.1.1) with the “docker run -it --gpus all --privileged” correctly configured to allow access to the GPU etc… Below is the code, any pointers would be much appreciated as I have spent WAY to long on this and need a second set of eyes.

Thank you!

from omni.isaac.kit import SimulationApp
import math
import os

# I have tried both RayTracedLighting and HydraRender"
#
#simulation_app = SimulationApp({
#    "headless": True,
#    "renderer": "RayTracedLighting",
#    "useExtension": True,
#})

simulation_app = SimulationApp({
    "headless": True,
    "renderer": "HydraRenderer",
    "useExtension": True,
})


import omni.kit.app
import omni.replicator.core as rep

kit = omni.kit.app.get_app()
kit.get_extension_manager().set_extension_enabled_immediate("omni.replicator.core", True)

def main():
    OUTPUT_DIR = "/workspace/reach_ws/outputs"
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    rep.new_layer()

    # Create cube
    cube = rep.create.cube(position=(0, 0, 0.05), scale=(0.5, 0.5, 0.5))
    print(f"Cube: {cube}, type: {type(cube)}")

    # Create material
    material = rep.create.material_omnipbr(diffuse=(1.0, 0.0, 0.0))  # Red

    # Bind material to cube using known path
    with cube:
        rep.modify.attribute("material:binding", material)

    # Add ground plane
    rep.create.plane(scale=(1, 1, 1), position=(0, 0, 0))

    # Add lights (Dome and distant)
    rep.create.light(
        light_type="dome",  # dome is good for soft environment light
        intensity=10000,    # increase intensity a lot to start
        rotation=(45, 0, 0) # tilt so light comes from above
    )

    rep.create.light(
        light_type="distant",
        intensity=15000,
        rotation=(-45, 0, 0)
    )

    # Add camera
    camera = rep.create.camera(position=(0, -0.3, 0.25), rotation=(-33, 0, 0))

    # Render product
    render_product = rep.create.render_product(camera, (640, 480))

    # Writer setup
    writer = rep.WriterRegistry.get("BasicWriter")
    writer.initialize(output_dir=OUTPUT_DIR, rgb=True)
    writer.attach([render_product])

    # Let it warm up and render
    rep.orchestrator.run()
    for _ in range(10):
        rep.orchestrator.step()
        print(f"Image saved to {OUTPUT_DIR}/replicator_0/RenderProduct_0/rgb_00{_}.png")

    writer.detach()

    print(f"Render complete")

    simulation_app.close()

if __name__ == "__main__":
    main()

Ok, I finally succumb and sought help from the GenAI Gods… Long story short, and it’s just anecdotal… but ChatGPT repeatedly led me down the wrong path for hours. CoPilot had me up and running with an example in about 15 minutes of debugging. My updated HelloWorld is below, this works, 100% within the same Docker container and same NVIDIA image, same run command, etc. I’m hoping this helps someone else if they come across this question, I know this working code will now help me better understand Isaac Sim.

from omni.isaac.kit import SimulationApp
import math
import os

simulation_app = SimulationApp({
    "headless": True,
    "renderer": "RayTracedLighting",
    "useExtension": True,
})


import omni.kit.app

kit = omni.kit.app.get_app()
kit.get_extension_manager().set_extension_enabled_immediate("omni.replicator.core", True)
import omni.replicator.core as rep

def main():
    OUTPUT_DIR = "/workspace/reach_ws/outputs"
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    # Create a plane
    rep.create.plane(scale=(1, 1, 1), position=(0, 0, 0))

    # Create cube
    cube = rep.create.cube(position=(-0.2, 0, 0.3), scale=(0.5, 0.5, 0.5))

    # Create sphere
    sphere = rep.create.sphere(position=(0.2, 0, 0.3), scale=(0.2, 0.2, 0.2))

    # Create a light, I don't have a texture local... need to update with: 
    # texture="omniverse://localhost/NVIDIA/Assets/Skies/CloudySky.exr"
    rep.create.light(
       light_type="dome",
       intensity=1000
    )

    # Create the camera and be sure the cube and sphere are in sight    
    camera = rep.create.camera(
        position=(0, -1.5, 0.5),  # Pull back and raise the camera
        look_at=(0, 0, 0.3),      # Aim directly at the cube/sphere
        clipping_range=(0.01, 1000.0)
    )

    # Render product
    rep.orchestrator.step(rt_subframes=4)
    render_product = rep.create.render_product(camera, (640, 480))

    # Writer setup
    writer = rep.WriterRegistry.get("BasicWriter")
    writer.initialize(output_dir=OUTPUT_DIR, rgb=True)
    writer.attach([render_product])

    # Make sure simulation_app.update() is called at least once before rendering begins. 
    # This helps initialize the stage and extensions properly.
    simulation_app.update()

    # Let it warm up and render
    rep.orchestrator.run()
    for _ in range(10):
        rep.orchestrator.step()
        print(f"Image saved to {OUTPUT_DIR}/replicator_0/RenderProduct_0/rgb_00{_}.png")

    writer.detach()

    print(f"Render complete")

    simulation_app.close()

if __name__ == "__main__":
    main()