Importing geometry

Panopti supports a variety of geometric data structures, see for example Geometry → Mesh. The example below demonstrates how these structures can be added to your scene.

import panopti
import trimesh
import numpy as np
from panopti.utils import to_rgb

viewer = panopti.connect(server_url='http://localhost:8080', viewer_id='client')

mesh = trimesh.load('./examples/demosthenes.obj')
verts, faces = mesh.vertices, mesh.faces
verts = np.ascontiguousarray(verts, dtype=np.float32)
faces = np.ascontiguousarray(faces, dtype=np.int32)
normals = np.ascontiguousarray(mesh.vertex_normals, dtype=np.float32)
verts = verts - verts.mean(axis=0, keepdims=True)  # Center the mesh

### Add mesh with vertex colors
vertex_colors = to_rgb(verts[:, 1], cmap='viridis')
viewer.add_mesh(
    vertices=verts,
    faces=faces,
    name="StatueMesh",
    vertex_colors=vertex_colors,
)

### Add Point cloud -- reuse vertex_colors
points = verts.copy()
points[:,0] += 2
viewer.add_points(
    points=points,
    name="StatuePoints",
    colors=vertex_colors,
    size=0.015,
)

### Add arrows pointing to another point cloud
points2 = points + (0.8, 0.8, -1.5)
random_subset = np.random.choice(points.shape[0], size=points.shape[0] // 128, replace=False)
viewer.add_points(
    points=points2[random_subset],
    name="PointsSubset",
    colors=vertex_colors[random_subset],
    size=0.05,
)

viewer.add_arrows(
    starts=points2[random_subset],
    ends=points[random_subset],
    name="Arrows",
    color=(0.01, 0.01, 0.01),
    width=0.01,
)

def whacky_transform(vertices, normals, t):
    # 1) swirl around Y: angle = t*2π * radius
    radii  = np.linalg.norm(vertices, axis=1)
    angles = radii * (t * 2*np.pi)
    cosA   = np.cos(angles)[:,None]
    sinA   = np.sin(angles)[:,None]
    x, y, z = vertices[:,0:1], vertices[:,1:2], vertices[:,2:3]

    x_sw = x * cosA - z * sinA
    z_sw = x * sinA + z * cosA
    verts_swirl = np.concatenate([x_sw, y, z_sw], axis=1)

    # 2) pulsate along normals: sin(frequency*radius + phase) * amplitude
    freq      = 6.0                   # number of ripples
    amp_base  = 0.2                   # max offset
    phase     = t * np.pi             # phase shift
    offsets   = np.sin(radii * freq + phase) * amp_base * t
    verts_out = verts_swirl + normals * offsets[:,None]

    return verts_out

# bake whacky animation:
verts_animation = []
for t in np.linspace(0, 1, 50):
    temp_verts = whacky_transform(verts.copy(), normals, t)
    verts_animation.append(temp_verts)
verts_animation = np.stack(verts_animation)
# boomerang the animation:
verts_animation = np.concatenate([verts_animation, verts_animation[::-1]], axis=0)
viewer.add_animated_mesh(
    vertices=verts_animation,
    faces=faces,
    name="WhackyAnimation",
    framerate=24,
    vertex_colors=vertex_colors,
    position=(-2, 0, 0) # move to the left
)

viewer.hold()