# scada-2d —— 组态画板（builder 型，SVG 渲染）

> 工业组态/工艺流程图：159 种图元形状（含 26 种 2.5D 等轴测、批次 10 标注/装饰/换热站素材）、连线与流动/立体管道、材质光影、测点长表数据驱动、事件。
> 返回目录：[index.md](index.md)；全量选项字典（最权威）：[widget-schema.generated.json](../widget-schema.generated.json)。

## 7. scada-2d —— 组态画板（builder 型，SVG 渲染）

工业组态/工艺流程图：图元（泵/阀/罐/电机/传感器…）+ 连线（拓扑或自由线）+ 测点数据驱动（变色/液位/状态/数值文本）。默认尺寸 `[800,480]`；组态通常独占一页——把 `size` 设为整页、`position:[0,0]`。

节点结构（与 map-2d 同为 builder 型，子元素放 `pieces`，顺序=叠放顺序，靠后在上）：

```jsonc
{
  "uid": "w_scada", "type": "scada-2d",
  "options": {
    "position": [0, 0], "size": [1920, 1080],
    "scene-width": 1920, "scene-height": 1080,   // 场景逻辑尺寸（内容坐标系，含缩放适配）
    "theme": "tech-blue",                          // tech-blue科技蓝/aurora-green极光绿/deep-purple深空紫/amber-gold琥珀金/graphite石墨灰
    "appearance-3d": true,                         // 立体观感：图元渐变+柔和阴影（默认开）
    "depth-sort": false,                           // 2.5D 深度排序：按图元底边 y 自动排前后遮挡（地台/标区恒垫底），替代手工层级
    "grid": true, "snap": true, "grid-size": 24,
    "view-zoom": true, "view-pan": true,           // 预览视图交互（默认开）：滚轮以光标为中心缩放(0.2~10倍)/左中键拖拽平移；双击空白复位，不需要可关
    "scada-initialized": true,                     // ⚠️ 建议写 true：无图元时首挂会播种「罐→阀→泵」演示场景，写 true 禁播种
    "point-key-field": [["conn_pts", "tbl_pts", "键"]],   // 测点长表：键列
    "point-value-field": [["conn_pts", "tbl_pts", "值"]]  // 测点长表：值列
  },
  "pieces": [
    { "uid": "n_pump", "type": "scada-node", "options": { "shape": "pump", "geo-x": 200, "geo-y": 400, "geo-w": 96, "geo-h": 96,
        "node-text": "1#提升泵", "data-enabled": true, "data-key": "PUMP1_STATE", "state-by-value": true } },
    { "uid": "n_tank", "type": "scada-node", "options": { "shape": "tank", "geo-x": 500, "geo-y": 300, "geo-w": 140, "geo-h": 180,
        "data-enabled": true, "data-key": "TANK1_LEVEL", "value-min": 0, "value-max": 100, "value-unit": "%" } },
    { "uid": "l_1", "type": "scada-link", "options": { "from-node": "n_pump", "from-port": "e", "to-node": "n_tank", "to-port": "w",
        "router": "polyline", "flow": true, "flow-style": "flow" } }
  ]
}
```

### 7.1 测点数据模型（长表，最重要）

组态用「**长表**」喂数：一张表两列——**键列**（测点名，如 `PUMP1_STATE`）+ **值列**（当前值）。画板 options 绑 `point-key-field`/`point-value-field`（各恰 1 个三元组）；每个图元用 `data-key` 填自己关心的**键**（字符串，非三元组）。运行时值变化 → 图元自动重算液位/状态色/数值文本。实时演示配 `mock` 连接（`refreshInterval` + `wave/walk` 字段）。

图元数据驱动 options（都在 `data-enabled:true` 下生效）：

| option | 作用 |
|---|---|
| `data-key` | 测点键（匹配键列的值） |
| `value-min`/`value-max` | 量程（液位/高度映射的输入区间） |
| `show-value-text`/`value-decimals`/`value-unit` | 图元下方数值文本 |
| `state-by-value:true` + `state-rules` | **状态→颜色**：文本规则每行 `匹配=颜色`，如 `"run\|1=#22c55e\nstop\|0=#94a3b8\nfault=#ef4444"`（`\|` 分隔同义值；最后一行无值=兜底） |
| `fill-by-value:true` + `fill-th1/fill-th2` + `fill-low/mid/high` | **阈值→填充色**三段 |
| `height-by-value:true` + `height-in-min/max` + `height-out-min/max` | **值→高度**线性映射（柱状液位） |
| `mock-enabled:true` + `mock-value` | 编辑态无数据时的预览值（运行态失效） |
| `liquid-wave:true` | 液面叠加轻摆波浪线（`tank`/`iso-tank` 支持），液位驱动时更生动 |

