修复高级算子的ROI编辑能力

This commit is contained in:
zhengxuan.zhang
2026-05-21 14:35:49 +08:00
parent 2d14954bd3
commit 2ac84ecc85
4 changed files with 142 additions and 18 deletions
@@ -139,10 +139,8 @@ namespace XP.ImageProcessing.RoiControl.Controls
control.ResetView();
}
// 图像切换时清除测量叠加层和ROI
// 图像切换时清除测量叠加层,但保留 ROI(ROI 是用户标注,应跟随图像持续显示)
control.ClearMeasurements();
control.ROIItems?.Clear();
control.SelectedROI = null;
// 图像尺寸变化后刷新十字线
if (control.ShowCrosshair)
@@ -628,6 +628,10 @@ namespace XplorePlane.ViewModels.Cnc
if (_currentProgram == null)
return;
_logger.Debug("[CNC-ROI][HandleNodeModelChanged] 节点={Name}(idx={Idx})updatedNode类型={Type},调用栈:{Stack}",
nodeVm.Name, nodeVm.Index, updatedNode.GetType().Name,
new System.Diagnostics.StackTrace(1, false).ToString().Split('\n')[0].Trim());
_currentProgram = _cncProgramService.UpdateNode(_currentProgram, nodeVm.Index, updatedNode);
IsModified = true;
ProgramName = _currentProgram.Name;
@@ -636,6 +640,9 @@ namespace XplorePlane.ViewModels.Cnc
private void RefreshNodes()
{
_logger.Debug("[CNC-ROI][RefreshNodes] 触发,调用栈:{Stack}",
new System.Diagnostics.StackTrace(1, false).ToString().Split('\n')[0].Trim());
NormalizeDefaultNodeNamesInCurrentProgram();
var selectedId = _preferredSelectedNodeId ?? SelectedNode?.Id;
@@ -93,8 +93,21 @@ namespace XplorePlane.ViewModels.Cnc
get => _selectedNode;
set
{
var oldKey = _selectedNode?.OperatorKey ?? "(null)";
var newKey = value?.OperatorKey ?? "(null)";
_logger.Debug("[CNC-ROI][SelectedNode] setter 触发:old={Old}(id={OldId}), new={New}(id={NewId}), caller={Caller}",
oldKey, _selectedNode?.GetHashCode(), newKey, value?.GetHashCode(),
new System.Diagnostics.StackTrace(1, false).GetFrame(0)?.GetMethod()?.Name ?? "?");
if (!SetProperty(ref _selectedNode, value))
{
_logger.Debug("[CNC-ROI][SelectedNode] 值未变化,跳过");
return;
}
_logger.Info("[CNC-ROI][SelectedNode] 已切换:{Old} → {New},调用栈:{Stack}",
oldKey, newKey,
new System.Diagnostics.StackTrace(1, false).ToString().Split('\n')[0].Trim());
// 切换节点时停止当前 ROI 编辑
CancelRoiEdit();
@@ -171,9 +184,13 @@ namespace XplorePlane.ViewModels.Cnc
private void RefreshFromSelection()
{
_logger.Info("[CNC-ROI][RefreshFromSelection] 触发,调用栈:{Stack}",
new System.Diagnostics.StackTrace(1, false).ToString().Split('\n')[0].Trim());
var selected = _editorViewModel.SelectedNode;
if (selected == null || !selected.IsInspectionModule)
{
_logger.Debug("[CNC-ROI][RefreshFromSelection] 无检测模块,清空");
_activeModuleNode = null;
PipelineNodes.Clear();
SelectedNode = null;
@@ -184,6 +201,8 @@ namespace XplorePlane.ViewModels.Cnc
return;
}
_logger.Info("[CNC-ROI][RefreshFromSelection] 加载模块:{Name}Pipeline节点数={Count}",
selected.Name, selected.Pipeline?.Nodes?.Count ?? 0);
_activeModuleNode = selected;
_currentFilePath = null;
LoadPipelineModel(_activeModuleNode.Pipeline ?? new PipelineModel
@@ -389,6 +408,9 @@ namespace XplorePlane.ViewModels.Cnc
private void LoadPipelineModel(PipelineModel pipeline)
{
_logger.Info("[CNC-ROI][LoadPipelineModel] 开始,节点数={Count},调用栈:{Stack}",
pipeline?.Nodes?.Count ?? 0,
new System.Diagnostics.StackTrace(1, false).ToString().Split('\n')[0].Trim());
_isSynchronizing = true;
try
{
@@ -440,7 +462,14 @@ namespace XplorePlane.ViewModels.Cnc
var parameterVm = new ProcessorParameterVM(definition);
if (savedValues != null && savedValues.TryGetValue(definition.Name, out var savedValue))
{
parameterVm.Value = ConvertSavedValue(savedValue, definition.ValueType);
var converted = ConvertSavedValue(savedValue, definition.ValueType);
parameterVm.Value = converted;
// 只记录 ROI 相关参数
if (definition.Name is "PolyCount" or "RoiMode")
_logger.Debug("[CNC-ROI][LoadNodeParameters] 算子={Key},参数={Param}savedValue={Saved}({SavedType})converted={Conv}({ConvType})",
node.OperatorKey, definition.Name,
savedValue, savedValue?.GetType().Name,
converted, converted?.GetType().Name);
}
parameterVm.PropertyChanged += (_, e) =>
@@ -457,9 +486,29 @@ namespace XplorePlane.ViewModels.Cnc
private void PersistActiveModule(string statusMessage)
{
if (!HasActiveModule || _isSynchronizing)
{
_logger.Debug("[CNC-ROI][PersistActiveModule] 跳过:HasActiveModule={Has}_isSynchronizing={Sync}msg={Msg}",
HasActiveModule, _isSynchronizing, statusMessage);
return;
}
_logger.Debug("[CNC-ROI][PersistActiveModule] 执行:{Msg},调用栈:{Stack}",
statusMessage,
new System.Diagnostics.StackTrace(1, false).ToString().Split('\n')[0].Trim());
_activeModuleNode.Pipeline = BuildPipelineModel();
// 记录写入后 Pipeline 中的 ROI 参数
var roiNode = _activeModuleNode.Pipeline?.Nodes?.FirstOrDefault(
n => AdvancedModuleOperatorKeys.Contains(n.OperatorKey));
if (roiNode != null)
{
roiNode.Parameters.TryGetValue("PolyCount", out var pc);
roiNode.Parameters.TryGetValue("RoiMode", out var rm);
_logger.Debug("[CNC-ROI][PersistActiveModule] 写入Pipeline:算子={Key}PolyCount={PC}RoiMode={RM}",
roiNode.OperatorKey, pc, rm);
}
StatusMessage = statusMessage;
TriggerDebouncedPreview();
}
@@ -698,6 +747,11 @@ namespace XplorePlane.ViewModels.Cnc
StatusMessage = "ROI 编辑中:在图像上点击添加顶点,完成后点击「完成 ROI」";
var existingPoints = ReadRoiPoints(SelectedNode);
var polyCountParam = SelectedNode.Parameters.FirstOrDefault(p => p.Name == "PolyCount");
var roiModeParam = SelectedNode.Parameters.FirstOrDefault(p => p.Name == "RoiMode");
_logger.Info("[CNC-ROI] ExecuteEditRoi:算子={Key},已有顶点数={Count}PolyCount参数值={PC}RoiMode={RM}",
SelectedNode.OperatorKey, existingPoints.Count,
polyCountParam?.Value, roiModeParam?.Value);
_eventAggregator.GetEvent<CncRoiEditRequestedEvent>().Publish(new CncRoiEditRequestedPayload
{
@@ -767,10 +821,21 @@ namespace XplorePlane.ViewModels.Cnc
return Convert.ToInt32(polyCountParam.Value);
}
private static IReadOnlyList<System.Windows.Point> ReadRoiPoints(PipelineNodeViewModel node)
private IReadOnlyList<System.Windows.Point> ReadRoiPoints(PipelineNodeViewModel node)
{
var roiModeParam = node?.Parameters.FirstOrDefault(p => p.Name == "RoiMode");
var polyCountParam = node?.Parameters.FirstOrDefault(p => p.Name == "PolyCount");
_logger.Debug("[CNC-ROI][ReadRoiPoints] 算子={Key}nodeId={Id}RoiMode={RM}({RMType})PolyCount={PC}({PCType})",
node?.OperatorKey, node?.GetHashCode(),
roiModeParam?.Value, roiModeParam?.Value?.GetType().Name,
polyCountParam?.Value, polyCountParam?.Value?.GetType().Name);
int count = GetRoiPointCount(node);
if (count < 3) return Array.Empty<System.Windows.Point>();
if (count < 3)
{
_logger.Debug("[CNC-ROI][ReadRoiPoints] count={Count} < 3,返回空列表", count);
return Array.Empty<System.Windows.Point>();
}
var points = new List<System.Windows.Point>(count);
for (int i = 0; i < count; i++)
@@ -781,24 +846,34 @@ namespace XplorePlane.ViewModels.Cnc
double y = py != null ? Convert.ToDouble(py.Value) : 0;
points.Add(new System.Windows.Point(x, y));
}
_logger.Debug("[CNC-ROI][ReadRoiPoints] 读取完成,返回 {Count} 个顶点", points.Count);
return points;
}
private static void WriteRoiPoints(PipelineNodeViewModel node, IReadOnlyList<System.Windows.Point> points)
private void WriteRoiPoints(PipelineNodeViewModel node, IReadOnlyList<System.Windows.Point> points)
{
if (node == null) return;
int count = points?.Count >= 3 ? points.Count : 0;
_logger.Debug("[CNC-ROI][WriteRoiPoints] 开始写入:算子={Key}nodeId={Id},输入点数={In},将写count={Count}",
node.OperatorKey, node.GetHashCode(), points?.Count ?? 0, count);
// 更新 RoiModeBgaVoidRate 专用)
var roiModeParam = node.Parameters.FirstOrDefault(p => p.Name == "RoiMode");
if (roiModeParam != null)
roiModeParam.Value = count >= 3 ? "Polygon" : "None";
{
var newRoiMode = count >= 3 ? "Polygon" : "None";
_logger.Debug("[CNC-ROI][WriteRoiPoints] 设置 RoiMode{Old} → {New}", roiModeParam.Value, newRoiMode);
roiModeParam.Value = newRoiMode;
}
// 更新 PolyCount
var polyCountParam = node.Parameters.FirstOrDefault(p => p.Name == "PolyCount");
if (polyCountParam != null)
{
_logger.Debug("[CNC-ROI][WriteRoiPoints] 设置 PolyCount{Old} → {New}", polyCountParam.Value, count);
polyCountParam.Value = count;
}
// 更新坐标(最多 32 个点)
for (int i = 0; i < 32; i++)
@@ -810,6 +885,12 @@ namespace XplorePlane.ViewModels.Cnc
if (px != null) px.Value = (int)x;
if (py != null) py.Value = (int)y;
}
// 写入后验证
var verifyRoiMode = node.Parameters.FirstOrDefault(p => p.Name == "RoiMode")?.Value;
var verifyPolyCount = node.Parameters.FirstOrDefault(p => p.Name == "PolyCount")?.Value;
_logger.Debug("[CNC-ROI][WriteRoiPoints] 写入完成验证:RoiMode={RM}PolyCount={PC}",
verifyRoiMode, verifyPolyCount);
}
}
}
@@ -8,6 +8,7 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
using Prism.Ioc;
using Serilog;
using XP.ImageProcessing.RoiControl.Controls;
using XplorePlane.Events;
using XplorePlane.Services;
@@ -17,6 +18,7 @@ namespace XplorePlane.Views
{
public partial class ViewportPanelView : UserControl
{
private static readonly ILogger _log = Log.ForContext<ViewportPanelView>();
private MainViewModel _mainVm;
private MainViewModel GetMainVm()
@@ -187,43 +189,75 @@ namespace XplorePlane.Views
private void OnCncRoiEditRequested(Events.CncRoiEditRequestedPayload payload)
{
_cncRoiPayload = payload;
_log.Information("[CNC-ROI] OnCncRoiEditRequested 触发,ExistingPoints={Count}",
payload?.ExistingPoints?.Count ?? -1);
// 清理旧的 ROI shape
// 清理旧状态(在保存新 payload 之前)
CleanupCncRoi();
_cncRoiPayload = payload;
// 确保 ROIItems 集合存在
if (RoiCanvas.ROIItems == null)
{
_log.Debug("[CNC-ROI] ROIItems 为 null,创建新集合");
RoiCanvas.ROIItems = new System.Collections.ObjectModel.ObservableCollection<XP.ImageProcessing.RoiControl.Models.ROIShape>();
}
// 创建新的多边形 ROI
_log.Debug("[CNC-ROI] 当前 ROIItems.Count={Count}", RoiCanvas.ROIItems.Count);
// 创建新的多边形 ROI(先加入 canvas,再添加顶点,确保 UI 绑定已建立)
_cncRoiShape = new XP.ImageProcessing.RoiControl.Models.PolygonROI
{
Color = "Cyan",
IsSelected = true
IsSelected = false
};
// 恢复已保存的顶点
if (payload.ExistingPoints != null)
RoiCanvas.ROIItems.Add(_cncRoiShape);
_log.Debug("[CNC-ROI] PolygonROI 已加入 ROIItems,当前 Count={Count}", RoiCanvas.ROIItems.Count);
// 恢复已保存的顶点(canvas 已绑定,每次 Add 都会触发 UI 更新)
if (payload.ExistingPoints != null && payload.ExistingPoints.Count >= 3)
{
foreach (var pt in payload.ExistingPoints)
_cncRoiShape.Points.Add(pt);
_log.Information("[CNC-ROI] 已恢复 {Count} 个顶点", _cncRoiShape.Points.Count);
}
else
{
_log.Information("[CNC-ROI] 无已保存顶点,新建空 ROI");
}
RoiCanvas.ROIItems.Add(_cncRoiShape);
RoiCanvas.SelectedROI = _cncRoiShape;
// 顶点变化时回调(在顶点恢复之后订阅,避免恢复时触发不必要的回调)
_cncRoiShape.Points.CollectionChanged += OnCncRoiPointsCollectionChanged;
// 禁用右键菜单,启用画布点击添加顶点
RoiCanvas.SetValue(System.Windows.Controls.ContextMenuService.IsEnabledProperty, false);
RoiCanvas.AddHandler(XP.ImageProcessing.RoiControl.Controls.PolygonRoiCanvas.CanvasClickedEvent,
new RoutedEventHandler(OnCncRoiCanvasClicked));
// 顶点变化时回调
_cncRoiShape.Points.CollectionChanged += OnCncRoiPointsCollectionChanged;
// 延迟设置 SelectedROI,确保 ItemsControl 完成布局后 Adorner 才能正确创建
var shapeRef = _cncRoiShape;
Dispatcher.BeginInvoke(new Action(() =>
{
if (shapeRef == _cncRoiShape) // 确保没有被 cleanup 掉
{
_log.Debug("[CNC-ROI] Dispatcher 延迟:设置 SelectedROIPoints.Count={Count}", shapeRef.Points.Count);
RoiCanvas.SelectedROI = null;
RoiCanvas.SelectedROI = shapeRef;
shapeRef.IsSelected = true;
_log.Information("[CNC-ROI] SelectedROI 已设置,ROI 应显示在画布上");
}
else
{
_log.Warning("[CNC-ROI] Dispatcher 延迟:shapeRef 已被 cleanup,跳过 SelectedROI 设置");
}
}), System.Windows.Threading.DispatcherPriority.Loaded);
}
private void OnCncRoiEditCancelled()
{
_log.Information("[CNC-ROI] OnCncRoiEditCancelled 触发");
CleanupCncRoi();
}
@@ -232,6 +266,8 @@ namespace XplorePlane.Views
if (_cncRoiShape == null || _cncRoiPayload == null) return;
if (e is XP.ImageProcessing.RoiControl.Controls.CanvasClickedEventArgs args)
{
_log.Debug("[CNC-ROI] 画布点击:Position={X},{Y},当前顶点数={Count}",
args.Position.X, args.Position.Y, _cncRoiShape.Points.Count);
InsertPointToPolygon(args.Position, _cncRoiShape.Points);
_cncRoiShape.IsSelected = true;
RoiCanvas.SelectedROI = _cncRoiShape;
@@ -242,11 +278,13 @@ namespace XplorePlane.Views
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (_cncRoiPayload?.OnPointsChanged == null || _cncRoiShape == null) return;
_log.Debug("[CNC-ROI] Points 变化,当前顶点数={Count}", _cncRoiShape.Points.Count);
_cncRoiPayload.OnPointsChanged(new List<System.Windows.Point>(_cncRoiShape.Points));
}
private void CleanupCncRoi()
{
_log.Debug("[CNC-ROI] CleanupCncRoi_cncRoiShape={HasShape}", _cncRoiShape != null);
if (_cncRoiShape != null)
{
_cncRoiShape.Points.CollectionChanged -= OnCncRoiPointsCollectionChanged;