673 lines
25 KiB
C#
673 lines
25 KiB
C#
using Microsoft.Win32;
|
|
using Prism.Commands;
|
|
using Prism.Mvvm;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media.Imaging;
|
|
using XplorePlane.Events;
|
|
using XplorePlane.Models;
|
|
using XplorePlane.Services;
|
|
using XplorePlane.Services.MainViewport;
|
|
using XplorePlane.Views;
|
|
using XP.Common.Logging.Interfaces;
|
|
using Prism.Events;
|
|
|
|
namespace XplorePlane.ViewModels.Cnc
|
|
{
|
|
public class CncInspectionModulePipelineViewModel : BindableBase, IPipelineEditorHostViewModel
|
|
{
|
|
private readonly CncEditorViewModel _editorViewModel;
|
|
private readonly IImageProcessingService _imageProcessingService;
|
|
private readonly IPipelinePersistenceService _persistenceService;
|
|
private readonly IPipelineExecutionService _executionService;
|
|
private readonly IMainViewportService _mainViewportService;
|
|
private readonly IEventAggregator _eventAggregator;
|
|
private readonly ILoggerService _logger;
|
|
|
|
private CncNodeViewModel _activeModuleNode;
|
|
private PipelineNodeViewModel _selectedNode;
|
|
private string _statusMessage = "请选择检测模块以编辑其流水线。";
|
|
private string _pipelineFileDisplayName = "未命名模块.xpm";
|
|
private string _currentFilePath;
|
|
private PipelineNodeViewModel _executionEndNode;
|
|
private bool _isSynchronizing;
|
|
private CancellationTokenSource _debounceCts;
|
|
|
|
private const int DebounceDelayMs = 300;
|
|
|
|
public CncInspectionModulePipelineViewModel(
|
|
CncEditorViewModel editorViewModel,
|
|
IImageProcessingService imageProcessingService,
|
|
IPipelinePersistenceService persistenceService,
|
|
ILoggerService logger,
|
|
IPipelineExecutionService executionService = null,
|
|
IMainViewportService mainViewportService = null,
|
|
IEventAggregator eventAggregator = null)
|
|
{
|
|
_editorViewModel = editorViewModel ?? throw new ArgumentNullException(nameof(editorViewModel));
|
|
_imageProcessingService = imageProcessingService ?? throw new ArgumentNullException(nameof(imageProcessingService));
|
|
_persistenceService = persistenceService ?? throw new ArgumentNullException(nameof(persistenceService));
|
|
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<CncInspectionModulePipelineViewModel>();
|
|
_executionService = executionService;
|
|
_mainViewportService = mainViewportService;
|
|
_eventAggregator = eventAggregator;
|
|
|
|
PipelineNodes = new ObservableCollection<PipelineNodeViewModel>();
|
|
|
|
AddOperatorCommand = new DelegateCommand<string>(AddOperator, _ => HasActiveModule);
|
|
RemoveOperatorCommand = new DelegateCommand<PipelineNodeViewModel>(RemoveOperator);
|
|
ReorderOperatorCommand = new DelegateCommand<PipelineReorderArgs>(ReorderOperator);
|
|
ToggleOperatorEnabledCommand = new DelegateCommand<PipelineNodeViewModel>(ToggleOperatorEnabled);
|
|
ExecuteToNodeCommand = new DelegateCommand<PipelineNodeViewModel>(ExecuteToNode);
|
|
ClearExecutionRangeCommand = new DelegateCommand(ClearExecutionRange);
|
|
MoveNodeUpCommand = new DelegateCommand<PipelineNodeViewModel>(MoveNodeUp);
|
|
MoveNodeDownCommand = new DelegateCommand<PipelineNodeViewModel>(MoveNodeDown);
|
|
NewPipelineCommand = new DelegateCommand(NewPipeline);
|
|
SavePipelineCommand = new DelegateCommand(SavePipelineToModule);
|
|
SaveAsPipelineCommand = new DelegateCommand(async () => await SaveAsPipelineAsync());
|
|
LoadPipelineCommand = new DelegateCommand(async () => await LoadPipelineAsync());
|
|
OpenTemplateMatchingToolCommand = new DelegateCommand(OpenTemplateMatchingTool, CanOpenTemplateMatchingTool);
|
|
|
|
_editorViewModel.PropertyChanged += OnEditorPropertyChanged;
|
|
RefreshFromSelection();
|
|
}
|
|
|
|
public ObservableCollection<PipelineNodeViewModel> PipelineNodes { get; }
|
|
|
|
public PipelineNodeViewModel SelectedNode
|
|
{
|
|
get => _selectedNode;
|
|
set
|
|
{
|
|
if (!SetProperty(ref _selectedNode, value))
|
|
return;
|
|
|
|
RaisePropertyChanged(nameof(IsTemplateMatchingNodeSelected));
|
|
(OpenTemplateMatchingToolCommand as DelegateCommand)?.RaiseCanExecuteChanged();
|
|
}
|
|
}
|
|
|
|
public bool IsTemplateMatchingNodeSelected =>
|
|
SelectedNode != null &&
|
|
string.Equals(SelectedNode.OperatorKey, "TemplateMatching", StringComparison.Ordinal);
|
|
|
|
public ICommand OpenTemplateMatchingToolCommand { get; }
|
|
|
|
public string StatusMessage
|
|
{
|
|
get => _statusMessage;
|
|
private set => SetProperty(ref _statusMessage, value);
|
|
}
|
|
|
|
public bool IsStatusError => false;
|
|
|
|
public string PipelineFileDisplayName
|
|
{
|
|
get => _pipelineFileDisplayName;
|
|
private set => SetProperty(ref _pipelineFileDisplayName, value);
|
|
}
|
|
|
|
public PipelineNodeViewModel ExecutionEndNode
|
|
{
|
|
get => _executionEndNode;
|
|
private set
|
|
{
|
|
if (SetProperty(ref _executionEndNode, value))
|
|
UpdateExecutionRangeState();
|
|
}
|
|
}
|
|
|
|
public bool HasActiveModule => _activeModuleNode?.IsInspectionModule == true;
|
|
|
|
public Visibility EditorVisibility => HasActiveModule ? Visibility.Visible : Visibility.Collapsed;
|
|
|
|
public Visibility EmptyStateVisibility => HasActiveModule ? Visibility.Collapsed : Visibility.Visible;
|
|
|
|
public ICommand AddOperatorCommand { get; }
|
|
|
|
public ICommand RemoveOperatorCommand { get; }
|
|
|
|
public ICommand ReorderOperatorCommand { get; }
|
|
|
|
public ICommand ToggleOperatorEnabledCommand { get; }
|
|
|
|
public ICommand ExecuteToNodeCommand { get; }
|
|
|
|
public ICommand ClearExecutionRangeCommand { get; }
|
|
|
|
public ICommand MoveNodeUpCommand { get; }
|
|
|
|
public ICommand MoveNodeDownCommand { get; }
|
|
|
|
public ICommand NewPipelineCommand { get; }
|
|
|
|
public ICommand SavePipelineCommand { get; }
|
|
|
|
public ICommand SaveAsPipelineCommand { get; }
|
|
|
|
public ICommand LoadPipelineCommand { get; }
|
|
|
|
private static string? GetPipelineRecipeDirectory(string? currentFilePath)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(currentFilePath))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
var full = Path.GetFullPath(currentFilePath);
|
|
return Path.GetDirectoryName(full);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private bool CanOpenTemplateMatchingTool() => IsTemplateMatchingNodeSelected;
|
|
|
|
private void OpenTemplateMatchingTool()
|
|
{
|
|
if (!CanOpenTemplateMatchingTool() || SelectedNode == null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
var recipeDir = GetPipelineRecipeDirectory(_currentFilePath);
|
|
var source = _mainViewportService?.CurrentDisplayImage as BitmapSource
|
|
?? _mainViewportService?.LatestManualImage as BitmapSource;
|
|
var win = new TemplateMatchingToolWindow(SelectedNode, source, _imageProcessingService, _logger, recipeDir);
|
|
win.Owner = Application.Current?.MainWindow;
|
|
win.ShowDialog();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Warn("打开模板匹配工具窗失败: {Message}", ex.Message);
|
|
StatusMessage = "无法打开模板匹配工具:" + ex.Message;
|
|
}
|
|
}
|
|
|
|
private void OnEditorPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(CncEditorViewModel.SelectedNode))
|
|
{
|
|
RefreshFromSelection();
|
|
}
|
|
}
|
|
|
|
private void RefreshFromSelection()
|
|
{
|
|
var selected = _editorViewModel.SelectedNode;
|
|
if (selected == null || !selected.IsInspectionModule)
|
|
{
|
|
_activeModuleNode = null;
|
|
PipelineNodes.Clear();
|
|
SelectedNode = null;
|
|
PipelineFileDisplayName = "未命名模块.xpm";
|
|
StatusMessage = "请选择检测模块以编辑其流水线。";
|
|
RaiseModuleVisibilityChanged();
|
|
RaiseCommandCanExecuteChanged();
|
|
return;
|
|
}
|
|
|
|
_activeModuleNode = selected;
|
|
_currentFilePath = null;
|
|
LoadPipelineModel(_activeModuleNode.Pipeline ?? new PipelineModel
|
|
{
|
|
Name = _activeModuleNode.Name
|
|
});
|
|
RaiseModuleVisibilityChanged();
|
|
RaiseCommandCanExecuteChanged();
|
|
}
|
|
|
|
private void AddOperator(string operatorKey)
|
|
{
|
|
if (!HasActiveModule || string.IsNullOrWhiteSpace(operatorKey))
|
|
return;
|
|
|
|
var available = _imageProcessingService.GetAvailableProcessors();
|
|
if (!available.Contains(operatorKey))
|
|
{
|
|
StatusMessage = $"算子 '{operatorKey}' 未注册。";
|
|
return;
|
|
}
|
|
|
|
var displayName = _imageProcessingService.GetProcessorDisplayName(operatorKey) ?? operatorKey;
|
|
var icon = ProcessorUiMetadata.GetOperatorIcon(operatorKey);
|
|
var node = new PipelineNodeViewModel(operatorKey, displayName, icon)
|
|
{
|
|
Order = PipelineNodes.Count
|
|
};
|
|
|
|
LoadNodeParameters(node, null);
|
|
PipelineNodes.Add(node);
|
|
SelectedNode = node;
|
|
UpdateExecutionRangeState();
|
|
PersistActiveModule($"已添加算子:{displayName}");
|
|
}
|
|
|
|
private void RemoveOperator(PipelineNodeViewModel node)
|
|
{
|
|
if (!HasActiveModule || node == null || !PipelineNodes.Contains(node))
|
|
return;
|
|
|
|
var removedIndex = PipelineNodes.IndexOf(node);
|
|
PipelineNodes.Remove(node);
|
|
RenumberNodes();
|
|
if (ReferenceEquals(ExecutionEndNode, node))
|
|
ExecutionEndNode = null;
|
|
else
|
|
UpdateExecutionRangeState();
|
|
SelectNeighborAfterRemoval(removedIndex);
|
|
|
|
PersistActiveModule($"已移除算子:{node.DisplayName}");
|
|
}
|
|
|
|
private void MoveNodeUp(PipelineNodeViewModel node)
|
|
{
|
|
if (!HasActiveModule || node == null)
|
|
return;
|
|
|
|
var index = PipelineNodes.IndexOf(node);
|
|
if (index <= 0)
|
|
return;
|
|
|
|
PipelineNodes.Move(index, index - 1);
|
|
RenumberNodes();
|
|
UpdateExecutionRangeState();
|
|
PersistActiveModule($"已上移算子:{node.DisplayName}");
|
|
}
|
|
|
|
private void ReorderOperator(PipelineReorderArgs args)
|
|
{
|
|
if (!HasActiveModule || args == null)
|
|
return;
|
|
|
|
var oldIndex = args.OldIndex;
|
|
var newIndex = args.NewIndex;
|
|
if (oldIndex < 0 || oldIndex >= PipelineNodes.Count)
|
|
return;
|
|
|
|
if (newIndex < 0 || newIndex >= PipelineNodes.Count || oldIndex == newIndex)
|
|
return;
|
|
|
|
var node = PipelineNodes[oldIndex];
|
|
PipelineNodes.Move(oldIndex, newIndex);
|
|
RenumberNodes();
|
|
UpdateExecutionRangeState();
|
|
SelectedNode = node;
|
|
PersistActiveModule($"已调整算子顺序:{node.DisplayName}");
|
|
}
|
|
|
|
private void MoveNodeDown(PipelineNodeViewModel node)
|
|
{
|
|
if (!HasActiveModule || node == null)
|
|
return;
|
|
|
|
var index = PipelineNodes.IndexOf(node);
|
|
if (index < 0 || index >= PipelineNodes.Count - 1)
|
|
return;
|
|
|
|
PipelineNodes.Move(index, index + 1);
|
|
RenumberNodes();
|
|
UpdateExecutionRangeState();
|
|
PersistActiveModule($"已下移算子:{node.DisplayName}");
|
|
}
|
|
|
|
private void ToggleOperatorEnabled(PipelineNodeViewModel node)
|
|
{
|
|
if (!HasActiveModule || node == null || !PipelineNodes.Contains(node))
|
|
return;
|
|
|
|
node.IsEnabled = !node.IsEnabled;
|
|
SelectedNode = node;
|
|
PersistActiveModule(node.IsEnabled
|
|
? $"已启用算子:{node.DisplayName}"
|
|
: $"已停用算子:{node.DisplayName}");
|
|
}
|
|
|
|
private void ExecuteToNode(PipelineNodeViewModel node)
|
|
{
|
|
if (!HasActiveModule || node == null || !PipelineNodes.Contains(node))
|
|
return;
|
|
|
|
SelectedNode = node;
|
|
ExecutionEndNode = node;
|
|
PersistActiveModule($"已设置执行截止点:{node.DisplayName}");
|
|
}
|
|
|
|
private void ClearExecutionRange()
|
|
{
|
|
if (!HasActiveModule || ExecutionEndNode == null)
|
|
return;
|
|
|
|
ExecutionEndNode = null;
|
|
PersistActiveModule("已切换为执行全部节点");
|
|
}
|
|
|
|
private void NewPipeline()
|
|
{
|
|
if (!HasActiveModule)
|
|
return;
|
|
|
|
PipelineNodes.Clear();
|
|
SelectedNode = null;
|
|
ExecutionEndNode = null;
|
|
_currentFilePath = null;
|
|
PipelineFileDisplayName = GetActivePipelineFileDisplayName();
|
|
PersistActiveModule("已为当前检测模块新建空流水线。");
|
|
}
|
|
|
|
private void SavePipelineToModule()
|
|
{
|
|
if (!HasActiveModule)
|
|
return;
|
|
|
|
PersistActiveModule("当前检测模块流水线已同步到 CNC 程序。");
|
|
}
|
|
|
|
private async System.Threading.Tasks.Task SaveAsPipelineAsync()
|
|
{
|
|
if (!HasActiveModule)
|
|
return;
|
|
|
|
var dialog = new SaveFileDialog
|
|
{
|
|
Filter = "XP 模块流水线 (*.xpm)|*.xpm",
|
|
DefaultExt = ".xpm",
|
|
AddExtension = true,
|
|
FileName = GetActivePipelineName()
|
|
};
|
|
|
|
if (dialog.ShowDialog() != true)
|
|
return;
|
|
|
|
var model = BuildPipelineModel();
|
|
await _persistenceService.SaveAsync(model, dialog.FileName);
|
|
_currentFilePath = dialog.FileName;
|
|
PipelineFileDisplayName = FormatPipelinePath(dialog.FileName);
|
|
StatusMessage = $"已导出模块流水线:{Path.GetFileName(dialog.FileName)}";
|
|
}
|
|
|
|
private async System.Threading.Tasks.Task LoadPipelineAsync()
|
|
{
|
|
if (!HasActiveModule)
|
|
return;
|
|
|
|
var dialog = new OpenFileDialog
|
|
{
|
|
Filter = "XP 模块流水线 (*.xpm)|*.xpm",
|
|
DefaultExt = ".xpm"
|
|
};
|
|
|
|
if (dialog.ShowDialog() != true)
|
|
return;
|
|
|
|
var model = await _persistenceService.LoadAsync(dialog.FileName);
|
|
_currentFilePath = dialog.FileName;
|
|
PipelineFileDisplayName = FormatPipelinePath(dialog.FileName);
|
|
LoadPipelineModel(model);
|
|
PersistActiveModule($"已加载模块流水线:{model.Name}");
|
|
}
|
|
|
|
private void LoadPipelineModel(PipelineModel pipeline)
|
|
{
|
|
_isSynchronizing = true;
|
|
try
|
|
{
|
|
PipelineNodes.Clear();
|
|
SelectedNode = null;
|
|
ExecutionEndNode = null;
|
|
|
|
var orderedNodes = (pipeline?.Nodes ?? new List<PipelineNodeModel>())
|
|
.OrderBy(node => node.Order)
|
|
.ToList();
|
|
|
|
foreach (var nodeModel in orderedNodes)
|
|
{
|
|
var displayName = _imageProcessingService.GetProcessorDisplayName(nodeModel.OperatorKey) ?? nodeModel.OperatorKey;
|
|
var icon = ProcessorUiMetadata.GetOperatorIcon(nodeModel.OperatorKey);
|
|
var node = new PipelineNodeViewModel(nodeModel.OperatorKey, displayName, icon)
|
|
{
|
|
Order = nodeModel.Order,
|
|
IsEnabled = nodeModel.IsEnabled
|
|
};
|
|
|
|
LoadNodeParameters(node, nodeModel.Parameters);
|
|
PipelineNodes.Add(node);
|
|
}
|
|
|
|
SelectedNode = PipelineNodes.FirstOrDefault();
|
|
UpdateExecutionRangeState();
|
|
if (string.IsNullOrEmpty(_currentFilePath))
|
|
PipelineFileDisplayName = GetActivePipelineFileDisplayName();
|
|
StatusMessage = HasActiveModule
|
|
? $"正在编辑检测模块:{_activeModuleNode.Name}"
|
|
: "请选择检测模块以编辑其流水线。";
|
|
}
|
|
finally
|
|
{
|
|
_isSynchronizing = false;
|
|
}
|
|
}
|
|
|
|
private void LoadNodeParameters(PipelineNodeViewModel node, IDictionary<string, object> savedValues)
|
|
{
|
|
var parameterDefinitions = _imageProcessingService.GetProcessorParameters(node.OperatorKey);
|
|
if (parameterDefinitions == null)
|
|
return;
|
|
|
|
node.Parameters.Clear();
|
|
foreach (var definition in parameterDefinitions)
|
|
{
|
|
var parameterVm = new ProcessorParameterVM(definition);
|
|
if (savedValues != null && savedValues.TryGetValue(definition.Name, out var savedValue))
|
|
{
|
|
parameterVm.Value = ConvertSavedValue(savedValue, definition.ValueType);
|
|
}
|
|
|
|
parameterVm.PropertyChanged += (_, e) =>
|
|
{
|
|
if (e.PropertyName == nameof(ProcessorParameterVM.Value) && !_isSynchronizing)
|
|
{
|
|
PersistActiveModule($"已更新参数:{parameterVm.DisplayName}");
|
|
}
|
|
};
|
|
node.Parameters.Add(parameterVm);
|
|
}
|
|
}
|
|
|
|
private void PersistActiveModule(string statusMessage)
|
|
{
|
|
if (!HasActiveModule || _isSynchronizing)
|
|
return;
|
|
|
|
_activeModuleNode.Pipeline = BuildPipelineModel();
|
|
StatusMessage = statusMessage;
|
|
TriggerDebouncedPreview();
|
|
}
|
|
|
|
private void TriggerDebouncedPreview()
|
|
{
|
|
if (_executionService == null || _mainViewportService == null)
|
|
return;
|
|
|
|
var sourceImage = _mainViewportService.CurrentDisplayImage as BitmapSource
|
|
?? _mainViewportService.LatestManualImage as BitmapSource;
|
|
if (sourceImage == null)
|
|
{
|
|
_logger.Debug("[图像链路][CNC] TriggerDebouncedPreview:无可用源图像,跳过");
|
|
return;
|
|
}
|
|
|
|
_debounceCts?.Cancel();
|
|
_debounceCts = new CancellationTokenSource();
|
|
var token = _debounceCts.Token;
|
|
|
|
Task.Delay(DebounceDelayMs, token).ContinueWith(t =>
|
|
{
|
|
if (!t.IsCanceled)
|
|
_ = ExecutePreviewAsync(sourceImage, token);
|
|
}, TaskScheduler.FromCurrentSynchronizationContext());
|
|
}
|
|
|
|
private async Task ExecutePreviewAsync(BitmapSource sourceImage, CancellationToken token)
|
|
{
|
|
try
|
|
{
|
|
_logger.Info("[图像链路][CNC] ExecutePreviewAsync:开始执行,节点数={Count}", PipelineNodes.Count);
|
|
var result = await _executionService.ExecutePipelineAsync(GetNodesInExecutionScope(), sourceImage, null, token);
|
|
_logger.Info("[图像链路][CNC] ExecutePreviewAsync:执行完成,推送结果图像");
|
|
_mainViewportService.SetManualImage(result, string.Empty);
|
|
_eventAggregator?.GetEvent<PipelinePreviewUpdatedEvent>()
|
|
.Publish(new PipelinePreviewUpdatedPayload(result, StatusMessage));
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
_logger.Debug("[图像链路][CNC] ExecutePreviewAsync:已取消");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error(ex, "[图像链路][CNC] ExecutePreviewAsync:执行失败");
|
|
}
|
|
}
|
|
|
|
private PipelineModel BuildPipelineModel()
|
|
{
|
|
return new PipelineModel
|
|
{
|
|
Id = _activeModuleNode?.Pipeline?.Id ?? Guid.NewGuid(),
|
|
Name = GetActivePipelineName(),
|
|
DeviceId = _activeModuleNode?.Pipeline?.DeviceId ?? string.Empty,
|
|
CreatedAt = _activeModuleNode?.Pipeline?.CreatedAt ?? DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
Nodes = PipelineNodes.Select((node, index) => new PipelineNodeModel
|
|
{
|
|
Id = node.Id,
|
|
OperatorKey = node.OperatorKey,
|
|
Order = index,
|
|
IsEnabled = node.IsEnabled,
|
|
Parameters = node.Parameters.ToDictionary(parameter => parameter.Name, parameter => parameter.Value)
|
|
}).ToList()
|
|
};
|
|
}
|
|
|
|
private IEnumerable<PipelineNodeViewModel> GetNodesInExecutionScope()
|
|
{
|
|
var orderedNodes = PipelineNodes.OrderBy(node => node.Order);
|
|
if (ExecutionEndNode == null)
|
|
return orderedNodes;
|
|
|
|
return orderedNodes.Where(node => node.Order <= ExecutionEndNode.Order);
|
|
}
|
|
|
|
private string GetActivePipelineName()
|
|
{
|
|
if (!HasActiveModule)
|
|
return "InspectionModulePipeline";
|
|
|
|
return string.IsNullOrWhiteSpace(_activeModuleNode.Pipeline?.Name)
|
|
? _activeModuleNode.Name
|
|
: _activeModuleNode.Pipeline.Name;
|
|
}
|
|
|
|
private string GetActivePipelineFileDisplayName()
|
|
{
|
|
var pipelineName = GetActivePipelineName();
|
|
return pipelineName.EndsWith(".xpm", StringComparison.OrdinalIgnoreCase)
|
|
? pipelineName
|
|
: $"{pipelineName}.xpm";
|
|
}
|
|
|
|
private static string FormatPipelinePath(string filePath)
|
|
{
|
|
return string.IsNullOrWhiteSpace(filePath)
|
|
? "未命名模块.xpm"
|
|
: Path.GetFullPath(filePath).Replace('\\', '/');
|
|
}
|
|
|
|
private void RenumberNodes()
|
|
{
|
|
for (var i = 0; i < PipelineNodes.Count; i++)
|
|
{
|
|
PipelineNodes[i].Order = i;
|
|
}
|
|
}
|
|
|
|
private void UpdateExecutionRangeState()
|
|
{
|
|
if (_executionEndNode != null && !PipelineNodes.Contains(_executionEndNode))
|
|
_executionEndNode = null;
|
|
|
|
var endOrder = _executionEndNode?.Order;
|
|
foreach (var node in PipelineNodes)
|
|
{
|
|
node.IsExecutionEndNode = endOrder.HasValue && node.Order == endOrder.Value;
|
|
node.IsSkippedByExecutionRange = endOrder.HasValue && node.Order > endOrder.Value;
|
|
}
|
|
}
|
|
|
|
private void SelectNeighborAfterRemoval(int removedIndex)
|
|
{
|
|
if (PipelineNodes.Count == 0)
|
|
{
|
|
SelectedNode = null;
|
|
return;
|
|
}
|
|
|
|
var nextIndex = removedIndex < PipelineNodes.Count
|
|
? removedIndex
|
|
: PipelineNodes.Count - 1;
|
|
SelectedNode = PipelineNodes[nextIndex];
|
|
}
|
|
|
|
private void RaiseModuleVisibilityChanged()
|
|
{
|
|
RaisePropertyChanged(nameof(HasActiveModule));
|
|
RaisePropertyChanged(nameof(EditorVisibility));
|
|
RaisePropertyChanged(nameof(EmptyStateVisibility));
|
|
}
|
|
|
|
private void RaiseCommandCanExecuteChanged()
|
|
{
|
|
(AddOperatorCommand as DelegateCommand<string>)?.RaiseCanExecuteChanged();
|
|
(OpenTemplateMatchingToolCommand as DelegateCommand)?.RaiseCanExecuteChanged();
|
|
}
|
|
|
|
private static object ConvertSavedValue(object savedValue, Type targetType)
|
|
{
|
|
if (savedValue is not JsonElement jsonElement)
|
|
return savedValue;
|
|
|
|
try
|
|
{
|
|
if (targetType == typeof(int))
|
|
return jsonElement.GetInt32();
|
|
|
|
if (targetType == typeof(double))
|
|
return jsonElement.GetDouble();
|
|
|
|
if (targetType == typeof(bool))
|
|
return jsonElement.GetBoolean();
|
|
|
|
if (targetType == typeof(string))
|
|
return jsonElement.GetString() ?? string.Empty;
|
|
|
|
return jsonElement.ToString();
|
|
}
|
|
catch
|
|
{
|
|
return jsonElement.ToString();
|
|
}
|
|
}
|
|
}
|
|
}
|