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

42 KiB

FreeCAD 二次开发笔记(中文)

本文档记录基于当前 FreeCAD 1.1.1 Windows 开发环境开展二次开发时,一些适合先固定下来的设计约定。目标不是一次把系统做满,而是先把“哪些对象能被识别、哪些对象能参与后续能力”这件事做稳。

1. 文档范围

当前先记录一个很具体、也很关键的方向:

  • 在 FreeCAD 原生对象体系中,如何表示“端子/接线柱”
  • 如何让系统先把端子识别出来
  • 如何在后期再给端子绑定电气属性
  • 如何约束“只有端子才能进行 3D 接线”

这份文档偏架构和对象设计,不要求现在就改源码实现全部功能。

2. 结论先说

如果目标是后续做“带电气语义的 3D 接线”,第一版最推荐的约定是:

  • 端子使用 Datum CoordinateSystemLCS本地坐标系表示
  • LCS 原点表示连接点位置
  • LCS 某一根轴表示出线方向
  • 只有被明确标记为端子的 LCS才允许参与 3D 接线

也就是说,第一版可以直接把:

端子 = 被标记过的 LCS

作为系统约定。

3. 为什么优先选 LCS而不是普通点或顶点

FreeCAD 里可以拿很多东西表示“一个点”,但如果后面要做真正可识别、可约束、可扩展的端子,Datum CoordinateSystem 比普通几何点更合适。

推荐 LCS 的原因:

  • 有明确的位置
  • 有明确的方向
  • 可以附着到面、边、孔位或其他基准对象
  • 更适合承载后续业务语义
  • 更适合做 3D 接线的起点/终点

不推荐直接使用几何顶点作为端子,主要原因是:

  • 拓扑命名不稳定,模型变化后引用容易失效
  • 顶点只有位置,没有天然方向
  • 顶点不适合作为长期挂载业务属性的对象
  • 后期做规则校验和交互约束会比较难

如果只是做纯几何辅助点,Datum Point 也能用;但只要目标里包含“出线方向”或“只有端子才能接线”,优先选 LCS。

4. 原生 FreeCAD 能做什么,不能直接做什么

FreeCAD 原生能力足够支持下面这些事:

  • 创建可定位、可附着的基准对象
  • 给对象增加属性
  • 建立对象之间的引用关系
  • 用 Python/C++ 扩展命令、对象和工作流

但 FreeCAD 原生并没有现成的 ECAD 级对象体系,例如:

  • 原生 Terminal
  • 原生 Pin
  • 原生 Net
  • 原生“端子之间自动形成电气连接关系”的对象模型

所以比较现实的路线不是“找到现成电气端子对象”,而是:

  1. 先选一个原生对象来承载几何定位
  2. 再通过类型或属性,把它定义成“端子”
  3. 最后在扩展层实现端子的电气语义和接线规则

5. 端子的识别策略

5.1 推荐的过渡方案

第一阶段最实用的做法是:

  • 底层对象类型:Datum CoordinateSystem
  • 业务标记属性:Role = "Terminal"

这样后续任何工具在扫描文档时,都可以用统一规则识别端子:

  1. 对象必须是 LCS
  2. 对象必须带有 Role
  3. Role 的值必须是 "Terminal"

这个方案的好处是:

  • 不需要一开始就引入新的底层对象类型
  • 不需要修改 FreeCAD 原生数据结构
  • 对后续升级成自定义对象也兼容

5.2 后续更干净的方案

如果后面需求稳定了,可以把过渡方案升级成真正的自定义对象类型,例如:

  • MyElectrical::Terminal

到那时,工具就不必依赖字符串属性判断,而可以直接按对象类型判断。这个方案更干净,但适合在第一版规则已经验证稳定之后再做。

6. 推荐的最小属性集合

如果第一版就要为后续电气扩展留好口子,建议端子对象至少预留下面这些属性:

  • Role:固定写 "Terminal"
  • TerminalId:端子唯一标识
  • TerminalName:端子显示名
  • CanWire:是否允许参与 3D 接线
  • DirectionAxis:约定使用哪根局部轴作为默认出线方向

如果准备继续往电气语义扩展,可以继续补:

  • NetName
  • VoltageClass
  • CurrentLimit
  • SignalType
  • ConnectorId
  • GroupName

第一版不一定要全部做完,但建议命名先统一,不然后面会很难兼容旧数据。

7. 推荐的对象分层

为了让系统后面好扩展,建议把端子拆成两层理解。

7.1 几何层

由 FreeCAD 原生对象负责:

  • 位置
  • 朝向
  • 附着关系
  • 在三维场景中的显示

这里推荐继续使用 LCS。

7.2 电气层

由二次开发逻辑负责:

  • 端子编号
  • 网络名
  • 电压等级
  • 电流等级
  • 信号类型
  • 可连接规则
  • 当前连接到了哪些对象

这样做的好处是,几何层保持稳定,电气层可以渐进增强。

8. “只有端子才能 3D 接线”的实现约定

如果要做 3D 接线,建议从第一版开始就把选择规则收紧,而不是允许任意点都参与接线。

推荐规则:

  • 3D 接线命令的起点必须是端子
  • 3D 接线命令的终点必须是端子
  • 普通顶点、普通草图点、普通边端点不允许直接接线

也就是:

接线工具只接受“被识别为 Terminal 的 LCS 对象”

