Skip to content

fix: add the payload to the end-effector link instead of overwriting it - #699

Open
SulimanAbdulrazzaq wants to merge 1 commit into
petercorke:mainfrom
SulimanAbdulrazzaq:fix/payload-point-mass
Open

SulimanAbdulrazzaq wants to merge 1 commit into
petercorke:mainfrom
SulimanAbdulrazzaq:fix/payload-point-mass

Conversation

@SulimanAbdulrazzaq

Copy link
Copy Markdown

Summary

payload(m, p) overwrote the last link's mass and centre of mass with the payload's (lastlink.m = m, lastlink.r = p), the quick hack from the MATLAB version mentioned in #134. The docstring says "payload(0) removes added payload", but payload(0) actually zeroed the link's own mass and centre of mass, so the dynamics changed. With the reproduction from #638, Puma560 rne(q, 0, 0) goes from [-0, 16.36782, 4.59518, -0.00517, -0.00394, 0] to [-0, 16.02365, 4.38141, 0, 0, 0].

The signature is unchanged. The function now does what its docstring says:

  • the point mass is combined with the end-effector link's own mass, centre of mass and inertia (inertia about the combined centre of mass, by the parallel axis theorem);
  • the link's own parameters are saved by the first call, so payload(0) restores them and a new payload replaces the previous one instead of accumulating.

It also changes which link gets the payload. links[n - 1] is the end-effector link of a DH robot, but a URDF robot's links includes static links, so there it can be a link in the middle of the chain (URDF UR5: wrist_1_link while ee_links is [wrist_3_link]; URDF Panda: panda_link6 while ee_links is [panda_link8]). The payload now goes on ee_links[0] when the robot has exactly one end-effector. Robots with several end-effectors keep links[n - 1], so nothing that runs today starts failing. RobotProto gains the ee_links property that payload() now reads.

The existing test_payload asserted the overwrite ("payload() overwrites the link's m/r outright"), so it now checks the combined mass and centre of mass and that payload(0) restores the link. New tests:

  • test_payload_zero_is_noop: the robot.payload(m=0, p=zeros) is not a no-op -- RNE result changes #638 reproduction;
  • test_payload_point_mass: a physics check that does not reuse the new formula. The joint-space inertia matrix grows by m Jp' Jp and the gravity load by -m Jp' g, where Jp is the Jacobian of the payload point;
  • test_payload_replaces_previous;
  • test_payload_end_effector_link and test_payload_several_end_effectors in test_ERobot.py.

One limitation is unchanged from before: copy() rebuilds links from their current parameters, so a copy made while a payload is attached carries the payload as part of the link's own parameters.

Related issue

Fixes #638

Checklist

  • PR title follows Conventional Commits (type: description)
  • Tests pass locally (pytest): pytest tests/ --ignore=tests/test_blocks.py --timeout=50 --timeout_method=thread -q gives 821 passed, 12 skipped (Python 3.12, Linux)
  • Added/updated tests for this change, if applicable
  • New/changed code is type-hinted with modern syntax (no new functions; the payload() signature is unchanged)
  • Docstrings updated
  • PR is as small/focused as practical
  • No test files, data files, or notebooks specific to your own project

payload(m, p) replaced the last link's mass and centre of mass with the
payload's, so payload(0), documented as removing the payload, zeroed the
link's own mass and centre of mass and changed the inverse dynamics
(petercorke#638). The point mass is now combined with the link's own mass, centre
of mass and inertia (parallel axis theorem). The link's own parameters
are kept, so payload(0) restores them and a new payload replaces the
previous one.

The payload now goes on the end-effector link when the robot has exactly
one. For a DH robot that is links[n - 1], as before, but a URDF robot's
links list includes static links, so links[n - 1] can be a link in the
middle of the chain, eg. UR5's wrist_1_link rather than wrist_3_link.
Robots with several end-effectors keep links[n - 1].
@SulimanAbdulrazzaq

Copy link
Copy Markdown
Author

@petercorke when you have a moment, could you approve the workflow runs and take a look? payload(m, p) overwrote the end-effector link's own mass and centre of mass, so payload(0) changed the robot's dynamics instead of removing the payload (#638); the payload is now combined with the link. CI is waiting for approval, and the branch has no conflicts.

@petercorke

Copy link
Copy Markdown
Owner

Thanks for this fix. It's a long standing issue. It's raised a couple of issues which I'm still thinking about like to how to generalise to a multi-EE arm and if that even makes sense.

petercorke added a commit that referenced this pull request Sep 27, 2026
Mesh.to_dict()["filename"] now normalizes to forward slashes (SG's
jhavl/swift#152 fix for Swift's JS mesh loader on Windows paths), which
broke test_mesh_filename's native-separator comparison on every Windows
CI job (confirmed on both #698 and #699, unrelated to either PR's own
changes) since SG 1.4.1 released 2026-09-21.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

robot.payload(m=0, p=zeros) is not a no-op -- RNE result changes

2 participants