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:
+18
-33
@@ -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>();
|
||||
|
||||
// 注册图像处理服务与视图
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user