Blender MCP + Antigravity:打通智慧仓储数字孪生建模到Three.js的自动化链路
1. 为什么我要用 Blender MCP 打通数字孪生的“最后一公里”
做智慧仓储数字孪生这个方向差不多三年了,从最早用 Unity 搭场景,到后来转 Three.js 做 Web 端可视化,中间踩过的坑能写一本小册子。最让我头疼的从来不是前端渲染性能,也不是数据接入,而是模型资产从 DCC 工具到 Web 引擎之间的那条“断头路”。你在 Blender 里精雕细琢的货架、AGV、堆垛机,导出 glTF 之后要么材质丢失,要么层级混乱,要么面数爆炸导致浏览器直接卡死。更别提每次模型迭代都要手动导出、手动替换、手动调坐标,一套流程下来半天就没了。
这次我决定换一个思路:用 Antigravity 作为智能调度层,通过 Blender MCP 协议把建模工具和代码生成链路串起来,让“描述需求 → 自动建模 → 自动导出 → 自动接入 Three.js 场景”变成一条可复用的流水线。Antigravity 在这里扮演的角色不是简单的代码补全,而是一个能理解 3D 场景语义、能调用 Blender 操作接口、能生成前端集成代码的“数字孪生编排器”。Blender MCP 则是它伸向建模软件的那只手,让 AI 真正能“动手”操作 Blender 的 Python API,而不是只给你一段需要自己复制粘贴的代码。
这篇文章是上半部分,重点讲清楚三件事:第一,智慧仓储数字孪生的场景拆解和资产规划怎么做;第二,Blender MCP 的环境搭建和核心通信机制是怎么回事;第三,如何用 Antigravity 驱动 Blender 完成第一批仓储核心资产的程序化建模。下半部分我会接着讲 Three.js 端的场景组装、数据绑定和性能调优。如果你正在做数字孪生园区、机房可视化或者任何需要把 3D 模型搬进浏览器的项目,这套思路可以直接复用。
注意:本文涉及的 Antigravity 和 Blender MCP 均为开发工具链中的技术组件,使用前请确保你已具备合法的软件授权和开发环境。所有操作均在本地开发环境中完成,不涉及任何网络代理或非合规配置。
2. 智慧仓储数字孪生的场景拆解与资产清单规划
2.1 仓储场景的层级划分逻辑
很多人做数字孪生一上来就开始建模型,结果做到一半发现层级乱成一锅粥,改一个货架位置要动十几个文件。我在第二个项目里学乖了,先把场景按“园区 → 库区 → 巷道 → 设备 → 部件”五级拆解,每一级对应 Blender 里的一个 Collection,导出 glTF 时自然形成嵌套节点树,Three.js 端可以直接用getObjectByName精准定位。
具体到智慧仓储,我的层级划分是这样的:
| 层级 | 对应 Blender Collection | 典型资产 | 是否独立导出 |
|---|---|---|---|
| L1 园区 | Site | 地面、围墙、道路、绿化 | 整体导出 |
| L2 库区 | Warehouse_A/B | 建筑外壳、月台、雨棚 | 按库区导出 |
| L3 巷道 | Aisle_01~N | 货架列、导轨、照明 | 按巷道导出 |
| L4 设备 | Equipment | AGV、堆垛机、输送线、机械臂 | 独立导出 |
| L5 部件 | Parts | 货叉、滚筒、轮组、传感器 | 不单独导出 |
这样拆的好处是:L1 和 L2 是静态环境,可以合并烘焙;L3 是半静态结构,按巷道做视锥剔除;L4 是动态设备,需要独立控制动画和位置更新;L5 只在需要精细交互时才暴露。导出策略上,L1+L2 合并成一个environment.glb,L3 每个巷道一个aisle_XX.glb,L4 每种设备一个equipment_type.glb然后通过实例化复用。
2.2 仓储核心设备的建模优先级排序
资源永远有限,不可能一次性把所有设备都建完。我的优先级排序依据是**“视觉占比 × 交互频率”**:
- 第一优先级:货架系统。视觉占比最大,而且货位状态需要实时变色,必须最先做。标准横梁式货架用参数化建模,一组货架就是“立柱 + 横梁 + 层板 + 斜撑”的阵列组合。
- 第二优先级:AGV 与堆垛机。这是仓储的“演员”,运动轨迹和状态变化最吸引眼球。AGV 用简化模型即可,重点是轮组转动和顶升动画;堆垛机重点是立柱升降和货叉伸缩。
- 第三优先级:输送线与分拣机。视觉占比中等,但动画逻辑复杂(滚筒转动、包裹流动),可以先用静态模型占位,后期再加动画。
- 第四优先级:建筑外壳与地面。看起来简单,但面数控制不好最容易拖垮性能。地面用 Plane 加纹理,墙面用单面片,千万别用实体墙。
实操心得:我见过太多项目把建筑外壳建得无比精细,结果一个库区就 50 万面,浏览器直接跪。记住,数字孪生的建筑是“背景板”,不是“主角”,能省的面一定省。
2.3 资产命名规范与元数据约定
这一步是给后面的自动化流程铺路。Blender 里的对象命名必须严格遵循约定,否则 Antigravity 生成的代码没法精准引用。我的命名规则是:
{层级}_{类型}_{编号}_{状态标识}比如L4_AGV_001_idle、L3_Aisle_01_rack、L5_Fork_001_extend。状态标识用于动画控制,Three.js 端通过名称后缀就能判断当前该播放哪个动画片段。
元数据方面,我在 Blender 的自定义属性面板里给每个 L4 设备挂上关键信息:device_id、device_type、max_speed、load_capacity。这些属性导出 glTF 时会保留在extras字段里,Three.js 加载后可以直接读取,省去了额外维护一份设备清单的麻烦。
3. Blender MCP 的通信机制与环境搭建
3.1 MCP 协议在 Blender 侧的工作原理
MCP 全称 Model Context Protocol,本质是一套标准化的工具调用协议。Blender MCP 的实现思路是在 Blender 内部跑一个 Python 服务端,监听本地端口,接收外部发来的 JSON-RPC 格式指令,然后调用 Blender 的bpyAPI 执行操作,最后把结果序列化返回。
整个链路是这样的:
Antigravity (客户端) → JSON-RPC over stdio/socket → Blender MCP Server (Blender 插件) → bpy API → Blender 场景关键点在于Blender MCP Server 是一个常驻插件,它注册了一系列“工具函数”,比如create_box、create_cylinder、array_duplicate、set_material、export_gltf。Antigravity 不需要知道 Blender 的具体 API 怎么调,只需要按 MCP 协议描述“我要创建一个长宽高分别为 X/Y/Z 的立方体”,剩下的由 MCP Server 翻译成bpy.ops.mesh.primitive_cube_add并设置尺寸。
这种设计的好处是解耦:Blender 版本升级导致 API 变化时,只需要更新 MCP Server 的工具实现,Antigravity 侧的调用逻辑不用动。而且 MCP 协议本身支持工具发现,Antigravity 可以动态查询当前 Blender 实例支持哪些操作,非常灵活。
3.2 环境准备:Blender 插件安装与端口配置
我用的 Blender 版本是 4.2 LTS,MCP 插件从官方仓库拉取后手动安装。步骤不复杂,但有几个坑必须提前说:
- Blender 必须以管理员权限运行(Windows 下),否则插件无法绑定端口。我第一次装的时候没注意,一直报
Permission denied,排查了半小时才发现是权限问题。 - 端口选择避开常用端口。默认的 9876 有时候会被其他开发工具占用,我改成 19876 之后一直很稳。配置写在插件的
preferences.py里,改完重启 Blender 生效。 - Python 依赖要装全。MCP Server 依赖
websockets和pydantic,Blender 自带的 Python 环境不一定有,需要在 Blender 的 Python 控制台里手动pip install。注意要用 Blender 内置的 Python 路径,不是系统的。
安装完成后,在 Blender 的 3D 视图侧边栏会出现 MCP 面板,点击“Start Server”就能看到日志输出MCP Server listening on 127.0.0.1:19876。这时候用curl发一个测试请求验证连通性:
curl -X POST http://127.0.0.1:19876/rpc \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"list_tools","id":1}'返回工具列表就说明通了。如果返回连接拒绝,检查防火墙和端口占用。
3.3 Antigravity 侧的 MCP 客户端配置
Antigravity 作为客户端,需要在配置文件里声明 MCP Server 的连接信息。我用的配置格式是这样的:
{ "mcpServers": { "blender": { "command": "python", "args": ["-m", "blender_mcp_client"], "env": { "BLENDER_MCP_HOST": "127.0.0.1", "BLENDER_MCP_PORT": "19876" } } } }配置好之后,Antigravity 启动时会自动连接 Blender MCP Server,并在工具列表中显示所有可用操作。你可以在对话里直接说“在场景原点创建一个 2x2x2 的立方体”,Antigravity 会解析意图、调用create_box工具、传入参数、等待返回结果。
注意:Antigravity 的 MCP 连接是有状态的,如果 Blender 重启了,Antigravity 需要重新连接。我建议在开发阶段保持 Blender 常开,避免频繁重连。
4. 用 Antigravity 驱动 Blender 完成仓储资产程序化建模
4.1 货架系统的参数化生成实战
货架是仓储场景里重复度最高的资产,用程序化生成最划算。我的做法是先定义参数,再让 Antigravity 生成 Blender Python 脚本,通过 MCP 执行。
参数定义如下:
rack_params = { "bay_width": 2.7, # 单跨宽度(米) "bay_count": 5, # 跨数 "level_count": 4, # 层数 "level_height": 1.8, # 层高 "upright_size": 0.09, # 立柱截面尺寸 "beam_height": 0.12, # 横梁高度 "depth": 1.1 # 货架深度 }然后我在 Antigravity 里输入:“根据以上参数,在 Blender 中生成一组横梁式货架,立柱用方管,横梁用矩形截面,层板用薄板,整体放在 Collection L3_Aisle_01 下,命名前缀 L3_Rack_。”
Antigravity 会生成类似这样的调用序列:
- 循环
bay_count次,每次在 X 方向偏移bay_width,创建立柱对。 - 循环
level_count次,每次在 Z 方向偏移level_height,创建横梁。 - 在每层横梁上创建层板。
- 添加斜撑(可选,用于视觉真实感)。
- 将所有对象加入指定 Collection,按规则命名。
实测下来,一组 5 跨 4 层的货架,从参数输入到模型出现在 Blender 里,不到 8 秒。手动建模的话,熟练工也要 20 分钟以上。而且参数一改,重新生成只要几秒,迭代成本几乎为零。
实操心得:程序化生成货架时,立柱和横梁的截面用
bpy.ops.mesh.primitive_cube_add加缩放实现,不要用primitive_cylinder_add,因为方管比方柱更符合真实货架,而且面数更少。层板用primitive_plane_add加 Solidify 修改器,厚度设 0.02 米就够了。
4.2 AGV 小车的模块化组装与动画骨骼预留
AGV 比货架复杂,因为它需要动画。我的策略是模块化建模 + 骨骼预留:
- 车体:一个扁长方体,倒角处理。
- 轮组:4 个圆柱体,分别命名为
L5_Wheel_FL、L5_Wheel_FR、L5_Wheel_RL、L5_Wheel_RR。 - 顶升盘:一个圆柱体,命名为
L5_Lift_Plate,用于顶升动画。 - 传感器:车头两个小圆柱,命名为
L5_Sensor_L、L5_Sensor_R。
关键操作是给轮组和顶升盘设置独立的原点。轮组的原点必须在轮子中心,这样旋转动画才正确;顶升盘的原点在底面中心,这样上下移动才自然。Antigravity 生成脚本时,我会明确要求:“每个轮组的原点设置到其几何中心,顶升盘原点设置到其底面中心。”
动画方面,我不在 Blender 里做关键帧,而是只预留命名和层级,动画逻辑全部在 Three.js 端用代码控制。这样做的好处是模型文件小,而且动画参数可以跟实时数据绑定(比如 AGV 速度变化时轮子转速同步变化)。
4.3 堆垛机的复杂运动结构拆解
堆垛机是仓储里运动最复杂的设备,有水平行走、立柱升降、货叉伸缩三个自由度。建模时如果结构不对,后期动画会非常别扭。
我的拆解方案:
- 下横梁 + 行走轮:整体作为一个对象
L4_Stacker_Base,水平移动。 - 立柱:作为
L4_Stacker_Base的子对象,本身不动,但上面的升降台会动。 - 升降台:命名为
L4_Stacker_Lift,沿立柱上下移动。 - 货叉:命名为
L4_Stacker_Fork,作为升降台的子对象,前后伸缩。
层级关系必须是Base → Lift → Fork的父子链,这样移动 Base 时整个设备跟着走,移动 Lift 时只有升降台和货叉动,移动 Fork 时只有货叉动。Antigravity 在生成时,我会强调“建立正确的父子关系,确保变换继承链正确”。
注意:Blender 里设置父子关系时,要用
parent_set并保持变换,否则子对象会跳位置。MCP 工具里我封装了一个set_parent_keep_transform操作,专门处理这个。
4.4 批量导出 glTF 与 Three.js 加载验证
所有资产建完后,最后一步是导出。我不建议一次性全选导出,而是按 Collection 分别导出:
export_list = [ ("L1_L2_Environment", "environment.glb"), ("L3_Aisle_01", "aisle_01.glb"), ("L4_AGV", "equipment_agv.glb"), ("L4_Stacker", "equipment_stacker.glb"), ]导出参数我固定用这套:
bpy.ops.export_scene.gltf( filepath=output_path, export_format='GLB', export_apply=True, # 应用修改器 export_yup=True, # Y轴向上,适配Three.js export_materials='EXPORT', export_texture_dir='textures', export_extras=True, # 保留自定义属性 export_cameras=False, export_lights=False )导出完成后,我会立刻在 Three.js 里写一个最小加载器验证:
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; const loader = new GLTFLoader(); loader.load('/models/aisle_01.glb', (gltf) => { const aisle = gltf.scene; aisle.traverse((node) => { if (node.isMesh) { console.log('Mesh:', node.name, 'Extras:', node.userData); } }); scene.add(aisle); });控制台能打印出正确的名称和 extras 信息,就说明导出链路通了。如果材质丢失,检查 Blender 里是否用了节点材质且贴图路径正确;如果位置偏移,检查export_yup和场景单位设置。
5. 踩坑实录:MCP 调用中最容易翻车的五个地方
5.1 坐标系转换:Blender 的 Z-up 与 Three.js 的 Y-up
这是最经典的坑。Blender 默认 Z 轴向上,Three.js 默认 Y 轴向上。虽然 glTF 导出时勾选export_yup会自动转换,但如果你的模型里有旋转过的对象,转换后旋转角度可能会出问题。
我遇到过一次:堆垛机在 Blender 里绕 Z 轴旋转了 90 度,导出后在 Three.js 里变成了绕 Y 轴旋转,但角度不对,设备朝向偏了 45 度。排查后发现是旋转顺序的问题。解决方案是在 Blender 里尽量用rotation_euler的单一轴旋转,避免复合旋转,如果必须复合,导出前先apply_transform把旋转烘焙到几何体里。
5.2 材质丢失:节点材质与 glTF 的兼容性边界
Blender 的节点材质功能强大,但 glTF 只支持 PBR 那套。我踩过的坑包括:
- Mix Shader 节点:glTF 不支持,导出后材质变默认灰色。解决方法是只用 Principled BSDF。
- 程序化纹理:Noise、Voronoi 这些节点生成的纹理,glTF 不支持,必须烘焙成图片。
- 透明材质:Blend Mode 要设成
Alpha Blend,并且勾选Show Backface,否则导出后透明区域显示异常。
实操心得:导出前用 Blender 的 glTF 预览功能检查一遍,或者直接导出后用 https://gltf-viewer.donmccurdy.com/ 在线预览,比在 Three.js 里调试快得多。
5.3 面数爆炸:程序化生成时的细分控制
程序化生成货架时,如果不控制细分,一个立柱用primitive_cube_add加 Bevel 修改器,段数设高了,面数直接起飞。我的经验值是:
| 对象类型 | 最大面数 | 细分策略 |
|---|---|---|
| 立柱 | 200 | Bevel 段数 1 |
| 横梁 | 150 | 不细分 |
| 层板 | 50 | 不细分 |
| AGV 车体 | 500 | Bevel 段数 2 |
| 轮组 | 300 | 圆柱段数 16 |
一组货架控制在 5000 面以内,一个库区 20 组货架就是 10 万面,加上建筑和设备,总面数控制在 30 万以内,浏览器跑 60fps 没问题。
5.4 命名冲突:重复生成导致的.001后缀
Antigravity 批量生成时,如果同一个名称用了两次,Blender 会自动加.001后缀。这会导致 Three.js 端按名称查找时找不到对象。解决方案是在生成脚本里加一个名称唯一性检查,或者每次生成前先清空目标 Collection。
我封装了一个clear_collection工具,在每次批量生成前调用:
def clear_collection(collection_name): if collection_name in bpy.data.collections: for obj in bpy.data.collections[collection_name].objects: bpy.data.objects.remove(obj, do_unlink=True)5.5 MCP 超时:大批量操作的分批策略
一次性生成 100 组货架,MCP 调用会超时。我的做法是分批调用,每批不超过 20 个对象,每批完成后返回一个进度标识,Antigravity 根据进度决定是否继续下一批。这样既避免了超时,又能在中途出错时快速定位。
6. 从 Blender 到 Three.js 的资产交接检查清单
6.1 导出前的自检项
在点击导出之前,我习惯过一遍这个清单:
- [ ] 所有对象命名符合
{层级}_{类型}_{编号}_{状态}规范 - [ ] L4 设备的自定义属性已填写完整
- [ ] 材质全部使用 Principled BSDF,无 Mix Shader
- [ ] 贴图路径为相对路径,且已打包到 GLB
- [ ] 修改器已应用或确认导出时应用
- [ ] 场景单位设置为米,缩放为 1.0
- [ ] 无孤立顶点、无重复面、无零面积面
- [ ] 目标 Collection 已清理,无
.001后缀对象
6.2 Three.js 端的加载验证要点
加载后重点检查:
- 节点树结构:用
console.log(gltf.scene)打印层级,确认 Collection 嵌套关系正确。 - 材质完整性:遍历所有 Mesh,检查
material.map、material.color、material.metalness是否符合预期。 - 包围盒尺寸:用
new THREE.Box3().setFromObject(model)计算尺寸,跟 Blender 里的实际尺寸对比,误差超过 5% 就要查单位设置。 - 动画片段:如果导出了动画,检查
gltf.animations数组是否包含预期的片段。 - 性能基线:用
renderer.info.render.triangles查看三角面数,跟预算对比。
6.3 常见问题的快速定位表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 模型全黑 | 法线反了 | 检查 Blender 里是否flip normals |
| 材质灰色 | 用了不支持的节点 | 检查是否只有 Principled BSDF |
| 位置偏移 | 坐标系转换问题 | 检查export_yup和对象变换 |
| 名称带后缀 | 命名冲突 | 清理 Collection 后重新生成 |
| 加载卡顿 | 面数过高 | 查看renderer.info三角面数 |
| 贴图丢失 | 路径问题 | 检查 GLB 是否内嵌贴图 |
这套流程跑通之后,我现在的建模效率大概是传统方式的 5 到 8 倍。更重要的是,参数化生成意味着改需求不再痛苦——客户说货架要从 5 跨改成 8 跨,我改一个参数重新生成,30 秒搞定,以前至少要半天。
下半部分我会详细讲 Three.js 端的场景组装、AGV 实时位置绑定、货位状态变色、以及如何用 InstancedMesh 把 1000 个货位的渲染性能压到 2ms 以内。如果你也在做数字孪生可视化,这套 Blender MCP + Antigravity 的组合值得花时间搭起来,前期投入两三天,后面每个项目都能省回来。