Having trouble wrapping my mind around resetting all asset positions and only randomizing a select random sample on each frame. That sounded simple in our call but I don’t see any rep.randomizer.* tools that would allow for this. The on_frame
only pre-builds a graph with no conditional elements.
It looks to me like you have the right idea. I think we can even avoid needing to use a custom trigger. Here’s a simplified script. Let me know if this approach works, or if I missed anything.
import random
import omni.replicator.core as rep
NUM_TOTAL_ASSETS = 100
NUM_VISIBLE_ASSETS = 2
UNDERGROUND = (0, 0, -10000)
ASSET_ROOT = "omniverse://localhost/NVIDIA/Assets/Isaac/4.2/Isaac/Props/Mugs/"
# Set stage defaults
rep.settings.set_stage_up_axis("Z")
rep.settings.set_stage_meters_per_unit(1.0)
# Create a plane
ground = rep.create.plane(scale=(10, 10, 1))
# Get assets
# You'll want to randomize their variants here as well
asset_paths = rep.utils.get_usd_files(ASSET_ROOT)
ref_paths = []
for _ in range(NUM_TOTAL_ASSETS):
path = random.choice(asset_paths)
ref = rep.create.from_usd(path)
ref_paths.append(str(ref.get_output("prims")[0]))
# Setup on-frame graph
with rep.trigger.on_frame():
with rep.create.group(ref_paths):
rep.modify.pose(position=rep.distribution.choice([UNDERGROUND]))
with rep.distribution.choice(ref_paths, num_samples=NUM_VISIBLE_ASSETS):
rep.randomizer.scatter_2d(surface_prims=ground)
@jlafleche unfortunately I get the same behaviour. Nothing gets scattered on the plane. Video below of pasting and running your script.
Looking at the graph generated by your code I can clearly see that both operations (scatter and pose) run in parallel so we get a race condition to see who finishes last. Is there a way to tell the randomizer to maintain order of operations in on_frame? This behaviour is not well documented and I have ran into this issue many many times.
I have also tried removing the post operation from on_frame to see if thats really the case but unfortunately it doesn’t “fix” the scatter. Seems like this approach is bugged on the distribution call.
BTW even moving the pose op out of the on_frame it still adds it to the SDG graph.
Found it: the distribution output prims are not connecting to the Scatter prims input:
[Warning] [omni.graph.core.plugin] /Replicator/SDGPipeline/OgnSampleUniform.outputs:samples of type double3[] is not compatible with /Replicator/SDGPipeline/OgnScatter2D.inputs:prims of type rel
This is not a solution but confirms my observations:
-
Connecting the group to the scatter manually makes it scatter.
-
The operations are still parallel so we have a race condition. Unplugging the pose operation does indeed scatter things.
Seems like the proposed approach is a no go @jlafleche. I would be very appreciative for any kind of alternative so we can get on with things.
@pcallender @jlafleche any ideas? Custom OGN?
Ahh, I see. Isaac Sim 4.2 is missing a Choice
node that is compatible with the Targets
Omnigraph datatype - that leads to no prims being attached to the scatter node and therefore no way for it to know to attach them sequentially.
Here’s a revised script that is compatible with Isaac Sim 4.2 using a custom AutoNode to add the missing functionality. I apologize for not testing on 4.2 before passing you the snippet. This workaround will be unnecessary when the next version of Isaac is released.
import random
import omni.replicator.core as rep
import omni.graph.core as og
import omni.graph.core.types as ot
NUM_TOTAL_ASSETS = 100
NUM_VISIBLE_ASSETS = 2
UNDERGROUND = (0, 0, -10000)
ASSET_ROOT = "omniverse://localhost/NVIDIA/Assets/Isaac/4.2/Isaac/Props/Mugs/"
# Set stage defaults
rep.settings.set_stage_up_axis("Z")
rep.settings.set_stage_meters_per_unit(1.0)
# Create a plane
ground = rep.create.plane(scale=(10, 10, 1))
# Get assets
# You'll want to randomize their variants here as well
asset_paths = rep.utils.get_usd_files(ASSET_ROOT)
ref_paths = []
for _ in range(NUM_TOTAL_ASSETS):
path = random.choice(asset_paths)
ref = rep.create.from_usd(path)
ref_paths.append(str(ref.get_output("prims")[0]))
# WORKAROUND FOR ISAAC 4.2
# Create and register custom node
@rep.utils.ReplicatorWrapper
def choice_prims(prims, num_samples: int) -> rep.utils.ReplicatorItem:
@og.create_node_type(add_execution_pins=True, unique_name="omni.replicator.core.choice_prims")
def random_prims(prims: ot.target, num_prims: ot.int) -> ot.target:
import random
return random.choices(prims, k=num_prims)
return rep.utils.create_node("omni.replicator.core.choice_prims", prims=prims, num_prims=num_samples)
rep.distribution.register(choice_prims)
# Add AutoNode output "out_0" to list of prim connection names
rep.utils.ATTRIBUTE_MAPPINGS.add(rep.utils.AttrMap("outputs:out_0", "inputs:prims"))
# Add AutoNode input "execute" to list of exec connection names
rep.utils.ATTRIBUTE_MAPPINGS.add(rep.utils.AttrMap("outputs:exec", "inputs:execute"))
rep.utils.ATTRIBUTE_MAPPINGS.add(rep.utils.AttrMap("outputs:execOut", "inputs:execute"))
# END OF WOKAROUND
# Setup on-frame graph
with rep.trigger.on_frame():
with rep.create.group(ref_paths):
rep.modify.pose(position=rep.distribution.choice([UNDERGROUND]))
with rep.distribution.choice_prims(ref_paths, num_samples=NUM_VISIBLE_ASSETS):
rep.randomizer.scatter_2d(surface_prims=ground)
Your code works fine. Thank you so much I will apply it to my solution ASAP aka next week!
@jlafleche looking at the graph it still contains race condition between scattering and positioning to UNDERGROUND
. Am I looking at this incorrectly?
@jlafleche @pcallender unfortunately my assumptions of parallelism are correct. After adding more randomizer steps I now get everything sent to UNDERGROUND
and nothing is on the plane. Is there a way we can enforce an order of trigger operations on each frame?
I think I have reached the point where its more efficient to wait for a usd loader fix in 4.3 so I can just load all those USDs variants on each frame and so it just works. The further we go down this rabbit hole the more time we all burn and we are not getting any closer to resolving the problem.
I understand the furstration. I’m curious why you’re still seeing a race condition though. You should see the exce connection tied to a Rational Sync Gate
like so:
This gate makes it impossible for the scatter node to activate before the one moving prims underground. You can however also modify the node connection in the script to enforce sequential execution:
import random
import omni.replicator.core as rep
import omni.graph.core as og
import omni.graph.core.types as ot
NUM_TOTAL_ASSETS = 100
NUM_VISIBLE_ASSETS = 2
UNDERGROUND = (0, 0, -10000)
ASSET_ROOT = "omniverse://localhost/NVIDIA/Assets/Isaac/4.2/Isaac/Props/Mugs/"
# Set stage defaults
rep.settings.set_stage_up_axis("Z")
rep.settings.set_stage_meters_per_unit(1.0)
# Create a plane
ground = rep.create.plane(scale=(10, 10, 1))
# Get assets
# You'll want to randomize their variants here as well
asset_paths = rep.utils.get_usd_files(ASSET_ROOT)
ref_paths = []
for _ in range(NUM_TOTAL_ASSETS):
path = random.choice(asset_paths)
ref = rep.create.from_usd(path)
ref_paths.append(str(ref.get_output("prims")[0]))
# WORKAROUND FOR ISAAC 4.2
# Create and register custom node
@rep.utils.ReplicatorWrapper
def choice_prims(prims, num_samples: int) -> rep.utils.ReplicatorItem:
@og.create_node_type(add_execution_pins=True, unique_name="omni.replicator.core.choice_prims")
def random_prims(prims: ot.target, num_prims: ot.int) -> ot.target:
import random
return random.choices(prims, k=num_prims)
return rep.utils.create_node("omni.replicator.core.choice_prims", prims=prims, num_prims=num_samples)
rep.distribution.register(choice_prims)
# Add AutoNode output "out_0" to list of prim connection names
rep.utils.ATTRIBUTE_MAPPINGS.add(rep.utils.AttrMap("outputs:out_0", "inputs:prims"))
# Add AutoNode input "execute" to list of exec connection names
rep.utils.ATTRIBUTE_MAPPINGS.add(rep.utils.AttrMap("outputs:exec", "inputs:execute"))
rep.utils.ATTRIBUTE_MAPPINGS.add(rep.utils.AttrMap("outputs:execOut", "inputs:execute"))
# END OF WOKAROUND
# Setup on-frame graph
trigger = rep.trigger.on_frame()
with trigger:
with rep.create.group(ref_paths):
rep.modify.pose(position=rep.distribution.choice([UNDERGROUND]))
with rep.distribution.choice_prims(ref_paths, num_samples=NUM_VISIBLE_ASSETS):
scatter = rep.randomizer.scatter_2d(surface_prims=ground)
# Enforce sequential execution
scatter_exec_in = scatter.node.get_attribute("inputs:execIn")
og.Controller.disconnect_all(scatter_exec_in)
trigger_last_exec = rep.utils._get_last_exec_attr(trigger.node)
trigger_last_exec.connect(scatter_exec_in, True)