# Capture Camera Views URL: /runtime/guides/headless-camera-capture `@taucad/image` fits a model or reproduces a viewer camera using serializable camera options. ## Choose framing [#choose-framing] Tau viewer, thumbnail, and capture callers use `framing: 'bounds'` so every surface composes around the same conservative scene AABB; reserve `framing: 'fit'` for renderer-owned composition from exact visible geometry. `direction` points from the subject centre toward the camera — the renderer accepts Cartesian direction only — and `up` controls the top of the image: ```typescript const fittedCamera = { framing: 'bounds', direction: [1, -1, 0.7], up: [0, 0, 1], margin: 0.1, projection: { kind: 'perspective', verticalFieldOfView: 45 }, } as const; ``` Use `framing: 'fixed'` when the image must preserve position, target, roll, and magnification. Position, target, orthographic span, and clipping use the GLB world unit: ```typescript const fixedCamera = { framing: 'fixed', position: [0.09, -0.065, 0.045], target: [0.005, 0, 0.008], up: [0.16, 0.11, 0.98], projection: { kind: 'perspective', verticalFieldOfView: 48, zoom: 1.25, }, clipping: { near: 0.0001, far: 2 }, } as const; ``` Perspective field of view is vertical, in degrees. Zoom is a positive magnification: the effective field of view is `2 × atan(tan(verticalFieldOfView / 2) / zoom)`, and an orthographic projection uses `verticalSpan / zoom` as its visible span. A fixed camera requires `up` because position and target alone cannot preserve roll. Omit `clipping` to derive planes from model bounds; set it only when the caller owns the scene depth range. Geometry at or behind the near plane is clipped, and line segments crossing it are trimmed before screen-space expansion. ## Capture a live Three.js camera [#capture-a-live-threejs-camera] Keep Three.js at the adapter boundary. `readThreeCameraState` copies world placement, roll, projection, zoom, clipping, and aspect from the active render frame into renderer-neutral `@taucad/camera` data; `toNanorasterCamera` preserves those values in the fixed-camera contract without applying another length scale: ```typescript import type { RuntimeDocument } from '@taucad/runtime/client'; import { readThreeCameraState } from '@taucad/three/camera'; import { toNanorasterCamera } from '@taucad/image/camera'; const tauWorld = { up: '+z', forward: '-y', unit: 'meter' } as const; export const captureCamera = async (document: RuntimeDocument, frame: Parameters[0]) => { const cameraState = readThreeCameraState(frame); const width = 2400; const height = Math.round(width / cameraState.aspect); return document.export('webp', { content: { includeEdges: true }, options: { width, height, lineWidth: 3, quality: 1, background: '#242424', label: 'main.ts', axes: true, scaleBar: true, world: tauWorld, camera: toNanorasterCamera({ cameraState }), }, }); }; ``` Pass a request-scoped document to these capture functions; its caller owns `close()`. Three rules keep the camera faithful: * Use the same aspect for output and source camera; a different output aspect deliberately changes horizontal composition. * `lineWidth: 3` is a flat 3 screen pixels at every output size — do not normalize it by image dimensions. * Copy camera state before awaiting geometry or network work, so the image represents the view at the moment of the capture request even if the viewer keeps moving. WebP `quality: 1` is lossless. ## Render editable presets [#render-editable-presets] Each batch view owns an editable camera: ```typescript import type { RuntimeDocument } from '@taucad/runtime/client'; const views = [ { id: 'three-quarter', label: 'Three-quarter', camera: { framing: 'bounds', direction: [1, -1, 0.7], up: [0, 0, 1], margin: 0.1, projection: { kind: 'perspective', verticalFieldOfView: 45 }, }, }, { id: 'plan', label: 'Plan', camera: { framing: 'fixed', position: [0, 0, 0.2], target: [0, 0, 0], up: [0, 1, 0], projection: { kind: 'orthographic', verticalSpan: 0.16, zoom: 1 }, }, }, ] as const; export const capturePresets = (document: RuntimeDocument) => document.export('webp', { content: { includeEdges: true }, options: { mode: 'batch', width: 1600, height: 1600, lineWidth: 3, quality: 1, background: '#242424', axes: true, scaleBar: true, views, }, }); ``` Bounds and fit framing share one magnification control, `margin`; fixed perspective uses field of view and zoom; fixed orthographic uses span and zoom. Separate cases avoid competing fit and zoom rules. ## Send the request over RPC [#send-the-request-over-rpc] Camera options are JSON data. An application RPC can serialize them; its receiver opens and closes the request-scoped document: ```typescript import { toNanorasterCamera } from '@taucad/image/camera'; export const serializeCapture = ( views: readonly { id: string; camera: ReturnType }[], ): string => JSON.stringify({ id: crypto.randomUUID(), method: 'capture', params: { source: { path: 'main.ts' }, to: 'webp', options: { mode: 'batch', width: 1600, height: 1600, lineWidth: 3, views }, }, }); ``` Validate at the receiver before rendering. The image plugin schemas reject non-finite vectors, coincident position and target, collinear direction and up, invalid projection values, inverted clip planes, duplicate view IDs, and unknown fields. ## Related APIs [#related-apis] * [`RuntimeDocument.export`](/runtime/api/client) — submits the image export * [`@taucad/image`](https://www.npmjs.com/package/@taucad/image) — camera and annotation options * [`@taucad/camera`](https://www.npmjs.com/package/@taucad/camera) — renderer-neutral projection math