18 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.pysrc/Mod/FreeCADExchange/DeviceImport.pysrc/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.cppsources/FreeCAD/FreeCADLaunchService.cpp
当前已经做到:
- 导出
<ProjectRoot>/.qet_freecad/2d_to_3d.json - 返回
<ProjectRoot>/.qet_freecad/scene.FCStd - 启动 FreeCAD,并设置:
QET_2D_TO_3D_JSONQET_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 只提供几何,不天然提供“哪个位置是端子”。
所以要实现端子显示和端子连线,本地模板还必须补一层端子语义。第一版采用下面的优先级:
- 正式方式:使用 FCStd 模板,在模板里提前放好 LCS 端子对象。
- 过渡方式:STEP + sidecar JSON,在同目录下保存端子槽位坐标。
- 验证方式:没有模板语义时,临时使用 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 = trueQetTemplateSlotName = "P1"/"P2"QetTerminalLabel = "P1"/"P2"QetTerminalType = "primary"等可选分类
模板端子不保存:
QetTerminalUuidQetInstanceIdproject_uuid- 任意工程内绑定字段
这些工程级字段由导入项目时的 2d_to_3d.json 和 FreeCADExchange 运行时补齐。这样同一个 FCStd 设备模板才能在多个工程、多台机器、多人之间复用。
3.2 模板制作工具目标
后续在 FreeCADExchange 中新增模板制作能力,目标是让用户不需要手工给 LCS 添加属性:
- 导入 STEP / STP / STE 几何模型。
- 在模型真实接线位置添加端子,例如
P1、P2。 - 自动创建 LCS,并写入模板端子语义。
- 保存为
.FCStd设备模板。 - 后续 LightWork3D 工程引用该
.FCStd后,自动识别模板端子并生成工程端子对象。
4. 为什么要先落地设备模板
这里的“设备模板”不是要求先把所有设备都建完,而是要先有一个稳定样板,证明端子显示和连线可以依附在真实设备上。
原因:
- 端子必须依附在设备实例上,不能只是空间中的孤立点。
- 端子连线需要稳定的起点、终点和出线方向。
- STEP 普通顶点不稳定,不适合作为端子主对象。
- 如果没有模板层,后续每种设备都要手工猜端子位置。
- 自动布线后续会依赖当前手动布线沉淀出的端子对象和路径对象。
因此第一版只需要选 1 到 2 个设备样板,例如断路器、端子排或继电器,把模板约定跑通。
5. 第一版对象模型
5.1 设备实例组
每个 2D 设备实例在 FreeCAD 中对应一个设备组。
建议对象:
QETExchangeDevices
QETDevice_<element_uuid>
imported model objects
QETTerminals
QETWires
设备组至少保存属性:
QetProjectUuidQetElementUuidQetInstanceIdQetResolvedModelPath
QetInstanceId 如果输入为空,由 FreeCAD 生成,并在保存回写时写入 3d_to_2d.json。
5.2 端子对象
端子使用 FreeCAD LCS 表示。推荐对象为:
Datum CoordinateSystem + Role="Terminal"
端子对象至少保存属性:
QetProjectUuidQetElementUuidQetTerminalUuidQetInstanceIdRole = "Terminal"CanWire = true
端子判断规则:
- 对象必须有
Role Role == "Terminal"- 对象必须有
QetTerminalUuid CanWire == true
第一版禁止把 terminal_key 或 connection_point_key 当作绑定主键。模板内部可以有槽位名,但最终业务识别只认 terminal_uuid。
5.3 手动连线对象
手动连线可以先用 FreeCAD 中的折线对象表示,例如 Draft Wire 或 Part 曲线。
连线对象至少保存属性:
QetProjectUuidQetStartTerminalUuidQetEndTerminalUuidQetStartInstanceIdQetEndInstanceIdRouteType = "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 模板
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.txt 的 FreeCADExchange_Scripts,否则 Visual Studio 构建/INSTALL 后运行目录里不会出现该脚本。
7. 开发步骤
阶段 A0:FCStd 设备模板制作
目标:
- 用户可以导入 STEP / STP / STE 几何模型。
- 用户可以给模型添加带电气语义的端子 LCS。
- 模板保存为
.FCStd后,可以脱离当前工程复用。
实现建议:
- 新增
TemplateAuthoring.py。 - 提供最小命令:
QET_Template_AddTerminalQET_Template_SaveAsFCStd
- 第一版端子位置可以通过用户选择对象/点位后的三维坐标生成。
- 第一版端子方向默认使用单位旋转,后续再补出线方向编辑。
模板端子属性:
Role = "Terminal"CanWire = trueQetTemplateSlotNameQetTerminalLabelQetTerminalType
验收:
- 打开一个普通 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子组。
端子位置策略:
- 如果 FCStd 模板已有
Role="Terminal"的 LCS,则优先复用模板 LCS。 - 如果有 sidecar JSON,则按 sidecar 坐标创建。
- 如果只有 STEP,则先按设备包围盒生成临时端子排列,用于打通流程。
临时端子排列只用于第一版验证,不作为长期物理端子定义。
验收:
- 控制台输出端子创建数量。
- 每个端子对象带
QetTerminalUuid。 - 选择端子时能确认它属于哪个设备实例。
阶段 C:端子手动连线
目标:
- 只能从端子连到端子。
- 普通几何点、边、面不能直接作为布线端点。
实现建议:
- 新增
ManualWiring.py。 - 提供函数:
is_terminal_object(obj)create_manual_wire(start_terminal, end_terminal, waypoints)list_manual_wires(doc)
- 第一版路径点可以先使用:
- 起点 LCS 原点
- 用户手动点选的中间点
- 终点 LCS 原点
如果暂时没有完整 UI,可先做最小命令:
- 用户选择两个端子对象。
- 执行命令生成一条直线或折线。
- 连线对象保存起止端子 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_instance、project_3d_space_object、project_2d3d_link。
8. 是否能达到图二效果
可以达到同方向效果,但需要分层理解。
图二效果包含:
- 本地模型导入
- 柜体和导轨等场景模型
- 设备装配
- FreeCAD 视图展示
- 模型树层级
本文档当前只保证:
- 本地模板能进入 FreeCAD
- 端子能显示
- 端子能手动连线
- FreeCAD 能保存并回写最小绑定
如果本地模板里包含机柜、导轨和设备,并且导入逻辑按组组织,就可以逐步做出图二那类 FreeCAD 场景。复杂 UI、自动排布、自动布线不属于当前个人任务第一阶段。
9. 首批模板建议
第一批不要铺太多设备。建议只选:
- 机柜或安装板:提供场景参考。
- DIN 导轨:提供设备摆放参考。
- 一个断路器或继电器:验证设备端子。
- 一个端子排:验证多端子排列和连线。
每个模板至少准备:
- 原始几何文件:
.STEP/.STP/.STE - 正式模板文件:
.FCStd - 模板内 LCS 端子:
Role="Terminal",带槽位名和接线资格 - 可选 sidecar:只作为过渡或校验,不作为正式交付优先方案
- 模板说明:原点、朝向、尺寸单位、端子数量
常用设备建议优先补齐 FCStd LCS,把端子位置从临时的 bbox fallback 提升为真实可用坐标。
10. 单人开发优先级
建议按下面顺序推进:
- 不改 UI,先把 Python 功能函数跑通。
- 不碰自动布线,先把端子对象和手动线对象持久化。
- 不扩旧数据库表,只生成
3d_to_2d.json。 - 不追求所有设备,先做一个样板设备跑通闭环。
- 不让 STEP 顶点直接参与布线,只认带属性的端子 LCS。
11. 最小验收场景
准备一个测试项目:
- 2 个设备
- 每个设备至少 2 个端子
- 每个设备绑定一个本地 STEP 或 FCStd 模型
验收步骤:
- 在 QET 点击打开 FreeCAD。
- QET 生成
2d_to_3d.json。 - FreeCAD 导入设备模型。
- FreeCAD 为端子创建 LCS。
- 用户选择两个端子创建手动连线。
- 保存 FreeCAD 文档。
- 生成
3d_to_2d.json。 - 关闭 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;已用单元测试验证端子语义写入和模板校验逻辑。