| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823 |
- using Prism.Commands;
- using Prism.Mvvm;
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Collections.Specialized;
- using System.ComponentModel;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Runtime.Serialization;
- using System.Runtime.Serialization.Formatters.Binary;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Windows;
- using TeamAAS.FlowEditor.Execution;
- using TeamAAS.FlowEditor.Models;
- using TeamAAS.FlowEditor.Plugins;
- using TeamAAS.FlowEngine.Execution;
- using TeamAAS.FlowEngine;
- namespace TeamAAS.FlowEditor
- {
- /// <summary>
- /// 流程编辑器 ViewModel(单流程)
- /// </summary>
- public class FlowEditorViewModel : TeamAAS.BindableBase
- {
- #region 属性
- private FlowGraph _graph;
- public FlowGraph Graph
- {
- get => _graph;
- set
- {
- if (SetProperty(ref _graph, value))
- HookGraph(_graph);
- }
- }
- public PropertyChangedEventHandler GraphDataChanged { get; set; }
- #region 脏标记(决定"切换产品时是否提示保存")
- private bool _isDirty;
- /// <summary>流程是否被修改过(点击保存或加载后恢复为 false)</summary>
- public bool IsDirty
- {
- get => _isDirty;
- private set => SetProperty(ref _isDirty, value);
- }
- /// <summary>标记流程已修改(节点增删/移动/连线/属性编辑等)</summary>
- public void MarkDirty()
- {
- IsDirty = true;
- }
- /// <summary>标记流程已保存/刚加载(清除修改标记)</summary>
- public void MarkClean()
- {
- IsDirty = false;
- }
- private bool _dirtyTrackingHooked;
- private void HookGraph(FlowGraph graph)
- {
- if (graph == null) return;
- if (_dirtyTrackingHooked)
- {
- graph.Nodes.CollectionChanged -= OnNodesCollectionChanged;
- graph.Connections.CollectionChanged -= OnConnectionsCollectionChanged;
- foreach (var node in graph.Nodes)
- node.PropertyChanged -= OnNodePropertyChanged;
- }
- graph.Nodes.CollectionChanged += OnNodesCollectionChanged;
- graph.Connections.CollectionChanged += OnConnectionsCollectionChanged;
- foreach (var node in graph.Nodes)
- node.PropertyChanged += OnNodePropertyChanged;
- _dirtyTrackingHooked = true;
- }
- private void OnNodesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- if (e.OldItems != null)
- foreach (FlowNode node in e.OldItems)
- node.PropertyChanged -= OnNodePropertyChanged;
- if (e.NewItems != null)
- foreach (FlowNode node in e.NewItems)
- node.PropertyChanged += OnNodePropertyChanged;
- MarkDirty();
- }
- private void OnConnectionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- MarkDirty();
- }
- // 只有"用户编辑类"属性才计入脏标记;运行状态(Status/CostTime/结果等)不算
- private static readonly HashSet<string> EditProperties = new HashSet<string>
- {
- nameof(FlowNode.X),
- nameof(FlowNode.Y),
- nameof(FlowNode.NodeName),
- nameof(FlowNode.IsEnabled),
- nameof(FlowNode.IsSkipWarning),
- nameof(FlowNode.NodeWidth),
- nameof(FlowNode.NodeHeight),
- };
- private void OnNodePropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- if (EditProperties.Contains(e.PropertyName))
- MarkDirty();
- }
- #endregion
- private bool _isSubFlowEditor;
- /// <summary>
- /// 是否为子流程编辑器
- /// </summary>
- public bool IsSubFlowEditor
- {
- get => _isSubFlowEditor;
- set
- {
- if (SetProperty(ref _isSubFlowEditor, value))
- {
- RunFlowCommand?.RaiseCanExecuteChanged();
- }
- }
- }
- public ObservableCollection<ToolboxGroup> ToolboxGroups { get; private set; }
- private FlowNode _selectedNode;
- public FlowNode SelectedNode
- {
- get => _selectedNode;
- set
- {
- if (SetProperty(ref _selectedNode, value))
- {
- // 切换节点时,自动选中最新历史记录(倒序,最新在 index 0)
- if (_selectedNode != null && _selectedNode.ExecutionHistory?.Count > 0)
- _selectedNode.SelectedHistoryEntry = _selectedNode.ExecutionHistory[0];
- }
- }
- }
- private double _zoom = 1.0;
- public double Zoom
- {
- get => _zoom;
- set => SetProperty(ref _zoom, value);
- }
- private bool _isRunning;
- public bool IsRunning
- {
- get => _isRunning;
- set => SetProperty(ref _isRunning, value);
- }
- private bool _isReadOnly;
- /// <summary>
- /// 只读监控模式(运行产品运行中):可选中节点查看结果,禁止一切修改与编辑器内运行。
- /// </summary>
- public bool IsReadOnly
- {
- get => _isReadOnly;
- private set => SetProperty(ref _isReadOnly, value);
- }
- /// <summary>由 Shell/产品级状态设置只读模式</summary>
- public void SetReadOnly(bool value)
- {
- IsReadOnly = value;
- RunFlowCommand?.RaiseCanExecuteChanged();
- StopFlowCommand?.RaiseCanExecuteChanged();
- }
- private bool _isLoading;
- /// <summary>导入流程时是否正在加载(画布显示转圈动画)</summary>
- public bool IsLoading
- {
- get => _isLoading;
- set => SetProperty(ref _isLoading, value);
- }
- private double _canvasWidth = 3500;
- /// <summary>画布宽度</summary>
- public double CanvasWidth
- {
- get => _canvasWidth;
- set => SetProperty(ref _canvasWidth, value);
- }
- private double _canvasHeight = 3500;
- /// <summary>画布高度</summary>
- public double CanvasHeight
- {
- get => _canvasHeight;
- set => SetProperty(ref _canvasHeight, value);
- }
- #endregion
- #region 命令
- [field: NonSerialized]
- public DelegateCommand ClearSelectionCommand { get; private set; }
- public DelegateCommand DeleteSelectedCommand { get; private set; }
- public DelegateCommand RunFlowCommand { get; private set; }
- public DelegateCommand StopFlowCommand { get; private set; }
- public DelegateCommand ImportFlowCommand { get; private set; }
- public DelegateCommand ExportFlowCommand { get; private set; }
- #endregion
- #region 节点创建
- /// <summary>
- /// 从插件描述符创建节点
- /// </summary>
- public static FlowNode CreateNodeFromPlugin(string NodeName,FlowGraph flowGraph, NodePluginInfo info, double x, double y)
- {
- var desc = PluginLoader.Instance.GetDescriptor(info.PluginId);
- var node = new FlowNode
- {
- NodeName = NodeName,
- Category = desc?.NodeShape ?? info.Category,
- FlowName = flowGraph.GraphName,
- FlowId = flowGraph.GraphId,
- PluginId = info.PluginId,
- X = x,
- Y = y,
- IconGeometry = info.IconGeometry
- };
- // 创建插件实例并获取默认模型
- var plugin = PluginLoader.Instance.CreateInstance(info.DisplayName);
- if (plugin != null)
- {
- plugin.GetModel.NodeId = node.NodeId;
- plugin.GetModel.ToolName = info.DisplayName;
- plugin.GetModel.NodeName = node.NodeName;
- plugin.GetModel.PluginId = node.PluginId;
- plugin.GetModel.FlowName = flowGraph.GraphName;
- plugin.GetModel.FlowId = flowGraph.GraphId;
- node.Registry = flowGraph.Registry;
- plugin.Registry = flowGraph.Registry; // InitRun 前需就绪,使默认输出注册到正确的注册表
- // 新建节点立即执行一次初始化运行:触发 DeclareOutputs 生成默认输出、注册到 ResultRegistry(供绑定树可见)、
- // 并填充 LastResults;随后 PluginModel setter 会用 LastResults 刷新节点输出列表。
- // 修复:从工具箱新拖入的节点不显示输出(此前仅 InitializeNode 加载/粘贴路径才 InitRun)。
- plugin.InitRun();
- node.PluginModel = plugin;
-
- }
- return node;
- }
- #endregion
- #region 构造函数
- public FlowEditorViewModel(FlowGraph graph = null, bool isSubFlowEditor = false, FlowToolboxScope toolboxScope = FlowToolboxScope.Main)
- {
- Graph = graph ?? new FlowGraph { GraphName = "流程" };
- IsSubFlowEditor = isSubFlowEditor;
- ToolboxScope = toolboxScope;
- InitCommands();
- MarkClean();
- }
- /// <summary>
- /// 工具箱作用域:决定本编辑器工具箱展示哪一批插件、按什么分组。
- /// Main=主流程(排除 IsSubFlowNode 子节点,按 PluginCategory 分组);
- /// Halcon=Halcon 子流程(只显 IsSubFlowNode 子节点,按 VisionPlugin 分组)。
- /// </summary>
- public FlowToolboxScope ToolboxScope { get; private set; } = FlowToolboxScope.Main;
- private void InitCommands()
- {
- // 从 PluginLoader 构建工具箱(按作用域过滤/分组)
- ToolboxGroups = new ObservableCollection<ToolboxGroup>();
- var infos = PluginLoader.Instance.GetAllPluginInfos();
- if (ToolboxScope != FlowToolboxScope.Main)
- {
- // 平台子流程编辑器(Halcon/Vpp/Vm):只取标记为子流程节点的插件,并按平台
- // (PluginCategory: Halocn模块/Vpp模块/Vm模块)隔离 —— 各平台只见到自己的算子,
- // 平台内按 VisionPlugin 枚举分组
- PluginCategory platform;
- switch (ToolboxScope)
- {
- case FlowToolboxScope.VisionVpp: platform = PluginCategory.Vpp模块; break;
- case FlowToolboxScope.VisionVm: platform = PluginCategory.Vm模块; break;
- default: platform = PluginCategory.Halocn模块; break;
- }
- var subInfos = infos.Where(i => i.IsSubFlowNode && i.Group == platform).ToList();
- foreach (var g in subInfos.GroupBy(i => i.VisionCategory).OrderBy(g => g.Key))
- {
- ToolboxGroups.Add(new ToolboxGroup
- {
- GroupName = g.Key.ToString(),
- Category = platform,
- GroupIcon = VisionCategoryIconMap.GetIcon(g.Key),
- Items = g.ToList()
- });
- }
- }
- else
- {
- // 主流程:排除子流程专用节点(保持既有行为——现有插件 IsSubFlowNode 均为 false,不受影响)
- var mainInfos = infos.Where(i => !i.IsSubFlowNode).ToList();
- foreach (var g in mainInfos.GroupBy(i => i.Group))
- {
- ToolboxGroups.Add(new ToolboxGroup
- {
- GroupName = g.Key.ToString(),
- Category = g.Key,
- GroupIcon = CategoryIconMap.GetIcon(g.Key),
- Items = g.ToList()
- });
- }
- }
- ClearSelectionCommand = new DelegateCommand(() => SelectedNode = null);
- DeleteSelectedCommand = new DelegateCommand(() =>
- {
- if (SelectedNode != null)
- {
- Graph.RemoveNode(SelectedNode.NodeId);
- SelectedNode = null;
- }
- });
- RunFlowCommand = new DelegateCommand(async () => await RunFlowAsync(), () => !IsRunning && !IsSubFlowEditor && !IsReadOnly);
- StopFlowCommand = new DelegateCommand(() => StopFlow(), () => IsRunning);
- // 导入/导出统一走命令(工具栏按钮与 Shell 右键菜单共用,避免重复代码)
- ImportFlowCommand = new DelegateCommand(async () =>
- {
- if (IsReadOnly)
- {
- DialogHelper.Info("运行产品监控中,无法导入流程");
- return;
- }
- var dlg = new Microsoft.Win32.OpenFileDialog
- {
- Filter = "流程文件|*.aas|所有文件|*.*",
- Title = "导入流程"
- };
- if (dlg.ShowDialog() == true)
- {
- if (Graph != null && await ImportFlowAsync(Graph.GraphName, dlg.FileName) == true)
- DialogHelper.Success("导入成功");
- else
- DialogHelper.Error("导入失败");
- }
- });
- ExportFlowCommand = new DelegateCommand(() =>
- {
- if (Graph == null) return;
- var dlg = new Microsoft.Win32.SaveFileDialog
- {
- Filter = "流程文件|*.aas|所有文件|*.*",
- Title = "导出流程",
- FileName = Graph.GraphName + ".aas"
- };
- if (dlg.ShowDialog() == true)
- {
- if (ExportFlow(Graph.GraphName, dlg.FileName) == true)
- DialogHelper.Success("导出成功");
- else
- DialogHelper.Error("导出失败");
- }
- });
- }
- #endregion
- #region 执行
- private CancellationTokenSource _cts;
- private FlowExecutor _executor;
- public async Task RunFlowAsync()
- {
- if (IsRunning) return;
- _cts = new CancellationTokenSource();
- _executor = new FlowExecutor(Graph);
- IsRunning = true;
- RunFlowCommand.RaiseCanExecuteChanged();
- StopFlowCommand.RaiseCanExecuteChanged();
- _executor.ExecutionCompleted += (success) =>
- {
- IsRunning = false;
- RunFlowCommand.RaiseCanExecuteChanged();
- StopFlowCommand.RaiseCanExecuteChanged();
- };
- try
- {
- await _executor.ExecuteAsync(_cts.Token);
- }
- finally
- {
- // 兜底:即使 ExecutionCompleted 因 UI 线程繁忙被延迟、或执行器抛异常,
- // 也保证运行态复位,避免"执行完仍被锁住、无法再次运行"。
- IsRunning = false;
- RunFlowCommand?.RaiseCanExecuteChanged();
- StopFlowCommand?.RaiseCanExecuteChanged();
- }
- }
- public void StopFlow()
- {
- _cts?.Cancel();
- }
- /// <summary>
- /// 仅运行单个节点(不启动后继)
- /// </summary>
- public async Task RunSingleNodeAsync(FlowNode node)
- {
- if (IsRunning || IsReadOnly || node == null) return;
- _cts = new CancellationTokenSource();
- _executor = new FlowExecutor(Graph);
- IsRunning = true;
- RunFlowCommand?.RaiseCanExecuteChanged();
- StopFlowCommand?.RaiseCanExecuteChanged();
- _executor.ExecutionCompleted += (success) =>
- {
- IsRunning = false;
- RunFlowCommand?.RaiseCanExecuteChanged();
- StopFlowCommand?.RaiseCanExecuteChanged();
- };
- try
- {
- await _executor.ExecuteSingleNodeAsync(node, _cts.Token);
- }
- finally
- {
- IsRunning = false;
- RunFlowCommand?.RaiseCanExecuteChanged();
- StopFlowCommand?.RaiseCanExecuteChanged();
- }
- }
- /// <summary>
- /// 从指定节点开始运行(向下流转)
- /// </summary>
- public async Task RunFromNodeAsync(FlowNode node)
- {
- if (IsRunning || IsReadOnly || node == null) return;
- _cts = new CancellationTokenSource();
- _executor = new FlowExecutor(Graph);
- IsRunning = true;
- RunFlowCommand?.RaiseCanExecuteChanged();
- StopFlowCommand?.RaiseCanExecuteChanged();
- _executor.ExecutionCompleted += (success) =>
- {
- IsRunning = false;
- RunFlowCommand?.RaiseCanExecuteChanged();
- StopFlowCommand?.RaiseCanExecuteChanged();
- };
- try
- {
- await _executor.ExecuteFromNodeAsync(node, _cts.Token);
- }
- finally
- {
- IsRunning = false;
- RunFlowCommand?.RaiseCanExecuteChanged();
- StopFlowCommand?.RaiseCanExecuteChanged();
- }
- }
- /// <summary>
- /// 右键属性 - 显示节点通用属性(非插件编辑器)
- /// </summary>
- public void ShowNodeProperties(FlowNode node)
- {
- if (node == null) return;
- string oldName = node.NodeName;
- if (DialogHelper.EditProperties(node, $"节点属性 - {node.NodeName}"))
- {
- // 重命名后确保唯一性
- if (node.NodeName != oldName)
- {
- string newName = node.NodeName;
- int suffix = 1;
- while (Graph.Nodes.Any(n => n != node && n.NodeName == newName))
- newName = $"{node.NodeName}_{suffix++}";
- if (newName != node.NodeName)
- {
- node.NodeName = newName;
- DialogHelper.Info($"名称已存在,自动改为: {newName}");
- }
- }
- }
- }
- #endregion
- #region 方法
- public void CreateNode(double x, double y, NodePluginInfo info)
- {
- string baseName = info.DisplayName;
- int suffix = 1;
- string candidate = baseName + suffix;
- while (Graph.Nodes.Any(n => n.NodeName == candidate))
- {
- suffix++;
- candidate = baseName + suffix;
- }
- var node = CreateNodeFromPlugin(candidate,Graph, info, x, y);
- // 重名加序号
-
-
- Graph.AddNode(node);
- }
- /// <summary>
- /// 双击节点 - 打开属性编辑器或插件自定义窗体
- /// </summary>
- public void OpenNodeEditor(FlowNode node)
- {
- if (node == null) return;
- // 异常分支节点:无属性可编辑,双击不弹任何窗
- if (node.Category == NodeCategory.ExceptionBranch) return;
- // 只读监控模式:禁止打开编辑器修改节点(允许选中查看结果)
- if (IsReadOnly)
- {
- DialogHelper.Info("运行产品监控中,节点处于只读状态");
- return;
- }
- var desc = PluginLoader.Instance.GetDescriptor(node.ToolName ?? node.PluginId);
- if (desc == null) return;
- var plugin = PluginLoader.Instance.CreateInstance(desc.DisplayName);
- if (plugin == null) return;
- if (node.PluginModel is IFlowNodePlugin savedModel)
- plugin.GetModel = savedModel.GetModel;
- PluginLoader.Instance.SelectFlow = node;
- if (desc.HasCustomView)
- {
- try
- {
- var view = System.Activator.CreateInstance(desc.Attribute.ViewType) as System.Windows.FrameworkElement;
- if (view != null)
- {
- view.DataContext = plugin.GetModel;
- System.Action<object, System.Threading.CancellationToken> execAction = (obj, token) =>
- {
- // 承载内嵌流程画布的自定义视图(如组合模块编辑器)实现 IPluginRunFeedback:
- // 执行前后同步其运行态,使"窗体执行"与"画布内 ▶ 运行"的反馈完全一致
- // (执行中:运行/停止按钮切换、画布锁定编辑、子流程节点依次亮灯可见)。
- var feedback = view as IPluginRunFeedback;
- feedback?.BeginRun();
- try
- {
- // 执行前先把视图当前编辑回写模型(如脚本编辑器),
- // 让「窗体执行」运行编辑器所见代码,而非上次「确定」时的旧代码。
- // execAction 在后台线程触发,编辑器控件读取必须封送回 UI 线程。
- var pendingSave = view as IPluginViewSave;
- if (pendingSave != null)
- System.Windows.Application.Current?.Dispatcher.Invoke(() => pendingSave.SaveChanges());
- plugin.GetModel = obj as BasePluginModel;
- var status = plugin.Run(token);
- var costMs = plugin.CostTime;
- System.Windows.Application.Current?.Dispatcher.Invoke(() =>
- {
- // 执行完成的 CT 信息优先写入承载窗体底部提示栏(组合模块等实现
- // 了 IPluginViewTip 的视图),不再弹全局 Growl;无提示栏时退回 Growl。
- var ctText = $"执行完成: {status}, 耗时 {costMs}ms";
- if (view is IPluginViewTip tipView)
- tipView.ShowTip(ctText);
- else
- DialogHelper.Info(ctText);
- });
- }
- finally
- {
- feedback?.EndRun();
- }
- };
- // 用户点击"确定"才回写模型并标记已修改
- bool confirmed = DialogHelper.ShowPluginView(view, desc.DisplayName, execAction);
- if (confirmed)
- {
- if (view is IPluginViewSave saveable)
- saveable.SaveChanges();
- node.PluginModel = plugin;
- MarkDirty();
- }
- }
- }
- catch (System.Exception ex)
- {
- DialogHelper.Error($"打开插件窗体失败: {ex.Message}");
- }
- }
- else
- {
- var model = plugin.GetModel;
- System.Action<object, System.Threading.CancellationToken> execAction = (obj, token) =>
- {
- plugin.GetModel = obj as BasePluginModel;
- var status = plugin.Run(token);
- System.Windows.Application.Current?.Dispatcher.Invoke(() =>
- DialogHelper.Info($"执行完成: {status}, 耗时 {plugin.CostTime}ms"));
- };
- // 无自定义视图的插件走属性编辑器:设置公式数据源上下文(与自定义视图的
- // BtnProperties_Click 行为对齐),否则 FormulaEditor 拿不到前序节点输出候选
- // 例外:视觉平台子流程编辑器(Halcon/Vpp/Vm)内编辑子节点时,若已挂容器
- // ParentGroupModel(含 ModuleInputs),保留它以便公式树显示容器「输入」变量(&{输入.图像});
- // 主流程与组合模块(ToolboxScope=Main)行为完全不变。
- bool keepParent = ToolboxScope != FlowToolboxScope.Main
- && PluginLoader.Instance.ParentGroupModel != null
- && PluginLoader.Instance.ParentGroupModel.GetType().GetProperty("ModuleInputs") != null;
- if (!keepParent)
- PluginLoader.Instance.ParentGroupModel = model;
- try
- {
- if (DialogHelper.EditProperties(model, $"属性编辑 - {node.NodeName}", execAction))
- {
- node.PluginModel = plugin;
- node.NotifyStatusChanged();
- MarkDirty(); // 确认修改 → 标记流程已更改
- }
- }
- finally
- {
- if (!keepParent)
- PluginLoader.Instance.ParentGroupModel = null;
- }
- }
- }
- #endregion
- #region 导入/导出
- /// <summary>
- /// 导出当前流程到文件(.aas,二进制格式)
- /// </summary>
- public bool ExportFlow(string FlowName, string filePath)
- {
- return FlowFileStore.Save(Graph, filePath);
- }
- /// <summary>
- /// 从文件(.aas)导入流程(异步:后台反序列化 + 画布加载动画)
- /// </summary>
- public async System.Threading.Tasks.Task<bool> ImportFlowAsync(string FlowName, string filePath)
- {
- IsLoading = true;
- try
- {
- // 清理当前流程的旧执行结果,防止新节点引用到残留数据
- Graph.Registry?.ClearFlow(Graph.GraphId);
- // 后台线程反序列化,避免卡 UI(让加载动画正常转动)
- FlowGraph Result = await System.Threading.Tasks.Task.Run(() => FlowFileStore.Load(filePath));
- if (Result != null)
- {
- Result.GraphId = Graph.GraphId;
- Result.GraphName = Graph.GraphName;
- var idMap = new Dictionary<string, string>();
- foreach (var item in Result.Nodes)
- {
- string oldNodeId = item.NodeId;
- item.InitializeNode(Graph.GraphId, Graph.GraphName, Graph.Registry);
- if (!string.IsNullOrEmpty(oldNodeId))
- idMap[oldNodeId] = item.NodeId;
- }
- // 重映射顶层连接线的 SourceNodeId/TargetNodeId
- if (Result.Connections != null && idMap.Count > 0)
- {
- foreach (var conn in Result.Connections)
- {
- if (idMap.TryGetValue(conn.SourceNodeId, out var newSrcId))
- conn.SourceNodeId = newSrcId;
- if (idMap.TryGetValue(conn.TargetNodeId, out var newTgtId))
- conn.TargetNodeId = newTgtId;
- }
- }
- // 将导入内容复制到现有 Graph,保持对象引用不变
- // (FlowTabItem.Graph、FlowCanvas._graph 等不会失效)
- // 释放被替换掉的旧节点插件资源(相机/图像等大对象),配合开头 ClearFlow 彻底清掉旧节点运行数据
- foreach (var oldNode in Graph.Nodes)
- {
- try { oldNode.PluginModel?.Dispose(); } catch { }
- }
- Graph.Nodes.Clear();
- foreach (var n in Result.Nodes)
- Graph.Nodes.Add(n);
- Graph.Connections.Clear();
- foreach (var c in Result.Connections)
- Graph.Connections.Add(c);
- GraphDataChanged?.Invoke(Graph, null);
- MarkDirty(); // 导入的新内容尚未保存
- }
- return Result != null;
- }
- finally
- {
- IsLoading = false;
- }
- }
- #endregion
- }
- /// <summary>
- /// 工具箱作用域。决定一个流程编辑器展示哪一批插件、按什么枚举分组。
- /// </summary>
- public enum FlowToolboxScope
- {
- /// <summary>主流程:排除子流程专用节点,按 PluginCategory 分组。</summary>
- Main = 0,
- /// <summary>Halcon 子流程:只显 Halocn模块 的子流程专用节点,按 VisionPlugin 分组。</summary>
- Vision = 1,
- /// <summary>VisionPro 子流程:只显 Vpp模块 的子流程专用节点(与 Halcon 平台互相隔离)。</summary>
- VisionVpp = 2,
- /// <summary>VisionMaster 子流程:只显 Vm模块 的子流程专用节点(与 Halcon/Vpp 平台互相隔离)。</summary>
- VisionVm = 3,
- }
- /// <summary>
- /// 工具箱分组
- /// </summary>
- public class ToolboxGroup
- {
- public string GroupName { get; set; }
- public PluginCategory Category { get; set; }
- public string GroupIcon { get; set; }
- public List<NodePluginInfo> Items { get; set; } = new List<NodePluginInfo>();
- }
- internal static class CategoryIconMap
- {
- public static string GetIcon(PluginCategory category)
- {
- switch (category)
- {
- // 视觉模块:相机 Camera
- case PluginCategory.视觉模块: return "Camera";
- // 硬件模块:主板芯片 Memory
- case PluginCategory.硬件模块: return "Memory";
- // 通讯模块:信号 Wifi
- case PluginCategory.通讯模块: return "Wifi";
- // Mes模块:表格 Table
- case PluginCategory.Mes模块: return "Table";
- // 逻辑判断:分支 CallSplit
- case PluginCategory.逻辑判断: return "CallSplit";
- // 深度学习:神经网络 Brain
- case PluginCategory.深度学习: return "Brain";
- // 文件操作:文档 FileDocumentOutline
- case PluginCategory.文件操作: return "FileDocumentOutline";
- // Vpp模块:Cognex VisionPro,图像对焦检测 ImageFilterCenterFocus
- case PluginCategory.Vpp模块: return "ImageFilterCenterFocus";
- // Vm模块:VisionMaster,相机光圈 CameraIris
- case PluginCategory.Vm模块: return "CameraIris";
- // Halocn模块:HALCON,六边形 HexagonOutline
- case PluginCategory.Halocn模块: return "HexagonOutline";
- // OpenCv模块:OpenCV,代码括号 CodeBraces
- case PluginCategory.OpenCv模块: return "CodeBraces";
- // 三维视觉:立方体 CubeOutline
- case PluginCategory.三维视觉: return "CubeOutline";
- default: return "CircleOutline";
- }
- }
- }
- /// <summary>
- /// VisionPlugin 枚举 → MahApps Material 图标 Kind 名映射(子流程工具箱分组图标用)。
- /// Kind 名无法识别时 IconKindConverter 会回退 CircleOutline,不会崩溃。
- /// </summary>
- internal static class VisionCategoryIconMap
- {
- public static string GetIcon(VisionPlugin category)
- {
- switch (category)
- {
- // 图像处理:滤镜 ImageFilterVintage
- case VisionPlugin.图像处理: return "ImageFilterVintage";
- // 检测识别:目标框 SelectionEllipseArrowInside
- case VisionPlugin.检测识别: return "FeatureSearchOutline";
- // 几何测量:尺子 Ruler
- case VisionPlugin.几何测量: return "Ruler";
- // 坐标标定:坐标系 AxisArrow
- case VisionPlugin.坐标标定: return "AxisArrow";
- // 深度学习:神经网络 Brain
- case VisionPlugin.深度学习: return "Brain";
- // 三维视觉:立方体 CubeOutline
- case VisionPlugin.三维视觉: return "CubeOutline";
- default: return "HexagonOutline";
- }
- }
- }
- }
|