#!/usr/bin/env python3 """Compile every example, reject invalid element/fluid geometry, and isolate the bundle. Run from any directory. Logs and PDFs are kept in tmp/fluid-verification. Visual inspection of the rendered galleries remains a separate step. """ from pathlib import Path from concurrent.futures import ThreadPoolExecutor import os import shutil import subprocess ROOT = Path(__file__).resolve().parents[1] OUT = ROOT / 'tmp/fluid-verification' OUT.mkdir(parents=True, exist_ok=True) def compile_tex(source, out=OUT, cwd=ROOT, env=None): p = subprocess.run(['pdflatex', '-interaction=nonstopmode', '-halt-on-error', '-output-directory='+str(out), str(source)], cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) (out / (Path(source).stem+'.stdout')).write_bytes(p.stdout) return p.returncode, p.stdout.decode(errors='replace') def example(source): code, _ = compile_tex(source) return source.name, code with ThreadPoolExecutor(max_workers=4) as pool: results = list(pool.map(example, sorted((ROOT/'examples').glob('*.tex')))) failures = [name for name, code in results if code] print(f'Examples: {len(results)-len(failures)}/{len(results)} passed', flush=True) invalid = [ ('zero-size', 'fluid tank={fluid height=0}', 'greater than zero'), ('angle-unit', 'liquid ring={fluid angle=2cm}', 'must be unitless'), ('level-unit', 'fluid tank={fluid level=2pt}', 'must be unitless'), ('negative-size', 'fluid tank={fluid width=-1}', 'must not be negative'), ('fraction', 'fluid tank={fluid left level=1.2}', 'above one'), ('tube-width', 'u tube={fluid tube width=1cm}', 'too large'), ('bend-level', 'u tube={fluid left level=.1}', 'intersect the bend'), ('press', 'hydraulic press={fluid tube width=2cm}', 'hydraulic press geometry'), ('rotating-section', 'rotating tube={fluid level=.99}', 'leaves the tube'), ('parabola', 'rotating fluid={fluid level=.9,fluid bend=.3}', 'surface leaves'), ('meniscus', 'meniscus={fluid bend=.8}', 'Meniscus leaves'), ('capillary', 'capillary={fluid left level=.2}', 'capillary rise'), ('ring', 'liquid ring={fluid sweep=361}', 'ring geometry'), ('sphere-slice-boundary', 'sphere slice diagram={element position=.95,element axial thickness=5mm}', 'extends beyond the sphere body'), ('cylinder-slice-boundary', 'cylinder slice diagram={element position=.99,element axial thickness=2mm}', 'extends beyond the cylinder body'), ('cone-slice-boundary', 'cone slice diagram={element position=.99,element axial thickness=2mm}', 'extends beyond the base'), ] for name, pic, message in invalid: source = OUT/(name+'.tex') source.write_text('\\documentclass{article}\n\\usepackage{tikz}\n' '\\usetikzlibrary{tikzphysics.elements,tikzphysics.fluids}\n\\begin{document}\n' '\\begin{tikzpicture}\\pic {'+pic+'};\\end{tikzpicture}\n\\end{document}\n') code, log = compile_tex(source) if not code or message not in log: failures.append(name) print('Unexpected invalid-geometry result:', name, flush=True) print(f'Invalid element/fluid geometry: {len(invalid)} cases checked', flush=True) # Absolute input path to the bundle plus a clean working directory and TEXINPUTS # prevents sibling runtime libraries from silently satisfying missing content. bundle = OUT/'isolated-bundle' bundle.mkdir(exist_ok=True) shutil.copy2(ROOT/'output/overleaf/tikzphysics.sty', bundle/'tikzphysics.sty') for name in ['fluid-mechanics-reference-scenes','fluid-mechanics-nodes','fluid-patterns','elements-fonts','elements-solid-geometries']: source = bundle/(name+'.tex') shutil.copy2(ROOT/'examples'/(name+'.tex'), source) code, log = compile_tex(source, bundle, bundle, {**os.environ, 'TEXINPUTS':str(bundle)+os.pathsep}) if code or 'tikzlibrarytikzphysics.' in log: failures.append('bundle '+name) print('Failures:', ', '.join(failures) or 'none') raise SystemExit(bool(failures))