Skip to content

Fix contact shape mapping#620

Merged
camevor merged 3 commits into
newton-physics:mainfrom
camevor:fix-contact-shape-mapping
Aug 22, 2025
Merged

Fix contact shape mapping#620
camevor merged 3 commits into
newton-physics:mainfrom
camevor:fix-contact-shape-mapping

Conversation

@camevor

@camevor camevor commented Aug 22, 2025

Copy link
Copy Markdown
Member

Description

This PR removes the no-longer correct contact shape introduced for the contact sensors in favor of the mapping used elsewhere.

Before your PR is "Ready for review"

  • All commits are signed-off to indicate that your contribution adheres to the Developer Certificate of Origin requirements
  • 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

  • Refactor
    • Simplified MuJoCo contact/geometry mapping with direct geometry creation and a unified internal mapping, reducing intermediate structures and streamlining contact updates.
  • Chores
    • Removed deprecated API for explicit contact-geometry mapping and related configuration state. If your integration depended on creating or passing a custom mapping, update it to rely on the built-in unified mapping.

Signed-off-by: camevor <camevor@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 22, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Simplifies MuJoCo solver contact and geometry mapping by removing contact_geom_mapping, mj_geoms, shape_map, and selected_bodies_set. Deletes create_newton_contact_geom_mapping. update_contacts now passes to_newton_shape_index into convert_mjw_contact_to_warp_kernel. Geometry creation uses body.add_geom(**geom_params) directly.

Changes

Cohort / File(s) Summary of Changes
MuJoCo solver mapping simplification
newton/_src/solvers/mujoco/solver_mujoco.py
Removed contact_geom_mapping initialization and create_newton_contact_geom_mapping method; deleted mj_geoms/shape_map and selected_bodies_set logic; updated update_contacts to pass to_newton_shape_index to convert_mjw_contact_to_warp_kernel; switched to direct geom creation via body.add_geom(**geom_params).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Sim as SolverMuJoCo
  participant MJ as MuJoCo Model
  participant K as convert_mjw_contact_to_warp_kernel
  participant W as Warp/Newton Shapes

  Sim->>MJ: Gather contacts
  note over Sim,MJ: Existing contact data from MuJoCo

  rect rgba(200,230,255,0.3)
  note right of Sim: Mapping source changed
  Sim->>K: update_contacts(..., to_newton_shape_index, ...)
  end

  K->>W: Convert MJ contacts -> Warp using to_newton_shape_index
  K-->>Sim: Converted contacts
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • adenzler-nvidia
  • eric-heiden
  • AntoineRichard

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Signed-off-by: camevor <camevor@nvidia.com>
@camevor camevor marked this pull request as ready for review August 22, 2025 15:02
Signed-off-by: camevor <camevor@nvidia.com>
@camevor camevor force-pushed the fix-contact-shape-mapping branch from 091bcba to 147993a Compare August 22, 2025 15:07
@camevor camevor enabled auto-merge (squash) August 22, 2025 15:08

@adenzler-nvidia adenzler-nvidia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the fix and cleanup! Verified that this fixes training in issacLab. Let's add a ticket and follow-up on some regression tests next week.

@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: 0

Caution

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

⚠️ Outside diff range comments (2)
newton/_src/solvers/mujoco/solver_mujoco.py (2)

1006-1028: Static-shape mapping is overwritten across worlds; world-id becomes ambiguous and there’s a write race.

For shapes with shape_group < 0 (static, shared across all envs), full_shape_mapping[global_shape_idx] is written once per world with a different env_idx, so the last writer wins. Later, convert_newton_contacts_to_mjwarp_kernel infers worldid from this mapping, which can point to the wrong world for contacts in other envs. Also, multiple threads write the same index concurrently.

Fix: store worldid = -1 for static shapes (so the contact code can derive the world from the other shape), write the static entry once (e.g., env_idx == 0), but still fill reverse_shape_mapping for every world. This also removes the write race.

