Skip to main content

Physics export (rod_spec)

The rod_spec export writes a device's reduced constitutive properties (bending, torsion and axial stiffness, plus outer and inner radius and linear density, per section) as a small, versioned JSON file that any physics model can import. It is the supported way to drive an external simulator (a robotics gym, a navigation solver, a custom FEA or XPBD rod) with real device mechanics computed from construction, instead of stiffness values hand-tuned to a physical sample.

VirtuCath produces the file and nothing else: there is no bundled runtime, plugin or solver integration to install. The format is documented in full below so you can read it with a plain JSON parser.

Introduced in VirtuCath™ 1.2.0.

Why it exists

Most rod and beam solvers model a catheter from a single material: one Young's modulus for the whole device, with torsion tied to bending through a fixed Poisson's ratio. That cannot represent a real catheter, which is a graded construction (a stiff braided shaft tapering to a soft atraumatic tip) and, in the case of a braid, one whose torsion is deliberately decoupled from bending.

VirtuCath already computes the true per-section stiffness profile from the actual construction: braid angle, wire, liner, durometers, extruded cores. The rod_spec bridge hands those numbers to your solver directly, section by section:

  • EI: bending stiffness (per axis; see anisotropy)
  • GJ: torsional stiffness (independent of EI)
  • EA: axial stiffness
  • r_out, r_in, lin_density: geometry and mass for contact and inertia

The contract

Files declare a schema version (virtucath.rod_spec/1.0) and are SI throughout (N, m, kg, rad). The device is described as piecewise-constant sections, which is VirtuCath's native model, with an optional resampled convenience array for fixed-segment rods.

{
"schema": "virtucath.rod_spec/1.0",
"provenance": {
"virtucath_version": "1.5.1",
"config_hash": "sha256:…", // hash of the source configuration
"units": "SI"
},
"device": {
"name": "DefaultCatheter",
"total_length_m": 0.319888,
"proximal_is_s0": true, // s is measured from the proximal hub toward the tip
"transitions": "step" // sections are STEP changes, not tapers
},
"sections": [ // source of truth — piecewise constant
{
"s_start_m": 0.0,
"s_end_m": 0.20,
"label": "Proximal",
"EI_x_Nm2": 1.70e-3, // bending about local x
"EI_y_Nm2": 1.70e-3, // bending about local y (equal when isotropic)
"GJ_Nm2": 1.74e-3, // torsion (independent of EI)
"EA_N": 2831.1, // axial
"r_out_m": 0.001694, // OD / 2, for contact + rendering radii
"r_in_m": 0.0005, // ID / 2; null when the section has no bore
"lin_density_kg_m": 8.996e-3
// "principal_axis_angle_deg" appears only on a rotated anisotropic section
}
// … one entry per VirtuCath section
],
"resampled": { // OPTIONAL — present when requested
"ds_m": 0.001,
"s_m": [ … ],
"EI_x_Nm2": [ … ], "EI_y_Nm2": [ … ], "GJ_Nm2": [ … ],
"EA_N": [ … ], "r_out_m": [ … ], "lin_density_kg_m": [ … ]
}
}

Rules a consumer must respect

  • SI units. Newtons, metres, kilograms, radians. No mm, no grams.
  • s = 0 is the proximal hub, increasing toward the distal tip (proximal_is_s0: true).
  • Transitions are step changes. Do not smooth or interpolate stiffness across a section boundary unless you are deliberately modelling a physical taper: the steps are real (durometer and reinforcement changes).
  • Use lin_density_kg_m for mass per unit length; do not derive mass from r_out and an assumed density (the wall is a composite).
  • EA and EI are independent of the geometry. Because the wall is a composite, EA/EI does not equal the section's area-over-second-moment, and GJ/EI varies section to section. Feed the stiffnesses directly; do not back them out into a single modulus.

Anisotropic sections

For isotropic sections EI_x_Nm2 == EI_y_Nm2. They differ only when a section is genuinely anisotropic: most commonly a multi-lumen extruded core or an oval profile (VirtuCath 1.2.0). A solver that supports only a single bending stiffness can use EI_y_Nm2 (the primary axis); a Cosserat solver should take both. Off-axis material rotation, when present, is reported as principal_axis_angle_deg.

Steering (pullwires)

A steerable device carries an optional actuation block. It is geometry only: it describes where each pullwire sits and how a pull maps to a bending moment, so any engine can reproduce the steering from the exported EI profile. No construction is exposed.

"actuation": {
"type": "pullwire",
"convention": "moment_Nm = tension_N * radial_offset_m, applied over [proximal_s_m, distal_attachment_s_m], bending toward angle_deg",
"pullwires": [
{ "id": 0, "radial_offset_m": 0.00114, "angle_deg": 180.0,
"proximal_s_m": 0.0, "distal_attachment_s_m": 0.30 }
]
}

A wire at tension T applies a moment M = T · radial_offset_m over its span [proximal_s_m, distal_attachment_s_m], bending the device toward angle_deg in the material frame. Because the local EI is in the same file, an engine gets the resulting curvature directly (κ = M / EI). Mapping that onto an engine's native mechanism:

  • MuJoCo: a spatial tendon through sites at the wire offset, one per pull wire, driven by a position or force actuator.
  • NVIDIA Cosserat: an imposed tip rest-curvature (set_tip_bend / RodTipConfig), since that solver steers by target curvature rather than a physical tendon.

