app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
#!/usr/bin/env python3
"""cswa-check.py - the CSWA benchmark on REAL SOLIDWORKS (0.11.0).
Every spec in resources/cswa/*.json is one exam-style part: the published answer key (mass in grams,
sometimes volume / centre of mass / surface area), the material, and the swx_run_script that builds
it. Each part is built through the bridge, weighed with swx_mass_properties, and compared with the
key within the spec's tolerance (CSWA accepts about 1 %). Nothing is trusted that was not measured.
python3 tools/cswa-check.py [--target adom_drew2] [--report auto|<path>] [--only cswa-00-lego-2x4] [--fresh] [--keep]
python3 tools/cswa-check.py --derivations # re-derive every derived key (no bridge call)
Same rules as tools/dsl-live-check.py: every creation says purpose:"bridge"; by default the parts are
built in ONE bridge-made part (reuseDocument: its leading new_part becomes clear_part), so N parts
cost one document; the run STOPS on a user_present / low_commit_memory / dialog refusal and never
creates another part after one. The report (every reply, every expectation, pass/fail) is written
to cswa-report.json (--report auto: tools/cswa/reports/cswa-<date>-sw<year>-<version>.json);
tools/release.sh --require-live <report> refuses to publish without a passing one newer than the last code
commit. The report also says WHICH BUILD ran (`bridge`: the installed exe's sha256, read back through ab), and
the publish ships only the canary zip holding that exe (README, "Live validation").
"""
import argparse, base64, glob, hashlib, json, math, os, re, subprocess, sys, time
TARGET = "adom_drew2"
THREAD = "SolidWorks Bridge"
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STOP_CODES = ("user_present", "low_commit_memory", "stopped_by_dialog", "dialog_open", "user_present_refusal")
def ascii_reason(args):
"""A non-ASCII `reason` makes ab fail the call before it reaches the bridge (measured 2026-10-13)."""
if isinstance(args, dict) and isinstance(args.get("reason"), str):
args = dict(args, reason=args["reason"].encode("ascii", "replace").decode("ascii"))
return args
def call(verb, args, timeout=300):
args = ascii_reason(args)
try:
r = subprocess.run(["adom-bridge", "--target", TARGET, "--ai-thread", THREAD, verb, json.dumps(args)],
capture_output=True, text=True, timeout=timeout + 60)
except subprocess.TimeoutExpired:
return {"status": "error", "errorCode": "no_answer", "error": f"{verb} did not return in {timeout + 60}s"}
t = r.stdout
i = t.find("{")
if i < 0:
return {"status": "error", "errorCode": "not_json", "error": (t + r.stderr)[:400]}
try:
d = json.loads(t[i:])
except ValueError:
return {"status": "error", "errorCode": "not_json", "error": t[i:i + 400]}
return d["result"] if isinstance(d, dict) and isinstance(d.get("result"), dict) else d
# ── spec variables: the SAME grammar as src/cswa.rs (eval / expand) ──────────
FUNCS1 = {"sqrt": math.sqrt, "sin": math.sin, "cos": math.cos, "tan": math.tan, "asin": math.asin, "acos": math.acos,
"atan": math.atan, "abs": abs, "rad": math.radians, "deg": math.degrees}
FUNCS2 = {"atan2": math.atan2, "min": min, "max": max}
def evaluate(expr, vars_):
"""+ - * / ^ (right-assoc), unary minus, parentheses, pi, the functions above. No eval()."""
s, i = expr, [0]
def ws():
while i[0] < len(s) and s[i[0]].isspace():
i[0] += 1
def peek():
ws()
return s[i[0]] if i[0] < len(s) else None
def e():
v = t()
while peek() in ("+", "-"):
op = s[i[0]]; i[0] += 1
v = v + t() if op == "+" else v - t()
return v
def t():
v = pw()
while peek() in ("*", "/"):
op = s[i[0]]; i[0] += 1
v = v * pw() if op == "*" else v / pw()
return v
def pw():
b = un()
if peek() == "^":
i[0] += 1
return b ** pw()
return b
def un():
c = peek()
if c == "-":
i[0] += 1
return -un()
if c == "+":
i[0] += 1
return un()
return atom()
def atom():
c = peek()
if c == "(":
i[0] += 1
v = e()
if peek() != ")":
raise ValueError(f"missing ) in {expr!r}")
i[0] += 1
return v
if c is not None and (c.isdigit() or c == "."):
st = i[0]
while i[0] < len(s) and (s[i[0]].isdigit() or s[i[0]] in ".e" or (s[i[0]] in "+-" and i[0] > st and s[i[0] - 1] == "e")):
i[0] += 1
return float(s[st:i[0]])
if c is not None and (c.isalpha() or c == "_"):
st = i[0]
while i[0] < len(s) and (s[i[0]].isalnum() or s[i[0]] == "_"):
i[0] += 1
name = s[st:i[0]]
if peek() == "(":
i[0] += 1
args = [e()]
while peek() == ",":
i[0] += 1
args.append(e())
if peek() != ")":
raise ValueError(f"missing ) after {name}( in {expr!r}")
i[0] += 1
if name in FUNCS1 and len(args) == 1:
return float(FUNCS1[name](args[0]))
if name in FUNCS2 and len(args) == 2:
return float(FUNCS2[name](args[0], args[1]))
raise ValueError(f"unknown function {name}/{len(args)} in {expr!r}")
if name == "pi":
return math.pi
if name not in vars_:
raise ValueError(f"unknown variable {name} in {expr!r}")
return vars_[name]
raise ValueError(f"unexpected {c!r} in {expr!r}")
v = e()
if peek() is not None:
raise ValueError(f"unexpected text in {expr!r}")
if not math.isfinite(v):
raise ValueError(f"{expr!r} is not finite")
return v
def resolve_extends(spec, by_id, depth=0):
base_id = spec.get("extends")
if not base_id:
return json.loads(json.dumps(spec))
if depth > 8:
raise ValueError(f"{spec['id']}: extends goes more than 8 deep")
merged = resolve_extends(by_id[base_id], by_id, depth + 1)
# What is true of the BASE part only is not inherited (src/cswa.rs::resolve_extends).
for k in ("needsOps", "derivation", "simulatorSkips"):
merged.pop(k, None)
variables = dict(merged.get("variables") or {})
variables.update(spec.get("variables") or {})
for k, v in spec.items():
if k not in ("extends", "variables", "scriptAppend", "derivedAppend"):
merged[k] = v
merged["variables"] = variables
# A derivedAppend name the base already derives would shadow it (src/cswa.rs::resolve_extends).
taken = {p[0] for p in merged.get("derived") or []}
for name, _ in spec.get("derivedAppend") or []:
if name in taken:
raise ValueError(f"{spec['id']}: derivedAppend redefines {name!r}, which {base_id} already derives - name it differently")
for key, into in (("scriptAppend", "script"), ("derivedAppend", "derived")):
if key in spec:
merged.setdefault(into, []).extend(spec[key])
return merged
def expand(spec, by_id):
spec = resolve_extends(spec, by_id)
vars_ = {k: float(v) for k, v in (spec.get("variables") or {}).items()}
for name, ex in spec.get("derived") or []:
vars_[name] = evaluate(ex, vars_)
def sub(v):
if isinstance(v, str) and v.startswith("="):
return evaluate(v[1:], vars_)
if isinstance(v, list):
return [sub(x) for x in v]
if isinstance(v, dict):
return {k: sub(x) for k, x in v.items()}
return v
for key in ("script", "expected"):
if key in spec:
spec[key] = sub(spec[key])
return spec
def spec_vars(spec):
"""The spec's variables and derived values, by the spec grammar (as expand() computes them)."""
vars_ = {k: float(v) for k, v in (spec.get("variables") or {}).items()}
for name, ex in spec.get("derived") or []:
vars_[name] = evaluate(ex, vars_)
return vars_
QUANTITIES = ("massG", "volumeMm3", "centerOfMassMm", "surfaceAreaMm2")
def check_derivation(spec, raw):
"""0.11.4: a DERIVED key must be reproducible. Evaluate the spec's `derivation` ([name, Python
expression] pairs, in order; math's functions and the spec's variables in scope) and compare every
quantity it yields with the key: within 1e-6 (relative, or absolute below 1). Returns (values, problems)."""
import math as _m
ns = {k: getattr(_m, k) for k in dir(_m) if not k.startswith("_")}
ns.update({"abs": abs, "min": min, "max": max, "sum": sum, "len": len, "round": round, "range": range})
ns.update(spec_vars(raw))
problems, values = [], {}
for name, ex in spec.get("derivation") or []:
try:
ns[name] = eval(ex, {"__builtins__": {}}, ns) # noqa: S307 - the repo's own spec files, a reviewer's tool
except Exception as e: # pragma: no cover - reported
problems.append(f"{name} = {ex}: {type(e).__name__}: {e}")
return values, problems
if name in QUANTITIES:
values[name] = ns[name]
exp = spec.get("expected") or {}
if "massG" not in values:
problems.append("the derivation does not yield massG")
for q in QUANTITIES:
if exp.get(q) is None:
continue
if q not in values:
problems.append(f"the key has {q} but the derivation does not yield it")
continue
got, want = values[q], exp[q]
pairs = list(zip(got, want)) if isinstance(want, list) else [(got, want)]
if isinstance(want, list) and (not isinstance(got, (list, tuple)) or len(got) != len(want)):
problems.append(f"{q}: the derivation gives {got}, the key {want}")
continue
for g, w in pairs:
if abs(g - w) > 1e-6 * max(1.0, abs(w)):
problems.append(f"{q}: the derivation gives {got}, the key says {want}")
break
return values, problems
# ── WHICH BUILD RAN (the canary gate) ─────────────────────────────────────────
# tools/release.sh --publish ships the exact zip a live run tested (the canary), and refuses a report that does
# not say which build it ran. The bridge cannot hash the zip it came in (ab unzips it), so the facts come from
# ab, read-only: bridge_info (the install path), the INSTALLED exe read back through read_file (base64, in
# ranges) and hashed here, and - best effort - the manifest ab installed it from (its zip sha256, which ab
# checked before extracting).
WIKI = "https://wiki.adom.inc"
def remote_sha256(path, chunk=1_000_000):
"""sha256 of a file on the target, read back through ab's read_file in ranges. (hex, None) or (None, why)."""
h, off, total = hashlib.sha256(), 0, None
for _ in range(64):
r = call("read_file", {"path": path, "encoding": "base64", "offset": off, "length": chunk}, timeout=120)
if not isinstance(r, dict) or r.get("success") is False or r.get("content") is None:
return None, f"read_file {path}: {str(r)[:200]}"
data = base64.b64decode(r["content"])
h.update(data)
total = r.get("totalSize", r.get("size"))
off = r.get("nextOffset", off + len(data))
if r.get("eof") or not data or (total is not None and off >= total):
return h.hexdigest(), None
return None, f"read_file {path}: more than 64 ranges"
def installed_from():
"""The manifest URL ab installed the bridge from (~/.adom/bridge-install-urls.json on the target), or None."""
r = call("read_file", {"path": "~/.adom/bridge-install-urls.json"}, timeout=60)
try:
d = json.loads(r.get("content") or "")
except (AttributeError, ValueError):
return None
if isinstance(d, dict):
for k in ("bridges", "urls"):
if isinstance(d.get(k), (dict, list)):
d = d[k]
break
entries = d.items() if isinstance(d, dict) else [((e or {}).get("name"), e) for e in d if isinstance(e, dict)] if isinstance(d, list) else []
for name, v in entries:
if name == "solidworks":
return v if isinstance(v, str) else (v or {}).get("manifestUrl") or (v or {}).get("url")
return None
def wiki_json(url):
"""A wiki page file as JSON: https://wiki.adom.inc/api/v1/pages/<slug>/files/<path>, or the page form
https://wiki.adom.inc/<owner>/<slug>/files/<path>, read with adom-wiki api (authenticated)."""
m = re.match(r"^https://wiki\.adom\.inc(/api/v1/pages/[^/]+/files/.+)$", url or "")
if not m:
m2 = re.match(r"^https://wiki\.adom\.inc/[^/]+/([^/]+)/files/(.+)$", url or "")
if not m2:
return None
api = f"/api/v1/pages/{m2.group(1)}/files/{m2.group(2)}"
else:
api = m.group(1)
try:
out = subprocess.run(["adom-wiki", "api", api], capture_output=True, text=True, timeout=60).stdout
return json.loads(out)
except (OSError, subprocess.TimeoutExpired, ValueError):
return None
def bridge_facts():
facts = {"source": "ab bridge_info + read_file (read-only)"}
info = call("bridge_info", {"name": "solidworks"}, timeout=60)
if not isinstance(info, dict) or info.get("success") is False or info.get("status") == "error":
facts["why"] = f"bridge_info failed: {str(info)[:200]}"
return facts
facts.update({"version": info.get("version"), "installPath": info.get("installPath"), "manifestUrl": info.get("manifestUrl"),
"installSource": info.get("source")})
path = (info.get("installPath") or "").rstrip("\\/")
if path:
facts["exeSha256"], why = remote_sha256(path + "\\swx-bridge.exe")
if why:
facts["exeWhy"] = why
facts["bridgeJsonSha256"], _ = remote_sha256(path + "\\bridge.json")
else:
facts["exeSha256"], facts["exeWhy"] = None, "bridge_info gave no installPath"
src = installed_from()
facts["installedFrom"] = src
m = wiki_json(src) if src else None
if isinstance(m, dict):
facts["zipSha256"] = m.get("sha256")
facts["installedManifestVersion"] = m.get("version")
if m.get("canary"):
facts["canary"] = m.get("canary")
return facts
def report_path(arg, st, sw):
"""--report auto: tools/cswa/reports/cswa-<UTC date>-sw<year>-<bridge version>.json (never overwriting one)."""
if arg != "auto":
return arg
year = (sw.get("versionSupport") or {}).get("year") or (sw.get("majorVersion") or 0) + 1992
base = os.path.join(ROOT, "tools", "cswa", "reports", f"cswa-{time.strftime('%Y-%m-%d', time.gmtime())}-sw{year}-{st.get('bridgeVersion')}")
path, n = base + ".json", 2
while os.path.exists(path):
path, n = f"{base}-{n}.json", n + 1
return path
def ok(r):
return isinstance(r, dict) and r.get("success") is not False and r.get("status") != "error"
def pct_off(got, want):
if want == 0:
return 0.0 if got == 0 else float("inf")
return abs(got - want) / abs(want) * 100.0
def compare(spec, mp):
"""The same rules as src/cswa.rs::compare (the self-test's): mass always; volume, surface area and
centre of mass when the key has them; a coordinate passes within tol % or 0.05 mm."""
exp = spec["expected"]
tol = exp.get("tolerancePct", 1.0)
checks = []
mass = mp.get("massG")
if mass is None and mp.get("massKg") is not None:
mass = mp["massKg"] * 1000.0
def one(q, got, want):
off = None if got is None else pct_off(got, want)
checks.append({"quantity": q, "expected": want, "measured": got, "offPct": off, "pass": off is not None and off <= tol})
one("massG", mass, exp["massG"])
if exp.get("volumeMm3") is not None:
v = mp.get("volumeMm3")
if v is None and mp.get("volumeM3") is not None:
v = mp["volumeM3"] * 1e9
one("volumeMm3", v, exp["volumeMm3"])
if exp.get("surfaceAreaMm2") is not None:
a = mp.get("surfaceAreaMm2")
if a is None and mp.get("surfaceAreaM2") is not None:
a = mp["surfaceAreaM2"] * 1e6
one("surfaceAreaMm2", a, exp["surfaceAreaMm2"])
if exp.get("centerOfMassMm") is not None:
got = mp.get("centerOfMassMm") or []
want = exp["centerOfMassMm"]
good = len(got) == 3 and all(abs(g - w) <= max(abs(w) * tol / 100.0, 0.05) for g, w in zip(got, want))
checks.append({"quantity": "centerOfMassMm", "expected": want, "measured": got, "pass": good})
return all(c["pass"] for c in checks), checks
def main():
global TARGET
ap = argparse.ArgumentParser()
ap.add_argument("--target", default=TARGET)
ap.add_argument("--only", default="")
ap.add_argument("--fresh", action="store_true", help="a fresh part per spec instead of one reused part")
ap.add_argument("--keep", action="store_true", help="leave the parts open")
ap.add_argument("--report", default="cswa-report.json", help="the report path, or auto: tools/cswa/reports/cswa-<date>-sw<year>-<version>.json")
ap.add_argument("--expand-only", action="store_true", help="print every spec's script with its variables substituted, and stop (no bridge call)")
ap.add_argument("--derivations", action="store_true", help="re-derive every derived key from its spec's `derivation` and compare (no bridge call)")
a = ap.parse_args()
TARGET = a.target
raw = [json.load(open(p)) for p in sorted(glob.glob(os.path.join(ROOT, "resources", "cswa", "*.json")))]
by_id = {s["id"]: s for s in raw}
specs = [expand(s, by_id) for s in raw]
if a.expand_only:
print(json.dumps({s["id"]: {"script": s.get("script"), "expected": s.get("expected"), "needsOps": s.get("needsOps") or []} for s in specs}))
return 0
if a.derivations:
out, bad = {}, 0
for spec in specs:
if not spec.get("derivation"):
continue
values, problems = check_derivation(spec, resolve_extends(by_id[spec["id"]], by_id))
out[spec["id"]] = {"derived": values, "expected": spec.get("expected"), "problems": problems, "ok": not problems}
bad += bool(problems)
print(json.dumps(out, indent=1))
print(f"{len(out) - bad} of {len(out)} derived keys reproduced", file=sys.stderr)
return 1 if bad else 0
only = {x.strip() for x in a.only.split(",") if x.strip()}
st = call("swx_status", {"reason": "cswa-check: is SOLIDWORKS ready"})
sw = st.get("solidworks") or {}
print(f"bridge {st.get('bridgeVersion')} | SOLIDWORKS running={sw.get('running')} ready={sw.get('ready')} "
f"version={sw.get('majorVersion')} edition={sw.get('edition')}")
if not sw.get("running"):
print("SOLIDWORKS is not running on the target: start it (swx_start) and run again. Nothing was done.")
return 2
a.report = report_path(a.report, st, sw)
facts = bridge_facts()
print(f"bridge build: exe sha256 {(facts.get('exeSha256') or 'UNREAD - ' + str(facts.get('exeWhy') or facts.get('why')))[:76]}"
f" | installed from {facts.get('installedFrom') or '?'}")
shared = None
if not a.fresh:
nd = call("swx_new_document", {"type": "part", "purpose": "bridge", "reason": "cswa-check: the one part every CSWA spec is built in"})
if nd.get("errorCode") in STOP_CODES:
print(f"STOP the bridge refused to create the shared part ({nd.get('errorCode')}). Nothing was built.")
return 3
shared = (nd.get("documentIdentity") or {}).get("path") or (nd.get("created") or {}).get("path") or (nd.get("created") or {}).get("title")
results, stopped = [], None
for spec in specs:
sid = spec["id"]
if only and sid not in only:
continue
rec = {"id": sid, "title": spec.get("title"), "official": spec.get("official"), "source": spec.get("source"),
"expected": spec["expected"], "material": spec.get("material")}
if spec.get("needsOps"):
rec["skipped"] = f"needs ops the bridge does not have yet: {', '.join(spec['needsOps'])}"
results.append(rec)
print(f"SKIP {sid} {rec['skipped']}")
continue
t0 = time.time()
req = {"script": spec["script"], "purpose": "bridge", "reason": f"cswa-check {sid}"}
if shared:
req["reuseDocument"] = shared
r = call("swx_run_script", req)
rec["runScript"] = r
doc = r.get("documentName")
if not ok(r):
rec["pass"] = False
rec["fails"] = [f"swx_run_script failed: {r.get('errorCode')} {str(r.get('error'))[:300]}"]
if r.get("errorCode") in STOP_CODES:
stopped = r.get("errorCode")
else:
mp = call("swx_mass_properties", {"targetDocument": doc, "reason": f"cswa-check {sid}: weigh it"})
rec["massProperties"] = mp
rec["pass"], rec["checks"] = compare(spec, mp)
rec["fails"] = [f"{c['quantity']}: {c.get('measured')} vs {c['expected']} ({c.get('offPct')} % off)" for c in rec["checks"] if not c["pass"]]
rec["seconds"] = round(time.time() - t0, 1)
results.append(rec)
print(f"{'PASS' if rec['pass'] else 'FAIL'} {sid} {spec.get('title')} ({rec['seconds']} s)")
for f in rec.get("fails", []):
print(f" - {f}")
if stopped:
print(f"STOP the bridge refused ({stopped}); no more parts will be built. Re-run when the user is idle / memory is back / the dialog is answered.")
break
if not shared and not a.keep and doc:
rw = call("swx_close_doc", {"targetDocument": doc, "save": False, "reason": f"cswa-check: close {sid}"})
if not ok(rw):
stopped = rw.get("errorCode") or "close_refused"
print(f"STOP the close after {sid} was refused ({stopped}); no more parts will be built.")
break
if shared and not a.keep:
call("swx_close_doc", {"path" if ("\\" in shared or "/" in shared) else "targetDocument": shared, "save": False, "reason": "cswa-check: close the shared part"})
ran = [r for r in results if "skipped" not in r]
passed = sum(1 for r in ran if r.get("pass"))
report = {"tool": "cswa-check", "when": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "whenEpoch": int(time.time()),
"target": TARGET, "bridgeVersion": st.get("bridgeVersion"), "bridge": facts,
"solidworks": {k: sw.get(k) for k in ("majorVersion", "edition", "versionSupport")},
"summary": f"{passed} of {len(ran)} CSWA parts within tolerance", "passed": passed, "ran": len(ran),
"skipped": len(results) - len(ran), "stopped": stopped, "allPassed": bool(ran) and passed == len(ran) and not stopped,
"parts": results}
json.dump(report, open(a.report, "w"), indent=1)
print(f"\n{report['summary']}{' (STOPPED: ' + stopped + ')' if stopped else ''}. Full evidence: {a.report}")
return 0 if report["allPassed"] else 1
if __name__ == "__main__":
sys.exit(main())