TypeScript SDK
Aholo OpenAPI 官方 TypeScript / Node.js SDK。
- 运行要求: Node.js ≥ 18
- GitHub: manycoretech/aholo-spatial-sdk
安装
按需安装,只装你用到的包:
npm install @manycore/aholo-sdk-asset # 文件上传
npm install @manycore/aholo-sdk-world@^1.3.0 # 世界重建与生成(v1.3.0+ 支持 insv)
npm install @manycore/aholo-sdk-lux3d # Lux3D 3D 生成
鉴权
推荐: 通过环境变量设置,SDK 自动读取 AHOLO_API_KEY:
export AHOLO_API_KEY=your_api_key_here
也可在代码中显式传入:
import { createWorldClient } from '@manycore/aholo-sdk-world';
const world = createWorldClient({ apiKey: 'your_api_key_here', region: 'cn' });
请勿将 API Key 硬编码到源代码、安装包或公开仓库中。
区域
| 值 | 说明 | API 接入点 |
|---|---|---|
cn | 中国区 | https://api.aholo3d.cn |
com | 海外区 | https://api.aholo3d.com |
通用上传(Asset)
import { createAssetClient } from '@manycore/aholo-sdk-asset';
const asset = createAssetClient({ region: 'cn' });
上传文件
const result = await asset.uploadFile('./video.mp4');
console.log(result.url); // 公开访问 URL
上传 Buffer
import { readFileSync } from 'node:fs';
const data = readFileSync('./image.jpg');
const result = await asset.uploadBuffer(data, { filename: 'image.jpg' });
带进度回调
const result = await asset.uploadFile('./video.mp4', {
onProgress: (uploaded, total) => {
const pct = Math.round((uploaded / total) * 100);
process.stdout.write(`\r上传进度: ${pct}%`);
},
});
UploadOptions 参数
| 参数 | 类型 | 说明 |
|---|---|---|
filename | string | 覆盖文件名(默认取路径 basename) |
onProgress | (uploaded: number, total: number) => void | 进度回调(字节) |
partTimeoutMs | number | 每个分块上传超时(默认 120,000 ms) |
signal | AbortSignal | 取消信号 |
UploadResult 返回值
| 字段 | 类型 | 说明 |
|---|---|---|
url | string | 文件公开访问 URL |
md5 | string | 文件 MD5 |
世界(World)
import { createWorldClient, type WorldResourceItem, type WorldResourceType } from '@manycore/aholo-sdk-world';
const world = createWorldClient({ region: 'cn' });
重建 resources 使用 WorldResourceItem,type 为 WorldResourceType(image | video | insv)。生成 resources 使用 GenerateWorldResourceItem(仅 image,至多 1 条)。v1.3.0 起支持 Insta360 全景 type: 'insv'。
3DGS 重建(从视频 / 图像)
const { worldId } = await world.reconstructions.create({
name: '客厅',
resources: [{ url: 'https://cdn.example.com/room.mp4', type: 'video' }], // type: 'video' | 'image' | 'insv'
taskQuality: 'normal', // 'low' | 'normal' | 'high'
scene: 'model', // 'model' | 'space'
useMask: false, // optional: segment uploaded resources when true
});
const detail = await world.waitFor(worldId);
console.log(detail.assets?.splats?.urls?.plyPath); // PLY 下载 URL
使用图片重建时,resources 中图片类资源须 ≥ 20 条(type: 'image' 或省略,.jpg/.jpeg/.png/.webp)。普通视频用 type: 'video'(.mp4/.mov);Insta360 全景用 type: 'insv'(.insv)。URL 扩展名须与 type 一致。
Insta360 示例:
const { worldId } = await world.reconstructions.create({
name: '全景客厅',
resources: [{ url: 'https://cdn.example.com/room.insv', type: 'insv' }],
taskQuality: 'high',
scene: 'space',
});
3DGS 生成(从文字描述)
生成接口 resources 仅支持图片(type: 'image',至多 1 张,扩展名 .jpg/.jpeg/.png/.webp),不支持 video / insv。
const { worldId } = await world.generations.create({
name: '森林小屋',
prompt: '森林中的现代风格小木屋',
// resources: [{ url: 'https://cdn.example.com/ref.jpg', type: 'image' }], // 可选,至多 1 张
});
const detail = await world.waitFor(worldId);
查询世界详情
const detail = await world.retrieve(worldId);
console.log(detail.status);
任务状态与轮询
| 阶段 | 状态值 | 说明 |
|---|---|---|
| 进行中 | PENDING | 排队中 |
| 进行中 | PREPROCESSING | 预处理中 |
| 进行中 | RUNNING | 执行中 |
| 成功终态 | SUCCEEDED | 成功 |
| 失败终态 | FAILED | 失败 |
| 失败终态 | CANCELED | 已取消 |
| 失败终态 | TIMEOUT | 超时 |
| 失败终态 | REJECTED | 被拒绝 |
world.waitFor(worldId) 轮询直至 SUCCEEDED 并返回 WorldDetail;若进入 FAILED / CANCELED / TIMEOUT / REJECTED,抛出 PollingFailedError。
查询世界列表
const list = await world.list({ pageNum: 1, pageSize: 20 });
list.result?.forEach((w) => console.log(w.worldId, w.status));
WorldDetail 返回值
| 字段 | 类型 | 说明 |
|---|---|---|
worldId | string | 世界 ID |
name | string? | 名称 |
status | string | 见上方「任务状态与轮询」 |
assets.splats.urls.plyPath | string? | PLY 下载 URL |
assets.splats.urls.spzPath | string? | SPZ 下载 URL |
assets.splats.urls.lodMetaPath | string? | LOD 元数据 URL(若已生成) |
assets.imagery.panoUrl | string? | AI 生成全景图 URL(仅 Spatial Gen,全景子任务成功后) |
assets.semanticsMetadata.upAxis | "Y" | "Z"? | 世界上轴(Y = glTF/USD;Z = 3DGS 约定) |
createTime | number? | 创建时间(Unix 毫秒) |
updateTime | number? | 更新时间(Unix 毫秒) |
Lux3D
以下接口需要 @manycore/aholo-sdk-lux3d@1.7.0 或更高版本。
import { createLux3dClient } from '@manycore/aholo-sdk-lux3d';
const lux3d = createLux3dClient({ region: 'cn' });
多模态生图
img 与 prompt 至少一个。
const taskId = await lux3d.multimodalToImage.create({
prompt: '木椅产品图,白色背景',
img: 'https://example.com/object.jpg',
});
const result = await lux3d.tasks.waitFor(taskId);
// 从本地文件
const taskId2 = await lux3d.multimodalToImage.createFromFile('./object.jpg', {
prompt: '木椅产品图,白色背景',
});
生成四视图
img 与 prompt 至少一个;可只传文案,也可图文组合。
const taskId = await lux3d.imageToFourView.create({
img: 'https://example.com/object.jpg',
prompt: '产品四视图,白色背景',
});
const result = await lux3d.tasks.waitFor(taskId);
const taskId2 = await lux3d.imageToFourView.createFromFile('./object.jpg', {
prompt: '产品四视图,白色背景',
});
图像转 3D
const taskId = await lux3d.imgTo3d.create({
img: 'https://example.com/object.jpg',
version: 'G1', // 必填:G1 或 G1-Turbo
faceCount: 200_000,
outputFormat: ['zip', 'glb', 'ply'],
aiPredictSize: true,
});
// G1 多视角(本地文件)
const taskIdG1 = await lux3d.imgTo3d.createFromFiles(
['./view1.png', './view2.png'],
{ version: 'G1', outputFormat: ['glb'], enablePbr: true },
);
// 从本地文件
const taskId2 = await lux3d.imgTo3d.createFromFile('./object.jpg', { version: 'G1-Turbo' });
const result = await lux3d.tasks.waitFor(taskId);
console.log(result.outputs[0]?.content); // 默认 zip 下载 URL
文字转 3D
const taskId = await lux3d.textTo3d.create({
prompt: '带雕花腿的木椅',
version: 'G1',
// style: 'photorealistic', // 见下方风格列表
});
const result = await lux3d.tasks.waitFor(taskId);
文字转 3D 风格:
photorealistic(写实,默认)| cartoon(卡通)| anime(动漫)| hand_painted(手绘)| cyberpunk(赛博朋克)| fantasy(奇幻)| glass(玻璃)
多格式导出
const taskId = await lux3d.multiFormatExport.create({
modelUrl: 'https://example.com/model.glb',
outputFormat: ['usdz', 'obj_zip', 'stl'], // GLB 输入时不能为空;另支持 fbx_zip、3mf
});
const result = await lux3d.tasks.waitFor(taskId);
查询历史任务
const page = await lux3d.tasks.list({
page: 1,
pageSize: 20,
status: 3, // 可选筛选:0 初始化、1 进行中、3 成功、4 失败;结果中仍可能出现 6 已取消
// startTime / endTime:可选 Unix 毫秒时间戳
});
page.items.forEach((task) => console.log(task.taskId, task.status));
材质迁移
const taskId = await lux3d.materialTransfer.create({
img: 'https://example.com/material.jpg',
meshUrl: 'https://example.com/model.glb',
version: 'v3.0-standard', // 必填且固定
aiPredictSize: true,
});
const result = await lux3d.tasks.waitFor(taskId);
生成参数
- 图生/文生 3D 的
version必填:G1或G1-Turbo。 faceCount范围 10_000–300_000,默认 200_000;不影响 PLY。outputFormat支持zip/glb/ply;aiPredictSize默认true。- G1-Turbo 的 ZIP/GLB 输出可用
enablePbr控制材质;图生 3D 的img/imgs必须二选一。
Lux3dTaskResult 返回值
| 字段 | 类型 | 说明 |
|---|---|---|
taskId | number | 任务 ID |
status | 0 | 1 | 3 | 4 | 6 | 0 初始化;1 进行中;3 成功;4 失败;6 已取消 |
outputs | TaskOutput[] | 输出文件列表(outputs[n].content 为下载 URL,成功后约 2 小时内有效) |
lux3d.tasks.waitFor(taskId) 在 status === 3 时返回;status === 4 或 6 时抛出 PollingFailedError。建议每 10–15 秒轮询一次。
错误处理
import {
AuthenticationError,
RateLimitError,
BusinessError,
PollingTimeoutError,
PollingFailedError,
} from '@manycore/aholo-sdk-core';
try {
const detail = await world.waitFor(worldId);
} catch (e) {
if (e instanceof AuthenticationError) {
console.error('API Key 无效或未设置');
} else if (e instanceof RateLimitError) {
console.error('请求频率超限,稍后重试');
} else if (e instanceof BusinessError) {
console.error('业务错误:', e.code, e.message);
} else if (e instanceof PollingTimeoutError) {
console.error('任务轮询超时');
} else if (e instanceof PollingFailedError) {
console.error('任务执行失败:', e.message);
}
}
| 错误类型 | 说明 |
|---|---|
AuthenticationError | API Key 无效或未设置 |
RateLimitError | 请求频率超限 |
BusinessError | 业务错误(含 code 字段) |
PollingTimeoutError | 轮询等待超时 |
PollingFailedError | 任务执行失败 |
更多示例
完整示例见 GitHub examples 目录:
upload-file.mts— 上传本地文件world-reconstruct.mts— 3DGS 重建完整流程(支持.mp4/.mov/.insv)lux3d-img-to-3d.mts— 图像转 3Dlux3d-multi-format-export.mts— GLB 多格式导出
GitHub README 仅作安装说明。若与本文冲突,以本文为准。源码与可运行示例见 GitHub。