Skip to content

Adds fixed tendon support to articulation#5164

Draft
nv-rgresia wants to merge 3 commits intoisaac-sim:developfrom
nv-rgresia:fixed-tendons-impl
Draft

Adds fixed tendon support to articulation#5164
nv-rgresia wants to merge 3 commits intoisaac-sim:developfrom
nv-rgresia:fixed-tendons-impl

Conversation

@nv-rgresia
Copy link
Copy Markdown
Contributor

Description

Updates articulation to support the Mujoco actuator control api in order to allow control over fixed tendons. I tried to stick to existing patterns where possible, but articulation/data may need to be overhauled to support mjc actuators + tendons properly since they are no longer tied to a joint; configuration updates to follow. A prototype testing environment is included, but needs to be upgraded to a proper tutorial enviroment before merge.

Type of change

  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the changelog and the corresponding version in the extension's config/extension.toml file
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

@nv-rgresia nv-rgresia self-assigned this Apr 3, 2026
@nv-rgresia nv-rgresia added enhancement New feature or request help wanted Extra attention is needed labels Apr 3, 2026
@github-actions github-actions bot added bug Something isn't working asset New asset feature or request labels Apr 3, 2026
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

This PR adds fixed tendon support to the Newton articulation by implementing MuJoCo actuator control APIs. However, the implementation is incomplete and contains several critical bugs that will cause runtime failures. The environment code calls methods with arguments that don't exist in the function signatures.

Design Assessment

Problem being solved: Enable control of fixed tendons in articulations via MuJoCo's actuator CTRL buffer.

Design evaluation:

  • ✅ Placing tendon control in the Articulation class is correct — articulations own their actuators/tendons
  • ✅ Using _tendon_name_to_ctrl_idx mapping is a reasonable approach for translating tendon names to ctrl indices
  • ⚠️ The ctrl buffer is 1D (actuator_count,) instead of 2D (num_instances, actuator_count), breaking per-environment control
  • ⚠️ _process_tendons() unconditionally requires MuJoCo actuator attributes, breaking non-tendon articulations

Alternative considered: Could have added ctrl support as a separate mixin or manager, but integrating into Articulation directly is more consistent with how joint control already works.

Verdict: Acceptable short-term design, but the buffer shapes need to follow the (num_instances, num_X) convention for proper per-environment support.

Architecture Impact

  • num_fixed_tendons and fixed_tendon_names now return real data from _root_view.tendon_count/tendon_names
  • _process_tendons() now unconditionally requires MuJoCo attributes — this will break any Newton articulation that doesn't have MuJoCo actuators
  • New write_actuator_ctrl_to_sim() method added to public API
  • New set_fixed_tendon_ctrl() method added but is non-functional (empty body)

Implementation Verdict

Needs rework — Multiple critical bugs that will cause immediate runtime failures.

Test Coverage

🟡 Warning: No unit tests included. This is a new feature that adds:

  • New public methods (write_actuator_ctrl_to_sim, set_fixed_tendon_ctrl)
  • New data buffers (_actuator_ctrl, _tendon_stiffness, _tendon_damping)
  • Modified initialization logic (_process_tendons)

At minimum, tests should verify:

  1. Articulations with tendons can be created and ctrl buffers are accessible
  2. write_actuator_ctrl_to_sim correctly updates the simulation
  3. Articulations WITHOUT tendons still work (regression test for the _process_tendons change)

CI Status

  • pre-commit — failing (likely formatting issues)
  • environments_training — failing
  • Multiple other checks pending/skipped

Findings

🔴 Critical: articulation.py:3143-3153 — set_fixed_tendon_ctrl is an empty stub
The method has a statement before the docstring, making the docstring a no-op string. More importantly, the method body does nothing after resolving env_ids — it never actually sets the ctrl buffer.

🔴 Critical: articulation.py:3155 — write_actuator_ctrl_to_sim signature mismatch
The method takes no arguments (def write_actuator_ctrl_to_sim(self) -> None), but the environment code calls it with ctrl=[-1.0, -1.0]. This will raise TypeError: got unexpected keyword argument 'ctrl'.

