Skip to main content

navigation-primitives

Shared mobile-robot substrate: footprints, A*, kinematics, cameras. Use before navigation/MobilityGen; not for map.yaml export (use occupancy-map).

Datos de origen

Repositorio
isaac-sim/IsaacSim
Última actividad en el origen
18 de septiembre de 2026 a las 16:05
Idioma detectado de SKILL.md
inglés
Estrellas
4169
Forks
560

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Explorador de archivos
5 archivos

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
navigation-primitives
description
Shared mobile-robot substrate: footprints, A*, kinematics, cameras. Use before navigation/MobilityGen; not for map.yaml export (use occupancy-map).
license
Apache-2.0
metadata
{"author":"Renato Gasoto <info@nvidia.com>"}
# Navigation Primitives — Shared Substrate ## Purpose Shared mobile-robot substrate: occupancy maps, A* planning, robot footprints, differential/holonomic kinematics, and chase-camera math. ## Prerequisites - Built Isaac Sim with `isaacsim.core.experimental`, `wheeled_robots` controllers, and `SimulationManager`. - A loaded robot articulation for footprint derivation; `numpy`, `scipy`, `cv2` for planning/collision helpers. - A `map.yaml`/`map.png` pair from `occupancy-map` when using pre-baked grids (optional for runtime projection). ## Limitations - This is the shared substrate only — it does not drive robots, record SDG, or publish ROS topics; jump to the specialization skills for those. - Footprints/kinematics assume authored collider geometry and correct scene units; garbage colliders yield garbage footprints. - Oriented-footprint PhysX checks require `SimulationManager.initialize()`; the grid-based check works offline but is coarser. ## Troubleshooting | Error / symptom | Cause | Solution | |---|---|---| | Robot falls through floor / feet float | Spawned without `z_offset` | Spawn at `z = ground + compute_robot_footprint(...)["z_offset"]` | | Path clips walls in render but not omap | Skipped oriented-footprint validation | Run `footprint_clips_grid` per waypoint (Validation Pipeline steps 6-7) | | Grid full of shell/signage obstacles | Visual-bbox projection without filtering | Prefer collider-driven filtering; apply the geometric filter list | Foundation layer for mobile robot navigation. Consumed by: - `isaac-sim-robot-navigation`: runtime navigation in custom scripts (RL policy, physics-vs-baked, GPU OOM). - `mobility-gen`: two-phase MobilityGen SDG (record -> replay+render). - `occupancy-map`: produces the `map.yaml` consumed here. Read this first for any mobile-robot work, then jump to the specialization. ## Available Scripts | Script | Purpose | Arguments | |---|---|---| | `scripts/occupancy_map_from_usd.py` | Rasterize a 2D occupancy grid directly from USD geometry (no PhysX required) | see script --help | | `scripts/robot_footprint.py` | Compute robot footprint dimensions and Z-offset from USD collision geometry | see script --help | ## Running scripts From agent runtimes that expose skill execution helpers, invoke helpers with `run_script()`: ```python run_script("scripts/occupancy_map_from_usd.py", args=["--help"]) ``` From a built Isaac Sim tree, run the same file with `./python.sh` (Linux) or `python.bat` (Windows) from `_build/*/release`, or execute shell helpers directly when they do not require the simulator. ## Shared NVIDIA APIs | Capability | Module | |---|---| | Occupancy maps | `isaacsim.replicator.mobility_gen.impl.occupancy_map.OccupancyMap` | | A* path planner | `isaacsim.replicator.mobility_gen.impl.path_planner.generate_paths` | | Runtime omap from stage | `isaacsim.asset.gen.omap.bindings._omap.Generator` | | Robot articulation | `isaacsim.core.experimental.prims.Articulation` | | Differential controller | `isaacsim.robot.experimental.wheeled_robots.controllers.DifferentialController` | | Holonomic controller | `isaacsim.robot.wheeled_robots.controllers.holonomic_controller.HolonomicController` | | Physics lifecycle | `isaacsim.core.simulation_manager.SimulationManager` | `OccupancyMap.from_ros_yaml(path)` loads a YAML+PNG pair (produced by `occupancy-map`). Both isaac-sim-robot-navigation and mobility-gen consume this same format. ## Robot Footprints & Z-Offsets — Derived at Runtime Do not hardcode footprints. Walk the articulation's collider prims and union their world-space AABBs. This handles every robot (Spot, Carter, VSVXL, Jetbot, Kaya, H1, custom) and stays correct when assets change. `compute_robot_footprint(stage, robot_root)` — walk CollisionAPI prims, union their AABBs, return size, z_offset, inscribed_radius, circumscribed_radius. See [`scripts/robot_footprint.py`](scripts/robot_footprint.py). Use `inscribed_radius` when the robot can rotate freely in place (over-conservative, zero clip). Use `circumscribed_radius` only when you require zero false negatives. For non-circular robots (Spot, VSVXL), prefer the oriented-footprint check below over a single radius. **Always spawn the robot at `z = ground + z_offset`**. Missing the Z-offset is the #1 cause of "robot falls through the floor" or "feet pop above ground" bugs. ### Reference Values (sanity check only) If your `compute_robot_footprint` output is far from these, your collider authoring or scene units are wrong: | Robot | Expected size (m) | Expected z_offset | Inscribed r | |---|---|---|---| | Spot | ~1.08 × 0.44 × 0.55 | ~0.69 | ~0.22 | | Spot + arm | ~1.10 × 0.40 × 1.20 | ~0.69 | ~0.20 | | Nova Carter | track_w=0.499, wheel_r=0.14 | ~0.0 | ~0.25 | | VSVXL | ~2.52 × 1.72, 6-wheel diff | ~0.0 | ~0.86 | | Jetbot | wheel_base=0.1125, wheel_r=0.03 | ~0.02 | ~0.06 | | Kaya (holonomic) | wheel_base=0.10, wheel_r=0.04 | ~0.02 | ~0.10 | | H1 (humanoid) | — | ~1.05 | ~0.20 | ## Occupancy Map from USD (Direct Projection) Use when you need a runtime omap and don't already have a `map.yaml`. For the canonical `map.yaml` workflow consumed by MobilityGen, use `occupancy-map` instead. `occupancy_map_from_usd(stage, x_range, y_range, resolution, z_cutoff, colliders_only)` — rasterize USD geometry into a 2D uint8 grid (0=free, 255=occupied). See [`scripts/occupancy_map_from_usd.py`](scripts/occupancy_map_from_usd.py). ### Obstacle Filtering (CRITICAL — learned 2026-03-13) Two strategies, in order of preference: **A. Collider-driven (preferred when assets have authored colliders).** Iterate only prims with `UsdPhysics.CollisionAPI`. This already excludes visual-only geometry (signage, decals, light cones, debug arrows) without a filter list. ```python from pxr import UsdPhysics for prim in Usd.PrimRange(stage.GetPrimAtPath("/World")): if not prim.HasAPI(UsdPhysics.CollisionAPI): continue enabled = prim.GetAttribute("physics:collisionEnabled") if enabled and enabled.Get() is False: continue # rasterize this prim's AABB into the grid ``` **B. Visual-bbox + filter list (fallback for scenes without colliders).** Naive bbox projection fills the grid with shell, zones, signage. Filter aggressively: ```python SKIP_SCOPES = {"GroundPlane", "Looks", "Lighting", "Render", "PushGraph", "DomeLight", "DemoCamera", "Spot_01", "Spot_02", "Obstacles_Anim"} SKIP_PREFIXES = ("Floor", "FL_", "FR_", "Exit_", "Hum", "SM_Deluxe") SKIP_CHILDREN = ("sm_warehouse_mega", "Zones", "signage") ``` Geometric filters (apply after either strategy): - Skip area > 3000 m² (shell, zone assemblies) - Skip height < 0.1 m (floor markings, safety tape) - Skip Z_min > 3.6 m (ceiling-only objects, ≈ 3× robot height) - Skip Z_max < `fp["z_offset"]` + 0.05 m (anything the robot can drive over) **Result on V4 KION**: 244 real obstacles (vs 5000+ before filtering), 70% free space. ### Buffer Sizing — Derived from Footprint The buffer is `circumscribed_radius + safety_margin`, not a magic constant. Empirical `safety_margin` defaults (validated 2026-03-15): | Context | Safety margin | |---|---| | Open corridor, smooth control | 0.10 m | | Aisle navigation, cluttered | 0.30 m | | Cluttered + non-zero yaw error | 0.50 m | ```python fp = compute_robot_footprint(stage, "/World/Robot") buffer_m = fp["circumscribed_radius"] + 0.30 # aisle default buffer_cells = int(round(buffer_m / RESOLUTION)) ``` For Spot (`circumscribed_radius ≈ 0.58 m`) this yields ~0.88–1.08 m, not the legacy 1.5 m blanket value. The legacy value was conservatively tuned against a circular proxy; with the oriented-footprint check (below) you recover the extra ~0.5 m of navigable space. ## A* Path Planning Erode by `inscribed_radius` (fast, conservative). Then validate the smoothed path with an oriented-footprint collision check, which recovers the navigable space the inscribed-radius erosion threw away. ```python from scipy.ndimage import binary_erosion import numpy as np fp = compute_robot_footprint(stage, "/World/Robot") kernel_r = int(fp["inscribed_radius"] / RESOLUTION) kernel = np.zeros((2*kernel_r+1, 2*kernel_r+1), dtype=bool) for dy in range(-kernel_r, kernel_r+1): for dx in range(-kernel_r, kernel_r+1): if dx*dx + dy*dy <= kernel_r*kernel_r: kernel[dy+kernel_r, dx+kernel_r] = True navigable = (grid == 0) eroded = binary_erosion(navigable, structure=kernel) # Standard A* over `eroded` (heapq-based) ``` ### Oriented-Footprint Collision Check (recommended for non-circular robots) Drives the same PhysX query the simulator uses. Works in two modes: **1. Against PhysX scene (after `SimulationManager.initialize()`):** use `get_physx_scene_query_interface().overlap_box`. Returns hit count; >0 means clip. ```python import carb from omni.physx import get_physx_scene_query_interface from pxr import Gf def footprint_clips(x: float, y: float, yaw: float, fp: dict, z_query: float = 0.2) -> bool: # half-extents of the robot footprint half = carb.Float3(fp["size"][0] / 2, fp["size"][1] / 2, fp["size"][2] / 2) origin = carb.Float3(x, y, z_query + fp["size"][2] / 2) # quaternion (x, y, z, w) for yaw about Z rot = Gf.Rotation(Gf.Vec3d(0, 0, 1), np.degrees(yaw)).GetQuat() quat = carb.Float4(*rot.GetImaginary(), rot.GetReal()) hits = get_physx_scene_query_interface().overlap_box( half, origin, quat, lambda h: True, anyHit=True, # early-exit on first hit ) return hits > 0 ``` **2. Against the rasterized omap (no PhysX needed):** stamp the rotated rectangle onto the obstacle grid and AND with the occupied mask. ```python import cv2 def footprint_clips_grid(px: int, py: int, yaw: float, fp: dict, grid: np.ndarray) -> bool: w_cells = fp["size"][0] / RESOLUTION h_cells = fp["size"][1] / RESOLUTION rect = ((px, py), (w_cells, h_cells), np.degrees(yaw)) pts = cv2.boxPoints(rect).astype(np.int32) mask = np.zeros_like(grid, dtype=np.uint8) cv2.fillPoly(mask, [pts], 1) return bool(np.any((grid > 0) & (mask > 0))) ``` ### Validation Pipeline (MANDATORY) 1. `compute_robot_footprint(stage, robot_root)` — get size, z_offset, radii. 2. Rasterize obstacles onto grid (0.25 m resolution typical). 3. Binary erode with circular kernel of radius = `inscribed_radius / resolution`. 4. A* pathfind on eroded grid. 5. Catmull-Rom smooth the raw path; assign yaw = `atan2(dy, dx)` along the curve. 6. **For every smoothed waypoint, run `footprint_clips_grid(px, py, yaw, fp, grid)`** (or `footprint_clips(...)` against PhysX). Reject the path on any hit. 7. If a single waypoint fails, snap to the nearest navigable cell and re-validate. If multiple fail, the inscribed-radius A* path is fundamentally bad — re-plan with a larger erosion kernel (`circumscribed_radius`). Skipping steps 6–7 produces paths that look fine on the omap but clip walls in render — especially on rectangular robots (Spot, VSVXL) cornering through aisles. ## Kinematics Helpers [`scripts/kinematics.py`](scripts/kinematics.py) — standalone differential-drive and holonomic kinematics with pure-pursuit path following. No Isaac Sim dependency; works offline for planning or inside a simulation loop. Key exports: - `differential_forward(v, ω, params)` → `(vL, vR)` wheel rad/s - `differential_inverse(vL, vR, params)` → `(v, ω)` body twist - `holonomic_forward(vx, vy, ω, params)` → 3 wheel velocities (120°-spaced) - `holonomic_inverse(wheel_vels, params)` → `(vx, vy, ω)` - `pure_pursuit_step(pos, yaw, path, config)` — PD-steered differential path follower - `holonomic_path_step(pos, yaw, path, config)` — strafing holonomic path follower ## Differential Drive Kinematics For `motion_generation` controller composition, see `motion-generation`; this section owns the geometry, wheel kinematics, and navigation tuning. ```python # Wheel velocities from body twist vL = (vx - omega * track_w / 2) / wheel_r vR = (vx + omega * track_w / 2) / wheel_r ``` PD steering (validated on Nova Carter / VSVXL): - KP=2.5, KD=1.2, MAX_W=1.5 rad/s - Waypoint tolerance: 4.0m - Speed reduction near waypoints and during large heading errors - Out-of-bounds: |Z| > 50 or |X|/|Y| > 500 → mark dead VSVXL validated parameters (LLM Advisor Grok-4, 2026-03-15): - ω = v / r (0.15m wheels → 10 rad/s for 1.5 m/s) - Physics: dt=1/120s, substeps=4 (effective 480Hz) - PID heading: Kp=2.0, Ki=0.1, Kd=0.5, max angular 1.0 rad/s - Aisle speed: 0.8 m/s; corridor speed: 1.5 m/s - Corridor buffer: 0.5m; aisle buffer: 0.2m + reduced speed ## Holonomic / Mecanum (Kaya, AMR) Use `HolonomicController` with `HolonomicRobotUsdSetup` to extract wheel positions, orientations, mecanum angles from the robot USD. Apply via `WheeledRobot.apply_wheel_actions`. 2D MobilityGen action `[lin, ang]` → 3D holonomic command `[forward, lateral=0, yaw]`. See `mobility-gen` for the `WheeledMobilityGenRobot.build()` override pattern. ## DifferentialController + Articulation (Kit 110) ```python from isaacsim.robot.experimental.wheeled_robots.controllers import DifferentialController from isaacsim.core.experimental.prims import Articulation import numpy as np robot = Articulation("/World/Robot") robot.initialize_cpp_data_view()
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub