Evaluating the Newton physics engine for a robotics datacenter-cabling task — manipulating cables and inserting/extracting RJ45 connectors — and factoring the hard-won knowledge into a small, tested package.
MuJoCo and Genesis proved inadequate for the fine cable/connector detail this task needs.
Conclusion so far: Newton handles the hard contact and deformable-cable physics out of the
box, so building a custom simulator is not justified. The reusable building blocks live in
newton_cabling/; the record_*.py scripts are runnable demos.
Status / honesty note. The arm demo currently drives the plug with a force-spring coupling, not a real finger grasp — it's a faithful insertion/extraction demo, not real manipulation. A true friction grasp (fingers physically holding the plug) is in progress; see Friction grasp. Where a demo is scripted rather than contact-driven (e.g. the latch press), it says so.
We evaluated both. The task's defining difficulty is fine deformable-cable behaviour and sub-millimetre contact at the plug/socket interface — exactly where MuJoCo and Genesis fell short, and the axis on which the two NVIDIA-stack options most differ.
What pushed us to Newton:
- Cable fidelity is the whole task. Newton's VBD solver simulates 1D deformables (cables) with self-collision and proper bend/twist transport as a first-class, GPU-batched feature, two-way coupled to rigid bodies. Newton 1.0 ships this as a headline capability, and NVIDIA's own contact-rich manipulation demos (hose-connector insertion, GPU rack assembly) target this problem class. We confirmed it: latch click/hold/press-extract and cable bend/twist all work.
- Code-first, GUI-optional. Newton is a Python library on Warp;
the entire workflow here is a
.pyfile run headless, edit-run-inspect in minutes. Isaac Sim is GUI/Omniverse-centric with a large install and USD-composition overhead. - Cost and iteration. Headless Newton runs on one modest GPU; recordings are small
.rrdfiles viewed locally in rerun. - Open and portable. Apache-2.0, Linux Foundation governance, and it plugs into Isaac Lab / Isaac Sim as a swappable physics backend — choosing Newton now doesn't lock us out later.
What we gave up (the honest trade): Isaac Sim's PhysX articulations are a decade more
battle-tested. Newton 1.0's rigid-robot coupling seam is rough, and we paid for it (~15 debug
iterations on undocumented VBD constraints — now encoded as asserts in
newton_cabling/sim/safe_vbd.py). Isaac Sim also has
first-class robot/gripper assets and a built-in surface gripper.
Net: for a cable-manipulation task where deformable fidelity is make-or-break and fast code-driven iteration matters, Newton was the right call — with eyes open about its 1.0-era rigid-coupling roughness. The likely endgame is the hybrid that's emerging anyway: Newton as the physics backend inside Isaac Lab for batched policy training. (Newton 1.0 announcement, contact-rich manipulation.)
Pure-logic modules import no GPU code and are linted, type-checked, and unit-tested anywhere.
The GPU-backed sim/ modules live behind the sim extra and are verified by execution.
| Module | What it does |
|---|---|
timeline.py |
Declarative CableTimeline: one ordered list of Phase (plug offset + GraspState/LatchState) sampled at any time. loop=True validates start == end so a recording can't jitter on wrap. Replaces three hand-synced functions. |
report.py |
evaluate_cycle() — single source of truth for pass/fail, returning an outcome variant (CycleSucceeded / InsertionFailed / LatchSlipped / ExtractionIncomplete) the tuning harness pattern-matches on, with JSON round-trip. |
connector.py |
ConnectorSpec / LatchSpec / ContactSpec domain types with validation; rj45_connector() is the proven instance. Swapping a connector is a new spec value. |
cable.py |
route_cable_from_boot() — cable centreline routing with the modelling learnings baked in (exit the boot, ~10mm segments, gentle drape near rest, short kinematic prefix). |
sim/safe_vbd.py |
The footgun-free VBD setup: new_vbd_builder / add_actuated_urdf / finalize_for_vbd bake in collapse_fixed_joints, soft actuated joints, eval_fk-into-model, and the color/finalize order, with build-time asserts. |
sim/scene.py |
load_connector_meshes + add_connector_rig build the socket/plug/latch rig (world-d6 plug, latch revolute, SDF contact) from a ConnectorSpec; patch_panel_port_offsets for a real keystone panel. |
sim/grasp.py |
GraspSpring — force-coupling (anti-gravity + position spring) driven by a 0..1 weight. The interim grasp; being replaced by a real friction grasp. |
sim/recording.py |
open_rrd_recorder (keeps the .rrd file sink) and auto_blueprint (warns if a ground plane is present — see learnings). |
Each record_*.py builds a scene, runs headless, and writes a rerun .rrd (plus a focused
.rbl blueprint). Recordings are not committed — they're large and fully regenerable.
To produce and view one (needs a CUDA GPU and the sim extra — see Running it):
uv run --extra sim python record_patch_panel.py # writes patch_panel.rrd + patch_panel.rbl
uvx --from rerun-sdk rerun patch_panel.rrd patch_panel.rbl(Substitute any record_*.py; each writes a matching .rrd/.rbl pair.)
record_rj45_insert.py/record_rj45_unplug.py— the connector physics, driven by the proven spring rig: insertion with a latch that deflects over the socket lip and clicks; a pull-without-press control (latch holds), then press-and-extract.record_cable_twist.py— three cables of increasing bend stiffness with a spinning driven end; twist propagation through 90° bends.record_panda_cycle.py— a Franka FR3 running the full insert → release → retreat → return → press → extract loop (force-spring grasp), as a seamless 19s loop.record_patch_panel.py— a row of RJ45 ports at real keystone pitch (18mm); cables start retracted and plug in side by side.record_param_sweep.py— N connector worlds in one model, one GPU pass, scored per-world byevaluate_cycle; sweeping press depth discovers the extraction threshold.record_grasp_test.py— milestone toward the real friction grasp (see below).
- FIXED joints freeze VBD chains. A URDF with interleaved fixed joints does not move at all
under SolverVBD. Import with
collapse_fixed_joints=True. - Actuated joints must be soft (
model.vbd.joint_is_hard[j] = 0, post-finalize). - Call
eval_fk(model, model.joint_q, model.joint_qd, model)(into the model) before constructing SolverVBD — VBD measures joint angles against the model's rest pose. - URDF
limit_kd≈10is joint-freezing under VBD's Rayleigh convention; uselimit_kd≈1e-4. - d6 angular drives can hold the child rotated ~90°; a d6 with no angular axes locks rotation.
- Seed IK from a non-singular home configuration, not the URDF zero pose.
All of the above are encoded as defaults/asserts in newton_cabling/sim/safe_vbd.py.
Recordings kept opening to "just the floor." The viewer instances identical shapes, so the
ground plane's rendered index can't be matched to a builder or model index to exclude it from the
blueprint. The fix that works: don't add a ground plane in anything that records — nothing here
needs one (arms are position-controlled, plugs ride springs, cables are pinned at both ends).
auto_blueprint() warns if a plane is present.
Match Newton's RJ45 example: exit the back of the plug (the boot), ~10mm segments, a short kinematic prefix at the boot (threading it inside the plug body shoves the plug), and start near the gravity rest shape with extra bend damping so a hanging cable doesn't swing.
The arm grasp is currently a force-spring, which has known limits: it can't take cable-vs-arm
contact (the cable pushes the position-controlled gripper off the plug). The example we adapted
the arm from (example_robot_panda_hydro) grasps with real finger friction; Newton's
example_cloth_franka shows the workable pattern — a kinematically driven arm (so contact
can't shake it) co-simulated with a VBD deformable, gripping via finger contact.
record_grasp_test.py validates the first milestone: kinematic fingers holding a free VBD rigid
body by contact friction (no spring). Remaining milestones: conclusive grip + lift, insertion
while gripping (the grip must resist the insertion force — the genuinely hard part of robotic
connector insertion), a fingertip-driven latch press, and the cable draping on the now-stable arm.
Requires uv. The pure-logic gate runs anywhere:
uv run ruff format . && uv run ruff check .
uv run ty check newton_cabling/__init__.py newton_cabling/timeline.py \
newton_cabling/report.py newton_cabling/connector.py newton_cabling/cable.py tests
uv run pytestThe record_*.py demos need a CUDA GPU and the sim extra, which installs Newton from git
(the demos use main-branch APIs the released PyPI build lags on):
uv run --extra sim python record_panda_cycle.pyThe demos import Newton/Warp and are excluded from the static gate above.
Licensed under Apache-2.0. This project adapts patterns and small helpers from Newton (Apache-2.0) — see NOTICE. Robot and connector assets (Franka URDF, RJ45 mesh) are downloaded at runtime from Newton's asset registry and are not redistributed here.