Growing arm (GrowingCR + boundary control)
Introduction
The growing arm setup pairs a storage-backed Cosserat rod (GrowingCR) with
boundary forcing that acts like a turret at the base of the active segment
and discrete growth when the user adds or removes elements. The same idea
backs dual-arm demos in the Virtual Field runtime: fixed bases in the world,
tracked controller orientation, and buttons to extend or retract the simulated
length.
The figures below are taken from that workflow: how the scene is arranged, how targeting maps to the base, and a short loop of growth in VR.



This page explains the mechanism: a fixed-capacity rod where only a suffix
of elements is simulated, the rest folded as storage; _GrowingCRBoundaryConditions
applies PD at that suffix base and resizes current_elements on triggers.
Mental model: active suffix
The implementation is GrowingCR in
src/virtual_field/runtime/custom_elastica/rods/growing_cr.py. Arrays are
allocated for total_elements, but current_elements controls how many elements
from the end of the array participate in internal forces, damping, and
integration. Equivalently, physics uses slices [-current_elements:] on
per-element data and the last current_elements + 1 nodes.
At build time, growing_cr_allocate lays out a straight rod so the inactive
prefix sits near the base and the active segment points along the desired
direction; the base of the simulated arm is the first node of the active
suffix, at index total_elements - current_elements.

What _GrowingCRBoundaryConditions does
The class in
src/virtual_field/runtime/custom_elastica/boundary_conditions.py subclasses
PyElastica’s NoForces and runs each step as forcing on the rod.
Turret tracking A callable
controller()returns a world-frame rotation matrix (and a position that is currently unused). The base of the active segment is pulled toward a fixedtarget_positionwith gainp_linear_value, and its directors are aligned to the controller orientation with gainp_angular_value. The torque uses the rotation vector from the relative rotation (SO(3) logarithm / inverse Rodrigues construction), including a stable branch near π.Growth / shrink Two booleans,
trigger_increase_elementsandtrigger_decrease_elements, request changingcurrent_elements. A short debounce (0.3 s) avoids repeated toggles.Increase (when
current_elements < total_elements): incrementcurrent_elements, then call_reset_element_kinematics_and_strainson the new base index so the newly exposed segment is snapped to rest length, inherits directors from its neighbor, and clears spurious velocity/angular rates at that joint.Decrease (when
current_elements > min_elements): decrementcurrent_elementsonly; the shorter active prefix leaves the folded storage as-is for the next steps.
Ramp (artifact from rest of the PyElastica)
ramp_up_timescales both linear and angular efforts bymin(1, time / ramp_up_time)so startup does not impulse the rod (in typical setupsramp_up_timeis chosen very small).
Together, this replaces a classical fixed base constraint: the base moves with the user’s aim while the arm length changes on discrete button edges.
Example wiring
TwoGCRSimulation (src/virtual_field/runtime/two_gcr_simulation.py) builds two
GrowingCR rods with total_elements = 5 * n_elem and current_elements = n_elem, then attaches _GrowingCRBoundaryConditions with:
target_positionat each arm’s fixed base in the scene,controllerreturning the tracked controller pose for that arm,primary button: decrease length,
secondary button: increase length (edge-triggered in
handle_commands).
That pairing is one concrete UX; the boundary class only needs the triggers and controller you provide.