WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s
ci / test (push) Successful in 6m18s
History-free snapshot of the private development repository at cbe45c5 (2026-09-09). Code, docs and public catalogue only; no plant or job knowledge layers, no client or job names (tools/scrub.py clean). The intro deck is left out of this snapshot because its slide images could not be checked for job drawings.
This commit is contained in:
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/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 <out>/<tool>-tab<N>.png plus <out>/<tool>-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()
|
||||
Reference in New Issue
Block a user