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

Farewell, Leva #52

Merged
merged 10 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions examples/03_gui_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ def main() -> None:

with server.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"):
gui_location = server.add_gui_slider(
Expand Down
2 changes: 1 addition & 1 deletion examples/07_record3d_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def main(
"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
2 changes: 1 addition & 1 deletion examples/08_smplx_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def _(_):
gui_joints: List[viser.GuiHandle[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 Down
2 changes: 1 addition & 1 deletion examples/09_urdf_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def update_frames():
else onp.pi
)
slider = server.add_gui_slider(
name=joint_name,
label=joint_name,
min=min,
max=max,
step=1e-3,
Expand Down
5 changes: 4 additions & 1 deletion examples/11_colmap_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ def main(
cameras = read_cameras_binary(colmap_path / "cameras.bin")
images = read_images_binary(colmap_path / "images.bin")
points3d = read_points3d_binary(colmap_path / "points3D.bin")
gui_reset_up = server.add_gui_button("Reset up direction")
gui_reset_up = server.add_gui_button(
"Reset up direction",
hint="Set the camera control 'up' direction to the current camera's 'up'.",
)

@gui_reset_up.on_click
def _(_) -> None:
Expand Down
4 changes: 2 additions & 2 deletions examples/12_click_meshes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ def main() -> None:

with server.gui_folder("Last clicked"):
x_value = server.add_gui_number(
name="x",
label="x",
initial_value=0,
disabled=True,
hint="x coordinate of the last clicked mesh",
)
y_value = server.add_gui_number(
name="y",
label="y",
initial_value=0,
disabled=True,
hint="y coordinate of the last clicked mesh",
Expand Down
74 changes: 32 additions & 42 deletions viser/_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
TYPE_CHECKING,
Any,
Callable,
Dict,
Generic,
Iterable,
List,
Optional,
Tuple,
Type,
TypeVar,
Union,
Expand All @@ -19,8 +20,9 @@
import numpy as onp

from ._messages import (
GuiAddDropdownMessage,
GuiRemoveMessage,
GuiSetLevaConfMessage,
GuiSetDisabledMessage,
GuiSetValueMessage,
GuiSetVisibleMessage,
)
Expand All @@ -38,21 +40,18 @@
class _GuiHandleState(Generic[T]):
"""Internal API for GUI elements."""

name: str
label: str
typ: Type[T]
api: MessageApi
value: T
update_timestamp: float

folder_labels: List[str]
folder_labels: Tuple[str, ...]
"""Name of the folders this GUI input was placed into."""

update_cb: List[Callable[[Any], None]]
"""Registered functions to call when this input is updated."""

leva_conf: Dict[str, Any]
"""Input config for Leva."""

is_button: bool
"""Indicates a button element, which requires special handling."""

Expand All @@ -62,17 +61,14 @@ class _GuiHandleState(Generic[T]):
cleanup_cb: Optional[Callable[[], Any]]
"""Function to call when GUI element is removed."""

# Encoder: run on outgoing message values.
# Decoder: run on incoming message values.
#
# This helps us handle cases where types used by Leva don't match what we want to
# expose as a Python API.
encoder: Callable[[T], Any]
decoder: Callable[[Any], T]

disabled: bool
visible: bool

order: float
id: str
initial_value: T
hint: Optional[str]


@dataclasses.dataclass
class _GuiHandle(Generic[T]):
Expand Down Expand Up @@ -110,7 +106,7 @@ def value(self, value: Union[T, onp.ndarray]) -> None:
# Send to client, except for buttons.
if not self._impl.is_button:
self._impl.api._queue(
GuiSetValueMessage(self._impl.name, self._impl.encoder(value)) # type: ignore
GuiSetValueMessage(self._impl.id, value) # type: ignore
)

# Set internal state. We automatically convert numpy arrays to the expected
Expand Down Expand Up @@ -140,16 +136,7 @@ def disabled(self, disabled: bool) -> None:
if disabled == self.disabled:
return

if self._impl.is_button:
self._impl.leva_conf["settings"]["disabled"] = disabled
self._impl.api._queue(
GuiSetLevaConfMessage(self._impl.name, self._impl.leva_conf),
)
else:
self._impl.leva_conf["disabled"] = disabled
self._impl.api._queue(
GuiSetLevaConfMessage(self._impl.name, self._impl.leva_conf),
)
self._impl.api._queue(GuiSetDisabledMessage(self._impl.id, disabled=disabled))
self._impl.disabled = disabled

@property
Expand All @@ -163,12 +150,12 @@ def visible(self, visible: bool) -> None:
if visible == self.visible:
return

self._impl.api._queue(GuiSetVisibleMessage(self._impl.name, visible=visible))
self._impl.api._queue(GuiSetVisibleMessage(self._impl.id, visible=visible))
self._impl.visible = visible

def remove(self) -> None:
"""Permanently remove this GUI element from the visualizer."""
self._impl.api._queue(GuiRemoveMessage(self._impl.name))
self._impl.api._queue(GuiRemoveMessage(self._impl.id))
assert self._impl.cleanup_cb is not None
self._impl.cleanup_cb()

Expand Down Expand Up @@ -234,10 +221,10 @@ class GuiDropdownHandle(GuiHandle[StringType], Generic[StringType]):

Lets us get values, set values, and detect updates."""

_impl_options: List[StringType]
_impl_options: Tuple[StringType, ...]

@property
def options(self) -> List[StringType]:
def options(self) -> Tuple[StringType, ...]:
"""Options for our dropdown. Synchronized automatically when assigned.

For projects that care about typing: the static type of `options` should be
Expand All @@ -248,19 +235,22 @@ def options(self) -> List[StringType]:
return self._impl_options

@options.setter
def options(self, options: List[StringType]) -> None:
self._impl_options = options
def options(self, options: Iterable[StringType]) -> None:
self._impl_options = tuple(options)
if self._impl.initial_value not in self._impl_options:
self._impl.initial_value = self._impl_options[0]

# Make sure initial value is in options.
self._impl.leva_conf["options"] = options
if self._impl.leva_conf["value"] not in options:
self._impl.leva_conf["value"] = options[0]

# Update options.
self._impl.api._queue(
GuiSetLevaConfMessage(self._impl.name, self._impl.leva_conf),
GuiAddDropdownMessage(
order=self._impl.order,
id=self._impl.id,
label=self._impl.label,
folder_labels=self._impl.folder_labels,
hint=self._impl.hint,
initial_value=self._impl.initial_value,
options=self._impl_options,
)
)

# Make sure current value is in options.
if self.value not in options:
self.value = options[0]
if self.value not in self._impl_options:
self.value = self._impl_options[0]
Loading