VmModels.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. using PropertyGridLib.Attributes;
  2. using PropertyGridLib.Controls;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.ComponentModel;
  6. using TeamAAS.FlowEditor.Plugins;
  7. using TeamAAS.FlowEngine.FormulaData;
  8. namespace Plugins.Vm.Models
  9. {
  10. /// <summary>
  11. /// VM(海康 VisionMaster)流程/工具块节点模型。镜像 VppToolBlockModel 的结构,
  12. /// 但不直接持有 SDK 类型:方案/流程句柄用 object 承载并标记 [NonSerialized](运行态,不入库)。
  13. /// </summary>
  14. [Serializable]
  15. public class VmToolBlockModel : BasePluginModel
  16. {
  17. [Category("I.参数")]
  18. [DisplayName("1.VM方案路径")]
  19. [Description("海康 VisionMaster 方案(.sol)文件路径;运行前由适配层加载")]
  20. public string SolutionPath { get; set; } = "";
  21. [Category("I.参数")]
  22. [DisplayName("2.流程名")]
  23. [Description("方案内要执行的流程/工具块名称")]
  24. public string ProcedureName { get; set; } = "";
  25. [Category("I.参数")]
  26. [DisplayName("3.输入参数映射")]
  27. [Description("将主流程前序节点的输出映射到 VM 流程的输入终端。Key 必须与 VM 内输入名一致。")]
  28. public List<VmInputMapping> InputMappings { get; set; } = new List<VmInputMapping>();
  29. /// <summary>
  30. /// 输出终端名列表:VM 的输出终端在接入 SDK 前无法自动读取,先由用户在此声明,
  31. /// 使 <see cref="VmToolBlockPlugin.DeclareOutputs"/> 在跑之前就能声明输出、被下游绑定树索引。
  32. /// 接入 SDK 后可改为运行时自动学习并回填。
  33. /// </summary>
  34. [Category("I.参数")]
  35. [DisplayName("4.输出终端名")]
  36. [Description("声明 VM 流程的输出终端名(每行一个);用于运行前即可被下游绑定")]
  37. public List<string> OutputNames { get; set; } = new List<string>();
  38. /// <summary>上次运行学习到的“输出图层”数量,供 DeclareOutputs 在跑之前声明 输出图层N。</summary>
  39. [Browsable(false)]
  40. public int LastLayerCount { get; set; } = 0;
  41. /// <summary>已加载句柄对应的方案路径(变化时自动重载)。</summary>
  42. [Browsable(false)]
  43. public string LoadedSolutionPath { get; set; }
  44. /// <summary>已加载句柄对应的流程名(变化时自动重载)。</summary>
  45. [Browsable(false)]
  46. public string LoadedProcedureName { get; set; }
  47. // ── 高级编辑(属性面板按钮) ──
  48. // PropertyGridLib v1.1.2 [Button]:属性面板渲染按钮,点击反射调用 OpenAdvancedEditor。
  49. // 宿主可注入 AdvancedEditorLauncher 指到原生 VM 编辑入口;未注入时兜底用系统关联
  50. // 调起 VisionMaster 打开方案(原生编辑体验一致)。
  51. [NonSerialized]
  52. public Action AdvancedEditorLauncher;
  53. [Category("II.高级编辑")]
  54. [DisplayName("1.打开 VM 工具编辑")]
  55. [Description("打开海康 VisionMaster 原生界面编辑该工具块(与双击节点等效)")]
  56. [Button("打开 VM 工具编辑", nameof(OpenAdvancedEditor))]
  57. public object AdvancedEditButton { get; set; }
  58. public void OpenAdvancedEditor()
  59. {
  60. var launch = AdvancedEditorLauncher;
  61. if (launch != null) { launch(); return; }
  62. // 兜底:调起 VisionMaster 打开方案(经安全封装:扩展名白名单 + 参数列表启动)
  63. VmSolutionLauncher.OpenSolution(SolutionPath, null);
  64. }
  65. /// <summary>VM 方案句柄(运行态,不序列化)。</summary>
  66. [NonSerialized]
  67. public object Solution;
  68. /// <summary>VM 流程/工具块句柄(运行态,不序列化)。</summary>
  69. [NonSerialized]
  70. public object Procedure;
  71. }
  72. [Serializable]
  73. public class VmInputMapping
  74. {
  75. public VmInputMapping()
  76. {
  77. Source = new FormulaBound<object>();
  78. }
  79. [Category("输入映射")]
  80. [DisplayName("输入名")]
  81. [Description("必须与 VM 流程里定义的输入终端名完全一致")]
  82. public string InputName { get; set; } = "";
  83. [Category("输入映射")]
  84. [DisplayName("来源值")]
  85. [Description("从主流程前序节点的输出结果中选择")]
  86. [FormulaEditor(typeof(InputSourceDataProvider))]
  87. public FormulaBound<object> Source { get; set; }
  88. public override string ToString() => $"{InputName} ← {Source?.Formula ?? ""}";
  89. }
  90. /// <summary>
  91. /// VisionMaster 方案安全启动封装。
  92. /// 安全约束(防命令注入/任意执行):
  93. /// 1. 扩展名白名单——仅允许 .sol(VisionMaster 方案),配置成 .bat/.exe 等一律拒绝;
  94. /// 2. 配置了 VM 客户端路径时用「参数列表」方式启动(UseShellExecute=false),
  95. /// 方案路径作为单个带引号参数传入,不拼接任何 shell 命令;
  96. /// 3. 未配置客户端时走系统文件关联打开 .sol(仍受白名单限制)。
  97. /// </summary>
  98. public static class VmSolutionLauncher
  99. {
  100. public static bool OpenSolution(string solutionPath, string vmClientPath)
  101. {
  102. if (string.IsNullOrWhiteSpace(solutionPath) || !System.IO.File.Exists(solutionPath))
  103. {
  104. TeamAAS.AppLogger.Warning($"VM 方案文件不存在,无法打开编辑: {solutionPath}", "Vm");
  105. return false;
  106. }
  107. if (!string.Equals(System.IO.Path.GetExtension(solutionPath), ".sol", StringComparison.OrdinalIgnoreCase))
  108. {
  109. TeamAAS.AppLogger.Warning($"仅支持打开 .sol 方案文件,已拒绝: {solutionPath}", "Vm");
  110. return false;
  111. }
  112. try
  113. {
  114. if (!string.IsNullOrWhiteSpace(vmClientPath) && System.IO.File.Exists(vmClientPath))
  115. {
  116. System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
  117. {
  118. FileName = vmClientPath,
  119. Arguments = "\"" + solutionPath + "\"",
  120. UseShellExecute = false,
  121. });
  122. return true;
  123. }
  124. System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
  125. {
  126. FileName = solutionPath,
  127. Verb = "open",
  128. UseShellExecute = true,
  129. });
  130. return true;
  131. }
  132. catch (Exception ex)
  133. {
  134. TeamAAS.AppLogger.Error("调起 VisionMaster 失败", ex, "Vm");
  135. return false;
  136. }
  137. }
  138. }
  139. }