# Cenas 3D

> Em TypeScript, gere uma cena 3D editável com um prompt, edite-a, renderize-a em MP4 e execute a Renderização 3D Pro com client.scene3d e os nós de cena 3D.

Source: https://nodaro.ai/pt-BR/docs/developers/sdk/scenes-3d

**`client.scene3d`** faz cenas 3D editáveis: cria uma cena a partir de um prompt, edita a cena por instrução ou por operações exatas e renderiza uma revisão em MP4. Também executa a **Renderização 3D Pro** (3D Render Pro), uma operação que cria uma cena e a renderiza, e lê os arquivos que uma renderização entregou. Os métodos usam os nós [**Gerar cena 3D** (Generate 3D Scene)](https://nodaro.ai/docs/nodes/video/generate-3d-scene), [**Editar cena 3D** (Edit 3D Scene)](https://nodaro.ai/docs/nodes/video/edit-3d-scene), [**Renderizar vídeo** (Render Video)](https://nodaro.ai/docs/nodes/video/render-video) e [Renderização 3D Pro](https://nodaro.ai/docs/nodes/video/pro-3d-render). Veja a [API REST de cenas 3D](https://nodaro.ai/docs/developers/api/3d-scenes) para os endpoints.

## Métodos
| Método | O que faz |
| --- | --- |
| [`capabilities()`](#capabilities) | Lê quais mecanismos e opções Pro esta instalação oferece |
| [`generate(params)` e `generateAndWait()`](#generateparams-and-generateandwaitparams-options) | Cria uma nova cena a partir de um prompt |
| [`edit(params)` e `editAndWait()`](#editparams-and-editandwaitparams-options) | Faz uma nova revisão de uma cena |
| [`render(params)` e `renderAndWait()`](#renderparams-and-renderandwaitparams-options) | Renderiza uma revisão em MP4, sem etapa de criação |
| [`applyEdits(revisionId, params)`](#applyeditsrevisionid-params) | Salva edições exatas sem etapa de criação e sem cobrança |
| [`quotePro(params)`](#quoteproparams) | Calcula o preço de uma execução da Renderização 3D Pro |
| [`runPro(params, options?)`](#runproparams-options) | Inicia uma execução cotada da Renderização 3D Pro |
| [`renderProAndWait(params, options?)`](#renderproandwaitparams-options) | Faz a cotação, executa e aguarda em uma chamada |
| [`getDelivery(jobId)`](#getdeliveryjobid) | Lê os arquivos que uma renderização entregou |
| [`deliveryAssetBytes(jobId, asset, options?)`](#deliveryassetbytesjobid-asset-options) | Baixa um arquivo entregue |
| [`retainedRecipe(jobId, options?)`](#retainedrecipejobid-options) | Lê a receita que uma execução Pro recusada guardou |
| [`assetBytes(revisionId, asset, options?)`](#assetbytesrevisionid-asset-options) | Baixa um arquivo de uma revisão de cena |
| [`sourceBytes(revisionId, options?)`](#sourcebytesrevisionid-options) | Baixa o arquivo-fonte nativo de uma revisão |

## Do prompt ao MP4
```ts
const created = await client.scene3d.generateAndWait({
prompt: "Orbit a single box on a floor over four seconds",
durationSeconds: 4,
fps: 24,
aspectRatio: "16:9",
})

const edited = await client.scene3d.editAndWait({
scenePlan: created.scenePlan,
expectedRevisionId: created.scenePlan.revisionId,
operations: [{ op: "set-camera", changes: { focalLengthMm: 50 } }],
})

const clip = await client.scene3d.renderAndWait({ planType: "3d-scene", plan: edited.scenePlan })
console.log(clip.videoUrl)
```

Os mesmos três passos funcionam com [`client.nodes.runAndWait()`](https://nodaro.ai/docs/developers/sdk/nodes) e os tipos de nó `generate-3d-scene`, `edit-3d-scene` e `render-video`, que têm parâmetros tipados. Para permitir que as pessoas girem e ajustem uma cena na sua página, use a [incorporação da prévia 3D](https://nodaro.ai/docs/developers/embed/scene3d). Ela não precisa de uma cópia do renderizador nem de tokens nas mensagens.

## client.scene3d
### capabilities()
Retorna o que esta instalação consegue criar e renderizar (`GET /v1/3d-scene/capabilities`).

```ts
capabilities(): Promise<Scene3DCapabilities>
```

```ts
const caps = await client.scene3d.capabilities()
if (caps.advanced) console.log(caps.advanced.engines) // for example ["blender-cloud"]
if (caps.pro) console.log("3D Render Pro is available")
```

- **`basic`** é o mecanismo determinístico: `{ available, sceneSchemaVersions }`.
- **`advanced`** lista os mecanismos opcionais, ou é `null` quando não há nenhum. Um mecanismo explícito que não está disponível é recusado antes de uma geração Basic ou das verificações de créditos dela.
- **`pro`** descreve a Renderização 3D Pro: os mecanismos, os perfis de qualidade, os estilos e as proporções que esta instalação oferece. Ofereça só esses. Quando `pro` está ausente, o nó não está disponível.

### generate(params) e generateAndWait(params, options?)
Cria uma nova cena a partir de um prompt. `generate()` retorna o job imediatamente; `generateAndWait()` espera por ele e resolve com `scenePlan` e um `changeSummary` opcional.

```ts
generate(params: GenerateScene3DParams): Promise<RunNodeResult>
generateAndWait(params: GenerateScene3DParams, options?: RunAndWaitOptions): Promise<Scene3DJobOutput>
```

<TypeTable
type={{
prompt: { type: 'string', required: true, description: "O que acontece na cena." },
durationSeconds: { type: 'number', description: "A duração em segundos." },
fps: { type: 'number', description: "A taxa de quadros." },
aspectRatio: { type: 'string', description: "O formato do quadro, como 16:9." },
references: { type: 'Scene3DReference[]', description: "Referências de imagem e de vídeo, cada uma com um papel: appearance, layout ou motion." },
inputAssets: { type: 'Scene3DInputAsset[]', description: "Até 8 modelos GLB existentes a usar, cada um { id, revisionId, assetId, label? }. Exige um mecanismo avançado que consiga importar." },
engine: { type: '"basic" | "blender-cloud" | "blender-local"', default: '"basic"', description: "O mecanismo de criação. Veja capabilities()." },
acceptedSceneSchemaVersions: { type: 'number[]', description: "As versões do esquema de cena que o seu cliente consegue renderizar." },
maxRepairPasses: { type: 'number', description: "O orçamento de correções de um mecanismo avançado." },
llmModel: { type: 'string', description: "O modelo de linguagem que planeja a cena." },
reasoningEffort: { type: 'string', description: "O esforço de raciocínio do planejador." },
workflowId: { type: 'string', description: "Um workflow sob o qual listar a execução." },
}}
/>

```ts
const { scenePlan } = await client.scene3d.generateAndWait({
prompt: "A paper boat drifts across a puddle as rain starts",
references: [{ id: "look", kind: "image", role: "appearance", url: moodImageUrl }],
})
```

**`inputAssets`** seleciona modelos GLB que você tem permissão de usar, pelos IDs imutáveis deles; `references` continua trazendo as imagens de aparência e os vídeos de movimento. Não envie URLs nem hashes dos arquivos: o servidor fornece os registros dos bytes. O Basic e os mecanismos que não conseguem importar recusam `inputAssets` antes de cobrar. Reutilize as mesmas seleções quando enviar uma cotação Pro.

### edit(params) e editAndWait(params, options?)
Faz uma **nova revisão** de uma cena. A cena que você passa nunca é alterada. Dê uma instrução em `prompt` ou `operations` exatas, nunca os dois.

```ts
edit(params: EditScene3DParams): Promise<RunNodeResult>
editAndWait(params: EditScene3DParams, options?: RunAndWaitOptions): Promise<Scene3DJobOutput>
```

<TypeTable
type={{
scenePlan: { type: 'Scene3DPlan', required: true, description: "A cena de partida." },
expectedRevisionId: { type: 'string', required: true, description: "A revisão que você está editando." },
prompt: { type: 'string', description: "Uma instrução, como “make the camera slower”." },
operations: { type: 'Scene3DEditOperation[]', description: "Edições exatas, como { op: set-camera, changes: { focalLengthMm: 50 } }." },
references: { type: 'Scene3DReference[]', description: "Referências a adicionar, mescladas pelo ID." },
replaceReferences: { type: 'boolean', description: "Substitui o conjunto inteiro de referências em vez de mesclar. Uma lista vazia limpa o conjunto." },
lockedObjectIds: { type: 'string[]', description: "Objetos que a edição não pode alterar." },
selectedObjectIds: { type: 'string[]', description: "Objetos aos quais a instrução se refere." },
engine: { type: 'string', description: "O mecanismo de criação." },
llmModel: { type: 'string', description: "O modelo do planejador." },
}}
/>

```ts
const { scenePlan: next, changeSummary } = await client.scene3d.editAndWait({
scenePlan,
expectedRevisionId: scenePlan.revisionId,
prompt: "Make the rain heavier and lower the camera",
})
```

### render(params) e renderAndWait(params, options?)
Renderiza em MP4 exatamente a revisão que você passa, sem criar nem reconstruir nada (`POST /v1/render-video/plan`).

```ts
render(params: { planType: "3d-scene"; plan: Scene3DPlan; workflowId?: string; nodeId?: string }): Promise<RunNodeResult>
renderAndWait(params: RenderScene3DParams, options?: RunAndWaitOptions): Promise<NodeJobOutput>
```

<TypeTable
type={{
planType: { type: '"3d-scene"', required: true, description: "Marca o plano como uma cena 3D." },
plan: { type: 'Scene3DPlan', required: true, description: "A revisão a renderizar." },
workflowId: { type: 'string', description: "Um workflow sob o qual listar a execução." },
nodeId: { type: 'string', description: "O nó ao qual a execução pertence." },
}}
/>

```ts
const { videoUrl } = await client.scene3d.renderAndWait({ planType: "3d-scene", plan: scenePlan })
```

O preço segue a `width` e a `height` do próprio plano:

| Quadro | Créditos | Identificador de preço |
| --- | --- | --- |
| Até 1920 pixels no lado maior | 55 | `render-video` |
| Maior, até 5,12 megapixels | 83 | `render-video:3d-large` |
| Acima de 5,12 megapixels | 138 | `render-video:3d-xlarge` |

Leia os preços atuais desses identificadores com [`client.credits.modelCosts()`](https://nodaro.ai/docs/developers/sdk/models-and-credits).

### applyEdits(revisionId, params)
Salva edições exatas em uma revisão guardada, sem etapa de criação e sem cobrança (`POST /v1/3d-scene/revisions/:id/edits`). Retorna o novo `scenePlan` e um `changeSummary`.

```ts
applyEdits(revisionId: string, params: {
newRevisionId: string
expectedContentHash: string
operations: Scene3DV2EditOperation[]
lockedObjectIds?: string[]
}): Promise<{ scenePlan: Scene3DPlanV2; changeSummary: string }>
```

<TypeTable
type={{
revisionId: { type: 'string', required: true, description: "A revisão a editar." },
newRevisionId: { type: 'string', required: true, description: "O ID da nova revisão. Mantenha o mesmo valor quando repetir a mesma edição." },
expectedContentHash: { type: 'string', required: true, description: "O hash do conteúdo da revisão que você está editando." },
operations: { type: 'Scene3DV2EditOperation[]', required: true, description: "As edições exatas, para cenas da versão 2." },
lockedObjectIds: { type: 'string[]', description: "Objetos que as edições não podem alterar." },
}}
/>

```ts
const { scenePlan: saved } = await client.scene3d.applyEdits(revisionId, {
newRevisionId: crypto.randomUUID(),
expectedContentHash,
operations,
})
```

Adote a cena retornada só se o usuário ainda estiver editando a revisão de onde você partiu. Os arquivos de geometria e de câmera são reutilizados. Os pôsteres, a validação e os downloads nativos só são anexados de novo depois de serem gerados para a nova revisão.

## Renderização 3D Pro
A Renderização 3D Pro é uma única operação: entra uma `source`, e um único job termina com os dois resultados: o `scenePlan` (a composição exata) e o `videoUrl` (o MP4). O resultado também traz a revisão, um pôster, `shotStills`, a validação e os detalhes do renderizador. `client.nodes.run("pro-3d-render")` e `runAndWait` chegam à mesma rota com os mesmos parâmetros tipados.

Uma instalação sem o mecanismo responde `503 SCENE_CAPABILITY_UNAVAILABLE` e nunca recorre ao Basic. Uma instalação sem preço configurado responde `503 price_not_configured` antes de reservar qualquer coisa. Não há configuração de modelo nem de esforço: o servidor define o planejador.

### quotePro(params)
Calcula o preço de uma execução **sem iniciá-la**. Não reserva nem gasta nada. A resposta tem `quoteId`, um teto em `maxCredits`, um `breakdown` para mostrar e o hash de entrada com o qual a execução é verificada depois.

```ts
quotePro(params: Pro3DRenderParams): Promise<Pro3DRenderQuote>
```

<TypeTable
type={{
source: { type: 'Pro3DRenderSource', required: true, description: "O que renderizar. Veja as origens abaixo." },
engine: { type: 'string', description: "Um mecanismo de capabilities().pro. Um mecanismo desconhecido ou indisponível é recusado, nunca rebaixado." },
durationSeconds: { type: 'number', description: "A duração. Em uma origem de cena, ela reajusta o tempo da cena; omita-a para manter o tempo da cena." },
fps: { type: 'number', description: "A taxa de quadros." },
aspectRatio: { type: 'string', description: "O formato do quadro, de capabilities().pro." },
quality: { type: 'string', description: "Um perfil de qualidade de capabilities().pro." },
style: { type: 'string', description: "Um estilo de capabilities().pro." },
maxRepairPasses: { type: 'number', description: "O orçamento de correções, de 0 a 2. Cada passada é paga." },
acceptedSceneSchemaVersions: { type: 'number[]', description: "As versões de cena que o seu cliente consegue renderizar. Uma origem de prompt gera a versão 2, então inclua 2." },
localConnectionId: { type: 'string', description: "Um computador pareado, para uma execução local." },
forcePrivate: { type: 'boolean', description: "Mantém todos os arquivos da execução fora do armazenamento de leitura pública." },
}}
/>

`source` tem um destes três formatos:

- **`{ kind: "prompt", prompt, references? }`** cria uma nova cena.
- **`{ kind: "scene", revisionId, sourceJobId }`** sem `editPrompt` exporta uma cena guardada sem cobrança de criação. Adicionar `editPrompt` revisa a cena antes. `sourceJobId` é obrigatório para cenas Basic que existem só no histórico de jobs.
- **`{ kind: "local-export", exportId, connectionId }`** usa um computador pareado.

```ts
const quote = await client.scene3d.quotePro(params)
showPrice(quote.maxCredits, quote.breakdown)
```

### runPro(params, options?)
Inicia uma execução cotada. O método precisa do `quoteId` de `quotePro()`, para que nenhuma execução comece com um preço que ninguém viu. Ele envia um `Idempotency-Key`: um novo a cada chamada, ou o seu em `options.idempotencyKey`. Reutilize o seu quando repetir uma chamada que esgotou o tempo.

```ts
runPro(params: Pro3DRenderParams & { quoteId: string }, options?: { idempotencyKey?: string }): Promise<RunNodeResult>
```

<TypeTable
type={{
quoteId: { type: 'string', required: true, description: "A cotação sob a qual a execução é admitida." },
'...params': { type: 'Pro3DRenderParams', required: true, description: "O mesmo corpo que você cotou." },
idempotencyKey: { type: 'string', description: "O seu token de nova tentativa para esta execução." },
}}
/>

```ts
await client.scene3d.runPro({ ...params, quoteId: quote.quoteId })
```

### renderProAndWait(params, options?)
Faz a cotação quando `params` não tem `quoteId`, executa e espera: a operação inteira em uma chamada. Envia no máximo duas requisições e inicia um único job pago, admitido com base em um hash exatamente do que foi cotado.

```ts
renderProAndWait(params: Pro3DRenderParams | Pro3DRenderRunParams, options?: RunAndWaitOptions & { idempotencyKey?: string }): Promise<Pro3DRenderJobOutput>
```

<TypeTable
type={{
params: { type: 'Pro3DRenderParams', required: true, description: "A execução, com ou sem quoteId." },
options: { type: 'RunAndWaitOptions & { idempotencyKey? }', description: "Opções de consulta periódica e um token de nova tentativa." },
}}
/>

```ts
const caps = await client.scene3d.capabilities()
if (caps.pro?.available) {
const shot = await client.scene3d.renderProAndWait({
source: {
kind: "prompt",
prompt: "A red suitcase rolls behind a central pillar and reappears",
references: [{ id: "look", kind: "image", role: "appearance", url: appearanceImageUrl }],
},
durationSeconds: 30,
fps: 24,
aspectRatio: "21:9",
maxRepairPasses: 2,
acceptedSceneSchemaVersions: [2],
})
console.log(shot.videoUrl)        // the MP4
console.log(shot.sceneRevisionId) // export it again later, with no authoring charge
}
```

**Imagens fixas das tomadas.** `shotStills` é uma lista de `{ shotIndex, frame, assetId, url }`, uma imagem fixa por tomada no quadro em que a tomada começa, feita pela mesma execução sem custo extra. Uma cena de uma única tomada tem uma, no quadro 0. Os resultados feitos antes de o campo existir não têm nenhuma, então leia `shot.shotStills ?? []`.

Cada `url` é um endpoint **autenticado** da sua instalação, não um link público. Busque-a com as mesmas credenciais que você usou na execução; uma tag `img`, ou um serviço de terceiros, recebe um 401:

```ts
for (const still of shot.shotStills ?? []) {
const res = await fetch(still.url, { headers: { Authorization: `Bearer ${token}` } })
const bytes = await res.arrayBuffer() // use the bytes, or store them where your pipeline can read them
}
```

Você ainda pode usar uma imagem fixa como referência de modelo: passe a URL dela do jeito que você a leu, em `referenceImageUrls` ou pela saída `stills` do nó. A plataforma concede a essa execução uma leitura breve desse único arquivo, em seu nome. A concessão expira minutos depois, então armazene a URL autenticada, nunca a concessão.

### O que uma execução Pro informa sobre si mesma
Uma execução que criou uma cena informa o que presumiu e o que fez. Trate todos os campos como opcionais: uma instalação sem mecanismo avançado, ou um resultado mais antigo, não tem nenhum deles.

```ts
const summary = shot.metadata?.summary // what the planner says it built
const repairs = shot.repairPasses      // 0 means accepted the first time
const retries = shot.admissionRetries  // planner retries before a build
const mechanical = shot.mechanicalPasses
const restored = shot.restoredAssertions
const assumptions = (shot.validation?.warnings ?? [])
.filter((w) => w.code === "SCENE_AUTHORING_ASSUMPTION")
.map((w) => w.message)
```

- Os avisos **`SCENE_AUTHORING_ASSUMPTION`** listam o que o prompt não disse e, por isso, a execução decidiu. Novos códigos de aviso podem ser adicionados: trate um código desconhecido como informação, não como erro.
- **`repairPasses`** conta reparos, não passadas de criação, então `0` significa que a cena foi aceita de primeira. `admissionRetries` conta outra coisa: os pedidos repetidos de receita ao planejador antes de qualquer construção.
- **`mechanicalPasses`** conta os reparos que o mecanismo aplicou a partir da solução do próprio compilador, sem o planejador. Eles têm uma franquia cotada própria de até 2, liberada quando não é usada, e cada um adiciona um aviso `REMEDY_AUTO_APPLIED`. Uma execução cotada antes de essa franquia existir cobrava essas passadas como reparos; leia a cotação que você recebeu para saber.
- **`restoredAssertions`** lista as verificações obrigatórias que o mecanismo restaurou depois que o planejador alterou uma delas sem que isso tivesse sido pedido. Cada uma também adiciona um aviso `ASSERTION_RESTORED`.
- Uma exportação só de renderização não criou nada, então não tem contagens nem resumo. Especialmente em `mechanicalPasses`, ausente não é `0`.

Os resultados de `generateAndWait()` trazem os mesmos campos quando um mecanismo avançado criou a cena. O mecanismo Basic não consulta nenhum modelo e não traz nenhum deles.

### Uma entrega que o revisor visual não aprovou
Um resultado **concluído** pode chegar sem a aprovação do revisor visual. Nos dois casos, o vídeo é real e os créditos foram gastos. `metadata.review.verdict` diz qual é o caso:

- **`"refused"`**: o orçamento de reparos foi gasto, todas as verificações obrigatórias passaram, e o revisor ainda assim fez objeções. A cena foi entregue com a recusa anexada.
- **`"unavailable"`**: a revisão não deu um veredito utilizável. `reason` é `"provider"` quando ela nunca chegou ao provedor, e `"unusable"` quando a resposta não pôde ser usada. `attempts` diz quantas vezes a revisão foi solicitada. **Ninguém avaliou esta cena.**

```ts

const review = scene3DReviewVerdictOf(shot)
if (review) {
console.log(scene3DReviewNote(review)) // one user-safe sentence for either verdict
if (review.verdict === "unavailable") console.log(`unreviewed (${review.reason}) after ${review.attempts} attempts`)
for (const objection of review.objections) console.log(objection.category, objection.what, objection.correction)
}
```

Use os dois helpers de `@nodaro/shared` em vez de ler os campos você mesmo, porque três leituras parecem certas e não são:

- **`validation.status` continua `"passed"`.** As verificações obrigatórias de fato passaram, e é por isso que a cena foi entregue.
- **`objections` pode estar vazio.** Uma recusa que não apontou nada específico continua sendo uma recusa, então contar os avisos `SCENE_REVIEW_REFUSED` não a detecta.
- **As objeções sob `"unavailable"` não são o veredito.** Elas vêm de lotes de revisão que responderam antes de um deles falhar. Nesse caso, uma lista vazia significa silêncio, não aprovação.

Cada objeção é `{ category, what, correction?, frames }`. No veredito `"unavailable"`, um aviso `SCENE_REVIEW_UNAVAILABLE` vem primeiro em `validation.warnings`. Uma recusa visual sozinha não faz mais o job falhar. `SCENE_QUALITY_FAILED` significa que uma verificação obrigatória falhou ou que o compilador recusou a receita. Veja [Renderização 3D Pro](https://nodaro.ai/docs/nodes/video/pro-3d-render) para saber o que um resultado com falha desse tipo mantém.

## Entregas e arquivos
Estes métodos leem arquivos que uma execução já entregou. Eles nunca iniciam uma renderização. Toda leitura verifica de novo o seu acesso à entrega e à origem dela, mesmo depois que a revisão de origem foi excluída.

### getDelivery(jobId)
Lê o registro de uma entrega (`GET /v1/3d-scene/deliveries/:jobId`): o `sourceKind` dela, a revisão de origem exata e os arquivos que ela fixou.

```ts
getDelivery(jobId: string): Promise<Scene3DDelivery>
```

<TypeTable
type={{
jobId: { type: 'string', required: true, description: "O ID do job da renderização." },
}}
/>

```ts
const delivery = await client.scene3d.getDelivery(jobId)
const stills = delivery.assets.filter((a) => a.kind === "shot-still")
```

Aparecem quatro tipos de arquivo: `validation-report` em toda entrega, `poster` em toda entrega que não foi recusada, `shot-still` uma vez por tomada, cada um com o seu `shotIndex`, `frame`, `width` e `height`, e `source-json` só em uma entrega `refused-authoring`. `sourceKind` é `retained-revision`, `job-output` ou `refused-authoring`. Em uma entrega recusada, `sceneRevisionId` e `sourcePlanSha256` são `null` e não há pôster, porque nada foi compilado.

### deliveryAssetBytes(jobId, asset, options?)
Baixa um arquivo listado pela entrega, com autenticação nova e um limite de tamanho.

```ts
deliveryAssetBytes(jobId: string, asset: Scene3DDeliveryAsset, options?: { signal?: AbortSignal }): Promise<ArrayBuffer>
```

<TypeTable
type={{
jobId: { type: 'string', required: true, description: "O ID do job da renderização." },
asset: { type: 'Scene3DDeliveryAsset', required: true, description: "Um descritor de arquivo de getDelivery(), passado como está." },
signal: { type: 'AbortSignal', description: "Interrompe o download." },
}}
/>

```ts
const bytes = await client.scene3d.deliveryAssetBytes(jobId, stills[0])
```

### retainedRecipe(jobId, options?)
Lê a receita que uma execução recusada da Renderização 3D Pro guardou, já interpretada, ou `null` quando não há nenhuma. A receita de uma execução recusada nunca foi compilada, então não há revisão de cena, pôster nem arquivo-fonte; a receita é o que ela deixa para trás.

```ts
retainedRecipe(jobId: string, options?: { signal?: AbortSignal }): Promise<unknown | null>
```

<TypeTable
type={{
jobId: { type: 'string', required: true, description: "O ID do job da execução recusada." },
signal: { type: 'AbortSignal', description: "Interrompe o download." },
}}
/>

```ts
const recipe = await client.scene3d.retainedRecipe(jobId)
```

- **Exige acesso de edição** ao workflow do job. Um leitor com menos acesso nem chega a ver a receita, então a resposta é `null`, não um erro.
- **O job com falha diz se vale a pena pedir.** O `validation.sourceRetained` dele é `true` quando uma receita foi guardada.
- **É evidência, não entrada.** Uma execução recusada não publicou nenhuma revisão, então você não pode executá-la de novo a partir da receita. Leia a receita para ver o que foi tentado e melhorar o próximo prompt. A leitura não custa créditos.

### assetBytes(revisionId, asset, options?)
Baixa um arquivo de uma revisão de cena guardada: um modelo GLB, uma trilha de câmera, o pôster ou o relatório de validação. Passe o descritor exato dessa revisão. O SDK limita o download ao tamanho declarado, e o renderizador de cenas também verifica o hash SHA-256.

```ts
assetBytes(revisionId: string, asset: Scene3DAssetRef, options?: { signal?: AbortSignal }): Promise<ArrayBuffer>
```

<TypeTable
type={{
revisionId: { type: 'string', required: true, description: "A revisão da cena." },
asset: { type: 'Scene3DAssetRef', required: true, description: "Um descritor do tipo glb, camera-track-json, poster ou validation-report." },
signal: { type: 'AbortSignal', description: "Interrompe o download." },
}}
/>

```ts
const glb = await client.scene3d.assetBytes(scenePlan.revisionId, glbAsset)
```

### sourceBytes(revisionId, options?)
Baixa o arquivo-fonte nativo de uma revisão, como um arquivo `.blend`, com uma autorização própria. Exige o mesmo acesso de edição que uma receita guardada. Um arquivo nativo só está disponível quando representa exatamente aquela revisão aceita.

```ts
sourceBytes(revisionId: string, options?: { signal?: AbortSignal }): Promise<ArrayBuffer>
```

<TypeTable
type={{
revisionId: { type: 'string', required: true, description: "A revisão da cena." },
signal: { type: 'AbortSignal', description: "Interrompe o download." },
}}
/>

```ts
const blend = await client.scene3d.sourceBytes(revisionId)
```

Os dois métodos de bytes usam credenciais novas, respeitam o cancelamento e lançam os [erros tipados](https://nodaro.ai/docs/developers/sdk/errors) de sempre.

## Frequently asked questions

### Como transformo um prompt em uma animação 3D com o SDK?

Execute client.scene3d.generateAndWait com um prompt para obter um plano de cena editável, altere-o com editAndWait se quiser e depois renderize-o em MP4 com renderAndWait.

### Quanto custa renderizar uma cena 3D?

O preço segue o tamanho do quadro do plano: 55 créditos até 1920 pixels no lado maior, 83 créditos até 5,12 megapixels e 138 créditos acima disso. Leia os preços atuais na API de custos dos modelos.

### O que é a Renderização 3D Pro?

Uma operação que cria uma cena e a renderiza: o resultado traz tanto o plano de cena quanto o MP4. Faça antes a cotação com quotePro. Verifique capabilities().pro para saber se a sua instalação oferece a operação.

### Posso abrir a URL da imagem fixa de uma tomada em uma tag img?

Não. As imagens fixas das tomadas e os outros arquivos de entrega são servidos por um endpoint autenticado. Busque-os com as mesmas credenciais que você usou para executar o job e depois mostre ou armazene os bytes.
