Skip to content

Exclude body contacts#799

Merged
eric-heiden merged 11 commits into
newton-physics:mainfrom
eric-heiden:exclude-body-contacts
Sep 20, 2025
Merged

Exclude body contacts#799
eric-heiden merged 11 commits into
newton-physics:mainfrom
eric-heiden:exclude-body-contacts

Conversation

@eric-heiden

@eric-heiden eric-heiden commented Sep 19, 2025

Copy link
Copy Markdown
Member

Description

Continuation of @camevor's work on #793:

  • Faster implementation of the body pairs to exclude in contact generation from SolverMuJoCo
  • Only generate shape contact filters for colliding shapes
  • Improvements to some graph plotting utility functions (only used for debugging)
  • Enable self collisions on Allegro hand example

Newton Migration Guide

Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this PR.

  • The migration guide in docs/migration.rst is up-to date

Before your PR is "Ready for review"

  • Necessary tests have been added and new examples are tested (see newton/tests/test_examples.py)
  • Documentation is up-to-date
  • Code passes formatting and linting checks with pre-commit run -a

Summary by CodeRabbit

  • New Features

    • Articulation plots: optional show_shape_keys for richer shape labels.
    • Graph plots: support custom node labels and node colors.
    • Collision-coloring: accepts optional shape_keys for labeled visualization; new helper to compute body-level exclusion pairs.
  • Improvements

    • Consistent per-shape collision gating across add_shape/add_joint and solver paths.
    • Simplified shape-to-body registration and clearer visualization labels.
  • Bug Fixes

    • Prevent non-collidable shapes from entering collision-filter pair generation.
  • Examples

    • Allegro Hand: enabled self-collisions and removed manual filters/viewer auto-launch.

@coderabbitai

coderabbitai Bot commented Sep 19, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Collision participation is now gated per-shape via ShapeConfig.has_shape_collision; builder shape/joint registration and collision-pair generation were simplified and tightened. Graph plotting and MuJoCo solver visualization APIs gained optional labeling parameters and body-level collision exclude logic was added. An example enables self-collisions and removes manual filters.

Changes

Cohort / File(s) Summary of changes
Builder: collision filtering & plotting
newton/_src/sim/builder.py
Gate collision-filter pair generation on ShapeConfig.has_shape_collision; simplify add_shape shape-to-body registration (always append); ensure joint pair creation only adds pairs when both shapes allow collisions; add plot_articulation(..., show_shape_keys=True) and build shape labels from components.
Graph plotting API
newton/_src/sim/graph_coloring.py
plot_graph signature extended with node_labels and node_colors; updated labeling and node coloring logic and docs.
MuJoCo solver: collision graph & per-body excludes
newton/_src/solvers/mujoco/solver_mujoco.py
Added find_body_collision_filter_pairs; use np.triu_indices for unique pairs; apply per-body exclusion pairs when building MuJoCo spec (add_exclude); color_collision_shapes accepts optional shape_keys for node labels and returns per-shape colors; geom naming uses model.shape_key[...]; friction uses shape_mu; selected arrays explicitly typed; minor validation adjustments.
Example: Allegro hand
newton/examples/robot/example_robot_allegro_hand.py
Removed manual self-collision filtering and related itertools import and debug prints; enabled enable_self_collisions=True for USD load; removed MuJoCo viewer render call; minor formatting.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant Builder
  participant Model
  Note over Builder,Model: Shape registration & joint pairing
  User->>Builder: add_shape(shape_cfg)
  Builder->>Model: append shape to body_shapes
  alt shape_cfg.has_shape_collision
    Note over Builder: shape participates in filter pairs
  else
    Note over Builder: shape excluded from filter pairs
  end
  User->>Builder: add_joint(parent, child)
  Builder->>Model: enumerate parent & child shapes
  loop each parent-child shape pair
    alt both shapes have has_shape_collision
      Builder->>Model: add joint-pair candidate to collision filter
    else
      Note over Builder: skip pair
    end
  end