液面/进度类形状（`tank`/`sphere-tank`/`tank-horizontal`/`silo`/`mixer`/`water-tank`/`artesian-well`/`collection-pit`/`floc-tank`/`distribution-well`/`level-bar`/`progress-bar`/`thermometer`/`gauge`/`battery` 等）自带 overlay：值按量程（`value-min`/`value-max`）归一化直接画液位/电量/进度/指针，无须额外配置。

### 7.2 图元 scada-node

- **形状 `shape`**（内置 159 种，可插件扩展）：
  - 通用：`rect`/`round-rect`/`circle`/`ellipse`/`diamond`/`triangle`
  - 管阀：`valve`(阀)/`ball-valve`(球阀)/`check-valve`(止回阀)/`gate-valve`(闸阀)/`butterfly-valve`(蝶阀)/`three-way-valve`(三通阀)/`globe-valve`(截止阀)/`safety-valve`(安全阀)/`solenoid-valve`(电磁阀)/`control-valve`(调节阀,带执行器)/`motor-valve`(电动阀,顶部电动执行机构)
  - 管路/管件：`pipe`(管段)/`pipe-elbow`(管弯头,端口 `w`/`s`)/`pipe-tee`(管三通,端口 `w`/`e`/`s`)/`pipe-cross`(四通)/`reducer`(异径管)/`flange`(法兰)
  - 设备：`pump`(泵,端口 `w`/`e`/`s`)/`motor`(电机)/`fan`(风机)/`conveyor`(输送带)/`heat-exchanger`(换热器,横置箱式)/`shell-tube-exchanger`(立式换热器,换热站管壳式：上下封头+两道法兰+四角侧接管,端口 `n`/`s`+`wn`/`ws`/`en`/`es`)/`plate-exchanger`(板式换热器)/`filter`(过滤器,端口 `n`/`s`)/`compressor`(压缩机)/`cooling-tower`(冷却塔,端口 `s`/`w`)/`boiler`(锅炉,含火焰,端口 `n`/`s`/`w`)/`turbine`(汽轮机)/`cyclone`(旋风分离器)/`generator`(发电机)/`wind-turbine`(风力发电机,叶片可旋)/`distillation-column`(精馏塔)/`hopper`(料斗)
  - 容器（值驱动液位）：`tank`(立式罐)/`sphere-tank`(球罐,曲面液位)/`tank-horizontal`(卧罐)/`silo`(料仓,锥体充注)/`mixer`(搅拌罐)/`water-tank`(矩形水箱,液位+波浪)
  - 仪表（值驱动）：`gauge`(仪表盘指针,端口 `s`)/`sensor`(传感器,端口 `s`)/`indicator`(指示灯)/`thermometer`(温度计,端口 `s`)/`flow-meter`(流量计)/`level-bar`(液位柱,端口 `s`)/`progress-bar`(进度条)/`transmitter`(变送器)/`digital-display`(数码显示)/`pilot-light`(信号灯)/`beacon`(声光报警器)/`pressure-sensor`(压力探头,端口 `s`)/`temperature-sensor`(温度探头,端口 `s`)/`flow-sensor`(流量探头)
  - 电气/电子：`knife-switch`/`toggle-switch`/`breaker`/`resistor`/`capacitor`/`inductor`/`diode`/`ground`(端口 `n`)/`battery`(蓄电池,电量分档变色)/`solar-panel`(光伏板,端口 `s`)/`transformer`(变压器)/`vfd`(变频器)/`fuse`(熔断器)/`control-cabinet`(控制柜)
  - 电力（单线图 SLD）：`busbar`(母线,含 `w`/`e`+`t1`/`t2`/`t3` 抽头)/`disconnector`(隔离开关)/`current-transformer`(电流互感器,端口 `w`/`e`/`s`)/`voltage-transformer`(电压互感器,端口 `n`/`s`)/`surge-arrester`(避雷器,端口 `n`/`s`)/`energy-meter`(电能表)/`load`(负荷,端口 `n`)/`grid-tower`(输电铁塔,端口 `s`)
  - 新能源：`inverter`(逆变器 DC→AC)/`fuel-cell`(燃料电池,值驱动输出条)/`ev-charger`(充电桩,值驱动电量,端口 `s`)
  - 油气：`pump-jack`(抽油机,端口 `s`)/`flare-stack`(火炬塔,值驱动火焰,端口 `s`/`w`)/`wellhead`(采油树,端口 `s`/`e`)
  - 水处理/给排水：`clarifier`(沉淀池,值驱动)/`aeration-tank`(曝气池,值驱动气泡,端口 `w`/`e`/`s`)/`bar-screen`(格栅)/`uv-reactor`(紫外消毒)/`dosing-pump`(加药泵,端口 `w`/`e`/`n`)/`digester`(消化池,值驱动液位,端口 `n`/`s`/`e`)/`deep-well-pump`(深井泵,立式井泵叶轮可旋,端口 `n`/`e`/`s`)/`artesian-well`(自流井,井房+三出水立管,值驱动井内水位,端口 `n`/`w`/`e`)/`collection-pit`(集水坑池,格栅盖板+半透液位)/`sludge-pump`(污泥泵,叶轮可旋)/`distribution-well`(配水井,值驱动)/`floc-tank`(折板絮凝池,值驱动)
  - 暖通 HVAC：`ahu`(空气处理机组)/`chiller`(冷水机组,端口 `w`/`e`/`s`)/`cooling-coil`(表冷器)/`damper`(风阀,值驱动开度/开红闭绿)/`fan-coil`(风机盘管,端口 `w`/`e`/`s`)/`duct`(风管)
  - 粒子特效（CSS 合成线程动画，本体透明、视觉由粒子承载；盒=发射/覆盖区域）：`fx-fire`(火焰)/`fx-smoke`(烟雾)/`fx-steam`(蒸汽)/`fx-spark`(火花)/`fx-bubbles`(气泡)
  - 天气：`fx-rain`(雨)/`fx-snow`(雪)/`fx-fog`(雾)/`fx-cloud`(流云)/`fx-sun`(晴日,旋转光芒)/`fx-lightning`(闪电,折线闪现)
  - 2.5D 等轴测（顶面提亮/侧面压暗三面体块，随填充联动换色；配 `depth-sort`/立体管道/材质搭 2.5D 场站）：`iso-cube`(立方体)/`iso-platform`(地台,深度排序恒垫底)/`iso-zone`(地面标区,恒垫底)/`iso-tank`(立式罐,值驱动液位+可配 `liquid-wave` 波浪)/`iso-tank-h`(卧式罐)/`iso-box`(设备箱)/`iso-conveyor`(传送带)/`iso-rack`(机柜)/`iso-building`(厂房)/`iso-cooling-tower`(冷却塔)/`iso-pump`(泵组撬块)/`iso-pipe-support`(管架)/`iso-wall`(围墙)/`iso-stack`(烟囱)/`iso-pool`(水池,值驱动液位+波浪)/`iso-solar`(光伏板)/`iso-transformer`(变压器)/`iso-ess-container`(储能集装箱,值驱动 SOC 电量条)/`iso-shelf`(立体货架)/`iso-container`(集装箱)/`iso-agv`(AGV小车,配「按值移动」做走位)/`iso-robot-arm`(机械臂)/`iso-machine`(加工机床)/`iso-truck`(货车)/`iso-tree`(树木,园区点缀)/`iso-road`(道路段,恒垫底)
  - 标注（钉在厂区/道路上，可配 `blink`+状态色）：`map-pin`(地图钉)/`marker-beacon`(立体信标)/`alarm-marker`(告警)/`people-marker`(人数)/`building-marker`(楼栋)/`area-marker`(面积)/`co2-marker`(气体)/`pm-marker`(颗粒物)
  - 大屏装饰（不依赖外链图片搭科技风框）：`panel-frame`(面板框)/`title-bar`(标题栏)/`subtitle-bar`(分区标题)/`side-trim`(侧饰条)/`bottom-trim`(底饰条)/`status-pill`(状态牌,可 `state-by-value`)
  - 自定义：`svg-path`（自定义 SVG 图元：单路径写 `svg-path`=路径 d + `svg-viewbox`；多元件写 `svg-els`=元素数组 JSON + `svg-viewbox`，编辑器「导入SVG」按内容自动二选一，保留描边/填充/渐变近似与 transform。`svg-symbol` 只是**旧工程的兼容别名**，运行时仍归一化为 `svg-path`）/`image`（**用户自备位图/矢量图当图元**，见下）