这样做的收益很直接:

  • 交互更稳定
  • 业务语义更清楚
  • 后期规则校验更容易加
  • 不会把“几何上的点”和“电气上的连接点”混在一起

9. 推荐的第一版判断规则

后续插件或命令在判断一个对象能不能参与 3D 接线时,可以采用下面这组规则:

  1. 对象类型是 Datum CoordinateSystem
  2. 对象具有 Role 属性
  3. Role == "Terminal"
  4. CanWire == true(如果启用这个属性)

如果以上条件不满足,就不允许作为接线端点。

这套规则比“靠对象名称前缀判断”稳得多。名称前缀可以保留为辅助约定,但不建议把它作为唯一依据。

10. 是否需要专门的端子分组

可以有,但建议把它当辅助信息,不要当唯一判定条件。

例如可以建立一个组:

  • ElectricalTerminals

然后把所有端子对象放进去。这样做有两个好处:

  • 方便用户浏览和管理
  • 方便批量检查端子

但真正的业务判断,仍建议以“对象类型 + 属性标记”为准。

11. 一个适合落地的第一版约定

如果现在就要开始做,并且希望后期演进成本低,建议我们先统一以下约定:

  • 所有端子都使用 LCS 创建
  • 所有端子都必须带 Role = "Terminal"
  • 所有端子都应具有稳定的 TerminalId
  • 如果对象没有被标记为端子,就不能参与 3D 接线
  • 接线工具只能选择端子对象作为端点

这一版先解决“识别”和“约束”的问题,后面再逐步加:

  • 电压/电流/信号属性
  • 网络关系
  • 自动路由
  • 连接合法性检查
  • 端子表和报表输出

12. 后续扩展建议

等第一版跑顺之后,可以继续往下面这些方向扩展:

  • 自定义对象类型:把属性式端子升级为真正的 Terminal 对象
  • 自定义视图提供器:让端子在三维视图中更醒目
  • 连接关系对象:显式记录“哪个端子连到哪个端子”
  • 规则检查器:检查悬空端子、重复连接、等级不匹配
  • 线束/线缆语义:把单根连接升级为可管理的线束对象

13. 当前推荐结论

在当前 FreeCAD 二次开发阶段,最适合作为“电气端子”的原生对象是:

  • Datum CoordinateSystemLCS

最适合作为第一版识别规则的是:

  • LCS + Role="Terminal"

最适合作为 3D 接线准入规则的是:

  • 只有被标记为端子的 LCS 才能作为接线端点

这个方案不要求现在就修改 FreeCAD 原生源码,但已经足够支撑后续做一个结构清晰、规则明确的电气端子与 3D 接线系统。

13.1 FCStd 设备模板作为正式资产

对于从厂家、网络或已有资源库拿到的 .step.stp.ste 模型,不建议把它们直接当作最终电气设备模板。原因是 STEP 系列文件主要表达几何,不能可靠保存 FreeCAD LCS、动态属性和二次开发语义。

当前推荐的正式资产流程是:

STEP / STP / STE 几何模型
  -> FreeCAD 模板制作
  -> 添加 LCS 端子
  -> 写入端子槽位语义
  -> 保存为 FCStd 设备模板

这样得到的 .FCStd 才是后续工程复用、交付给其他人、放入设备资源库的主文件。

模板端子是“通用槽位”,不是“某个工程中的端子实例”。例如电流互感器模板中可以保存:

Terminal_P1
Terminal_P2

这些对象表示模型上 P1/P2 的真实接线位置和方向,但不保存某个工程里的 terminal_uuid。项目导入时FreeCADExchange 再根据 2d_to_3d.json 把工程端子 UUID 绑定到模板槽位上。

13.2 A 方案下 zwl 与 FreeCAD 的文件职责

A 方案要求 D:\code\zwl 支持 .FCStd 作为设备 3D 资产格式,但不要求 zwl 解析 .FCStd 文件。

职责划分如下:

  • zwl/QET选择 .FCStd、复制到工程资产目录、保存到 device_3d_asset、导出 resolved_model_path
  • FreeCADExchange导入 .FCStd、扫描模板内 Role="Terminal" 的 LCS、创建工程端子对象、保存 3D 装配和手动连线。

也就是说,.FCStd 内部的端子、LCS、动态属性和装配结构都是 FreeCAD 侧语义zwl 只把 .FCStd 当作一种可流转的 3D 资源文件。

详细设计见:

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

13.3 面向 CAD 人员的模板制作交互

Python 控制台方式只作为开发验证手段,不能作为正式 CAD 工作流。A 方案第一版需要补一个 FreeCAD 任务面板,把端子制作流程封装成按钮和输入框。

推荐交互:

  1. 用户打开 STEP / STP / STE 模型。
  2. 打开“设备模板端子制作”面板。
  3. 在端子名输入框填写 P1
  4. 用鼠标选中模型上的端子位置。
  5. 点击“添加端子”。
  6. 重复添加 P2 等端子。
  7. 点击“校验端子”。
  8. 点击“保存为 FCStd”。

面板背后仍然使用 LCS 作为端子对象,仍然写入:

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

这样既保持当前技术路线稳定,又让非开发人员可以直接使用。

14. 电气柜与设备装配

除了端子与接线,电气柜场景通常还会遇到另一个基础问题:

  • 是否可以把多个设备模型装配到一个柜体模型中

对于这一点,当前 FreeCAD 原生能力是支持的。

14.1 原生是否支持多设备装配

支持。

