Skip to content

FEATURE 1 - Interactive transform gizmo #161

@lightningpixel

Description

@lightningpixel

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

  • Selecting the mesh shows a gizmo; deselecting hides it.
  • Translate / rotate / scale all work and update the mesh live.
  • W/E/R switch modes; toolbar buttons reflect the active mode.
  • OrbitControls is disabled while dragging the gizmo, re-enabled after.
  • "Apply" bakes the transform into the GLB; re-loading keeps the new pose.
  • "Reset" restores the auto-centered position.
  • No interference with viewMode switching, screenshot, or Delete-to-remove.

Estimate: M

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions