will702's picture
Upload folder using huggingface_hub
eab734a verified
Raw
History Blame Contribute Delete
3.67 kB
"""CLI: stt — statement to TikZ."""
from __future__ import annotations
import json
from pathlib import Path
from typing import Optional
import typer
from statement_to_tikz.pipeline import load_ir, run_pipeline
app = typer.Typer(
add_completion=False,
help="Convert geometry statements into constraint-solved TikZ diagrams.",
)
def _split_out(out: Path) -> tuple[Path, str]:
"""Interpret --out out/fig as directory out, basename fig."""
if out.suffix in {".tex", ".pdf", ".json"}:
return out.parent or Path("."), out.stem
parent = out.parent
name = out.name or "fig"
if parent == Path(""):
parent = Path(".")
return parent, name
@app.command()
def main(
statement: Optional[str] = typer.Argument(
None,
help="Geometry statement text, or path to a .txt file",
),
out: Path = typer.Option(
Path("out/fig"),
"--out",
"-o",
help="Output path prefix (directory/basename without extension)",
),
ir_path: Optional[Path] = typer.Option(
None,
"--ir",
help="Load GeometryIR JSON instead of formalizing a statement",
),
emit_only: bool = typer.Option(
False,
"--emit-only",
help="With --ir: solve and emit without calling the LLM",
),
no_compile: bool = typer.Option(
False,
"--no-compile",
help="Skip pdflatex/latexmk",
),
schematic: bool = typer.Option(
False,
"--schematic",
help="Force schematic layout",
),
no_offline: bool = typer.Option(
False,
"--no-offline",
help="Disable offline pattern formalizer; require LLM",
),
tol: float = typer.Option(1e-4, "--tol", help="Exact-mode residual tolerance"),
) -> None:
"""Generate TikZ from a statement or IR file."""
if statement is None and ir_path is None:
typer.echo(
"Usage: stt STATEMENT [-o out/fig] | stt --ir scene.ir.json --emit-only"
)
raise typer.Exit(code=1)
text: str | None = None
ir = None
if ir_path is not None:
ir = load_ir(ir_path)
if not emit_only and statement:
text = statement
else:
text = ir.statement or None
elif statement is not None:
p = Path(statement)
if p.is_file():
text = p.read_text(encoding="utf-8").strip()
else:
text = statement
out_dir, basename = _split_out(out)
result = run_pipeline(
text,
ir=ir,
out_dir=out_dir,
basename=basename,
tol=tol,
compile_pdf=not no_compile,
allow_offline_formalizer=not no_offline,
force_schematic=schematic,
)
typer.echo(f"mode: {result['mode']}")
typer.echo(f"max_residual: {result['max_residual']:.6g}")
if result.get("paths"):
for k, v in result["paths"].items():
if v:
typer.echo(f"{k}: {v}")
compile_info = (result.get("report") or {}).get("compile")
if compile_info:
if compile_info.get("skipped"):
typer.echo(f"compile skipped: {compile_info.get('error')}")
elif compile_info.get("ok"):
typer.echo("compile: ok")
else:
typer.echo(
f"compile: failed "
f"({compile_info.get('error') or compile_info.get('returncode')})"
)
raise typer.Exit(code=2)
summary = {
"mode": result["mode"],
"max_residual": result["max_residual"],
"paths": result.get("paths"),
}
typer.echo(json.dumps(summary))
if __name__ == "__main__":
app()