当前 FreeCAD 已经提供原生 Assembly 工作台,可以用于:

  • 创建装配
  • 插入外部或已有零部件
  • 建立装配关系
  • 求解装配位置
  • 管理装配层级

对于“电气柜 + 多个设备”的典型场景,原生 FreeCAD 可以承担:

  • 柜体作为总装配基准
  • 断路器、继电器、电源、端子排等设备作为子部件
  • 将多个设备模型放入同一个柜体模型
  • 调整设备的位置和姿态
  • 维护装配结构

也就是说从几何装配角度看FreeCAD 原生已经具备“把很多设备装进一个电气柜”的能力。

14.2 原生能力更适合解决什么问题

在电气柜场景里,原生装配更适合负责下面这些内容:

  • 设备模型复用
  • 设备插入与摆放
  • 设备姿态与空间位置控制
  • 装配层级组织
  • 柜体与设备之间的几何关系

它比较像一个可靠的三维装配底座。

14.3 原生不会直接替代的电气语义

虽然原生装配能做几何放置,但它并不会天然提供完整的电气柜业务语义,例如:

  • 自动识别“这是导轨设备”或“这是面板设备”
  • 自动判断安装孔位、导轨、槽位是否符合某类电器安装规则
  • 自动完成电气设备布置规则
  • 自动识别设备的电气连接关系
  • 自动限制哪些点能接线、哪些设备能互连

因此,比较现实的路线依然是:

  1. 使用原生装配能力处理几何放置
  2. 在二次开发层补充设备、电气、安装规则等业务语义

14.4 推荐的装配思路

如果目标是后续做“电气柜设备布置 + 端子识别 + 3D 接线”,建议按下面这条路线走:

  1. 柜体模型作为总装配对象
  2. 各类设备模型作为子部件插入装配
  3. 使用 App::Link 或装配中的链接对象复用设备模型
  4. 使用 Placement、LCS 和装配约束管理设备位置
  5. 在后续扩展层再给设备补安装语义和电气语义

这样做的优点是:

  • 几何装配先跑通
  • 设备复用能力强
  • 不会把电气逻辑硬塞进几何底层
  • 后续接线与设备属性扩展更容易接上

14.5 为什么推荐继续使用 LCS 作为安装基准

在装配场景里LCS 依然很重要。

推荐做法是:

  • 柜体上定义安装基准 LCS
  • 设备上定义安装基准 LCS
  • 装配时优先让“设备安装 LCS”对齐“柜体安装 LCS”

这样做有几个明显好处:

  • 设备摆放规则更清楚
  • 安装方向更明确
  • 后续端子、接线点、设备朝向可以共用同一套基准体系
  • 从“设备装配”过渡到“设备接线”会自然很多

14.6 当前推荐结论

对于“电气柜中放置多个设备模型”这个需求,当前推荐结论如下:

  • 原生 FreeCAD 可以做多设备装配
  • 原生 Assembly 工作台足够承担柜体与设备的几何装配任务
  • 原生装配适合作为电气柜二次开发的三维底座
  • 电气设备类型、安装规则、接线规则仍建议放在二次开发层实现

如果后续要把这条路线继续收敛成统一约定,可以先把系统拆成下面两层:

  • 装配层:负责柜体、设备、位置、方向、层级
  • 电气层:负责设备属性、端子属性、连线规则、网络关系

这样从模型装配到电气语义的演进路径会比较清楚,也更适合逐步落地。

15. 2D 与 3D 协同的总体思路

当前系统并不是“从零做一个既懂 2D 又懂 3D 的全新电气 CAD”而是更适合拆成两个真相源

  • 2D 软件(D:\project\MingTuCAD\qelectrotech)负责电气语义
  • 3D 软件(D:\project\LightWork3D\FreeCAD)负责三维装配、空间位置与走线表现

推荐原则:

  • 2D 继续作为设备、符号、端子、网络、导线关系的主数据源
  • 3D 不重复发明电气语义,而是读取并绑定 2D 已有语义
  • 2D 与 3D 通过稳定标识进行绑定,而不是靠显示名称猜测

一句话概括:

2D 是“电气真相源”3D 是“空间真相源”。

16. 为什么优先走数据库绑定,而不是重新解析 XML

从 2D 代码看,项目文件虽然是 XML 体系,但 2D/3D 协同所需的数据已经被整理到本地数据库和专用仓储层。

已经存在的关键入口包括:

  • 项目运行库表结构
    projectlocaldatabase.cpp
  • 3D 绑定仓储
    ThreeDAssemblyRepository.cpp
  • 3D 绑定同步服务
    ThreeDAssemblySyncService.cpp

因此当前推荐:

  • 不把 XML 作为首选交换接口
  • 优先复用 2D 现有 SQLite/运行库表和同步服务

这样做的好处:

  • 标识字段已经存在
  • 2D 端子与导线关系已经能稳定落库
  • 3D 侧只需要读写明确的绑定表,不必重新推断图纸语义

17. 2D 侧已经具备的关键数据

17.1 设备级数据

设备元数据里已经存在与 3D 相关的字段:

  • parts_3d
  • layout_2d_diagram

对应代码:

  • DeviceMeta.h
  • deviceeditor.cpp

这意味着 2D 侧已经能表达:

  • 设备使用哪个 3D 资产
  • 设备关联哪个 2D 布局图

17.2 设备端子数据

设备端子并不是简单文本,而是已经有结构化表:

  • device_circuit_terminals