- **几何**：`geo-x`/`geo-y`/`geo-w`/`geo-h`/`geo-rotate`(°)——场景坐标，非 position/size；`iso-plane`(`floor`/`wall-l`/`wall-r`) 把平面图元斜切投影到等轴测地面/墙面（2.5D 拼装，对照图扑 HT 轴测变换）。
- **外观**：`fill`/`stroke`/`accent`/`text-color` **留空=跟随画板主题**（推荐，切主题整屏协调）；`material`(`metal`金属/`glass`玻璃/`plastic`塑料/`matte`哑光/`flat`纯色，默认''=跟随场景 `appearance-3d`)；`ground-shadow`(落地接触阴影，站立感)；`line-width`、`opacity`、`node-text`、`font-size`。
- **动画 `anim`**：`none`/`blink`(闪烁)/`pulse`(脉冲)/`spin`(旋转,风机/泵叶轮)/`shake`(故障抖动)/`bounce`(跳动)/`sway`(摆动,抽油机)/`flash`(急闪)/`breathe`(呼吸)/`float`(漂浮)/`wobble`(晃动)/`ripple`(波纹光效)/`glow`(光晕，强调色晕圈画在图元外侧)，配 `anim-speed`。`pump`/`fan`/`wind-turbine`/`sludge-pump` 为**部件级旋转**：`spin` 时仅叶轮/叶片转、机体静止。
- **特效**（仅 `fx-*` 粒子/天气形状生效）：`fx-density`(密度档 1~5，越大越密)、`fx-speed`(速度 0.1~10)、`fx-size`(粒子大小 0.2~5)、`fx-color`(留空=该特效默认色；火焰/晴日为固定暖色，仅微调余烬/光晕)、`fx-seed`(随机种子，改变换一组分布；同一图元布局稳定)。特效为环境动效，不占主题填充/描边、不依赖数据；用 `geo-w`/`geo-h` 控制发射/覆盖区域（雨/雪/雾/云盒即天空范围，火焰/烟雾/蒸汽/气泡自盒底发射）。
- **端口**：连线锚点，通用形状为四边中点 `n`/`e`/`s`/`w`；部分工业形状有专属端口（如 pipe 两端）。

