FEATURE 1 — Interactive transform gizmo (move / rotate / scale in scene)
As a user viewing a generated mesh, I want to grab a gizmo and move,
rotate, or scale the mesh directly in the 3D scene, so I can position it
visually without typing numeric values or leaving the app.
Complements the numeric POST /optimize/transform ticket — this is the
interactive counterpart (previously marked "Tier 3 / out of scope").
Scope
- Gizmo attached to the loaded mesh when it is selected (selection state
already exists in Viewer3D.tsx).
- Three modes: Translate / Rotate / Scale, switchable via toolbar buttons
and keyboard (W / E / R).
- Gizmo dragging disables OrbitControls (drei handles this automatically when
OrbitControls has makeDefault).
- Optional snapping (grid / 15° / 0.1) while holding a modifier key.
- "Apply" bakes the gizmo transform into the mesh so it persists to export;
"Reset" reverts to the auto-centered pose.
Implementation
- Viewer
src/areas/generate/components/Viewer3D.tsx:
- Add
<TransformControls> from @react-three/drei, attached to the mesh
<primitive object={scene}> via a ref (lift a ref out of SceneMeshModel).
- Render only when
selected. Mode driven by new state gizmoMode: 'translate' | 'rotate' | 'scale' | null.
- Reconcile with the existing auto-center
useEffect (Viewer3D.tsx:201) so a
user transform isn't overwritten on re-render (only auto-center on first
load / model change).
- Toolbar
ViewerToolbar.tsx: add a Move/Rotate/Scale button group
(mirrors the existing MODES button pattern), plus gizmoMode props.
- Keyboard: extend the existing keydown handler (Viewer3D.tsx:396) with
W/E/R to switch mode and Esc to exit gizmo (skip when an input is focused).
- Bake/persist (Apply): read the mesh's world matrix and send it to the
backend to bake into the GLB. Reuse / extend POST /optimize/transform
(api/routers/optimize.py) by accepting a raw 4×4 matrix, applying it at
scene level with Scene.apply_transform() (preserves materials/UV), then
updateCurrentJob + pushMeshUrl to reload the viewer on the result.
Acceptance criteria
Estimate: M
FEATURE 1 — Interactive transform gizmo (move / rotate / scale in scene)
As a user viewing a generated mesh, I want to grab a gizmo and move,
rotate, or scale the mesh directly in the 3D scene, so I can position it
visually without typing numeric values or leaving the app.
Complements the numeric
POST /optimize/transformticket — this is theinteractive counterpart (previously marked "Tier 3 / out of scope").
Scope
already exists in
Viewer3D.tsx).and keyboard (
W/E/R).OrbitControlshasmakeDefault)."Reset" reverts to the auto-centered pose.
Implementation
src/areas/generate/components/Viewer3D.tsx:<TransformControls>from@react-three/drei, attached to the mesh<primitive object={scene}>via a ref (lift a ref out ofSceneMeshModel).selected. Mode driven by new stategizmoMode: 'translate' | 'rotate' | 'scale' | null.useEffect(Viewer3D.tsx:201) so auser transform isn't overwritten on re-render (only auto-center on first
load / model change).
ViewerToolbar.tsx: add a Move/Rotate/Scale button group(mirrors the existing
MODESbutton pattern), plusgizmoModeprops.W/E/R to switch mode and
Escto exit gizmo (skip when an input is focused).backend to bake into the GLB. Reuse / extend
POST /optimize/transform(
api/routers/optimize.py) by accepting a raw 4×4matrix, applying it atscene level with
Scene.apply_transform()(preserves materials/UV), thenupdateCurrentJob+pushMeshUrlto reload the viewer on the result.Acceptance criteria
Estimate: M