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>
93 lines
3.8 KiB
Python
93 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Turn the plow round: pivot upstream, free end downstream at the discharge edge.
|
|
|
|
/home/whatevenif/isaacsim/python.sh scripts/reverse_plow_mount.py
|
|
|
|
Measured on the built scene, with goods travelling in **-X**:
|
|
|
|
pivot x = -7.050 <- DOWNSTREAM end
|
|
free tip x = -6.658 (at 42 deg) <- UPSTREAM end, y +0.353
|
|
|
|
That is a plough mounted backwards. A plough is an inclined plane: the belt drives the item
|
|
along the blade toward the blade's **downstream** end, and the item leaves there. With the
|
|
downstream end sitting at the pivot on the belt centreline (y = 0), goods are funnelled
|
|
*inward*, slip past the pivot and carry on down the line. They can never discharge.
|
|
|
|
It explains every symptom: the 0.39 m ceiling is the brief shove from the sweep, after
|
|
which the item slides back toward the centre; moving the lanes inboard changed nothing
|
|
because the lane edge was never what goods were failing to reach; and goods pile in the
|
|
wedge between the blade and the belt centre, which is what the viewport shows.
|
|
|
|
The fix is the mounting, not the length:
|
|
|
|
pivot x -7.050 -> -6.522 (upstream end of the same physical span)
|
|
arm extends -X instead of +X (rotateZ 180 -> 0)
|
|
|
|
so at 42 deg the free end lands near x -6.92, y +-0.353 - downstream of the pivot and out
|
|
at the discharge side. Goods now slide *outward and forward* along the blade.
|
|
|
|
**The swing sign flips with the mount.** `plow_sort.calibrate_mapping()` says to measure it
|
|
rather than reason about it; re-measure after running this.
|
|
"""
|
|
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"
|
|
DIVERTER = "/World/Diverters/DiverterEnd"
|
|
ARM = DIVERTER + "/Arm"
|
|
|
|
|
|
def main():
|
|
if not SCENE.exists():
|
|
sys.exit(f"{SCENE} not found")
|
|
stage = Usd.Stage.Open(str(SCENE))
|
|
prim = stage.GetPrimAtPath(DIVERTER)
|
|
if not prim.IsValid():
|
|
sys.exit(f"{DIVERTER} missing")
|
|
|
|
xc = UsdGeom.XformCache()
|
|
piv = xc.GetLocalToWorldTransform(stage.GetPrimAtPath(ARM)).ExtractTranslation()
|
|
r = UsdGeom.BBoxCache(0, ["default"]).ComputeWorldBound(
|
|
stage.GetPrimAtPath(ARM)).ComputeAlignedRange()
|
|
reach = r.GetMax()[0] - piv[0] # +0.528: arm points upstream today
|
|
print(f"before: pivot x={piv[0]:.3f}, arm reaches {reach:+.3f} m in X "
|
|
f"({'UPSTREAM - wrong way' if reach > 0 else 'downstream'})")
|
|
|
|
xf = UsdGeom.Xformable(prim)
|
|
moved = flipped = False
|
|
for op in xf.GetOrderedXformOps():
|
|
if op.GetOpType() == UsdGeom.XformOp.TypeTranslate and not moved:
|
|
t = op.Get()
|
|
op.Set(Gf.Vec3d(t[0] + reach, t[1], t[2])) # pivot to the upstream end
|
|
moved = True
|
|
elif op.GetOpType() == UsdGeom.XformOp.TypeRotateZ and not flipped:
|
|
op.Set(float((op.Get() or 0.0) + 180.0) % 360.0) # arm now points downstream
|
|
flipped = True
|
|
if not (moved and flipped):
|
|
sys.exit(f"{DIVERTER} needs both a translate and a rotateZ op "
|
|
f"(moved={moved}, flipped={flipped})")
|
|
|
|
stage.GetRootLayer().Save()
|
|
|
|
xc2 = UsdGeom.XformCache()
|
|
piv2 = xc2.GetLocalToWorldTransform(stage.GetPrimAtPath(ARM)).ExtractTranslation()
|
|
r2 = UsdGeom.BBoxCache(0, ["default"]).ComputeWorldBound(
|
|
stage.GetPrimAtPath(ARM)).ComputeAlignedRange()
|
|
L = abs(reach)
|
|
print(f"after : pivot x={piv2[0]:.3f}, arm spans x[{r2.GetMin()[0]:.3f}, "
|
|
f"{r2.GetMax()[0]:.3f}]")
|
|
for a in (0, 20, 42):
|
|
print(f" {a:2d} deg: free end x={piv2[0] - L * math.cos(math.radians(a)):+.3f} "
|
|
f"y={L * math.sin(math.radians(a)):+.3f} (downstream of pivot = correct)")
|
|
print(f"saved {SCENE}")
|
|
print("NOTE: the swing sign flips with the mount - re-measure calibrate_mapping()")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|