**图片图元 `shape:"image"`**：把用户自己的设备照片、厂区底图、第三方图标当图元用，与内置符号同权——可接线（四边中点端口）、可旋转、可等轴测投影、可多状态/条件样式、可按值显隐与位移动画。字段：`image-url`(图片地址：素材库地址 `/api/files/...`、外链、或 dataURL) + `image-fit`(`contain` 等比适配留白 / `cover` 等比裁切铺满 / `stretch` 拉伸铺满可能变形，默认 `contain`)。未填 `image-url` 时画虚线占位框（画布上可见可选中）。

**按值换图**（组态经典的泵启停两张图、阀开关三态图）：`data-enabled:true` + `data-key` + `image-by-value:true` + `image-rules`（多行 `值1|值2=图片地址`，与状态色规则同语法；地址里的 `=` 不影响解析，只按首个 `=` 切分）。未命中或缺数据时回落到 `image-url`，测点断线不会让图元消失。

```json
{ "type": "scada-node", "options": {
  "shape": "image", "geo-x": 200, "geo-y": 120, "geo-w": 160, "geo-h": 120,
  "image-url": "/api/files/vp_resources/abc123/pump-stop.png", "image-fit": "contain",
  "data-enabled": true, "data-key": "pump1.state", "image-by-value": true,
  "image-rules": "run|1=/api/files/vp_resources/abc123/pump-run.png\nfault=/api/files/vp_resources/abc123/pump-fault.png"
} }
```

**精灵图动画 `sprite-*`**（序列帧：一张图里排布多帧，逐帧切换成动画——风机转/管道流/告警灯闪）：`sprite-enabled:true` + 网格 `sprite-cols`(列数,默认4)/`sprite-rows`(行数,默认1)/`sprite-frames`(总帧数，0=自动取 列×行) + `sprite-fps`(帧率 1~60,默认12) + `sprite-play`(播放,默认 true)/`sprite-reverse`(倒放)/`sprite-stop-frame`(停止时静止在第几帧,从0起)。帧按**行优先**编号（左→右、上→下）。⚠️ 单行/单列条带允许 `sprite-frames` 少于格数（末尾空格不播）；**网格排布（列行都>1）的帧数会被钳回 列×行**。每格恒等于图元盒，`image-fit` 在精灵模式下不生效（编辑器有「按帧比例适配盒子」按钮按单帧宽高比重摆盒）。

**按值起停动画**：`data-enabled:true` + `data-key` + `sprite-by-value:true` + `sprite-rules`（多行 `值1|值2=token`；token 为 `on`/`off`/`reverse`，可带冒号参数——`on:24`/`reverse:12` 是帧率，`off:3` 是停在第 3 帧）。未命中或缺数据时沿用静态设置。