The block is omitted entirely for a non-steerable device.

Producing an export

From the application

File → Export → Export Physics Simulation Spec (JSON)... writes the file for the currently loaded device.

From the CLI API

Load a device, then call export_rod_spec. It returns the rod_spec object and, if you pass path, also writes it to disk. Pass resample_ds_m to include the fixed-step array.

{"command": "load", "config": "Catheter Files/default_catheter.json"}
{"command": "export_rod_spec", "path": "device.rod_spec.json", "resample_ds_m": 0.001}
{
"status": "success",
"rod_spec": { "schema": "virtucath.rod_spec/1.0", … },
"warnings": [], // empty = passed the round-trip validator
"written_to": "device.rod_spec.json"
}

Consuming a rod_spec

VirtuCath ships the export, not solver integrations: what you get is the JSON file, and wiring it into an engine is yours to write. The format is deliberately small so that is a short job. Reading it takes no library, just a JSON parser and a lookup of which section contains a given arc-length s, or the resampled arrays if your rod has fixed segments.

What that mapping looks like per target:

  • A custom beam / FEA / XPBD rod: assign each section's EI_x_Nm2, EI_y_Nm2, GJ_Nm2 and EA_N to the elements that fall inside [s_start_m, s_end_m], and take mass from lin_density_kg_m. Nothing else is needed.
  • MuJoCo: build the chain from the rod_spec (per-segment stiffness maps directly onto joint stiffness) and add the tendons from the actuation block.
  • NVIDIA Isaac for Healthcare (Cosserat rod): this one needs a workaround, described below, because the public API takes a single scalar material for the whole rod.

Solver support and current limitations

What a target engine can accept varies, and one common case needs a workaround today.

Per-segment properties (NVIDIA Cosserat solver)

The open NVIDIA Isaac for Healthcare Cosserat-rod solver takes a single scalar material for the whole rod: one Young's modulus, with the shear modulus tied to it through a fixed Poisson's ratio (ν = 0.3), and one bend-stiffness multiplier. There is no public API to set stiffness per segment, to make torsion independent of bending, or to set axial stiffness directly. As shipped, it cannot represent a graded catheter or a braided shaft.

Workaround: writing per-section stiffness into that solver

The solver already stores stiffness per rod edge internally; the scalar config is only broadcast into those arrays at setup. So write VirtuCath's real per-section EI/GJ straight into that per-edge array yourself, after setup, neutralising the scalar modulus. Two details make it exact:

  1. Run the solver in mm-kg-s. Its bend/twist compliance is regularised with a fixed epsilon; catheter-scale rigidities in SI fall below it and get silently clamped. Working in millimetres scales EI up by 1e9 and clears the floor.
  2. Map to the discrete constraint. The solver's bend/twist constraint is C ≈ κ·l/2, so the per-edge stiffness that reproduces a target EI is 4·EI/l² (l = segment length). Same factor for GJ.

We validated this against Euler-Bernoulli (δ = P·L³/3EI) to within a few percent. A feature request has been filed with NVIDIA to accept per-segment material natively; if accepted, the workaround collapses to a direct assignment.

Axial stiffness on the NVIDIA solver

That solver's XPBD path treats stretch and shear as near-rigid regardless of configuration, so EA is not exercised there: the rod is effectively inextensible. This is usually fine for catheters (they are ~inextensible), but if your application depends on axial compliance, the NVIDIA Cosserat path is not the right target today. EA is present in the rod_spec and is honoured by MuJoCo and by custom solvers that model stretch.

MuJoCo and most custom beam/FEA solvers accept per-segment stiffness natively, so they consume the rod_spec directly with no workaround.

Pullwire steering

The actuation block is a v1 abstraction: it exports pullwire geometry (offset, angle, attachment span) and the moment convention, not a calibrated force→deflection curve. How faithfully it reproduces steering depends on the engine:

  • MuJoCo models the pull wire directly as a spatial tendon, so steering is physical and multi-wire control is exact; the actuation block carries the sites and offsets needed to build it.
  • NVIDIA Cosserat has no tendon. It steers by imposing a rest curvature on the tip, so a pullwire has to be approximated as a target tip bend. That approximation has limits: it applies a curvature at the distal tip rather than a moment distributed along the wire's full attachment span, and independent simultaneous control of multiple wires is not represented. It reproduces single-wire tip deflection well; complex multi-wire or proximal-shaft steering is out of scope on that solver.
note

The actuation block does not yet carry a calibrated actuation gain (pull force → tip deflection). Engines derive curvature from the exported EI and the wire offset (κ = T·offset / EI). A calibrated reference point may be added in a later schema revision.

What is not exported

The bridge emits only the reduced constitutive outputs. It never exports the composite internals that produced them: braid engagement model, flowdown stackup, ply moduli, layer-by-layer construction. That keeps the format engine-neutral and is the intellectual-property boundary of the export (EULA §2.8). Bulk automated exports are rate-limited.