突出检测模块和检测标记的父子节点关系

This commit is contained in:
zhengxuan.zhang
2026-04-22 01:44:52 +08:00
parent 8beed66ef0
commit 3211dbc473
3 changed files with 235 additions and 19 deletions
+232 -16
View File
@@ -33,6 +33,7 @@ namespace XplorePlane.ViewModels.Cnc
private CncNodeViewModel _selectedNode; private CncNodeViewModel _selectedNode;
private bool _isModified; private bool _isModified;
private string _programName; private string _programName;
private Guid? _preferredSelectedNodeId;
public CncEditorViewModel( public CncEditorViewModel(
ICncProgramService cncProgramService, ICncProgramService cncProgramService,
@@ -136,8 +137,9 @@ namespace XplorePlane.ViewModels.Cnc
try try
{ {
var node = _cncProgramService.CreateNode(nodeType); var node = _cncProgramService.CreateNode(nodeType);
int afterIndex = SelectedNode?.Index ?? _currentProgram.Nodes.Count - 1; int afterIndex = ResolveInsertAfterIndex(nodeType);
_currentProgram = _cncProgramService.InsertNode(_currentProgram, afterIndex, node); _currentProgram = _cncProgramService.InsertNode(_currentProgram, afterIndex, node);
_preferredSelectedNodeId = node.Id;
OnProgramEdited(); OnProgramEdited();
_logger.Info("Inserted node: Type={NodeType}", nodeType); _logger.Info("Inserted node: Type={NodeType}", nodeType);
@@ -155,7 +157,19 @@ namespace XplorePlane.ViewModels.Cnc
try try
{ {
_currentProgram = _cncProgramService.RemoveNode(_currentProgram, SelectedNode.Index); if (SelectedNode.IsSavePosition)
{
var nodes = _currentProgram.Nodes.ToList();
int startIndex = SelectedNode.Index;
int endIndex = GetSavePositionBlockEndIndex(startIndex);
nodes.RemoveRange(startIndex, endIndex - startIndex + 1);
_currentProgram = ReplaceProgramNodes(nodes);
}
else
{
_currentProgram = _cncProgramService.RemoveNode(_currentProgram, SelectedNode.Index);
}
OnProgramEdited(); OnProgramEdited();
_logger.Info("Deleted node at index: {Index}", SelectedNode.Index); _logger.Info("Deleted node at index: {Index}", SelectedNode.Index);
} }
@@ -179,10 +193,18 @@ namespace XplorePlane.ViewModels.Cnc
try try
{ {
_currentProgram = _cncProgramService.MoveNode(_currentProgram, nodeVm.Index, nodeVm.Index - 1); if (IsSavePositionChild(nodeVm.NodeType))
{
MoveSavePositionChild(nodeVm, moveDown: false);
}
else
{
MoveRootBlock(nodeVm, moveDown: false);
}
OnProgramEdited(); OnProgramEdited();
} }
catch (ArgumentOutOfRangeException ex) catch (Exception ex) when (ex is ArgumentOutOfRangeException or InvalidOperationException)
{ {
_logger.Warn("Move node up failed: {Message}", ex.Message); _logger.Warn("Move node up failed: {Message}", ex.Message);
} }
@@ -195,10 +217,18 @@ namespace XplorePlane.ViewModels.Cnc
try try
{ {
_currentProgram = _cncProgramService.MoveNode(_currentProgram, nodeVm.Index, nodeVm.Index + 1); if (IsSavePositionChild(nodeVm.NodeType))
{
MoveSavePositionChild(nodeVm, moveDown: true);
}
else
{
MoveRootBlock(nodeVm, moveDown: true);
}
OnProgramEdited(); OnProgramEdited();
} }
catch (ArgumentOutOfRangeException ex) catch (Exception ex) when (ex is ArgumentOutOfRangeException or InvalidOperationException)
{ {
_logger.Warn("Move node down failed: {Message}", ex.Message); _logger.Warn("Move node down failed: {Message}", ex.Message);
} }
@@ -349,12 +379,12 @@ namespace XplorePlane.ViewModels.Cnc
private void RefreshNodes() private void RefreshNodes()
{ {
var selectedId = SelectedNode?.Id; var selectedId = _preferredSelectedNodeId ?? SelectedNode?.Id;
var expansionState = Nodes.ToDictionary(node => node.Id, node => node.IsExpanded); var expansionState = Nodes.ToDictionary(node => node.Id, node => node.IsExpanded);
var flatNodes = new List<CncNodeViewModel>(); var flatNodes = new List<CncNodeViewModel>();
var rootNodes = new List<CncNodeViewModel>(); var rootNodes = new List<CncNodeViewModel>();
CncNodeViewModel currentModule = null; CncNodeViewModel currentSavePosition = null;
if (_currentProgram?.Nodes != null) if (_currentProgram?.Nodes != null)
{ {
@@ -367,21 +397,21 @@ namespace XplorePlane.ViewModels.Cnc
flatNodes.Add(vm); flatNodes.Add(vm);
if (vm.IsInspectionModule) if (vm.IsSavePosition)
{ {
rootNodes.Add(vm); rootNodes.Add(vm);
currentModule = vm; currentSavePosition = vm;
continue; continue;
} }
if (currentModule != null && IsModuleChild(vm.NodeType)) if (currentSavePosition != null && IsSavePositionChild(vm.NodeType))
{ {
currentModule.Children.Add(vm); currentSavePosition.Children.Add(vm);
continue; continue;
} }
rootNodes.Add(vm); rootNodes.Add(vm);
currentModule = null; currentSavePosition = null;
} }
} }
@@ -391,13 +421,199 @@ namespace XplorePlane.ViewModels.Cnc
SelectedNode = selectedId.HasValue SelectedNode = selectedId.HasValue
? Nodes.FirstOrDefault(node => node.Id == selectedId.Value) ?? Nodes.LastOrDefault() ? Nodes.FirstOrDefault(node => node.Id == selectedId.Value) ?? Nodes.LastOrDefault()
: Nodes.LastOrDefault(); : Nodes.LastOrDefault();
_preferredSelectedNodeId = null;
} }
private static bool IsModuleChild(CncNodeType type) private int ResolveInsertAfterIndex(CncNodeType nodeType)
{
if (_currentProgram == null || _currentProgram.Nodes.Count == 0)
{
return -1;
}
if (!IsSavePositionChild(nodeType))
{
return SelectedNode?.Index ?? _currentProgram.Nodes.Count - 1;
}
int? savePositionIndex = FindOwningSavePositionIndex(SelectedNode?.Index);
if (!savePositionIndex.HasValue)
{
throw new InvalidOperationException("请先选择一个“保存位置”节点,再插入标记点或检测模块。");
}
return GetSavePositionBlockEndIndex(savePositionIndex.Value);
}
private void MoveSavePositionChild(CncNodeViewModel nodeVm, bool moveDown)
{
int? parentIndex = FindOwningSavePositionIndex(nodeVm.Index);
if (!parentIndex.HasValue)
{
throw new InvalidOperationException("当前子节点未归属于任何保存位置,无法移动。");
}
int childStartIndex = parentIndex.Value + 1;
int childEndIndex = GetSavePositionBlockEndIndex(parentIndex.Value);
int targetIndex = moveDown ? nodeVm.Index + 1 : nodeVm.Index - 1;
if (targetIndex < childStartIndex || targetIndex > childEndIndex)
{
return;
}
_currentProgram = _cncProgramService.MoveNode(_currentProgram, nodeVm.Index, targetIndex);
_preferredSelectedNodeId = nodeVm.Id;
}
private void MoveRootBlock(CncNodeViewModel nodeVm, bool moveDown)
{
var blocks = BuildRootBlocks(_currentProgram.Nodes);
int blockIndex = blocks.FindIndex(block => block.Start == nodeVm.Index);
if (blockIndex < 0)
{
return;
}
if (!moveDown && blockIndex == 0)
{
return;
}
if (moveDown && blockIndex >= blocks.Count - 1)
{
return;
}
var currentBlock = blocks[blockIndex];
var targetBlock = blocks[moveDown ? blockIndex + 1 : blockIndex - 1];
var nodes = _currentProgram.Nodes.ToList();
var movingNodes = nodes.GetRange(currentBlock.Start, currentBlock.End - currentBlock.Start + 1);
nodes.RemoveRange(currentBlock.Start, movingNodes.Count);
int insertAt = moveDown
? targetBlock.End - movingNodes.Count + 1
: targetBlock.Start;
nodes.InsertRange(insertAt, movingNodes);
_currentProgram = ReplaceProgramNodes(nodes);
_preferredSelectedNodeId = nodeVm.Id;
}
private int? FindOwningSavePositionIndex(int? startIndex)
{
if (!startIndex.HasValue || _currentProgram == null)
{
return null;
}
int index = startIndex.Value;
if (index < 0 || index >= _currentProgram.Nodes.Count)
{
return null;
}
var selectedType = _currentProgram.Nodes[index].NodeType;
if (_currentProgram.Nodes[index].NodeType == CncNodeType.SavePosition)
{
return index;
}
if (!IsSavePositionChild(selectedType))
{
return null;
}
for (int i = index - 1; i >= 0; i--)
{
var type = _currentProgram.Nodes[i].NodeType;
if (type == CncNodeType.SavePosition)
{
return i;
}
if (!IsSavePositionChild(type))
{
break;
}
}
return null;
}
private int GetSavePositionBlockEndIndex(int savePositionIndex)
{
if (_currentProgram == null)
{
return savePositionIndex;
}
int endIndex = savePositionIndex;
for (int i = savePositionIndex + 1; i < _currentProgram.Nodes.Count; i++)
{
if (!IsSavePositionChild(_currentProgram.Nodes[i].NodeType))
{
break;
}
endIndex = i;
}
return endIndex;
}
private static List<(int Start, int End)> BuildRootBlocks(IReadOnlyList<CncNode> nodes)
{
var blocks = new List<(int Start, int End)>();
for (int i = 0; i < nodes.Count; i++)
{
if (IsSavePositionChild(nodes[i].NodeType))
{
continue;
}
int end = i;
if (nodes[i].NodeType == CncNodeType.SavePosition)
{
for (int j = i + 1; j < nodes.Count; j++)
{
if (!IsSavePositionChild(nodes[j].NodeType))
{
break;
}
end = j;
}
}
blocks.Add((i, end));
}
return blocks;
}
private CncProgram ReplaceProgramNodes(List<CncNode> nodes)
{
var renumberedNodes = nodes
.Select((node, index) => node with { Index = index })
.ToList()
.AsReadOnly();
return _currentProgram with
{
Nodes = renumberedNodes,
UpdatedAt = DateTime.UtcNow
};
}
private static bool IsSavePositionChild(CncNodeType type)
{ {
return type is CncNodeType.InspectionMarker return type is CncNodeType.InspectionMarker
or CncNodeType.PauseDialog or CncNodeType.InspectionModule;
or CncNodeType.WaitDelay;
} }
private void PublishProgramChanged() private void PublishProgramChanged()
+1 -1
View File
@@ -14,7 +14,7 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Border Grid.Row="0" Background="#F0F0F0" BorderBrush="#DDDDDD" BorderThickness="0,0,0,1"> <Border Grid.Row="0" Background="#F0F0F0" BorderBrush="#DDDDDD" BorderThickness="0,0,0,1">
<TextBlock Margin="4,2" HorizontalAlignment="Left" VerticalAlignment="Center" <TextBlock Margin="4,2" HorizontalAlignment="Left" VerticalAlignment="Center"
FontWeight="SemiBold" Foreground="#333333" Text="图像" /> FontWeight="SemiBold" Foreground="#333333" Text="图像处理" />
</Border> </Border>
<ContentControl Grid.Row="1" Content="{Binding ImagePanelContent}" /> <ContentControl Grid.Row="1" Content="{Binding ImagePanelContent}" />
</Grid> </Grid>
+2 -2
View File
@@ -312,11 +312,11 @@
</StackPanel> </StackPanel>
<StackPanel> <StackPanel>
<telerik:RadRibbonButton <telerik:RadRibbonButton
telerik:ScreenTip.Title="标记" telerik:ScreenTip.Title="检测标记"
Size="Medium" Size="Medium"
Command="{Binding InsertInspectionMarkerCommand}" Command="{Binding InsertInspectionMarkerCommand}"
SmallImage="/Assets/Icons/mark.png" SmallImage="/Assets/Icons/mark.png"
Text="标记" /> Text="检测标记" />
<telerik:RadRibbonButton <telerik:RadRibbonButton
telerik:ScreenTip.Title="模块" telerik:ScreenTip.Title="模块"
Size="Medium" Size="Medium"