From 3211dbc473524ced171dfe48e37398739f7be548 Mon Sep 17 00:00:00 2001 From: "zhengxuan.zhang" Date: Wed, 22 Apr 2026 01:44:52 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AA=81=E5=87=BA=E6=A3=80=E6=B5=8B=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=92=8C=E6=A3=80=E6=B5=8B=E6=A0=87=E8=AE=B0=E7=9A=84?= =?UTF-8?q?=E7=88=B6=E5=AD=90=E8=8A=82=E7=82=B9=E5=85=B3=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModels/Cnc/CncEditorViewModel.cs | 248 ++++++++++++++++-- XplorePlane/Views/Main/ImagePanelView.xaml | 2 +- XplorePlane/Views/Main/MainWindow.xaml | 4 +- 3 files changed, 235 insertions(+), 19 deletions(-) diff --git a/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs b/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs index 0b3590f..61a3cdb 100644 --- a/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs +++ b/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs @@ -33,6 +33,7 @@ namespace XplorePlane.ViewModels.Cnc private CncNodeViewModel _selectedNode; private bool _isModified; private string _programName; + private Guid? _preferredSelectedNodeId; public CncEditorViewModel( ICncProgramService cncProgramService, @@ -136,8 +137,9 @@ namespace XplorePlane.ViewModels.Cnc try { var node = _cncProgramService.CreateNode(nodeType); - int afterIndex = SelectedNode?.Index ?? _currentProgram.Nodes.Count - 1; + int afterIndex = ResolveInsertAfterIndex(nodeType); _currentProgram = _cncProgramService.InsertNode(_currentProgram, afterIndex, node); + _preferredSelectedNodeId = node.Id; OnProgramEdited(); _logger.Info("Inserted node: Type={NodeType}", nodeType); @@ -155,7 +157,19 @@ namespace XplorePlane.ViewModels.Cnc 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(); _logger.Info("Deleted node at index: {Index}", SelectedNode.Index); } @@ -179,10 +193,18 @@ namespace XplorePlane.ViewModels.Cnc try { - _currentProgram = _cncProgramService.MoveNode(_currentProgram, nodeVm.Index, nodeVm.Index - 1); + if (IsSavePositionChild(nodeVm.NodeType)) + { + MoveSavePositionChild(nodeVm, moveDown: false); + } + else + { + MoveRootBlock(nodeVm, moveDown: false); + } + OnProgramEdited(); } - catch (ArgumentOutOfRangeException ex) + catch (Exception ex) when (ex is ArgumentOutOfRangeException or InvalidOperationException) { _logger.Warn("Move node up failed: {Message}", ex.Message); } @@ -195,10 +217,18 @@ namespace XplorePlane.ViewModels.Cnc try { - _currentProgram = _cncProgramService.MoveNode(_currentProgram, nodeVm.Index, nodeVm.Index + 1); + if (IsSavePositionChild(nodeVm.NodeType)) + { + MoveSavePositionChild(nodeVm, moveDown: true); + } + else + { + MoveRootBlock(nodeVm, moveDown: true); + } + OnProgramEdited(); } - catch (ArgumentOutOfRangeException ex) + catch (Exception ex) when (ex is ArgumentOutOfRangeException or InvalidOperationException) { _logger.Warn("Move node down failed: {Message}", ex.Message); } @@ -349,12 +379,12 @@ namespace XplorePlane.ViewModels.Cnc private void RefreshNodes() { - var selectedId = SelectedNode?.Id; + var selectedId = _preferredSelectedNodeId ?? SelectedNode?.Id; var expansionState = Nodes.ToDictionary(node => node.Id, node => node.IsExpanded); var flatNodes = new List(); var rootNodes = new List(); - CncNodeViewModel currentModule = null; + CncNodeViewModel currentSavePosition = null; if (_currentProgram?.Nodes != null) { @@ -367,21 +397,21 @@ namespace XplorePlane.ViewModels.Cnc flatNodes.Add(vm); - if (vm.IsInspectionModule) + if (vm.IsSavePosition) { rootNodes.Add(vm); - currentModule = vm; + currentSavePosition = vm; continue; } - if (currentModule != null && IsModuleChild(vm.NodeType)) + if (currentSavePosition != null && IsSavePositionChild(vm.NodeType)) { - currentModule.Children.Add(vm); + currentSavePosition.Children.Add(vm); continue; } rootNodes.Add(vm); - currentModule = null; + currentSavePosition = null; } } @@ -391,13 +421,199 @@ namespace XplorePlane.ViewModels.Cnc SelectedNode = selectedId.HasValue ? Nodes.FirstOrDefault(node => node.Id == selectedId.Value) ?? 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 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 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 - or CncNodeType.PauseDialog - or CncNodeType.WaitDelay; + or CncNodeType.InspectionModule; } private void PublishProgramChanged() diff --git a/XplorePlane/Views/Main/ImagePanelView.xaml b/XplorePlane/Views/Main/ImagePanelView.xaml index a772851..8e2f834 100644 --- a/XplorePlane/Views/Main/ImagePanelView.xaml +++ b/XplorePlane/Views/Main/ImagePanelView.xaml @@ -14,7 +14,7 @@ + FontWeight="SemiBold" Foreground="#333333" Text="图像处理" /> diff --git a/XplorePlane/Views/Main/MainWindow.xaml b/XplorePlane/Views/Main/MainWindow.xaml index 0ad8b9f..5fe132d 100644 --- a/XplorePlane/Views/Main/MainWindow.xaml +++ b/XplorePlane/Views/Main/MainWindow.xaml @@ -312,11 +312,11 @@ + Text="检测标记" />