"""Blender headless scene validation (collections and naming). Usage (from repo root): blender -b sensors/finger/r01/source/geometry.blend -P blender/scripts/validate.py -- sensors/finger/r01 Note: this script loads design.yaml with PyYAML. Prefer installing PyYAML into Blender's Python, or run metadata-only checks via: orisys-design validate """ from __future__ import annotations import sys from pathlib import Path import bpy try: import yaml except ImportError: yaml = None def _argv_after_double_dash() -> list[str]: if "--" not in sys.argv: return [] return sys.argv[sys.argv.index("--") + 1 :] def _load_design(revision_dir: Path) -> dict: if yaml is None: raise RuntimeError( "PyYAML is not installed in Blender's Python. " "Run: orisys-design validate without --blender, " "or pip install PyYAML into Blender's Python." ) with (revision_dir / "design.yaml").open(encoding="utf-8") as handle: data = yaml.safe_load(handle) if not isinstance(data, dict): raise ValueError("design.yaml must be a mapping") return data def main() -> int: args = _argv_after_double_dash() if not args: print("validate.py: pass revision directory after --", file=sys.stderr) return 2 revision_dir = Path(args[0]).resolve() try: design = _load_design(revision_dir) except Exception as exc: print(f"ERROR: {exc}", file=sys.stderr) return 1 collections = design.get("collections", {}) objects = design.get("objects", {}) errors: list[str] = [] present = {col.name for col in bpy.data.collections} for key, expected_name in collections.items(): if expected_name not in present: errors.append(f"Missing collection {expected_name!r} (design key: {key})") for key, expected_name in objects.items(): obj = bpy.data.objects.get(expected_name) if obj is None: errors.append(f"Missing object {expected_name!r} (design key: {key})") if errors: for message in errors: print(f"ERROR: {message}", file=sys.stderr) print(f"Present collections: {sorted(present)}", file=sys.stderr) return 1 print(f"OK: Blender scene matches conventions for {revision_dir.name}") return 0 if __name__ == "__main__": raise SystemExit(main())