Clamp viewer picking force to prevent explosion on light objects#2385
Conversation
📝 WalkthroughWalkthroughClamp viewer picking forces using a per-body effective mass (body mass or summed articulation mass). The picking kernel now rescales applied forces to at most pick_max_acceleration × g × effective_mass (default 5g) to prevent explosive responses when picking light or articulated bodies near stiff contacts. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
adenzler-nvidia
left a comment
There was a problem hiding this comment.
Clean, minimal fix — correct placement (before torque computation), safe from division-by-zero, and the 5g heuristic is well-chosen so heavier objects are unaffected.
Minor: the PR branch may need a rebase to resolve a CHANGELOG merge conflict with recent main entries.
462fcc0 to
ca85bd1
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
One issue that I see is that when picking for example the finger of a humanoid, we will never manage to pick it up because the force is so small. |
|
@Kenny-Vilella which example did you try for the finger of the humanoid? We can also increase the clamp to be a bit higher (10x). |
|
For future reference, the most serious case was with the |
|
@Kenny-Vilella thanks for testing!! If I remove that clamp (revert all changes), I am not able to move the fingers of the robot_g1 with the picker. Was that working at some point? |
|
I can confirm Kenny's findings, g1 does move nicely before but not with this fix. |
466bdc2 to
23af427
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
newton/_src/viewer/picking.py (1)
103-111: Tighten type annotations for nullable input and Warp array typingAt Line 110 the function accepts
None, so the parameter type should reflect that. Also prefer typed Warp array annotations for consistency.Proposed annotation update
- def _compute_effective_mass(model: newton.Model) -> wp.array: + def _compute_effective_mass(model: newton.Model | None) -> wp.array[float]:As per coding guidelines: "Use PEP 604 unions (
x | None, notOptional[x]) in type annotations." and "Annotate Warp arrays with bracket syntax (wp.array[wp.vec3],wp.array2d[float],wp.array[Any]), not the parenthesized form."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@newton/_src/viewer/picking.py` around lines 103 - 111, Update the _compute_effective_mass annotation to reflect that model can be None and use Warp's bracketed array typing: change the parameter type to "newton.Model | None" and the return type to "wp.array[float]". Locate the function _compute_effective_mass in picking.py and replace the current signature accordingly so callers and type checkers know the input is nullable and the function returns a typed Warp array.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@newton/_src/viewer/picking.py`:
- Around line 120-138: The code only marks articulation membership using
joint_child_np, so root/parent-only bodies that never appear as children are
left with body_art == -1 and their mass is omitted; modify the initialization
loop over joints to also mark parent bodies (e.g., use joint_parent_np[j] when
parent >= 0) with joint_art_np[j] so those bodies are included in art_mass, then
the subsequent art_mass aggregation and effective[b] assignment (which reference
body_art, body_mass_np, and art_mass) will correctly include root/parent-only
bodies.
---
Nitpick comments:
In `@newton/_src/viewer/picking.py`:
- Around line 103-111: Update the _compute_effective_mass annotation to reflect
that model can be None and use Warp's bracketed array typing: change the
parameter type to "newton.Model | None" and the return type to
"wp.array[float]". Locate the function _compute_effective_mass in picking.py and
replace the current signature accordingly so callers and type checkers know the
input is nullable and the function returns a typed Warp array.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 9650b92c-9e53-4b1e-bd36-42cf3cde57da
📒 Files selected for processing (3)
CHANGELOG.mdnewton/_src/viewer/kernels.pynewton/_src/viewer/picking.py
✅ Files skipped from review due to trivial changes (1)
- CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
- newton/_src/viewer/kernels.py
|
@Kenny-Vilella @adenzler-nvidia with the current changes, users should be able to lift a robot even if they pick from a finger. |
adenzler-nvidia
left a comment
There was a problem hiding this comment.
Looks good — the force clamping logic is correct, the articulation mass approach is well-motivated, and the model is None case is handled.
Two minor suggestions, both non-blocking:
-
The
5.0factor in the kernel (5.0 * 9.81 * pick_effective_mass[pick_body]) is a tuning constant baked into the kernel. Consider exposing it as a constructor parameter (likepick_stiffness/pick_damping) so users can adjust it, or at minimum extracting it to a named constant. -
In
_compute_effective_mass, the fallbackart_start_np[a + 1] if a + 1 < len(art_start_np) else model.joint_countis unreachable —articulation_startalways has a sentinel entry fromfinalize(). Could simplify toart_start_np[a + 1].
…for linked bodies, own mass for free bodies)
Extract the hardcoded 5g force clamp into a configurable pick_max_acceleration field on PickingState (default 5.0).
23af427 to
9413232
Compare
adenzler-nvidia
left a comment
There was a problem hiding this comment.
LGTM — the pick_max_acceleration parameter is a clean addition, docstring follows project conventions with SI units, and the kernel now reads from pick_state instead of a hardcoded constant.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
newton/_src/viewer/picking.py (1)
107-145: Implementation is correct — optional numpy optimization available.The articulation mass aggregation logic correctly handles all cases:
- Free bodies retain their own mass
- Articulated bodies get the total mass of their articulation
- The
joint_child-only traversal is sufficient since Newton's model ensures every articulation body (including root) appears as a child of exactly one joint.For large models, the Python loops could be replaced with NumPy vectorized operations for marginal performance gains, though this is initialization-time code that runs once.
♻️ Optional: NumPy-based aggregation
if model.joint_count > 0: joint_child_np = model.joint_child.numpy() joint_art_np = model.joint_articulation.numpy() # Map each body to its articulation index (-1 if free) body_art = np.full(model.body_count, -1, dtype=np.int32) - for j in range(model.joint_count): - child = joint_child_np[j] - if child >= 0: - body_art[child] = joint_art_np[j] + valid_children = joint_child_np >= 0 + body_art[joint_child_np[valid_children]] = joint_art_np[valid_children] - # Sum mass per articulation - art_mass = {} - for b in range(model.body_count): - a = body_art[b] - if a >= 0: - art_mass[a] = art_mass.get(a, 0.0) + body_mass_np[b] - - # Assign total articulation mass to each body in that articulation - for b in range(model.body_count): - a = body_art[b] - if a >= 0: - effective[b] = art_mass[a] + # Sum mass per articulation and assign back + in_art = body_art >= 0 + if np.any(in_art): + art_ids = body_art[in_art] + art_mass_sum = np.bincount(art_ids, weights=body_mass_np[in_art]) + effective[in_art] = art_mass_sum[art_ids]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@newton/_src/viewer/picking.py` around lines 107 - 145, The current _compute_effective_mass implementation is correct but can be optimized by replacing the Python loops that build body_art, compute art_mass, and assign per-body effective masses with NumPy vectorized ops: create body_art from joint_child_np/joint_art_np by initializing body_art = np.full(...) and using body_art[joint_child_np >= 0] = joint_art_np[...] (or equivalent indexed assignment), compute articulation masses with np.bincount on joint_art_np filtered by child indices and weights from body_mass_np (ensuring the bincount length covers max articulation index), then assign effective = np.where(body_art >= 0, art_mass_lookup[body_art], body_mass_np); keep returning wp.array(effective, dtype=float, device=model.device) and preserve behavior for model is None and free bodies.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@newton/_src/viewer/picking.py`:
- Around line 107-145: The current _compute_effective_mass implementation is
correct but can be optimized by replacing the Python loops that build body_art,
compute art_mass, and assign per-body effective masses with NumPy vectorized
ops: create body_art from joint_child_np/joint_art_np by initializing body_art =
np.full(...) and using body_art[joint_child_np >= 0] = joint_art_np[...] (or
equivalent indexed assignment), compute articulation masses with np.bincount on
joint_art_np filtered by child indices and weights from body_mass_np (ensuring
the bincount length covers max articulation index), then assign effective =
np.where(body_art >= 0, art_mass_lookup[body_art], body_mass_np); keep returning
wp.array(effective, dtype=float, device=model.device) and preserve behavior for
model is None and free bodies.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 9c6d6ce6-5483-4cde-aef3-2b9e92e4ce00
📒 Files selected for processing (3)
CHANGELOG.mdnewton/_src/viewer/kernels.pynewton/_src/viewer/picking.py
✅ Files skipped from review due to trivial changes (1)
- CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
- newton/_src/viewer/kernels.py
The CollisionPipeline fix was not cherry-picked but its changelog entry came along via a merge conflict resolution in newton-physics#2385.
Description
Clamp the viewer picking force magnitude to 5g of the picked body's weight, preventing a positive feedback loop where the spring-damper force diverges on light objects bouncing off stiff contacts.
Closes #2361
Checklist
CHANGELOG.mdhas been updated (if user-facing change)Test plan
Manual verification with examples that use the viewer picker:
Bug fix
The viewer's picking spring-damper force enters a positive feedback loop on light objects (e.g. the 64g M20 nut). When the picked body bounces off a stiff contact, the damping term produces a disproportionate opposing force, driving the body back harder and creating a runaway cycle that diverges within ~20 frames.
Steps to reproduce:
uv run -m newton.examples nut_bolt_hydroThe fix clamps the picking force to 5g of the body's weight (4 lines, no API changes). The clamp only activates when the force is extreme — heavier objects are virtually unaffected.
Summary by CodeRabbit