OpenPLC Editor v4 FBD 与 SFC 图形编辑器
OpenPLC 编辑器通过构建在 ReactFlow (xyflow/react) 之上的节点与边画布架构实现了图形化 PLC 编程。功能块图 (FBD) 编辑器是成熟且功能完备的实现顺序功能图 (SFC) 编辑器目前仅作为结构占位符等待后续实现。这两个编辑器均由统一的图形编辑器调度器分发该调度器会根据 POU 声明的语言fbd、sfc或ld选择相应的组件。本页将介绍定义这些图形编辑器的架构模式、节点类型分类、数据流以及调试集成。图形编辑器分发与语言路由图形编辑器系统使用基于语言的区分联合来选择正确的编辑器组件。GraphicalEditor组件接收language作为属性取值为ld | sfc | fbd之一并通过简单的查找对象将其映射到相应的编辑器组件。这种模式保持了分发逻辑的声明性并确保每种语言变体都是独立、自包含的 React 组件。在查看历史提交时会应用一个只读覆盖层使用cursor-not-allowed指示器拦截所有指针事件。语言组件状态fbdFbdEditor已完全实现sfcSfcEditor未实现ldLadderEditor已完全实现FBD 节点类型分类与构建器模式FBD 编辑器定义了七种不同的节点类型每种类型都有专用的构建器函数和可视化组件。节点类型注册表集中存储在customNodeTypes映射中ReactFlow 使用它来解析哪个组件渲染哪个节点。并行的nodesBuilder映射提供了在构建新节点拖放、复制粘贴或编程式创建时使用的工厂函数。节点类型组件构建器描述blockBlockNodeElementbuildBlockNode带有类型化输入/输出句柄的函数/功能块调用input-variableVariableElementbuildVariableNode向图表输入数据的只读变量output-variableVariableElementbuildVariableNode从图表接收数据的写入目标变量inout-variableVariableElementbuildVariableNode同时具有输入和输出句柄的双向变量connectorConnectionElementbuildConnectionNode入站连接点左侧句柄目标continuationConnectionElementbuildConnectionNode出站延续点右侧句柄源commentCommentElementbuildCommentNode无电气行为的自由文本注释构建器模式至关重要每个构建器函数接受一个BuilderBasicPropsid 位置以及特定类型的参数并返回一个完整成型的 ReactFlowNode对象其中包含计算好的句柄位置、尺寸和默认数据。buildGenericNode函数充当多态分发器根据nodeType参数路由到正确的构建器——这是拖放处理程序和剪贴板粘贴逻辑使用的单一入口点。FBD 数据模型梯级状态与句柄几何结构FBD 编辑器的状态被捕获在FBDRungState类型中它封装了完整的 ReactFlow 图——一个Node对象数组、一个Edge对象数组以及一个selectedNodes子集。此梯级状态持久化在 Zustandfbd存储 slice 中并通过防抖本地状态镜像模式与FBDBody分子组件进行双向同步该组件维护一个rungLocal副本以实现即时的 ReactFlow 响应然后通过fbdFlowActions.setRung将更改防抖200ms回写到存储中。这避免了在每次像素级拖动事件时写入存储的性能开销同时保证了完成时的正确序列化。每个节点的data字段携带一个BasicNodeData结构其中包含定义节点连接点的句柄拓扑。句柄在构建时使用全局相对于流面板和相对节点内部位置进行预计算并存储为CustomHandleProps。这种双坐标系使编辑器能够在不查询 DOM 的情况下计算边路由和连接验证这对于无头序列化至关重要。BasicNodeData 字段用途handles所有句柄输入 输出 连接器的联合inputHandles/outputHandles用于连接验证的方向子集inputConnector/outputConnector主电气连接器第一个句柄numericId用于编译器临时变量命名的数字 UUIDexecutionOrderIEC 61131-3 执行顺序索引variable关联的 PLC 变量引用draggable/selectable/deletable交互控制标志调试期间锁定FbdEditor特性组件将 FBD 流状态桥接到项目数据模型。当flow.updated变为true时它会根据zodFBDFlowSchema验证流然后调用updatePou将 FBD 图持久化到项目 JSON 中。这确保了图形状态在写入项目文件之前始终是可序列化且符合模式规范的。FBD 拖放元素创建新的 FBD 元素通过拖放管道添加到画布中该管道将浏览器拖动事件转换为 ReactFlow 节点构建。FBDBody组件检测被拖动元素何时进入视口insideViewport捕获鼠标位置并在放置时调用handleAddElementByDropping。此处理程序从拖放负载中确定节点类型使用拖放检测工具中的getFbdBlockType和isFbdBlockDrag通过newGraphicalEditorNodeID生成唯一的节点 ID通过reactFlowInstance.screenToFlowPosition将屏幕位置转换为流坐标并委托给buildGenericNode进行实际的节点实例化。对于块类型节点处理程序从拖放负载中解析库源。库字符串的结构为system|user/{libraryName}/{pouName}处理程序解析它以在libraries存储中定位正确的函数或功能块定义。解析出的变体被传递给buildGenericNode→buildBlockNode后者根据变体的变量签名计算块的尺寸和句柄布局。然后新节点通过fbdFlowActions.setRung插入到梯级中并捕获快照以支持撤销/重做。FBD 调试集成实时边着色与变量强制FBD 编辑器实现了实时调试可视化当承载的布尔信号为 TRUE 时将边染成绿色#00FF00并在变量节点上叠加调试值徽章。这是在styledEdgesmemo 中计算的它执行递归图遍历以确定每条边的逻辑状态。determineEdgeState函数在图中递归工作对于每条边它检查源节点是否是直通节点connector/continuation——如果是则传播传入边的状态否则查询getNodeOutputState以解析实际的信号值。对于变量节点该函数通过复合键查找调试值。对于块节点它区分功能块实例使用{instanceName}.{outputName}键格式和函数调用使用_TMP_{BLOCKNAME}{numericId}_{OUTPUT}临时变量命名约定。这种双重命名解析是必要的因为 IEC 61131-3 编译管道为函数和功能块分配了不同的变量策略。当调试器激活时节点交互被锁定styledNodes将所有节点的draggable、selectable和deletable覆盖为false防止在实时执行期间修改图表。变量节点还通过上下文菜单和模态框支持强制/解强制操作使用直接写入 Modbus 调试协议的forceDebugVariable和releaseDebugVariable服务。函数调用输出的_TMP_{BLOCKNAME}{numericId}_{OUTPUT}命名约定是一个关键的实现细节——每个节点data上的numericId字段是在构建时通过generateNumericUUID()生成的并且必须在序列化周期中保持稳定调试值解析才能正确工作。FBD 剪贴板复制、剪切与粘贴useFBDClipboard钩子使用浏览器原生的剪贴板事件实现了一个类型区分的剪贴板。复制和剪切操作将选定的节点及其连接的边序列化为一个标记为language: fbd的ClipboardType对象该对象通过event.clipboardData.setData存储在自定义 MIME 类型fbd:nodes下。这种类型区分防止了跨语言粘贴错误例如将梯形图节点粘贴到 FBD 画布中。粘贴处理程序解析剪贴板数据验证language字段然后通过reactFlowInstance.screenToFlowPosition将屏幕空间鼠标位置转换为流空间坐标——如果光标在视口内粘贴的节点将以光标为中心否则默认为当前视口原点。pasteNodesAtFBD工具来自存储的 fbd slice重新映射节点 ID 和边引用以避免冲突然后结果通过fbdFlowActions.setNodes和fbdFlowActions.addEdge合并到梯级中。FBD 库差异检测FbdEditor特性组件计算库差异——即块节点的变体签名与当前库定义发生漂移的情况。当用户定义的函数或功能块在实例被放置到画布上之后被修改时就会发生这种情况。检测算法将原始 POU 的接口变量与块节点存储的variant.variables进行比较将每个变量格式化为{name}|{class}|{definition}|{value}以进行比较。具有差异签名的节点通过 ID 标记在nodeDivergences数组中该数组传递给FBDBody并传播到每个节点的data.hasDivergence标志以进行视觉指示。对于函数类型的 POU算法还检查OUT变量的类型是否与 POU 声明的返回类型匹配——这是一种微妙但关键的验证因为编译管道从OUT派生函数的返回类型。FBD 视口状态持久化当用户在编辑器标签页之间切换时FBD 编辑器通过saveEditorViewState操作持久化 ReactFlow 视口平移偏移和缩放级别。这被实现为一个订阅editor.meta.name的 Zustand 订阅——当活动编辑器改变时当前视口在新编辑器加载之前以先前编辑器的名称保存。挂载时FBDBody组件通过reactFlowInstance.setViewport恢复保存的视口确保用户返回到他们离开时完全相同的视图。保存操作使用零持续时间动画以避免可见的视口过渡。SFC 编辑器当前实现状态SFC 编辑器目前作为一个结构占位符存在——一个渲染静态div且无功能行为的裸组件。GraphicalEditor中的分发路由已配置为在language sfc时渲染SfcEditor因此集成点已经建立。实现 SFC 编辑器需要新的节点类型分类用于 SFC 元素步、转换、动作、发散、收敛专用的 Zustand 存储 slice类似于fbd/ladder用于 SFC 图状态Zod 模式用于序列化验证类似于zodFBDFlowSchemaSFC 专用工具箱位于工作区活动栏中FBD 编辑器的架构——特别是构建器模式、带防抖同步的梯级状态模式以及调试边着色引擎——为 SFC 实现提供了经过验证的蓝图。架构总结FBD 组件分层FBD 编辑器跨越三个原子设计层进行组织每层在渲染和状态管理管道中承担不同的职责层级组件职责特性FbdEditor流与项目同步差异检测Zod 验证编排FBDBody分子FBDBodyReactFlow 画布拖放处理调试边着色剪贴板视口持久化防抖存储同步原子BlockNodeElement,VariableElement,ConnectionElement,CommentElement单个节点渲染内联编辑句柄渲染调试徽章原子buildGenericNode,buildBlockNode,buildVariableNode,buildConnectionNode,buildCommentNode计算句柄几何和默认数据的节点工厂函数FBDBody中的防抖本地状态镜像模式是关键的性能优化——没有它每次拖动移动都会触发完整的存储写入、Zod 验证和项目 JSON 序列化。200ms 的防抖确保仅持久化稳定的状态而dragging标志在主动拖动期间完全抑制同步。