Сортировочная ячейка 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,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Close the two geometry faults that stop goods reaching a tray.
|
||||
|
||||
/home/whatevenif/isaacsim/python.sh scripts/fix_plow_reach_and_trays.py
|
||||
|
||||
**1. Tray B is walled shut on the side goods arrive from.** Measured:
|
||||
|
||||
B_W0 (near, y -2.55) z 1.16 .. 2.00 <- 220 mm ABOVE the lane belt (1.7805)
|
||||
B_W1 (far, y -3.35) z 1.16 .. 1.70
|
||||
tray C, correctly built:
|
||||
C_W1 (near, y +1.88) z 1.16 .. 1.70 <- 80 mm BELOW the belt, goods slide over
|
||||
C_W0 (far, y +2.68) z 1.16 .. 2.00
|
||||
|
||||
B has its tall backboard on the near face instead of the far one, so the lane runs
|
||||
goods straight into a wall. The two heights are swapped to match C.
|
||||
|
||||
**2. The plow cannot reach the lane.** The arm is 600 mm on a hinge at the belt centre, so
|
||||
its tip reaches ``0.6 * sin(limit)``. At the authored +-35 deg that is 344 mm, while
|
||||
the lanes start at |y| = 450 mm: a 106 mm dead zone no command can cross. Goods are
|
||||
nudged to about y 0.15 and left on the line, which is exactly what every trace shows.
|
||||
|
||||
Raising the joint limit to +-45 deg gives 424 mm of tip travel. That is still short of
|
||||
450 mm *at the centre of the item*, but an item is not a point: a 150 mm-wide box is
|
||||
carried by the lane once its near edge crosses, i.e. at a centre of about 375 mm, so
|
||||
42 deg (402 mm) delivers it with margin. Moving the lanes inboard instead was rejected -
|
||||
they would overlap the main belt, and two coincident belt colliders at the same height
|
||||
is its own failure.
|
||||
|
||||
Both edits are written back into scene/plow_cell.usd.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from pxr import Usd, UsdGeom, UsdPhysics
|
||||
|
||||
SCENE = Path(__file__).resolve().parent.parent / "scene" / "plow_cell.usd"
|
||||
HINGE = "/World/Diverters/DiverterEnd/ArmHinge"
|
||||
TRAYS = "/World/PlowContainers"
|
||||
|
||||
NEW_LIMIT = 45.0 # joint hard limit, degrees either side
|
||||
NEAR_WALL_TOP = 1.70 # must sit below the lane belt at 1.7805
|
||||
FAR_WALL_TOP = 2.00
|
||||
|
||||
|
||||
def _range(stage, path):
|
||||
return UsdGeom.BBoxCache(0, ["default"]).ComputeWorldBound(
|
||||
stage.GetPrimAtPath(path)).ComputeAlignedRange()
|
||||
|
||||
|
||||
def _set_top(stage, path, top):
|
||||
"""scale a wall Cube about its base so its top lands at `top`"""
|
||||
prim = stage.GetPrimAtPath(path)
|
||||
r = _range(stage, path)
|
||||
z0, z1 = r.GetMin()[2], r.GetMax()[2]
|
||||
if abs(z1 - top) < 1e-4:
|
||||
return None
|
||||
want = max(top - z0, 0.02)
|
||||
xf = UsdGeom.Xformable(prim)
|
||||
for op in xf.GetOrderedXformOps():
|
||||
if op.GetOpType() == UsdGeom.XformOp.TypeScale:
|
||||
s = op.Get()
|
||||
op.Set(type(s)(s[0], s[1], s[2] * (want / max(z1 - z0, 1e-6))))
|
||||
break
|
||||
else:
|
||||
return None
|
||||
# keep the base where it was: scaling a centred cube moves it by half the delta
|
||||
for op in xf.GetOrderedXformOps():
|
||||
if op.GetOpType() == UsdGeom.XformOp.TypeTranslate:
|
||||
t = op.Get()
|
||||
op.Set(type(t)(t[0], t[1], t[2] + (want - (z1 - z0)) / 2.0))
|
||||
break
|
||||
return (round(z1, 3), round(_range(stage, path).GetMax()[2], 3))
|
||||
|
||||
|
||||
def main():
|
||||
if not SCENE.exists():
|
||||
sys.exit(f"{SCENE} not found")
|
||||
stage = Usd.Stage.Open(str(SCENE))
|
||||
|
||||
print("tray B - swap the tall backboard to the far face")
|
||||
for wall, top, tag in ((f"{TRAYS}/B_W0", NEAR_WALL_TOP, "near (goods arrive)"),
|
||||
(f"{TRAYS}/B_W1", FAR_WALL_TOP, "far (backboard)")):
|
||||
if not stage.GetPrimAtPath(wall).IsValid():
|
||||
print(f" {wall} missing"); continue
|
||||
changed = _set_top(stage, wall, top)
|
||||
print(f" {wall.rsplit('/', 1)[1]:6s} {tag:22s} "
|
||||
+ (f"top {changed[0]} -> {changed[1]}" if changed else "already correct"))
|
||||
|
||||
print(f"plow hinge - raise the limit so the arm can reach the lane")
|
||||
hinge = stage.GetPrimAtPath(HINGE)
|
||||
if hinge.IsValid():
|
||||
j = UsdPhysics.RevoluteJoint(hinge)
|
||||
lo, hi = j.GetLowerLimitAttr().Get(), j.GetUpperLimitAttr().Get()
|
||||
j.GetLowerLimitAttr().Set(-NEW_LIMIT)
|
||||
j.GetUpperLimitAttr().Set(NEW_LIMIT)
|
||||
import math
|
||||
print(f" limits {lo:+.0f}/{hi:+.0f} -> {-NEW_LIMIT:+.0f}/{NEW_LIMIT:+.0f} "
|
||||
f"(tip reach {0.6 * math.sin(math.radians(abs(hi))):.3f} -> "
|
||||
f"{0.6 * math.sin(math.radians(NEW_LIMIT)):.3f} m, lane edge at 0.450)")
|
||||
else:
|
||||
print(f" {HINGE} missing")
|
||||
|
||||
stage.GetRootLayer().Save()
|
||||
print(f"saved {SCENE}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user