diff --git a/docs/source/examples/09_urdf_visualizer.rst b/docs/source/examples/09_urdf_visualizer.rst index 817f60f2b..354dcf8f7 100644 --- a/docs/source/examples/09_urdf_visualizer.rst +++ b/docs/source/examples/09_urdf_visualizer.rst @@ -23,7 +23,6 @@ and viser. It can also take a path to a local URDF file as input. from __future__ import annotations import time - from pathlib import Path import numpy as onp import tyro @@ -31,6 +30,27 @@ and viser. It can also take a path to a local URDF file as input. from robot_descriptions.loaders.yourdfpy import load_robot_description from viser.extras import ViserUrdf + # A subset of robots available in the robot_descriptions package. + ROBOT_MODEL_LIST = ( + "panda_description", + "ur10_description", + "ur3_description", + "ur5_description", + "cassie_description", + "skydio_x2_description", + "allegro_hand_description", + "barrett_hand_description", + "robotiq_2f85_description", + "atlas_drc_description", + "atlas_v4_description", + "draco3_description", + "g1_description", + "h1_description", + "anymal_c_description", + "go2_description", + "mini_cheetah_description", + ) + def main() -> None: # Start viser server. @@ -97,64 +117,5 @@ and viser. It can also take a path to a local URDF file as input. time.sleep(10.0) - ROBOT_MODEL_LIST = ( - "edo_description", - "fanuc_m710ic_description", - "gen2_description", - "gen3_description", - "iiwa14_description", - "iiwa7_description", - "panda_description", - "poppy_ergo_jr_description", - "ur10_description", - "ur3_description", - "ur5_description", - "z1_description", - "bolt_description", - "cassie_description", - "rhea_description", - "spryped_description", - "upkie_description", - "baxter_description", - "nextage_description", - "poppy_torso_description", - "yumi_description", - "cf2_description", - "skydio_x2_description", - "double_pendulum_description", - "finger_edu_description", - "simple_humanoid_description", - "trifinger_edu_description", - "allegro_hand_description", - "barrett_hand_description", - "robotiq_2f85_description", - "atlas_drc_description", - "atlas_v4_description", - "draco3_description", - "ergocub_description", - "g1_description", - "h1_description", - "icub_description", - "jaxon_description", - "jvrc_description", - "r2_description", - "romeo_description", - "sigmaban_description", - "talos_description", - "valkyrie_description", - "a1_description", - "aliengo_description", - "anymal_b_description", - "anymal_c_description", - "b1_description", - "go1_description", - "go2_description", - "hyq_description", - "laikago_description", - "mini_cheetah_description", - "minitaur_description", - "solo_description", - ) - if __name__ == "__main__": tyro.cli(main) diff --git a/examples/01_image.py b/examples/01_image.py index 86a2a6860..05568b679 100644 --- a/examples/01_image.py +++ b/examples/01_image.py @@ -11,7 +11,6 @@ import imageio.v3 as iio import numpy as onp - import viser diff --git a/examples/02_gui.py b/examples/02_gui.py index 75b58449f..ebbc62bbc 100644 --- a/examples/02_gui.py +++ b/examples/02_gui.py @@ -5,7 +5,6 @@ import time import numpy as onp - import viser diff --git a/examples/03_gui_callbacks.py b/examples/03_gui_callbacks.py index a1df139e2..a7d2133a5 100644 --- a/examples/03_gui_callbacks.py +++ b/examples/03_gui_callbacks.py @@ -6,9 +6,8 @@ import time import numpy as onp -from typing_extensions import assert_never - import viser +from typing_extensions import assert_never def main() -> None: diff --git a/examples/05_camera_commands.py b/examples/05_camera_commands.py index 4a51aeaa5..7130b7aa2 100644 --- a/examples/05_camera_commands.py +++ b/examples/05_camera_commands.py @@ -7,7 +7,6 @@ import time import numpy as onp - import viser import viser.transforms as tf diff --git a/examples/06_mesh.py b/examples/06_mesh.py index 3d30f09e5..eeeaf783b 100644 --- a/examples/06_mesh.py +++ b/examples/06_mesh.py @@ -8,7 +8,6 @@ import numpy as onp import trimesh - import viser import viser.transforms as tf diff --git a/examples/07_record3d_visualizer.py b/examples/07_record3d_visualizer.py index e8b926bbb..2d869257f 100644 --- a/examples/07_record3d_visualizer.py +++ b/examples/07_record3d_visualizer.py @@ -8,11 +8,10 @@ import numpy as onp import tyro -from tqdm.auto import tqdm - import viser import viser.extras import viser.transforms as tf +from tqdm.auto import tqdm def main( diff --git a/examples/08_smpl_visualizer.py b/examples/08_smpl_visualizer.py index 92a63bf37..000501a3c 100644 --- a/examples/08_smpl_visualizer.py +++ b/examples/08_smpl_visualizer.py @@ -13,7 +13,6 @@ import numpy as np import numpy as onp import tyro - import viser import viser.transforms as tf diff --git a/examples/09_urdf_visualizer.py b/examples/09_urdf_visualizer.py index c6d970721..fe448539b 100644 --- a/examples/09_urdf_visualizer.py +++ b/examples/09_urdf_visualizer.py @@ -14,11 +14,31 @@ import numpy as onp import tyro -from robot_descriptions.loaders.yourdfpy import load_robot_description - import viser +from robot_descriptions.loaders.yourdfpy import load_robot_description from viser.extras import ViserUrdf +# A subset of robots available in the robot_descriptions package. +ROBOT_MODEL_LIST = ( + "panda_description", + "ur10_description", + "ur3_description", + "ur5_description", + "cassie_description", + "skydio_x2_description", + "allegro_hand_description", + "barrett_hand_description", + "robotiq_2f85_description", + "atlas_drc_description", + "atlas_v4_description", + "draco3_description", + "g1_description", + "h1_description", + "anymal_c_description", + "go2_description", + "mini_cheetah_description", +) + def main() -> None: # Start viser server. @@ -85,64 +105,5 @@ def _(_): time.sleep(10.0) -ROBOT_MODEL_LIST = ( - "edo_description", - "fanuc_m710ic_description", - "gen2_description", - "gen3_description", - "iiwa14_description", - "iiwa7_description", - "panda_description", - "poppy_ergo_jr_description", - "ur10_description", - "ur3_description", - "ur5_description", - "z1_description", - "bolt_description", - "cassie_description", - "rhea_description", - "spryped_description", - "upkie_description", - "baxter_description", - "nextage_description", - "poppy_torso_description", - "yumi_description", - "cf2_description", - "skydio_x2_description", - "double_pendulum_description", - "finger_edu_description", - "simple_humanoid_description", - "trifinger_edu_description", - "allegro_hand_description", - "barrett_hand_description", - "robotiq_2f85_description", - "atlas_drc_description", - "atlas_v4_description", - "draco3_description", - "ergocub_description", - "g1_description", - "h1_description", - "icub_description", - "jaxon_description", - "jvrc_description", - "r2_description", - "romeo_description", - "sigmaban_description", - "talos_description", - "valkyrie_description", - "a1_description", - "aliengo_description", - "anymal_b_description", - "anymal_c_description", - "b1_description", - "go1_description", - "go2_description", - "hyq_description", - "laikago_description", - "mini_cheetah_description", - "minitaur_description", - "solo_description", -) - if __name__ == "__main__": tyro.cli(main) diff --git a/examples/10_realsense.py b/examples/10_realsense.py index a16e6c2d3..8cc08f345 100644 --- a/examples/10_realsense.py +++ b/examples/10_realsense.py @@ -11,9 +11,8 @@ import numpy as np import numpy.typing as npt import pyrealsense2 as rs # type: ignore -from tqdm.auto import tqdm - import viser +from tqdm.auto import tqdm @contextlib.contextmanager diff --git a/examples/11_colmap_visualizer.py b/examples/11_colmap_visualizer.py index c9db0130a..493131b69 100644 --- a/examples/11_colmap_visualizer.py +++ b/examples/11_colmap_visualizer.py @@ -10,10 +10,9 @@ import imageio.v3 as iio import numpy as onp import tyro -from tqdm.auto import tqdm - import viser import viser.transforms as tf +from tqdm.auto import tqdm from viser.extras.colmap import ( read_cameras_binary, read_images_binary, diff --git a/examples/12_click_meshes.py b/examples/12_click_meshes.py index 8fdec08fc..ddd705840 100644 --- a/examples/12_click_meshes.py +++ b/examples/12_click_meshes.py @@ -6,7 +6,6 @@ import time import matplotlib - import viser diff --git a/examples/15_gui_in_scene.py b/examples/15_gui_in_scene.py index 89866dd35..ccce02c25 100644 --- a/examples/15_gui_in_scene.py +++ b/examples/15_gui_in_scene.py @@ -9,7 +9,6 @@ from typing import Optional import numpy as onp - import viser import viser.transforms as tf diff --git a/examples/17_background_composite.py b/examples/17_background_composite.py index 6098f02f3..3904d442e 100644 --- a/examples/17_background_composite.py +++ b/examples/17_background_composite.py @@ -9,7 +9,6 @@ import numpy as onp import trimesh import trimesh.creation - import viser server = viser.ViserServer() diff --git a/examples/18_splines.py b/examples/18_splines.py index a6d42ebcf..3c939c9d0 100644 --- a/examples/18_splines.py +++ b/examples/18_splines.py @@ -6,7 +6,6 @@ import time import numpy as onp - import viser diff --git a/examples/19_get_renders.py b/examples/19_get_renders.py index 2de8a0655..f235730bb 100644 --- a/examples/19_get_renders.py +++ b/examples/19_get_renders.py @@ -6,7 +6,6 @@ import imageio.v3 as iio import numpy as onp - import viser diff --git a/examples/20_scene_pointer.py b/examples/20_scene_pointer.py index 5b58ada0e..8f9c51eb8 100644 --- a/examples/20_scene_pointer.py +++ b/examples/20_scene_pointer.py @@ -16,7 +16,6 @@ import trimesh import trimesh.creation import trimesh.ray - import viser import viser.transforms as tf diff --git a/examples/22_games.py b/examples/22_games.py index 1ff2f33ad..6a45f5a6c 100644 --- a/examples/22_games.py +++ b/examples/22_games.py @@ -7,10 +7,9 @@ import numpy as onp import trimesh.creation -from typing_extensions import assert_never - import viser import viser.transforms as tf +from typing_extensions import assert_never def main() -> None: diff --git a/examples/23_plotly.py b/examples/23_plotly.py index 7f7231a7f..48cfd36b5 100644 --- a/examples/23_plotly.py +++ b/examples/23_plotly.py @@ -7,9 +7,8 @@ import numpy as onp import plotly.express as px import plotly.graph_objects as go -from PIL import Image - import viser +from PIL import Image def create_sinusoidal_wave(t: float) -> go.Figure: diff --git a/pyproject.toml b/pyproject.toml index 9519a85a1..6e0212dc1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "viser" -version = "0.1.31" +version = "0.2.0" description = "3D visualization + Python" readme = "README.md" license = { text="MIT" } @@ -40,7 +40,7 @@ dependencies = [ [project.optional-dependencies] dev = [ "pyright>=1.1.308", - "ruff==0.4.3", + "ruff==0.4.6", "pre-commit==3.3.2", ] examples = [ diff --git a/src/viser/_client_autobuild.py b/src/viser/_client_autobuild.py index 84e17ebcc..fa4029781 100644 --- a/src/viser/_client_autobuild.py +++ b/src/viser/_client_autobuild.py @@ -48,8 +48,12 @@ def ensure_client_is_built() -> None: elif not (build_dir / "index.html").exists(): rich.print("[bold](viser)[/bold] No client build found. Building now...") build = True - elif _modified_time_recursive(client_dir / "src") > _modified_time_recursive( - build_dir + elif ( + # We should be at least 10 seconds newer than the last build. + # This buffer is important when we install from pip, and the src/ + + # build/ directories have very similar timestamps. + _modified_time_recursive(client_dir / "src") + > _modified_time_recursive(build_dir) + 10.0 ): rich.print( "[bold](viser)[/bold] Client build looks out of date. Building now..." diff --git a/src/viser/_gui_api.py b/src/viser/_gui_api.py index 2e506e80b..0fcc4e06c 100644 --- a/src/viser/_gui_api.py +++ b/src/viser/_gui_api.py @@ -812,8 +812,7 @@ def add_button_group( disabled: bool = False, hint: str | None = None, order: float | None = None, - ) -> GuiButtonGroupHandle[TLiteralString]: - ... + ) -> GuiButtonGroupHandle[TLiteralString]: ... @overload def add_button_group( @@ -824,8 +823,7 @@ def add_button_group( disabled: bool = False, hint: str | None = None, order: float | None = None, - ) -> GuiButtonGroupHandle[TString]: - ... + ) -> GuiButtonGroupHandle[TString]: ... def add_button_group( self, @@ -1153,8 +1151,7 @@ def add_dropdown( visible: bool = True, hint: str | None = None, order: float | None = None, - ) -> GuiDropdownHandle[TLiteralString]: - ... + ) -> GuiDropdownHandle[TLiteralString]: ... @overload def add_dropdown( @@ -1166,8 +1163,7 @@ def add_dropdown( visible: bool = True, hint: str | None = None, order: float | None = None, - ) -> GuiDropdownHandle[TString]: - ... + ) -> GuiDropdownHandle[TString]: ... def add_dropdown( self, diff --git a/src/viser/_gui_handles.py b/src/viser/_gui_handles.py index dd497d758..7a458ad36 100644 --- a/src/viser/_gui_handles.py +++ b/src/viser/_gui_handles.py @@ -42,8 +42,7 @@ class GuiContainerProtocol(Protocol): class SupportsRemoveProtocol(Protocol): - def remove(self) -> None: - ... + def remove(self) -> None: ... @dataclasses.dataclass @@ -342,6 +341,8 @@ def options(self, options: Iterable[StringType]) -> None: @dataclasses.dataclass(frozen=True) class GuiTabGroupHandle: + """Handle for a tab group. Call :meth:`add_tab()` to add a tab.""" + _tab_group_id: str _labels: list[str] _icons_html: list[str | None] @@ -623,7 +624,7 @@ def remove(self) -> None: @dataclasses.dataclass class GuiPlotlyHandle: - """Use to remove markdown.""" + """Use to update or remove markdown elements.""" _gui_api: GuiApi _id: str diff --git a/src/viser/_scene_api.py b/src/viser/_scene_api.py index 019b85fe5..c2d675410 100644 --- a/src/viser/_scene_api.py +++ b/src/viser/_scene_api.py @@ -1211,8 +1211,9 @@ def on_pointer_callback_removed( ) -> Callable[[], None]: """Add a callback to run automatically when the callback for a scene pointer event is removed. This will be triggered exactly once, either - manually (via `remove_scene_pointer()`) or automatically (if the scene - pointer event is overridden with another call to `on_scene_pointer()`). + manually (via :meth:`remove_pointer_callback()`) or automatically (if + the scene pointer event is overridden with another call to + :meth:`on_pointer_event()`). Args: func: Callback for when scene pointer events are removed. @@ -1272,17 +1273,16 @@ def add_3d_gui_container( """ # Avoids circular import. - from ._gui_api import GuiApi, _make_unique_id + from ._gui_api import _make_unique_id # New name to make the type checker happy; ViserServer and ClientHandle inherit # from both GuiApi and MessageApi. The pattern below is unideal. - gui_api = self - assert isinstance(gui_api, GuiApi) + gui_api = self._owner.gui # Remove the 3D GUI container if it already exists. This will make sure # contained GUI elements are removed, preventing potential memory leaks. - if name in gui_api._handle_from_node_name: - gui_api._handle_from_node_name[name].remove() + if name in self._handle_from_node_name: + self._handle_from_node_name[name].remove() container_id = _make_unique_id() self._websock_interface.queue_message( diff --git a/src/viser/_scene_handles.py b/src/viser/_scene_handles.py index d6d5d7333..c4b045ed9 100644 --- a/src/viser/_scene_handles.py +++ b/src/viser/_scene_handles.py @@ -273,6 +273,6 @@ def remove(self) -> None: super().remove() # Clean up contained GUI elements. - self._gui_api._container_handle_from_id.pop(self._container_id) - for child in self._children.values(): + for child in tuple(self._children.values()): child.remove() + self._gui_api._container_handle_from_id.pop(self._container_id) diff --git a/src/viser/_viser.py b/src/viser/_viser.py index 6a7db4fdc..8fd66ed0e 100644 --- a/src/viser/_viser.py +++ b/src/viser/_viser.py @@ -31,7 +31,7 @@ class _BackwardsCompatibilityShim: def __getattr__(self, name: str) -> Any: fixed_name = { - # Map from old method names (viser v0.1.30) to new methods names. + # Map from old method names (viser v0.1.*) to new methods names. "reset_scene": "reset", "set_global_scene_node_visibility": "set_global_visibility", "on_scene_pointer": "on_pointer_event", @@ -41,7 +41,8 @@ def __getattr__(self, name: str) -> Any: }.get(name, name) if hasattr(self.scene, fixed_name): warnings.warn( - f"{type(self).__name__}.{name} has been deprecated, use {type(self).__name__}.scene.{fixed_name} instead. Alternatively, pin to `viser<=0.1.30`.", + f"{type(self).__name__}.{name} has been deprecated, use {type(self).__name__}.scene.{fixed_name} instead. Alternatively, pin to `viser<0.2.0`.", + category=DeprecationWarning, stacklevel=2, ) return object.__getattribute__(self.scene, fixed_name) @@ -49,7 +50,8 @@ def __getattr__(self, name: str) -> Any: fixed_name = name.replace("add_gui_", "add_").replace("set_gui_", "set_") if hasattr(self.gui, fixed_name): warnings.warn( - f"{type(self).__name__}.{name} has been deprecated, use {type(self).__name__}.gui.{fixed_name} instead. Alternatively, pin to `viser<=0.1.30`.", + f"{type(self).__name__}.{name} has been deprecated, use {type(self).__name__}.gui.{fixed_name} instead. Alternatively, pin to `viser<0.2.0`.", + category=DeprecationWarning, stacklevel=2, ) return object.__getattribute__(self.gui, fixed_name) diff --git a/src/viser/client/src/ControlPanel/Generated.tsx b/src/viser/client/src/ControlPanel/Generated.tsx index fbd0113bf..7a9761dd5 100644 --- a/src/viser/client/src/ControlPanel/Generated.tsx +++ b/src/viser/client/src/ControlPanel/Generated.tsx @@ -32,7 +32,7 @@ export default function GeneratedGuiContainer({ const updateGuiProps = viewer.useGui((state) => state.updateGuiProps); const messageSender = makeThrottledMessageSender(viewer.websocketRef, 50); - function setValue(id: string, value: any) { + function setValue(id: string, value: NonNullable) { updateGuiProps(id, { value: value }); messageSender({ type: "GuiUpdateMessage", diff --git a/src/viser/client/src/ControlPanel/GuiComponentContext.tsx b/src/viser/client/src/ControlPanel/GuiComponentContext.tsx index 91948101c..fe10d5361 100644 --- a/src/viser/client/src/ControlPanel/GuiComponentContext.tsx +++ b/src/viser/client/src/ControlPanel/GuiComponentContext.tsx @@ -3,7 +3,7 @@ import * as Messages from "../WebsocketMessages"; interface GuiComponentContext { folderDepth: number; - setValue: (id: string, value: any) => void; + setValue: (id: string, value: NonNullable) => void; messageSender: (message: Messages.Message) => void; GuiContainer: React.FC<{ containerId: string }>; } diff --git a/src/viser/client/src/components/Dropdown.tsx b/src/viser/client/src/components/Dropdown.tsx index 95a13f960..9a027ac6d 100644 --- a/src/viser/client/src/components/Dropdown.tsx +++ b/src/viser/client/src/components/Dropdown.tsx @@ -22,7 +22,7 @@ export default function DropdownComponent({ radius="xs" value={value} data={options} - onChange={(value) => setValue(id, value)} + onChange={(value) => value !== null && setValue(id, value)} disabled={disabled} searchable maxDropdownHeight={400}