🔴 Critical: articulation.py:3497 — _process_tendons breaks non-tendon articulations
The method unconditionally raises Exception("Mujoco has no actuator trntype") when MuJoCo actuator attributes are missing. This breaks any Newton articulation that doesn't use MuJoCo actuators. Should check if self.num_fixed_tendons > 0 before requiring these attributes.

🔴 Critical: articulation_data.py:1371 — actuator_count may not be initialized
actuator_count is only set inside a conditional block at line 1337, but accessed unconditionally at line 1371. This will raise AttributeError when MuJoCo actuator attributes are missing.

🟡 Warning: articulation_data.py:1372-1373 — Ctrl buffers have wrong shape
Buffers are shaped (actuator_count,) (1D) instead of (num_instances, actuator_count) (2D). This means ctrl values are shared across all environments instead of being per-environment. All other articulation buffers use the 2D pattern.

🟡 Warning: capsules.py:29 — Hardcoded local path
The USD path /home/rgresia/Repositories/tendon-assets/... only works on the author's machine. Should use ISAAC_NUCLEUS_DIR or a relative path.

🔵 Improvement: capsules.py:7-16 — Wrong docstring
Docstring describes Shadow Robot hand but this file is for capsules. Also, ImplicitActuatorCfg is imported but unused.

🔵 Improvement: capsules_flex_env.py:82 — Wrong type annotation syntax
tuple(torch.Tensor, torch.Tensor) should be tuple[torch.Tensor, torch.Tensor]. Using parentheses calls the tuple constructor instead of creating a type annotation.

🔵 Improvement: articulation.py:3249 — Missing space
self._ALL_ACTUATOR_INDICES =wp.array(...) is missing a space before =.

Args:
ctrl: Value(s) to update. If array, shape is
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical: Empty method body

This method does nothing after resolving env_ids. The docstring is placed after the first statement, so it becomes a no-op string expression. The actual ctrl-setting logic is missing.

Suggested change
def set_fixed_tendon_ctrl(
self,
*,
ctrl: float | torch.Tensor | wp.array | None = None,
tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None,
env_ids: Sequence[int] | wp.array | None = None,
) -> None:
"""Set the Mujoco CTRL buffer for fixed tendon actuators.
.. note::
This method only sets the ctrl buffer. Call write_actuator_ctrl_to_sim to update the sim buffer.
Args:
ctrl: Value(s) to update. Shape should be (len(env_ids), len(tendon_ids)) or broadcastable.
tendon_ids: Tendon indices to update. If None, all tendons are updated.
env_ids: Environment indices. If None, all environments are updated.
"""
env_ids = self._resolve_env_ids(env_ids)
# TODO: Implement actual ctrl buffer update logic
raise NotImplementedError("set_fixed_tendon_ctrl is not yet implemented")



def write_actuator_ctrl_to_sim(self) -> None:
self.data._sim_bind_actuator_ctrl.assign(self.data._actuator_ctrl)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical: Signature doesn't match usage

This method takes no arguments, but the environment code calls:

  • write_actuator_ctrl_to_sim(ctrl=[-1.0, -1.0]) (line 56)
  • write_actuator_ctrl_to_sim(ctrl=wp.to_torch(...)[env_ids]) (line 98)

Both will raise TypeError: got unexpected keyword argument 'ctrl'.

Either add a ctrl parameter here, or remove the ctrl= argument from the env calls and have the caller set self.data._actuator_ctrl first.

model = SimulationManager.get_model()
mujoco_attrs = getattr(model, "mujoco", None)
if mujoco_attrs is None or not hasattr(mujoco_attrs, "actuator_trntype"):
raise Exception("Mujoco has no actuator trntype")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical: Breaks non-tendon articulations

This unconditionally raises when MuJoCo actuator attributes are missing, but _process_tendons() is called for ALL articulations. Any Newton articulation without MuJoCo actuators will crash here.

Suggested change
raise Exception("Mujoco has no actuator trntype")
if mujoco_attrs is None or not hasattr(mujoco_attrs, "actuator_trntype"):
# No MuJoCo actuators — nothing to process for tendons
return

