Files
geMoldInsight/venv/lib/python3.11/site-packages/pyvista/trame/ui/base_viewer.py
T
2026-02-12 23:27:48 +08:00

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