Files
isaac/scripts/live_demo_webrtc.py
dasha_f 0d32f32db0 Сортировочная ячейка Isaac Sim: CV-пайплайн и меши товаров
Замкнутый контур "поток -> CV -> механика": товары идут по конвейеру с шагом 700 мм,
класс определяется стереопайплайном во время движения, пушер и плуг реагируют физически.

Состав:
* control_test/ - ячейка и CV. run_sorting_cv.py + cv_worker.py (два процесса, потому что
  torch внутри Isaac роняет сцену), cell.py (физика лент, плуга, пушера), measure_plane.py
  (замер габаритов), README.md и .memory.md с замерами, проблемами и ловушками
* robozon_sorter/ - модули симуляции, scripts/ - утилиты, scene/ - сцены
* assets/ - меши товаров, плуг, объекты Objaverse

Бейзлайн CV: DEFOM-Stereo vitl, вход 480, iters 24, кроп зоны осмотра, без сегментации.
На потоке 700 мм - классы 8/9, габариты MAE 32.8 мм, 469 мс на товар при такте 700 мс.

Веса моделей (4.5 ГБ) и пропсы конвейера NVIDIA (274 МБ) не включены - источники и
команды скачивания в MODELS.md. Выход прогонов (captures/, runtime/) не включён:
воспроизводится.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 13:07:24 +00:00

87 lines
3.4 KiB
Python

"""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")