using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using PropertyGridLib.Attributes;
using PropertyGridLib.Controls;
using TeamAAS.FlowEditor.Execution;
using TeamAAS.FlowEditor.Models;
using TeamAAS.FlowEditor.Plugins;
using TeamAAS.FlowEngine.FormulaData;
namespace Plugins.Vm
{
// ─────────────────────────────────────────────────────────────────────
// VM(VisionMaster)流程容器 —— 镜像 HalconFlowPlugin/VppFlowPlugin 的"平台子流程"模式:
// 主流程拖一个「VM流程」容器节点,双击进入 VM 专用子流程编辑器(工具箱只显
// Vm模块 的算子),配置输入/输出映射后循环执行子图。
// ─────────────────────────────────────────────────────────────────────
///
/// VM 流程容器节点。镜像 HalconFlowPlugin 的行为:
/// 配置输入映射(把主流程图像等注入子流程)→ 运行 VM 子流程(可循环)→ 收集输出映射回主流程。
/// 双击打开 :一个只含 VM 算子(按 VisionPlugin 分类)的流程编辑器。
///
[Serializable]
[Plugin("VM流程", PluginCategory.Vm模块, typeof(Models.VmFlowModel), typeof(Views.VmFlowEditorView),
"CameraIris", NodeShape = NodeCategory.Group,
Description = "VisionMaster 视觉子流程容器 - 图像经输入映射进入,内部用 VM 模块算子处理,结果映射回主流程")]
public class VmFlowPlugin : BasePlugin
{
public override List DeclareOutputs()
{
var list = new List();
if (Model?.ModuleOutputs != null)
{
foreach (var kv in Model.ModuleOutputs)
{
if (string.IsNullOrWhiteSpace(kv.OutName)) continue;
list.Add(new OutputField(kv.OutName, typeof(object)));
}
}
list.Add(new OutputField("实际循环次数", typeof(int)));
list.Add(new OutputField("循环设置", typeof(string)));
list.Add(new OutputField("结果", typeof(bool)));
list.Add(new OutputField("Message", typeof(string)));
return list;
}
public override bool InitPlugin()
{
Model.EnsureSubGraph();
return true;
}
[Newtonsoft.Json.JsonIgnore]
public int CurrentCount { get; set; } = 0;
public override NodeRunStatus PluginRun(CancellationToken token, out Dictionary results)
{
var res = new Dictionary();
results = res;
Model.EnsureSubGraph();
NodeRunStatus Fail(string message)
{
res["Error"] = message;
res["结果"] = false;
Log(1, $"VM 流程失败: {message}", TeamAAS.LogLevel.Error);
return NodeRunStatus.Failed;
}
if (Model.LoopCount == 0)
{
Log(2, "循环次数为 0,跳过 VM 流程执行");
res["结果"] = true;
res["实际循环次数"] = 0;
res["循环设置"] = "0";
return NodeRunStatus.Skipped;
}
if (Model.SubGraph.Nodes.Count == 0)
return Fail("VM 子流程为空(双击节点进入编辑器拖入 VM 模块算子)");
Log(3, $"VM 流程开始,循环设置={(Model.LoopCount == -1 ? "无限" : Model.LoopCount.ToString())},输入 {Model.ModuleInputs?.Count ?? 0} 个、输出 {Model.ModuleOutputs?.Count ?? 0} 个、子节点 {Model.SubGraph.Nodes.Count} 个");
Model.IsBreakRequested = false;
int maxLoops = Model.LoopCount == -1 ? int.MaxValue : Model.LoopCount;
int actualLoops = 0;
try
{
for (int i = 0; i < maxLoops; i++)
{
if (token.IsCancellationRequested) break;
CurrentCount = i + 1;
foreach (var node in Model.SubGraph.Nodes)
{
node.Status = NodeRunStatus.NotStarted;
node.CostTime = 0;
}
// ResetAllNodes 会 ClearFlow 清掉子流程所有结果(含刚注册的输入),
// 所以用 SkipReset=true 保留输入变量;节点状态由上面的循环手动复位(与 GroupPlugin 一致)
RegisterInputs();
var executor = new FlowExecutor(Model.SubGraph) { SkipReset = true };
executor.EndNodeEncountered += () => { Model.IsBreakRequested = true; };
executor.ExecuteAsync(token).Wait();
actualLoops++;
if (Model.IsBreakRequested)
{
res["Message"] = $"第 {i + 1} 次循环遇到结束节点";
Log(2, $"第 {i + 1} 次循环遇到结束节点,跳出循环");
break;
}
}
// 收集输出:把子流程内部节点结果映射回主流程
SaveOutputs(res);
res["实际循环次数"] = actualLoops;
res["循环设置"] = Model.LoopCount == -1 ? "无限" : Model.LoopCount.ToString();
if (token.IsCancellationRequested)
{
Log(1, $"VM 流程被取消,已执行 {actualLoops} 次循环", TeamAAS.LogLevel.Warning);
res["结果"] = false;
return NodeRunStatus.Failed;
}
res["结果"] = true;
Log(2, $"VM 流程执行完成,实际循环 {actualLoops} 次");
return NodeRunStatus.Success;
}
catch (Exception ex)
{
return Fail(ex.Message);
}
}
///
/// 把 ModuleInputs 映射到子流程注册表的「输入」区,内部节点通过 &{输入.变量名} 引用。
///
private void RegisterInputs()
{
if (Model?.ModuleInputs == null || Model.ModuleInputs.Count == 0) return;
var subGraphId = Model.SubGraph.GraphId;
var flowId = GetModel.FlowId;
foreach (var input in Model.ModuleInputs)
{
if (string.IsNullOrWhiteSpace(input.InputName)) continue;
if (input.SourceValue == null) continue;
object value = null;
if (!string.IsNullOrWhiteSpace(input.SourceValue.Formula))
value = RegistryOrDebug.ResolveFormula(flowId, input.SourceValue.Formula);
else
value = input.SourceValue.Value;
RegistryOrDebug.SetInputVariable(subGraphId, input.InputName, value);
Log(4, $"输入[{input.InputName}] ← {(value?.GetType().Name ?? "null")}");
}
}
/// 把每个 ModuleOutput 的公式在子流程内解析后写入结果(键=OutName)。
private void SaveOutputs(Dictionary results)
{
if (Model?.ModuleOutputs == null) return;
foreach (var kv in Model.ModuleOutputs)
{
if (string.IsNullOrWhiteSpace(kv.OutName)) continue;
object value = ResolveFromSubGraph(kv.OutValue?.Formula, kv.OutValue?.Value);
results[kv.OutName] = value;
Log(4, $"输出[{kv.OutName}] = {(value?.ToString() ?? "null")}");
}
}
///
/// 解析 &{节点名.属性名}(从子流程节点本地 RawResults 读),
/// 或 &{流程名.节点名.属性名}(回退到注册表);无公式时返回 fallback。
///
private object ResolveFromSubGraph(string formula, object fallback)
{
if (string.IsNullOrEmpty(formula)) return fallback;
var m2 = Regex.Match(formula, @"^&\{(?[^.}]+)\.(?[^.}]+)\}$");
if (m2.Success)
{
var nodeName = m2.Groups["node"].Value;
var propName = m2.Groups["prop"].Value;
// 「输入」伪节点直接查注册表
if (nodeName == "输入")
return RegistryOrDebug.GetValue(Model.SubGraph.GraphId, "输入", propName);
var node = Model.SubGraph?.Nodes?.FirstOrDefault(n => n.NodeName == nodeName);
if (node?.RawResults != null && node.RawResults.TryGetValue(propName, out var val))
return val;
return null;
}
var m3 = Regex.Match(formula, @"^&\{(?[^.}]+)\.(?[^.}]+)\.(?[^.}]+)\}$");
if (m3.Success)
return RegistryOrDebug.GetValue(m3.Groups["flow"].Value, m3.Groups["node"].Value, m3.Groups["prop"].Value);
return formula;
}
}
}
namespace Plugins.Vm.Models
{
using TeamAAS.FlowEditor.Plugins;
///
/// VM 流程容器节点模型。镜像 HalconFlowModel:持有一张子流程图 ,
/// 支持把主流程前序节点的输出(尤其是图像)映射进来,并把子流程结果映射回主流程。
/// 子流程内部的节点是 VM 专用算子(按 VisionPlugin 分类),不含主流程节点。
///
[Serializable]
public class VmFlowModel : BasePluginModel
{
[Category("I.参数")]
[DisplayName("1.输入参数")]
[Description("把主流程前序节点的输出映射进来(图像通常映射为「图像」);子流程内部节点用 &{输入.变量名} 引用")]
[CollectionEditor(CanAdd = true, CanRemove = true, CanSort = true)]
public List ModuleInputs { get; set; } = new List();
[Category("I.参数")]
[DisplayName("2.输出参数")]
[Description("把子流程内部节点的结果映射回主流程(如结果键值、OK/NG)")]
[CollectionEditor(CanAdd = true, CanRemove = true, CanSort = true)]
public List ModuleOutputs { get; set; } = new List();
private int _loopCount = 1;
[Category("II.循环设置")]
[DisplayName("1.循环次数")]
[Description("循环次数。-1=无限循环, 0=跳过不执行, 其他=按次数执行")]
public int LoopCount
{
get => _loopCount;
set => _loopCount = value;
}
[Browsable(false)]
public FlowGraph SubGraph { get; set; }
[Browsable(false)]
public bool IsBreakRequested { get; set; } = false;
public void EnsureSubGraph()
{
if (SubGraph == null)
SubGraph = new FlowGraph { GraphName = ToolName ?? "VM流程", GraphId = NodeId };
}
}
/// 容器输入映射项:主流程来源值 → 子流程内 &{输入.InputName}。
[Serializable]
public class VmFlowInput
{
public VmFlowInput()
{
SourceValue = new FormulaBound