self._ALL_ENV_MASK = wp.ones((self.num_instances,), dtype=wp.bool, device=self.device)
self._ALL_JOINT_INDICES = wp.array(np.arange(self.num_joints, dtype=np.int32), device=self.device)
self._ALL_JOINT_MASK = wp.ones((self.num_joints,), dtype=wp.bool, device=self.device)
self._ALL_ACTUATOR_INDICES =wp.array(np.arange(self.data.actuator_count, dtype=np.int32), device=self.device)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Style: Missing space before =

Suggested change
self._ALL_ACTUATOR_INDICES =wp.array(np.arange(self.data.actuator_count, dtype=np.int32), device=self.device)
self._ALL_ACTUATOR_INDICES = wp.array(np.arange(self.data.actuator_count, dtype=np.int32), device=self.device)

)

# -- mujoco ctrl api
if self.actuator_count > 0:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical: actuator_count may not be defined

self.actuator_count is only set at line 1337 inside if hasattr(mujoco_attrs, "actuator_trntype"). If that condition is false, this line raises AttributeError.

Suggested change
if self.actuator_count > 0:
# -- mujoco ctrl api
if hasattr(self, 'actuator_count') and self.actuator_count > 0:

Or better: initialize self.actuator_count = 0 before the conditional block in _create_simulation_bindings.


CAPSULES_CFG = ArticulationCfg(
spawn=sim_utils.UsdFileCfg(
usd_path=f"/home/rgresia/Repositories/tendon-assets/tendon-capsules.usda/capsules.usda",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical: Hardcoded local path

This path only exists on your machine. Use a Nucleus path or a path relative to the asset directory.

Suggested change
usd_path=f"/home/rgresia/Repositories/tendon-assets/tendon-capsules.usda/capsules.usda",
usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/Capsules/capsules.usda",

Or if the asset isn't on Nucleus yet, document that this is a placeholder and add a TODO.

# SPDX-License-Identifier: BSD-3-Clause

"""Configuration for the dexterous hand from Shadow Robot.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Improvement: Wrong docstring

This docstring describes Shadow Robot hand, but this file defines capsules configuration. Also, ImplicitActuatorCfg is imported but unused.

Suggested change
"""Configuration for a tendon-actuated capsule robot.
The following configurations are available:
* :obj:`CAPSULES_CFG`: Capsule robot with fixed tendon actuators.
"""

self.joint_vel = wp.to_torch(self.robot.data.joint_vel)

time_out = self.episode_length_buf >= self.max_episode_length - 1
out_of_bounds = torch.any(torch.abs(self.joint_pos) > math.pi , dim=1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Style: Wrong type annotation syntax

tuple(...) calls the constructor. Use tuple[...] for type annotations.

Suggested change
out_of_bounds = torch.any(torch.abs(self.joint_pos) > math.pi , dim=1)
def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]:

obs = torch.cat(
(
self.joint_pos[:, 0].unsqueeze(dim=1),
self.joint_pos[:, 1].unsqueeze(dim=1),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical: Method signature mismatch

write_actuator_ctrl_to_sim() takes no arguments, but you're passing ctrl=. This will raise TypeError.

Either:

  1. Update write_actuator_ctrl_to_sim to accept a ctrl parameter, or
  2. Set self.robot.data._actuator_ctrl directly before calling the method


self.robot.write_actuator_ctrl_to_sim(ctrl=wp.to_torch(self.robot.data._default_actuator_ctrl)[env_ids])

# self.robot.write_root_pose_to_sim_index(default_root_state[:, :7], env_ids)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical: Multiple issues

  1. Same signature mismatch as line 56 — ctrl= is not a valid argument
  2. _default_actuator_ctrl is shaped (actuator_count,), so [env_ids] indexes into actuators, not environments. This is semantically wrong.
  3. Accessing private _default_actuator_ctrl directly — should use a public API

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

asset New asset feature or request bug Something isn't working enhancement New feature or request help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant