Skip to content

[BUG FIX] Fix IPC coupler auto-detecting wrong coup_type for Plane entities.#2877

Merged
duburcqa merged 3 commits into
Genesis-Embodied-AI:mainfrom
liminchen:fix/ipc-plane-coup-type-auto-detection
Jul 16, 2026
Merged

[BUG FIX] Fix IPC coupler auto-detecting wrong coup_type for Plane entities.#2877
duburcqa merged 3 commits into
Genesis-Embodied-AI:mainfrom
liminchen:fix/ipc-plane-coup-type-auto-detection

Conversation

@liminchen

Copy link
Copy Markdown
Contributor

Description

A Plane entity added to a scene with IPCCouplerOptions(two_way_coupling=True) crashed at scene.build() with:

GenesisException: Plane entity (solver idx=0) has coup_type='1', but only 'ipc_only' is supported for plane geoms.

Root Cause

Primitive morphs (Plane, Box, Sphere, etc.) always create one joint in the rigid entity tree, even when that joint is FIXED (zero DOFs). The auto-detection logic in _setup_coupling_config used entity.n_joints > 0 to identify articulated entities, which incorrectly classified a Plane (1 FIXED joint, 0 DOFs) as external_articulation (enum value 1). PLANE geoms only support ipc_only, triggering the exception.

Fix

Changed entity.n_joints > 0entity.n_dofs > 0 in _setup_coupling_config. This correctly distinguishes:

  • Fixed entities with no DOFs (Plane, fixed Box, etc.) → ipc_only
  • Fixed-base articulated robots (joints with DOFs) → external_articulation
  • Free-floating rigid bodies → two_way_soft_constraint

To Reproduce

python examples/IPC_Solver/ipc_objects_falling.py

Crashes before the fix, runs successfully after.

QuantuMope added a commit to HorizonRoboticsInternal/genesis-world that referenced this pull request Jun 8, 2026
…tities

Primitive morphs (Plane, Box, Sphere, etc.) always create one joint in the
rigid entity tree, even when that joint is FIXED (zero DOFs). The previous
auto-detection logic used `entity.n_joints > 0` to identify articulated
entities, which incorrectly classified a fixed Plane (1 FIXED joint, 0 DOFs)
as `external_articulation`. PLANE geoms only support `ipc_only`, so this
raised GenesisException at build time whenever a Plane was added to a scene
with `IPCCouplerOptions(two_way_coupling=True)` and no explicit `coup_type`.

Fix: replace `n_joints > 0` with `n_dofs > 0` in `_setup_coupling_config` so
that fixed entities with no actual DOFs (Plane, fixed Box, etc.) correctly
receive `ipc_only` rather than `external_articulation`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@duburcqa
duburcqa force-pushed the fix/ipc-plane-coup-type-auto-detection branch from 6560c23 to 2b6c929 Compare July 15, 2026 17:59
@alanray-tech

alanray-tech commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

LGTM now, but need an deep investigation into the whole structure

…test

Auto-detect and the external_articulation validation both decided whether an
entity is articulated, but each used its own predicate. Auto-detect used
n_dofs > 0 while the validation still used n_joints == 0, which is the same
root cause the previous fix addressed: primitive morphs always keep one FIXED
base joint (n_joints == 1, n_dofs == 0), so n_joints cannot express "has
dynamic DOFs".

Introduce has_articulation_dofs and default_coup_type in ipc_coupler/utils.py
so both sites share a single discriminator. Route auto-detect through
default_coup_type and the external_articulation guard through
has_articulation_dofs, so an explicit external_articulation on a fixed
primitive (n_dofs == 0) is now correctly rejected instead of silently
building an empty articulation.

Add test_auto_coup_type_from_dofs covering Plane, fixed Box, free Box and a
fixed-base articulated robot in one scene, asserting the resolved coup_type
and the underlying n_joints/n_dofs/is_fixed invariants.
@alanray-tech

Copy link
Copy Markdown
Contributor

Took a deeper look at the whole coup_type detection path as discussed, and pushed a follow-up commit (2d3bbc6) that keeps the original fix but removes the remaining inconsistency and adds a regression test.

Analysis

The bug is real and the original one-line fix (n_joints > 0 -> n_dofs > 0) is the correct direction. The root cause is not in the coupler semantics but in how the rigid entity tree represents joints:

  • Primitive morphs (Plane, Box, Sphere, ...) always keep exactly one base joint: FIXED (n_dofs=0) when fixed=True, otherwise FREE (n_dofs=6). See RigidEntity._load_primitive.
  • MJCF/URDF entities instead drop zero-DOF joints from the tree (to align with MuJoCo), see rigid_entity.py:912.

So a Plane ends up with n_joints=1 but n_dofs=0. n_joints therefore cannot express "is articulated", while n_dofs can. I verified this empirically by building a scene with all four entity kinds and printing the raw values before/after build:

entity n_joints n_dofs is_fixed resolved coup_type
Plane 1 0 True ipc_only
fixed Box 1 0 True ipc_only
free Box 1 6 False two_way_soft_constraint
fixed-base robot 1 1 True external_articulation

Remaining inconsistency this addresses

Auto-detect and the external_articulation validation both answer the same question ("does this entity have dynamic DOFs?"), but after the original fix they used two different predicates: auto-detect used n_dofs > 0, while the guard a few lines below still used n_joints == 0. That guard has the exact same blind spot: an explicit coup_type="external_articulation" on a fixed primitive (n_joints=1, n_dofs=0) passed the check and then silently built an empty articulation (all its FIXED joints are skipped later).

Changes

  • Add has_articulation_dofs(entity) and default_coup_type(entity) to ipc_coupler/utils.py, so the "articulated?" discriminator lives in exactly one place. The "why n_dofs and not n_joints" rationale is documented once, on the util.
  • Route auto-detect through default_coup_type and the external_articulation guard through has_articulation_dofs. This also fixes the guard so a fixed primitive explicitly marked external_articulation is now rejected up front instead of building an empty articulation.
  • Add test_auto_coup_type_from_dofs: one scene with Plane / fixed Box / free Box / fixed-base robot, asserting the resolved coup_type for each and the underlying n_joints/n_dofs/is_fixed invariants. This fails on main (Plane crashes at build) and passes here.

Verification

Ran on an RTX 5090 (CUDA backend, pyuipc 0.0.25):

  • tests/ipc/test_api.py::test_auto_coup_type_from_dofs -> passed
  • full tests/ipc/test_api.py -> 12 passed (covers explicit ipc_only / two_way_soft_constraint / external_articulation paths as well)

No new heuristic or workaround was introduced; this only unifies the existing decision into a single predicate.

@alanray-tech
alanray-tech self-requested a review July 16, 2026 12:36
@duburcqa duburcqa changed the title [BUG FIX] Fix IPC coupler auto-detecting wrong coup_type for Plane entities [BUG FIX] Fix IPC coupler auto-detecting wrong coup_type for Plane entities. Jul 16, 2026

@alanray-tech alanray-tech 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.

Approving. After reviewing the whole coup_type detection path, this is a real bug with the correct fix, not a workaround: primitive morphs always keep one FIXED base joint, so n_joints cannot express "is articulated" while n_dofs can. The follow-up unifies the discriminator into a single has_articulation_dofs util (also fixing the sibling n_joints == 0 guard) and adds a regression test covering Plane / fixed Box / free Box / fixed-base robot. Verified locally: full tests/ipc/test_api.py passes (12 tests).

@duburcqa
duburcqa merged commit 763e43e into Genesis-Embodied-AI:main Jul 16, 2026
18 of 20 checks passed
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.

3 participants