307 lines
8.5 KiB
Python
307 lines
8.5 KiB
Python
"""PyVista Trame Base Viewer class.
|
|
|
|
This base class defines methods to manipulate a PyVista Plotter.
|
|
This base class does not define a `ui` method, but its derived classes do.
|
|
See `pyvista.trame.ui.vuetify2` and ``pyvista.trame.ui.vuetify3` for its derived classes.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
from typing import TYPE_CHECKING
|
|
|
|
from trame.app import get_server
|
|
|
|
import pyvista
|
|
|
|
if TYPE_CHECKING:
|
|
from trame_client.ui.core import AbstractLayout
|
|
|
|
|
|
class BaseViewer:
|
|
"""Internal wrapper to sync trame view with Plotter.
|
|
|
|
Parameters
|
|
----------
|
|
plotter : pyvista.Plotter
|
|
Target Plotter instance to view.
|
|
server : trame.Server, optional
|
|
Current Server for Trame Application.
|
|
suppress_rendering : bool, default=False
|
|
Whether to suppress rendering on the Plotter.
|
|
|
|
"""
|
|
|
|
def __init__(self, plotter, server=None, suppress_rendering=False):
|
|
"""Initialize Viewer."""
|
|
self._html_views = set()
|
|
|
|
if server is None:
|
|
server = get_server()
|
|
self.server = server
|
|
self.plotter = plotter
|
|
self.plotter.suppress_rendering = suppress_rendering
|
|
|
|
# State variable names
|
|
self.SHOW_UI = f'{plotter._id_name}_show_ui'
|
|
self.GRID = f'{plotter._id_name}_grid_visibility'
|
|
self.OUTLINE = f'{plotter._id_name}_outline_visibility'
|
|
self.EDGES = f'{plotter._id_name}_edge_visibility'
|
|
self.AXIS = f'{plotter._id_name}_axis_visibility'
|
|
self.PARALLEL = f'{plotter._id_name}_parallel_projection'
|
|
self.SERVER_RENDERING = f'{plotter._id_name}_use_server_rendering'
|
|
self.VALID_UI_MODES = [
|
|
'trame',
|
|
'client',
|
|
'server',
|
|
]
|
|
server.state[self.SHOW_UI] = True
|
|
server.state[self.GRID] = False
|
|
server.state[self.OUTLINE] = False
|
|
server.state[self.EDGES] = False
|
|
server.state[self.AXIS] = False
|
|
server.state[self.PARALLEL] = False
|
|
|
|
@property
|
|
def views(self): # numpydoc ignore=RT01
|
|
"""Get a set of all associate trame views for this viewer."""
|
|
return self._html_views
|
|
|
|
def update(self, **kwargs): # noqa: ARG002
|
|
"""Update all associated views.
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dict, optional
|
|
Unused keyword arguments.
|
|
|
|
"""
|
|
for view in self._html_views:
|
|
view.update()
|
|
|
|
def push_camera(self, **kwargs): # noqa: ARG002
|
|
"""Push camera to all associated views.
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dict, optional
|
|
Unused keyword arguments.
|
|
|
|
"""
|
|
for view in self._html_views:
|
|
view.push_camera()
|
|
|
|
def reset_camera(self, **kwargs): # noqa: ARG002
|
|
"""Reset camera for all associated views.
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dict, optional
|
|
Unused keyword arguments.
|
|
|
|
"""
|
|
for view in self._html_views:
|
|
view.reset_camera()
|
|
|
|
def update_image(self, **kwargs): # noqa: ARG002
|
|
"""Update image for all associated views.
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dict, optional
|
|
Unused keyword arguments.
|
|
|
|
"""
|
|
for view in self._html_views:
|
|
view.update_image()
|
|
|
|
def update_camera(self, **kwargs): # noqa: ARG002
|
|
"""Update image and camera for all associated views.
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dict, optional
|
|
Unused keyword arguments.
|
|
|
|
"""
|
|
for view in self._html_views:
|
|
view.update_camera()
|
|
|
|
def view_isometric(self):
|
|
"""View isometric."""
|
|
self.plotter.view_isometric(render=False)
|
|
self.update_camera()
|
|
|
|
def view_yz(self):
|
|
"""View YZ plane."""
|
|
self.plotter.view_yz(render=False)
|
|
self.update_camera()
|
|
|
|
def view_xz(self):
|
|
"""View XZ plane."""
|
|
self.plotter.view_xz(render=False)
|
|
self.update_camera()
|
|
|
|
def view_xy(self):
|
|
"""View XY plane."""
|
|
self.plotter.view_xy(render=False)
|
|
self.update_camera()
|
|
|
|
def on_parallel_projection_change(self, **kwargs):
|
|
"""Toggle parallel projection for all renderers.
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dict, optional
|
|
Unused keyword arguments.
|
|
|
|
"""
|
|
value = kwargs[self.PARALLEL]
|
|
for renderer in self.plotter.renderers:
|
|
if value:
|
|
renderer.enable_parallel_projection()
|
|
else:
|
|
renderer.disable_parallel_projection()
|
|
self.update()
|
|
|
|
def on_edge_visibility_change(self, **kwargs):
|
|
"""Toggle edge visibility for all actors.
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dict, optional
|
|
Unused keyword arguments.
|
|
|
|
"""
|
|
value = kwargs[self.EDGES]
|
|
for renderer in self.plotter.renderers:
|
|
for actor in renderer.actors.values():
|
|
if isinstance(actor, pyvista.Actor):
|
|
actor.prop.show_edges = value
|
|
self.update()
|
|
|
|
def on_grid_visibility_change(self, **kwargs):
|
|
"""Handle axes grid visibility.
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dict, optional
|
|
Unused keyword arguments.
|
|
|
|
"""
|
|
value = kwargs[self.GRID]
|
|
for renderer in self.plotter.renderers:
|
|
if value:
|
|
renderer.show_grid()
|
|
else:
|
|
renderer.remove_bounds_axes()
|
|
self.update()
|
|
|
|
def on_outline_visibility_change(self, **kwargs):
|
|
"""Handle outline visibility.
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dict, optional
|
|
Unused keyword arguments.
|
|
|
|
"""
|
|
value = kwargs[self.OUTLINE]
|
|
for renderer in self.plotter.renderers:
|
|
if value:
|
|
renderer.add_bounding_box(reset_camera=False)
|
|
else:
|
|
renderer.remove_bounding_box()
|
|
self.update()
|
|
|
|
def on_axis_visibility_change(self, **kwargs):
|
|
"""Handle outline visibility.
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dict, optional
|
|
Unused keyword arguments.
|
|
|
|
"""
|
|
value = kwargs[self.AXIS]
|
|
for renderer in self.plotter.renderers:
|
|
if value:
|
|
renderer.show_axes()
|
|
else:
|
|
renderer.hide_axes()
|
|
for view in self._html_views:
|
|
if view.set_widgets:
|
|
# VtkRemoteView does not have set_widgets function, but
|
|
# VtkRemoteLocalView and VtkLocalView do.
|
|
view.set_widgets(
|
|
[
|
|
ren.axes_widget
|
|
for ren in self.plotter.renderers
|
|
if ren.axes_widget is not None
|
|
],
|
|
)
|
|
self.update()
|
|
|
|
def on_rendering_mode_change(self, **kwargs):
|
|
"""Handle any configurations when the render mode changes between client and server.
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dict, optional
|
|
Unused keyword arguments.
|
|
|
|
"""
|
|
if not kwargs[self.SERVER_RENDERING]:
|
|
self.update_camera()
|
|
|
|
@property
|
|
def actors(self): # numpydoc ignore=RT01
|
|
"""Get dataset actors."""
|
|
return {k: v for k, v in self.plotter.actors.items() if isinstance(v, pyvista.Actor)}
|
|
|
|
def screenshot(self):
|
|
"""Take screenshot and add attachament.
|
|
|
|
Returns
|
|
-------
|
|
memoryview
|
|
Screenshot as a ``memoryview``.
|
|
|
|
"""
|
|
self.plotter.render()
|
|
self.update() # makes sure the plotter and views are in sync
|
|
buffer = io.BytesIO()
|
|
self.plotter.screenshot(filename=buffer)
|
|
buffer.seek(0)
|
|
return memoryview(buffer.read())
|
|
|
|
def export(self): # numpydoc ignore=RT01
|
|
"""Export the scene as a zip file."""
|
|
for view in self._html_views:
|
|
return memoryview(view.export_html())
|
|
msg = 'This viewer cannot be exported.'
|
|
raise TypeError(msg)
|
|
|
|
def ui(self):
|
|
"""Implement in derived classes."""
|
|
raise NotImplementedError
|
|
|
|
def make_layout(self, *args, **kwargs) -> AbstractLayout: # pragma: no cover
|
|
"""Create an instance of an AbstractLayout which is appropriate for a concrete viewer.
|
|
|
|
Parameters
|
|
----------
|
|
*args : tuple
|
|
Positional arguments.
|
|
|
|
**kwargs : dict, optional
|
|
Keyword arguments.
|
|
|
|
Returns
|
|
-------
|
|
AbstractLayout
|
|
A layout this viewer can be embedded in.
|
|
|
|
"""
|
|
raise NotImplementedError
|