3D 장면
client.scene3d와 3D 장면 노드로 TypeScript에서 프롬프트로 편집 가능한 3D 장면을 생성하고, 편집하고, MP4로 렌더링하며, 3D 렌더링 Pro를 실행합니다.
client.scene3d는 편집 가능한 3D 장면을 만듭니다. 프롬프트로 장면을 제작하고, 지시나 정확한 연산으로 편집하며, 리비전을 MP4로 렌더링합니다. 장면을 제작하고 렌더링까지 하는 하나의 작업인 3D 렌더링 Pro도 실행하고, 렌더링이 전달한 파일도 읽습니다. 이 메서드들은 3D 장면 생성, 3D 장면 편집, 동영상 렌더링, 3D 렌더링 Pro 노드를 사용합니다. 엔드포인트는 3D 장면 REST API를 참조하세요.
메서드
| 메서드 | 하는 일 |
|---|---|
capabilities() | 이 설치가 제공하는 엔진과 Pro 옵션을 읽습니다 |
generate(params)와 generateAndWait() | 프롬프트로 새 장면을 제작합니다 |
edit(params)와 editAndWait() | 장면의 새 리비전을 만듭니다 |
render(params)와 renderAndWait() | 제작 없이 리비전을 MP4로 렌더링합니다 |
applyEdits(revisionId, params) | 제작이나 요금 없이 정확한 편집을 저장합니다 |
quotePro(params) | 3D 렌더링 Pro 실행의 가격을 매깁니다 |
runPro(params, options?) | 견적을 받은 3D 렌더링 Pro 실행을 시작합니다 |
renderProAndWait(params, options?) | 견적, 실행, 대기를 한 번의 호출로 처리합니다 |
getDelivery(jobId) | 렌더링이 전달한 파일을 읽습니다 |
deliveryAssetBytes(jobId, asset, options?) | 전달된 파일 하나를 다운로드합니다 |
retainedRecipe(jobId, options?) | 거부된 Pro 실행이 보관한 레시피를 읽습니다 |
assetBytes(revisionId, asset, options?) | 장면 리비전의 파일을 다운로드합니다 |
sourceBytes(revisionId, options?) | 리비전의 네이티브 소스 파일을 다운로드합니다 |
프롬프트에서 MP4까지
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)같은 세 단계는 client.nodes.runAndWait()와, 타입이 지정된 매개변수가 있는 노드 유형 generate-3d-scene, edit-3d-scene, render-video로도 할 수 있습니다. 내 페이지에서 사람들이 장면을 회전하고 조정할 수 있게 하려면 3D 미리 보기 임베드를 사용하세요. 렌더러를 복사할 필요가 없고, 메시지에 토큰도 들어가지 않습니다.
client.scene3d
capabilities()
이 설치가 제작하고 렌더링할 수 있는 것을 반환합니다(GET /v1/3d-scene/capabilities).
capabilities(): Promise<Scene3DCapabilities>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은 결정론적 엔진입니다.{ available, sceneSchemaVersions }형태입니다.advanced는 선택적인 엔진을 나열하며, 엔진이 없으면null입니다. 사용할 수 없는 엔진을 명시적으로 지정하면 기본 엔진의 생성이나 그 크레딧 확인보다 먼저 거부됩니다.pro는 3D 렌더링 Pro를 설명합니다. 이 설치가 제공하는 엔진, 품질 프로필, 스타일, 화면 비율입니다. 이 옵션만 제공하세요.pro가 없으면 그 노드는 사용할 수 없습니다.
generate(params)와 generateAndWait(params, options?)
프롬프트로 새 장면을 제작합니다. generate()는 작업을 즉시 반환하고, generateAndWait()는 작업을 기다린 뒤 scenePlan과 선택적인 changeSummary를 반환합니다.
generate(params: GenerateScene3DParams): Promise<RunNodeResult>
generateAndWait(params: GenerateScene3DParams, options?: RunAndWaitOptions): Promise<Scene3DJobOutput>속성
타입
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는 내가 사용할 수 있는 GLB 모델을 변하지 않는 ID로 선택합니다. 외관 이미지와 모션 동영상은 여전히 references로 전달합니다. 에셋 URL이나 해시는 보내지 마세요. 바이트 기록은 서버가 제공합니다. 기본 엔진과 가져오기를 지원하지 않는 엔진은 요금을 청구하기 전에 inputAssets를 거부합니다. Pro 견적을 요청할 때도 같은 선택자를 다시 사용하세요.
edit(params)와 editAndWait(params, options?)
장면의 새 리비전을 만듭니다. 전달한 장면은 절대 바뀌지 않습니다. prompt에 지시를 쓰거나 정확한 operations를 전달하세요. 둘을 함께 쓸 수는 없습니다.
edit(params: EditScene3DParams): Promise<RunNodeResult>
editAndWait(params: EditScene3DParams, options?: RunAndWaitOptions): Promise<Scene3DJobOutput>속성
타입
const { scenePlan: next, changeSummary } = await client.scene3d.editAndWait({
scenePlan,
expectedRevisionId: scenePlan.revisionId,
prompt: "Make the rain heavier and lower the camera",
})render(params)와 renderAndWait(params, options?)
전달한 리비전을 그대로, 아무것도 제작하거나 다시 빌드하지 않고 MP4로 렌더링합니다(POST /v1/render-video/plan).
render(params: { planType: "3d-scene"; plan: Scene3DPlan; workflowId?: string; nodeId?: string }): Promise<RunNodeResult>
renderAndWait(params: RenderScene3DParams, options?: RunAndWaitOptions): Promise<NodeJobOutput>속성
타입
const { videoUrl } = await client.scene3d.renderAndWait({ planType: "3d-scene", plan: scenePlan })가격은 플랜 자체의 width와 height에 따라 정해집니다.
| 프레임 | 크레딧 | 가격 식별자 |
|---|---|---|
| 긴 변이 1,920픽셀 이하 | 55 | render-video |
| 그보다 크고 5.12메가픽셀 이하 | 83 | render-video:3d-large |
| 5.12메가픽셀 초과 | 138 | render-video:3d-xlarge |
이 식별자들의 현재 가격은 client.credits.modelCosts()로 읽으세요.
applyEdits(revisionId, params)
보관된 리비전에 정확한 편집을 제작 없이, 요금 없이 저장합니다(POST /v1/3d-scene/revisions/:id/edits). 새 scenePlan과 changeSummary를 반환합니다.
applyEdits(revisionId: string, params: {
newRevisionId: string
expectedContentHash: string
operations: Scene3DV2EditOperation[]
lockedObjectIds?: string[]
}): Promise<{ scenePlan: Scene3DPlanV2; changeSummary: string }>속성
타입
const { scenePlan: saved } = await client.scene3d.applyEdits(revisionId, {
newRevisionId: crypto.randomUUID(),
expectedContentHash,
operations,
})사용자가 아직 편집을 시작한 리비전을 편집 중일 때만 반환된 장면을 채택하세요. 형상과 카메라 파일은 재사용됩니다. 포스터, 검증 결과, 네이티브 다운로드는 새 리비전용으로 만들어진 뒤에야 다시 첨부됩니다.
3D 렌더링 Pro
3D 렌더링 Pro는 하나의 작업입니다. source를 넣으면 작업 하나가 정확한 구성인 scenePlan과 MP4인 videoUrl을 모두 담아 완료됩니다. 결과에는 리비전, 포스터, shotStills, 검증 결과, 렌더러 정보도 담깁니다. client.nodes.run("pro-3d-render")와 runAndWait도 같은 타입의 매개변수로 같은 라우트를 호출합니다.
엔진이 없는 설치는 503 SCENE_CAPABILITY_UNAVAILABLE로 응답하며, 기본 엔진으로 대체하지 않습니다. 가격이 설정되지 않은 설치는 무엇이든 예약하기 전에 503 price_not_configured로 응답합니다. 모델이나 추론 강도 설정은 없습니다. 플래너는 서버가 정합니다.
quotePro(params)
실행을 시작하지 않고 가격을 매깁니다. 아무것도 예약하거나 사용하지 않습니다. 응답에는 quoteId, 상한인 maxCredits, 표시용 breakdown, 그리고 나중에 실행을 대조할 입력 해시가 있습니다.
quotePro(params: Pro3DRenderParams): Promise<Pro3DRenderQuote>속성
타입
source는 다음 세 가지 형태 중 하나입니다.
{ kind: "prompt", prompt, references? }는 새 장면을 제작합니다.{ kind: "scene", revisionId, sourceJobId }는editPrompt가 없으면 제작 요금 없이 보관된 장면을 내보냅니다.editPrompt를 추가하면 먼저 장면을 수정합니다. 작업 기록에만 있는 기본 엔진 장면에는sourceJobId가 필요합니다.{ kind: "local-export", exportId, connectionId }는 페어링된 데스크톱을 사용합니다.
const quote = await client.scene3d.quotePro(params)
showPrice(quote.maxCredits, quote.breakdown)runPro(params, options?)
견적을 받은 실행을 시작합니다. quotePro()의 quoteId가 필요하므로, 아무도 보지 않은 가격으로 실행이 시작되는 일은 없습니다. Idempotency-Key를 보내는데, 호출마다 새로 만든 키이거나 options.idempotencyKey에 넣은 내 키입니다. 시간 초과된 호출을 다시 시도할 때는 내 키를 다시 사용하세요.
runPro(params: Pro3DRenderParams & { quoteId: string }, options?: { idempotencyKey?: string }): Promise<RunNodeResult>속성
타입
await client.scene3d.runPro({ ...params, quoteId: quote.quoteId })renderProAndWait(params, options?)
params에 quoteId가 없으면 견적을 받고, 실행하고, 기다립니다. 전체 작업을 한 번의 호출로 처리합니다. 요청은 최대 2번 보내며, 가격을 매긴 내용과 정확히 같은 것의 해시를 기준으로 승인된 유료 작업 하나를 시작합니다.
renderProAndWait(params: Pro3DRenderParams | Pro3DRenderRunParams, options?: RunAndWaitOptions & { idempotencyKey?: string }): Promise<Pro3DRenderJobOutput>속성
타입
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
}샷 스틸. shotStills는 { shotIndex, frame, assetId, url }의 목록입니다. 샷마다 그 샷이 시작되는 프레임의 스틸 하나가 있으며, 같은 실행이 추가 비용 없이 만듭니다. 샷이 하나인 장면에는 프레임 0의 스틸 하나가 있습니다. 이 필드가 생기기 전에 만들어진 결과에는 스틸이 없으므로 shot.shotStills ?? []로 읽으세요.
각 url은 공개 링크가 아니라 내 설치의 인증이 필요한 엔드포인트입니다. 실행에 쓴 것과 같은 자격 증명으로 가져오세요. img 태그나 외부 서비스로 요청하면 401을 받습니다.
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
}그래도 스틸을 모델의 레퍼런스로 사용할 수 있습니다. 읽은 URL을 그대로 referenceImageUrls에 넣거나 노드의 stills 출력으로 전달하세요. 플랫폼은 내 이름으로 그 실행에 그 파일 하나를 잠시 읽을 권한을 줍니다. 이 권한은 몇 분 뒤에 만료되므로, 권한이 아니라 인증이 필요한 URL을 저장하세요.
Pro 실행이 스스로 보고하는 내용
장면을 제작한 실행은 무엇을 가정했고 무엇을 했는지 보고합니다. 모든 필드를 선택 사항으로 읽으세요. 고급 엔진이 없는 설치나 오래된 결과에는 이 필드가 하나도 없습니다.
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)SCENE_AUTHORING_ASSUMPTION경고는 프롬프트가 말하지 않아 실행이 결정한 내용을 나열합니다. 경고 코드는 추가될 수 있으므로, 모르는 코드는 오류가 아니라 참고 정보로 취급하세요.repairPasses는 제작 패스가 아니라 수정 횟수를 세므로,0은 장면이 처음에 바로 승인되었다는 뜻입니다.admissionRetries는 다른 것을 셉니다. 빌드 전에 플래너에게 레시피를 다시 요청한 횟수입니다.mechanicalPasses는 플래너 없이 컴파일러 자체의 해결책으로 엔진이 적용한 수정 횟수입니다. 이 수정에는 최대 2회의 별도 견적 허용량이 있고, 쓰지 않으면 해제되며, 한 번마다REMEDY_AUTO_APPLIED경고가 추가됩니다. 이 허용량이 생기기 전에 견적을 받은 실행은 이런 패스를 수정으로 청구했습니다. 받은 견적을 읽어 구분하세요.restoredAssertions는 플래너가 변경을 요청받지 않은 필수 검사를 바꾼 뒤 엔진이 되돌려 놓은 검사를 나열합니다. 각각ASSERTION_RESTORED경고도 추가합니다.- 렌더링만 하는 내보내기는 아무것도 제작하지 않았으므로 횟수도 요약도 없습니다. 특히
mechanicalPasses의 경우 필드가 없는 것은0이 아닙니다.
고급 엔진이 장면을 제작했다면 generateAndWait()의 결과에도 같은 필드가 담깁니다. 기본 엔진은 모델을 호출하지 않으므로 이 필드가 하나도 없습니다.
시각 검토자가 승인하지 않은 결과물
완료된 결과가 시각 검토자의 승인 없이 도착할 수 있습니다. 두 경우 모두 동영상은 실제로 만들어졌고 크레딧도 사용되었습니다. 어느 경우인지는 metadata.review.verdict가 알려 줍니다.
"refused": 수정 한도를 다 쓰고 모든 필수 검사를 통과했지만, 검토자가 여전히 이의를 제기했습니다. 장면은 거부 의견이 첨부된 채로 전달되었습니다."unavailable": 검토가 쓸 만한 판정을 내지 못했습니다.reason은 검토가 공급자에 닿지 못했으면"provider", 답을 쓸 수 없었으면"unusable"입니다.attempts는 몇 번 요청했는지 알려 줍니다. 아무도 이 장면을 판단하지 않았습니다.
import { scene3DReviewNote, scene3DReviewVerdictOf } from "@nodaro/shared"
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)
}필드를 직접 읽지 말고 @nodaro/shared의 두 도우미 함수를 사용하세요. 맞아 보이지만 틀린 해석이 세 가지 있기 때문입니다.
validation.status는 여전히"passed"입니다. 필수 검사는 실제로 통과했고, 그래서 장면이 전달되었습니다.objections는 비어 있을 수 있습니다. 구체적으로 아무것도 지적하지 않은 거부도 거부이므로,SCENE_REVIEW_REFUSED경고를 세면 이를 놓칩니다."unavailable"의 이의는 판정이 아닙니다. 하나가 실패하기 전에 답한 검토 배치에서 나온 것입니다. 이 경우 빈 목록은 승인이 아니라 응답이 없었다는 뜻입니다.
각 이의는 { category, what, correction?, frames }입니다. "unavailable" 판정에서는 SCENE_REVIEW_UNAVAILABLE 경고가 validation.warnings의 맨 앞에 옵니다. 시각적 거부만으로는 더 이상 작업이 실패하지 않습니다. SCENE_QUALITY_FAILED는 필수 검사가 실패했거나 컴파일러가 레시피를 거부했다는 뜻입니다. 그렇게 실패한 결과에 무엇이 남는지는 3D 렌더링 Pro를 참조하세요.
결과물과 파일
이 메서드들은 실행이 이미 전달한 파일을 읽습니다. 렌더링을 시작하지는 않습니다. 모든 읽기는 결과물과 그 소스 양쪽에 대한 내 접근 권한을 다시 확인하며, 소스 리비전이 삭제된 뒤에도 마찬가지입니다.
getDelivery(jobId)
결과물의 기록을 읽습니다(GET /v1/3d-scene/deliveries/:jobId). sourceKind, 정확한 소스 리비전, 고정된 파일이 담겨 있습니다.
getDelivery(jobId: string): Promise<Scene3DDelivery>속성
타입
const delivery = await client.scene3d.getDelivery(jobId)
const stills = delivery.assets.filter((a) => a.kind === "shot-still")파일은 네 종류입니다. 모든 결과물에 있는 validation-report, 거부된 결과물을 제외한 모든 결과물에 있는 poster, 샷마다 하나씩 있고 각각 shotIndex, frame, width, height가 딸린 shot-still, 그리고 refused-authoring 결과물에만 있는 source-json입니다. sourceKind는 retained-revision, job-output, refused-authoring 중 하나입니다. 거부된 결과물에서는 아무것도 컴파일되지 않았으므로 sceneRevisionId와 sourcePlanSha256이 null이고 포스터도 없습니다.
deliveryAssetBytes(jobId, asset, options?)
결과물이 나열하는 파일 하나를 새로운 인증과 크기 제한을 적용해 다운로드합니다.
deliveryAssetBytes(jobId: string, asset: Scene3DDeliveryAsset, options?: { signal?: AbortSignal }): Promise<ArrayBuffer>속성
타입
const bytes = await client.scene3d.deliveryAssetBytes(jobId, stills[0])retainedRecipe(jobId, options?)
거부된 3D 렌더링 Pro 실행이 보관한 레시피를 파싱해 읽으며, 레시피가 없으면 null을 반환합니다. 거부된 실행의 레시피는 컴파일되지 않았으므로 장면 리비전도, 포스터도, 소스 파일도 없습니다. 레시피가 그 실행이 남기는 것입니다.
retainedRecipe(jobId: string, options?: { signal?: AbortSignal }): Promise<unknown | null>속성
타입
const recipe = await client.scene3d.retainedRecipe(jobId)- 작업의 워크플로에 대한 편집 권한이 필요합니다. 그보다 낮은 권한의 사용자에게는 레시피가 전혀 보이지 않으므로, 응답은 오류가 아니라
null입니다. - 요청할지 여부는 실패한 작업이 알려 줍니다. 레시피가 보관되었으면 그 작업의
validation.sourceRetained가true입니다. - 레시피는 입력이 아니라 증거입니다. 거부된 실행은 리비전을 게시하지 않았으므로, 레시피로 다시 실행할 수 없습니다. 무엇을 시도했는지 확인하고 다음 프롬프트를 개선하는 데 읽으세요. 읽는 데 크레딧은 들지 않습니다.
assetBytes(revisionId, asset, options?)
보관된 장면 리비전의 파일을 다운로드합니다. GLB 모델, 카메라 트랙, 포스터 또는 검증 보고서입니다. 그 리비전의 디스크립터를 그대로 전달하세요. SDK는 다운로드를 선언된 크기로 제한하고, 장면 렌더러도 SHA-256 다이제스트를 확인합니다.
assetBytes(revisionId: string, asset: Scene3DAssetRef, options?: { signal?: AbortSignal }): Promise<ArrayBuffer>속성
타입
const glb = await client.scene3d.assetBytes(scenePlan.revisionId, glbAsset)sourceBytes(revisionId, options?)
.blend 파일 같은 리비전의 네이티브 소스 파일을 자체 권한 확인을 거쳐 다운로드합니다. 보관된 레시피와 같은 편집 권한이 필요합니다. 네이티브 파일은 승인된 바로 그 리비전을 정확히 나타낼 때만 사용할 수 있습니다.
sourceBytes(revisionId: string, options?: { signal?: AbortSignal }): Promise<ArrayBuffer>속성
타입
const blend = await client.scene3d.sourceBytes(revisionId)두 바이트 메서드 모두 새로운 자격 증명을 사용하고, 취소를 지원하며, 일반적인 타입 지정 오류를 발생시킵니다.
자주 묻는 질문
SDK로 프롬프트를 3D 애니메이션으로 만들려면 어떻게 하나요?
프롬프트와 함께 client.scene3d.generateAndWait를 실행하면 편집 가능한 장면 플랜을 받습니다. 원하면 editAndWait로 변경한 다음, renderAndWait로 MP4로 렌더링하세요.
3D 장면을 렌더링하면 비용이 얼마나 드나요?
가격은 플랜의 프레임 크기에 따라 정해집니다. 긴 변이 1,920픽셀 이하이면 55크레딧, 5.12메가픽셀 이하이면 83크레딧, 그보다 크면 138크레딧입니다. 현재 가격은 모델 비용 API에서 확인하세요.
3D 렌더링 Pro는 무엇인가요?
장면을 제작하고 렌더링까지 하는 하나의 작업이며, 결과에 장면 플랜과 MP4가 모두 담깁니다. 먼저 quotePro로 견적을 받으세요. 내 설치에서 제공되는지는 capabilities().pro로 확인하세요.
샷 스틸의 URL을 img 태그로 열 수 있나요?
아니요. 샷 스틸과 그 밖의 결과물 파일은 인증이 필요한 엔드포인트에서 제공됩니다. 작업을 실행할 때 쓴 것과 같은 자격 증명으로 가져온 다음, 그 바이트를 표시하거나 저장하세요.
관련 문서
3D 장면 생성
3D 렌더링 Pro
3D 장면 뷰포트 임베드
3D 장면
3D 장면
최종 업데이트