Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2969ada965 | |||
| 06714f819f |
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using XP.Common.GeneralForm.Views;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.InspectionResults;
|
||||
@@ -71,7 +72,7 @@ namespace XplorePlane.Services.Cnc
|
||||
case WaitDelayNode waitNode:
|
||||
try
|
||||
{
|
||||
await Task.Delay(waitNode.DelayMilliseconds, cancellationToken);
|
||||
await ExecuteWaitDelayWithProgressAsync(waitNode, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -160,5 +161,49 @@ namespace XplorePlane.Services.Cnc
|
||||
"Failed to complete inspection run '{0}'", runId);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ExecuteWaitDelayWithProgressAsync(WaitDelayNode waitNode, CancellationToken cancellationToken)
|
||||
{
|
||||
int totalMs = waitNode.DelayMilliseconds;
|
||||
if (totalMs <= 0)
|
||||
return;
|
||||
|
||||
const int tickMs = 50;
|
||||
ProgressWindow progressWindow = null;
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
progressWindow = new ProgressWindow(
|
||||
title: "延时等待",
|
||||
message: $"节点:{waitNode.Name} 等待 {totalMs / 1000.0:F1} 秒",
|
||||
isCancelable: false);
|
||||
progressWindow.Owner = Application.Current.MainWindow;
|
||||
progressWindow.Show();
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
int elapsed = 0;
|
||||
while (elapsed < totalMs)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
int remaining = totalMs - elapsed;
|
||||
int delay = Math.Min(tickMs, remaining);
|
||||
await Task.Delay(delay, cancellationToken);
|
||||
elapsed += delay;
|
||||
|
||||
double pct = Math.Min(100.0 * elapsed / totalMs, 100.0);
|
||||
double remainSec = Math.Max(0, (totalMs - elapsed) / 1000.0);
|
||||
string msg = $"节点:{waitNode.Name} 剩余 {remainSec:F1} 秒";
|
||||
|
||||
progressWindow?.UpdateProgress(msg, pct);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
progressWindow?.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
private bool _pendingInsertAfterAnchor;
|
||||
private CancellationTokenSource _cts;
|
||||
private bool _isRunning;
|
||||
private string _statusMessage = "就绪";
|
||||
private string _executionError;
|
||||
private bool _hasExecutionError;
|
||||
|
||||
public CncEditorViewModel(
|
||||
ICncProgramService cncProgramService,
|
||||
@@ -159,6 +162,24 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
}
|
||||
}
|
||||
|
||||
public string StatusMessage
|
||||
{
|
||||
get => _statusMessage;
|
||||
private set => SetProperty(ref _statusMessage, value);
|
||||
}
|
||||
|
||||
public string ExecutionError
|
||||
{
|
||||
get => _executionError;
|
||||
private set => SetProperty(ref _executionError, value);
|
||||
}
|
||||
|
||||
public bool HasExecutionError
|
||||
{
|
||||
get => _hasExecutionError;
|
||||
private set => SetProperty(ref _hasExecutionError, value);
|
||||
}
|
||||
|
||||
public DelegateCommand InsertReferencePointCommand { get; }
|
||||
public DelegateCommand InsertSaveNodeWithImageCommand { get; }
|
||||
public DelegateCommand InsertSaveNodeCommand { get; }
|
||||
@@ -435,14 +456,28 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
_cts = new CancellationTokenSource();
|
||||
IsRunning = true;
|
||||
HasExecutionError = false;
|
||||
ExecutionError = null;
|
||||
StatusMessage = $"正在执行:{_currentProgram?.Name ?? "程序"}(共 {_currentProgram?.Nodes?.Count ?? 0} 个节点)";
|
||||
try
|
||||
{
|
||||
var progress = new Progress<CncNodeExecutionProgress>(OnExecutionProgress);
|
||||
await _cncExecutionService.ExecuteAsync(_currentProgram, progress, _cts.Token);
|
||||
if (_cts.IsCancellationRequested)
|
||||
StatusMessage = "执行已停止";
|
||||
else
|
||||
StatusMessage = $"执行完成:{_currentProgram?.Name}";
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
StatusMessage = "执行已取消";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "CNC execution failed");
|
||||
ExecutionError = ex.Message;
|
||||
HasExecutionError = true;
|
||||
StatusMessage = $"执行失败:{ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -462,7 +497,17 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
var nodeVm = Nodes.FirstOrDefault(n => n.Id == progress.NodeId);
|
||||
if (nodeVm != null)
|
||||
{
|
||||
nodeVm.ExecutionState = progress.State;
|
||||
if (progress.State == NodeExecutionState.Running)
|
||||
StatusMessage = $"正在执行节点:{nodeVm.Name}({nodeVm.Index + 1}/{_currentProgram?.Nodes?.Count ?? 0})";
|
||||
else if (progress.State == NodeExecutionState.Failed)
|
||||
{
|
||||
HasExecutionError = true;
|
||||
ExecutionError = $"节点 [{nodeVm.Name}] 执行失败";
|
||||
StatusMessage = $"错误:节点 [{nodeVm.Name}] 执行失败";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetAllNodeStates()
|
||||
@@ -485,6 +530,8 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
DeleteNodeCommand.RaiseCanExecuteChanged();
|
||||
MoveNodeUpCommand.RaiseCanExecuteChanged();
|
||||
MoveNodeDownCommand.RaiseCanExecuteChanged();
|
||||
RunCncCommand.RaiseCanExecuteChanged();
|
||||
StopCncCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
private void OnProgramEdited()
|
||||
@@ -557,6 +604,7 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
: Nodes.LastOrDefault();
|
||||
|
||||
_preferredSelectedNodeId = null;
|
||||
RaiseEditCommandsCanExecuteChanged();
|
||||
}
|
||||
|
||||
private void NormalizeDefaultNodeNamesInCurrentProgram()
|
||||
|
||||
@@ -57,6 +57,20 @@ namespace XplorePlane.ViewModels
|
||||
_cncPageView = new CncPageView { DataContext = _cncEditorViewModel };
|
||||
|
||||
_mainViewportService.StateChanged += OnMainViewportStateChanged;
|
||||
_cncEditorViewModel.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(CncEditorViewModel.StatusMessage))
|
||||
RaisePropertyChanged(nameof(CncStatusMessage));
|
||||
else if (e.PropertyName == nameof(CncEditorViewModel.HasExecutionError))
|
||||
RaisePropertyChanged(nameof(CncHasExecutionError));
|
||||
else if (e.PropertyName == nameof(CncEditorViewModel.IsRunning))
|
||||
{
|
||||
RunCncCommand.RaiseCanExecuteChanged();
|
||||
StopCncCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
};
|
||||
_cncEditorViewModel.RunCncCommand.CanExecuteChanged += (s, e) => RunCncCommand.RaiseCanExecuteChanged();
|
||||
_cncEditorViewModel.StopCncCommand.CanExecuteChanged += (s, e) => StopCncCommand.RaiseCanExecuteChanged();
|
||||
|
||||
NavigationTree = new ObservableCollection<object>();
|
||||
|
||||
@@ -87,8 +101,12 @@ namespace XplorePlane.ViewModels
|
||||
InsertSaveNodeCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertSaveNodeCommand.Execute()));
|
||||
InsertPauseDialogCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertPauseDialogCommand.Execute()));
|
||||
InsertWaitDelayCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertWaitDelayCommand.Execute()));
|
||||
RunCncCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.RunCncCommand.Execute()));
|
||||
StopCncCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.StopCncCommand.Execute()));
|
||||
RunCncCommand = new DelegateCommand(
|
||||
() => ExecuteCncEditorAction(vm => vm.RunCncCommand.Execute()),
|
||||
() => _cncEditorViewModel.RunCncCommand.CanExecute());
|
||||
StopCncCommand = new DelegateCommand(
|
||||
() => ExecuteCncEditorAction(vm => vm.StopCncCommand.Execute()),
|
||||
() => _cncEditorViewModel.StopCncCommand.CanExecute());
|
||||
|
||||
PointDistanceMeasureCommand = new DelegateCommand(ExecutePointDistanceMeasure);
|
||||
PointLineDistanceMeasureCommand = new DelegateCommand(ExecutePointLineDistanceMeasure);
|
||||
@@ -121,6 +139,9 @@ namespace XplorePlane.ViewModels
|
||||
set => SetProperty(ref _licenseInfo, value);
|
||||
}
|
||||
|
||||
public string CncStatusMessage => _cncEditorViewModel.StatusMessage;
|
||||
public bool CncHasExecutionError => _cncEditorViewModel.HasExecutionError;
|
||||
|
||||
public ObservableCollection<object> NavigationTree { get; set; }
|
||||
|
||||
public DelegateCommand NavigateHomeCommand { get; set; }
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
<StackPanel>
|
||||
<telerik:RadRibbonButton
|
||||
telerik:ScreenTip.Title="运行"
|
||||
Command="{Binding RunCncCommand}"
|
||||
Size="Large"
|
||||
SmallImage="/Assets/Icons/run.png"
|
||||
Text="运行" />
|
||||
@@ -114,6 +115,7 @@
|
||||
<telerik:RadRibbonButton
|
||||
telerik:ScreenTip.Description="停止"
|
||||
telerik:ScreenTip.Title="停止"
|
||||
Command="{Binding StopCncCommand}"
|
||||
Size="Large"
|
||||
SmallImage="/Assets/Icons/stop.png"
|
||||
Text="停止" />
|
||||
@@ -554,7 +556,19 @@
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
FontSize="11"
|
||||
Foreground="White"
|
||||
Text="就绪" />
|
||||
Text="{Binding CncStatusMessage}">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding CncHasExecutionError}" Value="True">
|
||||
<Setter Property="Foreground" Value="#FF9090" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
@@ -563,7 +577,7 @@
|
||||
FontFamily="Consolas"
|
||||
FontSize="11"
|
||||
Foreground="White"
|
||||
Text="x: 0 y: 0 RGB: 0 0 0" />
|
||||
Text="x: 0 y: 0" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user