diff --git a/backlog.md b/backlog.md
index 098dea7..90606b8 100644
--- a/backlog.md
+++ b/backlog.md
@@ -19,6 +19,13 @@ Pose tools (rtmlib / RTMPose, 2D):
- ✅ Pose skeleton preview overlay in studio
- ✅ Draggable joints to refine/explore a pose
- ✅ Find similar poses across the library (translation/scale/mirror-invariant; `poses_index.json`)
+- ✅ Gesture presets in pose toolbar (T-pose, wave, arms raised, hands on hips, turn ±45°)
+- ✅ "Orbit all" button in Generate tab selects all 6 absolute camera angles at once
+
+Scenery:
+- ✅ source_refs now stores all references: person image + video name + extra ref image
+- ✅ /scenery/library endpoint — all scenery grouped by source video
+- ✅ "Previous scenery" panel in Scenery tab — browse & reuse scenery from any group, grouped by video
## Open
@@ -55,4 +62,9 @@ generating poses themself should use the adviced dimensions rather than the base
introduce a like/dislike base system for images/group to determine the sort order (and more)
- would also dislike the pose
-creating scenery should keep both video-frame + ref images as references, we only see 1 image now.
+creating scenery should keep both video-frame + ref images as references, we only see 1 image now. ✅ fixed
+
+gesture ideas for future (require ControlNet / OpenPose conditioning):
+- animate limbs along keyframe curves (true motion)
+- 3D-aware rotation / "circling around" with depth estimation
+- camera-orbit batch with consistent lighting across views
diff --git a/tour-comfy/car.html b/tour-comfy/car.html
index 6634257..4f1c5a5 100644
--- a/tour-comfy/car.html
+++ b/tour-comfy/car.html
@@ -2922,8 +2922,13 @@
bar.style.cssText = 'position:absolute;top:8px;left:8px;display:flex;gap:6px;z-index:102;';
bar.innerHTML =
''
- + '';
+ + ''
+ + '
'
+ + ' '
+ + ' '
+ + '
';
viewer.appendChild(bar);
+ _buildGestureMenu();
canvas.addEventListener('mousedown', _posePointerDown);
canvas.addEventListener('mousemove', _posePointerMove);
@@ -2933,6 +2938,70 @@
if (btn) btn.classList.add('primary');
}
+ // --- Gesture presets: normalized [x,y,conf] per COCO-17 keypoint ---
+ // Defined in [0,1] image space for a portrait-oriented standing figure.
+ // Applied to the actual image by scaling to image pixel dimensions.
+ const GESTURE_PRESETS = [
+ { name: 'T-pose', desc: 'Arms fully extended sideways',
+ kpts: [[.50,.07,1],[.47,.065,1],[.53,.065,1],[.43,.08,1],[.57,.08,1],
+ [.25,.22,1],[.75,.22,1],[.10,.22,1],[.90,.22,1],[-.02,.22,1],[1.02,.22,1],
+ [.40,.52,1],[.60,.52,1],[.40,.72,1],[.60,.72,1],[.40,.93,1],[.60,.93,1]] },
+ { name: 'Wave right', desc: 'Right arm raised and waving',
+ kpts: [[.50,.07,1],[.47,.065,1],[.53,.065,1],[.43,.08,1],[.57,.08,1],
+ [.35,.22,1],[.65,.22,1],[.20,.38,1],[.78,.10,1],[.22,.52,1],[.70,.04,1],
+ [.40,.52,1],[.60,.52,1],[.40,.72,1],[.60,.72,1],[.40,.93,1],[.60,.93,1]] },
+ { name: 'Arms raised', desc: 'Both arms raised above head',
+ kpts: [[.50,.07,1],[.47,.065,1],[.53,.065,1],[.43,.08,1],[.57,.08,1],
+ [.35,.22,1],[.65,.22,1],[.22,.08,1],[.78,.08,1],[.18,.02,1],[.82,.02,1],
+ [.40,.52,1],[.60,.52,1],[.40,.72,1],[.60,.72,1],[.40,.93,1],[.60,.93,1]] },
+ { name: 'Hands on hips', desc: 'Hands resting on hips',
+ kpts: [[.50,.07,1],[.47,.065,1],[.53,.065,1],[.43,.08,1],[.57,.08,1],
+ [.35,.22,1],[.65,.22,1],[.28,.40,1],[.72,.40,1],[.35,.52,1],[.65,.52,1],
+ [.38,.52,1],[.62,.52,1],[.38,.72,1],[.62,.72,1],[.38,.93,1],[.62,.93,1]] },
+ { name: 'Turn left 45°', desc: 'Body turned ~45° to the left',
+ kpts: [[.45,.07,1],[.44,.065,1],[.50,.065,1],[.41,.08,1],[.52,.08,1],
+ [.30,.22,1],[.58,.22,1],[.18,.38,1],[.65,.30,1],[.12,.52,1],[.70,.22,1],
+ [.36,.52,1],[.58,.52,1],[.36,.72,1],[.58,.72,1],[.36,.93,1],[.58,.93,1]] },
+ { name: 'Turn right 45°', desc: 'Body turned ~45° to the right',
+ kpts: [[.55,.07,1],[.50,.065,1],[.56,.065,1],[.48,.08,1],[.59,.08,1],
+ [.42,.22,1],[.70,.22,1],[.35,.30,1],[.82,.38,1],[.30,.22,1],[.88,.52,1],
+ [.42,.52,1],[.64,.52,1],[.42,.72,1],[.64,.72,1],[.42,.93,1],[.64,.93,1]] },
+ ];
+
+ function _buildGestureMenu() {
+ const panel = document.getElementById('gestureMenuPanel');
+ if (!panel) return;
+ panel.innerHTML = GESTURE_PRESETS.map((g, i) =>
+ `
${escHtml(g.name)}
`
+ ).join('');
+ }
+
+ function toggleGestureMenu(e) {
+ e.stopPropagation();
+ const p = document.getElementById('gestureMenuPanel');
+ if (!p) return;
+ const open = p.style.display !== 'none';
+ p.style.display = open ? 'none' : 'block';
+ if (!open) {
+ const close = (ev) => { if (!p.contains(ev.target)) { p.style.display='none'; document.removeEventListener('click',close); } };
+ setTimeout(() => document.addEventListener('click', close), 0);
+ }
+ }
+
+ function applyGesturePreset(idx) {
+ const st = window._poseState;
+ if (!st || !st.people.length) { showToast('No pose overlay active — click Pose first', 'info'); return; }
+ const g = GESTURE_PRESETS[idx];
+ const W = st.width, H = st.height;
+ st.people[0] = g.kpts.map(([nx, ny, c]) => [nx * W, ny * H, c]);
+ _drawPose();
+ document.getElementById('gestureMenuPanel').style.display = 'none';
+ showToast(`Gesture: ${g.name}`, 'success');
+ }
+
function _drawPose() {
const st = window._poseState; if (!st) return;
const { ctx, canvas, scale, offX, offY } = st;
@@ -4506,7 +4575,12 @@
// Camera angles section — absolute positions
const absAngles = CAMERA_ANGLES.filter(a => !a.relative);
const relAngles = CAMERA_ANGLES.filter(a => a.relative);
- let html = '