Skip to main content

· 连线端点(插槽)扩展评估:要不要为「母线 T 接」动引擎

本文是评估记录,不是实现计划。 起因:电力一次系统图(单线图)里,间隔是从母线任意位置 垂直引下的,而引擎现在只认 5 个固定插槽(T / R / B / L / C)——IED 的电力域包当前只能按 「母线中心 → 设备顶部」走线,出现水平绕行。本文回答三件事:改引擎的影响面有多大、 有没有不动引擎的做法、如果将来真要动引擎必须先满足什么。

结论速览

后续(2026-09-12,已完成):这条议题里的 getSerializableChildren() 钩子已经落地(引擎 1.4.8): 「既是复合组件、又是容器」的组件(流程图 / BPMN 节点与池)可以实现它返回真实子节点, 序列化只写这些子节点、反序列化时派生部件仍由构造函数重建。修复了 BPMN 池/泳道快照往返丢元素 的真实 bug(实测 3 个元素只剩 1 个)。默认行为逐字节不变(不实现的组件照旧跳过全部子节点)。

落地状态(2026-09-12):方案 A 已在 ice-entity-designer 0.0.33 落地 (电力域包:attachedBusId + 几何贴合 + 应用层位移传播 + 隐式等电位),引擎一行未改。 落地过程中实测到一条新约束,反过来印证了「先别动引擎」的判断: 复合组件的序列化契约不允许它同时当容器 —— hasDerivedChildren() === true 的组件, 序列化器会跳过它的全部子节点(Serializer.encodeRecursively),所以「把间隔收成母线的子节点」 这条路虽然运动学上成立(容器移动带动子节点,实测位移一致),但快照会丢设备(23 台掉成 15 台)。 结论:母线 T 接用「数据字段 + 几何判定」,既不碰引擎也能完整往返。若将来确实需要「复合组件当容器」 (例如 BPMN 泳道里再嵌一层带派生形状的容器),引擎应提供「只序列化真实子节点」的钩子 (如 getSerializableChildren(),默认返回 hasDerivedChildren() ? [] : childNodes)—— 这是一条独立的引擎议题,验收条件见 §5。

方案改动面风险能力上限建议
A. 应用层:「母线 = 容器 + 隐式 T 接」零引擎改动(全部在 IED 电力域包)极低覆盖一次图的母线接间隔(本轮的需求)先做这个
B. 引擎:参数化插槽 B@0.253 个引擎 switch + 1 个应用 switch + FlowPort 类型 + 交互插槽中(公共库、前向兼容缺口)沿边任意点连接(含跳线、跨线)⏳ 立独立议题,真要动就按 §5 的验收条件做
C. 引擎:端口提供者协议 component.getLinkAnchor(position)新增公开协议 + 全部调用点改走协议中高组件自定义任意锚点(3D、自定义形状…)❌ 现阶段不做

一句话:先把「母线接间隔」用容器语义解决(引擎一行不改),把「沿边任意点连接」留成独立的引擎议题—— 它不是不成立,而是它触碰的是所有下游工程共用的连线语义,必须单独排期、单独验收。

现状:端点是怎么定出来的(完整清单)

「插槽」不是引擎里一个集中的概念,而是一处字符串约定 + 三处 switch

#位置作用遇到未知 position 时的行为
1engine ICEPolyLine.followComponent()由宿主包围盒算出端点坐标default: 不动 → 端点是 [0,0]连线会跳到原点
2engine ICEPolyLine.dirVector()正交路由 / 贝塞尔控制点的出线方向default:[0,0](无方向偏移,绕线变差但不炸)
3engine ICELinkSlot.updatePosition()交互插槽(拖拽建线时的小圆点)的绘制位置default: → 不更新
4engine ICELinkSlotManager为可连接组件创建 5 个插槽、落点把 slot.state.position 写进 links交互上只提供这 5 个
5IED FlowDesigner.__slotPoint()应用层建线时的端点default: → 取中心点
6IED FlowPort 类型'T' | 'R' | 'B' | 'L' | 'C'类型层面的白名单
7序列化 / 快照 / 删除比对 / 跟随事件——只当它是字符串:不校验、不枚举,原样存取

这决定了影响面:真正「懂」插槽语义的只有 3 个引擎 switch + 1 个应用 switch; 其余链路(序列化、快照、removeLink 的相等比较、AFTER_MOVE 跟随)对值是宽容的。

⚠️ 但要注意两件事:

  1. 安全网很薄:全仓只有 2 个测试文件触到插槽语义 (tests/link/visio-bezier.test.tstests/graphic/ICEComponent.subtree-move.test.ts), 改动前必须补一批针对「端点解析 / 走线方向 / 交互插槽 / 序列化往返」的测试。
  2. 未知值的兜底是危险的followComponentdefault 会把端点算成 [0,0]。 老引擎读到新格式(比如 B@0.25)不会报错,而是静默把线画到原点——这类「不炸但画错」的兼容缺口, 比抛异常更难排查。

方案 A:母线 = 容器 + 隐式 T 接(零引擎改动)

