Skip to content

add articulation affordance - #509

Open
matafela wants to merge 10 commits into
mainfrom
cj/add-articulation-affordance
Open

add articulation affordance#509
matafela wants to merge 10 commits into
mainfrom
cj/add-articulation-affordance

Conversation

@matafela

@matafela matafela commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Description

  • Add articulation affordance
  • Add turn knob action: python scripts/tutorials/atomic_action/turn_knob.py (--rigid_object)
  • Add press button action: python scripts/tutorials/atomic_action/press_button.py (--rigid_object)
  • Add pull/push articulation part (for drawer, translation only): python scripts/tutorials/atomic_action/pull_push_articulated_part.py

Type of change

  • Enhancement (non-breaking change which improves an existing functionality)

Checklist

  • I have run the black . command to format the code base.
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • Dependencies have been updated, if applicable.

Copilot AI lite review requested due to automatic review settings August 12, 2026 11:36
@matafela
matafela requested a review from skywhite1024 August 12, 2026 11:38
@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds articulation-aware affordances and atomic actions for turning knobs, pressing buttons, and pulling or pushing articulated parts, while replacing the legacy press action.

  • Adds target geometry and live-pose handling for articulation links and rigid objects.
  • Registers and documents the three new action primitives.
  • Adds tutorials, benchmarks, and action, affordance, engine, and import tests.

Confidence Score: 3/5

The PR does not appear safe to merge until valid vertical turn and press axes can produce trajectory frames without raising.

Turn and press frame construction still crosses each transformed action axis with fixed world-up and raises for parallel axes, so both planners can reject valid affordances before producing trajectories.

Files Needing Attention: embodichain/lab/sim/atomic_actions/affordance.py, embodichain/lab/sim/atomic_actions/primitives/turn_knob.py, embodichain/lab/sim/atomic_actions/primitives/press_button.py

Important Files Changed

Filename Overview
embodichain/lab/sim/atomic_actions/affordance.py Adds turn, press-button, and pull/push affordances; the previously reported vertical-axis frame failures remain.
embodichain/lab/sim/atomic_actions/primitives/turn_knob.py Adds trajectory planning for knob approach, grasp, rotation, release, and retraction, but still relies on the restricted grasp-frame construction.
embodichain/lab/sim/atomic_actions/primitives/press_button.py Adds approach, press, and retract planning, but still relies on the restricted press-frame construction.
embodichain/lab/sim/atomic_actions/primitives/pull_push_articulated_part.py Adds linear pull and push trajectory planning around articulation-backed antipodal grasps.
tests/sim/atomic_actions/test_affordance.py Adds validation and pose-construction coverage for the new affordances.

Reviews (9): Last reviewed commit: "fix module import" | Re-trigger Greptile

Comment thread embodichain/lab/sim/atomic_actions/affordance.py
return parser.parse_args()


def create_microwave(sim) -> Articulation:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Public API annotations are incomplete

The new create_microwave helper leaves sim untyped, while MicrowaveOven.__init__ declares data_root as str despite accepting None, giving type checkers and API consumers incomplete or inaccurate signatures.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/tutorials/atomic_action/turn_knob.py
Line: 78

Comment:
**Public API annotations are incomplete**

The new `create_microwave` helper leaves `sim` untyped, while `MicrowaveOven.__init__` declares `data_root` as `str` despite accepting `None`, giving type checkers and API consumers incomplete or inaccurate signatures.

