Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
44 changes: 39 additions & 5 deletions myo_sim/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path
from typing import TYPE_CHECKING

from myo_sim.fragments import FragmentInfo as FragmentInfo, FragmentRegistry as FragmentRegistry

if TYPE_CHECKING:
import mujoco

try:
__version__ = version("myo-sim")
except PackageNotFoundError:
Expand Down Expand Up @@ -58,6 +62,7 @@ def get_xml_path(name: str) -> Path:
"myoarms",
"myofullbody",
"myolegs",
"myolegs26",
"myolegs_abdomen",
"myotorso",
"myotorso_abdomen",
Expand All @@ -79,16 +84,32 @@ def _left_hand_spec():
return load_left_hand_from_arm_spec()


# Maps fragment names to zero-arg callables returning MjSpec (hand-only, no torso
# scaffold). Used by downstream consumers (e.g. myosuite ModelBuilder) so that
# attach_fragment("hand") transparently routes through the compose pipeline instead
# of falling back to a bundled static XML. Includes myohand_l (left hand only);
# there is no myo_sim.load("myohand_l") alias — use this dict or myohands instead.
def _legs_spec():
from myo_sim.build.compose import load_legs_spec

return load_legs_spec()


def _legs26_spec():
from myo_sim.build.compose import load_legs26_spec

return load_legs26_spec()


# Maps fragment names to zero-arg callables returning an editable (uncompiled)
# MjSpec, with no torso scaffold. Used by downstream consumers (e.g. myosuite
# ModelBuilder, assist_sim) so that attaching a fragment routes through the compose
# pipeline instead of falling back to a bundled static XML. Includes myohand_l
# (left hand only); there is no myo_sim.load("myohand_l") alias — use this dict or
# myohands instead. Note "myolegs" here is the bare legs fragment; myo_sim.load(
# "myolegs") differs in that it attaches the legs to a passive torso scaffold.
FRAGMENT_SPEC_BUILDERS: dict[str, object] = {
"hand": _right_hand_spec,
"myohand": _right_hand_spec,
"myohand_r": _right_hand_spec,
"myohand_l": _left_hand_spec,
"myolegs": _legs_spec,
"myolegs26": _legs26_spec,
}


Expand Down Expand Up @@ -117,6 +138,19 @@ def load(name: str) -> tuple:
return model, mujoco.MjData(model)


def build_spec(name: str) -> "mujoco.MjSpec":
"""Return the uncompiled MjSpec for a composed model (mirror of load()).

Unlike load(), this stops at the editable MjSpec so downstream consumers
(e.g. assist_sim) can edit the model -- attach devices, delete bodies, add
actuators -- before compiling it themselves. Covers the generated composed
specs (myoarms, myotorso, myolegs, myolegs26, myofullbody).
"""
from myo_sim.build.compose import build_generated_model_spec

return build_generated_model_spec(name)


