Skip to content

Commit

Permalink
Cleaner API for saved/embedded visualizations + initial documentation (
Browse files Browse the repository at this point in the history
…#357)

* Fix embed bugs caused by loopStartIndex

* Add search param for initial up direction

* Fix visibility / reset edge cases

* API renaming

* Save version

* Add `viser-build-client`, remove `viser-dev-checks`

* polish, docs

* Shim for old APi

* improve docs
  • Loading branch information
brentyi authored Jan 4, 2025
1 parent 46a236a commit e7b5872
Show file tree
Hide file tree
Showing 16 changed files with 432 additions and 164 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ jobs:
# Get version from pyproject.toml.
- name: Get version + subdirectory
run: |
VERSION=$(python -c "import toml; print(toml.load('pyproject.toml')['project']['version'])")
VERSION=$(python -c "import viser; print(viser.__version__)")
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "DOCS_SUBDIR=versions/$VERSION" >> $GITHUB_ENV
# Hack to overwrite version.
- name: Set version to 'latest' for pushes (this will appear in the doc banner)
- name: Set version to 'main' for pushes (this will appear in the doc banner)
run: |
python -c "import toml; conf = toml.load('pyproject.toml'); conf['project']['version'] = 'latest'; toml.dump(conf, open('pyproject.toml', 'w'))"
echo "VISER_VERSION_STR_OVERRIDE=main" >> $GITHUB_ENV
if: github.event_name == 'push'

# Build documentation.
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ jobs:
- name: Only bundle client build for PyPI release
run: |
# This should delete everything in src/viser/client except for the
# build folder.
# build folder + original source files. We don't want to package
# .nodeenv, node_modules, etc in the release.
mv src/viser/client/build ./__built_client
rm -rf src/viser/client/*
git checkout src/viser/client
mv ./__built_client src/viser/client/build
- name: Build and publish
env:
Expand Down
10 changes: 4 additions & 6 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
# full list see the documentation:
# http://www.sphinx-doc.org/en/stable/config

from pathlib import Path
import os
from typing import Dict, List

import m2r2
import toml

import viser

# -- Path setup --------------------------------------------------------------

Expand All @@ -25,10 +26,7 @@
copyright = "2024"
author = "brentyi"

# The short X.Y version
version: str = toml.load(
Path(__file__).absolute().parent.parent.parent / "pyproject.toml"
)["project"]["version"]
version: str = os.environ.get("VISER_VERSION_STR_OVERRIDE", viser.__version__)

# Formatting!
# 0.1.30 => v0.1.30
Expand Down
11 changes: 7 additions & 4 deletions docs/source/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ pip install -e .[dev]
pre-commit install
```

It would be hard to write unit tests for `viser`. We rely on static typing for
robustness. To check your code, you can run the following:
For code quality, rely primarily on `pyright` and `ruff`:

```bash
# runs linting, formatting, and type-checking
viser-dev-checks
# Check static types.
pyright

# Lint and format.
ruff check --fix .
ruff format .
```

## Message updates
Expand Down
198 changes: 198 additions & 0 deletions docs/source/embedded_visualizations.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
Embedding Visualizations
===============================================

This guide describes how to export 3D visualizations from Viser and embed them into static webpages. The process involves three main steps: exporting scene state, creating a client build, and hosting the visualization.

.. warning::

This workflow is experimental and not yet polished. We're documenting it
nonetheless, since we think it's quite useful! If you have suggestions or
improvements, issues and PRs are welcome.


Step 1: Exporting Scene State
----------------------------

You can export static or dynamic 3D data from a Viser scene using the scene
serializer. :func:`ViserServer.get_scene_serializer` returns a serializer
object that can serialize the current scene state to a binary format.

Static Scene Export
~~~~~~~~~~~~~~~~~~~

For static 3D visualizations, use the following code to save the scene state:

.. code-block:: python
import viser
from pathlib import Path
server = viser.ViserServer()
# Add objects to the scene via server.scene
# For example:
# server.scene.add_mesh(...)
# server.scene.add_point_cloud(...)
server.scene.add_box("/box", color=(255, 0, 0), dimensions=(1, 1, 1))
# Serialize and save the scene state
data = server.get_scene_serializer().serialize() # Returns bytes
Path("recording.viser").write_bytes(data)
As a suggestion, you can also add a button for exporting the scene state:

.. code-block:: python
import viser
server = viser.ViserServer()
# Add objects to the scene via server.scene.
# For example:
# server.scene.add_mesh(...)
# server.scene.add_point_cloud(...)
server.scene.add_box("/box", color=(255, 0, 0), dimensions=(1, 1, 1))
save_button = server.gui.add_button("Save Scene")
@save_button.on_click
def _(event: viser.GuiEvent) -> None:
assert event.client is not None
event.client.send_file_download("recording.viser", server.get_scene_serializer().serialize())
server.sleep_forever()
Dynamic Scene Export
~~~~~~~~~~~~~~~~~~~~

For dynamic visualizations with animation, you can create a "3D video" by inserting sleep commands between frames:

.. code-block:: python
import viser
import numpy as np
from pathlib import Path
server = viser.ViserServer()
# Add objects to the scene via server.scene
# For example:
# server.scene.add_mesh(...)
# server.scene.add_point_cloud(...)
box = server.scene.add_box("/box", color=(255, 0, 0), dimensions=(1, 1, 1))
# Create serializer.
serializer = server.get_scene_serializer()
num_frames = 100
for t in range(num_frames):
# Update existing scene objects or add new ones.
box.position = (0.0, 0.0, np.sin(t / num_frames * 2 * np.pi))
# Add a frame delay.
serializer.insert_sleep(1.0 / 30.0)
# Save the complete animation.
data = serializer.serialize() # Returns bytes
Path("recording.viser").write_bytes(data)
.. note::
Always add scene elements using :attr:`ViserServer.scene`, not :attr:`ClientHandle.scene`.

.. note::
The ``.viser`` file is a binary format containing scene state data and is not meant to be human-readable.

Step 2: Creating a Viser Client Build
-----------------------------------

To serve the 3D visualization, you'll need two things:

1. The ``.viser`` file containing your scene data
2. A build of the Viser client (static HTML/JS/CSS files)

With Viser installed, create the Viser client build using the command-line tool:

.. code-block:: bash
# View available options
viser-build-client --help
# Build to a specific directory
viser-build-client --output-dir viser-client/
Step 3: Hosting
---------------

Directory Structure
~~~~~~~~~~~~~~~~~~~

For our hosting instructions, we're going to assume the following directory structure:

.. code-block::
.
├── recordings/
│ └── recording.viser # Your exported scene data
└── viser-client/
├── index.html # Generated client files
├── assets/
└── ...
This is just a suggestion; you can structure your files however you like.

Local Development Server
~~~~~~~~~~~~~~~~~~~~~~~~

For testing locally, you can use Python's built-in HTTP server:

.. code-block:: bash
# Navigate to the parent directory containing both folders
cd /path/to/parent/dir
# Start the server (default port 8000)
python -m http.server 8000
Then open your browser and navigate to:

* ``http://localhost:8000/viser-client/`` (default port)

This should show the a standard Viser client. To visualize the exported scene, you'll need to specify a URL via the ``?playbackPath=`` parameter:

* ``http://localhost:8000/viser-client/?playbackPath=http://localhost:8000/recordings/recording.viser``


GitHub Pages Deployment
~~~~~~~~~~~~~~~~~~~~~~~

To host your visualization on GitHub Pages:

1. Create a new repository or use an existing one
2. Create a ``gh-pages`` branch or enable GitHub Pages on your main branch
3. Push your directory structure to the repository:

.. code-block:: bash
git add recordings/ viser-client/
git commit -m "Add Viser visualization"
git push origin main # or gh-pages
Your visualization will be available at: ``https://user.github.io/repo/viser-client/?playbackPath=https://user.github.io/repo/recordings/recording.viser``

You can embed this into other webpages using an HTML ``<iframe />`` tag.


Step 4: Setting the initial camera pose
-----------------------------------------------

To set the initial camera pose, you can add a ``&logCamera`` parameter to the URL:

* ``http://localhost:8000/viser-client/?playbackPath=http://localhost:8000/recordings/recording.viser&logCamera``

Then, open your Javascript console. You should see the camera pose printed
whenever you move the camera. It should look something like this:

* ``&initialCameraPosition=2.216,-4.233,-0.947&initialCameraLookAt=-0.115,0.346,-0.192&initialCameraUp=0.329,-0.904,0.272``

You can then add this string to the URL to set the initial camera pose.
1 change: 1 addition & 0 deletions docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ URL (default: `http://localhost:8080`).

./conventions.md
./development.md
./embedded_visualizations.rst

.. toctree::
:caption: API (Basics)
Expand Down
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ exclude = ["src/viser/client/.nodeenv", "src/viser/client/node_modules", "**/__p
# Client build is in the gitignore, but we still want it in the distribution.
ignore-vcs = true

[tool.hatch.version]
path = "src/viser/__init__.py"

[tool.hatch.build.targets.sdist]
only-include = ["src/viser"]

Expand All @@ -15,7 +18,7 @@ packages = ["src/viser"]

[project]
name = "viser"
version = "0.2.19"
dynamic = ["version"]
description = "3D visualization + Python"
readme = "README.md"
license = { text="MIT" }
Expand Down Expand Up @@ -70,7 +73,7 @@ examples = [
"GitHub" = "https://github.com/nerfstudio-project/viser"

[project.scripts]
viser-dev-checks = "viser.scripts.dev_checks:entrypoint"
viser-build-client = "viser._client_autobuild:build_client_entrypoint"

[tool.pyright]
exclude = ["./docs/**/*", "./examples/assets/**/*", "./src/viser/client/.nodeenv", "./build"]
Expand All @@ -86,6 +89,7 @@ lint.select = [
"I", # Import sorting.
]
lint.ignore = [
"E731", # Do not assign a lambda expression, use a def.
"E741", # Ambiguous variable name. (l, O, or I)
"E501", # Line too long.
"E721", # Do not compare types, use `isinstance()`.
Expand Down
2 changes: 2 additions & 0 deletions src/viser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@
from ._viser import CameraHandle as CameraHandle
from ._viser import ClientHandle as ClientHandle
from ._viser import ViserServer as ViserServer

__version__ = "0.2.20"
Loading

0 comments on commit e7b5872

Please sign in to comment.