Couple the gripper fingers and repair the USD regeneration pipeline - #21
Open
johnnynunez wants to merge 1 commit into
Open
Couple the gripper fingers and repair the USD regeneration pipeline#21johnnynunez wants to merge 1 commit into
johnnynunez wants to merge 1 commit into
Conversation
johnnynunez
force-pushed
the
fix/gripper-coupling
branch
5 times, most recently
from
August 2, 2026 23:08
0a376af to
7de1aee
Compare
The gripper jaws drift on their own whenever the arm moves. This is an asset defect, not a solver one: it reproduces identically in PhysX, Newton and MuJoCo because all three read the same authored gains. Two independent causes, both fixed here. 1. The fingers were 9,248x softer than the actuator they model. Every arm joint sits at zeta 18-49 (overdamped) while the fingers were at K = 100 N/m, zeta = 0.685, f_n = 5.45 Hz -- underdamped, so any teleop motion excites them. They sagged 7.4 mm under gravity alone (14.75% of the 50 mm stroke) and peaked 2.3 mm during an arm sweep. The new gains come from the hardware, not from tuning. Motor 7 is a RobStride whose limit_torque reads 14 Nm live from the firmware over CAN; it drives both racks through one pinion of r = 7.353 mm/rad (derived from the calibrated 0 -> -6.8 rad travel over the 50 mm stroke). Reflecting the motor's 50 Nm/rad through that transmission gives K = 50/r^2 = 925 kN/m and F = 14/r = 1904 N at the finger. 925 kN/m is far above what the solver can integrate at 1/120 s, so the drive is capped at the stiffest gain that stays smooth, with damping set for zeta = 1.0. Same reasoning for the limits: effort 500 -> 1904 N, and maxVelocity 10 -> 0.243 m/s (10 m/s was ~41x faster than the motor's 33 rad/s can drive the fingers). 2. The fingers were modelled as two independent DOFs. The real gripper is one motor, one pinion and two opposed racks (hardware BOM ships 02_Rack.step x2), so finger travel is rigidly 1:1. The asset gave the solver a degree of freedom the robot does not have, which is why the jaws could separate at all. USD now authors PhysxMimicJointAPI on joint_right referencing joint_left, and the MJCF authors the equivalent <equality joint>. Note PhysX constrains q_follower + gearing * q_leader + offset = 0, so gearing = -1 is what makes the two coordinates EQUAL -- the finger axes are already antiparallel in the parent frame. Using +1 yields q_right = -q_left, which looks perfectly symmetric while collapsing to the wrong opening; MuJoCo hides this and only running both engines catches it. The MJCF equality uses the standard positive solref form (0.004 1, timeconst = 2*dt). Direct constraint gains (negative solref) couple ~11x tighter in MuJoCo but make Newton's SolverMuJoCo diverge to NaN, and this model is published for both engines. The URDF's asymmetric finger limits are also corrected: joint_left allowed 0-0.05 m and joint_right 0-0.0715 m. Both fingers use the same mesh, their travel axes are exactly antiparallel (dot product -1.000000) and their origins sit 0.147 mm apart, so the stroke is symmetric and 0.0715 was an authoring error -- it also made a 1:1 coupling impossible to express. Pipeline repairs this depends on -------------------------------- prep_asset.py could not run at all on main. Seeed-Projects#18 renamed the payload layers (base.usda -> RS-rebot-dev-arm_base.usd, physics.usda -> RS-rebot-dev-arm_physics.usd) without updating the script, so it died in its first function with AttributeError: 'NoneType' object has no attribute 'GetPrimAtPath'. The five stale paths and the ./instances.usda re-anchor are fixed, so the USD is regenerable again. Physics=none was not neutral. The physics payload was attached to the root prim as well as inside the variant, so it composed in every selection and the "no physics" variant still carried 10 colliders and 10 rigid bodies -- the exact condition prep_asset.py already asserted against, and a SimReady Robot-Body Neutral requirement. Removing the duplicate root-prim payload leaves the variant as the only opinion. Physics=none now composes zero colliders and zero rigid bodies, while physics and mujoco keep their 10 (7 convexHull, 3 convexDecomposition). The gain assertions compared floats with ==, which only held while every authored gain was integral; USD stores them as float32, so 41.28 reads back as 41.279998779296875. They now compare with a tolerance. verify() additionally asserts the mimic survives regeneration, so the coupling cannot be silently lost. Validation ---------- Isaac Sim 6.1 (PhysX), headless, articulation loaded from the asset: PhysxMimicJointAPI:Z composed, reference=joint_left, gearing=-1.0 static : finger dev 0.00006 mm asym 0.00011 mm slewing : finger dev 0.00629 mm asym 0.01204 mm MuJoCo, same experiment: dev 0.017 mm, asym 0.034 mm, neq=1. validate_physics_fidelity.py: failures: [], 10 links and 8 joints checked across the physics and mujoco variants, with the compiled-MJCF cross-check running (mujoco 3.10.0) rather than skipped. prep_asset.py: all three variants verify and re-running is a byte-level no-op. build_mjcf.py regenerates rebot_devarm.xml byte-identical to the committed file. Improvement, measured within each engine on the same experiment (upstream/main's asset vs this one), arm sweeping joint1 +/-0.4 rad and joint2 +/-0.2 rad: Isaac Sim 6.1 / PhysX MuJoCo 3.10 slew before after slew before after 0.25Hz 0.283mm 0.0064mm 0.25Hz 0.886mm 0.017mm 0.50Hz 0.580mm 0.0128mm 0.50Hz 1.900mm 0.033mm 1.00Hz 1.338mm 0.0305mm 1.00Hz 1.792mm 0.066mm 2.00Hz 3.428mm 0.0687mm 2.00Hz 2.159mm 0.147mm 4.00Hz 5.760mm 0.1353mm 8.00Hz 6.797mm 0.2526mm Roughly 44x across the range in PhysX and 15-57x in MuJoCo. The fingers are not motionless -- deviation grows with slew rate -- but the improvement holds at every speed rather than only at gentle ones, and the degradation is gradual with no instability threshold. At 8 Hz, well beyond any teleop rate, the residual 0.25 mm is 0.5% of the 50 mm stroke; the unfixed asset exceeded 1% of stroke by 0.5 Hz. Static gravity sag goes from 7.375 mm to ~0. For reference the physical gripper, unpowered, reads 268.5 urad of encoder noise (0.00197 mm at the finger), so the fixed asset sits 3x that floor at 0.25 Hz and 128x at 8 Hz. Why the jaws move at all is documented in usd/RS-rebot-dev-arm/docs/GRIPPER_RESIDUAL_MOTION.md, since a single motor holding them is a reasonable reason to expect zero. In short: it is not drive compliance (16x stiffer changes 0.1516 -> 0.1459 mm, and the actuator uses 0.88 N of 1904 N), and it is not coupling softness (tightening solref past the shipped 0.004 changes nothing). With the arm held still the deviation is 0.00000 mm; it appears only under wrist acceleration, and gets worse when the ARM tracks better (0.147 mm at shipped gains, 0.774 mm at 16x) because a stiffer arm accelerates the wrist harder. The jaws have mass, so dx = m*a/K -- ordinary rigid-body mechanics. The drive must ship at 5 kN/m rather than the motor's reflected 925 kN/m (50 N*m/rad over a 7.353 mm/rad pinion) because the real value diverges at dt = 1/120 s. At the real stiffness the same accelerations give 0.0001-0.0010 mm, below the 0.00197 mm encoder noise floor -- so the physical gripper does not move measurably, and the simulated residual is a timestep budget, not an asset parameter. Deviation falls with dt (1/240 s, 1/480 s, 1/960 s measured), which is the knob for anyone needing sub-0.01 mm fidelity. The README gripper-limit tables are corrected alongside: they still documented joint_right's 0.0715 m upper limit, which this change replaces with the measured symmetric 0.05 m on both fingers. Follow-up measurements on the physical arm (RobStride motor 7 over can0, MIT mode, after writing and verifying the official MotorBridge Studio parameter template) refine that note further, and refute two plausible fixes: breakaway torque 0.100 N*m -> 13.6 N at the finger closed-loop K 66,759 N/m at kp=3 N*m/rad (median of 12 plateaus, 64,800-69,900, 4% spread; theory kp/r^2 = 55,487, so 1.20x) backlash +/-0.37 mm, both directions free from a closed stop Real friction exceeds the inertial load on the jaws by 14-130x, so the physical gripper cannot be shifted by wrist acceleration at all. Writing that friction into the asset was tested and makes the simulation WORSE (2 Hz: 0.1466 -> 0.1832 mm), because in sim the joint is driven by the coupling constraint, and friction cannot oppose a constraint -- it only makes the solver push harder. Measuring the internal forces shows the constraint applies more force than the actuator (1.207 N vs 0.877 N at 2 Hz) and accounts for 43% of the residual (disabling it: 0.1466 -> 0.0840 mm). It is already saturated: solref from 0.004 down to 0.0002 gives an identical 0.1466 mm. Loosening solimp lowers the number only by weakening the coupling, which is not a fix. That leaves the integration rate, which converges cleanly as 1/dt from the asset's native 500 Hz (0.1466 mm at 2 Hz) to 4000 Hz (0.0195 mm), with the 1:1 coupling exact (|left-right| = 0.00000 mm) at every rate. Conclusion: no asset parameter changes. The mechanism model is correct, and the residual is a documented solver floor rather than a defect. Only the note, its evidence and the measurement scripts are added. Three properties of third_party/reBotArm_control_py are documented alongside, since they cost several failed runs and silently corrupt measurements: * send_mit() is ignored unless ensure_mode(Mode.MIT) is called first. No error is raised; measured torque simply never tracks the command (a 0.05 -> 2.0 N*m ramp produced a flat -0.13 N*m). run_mode is 0x7005, read as i8. * send_pos_vel(pos, vlim) does not carry vlim in the command frame -- it writes parameter 0x7017 (limit_spd). mode_pos_vel() likewise writes 0x7017, 0x701E, 0x701F and 0x7020 from the library YAML, overwriting the vendor's calibrated per-joint template without warning. * position-mode stall detection is not viable at the 40 Hz feedback rate: the drive goes from free to hard stop within one sample, so a 1.0 N*m threshold was never observed (consecutive samples read ~0 then -3.76 N*m). Five scripts reproduce every table: measure_gripper_stiffness.py and measure_gripper_breakaway.py need the arm (both take --dry-run, which exercises the full path against live telemetry without enabling the motor); sweep_gripper_friction.py, sweep_coupling_constraint.py and sweep_timestep_convergence.py are simulation-only. All resolve their paths relative to the repo and were verified from a clean working directory. SimReady conformance -------------------- Validated with the official simready-validate from NVIDIA/simready-foundation. Robot-Body-Neutral now PASSES; it failed on upstream/main before this change. Making Physics=none genuinely neutral had to be reconciled with SimReady ISA.001, which requires the _physics.usd payload on the default prim in the ROOT layer (isaac_sim/composition/validation.py reads prim_spec.payloadList directly, so a payload authored inside a variant does not count). A root-prim payload composes under every selection, so the two requirements pull opposite ways. The variant now subtracts what the payload adds: inside Physics=none the /Physics scope and the collision instances are deactivated and the body-level physics API schemas are stripped. ISA.001 keeps its payload and the neutral selection composes to plain geometry. Three upstream problems were found while doing this and are reported in NVIDIA/simready-foundation#18: - DJ.001 crashes on any joint carrying PhysxMimicJointAPI: validation.py lines 258 and 535 call .Get() on a float that lines 256-257 already resolved. Only reachable with a coupled mechanism, which is why it had gone unnoticed. - DJ.007 only accepts rotational mimic axes (rotX/rotY/rotZ), so no linear coupled gripper can satisfy it, even though UsdPhysics defines transX/Y/Z and PhysX's own Newton migration checker documents prismatic mimic support. - DJ.004 wants zero drive gains on a mimic follower. That is correct for a revolute pair, but measurements in Isaac Sim show PhysX does not enforce PhysxMimicJointAPI on a PRISMATIC joint at all -- removing the API entirely gives numbers identical to four decimal places, and with zero gains the fingers drift 51.5 mm, far worse than the 2.3 mm bug this fixes. The follower therefore keeps its drive: Newton and MuJoCo honour the constraint, PhysX is carried by the drive. The PhysicsScene is no longer authored by the asset. RC.005 rejects physics attributes authored in the interface layer, and SimReady's own reference robot (sample_content/.../Robotiq/2F-85) ships without one. Three placements were measured and none satisfies every constraint at once: interface layer /PhysicsScene composes, fails RC.005 physics layer /PhysicsScene never composes (a payload arc maps only the defaultPrim subtree) physics layer <robot>/PhysicsScene composes and passes RC.005, but leaks into any stage referencing the robot Gravity belongs to the stage that loads the robot: Isaac Sim, Newton and MuJoCo all supply it. verify() and validate_physics_fidelity.py now assert the asset does NOT author a scene, so a future re-export cannot reintroduce a hardcoded gravity that fights the host. Three environment problems made earlier SimReady results unreliable, and are worth knowing before re-running the validator: - The stock validate venv installs usd-exchange, whose pxr does not ship PhysxSchema. Every rule touching it reports "PhysxSchema is not available in this environment" as a FAILURE rather than skipping -- 161 such messages here. DJ.002 and DJ.003 were false negatives. - RC.007 and DJ.008/009/010 import usd.schema.isaac, which ships with the Isaac Sim build rather than the validator. Without it they abort with "No module named 'usd.schema.isaac'" before inspecting the asset. All four pass once it is on the path. - RC.001 and RC.004 need omni.client; without it they raise "module 'omni' has no attribute 'client'". Both still report that here, so their status is unknown rather than failing: the asset root holds only the interface layer (what RC.001 wants) and .thumbs/256x256/RS-rebot-dev-arm.usda.png exists (what RC.004 wants).
johnnynunez
force-pushed
the
fix/gripper-coupling
branch
from
August 2, 2026 23:28
7de1aee to
6a18bea
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
The gripper jaws move on their own whenever the arm moves — they swing as if nothing were holding them. It reproduces identically in PhysX, Newton and MuJoCo, which is the tell: all three read the same authored gains, so this is an asset defect rather than a solver one.
Two independent causes, both fixed here.
1. The fingers were 9,248x softer than the actuator they model
Every arm joint sits at ζ 18–49 (overdamped). The fingers were at K = 100 N/m, ζ = 0.685, f_n = 5.45 Hz — underdamped, so any teleop motion excites them like a pendulum hanging off the wrist.
The new gains are derived from the hardware, not tuned by hand. Motor 7 is a RobStride whose
limit_torquereads 14 Nm live from the firmware over CAN; it drives both racks through one pinion of r = 7.353 mm/rad (from the calibrated 0 → −6.8 rad travel over the 50 mm stroke). Reflecting the motor's 50 Nm/rad through that transmission gives K = 50/r² = 925 kN/m and F = 14/r = 1904 N at the finger. 925 kN/m is far above what the solver can integrate at 1/120 s, so the drive is capped at the stiffest gain that stays smooth, with damping set for ζ = 1.0.Same reasoning for the limits: effort 500 → 1904 N, maxVelocity 10 → 0.243 m/s (10 m/s was ~41x faster than the motor's 33 rad/s can actually drive the fingers).
2. The fingers were modelled as two independent DOFs
The real gripper is one motor, one pinion, two opposed racks (the hardware BOM ships
02_Rack.step×2), so finger travel is rigidly 1:1. The asset handed the solver a degree of freedom the robot does not have — which is why the jaws could separate at all.USD now authors
PhysxMimicJointAPIonjoint_rightreferencingjoint_left; the MJCF authors the equivalent<equality joint>.Two subtleties worth recording:
q_follower + gearing·q_leader + offset = 0, sogearing = -1is what makes the two coordinates equal — the finger axes are already antiparallel in the parent frame. Using+1yieldsq_right = -q_left, which looks perfectly symmetric while collapsing to the wrong opening. MuJoCo hides this; only running both engines catches it.solrefform (0.004 1, timeconst = 2·dt). Direct constraint gains (negative solref) couple ~11x tighter in MuJoCo but make Newton'sSolverMuJoCodiverge to NaN, and this model is published for both engines.3. Asymmetric finger limits in the URDF
joint_leftallowed 0–0.05 m,joint_right0–0.0715 m. Both fingers use the same mesh, their travel axes are exactly antiparallel (dot product −1.000000) and their origins sit 0.147 mm apart, so the stroke is symmetric —0.0715was an authoring error, and it also made a 1:1 coupling impossible to express.Pipeline repairs this depends on
prep_asset.pycould not run at all onmain. #18 renamed the payload layers (base.usda→RS-rebot-dev-arm_base.usd,physics.usda→RS-rebot-dev-arm_physics.usd) without updating the script, so it died in its first function:The five stale paths and the
./instances.usdare-anchor are fixed, so the USD is regenerable again.Physics=nonewas not neutral. The physics payload was attached to the root prim as well as inside the variant, so it composed in every selection and the "no physics" variant still carried 10 colliders and 10 rigid bodies — the exact conditionprep_asset.pyalready asserted against, and a SimReady Robot-Body Neutral requirement. Removing the duplicate root-prim payload leaves the variant as the only opinion:The gain assertions compared floats with
==, which only held while every authored gain was integral; USD stores them as float32, so 41.28 reads back as 41.279998779296875. They now compare with a tolerance.verify()additionally asserts the mimic survives regeneration, so the coupling cannot be silently lost by a future re-export.Validation
Isaac Sim 6.1 (PhysX), headless, articulation loaded from the asset:
The mimic composes through the payload and variant, not merely exists in the physics layer.
MuJoCo, same experiment:
neq=1, dev 0.017 mm, asym 0.034 mm.validate_physics_fidelity.py:failures: [], 10 links and 8 joints checked across thephysicsandmujocovariants, with the compiled-MJCF cross-check running (mujoco 3.10.0) rather than skipped.prep_asset.py: all three variants verify, and re-running is a byte-level no-op.build_mjcf.py: regeneratesrebot_devarm.xmlbyte-identical to the committed file, so the generator stays the source of truth.Net effect
Measured within each engine —
upstream/main's asset and this one through the identical experiment, arm sweeping joint1 ±0.4 rad and joint2 ±0.2 rad:The fingers are not motionless — deviation grows with slew rate — but the improvement holds at every speed rather than only at gentle ones, and the degradation is gradual with no instability threshold. At 8 Hz, well beyond any teleop rate, the residual 0.25 mm is 0.5% of the 50 mm stroke; the unfixed asset already exceeded 1% of stroke at 0.5 Hz. Static gravity sag goes from 7.375 mm to ~0.
For reference, the physical gripper unpowered reads 268.5 µrad of encoder noise (0.00197 mm at the finger), so the fixed asset sits 3× that floor at 0.25 Hz and 128× at 8 Hz.
Why the jaws move at all
A single motor holds them, so zero motion is a reasonable expectation. Documented in
usd/RS-rebot-dev-arm/docs/GRIPPER_RESIDUAL_MOTION.md, with the raw data underevidence/.It is not drive compliance — raising the gripper stiffness 16× barely moves the number, and the actuator uses 0.88 N of the 1904 N available:
It is not coupling softness either — tightening
solrefpast the shipped0.004 1changes nothing (0.1466 mm at0.004,0.002and0.001).With the arm held still deviation is 0.00000 mm. It appears only under wrist acceleration — and gets worse when the arm tracks better:
A stiffer arm follows the sinusoid more closely, so the wrist accelerates harder. The jaws have mass (75.2 g each), and a position drive produces force only from a position error:
dx = m·a/K. Ordinary rigid-body mechanics, not a modelling defect.What the real motor would do
The drive must ship at 5 kN/m rather than the motor's reflected 925 kN/m (50 N·m/rad through a 7.353 mm/rad pinion) because the real value diverges at dt = 1/120 s. At the real stiffness, the same accelerations give:
All below the 0.00197 mm encoder noise floor — the physical gripper does not move measurably. The simulated residual is a timestep budget, not an asset parameter: deviation falls with dt (measured at 1/240, 1/480 and 1/960 s), which is the knob for anyone needing sub-0.01 mm fidelity.
Measured on the physical arm
RobStride motor 7 over
can0in MIT mode, after writing and verifying the official MotorBridge Studio parameter template. Full write-up inGRIPPER_RESIDUAL_MOTION.md, raw data underevidence/.kp = 3N·m/radThe stiffness is the median of 12 plateaus spanning 64 800–69 900 N/m (4 % spread) including the reversed-sign return sweep; theory predicts
kp/r² = 55 487 N/m, so the measurement is 1.20× that.Two plausible fixes, both refuted by measurement
Writing the measured friction into the asset makes it worse. Real friction exceeds the inertial load on the jaws by 14–130×, so the physical gripper genuinely cannot be shifted by wrist acceleration. But in simulation:
frictionlossThe joint is driven by the coupling constraint, and friction cannot oppose a constraint — it only forces the solver to push harder.
Tightening the constraint does nothing. It is already saturated:
solrefMeasuring internal forces shows the constraint applies more force than the actuator (1.207 N vs 0.877 N at 2 Hz) and accounts for 43 % of the residual — disabling it drops 0.1466 → 0.0840 mm. That also corrects an earlier claim in this PR: inertial load via
dx = m·a/Kis real but secondary; the constraint dominates.The only real lever is the integration rate
Clean
1/dtconvergence with the 1:1 coupling exact at every rate.SDK behaviour worth knowing before reproducing this
Three properties of
third_party/reBotArm_control_pycost several failed runs and silently corrupt measurements:send_mit()is ignored unlessensure_mode(Mode.MIT)is called first. No error is raised — the frames are accepted, telemetry keeps flowing, and measured torque simply never tracks the command. A ramp from 0.05 to 2.0 N·m produced a flat −0.13 N·m at every step.run_modeis0x7005, read as i8 (robstride_get_param_f32raises "parameter type mismatch"):0= MIT,1= POS_VEL.send_pos_vel(pos, vlim)persistsvliminto the motor. It is not carried in the command frame; it writes parameter0x7017(limit_spd).mode_pos_vel()likewise writes0x7017,0x701E,0x701Fand0x7020from the library YAML, overwriting the vendor's calibrated per-joint template without warning. Repair via MotorBridge Studio (Read → Apply Default Template → Write), noting the template is per-joint, not uniform.Reproducing every table
measure_gripper_stiffness.pymeasure_gripper_breakaway.pysweep_gripper_friction.pyfrictionlosssweep_coupling_constraint.pysolref/solimpsweep_timestep_convergence.pyBoth hardware scripts take
--dry-run, which exercises the full path against live telemetry without enabling the motor. All resolve paths relative to the repo and were verified from a clean working directory.No asset parameters were changed as a result. The mechanism model is correct — fingers coupled exactly, drive not the limit, hardware friction mechanically irrelevant to what the solver is doing. Only the note and its evidence are added.
README correction
The gripper-limit tables in
README.md/README_EN.md/README_ES.mdstill documentedjoint_right's 0.0715 m upper limit. They now state the measured symmetric 0.05 m on both fingers, and that one motor drives both through a single pinion.SimReady conformance
Validated with the official
simready-validatefrom NVIDIA/simready-foundation.Robot-Body-Neutralnow PASSES — it failed onupstream/mainbefore this change.Making
Physics=nonegenuinely neutral had to be reconciled with ISA.001, which requires the_physics.usdpayload on the default prim in the root layer (isaac_sim/composition/validation.pyreadsprim_spec.payloadListdirectly, so a payload authored inside a variant does not count). A root-prim payload composes under every selection, so the two requirements pull opposite ways.The variant now subtracts what the payload adds: inside
Physics=nonethe/Physicsscope and the collision instances are deactivated, and the body-level physics API schemas are stripped. ISA.001 keeps its payload, and the neutral selection composes to plain geometry:This is authored by
prep_asset.py, not by hand: deleting the variant and re-running regenerates it, and re-running again is byte-identical.Three upstream problems found on the way
Reported in NVIDIA/simready-foundation#18:
PhysxMimicJointAPI.validation.pylines 258 and 535 call.Get()on a float that lines 256–257 already resolved. Only reachable with a coupled mechanism, which is why it had gone unnoticed. With a one-line fix applied locally,Robot-Body-Neutralgoes from FAILED to PASSED.rotX/rotY/rotZ), so no linear coupled gripper can satisfy it — even thoughUsdPhysicsdefinestransX/Y/Zand PhysX's own Newton migration checker documents prismatic mimic support.PhysxMimicJointAPIon a prismatic joint at all:Identical to four decimal places with and without the constraint. Complying with DJ.004 would leave the gripper worse than the original bug (51.5 mm vs 2.3 mm), so the follower keeps its drive: Newton and MuJoCo honour the constraint, PhysX is carried by the drive. The same probe on a revolute pair does show the constraint acting.
The PhysicsScene is no longer authored by the asset
RC.005 rejects physics attributes authored in the interface layer, and SimReady's own reference robot (
sample_content/.../Robotiq/2F-85/simready_isaac_usd) ships without one. Three placements were measured; none satisfies every constraint at once:/PhysicsScene/PhysicsScene<robot>/PhysicsSceneA payload arc maps only the
defaultPrimsubtree, so a scene at the physics-layer root never composes; placed under the robot prim it composes but then travels into any stage that references the robot, whichverify()forbids.Gravity belongs to the stage that loads the robot — Isaac Sim, Newton and MuJoCo all supply it.
verify()andvalidate_physics_fidelity.pynow assert the asset does not author a scene, so a future re-export cannot reintroduce a hardcoded gravity that fights the host.Three environment problems that made earlier SimReady results unreliable
Worth knowing before re-running the validator — none of them are asset defects:
usd-exchange, whosepxrdoes not shipPhysxSchema. Every rule touching it reports"PhysxSchema is not available in this environment"as a FAILURE rather than skipping — 161 such messages here. DJ.002 and DJ.003 were false negatives.usd.schema.isaac, which ships with the Isaac Sim build rather than the validator. Without it they abort withNo module named 'usd.schema.isaac'before inspecting the asset. All four pass once it is on the path.omni.client; without it they raisemodule 'omni' has no attribute 'client'. Both still report that here, so their status is unknown rather than failing: the asset root holds only the interface layer (what RC.001 wants) and.thumbs/256x256/RS-rebot-dev-arm.usda.pngexists (what RC.004 wants).Any prior "all profiles PASS" claim for this asset should be re-measured on that basis.
Where the profiles stand
main)omni.clientISA.001,RC.005,RC.007,BA.002,DJ.001,DJ.002,DJ.003,DJ.008,DJ.009andDJ.010now pass — every one of them failed onupstream/main.Supersedes #13, which was branched before #14, #17 and #18 landed and had grown conflicts in 9 files.