You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
LightWork3D/docs/FreeCAD 端子显示连线保存回写开发文档.md

23 KiB

FreeCAD 端子显示、手动连线、保存回写开发文档

1. 当前任务边界

本文档只面向当前由一个人负责的 FreeCAD 3D 工作台能力:

  • 端子显示
  • 端子手动连线
  • FreeCAD 文档保存
  • 最小 3D -> 2D 回写

当前不做:

  • 自动布线
  • D:\code\zwl\sources\ThreeD 旧 3D 引擎扩展
  • 复杂 UI 改造
  • FreeCAD 核心 C/C++ 源码改造
  • 3D 位姿写入数据库
  • 新增或依赖旧 3D 场景表

第一版 3D 真相源是 FreeCAD 文档,即 scene.FCStd。QET/zwl 只负责 2D 数据、交换 JSON、启动 FreeCAD、后续读取最小回写结果。

2. 已有代码与文档现状

2.1 LightWork3D / FreeCAD 侧

已有模块:

  • src/Mod/FreeCADExchange/ExchangeBootstrap.py
  • src/Mod/FreeCADExchange/DeviceImport.py
  • src/Mod/FreeCADExchange/InitGui.py

当前已经做到:

  • 从环境变量 QET_2D_TO_3D_JSON 读取 2d_to_3d.json
  • 校验 project_uuid / devices / terminals / device_models
  • 根据 resolved_model_path 导入 STEP / IGES / FCStd 等本地模型
  • 将导入的设备放入 QETExchangeDevices 根组

当前还没有真正完成:

  • 根据 terminals 创建设备端子对象
  • 端子对象的 LCS / 标记属性
  • 端子之间的手动连线
  • 3d_to_2d.json 回写

2.2 zwl / QET 侧

已有模块:

  • sources/FreeCAD/FreeCADExchangeExportService.cpp
  • sources/FreeCAD/FreeCADLaunchService.cpp

当前已经做到:

  • 导出 <ProjectRoot>/.qet_freecad/2d_to_3d.json
  • 返回 <ProjectRoot>/.qet_freecad/scene.FCStd
  • 启动 FreeCAD并设置
    • QET_2D_TO_3D_JSON
    • QET_FREECAD_SCENE_FILE

第一版数据库约束:

  • 只允许依赖 project_2d3d_symbol_binding
  • 只允许依赖 project_2d3d_terminal_binding
  • 设备绑定只依赖 project_uuid / element_uuid / instance_id
  • 端子绑定只依赖 project_uuid / terminal_uuid / instance_id
  • 第一版端子绑定唯一依据是 terminal_uuid

2.3 代码落地约束

当前第一版实现优先放在:

D:\LightWork3D\src\Mod\FreeCADExchange

实现形式优先使用 Python 文件,例如:

src/Mod/FreeCADExchange/DeviceImport.py
src/Mod/FreeCADExchange/TerminalImport.py
src/Mod/FreeCADExchange/ManualWiring.py
src/Mod/FreeCADExchange/ExchangeWriteBack.py

第一版尽量只改 FreeCAD 插件层:

  • 可以新增 FreeCADExchange 下的 .py 文件。
  • 可以修改 FreeCADExchange\ExchangeBootstrap.py 做流程调度。
  • 可以修改 FreeCADExchange\InitGui.py 注册简单命令。
  • 可以修改 FreeCADExchange\CMakeLists.txt,把新增 .py 加入安装/复制列表。

第一版尽量不改:

  • FreeCAD 核心 C/C++ 源码。
  • D:\code\zwl\sources\ThreeD 旧 3D 模块。
  • 数据库旧 3D 场景表。

这样做的好处是:你的端子显示、手动连线、保存回写可以先作为 FreeCAD 工作台插件跑通;后续 csm 改 UI、qdj 做自动布线时,可以直接调用这些 Python 层能力,不需要他们理解或修改 FreeCAD 核心源码。

3. 本地设备模板是否可以使用

可以,而且第一版建议优先使用本地模板。

QET 导出的 device_models[].resolved_model_path 本来就是给 FreeCAD 使用的本地模型路径。FreeCADExchange 当前支持导入:

  • .step
  • .stp
  • .iges
  • .igs
  • .brep
  • .brp
  • .fcstd

因此本地 STEP 文件可以直接作为第一批几何资源导入。

