Skip to content

Commit e5889c0

Browse files
andrewjongclaude
andcommitted
feat(sim): airstack up --scene <shortname> — simulator-agnostic scene selection
One flag maps a scene shortname to whatever the active simulator understands, from a new catalog (simulation/scenes.yaml, resolved host-side by simulation/resolve_scene.py). Unknown or wrong-simulator scenes error with a per-simulator availability table; without --scene the effective launch config is byte-identical to before. - Isaac: exports ISAAC_SIM_SCENE (+ ISAAC_SIM_STAGE_SCALE) — a Pegasus SIMULATION_ENVIRONMENTS key or a Nucleus/HTTP USD URL. The example launch scripts and fleet_spawn.py resolve their scene from env (pegasus_app.resolve_scene_from_env) instead of hardcoding env_url; --scene overrides a fleet's sim.scene with a logged note. Catalog includes the 15 Pegasus entries plus 6 guest-readable AirLab stages under omniverse://airlab-nucleus.andrew.cmu.edu:443/Public/AirStack/Stages/, each with stage_scale mirroring its layer's metersPerUnit. - MS AirSim: exports MS_AIRSIM_SCENE (a fetch_scene.sh key); the entrypoint auto-fetch, previously Blocks-only, fetches any catalog scene, and an interactive `airstack up` asks before a multi-GB download. - fetch_scene.sh catalog corrected to the UE4 binaries that actually ship in the AirSim v1.8.1 release: forest/soccerfield/building99 removed (never published or 0-byte), airsimnh fixed to AirSimNH.zip, africasavannah and msbuild2018 added; the entrypoint glob-resolves the UE launcher after extraction. - Docs: new docs/simulation/scenes.md (catalog, env vars, public-stage URLs, add-a-scene guide), simulation index + MS AirSim quick start updated, release-notes entry under 0.20.0. Validated: resolver contract + CLI dry-run matrix; Isaac headless run loaded the ConstructionSite stage from Public Nucleus (682 colliders, drone spawned, PX4 node created); MS AirSim blocks run reached PX4 "Ready for takeoff" with the bridge publishing /clock; msbuild2018 exercised the full download → extract → boot pipeline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5c0665f commit e5889c0

16 files changed

Lines changed: 663 additions & 27 deletions

File tree

‎.env‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ PROJECT_NAME="airstack"
1212
# If you've run ./airstack.sh setup, then this will auto-generate from the git commit hash every time a change is made
1313
# to a Dockerfile or docker-compose.yaml file. Otherwise this can also be set explicitly to make a release version.
1414
# auto-generated from git commit hash
15-
VERSION="0.20.0-alpha.11"
15+
VERSION="0.20.0-alpha.12"
1616
# Image-tag discriminator ONLY (appears in the image tag suffix, e.g. ..._robot-x86-64_dev).
1717
# No Dockerfile consumes it: "prebuilt" does NOT bake the built ros_ws into the image today —
1818
# a real prebuilt (workspace-baked) stage is future work. Keep "dev" (mounted code, built live).
@@ -37,10 +37,19 @@ ISAAC_SIM_USE_STANDALONE="true" # "true" or "false"
3737
# Script name (must be in /AirStack/simulation/isaac-sim/launch_scripts/)
3838
ISAAC_SIM_SCRIPT_NAME="example_one_px4_pegasus_launch_script.py"
3939
PLAY_SIM_ON_START="false" # Sim comes up PAUSED by default (press Play, or launch with `airstack up --play`). System tests force this to true.
40+
# Scene to load, set by `airstack up --scene <shortname>` (see simulation/scenes.yaml
41+
# for shortnames). Can also be set directly to a Pegasus SIMULATION_ENVIRONMENTS
42+
# key or a USD URL. Empty = the launch script's default (Default Environment).
43+
# ISAAC_SIM_SCENE="Warehouse"
44+
# ISAAC_SIM_STAGE_SCALE="1.0" # scale applied to /World/stage (0.01 for cm-authored stages)
4045

4146
# ===============================================
4247

4348
# ================= MS-AIRSIM =====================
49+
# Scene to run, set by `airstack up --scene <shortname>` (see simulation/scenes.yaml).
50+
# A fetch_scene.sh key; the pre-built UE4 binary is auto-fetched if absent.
51+
# Empty = blocks. Ignored when MS_AIRSIM_BINARY_PATH is set.
52+
# MS_AIRSIM_SCENE="blocks"
4453
# Do not set if you want airstack to fetch a simple blocks world.
4554
# MS_AIRSIM_ENV_DIR=./simulation/ms-airsim/assets/scenes
4655
# MS_AIRSIM_BINARY_PATH="/ms-airsim-env/Blocks/LinuxNoEditor/Blocks.sh"

