Files
isaac/scripts/verify_pusher.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

97 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Пушер после снятия коллизии с луча лазера.
Товар вставал на x = -3.044 и не доходил до точки срабатывания PUSH_X = -3.9. Причина
найдена в списке коллайдеров: /World/SortingRig/LaserGate/Beam - визуализация луча
датчика - имеет ВКЛЮЧЁННУЮ коллизию и перекрывает всю ширину полотна (y -0.42..+0.42
при ленте -0.45..+0.45) на высоте 19 мм над лентой. Датчик должен смотреть, а не
преграждать; коллизия с него снимается, после чего проверяется сам пушер.
"""
import sys, math
REPO = "/home/dasha/robozon-sorter"
if REPO not in sys.path:
sys.path.insert(0, REPO)
import omni.usd, omni.timeline
import isaacsim.core.experimental.utils.app as app_utils
from pxr import Gf, Usd, UsdGeom, UsdPhysics, PhysxSchema, UsdShade
from isaacsim.core.experimental.prims import RigidPrim
from robozon_sorter import config as C
from robozon_sorter.sim import plow_cell
from robozon_sorter.sim.mechanics import Cell
from robozon_sorter.sim.plow import Plow
stage = omni.usd.get_context().get_stage()
tl = omni.timeline.get_timeline_interface()
if tl.is_playing():
tl.stop(); await app_utils.update_app_async(steps=10)
# снять коллизию со всей визуализации датчиков
off = []
for p in stage.Traverse():
path = str(p.GetPath())
if "LaserGate" in path or "AimRay" in path or "TriggerRay" in path:
a = p.GetAttribute("physics:collisionEnabled")
if a:
a.Set(False); off.append(path)
elif p.HasAPI(UsdPhysics.CollisionAPI):
UsdPhysics.CollisionAPI(p).CreateCollisionEnabledAttr().Set(False); off.append(path)
print(f"коллизия снята с {len(off)} премов визуализации датчиков:")
for o in off:
print(f" {o}")
bb = UsdGeom.BBoxCache(Usd.TimeCode.Default(), [UsdGeom.Tokens.default_, UsdGeom.Tokens.render])
TOP = bb.ComputeWorldBound(stage.GetPrimAtPath("/World/ConveyorTrack_03/Belt")
).ComputeAlignedRange().GetMax()[2]
GRIP = stage.GetPrimAtPath(plow_cell.GRIP_MATERIAL)
if stage.GetPrimAtPath("/World/_Goods").IsValid():
stage.RemovePrim("/World/_Goods")
stage.DefinePrim("/World/_Goods", "Xform")
c = UsdGeom.Cube.Define(stage, "/World/_Goods/push_D"); c.CreateSizeAttr().Set(2.0)
xf = UsdGeom.Xformable(c.GetPrim())
xf.AddTranslateOp().Set(Gf.Vec3d(-2.40, 0.0, TOP + 0.055))
xf.AddScaleOp().Set(Gf.Vec3f(0.05, 0.05, 0.05))
p = c.GetPrim()
UsdPhysics.RigidBodyAPI.Apply(p); UsdPhysics.CollisionAPI.Apply(p)
UsdPhysics.MassAPI.Apply(p).CreateMassAttr().Set(0.5)
rb = PhysxSchema.PhysxRigidBodyAPI.Apply(p)
rb.CreateEnableCCDAttr().Set(True); rb.CreateSolverPositionIterationCountAttr().Set(32)
if GRIP.IsValid():
UsdShade.MaterialBindingAPI.Apply(p).Bind(
UsdShade.Material(GRIP), bindingStrength=UsdShade.Tokens.strongerThanDescendants,
materialPurpose="physics")
cell = Cell(stage, items={})
plow = Plow(stage); plow.target(C.PLOW_PRESET["D"])
print(f"\nход ножа {C.BLADE_HOME_Y} -> {C.BLADE_OUT_Y} ({C.BLADE_STROKE*1000:.0f} мм), "
f"срабатывание у PUSH_X={C.PUSH_X}, скорость ножа {C.PUSHER_SPEED} м/с")
tl.play(); await app_utils.update_app_async(steps=25)
rp = RigidPrim(paths=["/World/_Goods/push_D"])
P, fired, t_str = [], False, None
t0 = float(tl.get_current_time()); t_end = t0 + 7.0
print("\n t,с x y событие")
while float(tl.get_current_time()) < t_end:
q = rp.get_world_poses()[0].numpy()[0]
P.append(q.copy())
t = float(tl.get_current_time())
ev = ""
if not fired and float(q[0]) <= C.PUSH_X + 0.08:
ev = "КОМАНДА ножу"
print(f" {t-t0:5.2f} {float(q[0]):+7.3f} {float(q[1]):+6.3f} {ev}")
t_str = await cell.stroke(app_utils, out=True)
fired = True
ev = ""
if len(P) % 8 == 1:
print(f" {t-t0:5.2f} {float(q[0]):+7.3f} {float(q[1]):+6.3f} {ev}")
await app_utils.update_app_async(steps=4)
tl.stop(); await app_utils.update_app_async(steps=5)
dy = float(P[-1][1]) - float(P[0][1])
dx = float(P[-1][0]) - float(P[0][0])
print(f"\n нож сработал: {fired}, ход занял {t_str if t_str else 0:.2f} с")
print(f" товар: ({float(P[0][0]):+.2f},{float(P[0][1]):+.2f}) -> "
f"({float(P[-1][0]):+.2f},{float(P[-1][1]):+.2f})")
print(f" смещение: по X {dx*1000:+.0f} мм, по Y {dy*1000:+.0f} мм")
print(f" ВЫВОД: {'ТОВАР УВЕДЁН НА ВЕТКУ' if dy > 0.20 else 'товар НЕ уведён на ветку'}")