Session APIs - Managing Your Scene¶
Overview¶
Vuer provides a set of intuitive APIs for managing your 3D scene dynamically. Understanding when to use each API is crucial for building efficient, interactive applications.
This guide covers the six core session APIs:
session.set- Initialize the scenesession.upsert- Update or insert elementssession.update- Update existing elements onlysession.add- Add new elementssession.remove- Remove elementssession.till- Wait for client events
Quick Reference¶
API |
Purpose |
Behavior if element exists |
Behavior if element missing |
|---|---|---|---|
|
Initialize root scene |
N/A (replaces entire scene) |
N/A (creates new scene) |
|
Ensure element exists |
Updates it |
Creates it |
|
Modify existing only |
Updates it |
Does nothing (NOOP) |
|
Add new elements |
Error/duplicate |
Creates it |
|
Delete elements |
Removes it |
Does nothing |
|
Wait for event |
N/A |
Waits until received |
1. session.set - Initialize the Scene¶
Purpose: Set up the root scene structure. This should be called once at the beginning of your session.
When to use:
At the start of your session to initialize the scene
When you need to completely replace the entire scene structure
Accepts: Only Scene or DefaultScene objects
from vuer import Vuer, VuerSession
from vuer.schemas import DefaultScene, Box, Sphere
app = Vuer()
@app.spawn(start=True)
async def main(session: VuerSession):
# Initialize the scene once
session.set @ DefaultScene(
Box(key="box-1", args=[1, 1, 1], position=[0, 0, 0]),
Sphere(key="sphere-1", args=[0.5], position=[2, 0, 0]),
)
await session.forever()
Important
session.set replaces the entire scene tree from the root. Use it only for initialization, not for incremental updates.
2. session.upsert - Update or Insert (Most Common)¶
Purpose: Ensure an element with a specific key exists with the given properties. If it exists, update it; if not, create it.
When to use:
When you want to guarantee an element exists (most common use case)
For dynamic, real-time updates where you donât know if the element was created yet
When building interactive applications with frequent updates
Behavior:
If element with the key exists â updates it
If element doesnât exist â inserts it as new
@app.spawn(start=True)
async def main(session: VuerSession):
# Initialize scene
session.set @ DefaultScene()
# Upsert elements - creates them if they don't exist
session.upsert @ Box(
key="dynamic-box",
args=[1, 1, 1],
position=[0, 0, 0],
color="red"
)
# Later, update the same box (or create if removed)
await session.sleep(1)
session.upsert @ Box(
key="dynamic-box",
position=[2, 0, 0], # Moved to new position
color="blue" # Changed color
)
await session.forever()
Advanced: Targeting specific parents
# Upsert into a specific parent node
session.upsert(
Box(key="child-box", args=[0.5, 0.5, 0.5]),
to="parent-group-key"
)
# Upsert multiple elements at once
session.upsert @ [
Box(key="box-1", position=[0, 0, 0]),
Sphere(key="sphere-1", position=[2, 0, 0]),
Cylinder(key="cyl-1", position=[4, 0, 0])
]
Recommendation
session.upsert is the most commonly used API for dynamic scene updates because itâs safe and flexible.
3. session.update - Update Existing Only¶
Purpose: Modify existing elements without creating new ones. Safe for conditional updates.
When to use:
When you want to update elements but not create them if they donât exist
For defensive programming where you want to avoid accidental creation
When youâre unsure if an element exists
Behavior:
If element exists â updates it
If element doesnât exist â does nothing (NOOP)
@app.spawn(start=True)
async def main(session: VuerSession):
# Initialize with a box
session.set @ DefaultScene(
Box(key="my-box", args=[1, 1, 1], position=[0, 0, 0])
)
# This updates the existing box
session.update @ Box(key="my-box", color="blue") # â
Works
# This does nothing because "other-box" doesn't exist
session.update @ Box(key="other-box", color="red") # â NOOP (no error)
await session.forever()
Real-world example: Safe property updates
# Only update position if the robot model has been loaded
session.update @ Urdf(
key="robot",
position=new_position,
jointValues=new_joint_values
)
# If robot isn't loaded yet, this won't create a duplicate or error
Use Case
Use session.update when you want update-only behavior without the risk of creating duplicate elements.
4. session.add - Add New Elements¶
Purpose: Explicitly add new elements to the scene. Assumes the element doesnât already exist.
When to use:
When youâre certain the element doesnât exist yet
For one-time additions where you control the lifecycle
When you want explicit âaddâ semantics in your code
Behavior:
If element doesnât exist â adds it
If element exists â behavior depends on implementation (may create duplicate or error)
@app.spawn(start=True)
async def main(session: VuerSession):
# Initialize scene
session.set @ DefaultScene()
# Add new elements explicitly
session.add @ Box(
key="new-box",
args=[1, 1, 1],
position=[0, 0, 0]
)
# Add to specific parent
session.add(
Sphere(key="child-sphere", args=[0.3]),
to="new-box" # Add as child of the box
)
await session.forever()
Adding multiple elements:
# Add multiple elements at once
session.add @ [
Box(key="box-1", position=[0, 0, 0]),
Box(key="box-2", position=[2, 0, 0]),
Box(key="box-3", position=[4, 0, 0])
]
Note
Unlike upsert, add assumes the element is new. If youâre not certain, use upsert instead.
5. session.remove - Delete Elements¶
Purpose: Remove elements from the scene by their keys.
When to use:
When you need to delete specific elements
For cleanup operations
To remove temporary visualizations
Behavior:
Removes elements matching the provided key(s)
Safe to call even if elements donât exist
@app.spawn(start=True)
async def main(session: VuerSession):
# Initialize scene with multiple boxes
session.set @ DefaultScene(
Box(key="box-1", position=[0, 0, 0]),
Box(key="box-2", position=[2, 0, 0]),
Box(key="box-3", position=[4, 0, 0])
)
# Remove a single element
await session.sleep(1)
session.remove @ "box-1"
# Remove multiple elements
await session.sleep(1)
session.remove @ ["box-2", "box-3"]
await session.forever()
Practical example: Toggle visualization
async def toggle_grid(session: VuerSession, visible: bool):
if visible:
session.upsert @ Grid(key="main-grid")
else:
session.remove @ "main-grid"
Common Patterns¶
Pattern 1: Initialize then Update¶
The most common pattern: use set once, then upsert for updates.
@app.spawn(start=True)
async def main(session: VuerSession):
# 1. Initialize scene once
session.set @ DefaultScene(
Box(key="robot-base", args=[1, 0.5, 1])
)
# 2. Continuously update elements
for i in range(100):
session.upsert @ Box(
key="robot-base",
position=[i * 0.1, 0, 0] # Move along x-axis
)
await session.sleep(0.1)
Pattern 2: Dynamic Element Management¶
Add and remove elements dynamically based on application state.
@app.spawn(start=True)
async def main(session: VuerSession):
session.set @ DefaultScene()
active_markers = set()
# Add markers as they appear
for i, position in enumerate(detected_positions):
key = f"marker-{i}"
session.add @ Sphere(
key=key,
args=[0.1],
position=position
)
active_markers.add(key)
# Remove old markers
for key in old_markers:
session.remove @ key
active_markers.discard(key)
Pattern 3: Efficient Batch Updates¶
Update multiple elements efficiently.
# Update multiple objects in one call
session.upsert @ [
Box(key="box-1", position=positions[0]),
Box(key="box-2", position=positions[1]),
Box(key="box-3", position=positions[2]),
Box(key="box-4", position=positions[3])
]
Pattern 4: Safe Updates with Fallback¶
Use update for conditional changes, upsert when you need guarantees.
# Try to update existing camera
session.update @ PerspectiveCamera(
key="main-camera",
position=[5, 5, 5]
)
# If camera might not exist, use upsert instead
session.upsert @ PerspectiveCamera(
key="main-camera",
position=[5, 5, 5],
makeDefault=True
)
Performance Considerations¶
Efficient Updates¶
Performance Tip
session.upsert is more efficient than session.set for incremental updates because it only modifies specific elements instead of rebuilding the entire scene.
â Inefficient: Rebuilding entire scene
# Bad: rebuilding scene every frame
for frame in frames:
session.set @ DefaultScene(
Box(key="animated-box", position=frame.position),
Sphere(key="static-sphere", position=[0, 0, 0]),
# ... many other static elements
)
â Efficient: Updating only what changed
# Good: initialize once, update only animated elements
session.set @ DefaultScene(
Box(key="animated-box", position=[0, 0, 0]),
Sphere(key="static-sphere", position=[0, 0, 0]),
# ... other static elements
)
for frame in frames:
session.upsert @ Box(
key="animated-box",
position=frame.position # Only update position
)
Batch Operations¶
When updating multiple elements, batch them into a single operation:
# Better: batch update
session.upsert @ [
Box(key=f"box-{i}", position=[i, 0, 0])
for i in range(10)
]
# Less efficient: individual updates
for i in range(10):
session.upsert @ Box(key=f"box-{i}", position=[i, 0, 0])
Summary¶
Decision Tree:
Setting up initial scene? â Use
session.setNeed element to exist with specific properties? â Use
session.upsertâ (most common)Only update if exists, otherwise skip? â Use
session.updateAdding definitely new elements? â Use
session.addRemoving elements? â Use
session.removeNeed to wait for a client event? â Use
session.till
Most Common Usage:
@app.spawn(start=True)
async def main(session: VuerSession):
# Step 0: Identify client type (optional)
e = await session.till("INIT")
print(f"Client: {e.value.get('client')}")
# Step 1: Initialize scene (once)
session.set @ DefaultScene()
# Step 2: Add/update elements dynamically (many times)
while True:
session.upsert @ Box(
key="animated-box",
position=compute_position(),
rotation=compute_rotation()
)
await session.sleep(0.016) # ~60 FPS
6. session.till - Wait for Client Events¶
Purpose: Wait for and receive a specific event type from the client. This is useful for awaiting events like INIT to identify the client type.
When to use:
When you need to wait for a specific event before proceeding
To identify client type (browser vs Python client) on connection
For handshake or initialization sequences
Behavior:
Registers a one-time handler for the specified event type
Returns the event when received
Optionally times out if event doesnât arrive
@app.spawn(start=True)
async def main(session: VuerSession):
# Wait for the INIT event from the client
e = await session.till("INIT")
# Check if it's a Python client or browser
client = e.value.get('client')
if client == 'python':
print("Python client connected!")
print(f" Platform: {e.value.get('platform')}")
print(f" Python: {e.value.get('pythonVersion')}")
else:
# Browser client
print(f"Browser connected!")
print(f" User Agent: {e.value.get('userAgent')}")
print(f" Screen: {e.value.get('screenWidth')}x{e.value.get('screenHeight')}")
session.set @ DefaultScene()
await session.forever()
With timeout:
import asyncio
@app.spawn(start=True)
async def main(session: VuerSession):
try:
# Wait up to 5 seconds for INIT
e = await session.till("INIT", timeout=5.0)
print(f"Client connected: {e.value.get('clientType')}")
except asyncio.TimeoutError:
print("No INIT received within 5 seconds")
await session.forever()
Client Info (INIT Event Value):
Common fields (both clients):
clientâ"browser"or"python"clientVersionâ library versiontimezoneâ IANA format, e.g.,"America/Los_Angeles"timezoneOffsetâ minutes from UTC
Python clients send:
{
"client": "python",
"clientVersion": "0.1.x",
"pythonVersion": "3.11.13",
"platform": "Darwin",
"platformVersion": "24.2.0",
"machine": "arm64",
"timezone": "America/Los_Angeles",
"timezoneOffset": 480
}
Browser clients send:
{
"client": "browser",
"clientVersion": "0.0.97",
"userAgent": "Mozilla/5.0...",
"screenWidth": 2560,
"screenHeight": 1440,
"devicePixelRatio": 2,
"timezone": "America/Los_Angeles",
"timezoneOffset": 480
}
Use Case
Use session.till("INIT") to identify client types and customize behavior accordinglyâfor example, sending different scenes to VR headsets vs desktop browsers vs Python automation scripts.
See Also¶
Constructing a Scene - Learn about scene structure
Event Handling - Responding to user interactions
Client Connection - Connecting Python clients to Vuer