Files
isaac/scripts/export_item_library.py
T
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

77 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Flatten the classified objects out of the working scene into assets/items/.
/home/whatevenif/isaacsim/python.sh scripts/export_item_library.py
The six meshes bundled in `assets/meshes/` are a smoke-test set, two per class. A sorting
run wants the whole catalogue, and the classified objects live inside
`robozon_conveyor_scaled.usd` under `/World/CVObjects`, referencing `.glb` files that are
not in this repo. Each one is therefore *flattened* on export so the result carries its own
geometry and composes anywhere - the recipe in CLAUDE.md, applied in bulk.
Ground truth comes from `categories.json`: `zone` (B/C/D) and `obb_extents_m`, which the
run harness reports predicted dimensions against. Objects larger than the 500 mm incoming
envelope are skipped - they could never reach this conveyor.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from pxr import Usd, UsdGeom
ASSETS = Path("/home/dasha/isaac_assets")
SCENE = ASSETS / "robozon_conveyor_scaled.usd"
CATS = ASSETS / "categories.json"
OUT = Path(__file__).resolve().parent.parent / "assets" / "items"
ENVELOPE_MM = 500.0
def main():
if not SCENE.exists():
sys.exit(f"{SCENE} not found")
cats = json.loads(CATS.read_text())
OUT.mkdir(parents=True, exist_ok=True)
src = Usd.Stage.Open(str(SCENE))
root = src.GetPrimAtPath("/World/CVObjects")
if not root.IsValid():
sys.exit("/World/CVObjects missing")
present = {p.GetName() for p in root.GetChildren()}
manifest, skipped = {}, []
for name, meta in sorted(cats.items()):
if name not in present:
skipped.append((name, "not in scene"))
continue
ext_mm = [e * 1000.0 for e in meta["obb_extents_m"]]
if max(ext_mm) > ENVELOPE_MM:
skipped.append((name, f"oversize {max(ext_mm):.0f} mm"))
continue
ns = Usd.Stage.CreateInMemory()
item = UsdGeom.Xform.Define(ns, "/Item")
item.GetPrim().GetReferences().AddReference(str(SCENE), f"/World/CVObjects/{name}")
ns.SetDefaultPrim(item.GetPrim())
dst = OUT / f"{name}.usd"
ns.Flatten().Export(str(dst))
manifest[name] = dict(zone=meta["zone"],
gt_dims_mm=[round(v) for v in sorted(ext_mm, reverse=True)],
k_round=round(meta.get("k_round", 0.0), 3),
label_ru=meta.get("label_ru", ""))
print(f" {name:22s} {meta['zone']} {manifest[name]['gt_dims_mm']} "
f"{dst.stat().st_size / 1024:.0f} KB")
(OUT / "manifest.json").write_text(json.dumps(manifest, indent=2, ensure_ascii=False))
from collections import Counter
print(f"\nexported {len(manifest)} -> {OUT} "
f"{dict(Counter(v['zone'] for v in manifest.values()))}")
if skipped:
print(f"skipped {len(skipped)}: " + ", ".join(f"{n} ({w})" for n, w in skipped[:8]))
if __name__ == "__main__":
main()