"""SimReady asset packaging — entry point for the /simready-package skill. Usage: python package.py [--profile NAME] [--output DIR] For each immediate subfolder of containing a USD file, picks the interface USD and runs `simready ingest usd` on it, producing a packaged tree under . Independent of /simready-report — runs the packaging step alone, without validation or dashboard generation. """ from __future__ import annotations import argparse import os import shutil import subprocess import sys from pathlib import Path USD_EXTS = (".usd", ".usda", ".usdc", ".usdz") def _find_simready_cli() -> str | None: """Locate the `simready` CLI in a layout-agnostic way. Order: (1) on PATH, (2) alongside `sys.executable` (covers any active venv/conda regardless of OS). """ on_path = shutil.which("simready") if on_path: return on_path py_bin = Path(sys.executable).parent for candidate in (py_bin / "simready.exe", py_bin / "simready"): if candidate.is_file(): return str(candidate) return None def _discover_interface_usds(target: Path) -> list[Path]: """Pick one interface USD per immediate subfolder of `target`. Heuristic per subfolder: - If exactly one USD at root, that's the interface. - Else prefer the USD whose stem matches the folder name (case- and hyphen/underscore-insensitive). - Else fall back to packaging all of them. Subfolders starting with "." or "_" are ignored. """ interface_files: list[Path] = [] for sub in sorted(target.iterdir()): if not sub.is_dir() or sub.name.startswith((".", "_")): continue usds = [p for ext in USD_EXTS for p in sub.glob(f"*{ext}")] if not usds: continue if len(usds) == 1: interface_files.append(usds[0]) continue named = [p for p in usds if p.stem.lower() == sub.name.lower().replace("-", "_")] if named: interface_files.append(named[0]) else: interface_files.extend(usds) return interface_files def _default_output(target: Path) -> Path: """Pick a default output dir based on target location.""" target_str = str(target).replace("\\", "/") if "/assets_to_validate/" in target_str: # Workspace-style layout: /assets_to_validate/ # → output at sibling packages dir: /packages/ return target.parent.parent / "packages" / target.name return target.parent / "packages" / target.name def main() -> int: ap = argparse.ArgumentParser(description="Package SimReady assets via `simready ingest usd`.") ap.add_argument("target", nargs="?", default=None, help="Directory containing one or more asset subfolders (default: cwd)") ap.add_argument("--profile", default="Robot-Body-Runnable", help="Profile passed to `simready ingest usd -p` (default: Robot-Body-Runnable)") ap.add_argument("--output", default=None, help="Output dir (default: .parent/packages/, " "or sibling-of-assets_to_validate/packages/ when applicable)") args = ap.parse_args() target = Path(args.target).resolve() if args.target else Path.cwd().resolve() if not target.is_dir(): print(f"ERROR: target is not a directory: {target}", flush=True) return 2 simready_exe = _find_simready_cli() if simready_exe is None: print("ERROR: `simready` CLI not found on PATH or alongside the active Python.", flush=True) print(" Install simready-oem-sdk-poc into the active Python, or run bootstrap.ps1.", flush=True) return 2 interface_files = _discover_interface_usds(target) if not interface_files: print(f"ERROR: no USD files found in immediate subfolders of {target}.", flush=True) print(" /simready-package expects each asset to live in its own subfolder " "with an interface USD.", flush=True) return 2 output = Path(args.output).resolve() if args.output else _default_output(target) output.mkdir(parents=True, exist_ok=True) print(f"Target: {target}", flush=True) print(f"Output: {output}", flush=True) print(f"Profile: {args.profile}", flush=True) print(f"CLI: {simready_exe}", flush=True) print(f"Discovered {len(interface_files)} interface USD(s).", flush=True) env = {**os.environ, "PYTHONIOENCODING": "utf-8", "PYTHONUTF8": "1"} packaged = 0 skipped = 0 failures: list[tuple[str, str]] = [] for usd in interface_files: already = output / usd.stem / usd.name if already.is_file(): print(f" skip (already packaged): {usd.name}", flush=True) skipped += 1 continue print(f" package: {usd.name}", flush=True) cmd = [simready_exe, "ingest", "usd", str(usd), "-o", str(output), "-p", args.profile, "--no-validate"] try: subprocess.run(cmd, env=env, check=True, capture_output=True, text=True, timeout=120) packaged += 1 except subprocess.CalledProcessError as e: err = (e.stderr or "")[-300:] or str(e) failures.append((usd.name, err)) print(f" FAILED: {usd.name} - {err}", flush=True) print(f"\nSUMMARY: packaged={packaged} skipped={skipped} failed={len(failures)}", flush=True) print(f"OUTPUT: {output}", flush=True) return 0 if not failures else 1 if __name__ == "__main__": sys.exit(main())