Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ wheels/
.venv/

# Onshape export intermediates (transient; the committed urdf/<robot>.urdf hub
# is what --from-cache reuses).
# is what the generator reuses instead of re-exporting).
robots/*/cad/assets/
robots/*/cad/robot.pkl

Expand Down
138 changes: 78 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,62 @@
[![ROS 2](https://img.shields.io/badge/ROS_2-Jazzy-22314E.svg)](https://docs.ros.org/en/jazzy/)
[![License](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)

Robot description for the **Berkeley Humanoid Lite V2** — a low-cost, open-source
humanoid built on [Robstride](https://robstride.com) actuators. This repository is the
single source of truth for the robot's geometry: it holds the **URDF**, **MJCF**, and
**xacro** (with a `ros2_control` block) descriptions and meshes, all generated from the
Onshape CAD.
Robot description for the Berkeley Humanoid Lite V2, an open-source humanoid built on
[Robstride](https://robstride.com) actuators. This repository is the single source of
truth for the robot's geometry. It holds the URDF, MJCF and xacro descriptions and the
meshes, all generated from the Onshape CAD.

<!-- TODO: add a render / photo of the Lite robot here. -->

The same source serves both worlds:

- **Simulation / RL** — Mujoco Lab and Isaac Lab, via the `robot_assets` Python loader.
- **ROS 2** — `ros2_control` + `robot_state_publisher`, as the `lite_description`
- Simulation and RL, through the `robot_assets` Python loader, for Mujoco Lab and Isaac Lab.
- ROS 2, through `ros2_control` and `robot_state_publisher`, as the `lite_description`
ament package.

## Variants

| Variant | Description | DoF | Root | `ros2_control` |
|---|---|---|---|---|
| `lite` | full-body Lite humanoid (legs, 1-DoF waist yaw, arms, neck, 5-finger hands) | 72 | `pelvis` | model-only |
| `lite_pro` | full-body Lite **Pro** humanoid (legs, 3-DoF waist, arms, neck, 5-finger hands) | 74 | `pelvis` | model-only |
| `lite_dummy` | V1 bimanual upper body (arms + neck) — the configuration `Humanoid Control` deploys | 17 | `chest` | yes — Robstride on two CAN buses |
| `lite_bimanual` | V2 bimanual arms (no neck) | 14 | `chest` | yes — Robstride on two CAN buses |
| `lite_biped` | V2 legs (hip ×3 / knee / ankle ×3 per leg) | 14 | `pelvis` | model-only |
| `lite` | full body (legs, 1-DoF waist yaw, arms, neck, 5-finger hands) | 72 | `pelvis` | model-only |
| `lite_pro` | full body **Pro** (legs, 3-DoF waist, arms, neck, 5-finger hands) | 74 | `pelvis` | model-only |
| `lite_dummy` | V1 bimanual upper body (arms + neck), the configuration `Humanoid Control` deploys | 17 | `chest` | Robstride on two CAN buses |
| `lite_bimanual` | V2 bimanual arms, no neck | 14 | `chest` | Robstride on two CAN buses |
| `lite_biped` | V2 legs (hip x3, knee, ankle x3 per leg), floating base | 14 | `pelvis` | Robstride on two CAN buses + base IMU |
| `lite_biped_debug` | `lite_biped` with the flat debug foot instead of the rockered sole | 14 | `pelvis` | Robstride on two CAN buses + base IMU |

All variants are generated from the same Onshape document (different `Configuration=…`); the
`<robot>.urdf.xacro` of a `ros2_control` variant selects sim / mock / real hardware via xacro args.
Every variant comes from the same Onshape document with a different `Configuration=`.

## Hardware backends

A variant with a `ros2_control` block selects its backend through two xacro args, the
convention the [Universal Robots description](https://github.com/UniversalRobots/Universal_Robots_ROS2_Description/blob/ros2/urdf/ur.ros2_control.xacro)
uses: one boolean per non-real backend, with real hardware as the fallback.

| `sim_mujoco` | `use_mock_hardware` | Backend |
|---|---|---|
| `false` | `false` | `humanoid_devices_robstride/RobstrideSystem`, one block per CAN bus |
| `false` | `true` | `mock_components/GenericSystem` |
| `true` | any | `mujoco_ros2_control/MujocoSystem` |

`sim_mujoco` wins over `use_mock_hardware`. Note that `sim_mujoco` is unrelated to the
`use_sim_time` node parameter, which controls the clock.

The switches choose a plugin and nothing else. The joint macros are backend-agnostic and
always emit their hardware params, because every backend ignores the params it does not
know. Real hardware needs one `<ros2_control>` block per physical CAN bus, so the real
path emits several blocks while the mock and MuJoCo paths share one combined block.

## CAD source

Every description is generated from an Onshape assembly (document
`e9ee61a2e2678af2088d9f31`) by the `robot_assets` tool — see
[Re-generating from CAD](#re-generating-from-cad). The files under `robots/<variant>/`
are build artifacts: **do not hand-edit them**; change the `cad/` inputs and regenerate.
`e9ee61a2e2678af2088d9f31`) by the `robot_assets` tool. See
[Re-generating from CAD](#re-generating-from-cad). The files under `robots/<variant>/` are
build artifacts. Do not hand-edit them. Change the `cad/` inputs and regenerate.

## Usage

### Simulation / RL (Python, no ROS toolchain)
### Simulation and RL (Python, no ROS toolchain)

```bash
uv add git+https://github.com/Berkeley-Humanoids/Lite-Description.git
Expand All @@ -53,48 +72,49 @@ urdf_path = load("robots/lite/urdf/lite.urdf") # Isaac Lab
mjcf_path = load("robots/lite_dummy/mjcf/lite_dummy.xml") # MuJoCo
```

`load()` fetches and caches the requested variant's subtree from this GitHub repo; no ROS
install required.
`load()` fetches the requested variant's subtree from this GitHub repo and caches it. No
ROS install is required.

### ROS 2

`lite_description` is a standard `ament_cmake` package (its `package.xml` is at the repo
root). Build it in a ROS 2 workspace — or pull it via `vcs` / `humanoid_control.repos` from `Humanoid Control`
— and `colcon build`. Downstream, `robot_state_publisher` runs xacro on
`lite_description` is a standard `ament_cmake` package whose `package.xml` sits at the
repo root. Build it in a ROS 2 workspace, or pull it with `vcs` or
`humanoid_control.repos` from `Humanoid Control`, then run `colcon build`. Downstream,
`robot_state_publisher` runs xacro on
`robots/<variant>/xacro/<variant>.urdf.xacro`, and
`package://lite_description/robots/<variant>/meshes/visual/...` resolves after install.
The `<ros2_control>` block selects the hardware backend via xacro args
(`use_sim` / `use_fake_hardware`), following the `franka_ros2` / Universal Robots
convention.

## Repository layout

```
Lite-Description/ # repo root == ament package "lite_description"
Lite-Description/ # repo root == ament package "lite_description"
package.xml CMakeLists.txt # ament (colcon); installs robots/<variant>/...
pyproject.toml # pip/uv: builds the robot_assets Python module
robot_assets/ # Python module: the CAD->assets generator + load()
robot_assets/ # Python module: load() and the CAD->assets generator
actuators/ # actuator spec tables (velocity/effort/armature)
workflow/ # the generator stages
robots/ # per-variant assets (franka_description-style subdir)
<variant>/
xacro/ # ROS entry (GENERATED)
<variant>.urdf.xacro # top assembly: args + includes + instantiation
<variant>.description.xacro # <xacro:macro> model: kinematics, ${mesh_root}, base_link
<variant>.ros2_control.xacro # hardware macros: sim / mock / real, MIT interfaces, CAN ids
urdf/<variant>.urdf # flat URDF (GENERATED; the kinematic HUB; base_link-free)
<variant>.urdf.xacro # assembly: args, includes, instantiation
<variant>.description.xacro # model macro: kinematics, ${mesh_root}, base_link
<variant>.ros2_control.xacro # hardware macros: joints, groups, backends
urdf/<variant>.urdf # flat URDF (GENERATED; the kinematic HUB)
mjcf/<variant>.xml # MJCF (GENERATED; MuJoCo training + deployment sim)
meshes/visual/*.stl # one shared mesh copy
cad/ # generation INPUTS (not installed):
config.json # Onshape document + export options
joint_properties.json # sim tuning: armature / friction / effort_limit
physics.json # MJCF <option> (optional; deployment-sim tuning)
ros2_control.json # ROS hardware map (optional; CAN ids / models / buses / modes)
physics.json # MJCF <option>, freejoint, IMU, contact (optional)
ros2_control.json # ROS hardware map (optional)
scad/ # collider sources
```

The committed `urdf/<variant>.urdf` is the single kinematic hub: the `mjcf` and `xacro`
stages both derive from it, so the three formats cannot drift. It is `base_link`-free
(`base_link` is a ROS/KDL concern injected only into the description xacro), which keeps
the finalize stage idempotent and the MJCF rooted at the CAD root link.
The committed `urdf/<variant>.urdf` is the single kinematic hub. The `mjcf` and `xacro`
stages both derive from it, so the three formats cannot drift apart. The hub is
`base_link`-free, because `base_link` is a ROS and KDL concern that belongs only in the
description xacro. That keeps the finalize stage idempotent and the MJCF rooted at the CAD
root link.

## Re-generating from CAD

Expand All @@ -106,30 +126,32 @@ sudo apt install openscad # for collider editing (onshape-to-robot)
One command produces all three formats from a variant's `cad/` inputs:

```bash
# Full pipeline (Onshape -> URDF + MJCF + xacro). Auto-skips the Onshape stage
# when a committed urdf/<variant>.urdf hub is already present.
# Full pipeline. The Onshape stage is skipped whenever the URDF hub is committed.
uv run robot-assets-generate lite_dummy

# Skip the (expensive, ~1000-request) Onshape export and reuse the committed URDF hub:
uv run robot-assets-generate lite_dummy --from-cache

# Re-emit only some stages after editing physics.json / ros2_control.json:
# Re-emit only some stages, after editing physics.json or ros2_control.json:
uv run robot-assets-generate lite_dummy --only mjcf,xacro
```

Stages (the committed flat `urdf/<variant>.urdf` is the hub; MJCF and xacro both derive
from it, so the three formats share one kinematic origin):
# Re-run the Onshape export even though the hub is committed:
uv run robot-assets-generate lite_dummy --force
```

| Stage | Reads | Writes |
|---|---|---|
| `onshape` | `cad/config.json`, `cad/scad/` | `urdf/<variant>.urdf`, `meshes/visual/` |
| `urdf` | `urdf/<variant>.urdf`, `joint_properties.json` | finalized `urdf/<variant>.urdf` (effort harmonised; base_link-free; idempotent) |
| `mjcf` | `urdf/<variant>.urdf`, `joint_properties.json`, `physics.json` | `mjcf/<variant>.xml` (`<option>`, actuators; no `<sensor>` block — joint state is read from sim data, and `mujoco_ros2_control` aborts on non-site sensors) |
| `urdf` | `urdf/<variant>.urdf`, `joint_properties.json` | finalized `urdf/<variant>.urdf` (welded, effort harmonised, idempotent) |
| `mjcf` | `urdf/<variant>.urdf`, `joint_properties.json`, `physics.json` | `mjcf/<variant>.xml` |
| `xacro` | `urdf/<variant>.urdf`, `ros2_control.json` | `xacro/<variant>.*.xacro` (`base_link` injected here) |
| `package` | — | registers the variant in the repo-root `CMakeLists.txt` |

A variant without a `ros2_control.json` (`lite`, `lite_biped`) generates a **model-only**
package: a description macro + a thin assembly, no `<ros2_control>`.
The Onshape stage costs about 1000 API requests, which is why it is skipped by default.

A variant without a `ros2_control.json` generates a model-only package: a description
macro and a thin assembly, with no `<ros2_control>` block.

The MJCF carries actuators but no `<sensor>` block unless `physics.json` asks for the base
IMU. Joint state is read from the sim data, and `mujoco_ros2_control` aborts on a sensor
that is not backed by a `<site>`.

### Editing colliders (OpenSCAD)

Expand All @@ -142,14 +164,10 @@ uv run onshape-to-robot-edit-shape ./chest.stl
## Tests

```bash
uv run pytest # left/right symmetry, URDF<->MJCF parity, inertial plausibility,
# mesh existence, xacro well-formedness, generator-determinism
uv run pytest
```

The `xacro` + `check_urdf` expansion checks run only where those tools are installed
(ROS / RoboStack-pixi); they are skipped in the plain `uv` environment and exercised by
the ROS CI job.

## License

MIT — see [LICENSE](LICENSE).
The suite covers left/right symmetry, URDF-to-MJCF parity, inertial plausibility, mesh
existence, xacro well-formedness and generator determinism. The `xacro` and `check_urdf`
expansion checks run only where those tools are installed, so they are skipped in the
plain `uv` environment and are exercised by the ROS CI job instead.
23 changes: 13 additions & 10 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@
<name>lite_description</name>
<version>0.0.1</version>
<description>
Description package for the Berkeley-Humanoids Lite humanoid (variants under
robots/: `lite` full-body and `lite_dummy` bimanual). Each ships xacro (ROS
entry, incl. a generated &lt;ros2_control&gt; block), a flattened URDF and MJCF
(training / deployment sim), and meshes. All artifacts are generated from
Onshape CAD by the `robot_assets` Python tool; do not hand-edit them.
ros2_control hardware plugins
(mock / mujoco_ros2_control / humanoid_devices_robstride) are referenced by pluginlib
string only and are NOT build/exec dependencies of this package. This is an
asset-only package: it ships no launch files or RViz configs (visualization
and bringup are consumer concerns).
Description package for the Berkeley-Humanoids Lite humanoid. Each variant under
robots/ (full-body `lite` and `lite_pro`, bimanual `lite_dummy` and `lite_bimanual`,
biped `lite_biped` and `lite_biped_debug`) ships xacro as the ROS entry point,
including a generated &lt;ros2_control&gt; block, plus a flattened URDF, an MJCF for
the training and deployment sim, and meshes. The `robot_assets` Python tool generates
every artifact from Onshape CAD, so do not hand-edit them.

The ros2_control hardware plugins (mock_components, mujoco_ros2_control,
humanoid_devices_robstride) are referenced by pluginlib string only. They are not
build or exec dependencies of this package.

This is an asset-only package. It ships no launch files and no RViz configs, because
visualization and bringup are consumer concerns.
</description>
<maintainer email="chiyufeng@berkeley.edu">Humanoid Control Dev</maintainer>
<license>MIT</license>
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "robot-assets"
version = "0.3.1"
version = "0.4.0"
description = "Robot asset tool: generate URDF / MJCF / xacro from Onshape CAD, and load robot descriptions"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
53 changes: 35 additions & 18 deletions robot_assets/actuators/func.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,58 @@
"""
Functions to compute actuator parameters.
"""Compute actuator parameters from a second-order model of the joint.

These equations are derived from:
BeyondMimic: From Motion Tracking to Versatile Humanoid Control via Guided Diffusion, https://arxiv.org/abs/2508.08241
The equations come from BeyondMimic: From Motion Tracking to Versatile Humanoid Control
via Guided Diffusion (https://arxiv.org/abs/2508.08241). Each function takes one of the
actuator spec tables in this package, such as ``ROBSTRIDE_06_ACTUATOR_PARAMS``.
"""

from math import pi

# Default closed-loop natural frequency, 10 Hz in rad/s.
NATURAL_FREQUENCY = 10 * 2 * pi


def compute_stiffness(actuator_params: dict[str, float], natural_frequency: float = 10 * 2 * pi) -> float:
"""Compute the stiffness of an actuator.
def compute_stiffness(
actuator_params: dict[str, float],
natural_frequency: float = NATURAL_FREQUENCY,
) -> float:
"""Return the joint stiffness in Nm/rad.

Args:
actuator_params: The parameters of the actuator.
natural_frequency: The natural frequency of the actuator in rad/s.
actuator_params: An actuator spec table. Reads ``armature`` in kg m^2.
natural_frequency: Closed-loop natural frequency in rad/s.
"""
return actuator_params["armature"] * natural_frequency**2


def compute_damping(actuator_params: dict[str, float], natural_frequency: float = 10 * 2 * pi, damping_ratio: float = 2.0) -> float:
"""Compute the damping of an actuator.
def compute_damping(
actuator_params: dict[str, float],
natural_frequency: float = NATURAL_FREQUENCY,
damping_ratio: float = 2.0,
) -> float:
"""Return the joint damping in Nm s/rad.

Args:
actuator_params: The parameters of the actuator.
natural_frequency: The natural frequency of the actuator in rad/s.
damping_ratio: The damping ratio of the actuator.
actuator_params: An actuator spec table. Reads ``armature`` in kg m^2.
natural_frequency: Closed-loop natural frequency in rad/s.
damping_ratio: Damping ratio. Above 1.0 is overdamped.
"""
return 2.0 * damping_ratio * actuator_params["armature"] * natural_frequency


def compute_action_scale(actuator_params: dict[str, float], natural_frequency: float = 10 * 2 * pi, action_scale_coefficient: float = 0.25) -> float:
"""Compute the action scale from natural frequency and action scale coefficient.
def compute_action_scale(
actuator_params: dict[str, float],
natural_frequency: float = NATURAL_FREQUENCY,
action_scale_coefficient: float = 0.25,
) -> float:
"""Return the policy action scale in rad.

This is the position offset whose stiffness torque reaches
``action_scale_coefficient`` of the actuator's effort limit.

Args:
actuator_params: The parameters of the actuator.
natural_frequency: The natural frequency of the actuator in rad/s.
action_scale_coefficient: The action scale coefficient.
actuator_params: An actuator spec table. Reads ``armature`` and ``effort_limit``.
natural_frequency: Closed-loop natural frequency in rad/s.
action_scale_coefficient: Fraction of the effort limit a unit action commands.
"""
stiffness = compute_stiffness(actuator_params, natural_frequency)
return action_scale_coefficient * actuator_params["effort_limit"] / stiffness
Loading
Loading