import argparse import os import shutil import subprocess import sys from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[1] BLENDER_SCRIPTS = ( "build.py", "validate.py", "validate_mesh_plane.py", "validate_cutout.py", ) def project_env_value(name, path=None): env_path = path or PROJECT_ROOT / ".env" if not env_path.is_file(): return None for raw_line in env_path.read_text(encoding="utf-8").splitlines(): line = raw_line.strip() if not line or line.startswith("#"): continue if line.startswith("export "): line = line.removeprefix("export ").lstrip() key, separator, value = line.partition("=") if separator and key.strip() == name: value = value.strip() if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'": value = value[1:-1] return value or None return None def blender_path(explicit=None): candidate = ( explicit or os.environ.get("BLENDER_BIN") or project_env_value("BLENDER_BIN") or shutil.which("blender") ) if not candidate: raise RuntimeError( "Blender not found: pass --blender, set BLENDER_BIN, add it to .env, " "or add blender to PATH" ) path = Path(candidate).expanduser() if not path.is_file(): raise RuntimeError(f"Blender executable does not exist: {path}") return path def run(command): print("+", subprocess.list2cmdline([str(part) for part in command]), flush=True) subprocess.run(command, cwd=PROJECT_ROOT, check=True) def main(): parser = argparse.ArgumentParser( description="Build and validate O_AnyImage.blend.", ) parser.add_argument( "--blender", help="Blender executable; overrides BLENDER_BIN, .env, and PATH.", ) parser.add_argument( "--skip-tests", action="store_true", help="Skip the Python test suite.", ) options = parser.parse_args() executable = blender_path(options.blender) for script_name in BLENDER_SCRIPTS: run( ( executable, "--factory-startup", "--background", "--python", PROJECT_ROOT / "tools" / "node_assets" / script_name, ) ) if not options.skip_tests: run((sys.executable, "-m", "pytest")) if __name__ == "__main__": main()