表中已存字段包括:

  • tag
  • direction
  • type_id
  • max_wire
  • mnemonic
  • usage
  • max_sec
  • min_sec

对应代码:

  • deviceterminaleditor.cpp
  • deviceterminaleditor.cpp

这说明 2D 侧已经有“端子属性”基础,不需要在 3D 里重新手填。

17.3 2D/3D 绑定表

项目本地库已经定义了下面这些表:

  • project_3d_scene_instance
  • project_3d_space_object
  • project_2d3d_link
  • project_2d3d_symbol_binding
  • project_2d3d_terminal_binding
  • start_end_terminal_matches

对应代码:

  • projectlocaldatabase.cpp
  • projectlocaldatabase.cpp
  • projectlocaldatabase.cpp
  • projectlocaldatabase.cpp
  • projectlocaldatabase.cpp

这些表已经非常接近最终所需接口。

17.4 2D 已经在生成端子绑定

ThreeDAssemblySyncService 已经会为单个 2D 元件生成端子绑定数据,核心字段包括:

  • elementUuid
  • terminalUuid
  • symbolTerminal
  • instanceId
  • terminalKey
  • connectionPointKey
  • wireLabel
  • netId

对应代码:

  • ThreeDAssemblySyncService.cpp
  • ThreeDAssemblySyncService.cpp

这意味着“2D 端子 -> 3D 端子”的映射字段体系,其实已经具备。

18. 推荐的数据传递方案

18.1 设备级绑定

设备级建议用 project_2d3d_symbol_binding 表表达:

  • project_uuid
  • source_diagram_uuid
  • scene_diagram_uuid
  • element_uuid
  • instance_id
  • device_id
  • display_tag
  • asset_uri
  • host_binding_mode
  • host_object_id
  • host_object_type
  • extra_json

这一层解决的问题是:

  • 2D 中某个设备实例,对应 3D 中哪个设备实例
  • 这个设备实例使用哪个 3D 资源
  • 它被装配到哪个宿主对象上

18.2 端子级绑定

端子级建议用 project_2d3d_terminal_binding 表表达:

  • project_uuid
  • source_diagram_uuid
  • scene_diagram_uuid
  • element_uuid
  • terminal_uuid
  • binding_key
  • symbol_terminal
  • instance_id
  • terminal_key
  • connection_point_key
  • wire_label
  • net_id
  • conductor_uuid
  • extra_json

这一层解决的问题是:

  • 2D 里的哪个端子,对应 3D 里的哪个连接点
  • 当前端子属于哪个设备实例
  • 当前端子所在网络、线号、导线标识是什么

18.3 设备与端子的主键约定

建议后续统一按下面这组键思考:

  • 设备主键:element_uuid + instance_id
  • 端子主键:terminal_uuidbinding_key
  • 3D 连接点主键:connection_point_key

不要依赖:

  • 显示名称
  • 标签文字
  • 树节点名称

19. 推荐的数据流

19.1 从 2D 到 3D

推荐主流程:

  1. 2D 编辑设备、端子、导线和网络
  2. 2D 把设备级绑定写入 project_2d3d_symbol_binding
  3. 2D 把端子级绑定写入 project_2d3d_terminal_binding
  4. FreeCAD 插件读取这些表
  5. FreeCAD 创建/更新 3D 设备实例、3D 端子对象和连接点绑定

19.2 从 3D 回写到 2D

推荐回写内容:

  1. 设备实例的三维位姿写入 project_3d_scene_instance
  2. 宿主绑定信息写入 project_2d3d_symbol_binding
  3. 端子实际落到哪个连接点,写入 connection_point_key
  4. 后续若 3D 连线参与校验,可回写线束/路径附加信息到 extra_json

20. FreeCAD 侧的扩展基础

FreeCAD 这边不建议第一版就改 C++ 内核,先走 Python 工作台/插件路线更稳。

当前可直接复用的基础有:

  • FeaturePython:可创建自定义对象
    FeaturePython.h
  • App::LocalCoordinateSystem / Part::LocalCoordinateSystem:适合作为连接点
    Datums.h
  • 动态属性机制:可加 PropertyString / PropertyBool / PropertyLink / PropertyLinkList
    参考 ifctree.py

另外Assembly 对 LCS/Datum 本身也是认可的:

  • JointObject.py

21. 3D 端子的推荐定义

当前推荐把 3D 端子定义成:

一个带 LCS 的语义对象,而不是普通点

建议最小结构:

  • 一个 FeaturePython 端子对象,作为语义容器
  • 一个 Part::LocalCoordinateSystem,作为几何连接点
  • 一组绑定属性,映射到 2D 端子语义

21.1 端子对象最小属性建议

建议至少包含:

  • ProjectUuid
  • SourceDiagramUuid
  • ElementUuid
  • TerminalUuid
  • InstanceId
  • TerminalKey
  • ConnectionPointKey
  • SymbolTerminal
  • WireLabel
  • NetId
  • CanWire
  • Status
  • OwnerDevicePropertyLink

这样它就不再是“一个点”,而是一个:

  • 有几何位置
  • 有 2D 语义映射
  • 能参与接线规则

的端子对象。

21.2 LCS 的职责

LCS 只负责:

  • 连接点位置
  • 连接点姿态
  • 后续装配/连线的几何基准

LCS 不负责:

  • 保存全部电气语义
  • 保存网络拓扑

电气语义仍建议放在端子对象本身的属性里。

21.3 模板端子和工程端子的区别

