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>
134 lines
5.6 KiB
Python
134 lines
5.6 KiB
Python
"""Blade drive METHOD comparison - the speed sweep proved speed is not the variable.
|
|
|
|
A: write xformOp:translate (what the pipeline does now) = a TELEPORT. PhysX sees no
|
|
velocity; the item gets only a depenetration shove, so a FASTER blade pushes LESS -
|
|
exactly what the sweep measured (1.3->99mm, 2.6->17mm). This is probe_push_physics.py's
|
|
documented teleport signature.
|
|
B: RigidPrim.set_world_poses() -> sets the KINEMATIC TARGET on the physics backend, so
|
|
PhysX derives velocity = delta/dt and transfers real momentum.
|
|
C: dynamic blade + set_velocities() -> a genuine moving mass carrying momentum.
|
|
"""
|
|
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 numpy as np
|
|
import omni.usd, omni.timeline
|
|
from pxr import Gf, Usd, 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, plow_cell_9045
|
|
|
|
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)
|
|
await plow_cell_9045.prepare(stage, belt_speed=1.0, script_control=True)
|
|
|
|
blade_prim = stage.GetPrimAtPath(_scene.BLADE)
|
|
blade_rp = RigidPrim(paths=[_scene.BLADE])
|
|
def _bop():
|
|
for op in UsdGeom.Xformable(blade_prim).GetOrderedXformOps():
|
|
if op.GetOpType() == UsdGeom.XformOp.TypeTranslate:
|
|
return op
|
|
bop = _bop(); bbase = bop.Get()
|
|
def blade_xform_to(y):
|
|
bop.Set(Gf.Vec3d(bbase[0], y - _scene.BLADE_PARENT_Y, bbase[2]))
|
|
|
|
bbc = UsdGeom.BBoxCache(Usd.TimeCode.Default(), [UsdGeom.Tokens.default_, UsdGeom.Tokens.render])
|
|
r0 = bbc.ComputeWorldBound(blade_prim).ComputeAlignedRange()
|
|
BLADE_WORLD_X = (r0.GetMin()[0] + r0.GetMax()[0]) / 2.0
|
|
BLADE_WORLD_Z = (r0.GetMin()[2] + r0.GetMax()[2]) / 2.0
|
|
SENSE_X = r0.GetMax()[0]
|
|
print(f"blade centre x={BLADE_WORLD_X:+.3f} z={BLADE_WORLD_Z:+.3f}, sense {SENSE_X:+.3f}")
|
|
|
|
ipath = "/World/Items/_pushprobe2"
|
|
def spawn():
|
|
if stage.GetPrimAtPath(ipath).IsValid():
|
|
stage.RemovePrim(ipath)
|
|
prim = UsdGeom.Xform.Define(stage, ipath).GetPrim()
|
|
prim.GetReferences().AddReference(str(C.ROOT / "assets" / "items" / "box_300x200x200.usd"))
|
|
xf = UsdGeom.Xformable(prim); xf.ClearXformOpOrder()
|
|
xf.AddTranslateOp(precision=UsdGeom.XformOp.PrecisionDouble).Set(Gf.Vec3d(-3.05, 0.0, C.BELT_Z + 0.05))
|
|
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.CreateMaxDepenetrationVelocityAttr().Set(C.MAX_DEPENETRATION)
|
|
UsdGeom.Imageable(prim).MakeVisible()
|
|
return RigidPrim(paths=[ipath])
|
|
|
|
SPEED = 1.3
|
|
A, B = C.BLADE_HOME_Y, 0.55
|
|
|
|
async def ride_to_blade(rp):
|
|
for _ in range(400):
|
|
if float(rp.get_world_poses()[0].numpy()[0][0]) <= SENSE_X:
|
|
return True
|
|
await app_utils.update_app_async(steps=1)
|
|
return False
|
|
|
|
print(f"\n{'method':>34} {'y_gain':>8} {'final_y':>8} {'final_z':>8} verdict")
|
|
print("-" * 76)
|
|
|
|
for method in ("A: usd xform write (current)", "B: RigidPrim.set_world_poses",
|
|
"C: dynamic + set_velocities"):
|
|
# reset blade to kinematic home
|
|
UsdPhysics.RigidBodyAPI(blade_prim).CreateKinematicEnabledAttr().Set(True)
|
|
blade_xform_to(A)
|
|
rp = spawn()
|
|
tl.play(); await app_utils.update_app_async(steps=8)
|
|
await ride_to_blade(rp)
|
|
p0 = rp.get_world_poses()[0].numpy()[0].copy()
|
|
|
|
dur = abs(B - A) / SPEED
|
|
t0 = float(tl.get_current_time())
|
|
if method.startswith("C"):
|
|
UsdPhysics.RigidBodyAPI(blade_prim).CreateKinematicEnabledAttr().Set(False)
|
|
UsdPhysics.MassAPI.Apply(blade_prim).CreateMassAttr().Set(200.0)
|
|
PhysxSchema.PhysxRigidBodyAPI.Apply(blade_prim).CreateDisableGravityAttr().Set(True)
|
|
await app_utils.update_app_async(steps=2)
|
|
while True:
|
|
t = float(tl.get_current_time()) - t0
|
|
u = min(1.0, t / dur)
|
|
y = A + (B - A) * u
|
|
try:
|
|
if method.startswith("A"):
|
|
blade_xform_to(y)
|
|
elif method.startswith("B"):
|
|
blade_rp.set_world_poses(positions=np.array([[BLADE_WORLD_X, y, BLADE_WORLD_Z]]))
|
|
else:
|
|
vy = 0.0 if u >= 1.0 else SPEED
|
|
blade_rp.set_velocities(np.array([[0.0, vy, 0.0, 0.0, 0.0, 0.0]]))
|
|
except BaseException as exc:
|
|
print(f" {method}: drive call failed: {type(exc).__name__}")
|
|
break
|
|
await app_utils.update_app_async(steps=1)
|
|
if u >= 1.0:
|
|
break
|
|
for _ in range(60):
|
|
if method.startswith("C"):
|
|
try:
|
|
blade_rp.set_velocities(np.array([[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]))
|
|
except BaseException:
|
|
pass
|
|
await app_utils.update_app_async(steps=1)
|
|
|
|
p = rp.get_world_poses()[0].numpy()[0]
|
|
gain = float(p[1]) - float(p0[1])
|
|
verdict = ("FELL" if float(p[2]) < 1.2 else
|
|
"EJECTED" if abs(float(p[1])) > 3.0 else
|
|
"DELIVERED" if float(p[1]) > 0.45 else "short")
|
|
print(f"{method:>34} {gain:8.3f} {float(p[1]):8.3f} {float(p[2]):8.3f} {verdict}")
|
|
|
|
tl.stop(); await app_utils.update_app_async(steps=6)
|
|
UsdPhysics.RigidBodyAPI(blade_prim).CreateKinematicEnabledAttr().Set(True)
|
|
blade_xform_to(A)
|