loginowskid's picture
Sync from simready-oem-library-pm@c858e9dd
cd53438 verified
"""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())