但从当前开发方向开始,正式可复用的设备模板建议统一保存为 .FCStd。也就是说:

STEP / STP / STE 原始几何
  -> 在 FreeCAD 里添加端子 LCS 和电气语义
  -> 保存为 FCStd 设备模板
  -> 后续不同工程、不同人员统一使用这个 FCStd

STEP / STP / STE 适合作为模板制作的输入,不建议作为长期带电气语义的最终交付文件。因为 STEP 可以稳定保存几何,但不能可靠保存 FreeCAD LCS、动态属性、端子角色、接线资格等二次开发语义。

但需要注意:

本地 STEP 只提供几何,不天然提供“哪个位置是端子”。

所以要实现端子显示和端子连线,本地模板还必须补一层端子语义。第一版采用下面的优先级:

  1. 正式方式:使用 FCStd 模板,在模板里提前放好 LCS 端子对象。
  2. 过渡方式STEP + sidecar JSON在同目录下保存端子槽位坐标。
  3. 如果没有模板语义,则跳过端子生成并输出警告,不再用 bbox fallback 猜端子位置。

sidecar 只作为 FreeCAD 端模板辅助文件,不进入第一版数据库绑定主键。

sidecar 里除了端子坐标,还可以继续补端子朝向,例如 rotation,让模板端子不只是“有位置”,还可以“有方向”。

FCStd 模板里的 LCS 如果已经带了 Placement 朝向,导入时要把位置和朝向一并读取出来,这样生成的工程端子不只是有坐标,还能保留真实出线方向。

3.1 FCStd 设备模板制作约定

FCStd 设备模板用于解决“这个模型本身就带端子语义”的问题。模板端子是跨工程复用的槽位,不绑定某个具体工程里的 terminal_uuid

推荐模板结构:

电流互感器.FCStd
  ModelGeometry
  Terminal_P1
  Terminal_P2

模板端子使用 LCS 表示,至少保存:

  • Role = "Terminal"
  • CanWire = true
  • QetTemplateSlotName = "P1" / "P2"
  • QetTerminalLabel = "P1" / "P2"
  • QetTerminalType = "primary" 等可选分类

模板端子不保存:

  • QetTerminalUuid
  • QetInstanceId
  • project_uuid
  • 任意工程内绑定字段

这些工程级字段由导入项目时的 2d_to_3d.json 和 FreeCADExchange 运行时补齐。这样同一个 FCStd 设备模板才能在多个工程、多台机器、多人之间复用。

3.1.1 FCStd 导入运行时约定

FCStd 模板中的端子 LCS 是“模板槽位定义”,不是最终留在工程场景里的端子对象。

导入到 QETSceneFreeCADExchange 的处理顺序是:

  1. 复制 FCStd 中的几何对象到设备组。
  2. 读取模板 LCS 的 QetTemplateSlotName / Label / Placement
  3. 将槽位位置和朝向序列化到设备组属性 QetTemplateSlotsJson
  4. 删除复制出来的模板 LCS 及其 OriginFeatures,避免这些模板对象留在 App::Part 中参与变换或选择。
  5. 后续由 TerminalImport.pyterminal_uuidQETTerminals_* 下创建工程端子 LCS。

这样做不会改变第一版数据库约束:QetTemplateSlotsJson 只是 FreeCAD 文档内部的运行时缓存,不进入 project_2d3d_symbol_bindingproject_2d3d_terminal_binding

如果同一个设备从 FCStd 切回 STEP / IGES / BREP 等非 FCStd 模型,导入时会清空旧的 QetTemplateSlotsJson,避免继续沿用上一次 FCStd 的端子槽位。

3.2 模板制作工具目标

后续在 FreeCADExchange 中新增模板制作能力,目标是让用户不需要手工给 LCS 添加属性:

  1. 导入 STEP / STP / STE 几何模型。
  2. 在模型真实接线位置添加端子,例如 P1P2
  3. 自动创建 LCS并写入模板端子语义。
  4. 保存为 .FCStd 设备模板。
  5. 后续 LightWork3D 工程引用该 .FCStd 后,自动识别模板端子并生成工程端子对象。

3.2.1 方案 2设备模板端子制作面板

当前 Python 控制台方式只适合开发验证,不适合 CAD 工作人员。下一步开发目标改为在 FreeCAD 右侧任务区提供“设备模板端子制作”面板。

