Skip to content

Commit 5f2830c

Browse files
Visualization: positive prose, overview hub, code accuracy
Convert negation-heavy framing to noun-plus-job across the visualization pages (a visual and its collision geometry each get a positive role), and fix soft negatives vale does not flag (invisible, nothing, cannot). Move the section index into an overview page (thin _index + manualLink, matching the frame-system convention) and restructure it as a hub with a short section per visualization approach: the 3D scene, Viam Visualization, component data in apps, and time-series dashboards. Fix code against source: api.DrawGeometry takes a DrawGeometryOptions struct, and add the draw service's add/update/remove fan-out to the browser. Improve the frame system description to lead concrete. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdVfSBN5zBzSHStG1HoPDK
1 parent e56850f commit 5f2830c

6 files changed

Lines changed: 118 additions & 133 deletions

File tree

docs/motion-planning/3d-scene/debug-motion-plan.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ type: "docs"
77
description: "Publish a motion plan's trajectory and goals as custom visuals so the 3D scene renders the path, then compare it against obstacles and reach to debug failures."
88
---
99

10-
The **3D SCENE** tab does not show motion plans on its own. It is a static
11-
inspector of the configured frame system and live component poses: it has no
12-
timeline, no scrubber, and no plan playback. To see a plan, the trajectory the
13-
arm will follow, the goals it aims for, and how that path relates to your
14-
obstacles, you publish the plan as **custom visuals** through a world state store
15-
service. The scene then renders the path you can otherwise only read as numbers.
10+
The **3D SCENE** tab is a static inspector: it shows the configured frame system
11+
and live component poses. To see a motion plan, the trajectory the arm will follow,
12+
the goals it aims for, and how that path relates to your obstacles, you publish the
13+
plan as **custom visuals** through a world state store service. The scene then renders
14+
the path you can otherwise only read as numbers.
1615

1716
This page shows how to turn a plan into transforms the scene can draw, and how to
1817
use the rendered path to debug a plan that failed or moved unexpectedly.
@@ -81,7 +80,7 @@ from the trajectory leading to it.
8180

8281
Serve the transforms through a world state store service so the **3D SCENE** tab
8382
renders them. The plan markers stream in alongside the frames and obstacle
84-
geometry the scene already shows. For the service interface, the poll-and-update
83+
geometry the scene already shows. For the service methods, the poll-and-update
8584
loop, and how a module pulls data from its dependencies, see
8685
[Publish visuals from a module](/visualization/publish-visuals-from-a-module/).
8786

@@ -94,11 +93,10 @@ the rest of the scene:
9493
geometry, that is where the planner reports a collision. Check whether the
9594
obstacle is real or an oversized geometry.
9695
- **Does the goal fall outside the arm's reach?** If a goal marker sits far from
97-
any reachable arm configuration, the planner cannot get there. Move the goal or
96+
any reachable arm configuration, the goal is out of reach. Move the goal or
9897
check the frame system.
9998
- **Why the detour?** An unexpected route usually means an obstacle is forcing
100-
the planner around it. Look for geometry between the start and goal you did not
101-
intend to add.
99+
the planner around it. Look for stray geometry between the start and goal.
102100

103101
For checking the obstacle geometry itself, separate from the plan, see
104102
[Verify obstacles](/motion-planning/3d-scene/set-up-obstacle-avoidance/).
@@ -112,7 +110,7 @@ The 3D scene serves three distinct purposes, and it helps to keep them straight:
112110
- **Inspect static frames and geometry**: use the stock scene to check frame
113111
positions and obstacle coverage with no plan involved.
114112
- **Check feasibility**: use `armplanning.PlanMotion` to confirm a goal is
115-
reachable and a path exists before you visualize or execute anything.
113+
reachable and a path exists before you visualize or run anything.
116114

117115
Visualization shows you _what the path looks like_; static inspection shows you
118116
_what the world looks like_; feasibility checking tells you _whether a plan

