Spaces:
Sleeping
Sleeping
simready-validator / tools /validation /plugins /simready-report /skills /simready-package /package.py
| """SimReady asset packaging — entry point for the /simready-package skill. | |
| Usage: | |
| python package.py <target_dir> [--profile NAME] [--output DIR] | |
| For each immediate subfolder of <target_dir> containing a USD file, picks | |
| the interface USD and runs `simready ingest usd` on it, producing a | |
| packaged tree under <output_dir>. 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: <X>/assets_to_validate/<name> | |
| # → output at sibling packages dir: <X>/packages/<name> | |
| 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: <target>.parent/packages/<target.name>, " | |
| "or sibling-of-assets_to_validate/packages/<name> 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()) | |