# FreeCAD 二次开发笔记(中文) 本文档记录基于当前 FreeCAD 1.1.1 Windows 开发环境开展二次开发时,一些适合先固定下来的设计约定。目标不是一次把系统做满,而是先把“哪些对象能被识别、哪些对象能参与后续能力”这件事做稳。 ## 1. 文档范围 当前先记录一个很具体、也很关键的方向: - 在 FreeCAD 原生对象体系中,如何表示“端子/接线柱” - 如何让系统先把端子识别出来 - 如何在后期再给端子绑定电气属性 - 如何约束“只有端子才能进行 3D 接线” 这份文档偏架构和对象设计,不要求现在就改源码实现全部功能。 ## 2. 结论先说 如果目标是后续做“带电气语义的 3D 接线”,第一版最推荐的约定是: - 端子使用 `Datum CoordinateSystem`(LCS,本地坐标系)表示 - 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 CoordinateSystem`(LCS) 最适合作为第一版识别规则的是: - `LCS + Role="Terminal"` 最适合作为 3D 接线准入规则的是: - 只有被标记为端子的 LCS 才能作为接线端点 这个方案不要求现在就修改 FreeCAD 原生源码,但已经足够支撑后续做一个结构清晰、规则明确的电气端子与 3D 接线系统。 ## 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](D:/project/MingTuCAD/qelectrotech/sources/dataBase/projectlocaldatabase.cpp:364) - 3D 绑定仓储 [ThreeDAssemblyRepository.cpp](D:/project/MingTuCAD/qelectrotech/sources/ThreeD/ThreeDAssemblyRepository.cpp:300) - 3D 绑定同步服务 [ThreeDAssemblySyncService.cpp](D:/project/MingTuCAD/qelectrotech/sources/ThreeD/ThreeDAssemblySyncService.cpp:107) 因此当前推荐: - 不把 XML 作为首选交换接口 - 优先复用 2D 现有 SQLite/运行库表和同步服务 这样做的好处: - 标识字段已经存在 - 2D 端子与导线关系已经能稳定落库 - 3D 侧只需要读写明确的绑定表,不必重新推断图纸语义 ## 17. 2D 侧已经具备的关键数据 ### 17.1 设备级数据 设备元数据里已经存在与 3D 相关的字段: - `parts_3d` - `layout_2d_diagram` 对应代码: - [DeviceMeta.h](D:/project/MingTuCAD/qelectrotech/sources/DeviceManager/DeviceMeta.h:32) - [deviceeditor.cpp](D:/project/MingTuCAD/qelectrotech/sources/DeviceManager/deviceeditor.cpp:1484) 这意味着 2D 侧已经能表达: - 设备使用哪个 3D 资产 - 设备关联哪个 2D 布局图 ### 17.2 设备端子数据 设备端子并不是简单文本,而是已经有结构化表: - `device_circuit_terminals` 表中已存字段包括: - `tag` - `direction` - `type_id` - `max_wire` - `mnemonic` - `usage` - `max_sec` - `min_sec` 对应代码: - [deviceterminaleditor.cpp](D:/project/MingTuCAD/qelectrotech/sources/InsertFunction/deviceterminaleditor.cpp:94) - [deviceterminaleditor.cpp](D:/project/MingTuCAD/qelectrotech/sources/InsertFunction/deviceterminaleditor.cpp:214) 这说明 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](D:/project/MingTuCAD/qelectrotech/sources/dataBase/projectlocaldatabase.cpp:364) - [projectlocaldatabase.cpp](D:/project/MingTuCAD/qelectrotech/sources/dataBase/projectlocaldatabase.cpp:411) - [projectlocaldatabase.cpp](D:/project/MingTuCAD/qelectrotech/sources/dataBase/projectlocaldatabase.cpp:425) - [projectlocaldatabase.cpp](D:/project/MingTuCAD/qelectrotech/sources/dataBase/projectlocaldatabase.cpp:446) - [projectlocaldatabase.cpp](D:/project/MingTuCAD/qelectrotech/sources/dataBase/projectlocaldatabase.cpp:469) 这些表已经非常接近最终所需接口。 ### 17.4 2D 已经在生成端子绑定 `ThreeDAssemblySyncService` 已经会为单个 2D 元件生成端子绑定数据,核心字段包括: - `elementUuid` - `terminalUuid` - `symbolTerminal` - `instanceId` - `terminalKey` - `connectionPointKey` - `wireLabel` - `netId` 对应代码: - [ThreeDAssemblySyncService.cpp](D:/project/MingTuCAD/qelectrotech/sources/ThreeD/ThreeDAssemblySyncService.cpp:125) - [ThreeDAssemblySyncService.cpp](D:/project/MingTuCAD/qelectrotech/sources/ThreeD/ThreeDAssemblySyncService.cpp:159) 这意味着“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_uuid` 或 `binding_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](D:/project/LightWork3D/FreeCAD/src/App/FeaturePython.h:178) - `App::LocalCoordinateSystem / Part::LocalCoordinateSystem`:适合作为连接点 [Datums.h](D:/project/LightWork3D/FreeCAD/src/App/Datums.h:46) - 动态属性机制:可加 `PropertyString / PropertyBool / PropertyLink / PropertyLinkList` 参考 [ifctree.py](D:/project/LightWork3D/FreeCAD/src/Mod/BIM/utils/ifctree.py:100) 另外,Assembly 对 LCS/Datum 本身也是认可的: - [JointObject.py](D:/project/LightWork3D/FreeCAD/src/Mod/Assembly/JointObject.py:1449) ## 21. 3D 端子的推荐定义 当前推荐把 3D 端子定义成: > 一个带 LCS 的语义对象,而不是普通点 建议最小结构: - 一个 `FeaturePython` 端子对象,作为语义容器 - 一个 `Part::LocalCoordinateSystem`,作为几何连接点 - 一组绑定属性,映射到 2D 端子语义 ### 21.1 端子对象最小属性建议 建议至少包含: - `ProjectUuid` - `SourceDiagramUuid` - `ElementUuid` - `TerminalUuid` - `InstanceId` - `TerminalKey` - `ConnectionPointKey` - `SymbolTerminal` - `WireLabel` - `NetId` - `CanWire` - `Status` - `OwnerDevice`(`PropertyLink`) 这样它就不再是“一个点”,而是一个: - 有几何位置 - 有 2D 语义映射 - 能参与接线规则 的端子对象。 ### 21.2 LCS 的职责 LCS 只负责: - 连接点位置 - 连接点姿态 - 后续装配/连线的几何基准 LCS 不负责: - 保存全部电气语义 - 保存网络拓扑 电气语义仍建议放在端子对象本身的属性里。 ## 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 找到对应连接点 ### 阶段 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_uuid`:2D 原理图/来源图纸唯一标识 - `scene_diagram_uuid`:3D 场景或 3D 视图唯一标识 #### 24.3.2 设备级字段 - `element_uuid` - `instance_id` - `device_id` - `display_tag` - `asset_uri` - `layout_2d_diagram` - `host_object_id` - `host_object_type` 含义建议: - `element_uuid`:2D 元件实例唯一标识 - `instance_id`:3D 设备实例唯一标识 - `device_id`:设备类型或库元件标识 - `display_tag`:界面显示标签,例如 `QF1` - `asset_uri`:3D 资产路径或 3D 资源定位符 - `layout_2d_diagram`:2D 布局图标识 - `host_object_id`:3D 中的宿主对象标识,例如某导轨、安装板、柜体分区 - `host_object_type`:宿主对象类型,例如 `Rail`、`Panel`、`Cabinet` #### 24.3.3 端子级字段 - `terminal_uuid` - `terminal_key` - `symbol_terminal` - `connection_point_key` - `terminal_direction` - `terminal_type` - `wire_label` - `net_id` 含义建议: - `terminal_uuid`:2D 端子实例唯一标识 - `terminal_key`:设备内部端子业务键,例如 `QF1:13` - `symbol_terminal`:图纸显示端子号,例如 `13` - `connection_point_key`:3D 连接点键,例如设备模型中的 `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 = :` 但这个可读键建议作为辅助字段,不代替 `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 设备级绑定样例 ```json { "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 端子级绑定样例 ```json { "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](D:/project/MingTuCAD/qelectrotech/sources/DeviceManager/deviceeditor.cpp:1484) - [DeviceMeta.h](D:/project/MingTuCAD/qelectrotech/sources/DeviceManager/DeviceMeta.h:32) #### 24.8.2 端子属性字段 已有: - `tag` - `direction` - `type_id` - `max_wire` - `mnemonic` - `usage` - `max_sec` - `min_sec` 参考: - [deviceterminaleditor.cpp](D:/project/MingTuCAD/qelectrotech/sources/InsertFunction/deviceterminaleditor.cpp:94) - [deviceterminaleditor.cpp](D:/project/MingTuCAD/qelectrotech/sources/InsertFunction/deviceterminaleditor.cpp:214) #### 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](D:/project/MingTuCAD/qelectrotech/sources/dataBase/projectlocaldatabase.cpp:364) - [projectlocaldatabase.cpp](D:/project/MingTuCAD/qelectrotech/sources/dataBase/projectlocaldatabase.cpp:411) - [projectlocaldatabase.cpp](D:/project/MingTuCAD/qelectrotech/sources/dataBase/projectlocaldatabase.cpp:425) - [projectlocaldatabase.cpp](D:/project/MingTuCAD/qelectrotech/sources/dataBase/projectlocaldatabase.cpp:446) - [projectlocaldatabase.cpp](D:/project/MingTuCAD/qelectrotech/sources/dataBase/projectlocaldatabase.cpp:469) 所以阶段 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 | 宿主对象类型,例如 `Rail`、`Panel`、`Cabinet` | | `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 端子当前状态,例如 `Unbound`、`Bound`、`Connected` | | `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_uuid` 与 `terminal_key` - `terminal_uuid`:机器主键,稳定唯一 - `terminal_key`:业务可读键,便于调试和跨界面识别 不建议只保留 `terminal_key`,因为显示编号或设备标签变化时它可能变化。 #### 25.7.2 `element_uuid` 与 `instance_id` - `element_uuid`:2D 设备实例身份 - `instance_id`:3D 设备实例身份 这两个字段不要互相替代。第一版即使它们暂时值相同,也建议语义上保持区分。 #### 25.7.3 `symbol_terminal` 与 `connection_point_key` - `symbol_terminal`:2D 图纸上的端子显示号 - `connection_point_key`:3D 模型内部连接点键 一个显示端子号理论上可以映射到一个具体连接点,但两者不是同一个概念。 ### 25.8 字段对照表的使用方式 这张表建议在阶段 B/C 里直接作为开发输入使用: 1. 2D 侧先检查哪些字段已经稳定存在 2. 对不存在但必要的字段做补充或规范化 3. 3D 侧严格按对照表创建对象属性 4. 接口层不要临时发明新名字,优先复用这里的统一命名 也就是说,后续代码实现时应该尽量做到: > 看字段对照表就能知道字段从哪来、落到哪去、该由谁维护。 ### 24.11 阶段 A 结束后再进入代码 阶段 A 结束后,下一步再做代码时,建议顺序是: 1. 先在 2D 侧把设备级绑定输出稳定下来 2. 再在 3D 侧按 `instance_id` 落设备实例 3. 设备级稳定后,再进入端子级绑定 也就是说,阶段 A 的价值就在于: > 先把绑定语言说清楚,再开始真正的数据读写和对象创建。