#!/usr/bin/env python3 """Render a WillTools web page headless and screenshot every tab -- the browser dry-run this machine could not do until 2026-09-07 (owner: "浏览器你 能不能在debian上面自己渲染一个?"). python tools/webshot.py pa --reads _scratch/pa_reads_x.json --out /tmp/shots python tools/webshot.py ba --reads beamcalc/fixtures/rba5_16x22.json --out /tmp/shots Starts tools/pa_web.py or tools/ba_web.py on a free port, drives Chrome headless (google-chrome / chromium on PATH) to /?tab=N&expand=all for each tab, writes /-tab.png plus /-console.txt (the page's console errors, which is the half a screenshot cannot show), then closes the server through POST /api/close. Exit 1 if any tab logged a console error. CPython 3 only; no Revit anywhere. """ import argparse, json, os, shutil, subprocess, sys, time, urllib.request ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) def chrome(): for c in ('google-chrome', 'chromium', 'chromium-browser', 'chrome'): p = shutil.which(c) if p: return p sys.exit('no chrome/chromium on PATH') def start_server(tool, reads): args = [sys.executable, os.path.join(ROOT, 'tools', '%s_web.py' % tool), '--port', '0'] if reads: args += ['--reads', reads] p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=ROOT) url = None for _ in range(200): line = p.stdout.readline() if not line: break if '_WEB_URL=' in line: url = line.split('=', 1)[1].strip() break if not url: err = p.stderr.read() sys.exit('server did not print its URL\n' + err) return p, url def main(): ap = argparse.ArgumentParser() ap.add_argument('tool', choices=['pa', 'ba']) ap.add_argument('--reads') ap.add_argument('--out', default='/tmp/webshot') ap.add_argument('--tabs', type=int, default=5) ap.add_argument('--width', type=int, default=1400) ap.add_argument('--height', type=int, default=2200) a = ap.parse_args() os.makedirs(a.out, exist_ok=True) p, url = start_server(a.tool, a.reads) exe = chrome() errors = [] try: spec = json.load(urllib.request.urlopen(url + 'api/spec', timeout=30)) tabs = len(spec.get('pages', [])) or a.tabs for n in range(1, tabs + 1): png = os.path.join(a.out, '%s-tab%d.png' % (a.tool, n)) log = os.path.join(a.out, '%s-tab%d.console.txt' % (a.tool, n)) cmd = [exe, '--headless=new', '--no-sandbox', '--disable-gpu', '--hide-scrollbars', '--window-size=%d,%d' % (a.width, a.height), '--virtual-time-budget=4000', '--enable-logging=stderr', '--v=0', '--screenshot=' + png, '%s?tab=%d&expand=all' % (url, n)] r = subprocess.run(cmd, capture_output=True, text=True, timeout=120) console = [l for l in r.stderr.splitlines() if 'CONSOLE' in l] with open(log, 'w') as fh: fh.write('\n'.join(console)) bad = [l for l in console if 'error' in l.lower() or 'uncaught' in l.lower()] errors += bad print('tab %d -> %s (%d console lines, %d errors)' % (n, png, len(console), len(bad))) finally: try: urllib.request.urlopen(urllib.request.Request(url + 'api/close', method='POST', data=b'{}'), timeout=5) except Exception: pass p.terminate() if errors: print('CONSOLE ERRORS:\n' + '\n'.join(errors[:20])) sys.exit(1) if __name__ == '__main__': main()