1. 从"智慧仓储"这个场景说起:为什么我选择用 Blender MCP 打通数字孪生链路
做数字孪生项目的人大概都有过这种体验:模型师在 Blender 里精雕细琢出一套货架、堆垛机、AGV 小车,导出 glTF 丢给前端,结果前端一看——材质丢了、层级乱了、坐标轴对不上,改一次模型就要重新走一遍"导出-导入-调坐标"的循环。更麻烦的是,仓储场景里的设备是动态的,货架上的货物会变、AGV 会跑、堆垛机会升降,如果每次数据变化都要回到 Blender 里手动改模型再导出,这个项目基本没法维护。
我最近在做一个 3D 智慧仓储数字孪生的原型,核心思路是:用 Blender 作为模型资产的"源头工厂",通过 MCP(Model Context Protocol)把 Blender 的能力暴露给 AI 代理,让 AI 直接操控 Blender 完成建模、材质、导出等操作,前端用 Vue 3 + Three.js 做实时渲染和数据绑定。这套组合里,Antigravity 扮演的是"调度中枢"的角色——它负责理解需求、拆解任务、调用 Blender MCP 提供的工具接口,最终把一套可用的 3D 资产和场景配置交付给前端。
为什么不用传统的"人工建模 + 手动导出"流程?因为仓储数字孪生的模型不是一次性的。一个中型仓库可能有几十排货架、上百个货位、多台 AGV 和输送线,这些设备的型号、尺寸、布局会随着业务调整而变化。如果每次调整都靠人工在 Blender 里拖拽,成本高且容易出错。而通过 MCP 把 Blender 的 Python API 封装成可调用的工具,AI 就能根据结构化数据(比如货架排布表、设备清单)自动生成或修改模型,这才是数字孪生该有的"活"的状态。
这篇文章是"上篇",重点讲清楚三件事:Blender MCP 到底解决了什么问题、Antigravity 在其中怎么调度、以及前端 Three.js 侧需要提前约定哪些数据契约。下篇会讲具体的动态数据绑定和性能优化。如果你正在做数字孪生、3D 可视化或者想了解 MCP 在 3D 领域的落地方式,这篇应该能给你一些可直接参考的思路。
2. Blender MCP 的本质:把建模软件变成 AI 可调用的工具集
2.1 MCP 不是"插件",而是能力暴露层
很多人第一次听到"Blender MCP"会以为是一个 Blender 插件,装上去就能用。这个理解只对了一半。MCP 的全称是 Model Context Protocol,它定义了一套标准化的通信协议,让 AI 代理能够发现和调用外部工具。放到 Blender 场景里,它的结构是这样的:
- Blender 侧:运行一个 MCP Server,这个 Server 内部通过 Blender 的 Python API(bpy)操作场景、对象、材质、导出等。
- AI 代理侧:Antigravity 作为 MCP Client,连接到这个 Server,读取它暴露的工具列表(比如
create_box、set_material、export_gltf),然后根据任务需求调用。 - 通信层:通常是本地进程间通信,Antigravity 和 Blender 可以在同一台机器上,通过标准输入输出或本地端口交换 JSON-RPC 消息。
这个架构的关键价值在于:AI 不需要"看懂"Blender 的界面,它只需要知道有哪些工具、每个工具接受什么参数、返回什么结果。这就像给 Blender 装了一个"遥控器",AI 拿着遥控器按按钮,而不是自己去学怎么用鼠标拖拽。
2.2 仓储场景里,哪些操作值得封装成 MCP 工具
不是所有 Blender 操作都值得封装。我的经验是,高频、参数化、可批量的操作优先封装。以下是我在仓储数字孪生项目里实际封装的一批工具,按使用频率排序:
| 工具名称 | 功能 | 典型参数 | 调用频率 |
|---|---|---|---|
create_rack | 生成货架 | 排数、列数、层数、间距、货位尺寸 | 极高 |
create_agv | 生成 AGV 模型 | 型号、初始位置、朝向 | 高 |
set_material | 设置材质 | 对象名、材质类型、颜色、粗糙度 | 高 |
batch_place | 批量摆放对象 | 对象列表、坐标列表、旋转列表 | 高 |
export_gltf | 导出 glTF | 输出路径、是否压缩、是否包含动画 | 中 |
create_conveyor | 生成输送线 | 起点、终点、宽度、段数 | 中 |
add_light | 添加灯光 | 类型、位置、强度、颜色 | 低 |
set_camera | 设置相机 | 位置、目标点、焦距 | 低 |
这里有个坑要提前说:不要试图把 Blender 的所有功能都封装成工具。工具太多会导致 AI 在选择时犹豫,而且每个工具都需要维护参数校验和错误处理。我的做法是先用一个"最小可用集"跑通流程,再根据实际调用日志逐步补充。
2.3 为什么用 glTF 而不是 FBX 或 OBJ
导出格式的选择直接决定了前端 Three.js 的加载体验。我对比过三种常见格式:
- OBJ:结构简单,但不支持材质和动画,仓储场景里的设备颜色、透明度都没法保留,直接排除。
- FBX:功能全,但文件体积大,Three.js 的 FBXLoader 对某些版本的兼容性不稳定,而且 FBX 是二进制格式,调试时不好排查。
- glTF/GLB:Khronos 主导的标准,Three.js 原生支持最好,支持 PBR 材质、动画、节点层级,GLB 二进制格式体积可控,还能用 Draco 压缩。
实测下来,一个包含 50 排货架、10 台 AGV、2 条输送线的场景,导出 GLB 后大约 8-12MB,开启 Draco 压缩后能降到 3-5MB,前端首屏加载在局域网环境下基本无感。所以我的建议是:仓储数字孪生场景,glTF/GLB 是默认选择,除非有特殊需求,不要折腾其他格式。
3. Antigravity 在链路中的角色:不是"AI 建模",而是"任务调度"
3.1 把 Antigravity 当成一个"懂 Blender 的项目经理"
很多人对 Antigravity 的期待是"我说一句话,它就把整个仓库建好了"。这个期待不现实,也不必要。更准确的定位是:Antigravity 是一个能理解结构化需求、能拆解任务、能调用工具、能处理中间结果的调度器。
举个例子,我给它一个任务:"根据这份货架布局表,在 Blender 里生成对应的货架阵列,每排货架用不同颜色区分区域,然后导出 GLB。" Antigravity 的处理链路是这样的:
- 解析布局表,提取排数、列数、层数、区域划分。
- 调用
create_rack工具,传入第一排的参数。 - 检查返回结果,确认对象创建成功。
- 调用
set_material,给这一排货架设置区域颜色。 - 重复 2-4 步,直到所有排完成。
- 调用
export_gltf,指定输出路径和压缩选项。 - 返回导出结果和文件路径。
这个过程中,Antigravity 不需要"理解"货架长什么样,它只需要知道每个工具怎么用、参数怎么传、结果怎么判断。这就是 MCP 的价值:把复杂的 3D 操作拆解成 AI 能可靠执行的原子步骤。
3.2 任务拆解的粒度控制:太粗会失败,太细会低效
拆解粒度是实际使用中最需要调优的地方。我踩过的坑是:一开始把"生成整个仓库"当成一个任务丢给 Antigravity,结果它调用了十几次工具后开始"迷路",要么重复创建对象,要么参数传错。后来我把任务拆成"生成单排货架"这个粒度,成功率立刻上来了。
但拆得太细也有问题。如果每个货位都单独调用一次工具,一个 1000 货位的仓库就要调用 1000 次,通信开销和 Blender 的重绘开销都受不了。我的经验值是:单次工具调用处理的对象数量控制在 50-200 个之间。比如create_rack一次生成一排货架(假设 10 列 5 层,就是 50 个货位),batch_place一次摆放 100 个以内的对象。
3.3 错误处理:AI 调度最容易被忽视的一环
Blender 的 Python API 不是永远可靠的。对象名重复、材质不存在、导出路径无权限,这些都会导致工具调用失败。如果 Antigravity 没有错误处理逻辑,一个失败就会让整个任务中断。
我的做法是在 MCP Server 侧做两层防护:
- 参数校验层:在工具执行前检查参数合法性,比如对象名是否已存在、坐标是否在合理范围内。不合法直接返回结构化错误,不让 Blender 抛异常。
- 执行回滚层:对于批量操作,如果中途失败,记录已完成的操作,返回部分成功的结果,而不是全部回滚。这样 Antigravity 可以根据返回结果决定是重试还是跳过。
提示:MCP 工具的错误返回格式要统一,建议用
{ success: boolean, error: string, partialResult: any }这种结构,方便 AI 解析和决策。
4. 前端数据契约:Three.js 侧需要提前约定什么
4.1 模型命名规范:让前端能"按名索骥"
Blender 里创建的对象,导出 glTF 后会保留名称。如果名称是Cube.001、Cube.002这种默认名,前端根本没法用。所以在 MCP 工具里,我强制要求对象命名遵循一套规范:
- 货架:
rack_{区域}_{排号},例如rack_A_01 - 货位:
slot_{区域}_{排号}_{列号}_{层号},例如slot_A_01_03_02 - AGV:
agv_{编号},例如agv_01 - 输送线:
conveyor_{编号},例如conveyor_01
这套命名规范的好处是,前端 Three.js 加载模型后,可以直接用scene.getObjectByName('slot_A_01_03_02')拿到对应的货位对象,然后根据后端推送的数据改变它的颜色或显示状态。没有这套规范,数字孪生的"数据绑定"就无从谈起。
4.2 坐标系统一:Blender 的 Z-up 和 Three.js 的 Y-up
这是最经典的坑。Blender 默认是 Z 轴向上,Three.js 默认是 Y 轴向上。如果直接导出 glTF,Three.js 加载后模型会"躺倒"。解决方案有两个:
- 方案一:在 Blender 导出时勾选 "+Y Up" 选项,让 glTF 导出器自动转换坐标。
- 方案二:在 Three.js 侧对模型根节点做旋转,
model.rotation.x = -Math.PI / 2。
我推荐方案一,因为它在源头解决了问题,前端不需要做额外处理。但要注意:转换后,Blender 里的 Z 坐标变成了 Three.js 里的 Y 坐标,X 和 Z 的关系也变了。如果你在 MCP 工具里硬编码了坐标,导出后要重新核对一遍。
4.3 材质与光照:前端可调,但要有默认值
Blender 里设置的材质会随 glTF 导出,但 Three.js 的渲染环境和 Blender 的渲染引擎不同,直接导出往往"看起来不一样"。我的做法是:
- 材质:在 Blender 里只设置基础颜色和粗糙度/金属度,导出后在 Three.js 里用
MeshStandardMaterial重建,这样前端可以动态调整。 - 光照:Blender 里的灯光不导出,前端统一用
AmbientLight+DirectionalLight+HemisphereLight组合,保证场景亮度均匀。 - 环境贴图:如果需要反射效果,前端加载 HDR 环境贴图,而不是依赖 Blender 的材质反射。
这样做的代价是前端要多写一些材质初始化代码,但换来的是完全可控的渲染效果,而且模型文件更小(不包含灯光和复杂材质节点)。
5. 从零跑通一个最小闭环:货架阵列的生成与加载
5.1 环境准备:Blender、Python、MCP Server 的版本匹配
在开始之前,先把环境理清楚。我用的组合是:
- Blender 4.x(3.x 也可以,但 4.x 的 Python API 更稳定)
- Python 3.10+(Blender 内置的 Python 版本要确认,MCP Server 如果跑在外部,版本要匹配)
- Antigravity 客户端(确保能连接到本地 MCP Server)
- Node.js 18+(前端 Vue 3 + Three.js 的开发环境)
这里有个容易忽略的点:Blender 内置 Python 和系统 Python 是两套环境。如果你的 MCP Server 跑在 Blender 内部(作为插件),那它只能用 Blender 内置的 Python 和已安装的包。如果跑在外部,通过 socket 和 Blender 通信,那可以用系统 Python,但需要额外处理通信协议。我推荐前者,因为部署简单,不需要管理两个进程。
5.2 MCP Server 的最小实现:三个核心工具
先实现三个工具就能跑通闭环:create_rack、set_material、export_gltf。以下是create_rack的核心逻辑(伪代码,实际用 bpy 实现):
def create_rack(area, row_index, cols, levels, slot_width, slot_depth, slot_height): rack_name = f"rack_{area}_{row_index:02d}" # 创建货架立柱和横梁的简化逻辑 for col in range(cols): for level in range(levels): slot_name = f"slot_{area}_{row_index:02d}_{col:02d}_{level:02d}" # 计算货位中心坐标 x = col * slot_width y = 0 z = level * slot_height # 创建货位立方体 bpy.ops.mesh.primitive_cube_add(size=1, location=(x, y, z)) slot_obj = bpy.context.active_object slot_obj.name = slot_name slot_obj.scale = (slot_width/2, slot_depth/2, slot_height/2) return {"success": True, "rackName": rack_name, "slotCount": cols * levels}这个实现很粗糙,但足够验证流程。实际项目中,货架会用更精细的模型(立柱、横梁、货位板分开),但核心逻辑是一样的:根据参数计算坐标,批量创建对象,按规范命名。
5.3 导出与加载:从 Blender 到 Three.js 的完整链路
导出工具的实现要点:
def export_gltf(output_path, compress=True): bpy.ops.export_scene.gltf( filepath=output_path, export_format='GLB', export_yup=True, # 关键:转换坐标 export_apply=True, # 应用修改器 export_draco_mesh_compression_enable=compress ) return {"success": True, "path": output_path}前端加载:
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader' const loader = new GLTFLoader() const dracoLoader = new DRACOLoader() dracoLoader.setDecoderPath('/draco/') loader.setDRACOLoader(dracoLoader) loader.load('/models/warehouse.glb', (gltf) => { scene.add(gltf.scene) // 按名称查找货位 const slot = scene.getObjectByName('slot_A_01_03_02') if (slot) { slot.material = new THREE.MeshStandardMaterial({ color: 0xff0000 }) } })跑通这个闭环后,你就有了一个"AI 生成模型 → 导出 → 前端加载 → 按名操作"的完整链路。后续的动态数据绑定、AGV 动画、货位状态更新,都是在这个基础上扩展。
6. 实操中踩过的坑与应对经验
6.1 对象名冲突:Blender 的自动重命名机制
Blender 里如果创建同名对象,它会自动加后缀,比如slot_A_01_03_02变成slot_A_01_03_02.001。这会导致前端按名查找失败。我的应对方法是:在创建对象前先检查名称是否存在,存在就先删除或重命名。在 MCP 工具里加一段清理逻辑:
def ensure_unique_name(name): if name in bpy.data.objects: bpy.data.objects.remove(bpy.data.objects[name], do_unlink=True) return name6.2 批量操作的性能瓶颈:Blender 的重绘开销
在 Blender 里每创建一个对象,界面都会重绘一次。如果一次创建几百个对象,Blender 会卡住甚至无响应。解决方案是:在批量操作前禁用界面更新,操作完成后再启用。
bpy.context.preferences.view.show_splash = False # 或者用 bpy.ops.wm.redraw_timer 控制更彻底的做法是:MCP Server 跑在 Blender 的"后台模式"(blender --background),没有界面就没有重绘开销,速度能快好几倍。但后台模式下不能用bpy.ops里依赖界面的操作,需要用bpy.data直接操作数据。
6.3 导出路径的权限问题
Antigravity 调用export_gltf时,如果传入的路径不存在或没有写权限,Blender 会抛异常。我的做法是在工具里先检查路径,不存在就创建,无权限就返回明确的错误信息。另外,导出路径最好用绝对路径,相对路径在 Blender 的不同工作目录下行为不一致。
6.4 前端加载后的坐标偏移
即使勾选了export_yup,有时候模型加载后位置还是不对。这通常是因为 Blender 场景里有"父级空物体"或者模型原点不在几何中心。我的经验是:在导出前,把所有对象的变换应用掉(bpy.ops.object.transform_apply),确保每个对象的原点就是它的几何中心。这样前端加载后,对象的 position 就是它在场景里的实际位置,不需要额外计算偏移。
7. 这套方案适合谁,以及下篇会讲什么
如果你在做数字孪生、3D 可视化、或者任何需要"程序化生成 3D 场景"的项目,这套 Blender MCP + Antigravity + Three.js 的组合值得一试。它的核心优势不是"AI 自动建模"这种噱头,而是把 3D 资产的生成过程标准化、参数化、可调度化。一旦跑通,你就可以用结构化数据驱动整个场景的生成和更新,而不是靠人工在 Blender 里拖拽。
这套方案也有它的边界:它适合规则性强、参数化程度高的场景(仓储、机房、产线、园区),不适合需要大量手工雕刻和艺术创作的场景。另外,MCP Server 的开发和调试需要一定的 Blender Python 基础,如果你完全没接触过 bpy,建议先花半天时间熟悉一下基本 API。
下篇我会重点讲三件事:如何把后端实时数据绑定到 Three.js 场景里的货位和 AGV、如何用 InstancedMesh 优化大量重复对象的渲染性能、以及 Antigravity 在动态任务调度中的进阶用法。如果你在实操中遇到具体问题,欢迎在评论区交流,我尽量根据实际经验给出可落地的建议。