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

Abhik/splines #77

Merged
merged 10 commits into from
Sep 2, 2023
7 changes: 3 additions & 4 deletions docs/source/examples/02_gui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Examples of basic GUI elements that we can create, read from, and write to.
server = viser.ViserServer()

# Add some common GUI elements: number inputs, sliders, vectors, checkboxes.
with server.gui_folder("Read-only"):
with server.add_gui_folder("Read-only"):
gui_counter = server.add_gui_number(
"Counter",
initial_value=0,
Expand All @@ -40,7 +40,7 @@ Examples of basic GUI elements that we can create, read from, and write to.
disabled=True,
)

with server.gui_folder("Editable"):
with server.add_gui_folder("Editable"):
gui_vector2 = server.add_gui_vector2(
"Position",
initial_value=(0.0, 0.0),
Expand All @@ -50,9 +50,8 @@ Examples of basic GUI elements that we can create, read from, and write to.
"Size",
initial_value=(1.0, 1.0, 1.0),
step=0.25,
lock=True,
)
with server.gui_folder("Text toggle"):
with server.add_gui_folder("Text toggle"):
gui_checkbox_hide = server.add_gui_checkbox(
"Hide",
initial_value=False,
Expand Down
14 changes: 7 additions & 7 deletions docs/source/examples/03_gui_callbacks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,25 @@ we get updates.
def main() -> None:
server = viser.ViserServer()

with server.gui_folder("Control"):
gui_reset_scene = server.add_gui_button("Reset Scene")

with server.add_gui_folder("Control"):
gui_show = server.add_gui_checkbox("Show Frame", initial_value=True)
gui_axis = server.add_gui_dropdown("Axis", ["x", "y", "z"])
gui_axis = server.add_gui_dropdown("Axis", ("x", "y", "z"))
gui_include_z = server.add_gui_checkbox("Z in dropdown", initial_value=True)

@gui_include_z.on_update
def _(_) -> None:
gui_axis.options = ["x", "y", "z"] if gui_include_z.value else ["x", "y"]
gui_axis.options = ("x", "y", "z") if gui_include_z.value else ("x", "y")

with server.gui_folder("Sliders"):
with server.add_gui_folder("Sliders"):
gui_location = server.add_gui_slider(
"Location", min=-5.0, max=5.0, step=0.05, initial_value=0.0
)
gui_num_points = server.add_gui_slider(
"# Points", min=1000, max=200_000, step=1000, initial_value=10_000
)

gui_reset_scene = server.add_gui_button("Reset Scene")

def draw_frame() -> None:
axis = gui_axis.value
if axis == "x":
Expand Down Expand Up @@ -79,7 +79,7 @@ we get updates.
gui_num_points.on_update(lambda _: draw_points())

@gui_reset_scene.on_click
def _(_: viser.GuiButtonHandle) -> None:
def _(_) -> None:
"""Reset the scene when the reset button is clicked."""
gui_show.value = True
gui_location.value = 0.0
Expand Down
6 changes: 3 additions & 3 deletions docs/source/examples/05_camera_commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,17 @@ corresponding client automatically.

T_current_target = T_world_current.inverse() @ T_world_target

for j in range(50):
for j in range(20):
T_world_set = T_world_current @ tf.SE3.exp(
T_current_target.log() * j / 49.0
T_current_target.log() * j / 19.0
)

# Important bit: we atomically set both the orientation and the position
# of the camera.
with client.atomic():
client.camera.wxyz = T_world_set.rotation().wxyz
client.camera.position = T_world_set.translation()
time.sleep(0.01)
time.sleep(1.0 / 60.0)

# Mouse interactions should orbit around the frame origin.
client.camera.look_at = frame.position
Expand Down
21 changes: 13 additions & 8 deletions docs/source/examples/07_record3d_visualizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ Parse and stream record3d captures. To get the demo data, see ``./assets/downloa

def main(
data_path: Path = Path(__file__).parent / "assets/record3d_dance",
downsample_factor: int = 2,
max_frames: int = 50,
downsample_factor: int = 4,
max_frames: int = 100,
) -> None:
server = viser.ViserServer()

Expand All @@ -38,18 +38,23 @@ Parse and stream record3d captures. To get the demo data, see ``./assets/downloa
num_frames = min(max_frames, loader.num_frames())

# Add playback UI.
with server.gui_folder("Playback"):
with server.add_gui_folder("Playback"):
gui_timestep = server.add_gui_slider(
"Timestep", min=0, max=num_frames - 1, step=1, initial_value=0
"Timestep",
min=0,
max=num_frames - 1,
step=1,
initial_value=0,
disabled=True,
)
gui_next_frame = server.add_gui_button("Next Frame")
gui_prev_frame = server.add_gui_button("Prev Frame")
gui_playing = server.add_gui_checkbox("Playing", False)
gui_next_frame = server.add_gui_button("Next Frame", disabled=True)
gui_prev_frame = server.add_gui_button("Prev Frame", disabled=True)
gui_playing = server.add_gui_checkbox("Playing", True)
gui_framerate = server.add_gui_slider(
"FPS", min=1, max=60, step=0.1, initial_value=loader.fps
)
gui_framerate_options = server.add_gui_button_group(
"FPS options", ["10", "20", "30", "60"]
"FPS options", ("10", "20", "30", "60")
)

# Frame step buttons.
Expand Down
40 changes: 21 additions & 19 deletions docs/source/examples/08_smplx_visualizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ parameters to run this script:
ext: Literal["npz", "pkl"] = "npz",
) -> None:
server = viser.ViserServer()
server.configure_theme(control_layout="collapsible", dark_mode=True)
model = smplx.create(
model_path=str(model_path),
model_type=model_type,
Expand All @@ -66,7 +67,7 @@ parameters to run this script:
# Main loop. We'll just keep read from the joints, deform the mesh, then sending the
# updated mesh in a loop. This could be made a lot more efficient.
gui_elements = make_gui_elements(
server, num_betas=model.num_betas, num_body_joints=model.NUM_BODY_JOINTS
server, num_betas=model.num_betas, num_body_joints=int(model.NUM_BODY_JOINTS)
)
while True:
# Do nothing if no change.
Expand All @@ -75,6 +76,10 @@ parameters to run this script:
continue
gui_elements.changed = False

full_pose = torch.from_numpy(
onp.array([j.value for j in gui_elements.gui_joints[1:]], dtype=onp.float32)[None, ...] # type: ignore
)

# Get deformed mesh.
output = model.forward(
betas=torch.from_numpy( # type: ignore
Expand All @@ -84,9 +89,7 @@ parameters to run this script:
),
expression=None,
return_verts=True,
body_pose=torch.from_numpy(
onp.array([j.value for j in gui_elements.gui_joints[1:]], dtype=onp.float32)[None, ...] # type: ignore
),
body_pose=full_pose[:, : model.NUM_BODY_JOINTS], # type: ignore
global_orient=torch.from_numpy(onp.array(gui_elements.gui_joints[0].value, dtype=onp.float32)[None, ...]), # type: ignore
return_full_pose=True,
)
Expand Down Expand Up @@ -119,10 +122,10 @@ parameters to run this script:
class GuiElements:
"""Structure containing handles for reading from GUI elements."""

gui_rgb: viser.GuiHandle[Tuple[int, int, int]]
gui_wireframe: viser.GuiHandle[bool]
gui_betas: List[viser.GuiHandle[float]]
gui_joints: List[viser.GuiHandle[Tuple[float, float, float]]]
gui_rgb: viser.GuiInputHandle[Tuple[int, int, int]]
gui_wireframe: viser.GuiInputHandle[bool]
gui_betas: List[viser.GuiInputHandle[float]]
gui_joints: List[viser.GuiInputHandle[Tuple[float, float, float]]]

changed: bool
"""This flag will be flipped to True whenever the mesh needs to be re-generated."""
Expand All @@ -133,28 +136,28 @@ parameters to run this script:
) -> GuiElements:
"""Make GUI elements for interacting with the model."""

