@oeave/bakery3 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +562 -2
- package/dist/animation-B1h0Ryvj.d.ts +97 -0
- package/dist/bake/index.d.ts +369 -0
- package/dist/bake/index.js +9 -0
- package/dist/bake/index.js.map +1 -0
- package/dist/bake-DZ-CJR6f.d.ts +1364 -0
- package/dist/catalog/index.d.ts +100 -0
- package/dist/catalog/index.js +154 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/catalog.gen-BM-aNf7n.d.ts +733 -0
- package/dist/chunk-3G5QL4F4.js +145 -0
- package/dist/chunk-3G5QL4F4.js.map +1 -0
- package/dist/chunk-4E5VV4QY.js +12 -0
- package/dist/chunk-4E5VV4QY.js.map +1 -0
- package/dist/chunk-62M6XXNX.js +142 -0
- package/dist/chunk-62M6XXNX.js.map +1 -0
- package/dist/chunk-6FYI6AJM.js +1157 -0
- package/dist/chunk-6FYI6AJM.js.map +1 -0
- package/dist/chunk-6NYR73Y7.js +832 -0
- package/dist/chunk-6NYR73Y7.js.map +1 -0
- package/dist/chunk-APWUCEEB.js +118 -0
- package/dist/chunk-APWUCEEB.js.map +1 -0
- package/dist/chunk-HNMSWQU7.js +1709 -0
- package/dist/chunk-HNMSWQU7.js.map +1 -0
- package/dist/chunk-JFYUDERE.js +1412 -0
- package/dist/chunk-JFYUDERE.js.map +1 -0
- package/dist/chunk-KNUAOILG.js +551 -0
- package/dist/chunk-KNUAOILG.js.map +1 -0
- package/dist/chunk-KZLVTSBI.js +191 -0
- package/dist/chunk-KZLVTSBI.js.map +1 -0
- package/dist/chunk-LAKXC4WR.js +2028 -0
- package/dist/chunk-LAKXC4WR.js.map +1 -0
- package/dist/chunk-NXBAZGNB.js +1107 -0
- package/dist/chunk-NXBAZGNB.js.map +1 -0
- package/dist/chunk-QRCT5UGZ.js +3286 -0
- package/dist/chunk-QRCT5UGZ.js.map +1 -0
- package/dist/chunk-S4AJTLLN.js +23 -0
- package/dist/chunk-S4AJTLLN.js.map +1 -0
- package/dist/chunk-UWBP7B54.js +92 -0
- package/dist/chunk-UWBP7B54.js.map +1 -0
- package/dist/chunk-XK35ANPJ.js +346 -0
- package/dist/chunk-XK35ANPJ.js.map +1 -0
- package/dist/chunk-Z7IYVH22.js +4781 -0
- package/dist/chunk-Z7IYVH22.js.map +1 -0
- package/dist/chunk-ZEBVAIJJ.js +156 -0
- package/dist/chunk-ZEBVAIJJ.js.map +1 -0
- package/dist/devtools/index.d.ts +536 -0
- package/dist/devtools/index.js +15 -0
- package/dist/devtools/index.js.map +1 -0
- package/dist/environments/index.d.ts +93 -0
- package/dist/environments/index.js +382 -0
- package/dist/environments/index.js.map +1 -0
- package/dist/hotspots/index.d.ts +111 -0
- package/dist/hotspots/index.js +285 -0
- package/dist/hotspots/index.js.map +1 -0
- package/dist/index.d.ts +975 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/node/index.cjs +5240 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +4927 -0
- package/dist/node/index.d.ts +597 -0
- package/dist/node/index.js +1328 -0
- package/dist/node/index.js.map +1 -0
- package/dist/prepare-BtjY4G3q.d.ts +112 -0
- package/dist/presets/index.d.ts +562 -0
- package/dist/presets/index.js +14 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/r3f/index.d.ts +159 -0
- package/dist/r3f/index.js +592 -0
- package/dist/r3f/index.js.map +1 -0
- package/dist/room-FS26KAPQ.js +9 -0
- package/dist/room-FS26KAPQ.js.map +1 -0
- package/dist/rooms.gen-DItzBR9k.d.ts +1462 -0
- package/dist/session-VCIQEO26.js +9 -0
- package/dist/session-VCIQEO26.js.map +1 -0
- package/dist/shapes/index.d.ts +222 -0
- package/dist/shapes/index.js +836 -0
- package/dist/shapes/index.js.map +1 -0
- package/dist/testRun-20OARnQr.d.ts +1139 -0
- package/dist/timeline-ChwgD7bT.d.ts +470 -0
- package/dist/tsl/index.d.ts +165 -0
- package/dist/tsl/index.js +310 -0
- package/dist/tsl/index.js.map +1 -0
- package/package.json +170 -4
|
@@ -0,0 +1,4927 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation: authored as keyframes, shipped as baked frames.
|
|
3
|
+
*
|
|
4
|
+
* The SDK's `Timeline` is keyframes, easings and orbit segments; this is
|
|
5
|
+
* not. What travels is one exact value per output frame, and the renderer
|
|
6
|
+
* sets one keyframe per frame with linear interpolation, so there is nothing
|
|
7
|
+
* left for it to interpolate and nothing for the two sides to disagree
|
|
8
|
+
* about. Easing curves and orbit math exist in one implementation, and the
|
|
9
|
+
* browser preview and the traced frame agree on where everything is at
|
|
10
|
+
* frame N. It costs little: 10 s at 30 fps of camera motion is 300 frames of
|
|
11
|
+
* 7 floats.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* What a channel drives. The camera is not an object id: it may never have
|
|
15
|
+
* been in the scene graph at all (see `CameraSpec`). Neither is the
|
|
16
|
+
* environment: `scene.environment`, its intensity and rotation, and the
|
|
17
|
+
* scene's ambient light are scene state, not objects (see `EnvironmentSpec`).
|
|
18
|
+
*/
|
|
19
|
+
type AnimationTarget = {
|
|
20
|
+
type: 'camera';
|
|
21
|
+
} | {
|
|
22
|
+
type: 'object';
|
|
23
|
+
objectId: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'environment';
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* One animated property, sampled at every frame.
|
|
29
|
+
*
|
|
30
|
+
* `values` is flat: `frameCount * size` numbers, row-major, frame 0's
|
|
31
|
+
* components and then frame 1's. Nested arrays would cost two bytes of JSON
|
|
32
|
+
* per frame per channel, which for a long sequence is the larger part of
|
|
33
|
+
* the manifest.
|
|
34
|
+
*/
|
|
35
|
+
type AnimationChannel = {
|
|
36
|
+
target: AnimationTarget;
|
|
37
|
+
/**
|
|
38
|
+
* `position` | `quaternion` | `scale` | `fov` | `color` | `emissive` |
|
|
39
|
+
* `emissiveIntensity` | `roughness` | `metalness` | `opacity` | `intensity`
|
|
40
|
+
* | `target` | `groundColor`, and on the environment target the four in
|
|
41
|
+
* `ENVIRONMENT_CHANNEL_PROPERTIES`.
|
|
42
|
+
*
|
|
43
|
+
* `target` is a directional or spot light's aim, a WORLD point, as three's
|
|
44
|
+
* `light.target` holds it. Such a light is aimed from its position to its
|
|
45
|
+
* target at every frame, as three aims it, so animating either one turns
|
|
46
|
+
* the beam; `quaternion` on one is refused, because three ignores a
|
|
47
|
+
* directional or spot light's rotation.
|
|
48
|
+
*
|
|
49
|
+
* A property the renderer does not implement is refused with the name,
|
|
50
|
+
* the object's path and a fix, never dropped.
|
|
51
|
+
*/
|
|
52
|
+
property: string;
|
|
53
|
+
/** Components per frame: 3 position/scale/color, 4 quaternion, 1 scalar. */
|
|
54
|
+
size: number;
|
|
55
|
+
values: number[];
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The baked sequence.
|
|
59
|
+
*
|
|
60
|
+
* Frame `i` is sampled at `i / fps` seconds, so a `frameCount / fps` second
|
|
61
|
+
* clip never repeats its first pose at the end, which is what makes a 360
|
|
62
|
+
* degree orbit loop without a duplicated frame.
|
|
63
|
+
*/
|
|
64
|
+
type AnimationSpec = {
|
|
65
|
+
fps: number;
|
|
66
|
+
frameCount: number;
|
|
67
|
+
channels: AnimationChannel[];
|
|
68
|
+
/**
|
|
69
|
+
* Where the browser actually put things: see `WorldCheck`. Optional on the
|
|
70
|
+
* wire so an older worker reads straight through it; a worker that
|
|
71
|
+
* implements it refuses a clip whose frames disagree with the browser's.
|
|
72
|
+
*/
|
|
73
|
+
checks?: WorldCheck[];
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* A target's world matrix at a few of the clip's frames, straight off three
|
|
77
|
+
* (`Object3D.matrixWorld` after a seek; the camera's pose composed), in
|
|
78
|
+
* three's own Y-up frame, column-major, sixteen numbers per frame.
|
|
79
|
+
*
|
|
80
|
+
* The channels say what the browser did; this says where it ended up. The
|
|
81
|
+
* worker rebuilds every animated object from the channels through its own
|
|
82
|
+
* up-axis conversion and parent chain, then sets each of these frames, reads
|
|
83
|
+
* its own world matrices back, and refuses the job if they do not match the
|
|
84
|
+
* browser's: a refusal instead of a plausible clip with the motion in the
|
|
85
|
+
* wrong place. Five frames are enough (a convention error shows on the
|
|
86
|
+
* first, an axis error on any, a drift on the last) and cost eighty numbers
|
|
87
|
+
* per target.
|
|
88
|
+
*/
|
|
89
|
+
type WorldCheck = {
|
|
90
|
+
target: AnimationTarget;
|
|
91
|
+
/** Frame indices, ascending, each in `[0, frameCount)`. */
|
|
92
|
+
frames: number[];
|
|
93
|
+
/** `frames.length * 16` numbers. */
|
|
94
|
+
matrices: number[];
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Room presets: a staged interior the renderer rebuilds by itself.
|
|
99
|
+
*
|
|
100
|
+
* A room preset is a whole set: walls with windows, a floor, a pedestal,
|
|
101
|
+
* the furniture along the walls, the foliage outside the glass that dapples
|
|
102
|
+
* the sunlight, a designed lighting rig per mode. The browser shows a
|
|
103
|
+
* compressed stand-in of it (one small GLB from the preset library) so the
|
|
104
|
+
* developer can frame their product inside it; none of that geometry is
|
|
105
|
+
* captured. The manifest carries only this record (which room, which build
|
|
106
|
+
* of it, which lighting, which surfaces, which stand, where it sits), and
|
|
107
|
+
* the worker rebuilds the room from its own full-resolution copy of the
|
|
108
|
+
* definition. A room render uploads exactly what a bare product render
|
|
109
|
+
* uploads: the product.
|
|
110
|
+
*
|
|
111
|
+
* Two versions guard the pixels. `build` is the content hash of the
|
|
112
|
+
* published room definition the SDK previewed; a worker whose copy hashes
|
|
113
|
+
* differently refuses rather than rendering a room that is not the one on
|
|
114
|
+
* screen. `ROOM_PRESET_VERSION` covers the worker's room builder.
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
declare const ROOM_LIGHTING_MODES: readonly ["day", "golden", "overcast", "night"];
|
|
118
|
+
type RoomLightingMode = (typeof ROOM_LIGHTING_MODES)[number];
|
|
119
|
+
/** A preset material plus an optional sRGB hex tint multiplied into albedo. */
|
|
120
|
+
type RoomSurfaceChoice = {
|
|
121
|
+
material: string;
|
|
122
|
+
/** `#rrggbb`. Plaster and paint take a tint well; a plank floor less so. */
|
|
123
|
+
color?: string;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* What the manifest says about the room. Every field except `name`, `build`,
|
|
127
|
+
* `lighting` and `matrix` is an override on the published defaults; absent
|
|
128
|
+
* means "the room's own".
|
|
129
|
+
*/
|
|
130
|
+
type RoomPresetSpec = {
|
|
131
|
+
/** The published room, e.g. `loft_golden`. */
|
|
132
|
+
name: string;
|
|
133
|
+
/** Content hash (12 hex) of the published definition. See above. */
|
|
134
|
+
build: string;
|
|
135
|
+
lighting: RoomLightingMode;
|
|
136
|
+
surfaces?: {
|
|
137
|
+
floor?: RoomSurfaceChoice;
|
|
138
|
+
walls?: RoomSurfaceChoice;
|
|
139
|
+
ceiling?: RoomSurfaceChoice;
|
|
140
|
+
/** Window sills: stone or timber. No tint; sills are never painted. */
|
|
141
|
+
sill?: {
|
|
142
|
+
material: string;
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* What the product stands on. `null` puts nothing under it (the developer
|
|
147
|
+
* placed their own stand, or the product sits on the floor); absent keeps
|
|
148
|
+
* the room's stand. `material` applies to a baked pedestal only; a table
|
|
149
|
+
* used as a stand keeps the finish it came with.
|
|
150
|
+
*/
|
|
151
|
+
pedestal?: {
|
|
152
|
+
type: string;
|
|
153
|
+
material?: string;
|
|
154
|
+
} | null;
|
|
155
|
+
/**
|
|
156
|
+
* How much of the room the browser shipped, so the worker does not build
|
|
157
|
+
* it twice. Normally none of the room's geometry crosses the wire.
|
|
158
|
+
*
|
|
159
|
+
* `true`: the whole stand-in is in the GLB, the shell, the stand and the
|
|
160
|
+
* furniture. A bake can then lightmap the room's own surfaces, because a
|
|
161
|
+
* lightmap has to land on a mesh the page holds. The worker builds only
|
|
162
|
+
* what the stand-in cannot carry: the sky, the sun, the portals, the
|
|
163
|
+
* foliage outside the glass, the fixtures' light and the panes.
|
|
164
|
+
*
|
|
165
|
+
* `'stand'`: only the stand is in the GLB, and the worker builds the room
|
|
166
|
+
* around it at full quality. This is what a bake into a prebaked room
|
|
167
|
+
* asks for: that bake lightmaps nothing of the room but the stand, so the
|
|
168
|
+
* light bouncing around the product should come off the real room, not
|
|
169
|
+
* off the browser's compressed preview of it.
|
|
170
|
+
*/
|
|
171
|
+
standIn?: boolean | 'stand';
|
|
172
|
+
/**
|
|
173
|
+
* The HDRI outside the windows: an `HDRI_PRESETS` name to put another map
|
|
174
|
+
* there than the room's published one for this mode, turned so its sun
|
|
175
|
+
* sits where the room's sun is; `null` for nothing outside (the mode's
|
|
176
|
+
* plain sky); absent keeps the room's own. In day and golden the room's
|
|
177
|
+
* sun lamp still casts the direct light; the map is the sky's light and
|
|
178
|
+
* the view, projected onto the ground.
|
|
179
|
+
*/
|
|
180
|
+
hdri?: string | null;
|
|
181
|
+
/**
|
|
182
|
+
* Pictures of the developer's own in the room's frames, one entry per
|
|
183
|
+
* picture the room hangs, in the order it hangs them. A `sha256:` asset
|
|
184
|
+
* hash puts that image in that frame; `null` or a short list leaves the
|
|
185
|
+
* rest as published.
|
|
186
|
+
*
|
|
187
|
+
* The frame does not move. A picture's moulding, its passepartout and the
|
|
188
|
+
* opening they leave were built to the published image's shape, and the
|
|
189
|
+
* browser shows that geometry while the developer frames the shot, so a
|
|
190
|
+
* replacement is cropped to fill the opening (the way `object-fit: cover`
|
|
191
|
+
* fills a box) rather than resizing the frame around it.
|
|
192
|
+
*/
|
|
193
|
+
pictures?: (string | null)[];
|
|
194
|
+
/** The room group's world matrix, three.js column-major. */
|
|
195
|
+
matrix: Matrix4;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* One node. `inputs` are ids of earlier nodes: the array arrives
|
|
200
|
+
* topologically sorted, so the worker builds it in one pass. Constants are
|
|
201
|
+
* nodes too, so every input is a reference and nothing is written twice.
|
|
202
|
+
*/
|
|
203
|
+
type ShaderGraphNode = {
|
|
204
|
+
id: string;
|
|
205
|
+
op: string;
|
|
206
|
+
inputs?: string[];
|
|
207
|
+
params?: Record<string, number | string | boolean | number[]>;
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Which BSDF slot a graph output drives. Everything not listed here falls
|
|
211
|
+
* back to the material's static properties.
|
|
212
|
+
*/
|
|
213
|
+
type ShaderSlot = 'color' | 'opacity' | 'alphaTest' | 'roughness' | 'metalness' | 'normal' | 'emissive' | 'ao' | 'clearcoat' | 'clearcoatRoughness' | 'sheen' | 'sheenRoughness' | 'iridescence' | 'iridescenceIOR' | 'iridescenceThickness' | 'transmission' | 'thickness' | 'ior' | 'specularIntensity' | 'specularColor' | 'anisotropy';
|
|
214
|
+
type ShaderGraph = {
|
|
215
|
+
version: number;
|
|
216
|
+
/** Topologically sorted: a node's inputs always precede it. */
|
|
217
|
+
nodes: ShaderGraphNode[];
|
|
218
|
+
/** Which node feeds which BSDF slot. */
|
|
219
|
+
outputs: Partial<Record<ShaderSlot, string>>;
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* The static, non-graph half of the material: plain PBR values for every
|
|
223
|
+
* slot no node drives. Captured in full because the glTF exporter does not
|
|
224
|
+
* understand node materials, so the worker rebuilds this material from
|
|
225
|
+
* scratch rather than patching whatever the exporter guessed.
|
|
226
|
+
*/
|
|
227
|
+
type ShaderMaterialProperties = {
|
|
228
|
+
color?: string;
|
|
229
|
+
roughness?: number;
|
|
230
|
+
metalness?: number;
|
|
231
|
+
emissive?: string;
|
|
232
|
+
emissiveIntensity?: number;
|
|
233
|
+
opacity?: number;
|
|
234
|
+
transparent?: boolean;
|
|
235
|
+
alphaTest?: number;
|
|
236
|
+
ior?: number;
|
|
237
|
+
transmission?: number;
|
|
238
|
+
thickness?: number;
|
|
239
|
+
attenuationColor?: string;
|
|
240
|
+
attenuationDistance?: number;
|
|
241
|
+
clearcoat?: number;
|
|
242
|
+
clearcoatRoughness?: number;
|
|
243
|
+
sheen?: number;
|
|
244
|
+
sheenColor?: string;
|
|
245
|
+
sheenRoughness?: number;
|
|
246
|
+
specularIntensity?: number;
|
|
247
|
+
specularColor?: string;
|
|
248
|
+
iridescence?: number;
|
|
249
|
+
iridescenceIOR?: number;
|
|
250
|
+
anisotropy?: number;
|
|
251
|
+
doubleSided?: boolean;
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* One node material, ready to rebuild. Matched to the mesh by the material's
|
|
255
|
+
* uuid, which the SDK stamps through the exporter the same way object ids
|
|
256
|
+
* are.
|
|
257
|
+
*/
|
|
258
|
+
type MaterialShaderSpec = {
|
|
259
|
+
materialId: string;
|
|
260
|
+
name?: string;
|
|
261
|
+
/** Which BSDF family the material declared. */
|
|
262
|
+
base: 'standard' | 'physical' | 'basic';
|
|
263
|
+
properties?: ShaderMaterialProperties;
|
|
264
|
+
graph: ShaderGraph;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* The scene manifest: a GLB plus a versioned sidecar.
|
|
269
|
+
*
|
|
270
|
+
* glTF carries geometry, materials, textures, cameras, lights, skins and
|
|
271
|
+
* morph targets well. What it does not carry is the state the running
|
|
272
|
+
* application was in at the moment somebody pressed Render: which objects
|
|
273
|
+
* were hidden, which of four cameras was live, what the renderer's tone
|
|
274
|
+
* mapping was set to, which materials the SDK could not translate and what
|
|
275
|
+
* the developer wants done about them. So the GLB is an asset and this is
|
|
276
|
+
* the contract. `protocolVersion` is versioned independently of the SDK, of
|
|
277
|
+
* the renderer and of the material translator, which is what makes a render
|
|
278
|
+
* from six months ago reproducible.
|
|
279
|
+
*
|
|
280
|
+
* Everything here is JSON (no class instances, no functions) because it
|
|
281
|
+
* travels from the browser through the control plane to the render worker
|
|
282
|
+
* and is stored verbatim for replay.
|
|
283
|
+
*/
|
|
284
|
+
|
|
285
|
+
/** `sha256:<64 hex>`: how every immutable blob is named. See asset.ts. */
|
|
286
|
+
type AssetHash = string;
|
|
287
|
+
type Vec3 = [number, number, number];
|
|
288
|
+
/** glTF order: x, y, z, w. Any conversion to another order happens in the worker. */
|
|
289
|
+
type Quat = [number, number, number, number];
|
|
290
|
+
type Matrix4 = number[];
|
|
291
|
+
/**
|
|
292
|
+
* The active camera.
|
|
293
|
+
*
|
|
294
|
+
* Written out in full rather than referenced into the GLB by name: a live
|
|
295
|
+
* R3F camera is frequently not in the scene graph the exporter walked (it
|
|
296
|
+
* is held by `useThree`, or by drei's controls), so it travels as data.
|
|
297
|
+
*/
|
|
298
|
+
type CameraSpec = {
|
|
299
|
+
type: 'perspective';
|
|
300
|
+
/** Vertical FOV in degrees, matching three's `PerspectiveCamera.fov`. */
|
|
301
|
+
fov: number;
|
|
302
|
+
/**
|
|
303
|
+
* The viewport's aspect ratio at capture time.
|
|
304
|
+
*
|
|
305
|
+
* The render matches vertical fov, so a wider output shows more at the
|
|
306
|
+
* sides rather than cropping the top and bottom. Recorded so the
|
|
307
|
+
* control plane can default the output to the viewport's own shape.
|
|
308
|
+
*/
|
|
309
|
+
aspect?: number;
|
|
310
|
+
near: number;
|
|
311
|
+
far: number;
|
|
312
|
+
position: Vec3;
|
|
313
|
+
quaternion: Quat;
|
|
314
|
+
/** three's `.zoom`, which scales the projection and is easy to forget. */
|
|
315
|
+
zoom?: number;
|
|
316
|
+
/** Present when the developer named a camera; only ever a label. */
|
|
317
|
+
name?: string;
|
|
318
|
+
/**
|
|
319
|
+
* Depth of field, off unless present. `fStop` is a full-frame f-number
|
|
320
|
+
* at this `fov`; `focalDistance` is in metres along the view axis.
|
|
321
|
+
* `target` is the world point it was measured to: a worker that reads
|
|
322
|
+
* it keeps that point sharp on every frame of a clip, and one that
|
|
323
|
+
* does not still focuses the first frame right.
|
|
324
|
+
*/
|
|
325
|
+
focus?: {
|
|
326
|
+
fStop: number;
|
|
327
|
+
focalDistance: number;
|
|
328
|
+
target?: Vec3;
|
|
329
|
+
};
|
|
330
|
+
} | {
|
|
331
|
+
type: 'orthographic';
|
|
332
|
+
left: number;
|
|
333
|
+
right: number;
|
|
334
|
+
top: number;
|
|
335
|
+
bottom: number;
|
|
336
|
+
near: number;
|
|
337
|
+
far: number;
|
|
338
|
+
position: Vec3;
|
|
339
|
+
quaternion: Quat;
|
|
340
|
+
zoom?: number;
|
|
341
|
+
name?: string;
|
|
342
|
+
};
|
|
343
|
+
/**
|
|
344
|
+
* What the application had done to the scene by render time.
|
|
345
|
+
*
|
|
346
|
+
* Visibility is an explicit hidden list rather than "export only what is
|
|
347
|
+
* visible" on purpose: the GLB is content-addressed and cached, and a
|
|
348
|
+
* configurator that toggles one part between renders would otherwise miss
|
|
349
|
+
* the cache every time. Ship one GLB with every variant in it, then hide
|
|
350
|
+
* per render: the first render uploads the model, the next one uploads a
|
|
351
|
+
* few KB.
|
|
352
|
+
*/
|
|
353
|
+
type RuntimeState = {
|
|
354
|
+
/** Object ids (see `objectId` in capture) the SDK found with `visible === false`. */
|
|
355
|
+
hiddenObjectIds?: string[];
|
|
356
|
+
/**
|
|
357
|
+
* Transform overrides, for objects the application moved after export or
|
|
358
|
+
* that were exported from a shared cached GLB.
|
|
359
|
+
*/
|
|
360
|
+
transforms?: Array<{
|
|
361
|
+
objectId: string;
|
|
362
|
+
position?: Vec3;
|
|
363
|
+
quaternion?: Quat;
|
|
364
|
+
scale?: Vec3;
|
|
365
|
+
}>;
|
|
366
|
+
/** Morph target influences, by object. */
|
|
367
|
+
morphs?: Array<{
|
|
368
|
+
objectId: string;
|
|
369
|
+
influences: number[];
|
|
370
|
+
}>;
|
|
371
|
+
/** Current skeletal pose as bone-local matrices. */
|
|
372
|
+
poses?: Array<{
|
|
373
|
+
objectId: string;
|
|
374
|
+
boneMatrices: Matrix4[];
|
|
375
|
+
}>;
|
|
376
|
+
/**
|
|
377
|
+
* `InstancedMesh` transforms. Written here rather than baked into the GLB
|
|
378
|
+
* so changing a layout does not re-upload the geometry.
|
|
379
|
+
*
|
|
380
|
+
* `matrices` is flat: `count * 16` numbers, column-major, exactly as three
|
|
381
|
+
* stores `instanceMatrix.array`. Nested arrays would add two bytes of JSON
|
|
382
|
+
* per instance, which for a large scatter is a measurable part of the
|
|
383
|
+
* manifest.
|
|
384
|
+
*/
|
|
385
|
+
instances?: Array<{
|
|
386
|
+
objectId: string;
|
|
387
|
+
count: number;
|
|
388
|
+
matrices: number[];
|
|
389
|
+
}>;
|
|
390
|
+
/**
|
|
391
|
+
* Opaque application state, echoed back on the render record. Never read
|
|
392
|
+
* by the renderer; it exists so a stored render can say which sofa.
|
|
393
|
+
*/
|
|
394
|
+
productState?: Record<string, unknown>;
|
|
395
|
+
};
|
|
396
|
+
type EnvironmentSpec = {
|
|
397
|
+
/** An HDRI/EXR the SDK found on `scene.environment`, content-addressed. */
|
|
398
|
+
hdri?: AssetHash;
|
|
399
|
+
/** three's `Scene.environmentIntensity`. */
|
|
400
|
+
intensity?: number;
|
|
401
|
+
/** three's `Scene.environmentRotation`, radians, XYZ euler. */
|
|
402
|
+
rotation?: Vec3;
|
|
403
|
+
/**
|
|
404
|
+
* The scene's `AmbientLight`s as one flat term. It ADDS to the HDRI, as it
|
|
405
|
+
* does in three, and like three's it lights diffuse surfaces only: a
|
|
406
|
+
* mirror reflects the environment and nothing of this.
|
|
407
|
+
*/
|
|
408
|
+
ambient?: {
|
|
409
|
+
color: string;
|
|
410
|
+
intensity: number;
|
|
411
|
+
};
|
|
412
|
+
/**
|
|
413
|
+
* Background. `transparent` is the default: a still that composites is
|
|
414
|
+
* worth more to a catalog pipeline than one with a baked backdrop.
|
|
415
|
+
*/
|
|
416
|
+
background?: {
|
|
417
|
+
type: 'transparent';
|
|
418
|
+
} | {
|
|
419
|
+
type: 'color';
|
|
420
|
+
color: string;
|
|
421
|
+
} | {
|
|
422
|
+
type: 'environment';
|
|
423
|
+
blurriness?: number;
|
|
424
|
+
};
|
|
425
|
+
/** The scene's fog, when it has one the renderer can trace. */
|
|
426
|
+
fog?: FogSpec;
|
|
427
|
+
};
|
|
428
|
+
/**
|
|
429
|
+
* three's `FogExp2`, which the renderer traces as a volume filling the scene:
|
|
430
|
+
* air that scatters the light passing through it, so a lamp or an emissive
|
|
431
|
+
* mesh sits in a glow and throws visible beams.
|
|
432
|
+
*
|
|
433
|
+
* `density` is three's own number. three fades a surface by
|
|
434
|
+
* `exp(-(density · d)²)`; a real volume thins light by `exp(-σ · d)`. The
|
|
435
|
+
* two cannot agree at every distance, so the renderer uses σ = `density`,
|
|
436
|
+
* which makes them agree where three's fog has taken all but 1/e of the
|
|
437
|
+
* surface: nearer than that the render is a little foggier than the
|
|
438
|
+
* viewport, further a little clearer.
|
|
439
|
+
*
|
|
440
|
+
* `color` is the fog's TINT (`#rrggbb`, as three holds it): its hue colors
|
|
441
|
+
* the scattered light, its brightness is ignored. How bright traced fog
|
|
442
|
+
* looks is decided by the light in it, which a rasterizer's fog color has to
|
|
443
|
+
* guess. A linear `Fog` has no volume it corresponds to and is not carried;
|
|
444
|
+
* the compatibility report says so.
|
|
445
|
+
*/
|
|
446
|
+
type FogSpec = {
|
|
447
|
+
color: string;
|
|
448
|
+
density: number;
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* Lights the SDK lifted out of the scene graph.
|
|
452
|
+
*
|
|
453
|
+
* Exported here as well as in the GLB because glTF's punctual-lights
|
|
454
|
+
* extension has no notion of three's intensity-unit drift, no area lights
|
|
455
|
+
* and no IES. The sidecar is where the good version lives and the GLB copy
|
|
456
|
+
* is the fallback.
|
|
457
|
+
*/
|
|
458
|
+
type LightSpec = {
|
|
459
|
+
objectId: string;
|
|
460
|
+
type: 'directional' | 'point' | 'spot' | 'area' | 'hemisphere';
|
|
461
|
+
color: string;
|
|
462
|
+
/**
|
|
463
|
+
* three's own value, unconverted. The worker owns the unit conversion so
|
|
464
|
+
* the physics is written down in one place.
|
|
465
|
+
*/
|
|
466
|
+
intensity: number;
|
|
467
|
+
/**
|
|
468
|
+
* World position. A hemisphere light's is its up axis instead (three
|
|
469
|
+
* normalizes it; absent is `[0, 1, 0]`).
|
|
470
|
+
*/
|
|
471
|
+
position?: Vec3;
|
|
472
|
+
/**
|
|
473
|
+
* World point a directional, spot or area light aims at, as three's
|
|
474
|
+
* `light.target` (or `lookAt`) does. Absent aims a directional or spot
|
|
475
|
+
* light at the origin and leaves an area light facing straight down.
|
|
476
|
+
*/
|
|
477
|
+
target?: Vec3;
|
|
478
|
+
/**
|
|
479
|
+
* Hemisphere lights only: the color from below (`groundColor`). `color` is
|
|
480
|
+
* the sky. Absent is black, a sky with no ground bounce.
|
|
481
|
+
*/
|
|
482
|
+
groundColor?: string;
|
|
483
|
+
distance?: number;
|
|
484
|
+
decay?: number;
|
|
485
|
+
angle?: number;
|
|
486
|
+
penumbra?: number;
|
|
487
|
+
/** Area lights only, in world units. */
|
|
488
|
+
size?: {
|
|
489
|
+
width: number;
|
|
490
|
+
height: number;
|
|
491
|
+
};
|
|
492
|
+
/** Angular diameter in degrees: the shadow-softness dial for a sun. */
|
|
493
|
+
angleDeg?: number;
|
|
494
|
+
/** An IES profile, content-addressed. */
|
|
495
|
+
ies?: AssetHash;
|
|
496
|
+
castShadow?: boolean;
|
|
497
|
+
};
|
|
498
|
+
/**
|
|
499
|
+
* The renderer's own color settings, copied verbatim.
|
|
500
|
+
*
|
|
501
|
+
* This is the difference between a render that looks like the developer's
|
|
502
|
+
* app and one that is subtly, unfixably wrong. three's defaults changed in
|
|
503
|
+
* r152 and again in r155; reading the live renderer beats assuming any of
|
|
504
|
+
* them.
|
|
505
|
+
*/
|
|
506
|
+
type ColorSpec = {
|
|
507
|
+
outputColorSpace: 'srgb' | 'srgb-linear' | 'display-p3';
|
|
508
|
+
toneMapping: 'none' | 'linear' | 'reinhard' | 'cineon' | 'aces-filmic' | 'agx' | 'neutral';
|
|
509
|
+
toneMappingExposure: number;
|
|
510
|
+
};
|
|
511
|
+
/**
|
|
512
|
+
* What to do with a material the renderer cannot be given honestly.
|
|
513
|
+
*
|
|
514
|
+
* Arbitrary GLSL does not translate, and the answer is to say so rather
|
|
515
|
+
* than quietly render gray. A fallback is the developer's explicit
|
|
516
|
+
* override, from `<Bakery3Material render={...}>` or from
|
|
517
|
+
* `material.userData.bakery3.fallback`.
|
|
518
|
+
*/
|
|
519
|
+
type MaterialFallback = {
|
|
520
|
+
/** The material's uuid in the exported GLB. */
|
|
521
|
+
materialId: string;
|
|
522
|
+
type: 'physical';
|
|
523
|
+
color?: string;
|
|
524
|
+
metalness?: number;
|
|
525
|
+
roughness?: number;
|
|
526
|
+
clearcoat?: number;
|
|
527
|
+
clearcoatRoughness?: number;
|
|
528
|
+
transmission?: number;
|
|
529
|
+
thickness?: number;
|
|
530
|
+
ior?: number;
|
|
531
|
+
emissive?: string;
|
|
532
|
+
emissiveIntensity?: number;
|
|
533
|
+
opacity?: number;
|
|
534
|
+
sheen?: number;
|
|
535
|
+
sheenColor?: string;
|
|
536
|
+
iridescence?: number;
|
|
537
|
+
anisotropy?: number;
|
|
538
|
+
};
|
|
539
|
+
type CompatibilitySeverity = 'info' | 'warning' | 'error';
|
|
540
|
+
/**
|
|
541
|
+
* One thing the SDK could not promise to reproduce: what it is, where it
|
|
542
|
+
* lives in the tree, why, and the fix, without opening a debugger.
|
|
543
|
+
*/
|
|
544
|
+
type CompatibilityIssue = {
|
|
545
|
+
code: string;
|
|
546
|
+
severity: CompatibilitySeverity;
|
|
547
|
+
message: string;
|
|
548
|
+
/** `<Car>/<Body>/<Paint>`: the path a developer can actually find. */
|
|
549
|
+
path?: string;
|
|
550
|
+
objectId?: string;
|
|
551
|
+
materialId?: string;
|
|
552
|
+
materialName?: string;
|
|
553
|
+
materialType?: string;
|
|
554
|
+
/** Deep link into the docs. */
|
|
555
|
+
docs?: string;
|
|
556
|
+
};
|
|
557
|
+
type CompatibilityReport = {
|
|
558
|
+
/** 0 to 1. Objects that will render faithfully, weighted by triangle count. */
|
|
559
|
+
score: number;
|
|
560
|
+
issues: CompatibilityIssue[];
|
|
561
|
+
/** Rolled up for the devtools panel and the render record. */
|
|
562
|
+
counts: {
|
|
563
|
+
info: number;
|
|
564
|
+
warning: number;
|
|
565
|
+
error: number;
|
|
566
|
+
};
|
|
567
|
+
};
|
|
568
|
+
/** Presets, not raw renderer settings. Most scenes need only these. */
|
|
569
|
+
type QualityPreset = 'preview' | 'studio' | 'ultra';
|
|
570
|
+
type OutputFormat = 'png' | 'jpeg' | 'webp' | 'exr' | 'mp4' | 'webm' | 'png-sequence';
|
|
571
|
+
/** `frames.json`, at the head of a `png-sequence` tar. */
|
|
572
|
+
type FrameSequenceIndex = {
|
|
573
|
+
fps: number;
|
|
574
|
+
frameCount: number;
|
|
575
|
+
width: number;
|
|
576
|
+
height: number;
|
|
577
|
+
bitDepth: 8 | 16;
|
|
578
|
+
/** In frame order, one per frame: `frameFileName(0)` onwards. */
|
|
579
|
+
files: string[];
|
|
580
|
+
};
|
|
581
|
+
type OutputSpec = {
|
|
582
|
+
width: number;
|
|
583
|
+
height: number;
|
|
584
|
+
format: OutputFormat;
|
|
585
|
+
/** JPEG/WebP only, 1 to 100. */
|
|
586
|
+
quality?: number;
|
|
587
|
+
/**
|
|
588
|
+
* PNG and `png-sequence` only: bits per channel, 16 unless 8 is asked
|
|
589
|
+
* for (`PNG_BIT_DEPTH_DEFAULT`).
|
|
590
|
+
*/
|
|
591
|
+
bitDepth?: 8 | 16;
|
|
592
|
+
/** Force an opaque background even when the environment is transparent. */
|
|
593
|
+
opaque?: boolean;
|
|
594
|
+
};
|
|
595
|
+
/**
|
|
596
|
+
* Escape hatch under the presets. Present so an expert is never blocked,
|
|
597
|
+
* absent from the quickstart so a beginner is never asked to become a
|
|
598
|
+
* rendering expert to get one image.
|
|
599
|
+
*/
|
|
600
|
+
type AdvancedRenderSettings = {
|
|
601
|
+
samples?: number;
|
|
602
|
+
maxBounces?: number;
|
|
603
|
+
diffuseBounces?: number;
|
|
604
|
+
glossyBounces?: number;
|
|
605
|
+
transmissionBounces?: number;
|
|
606
|
+
/** Adaptive sampling noise floor: the real cost dial once denoising is on. */
|
|
607
|
+
adaptiveThreshold?: number;
|
|
608
|
+
denoise?: boolean;
|
|
609
|
+
denoiser?: 'OPTIX' | 'OPENIMAGEDENOISE';
|
|
610
|
+
/** Pays for itself with many emitters, costs overhead with few. */
|
|
611
|
+
lightTree?: boolean;
|
|
612
|
+
/**
|
|
613
|
+
* Light that reaches a surface through glass: the bright pool under a
|
|
614
|
+
* tumbler, the focus line beneath a lens. Off by default: with no caustic
|
|
615
|
+
* paths a transmissive object casts a solid shadow, which is also what the
|
|
616
|
+
* viewport's shadow maps draw, and tracing them costs samples a product
|
|
617
|
+
* shot rarely needs.
|
|
618
|
+
*
|
|
619
|
+
* `'soft'` lets the path tracer find caustic paths itself, with the glossy
|
|
620
|
+
* filter and the indirect clamp (both of which exist to hide caustics)
|
|
621
|
+
* switched off. Unbiased, and slow to converge: a hard light through
|
|
622
|
+
* curved glass stays grainy at studio samples.
|
|
623
|
+
*
|
|
624
|
+
* `'sharp'` adds a dedicated shadow-caustic solver on top: every scene
|
|
625
|
+
* lamp casts caustics, every transmissive mesh is a caster, and every
|
|
626
|
+
* other mesh (the shadow catcher included) receives them. This is what
|
|
627
|
+
* makes a glass on a table converge at normal sample counts. It handles a
|
|
628
|
+
* single refractive object between a lamp and a surface; light from an
|
|
629
|
+
* HDRI or an emissive mesh still goes the `'soft'` way.
|
|
630
|
+
*
|
|
631
|
+
* The solver takes over lamp light through a caster rather than adding to
|
|
632
|
+
* the traced paths, and it only follows light that goes straight through.
|
|
633
|
+
* A faceted stone, whose light leaves after internal reflections, comes
|
|
634
|
+
* back under `'sharp'` with no caustic at all. Cut gems want `'soft'`.
|
|
635
|
+
*
|
|
636
|
+
* Absent is off. There is no boolean on purpose: `caustics: true` would
|
|
637
|
+
* have to mean one of the two. Changes pixels, so it is in the cache key.
|
|
638
|
+
*/
|
|
639
|
+
caustics?: 'soft' | 'sharp';
|
|
640
|
+
/**
|
|
641
|
+
* A ceiling on how bright one indirect sample may be, in scene radiance.
|
|
642
|
+
* Absent is the renderer's own default, which `caustics` switches off: a
|
|
643
|
+
* caustic's focus is a very bright indirect sample, and capping it dims
|
|
644
|
+
* the focus. That is right for a still and wrong for a clip, because the
|
|
645
|
+
* rare uncapped hit lands on different pixels every frame, the denoiser
|
|
646
|
+
* smears each one into a soft patch, and the patches boil. For caustics
|
|
647
|
+
* in motion, start at 30 (the pool under a lens is still its full
|
|
648
|
+
* brightness there) and trace larger than you publish.
|
|
649
|
+
*
|
|
650
|
+
* Set, it wins over what `caustics` would have chosen. Changes pixels, so
|
|
651
|
+
* it is in the cache key, present only when set.
|
|
652
|
+
*/
|
|
653
|
+
clampIndirect?: number;
|
|
654
|
+
/**
|
|
655
|
+
* Multiplies the converted energy of every light lifted from the scene
|
|
656
|
+
* graph (directional, point, spot, area and the ambient dome) and leaves
|
|
657
|
+
* the HDRI environment alone. The worker's unit table is physics and stays
|
|
658
|
+
* fixed; this dial is for the residual gap, where a viewport (legacy-lights
|
|
659
|
+
* intensities, usually) reads brighter than the physically converted lamps
|
|
660
|
+
* do. One scalar, so the ratio between the scene's lights is preserved.
|
|
661
|
+
* Default 1. Changes pixels, so it is in the cache key.
|
|
662
|
+
*/
|
|
663
|
+
lightScale?: number;
|
|
664
|
+
/**
|
|
665
|
+
* Ground the product with a traced shadow catcher. Defaults on for
|
|
666
|
+
* transparent/solid output and off for a visible environment background.
|
|
667
|
+
*/
|
|
668
|
+
shadowCatcher?: boolean;
|
|
669
|
+
/** Catcher size, as a multiple of the model's largest dimension. */
|
|
670
|
+
shadowPad?: number;
|
|
671
|
+
/**
|
|
672
|
+
* The renderer's view transform override. Changes pixels, so it is in the
|
|
673
|
+
* cache key. Pinning one also switches off `toneCurveEmulation`: an
|
|
674
|
+
* explicit name is a statement about what the render should look like.
|
|
675
|
+
*/
|
|
676
|
+
viewTransform?: string;
|
|
677
|
+
/**
|
|
678
|
+
* Reproduce three's tone mapping exactly instead of substituting the
|
|
679
|
+
* nearest native view transform. On by default, and it is what makes a
|
|
680
|
+
* traced frame the same color as the viewport it came from.
|
|
681
|
+
*
|
|
682
|
+
* Turn it off to get the substitution: a wider, more forgiving curve for a
|
|
683
|
+
* scene whose viewport was never the reference. Changes pixels, so it is
|
|
684
|
+
* in the cache key.
|
|
685
|
+
*/
|
|
686
|
+
toneCurveEmulation?: boolean;
|
|
687
|
+
/** Seconds. The worker stops the render rather than billing an unbounded job. */
|
|
688
|
+
timeoutSeconds?: number;
|
|
689
|
+
};
|
|
690
|
+
/**
|
|
691
|
+
* Everything one render needs, minus the bytes.
|
|
692
|
+
*
|
|
693
|
+
* Typically a few KB. The heavy things (the GLB, textures, the HDRI) are
|
|
694
|
+
* `sha256:` references that the control plane resolves against storage the
|
|
695
|
+
* SDK already filled (see asset.ts).
|
|
696
|
+
*/
|
|
697
|
+
type SceneManifest = {
|
|
698
|
+
protocolVersion: string;
|
|
699
|
+
sdkVersion: string;
|
|
700
|
+
/** The exported GLB. */
|
|
701
|
+
sceneAsset: AssetHash;
|
|
702
|
+
/**
|
|
703
|
+
* Extra blobs this scene needs that are not inside the GLB: an HDRI, an
|
|
704
|
+
* IES profile, a texture the SDK stripped out to keep the GLB cacheable.
|
|
705
|
+
*/
|
|
706
|
+
assets?: AssetHash[];
|
|
707
|
+
camera: CameraSpec;
|
|
708
|
+
runtime?: RuntimeState;
|
|
709
|
+
environment?: EnvironmentSpec;
|
|
710
|
+
lights?: LightSpec[];
|
|
711
|
+
color: ColorSpec;
|
|
712
|
+
fallbacks?: MaterialFallback[];
|
|
713
|
+
/**
|
|
714
|
+
* Node materials, as portable shader graphs (see shader.ts). The GLB still
|
|
715
|
+
* carries the mesh; the material the exporter wrote for it is a stand-in
|
|
716
|
+
* that the worker replaces wholesale with the rebuilt graph.
|
|
717
|
+
*/
|
|
718
|
+
shaders?: MaterialShaderSpec[];
|
|
719
|
+
/**
|
|
720
|
+
* Baked per-frame motion (see animation.ts). Present only for a video,
|
|
721
|
+
* and its presence is what makes this a 3.0 manifest.
|
|
722
|
+
*
|
|
723
|
+
* Not inside `runtime` on purpose: runtime is the one state the
|
|
724
|
+
* application was in when Render was pressed, and every field in it is a
|
|
725
|
+
* single value. A sequence is a different kind of thing.
|
|
726
|
+
*/
|
|
727
|
+
animation?: AnimationSpec;
|
|
728
|
+
/**
|
|
729
|
+
* A room preset around the product (see room.ts). Its geometry, lights
|
|
730
|
+
* and sky are not in the GLB or in `lights`: the worker rebuilds all of it
|
|
731
|
+
* from the published definition, so a product in a room uploads only the
|
|
732
|
+
* product. Presence makes this a 4.0 manifest.
|
|
733
|
+
*/
|
|
734
|
+
room?: RoomPresetSpec;
|
|
735
|
+
compatibility?: CompatibilityReport;
|
|
736
|
+
/**
|
|
737
|
+
* What the scene weighs, measured by the SDK at capture. The control plane
|
|
738
|
+
* prices with it (a dense scene traces slower per pixel and unwraps slower
|
|
739
|
+
* per object), so the quote a job is accepted under is the same one
|
|
740
|
+
* `estimate()` gave with the same numbers. Operational, not visual: it is
|
|
741
|
+
* outside every cache key.
|
|
742
|
+
*/
|
|
743
|
+
stats?: {
|
|
744
|
+
triangles: number;
|
|
745
|
+
/** Bytes of texture the manifest's shader graphs sample, when known. */
|
|
746
|
+
textureBytes?: number;
|
|
747
|
+
/**
|
|
748
|
+
* Distinct materials that emit light: a standard material with a
|
|
749
|
+
* non-black emissive color, an unlit material (rendered as emission), or
|
|
750
|
+
* a node material with an emission graph. The worker turns its light
|
|
751
|
+
* tree on from this. Only the SDK can count them, since the GLB's
|
|
752
|
+
* materials are opaque to the control plane.
|
|
753
|
+
*/
|
|
754
|
+
emitters?: number;
|
|
755
|
+
/**
|
|
756
|
+
* How much of the sky above the camera the scene's geometry hides, 0 for
|
|
757
|
+
* an open set and 1 inside a closed room (`SceneComplexity`). Walls keep
|
|
758
|
+
* light bouncing, which is most of what makes an interior slow to render,
|
|
759
|
+
* so the quote reads it. A preset room's stand-in counts, though it is not
|
|
760
|
+
* uploaded: it stands for the room the renderer builds.
|
|
761
|
+
*/
|
|
762
|
+
enclosure?: number;
|
|
763
|
+
/** Meshes the scene draws, instances counted: how full it is. */
|
|
764
|
+
objects?: number;
|
|
765
|
+
};
|
|
766
|
+
/**
|
|
767
|
+
* Where the scene came from. Diagnostics only, but "which three version"
|
|
768
|
+
* is the first question every support conversation starts with.
|
|
769
|
+
*/
|
|
770
|
+
source?: {
|
|
771
|
+
three?: string;
|
|
772
|
+
r3f?: string;
|
|
773
|
+
renderer?: string;
|
|
774
|
+
userAgent?: string;
|
|
775
|
+
};
|
|
776
|
+
};
|
|
777
|
+
/** A manifest plus what to make from it. This is the render request body. */
|
|
778
|
+
type RenderSpec = {
|
|
779
|
+
manifest: SceneManifest;
|
|
780
|
+
quality: QualityPreset;
|
|
781
|
+
output: OutputSpec;
|
|
782
|
+
advanced?: AdvancedRenderSettings;
|
|
783
|
+
/**
|
|
784
|
+
* Hard ceiling in USD. Exceeded at estimate time means the job is refused
|
|
785
|
+
* before it is admitted: `RenderBudgetExceeded`, not a surprise bill.
|
|
786
|
+
*/
|
|
787
|
+
maxCost?: number;
|
|
788
|
+
/** Emit a low-sample preview partway through. On by default for `studio`. */
|
|
789
|
+
preview?: boolean;
|
|
790
|
+
/** Echoed on the render record and every webhook. */
|
|
791
|
+
metadata?: Record<string, unknown>;
|
|
792
|
+
/**
|
|
793
|
+
* A resubmit with the same key returns the existing render while it is
|
|
794
|
+
* running or once it has completed, never a second charge. This is what
|
|
795
|
+
* makes the SDK's own retries safe. A key whose render failed, was
|
|
796
|
+
* cancelled or expired is free again: nothing was charged, and the
|
|
797
|
+
* resubmit renders.
|
|
798
|
+
*/
|
|
799
|
+
idempotencyKey?: string;
|
|
800
|
+
/**
|
|
801
|
+
* `high` puts this render ahead of the account's other waiting work in
|
|
802
|
+
* the same lane: the hero shot before the rest of the queue. It never
|
|
803
|
+
* jumps a lane; a person waiting still goes first.
|
|
804
|
+
*/
|
|
805
|
+
priority?: 'normal' | 'high';
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* How good a bake should be. The worker chooses sample counts and margins
|
|
810
|
+
* from this, the way `QualityPreset` maps to `QualitySettings`.
|
|
811
|
+
*/
|
|
812
|
+
type BakeQuality = 'preview' | 'standard' | 'high';
|
|
813
|
+
/** The sides a lightmap or a stand-in texture may have, in texels. */
|
|
814
|
+
declare const BAKE_TEXTURE_SIZES: readonly [256, 512, 1024, 2048];
|
|
815
|
+
type BakeTextureSize = (typeof BAKE_TEXTURE_SIZES)[number];
|
|
816
|
+
/** Atlas sides. 4096² is 16.8M texels, inside the texel ceiling on its own. */
|
|
817
|
+
declare const BAKE_ATLAS_SIZES: readonly [1024, 2048, 4096];
|
|
818
|
+
type BakeAtlasSize = (typeof BAKE_ATLAS_SIZES)[number];
|
|
819
|
+
/**
|
|
820
|
+
* One lightmap for every baked object, rather than one each.
|
|
821
|
+
*
|
|
822
|
+
* A room of sixty objects lit from sixty textures is sixty texture binds and
|
|
823
|
+
* sixty downloads; lit from one atlas it is one of each, which is what an
|
|
824
|
+
* interactive scene wants. The atlas holds the same texels the separate maps
|
|
825
|
+
* would, and is priced as they are.
|
|
826
|
+
*
|
|
827
|
+
* Every object is baked into a square of its own: the side its world-space
|
|
828
|
+
* surface earns, capped by `textureSize` and `sizes` as usual, times the
|
|
829
|
+
* square root of its `weight`, rounded to a power of two. The squares are
|
|
830
|
+
* laid into the map with no waste, and the map is filled: when the squares
|
|
831
|
+
* would cover under a quarter of it every side doubles, and when they would
|
|
832
|
+
* not fit the largest are halved until they do. So `textureSize`, `sizes`
|
|
833
|
+
* and `weights` set relative shares, kept to within a power of two, and
|
|
834
|
+
* `size` sets the texels there are to share.
|
|
835
|
+
*
|
|
836
|
+
* A hero product is `weights: { [heroId]: 4 }`: twice the side and four times
|
|
837
|
+
* the area its surface alone would earn. Weights round to the nearest power
|
|
838
|
+
* of two in side: 1.5 to 2.8 doubles it, 2.9 to 5.6 quadruples it.
|
|
839
|
+
*/
|
|
840
|
+
type BakeAtlas = {
|
|
841
|
+
size: BakeAtlasSize;
|
|
842
|
+
/** Per-object density multipliers, keyed by objectId. Default 1. */
|
|
843
|
+
weights?: Record<string, number>;
|
|
844
|
+
};
|
|
845
|
+
/**
|
|
846
|
+
* What a group of objects is replaced with in the baked scene.
|
|
847
|
+
*
|
|
848
|
+
* A lightmap keeps the developer's geometry and changes its light. That is
|
|
849
|
+
* right for a product and wrong for a bookcase of forty books at the back of
|
|
850
|
+
* the room: forty draw calls, forty lightmaps and forty unwraps to light
|
|
851
|
+
* something the viewer reads as one lit shape. A stand-in is that one shape,
|
|
852
|
+
* with the detail it lost baked on: the color of the surface it replaces, a
|
|
853
|
+
* normal map for the relief it no longer has, and the irradiance traced at
|
|
854
|
+
* the real surface. The real objects stay in the traced scene, so they still
|
|
855
|
+
* cast their shadows and bounce their light onto everything else; they are
|
|
856
|
+
* only absent from what is delivered.
|
|
857
|
+
*
|
|
858
|
+
* `'lowpoly'` the objects, shrink-wrapped into one skin and reduced to
|
|
859
|
+
* `triangles`. The default.
|
|
860
|
+
* `'box'` the box round them, turned about the vertical to fit.
|
|
861
|
+
* Right for anything that reads as a box from where it is
|
|
862
|
+
* seen: a bookcase, a cabinet, a radiator.
|
|
863
|
+
* `'billboard'` one upright picture of them that turns to face the
|
|
864
|
+
* camera. For a potted plant: half a million triangles of
|
|
865
|
+
* leaves that no lightmap suits and no box resembles.
|
|
866
|
+
* `{ objectId }` a mesh of the developer's own, in the captured scene and
|
|
867
|
+
* hidden (so nothing traces it), used as the stand-in's
|
|
868
|
+
* shape exactly as authored.
|
|
869
|
+
* `'decimate'` one object, the same object with fewer triangles: its own
|
|
870
|
+
* parts, silhouette, texture uvs and materials kept, reduced
|
|
871
|
+
* to `triangles` by collapsing edges. For the product itself
|
|
872
|
+
* (a 146k-triangle shoe), where a skin would melt laces and
|
|
873
|
+
* eyelets together and a baked color would turn metal and
|
|
874
|
+
* gloss into paint. Only its light is baked, in one pass at
|
|
875
|
+
* the real surface; the stand-in wears the replaced object's
|
|
876
|
+
* own materials at their own resolution. One entry per
|
|
877
|
+
* object.
|
|
878
|
+
*/
|
|
879
|
+
type BakeSimplifyTo = 'lowpoly' | 'box' | 'billboard' | 'decimate' | {
|
|
880
|
+
objectId: string;
|
|
881
|
+
};
|
|
882
|
+
type BakeSimplify = {
|
|
883
|
+
/**
|
|
884
|
+
* objectIds the stand-in replaces. They are not in `include`: a replaced
|
|
885
|
+
* object gets no lightmap of its own, which is where the saving is.
|
|
886
|
+
*/
|
|
887
|
+
objects: string[];
|
|
888
|
+
to: BakeSimplifyTo;
|
|
889
|
+
/** `'lowpoly'` only: the triangle budget. Default `BAKE_SIMPLIFY_TRIANGLES`. */
|
|
890
|
+
triangles?: number;
|
|
891
|
+
/** The stand-in's texture side. Default `textureSize`. */
|
|
892
|
+
textureSize?: BakeTextureSize;
|
|
893
|
+
};
|
|
894
|
+
/**
|
|
895
|
+
* A plane under the baked objects carrying the difference they make to the
|
|
896
|
+
* light on it, and nothing else.
|
|
897
|
+
*
|
|
898
|
+
* A preset room is the same room for every customer, so its lightmaps are a
|
|
899
|
+
* library asset, baked once and served from the preset library
|
|
900
|
+
* (`BakeBundle.room`). What that prebaked room cannot know is the product
|
|
901
|
+
* standing in it: the contact shadow under a vase is what makes it look like
|
|
902
|
+
* it is in the room rather than pasted over it.
|
|
903
|
+
*
|
|
904
|
+
* So the bake traces one small plane just above the floor twice, with the
|
|
905
|
+
* product and its stand present and again with them hidden, and ships the
|
|
906
|
+
* ratio: 1 where they changed nothing, darker where they blocked light. The
|
|
907
|
+
* viewer lays it over the floor and multiplies. A ratio, not irradiance,
|
|
908
|
+
* because it has to compose with a floor lightmap traced at another time and
|
|
909
|
+
* another resolution.
|
|
910
|
+
*
|
|
911
|
+
* It only darkens. Light the product throws onto the floor (a red vase's
|
|
912
|
+
* bounce) is in neither the prebaked room nor this plane; that is the
|
|
913
|
+
* approximation this mode makes.
|
|
914
|
+
*/
|
|
915
|
+
type BakeShadow = {
|
|
916
|
+
/**
|
|
917
|
+
* Scene units, Y-up: the plane's centre, on the surface it lies over.
|
|
918
|
+
*
|
|
919
|
+
* Absent is the usual case: the worker measures the footprint of whatever
|
|
920
|
+
* casts, then finds the surface beneath with rays cast straight down and
|
|
921
|
+
* the casters hidden. A plinth is often modelled a centimetre into the
|
|
922
|
+
* floor, so the bottom of its bounds is inside the slab, where a plane
|
|
923
|
+
* would be lit in neither trace.
|
|
924
|
+
*/
|
|
925
|
+
center?: [number, number, number];
|
|
926
|
+
/**
|
|
927
|
+
* Metres. Absent takes the casters' footprint plus the room a shadow needs
|
|
928
|
+
* to spread into.
|
|
929
|
+
*/
|
|
930
|
+
width?: number;
|
|
931
|
+
height?: number;
|
|
932
|
+
/**
|
|
933
|
+
* How far above the surface the plane sits, in metres. Small enough to
|
|
934
|
+
* read as contact, large enough that nothing z-fights with the floor.
|
|
935
|
+
*/
|
|
936
|
+
lift?: number;
|
|
937
|
+
/** Texels a side. Default `BAKE_SHADOW_SIZE`. */
|
|
938
|
+
textureSize?: BakeTextureSize;
|
|
939
|
+
/**
|
|
940
|
+
* objectIds whose shadow this is: hidden for the second trace. Absent
|
|
941
|
+
* means every object the bake includes.
|
|
942
|
+
*/
|
|
943
|
+
cast?: string[];
|
|
944
|
+
/**
|
|
945
|
+
* objectIds the plane is placed beneath and sized to cover. Absent means
|
|
946
|
+
* the casters, which is the usual case.
|
|
947
|
+
*
|
|
948
|
+
* The two lists come apart when a product stands on something this bake is
|
|
949
|
+
* lightmapping anyway. A vase on a plinth casts on the plinth's top, and
|
|
950
|
+
* the plinth's own lightmap carries that. What the plinth must not do is
|
|
951
|
+
* cast into this plane: the prebaked room was lit with the plinth standing
|
|
952
|
+
* in it, so its shadow is already on that floor, and casting it again
|
|
953
|
+
* would lay it down twice.
|
|
954
|
+
*
|
|
955
|
+
* So the plinth spans but does not cast. It is present in both traces and
|
|
956
|
+
* divides out of the ratio exactly, leaving only what the vase adds; and
|
|
957
|
+
* because it is hidden while the surface beneath is looked for, the plane
|
|
958
|
+
* still lands on the floor rather than on the plinth's top.
|
|
959
|
+
*/
|
|
960
|
+
spans?: string[];
|
|
961
|
+
};
|
|
962
|
+
/**
|
|
963
|
+
* What the bake produced for `BakeSettings.shadow`.
|
|
964
|
+
*
|
|
965
|
+
* `uri` is a ratio in 0..1 per channel, linear, one value per texel: what
|
|
966
|
+
* fraction of the light still arrives with the product there. Lay the plane
|
|
967
|
+
* at `center` + `lift` on its up axis, map this across it, and multiply.
|
|
968
|
+
*/
|
|
969
|
+
type BakedShadow = {
|
|
970
|
+
uri: string;
|
|
971
|
+
format: 'exr' | 'png';
|
|
972
|
+
size: number;
|
|
973
|
+
center: [number, number, number];
|
|
974
|
+
width: number;
|
|
975
|
+
height: number;
|
|
976
|
+
lift: number;
|
|
977
|
+
/**
|
|
978
|
+
* The darkest ratio in the map, for a panel and for a sanity check: a
|
|
979
|
+
* shadow that came out 1.0 everywhere means nothing was cast.
|
|
980
|
+
*/
|
|
981
|
+
darkest: number;
|
|
982
|
+
};
|
|
983
|
+
/**
|
|
984
|
+
* What to bake and how well.
|
|
985
|
+
*
|
|
986
|
+
* `include` is explicit and authoritative: the SDK decides which objects can
|
|
987
|
+
* take a lightmap (it can see the live materials; the worker cannot) and the
|
|
988
|
+
* worker bakes exactly that list. An id the worker cannot find is a refusal
|
|
989
|
+
* with the id and a fix, never a silent skip.
|
|
990
|
+
*/
|
|
991
|
+
type BakeSettings = {
|
|
992
|
+
quality: BakeQuality;
|
|
993
|
+
/**
|
|
994
|
+
* Per-object resolution ceiling. The worker may choose a smaller size for
|
|
995
|
+
* a small object (by world-space surface area) so a screw does not cost
|
|
996
|
+
* what a wall costs. It never chooses larger.
|
|
997
|
+
*/
|
|
998
|
+
textureSize: BakeTextureSize;
|
|
999
|
+
/** objectIds to bake, from the same id space as `RuntimeState`. */
|
|
1000
|
+
include: string[];
|
|
1001
|
+
/** Per-object override of `textureSize`, keyed by objectId. */
|
|
1002
|
+
sizes?: Record<string, BakeTextureSize>;
|
|
1003
|
+
/**
|
|
1004
|
+
* Bake every object into one shared lightmap of `atlas.size`² texels
|
|
1005
|
+
* instead of one texture each: see `BakeAtlas`. `textureSize` and `sizes`
|
|
1006
|
+
* then set each object's relative share of the map rather than its own
|
|
1007
|
+
* texture.
|
|
1008
|
+
*/
|
|
1009
|
+
atlas?: BakeAtlas;
|
|
1010
|
+
/** Seam dilation in pixels. Default 4. */
|
|
1011
|
+
margin?: number;
|
|
1012
|
+
/**
|
|
1013
|
+
* Also produce the portable baked GLB: every baked object with an unlit
|
|
1014
|
+
* material (`KHR_materials_unlit`) whose one texture is the finished
|
|
1015
|
+
* surface (color, direct and bounced light, reflections as seen from
|
|
1016
|
+
* `view`, through the scene's tone curve). Loads anywhere with no lights
|
|
1017
|
+
* and no SDK. A second bake pass per object, so it roughly doubles GPU
|
|
1018
|
+
* time and is opt-in.
|
|
1019
|
+
*/
|
|
1020
|
+
deliverGlb?: boolean;
|
|
1021
|
+
/**
|
|
1022
|
+
* Objects whose `uv1` is the developer's own layout, not one a previous
|
|
1023
|
+
* bake wrote. Present, the worker may use an object's own layout instead
|
|
1024
|
+
* of unwrapping it: its `uv1` when listed here, its texture `uv` when the
|
|
1025
|
+
* mesh is too fragmented or too dense to unwrap well. Either is used only
|
|
1026
|
+
* when it passes the worker's test (inside the unit square, no stacked
|
|
1027
|
+
* islands, enough texels an island, an even density), and the result says
|
|
1028
|
+
* so with `BakeUv` `channel`. Absent, every object is unwrapped.
|
|
1029
|
+
*/
|
|
1030
|
+
existingUv?: {
|
|
1031
|
+
uv1: string[];
|
|
1032
|
+
};
|
|
1033
|
+
/**
|
|
1034
|
+
* Where the scene is looked at from, for seam placement only.
|
|
1035
|
+
*
|
|
1036
|
+
* The unwrap puts island boundaries on the side of each object that faces
|
|
1037
|
+
* away from this point, on its underside, or where it is hidden behind
|
|
1038
|
+
* other geometry, never across the surface this viewpoint sees. Nothing
|
|
1039
|
+
* about the lighting depends on it.
|
|
1040
|
+
*
|
|
1041
|
+
* It is not the manifest camera. The camera is excluded from the bake
|
|
1042
|
+
* cache key so that orbiting and baking again is free, and a seam layout
|
|
1043
|
+
* that followed the exact camera would make every orbit a re-cut. The SDK
|
|
1044
|
+
* sends the camera quantized (`quantizeBakeView`): direction in 30° steps,
|
|
1045
|
+
* distance in octaves around the baked objects, so nearby viewpoints share
|
|
1046
|
+
* a bake and only a real change of side re-cuts.
|
|
1047
|
+
*
|
|
1048
|
+
* Absent: seams prefer the underside and hard edges alone.
|
|
1049
|
+
*/
|
|
1050
|
+
view?: BakeView;
|
|
1051
|
+
/**
|
|
1052
|
+
* Where the reflection probe is taken, and what is hidden from it.
|
|
1053
|
+
*
|
|
1054
|
+
* Every bake delivers one. The lightmap replaces a baked surface's
|
|
1055
|
+
* diffuse, but the objects the bake is for (the product kept live on its
|
|
1056
|
+
* plinth, so it can turn and change finish) still reflect the scene's own
|
|
1057
|
+
* environment map, which in a baked room is not the room. The probe is the
|
|
1058
|
+
* room as seen from where those objects stand, with those objects hidden
|
|
1059
|
+
* so they do not reflect themselves, and the SDK puts it on their
|
|
1060
|
+
* materials as `envMap`.
|
|
1061
|
+
*
|
|
1062
|
+
* The SDK decides both fields: the objects it hides are the ones that
|
|
1063
|
+
* could have been baked and were deliberately left out. An object that
|
|
1064
|
+
* cannot take a lightmap at all (a glass sphere, an instanced hedge) is
|
|
1065
|
+
* scenery and stays in the map, so the product reflects the glass beside
|
|
1066
|
+
* it. Absent, the worker takes the probe from the center of the baked
|
|
1067
|
+
* objects and hides nothing.
|
|
1068
|
+
*/
|
|
1069
|
+
probe?: BakeProbe;
|
|
1070
|
+
/**
|
|
1071
|
+
* Groups of objects to deliver as a stand-in rather than light as they
|
|
1072
|
+
* are: see `BakeSimplify`. In request order; stand-in `n` of the bundle is
|
|
1073
|
+
* entry `n` here.
|
|
1074
|
+
*/
|
|
1075
|
+
simplify?: BakeSimplify[];
|
|
1076
|
+
/**
|
|
1077
|
+
* Trace a shadow plane under the baked objects: see `BakeShadow`. What a
|
|
1078
|
+
* bake of a product standing in a prebaked room needs, and the only part
|
|
1079
|
+
* of that room the bake has to pay for.
|
|
1080
|
+
*/
|
|
1081
|
+
shadow?: BakeShadow;
|
|
1082
|
+
/**
|
|
1083
|
+
* How a preset room in the scene is lit.
|
|
1084
|
+
*
|
|
1085
|
+
* `'full'` (the default) bakes the room's own surfaces along with
|
|
1086
|
+
* everything else.
|
|
1087
|
+
*
|
|
1088
|
+
* `'preset'` says the room is unmodified, so its lightmaps are a library
|
|
1089
|
+
* asset and this bake covers only what the library cannot know: the
|
|
1090
|
+
* product, the stand it is on, and the shadow it casts. The SDK only asks
|
|
1091
|
+
* for it when the room really is unmodified, and the worker records which
|
|
1092
|
+
* room the result stands on (`BakeBundle.room`).
|
|
1093
|
+
*/
|
|
1094
|
+
room?: 'full' | 'preset';
|
|
1095
|
+
};
|
|
1096
|
+
/** A viewpoint, in the scene's own (Y-up, meters) coordinates. */
|
|
1097
|
+
type BakeView = {
|
|
1098
|
+
position: [number, number, number];
|
|
1099
|
+
};
|
|
1100
|
+
/** The reflection probe's request: where, and what not to see. */
|
|
1101
|
+
type BakeProbe = {
|
|
1102
|
+
/** Scene units, Y-up: the center of the objects the probe is for. */
|
|
1103
|
+
position: [number, number, number];
|
|
1104
|
+
/** objectIds hidden from the probe: the objects it will be applied to. */
|
|
1105
|
+
hide: string[];
|
|
1106
|
+
};
|
|
1107
|
+
/**
|
|
1108
|
+
* A manifest plus what to bake from it: the bake request body. The manifest
|
|
1109
|
+
* is the ordinary capture, camera included but unused by the bake itself, so
|
|
1110
|
+
* two captures that differ only by viewpoint hit the same bake cache entry.
|
|
1111
|
+
*/
|
|
1112
|
+
type BakeSpec = {
|
|
1113
|
+
manifest: SceneManifest;
|
|
1114
|
+
bake: BakeSettings;
|
|
1115
|
+
advanced?: {
|
|
1116
|
+
timeoutSeconds?: number;
|
|
1117
|
+
};
|
|
1118
|
+
/**
|
|
1119
|
+
* Triangles in the objects `bake.include` names, for the quote only.
|
|
1120
|
+
*
|
|
1121
|
+
* The estimate has two triangle terms: syncing the whole scene (priced from
|
|
1122
|
+
* the manifest's own count) and unwrapping the objects being baked (priced
|
|
1123
|
+
* from this). Without it the unwrap term is priced against the whole
|
|
1124
|
+
* scene, which over-quotes a partial bake of a big room. The SDK sends it;
|
|
1125
|
+
* a request without it is quoted the higher way.
|
|
1126
|
+
*
|
|
1127
|
+
* Deliberately not inside `bake`: that object is the bake cache key, and a
|
|
1128
|
+
* number that only moves a price must not move what is cached.
|
|
1129
|
+
*/
|
|
1130
|
+
bakedTriangles?: number;
|
|
1131
|
+
/**
|
|
1132
|
+
* Hard ceiling in USD, refused at estimate time. The same contract as
|
|
1133
|
+
* `RenderSpec.maxCost`.
|
|
1134
|
+
*/
|
|
1135
|
+
maxCost?: number;
|
|
1136
|
+
metadata?: Record<string, unknown>;
|
|
1137
|
+
idempotencyKey?: string;
|
|
1138
|
+
};
|
|
1139
|
+
/**
|
|
1140
|
+
* The second UV set for one object, in the live geometry's own vertex order.
|
|
1141
|
+
*
|
|
1142
|
+
* `values` alone: `2 * vertexCount` floats, writable straight into a `uv1`
|
|
1143
|
+
* BufferAttribute on the existing geometry. The common case, and the one
|
|
1144
|
+
* that leaves the developer's geometry object untouched.
|
|
1145
|
+
*
|
|
1146
|
+
* `topology`: the unwrap needed vertex splits. `vertexSource[i]` names the
|
|
1147
|
+
* original vertex that new vertex `i` was copied from, so every original
|
|
1148
|
+
* attribute (custom ones included) can be gathered through it and nothing
|
|
1149
|
+
* the developer authored is lost in the rebuild. `index` is the new triangle
|
|
1150
|
+
* list over the new vertices.
|
|
1151
|
+
*/
|
|
1152
|
+
type BakeUv = {
|
|
1153
|
+
values: number[];
|
|
1154
|
+
} | {
|
|
1155
|
+
topology: {
|
|
1156
|
+
vertexSource: number[];
|
|
1157
|
+
index: number[];
|
|
1158
|
+
values: number[];
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* The lightmap is laid on a uv set the mesh already has: `0` is its
|
|
1163
|
+
* texture uv (three's `uv`, glTF `TEXCOORD_0`), `1` its second set (`uv1`,
|
|
1164
|
+
* `TEXCOORD_1`). Nothing is shipped and nothing is rebuilt: the SDK points
|
|
1165
|
+
* the lightmap at that attribute (`texture.channel`). Such a lightmap is
|
|
1166
|
+
* written in the glTF orientation (v = 0 at the top), the one the
|
|
1167
|
+
* attribute is in; every unwrap the worker ships itself is bottom-up.
|
|
1168
|
+
* Only sent to an SDK that asked for it (`BakeSettings.existingUv`).
|
|
1169
|
+
*/
|
|
1170
|
+
| {
|
|
1171
|
+
channel: 0 | 1;
|
|
1172
|
+
};
|
|
1173
|
+
/**
|
|
1174
|
+
* One baked object. `uri` is resolved against the bundle's own location: a
|
|
1175
|
+
* relative path on disk, an absolute URL from a server.
|
|
1176
|
+
*/
|
|
1177
|
+
type BakedObject = {
|
|
1178
|
+
objectId: string;
|
|
1179
|
+
/** The object's name at capture time, for humans reading a bundle. */
|
|
1180
|
+
name?: string;
|
|
1181
|
+
/** HDR diffuse irradiance (direct + indirect, no albedo), linear. */
|
|
1182
|
+
lightmap: {
|
|
1183
|
+
uri: string;
|
|
1184
|
+
format: 'exr' | 'png';
|
|
1185
|
+
size: number;
|
|
1186
|
+
};
|
|
1187
|
+
/** Small LDR thumbnail for pickers and panels. */
|
|
1188
|
+
preview?: {
|
|
1189
|
+
uri: string;
|
|
1190
|
+
};
|
|
1191
|
+
uv: BakeUv;
|
|
1192
|
+
/**
|
|
1193
|
+
* How the surface was cut, measured by the worker, so a panel can say
|
|
1194
|
+
* "2 islands, seams out of view" and mean it.
|
|
1195
|
+
*
|
|
1196
|
+
* `cut: 'hidden'` is the viewpoint-aware layout; `'angle'` is the plain
|
|
1197
|
+
* angle-limited fallback, used when the hidden-seam layout failed its
|
|
1198
|
+
* checks for this object (the job log says so too). `seamsInView` is the
|
|
1199
|
+
* fraction of seam length across smooth surface whose both sides face
|
|
1200
|
+
* `BakeSettings.view`; 0 is the goal. Seams on creases are not counted,
|
|
1201
|
+
* because they do not show.
|
|
1202
|
+
*/
|
|
1203
|
+
layout?: {
|
|
1204
|
+
islands: number;
|
|
1205
|
+
seamsInView?: number;
|
|
1206
|
+
/**
|
|
1207
|
+
* `'uv0'`/`'uv1'`: the object's own uv set, chosen over an unwrap
|
|
1208
|
+
* because it passed the worker's layout test.
|
|
1209
|
+
*/
|
|
1210
|
+
cut: 'hidden' | 'angle' | 'uv0' | 'uv1';
|
|
1211
|
+
};
|
|
1212
|
+
/**
|
|
1213
|
+
* In an atlas bake: the fraction of the shared map this object's islands
|
|
1214
|
+
* cover, which is what its budget and weight actually bought it.
|
|
1215
|
+
*/
|
|
1216
|
+
atlasShare?: number;
|
|
1217
|
+
};
|
|
1218
|
+
/**
|
|
1219
|
+
* One stand-in, ready to be put in the scene: see `BakeSimplify`.
|
|
1220
|
+
*
|
|
1221
|
+
* A mesh carries its geometry inline, in the scene's own coordinates (world
|
|
1222
|
+
* space, Y-up, meters), because it is a few hundred vertices and a loader
|
|
1223
|
+
* for it would weigh more than it does. `uv` is the one unwrap every map
|
|
1224
|
+
* uses. `tangent` (xyzw) is the frame the normal map was baked in; a viewer
|
|
1225
|
+
* that derives tangents from uv derivatives instead will be close, not
|
|
1226
|
+
* exact. `color` is an sRGB PNG and `normal` a linear one, both the right
|
|
1227
|
+
* way up for an ordinary image loader (`flipY` on); `lightmap` follows the
|
|
1228
|
+
* lightmap contract above.
|
|
1229
|
+
*
|
|
1230
|
+
* A billboard is an upright rectangle `width` by `height` centered on
|
|
1231
|
+
* `center`, to be turned about the vertical to face the camera, and `map` is
|
|
1232
|
+
* linear radiance with alpha: the objects as traced from `BakeSettings.view`'s
|
|
1233
|
+
* side, lit by the whole scene, with everything else cut away. It goes
|
|
1234
|
+
* through the viewer's own tone mapping like everything around it.
|
|
1235
|
+
*/
|
|
1236
|
+
type BakedProxy = {
|
|
1237
|
+
kind: 'mesh';
|
|
1238
|
+
/** Index into `settings.simplify`. */
|
|
1239
|
+
index: number;
|
|
1240
|
+
replaces: string[];
|
|
1241
|
+
name?: string;
|
|
1242
|
+
geometry: {
|
|
1243
|
+
position: number[];
|
|
1244
|
+
normal: number[];
|
|
1245
|
+
uv: number[];
|
|
1246
|
+
tangent?: number[];
|
|
1247
|
+
index: number[];
|
|
1248
|
+
};
|
|
1249
|
+
color: {
|
|
1250
|
+
uri: string;
|
|
1251
|
+
};
|
|
1252
|
+
normal: {
|
|
1253
|
+
uri: string;
|
|
1254
|
+
};
|
|
1255
|
+
lightmap: {
|
|
1256
|
+
uri: string;
|
|
1257
|
+
format: 'exr';
|
|
1258
|
+
size: number;
|
|
1259
|
+
};
|
|
1260
|
+
/** The replaced surfaces' roughness and metalness, area weighted. */
|
|
1261
|
+
roughness: number;
|
|
1262
|
+
metalness: number;
|
|
1263
|
+
layout?: BakedObject['layout'];
|
|
1264
|
+
} | {
|
|
1265
|
+
/** `to: 'decimate'`: the replaced object with fewer triangles. */
|
|
1266
|
+
kind: 'decimated';
|
|
1267
|
+
index: number;
|
|
1268
|
+
/** The one object it replaces, whose materials it wears. */
|
|
1269
|
+
replaces: [string];
|
|
1270
|
+
name?: string;
|
|
1271
|
+
/**
|
|
1272
|
+
* In the scene's frame, like a mesh stand-in's. `uv` is the object's
|
|
1273
|
+
* own texture uv, carried through the decimation, in the glTF
|
|
1274
|
+
* orientation its texture maps expect; `uv1` is present when the
|
|
1275
|
+
* lightmap needed an unwrap of its own. `groups` are its material
|
|
1276
|
+
* slots, in the order of the replaced mesh's materials.
|
|
1277
|
+
*/
|
|
1278
|
+
geometry: {
|
|
1279
|
+
position: number[];
|
|
1280
|
+
normal: number[];
|
|
1281
|
+
uv: number[];
|
|
1282
|
+
uv1?: number[];
|
|
1283
|
+
/** Its vertex colors (glTF COLOR_0), linear RGB, when it had them. */
|
|
1284
|
+
color?: number[];
|
|
1285
|
+
index: number[];
|
|
1286
|
+
groups?: Array<{
|
|
1287
|
+
start: number;
|
|
1288
|
+
count: number;
|
|
1289
|
+
materialIndex: number;
|
|
1290
|
+
}>;
|
|
1291
|
+
};
|
|
1292
|
+
lightmap: {
|
|
1293
|
+
uri: string;
|
|
1294
|
+
format: 'exr';
|
|
1295
|
+
size: number;
|
|
1296
|
+
};
|
|
1297
|
+
/** The uv set the lightmap reads: 0 is `uv`, 1 is `uv1`. */
|
|
1298
|
+
lightmapChannel: 0 | 1;
|
|
1299
|
+
triangles: {
|
|
1300
|
+
from: number;
|
|
1301
|
+
to: number;
|
|
1302
|
+
};
|
|
1303
|
+
layout?: BakedObject['layout'];
|
|
1304
|
+
} | {
|
|
1305
|
+
kind: 'billboard';
|
|
1306
|
+
index: number;
|
|
1307
|
+
replaces: string[];
|
|
1308
|
+
name?: string;
|
|
1309
|
+
center: [number, number, number];
|
|
1310
|
+
width: number;
|
|
1311
|
+
height: number;
|
|
1312
|
+
map: {
|
|
1313
|
+
uri: string;
|
|
1314
|
+
format: 'exr';
|
|
1315
|
+
width: number;
|
|
1316
|
+
height: number;
|
|
1317
|
+
};
|
|
1318
|
+
};
|
|
1319
|
+
/**
|
|
1320
|
+
* An object the bake did not produce, with the reason and the fix, per
|
|
1321
|
+
* object: "the bake finished" must never quietly mean "except for the glass
|
|
1322
|
+
* and the instanced chairs".
|
|
1323
|
+
*/
|
|
1324
|
+
type BakeSkip = {
|
|
1325
|
+
objectId: string;
|
|
1326
|
+
name?: string;
|
|
1327
|
+
code: string;
|
|
1328
|
+
message: string;
|
|
1329
|
+
why?: string;
|
|
1330
|
+
fix?: string;
|
|
1331
|
+
/**
|
|
1332
|
+
* The object was baked; this is a complaint about how well.
|
|
1333
|
+
*
|
|
1334
|
+
* An ordinary entry here means a lightmap was not made and the object is
|
|
1335
|
+
* still lit the way it was. A `warning` means one was made and is worse
|
|
1336
|
+
* than it should be. `BakeCoarseUnwrap` is the case: a surface whose
|
|
1337
|
+
* geometry is far finer than the map it earns cuts into more islands than
|
|
1338
|
+
* the map has room for, and the result is padding with a little light in
|
|
1339
|
+
* it.
|
|
1340
|
+
*/
|
|
1341
|
+
warning?: true;
|
|
1342
|
+
};
|
|
1343
|
+
/**
|
|
1344
|
+
* Everything one bake produced. The bundle file is written last, so an
|
|
1345
|
+
* interrupted bake never looks finished.
|
|
1346
|
+
*/
|
|
1347
|
+
type BakeBundle = {
|
|
1348
|
+
bundleVersion: 1;
|
|
1349
|
+
/** The `BAKE_VERSION` that produced this. */
|
|
1350
|
+
bakeVersion: number;
|
|
1351
|
+
settings: BakeSettings;
|
|
1352
|
+
objects: BakedObject[];
|
|
1353
|
+
skipped: BakeSkip[];
|
|
1354
|
+
/**
|
|
1355
|
+
* When `settings.atlas` was set: the one lightmap every object's
|
|
1356
|
+
* `lightmap.uri` names, so a loader can fetch it once and share it. Each
|
|
1357
|
+
* object's `uv` is already in the atlas's space.
|
|
1358
|
+
*/
|
|
1359
|
+
atlas?: {
|
|
1360
|
+
uri: string;
|
|
1361
|
+
format: 'exr' | 'png';
|
|
1362
|
+
size: number;
|
|
1363
|
+
preview?: {
|
|
1364
|
+
uri: string;
|
|
1365
|
+
};
|
|
1366
|
+
/** Fraction of the map's texels some island maps to: packing efficiency. */
|
|
1367
|
+
coverage?: number;
|
|
1368
|
+
};
|
|
1369
|
+
/**
|
|
1370
|
+
* The stand-ins `settings.simplify` asked for. One that could not be built
|
|
1371
|
+
* is in `skipped` under the id `simplify[n]`, and the objects it would have
|
|
1372
|
+
* replaced are left as they were.
|
|
1373
|
+
*/
|
|
1374
|
+
proxies?: BakedProxy[];
|
|
1375
|
+
/** The shadow plane `settings.shadow` asked for. */
|
|
1376
|
+
shadow?: BakedShadow;
|
|
1377
|
+
/**
|
|
1378
|
+
* The prebaked room this bake stands its objects in: an unmodified preset
|
|
1379
|
+
* room whose own lightmaps are a library asset, not something this bake
|
|
1380
|
+
* paid for. The SDK fetches that bundle and applies it underneath this
|
|
1381
|
+
* one. Absent from an ordinary bake, which lights everything it covers.
|
|
1382
|
+
*/
|
|
1383
|
+
room?: {
|
|
1384
|
+
/** The preset and the lighting it was baked under. */
|
|
1385
|
+
name: string;
|
|
1386
|
+
lighting: string;
|
|
1387
|
+
/**
|
|
1388
|
+
* The published room's build hash, which is what makes the prebake this
|
|
1389
|
+
* room and not a later edit of it.
|
|
1390
|
+
*/
|
|
1391
|
+
build: string;
|
|
1392
|
+
/** Where the prebaked bundle is, on the preset library. */
|
|
1393
|
+
uri: string;
|
|
1394
|
+
};
|
|
1395
|
+
/** The portable unlit GLB, when `deliverGlb` was set. */
|
|
1396
|
+
glb?: {
|
|
1397
|
+
uri: string;
|
|
1398
|
+
};
|
|
1399
|
+
/**
|
|
1400
|
+
* The reflection probe: a linear HDR equirectangular map of the scene from
|
|
1401
|
+
* `position`, in three's own equirect convention (load it, set
|
|
1402
|
+
* `EquirectangularReflectionMapping`, and it is an `envMap`). `hidden` are
|
|
1403
|
+
* the objects it was taken without, which are the ones to put it on.
|
|
1404
|
+
*/
|
|
1405
|
+
probe?: {
|
|
1406
|
+
uri: string;
|
|
1407
|
+
format: 'exr';
|
|
1408
|
+
width: number;
|
|
1409
|
+
height: number;
|
|
1410
|
+
position: [number, number, number];
|
|
1411
|
+
hidden: string[];
|
|
1412
|
+
/**
|
|
1413
|
+
* The probe as an LDR picture, for a panel, a drawer or a figure: the
|
|
1414
|
+
* same map through `exposure` (chosen by the worker to put its mean at
|
|
1415
|
+
* middle gray), Reinhard and sRGB. A label on the file, not something to
|
|
1416
|
+
* light with.
|
|
1417
|
+
*/
|
|
1418
|
+
preview?: {
|
|
1419
|
+
uri: string;
|
|
1420
|
+
exposure?: number;
|
|
1421
|
+
};
|
|
1422
|
+
};
|
|
1423
|
+
stats?: {
|
|
1424
|
+
texels: number;
|
|
1425
|
+
gpuSeconds?: number;
|
|
1426
|
+
/**
|
|
1427
|
+
* Where those seconds went, by step (`scene`, `probe`, `unwrap`,
|
|
1428
|
+
* `trace`, `repair`, `denoise`, `export`, `standIns`), summed over the
|
|
1429
|
+
* objects.
|
|
1430
|
+
*/
|
|
1431
|
+
seconds?: Record<string, number>;
|
|
1432
|
+
};
|
|
1433
|
+
};
|
|
1434
|
+
/**
|
|
1435
|
+
* A bundle without its uv arrays and stand-in geometry: what a job result
|
|
1436
|
+
* carries once the files are in storage. The arrays are the bulk of a bundle
|
|
1437
|
+
* (a dense room's are megabytes) and belong in `bundle.json`, which the SDK
|
|
1438
|
+
* reads from storage.
|
|
1439
|
+
*/
|
|
1440
|
+
type BakeBundleSummary = Omit<BakeBundle, 'objects' | 'proxies'> & {
|
|
1441
|
+
objects: Array<Omit<BakedObject, 'uv'>>;
|
|
1442
|
+
/** Stand-ins without their geometry, for the same reason. */
|
|
1443
|
+
proxies?: Array<DistributiveOmit<BakedProxy, 'geometry'>>;
|
|
1444
|
+
};
|
|
1445
|
+
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* A batch: a customer's declared body of work.
|
|
1449
|
+
*
|
|
1450
|
+
* A catalog is products x finishes x cameras, a few thousand stills. The
|
|
1451
|
+
* customer opens a batch, adds items in pages, closes it, and reads one
|
|
1452
|
+
* ledger back. On the control plane every item is a render row with a
|
|
1453
|
+
* `batchId`, staged behind the queue proper and fed into it a page at a
|
|
1454
|
+
* time, so everything that holds for a render (the claim, the reservation,
|
|
1455
|
+
* single billing, webhooks, retention) holds for an item because it is one.
|
|
1456
|
+
*/
|
|
1457
|
+
|
|
1458
|
+
declare const BATCH_STATES: readonly ["open", "sealed", "paused", "completed", "cancelled"];
|
|
1459
|
+
type BatchState = (typeof BATCH_STATES)[number];
|
|
1460
|
+
/**
|
|
1461
|
+
* How many items are in each state. `staged` items are admitted: priced,
|
|
1462
|
+
* reserved, waiting behind the queue proper. `queued` are in the queue and
|
|
1463
|
+
* `running` hold a worker. `refused` failed for a reason a retry would not
|
|
1464
|
+
* change (the scene's fault, or ours to explain); `failed` ran out of
|
|
1465
|
+
* attempts on an infrastructure failure. `adopted` were already completed
|
|
1466
|
+
* under the same key when they were added and cost nothing.
|
|
1467
|
+
*/
|
|
1468
|
+
type BatchCounts = {
|
|
1469
|
+
staged: number;
|
|
1470
|
+
queued: number;
|
|
1471
|
+
running: number;
|
|
1472
|
+
completed: number;
|
|
1473
|
+
failed: number;
|
|
1474
|
+
refused: number;
|
|
1475
|
+
cancelled: number;
|
|
1476
|
+
adopted: number;
|
|
1477
|
+
};
|
|
1478
|
+
type BatchCost = {
|
|
1479
|
+
/** Sum of estimatedCost over admitted items. */
|
|
1480
|
+
estimated: number;
|
|
1481
|
+
/** Sum of maximumCost over admitted items: what admission reserved. */
|
|
1482
|
+
maximum: number;
|
|
1483
|
+
/** Sum of what completed items actually cost, so far. */
|
|
1484
|
+
actual: number;
|
|
1485
|
+
};
|
|
1486
|
+
type BatchRecord = {
|
|
1487
|
+
id: string;
|
|
1488
|
+
key: string;
|
|
1489
|
+
name?: string;
|
|
1490
|
+
kind: 'render' | 'bake';
|
|
1491
|
+
state: BatchState;
|
|
1492
|
+
lane: 'batch' | 'normal';
|
|
1493
|
+
maxParallel?: number;
|
|
1494
|
+
/** The customer's ceiling for the whole batch. */
|
|
1495
|
+
maxCost?: number;
|
|
1496
|
+
counts: BatchCounts;
|
|
1497
|
+
/** Every item ever admitted, adopted included. */
|
|
1498
|
+
total: number;
|
|
1499
|
+
/** How many are terminal. `done === total` once sealed is `completed`. */
|
|
1500
|
+
done: number;
|
|
1501
|
+
cost: BatchCost;
|
|
1502
|
+
/** Why it is paused, when it is: the breaker names the top refusal codes. */
|
|
1503
|
+
pausedReason?: string;
|
|
1504
|
+
createdAt: number;
|
|
1505
|
+
sealedAt?: number;
|
|
1506
|
+
finishedAt?: number;
|
|
1507
|
+
};
|
|
1508
|
+
type BatchOpenRequest = {
|
|
1509
|
+
/**
|
|
1510
|
+
* Stable across re-runs: the same key re-opens the same batch while it is
|
|
1511
|
+
* not terminal, and items already completed under their keys are adopted
|
|
1512
|
+
* rather than rendered again.
|
|
1513
|
+
*/
|
|
1514
|
+
key: string;
|
|
1515
|
+
name?: string;
|
|
1516
|
+
kind?: 'render' | 'bake';
|
|
1517
|
+
/**
|
|
1518
|
+
* The ceiling for the whole batch, in dollars. A page that would take the
|
|
1519
|
+
* batch's maximum past it is refused whole.
|
|
1520
|
+
*/
|
|
1521
|
+
maxCost?: number;
|
|
1522
|
+
/**
|
|
1523
|
+
* `batch` (the default) never runs ahead of a person in a configurator;
|
|
1524
|
+
* `normal` is the API's ordinary lane.
|
|
1525
|
+
*/
|
|
1526
|
+
lane?: 'batch' | 'normal';
|
|
1527
|
+
/** Fewer parallel items than the plan allows, for a gentler budget. */
|
|
1528
|
+
maxParallel?: number;
|
|
1529
|
+
};
|
|
1530
|
+
type BatchItemRequest = {
|
|
1531
|
+
/**
|
|
1532
|
+
* The item's idempotency key, scoped to the project like every other
|
|
1533
|
+
* render's. `sku/finish/camera@v3` is the shape the docs suggest.
|
|
1534
|
+
*/
|
|
1535
|
+
key: string;
|
|
1536
|
+
spec: RenderSpec | BakeSpec;
|
|
1537
|
+
metadata?: Record<string, unknown>;
|
|
1538
|
+
/** A ceiling for this one item, under the batch's. */
|
|
1539
|
+
maxCost?: number;
|
|
1540
|
+
};
|
|
1541
|
+
type BatchAddRequest = {
|
|
1542
|
+
items: BatchItemRequest[];
|
|
1543
|
+
/**
|
|
1544
|
+
* Price everything, admit nothing: the whole catalog's numbers before a
|
|
1545
|
+
* dollar is reserved.
|
|
1546
|
+
*/
|
|
1547
|
+
dryRun?: boolean;
|
|
1548
|
+
};
|
|
1549
|
+
type BatchItemOutcome = {
|
|
1550
|
+
key: string;
|
|
1551
|
+
/** The render's id, when it was admitted or adopted. */
|
|
1552
|
+
id?: string;
|
|
1553
|
+
estimatedCost: number;
|
|
1554
|
+
maximumCost: number;
|
|
1555
|
+
/** Already completed under this key: attached at no cost. */
|
|
1556
|
+
adopted?: boolean;
|
|
1557
|
+
/**
|
|
1558
|
+
* Why this item, alone, was not admitted (a bad spec, an item over its own
|
|
1559
|
+
* maxCost). The page's other items are unaffected.
|
|
1560
|
+
*/
|
|
1561
|
+
error?: RenderError;
|
|
1562
|
+
};
|
|
1563
|
+
type BatchAddResponse = {
|
|
1564
|
+
batch: BatchRecord;
|
|
1565
|
+
items: BatchItemOutcome[];
|
|
1566
|
+
};
|
|
1567
|
+
/** One line of `GET /v1/batches/:id/export`: the ledger, item by item. */
|
|
1568
|
+
type BatchExportLine = {
|
|
1569
|
+
key: string;
|
|
1570
|
+
id: string;
|
|
1571
|
+
state: string;
|
|
1572
|
+
resultUrl?: string;
|
|
1573
|
+
error?: RenderError;
|
|
1574
|
+
cost?: number;
|
|
1575
|
+
versions?: Record<string, unknown>;
|
|
1576
|
+
metadata?: Record<string, unknown>;
|
|
1577
|
+
};
|
|
1578
|
+
|
|
1579
|
+
/**
|
|
1580
|
+
* Render job states and the events that carry them.
|
|
1581
|
+
*
|
|
1582
|
+
* Every transition is timestamped, idempotent and observable. Idempotent is
|
|
1583
|
+
* the load-bearing word: a worker that retries a stage must not bill twice
|
|
1584
|
+
* and must not emit `render.completed` twice, so the control plane treats a
|
|
1585
|
+
* transition to a state it is already in as a no-op rather than an error.
|
|
1586
|
+
*/
|
|
1587
|
+
declare const RENDER_STATES: readonly ["created", "uploading", "validating", "queued", "starting", "preparing", "rendering_preview", "rendering", "encoding", "uploading_result", "completed", "failed", "cancelled", "expired"];
|
|
1588
|
+
type RenderState = (typeof RENDER_STATES)[number];
|
|
1589
|
+
type RenderResult = {
|
|
1590
|
+
/**
|
|
1591
|
+
* Signed, private, and only good until `expiresAt` (24 hours). Copy the
|
|
1592
|
+
* bytes into your own storage if you keep them; `getRender(id)` hands out
|
|
1593
|
+
* fresh URLs until the project's retention deletes the files.
|
|
1594
|
+
*/
|
|
1595
|
+
url: string;
|
|
1596
|
+
/**
|
|
1597
|
+
* Millisecond epoch at which `url` (and `previewUrl`) stop working. The
|
|
1598
|
+
* URLs are signed when the render is read, so reading it again with
|
|
1599
|
+
* `getRender(id)` returns fresh ones. Absent from an API that predates it.
|
|
1600
|
+
*/
|
|
1601
|
+
expiresAt?: number;
|
|
1602
|
+
width: number;
|
|
1603
|
+
height: number;
|
|
1604
|
+
format: string;
|
|
1605
|
+
bytes: number;
|
|
1606
|
+
/** What was actually charged, in USD. Not the estimate. */
|
|
1607
|
+
cost: number;
|
|
1608
|
+
/** Everything needed to reproduce this exact image later. */
|
|
1609
|
+
versions: {
|
|
1610
|
+
protocol: string;
|
|
1611
|
+
sdk: string;
|
|
1612
|
+
translator: string;
|
|
1613
|
+
qualityPreset: string;
|
|
1614
|
+
};
|
|
1615
|
+
};
|
|
1616
|
+
/**
|
|
1617
|
+
* Which queue a render waits in inside its account.
|
|
1618
|
+
*
|
|
1619
|
+
* `interactive` is a person waiting: every browser-token render, and the
|
|
1620
|
+
* SDK's `render()` by default. `normal` is the API default. `batch` is an
|
|
1621
|
+
* item of a batch. A lane above always dispatches before a lane below, so a
|
|
1622
|
+
* configurator's visitor never waits behind an overnight catalog.
|
|
1623
|
+
*/
|
|
1624
|
+
declare const RENDER_LANES: readonly ["interactive", "normal", "batch"];
|
|
1625
|
+
type RenderLane = (typeof RENDER_LANES)[number];
|
|
1626
|
+
type RenderRecord = {
|
|
1627
|
+
id: string;
|
|
1628
|
+
state: RenderState;
|
|
1629
|
+
progress: number;
|
|
1630
|
+
/**
|
|
1631
|
+
* A video's frames traced so far and in all, while it renders. Updated
|
|
1632
|
+
* every ten seconds or so; absent for a still and once the render ends.
|
|
1633
|
+
*/
|
|
1634
|
+
frames?: {
|
|
1635
|
+
done: number;
|
|
1636
|
+
total: number;
|
|
1637
|
+
};
|
|
1638
|
+
/** Which of the account's queues it waits in. */
|
|
1639
|
+
lane?: RenderLane;
|
|
1640
|
+
/**
|
|
1641
|
+
* How many times a worker has been handed this render. 1 is the normal
|
|
1642
|
+
* case; more means an infrastructure failure was retried for you.
|
|
1643
|
+
*/
|
|
1644
|
+
attempts?: number;
|
|
1645
|
+
/** The batch this render belongs to, and its key within it. */
|
|
1646
|
+
batchId?: string;
|
|
1647
|
+
batchKey?: string;
|
|
1648
|
+
/** A low-sample image, available long before the final one. */
|
|
1649
|
+
previewUrl?: string;
|
|
1650
|
+
result?: RenderResult;
|
|
1651
|
+
error?: RenderError;
|
|
1652
|
+
estimate?: CostEstimate;
|
|
1653
|
+
createdAt: number;
|
|
1654
|
+
updatedAt: number;
|
|
1655
|
+
metadata?: Record<string, unknown>;
|
|
1656
|
+
};
|
|
1657
|
+
/**
|
|
1658
|
+
* A finished bake, as `GET /v1/bakes/:id` answers it.
|
|
1659
|
+
*
|
|
1660
|
+
* The bundle's uv arrays are behind `bundleUrl`, not here: a dense room's
|
|
1661
|
+
* are megabytes, and the SDK reads them once from storage. `files` is every
|
|
1662
|
+
* name the bundle uses, resolved to a signed URL, so a loader can take
|
|
1663
|
+
* `bundle.objects[i].lightmap.uri` and look it up without knowing where the
|
|
1664
|
+
* files live. All of them expire together, at `expiresAt`.
|
|
1665
|
+
*/
|
|
1666
|
+
type BakeResult = {
|
|
1667
|
+
/** Signed GET for bundle.json: the BakeBundle, uv arrays and all. */
|
|
1668
|
+
bundleUrl: string;
|
|
1669
|
+
/** Every file the bundle names, by that name, as a signed GET. */
|
|
1670
|
+
files: Record<string, string>;
|
|
1671
|
+
/** Millisecond epoch at which the URLs stop working. */
|
|
1672
|
+
expiresAt: number;
|
|
1673
|
+
/**
|
|
1674
|
+
* The bundle without its uv arrays: what a panel shows before it fetches
|
|
1675
|
+
* anything.
|
|
1676
|
+
*/
|
|
1677
|
+
summary: BakeBundleSummary;
|
|
1678
|
+
/** Every file's size together, in bytes. */
|
|
1679
|
+
bytes: number;
|
|
1680
|
+
cost: number;
|
|
1681
|
+
versions: Record<string, string | number>;
|
|
1682
|
+
};
|
|
1683
|
+
type BakeRecord = {
|
|
1684
|
+
id: string;
|
|
1685
|
+
state: RenderState;
|
|
1686
|
+
progress: number;
|
|
1687
|
+
result?: BakeResult;
|
|
1688
|
+
error?: RenderError;
|
|
1689
|
+
estimate?: CostEstimate;
|
|
1690
|
+
createdAt: number;
|
|
1691
|
+
updatedAt: number;
|
|
1692
|
+
metadata?: Record<string, unknown>;
|
|
1693
|
+
};
|
|
1694
|
+
/**
|
|
1695
|
+
* Cost, before anything expensive happens.
|
|
1696
|
+
*
|
|
1697
|
+
* `maximum` is what `maxCost` is compared against, so a developer who sets
|
|
1698
|
+
* a ceiling gets a refusal rather than a bill somewhere between the two
|
|
1699
|
+
* numbers.
|
|
1700
|
+
*/
|
|
1701
|
+
type CostEstimate = {
|
|
1702
|
+
estimatedCost: number;
|
|
1703
|
+
maximumCost: number;
|
|
1704
|
+
estimatedSeconds: number;
|
|
1705
|
+
compatibilityScore: number;
|
|
1706
|
+
};
|
|
1707
|
+
/**
|
|
1708
|
+
* A failure a developer can act on.
|
|
1709
|
+
*
|
|
1710
|
+
* `code` is stable and machine-readable; `message`, `why` and `fix` are for
|
|
1711
|
+
* the developer. A traceback is never any of them.
|
|
1712
|
+
*/
|
|
1713
|
+
type RenderError = {
|
|
1714
|
+
code: RenderErrorCode;
|
|
1715
|
+
message: string;
|
|
1716
|
+
why?: string;
|
|
1717
|
+
fix?: string;
|
|
1718
|
+
docs?: string;
|
|
1719
|
+
/** Where in the scene, when the failure has a location. */
|
|
1720
|
+
path?: string;
|
|
1721
|
+
/** Whether resubmitting unchanged could plausibly work. */
|
|
1722
|
+
retryable: boolean;
|
|
1723
|
+
/**
|
|
1724
|
+
* The control plane tried this render again and gave up: every attempt
|
|
1725
|
+
* failed the same retryable way. We have been told, and
|
|
1726
|
+
* `POST /v1/renders/:id/retry` tries once more on request.
|
|
1727
|
+
*/
|
|
1728
|
+
attemptsExhausted?: boolean;
|
|
1729
|
+
};
|
|
1730
|
+
declare const RENDER_ERROR_CODES: readonly ["RenderBudgetExceeded", "UnsupportedMaterial", "SceneTooLarge", "TextureTooLarge", "AssetNotFound", "AssetUploadIncomplete", "ProtocolVersionUnsupported", "RoomPresetUnknown", "RoomPresetBuildMismatch", "CompatibilityPreflightFailed", "OutOfVideoMemory", "RenderTimeout", "WorkerFailure", "WorkerLost", "RendererCrashed", "UploadFailed", "ResultLost", "CapacityUnavailable", "QueueFull", "QueueTimeout", "BatchBudgetExceeded", "BatchClosed", "ImportRefused", "ImportFailed", "PlanRequired", "StorageUnavailable", "InsufficientBalance", "QuotaExceeded", "ConcurrencyLimitReached", "RateLimited", "Unauthorized", "TokenExpired", "OriginNotAllowed", "InvalidRequest"];
|
|
1731
|
+
type RenderErrorCode = (typeof RENDER_ERROR_CODES)[number];
|
|
1732
|
+
/** Webhook event names. */
|
|
1733
|
+
declare const WEBHOOK_EVENTS: readonly ["render.created", "render.started", "render.preview", "render.completed", "render.failed", "render.cancelled", "render.retrying", "video.started", "video.frame.completed", "video.completed", "bake.created", "bake.started", "bake.completed", "bake.failed", "bake.cancelled", "bake.retrying", "batch.progress", "batch.completed", "batch.paused"];
|
|
1734
|
+
type WebhookEvent = (typeof WEBHOOK_EVENTS)[number];
|
|
1735
|
+
/**
|
|
1736
|
+
* What a webhook carries. A `render.*` event has `render`; a `bake.*` event
|
|
1737
|
+
* has `bake`. Never both: a receiver switches on `event` and reads the one
|
|
1738
|
+
* record the event is about.
|
|
1739
|
+
*/
|
|
1740
|
+
type WebhookPayload = {
|
|
1741
|
+
event: WebhookEvent;
|
|
1742
|
+
/** Millisecond epoch. Signed together with the body; see the docs. */
|
|
1743
|
+
sentAt: number;
|
|
1744
|
+
render?: RenderRecord;
|
|
1745
|
+
bake?: BakeRecord;
|
|
1746
|
+
/** A `batch.*` event carries the batch's ledger instead. */
|
|
1747
|
+
batch?: BatchRecord;
|
|
1748
|
+
};
|
|
1749
|
+
|
|
1750
|
+
type AssetKind = 'scene' | 'texture' | 'hdri' | 'ies' | 'geometry' | 'animation';
|
|
1751
|
+
type AssetDescriptor = {
|
|
1752
|
+
hash: string;
|
|
1753
|
+
kind: AssetKind;
|
|
1754
|
+
bytes: number;
|
|
1755
|
+
contentType: string;
|
|
1756
|
+
/** Diagnostics and the devtools' upload list; never used for addressing. */
|
|
1757
|
+
name?: string;
|
|
1758
|
+
};
|
|
1759
|
+
/** Request body for "which of these do you already have?". */
|
|
1760
|
+
type AssetCheckRequest = {
|
|
1761
|
+
assets: AssetDescriptor[];
|
|
1762
|
+
};
|
|
1763
|
+
/**
|
|
1764
|
+
* The answer, plus a signed PUT for each miss.
|
|
1765
|
+
*
|
|
1766
|
+
* The presigned URL is a capability, not a credential: one object, one
|
|
1767
|
+
* method, one expiry. It lets the browser upload straight to storage without
|
|
1768
|
+
* the control plane ever touching the bytes.
|
|
1769
|
+
*/
|
|
1770
|
+
type AssetCheckResponse = {
|
|
1771
|
+
/** Hashes already in storage. Do not upload these. */
|
|
1772
|
+
present: string[];
|
|
1773
|
+
/** One signed PUT per hash that is missing. */
|
|
1774
|
+
uploads: Array<{
|
|
1775
|
+
hash: string;
|
|
1776
|
+
url: string;
|
|
1777
|
+
/**
|
|
1778
|
+
* Headers the signature covers. A presigned PUT is signed over an exact
|
|
1779
|
+
* header set, so sending more or fewer than these is a 403. Pass them
|
|
1780
|
+
* through verbatim.
|
|
1781
|
+
*/
|
|
1782
|
+
headers?: Record<string, string>;
|
|
1783
|
+
expiresAt: number;
|
|
1784
|
+
}>;
|
|
1785
|
+
};
|
|
1786
|
+
/**
|
|
1787
|
+
* `DELETE /v1/assets/:hash`. Secret key only. The stored file and its
|
|
1788
|
+
* record are gone; the next render that needs it uploads it again.
|
|
1789
|
+
*/
|
|
1790
|
+
type AssetDeleteResponse = {
|
|
1791
|
+
hash: string;
|
|
1792
|
+
/** False when the project held no file with this hash: not an error. */
|
|
1793
|
+
deleted: boolean;
|
|
1794
|
+
};
|
|
1795
|
+
|
|
1796
|
+
/**
|
|
1797
|
+
* What a GLB says about itself, read from its JSON chunk alone.
|
|
1798
|
+
*
|
|
1799
|
+
* A scene the SDK captured arrives with everything the control plane needs
|
|
1800
|
+
* beside it: a triangle count to price with, a camera, a compatibility
|
|
1801
|
+
* report. A model fetched by URL arrives with nothing, and the renderer is
|
|
1802
|
+
* the wrong place to find out it cannot be read: that is a refusal at three
|
|
1803
|
+
* in the morning, a thousand times. So the import reads the file's own
|
|
1804
|
+
* table of contents, which is the first chunk and a few hundred KB at most,
|
|
1805
|
+
* and answers before anything is admitted:
|
|
1806
|
+
*
|
|
1807
|
+
* - how heavy it is (triangles, per node instance, the way it will trace);
|
|
1808
|
+
* - where it is (the world bounds, from accessor min/max through the node
|
|
1809
|
+
* transforms), which is what lets a camera be FRAMED instead of guessed;
|
|
1810
|
+
* - which cameras it carries, by name;
|
|
1811
|
+
* - what in it the renderer cannot read (a required extension its importer
|
|
1812
|
+
* does not implement), by name and with the fix.
|
|
1813
|
+
*
|
|
1814
|
+
* Pure, no dependencies, no binary chunk: nothing here decodes geometry.
|
|
1815
|
+
*/
|
|
1816
|
+
|
|
1817
|
+
type GlbCamera = {
|
|
1818
|
+
/** The node's name, or the camera's. What a caller asks for it by. */
|
|
1819
|
+
name: string;
|
|
1820
|
+
/** As it stands in the file, in world space. `aspect` is the file's own, when it gives one. */
|
|
1821
|
+
camera: CameraSpec;
|
|
1822
|
+
};
|
|
1823
|
+
type GlbFacts = {
|
|
1824
|
+
bytes: number;
|
|
1825
|
+
/** Counted per node instance: a mesh two nodes show is traced twice. */
|
|
1826
|
+
triangles: number;
|
|
1827
|
+
meshes: number;
|
|
1828
|
+
materials: number;
|
|
1829
|
+
textures: number;
|
|
1830
|
+
/** Punctual lights the file carries. They render as its author set them. */
|
|
1831
|
+
lights: number;
|
|
1832
|
+
animations: number;
|
|
1833
|
+
/** World bounds of everything with geometry, or null for a file with none. */
|
|
1834
|
+
bounds: {
|
|
1835
|
+
min: Vec3;
|
|
1836
|
+
max: Vec3;
|
|
1837
|
+
} | null;
|
|
1838
|
+
cameras: GlbCamera[];
|
|
1839
|
+
/** Named nodes, in file order, bounded. */
|
|
1840
|
+
objects: string[];
|
|
1841
|
+
extensionsUsed: string[];
|
|
1842
|
+
extensionsRequired: string[];
|
|
1843
|
+
generator?: string;
|
|
1844
|
+
/** `error` issues mean the renderer cannot read the file as authored. */
|
|
1845
|
+
issues: CompatibilityIssue[];
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
/**
|
|
1849
|
+
* Importing an asset by URL: `POST /v1/assets/import`.
|
|
1850
|
+
*
|
|
1851
|
+
* Every other asset is pushed: the browser hashes it and PUTs it on a
|
|
1852
|
+
* presigned URL. A catalog that already sits on a CDN should not be pulled
|
|
1853
|
+
* through somebody's laptop to be pushed again, so this one is pulled: the
|
|
1854
|
+
* control plane fetches the public URL once, stores the bytes content
|
|
1855
|
+
* addressed exactly as an upload would have, and answers with the hash and
|
|
1856
|
+
* what the file says about itself. The result is an ordinary asset, and
|
|
1857
|
+
* everything after it (the manifest, the cache keys, the render) is the
|
|
1858
|
+
* ordinary path.
|
|
1859
|
+
*
|
|
1860
|
+
* Fetching a URL a customer names is the one thing here a stranger could aim
|
|
1861
|
+
* somewhere else, so the route takes a secret key only (never a browser
|
|
1862
|
+
* token), and the fetch is fenced: https, public addresses only, no
|
|
1863
|
+
* credentials in the URL, and a size limit.
|
|
1864
|
+
*/
|
|
1865
|
+
|
|
1866
|
+
type AssetImportKind = 'scene' | 'hdri';
|
|
1867
|
+
type AssetImportRequest = {
|
|
1868
|
+
/** `https://`, public, no credentials in it. */
|
|
1869
|
+
url: string;
|
|
1870
|
+
/** `scene` (a GLB, the default) or `hdri` (a Radiance .hdr or an OpenEXR). */
|
|
1871
|
+
kind?: AssetImportKind;
|
|
1872
|
+
};
|
|
1873
|
+
type AssetImportResponse = {
|
|
1874
|
+
hash: AssetHash;
|
|
1875
|
+
kind: AssetImportKind;
|
|
1876
|
+
bytes: number;
|
|
1877
|
+
/**
|
|
1878
|
+
* True when nothing was downloaded: the URL answered 304 to the validator
|
|
1879
|
+
* kept from the last import, so the stored bytes are still what it serves.
|
|
1880
|
+
*/
|
|
1881
|
+
unchanged: boolean;
|
|
1882
|
+
/** For a `scene`: what the GLB says about itself. */
|
|
1883
|
+
facts?: GlbFacts;
|
|
1884
|
+
};
|
|
1885
|
+
|
|
1886
|
+
/**
|
|
1887
|
+
* Errors a developer can act on.
|
|
1888
|
+
*
|
|
1889
|
+
* "Render failed" is a bad error. A traceback is a worse one. Every failure
|
|
1890
|
+
* that reaches the developer carries what went wrong, where, why, and what to
|
|
1891
|
+
* do about it, and `code` stays stable so the failure can also be branched on
|
|
1892
|
+
* in code.
|
|
1893
|
+
*/
|
|
1894
|
+
|
|
1895
|
+
/**
|
|
1896
|
+
* Codes the SDK can raise that the render protocol does not: a fault on the
|
|
1897
|
+
* way (`NetworkError`), a fault before anything left the browser
|
|
1898
|
+
* (`CaptureFailed`), the two refusals a batch page can meet as a whole (over
|
|
1899
|
+
* the batch's own ceiling, or after it was sealed), and the two ends of a job
|
|
1900
|
+
* that are not faults in the scene: it was cancelled (`Cancelled`), or its
|
|
1901
|
+
* files were deleted before it was read (`Expired`).
|
|
1902
|
+
*/
|
|
1903
|
+
type Bakery3ErrorCode = RenderErrorCode | 'NetworkError' | 'CaptureFailed' | 'BatchBudgetExceeded' | 'BatchClosed' | 'Cancelled' | 'Expired';
|
|
1904
|
+
declare class Bakery3Error extends Error {
|
|
1905
|
+
readonly code: Bakery3ErrorCode;
|
|
1906
|
+
readonly why?: string;
|
|
1907
|
+
readonly fix?: string;
|
|
1908
|
+
readonly docs?: string;
|
|
1909
|
+
readonly path?: string;
|
|
1910
|
+
readonly retryable: boolean;
|
|
1911
|
+
constructor(init: {
|
|
1912
|
+
code: Bakery3ErrorCode;
|
|
1913
|
+
message: string;
|
|
1914
|
+
why?: string;
|
|
1915
|
+
fix?: string;
|
|
1916
|
+
docs?: string;
|
|
1917
|
+
path?: string;
|
|
1918
|
+
retryable?: boolean;
|
|
1919
|
+
});
|
|
1920
|
+
static from(error: RenderError): Bakery3Error;
|
|
1921
|
+
/**
|
|
1922
|
+
* The multi-line form, for a console or a devtools panel.
|
|
1923
|
+
*
|
|
1924
|
+
* `Error.message` stays one line so it reads correctly everywhere a message
|
|
1925
|
+
* is expected to be one: a log aggregator, a toast, a test name. `stack`
|
|
1926
|
+
* starts with this form, so an uncaught error prints its fix.
|
|
1927
|
+
*/
|
|
1928
|
+
toDisplayString(): string;
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* A whole page of batch items was refused.
|
|
1932
|
+
*
|
|
1933
|
+
* Admission is per page: the control plane prices the page, checks it against
|
|
1934
|
+
* the account's balance and the batch's own `maxCost`, and either admits
|
|
1935
|
+
* every item or none. A `402 InsufficientBalance`, a `402 BatchBudgetExceeded`
|
|
1936
|
+
* or a `409 BatchClosed` means nothing in the page was admitted. The `Batch`
|
|
1937
|
+
* keeps those items buffered, so after a top-up (or a higher ceiling)
|
|
1938
|
+
* `flush()` sends exactly the same page again.
|
|
1939
|
+
*
|
|
1940
|
+
* `admitted` is what earlier pages of the same flush already got through:
|
|
1941
|
+
* those items are the server's now, and this error carries their outcomes so
|
|
1942
|
+
* a caller does not lose them because a later page was refused.
|
|
1943
|
+
*/
|
|
1944
|
+
declare class BatchPageRefused extends Bakery3Error {
|
|
1945
|
+
/** The page that was refused whole, still in the batch's buffer. */
|
|
1946
|
+
readonly page: BatchItemRequest[];
|
|
1947
|
+
/** Outcomes of the pages this flush admitted before the refusal. */
|
|
1948
|
+
readonly admitted: BatchItemOutcome[];
|
|
1949
|
+
constructor(cause: Bakery3Error, page: BatchItemRequest[], admitted: BatchItemOutcome[]);
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
/**
|
|
1953
|
+
* The HTTP client for the control plane.
|
|
1954
|
+
*
|
|
1955
|
+
* Small on purpose: a handful of endpoints, one auth header, one error shape.
|
|
1956
|
+
* The interesting work happens in the browser (capture, hashing) and on the
|
|
1957
|
+
* render workers; this is the thin part in between.
|
|
1958
|
+
*
|
|
1959
|
+
* The one piece of policy it carries is the retry: a 429 or a 503 is the
|
|
1960
|
+
* control plane saying "not now", and it is answered with a bounded, jittered
|
|
1961
|
+
* wait rather than an error the developer has to write a loop around. Only
|
|
1962
|
+
* requests that are safe to repeat are repeated (see `RequestOptions.retry`).
|
|
1963
|
+
*
|
|
1964
|
+
* In production no secret key reaches this file. The browser holds a
|
|
1965
|
+
* short-lived, project-scoped, origin-restricted, spend-capped token minted by
|
|
1966
|
+
* the developer's own server. `token` is a function rather than a string so
|
|
1967
|
+
* it can be fetched again when one expires, which for a five-minute token in
|
|
1968
|
+
* a long configurator session is routine.
|
|
1969
|
+
*
|
|
1970
|
+
* `apiKey` is the other door, and it is open on purpose: a key works from the
|
|
1971
|
+
* browser so the first render is one paste away, and `warnIfKeyIsExposed`
|
|
1972
|
+
* says once, with the replacement code, why it must not stay there.
|
|
1973
|
+
*
|
|
1974
|
+
* Neither is needed to construct a client: `check()` and `inspect()` never
|
|
1975
|
+
* touch the network, and a React tree whose environment variable is unset
|
|
1976
|
+
* must still mount. A missing credential is refused at the first request,
|
|
1977
|
+
* with what to pass instead.
|
|
1978
|
+
*/
|
|
1979
|
+
|
|
1980
|
+
/** What `/v1/estimate` prices: a still or a clip, or a bake. */
|
|
1981
|
+
type EstimateRequest = (Pick<RenderSpec, 'quality' | 'output' | 'advanced'> & {
|
|
1982
|
+
/** Triangle count and texture footprint, so an estimate can be given
|
|
1983
|
+
* before anything has been uploaded. `room` says the scene stands in a
|
|
1984
|
+
* preset room, which the renderer builds around it; `enclosure` and
|
|
1985
|
+
* `objects` say how walled in and how full it is from the camera
|
|
1986
|
+
* (`SceneComplexity`), which is what makes an interior cost more. */
|
|
1987
|
+
scene?: {
|
|
1988
|
+
triangles?: number;
|
|
1989
|
+
textureBytes?: number;
|
|
1990
|
+
fog?: boolean;
|
|
1991
|
+
room?: boolean;
|
|
1992
|
+
enclosure?: number;
|
|
1993
|
+
objects?: number;
|
|
1994
|
+
};
|
|
1995
|
+
/** Frames in a clip; the control plane prices a video per frame. */
|
|
1996
|
+
frames?: number;
|
|
1997
|
+
}) | {
|
|
1998
|
+
/** The settings a bake request would carry. Texels (objects × size²)
|
|
1999
|
+
* are what the control plane prices. */
|
|
2000
|
+
bake: Pick<BakeSettings, 'quality' | 'textureSize' | 'include' | 'sizes' | 'atlas' | 'deliverGlb' | 'simplify' | 'shadow'> & {
|
|
2001
|
+
/** The scene is a room interior: a ray that misses keeps going,
|
|
2002
|
+
* which is most of what such a bake costs. */
|
|
2003
|
+
enclosed?: boolean;
|
|
2004
|
+
};
|
|
2005
|
+
/** Triangles in the objects the bake would cover: what the unwrap runs
|
|
2006
|
+
* over, and priced separately from the scene's own count. */
|
|
2007
|
+
bakedTriangles?: number;
|
|
2008
|
+
scene?: {
|
|
2009
|
+
triangles?: number;
|
|
2010
|
+
textureBytes?: number;
|
|
2011
|
+
fog?: boolean;
|
|
2012
|
+
};
|
|
2013
|
+
};
|
|
2014
|
+
/** A browser token: the string, or the JSON `tokens.create()` answers with. */
|
|
2015
|
+
type MintedToken = string | {
|
|
2016
|
+
token: string;
|
|
2017
|
+
expiresAt?: number;
|
|
2018
|
+
};
|
|
2019
|
+
type TokenProvider = () => MintedToken | Promise<MintedToken>;
|
|
2020
|
+
/**
|
|
2021
|
+
* One of the two, never both. Neither is allowed too, for a client that only
|
|
2022
|
+
* checks scenes; its first request is refused with what to pass.
|
|
2023
|
+
*
|
|
2024
|
+
* The `?: never` arms turn passing both into a type error at the call site
|
|
2025
|
+
* rather than a runtime throw later: the two are different integrations, and
|
|
2026
|
+
* a codebase carrying both is one that has not finished moving off the key.
|
|
2027
|
+
*/
|
|
2028
|
+
type ClientAuth = {
|
|
2029
|
+
/** Mints a browser token, usually from your own `/api/bakery3-token`. */
|
|
2030
|
+
token: TokenProvider;
|
|
2031
|
+
apiKey?: never;
|
|
2032
|
+
} | {
|
|
2033
|
+
/**
|
|
2034
|
+
* A `bk_sk_` key used directly, for local testing, a Node script, or a
|
|
2035
|
+
* server-rendered call.
|
|
2036
|
+
*
|
|
2037
|
+
* From a browser this ships the key to every visitor. The SDK allows it
|
|
2038
|
+
* so the first render is one paste away, warns about it in the console,
|
|
2039
|
+
* and expects `token` by the time you deploy.
|
|
2040
|
+
*
|
|
2041
|
+
* `undefined` (an environment variable that is not set) is accepted
|
|
2042
|
+
* here and refused at the first request, with that said.
|
|
2043
|
+
*/
|
|
2044
|
+
apiKey: string | undefined;
|
|
2045
|
+
token?: never;
|
|
2046
|
+
} | {
|
|
2047
|
+
token?: undefined;
|
|
2048
|
+
apiKey?: undefined;
|
|
2049
|
+
};
|
|
2050
|
+
/**
|
|
2051
|
+
* How a 429 or a 503 is waited out. Bounded: after `maxTries` the error is
|
|
2052
|
+
* the developer's. The defaults are the SDK's; a test shortens the delays.
|
|
2053
|
+
*/
|
|
2054
|
+
type RetryPolicy = {
|
|
2055
|
+
/** Attempts in total, the first included. Default 5. */
|
|
2056
|
+
maxTries?: number;
|
|
2057
|
+
/** The first wait, before jitter and doubling. Default 500 ms. */
|
|
2058
|
+
baseDelayMs?: number;
|
|
2059
|
+
/** No single wait is longer than this, `Retry-After` included. Default 30 s. */
|
|
2060
|
+
maxDelayMs?: number;
|
|
2061
|
+
};
|
|
2062
|
+
type ClientOptions = ClientAuth & {
|
|
2063
|
+
baseUrl?: string;
|
|
2064
|
+
fetch?: typeof fetch;
|
|
2065
|
+
retry?: RetryPolicy;
|
|
2066
|
+
};
|
|
2067
|
+
/** Per-request knobs, for the client's own methods. */
|
|
2068
|
+
type RequestOptions = {
|
|
2069
|
+
/**
|
|
2070
|
+
* Whether a 429/503 may be answered by sending the request again.
|
|
2071
|
+
*
|
|
2072
|
+
* Every GET is. A POST is only when repeating it cannot do a second thing:
|
|
2073
|
+
* a batch page (its items carry keys), a batch action, a render that
|
|
2074
|
+
* carries an `idempotencyKey`. `POST /v1/renders` without one is not: a
|
|
2075
|
+
* second copy would be a second charge, which no amount of backoff is
|
|
2076
|
+
* worth.
|
|
2077
|
+
*/
|
|
2078
|
+
retry?: boolean;
|
|
2079
|
+
/** Cuts a long-poll short. */
|
|
2080
|
+
signal?: AbortSignal;
|
|
2081
|
+
};
|
|
2082
|
+
/** `GET /v1/batches/:id/items`: the render record plus the item's key. */
|
|
2083
|
+
type BatchItemRecord = RenderRecord & {
|
|
2084
|
+
key: string;
|
|
2085
|
+
};
|
|
2086
|
+
type BatchItemsPage = {
|
|
2087
|
+
items: BatchItemRecord[];
|
|
2088
|
+
cursor: string | null;
|
|
2089
|
+
};
|
|
2090
|
+
type BatchAction = 'close' | 'pause' | 'resume' | 'cancel' | 'retry';
|
|
2091
|
+
declare class Bakery3Client {
|
|
2092
|
+
private readonly baseUrl;
|
|
2093
|
+
private readonly auth;
|
|
2094
|
+
/**
|
|
2095
|
+
* The fetch this client sends with: the one passed in `options.fetch`, or
|
|
2096
|
+
* the global. Uploads and bundle reads use it too, so a test, a proxy or a
|
|
2097
|
+
* runtime with its own fetch sees every request the SDK makes.
|
|
2098
|
+
*/
|
|
2099
|
+
readonly fetch: typeof fetch;
|
|
2100
|
+
/** Cached until it expires. Re-minting per render would add a round trip to
|
|
2101
|
+
* the developer's own server on the critical path of every click. */
|
|
2102
|
+
private cached;
|
|
2103
|
+
/** One token fetch at a time. React's StrictMode mounts twice, and two
|
|
2104
|
+
* renders at once both find the cache empty: they share this one. */
|
|
2105
|
+
private minting;
|
|
2106
|
+
/**
|
|
2107
|
+
* The browser token each render, bake and batch was created with, by id.
|
|
2108
|
+
*
|
|
2109
|
+
* A token can only read the rows it created. Once it has spent its
|
|
2110
|
+
* allowance or expired, the API still lets it read and cancel those rows
|
|
2111
|
+
* (for 24 hours past expiry) but not create more, and a freshly minted
|
|
2112
|
+
* token cannot read them at all. So a job polls with the token that
|
|
2113
|
+
* started it, whatever the local cache thinks of that token's expiry.
|
|
2114
|
+
*/
|
|
2115
|
+
private readonly owners;
|
|
2116
|
+
private readonly retry;
|
|
2117
|
+
private readonly sendVersion;
|
|
2118
|
+
constructor(options?: ClientOptions);
|
|
2119
|
+
/**
|
|
2120
|
+
* Why this client cannot make a request, or null when it can. The same
|
|
2121
|
+
* error its first request would throw, for a panel or a status line that
|
|
2122
|
+
* wants to say so before anyone presses Render.
|
|
2123
|
+
*/
|
|
2124
|
+
credentialError(): Bakery3Error | null;
|
|
2125
|
+
token(force?: boolean): Promise<string>;
|
|
2126
|
+
private mint;
|
|
2127
|
+
checkAssets(body: AssetCheckRequest): Promise<AssetCheckResponse>;
|
|
2128
|
+
/**
|
|
2129
|
+
* `POST /v1/assets/import`: have the service fetch a public URL into the
|
|
2130
|
+
* project's assets. Secret key only. Safe to repeat: the same bytes are the
|
|
2131
|
+
* same asset, and an unchanged URL is answered without a download.
|
|
2132
|
+
*/
|
|
2133
|
+
importAsset(body: AssetImportRequest): Promise<AssetImportResponse>;
|
|
2134
|
+
estimate(body: EstimateRequest): Promise<CostEstimate>;
|
|
2135
|
+
createRender(body: RenderSpec): Promise<RenderRecord>;
|
|
2136
|
+
/**
|
|
2137
|
+
* A render's record, with fresh signed URLs for its result and preview.
|
|
2138
|
+
* Read with the token that created it, when this client created it.
|
|
2139
|
+
*/
|
|
2140
|
+
getRender(id: string, options?: {
|
|
2141
|
+
signal?: AbortSignal;
|
|
2142
|
+
}): Promise<RenderRecord>;
|
|
2143
|
+
cancelRender(id: string): Promise<RenderRecord>;
|
|
2144
|
+
/**
|
|
2145
|
+
* `POST /v1/renders/:id/retry`: one more attempt for a dead-lettered render.
|
|
2146
|
+
* Answers 201 with a new render linked to the old one, or 409 when the
|
|
2147
|
+
* render is not dead-lettered. Not repeated on a 429/503: it creates a
|
|
2148
|
+
* render, and a duplicate would be a duplicate charge.
|
|
2149
|
+
*/
|
|
2150
|
+
retryRender(id: string): Promise<RenderRecord>;
|
|
2151
|
+
createBake(body: BakeSpec): Promise<BakeRecord>;
|
|
2152
|
+
getBake(id: string, options?: {
|
|
2153
|
+
signal?: AbortSignal;
|
|
2154
|
+
}): Promise<BakeRecord>;
|
|
2155
|
+
cancelBake(id: string): Promise<BakeRecord>;
|
|
2156
|
+
/** `POST /v1/batches`. The key is the idempotency key: the same key
|
|
2157
|
+
* re-opens the same batch (200) while it is not terminal. */
|
|
2158
|
+
openBatch(body: BatchOpenRequest): Promise<BatchRecord>;
|
|
2159
|
+
/**
|
|
2160
|
+
* `GET /v1/batches/:id`. With `wait`, long-polls for up to that many
|
|
2161
|
+
* seconds (at most 25) until the counts change: the one poll a batch needs.
|
|
2162
|
+
*/
|
|
2163
|
+
getBatch(id: string, options?: {
|
|
2164
|
+
wait?: number;
|
|
2165
|
+
signal?: AbortSignal;
|
|
2166
|
+
}): Promise<BatchRecord>;
|
|
2167
|
+
/**
|
|
2168
|
+
* `POST /v1/batches/:id/items`: one page, at most `BATCH_PAGE_SIZE`.
|
|
2169
|
+
*
|
|
2170
|
+
* Every item carries its own key, so the page is safe to send twice: the
|
|
2171
|
+
* second copy adopts what the first admitted. A refusal of the whole page
|
|
2172
|
+
* (402 InsufficientBalance / BatchBudgetExceeded, 409 BatchClosed) throws;
|
|
2173
|
+
* a problem with one item comes back inside `items[i].error` with a 200.
|
|
2174
|
+
*/
|
|
2175
|
+
addBatchItems(id: string, body: BatchAddRequest): Promise<BatchAddResponse>;
|
|
2176
|
+
/** `GET /v1/batches/:id/items?state=&cursor=&limit=`. */
|
|
2177
|
+
listBatchItems(id: string, options?: {
|
|
2178
|
+
state?: RenderState;
|
|
2179
|
+
cursor?: string;
|
|
2180
|
+
limit?: number;
|
|
2181
|
+
}): Promise<BatchItemsPage>;
|
|
2182
|
+
/**
|
|
2183
|
+
* `GET /v1/batches/:id/export`: the ledger as NDJSON, one line per item,
|
|
2184
|
+
* yielded as it streams so a ten-thousand-item batch is never one string.
|
|
2185
|
+
*/
|
|
2186
|
+
exportBatch(id: string): AsyncIterable<BatchExportLine>;
|
|
2187
|
+
/** `POST /v1/batches/:id/{close,pause,resume,cancel,retry}`. Every one is a
|
|
2188
|
+
* state transition the control plane treats idempotently, so all repeat. */
|
|
2189
|
+
batchAction(id: string, action: BatchAction): Promise<BatchRecord>;
|
|
2190
|
+
private post;
|
|
2191
|
+
/** A request about a row this client may have created, with its token. */
|
|
2192
|
+
private read;
|
|
2193
|
+
/** Remember which token created a row, so the row is read with it. */
|
|
2194
|
+
private remember;
|
|
2195
|
+
/**
|
|
2196
|
+
* One authenticated JSON request, with the 401 re-mint and the 429/503
|
|
2197
|
+
* backoff. Public for the server SDK, which shares the transport; the
|
|
2198
|
+
* typed methods above are the API.
|
|
2199
|
+
*/
|
|
2200
|
+
request<T>(method: string, path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
2201
|
+
/** A JSON request, and the credential that got the answer. */
|
|
2202
|
+
private call;
|
|
2203
|
+
private send;
|
|
2204
|
+
private fetchOnce;
|
|
2205
|
+
/**
|
|
2206
|
+
* Exponential with full jitter, or the server's own `Retry-After` when it
|
|
2207
|
+
* sent one: a server that says "in 3 s" knows more than a formula does.
|
|
2208
|
+
* Both are capped, so a header naming next Tuesday does not hang a page.
|
|
2209
|
+
*/
|
|
2210
|
+
private delayFor;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
/**
|
|
2214
|
+
* A batch, from the customer's side.
|
|
2215
|
+
*
|
|
2216
|
+
* const batch = await bakery3.batches.open({ key: 'fall-2026-v3', maxCost: 600 });
|
|
2217
|
+
* for await (const item of source) await batch.write(item); // a full page sends itself
|
|
2218
|
+
* await batch.close(); // the tail is flushed, then sealed: the total is known
|
|
2219
|
+
* await batch.wait({ onProgress: (p) => console.log(`${p.completed}/${p.total}`) });
|
|
2220
|
+
* for await (const item of batch.items({ state: 'failed' })) console.error(item.key);
|
|
2221
|
+
*
|
|
2222
|
+
* Two things this class is careful about, because they are the whole reason
|
|
2223
|
+
* the batch exists rather than a loop over `render()`:
|
|
2224
|
+
*
|
|
2225
|
+
* - Memory. `write` buffers a few KB of manifest per item and sends a page
|
|
2226
|
+
* the moment one is full, by count or by bytes, so awaiting it is the
|
|
2227
|
+
* backpressure and nothing here ever holds the catalog. (`add` and
|
|
2228
|
+
* `flush` are the same two steps taken by hand.) A refused page stays
|
|
2229
|
+
* buffered: the caller tops up and flushes again, and the same items go,
|
|
2230
|
+
* under the same keys.
|
|
2231
|
+
* - One poll loop. `wait` long-polls the batch record; it never polls a
|
|
2232
|
+
* render. Items are read by state with a cursor when they are wanted.
|
|
2233
|
+
*/
|
|
2234
|
+
|
|
2235
|
+
/** The slice of the client a batch drives: what a mock has to provide. */
|
|
2236
|
+
type BatchTransport = {
|
|
2237
|
+
getBatch(id: string, options?: {
|
|
2238
|
+
wait?: number;
|
|
2239
|
+
signal?: AbortSignal;
|
|
2240
|
+
}): Promise<BatchRecord>;
|
|
2241
|
+
addBatchItems(id: string, body: BatchAddRequest): Promise<{
|
|
2242
|
+
batch: BatchRecord;
|
|
2243
|
+
items: BatchItemOutcome[];
|
|
2244
|
+
}>;
|
|
2245
|
+
listBatchItems(id: string, options?: {
|
|
2246
|
+
state?: RenderState;
|
|
2247
|
+
cursor?: string;
|
|
2248
|
+
limit?: number;
|
|
2249
|
+
}): Promise<BatchItemsPage>;
|
|
2250
|
+
exportBatch(id: string): AsyncIterable<BatchExportLine>;
|
|
2251
|
+
batchAction(id: string, action: 'close' | 'pause' | 'resume' | 'cancel' | 'retry'): Promise<BatchRecord>;
|
|
2252
|
+
};
|
|
2253
|
+
/**
|
|
2254
|
+
* The counts, the cost and where the batch stands: what a status line
|
|
2255
|
+
* prints. Flat on purpose (`p.completed`, not `p.counts.completed`).
|
|
2256
|
+
*/
|
|
2257
|
+
type BatchProgress = BatchCounts & {
|
|
2258
|
+
id: string;
|
|
2259
|
+
state: BatchState;
|
|
2260
|
+
/** Every item ever admitted, adopted included. Final once sealed. */
|
|
2261
|
+
total: number;
|
|
2262
|
+
/** How many are terminal. */
|
|
2263
|
+
done: number;
|
|
2264
|
+
cost: BatchCost;
|
|
2265
|
+
pausedReason?: string;
|
|
2266
|
+
};
|
|
2267
|
+
type FlushOptions = {
|
|
2268
|
+
/** Price the pages, admit nothing: the buffer stays for a real flush. */
|
|
2269
|
+
dryRun?: boolean;
|
|
2270
|
+
};
|
|
2271
|
+
type WaitOptions = {
|
|
2272
|
+
/** Called after every poll that returned, whether or not the counts moved. */
|
|
2273
|
+
onProgress?: (progress: BatchProgress) => void;
|
|
2274
|
+
signal?: AbortSignal;
|
|
2275
|
+
};
|
|
2276
|
+
declare class Batch {
|
|
2277
|
+
readonly id: string;
|
|
2278
|
+
readonly key: string;
|
|
2279
|
+
private latest;
|
|
2280
|
+
/** The flush in flight, or the last one, settled either way. */
|
|
2281
|
+
private flushing;
|
|
2282
|
+
private readonly buffer;
|
|
2283
|
+
/** `buffer[i]` as JSON is about `sizes[i]` bytes. Kept beside it so a
|
|
2284
|
+
* page can be closed on bytes without serializing anything twice. */
|
|
2285
|
+
private readonly sizes;
|
|
2286
|
+
private bytes;
|
|
2287
|
+
private readonly client;
|
|
2288
|
+
/** The clock `wait()` measures its patience with. A test turns it. */
|
|
2289
|
+
private clock;
|
|
2290
|
+
/** @internal `bakery3.batches.open()` and `.get()` make these. */
|
|
2291
|
+
constructor(client: BatchTransport, record: BatchRecord);
|
|
2292
|
+
/** Buffer an item. Nothing is sent until `flush()`. */
|
|
2293
|
+
add(item: BatchItemRequest): this;
|
|
2294
|
+
/**
|
|
2295
|
+
* `add`, and send a page when one is full. Awaiting it is the
|
|
2296
|
+
* backpressure: a loop over a file, a cursor or a generator of any length
|
|
2297
|
+
* holds one page at most.
|
|
2298
|
+
*
|
|
2299
|
+
* for await (const item of source) await batch.write(item);
|
|
2300
|
+
* await batch.close();
|
|
2301
|
+
*/
|
|
2302
|
+
write(item: BatchItemRequest): Promise<void>;
|
|
2303
|
+
/** Items added and not yet sent. */
|
|
2304
|
+
get buffered(): number;
|
|
2305
|
+
/** About how many bytes of JSON those items are. */
|
|
2306
|
+
get bufferedBytes(): number;
|
|
2307
|
+
/**
|
|
2308
|
+
* Price items without buffering or admitting them: the estimate, the
|
|
2309
|
+
* maximum and any refusal, per item, in the order given. Paged like a
|
|
2310
|
+
* flush. An item already completed under its key is quoted as adopted, at
|
|
2311
|
+
* nothing, which makes this the price of a re-run too.
|
|
2312
|
+
*/
|
|
2313
|
+
quote(items: BatchItemRequest[]): Promise<BatchItemOutcome[]>;
|
|
2314
|
+
/**
|
|
2315
|
+
* Send the buffer in pages: `BATCH_PAGE_SIZE` items or `BATCH_PAGE_BYTES`
|
|
2316
|
+
* of JSON, whichever a page reaches first.
|
|
2317
|
+
*
|
|
2318
|
+
* Returns one outcome per item sent: admitted, adopted, or refused alone
|
|
2319
|
+
* (`outcome.error`). A page refused whole throws `BatchPageRefused` and
|
|
2320
|
+
* leaves that page and everything behind it in the buffer, so the caller
|
|
2321
|
+
* can top up and call `flush()` again; the error carries the outcomes of
|
|
2322
|
+
* the pages that did get through.
|
|
2323
|
+
*/
|
|
2324
|
+
flush(options?: FlushOptions): Promise<BatchItemOutcome[]>;
|
|
2325
|
+
private flushNow;
|
|
2326
|
+
/** Seal: no more items. `completed` fires once the last one is terminal.
|
|
2327
|
+
* Anything still buffered is flushed first, because closing over unsent
|
|
2328
|
+
* items would quietly drop the tail of a catalog. */
|
|
2329
|
+
close(): Promise<BatchRecord>;
|
|
2330
|
+
/** Stop feeding the queue; running items finish. */
|
|
2331
|
+
pause(): Promise<BatchRecord>;
|
|
2332
|
+
resume(): Promise<BatchRecord>;
|
|
2333
|
+
/** Cancel every non-terminal item. What completed stays completed: it rendered. */
|
|
2334
|
+
cancel(): Promise<BatchRecord>;
|
|
2335
|
+
/** Re-queue every dead-lettered item, one new attempt each. */
|
|
2336
|
+
retry(): Promise<BatchRecord>;
|
|
2337
|
+
/** The latest record this batch has seen. No request. */
|
|
2338
|
+
record(): BatchRecord;
|
|
2339
|
+
/** Fetch the record now. */
|
|
2340
|
+
refresh(): Promise<BatchRecord>;
|
|
2341
|
+
/** Counts and cost from the latest record. No request. */
|
|
2342
|
+
progress(): BatchProgress;
|
|
2343
|
+
/**
|
|
2344
|
+
* Wait for the batch to finish: one long-poll loop on the batch record
|
|
2345
|
+
* until it is `completed` or `cancelled` (or sealed with every item
|
|
2346
|
+
* terminal). Never a poll per render.
|
|
2347
|
+
*
|
|
2348
|
+
* A paused batch is waited on, not given up on (someone may resume it from
|
|
2349
|
+
* the console), and `onProgress` sees `pausedReason` meanwhile. Pass
|
|
2350
|
+
* `signal` to stop waiting; the batch itself is untouched.
|
|
2351
|
+
*
|
|
2352
|
+
* A fault that clears is ridden out. One that does not (five minutes with
|
|
2353
|
+
* not one poll through) rejects with an error that says so and carries the
|
|
2354
|
+
* last fault's code. The batch renders on regardless, and `wait()` can be
|
|
2355
|
+
* called again.
|
|
2356
|
+
*/
|
|
2357
|
+
wait(options?: WaitOptions): Promise<BatchRecord>;
|
|
2358
|
+
/** Every item, or every item in one state, walked with the cursor. */
|
|
2359
|
+
items(options?: {
|
|
2360
|
+
state?: RenderState;
|
|
2361
|
+
}): AsyncIterable<BatchItemRecord>;
|
|
2362
|
+
/** The ledger, line by line, as the server streams it. */
|
|
2363
|
+
export(): AsyncIterable<BatchExportLine>;
|
|
2364
|
+
private apply;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
/**
|
|
2368
|
+
* A set of pictures: what every many-pictures call hands back.
|
|
2369
|
+
*
|
|
2370
|
+
* `render({ cameras })` and `renderVariants()` in the browser, and
|
|
2371
|
+
* `renderBatch()` from Node, all record into a batch and return one of
|
|
2372
|
+
* these, so a set of three and a catalog of six thousand are the same object
|
|
2373
|
+
* with the same guarantees: one ceiling for all of it, one poll for all of
|
|
2374
|
+
* it, results walked a page at a time and never held.
|
|
2375
|
+
*
|
|
2376
|
+
* No three.js here: the server entry imports this file.
|
|
2377
|
+
*/
|
|
2378
|
+
|
|
2379
|
+
/** One finished picture of a set. */
|
|
2380
|
+
type RenderSetImage = {
|
|
2381
|
+
/** The item's key: `variant/finish/camera`, as recorded. */
|
|
2382
|
+
key: string;
|
|
2383
|
+
/** The render's id. */
|
|
2384
|
+
id: string;
|
|
2385
|
+
variant?: string;
|
|
2386
|
+
finish?: string;
|
|
2387
|
+
camera?: string;
|
|
2388
|
+
/** The shot this is, for a set recorded with shots. */
|
|
2389
|
+
shot?: string;
|
|
2390
|
+
/** `'video'` when the url is a clip. */
|
|
2391
|
+
kind?: 'image' | 'video';
|
|
2392
|
+
/** Signed and expiring. Copy the bytes; do not serve from it. */
|
|
2393
|
+
url: string;
|
|
2394
|
+
width: number;
|
|
2395
|
+
height: number;
|
|
2396
|
+
/** What this picture cost. Nothing, when it was adopted from an earlier run. */
|
|
2397
|
+
cost: number;
|
|
2398
|
+
metadata?: Record<string, unknown>;
|
|
2399
|
+
result: RenderResult;
|
|
2400
|
+
};
|
|
2401
|
+
/** One picture that did not render, and why. */
|
|
2402
|
+
type RenderSetFailure = {
|
|
2403
|
+
key: string;
|
|
2404
|
+
id: string;
|
|
2405
|
+
variant?: string;
|
|
2406
|
+
finish?: string;
|
|
2407
|
+
camera?: string;
|
|
2408
|
+
shot?: string;
|
|
2409
|
+
error: Bakery3Error;
|
|
2410
|
+
metadata?: Record<string, unknown>;
|
|
2411
|
+
};
|
|
2412
|
+
/** One picture turned down when it was submitted: it has no render and never will. */
|
|
2413
|
+
type RenderSetRefusal = {
|
|
2414
|
+
key: string;
|
|
2415
|
+
variant?: string;
|
|
2416
|
+
finish?: string;
|
|
2417
|
+
camera?: string;
|
|
2418
|
+
shot?: string;
|
|
2419
|
+
error: RenderError;
|
|
2420
|
+
};
|
|
2421
|
+
/** What a recording did, in numbers small enough to keep. */
|
|
2422
|
+
type Recorded = {
|
|
2423
|
+
variants: number;
|
|
2424
|
+
images: number;
|
|
2425
|
+
/** Already rendered under their keys in an earlier run. Cost nothing. */
|
|
2426
|
+
adopted: number;
|
|
2427
|
+
/** Turned down alone at admission (a spec that does not parse, an item over its own ceiling). */
|
|
2428
|
+
refused: number;
|
|
2429
|
+
/** The first of those, with their reasons. Capped, because a broken pipeline refuses every item. */
|
|
2430
|
+
refusals: RenderSetRefusal[];
|
|
2431
|
+
};
|
|
2432
|
+
declare class RenderSet {
|
|
2433
|
+
/** The batch underneath: pause, resume, retry, the export. */
|
|
2434
|
+
readonly batch: Batch;
|
|
2435
|
+
readonly id: string;
|
|
2436
|
+
readonly key: string;
|
|
2437
|
+
/** What the call that made this set recorded. Empty for a set fetched by id. */
|
|
2438
|
+
readonly recorded: Recorded;
|
|
2439
|
+
/** Item key → position, for a set small enough to have asked for an order. */
|
|
2440
|
+
private order?;
|
|
2441
|
+
constructor(batch: Batch, recorded?: Recorded);
|
|
2442
|
+
/** Counts and cost from the latest record. No request. */
|
|
2443
|
+
progress(): BatchProgress;
|
|
2444
|
+
/**
|
|
2445
|
+
* Until every picture is terminal. One long-poll on the set, however many
|
|
2446
|
+
* pictures it has. Resolves with the final counts: pictures that failed
|
|
2447
|
+
* are in `failures()`, they do not reject the wait.
|
|
2448
|
+
*/
|
|
2449
|
+
wait(options?: WaitOptions): Promise<BatchProgress>;
|
|
2450
|
+
/**
|
|
2451
|
+
* Every finished picture, a page at a time. Safe on a set of any size,
|
|
2452
|
+
* and safe to call before the set is done: it walks what has completed
|
|
2453
|
+
* so far.
|
|
2454
|
+
*/
|
|
2455
|
+
images(): AsyncIterable<RenderSetImage>;
|
|
2456
|
+
/**
|
|
2457
|
+
* Every picture that did not render, with the reason and the fix.
|
|
2458
|
+
* `error.retryable` tells the two kinds apart: false is a refusal, the
|
|
2459
|
+
* scene's fault and the same next time; true ran out of attempts on a
|
|
2460
|
+
* fault of ours, and `set.batch.retry()` queues it again.
|
|
2461
|
+
*/
|
|
2462
|
+
failures(): AsyncIterable<RenderSetFailure>;
|
|
2463
|
+
/**
|
|
2464
|
+
* Wait, then every picture in one array, in camera order for a
|
|
2465
|
+
* `render({ cameras })`: one picture per camera, or a rejection. Rejects
|
|
2466
|
+
* with `RenderSetRefused` when a picture was turned down at submission
|
|
2467
|
+
* (it has no render, and the array would be one short with every later
|
|
2468
|
+
* picture in the wrong place), and otherwise with the first failure, as
|
|
2469
|
+
* `job.result()` does. The pictures that did render are still there:
|
|
2470
|
+
* walk `images()`, each named by its `camera`.
|
|
2471
|
+
*
|
|
2472
|
+
* It collects, so it is for a handful of pictures. For a catalog, walk
|
|
2473
|
+
* `images()`.
|
|
2474
|
+
*/
|
|
2475
|
+
result(options?: WaitOptions): Promise<RenderSetImage[]>;
|
|
2476
|
+
/** Cancel every picture that has not finished. What completed, completed. */
|
|
2477
|
+
cancel(): Promise<void>;
|
|
2478
|
+
/** @internal */
|
|
2479
|
+
ordered(keys: string[]): this;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
/**
|
|
2483
|
+
* The test run: a few products, every camera, small and fast.
|
|
2484
|
+
*
|
|
2485
|
+
* Before six thousand pictures are paid for, three products are worth
|
|
2486
|
+
* looking at: are the cameras where they were meant to be, is the room the
|
|
2487
|
+
* right way round, does the finish list hide what it should. A test run is
|
|
2488
|
+
* the real run's own options with three changes, made here so the browser
|
|
2489
|
+
* and the server make the same ones:
|
|
2490
|
+
*
|
|
2491
|
+
* - A sample of the products. From an array: the first, the middle and the
|
|
2492
|
+
* last, because a catalog is usually sorted and its ends differ most.
|
|
2493
|
+
* From anything else (a generator, a cursor): the first three, since a
|
|
2494
|
+
* list that is never collected has no middle to ask for.
|
|
2495
|
+
* - Preview quality at a small size, the long edge 512 unless told
|
|
2496
|
+
* otherwise, the aspect kept, so the framing is the real run's framing.
|
|
2497
|
+
* - Its own keys. A test is looked at and thrown away; if it shared the real
|
|
2498
|
+
* run's keys, the real run would adopt these little previews as finished
|
|
2499
|
+
* pictures. Every test also differs from the last one, so a test after a
|
|
2500
|
+
* fix shows the fix and not yesterday's frame.
|
|
2501
|
+
*
|
|
2502
|
+
* No three.js here: the server entry imports this file.
|
|
2503
|
+
*/
|
|
2504
|
+
|
|
2505
|
+
type TestRunOptions = {
|
|
2506
|
+
/** How many products. Default 3. */
|
|
2507
|
+
products?: number;
|
|
2508
|
+
/** The long edge of each picture, in pixels. Default 512. */
|
|
2509
|
+
size?: number;
|
|
2510
|
+
/** The ceiling for the test, in dollars. Default 5. */
|
|
2511
|
+
maxCost?: number;
|
|
2512
|
+
};
|
|
2513
|
+
|
|
2514
|
+
/**
|
|
2515
|
+
* Cameras without a scene graph: where to stand to photograph a box.
|
|
2516
|
+
*
|
|
2517
|
+
* In the browser a camera is an object the developer placed. From a server
|
|
2518
|
+
* there is a model's bounds (`GlbFacts.bounds`, read at import) and nothing
|
|
2519
|
+
* else, so a view is described the way a photographer would: from which
|
|
2520
|
+
* side, how high, how tight. `framedCamera` turns that into the same
|
|
2521
|
+
* `CameraSpec` a captured camera becomes.
|
|
2522
|
+
*
|
|
2523
|
+
* No three.js here: the server entry imports this file.
|
|
2524
|
+
*/
|
|
2525
|
+
|
|
2526
|
+
/** Where to look from: degrees around the model from its front (+Z, where glTF says a model faces) toward its right (+X), and degrees above the horizon. */
|
|
2527
|
+
type Angle = {
|
|
2528
|
+
azimuthDeg: number;
|
|
2529
|
+
elevationDeg?: number;
|
|
2530
|
+
};
|
|
2531
|
+
/** The framings with names. */
|
|
2532
|
+
declare const VIEW_WORDS: {
|
|
2533
|
+
readonly front: {
|
|
2534
|
+
readonly azimuthDeg: 0;
|
|
2535
|
+
readonly elevationDeg: 6;
|
|
2536
|
+
};
|
|
2537
|
+
readonly 'three-quarter': {
|
|
2538
|
+
readonly azimuthDeg: 35;
|
|
2539
|
+
readonly elevationDeg: 14;
|
|
2540
|
+
};
|
|
2541
|
+
readonly side: {
|
|
2542
|
+
readonly azimuthDeg: 90;
|
|
2543
|
+
readonly elevationDeg: 6;
|
|
2544
|
+
};
|
|
2545
|
+
readonly back: {
|
|
2546
|
+
readonly azimuthDeg: 180;
|
|
2547
|
+
readonly elevationDeg: 6;
|
|
2548
|
+
};
|
|
2549
|
+
readonly 'three-quarter-back': {
|
|
2550
|
+
readonly azimuthDeg: 145;
|
|
2551
|
+
readonly elevationDeg: 14;
|
|
2552
|
+
};
|
|
2553
|
+
readonly top: {
|
|
2554
|
+
readonly azimuthDeg: 0;
|
|
2555
|
+
readonly elevationDeg: 88;
|
|
2556
|
+
};
|
|
2557
|
+
};
|
|
2558
|
+
type ViewWord = keyof typeof VIEW_WORDS;
|
|
2559
|
+
|
|
2560
|
+
declare const HDRI_PRESETS: {
|
|
2561
|
+
readonly pergola_walkway: {
|
|
2562
|
+
readonly label: "Pergola Walkway";
|
|
2563
|
+
readonly description: "A garden walkway under a vine-covered pergola, high sun, greenery on every side.";
|
|
2564
|
+
readonly category: "day";
|
|
2565
|
+
readonly groundHeight: 1.6;
|
|
2566
|
+
readonly skyIrradiance: 1.7631801999999999;
|
|
2567
|
+
readonly preview: {
|
|
2568
|
+
readonly file: "pergola_walkway_1k.hdr";
|
|
2569
|
+
readonly resolution: 1024;
|
|
2570
|
+
};
|
|
2571
|
+
readonly render: {
|
|
2572
|
+
readonly file: "pergola_walkway_4k.exr";
|
|
2573
|
+
readonly resolution: 4096;
|
|
2574
|
+
};
|
|
2575
|
+
};
|
|
2576
|
+
readonly sunny_country_road: {
|
|
2577
|
+
readonly label: "Sunny Country Road";
|
|
2578
|
+
readonly description: "A dirt road between fields under a high midday sun.";
|
|
2579
|
+
readonly category: "day";
|
|
2580
|
+
readonly groundHeight: 1.6;
|
|
2581
|
+
readonly skyIrradiance: 1.0989418;
|
|
2582
|
+
readonly preview: {
|
|
2583
|
+
readonly file: "sunny_country_road_1k.hdr";
|
|
2584
|
+
readonly resolution: 1024;
|
|
2585
|
+
};
|
|
2586
|
+
readonly render: {
|
|
2587
|
+
readonly file: "sunny_country_road_4k.exr";
|
|
2588
|
+
readonly resolution: 4096;
|
|
2589
|
+
};
|
|
2590
|
+
};
|
|
2591
|
+
readonly "horn-koppe_spring": {
|
|
2592
|
+
readonly label: "Horn-Koppe Spring";
|
|
2593
|
+
readonly description: "Alpine meadow in spring, snow on the peaks, a high clear sun.";
|
|
2594
|
+
readonly category: "day";
|
|
2595
|
+
readonly groundHeight: 1.6;
|
|
2596
|
+
readonly skyIrradiance: 1.4612264;
|
|
2597
|
+
readonly preview: {
|
|
2598
|
+
readonly file: "horn-koppe_spring_1k.hdr";
|
|
2599
|
+
readonly resolution: 1024;
|
|
2600
|
+
};
|
|
2601
|
+
readonly render: {
|
|
2602
|
+
readonly file: "horn-koppe_spring_4k.exr";
|
|
2603
|
+
readonly resolution: 4096;
|
|
2604
|
+
};
|
|
2605
|
+
};
|
|
2606
|
+
readonly grasslands_sunset: {
|
|
2607
|
+
readonly label: "Grasslands Late Sun";
|
|
2608
|
+
readonly description: "Open grassland with the sun low but still white, a long afternoon.";
|
|
2609
|
+
readonly category: "day";
|
|
2610
|
+
readonly groundHeight: 1.6;
|
|
2611
|
+
readonly skyIrradiance: 3.9029496;
|
|
2612
|
+
readonly preview: {
|
|
2613
|
+
readonly file: "grasslands_sunset_1k.hdr";
|
|
2614
|
+
readonly resolution: 1024;
|
|
2615
|
+
};
|
|
2616
|
+
readonly render: {
|
|
2617
|
+
readonly file: "grasslands_sunset_4k.exr";
|
|
2618
|
+
readonly resolution: 4096;
|
|
2619
|
+
};
|
|
2620
|
+
};
|
|
2621
|
+
readonly je_gray_02: {
|
|
2622
|
+
readonly label: "Grey Park";
|
|
2623
|
+
readonly description: "A shaded park path, the sun low behind trees, a soft, cool day.";
|
|
2624
|
+
readonly category: "day";
|
|
2625
|
+
readonly groundHeight: 1.6;
|
|
2626
|
+
readonly skyIrradiance: 0.21612759999999998;
|
|
2627
|
+
readonly preview: {
|
|
2628
|
+
readonly file: "je_gray_02_1k.hdr";
|
|
2629
|
+
readonly resolution: 1024;
|
|
2630
|
+
};
|
|
2631
|
+
readonly render: {
|
|
2632
|
+
readonly file: "je_gray_02_4k.exr";
|
|
2633
|
+
readonly resolution: 4096;
|
|
2634
|
+
};
|
|
2635
|
+
};
|
|
2636
|
+
readonly qwantani_sunset: {
|
|
2637
|
+
readonly label: "Qwantani Sunset";
|
|
2638
|
+
readonly description: "Sunset over the veld: a warm horizon and a clear deep sky above.";
|
|
2639
|
+
readonly category: "golden";
|
|
2640
|
+
readonly groundHeight: 1.6;
|
|
2641
|
+
readonly skyIrradiance: 2.3187784;
|
|
2642
|
+
readonly preview: {
|
|
2643
|
+
readonly file: "qwantani_sunset_1k.hdr";
|
|
2644
|
+
readonly resolution: 1024;
|
|
2645
|
+
};
|
|
2646
|
+
readonly render: {
|
|
2647
|
+
readonly file: "qwantani_sunset_4k.exr";
|
|
2648
|
+
readonly resolution: 4096;
|
|
2649
|
+
};
|
|
2650
|
+
};
|
|
2651
|
+
readonly venice_sunset: {
|
|
2652
|
+
readonly label: "Venice Sunset";
|
|
2653
|
+
readonly description: "The lagoon at sunset, water and sky both gold.";
|
|
2654
|
+
readonly category: "golden";
|
|
2655
|
+
readonly groundHeight: 1.6;
|
|
2656
|
+
readonly skyIrradiance: 2.1780174;
|
|
2657
|
+
readonly preview: {
|
|
2658
|
+
readonly file: "venice_sunset_1k.hdr";
|
|
2659
|
+
readonly resolution: 1024;
|
|
2660
|
+
};
|
|
2661
|
+
readonly render: {
|
|
2662
|
+
readonly file: "venice_sunset_4k.exr";
|
|
2663
|
+
readonly resolution: 4096;
|
|
2664
|
+
};
|
|
2665
|
+
};
|
|
2666
|
+
readonly bambanani_sunset: {
|
|
2667
|
+
readonly label: "Bambanani Sunset";
|
|
2668
|
+
readonly description: "A township street at sunset, the sun red and low.";
|
|
2669
|
+
readonly category: "golden";
|
|
2670
|
+
readonly groundHeight: 1.6;
|
|
2671
|
+
readonly skyIrradiance: 3.300283399999999;
|
|
2672
|
+
readonly preview: {
|
|
2673
|
+
readonly file: "bambanani_sunset_1k.hdr";
|
|
2674
|
+
readonly resolution: 1024;
|
|
2675
|
+
};
|
|
2676
|
+
readonly render: {
|
|
2677
|
+
readonly file: "bambanani_sunset_4k.exr";
|
|
2678
|
+
readonly resolution: 4096;
|
|
2679
|
+
};
|
|
2680
|
+
};
|
|
2681
|
+
readonly umhlanga_sunrise: {
|
|
2682
|
+
readonly label: "Umhlanga Sunrise";
|
|
2683
|
+
readonly description: "Sunrise over the sea, the sun just clear of the horizon.";
|
|
2684
|
+
readonly category: "golden";
|
|
2685
|
+
readonly groundHeight: 1.6;
|
|
2686
|
+
readonly skyIrradiance: 3.3150258000000004;
|
|
2687
|
+
readonly preview: {
|
|
2688
|
+
readonly file: "umhlanga_sunrise_1k.hdr";
|
|
2689
|
+
readonly resolution: 1024;
|
|
2690
|
+
};
|
|
2691
|
+
readonly render: {
|
|
2692
|
+
readonly file: "umhlanga_sunrise_4k.exr";
|
|
2693
|
+
readonly resolution: 4096;
|
|
2694
|
+
};
|
|
2695
|
+
};
|
|
2696
|
+
readonly flamingo_pan: {
|
|
2697
|
+
readonly label: "Flamingo Pan";
|
|
2698
|
+
readonly description: "A salt pan at sunset, the sun on the horizon and a violet sky.";
|
|
2699
|
+
readonly category: "golden";
|
|
2700
|
+
readonly groundHeight: 1.6;
|
|
2701
|
+
readonly skyIrradiance: 3.1940932;
|
|
2702
|
+
readonly preview: {
|
|
2703
|
+
readonly file: "flamingo_pan_1k.hdr";
|
|
2704
|
+
readonly resolution: 1024;
|
|
2705
|
+
};
|
|
2706
|
+
readonly render: {
|
|
2707
|
+
readonly file: "flamingo_pan_4k.exr";
|
|
2708
|
+
readonly resolution: 4096;
|
|
2709
|
+
};
|
|
2710
|
+
};
|
|
2711
|
+
readonly minedump_flats: {
|
|
2712
|
+
readonly label: "Minedump Flats";
|
|
2713
|
+
readonly description: "Flat scrub at sunset under a hazy, dusty sky.";
|
|
2714
|
+
readonly category: "golden";
|
|
2715
|
+
readonly groundHeight: 1.6;
|
|
2716
|
+
readonly skyIrradiance: 2.9883286;
|
|
2717
|
+
readonly preview: {
|
|
2718
|
+
readonly file: "minedump_flats_1k.hdr";
|
|
2719
|
+
readonly resolution: 1024;
|
|
2720
|
+
};
|
|
2721
|
+
readonly render: {
|
|
2722
|
+
readonly file: "minedump_flats_4k.exr";
|
|
2723
|
+
readonly resolution: 4096;
|
|
2724
|
+
};
|
|
2725
|
+
};
|
|
2726
|
+
readonly sunset_fairway: {
|
|
2727
|
+
readonly label: "Sunset Fairway";
|
|
2728
|
+
readonly description: "A golf fairway at dusk, the sun already down and the sky lit from below.";
|
|
2729
|
+
readonly category: "golden";
|
|
2730
|
+
readonly groundHeight: 1.6;
|
|
2731
|
+
readonly skyIrradiance: 2.7264117999999997;
|
|
2732
|
+
readonly preview: {
|
|
2733
|
+
readonly file: "sunset_fairway_1k.hdr";
|
|
2734
|
+
readonly resolution: 1024;
|
|
2735
|
+
};
|
|
2736
|
+
readonly render: {
|
|
2737
|
+
readonly file: "sunset_fairway_4k.exr";
|
|
2738
|
+
readonly resolution: 4096;
|
|
2739
|
+
};
|
|
2740
|
+
};
|
|
2741
|
+
readonly the_sky_is_on_fire: {
|
|
2742
|
+
readonly label: "Sky on Fire";
|
|
2743
|
+
readonly description: "A blazing red-orange sunset over open country.";
|
|
2744
|
+
readonly category: "golden";
|
|
2745
|
+
readonly groundHeight: 1.6;
|
|
2746
|
+
readonly skyIrradiance: 2.4784194;
|
|
2747
|
+
readonly preview: {
|
|
2748
|
+
readonly file: "the_sky_is_on_fire_1k.hdr";
|
|
2749
|
+
readonly resolution: 1024;
|
|
2750
|
+
};
|
|
2751
|
+
readonly render: {
|
|
2752
|
+
readonly file: "the_sky_is_on_fire_4k.exr";
|
|
2753
|
+
readonly resolution: 4096;
|
|
2754
|
+
};
|
|
2755
|
+
};
|
|
2756
|
+
readonly dalkey_view: {
|
|
2757
|
+
readonly label: "Dalkey View";
|
|
2758
|
+
readonly description: "A coastal hillside under a bright overcast sky.";
|
|
2759
|
+
readonly category: "overcast";
|
|
2760
|
+
readonly groundHeight: 1.6;
|
|
2761
|
+
readonly skyIrradiance: 4.501861400000001;
|
|
2762
|
+
readonly preview: {
|
|
2763
|
+
readonly file: "dalkey_view_1k.hdr";
|
|
2764
|
+
readonly resolution: 1024;
|
|
2765
|
+
};
|
|
2766
|
+
readonly render: {
|
|
2767
|
+
readonly file: "dalkey_view_4k.exr";
|
|
2768
|
+
readonly resolution: 4096;
|
|
2769
|
+
};
|
|
2770
|
+
};
|
|
2771
|
+
readonly afrikaans_church_exterior: {
|
|
2772
|
+
readonly label: "Church Square";
|
|
2773
|
+
readonly description: "A quiet square under heavy cloud, a white church across it.";
|
|
2774
|
+
readonly category: "overcast";
|
|
2775
|
+
readonly groundHeight: 1.6;
|
|
2776
|
+
readonly skyIrradiance: 4.194848599999999;
|
|
2777
|
+
readonly preview: {
|
|
2778
|
+
readonly file: "afrikaans_church_exterior_1k.hdr";
|
|
2779
|
+
readonly resolution: 1024;
|
|
2780
|
+
};
|
|
2781
|
+
readonly render: {
|
|
2782
|
+
readonly file: "afrikaans_church_exterior_4k.exr";
|
|
2783
|
+
readonly resolution: 4096;
|
|
2784
|
+
};
|
|
2785
|
+
};
|
|
2786
|
+
readonly quarry_cloudy: {
|
|
2787
|
+
readonly label: "Cloudy Quarry";
|
|
2788
|
+
readonly description: "A stone quarry under an even grey sky.";
|
|
2789
|
+
readonly category: "overcast";
|
|
2790
|
+
readonly groundHeight: 1.6;
|
|
2791
|
+
readonly skyIrradiance: 3.9304787999999995;
|
|
2792
|
+
readonly preview: {
|
|
2793
|
+
readonly file: "quarry_cloudy_1k.hdr";
|
|
2794
|
+
readonly resolution: 1024;
|
|
2795
|
+
};
|
|
2796
|
+
readonly render: {
|
|
2797
|
+
readonly file: "quarry_cloudy_4k.exr";
|
|
2798
|
+
readonly resolution: 4096;
|
|
2799
|
+
};
|
|
2800
|
+
};
|
|
2801
|
+
readonly moonless_golf: {
|
|
2802
|
+
readonly label: "Moonless Golf Course";
|
|
2803
|
+
readonly description: "A moonless night over a golf course, a faint glow from the town.";
|
|
2804
|
+
readonly category: "night";
|
|
2805
|
+
readonly groundHeight: 1.6;
|
|
2806
|
+
readonly skyIrradiance: 0.5007998;
|
|
2807
|
+
readonly preview: {
|
|
2808
|
+
readonly file: "moonless_golf_1k.hdr";
|
|
2809
|
+
readonly resolution: 1024;
|
|
2810
|
+
};
|
|
2811
|
+
readonly render: {
|
|
2812
|
+
readonly file: "moonless_golf_4k.exr";
|
|
2813
|
+
readonly resolution: 4096;
|
|
2814
|
+
};
|
|
2815
|
+
};
|
|
2816
|
+
readonly preller_drive: {
|
|
2817
|
+
readonly label: "Preller Drive";
|
|
2818
|
+
readonly description: "A suburban street at night, sodium street lamps and dark trees.";
|
|
2819
|
+
readonly category: "night";
|
|
2820
|
+
readonly groundHeight: 1.6;
|
|
2821
|
+
readonly skyIrradiance: 0.5283702;
|
|
2822
|
+
readonly preview: {
|
|
2823
|
+
readonly file: "preller_drive_1k.hdr";
|
|
2824
|
+
readonly resolution: 1024;
|
|
2825
|
+
};
|
|
2826
|
+
readonly render: {
|
|
2827
|
+
readonly file: "preller_drive_4k.exr";
|
|
2828
|
+
readonly resolution: 4096;
|
|
2829
|
+
};
|
|
2830
|
+
};
|
|
2831
|
+
readonly satara_night: {
|
|
2832
|
+
readonly label: "Satara Night";
|
|
2833
|
+
readonly description: "A bush camp at night, a few lamps and a starry sky.";
|
|
2834
|
+
readonly category: "night";
|
|
2835
|
+
readonly groundHeight: 1.6;
|
|
2836
|
+
readonly skyIrradiance: 0.25447;
|
|
2837
|
+
readonly preview: {
|
|
2838
|
+
readonly file: "satara_night_1k.hdr";
|
|
2839
|
+
readonly resolution: 1024;
|
|
2840
|
+
};
|
|
2841
|
+
readonly render: {
|
|
2842
|
+
readonly file: "satara_night_4k.exr";
|
|
2843
|
+
readonly resolution: 4096;
|
|
2844
|
+
};
|
|
2845
|
+
};
|
|
2846
|
+
readonly ferndale_studio_11: {
|
|
2847
|
+
readonly label: "Ferndale Studio 11";
|
|
2848
|
+
readonly description: "High-contrast studio environment with broad soft sources.";
|
|
2849
|
+
readonly category: "studio";
|
|
2850
|
+
readonly groundHeight: 1.2;
|
|
2851
|
+
readonly skyIrradiance: 1.2286402;
|
|
2852
|
+
readonly preview: {
|
|
2853
|
+
readonly file: "ferndale_studio_11_1k.hdr";
|
|
2854
|
+
readonly resolution: 1024;
|
|
2855
|
+
};
|
|
2856
|
+
readonly render: {
|
|
2857
|
+
readonly file: "ferndale_studio_11_4k.exr";
|
|
2858
|
+
readonly resolution: 4096;
|
|
2859
|
+
};
|
|
2860
|
+
};
|
|
2861
|
+
readonly ferndale_studio_12: {
|
|
2862
|
+
readonly label: "Ferndale Studio 12";
|
|
2863
|
+
readonly description: "Balanced product studio with shaped highlights.";
|
|
2864
|
+
readonly category: "studio";
|
|
2865
|
+
readonly groundHeight: 1.2;
|
|
2866
|
+
readonly skyIrradiance: 1.2324262000000001;
|
|
2867
|
+
readonly preview: {
|
|
2868
|
+
readonly file: "ferndale_studio_12_1k.hdr";
|
|
2869
|
+
readonly resolution: 1024;
|
|
2870
|
+
};
|
|
2871
|
+
readonly render: {
|
|
2872
|
+
readonly file: "ferndale_studio_12_4k.exr";
|
|
2873
|
+
readonly resolution: 4096;
|
|
2874
|
+
};
|
|
2875
|
+
};
|
|
2876
|
+
readonly monochrome_studio_02: {
|
|
2877
|
+
readonly label: "Monochrome Studio 02";
|
|
2878
|
+
readonly description: "Neutral monochrome studio for material-focused renders.";
|
|
2879
|
+
readonly category: "studio";
|
|
2880
|
+
readonly groundHeight: 1.2;
|
|
2881
|
+
readonly skyIrradiance: 0.9193722;
|
|
2882
|
+
readonly preview: {
|
|
2883
|
+
readonly file: "monochrome_studio_02_1k.hdr";
|
|
2884
|
+
readonly resolution: 1024;
|
|
2885
|
+
};
|
|
2886
|
+
readonly render: {
|
|
2887
|
+
readonly file: "monochrome_studio_02_4k.exr";
|
|
2888
|
+
readonly resolution: 4096;
|
|
2889
|
+
};
|
|
2890
|
+
};
|
|
2891
|
+
readonly studio_kontrast_04: {
|
|
2892
|
+
readonly label: "Studio Kontrast 04";
|
|
2893
|
+
readonly description: "Directional studio contrast with crisp reflections.";
|
|
2894
|
+
readonly category: "studio";
|
|
2895
|
+
readonly groundHeight: 1.2;
|
|
2896
|
+
readonly skyIrradiance: 2.9173432;
|
|
2897
|
+
readonly preview: {
|
|
2898
|
+
readonly file: "studio_kontrast_04_1k.hdr";
|
|
2899
|
+
readonly resolution: 1024;
|
|
2900
|
+
};
|
|
2901
|
+
readonly render: {
|
|
2902
|
+
readonly file: "studio_kontrast_04_4k.exr";
|
|
2903
|
+
readonly resolution: 4096;
|
|
2904
|
+
};
|
|
2905
|
+
};
|
|
2906
|
+
readonly studio_small_03: {
|
|
2907
|
+
readonly label: "Small Studio 03";
|
|
2908
|
+
readonly description: "Compact studio environment with close softboxes.";
|
|
2909
|
+
readonly category: "studio";
|
|
2910
|
+
readonly groundHeight: 1.2;
|
|
2911
|
+
readonly skyIrradiance: 13.877922599999998;
|
|
2912
|
+
readonly preview: {
|
|
2913
|
+
readonly file: "studio_small_03_1k.hdr";
|
|
2914
|
+
readonly resolution: 1024;
|
|
2915
|
+
};
|
|
2916
|
+
readonly render: {
|
|
2917
|
+
readonly file: "studio_small_03_4k.exr";
|
|
2918
|
+
readonly resolution: 4096;
|
|
2919
|
+
};
|
|
2920
|
+
};
|
|
2921
|
+
};
|
|
2922
|
+
type HdriPresetName = keyof typeof HDRI_PRESETS;
|
|
2923
|
+
|
|
2924
|
+
/**
|
|
2925
|
+
* Generated by the preset library's sync script and by every room publish.
|
|
2926
|
+
* Do not edit.
|
|
2927
|
+
*
|
|
2928
|
+
* The rooms the SDK can offer. `build` is the content hash of the published
|
|
2929
|
+
* room definition; the manifest repeats it and the renderer refuses any
|
|
2930
|
+
* other, so what was framed in the browser is what gets traced.
|
|
2931
|
+
*/
|
|
2932
|
+
type RoomPresetLighting = 'day' | 'golden' | 'overcast' | 'night';
|
|
2933
|
+
declare const ROOM_PRESETS: {
|
|
2934
|
+
readonly atelier_skylight: {
|
|
2935
|
+
readonly label: "Skylight Atelier";
|
|
2936
|
+
readonly description: "Concrete floor, tall plaster walls and two roof lights pouring foliage-broken sun straight down onto a low plinth.";
|
|
2937
|
+
readonly build: "6d26daeb0abb";
|
|
2938
|
+
readonly presetsVersion: 1;
|
|
2939
|
+
readonly size: {
|
|
2940
|
+
readonly width: 5.5;
|
|
2941
|
+
readonly height: 3.4;
|
|
2942
|
+
readonly depth: 5.5;
|
|
2943
|
+
};
|
|
2944
|
+
readonly lighting: "day";
|
|
2945
|
+
readonly surfaces: {
|
|
2946
|
+
readonly floor: {
|
|
2947
|
+
readonly material: "polished_concrete";
|
|
2948
|
+
readonly color: "#ffffff";
|
|
2949
|
+
};
|
|
2950
|
+
readonly walls: {
|
|
2951
|
+
readonly material: "white_plaster";
|
|
2952
|
+
readonly color: "#f2efe8";
|
|
2953
|
+
};
|
|
2954
|
+
readonly ceiling: {
|
|
2955
|
+
readonly material: "matte_paint";
|
|
2956
|
+
readonly color: "#f5f4f0";
|
|
2957
|
+
};
|
|
2958
|
+
readonly sill: {
|
|
2959
|
+
readonly material: "travertine";
|
|
2960
|
+
};
|
|
2961
|
+
};
|
|
2962
|
+
readonly stand: {
|
|
2963
|
+
readonly type: "pedestal_plinth";
|
|
2964
|
+
readonly material: "carrara_marble";
|
|
2965
|
+
readonly x: 0;
|
|
2966
|
+
readonly z: -0.2;
|
|
2967
|
+
readonly rotationDeg: 0;
|
|
2968
|
+
readonly top: 0.3;
|
|
2969
|
+
readonly size: {
|
|
2970
|
+
readonly x: 0.9;
|
|
2971
|
+
readonly y: 0.3;
|
|
2972
|
+
readonly z: 0.9;
|
|
2973
|
+
};
|
|
2974
|
+
};
|
|
2975
|
+
readonly camera: {
|
|
2976
|
+
readonly position: readonly [-1.7918, 1.38, 1.4709];
|
|
2977
|
+
readonly target: readonly [0, 0.55, -0.2];
|
|
2978
|
+
readonly lensMm: 38;
|
|
2979
|
+
readonly fstop: 2.4000000953674316;
|
|
2980
|
+
};
|
|
2981
|
+
readonly preview: {
|
|
2982
|
+
readonly glb: "rooms/atelier_skylight/room.glb";
|
|
2983
|
+
readonly bytes: 1609624;
|
|
2984
|
+
readonly triangles: 71936;
|
|
2985
|
+
readonly textures: 21;
|
|
2986
|
+
};
|
|
2987
|
+
readonly complexity: {
|
|
2988
|
+
readonly enclosure: 1;
|
|
2989
|
+
readonly objects: 64;
|
|
2990
|
+
};
|
|
2991
|
+
readonly prebaked: readonly [];
|
|
2992
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
2993
|
+
readonly standGlb: "rooms/atelier_skylight/stand.glb";
|
|
2994
|
+
readonly plants: readonly ["potted_dracaena_01", "potted_monstera_01"];
|
|
2995
|
+
readonly pictures: readonly [];
|
|
2996
|
+
readonly previewBrightness: {
|
|
2997
|
+
readonly day: 0.775;
|
|
2998
|
+
readonly golden: 0.543;
|
|
2999
|
+
readonly overcast: 0.723;
|
|
3000
|
+
readonly night: 0.243;
|
|
3001
|
+
};
|
|
3002
|
+
readonly previewFill: {
|
|
3003
|
+
readonly day: {
|
|
3004
|
+
readonly sky: readonly [0.861, 1.012, 1.285];
|
|
3005
|
+
readonly ground: readonly [0.861, 1.012, 1.285];
|
|
3006
|
+
};
|
|
3007
|
+
readonly golden: {
|
|
3008
|
+
readonly sky: readonly [1.154, 0.972, 0.825];
|
|
3009
|
+
readonly ground: readonly [1.154, 0.972, 0.825];
|
|
3010
|
+
};
|
|
3011
|
+
readonly overcast: {
|
|
3012
|
+
readonly sky: readonly [0.997, 0.997, 1.041];
|
|
3013
|
+
readonly ground: readonly [0.997, 0.997, 1.041];
|
|
3014
|
+
};
|
|
3015
|
+
readonly night: {
|
|
3016
|
+
readonly sky: readonly [1.488, 0.894, 0.616];
|
|
3017
|
+
readonly ground: readonly [1.488, 0.894, 0.616];
|
|
3018
|
+
};
|
|
3019
|
+
};
|
|
3020
|
+
readonly exposure: {
|
|
3021
|
+
readonly day: 1.741;
|
|
3022
|
+
readonly golden: 1.741;
|
|
3023
|
+
readonly overcast: 1.866;
|
|
3024
|
+
readonly night: 2.144;
|
|
3025
|
+
};
|
|
3026
|
+
readonly hdri: {
|
|
3027
|
+
readonly day: null;
|
|
3028
|
+
readonly golden: null;
|
|
3029
|
+
readonly overcast: null;
|
|
3030
|
+
readonly night: {
|
|
3031
|
+
readonly name: "moonless_golf";
|
|
3032
|
+
readonly rotationDeg: 0;
|
|
3033
|
+
readonly groundHeight: 1.6;
|
|
3034
|
+
};
|
|
3035
|
+
};
|
|
3036
|
+
readonly skyIrradiance: {
|
|
3037
|
+
readonly day: 8.595;
|
|
3038
|
+
readonly golden: 5.273;
|
|
3039
|
+
readonly overcast: 3.286;
|
|
3040
|
+
readonly night: 0.011;
|
|
3041
|
+
};
|
|
3042
|
+
};
|
|
3043
|
+
readonly beam_loft: {
|
|
3044
|
+
readonly label: "Beam Loft";
|
|
3045
|
+
readonly description: "A tall plaster room under oak beams: three paned windows on the east side over a slatted oak ledge, dark boards, a pale modular sofa, a leather lounge chair and an opal globe on a long cord.";
|
|
3046
|
+
readonly build: "306f502d2191";
|
|
3047
|
+
readonly presetsVersion: 1;
|
|
3048
|
+
readonly size: {
|
|
3049
|
+
readonly width: 9;
|
|
3050
|
+
readonly height: 3.7;
|
|
3051
|
+
readonly depth: 7.6;
|
|
3052
|
+
};
|
|
3053
|
+
readonly lighting: "day";
|
|
3054
|
+
readonly surfaces: {
|
|
3055
|
+
readonly floor: {
|
|
3056
|
+
readonly material: "plank_flooring_04";
|
|
3057
|
+
readonly color: "#b7aea4";
|
|
3058
|
+
};
|
|
3059
|
+
readonly walls: {
|
|
3060
|
+
readonly material: "white_plaster";
|
|
3061
|
+
readonly color: "#eeebe5";
|
|
3062
|
+
};
|
|
3063
|
+
readonly ceiling: {
|
|
3064
|
+
readonly material: "white_plaster";
|
|
3065
|
+
readonly color: "#f2f0eb";
|
|
3066
|
+
};
|
|
3067
|
+
readonly sill: {
|
|
3068
|
+
readonly material: "coated_pine";
|
|
3069
|
+
};
|
|
3070
|
+
};
|
|
3071
|
+
readonly stand: {
|
|
3072
|
+
readonly type: "coffee_table_round_01";
|
|
3073
|
+
readonly material: null;
|
|
3074
|
+
readonly x: 0.2;
|
|
3075
|
+
readonly z: -1.5;
|
|
3076
|
+
readonly rotationDeg: 0;
|
|
3077
|
+
readonly top: 0.491;
|
|
3078
|
+
readonly size: {
|
|
3079
|
+
readonly x: 1.3013;
|
|
3080
|
+
readonly y: 0.491;
|
|
3081
|
+
readonly z: 1.3013;
|
|
3082
|
+
};
|
|
3083
|
+
};
|
|
3084
|
+
readonly camera: {
|
|
3085
|
+
readonly position: readonly [-1.7117, 1.3, 2.7937];
|
|
3086
|
+
readonly target: readonly [0.2, 0.741, -1.5];
|
|
3087
|
+
readonly lensMm: 21;
|
|
3088
|
+
readonly fstop: 5.599999904632568;
|
|
3089
|
+
};
|
|
3090
|
+
readonly preview: {
|
|
3091
|
+
readonly glb: "rooms/beam_loft/room.glb";
|
|
3092
|
+
readonly bytes: 2350456;
|
|
3093
|
+
readonly triangles: 102810;
|
|
3094
|
+
readonly textures: 37;
|
|
3095
|
+
};
|
|
3096
|
+
readonly complexity: {
|
|
3097
|
+
readonly enclosure: 1;
|
|
3098
|
+
readonly objects: 51;
|
|
3099
|
+
};
|
|
3100
|
+
readonly prebaked: readonly [];
|
|
3101
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
3102
|
+
readonly standGlb: "rooms/beam_loft/stand.glb";
|
|
3103
|
+
readonly plants: readonly ["potted_dracaena_01", "potted_plant_01"];
|
|
3104
|
+
readonly pictures: readonly [];
|
|
3105
|
+
readonly previewBrightness: {
|
|
3106
|
+
readonly day: 1.877;
|
|
3107
|
+
readonly golden: 2.176;
|
|
3108
|
+
readonly overcast: 0.381;
|
|
3109
|
+
readonly night: 0.191;
|
|
3110
|
+
};
|
|
3111
|
+
readonly previewFill: {
|
|
3112
|
+
readonly day: {
|
|
3113
|
+
readonly sky: readonly [0.976, 1.001, 1.058];
|
|
3114
|
+
readonly ground: readonly [1.076, 0.986, 0.914];
|
|
3115
|
+
};
|
|
3116
|
+
readonly golden: {
|
|
3117
|
+
readonly sky: readonly [1.242, 0.96, 0.686];
|
|
3118
|
+
readonly ground: readonly [1.355, 0.936, 0.586];
|
|
3119
|
+
};
|
|
3120
|
+
readonly overcast: {
|
|
3121
|
+
readonly sky: readonly [0.964, 1.004, 1.071];
|
|
3122
|
+
readonly ground: readonly [1.062, 0.989, 0.925];
|
|
3123
|
+
};
|
|
3124
|
+
readonly night: {
|
|
3125
|
+
readonly sky: readonly [1.466, 0.899, 0.627];
|
|
3126
|
+
readonly ground: readonly [1.59, 0.872, 0.533];
|
|
3127
|
+
};
|
|
3128
|
+
};
|
|
3129
|
+
readonly exposure: {
|
|
3130
|
+
readonly day: 1.741;
|
|
3131
|
+
readonly golden: 1.741;
|
|
3132
|
+
readonly overcast: 1.866;
|
|
3133
|
+
readonly night: 2.144;
|
|
3134
|
+
};
|
|
3135
|
+
readonly hdri: {
|
|
3136
|
+
readonly day: {
|
|
3137
|
+
readonly name: "horn-koppe_spring";
|
|
3138
|
+
readonly rotationDeg: 352;
|
|
3139
|
+
readonly groundHeight: 1.6;
|
|
3140
|
+
};
|
|
3141
|
+
readonly golden: {
|
|
3142
|
+
readonly name: "umhlanga_sunrise";
|
|
3143
|
+
readonly rotationDeg: 330;
|
|
3144
|
+
readonly groundHeight: 1.6;
|
|
3145
|
+
};
|
|
3146
|
+
readonly overcast: {
|
|
3147
|
+
readonly name: "dalkey_view";
|
|
3148
|
+
readonly rotationDeg: 0;
|
|
3149
|
+
readonly groundHeight: 1.6;
|
|
3150
|
+
};
|
|
3151
|
+
readonly night: {
|
|
3152
|
+
readonly name: "preller_drive";
|
|
3153
|
+
readonly rotationDeg: 0;
|
|
3154
|
+
readonly groundHeight: 1.6;
|
|
3155
|
+
};
|
|
3156
|
+
};
|
|
3157
|
+
readonly skyIrradiance: {
|
|
3158
|
+
readonly day: 8.707;
|
|
3159
|
+
readonly golden: 5.273;
|
|
3160
|
+
readonly overcast: 3.286;
|
|
3161
|
+
readonly night: 0.011;
|
|
3162
|
+
};
|
|
3163
|
+
};
|
|
3164
|
+
readonly conservatory_day: {
|
|
3165
|
+
readonly label: "Conservatory";
|
|
3166
|
+
readonly description: "Travertine and white plaster under a foliage-broken roof light, three east windows of morning sun, and all five plants of the set standing around a vase on a marble cylinder.";
|
|
3167
|
+
readonly build: "37c1bf94d793";
|
|
3168
|
+
readonly presetsVersion: 1;
|
|
3169
|
+
readonly size: {
|
|
3170
|
+
readonly width: 5.6;
|
|
3171
|
+
readonly height: 3.4;
|
|
3172
|
+
readonly depth: 5.6;
|
|
3173
|
+
};
|
|
3174
|
+
readonly lighting: "day";
|
|
3175
|
+
readonly surfaces: {
|
|
3176
|
+
readonly floor: {
|
|
3177
|
+
readonly material: "travertine";
|
|
3178
|
+
readonly color: "#ffffff";
|
|
3179
|
+
};
|
|
3180
|
+
readonly walls: {
|
|
3181
|
+
readonly material: "white_plaster";
|
|
3182
|
+
readonly color: "#f9f3fa";
|
|
3183
|
+
};
|
|
3184
|
+
readonly ceiling: {
|
|
3185
|
+
readonly material: "matte_paint";
|
|
3186
|
+
readonly color: "#f5f4f0";
|
|
3187
|
+
};
|
|
3188
|
+
readonly sill: {
|
|
3189
|
+
readonly material: "travertine";
|
|
3190
|
+
};
|
|
3191
|
+
};
|
|
3192
|
+
readonly stand: {
|
|
3193
|
+
readonly type: "pedestal_cylinder";
|
|
3194
|
+
readonly material: "carrara_marble";
|
|
3195
|
+
readonly x: 0;
|
|
3196
|
+
readonly z: -0.15;
|
|
3197
|
+
readonly rotationDeg: 0;
|
|
3198
|
+
readonly top: 0.95;
|
|
3199
|
+
readonly size: {
|
|
3200
|
+
readonly x: 0.42;
|
|
3201
|
+
readonly y: 0.95;
|
|
3202
|
+
readonly z: 0.42;
|
|
3203
|
+
};
|
|
3204
|
+
};
|
|
3205
|
+
readonly camera: {
|
|
3206
|
+
readonly position: readonly [-0.2066, 1.3, -0.9848];
|
|
3207
|
+
readonly target: readonly [0, 1.2, -0.15];
|
|
3208
|
+
readonly lensMm: 26;
|
|
3209
|
+
readonly fstop: 4;
|
|
3210
|
+
};
|
|
3211
|
+
readonly preview: {
|
|
3212
|
+
readonly glb: "rooms/conservatory_day/room.glb";
|
|
3213
|
+
readonly bytes: 3089392;
|
|
3214
|
+
readonly triangles: 171145;
|
|
3215
|
+
readonly textures: 35;
|
|
3216
|
+
};
|
|
3217
|
+
readonly complexity: {
|
|
3218
|
+
readonly enclosure: 1;
|
|
3219
|
+
readonly objects: 60;
|
|
3220
|
+
};
|
|
3221
|
+
readonly prebaked: readonly [];
|
|
3222
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
3223
|
+
readonly standGlb: "rooms/conservatory_day/stand.glb";
|
|
3224
|
+
readonly plants: readonly ["potted_bird_of_paradise_01", "potted_monstera_01", "potted_monstera_02", "potted_plant_04"];
|
|
3225
|
+
readonly pictures: readonly [];
|
|
3226
|
+
readonly previewBrightness: {
|
|
3227
|
+
readonly day: 1.551;
|
|
3228
|
+
readonly golden: 1.292;
|
|
3229
|
+
readonly overcast: 1.447;
|
|
3230
|
+
readonly night: 0.196;
|
|
3231
|
+
};
|
|
3232
|
+
readonly previewFill: {
|
|
3233
|
+
readonly day: {
|
|
3234
|
+
readonly sky: readonly [0.966, 1, 1.104];
|
|
3235
|
+
readonly ground: readonly [0.966, 1, 1.104];
|
|
3236
|
+
};
|
|
3237
|
+
readonly golden: {
|
|
3238
|
+
readonly sky: readonly [1.22, 0.961, 0.738];
|
|
3239
|
+
readonly ground: readonly [1.22, 0.961, 0.738];
|
|
3240
|
+
};
|
|
3241
|
+
readonly overcast: {
|
|
3242
|
+
readonly sky: readonly [1.001, 0.996, 1.036];
|
|
3243
|
+
readonly ground: readonly [1.001, 0.996, 1.036];
|
|
3244
|
+
};
|
|
3245
|
+
readonly night: {
|
|
3246
|
+
readonly sky: readonly [1.486, 0.894, 0.619];
|
|
3247
|
+
readonly ground: readonly [1.486, 0.894, 0.619];
|
|
3248
|
+
};
|
|
3249
|
+
};
|
|
3250
|
+
readonly exposure: {
|
|
3251
|
+
readonly day: 1.741;
|
|
3252
|
+
readonly golden: 1.741;
|
|
3253
|
+
readonly overcast: 1.866;
|
|
3254
|
+
readonly night: 2.144;
|
|
3255
|
+
};
|
|
3256
|
+
readonly hdri: {
|
|
3257
|
+
readonly day: {
|
|
3258
|
+
readonly name: "grasslands_sunset";
|
|
3259
|
+
readonly rotationDeg: 299.3;
|
|
3260
|
+
readonly groundHeight: 1.6;
|
|
3261
|
+
};
|
|
3262
|
+
readonly golden: {
|
|
3263
|
+
readonly name: "qwantani_sunset";
|
|
3264
|
+
readonly rotationDeg: 304;
|
|
3265
|
+
readonly groundHeight: 1.6;
|
|
3266
|
+
};
|
|
3267
|
+
readonly overcast: {
|
|
3268
|
+
readonly name: "dalkey_view";
|
|
3269
|
+
readonly rotationDeg: 0;
|
|
3270
|
+
readonly groundHeight: 1.6;
|
|
3271
|
+
};
|
|
3272
|
+
readonly night: {
|
|
3273
|
+
readonly name: "moonless_golf";
|
|
3274
|
+
readonly rotationDeg: 0;
|
|
3275
|
+
readonly groundHeight: 1.6;
|
|
3276
|
+
};
|
|
3277
|
+
};
|
|
3278
|
+
readonly skyIrradiance: {
|
|
3279
|
+
readonly day: 8.707;
|
|
3280
|
+
readonly golden: 5.273;
|
|
3281
|
+
readonly overcast: 3.286;
|
|
3282
|
+
readonly night: 0.011;
|
|
3283
|
+
};
|
|
3284
|
+
};
|
|
3285
|
+
readonly dining_golden: {
|
|
3286
|
+
readonly label: "Golden Hour Dining";
|
|
3287
|
+
readonly description: "Painted brick and dark planks, low sun through rippled cast glass, a walnut table set for four off to one side and a bird of paradise holding the light.";
|
|
3288
|
+
readonly build: "aaf9e73a653b";
|
|
3289
|
+
readonly presetsVersion: 1;
|
|
3290
|
+
readonly size: {
|
|
3291
|
+
readonly width: 7;
|
|
3292
|
+
readonly height: 3.3;
|
|
3293
|
+
readonly depth: 5.2;
|
|
3294
|
+
};
|
|
3295
|
+
readonly lighting: "golden";
|
|
3296
|
+
readonly surfaces: {
|
|
3297
|
+
readonly floor: {
|
|
3298
|
+
readonly material: "oak_veneer_05";
|
|
3299
|
+
readonly color: "#ffffff";
|
|
3300
|
+
};
|
|
3301
|
+
readonly walls: {
|
|
3302
|
+
readonly material: "matte_paint";
|
|
3303
|
+
readonly color: "#efe6d9";
|
|
3304
|
+
};
|
|
3305
|
+
readonly ceiling: {
|
|
3306
|
+
readonly material: "matte_paint";
|
|
3307
|
+
readonly color: "#f5f4f0";
|
|
3308
|
+
};
|
|
3309
|
+
readonly sill: {
|
|
3310
|
+
readonly material: "polished_concrete";
|
|
3311
|
+
};
|
|
3312
|
+
};
|
|
3313
|
+
readonly stand: {
|
|
3314
|
+
readonly type: "round_wooden_table_01";
|
|
3315
|
+
readonly material: null;
|
|
3316
|
+
readonly x: 0.69;
|
|
3317
|
+
readonly z: -0.38;
|
|
3318
|
+
readonly rotationDeg: 0;
|
|
3319
|
+
readonly top: 1.005;
|
|
3320
|
+
readonly size: {
|
|
3321
|
+
readonly x: 1.3993;
|
|
3322
|
+
readonly y: 1.005;
|
|
3323
|
+
readonly z: 1.3993;
|
|
3324
|
+
};
|
|
3325
|
+
};
|
|
3326
|
+
readonly camera: {
|
|
3327
|
+
readonly position: readonly [0.9851, 1.21, -0.0142];
|
|
3328
|
+
readonly target: readonly [0.69, 1.255, -0.38];
|
|
3329
|
+
readonly lensMm: 24;
|
|
3330
|
+
readonly fstop: 4;
|
|
3331
|
+
};
|
|
3332
|
+
readonly preview: {
|
|
3333
|
+
readonly glb: "rooms/dining_golden/room.glb";
|
|
3334
|
+
readonly bytes: 1020424;
|
|
3335
|
+
readonly triangles: 29323;
|
|
3336
|
+
readonly textures: 24;
|
|
3337
|
+
};
|
|
3338
|
+
readonly complexity: {
|
|
3339
|
+
readonly enclosure: 1;
|
|
3340
|
+
readonly objects: 60;
|
|
3341
|
+
};
|
|
3342
|
+
readonly prebaked: readonly [];
|
|
3343
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
3344
|
+
readonly standGlb: "rooms/dining_golden/stand.glb";
|
|
3345
|
+
readonly plants: readonly ["potted_bird_of_paradise_01", "potted_plant_04"];
|
|
3346
|
+
readonly pictures: readonly [];
|
|
3347
|
+
readonly previewBrightness: {
|
|
3348
|
+
readonly day: 0.196;
|
|
3349
|
+
readonly golden: 0.31;
|
|
3350
|
+
readonly overcast: 0.289;
|
|
3351
|
+
readonly night: 0.154;
|
|
3352
|
+
};
|
|
3353
|
+
readonly previewFill: {
|
|
3354
|
+
readonly day: {
|
|
3355
|
+
readonly sky: readonly [1.074, 0.983, 0.948];
|
|
3356
|
+
readonly ground: readonly [1.074, 0.983, 0.948];
|
|
3357
|
+
};
|
|
3358
|
+
readonly golden: {
|
|
3359
|
+
readonly sky: readonly [1.338, 0.937, 0.63];
|
|
3360
|
+
readonly ground: readonly [1.338, 0.937, 0.63];
|
|
3361
|
+
};
|
|
3362
|
+
readonly overcast: {
|
|
3363
|
+
readonly sky: readonly [1.297, 0.934, 0.775];
|
|
3364
|
+
readonly ground: readonly [1.297, 0.934, 0.775];
|
|
3365
|
+
};
|
|
3366
|
+
readonly night: {
|
|
3367
|
+
readonly sky: readonly [1.487, 0.894, 0.617];
|
|
3368
|
+
readonly ground: readonly [1.487, 0.894, 0.617];
|
|
3369
|
+
};
|
|
3370
|
+
};
|
|
3371
|
+
readonly exposure: {
|
|
3372
|
+
readonly day: 1.741;
|
|
3373
|
+
readonly golden: 1.741;
|
|
3374
|
+
readonly overcast: 1.866;
|
|
3375
|
+
readonly night: 2.144;
|
|
3376
|
+
};
|
|
3377
|
+
readonly hdri: {
|
|
3378
|
+
readonly day: {
|
|
3379
|
+
readonly name: "pergola_walkway";
|
|
3380
|
+
readonly rotationDeg: 79.7;
|
|
3381
|
+
readonly groundHeight: 1.6;
|
|
3382
|
+
};
|
|
3383
|
+
readonly golden: {
|
|
3384
|
+
readonly name: "bambanani_sunset";
|
|
3385
|
+
readonly rotationDeg: 104;
|
|
3386
|
+
readonly groundHeight: 1.6;
|
|
3387
|
+
};
|
|
3388
|
+
readonly overcast: {
|
|
3389
|
+
readonly name: "dalkey_view";
|
|
3390
|
+
readonly rotationDeg: 0;
|
|
3391
|
+
readonly groundHeight: 1.6;
|
|
3392
|
+
};
|
|
3393
|
+
readonly night: {
|
|
3394
|
+
readonly name: "moonless_golf";
|
|
3395
|
+
readonly rotationDeg: 0;
|
|
3396
|
+
readonly groundHeight: 1.6;
|
|
3397
|
+
};
|
|
3398
|
+
};
|
|
3399
|
+
readonly skyIrradiance: {
|
|
3400
|
+
readonly day: 8.468;
|
|
3401
|
+
readonly golden: 5.273;
|
|
3402
|
+
readonly overcast: 3.286;
|
|
3403
|
+
readonly night: 0.011;
|
|
3404
|
+
};
|
|
3405
|
+
};
|
|
3406
|
+
readonly gallery_day: {
|
|
3407
|
+
readonly label: "Daylight Gallery";
|
|
3408
|
+
readonly description: "White plaster and oak, three tall west windows with foliage-dappled midday sun, shelves of ceramics behind the centerpiece.";
|
|
3409
|
+
readonly build: "4b9e7b77b32b";
|
|
3410
|
+
readonly presetsVersion: 1;
|
|
3411
|
+
readonly size: {
|
|
3412
|
+
readonly width: 6;
|
|
3413
|
+
readonly height: 3.2;
|
|
3414
|
+
readonly depth: 5;
|
|
3415
|
+
};
|
|
3416
|
+
readonly lighting: "day";
|
|
3417
|
+
readonly surfaces: {
|
|
3418
|
+
readonly floor: {
|
|
3419
|
+
readonly material: "oak_floor";
|
|
3420
|
+
readonly color: "#ffffff";
|
|
3421
|
+
};
|
|
3422
|
+
readonly walls: {
|
|
3423
|
+
readonly material: "white_plaster";
|
|
3424
|
+
readonly color: "#f4f1ea";
|
|
3425
|
+
};
|
|
3426
|
+
readonly ceiling: {
|
|
3427
|
+
readonly material: "matte_paint";
|
|
3428
|
+
readonly color: "#f5f4f0";
|
|
3429
|
+
};
|
|
3430
|
+
readonly sill: {
|
|
3431
|
+
readonly material: "travertine";
|
|
3432
|
+
};
|
|
3433
|
+
};
|
|
3434
|
+
readonly stand: {
|
|
3435
|
+
readonly type: "pedestal_block";
|
|
3436
|
+
readonly material: "carrara_marble";
|
|
3437
|
+
readonly x: 0.18;
|
|
3438
|
+
readonly z: -0.36;
|
|
3439
|
+
readonly rotationDeg: 0;
|
|
3440
|
+
readonly top: 1;
|
|
3441
|
+
readonly size: {
|
|
3442
|
+
readonly x: 0.4;
|
|
3443
|
+
readonly y: 1;
|
|
3444
|
+
readonly z: 0.4;
|
|
3445
|
+
};
|
|
3446
|
+
};
|
|
3447
|
+
readonly camera: {
|
|
3448
|
+
readonly position: readonly [0.3653, 1.17, -0.0055];
|
|
3449
|
+
readonly target: readonly [0.18, 1.25, -0.36];
|
|
3450
|
+
readonly lensMm: 34;
|
|
3451
|
+
readonly fstop: 4;
|
|
3452
|
+
};
|
|
3453
|
+
readonly preview: {
|
|
3454
|
+
readonly glb: "rooms/gallery_day/room.glb";
|
|
3455
|
+
readonly bytes: 2109044;
|
|
3456
|
+
readonly triangles: 119502;
|
|
3457
|
+
readonly textures: 21;
|
|
3458
|
+
};
|
|
3459
|
+
readonly complexity: {
|
|
3460
|
+
readonly enclosure: 1;
|
|
3461
|
+
readonly objects: 72;
|
|
3462
|
+
};
|
|
3463
|
+
readonly prebaked: readonly [];
|
|
3464
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
3465
|
+
readonly standGlb: "rooms/gallery_day/stand.glb";
|
|
3466
|
+
readonly plants: readonly ["potted_plant_01", "potted_plant_04"];
|
|
3467
|
+
readonly pictures: readonly [];
|
|
3468
|
+
readonly previewBrightness: {
|
|
3469
|
+
readonly day: 0.093;
|
|
3470
|
+
readonly golden: 0.15;
|
|
3471
|
+
readonly overcast: 0.043;
|
|
3472
|
+
readonly night: 0.159;
|
|
3473
|
+
};
|
|
3474
|
+
readonly previewFill: {
|
|
3475
|
+
readonly day: {
|
|
3476
|
+
readonly sky: readonly [1.101, 0.978, 0.915];
|
|
3477
|
+
readonly ground: readonly [1.101, 0.978, 0.915];
|
|
3478
|
+
};
|
|
3479
|
+
readonly golden: {
|
|
3480
|
+
readonly sky: readonly [1.298, 0.947, 0.643];
|
|
3481
|
+
readonly ground: readonly [1.298, 0.947, 0.643];
|
|
3482
|
+
};
|
|
3483
|
+
readonly overcast: {
|
|
3484
|
+
readonly sky: readonly [1.266, 0.942, 0.791];
|
|
3485
|
+
readonly ground: readonly [1.266, 0.942, 0.791];
|
|
3486
|
+
};
|
|
3487
|
+
readonly night: {
|
|
3488
|
+
readonly sky: readonly [1.488, 0.894, 0.615];
|
|
3489
|
+
readonly ground: readonly [1.488, 0.894, 0.615];
|
|
3490
|
+
};
|
|
3491
|
+
};
|
|
3492
|
+
readonly exposure: {
|
|
3493
|
+
readonly day: 1.741;
|
|
3494
|
+
readonly golden: 1.741;
|
|
3495
|
+
readonly overcast: 1.866;
|
|
3496
|
+
readonly night: 2.144;
|
|
3497
|
+
};
|
|
3498
|
+
readonly hdri: {
|
|
3499
|
+
readonly day: {
|
|
3500
|
+
readonly name: "sunny_country_road";
|
|
3501
|
+
readonly rotationDeg: 127;
|
|
3502
|
+
readonly groundHeight: 1.6;
|
|
3503
|
+
};
|
|
3504
|
+
readonly golden: {
|
|
3505
|
+
readonly name: "qwantani_sunset";
|
|
3506
|
+
readonly rotationDeg: 124;
|
|
3507
|
+
readonly groundHeight: 1.6;
|
|
3508
|
+
};
|
|
3509
|
+
readonly overcast: {
|
|
3510
|
+
readonly name: "dalkey_view";
|
|
3511
|
+
readonly rotationDeg: 0;
|
|
3512
|
+
readonly groundHeight: 1.6;
|
|
3513
|
+
};
|
|
3514
|
+
readonly night: {
|
|
3515
|
+
readonly name: "moonless_golf";
|
|
3516
|
+
readonly rotationDeg: 0;
|
|
3517
|
+
readonly groundHeight: 1.6;
|
|
3518
|
+
};
|
|
3519
|
+
};
|
|
3520
|
+
readonly skyIrradiance: {
|
|
3521
|
+
readonly day: 8.212;
|
|
3522
|
+
readonly golden: 5.273;
|
|
3523
|
+
readonly overcast: 3.286;
|
|
3524
|
+
readonly night: 0.011;
|
|
3525
|
+
};
|
|
3526
|
+
};
|
|
3527
|
+
readonly living_evening: {
|
|
3528
|
+
readonly label: "Evening Living Room";
|
|
3529
|
+
readonly description: "Warm greige and hardwood after dark: a channel-tufted sofa under a washed wall, a plaster console opposite, monstera and palm in the corners.";
|
|
3530
|
+
readonly build: "204a81d87141";
|
|
3531
|
+
readonly presetsVersion: 1;
|
|
3532
|
+
readonly size: {
|
|
3533
|
+
readonly width: 6.4;
|
|
3534
|
+
readonly height: 3;
|
|
3535
|
+
readonly depth: 5.4;
|
|
3536
|
+
};
|
|
3537
|
+
readonly lighting: "night";
|
|
3538
|
+
readonly surfaces: {
|
|
3539
|
+
readonly floor: {
|
|
3540
|
+
readonly material: "wooden_floor_02";
|
|
3541
|
+
readonly color: "#ffffff";
|
|
3542
|
+
};
|
|
3543
|
+
readonly walls: {
|
|
3544
|
+
readonly material: "matte_paint";
|
|
3545
|
+
readonly color: "#e5ded2";
|
|
3546
|
+
};
|
|
3547
|
+
readonly ceiling: {
|
|
3548
|
+
readonly material: "matte_paint";
|
|
3549
|
+
readonly color: "#f5f4f0";
|
|
3550
|
+
};
|
|
3551
|
+
readonly sill: {
|
|
3552
|
+
readonly material: "travertine";
|
|
3553
|
+
};
|
|
3554
|
+
};
|
|
3555
|
+
readonly stand: {
|
|
3556
|
+
readonly type: "pedestal_cylinder";
|
|
3557
|
+
readonly material: "carrara_marble";
|
|
3558
|
+
readonly x: 0.75;
|
|
3559
|
+
readonly z: -0.15;
|
|
3560
|
+
readonly rotationDeg: 0;
|
|
3561
|
+
readonly top: 0.95;
|
|
3562
|
+
readonly size: {
|
|
3563
|
+
readonly x: 0.42;
|
|
3564
|
+
readonly y: 0.95;
|
|
3565
|
+
readonly z: 0.42;
|
|
3566
|
+
};
|
|
3567
|
+
};
|
|
3568
|
+
readonly camera: {
|
|
3569
|
+
readonly position: readonly [1.8407, 0.78, 1.0613];
|
|
3570
|
+
readonly target: readonly [0.75, 1.2, -0.15];
|
|
3571
|
+
readonly lensMm: 32;
|
|
3572
|
+
readonly fstop: 3.200000047683716;
|
|
3573
|
+
};
|
|
3574
|
+
readonly preview: {
|
|
3575
|
+
readonly glb: "rooms/living_evening/room.glb";
|
|
3576
|
+
readonly bytes: 4367688;
|
|
3577
|
+
readonly triangles: 237432;
|
|
3578
|
+
readonly textures: 21;
|
|
3579
|
+
};
|
|
3580
|
+
readonly complexity: {
|
|
3581
|
+
readonly enclosure: 1;
|
|
3582
|
+
readonly objects: 102;
|
|
3583
|
+
};
|
|
3584
|
+
readonly prebaked: readonly [];
|
|
3585
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
3586
|
+
readonly standGlb: "rooms/living_evening/stand.glb";
|
|
3587
|
+
readonly plants: readonly ["potted_monstera_02", "potted_palm_01"];
|
|
3588
|
+
readonly pictures: readonly [{
|
|
3589
|
+
readonly wall: "north";
|
|
3590
|
+
readonly size: 0.7;
|
|
3591
|
+
}];
|
|
3592
|
+
readonly previewBrightness: {
|
|
3593
|
+
readonly day: 0.134;
|
|
3594
|
+
readonly golden: 0.398;
|
|
3595
|
+
readonly overcast: 0.106;
|
|
3596
|
+
readonly night: 0.103;
|
|
3597
|
+
};
|
|
3598
|
+
readonly previewFill: {
|
|
3599
|
+
readonly day: {
|
|
3600
|
+
readonly sky: readonly [1.126, 0.972, 0.906];
|
|
3601
|
+
readonly ground: readonly [1.126, 0.972, 0.906];
|
|
3602
|
+
};
|
|
3603
|
+
readonly golden: {
|
|
3604
|
+
readonly sky: readonly [1.331, 0.938, 0.637];
|
|
3605
|
+
readonly ground: readonly [1.331, 0.938, 0.637];
|
|
3606
|
+
};
|
|
3607
|
+
readonly overcast: {
|
|
3608
|
+
readonly sky: readonly [1.404, 0.911, 0.688];
|
|
3609
|
+
readonly ground: readonly [1.404, 0.911, 0.688];
|
|
3610
|
+
};
|
|
3611
|
+
readonly night: {
|
|
3612
|
+
readonly sky: readonly [1.488, 0.894, 0.615];
|
|
3613
|
+
readonly ground: readonly [1.488, 0.894, 0.615];
|
|
3614
|
+
};
|
|
3615
|
+
};
|
|
3616
|
+
readonly exposure: {
|
|
3617
|
+
readonly day: 1.741;
|
|
3618
|
+
readonly golden: 1.741;
|
|
3619
|
+
readonly overcast: 1.866;
|
|
3620
|
+
readonly night: 2.144;
|
|
3621
|
+
};
|
|
3622
|
+
readonly hdri: {
|
|
3623
|
+
readonly day: {
|
|
3624
|
+
readonly name: "pergola_walkway";
|
|
3625
|
+
readonly rotationDeg: 79.7;
|
|
3626
|
+
readonly groundHeight: 1.6;
|
|
3627
|
+
};
|
|
3628
|
+
readonly golden: {
|
|
3629
|
+
readonly name: "qwantani_sunset";
|
|
3630
|
+
readonly rotationDeg: 124;
|
|
3631
|
+
readonly groundHeight: 1.6;
|
|
3632
|
+
};
|
|
3633
|
+
readonly overcast: {
|
|
3634
|
+
readonly name: "dalkey_view";
|
|
3635
|
+
readonly rotationDeg: 0;
|
|
3636
|
+
readonly groundHeight: 1.6;
|
|
3637
|
+
};
|
|
3638
|
+
readonly night: {
|
|
3639
|
+
readonly name: "moonless_golf";
|
|
3640
|
+
readonly rotationDeg: 0;
|
|
3641
|
+
readonly groundHeight: 1.6;
|
|
3642
|
+
};
|
|
3643
|
+
};
|
|
3644
|
+
readonly skyIrradiance: {
|
|
3645
|
+
readonly day: 8.468;
|
|
3646
|
+
readonly golden: 5.273;
|
|
3647
|
+
readonly overcast: 3.286;
|
|
3648
|
+
readonly night: 0.011;
|
|
3649
|
+
};
|
|
3650
|
+
};
|
|
3651
|
+
readonly loft_golden: {
|
|
3652
|
+
readonly label: "Golden Hour Loft";
|
|
3653
|
+
readonly description: "Painted brick and dark planks, two big steel-framed windows of rippled cast glass, low warm sun and a hint of haze.";
|
|
3654
|
+
readonly build: "6688f7d07182";
|
|
3655
|
+
readonly presetsVersion: 1;
|
|
3656
|
+
readonly size: {
|
|
3657
|
+
readonly width: 7;
|
|
3658
|
+
readonly height: 3.6;
|
|
3659
|
+
readonly depth: 5.5;
|
|
3660
|
+
};
|
|
3661
|
+
readonly lighting: "golden";
|
|
3662
|
+
readonly surfaces: {
|
|
3663
|
+
readonly floor: {
|
|
3664
|
+
readonly material: "plank_flooring_04";
|
|
3665
|
+
readonly color: "#ffffff";
|
|
3666
|
+
};
|
|
3667
|
+
readonly walls: {
|
|
3668
|
+
readonly material: "white_plaster";
|
|
3669
|
+
readonly color: "#f0e7da";
|
|
3670
|
+
};
|
|
3671
|
+
readonly ceiling: {
|
|
3672
|
+
readonly material: "matte_paint";
|
|
3673
|
+
readonly color: "#f5f4f0";
|
|
3674
|
+
};
|
|
3675
|
+
readonly sill: {
|
|
3676
|
+
readonly material: "polished_concrete";
|
|
3677
|
+
};
|
|
3678
|
+
};
|
|
3679
|
+
readonly stand: {
|
|
3680
|
+
readonly type: "pedestal_cylinder";
|
|
3681
|
+
readonly material: "carrara_marble";
|
|
3682
|
+
readonly x: 0.1;
|
|
3683
|
+
readonly z: -0.3;
|
|
3684
|
+
readonly rotationDeg: 0;
|
|
3685
|
+
readonly top: 0.95;
|
|
3686
|
+
readonly size: {
|
|
3687
|
+
readonly x: 0.42;
|
|
3688
|
+
readonly y: 0.95;
|
|
3689
|
+
readonly z: 0.42;
|
|
3690
|
+
};
|
|
3691
|
+
};
|
|
3692
|
+
readonly camera: {
|
|
3693
|
+
readonly position: readonly [0.7923, 1.3, 0.6924];
|
|
3694
|
+
readonly target: readonly [0.1, 1.2, -0.3];
|
|
3695
|
+
readonly lensMm: 35;
|
|
3696
|
+
readonly fstop: 2;
|
|
3697
|
+
};
|
|
3698
|
+
readonly preview: {
|
|
3699
|
+
readonly glb: "rooms/loft_golden/room.glb";
|
|
3700
|
+
readonly bytes: 3578328;
|
|
3701
|
+
readonly triangles: 165919;
|
|
3702
|
+
readonly textures: 23;
|
|
3703
|
+
};
|
|
3704
|
+
readonly complexity: {
|
|
3705
|
+
readonly enclosure: 1;
|
|
3706
|
+
readonly objects: 64;
|
|
3707
|
+
};
|
|
3708
|
+
readonly prebaked: readonly [];
|
|
3709
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
3710
|
+
readonly standGlb: "rooms/loft_golden/stand.glb";
|
|
3711
|
+
readonly plants: readonly ["potted_monstera_01", "potted_palm_01"];
|
|
3712
|
+
readonly pictures: readonly [];
|
|
3713
|
+
readonly previewBrightness: {
|
|
3714
|
+
readonly day: 0.584;
|
|
3715
|
+
readonly golden: 1.034;
|
|
3716
|
+
readonly overcast: 0.965;
|
|
3717
|
+
readonly night: 0.205;
|
|
3718
|
+
};
|
|
3719
|
+
readonly previewFill: {
|
|
3720
|
+
readonly day: {
|
|
3721
|
+
readonly sky: readonly [1.056, 0.998, 0.852];
|
|
3722
|
+
readonly ground: readonly [1.056, 0.998, 0.852];
|
|
3723
|
+
};
|
|
3724
|
+
readonly golden: {
|
|
3725
|
+
readonly sky: readonly [1.414, 0.896, 0.816];
|
|
3726
|
+
readonly ground: readonly [1.414, 0.896, 0.816];
|
|
3727
|
+
};
|
|
3728
|
+
readonly overcast: {
|
|
3729
|
+
readonly sky: readonly [0.953, 1.019, 0.947];
|
|
3730
|
+
readonly ground: readonly [0.953, 1.019, 0.947];
|
|
3731
|
+
};
|
|
3732
|
+
readonly night: {
|
|
3733
|
+
readonly sky: readonly [1.485, 0.895, 0.614];
|
|
3734
|
+
readonly ground: readonly [1.485, 0.895, 0.614];
|
|
3735
|
+
};
|
|
3736
|
+
};
|
|
3737
|
+
readonly exposure: {
|
|
3738
|
+
readonly day: 1.741;
|
|
3739
|
+
readonly golden: 1.741;
|
|
3740
|
+
readonly overcast: 1.866;
|
|
3741
|
+
readonly night: 2.144;
|
|
3742
|
+
};
|
|
3743
|
+
readonly hdri: {
|
|
3744
|
+
readonly day: {
|
|
3745
|
+
readonly name: "pergola_walkway";
|
|
3746
|
+
readonly rotationDeg: 79.7;
|
|
3747
|
+
readonly groundHeight: 1.6;
|
|
3748
|
+
};
|
|
3749
|
+
readonly golden: {
|
|
3750
|
+
readonly name: "the_sky_is_on_fire";
|
|
3751
|
+
readonly rotationDeg: 45.4;
|
|
3752
|
+
readonly groundHeight: 1.6;
|
|
3753
|
+
};
|
|
3754
|
+
readonly overcast: {
|
|
3755
|
+
readonly name: "dalkey_view";
|
|
3756
|
+
readonly rotationDeg: 0;
|
|
3757
|
+
readonly groundHeight: 1.6;
|
|
3758
|
+
};
|
|
3759
|
+
readonly night: {
|
|
3760
|
+
readonly name: "moonless_golf";
|
|
3761
|
+
readonly rotationDeg: 0;
|
|
3762
|
+
readonly groundHeight: 1.6;
|
|
3763
|
+
};
|
|
3764
|
+
};
|
|
3765
|
+
readonly skyIrradiance: {
|
|
3766
|
+
readonly day: 8.468;
|
|
3767
|
+
readonly golden: 5.273;
|
|
3768
|
+
readonly overcast: 3.286;
|
|
3769
|
+
readonly night: 0.011;
|
|
3770
|
+
};
|
|
3771
|
+
};
|
|
3772
|
+
readonly noir_parlour: {
|
|
3773
|
+
readonly label: "Noir Parlour";
|
|
3774
|
+
readonly description: "Charcoal walls in frame mouldings above a dado rail, a pale skirting, grey oak boards, two leather lounge chairs under a pair of white pendants and a print between them.";
|
|
3775
|
+
readonly build: "c444e081de93";
|
|
3776
|
+
readonly presetsVersion: 1;
|
|
3777
|
+
readonly size: {
|
|
3778
|
+
readonly width: 7.6;
|
|
3779
|
+
readonly height: 3.4;
|
|
3780
|
+
readonly depth: 6.2;
|
|
3781
|
+
};
|
|
3782
|
+
readonly lighting: "golden";
|
|
3783
|
+
readonly surfaces: {
|
|
3784
|
+
readonly floor: {
|
|
3785
|
+
readonly material: "wooden_floor_02";
|
|
3786
|
+
readonly color: "#7d7872";
|
|
3787
|
+
};
|
|
3788
|
+
readonly walls: {
|
|
3789
|
+
readonly material: "matte_paint";
|
|
3790
|
+
readonly color: "#2b2b2f";
|
|
3791
|
+
};
|
|
3792
|
+
readonly ceiling: {
|
|
3793
|
+
readonly material: "matte_paint";
|
|
3794
|
+
readonly color: "#3b3b40";
|
|
3795
|
+
};
|
|
3796
|
+
readonly sill: {
|
|
3797
|
+
readonly material: "polished_concrete";
|
|
3798
|
+
};
|
|
3799
|
+
};
|
|
3800
|
+
readonly stand: {
|
|
3801
|
+
readonly type: "oval_side_table";
|
|
3802
|
+
readonly material: null;
|
|
3803
|
+
readonly x: 0;
|
|
3804
|
+
readonly z: -1.9;
|
|
3805
|
+
readonly rotationDeg: 0;
|
|
3806
|
+
readonly top: 0.6048;
|
|
3807
|
+
readonly size: {
|
|
3808
|
+
readonly x: 0.562;
|
|
3809
|
+
readonly y: 0.605;
|
|
3810
|
+
readonly z: 0.34;
|
|
3811
|
+
};
|
|
3812
|
+
};
|
|
3813
|
+
readonly camera: {
|
|
3814
|
+
readonly position: readonly [0, 1.15, 2.5];
|
|
3815
|
+
readonly target: readonly [0, 0.8548, -1.9];
|
|
3816
|
+
readonly lensMm: 28;
|
|
3817
|
+
readonly fstop: 4;
|
|
3818
|
+
};
|
|
3819
|
+
readonly preview: {
|
|
3820
|
+
readonly glb: "rooms/noir_parlour/room.glb";
|
|
3821
|
+
readonly bytes: 1399740;
|
|
3822
|
+
readonly triangles: 55172;
|
|
3823
|
+
readonly textures: 20;
|
|
3824
|
+
};
|
|
3825
|
+
readonly complexity: {
|
|
3826
|
+
readonly enclosure: 1;
|
|
3827
|
+
readonly objects: 55;
|
|
3828
|
+
};
|
|
3829
|
+
readonly prebaked: readonly [];
|
|
3830
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
3831
|
+
readonly standGlb: "rooms/noir_parlour/stand.glb";
|
|
3832
|
+
readonly plants: readonly ["potted_dracaena_01"];
|
|
3833
|
+
readonly pictures: readonly [{
|
|
3834
|
+
readonly wall: "north";
|
|
3835
|
+
readonly size: 1.25;
|
|
3836
|
+
}];
|
|
3837
|
+
readonly previewBrightness: {
|
|
3838
|
+
readonly day: 0.362;
|
|
3839
|
+
readonly golden: 0.367;
|
|
3840
|
+
readonly overcast: 0.111;
|
|
3841
|
+
readonly night: 0.168;
|
|
3842
|
+
};
|
|
3843
|
+
readonly previewFill: {
|
|
3844
|
+
readonly day: {
|
|
3845
|
+
readonly sky: readonly [1.021, 0.994, 0.999];
|
|
3846
|
+
readonly ground: readonly [1.101, 0.982, 0.884];
|
|
3847
|
+
};
|
|
3848
|
+
readonly golden: {
|
|
3849
|
+
readonly sky: readonly [1.22, 0.963, 0.723];
|
|
3850
|
+
readonly ground: readonly [1.308, 0.945, 0.636];
|
|
3851
|
+
};
|
|
3852
|
+
readonly overcast: {
|
|
3853
|
+
readonly sky: readonly [1.083, 0.979, 0.964];
|
|
3854
|
+
readonly ground: readonly [1.167, 0.965, 0.852];
|
|
3855
|
+
};
|
|
3856
|
+
readonly night: {
|
|
3857
|
+
readonly sky: readonly [1.486, 0.894, 0.618];
|
|
3858
|
+
readonly ground: readonly [1.584, 0.873, 0.54];
|
|
3859
|
+
};
|
|
3860
|
+
};
|
|
3861
|
+
readonly exposure: {
|
|
3862
|
+
readonly day: 1.741;
|
|
3863
|
+
readonly golden: 1.741;
|
|
3864
|
+
readonly overcast: 1.866;
|
|
3865
|
+
readonly night: 2.144;
|
|
3866
|
+
};
|
|
3867
|
+
readonly hdri: {
|
|
3868
|
+
readonly day: {
|
|
3869
|
+
readonly name: "horn-koppe_spring";
|
|
3870
|
+
readonly rotationDeg: 114;
|
|
3871
|
+
readonly groundHeight: 1.6;
|
|
3872
|
+
};
|
|
3873
|
+
readonly golden: {
|
|
3874
|
+
readonly name: "bambanani_sunset";
|
|
3875
|
+
readonly rotationDeg: 136;
|
|
3876
|
+
readonly groundHeight: 1.6;
|
|
3877
|
+
};
|
|
3878
|
+
readonly overcast: {
|
|
3879
|
+
readonly name: "dalkey_view";
|
|
3880
|
+
readonly rotationDeg: 0;
|
|
3881
|
+
readonly groundHeight: 1.6;
|
|
3882
|
+
};
|
|
3883
|
+
readonly night: {
|
|
3884
|
+
readonly name: "satara_night";
|
|
3885
|
+
readonly rotationDeg: 0;
|
|
3886
|
+
readonly groundHeight: 1.6;
|
|
3887
|
+
};
|
|
3888
|
+
};
|
|
3889
|
+
readonly skyIrradiance: {
|
|
3890
|
+
readonly day: 8.34;
|
|
3891
|
+
readonly golden: 5.529;
|
|
3892
|
+
readonly overcast: 3.286;
|
|
3893
|
+
readonly night: 0.011;
|
|
3894
|
+
};
|
|
3895
|
+
};
|
|
3896
|
+
readonly plaster_atelier: {
|
|
3897
|
+
readonly label: "Plaster Atelier";
|
|
3898
|
+
readonly description: "Raw plaster, a concrete floor and one tall opening on the west side that lays a slab of sun across the room. A low white sofa against a travertine wall, a dome pendant, a palm in the corner.";
|
|
3899
|
+
readonly build: "6ee46115fa42";
|
|
3900
|
+
readonly presetsVersion: 1;
|
|
3901
|
+
readonly size: {
|
|
3902
|
+
readonly width: 10;
|
|
3903
|
+
readonly height: 3.8;
|
|
3904
|
+
readonly depth: 7.6;
|
|
3905
|
+
};
|
|
3906
|
+
readonly lighting: "day";
|
|
3907
|
+
readonly surfaces: {
|
|
3908
|
+
readonly floor: {
|
|
3909
|
+
readonly material: "polished_concrete";
|
|
3910
|
+
readonly color: "#e2e0dc";
|
|
3911
|
+
};
|
|
3912
|
+
readonly walls: {
|
|
3913
|
+
readonly material: "white_plaster";
|
|
3914
|
+
readonly color: "#dcd7ce";
|
|
3915
|
+
};
|
|
3916
|
+
readonly ceiling: {
|
|
3917
|
+
readonly material: "white_plaster";
|
|
3918
|
+
readonly color: "#f1efea";
|
|
3919
|
+
};
|
|
3920
|
+
readonly sill: {
|
|
3921
|
+
readonly material: "polished_concrete";
|
|
3922
|
+
};
|
|
3923
|
+
};
|
|
3924
|
+
readonly stand: {
|
|
3925
|
+
readonly type: "pedestal_cylinder";
|
|
3926
|
+
readonly material: "travertine";
|
|
3927
|
+
readonly x: -1.7;
|
|
3928
|
+
readonly z: -1;
|
|
3929
|
+
readonly rotationDeg: 0;
|
|
3930
|
+
readonly top: 0.95;
|
|
3931
|
+
readonly size: {
|
|
3932
|
+
readonly x: 0.42;
|
|
3933
|
+
readonly y: 0.95;
|
|
3934
|
+
readonly z: 0.42;
|
|
3935
|
+
};
|
|
3936
|
+
};
|
|
3937
|
+
readonly camera: {
|
|
3938
|
+
readonly position: readonly [-2.6356, 1.2, 3.4017];
|
|
3939
|
+
readonly target: readonly [-1.7, 1.2, -1];
|
|
3940
|
+
readonly lensMm: 24;
|
|
3941
|
+
readonly fstop: 5.599999904632568;
|
|
3942
|
+
};
|
|
3943
|
+
readonly preview: {
|
|
3944
|
+
readonly glb: "rooms/plaster_atelier/room.glb";
|
|
3945
|
+
readonly bytes: 3991680;
|
|
3946
|
+
readonly triangles: 187489;
|
|
3947
|
+
readonly textures: 30;
|
|
3948
|
+
};
|
|
3949
|
+
readonly complexity: {
|
|
3950
|
+
readonly enclosure: 1;
|
|
3951
|
+
readonly objects: 37;
|
|
3952
|
+
};
|
|
3953
|
+
readonly prebaked: readonly [];
|
|
3954
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
3955
|
+
readonly standGlb: "rooms/plaster_atelier/stand.glb";
|
|
3956
|
+
readonly plants: readonly ["potted_palm_01", "potted_plant_01"];
|
|
3957
|
+
readonly pictures: readonly [];
|
|
3958
|
+
readonly previewBrightness: {
|
|
3959
|
+
readonly day: 1.478;
|
|
3960
|
+
readonly golden: 1.742;
|
|
3961
|
+
readonly overcast: 0.314;
|
|
3962
|
+
readonly night: 0.182;
|
|
3963
|
+
};
|
|
3964
|
+
readonly previewFill: {
|
|
3965
|
+
readonly day: {
|
|
3966
|
+
readonly sky: readonly [0.975, 1.002, 1.058];
|
|
3967
|
+
readonly ground: readonly [0.993, 1.001, 1.015];
|
|
3968
|
+
};
|
|
3969
|
+
readonly golden: {
|
|
3970
|
+
readonly sky: readonly [1.223, 0.964, 0.704];
|
|
3971
|
+
readonly ground: readonly [1.244, 0.961, 0.674];
|
|
3972
|
+
};
|
|
3973
|
+
readonly overcast: {
|
|
3974
|
+
readonly sky: readonly [0.964, 1.004, 1.071];
|
|
3975
|
+
readonly ground: readonly [0.982, 1.003, 1.027];
|
|
3976
|
+
};
|
|
3977
|
+
readonly night: {
|
|
3978
|
+
readonly sky: readonly [1.427, 0.909, 0.647];
|
|
3979
|
+
readonly ground: readonly [1.449, 0.905, 0.618];
|
|
3980
|
+
};
|
|
3981
|
+
};
|
|
3982
|
+
readonly exposure: {
|
|
3983
|
+
readonly day: 1.741;
|
|
3984
|
+
readonly golden: 1.741;
|
|
3985
|
+
readonly overcast: 1.866;
|
|
3986
|
+
readonly night: 2.144;
|
|
3987
|
+
};
|
|
3988
|
+
readonly hdri: {
|
|
3989
|
+
readonly day: {
|
|
3990
|
+
readonly name: "sunny_country_road";
|
|
3991
|
+
readonly rotationDeg: 122;
|
|
3992
|
+
readonly groundHeight: 1.6;
|
|
3993
|
+
};
|
|
3994
|
+
readonly golden: {
|
|
3995
|
+
readonly name: "venice_sunset";
|
|
3996
|
+
readonly rotationDeg: 136;
|
|
3997
|
+
readonly groundHeight: 1.6;
|
|
3998
|
+
};
|
|
3999
|
+
readonly overcast: {
|
|
4000
|
+
readonly name: "dalkey_view";
|
|
4001
|
+
readonly rotationDeg: 0;
|
|
4002
|
+
readonly groundHeight: 1.6;
|
|
4003
|
+
};
|
|
4004
|
+
readonly night: {
|
|
4005
|
+
readonly name: "preller_drive";
|
|
4006
|
+
readonly rotationDeg: 0;
|
|
4007
|
+
readonly groundHeight: 1.6;
|
|
4008
|
+
};
|
|
4009
|
+
};
|
|
4010
|
+
readonly skyIrradiance: {
|
|
4011
|
+
readonly day: 8.633;
|
|
4012
|
+
readonly golden: 5.273;
|
|
4013
|
+
readonly overcast: 3.286;
|
|
4014
|
+
readonly night: 0.011;
|
|
4015
|
+
};
|
|
4016
|
+
};
|
|
4017
|
+
readonly slat_lounge: {
|
|
4018
|
+
readonly label: "Oak Slat Lounge";
|
|
4019
|
+
readonly description: "Oak battens either side of a charcoal wall, a low sofa under a large print, and tall windows behind sheers on the east side.";
|
|
4020
|
+
readonly build: "d4b3a554137d";
|
|
4021
|
+
readonly presetsVersion: 1;
|
|
4022
|
+
readonly size: {
|
|
4023
|
+
readonly width: 8.4;
|
|
4024
|
+
readonly height: 3;
|
|
4025
|
+
readonly depth: 6.4;
|
|
4026
|
+
};
|
|
4027
|
+
readonly lighting: "day";
|
|
4028
|
+
readonly surfaces: {
|
|
4029
|
+
readonly floor: {
|
|
4030
|
+
readonly material: "wooden_floor_02";
|
|
4031
|
+
readonly color: "#e6e2dc";
|
|
4032
|
+
};
|
|
4033
|
+
readonly walls: {
|
|
4034
|
+
readonly material: "matte_paint";
|
|
4035
|
+
readonly color: "#e9e6e0";
|
|
4036
|
+
};
|
|
4037
|
+
readonly ceiling: {
|
|
4038
|
+
readonly material: "matte_paint";
|
|
4039
|
+
readonly color: "#f5f4f0";
|
|
4040
|
+
};
|
|
4041
|
+
readonly sill: {
|
|
4042
|
+
readonly material: "polished_concrete";
|
|
4043
|
+
};
|
|
4044
|
+
};
|
|
4045
|
+
readonly stand: {
|
|
4046
|
+
readonly type: "coffee_table_round_01";
|
|
4047
|
+
readonly material: null;
|
|
4048
|
+
readonly x: -0.7;
|
|
4049
|
+
readonly z: -0.7;
|
|
4050
|
+
readonly rotationDeg: 0;
|
|
4051
|
+
readonly top: 0.491;
|
|
4052
|
+
readonly size: {
|
|
4053
|
+
readonly x: 1.3013;
|
|
4054
|
+
readonly y: 0.491;
|
|
4055
|
+
readonly z: 1.3013;
|
|
4056
|
+
};
|
|
4057
|
+
};
|
|
4058
|
+
readonly camera: {
|
|
4059
|
+
readonly position: readonly [-2.75, 1.2, 2.8507];
|
|
4060
|
+
readonly target: readonly [-0.7, 0.741, -0.7];
|
|
4061
|
+
readonly lensMm: 24;
|
|
4062
|
+
readonly fstop: 5.599999904632568;
|
|
4063
|
+
};
|
|
4064
|
+
readonly preview: {
|
|
4065
|
+
readonly glb: "rooms/slat_lounge/room.glb";
|
|
4066
|
+
readonly bytes: 2042948;
|
|
4067
|
+
readonly triangles: 122153;
|
|
4068
|
+
readonly textures: 30;
|
|
4069
|
+
};
|
|
4070
|
+
readonly complexity: {
|
|
4071
|
+
readonly enclosure: 1;
|
|
4072
|
+
readonly objects: 77;
|
|
4073
|
+
};
|
|
4074
|
+
readonly prebaked: readonly [];
|
|
4075
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
4076
|
+
readonly standGlb: "rooms/slat_lounge/stand.glb";
|
|
4077
|
+
readonly plants: readonly ["potted_bird_of_paradise_01", "potted_monstera_02"];
|
|
4078
|
+
readonly pictures: readonly [{
|
|
4079
|
+
readonly wall: "north";
|
|
4080
|
+
readonly size: 1.25;
|
|
4081
|
+
}];
|
|
4082
|
+
readonly previewBrightness: {
|
|
4083
|
+
readonly day: 0.346;
|
|
4084
|
+
readonly golden: 0.383;
|
|
4085
|
+
readonly overcast: 0.121;
|
|
4086
|
+
readonly night: 0.191;
|
|
4087
|
+
};
|
|
4088
|
+
readonly previewFill: {
|
|
4089
|
+
readonly day: {
|
|
4090
|
+
readonly sky: readonly [1.05, 0.987, 0.978];
|
|
4091
|
+
readonly ground: readonly [1.088, 0.982, 0.915];
|
|
4092
|
+
};
|
|
4093
|
+
readonly golden: {
|
|
4094
|
+
readonly sky: readonly [1.273, 0.952, 0.672];
|
|
4095
|
+
readonly ground: readonly [1.314, 0.944, 0.628];
|
|
4096
|
+
};
|
|
4097
|
+
readonly overcast: {
|
|
4098
|
+
readonly sky: readonly [1.213, 0.952, 0.844];
|
|
4099
|
+
readonly ground: readonly [1.254, 0.946, 0.789];
|
|
4100
|
+
};
|
|
4101
|
+
readonly night: {
|
|
4102
|
+
readonly sky: readonly [1.46, 0.901, 0.63];
|
|
4103
|
+
readonly ground: readonly [1.504, 0.892, 0.587];
|
|
4104
|
+
};
|
|
4105
|
+
};
|
|
4106
|
+
readonly exposure: {
|
|
4107
|
+
readonly day: 1.741;
|
|
4108
|
+
readonly golden: 1.741;
|
|
4109
|
+
readonly overcast: 1.866;
|
|
4110
|
+
readonly night: 2.144;
|
|
4111
|
+
};
|
|
4112
|
+
readonly hdri: {
|
|
4113
|
+
readonly day: {
|
|
4114
|
+
readonly name: "pergola_walkway";
|
|
4115
|
+
readonly rotationDeg: 317.7;
|
|
4116
|
+
readonly groundHeight: 1.6;
|
|
4117
|
+
};
|
|
4118
|
+
readonly golden: {
|
|
4119
|
+
readonly name: "qwantani_sunset";
|
|
4120
|
+
readonly rotationDeg: 332;
|
|
4121
|
+
readonly groundHeight: 1.6;
|
|
4122
|
+
};
|
|
4123
|
+
readonly overcast: {
|
|
4124
|
+
readonly name: "dalkey_view";
|
|
4125
|
+
readonly rotationDeg: 0;
|
|
4126
|
+
readonly groundHeight: 1.6;
|
|
4127
|
+
};
|
|
4128
|
+
readonly night: {
|
|
4129
|
+
readonly name: "moonless_golf";
|
|
4130
|
+
readonly rotationDeg: 0;
|
|
4131
|
+
readonly groundHeight: 1.6;
|
|
4132
|
+
};
|
|
4133
|
+
};
|
|
4134
|
+
readonly skyIrradiance: {
|
|
4135
|
+
readonly day: 8.468;
|
|
4136
|
+
readonly golden: 5.273;
|
|
4137
|
+
readonly overcast: 3.286;
|
|
4138
|
+
readonly night: 0.011;
|
|
4139
|
+
};
|
|
4140
|
+
};
|
|
4141
|
+
readonly studio_night: {
|
|
4142
|
+
readonly label: "Night Studio";
|
|
4143
|
+
readonly description: "Warm greige walls after dark: IES downlights, a lit cove channel round the ceiling and one accent spot carrying the centerpiece.";
|
|
4144
|
+
readonly build: "a62ba8b9dc1f";
|
|
4145
|
+
readonly presetsVersion: 1;
|
|
4146
|
+
readonly size: {
|
|
4147
|
+
readonly width: 5;
|
|
4148
|
+
readonly height: 3;
|
|
4149
|
+
readonly depth: 4.4;
|
|
4150
|
+
};
|
|
4151
|
+
readonly lighting: "night";
|
|
4152
|
+
readonly surfaces: {
|
|
4153
|
+
readonly floor: {
|
|
4154
|
+
readonly material: "polished_concrete";
|
|
4155
|
+
readonly color: "#ffffff";
|
|
4156
|
+
};
|
|
4157
|
+
readonly walls: {
|
|
4158
|
+
readonly material: "matte_paint";
|
|
4159
|
+
readonly color: "#e6dfd3";
|
|
4160
|
+
};
|
|
4161
|
+
readonly ceiling: {
|
|
4162
|
+
readonly material: "matte_paint";
|
|
4163
|
+
readonly color: "#f5f4f0";
|
|
4164
|
+
};
|
|
4165
|
+
readonly sill: {
|
|
4166
|
+
readonly material: "carrara_marble";
|
|
4167
|
+
};
|
|
4168
|
+
};
|
|
4169
|
+
readonly stand: {
|
|
4170
|
+
readonly type: null;
|
|
4171
|
+
readonly material: null;
|
|
4172
|
+
readonly x: 0;
|
|
4173
|
+
readonly z: 0;
|
|
4174
|
+
readonly rotationDeg: 0;
|
|
4175
|
+
readonly top: 0;
|
|
4176
|
+
readonly size: null;
|
|
4177
|
+
};
|
|
4178
|
+
readonly camera: {
|
|
4179
|
+
readonly position: readonly [-1.5455, 0.83, 1.2695];
|
|
4180
|
+
readonly target: readonly [0, 0.25, 0];
|
|
4181
|
+
readonly lensMm: 38;
|
|
4182
|
+
readonly fstop: 2;
|
|
4183
|
+
};
|
|
4184
|
+
readonly preview: {
|
|
4185
|
+
readonly glb: "rooms/studio_night/room.glb";
|
|
4186
|
+
readonly bytes: 4251684;
|
|
4187
|
+
readonly triangles: 185457;
|
|
4188
|
+
readonly textures: 21;
|
|
4189
|
+
};
|
|
4190
|
+
readonly complexity: {
|
|
4191
|
+
readonly enclosure: 1;
|
|
4192
|
+
readonly objects: 63;
|
|
4193
|
+
};
|
|
4194
|
+
readonly prebaked: readonly [];
|
|
4195
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
4196
|
+
readonly standGlb: null;
|
|
4197
|
+
readonly plants: readonly ["potted_palm_01", "potted_plant_04"];
|
|
4198
|
+
readonly pictures: readonly [];
|
|
4199
|
+
readonly previewBrightness: {
|
|
4200
|
+
readonly day: 0.222;
|
|
4201
|
+
readonly golden: 0.636;
|
|
4202
|
+
readonly overcast: 0.482;
|
|
4203
|
+
readonly night: 0.466;
|
|
4204
|
+
};
|
|
4205
|
+
readonly previewFill: {
|
|
4206
|
+
readonly day: {
|
|
4207
|
+
readonly sky: readonly [1.222, 0.951, 0.829];
|
|
4208
|
+
readonly ground: readonly [1.222, 0.951, 0.829];
|
|
4209
|
+
};
|
|
4210
|
+
readonly golden: {
|
|
4211
|
+
readonly sky: readonly [1.37, 0.927, 0.632];
|
|
4212
|
+
readonly ground: readonly [1.37, 0.927, 0.632];
|
|
4213
|
+
};
|
|
4214
|
+
readonly overcast: {
|
|
4215
|
+
readonly sky: readonly [1.439, 0.904, 0.657];
|
|
4216
|
+
readonly ground: readonly [1.439, 0.904, 0.657];
|
|
4217
|
+
};
|
|
4218
|
+
readonly night: {
|
|
4219
|
+
readonly sky: readonly [1.486, 0.894, 0.616];
|
|
4220
|
+
readonly ground: readonly [1.486, 0.894, 0.616];
|
|
4221
|
+
};
|
|
4222
|
+
};
|
|
4223
|
+
readonly exposure: {
|
|
4224
|
+
readonly day: 1.741;
|
|
4225
|
+
readonly golden: 1.741;
|
|
4226
|
+
readonly overcast: 1.866;
|
|
4227
|
+
readonly night: 2.144;
|
|
4228
|
+
};
|
|
4229
|
+
readonly hdri: {
|
|
4230
|
+
readonly day: {
|
|
4231
|
+
readonly name: "pergola_walkway";
|
|
4232
|
+
readonly rotationDeg: 169.7;
|
|
4233
|
+
readonly groundHeight: 1.6;
|
|
4234
|
+
};
|
|
4235
|
+
readonly golden: {
|
|
4236
|
+
readonly name: "qwantani_sunset";
|
|
4237
|
+
readonly rotationDeg: 214;
|
|
4238
|
+
readonly groundHeight: 1.6;
|
|
4239
|
+
};
|
|
4240
|
+
readonly overcast: {
|
|
4241
|
+
readonly name: "dalkey_view";
|
|
4242
|
+
readonly rotationDeg: 0;
|
|
4243
|
+
readonly groundHeight: 1.6;
|
|
4244
|
+
};
|
|
4245
|
+
readonly night: {
|
|
4246
|
+
readonly name: "satara_night";
|
|
4247
|
+
readonly rotationDeg: 0;
|
|
4248
|
+
readonly groundHeight: 1.6;
|
|
4249
|
+
};
|
|
4250
|
+
};
|
|
4251
|
+
readonly skyIrradiance: {
|
|
4252
|
+
readonly day: 8.468;
|
|
4253
|
+
readonly golden: 5.273;
|
|
4254
|
+
readonly overcast: 3.286;
|
|
4255
|
+
readonly night: 0.011;
|
|
4256
|
+
};
|
|
4257
|
+
};
|
|
4258
|
+
readonly walnut_salon: {
|
|
4259
|
+
readonly label: "Walnut Salon";
|
|
4260
|
+
readonly description: "A long wall of walnut veneer and slatted bays under a lit cove, a pale modular sofa in front of it, three disc pendants at different heights and a charcoal wall with a print on the east side.";
|
|
4261
|
+
readonly build: "ce8b1acc2907";
|
|
4262
|
+
readonly presetsVersion: 1;
|
|
4263
|
+
readonly size: {
|
|
4264
|
+
readonly width: 10;
|
|
4265
|
+
readonly height: 3.2;
|
|
4266
|
+
readonly depth: 7;
|
|
4267
|
+
};
|
|
4268
|
+
readonly lighting: "night";
|
|
4269
|
+
readonly surfaces: {
|
|
4270
|
+
readonly floor: {
|
|
4271
|
+
readonly material: "polished_concrete";
|
|
4272
|
+
readonly color: "#cfccc6";
|
|
4273
|
+
};
|
|
4274
|
+
readonly walls: {
|
|
4275
|
+
readonly material: "matte_paint";
|
|
4276
|
+
readonly color: "#ddd7cd";
|
|
4277
|
+
};
|
|
4278
|
+
readonly ceiling: {
|
|
4279
|
+
readonly material: "matte_paint";
|
|
4280
|
+
readonly color: "#e4e1db";
|
|
4281
|
+
};
|
|
4282
|
+
readonly sill: {
|
|
4283
|
+
readonly material: "polished_concrete";
|
|
4284
|
+
};
|
|
4285
|
+
};
|
|
4286
|
+
readonly stand: {
|
|
4287
|
+
readonly type: "coffee_table_round_01";
|
|
4288
|
+
readonly material: null;
|
|
4289
|
+
readonly x: 0.2;
|
|
4290
|
+
readonly z: -1.2;
|
|
4291
|
+
readonly rotationDeg: 0;
|
|
4292
|
+
readonly top: 0.491;
|
|
4293
|
+
readonly size: {
|
|
4294
|
+
readonly x: 1.3013;
|
|
4295
|
+
readonly y: 0.491;
|
|
4296
|
+
readonly z: 1.3013;
|
|
4297
|
+
};
|
|
4298
|
+
};
|
|
4299
|
+
readonly camera: {
|
|
4300
|
+
readonly position: readonly [0.2, 1.45, 3.2];
|
|
4301
|
+
readonly target: readonly [0.2, 0.741, -1.2];
|
|
4302
|
+
readonly lensMm: 24;
|
|
4303
|
+
readonly fstop: 5.599999904632568;
|
|
4304
|
+
};
|
|
4305
|
+
readonly preview: {
|
|
4306
|
+
readonly glb: "rooms/walnut_salon/room.glb";
|
|
4307
|
+
readonly bytes: 1958456;
|
|
4308
|
+
readonly triangles: 121487;
|
|
4309
|
+
readonly textures: 36;
|
|
4310
|
+
};
|
|
4311
|
+
readonly complexity: {
|
|
4312
|
+
readonly enclosure: 1;
|
|
4313
|
+
readonly objects: 117;
|
|
4314
|
+
};
|
|
4315
|
+
readonly prebaked: readonly [];
|
|
4316
|
+
readonly baked: readonly ["day", "golden", "night"];
|
|
4317
|
+
readonly standGlb: "rooms/walnut_salon/stand.glb";
|
|
4318
|
+
readonly plants: readonly ["potted_bird_of_paradise_01", "potted_monstera_02"];
|
|
4319
|
+
readonly pictures: readonly [{
|
|
4320
|
+
readonly wall: "east";
|
|
4321
|
+
readonly size: 1.3;
|
|
4322
|
+
}];
|
|
4323
|
+
readonly previewBrightness: {
|
|
4324
|
+
readonly day: 0.295;
|
|
4325
|
+
readonly golden: 0.295;
|
|
4326
|
+
readonly overcast: 0.13;
|
|
4327
|
+
readonly night: 0.313;
|
|
4328
|
+
};
|
|
4329
|
+
readonly previewFill: {
|
|
4330
|
+
readonly day: {
|
|
4331
|
+
readonly sky: readonly [1.112, 0.975, 0.922];
|
|
4332
|
+
readonly ground: readonly [1.145, 0.971, 0.859];
|
|
4333
|
+
};
|
|
4334
|
+
readonly golden: {
|
|
4335
|
+
readonly sky: readonly [1.301, 0.944, 0.67];
|
|
4336
|
+
readonly ground: readonly [1.336, 0.938, 0.623];
|
|
4337
|
+
};
|
|
4338
|
+
readonly overcast: {
|
|
4339
|
+
readonly sky: readonly [1.35, 0.925, 0.716];
|
|
4340
|
+
readonly ground: readonly [1.386, 0.919, 0.665];
|
|
4341
|
+
};
|
|
4342
|
+
readonly night: {
|
|
4343
|
+
readonly sky: readonly [1.427, 0.909, 0.645];
|
|
4344
|
+
readonly ground: readonly [1.464, 0.902, 0.599];
|
|
4345
|
+
};
|
|
4346
|
+
};
|
|
4347
|
+
readonly exposure: {
|
|
4348
|
+
readonly day: 1.741;
|
|
4349
|
+
readonly golden: 1.741;
|
|
4350
|
+
readonly overcast: 1.866;
|
|
4351
|
+
readonly night: 2.144;
|
|
4352
|
+
};
|
|
4353
|
+
readonly hdri: {
|
|
4354
|
+
readonly day: {
|
|
4355
|
+
readonly name: "pergola_walkway";
|
|
4356
|
+
readonly rotationDeg: 59.7;
|
|
4357
|
+
readonly groundHeight: 1.6;
|
|
4358
|
+
};
|
|
4359
|
+
readonly golden: {
|
|
4360
|
+
readonly name: "qwantani_sunset";
|
|
4361
|
+
readonly rotationDeg: 132;
|
|
4362
|
+
readonly groundHeight: 1.6;
|
|
4363
|
+
};
|
|
4364
|
+
readonly overcast: {
|
|
4365
|
+
readonly name: "dalkey_view";
|
|
4366
|
+
readonly rotationDeg: 0;
|
|
4367
|
+
readonly groundHeight: 1.6;
|
|
4368
|
+
};
|
|
4369
|
+
readonly night: {
|
|
4370
|
+
readonly name: "moonless_golf";
|
|
4371
|
+
readonly rotationDeg: 0;
|
|
4372
|
+
readonly groundHeight: 1.6;
|
|
4373
|
+
};
|
|
4374
|
+
};
|
|
4375
|
+
readonly skyIrradiance: {
|
|
4376
|
+
readonly day: 8.468;
|
|
4377
|
+
readonly golden: 5.273;
|
|
4378
|
+
readonly overcast: 3.286;
|
|
4379
|
+
readonly night: 0.011;
|
|
4380
|
+
};
|
|
4381
|
+
};
|
|
4382
|
+
};
|
|
4383
|
+
type RoomPresetName = keyof typeof ROOM_PRESETS;
|
|
4384
|
+
|
|
4385
|
+
/**
|
|
4386
|
+
* A catalog from a server: models by URL, shots per product, no browser.
|
|
4387
|
+
*
|
|
4388
|
+
* import { Bakery3Server } from '@oeave/bakery3/node';
|
|
4389
|
+
* const api = new Bakery3Server(process.env.BAKERY3_SECRET_KEY!);
|
|
4390
|
+
*
|
|
4391
|
+
* const run = await api.renderBatch({
|
|
4392
|
+
* key: 'fall-2026',
|
|
4393
|
+
* products: [{ id: 'chair-aria', url: 'https://cdn.shop.com/models/chair-aria.glb' }],
|
|
4394
|
+
* shots: [
|
|
4395
|
+
* 'three-quarter',
|
|
4396
|
+
* { kind: 'video', from: 'three-quarter', to: 'three-quarter-back', seconds: 4 },
|
|
4397
|
+
* { id: 'low', azimuthDeg: -30, elevationDeg: 4 },
|
|
4398
|
+
* ],
|
|
4399
|
+
* room: { preset: 'loft_golden' },
|
|
4400
|
+
* maxCost: 600,
|
|
4401
|
+
* });
|
|
4402
|
+
*
|
|
4403
|
+
* A shot is a picture or a clip of a product from an angle: degrees around
|
|
4404
|
+
* the model from its front and degrees above the horizon, or a word for one.
|
|
4405
|
+
* The camera is framed from the model's own bounds, read when the model is
|
|
4406
|
+
* imported, so a vase and a sofa both fill the frame from the same word and
|
|
4407
|
+
* nothing is asked of the file itself. A clip is the camera carried from one
|
|
4408
|
+
* angle to another over `seconds`, one framed camera per frame.
|
|
4409
|
+
*
|
|
4410
|
+
* Settings come in three layers: the run's, then a product's, then a shot's,
|
|
4411
|
+
* the nearer winning. So one `room` at the top is every shot's room, a
|
|
4412
|
+
* product that wants the studio instead says `room: null, environment: {…}`,
|
|
4413
|
+
* and one shot in it can still be a close-up at 2048² in a jpeg.
|
|
4414
|
+
*
|
|
4415
|
+
* The model's bytes never pass through this process. Each URL is handed to
|
|
4416
|
+
* `POST /v1/assets/import`; the service fetches it once, stores it content
|
|
4417
|
+
* addressed like any upload, and answers with the hash and what the GLB says
|
|
4418
|
+
* about itself. A model that did not change since the last run is not
|
|
4419
|
+
* downloaded again. What this process sends is a few KB of manifest per shot.
|
|
4420
|
+
*
|
|
4421
|
+
* A manifest built here describes a model, cameras and light. A scene whose
|
|
4422
|
+
* look lives in code exists only where that code runs, so it is recorded
|
|
4423
|
+
* there, with `renderVariants()` in the page. Both kinds of item can share a
|
|
4424
|
+
* batch.
|
|
4425
|
+
*
|
|
4426
|
+
* No three.js here.
|
|
4427
|
+
*/
|
|
4428
|
+
|
|
4429
|
+
/**
|
|
4430
|
+
* Where to look from: a word, or degrees. `'room'` is the framing a preset
|
|
4431
|
+
* room was published with, from level or above: never looking up at the
|
|
4432
|
+
* product.
|
|
4433
|
+
*/
|
|
4434
|
+
type View = ViewWord | 'room' | Angle;
|
|
4435
|
+
type BatchEnvironment = {
|
|
4436
|
+
/** A studio or a sky from the preset library. */
|
|
4437
|
+
preset?: HdriPresetName;
|
|
4438
|
+
/**
|
|
4439
|
+
* Or an HDRI of your own: a public https URL of a .hdr or .exr, imported
|
|
4440
|
+
* like the models.
|
|
4441
|
+
*/
|
|
4442
|
+
url?: string;
|
|
4443
|
+
/** How strongly it lights the product. Default 1 (0.6 for the default studio). */
|
|
4444
|
+
intensity?: number;
|
|
4445
|
+
/** Turn the environment about the vertical, in degrees. */
|
|
4446
|
+
rotationDeg?: number;
|
|
4447
|
+
};
|
|
4448
|
+
type BatchRoom = {
|
|
4449
|
+
/** A room from the preset library. */
|
|
4450
|
+
preset: RoomPresetName;
|
|
4451
|
+
/** Default: the mode the room was designed under. */
|
|
4452
|
+
mode?: RoomPresetLighting;
|
|
4453
|
+
/**
|
|
4454
|
+
* What the product stands on. `'auto'` (the default) goes by its size: the
|
|
4455
|
+
* room's own pedestal for a small thing, the low plinth for a mid-sized
|
|
4456
|
+
* one, the floor for furniture and anything that would overhang. `'own'`
|
|
4457
|
+
* and `'floor'` say so yourself.
|
|
4458
|
+
*/
|
|
4459
|
+
stand?: 'auto' | 'own' | 'floor';
|
|
4460
|
+
/**
|
|
4461
|
+
* Where the room's own camera sees the product from, in degrees from the
|
|
4462
|
+
* product's front. The room is turned about the product to make it so.
|
|
4463
|
+
* Default 30: a three-quarter view from the side the room is open to.
|
|
4464
|
+
*/
|
|
4465
|
+
viewFromDeg?: number;
|
|
4466
|
+
};
|
|
4467
|
+
/**
|
|
4468
|
+
* What every shot can say about how it is taken. Set on the run for all of
|
|
4469
|
+
* them, on a product for its own, on a shot for that one; the nearest wins.
|
|
4470
|
+
*/
|
|
4471
|
+
type ShotSettings = {
|
|
4472
|
+
/**
|
|
4473
|
+
* A preset room to stand in. `null` says no room, where a wider layer had
|
|
4474
|
+
* one.
|
|
4475
|
+
*/
|
|
4476
|
+
room?: BatchRoom | null;
|
|
4477
|
+
/**
|
|
4478
|
+
* The light and backdrop when there is no room. Default: the
|
|
4479
|
+
* `studio_small_03` preset at 0.6.
|
|
4480
|
+
*/
|
|
4481
|
+
environment?: BatchEnvironment;
|
|
4482
|
+
/**
|
|
4483
|
+
* `'transparent'` (the default, with a traced shadow), a `#rrggbb`, or
|
|
4484
|
+
* `'environment'`. A video has no alpha: `'transparent'` is `#f4f4f5` there
|
|
4485
|
+
* (a `png-sequence` keeps it).
|
|
4486
|
+
*/
|
|
4487
|
+
background?: 'transparent' | 'environment' | `#${string}`;
|
|
4488
|
+
/** Default `'studio'`. */
|
|
4489
|
+
quality?: QualityPreset;
|
|
4490
|
+
/** Pixels. Default: the quality preset's long edge, square. */
|
|
4491
|
+
width?: number;
|
|
4492
|
+
height?: number;
|
|
4493
|
+
/**
|
|
4494
|
+
* `png` (the default), `jpeg` or `webp` for an image; `mp4` (the default),
|
|
4495
|
+
* `webm` or `png-sequence` (every frame a PNG with alpha, in one tar) for a
|
|
4496
|
+
* video.
|
|
4497
|
+
*/
|
|
4498
|
+
format?: OutputSpec['format'];
|
|
4499
|
+
/** JPEG/WebP only, 1 to 100. */
|
|
4500
|
+
imageQuality?: number;
|
|
4501
|
+
/**
|
|
4502
|
+
* Vertical field of view in degrees. Default 30, or the room's own lens in
|
|
4503
|
+
* a room.
|
|
4504
|
+
*/
|
|
4505
|
+
fov?: number;
|
|
4506
|
+
/**
|
|
4507
|
+
* Room around the model, as a share of its size. Default 0.12, or 0.9 in
|
|
4508
|
+
* a room.
|
|
4509
|
+
*/
|
|
4510
|
+
margin?: number;
|
|
4511
|
+
/** Frames a second, for a video. Default 30, at most 60. */
|
|
4512
|
+
fps?: number;
|
|
4513
|
+
/** Renderer settings beyond the quality preset. */
|
|
4514
|
+
advanced?: RenderSpec['advanced'];
|
|
4515
|
+
/**
|
|
4516
|
+
* How the pictures are developed. Default: sRGB, ACES filmic, exposure 1
|
|
4517
|
+
* (a room's own exposure in a room).
|
|
4518
|
+
*/
|
|
4519
|
+
color?: Partial<ColorSpec>;
|
|
4520
|
+
};
|
|
4521
|
+
type ImageShot = ShotSettings & {
|
|
4522
|
+
kind?: 'image';
|
|
4523
|
+
/** Default: the view's word, or `az35-el14` for degrees. Ends the item's key. */
|
|
4524
|
+
id?: string;
|
|
4525
|
+
/** Where from. Default `'three-quarter'`, or `'room'` in a room. */
|
|
4526
|
+
view?: View;
|
|
4527
|
+
/**
|
|
4528
|
+
* Instead of a view: a camera inside the model, by its node name, as the
|
|
4529
|
+
* file has it.
|
|
4530
|
+
*/
|
|
4531
|
+
camera?: string;
|
|
4532
|
+
};
|
|
4533
|
+
type VideoShot = ShotSettings & {
|
|
4534
|
+
kind: 'video';
|
|
4535
|
+
/** Default `orbit-<from>-<to>`. */
|
|
4536
|
+
id?: string;
|
|
4537
|
+
/** The camera is carried from here. */
|
|
4538
|
+
from: View;
|
|
4539
|
+
/**
|
|
4540
|
+
* To here, azimuth as written: `{ azimuthDeg: 395 }` from 35 is a full
|
|
4541
|
+
* turn.
|
|
4542
|
+
*/
|
|
4543
|
+
to: View;
|
|
4544
|
+
/** Default 4. At most 30. */
|
|
4545
|
+
seconds?: number;
|
|
4546
|
+
};
|
|
4547
|
+
/** A word or an angle is an image from that view. */
|
|
4548
|
+
type Shot = ViewWord | 'room' | Angle | ImageShot | VideoShot;
|
|
4549
|
+
type BatchProduct = ShotSettings & {
|
|
4550
|
+
/** Starts every shot's key. Stable across re-runs. */
|
|
4551
|
+
id: string;
|
|
4552
|
+
/** A public https URL of a binary glTF (.glb). */
|
|
4553
|
+
url: string;
|
|
4554
|
+
/** This product's shots, instead of the run's. */
|
|
4555
|
+
shots?: Shot[];
|
|
4556
|
+
/** Echoed on every shot of this product, its webhooks and the ledger. */
|
|
4557
|
+
metadata?: Record<string, unknown>;
|
|
4558
|
+
};
|
|
4559
|
+
type RenderBatchProgress = BatchProgress & {
|
|
4560
|
+
/** The product just recorded, or skipped. */
|
|
4561
|
+
product: string;
|
|
4562
|
+
/** Products recorded so far. */
|
|
4563
|
+
products: number;
|
|
4564
|
+
/** Shots recorded so far, clips included. */
|
|
4565
|
+
images: number;
|
|
4566
|
+
/** Products skipped so far. */
|
|
4567
|
+
skipped: number;
|
|
4568
|
+
};
|
|
4569
|
+
/** A product left out of a run, with the reason. */
|
|
4570
|
+
type ProductSkip = {
|
|
4571
|
+
id: string;
|
|
4572
|
+
url: string;
|
|
4573
|
+
error: Bakery3Error;
|
|
4574
|
+
};
|
|
4575
|
+
type RenderBatchRecorded = Recorded & {
|
|
4576
|
+
/**
|
|
4577
|
+
* Products whose model could not be imported or read. The run went on
|
|
4578
|
+
* without them.
|
|
4579
|
+
*/
|
|
4580
|
+
skipped: number;
|
|
4581
|
+
/** The first of those, with their reasons. */
|
|
4582
|
+
skips: ProductSkip[];
|
|
4583
|
+
};
|
|
4584
|
+
type RenderBatchOptions = ShotSettings & {
|
|
4585
|
+
/** Names the run. The same key re-opens it while it is unfinished. */
|
|
4586
|
+
key: string;
|
|
4587
|
+
/** A label for the console. Default: the key. */
|
|
4588
|
+
name?: string;
|
|
4589
|
+
/**
|
|
4590
|
+
* An array, a generator, a database cursor. Pulled as it is needed, never
|
|
4591
|
+
* collected.
|
|
4592
|
+
*/
|
|
4593
|
+
products: Iterable<BatchProduct> | AsyncIterable<BatchProduct>;
|
|
4594
|
+
/**
|
|
4595
|
+
* Every product's shots, unless it has its own. Default: one image,
|
|
4596
|
+
* `'three-quarter'` (`'room'` in a room).
|
|
4597
|
+
*/
|
|
4598
|
+
shots?: Shot[];
|
|
4599
|
+
/**
|
|
4600
|
+
* Appended to every key as `@version`. Bump it to trace again what already
|
|
4601
|
+
* rendered.
|
|
4602
|
+
*/
|
|
4603
|
+
version?: string;
|
|
4604
|
+
/** The ceiling for the whole run, in USD. */
|
|
4605
|
+
maxCost: number;
|
|
4606
|
+
/** A ceiling per shot, in USD. A shot quoted above it is refused. */
|
|
4607
|
+
maxCostPerImage?: number;
|
|
4608
|
+
/**
|
|
4609
|
+
* `'batch'` (the default) never runs ahead of a person in a configurator;
|
|
4610
|
+
* `'normal'` is the API's ordinary lane.
|
|
4611
|
+
*/
|
|
4612
|
+
lane?: 'batch' | 'normal';
|
|
4613
|
+
/** Fewer parallel shots than the plan allows, for a gentler budget. */
|
|
4614
|
+
maxParallel?: number;
|
|
4615
|
+
/** Imports in flight at once. Default 4, at most 16. */
|
|
4616
|
+
concurrency?: number;
|
|
4617
|
+
/** Called after each product is recorded or skipped. */
|
|
4618
|
+
onProgress?: (progress: RenderBatchProgress) => void;
|
|
4619
|
+
/**
|
|
4620
|
+
* Stops between products. What was recorded stays admitted; call again to
|
|
4621
|
+
* go on.
|
|
4622
|
+
*/
|
|
4623
|
+
signal?: AbortSignal;
|
|
4624
|
+
};
|
|
4625
|
+
type RenderBatchRecording = RenderBatchRecorded & {
|
|
4626
|
+
estimated: number;
|
|
4627
|
+
maximum: number;
|
|
4628
|
+
};
|
|
4629
|
+
|
|
4630
|
+
/**
|
|
4631
|
+
* Webhook signature verification.
|
|
4632
|
+
*
|
|
4633
|
+
* A webhook endpoint is a public URL that causes writes in the developer's
|
|
4634
|
+
* database. Verifying is not optional, so it is one function with no options
|
|
4635
|
+
* and no way to get it subtly wrong.
|
|
4636
|
+
*
|
|
4637
|
+
* const event = await verifyWebhook({
|
|
4638
|
+
* body: await request.text(),
|
|
4639
|
+
* signature: request.headers.get('bakery3-signature'),
|
|
4640
|
+
* secret: process.env.BAKERY3_WEBHOOK_SECRET!,
|
|
4641
|
+
* });
|
|
4642
|
+
*
|
|
4643
|
+
* Pass the raw body, not a parsed and re-stringified object. Re-serializing
|
|
4644
|
+
* JSON changes key order and whitespace and therefore the signature, and it
|
|
4645
|
+
* is the single most common reason a correct implementation fails.
|
|
4646
|
+
*/
|
|
4647
|
+
|
|
4648
|
+
/**
|
|
4649
|
+
* Verify a webhook request and return its payload. Throws a `Bakery3Error`
|
|
4650
|
+
* (`Unauthorized`) when the header is missing, malformed, older than five
|
|
4651
|
+
* minutes, or signed with another secret.
|
|
4652
|
+
*/
|
|
4653
|
+
declare function verifyWebhook(input: {
|
|
4654
|
+
/** The request body exactly as received, as text. */
|
|
4655
|
+
body: string;
|
|
4656
|
+
/**
|
|
4657
|
+
* The `bakery3-signature` header. `headers.get()` gives `null` when the
|
|
4658
|
+
* request has none, and that is refused like a bad signature.
|
|
4659
|
+
*/
|
|
4660
|
+
signature: string | null | undefined;
|
|
4661
|
+
/** The signing secret from your webhook settings. */
|
|
4662
|
+
secret: string;
|
|
4663
|
+
/** Test seam. Never pass this in production. */
|
|
4664
|
+
now?: number;
|
|
4665
|
+
}): Promise<WebhookPayload>;
|
|
4666
|
+
|
|
4667
|
+
/**
|
|
4668
|
+
* @oeave/bakery3/node: the server half.
|
|
4669
|
+
*
|
|
4670
|
+
* The secret key lives here and only here. It never reaches a browser and is
|
|
4671
|
+
* never an argument to anything in `@oeave/bakery3`. This module mints the
|
|
4672
|
+
* short-lived, narrow token that goes in its place, verifies webhooks, and
|
|
4673
|
+
* calls the API with the secret key for the things a browser may not do.
|
|
4674
|
+
*
|
|
4675
|
+
* import { Bakery3Server } from '@oeave/bakery3/node';
|
|
4676
|
+
*
|
|
4677
|
+
* const bakery3 = new Bakery3Server(process.env.BAKERY3_SECRET_KEY!);
|
|
4678
|
+
*
|
|
4679
|
+
* export async function POST() {
|
|
4680
|
+
* return Response.json(
|
|
4681
|
+
* await bakery3.tokens.create({
|
|
4682
|
+
* expiresInSeconds: 300,
|
|
4683
|
+
* maxRenders: 1,
|
|
4684
|
+
* maxCost: 1,
|
|
4685
|
+
* allowedOrigins: ['https://shop.example.com'],
|
|
4686
|
+
* })
|
|
4687
|
+
* );
|
|
4688
|
+
* }
|
|
4689
|
+
*
|
|
4690
|
+
* That endpoint is the entire server-side integration.
|
|
4691
|
+
*/
|
|
4692
|
+
|
|
4693
|
+
type BrowserTokenOptions = {
|
|
4694
|
+
/**
|
|
4695
|
+
* How long the token lives, in seconds. Default 300. Keep it short: a
|
|
4696
|
+
* browser token is a capability handed to a page, and the shorter its life
|
|
4697
|
+
* the less a leaked one is worth. Five minutes is plenty, because the
|
|
4698
|
+
* browser SDK re-mints on a 401 without the developer writing anything.
|
|
4699
|
+
*/
|
|
4700
|
+
expiresInSeconds?: number;
|
|
4701
|
+
/** Hard ceiling on renders this token can start. Default 5. */
|
|
4702
|
+
maxRenders?: number;
|
|
4703
|
+
/** Hard ceiling on spend across all of them, in USD. Default 2. */
|
|
4704
|
+
maxCost?: number;
|
|
4705
|
+
/**
|
|
4706
|
+
* Origins allowed to spend it. Set this in production: it is what stops a
|
|
4707
|
+
* token lifted out of one page's network tab from working anywhere else.
|
|
4708
|
+
*/
|
|
4709
|
+
allowedOrigins?: string[];
|
|
4710
|
+
/** Your own user/session id. Shows up in the usage ledger for attribution. */
|
|
4711
|
+
subject?: string;
|
|
4712
|
+
};
|
|
4713
|
+
type BrowserToken = {
|
|
4714
|
+
/** The token itself, a `bk_pt_` string. Hand it to the browser SDK. */
|
|
4715
|
+
token: string;
|
|
4716
|
+
/** When it stops working, as a Unix timestamp in milliseconds. */
|
|
4717
|
+
expiresAt: number;
|
|
4718
|
+
/** The project the token renders into. */
|
|
4719
|
+
projectId: string;
|
|
4720
|
+
};
|
|
4721
|
+
type Bakery3ServerOptions = {
|
|
4722
|
+
/** The API's base URL. Only for a self-hosted or test deployment. */
|
|
4723
|
+
baseUrl?: string;
|
|
4724
|
+
/** A `fetch` to use instead of the global one. */
|
|
4725
|
+
fetch?: typeof fetch;
|
|
4726
|
+
/** How a 429/503 is waited out. The browser SDK's defaults otherwise. */
|
|
4727
|
+
retry?: RetryPolicy;
|
|
4728
|
+
};
|
|
4729
|
+
/** A run's pictures, plus which products were left out and why. */
|
|
4730
|
+
type RenderBatchRun = RenderSet & {
|
|
4731
|
+
readonly recorded: RenderBatchRecorded;
|
|
4732
|
+
};
|
|
4733
|
+
declare class Bakery3Server {
|
|
4734
|
+
readonly tokens: TokensApi;
|
|
4735
|
+
readonly renders: RendersApi;
|
|
4736
|
+
readonly batches: BatchesApi;
|
|
4737
|
+
readonly assets: AssetsApi;
|
|
4738
|
+
private readonly secretKey;
|
|
4739
|
+
private readonly options;
|
|
4740
|
+
private transport;
|
|
4741
|
+
/**
|
|
4742
|
+
* @param secretKey Your `bk_sk_` key, as a string, from a server-only
|
|
4743
|
+
* environment variable. Not the browser SDK's `{ apiKey }` object.
|
|
4744
|
+
*
|
|
4745
|
+
* The key is checked on the first request, not here. A route module that
|
|
4746
|
+
* builds its client at the top level is evaluated by `next build`, often
|
|
4747
|
+
* where the server's secrets are not set, and a constructor that threw
|
|
4748
|
+
* there would fail the build over a key the build never uses.
|
|
4749
|
+
*/
|
|
4750
|
+
constructor(secretKey: string, options?: Bakery3ServerOptions);
|
|
4751
|
+
/**
|
|
4752
|
+
* The same transport the browser SDK uses (one error shape, the same
|
|
4753
|
+
* 429/503 backoff) with the secret key as the bearer. It has nothing to
|
|
4754
|
+
* re-mint, so a 401 here is a 401. Made on first use, after the key is
|
|
4755
|
+
* checked.
|
|
4756
|
+
*/
|
|
4757
|
+
private client;
|
|
4758
|
+
/**
|
|
4759
|
+
* Render a catalog of models that already sit on a CDN: one call, no
|
|
4760
|
+
* browser, and none of the models' bytes through this process. Needs a
|
|
4761
|
+
* plan that includes imports (see https://bakery3.com/docs/catalog#plans).
|
|
4762
|
+
*
|
|
4763
|
+
* const run = await api.renderBatch({
|
|
4764
|
+
* key: 'fall-2026',
|
|
4765
|
+
* products: [{ id: 'chair-aria', url: 'https://cdn.shop.com/models/chair-aria.glb' }],
|
|
4766
|
+
* shots: ['three-quarter', 'front', { kind: 'video', from: 'front', to: 'back', seconds: 4 }],
|
|
4767
|
+
* room: { preset: 'loft_golden' },
|
|
4768
|
+
* maxCost: 600,
|
|
4769
|
+
* });
|
|
4770
|
+
* await run.wait(); // or let a webhook tell you, and exit now
|
|
4771
|
+
*
|
|
4772
|
+
* Resolves once every product is recorded and the run is sealed. The
|
|
4773
|
+
* shots render on from there; this process does not have to stay up.
|
|
4774
|
+
* A product whose model cannot be imported is skipped and listed in
|
|
4775
|
+
* `run.recorded.skips`, and the run goes on. Calling it again with the
|
|
4776
|
+
* same `key` resumes a recording that stopped, re-downloads no model that
|
|
4777
|
+
* did not change, and adopts every shot that already rendered.
|
|
4778
|
+
*
|
|
4779
|
+
* What a model by URL cannot carry (node materials, a shader-lit set) is
|
|
4780
|
+
* recorded where that code runs; see `renderVariants()` in `@oeave/bakery3`.
|
|
4781
|
+
*/
|
|
4782
|
+
renderBatch(options: RenderBatchOptions): Promise<RenderBatchRun>;
|
|
4783
|
+
/**
|
|
4784
|
+
* What `renderBatch()` would cost. The models are imported (that is how
|
|
4785
|
+
* they are priced); nothing is rendered or charged.
|
|
4786
|
+
*/
|
|
4787
|
+
quoteBatch(options: Omit<RenderBatchOptions, 'maxCost'> & {
|
|
4788
|
+
maxCost?: number;
|
|
4789
|
+
}): Promise<RenderBatchRecording>;
|
|
4790
|
+
/**
|
|
4791
|
+
* Look before paying: three products (the first, the middle and the last
|
|
4792
|
+
* of an array; the first three of anything else), every shot, at preview
|
|
4793
|
+
* quality and a small size, in the ordinary lane so they come back in
|
|
4794
|
+
* about a minute. A clip is cut to two seconds at twelve frames. Same
|
|
4795
|
+
* options as `renderBatch()`. The test run has keys of its own, so the
|
|
4796
|
+
* real run never adopts these previews as finished shots.
|
|
4797
|
+
*
|
|
4798
|
+
* const test = await api.testBatch(options);
|
|
4799
|
+
* for (const shot of await test.result()) console.log(shot.variant, shot.shot, shot.url);
|
|
4800
|
+
*/
|
|
4801
|
+
testBatch(options: Omit<RenderBatchOptions, 'maxCost'> & {
|
|
4802
|
+
maxCost?: number;
|
|
4803
|
+
}, test?: TestRunOptions): Promise<RenderBatchRun>;
|
|
4804
|
+
/**
|
|
4805
|
+
* A run by id, from another process, a webhook handler, or tomorrow
|
|
4806
|
+
* morning.
|
|
4807
|
+
*/
|
|
4808
|
+
getRenderSet(id: string): Promise<RenderSet>;
|
|
4809
|
+
private openRun;
|
|
4810
|
+
}
|
|
4811
|
+
declare class AssetsApi {
|
|
4812
|
+
private readonly client;
|
|
4813
|
+
constructor(client: () => Bakery3Client);
|
|
4814
|
+
/**
|
|
4815
|
+
* Have the service fetch a public https URL into the project's assets,
|
|
4816
|
+
* and say what it found: the hash to put in a manifest, and for a model
|
|
4817
|
+
* its triangles, bounds, cameras and anything the renderer cannot read.
|
|
4818
|
+
*
|
|
4819
|
+
* Secret key only, which is why it is here and not in the browser SDK.
|
|
4820
|
+
* What is fetched is fenced on the service's side: https, public
|
|
4821
|
+
* addresses only, no credentials, a size limit, and the file must be a
|
|
4822
|
+
* model or an HDRI. An unchanged URL is answered without a download
|
|
4823
|
+
* (`unchanged: true`).
|
|
4824
|
+
*/
|
|
4825
|
+
import(url: string, options?: {
|
|
4826
|
+
kind?: AssetImportKind;
|
|
4827
|
+
}): Promise<AssetImportResponse>;
|
|
4828
|
+
/**
|
|
4829
|
+
* Delete one uploaded file from the project now, rather than when
|
|
4830
|
+
* retention would. `deleted` is false when the project held no such file,
|
|
4831
|
+
* so deleting twice is not an error. The next render that needs the file
|
|
4832
|
+
* uploads it again; a render still queued that needs it can fail, and a
|
|
4833
|
+
* failed render costs nothing.
|
|
4834
|
+
*
|
|
4835
|
+
* await api.assets.delete('sha256:9f2b…');
|
|
4836
|
+
* // { hash: 'sha256:9f2b…', deleted: true }
|
|
4837
|
+
*/
|
|
4838
|
+
delete(hash: string): Promise<AssetDeleteResponse>;
|
|
4839
|
+
/**
|
|
4840
|
+
* Delete every uploaded file in the project, a page per request until
|
|
4841
|
+
* none is left. Resolves with how many were deleted.
|
|
4842
|
+
*
|
|
4843
|
+
* await api.assets.deleteAll();
|
|
4844
|
+
* // { deleted: 240 }
|
|
4845
|
+
*/
|
|
4846
|
+
deleteAll(): Promise<{
|
|
4847
|
+
deleted: number;
|
|
4848
|
+
}>;
|
|
4849
|
+
}
|
|
4850
|
+
type Request = <T>(method: string, path: string, body?: unknown) => Promise<T>;
|
|
4851
|
+
declare class TokensApi {
|
|
4852
|
+
private readonly request;
|
|
4853
|
+
constructor(request: Request);
|
|
4854
|
+
/**
|
|
4855
|
+
* Mint a browser token.
|
|
4856
|
+
*
|
|
4857
|
+
* Everything about it is a ceiling rather than a grant: it cannot outlive
|
|
4858
|
+
* its expiry, cannot start more renders than `maxRenders`, cannot spend past
|
|
4859
|
+
* `maxCost`, cannot be used from an origin outside `allowedOrigins`, and
|
|
4860
|
+
* cannot perform a single billing or admin operation. That last one is not
|
|
4861
|
+
* a policy: those routes require a `bk_sk_` key and this is not one.
|
|
4862
|
+
*/
|
|
4863
|
+
create(options?: BrowserTokenOptions): Promise<BrowserToken>;
|
|
4864
|
+
/** Revoke a token before its expiry. */
|
|
4865
|
+
revoke(token: string): Promise<void>;
|
|
4866
|
+
}
|
|
4867
|
+
declare class RendersApi {
|
|
4868
|
+
private readonly request;
|
|
4869
|
+
private readonly client;
|
|
4870
|
+
constructor(request: Request, client: () => Bakery3Client);
|
|
4871
|
+
/**
|
|
4872
|
+
* Render from a manifest you already have: a batch job, a re-render, a
|
|
4873
|
+
* queued webhook. The browser flow is `@oeave/bakery3`; this is the API.
|
|
4874
|
+
* Repeated on a 429/503 only when `spec.idempotencyKey` makes that safe.
|
|
4875
|
+
*/
|
|
4876
|
+
create(spec: RenderSpec): Promise<RenderRecord>;
|
|
4877
|
+
/** A render's record, with its state, cost and result URLs. */
|
|
4878
|
+
get(id: string): Promise<RenderRecord>;
|
|
4879
|
+
/** Cancel a render that has not finished. Already terminal is a no-op. */
|
|
4880
|
+
cancel(id: string): Promise<RenderRecord>;
|
|
4881
|
+
/**
|
|
4882
|
+
* One more attempt for a render that failed with `attemptsExhausted`.
|
|
4883
|
+
* Returns the new render, linked to the old one; a render that is not in
|
|
4884
|
+
* that state answers 409.
|
|
4885
|
+
*/
|
|
4886
|
+
retry(id: string): Promise<RenderRecord>;
|
|
4887
|
+
/**
|
|
4888
|
+
* Delete a render now rather than when retention would: the result, the
|
|
4889
|
+
* preview, and every signed URL that pointed at them. The record survives
|
|
4890
|
+
* as `expired`, so the receipt is still readable. A live render is
|
|
4891
|
+
* cancelled first.
|
|
4892
|
+
*/
|
|
4893
|
+
delete(id: string): Promise<RenderRecord>;
|
|
4894
|
+
/** The project's renders, newest first, a page at a time. */
|
|
4895
|
+
list(options?: {
|
|
4896
|
+
limit?: number;
|
|
4897
|
+
cursor?: string;
|
|
4898
|
+
}): Promise<{
|
|
4899
|
+
renders: RenderRecord[];
|
|
4900
|
+
cursor: string | null;
|
|
4901
|
+
}>;
|
|
4902
|
+
/** The quote for a spec: a likely cost and a maximum, in USD. */
|
|
4903
|
+
estimate(spec: Pick<RenderSpec, 'quality' | 'output' | 'advanced'>): Promise<CostEstimate>;
|
|
4904
|
+
}
|
|
4905
|
+
/**
|
|
4906
|
+
* Batches from a server: manifests you already have, added by the hundred
|
|
4907
|
+
* and rendered behind one cost ceiling. `renderBatch()` builds on this.
|
|
4908
|
+
*
|
|
4909
|
+
* const batch = await api.batches.open({ key: 'fall-2026-v3', lane: 'batch', maxCost: 600 });
|
|
4910
|
+
* for await (const line of readLines('manifests.jsonl')) {
|
|
4911
|
+
* const item = JSON.parse(line);
|
|
4912
|
+
* batch.add({ key: item.key, spec: item.spec, metadata: item.metadata });
|
|
4913
|
+
* if (batch.buffered >= 100) await batch.flush();
|
|
4914
|
+
* }
|
|
4915
|
+
* await batch.close();
|
|
4916
|
+
* await batch.wait({ onProgress: (p) => console.log(`${p.completed}/${p.total}`) });
|
|
4917
|
+
*/
|
|
4918
|
+
declare class BatchesApi {
|
|
4919
|
+
private readonly client;
|
|
4920
|
+
constructor(client: () => Bakery3Client);
|
|
4921
|
+
/** Open, or re-open by key. */
|
|
4922
|
+
open(request: BatchOpenRequest): Promise<Batch>;
|
|
4923
|
+
/** A batch by id, with its latest counts. */
|
|
4924
|
+
get(id: string): Promise<Batch>;
|
|
4925
|
+
}
|
|
4926
|
+
|
|
4927
|
+
export { type Angle, type AssetDeleteResponse, Bakery3Error, Bakery3Server, type Bakery3ServerOptions, Batch, type BatchAddRequest, type BatchAddResponse, type BatchEnvironment, type BatchExportLine, type BatchItemOutcome, type BatchItemRecord, type BatchItemRequest, type BatchItemsPage, type BatchOpenRequest, BatchPageRefused, type BatchProduct, type BatchProgress, type BatchRecord, type BatchRoom, type BrowserToken, type BrowserTokenOptions, type CostEstimate, type FlushOptions, type FrameSequenceIndex, type ImageShot, type ProductSkip, type Recorded, type RenderBatchOptions, type RenderBatchProgress, type RenderBatchRecorded, type RenderBatchRun, type RenderRecord, RenderSet, type RenderSetFailure, type RenderSetImage, type RenderSpec, type RetryPolicy, type Shot, type ShotSettings, type TestRunOptions, type VideoShot, type View, type ViewWord, type WaitOptions, verifyWebhook };
|