feat(simulation): add Worker AI polygon runtime and terrain navigation
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""Worker-only contact replay on a retained episode's exact collision asset.
|
||||
|
||||
This isolates physical support from perception/planning. It never supplies a
|
||||
route or collision truth to inference and never writes to the original run.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
import time
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
from isaacsim import SimulationApp
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--run", type=Path, required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--mode", choices=("straight", "replay"), default="straight")
|
||||
parser.add_argument("--seconds", type=float, default=40)
|
||||
args = parser.parse_args()
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "src"))
|
||||
|
||||
app = SimulationApp({"headless": True, "hide_ui": True})
|
||||
try:
|
||||
import omni.timeline
|
||||
import omni.usd
|
||||
from isaacsim.core.experimental.prims import Articulation
|
||||
from isaacsim.core.simulation_manager import SimulationManager
|
||||
from pxr import UsdGeom
|
||||
from rover_profile import PROFILE, DifferentialDrive, create_rover
|
||||
from terrain import install_terrain
|
||||
|
||||
from k1link.simulation.ai_polygon.mission_policy import inclination
|
||||
|
||||
run = json.loads(args.run.read_text())
|
||||
settings = run["world"]["settings"]
|
||||
stage = omni.usd.get_context().get_stage()
|
||||
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z)
|
||||
UsdGeom.SetStageMetersPerUnit(stage, 1)
|
||||
height, terrain = install_terrain(stage, run["terrain_manifest"], run["world"])
|
||||
create_rover(
|
||||
stage,
|
||||
[*settings["spawn_xy"], height],
|
||||
settings["heading_degrees"],
|
||||
terrain["initial_ground_normal"],
|
||||
)
|
||||
SimulationManager.setup_simulation(dt=1 / 60, device="cpu")
|
||||
omni.timeline.get_timeline_interface().play()
|
||||
for _ in range(30):
|
||||
app.update()
|
||||
robot = Articulation("/World/Rover")
|
||||
drive = DifferentialDrive(robot)
|
||||
playback = []
|
||||
if args.mode == "replay":
|
||||
playback = [
|
||||
json.loads(x) for x in (args.run.parent / "motion.jsonl").read_text().splitlines()
|
||||
]
|
||||
trace, cursor, stopped = [], 0, False
|
||||
started = time.monotonic()
|
||||
for step in range(round(args.seconds * 60)):
|
||||
p, q = robot.get_world_poses()
|
||||
p, q = p.numpy()[0].tolist(), q.numpy()[0].tolist()
|
||||
pose = p + q[1:] + q[:1]
|
||||
tilt = inclination(pose)
|
||||
stopped |= tilt >= PROFILE["stop_tilt_degrees"]
|
||||
v, w = settings["max_speed_mps"], 0.0
|
||||
if playback:
|
||||
while (
|
||||
cursor + 1 < len(playback)
|
||||
and playback[cursor + 1]["simulation_time_ns"] / 1e9 <= step / 60
|
||||
):
|
||||
cursor += 1
|
||||
v, w = playback[cursor]["applied_speed_mps"], playback[cursor]["applied_yaw_rate_rps"]
|
||||
drive.command(0 if stopped else v, 0 if stopped else w)
|
||||
if step % 10 == 0:
|
||||
trace.append(dict(time_s=step / 60, pose=pose, tilt_degrees=tilt, stopped=stopped))
|
||||
SimulationManager.step(steps=1)
|
||||
report = dict(
|
||||
schema_version="missioncore.scene-contact-qualification/v1",
|
||||
utc=datetime.now(UTC).isoformat(),
|
||||
monotonic=time.monotonic(),
|
||||
elapsed_wall_s=time.monotonic() - started,
|
||||
run_sha256=hashlib.sha256(args.run.read_bytes()).hexdigest(),
|
||||
mode=args.mode,
|
||||
wheel_collision=PROFILE["wheel_collision"],
|
||||
seconds=args.seconds,
|
||||
profile=PROFILE,
|
||||
terrain=terrain,
|
||||
trace=trace,
|
||||
displacement_m=math.dist(trace[0]["pose"][:2], trace[-1]["pose"][:2]),
|
||||
max_tilt_degrees=max(x["tilt_degrees"] for x in trace),
|
||||
stopped=stopped,
|
||||
)
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(json.dumps(report, indent=2), encoding="utf-8")
|
||||
print(json.dumps({k: v for k, v in report.items() if k not in ("trace", "terrain", "profile")}))
|
||||
finally:
|
||||
app.close()
|
||||
Reference in New Issue
Block a user