Merge remote-tracking branch 'origin/Develop/XP' into turbo-002-cnc

# Conflicts:
#	XplorePlane/ViewModels/Main/MainViewModel.cs
#	XplorePlane/Views/Main/MainWindow.xaml
This commit is contained in:
zhengxuan.zhang
2026-04-28 12:47:47 +08:00
18 changed files with 2070 additions and 257 deletions
+18 -33
View File
@@ -282,7 +282,8 @@ namespace XplorePlane
// 主窗口加载完成后再连接相机,确保所有模块和原生 DLL 已完成初始化
shell.Loaded += (s, e) =>
{
TryConnectCamera();
// [DEV] 导航相机连接已屏蔽,开发阶段跳过以加快启动速度
// TryConnectCamera();
// 初始化主界面探测器帧流水线,开始接收探测器图像事件
try
@@ -294,38 +295,21 @@ namespace XplorePlane
Log.Error(ex, "初始化主界面探测器帧流水线失败");
}
// 通知 ViewModel 相机状态已确定,启动实时预览或显示错误
try
{
var cameraVm = Container.Resolve<NavigationPropertyPanelViewModel>();
cameraVm.OnCameraReady();
}
catch (Exception ex)
{
Log.Error(ex, "通知相机 ViewModel 失败");
}
// [DEV] 相机状态通知已屏蔽
// try
// {
// var cameraVm = Container.Resolve<NavigationPropertyPanelViewModel>();
// cameraVm.OnCameraReady();
// }
// catch (Exception ex)
// {
// Log.Error(ex, "通知相机 ViewModel 失败");
// }
if (_cameraError != null)
{
HexMessageBox.Show(_cameraError, MessageBoxButton.OK, MessageBoxImage.Error);
}
//TryConnectCamera();
//// 通知 ViewModel 相机状态已确定,启动实时预览或显示错误
// try
// {
// var cameraVm = Container.Resolve<NavigationPropertyPanelViewModel>();
// cameraVm.OnCameraReady();
// }
// catch (Exception ex)
// {
// Log.Error(ex, "通知相机 ViewModel 失败");
// }
// if (_cameraError != null)
// {
// HexMessageBox.Show(_cameraError, MessageBoxButton.OK, MessageBoxImage.Error);
//}
// if (_cameraError != null)
// {
// HexMessageBox.Show(_cameraError, MessageBoxButton.OK, MessageBoxImage.Error);
// }
};
return shell;
@@ -378,7 +362,8 @@ namespace XplorePlane
// 注册视图和视图模型
containerRegistry.RegisterForNavigation<MainWindow>();
containerRegistry.Register<MainViewModel>();
containerRegistry.RegisterSingleton<MainViewModel>();
containerRegistry.RegisterSingleton<ViewportPanelViewModel>();
containerRegistry.RegisterSingleton<NavigationPropertyPanelViewModel>();
// 注册图像处理服务与视图
+3 -1
View File
@@ -11,7 +11,9 @@ namespace XplorePlane.Events
PointDistance,
PointLineDistance,
Angle,
ThroughHoleFillRate
ThroughHoleFillRate,
BgaVoid,
BubbleMeasure
}
/// <summary>
@@ -15,6 +15,7 @@ using XplorePlane.ViewModels.Cnc;
using XplorePlane.Views;
using XplorePlane.Views.Cnc;
using XP.Common.Logging.Interfaces;
using XP.Common.GeneralForm.Views;
using XP.Common.PdfViewer.Interfaces;
using XP.Hardware.MotionControl.Abstractions;
@@ -31,6 +32,113 @@ namespace XplorePlane.ViewModels
private readonly CncEditorViewModel _cncEditorViewModel;
private readonly CncPageView _cncPageView;
public string LicenseInfo
{
get => _licenseInfo;
set => SetProperty(ref _licenseInfo, value);
}
private string _statusMessage = "就绪";
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
public ObservableCollection<object> NavigationTree { get; set; }
// 导航命令
public DelegateCommand NavigateHomeCommand { get; set; }
public DelegateCommand NavigateInspectCommand { get; set; }
public DelegateCommand OpenFileCommand { get; set; }
public DelegateCommand ExportCommand { get; set; }
public DelegateCommand ClearCommand { get; set; }
public DelegateCommand EditPropertiesCommand { get; set; }
// 窗口打开命令
public DelegateCommand OpenImageProcessingCommand { get; }
public DelegateCommand LoadImageCommand { get; }
public DelegateCommand OpenPipelineEditorCommand { get; }
public DelegateCommand OpenCncEditorCommand { get; }
public DelegateCommand OpenMatrixEditorCommand { get; }
public DelegateCommand OpenToolboxCommand { get; }
public DelegateCommand OpenLibraryVersionsCommand { get; }
public DelegateCommand OpenUserManualCommand { get; }
public DelegateCommand OpenCameraSettingsCommand { get; }
public DelegateCommand NewCncProgramCommand { get; }
public DelegateCommand SaveCncProgramCommand { get; }
public DelegateCommand LoadCncProgramCommand { get; }
public DelegateCommand InsertReferencePointCommand { get; }
public DelegateCommand InsertSavePositionCommand { get; }
public DelegateCommand InsertCompleteProgramCommand { get; }
public DelegateCommand InsertInspectionMarkerCommand { get; }
public DelegateCommand InsertInspectionModuleCommand { get; }
public DelegateCommand InsertSaveNodeCommand { get; }
public DelegateCommand InsertPauseDialogCommand { get; }
public DelegateCommand InsertWaitDelayCommand { get; }
// 硬件命令
public DelegateCommand AxisResetCommand { get; }
public DelegateCommand OpenDetectorConfigCommand { get; }
public DelegateCommand OpenMotionDebugCommand { get; }
public DelegateCommand OpenPlcAddrConfigCommand { get; }
public DelegateCommand OpenRaySourceConfigCommand { get; }
public DelegateCommand WarmUpCommand { get; }
// 测量命令
public DelegateCommand PointDistanceMeasureCommand { get; }
public DelegateCommand PointLineDistanceMeasureCommand { get; }
public DelegateCommand AngleMeasureCommand { get; }
public DelegateCommand ThroughHoleFillRateMeasureCommand { get; }
public DelegateCommand BgaVoidMeasureCommand { get; }
public DelegateCommand BubbleMeasureCommand { get; }
// 辅助线命令
public DelegateCommand ToggleCrosshairCommand { get; }
// 设置命令
public DelegateCommand OpenLanguageSwitcherCommand { get; }
public DelegateCommand OpenRealTimeLogViewerCommand { get; }
public DelegateCommand UseLiveDetectorSourceCommand { get; }
public bool IsMainViewportRealtimeEnabled
{
get => _mainViewportService.IsRealtimeDisplayEnabled;
set
{
if (_mainViewportService.IsRealtimeDisplayEnabled == value)
return;
_mainViewportService.SetRealtimeDisplayEnabled(value);
RaisePropertyChanged();
}
}
public bool IsUsingLiveDetectorSource => _mainViewportService.CurrentSourceMode == MainViewportSourceMode.LiveDetector;
/// <summary>右侧图像区域内容 | Right-side image panel content</summary>
public object ImagePanelContent
{
get => _imagePanelContent;
set => SetProperty(ref _imagePanelContent, value);
}
/// <summary>右侧图像区域宽度 | Right-side image panel width</summary>
public GridLength ImagePanelWidth
{
get => _imagePanelWidth;
set => SetProperty(ref _imagePanelWidth, value);
}
/// <summary>主视图区宽度 | Main viewport width</summary>
public GridLength ViewportPanelWidth
{
get => _viewportPanelWidth;
set => SetProperty(ref _viewportPanelWidth, value);
}
// 窗口引用(单例窗口防止重复打开)
private Window _motionDebugWindow;
private Window _detectorConfigWindow;
private Window _plcAddrConfigWindow;
@@ -115,6 +223,11 @@ namespace XplorePlane.ViewModels
PointLineDistanceMeasureCommand = new DelegateCommand(ExecutePointLineDistanceMeasure);
AngleMeasureCommand = new DelegateCommand(ExecuteAngleMeasure);
ThroughHoleFillRateMeasureCommand = new DelegateCommand(ExecuteThroughHoleFillRateMeasure);
BgaVoidMeasureCommand = new DelegateCommand(ExecuteBgaVoidMeasure);
BubbleMeasureCommand = new DelegateCommand(ExecuteBubbleMeasure);
// 辅助线命令
ToggleCrosshairCommand = new DelegateCommand(() =>
_eventAggregator.GetEvent<ToggleCrosshairEvent>().Publish());
@@ -471,30 +584,104 @@ namespace XplorePlane.ViewModels
}
}
#region
private bool CheckImageLoaded()
{
try
{
var viewportVm = _containerProvider.Resolve<ViewportPanelViewModel>();
if (viewportVm?.ImageSource != null) return true;
}
catch { }
HexMessageBox.Show("请先加载图像", MessageBoxButton.OK, MessageBoxImage.Information);
return false;
}
private void ExecutePointDistanceMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("点点距测量功能已触发");
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.PointDistance);
}
private void ExecutePointLineDistanceMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("点线距测量功能已触发");
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.PointLineDistance);
}
private void ExecuteAngleMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("角度测量功能已触发");
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.Angle);
}
private void ExecuteThroughHoleFillRateMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("通孔填锡率测量功能已触发");
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.ThroughHoleFillRate);
}
private Window _bgaMeasurePanel;
private void ExecuteBgaVoidMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("BGA空隙测量功能已触发");
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.BgaVoid);
if (_bgaMeasurePanel != null && _bgaMeasurePanel.IsVisible)
{
_bgaMeasurePanel.Activate();
return;
}
_bgaMeasurePanel = new Views.ImageProcessing.BgaMeasurePanel
{
Owner = System.Windows.Application.Current.MainWindow
};
_bgaMeasurePanel.Closed += (s, e) =>
{
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.None);
};
_bgaMeasurePanel.Show();
}
private Window _bubbleMeasurePanel;
private void ExecuteBubbleMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("气泡测量功能已触发");
// 进入气泡测量模式
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.BubbleMeasure);
// 弹出工具面板
if (_bubbleMeasurePanel != null && _bubbleMeasurePanel.IsVisible)
{
_bubbleMeasurePanel.Activate();
return;
}
_bubbleMeasurePanel = new Views.ImageProcessing.BubbleMeasurePanel
{
Owner = System.Windows.Application.Current.MainWindow
};
_bubbleMeasurePanel.Closed += (s, e) =>
{
// 关闭面板时退出气泡测量模式
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.None);
};
_bubbleMeasurePanel.Show();
}
private void ExecuteOpenLanguageSwitcher()
{
try
@@ -0,0 +1,74 @@
<Window
x:Class="XplorePlane.Views.ImageProcessing.BgaMeasurePanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BGA空隙测量"
Width="240" Height="260"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
Topmost="True"
ShowInTaskbar="False">
<Window.Resources>
<Style x:Key="ToolToggleStyle" TargetType="RadioButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border x:Name="Bd" Background="#EEEEEE" BorderBrush="#CCCCCC"
BorderThickness="1" CornerRadius="3" Padding="14,5" Cursor="Hand">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#0078D7" />
<Setter TargetName="Bd" Property="BorderBrush" Value="#005A9E" />
<Setter Property="Foreground" Value="White" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#DDDDDD" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsChecked" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Bd" Property="Background" Value="#006CBE" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel Margin="10">
<!-- 绘制模式 -->
<TextBlock Text="绘制模式" FontWeight="SemiBold" Margin="0,0,0,4" />
<WrapPanel>
<RadioButton x:Name="RbVoid" Content="画气泡" IsChecked="True" Margin="0,0,6,4"
Style="{StaticResource ToolToggleStyle}" />
<RadioButton x:Name="RbBall" Content="画焊球" Margin="0,0,0,4"
Style="{StaticResource ToolToggleStyle}" />
</WrapPanel>
<TextBlock Text="左键点两次画圆(圆心+半径),右键删除" FontSize="10" Foreground="Gray" Margin="0,2,0,0" />
<Separator Margin="0,8" />
<!-- VoidLimit -->
<TextBlock Text="VoidLimit(%)" Margin="0,0,0,4" />
<DockPanel>
<TextBox x:Name="TbVoidLimit" DockPanel.Dock="Right" Width="50" Text="25.0"
VerticalContentAlignment="Center" Margin="6,0,0,0" />
<Slider x:Name="SliderVoidLimit" Minimum="0" Maximum="100" Value="25"
VerticalAlignment="Center" />
</DockPanel>
<Separator Margin="0,8" />
<!-- 结果 -->
<TextBlock x:Name="TbResult" Text="空隙率: --" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,8" />
<!-- 操作 -->
<WrapPanel HorizontalAlignment="Center">
<Button Content="完成" Padding="14,4" Click="Finish_Click" />
</WrapPanel>
</StackPanel>
</Window>
@@ -0,0 +1,74 @@
using System.Windows;
using XP.ImageProcessing.RoiControl.Controls;
namespace XplorePlane.Views.ImageProcessing
{
public partial class BgaMeasurePanel : Window
{
private PolygonRoiCanvas _canvas;
public BgaMeasurePanel()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
try
{
var mainWin = Owner as MainWindow;
if (mainWin != null)
_canvas = FindChild<PolygonRoiCanvas>(mainWin);
}
catch { }
// 模式切换:通知 canvas 切换气泡/焊球
RbVoid.Checked += (s, ev) =>
{
if (_canvas != null) _canvas.SetBgaDrawBall(false);
};
RbBall.Checked += (s, ev) =>
{
if (_canvas != null) _canvas.SetBgaDrawBall(true);
};
// VoidLimit 同步
SliderVoidLimit.ValueChanged += (s, ev) =>
{
TbVoidLimit.Text = SliderVoidLimit.Value.ToString("F1");
_canvas?.SetBgaVoidLimit(SliderVoidLimit.Value);
};
// 监听测量完成事件更新结果
if (_canvas != null)
{
_canvas.MeasureCompleted += (s, ev) =>
{
if (ev is MeasureCompletedEventArgs args && args.MeasureType == "BgaVoid")
{
TbResult.Text = $"空隙率: {args.Distance:F1}%";
}
};
}
}
private void Finish_Click(object sender, RoutedEventArgs e)
{
Close();
}
private static T FindChild<T>(DependencyObject parent) where T : DependencyObject
{
int count = System.Windows.Media.VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
var child = System.Windows.Media.VisualTreeHelper.GetChild(parent, i);
if (child is T t) return t;
var result = FindChild<T>(child);
if (result != null) return result;
}
return null;
}
}
}
@@ -0,0 +1,94 @@
<Window
x:Class="XplorePlane.Views.ImageProcessing.BubbleMeasurePanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="气泡测量工具"
Width="260" Height="340"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
Topmost="True"
ShowInTaskbar="False">
<Window.Resources>
<Style x:Key="ToolToggleStyle" TargetType="RadioButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border x:Name="Bd" Background="#EEEEEE" BorderBrush="#CCCCCC"
BorderThickness="1" CornerRadius="3" Padding="10,4" Cursor="Hand">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#0078D7" />
<Setter TargetName="Bd" Property="BorderBrush" Value="#005A9E" />
<Setter Property="Foreground" Value="White" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#DDDDDD" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsChecked" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Bd" Property="Background" Value="#006CBE" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel Margin="10">
<!-- 工具选择 -->
<TextBlock Text="工具" FontWeight="SemiBold" Margin="0,0,0,4" />
<WrapPanel>
<RadioButton x:Name="RbRoi" Content="ROI" IsChecked="True" Margin="0,0,4,4"
Style="{StaticResource ToolToggleStyle}" />
<RadioButton x:Name="RbWand" Content="魔棒" Margin="0,0,4,4"
Style="{StaticResource ToolToggleStyle}" />
<RadioButton x:Name="RbBrush" Content="画笔" Margin="0,0,4,4"
Style="{StaticResource ToolToggleStyle}" />
<RadioButton x:Name="RbEraser" Content="橡皮擦" Margin="0,0,0,4"
Style="{StaticResource ToolToggleStyle}" />
</WrapPanel>
<Separator Margin="0,6" />
<!-- 阈值 -->
<TextBlock Text="灰度阈值(与点击点的最大灰度差)" Margin="0,0,0,4" FontSize="11" Foreground="Gray" />
<DockPanel>
<TextBox x:Name="TbThreshold" DockPanel.Dock="Right" Width="45" Text="128"
VerticalContentAlignment="Center" Margin="6,0,0,0" />
<Slider x:Name="SliderThreshold" Minimum="0" Maximum="255" Value="128"
VerticalAlignment="Center" />
</DockPanel>
<!-- 画笔大小 -->
<TextBlock Text="画笔/橡皮大小" Margin="0,8,0,4" />
<DockPanel>
<TextBox x:Name="TbBrushSize" DockPanel.Dock="Right" Width="45" Text="5"
VerticalContentAlignment="Center" Margin="6,0,0,0" />
<Slider x:Name="SliderBrushSize" Minimum="1" Maximum="30" Value="5"
VerticalAlignment="Center" />
</DockPanel>
<Separator Margin="0,8" />
<!-- 结果 -->
<TextBlock Text="测量结果" FontWeight="SemiBold" Margin="0,0,0,4" />
<TextBlock x:Name="TbResult" Text="空隙率: --" FontSize="14" Margin="0,0,0,4" />
<DockPanel Margin="0,0,0,8">
<TextBlock Text="VoidLimit(%):" VerticalAlignment="Center" />
<TextBox x:Name="TbVoidLimit" Width="50" Text="25.0" Margin="6,0,0,0"
VerticalContentAlignment="Center" />
</DockPanel>
<!-- 操作按钮 -->
<WrapPanel HorizontalAlignment="Center">
<Button Content="撤销" Padding="12,4" Margin="0,0,8,0" Click="Undo_Click" />
<Button Content="清除标记" Padding="12,4" Margin="0,0,8,0" Click="ClearMask_Click" />
<Button Content="完成" Padding="12,4" Click="Finish_Click" />
</WrapPanel>
</StackPanel>
</Window>
@@ -0,0 +1,98 @@
using System.Windows;
using Prism.Ioc;
using XP.ImageProcessing.RoiControl.Controls;
using XplorePlane.ViewModels;
namespace XplorePlane.Views.ImageProcessing
{
public partial class BubbleMeasurePanel : Window
{
private PolygonRoiCanvas _canvas;
public BubbleMeasurePanel()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// 获取主界面的 RoiCanvas
try
{
var mainWin = Owner as MainWindow;
if (mainWin != null)
{
_canvas = FindChild<PolygonRoiCanvas>(mainWin);
}
}
catch { }
// 工具切换
RbRoi.Checked += (s, ev) => _canvas?.SetBubbleTool(PolygonRoiCanvas.BubbleSubTool.Roi);
RbWand.Checked += (s, ev) => _canvas?.SetBubbleTool(PolygonRoiCanvas.BubbleSubTool.Wand);
RbBrush.Checked += (s, ev) => _canvas?.SetBubbleTool(PolygonRoiCanvas.BubbleSubTool.Brush);
RbEraser.Checked += (s, ev) => _canvas?.SetBubbleTool(PolygonRoiCanvas.BubbleSubTool.Eraser);
// 阈值同步
SliderThreshold.ValueChanged += (s, ev) =>
{
TbThreshold.Text = ((int)SliderThreshold.Value).ToString();
_canvas?.SetBubbleThreshold((int)SliderThreshold.Value);
};
// 画笔大小同步
SliderBrushSize.ValueChanged += (s, ev) =>
{
TbBrushSize.Text = ((int)SliderBrushSize.Value).ToString();
_canvas?.SetBubbleBrushSize((int)SliderBrushSize.Value);
};
// 监听 canvas 的工具切换事件(ROI 画完后自动切换时同步面板)
if (_canvas != null)
{
_canvas.BubbleToolChanged += (s, ev) =>
{
// 同步面板 radio button
};
_canvas.MeasureCompleted += (s, ev) =>
{
if (ev is MeasureCompletedEventArgs args && args.MeasureType == "BubbleVoid")
{
TbResult.Text = $"空隙率: {args.Distance:F1}%";
}
};
}
}
private void ClearMask_Click(object sender, RoutedEventArgs e)
{
_canvas?.ClearBubbleMeasure();
TbResult.Text = "空隙率: --";
}
private void Undo_Click(object sender, RoutedEventArgs e)
{
_canvas?.UndoBubble();
}
private void Finish_Click(object sender, RoutedEventArgs e)
{
Close();
}
private static T FindChild<T>(DependencyObject parent) where T : DependencyObject
{
int count = System.Windows.Media.VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
var child = System.Windows.Media.VisualTreeHelper.GetChild(parent, i);
if (child is T t) return t;
var result = FindChild<T>(child);
if (result != null) return result;
}
return null;
}
}
}
+18
View File
@@ -237,6 +237,24 @@
SmallImage="/Assets/Icons/pores.png"
Text="通孔填锡率" />
</StackPanel>
<!-- 第三列: BGA空隙测量 + 气泡测量 -->
<StackPanel>
<telerik:RadRibbonButton
telerik:ScreenTip.Description="BGA焊球空隙率检测"
telerik:ScreenTip.Title="BGA空隙测量"
Command="{Binding BgaVoidMeasureCommand}"
Size="Medium"
SmallImage="/Assets/Icons/bga.png"
Text="BGA空隙" />
<telerik:RadRibbonButton
telerik:ScreenTip.Description="手动气泡测量(魔棒+画笔)"
telerik:ScreenTip.Title="气泡测量"
Command="{Binding BubbleMeasureCommand}"
Size="Medium"
SmallImage="/Assets/Icons/pores.png"
Text="气泡测量" />
</StackPanel>
</telerik:RadRibbonGroup>
<telerik:RadRibbonGroup Header="图像算子" telerik:ScreenTip.Title="图像算子">
@@ -15,25 +15,51 @@ namespace XplorePlane.Views
{
public partial class ViewportPanelView : UserControl
{
private MainViewModel _mainVm;
private void SetStatus(string msg)
{
if (_mainVm == null)
{
try { _mainVm = ContainerLocator.Current?.Resolve<MainViewModel>(); } catch { }
}
if (_mainVm != null) _mainVm.StatusMessage = msg;
}
public ViewportPanelView()
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
// 测量事件 → 更新状态栏
// 测量事件 → 更新主界面状态栏
RoiCanvas.MeasureCompleted += (s, e) =>
{
if (e is MeasureCompletedEventArgs args && DataContext is ViewportPanelViewModel vm)
if (e is MeasureCompletedEventArgs args)
{
vm.MeasurementResult = $"{args.Distance:F2} px";
string typeLabel = args.MeasureType == "PointToLine" ? "点线距" : "点点距";
vm.ImageInfo = $"{typeLabel}: {args.Distance:F2} px | ({args.P1.X:F0},{args.P1.Y:F0}) → ({args.P2.X:F0},{args.P2.Y:F0}) | 共 {args.TotalCount} 条测量";
string typeLabel = args.MeasureType switch
{
"PointToLine" => "点线距",
"Angle" => "角度",
"FillRate" => "填锡率",
"BgaVoid" => "BGA空隙",
"BubbleVoid" => "气泡空隙",
_ => "点点距"
};
string valueText = args.MeasureType switch
{
"Angle" => $"{args.Distance:F2}°",
"FillRate" => $"{args.Distance:F1}%",
"BgaVoid" => $"{args.Distance:F1}%",
"BubbleVoid" => $"{args.Distance:F1}%",
_ => $"{args.Distance:F2} px"
};
SetStatus($"{typeLabel}: {valueText} | 共 {args.TotalCount} 条测量");
}
};
RoiCanvas.MeasureStatusChanged += (s, e) =>
{
if (e is MeasureStatusEventArgs args && DataContext is ViewportPanelViewModel vm)
vm.ImageInfo = args.Message;
if (e is MeasureStatusEventArgs args)
SetStatus(args.Message);
};
// 十字辅助线:直接订阅 Prism 事件
@@ -52,6 +78,10 @@ namespace XplorePlane.Views
{
MeasurementToolMode.PointDistance => XP.ImageProcessing.RoiControl.Models.MeasureMode.PointDistance,
MeasurementToolMode.PointLineDistance => XP.ImageProcessing.RoiControl.Models.MeasureMode.PointToLine,
MeasurementToolMode.Angle => XP.ImageProcessing.RoiControl.Models.MeasureMode.Angle,
MeasurementToolMode.ThroughHoleFillRate => XP.ImageProcessing.RoiControl.Models.MeasureMode.FillRate,
MeasurementToolMode.BgaVoid => XP.ImageProcessing.RoiControl.Models.MeasureMode.BgaVoid,
MeasurementToolMode.BubbleMeasure => XP.ImageProcessing.RoiControl.Models.MeasureMode.BubbleMeasure,
_ => XP.ImageProcessing.RoiControl.Models.MeasureMode.None
};
}, Prism.Events.ThreadOption.UIThread);
@@ -82,10 +112,8 @@ namespace XplorePlane.Views
{
RoiCanvas.ClearMeasurements();
if (DataContext is ViewportPanelViewModel vm)
{
vm.ResetMeasurementState();
vm.ImageInfo = "已清除所有测量";
}
SetStatus("已清除所有测量");
}
private void SaveOriginalImage_Click(object sender, RoutedEventArgs e)