def get_path(rel: str) -> Path:
"""Return the absolute Path to a model file by relative path within MODELS_DIR.

Expand Down
77 changes: 77 additions & 0 deletions myo_sim/build/compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,28 @@
LEGS_TENDONS_XML = ROOT / "leg" / "assets" / "myolegs_tendon.xml"
LEGS_MUSCLES_XML = ROOT / "leg" / "assets" / "myolegs_muscle.xml"
LEGS_CHAIN_XML = ROOT / "leg" / "assets" / "myolegs_chain.xml"
LEGS26_ASSETS_XML = ROOT / "leg" / "assets" / "myolegs26_assets.xml"
LEGS26_TENDONS_XML = ROOT / "leg" / "assets" / "myolegs26_tendon.xml"
LEGS26_MUSCLES_XML = ROOT / "leg" / "assets" / "myolegs26_muscle.xml"
LEGS26_CHAIN_XML = ROOT / "leg" / "assets" / "myolegs26_chain.xml"

# Upright "stand" pose for the legs-only 26-muscle base, in the base model's qpos
# layout. Main (independent) joints come from the reference myoLeg26 stand keyframe;
# every coupled/dependent joint is set to its coupler equilibrium, and the free root
# is raised so the feet rest on the pedestal -- together these give a zero-residual,
# fully at-rest pose. Shipped as a keyframe because the couplers cannot be satisfied
# at qpos0.
# fmt: off
LEGS26_STAND_QPOS = (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might want to have those in a separate place instead to bake them in the compose function e.g. we could have a myolegs26_keyframes.xml

0, 0, 0.935868, 0.707107, 0, 0, -0.707107, # free root: pos + (-90deg yaw)
0, 0, 0, -0.003639, -0.395, 0, -0.0143, 0, -0.03429, # right leg: hip/knee/ankle + via-points
-0.03601, 0.06259, 0.02032, 0.05647, 0.02476, -0.02607, -0.3989, -0.02498,
0, 0, 0, -0.003639, -0.395, 0, -0.0143, 0, -0.03429, # left leg
-0.03601, 0.06259, 0.02032, 0.05647, 0.02476, -0.02607, -0.3989, 0.02498,
-0.02855, -0.07933, 0.08132, # iliopsoas_r via-point (x, y, z)
-0.02855, -0.07933, -0.08132, # iliopsoas_l via-point (x, y, z)
)
# fmt: on

TORSO_ROOT_BODY = "Torso"
RIGHT_ARM_ATTACH_SITE = "arm_attach_r"
Expand All @@ -99,6 +121,7 @@ class BuildStrategy(str, Enum):
BOTH_HANDS = "both_hands"
FULLBODY = "fullbody"
LEGS_BODY = "legs_body"
LEGS26_BASE = "legs26_base"
TORSO_ABDOMEN = "torso_abdomen"
LEGS_ABDOMEN = "legs_abdomen"

Expand Down Expand Up @@ -187,6 +210,15 @@ class ModelRegistration:
include_arm_contacts=False,
include_legs=True,
),
"myolegs26": ModelRegistration(
name="myolegs26",
build_strategy=BuildStrategy.LEGS26_BASE,
left_arm_strategy=LEFT_ARM_STRATEGY_NONE,
description="Reduced 26-muscle legs-only base (free root, no torso)",
include_left_arm_contacts=False,
include_arm_contacts=False,
include_legs=True,
),
"myolegs_abdomen": ModelRegistration(
name="myolegs_abdomen",
build_strategy=BuildStrategy.LEGS_ABDOMEN,
Expand Down Expand Up @@ -331,6 +363,29 @@ def load_legs_spec() -> mujoco.MjSpec:
return legs


def load_legs26_spec(include_scene: bool = False) -> mujoco.MjSpec:
"""Load the reduced 26-muscle, legs-only chain (no torso scaffold).

