1、当前根节点选择中是 高亮背景和文字不协调,文字改为黑色

2、延时节点执行时,进度条显示功能失效,修复
   3、自动执行,当加载CNC后,点击rabbion中运行,此时没有看到执行中,已执行,等高亮规则
This commit is contained in:
zhengxuan.zhang
2026-05-06 11:46:49 +08:00
parent 48c419d777
commit 2eb3fed4d0
8 changed files with 121 additions and 65 deletions
@@ -95,7 +95,7 @@ namespace XplorePlane.Services.Cnc
break; break;
} }
progress?.Report(new CncNodeExecutionProgress(node.Id, NodeExecutionState.Running)); progress?.Report(new CncNodeExecutionProgress(node.Id, NodeExecutionState.Running, ProgressPercent: 0));
bool nodeSucceeded = true; bool nodeSucceeded = true;
@@ -106,7 +106,7 @@ namespace XplorePlane.Services.Cnc
case WaitDelayNode waitNode: case WaitDelayNode waitNode:
try try
{ {
await ExecuteWaitDelayWithProgressAsync(waitNode, cancellationToken); await ExecuteWaitDelayWithProgressAsync(waitNode, progress, cancellationToken);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -324,11 +324,17 @@ namespace XplorePlane.Services.Cnc
return ms.ToArray(); return ms.ToArray();
} }
private static async Task ExecuteWaitDelayWithProgressAsync(WaitDelayNode waitNode, CancellationToken cancellationToken) private static async Task ExecuteWaitDelayWithProgressAsync(
WaitDelayNode waitNode,
IProgress<CncNodeExecutionProgress> progress,
CancellationToken cancellationToken)
{ {
int totalMs = waitNode.DelayMilliseconds; int totalMs = waitNode.DelayMilliseconds;
if (totalMs <= 0) if (totalMs <= 0)
{
progress?.Report(new CncNodeExecutionProgress(waitNode.Id, NodeExecutionState.Running, ProgressPercent: 100));
return; return;
}
const int tickMs = 50; const int tickMs = 50;
@@ -341,6 +347,10 @@ namespace XplorePlane.Services.Cnc
int delay = Math.Min(tickMs, remaining); int delay = Math.Min(tickMs, remaining);
await Task.Delay(delay, cancellationToken); await Task.Delay(delay, cancellationToken);
elapsed += delay; elapsed += delay;
progress?.Report(new CncNodeExecutionProgress(
waitNode.Id,
NodeExecutionState.Running,
ProgressPercent: elapsed * 100d / totalMs));
} }
} }
} }
@@ -17,6 +17,11 @@ namespace XplorePlane.Services.Cnc
/// <summary> /// <summary>
/// Progress report for a single CNC node execution. /// Progress report for a single CNC node execution.
/// ResultImage is non-null when an InspectionModuleNode produces output. /// ResultImage is non-null when an InspectionModuleNode produces output.
/// ProgressPercent is used by long-running nodes such as WaitDelayNode.
/// </summary> /// </summary>
public record CncNodeExecutionProgress(Guid NodeId, NodeExecutionState State, BitmapSource ResultImage = null); public record CncNodeExecutionProgress(
Guid NodeId,
NodeExecutionState State,
BitmapSource ResultImage = null,
double? ProgressPercent = null);
} }
@@ -455,6 +455,7 @@ namespace XplorePlane.ViewModels.Cnc
private async Task ExecuteRunAsync() private async Task ExecuteRunAsync()
{ {
_cts = new CancellationTokenSource(); _cts = new CancellationTokenSource();
ResetAllNodeStates();
IsRunning = true; IsRunning = true;
HasExecutionError = false; HasExecutionError = false;
ExecutionError = null; ExecutionError = null;
@@ -482,7 +483,6 @@ namespace XplorePlane.ViewModels.Cnc
finally finally
{ {
IsRunning = false; IsRunning = false;
ResetAllNodeStates();
_cts?.Dispose(); _cts?.Dispose();
_cts = null; _cts = null;
} }
@@ -499,6 +499,7 @@ namespace XplorePlane.ViewModels.Cnc
if (nodeVm != null) if (nodeVm != null)
{ {
nodeVm.ExecutionState = progress.State; nodeVm.ExecutionState = progress.State;
nodeVm.ExecutionProgressPercent = progress.ProgressPercent ?? (progress.State == NodeExecutionState.Succeeded ? 100d : 0d);
if (progress.State == NodeExecutionState.Running) if (progress.State == NodeExecutionState.Running)
StatusMessage = $"正在执行节点:{nodeVm.Name}{nodeVm.Index + 1}/{_currentProgram?.Nodes?.Count ?? 0}"; StatusMessage = $"正在执行节点:{nodeVm.Name}{nodeVm.Index + 1}/{_currentProgram?.Nodes?.Count ?? 0}";
else if (progress.State == NodeExecutionState.Succeeded) else if (progress.State == NodeExecutionState.Succeeded)
@@ -519,7 +520,10 @@ namespace XplorePlane.ViewModels.Cnc
private void ResetAllNodeStates() private void ResetAllNodeStates()
{ {
foreach (var node in Nodes) foreach (var node in Nodes)
{
node.ExecutionState = NodeExecutionState.Idle; node.ExecutionState = NodeExecutionState.Idle;
node.ExecutionProgressPercent = 0;
}
} }
private void RaiseEditCommandsCanExecuteChanged() private void RaiseEditCommandsCanExecuteChanged()
@@ -16,6 +16,7 @@ namespace XplorePlane.ViewModels.Cnc
private string _icon; private string _icon;
private bool _isExpanded = true; private bool _isExpanded = true;
private NodeExecutionState _executionState = NodeExecutionState.Idle; private NodeExecutionState _executionState = NodeExecutionState.Idle;
private double _executionProgressPercent;
/// <summary>执行后缓存的流水线输出图像(仅 InspectionModuleNode</summary> /// <summary>执行后缓存的流水线输出图像(仅 InspectionModuleNode</summary>
public BitmapSource ResultImage { get; set; } public BitmapSource ResultImage { get; set; }
@@ -72,6 +73,7 @@ namespace XplorePlane.ViewModels.Cnc
RaisePropertyChanged(nameof(IsRunningNode)); RaisePropertyChanged(nameof(IsRunningNode));
RaisePropertyChanged(nameof(IsSucceededNode)); RaisePropertyChanged(nameof(IsSucceededNode));
RaisePropertyChanged(nameof(IsFailedNode)); RaisePropertyChanged(nameof(IsFailedNode));
RaisePropertyChanged(nameof(IsDelayProgressVisible));
} }
} }
} }
@@ -79,6 +81,21 @@ namespace XplorePlane.ViewModels.Cnc
public bool IsRunningNode => ExecutionState == NodeExecutionState.Running; public bool IsRunningNode => ExecutionState == NodeExecutionState.Running;
public bool IsSucceededNode => ExecutionState == NodeExecutionState.Succeeded; public bool IsSucceededNode => ExecutionState == NodeExecutionState.Succeeded;
public bool IsFailedNode => ExecutionState == NodeExecutionState.Failed; 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 IsReferencePoint => _model is ReferencePointNode;
public bool IsSaveNode => _model is SaveNodeNode; public bool IsSaveNode => _model is SaveNodeNode;
@@ -567,6 +584,9 @@ namespace XplorePlane.ViewModels.Cnc
RaisePropertyChanged(nameof(IsRunningNode)); RaisePropertyChanged(nameof(IsRunningNode));
RaisePropertyChanged(nameof(IsSucceededNode)); RaisePropertyChanged(nameof(IsSucceededNode));
RaisePropertyChanged(nameof(IsFailedNode)); RaisePropertyChanged(nameof(IsFailedNode));
RaisePropertyChanged(nameof(IsDelayProgressVisible));
RaisePropertyChanged(nameof(ExecutionProgressPercent));
RaisePropertyChanged(nameof(ExecutionProgressText));
} }
private enum MotionAxis private enum MotionAxis
@@ -32,7 +32,7 @@ namespace XplorePlane.ViewModels
private PipelineNodeViewModel _selectedNode; private PipelineNodeViewModel _selectedNode;
private BitmapSource _sourceImage; private BitmapSource _sourceImage;
private BitmapSource _previewImage; private BitmapSource _previewImage;
private string _pipelineName = "新建流水线"; private string _pipelineName = "新建模块";
private string _selectedDevice = string.Empty; private string _selectedDevice = string.Empty;
private bool _isExecuting; private bool _isExecuting;
private bool _isStatusError; private bool _isStatusError;
@@ -646,7 +646,7 @@ namespace XplorePlane.ViewModels
var dialog = new SaveFileDialog var dialog = new SaveFileDialog
{ {
Filter = "XP 模块流水线 (*.xpm)|*.xpm", Filter = "XP 模块 (*.xpm)|*.xpm",
DefaultExt = ".xpm", DefaultExt = ".xpm",
AddExtension = true, AddExtension = true,
FileName = PipelineName, FileName = PipelineName,
@@ -698,7 +698,7 @@ namespace XplorePlane.ViewModels
{ {
var dialog = new OpenFileDialog var dialog = new OpenFileDialog
{ {
Filter = "XP 模块流水线 (*.xpm)|*.xpm", Filter = "XP 模块 (*.xpm)|*.xpm",
DefaultExt = ".xpm", DefaultExt = ".xpm",
InitialDirectory = GetPipelineDirectory() InitialDirectory = GetPipelineDirectory()
}; };
+62 -6
View File
@@ -280,6 +280,7 @@
FontFamily="{StaticResource UiFont}" FontFamily="{StaticResource UiFont}"
FontSize="12" FontSize="12"
FontWeight="SemiBold" FontWeight="SemiBold"
x:Name="ProgramRootNameText"
Text="{Binding DisplayName}" Text="{Binding DisplayName}"
TextTrimming="CharacterEllipsis" /> TextTrimming="CharacterEllipsis" />
</StackPanel> </StackPanel>
@@ -288,6 +289,7 @@
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="True"> <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="True">
<Setter TargetName="ProgramRootCard" Property="Background" Value="#E7F1FB" /> <Setter TargetName="ProgramRootCard" Property="Background" Value="#E7F1FB" />
<Setter TargetName="ProgramRootCard" Property="BorderBrush" Value="#9FC6E8" /> <Setter TargetName="ProgramRootCard" Property="BorderBrush" Value="#9FC6E8" />
<Setter TargetName="ProgramRootNameText" Property="Foreground" Value="#111111" />
</DataTrigger> </DataTrigger>
</DataTemplate.Triggers> </DataTemplate.Triggers>
</HierarchicalDataTemplate> </HierarchicalDataTemplate>
@@ -305,6 +307,10 @@
BorderThickness="1" BorderThickness="1"
CornerRadius="4"> CornerRadius="4">
<Grid x:Name="NodeRoot" MinHeight="23"> <Grid x:Name="NodeRoot" MinHeight="23">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="15" /> <ColumnDefinition Width="15" />
<ColumnDefinition Width="20" /> <ColumnDefinition Width="20" />
@@ -312,7 +318,7 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid Grid.Column="0"> <Grid Grid.RowSpan="2" Grid.Column="0">
<Border <Border
x:Name="ChildStem" x:Name="ChildStem"
Width="1" Width="1"
@@ -330,6 +336,7 @@
</Grid> </Grid>
<Border <Border
Grid.RowSpan="2"
Grid.Column="1" Grid.Column="1"
Width="16" Width="16"
Height="16" Height="16"
@@ -346,6 +353,7 @@
<TextBlock <TextBlock
x:Name="NodeNameText" x:Name="NodeNameText"
Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Margin="3,0,0,0" Margin="3,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -357,6 +365,7 @@
<StackPanel <StackPanel
x:Name="NodeActions" x:Name="NodeActions"
Grid.Row="0"
Grid.Column="3" Grid.Column="3"
Margin="0,0,2,0" Margin="0,0,2,0"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -375,6 +384,31 @@
FontSize="10" FontSize="10"
ToolTip="删除" /> ToolTip="删除" />
</StackPanel> </StackPanel>
<Grid
x:Name="WaitDelayProgressHost"
Grid.Row="1"
Grid.Column="2"
Grid.ColumnSpan="2"
Margin="3,2,6,1"
Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ProgressBar
Height="6"
Minimum="0"
Maximum="100"
Value="{Binding ExecutionProgressPercent}" />
<TextBlock
Grid.Column="1"
Margin="6,-4,0,0"
VerticalAlignment="Center"
FontFamily="{StaticResource UiFont}"
FontSize="10"
Foreground="#444444"
Text="{Binding ExecutionProgressText}" />
</Grid>
</Grid> </Grid>
</Border> </Border>
<DataTemplate.Triggers> <DataTemplate.Triggers>
@@ -387,22 +421,30 @@
<Setter TargetName="NodeActions" Property="Visibility" Value="Visible" /> <Setter TargetName="NodeActions" Property="Visibility" Value="Visible" />
<Setter TargetName="NodeCard" Property="Background" Value="#DCEEFF" /> <Setter TargetName="NodeCard" Property="Background" Value="#DCEEFF" />
<Setter TargetName="NodeCard" Property="BorderBrush" Value="#71A9DB" /> <Setter TargetName="NodeCard" Property="BorderBrush" Value="#71A9DB" />
<Setter TargetName="NodeNameText" Property="Foreground" Value="#1F2D3D" />
</DataTrigger> </DataTrigger>
<DataTrigger Binding="{Binding ExecutionState}" Value="Running"> <DataTrigger Binding="{Binding ExecutionState}" Value="Running">
<Setter TargetName="NodeCard" Property="Background" Value="#FF1E6FD9" /> <Setter TargetName="NodeCard" Property="Background" Value="#FFD54F" />
<Setter TargetName="NodeCard" Property="BorderBrush" Value="#FF1E6FD9" /> <Setter TargetName="NodeCard" Property="BorderBrush" Value="#C89B00" />
<Setter TargetName="NodeNameText" Property="Foreground" Value="White" /> <Setter TargetName="NodeNameText" Property="Foreground" Value="#1F1F1F" />
</DataTrigger> </DataTrigger>
<DataTrigger Binding="{Binding ExecutionState}" Value="Succeeded"> <DataTrigger Binding="{Binding ExecutionState}" Value="Succeeded">
<Setter TargetName="NodeCard" Property="Background" Value="#FF2E7D32" /> <Setter TargetName="NodeCard" Property="Background" Value="#FF2E7D32" />
<Setter TargetName="NodeCard" Property="BorderBrush" Value="#FF2E7D32" /> <Setter TargetName="NodeCard" Property="BorderBrush" Value="#FF1B5E20" />
<Setter TargetName="NodeNameText" Property="Foreground" Value="White" /> <Setter TargetName="NodeNameText" Property="Foreground" Value="White" />
</DataTrigger> </DataTrigger>
<DataTrigger Binding="{Binding ExecutionState}" Value="Failed"> <DataTrigger Binding="{Binding ExecutionState}" Value="Failed">
<Setter TargetName="NodeCard" Property="Background" Value="#FFC62828" /> <Setter TargetName="NodeCard" Property="Background" Value="#FFC62828" />
<Setter TargetName="NodeCard" Property="BorderBrush" Value="#FFC62828" /> <Setter TargetName="NodeCard" Property="BorderBrush" Value="#FF8E0000" />
<Setter TargetName="NodeNameText" Property="Foreground" Value="White" /> <Setter TargetName="NodeNameText" Property="Foreground" Value="White" />
</DataTrigger> </DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsWaitDelay}" Value="True" />
<Condition Binding="{Binding IsRunningNode}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="WaitDelayProgressHost" Property="Visibility" Value="Visible" />
</MultiDataTrigger>
</DataTemplate.Triggers> </DataTemplate.Triggers>
</HierarchicalDataTemplate> </HierarchicalDataTemplate>
</TreeView.Resources> </TreeView.Resources>
@@ -569,6 +611,20 @@
<StackPanel Margin="10,8,10,6"> <StackPanel Margin="10,8,10,6">
<TextBlock Style="{StaticResource LabelStyle}" Text="延时 (ms)" /> <TextBlock Style="{StaticResource LabelStyle}" Text="延时 (ms)" />
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DelayMilliseconds, UpdateSourceTrigger=LostFocus}" /> <TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DelayMilliseconds, UpdateSourceTrigger=LostFocus}" />
<ProgressBar
Height="8"
Margin="0,2,0,0"
Minimum="0"
Maximum="100"
Value="{Binding SelectedNode.ExecutionProgressPercent}"
Visibility="{Binding SelectedNode.IsDelayProgressVisible, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
Margin="0,4,0,0"
FontFamily="{StaticResource UiFont}"
FontSize="10"
Foreground="#666666"
Text="{Binding SelectedNode.ExecutionProgressText}"
Visibility="{Binding SelectedNode.IsDelayProgressVisible, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel> </StackPanel>
</GroupBox> </GroupBox>
</StackPanel> </StackPanel>
+5 -31
View File
@@ -19,14 +19,6 @@ namespace XplorePlane.Views.Cnc
/// </summary> /// </summary>
public partial class CncPageView : UserControl 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 CncInspectionModulePipelineViewModel _inspectionModulePipelineViewModel;
private readonly Dictionary<TextBox, Label> _textDisplayLabels = new(); private readonly Dictionary<TextBox, Label> _textDisplayLabels = new();
private readonly Dictionary<CheckBox, Label> _checkDisplayLabels = new(); private readonly Dictionary<CheckBox, Label> _checkDisplayLabels = new();
@@ -186,24 +178,9 @@ namespace XplorePlane.Views.Cnc
continue; continue;
} }
if (item.IsSelected) card.ClearValue(Border.BackgroundProperty);
{ card.ClearValue(Border.BorderBrushProperty);
card.Background = SelectedNodeBackground; ClearNodeTextForeground(card);
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);
}
} }
} }
@@ -318,14 +295,11 @@ namespace XplorePlane.Views.Cnc
panel.Children.Insert(index + 1, companionControl); 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<TextBlock>(card)) foreach (var textBlock in FindVisualDescendants<TextBlock>(card))
{ {
if (textBlock.Visibility == Visibility.Visible) textBlock.ClearValue(TextBlock.ForegroundProperty);
{
textBlock.Foreground = foreground;
}
} }
} }
@@ -80,38 +80,25 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
Command="{Binding NewPipelineCommand}" Command="{Binding NewPipelineCommand}"
Content="新建" Content="新建配方"
Style="{StaticResource ToolbarBtn}" Style="{StaticResource ToolbarBtn}"
ToolTip="新建流水线" /> ToolTip="新建配方" />
<Button <Button
Command="{Binding SavePipelineCommand}" Command="{Binding SavePipelineCommand}"
Content="保存" Content="保存配方"
Style="{StaticResource ToolbarBtn}" Style="{StaticResource ToolbarBtn}"
ToolTip="保存当前流水线" /> ToolTip="保存当前配方" />
<Button <Button
Width="64" Width="64"
Command="{Binding SaveAsPipelineCommand}" Command="{Binding SaveAsPipelineCommand}"
Content="另存为" Content="另存为"
Style="{StaticResource ToolbarBtn}" Style="{StaticResource ToolbarBtn}"
ToolTip="另存当前流水线" /> ToolTip="另存当前配方" />
<Button <Button
Command="{Binding LoadPipelineCommand}" Command="{Binding LoadPipelineCommand}"
Content="加载" Content="加载配方"
Style="{StaticResource ToolbarBtn}" Style="{StaticResource ToolbarBtn}"
ToolTip="加载流水线" /> ToolTip="加载配方" />
<Button
Width="84"
Command="{Binding ExecuteToNodeCommand}"
CommandParameter="{Binding SelectedNode}"
Content="执行到当前"
Style="{StaticResource ToolbarBtn}"
ToolTip="执行到当前选中的节点" />
<Button
Width="68"
Command="{Binding ClearExecutionRangeCommand}"
Content="执行全部"
Style="{StaticResource ToolbarBtn}"
ToolTip="清除截止位置并执行全部节点" />
</StackPanel> </StackPanel>
<TextBlock <TextBlock