HalconFlowPlugin.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text.RegularExpressions;
  5. using System.Threading;
  6. using TeamAAS.FlowEditor.Execution;
  7. using TeamAAS.FlowEditor.Models;
  8. using TeamAAS.FlowEditor.Plugins;
  9. using Plugins.Halcon.Models;
  10. using Plugins.Halcon.Views;
  11. namespace Plugins.Halcon
  12. {
  13. /// <summary>
  14. /// Halcon 流程容器节点。镜像 GroupPlugin(组合模块)的行为:
  15. /// 配置输入映射(把主流程图像等注入子流程)→ 运行 HALCON 子流程(可循环)→ 收集输出映射回主流程。
  16. /// 双击打开 <see cref="HalconFlowEditorView"/>:一个只含 HALCON 节点(按 VisionPlugin 分类)的流程编辑器。
  17. /// </summary>
  18. [Serializable]
  19. [Plugin("Halcon流程", PluginCategory.Halocn模块, typeof(HalconFlowModel), typeof(HalconFlowEditorView),
  20. "HexagonOutline", NodeShape = NodeCategory.Group,
  21. Description = "HALCON 视觉子流程容器 - 图像经输入映射进入,内部用 HALCON 节点处理,结果映射回主流程")]
  22. public class HalconFlowPlugin : BasePlugin<HalconFlowModel>
  23. {
  24. public override List<OutputField> DeclareOutputs()
  25. {
  26. var list = new List<OutputField>();
  27. if (Model?.ModuleOutputs != null)
  28. {
  29. foreach (var kv in Model.ModuleOutputs)
  30. {
  31. if (string.IsNullOrWhiteSpace(kv.OutName)) continue;
  32. list.Add(new OutputField(kv.OutName, typeof(object)));
  33. }
  34. }
  35. list.Add(new OutputField("实际循环次数", typeof(int)));
  36. list.Add(new OutputField("循环设置", typeof(string)));
  37. list.Add(new OutputField("结果", typeof(bool)));
  38. list.Add(new OutputField("Message", typeof(string)));
  39. return list;
  40. }
  41. public override bool InitPlugin()
  42. {
  43. Model.EnsureSubGraph();
  44. return true;
  45. }
  46. [Newtonsoft.Json.JsonIgnore]
  47. public int CurrentCount { get; set; } = 0;
  48. public override NodeRunStatus PluginRun(CancellationToken token, out Dictionary<string, object> results)
  49. {
  50. var res = new Dictionary<string, object>();
  51. results = res;
  52. Model.EnsureSubGraph();
  53. NodeRunStatus Fail(string message)
  54. {
  55. res["Error"] = message;
  56. res["结果"] = false;
  57. Log(1, $"HALCON 流程失败: {message}", TeamAAS.LogLevel.Error);
  58. return NodeRunStatus.Failed;
  59. }
  60. if (Model.LoopCount == 0)
  61. {
  62. Log(2, "循环次数为 0,跳过 HALCON 流程执行");
  63. res["结果"] = true;
  64. res["实际循环次数"] = 0;
  65. res["循环设置"] = "0";
  66. return NodeRunStatus.Skipped;
  67. }
  68. if (Model.SubGraph.Nodes.Count == 0)
  69. return Fail("HALCON 子流程为空(双击节点进入编辑器拖入 HALCON 节点)");
  70. Log(3, $"HALCON 流程开始,循环设置={(Model.LoopCount == -1 ? "无限" : Model.LoopCount.ToString())},输入 {Model.ModuleInputs?.Count ?? 0} 个、输出 {Model.ModuleOutputs?.Count ?? 0} 个、子节点 {Model.SubGraph.Nodes.Count} 个");
  71. Model.IsBreakRequested = false;
  72. int maxLoops = Model.LoopCount == -1 ? int.MaxValue : Model.LoopCount;
  73. int actualLoops = 0;
  74. // 新一轮运行:换图层批次并清掉上一轮遗留图层(显示窗只保留当前运行的图层)
  75. HalconEditorPreview.BeginRunCycle();
  76. try
  77. {
  78. for (int i = 0; i < maxLoops; i++)
  79. {
  80. if (token.IsCancellationRequested) break;
  81. CurrentCount = i + 1;
  82. foreach (var node in Model.SubGraph.Nodes)
  83. {
  84. node.Status = NodeRunStatus.NotStarted;
  85. node.CostTime = 0;
  86. }
  87. // ResetAllNodes 会 ClearFlow 清掉子流程所有结果(含刚注册的输入),
  88. // 所以用 SkipReset=true 保留输入变量;节点状态由上面的循环手动复位(与 GroupPlugin 一致)
  89. RegisterInputs();
  90. var executor = new FlowExecutor(Model.SubGraph) { SkipReset = true };
  91. executor.EndNodeEncountered += () => { Model.IsBreakRequested = true; };
  92. executor.ExecuteAsync(token).Wait();
  93. actualLoops++;
  94. if (Model.IsBreakRequested)
  95. {
  96. res["Message"] = $"第 {i + 1} 次循环遇到结束节点";
  97. Log(2, $"第 {i + 1} 次循环遇到结束节点,跳出循环");
  98. break;
  99. }
  100. }
  101. // 收集输出:把子流程内部节点结果映射回主流程
  102. SaveOutputs(res);
  103. res["实际循环次数"] = actualLoops;
  104. res["循环设置"] = Model.LoopCount == -1 ? "无限" : Model.LoopCount.ToString();
  105. if (token.IsCancellationRequested)
  106. {
  107. Log(1, $"HALCON 流程被取消,已执行 {actualLoops} 次循环", TeamAAS.LogLevel.Warning);
  108. res["结果"] = false;
  109. return NodeRunStatus.Failed;
  110. }
  111. res["结果"] = true;
  112. Log(2, $"HALCON 流程执行完成,实际循环 {actualLoops} 次");
  113. return NodeRunStatus.Success;
  114. }
  115. catch (Exception ex)
  116. {
  117. return Fail(ex.Message);
  118. }
  119. }
  120. /// <summary>
  121. /// 把 ModuleInputs 映射到子流程注册表的「输入」区,内部节点通过 &amp;{输入.变量名} 引用。
  122. /// </summary>
  123. private void RegisterInputs()
  124. {
  125. if (Model?.ModuleInputs == null || Model.ModuleInputs.Count == 0) return;
  126. var subGraphId = Model.SubGraph.GraphId;
  127. var flowId = GetModel.FlowId;
  128. foreach (var input in Model.ModuleInputs)
  129. {
  130. if (string.IsNullOrWhiteSpace(input.InputName)) continue;
  131. if (input.SourceValue == null) continue;
  132. object value = null;
  133. if (!string.IsNullOrWhiteSpace(input.SourceValue.Formula))
  134. value = RegistryOrDebug.ResolveFormula(flowId, input.SourceValue.Formula);
  135. else
  136. value = input.SourceValue.Value;
  137. RegistryOrDebug.SetInputVariable(subGraphId, input.InputName, value);
  138. Log(4, $"输入[{input.InputName}] ← {(value?.GetType().Name ?? "null")}");
  139. }
  140. }
  141. /// <summary>把每个 ModuleOutput 的公式在子流程内解析后写入结果(键=OutName)。</summary>
  142. private void SaveOutputs(Dictionary<string, object> results)
  143. {
  144. if (Model?.ModuleOutputs == null) return;
  145. foreach (var kv in Model.ModuleOutputs)
  146. {
  147. if (string.IsNullOrWhiteSpace(kv.OutName)) continue;
  148. object value = ResolveFromSubGraph(kv.OutValue?.Formula, kv.OutValue?.Value);
  149. results[kv.OutName] = value;
  150. Log(4, $"输出[{kv.OutName}] = {(value?.ToString() ?? "null")}");
  151. }
  152. }
  153. /// <summary>
  154. /// 解析 &amp;{节点名.属性名}(从子流程节点本地 RawResults 读),
  155. /// 或 &amp;{流程名.节点名.属性名}(回退到注册表);无公式时返回 fallback。
  156. /// </summary>
  157. private object ResolveFromSubGraph(string formula, object fallback)
  158. {
  159. if (string.IsNullOrEmpty(formula)) return fallback;
  160. var m2 = Regex.Match(formula, @"^&\{(?<node>[^.}]+)\.(?<prop>[^.}]+)\}$");
  161. if (m2.Success)
  162. {
  163. var nodeName = m2.Groups["node"].Value;
  164. var propName = m2.Groups["prop"].Value;
  165. // 「输入」伪节点直接查注册表
  166. if (nodeName == "输入")
  167. return RegistryOrDebug.GetValue(Model.SubGraph.GraphId, "输入", propName);
  168. var node = Model.SubGraph?.Nodes?.FirstOrDefault(n => n.NodeName == nodeName);
  169. if (node?.RawResults != null && node.RawResults.TryGetValue(propName, out var val))
  170. return val;
  171. return null;
  172. }
  173. var m3 = Regex.Match(formula, @"^&\{(?<flow>[^.}]+)\.(?<node>[^.}]+)\.(?<prop>[^.}]+)\}$");
  174. if (m3.Success)
  175. return RegistryOrDebug.GetValue(m3.Groups["flow"].Value, m3.Groups["node"].Value, m3.Groups["prop"].Value);
  176. return formula;
  177. }
  178. }
  179. }