kglobe/terminalplotter.py
2025-07-21 21:55:44 +02:00

71 lines
2.3 KiB
Python

import asyncio
import io
import math
import select
import sys
import termios
import tty
import pyvista as pv
import kitty
class TerminalPlotter(pv.Plotter):
def __init__(self, width, height, **kwargs):
super().__init__(off_screen=True, window_size=(height, width), **kwargs)
self._running = True
h_pix, _ = kitty.get_terminal_cell_size()
self.rows, _ = kitty.get_terminal_size()
self.start_y, self.start_x = kitty.get_position()
# the image requires height/cell_height lines
self.needed_lines = math.ceil(height / h_pix)
self.set_background([0.0, 1.0, 0.0])
# if we are too close to the bottom of the terminal, create some space.
if self.rows - self.start_y < self.needed_lines:
self.set_background([1.0, 0.0, 0.0])
missing = self.needed_lines - (self.rows - self.start_y)
self.start_y -= missing
print("\n" * self.needed_lines, end="")
kitty.set_position(self.start_y, self.start_x)
def render_to_kitty(self):
self.render()
buf = io.BytesIO()
self.screenshot(buf, transparent_background=True)
kitty.draw_to_terminal(buf)
# print("y:", self.start_y, "rows:", self.rows, end="")
kitty.set_position(self.start_y, self.start_x)
async def _input_loop(self):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setcbreak(fd)
while self._running:
rlist, _, _ = select.select([sys.stdin], [], [], 0)
if rlist:
c = sys.stdin.read(1)
if c == "q": # quit on q
self._running = False
else:
self._handle_key(c)
await asyncio.sleep(0.01)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
def _handle_key(self, c):
if c == "a":
self.camera.Azimuth(10)
elif c == "d":
self.camera.Azimuth(-10)
elif c == "w":
self.camera.Elevation(10)
elif c == "s":
self.camera.Elevation(-10)
self.render_to_kitty()
async def run(self):
self.render_to_kitty()
await self._input_loop()