Loading
sequenceDiagram
  autonumber
  participant Solver
  participant Model
  participant Graph
  participant MJSpec as MuJoCoSpec
  Note over Solver,Graph: Collision graph coloring
  Solver->>Model: selected_shapes (± shape_keys)
  Solver->>Graph: build edges using np.triu_indices
  Graph-->>Solver: edges
  Solver->>Solver: remove edges blocked by shape collision-filter or groups
  Solver->>Graph: run coloring -> colors
  Graph-->>Solver: colors (optionally labeled by shape_keys)
  Note over Solver,MJSpec: MuJoCo assembly with excludes
  Solver->>Model: compute body-level exclude pairs
  Solver->>MJSpec: add_exclude(body_i, body_j) for pairs
  Solver->>MJSpec: add geoms (names from shape_key, friction from shape_mu)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • AntoineRichard
  • adenzler-nvidia
  • shi-eric

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Exclude body contacts" succinctly and accurately describes the primary change in this PR: introducing body-level contact exclusions and tightening collision-filter generation. The patch adds a find_body_collision_filter_pairs helper and per-body exclusion logic in SolverMuJoCo, consistently gates shape-pair creation in the builder, and removes manual filtering in the example, so the title maps to the dominant intent. It is concise and specific enough for a teammate scanning history to understand the main change.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@eric-heiden eric-heiden marked this pull request as ready for review September 19, 2025 23:50

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (10)
newton/_src/sim/graph_coloring.py (1)

295-297: Guard against None for node_colors in draw call.

Passing node_color=None to NetworkX/Matplotlib can error depending on versions. Draw without the arg when colors aren’t provided.

Apply this diff:

-    nx.draw_networkx_nodes(G, pos, node_color=node_colors, **default_draw_args)
+    if node_colors is None:
+        nx.draw_networkx_nodes(G, pos, **default_draw_args)
+    else:
+        nx.draw_networkx_nodes(G, pos, node_color=node_colors, **default_draw_args)
newton/_src/sim/builder.py (4)

1985-1991: Double newline in shape labels.

You append "\n(...)" and then "\n".join(...), yielding a blank line. Drop the leading newline in the type.

Apply this diff:

-                if show_shape_types:
-                    shape_label.append(f"\n({shape_type_str(self.shape_type[i])})")
+                if show_shape_types:
+                    shape_label.append(f"({shape_type_str(self.shape_type[i])})")

2413-2418: Canonicalize same-body filter pairs for robustness.

While current indices likely make (same_body_shape, shape) ordered, enforce canonical ordering to avoid future edge cases.

Apply this diff:

-        if cfg.has_shape_collision:
+        if cfg.has_shape_collision:
             # no contacts between shapes of the same body
             for same_body_shape in self.body_shapes[body]:
-                self.shape_collision_filter_pairs.append((same_body_shape, shape))
+                a, b = same_body_shape, shape
+                if a > b:
+                    a, b = b, a
+                self.shape_collision_filter_pairs.append((a, b))

2435-2440: Canonicalize parent–child filter pairs too.

Match the ordering used in find_shape_contact_pairs to ensure lookups succeed.

Apply this diff:

-                    for parent_shape in self.body_shapes[parent_body]:
-                        self.shape_collision_filter_pairs.append((parent_shape, shape))
+                    for parent_shape in self.body_shapes[parent_body]:
+                        a, b = parent_shape, shape
+                        if a > b:
+                            a, b = b, a
+                        self.shape_collision_filter_pairs.append((a, b))

4463-4465: Handle empty contact_pairs without constructing a NumPy array.

Warp arrays from np.array([]) can be brittle for vector dtypes; allocate an empty vec2i directly.

Apply this diff:

-        model.shape_contact_pairs = wp.array(np.array(contact_pairs), dtype=wp.vec2i, device=model.device)
+        if contact_pairs:
+            model.shape_contact_pairs = wp.array(np.array(contact_pairs), dtype=wp.vec2i, device=model.device)
+        else:
+            model.shape_contact_pairs = wp.empty(shape=(0,), dtype=wp.vec2i, device=model.device)
newton/_src/solvers/mujoco/solver_mujoco.py (5)