后续 FreeCAD 二次开发中需要区分两类端子:

  1. 模板端子
  2. 工程端子

模板端子存在于设备模板 .FCStd 中,职责是描述“这个设备模型上哪里可以接线”。它只保存跨工程稳定的信息:

  • Role = "Terminal"
  • CanWire = true
  • QetTemplateSlotName
  • QetTerminalLabel
  • QetTerminalType
  • LCS 的位置和方向

模板端子不保存:

  • project_uuid
  • element_uuid
  • terminal_uuid
  • instance_id
  • 数据库绑定字段

工程端子存在于具体项目的 scene.FCStd 中,职责是描述“当前工程里的哪个 2D 端子绑定到了哪个 3D 连接点”。它由 FreeCADExchange 根据 2d_to_3d.json 生成或更新,才会保存:

  • QetProjectUuid
  • QetElementUuid
  • QetTerminalUuid
  • QetInstanceId
  • CanWire

因此FCStd 设备模板是可复用的电气几何资产,项目场景 FCStd 是某个工程的装配和接线结果。不要把两者的职责混在同一个对象里。

21.4 模板制作工具方向

为了让普通 STEP 模型变成可复用电气模板,建议在 FreeCADExchange Python 层增加模板制作工具,而不是修改 FreeCAD C++ 内核。

第一版工具目标:

  1. 导入 STEP / STP / STE 几何模型。
  2. 用户选择模型上的接线位置。
  3. 输入端子槽位名,例如 P1P2A1A2
  4. 自动创建 LCS。
  5. 自动写入模板端子属性。
  6. 保存为 .FCStd

第一版建议新增模块:

src/Mod/FreeCADExchange/TemplateAuthoring.py

该模块只负责设备模板制作,不负责项目导入、手动连线或数据库回写。

22. 3D 端子二开实施步骤

阶段 1读绑定不做自动路由

先完成:

  1. 在 FreeCAD 侧做一个独立工作台或插件
  2. 读取 project_2d3d_symbol_binding
  3. 读取 project_2d3d_terminal_binding
  4. 为每个 2D 设备创建一个 3D 设备实例
  5. 为每个 2D 端子创建一个 3D 端子对象 + LCS

这一阶段先验证:

  • 2D 设备能不能在 3D 找到对应设备
  • 2D 端子能不能在 3D 找到对应连接点

补充说明:

  • 项目端子创建前,优先读取 FCStd 设备模板中的 Role="Terminal" LCS。
  • 如果模板没有端子语义,才使用 sidecar 或 bbox fallback。
  • 长期目标是让常用设备都具备 FCStd 模板端子,而不是长期依赖 fallback。

阶段 2让 3D 连线只认端子对象

下一步完成:

  1. 3D 连线命令只允许选择端子对象
  2. 端子对象必须带 CanWire == true
  3. 连线命令优先读取:
    • TerminalUuid
    • TerminalKey
    • ConnectionPointKey
    • NetId

这一阶段的目标是:

  • 把“3D 接线点”升级成“可识别的电气端子”

阶段 3回写绑定状态

再往后完成:

  1. 将设备位姿写回 project_3d_scene_instance
  2. 将端子落点写回 project_2d3d_terminal_binding.connection_point_key
  3. 将宿主对象关系写回 project_2d3d_symbol_binding.host_object_id / host_object_type

这一阶段的目标是:

  • 让 2D 和 3D 之间形成真正稳定的双向绑定

阶段 4再考虑自动路由

只有在前 3 个阶段稳定之后,才建议继续做:

  • 沿机柜/线槽的自动路径
  • 端子与线槽的吸附规则
  • 空间避障
  • 线束与报表联动

23. 当前推荐结论

对于当前这两套代码,最推荐的路线不是:

  • 在 FreeCAD 里重新造一套电气数据库
  • 或在 3D 端重复维护设备和端子语义

而是:

  1. 继续让 QElectroTech 负责设备、端子、导线、网络等电气语义
  2. 继续使用其现有 project_2d3d_* 表和 ThreeDAssembly* 服务作为绑定入口
  3. 让 FreeCAD 读取绑定表,把端子做成“带 LCS 的语义对象”
  4. 让 3D 接线、装配、空间走线全部围绕这些绑定对象展开

一句话总结:

3D 端子不应只是几何点,而应是“有位置的 2D 电气语义映射节点”。

24. 阶段 A统一标识和数据口径

阶段 A 的目标不是先把 2D/3D 联动界面做出来,而是先把“双方到底在说谁”这件事说清楚。

如果这一阶段没有先定死,后面最容易出现的问题是:

  • 2D 里的设备实例和 3D 里的设备实例靠显示名硬猜
  • 端子编号改一次,绑定关系全部漂移
  • 同一个对象在数据库、3D 文档、界面显示里分别有三套名字
  • 回写数据时不知道应该更新哪一条记录

所以阶段 A 的交付物应该是:

  • 一份统一字段清单
  • 一份主键和唯一性约定
  • 一份“谁是主数据源”的边界说明
  • 一份最小同步记录样例

24.1 阶段 A 的范围

阶段 A 先不做下面这些事:

  • 不做 FreeCAD 端子对象代码
  • 不做自动装配
  • 不做自动路由
  • 不做 2D/3D 双向实时联动

阶段 A 只回答四个问题:

  1. 设备怎么唯一识别
  2. 端子怎么唯一识别
  3. 2D 和 3D 之间靠哪些字段绑定
  4. 哪些字段由 2D 负责,哪些字段由 3D 负责

