Skip to content

Commit

Permalink
Prepare design change
Browse files Browse the repository at this point in the history
  • Loading branch information
jkulhanek committed Dec 30, 2023
1 parent 0cf3974 commit 07c90eb
Show file tree
Hide file tree
Showing 15 changed files with 3,433 additions and 120 deletions.
28 changes: 28 additions & 0 deletions examples/22_custom_gui_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import trimesh
import viser
import viser.transforms as tf
from viser import Icon

mesh = trimesh.load_mesh(Path(__file__).parent / "assets/dragon.obj")
assert isinstance(mesh, trimesh.Trimesh)
Expand All @@ -18,6 +19,33 @@
print(f"Loaded mesh with {vertices.shape} vertices, {faces.shape} faces")

server = viser.ViserServer()

import_button = server.add_gui_button("Import", icon=Icon.FOLDER_OPEN)
export_button = server.add_gui_button("Export", icon=Icon.DOWNLOAD)

fps = server.add_gui_number("FPS", 24, min=1, icon=Icon.KEYFRAMES, hint="Frames per second")
duration = server.add_gui_number("Duration", 4.0, min=0.1, icon=Icon.CLOCK_HOUR_5, hint="Duration in seconds")
width = server.add_gui_number("Width", 1920, min=100, icon=Icon.ARROWS_HORIZONTAL, hint="Width in px")
height = server.add_gui_number("Height", 1080, min=100, icon=Icon.ARROWS_VERTICAL, hint="Height in px")
fov = server.add_gui_number("FOV", 75, min=1, max=179, icon=Icon.CAMERA, hint="Field of view")
smoothness = server.add_gui_slider("Smoothness", 0.5, min=0.0, max=1.0, step=0.01, hint="Trajectory smoothing")


duration = 4
cameras_slider = server.add_gui_multi_slider(
"Timeline",
min=0.,
max=1.,
step=0.01,
initial_value=[0.0, 0.5, 1.0],
disabled=False,
marks=[(x, f'{x*duration:.1f}s') for x in [0., 0.5, 1.0]],
)

@duration.on_update
def _(_) -> None:
cameras_slider.marks=[(x, f'{x*duration.value:.1f}s') for x in [0., 0.5, 1.0]],

server.add_mesh_simple(
name="/simple",
vertices=vertices,
Expand Down
81 changes: 78 additions & 3 deletions src/viser/_gui_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
_GuiInputHandle,
_make_unique_id,
)
from ._icons import base64_from_icon
from ._icons_enum import Icon
from ._message_api import MessageApi, cast_vector

