Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor scene pointer events + support rectangular selection #157

Merged
merged 47 commits into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
168d844
Initial commit for 3D scribble, WIP
chungmin99 Jan 5, 2024
ce7a68b
Add Viewer2DCanvas, use that to draw cursor feedback!
chungmin99 Feb 1, 2024
c47cfdb
Remove scribble support, smoothen out box select, add example
chungmin99 Feb 1, 2024
35d52c8
Correctly update canvas size on resize
chungmin99 Feb 2, 2024
20173b3
Refactor ScenePointerMessage, send 2D coordinates for box
chungmin99 Feb 7, 2024
d7a837a
Update example, walk through OpenCV/NDC coordinates
chungmin99 Feb 7, 2024
64d4328
Rename SceneClickEnableMessage->ScenePointerEnableMessage
chungmin99 Feb 7, 2024
6d80819
Merge branch 'main' into cmk/scribble
chungmin99 Feb 7, 2024
6cf4337
ruff, mypy
chungmin99 Feb 8, 2024
c6bba6d
Put camera in a reasonable initial location
chungmin99 Feb 8, 2024
bec8cf6
Add event type to on_scene_pointer
chungmin99 Feb 10, 2024
3b54641
Rename example file from scene_click->scene_pointer
chungmin99 Feb 10, 2024
58f1057
Merge branch 'main' into cmk/scribble
chungmin99 Feb 23, 2024
02f063f
mypy, ruff
chungmin99 Feb 23, 2024
51d3aa4
Add backwards compatibility
chungmin99 Feb 23, 2024
df52698
ruff, mypy
chungmin99 Feb 23, 2024
e302393
ruff, mypy
chungmin99 Feb 23, 2024
5ad7394
Rename click->pointer
chungmin99 Feb 23, 2024
8158ffe
Use resizeobserver for element size tracking
chungmin99 Feb 25, 2024
c4a3ac7
Merge branch 'main' into cmk/scribble
chungmin99 Feb 26, 2024
7e4d942
Merge branch 'main' into cmk/scribble
chungmin99 Mar 18, 2024
376e9f5
Use OpenCV image coords (normalized)
chungmin99 Mar 18, 2024
6679aa1
mypy
chungmin99 Mar 18, 2024
2d557cd
Remove irrelevant file
chungmin99 Mar 18, 2024
abe2773
mypy
chungmin99 Mar 18, 2024
f9f74ee
ruff, pyright
chungmin99 Mar 18, 2024
a8c0dfa
mypy
chungmin99 Mar 18, 2024
df7c3d1
Merge branch 'main' into cmk/scribble
chungmin99 Mar 18, 2024
c848cfa
Nits
brentyi Mar 20, 2024
c34add9
Try to fix mypy
brentyi Mar 20, 2024
dbb122b
Merge branch 'main' into cmk/scribble
brentyi Mar 20, 2024
7fe3efa
Rename box to rect-select
chungmin99 Mar 21, 2024
52cf8a9
Listen to only one scenepointerevent at a given time.
chungmin99 Mar 21, 2024
7386872
mypy, ruff
chungmin99 Mar 21, 2024
8bef985
ruff
chungmin99 Mar 21, 2024
1a8e514
Merge branch 'main' into cmk/scribble
chungmin99 Mar 22, 2024
d149efd
Track events for clients!
chungmin99 Mar 22, 2024
2beac59
Clear pre-existing callbacks for both server/clients, include warnings.
chungmin99 Mar 22, 2024
729e340
ruff
chungmin99 Mar 22, 2024
78c90fc
Code nit
chungmin99 Mar 22, 2024
f2a4f94
Nits
brentyi Mar 23, 2024
298d78a
oops
brentyi Mar 23, 2024
1e39361
Move pointer cleanup logic
brentyi Mar 23, 2024
1ffd9ce
Merge branch 'main' into cmk/scribble
chungmin99 Mar 23, 2024
5f9bae9
minor UI tweaks
brentyi Mar 23, 2024
9d3a3ac
Merge branch 'cmk/scribble' of github.com:brentyi/viser into cmk/scri…
brentyi Mar 23, 2024
5d75023
docs sync
brentyi Mar 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/source/examples/02_gui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ Examples of basic GUI elements that we can create, read from, and write to.
initial_value=3,
marks=((0, "0"), (5, "5"), (7, "7"), 10),
)
gui_upload_button = server.add_gui_upload_button(
"Upload", icon=viser.Icon.UPLOAD
)

@gui_upload_button.on_upload
def _(_) -> None:
"""Callback for when a file is uploaded."""
file = gui_upload_button.value
print(file.name, len(file.content), "bytes")

# Pre-generate a point cloud to send.
point_positions = onp.random.uniform(low=-1.0, high=1.0, size=(5000, 3))
Expand Down
92 changes: 0 additions & 92 deletions docs/source/examples/20_scene_click.rst

This file was deleted.

168 changes: 168 additions & 0 deletions docs/source/examples/20_scene_pointer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
.. Comment: this file is automatically generated by `update_example_docs.py`.
It should not be modified manually.

Scene pointer events.
==========================================


This example shows how to use scene pointer events to specify rays, and how they can be
used to interact with the scene (e.g., ray-mesh intersections).

To get the demo data, see ``./assets/download_dragon_mesh.sh``.



.. code-block:: python
:linenos:


import time
from pathlib import Path
from typing import List, cast

import numpy as onp
import trimesh
import trimesh.creation
import trimesh.ray
import viser
import viser.transforms as tf

