1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
"""
.. _customize_trame_toolbar_example:
Customize Trame toolbar
~~~~~~~~~~~~~~~~~~~~~~~~
Bring more of the power of trame to the jupyter view.
"""
from __future__ import annotations
import asyncio
import pyvista as pv
from pyvista.trame.ui.vuetify3 import button
from pyvista.trame.ui.vuetify3 import divider
from pyvista.trame.ui.vuetify3 import select
from pyvista.trame.ui.vuetify3 import slider
from pyvista.trame.ui.vuetify3 import text_field
# %%
# Let's first create the menu items we want to add to the trame's toolbar.
# Here we want a "play" button that will be later connected to a slider
# through the ``button_play`` function. The slider itself will represent the
# "resolution" of the model we will render, a text field where the value of
# the "resolution" will be displayed.
# We will also add a dropdown menu to toggle the visibility of the model.
# The dividers are the same as already used to divide and organize the toolbar.
def custom_tools():
divider(vertical=True, classes='mx-1')
button(
click=button_play,
icon='mdi-play',
tooltip='Play',
)
slider(
model=("resolution", 10),
tooltip="Resolution slider",
min=3,
max=20,
step=1,
dense=True,
hide_details=True,
style="width: 300px",
classes='my-0 py-0 ml-1 mr-1',
)
text_field(
model=("resolution", 10),
tooltip="Resolution value",
readonly=True,
type="number",
dense=True,
hide_details=True,
style="min-width: 40px; width: 60px",
classes='my-0 py-0 ml-1 mr-1',
)
divider(vertical=True, classes='mx-1')
select(
model=("visibility", "Show"),
tooltip="Toggle visibility",
items=['Visibility', ["Hide", "Show"]],
hide_details=True,
dense=True,
)
# %%
# The button callback function ``button_play`` needs to be created before starting
# the server. This function will toggle the boolean state variable ``play``
# and flush the server, i.e. "force" the server to see the change.
# We will see more on the state variables in a bit, but we need to create the
# function here otherwise the server will complain ``button_play`` does not exist.
def button_play():
state.play = not state.play
state.flush()
# %%
# We will do a simple rendering of a Cone using `ConeSouce`.
#
# When using the ``pl.show`` method. The function we created ``custom_tools``
# should be passed as a ``jupyter_kwargs`` argument under the key
# ``add_menu_items``.
pl = pv.Plotter(notebook=True)
algo = pv.ConeSource()
mesh_actor = pl.add_mesh(algo)
widget = pl.show(jupyter_kwargs=dict(add_menu_items=custom_tools), return_viewer=True)
# %%
# To interact with ``trame``'s server we need to get the server's state.
#
# We initialize the ``play`` variable in the shared state and this will be
# controlled by the play button we created. Note that when creating the
# ``slider``, the ``text_field`` and the ``select`` tools, we passed something
# like ``model=("variable", value). This will automatically create the variable
# "variable" with value ``value`` in the server's shared state, so we do not need
# to create ``state.resolution`` or ``state.visibility``.
state, ctrl = widget.viewer.server.state, widget.viewer.server.controller
state.play = False
ctrl.view_update = widget.viewer.update
# %%
# Now we can create the callback functions for our menu items.
#
# The functions are decorated with a ``state.change("variable")``. This means
# they will be called when this specific variable has its value changed in the
# server's shared state. When ``resolution`` changes, we want to update the
# resolution of our cone algorithm. When ``visibility`` changes, we want to toggle the
# visibility of our cone.
#
# The ``play`` variable is a little bit trickier. We want to start something like
# a timer so that an animation can be set to play. To do that with ``trame`` we need
# to have an asynchronous function so we can continue to do stuff while the
# "timer" function is running. The ``_play`` function will be called when the ``play``
# variable is changed (when we click the play button, through the ``button_play``
# callback). While ``state.play`` is ``True`` we want to play the animation. We
# change the ``state.resolution`` value, but to really call the ``update_resolution``
# function we need to ``flush`` the server and force it to see the change in
# the shared variables. When ``state.play`` changes to ``False``, the animation stops.
#
# Note that using ``while play: ...`` would not work here because it is not the
# actual state variable, but only an argument value passed to the callback function.
# trame callbacks
@state.change("play")
async def _play(play, **kwargs):
while state.play:
state.resolution += 1
state.flush()
if state.resolution >= 20:
state.play = False
await asyncio.sleep(0.3)
@state.change("resolution")
def update_resolution(resolution, **kwargs):
algo.resolution = resolution
ctrl.view_update()
@state.change("visibility")
def set_visibility(visibility, **kwargs):
toggle = {"Hide": 0, "Show": 1}
mesh_actor.visibility = toggle[visibility]
ctrl.view_update()
widget
|