Controls & Callbacks
This example demonstrates a mix of user-defined control elements. Each control creates a small widget in the viewer while the corresponding callback stays entirely in Python. The code example below loads a mesh, modifies it using various controls, and displays a Plotly histogram showing relevant data. For a full list of supported controls, see Controls.
import panopti
import trimesh
import numpy as np
import plotly.graph_objects as go
from panopti.utils import to_rgb
viewer = panopti.connect(server_url="http://localhost:8080", viewer_id='client')
viewer.capture_prints(capture_stderr=True)
mesh = trimesh.load('./examples/demosthenes.obj')
verts, faces = mesh.vertices, mesh.faces
# Add a mesh
vertex_colors = to_rgb(verts[:, 1], cmap='viridis')
viewer.add_mesh(
vertices=verts,
faces=faces,
name="Statue",
vertex_colors=vertex_colors,
)
# --- Let's add some controls ---
# On button press, randomly rescale our mesh
def callback_random_rotate(viewer):
random_rotation = np.random.uniform(0, 2 * np.pi, (3,))
yaxis_only = viewer.get('Y-axis Only').value() # read checkbox value
if yaxis_only: # zero out X,Z axes
random_rotation[[0, 2]] = 0
viewer.get('Statue').update(rotation=random_rotation)
plot_histogram(viewer) # update histogram
viewer.button(callback=callback_random_rotate, name='Randomly rotate mesh')
# Checkbox will control if we uniformly scale or not -- we can leave the callback blank
viewer.checkbox(callback=None, name='Y-axis Only', initial=True)
# Override mesh color using a color picker:
def callback_recolor_mesh(viewer, rgba):
mesh = viewer.get("Statue")
if mesh is not None:
mat = mesh.material
mat.color = rgba[:3]
mat.opacity = rgba[3]
mat.transparency = mat.opacity < 1.0
# Remove vertex colors and update material:
mesh.update(material=mat, vertex_colors=None)
viewer.color_picker(callback=callback_recolor_mesh, name='Recolor Mesh', initial=(0.5, 0.5, 0.5, 1.0))
# Slider that adds Gaussian noise to the mesh
def callback_add_noise(viewer, value):
noise = value * np.random.randn(*verts.shape)
new_verts = verts.copy() + noise
viewer.get('Statue').update(vertices=new_verts)
plot_histogram(viewer) # update histogram
viewer.slider(callback=callback_add_noise, name='Gaussian Noise STD', min=0.0, max=0.2, initial=0.0, step=0.01)
# Let's plot a histogram of the mesh vertex y-coordinates:
def plot_histogram(viewer):
# Get the y-coordinates of current mesh
data = viewer.get('Statue').viewer_verts[:,1]
counts, bins = np.histogram(data, bins=128)
bin_centers = 0.5 * (bins[:-1] + bins[1:])
widths = bins[1:] - bins[:-1]
fig = go.Figure(
go.Bar(
x=bin_centers,
y=counts,
width=widths,
marker=dict(
color=bin_centers, # color by bin center
colorscale='Viridis', # choose any Plotly colormap
showscale=False, # display the colorbar
colorbar=dict(title='Bin value'),
line=dict(width=0)
),
showlegend=False
)
)
fig.update_layout(
margin=dict(l=0, r=0, t=20, b=0),
title_text='Histogram of Statue Y-coordinates',
xaxis_title='Statue Y-coordinates',
yaxis_title='Count',
showlegend=False,
)
fig.update_layout(title_font_size=12)
fig.update_layout(title_x=0.13, title_xanchor='left')
fig.update_xaxes(title_font_size=12, tickfont_size=8)
fig.update_yaxes(title_font_size=12, tickfont_size=8)
viewer.add_plotly(fig.to_plotly_json(), name='Histogram')
plot_histogram(viewer)
viewer.hold()