新增 WMS 搬运规则与任务管理
支持搬运规则维护、候选预览、任务生成、预占、下发、取消和完成,并完善仓储管理前端交互。
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
# AGV 数字孪生 GLB 导出规范
|
||||
|
||||
> **文档版本**:1.1
|
||||
> **适用对象**:负责 AGV 三维建模与 GLB 导出的同事
|
||||
> **对接系统**:Web 查看器(Three.js)、PanguCanvas 桌面播放器
|
||||
> **交付范围**:仅需交付 GLB 模型文件;JSON 配置由程序同事在收到 GLB 后生成与维护
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
我们使用 **GLB 模型** 驱动 AGV 数字孪生(轮子旋转/转向、举升、灯光、动作切换等);配套的 JSON 配置由程序侧根据 GLB 自动生成,**无需建模同事参与**。
|
||||
|
||||
当前部分模型在导出时未统一层级与坐标,导致程序在运行时不得不:
|
||||
|
||||
- 猜测轮子旋转轴与轴心位置
|
||||
- 动态插入临时空节点
|
||||
- 做复杂的世界坐标 ↔ 本地坐标换算
|
||||
- 兼容混乱的节点命名(中文、`.001` 后缀、空格等)
|
||||
|
||||
**本规范的目标**:把几何与运动语义在 Blender 导出阶段写进 GLB,程序侧只做「查节点 + 改 transform / 材质」,降低联调成本、减少 bug。
|
||||
|
||||
---
|
||||
|
||||
## 2. 交付物清单
|
||||
|
||||
每款 AGV **只需交付一个文件**:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `{车型名}.glb` | 按本规范导出的模型 |
|
||||
|
||||
可选(联调时有助于排查问题):Blender 源文件(`.blend`)、各可动件行程与轴方向的简要说明。
|
||||
|
||||
导出前请自检(见第 10 节)。收到 GLB 后,程序同事会负责生成配置、绑定动作并完成验证。
|
||||
|
||||
---
|
||||
|
||||
## 3. 场景层级(必须遵守)
|
||||
|
||||
请在 Blender 中按以下结构组织,**导出前建好空物体(Empty)层级**
|
||||
|
||||
```
|
||||
agv_root ← 唯一根节点(程序侧 root 固定为 "agv_root")
|
||||
├── body/ ← 静态车体
|
||||
│ └── chassis
|
||||
├── wheels/
|
||||
│ ├── wheel_fl/ ← 左前舵轮(命名按实际车型调整)
|
||||
│ │ ├── steer ← 转向节点:绕本地 Y 轴旋转
|
||||
│ │ │ └── spin ← 滚转节点:绕本地 X 轴旋转
|
||||
│ │ │ └── mesh ← 轮子网格
|
||||
│ ├── wheel_fr/
|
||||
│ └── ...
|
||||
├── actuators/
|
||||
│ └── lift_fork/ ← 举升机构:沿本地 Y 轴平移
|
||||
│ └── mesh
|
||||
├── lights/ ← 可选分组,非必须
|
||||
│ ├── light_status_l/
|
||||
│ └── light_status_r/
|
||||
└── sensors/
|
||||
└── lidar_top/
|
||||
```
|
||||
|
||||
### 3.1 根节点
|
||||
|
||||
- **名称固定为 `agv_root`**(不要用 `lux_root`、`Scene`、`StealthAGV` 等)
|
||||
- 原点建议:车辆地面接触面几何中心,或后轴中心;**Y = 0 贴地**
|
||||
- 车头朝向建议统一为 **+Z**(见第 4 节)
|
||||
|
||||
### 3.2 轮子层级(最重要)
|
||||
|
||||
每个可动轮子必须包含三层空物体:
|
||||
|
||||
| 节点 | 作用 | 程序操作 |
|
||||
|------|------|----------|
|
||||
| `wheel_xxx` | 轮子总成,固定在父级 | 程序按节点名绑定 |
|
||||
| `steer` | 转向 | `rotation.y`(或约定的转向轴) |
|
||||
| `spin` | 滚转 | `rotation.x`(所有轮子统一) |
|
||||
| `mesh` | 视觉网格 | 不直接改 transform |
|
||||
|
||||
**所有轮子的滚转轴统一为 spin 节点的本地 X 轴,转向轴统一为 steer 节点的本地 Y 轴。**
|
||||
麦轮、舵轮等特殊情况也用独立命名的 wheel 节点表达,不要在不同轮子上使用不同旋转轴约定。
|
||||
|
||||
### 3.3 举升 / 执行机构
|
||||
|
||||
- 举升件放在 `actuators/` 下
|
||||
- **收回位置 = 本地 Y = 0**(相对 actuators 父节点或 lift 节点自身约定)
|
||||
- **完全伸出位置 = 本地 Y = 行程米数**(如 0.15 m = 15 cm)
|
||||
- 行程以 **米** 为单位,与 Blender 中 1 单位 = 1 米 保持一致(程序侧会据此配置)
|
||||
|
||||
### 3.4 灯光
|
||||
|
||||
- 每个需独立控制的指示灯单独 **命名节点**(如 `light_status_l`),程序按节点名绑定
|
||||
- 节点下可有 mesh,材质 **可与其他部件共用**;程序运行时会 clone,无需刻意拆材质
|
||||
- 材质使用 PBR,支持 **Emission** 即可(亮灭由程序改 emissive 控制)
|
||||
- 不必强制放在 `lights/` 分组下,但名称建议带 `light_` 前缀便于识别
|
||||
|
||||
### 3.5 静态件
|
||||
|
||||
- 不参与动作的零件合并进 `body/chassis`,减少零散节点
|
||||
- 仅保留需要单独隐藏、透明或换色的零件为独立节点
|
||||
|
||||
---
|
||||
|
||||
## 4. 坐标系与单位
|
||||
|
||||
| 项目 | 要求 |
|
||||
|------|------|
|
||||
| 坐标系 | **Y 轴向上**(glTF 标准),**右手系** |
|
||||
| 单位 | **1 Blender 单位 = 1 米** |
|
||||
| 朝向 | 车头 **+Z**,左右 **±X**,上下 **Y**(全项目统一) |
|
||||
| Transform | 导出前对每个可动节点 **Apply All Transforms**(Ctrl+A),scale 应为 `(1, 1, 1)` |
|
||||
| 全局旋转 | **不要在模型根节点上留 ±90° 的「纠正旋转」**;朝向在 Blender 里摆好再导出 |
|
||||
|
||||
> 说明:若导出时未 Apply Transform 或 scale ≠ 1,程序侧举升、轮子会出现位移异常或「部件飞出视野」。
|
||||
|
||||
---
|
||||
|
||||
## 5. 命名规范
|
||||
|
||||
### 5.1 必须
|
||||
|
||||
- 仅使用 **ASCII 英文字母、数字、下划线**
|
||||
- 使用 **snake_case**:`wheel_fl`、`lift_fork`、`light_status_l`
|
||||
- 按角色加前缀:
|
||||
|
||||
| 前缀 | 用途 | 示例 |
|
||||
|------|------|------|
|
||||
| `body_` / 放在 body/ 下 | 静态车体 | `body/chassis` |
|
||||
| `wheel_` | 轮子 | `wheel_fl`, `wheel_rl` |
|
||||
| `lift_` / 放在 actuators/ 下 | 举升 | `lift_fork` |
|
||||
| `light_` | 指示灯 | `light_status_l` |
|
||||
| `sensor_` | 传感器 | `sensor_lidar_top` |
|
||||
|
||||
### 5.2 禁止
|
||||
|
||||
- 中文节点名(如 `前座`、`雷达`、`柱体`)
|
||||
- Blender 自动后缀(如 `主体.001`、`平面.003`)
|
||||
- 空格、全角字符、不可见字符(含 NBSP)
|
||||
- 无语义的编号(如仅有 `light_1`、`light_2` 而不说明用途)
|
||||
- 多个不相关含义的节点重名
|
||||
|
||||
### 5.3 推荐命名示例(舵轮 AGV)
|
||||
|
||||
```
|
||||
agv_root
|
||||
body/chassis
|
||||
wheels/wheel_fl/steer/spin/mesh
|
||||
wheels/wheel_fr/steer/spin/mesh
|
||||
wheels/wheel_rl/spin/mesh ← 从动轮可无 steer 层,spin 下挂 mesh
|
||||
actuators/lift_fork/mesh
|
||||
lights/light_status_l
|
||||
lights/light_status_r
|
||||
sensors/lidar_top
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 材质要求
|
||||
|
||||
| 项目 | 要求 |
|
||||
|------|------|
|
||||
| 材质类型 | **PBR Principled BSDF** → 导出为 glTF Standard Material |
|
||||
| 车体 | 正常 albedo / roughness / metalness,贴图嵌入 GLB |
|
||||
| 指示灯 | PBR 材质,支持 Emission;可与车体共用材质 |
|
||||
| 材质实例 | 无特殊要求;程序侧会对需动画的部件 clone 材质 |
|
||||
| 法线 | 导出时包含 Normals;有贴图时包含 Tangents |
|
||||
|
||||
---
|
||||
|
||||
## 7. Blender 导出设置
|
||||
|
||||
**File → Export → glTF 2.0 (.glb/.gltf)**
|
||||
|
||||
| 选项 | 设置 |
|
||||
|------|------|
|
||||
| Format | **glTF Binary (.glb)** |
|
||||
| Include | Selected Objects **关闭**(导出整场景) |
|
||||
| Transform | **+Y Up** |
|
||||
| Geometry | 勾选 Apply Modifiers、UVs、Normals;有 normal map 时勾选 Tangents |
|
||||
| Material | Export 开启 |
|
||||
| Compression | 开发联调阶段 **关闭 Draco**(上线可按体积需求再开) |
|
||||
| 层级 | **保持层级**,不要勾选 Flatten / 合并为单 mesh |
|
||||
|
||||
导出后请确认文件大小合理,并用 glTF 查看器预览,确认模型显示正常。
|
||||
|
||||
---
|
||||
|
||||
## 8. 节点命名与程序侧映射(供参考,无需建模同事配置)
|
||||
|
||||
程序会根据 GLB 节点名自动绑定逻辑组件。**请按第 5 节规范命名**,便于程序识别;以下类型说明仅供理解命名意图,JSON 配置由程序同事完成。
|
||||
|
||||
### 8.1 组件类型(程序侧配置)
|
||||
|
||||
| type | 用途 | motion.role |
|
||||
|------|------|-------------|
|
||||
| `static` / `body` | 静态件 | 无 |
|
||||
| `wheel` | 轮子 | `wheel` |
|
||||
| `actuator` | 举升等 | `lift` |
|
||||
| `light` | 指示灯 | `light` |
|
||||
| `sensor` | 传感器(可选动画) | `lidar` / `camera` 等 |
|
||||
|
||||
### 8.2 命名与用途对照
|
||||
|
||||
| 节点示例 | 程序侧用途 | 建模侧要求 |
|
||||
|----------|------------|------------|
|
||||
| `wheel_fl` | 轮子旋转/转向 | 含 `steer` → `spin` → `mesh` 层级 |
|
||||
| `lift_fork` | 举升平移 | 收回 Y=0,伸出 Y=行程(米) |
|
||||
| `light_status_l` | 指示灯 emissive | 单独命名节点,材质支持 Emission |
|
||||
| `body/chassis` | 静态车体 | 不参与 transform 动画 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 常见问题对照(旧模型 vs 规范)
|
||||
|
||||
以现有「英招」模型为例,以下做法请避免:
|
||||
|
||||
| 现状问题 | 规范做法 |
|
||||
|----------|----------|
|
||||
| 根节点名 `lux_root` | 改为 `agv_root` |
|
||||
| 节点 `柱体.003`、`主体.001` | 语义命名如 `wheel_rl` |
|
||||
| 同车型轮子 axis 有的 y、有的 z | 统一预置 spin/steer,axis 统一 x/y |
|
||||
| 6 个 light 节点对应 3 盏灯 | 合并为 3 个 `light_*` 节点 |
|
||||
| 19 个零散 static 组件 | 合并 body,仅保留可动件独立节点 |
|
||||
| 举升行程不清晰或为负值 | 在 Blender 摆好 0 ~ 行程(米),并附简要说明 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 导出前自检清单
|
||||
|
||||
请在交付前逐项确认:
|
||||
|
||||
- [ ] 根节点名为 `agv_root`,Y=0 贴地,车头朝 +Z
|
||||
- [ ] 所有可动节点已 **Apply All Transforms**,scale = (1,1,1)
|
||||
- [ ] 每个舵轮含 `steer` → `spin` → `mesh` 层级
|
||||
- [ ] 所有轮子滚转轴为 spin 的 **本地 X**,转向轴为 steer 的 **本地 Y**
|
||||
- [ ] 举升行程在 Blender 中以米制摆好(如收回 0 m、伸出 0.15 m)
|
||||
- [ ] 节点名全部为 ASCII snake_case,无中文、无 `.001` 后缀
|
||||
- [ ] 需独立控制的指示灯有单独命名节点(`light_*`)
|
||||
- [ ] 静态件已合并到 body,无多余零散 mesh
|
||||
- [ ] glTF 导出:+Y Up、保留层级、Apply Modifiers
|
||||
- [ ] 用任意 glTF 查看器预览 GLB,确认无错位、无缩放异常
|
||||
|
||||
---
|
||||
|
||||
## 11. 联调流程
|
||||
|
||||
**建模同事:**
|
||||
|
||||
1. 按本规范完成 Blender 场景
|
||||
2. 导出 `{车型名}.glb` 并交付给程序同事
|
||||
3. (建议)一并提供 `.blend` 源文件及可动件行程说明
|
||||
|
||||
**程序同事(建模同事无需参与):**
|
||||
|
||||
1. 将 GLB 放入项目 `agv/` 目录
|
||||
2. 生成 JSON 配置、绑定动作并在查看器 / PanguCanvas 中验证
|
||||
|
||||
如有疑问,请提供:**Blender 源文件 + GLB + 各可动件行程与轴方向说明**,便于对照排查。
|
||||
|
||||
---
|
||||
|
||||
## 12. 附录:层级示意图
|
||||
|
||||
```
|
||||
agv_root (原点贴地, 车头 +Z)
|
||||
│
|
||||
┌─────────────────┼─────────────────┐
|
||||
│ │ │
|
||||
body/ wheels/ actuators/
|
||||
│ │ │
|
||||
chassis wheel_fl lift_fork
|
||||
│ │
|
||||
steer ── spin mesh
|
||||
│ │
|
||||
spin mesh
|
||||
│
|
||||
mesh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*文档维护:数字孪生开发组 · 如有规范变更请同步更新本文档版本号*
|
||||
@@ -0,0 +1,151 @@
|
||||
# AGV 播放 API
|
||||
|
||||
> 版本 1 · `http://127.0.0.1:8765`(先 `python serve.py`,并保持 `play.html` 打开)
|
||||
> 集成步骤与代码示例见 [接入教程.md](./接入教程.md)。
|
||||
|
||||
对外仅 **2 个 HTTP 接口**、**3 个动作**。
|
||||
|
||||
---
|
||||
|
||||
## 接口
|
||||
|
||||
| 方法 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| `GET` | `/api/catalog` | 查有哪些车、每辆车有哪些动作 |
|
||||
| `POST` | `/api/agv` | 控制播放(`playAction` / `stopAction` / `clearActions`) |
|
||||
|
||||
---
|
||||
|
||||
## 协议
|
||||
|
||||
**POST 请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"method": "playAction",
|
||||
"params": {
|
||||
"modelId": "英招",
|
||||
"actionId": "dongzuo1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `version` | 固定 `1` |
|
||||
| `method` | `playAction` · `stopAction` · `clearActions` |
|
||||
| `params.modelId` | **必填**,车辆 ID |
|
||||
| `params.actionId` | `playAction` / `stopAction` 必填 |
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"ok": true,
|
||||
"data": {
|
||||
"dispatched": true,
|
||||
"subscribers": 1,
|
||||
"modelId": "英招",
|
||||
"activeActions": ["dongzuo1"]
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
`subscribers: 0` 表示播放页未打开,命令已缓存,打开 `play.html` 后自动执行。
|
||||
|
||||
---
|
||||
|
||||
## 1. GET /api/catalog — 查目录
|
||||
|
||||
```
|
||||
GET http://127.0.0.1:8765/api/catalog
|
||||
```
|
||||
|
||||
响应 `data.vehicles` 示例:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"modelId": "英招",
|
||||
"name": "英招",
|
||||
"actions": [
|
||||
{ "id": "dongzuo1", "label": "动作1" }
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. POST /api/agv — 控制播放
|
||||
|
||||
须先打开:`http://127.0.0.1:8765/viewer/play.html`
|
||||
|
||||
### playAction — 开启动作(自动切车)
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"method": "playAction",
|
||||
"params": {
|
||||
"modelId": "英招",
|
||||
"actionId": "dongzuo1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### stopAction — 关闭指定动作
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"method": "stopAction",
|
||||
"params": {
|
||||
"modelId": "英招",
|
||||
"actionId": "dongzuo1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### clearActions — 关闭该车全部动作
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"method": "clearActions",
|
||||
"params": {
|
||||
"modelId": "英招"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 联调流程
|
||||
|
||||
```
|
||||
GET /api/catalog → 取 modelId、actionId
|
||||
↓
|
||||
POST playAction → 播放页执行
|
||||
↓
|
||||
POST stopAction / clearActions → 停止
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
| 现象 | 处理 |
|
||||
|------|------|
|
||||
| 页面打不开 | 只保留一个 `python serve.py` 进程 |
|
||||
| Postman 卡住 | 重启 `serve.py` |
|
||||
| `subscribers: 0` | 先打开 `play.html` |
|
||||
| `params.modelId 必填` | 三个动作都要带 `modelId` |
|
||||
| `UNKNOWN_METHOD` | POST 只能用上述 3 个 method;查目录用 GET |
|
||||
|
||||
---
|
||||
|
||||
*数字孪生开发组*
|
||||
@@ -0,0 +1,368 @@
|
||||
# AGV GLB 接入教程
|
||||
|
||||
> 面向 MES、数字孪生大屏、调度平台等外部系统的集成指南。
|
||||
> API 协议细节见 [AGV播放API.md](./AGV播放API.md)。
|
||||
|
||||
---
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
┌─────────────────┐ HTTP POST/GET ┌──────────────┐ SSE 推送 ┌─────────────────┐
|
||||
│ 你的业务系统 │ ──────────────────────▶│ serve.py │ ───────────────▶│ play.html │
|
||||
│ (任意语言) │ /api/catalog │ :8765 │ /api/agv/events│ (3D 播放页) │
|
||||
│ │ /api/agv │ │ │ │
|
||||
└─────────────────┘ └──────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| **业务系统** | 调用 HTTP 接口,无需了解 Three.js |
|
||||
| **serve.py** | 协议校验、命令缓存与转发 |
|
||||
| **play.html** | 3D 渲染;须保持打开才会执行动作 |
|
||||
|
||||
播放页未打开时,命令会**缓存**;打开 `play.html` 后自动同步执行。
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 启动服务
|
||||
|
||||
**Windows(推荐):** 双击项目根目录 `start.bat`
|
||||
|
||||
```powershell
|
||||
# 或命令行
|
||||
.\start.ps1
|
||||
```
|
||||
|
||||
**手动启动:**
|
||||
|
||||
```bash
|
||||
python serve.py
|
||||
# 默认端口 8765;可通过环境变量 PORT 修改
|
||||
```
|
||||
|
||||
首次部署且需使用 `analyze_agv.py` 扫描 GLB 时:
|
||||
|
||||
```powershell
|
||||
.\start.ps1 -Setup
|
||||
```
|
||||
|
||||
### 2. 打开播放页
|
||||
|
||||
服务启动后,在浏览器中保持以下页面打开:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8765/viewer/play.html
|
||||
```
|
||||
|
||||
配置页(编辑动作,非播放必需):
|
||||
|
||||
```
|
||||
http://127.0.0.1:8765/viewer/index.html
|
||||
```
|
||||
|
||||
### 3. 验证连通
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8765/api/catalog
|
||||
```
|
||||
|
||||
返回 `ok: true` 且 `data.vehicles` 非空即表示服务正常。
|
||||
|
||||
### 4. 发送第一条播放命令
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8765/api/agv \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"version\":1,\"method\":\"playAction\",\"params\":{\"modelId\":\"英招\",\"actionId\":\"zuozhuan\"}}"
|
||||
```
|
||||
|
||||
响应中 `data.subscribers >= 1` 表示播放页在线;为 `0` 时命令已缓存,打开播放页后会自动执行。
|
||||
|
||||
---
|
||||
|
||||
## 依赖说明
|
||||
|
||||
| 用途 | 要求 |
|
||||
|------|------|
|
||||
| 运行服务 + 播放 | Python 3.10+(标准库即可) |
|
||||
| 前端 Three.js | CDN 加载,**无需 npm install** |
|
||||
| GLB 扫描工具 | 可选,运行 `.\start.ps1 -Setup` 安装 |
|
||||
|
||||
---
|
||||
|
||||
## 接入方式
|
||||
|
||||
### 方式 A:HTTP API(推荐)
|
||||
|
||||
业务系统直接请求 `serve.py` 提供的 REST 接口,适合 Python、Java、C#、Node.js 等任意语言。
|
||||
|
||||
### 方式 B:iframe 嵌入(适合前端大屏)
|
||||
|
||||
将 `play.html` 嵌入 iframe,通过 `postMessage` 或 `AgvPlaybackApi` 控制,协议字段与 HTTP 一致。
|
||||
|
||||
---
|
||||
|
||||
## 标准流程
|
||||
|
||||
```
|
||||
① 启动 serve.py
|
||||
↓
|
||||
② 打开 play.html(可全屏或 iframe 嵌入大屏)
|
||||
↓
|
||||
③ GET /api/catalog → 获取 modelId、actionId
|
||||
↓
|
||||
④ POST /api/agv → playAction / stopAction / clearActions
|
||||
↓
|
||||
⑤ 检查 data.subscribers >= 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP API
|
||||
|
||||
**Base URL:** `http://127.0.0.1:8765`
|
||||
**协议版本:** `version: 1`
|
||||
**Content-Type:** `application/json`
|
||||
|
||||
### GET /api/catalog — 查询目录
|
||||
|
||||
接入前先调用,获取可用的 `modelId` 与 `actionId`。
|
||||
|
||||
```
|
||||
GET http://127.0.0.1:8765/api/catalog
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"ok": true,
|
||||
"data": {
|
||||
"vehicles": [
|
||||
{
|
||||
"modelId": "英招",
|
||||
"name": "英招",
|
||||
"actions": [
|
||||
{ "id": "zuozhuan", "label": "左转" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/agv — 控制播放
|
||||
|
||||
| method | 说明 | params |
|
||||
|--------|------|--------|
|
||||
| `playAction` | 播放动作,自动切换车辆 | `modelId` + `actionId` 必填 |
|
||||
| `stopAction` | 停止指定动作 | `modelId` + `actionId` 必填 |
|
||||
| `clearActions` | 停止该车全部动作 | 仅 `modelId` 必填 |
|
||||
|
||||
**playAction 示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"method": "playAction",
|
||||
"params": {
|
||||
"modelId": "英招",
|
||||
"actionId": "zuozhuan"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**成功响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"ok": true,
|
||||
"data": {
|
||||
"dispatched": true,
|
||||
"subscribers": 1,
|
||||
"modelId": "英招",
|
||||
"activeActions": ["zuozhuan"]
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
**失败响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"ok": false,
|
||||
"data": null,
|
||||
"error": {
|
||||
"code": "INVALID_REQUEST",
|
||||
"message": "params.modelId 必填"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 响应字段 | 含义 |
|
||||
|----------|------|
|
||||
| `subscribers` | 当前连接的播放页数量;`0` = 无播放页,命令已缓存 |
|
||||
| `activeActions` | 当前正在播放的动作 ID 列表 |
|
||||
| `dispatched` | 命令是否已成功写入总线 |
|
||||
|
||||
更多请求/响应示例见 [AGV播放API.md](./AGV播放API.md)。
|
||||
|
||||
---
|
||||
|
||||
## 代码示例
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
BASE = "http://127.0.0.1:8765"
|
||||
|
||||
def get_catalog():
|
||||
r = requests.get(f"{BASE}/api/catalog", timeout=5)
|
||||
r.raise_for_status()
|
||||
return r.json()["data"]["vehicles"]
|
||||
|
||||
def play_action(model_id: str, action_id: str):
|
||||
payload = {
|
||||
"version": 1,
|
||||
"method": "playAction",
|
||||
"params": {"modelId": model_id, "actionId": action_id},
|
||||
}
|
||||
r = requests.post(f"{BASE}/api/agv", json=payload, timeout=5)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
if not data["ok"]:
|
||||
raise RuntimeError(data["error"]["message"])
|
||||
if data["data"]["subscribers"] == 0:
|
||||
print("警告: 播放页未打开,命令已缓存")
|
||||
return data
|
||||
|
||||
vehicles = get_catalog()
|
||||
vehicle = vehicles[0]
|
||||
action_id = vehicle["actions"][0]["id"]
|
||||
play_action(vehicle["modelId"], action_id)
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
const BASE = "http://127.0.0.1:8765";
|
||||
|
||||
async function getCatalog() {
|
||||
const res = await fetch(`${BASE}/api/catalog`);
|
||||
const json = await res.json();
|
||||
return json.data.vehicles;
|
||||
}
|
||||
|
||||
async function playAction(modelId, actionId) {
|
||||
const res = await fetch(`${BASE}/api/agv`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
version: 1,
|
||||
method: "playAction",
|
||||
params: { modelId, actionId },
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!json.ok) throw new Error(json.error.message);
|
||||
return json;
|
||||
}
|
||||
```
|
||||
|
||||
### iframe 嵌入
|
||||
|
||||
```html
|
||||
<iframe
|
||||
id="agv-player"
|
||||
src="http://127.0.0.1:8765/viewer/play.html"
|
||||
style="width:100%;height:100%;border:none"
|
||||
></iframe>
|
||||
|
||||
<script>
|
||||
const iframe = document.getElementById("agv-player");
|
||||
|
||||
// postMessage(协议与 HTTP 一致)
|
||||
iframe.contentWindow.postMessage(
|
||||
{
|
||||
version: 1,
|
||||
id: "req-1",
|
||||
method: "playAction",
|
||||
params: { modelId: "英招", actionId: "zuozhuan" },
|
||||
},
|
||||
"*"
|
||||
);
|
||||
|
||||
window.addEventListener("message", (e) => {
|
||||
if (e.data?.id === "req-1") console.log("播放结果:", e.data);
|
||||
});
|
||||
|
||||
// 同源时可直接调用 AgvPlaybackApi
|
||||
iframe.onload = async () => {
|
||||
const api = iframe.contentWindow.AgvPlaybackApi;
|
||||
await api.request({
|
||||
version: 1,
|
||||
method: "playAction",
|
||||
params: { modelId: "英招", actionId: "zuozhuan" },
|
||||
});
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 业务映射建议
|
||||
|
||||
| 业务事件 | API 调用 |
|
||||
|----------|----------|
|
||||
| AGV 开始某动作 | `playAction` + 对应 `actionId` |
|
||||
| AGV 停止当前动作 | `stopAction` + 对应 `actionId` |
|
||||
| AGV 复位 / 全部停止 | `clearActions` |
|
||||
| 切换 AGV 型号 | `playAction` 带新的 `modelId`(自动切车) |
|
||||
|
||||
建议启动时拉一次 catalog,将业务编码(如 `"TURN_LEFT"`)映射到 `actionId`,避免硬编码。
|
||||
|
||||
---
|
||||
|
||||
## 生产部署
|
||||
|
||||
1. **播放页常开**:大屏或工控机全屏打开 `play.html`,或用 iframe 嵌入业务页面
|
||||
2. **单实例**:同一端口只运行一个 `serve.py`
|
||||
3. **自定义端口**:`PORT=9000 python serve.py` 或 `.\start.ps1 -Port 9000`
|
||||
4. **健康检查**:定期 `GET /api/catalog` 确认服务存活
|
||||
5. **新增车辆**:将 GLB + JSON 放入 `agv/`,更新 `agv/manifest.json`,重启服务后 catalog 自动更新
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
| 现象 | 处理 |
|
||||
|------|------|
|
||||
| `subscribers: 0` | 先打开 `play.html` |
|
||||
| 页面打不开 | 只保留一个 `serve.py` 进程 |
|
||||
| Postman 卡住 | 重启 `serve.py` |
|
||||
| `params.modelId 必填` | 三个 method 都要带 `modelId` |
|
||||
| `UNKNOWN_METHOD` | POST 只能用 3 个 method;查目录用 GET |
|
||||
| `动作不存在` | 先调 catalog 核对 `actionId` |
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [AGV播放API.md](./AGV播放API.md) | HTTP 协议与请求/响应格式 |
|
||||
| [AGV_GLB导出规范.md](./AGV_GLB导出规范.md) | GLB 模型导出与配置规范 |
|
||||
|
||||
---
|
||||
|
||||
*数字孪生开发组*
|
||||
Reference in New Issue
Block a user