Skip to content
Draft
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
30 changes: 30 additions & 0 deletions docs/wiki/build-and-composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,36 @@ All file paths are resolved relative to `MODELS_DIR` (the packaged `myo_sim/mode

6. Add a test entry in `tests/test_build_registry.py` per `docs/wiki/testing-guide.md`.

## Torque-driven actuation

Each body part can be built with direct per-joint torque motors instead of muscles+tendons, via an `actuation` argument threaded through `build_model()` and every builder function:

```python
from myo_sim.build.compose import PART_ARMS, PART_LEGS, PART_TORSO, build_model

build_model("myofullbody") # default: every part muscle-driven
build_model("myofullbody", actuation="torque") # every part torque-driven
build_model( # mixed: torso/arms torque, legs muscle
"myofullbody",
actuation={PART_TORSO: "torque", PART_ARMS: "torque", PART_LEGS: "muscle"},
)
```

CLI equivalent:

```bash
uv run python -m myo_sim.build.compose --model myofullbody --actuation torque
uv run python -m myo_sim.build.compose --model myofullbody --actuation arms=torque --actuation legs=muscle
```

Each part's torque actuators live in a fourth asset file, parallel to `*_muscle.xml`/`*_tendon.xml`: `myo<part>_torque.xml` (or `myo<part>_r_torque.xml` for parts mirrored from a right-side source, e.g. the arm). It contains only `<general name="mot_<joint>" joint="<joint>" gear="..." ctrlrange="-1 1" forcerange="..."/>` elements — one per independently-actuated joint in that part's chain file. Joints that are passive and driven by polynomial `<equality>` constraints (e.g. the leg's `knee_angle_beta_*`/`knee_angle_rotation*`/`knee_angle_translation*`, the arm's scapulothoracic/clavicular joints, the torso's `Abs_*`/`L*_L*_*` segment joints) are not actuated directly and have no entry in the torque file.

`load_torso_spec()`, `load_right_arm_spec()`, `load_mirrored_left_arm_spec()`, and `load_legs_spec()` each accept an `actuation: str` parameter (`"muscle"` or `"torque"`) that selects which pair of files (`tendons_xml`/`muscles_xml` vs. `None`/`torque_xml`) gets merged into that part's child XML by `build_child_xml_from_components()`/`build_mirrored_child_xml()`. `resolve_actuation()` turns a `build_model()`-level `actuation` argument (`None`, a string, or a per-part dict) into a fully-resolved `{PART_TORSO: ..., PART_ARMS: ..., PART_LEGS: ...}` mapping, falling back to each `ModelRegistration.default_actuation` (currently always `"muscle"`) for parts not specified.

Mirroring a torque file works through the existing `mirror_element()` pipeline unchanged — `<general>` motor elements have no sites or tendons, so only the `name`/`joint` attribute renaming (`_r` -> `_l`) applies.

Hand-only and abdomen-only builds (`myohand_r`, `myohands`, `myotorso_abdomen`) do not yet have torque files; their builder functions accept the `actuation` argument for a uniform `BUILDERS` dispatch signature but currently ignore it.

## Key Utilities in utils.py

### `MjSpec.attach(...)`
Expand Down
4 changes: 4 additions & 0 deletions docs/wiki/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Append-only. Add entries at the top (most recent first). Do not edit past entrie

---

## 2026-06-17 — Add torque-driven actuation
Changed: docs/wiki/build-and-composition.md (new "Torque-driven actuation" section), docs/wiki/repository-map.md (file/table updates)
Why: Added per-part torque-motor actuator files (`myo<part>_torque.xml`) and a `build_model(name, actuation=...)` parameter supporting muscle/torque/mixed builds (e.g. torque torso+arms with muscle legs), modeled after MyoHub/myosuite's `torque` branch myoskeleton.

## 2026-06-09 — Initial wiki creation
Changed: docs/wiki/ (all files created)
Why: Established docs/wiki/ as the persistent knowledge layer for agents and contributors, following the myosuite pattern.
7 changes: 4 additions & 3 deletions docs/wiki/repository-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ myo_sim/ # installable Python package
__init__.py # MODELS_DIR, load(), get_path(), composed-model aliases
fragments.py # FragmentInfo and _FragmentRegistry definitions
models/ # all MJCF content — packaged into wheels
arm/assets/ # myoarm_r_chain.xml, myoarm_r_muscles.xml, myoarm_r_tendons.xml, myoarm_r_assets.xml
leg/assets/ # myolegs_chain.xml, myolegs_muscle.xml, myolegs_tendon.xml, myolegs_assets.xml
torso/assets/ # myotorso_chain.xml (+abdomen variant), myotorso_muscle.xml, _tendon.xml, _assets.xml
arm/assets/ # myoarm_r_chain.xml, myoarm_r_muscles.xml, myoarm_r_tendons.xml, myoarm_r_torque.xml, myoarm_r_assets.xml
leg/assets/ # myolegs_chain.xml, myolegs_muscle.xml, myolegs_tendon.xml, myolegs_torque.xml, myolegs_assets.xml
torso/assets/ # myotorso_chain.xml (+abdomen variant), myotorso_muscle.xml, _tendon.xml, _torque.xml, _assets.xml
head/assets/ # myohead_rigid_chain.xml, myohead_simple_assets.xml
meshes/ # 127 shared STL files
contacts/ # myoarm_contacts.xml, myolegs_contacts.xml, myofullbody_contacts.xml
Expand Down Expand Up @@ -45,6 +45,7 @@ docs/wiki/ # canonical wiki location
| Edit a muscle path or via-point | `myo_sim/models/<part>/assets/*_muscle.xml` and `*_tendon.xml` |
| Edit skeleton geometry | `myo_sim/models/<part>/assets/*_chain.xml` |
| Add a composed model | `myo_sim/build/compose.py` — new `BuildStrategy`, builder, `BUILDERS` and `MODEL_REGISTRY` entries |
| Build a part with torque instead of muscles | `build_model(name, actuation=...)` — see `docs/wiki/build-and-composition.md#torque-driven-actuation` |
| Add cross-part contact pairs | `myo_sim/models/contacts/` then `add_contact_pairs()` in `build/compose.py` |
| Add a mesh | `myo_sim/models/meshes/` |
| Edit public composed models | `myo_sim/build/compose.py` — `MODEL_REGISTRY` |
47 changes: 47 additions & 0 deletions examples/torque_actuation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Examples for building torque-driven and mixed muscle/torque models.

Run from the repository root:

uv run python examples/torque_actuation.py

`build_model()`'s `actuation` argument selects per-part actuation mode:
- omitted / None -> every part uses its muscle-driven default
- a single string (e.g. "torque") -> applied uniformly to every part
- a dict keyed by PART_TORSO / PART_ARMS / PART_LEGS -> mixes modes
within one build (parts left out of the dict keep their default)
"""

from myo_sim.build.compose import PART_ARMS, PART_LEGS, PART_TORSO, build_model


def build_torque_fullbody():
"""Full body where every joint is driven by a direct torque motor
instead of muscles+tendons."""
model = build_model("myofullbody", actuation="torque")
print(f"myofullbody (torque): nbody={model.nbody}, njnt={model.njnt}, nu={model.nu}")
return model


def build_mixed_fullbody():
"""Full body with torque-driven torso + arms, but muscle-driven legs --
e.g. for locomotion tasks where leg muscle dynamics matter but upper-body
control can be simplified."""
model = build_model(
"myofullbody",
actuation={PART_TORSO: "torque", PART_ARMS: "torque", PART_LEGS: "muscle"},
)
print(f"myofullbody (mixed torso/arms=torque, legs=muscle): nbody={model.nbody}, njnt={model.njnt}, nu={model.nu}")
return model


def build_muscle_fullbody():
"""Default fully muscle-driven full body, for comparison."""
model = build_model("myofullbody")
print(f"myofullbody (muscle, default): nbody={model.nbody}, njnt={model.njnt}, nu={model.nu}")
return model


if __name__ == "__main__":
build_muscle_fullbody()
build_torque_fullbody()
build_mixed_fullbody()
Loading
Loading