docs/visualization/_index.md

Lines changed: 1 addition & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -5,90 +5,6 @@ weight: 165
55
layout: "docs"
66
type: "docs"
77
no_list: true
8+
manualLink: "/visualization/overview/"
89
description: "See your machine in 3D: frames, geometries, point clouds, and custom visuals your modules publish."
910
---
10-
11-
Spatial configuration is invisible in JSON. A frame translation of
12-
`{x: 50, y: 0, z: 110}` tells you nothing about whether the gripper actually
13-
sits where the arm needs it, and a list of obstacle geometries tells you nothing
14-
about whether they cover the real workspace. Visualization renders these in 3D
15-
so you can see the spatial model your machine is actually working with, catch
16-
misconfigurations before they cause a failure, and watch live data in context.
17-
18-
This section covers the **3D SCENE** tab in the Viam app, the transforms and
19-
metadata that make up a custom visual, how a module publishes visuals, and the
20-
standalone Viam Visualization app for previewing spatial data outside the app.
21-
22-
## What the 3D scene renders
23-
24-
The **3D SCENE** tab on your machine's page renders an interactive 3D view of
25-
your machine. It draws four kinds of content, each from a different source:
26-
27-
- **Component frames**, from the [frame system](#the-frame-system). Each
28-
configured component appears as a set of coordinate axes at its computed
29-
position.
30-
- **Geometries**, from the components' `frame.geometry` configuration and from
31-
obstacles. These render as translucent shapes the motion planner uses for
32-
collision checking.
33-
- **Point clouds**, from depth cameras, rendered as colored point sets.
34-
- **Custom visuals**, from a [world state store service](/visualization/publish-visuals-from-a-module/).
35-
A module publishes these at runtime, and the scene streams them in as they
36-
change.
37-
38-
The scene reads your saved configuration, and when the machine is online it
39-
connects for live data, so frames move to their current poses and point clouds
40-
update as the cameras report them.
41-
42-
## The frame system
43-
44-
Everything in the scene is positioned by the **frame system**: the single
45-
coordinate tree that records where every component sits relative to every other.
46-
47-
Each component has a frame with a parent, a translation, and an orientation.
48-
A component's frame is defined relative to its parent's frame, not to the world
49-
directly, so the frames form a tree rooted at the fixed **world frame**. A
50-
gripper's frame is a child of the arm's, the arm's is a child of the world, and
51-
so on. Because the tree is connected, the frame system can compute the transform
52-
between any two frames: it walks the path between them, composing each frame's
53-
local transform along the way. When the arm's joints move, every frame below it
54-
in the tree moves with it automatically.
55-
56-
This is what makes the scene meaningful. A point cloud from a wrist camera and an
57-
obstacle defined in world coordinates can be drawn in the same view because the
58-
frame system relates both back to the world frame. The same machinery lets the
59-
motion planner reason about where the gripper is while the arm moves, and lets a
60-
custom visual attach itself to a moving component.
61-
62-
For configuring frames in detail (parent, translation, orientation, geometry),
63-
see the [frame system documentation](/motion-planning/frame-system/).
64-
65-
## Built-in content versus custom visuals
66-
67-
Two kinds of content reach the scene by two different routes, and the difference
68-
determines where you change them:
69-
70-
- **Built-in content** (component frames and configured geometry) comes from
71-
your machine **configuration**. You change it by editing the frame system and
72-
obstacle config. The scene reads it directly.
73-
- **Custom visuals** come from a **module** that implements a world state store
74-
service and publishes transforms at runtime. You change them in **code**, not
75-
config, and the scene streams them in as the module adds, updates, and removes
76-
them.
77-
78-
If a frame or geometry looks wrong, fix the configuration. If a custom visual
79-
looks wrong, the module that publishes it is where to look.
80-
81-
## Topics
82-
83-
{{< cards >}}
84-
{{% card link="/visualization/visuals-and-collisions/" noimage="true" %}}
85-
{{% card link="/visualization/publish-visuals-from-a-module/" noimage="true" %}}
86-
{{% card link="/visualization/drawing-library/" noimage="true" %}}
87-
{{< /cards >}}
88-
89-
## Use the 3D scene with motion planning
90-
91-
{{< cards >}}
92-
{{% card link="/motion-planning/3d-scene/set-up-obstacle-avoidance/" noimage="true" %}}
93-
{{% card link="/motion-planning/3d-scene/debug-motion-plan/" noimage="true" %}}
94-
{{< /cards >}}

docs/visualization/drawing-library.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,34 @@ then push visuals to it from Go with the client API. Reusing an entity ID update
7777
that visual in place; a new ID adds another:
7878

7979
```go
80-
import "github.com/viam-labs/motion-tools/client/api"
81-
82-
// Update in place by reusing the ID; add a new entity with a different ID.
83-
err := api.DrawGeometry(box, api.WithID("obstacle-1"), api.WithColor(red))
80+
import (
81+
"github.com/viam-labs/motion-tools/client/api"
82+
"github.com/viam-labs/motion-tools/draw"
83+
)
84+
85+
// Reuse an ID to update that visual in place; change or omit it to add another.
86+
_, err := api.DrawGeometry(api.DrawGeometryOptions{
87+
ID: "obstacle-1",
88+
Geometry: box,
89+
Color: draw.NewColor(draw.WithName("red")),
90+
})
8491
```
8592

8693
This lets you preview spatial data, a point cloud, a set of detections, a planned
8794
path, straight from a script or test, without deploying a module or connecting
8895
through the Viam app. For setup, the local server, and the full client API, see
8996
the [Viam Visualization documentation](https://viamrobotics.github.io/visualization/).
9097

98+
## How updates reach the browser
99+
100+
The Viam Visualization app runs a **draw service** that the client API calls. The service
101+
exposes `AddEntity`, `UpdateEntity`, and `RemoveEntity` for the changes you push, and a
102+
`StreamEntity` stream the browser subscribes to. When you draw, update, or remove an
103+
entity, the service fans that single change out over `StreamEntity` to the browser, which
104+
applies it incrementally instead of re-rendering the whole scene. This is the same add,
105+
update, and remove model the world state store service uses to feed the in-app 3D scene,
106+
so a busy scene stays in sync as your data changes.
107+
91108
## What's next
92109

93110
- [Publish visuals from a module](/visualization/publish-visuals-from-a-module/):

docs/visualization/overview.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
linkTitle: "Overview"
3+
title: "Visualization"
4+
weight: 1
5+
layout: "docs"
6+
type: "docs"
7+
description: "Ways to visualize a Viam machine: the 3D scene, the standalone Viam Visualization app, component data in apps, and time-series dashboards."
8+
---
9+
10+
There are several approaches to visualizing information in Viam. For things like frames,
11+
robot state, motion, collisions, and perception data, you can use the 3D scene view or an
12+
offline visualizer called Viam Visualization. This data can be retrieved from components
13+
and used in Viam apps or custom user applications. For time series data, services and
14+
modules can push data to the cloud and visualize it with Viam's Teleop workspaces and
15+
dashboards to get live information across a machine or fleet.
16+
17+
## The 3D scene
18+
19+
The **3D SCENE** tab on your machine's page renders an interactive 3D view of your
20+
machine: component frames from the frame system, configured geometries, depth-camera
21+
point clouds, and custom visuals a module publishes at runtime. Use it to check spatial
22+
configuration and watch live data in context.
23+
24+
{{< cards >}}
25+
{{% card link="/visualization/visuals-and-collisions/" noimage="true" %}}
26+
{{% card link="/visualization/publish-visuals-from-a-module/" noimage="true" %}}
27+
{{% card link="/motion-planning/3d-scene/debug-motion-plan/" noimage="true" %}}
28+
{{< /cards >}}
29+
30+
## Viam Visualization
31+
32+
Viam Visualization is a standalone 3D app you run yourself to preview and debug spatial
33+
data from a Go client, without deploying a module or opening the Viam app. It shares the
34+
same `draw` library as the in-app 3D scene, so the visuals you build work either way.
35+
36+
{{< cards >}}
37+
{{% card link="/visualization/drawing-library/" noimage="true" %}}
38+
{{< /cards >}}
39+
40+
## Component data in your applications
41+
42+
Components report spatial data, geometries and poses, through their APIs. Read it with an
43+
SDK and render or process it yourself in a Viam app or a custom application.
44+
45+
{{< cards >}}
46+
{{% card link="/build-apps/" noimage="true" %}}
47+
{{< /cards >}}
48+
49+
## Time series data
50+
51+
Services and modules push readings to the cloud, where you watch them live with Viam's
52+
Teleop workspaces and dashboards across a single machine or a whole fleet.
53+
54+
{{< cards >}}
55+
{{% card link="/monitor/teleop-workspaces/" noimage="true" %}}
56+
{{% card link="/monitor/dashboards/overview/" noimage="true" %}}
57+
{{< /cards >}}

docs/visualization/publish-visuals-from-a-module.md

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,21 @@ shapes, you implement a **world state store service**. A module that implements
1313
this service is a producer: it reads data from other resources, turns that data
1414
into transforms, and streams them to the scene as they change.
1515

16-
This page covers when to implement the service, the interface to implement, how
16+
This page covers when to implement the service, the methods to implement, how
1717
to build transforms with the `draw` library, and the poll-and-update loop that
1818
keeps the scene in sync. The pattern throughout is **pull**: the module depends
1919
on the resources it visualizes and reads their data, rather than having those
2020
resources push into it.
2121

2222
## When to implement a world state store service
2323

24-
Implement one when you want custom visuals in the 3D scene that the default
25-
content cannot show. The scene already draws component frames and configured
26-
geometry, so you do not need a module to see those. You do need one to visualize
27-
anything computed or sensed at runtime: a vision service's detections, a sensor's
28-
obstacle readings, a motion plan's trajectory, or any annotation specific to your
29-
application.
24+
Implement one when you want custom visuals in the 3D scene beyond the default
25+
content. The scene already draws component frames and configured geometry on its
26+
own. A module adds anything computed or sensed at runtime: a vision service's
27+
detections, a sensor's obstacle readings, a motion plan's trajectory, or any
28+
annotation specific to your application.
3029

31-
## Implement the service interface
30+
## Implement the service methods
3231

3332
The world state store service exposes three read methods, which the 3D scene
3433
calls to discover and follow your visuals:
@@ -83,7 +82,7 @@ func buildTransform(o obstacle) (*commonpb.Transform, error) {
8382
```
8483

8584
The library produces standard `commonpb.Transform` values, the same type the
86-
service interface returns, so the transforms you build this way flow straight
85+
service methods return, so the transforms you build this way flow straight
8786
through `ListUUIDs`, `GetTransform`, and `StreamTransformChanges` to the scene.
8887

8988
## Drive a poll-and-update loop
@@ -135,10 +134,10 @@ reads everything else. Data flows one way:
135134
This is why the loop above calls `s.sensor.Readings(...)`: the sensor is a
136135
dependency, and the module pulls from it.
137136

138-
## Visualize a resource that is not a world state store
137+
## Visualize any other resource
139138

140-
A module whose primary job is something else, an arm, a sensor, a planner, does
141-
not push visuals into the scene. Instead, you write a world state store module
139+
A module whose primary job is something else (an arm, a sensor, a planner) stays
140+
focused on that job. To visualize it, you write a separate world state store module
142141
that takes that resource as a dependency and pulls from its existing API:
143142

144143
- a sensor's `Readings`
@@ -163,7 +162,7 @@ func newVisualizer(deps resource.Dependencies, conf resource.Config) (worldstate
163162
## What's next
164163

165164
- [Visuals and collisions](/visualization/visuals-and-collisions/):
166-
what a transform contains and why a visual is not an obstacle.
165+
what a transform contains, and which geometry the planner collision-checks.
167166
- [The drawing library and Viam visualization](/visualization/drawing-library/):
168167
the `draw` primitives and the standalone visualizer app.
169168
- [Frame system](/motion-planning/frame-system/): position the transforms you

docs/visualization/visuals-and-collisions.md

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ title: "Visuals and collisions"
44
weight: 10
55
layout: "docs"
66
type: "docs"
7-
description: "How a Transform defines a custom visual, and why a visual in the scene is not the same as geometry the motion planner avoids."
7+
description: "How a Transform defines a custom visual, and which geometry the motion planner actually collision-checks."
88
---
99

1010
A custom visual in the 3D scene is a `Transform`: a piece of geometry placed
1111
somewhere in the frame system, with styling that controls how it draws. The
1212
world state store service streams these transforms to the scene. This page
13-
covers what a transform contains, how the scene tracks it over time, and the
14-
distinction that trips people up most: a visual you can see is not automatically
15-
an obstacle the motion planner avoids.
13+
covers what a transform contains, how the scene tracks it over time, and the point
14+
that trips people up most: the scene draws your visual, while the planner avoids a
15+
separate collision geometry.
1616

1717
## Anatomy of a transform
1818

@@ -63,27 +63,25 @@ geometry:
6363
- `collision_allowed`: a hint about whether the geometry represents an allowed
6464
collision
6565

66-
These are **visualization attributes**, not planning inputs. They change how a
67-
visual looks in the scene. They do not change what the motion planner does,
68-
including `collision_allowed`: setting it affects how the visual is presented,
69-
not whether the planner treats anything as solid.
66+
These are **visualization attributes**: the scene reads them when it draws the
67+
geometry. They control how the visual looks, including `collision_allowed`, which is
68+
a rendering hint about the visual. The planner reads its solid geometry from the frame
69+
system and `WorldState` instead, so these attributes shape the picture while the
70+
planner's obstacles come from elsewhere.
7071

71-
## A visual is not an obstacle
72+
## The scene draws, the planner collision-checks
7273

73-
This is the distinction to internalize: the geometry on a world state store
74-
transform renders in the 3D scene, but the motion planner does not read the
75-
world state store. Publishing a box to the scene draws a box. It does not add an
76-
obstacle the arm will avoid.
77-
78-
The geometry the planner actually collision-checks comes from two places:
74+
The geometry on a world state store transform renders in the 3D scene: publishing a
75+
box draws a box. The motion planner collision-checks a separate geometry, which it
76+
reads from two places:
7977

8078
- The **frame system**: each component's `frame.geometry`.
8179
- The **`WorldState`** you pass to a `Move` call: obstacles and transforms
8280
supplied for that single planning request.
8381

84-
A transform in the world state store and an obstacle in a `WorldState` are
85-
therefore different things on different paths, even when they describe the same
86-
shape.
82+
A world state store transform and a `WorldState` obstacle travel two paths, each with
83+
its own job: one is drawn in the scene, the other is planned around. The same shape can
84+
take both paths.
8785

8886
## Making a geometry both visible and collision-checked
8987

@@ -95,8 +93,8 @@ you do both, separately:
9593
- **For planning**: add it to the frame system, or include it in the
9694
`WorldState` you pass to `Move`.
9795

98-
There is no single field today that does both. Treat the visual and the
99-
collision geometry as two outputs you produce from the same source data.
96+
Today these are two separate outputs you produce from the same source data: one
97+
transform for the scene, one geometry for the planner.
10098

10199
## What's next
102100

0 commit comments

Comments
 (0)