24.2 总体原则

建议在阶段 A 固定下面四条原则:

  1. 电气语义真相源在 2D
  2. 空间位姿真相源在 3D
  3. 绑定靠稳定 ID不靠显示名称
  4. 一个字段只定义一个主责任方,避免双写冲突

也就是说:

  • 2D 负责定义“这是什么设备、这是什么端子、它属于哪个网络”
  • 3D 负责定义“这个设备装在哪里、这个端子在空间里具体在哪”

24.3 阶段 A 推荐统一字段

建议第一版统一下面这组字段名。后续无论走 SQLite、JSON、还是接口对象都尽量保持同名。

24.3.1 项目级字段

  • project_uuid
  • project_name
  • source_diagram_uuid
  • scene_diagram_uuid

含义建议:

  • project_uuid:整个项目唯一标识
  • project_name:项目显示名称,不参与绑定主键
  • source_diagram_uuid2D 原理图/来源图纸唯一标识
  • scene_diagram_uuid3D 场景或 3D 视图唯一标识

24.3.2 设备级字段

  • element_uuid
  • instance_id
  • device_id
  • display_tag
  • asset_uri
  • layout_2d_diagram
  • host_object_id
  • host_object_type

含义建议:

  • element_uuid2D 元件实例唯一标识
  • instance_id3D 设备实例唯一标识
  • device_id:设备类型或库元件标识
  • display_tag:界面显示标签,例如 QF1
  • asset_uri3D 资产路径或 3D 资源定位符
  • layout_2d_diagram2D 布局图标识
  • host_object_id3D 中的宿主对象标识,例如某导轨、安装板、柜体分区
  • host_object_type:宿主对象类型,例如 RailPanelCabinet

24.3.3 端子级字段

  • terminal_uuid
  • terminal_key
  • symbol_terminal
  • connection_point_key
  • terminal_direction
  • terminal_type
  • wire_label
  • net_id

含义建议:

  • terminal_uuid2D 端子实例唯一标识
  • terminal_key:设备内部端子业务键,例如 QF1:13
  • symbol_terminal:图纸显示端子号,例如 13
  • connection_point_key3D 连接点键,例如设备模型中的 cp_13
  • terminal_direction:端子方向/进出方向语义
  • terminal_type:端子类型语义
  • wire_label:当前线号/导线标签
  • net_id:网络唯一标识

24.4 推荐主键和唯一性约定

建议在阶段 A 先把“哪个字段必须稳定、哪个字段只是显示用途”彻底分开。

24.4.1 只做显示用途,不可作为绑定主键

下面这些字段不建议直接作为绑定主键:

  • project_name
  • display_tag
  • symbol_terminal
  • 任何中文名称
  • 任何模型树显示名称

原因很简单:

  • 这些字段可能会被人工修改
  • 它们有显示意义,但不一定有全局唯一性
  • 一旦改名,绑定关系会断

24.4.2 推荐的主键约定

建议按下面规则理解:

  • 设备绑定主键:project_uuid + element_uuid
  • 3D 设备实例主键:project_uuid + instance_id
  • 端子绑定主键:project_uuid + terminal_uuid
  • 3D 连接点主键:project_uuid + instance_id + connection_point_key

如果需要业务可读键,可以再保留:

  • terminal_key = <display_tag>:<symbol_terminal>

但这个可读键建议作为辅助字段,不代替 terminal_uuid

24.5 一对一 / 一对多关系约定

阶段 A 建议先把关系收紧,不要一开始就支持太多复杂映射。

第一版建议:

  • 一个 element_uuid 对应一个 3D instance_id
  • 一个 terminal_uuid 对应一个 3D 端子对象
  • 一个 3D 端子对象对应一个 connection_point_key

这意味着第一版先按最稳的模型来:

  • 一个 2D 设备实例,只落成一个 3D 设备实例
  • 一个 2D 端子,只绑定一个 3D 连接点

后续如果要支持:

  • 一符号多模型
  • 一端子多连接点
  • 跳线桥接

再在阶段 B/C 之后扩展,不建议在阶段 A 就放开。

24.6 2D 与 3D 的职责边界

为了避免双向覆盖,建议把字段责任按下面方式固定。

24.6.1 由 2D 负责的字段

  • project_uuid
  • source_diagram_uuid
  • element_uuid
  • device_id
  • display_tag
  • terminal_uuid
  • terminal_key
  • symbol_terminal
  • wire_label
  • net_id
  • 端子类型、端子方向、电气属性

也就是说2D 负责:

  • 设备是谁
  • 端子是谁
  • 设备和端子在电气上是什么意思
  • 它们属于哪个网络

24.6.2 由 3D 负责的字段

  • instance_id 对应的空间位姿
  • 宿主对象落点
  • 3D 端子具体几何位置
  • connection_point_key 对应的实际连接点落位
  • 柜内装配关系

也就是说3D 负责:

  • 它在柜体中装在哪里
  • 它挂在哪条导轨、哪块安装板上
  • 端子在三维空间里具体位于哪里

24.6.3 需要双方共同遵守但不能双写冲突的字段

  • asset_uri
  • instance_id
  • connection_point_key
  • host_object_id
  • host_object_type

建议策略:

  • 第一版由 2D 提供 asset_uri
  • 第一版由 3D 回写 instance_id / connection_point_key / host_object_*
  • 如果 2D 后续能预先规划 3D 实例 ID也要保证生成规则稳定且不会和 3D 重复造号

