高级模块插入后的再编辑问题,包括ROI的显示和调节,要支持实时调节
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
using Prism.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
namespace XplorePlane.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求在主视口画布上激活 ROI 编辑模式的事件。
|
||||
/// 由 CNC 流水线编辑器发布,ViewportPanelView 订阅并操作 PolygonRoiCanvas。
|
||||
/// </summary>
|
||||
public sealed class CncRoiEditRequestedEvent : PubSubEvent<CncRoiEditRequestedPayload>
|
||||
{
|
||||
}
|
||||
|
||||
public class CncRoiEditRequestedPayload
|
||||
{
|
||||
/// <summary>已保存的 ROI 多边形顶点(图像坐标)。为空表示新建 ROI。</summary>
|
||||
public IReadOnlyList<Point> ExistingPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ROI 顶点变化时的回调,参数为最新的顶点列表。
|
||||
/// 每次用户添加/移动顶点后调用,用于实时写回参数并触发预览。
|
||||
/// </summary>
|
||||
public Action<IReadOnlyList<Point>> OnPointsChanged { get; set; }
|
||||
|
||||
/// <summary>ROI 编辑结束(用户完成或取消)时的回调。</summary>
|
||||
public Action OnEditFinished { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求停止 ROI 编辑模式(清理画布状态)。
|
||||
/// </summary>
|
||||
public sealed class CncRoiEditCancelledEvent : PubSubEvent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,10 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
SaveAsPipelineCommand = new DelegateCommand(async () => await SaveAsPipelineAsync());
|
||||
LoadPipelineCommand = new DelegateCommand(async () => await LoadPipelineAsync());
|
||||
|
||||
EditRoiCommand = new DelegateCommand(ExecuteEditRoi, () => SelectedNodeIsAdvancedModule && !IsRoiEditing);
|
||||
ClearRoiCommand = new DelegateCommand(ExecuteClearRoi, () => SelectedNodeIsAdvancedModule && SelectedNodeHasRoi);
|
||||
FinishRoiCommand = new DelegateCommand(FinishRoiEdit, () => IsRoiEditing);
|
||||
|
||||
_editorViewModel.PropertyChanged += OnEditorPropertyChanged;
|
||||
RefreshFromSelection();
|
||||
|
||||
@@ -91,6 +95,15 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
if (!SetProperty(ref _selectedNode, value))
|
||||
return;
|
||||
|
||||
// 切换节点时停止当前 ROI 编辑
|
||||
CancelRoiEdit();
|
||||
RaisePropertyChanged(nameof(SelectedNodeIsAdvancedModule));
|
||||
RaisePropertyChanged(nameof(SelectedNodeHasRoi));
|
||||
RaisePropertyChanged(nameof(SelectedNodeRoiSummary));
|
||||
// CanExecute 依赖 SelectedNode,必须手动通知
|
||||
(EditRoiCommand as DelegateCommand)?.RaiseCanExecuteChanged();
|
||||
(ClearRoiCommand as DelegateCommand)?.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,5 +636,180 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
return jsonElement.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> AdvancedModuleOperatorKeys = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"BgaVoidRate",
|
||||
"VoidMeasurement"
|
||||
};
|
||||
|
||||
// ── ROI 内联编辑 ──────────────────────────────────────────────────────
|
||||
|
||||
private bool _isRoiEditing;
|
||||
|
||||
/// <summary>当前选中节点是否为支持 ROI 的高级模块算子</summary>
|
||||
public bool SelectedNodeIsAdvancedModule =>
|
||||
SelectedNode != null && AdvancedModuleOperatorKeys.Contains(SelectedNode.OperatorKey);
|
||||
|
||||
/// <summary>当前选中节点是否已有保存的 ROI 多边形</summary>
|
||||
public bool SelectedNodeHasRoi => GetRoiPointCount(SelectedNode) >= 3;
|
||||
|
||||
/// <summary>ROI 摘要文字(如"多边形 ROI:6 个顶点")</summary>
|
||||
public string SelectedNodeRoiSummary
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = GetRoiPointCount(SelectedNode);
|
||||
if (count < 3) return "未设置 ROI(全图检测)";
|
||||
return $"多边形 ROI:{count} 个顶点";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>是否正在编辑 ROI</summary>
|
||||
public bool IsRoiEditing
|
||||
{
|
||||
get => _isRoiEditing;
|
||||
private set
|
||||
{
|
||||
if (SetProperty(ref _isRoiEditing, value))
|
||||
{
|
||||
(EditRoiCommand as DelegateCommand)?.RaiseCanExecuteChanged();
|
||||
(ClearRoiCommand as DelegateCommand)?.RaiseCanExecuteChanged();
|
||||
(FinishRoiCommand as DelegateCommand)?.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand EditRoiCommand { get; }
|
||||
public ICommand ClearRoiCommand { get; }
|
||||
public ICommand FinishRoiCommand { get; }
|
||||
|
||||
private void InitRoiCommands()
|
||||
{
|
||||
// Commands are initialized in constructor via field initializers below
|
||||
}
|
||||
|
||||
private void ExecuteEditRoi()
|
||||
{
|
||||
if (SelectedNode == null || !SelectedNodeIsAdvancedModule || _eventAggregator == null)
|
||||
return;
|
||||
|
||||
IsRoiEditing = true;
|
||||
StatusMessage = "ROI 编辑中:在图像上点击添加顶点,完成后点击「完成 ROI」";
|
||||
|
||||
var existingPoints = ReadRoiPoints(SelectedNode);
|
||||
|
||||
_eventAggregator.GetEvent<CncRoiEditRequestedEvent>().Publish(new CncRoiEditRequestedPayload
|
||||
{
|
||||
ExistingPoints = existingPoints,
|
||||
OnPointsChanged = points => OnRoiPointsChanged(SelectedNode, points),
|
||||
OnEditFinished = FinishRoiEdit
|
||||
});
|
||||
}
|
||||
|
||||
private void ExecuteClearRoi()
|
||||
{
|
||||
if (SelectedNode == null || !SelectedNodeIsAdvancedModule) return;
|
||||
|
||||
WriteRoiPoints(SelectedNode, null);
|
||||
CancelRoiEdit();
|
||||
PersistActiveModule("已清除 ROI,将使用全图检测");
|
||||
RaiseRoiProperties();
|
||||
}
|
||||
|
||||
private void OnRoiPointsChanged(PipelineNodeViewModel node, IReadOnlyList<System.Windows.Point> points)
|
||||
{
|
||||
if (node == null) return;
|
||||
WriteRoiPoints(node, points);
|
||||
PersistActiveModule($"ROI 已更新:{points?.Count ?? 0} 个顶点");
|
||||
RaiseRoiProperties();
|
||||
TriggerDebouncedPreview();
|
||||
}
|
||||
|
||||
private void FinishRoiEdit()
|
||||
{
|
||||
IsRoiEditing = false;
|
||||
StatusMessage = HasActiveModule
|
||||
? $"正在编辑检测模块:{_activeModuleNode?.Name}"
|
||||
: "请选择检测模块以编辑其流水线。";
|
||||
RaiseRoiProperties();
|
||||
}
|
||||
|
||||
private void CancelRoiEdit()
|
||||
{
|
||||
if (!_isRoiEditing) return;
|
||||
IsRoiEditing = false;
|
||||
_eventAggregator?.GetEvent<CncRoiEditCancelledEvent>().Publish();
|
||||
}
|
||||
|
||||
private void RaiseRoiProperties()
|
||||
{
|
||||
RaisePropertyChanged(nameof(SelectedNodeHasRoi));
|
||||
RaisePropertyChanged(nameof(SelectedNodeRoiSummary));
|
||||
}
|
||||
|
||||
// ── ROI 参数读写 ──────────────────────────────────────────────────────
|
||||
|
||||
private static int GetRoiPointCount(PipelineNodeViewModel node)
|
||||
{
|
||||
if (node == null) return 0;
|
||||
|
||||
// BgaVoidRate 用 RoiMode + PolyCount
|
||||
var roiModeParam = node.Parameters.FirstOrDefault(p => p.Name == "RoiMode");
|
||||
if (roiModeParam != null)
|
||||
{
|
||||
if (!string.Equals(roiModeParam.Value?.ToString(), "Polygon", StringComparison.OrdinalIgnoreCase))
|
||||
return 0;
|
||||
}
|
||||
|
||||
var polyCountParam = node.Parameters.FirstOrDefault(p => p.Name == "PolyCount");
|
||||
if (polyCountParam == null) return 0;
|
||||
return Convert.ToInt32(polyCountParam.Value);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<System.Windows.Point> ReadRoiPoints(PipelineNodeViewModel node)
|
||||
{
|
||||
int count = GetRoiPointCount(node);
|
||||
if (count < 3) return Array.Empty<System.Windows.Point>();
|
||||
|
||||
var points = new List<System.Windows.Point>(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var px = node.Parameters.FirstOrDefault(p => p.Name == $"PolyX{i}");
|
||||
var py = node.Parameters.FirstOrDefault(p => p.Name == $"PolyY{i}");
|
||||
double x = px != null ? Convert.ToDouble(px.Value) : 0;
|
||||
double y = py != null ? Convert.ToDouble(py.Value) : 0;
|
||||
points.Add(new System.Windows.Point(x, y));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
private static void WriteRoiPoints(PipelineNodeViewModel node, IReadOnlyList<System.Windows.Point> points)
|
||||
{
|
||||
if (node == null) return;
|
||||
|
||||
int count = points?.Count >= 3 ? points.Count : 0;
|
||||
|
||||
// 更新 RoiMode(BgaVoidRate 专用)
|
||||
var roiModeParam = node.Parameters.FirstOrDefault(p => p.Name == "RoiMode");
|
||||
if (roiModeParam != null)
|
||||
roiModeParam.Value = count >= 3 ? "Polygon" : "None";
|
||||
|
||||
// 更新 PolyCount
|
||||
var polyCountParam = node.Parameters.FirstOrDefault(p => p.Name == "PolyCount");
|
||||
if (polyCountParam != null)
|
||||
polyCountParam.Value = count;
|
||||
|
||||
// 更新坐标(最多 32 个点)
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
var px = node.Parameters.FirstOrDefault(p => p.Name == $"PolyX{i}");
|
||||
var py = node.Parameters.FirstOrDefault(p => p.Name == $"PolyY{i}");
|
||||
double x = (points != null && i < points.Count) ? points[i].X : 0;
|
||||
double y = (points != null && i < points.Count) ? points[i].Y : 0;
|
||||
if (px != null) px.Value = (int)x;
|
||||
if (py != null) py.Value = (int)y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace XplorePlane.Views.ImageProcessing
|
||||
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
// 获取主界面的 RoiCanvas 传给 ViewModel
|
||||
var mainWin = Owner as MainWindow;
|
||||
if (mainWin != null)
|
||||
{
|
||||
@@ -26,32 +25,23 @@ namespace XplorePlane.Views.ImageProcessing
|
||||
vm.SetCanvas(canvas);
|
||||
}
|
||||
|
||||
// 从 MainViewModel 获取 CncEditorViewModel 引用
|
||||
if (DataContext is BgaDetectionViewModel bgaVm && Owner?.DataContext is ViewModels.MainViewModel mainVm)
|
||||
{
|
||||
var cncEditorField = mainVm.GetType().GetField("_cncEditorViewModel",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (cncEditorField?.GetValue(mainVm) is CncEditorViewModel cncEditor)
|
||||
{
|
||||
bgaVm.SetCncEditorViewModel(cncEditor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Closed += (s, e) =>
|
||||
{
|
||||
if (DataContext is BgaDetectionViewModel vm)
|
||||
{
|
||||
// 恢复右键菜单,但保留 ROI
|
||||
vm.RestoreContextMenu();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void Close_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
private void Close_Click(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
private static T FindChild<T>(DependencyObject parent) where T : DependencyObject
|
||||
{
|
||||
|
||||
@@ -298,6 +298,147 @@
|
||||
FontWeight="Bold"
|
||||
Foreground="#555"
|
||||
Text="属性" />
|
||||
|
||||
<!-- ROI 编辑区(仅高级模块算子显示) -->
|
||||
<Border
|
||||
Margin="0,0,0,8"
|
||||
Padding="8,6"
|
||||
Background="#F0F7FF"
|
||||
BorderBrush="#B0D4F1"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding SelectedNodeIsAdvancedModule}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<StackPanel>
|
||||
<!-- 标题行 -->
|
||||
<DockPanel Margin="0,0,0,4">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="11"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#1A5276"
|
||||
Text="ROI 区域" />
|
||||
</DockPanel>
|
||||
|
||||
<!-- ROI 摘要 -->
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="10"
|
||||
Foreground="#555"
|
||||
Text="{Binding SelectedNodeRoiSummary}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- 编辑中提示 -->
|
||||
<Border
|
||||
Margin="0,0,0,6"
|
||||
Padding="6,4"
|
||||
Background="#FFF3CD"
|
||||
BorderBrush="#FFEAA7"
|
||||
BorderThickness="1"
|
||||
CornerRadius="3">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsRoiEditing}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<TextBlock
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="10"
|
||||
Foreground="#856404"
|
||||
Text="在图像上点击添加顶点,拖动顶点调整位置"
|
||||
TextWrapping="Wrap" />
|
||||
</Border>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<!-- 编辑 ROI 按钮(非编辑状态显示) -->
|
||||
<Button
|
||||
Height="24"
|
||||
Margin="0,0,4,0"
|
||||
Padding="8,0"
|
||||
Command="{Binding EditRoiCommand}"
|
||||
Cursor="Hand"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="11"
|
||||
ToolTip="在图像上绘制 ROI 多边形">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#2980B9" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Content" Value="✏ 编辑 ROI" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsRoiEditing}" Value="True">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<!-- 完成 ROI 按钮(编辑状态显示) -->
|
||||
<Button
|
||||
Height="24"
|
||||
Margin="0,0,4,0"
|
||||
Padding="8,0"
|
||||
Command="{Binding FinishRoiCommand}"
|
||||
Cursor="Hand"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="11"
|
||||
ToolTip="完成 ROI 编辑">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#27AE60" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Content" Value="✔ 完成 ROI" />
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsRoiEditing}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<!-- 清除 ROI 按钮 -->
|
||||
<Button
|
||||
Height="24"
|
||||
Padding="8,0"
|
||||
Command="{Binding ClearRoiCommand}"
|
||||
Cursor="Hand"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="11"
|
||||
ToolTip="清除 ROI,恢复全图检测">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#E74C3C" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Content" Value="✕ 清除" />
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding SelectedNode.Parameters}">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="ContentPresenter">
|
||||
|
||||
@@ -25,15 +25,12 @@ namespace XplorePlane.Views.ImageProcessing
|
||||
vm.SetCanvas(canvas);
|
||||
}
|
||||
|
||||
// 从 MainViewModel 获取 CncEditorViewModel 引用
|
||||
if (DataContext is VoidDetectionViewModel voidVm && Owner?.DataContext is ViewModels.MainViewModel mainVm)
|
||||
{
|
||||
var cncEditorField = mainVm.GetType().GetField("_cncEditorViewModel",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (cncEditorField?.GetValue(mainVm) is CncEditorViewModel cncEditor)
|
||||
{
|
||||
voidVm.SetCncEditorViewModel(cncEditor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
@@ -145,6 +146,17 @@ namespace XplorePlane.Views
|
||||
}
|
||||
catch { }
|
||||
|
||||
// 订阅 CNC ROI 内联编辑事件
|
||||
try
|
||||
{
|
||||
var ea2 = ContainerLocator.Current?.Resolve<Prism.Events.IEventAggregator>();
|
||||
ea2?.GetEvent<Events.CncRoiEditRequestedEvent>()
|
||||
.Subscribe(OnCncRoiEditRequested, Prism.Events.ThreadOption.UIThread);
|
||||
ea2?.GetEvent<Events.CncRoiEditCancelledEvent>()
|
||||
.Subscribe(OnCncRoiEditCancelled, Prism.Events.ThreadOption.UIThread);
|
||||
}
|
||||
catch { }
|
||||
|
||||
// 光标信息:从 RoiCanvas.CursorInfo 同步到 MainViewModel
|
||||
var cursorInfoDesc = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(
|
||||
PolygonRoiCanvas.CursorInfoProperty, typeof(PolygonRoiCanvas));
|
||||
@@ -168,6 +180,115 @@ namespace XplorePlane.Views
|
||||
// 测量模式和十字线通过 Prism 事件直接驱动,不再依赖 PropertyChanged
|
||||
}
|
||||
|
||||
#region CNC ROI 内联编辑
|
||||
|
||||
private XP.ImageProcessing.RoiControl.Models.PolygonROI _cncRoiShape;
|
||||
private Events.CncRoiEditRequestedPayload _cncRoiPayload;
|
||||
|
||||
private void OnCncRoiEditRequested(Events.CncRoiEditRequestedPayload payload)
|
||||
{
|
||||
_cncRoiPayload = payload;
|
||||
|
||||
// 清理旧的 ROI shape
|
||||
CleanupCncRoi();
|
||||
|
||||
// 确保 ROIItems 集合存在
|
||||
if (RoiCanvas.ROIItems == null)
|
||||
RoiCanvas.ROIItems = new System.Collections.ObjectModel.ObservableCollection<XP.ImageProcessing.RoiControl.Models.ROIShape>();
|
||||
|
||||
// 创建新的多边形 ROI
|
||||
_cncRoiShape = new XP.ImageProcessing.RoiControl.Models.PolygonROI
|
||||
{
|
||||
Color = "Cyan",
|
||||
IsSelected = true
|
||||
};
|
||||
|
||||
// 恢复已保存的顶点
|
||||
if (payload.ExistingPoints != null)
|
||||
{
|
||||
foreach (var pt in payload.ExistingPoints)
|
||||
_cncRoiShape.Points.Add(pt);
|
||||
}
|
||||
|
||||
RoiCanvas.ROIItems.Add(_cncRoiShape);
|
||||
RoiCanvas.SelectedROI = _cncRoiShape;
|
||||
|
||||
// 禁用右键菜单,启用画布点击添加顶点
|
||||
RoiCanvas.SetValue(System.Windows.Controls.ContextMenuService.IsEnabledProperty, false);
|
||||
RoiCanvas.AddHandler(XP.ImageProcessing.RoiControl.Controls.PolygonRoiCanvas.CanvasClickedEvent,
|
||||
new RoutedEventHandler(OnCncRoiCanvasClicked));
|
||||
|
||||
// 顶点变化时回调
|
||||
_cncRoiShape.Points.CollectionChanged += OnCncRoiPointsCollectionChanged;
|
||||
}
|
||||
|
||||
private void OnCncRoiEditCancelled()
|
||||
{
|
||||
CleanupCncRoi();
|
||||
}
|
||||
|
||||
private void OnCncRoiCanvasClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_cncRoiShape == null || _cncRoiPayload == null) return;
|
||||
if (e is XP.ImageProcessing.RoiControl.Controls.CanvasClickedEventArgs args)
|
||||
{
|
||||
InsertPointToPolygon(args.Position, _cncRoiShape.Points);
|
||||
_cncRoiShape.IsSelected = true;
|
||||
RoiCanvas.SelectedROI = _cncRoiShape;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCncRoiPointsCollectionChanged(object sender,
|
||||
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (_cncRoiPayload?.OnPointsChanged == null || _cncRoiShape == null) return;
|
||||
_cncRoiPayload.OnPointsChanged(new List<System.Windows.Point>(_cncRoiShape.Points));
|
||||
}
|
||||
|
||||
private void CleanupCncRoi()
|
||||
{
|
||||
if (_cncRoiShape != null)
|
||||
{
|
||||
_cncRoiShape.Points.CollectionChanged -= OnCncRoiPointsCollectionChanged;
|
||||
RoiCanvas.ROIItems?.Remove(_cncRoiShape);
|
||||
RoiCanvas.SelectedROI = null;
|
||||
_cncRoiShape = null;
|
||||
}
|
||||
RoiCanvas.RemoveHandler(XP.ImageProcessing.RoiControl.Controls.PolygonRoiCanvas.CanvasClickedEvent,
|
||||
new RoutedEventHandler(OnCncRoiCanvasClicked));
|
||||
RoiCanvas.SetValue(System.Windows.Controls.ContextMenuService.IsEnabledProperty, true);
|
||||
_cncRoiPayload = null;
|
||||
}
|
||||
|
||||
/// <summary>智能插入顶点:找到距离新点最近的边,将新点插入到该边的两个顶点之间</summary>
|
||||
private static void InsertPointToPolygon(System.Windows.Point newPoint,
|
||||
System.Collections.ObjectModel.ObservableCollection<System.Windows.Point> points)
|
||||
{
|
||||
if (points.Count < 2) { points.Add(newPoint); return; }
|
||||
int insertIndex = 0;
|
||||
double minDist = double.MaxValue;
|
||||
double d = PointToSegmentDist(points[points.Count - 1], points[0], newPoint);
|
||||
if (d < minDist) { minDist = d; insertIndex = 0; }
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
d = PointToSegmentDist(points[i - 1], points[i], newPoint);
|
||||
if (d < minDist) { minDist = d; insertIndex = i; }
|
||||
}
|
||||
points.Insert(insertIndex, newPoint);
|
||||
}
|
||||
|
||||
private static double PointToSegmentDist(System.Windows.Point a, System.Windows.Point b, System.Windows.Point p)
|
||||
{
|
||||
double dx = b.X - a.X, dy = b.Y - a.Y;
|
||||
double lenSq = dx * dx + dy * dy;
|
||||
if (lenSq < 1e-10) return Math.Sqrt((p.X - a.X) * (p.X - a.X) + (p.Y - a.Y) * (p.Y - a.Y));
|
||||
double t = Math.Clamp(((p.X - a.X) * dx + (p.Y - a.Y) * dy) / lenSq, 0, 1);
|
||||
double projX = a.X + t * dx, projY = a.Y + t * dy;
|
||||
return Math.Sqrt((p.X - projX) * (p.X - projX) + (p.Y - projY) * (p.Y - projY));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 右键菜单
|
||||
|
||||
private void ZoomIn_Click(object sender, RoutedEventArgs e) => RoiCanvas.ZoomScale = Math.Min(10.0, RoiCanvas.ZoomScale * 1.2);
|
||||
|
||||
Reference in New Issue
Block a user