Apply this diff:

 def update_incoming_shape_xform_kernel(
@@
-    template_shape_idx = geom_to_shape_idx[geom_idx]
+    template_shape_idx = geom_to_shape_idx[geom_idx]
     if template_shape_idx < 0:
         return
-    if shape_group[template_shape_idx] < 0:
-        # this is a static shape that is used in all environments
-        global_shape_idx = template_shape_idx
-    else:
-        global_shape_idx = env_idx * shape_range_len + template_shape_idx
-    full_shape_mapping[global_shape_idx] = wp.vec2i(env_idx, geom_idx)
-    reverse_shape_mapping[env_idx, geom_idx] = global_shape_idx
-    # Update incoming shape transforms
-    # compute the difference between the original shape transform
-    # and the transform after applying the joint child transform
-    # and the transform MuJoCo does on mesh geoms
-    original_tf = shape_transform[global_shape_idx]
-    mjc_p = geom_pos[geom_idx]
-    q = geom_quat[geom_idx]
-    # convert quat from wxyz to xyzw
-    mjc_q = wp.quat(q[1], q[2], q[3], q[0])
-    mjc_tf = wp.transform(mjc_p, mjc_q)
-    shape_incoming_xform[global_shape_idx] = mjc_tf * wp.transform_inverse(original_tf)
+    if shape_group[template_shape_idx] < 0:
+        # static shape shared across envs
+        global_shape_idx = template_shape_idx
+        # reverse map must exist for each env/geom pair
+        reverse_shape_mapping[env_idx, geom_idx] = global_shape_idx
+        # write the forward mapping and incoming xform once to avoid a race
+        if env_idx != 0:
+            return
+        # use worldid = -1 so downstream chooses the other shape's world
+        full_shape_mapping[global_shape_idx] = wp.vec2i(-1, geom_idx)
+    else:
+        # per-env replicated shape
+        global_shape_idx = env_idx * shape_range_len + template_shape_idx
+        reverse_shape_mapping[env_idx, geom_idx] = global_shape_idx
+        full_shape_mapping[global_shape_idx] = wp.vec2i(env_idx, geom_idx)
+    # Update incoming shape transforms (computed from env0 geom arrays)
+    original_tf = shape_transform[global_shape_idx]
+    mjc_p = geom_pos[geom_idx]
+    q = geom_quat[geom_idx]
+    mjc_q = wp.quat(q[1], q[2], q[3], q[0])  # wxyz -> xyzw
+    mjc_tf = wp.transform(mjc_p, mjc_q)
+    shape_incoming_xform[global_shape_idx] = mjc_tf * wp.transform_inverse(original_tf)

273-315: Guard against negative shape indices when building geoms in contact conversion.

shape_a/shape_b are conditionally checked for bodies, but to_mjc_geom_index[shape_*] is still indexed unconditionally. If a sentinel like -1 ever occurs, we index out of range.

Apply this diff:

-    world_geom_a = to_mjc_geom_index[shape_a]
-    world_geom_b = to_mjc_geom_index[shape_b]
+    world_geom_a = wp.vec2i(-1, -1)
+    world_geom_b = wp.vec2i(-1, -1)
+    if shape_a >= 0:
+        world_geom_a = to_mjc_geom_index[shape_a]
+    if shape_b >= 0:
+        world_geom_b = to_mjc_geom_index[shape_b]
🧹 Nitpick comments (2)
newton/_src/solvers/mujoco/solver_mujoco.py (2)

511-552: Rename kernel parameter for clarity and consistency.

convert_mjw_contact_to_warp_kernel still names the first parameter contact_geom_mapping, but you now pass to_newton_shape_index. Renaming improves readability and reduces future confusion.

Apply this diff:

 @wp.kernel
 def convert_mjw_contact_to_warp_kernel(
     # inputs
-    contact_geom_mapping: wp.array2d(dtype=wp.int32),
+    to_newton_shape_index: wp.array2d(dtype=wp.int32),
     pyramidal_cone: bool,
@@
-    for i in range(2):
-        pair[i] = contact_geom_mapping[worldid, geoms_mjw[i]]
+    for i in range(2):
+        pair[i] = to_newton_shape_index[worldid, geoms_mjw[i]]

2014-2017: Storing geom names and resolving indices post-compile — sensible decoupling.

Writing body.add_geom(**geom_params) and deferring to mj_name2id avoids relying on speculative indices. The subsequent name→id resolution (Lines 2274-2284) is robust given the unique naming scheme f"{geom_type_name[stype]}_{shape}".

Optionally assert uniqueness at creation time to catch accidental collisions early:

-                name = f"{geom_type_name[stype]}_{shape}"
+                name = f"{geom_type_name[stype]}_{shape}"
+                # Defensive: ensure name is unique
+                if mujoco.mj_name2id(spec, mujoco.mjtObj.mjOBJ_GEOM, name) >= 0:
+                    raise ValueError(f"Duplicate geom name detected: {name}")
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9d1b1d1 and 147993a.

📒 Files selected for processing (1)
  • newton/_src/solvers/mujoco/solver_mujoco.py (2 hunks)
⏰ 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 (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (3)
newton/_src/solvers/mujoco/solver_mujoco.py (3)

1617-1636: Switched to to_newton_shape_index for contact mapping — correct direction.

Passing self.to_newton_shape_index into convert_mjw_contact_to_warp_kernel aligns update_contacts with the new post-compile mapping. Shapes: the kernel expects a 2D int32 map [world, geom] → shape, which matches how to_newton_shape_index is constructed at Lines 2295-2321. No functional concerns here.


1006-1028: Assumption: template → global shape indexing via env-stride may be invalid if indices aren’t block-strided by group.

The global_shape_idx = env_idx * shape_range_len + template_shape_idx formula assumes per-env shapes occupy contiguous blocks with identical intra-env ordering. If shape indices are interleaved or not strictly block-strided, reverse mapping becomes incorrect.

Consider asserting this precondition right after computing selected_shapes (Line 1937) in convert_to_mjc. Example check (debug-only):

@@
-        self.selected_shapes = wp.array(selected_shapes, dtype=wp.int32, device=model.device)
+        self.selected_shapes = wp.array(selected_shapes, dtype=wp.int32, device=model.device)
+        # Debug: verify per-env shapes are block-strided and consistent
+        if separate_envs_to_worlds:
+            non_neg = np.where(shape_group >= 0)[0]
+            if len(non_neg):
+                first_group = np.min(shape_group[non_neg])
+                per_env = np.where(shape_group == first_group)[0]
+                stride = len(per_env)
+                for env_idx in range(1, model.num_envs):
+                    g = first_group + env_idx
+                    env_shapes = np.where(shape_group == g)[0]
+                    if len(env_shapes) != stride:
+                        raise AssertionError("Per-env shape counts differ; mapping by fixed stride is invalid")
+                    # Optional: verify relative ordering matches template indices

If this assertion fails in real models, we should pass an explicit “template index → per-env global index” lookup into update_incoming_shape_xform_kernel instead of deriving it arithmetically.


2539-2570: Use the actual number of worlds from mjwarp data when launching geom-updates.

update_geom_properties uses num_worlds = self.model.num_envs. If separate_envs_to_worlds=False, put_data(..., nworld=1) creates 2D geom arrays with first dim 1, but self.model.num_envs could be >1, leading to OOB writes.

Consider deriving num_worlds from the target arrays or from self.mjw_data.nworld:

-        num_worlds = self.model.num_envs  # why is there no 'self.mjw_model.nworld'?
+        num_worlds = int(self.mjw_data.nworld) if hasattr(self, "mjw_data") else self.model.num_envs

If mjw_data isn’t available (CPU path), ensure this path isn’t invoked or adjust accordingly.

@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: 0

🧹 Nitpick comments (2)
newton/_src/solvers/mujoco/solver_mujoco.py (2)

1617-1630: Contact mapping switch is correct; add precondition checks and align kernel arg name.

Good change passing to_newton_shape_index into the contact conversion. Two follow-ups:

  • Add a guard to fail fast if the mapping wasn’t initialized (e.g., when constructing the solver with pre-existing mjw_model/mjw_data).
  • Minor naming nit: the kernel’s parameter is still called contact_geom_mapping, which now conceptually represents to_newton_shape_index. Renaming improves readability.

Apply the precondition check close to the launch:

         wp.launch(
             convert_mjw_contact_to_warp_kernel,
             dim=mj_data.nconmax,
             inputs=[
-                self.to_newton_shape_index,
+                # Preconditions
+                # Ensure mapping exists and matches current MjModel ngeom.
+                # This is especially important if the solver was constructed
+                # with pre-existing mjw_model/mjw_data and skipped convert_to_mjc().
+                (None if self.to_newton_shape_index is not None else (_ for _ in ()).throw(
+                    RuntimeError("to_newton_shape_index is None; mapping not initialized. Call convert_to_mjc or provide mapping.")
+                )),
+                (None if self.to_newton_shape_index is not None and
+                        self.to_newton_shape_index.shape[1] == self.mj_model.ngeom else (_ for _ in ()).throw(
+                    ValueError(f"to_newton_shape_index shape mismatch; expected second dim {self.mj_model.ngeom}")
+                )),
+                self.to_newton_shape_index,
                 self.mjw_model.opt.cone == int(self.mujoco.mjtCone.mjCONE_PYRAMIDAL),
                 mj_data.ncon,
                 mj_contact.frame,
                 mj_contact.dim,
                 mj_contact.geom,
                 mj_contact.efc_address,
                 mj_contact.worldid,
                 mj_data.efc.force,
             ],

If you prefer a rename for clarity (outside this hunk), consider updating the kernel signature and its uses:

- def convert_mjw_contact_to_warp_kernel(
-     contact_geom_mapping: wp.array2d(dtype=wp.int32),
+ def convert_mjw_contact_to_warp_kernel(
+     to_newton_shape_index: wp.array2d(dtype=wp.int32),
      pyramidal_cone: bool,
      ...
 )
@@
-    for i in range(2):
-        pair[i] = contact_geom_mapping[worldid, geoms_mjw[i]]
+    for i in range(2):
+        pair[i] = to_newton_shape_index[worldid, geoms_mjw[i]]

1979-1984: Avoid in-place mutation of shape_size when zero-filling.

size = shape_size[shape] returns a view; zero-filling will mutate model.shape_scale unexpectedly. Copy before patching zeros.

-                size = shape_size[shape]
+                # copy to avoid mutating model.shape_scale in-place
+                size = np.array(shape_size[shape], copy=True)
                 if np.any(size > 0.0):
                     # duplicate nonzero entries at places where size is 0
                     nonzero = size[size > 0.0][0]
                     size[size == 0.0] = nonzero
                     geom_params["size"] = size
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9d1b1d1 and 147993a.

📒 Files selected for processing (1)
  • newton/_src/solvers/mujoco/solver_mujoco.py (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-19T12:27:30.629Z
Learnt from: preist-nvidia
PR: newton-physics/newton#579
File: newton/examples/example_mujoco.py:350-354
Timestamp: 2025-08-19T12:27:30.629Z
Learning: In Newton examples, Example.use_mujoco is a high-level attribute that controls whether to use MuJoCo solver vs other solvers (like XPBD), while SolverMuJoCo.use_mujoco_cpu controls the backend within MuJoCo (CPU vs Warp). These are separate concepts serving different purposes - the PR rename only applies to the solver parameter, not the Example class attributes.

Applied to files:

  • newton/_src/solvers/mujoco/solver_mujoco.py
⏰ 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 (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (1)
newton/_src/solvers/mujoco/solver_mujoco.py (1)

2014-2017: Recording geoms by name and resolving indices post-compile: solid improvement.

Storing shape -> geom_name at creation and resolving to indices post-compile removes fragility around assumed ordering and fixes the contact shape mapping issue targeted by this PR. The approach fits well with MuJoCo’s name lookup and your later two-way mapping builds.

@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: 0

🧹 Nitpick comments (2)
newton/_src/solvers/mujoco/solver_mujoco.py (2)

2014-2017: Name-based geom bookkeeping is safer; mapping computed post-compile is correct.

Using body.add_geom(**geom_params) and storing the geom name, then resolving indices with mj_name2id post-compile, avoids brittle index assumptions during spec construction. The uniqueness guarantees above make this robust.

Minor nit:

  • The local shape_mapping now holds names during spec-build and indices afterwards; consider renaming the temporary (e.g., shape_to_geom_name) to reduce cognitive load while reading this function. No functional change.

1616-1629: Good fix: use the canonical geom→shape map in contacts.

Switching the first kernel arg to self.to_newton_shape_index aligns contact-pair reporting with the mapping used elsewhere (built in update_incoming_shape_xform_kernel). Shapes/dtypes match (int32, [nworld, ngeom]), so this resolves the incorrect contact sensor mapping.

✅ Verified: no leftover references to the old mapping helpers in solver_mujoco.py. Searches for create_newton_contact_geom_mapping and mj_geoms returned no matches, and contact_geom_mapping only appears in the kernel’s parameter list and its use sites as intended. All shape_map hits are confined to the USD-import utility (import_usd.py) and are unrelated to this change.

Two small follow-ups:

  • Consider renaming the kernel’s parameter from contact_geom_mapping to geom_to_shape_index for clarity, since it now directly receives to_newton_shape_index. This is a naming-only change.
  • Optional guard: if any pair[i] ends up -1, you may want to skip/zero that contact to avoid downstream consumers tripping on invalid shape ids (rare, but can occur with visual-only or filtered geoms if assumptions drift).
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9d1b1d1 and 147993a.

📒 Files selected for processing (1)
  • newton/_src/solvers/mujoco/solver_mujoco.py (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-19T12:27:30.629Z
Learnt from: preist-nvidia
PR: newton-physics/newton#579
File: newton/examples/example_mujoco.py:350-354
Timestamp: 2025-08-19T12:27:30.629Z
Learning: In Newton examples, Example.use_mujoco is a high-level attribute that controls whether to use MuJoCo solver vs other solvers (like XPBD), while SolverMuJoCo.use_mujoco_cpu controls the backend within MuJoCo (CPU vs Warp). These are separate concepts serving different purposes - the PR rename only applies to the solver parameter, not the Example class attributes.

Applied to files:

  • newton/_src/solvers/mujoco/solver_mujoco.py
⏰ 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 (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)

@codecov

codecov Bot commented Aug 22, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@camevor

camevor commented Aug 22, 2025

Copy link
Copy Markdown
Member Author

Thanks for the fix and cleanup! Verified that this fixes training in issacLab. Let's add a ticket and follow-up on some regression tests next week.

Thanks for the quick review! Created #621 for the testing.

@camevor camevor merged commit f671607 into newton-physics:main Aug 22, 2025
12 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Sep 20, 2025
4 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Oct 13, 2025
4 tasks
@camevor camevor deleted the fix-contact-shape-mapping branch October 17, 2025 13:51
eric-heiden pushed a commit to eric-heiden/newton that referenced this pull request Jan 28, 2026
Signed-off-by: camevor <camevor@nvidia.com>
mmacklin pushed a commit to mmacklin/newton that referenced this pull request Apr 7, 2026
Signed-off-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.

2 participants