> For the complete documentation index, see [llms.txt](https://docs.hello-robot.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hello-robot.com/stretch4-body-repo/readme.md).

# Introduction

The `stretch4_body` repository contains the core Python software stack that allows developers to interact with the hardware of Stretch 4 robots. The repository for Stretch 3 and below can be found in the [stretch\_body](https://github.com/hello-robot/stretch_body) repo. This repo provides a robust, soft real-time capable framework for managing low-level motor communication, subsystem coordination, autonomous behaviors, and a high-level API for user applications. This repository is intended to be imported by other code that needs access to these features.

This package can be installed by:

```
python3 -m pip install -U hello-robot-stretch4-body
```

## Architecture Block Diagram

At its heart, the architecture is built around a Client-Server model. A dedicated `RobotServer` runs as a background daemon managing the physical hardware at 100Hz, executing safety monitoring, self-collision detection, and hardware command multiplexing. Developers build their applications using the `RobotClient`, which asynchronously communicates with the server over ZeroMQ. This decouples user scripts from strict hardware timing constraints and allows for safe, concurrent control of the robot.

```mermaid
graph TD
    ClientCode["User Application RobotClient"]
    Server["Robot Server 100Hz Loop"]

    Subsystems["Hardware Subsystems"]
    Arm
    Lift
    Omnibase
    PowerPeriph
    EndOfArm

    Behaviors["Behaviors"]
    Sentries["Sentries Safety Monitors"]
    SafeMotions["Safe Motions Collision Avoidance"]
    Routines["Routines Autonomous Actions"]

    Workers["Background Workers"]
    LineSensorLoop["Line Sensor Loop"]
    CollisionLoop["Self Collision Loop"]
    EOALoop["End Of Arm Loop"]

    ClientCode -->|ZeroMQ Commands and Status| Server

    Server --> Behaviors
    Behaviors --> Sentries
    Behaviors --> SafeMotions
    Behaviors --> Routines

    Server --> Subsystems
    Subsystems --> Arm
    Subsystems --> Lift
    Subsystems --> Omnibase
    Subsystems --> PowerPeriph
    Subsystems --> EndOfArm

    Server --> Workers
    Workers --> LineSensorLoop
    Workers --> CollisionLoop
    Workers --> EOALoop
```

## Technical Primers

For an in-depth understanding of how specific parts of the system are designed, refer to the following technical primers:

| Primer                                                                                  | Description                                                                                                 |
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| [Core Architecture](/stretch4-body-repo/core-framework/primer_core.md)                  | Maps out the foundational classes, IPC communication, and file organization of the core library.            |
| [Robot Parameters](/stretch4-body-repo/core-framework/primer_robot_params.md)           | Explains the multi-layered parameter system (default vs user) and dynamic runtime generation.               |
| [Robot Client API](/stretch4-body-repo/core-framework/primer_robot_client.md)           | A guide to using the RobotClient API for reading status and commanding motion asynchronously.               |
| [Hardware Subsystems](/stretch4-body-repo/subsystems-and-hardware/primer_subsystems.md) | Overview of the primary hardware abstractions (Arm, Lift, Base) and how they are instantiated.              |
| [End-Of-Arm EOA](/stretch4-body-repo/subsystems-and-hardware/primer_end_of_arm.md)      | Details the dynamically instantiated, multi-process architecture for interchangeable tool attachments.      |
| [Line Sensors](/stretch4-body-repo/subsystems-and-hardware/primer_line_sensor.md)       | Details the operation and background processing for the downward-facing Pixart line sensors.                |
| [Server Behaviors](/stretch4-body-repo/behaviors-and-safety/primer_behaviors.md)        | Explains the plugin architecture for Sentries, Safe Motions, and Routines within the 100Hz server loop.     |
| [Self-Collision](/stretch4-body-repo/behaviors-and-safety/primer_self_collision.md)     | Details the MuJoCo-based collision checking system, its background loop, and configuration parameters.      |
| [Gamepad Teleop](/stretch4-body-repo/teleoperation/primer_gamepad_teleop.md)            | Explains how different control schemes can be mapped onto a standard gamepad controller + how to extend it. |
| [Cameras](/stretch4-body-repo/subsystems-and-hardware/primer_cameras.md)                | A guide to the cameras on Stretch 4's head and wrist, with an overview of the CLIs and API.                 |

## Installation

1. `pip3 install -e .`
2. `stretch_body_server --launch`

*Note: The C++ shared libraries for `transport` and `SCSerial` will compile automatically via Meson during the `pip install`.*

If you want to install the object detection dependencies:

```bash
pip3 install -e .[object_detection]
```

### Troubleshooting Editable Installs

If you make a C++ syntax error or typo in the source files and attempt to run a command while in editable mode (e.g., launching `stretch_body_server`), you may encounter an obscure Python exception instead of the actual C++ compiler error message:

```
subprocess.CalledProcessError: Command '['ninja']' returned non-zero exit status 1.
```

Because `meson-python` editable builds run quietly in the background on import, it drops the standard output of the C++ compiler natively, hiding your C++ syntax error. To see the actual compiler output and locate the line where C++ failed, prepend your command with the verbose flag:

```bash
MESONPY_EDITABLE_VERBOSE=1 stretch_body_server --launch
```

## Custom User End-of-Arm Tools

Stretch 4 supports dynamic user-defined custom end-of-arm tools. Users can define, process, register, and switch to their own tools without modifying the core software stack.

### Overview

A tool is made of three independently-configured pieces. Each is described in detail in the matching step below, but at a glance:

* **Driver** (`driver_class_name`, Step 1) — the server-side class that talks directly to your tool's physical motor/servo hardware from inside the 100Hz `RobotServer` loop. Declaring it is what adds your servo to the wrist bus. A tool that declares no driver is passive: it keeps the bare 3-DOF wrist and the `EOA_Wrist_DW4_Tool_NIL` subsystem.
* **Metadata** (`ToolMetadata`, Step 2) — defines the conversions between the `urdf`/`command`/`actuator`/ `aperture`/`normalized` units. Never performs hardware I/O itself. Most tools need no custom Python here: the built-in `LinearToolMetadata` handles any linear mapping from YAML keys alone; only a nonlinear transmission (e.g. a linkage) requires writing a bespoke subclass.
* **Client** (`client_class`, Step 2) — the `RobotClient`-facing class used by application code for `move_to()`, `move_by()`, `pose()`, and status reads. Optional: the generic `ToolJointClient` can handle single degree of freedom tools using the poses and conversions defined in the metadata. A bespoke client class may be required for more complex tools.

### 1. Directory Structure

Custom tools should be placed in your fleet's `user_tools` directory:

* If `HELLO_FLEET_PATH` is set: `<HELLO_FLEET_PATH>/user_tools/`
* Otherwise (fallback): `~/stretch_user/user_tools/`

Create a subdirectory named after your tool (e.g., `user_eoa_tool`). The directory name is the tool name, and it must not collide with a built-in tool name — a user directory named after a built-in is ignored.

```yaml
> user_eoa_tool
    > meshes
        my_tool_link.STL               # Visual meshes, referenced by the URDF
        my_tool_collision_link.STL     # Collision meshes, generated in Step 3
    user_eoa_tool.urdf               # URDF describing joints & links -- exactly one per tool
    collision_mesh_config.yaml         # Per-link collision mesh recipe -- see Step 3
    tool_params.yaml                   # YAML config
    user_eoa_tool_driver.py          # Python driver class for the tool's actuator(s)
    user_eoa_tool_client.py          # Optional custom Python RobotClient class
    user_eoa_tool_metadata.py        # Optional custom Python ToolMetadata subclass
```

Your URDF's root link must be named `quick_connect_interface_link`. This is how the tool attaches to the robot: `SE4.xacro` includes your URDF and then adds a fixed `tool_connection_joint` whose parent is the robot's `tool_attachment_site_link` and whose child is `quick_connect_interface_link`, by that exact name. Every built-in tool follows the same convention.

Root link here means a link that is not the child of any joint *within your URDF* — the robot supplies the joint above it. If your CAD export names the root something else, add `quick_connect_interface_link` as a new root and tie it to your old root with a fixed identity joint:

```xml
<link name="quick_connect_interface_link"/>

<joint name="quick_connect_interface_joint" type="fixed">
  <parent link="quick_connect_interface_link"/>
  <child link="my_old_root_link"/>
  <origin xyz="0 0 0" rpy="0 0 0"/>
</joint>
```

The three Python files above can be named anything you like — there is no filename or class-name convention to follow. Each is wired up explicitly by a pair of keys in `tool_params.yaml`, pointing at a module name (filename without `.py`) and the class within it:

```yaml
driver_module_name: user_eoa_tool_driver      # driver -- see Overview
driver_class_name: UserEoaTool

client_module_name: user_eoa_tool_client  # client -- optional, see Overview
client_class_name: UserEoaToolClient

metadata_module_name: user_eoa_tool_metadata  # metadata -- optional, see Overview and Step 2
metadata_class_name: UserEoaToolMetadata
```

All three are independently optional: omit `driver_module_name`/`driver_class_name` and the tool stays passive; omit `client_module_name`/`client_class_name` and it falls back to the generic `ToolJointClient`; omit `metadata_module_name`/`metadata_class_name` and it falls back to the built-in `LinearToolMetadata` (Step 2, Path A). See the Overview above for what each piece does and when you actually need to provide one.

A fourth pair, `py_module_name`/`py_class_name`, names an `EndOfArm` subsystem subclass. Most tools omit it and inherit `EOA_Wrist_DW4_Tool_NIL`, which drives the three wrist joints and leaves your tool joint to the driver above.

Your tool starts from the bare 3-DOF wrist. Declaring a driver adds your servo to the end-of-arm chain under your tool's own name, starting from `SE4_eoa_tool_servo_DW4` in `robot/robot_params_SE4.py` — the shared baseline holding the bus, motion-profile, stall and protection settings that are the same for every tool. State only what your hardware does differently, as top-level keys:

```yaml
# Required: this servo's bus address, travel, and which way it homes. The baseline leaves
# these out, because a default would be a guess about hardware it has never seen.
id: 24                              # servo id on the wrist bus, unique across tools
range_deg: [0, 187]                 # mechanical travel
homing_to_neg_limit: 1
homing_pwm: -80                     # sign sets the homing direction
flip_encoder_polarity: 1

# Optional: anything else that differs from the baseline, merged key by key.
eeprom_cfg:
  max_load_limit_pct: 20.0          # this tool's fingers pull less than the baseline allows

stow:
  my_tool_joint: 0.0                # optional stow position, in "command" units (defaults to 0)

homing:
  wrist_roll: -0.4                  # optional final position after homing, in actuator units (defaults to 0)

i_feedforward_payload: 0.3          # optional lift feedforward current for this tool's weight, in amps (defaults to 0.0)
```

Any top-level key that is not a tool-level key — the class names above, `stow`, `homing`, `i_feedforward_payload`, `collision_mgmt`, `self_collision_mujoco`, `pose_models`, and the unit-conversion keys from Step 2 — is treated as a servo parameter and merged over the baseline. Omitting one of the five required keys is reported at startup, and `stretch_configure_tool`'s bus scan can mistake one tool for another if two share an `id`.

`homing` sets where `wrist_pitch`, `wrist_roll` and `wrist_yaw` are left when each finishes homing, defaulting to 0. This can be helpful to keep the end effector out of the way while the wrist is homing and collision is off. The end-of-arm will home the yaw joint, then the roll joint, the pitch joint, and finally the tool joints. Each will hold its final position while the next homes to its hardstop.

`i_feedforward_payload` adds to the lift's feedforward current (amps, 0.0-1.0) to counterbalance the added weight of the arm, wrist and tool, so the lift doesn't have to close a position error to hold against gravity. It defaults to 0.0; a tool with real mass that leaves it unset will sag under its own weight and lean on the lift's position control to hold height. See `Lift.set_i_feedforward_payload` in `subsystem/lift.py`.

`collision_mgmt` declares brake distances and collision pairs against the rest of the robot body for a tool that can reach the base or mast, so the lift or arm brakes before contact rather than after. `k_brake_distance` pads a joint's stopping distance by the given multiplier; `collision_pairs` names which of the tool's links to check against which robot links (as a point against a bounding box, via `detect_as: 'pts'`); `joints` maps a joint to the collision pairs and direction (`motion_dir`) that should brake it. Omitting `collision_mgmt` means the tool participates in no such check. See `SE4_eoa_wrist_dw4_tool_pg4` in `robot/robot_params_SE4.py` for a worked example — its parallel gripper hangs below the wrist and needs the lift to brake before it reaches the base.

`self_collision_mujoco` configures this tool's participation in the self-collision system (see `docs/primer_self_collision.md`), which uses MuJoCo to check the tool's links against the rest of the robot at runtime. Omitting it leaves the tool with no exclusions: every pair of its links, including pairs that naturally overlap at a joint (e.g. adjacent fingers), is checked, and an overlapping pair with no exclusion falsely reports a collision.

```yaml
self_collision_mujoco:
  exclusions:
    - ['my_tool_finger_left_link', 'my_tool_finger_right_link']  # allowed to touch/overlap
  ignore_links: ['my_tool_camera_link']    # skipped by the collision engine entirely
  k_brake_distance:
    wrist_pitch: 1.1                       # multiplies this joint's required braking distance
```

`pose_models` is a list of named joint poses for this tool, readable with `RobotPose.load_tool_pose_models(tool_name)` (`stretch4_body/utils/stretch_pose_models.py`). Each entry needs `name` and `timestamp`; `joints` maps a joint name to a `position` (that joint's command units), plus `velocity` and `effort` — recorded alongside a live capture, harmless to leave at `0.0` for a pose you write by hand. `base` and `delay_before_start` are optional. For example, a "stow" pose that tucks `my_tool_joint` to zero:

```yaml
pose_models:
  - name: stow
    timestamp: 0.0
    joints:
      my_tool_joint:
        position: 0.0    # command units, same as tool_joints/stow above
        velocity: 0.0
        effort: 0.0
```

### 2. Configuring Unit Conversions

For actuated tools, the software works in five primary units.

| Unit         | Description                                                                                                                                                                                           |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `urdf`       | The units used to define the joint in ROS's robot model, used with `JointTrajectory`, `JointState`, and other ROS topics                                                                              |
| `command`    | The units expected by stretch4\_body's `move_to()` and `move_by()` methods. stretch4\_body will translate values into raw motor units, and raw motor readings back into these units to report status. |
| `actuator`   | The servo/motor register value (radians).                                                                                                                                                             |
| `aperture`   | Physical fingertip opening (meters) — a client convenience unit.                                                                                                                                      |
| `normalized` | 0.0 (closed) .. 1.0 (open) — another client convenience unit, e.g. for a UI slider                                                                                                                    |

Each tool is expected to provide a conversion path between each of the 5 units. There are two ways to configure this, depending on how your gripper's motor motion relates to its physical motion:

**Path A — Linear tools.** To send motor commands directly without specialized conversations (no gearbox nonlinearity, no linkage), just add these keys to your tool's `tool_params.yaml`:

```yaml
driver_module_name: user_eoa_tool_driver
driver_class_name: UserEoaTool

tool_joints: ['my_finger_left_joint', 'my_finger_right_joint']
primary_joint: 'my_finger_left_joint'   # optional, defaults to the first tool_joints entry
tool_links: ['my_finger_left_link', 'my_finger_right_link']

actuator_command_range: [0.0, 100.0]

# Physical fingertip opening bounds (meters)
aperture_range: [0.0, 0.08]

# Linear scale factor: command = urdf * urdf_to_actuator_scale. Optional, defaults to 1.0.
urdf_to_actuator_scale: 100.0

# How close to a commanded position counts as "arrived", in URDF units (meters or radians).
# Optional; defaults to 2% of the joint's URDF range. The ROS trajectory server uses this to
# decide when a gripper goal is finished, so a tolerance that is too tight will hang a
# trajectory and one that is too loose will end the motion early.
position_tolerance: 0.002
```

**Path B — Nonlinear tools.** If your motor's motion relates to the gripper's physical motion through a linkage or other nonlinear transmission and a single linear scale can't describe it, write your own `ToolMetadata` subclass and register it in `tool_params.yaml`:

```yaml
driver_module_name: user_eoa_tool_driver
driver_class_name: UserEoaTool

client_module_name: user_eoa_tool_client
client_class_name: UserEoaToolClient

metadata_module_name: user_eoa_tool_metadata
metadata_class_name: UserEoaToolMetadata
```

Your subclass must implement every abstract member of `ToolMetadata` (`stretch4_body/utils/tool_metadata.py`) — `tool_joints`, `tool_links`, `client_class`, `driver_class`, `status_to_metadata`, the two ranges, and the six conversions between the five units above. It must also define `tool_name`, returning your tool's own name: that is the key of its entry in `status['end_of_arm']` and the joint `ToolJointClient` commands, so the self-collision sentry and the client both reach the tool through it. `LinearToolMetadata` takes it from the tool's name; a subclass that leaves it undefined raises `ToolConfigurationError`.

```python
from stretch4_body.utils.tool_metadata import ToolMetadata

class UserEoaToolMetadata(ToolMetadata):
    ...  # tool_name, tool_joints, tool_links, client_class, driver_class

    @property
    def actuator_range(self) -> tuple[float, float]:
        """(min, max) servo angle (radians)."""

    @property
    def command_range(self) -> tuple[float, float]:
        """(min, max) in whatever units your move_to()/move_by() actually accept."""

    def urdf_to_command(self, urdf: float) -> float:
        """URDF joint value -> your move_to()/move_by()'s own units."""

    def command_to_urdf(self, command: float) -> float:
        """Your move_to()/move_by()'s own units -> URDF joint value."""

    def command_to_actuator(self, command: float) -> float:
        """Your move_to()/move_by()'s own units -> servo angle (radians)."""

    def actuator_to_command(self, actuator: float) -> float:
        """Servo angle (radians) -> your move_to()/move_by()'s own units."""

    def aperture_to_actuator(self, aperture: float) -> float:
        """Physical fingertip opening (meters) -> servo angle (radians)."""

    def actuator_to_aperture(self, actuator: float) -> float:
        """Servo angle (radians) -> physical fingertip opening (meters)."""

    def status_to_metadata(self, status: dict) -> dict:
        """Raw hardware status -> {'aperture_m', 'finger_rad', 'finger_effort', 'finger_vel'}."""
```

The six cover three of the four edges between those units — `urdf`↔`command`, `command`↔`actuator` and `actuator`↔`aperture`, each in both directions. The fourth, `actuator`↔`normalized`, is derived from `actuator_range`, so the base class provides it along with `urdf_to_actuator`/`actuator_to_urdf`, the remaining chained pairs, and the differential conversions below. You only need the two ranges, the six conversions, and `status_to_metadata` shown above, plus `tool_name`, `tool_joints`, `tool_links`, `client_class` and `driver_class`, unchanged from a normal user tool. See `ParallelGripperMetadata` (linkage-based) and `StretchGripperMetadata` (near-linear) in `tool_metadata.py` for complete worked examples.

`position_tolerance` is also provided by the base class, defaulting to 2% of the joint's URDF range. Override the property if your tool needs a different arrival threshold — the ROS trajectory server reads it to decide when a gripper goal is complete.

#### Converting velocities

**A rate does not convert like a position.** For a position conversion `y = f(x)`, a velocity transforms by the derivative: `ẏ = f'(x)·ẋ`. Running a rate through the position conversion is incorrect whenever `f` is not a pure scaling through the origin — either because it carries an offset (`f(0) ≠ 0`, as when a fully-closed gripper sits at a nonzero servo angle), or because it is nonlinear (the gain varies with position, as across PG4's slider-crank linkage).

So `ToolMetadata` provides a separate family of differential conversions:

| Method                                                                     | Use for                                                                                                                                                                                                                                                            |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `convert_velocity(v, frm, to, at)`                                         | A rate. Multiplies by the Jacobian evaluated at position `at`.                                                                                                                                                                                                     |
| `convert_delta(d, frm, to, at)`                                            | A finite displacement (a `move_by` amount). Computed exactly as `f(at + d) - f(at)`, so it needs no derivative and stays exact across a nonlinear transmission. **Prefer this whenever the quantity really is a displacement.**                                    |
| `convert_acceleration(a, frm, to, at)`                                     | A motion-profile acceleration limit. First-order only.                                                                                                                                                                                                             |
| `conversion_gain(frm, to, at)`                                             | The Jacobian itself — the partial derivative `d(to)/d(frm)` at `at`, if you want to multiply yourself.                                                                                                                                                             |
| `velocity_limit(unit_type, at)` / `conservative_velocity_limit(unit_type)` | This tool's servo velocity limit pushed through the Jacobian into other units: `\|d(unit_type)/d(actuator)\| * limit_actuator`. The conservative form minimizes that over the range, giving a rate achievable at every position — use it when you need one scalar. |

Named wrappers exist for the common pairs, e.g. `urdf_to_command_velocity(v, at_urdf)` and `actuator_to_urdf_velocity(v, at_actuator)`.

**`at` is required, not optional** — for a nonlinear tool there is no position-independent answer — and it is expressed in the *source* units. `ToolMetadata` is stateless, so the caller supplies the current position, typically from `status['pos']`.

Your subclass gets velocity support for free: the base class differentiates the six position conversions above numerically. Since a Path B transmission is nonlinear by definition, you should still override `_analytic_gain(frm, to, at)` to return a closed-form derivative, which is exact and cheaper. Two things to watch for if you rely on the numeric fallback: do not round or quantize inside a conversion function (it makes the derivative meaningless, and a small enough step can return exactly zero), and make sure your conversions do not raise at the edges of their range.

Note the one asymmetry: a tool driver's `move_to(x, v_r, a_r)` takes its *position* in command units but its `v_r`/`a_r` in **actuator rad/s**, since those are servo motion-profile limits. If you hold a rate in command units, convert it with `command_to_actuator_velocity()` first.

### 3. Mesh Processing

For a tool directory to fit into the robot's URDF structure, all of the following have to be true:

* It holds exactly one `.urdf` file. That is how the robot model finds it.
* That URDF's root link is named `quick_connect_interface_link`
* Every mesh the URDF references is in the directory's `meshes/` folder.
* Every `<mesh filename>` is written as `$(arg tool_mesh_dir)/<filename>`, which is how the assembled robot model resolves them.
* Links that need collision checking reference a mesh in their `<collision>` tag.
* Moving joints carry `velocity` and `effort` in their `<limit>` element.

A convenience script is provided to help with these steps. It is part of the [stretch4\_urdf](https://github.com/hello-robot/stretch4_urdf) source repository, not the `hello-robot-stretch4-urdf` pip package, so clone the repository and run the script from the clone's root directory:

```bash
git clone https://github.com/hello-robot/stretch4_urdf.git
cd stretch4_urdf
python3 -m stretch4_urdf.utils.preprocessing.process_new_user_tool ~/stretch_user/user_tools/user_eoa_tool
```

Simplifying a collision mesh requires `open3d` (`python3 -m pip install open3d`).

In order, it:

1. Checks the root link is `quick_connect_interface_link`, and offers to insert one if not.
2. Writes a `collision_mesh_config.yaml` covering every visual mesh link if you do not already have one.
3. Generates each collision mesh next to its visual mesh: `my_tool_body_link.STL` spawns `my_tool_body_link_collision_link.STL`.
4. Rewrites your URDF in place so each `<collision>` tag points at the generated mesh.
5. Points every mesh path at `$(arg tool_mesh_dir)/...`, which is how the assembled robot model resolves them. See below.

By default, collision links are reduced by 90%. Different decimation ratios or shapes can be specified in `collision_mesh_config.yaml`, next to your URDF. Keys under `links:` are URDF link names; each needs an `action`:

```yaml
links:
  my_tool_body_link:
    action: qem
    simplification_ratio: 0.1
  my_finger_left_link:
    action: bounding_box
    padding: [0.0, 0.0, 0.005]
  my_fingertip_left_link:
    action: convex_hull
```

| `action`       | Result                                                                                                                                                                                                                 |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `qem`          | Quadric-error-metric decimation, keeping `simplification_ratio` of the original face count (0.0 = maximal simplification, 1.0 = unchanged; defaults to 0.5). The usual choice for a detailed part whose shape matters. |
| `bounding_box` | An axis-aligned box around the mesh, grown by `padding` in x/y/z (meters; a single number applies to all three, and it defaults to zero). Cheapest, and the right choice for a chunky body link.                       |
| `convex_hull`  | The mesh's convex hull. Good for a part that is roughly convex already.                                                                                                                                                |
| `nop`          | Copy the visual mesh through unchanged.                                                                                                                                                                                |

A link without a mesh specified in the `<visual>` tag will be skipped. Only `.stl`, `.obj` and `.dae` are processed.

`stretch4_urdf` sets the xacro argument `tool_mesh_dir` to the absolute path of your tool's `meshes/` directory when it builds the robot model, so every mesh reference in your URDF should be written against it:

```xml
<mesh filename="$(arg tool_mesh_dir)/my_tool_body_link.STL"/>
```

Whatever your CAD exporter wrote — a bare filename, `meshes/...`, `package://...`, or an absolute path on your machine will not resolve on the robot. Item 5 above fixes that: it walks the URDF's `<mesh>` elements, takes each filename's basename, confirms that file is in `meshes/`, and rewrites the reference. Matching on the elements rather than on the text of the path means a reference written any way at all is normalized.

### 4. Switching to Your Tool

Before switching, check that your `tool_params.yaml` resolves to the classes it names:

```bash
stretch_check_user_tool
```

It reads configuration only — nothing there talks to hardware, so it is safe to run with no robot attached. It reports the metadata, driver and client classes it resolved, confirms `tool_name` names a servo on the wrist bus, and lists any pose models your tool ships.

To switch your robot to the custom tool, run the configuration tool and pick it from the menu:

```bash
stretch_configure_tool
```

Your tool appears in the numbered list alongside the built-ins, under a display name derived from its directory name (`user_eoa_tool` shows as "User Eoa Tool").


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.hello-robot.com/stretch4-body-repo/readme.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
