Compare commits

..

No commits in common. "7f04e0e0775ebbda331d7ae9088287165ff37cf5" and "070c1290b938afb17aae76749b4eb966f77497e0" have entirely different histories.

2 changed files with 59 additions and 46 deletions

View File

@ -146,7 +146,7 @@ async def main():
plotter.add_mesh(line, color="red", line_width=2) plotter.add_mesh(line, color="red", line_width=2)
kitty.hide_cursor() #kitty.hide_cursor()
try: try:
#asyncio.run(plotter.run()) #asyncio.run(plotter.run())
await plotter.run() await plotter.run()

View File

@ -1,14 +1,10 @@
import asyncio import asyncio
import io
import math import math
import select import select
import sys import sys
import termios import termios
import tty import tty
import vtk
from vtk.util import numpy_support
import numpy as np
from io import BytesIO
from PIL import Image
import pyvista as pv import pyvista as pv
@ -21,14 +17,13 @@ class TerminalPlotter(pv.Plotter):
self.width = width self.width = width
self.height = height self.height = height
self._running = True self._running = True
self.w2i_filter = vtk.vtkWindowToImageFilter() self._needs_render = True
self.w2i_filter.SetInputBufferTypeToRGBA() self._render_complete = False
self.w2i_filter.ReadFrontBufferOff() self._rendering = False
self.w2i_filter.SetInput(self.ren_win) self._render_pending = True
self.set_background([0.0, 0.0, 0.0]) self.set_background([0.0, 0.0, 0.0])
# transparency self.add_on_render_callback(self._on_render)
self.ren_win.SetAlphaBitPlanes(1) # if we are too close to the bottom of the terminal, create some space.
self.ren_win.SetMultiSamples(0)
async def initialize(self): async def initialize(self):
h_pix, _ = await kitty.get_terminal_cell_size() h_pix, _ = await kitty.get_terminal_cell_size()
@ -43,19 +38,14 @@ class TerminalPlotter(pv.Plotter):
print("\n" * self.needed_lines, end="") print("\n" * self.needed_lines, end="")
kitty.set_position(self.start_y, self.start_x) kitty.set_position(self.start_y, self.start_x)
async def _handle_key(self, c):
if c == "a": async def render_to_kitty(self):
self.camera.Azimuth(10) self.render()
elif c == "d": buf = io.BytesIO()
self.camera.Azimuth(-10) self.screenshot(buf, transparent_background=True, window_size=(self.width, self.height))
elif c == "w": await kitty.draw_to_terminal(buf)
self.camera.Elevation(10) # print("y:", self.start_y, "rows:", self.rows, end="")
elif c == "s": kitty.set_position(self.start_y, self.start_x)
self.camera.Elevation(-10)
elif c == "+":
self.camera.zoom(1.1)
elif c == "-":
self.camera.zoom(0.9)
async def _input_loop(self): async def _input_loop(self):
fd = sys.stdin.fileno() fd = sys.stdin.fileno()
@ -74,37 +64,60 @@ class TerminalPlotter(pv.Plotter):
finally: finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
async 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._needs_render = True
# await self.render_to_kitty()
def _on_render(self, *args):
"""Callback that runs after each render."""
self._rendering = False
self._render_complete = True
async def _display_loop(self):
"""Loop that captures the screen and updates the terminal after rendering."""
while self._running:
if self._render_complete:
self._render_complete = False
buf = io.BytesIO()
# Safe: Screenshot only after render is confirmed complete
self.screenshot(buf, transparent_background=True, window_size=(self.width, self.height))
await kitty.draw_to_terminal(buf)
kitty.set_position(self.start_y, self.start_x)
await asyncio.sleep(0.001)
async def _render_loop(self): async def _render_loop(self):
while self._running: while self._running:
# keep renedring the scene if self._needs_render and not self._rendering:
self.ren_win.Render() self._rendering = True
# Update the filter to grab the current buffer self._needs_render = False
self.w2i_filter.Modified() self.update() # triggers a render and calls _on_render
self.w2i_filter.Update() await asyncio.sleep(0.001)
vtk_image = self.w2i_filter.GetOutput() async def _display_in_terminal(self, buf):
await kitty.draw_to_terminal(buf)
width, height, _ = vtk_image.GetDimensions()
vtk_array = vtk_image.GetPointData().GetScalars()
arr = numpy_support.vtk_to_numpy(vtk_array)
# Reshape the array to height x width x channels (probably 3 or 4)
arr = arr.reshape(height, width, -1)
# Flip vertically because VTK's origin is bottom-left
arr = np.flip(arr, axis=0)
img = Image.fromarray(arr)
buffer: BytesIO = BytesIO()
img.save(buffer, format="PNG")
await kitty.draw_to_terminal(buffer)
kitty.set_position(self.start_y, self.start_x) kitty.set_position(self.start_y, self.start_x)
async def run(self): async def run(self):
await self.initialize() await self.initialize()
self.iren.initialize() self.iren.initialize()
self._needs_render = True
tasks = [ tasks = [
asyncio.create_task(self._input_loop()), asyncio.create_task(self._input_loop()),
asyncio.create_task(self._render_loop()), asyncio.create_task(self._render_loop()),
asyncio.create_task(self._display_loop()),
] ]
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
self._running = False self._running = False
#await self.render_to_kitty()
#await self._input_loop()