# 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 /.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 的最小项目快照”,而不是数据库整表镜像。