**按值选帧（图像集）**：同一张精灵图当多态图用——`sprite-frame-by-value:true` + `sprite-frame-mode`（`rules` 按值集合 / `linear` 按量程均分）+ `sprite-frame-rules`（`normal|0=0` 正常第 0 张、`alarm|1=1` 报警第 1 张）或 `sprite-frame-in-min/max`。命中即静止在该帧；与「按值起停」可同时开，起停规则没表态时默认停在选中帧。

**命令式指定第几张**：不必造测点。交互动作 `scadaSetOption`（目标画板 + `piece-uid` + `option:"sprite-stop-frame"` + `value`）或 SDK `getScada(uid).setPieceOption(pieceUid, 'sprite-stop-frame', 1)`。写 `sprite-stop-frame` 会自动停播并停在那一帧；不要同时开 `sprite-by-value`，否则测点会把按钮改的起停盖掉。

图片图元的旋转/缩放/移动/动画与泵阀**同一套选项**（`geo-rotate` / `anim` / `rotate-by-value` / `move-by-value` / `scale-by-value`），不另开接口。

#### 2.5D 摆位的三条几何硬约束（校验器查不出，但一眼就丑）

用抠图素材拼等轴测场景时，「设备悬空 / 被拉扁 / 飘在地台外」几乎都出自这三条。写完跑 `node vjvp/scripts/check-scada-scene.mjs <工程.vpproj.json>` 逐条体检（素材目录优先取环境变量 `VP_SCADA_IMAGES`，否则按序探测当前目录下的 `pb_public/scada-images`、`scada-images`、`data/images`；探测不到时图片尺寸类检查自动跳过）。

1. **静态图片：盒宽高比 ≥ 图片宽高比**。`image-fit:contain` 只有在盒比图"更宽"时才按高铺满、底边与盒底齐平；盒更窄就上下留白居中，设备浮在盒中间。按值换图要取**所有候选图里最宽的**那张来算（如 `gate-valve-open` 0.686 与 `gate-valve-closed` 0.930 不同，盒得按 0.930 定）。
2. **精灵图：盒宽高比 = 单帧宽高比**。精灵图渲染用 `preserveAspectRatio="none"`，单帧被拉伸铺满盒，比例不对就是可见的变形。单帧比例 = `图宽/cols : 图高/rows`。
3. **`iso-platform` 是内接菱形，不是矩形**：顶点在盒四边中点，盒的四个角是空的。设备**底边中点**必须落在菱形内——判据 `|x-cx|/(w/2) + |y-cy|/(dh/2) ≤ 1`（`dh = 盒高 - 台厚`，台厚 = `min(盒高*0.22, 16)`）。一块 1380×300 的扁地台，左右两端根本没有可站的地面。

推荐写法：把设备按「**站立点(底边中点) + 高度**」落位，宽度由素材比例算出，而不是手填 `geo-w/geo-h`：

```ts
const width = Math.ceil(height * assetAspect)   // 向上取整，保证盒不比图窄
node(uid, name, 'image', Math.round(baseX - width / 2), baseY - height, width, height, { 'ground-shadow': true })
```

配套：地台取接近 2:1 的等轴测比例；工艺链按前后两排交错走（靠 `depth-sort` 自然遮挡），别排成一条横线——横线两端必然掉出菱形。完整范例见 `data/组态/20-生成图复原-化工储运罐区-科技蓝.vp.tsx`。

```json
{ "type": "scada-node", "options": {
  "shape": "image", "geo-x": 200, "geo-y": 120, "geo-w": 120, "geo-h": 120,
  "image-url": "/api/files/vp_resources/abc123/fan-8frames.png",
  "sprite-enabled": true, "sprite-cols": 8, "sprite-rows": 1, "sprite-fps": 16,
  "data-enabled": true, "data-key": "fan1.state", "sprite-by-value": true,
  "sprite-rules": "run|1=on:16\nstop|0=off:0\nfault=reverse:4"
} }
```

```json
{ "type": "scada-node", "options": {
  "shape": "image", "geo-x": 360, "geo-y": 120, "geo-w": 96, "geo-h": 96,
  "image-url": "/api/files/vp_resources/abc123/status-3frames.png",
  "sprite-enabled": true, "sprite-cols": 3, "sprite-rows": 1, "sprite-play": false,
  "data-enabled": true, "data-key": "pump1.alarm",
  "sprite-frame-by-value": true, "sprite-frame-mode": "rules",
  "sprite-frame-rules": "normal|0=0\nalarm|1=1\nfault|2=2"
} }
```