第一版面板能力:

  • 显示当前文档中的模板端子列表。
  • 输入端子名,例如 P1P2
  • 用户选择模型上的孔、点或对象后,点击“添加端子”。
  • 点击“校验端子”显示总数、有效数和警告。
  • 点击“保存为 FCStd”选择路径并保存模板。

该面板只包装现有 Python 能力,不改 FreeCAD C/C++ 源码:

  • TemplateAuthoring.create_template_terminal
  • TemplateAuthoring.validate_template_terminals
  • TemplateAuthoring.save_template_as_fcstd

目标是让 CAD 工作人员只通过鼠标选择和按钮点击完成模板制作,不再直接输入 Python 代码。

3.3 A 方案资产流转约定

A 方案下,.FCStd 是正式可复用设备资产STEP / STP / STE 只作为模板制作的原始几何输入。

完整流转固定为:

STEP / STP / STE 原始几何
  -> FreeCAD 中添加端子 LCS 和模板语义
  -> 保存为 FCStd 设备模板
  -> zwl/QET 选择该 FCStd 作为设备 3D 资产
  -> 3D 视图导出 2d_to_3d.json
  -> FreeCAD 导入 FCStd 并绑定工程端子
  -> 手动连线并保存 scene.FCStd
  -> 生成 3d_to_2d.json

zwl/QET 侧只需要支持 .FCStd 的选择、复制、保存和导出路径,不解析 .FCStd 内部的 LCS、端子属性或装配结构。.FCStd 的内部语义由 FreeCADExchange 读取。

详细设计见:

  • docs/superpowers/specs/2026-05-20-freecad-fcstd-asset-flow-design.md

4. 为什么要先落地设备模板

这里的“设备模板”不是要求先把所有设备都建完,而是要先有一个稳定样板,证明端子显示和连线可以依附在真实设备上。

原因:

  • 端子必须依附在设备实例上,不能只是空间中的孤立点。
  • 端子连线需要稳定的起点、终点和出线方向。
  • STEP 普通顶点不稳定,不适合作为端子主对象。
  • 如果没有模板层,后续每种设备都要手工猜端子位置。
  • 自动布线后续会依赖当前手动布线沉淀出的端子对象和路径对象。

因此第一版只需要选 1 到 2 个设备样板,例如断路器、端子排或继电器,把模板约定跑通。

5. 第一版对象模型

5.1 设备实例组

每个 2D 设备实例在 FreeCAD 中对应一个设备组。

建议对象:

QETExchangeDevices
  QETDevice_<element_uuid>
    imported model objects
    QETTerminals
    QETWires

设备组至少保存属性:

  • QetProjectUuid
  • QetElementUuid
  • QetInstanceId
  • QetResolvedModelPath

QetInstanceId 如果输入为空,由 FreeCAD 生成,并在保存回写时写入 3d_to_2d.json

5.2 端子对象

端子使用 FreeCAD LCS 表示。推荐对象为:

Datum CoordinateSystem + Role="Terminal"

端子对象至少保存属性:

  • QetProjectUuid
  • QetElementUuid
  • QetTerminalUuid
  • QetInstanceId
  • Role = "Terminal"
  • CanWire = true

端子判断规则:

  1. 对象必须有 Role
  2. Role == "Terminal"
  3. 对象必须有 QetTerminalUuid
  4. CanWire == true

第一版禁止把 terminal_keyconnection_point_key 当作绑定主键。模板内部可以有槽位名,但最终业务识别只认 terminal_uuid

5.3 手动连线对象

手动连线可以先用 FreeCAD 中的折线对象表示,例如 Draft Wire 或 Part 曲线。

连线对象至少保存属性:

  • QetProjectUuid
  • QetStartTerminalUuid
  • QetEndTerminalUuid
  • QetStartInstanceId
  • QetEndInstanceId
  • RouteType = "Manual"

连线对象建议挂在对应设备实例下的 QETWires_<element_uuid> 组里,优先跟随起点端子所属设备。

第一版连线路径保存在 scene.FCStd。是否把路径几何回写给 QET后续单独扩协议不在当前最小数据库绑定范围内。

6. 推荐文件结构

src/Mod/FreeCADExchange 下逐步拆分模块:

