"""Self-running plow demo for watching over WebRTC. Sent into the live streaming Kit. Unlike the test harness this does **not** block in a loop: it hooks the feeder and the plow onto the physics step, aims the viewport at the plow, presses Play and returns. The cell then runs on its own for as long as the session lives, which is what makes it watchable in the browser - a blocking script would hold the interpreter and the stream would show a frozen frame. The subscriptions are stashed in the module namespace on purpose. A PhysX step subscription dies the moment its Python handle is garbage-collected, so a demo that forgets to keep a reference stops after the call returns and looks like the scene simply ignoring the Play button. """ import sys REPO = "/home/dasha/robozon-sorter" if REPO not in sys.path: sys.path.insert(0, REPO) import importlib for _m in [k for k in list(sys.modules) if k.startswith("robozon_sorter")]: del sys.modules[_m] importlib.invalidate_caches() import omni.timeline import isaacsim.core.experimental.utils.app as app_utils from isaacsim.core.rendering_manager import ViewportManager from robozon_sorter import config as C from robozon_sorter.sim import lane_beams, plow_sort, plow_vision, staging from robozon_sorter.sim.mechanics import Cell from robozon_sorter.sim.spawner import AutoFeeder SPEED = float(globals().get("speed", 0.8)) PITCH = float(globals().get("pitch", 3.0)) RATE = float(globals().get("rate", 300.0)) N = int(globals().get("n", 8)) C.BELT_SPEED = SPEED C.PLOW_SWEEP_RATE = RATE stage, info = plow_vision.load(belt_speed=SPEED, script_control=True, meshes_dir=f"{REPO}/assets/items") staging.stage_cell(stage, preset="bright", floor=True) plow_sort.keep_lanes_active(stage) lanes = plow_sort.configure_lanes(stage, SPEED) plow_sort.open_junction(stage) items = {k: v["zone"] for k, v in info["items"].items()} await app_utils.update_app_async(steps=40) cell = Cell(stage, items.keys()) cell.park_all() await app_utils.update_app_async(steps=15) order = ([n for n in sorted(items) if items[n] == "B"][:3] + [n for n in sorted(items) if items[n] == "C"][:3] + [n for n in sorted(items) if items[n] == "D"][:2])[:N] sorter = plow_sort.PlowSorter(stage, cell, items, plow_sort.calibrate_mapping()) beams = lane_beams.LaneBeams(stage, cell, plow=sorter.plow) def _step(dt): try: sorter.update(dt) beams.tick(dt) beams.poll(rate=RATE) except Exception: pass from omni.physx import get_physx_interface # keep the handles alive in the namespace or the callbacks are collected and the cell stops STEP_SUB = get_physx_interface().subscribe_physics_step_events(_step) FEEDER = AutoFeeder(cell, order=order, pitch=PITCH, route=dict(items), loop=True).install() # look at the plow from the discharge side so the sweep and both lanes are in frame ViewportManager.set_camera_view("/OmniverseKit_Persp", eye=[-5.2, -3.4, 3.2], target=[-7.0, 0.0, 1.9]) await app_utils.update_app_async(steps=20) app_utils.play(commit=True) await app_utils.update_app_async(steps=20) print(f"LIVE: {len(order)} items looping, pitch {PITCH} m @ {SPEED} m/s, " f"sweep {RATE} deg/s (tip {C.PLOW_ARM_LEN * RATE * 3.14159 / 180:.2f} m/s)") print(f"order: {order}") print(f"lanes/decks driven: {len(lanes)} | mapping {sorter.mapping}") print("running on the physics step - the stream stays live, nothing is blocking")