Source code for reasitic.exports.tek
"""Tektronix-style line-drawing dumps.
The binary's ``PrintTekFile`` command (case 214) writes a Tek 4014-
emulator command stream that draws the current view to a vector
plotter / terminal. We support two outputs:
* :func:`write_tek` — gnuplot-friendly x/y text format::
# name=L1 metal=2
x1 y1
x2 y2
...
(blank line)
Loads via ``plot 'foo.tek' with lines``.
* :func:`write_tek4014` — true Tek 4014 escape-code stream
(GS for graphics, US for ASCII, addressed-vector mode HiY/HiX/LoY/LoX
bytes). Mirrors the binary's textual output byte-for-byte for tools
that expect that format (Tektronix terminal emulators).
"""
from __future__ import annotations
from collections.abc import Iterable
from io import StringIO
from pathlib import Path
from reasitic.geometry import Shape
[docs]
def write_tek(shapes: Iterable[Shape]) -> str:
"""Emit a gnuplot-style x/y dump of every polygon in ``shapes``."""
out = StringIO()
for sh in shapes:
for p in sh.polygons:
out.write(f"# name={sh.name} metal={p.metal}\n")
for v in p.vertices:
out.write(f"{v.x:.4f} {v.y:.4f}\n")
out.write("\n")
return out.getvalue()
[docs]
def write_tek_file(path: str | Path, shapes: Iterable[Shape]) -> None:
"""Write the Tek/gnuplot rendering of ``shapes`` to ``path``."""
Path(path).write_text(write_tek(shapes))
[docs]
def write_tek4014(
shapes: Iterable[Shape],
*,
extent_x: float | None = None,
extent_y: float | None = None,
) -> bytes:
r"""Emit a Tek 4014-format escape-code byte stream.
Tek 4014 graphics: ``\x1d`` (GS) enters graphics mode; coordinates
are 12-bit (0..4095) sent as four bytes:
.. code-block:: text
HiY = 0x20 + ((Y >> 7) & 0x1f)
LoY = 0x60 + ((Y >> 2) & 0x1f)
HiX = 0x20 + ((X >> 7) & 0x1f)
LoX = 0x40 + ((X >> 2) & 0x1f)
The first vector after GS is "dark" (move-to); subsequent vectors
are "bright" (line-to) until the next GS. Returns ``bytes`` since
the Tek 4014 stream is not pure ASCII.
"""
shape_list = list(shapes)
if not shape_list:
return b""
if extent_x is None or extent_y is None:
all_x: list[float] = []
all_y: list[float] = []
for sh in shape_list:
for p in sh.polygons:
for v in p.vertices:
all_x.append(v.x)
all_y.append(v.y)
if not all_x:
return b""
extent_x = max(extent_x or 0, max(all_x) - min(all_x), 1e-6)
extent_y = max(extent_y or 0, max(all_y) - min(all_y), 1e-6)
x_off = -min(all_x)
y_off = -min(all_y)
else:
x_off = 0.0
y_off = 0.0
out = bytearray()
GS = 0x1D # graphics mode
def addr(x: float, y: float) -> bytes:
# Map (x, y) into 0..4095 range using the chip extent.
ix = int(((x + x_off) / max(extent_x, 1e-9)) * 4095.0)
iy = int(((y + y_off) / max(extent_y, 1e-9)) * 4095.0)
ix = max(0, min(4095, ix))
iy = max(0, min(4095, iy))
hi_y = 0x20 | ((iy >> 7) & 0x1F)
lo_y = 0x60 | ((iy >> 2) & 0x1F) # 5-bit rather than 7
hi_x = 0x20 | ((ix >> 7) & 0x1F)
lo_x = 0x40 | ((ix >> 2) & 0x1F)
return bytes([hi_y, lo_y, hi_x, lo_x])
for sh in shape_list:
for poly in sh.polygons:
if not poly.vertices:
continue
out.append(GS) # enter graphics
v0 = poly.vertices[0]
out.extend(addr(v0.x, v0.y)) # dark vector (move)
for v in poly.vertices[1:]:
out.extend(addr(v.x, v.y)) # bright vector (line-to)
# Exit graphics with US (unit separator)
out.append(0x1F)
return bytes(out)
[docs]
def write_tek4014_file(
path: str | Path,
shapes: Iterable[Shape],
**kwargs: object,
) -> None:
"""Write a Tek 4014 binary stream to ``path``."""
Path(path).write_bytes(write_tek4014(shapes, **kwargs)) # type: ignore[arg-type]