Improve performance of ModelBuilder.add_builder method#772
Conversation
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
📝 WalkthroughWalkthroughMigrate ModelBuilder collision-filter storage from a set to a list; initialize static-body shapes entry (-1); refactor replicate/add_builder to reuse a single transform and reduce allocations; streamline transform/translation handling and joint/body merging; update importers and examples to append collision pairs; add tests for add_builder. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Src as Sub-builder
participant Dst as Main Builder
participant X as Reused Transform
participant F as FilterChecker
rect rgb(240,248,255)
note over Dst,X: add_builder / replicate (new flow)
Dst->>X: allocate single identity wp.transform
loop per copy
Dst->>X: mutate X.p (translation offset)
Src->>Dst: supply body/joint/shape arrays
Dst->>Dst: append bodies/joints/shapes, compute start_* offsets
Dst->>Dst: write body_q/body_qd/joint_X_p/joint_q at remapped indices
Dst->>Dst: append shape transforms via transform_mul(X, Src.shape_transform[s])
Dst->>Dst: append collision-filter pairs via .append((a,b))
end
end
rect rgb(240,255,240)
note over Dst,F: find_shape_contact_pairs (filter check)
Dst->>F: iterate model.shape_collision_filter_pairs (list)
loop each pair
F-->>Dst: membership check / skip duplicate processing
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
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 |
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
There was a problem hiding this comment.
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 (1)
newton/_src/utils/import_mjcf.py (1)
291-295: Bug:incoming_xformapplied twice for world geoms (link == -1).
tf = incoming_xform * tfis done at Lines 291–295 and again at Lines 310–312, double-shifting world-attached shapes. Fix by removing the second multiplication or guarding it to only apply forlink != -1.- if incoming_xform is not None: - tf = incoming_xform * tf + # already applied for world geoms above; keep only for non-world links if ever used there + if incoming_xform is not None and link != -1: + tf = incoming_xform * tfAlso applies to: 310-312
🧹 Nitpick comments (12)
newton/_src/sim/builder.py (6)
364-364: List replaces set for collision filters: enforce canonical pairs to avoid bloat.Now that this is a list, duplicates/non‑canonical ordering will accumulate. Please ensure all insertions use (min, max) ordering to keep
find_shape_contact_pairs()set‑conversion cheap.
977-981: Type mixing in particle offset.
pos_offset = xform.pmay be a Warp vec; adding it tonp.array(builder.particle_q)can incur dtype coercions. Cast once.Apply:
- pos_offset = xform.p + pos_offset = np.asarray(xform.p, dtype=np.float32)
1060-1062: Use captured start index for clarity and robustness.Using
start_shape_idxavoids relying on currentshape_countinvariants.- shape_count_offset = self.shape_count - self.shape_collision_filter_pairs.extend( - [(i + shape_count_offset, j + shape_count_offset) for i, j in builder.shape_collision_filter_pairs] - ) + self.shape_collision_filter_pairs.extend( + [(i + start_shape_idx, j + start_shape_idx) for i, j in builder.shape_collision_filter_pairs] + )
2416-2417: Canonicalize same‑body shape filter pairs.Ensure
(a, b)is ordered to matchfind_shape_contact_pairs()checks and prevent duplicates.- 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))
2441-2442: Canonicalize parent–child filter pairs added fromadd_shape.Same rationale as above.
- self.shape_collision_filter_pairs.append((parent_shape, shape)) + a, b = (parent_shape, shape) + if a > b: + a, b = b, a + self.shape_collision_filter_pairs.append((a, b))
4431-4465: Set conversion on filters: good safeguard; consider pruning on insert.Converting to a set at runtime avoids duplicates, but the list can still grow large. With canonicalization fixes above, consider deduping opportunistically if perf/memory becomes a concern.
newton/_src/utils/import_mjcf.py (2)
923-924: Filter pairs for visuals: canonicalize and skip self‑pairs.Avoid
(i, i)and enforce(min, max).- builder.shape_collision_filter_pairs.append((i, j)) + if i != j: + a, b = (i, j) if i < j else (j, i) + builder.shape_collision_filter_pairs.append((a, b))
926-929: Self‑collision disable loop: canonicalize pair order.Same rationale as above.
- builder.shape_collision_filter_pairs.append((i, j)) + a, b = (i, j) if i < j else (j, i) + builder.shape_collision_filter_pairs.append((a, b))newton/examples/robot/example_robot_allegro_hand.py (1)
111-112: Canonicalize appended filter pairs.Keep
(min, max)ordering to match filtering semantics elsewhere.- allegro_hand.shape_collision_filter_pairs.append((shape1, shape2)) + a, b = (shape1, shape2) if shape1 < shape2 else (shape2, shape1) + allegro_hand.shape_collision_filter_pairs.append((a, b))newton/_src/utils/import_usd.py (1)
1253-1271: Canonicalize and skip world–world pairs to avoid duplicates with list-backed filtersNow that shape_collision_filter_pairs is a list, appending unordered pairs introduces duplicates and unnecessary entries (especially (a,b) vs (b,a)). Also, per our previous learning, world-attached shapes (-1) already don’t collide; skip those pairs to keep the list compact.
Apply this diff in the three places where pairs are appended:
@@ - builder.shape_collision_filter_pairs.append((shape1, shape2)) + if not (builder.shape_body[shape1] == -1 and builder.shape_body[shape2] == -1): + a, b = sorted((shape1, shape2)) + builder.shape_collision_filter_pairs.append((a, b)) @@ - if other_shape_id != shape_id: - builder.shape_collision_filter_pairs.append((shape_id, other_shape_id)) + if other_shape_id != shape_id: + if not (builder.shape_body[shape_id] == -1 and builder.shape_body[other_shape_id] == -1): + a, b = sorted((shape_id, other_shape_id)) + builder.shape_collision_filter_pairs.append((a, b)) @@ - builder.shape_collision_filter_pairs.append((shape1, shape2)) + if not (builder.shape_body[shape1] == -1 and builder.shape_body[shape2] == -1): + a, b = sorted((shape1, shape2)) + builder.shape_collision_filter_pairs.append((a, b))(Background: collisions between body == -1 shapes are auto-skipped; no need to list them.)
newton/tests/test_model.py (1)
570-571: Use tolerant comparison for transforms to avoid float-equality flakinessDirect equality on warp transforms can be brittle due to FP rounding. Use the helper already imported.
- self.assertEqual(builder.body_q[0], offset_xform * orig_xform) - self.assertEqual(builder.body_q[1], offset_xform * orig_xform) + assert_np_equal(np.array(builder.body_q[0]), np.array(offset_xform * orig_xform), tol=1.0e-6) + assert_np_equal(np.array(builder.body_q[1]), np.array(offset_xform * orig_xform), tol=1.0e-6)newton/_src/utils/import_urdf.py (1)
533-541: Canonicalize filter pairs (and guard i==j) to reduce duplicates with list-backed storageSmall cleanup to keep the list minimal.
- for i in range(start_shape_count, end_shape_count): - for j in visual_shapes: - builder.shape_collision_filter_pairs.append((i, j)) + for i in range(start_shape_count, end_shape_count): + for j in visual_shapes: + if i != j: + a, b = sorted((i, j)) + builder.shape_collision_filter_pairs.append((a, b)) @@ - for i in range(start_shape_count, end_shape_count): - for j in range(i + 1, end_shape_count): - builder.shape_collision_filter_pairs.append((i, j)) + for i in range(start_shape_count, end_shape_count): + for j in range(i + 1, end_shape_count): + a, b = sorted((i, j)) + builder.shape_collision_filter_pairs.append((a, b))
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
newton/_src/sim/builder.py(12 hunks)newton/_src/utils/import_mjcf.py(1 hunks)newton/_src/utils/import_urdf.py(1 hunks)newton/_src/utils/import_usd.py(1 hunks)newton/examples/robot/example_robot_allegro_hand.py(1 hunks)newton/tests/test_model.py(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 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/examples/robot/example_robot_allegro_hand.pynewton/_src/utils/import_mjcf.pynewton/_src/utils/import_usd.pynewton/_src/utils/import_urdf.pynewton/_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/utils/import_usd.py
📚 Learning: 2025-08-20T03:30:16.037Z
Learnt from: eric-heiden
PR: newton-physics/newton#584
File: newton/_src/utils/import_urdf.py:159-167
Timestamp: 2025-08-20T03:30:16.037Z
Learning: In the URDF importer (newton/_src/utils/import_urdf.py), cylinders are intentionally created using add_shape_capsule() instead of add_shape_cylinder() because cylinder collisions are not fully supported yet. This is a deliberate workaround until proper cylinder collision support is implemented.
Applied to files:
newton/_src/utils/import_usd.pynewton/_src/utils/import_urdf.py
🧬 Code graph analysis (3)
newton/_src/utils/import_usd.py (1)
newton/_src/sim/builder.py (1)
shape_count(508-512)
newton/tests/test_model.py (3)
newton/_src/sim/builder.py (10)
ModelBuilder(67-4464)add_body(1184-1246)add_joint_revolute(1367-1449)add_shape_sphere(2524-2555)add_joint_free(1610-1661)add_builder(900-1182)body_count(515-519)joint_count(522-526)articulation_count(571-575)shape_count(508-512)newton/_src/sim/joints.py (1)
JointType(20-44)newton/tests/unittest_utils.py (1)
assert_np_equal(239-245)
newton/_src/sim/builder.py (2)
newton/_src/sim/style3d/builder_style3d.py (1)
add_builder(110-140)newton/_src/sim/state.py (2)
particle_count(94-98)body_count(87-91)
⏰ 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 (6)
newton/_src/sim/builder.py (6)
639-642: Reusing a single Transform instance across iterations: confirm no aliasing.
xformis mutated in-place then passed toadd_builder(). Verifyadd_builder()never storesxformby reference; otherwise subsequent mutations could affect prior copies. If unsure, instantiate per-iteration.
968-971: Direct call into Warp core + from_buffer: verify ABI stability and perf.
wp.context.runtime.core.wp_builtin_mul_transformf_transformfis internal;from_buffer(np.empty(7))allocates every call. Confirm against the Warp version targeted in CI and benchmark vs. plaina * bto justify the indirection.
1025-1041: Root transforms: looks correct; add a targeted test for FREE joints.Good handling of FREE joint
qand world-parentX_p. Please add/keep a unit test that asserts bothjoint_qandjoint_X_pupdate withxform(position + quaternion).
1360-1364: LGTM: parent–child filter pairs now canonicalized and appended.
2228-2231: Collapse path: direct re-append of transforms looks fine.Reusing stored
q/qdis OK here.
2310-2311: Collapse path: preserving joint frames OK.Keeping
parent_xform/child_xformdirectly preserves original frames.
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
newton/_src/sim/builder.py (4)
364-365: List-based collision-filter storage: OK; consider dedup before use.Switching from a set to a list is fine for amortized appends. To keep memory bounded across many add_builder/replicate calls, dedup the list at a choke point (e.g., before assigning to the Model).
You can do this near finalize:
unique_pairs = sorted(set(self.shape_collision_filter_pairs)) m.shape_collision_filter_pairs = unique_pairs
639-646: Avoid item-assignment on wp.transform; set .p directly.Index-based writes on wp.transform may not be supported and could fail on some Warp versions. Set the translation via .p (or construct a new transform).
- xform = wp.transform_identity() + xform = wp.transform_identity() for i in range(num_copies): offset = offsets[i] - # assign translation - for j in range(3): - xform[j] = offset[j] + # assign translation + xform.p = wp.vec3(offset[0], offset[1], offset[2]) self.add_builder(builder, xform=xform)
980-983: Normalize pos_offset to NumPy for vectorized add.xform.p may be a Warp vec3; converting once avoids dtype surprises and object arrays during np math.
- if xform is not None: - pos_offset = xform.p + if xform is not None: + pos_offset = np.array(xform.p, dtype=np.float32) else: pos_offset = np.zeros(3)
1061-1063: Offsetting imported filter pairs: OK; consider canonicalization + dedup.Pairs are appended with offsets. To prevent accidental duplicates from different code paths, canonicalize and dedup once (see earlier comment).
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
newton/_src/sim/builder.py(12 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 (1)
newton/_src/sim/builder.py (2)
newton/_src/sim/style3d/builder_style3d.py (1)
add_builder(110-140)newton/_src/sim/joints.py (1)
JointType(20-44)
⏰ 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 Unit Tests on AWS EC2 (Pull Request)
- GitHub Check: Run GPU Benchmarks (Pull Request)
- GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
- GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (8)
newton/_src/sim/builder.py (8)
408-409: Static body bucket always present (-1): LGTM.This removes repeated get(-1, []) boilerplate and simplifies callers.
1015-1017: Static shape transform offset: LGTM.Correctly applies xform only to world-attached shapes; body-attached shapes remain in body frame.
1026-1041: Joint relocation under xform: LGTM; verify FREE joint quaternion ordering.FREE joints’ q are updated and parent-frame X_p is offset when parent == -1. Please confirm builder.joint_q stores orientation as xyzw (the code assumes 7-tuple [px,py,pz,qx,qy,qz,qw]).
1050-1055: Body pose offset under xform: LGTM.Applying xform to all body_q is the right global relocation; FREE joint fix-ups above keep consistency.
1361-1365: Parent/child collision filtering on joint add: LGTM.Canonical ordering and append semantics match the new list storage.
2217-2221: Fixed-joint collapse: static-shape restoration and direct re-append: LGTM.Keeping -1 bucket through clear/restore and reusing stored q/qd avoids extra allocations.
Also applies to: 2226-2228, 2307-2309
2414-2415: Same-body and parent-body collision filtering on shape add: LGTM.New shapes are larger-index; pairs are naturally in canonical order.
Also applies to: 2439-2440
4429-4463: Building filter set for contact search: LGTM.Converting the list to a set here provides O(1) lookups; matches the list-backed storage change.
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
newton/_src/sim/builder.py (1)
967-974: Unsafe from_buffer and reliance on private Warp core API.Creating a transform via from_buffer over a temporary NumPy array risks lifetime/aliasing; also wp.context.runtime.core.* is private and brittle. Allocate a proper wp.transform and add a safe fallback to Python-level multiply.
Apply this diff:
- transform_mul_cfunc = wp.context.runtime.core.wp_builtin_mul_transformf_transformf - - # dispatches two transform multiplies to the native implementation - def transform_mul(a, b): - out = wp.transform.from_buffer(np.empty(7, dtype=np.float32)) - transform_mul_cfunc(a, b, ctypes.byref(out)) - return out + # best-effort: use native cfunc if present; otherwise fall back to Python operator * + try: + _mul_tf_tf = wp.context.runtime.core.wp_builtin_mul_transformf_transformf # type: ignore[attr-defined] + except Exception: + _mul_tf_tf = None + + def transform_mul(a, b): + if _mul_tf_tf is not None: + out = wp.transform() # owns its memory + try: + _mul_tf_tf(a, b, ctypes.byref(out)) + return out + except Exception: + pass + return a * b
🧹 Nitpick comments (1)
newton/_src/sim/builder.py (1)
978-983: Ensure NumPy addition with pos_offset uses an array.xform.p is a Warp vec3; add explicit conversion to avoid dtype surprises.
- if xform is not None: - pos_offset = xform.p + if xform is not None: + pos_offset = np.array(xform.p, dtype=np.float32) else: pos_offset = np.zeros(3) - self.particle_q.extend((np.array(builder.particle_q) + pos_offset).tolist()) + self.particle_q.extend((np.array(builder.particle_q, dtype=np.float32) + pos_offset).tolist())
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
newton/_src/sim/builder.py(13 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 (1)
newton/_src/sim/builder.py (2)
newton/_src/sim/style3d/builder_style3d.py (1)
add_builder(110-140)newton/_src/sim/joints.py (1)
JointType(20-44)
⏰ 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-newton-tests / newton-unittests (windows-latest)
- GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (12)
newton/_src/sim/builder.py (12)
364-365: Switching collision-filter storage to list looks fine.Using a list simplifies append sites; you already convert to a set when needed. Keep an eye on duplicates/memory, but functionally LGTM.
408-409: Good default for static/global shapes.Initializing body_shapes with {-1: []} removes conditional checks downstream. LGTM.
1014-1015: Correctly offsetting world-attached shape transforms.Applying xform only for shapes with body == -1 is correct; local body-attached shapes already move with body_q. LGTM.
1025-1041: Joint offset handling looks right; minor sanity check.Updating FREE joint q from the prior 7-tuple and premultiplying X_p when parent == -1 is correct. Please verify quaternion normalization stays intact after transform_mul.
1049-1054: Body pose offsetting LGTM.Premultiplying body_q with xform only when provided is correct and avoids extra allocs.
1060-1062: Offsetting copied filter pairs is correct.Order is preserved since a constant offset is added to both indices. LGTM.
1363-1364: Collision filter insertion uses canonical order.Append with (a,b) after swap ensures stable lookup later. LGTM.
2216-2220: Static shape preservation across collapse is correct.Saving/restoring body_shapes[-1] around clear() avoids KeyError and keeps globals intact. LGTM.
2306-2308: Rebuilding joint frames uses stored transforms directly.Appending parent/child transforms from retained joints is fine. LGTM.
2412-2414: Same-body shape filter pairs OK.Adding (same_body_shape, shape) prevents intra-body contacts; index monotonicity makes the pair canonical. LGTM.
2435-2439: Parent-child collision filtering OK.Appending parent/child shape pairs when requested is consistent with the new list semantics. LGTM.
4428-4429: Set conversion for filters is appropriate.Building a set once for membership checks amortizes the list choice. LGTM.
There was a problem hiding this comment.
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)
4454-4462: Bug: mutating s1 corrupts subsequent iterations; also handle empty result array.
- Don’t swap s1 in-place; use local a,b to keep outer-loop state intact.
- When no pairs are found, np.array(contact_pairs) yields shape (0,), which breaks wp.vec2i construction.
Apply this diff:
- # Ensure canonical order (smaller_element, larger_element) - if s1 > s2: - s1, s2 = s2, s1 - - if (s1, s2) not in filters: - contact_pairs.append((s1, s2)) + # Ensure canonical order (smaller, larger) without mutating loop vars + a, b = (s1, s2) if s1 < s2 else (s2, s1) + if (a, b) not in filters: + contact_pairs.append((a, b)) - model.shape_contact_pairs = wp.array(np.array(contact_pairs), dtype=wp.vec2i, device=model.device) + if contact_pairs: + pairs_np = np.asarray(contact_pairs, dtype=np.int32).reshape(-1, 2) + else: + pairs_np = np.empty((0, 2), dtype=np.int32) + model.shape_contact_pairs = wp.array(pairs_np, dtype=wp.vec2i, device=model.device) model.shape_contact_pair_count = len(contact_pairs)
♻️ Duplicate comments (1)
newton/_src/sim/builder.py (1)
967-974: Stop using private Warp cfunc + from_buffer; allocate a proper transform with a safe fallback.Accessing wp.context.runtime.core is private and out may alias temporary memory. Use wp.transform() and fall back to public operator overload if the cfunc isn’t available. This was flagged previously and remains unresolved.
Apply this diff:
- # explicitly resolve the transform multiplication function to avoid - # repeatedly resolving builtin overloads during shape transformation - transform_mul_cfunc = wp.context.runtime.core.wp_builtin_mul_transformf_transformf - - # dispatches two transform multiplies to the native implementation - def transform_mul(a, b): - out = wp.transform.from_buffer(np.empty(7, dtype=np.float32)) - transform_mul_cfunc(a, b, ctypes.byref(out)) - return out + # Try to use the native transform mul; fall back to the public overload + try: + _mul_cfunc = wp.context.runtime.core.wp_builtin_mul_transformf_transformf # private; may not exist + except Exception: + _mul_cfunc = None + + def transform_mul(a, b): + if _mul_cfunc is not None: + out = wp.transform() + _mul_cfunc(a, b, ctypes.byref(out)) + return out + return a * b
🧹 Nitpick comments (2)
newton/_src/sim/builder.py (2)
1025-1040: Joint offsetting logic mostly fine; consider slice assign for FREE joints.Minor micro-opt: replace the 7‑element loop with a slice assign to reduce Python overhead.
- qi += start_q - for j in range(7): - self.joint_q[qi + j] = tf[j] + qi += start_q + self.joint_q[qi : qi + 7] = list(tf)
2217-2220: Robustness: guard static_shapes retrieval when clearing.If invariant breaks in future, this can KeyError. Cheap to harden.
- static_shapes = self.body_shapes[-1] + static_shapes = self.body_shapes.get(-1, []) self.body_shapes.clear() - # restore static shapes - self.body_shapes[-1] = static_shapes + # restore static shapes + self.body_shapes[-1] = static_shapes
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
newton/_src/sim/builder.py(13 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 (1)
newton/_src/sim/builder.py (1)
newton/_src/sim/style3d/builder_style3d.py (1)
add_builder(110-140)
⏰ 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). (1)
- GitHub Check: Run GPU Benchmarks (Pull Request)
🔇 Additional comments (11)
newton/_src/sim/builder.py (11)
364-365: API switch to list for collision filters looks good.Using a list is fine; downstream code converts to a set when needed.
409-409: Initialize body_shapes with -1 entry — good default.Aligns with the “global/world body = -1” convention and simplifies lookups.
975-983: Particle offset path reads xform.p — LGTM.Vectorized add with NumPy is appropriate here.
1014-1016: Efficient root-shape transform application — LGTM.Using transform_mul only when xform is provided avoids extra allocations.
1049-1054: Body pose offset via transform_mul — LGTM.
1060-1062: Filter-pair remap uses current shape_count as offset — verify this equals start_shape_idx.Given the ordering here, self.shape_count still reflects pre-copy counts (len(self.shape_type)), so offset==start_shape_idx. Please confirm no earlier code appends to shape_type before this block.
1360-1364: Canonicalize parent/child filter pairs — LGTM.Ensures stable lookup keys; good.
2307-2308: Re-appending parent/child joint transforms — LGTM.
2411-2415: Same-body collision filter insertion — LGTM.Pairs are inserted in canonical order since existing indices < new shape.
2434-2439: Parent–child collision filtering — LGTM.New child shape index ≥ existing parent shape indices, so pairs remain canonical.
4428-4429: Converting filters list to a set — LGTM.Dedupes at query time; acceptable given low frequency.
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
newton/_src/sim/builder.py (2)
639-645: Don’t mutate wp.transform via indexing; assign translation via .p.Index assignment on Warp transforms is not supported. Set xform.p explicitly.
Apply this diff:
- xform = wp.transform_identity() + xform = wp.transform() for i in range(num_copies): offset = offsets[i] - # assign translation - for j in range(3): - xform[j] = offset[j] + # assign translation + xform.p = wp.vec3(offset[0], offset[1], offset[2]) self.add_builder(builder, xform=xform)
969-974: Unsafe from_buffer + private C symbol; use proper transform and add fallback.Avoid from_buffer on a temp NumPy array and reliance on private runtime symbols without guard.
Apply this diff:
- def transform_mul(a, b): - out = wp.transform.from_buffer(np.empty(7, dtype=np.float32)) - transform_mul_cfunc(a, b, ctypes.byref(out)) - return out + def transform_mul(a, b): + out = wp.transform() + try: + transform_mul_cfunc(a, b, ctypes.byref(out)) + return out + except Exception: + # safe public fallback + return a * b
🧹 Nitpick comments (1)
newton/_src/sim/builder.py (1)
364-364: List-based collision filters: enforce invariants or dedup to bound growth.Switching to list is fine for determinism, but duplicates can accumulate. Guarantee (a <= b) ordering at all append sites (looks mostly true) and consider dedup once (e.g., in finalize) if lists get large.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
newton/_src/sim/builder.py(13 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 (1)
newton/_src/sim/builder.py (2)
newton/_src/sim/style3d/builder_style3d.py (1)
add_builder(110-140)newton/_src/sim/joints.py (1)
JointType(20-44)
⏰ 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 (12)
newton/_src/sim/builder.py (12)
408-408: Good default for static shapes bucket.Initializing body_shapes with {-1: []} avoids KeyErrors and simplifies callers.
1013-1016: Applying xform only for world-attached shapes is correct.Keeping body-attached shape transforms local avoids double-transforming.
1025-1039: Joint rebase logic: confirm constructor and indexing expectations.Please confirm that
wp.transform(*builder.joint_q[qi:qi+7])is a supported constructor call and thatstart_q/start_X_poffsets align withjoint_q_start/joint_X_plengths for all joint types.
1049-1054: body_q transform application looks good.Using transform_mul when xform is provided and raw extend otherwise is correct.
1359-1364: Canonical pair ordering on joint-based filters: LGTM.This avoids (a,b)/(b,a) duplicates.
2216-2220: Static-shapes preservation across rebuild: LGTM.Capturing and restoring
body_shapes[-1]is the right fix.
2225-2227: Retaining body q/qd directly is fine.Keeps transforms and velocities intact after collapse.
2306-2308: Re-append joint frames directly: LGTM.Consistent with earlier stored transforms.
2411-2417: Same-body collision filters on add_shape: LGTM.New shape index is always greater than existing, so canonical order holds.
2435-2440: Parent–child collision filters on add_shape: LGTM.Parent shapes precede child shapes; order is canonical.
4428-4428: Converting filters to a set for lookup is appropriate.This compensates for list storage and removes duplicates at query time.
4454-4458: Stable pair construction without mutating loop vars: LGTM.Fixes the prior s1/s2 mutation issue.
camevor
left a comment
There was a problem hiding this comment.
Added one point, otherwise lgtm!
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (5)
newton/_src/sim/builder.py (5)
1059-1061: Fix validated: use start_shape_idx for filter-pair offset.This resolves the over-shift bug when copying filter pairs.
4162-4163: Model expects a set — good cast at finalize.Matches solver expectations noted in prior review.
4427-4463: Contact-pair generation is now set-aware and canonical; minor optional skip for world–world.
- Using model.shape_collision_filter_pairs (set) is right.
- Canonicalization avoids s1/s2 mutation bugs from earlier versions.
- Optional: skip pairs where both shapes are world-attached (body == -1); collision is auto-skipped, so this trims work.
Apply:
for i1 in range(len(sorted_indices)): s1 = sorted_indices[i1] + b1 = self.shape_body[s1] env1 = self.shape_group[s1] collision_group1 = self.shape_collision_group[s1] for i2 in range(i1 + 1, len(sorted_indices)): s2 = sorted_indices[i2] + b2 = self.shape_body[s2] env2 = self.shape_group[s2] + # skip world–world pairs (auto-ignored by collision) + if b1 == -1 and b2 == -1: + continueAlso safe to place the world–world check after the env check to retain the early-break behavior.
639-646: Don’t mutate wp.transform via indexing; set translation via .p.Index assignment on wp.transform isn’t supported; assign the position vector directly.
Apply:
- xform = wp.transform_identity() + xform = wp.transform_identity() for i in range(num_copies): offset = offsets[i] - # assign translation - for j in range(3): - xform[j] = offset[j] + # assign translation safely + xform.p = wp.vec3(offset[0], offset[1], offset[2]) self.add_builder(builder, xform=xform)
969-974: Unsafe transform buffer + private Warp API; allocate a real transform and add fallback.Avoid from_buffer on a temp NumPy array and hard reliance on wp.context.runtime.core.*.
Apply:
- transform_mul_cfunc = wp.context.runtime.core.wp_builtin_mul_transformf_transformf + try: + transform_mul_cfunc = wp.context.runtime.core.wp_builtin_mul_transformf_transformf + except AttributeError: + transform_mul_cfunc = None @@ - def transform_mul(a, b): - out = wp.transform.from_buffer(np.empty(7, dtype=np.float32)) - transform_mul_cfunc(a, b, ctypes.byref(out)) - return out + def transform_mul(a, b): + if transform_mul_cfunc is None: + return a * b + out = wp.transform() # proper Warp-owned storage + try: + transform_mul_cfunc(a, b, ctypes.byref(out)) + return out + except Exception: + return a * b
🧹 Nitpick comments (2)
newton/_src/sim/builder.py (2)
979-983: Ensure pos_offset is NumPy-compatible.xform.p is a wp.vec3; make the dtype explicit before vectorized add.
Apply:
- pos_offset = xform.p + pos_offset = np.array([xform.p[0], xform.p[1], xform.p[2]], dtype=np.float32)
940-949: Docs/migration note needed for users of Shape filter API change.Public API now stores filter pairs as list in the builder; document the cast to set at finalize and that callers should not rely on set semantics pre-finalize.
I can draft a short migration snippet for docs/migration.rst if helpful.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
newton/_src/sim/builder.py(14 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 (1)
newton/_src/sim/builder.py (2)
newton/_src/sim/style3d/builder_style3d.py (1)
add_builder(110-140)newton/_src/sim/joints.py (1)
JointType(20-44)
⏰ 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 (14)
newton/_src/sim/builder.py (14)
408-409: Initialize body_shapes with -1 entry.Good default to avoid KeyErrors for world/static shapes.
1014-1016: OK: shape transform offset uses fast mul.Correctly applies xform only for world-attached shapes.
1025-1040: Free-joint pose and parent-frame offsets look correct.Offsets joint_q for FREE and X_p for world-anchored parents only; indices adjusted via start_q/start_X_p.
Please confirm unit coverage hits:
- FREE joint child under xform
- Non-FREE with parent == -1
1359-1363: Canonical order for parent/child filter pairs.Consistent tuple ordering; fine with list storage.
2412-2413: Same-body shape filters are canonical by construction.New shape index is always larger; appending (older, newer) is correct and duplicate-safe once cast to set.
2434-2438: Parent–child collision filters: order and timing are correct.Parent shapes exist before child; tuples are canonical and offset-free here.
1170-1179: Env-count logic LGTM.Globals don’t bump num_envs; explicit environment ensures monotonic num_envs.
1090-1166: Bulk attribute extension is coherent.Order aligns with earlier pre-extensions (shape_body/transform first, rest later).
Run tests that assert all per-shape arrays have equal length after add_builder.
2215-2219: Static-shape preservation during collapse is correct.Saving/restoring body_shapes[-1] avoids losing world-attached shapes.
2305-2307: Reusing stored joint transforms is fine.No re-expand/pack overhead; consistent with earlier storage.
955-964: Assigning current_env_group per add_builder instance is sound.Group override semantics are clear; matches the docstring.
1063-1089: Group overrides for particles/bodies/shapes/joints/articulations are consistent.Flat override is expected with add_builder(environment=...).
4122-4152: Shape arrays to Model: consistent dtypes; geo_source de-dup is good.No concerns.
364-364: Switching collision filters to list is fine; cast to set at finalize.No .add calls found — all mutations use append/extend; finalize casts to set (newton/_src/sim/builder.py:4162); model still stores a set (newton/_src/sim/model.py:140).
There was a problem hiding this comment.
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 (1)
newton/_src/sim/builder.py (1)
975-983: Potential type/broadcast bug: mixing wp.vec3 with NumPy array math.
xform.pis a wp.vec3; adding it tonp.array(builder.particle_q)can yield dtype=object or raise. Convert to a NumPy vector and stack particle positions robustly.Apply:
- if xform is not None: - pos_offset = xform.p - else: - pos_offset = np.zeros(3) - self.particle_q.extend((np.array(builder.particle_q) + pos_offset).tolist()) + if xform is not None: + pos_offset = np.array([xform.p[0], xform.p[1], xform.p[2]], dtype=np.float32) + # robust stacking regardless of wp.vec3 or lists/tuples + pos_np = np.array([[p[0], p[1], p[2]] for p in builder.particle_q], dtype=np.float32) + self.particle_q.extend((pos_np + pos_offset).tolist()) + else: + # fast path: no offset + self.particle_q.extend(builder.particle_q)
♻️ Duplicate comments (2)
newton/_src/sim/builder.py (2)
639-646: Don’t mutate wp.transform via indexing; set translation via .p.Index-based mutation on wp.transform is brittle and was previously flagged. Assign the position once via xform.p.
Apply:
- xform = wp.transform_identity() + xform = wp.transform_identity() for i in range(num_copies): offset = offsets[i] - # assign translation - for j in range(3): - xform[j] = offset[j] + # assign translation + xform.p = wp.vec3(offset[0], offset[1], offset[2]) self.add_builder(builder, xform=xform)
967-974: Unsafe from_buffer + private API usage; allocate a real transform and add a safe fallback.Creating a transform from a temporary NumPy buffer risks lifetime issues, and accessing wp.context.runtime.core is private/fragile. Use a proper wp.transform for the out param and fall back to Python-level multiply if the cfunc is unavailable.
Apply:
- transform_mul_cfunc = wp.context.runtime.core.wp_builtin_mul_transformf_transformf + # Try to resolve native mul; fall back to Python operator if unavailable + transform_mul_cfunc = getattr(getattr(getattr(wp.context.runtime, "core", object()), + "wp_builtin_mul_transformf_transformf", None), + "__call__", None) # dispatches two transform multiplies to the native implementation def transform_mul(a, b): - out = wp.transform.from_buffer(np.empty(7, dtype=np.float32)) - transform_mul_cfunc(a, b, ctypes.byref(out)) - return out + # allocate a proper Warp transform; C kernel will fill it + out = wp.transform() + if transform_mul_cfunc is not None: + try: + transform_mul_cfunc(a, b, ctypes.byref(out)) + return out + except Exception: + pass + # safe fallback using public operator + return a * b
🧹 Nitpick comments (5)
newton/_src/sim/builder.py (5)
364-364: LGTM: list-backed collision-filter store with set materialization at finalize.Storing as a list here and converting to a set in finalize is fine. Keep an eye on growth if many duplicates are appended in hot import paths.
If duplicates become an issue, we can keep a small shadow set for O(1) membership checks at append sites while still storing the list for order/debuggability.
1025-1040: FREE-joint rebase logic looks correct; minor robustness nit.The loop writes 7 scalars via
tf[j]. If Warp ever changes transform indexing, consider slicing with.p/.qto be explicit.Would you like a follow-up patch to assign
self.joint_q[qi:qi+3] = tf.pandself.joint_q[qi+3:qi+7] = tf.q?
2411-2415: Skip adding filter pairs for world (-1) shapes; canonicalize order.Per our learnings, collisions between shapes with body index -1 are already skipped by the collision system; adding pairs here bloats the list. Also make the order explicit.
Apply:
if body in self.body_shapes: # 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 body != -1: + for same_body_shape in self.body_shapes[body]: + a, b = (same_body_shape, shape) if same_body_shape < shape else (shape, same_body_shape) + self.shape_collision_filter_pairs.append((a, b)) self.body_shapes[body].append(shape) else: self.body_shapes[body] = [shape]Context: “collisions between shapes with body index -1 ... are automatically skipped,” so we can avoid emitting those pairs. [Used retrieved learnings]
2434-2438: Canonicalize parent/child shape filter pairs for consistency.Guarantees
(min, max)ordering like other sites.Apply:
- 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 parent_shape < shape else (shape, parent_shape) + self.shape_collision_filter_pairs.append((a, b))
4427-4460: LGTM: contact pair generation with env and group gating; stable ordering fixed.The canonical pair formation without mutating loop vars is correct. Minor doc nit: the comment says “Copy the set …” but we only alias it.
Change the comment to “Alias the set of filtered-out shape pairs …” or actually copy via
filters = set(model.shape_collision_filter_pairs)if you plan to mutate.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
newton/_src/sim/builder.py(14 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
⏰ 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 (10)
newton/_src/sim/builder.py (10)
409-409: LGTM: initialize body_shapes with static (-1) bucket.This avoids repeated get(-1, []) patterns and simplifies merges.
1014-1016: LGTM: apply xform to world-attached shape transforms on import.Using transform_mul here maintains correctness for root shapes.
1049-1054: LGTM: body_q offset via transform_mul.This is the right spot to apply the root transform to bodies.
1059-1061: LGTM: correct offset for copied collision-filter pairs (uses start_shape_idx).This fixes the prior over-shift bug.
2215-2219: LGTM: preserve and restore static (-1) shapes across collapse.Keeps the static bucket intact while rebuilding.
2224-2225: LGTM: carry over body_q/body_qd verbatim during rebuild.Matches the retained bodies without extra conversions.
2305-2306: LGTM: store parent/child joint frames directly from retained data.Keeps transforms consistent with the merged topology.
4162-4162: LGTM: finalize converts list→set for filters (aligns with Model expectations).Matches downstream consumers that rely on set semantics.
1362-1362: LGTM: canonicalize joint parent/child filter pair and append.Matches the new list-based store and avoids duplicate (a,b)/(b,a).
965-974: Audit private wp.context.runtime.core usageFindings: direct uses located at newton/_src/sim/builder.py:967, newton/_src/sim/graph_coloring.py:233,241, and tests newton/tests/test_coloring.py:142,155,170,176,190,196.
Action: replace direct wp.context.runtime.core calls with public Warp APIs or centralize them behind a single compatibility shim with runtime feature-detection and documentation.
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
newton/_src/sim/builder.py (2)
639-643: Don’t mutate Warp transforms via slicing; assign translation through .p.Index assignment on wp.transform is unreliable. Set the position explicitly while reusing the same object.
Apply:
- xform = wp.transform_identity() + xform = wp.transform() for i in range(num_copies): - xform[:3] = offsets[i] + xform.p = wp.vec3(offsets[i][0], offsets[i][1], offsets[i][2]) self.add_builder(builder, xform=xform)
964-971: Critical: from_buffer() out-param risks dangling memory; avoid private cfunc hard dependency.Creating a transform from a temporary NumPy buffer can leave
outpointing at freed memory; alsowp.context.runtime.coreis private and may change.Apply:
- transform_mul_cfunc = wp.context.runtime.core.wp_builtin_mul_transformf_transformf + transform_mul_cfunc = getattr(getattr(wp.context.runtime, "core", None), "wp_builtin_mul_transformf_transformf", None) ... - def transform_mul(a, b): - out = wp.transform.from_buffer(np.empty(7, dtype=np.float32)) - transform_mul_cfunc(a, b, ctypes.byref(out)) - return out + def transform_mul(a, b): + if transform_mul_cfunc: + out = wp.transform() # proper Warp-owned storage + transform_mul_cfunc(a, b, ctypes.byref(out)) + return out + # safe public fallback + return a * b
🧹 Nitpick comments (3)
newton/_src/sim/builder.py (3)
972-980: Normalize xform input before use.If callers pass a tuple/list instead of a Warp transform,
xform.pwill fail.Apply:
- self.particle_max_velocity = builder.particle_max_velocity - if xform is not None: - pos_offset = xform.p + self.particle_max_velocity = builder.particle_max_velocity + if xform is not None: + if not hasattr(xform, "p"): + xform = wp.transform(*xform) + pos_offset = xform.p
2405-2411: Skip generating same-body filter pairs for world (-1) to reduce pair count.Per your prior guidance, world-attached shapes are auto-excluded; explicit (-1, -1) pairs are redundant and can bloat the list for large scenes.
[retrieved_learnings used]Apply:
- if body in self.body_shapes: + if body in self.body_shapes: # 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 body != -1: + for same_body_shape in self.body_shapes[body]: + self.shape_collision_filter_pairs.append((same_body_shape, shape))
4423-4455: Edge case: ensure empty contact pair array has shape (0,2).When no pairs exist,
np.array([])yields shape (0,), which can trip Warp dtype/shape checks.Apply:
- model.shape_contact_pairs = wp.array(np.array(contact_pairs), dtype=wp.vec2i, device=model.device) + if contact_pairs: + arr = np.asarray(contact_pairs, dtype=np.int32) + else: + arr = np.empty((0, 2), dtype=np.int32) + model.shape_contact_pairs = wp.array(arr, dtype=wp.vec2i, device=model.device)
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
newton/_src/sim/builder.py(14 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 (1)
newton/_src/sim/builder.py (3)
newton/_src/sim/style3d/builder_style3d.py (1)
add_builder(110-140)newton/_src/sim/state.py (2)
particle_count(94-98)body_count(87-91)newton/_src/sim/joints.py (1)
JointType(20-44)
⏰ 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 Unit Tests on AWS EC2 (Pull Request)
- GitHub Check: Run GPU Benchmarks (Pull Request)
- GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
- GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (7)
newton/_src/sim/builder.py (7)
364-364: Builder stores list, Model converts to set: good choice.List storage reduces builder‑time overhead; set conversion happens in finalize. No changes requested.
408-409: Always-initialized static (-1) body bucket is sensible.This prevents KeyErrors and makes downstream logic simpler.
1003-1014: Root-shape transform application looks correct.Transforming only world-attached shapes when
xformis provided is the right behavior.
1045-1050: Body pose offset on add_builder is correct.Multiplying
xform * body_qfor incoming bodies preserves orientation and translation as intended.
1054-1058: Correct offset for copied collision-filter pairs.Using
start_shape_idxavoids over-shifting. Thanks for fixing this.
2211-2215: Static shapes preserved across collapse: good.Saving and restoring
self.body_shapes[-1]after clearing avoids losing world-attached shapes.
4158-4160: Model now holds a set of filter pairs: correct type for fast membership.Matches usage in
find_shape_contact_pairs.
…ics#772) Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
…ics#772) Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Description
Improve performance of
add_builder,replicate, andfind_shape_contact_pairsmethods inModelBuilder.Add unit tests for
add_buildermethod.Addresses #55.
Time Measurements
Newton Migration Guide
Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this PR.
docs/migration.rstis up-to dateBefore your PR is "Ready for review"
newton/tests/test_examples.py)pre-commit run -aSummary by CodeRabbit
Refactor
Tests
Documentation