#!/usr/bin/env python3 """ check_machine_paths.py - Read-only environment & asset check for PCB AFM project. Checks: 1. Python version (>= 3.10) 2. Environment variables: MOTORCAD_ACTIVEX, ANSYSLMD_LICENSE_FILE 3. Motor-CAD executable path existence (env var + documented fallback path) 4. Python dependencies: ansys-motorcad-core, PySide6, pandas, fastapi, uvicorn, pydantic 5. Git repo: .git exists, HEAD valid, working tree clean 6. Repo assets: models/*.mot, experience/experience.db, executor_config.json, web dirs, src/afmcore 7. node / npm presence (needed for frontend build) Usage: python scripts/check_machine_paths.py # read-only check python scripts/check_machine_paths.py --fix # print exact fix commands (does NOT auto-execute) Exit code: 0 = all checks passed 1 = at least one check failed Note: The script is read-only by default. --fix only PRINTS the exact commands to run (setx / pip install), it never modifies the system by itself. All output is ASCII (project source rule). """ import importlib.util import os import shutil import subprocess import sys from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent # documented fallback for Motor-CAD when env var is missing MOTORCAD_FALLBACK = r"D:\Program Files\ANSYS Inc\v261\motorcad\MotorCAD.exe" FAILURES = [] WARNINGS = [] def check(name, ok, detail): """Record one check result and print it.""" tag = "PASS" if ok else "FAIL" print("[%s] %s: %s" % (tag, name, detail)) if not ok: FAILURES.append(name) def check_env_var(name): """Check a required environment variable; return its value or ''.""" val = os.environ.get(name, "").strip() check("env:" + name, bool(val), val if val else "MISSING") return val def main(): fix_mode = "--fix" in sys.argv[1:] print("=== PCB AFM - machine path check ===") print("repo root: %s" % REPO_ROOT) # 1. Python version ver = sys.version_info ok_ver = (ver.major, ver.minor) >= (3, 10) check("python-version", ok_ver, "%d.%d.%d" % (ver.major, ver.minor, ver.micro)) # 2. required environment variables motorcad_act = check_env_var("MOTORCAD_ACTIVEX") check_env_var("ANSYSLMD_LICENSE_FILE") # 3. Motor-CAD executable candidates = [] if motorcad_act: candidates.append(Path(motorcad_act)) candidates.append(Path(MOTORCAD_FALLBACK)) found_exes = [str(p) for p in candidates if p.exists()] check("motorcad-exe", bool(found_exes), "; ".join(found_exes) or "none found") # 4. python dependencies deps = ["ansys.motorcad.core", "PySide6", "pandas", "fastapi", "uvicorn", "pydantic"] for d in deps: try: found = importlib.util.find_spec(d) is not None except Exception: found = False check("pkg:" + d, found, "installed" if found else "MISSING") # detect a project-local virtualenv (informational: shell python may lack project deps) for venv_name in (".venv", ".env"): vd = REPO_ROOT / venv_name if vd.exists(): py = vd / ("Scripts/python.exe" if os.name == "nt" else "bin/python") if py.exists(): print("[INFO] project venv found: %s (re-run checks with that python to validate project deps)" % py) break # 5. git repo state check("git-dir", (REPO_ROOT / ".git").exists(), str(REPO_ROOT / ".git")) head_ok = False head = "" try: r = subprocess.run( ["git", "rev-parse", "--verify", "HEAD"], cwd=str(REPO_ROOT), capture_output=True, text=True, timeout=10, ) head_ok = r.returncode == 0 head = r.stdout.strip()[:12] if head_ok else "INVALID" except Exception as exc: # noqa: BLE001 - report any failure as invalid head head = "error:%s" % exc check("git-head", head_ok, head) clean = False try: r = subprocess.run( ["git", "status", "--porcelain"], cwd=str(REPO_ROOT), capture_output=True, text=True, timeout=10, ) clean = r.returncode == 0 and r.stdout.strip() == "" except Exception: clean = False check("git-clean", clean, "clean" if clean else "has uncommitted changes") # 6. repo assets models_dir = REPO_ROOT / "models" mot_files = list(models_dir.glob("*.mot")) if models_dir.exists() else [] check("asset:models-mot", len(mot_files) > 0, "%d .mot file(s)" % len(mot_files)) exp_db = REPO_ROOT / "experience" / "experience.db" check("asset:experience-db", exp_db.exists(), str(exp_db)) cfg = REPO_ROOT / "executor_config.json" check("asset:executor-config", cfg.exists(), str(cfg)) web_f = REPO_ROOT / "web" / "frontend" web_b = REPO_ROOT / "web" / "backend" check("asset:web-frontend", web_f.exists(), str(web_f)) check("asset:web-backend", web_b.exists(), str(web_b)) afmcore = REPO_ROOT / "src" / "afmcore" check("asset:afmcore", afmcore.exists(), str(afmcore)) # 7. node / npm (warning only, frontend build) node_ok = shutil.which("node") is not None npm_ok = shutil.which("npm") is not None if node_ok and npm_ok: print("[PASS] node/npm: found on PATH") else: WARNINGS.append("node/npm not found on PATH (needed for frontend build)") print("[WARN] node/npm: missing on PATH") # 8. summary print("=== summary ===") if FAILURES: print("FAILED (%d): %s" % (len(FAILURES), ", ".join(FAILURES))) for w in WARNINGS: print("WARN: %s" % w) if fix_mode: _print_fix_commands() sys.exit(1) else: print("ALL CHECKS PASSED") for w in WARNINGS: print("WARN: %s" % w) sys.exit(0) def _print_fix_commands(): """Print (do not execute) the exact commands to fix each failure.""" print("=== suggested fixes (run manually) ===") for name in FAILURES: if name == "env:MOTORCAD_ACTIVEX": print( 'setx MOTORCAD_ACTIVEX "%s" # or let scripts fall back to set_motorcad_exe()' % MOTORCAD_FALLBACK ) elif name == "env:ANSYSLMD_LICENSE_FILE": print('setx ANSYSLMD_LICENSE_FILE "1055@localhost"') elif name.startswith("pkg:"): pkg = name.split(":", 1)[1] pip_name = "ansys-motorcad-core" if pkg == "ansys.motorcad.core" else pkg print("pip install %s" % pip_name) elif name == "git-clean": print("git commit (or stash) uncommitted changes before running simulations") elif name == "asset:experience-db": print("run once to bootstrap the experience database (see README)") elif name == "asset:models-mot": print("place a baseline .mot model into models/ (read-only)") if __name__ == "__main__": main()