Expand Down Expand Up @@ -273,7 +272,7 @@ def add_gui_tab_group(
return GuiTabGroupHandle(
_tab_group_id=tab_group_id,
_labels=[],
_icons_base64=[],
_icons=[],
_tabs=[],
_gui_api=self,
_container_id=self._get_container_id(),
Expand Down Expand Up @@ -367,7 +366,7 @@ def add_gui_button(
hint=hint,
initial_value=False,
color=color,
icon_base64=None if icon is None else base64_from_icon(icon),
icon=None if icon is None else icon.value,
),
disabled=disabled,
visible=visible,
Expand Down Expand Up @@ -535,6 +534,7 @@ def add_gui_number(
visible: bool = True,
hint: Optional[str] = None,
order: Optional[float] = None,
icon: Optional[Icon] = None,
) -> GuiInputHandle[IntOrFloat]:
"""Add a number input to the GUI, with user-specifiable bound and precision parameters.
Expand Down Expand Up @@ -585,6 +585,7 @@ def add_gui_number(
min=min,
max=max,
precision=_compute_precision_digits(step),
icon=icon.value if icon is not None else None,
step=step,
),
disabled=disabled,
Expand Down Expand Up @@ -888,6 +889,80 @@ def add_gui_slider(
is_button=False,
)

def add_gui_multi_slider(
self,
label: str,
min: IntOrFloat,
max: IntOrFloat,
step: IntOrFloat,
initial_value: List[IntOrFloat],
disabled: bool = False,
visible: bool = True,
min_range: Optional[IntOrFloat] = None,
hint: Optional[str] = None,
order: Optional[float] = None,
marks: Optional[List[Tuple[IntOrFloat, Optional[str]]]] = None,
) -> GuiInputHandle[IntOrFloat]:
"""Add a multi slider to the GUI. Types of the min, max, step, and initial value should match.
Args:
label: Label to display on the slider.
min: Minimum value of the slider.
max: Maximum value of the slider.
step: Step size of the slider.
initial_value: Initial values of the slider.
disabled: Whether the slider is disabled.
visible: Whether the slider is visible.
min_range: Optional minimum difference between two values of the slider.
hint: Optional hint to display on hover.
order: Optional ordering, smallest values will be displayed first.
Returns:
A handle that can be used to interact with the GUI element.
"""
assert max >= min
if step > max - min:
step = max - min
assert all(max >= x >= min for x in initial_value)

# GUI callbacks cast incoming values to match the type of the initial value. If
# the min, max, or step is a float, we should cast to a float.
if len(initial_value) > 0 and (type(initial_value[0]) is int and (
type(min) is float or type(max) is float or type(step) is float
)):
initial_value = [float(x) for x in initial_value] # type: ignore

# TODO: as of 6/5/2023, this assert will break something in nerfstudio. (at
# least LERF)
#
# assert type(min) == type(max) == type(step) == type(initial_value)

id = _make_unique_id()
order = _apply_default_order(order)
return self._create_gui_input(
initial_value=initial_value,
message=_messages.GuiAddMultiSliderMessage(
order=order,
id=id,
label=label,
container_id=self._get_container_id(),
hint=hint,
min=min,
min_range=min_range,
max=max,
step=step,
initial_value=initial_value,
precision=_compute_precision_digits(step),
marks=[
_messages.GuiSliderMark(value=x, label=label)
for x, label in marks
] if marks is not None else None,
),
disabled=disabled,
visible=visible,
is_button=False,
)

def add_gui_rgb(
self,
label: str,
Expand Down
9 changes: 4 additions & 5 deletions src/viser/_gui_handles.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import numpy as onp
from typing_extensions import Protocol

from ._icons import base64_from_icon
from ._icons_enum import Icon
from ._message_api import _encode_image_base64
from ._messages import (
Expand Down Expand Up @@ -332,7 +331,7 @@ def options(self, options: Iterable[StringType]) -> None:
class GuiTabGroupHandle:
_tab_group_id: str
_labels: List[str]
_icons_base64: List[Optional[str]]
_icons: List[Optional[str]]
_tabs: List[GuiTabHandle]
_gui_api: GuiApi
_container_id: str # Parent.
Expand All @@ -352,7 +351,7 @@ def add_tab(self, label: str, icon: Optional[Icon] = None) -> GuiTabHandle:
out = GuiTabHandle(_parent=self, _id=id)

self._labels.append(label)
self._icons_base64.append(None if icon is None else base64_from_icon(icon))
self._icons.append(None if icon is None else icon.value)
self._tabs.append(out)

self._sync_with_client()
Expand All @@ -372,7 +371,7 @@ def _sync_with_client(self) -> None:
id=self._tab_group_id,
container_id=self._container_id,
tab_labels=tuple(self._labels),
tab_icons_base64=tuple(self._icons_base64),
tab_icons=tuple(self._icons),
tab_container_ids=tuple(tab._id for tab in self._tabs),
)
)
Expand Down Expand Up @@ -495,7 +494,7 @@ def remove(self) -> None:
self._parent._gui_api._container_handle_from_id.pop(self._id)

self._parent._labels.pop(container_index)
self._parent._icons_base64.pop(container_index)
self._parent._icons.pop(container_index)
self._parent._tabs.pop(container_index)
self._parent._sync_with_client()

Expand Down
26 changes: 23 additions & 3 deletions src/viser/_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from __future__ import annotations

import dataclasses
from typing import Any, Optional, Tuple, Union
from typing import Any, Optional, Tuple, Union, List

import numpy as onp
import numpy.typing as onpt
Expand Down Expand Up @@ -364,7 +364,7 @@ class GuiAddTabGroupMessage(Message):
id: str
container_id: str
tab_labels: Tuple[str, ...]
tab_icons_base64: Tuple[Union[str, None], ...]
tab_icons: Tuple[Union[str, None], ...]
tab_container_ids: Tuple[str, ...]


Expand Down Expand Up @@ -415,7 +415,7 @@ class GuiAddButtonMessage(_GuiAddInputBase):
"teal",
]
]
icon_base64: Optional[str]
icon: Optional[str]


@dataclasses.dataclass
Expand All @@ -427,13 +427,33 @@ class GuiAddSliderMessage(_GuiAddInputBase):
precision: int


@dataclasses.dataclass
class GuiSliderMark:
value: float
label: Optional[str] = None


@dataclasses.dataclass
class GuiAddMultiSliderMessage(_GuiAddInputBase):
min: float
max: float
step: Optional[float]
min_range: Optional[float]
initial_value: List[float]
precision: int
fixed_endpoints: bool = False
marks: Optional[List[GuiSliderMark]] = None



@dataclasses.dataclass
class GuiAddNumberMessage(_GuiAddInputBase):
initial_value: float
precision: int
step: float
min: Optional[float]
max: Optional[float]
icon: Optional[str]


@dataclasses.dataclass
Expand Down
28 changes: 28 additions & 0 deletions src/viser/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ export type ViewerContextContents = {
>;
getRenderRequest: React.MutableRefObject<null | GetRenderRequestMessage>;
sceneClickEnable: React.MutableRefObject<boolean>;
setIsRenderMode: (isRenderMode: boolean) => void;
viewerModeCameraStateBackup: React.MutableRefObject<null | [number[], string]>;
};
export const ViewerContext = React.createContext<null | ViewerContextContents>(
null,
Expand Down Expand Up @@ -129,6 +131,32 @@ function ViewerRoot() {
getRenderRequestState: React.useRef("ready"),
getRenderRequest: React.useRef(null),
sceneClickEnable: React.useRef(false),
viewerModeCameraStateBackup: React.useRef<null | [number[], string]>(null),
setIsRenderMode: (isRenderMode: boolean) => {
viewer.useGui.setState((state) => {
state.isRenderMode = isRenderMode;
const camera = viewer.cameraRef.current!;
// Backup/restore camera state
if (isRenderMode) {
viewer.viewerModeCameraStateBackup.current = [
[...camera.position.toArray()],
viewer.cameraControlRef.current!.toJSON()
];
} else if (viewer.viewerModeCameraStateBackup.current !== null) {
const [cameraData, cameraControl] = viewer.viewerModeCameraStateBackup.current!;
const position = new THREE.Vector3(...cameraData.slice(0, 3));
const rotation = new THREE.Euler(...cameraData.slice(3, 6));
//viewer.cameraControlRef.current?.fromJSON(cameraControl, false);
camera.position.copy(position);
camera.rotation.copy(rotation);
camera.updateProjectionMatrix();
//viewer.cameraControlRef.current?.updateCameraUp();
//viewer.cameraControlRef.current?.update(1);
viewer.viewerModeCameraStateBackup.current = null;
}
//console.log(camera.up);
});
}
};

return (
Expand Down
1 change: 0 additions & 1 deletion src/viser/client/src/CameraControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export function SynchronizedCameraControls() {

// Callback for sending cameras.
const sendCamera = React.useCallback(() => {
console.log("Sending camera");
const three_camera = camera;
const camera_control = viewer.cameraControlRef.current;

Expand Down
24 changes: 12 additions & 12 deletions src/viser/client/src/ControlPanel/CameraTrajectoryPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,11 @@ function getPoseFromCamera(viewer: ViewerContextContents) {
const three_camera = viewer.cameraRef.current!;
const R_threecam_cam = new THREE.Quaternion().setFromEuler(
new THREE.Euler(Math.PI, 0.0, 0.0),
).multiply(new THREE.Quaternion().setFromEuler(
new THREE.Euler(0.0, 0.0, Math.PI),
));
);
const R_world_threeworld = getR_threeworld_world(viewer).invert();
const R_world_camera = R_world_threeworld.clone()
.multiply(three_camera.quaternion)
.multiply(R_threecam_cam);
.multiply(three_camera.quaternion);
//.multiply(R_threecam_cam);

return {
wxyz: [
Expand Down Expand Up @@ -265,19 +263,21 @@ export default function CameraTrajectoryPanel(props: CameraTrajectoryPanelProps)
const up = curveObject.curve_ups.getPoint(point).multiplyScalar(-1).applyQuaternion(R_threeworld_world);
const fov = curveObject.curve_fovs.getPoint(point).z;

const cameraControls = viewer.cameraControlRef.current!;
// const cameraControls = viewer.cameraControlRef.current!;
const threeCamera = viewer.cameraRef.current!;

threeCamera.position.set(...position.toArray());
threeCamera.up.set(...up.toArray());
cameraControls.updateCameraUp();
cameraControls.setLookAt(...position.toArray(), ...lookat.toArray(), false);
const target = position.clone().add(lookat);
threeCamera.lookAt(...lookat.toArray());
// cameraControls.updateCameraUp();
// cameraControls.setLookAt(...position.toArray(), ...lookat.toArray(), false);
// const target = position.clone().add(lookat);
// NOTE: lookat is being ignored when calling setLookAt
cameraControls.setTarget(...target.toArray(), false);
// cameraControls.setTarget(...target.toArray(), false);
threeCamera.setFocalLength(
(0.5 * threeCamera.getFilmHeight()) / Math.tan(fov / 2.0),
);
cameraControls.update(1.);
// cameraControls.update(1.);
} else {
const point = getKeyframePoint(playerTime);
const position = curveObject.curve_positions.getPoint(point);
Expand Down Expand Up @@ -889,7 +889,7 @@ export default function CameraTrajectoryPanel(props: CameraTrajectoryPanelProps)
label="Render mode"
checked={isRenderMode}
onChange={(event) => {
viewer.useGui.setState({ isRenderMode: event.currentTarget.checked });
viewer.setIsRenderMode(event.currentTarget.checked);
}}
size="sm"
/>
Expand Down
2 changes: 2 additions & 0 deletions src/viser/client/src/ControlPanel/ControlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import BottomPanel from "./BottomPanel";
import FloatingPanel from "./FloatingPanel";
import { ThemeConfigurationMessage } from "../WebsocketMessages";
import SidebarPanel from "./SidebarPanel";
import CameraTrajectoryPanel from "./CameraTrajectoryPanel";

// Must match constant in Python.
const ROOT_CONTAINER_ID = "root";
Expand Down Expand Up @@ -77,6 +78,7 @@ export default function ControlPanel(props: {
const panelContents = (
<>
<Collapse in={!showGenerated || showSettings} p="xs" pt="0.375em">
<CameraTrajectoryPanel />
<ServerControls />
</Collapse>
<Collapse in={showGenerated && !showSettings}>
Expand Down
Loading

0 comments on commit 07c90eb

Please sign in to comment.