24.7 阶段 A 的推荐记录样例

下面给出两个“建议形态”的样例,目的是帮助阶段 A 对齐字段语义,不代表必须先改成 JSON 存储。

24.7.1 设备级绑定样例

{
  "project_uuid": "proj-001",
  "source_diagram_uuid": "sch-001",
  "element_uuid": "elem-a1b2c3",
  "instance_id": "inst-qf1",
  "device_id": "MCCB_1P_OF",
  "display_tag": "QF1",
  "asset_uri": "file:///assets/MCCB_1P_OF.step",
  "layout_2d_diagram": "layout-main",
  "host_object_id": "rail-01",
  "host_object_type": "Rail"
}

24.7.2 端子级绑定样例

{
  "project_uuid": "proj-001",
  "source_diagram_uuid": "sch-001",
  "element_uuid": "elem-a1b2c3",
  "terminal_uuid": "term-13",
  "instance_id": "inst-qf1",
  "terminal_key": "QF1:13",
  "symbol_terminal": "13",
  "connection_point_key": "cp_13",
  "terminal_direction": "bidirectional",
  "terminal_type": "power",
  "wire_label": "W-001",
  "net_id": "net-l1"
}

24.8 阶段 A 与现有 2D 代码的对应关系

当前 2D 代码里,阶段 A 所需的大部分基础其实已经存在。

24.8.1 设备 3D 资源字段

已有:

  • parts_3d
  • layout_2d_diagram

参考:

  • deviceeditor.cpp
  • DeviceMeta.h

24.8.2 端子属性字段

已有:

  • tag
  • direction
  • type_id
  • max_wire
  • mnemonic
  • usage
  • max_sec
  • min_sec

参考:

  • deviceterminaleditor.cpp
  • deviceterminaleditor.cpp

24.8.3 2D/3D 绑定表

已有:

  • project_3d_scene_instance
  • project_3d_space_object
  • project_2d3d_link
  • project_2d3d_symbol_binding
  • project_2d3d_terminal_binding
  • start_end_terminal_matches

参考:

  • projectlocaldatabase.cpp
  • projectlocaldatabase.cpp
  • projectlocaldatabase.cpp
  • projectlocaldatabase.cpp
  • projectlocaldatabase.cpp

所以阶段 A 的重点并不是“重新设计一套完全新的数据结构”,而是:

  • 统一字段命名
  • 统一主键规则
  • 明确哪些现有字段可以直接沿用
  • 明确哪些字段需要补充或规范化

24.9 阶段 A 的文档交付清单

建议阶段 A 至少完成下面这些文档性交付物:

  1. 《字段对照表》
  2. 《主键与唯一性约定》
  3. 《2D/3D 职责边界说明》
  4. 《设备级绑定样例》
  5. 《端子级绑定样例》

只有这几样都齐了,后面的代码实现才不容易边写边改字段语义。

24.10 阶段 A 的验收标准

阶段 A 完成的判断标准建议定成下面这些:

  1. 团队内部能明确回答:
    • 设备靠哪个字段唯一识别
    • 端子靠哪个字段唯一识别
    • 哪些字段只是显示用途
  2. 能给出一条设备绑定记录样例
  3. 能给出一条端子绑定记录样例
  4. 能明确指出每个关键字段由 2D 还是 3D 负责
  5. 不需要看代码猜字段含义,单看文档就能开始阶段 B

25. 2D -> 3D 字段对照表

下面这张表的目标不是一次覆盖所有未来字段,而是先把阶段 A 真正要对齐的核心字段固定下来。

建议使用原则:

  • 左侧 2D 字段 表示 QElectroTech 侧已有字段或推荐输出字段
  • 中间 3D 字段 表示 FreeCAD 侧对象属性、绑定对象属性或同步结构中的字段名
  • 责任方 表示该字段的主维护方
  • 备注 用于说明是否直接映射、是否仅显示、是否后续回写

25.1 项目级字段对照

2D 字段 3D 字段 责任方 备注
project_uuid ProjectUuid 2D 项目全局主键3D 只读并保存,不自行生成
project_name ProjectName 2D 仅显示用途,不作为绑定主键
source_diagram_uuid SourceDiagramUuid 2D 2D 来源图纸主键3D 端用于追溯来源
scene_diagram_uuid SceneDiagramUuid 3D 3D 场景主键,若 2D 侧已有对应字段可同步保存

25.2 设备级字段对照

2D 字段 3D 字段 责任方 备注
element_uuid ElementUuid 2D 2D 设备实例主键,设备绑定首要识别字段
instance_id InstanceId 3D 3D 设备实例主键,建议由 3D 创建后回写 2D
device_id DeviceId 2D 设备类型标识,可映射到元件库类型
display_tag DisplayTag 2D 显示名,例如 QF1,不可替代主键
parts_3d / asset_uri AssetUri 2D 3D 资产路径来源字段,第一版由 2D 提供
layout_2d_diagram Layout2DDiagram 2D 2D 布局来源标识3D 仅保存引用
host_object_id HostObjectId 3D 设备在 3D 中挂载到哪个宿主对象,例如导轨或安装板
host_object_type HostObjectType 3D 宿主对象类型,例如 RailPanelCabinet
pos_x / pos_y / pos_z(若 2D 侧后续引入) Placement.Base 3D 第一版建议位姿由 3D 主导2D 可只保存回写结果
rot_x / rot_y / rot_z(若 2D 侧后续引入) Placement.Rotation 3D 同上,作为 3D 位姿结果回写,不建议 2D 首发控制

