6 Commits

Author SHA1 Message Date
zhengxuan.zhang a483144d29 调整流水线算子参数区 滑块控件的大小 2026-05-06 13:25:18 +08:00
zhengxuan.zhang 2eb3fed4d0 1、当前根节点选择中是 高亮背景和文字不协调,文字改为黑色
2、延时节点执行时,进度条显示功能失效,修复
   3、自动执行,当加载CNC后,点击rabbion中运行,此时没有看到执行中,已执行,等高亮规则
2026-05-06 11:46:49 +08:00
zhengxuan.zhang 48c419d777 调整CNC编辑页面按钮样式 2026-05-06 11:22:21 +08:00
zhengxuan.zhang d4050b4218 调整CNC按钮逻辑改为内置 2026-05-06 11:12:05 +08:00
zhengxuan.zhang f5dceeceb7 对 流程图 新增 4个状态 已启用 已停用 执行到此 未参与本次执行 和右键菜单,执行到此,执行全部 2026-05-06 10:39:19 +08:00
zhengxuan.zhang fd4048a6c0 修复算子工具箱搜索功能 2026-05-06 10:01:27 +08:00
22 changed files with 740 additions and 115 deletions
@@ -16,8 +16,8 @@
Background="Transparent" Background="Transparent"
BorderBrush="#FFD5DFE5" BorderBrush="#FFD5DFE5"
BorderThickness="1" BorderThickness="1"
Padding="10" Padding="8"
Margin="0,0,0,10"> Margin="0,0,0,8">
<StackPanel> <StackPanel>
<TextBlock x:Name="txtProcessorName" <TextBlock x:Name="txtProcessorName"
FontSize="14" FontSize="14"
@@ -32,7 +32,7 @@
<!-- 参数列表 --> <!-- 参数列表 -->
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto"> <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="pnlParameters" Margin="5" /> <StackPanel x:Name="pnlParameters" Margin="2" />
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
</UserControl> </UserControl>
@@ -143,12 +143,15 @@ public partial class ProcessorParameterControl : UserControl
var textBox = new TextBox var textBox = new TextBox
{ {
Text = param.Value.ToString(), Text = param.Value.ToString(),
Width = 100, Width = 56,
HorizontalAlignment = HorizontalAlignment.Left MinWidth = 56,
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center
}; };
if (param.MinValue != null && param.MaxValue != null) if (param.MinValue != null && param.MaxValue != null)
{ {
var rangeGrid = CreateRangeEditorContainer();
var slider = new Slider var slider = new Slider
{ {
Minimum = Convert.ToDouble(param.MinValue), Minimum = Convert.ToDouble(param.MinValue),
@@ -156,7 +159,8 @@ public partial class ProcessorParameterControl : UserControl
Value = Convert.ToDouble(param.Value), Value = Convert.ToDouble(param.Value),
TickFrequency = 1, TickFrequency = 1,
IsSnapToTickEnabled = true, IsSnapToTickEnabled = true,
Margin = new Thickness(0, 0, 0, 5) MinWidth = 120,
VerticalAlignment = VerticalAlignment.Center
}; };
slider.ValueChanged += (s, e) => slider.ValueChanged += (s, e) =>
@@ -181,7 +185,11 @@ public partial class ProcessorParameterControl : UserControl
} }
}; };
panel.Children.Add(slider); Grid.SetColumn(slider, 0);
Grid.SetColumn(textBox, 2);
rangeGrid.Children.Add(slider);
rangeGrid.Children.Add(textBox);
panel.Children.Add(rangeGrid);
} }
else else
{ {
@@ -193,9 +201,9 @@ public partial class ProcessorParameterControl : UserControl
OnParameterChanged(); OnParameterChanged();
} }
}; };
}
panel.Children.Add(textBox); panel.Children.Add(textBox);
}
return panel; return panel;
} }
@@ -211,19 +219,23 @@ public partial class ProcessorParameterControl : UserControl
var textBox = new TextBox var textBox = new TextBox
{ {
Text = Convert.ToDouble(param.Value).ToString("F2"), Text = Convert.ToDouble(param.Value).ToString("F2"),
Width = 100, Width = 56,
HorizontalAlignment = HorizontalAlignment.Left MinWidth = 56,
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center
}; };
if (param.MinValue != null && param.MaxValue != null) if (param.MinValue != null && param.MaxValue != null)
{ {
var rangeGrid = CreateRangeEditorContainer();
var slider = new Slider var slider = new Slider
{ {
Minimum = Convert.ToDouble(param.MinValue), Minimum = Convert.ToDouble(param.MinValue),
Maximum = Convert.ToDouble(param.MaxValue), Maximum = Convert.ToDouble(param.MaxValue),
Value = Convert.ToDouble(param.Value), Value = Convert.ToDouble(param.Value),
TickFrequency = 0.1, TickFrequency = 0.1,
Margin = new Thickness(0, 0, 0, 5) MinWidth = 120,
VerticalAlignment = VerticalAlignment.Center
}; };
slider.ValueChanged += (s, e) => slider.ValueChanged += (s, e) =>
@@ -248,7 +260,11 @@ public partial class ProcessorParameterControl : UserControl
} }
}; };
panel.Children.Add(slider); Grid.SetColumn(slider, 0);
Grid.SetColumn(textBox, 2);
rangeGrid.Children.Add(slider);
rangeGrid.Children.Add(textBox);
panel.Children.Add(rangeGrid);
} }
else else
{ {
@@ -260,9 +276,9 @@ public partial class ProcessorParameterControl : UserControl
OnParameterChanged(); OnParameterChanged();
} }
}; };
}
panel.Children.Add(textBox); panel.Children.Add(textBox);
}
return panel; return panel;
} }
@@ -302,8 +318,8 @@ public partial class ProcessorParameterControl : UserControl
var comboBox = new ComboBox var comboBox = new ComboBox
{ {
Margin = new Thickness(0, 5, 0, 0), Margin = new Thickness(0, 5, 0, 0),
Width = 200, MinWidth = 160,
HorizontalAlignment = HorizontalAlignment.Left HorizontalAlignment = HorizontalAlignment.Stretch
}; };
if (param.Options != null) if (param.Options != null)
@@ -344,8 +360,8 @@ public partial class ProcessorParameterControl : UserControl
{ {
Text = param.Value?.ToString() ?? "", Text = param.Value?.ToString() ?? "",
Margin = new Thickness(0, 5, 0, 0), Margin = new Thickness(0, 5, 0, 0),
Width = 200, MinWidth = 160,
HorizontalAlignment = HorizontalAlignment.Left HorizontalAlignment = HorizontalAlignment.Stretch
}; };
textBox.TextChanged += (s, e) => textBox.TextChanged += (s, e) =>
@@ -374,4 +390,16 @@ public partial class ProcessorParameterControl : UserControl
pnlParameters.Children.Clear(); pnlParameters.Children.Clear();
UpdateNoProcessorText(); UpdateNoProcessorText();
} }
}
private static Grid CreateRangeEditorContainer()
{
var grid = new Grid
{
Margin = new Thickness(0, 0, 0, 4)
};
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(6) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
return grid;
}
}
@@ -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()
@@ -37,6 +37,7 @@ namespace XplorePlane.ViewModels.Cnc
private string _statusMessage = "请选择检测模块以编辑其流水线。"; private string _statusMessage = "请选择检测模块以编辑其流水线。";
private string _pipelineFileDisplayName = "未命名模块.xpm"; private string _pipelineFileDisplayName = "未命名模块.xpm";
private string _currentFilePath; private string _currentFilePath;
private PipelineNodeViewModel _executionEndNode;
private bool _isSynchronizing; private bool _isSynchronizing;
private CancellationTokenSource _debounceCts; private CancellationTokenSource _debounceCts;
@@ -65,6 +66,8 @@ namespace XplorePlane.ViewModels.Cnc
RemoveOperatorCommand = new DelegateCommand<PipelineNodeViewModel>(RemoveOperator); RemoveOperatorCommand = new DelegateCommand<PipelineNodeViewModel>(RemoveOperator);
ReorderOperatorCommand = new DelegateCommand<PipelineReorderArgs>(ReorderOperator); ReorderOperatorCommand = new DelegateCommand<PipelineReorderArgs>(ReorderOperator);
ToggleOperatorEnabledCommand = new DelegateCommand<PipelineNodeViewModel>(ToggleOperatorEnabled); ToggleOperatorEnabledCommand = new DelegateCommand<PipelineNodeViewModel>(ToggleOperatorEnabled);
ExecuteToNodeCommand = new DelegateCommand<PipelineNodeViewModel>(ExecuteToNode);
ClearExecutionRangeCommand = new DelegateCommand(ClearExecutionRange);
MoveNodeUpCommand = new DelegateCommand<PipelineNodeViewModel>(MoveNodeUp); MoveNodeUpCommand = new DelegateCommand<PipelineNodeViewModel>(MoveNodeUp);
MoveNodeDownCommand = new DelegateCommand<PipelineNodeViewModel>(MoveNodeDown); MoveNodeDownCommand = new DelegateCommand<PipelineNodeViewModel>(MoveNodeDown);
NewPipelineCommand = new DelegateCommand(NewPipeline); NewPipelineCommand = new DelegateCommand(NewPipeline);
@@ -98,6 +101,16 @@ namespace XplorePlane.ViewModels.Cnc
private set => SetProperty(ref _pipelineFileDisplayName, value); private set => SetProperty(ref _pipelineFileDisplayName, value);
} }
public PipelineNodeViewModel ExecutionEndNode
{
get => _executionEndNode;
private set
{
if (SetProperty(ref _executionEndNode, value))
UpdateExecutionRangeState();
}
}
public bool HasActiveModule => _activeModuleNode?.IsInspectionModule == true; public bool HasActiveModule => _activeModuleNode?.IsInspectionModule == true;
public Visibility EditorVisibility => HasActiveModule ? Visibility.Visible : Visibility.Collapsed; public Visibility EditorVisibility => HasActiveModule ? Visibility.Visible : Visibility.Collapsed;
@@ -112,6 +125,10 @@ namespace XplorePlane.ViewModels.Cnc
public ICommand ToggleOperatorEnabledCommand { get; } public ICommand ToggleOperatorEnabledCommand { get; }
public ICommand ExecuteToNodeCommand { get; }
public ICommand ClearExecutionRangeCommand { get; }
public ICommand MoveNodeUpCommand { get; } public ICommand MoveNodeUpCommand { get; }
public ICommand MoveNodeDownCommand { get; } public ICommand MoveNodeDownCommand { get; }
@@ -179,6 +196,7 @@ namespace XplorePlane.ViewModels.Cnc
LoadNodeParameters(node, null); LoadNodeParameters(node, null);
PipelineNodes.Add(node); PipelineNodes.Add(node);
SelectedNode = node; SelectedNode = node;
UpdateExecutionRangeState();
PersistActiveModule($"已添加算子:{displayName}"); PersistActiveModule($"已添加算子:{displayName}");
} }
@@ -190,6 +208,10 @@ namespace XplorePlane.ViewModels.Cnc
var removedIndex = PipelineNodes.IndexOf(node); var removedIndex = PipelineNodes.IndexOf(node);
PipelineNodes.Remove(node); PipelineNodes.Remove(node);
RenumberNodes(); RenumberNodes();
if (ReferenceEquals(ExecutionEndNode, node))
ExecutionEndNode = null;
else
UpdateExecutionRangeState();
SelectNeighborAfterRemoval(removedIndex); SelectNeighborAfterRemoval(removedIndex);
PersistActiveModule($"已移除算子:{node.DisplayName}"); PersistActiveModule($"已移除算子:{node.DisplayName}");
@@ -206,6 +228,7 @@ namespace XplorePlane.ViewModels.Cnc
PipelineNodes.Move(index, index - 1); PipelineNodes.Move(index, index - 1);
RenumberNodes(); RenumberNodes();
UpdateExecutionRangeState();
PersistActiveModule($"已上移算子:{node.DisplayName}"); PersistActiveModule($"已上移算子:{node.DisplayName}");
} }
@@ -225,6 +248,7 @@ namespace XplorePlane.ViewModels.Cnc
var node = PipelineNodes[oldIndex]; var node = PipelineNodes[oldIndex];
PipelineNodes.Move(oldIndex, newIndex); PipelineNodes.Move(oldIndex, newIndex);
RenumberNodes(); RenumberNodes();
UpdateExecutionRangeState();
SelectedNode = node; SelectedNode = node;
PersistActiveModule($"已调整算子顺序:{node.DisplayName}"); PersistActiveModule($"已调整算子顺序:{node.DisplayName}");
} }
@@ -240,6 +264,7 @@ namespace XplorePlane.ViewModels.Cnc
PipelineNodes.Move(index, index + 1); PipelineNodes.Move(index, index + 1);
RenumberNodes(); RenumberNodes();
UpdateExecutionRangeState();
PersistActiveModule($"已下移算子:{node.DisplayName}"); PersistActiveModule($"已下移算子:{node.DisplayName}");
} }
@@ -255,6 +280,25 @@ namespace XplorePlane.ViewModels.Cnc
: $"已停用算子:{node.DisplayName}"); : $"已停用算子:{node.DisplayName}");
} }
private void ExecuteToNode(PipelineNodeViewModel node)
{
if (!HasActiveModule || node == null || !PipelineNodes.Contains(node))
return;
SelectedNode = node;
ExecutionEndNode = node;
PersistActiveModule($"已设置执行截止点:{node.DisplayName}");
}
private void ClearExecutionRange()
{
if (!HasActiveModule || ExecutionEndNode == null)
return;
ExecutionEndNode = null;
PersistActiveModule("已切换为执行全部节点");
}
private void NewPipeline() private void NewPipeline()
{ {
if (!HasActiveModule) if (!HasActiveModule)
@@ -262,6 +306,7 @@ namespace XplorePlane.ViewModels.Cnc
PipelineNodes.Clear(); PipelineNodes.Clear();
SelectedNode = null; SelectedNode = null;
ExecutionEndNode = null;
_currentFilePath = null; _currentFilePath = null;
PipelineFileDisplayName = GetActivePipelineFileDisplayName(); PipelineFileDisplayName = GetActivePipelineFileDisplayName();
PersistActiveModule("已为当前检测模块新建空流水线。"); PersistActiveModule("已为当前检测模块新建空流水线。");
@@ -326,6 +371,7 @@ namespace XplorePlane.ViewModels.Cnc
{ {
PipelineNodes.Clear(); PipelineNodes.Clear();
SelectedNode = null; SelectedNode = null;
ExecutionEndNode = null;
var orderedNodes = (pipeline?.Nodes ?? new List<PipelineNodeModel>()) var orderedNodes = (pipeline?.Nodes ?? new List<PipelineNodeModel>())
.OrderBy(node => node.Order) .OrderBy(node => node.Order)
@@ -346,6 +392,7 @@ namespace XplorePlane.ViewModels.Cnc
} }
SelectedNode = PipelineNodes.FirstOrDefault(); SelectedNode = PipelineNodes.FirstOrDefault();
UpdateExecutionRangeState();
if (string.IsNullOrEmpty(_currentFilePath)) if (string.IsNullOrEmpty(_currentFilePath))
PipelineFileDisplayName = GetActivePipelineFileDisplayName(); PipelineFileDisplayName = GetActivePipelineFileDisplayName();
StatusMessage = HasActiveModule StatusMessage = HasActiveModule
@@ -423,7 +470,7 @@ namespace XplorePlane.ViewModels.Cnc
try try
{ {
_logger.Info("[图像链路][CNC] ExecutePreviewAsync:开始执行,节点数={Count}", PipelineNodes.Count); _logger.Info("[图像链路][CNC] ExecutePreviewAsync:开始执行,节点数={Count}", PipelineNodes.Count);
var result = await _executionService.ExecutePipelineAsync(PipelineNodes, sourceImage, null, token); var result = await _executionService.ExecutePipelineAsync(GetNodesInExecutionScope(), sourceImage, null, token);
_logger.Info("[图像链路][CNC] ExecutePreviewAsync:执行完成,推送结果图像"); _logger.Info("[图像链路][CNC] ExecutePreviewAsync:执行完成,推送结果图像");
_mainViewportService.SetManualImage(result, string.Empty); _mainViewportService.SetManualImage(result, string.Empty);
_eventAggregator?.GetEvent<PipelinePreviewUpdatedEvent>() _eventAggregator?.GetEvent<PipelinePreviewUpdatedEvent>()
@@ -459,6 +506,15 @@ namespace XplorePlane.ViewModels.Cnc
}; };
} }
private IEnumerable<PipelineNodeViewModel> GetNodesInExecutionScope()
{
var orderedNodes = PipelineNodes.OrderBy(node => node.Order);
if (ExecutionEndNode == null)
return orderedNodes;
return orderedNodes.Where(node => node.Order <= ExecutionEndNode.Order);
}
private string GetActivePipelineName() private string GetActivePipelineName()
{ {
if (!HasActiveModule) if (!HasActiveModule)
@@ -492,6 +548,19 @@ namespace XplorePlane.ViewModels.Cnc
} }
} }
private void UpdateExecutionRangeState()
{
if (_executionEndNode != null && !PipelineNodes.Contains(_executionEndNode))
_executionEndNode = null;
var endOrder = _executionEndNode?.Order;
foreach (var node in PipelineNodes)
{
node.IsExecutionEndNode = endOrder.HasValue && node.Order == endOrder.Value;
node.IsSkippedByExecutionRange = endOrder.HasValue && node.Order > endOrder.Value;
}
}
private void SelectNeighborAfterRemoval(int removedIndex) private void SelectNeighborAfterRemoval(int removedIndex)
{ {
if (PipelineNodes.Count == 0) if (PipelineNodes.Count == 0)
@@ -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
@@ -23,6 +23,10 @@ namespace XplorePlane.ViewModels
ICommand ToggleOperatorEnabledCommand { get; } ICommand ToggleOperatorEnabledCommand { get; }
ICommand ExecuteToNodeCommand { get; }
ICommand ClearExecutionRangeCommand { get; }
ICommand MoveNodeUpCommand { get; } ICommand MoveNodeUpCommand { get; }
ICommand MoveNodeDownCommand { get; } ICommand MoveNodeDownCommand { get; }
@@ -36,6 +36,7 @@ namespace XplorePlane.ViewModels
{ {
private readonly IImageProcessingService _imageProcessingService; private readonly IImageProcessingService _imageProcessingService;
private string _searchText = string.Empty; private string _searchText = string.Empty;
private OperatorGroupViewModel _selectedGroup;
// UI 元数据(分类 + 图标)由 ProcessorUiMetadata 统一提供,保持工具箱与流水线图标一致 // UI 元数据(分类 + 图标)由 ProcessorUiMetadata 统一提供,保持工具箱与流水线图标一致
@@ -52,6 +53,12 @@ namespace XplorePlane.ViewModels
public ObservableCollection<OperatorDescriptor> FilteredOperators { get; } public ObservableCollection<OperatorDescriptor> FilteredOperators { get; }
public ObservableCollection<OperatorGroupViewModel> FilteredGroups { get; } public ObservableCollection<OperatorGroupViewModel> FilteredGroups { get; }
public OperatorGroupViewModel SelectedGroup
{
get => _selectedGroup;
set => SetProperty(ref _selectedGroup, value);
}
public string SearchText public string SearchText
{ {
get => _searchText; get => _searchText;
@@ -78,6 +85,7 @@ namespace XplorePlane.ViewModels
{ {
FilteredOperators.Clear(); FilteredOperators.Clear();
FilteredGroups.Clear(); FilteredGroups.Clear();
SelectedGroup = null;
var filtered = string.IsNullOrWhiteSpace(SearchText) var filtered = string.IsNullOrWhiteSpace(SearchText)
? AvailableOperators ? AvailableOperators
@@ -101,6 +109,8 @@ namespace XplorePlane.ViewModels
Operators = new ObservableCollection<OperatorDescriptor>(group) Operators = new ObservableCollection<OperatorDescriptor>(group)
}); });
} }
SelectedGroup = FilteredGroups.FirstOrDefault();
} }
private static int GetCategoryOrder(string category) => category switch private static int GetCategoryOrder(string category) => category switch
@@ -32,13 +32,14 @@ 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;
private string _statusMessage = string.Empty; private string _statusMessage = string.Empty;
private string _pipelineFileDisplayName = DefaultPipelineFileDisplayName; private string _pipelineFileDisplayName = DefaultPipelineFileDisplayName;
private string _currentFilePath; private string _currentFilePath;
private PipelineNodeViewModel _executionEndNode;
private CancellationTokenSource _executionCts; private CancellationTokenSource _executionCts;
private CancellationTokenSource _debounceCts; private CancellationTokenSource _debounceCts;
@@ -64,6 +65,8 @@ namespace XplorePlane.ViewModels
ReorderOperatorCommand = new DelegateCommand<PipelineReorderArgs>(ReorderOperator); ReorderOperatorCommand = new DelegateCommand<PipelineReorderArgs>(ReorderOperator);
ToggleOperatorEnabledCommand = new DelegateCommand<PipelineNodeViewModel>(ToggleOperatorEnabled); ToggleOperatorEnabledCommand = new DelegateCommand<PipelineNodeViewModel>(ToggleOperatorEnabled);
ExecutePipelineCommand = new DelegateCommand(async () => await ExecutePipelineAsync(), () => !IsExecuting && SourceImage != null); ExecutePipelineCommand = new DelegateCommand(async () => await ExecutePipelineAsync(), () => !IsExecuting && SourceImage != null);
ExecuteToNodeCommand = new DelegateCommand<PipelineNodeViewModel>(async node => await ExecuteToNodeAsync(node), CanExecuteToNode);
ClearExecutionRangeCommand = new DelegateCommand(async () => await ClearExecutionRangeAsync(), CanClearExecutionRange);
CancelExecutionCommand = new DelegateCommand(CancelExecution, () => IsExecuting); CancelExecutionCommand = new DelegateCommand(CancelExecution, () => IsExecuting);
NewPipelineCommand = new DelegateCommand(NewPipeline); NewPipelineCommand = new DelegateCommand(NewPipeline);
SavePipelineCommand = new DelegateCommand(async () => await SavePipelineAsync()); SavePipelineCommand = new DelegateCommand(async () => await SavePipelineAsync());
@@ -162,6 +165,20 @@ namespace XplorePlane.ViewModels
private set => SetProperty(ref _pipelineFileDisplayName, value); private set => SetProperty(ref _pipelineFileDisplayName, value);
} }
public PipelineNodeViewModel ExecutionEndNode
{
get => _executionEndNode;
private set
{
if (SetProperty(ref _executionEndNode, value))
{
UpdateExecutionRangeState();
ExecuteToNodeCommand.RaiseCanExecuteChanged();
ClearExecutionRangeCommand.RaiseCanExecuteChanged();
}
}
}
// ── Commands ────────────────────────────────────────────────── // ── Commands ──────────────────────────────────────────────────
public DelegateCommand<string> AddOperatorCommand { get; } public DelegateCommand<string> AddOperatorCommand { get; }
@@ -169,6 +186,8 @@ namespace XplorePlane.ViewModels
public DelegateCommand<PipelineReorderArgs> ReorderOperatorCommand { get; } public DelegateCommand<PipelineReorderArgs> ReorderOperatorCommand { get; }
public DelegateCommand<PipelineNodeViewModel> ToggleOperatorEnabledCommand { get; } public DelegateCommand<PipelineNodeViewModel> ToggleOperatorEnabledCommand { get; }
public DelegateCommand ExecutePipelineCommand { get; } public DelegateCommand ExecutePipelineCommand { get; }
public DelegateCommand<PipelineNodeViewModel> ExecuteToNodeCommand { get; }
public DelegateCommand ClearExecutionRangeCommand { get; }
public DelegateCommand CancelExecutionCommand { get; } public DelegateCommand CancelExecutionCommand { get; }
public DelegateCommand NewPipelineCommand { get; } public DelegateCommand NewPipelineCommand { get; }
public DelegateCommand SavePipelineCommand { get; } public DelegateCommand SavePipelineCommand { get; }
@@ -184,6 +203,8 @@ namespace XplorePlane.ViewModels
ICommand IPipelineEditorHostViewModel.RemoveOperatorCommand => RemoveOperatorCommand; ICommand IPipelineEditorHostViewModel.RemoveOperatorCommand => RemoveOperatorCommand;
ICommand IPipelineEditorHostViewModel.ReorderOperatorCommand => ReorderOperatorCommand; ICommand IPipelineEditorHostViewModel.ReorderOperatorCommand => ReorderOperatorCommand;
ICommand IPipelineEditorHostViewModel.ToggleOperatorEnabledCommand => ToggleOperatorEnabledCommand; ICommand IPipelineEditorHostViewModel.ToggleOperatorEnabledCommand => ToggleOperatorEnabledCommand;
ICommand IPipelineEditorHostViewModel.ExecuteToNodeCommand => ExecuteToNodeCommand;
ICommand IPipelineEditorHostViewModel.ClearExecutionRangeCommand => ClearExecutionRangeCommand;
ICommand IPipelineEditorHostViewModel.MoveNodeUpCommand => MoveNodeUpCommand; ICommand IPipelineEditorHostViewModel.MoveNodeUpCommand => MoveNodeUpCommand;
ICommand IPipelineEditorHostViewModel.MoveNodeDownCommand => MoveNodeDownCommand; ICommand IPipelineEditorHostViewModel.MoveNodeDownCommand => MoveNodeDownCommand;
ICommand IPipelineEditorHostViewModel.NewPipelineCommand => NewPipelineCommand; ICommand IPipelineEditorHostViewModel.NewPipelineCommand => NewPipelineCommand;
@@ -234,6 +255,7 @@ namespace XplorePlane.ViewModels
LoadNodeParameters(node); LoadNodeParameters(node);
PipelineNodes.Add(node); PipelineNodes.Add(node);
SelectedNode = node; SelectedNode = node;
UpdateExecutionRangeState();
_logger.Info("节点已添加到 PipelineNodes{Key} ({DisplayName}),当前节点数={Count}", _logger.Info("节点已添加到 PipelineNodes{Key} ({DisplayName}),当前节点数={Count}",
operatorKey, displayName, PipelineNodes.Count); operatorKey, displayName, PipelineNodes.Count);
SetInfoStatus($"已添加算子:{displayName}"); SetInfoStatus($"已添加算子:{displayName}");
@@ -247,6 +269,10 @@ namespace XplorePlane.ViewModels
var removedIndex = PipelineNodes.IndexOf(node); var removedIndex = PipelineNodes.IndexOf(node);
PipelineNodes.Remove(node); PipelineNodes.Remove(node);
RenumberNodes(); RenumberNodes();
if (ReferenceEquals(ExecutionEndNode, node))
ExecutionEndNode = null;
else
UpdateExecutionRangeState();
SelectNeighborAfterRemoval(removedIndex); SelectNeighborAfterRemoval(removedIndex);
SetInfoStatus($"已移除算子:{node.DisplayName}"); SetInfoStatus($"已移除算子:{node.DisplayName}");
@@ -260,6 +286,7 @@ namespace XplorePlane.ViewModels
if (index <= 0) return; if (index <= 0) return;
PipelineNodes.Move(index, index - 1); PipelineNodes.Move(index, index - 1);
RenumberNodes(); RenumberNodes();
UpdateExecutionRangeState();
TriggerDebouncedExecution(); TriggerDebouncedExecution();
} }
@@ -270,6 +297,7 @@ namespace XplorePlane.ViewModels
if (index < 0 || index >= PipelineNodes.Count - 1) return; if (index < 0 || index >= PipelineNodes.Count - 1) return;
PipelineNodes.Move(index, index + 1); PipelineNodes.Move(index, index + 1);
RenumberNodes(); RenumberNodes();
UpdateExecutionRangeState();
TriggerDebouncedExecution(); TriggerDebouncedExecution();
} }
@@ -287,6 +315,7 @@ namespace XplorePlane.ViewModels
PipelineNodes.RemoveAt(oldIndex); PipelineNodes.RemoveAt(oldIndex);
PipelineNodes.Insert(newIndex, node); PipelineNodes.Insert(newIndex, node);
RenumberNodes(); RenumberNodes();
UpdateExecutionRangeState();
SelectedNode = node; SelectedNode = node;
SetInfoStatus($"已调整算子顺序:{node.DisplayName}"); SetInfoStatus($"已调整算子顺序:{node.DisplayName}");
TriggerDebouncedExecution(); TriggerDebouncedExecution();
@@ -304,6 +333,34 @@ namespace XplorePlane.ViewModels
TriggerDebouncedExecution(); TriggerDebouncedExecution();
} }
private bool CanExecuteToNode(PipelineNodeViewModel node) =>
node != null && PipelineNodes.Contains(node) && !IsExecuting && SourceImage != null;
private async Task ExecuteToNodeAsync(PipelineNodeViewModel node)
{
if (!CanExecuteToNode(node))
return;
SelectedNode = node;
ExecutionEndNode = node;
await ExecutePipelineAsync();
}
private bool CanClearExecutionRange() =>
ExecutionEndNode != null && !IsExecuting;
private async Task ClearExecutionRangeAsync()
{
if (ExecutionEndNode == null)
return;
ExecutionEndNode = null;
SetInfoStatus("已切换为执行全部节点");
if (SourceImage != null)
await ExecutePipelineAsync();
}
private void RenumberNodes() private void RenumberNodes()
{ {
for (int i = 0; i < PipelineNodes.Count; i++) for (int i = 0; i < PipelineNodes.Count; i++)
@@ -367,10 +424,15 @@ namespace XplorePlane.ViewModels
_executionCts?.Cancel(); _executionCts?.Cancel();
_executionCts = new CancellationTokenSource(); _executionCts = new CancellationTokenSource();
var token = _executionCts.Token; var token = _executionCts.Token;
var executionNodes = GetNodesInExecutionScope()
.Where(n => n.IsEnabled)
.OrderBy(n => n.Order)
.ToList();
IsExecuting = true; IsExecuting = true;
SetInfoStatus("正在执行流水线..."); SetInfoStatus(BuildExecutionStartMessage(executionNodes.Count));
_logger.Info("[图像链路] ExecutePipelineAsync:开始执行,节点数={Count}", PipelineNodes.Count); _logger.Info("[图像链路] ExecutePipelineAsync:开始执行,范围节点数={Count},截止节点={Node}",
executionNodes.Count, ExecutionEndNode?.DisplayName ?? "<all>");
try try
{ {
@@ -378,10 +440,10 @@ namespace XplorePlane.ViewModels
SetInfoStatus($"执行中:{p.CurrentOperator} ({p.CurrentStep}/{p.TotalSteps})")); SetInfoStatus($"执行中:{p.CurrentOperator} ({p.CurrentStep}/{p.TotalSteps})"));
var result = await _executionService.ExecutePipelineAsync( var result = await _executionService.ExecutePipelineAsync(
PipelineNodes, SourceImage, progress, token); executionNodes, SourceImage, progress, token);
PreviewImage = result; PreviewImage = result;
SetInfoStatus("流水线执行完成"); SetInfoStatus(BuildExecutionCompletedMessage(executionNodes.Count));
_logger.Info("[图像链路] ExecutePipelineAsync:执行完成,准备发布 PipelinePreviewUpdatedEvent"); _logger.Info("[图像链路] ExecutePipelineAsync:执行完成,准备发布 PipelinePreviewUpdatedEvent");
PublishPipelinePreviewUpdated(result, StatusMessage); PublishPipelinePreviewUpdated(result, StatusMessage);
} }
@@ -408,7 +470,7 @@ namespace XplorePlane.ViewModels
private bool TryReportInvalidParameters() private bool TryReportInvalidParameters()
{ {
var firstInvalidNode = PipelineNodes var firstInvalidNode = GetNodesInExecutionScope()
.Where(n => n.IsEnabled) .Where(n => n.IsEnabled)
.OrderBy(n => n.Order) .OrderBy(n => n.Order)
.FirstOrDefault(n => n.Parameters.Any(p => !p.IsValueValid)); .FirstOrDefault(n => n.Parameters.Any(p => !p.IsValueValid));
@@ -549,6 +611,7 @@ namespace XplorePlane.ViewModels
{ {
PipelineNodes.Clear(); PipelineNodes.Clear();
SelectedNode = null; SelectedNode = null;
ExecutionEndNode = null;
PipelineName = "新建流水线"; PipelineName = "新建流水线";
PreviewImage = null; PreviewImage = null;
_currentFilePath = null; _currentFilePath = null;
@@ -583,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,
@@ -635,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()
}; };
@@ -648,6 +711,7 @@ namespace XplorePlane.ViewModels
PipelineNodes.Clear(); PipelineNodes.Clear();
SelectedNode = null; SelectedNode = null;
ExecutionEndNode = null;
PipelineName = model.Name; PipelineName = model.Name;
SelectedDevice = model.DeviceId; SelectedDevice = model.DeviceId;
@@ -676,6 +740,8 @@ namespace XplorePlane.ViewModels
PipelineNodes.Add(node); PipelineNodes.Add(node);
} }
UpdateExecutionRangeState();
_logger.Info("流水线已加载:{Name},节点数={Count}", model.Name, PipelineNodes.Count); _logger.Info("流水线已加载:{Name},节点数={Count}", model.Name, PipelineNodes.Count);
SetInfoStatus($"已加载流水线:{model.Name}{PipelineNodes.Count} 个节点)"); SetInfoStatus($"已加载流水线:{model.Name}{PipelineNodes.Count} 个节点)");
} }
@@ -704,6 +770,44 @@ namespace XplorePlane.ViewModels
}; };
} }
private System.Collections.Generic.IEnumerable<PipelineNodeViewModel> GetNodesInExecutionScope()
{
var orderedNodes = PipelineNodes.OrderBy(n => n.Order);
if (ExecutionEndNode == null)
return orderedNodes;
return orderedNodes.Where(n => n.Order <= ExecutionEndNode.Order);
}
private void UpdateExecutionRangeState()
{
if (_executionEndNode != null && !PipelineNodes.Contains(_executionEndNode))
_executionEndNode = null;
var endOrder = _executionEndNode?.Order;
foreach (var node in PipelineNodes)
{
node.IsExecutionEndNode = endOrder.HasValue && node.Order == endOrder.Value;
node.IsSkippedByExecutionRange = endOrder.HasValue && node.Order > endOrder.Value;
}
}
private string BuildExecutionStartMessage(int executionCount)
{
if (ExecutionEndNode == null)
return "正在执行流水线...";
return $"正在执行到“{ExecutionEndNode.DisplayName}” ({executionCount} 个有效节点)...";
}
private string BuildExecutionCompletedMessage(int executionCount)
{
if (ExecutionEndNode == null)
return "流水线执行完成";
return $"已执行到“{ExecutionEndNode.DisplayName}” ({executionCount} 个有效节点)";
}
private static string GetPipelineDirectory() private static string GetPipelineDirectory()
{ {
var dir = Path.Combine( var dir = Path.Combine(
@@ -11,6 +11,8 @@ namespace XplorePlane.ViewModels
private int _order; private int _order;
private bool _isSelected; private bool _isSelected;
private bool _isEnabled = true; private bool _isEnabled = true;
private bool _isExecutionEndNode;
private bool _isSkippedByExecutionRange;
public PipelineNodeViewModel(string operatorKey, string displayName, string iconPath = null) public PipelineNodeViewModel(string operatorKey, string displayName, string iconPath = null)
{ {
@@ -51,9 +53,49 @@ namespace XplorePlane.ViewModels
public bool IsEnabled public bool IsEnabled
{ {
get => _isEnabled; get => _isEnabled;
set => SetProperty(ref _isEnabled, value); set
{
if (SetProperty(ref _isEnabled, value))
RaisePropertyChanged(nameof(NodeStateText));
}
} }
public ObservableCollection<ProcessorParameterVM> Parameters { get; } public ObservableCollection<ProcessorParameterVM> Parameters { get; }
public bool IsExecutionEndNode
{
get => _isExecutionEndNode;
set
{
if (SetProperty(ref _isExecutionEndNode, value))
RaisePropertyChanged(nameof(NodeStateText));
}
}
public bool IsSkippedByExecutionRange
{
get => _isSkippedByExecutionRange;
set
{
if (SetProperty(ref _isSkippedByExecutionRange, value))
RaisePropertyChanged(nameof(NodeStateText));
}
}
public string NodeStateText
{
get
{
if (IsExecutionEndNode && !IsEnabled)
return "执行到此(停用)";
if (IsExecutionEndNode)
return "执行到此";
if (!IsEnabled)
return "已停用";
if (IsSkippedByExecutionRange)
return "未参与本次执行";
return "已启用";
}
}
} }
} }
@@ -39,7 +39,15 @@ namespace XplorePlane.ViewModels
public string ParameterType { get; } public string ParameterType { get; }
public bool HasOptions => Options is { Length: > 0 }; public bool HasOptions => Options is { Length: > 0 };
public bool IsBool => ParameterType == "bool"; public bool IsBool => ParameterType == "bool";
public bool IsTextInput => !IsBool && !HasOptions; public bool IsNumeric => ParameterType is "int" or "double";
public bool HasRange => IsNumeric && MinValue != null && MaxValue != null;
public bool IsSliderInput => HasRange;
public bool IsTextInput => !IsBool && !HasOptions && !IsSliderInput;
public double SliderMinimum => TryConvertToDouble(MinValue, out var minValue) ? minValue : 0d;
public double SliderMaximum => TryConvertToDouble(MaxValue, out var maxValue) ? maxValue : 100d;
public double SliderTickFrequency => ResolveTickFrequency();
public bool IsIntegerSlider => ParameterType == "int";
public bool IsValueValid public bool IsValueValid
{ {
@@ -60,10 +68,46 @@ namespace XplorePlane.ViewModels
RaisePropertyChanged(nameof(Value)); RaisePropertyChanged(nameof(Value));
RaisePropertyChanged(nameof(BoolValue)); RaisePropertyChanged(nameof(BoolValue));
RaisePropertyChanged(nameof(SelectedOption)); RaisePropertyChanged(nameof(SelectedOption));
RaisePropertyChanged(nameof(SliderValue));
RaisePropertyChanged(nameof(DisplayValueText));
} }
} }
} }
public double SliderValue
{
get => TryConvertToDouble(_value, out var sliderValue) ? sliderValue : SliderMinimum;
set
{
if (!IsSliderInput)
{
return;
}
Value = ParameterType == "int"
? (object)Convert.ToInt32(Math.Round(value, MidpointRounding.AwayFromZero), CultureInfo.InvariantCulture)
: Math.Round(value, ResolveDecimalPlaces(), MidpointRounding.AwayFromZero);
}
}
public string DisplayValueText
{
get
{
if (ParameterType == "int" && TryConvertToInt(_value, out var intValue))
{
return intValue.ToString(CultureInfo.InvariantCulture);
}
if (ParameterType == "double" && TryConvertToDouble(_value, out var doubleValue))
{
return doubleValue.ToString($"F{ResolveDecimalPlaces()}", CultureInfo.InvariantCulture);
}
return Convert.ToString(_value, CultureInfo.InvariantCulture) ?? string.Empty;
}
}
public bool BoolValue public bool BoolValue
{ {
get => ParameterType == "bool" && TryConvertToBool(_value, out var boolValue) && boolValue; get => ParameterType == "bool" && TryConvertToBool(_value, out var boolValue) && boolValue;
@@ -154,6 +198,58 @@ namespace XplorePlane.ViewModels
return true; return true;
} }
private double ResolveTickFrequency()
{
if (!HasRange)
{
return 1d;
}
if (ParameterType == "int")
{
return 1d;
}
double range = SliderMaximum - SliderMinimum;
if (range <= 1d)
{
return 0.01d;
}
if (range <= 10d)
{
return 0.1d;
}
return 1d;
}
private int ResolveDecimalPlaces()
{
if (ParameterType == "int")
{
return 0;
}
double tick = SliderTickFrequency;
if (tick >= 1d)
{
return 0;
}
if (tick >= 0.1d)
{
return 1;
}
if (tick >= 0.01d)
{
return 2;
}
return 3;
}
private static string NormalizeNumericText(string value) private static string NormalizeNumericText(string value)
{ {
return value.Trim().TrimEnd('、', '', ',', '。', '.', ';', '', ':', ''); return value.Trim().TrimEnd('、', '', ',', '。', '.', ';', '', ':', '');
+2 -2
View File
@@ -23,7 +23,7 @@ namespace XplorePlane.ViewModels
{ {
public class MainViewModel : BindableBase public class MainViewModel : BindableBase
{ {
private const double CncEditorHostWidth = 402d; private const double CncEditorHostWidth = 452d;
private readonly ILoggerService _logger; private readonly ILoggerService _logger;
private readonly IContainerProvider _containerProvider; private readonly IContainerProvider _containerProvider;
@@ -205,7 +205,7 @@ namespace XplorePlane.ViewModels
LoadImageCommand = new DelegateCommand(ExecuteLoadImage); LoadImageCommand = new DelegateCommand(ExecuteLoadImage);
OpenPipelineEditorCommand = new DelegateCommand(() => ShowWindow(new Views.PipelineEditorWindow(), "流水线编辑器"));
OpenCncEditorCommand = new DelegateCommand(ExecuteOpenCncEditor); OpenCncEditorCommand = new DelegateCommand(ExecuteOpenCncEditor);
OpenMatrixEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.MatrixEditorWindow(), "矩阵编排")); OpenMatrixEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.MatrixEditorWindow(), "矩阵编排"));
OpenToolboxCommand = new DelegateCommand(ExecuteOpenToolbox); OpenToolboxCommand = new DelegateCommand(ExecuteOpenToolbox);
+179 -23
View File
@@ -10,7 +10,7 @@
xmlns:views="clr-namespace:XplorePlane.Views" xmlns:views="clr-namespace:XplorePlane.Views"
xmlns:vm="clr-namespace:XplorePlane.ViewModels.Cnc" xmlns:vm="clr-namespace:XplorePlane.ViewModels.Cnc"
d:DesignHeight="760" d:DesignHeight="760"
d:DesignWidth="402" d:DesignWidth="452"
prism:ViewModelLocator.AutoWireViewModel="True" prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"> mc:Ignorable="d">
@@ -96,11 +96,26 @@
<Setter Property="FontFamily" Value="{StaticResource UiFont}" /> <Setter Property="FontFamily" Value="{StaticResource UiFont}" />
<Setter Property="FontSize" Value="11" /> <Setter Property="FontSize" Value="11" />
</Style> </Style>
<Style x:Key="TreeToolbarButtonCompact" TargetType="Button" BasedOn="{StaticResource TreeToolbarButton}">
<Setter Property="Width" Value="28" />
<Setter Property="Height" Value="28" />
<Setter Property="MinWidth" Value="28" />
<Setter Property="Margin" Value="0,0,4,4" />
<Setter Property="Padding" Value="0" />
<Setter Property="FontSize" Value="10.5" />
</Style>
<Style x:Key="TreeToolbarIcon" TargetType="Image">
<Setter Property="Width" Value="14" />
<Setter Property="Height" Value="14" />
<Setter Property="Margin" Value="0" />
<Setter Property="Stretch" Value="Uniform" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</UserControl.Resources> </UserControl.Resources>
<Border <Border
Width="402" Width="452"
MinWidth="402" MinWidth="452"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Background="{StaticResource PanelBg}" Background="{StaticResource PanelBg}"
BorderBrush="{StaticResource PanelBorder}" BorderBrush="{StaticResource PanelBorder}"
@@ -108,7 +123,7 @@
CornerRadius="4"> CornerRadius="4">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="200" /> <ColumnDefinition Width="180" />
<ColumnDefinition Width="1" /> <ColumnDefinition Width="1" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@@ -125,24 +140,109 @@
Background="{StaticResource HeaderBg}" Background="{StaticResource HeaderBg}"
BorderBrush="{StaticResource SeparatorBrush}" BorderBrush="{StaticResource SeparatorBrush}"
BorderThickness="0,0,0,1"> BorderThickness="0,0,0,1">
<WrapPanel> <StackPanel>
<WrapPanel/>
<WrapPanel Margin="0,4,0,0" Visibility="Collapsed">
<Button <Button
Command="{Binding NewProgramCommand}" Command="{Binding InsertReferencePointCommand}"
Content="新建" Content="参考点"
Style="{StaticResource TreeToolbarButton}" /> Style="{StaticResource TreeToolbarButtonCompact}" />
<Button <Button
Command="{Binding SaveProgramCommand}" Command="{Binding InsertSavePositionCommand}"
Content="保存" Content="添加位置"
Style="{StaticResource TreeToolbarButton}" /> Style="{StaticResource TreeToolbarButtonCompact}" />
<Button <Button
Command="{Binding LoadProgramCommand}" Command="{Binding InsertInspectionModuleCommand}"
Content="加载" Content="检测模块"
Style="{StaticResource TreeToolbarButton}" /> Style="{StaticResource TreeToolbarButtonCompact}" />
<Button <Button
Command="{Binding ExportCsvCommand}" Command="{Binding InsertInspectionMarkerCommand}"
Content="导出" Content="检测标记"
Style="{StaticResource TreeToolbarButton}" /> Style="{StaticResource TreeToolbarButtonCompact}" />
<Button
Command="{Binding InsertPauseDialogCommand}"
Content="消息弹窗"
Style="{StaticResource TreeToolbarButtonCompact}" />
<Button
Command="{Binding InsertWaitDelayCommand}"
Content="插入等待"
Style="{StaticResource TreeToolbarButtonCompact}" />
<Button
Command="{Binding InsertCompleteProgramCommand}"
Content="完成"
Style="{StaticResource TreeToolbarButtonCompact}" />
</WrapPanel> </WrapPanel>
<WrapPanel Margin="0,4,0,0">
<WrapPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</WrapPanel.Resources>
<Button
Command="{Binding InsertReferencePointCommand}"
Style="{StaticResource TreeToolbarButtonCompact}"
ToolTip="参考点">
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/Icons/reference.png" Style="{StaticResource TreeToolbarIcon}" />
<TextBlock Text="参考点" />
</StackPanel>
</Button>
<Button
Command="{Binding InsertSavePositionCommand}"
Style="{StaticResource TreeToolbarButtonCompact}"
ToolTip="添加位置">
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/Icons/add-pos.png" Style="{StaticResource TreeToolbarIcon}" />
<TextBlock Text="添加位置" />
</StackPanel>
</Button>
<Button
Command="{Binding InsertInspectionModuleCommand}"
Style="{StaticResource TreeToolbarButtonCompact}"
ToolTip="检测模块">
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/Icons/Module.png" Style="{StaticResource TreeToolbarIcon}" />
<TextBlock Text="检测模块" />
</StackPanel>
</Button>
<Button
Command="{Binding InsertInspectionMarkerCommand}"
Style="{StaticResource TreeToolbarButtonCompact}"
ToolTip="检测标记">
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/Icons/mark.png" Style="{StaticResource TreeToolbarIcon}" />
<TextBlock Text="检测标记" />
</StackPanel>
</Button>
<Button
Command="{Binding InsertPauseDialogCommand}"
Style="{StaticResource TreeToolbarButtonCompact}"
ToolTip="消息弹窗">
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/Icons/message.png" Style="{StaticResource TreeToolbarIcon}" />
<TextBlock Text="消息弹窗" />
</StackPanel>
</Button>
<Button
Command="{Binding InsertWaitDelayCommand}"
Style="{StaticResource TreeToolbarButtonCompact}"
ToolTip="插入等待">
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/Icons/wait.png" Style="{StaticResource TreeToolbarIcon}" />
<TextBlock Text="插入等待" />
</StackPanel>
</Button>
<Button
Command="{Binding InsertCompleteProgramCommand}"
Style="{StaticResource TreeToolbarButtonCompact}"
ToolTip="完成">
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/Icons/finish.png" Style="{StaticResource TreeToolbarIcon}" />
<TextBlock Text="完成" />
</StackPanel>
</Button>
</WrapPanel>
</StackPanel>
</Border> </Border>
<TreeView <TreeView
@@ -180,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>
@@ -188,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>
@@ -205,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" />
@@ -212,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"
@@ -230,6 +336,7 @@
</Grid> </Grid>
<Border <Border
Grid.RowSpan="2"
Grid.Column="1" Grid.Column="1"
Width="16" Width="16"
Height="16" Height="16"
@@ -246,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"
@@ -257,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"
@@ -275,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>
@@ -287,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>
@@ -469,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;
}
} }
} }
@@ -63,7 +63,7 @@
<Setter Property="FontWeight" Value="Bold" /> <Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="#1c1c1b" /> <Setter Property="Foreground" Value="#1c1c1b" />
<Setter Property="BorderBrush" Value="#cdcbcb" /> <Setter Property="BorderBrush" Value="#cdcbcb" />
<Setter Property="Padding" Value="4,6,4,4" /> <Setter Property="Padding" Value="3,4,3,3" />
</Style> </Style>
</UserControl.Resources> </UserControl.Resources>
@@ -145,7 +145,7 @@
<!-- 右侧:算子选择 + 参数配置 --> <!-- 右侧:算子选择 + 参数配置 -->
<Border Grid.Column="3" Style="{StaticResource PanelBorderStyle}"> <Border Grid.Column="3" Style="{StaticResource PanelBorderStyle}">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel Margin="10,8,10,10"> <StackPanel Margin="8,6,8,8">
<GroupBox Margin="0,0,0,8" Header="选择算子"> <GroupBox Margin="0,0,0,8" Header="选择算子">
<ComboBox <ComboBox
@@ -204,4 +204,4 @@
</Grid> </Grid>
</Border> </Border>
</Grid> </Grid>
</UserControl> </UserControl>
@@ -100,6 +100,7 @@
<TabControl x:Name="ToolboxListBox" <TabControl x:Name="ToolboxListBox"
Margin="8" Margin="8"
ItemContainerStyle="{StaticResource OperatorToolboxTabItemStyle}" ItemContainerStyle="{StaticResource OperatorToolboxTabItemStyle}"
SelectedItem="{Binding SelectedGroup, Mode=TwoWay}"
ItemsSource="{Binding FilteredGroups}"> ItemsSource="{Binding FilteredGroups}">
<TabControl.ItemTemplate> <TabControl.ItemTemplate>
<DataTemplate> <DataTemplate>
@@ -3,7 +3,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:XplorePlane.Views" xmlns:views="clr-namespace:XplorePlane.Views"
Title="算子工具箱" Title="算子工具箱"
Width="500" Height="560" Width="460" Height="540"
MinWidth="420" MinHeight="500"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
ShowInTaskbar="False" ShowInTaskbar="False"
WindowStyle="None" WindowStyle="None"
@@ -80,25 +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="加载配方" />
</StackPanel> </StackPanel>
<TextBlock <TextBlock
@@ -132,10 +132,20 @@
x:Name="NodeContainer" x:Name="NodeContainer"
Margin="2" Margin="2"
Padding="2" Padding="2"
Tag="{Binding DataContext, ElementName=RootControl}"
Background="Transparent" Background="Transparent"
BorderBrush="Transparent" BorderBrush="Transparent"
BorderThickness="1" BorderThickness="1"
CornerRadius="3"> CornerRadius="3">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="执行到此处"
Command="{Binding PlacementTarget.Tag.ExecuteToNodeCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding}" />
<MenuItem Header="执行全部"
Command="{Binding PlacementTarget.Tag.ClearExecutionRangeCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</Border.ContextMenu>
<Grid x:Name="NodeRoot" MinHeight="48"> <Grid x:Name="NodeRoot" MinHeight="48">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="44" /> <ColumnDefinition Width="44" />
@@ -193,7 +203,7 @@
FontFamily="{StaticResource UiFont}" FontFamily="{StaticResource UiFont}"
FontSize="10" FontSize="10"
Foreground="#6E6E6E" Foreground="#6E6E6E"
Text="已启用" /> Text="{Binding NodeStateText}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>
@@ -209,13 +219,37 @@
<Setter TargetName="IconBorder" Property="BorderBrush" Value="{StaticResource DisabledNodeLine}" /> <Setter TargetName="IconBorder" Property="BorderBrush" Value="{StaticResource DisabledNodeLine}" />
<Setter TargetName="IconBorder" Property="Background" Value="#ECECEC" /> <Setter TargetName="IconBorder" Property="Background" Value="#ECECEC" />
<Setter TargetName="NodeTitle" Property="Foreground" Value="{StaticResource DisabledNodeText}" /> <Setter TargetName="NodeTitle" Property="Foreground" Value="{StaticResource DisabledNodeText}" />
<Setter TargetName="NodeState" Property="Text" Value="已停用" />
<Setter TargetName="NodeState" Property="Foreground" Value="#9A6767" /> <Setter TargetName="NodeState" Property="Foreground" Value="#9A6767" />
</DataTrigger> </DataTrigger>
<DataTrigger Binding="{Binding IsExecutionEndNode}" Value="True">
<Setter TargetName="NodeContainer" Property="Background" Value="#E8F6EA" />
<Setter TargetName="NodeContainer" Property="BorderBrush" Value="#4F9D69" />
<Setter TargetName="IconBorder" Property="Background" Value="#E8F6EA" />
<Setter TargetName="IconBorder" Property="BorderBrush" Value="#4F9D69" />
<Setter TargetName="NodeState" Property="Foreground" Value="#2E7D32" />
</DataTrigger>
<DataTrigger Binding="{Binding IsSkippedByExecutionRange}" Value="True">
<Setter TargetName="NodeContainer" Property="Background" Value="#FAFAFA" />
<Setter TargetName="NodeContainer" Property="Opacity" Value="0.72" />
<Setter TargetName="TopLine" Property="Stroke" Value="#D0D0D0" />
<Setter TargetName="BottomLine" Property="Stroke" Value="#D0D0D0" />
<Setter TargetName="IconBorder" Property="BorderBrush" Value="#C8C8C8" />
<Setter TargetName="IconBorder" Property="Background" Value="#F4F4F4" />
<Setter TargetName="NodeTitle" Property="Foreground" Value="#909090" />
<Setter TargetName="NodeState" Property="Foreground" Value="#A0A0A0" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsSelected}" Value="True"> <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsSelected}" Value="True">
<Setter TargetName="NodeContainer" Property="Background" Value="#D9ECFF" /> <Setter TargetName="NodeContainer" Property="Background" Value="#D9ECFF" />
<Setter TargetName="NodeContainer" Property="BorderBrush" Value="#5B9BD5" /> <Setter TargetName="NodeContainer" Property="BorderBrush" Value="#5B9BD5" />
</DataTrigger> </DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExecutionEndNode}" Value="True" />
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsSelected}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="NodeContainer" Property="Background" Value="#DFF0E3" />
<Setter TargetName="NodeContainer" Property="BorderBrush" Value="#3F8A58" />
</MultiDataTrigger>
<MultiDataTrigger> <MultiDataTrigger>
<MultiDataTrigger.Conditions> <MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsEnabled}" Value="False" /> <Condition Binding="{Binding IsEnabled}" Value="False" />
@@ -225,6 +259,15 @@
<Setter TargetName="NodeContainer" Property="BorderBrush" Value="#7E9AB6" /> <Setter TargetName="NodeContainer" Property="BorderBrush" Value="#7E9AB6" />
<Setter TargetName="NodeContainer" Property="Opacity" Value="1" /> <Setter TargetName="NodeContainer" Property="Opacity" Value="1" />
</MultiDataTrigger> </MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSkippedByExecutionRange}" Value="True" />
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsSelected}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="NodeContainer" Property="Background" Value="#EEF1F4" />
<Setter TargetName="NodeContainer" Property="BorderBrush" Value="#AAB4BF" />
<Setter TargetName="NodeContainer" Property="Opacity" Value="0.9" />
</MultiDataTrigger>
</DataTemplate.Triggers> </DataTemplate.Triggers>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
@@ -274,9 +317,53 @@
Text="{Binding DisplayName}" Text="{Binding DisplayName}"
TextTrimming="CharacterEllipsis" /> TextTrimming="CharacterEllipsis" />
<Grid Grid.Column="1">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSliderInput}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="58" />
</Grid.ColumnDefinitions>
<Slider
Grid.Column="0"
Margin="0,0,8,0"
VerticalAlignment="Center"
IsSnapToTickEnabled="{Binding IsIntegerSlider}"
LargeChange="{Binding SliderTickFrequency}"
Maximum="{Binding SliderMaximum}"
Minimum="{Binding SliderMinimum}"
SmallChange="{Binding SliderTickFrequency}"
TickFrequency="{Binding SliderTickFrequency}"
Value="{Binding SliderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Border
Grid.Column="1"
Padding="2,2"
Background="#F8F8F8"
BorderBrush="#CDCBCB"
BorderThickness="1"
CornerRadius="2">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{StaticResource UiFont}"
FontSize="11"
Text="{Binding DisplayValueText}" />
</Border>
</Grid>
<TextBox <TextBox
Grid.Column="1" Grid.Column="1"
Padding="4,2" Padding="2,2"
BorderBrush="#CDCBCB" BorderBrush="#CDCBCB"
BorderThickness="1" BorderThickness="1"
FontFamily="{StaticResource UiFont}" FontFamily="{StaticResource UiFont}"
@@ -59,6 +59,8 @@ namespace XplorePlane.Views
PipelineListBox.PreviewMouseMove += OnPreviewMouseMove; PipelineListBox.PreviewMouseMove += OnPreviewMouseMove;
PipelineListBox.PreviewMouseLeftButtonUp -= OnPreviewMouseLeftButtonUp; PipelineListBox.PreviewMouseLeftButtonUp -= OnPreviewMouseLeftButtonUp;
PipelineListBox.PreviewMouseLeftButtonUp += OnPreviewMouseLeftButtonUp; PipelineListBox.PreviewMouseLeftButtonUp += OnPreviewMouseLeftButtonUp;
PipelineListBox.PreviewMouseRightButtonDown -= OnPreviewMouseRightButtonDown;
PipelineListBox.PreviewMouseRightButtonDown += OnPreviewMouseRightButtonDown;
PipelineListBox.MouseDoubleClick -= OnMouseDoubleClick; PipelineListBox.MouseDoubleClick -= OnMouseDoubleClick;
PipelineListBox.MouseDoubleClick += OnMouseDoubleClick; PipelineListBox.MouseDoubleClick += OnMouseDoubleClick;
PipelineListBox.PreviewKeyDown -= OnPreviewKeyDown; PipelineListBox.PreviewKeyDown -= OnPreviewKeyDown;
@@ -133,6 +135,16 @@ namespace XplorePlane.Views
ResetDragState(); ResetDragState();
} }
private void OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var clickedNode = FindNodeFromOriginalSource(e.OriginalSource);
if (clickedNode == null || IsInteractiveChild(e.OriginalSource))
return;
PipelineListBox.SelectedItem = clickedNode;
PipelineListBox.Focus();
}
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e) private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{ {
var vm = DataContext as IPipelineEditorHostViewModel; var vm = DataContext as IPipelineEditorHostViewModel;
+3 -7
View File
@@ -279,7 +279,7 @@
Size="Large" Size="Large"
SmallImage="/Assets/Icons/cnc.png" SmallImage="/Assets/Icons/cnc.png"
Text="CNC 编辑" /> Text="CNC 编辑" />
<!--
<StackPanel> <StackPanel>
<telerik:RadRibbonButton <telerik:RadRibbonButton
telerik:ScreenTip.Title="参考点" telerik:ScreenTip.Title="参考点"
@@ -313,12 +313,7 @@
Command="{Binding InsertInspectionModuleCommand}" Command="{Binding InsertInspectionModuleCommand}"
SmallImage="/Assets/Icons/Module.png" SmallImage="/Assets/Icons/Module.png"
Text="检测模块" /> Text="检测模块" />
<telerik:RadRibbonButton
telerik:ScreenTip.Title="全部保存"
Size="Medium"
Command="{Binding SaveCncProgramCommand}"
SmallImage="/Assets/Icons/saveall.png"
Text="全部保存" />
</StackPanel> </StackPanel>
<StackPanel> <StackPanel>
<telerik:RadRibbonButton <telerik:RadRibbonButton
@@ -334,6 +329,7 @@
SmallImage="/Assets/Icons/wait.png" SmallImage="/Assets/Icons/wait.png"
Text="插入等待" /> Text="插入等待" />
</StackPanel> </StackPanel>
-->
<telerik:RadRibbonButton <telerik:RadRibbonButton
telerik:ScreenTip.Description="打开矩阵编排窗口,配置多工件阵列检测方案" telerik:ScreenTip.Description="打开矩阵编排窗口,配置多工件阵列检测方案"
+6
View File
@@ -17,6 +17,7 @@
<Page Remove="MainWindow.xaml" /> <Page Remove="MainWindow.xaml" />
<Page Remove="Views\ImageProcessing\ImageProcessingPanelView.xaml" /> <Page Remove="Views\ImageProcessing\ImageProcessingPanelView.xaml" />
<Page Remove="Views\ImageProcessing\ImageProcessingWindow.xaml" /> <Page Remove="Views\ImageProcessing\ImageProcessingWindow.xaml" />
<Page Remove="Views\ImageProcessing\PipelineEditorWindow.xaml" />
<Page Remove="Views\Main\MainWindowB.xaml" /> <Page Remove="Views\Main\MainWindowB.xaml" />
<Page Remove="Views\Main\NavigationPanelView.xaml" /> <Page Remove="Views\Main\NavigationPanelView.xaml" />
</ItemGroup> </ItemGroup>
@@ -149,8 +150,13 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>Libs\Hardware\zh-TW\%(Filename)%(Extension)</Link> <Link>Libs\Hardware\zh-TW\%(Filename)%(Extension)</Link>
</None> </None>
<Compile Remove="Views\Hardware\**" />
<EmbeddedResource Remove="Views\Hardware\**" />
<None Remove="Views\Hardware\**" />
<Page Remove="Views\Hardware\**" />
<Compile Remove="Views\ImageProcessing\ImageProcessingPanelView.xaml.cs" /> <Compile Remove="Views\ImageProcessing\ImageProcessingPanelView.xaml.cs" />
<Compile Remove="Views\ImageProcessing\ImageProcessingWindow.xaml.cs" /> <Compile Remove="Views\ImageProcessing\ImageProcessingWindow.xaml.cs" />
<Compile Remove="Views\ImageProcessing\PipelineEditorWindow.xaml.cs" />
<Compile Remove="Views\Main\MainWindowB.xaml.cs" /> <Compile Remove="Views\Main\MainWindowB.xaml.cs" />
<Compile Remove="Views\Main\NavigationPanelView.xaml.cs" /> <Compile Remove="Views\Main\NavigationPanelView.xaml.cs" />
<Content Include="XplorerPlane.ico"> <Content Include="XplorerPlane.ico">