# 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` 当前已经做到: - 导出 `/.qet_freecad/2d_to_3d.json` - 返回 `/.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 代码落地约束 当前第一版实现优先放在: ```text D:\LightWork3D\src\Mod\FreeCADExchange ``` 实现形式优先使用 Python 文件,例如: ```text 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`。也就是说: ```text 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`。 推荐模板结构: ```text 电流互感器.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 是“模板槽位定义”,不是最终留在工程场景里的端子对象。 导入到 `QETScene` 时,FreeCADExchange 的处理顺序是: 1. 复制 FCStd 中的几何对象到设备组。 2. 读取模板 LCS 的 `QetTemplateSlotName / Label / Placement`。 3. 将槽位位置和朝向序列化到设备组属性 `QetTemplateSlotsJson`。 4. 删除复制出来的模板 LCS 及其 `OriginFeatures`,避免这些模板对象留在 `App::Part` 中参与变换或选择。 5. 后续由 `TerminalImport.py` 按 `terminal_uuid` 在 `QETTerminals_*` 下创建工程端子 LCS。 这样做不会改变第一版数据库约束:`QetTemplateSlotsJson` 只是 FreeCAD 文档内部的运行时缓存,不进入 `project_2d3d_symbol_binding` 或 `project_2d3d_terminal_binding`。 如果同一个设备从 FCStd 切回 STEP / IGES / BREP 等非 FCStd 模型,导入时会清空旧的 `QetTemplateSlotsJson`,避免继续沿用上一次 FCStd 的端子槽位。 ### 3.2 模板制作工具目标 后续在 `FreeCADExchange` 中新增模板制作能力,目标是让用户不需要手工给 LCS 添加属性: 1. 导入 STEP / STP / STE 几何模型。 2. 在模型真实接线位置添加端子,例如 `P1`、`P2`。 3. 自动创建 LCS,并写入模板端子语义。 4. 保存为 `.FCStd` 设备模板。 5. 后续 LightWork3D 工程引用该 `.FCStd` 后,自动识别模板端子并生成工程端子对象。 ### 3.2.1 方案 2:设备模板端子制作面板 当前 Python 控制台方式只适合开发验证,不适合 CAD 工作人员。下一步开发目标改为在 FreeCAD 右侧任务区提供“设备模板端子制作”面板。 第一版面板能力: - 显示当前文档中的模板端子列表。 - 输入端子名,例如 `P1`、`P2`。 - 用户选择模型上的孔、点或对象后,点击“添加端子”。 - 点击“校验端子”显示总数、有效数和警告。 - 点击“保存为 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 只作为模板制作的原始几何输入。 完整流转固定为: ```text 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 中对应一个设备组。 建议对象: ```text QETExchangeDevices QETDevice_ imported model objects QETTerminals QETWires ``` 设备组至少保存属性: - `QetProjectUuid` - `QetElementUuid` - `QetInstanceId` - `QetResolvedModelPath` `QetInstanceId` 如果输入为空,由 FreeCAD 生成,并在保存回写时写入 `3d_to_2d.json`。 ### 5.2 端子对象 端子使用 FreeCAD LCS 表示。推荐对象为: ```text Datum CoordinateSystem + Role="Terminal" ``` 端子对象至少保存属性: - `QetProjectUuid` - `QetElementUuid` - `QetTerminalUuid` - `QetInstanceId` - `Role = "Terminal"` - `CanWire = true` 端子判断规则: 1. 对象必须有 `Role` 2. `Role == "Terminal"` 3. 对象必须有 `QetTerminalUuid` 4. `CanWire == true` 第一版禁止把 `terminal_key` 或 `connection_point_key` 当作绑定主键。模板内部可以有槽位名,但最终业务识别只认 `terminal_uuid`。 ### 5.3 手动连线对象 手动连线可以先用 FreeCAD 中的折线对象表示,例如 Draft Wire 或 Part 曲线。 连线对象至少保存属性: - `QetProjectUuid` - `QetStartTerminalUuid` - `QetEndTerminalUuid` - `QetStartInstanceId` - `QetEndInstanceId` - `RouteType = "Manual"` 连线对象建议挂在对应设备实例下的 `QETWires_` 组里,优先跟随起点端子所属设备。 第一版连线路径保存在 `scene.FCStd`。是否把路径几何回写给 QET,后续单独扩协议,不在当前最小数据库绑定范围内。 ## 6. 推荐文件结构 在 `src/Mod/FreeCADExchange` 下逐步拆分模块: ```text 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 函数和简单命令,保证功能链路可测。 推荐第一版调用链: ```text 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.txt` 的 `FreeCADExchange_Scripts`,否则 Visual Studio 构建/INSTALL 后运行目录里不会出现该脚本。 ## 7. 开发步骤 ### 阶段 A0:FCStd 设备模板制作 目标: - 用户可以导入 STEP / STP / STE 几何模型。 - 用户可以给模型添加带电气语义的端子 LCS。 - 模板保存为 `.FCStd` 后,可以脱离当前工程复用。 实现建议: - 新增 `TemplateAuthoring.py`。 - 提供最小命令: - `QET_Template_AddTerminal` - `QET_Template_SaveAsFCStd` - 第一版端子位置可以通过用户选择对象/点位后的三维坐标生成。 - 第一版端子方向默认使用单位旋转,后续再补出线方向编辑。 模板端子属性: - `Role = "Terminal"` - `CanWire = true` - `QetTemplateSlotName` - `QetTerminalLabel` - `QetTerminalType` 验收: - 打开一个普通 STEP 模型。 - 添加 `P1`、`P2` 两个端子。 - 保存为 `电流互感器.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.json` 的 `terminals` 为设备创建端子对象。 - 每个端子都能在 FreeCAD 场景中被看到、被选中。 实现建议: - 新增 `TerminalImport.py`。 - 按 `element_uuid` 或 `instance_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`。 - 从文档中扫描: - 设备组 - 端子对象 - 手动连线对象 - 第一版正式回写结构遵守现有协议最小集: ```json { "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_instance`、`project_3d_space_object`、`project_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. 开发结果记录要求 每次开发完成后,都要在本文档末尾追加一条开发记录,至少包含: - 时间 - 本次实现了什么 - 验证结果 - 未完成或待跟进事项 建议格式: ```text - 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`。 ```