25.3 端子级字段对照

2D 字段 3D 字段 责任方 备注
terminal_uuid TerminalUuid 2D 2D 端子实例主键,端子绑定核心标识
terminal_key TerminalKey 2D 业务可读键,例如 QF1:13,建议稳定生成
symbol_terminal SymbolTerminal 2D 图纸显示端子号,例如 13,仅显示和辅助检索
tag TerminalTag 2D 若 2D 端已有端子标签字段,可单独映射保留
direction TerminalDirection 2D 端子方向语义3D 用于规则判断,不直接决定整体路由
type_id TerminalTypeId 2D 端子类型枚举主键,便于后续规则扩展
mnemonic Mnemonic 2D 端子助记符,偏显示和检索用途
usage Usage 2D 端子用途说明,例如控制、电源、保护
max_wire MaxWire 2D 最大接线数,后续可用于 3D 规则检查
max_sec MaxSection 2D 最大线径/截面积,第一版可先只保存不执行校验
min_sec MinSection 2D 最小线径/截面积,第一版可先只保存不执行校验
connection_point_key ConnectionPointKey 3D 3D 连接点键,建议由 3D 端确定后回写 2D
wire_label WireLabel 2D 线号/导线标签3D 只读并附着到端子或导线对象
net_id NetId 2D 网络主键3D 连线对象应直接读取并保存
can_wire(若 2D 侧后续显式输出) CanWire 2D/3D 共同遵守 第一版默认可由 3D 端按端子对象类型推导,也可由 2D 显式下发

25.4 3D 端子与连接点扩展字段

这部分字段不一定在 2D 现有数据库里原生存在,但建议在 3D 侧先固定命名,便于后续回写和扩展。

2D 字段 3D 字段 责任方 备注
- OwnerDevice 3D FreeCAD 端子对象指向所属设备对象的链接属性
- LcsName 3D 端子几何基准的 LCS 名称,便于界面调试和对象检索
- Status 3D 3D 端子当前状态,例如 UnboundBoundConnected
extra_json(若已有或后续扩展) ExtraJson 双方约定 放临时扩展字段,但不建议承载主键语义

25.5 表级映射建议

为了让字段对照真正能落到代码里,建议阶段 A 同时固定“字段主要从哪张表来”。

2D 表/来源 对应 3D 对象/结构 责任方 备注
project_2d3d_symbol_binding 3D 设备实例绑定记录 2D 写入3D 读取/回写部分字段 设备级绑定主表
project_2d3d_terminal_binding 3D 端子对象绑定记录 2D 写入3D 回写 connection_point_key 端子级绑定主表
project_3d_scene_instance 3D 场景实例位姿记录 3D 位姿与宿主落位建议由 3D 主导
device_circuit_terminals 3D 端子语义属性来源 2D 类型、方向、线径、接线数等语义来源
parts_3d / layout_2d_diagram 3D 资产入口与布局引用 2D 第一版不建议 3D 自行猜资产路径

25.6 推荐命名规则

为了减少后续字段漂移,建议阶段 A 先固定这些命名规则:

  • 数据库存储字段保持 snake_case
  • FreeCAD 对象属性使用 PascalCase
  • 界面显示名允许中文,但不参与绑定
  • Uuid 后缀只用于真正全局唯一标识
  • Key 后缀用于业务键或连接点键
  • Id 后缀用于类型标识、实例标识或宿主标识

示例:

  • 数据库字段:terminal_uuid
  • FreeCAD 属性:TerminalUuid
  • 界面显示:端子 13

25.7 第一版建议严格区分的字段

下面这些字段看起来相近,但建议在阶段 A 就明确区分,避免后面混用:

25.7.1 terminal_uuidterminal_key

  • terminal_uuid:机器主键,稳定唯一
  • terminal_key:业务可读键,便于调试和跨界面识别

不建议只保留 terminal_key,因为显示编号或设备标签变化时它可能变化。

25.7.2 element_uuidinstance_id

  • element_uuid2D 设备实例身份
  • instance_id3D 设备实例身份

这两个字段不要互相替代。第一版即使它们暂时值相同,也建议语义上保持区分。

25.7.3 symbol_terminalconnection_point_key

  • symbol_terminal2D 图纸上的端子显示号
  • connection_point_key3D 模型内部连接点键

一个显示端子号理论上可以映射到一个具体连接点,但两者不是同一个概念。

25.8 字段对照表的使用方式

这张表建议在阶段 B/C 里直接作为开发输入使用:

  1. 2D 侧先检查哪些字段已经稳定存在
  2. 对不存在但必要的字段做补充或规范化
  3. 3D 侧严格按对照表创建对象属性
  4. 接口层不要临时发明新名字,优先复用这里的统一命名

也就是说,后续代码实现时应该尽量做到:

看字段对照表就能知道字段从哪来、落到哪去、该由谁维护。

24.11 阶段 A 结束后再进入代码

阶段 A 结束后,下一步再做代码时,建议顺序是:

  1. 先在 2D 侧把设备级绑定输出稳定下来
  2. 再在 3D 侧按 instance_id 落设备实例
  3. 设备级稳定后,再进入端子级绑定

也就是说,阶段 A 的价值就在于:

先把绑定语言说清楚,再开始真正的数据读写和对象创建。