补充依赖
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