做法:母线仍是一个宽矩形节点,但把接在这条母线上的间隔设备收成它的子节点ICEGroup.adoptChild()), 设备顶部引线直接落在母线底边上;电气语义由应用层定义——拓扑分析时把「父是母线」当成一条隐式连接, 不需要真画一段绕行线。

为什么这是「顺」的:

  • adoptChild() 是引擎已有 API(BPMN 池/泳道就用它):只搬子树、不销毁,坐标由调用方换算;
  • 容器移动带动子节点是引擎已验证的能力(BPMN 那条 e2e:拖动池,泳道与里面的节点一起走);
  • 视觉上,间隔的引线正好从母线垂直落下——和真实一次接线图的画法一致,比「在母线上打点再连线」更干净;
  • 电气上「同一条母线上的所有间隔等电位」本来就是母线的定义,用父容器表达语义是准确的。

代价(都是应用层工作,不碰引擎):

  • 电力域包要新增:attachToBus(device, bus)(换算坐标 + adoptChild)、拖到母线附近自动吸附、 拓扑里把「父容器」当隐式边、校验里区分「用导体连接的设备」与「挂在母线上的设备」;
  • 母线节点变矮(10px 左右)时,点选/拖拽的命中区域偏小 → 应用层可以给母线加一点「命中 padding」 (或提供一个更粗的透明命中盒)。

可行性实测(本轮做的探针,跑完即删)

用 IED 的电力图元做了一次一次性验证(未提交代码):

检查项结果
adoptChild 后子设备世界坐标不变(按父坐标换算局部坐标)✅ 位移 0px
子设备上端点与母线底边中点水平对齐(即「垂直落下」)✅ 对齐误差 0px
拖动母线 (120, 40) → 子设备位移✅ 正好 (120, 40)

也就是说:方案 A 不需要引擎提供任何新能力

方案 B:引擎侧扩展插槽(评两个子选项)

B1 参数化插槽(推荐形态,如果要做)

position 支持 B@0.25(沿底边 25% 处)之类的形式,@ 缺省即 0.5 —— 与现有语义逐字兼容

改动面(全部可枚举):

  1. 新增 parseLinkPosition(position):把 'B' / 'B@0.25' 解析成 { side, ratio }, 未知输入回退到该边中点(顺手把 §1 里那个「跳到 [0,0]」的兜底修掉);
  2. followComponent() 用它算端点;dirVector() 只用 side;ICELinkSlot.updatePosition() 同步;
  3. IED FlowDesigner.__slotPoint()FlowPort 类型同步放宽('B@0.25' 用模板字面量类型收);
  4. 交互(可选):拖线时在边上按鼠标位置生成临时插槽 → 工作量比 1–3 大得多,可先不做。

前向兼容缺口:新数据(B@0.25)在旧版本引擎里会落到 default → 端点算成 [0,0]。 可选缓解:写入时按「引擎版本」降级为 5 个固定插槽;或先只做 @0.5 之类的等价形式。

B2 命名细分插槽(用户提的「多加几组插槽」)

TRBL 扩成 T1..Tn / B1..Bn 之类。它和 B1 摸的是同一批代码,但:

  • 一条母线可能有十几个间隔,档位要么开到很多(字符串丑、交互插槽满屏小圆点), 要么还是不够用;
  • 真正的需求是「任意位置」,B1 只比 B2 多一个除法,却把上限打开了。

结论:要么不做,要做就做 B1,不做 B2。

方案 C:端口提供者协议(暂不做)

让组件自己实现 getLinkAnchor(position),默认实现就是现在的 5 个 case。 这是最通用的形态(自定义形状、3D、饼图扇区等都能用),但它把「插槽语义」从引擎的 3 处 switch 变成一个公开扩展协议,要考虑:协议版本化、第三方实现的健壮性、序列化与协议的对齐。 在没有真实第三方需求之前,投入产出比不高。

如果将来要动引擎(B 或 C),必须先满足的验收条件

  1. 现有 5 个插槽语义逐字节不变:老数据、老交互、老示例的输出与现在一致;
  2. 兜底从「跳到 [0,0]」改成「回退到该边中点」:任何未知 position 都不允许把线画到原点;
  3. 三处 switch 收敛到一个解析函数,避免以后再漏一处;
  4. 补测试:端点解析 / 路由方向 / 交互插槽 / 序列化往返 / 未知值兜底;
  5. 全量门禁 + 可视化回归(引擎 npm run verify + npm run test:visual:ci)全绿;
  6. 三个下游(ice-entity-designer / ice-web-components / ice-chart)各跑一遍各自的门禁;
  7. 兼容矩阵:旧数据 → 新引擎新数据 → 旧引擎 两种组合都有明确的、可解释的行为。

建议的顺序

  1. 先用方案 A 把电力一次图的「母线接间隔」画对(本轮已实测可行,零引擎改动);
  2. 等真出现「沿母线任意点」以外的需求(跳线、跨线、非矩形母线的复杂接线),再把 B1 立成独立的引擎议题, 按 §5 的七条验收;
  3. C 留到有明确的第三方扩展需求时再评估。