**Context Used:** AGENTS.md ([source](https://github.com/dexforce/embodichain/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code

Copilot AI left a comment

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.

Pull request overview

This PR extends the atomic-actions subsystem with articulation-backed affordances and a new TurnKnob primitive, enabling deterministic knob-turn planning from an articulation link’s live pose/geometry and providing an end-to-end tutorial + docs/tests.

Changes:

  • Added TurnAffordance (articulation-link knob semantics) and TurnKnob atomic action (approach → reach → close → turn → open → retract).
  • Extended AntipodalAffordance to optionally resolve mesh/pose from an articulation link.
  • Added tests, docs, and a new tutorial script demonstrating knob turning on a microwave articulation.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/sim/atomic_actions/test_affordance.py Adds coverage for articulation-backed antipodal affordances and TurnAffordance grasp-pose behavior.
tests/sim/atomic_actions/test_actions.py Adds coverage for TurnKnob planning/segments and validates affordance-type requirements.
scripts/tutorials/atomic_action/tutorial_utils.py Adds optional init_qpos support for tutorial robot setup.
scripts/tutorials/atomic_action/turn_knob.py New tutorial demonstrating TurnKnob on a microwave articulation.
embodichain/lab/sim/atomic_actions/primitives/turn_knob.py New TurnKnob primitive, goal, and options implementation.
embodichain/lab/sim/atomic_actions/primitives/init.py Registers TurnKnob as a built-in primitive and exports symbols.
embodichain/lab/sim/atomic_actions/affordance.py Adds TurnAffordance and extends AntipodalAffordance to support articulation-link geometry/pose resolution.
embodichain/lab/sim/atomic_actions/init.py Re-exports TurnAffordance and TurnKnob* public API.
embodichain/data/assets/obj_assets.py Adds a MicrowaveOven dataset helper entry for the tutorial asset.
docs/source/overview/sim/atomic_actions/index.md Updates built-in action count reference (now 10).
docs/source/overview/sim/atomic_actions/builtin_actions.md Documents TurnKnob contract and adds it to the built-in actions table.
docs/source/api_reference/embodichain/embodichain.lab.sim.atomic_actions.primitives.rst Adds API reference entries for TurnKnob, TurnKnobGoal, TurnKnobOptions.
Suppressed comments (2)

embodichain/lab/sim/atomic_actions/primitives/turn_knob.py:206

  • TurnKnob allocates the output trajectory with self.n_envs rows, but fills it from context.last_qpos and hand_* tensors built with context.batch_size. This will error if those batch sizes ever diverge. Allocate using context.batch_size (or link_pose.shape[0]) so the tensor shapes are consistent within _plan.
        full = torch.empty(
            (self.n_envs, sum(lengths), self.robot_dof),
            dtype=context.robot.qpos.dtype,
            device=self.device,
        )
        full[:] = context.last_qpos.unsqueeze(1)

scripts/tutorials/atomic_action/tutorial_utils.py:759

  • create_ur5_gripper_robot_cfg adds an init_qpos parameter but the docstring Args section doesn't document it, which makes the function contract unclear in the tutorial utilities.
    init_qpos: Sequence[float] | None = None,
) -> RobotCfg:
    """Build a UR5 arm + DH_PGI_140_80 gripper robot configuration.

    The arm is taken from :class:`~embodichain.lab.sim.robots.ur_robot.URRobotCfg`

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +328 to +336
y_axis = torch.tensor(
[0.0, 0.0, 1.0], dtype=torch.float32, device=device
).expand_as(z_axis)
x_axis = torch.linalg.cross(y_axis, z_axis, dim=1)
if torch.any(torch.linalg.vector_norm(x_axis, dim=1) <= 1.0e-6):
raise ValueError(
"TurnAffordance turn axis must not be parallel to world (0, 0, 1)."
)
x_axis = torch.nn.functional.normalize(x_axis, dim=1)
Comment on lines +123 to +130
link_pose = affordance.get_link_pose().to(
device=self.device, dtype=torch.float32
)
if link_pose.shape != (self.n_envs, 4, 4):
raise ValueError(
"Articulation link pose must have shape "
f"({self.n_envs}, 4, 4), got {tuple(link_pose.shape)}."
)
Comment on lines 224 to 228
def add_ur5_gripper_robot(
sim: SimulationManager,
init_pos: Sequence[float] = (0.0, 0.0, 0.0),
init_qpos: Sequence[float] | None = None,
) -> Robot:
Copilot AI review requested due to automatic review settings August 14, 2026 03:39

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (3)

scripts/tutorials/atomic_action/tutorial_utils.py:784

  • init_qpos is accepted as an arbitrary sequence, but there is no validation that it matches the expected number of joints for this tutorial robot (arm + gripper). A length mismatch will fail later during robot initialization/reset with a harder-to-debug error.
    qpos = (
        [0.0, -1.57, 1.57, -1.57, -1.57, 0.0, 0.0, 0.0]
        if init_qpos is None
        else list(init_qpos)
    )

scripts/tutorials/atomic_action/tutorial_utils.py:230

  • The new init_qpos parameter is not mentioned in the function docstring, so it’s easy to miss that callers can override the default tutorial joint pose.
    """Add the standard UR5 plus PGI gripper tutorial robot.

scripts/tutorials/atomic_action/tutorial_utils.py:758

  • init_qpos was added to the signature, but the docstring description doesn’t mention what it does. Adding a short note here makes the new capability discoverable without scanning the whole function.

This issue also appears on line 780 of the same file.

    """Build a UR5 arm + DH_PGI_140_80 gripper robot configuration.

Copilot AI review requested due to automatic review settings August 17, 2026 01:48

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Suppressed comments (2)

embodichain/lab/sim/atomic_actions/primitives/turn_knob.py:115

  • TurnKnob mixes batch dimensions: hand joint targets are built with context.batch_size, but the rest of the planner (link_pose shape check, trajectory allocation) assumes self.n_envs from the bound robot. If PlanningContext.batch_size differs (env subset planning), this can trigger shape mismatches or incorrect broadcasting.
        hand_open_qpos = end_effector.joint_positions(
            OPEN_COMMAND,
            n_envs=context.batch_size,
            device=self.device,
            dtype=context.robot.qpos.dtype,
        )

embodichain/data/assets/obj_assets.py:257

  • The new MicrowaveOven dataset block has formatting that deviates from the surrounding DataDescriptor pattern (closing paren on the same line, extra whitespace) and is likely to fail black/linters. It also removes the blank line separation between dataset classes.
        data_descriptor = o3d.data.DataDescriptor(
            os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "MicrowaveOven.zip"),
            "5c90aa6911b445811fc81d704d461057",        )
        prefix = type(self).__name__

Copilot AI review requested due to automatic review settings August 17, 2026 03:31

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (2)

embodichain/lab/sim/atomic_actions/affordance.py:332

  • TurnAffordance.get_grasp_pose constructs a rotation matrix with y_axis fixed to world up without re-orthogonalizing it against z_axis. If the provided turn_axis has any world-up component, y_axis will not be perpendicular to z_axis and the resulting pose will not be a valid orthonormal transform (can break downstream FK/IK / relative-rotation math). Project world-up onto the plane orthogonal to z_axis and normalize before computing x_axis.
        y_axis = torch.tensor(
            [0.0, 0.0, 1.0], dtype=torch.float32, device=device
        ).expand_as(z_axis)
        x_axis = torch.linalg.cross(y_axis, z_axis, dim=1)
        if torch.any(torch.linalg.vector_norm(x_axis, dim=1) <= 1.0e-6):

embodichain/data/assets/obj_assets.py:246

  • MicrowaveOven dataset docstring points to "MicrowaveOven/microwave_oven.urdf", but this PR’s new TurnKnob tutorial uses "MicrowaveOven/microwave_oven_with_inertials.urdf". This mismatch is confusing for users trying to locate the correct asset path via get_data_path(). Consider updating the docstring to match the tutorial asset (or mention both URDFs if both are shipped).
class MicrowaveOven(EmbodiChainDataset):
    """get_data_path("MicrowaveOven/microwave_oven.urdf")"""

Copilot AI review requested due to automatic review settings August 17, 2026 08:00
Comment thread embodichain/lab/sim/atomic_actions/affordance.py

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Suppressed comments (2)

docs/source/api_reference/embodichain/embodichain.lab.sim.atomic_actions.rst:75

  • The autosummary list was updated for PressButton*, but it still omits the new TurnKnob* symbols and TurnAffordance, even though they are exported from embodichain.lab.sim.atomic_actions. This makes the generated API reference incomplete.
      PressGoal
      PressButtonGoal
      PressButtonOptions
      PressButtonAffordance
      CoordinatedPickGoal

embodichain/data/assets/obj_assets.py:246

  • The MicrowaveOven dataset docstring example path is inconsistent with the new tutorials (which reference microwave_oven_with_inertials.urdf). Updating this example avoids confusion about which asset path is intended.
class MicrowaveOven(EmbodiChainDataset):
    """get_data_path("MicrowaveOven/microwave_oven.urdf")"""

angle_b = get_relative_rotation(
reference_xpos[:, :3, :3], symmetric_xpos[:, :3, :3]
)
target_xpos = torch.where(angle_a < angle_b, target_xpos, symmetric_xpos)
angle_b = get_relative_rotation(
reference_xpos[:, :3, :3], symmetric_xpos[:, :3, :3]
)
target_xpos = torch.where(angle_a < angle_b, target_xpos, symmetric_xpos)
Copilot AI review requested due to automatic review settings August 17, 2026 08:33

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

Suppressed comments (4)

embodichain/lab/sim/atomic_actions/affordance.py:336

  • TurnAffordance.get_grasp_pose currently sets y_axis to world up and sets z_axis from the turn axis, but it never re-orthogonalizes y_axis against z_axis. Unless z_axis is exactly perpendicular to world up, the resulting rotation is not orthonormal (y·z != 0), which can break downstream IK / motion generation that assumes valid rotation matrices.

Compute y_axis as cross(z_axis, x_axis) after constructing x_axis from a world-up reference vector.

        y_axis = torch.tensor(
            [0.0, 0.0, 1.0], dtype=torch.float32, device=device
        ).expand_as(z_axis)
        x_axis = torch.linalg.cross(y_axis, z_axis, dim=1)
        if torch.any(torch.linalg.vector_norm(x_axis, dim=1) <= 1.0e-6):
            raise ValueError(
                "TurnAffordance turn axis must not be parallel to world (0, 0, 1)."
            )
        x_axis = torch.nn.functional.normalize(x_axis, dim=1)

embodichain/lab/sim/atomic_actions/affordance.py:310

  • The TurnAffordance.get_grasp_pose docstring states that the pose y-axis is fixed to world (0, 0, 1), but the implementation needs to (and should) orthonormalize the frame for arbitrary turn axes. The docstring should describe that world-up is used as a reference to build an orthonormal basis rather than being kept as an exact axis.
        The pose z-axis follows :attr:`turn_axis` transformed into the world
        frame, while its y-axis is fixed to world ``(0, 0, 1)``.

docs/source/api_reference/embodichain/embodichain.lab.sim.atomic_actions.rst:75

  • The atomic actions API reference autosummary was updated for PressButton, but it doesn’t list the newly added TurnKnob / TurnAffordance symbols. This makes the public API docs incomplete/inconsistent with the new built-ins exported from embodichain.lab.sim.atomic_actions.
      PressGoal
      PressButtonGoal
      PressButtonOptions
      PressButtonAffordance
      CoordinatedPickGoal

embodichain/lab/sim/atomic_actions/affordance.py:450

  • PressButtonAffordance.get_press_pose sets y_axis to world up but doesn’t enforce orthogonality between y_axis and the computed z_axis. For press axes that aren’t perpendicular to world up, this produces a non-orthonormal rotation matrix (invalid transform).

Derive y_axis from cross(z_axis, x_axis) after constructing x_axis from the world-up reference vector.

        z_axis = torch.matmul(link_pose[:, :3, :3], press_axis)
        z_axis = torch.nn.functional.normalize(z_axis, dim=1)
        y_axis = torch.tensor(
            [0.0, 0.0, 1.0], dtype=torch.float32, device=device
        ).expand_as(z_axis)
        x_axis = torch.linalg.cross(y_axis, z_axis, dim=1)
        if torch.any(torch.linalg.vector_norm(x_axis, dim=1) <= 1.0e-6):

Copilot AI review requested due to automatic review settings August 18, 2026 09:48

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Suppressed comments (7)

embodichain/lab/sim/atomic_actions/affordance.py:356

  • TurnAffordance.get_grasp_pose builds a pose with y_axis fixed to world up, but z_axis is generally not orthogonal to that y_axis. This yields a non-orthonormal rotation matrix (not a valid rigid transform), which can break downstream rotation math/IK. Consider computing y_axis as the normalized projection of world_up onto the plane orthogonal to z_axis (and then x_axis = cross(y_axis, z_axis)) so the basis is orthonormal while staying as close as possible to world up.
        z_axis = torch.matmul(link_pose[:, :3, :3], turn_axis)
        z_axis = torch.nn.functional.normalize(z_axis, dim=1)
        y_axis = torch.tensor(
            [0.0, 0.0, 1.0], dtype=torch.float32, device=device
        ).expand_as(z_axis)

embodichain/lab/sim/atomic_actions/affordance.py:501

  • PressButtonAffordance.get_press_pose has the same basis construction issue as TurnAffordance: it fixes y_axis to world up but does not enforce/restore orthogonality with z_axis. The resulting transform can have a non-orthonormal rotation submatrix, which is not a valid pose for IK/planning. Consider projecting world_up onto the plane orthogonal to z_axis (or otherwise orthonormalizing) before forming the rotation matrix.
        z_axis = torch.matmul(link_pose[:, :3, :3], press_axis)
        z_axis = torch.nn.functional.normalize(z_axis, dim=1)
        y_axis = torch.tensor(
            [0.0, 0.0, 1.0], dtype=torch.float32, device=device
        ).expand_as(z_axis)

docs/source/overview/sim/atomic_actions/index.md:301

  • The docs claim there are "eleven" built-in actions, but BUILTIN_ACTION_TYPES now contains 12 actions (MoveJoints plus the 3 newly added primitives). This count should match the code so users don’t get confused when enumerating engine.actions.
# All eleven built-ins are immediately usable by stable skill ID.

docs/source/overview/sim/atomic_actions/builtin_actions.md:8

  • This page says there are eleven built-in actions, but BUILTIN_ACTION_TYPES now contains 12 actions (including MoveJoints and the 3 new primitives). The headline count should be updated to stay consistent with the code.
EmbodiChain ships eleven built-in action implementations with stable skill IDs;

docs/source/api_reference/embodichain/embodichain.lab.sim.atomic_actions.rst:77

  • The autosummary list was updated for PressButton and PullPush, but it does not include the newly exported TurnKnob/TurnAffordance symbols (TurnKnobGoal, TurnKnobOptions, TurnAffordance, TurnKnob). This makes the API reference incomplete relative to embodichain.lab.sim.atomic_actions.all.
      PressButtonGoal
      PressButtonOptions
      PressButtonAffordance
      PullPushArticulatedPartGoal
      PullPushArticulatedPartOptions
      PullPushAffordance

scripts/tutorials/atomic_action/turn_knob.py:57

  • draw_axis_marker is imported but never used in this tutorial script. Unused imports are easy to miss in examples and can fail linting if enabled; either use it under the visualize_axes feature or remove it.
from scripts.tutorials.atomic_action.tutorial_utils import (
    add_ur5_gripper_robot,
    create_toppra_motion_generator,
    create_tutorial_argument_parser,
    create_tutorial_simulation,
    draw_axis_marker,
    get_hand_open_close_qpos,

scripts/tutorials/atomic_action/press_button.py:56

  • draw_axis_marker is imported but never used in this tutorial script. If the visualize_axes feature is intended here, consider drawing an axis marker; otherwise remove the unused import to avoid lint failures and keep the example minimal.
from scripts.tutorials.atomic_action.tutorial_utils import (
    add_ur5_gripper_robot,
    create_toppra_motion_generator,
    create_tutorial_argument_parser,
    create_tutorial_simulation,
    draw_axis_marker,
    get_hand_open_close_qpos,

Comment on lines 65 to 69
GRIPPER_URDF_PATH = "DH_PGI_140_80/DH_PGI_140_80.urdf"
GRIPPER_HAND_JOINT_PATTERN = "gripper_finger1_joint_1"
GRIPPER_TCP_Z = 0.15
GRIPPER_MAX_OPEN_WIDTH = 0.100
GRIPPER_MIN_OPEN_WIDTH = 0.003
GRIPPER_FINGER_LENGTH = 0.10
Copilot AI review requested due to automatic review settings August 18, 2026 10:34

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 26 out of 27 changed files in this pull request and generated no new comments.

Suppressed comments (4)

scripts/tutorials/atomic_action/tutorial_utils.py:67

  • GRIPPER_TCP_Z was removed from this module, but it is still listed in __all__ later in the file. This can break from scripts.tutorials.atomic_action.tutorial_utils import * (and any code importing GRIPPER_TCP_Z). Either reintroduce the constant (keeping it aligned with the new default tcp_z) or remove it from __all__.
GRIPPER_URDF_PATH = "DH_PGI_140_80/DH_PGI_140_80.urdf"
GRIPPER_HAND_JOINT_PATTERN = "gripper_finger1_joint_1"
GRIPPER_MAX_OPEN_WIDTH = 0.100

embodichain/lab/sim/atomic_actions/primitives/turn_knob.py:80

  • TurnKnobOptions.__post_init__ validates that pre_grasp_distance is non-negative, but it doesn't reject NaN/inf values. A non-finite distance will propagate NaNs into pose targets and can make planning fail in hard-to-debug ways.
            raise ValueError("turn_waypoint_count must be at least 1.")
        if self.pre_grasp_distance < 0.0:
            raise ValueError("pre_grasp_distance must be non-negative.")
        if not math.isfinite(self.turn_angle):
            raise ValueError("turn_angle must be finite.")

embodichain/data/assets/obj_assets.py:246

  • The MicrowaveOven dataset docstring advertises MicrowaveOven/microwave_oven.urdf, but the new tutorials reference MicrowaveOven/microwave_oven_with_inertials.urdf. This mismatch makes it harder to find the right asset path.
class MicrowaveOven(EmbodiChainDataset):
    """get_data_path("MicrowaveOven/microwave_oven.urdf")"""

docs/source/api_reference/embodichain/embodichain.lab.sim.atomic_actions.rst:75

  • The API reference autosummary lists the new PressButton and PullPush symbols, but it omits the TurnKnob/TurnAffordance symbols that are now exported from embodichain.lab.sim.atomic_actions. This causes the generated docs to be incomplete.
      PressButtonOptions
      PressButtonAffordance
      PullPushArticulatedPartGoal
      PullPushArticulatedPartOptions
      PullPushAffordance

Copilot AI review requested due to automatic review settings August 18, 2026 10:39

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 26 out of 27 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

embodichain/lab/sim/atomic_actions/affordance.py:379

  • TurnAffordance.get_grasp_pose() builds a rotation matrix with y_axis fixed to world-up and z_axis set from the turn axis. Unless z_axis is exactly perpendicular to world-up, the resulting [x,y,z] basis is not orthonormal (y and z are not perpendicular), which can produce invalid transforms for IK/planning.
        z_axis = torch.matmul(link_pose[:, :3, :3], turn_axis)
        z_axis = torch.nn.functional.normalize(z_axis, dim=1)
        y_axis = torch.tensor(
            [0.0, 0.0, 1.0], dtype=torch.float32, device=device
        ).expand_as(z_axis)

scripts/tutorials/atomic_action/tutorial_utils.py:67

  • GRIPPER_TCP_Z was removed from the constants block, but it is still referenced later in this module’s all list (and may be imported externally). As-is, importing this module will raise NameError when evaluating all.
GRIPPER_URDF_PATH = "DH_PGI_140_80/DH_PGI_140_80.urdf"
GRIPPER_HAND_JOINT_PATTERN = "gripper_finger1_joint_1"
GRIPPER_MAX_OPEN_WIDTH = 0.100

docs/source/api_reference/embodichain/embodichain.lab.sim.atomic_actions.rst:74

  • The autosummary list for built-in goals/actions omits the newly added TurnKnob and TurnAffordance symbols, so the API reference doesn’t reflect all built-ins shipped by the package.
      PressButtonGoal
      PressButtonOptions
      PressButtonAffordance
      PullPushArticulatedPartGoal
      PullPushArticulatedPartOptions

Comment on lines +563 to +574
z_axis = torch.matmul(link_pose[:, :3, :3], press_axis)
z_axis = torch.nn.functional.normalize(z_axis, dim=1)
y_axis = torch.tensor(
[0.0, 0.0, 1.0], dtype=torch.float32, device=device
).expand_as(z_axis)
x_axis = torch.linalg.cross(y_axis, z_axis, dim=1)
if torch.any(torch.linalg.vector_norm(x_axis, dim=1) <= 1.0e-6):
raise ValueError(
"PressButtonAffordance press axis must not be parallel to world "
"(0, 0, 1)."
)
x_axis = torch.nn.functional.normalize(x_axis, dim=1)
Copilot AI review requested due to automatic review settings August 18, 2026 10:57

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 35 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

embodichain/lab/sim/atomic_actions/affordance.py:392

  • TurnAffordance.get_grasp_pose() builds axes with x = cross(world_up, z) but then keeps y_axis fixed to world_up without re-orthogonalizing it against z_axis. When z_axis has any non-zero component along world-up, the resulting rotation matrix is not orthonormal (i.e., not a valid rigid transform), which can cause IK/planning instability. Consider constructing an orthonormal frame (e.g., x = normalize(cross(up, z)), y = normalize(cross(z, x))) similar to embodichain/utils/math.py:1904-1919.
        z_axis = torch.matmul(link_pose[:, :3, :3], turn_axis)
        z_axis = torch.nn.functional.normalize(z_axis, dim=1)
        y_axis = torch.tensor(
            [0.0, 0.0, 1.0], dtype=torch.float32, device=device
        ).expand_as(z_axis)
        x_axis = torch.linalg.cross(y_axis, z_axis, dim=1)
        if torch.any(torch.linalg.vector_norm(x_axis, dim=1) <= 1.0e-6):
            raise ValueError(
                "TurnAffordance turn axis must not be parallel to world (0, 0, 1)."
            )
        x_axis = torch.nn.functional.normalize(x_axis, dim=1)

        grasp_pose = torch.eye(4, dtype=torch.float32, device=device).repeat(
            link_pose.shape[0], 1, 1
        )
        grasp_pose[:, :3, 0] = x_axis
        grasp_pose[:, :3, 1] = y_axis
        grasp_pose[:, :3, 2] = z_axis

embodichain/lab/sim/atomic_actions/affordance.py:582

  • PressButtonAffordance.get_press_pose() has the same frame-construction issue as TurnAffordance: y_axis is fixed to world-up and never re-orthogonalized after choosing z_axis, so the produced rotation may be non-orthonormal (invalid) whenever the press axis is not exactly perpendicular to world-up. This can propagate invalid transforms into motion planning/IK. Prefer an orthonormal basis build (e.g., x = normalize(cross(up, z)), y = normalize(cross(z, x))).
        z_axis = torch.matmul(link_pose[:, :3, :3], press_axis)
        z_axis = torch.nn.functional.normalize(z_axis, dim=1)
        y_axis = torch.tensor(
            [0.0, 0.0, 1.0], dtype=torch.float32, device=device
        ).expand_as(z_axis)
        x_axis = torch.linalg.cross(y_axis, z_axis, dim=1)
        if torch.any(torch.linalg.vector_norm(x_axis, dim=1) <= 1.0e-6):
            raise ValueError(
                "PressButtonAffordance press axis must not be parallel to world "
                "(0, 0, 1)."
            )
        x_axis = torch.nn.functional.normalize(x_axis, dim=1)

        press_pose = torch.eye(4, dtype=torch.float32, device=device).repeat(
            link_pose.shape[0], 1, 1
        )
        press_pose[:, :3, 0] = x_axis
        press_pose[:, :3, 1] = y_axis
        press_pose[:, :3, 2] = z_axis
        press_pose[:, :3, 3] = (

scripts/tutorials/atomic_action/tutorial_utils.py:67

  • GRIPPER_TCP_Z was removed, but tutorial_utils still exports it via __all__. A from ...tutorial_utils import * (or docs tooling that expects the symbol) will now fail with AttributeError/missing symbol. Reintroduce the constant (or alternatively remove it from __all__) to keep the module exports consistent.

Comment on lines +31 to +35
PUBLIC_PRIMITIVE_SCRIPTS = tuple(
path
for path in sorted(PRIMITIVES_DIRECTORY.glob("*.py"))
if not path.name.startswith("_")
)
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.

2 participants