‎airstack.sh‎

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ function print_command_help {
183183
echo " heterogeneous fleets) generates the per-robot services plus"
184184
echo " split-stack DDS-router configs. Mutually exclusive with"
185185
echo " --robots. See docs/development/fleets.md."
186+
echo " --scene NAME Select the simulation scene by shortname (simulation/scenes.yaml):"
187+
echo " maps NAME to the selected simulator's scene reference and"
188+
echo " exports it (Isaac: ISAAC_SIM_SCENE [+ ISAAC_SIM_STAGE_SCALE] —"
189+
echo " a Pegasus catalog key or Nucleus USD URL; MS AirSim:"
190+
echo " MS_AIRSIM_SCENE — a pre-built UE4 scene, auto-fetched if"
191+
echo " missing). Unknown scene = error + table of available scenes."
186192
echo " --headless Run the sim without a GUI (ISAAC_SIM_HEADLESS/MS_AIRSIM_HEADLESS)."
187193
echo " --play / --no-play Start the sim playing, or paused (PLAY_SIM_ON_START)."
188194
echo " --no-autolaunch Start containers idle (AUTOLAUNCH=false; launch manually)."
@@ -1126,6 +1132,7 @@ function parse_launch_intent {
11261132
AIRSTACK_INTENT_AUTOLAUNCH=""
11271133
AIRSTACK_INTENT_STACK=""
11281134
AIRSTACK_INTENT_FLEET=""
1135+
AIRSTACK_INTENT_SCENE=""
11291136
AIRSTACK_FLEET_COMPOSE_FILE=""
11301137
AIRSTACK_DRY_RUN=""
11311138
AIRSTACK_UP_WAIT=""
@@ -1146,6 +1153,8 @@ function parse_launch_intent {
11461153
--stack=*) AIRSTACK_INTENT_STACK="${a#--stack=}";;
11471154
--fleet) i=$((i+1)); AIRSTACK_INTENT_FLEET="${args[$i]:-}";;
11481155
--fleet=*) AIRSTACK_INTENT_FLEET="${a#--fleet=}";;
1156+
--scene) i=$((i+1)); AIRSTACK_INTENT_SCENE="${args[$i]:-}";;
1157+
--scene=*) AIRSTACK_INTENT_SCENE="${a#--scene=}";;
11491158
--wait) AIRSTACK_UP_WAIT="1";;
11501159
# NOTE: shadows compose's own `up --dry-run`; ours validates the
11511160
# derived launch config and exits without starting services.
@@ -1413,6 +1422,77 @@ function apply_launch_intent {
14131422
export ISAAC_SIM_SCRIPT_NAME="$want"
14141423
fi
14151424
fi
1425+
1426+
# --scene: map the shortname to the ACTIVE simulator's scene reference via
1427+
# simulation/scenes.yaml and export it (leaf values only). Unknown scene or
1428+
# wrong simulator = the resolver prints the availability table and we fail.
1429+
if [[ -n "$AIRSTACK_INTENT_SCENE" ]]; then
1430+
local scene_sim=""
1431+
[[ ",$profiles_final," == *",isaac-sim,"* ]] && scene_sim="isaac"
1432+
[[ ",$profiles_final," == *",ms-airsim,"* ]] && scene_sim="msairsim"
1433+
if [[ -z "$scene_sim" ]]; then
1434+
log_error "--scene requires an Isaac or MS AirSim profile (resolved profiles: ${profiles_final:-<none>}); simple-sim has no scene catalog. Use 'airstack up --sim isaac|airsim --scene $AIRSTACK_INTENT_SCENE'."
1435+
python3 "$PROJECT_ROOT/simulation/resolve_scene.py" --table >&2 || true
1436+
return 1
1437+
fi
1438+
local scene_exports line
1439+
if ! scene_exports=$(python3 "$PROJECT_ROOT/simulation/resolve_scene.py" \
1440+
--sim "$scene_sim" --scene "$AIRSTACK_INTENT_SCENE"); then
1441+
return 1 # resolver already printed the error + table on stderr
1442+
fi
1443+
while IFS= read -r line; do
1444+
[[ -z "$line" ]] && continue
1445+
export "${line?}"
1446+
log_info "--scene $AIRSTACK_INTENT_SCENE → $line"
1447+
done <<< "$scene_exports"
1448+
if [[ "$scene_sim" == "msairsim" ]]; then
1449+
ensure_msairsim_scene "$(resolve_launch_var MS_AIRSIM_SCENE "$@")" "$@" || return 1
1450+
fi
1451+
fi
1452+
return 0
1453+
}
1454+
1455+
# MS AirSim scenes are pre-built UE4 binaries fetched into the (bind-mounted)
1456+
# scenes dir on first use. When --scene selects one that is not present
1457+
# locally, offer to download it now (interactive) so the multi-GB fetch
1458+
# doesn't silently happen inside the container; non-interactive runs keep the
1459+
# in-container auto-fetch (visible in the airsim tmux window).
1460+
function ensure_msairsim_scene {
1461+
local scene_key="$1"; shift
1462+
local fetch_sh="$PROJECT_ROOT/simulation/ms-airsim/assets/scenes/fetch_scene.sh"
1463+
1464+
# An explicit binary path bypasses the catalog entirely.
1465+
local bin
1466+
bin=$(resolve_launch_var MS_AIRSIM_BINARY_PATH "$@")
1467+
if [[ -n "$bin" ]]; then
1468+
log_warn "--scene is overridden by MS_AIRSIM_BINARY_PATH=$bin (the explicit binary path wins in the container). Unset MS_AIRSIM_BINARY_PATH to use --scene."
1469+
return 0
1470+
fi
1471+
1472+
local env_dir name
1473+
env_dir=$(resolve_launch_var MS_AIRSIM_ENV_DIR "$@")
1474+
[[ -z "$env_dir" ]] && env_dir="$PROJECT_ROOT/simulation/ms-airsim/assets/scenes"
1475+
[[ "$env_dir" != /* ]] && env_dir="$PROJECT_ROOT/${env_dir#./}"
1476+
name=$(bash "$fetch_sh" --name "$scene_key") || {
1477+
log_error "fetch_scene.sh does not know scene key '$scene_key' (simulation/scenes.yaml out of sync?)"
1478+
return 1
1479+
}
1480+
if compgen -G "$env_dir/$name/LinuxNoEditor/*.sh" > /dev/null; then
1481+
return 0
1482+
fi
1483+
log_warn "MS AirSim scene '$name' is not present locally under $env_dir."
1484+
if [[ -t 0 && -t 1 && "${AIRSTACK_ASSUME_YES:-}" != "1" ]]; then
1485+
local ans
1486+
read -r -p "Download it now from github.com/microsoft/AirSim v1.8.1 (can be several GB)? [Y/n] " ans
1487+
case "$ans" in
1488+
n|N|no|NO)
1489+
log_error "Aborted. Point MS_AIRSIM_BINARY_PATH at an existing scene binary, or rerun and accept the download."
1490+
return 1;;
1491+
esac
1492+
SCENES_DIR="$env_dir" bash "$fetch_sh" "$scene_key" || return 1
1493+
else
1494+
log_info "Non-interactive run: the airsim container will auto-fetch '$name' on first launch (progress in the airsim tmux window)."
1495+
fi
14161496
return 0
14171497
}
14181498

@@ -1428,6 +1508,11 @@ function print_launch_config {
14281508
local _fleet_cfg
14291509
_fleet_cfg=$(resolve_launch_var FLEET_CONFIG_FILE "$@")
14301510
[[ -n "$_fleet_cfg" ]] && keys+=(FLEET_CONFIG_FILE)
1511+
# Scene keys likewise only appear when a scene is selected (--scene or env).
1512+
local _sk
1513+
for _sk in ISAAC_SIM_SCENE ISAAC_SIM_STAGE_SCALE MS_AIRSIM_SCENE; do
1514+
[[ -n "$(resolve_launch_var "$_sk" "$@")" ]] && keys+=("$_sk")
1515+
done
14311516
local k v lines=()
14321517
for k in "${keys[@]}"; do
14331518
v=$(resolve_launch_var "$k" "$@")
@@ -1438,7 +1523,14 @@ function print_launch_config {
14381523
profiles=$(resolve_launch_var COMPOSE_PROFILES "$@")
14391524
log_info "Launch config: profiles=$profiles robots=$(resolve_launch_var NUM_ROBOTS "$@") autolaunch=$(resolve_launch_var AUTOLAUNCH "$@") play_on_start=$(resolve_launch_var PLAY_SIM_ON_START "$@")"
14401525
if [[ ",$profiles," == *",isaac-sim,"* ]]; then
1441-
log_info " isaac: script=$(resolve_launch_var ISAAC_SIM_SCRIPT_NAME "$@") headless=$(resolve_launch_var ISAAC_SIM_HEADLESS "$@")"
1526+
local _isaac_scene
1527+
_isaac_scene=$(resolve_launch_var ISAAC_SIM_SCENE "$@")
1528+
log_info " isaac: script=$(resolve_launch_var ISAAC_SIM_SCRIPT_NAME "$@") headless=$(resolve_launch_var ISAAC_SIM_HEADLESS "$@")${_isaac_scene:+ scene=$_isaac_scene}"
1529+
fi
1530+
if [[ ",$profiles," == *",ms-airsim,"* ]]; then
1531+
local _airsim_scene
1532+
_airsim_scene=$(resolve_launch_var MS_AIRSIM_SCENE "$@")
1533+
[[ -n "$_airsim_scene" ]] && log_info " ms-airsim: scene=$_airsim_scene"
14421534
fi
14431535
log_info " urdf=$(resolve_launch_var URDF_FILE "$@")"
14441536
local _stack_dir

‎docs/release_notes/index.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,28 @@ Feature docs deliberately cite none of these — the design sources live here:
128128
`--dry-run` — deriving the coordinated env-var sets as exported leaf
129129
values, with a resolved-config banner and a per-run
130130
`.airstack/runs/<ts>/effective_config.env` dump; contract-tested
131+
- `airstack up --scene <shortname>`: simulator-agnostic scene selection via
132+
a new catalog (`simulation/scenes.yaml`, resolved host-side by
133+
`simulation/resolve_scene.py`). Isaac maps shortnames to Pegasus catalog
134+
keys or Nucleus USD URLs (exported as `ISAAC_SIM_SCENE` +
135+
`ISAAC_SIM_STAGE_SCALE`; the example launch scripts and `fleet_spawn.py`
136+
now resolve their scene from these instead of hardcoding `env_url`);
137+
MS AirSim maps to `fetch_scene.sh` keys (`MS_AIRSIM_SCENE` — the
138+
entrypoint's auto-fetch, previously Blocks-only, now fetches any catalog
139+
scene, and an interactive `airstack up` asks before a multi-GB download).
140+
The `fetch_scene.sh` catalog was corrected to the UE4 binaries that
141+
actually exist in the AirSim v1.8.1 release: `forest`, `soccerfield`, and
142+
`building99` were removed (their zips were never published, or are empty),
143+
`airsimnh` now downloads `AirSimNH.zip` (was the nonexistent
144+
`Neighborhood.zip`), and `africasavannah` / `msbuild2018` were added; the
145+
entrypoint resolves the UE launcher by glob after extraction, so zips whose
146+
inner `.sh` doesn't match the folder name still boot.
147+
Unknown scene = error + per-simulator availability table. Five stages were
148+
published to the guest-readable
149+
`omniverse://airlab-nucleus.andrew.cmu.edu:443/Public/AirStack/Stages/`
150+
(AbandonedFactory, AbandonedWarehouse day/night, ChemicalPlant,
151+
ConstructionSite, RetroNeighborhood) and cataloged with per-stage scale.
152+
See [Simulation Scenes](../simulation/scenes.md)
131153
- `airstack ready` (and `airstack up --wait`): staged flight-readiness gates
132154
mirroring the system-test budgets — containers → sim `/clock` → per-robot
133155
sentinel nodes → PX4 MAVROS-connected + `local_position/odom` streaming —

‎docs/simulation/index.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Simulation components are launched via Docker Compose. Each simulator has its ow
4343

4444
- **Launch command:** `airstack up --sim isaac` (or `airstack up isaac-sim` to start only the sim service)
4545
- **Main process:** The `command:` in docker-compose.yaml starts the simulator
46-
- **Scene selection (Isaac Sim):** the standalone launch script named by `ISAAC_SIM_SCRIPT_NAME` (in `.env`) defines the scene and drones; with `ISAAC_SIM_USE_STANDALONE=false`, `ISAAC_SIM_GUI` points at a USD file to open instead
46+
- **Scene selection:** `airstack up --scene <shortname>` picks the environment for whichever simulator is active — see [Simulation Scenes](scenes.md). The standalone launch script named by `ISAAC_SIM_SCRIPT_NAME` (in `.env`) defines the drones and honors the selected scene; with `ISAAC_SIM_USE_STANDALONE=false`, `ISAAC_SIM_GUI` points at a USD file to open instead
4747
- **Auto-play:** Controlled by `PLAY_SIM_ON_START` (or `airstack up --play`)
4848

4949
**Example:**

0 commit comments

Comments
 (0)