Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
519e4f6
fix(configclass,cfg_utils): respect custom to_dict + tolerate subclas…
yuecideng Jun 25, 2026
4c376d0
refactor(robot-cfg): lift _build_defaults hook + serialization into b…
yuecideng Jun 25, 2026
b1494c9
refactor(dexforce_w1): conform to _build_defaults hook, fix __all__/t…
yuecideng Jun 25, 2026
a72f06e
refactor(cobotmagic): conform to _build_defaults hook, add __all__
yuecideng Jun 25, 2026
31a7011
fix(robot): route build_pk_serial_chain through _pk_urdf_path + add D…
yuecideng Jun 25, 2026
e847c20
refactor(robots): add __all__ to robots registry
yuecideng Jun 25, 2026
b035808
docs(robot): rewrite add_robot quick-reference for the unified protocol
yuecideng Jun 25, 2026
ef27dca
docs(robot): create the missing add_robot tutorial (Path A + Path B)
yuecideng Jun 25, 2026
876643d
docs(robot-context): align robot-system checklist with unified protocol
yuecideng Jun 25, 2026
283a284
feat(skill): add canonical add-robot skill
yuecideng Jun 25, 2026
b288f77
feat(skill): add Claude + Copilot adapters for add-robot
yuecideng Jun 25, 2026
9f78343
feat(robot): add URRobotCfg for the UR family (ur3/3e/5/5e/10/10e)
yuecideng Jun 26, 2026
09e31fe
docs(ur-robot): add UR family robot doc + __main__ smoke test
yuecideng Jun 26, 2026
6750d78
wip
yuecideng Jun 27, 2026
7cd1902
wip
yuecideng Jun 27, 2026
31e4af5
docs(dual-arm): design spec for composing a dual manipulator from a s…
yuecideng Jun 27, 2026
6e9dc5f
feat(robot): add DualArmRobotCfg to compose two arms from a single-ar…
yuecideng Jun 27, 2026
4843e63
wip
yuecideng Jun 28, 2026
5aa834c
Merge branch 'feat/robot-unified-protocol' into feat/ur-robot
yuecideng Jun 28, 2026
ca6c4d6
wip
yuecideng Jun 29, 2026
877c6f4
Merge remote-tracking branch 'origin/main' into feat/ur-robot
yuecideng Jun 29, 2026
1b11655
wip
yuecideng Jun 29, 2026
c05bb75
wip
yuecideng Jun 29, 2026
f5b1e27
fix tests
yuecideng Jun 29, 2026
9fb3aff
wip
yuecideng Jun 29, 2026
c25316d
wip
yuecideng Jun 29, 2026
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
11 changes: 1 addition & 10 deletions configs/agents/rl/push_cube/gym_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,10 @@
}
},
"robot": {
"robot_type": "URRobot",
"uid": "Manipulator",
"urdf_cfg": {
"components": [
{
"component_type": "arm",
"urdf_path": "UniversalRobots/UR10/UR10.urdf"
},
{
"component_type": "hand",
"urdf_path": "DH_PGI_140_80/DH_PGI_140_80.urdf"
Expand All @@ -147,19 +144,13 @@
},
"solver_cfg": {
"arm": {
"class_type": "PytorchSolver",
"end_link_name": "ee_link",
"root_link_name": "base_link",
"tcp": [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.16],
[0.0, 0.0, 0.0, 1.0]
]
}
},
"control_parts": {
"arm": ["JOINT[1-6]"]
}
},
"sensor": [],
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/robots/dual/mirror.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/robots/dual/side_by_side.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/robots/ur/ur10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/robots/ur/ur10e.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/robots/ur/ur3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/robots/ur/ur3e.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/robots/ur/ur5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/robots/ur/ur5e.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
130 changes: 130 additions & 0 deletions docs/source/resources/robot/dual_arm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Dual-Arm Composition

`DualArmRobotCfg` builds a bimanual robot from a single-arm base config that
already follows the standard `"arm"` convention. In the current EmbodiChain
setup, the dual-arm examples are based on the UR family and are assembled onto a
shared synthetic `base_link` through `URDFCfg`.

The images below show the three documented dual-arm layouts currently used for
this robot family: a separated side-by-side arrangement, a wide mirrored
forward-facing arrangement, and a tighter mirrored arrangement for close
workspace overlap.

<div style="display: flex; justify-content: center; align-items: flex-start; gap: 20px; flex-wrap: wrap;">
<figure style="text-align: center; margin: 10px;">
<img src="../../_static/robots/dual/side_by_side.png" alt="Side-by-side dual arm layout" style="height: 220px; width: auto;"/>
<figcaption><b>Side-by-Side</b></figcaption>
</figure>
<figure style="text-align: center; margin: 10px;">
<img src="../../_static/robots/dual/facing_forward.png" alt="Wide mirrored dual arm layout" style="height: 220px; width: auto;"/>
<figcaption><b>Wide Mirrored Layout</b></figcaption>
</figure>
<figure style="text-align: center; margin: 10px;">
<img src="../../_static/robots/dual/mirror.png" alt="Close mirrored dual arm layout" style="height: 220px; width: auto;"/>
<figcaption><b>Close Mirrored Layout</b></figcaption>
</figure>
</div>

## Key Features

- **Generic dual-arm builder** from any single-arm `RobotCfg` that exposes one `arm` URDF component, one `control_parts["arm"]`, and one `solver_cfg["arm"]`.
- **Image-backed mount presets** covering separated, inward-facing, and mirrored-`rz` layouts.
- **Automatic left/right derivation** for URDF components, control parts, solver configs, and drive properties.
- **Config-driven construction** through `DualArmRobotCfg.from_dict(...)` with registry-based base robot lookup.
- **Optional composite `dual_arm` part** for commanding both manipulators together.
- **Round-trip support** through `to_dict()` / `from_dict()` on the generated config.

## Visual Layouts

- **Side-by-side** keeps both bases offset by `±separation/2` along Y with the same orientation.
- **Wide mirrored layout** spreads the arms apart while rotating them symmetrically so they open away from each other.
- **Close mirrored layout** rotates the arms symmetrically toward a shared center workspace for overlapping reach.

## Usage

```python
from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
from embodichain.lab.sim.robots import DualArmRobotCfg

sim = SimulationManager(SimulationManagerCfg(headless=True, num_envs=4))

cfg = DualArmRobotCfg.from_dict(
{
"base_robot": "ur5",
"mount": {
"preset": "mirrored_rz",
"separation": 0.6,
"rz": 0.7853981633974483,
},
}
)
robot = sim.add_robot(cfg=cfg)
```

## Mount Presets

| Preset | Layout |
|--------|--------|
| `side_by_side` | Left arm at `+separation/2` in Y, right arm at `-separation/2` in Y, same orientation on both sides. |
| `facing_inward` | Same `±separation/2` offsets, with yaw `+pi/2` on the left arm and `-pi/2` on the right arm. |
| `mirrored_rz` | Same `±separation/2` offsets, with yaw `+rz` on the left arm and `-rz` on the right arm. |

The `mount` configuration also supports paired per-arm overrides:

```python
cfg = DualArmRobotCfg.from_dict(
{
"base_robot": "ur5",
"mount": {
"preset": "side_by_side",
"separation": 0.6,
"left": {"xyz": [0.0, 0.35, 0.0], "rpy": [0.0, 0.0, 0.0]},
"right": {"xyz": [0.1, -0.35, 0.0], "rpy": [0.0, 0.0, 0.0]},
},
}
)
```

## Configuration Parameters

| Parameter | Description |
|-----------|-------------|
| `base_robot` | Registry key such as `"ur3"` through `"ur10e"`, or `{"type": ..., "init": {...}}` for extra base config overrides. |
| `mount` | Mount dict consumed by `resolve_mounts`, including `preset`, `separation`, optional `rz`, and optional paired `left` / `right` overrides. |
| `arm_part` | Base robot manipulator control part name. Default: `"arm"`. |
| `dual_part` | Whether to emit the concatenated `dual_arm` control part. Default: `True`. |

## Derived Control Parts

For a UR5 base robot with the default preserved joint casing, `DualArmRobotCfg`
produces:

| Part | Joints |
|------|--------|
| `left_arm` | `left_joint1` to `left_joint6` |
| `right_arm` | `right_joint1` to `right_joint6` |
| `dual_arm` | concatenation of the 12 joints above |

For other base robots, the builder preserves the source URDF naming policy and
applies the `left_` / `right_` prefixes consistently with the assembled URDF.

## Programmatic Build Path

```python
from embodichain.lab.sim.robots import URRobotCfg, build_dual_arm_cfg, resolve_mounts

base = URRobotCfg.from_dict({"robot_type": "ur5"})
mounts = resolve_mounts({"preset": "facing_inward", "separation": 0.6})
cfg = build_dual_arm_cfg(base, mounts, dual_part=False)
```

## Adding a New Base Robot

A single-arm robot becomes dual-arm-ready by:

1. following the existing `"arm"` convention in its single-arm cfg,
2. exposing `control_parts["arm"]` and `solver_cfg["arm"]`,
3. adding one registry entry in `embodichain/lab/sim/robots/dual_arm.py`.

That keeps the dual-arm path generic: no extra mixin or robot-specific dual-arm
class is required.
2 changes: 2 additions & 0 deletions docs/source/resources/robot/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Supported Robots
.. toctree::
:maxdepth: 1

UR Family <ur_robot.md>
Dexforce W1 <dexforce_w1.md>
CobotMagic <cobotmagic.md>
Dual-Arm Composition <dual_arm.md>

93 changes: 93 additions & 0 deletions docs/source/resources/robot/ur_robot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# UR Family (UR3 / UR5 / UR10 + e variants)

`URRobotCfg` covers six Universal Robots manipulators in one configuration class:
`ur3`, `ur3e`, `ur5`, `ur5e`, `ur10`, and `ur10e`. The images below show the exact
variants currently documented in EmbodiChain: the compact UR3 pair, the mid-reach
UR5 pair, and the long-reach UR10 pair, with darker classic models and brighter
silver-arm e-series renders.

<div style="display: flex; justify-content: center; align-items: flex-start; gap: 20px; flex-wrap: wrap;">
<figure style="text-align: center; margin: 10px;">
<img src="../../_static/robots/ur/ur3.png" alt="UR3" style="height: 220px; width: auto;"/>
<figcaption><b>UR3</b></figcaption>
</figure>
<figure style="text-align: center; margin: 10px;">
<img src="../../_static/robots/ur/ur3e.png" alt="UR3e" style="height: 220px; width: auto;"/>
<figcaption><b>UR3e</b></figcaption>
</figure>
<figure style="text-align: center; margin: 10px;">
<img src="../../_static/robots/ur/ur5.png" alt="UR5" style="height: 220px; width: auto;"/>
<figcaption><b>UR5</b></figcaption>
</figure>
<figure style="text-align: center; margin: 10px;">
<img src="../../_static/robots/ur/ur5e.png" alt="UR5e" style="height: 220px; width: auto;"/>
<figcaption><b>UR5e</b></figcaption>
</figure>
<figure style="text-align: center; margin: 10px;">
<img src="../../_static/robots/ur/ur10.png" alt="UR10" style="height: 220px; width: auto;"/>
<figcaption><b>UR10</b></figcaption>
</figure>
<figure style="text-align: center; margin: 10px;">
<img src="../../_static/robots/ur/ur10e.png" alt="UR10e" style="height: 220px; width: auto;"/>
<figcaption><b>UR10e</b></figcaption>
</figure>
</div>

## Key Features

- **One config, six UR variants** selected through `robot_type`.
- **Image-matched family lineup** from the shortest-reach UR3 pair to the longest-reach UR10 pair.
- **Classic and e-series styling** with darker legacy arms and brighter silver-arm e-series renders.
- **Analytic UR inverse kinematics** through `URSolverCfg` and the Warp-based UR solver path.
- **Simulation-ready defaults** for URDF selection, control parts, drive properties, and rigid-body attributes.

## Visual Differences Across Variants

- **UR3 / UR3e** are the most compact renders in the set and keep the wrist close to the base.
- **UR5 / UR5e** extend the shoulder-to-wrist span while keeping the same 6-axis arm layout.
- **UR10 / UR10e** show the longest upper-arm and forearm links, matching the largest reach tier in the family.
- **Classic models (`ur3`, `ur5`, `ur10`)** appear darker overall, especially on the arm links.
- **e-series models (`ur3e`, `ur5e`, `ur10e`)** use brighter silver links and lighter joint accents in these renders.

## Usage

```python
from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
from embodichain.lab.sim.robots import URRobotCfg

sim = SimulationManager(SimulationManagerCfg(headless=True, num_envs=4))
cfg = URRobotCfg.from_dict({"robot_type": "ur5"})
robot = sim.add_robot(cfg=cfg)
```

## Robot Parameters

| Parameter | Description |
|-----------|-------------|
| `robot_type` | UR variant: `ur3`, `ur3e`, `ur5`, `ur5e`, `ur10`, or `ur10e` |
| Number of joints | 6 revolute joints plus the fixed `ee_link` |
| Control parts | `arm` (6 joints) |
| Root / end link | `base_link` / `ee_link` |
| Solver | `URSolverCfg` (analytic UR IK) |
| Drive `max_effort` | UR3/UR3e about 56 N.m, UR5/UR5e about 150 N.m, UR10/UR10e about 330 N.m |

> **Note:** The `ur5` URDF uses lowercase joint names (`joint1` to `joint6`), while
> the other variants use `Joint1` to `Joint6`. `URRobotCfg._build_defaults`
> selects the correct naming scheme automatically.

## Variants at a Glance

| `robot_type` | Preview | URDF | Reach (m) | Payload (kg) |
|--------------|---------|------|-----------|--------------|
| `ur3` | Compact classic render | `UniversalRobots/UR3/UR3.urdf` | ~0.5 | 3 |
| `ur3e` | Compact e-series render | `UniversalRobots/UR3e/UR3e.urdf` | ~0.5 | 3 |
| `ur5` | Mid-size classic render | `UniversalRobots/UR5/UR5.urdf` | ~0.85 | 5 |
| `ur5e` | Mid-size e-series render | `UniversalRobots/UR5e/UR5e.urdf` | ~0.85 | 5 |
| `ur10` | Long-reach classic render | `UniversalRobots/UR10/UR10.urdf` | ~1.3 | 10 |
| `ur10e` | Long-reach e-series render | `UniversalRobots/UR10e/UR10e.urdf` | ~1.3 | 10 |

## See Also

- :doc:`/guides/add_robot` - Adding a new robot (quick reference)
- :doc:`/tutorial/add_robot` - Adding a new robot (full tutorial)
- :doc:`/overview/sim/solvers/index` - IK solver reference
14 changes: 10 additions & 4 deletions embodichain/lab/sim/cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -1049,10 +1049,16 @@ class URDFCfg:

name_case: dict[str, str] = field(
default_factory=lambda: {
"joint": "upper",
"link": "lower",
"joint": "original",
"link": "original",
}
)
"""Case normalization policy applied to joint/link names during URDF assembly.

Supported values per key are ``"upper"``, ``"lower"`` or ``"original"``
(legacy alias ``"none"``). The default upper-cases joints and lower-cases
links. Set ``{"joint": "original"}`` to preserve the source URDF casing.
"""
Comment on lines +1056 to +1061

def __init__(
self,
Expand Down Expand Up @@ -1117,8 +1123,8 @@ def __init__(

if name_case is None:
self.name_case = {
"joint": "upper",
"link": "lower",
"joint": "original",
"link": "original",
}
else:
self.name_case = name_case
Expand Down
10 changes: 9 additions & 1 deletion embodichain/lab/sim/robots/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,13 @@

from .dexforce_w1 import *
from .cobotmagic import CobotMagicCfg
from .ur_robot import URRobotCfg
from .dual_arm import DualArmRobotCfg, build_dual_arm_cfg

__all__ = ["DexforceW1Cfg", "CobotMagicCfg"]
__all__ = [
"DexforceW1Cfg",
"CobotMagicCfg",
"URRobotCfg",
"DualArmRobotCfg",
"build_dual_arm_cfg",
]
52 changes: 26 additions & 26 deletions embodichain/lab/sim/robots/cobotmagic.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,23 +88,23 @@ def _build_defaults(self, init_dict: dict | None = None) -> None:
)
self.control_parts = {
"left_arm": [
"LEFT_JOINT1",
"LEFT_JOINT2",
"LEFT_JOINT3",
"LEFT_JOINT4",
"LEFT_JOINT5",
"LEFT_JOINT6",
"left_joint1",
"left_joint2",
"left_joint3",
"left_joint4",
"left_joint5",
"left_joint6",
],
"left_eef": ["LEFT_JOINT7", "LEFT_JOINT8"],
"left_eef": ["left_joint7", "left_joint8"],
"right_arm": [
"RIGHT_JOINT1",
"RIGHT_JOINT2",
"RIGHT_JOINT3",
"RIGHT_JOINT4",
"RIGHT_JOINT5",
"RIGHT_JOINT6",
"right_joint1",
"right_joint2",
"right_joint3",
"right_joint4",
"right_joint5",
"right_joint6",
],
"right_eef": ["RIGHT_JOINT7", "RIGHT_JOINT8"],
"right_eef": ["right_joint7", "right_joint8"],
}
self.solver_cfg = {
"left_arm": OPWSolverCfg(
Expand All @@ -126,22 +126,22 @@ def _build_defaults(self, init_dict: dict | None = None) -> None:
self.min_velocity_iters = 2
self.drive_pros = JointDrivePropertiesCfg(
stiffness={
"LEFT_JOINT[1-6]": 7e4,
"RIGHT_JOINT[1-6]": 7e4,
"LEFT_JOINT[7-8]": 3e2,
"RIGHT_JOINT[7-8]": 3e2,
"left_joint[1-6]": 7e4,
"right_joint[1-6]": 7e4,
"left_joint[7-8]": 3e2,
"right_joint[7-8]": 3e2,
},
damping={
"LEFT_JOINT[1-6]": 1e3,
"RIGHT_JOINT[1-6]": 1e3,
"LEFT_JOINT[7-8]": 3e1,
"RIGHT_JOINT[7-8]": 3e1,
"left_joint[1-6]": 1e3,
"right_joint[1-6]": 1e3,
"left_joint[7-8]": 3e1,
"right_joint[7-8]": 3e1,
},
max_effort={
"LEFT_JOINT[1-6]": 3e6,
"RIGHT_JOINT[1-6]": 3e6,
"LEFT_JOINT[7-8]": 3e3,
"RIGHT_JOINT[7-8]": 3e3,
"left_joint[1-6]": 3e6,
"right_joint[1-6]": 3e6,
"left_joint[7-8]": 3e3,
"right_joint[7-8]": 3e3,
},
)
self.attrs = RigidBodyAttributesCfg(
Expand Down
Loading
Loading