diff --git a/kglobe.py b/kglobe.py index 13e803c..09639ad 100644 --- a/kglobe.py +++ b/kglobe.py @@ -146,7 +146,7 @@ async def main(): plotter.add_mesh(line, color="red", line_width=2) - #kitty.hide_cursor() + kitty.hide_cursor() try: #asyncio.run(plotter.run()) await plotter.run() diff --git a/terminalplotter.py b/terminalplotter.py index 262a589..47f052d 100644 --- a/terminalplotter.py +++ b/terminalplotter.py @@ -5,6 +5,11 @@ import select import sys import termios 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 @@ -17,13 +22,14 @@ class TerminalPlotter(pv.Plotter): self.width = width self.height = height self._running = True - self._needs_render = True - self._render_complete = False - self._rendering = False - self._render_pending = True + self.w2i_filter = vtk.vtkWindowToImageFilter() + self.w2i_filter.SetInputBufferTypeToRGBA() + self.w2i_filter.ReadFrontBufferOff() + self.w2i_filter.SetInput(self.ren_win) self.set_background([0.0, 0.0, 0.0]) - self.add_on_render_callback(self._on_render) - # if we are too close to the bottom of the terminal, create some space. + # transparency + self.ren_win.SetAlphaBitPlanes(1) + self.ren_win.SetMultiSamples(0) async def initialize(self): h_pix, _ = await kitty.get_terminal_cell_size() @@ -38,14 +44,15 @@ class TerminalPlotter(pv.Plotter): print("\n" * self.needed_lines, end="") kitty.set_position(self.start_y, self.start_x) - - async def render_to_kitty(self): - self.render() - buf = io.BytesIO() - self.screenshot(buf, transparent_background=True, window_size=(self.width, self.height)) - await 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 _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) async def _input_loop(self): fd = sys.stdin.fileno() @@ -64,60 +71,37 @@ class TerminalPlotter(pv.Plotter): finally: 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): while self._running: - if self._needs_render and not self._rendering: - self._rendering = True - self._needs_render = False - self.update() # triggers a render and calls _on_render - await asyncio.sleep(0.001) + # keep renedring the scene + self.ren_win.Render() + # Update the filter to grab the current buffer + self.w2i_filter.Modified() + self.w2i_filter.Update() - async def _display_in_terminal(self, buf): - await kitty.draw_to_terminal(buf) - kitty.set_position(self.start_y, self.start_x) + vtk_image = self.w2i_filter.GetOutput() + 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) async def run(self): await self.initialize() self.iren.initialize() - self._needs_render = True tasks = [ asyncio.create_task(self._input_loop()), asyncio.create_task(self._render_loop()), - asyncio.create_task(self._display_loop()), ] await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) self._running = False - - - #await self.render_to_kitty() - #await self._input_loop()