1670-1680: Avoid false positives by checking both (a,b) and (b,a) in filter set

If model.shape_collision_filter_pairs isn’t guaranteed to store both orders, current membership test can misclassify edges.

Apply this diff:

@@
-        num_shapes = len(selected_shapes)
-        shape_a, shape_b = np.triu_indices(num_shapes, k=1)
-        cgroup = [model.shape_collision_group[i] for i in selected_shapes]
+        num_shapes = len(selected_shapes)
+        shape_a, shape_b = np.triu_indices(num_shapes, k=1)
+        cgroup = [model.shape_collision_group[i] for i in selected_shapes]
+        pair_filters = model.shape_collision_filter_pairs
@@
-        graph_edges = [
-            (i, j)
-            for i, j in zip(shape_a, shape_b, strict=True)
-            if (
-                (selected_shapes[i], selected_shapes[j]) not in model.shape_collision_filter_pairs
-                and (cgroup[i] == cgroup[j] or cgroup[i] == -1 or cgroup[j] == -1)
-            )
-        ]
+        graph_edges = [
+            (i, j)
+            for i, j in zip(shape_a, shape_b, strict=True)
+            if (
+                (selected_shapes[i], selected_shapes[j]) not in pair_filters
+                and (selected_shapes[j], selected_shapes[i]) not in pair_filters
+                and (cgroup[i] == cgroup[j] or cgroup[i] == -1 or cgroup[j] == -1)
+            )
+        ]

1664-1669: Remove unused mapping

to_color_shape_index is created but never used.


2063-2063: Sanitize geom names derived from shape_key

MuJoCo names must be valid identifiers; if shape_key can contain spaces or special chars, sanitize (e.g., replace non-alnum with '', ensure first char is letter/).


2107-2111: Color bit budget: guard for >=32 colors

If graph coloring yields >=32, contype won’t be set and MuJoCo defaults may cause unintended collisions. Consider modulo bucketing or fallback to affinity-based excludes when color >= 32.


2360-2365: Add excludes safely; consider dedup and self-checks

  • If body_filters can contain duplicates across envs/runs, dedup to avoid bloating XML.
  • Optionally skip when b1 or b2 not in body_mapping (defensive).

Minor, but helps robustness.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9714e98 and 660d47f.

