0d32f32db0
Замкнутый контур "поток -> 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>
130 lines
5.2 KiB
Python
130 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Rebuild the discharge as a Y fork with the plow at its apex.
|
|
|
|
/home/whatevenif/isaacsim/python.sh scripts/build_y_fork.py
|
|
|
|
The layout so far was a T: lane B perpendicular, lane C at 45 deg, meeting the main run at
|
|
different x, with the blade out in the middle of the belt trying to shove goods 400 mm
|
|
sideways onto them. Every failure this session came from that - the dead zone, the wedges,
|
|
goods stalling on the lip - because a push was being asked to do the job of a route.
|
|
|
|
A fork does not need the push. Both branches leave one apex, the blade sits in it as a
|
|
railway point, and goods **drive** into their branch:
|
|
|
|
rest (class C) blade closes the B mouth -> everything runs straight on to C
|
|
B arrives blade swings over -> the B mouth opens and takes it
|
|
|
|
Geometry, all from the apex at the downstream end of the main run:
|
|
|
|
apex x -7.00, y 0
|
|
branch C 15 deg up from the run (travel -0.966, +0.259)
|
|
branch B 35 deg down from the run (travel -0.819, -0.574)
|
|
|
|
Each track is placed by measurement, not by assumption: the script reads where the belt slab
|
|
currently sits relative to its own prim origin, then sets the transform so the slab's near
|
|
end lands on the apex pointing along its branch. The trays follow their branches.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from pxr import Gf, Usd, UsdGeom
|
|
|
|
SCENE = Path(__file__).resolve().parent.parent / "scene" / "plow_cell.usd"
|
|
APEX = (-7.00, 0.0)
|
|
TRAYS = "/World/PlowContainers"
|
|
|
|
# track prim -> (branch angle from the -X run, tray prefix, tray distance along the branch)
|
|
BRANCHES = {
|
|
"/World/ConveyorTrack_01": dict(deg=+15.0, tray="C_", tray_at=2.30, name="C"),
|
|
"/ConveyorTrack_01": dict(deg=-35.0, tray="B_", tray_at=2.30, name="B"),
|
|
}
|
|
|
|
|
|
def _dir(deg):
|
|
"""world travel direction of a branch `deg` off the -X run"""
|
|
a = math.radians(180.0 - deg) # -X is 180 deg; +deg swings toward +Y
|
|
return math.cos(a), math.sin(a)
|
|
|
|
|
|
def _belt_of(stage, track):
|
|
for p in (f"{track}/Belt", f"{track}/Belt_01"):
|
|
if stage.GetPrimAtPath(p).IsValid():
|
|
return p
|
|
return None
|
|
|
|
|
|
def _set_xform(stage, path, tx, ty, tz, rot_deg):
|
|
xf = UsdGeom.Xformable(stage.GetPrimAtPath(path))
|
|
xf.ClearXformOpOrder()
|
|
xf.AddTranslateOp().Set(Gf.Vec3d(tx, ty, tz))
|
|
xf.AddRotateZOp().Set(float(rot_deg))
|
|
|
|
|
|
def main():
|
|
if not SCENE.exists():
|
|
sys.exit(f"{SCENE} not found")
|
|
stage = Usd.Stage.Open(str(SCENE))
|
|
bb = UsdGeom.BBoxCache(0, ["default"])
|
|
xc = UsdGeom.XformCache()
|
|
|
|
for track, b in BRANCHES.items():
|
|
prim = stage.GetPrimAtPath(track)
|
|
if not prim.IsValid():
|
|
print(f" {track} missing"); continue
|
|
belt = _belt_of(stage, track)
|
|
if belt is None:
|
|
print(f" {track} has no Belt"); continue
|
|
|
|
# where the slab sits now, relative to this track's own origin
|
|
org = xc.GetLocalToWorldTransform(prim).ExtractTranslation()
|
|
r = bb.ComputeWorldBound(stage.GetPrimAtPath(belt)).ComputeAlignedRange()
|
|
span_x, span_y = r.GetMax()[0] - r.GetMin()[0], r.GetMax()[1] - r.GetMin()[1]
|
|
length = max(span_x, span_y)
|
|
top_z = r.GetMax()[2]
|
|
# the slab's centre offset from the origin, in the track's own frame
|
|
cx = (r.GetMin()[0] + r.GetMax()[0]) / 2.0 - org[0]
|
|
cy = (r.GetMin()[1] + r.GetMax()[1]) / 2.0 - org[1]
|
|
off = math.hypot(cx, cy)
|
|
|
|
dx, dy = _dir(b["deg"])
|
|
# put the slab centre half a length down the branch from the apex
|
|
tx = APEX[0] + dx * (length / 2.0) - (dx * off - dx * off)
|
|
ty = APEX[1] + dy * (length / 2.0)
|
|
_set_xform(stage, track, tx - cx, ty - cy, org[2], 180.0 - b["deg"])
|
|
|
|
r2 = bb.ComputeWorldBound(stage.GetPrimAtPath(belt)).ComputeAlignedRange()
|
|
print(f" branch {b['name']} {b['deg']:+.0f} deg dir ({dx:+.3f},{dy:+.3f}) "
|
|
f"length {length:.2f} m")
|
|
print(f" slab now x[{r2.GetMin()[0]:+.2f},{r2.GetMax()[0]:+.2f}] "
|
|
f"y[{r2.GetMin()[1]:+.2f},{r2.GetMax()[1]:+.2f}] top z={r2.GetMax()[2]:.3f}")
|
|
|
|
# the tray rides to the end of its branch
|
|
tex, tey = APEX[0] + dx * b["tray_at"], APEX[1] + dy * b["tray_at"]
|
|
moved = 0
|
|
for c in stage.GetPrimAtPath(TRAYS).GetChildren():
|
|
if not c.GetName().startswith(b["tray"]):
|
|
continue
|
|
cr = bb.ComputeWorldBound(c).ComputeAlignedRange()
|
|
ccx = (cr.GetMin()[0] + cr.GetMax()[0]) / 2.0
|
|
ccy = (cr.GetMin()[1] + cr.GetMax()[1]) / 2.0
|
|
cxf = UsdGeom.Xformable(c)
|
|
for op in cxf.GetOrderedXformOps():
|
|
if op.GetOpType() == UsdGeom.XformOp.TypeTranslate:
|
|
t = op.Get()
|
|
op.Set(type(t)(t[0] + (tex - ccx), t[1] + (tey - ccy), t[2]))
|
|
moved += 1
|
|
break
|
|
print(f" tray {b['name']} -> ({tex:+.2f},{tey:+.2f}) {moved} parts moved")
|
|
|
|
stage.GetRootLayer().Save()
|
|
print(f"saved {SCENE}")
|
|
print("NOTE: the blade's rest position is now 'B closed', not 0 - re-measure which sign")
|
|
print(" closes B before running a sort.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|