using Prism.Mvvm; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.Tracing; using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Windows; using System.Windows.Media; using Newtonsoft.Json; using TeamAAS.FlowEditor.Execution; using TeamAAS.FlowEditor.Plugins; namespace TeamAAS.FlowEditor.Models { [Serializable] /// /// 流程节点基类 - 所有节点类型的公共属性 /// public class FlowNode : TeamAAS.BindableBase { #region 标识属性 [Category("II.杂项")] [DisplayName("1.节点ID")] [ReadOnly(true)] [Browsable(true)] public string NodeId { get; set; } = System.Guid.NewGuid().ToString("N"); private string _nodeName = "新节点"; [Category("I.节点参数")] [DisplayName("1.节点名称")] [Description("设置流程名称")] [Browsable(true)] /// /// 节点唯一ID(用于连接线关联,自动生成) /// public string NodeName { get => _nodeName; set { if (SetProperty(ref _nodeName, value)) { if (PluginModel?.GetModel != null) PluginModel.GetModel.NodeName = value; } } } [Category("II.杂项")] [DisplayName("2.工具ID")] [ReadOnly(true)] [Browsable(true)] /// /// 关联的插件ID(兼容旧代码) 不变 /// public string PluginId { get; set; } /// /// 所属流程绑定的结果注册表(运行态=Main,编辑/调试态=Debug)。 /// 由 FlowGraph/InitializeNode 注入,并同步到 PluginModel.Registry。 /// [JsonIgnore] [ReadOnly(true)] [Browsable(false)] public ResultRegistry Registry { get => _registry; set { _registry = value; if (PluginModel != null) PluginModel.Registry = value; } } [NonSerialized] private ResultRegistry _registry; [Category("II.杂项")] [DisplayName("3.工具名称")] [Description("设置工具名称")] [Browsable(true)] [ReadOnly(true)] /// /// 工具名称 /// public string ToolName { get => PluginModel?.GetModel?.ToolName; set { if (PluginModel?.GetModel != null) PluginModel.GetModel.ToolName = value; } } private string _flowId; [Category("II.杂项")] [DisplayName("4.流程ID")] [ReadOnly(true)] [Browsable(true)] /// /// 所属流程ID(用于结果注册表和公式解析) /// public string FlowId { get => _flowId; set { if (_flowId != value) { _flowId = value; if (PluginModel?.GetModel != null) PluginModel.GetModel.FlowId = value; } } } private string _flowName; [Category("II.杂项")] [DisplayName("5.流程名称")] [ReadOnly(true)] [Browsable(true)] /// /// 所属流程名称(用于UI展示和公式解析) /// public string FlowName { get => _flowName; set { if (_flowName != value) { _flowName = value; if (PluginModel?.GetModel != null) PluginModel.GetModel.FlowName = value; } } } /// 节点是否启用(委托到 PluginModel.IsEnable,禁用后不执行+画布变灰) [Category("I.节点参数")] [DisplayName("2.启用工具")] [Description("是否启用工具")] [Browsable(true)] public bool IsEnabled { get => PluginModel?.IsEnable ?? true; set { if (PluginModel != null && PluginModel.IsEnable != value) { PluginModel.IsEnable = value; RaisePropertyChanged(nameof(IsEnabled)); } } } [Browsable(false)] /// /// 节点分类(用于UI展示不同形状和颜色) /// public NodeCategory Category { get; set; } = NodeCategory.Normal; private IFlowNodePlugin _PluginModel { get; set; } [Browsable(false)] /// /// 插件模型数据(序列化存储,类型为 BasePluginModel 派生类) /// public IFlowNodePlugin PluginModel { get => _PluginModel; set { if (value != null && _PluginModel != value) { _PluginModel = value; //_PluginModel.InitRun(); // 同步 FlowNode 当前属性到模型 _PluginModel.Registry = _registry; _PluginModel.GetModel.NodeName = _nodeName; if (_flowName != null) _PluginModel.GetModel.FlowName = _flowName; if (_flowId != null) _PluginModel.GetModel.FlowId = _flowId; SetResults(FlowId, _PluginModel.LastResults); } } } [Category("I.节点参数")] [DisplayName("3.跳过警告")] [Description("是否跳过返回值为 Warning 的返回任务")] [Browsable(true)] public bool IsSkipWarning { get; set; } = false; private int _preDelayMs = 0; [Category("I.节点参数")] [DisplayName("4.运行前休眠(ms)")] [Description("节点执行前等待的毫秒数。默认0=不等待,最小0ms。")] [Browsable(true)] public int PreDelayMs { get => _preDelayMs; set => _preDelayMs = value <= 0 ? 0 : value; } private int _postDelayMs = 0; [Category("I.节点参数")] [DisplayName("5.运行后休眠(ms)")] [Description("节点执行完成后等待的毫秒数,再启动后继节点。默认0=不等待,最小0ms。")] [Browsable(true)] public int PostDelayMs { get => _postDelayMs; set => _postDelayMs = value <= 0 ? 0 : value; } private int _timeoutMs = 0; [Category("I.节点参数")] [DisplayName("运行超时(ms)")] [Description("节点执行超时看门狗:超过该毫秒数仍未返回则判定为超时失败并继续后续流程(防止硬件卡死拖垮整条流程)。默认0=不限时。注:阻塞式硬件调用无法被强制中断,超时仅保证流程不再无限等待。")] [Browsable(true)] public int TimeoutMs { get => _timeoutMs; set => _timeoutMs = value <= 0 ? 0 : value; } /// /// 任务插件「日志存储详细度」(委托到 PluginModel.GetModel.StoreVerbosity,实际存储与序列化在模型侧)。 /// 粗略=只写 L1/L2 到公共日志;详细=额外把 L3/L4 写到独立路径。 /// [Category("I.节点参数")] [DisplayName("6.日志存储")] [Description("日志存储详细度:粗略=只写公共日志(L1/L2);详细=额外把 L3/L4 写到 流程\\任务 独立日志")] [Browsable(true)] public TeamAAS.Logging.LogVerbosity StoreVerbosity { get => PluginModel?.GetModel?.StoreVerbosity ?? TeamAAS.Logging.LogVerbosity.粗略; set { if (PluginModel?.GetModel != null && PluginModel.GetModel.StoreVerbosity != value) { PluginModel.GetModel.StoreVerbosity = value; RaisePropertyChanged(nameof(StoreVerbosity)); } } } /// /// 任务插件「日志显示详细度」(委托到 PluginModel.GetModel.DisplayVerbosity,实际存储在模型侧)。 /// 粗略=实时日志只显 L1/L2;详细=额外显 L3/L4(与是否落盘无关)。 /// [Category("I.节点参数")] [DisplayName("7.日志显示")] [Description("日志显示详细度:粗略=实时日志只显 L1/L2;详细=额外显 L3/L4(独立于存储)")] [Browsable(true)] public TeamAAS.Logging.LogVerbosity DisplayVerbosity { get => PluginModel?.GetModel?.DisplayVerbosity ?? TeamAAS.Logging.LogVerbosity.粗略; set { if (PluginModel?.GetModel != null && PluginModel.GetModel.DisplayVerbosity != value) { PluginModel.GetModel.DisplayVerbosity = value; RaisePropertyChanged(nameof(DisplayVerbosity)); } } } #endregion #region 布局属性 [Browsable(false)] public double X { get => _x; set => SetProperty(ref _x, value); } private double _x = 100; [Browsable(false)] public double Y { get => _y; set => SetProperty(ref _y, value); } private double _y = 100; #endregion #region 运行属性 [Browsable(false)] [JsonIgnore] public NodeRunStatus Status { get => _status; set => SetProperty(ref _status, value); } [NonSerialized] private NodeRunStatus _status = NodeRunStatus.NotStarted; [Browsable(false)] [JsonIgnore] public int CostTime { get => _costTime; set => SetProperty(ref _costTime, value); } [NonSerialized] private int _costTime; #endregion #region 异常分支触发状态(运行时,不序列化) [NonSerialized] private int _exceptionTriggers; [NonSerialized] private int _exceptionRunFlag; [Browsable(false)] [JsonIgnore] public int ExceptionTriggers => _exceptionTriggers; /// 原子 +1 触发计数,返回新值。用于异常分支节点排队触发。 public int IncrementExceptionTrigger() => Interlocked.Increment(ref _exceptionTriggers); /// 原子 -1 触发计数,返回新值。每次实际执行完一次后调用。 public int DecrementExceptionTrigger() => Interlocked.Decrement(ref _exceptionTriggers); /// 尝试获取运行权:CAS 把 _exceptionRunFlag 从 0 改为 1。成功=true 表示获得运行权。 public bool TryAcquireExceptionRun() => Interlocked.CompareExchange(ref _exceptionRunFlag, 1, 0) == 0; /// 释放运行权:把 _exceptionRunFlag 重置为 0。 public void ReleaseExceptionRun() => Interlocked.Exchange(ref _exceptionRunFlag, 0); /// 重置异常触发状态(每次流程开始 ResetAllNodes 时调用)。 public void ResetExceptionState() { Interlocked.Exchange(ref _exceptionTriggers, 0); Interlocked.Exchange(ref _exceptionRunFlag, 0); } #endregion #region 执行结果 [NonSerialized] private ObservableCollection _resultItems = new ObservableCollection(); [Browsable(false)] [JsonIgnore] public ObservableCollection ResultItems { get => _resultItems; set => SetProperty(ref _resultItems, value); } /// /// 执行历史记录(每次执行追加一条) /// [NonSerialized] private ObservableCollection _executionHistory = new ObservableCollection(); [Browsable(false)] [JsonIgnore] public ObservableCollection ExecutionHistory { get => _executionHistory; set => SetProperty(ref _executionHistory, value); } /// /// 当前选中的历史记录(UI展示用) /// [NonSerialized] private ExecutionHistoryEntry _selectedHistoryEntry; [Browsable(false)] [JsonIgnore] public ExecutionHistoryEntry SelectedHistoryEntry { get => _selectedHistoryEntry; set => SetProperty(ref _selectedHistoryEntry, value); } [Browsable(false)] /// /// 历史记录最大保留条数(默认50,可外部设置) /// public int MaxHistoryCount { get; set; } = 50; /// /// 节点最新执行结果原始字典(供公式引用,不参与序列化) /// [Browsable(false)] [JsonIgnore] public Dictionary RawResults { get; set; } private static bool IsBasicType(object value) { if (value == null) return true; var t = value.GetType(); return t.IsPrimitive || t == typeof(string) || t == typeof(decimal) || t == typeof(DateTime) || t == typeof(TimeSpan) || t == typeof(Guid) || t.IsEnum; } /// /// 设置执行结果(UI线程调用) /// public void SetResults(string GraphId, Dictionary results) { if (_resultItems == null) _resultItems = new ObservableCollection(); _resultItems?.Clear(); if (results != null) { foreach (var kv in results) { // 非基础类型只存 ToString,避免图像/CogRecord 等大对象挂在 UI 和历史快照里 // RawResults 保留原始引用供节点间公式解析使用 bool converted = kv.Value != null && !IsBasicType(kv.Value); _resultItems?.Add(new ResultItem { Key = kv.Key, Value = converted ? kv.Value.ToString() : kv.Value, // 转换后 TypeName 显式记录原始实际类型,避免显示成 String TypeName = converted ? ResultItem.DescribeType(kv.Value.GetType()) : null }); } } RaisePropertyChanged(nameof(ResultItems)); RaisePropertyChanged(nameof(ExecutionHistory)); RaisePropertyChanged(nameof(SelectedHistoryEntry)); // 添加到历史记录 var snapshot = new ObservableCollection(); foreach (var item in _resultItems) snapshot.Add(new ResultItem { Key = item.Key, Value = item.Value, TypeName = item.TypeName }); var entry = new ExecutionHistoryEntry(DateTime.Now, snapshot); if (_executionHistory == null) _executionHistory = new ObservableCollection(); Application.Current?.Dispatcher.Invoke(() => _executionHistory.Insert(0, entry)); // 超过最大条数自动清理最旧的(末尾) while (_executionHistory.Count > MaxHistoryCount) Application.Current?.Dispatcher.Invoke(() => _executionHistory.RemoveAt(_executionHistory.Count - 1)); SelectedHistoryEntry = entry; // 自动选最新 RawResults = results; } #endregion #region UI辅助属性 [Browsable(false)] /// /// 节点图标(Material Design 图标 Kind 名,如 "Camera";由 MahApps IconPacks 渲染) /// public string IconGeometry { get; set; } = "CircleOutline"; [Browsable(false)] [JsonIgnore] public bool IsSelected { get => _isSelected; set { if (SetProperty(ref _isSelected, value)) RaisePropertyChanged(nameof(BorderColor)); } } [NonSerialized] private bool _isSelected; [Browsable(false)] [JsonIgnore] /// /// 边框颜色(选中时高亮) /// public string BorderColor => IsSelected ? "#007ACC" : "#3F3F46"; /// /// 节点宽度(可被实际渲染尺寸覆盖) /// [NonSerialized] private double _nodeWidth = 0; [Browsable(false)] public double NodeWidth { get { if (_nodeWidth > 0) return _nodeWidth; switch (Category) { case NodeCategory.Decision: return 180; case NodeCategory.ForLoop: return 180; case NodeCategory.Group: return 200; case NodeCategory.End: return 140; case NodeCategory.ExceptionBranch: return 160; default: return 160; } } set => _nodeWidth = value; } /// /// 节点高度(可被实际渲染尺寸覆盖) /// [NonSerialized] private double _nodeHeight = 0; [Browsable(false)] public double NodeHeight { get { if (_nodeHeight > 0) return _nodeHeight; switch (Category) { case NodeCategory.Decision: return 66; case NodeCategory.ForLoop: return 66; case NodeCategory.Group: return 66; case NodeCategory.End: return 52; case NodeCategory.ExceptionBranch: return 66; default: return 66; } } set => _nodeHeight = value; } [Browsable(false)] [JsonIgnore] /// /// 是否有第二个输出端口(判断节点) /// public System.Windows.Visibility HasSecondOutput { get { return Category == NodeCategory.Decision ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed; } } [Browsable(false)] [JsonIgnore] /// /// 分类对应的标题色(十六进制) /// public string CategoryColor { get { switch (Category) { case NodeCategory.Decision: return "#C2771A"; case NodeCategory.ToolBlock: return "#7B1FA2"; case NodeCategory.ForLoop: return "#00897B"; case NodeCategory.Group: return "#E65100"; case NodeCategory.End: return "#D32F2F"; case NodeCategory.ExceptionBranch: return "#B71C1C"; default: return "#007ACC"; } } } [Browsable(false)] [JsonIgnore] /// /// 状态文本(属性面板用) /// public string StatusText { get { switch (Status) { case NodeRunStatus.NotStarted: return "未运行"; case NodeRunStatus.Running: return "运行中..."; case NodeRunStatus.Success: return "成功"; case NodeRunStatus.Failed: return "失败"; case NodeRunStatus.Skipped: return "跳过"; default: return ""; } } } [Browsable(false)] [JsonIgnore] /// /// 耗时显示文本(节点上显示) /// public string CostTimeText { get { if (CostTime == 0) return "0ms"; if (CostTime < 1000) return CostTime + "ms"; return (CostTime / 1000.0).ToString("F1") + "s"; } } [Browsable(false)] [JsonIgnore] /// /// 状态指示色(十六进制) /// public string StatusColor { get { switch (Status) { case NodeRunStatus.NotStarted: return "#888888"; case NodeRunStatus.Running: return "#FF9800"; case NodeRunStatus.Success: return "#4CAF50"; case NodeRunStatus.Failed: return "#F44336"; case NodeRunStatus.Skipped: return "#666666"; default: return "#888888"; } } } public void NotifyStatusChanged() { RaisePropertyChanged(nameof(StatusText)); RaisePropertyChanged(nameof(StatusColor)); RaisePropertyChanged(nameof(CostTimeText)); //RaisePropertyChanged(nameof(PropertySummary)); RaisePropertyChanged(nameof(ResultItems)); } #endregion public FlowNode() { } /// /// 初始化ID链接。 /// 导入 .aas / 打开产品流程 / 复制粘贴等所有加载路径最终都会经过这里, /// 是节点"重挂接"的唯一入口;插件初始化(InitPlugin + 注册默认输出)也在这里统一补跑。 /// /// /// public void InitializeNode(string flowId, string flowName, ResultRegistry registry = null) { FlowId = flowId; FlowName = flowName; NodeId = System.Guid.NewGuid().ToString("N"); // 绑定所属流程的结果注册表(setter 会同步到 PluginModel.Registry) Registry = registry ?? _registry ?? ResultRegistry.Debug; var model = PluginModel?.GetModel; if (model != null) { model.NodeId = NodeId; model.FlowId = FlowId; model.FlowName = FlowName; model.NodeName = NodeName; } // Group/ForLoop 子流程:递归初始化子节点并重映射子图连线 if ((Category == NodeCategory.Group || Category == NodeCategory.ForLoop) && model != null) { // 反射查找 FlowGraph 类型的属性,遍历子节点初始化ID和Name var graphProp = model.GetType().GetProperties() .FirstOrDefault(p => p.PropertyType == typeof(FlowGraph)); if (graphProp != null) { var subGraph = graphProp.GetValue(model) as FlowGraph; if (subGraph != null) { subGraph.GraphId = NodeId; subGraph.GraphName = NodeName; subGraph.Registry = Registry; // 子流程与父流程共用同一注册表 if (subGraph.Nodes != null) { var childIdMap = new Dictionary(); foreach (var subNode in subGraph.Nodes) { string oldSubNodeId = subNode.NodeId; subNode.InitializeNode(NodeId, NodeName, Registry); if (!string.IsNullOrEmpty(oldSubNodeId)) { childIdMap[oldSubNodeId] = subNode.NodeId; } } // 重映射子图连接线的 SourceNodeId/TargetNodeId if (subGraph.Connections != null && childIdMap.Count > 0) { foreach (var conn in subGraph.Connections) { if (childIdMap.TryGetValue(conn.SourceNodeId, out var newSrcId)) conn.SourceNodeId = newSrcId; if (childIdMap.TryGetValue(conn.TargetNodeId, out var newTgtId)) conn.TargetNodeId = newTgtId; } } } } } } ExecutionHistory?.Clear(); // 恢复运行态集合([NonSerialized] 字段经 BinaryFormatter 反序列化后为 null) if (_resultItems == null) _resultItems = new ObservableCollection(); if (_executionHistory == null) _executionHistory = new ObservableCollection(); // 关键修复:反序列化直接恢复私有字段 _PluginModel,不会经过 PluginModel 的 setter, // 因此这里统一补跑插件初始化(InitPlugin + 将默认输出注册到 ResultRegistry), // 保证导入/打开方案/粘贴后的节点与编辑器新建节点行为一致。 if (PluginModel != null) { PluginModel.InitRun(); // 与新建节点一致:默认输出同步到 UI 结果列表(仅在集合尚未建立时执行,避免重复历史记录) if (_resultItems.Count == 0 && _executionHistory.Count == 0) SetResults(FlowId, PluginModel.LastResults); } } } [Serializable] /// /// 执行历史记录项 /// public class ExecutionHistoryEntry : TeamAAS.BindableBase { public DateTime Timestamp { get; } public string TimestampText => Timestamp.ToString("HH:mm:ss"); public string FullTimestampText => Timestamp.ToString("HH:mm:ss.fff"); private ObservableCollection _results; public ObservableCollection Results { get => _results; set => SetProperty(ref _results, value); } public ExecutionHistoryEntry(DateTime timestamp, ObservableCollection results) { Timestamp = timestamp; _results = results; } } [Serializable] /// /// 执行结果项(UI展示用) /// public class ResultItem { public string Key { get; set; } public object Value { get; set; } /// /// 显式类型名:大对象被转成字符串展示时,记录转换前的实际类型; /// 未显式设置时按 Value 运行时类型推断。 /// private string _typeName; public string TypeName { get => _typeName ?? (Value == null ? "null" : DescribeType(Value.GetType())); set => _typeName = value; } /// 类型名格式化(泛型显示为 List<T> 形式) internal static string DescribeType(Type t) { if (t.IsGenericType) { var args = string.Join(", ", t.GetGenericArguments().Select(a => a.Name)); return t.Name.Split('`')[0] + "<" + args + ">"; } return t.Name; } /// 简单类型的字符串展示 public string DisplayValue { get { if (Value == null) return ""; if (Value is System.Collections.IList list && !(Value is string)) return list.Count + " 项"; if (IsComplex) return ""; return Value?.ToString() ?? ""; } } /// 是否为复杂对象(需展开子属性) public bool IsComplex => Value != null && !IsSimpleType(Value.GetType()); private static bool IsSimpleType(Type type) { return type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime); } private ObservableCollection _subItems; public ObservableCollection SubItems { get { if (_subItems == null && IsComplex) { _subItems = new ObservableCollection(); // 列表/数组:按索引展开元素 if (Value is System.Collections.IList list && !(Value is string)) { for (int i = 0; i < list.Count; i++) { _subItems.Add(new ResultItem { Key = "[" + i + "]", Value = list[i] }); } } else { // 普通对象:反射公开属性 foreach (var prop in Value.GetType().GetProperties( System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) { try { var val = prop.GetValue(Value); _subItems.Add(new ResultItem { Key = prop.Name, Value = val }); } catch { } } } } return _subItems ?? new ObservableCollection(); } } } }