FreeCADExchange/
  ExchangeBootstrap.py      # 启动入口,读取 JSON调度导入流程
  DeviceImport.py           # 设备导入,已存在
  TemplateSemantics.py      # 新增:读取 FCStd LCS 或 STEP sidecar 端子槽位
  TemplateAuthoring.py      # 计划新增:把 STEP/STP/STE 制作为带端子语义的 FCStd 模板
  TemplateAuthoringPanel.py # 新增CAD 人员使用的端子制作任务面板
  TerminalImport.py         # 新增:根据 terminals 创建/更新端子对象
  TerminalObjects.py        # 新增:端子对象属性、查找、校验工具
  ManualWiring.py           # 新增:端子选择、折线路径创建、连线对象属性
  ExchangeWriteBack.py      # 新增:生成 3d_to_2d.json
  InitGui.py                # 后续由 UI 改造统一接入命令
  CMakeLists.txt            # 新增 .py 后必须同步加入 FreeCADExchange_Scripts

如果暂时不做 UI可以先暴露 Python 函数和简单命令,保证功能链路可测。

推荐第一版调用链:

ExchangeBootstrap.py
  -> DeviceImport.import_devices_from_payload(...)
  -> TerminalImport.import_terminals_from_payload(...)
  -> ExchangeWriteBack.write_back_if_needed(...) 或由保存命令触发

ManualWiring.py
  -> 只允许选择 TerminalObjects.is_terminal_object(...) 认可的端子对象
  -> 创建 RouteType="Manual" 的连线对象
  -> 保存到 scene.FCStd

每新增一个 Python 模块,都要加入 FreeCADExchange\CMakeLists.txtFreeCADExchange_Scripts,否则 Visual Studio 构建/INSTALL 后运行目录里不会出现该脚本。

7. 开发步骤

阶段 A0FCStd 设备模板制作

目标:

  • 用户可以导入 STEP / STP / STE 几何模型。
  • 用户可以给模型添加带电气语义的端子 LCS。
  • 模板保存为 .FCStd 后,可以脱离当前工程复用。

实现建议:

  • 新增 TemplateAuthoring.py
  • 提供最小命令:
    • QET_Template_AddTerminal
    • QET_Template_SaveAsFCStd
  • 第一版端子位置可以通过用户选择对象/点位后的三维坐标生成。
  • 第一版端子方向默认使用单位旋转,后续再补出线方向编辑。

模板端子属性:

  • Role = "Terminal"
  • CanWire = true
  • QetTemplateSlotName
  • QetTerminalLabel
  • QetTerminalType

验收:

  • 打开一个普通 STEP 模型。
  • 添加 P1P2 两个端子。
  • 保存为 电流互感器.FCStd
  • 重新打开该 FCStd 后,端子 LCS 仍存在,属性仍存在。
  • 在项目导入流程中引用该 FCStd端子位置优先来自模板 LCS而不是 bbox fallback。

阶段 A本地模板导入基线

目标:

  • 本地 STEP / FCStd 可以稳定导入。
  • 正式设备资源优先使用带端子语义的 FCStd 模板。
  • 设备组有 QetElementUuid / QetInstanceId
  • 重新打开同一个项目时,不重复创建同一设备。

改动点:

  • 保持 DeviceImport.py 现有逻辑。
  • 补齐设备组上的 QetProjectUuid
  • 如果 instance_id 为空,生成稳定的 FreeCAD 实例 ID。

验收:

  • 使用本地 FCStd 模板导入设备。
  • FreeCAD 树中可看到设备组。
  • 关闭并重新打开,不产生重复设备组。

阶段 B端子显示

目标:

  • 根据 2d_to_3d.jsonterminals 为设备创建端子对象。
  • 每个端子都能在 FreeCAD 场景中被看到、被选中。

实现建议:

  • 新增 TerminalImport.py
  • element_uuidinstance_id 找到设备组。
  • 为每个 terminal_uuid 创建 LCS。
  • LCS 放入设备组下的 QETTerminals 子组。

端子位置策略:

  1. 如果 FCStd 模板已有 Role="Terminal" 的 LCS则优先读取模板 LCS 的槽位信息,再生成工程端子 LCS。
  2. 如果有 sidecar JSON则按 sidecar 坐标创建。
  3. 如果只有 STEP 且没有 sidecar则不生成端子并在导入报告中提示缺少模板槽位。

第一版不再按设备包围盒猜测临时端子,避免把错误端子位置保存进工程。