tab_group = server.add_gui_tab_group()

# GUI elements: mesh settings + visibility.
with server.gui_folder("View"):
with tab_group.add_tab("View", viser.Icon.VIEWFINDER):
gui_rgb = server.add_gui_rgb("Color", initial_value=(90, 200, 255))
gui_wireframe = server.add_gui_checkbox("Wireframe", initial_value=False)
gui_show_controls = server.add_gui_checkbox("Handles", initial_value=False)

@gui_rgb.on_update
def _(_):
out.changed = True

gui_wireframe = server.add_gui_checkbox("Wireframe", initial_value=False)

@gui_wireframe.on_update
def _(_):
out.changed = True

gui_show_controls = server.add_gui_checkbox("Handles", initial_value=False)

@gui_show_controls.on_update
def _(_):
add_transform_controls(enabled=gui_show_controls.value)

# GUI elements: shape parameters.
with server.gui_folder("Shape"):
with tab_group.add_tab("Shape", viser.Icon.BOX):
gui_reset_shape = server.add_gui_button("Reset Shape")
gui_random_shape = server.add_gui_button("Random Shape")

Expand All @@ -180,8 +183,7 @@ parameters to run this script:
out.changed = True

# GUI elements: joint angles.
with server.gui_folder("Joints"):
# Reset button.
with tab_group.add_tab("Joints", viser.Icon.ANGLE):
gui_reset_joints = server.add_gui_button("Reset Joints")
gui_random_joints = server.add_gui_button("Random Joints")

Expand All @@ -203,10 +205,10 @@ parameters to run this script:
joint.value = tf.SO3(wxyz=quat).log()
sync_transform_controls()