> 生成工程时 `image-url` 建议写可访问的外链或已知素材地址。编辑器图元库 **「行业图片」** 提供 9 行业 2.5D 静态图与等格精灵图（`/scada-images/...`，插入精灵图会同时写 `sprite-*`）；**「我的图片」** 用账号素材库。也可从桌面拖文件或 Ctrl+V 粘贴。

### 7.3 连线 scada-link

两端三种写法（可混用）：

| 写法 | options |
|---|---|
| 拓扑端（挂图元端口，随图元移动） | `from-node`+`from-port`、`to-node`+`to-port` |
| 自由端（无拓扑，固定场景坐标） | 对应端 node 留空 `""`，写 `from-x`/`from-y` 或 `to-x`/`to-y` |
| 途径点（线依次穿过） | `waypoints: [[x,y],...]` |

样式：`router`(`line`直线/`polyline`折线/`curve`曲线)、`stroke`(留空跟随主题)、`line-width`、`arrow`、`dash`；流动动画 `flow:true` + `flow-style`(`flow`水流/`beads`水珠/`dot`圆点/`arrow`箭头/`waterdrop`水滴/`pulse`能量脉冲)/`flow-direction`(`forward`/`reverse`)/`flow-color`(留空=主题流体色)/`flow-speed`。

**立体管道**：`line-style:"pipe"`（对照组态王立体管道）——同心分层描边（暗缘/体色/高光/白芯）出圆柱观感，**线宽即管径**（建议 10~16）；`pipe-joints`(默认 true) 在途径点画弯头节点圆（曲线走线不画）、两端画法兰短线；`flow` 流动层自动贴合管芯内腔、`dash` 在管道模式下忽略。

**路径动画（符号沿线行进，随线弯折自动转向）**：`marker:true` + `marker-shape`(几何：`arrow`箭头/`triangle`三角/`chevron`人字/`dot`圆点/`ring`圆环/`square`方块/`diamond`菱形/`star`星形；工业图标：`droplet`水滴/`bolt`电流/`box`立方/`flame`火焰/`gear`齿轮；`svg`自定义SVG)、`marker-size`(px，符号盒大小)、`marker-count`(1~12，等相位错开)、`marker-speed`(0.1~20，越大越快)、`marker-color`(留空=主题强调色)、`marker-rotate`(默认 true，符号沿切线转向)、`marker-keep-upright`(防止颠倒，默认 false；随向旋转时符号转到会上下颠倒的朝向自动翻正保持正立，适合图标/文字，仅在 `marker-rotate` 开启时有意义)、`marker-direction`(`forward`/`reverse`)、`marker-offset-x`/`marker-offset-y`(符号相对线的偏移 px，±500；随向旋转开启时 x=沿切线前后、y=沿法线控制在线的上方/下方，`+y` 下 `−y` 上；关闭旋转时为屏幕 x/y)。与 `flow` 可叠加（`flow` 画流体、`marker` 画离散符号）。开了 `flow-by-value` / `flow-map-by-value` 时，符号随流动启停（规则 `off` 停机则箭头/水滴一并消失），方向跟流动换向。

**自定义 SVG 符号**（`marker-shape:"svg"` 时）：符号形状由本条连线自带的 SVG 图形提供，写以下字段（编辑器「SVG 图形」入口会自动填，手写也可）——`marker-svg-els`(多元素多色符号：`ScadaSvgSymbolEl[]` 的 JSON 串，保留各元素填充/描边/透明度/组变换) 或 `marker-svg-path`(合并单路径的 `d`；与 `els` 二选一，`els` 优先) + `marker-svg-viewbox`(源 `viewBox`，`"minX minY w h"`) + `marker-svg-src`(可选，原始 SVG 源码，供再次「编辑源码」往返)。SVG 会等比缩放居中到 `marker-size` 方盒、中心随路径点移动、默认以 SVG 的 +x(右) 为前进方向（配合 `marker-rotate` 随切线转向）；`marker-color` 给未指定颜色(role=body)的元素上色，多色元素保留原色。解析不出图形时回退内置箭头。⚠️ 此为**连线专用**，与图元(`scada-node`)的自定义SVG(`shape:"svg-path"` + `svg-els`/`svg-path`/`svg-viewbox`)字段各自独立、互不影响。

```json
{ "type": "scada-link", "options": {
  "from-node": "n_pump", "from-port": "e", "to-node": "n_tank", "to-port": "w",
  "marker": true, "marker-shape": "svg", "marker-size": 22, "marker-count": 3,
  "marker-speed": 2, "marker-color": "#7fe3ff", "marker-rotate": true, "marker-direction": "forward",
  "marker-svg-path": "M2 12 L22 12 M14 5 L22 12 L14 19",
  "marker-svg-viewbox": "0 0 24 24"
} }
```

