From 4e915951a5645c521f97ce1cca30fc76c1e0715a Mon Sep 17 00:00:00 2001 From: Brandon Shrewsbury Date: Tue, 14 Jul 2026 10:52:47 -0600 Subject: [PATCH] Motion planning section: code hygiene and style batch from section review Accuracy: - pick-an-object.md: define the world_state the page passes to every Move call (the pick-to-place running example previously failed with NameError); grab() returns what the gripper module reports, not a contact-sensor reading; recommend is_holding_something() instead of a force-feedback API that does not exist (confirmed in Python SDK 0.79.1 against a live v1.0.0 server). - place-an-object.md: SURFACE_HEIGHT is the surface z plus the end-effector-to-object-bottom distance, not half the object height. - move-by-joint-positions.md: max_tcp_speed is a cap; the arm module enforces caps. - constraints.md: orientation_tolerance_degs is optional (default 0); linear constraints below 10 mm/deg disable the cBiRRT fallback (link to How motion planning works); non-adjacent frame pairs. - move-gantry.md: gantries have one or more linear axes; Move plans and executes; removed a safety-guarantee claim. - attach-detach-geometries.md: replace an invented error string with the message a live v1.0.0 server actually returns; fix a comment/code sign mismatch on the attached-box offset. - 3d-scene pages: orientation-vector components are unitless (only th is degrees), stated on three pages. Style and structure: - Em dashes to colons; banned words (surfaces, ships, whitelist, shape-as-vague-noun); negation-first openings flipped positive per house style; personified planner sentences given real actors. - Undefined Go variables get imports or continuation comments (avoid-obstacles, move-gantry, move-with-constraints, move-to-pose). - Prerequisites heading standardized; multiple-waypoints gains a Prerequisites section; constraints reference re-weighted after the how-tos; card order matches the learning path; two frontmatter descriptions shortened under the Hugo limit. --- docs/motion-planning/3d-scene/_index.md | 22 +++++----- .../3d-scene/calibrate-frame-offsets.md | 4 +- docs/motion-planning/3d-scene/edit-frames.md | 12 +++--- .../3d-scene/set-up-obstacle-avoidance.md | 15 ++++--- .../3d-scene/verify-point-cloud-alignment.md | 13 +++--- .../move-an-arm/constraints.md | 19 +++++---- .../move-an-arm/move-by-joint-positions.md | 14 ++++--- .../move-an-arm/move-to-pose.md | 14 +++---- .../move-an-arm/move-with-constraints.md | 6 ++- .../move-an-arm/multiple-waypoints.md | 8 ++++ docs/motion-planning/move-an-arm/overview.md | 11 ++--- .../move-an-arm/pick-an-object.md | 41 +++++++++++++++---- .../move-an-arm/place-an-object.md | 15 +++---- docs/motion-planning/move-gantry.md | 33 +++++++++------ docs/motion-planning/obstacles/_index.md | 2 +- .../obstacles/attach-detach-geometries.md | 24 ++++++----- .../obstacles/avoid-obstacles.md | 37 ++++++++++------- docs/motion-planning/quickstarts/_index.md | 8 ++-- 18 files changed, 183 insertions(+), 115 deletions(-) diff --git a/docs/motion-planning/3d-scene/_index.md b/docs/motion-planning/3d-scene/_index.md index 18241314db..f3f9f7d3e0 100644 --- a/docs/motion-planning/3d-scene/_index.md +++ b/docs/motion-planning/3d-scene/_index.md @@ -9,11 +9,11 @@ description: "Visualize your machine's frame system, geometries, and point cloud --- The **3D SCENE** tab renders your machine's frame system as an interactive 3D visualization on your machine's page in the [Viam app](https://app.viam.com). -Frame configuration is otherwise invisible: a JSON translation of `{x: 50, y: 0, z: 110}` tells you nothing about whether the gripper actually sits where the arm needs it. The **3D SCENE** tab makes that spatial relationship visible so you can catch misconfigurations before a motion plan fails. +It shows the spatial relationships your frame configuration describes: the offset you configured as `{x: 50, y: 0, z: 110}` renders as a gripper 50 mm forward and 110 mm above the arm flange, so you can catch a misplaced frame before a motion plan fails. -The tab reads your machine's configuration and, when the machine is online, connects for live data. Each component's frame appears as a set of coordinate axes positioned by its translation and orientation relative to its parent frame. Attached geometries render as translucent shapes, and point clouds from depth cameras render as colored point sets. +The tab reads your machine's configuration and, when the machine is online, connects for live data. Each component's frame appears as a set of coordinate axes positioned by its translation and orientation relative to its parent frame. -## The interface +## Interface The tab has four areas, each doing a distinct job: the **viewport** renders the scene; the **World panel** and **Details panel** select and inspect entities; the **Dashboard toolbar** changes how the viewport renders. @@ -29,7 +29,7 @@ Click a row to select the entity; its details appear in the Details panel. The panel is draggable and anchors to the top-right of the viewport by default. It includes: -- **world position** (mm) and **world orientation** (deg, as an orientation vector `x / y / z / th`): the entity's absolute pose in the world frame. Read-only. +- **world position** (mm) and **world orientation** (an orientation vector: `x / y / z` unit-vector components, `th` in degrees): the entity's absolute pose in the world frame. Read-only. - **parent frame**: which frame this entity is a child of. Editable when the entity is a configurable frame. - **local position** (mm) and **local orientation** (deg): pose relative to the parent frame. Editable for configurable frames; these correspond to the `translation` and `orientation` in your frame configuration. - **geometry**: four buttons (`None` / `Box` / `Sphere` / `Capsule`) plus **dimensions** (`x / y / z` for Box, `r / l` for Capsule, `r` for Sphere, all in mm). @@ -39,13 +39,13 @@ Entities that can be removed (for example, dropped PCD files) also show a **Remo **Dashboard toolbar** (top-center): Visible buttons, left to right: -- **Orthographic / Perspective** toggle — switch between an orthographic view (no foreshortening) and a perspective view. Keyboard: `C`. -- **Add frames** — opens a floating panel listing components that do not yet have a frame; click a component and then **Add frame** (singular) to attach a default frame to it. See [Edit frames visually](/motion-planning/3d-scene/edit-frames/). -- **Measurement** (ruler icon) — activate to measure distance between two points you pick in the viewport. Click the icon again to clear. -- **Measurement settings** (sliders icon next to the ruler) — toggle `x`, `y`, or `z` under **Enabled axes** to constrain the second point to the enabled axes of the first. -- **AI Scene Builder** — opens a prompt panel for editing frames with natural language. See [Edit frames with AI](/motion-planning/3d-scene/edit-frames/#edit-frames-with-ai). -- **Logs** — shows a count badge for errors/warnings from the scene renderer. -- **Settings** (gear icon) — opens the Settings panel. +- **Orthographic / Perspective** toggle: switches between an orthographic view (no foreshortening) and a perspective view. Keyboard: `C`. +- **Add frames**: opens a floating panel listing components that do not yet have a frame; click a component and then **Add frame** (singular) to attach a default frame to it. See [Edit frames visually](/motion-planning/3d-scene/edit-frames/). +- **Measurement** (ruler icon): activate to measure distance between two points you pick in the viewport. Click the icon again to clear. +- **Measurement settings** (sliders icon next to the ruler): toggle `x`, `y`, or `z` under **Enabled axes** to constrain the second point to the enabled axes of the first. +- **AI Scene Builder**: opens a prompt panel for editing frames with natural language. See [Edit frames with AI](/motion-planning/3d-scene/edit-frames/#edit-frames-with-ai). +- **Logs**: shows a count badge for errors/warnings from the scene renderer. +- **Settings** (gear icon): opens the Settings panel. ## Navigation controls diff --git a/docs/motion-planning/3d-scene/calibrate-frame-offsets.md b/docs/motion-planning/3d-scene/calibrate-frame-offsets.md index 383100f136..756766efae 100644 --- a/docs/motion-planning/3d-scene/calibrate-frame-offsets.md +++ b/docs/motion-planning/3d-scene/calibrate-frame-offsets.md @@ -7,7 +7,7 @@ type: "docs" description: "Verify and adjust the spatial relationship between components using the 3D scene and measurement tool." --- -When you configure a camera on an arm, or a sensor on a base, the frame system needs the exact translation and orientation between the two components. A 15 mm error in a camera offset places a detected object 15 mm off; the arm then reaches for the wrong spot, or the point cloud sits behind the table instead of on it. The **3D SCENE** tab lets you verify offsets visually and measure distances directly, so you can catch these errors before they produce bad motion. +When you configure a camera on an arm, or a sensor on a base, the frame system needs the exact translation and orientation between the two components. A 15 mm error in a camera offset places a detected object 15 mm off, and the arm reaches for the wrong spot. The **3D SCENE** tab lets you verify offsets visually and measure distances directly, so you can catch these errors before you run a plan. ## Prerequisites @@ -50,7 +50,7 @@ Select a component and compare its coordinate axes in 3D to the physical compone - A camera's Z axis (blue) should point forward along the optical axis. - An arm's axes should follow the manufacturer's convention. -If the axes are rotated relative to what you expect, adjust the orientation vector in the frame configuration. The Details panel displays it as (`x`, `y`, `z`, `th` in degrees). +If the axes are rotated relative to what you expect, adjust the orientation vector in the frame configuration. The Details panel displays it as (`x`, `y`, `z` as unit-vector components and `th` in degrees). ### 5. Verify with a point cloud diff --git a/docs/motion-planning/3d-scene/edit-frames.md b/docs/motion-planning/3d-scene/edit-frames.md index abf3fc8143..3e1caf1468 100644 --- a/docs/motion-planning/3d-scene/edit-frames.md +++ b/docs/motion-planning/3d-scene/edit-frames.md @@ -4,12 +4,12 @@ title: "Edit frames visually" weight: 50 layout: "docs" type: "docs" -description: "Add, edit, and attach geometry to frames directly in the 3D scene instead of editing JSON configuration." +description: "Add, edit, and attach geometry to frames directly in the 3D scene." --- The **3D SCENE** tab can serve as a configuration editor: you can add, move, re-parent, and reshape frames without writing JSON. -Visual editing is most useful while you are still figuring out where things go. Typing coordinates into JSON and reloading the 3D view to check them is slow; editing in the viewport and seeing the result immediately is faster. The trade-off is that the visual editor writes the same JSON fields through a smaller surface area, so it is less suited to bulk changes or cross-machine-part frames. Changes flow back to the machine configuration, and the app surfaces an unsaved-changes banner on the CONFIGURE tab where you save them with **Save** or `⌘/Ctrl+S`. +Visual editing is most useful while you are still figuring out where things go. Edits render in the viewport as you type, so you can position a frame by eye and read off the values. The trade-off is that the visual editor edits only a subset of the frame JSON (translation, orientation vector, and a single geometry), so it is less suited to bulk changes or cross-machine-part frames. ## Prerequisites @@ -30,7 +30,7 @@ You can then reposition it using the Details panel. 1. Select the component in the **World** panel on the upper-left, or by clicking it in the 3D viewport. 2. The Details panel (upper-right) shows the entity's current values. There is no edit-mode toggle; for any configurable frame, the **local position** and **local orientation** fields are editable inputs. 3. Edit the position values (`x`, `y`, `z` in mm) to set the translation relative to the parent frame. -4. Edit the orientation values (`x`, `y`, `z`, `th` in degrees) to set the orientation as an orientation vector. +4. Edit the orientation values (`x`, `y`, `z` unit-vector components and `th` in degrees) to set the orientation as an orientation vector. Changes appear immediately in the 3D viewport as you type. The values you enter here correspond directly to the `translation` and `orientation` fields in the frame JSON configuration. @@ -86,10 +86,10 @@ After the AI applies changes, save or discard buttons appear in the **3D SCENE** Visual editing covers most cases, but a few are faster in JSON: -- **Bulk changes** (renaming many frames, regenerating a layout) — JSON +- **Bulk changes** (renaming many frames, regenerating a layout): JSON edits are easier in a text editor. -- **Frames that reference components on a different machine part** — +- **Frames that reference components on a different machine part**: the visual editor's parent dropdown only shows local frames. - **Complex orientations** (rotations expressed in `axis_angles` or - `quaternion` rather than `ov_degrees`) — the visual editor surfaces + `quaternion` rather than `ov_degrees`): the visual editor shows only the orientation vector form. diff --git a/docs/motion-planning/3d-scene/set-up-obstacle-avoidance.md b/docs/motion-planning/3d-scene/set-up-obstacle-avoidance.md index f74a90004e..6406bbfa76 100644 --- a/docs/motion-planning/3d-scene/set-up-obstacle-avoidance.md +++ b/docs/motion-planning/3d-scene/set-up-obstacle-avoidance.md @@ -7,7 +7,7 @@ type: "docs" description: "Visualize and adjust obstacle geometry so the motion planner routes around physical objects." --- -The motion planner avoids obstacles only if it knows about them, and it knows about them only as geometries you define: boxes, spheres, capsules, or cylinders positioned in the frame system. That definition is invisible in JSON. A box specified as `{x: 800, y: 1200, z: 20}` at some parent-relative translation either covers the table or it doesn't, and you can't tell which from the numbers. The **3D SCENE** tab lets you see what the planner sees, so you can check coverage before running a plan. +The motion planner avoids only the obstacles you define as geometries: boxes, spheres, capsules, or cylinders positioned in the frame system. The **3D SCENE** tab draws each geometry at its configured position and size, so you can see whether a box specified as `{x: 800, y: 1200, z: 20}` actually covers the table before you run a plan. ## Prerequisites @@ -35,6 +35,8 @@ Click an obstacle in the scene or in the World panel to see its details: - **local position** (mm): the geometry's center relative to its parent frame. - **parent frame**: which frame the geometry is attached to. +Cylinders configured in JSON render in the scene; the geometry buttons cover box, sphere, and capsule only. + ### 3. Compare geometry to physical objects Orbit the scene to view obstacles from different angles. @@ -67,15 +69,16 @@ After defining obstacles, run through this checklist in the **3D SCENE** tab: ## Dynamic obstacles -Static obstacles in configuration cover fixed workspace objects. For objects -that move, pass geometry at runtime through the `WorldState` parameter of the -`Move` request. See +The scene draws only geometries from saved configuration; obstacles passed +in a `Move` call's `WorldState` are not drawn. Static obstacles in +configuration cover fixed workspace objects. For objects that move, pass +geometry at runtime through the `WorldState` parameter of the `Move` +request. See [Static vs dynamic obstacles](/motion-planning/obstacles/overview/#static-vs-dynamic-obstacles) for the distinction and [Plan collision-free paths](/motion-planning/obstacles/avoid-obstacles/) for code examples. -Dynamic obstacles passed to a single `Move` call are not drawn in the **3D -SCENE** tab. To verify a dynamic obstacle's position visually, log its pose +To verify a dynamic obstacle's position visually, log its pose from your code or temporarily add a static geometry with the same dimensions to a component frame. diff --git a/docs/motion-planning/3d-scene/verify-point-cloud-alignment.md b/docs/motion-planning/3d-scene/verify-point-cloud-alignment.md index 6cc06052a6..c5101a2bab 100644 --- a/docs/motion-planning/3d-scene/verify-point-cloud-alignment.md +++ b/docs/motion-planning/3d-scene/verify-point-cloud-alignment.md @@ -11,7 +11,7 @@ aliases: Depth cameras produce point clouds: sets of 3D points that represent the surfaces the camera sees. The **3D SCENE** tab renders those points in your frame system, so you can check two things at once: the camera is producing usable data, and the data lines up with the rest of the workspace. -Misalignment usually means one of two things: the camera's frame offset is wrong, or the camera itself has a problem. Finding that out now, before a motion plan runs or an ML detector ships, costs minutes; finding it out later costs a day of debugging a downstream pipeline. +Misalignment usually means one of two things: the camera's frame offset is wrong, or the camera itself has a problem. Catch it before a motion plan runs or you train an ML detector on the data. ## Prerequisites @@ -62,15 +62,14 @@ Adjust the camera's configuration, mounting angle, or lighting conditions to add ## What's next -The motion planner does not consume raw point clouds directly: a vision -service with a 3D segmenter (such as the +The motion planner accepts bounded geometries, not raw point clouds: a +vision service with a 3D segmenter (such as the [`obstacles_pointcloud` module](https://app.viam.com/module/viam/obstacles-pointcloud)) -turns point clouds into bounded 3D objects, and you feed those to the -planner. +turns point clouds into bounded 3D objects that you pass to the planner. - [Pick an object](/motion-planning/move-an-arm/pick-an-object/): - uses `GetObjectPointClouds` to localize the target the arm should - grasp on a single `Move` call. + uses `GetObjectPointClouds` to localize the grasp target, then moves + to it with a single `Move` call. - [Define obstacles](/motion-planning/obstacles/): the geometry types the motion planner accepts, including the [`WorldState.obstacles`](/motion-planning/obstacles/avoid-obstacles/) diff --git a/docs/motion-planning/move-an-arm/constraints.md b/docs/motion-planning/move-an-arm/constraints.md index 5474a1a0aa..aa58c81e38 100644 --- a/docs/motion-planning/move-an-arm/constraints.md +++ b/docs/motion-planning/move-an-arm/constraints.md @@ -1,7 +1,7 @@ --- linkTitle: "Configure constraints" title: "Configure motion constraints" -weight: 5 +weight: 25 layout: "docs" type: "docs" description: "Restrict how the arm moves between poses using linear, orientation, and collision constraints." @@ -52,9 +52,9 @@ Forces the end effector to maintain a consistent orientation throughout the motion. Use this when the end effector must stay level or keep a fixed orientation (for example, carrying a liquid). -| Parameter | Type | Description | -| ---------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `orientation_tolerance_degs` | float (required) | Maximum orientation deviation, in degrees, for orientations that fall outside the start-to-goal box. A value of 0 rejects any deviation outside that box. | +| Parameter | Type | Description | +| ---------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `orientation_tolerance_degs` | float (optional, default 0) | Maximum orientation deviation, in degrees, for orientations that fall outside the start-to-goal box. A value of 0 rejects any deviation outside that box. | The planner checks each orientation vector component (`OX`, `OY`, `OZ`, `Theta`) against the start and goal independently. If every component of @@ -83,8 +83,9 @@ short move gets a tight tolerance; a long move gets a proportionally larger one. ### CollisionSpecification Allows specific pairs of frames to collide during planning. By default, the -planner rejects any path where any two frames collide. CollisionSpecification -lets you whitelist specific pairs. +planner rejects any path where any two non-adjacent frames collide. +CollisionSpecification lets you list specific pairs the planner allows to +collide. | Parameter | Type | Description | | --------- | ------------------- | ---------------------------------------------------- | @@ -130,7 +131,11 @@ and the failure rate. - **Tight tolerances** (small `line_tolerance_mm` or `orientation_tolerance_degs`) increase planning time and may cause the planner to fail if no path exists - within the tolerance. + within the tolerance. A `LinearConstraint` also switches planning to + Cartesian-step subdivision, and tolerances below 10 mm or 10 degrees disable + the cBiRRT fallback entirely: each step then needs a direct straight-line IK + solution, or planning fails. See + [How motion planning works](/motion-planning/how-planning-works/). - **Start with larger tolerances** and tighten only as needed. A 10 mm linear tolerance is easier to satisfy than a 1 mm tolerance. - **Combining constraints** multiplies the difficulty. Use the minimum set of diff --git a/docs/motion-planning/move-an-arm/move-by-joint-positions.md b/docs/motion-planning/move-an-arm/move-by-joint-positions.md index 7185107ad4..50924784ce 100644 --- a/docs/motion-planning/move-an-arm/move-by-joint-positions.md +++ b/docs/motion-planning/move-an-arm/move-by-joint-positions.md @@ -28,7 +28,7 @@ commanded configuration makes the arm swing through the table or your workspace fixture, the arm will swing through the table. Joint-space is for configurations you have already verified safe. -## Before you start +## Prerequisites - A configured arm component and an SDK client. - You know the joint angles you want. For a 6-DOF arm, this is six @@ -178,10 +178,11 @@ from Python; the arm uses its module's default speed profile. | `max_acc_degs_per_sec2` | `double` (optional) | Uniform acceleration cap across every joint, in degrees per second squared. | | `max_vel_degs_per_sec_joints` | `[]double` (repeated) | Per-joint velocity caps. Length must match the arm's degrees of freedom. Overrides the uniform cap when set. | | `max_acc_degs_per_sec2_joints` | `[]double` (repeated) | Per-joint acceleration caps. Length must match the arm's degrees of freedom. Overrides the uniform cap when set. | -| `max_tcp_speed` | `double` (optional) | Maximum speed of the tool center point in meters per second. The arm moves as fast as possible up to this limit. | +| `max_tcp_speed` | `double` (optional) | Caps the tool center point's speed, in meters per second. Unset means no cap. | -All fields are optional ceilings. Any combination may be set. Every -constraint that is set is respected at every point along the trajectory. +All fields are optional ceilings. Any combination may be set. Each cap +you set applies along the whole trajectory; the arm module is responsible +for enforcing it. Per-joint fields take precedence over global fields. Pass `nil` options to use the module's default motion profile. @@ -218,8 +219,9 @@ logger.Infof("joint positions (radians): %v", current) {{% /tab %}} {{< /tabs >}} -Pair `GetJointPositions` with `MoveToJointPositions` to capture a pose -by hand (teach-by-demonstration) and replay it programmatically. +Pair `GetJointPositions` with `MoveToJointPositions` to capture a +configuration by hand (teach-by-demonstration) and replay it +programmatically. ## Joint-space moves compared to motion.Move diff --git a/docs/motion-planning/move-an-arm/move-to-pose.md b/docs/motion-planning/move-an-arm/move-to-pose.md index f2c343a12c..181d0ed54e 100644 --- a/docs/motion-planning/move-an-arm/move-to-pose.md +++ b/docs/motion-planning/move-an-arm/move-to-pose.md @@ -4,7 +4,7 @@ title: "Move an arm to a target pose" weight: 10 layout: "docs" type: "docs" -description: "Use the motion service to move a robot arm to a position in 3D space." +description: "Use the motion service to move a robot arm to a target pose (position and orientation) in 3D space." aliases: - /operate/mobility/move-arm/ - /operate/mobility/move-arm/arm-motion/ @@ -17,10 +17,8 @@ aliases: - /motion-planning/motion-how-to/move-arm-to-pose/ --- -A robot arm needs to reach a specific position and orientation in 3D space, but -joint angles alone do not tell you whether the end effector ends up where you -want it, and a naive joint trajectory may swing through the table or the back -wall. The motion service takes a target pose, solves inverse kinematics, and +A naive joint trajectory can swing the arm through the table on the way to a +target. The motion service takes a target pose, solves inverse kinematics, and returns a collision-free path that respects the frame system and any obstacles you declare. @@ -65,6 +63,8 @@ import ( "github.com/golang/geo/r3" ) +// machine is a connected robot client; copy the connection snippet from +// your machine's CONNECT tab in the Viam app. motionService, err := motion.FromProvider(machine, "builtin") if err != nil { logger.Fatal(err) @@ -142,7 +142,7 @@ fmt.Println("Arm moved to target pose") ### 4. Move with obstacle avoidance To make the planner avoid things in the workspace, pass a `WorldState` that -lists them. The planner checks every frame in the scene against every obstacle +lists them. The planner checks the robot's geometries against every obstacle you include. {{< tabs >}} @@ -269,7 +269,7 @@ viam machines part motion set-pose --part "my-machine-main" --component "my-arm" - Verify the `reference_frame` on your destination. The point `(x=300, y=200, z=400)` in `"world"` and the same point in `"my-arm"` name - different physical locations; whichever one you set, the arm drives to. + different physical locations; the arm drives to whichever one you set. - Check the frame system configuration. Incorrect translations or orientations shift the target. - Read the arm's position before and after to see what actually changed. diff --git a/docs/motion-planning/move-an-arm/move-with-constraints.md b/docs/motion-planning/move-an-arm/move-with-constraints.md index d97e332254..fb005e6a7b 100644 --- a/docs/motion-planning/move-an-arm/move-with-constraints.md +++ b/docs/motion-planning/move-an-arm/move-with-constraints.md @@ -59,6 +59,10 @@ await motion_service.move( {{% /tab %}} {{% tab name="Go" %}} +The Go tabs on this page continue from +[Move an arm to a pose](/motion-planning/move-an-arm/move-to-pose/): +`motionService` and `destination` are defined there. + ```go import "go.viam.com/rdk/motionplan" @@ -157,7 +161,7 @@ constraints := &motionplan.Constraints{ ### 4. Use proportional tolerances -`PseudolinearConstraint` scales the tolerance with the motion distance. Useful +`PseudolinearConstraint` scales the tolerance with the motion distance. Use it when the same code handles both short and long moves. {{< tabs >}} diff --git a/docs/motion-planning/move-an-arm/multiple-waypoints.md b/docs/motion-planning/move-an-arm/multiple-waypoints.md index 9731e50cb8..2488feda43 100644 --- a/docs/motion-planning/move-an-arm/multiple-waypoints.md +++ b/docs/motion-planning/move-an-arm/multiple-waypoints.md @@ -26,6 +26,13 @@ than over the service API. For the difference between planning in process and calling the service, see [Verify a motion plan](/motion-planning/verify-a-plan/). {{% /alert %}} +## Prerequisites + +- A running machine with an arm component configured. +- A [frame system](/motion-planning/frame-system/) configured for the arm. +- A Go program or module that imports the RDK. Planning runs in-process, + not over the service API. + ## Why route through ordered goals One multi-goal plan differs from chaining separate `Move` calls: @@ -101,6 +108,7 @@ goals := []*armplanning.PlanState{ }, nil), } +// second return is plan metadata plan, _, err := armplanning.PlanMotion(ctx, logger, &armplanning.PlanRequest{ FrameSystem: fs, StartState: startState, diff --git a/docs/motion-planning/move-an-arm/overview.md b/docs/motion-planning/move-an-arm/overview.md index e51bd94129..0ca695cbc0 100644 --- a/docs/motion-planning/move-an-arm/overview.md +++ b/docs/motion-planning/move-an-arm/overview.md @@ -14,16 +14,17 @@ Viam exposes three ways to command an arm. Three questions sort them: 1. **What do you know about the destination?** A Cartesian target (a pose in space) calls for the motion service. A specific joint configuration calls for direct joint commands. -2. **Does the shape of the motion matter, or only the endpoint?** If you +2. **Does the path matter, or only the endpoint?** If you need a straight line, a fixed orientation, or any other rule about the path itself, you need constraints. 3. **Do you want obstacle avoidance and IK picked for you, or fine - manual control?** The motion service handles both; direct joint - commands skip both. + manual control?** The motion service picks the IK solution and plans + around obstacles for you; direct joint commands execute exactly the + angles you send. | Pattern | Input | Obstacle avoidance | Path-shape control | When to pick | | -------------------------------------------------------------------------------- | ------------------------ | ------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| [Move to a pose](/motion-planning/move-an-arm/move-to-pose/) | Cartesian target | Yes | No | You know where the end effector needs to go and want the planner to figure out how. | +| [Move to a pose](/motion-planning/move-an-arm/move-to-pose/) | Cartesian target | Yes | No | You know where the end effector needs to go and want the planner to choose the path. | | [Move with constraints](/motion-planning/move-an-arm/move-with-constraints/) | Cartesian target + rules | Yes | Yes | The shape of the motion matters (straight-line tool path, level end effector). | | [Move by joint positions](/motion-planning/move-an-arm/move-by-joint-positions/) | Joint angles | No | Direct | You know the joint angles, need predictable motion between known configurations, or want to avoid the planner picking an unexpected IK solution. | @@ -53,9 +54,9 @@ from the table above. ## How-tos {{< cards >}} -{{% card link="/motion-planning/move-an-arm/constraints/" noimage="true" %}} {{% card link="/motion-planning/move-an-arm/move-to-pose/" noimage="true" %}} {{% card link="/motion-planning/move-an-arm/move-with-constraints/" noimage="true" %}} +{{% card link="/motion-planning/move-an-arm/constraints/" noimage="true" %}} {{% card link="/motion-planning/move-an-arm/move-by-joint-positions/" noimage="true" %}} {{% card link="/motion-planning/move-an-arm/multiple-waypoints/" noimage="true" %}} {{% card link="/motion-planning/move-an-arm/pose-clouds/" noimage="true" %}} diff --git a/docs/motion-planning/move-an-arm/pick-an-object.md b/docs/motion-planning/move-an-arm/pick-an-object.md index 0c21848969..ace5595c25 100644 --- a/docs/motion-planning/move-an-arm/pick-an-object.md +++ b/docs/motion-planning/move-an-arm/pick-an-object.md @@ -19,6 +19,9 @@ steps, calls out the common places things go wrong, and leaves the result ready for the [placement](/motion-planning/move-an-arm/place-an-object/) half. +The code examples on this page are Python. The Go flow is the same: the +motion, vision, and gripper APIs take the same arguments in both SDKs. + ## Prerequisites - Arm with [frame system](/motion-planning/frame-system/) and @@ -37,7 +40,9 @@ Detection tells you _which_ object the camera sees; localization tells you _where_ it is in 3D. For grasping, you need both. 2D detections from `GetDetections` are not enough on their own: use `GetObjectPointClouds`, which returns 3D geometries in the camera's frame, then `TransformPose` the result -into the world frame so the motion service can plan against the pose. +into the world frame so the motion service can plan against the pose. In the +code below, the detection is a presence check: it confirms the camera sees an +object before you commit to 3D localization. {{< tabs >}} {{% tab name="Python" %}} @@ -52,7 +57,7 @@ vision = VisionClient.from_robot(machine, "my-detector") detections = await vision.get_detections_from_camera("my-camera") if not detections: print("No objects detected") - exit() + return target = detections[0] print(f"Detected: {target.class_name} ({target.confidence:.2f})") @@ -87,13 +92,33 @@ descent. The two-motion approach gives the planner a clean path that does not brush obstacles on the way down, and it makes the grasp itself nearly deterministic since only one axis is moving at the end. +Every `move()` call in this guide passes the same `world_state`: here, a +single 800 mm x 600 mm x 40 mm table box in the world frame. Extend it with +the rest of the obstacles from your prerequisites. + {{< tabs >}} {{% tab name="Python" %}} ```python from viam.services.motion import MotionClient +from viam.proto.common import ( + Vector3, RectangularPrism, Geometry, + GeometriesInFrame, WorldState +) + motion_service = MotionClient.from_robot(machine, "builtin") +# The table the arm is mounted on: an 800mm x 600mm x 40mm box +# whose top surface is at z=0 in the world frame. +table = Geometry( + center=Pose(x=0, y=0, z=-20), + box=RectangularPrism(dims_mm=Vector3(x=800, y=600, z=40)), + label="table" +) +world_state = WorldState(obstacles=[ + GeometriesInFrame(reference_frame="world", geometries=[table]) +]) + # Pre-grasp: 100mm above the object, end effector pointing down pre_grasp = PoseInFrame( reference_frame="world", @@ -155,10 +180,10 @@ print("At grasp position") The grasp itself closes the gripper on the object, confirms contact, and lifts straight up before planning any further motion. `gripper.grab()` returns a -boolean: true if the gripper's contact sensor confirms closure, false if it -closed but sensed nothing. Treat a false return as "try again with a different -approach": pressing onward with an empty gripper wastes time and can damage -downstream setups. +boolean: `True` if the gripper module reports it grabbed something, `False` +if it closed without detecting an object. Treat a `False` return as "try +again with a different approach": pressing onward with an empty gripper +wastes time and can damage downstream setups. {{< tabs >}} {{% tab name="Python" %}} @@ -204,8 +229,8 @@ print("Object lifted") reported top surface prevents the gripper from crashing into it when depth is off. - **Confirm the grasp before moving.** `gripper.grab()`'s boolean return is - fast but not infallible. For fragile or expensive objects, poll force - feedback or `is_moving` for a second after closure to catch slippage before + fast but not infallible. For fragile or expensive objects, call + `is_holding_something()` a moment after closing to catch slippage before you lift. ## What's next diff --git a/docs/motion-planning/move-an-arm/place-an-object.md b/docs/motion-planning/move-an-arm/place-an-object.md index 7fb1e5d65d..f0ac2fcecf 100644 --- a/docs/motion-planning/move-an-arm/place-an-object.md +++ b/docs/motion-planning/move-an-arm/place-an-object.md @@ -56,10 +56,11 @@ await motion_service.move( ### 2. Descend to the placement surface The descent pose puts the object where you want it to end up. -`SURFACE_HEIGHT` is the world-frame z of the placement surface plus half the -object's height (roughly: you want the bottom of the object touching the -surface at release). For a known surface, measure once and hard-code it. For -a detected surface, set it from the vision result. +`SURFACE_HEIGHT` is the world-frame z of the placement surface plus the +distance from the arm's end effector to the bottom of the held object (the +grasp offset plus the part of the object below the grasp), so the object's +bottom touches the surface at release. For a known surface, measure once and +hard-code it. For a detected surface, set it from the vision result. {{< tabs >}} {{% tab name="Python" %}} @@ -68,7 +69,7 @@ a detected surface, set it from the vision result. # Place: at the surface. # Set z to the height of the placement surface in your workspace. # For example, if you detected the target location, use its z coordinate. -SURFACE_HEIGHT = 50 # mm: world-frame z of the surface plus half the object's height +SURFACE_HEIGHT = 50 # mm: surface z plus the end-effector-to-object-bottom distance place_pose = PoseInFrame( reference_frame="world", pose=Pose( @@ -129,8 +130,8 @@ print("Retreated from placement") and y as the place pose and only the z raised. - **Update `WorldState` after release.** The placed object is now a static obstacle. If the next motion passes near the placement location, add a - `Geometry` matching the placed object to `WorldState.obstacles` so the - planner routes around it. + `Geometry` matching the placed object to a `GeometriesInFrame` entry in + `WorldState.obstacles` so the planner routes around it. ## What's next diff --git a/docs/motion-planning/move-gantry.md b/docs/motion-planning/move-gantry.md index a58b3ef260..208c62f379 100644 --- a/docs/motion-planning/move-gantry.md +++ b/docs/motion-planning/move-gantry.md @@ -10,8 +10,9 @@ aliases: - /motion-planning/motion-how-to/move-gantry/ --- -A gantry is a Cartesian robot: three linear axes, no joint-angle to pose -conversion to do. Most gantry motion is a direct axis command and nothing +A gantry moves along one or more linear axes, so an axis position is already +a Cartesian coordinate: no joint-angle conversion needed. +Most gantry motion is a direct axis command and nothing more. But if the gantry shares its workspace with obstacles, carries a payload that must avoid something, or moves alongside another machine, the motion service plans a collision-aware path the same way it does for an arm. This @@ -20,9 +21,9 @@ guide covers both. ## Direct axis control Use the gantry component API for direct moves to specific positions. -Direct axis control bypasses the motion service: there is no obstacle -checking, no IK, no path planning. Use it when you know the path is -clear and you want the move to happen now. Read current state first +Direct axis control skips the motion service: the gantry drives straight +to the target positions without collision checking. Use it when you know +the path is clear and you want the move to happen now. Read current state first (`get_position`, `get_lengths`) so you know where each axis sits and how much travel is available. @@ -89,13 +90,13 @@ if err != nil { ## Motion-service planning -The motion service treats a gantry the same way it treats an arm: you pass -a target pose and the planner returns a collision-free path that respects -the obstacles in the frame system and any `WorldState` you supply. Planning -takes longer than a direct move (a planning call before execution), but it -is the only safe choice when obstacles share the workspace, when you need -to coordinate the gantry with other components, or when you want the same -planning API across every machine in your fleet. +The motion service treats a gantry the same way it treats an arm. You pass +a target pose; the motion service plans a collision-free path that respects +the obstacles in the frame system and any `WorldState` you supply, and moves +the gantry along it. Planning takes longer than a direct move (a planning +call before execution). Plan through the motion service when obstacles share +the workspace, when you coordinate the gantry with other components, or when +you want one planning API across your fleet. {{< tabs >}} {{% tab name="Python" %}} @@ -121,6 +122,14 @@ await motion_service.move( {{% tab name="Go" %}} ```go +import ( + "github.com/golang/geo/r3" + "go.viam.com/rdk/referenceframe" + "go.viam.com/rdk/services/motion" + "go.viam.com/rdk/spatialmath" +) + +// motionService follows the same setup as Move an arm to a pose. destination := referenceframe.NewPoseInFrame("world", spatialmath.NewPose( r3.Vector{X: 200, Y: 300, Z: 100}, diff --git a/docs/motion-planning/obstacles/_index.md b/docs/motion-planning/obstacles/_index.md index 8f2edd590e..e54d91b846 100644 --- a/docs/motion-planning/obstacles/_index.md +++ b/docs/motion-planning/obstacles/_index.md @@ -6,5 +6,5 @@ layout: "docs" type: "docs" no_list: true manualLink: "/motion-planning/obstacles/overview/" -description: "Define collision geometry so the motion planner computes safe, collision-free paths." +description: "Define collision geometry so the motion planner computes collision-free paths." --- diff --git a/docs/motion-planning/obstacles/attach-detach-geometries.md b/docs/motion-planning/obstacles/attach-detach-geometries.md index e97d3fe4c6..9b5f0643ac 100644 --- a/docs/motion-planning/obstacles/attach-detach-geometries.md +++ b/docs/motion-planning/obstacles/attach-detach-geometries.md @@ -24,15 +24,15 @@ releases dynamically. ## Before you start -- A working motion service call (`motion.Move` for arms, a - grasp-capable gripper configured on the machine). +- A working motion service call (`motion.Move` for an arm). +- A grasp-capable gripper configured on the machine. - The grasped object's rough dimensions. You can approximate with a box, sphere, or capsule. - The offset between the gripper frame origin and the object's center (usually close to the gripper's tool center point when the object is held centered). -## The Transform message +## Transform message fields `WorldState.transforms` is a list of `Transform` entries. Each entry defines a new frame and optionally attaches a geometry to it: @@ -70,7 +70,8 @@ from viam.proto.common import ( Geometry, GeometriesInFrame, Transform, WorldState, ) -# Object: a 80mm x 80mm x 100mm box centered 50mm below the gripper origin. +# Object: an 80 mm x 80 mm x 100 mm box, offset 50 mm along the +# gripper frame's +z axis (toward the fingers). grasped_object = Transform( reference_frame="grasped-object", pose_in_observer_frame=PoseInFrame( @@ -101,6 +102,7 @@ world_state = WorldState( ) # Use world_state on every Move call while the object is held. +# destination from your existing Move call await motion_service.move( component_name="my-arm", destination=destination, @@ -120,7 +122,8 @@ import ( "go.viam.com/rdk/services/motion" ) -// Object: a 80mm x 80mm x 100mm box centered 50mm below the gripper origin. +// Object: an 80 mm x 80 mm x 100 mm box, offset 50 mm along the +// gripper frame's +z axis (toward the fingers). grasped, err := spatialmath.NewBox( spatialmath.NewZeroPose(), r3.Vector{X: 80, Y: 80, Z: 100}, @@ -159,6 +162,7 @@ if err != nil { logger.Fatal(err) } +// destination from your existing Move call _, err = motionService.Move(ctx, motion.MoveReq{ ComponentName: "my-arm", Destination: destination, @@ -190,9 +194,6 @@ await motion_service.move( ) ``` -The planner's view of the world for this and subsequent calls does -not include the grasped object. - ## Allow contact while grasping During the grasp motion itself, the gripper must come into contact @@ -252,10 +253,11 @@ includes the transform) on every call while the object is held. {{< /expand >}} -{{< expand "Error: unknown parent frame for transform" >}} +{{< expand "Error: Cannot construct frame system. Some parts are not linked to the world frame." >}} -The `pose_in_observer_frame.reference_frame` must name a frame that -exists in the machine's frame system. A typo, a component that is +The error lists the unlinked part names, including your transform's +frame. The `pose_in_observer_frame.reference_frame` must name a frame +that exists in the machine's frame system. A typo, a component that is missing, or a component whose frame is not configured will all produce this error. Run [`viam machines part motion print-config`](/motion-planning/reference/cli-commands/#print-config) diff --git a/docs/motion-planning/obstacles/avoid-obstacles.md b/docs/motion-planning/obstacles/avoid-obstacles.md index e2986332df..d45acb37be 100644 --- a/docs/motion-planning/obstacles/avoid-obstacles.md +++ b/docs/motion-planning/obstacles/avoid-obstacles.md @@ -9,12 +9,10 @@ aliases: - /motion-planning/motion-how-to/avoid-obstacles/ --- -A motion plan that ignores the table, the back wall, and the fixture on the -bench next to the arm will collide on its way to the target. The motion service -can plan around these obstacles, but only if you describe them in a -`WorldState`. This guide walks through building a `WorldState` for a typical -bench setup, running a plan against it, and verifying that the planner actually -routes around each obstacle. +The motion service plans around the table, the back wall, and the fixture on +the bench when you describe them in a `WorldState`. This guide builds a +`WorldState` for a typical bench setup, runs a plan against it, and verifies +that the planner routes around each obstacle. This page covers dynamic `WorldState` obstacles passed at call time. For permanent fixtures (the table the arm is bolted to, the back wall, a @@ -25,17 +23,18 @@ of re-sending it on every `Move`. See ## Prerequisites - An arm or gantry configured on a machine. -- [Frame system](/motion-planning/frame-system/) configured. -- [Obstacles concept](/motion-planning/obstacles/) understood. +- Configure a [frame system](/motion-planning/frame-system/). +- Read the [obstacles overview](/motion-planning/obstacles/). ## Steps ### 1. Describe your workspace obstacles -A `WorldState` is a list of geometries expressed in a reference frame. Each -geometry needs a shape (box, capsule, or sphere), a pose relative to the frame, -and a label. Model every significant obstacle: surfaces the arm can press into, -fixtures it can strike, and vertical obstructions like posts or walls. +A `WorldState`'s `obstacles` field is a list of geometries expressed in a +reference frame. Each geometry needs a shape (box, capsule, or sphere), a pose +relative to the frame, and a label. Model every significant obstacle: surfaces +the arm can press into, fixtures it can strike, and vertical obstructions such +as posts or walls. {{< tabs >}} {{% tab name="Python" %}} @@ -78,6 +77,12 @@ world_state = WorldState(obstacles=[obstacles]) {{% tab name="Go" %}} ```go +import ( + "github.com/golang/geo/r3" + "go.viam.com/rdk/referenceframe" + "go.viam.com/rdk/spatialmath" +) + obstacles := make([]spatialmath.Geometry, 0) table, _ := spatialmath.NewBox( @@ -134,6 +139,10 @@ await motion_service.move( {{% tab name="Go" %}} ```go +import "go.viam.com/rdk/services/motion" + +// worldState continues from step 1. motionService and destination follow +// the same setup as Move an arm to a pose. _, err = motionService.Move(ctx, motion.MoveReq{ ComponentName: "my-arm", Destination: destination, @@ -144,7 +153,7 @@ _, err = motionService.Move(ctx, motion.MoveReq{ {{% /tab %}} {{< /tabs >}} -### 3. Verify the planner is actually routing around obstacles +### 3. Verify the planner routes around obstacles You need a concrete test that proves `WorldState` reached the planner. A before-and-after with an obstacle placed between start and target works: @@ -158,4 +167,4 @@ the `WorldState` is not reaching the planner. - [Move an arm to a target pose](/motion-planning/move-an-arm/move-to-pose/) - [Pick an object](/motion-planning/move-an-arm/pick-an-object/) -- [Allow frame collisions](/motion-planning/obstacles/allow-frame-collisions/) — when the planner rejects expected contact +- [Allow frame collisions](/motion-planning/obstacles/allow-frame-collisions/): when the planner rejects expected contact diff --git a/docs/motion-planning/quickstarts/_index.md b/docs/motion-planning/quickstarts/_index.md index eff92fcec6..3dc33cf2a9 100644 --- a/docs/motion-planning/quickstarts/_index.md +++ b/docs/motion-planning/quickstarts/_index.md @@ -4,18 +4,18 @@ title: "Quickstart tutorials" weight: 5 layout: "docs" type: "docs" -description: "Short end-to-end tutorials that take a first-time user from zero to a working motion example. Each tutorial uses fake components so it runs without specific hardware." +description: "Short end-to-end motion tutorials that run on fake components, so you need no hardware." --- Start with [Move your first arm](/motion-planning/quickstarts/first-arm/) to see the motion service drive an arm end-to-end. Then do [Configure a frame system](/motion-planning/quickstarts/frame-system/) -to see how Viam ties arms, grippers, and cameras into one coordinate tree: -the foundation everything else in this section builds on. +to see how Viam ties arms, grippers, and cameras into one coordinate tree. +Everything else in this section builds on that tree. Both quickstarts run on fake components, so you can do them on any laptop without hardware. When you swap in a real arm or camera later, -the machine configuration changes; your code does not. +the machine configuration changes; your code stays the same. {{< cards >}} {{% card link="/motion-planning/quickstarts/first-arm/" noimage="true" %}}