gui_joints: List[viser.GuiHandle[Tuple[float, float, float]]] = []
gui_joints: List[viser.GuiInputHandle[Tuple[float, float, float]]] = []
for i in range(num_body_joints + 1):
gui_joint = server.add_gui_vector3(
name=smplx.joint_names.JOINT_NAMES[i],
label=smplx.joint_names.JOINT_NAMES[i],
initial_value=(0.0, 0.0, 0.0),
step=0.05,
)
Expand All @@ -221,7 +223,7 @@ parameters to run this script:
transform_controls: List[viser.TransformControlsHandle] = []

def add_transform_controls(enabled: bool) -> List[viser.TransformControlsHandle]:
for i in range(num_body_joints + 1):
for i in range(1 + num_body_joints):
controls = server.add_transform_controls(
f"/reoriented/smpl/joint_{i}/controls",
depth_test=False,
Expand Down
111 changes: 36 additions & 75 deletions docs/source/examples/09_urdf_visualizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,97 +19,58 @@ Examples:
:linenos:


from __future__ import annotations

import time
from functools import partial
from pathlib import Path
from typing import List

import numpy as onp
import trimesh
import tyro
import yourdfpy

import viser
import viser.transforms as tf
from viser.extras import ViserUrdf


def main(urdf_path: Path) -> None:
urdf = yourdfpy.URDF.load(
urdf_path,
filename_handler=partial(yourdfpy.filename_handler_magic, dir=urdf_path.parent),
)
server = viser.ViserServer()

def frame_name_with_parents(frame_name: str) -> str:
frames = []
while frame_name != urdf.scene.graph.base_frame:
frames.append(frame_name)
frame_name = urdf.scene.graph.transforms.parents[frame_name]
return "/" + "/".join(frames[::-1])

for frame_name, mesh in urdf.scene.geometry.items():
assert isinstance(mesh, trimesh.Trimesh)
T_parent_child = urdf.get_transform(
frame_name, urdf.scene.graph.transforms.parents[frame_name]
# Create a helper for adding URDFs to Viser. This just adds meshes to the scene,
# helps us set the joint angles, etc.
urdf = ViserUrdf(server, urdf_path)

# Create joint angle sliders.
gui_joints: List[viser.GuiInputHandle[float]] = []
initial_angles: List[float] = []
for joint_name, (lower, upper) in urdf.get_actuated_joint_limits().items():
lower = lower if lower is not None else -onp.pi
upper = upper if upper is not None else onp.pi

initial_angle = 0.0 if lower < 0 and upper > 0 else (lower + upper) / 2.0
slider = server.add_gui_slider(
label=joint_name,
min=lower,
max=upper,
step=1e-3,
initial_value=initial_angle,
)
server.add_mesh_trimesh(
frame_name_with_parents(frame_name),
mesh,
wxyz=tf.SO3.from_matrix(T_parent_child[:3, :3]).wxyz,
position=T_parent_child[:3, 3],
slider.on_update( # When sliders move, we update the URDF configuration.
lambda _: urdf.update_cfg(onp.array([gui.value for gui in gui_joints]))
)

gui_joints: List[viser.GuiHandle[float]] = []
with server.gui_folder("Joints"):
button = server.add_gui_button("Reset")

@button.on_click
def _(_):
for g in gui_joints:
g.value = 0.0

def update_frames():
urdf.update_cfg(onp.array([gui.value for gui in gui_joints]))
for joint in urdf.joint_map.values():
assert isinstance(joint, yourdfpy.Joint)
T_parent_child = urdf.get_transform(joint.child, joint.parent)
server.add_frame(
frame_name_with_parents(joint.child),
wxyz=tf.SO3.from_matrix(T_parent_child[:3, :3]).wxyz,
position=T_parent_child[:3, 3],
show_axes=False,
)

for joint_name, joint in urdf.joint_map.items():
assert isinstance(joint, yourdfpy.Joint)

min = (
joint.limit.lower
if joint.limit is not None and joint.limit.lower is not None
else -onp.pi
)
max = (
joint.limit.upper
if joint.limit is not None and joint.limit.upper is not None
else onp.pi
)
slider = server.add_gui_slider(
name=joint_name,
min=min,
max=max,
step=1e-3,
initial_value=0.0 if min < 0 and max > 0 else (min + max) / 2.0,
)
if joint.limit is None:
slider.visible = False

@slider.on_update
def _(_):
update_frames()

gui_joints.append(slider)

update_frames()
gui_joints.append(slider)
initial_angles.append(initial_angle)

# Create joint reset button.
reset_button = server.add_gui_button("Reset")

@reset_button.on_click
def _(_):
for g, initial_angle in zip(gui_joints, initial_angles):
g.value = initial_angle

# Apply initial joint angles.
urdf.update_cfg(onp.array([gui.value for gui in gui_joints]))

while True:
time.sleep(10.0)
Expand Down
Loading
Loading