| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- #!/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()
|