From 84c2d5c533d0403c8c69cb42df3c033c4f34a0d5 Mon Sep 17 00:00:00 2001 From: zhanghao <2024138486@qq.com> Date: Wed, 27 May 2026 11:04:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4=202D-3D=20=E4=BA=A4?= =?UTF-8?q?=E6=8D=A2=E7=BB=88=E7=AB=AF=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docs/2D-3D交换协议.md | 17 +++-- src/Mod/FreeCADExchange/ExchangeBootstrap.py | 70 ++++++++++++-------- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index a7a5059..b9c6f70 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ install_manifest.txt /bulid/ /build-*/ /cmake-build*/ +/run/ /src/Tools/offlinedoc/localwiki/ /src/Tools/offlinedoc/*.txt /conda/environment.yml diff --git a/docs/2D-3D交换协议.md b/docs/2D-3D交换协议.md index dddacae..9418b4b 100644 --- a/docs/2D-3D交换协议.md +++ b/docs/2D-3D交换协议.md @@ -110,7 +110,6 @@ }, "cabinet": {}, "devices": [], - "terminals": [], "device_models": [], "wires": [] } @@ -123,8 +122,7 @@ - `generated_at`:导出时间 - `source`:导出来源信息 - `cabinet`:当前图纸属性中绑定的机柜信息 -- `devices`:设备实例绑定 -- `terminals`:端子实例绑定 +- `devices`:设备实例绑定,以及每个设备名下的接线处 - `device_models`:设备 3D 模型解析结果 - `wires`:导线起点/终点与标注快照 @@ -193,7 +191,8 @@ { "element_uuid": "string", "instance_id": "string", - "display_tag": "string" + "display_tag": "string", + "terminals": [] } ``` @@ -204,20 +203,22 @@ | `element_uuid` | 2D设备实例UUID | 是 | QET 图纸中的设备实例主键 | | `instance_id` | 3D实例ID | 是 | FreeCAD 侧设备实例主键 | | `display_tag` | 2D设备实例标注 | 否 | JSON 显示辅助字段,优先使用 2D 中设备标注作为 FreeCAD 树标签;为空时再退回 `instance_id` / `element_uuid` | +| `terminals` | 设备接线处列表 | 否 | 当前设备名下的 2D 接线处集合,FreeCAD 会先按设备再导入接线处 | ### 6.4 说明 - 如果第一次进入 3D 时还没有 `instance_id`,允许先导出空字符串或缺省值 - FreeCAD 创建 3D 实例后,再在回写阶段补齐 - `display_tag` 不进入第一版数据库最小字段集,它只存在于交换 JSON 中,用来让 3D 树视图与 2D 标注更容易对上 +- `terminals` 是设备级子结构,不再单独放在顶层 --- -## 7. `terminals` 结构 +## 7. `devices[].terminals` 结构 ### 7.1 作用 -`terminals` 负责表达: +`devices[].terminals` 负责表达: > 一个 2D 端子实例,属于哪个 3D 设备实例。 @@ -227,7 +228,8 @@ { "terminal_uuid": "string", "instance_id": "string", - "element_uuid": "string" + "element_uuid": "string", + "terminal_display": "string" } ``` @@ -238,6 +240,7 @@ | `terminal_uuid` | 2D端子UUID | 是 | QET 端子实例主键 | | `instance_id` | 3D实例ID | 是 | 该端子所属的 3D 设备实例 | | `element_uuid` | 2D设备实例UUID | 否 | JSON 导入辅助字段,帮助 FreeCAD 在首次没有 `instance_id` 时仍能知道端子属于哪个设备 | +| `terminal_display` | 接线处标注 | 否 | 2D 端子在图纸上的显示标注,供 FreeCAD 端子对象显示和槽位匹配使用 | ### 7.4 为什么这里允许带 `element_uuid` diff --git a/src/Mod/FreeCADExchange/ExchangeBootstrap.py b/src/Mod/FreeCADExchange/ExchangeBootstrap.py index b41ba02..edb4b70 100644 --- a/src/Mod/FreeCADExchange/ExchangeBootstrap.py +++ b/src/Mod/FreeCADExchange/ExchangeBootstrap.py @@ -337,43 +337,55 @@ def _normalize_devices(payload): index ) ) + device_terminals = item.get("terminals", []) + if device_terminals is None: + device_terminals = [] + if not isinstance(device_terminals, list): + raise ExchangeValidationError( + "Field 'terminals' in device entry #{0} must be a list.".format(index) + ) + + normalized_terminals = [] + for terminal_index, terminal_item in enumerate(device_terminals): + terminal_entry_label = "device entry #{0} terminal entry #{1}".format( + index, terminal_index + ) + if not isinstance(terminal_item, dict): + raise ExchangeValidationError( + "{0} must be an object.".format(terminal_entry_label.capitalize()) + ) + terminal_uuid = _require_string(terminal_item, "terminal_uuid") + terminal_element_uuid = _optional_string( + terminal_item, "element_uuid", terminal_entry_label + ) or element_uuid + normalized_terminals.append( + { + "terminal_uuid": terminal_uuid, + "instance_id": _normalize_instance_id(terminal_item) + or _normalize_instance_id(item), + "element_uuid": terminal_element_uuid, + "terminal_display": _optional_string( + terminal_item, "terminal_display", terminal_entry_label + ), + } + ) + normalized.append( { "element_uuid": element_uuid, "instance_id": _normalize_instance_id(item), "display_tag": display_tag.strip() if isinstance(display_tag, str) else "", + "terminals": normalized_terminals, } ) return normalized -def _normalize_terminals(payload): - terminals = payload.get("terminals", []) - if not isinstance(terminals, list): - raise ExchangeValidationError("Field 'terminals' must be a list.") - +def _normalize_terminals(devices): normalized = [] - for index, item in enumerate(terminals): - if not isinstance(item, dict): - raise ExchangeValidationError( - "Terminal entry #{0} must be an object.".format(index) - ) - terminal_uuid = _require_string(item, "terminal_uuid") - element_uuid = item.get("element_uuid", "") - if element_uuid and not isinstance(element_uuid, str): - raise ExchangeValidationError( - "Field 'element_uuid' in terminal entry #{0} must be a string.".format( - index - ) - ) - normalized.append( - { - "terminal_uuid": terminal_uuid, - "instance_id": _normalize_instance_id(item), - "element_uuid": element_uuid.strip() if isinstance(element_uuid, str) else "", - "terminal_display": _optional_string(item, "terminal_display", "terminal entry #{0}".format(index)), - } - ) + for device in devices: + for terminal in device.get("terminals", []) or []: + normalized.append(dict(terminal)) return normalized @@ -562,14 +574,16 @@ def load_exchange_payload(json_path): if not isinstance(schema_version, str) or not schema_version.strip(): raise ExchangeValidationError("Field 'schema_version' must be a string.") + normalized_devices = _normalize_devices(payload) + normalized = { "schema_version": schema_version.strip(), "project_uuid": project_uuid, "generated_at": payload.get("generated_at", ""), "source": payload.get("source", {}), "cabinet": _normalize_cabinet(payload), - "devices": _normalize_devices(payload), - "terminals": _normalize_terminals(payload), + "devices": normalized_devices, + "terminals": _normalize_terminals(normalized_devices), "device_models": _normalize_device_models(payload), "wires": _normalize_wires(payload), }