### 7.4 事件（播放态）

- **触发器**（写在 scada-2d 的 `options.interactions`）：`scada-node-click`/`scada-node-dblclick`(可选目标图元 uid)、`scada-link-click`、`scada-blank-click`。payload 含 `nodeUid`/`name`/`shape`/`dataKey`/`value`，公式 `{:dataKey}`、`{:value}` 可取。
- **动作**：`scadaWritePoint`（写测点：目标组态 + `key` + 字面值或取 payload 字段；`value` 填 `toggle` 则在 0↔1 翻转，适合点管线开/关流），配合按钮/图元点击做「点泵下发启停」。`scadaSetOption`（改图元选项：`piece-uid` + `option` + `value`，JSON 优先解析；`piece-uid` 留空取 payload.uid）。典型：写 `sprite-stop-frame` 指定图像集第几张、写 `sprite-play` 起停。也可用通用动作（`changeOption`/`switchScreen`/弹窗等）。SDK：`getScada(uid).setPieceOption(pieceUid, option, value)`。

### 7.5 编辑器内操作（供理解，不入 JSON）

双击组态进入内部编辑：拖拽图元库落点、端口拉线（落端口=拓扑线、落空白=自由线）、选中线拖端点改接/脱开、双击线段加途径点/双击途径点删点、旋转手柄、对齐分布、右键菜单。生成 JSON 时无须这些，直接写 pieces 即可。

图元库五个页签：**工业图元**（内置符号 + 「导入SVG」）、**行业图片**（编辑器随附素材库，URL 前缀 `/scada-images/`；2.5D/序列帧，按行业/静态·序列帧/分类筛选，悬停可预览序列帧，最近使用置顶，双击写入 `image-url`，精灵图同时写 `sprite-*`；当前行业搜空会拓宽到全库）、**我的图片**（账号级素材库，双击/拖入即落成 `shape:"image"` 图元；也可直接把图片文件从桌面拖到画布或 Ctrl+V 粘贴截图，自动上传后落图元）、**我的图元**（画布上框选一组右键「另存为图元…」存下的可复用符号，跨工程可用）、**通用组件**（内嵌图表/文本等）。