验收:

  • 控制台输出端子创建数量。
  • 每个端子对象带 QetTerminalUuid
  • 选择端子时能确认它属于哪个设备实例。

阶段 C端子手动连线

目标:

  • 只能从端子连到端子。
  • 普通几何点、边、面不能直接作为布线端点。

实现建议:

  • 新增 ManualWiring.py
  • 提供函数:
    • is_terminal_object(obj)
    • create_manual_wire(start_terminal, end_terminal, waypoints)
    • list_manual_wires(doc)
  • 第一版路径点可以先使用:
    • 起点 LCS 原点
    • 用户手动点选的中间点
    • 终点 LCS 原点

如果暂时没有完整 UI可先做最小命令

  1. 用户选择两个端子对象。
  2. 执行命令生成一条直线或折线。
  3. 连线对象保存起止端子 UUID。

验收:

  • 未选择端子时拒绝创建连线。
  • 选择同一个端子作为起终点时拒绝创建。
  • 创建成功后,连线对象带起点和终点 UUID。

阶段 D保存与回写

目标:

  • FreeCAD 保存 scene.FCStd
  • 生成 .qet_freecad/3d_to_2d.json

实现建议:

  • 新增 ExchangeWriteBack.py
  • 从文档中扫描:
    • 设备组
    • 端子对象
    • 手动连线对象
  • 第一版正式回写结构遵守现有协议最小集:
{
  "schema_version": "1.0",
  "project_uuid": "string",
  "generated_at": "2026-05-20T10:00:00+08:00",
  "instances": [
    {
      "element_uuid": "string",
      "instance_id": "string"
    }
  ],
  "terminals": [
    {
      "terminal_uuid": "string",
      "instance_id": "string"
    }
  ]
}

连线路径第一版保存在 .FCStd。如需 QET 读取线对象,建议后续扩展协议字段,不直接写入现有两张绑定表。

验收:

  • 保存后存在 3d_to_2d.json
  • JSON 中每个设备有 element_uuid / instance_id
  • JSON 中每个端子有 terminal_uuid / instance_id
  • 不写入 project_3d_scene_instanceproject_3d_space_objectproject_2d3d_link

8. 是否能达到图二效果

可以达到同方向效果,但需要分层理解。

图二效果包含:

  • 本地模型导入
  • 柜体和导轨等场景模型
  • 设备装配
  • FreeCAD 视图展示
  • 模型树层级

本文档当前只保证:

  • 本地模板能进入 FreeCAD
  • 端子能显示
  • 端子能手动连线
  • FreeCAD 能保存并回写最小绑定

如果本地模板里包含机柜、导轨和设备,并且导入逻辑按组组织,就可以逐步做出图二那类 FreeCAD 场景。复杂 UI、自动排布、自动布线不属于当前个人任务第一阶段。

9. 首批模板建议

第一批不要铺太多设备。建议只选:

  1. 机柜或安装板:提供场景参考。
  2. DIN 导轨:提供设备摆放参考。
  3. 一个断路器或继电器:验证设备端子。
  4. 一个端子排:验证多端子排列和连线。

每个模板至少准备:

  • 原始几何文件:.STEP / .STP / .STE
  • 正式模板文件:.FCStd
  • 模板内 LCS 端子:Role="Terminal",带槽位名和接线资格
  • 可选 sidecar只作为过渡或校验不作为正式交付优先方案
  • 模板说明:原点、朝向、尺寸单位、端子数量

常用设备建议优先补齐 FCStd LCS,把端子位置从临时的 bbox fallback 提升为真实可用坐标。

10. 单人开发优先级

建议按下面顺序推进:

  1. 不改 UI先把 Python 功能函数跑通。
  2. 不碰自动布线,先把端子对象和手动线对象持久化。
  3. 不扩旧数据库表,只生成 3d_to_2d.json
  4. 不追求所有设备,先做一个样板设备跑通闭环。
  5. 不让 STEP 顶点直接参与布线,只认带属性的端子 LCS。

11. 最小验收场景

准备一个测试项目:

  • 2 个设备
  • 每个设备至少 2 个端子
  • 每个设备绑定一个本地 STEP 或 FCStd 模型

