From 2eb3fed4d06602e38a046630ee74278f2e84faa9 Mon Sep 17 00:00:00 2001 From: "zhengxuan.zhang" Date: Wed, 6 May 2026 11:46:49 +0800 Subject: [PATCH] =?UTF-8?q?=20=20=201=E3=80=81=E5=BD=93=E5=89=8D=E6=A0=B9?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E9=80=89=E6=8B=A9=E4=B8=AD=E6=98=AF=20?= =?UTF-8?q?=E9=AB=98=E4=BA=AE=E8=83=8C=E6=99=AF=E5=92=8C=E6=96=87=E5=AD=97?= =?UTF-8?q?=E4=B8=8D=E5=8D=8F=E8=B0=83=EF=BC=8C=E6=96=87=E5=AD=97=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E9=BB=91=E8=89=B2=20=20=20=202=E3=80=81=E5=BB=B6?= =?UTF-8?q?=E6=97=B6=E8=8A=82=E7=82=B9=E6=89=A7=E8=A1=8C=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E8=BF=9B=E5=BA=A6=E6=9D=A1=E6=98=BE=E7=A4=BA=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=A4=B1=E6=95=88=EF=BC=8C=E4=BF=AE=E5=A4=8D=20=20=20=203?= =?UTF-8?q?=E3=80=81=E8=87=AA=E5=8A=A8=E6=89=A7=E8=A1=8C=EF=BC=8C=E5=BD=93?= =?UTF-8?q?=E5=8A=A0=E8=BD=BDCNC=E5=90=8E=EF=BC=8C=E7=82=B9=E5=87=BBrabbio?= =?UTF-8?q?n=E4=B8=AD=E8=BF=90=E8=A1=8C=EF=BC=8C=E6=AD=A4=E6=97=B6?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E7=9C=8B=E5=88=B0=E6=89=A7=E8=A1=8C=E4=B8=AD?= =?UTF-8?q?=EF=BC=8C=E5=B7=B2=E6=89=A7=E8=A1=8C=EF=BC=8C=E7=AD=89=E9=AB=98?= =?UTF-8?q?=E4=BA=AE=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/Cnc/CncExecutionService.cs | 16 ++++- .../Services/Cnc/ICncExecutionService.cs | 7 +- .../ViewModels/Cnc/CncEditorViewModel.cs | 6 +- .../ViewModels/Cnc/CncNodeViewModel.cs | 20 ++++++ .../PipelineEditorViewModel.cs | 6 +- XplorePlane/Views/Cnc/CncPageView.xaml | 68 +++++++++++++++++-- XplorePlane/Views/Cnc/CncPageView.xaml.cs | 36 ++-------- .../ImageProcessing/PipelineEditorView.xaml | 27 ++------ 8 files changed, 121 insertions(+), 65 deletions(-) diff --git a/XplorePlane/Services/Cnc/CncExecutionService.cs b/XplorePlane/Services/Cnc/CncExecutionService.cs index 3fdae6c..9d1e0f8 100644 --- a/XplorePlane/Services/Cnc/CncExecutionService.cs +++ b/XplorePlane/Services/Cnc/CncExecutionService.cs @@ -95,7 +95,7 @@ namespace XplorePlane.Services.Cnc break; } - progress?.Report(new CncNodeExecutionProgress(node.Id, NodeExecutionState.Running)); + progress?.Report(new CncNodeExecutionProgress(node.Id, NodeExecutionState.Running, ProgressPercent: 0)); bool nodeSucceeded = true; @@ -106,7 +106,7 @@ namespace XplorePlane.Services.Cnc case WaitDelayNode waitNode: try { - await ExecuteWaitDelayWithProgressAsync(waitNode, cancellationToken); + await ExecuteWaitDelayWithProgressAsync(waitNode, progress, cancellationToken); } catch (OperationCanceledException) { @@ -324,11 +324,17 @@ namespace XplorePlane.Services.Cnc return ms.ToArray(); } - private static async Task ExecuteWaitDelayWithProgressAsync(WaitDelayNode waitNode, CancellationToken cancellationToken) + private static async Task ExecuteWaitDelayWithProgressAsync( + WaitDelayNode waitNode, + IProgress progress, + CancellationToken cancellationToken) { int totalMs = waitNode.DelayMilliseconds; if (totalMs <= 0) + { + progress?.Report(new CncNodeExecutionProgress(waitNode.Id, NodeExecutionState.Running, ProgressPercent: 100)); return; + } const int tickMs = 50; @@ -341,6 +347,10 @@ namespace XplorePlane.Services.Cnc int delay = Math.Min(tickMs, remaining); await Task.Delay(delay, cancellationToken); elapsed += delay; + progress?.Report(new CncNodeExecutionProgress( + waitNode.Id, + NodeExecutionState.Running, + ProgressPercent: elapsed * 100d / totalMs)); } } } diff --git a/XplorePlane/Services/Cnc/ICncExecutionService.cs b/XplorePlane/Services/Cnc/ICncExecutionService.cs index 504c60b..602c655 100644 --- a/XplorePlane/Services/Cnc/ICncExecutionService.cs +++ b/XplorePlane/Services/Cnc/ICncExecutionService.cs @@ -17,6 +17,11 @@ namespace XplorePlane.Services.Cnc /// /// Progress report for a single CNC node execution. /// ResultImage is non-null when an InspectionModuleNode produces output. + /// ProgressPercent is used by long-running nodes such as WaitDelayNode. /// - public record CncNodeExecutionProgress(Guid NodeId, NodeExecutionState State, BitmapSource ResultImage = null); + public record CncNodeExecutionProgress( + Guid NodeId, + NodeExecutionState State, + BitmapSource ResultImage = null, + double? ProgressPercent = null); } diff --git a/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs b/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs index a3427b5..fd6804b 100644 --- a/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs +++ b/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs @@ -455,6 +455,7 @@ namespace XplorePlane.ViewModels.Cnc private async Task ExecuteRunAsync() { _cts = new CancellationTokenSource(); + ResetAllNodeStates(); IsRunning = true; HasExecutionError = false; ExecutionError = null; @@ -482,7 +483,6 @@ namespace XplorePlane.ViewModels.Cnc finally { IsRunning = false; - ResetAllNodeStates(); _cts?.Dispose(); _cts = null; } @@ -499,6 +499,7 @@ namespace XplorePlane.ViewModels.Cnc if (nodeVm != null) { nodeVm.ExecutionState = progress.State; + nodeVm.ExecutionProgressPercent = progress.ProgressPercent ?? (progress.State == NodeExecutionState.Succeeded ? 100d : 0d); if (progress.State == NodeExecutionState.Running) StatusMessage = $"正在执行节点:{nodeVm.Name}({nodeVm.Index + 1}/{_currentProgram?.Nodes?.Count ?? 0})"; else if (progress.State == NodeExecutionState.Succeeded) @@ -519,7 +520,10 @@ namespace XplorePlane.ViewModels.Cnc private void ResetAllNodeStates() { foreach (var node in Nodes) + { node.ExecutionState = NodeExecutionState.Idle; + node.ExecutionProgressPercent = 0; + } } private void RaiseEditCommandsCanExecuteChanged() diff --git a/XplorePlane/ViewModels/Cnc/CncNodeViewModel.cs b/XplorePlane/ViewModels/Cnc/CncNodeViewModel.cs index fb8d83f..ac46829 100644 --- a/XplorePlane/ViewModels/Cnc/CncNodeViewModel.cs +++ b/XplorePlane/ViewModels/Cnc/CncNodeViewModel.cs @@ -16,6 +16,7 @@ namespace XplorePlane.ViewModels.Cnc private string _icon; private bool _isExpanded = true; private NodeExecutionState _executionState = NodeExecutionState.Idle; + private double _executionProgressPercent; /// 执行后缓存的流水线输出图像(仅 InspectionModuleNode) public BitmapSource ResultImage { get; set; } @@ -72,6 +73,7 @@ namespace XplorePlane.ViewModels.Cnc RaisePropertyChanged(nameof(IsRunningNode)); RaisePropertyChanged(nameof(IsSucceededNode)); RaisePropertyChanged(nameof(IsFailedNode)); + RaisePropertyChanged(nameof(IsDelayProgressVisible)); } } } @@ -79,6 +81,21 @@ namespace XplorePlane.ViewModels.Cnc public bool IsRunningNode => ExecutionState == NodeExecutionState.Running; public bool IsSucceededNode => ExecutionState == NodeExecutionState.Succeeded; public bool IsFailedNode => ExecutionState == NodeExecutionState.Failed; + public bool IsDelayProgressVisible => IsWaitDelay && IsRunningNode; + + public double ExecutionProgressPercent + { + get => _executionProgressPercent; + set + { + if (SetProperty(ref _executionProgressPercent, Math.Clamp(value, 0d, 100d))) + { + RaisePropertyChanged(nameof(ExecutionProgressText)); + } + } + } + + public string ExecutionProgressText => $"{ExecutionProgressPercent:0}%"; public bool IsReferencePoint => _model is ReferencePointNode; public bool IsSaveNode => _model is SaveNodeNode; @@ -567,6 +584,9 @@ namespace XplorePlane.ViewModels.Cnc RaisePropertyChanged(nameof(IsRunningNode)); RaisePropertyChanged(nameof(IsSucceededNode)); RaisePropertyChanged(nameof(IsFailedNode)); + RaisePropertyChanged(nameof(IsDelayProgressVisible)); + RaisePropertyChanged(nameof(ExecutionProgressPercent)); + RaisePropertyChanged(nameof(ExecutionProgressText)); } private enum MotionAxis diff --git a/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs index 07242f4..2c6b3fe 100644 --- a/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs +++ b/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs @@ -32,7 +32,7 @@ namespace XplorePlane.ViewModels private PipelineNodeViewModel _selectedNode; private BitmapSource _sourceImage; private BitmapSource _previewImage; - private string _pipelineName = "新建流水线"; + private string _pipelineName = "新建模块"; private string _selectedDevice = string.Empty; private bool _isExecuting; private bool _isStatusError; @@ -646,7 +646,7 @@ namespace XplorePlane.ViewModels var dialog = new SaveFileDialog { - Filter = "XP 模块流水线 (*.xpm)|*.xpm", + Filter = "XP 模块 (*.xpm)|*.xpm", DefaultExt = ".xpm", AddExtension = true, FileName = PipelineName, @@ -698,7 +698,7 @@ namespace XplorePlane.ViewModels { var dialog = new OpenFileDialog { - Filter = "XP 模块流水线 (*.xpm)|*.xpm", + Filter = "XP 模块 (*.xpm)|*.xpm", DefaultExt = ".xpm", InitialDirectory = GetPipelineDirectory() }; diff --git a/XplorePlane/Views/Cnc/CncPageView.xaml b/XplorePlane/Views/Cnc/CncPageView.xaml index 9628cb2..18aef55 100644 --- a/XplorePlane/Views/Cnc/CncPageView.xaml +++ b/XplorePlane/Views/Cnc/CncPageView.xaml @@ -280,6 +280,7 @@ FontFamily="{StaticResource UiFont}" FontSize="12" FontWeight="SemiBold" + x:Name="ProgramRootNameText" Text="{Binding DisplayName}" TextTrimming="CharacterEllipsis" /> @@ -288,6 +289,7 @@ + @@ -305,6 +307,10 @@ BorderThickness="1" CornerRadius="4"> + + + + @@ -312,7 +318,7 @@ - + + + + + + + + + @@ -387,22 +421,30 @@ + - - - + + + - + - + + + + + + + + @@ -569,6 +611,20 @@ + + diff --git a/XplorePlane/Views/Cnc/CncPageView.xaml.cs b/XplorePlane/Views/Cnc/CncPageView.xaml.cs index c69f085..9addaf5 100644 --- a/XplorePlane/Views/Cnc/CncPageView.xaml.cs +++ b/XplorePlane/Views/Cnc/CncPageView.xaml.cs @@ -19,14 +19,6 @@ namespace XplorePlane.Views.Cnc /// public partial class CncPageView : UserControl { - private static readonly Brush SelectedNodeBackground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#E7F0F7")); - private static readonly Brush SelectedNodeBorder = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#9CB9D1")); - private static readonly Brush HoverNodeBackground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F6FAFC")); - private static readonly Brush HoverNodeBorder = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#D7E4EE")); - private static readonly Brush SelectedNodeForeground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1F4E79")); - private static readonly Brush DefaultNodeForeground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#202020")); - private static readonly Brush TransparentBrush = Brushes.Transparent; - private CncInspectionModulePipelineViewModel _inspectionModulePipelineViewModel; private readonly Dictionary _textDisplayLabels = new(); private readonly Dictionary _checkDisplayLabels = new(); @@ -186,24 +178,9 @@ namespace XplorePlane.Views.Cnc continue; } - if (item.IsSelected) - { - card.Background = SelectedNodeBackground; - card.BorderBrush = SelectedNodeBorder; - ApplyNodeTextForeground(card, SelectedNodeForeground); - } - else if (card.IsMouseOver) - { - card.Background = HoverNodeBackground; - card.BorderBrush = HoverNodeBorder; - ApplyNodeTextForeground(card, DefaultNodeForeground); - } - else - { - card.Background = TransparentBrush; - card.BorderBrush = TransparentBrush; - ApplyNodeTextForeground(card, DefaultNodeForeground); - } + card.ClearValue(Border.BackgroundProperty); + card.ClearValue(Border.BorderBrushProperty); + ClearNodeTextForeground(card); } } @@ -318,14 +295,11 @@ namespace XplorePlane.Views.Cnc panel.Children.Insert(index + 1, companionControl); } - private static void ApplyNodeTextForeground(Border card, Brush foreground) + private static void ClearNodeTextForeground(Border card) { foreach (var textBlock in FindVisualDescendants(card)) { - if (textBlock.Visibility == Visibility.Visible) - { - textBlock.Foreground = foreground; - } + textBlock.ClearValue(TextBlock.ForegroundProperty); } } diff --git a/XplorePlane/Views/ImageProcessing/PipelineEditorView.xaml b/XplorePlane/Views/ImageProcessing/PipelineEditorView.xaml index 184061c..90289e3 100644 --- a/XplorePlane/Views/ImageProcessing/PipelineEditorView.xaml +++ b/XplorePlane/Views/ImageProcessing/PipelineEditorView.xaml @@ -80,38 +80,25 @@ Orientation="Horizontal">