Сортировочная ячейка 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>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""Isolated pusher diagnostic: place one item right at the blade, sweep it, log velocity
|
||||
every step. Determines push (item picks up tangential speed) vs teleport (item barely
|
||||
moves, gets a depenetration nudge, stops) - probe_push_physics.py's own distinction.
|
||||
Reuses the CURRENTLY prepared stage (belts/plow/pusher/rails already configured by the
|
||||
last full run) rather than reopening.
|
||||
"""
|
||||
import sys
|
||||
REPO = "/home/dasha/robozon-sorter"
|
||||
if REPO not in sys.path:
|
||||
sys.path.insert(0, REPO)
|
||||
for _m in [k for k in list(sys.modules) if k.startswith("robozon_sorter")]:
|
||||
del sys.modules[_m]
|
||||
import importlib
|
||||
importlib.invalidate_caches()
|
||||
|
||||
import omni.usd, omni.timeline
|
||||
from pxr import Gf, UsdGeom, UsdPhysics, PhysxSchema
|
||||
import isaacsim.core.experimental.utils.app as app_utils
|
||||
from isaacsim.core.experimental.prims import RigidPrim
|
||||
|
||||
from robozon_sorter import config as C
|
||||
from robozon_sorter.sim import scene as _scene
|
||||
|
||||
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)
|
||||
|
||||
print("=== blade geometry now ===")
|
||||
geom = stage.GetPrimAtPath("/World/Diverters/DiverterY_Split/Pusher/Geom")
|
||||
bbc = UsdGeom.BBoxCache(0, ["default", "render"])
|
||||
r = bbc.ComputeWorldBound(geom).ComputeAlignedRange()
|
||||
print(f" blade bbox x[{r.GetMin()[0]:+.3f}..{r.GetMax()[0]:+.3f}] "
|
||||
f"y[{r.GetMin()[1]:+.3f}..{r.GetMax()[1]:+.3f}] z[{r.GetMin()[2]:+.3f}..{r.GetMax()[2]:+.3f}]")
|
||||
print(f" BLADE_HOME_Y={C.BLADE_HOME_Y} BLADE_OUT_Y={C.BLADE_OUT_Y} PUSH_X={C.PUSH_X} BELT_Z={C.BELT_Z}")
|
||||
|
||||
blade_prim = stage.GetPrimAtPath(_scene.BLADE)
|
||||
print(" blade kinematic:", UsdPhysics.RigidBodyAPI(blade_prim).GetKinematicEnabledAttr().Get())
|
||||
print(" blade collision enabled (self):", blade_prim.GetAttribute("physics:collisionEnabled").Get())
|
||||
for c in blade_prim.GetChildren():
|
||||
print(" child", c.GetPath(), "collisionEnabled:", c.GetAttribute("physics:collisionEnabled").Get())
|
||||
|
||||
# reset blade to home
|
||||
def blade_op():
|
||||
for op in UsdGeom.Xformable(blade_prim).GetOrderedXformOps():
|
||||
if op.GetOpType() == UsdGeom.XformOp.TypeTranslate:
|
||||
return op
|
||||
blade_base = blade_op().Get()
|
||||
def blade_to(y):
|
||||
b = blade_base
|
||||
blade_op().Set(Gf.Vec3d(b[0], y - _scene.BLADE_PARENT_Y, b[2]))
|
||||
blade_to(C.BLADE_HOME_Y)
|
||||
|
||||
# place a fresh item just upstream of the blade, in its path
|
||||
name = "probe_item"
|
||||
items_dir = C.ROOT / "assets" / "items"
|
||||
prim = UsdGeom.Xform.Define(stage, f"/World/Items/{name}").GetPrim()
|
||||
prim.GetReferences().ClearReferences()
|
||||
prim.GetReferences().AddReference(str(items_dir / "box_300x200x200.usd"))
|
||||
xf = UsdGeom.Xformable(prim)
|
||||
xf.ClearXformOpOrder()
|
||||
xf.AddTranslateOp(precision=UsdGeom.XformOp.PrecisionDouble).Set(Gf.Vec3d(C.PUSH_X + 0.05, 0.0, C.BELT_Z + 0.10))
|
||||
UsdPhysics.RigidBodyAPI.Apply(prim)
|
||||
UsdPhysics.RigidBodyAPI(prim).CreateKinematicEnabledAttr().Set(False)
|
||||
UsdPhysics.MassAPI.Apply(prim).CreateMassAttr().Set(0.6)
|
||||
px = PhysxSchema.PhysxRigidBodyAPI.Apply(prim)
|
||||
px.CreateEnableCCDAttr().Set(True)
|
||||
px.CreateSolverPositionIterationCountAttr().Set(24)
|
||||
px.CreateSleepThresholdAttr().Set(0.0)
|
||||
UsdGeom.Imageable(prim).MakeVisible()
|
||||
rp = RigidPrim(paths=[f"/World/Items/{name}"])
|
||||
|
||||
tl.play()
|
||||
await app_utils.update_app_async(steps=40)
|
||||
p0 = rp.get_world_poses()[0].numpy()[0]
|
||||
print(f"\n--- settled at x={p0[0]:+.3f} y={p0[1]:+.3f} z={p0[2]:+.3f} ---")
|
||||
|
||||
print("\n--- sweeping blade out at PUSHER_MAX_SAFE, logging item state ---")
|
||||
speed = C.PUSHER_MAX_SAFE
|
||||
a, b = C.BLADE_HOME_Y, C.BLADE_OUT_Y
|
||||
duration = abs(b - a) / speed
|
||||
t0 = float(tl.get_current_time())
|
||||
i = 0
|
||||
print(f"{'i':>3} {'t':>6} {'blade_y':>8} {'item_x':>8} {'item_y':>8} {'item_z':>8} {'vx':>7} {'vy':>7}")
|
||||
while True:
|
||||
u = min(1.0, (float(tl.get_current_time()) - t0) / duration)
|
||||
blade_to(a + (b - a) * u)
|
||||
await app_utils.update_app_async(steps=1)
|
||||
p = rp.get_world_poses()[0].numpy()[0]
|
||||
v = rp.get_velocities()[0].numpy()[0]
|
||||
if i % 3 == 0 or u >= 1.0:
|
||||
print(f"{i:3d} {float(tl.get_current_time())-t0:6.3f} {a+(b-a)*u:8.3f} "
|
||||
f"{p[0]:8.3f} {p[1]:8.3f} {p[2]:8.3f} {v[0]:7.3f} {v[1]:7.3f}")
|
||||
i += 1
|
||||
if u >= 1.0:
|
||||
break
|
||||
|
||||
p_final = rp.get_world_poses()[0].numpy()[0]
|
||||
print(f"\n--- after stroke: x={p_final[0]:+.3f} y={p_final[1]:+.3f} (started y={p0[1]:+.3f}, moved {p_final[1]-p0[1]:+.3f}) ---")
|
||||
tl.stop()
|
||||
Reference in New Issue
Block a user