验收步骤:

  1. 在 QET 点击打开 FreeCAD。
  2. QET 生成 2d_to_3d.json
  3. FreeCAD 导入设备模型。
  4. FreeCAD 为端子创建 LCS。
  5. 用户选择两个端子创建手动连线。
  6. 保存 FreeCAD 文档。
  7. 生成 3d_to_2d.json
  8. 关闭 FreeCAD 再打开,设备、端子、连线仍存在。

达到以上结果,就说明“端子显示 + 端子连线 + 保存回写”第一版闭环成立。

12. 开发结果记录要求

每次开发完成后,都要在本文档末尾追加一条开发记录,至少包含:

  • 时间
  • 本次实现了什么
  • 验证结果
  • 未完成或待跟进事项

建议格式:

- 2026-05-20完成端子对象创建与显示逻辑支持 FCStd LCS / sidecar JSON / bbox fallback已验证 FreeCAD 可导入并生成端子对象。待补常用设备真实端子坐标。
- 2026-05-20补上 sidecar `rotation` 解析与端子 Placement 应用,端子模板现在可以同时带位置和方向;已用单元测试验证解析和放置逻辑。
- 2026-05-20补上 FCStd 模板 LCS 朝向保留,模板端子现在可以从源对象直接继承 Placement 方向;已用单元测试验证。
- 2026-05-20补上手动连线对象归属到设备 `QETWires_*` 组,连线树结构现在和设备模板一致;已用单元测试验证。
- 2026-05-20明确 A 方案STEP/STP/STE 只作为原始几何输入,正式可复用设备资源统一保存为带 LCS 电气端子的 FCStd 模板;后续设计 `TemplateAuthoring.py` 做模板制作工具。
- 2026-05-20新增 FCStd 设备模板制作基础能力,支持把模型上的点位创建为带 `Role="Terminal"`、`CanWire=true`、`QetTemplateSlotName` 的模板端子 LCS已用单元测试验证端子语义写入和模板校验逻辑。
- 2026-05-20补充 A 方案资产流转设计,明确 `.FCStd` 为正式设备资产zwl/QET 只负责选择、保存、导出 `.FCStd` 路径FreeCADExchange 负责读取 LCS 端子语义并生成工程端子。
- 2026-05-20补上 `QET_Template_SaveAsFCStd` 模板保存命令,保存前会校验至少存在一个有效模板端子,并自动补 `.FCStd` 后缀;已用单元测试验证保存路径和端子校验结果。
- 2026-05-20修复 `TemplateAuthoring.py` 在 `FreeCADCmd.exe` 命令行模式下导入时误注册 GUI 命令的问题;已在运行目录验证创建 `P1` 模板端子、保存 `.FCStd`、重新打开后端子语义仍可识别。
- 2026-05-20确定方案 2 开发目标:新增“设备模板端子制作”任务面板,让 CAD 工作人员通过输入端子名、选择模型位置、点击按钮完成添加端子、校验端子和保存 FCStd不再依赖 Python 控制台。
- 2026-05-20新增 `TemplateAuthoringPanel.py`,提供“设备模板端子制作”任务面板和 `QET_Template_OpenAuthoringPanel` 命令;面板支持输入端子名、添加端子、校验端子、保存 FCStd并已同步到运行目录验证模块可导入。
- 2026-05-25新增 `WiringImport.py`,把 `2d_to_3d.json` 中的 `wires` 导入为 `QETWiring_01_Tasks` 下的导线任务;`ExchangeBootstrap.py` 已接入启动导入流程。`ManualWiringPanel.py` 增加任务列表、选择导线任务和删除最后折点,按任务生成导线时会把 `wire_id / net_uuid / group_uuid / wire_mark` 写入正式导线对象,并把任务状态更新为 `Routed`。已通过 35 项 `freecad_exchange*_test.py` 单元测试,并安装到 `D:\fc\run-FreeCAD-1.1.1` 运行目录验证 `WiringImport / ManualWiring / ManualWiringPanel / WiringObjects` 可导入。
- 2026-05-25修复 FCStd 设备导入后模板 LCS 留在工程场景的问题;导入时会把模板槽位位置和朝向缓存到设备组 `QetTemplateSlotsJson`,随后删除模板 LCS 及其 `OriginFeatures`,工程端子仍按 `terminal_uuid` 生成到 `QETTerminals_*`。已补单元测试验证 FCStd 导入不保留模板 LCS、切回 STEP 会清空旧槽位缓存,并避免重复访问已删除对象的 `Group / InList / Name`。