- 完整可导入示例：
  - `data/组态/01-智慧水厂工艺监控-组态全功能综合示例-科技蓝.vpproj.json`（形状/主题/流动/测点驱动/事件全覆盖，含 mock 实时数据）。
  - `data/组态/02-工业组件效果展示-新组件综合示例-科技蓝.vpproj.json`（新组件专场：球罐/卧罐/料仓/搅拌罐/液位柱/蓄电池/进度条 值驱动动态层 + 管阀流动链 + 内嵌图表）。
  - `data/组态/03-工业组件效果展示II-新形状与SVG符号-科技蓝.vpproj.json`（批次2 新形状：闸阀/蝶阀/三通阀/料斗/精馏塔/变频器/发电机/声光报警 + `svg-symbol` 导入符号 + 连线路径动画 `marker` 各形状演示 + 内嵌图表，占满画布）。
  - `data/组态/04-能源行业组态-电力新能源油气综合示例-科技蓝.vpproj.json`（批次4 能源行业：电力单线图(铁塔/母线/互感器/电能表/负荷/避雷器) + 新能源(光伏/风机/逆变器/储能/燃料电池/充电桩) + 油气(采油树/抽油机/分离器/储油罐/火炬) + 值驱动 + 流动/路径动画 + 内嵌图表，占满画布）。
  - `data/组态/05-水务与暖通组态-给排水与HVAC综合示例-科技蓝.vpproj.json`（批次5 水处理/暖通：给排水工艺(格栅/曝气/沉淀/紫外/消化/加药) + HVAC(冷机/表冷器/AHU/风管/风阀/盘管) + 值驱动 + 流动/路径动画 + 内嵌图表，占满画布）。
  - `data/组态/06-天气与粒子特效-气象与工业动效综合示例-科技蓝.vpproj.json`（批次6 粒子/天气特效：晴/云/雨/雪/闪电/雾 天气带 + 锅炉火焰/冷却塔蒸汽/烟囱烟雾/储罐气泡/电机火花/火炬 工业动效带 + 值驱动 + 流动/路径动画 + 内嵌图表，占满画布）。
  - `data/组态/07-动画连接演示-值驱动旋转移动缩放显隐与按值流速-科技蓝.vpproj.json`（值驱动旋转/移动/缩放/显隐与按值流速）。
  - `data/组态/08-2.5D立体组态-等轴测泵站与立体管道-科技蓝.vpproj.json`（2.5D 专场：等轴测地台/立式罐/厂房/冷却塔/机柜/传送带 + 立体管道按值流速 + 材质(金属/玻璃/哑光) + 落地阴影 + 泵叶轮部件旋转 + 液面波浪 + iso-plane 斜切 + depth-sort 深度排序）。
  - `data/组态/09-换热站远程监控系统2.0-科技蓝.vpproj.json`（换热站 2.0：一次/二次回路 + 管壳换热器 + 电动阀/深井泵/集水坑/仪表墙 + title-bar/status-pill）。
  - `data/组态/10-开源组态符号-ISA101工艺流程-科技蓝.vpproj.json`（ISA-101 HP-HMI 开源符号作 `shape:"image"`：泵/阀/电机/输送带按测点换图）。
  - `data/组态/11-开源组态符号-ThingsBoard高画质厂区-科技蓝.vpproj.json`（ThingsBoard HP 高画质符号作图片图元：罐/泵/换热/阀/光伏风机储能）。
  - `data/组态/12-精灵图动画-序列帧图片图元-科技蓝.vpproj.json`（精灵图专场：横排 8×1 / 网格 4×3 / 竖排 1×6 / 帧数少于格数 + 起停/帧率/倒放/停止帧 + 按值起停 + 按值选帧/命令式选帧 + 与图元旋转缩放同权；精灵图为编译期内联 SVG，示例离线可导入）。
  - `data/组态/13-行业图像集画廊-静态与序列帧-科技蓝.vpproj.json`（9 行业墙：静态卡 + 精灵图自动播）。
  - `data/组态/14-水务工艺-序列帧与按值换图-科技蓝.vpproj.json`（泵叶轮 `sprite-by-value`、曝气序列帧、阀 `image-by-value`）。
  - `data/组态/15-2.5D场站-等轴测与序列帧-科技蓝.vpproj.json`（`iso-platform` 地台 + 风机/输送带精灵图 + `iso-plane`）。
  - `data/组态/16-煤矿采运提-皮带与瓦斯序列帧-科技蓝.vpproj.json`（皮带走带 / 采煤机 / 瓦斯闪 + 测点起停）。
  - `data/组态/20-生成图复原-化工储运罐区-科技蓝.vpproj.json`（生成场景抠图复原：球罐→立罐→泵→反应釜搅拌序列帧→精馏塔→火炬；**2.5D 摆位三条硬约束的参考实现**，站立点+高度落位、地台菱形内、精灵图按单帧比例定盒）。
  - `data/组态/21-生成图复原-煤矿井口洗选-科技蓝.vpproj.json`（生成场景抠图复原：井架提升→1#皮带→储煤仓→2#皮带→洗选楼→装车站，皮带走带与主扇通风机两组 8 帧按值起停；同样按三条硬约束摆位）。
  - 生成脚本：`scripts/gen-scada-full-example.mjs`、`scripts/gen-scada-showcase-example.mjs`、`scripts/gen-scada-showcase2-example.mjs`、`scripts/gen-scada-showcase3-example.mjs`、`scripts/gen-scada-showcase4-example.mjs`、`scripts/gen-scada-showcase5-example.mjs`、`scripts/gen-scada-iso-example.mjs`。

### 7.6 VP-TSX 写法

`pieces` 是节点**顶层 prop**；标注 `VpScada2dPiece[]` 获得编译期校验；图元用 `geo-*` 场景坐标（映射总规则见 [vptsx.md](../vptsx.md)）：

```tsx
import { defineProject, h, bind, type VpScada2dPiece } from '<...>/runtime'

const scadaPieces: VpScada2dPiece[] = [
  { uid: 'n_pump', type: 'scada-node', options: { shape: 'pump', 'geo-x': 200, 'geo-y': 400, 'geo-w': 96, 'geo-h': 96, 'node-text': '1#提升泵', 'data-enabled': true, 'data-key': 'PUMP1_STATE', 'state-by-value': true } },
  { uid: 'l_1', type: 'scada-link', options: { 'from-node': 'n_pump', 'from-port': 'e', 'to-node': 'n_tank', 'to-port': 'w', router: 'polyline', flow: true } },
]
// <scada-2d uid="w_scada" position={[0, 0]} size={[1920, 1080]} theme="tech-blue" scada-initialized={true}
//   point-key-field={bind('conn_pts', 'tbl_pts', '键')} point-value-field={bind('conn_pts', 'tbl_pts', '值')}
//   pieces={scadaPieces} />
```
