using PropertyGridLib.Attributes; using PropertyGridLib.Controls; using System; using System.Collections.Generic; using System.ComponentModel; using TeamAAS.FlowEditor.Plugins; using TeamAAS.FlowEngine.FormulaData; namespace Plugins.Vm.Models { /// /// VM(海康 VisionMaster)流程/工具块节点模型。镜像 VppToolBlockModel 的结构, /// 但不直接持有 SDK 类型:方案/流程句柄用 object 承载并标记 [NonSerialized](运行态,不入库)。 /// [Serializable] public class VmToolBlockModel : BasePluginModel { [Category("I.参数")] [DisplayName("1.VM方案路径")] [Description("海康 VisionMaster 方案(.sol)文件路径;运行前由适配层加载")] public string SolutionPath { get; set; } = ""; [Category("I.参数")] [DisplayName("2.流程名")] [Description("方案内要执行的流程/工具块名称")] public string ProcedureName { get; set; } = ""; [Category("I.参数")] [DisplayName("3.输入参数映射")] [Description("将主流程前序节点的输出映射到 VM 流程的输入终端。Key 必须与 VM 内输入名一致。")] public List InputMappings { get; set; } = new List(); /// /// 输出终端名列表:VM 的输出终端在接入 SDK 前无法自动读取,先由用户在此声明, /// 使 在跑之前就能声明输出、被下游绑定树索引。 /// 接入 SDK 后可改为运行时自动学习并回填。 /// [Category("I.参数")] [DisplayName("4.输出终端名")] [Description("声明 VM 流程的输出终端名(每行一个);用于运行前即可被下游绑定")] public List OutputNames { get; set; } = new List(); /// 上次运行学习到的“输出图层”数量,供 DeclareOutputs 在跑之前声明 输出图层N。 [Browsable(false)] public int LastLayerCount { get; set; } = 0; /// 已加载句柄对应的方案路径(变化时自动重载)。 [Browsable(false)] public string LoadedSolutionPath { get; set; } /// 已加载句柄对应的流程名(变化时自动重载)。 [Browsable(false)] public string LoadedProcedureName { get; set; } // ── 高级编辑(属性面板按钮) ── // PropertyGridLib v1.1.2 [Button]:属性面板渲染按钮,点击反射调用 OpenAdvancedEditor。 // 宿主可注入 AdvancedEditorLauncher 指到原生 VM 编辑入口;未注入时兜底用系统关联 // 调起 VisionMaster 打开方案(原生编辑体验一致)。 [NonSerialized] public Action AdvancedEditorLauncher; [Category("II.高级编辑")] [DisplayName("1.打开 VM 工具编辑")] [Description("打开海康 VisionMaster 原生界面编辑该工具块(与双击节点等效)")] [Button("打开 VM 工具编辑", nameof(OpenAdvancedEditor))] public object AdvancedEditButton { get; set; } public void OpenAdvancedEditor() { var launch = AdvancedEditorLauncher; if (launch != null) { launch(); return; } // 兜底:调起 VisionMaster 打开方案(经安全封装:扩展名白名单 + 参数列表启动) VmSolutionLauncher.OpenSolution(SolutionPath, null); } /// VM 方案句柄(运行态,不序列化)。 [NonSerialized] public object Solution; /// VM 流程/工具块句柄(运行态,不序列化)。 [NonSerialized] public object Procedure; } [Serializable] public class VmInputMapping { public VmInputMapping() { Source = new FormulaBound(); } [Category("输入映射")] [DisplayName("输入名")] [Description("必须与 VM 流程里定义的输入终端名完全一致")] public string InputName { get; set; } = ""; [Category("输入映射")] [DisplayName("来源值")] [Description("从主流程前序节点的输出结果中选择")] [FormulaEditor(typeof(InputSourceDataProvider))] public FormulaBound Source { get; set; } public override string ToString() => $"{InputName} ← {Source?.Formula ?? ""}"; } /// /// VisionMaster 方案安全启动封装。 /// 安全约束(防命令注入/任意执行): /// 1. 扩展名白名单——仅允许 .sol(VisionMaster 方案),配置成 .bat/.exe 等一律拒绝; /// 2. 配置了 VM 客户端路径时用「参数列表」方式启动(UseShellExecute=false), /// 方案路径作为单个带引号参数传入,不拼接任何 shell 命令; /// 3. 未配置客户端时走系统文件关联打开 .sol(仍受白名单限制)。 /// public static class VmSolutionLauncher { public static bool OpenSolution(string solutionPath, string vmClientPath) { if (string.IsNullOrWhiteSpace(solutionPath) || !System.IO.File.Exists(solutionPath)) { TeamAAS.AppLogger.Warning($"VM 方案文件不存在,无法打开编辑: {solutionPath}", "Vm"); return false; } if (!string.Equals(System.IO.Path.GetExtension(solutionPath), ".sol", StringComparison.OrdinalIgnoreCase)) { TeamAAS.AppLogger.Warning($"仅支持打开 .sol 方案文件,已拒绝: {solutionPath}", "Vm"); return false; } try { if (!string.IsNullOrWhiteSpace(vmClientPath) && System.IO.File.Exists(vmClientPath)) { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = vmClientPath, Arguments = "\"" + solutionPath + "\"", UseShellExecute = false, }); return true; } System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = solutionPath, Verb = "open", UseShellExecute = true, }); return true; } catch (Exception ex) { TeamAAS.AppLogger.Error("调起 VisionMaster 失败", ex, "Vm"); return false; } } } }