|
|
# 2D / 3D 交换协议(第一版)
|
|
|
|
|
|
本文档只描述 **QET 与 FreeCAD 之间的 JSON 交换格式**。
|
|
|
|
|
|
不负责描述:
|
|
|
|
|
|
- 数据库建表
|
|
|
- 数据库存储约束
|
|
|
- 旧表兼容策略
|
|
|
|
|
|
这些内容统一放在:
|
|
|
|
|
|
- [数据库设计.md](D:\LightWork3D\docs\数据库设计.md)
|
|
|
|
|
|
---
|
|
|
|
|
|
## 1. 协议目标
|
|
|
|
|
|
第一版协议只解决下面这条最小闭环:
|
|
|
|
|
|
1. QET 能把当前项目中的设备实例和端子实例导出给 FreeCAD
|
|
|
2. FreeCAD 能根据导出结果创建或更新 3D 设备实例
|
|
|
3. FreeCAD 能根据导出结果创建或更新 3D 端子对象
|
|
|
4. FreeCAD 保存后,能把最小回写结果再交还给 QET
|
|
|
|
|
|
当前版本明确采用:
|
|
|
|
|
|
- **文件交换**
|
|
|
- **JSON 格式**
|
|
|
|
|
|
当前版本明确不采用:
|
|
|
|
|
|
- 实时 RPC
|
|
|
- HTTP API
|
|
|
- 直接双向数据库同步
|
|
|
|
|
|
---
|
|
|
|
|
|
## 2. 交换文件位置建议
|
|
|
|
|
|
建议所有交换文件都放到项目目录下:
|
|
|
|
|
|
```text
|
|
|
<ProjectRoot>/.qet_freecad/
|
|
|
2d_to_3d.json
|
|
|
3d_to_2d.json
|
|
|
scene.FCStd
|
|
|
logs/
|
|
|
```
|
|
|
|
|
|
含义:
|
|
|
|
|
|
- `2d_to_3d.json`:QET 导出给 FreeCAD 的输入快照
|
|
|
- `3d_to_2d.json`:FreeCAD 回写给 QET 的结果快照
|
|
|
- `scene.FCStd`:该项目对应的 FreeCAD 3D 工程
|
|
|
|
|
|
---
|
|
|
|
|
|
## 3. 第一版 `2d_to_3d.json` 设计原则
|
|
|
|
|
|
### 3.1 最小主键集
|
|
|
|
|
|
第一版最小主键集只认:
|
|
|
|
|
|
- `project_uuid`
|
|
|
- `element_uuid`
|
|
|
- `terminal_uuid`
|
|
|
- `instance_id`
|
|
|
|
|
|
### 3.2 数据来源
|
|
|
|
|
|
`2d_to_3d.json` 不是数据库整表导出,而是:
|
|
|
|
|
|
> 从 QET 当前项目状态中整理出的、面向 FreeCAD 使用的最小交换快照。
|
|
|
|
|
|
也就是说:
|
|
|
|
|
|
- 它可以来自数据库
|
|
|
- 也可以来自当前内存状态
|
|
|
- 但最终输出是给 FreeCAD 消费的统一协议格式
|
|
|
|
|
|
### 3.3 协议可以比数据库稍微丰富
|
|
|
|
|
|
数据库设计要求尽量去冗余。
|
|
|
|
|
|
但 JSON 交换协议可以为了:
|
|
|
|
|
|
- 降低 FreeCAD 读取复杂度
|
|
|
- 提升可调试性
|
|
|
- 避免 FreeCAD 再回查 QET 内部数据库
|
|
|
|
|
|
而适当带上一些“已解析数据”。
|
|
|
|
|
|
这不等于数据库必须保留这些字段。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 4. 第一版 `2d_to_3d.json` 顶层结构
|
|
|
|
|
|
推荐结构:
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"schema_version": "1.2",
|
|
|
"project_uuid": "string",
|
|
|
"generated_at": "2026-05-18T10:30:00+08:00",
|
|
|
"source": {
|
|
|
"app": "QET",
|
|
|
"version": "string"
|
|
|
},
|
|
|
"cabinet": {},
|
|
|
"devices": [],
|
|
|
"terminals": [],
|
|
|
"device_models": [],
|
|
|
"wires": []
|
|
|
}
|
|
|
```
|
|
|
|
|
|
说明:
|
|
|
|
|
|
- `schema_version`:协议版本,便于后续兼容升级
|
|
|
- `project_uuid`:项目主键
|
|
|
- `generated_at`:导出时间
|
|
|
- `source`:导出来源信息
|
|
|
- `cabinet`:当前图纸属性中绑定的机柜信息
|
|
|
- `devices`:设备实例绑定
|
|
|
- `terminals`:端子实例绑定
|
|
|
- `device_models`:设备 3D 模型解析结果
|
|
|
- `wires`:导线起点/终点与标注快照
|
|
|
|
|
|
---
|
|
|
|
|
|
## 5. `cabinet` 结构
|
|
|
|
|
|
### 5.1 作用
|
|
|
|
|
|
当前版本仍然以 **图纸** 作为交换单位。
|
|
|
|
|
|
但图纸属性本身已经绑定了一个机柜,因此 `2d_to_3d.json` 顶层会额外带上:
|
|
|
|
|
|
> 当前图纸绑定的机柜信息
|
|
|
|
|
|
这样 FreeCAD 在保持“按图纸导入设备/端子”的同时,也能知道:
|
|
|
|
|
|
- 这批设备属于哪个机柜
|
|
|
- 这个机柜当前绑定了哪个 3D 相对路径
|
|
|
|
|
|
### 5.2 推荐字段
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"location_id": 12,
|
|
|
"label": "C",
|
|
|
"name": "电容柜",
|
|
|
"display_text": "C - 电容柜",
|
|
|
"associated_fileset": "电容柜文件集",
|
|
|
"three_d_relative_path": "3D/Cabinets/C.FCStd",
|
|
|
"resolved_scene_path": "C:/Users/Admin/Documents/MingTuProject/xxx/3D/Cabinets/C.FCStd"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 5.3 字段说明
|
|
|
|
|
|
| 字段 | 中文 | 必需 | 说明 |
|
|
|
| --- | --- | --- | --- |
|
|
|
| `location_id` | 机柜位置ID | 否 | 当前图纸属性绑定的机柜位置 ID |
|
|
|
| `label` | 机柜标注 | 否 | 用于 3D 侧快速识别机柜 |
|
|
|
| `name` | 机柜名称 | 否 | 机柜名称 |
|
|
|
| `display_text` | 机柜显示文本 | 否 | 等价于 QET 图纸属性界面中看到的机柜显示文本 |
|
|
|
| `associated_fileset` | 关联文件集 | 否 | 当前机柜位置绑定的文件集信息 |
|
|
|
| `three_d_relative_path` | 3D 相对路径 | 否 | 相对于工程目录保存的 3D 机柜路径 |
|
|
|
| `resolved_scene_path` | 已解析机柜场景路径 | 否 | QET 已解析出的本地场景文件路径,便于 FreeCAD 直接使用 |
|
|
|
|
|
|
### 5.4 说明
|
|
|
|
|
|
- `cabinet` 是一个 **图纸级上下文对象**
|
|
|
- 它不是设备绑定表或端子绑定表的扩张
|
|
|
- 它只描述“当前图纸绑定了哪个机柜,以及机柜当前对应哪个 3D 文件”
|
|
|
|
|
|
---
|
|
|
|
|
|
## 6. `devices` 结构
|
|
|
|
|
|
### 6.1 作用
|
|
|
|
|
|
`devices` 负责表达:
|
|
|
|
|
|
> 一个 2D 设备实例,对应哪个 3D 设备实例。
|
|
|
|
|
|
### 6.2 第一版字段
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"element_uuid": "string",
|
|
|
"instance_id": "string",
|
|
|
"display_tag": "string"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 6.3 字段说明
|
|
|
|
|
|
| 字段 | 中文 | 必需 | 说明 |
|
|
|
| --- | --- | --- | --- |
|
|
|
| `element_uuid` | 2D设备实例UUID | 是 | QET 图纸中的设备实例主键 |
|
|
|
| `instance_id` | 3D实例ID | 是 | FreeCAD 侧设备实例主键 |
|
|
|
| `display_tag` | 2D设备实例标注 | 否 | JSON 显示辅助字段,优先使用 2D 中设备标注作为 FreeCAD 树标签;为空时再退回 `instance_id` / `element_uuid` |
|
|
|
|
|
|
### 6.4 说明
|
|
|
|
|
|
- 如果第一次进入 3D 时还没有 `instance_id`,允许先导出空字符串或缺省值
|
|
|
- FreeCAD 创建 3D 实例后,再在回写阶段补齐
|
|
|
- `display_tag` 不进入第一版数据库最小字段集,它只存在于交换 JSON 中,用来让 3D 树视图与 2D 标注更容易对上
|
|
|
|
|
|
---
|
|
|
|
|
|
## 7. `terminals` 结构
|
|
|
|
|
|
### 7.1 作用
|
|
|
|
|
|
`terminals` 负责表达:
|
|
|
|
|
|
> 一个 2D 端子实例,属于哪个 3D 设备实例。
|
|
|
|
|
|
### 7.2 第一版字段
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"terminal_uuid": "string",
|
|
|
"instance_id": "string",
|
|
|
"element_uuid": "string"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 7.3 字段说明
|
|
|
|
|
|
| 字段 | 中文 | 必需 | 说明 |
|
|
|
| --- | --- | --- | --- |
|
|
|
| `terminal_uuid` | 2D端子UUID | 是 | QET 端子实例主键 |
|
|
|
| `instance_id` | 3D实例ID | 是 | 该端子所属的 3D 设备实例 |
|
|
|
| `element_uuid` | 2D设备实例UUID | 否 | JSON 导入辅助字段,帮助 FreeCAD 在首次没有 `instance_id` 时仍能知道端子属于哪个设备 |
|
|
|
|
|
|
### 7.4 为什么这里允许带 `element_uuid`
|
|
|
|
|
|
注意:
|
|
|
|
|
|
- `element_uuid` **不是**第一版端子绑定表的数据库字段扩张
|
|
|
- 它只是交换 JSON 中的上下文辅助字段
|
|
|
|
|
|
原因:
|
|
|
|
|
|
- 当某些设备第一次进入 3D、暂时还没有 `instance_id` 时
|
|
|
- FreeCAD 仍需要知道该端子属于哪个 2D 设备实例
|
|
|
|
|
|
所以这里允许 JSON 比数据库稍微丰富一些。
|
|
|
|
|
|
### 7.5 为什么第一版不带更多字段
|
|
|
|
|
|
第一版先不强制包含:
|
|
|
|
|
|
- `terminal_key`
|
|
|
- `connection_point_key`
|
|
|
- `symbol_terminal`
|
|
|
|
|
|
---
|
|
|
|
|
|
## 8. `wires` 结构
|
|
|
|
|
|
### 8.1 作用
|
|
|
|
|
|
`wires` 负责表达:
|
|
|
|
|
|
> 当前图纸中每一条逻辑导线,连接了哪两个端子,以及它当前的导线标注。
|
|
|
|
|
|
第一版先只解决:
|
|
|
|
|
|
- 导线是谁
|
|
|
- 起点端子是谁
|
|
|
- 终点端子是谁
|
|
|
- 当前导线标注是什么
|
|
|
|
|
|
不在这一版里解决:
|
|
|
|
|
|
- 3D 路径点
|
|
|
- 3D 线束位姿
|
|
|
- 导线自动布线路由结果
|
|
|
|
|
|
### 8.2 第一版字段
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"wire_id": "string",
|
|
|
"net_uuid": "string",
|
|
|
"group_uuid": "string",
|
|
|
"wire_mark": "string",
|
|
|
"wire_mark_is_manual": false,
|
|
|
"wire_style_id": 0,
|
|
|
"start_element_uuid": "string",
|
|
|
"start_terminal_uuid": "string",
|
|
|
"end_element_uuid": "string",
|
|
|
"end_terminal_uuid": "string",
|
|
|
"start_terminal_display": "string",
|
|
|
"end_terminal_display": "string"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 8.3 字段说明
|
|
|
|
|
|
| 字段 | 中文 | 必需 | 说明 |
|
|
|
| --- | --- | --- | --- |
|
|
|
| `wire_id` | 导线交换ID | 是 | JSON 交换层稳定标识;按 `DirectionInfo` 生成,格式为 `direction:${net_uuid}:${index}` |
|
|
|
| `net_uuid` | 网络UUID | 否 | 当前逻辑导线所属网络 |
|
|
|
| `group_uuid` | 网络分组UUID | 否 | 当前逻辑导线所属网络分组 |
|
|
|
| `wire_mark` | 导线标注 | 否 | 导线当前标注;为空时导出为 `无标注导线` |
|
|
|
| `wire_mark_is_manual` | 导线标注是否手工 | 否 | 是否手工修改过导线标注 |
|
|
|
| `wire_style_id` | 导线样式ID | 否 | 取 `start_terminal` 所连接导线的样式 ID |
|
|
|
| `start_element_uuid` | 起点设备UUID | 是 | 起点端子所属 2D 设备实例 |
|
|
|
| `start_terminal_uuid` | 起点端子UUID | 是 | 起点 2D 端子实例 |
|
|
|
| `end_element_uuid` | 终点设备UUID | 是 | 终点端子所属 2D 设备实例 |
|
|
|
| `end_terminal_uuid` | 终点端子UUID | 是 | 终点 2D 端子实例 |
|
|
|
| `start_terminal_display` | 起点端子显示号 | 否 | 起点端子在 QET 中的显示编号 |
|
|
|
| `end_terminal_display` | 终点端子显示号 | 否 | 终点端子在 QET 中的显示编号 |
|
|
|
|
|
|
### 8.4 说明
|
|
|
|
|
|
- `wire_mark` 来源于当前图纸导线方向信息里的 `wireMark`
|
|
|
- 它是**导线标注**
|
|
|
- 不是设备实例标注,也不是符号设备标注
|
|
|
|
|
|
- `wire_id` 代表一条 `DirectionInfo`
|
|
|
- 不再混入几何 `Conductor` UUID 作为导线主标识
|
|
|
|
|
|
- `wire_style_id` 只取 `start_terminal` 所连接导线的样式
|
|
|
- 不按整条几何路径聚合多个样式
|
|
|
|
|
|
- `wires` 是交换 JSON 的扩展层
|
|
|
- 不意味着第一版数据库绑定表要新增导线绑定表
|
|
|
|
|
|
- 第一版 FreeCAD 侧即使暂时不消费 `wires`
|
|
|
- 也建议先由 QET 输出出来,方便后续 3D 布线与调试
|
|
|
|
|
|
---
|
|
|
|
|
|
## 9. `device_models` 结构
|
|
|
|
|
|
### 9.1 作用
|
|
|
|
|
|
`device_models` 不是绑定表本身,而是:
|
|
|
|
|
|
> QET 在导出时,顺手把设备对应 3D 模型资源解析出来,减少 FreeCAD 再回查 QET 内部数据库的复杂度。
|
|
|
|
|
|
### 9.2 第二步设备导入推荐字段
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"element_uuid": "string",
|
|
|
"device_id": 123,
|
|
|
"parts_3d": "string",
|
|
|
"resolved_model_path": "string"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 8.3 字段说明
|
|
|
|
|
|
| 字段 | 中文 | 必需 | 说明 |
|
|
|
| --- | --- | --- | --- |
|
|
|
| `element_uuid` | 2D设备实例UUID | 是 | 与 `devices` 关联 |
|
|
|
| `device_id` | 设备类型ID | 否 | QET 设备主数据 ID |
|
|
|
| `parts_3d` | 3D模型资源URI | 否 | 原始资源引用,来自 `device_3d_asset.uri` 或 `device_attribute.parts_3d` |
|
|
|
| `resolved_model_path` | 已解析模型路径 | 是 | QET 已经解析好的本地模型文件路径,FreeCAD 第二步直接用它导入 STEP / FCStd |
|
|
|
|
|
|
### 8.4 为什么 `resolved_model_path` 是第二步关键字段
|
|
|
|
|
|
第二步开始,FreeCAD 不再只做 JSON 校验,而要真正导入设备模型。
|
|
|
|
|
|
如果没有 `resolved_model_path`,FreeCAD 就必须自己理解和回查:
|
|
|
|
|
|
1. `element_uuid`
|
|
|
2. `device_id`
|
|
|
3. `device_3d_asset`
|
|
|
4. `device_attribute.parts_3d`
|
|
|
5. 本地路径解析规则
|
|
|
|
|
|
这会让 FreeCAD 过度耦合 QET 内部数据库结构。
|
|
|
|
|
|
所以第二步推荐由 QET 在导出时直接给出:
|
|
|
|
|
|
- `resolved_model_path`
|
|
|
|
|
|
让 FreeCAD 只负责消费结果。
|
|
|
|
|
|
### 8.5 `.FCStd` 设备资产支持
|
|
|
|
|
|
A 方案下,`.FCStd` 是正式可复用设备资产格式。QET 导出时不需要解析 `.FCStd` 内部内容,只需要把设备资产路径解析成 `resolved_model_path` 交给 FreeCAD。
|
|
|
|
|
|
示例:
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"element_uuid": "elem-1001",
|
|
|
"device_id": 123,
|
|
|
"parts_3d": "models/mccb/MCCB_1P.FCStd",
|
|
|
"resolved_model_path": "C:/Users/Admin/Documents/MingTuProject/models/mccb/MCCB_1P.FCStd"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
FreeCAD 根据 `resolved_model_path` 的扩展名导入 `.FCStd`,并在导入后的设备组内优先扫描 `Role="Terminal"` 的 LCS 作为模板端子槽位。
|
|
|
|
|
|
如果 QET 侧已经在 `device_3d_asset.format` 中保存了资源格式,可以在后续协议中追加可选字段:
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"format": "fcstd"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
第一版 FreeCAD 不应强依赖该字段,避免旧版本 JSON 缺少 `format` 时无法导入。
|
|
|
|
|
|
### 8.6 为什么这里允许同时带 `parts_3d`
|
|
|
|
|
|
注意:
|
|
|
|
|
|
- **数据库设计**里我们已经决定不在绑定表冗余保存 `asset_uri`
|
|
|
- 但**交换协议**里允许带 `parts_3d`
|
|
|
|
|
|
原因是:
|
|
|
|
|
|
- JSON 是导出快照
|
|
|
- 不是绑定数据库本身
|
|
|
- 这样 FreeCAD 读取时更简单
|
|
|
|
|
|
也就是说:
|
|
|
|
|
|
> 数据库去冗余
|
|
|
> JSON 可适度带已解析结果
|
|
|
|
|
|
---
|
|
|
|
|
|
## 8. 第一版 `2d_to_3d.json` 完整样例
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"schema_version": "1.0",
|
|
|
"project_uuid": "proj-001",
|
|
|
"generated_at": "2026-05-18T10:30:00+08:00",
|
|
|
"source": {
|
|
|
"app": "QET",
|
|
|
"version": "1.0"
|
|
|
},
|
|
|
"devices": [
|
|
|
{
|
|
|
"element_uuid": "elem-1001",
|
|
|
"instance_id": "fc-inst-0001"
|
|
|
}
|
|
|
],
|
|
|
"terminals": [
|
|
|
{
|
|
|
"terminal_uuid": "term-2001",
|
|
|
"instance_id": "fc-inst-0001",
|
|
|
"element_uuid": "elem-1001"
|
|
|
},
|
|
|
{
|
|
|
"terminal_uuid": "term-2002",
|
|
|
"instance_id": "fc-inst-0001",
|
|
|
"element_uuid": "elem-1001"
|
|
|
}
|
|
|
],
|
|
|
"device_models": [
|
|
|
{
|
|
|
"element_uuid": "elem-1001",
|
|
|
"device_id": 123,
|
|
|
"parts_3d": "models/mccb/model.step",
|
|
|
"resolved_model_path": "C:/Users/Admin/Documents/MingTuProject/models/mccb/model.step"
|
|
|
}
|
|
|
]
|
|
|
}
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
## 9. 第一版 `3d_to_2d.json` 建议
|
|
|
|
|
|
第一版回写建议同样保持最小化。
|
|
|
|
|
|
推荐结构:
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"schema_version": "1.0",
|
|
|
"project_uuid": "string",
|
|
|
"generated_at": "2026-05-18T11:00:00+08:00",
|
|
|
"instances": [],
|
|
|
"terminals": []
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 9.1 `instances`
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"element_uuid": "string",
|
|
|
"instance_id": "string"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 9.2 `terminals`
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"terminal_uuid": "string",
|
|
|
"instance_id": "string"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 9.3 说明
|
|
|
|
|
|
第一版不回写:
|
|
|
|
|
|
- 3D 位姿
|
|
|
- 装配层级
|
|
|
- 几何路径
|
|
|
- 线槽信息
|
|
|
|
|
|
这些仍以 FreeCAD 文档为准。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 10. 第一版推荐交互流程
|
|
|
|
|
|
1. 用户在 QET 中点击 `3D视图`
|
|
|
2. QET 生成 `2d_to_3d.json`
|
|
|
3. QET 打开 FreeCAD,并打开 `scene.FCStd`
|
|
|
4. FreeCAD 读取 `2d_to_3d.json`
|
|
|
5. FreeCAD 创建或更新:
|
|
|
- 3D 设备实例
|
|
|
- 3D 端子对象
|
|
|
6. 用户在 FreeCAD 中完成装配和接线
|
|
|
7. 用户保存 FreeCAD 工程
|
|
|
8. FreeCAD 生成 `3d_to_2d.json`
|
|
|
9. QET 在后续时机读取 `3d_to_2d.json`
|
|
|
|
|
|
---
|
|
|
|
|
|
## 11. 当前推荐结论
|
|
|
|
|
|
第一版协议建议明确分层:
|
|
|
|
|
|
- **数据库设计**:尽量去冗余
|
|
|
- **JSON 协议**:允许带少量已解析结果,方便 FreeCAD 使用
|
|
|
|
|
|
一句话总结:
|
|
|
|
|
|
> 第一版先把 `2d_to_3d.json` 做成“面向 FreeCAD 的最小项目快照”,而不是数据库整表镜像。
|