|
| 1 | +(vf-growing-arm)= |
| 2 | +# Growing arm (GrowingCR + boundary control) |
| 3 | + |
| 4 | +## Introduction |
| 5 | + |
| 6 | +The **growing arm** setup pairs a storage-backed Cosserat rod (`GrowingCR`) with |
| 7 | +boundary forcing that acts like a **turret** at the base of the active segment |
| 8 | +and **discrete growth** when the user adds or removes elements. The same idea |
| 9 | +backs dual-arm demos in the Virtual Field runtime: fixed bases in the world, |
| 10 | +tracked controller orientation, and buttons to extend or retract the simulated |
| 11 | +length. |
| 12 | + |
| 13 | +The figures below are taken from that workflow: how the scene is arranged, how |
| 14 | +targeting maps to the base, and a short loop of growth in VR. |
| 15 | + |
| 16 | + |
| 17 | + |
| 18 | + |
| 19 | + |
| 20 | + |
| 21 | + |
| 22 | +This page explains the **mechanism**: a fixed-capacity rod where only a **suffix** |
| 23 | +of elements is simulated, the rest folded as **storage**; `_GrowingCRBoundaryConditions` |
| 24 | +applies PD at that suffix base and resizes `current_elements` on triggers. |
| 25 | + |
| 26 | +## Mental model: active suffix |
| 27 | + |
| 28 | +The implementation is `GrowingCR` in |
| 29 | +`src/virtual_field/runtime/custom_elastica/rods/growing_cr.py`. Arrays are |
| 30 | +allocated for `total_elements`, but `current_elements` controls how many elements |
| 31 | +from the **end** of the array participate in internal forces, damping, and |
| 32 | +integration. Equivalently, physics uses slices `[-current_elements:]` on |
| 33 | +per-element data and the last `current_elements + 1` nodes. |
| 34 | + |
| 35 | +At build time, `growing_cr_allocate` lays out a straight rod so the **inactive** |
| 36 | +prefix sits near the base and the **active** segment points along the desired |
| 37 | +direction; the **base** of the simulated arm is the first node of the active |
| 38 | +suffix, at index `total_elements - current_elements`. |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | +Editor and GitHub **Markdown previews** only render ordinary `` image |
| 43 | +links like the lines above. They do not run Sphinx, so fenced `image` directives |
| 44 | +would show as raw text. After `sphinx-build` or `make html` in `docs/`, open |
| 45 | +`_build/html/custom_elastica/growing_arm.html` in a browser (or use hosted docs) |
| 46 | +to see the full themed layout. |
| 47 | + |
| 48 | +## What `_GrowingCRBoundaryConditions` does |
| 49 | + |
| 50 | +The class in |
| 51 | +`src/virtual_field/runtime/custom_elastica/boundary_conditions.py` subclasses |
| 52 | +PyElastica’s `NoForces` and runs each step as **forcing** on the rod. |
| 53 | + |
| 54 | +1. **Turret tracking** |
| 55 | + A callable `controller()` returns a world-frame rotation matrix (and a |
| 56 | + position that is currently unused). The base of the active segment is pulled |
| 57 | + toward a fixed `target_position` with gain `p_linear_value`, and its |
| 58 | + directors are aligned to the controller orientation with gain |
| 59 | + `p_angular_value`. The torque uses the rotation vector from the relative |
| 60 | + rotation (SO(3) logarithm / inverse Rodrigues construction), including a |
| 61 | + stable branch near π. |
| 62 | + |
| 63 | +2. **Growth / shrink** |
| 64 | + Two booleans, `trigger_increase_elements` and `trigger_decrease_elements`, |
| 65 | + request changing `current_elements`. A short **debounce** (0.3 s) avoids |
| 66 | + repeated toggles. |
| 67 | + - **Increase** (when `current_elements < total_elements`): increment |
| 68 | + `current_elements`, then call `_reset_element_kinematics_and_strains` on the |
| 69 | + new base index so the newly exposed segment is snapped to rest length, |
| 70 | + inherits directors from its neighbor, and clears spurious velocity/angular |
| 71 | + rates at that joint. |
| 72 | + - **Decrease** (when `current_elements > min_elements`): decrement |
| 73 | + `current_elements` only; the shorter active prefix leaves the folded |
| 74 | + storage as-is for the next steps. |
| 75 | + |
| 76 | +3. **Ramp** (artifact from rest of the PyElastica) |
| 77 | + `ramp_up_time` scales both linear and angular efforts by |
| 78 | + `min(1, time / ramp_up_time)` so startup does not impulse the rod (in typical |
| 79 | + setups `ramp_up_time` is chosen very small). |
| 80 | + |
| 81 | +Together, this replaces a classical **fixed** base constraint: the base **moves** |
| 82 | +with the user’s aim while the arm length changes on discrete button edges. |
| 83 | + |
| 84 | +## Example wiring |
| 85 | + |
| 86 | +`TwoGCRSimulation` (`src/virtual_field/runtime/two_gcr_simulation.py`) builds two |
| 87 | +`GrowingCR` rods with `total_elements = 5 * n_elem` and `current_elements = |
| 88 | +n_elem`, then attaches `_GrowingCRBoundaryConditions` with: |
| 89 | + |
| 90 | +- `target_position` at each arm’s fixed base in the scene, |
| 91 | +- `controller` returning the tracked controller pose for that arm, |
| 92 | +- **primary** button: decrease length, |
| 93 | +- **secondary** button: increase length (edge-triggered in `handle_commands`). |
| 94 | + |
| 95 | +That pairing is one concrete UX; the boundary class only needs the triggers and |
| 96 | +controller you provide. |
0 commit comments