include_scene attaches the standard myosuite scene (floor + lights) for the
standalone base; the bare fragment leaves it off so the chain can be attached
or merged without a stray floor.
"""
legs_xml = build_child_xml_from_components(
model_name="myolegs26_attach",
compiler_meshdir=ROOT,
assets_xml=LEGS26_ASSETS_XML,
tendons_xml=LEGS26_TENDONS_XML,
muscles_xml=LEGS26_MUSCLES_XML,
chain_xml=LEGS26_CHAIN_XML,
root_body_name="myolegs26_root",
root_site_name="legs26_root_attach",
scene_xmls=(SCENE_XML,) if include_scene else (),
)
legs = mujoco.MjSpec.from_string(legs_xml)
legs.compiler.balanceinertia = True
return legs


def load_torso_abdomen_spec() -> mujoco.MjSpec:
abdomen_xml = build_child_xml_from_components(
model_name="myotorso_abdomen_attach",
Expand Down Expand Up @@ -471,6 +526,26 @@ def build_legs_body_model(registration: ModelRegistration) -> mujoco.MjModel:
return build_legs_body_spec(registration).compile()


def build_legs26_base_spec(registration: ModelRegistration) -> mujoco.MjSpec:
"""Standalone legs-only 26-muscle base: the chain with a free root joint and the
standard scene (floor + lights), no torso. Ships a ``stand`` keyframe (upright on the
pedestal, facing the myo_sim heading) because the joint couplers cannot be satisfied
at qpos0. Downstream consumers that supply their own ground (e.g. assist_sim) are
expected to strip the scene."""
legs = load_legs26_spec(include_scene=True)
root_body = find_body(legs, "myolegs26_root")
root_body.add_freejoint(name="root")
root_body.quat = [0.70710678, 0.0, 0.0, -0.70710678] # -90deg yaw about world z (qpos0 heading)
key = legs.add_key()
key.name = "stand"
key.qpos = list(LEGS26_STAND_QPOS)
return legs


def build_legs26_base_model(registration: ModelRegistration) -> mujoco.MjModel:
return build_legs26_base_spec(registration).compile()


def build_torso_abdomen_model(registration: ModelRegistration) -> mujoco.MjModel:
abdomen = load_torso_abdomen_spec()
root_body = find_body(abdomen, "root")
Expand Down Expand Up @@ -501,6 +576,7 @@ def build_legs_abdomen_model(registration: ModelRegistration) -> mujoco.MjModel:
BuildStrategy.BOTH_HANDS: build_both_hands_from_arm_model,
BuildStrategy.FULLBODY: build_fullbody_model,
BuildStrategy.LEGS_BODY: build_legs_body_model,
BuildStrategy.LEGS26_BASE: build_legs26_base_model,
BuildStrategy.TORSO_ABDOMEN: build_torso_abdomen_model,
BuildStrategy.LEGS_ABDOMEN: build_legs_abdomen_model,
}
Expand All @@ -509,6 +585,7 @@ def build_legs_abdomen_model(registration: ModelRegistration) -> mujoco.MjModel:
"myoarms": build_arms_body_spec,
"myotorso": build_torso_body_spec,
"myolegs": build_legs_body_spec,
"myolegs26": build_legs26_base_spec,
"myofullbody": build_fullbody_spec,
}

Expand Down
118 changes: 118 additions & 0 deletions myo_sim/models/leg/assets/myolegs26_assets.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?xml version="1.0" ?>
<mujocoinclude model="MyoLegs26">
<!-- =================================================
Copyright 2020 Vikash Kumar, Vittorio Caggiano, Huawei Wang
Model :: Myo Legs 26 (MuJoCoV3.3.3)
Author :: Vikash Kumar (vikashplus@gmail.com), Vittorio Caggiano, Huawei Wang, (Charlie) Li, Cheryl Wang, Calder Robbins
source :: https://github.com/vikashplus

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you remove this?

License :: Under Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
====================================================== -->

<!-- Reduced legs-only gait model: 26 muscles (OpenSim gait2392 22-muscle base + EDL/FDL).
Original gait2392/gait9dof18 by Ajay Seth, based on Delp et al. (1990); muscle strengths
(Handsfield/Rajagopal) tuned by Carmichael Ong; planar knee (Yamaguchi & Zajac 1989);
patella removed with quadriceps insertions as moving points. License: CC-BY 3.0.
Adapted for MyoLeg by Chun Kwang Tan (sagittal-plane joints, ankle ROM, GRF foot sensors);
extended by Calder Robbins (toes + mtp joints, EDL/FDL muscles). -->

<default class="main">
<joint armature="1e-05" damping="0.05" limited="true" />
<geom margin="0.001" />
<site size="0.001 0.005 0.005" />
<tendon rgba="0.95 0.3 0.3 1" width="0.005" />
<default class="myolegs26_muscle">
<general biasprm="0.75 1.05 -1 200 0.5 1.6 1.5 1.3 1.2 0" biastype="muscle" ctrllimited="true" ctrlrange="0 1" dynprm="0.01 0.04 0 0 0 0 0 0 0 0" dyntype="muscle" gainprm="0.75 1.05 -1 200 0.5 1.6 1.5 1.3 1.2 0" gaintype="muscle" />
</default>
<default class="myolegs26_touch">
<site type="box" group="3" rgba="0.8 0.2 .2 .4" />
</default>
</default>
<asset>
<mesh file="meshes/sacrum.stl" name="pelvis_geom_1_sacrum" />
<mesh file="meshes/r_pelvis.stl" name="pelvis_geom_2_pelvis" />
<mesh file="meshes/l_pelvis.stl" name="pelvis_geom_3_l_pelvis" />
<mesh file="meshes/r_femur.stl" name="femur_r_geom_1_femur" />
<mesh file="meshes/r_tibia.stl" name="tibia_r_geom_1_tibia" />
<mesh file="meshes/r_fibula.stl" name="tibia_r_geom_2_fibula" />
<mesh file="meshes/r_talus.stl" name="talus_r_geom_1_talus" />
<mesh file="meshes/r_foot.stl" name="calcn_r_geom_1_foot" />
<mesh file="meshes/r_bofoot.stl" name="toes_r_geom_1_bofoot" />
<mesh file="meshes/l_femur.stl" name="femur_l_geom_1_l_femur" />
<mesh file="meshes/l_tibia.stl" name="tibia_l_geom_1_l_tibia" />
<mesh file="meshes/l_fibula.stl" name="tibia_l_geom_2_l_fibula" />
<mesh file="meshes/l_talus.stl" name="talus_l_geom_1_l_talus" />
<mesh file="meshes/l_foot.stl" name="calcn_l_geom_1_l_foot" />
<mesh file="meshes/l_bofoot.stl" name="toes_l_geom_1_l_bofoot" />
</asset>
<equality>
<joint joint1="knee_r_translation1" joint2="knee_angle_r" polycoef="-0.003639 -0.006267 0.002807 0.00134 -0.0006776" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="knee_r_translation2" joint2="knee_angle_r" polycoef="-0.395 0.003413 -0.005587 0.0006697 0.0005674" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="knee_l_translation1" joint2="knee_angle_l" polycoef="-0.003639 -0.006267 0.002807 0.00134 -0.0006776" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="knee_l_translation2" joint2="knee_angle_l" polycoef="-0.395 0.003413 -0.005587 0.0006697 0.0005674" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="hamstrings_r_semimem_r-P2_x" joint2="knee_angle_r" polycoef="-0.03429 -0.005119 0.001791 0.001624 0.0002251" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="hamstrings_r_semimem_r-P2_y" joint2="knee_angle_r" polycoef="-0.03601 0.008217 -0.002875 -0.002607 -0.0003613" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="iliopsoas_r_psoas_r-P3_x" joint2="hip_flexion_r" polycoef="-0.02855 0.001773 0.001122 -0.0001037 -0.0001111" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="iliopsoas_r_psoas_r-P3_y" joint2="hip_flexion_r" polycoef="-0.07933 0.008332 0.005272 -0.0004875 -0.0005223" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="iliopsoas_r_psoas_r-P3_z" joint2="hip_flexion_r" polycoef="0.08132 -0.002021 -0.001279 0.0001182 0.0001267" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="rect_fem_r_rect_fem_r-P3_x" joint2="knee_angle_r" polycoef="0.06259 0.04199 0.01152 -0.007248 -0.004008" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="rect_fem_r_rect_fem_r-P3_y" joint2="knee_angle_r" polycoef="0.02032 -0.01159 -0.008624 -0.002308 -0.0002292" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="vasti_r_vas_int_r-P4_x" joint2="knee_angle_r" polycoef="0.05647 0.04555 0.01393 -0.007247 -0.004235" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="vasti_r_vas_int_r-P4_y" joint2="knee_angle_r" polycoef="0.02476 -0.006322 -0.00661 -0.003684 -0.0008973" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="gastroc_r_med_gas_r-P2_x" joint2="knee_angle_r" polycoef="-0.02607 0.00676 0.0009439 -0.001031 -6.818e-05" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="gastroc_r_med_gas_r-P2_y" joint2="knee_angle_r" polycoef="-0.3989 0.005715 0.000798 -0.000872 -5.764e-05" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="gastroc_r_med_gas_r-P2_z" joint2="knee_angle_r" polycoef="-0.02498 0.001413 0.0001974 -0.0002157 -1.426e-05" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="hamstrings_l_semimem_l-P2_x" joint2="knee_angle_l" polycoef="-0.03429 -0.005119 0.001791 0.001624 0.0002251" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="hamstrings_l_semimem_l-P2_y" joint2="knee_angle_l" polycoef="-0.03601 0.008217 -0.002875 -0.002607 -0.0003613" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="iliopsoas_l_psoas_l-P3_x" joint2="hip_flexion_l" polycoef="-0.02855 0.001773 0.001122 -0.0001037 -0.0001111" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="iliopsoas_l_psoas_l-P3_y" joint2="hip_flexion_l" polycoef="-0.07933 0.008332 0.005272 -0.0004875 -0.0005223" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="iliopsoas_l_psoas_l-P3_z" joint2="hip_flexion_l" polycoef="-0.08132 0.002021 0.001279 -0.0001182 -0.0001267" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="rect_fem_l_rect_fem_l-P3_x" joint2="knee_angle_l" polycoef="0.06259 0.04199 0.01152 -0.007248 -0.004008" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="rect_fem_l_rect_fem_l-P3_y" joint2="knee_angle_l" polycoef="0.02032 -0.01159 -0.008624 -0.002308 -0.0002292" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="vasti_l_vas_int_l-P4_x" joint2="knee_angle_l" polycoef="0.05647 0.04555 0.01393 -0.007247 -0.004235" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="vasti_l_vas_int_l-P4_y" joint2="knee_angle_l" polycoef="0.02476 -0.006322 -0.00661 -0.003684 -0.0008973" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="gastroc_l_med_gas_l-P2_x" joint2="knee_angle_l" polycoef="-0.02607 0.00676 0.0009439 -0.001031 -6.818e-05" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="gastroc_l_med_gas_l-P2_y" joint2="knee_angle_l" polycoef="-0.3989 0.005715 0.000798 -0.000872 -5.764e-05" solimp="0.9999 0.9999 0.001 0.5 2" />
<joint joint1="gastroc_l_med_gas_l-P2_z" joint2="knee_angle_l" polycoef="0.02498 -0.001413 -0.0001974 0.0002157 1.426e-05" solimp="0.9999 0.9999 0.001 0.5 2" />
</equality>
<sensor>
<touch name="r_foot" site="r_foot_touch" />
<touch name="r_toes" site="r_toes_touch" />
<touch name="l_foot" site="l_foot_touch" />
<touch name="l_toes" site="l_toes_touch" />
<jointlimitfrc name="r_knee_sensor" joint="knee_angle_r" />
<jointlimitfrc name="l_knee_sensor" joint="knee_angle_l" />
<jointlimitfrc name="r_hip_sensor" joint="hip_flexion_r" />
<jointlimitfrc name="l_hip_sensor" joint="hip_flexion_l" />
<jointlimitfrc name="r_ankle_sensor" joint="ankle_angle_r" />
<jointlimitfrc name="l_ankle_sensor" joint="ankle_angle_l" />
<jointlimitfrc name="r_mtp_sensor" joint="mtp_angle_r" />
<jointlimitfrc name="l_mtp_sensor" joint="mtp_angle_l" />
</sensor>
<contact>
<pair geom1="femur_r_geom_1" geom2="femur_l_geom_1" />
<pair geom1="femur_r_geom_1" geom2="tibia_l_geom_1" />
<pair geom1="femur_r_geom_1" geom2="tibia_l_geom_2" />
<pair geom1="femur_r_geom_1" geom2="talus_l_geom_1" />
<pair geom1="femur_r_geom_1" geom2="calcn_l_geom_1" />
<pair geom1="tibia_r_geom_1" geom2="femur_l_geom_1" />
<pair geom1="tibia_r_geom_1" geom2="tibia_l_geom_1" />
<pair geom1="tibia_r_geom_1" geom2="tibia_l_geom_2" />
<pair geom1="tibia_r_geom_1" geom2="talus_l_geom_1" />
<pair geom1="tibia_r_geom_1" geom2="calcn_l_geom_1" />
<pair geom1="tibia_r_geom_2" geom2="femur_l_geom_1" />
<pair geom1="tibia_r_geom_2" geom2="tibia_l_geom_1" />
<pair geom1="tibia_r_geom_2" geom2="tibia_l_geom_2" />
<pair geom1="tibia_r_geom_2" geom2="talus_l_geom_1" />
<pair geom1="tibia_r_geom_2" geom2="calcn_l_geom_1" />
<pair geom1="talus_r_geom_1" geom2="femur_l_geom_1" />
<pair geom1="talus_r_geom_1" geom2="tibia_l_geom_1" />
<pair geom1="talus_r_geom_1" geom2="tibia_l_geom_2" />
<pair geom1="talus_r_geom_1" geom2="talus_l_geom_1" />
<pair geom1="talus_r_geom_1" geom2="calcn_l_geom_1" />
<pair geom1="calcn_r_geom_1" geom2="femur_l_geom_1" />
<pair geom1="calcn_r_geom_1" geom2="tibia_l_geom_1" />
<pair geom1="calcn_r_geom_1" geom2="tibia_l_geom_2" />
<pair geom1="calcn_r_geom_1" geom2="talus_l_geom_1" />
<pair geom1="calcn_r_geom_1" geom2="calcn_l_geom_1" />
</contact>
</mujocoinclude>
Loading
Loading