补充依赖
This commit is contained in:
@@ -0,0 +1,401 @@
|
|||||||
|
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.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using XplorePlane.Models;
|
||||||
|
using XplorePlane.Services;
|
||||||
|
using XP.Common.Logging.Interfaces;
|
||||||
|
|
||||||
|
namespace XplorePlane.ViewModels.Cnc
|
||||||
|
{
|
||||||
|
public class CncInspectionModulePipelineViewModel : BindableBase, IPipelineEditorHostViewModel
|
||||||
|
{
|
||||||
|
private readonly CncEditorViewModel _editorViewModel;
|
||||||
|
private readonly IImageProcessingService _imageProcessingService;
|
||||||
|
private readonly IPipelinePersistenceService _persistenceService;
|
||||||
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
|
private CncNodeViewModel _activeModuleNode;
|
||||||
|
private PipelineNodeViewModel _selectedNode;
|
||||||
|
private string _statusMessage = "请选择检测模块以编辑其流水线。";
|
||||||
|
private string _currentFilePath;
|
||||||
|
private bool _isSynchronizing;
|
||||||
|
|
||||||
|
public CncInspectionModulePipelineViewModel(
|
||||||
|
CncEditorViewModel editorViewModel,
|
||||||
|
IImageProcessingService imageProcessingService,
|
||||||
|
IPipelinePersistenceService persistenceService,
|
||||||
|
ILoggerService logger)
|
||||||
|
{
|
||||||
|
_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>();
|
||||||
|
|
||||||
|
PipelineNodes = new ObservableCollection<PipelineNodeViewModel>();
|
||||||
|
|
||||||
|
AddOperatorCommand = new DelegateCommand<string>(AddOperator, _ => HasActiveModule);
|
||||||
|
RemoveOperatorCommand = new DelegateCommand<PipelineNodeViewModel>(RemoveOperator);
|
||||||
|
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());
|
||||||
|
|
||||||
|
_editorViewModel.PropertyChanged += OnEditorPropertyChanged;
|
||||||
|
RefreshFromSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<PipelineNodeViewModel> PipelineNodes { get; }
|
||||||
|
|
||||||
|
public PipelineNodeViewModel SelectedNode
|
||||||
|
{
|
||||||
|
get => _selectedNode;
|
||||||
|
set => SetProperty(ref _selectedNode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StatusMessage
|
||||||
|
{
|
||||||
|
get => _statusMessage;
|
||||||
|
private set => SetProperty(ref _statusMessage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 MoveNodeUpCommand { get; }
|
||||||
|
|
||||||
|
public ICommand MoveNodeDownCommand { get; }
|
||||||
|
|
||||||
|
public ICommand NewPipelineCommand { get; }
|
||||||
|
|
||||||
|
public ICommand SavePipelineCommand { get; }
|
||||||
|
|
||||||
|
public ICommand SaveAsPipelineCommand { get; }
|
||||||
|
|
||||||
|
public ICommand LoadPipelineCommand { get; }
|
||||||
|
|
||||||
|
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;
|
||||||
|
StatusMessage = "请选择检测模块以编辑其流水线。";
|
||||||
|
RaiseModuleVisibilityChanged();
|
||||||
|
RaiseCommandCanExecuteChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_activeModuleNode = selected;
|
||||||
|
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;
|
||||||
|
PersistActiveModule($"已添加算子:{displayName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveOperator(PipelineNodeViewModel node)
|
||||||
|
{
|
||||||
|
if (!HasActiveModule || node == null || !PipelineNodes.Contains(node))
|
||||||
|
return;
|
||||||
|
|
||||||
|
PipelineNodes.Remove(node);
|
||||||
|
RenumberNodes();
|
||||||
|
|
||||||
|
if (SelectedNode == node)
|
||||||
|
{
|
||||||
|
SelectedNode = PipelineNodes.LastOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
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();
|
||||||
|
PersistActiveModule($"已下移算子:{node.DisplayName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NewPipeline()
|
||||||
|
{
|
||||||
|
if (!HasActiveModule)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PipelineNodes.Clear();
|
||||||
|
SelectedNode = null;
|
||||||
|
_currentFilePath = null;
|
||||||
|
PersistActiveModule("已为当前检测模块新建空流水线。");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SavePipelineToModule()
|
||||||
|
{
|
||||||
|
if (!HasActiveModule)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PersistActiveModule("当前检测模块流水线已同步到 CNC 程序。");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async System.Threading.Tasks.Task SaveAsPipelineAsync()
|
||||||
|
{
|
||||||
|
if (!HasActiveModule)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dialog = new SaveFileDialog
|
||||||
|
{
|
||||||
|
Filter = "图像处理流水线 (*.imw)|*.imw",
|
||||||
|
FileName = GetActivePipelineName()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var model = BuildPipelineModel();
|
||||||
|
await _persistenceService.SaveAsync(model, dialog.FileName);
|
||||||
|
_currentFilePath = dialog.FileName;
|
||||||
|
StatusMessage = $"已导出模块流水线:{Path.GetFileName(dialog.FileName)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async System.Threading.Tasks.Task LoadPipelineAsync()
|
||||||
|
{
|
||||||
|
if (!HasActiveModule)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Filter = "图像处理流水线 (*.imw)|*.imw"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var model = await _persistenceService.LoadAsync(dialog.FileName);
|
||||||
|
_currentFilePath = dialog.FileName;
|
||||||
|
LoadPipelineModel(model);
|
||||||
|
PersistActiveModule($"已加载模块流水线:{model.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadPipelineModel(PipelineModel pipeline)
|
||||||
|
{
|
||||||
|
_isSynchronizing = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PipelineNodes.Clear();
|
||||||
|
SelectedNode = 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();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 string GetActivePipelineName()
|
||||||
|
{
|
||||||
|
if (!HasActiveModule)
|
||||||
|
return "InspectionModulePipeline";
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(_activeModuleNode.Pipeline?.Name)
|
||||||
|
? _activeModuleNode.Name
|
||||||
|
: _activeModuleNode.Pipeline.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenumberNodes()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < PipelineNodes.Count; i++)
|
||||||
|
{
|
||||||
|
PipelineNodes[i].Order = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseModuleVisibilityChanged()
|
||||||
|
{
|
||||||
|
RaisePropertyChanged(nameof(HasActiveModule));
|
||||||
|
RaisePropertyChanged(nameof(EditorVisibility));
|
||||||
|
RaisePropertyChanged(nameof(EmptyStateVisibility));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseCommandCanExecuteChanged()
|
||||||
|
{
|
||||||
|
(AddOperatorCommand as DelegateCommand<string>)?.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace XplorePlane.ViewModels
|
||||||
|
{
|
||||||
|
public interface IPipelineEditorHostViewModel
|
||||||
|
{
|
||||||
|
ObservableCollection<PipelineNodeViewModel> PipelineNodes { get; }
|
||||||
|
|
||||||
|
PipelineNodeViewModel SelectedNode { get; set; }
|
||||||
|
|
||||||
|
string StatusMessage { get; }
|
||||||
|
|
||||||
|
ICommand AddOperatorCommand { get; }
|
||||||
|
|
||||||
|
ICommand RemoveOperatorCommand { get; }
|
||||||
|
|
||||||
|
ICommand MoveNodeUpCommand { get; }
|
||||||
|
|
||||||
|
ICommand MoveNodeDownCommand { get; }
|
||||||
|
|
||||||
|
ICommand NewPipelineCommand { get; }
|
||||||
|
|
||||||
|
ICommand SavePipelineCommand { get; }
|
||||||
|
|
||||||
|
ICommand SaveAsPipelineCommand { get; }
|
||||||
|
|
||||||
|
ICommand LoadPipelineCommand { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user