server = viser.ViserServer()
server.configure_theme(brand_color=(130, 0, 150))
server.set_up_direction("+y")

mesh = cast(
trimesh.Trimesh, trimesh.load_mesh(str(Path(__file__).parent / "assets/dragon.obj"))
)
mesh.apply_scale(0.05)

mesh_handle = server.add_mesh_trimesh(
name="/mesh",
mesh=mesh,
position=(0.0, 0.0, 0.0),
)

hit_pos_handles: List[viser.GlbHandle] = []


# Buttons + callbacks will operate on a per-client basis, but will modify the global scene! :)
@server.on_client_connect
def _(client: viser.ClientHandle) -> None:
# Set up the camera -- this gives a nice view of the full mesh.
client.camera.position = onp.array([0.0, 0.0, -10.0])
client.camera.wxyz = onp.array([0.0, 0.0, 0.0, 1.0])

# Tests "click" scenepointerevent.
click_button_handle = client.add_gui_button("Add sphere", icon=viser.Icon.POINTER)

@click_button_handle.on_click
def _(_):
click_button_handle.disabled = True

@client.on_scene_pointer(event_type="click")
def _(event: viser.ScenePointerEvent) -> None:
# Check for intersection with the mesh, using trimesh's ray-mesh intersection.
# Note that mesh is in the mesh frame, so we need to transform the ray.
R_world_mesh = tf.SO3(mesh_handle.wxyz)
R_mesh_world = R_world_mesh.inverse()
origin = (R_mesh_world @ onp.array(event.ray_origin)).reshape(1, 3)
direction = (R_mesh_world @ onp.array(event.ray_direction)).reshape(1, 3)
intersector = trimesh.ray.ray_triangle.RayMeshIntersector(mesh)
hit_pos, _, _ = intersector.intersects_location(origin, direction)

if len(hit_pos) == 0:
return

# Get the first hit position (based on distance from the ray origin).
hit_pos = min(hit_pos, key=lambda x: onp.linalg.norm(x - origin))

# Create a sphere at the hit location.
hit_pos_mesh = trimesh.creation.icosphere(radius=0.1)
hit_pos_mesh.vertices += R_world_mesh @ hit_pos
hit_pos_mesh.visual.vertex_colors = (0.5, 0.0, 0.7, 1.0) # type: ignore
hit_pos_handle = server.add_mesh_trimesh(
name=f"/hit_pos_{len(hit_pos_handles)}", mesh=hit_pos_mesh
)
hit_pos_handles.append(hit_pos_handle)

@client.on_scene_pointer_done
def _():
click_button_handle.disabled = False
client.remove_scene_pointer_callback()

# Tests "rect-select" scenepointerevent.
paint_button_handle = client.add_gui_button("Paint mesh", icon=viser.Icon.PAINT)

@paint_button_handle.on_click
def _(_):
paint_button_handle.disabled = True

@client.on_scene_pointer(event_type="rect-select")
def _(message: viser.ScenePointerEvent) -> None:
global mesh_handle
camera = message.client.camera

# Put the mesh in the camera frame.
R_world_mesh = tf.SO3(mesh_handle.wxyz)
R_mesh_world = R_world_mesh.inverse()
R_camera_world = tf.SE3.from_rotation_and_translation(
tf.SO3(camera.wxyz), camera.position
).inverse()
vertices = mesh.vertices
vertices = (R_mesh_world.as_matrix() @ vertices.T).T
vertices = (
R_camera_world.as_matrix()
@ onp.hstack([vertices, onp.ones((vertices.shape[0], 1))]).T
).T[:, :3]

# Get the camera intrinsics, and project the vertices onto the image plane.
fov, aspect = camera.fov, camera.aspect
vertices_proj = vertices[:, :2] / vertices[:, 2].reshape(-1, 1)
vertices_proj /= onp.tan(fov / 2)
vertices_proj[:, 0] /= aspect

# Move the origin to the upper-left corner, and scale to [0, 1].
# ... make sure to match the OpenCV's image coordinates!
vertices_proj = (1 + vertices_proj) / 2

# Select the vertices that lie inside the 2D selected box, once projected.
mask = (
(vertices_proj > onp.array(message.screen_pos[0]))
& (vertices_proj < onp.array(message.screen_pos[1]))
).all(axis=1)[..., None]

# Update the mesh color based on whether the vertices are inside the box
mesh.visual.vertex_colors = onp.where( # type: ignore
mask, (0.5, 0.0, 0.7, 1.0), (0.9, 0.9, 0.9, 1.0)
)
mesh_handle = server.add_mesh_trimesh(
name="/mesh",
mesh=mesh,
position=(0.0, 0.0, 0.0),
)

@client.on_scene_pointer_done
def _():
paint_button_handle.disabled = False
client.remove_scene_pointer_callback()

# Button to clear spheres.
clear_button_handle = client.add_gui_button("Clear scene", icon=viser.Icon.X)

@clear_button_handle.on_click
def _(_):
"""Reset the mesh color and remove all click-generated spheres."""
global mesh_handle
for handle in hit_pos_handles:
handle.remove()
hit_pos_handles.clear()
mesh.visual.vertex_colors = (0.9, 0.9, 0.9, 1.0) # type: ignore
mesh_handle = server.add_mesh_trimesh(
name="/mesh",
mesh=mesh,
position=(0.0, 0.0, 0.0),
)


while True:
time.sleep(10.0)
82 changes: 0 additions & 82 deletions examples/20_scene_click.py

This file was deleted.

Loading
Loading