📒 Files selected for processing (4)
  • newton/_src/sim/builder.py (6 hunks)
  • newton/_src/sim/graph_coloring.py (2 hunks)
  • newton/_src/solvers/mujoco/solver_mujoco.py (9 hunks)
  • newton/examples/robot/example_robot_allegro_hand.py (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: eric-heiden
PR: newton-physics/newton#587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
PR: newton-physics/newton#587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.

Applied to files:

  • newton/_src/sim/builder.py
🧬 Code graph analysis (2)
newton/_src/sim/builder.py (1)
newton/_src/geometry/flags.py (1)
  • ShapeFlags (30-42)
newton/_src/solvers/mujoco/solver_mujoco.py (2)
newton/_src/sim/builder.py (3)
  • shape_count (508-512)
  • body_count (515-519)
  • joint_count (522-526)
newton/_src/sim/graph_coloring.py (2)
  • color_graph (189-259)
  • plot_graph (262-314)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (11)
newton/examples/robot/example_robot_allegro_hand.py (1)

98-102: Enabling self-collisions may increase contact counts; verify contact budgets.

With enable_self_collisions=True, ensure ncon_per_env=150 and njmax=200 are sufficient for peak frames; otherwise you may silently drop contacts. Consider bumping these or adding a debug assert on len(self.contacts) vs budget during stress poses.

newton/_src/sim/graph_coloring.py (2)

262-272: API extension looks good.

New node_labels/node_colors params and updated docstring/readback behavior are consistent.


262-272: Verified — no downstream changes required

Only usage found: newton/_src/solvers/mujoco/solver_mujoco.py (lines ~1694–1696). It calls plot_graph(vertices=..., edges=...) with keyword args only; the new optional node_labels/node_colors params default safely.

newton/_src/sim/builder.py (3)

1353-1363: Joint parent/child collision filtering gated on COLLIDE_SHAPES — good.

Skips generating filter pairs for non-colliding shapes and enforces canonical order. Looks correct.


4433-4465: find_shape_contact_pairs: efficient and gated to colliding shapes — nice.

Sorting by env group and early-break improve performance; group and filter checks read cleanly.


1920-1937: No internal positional callers found — confirm external API impact

Ripgrep found only the function definition at newton/_src/sim/builder.py:1914; no other calls in the repo. Reordering parameters (inserting show_shape_keys before show_shape_types) is still a breaking change for external callers — restore the original order or add a backward-compatible shim that preserves positional semantics.

newton/_src/solvers/mujoco/solver_mujoco.py (5)

1646-1650: API extension looks good

Adding shape_keys to color_collision_shapes is a useful ergonomics improvement for debugging/visualization.


2013-2016: Good: explicit int32 for indices

Using np.int32 for selected_* arrays prevents dtype surprises when passing to Warp.


2021-2021: Joint order check tweak is fine

any(...) keeps it simple without allocating an array.


2034-2044: Body-filter precompute is a nice win; confirm no -1 world body involvement

Looks good. Please double-check selected_bodies cannot include -1; excludes involving the world aren’t needed (collision system already skips world-attached shape collisions across builders per prior discussion).


2113-2119: Per-shape friction mapping: LGTM

Computes slide/torsional/rolling as (μ, τ·μ, ρ·μ). Matches update_geom_properties_kernel semantics.

Comment thread newton/_src/solvers/mujoco/solver_mujoco.py
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
newton/_src/sim/builder.py (1)

2435-2440: Also canonicalize parent–child filter pairs and skip non‑colliding parents

Same ordering issue here; additionally, skip parents without COLLIDE_SHAPES to align with “colliding shapes only”.

Apply:

-        if cfg.has_shape_collision and cfg.collision_filter_parent and body > -1 and body in self.joint_parents:
-            for parent_body in self.joint_parents[body]:
-                if parent_body > -1:
-                    for parent_shape in self.body_shapes[parent_body]:
-                        self.shape_collision_filter_pairs.append((parent_shape, shape))
+        if cfg.has_shape_collision and cfg.collision_filter_parent and body > -1 and body in self.joint_parents:
+            for parent_body in self.joint_parents[body]:
+                if parent_body > -1:
+                    for parent_shape in self.body_shapes[parent_body]:
+                        if not (self.shape_flags[parent_shape] & ShapeFlags.COLLIDE_SHAPES):
+                            continue
+                        a, b = parent_shape, shape
+                        if a > b:
+                            a, b = b, a
+                        self.shape_collision_filter_pairs.append((a, b))
newton/_src/solvers/mujoco/solver_mujoco.py (1)

1646-1700: Fix asymmetric filter checks, remove dead code, correct return documentation

  • In graph_edges (and the earlier exclusion loop), check both (si, sj) and (sj, si) against model.shape_collision_filter_pairs to avoid unfiltered pairs when storage order differs.
  • Remove the unused to_color_shape_index mapping at the top of color_collision_shapes.
  • Update the docstring’s Returns section to reflect that the function returns an array of shape (model.shape_count,) rather than (num_shapes,).
🧹 Nitpick comments (7)
newton/_src/sim/graph_coloring.py (2)

262-272: plot_graph: accept optional labels/colors more defensively

Small guardrails will prevent confusing errors when node_labels/node_colors lengths don’t match vertices, and avoid passing None to node_color.

Apply:

 def plot_graph(vertices, edges, edge_labels=None, node_labels=None, node_colors=None):
@@
-    if edge_labels is None:
-        edge_labels = []
+    if edge_labels is None:
+        edge_labels = []
+    if node_labels is not None and len(node_labels) != len(vertices):
+        raise ValueError("node_labels length must match vertices length")
+    if node_colors is not None and len(node_colors) != len(vertices):
+        raise ValueError("node_colors length must match vertices length")

296-301: Avoid passing node_color=None; ensure clean label fallback

  • Only pass node_color when provided.
  • Current type-line adds a leading newline when only shape type is shown; drop the prefix newline to avoid leading blank labels.

Apply:

-    nx.draw_networkx_nodes(G, pos, node_color=node_colors, **default_draw_args)
+    draw_args = dict(default_draw_args)
+    if node_colors is not None:
+        draw_args["node_color"] = node_colors
+    nx.draw_networkx_nodes(G, pos, **draw_args)
@@
-        labels=dict(enumerate(node_labels if node_labels is not None else vertices)),
+        labels=dict(enumerate(node_labels if node_labels is not None else vertices)),
newton/_src/sim/builder.py (3)

1985-1991: Shape labels may start with a leading newline when only type is shown

When show_shape_keys=False and show_shape_types=True, shape_label becomes ["\n(type)"], producing a leading blank line. Drop the prefix newline and rely on join.

Apply:

-                shape_label = []
+                shape_label = []
                 if show_shape_keys:
                     shape_label.append(self.shape_key[i])
                 if show_shape_types:
-                    shape_label.append(f"\n({shape_type_str(self.shape_type[i])})")
-                vertices.append("\n".join(shape_label))
+                    shape_label.append(f"({shape_type_str(self.shape_type[i])})")
+                vertices.append("\n".join(shape_label))

1920-1937: plot_articulation: new show_shape_keys option — nice usability

The new toggle is sensible and defaults are reasonable. Please reflect the new argument in the public API docs/migration guide.

Add a brief note in docs/migration.rst describing:

  • ShapeConfig.has_shape_collision (new)
  • plot_articulation(show_shape_keys=True) (new default)

2413-2417: Optional: centralize pair canonicalization to avoid future drift

Consider a tiny helper (local function or private method) append_filter_pair(i, j) that sorts and appends once. Reduces repetition and prevents regressions.

Also applies to: 2435-2440

newton/_src/solvers/mujoco/solver_mujoco.py (2)

1071-1132: Clamp mu to non-negative and preempt invalid friction

If a material supplies a negative μ (bad input), MuJoCo will receive negative friction, which is non-physical. Clamp μ ≥ 0 in-kernel.

Apply this diff:

-    mu = shape_mu[shape_idx]
-    geom_friction[worldid, geom_idx] = wp.vec3f(mu, torsional_friction * mu, rolling_friction * mu)
+    mu = wp.max(shape_mu[shape_idx], 0.0)
+    geom_friction[worldid, geom_idx] = wp.vec3f(mu, torsional_friction * mu, rolling_friction * mu)

2034-2044: Body-exclude + color: order of operations is good; guard colliding_shapes empty

Sequence (compute excludes → colors) makes sense. If colliding_shapes is empty, both operations are no-ops; consider an early return to skip work.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9714e98 and 660d47f.

📒 Files selected for processing (4)
  • newton/_src/sim/builder.py (6 hunks)
  • newton/_src/sim/graph_coloring.py (2 hunks)
  • newton/_src/solvers/mujoco/solver_mujoco.py (9 hunks)
  • newton/examples/robot/example_robot_allegro_hand.py (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: eric-heiden
PR: newton-physics/newton#587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
PR: newton-physics/newton#587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.

Applied to files:

  • newton/_src/sim/builder.py
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/_src/sim/builder.py
🧬 Code graph analysis (2)
newton/_src/solvers/mujoco/solver_mujoco.py (2)
newton/_src/sim/builder.py (3)
  • shape_count (508-512)
  • body_count (515-519)
  • joint_count (522-526)
newton/_src/sim/graph_coloring.py (2)
  • color_graph (189-259)
  • plot_graph (262-314)
newton/_src/sim/builder.py (1)
newton/_src/geometry/flags.py (1)
  • ShapeFlags (30-42)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (10)
newton/_src/sim/builder.py (2)

168-171: ShapeConfig: explicit collision toggles LGTM

Introducing has_shape_collision/has_particle_collision and wiring them into flags is clear and backward‑compatible via the setter. No issues spotted.

Also applies to: 179-191


1353-1357: add_joint: filter only colliding shapes — good tightening

Skipping non‑colliding shapes on both sides before adding filter pairs is correct and prevents unnecessary entries.

newton/examples/robot/example_robot_allegro_hand.py (1)

98-98: Enabling self-collisions: verify contact budget

With self-collisions on, contact count per env may rise. Ensure ncon_per_env=150 is sufficient for the worst case to avoid contact truncation; bump if you observe overflows.

newton/_src/solvers/mujoco/solver_mujoco.py (7)

1947-1948: LGTM: source of μ now comes from materials

Using model.shape_material_mu here keeps CPU-path friction consistent with the runtime kernel’s per-geom friction update.


2013-2016: LGTM: explicit int32 typing for selections

Explicit dtypes avoid accidental upcasts on device transfers and align with Warp’s int32 expectations.


2021-2025: LGTM: clearer joint-order validation

Index-based comparison is simple and avoids list allocation; warning is appropriate.


2063-2064: Geom naming via shape_key improves debuggability

Names are now stable and readable; index suffix preserves uniqueness.


2113-2119: LGTM: per-geom friction set consistently for CPU path

Matches kernel formula; ensures parity across backends.


2360-2365: Spec excludes applied at body level — confirm no unintended suppression

spec.add_exclude is applied per-body (newton/_src/solvers/mujoco/solver_mujoco.py:2362–2364) and will suppress collisions for any shapes subsequently attached to those bodies. Repo scan shows body/shape state is set/mutated during build/import (sim/builder.py — m.shape_flags / m.body_shapes set ~4125; utils/import_usd.py clears shape collision flags ~1246). Confirm shape attachments and COLLIDE_SHAPES flags are finalized before spec.compile (solver_mujoco.py:2366); otherwise use shape-pair excludes or gate the body-level excludes behind a feature flag.


1646-1659: Small: parameter doc type and Python version — resolved

pyproject.toml declares requires-python = ">=3.10" and classifiers include Python 3.10–3.12, so zip(..., strict=True) and PEP 604 annotations are supported; no change required.

Comment thread newton/_src/sim/builder.py
Comment thread newton/_src/solvers/mujoco/solver_mujoco.py
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
newton/_src/sim/builder.py (1)

2435-2440: Parent–child filter pairs should also be canonicalized and gated

Same issue here: append uses raw (parent_shape, shape) and doesn’t check parent_shape’s COLLIDE_SHAPES. Normalize and gate to avoid missed filters and noise.

Apply:

-        if cfg.has_shape_collision and cfg.collision_filter_parent and body > -1 and body in self.joint_parents:
-            for parent_body in self.joint_parents[body]:
-                if parent_body > -1:
-                    for parent_shape in self.body_shapes[parent_body]:
-                        self.shape_collision_filter_pairs.append((parent_shape, shape))
+        if cfg.has_shape_collision and cfg.collision_filter_parent and body > -1 and body in self.joint_parents:
+            for parent_body in self.joint_parents[body]:
+                if parent_body > -1:
+                    for parent_shape in self.body_shapes[parent_body]:
+                        if not (self.shape_flags[parent_shape] & ShapeFlags.COLLIDE_SHAPES):
+                            continue
+                        a, b = parent_shape, shape
+                        if a > b:
+                            a, b = b, a
+                        self.shape_collision_filter_pairs.append((a, b))
♻️ Duplicate comments (3)
newton/_src/sim/builder.py (1)

2413-2418: Canonicalize and gate same‑body filter pairs; current order can miss filtering

Pairs are appended as (same_body_shape, shape) without normalization and without checking the existing shape’s COLLIDE_SHAPES flag. find_shape_contact_pairs checks membership using canonical (min, max), so reversed tuples won’t match and same‑body pairs may slip through.

Apply:

-        if cfg.has_shape_collision:
-            # no contacts between shapes of the same body
-            for same_body_shape in self.body_shapes[body]:
-                self.shape_collision_filter_pairs.append((same_body_shape, shape))
+        if cfg.has_shape_collision:
+            # no contacts between shapes of the same body
+            for same_body_shape in self.body_shapes[body]:
+                # skip non-colliding shapes
+                if not (self.shape_flags[same_body_shape] & ShapeFlags.COLLIDE_SHAPES):
+                    continue
+                # store in canonical order for consistent lookup
+                a, b = same_body_shape, shape
+                if a > b:
+                    a, b = b, a
+                self.shape_collision_filter_pairs.append((a, b))

Optional: also skip when body == -1 if global–global contacts are already excluded by the collision system.

Run to find any other non-canonical appends:

#!/bin/bash
rg -nP -C2 'shape_collision_filter_pairs\.append\('
newton/_src/solvers/mujoco/solver_mujoco.py (2)

1618-1651: Fix potential KeyError and canonicalize pair membership checks

The method has two issues:

  1. Direct indexing of model.body_shapes[b1] will raise KeyError if a body has no shapes
  2. Shape collision filter pairs may not be stored in canonical order, causing missed exclusions

Apply this diff to fix both issues:

     @staticmethod
     def find_body_collision_filter_pairs(
         model: Model,
         selected_bodies: nparray,
         colliding_shapes: nparray,
     ):
         """For shape collision filter pairs, find body collision filter pairs that are contained within."""
 
         body_exclude_pairs = []
         shape_set = set(colliding_shapes)
 
-        body_shapes = {}
-        for body in selected_bodies:
-            shapes = model.body_shapes[body]
-            shapes = [s for s in shapes if s in shape_set]
-            body_shapes[body] = shapes
-
         bodies_a, bodies_b = np.triu_indices(len(selected_bodies), k=1)
         for body_a, body_b in zip(bodies_a, bodies_b, strict=True):
             b1, b2 = selected_bodies[body_a], selected_bodies[body_b]
-            shapes_1 = body_shapes[b1]
-            shapes_2 = body_shapes[b2]
+            shapes_1 = model.body_shapes.get(b1, [])
+            shapes_2 = model.body_shapes.get(b2, [])
+            shapes_1 = [s for s in shapes_1 if s in shape_set]
+            shapes_2 = [s for s in shapes_2 if s in shape_set]
+            if not shapes_1 or not shapes_2:
+                continue
             excluded = True
             for shape_1 in shapes_1:
                 for shape_2 in shapes_2:
-                    if shape_1 > shape_2:
-                        s1, s2 = shape_2, shape_1
-                    else:
-                        s1, s2 = shape_1, shape_2
-                    if (s1, s2) not in model.shape_collision_filter_pairs:
+                    # Check both orderings since pairs may not be canonicalized
+                    if ((shape_1, shape_2) not in model.shape_collision_filter_pairs and
+                        (shape_2, shape_1) not in model.shape_collision_filter_pairs):
                         excluded = False
                         break
+                if not excluded:
+                    break
             if excluded:
                 body_exclude_pairs.append((b1, b2))
         return body_exclude_pairs

1681-1688: Normalize shape pair before membership check in solver_mujoco.py

Verified shape_collision_filter_pairs are appended from multiple importers and builder code and ultimately turned into a set; some append sites do not guarantee a consistent ordering. Make the membership test order-independent.

Location: newton/_src/solvers/mujoco/solver_mujoco.py (around lines 1681–1688)

Suggested change (use min/max to normalize the pair):

         graph_edges = [
             (i, j)
             for i, j in zip(shape_a, shape_b, strict=True)
             if (
-                (selected_shapes[i], selected_shapes[j]) not in model.shape_collision_filter_pairs
+                (min(selected_shapes[i], selected_shapes[j]), max(selected_shapes[i], selected_shapes[j]))
+                not in model.shape_collision_filter_pairs
                 and (cgroup[i] == cgroup[j] or cgroup[i] == -1 or cgroup[j] == -1)
             )
         ]
🧹 Nitpick comments (3)
newton/_src/sim/builder.py (1)

1920-1920: New plot_articulation flag works; avoid empty labels edge-case

When both show_shape_keys and show_shape_types are False, shape nodes get an empty label. Harmless, but consider defaulting to the shape index to keep labels non-empty.

Would you like a small patch to show the index when both toggles are False?

Also applies to: 1934-1935, 1985-1990

newton/_src/solvers/mujoco/solver_mujoco.py (2)

2121-2127: Friction setup uses undefined shape_mu variable

After fixing line 1955, ensure the geom friction setup uses the correct variable name consistently.

Verify the variable name is consistent after the fix at line 1955:

                # set friction from Newton shape materials using model's friction parameters
                mu = shape_mu[shape]
                geom_params["friction"] = [
                    mu,
                    model.rigid_contact_torsional_friction * mu,
                    model.rigid_contact_rolling_friction * mu,
                ]

2029-2029: Simplify joint order validation

The current validation using any() with a generator is correct but could be more readable.

Consider this more explicit approach:

-        if any(joint_order[i] != i for i in range(len(joints_simple))):
+        if not np.array_equal(joint_order, np.arange(len(joints_simple))):
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 660d47f and 53b7f34.

📒 Files selected for processing (2)
  • newton/_src/sim/builder.py (6 hunks)
  • newton/_src/solvers/mujoco/solver_mujoco.py (9 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: eric-heiden
PR: newton-physics/newton#587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
PR: newton-physics/newton#587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.

Applied to files:

  • newton/_src/sim/builder.py
  • newton/_src/solvers/mujoco/solver_mujoco.py
🧬 Code graph analysis (2)
newton/_src/sim/builder.py (1)
newton/_src/geometry/flags.py (1)
  • ShapeFlags (30-42)
newton/_src/solvers/mujoco/solver_mujoco.py (2)
newton/_src/sim/builder.py (3)
  • shape_count (508-512)
  • body_count (515-519)
  • joint_count (522-526)
newton/_src/sim/graph_coloring.py (2)
  • color_graph (189-259)
  • plot_graph (262-314)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (5)
newton/_src/sim/builder.py (1)

1353-1357: Good gating on collision participation before adding joint-based filters

Skipping parent/child shapes without COLLIDE_SHAPES avoids unnecessary filter pairs. Looks correct.

newton/_src/solvers/mujoco/solver_mujoco.py (4)

1654-1709: Excellent addition of shape labeling for graph visualization!

The new shape_keys parameter makes debugging collision graphs much more intuitive by allowing meaningful labels on nodes. The implementation cleanly integrates with the existing visualization without disrupting the core graph coloring logic.


2368-2373: Good addition of body-level collision exclusions!

The implementation correctly adds MuJoCo contact exclusions for body pairs that should not collide, ensuring parent-child collisions are properly ignored even when one body is static.


2018-2023: Explicit typing improves maintainability

Good practice using explicit np.int32 dtype for the shape/body/joint arrays when not separating environments. This ensures type consistency across different code paths.


2071-2071: Improved geom naming using model.shape_key

Good change from type-based naming to using the actual shape keys, making debugging and identification easier.

Comment thread newton/_src/solvers/mujoco/solver_mujoco.py
@eric-heiden

Copy link
Copy Markdown
Member Author

The benchmark performance has decreased slightly, however I found this not to be severe since the overhead for computing those body pairs to exclude from collisions is rather small when comparing 8192 envs (3 runs each):

Environment Before (ms) After (ms)
Allegro hand 2233.59 ± 99.33 1908.22 ± 59.97
G1 humanoid 2565.19 ± 215.68 2711.45 ± 133.25

@eric-heiden eric-heiden merged commit c054658 into newton-physics:main Sep 20, 2025
11 of 12 checks passed
eric-heiden added a commit to eric-heiden/newton that referenced this pull request Jan 28, 2026
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Co-authored-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>
Co-authored-by: camevor <camevor@nvidia.com>
mmacklin pushed a commit to mmacklin/newton that referenced this pull request Apr 7, 2026
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Co-authored-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>
Co-authored-by: camevor <camevor@nvidia.com>
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