Devtools

Add a panel to your app while you develop. It checks your live scene, shows what a render will cost, and renders images, videos and bakes with a click.

Mount it

Mount the panel next to your canvas. In React Three Fiber, give it your key or token the way you give useBakery3(), and it shows the scene in the page's one <Canvas>.

import { Bakery3Devtools } from '@oeave/bakery3/r3f';

<>
  <Canvas>
    <Scene />
  </Canvas>

  {import.meta.env.DEV && <Bakery3Devtools token={getToken} />}
</>

With several canvases on the page, put a <Bakery3Provider> inside the one to render and mount <Bakery3Devtools /> without a key or token. It then uses the provider's connection and scene.

<>
  <Canvas>
    <Bakery3Provider token={getToken}>
      <Scene />
    </Bakery3Provider>
  </Canvas>

  {import.meta.env.DEV && <Bakery3Devtools />}
</>

With neither a key nor a provider, the panel still opens after a moment. It says that no key or token was given, links to the Keys page, and logs one warning in the console.

Options

Both mounts take these:

OptionWhat it does
position'bottom-right' (default), 'bottom-left', 'top-right' or 'top-left'.
defaultQualityThe quality preset the Render and Video tabs open on. Default 'studio'.
defaultOpenStart open instead of as a small button.
defaultTab'render', 'video' or 'bake'.
timelineThe timeline the Video tab films. Without one the panel makes its own on your live camera.
timelineTargetsObjects offered in the timeline's "add track" picker.
onPlaybackChangeCalled when the timeline takes the camera and when it gives it back.
bakeTransportBake with your own worker instead of the hosted one.
bakeBaseUrlWhere relative URLs in a bundle from your own worker resolve.
muteLightsOnApplyTurn your live lights off when a bake is applied. Off by default, because the lightmap already replaces their diffuse light. See Bake a scene.
history{ list, download? }: your own list of past renders, shown as Recent instead of the panel's own list.

<Bakery3Devtools> also takes these. Leave out apiKey and token to use <Bakery3Provider> instead.

OptionWhat it does
apiKeyA secret key, for testing on your own machine.
tokenA function that fetches a browser token, as useBakery3() takes it.
baseUrl, fetchThe API address and the fetch to use, with apiKey or token.
defaultsDefaults for every render, as createBakery3() takes them, with apiKey or token.
onReadyCalled with the panel's handle when it mounts, and with null when it unmounts. See below.

mountBakery3Devtools also takes these:

OptionWhat it does
bakery3The client. Required.
scene, cameraThe live scene and camera. Required. Getters work, so the panel can follow a camera you swap.
rendererYour WebGLRenderer, so the render uses your color space and tone mapping. A getter works here too.
containerWhere the panel mounts. Defaults to document.body.
titleA name beside the logo in the header. None by default.
onOpenChangeCalled when the panel is opened or collapsed.

What the panel shows

PartWhat you get
CompatibilityBeside each button, how much of your scene renders as you see it, such as 98% compatible. Click it for each object that renders differently, with a link to the fix. The check is free and runs in your browser.
PriceThe price beside each button, from estimate(). Hover it to see what it is made of (for a video, the frame count times the price of one frame) and the most it can cost. It updates when you change a setting, the scene or the camera, once the change has settled, and is not fetched again while nothing changes.
RenderQuality and size. The size is the long edge, and the picture takes your camera's shape.
VideoThe timeline: scrub, add keyframes, record the camera with Keyframe camera, and set the length and fps, which give the frame count. The price is the clip's, quoted the way the clip is charged.
BakeThe objects that can be baked (and why others cannot), and your bakes under Recent. Click a bake to put it on the scene, and click it again to go back to the live scene. Hold to compare shows the scene without the bake.
JobsA row for every render, from the moment you press the button: capturing the scene, uploading, then the render's own progress and preview, with a button that cancels it. Press Render again to start another while one runs. In React Three Fiber, renders your own useBakery3() button starts show here too.
RecentEvery render, video and bake you made, newest first, with its size, quality and price. Click one to see it again. The list is kept in this browser, so it is still there after a reload, until its links expire after 24 hours.

A missing or wrong key, a token the API refused and an empty balance show in the panel as soon as it asks for a price, with a link to the Keys page or the Billing page.

Control the panel from code

mountBakery3Devtools() returns the panel's handle. <Bakery3Devtools> hands you the same handle through onReady:

import type { DevPanel } from '@oeave/bakery3/r3f';

const panel = useRef<DevPanel | null>(null);

<Bakery3Devtools onReady={(handle) => (panel.current = handle)} />

The handle does this:

panel.open();
panel.close();
panel.setTab('bake');
panel.refresh();        // re-read bake candidates and history
panel.refreshCost();    // ask for a fresh estimate now
panel.setStatus('uploading 148 MB…', 'info');   // 'info' | 'error' | 'ok'
panel.setProgress('Baking pedestal · 3/12', 0.25);
panel.setProgress(null);                        // clear
panel.dispose();

dispose() also reverts any bake the panel applied and frees its lightmaps. In React that happens on unmount.

Pause your orbit controls during playback

Orbit controls and the timeline both move the camera, and the preview stutters if both run. Turn your controls off while the timeline plays.

<Bakery3Devtools
  token={getToken}
  timeline={tl}
  onPlaybackChange={(active) => {
    controls.current.enabled = !active;
  }}
/>

Bake with your own worker

The Bake tab calls bakery3.bake() by default, which is our hosted baking. To use a bake worker of your own, pass a bakeTransport: a function that takes a BakeSpec and its assets and returns a BakeBundle.

<Bakery3Devtools
  token={getToken}
  bakeTransport={async (spec, assets) => {
    const response = await fetch('/api/bake', {
      method: 'POST',
      body: pack(spec, assets),   // your own function
    });
    return response.json();
  }}
/>

If your bundle has relative URLs, set bakeBaseUrl to where they resolve.

Keep the panel out of production

{process.env.NODE_ENV === 'development' && <Bakery3Devtools token={getToken} />}

@oeave/bakery3/devtools is its own entry point, so an app that never imports it never bundles it. You can ship the panel in production behind your own access control, but it is made for development.

Use the panel without the hosted API

mountDevPanel is the same panel with no client attached. You supply every callback. Use it when the panel talks to a local worker or a proxy of your own.

import { mountDevPanel } from '@oeave/bakery3/devtools';

const panel = mountDevPanel({
  position: 'bottom-right',
  compatibility: () => report,
  estimate: (request) => post('/api/estimate', request),
  sceneVersion: () => scene.children.length,
  viewVersion: () => camera.position.toArray().join(),
  render: { onRender: (settings) => post('/api/render', settings) },
});

sceneVersion and viewVersion are cheap fingerprints of what is in the scene and where the camera is. The panel compares them a few times a second while it is open and asks for a new price when one changes: a change to the scene as soon as it settles, a camera move once the camera stops.

Show only the timeline

mountTimelinePanel gives you the timeline without the render and bake tabs. See Render video.