from __future__ import annotations
import json
import os
from typing import Iterable, List, Optional, Sequence, Tuple
from alphageometry.modules import utils
Point = Tuple[float, float]
Triangle = Tuple[int, int, int]
def _scale_and_translate(points: Iterable[Point], width: float = 400, height: float = 400, margin: float = 8.0):
pts = list(points)
if not pts:
return [], width, height
(minx, miny), (maxx, maxy) = utils.bounding_box(pts)
w = maxx - minx
h = maxy - miny
if w == 0 and h == 0:
# single point
return [(width / 2.0, height / 2.0)], width, height
sx = (width - 2 * margin) / (w if w != 0 else 1.0)
sy = (height - 2 * margin) / (h if h != 0 else 1.0)
s = min(sx, sy)
out = [((p[0] - minx) * s + margin, (maxy - p[1]) * s + margin) for p in pts]
return out, width, height
def polygon_to_svg(
poly: Iterable[Point],
stroke: str = "black",
fill: str = "none",
width: int = 400,
height: int = 400,
label: Optional[str] = None,
show_vertex_labels: bool = False,
) -> str:
"""Render a single polygon to an SVG string.
Parameters:
- label: optional text label for the polygon (renders at centroid)
- show_vertex_labels: when True, each vertex is annotated with its index
"""
pts = list(poly)
scaled, w, h = _scale_and_translate(pts, width=width, height=height)
path = " ".join(f"{x:.2f},{y:.2f}" for x, y in scaled)
svg = [f"")
return "\n".join(svg)
def mesh_to_svg(
vertices: Iterable[Point],
triangles: Iterable[Triangle],
stroke: str = "black",
fill: str = "none",
width: int = 600,
height: int = 600,
show_triangle_labels: bool = False,
) -> str:
verts = list(vertices)
tri = list(triangles)
scaled, w, h = _scale_and_translate(verts, width=width, height=height)
svg = [f"")
return "\n".join(svg)
def render_scene(
polygons: Optional[Sequence[Iterable[Point]]] = None,
meshes: Optional[Sequence[Tuple[Iterable[Point], Iterable[Triangle]]]] = None,
width: int = 800,
height: int = 600,
background: str = "white",
) -> str:
"""Render multiple polygons and meshes into a single SVG scene.
This composes objects into one SVG canvas. Each polygon/mesh will be
scaled independently to the full canvas; callers who need consistent
coordinates should pre-scale externally.
"""
svg = [f"")
return "\n".join(svg)
def write_svg(path: str, svg_text: str) -> None:
"""Write the SVG string to disk, creating parent directories if needed."""
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf8") as f:
f.write(svg_text)
def mesh_to_json(vertices: Iterable[Point], triangles: Iterable[Triangle]) -> str:
payload = {"vertices": [list(v) for v in vertices], "triangles": [list(t) for t in triangles]}
return json.dumps(payload)