|
|
# 2D / 3D 协同数据库设计(第一版最小集)
|
|
|
|
|
|
本文档只保留当前阶段真正必需的字段。
|
|
|
|
|
|
目标不是一次把未来所有能力都放进表里,而是先支持下面这条最小闭环:
|
|
|
|
|
|
1. 2D 里有设备实例
|
|
|
2. 2D 里有端子实例
|
|
|
3. FreeCAD 里创建对应的 3D 设备实例
|
|
|
4. FreeCAD 里创建对应的 3D 端子对象
|
|
|
5. 2D 与 3D 可以靠稳定主键互相找到对方
|
|
|
|
|
|
当前设计严格遵守两条原则:
|
|
|
|
|
|
- 电气语义以 2D 为准
|
|
|
- 空间位姿以 3D 为准
|
|
|
|
|
|
另外再补一条当前版本明确采用的约定:
|
|
|
|
|
|
- 3D 设备资源不在绑定表中重复保存,统一通过 `element_uuid -> device_id -> parts_3d` 回查
|
|
|
|
|
|
因此,第一版不重复存这些信息:
|
|
|
|
|
|
- `display_tag`
|
|
|
- `terminal_key`
|
|
|
- `connection_point_key`
|
|
|
- `host_binding_mode`
|
|
|
- `host_object_id`
|
|
|
- `host_object_type`
|
|
|
- `extra_json`
|
|
|
- `scene_diagram_uuid`
|
|
|
- `source_diagram_uuid`
|
|
|
|
|
|
原因很简单:
|
|
|
|
|
|
- 这些字段当前要么能通过 2D 主键回查
|
|
|
- 要么属于未来扩展
|
|
|
- 要么当前语义还不够稳定
|
|
|
|
|
|
## 1. 统一主键约定
|
|
|
|
|
|
第一版只认下面这几个核心标识:
|
|
|
|
|
|
- `project_uuid`
|
|
|
- `element_uuid`
|
|
|
- `terminal_uuid`
|
|
|
- `instance_id`
|
|
|
|
|
|
含义:
|
|
|
|
|
|
- `project_uuid`:项目唯一标识
|
|
|
- `element_uuid`:2D 设备实例唯一标识
|
|
|
- `terminal_uuid`:2D 端子实例唯一标识
|
|
|
- `instance_id`:3D 设备实例唯一标识,由 FreeCAD 生成
|
|
|
|
|
|
## 2. 需要保留的最小表
|
|
|
|
|
|
第一版只建议保留 2 张表:
|
|
|
|
|
|
1. `project_2d3d_symbol_binding`
|
|
|
2. `project_2d3d_terminal_binding`
|
|
|
|
|
|
---
|
|
|
|
|
|
## 3. 设备绑定表
|
|
|
|
|
|
表名:
|
|
|
|
|
|
`project_2d3d_symbol_binding`
|
|
|
|
|
|
作用:
|
|
|
|
|
|
> 记录“一个 2D 设备实例,对应哪个 3D 设备实例,以及它使用哪个 3D 资源”
|
|
|
|
|
|
但“它使用哪个 3D 资源”这一点,第一版不在本表中直接存储,而是通过 2D 侧已有关系回查:
|
|
|
|
|
|
`element_uuid -> device_id -> parts_3d`
|
|
|
|
|
|
### 3.1 保留字段
|
|
|
|
|
|
| 字段名 | 中文 | 责任方 | 说明 |
|
|
|
| --- | --- | --- | --- |
|
|
|
| `project_uuid` | 项目UUID | 2D | 项目范围主键 |
|
|
|
| `element_uuid` | 设备实例UUID | 2D | 2D 设备实例唯一标识 |
|
|
|
| `instance_id` | 3D实例ID | 3D | FreeCAD 生成的 3D 设备实例唯一标识 |
|
|
|
|
|
|
### 3.2 推荐约束
|
|
|
|
|
|
- 主唯一键:`(project_uuid, element_uuid)`
|
|
|
- 3D 实例唯一键:`(project_uuid, instance_id)`
|
|
|
|
|
|
### 3.3 推荐 SQLite 结构
|
|
|
|
|
|
```sql
|
|
|
CREATE TABLE IF NOT EXISTS project_2d3d_symbol_binding (
|
|
|
project_uuid TEXT NOT NULL,
|
|
|
element_uuid TEXT NOT NULL,
|
|
|
instance_id TEXT NOT NULL,
|
|
|
PRIMARY KEY (project_uuid, element_uuid)
|
|
|
);
|
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_symbol_binding_instance
|
|
|
ON project_2d3d_symbol_binding(project_uuid, instance_id);
|
|
|
```
|
|
|
|
|
|
### 3.4 为什么只留这 3 个字段
|
|
|
|
|
|
因为第一版要解决的问题只有:
|
|
|
|
|
|
- 2D 设备是谁:`element_uuid`
|
|
|
- 3D 设备是谁:`instance_id`
|
|
|
- 属于哪个项目:`project_uuid`
|
|
|
|
|
|
而 3D 模型资源本身可以通过:
|
|
|
|
|
|
- `element_uuid`
|
|
|
- 回查到对应设备实例
|
|
|
- 再回查 `device_id`
|
|
|
- 最后拿到 `parts_3d`
|
|
|
|
|
|
所以第一版不重复存 `asset_uri`。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 4. 端子绑定表
|
|
|
|
|
|
表名:
|
|
|
|
|
|
`project_2d3d_terminal_binding`
|
|
|
|
|
|
作用:
|
|
|
|
|
|
> 记录“一个 2D 端子实例,对应哪个 3D 设备实例上的端子对象”
|
|
|
|
|
|
### 4.1 保留字段
|
|
|
|
|
|
| 字段名 | 中文 | 责任方 | 说明 |
|
|
|
| --- | --- | --- | --- |
|
|
|
| `project_uuid` | 项目UUID | 2D | 项目范围主键 |
|
|
|
| `terminal_uuid` | 端子UUID | 2D | 2D 端子实例唯一标识 |
|
|
|
| `instance_id` | 3D实例ID | 3D | 该端子属于哪个 3D 设备实例 |
|
|
|
|
|
|
### 4.2 推荐约束
|
|
|
|
|
|
- 主唯一键:`(project_uuid, terminal_uuid)`
|
|
|
- 查询索引:`(project_uuid, instance_id)`
|
|
|
|
|
|
### 4.3 推荐 SQLite 结构
|
|
|
|
|
|
```sql
|
|
|
CREATE TABLE IF NOT EXISTS project_2d3d_terminal_binding (
|
|
|
project_uuid TEXT NOT NULL,
|
|
|
terminal_uuid TEXT NOT NULL,
|
|
|
instance_id TEXT NOT NULL,
|
|
|
PRIMARY KEY (project_uuid, terminal_uuid)
|
|
|
);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_terminal_binding_instance
|
|
|
ON project_2d3d_terminal_binding(project_uuid, instance_id);
|
|
|
```
|
|
|
|
|
|
### 4.4 为什么不保留 `terminal_key` 和 `connection_point_key`
|
|
|
|
|
|
当前第一版设计里:
|
|
|
|
|
|
- 3D 端子本身就是 2D 端子的空间映射
|
|
|
- 所有端子语义都可以通过 `terminal_uuid` 从 2D 回查
|
|
|
|
|
|
所以目前不需要额外再保存:
|
|
|
|
|
|
- `terminal_key`
|
|
|
- `symbol_terminal`
|
|
|
- `connection_point_key`
|
|
|
- `wire_label`
|
|
|
- `net_id`
|
|
|
|
|
|
这些信息如果后续 3D 侧需要显示或校验,可以通过 `terminal_uuid` 回查 2D。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 5. 现阶段明确删掉的字段
|
|
|
|
|
|
下面这些字段不是说永远没用,而是 **第一版先删掉,不进入最小表结构**。
|
|
|
|
|
|
### 5.1 从设备绑定表中删掉
|
|
|
|
|
|
- `source_diagram_uuid`
|
|
|
- `scene_diagram_uuid`
|
|
|
- `device_id`
|
|
|
- `display_tag`
|
|
|
- `asset_uri`
|
|
|
- `host_binding_mode`
|
|
|
- `host_object_id`
|
|
|
- `host_object_type`
|
|
|
- `extra_json`
|
|
|
|
|
|
### 5.2 从端子绑定表中删掉
|
|
|
|
|
|
- `element_uuid`
|
|
|
- `binding_key`
|
|
|
- `symbol_terminal`
|
|
|
- `terminal_key`
|
|
|
- `connection_point_key`
|
|
|
- `wire_label`
|
|
|
- `net_id`
|
|
|
- `conductor_uuid`
|
|
|
- `extra_json`
|
|
|
|
|
|
### 5.3 从 3D 实例表中删掉
|
|
|
|
|
|
- `project_3d_scene_instance` 整张表
|
|
|
- 所有位姿字段:`tx / ty / tz / rx / ry / rz`
|
|
|
- 所有缩放字段:`sx / sy / sz`
|
|
|
- 所有 3D 场景附加字段:`diagram_uuid / asset_id / extra_json`
|
|
|
|
|
|
---
|
|
|
|
|
|
## 6. 第一版数据流
|
|
|
|
|
|
### 6.1 2D -> 3D
|
|
|
|
|
|
2D 提供:
|
|
|
|
|
|
- `project_uuid`
|
|
|
- `element_uuid`
|
|
|
- `terminal_uuid`
|
|
|
|
|
|
3D 在导入和实例化时生成:
|
|
|
|
|
|
- `instance_id`
|
|
|
|
|
|
然后写入:
|
|
|
|
|
|
- `project_2d3d_symbol_binding`
|
|
|
- `project_2d3d_terminal_binding`
|
|
|
|
|
|
### 6.2 3D -> 2D
|
|
|
|
|
|
3D 第一版只回写:
|
|
|
|
|
|
- `instance_id`
|
|
|
|
|
|
当前不回写位姿。
|
|
|
|
|
|
原因是:
|
|
|
|
|
|
- 3D 场景真相源已经切换为 FreeCAD
|
|
|
- 2D 不再承担 3D 场景保存职责
|
|
|
- 避免 FreeCAD 文档和数据库各存一份位姿造成冲突
|
|
|
|
|
|
---
|
|
|
|
|
|
## 7. 这版设计的核心思想
|
|
|
|
|
|
第一版故意把设计收得很紧,只保留:
|
|
|
|
|
|
- 一个 2D 设备实例如何找到 3D 设备实例
|
|
|
- 一个 2D 端子实例如何找到 3D 端子对象
|
|
|
|
|
|
也就是说,当前数据库设计只服务于:
|
|
|
|
|
|
> “2D 语义 + 3D 空间映射”这个最小闭环
|
|
|
|
|
|
而且这里的“空间映射”只表示:
|
|
|
|
|
|
- 2D 对象知道自己对应哪个 3D 实例
|
|
|
|
|
|
不表示数据库要保存完整 3D 位姿。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 8. 后续扩展时再加的字段
|
|
|
|
|
|
只有当下面这些需求真的出现时,再补字段:
|
|
|
|
|
|
- 多 3D 场景:补 `scene_diagram_uuid`
|
|
|
- 设备挂载规则:补 `host_object_id / host_object_type`
|
|
|
- 3D 资源缓存:补 `asset_uri`
|
|
|
- 多连接点/复杂端子:补 `connection_point_key`
|
|
|
- 3D 端子独立语义缓存:补 `terminal_key / symbol_terminal`
|
|
|
- 导线级 3D 校验:补 `wire_label / net_id / conductor_uuid`
|
|
|
- 2D 侧需要读取 3D 位姿:再恢复 3D 场景实例表或等价位姿存储
|
|
|
- 临时扩展:最后才考虑 `extra_json`
|
|
|
|
|
|
---
|
|
|
|
|
|
## 9. FreeCAD 与数据库的职责边界
|
|
|
|
|
|
继续收缩后的职责建议如下:
|
|
|
|
|
|
### 9.1 数据库负责
|
|
|
|
|
|
- 2D 设备实例和 3D 设备实例的绑定
|
|
|
- 2D 端子实例和 3D 端子对象的绑定
|
|
|
|
|
|
### 9.2 FreeCAD 负责
|
|
|
|
|
|
- 3D 设备实例实际创建
|
|
|
- 设备位姿
|
|
|
- 导轨/安装板/柜体装配关系
|
|
|
- 端子在空间中的实际几何位置
|
|
|
- 3D 接线几何
|
|
|
|
|
|
也就是说,第一版开始就建议明确:
|
|
|
|
|
|
> 3D 位姿和装配关系只保存在 FreeCAD 文档里,不在数据库中重复保存。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 10. 当前推荐结论
|
|
|
|
|
|
第一版数据库设计建议就收成这三张表:
|
|
|
|
|
|
1. `project_2d3d_symbol_binding`
|
|
|
2. `project_2d3d_terminal_binding`
|
|
|
|
|
|
并且只保留最小核心字段:
|
|
|
|
|
|
- 设备绑定只保留:`project_uuid / element_uuid / instance_id`
|
|
|
- 端子绑定只保留:`project_uuid / terminal_uuid / instance_id`
|
|
|
|
|
|
一句话总结:
|
|
|
|
|
|
> 第一版先让 2D 能找到 3D;至于 3D 怎么摆、摆在哪、怎么装配,全部交给 FreeCAD 自己管理。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 11. 第一版交换形式
|
|
|
|
|
|
第一版 2D / 3D 协同,交换形式先统一为:
|
|
|
|
|
|
- **JSON 文件交换**
|
|
|
|
|
|
当前不建议第一版直接做:
|
|
|
|
|
|
- 实时数据库双向同步
|
|
|
- 进程间 RPC
|
|
|
- HTTP API
|
|
|
- FreeCAD 保存后直接写回 QET 运行库
|
|
|
|
|
|
原因:
|
|
|
|
|
|
- JSON 最容易调试
|
|
|
- 字段改动时最容易观察
|
|
|
- 不会把 QET 和 FreeCAD 的运行时强耦合在一起
|
|
|
- 更适合当前仍在收缩字段和表结构的阶段
|
|
|
|
|
|
---
|
|
|
|
|
|
## 12. 第一版工具入口
|
|
|
|
|
|
QET 侧建议保留并改造一个工具项:
|
|
|
|
|
|
- `3D视图`
|
|
|
|
|
|
这个工具项的职责不再是走 QET 旧的 3D 模块逻辑,而是:
|
|
|
|
|
|
1. 从 QET 当前项目和当前 2D 图纸中整理 2D -> 3D 最小绑定数据
|
|
|
2. 导出 `2d_to_3d.json`
|
|
|
3. 启动 FreeCAD,或打开已有的 FreeCAD 工程文件
|
|
|
|
|
|
也就是说:
|
|
|
|
|
|
> 第一版 `3D视图` 本质上是“导出 2D 快照并切换到 FreeCAD”的入口。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 13. 第一版交换目录建议
|
|
|
|
|
|
建议在项目目录下固定一套交换目录:
|
|
|
|
|
|
```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 工程
|
|
|
- `logs/`:调试与排障日志
|
|
|
|
|
|
---
|
|
|
|
|
|
## 14. 第一版时机约定
|
|
|
|
|
|
### 14.1 QET -> FreeCAD
|
|
|
|
|
|
触发时机:
|
|
|
|
|
|
- 用户点击 QET 的 `3D视图`
|
|
|
|
|
|
动作:
|
|
|
|
|
|
1. 更新并确认当前 2D 设备实例 / 端子实例绑定
|
|
|
2. 生成 `2d_to_3d.json`
|
|
|
3. 打开 FreeCAD
|
|
|
4. 打开或创建 `scene.FCStd`
|
|
|
|
|
|
### 14.2 FreeCAD -> QET
|
|
|
|
|
|
第一版不建议:
|
|
|
|
|
|
- FreeCAD 保存时直接写数据库
|
|
|
|
|
|
第一版建议:
|
|
|
|
|
|
- FreeCAD 保存时输出 `3d_to_2d.json`
|
|
|
|
|
|
然后由 QET 在后续时机主动读取:
|
|
|
|
|
|
- 手动刷新
|
|
|
- 再次点击 `3D视图`
|
|
|
- 或后续增加单独的“从 3D 刷新”命令
|
|
|
|
|
|
这样做的目的,是先把同步时机收成“文件交换”,避免两边运行时直接打架。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 15. 第一版最小交换内容
|
|
|
|
|
|
### 15.1 `2d_to_3d.json`
|
|
|
|
|
|
第一版只要求包含最小绑定信息:
|
|
|
|
|
|
- 设备绑定:
|
|
|
- `project_uuid`
|
|
|
- `element_uuid`
|
|
|
- `instance_id`
|
|
|
- 端子绑定:
|
|
|
- `project_uuid`
|
|
|
- `terminal_uuid`
|
|
|
- `instance_id`
|
|
|
|
|
|
说明:
|
|
|
|
|
|
- `instance_id` 在第一版中由 FreeCAD 侧生成更合理
|
|
|
- 如果首次进入 3D 时尚未生成 `instance_id`,可以先导出为空,再由 FreeCAD 创建后回写
|
|
|
|
|
|
### 15.2 `3d_to_2d.json`
|
|
|
|
|
|
第一版只建议回写:
|
|
|
|
|
|
- `project_uuid`
|
|
|
- `element_uuid`
|
|
|
- `instance_id`
|
|
|
- `terminal_uuid`
|
|
|
|
|
|
当前不要求回写:
|
|
|
|
|
|
- 3D 位姿
|
|
|
- 装配结构
|
|
|
- 导线几何路径
|
|
|
|
|
|
这些仍以 FreeCAD 文档为准。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 16. 第一版推荐交互流程
|
|
|
|
|
|
建议按下面这条闭环实现:
|
|
|
|
|
|
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`
|
|
|
|
|
|
一句话总结:
|
|
|
|
|
|
> 第一版先做“QET 导出 JSON + 打开 FreeCAD;FreeCAD 保存时回写 JSON”的文件交换闭环,不做强耦合实时同步。
|