修复高级算子的ROI编辑能力
This commit is contained in:
@@ -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);
|
||||
|
||||
// 更新 RoiMode(BgaVoidRate 专用)
|
||||
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 延迟:设置 SelectedROI,Points.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;
|
||||
|
||||
Reference in New Issue
Block a user