fix: improve inspection pool grouping wizard flow

This commit is contained in:
zhengxuan.zhang
2026-08-04 15:02:16 +08:00
parent 2760bd6dac
commit fb7fac32ad
7 changed files with 71 additions and 33 deletions
@@ -18,6 +18,7 @@ namespace XplorePlane.Models.Inspection.Tasks
BGADetected,
/// <summary>Stage 3 检测池 Block 分组完成</summary>
PoolGenerated,
BubbleParameters,
/// <summary>Stage 5 首件 Block 校准完成</summary>
Calibration,
/// <summary>Stage 6 已应用校准到全部 Block,等待扫描</summary>
@@ -42,7 +43,8 @@ namespace XplorePlane.Models.Inspection.Tasks
[InspectionTaskStage.Created] = new[] { InspectionTaskStage.ImageAdjust, InspectionTaskStage.Aborted },
[InspectionTaskStage.ImageAdjust] = new[] { InspectionTaskStage.BGADetected, InspectionTaskStage.Created, InspectionTaskStage.Aborted },
[InspectionTaskStage.BGADetected] = new[] { InspectionTaskStage.PoolGenerated, InspectionTaskStage.ImageAdjust, InspectionTaskStage.Aborted },
[InspectionTaskStage.PoolGenerated] = new[] { InspectionTaskStage.Calibration, InspectionTaskStage.BGADetected, InspectionTaskStage.Aborted },
[InspectionTaskStage.PoolGenerated] = new[] { InspectionTaskStage.BubbleParameters, InspectionTaskStage.BGADetected, InspectionTaskStage.Aborted },
[InspectionTaskStage.BubbleParameters] = new[] { InspectionTaskStage.Calibration, InspectionTaskStage.PoolGenerated, InspectionTaskStage.Aborted },
[InspectionTaskStage.Calibration] = new[] { InspectionTaskStage.ReadyToScan, InspectionTaskStage.PoolGenerated, InspectionTaskStage.Aborted },
[InspectionTaskStage.ReadyToScan] = new[] { InspectionTaskStage.Scanning, InspectionTaskStage.Calibration, InspectionTaskStage.Aborted },
[InspectionTaskStage.Scanning] = new[] { InspectionTaskStage.Analysis, InspectionTaskStage.ReadyToScan, InspectionTaskStage.Aborted },
@@ -101,7 +101,7 @@ namespace XplorePlane.Services.ImageProcessing
Width = Math.Max(1, block.Width),
Height = Math.Max(1, block.Height),
Stroke = stroke,
StrokeThickness = 2,
StrokeThickness = 4,
Fill = Brushes.Transparent,
IsHitTestVisible = false
};
@@ -113,13 +113,13 @@ namespace XplorePlane.Services.ImageProcessing
{
X1 = block.CenterX - crossHalf, Y1 = block.CenterY,
X2 = block.CenterX + crossHalf, Y2 = block.CenterY,
Stroke = stroke, StrokeThickness = 1.5, IsHitTestVisible = false
Stroke = stroke, StrokeThickness = 2.5, IsHitTestVisible = false
};
var vLine = new System.Windows.Shapes.Line
{
X1 = block.CenterX, Y1 = block.CenterY - crossHalf,
X2 = block.CenterX, Y2 = block.CenterY + crossHalf,
Stroke = stroke, StrokeThickness = 1.5, IsHitTestVisible = false
Stroke = stroke, StrokeThickness = 2.5, IsHitTestVisible = false
};
canvas.Children.Add(hLine);
canvas.Children.Add(vLine);
@@ -28,9 +28,7 @@ namespace XplorePlane.Services.Inspection.Tasks
{
ArgumentNullException.ThrowIfNull(balls);
ArgumentNullException.ThrowIfNull(context);
var labels = BgaBallGroupLabeler.AssignLabels(
balls,
XplorePlane.Models.Inspection.BgaGridIndexingMode.AlphaNumericTopLeft);
var labels = BgaBallGroupLabeler.AssignLabels(balls, context.IndexingMode);
_overlay.PublishBgaBalls(balls.Select(ball => new BallOverlayItem
{
Id = ball.Id,
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using XplorePlane.Models.Inspection;
using XplorePlane.Models.Inspection.Tasks;
namespace XplorePlane.Services.Inspection.Tasks
@@ -24,7 +25,8 @@ namespace XplorePlane.Services.Inspection.Tasks
InspectionPool GeneratePool(
IReadOnlyList<BGABall> balls,
int columns, int rows, int ballsPerBlock,
int refWidth, int refHeight);
int refWidth, int refHeight,
BgaGridIndexingMode indexingMode = BgaGridIndexingMode.AlphaNumericTopLeft);
}
/// <summary>
@@ -36,7 +38,8 @@ namespace XplorePlane.Services.Inspection.Tasks
public InspectionPool GeneratePool(
IReadOnlyList<BGABall> balls,
int columns, int rows, int ballsPerBlock,
int refWidth, int refHeight)
int refWidth, int refHeight,
BgaGridIndexingMode indexingMode = BgaGridIndexingMode.AlphaNumericTopLeft)
{
if (columns <= 0) throw new ArgumentOutOfRangeException(nameof(columns));
if (rows <= 0) throw new ArgumentOutOfRangeException(nameof(rows));
@@ -72,21 +75,17 @@ namespace XplorePlane.Services.Inspection.Tasks
blockId++;
// 计算质心
double centroidX = cellBalls.Length > 0
? cellBalls.Average(b => b.X)
: (xMin + xMax) / 2.0;
double centroidY = cellBalls.Length > 0
? cellBalls.Average(b => b.Y)
: (yMin + yMax) / 2.0;
string name = $"{IndexToLetters(row)}{col + 1}";
// Block 边界必须使用网格单元中心,不能使用球心作为矩形中心,否则相邻 Block 会重叠。
double blockCenterX = (xMin + xMax) / 2.0;
double blockCenterY = (yMin + yMax) / 2.0;
string name = GetBlockName(row, col, rows, columns, indexingMode);
blocks.Add(new InspectionBlock
{
Id = blockId,
Name = name,
CenterX = centroidX,
CenterY = centroidY,
CenterX = blockCenterX,
CenterY = blockCenterY,
Width = cellWidth,
Height = cellHeight,
Balls = cellBalls,
@@ -107,11 +106,12 @@ namespace XplorePlane.Services.Inspection.Tasks
};
}
/// <summary>将行索引转换为字母前缀:0→A, 1→B, ..., 25→Z。</summary>
private static string IndexToLetters(int index)
private static string GetBlockName(
int row, int column, int rows, int columns, BgaGridIndexingMode indexingMode)
{
if (index < 0) return "?";
return ((char)('A' + (index % 26))).ToString();
var grid = new BgaGridGeometry(
rows, columns, 0, 0, Array.Empty<string>(), indexingMode);
return BgaTargetIdGenerator.Generate(grid)[row * columns + column];
}
}
}
}
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using XplorePlane.Models.Inspection;
namespace XplorePlane.Services.Inspection.Tasks
{
@@ -84,7 +85,8 @@ namespace XplorePlane.Services.Inspection.Tasks
double OriginY = 0,
double ScaleX = 1,
double ScaleY = 1,
string SourceId = "");
string SourceId = "",
BgaGridIndexingMode IndexingMode = BgaGridIndexingMode.AlphaNumericTopLeft);
/// <summary>Block 叠加层元素。</summary>
public class BlockOverlayItem
@@ -204,6 +204,14 @@ namespace XplorePlane.ViewModels.Inspection.TaskWizard
set => SetProperty(ref _ballsPerBlock, value);
}
private string _indexingMode = "AlphaNumericTopLeft";
public string IndexingMode
{
get => _indexingMode;
set => SetProperty(ref _indexingMode, value);
}
private double _sensitivity = 50.0;
public double Sensitivity
@@ -356,10 +364,11 @@ namespace XplorePlane.ViewModels.Inspection.TaskWizard
"首件校准", // Stage 5
"应用确认", // Stage 6
"自动扫描", // Stage 7
"结果输出" // Stage 8
"结果分析", // Stage 8
"结果输出" // Stage 9
};
public const int StageCount = 9;
public const int StageCount = 10;
// ── 命令 ──
@@ -466,6 +475,7 @@ namespace XplorePlane.ViewModels.Inspection.TaskWizard
InspectionTaskStage.ImageAdjust => !string.IsNullOrWhiteSpace(PipelineSummary),
InspectionTaskStage.BGADetected => DetectedBalls.Count > 0,
InspectionTaskStage.PoolGenerated => Pool != null,
InspectionTaskStage.BubbleParameters => Pool != null,
InspectionTaskStage.Calibration => TaskCalibrationResult != null,
InspectionTaskStage.ReadyToScan => true,
InspectionTaskStage.Completed => true,
@@ -482,6 +492,7 @@ namespace XplorePlane.ViewModels.Inspection.TaskWizard
InspectionTaskStage.ImageAdjust => string.IsNullOrWhiteSpace(PipelineSummary) ? "PipelineSummary=空" : "PipelineSummary=正常",
InspectionTaskStage.BGADetected => $"DetectedBalls.Count={DetectedBalls.Count}",
InspectionTaskStage.PoolGenerated => $"Pool={(Pool == null ? "null" : "")}",
InspectionTaskStage.BubbleParameters => $"Pool={(Pool == null ? "null" : "")}",
InspectionTaskStage.Calibration => $"TaskCalibrationResult={(TaskCalibrationResult == null ? "null" : "")}",
InspectionTaskStage.Scanning => "扫描中,下一步禁用",
InspectionTaskStage.Analysis => "分析中,下一步禁用",
@@ -634,7 +645,8 @@ namespace XplorePlane.ViewModels.Inspection.TaskWizard
BallCoordinateSystem.ViewportImage,
sourceImage.PixelWidth,
sourceImage.PixelHeight,
SourceId: InspectionTask.Id.ToString()));
SourceId: InspectionTask.Id.ToString(),
IndexingMode: ParseIndexingMode(IndexingMode)));
_eventAggregator.GetEvent<BgaBallsDetectedEvent>().Publish(DetectedBalls);
CurrentStage = InspectionTaskStage.BGADetected;
}
@@ -660,7 +672,7 @@ namespace XplorePlane.ViewModels.Inspection.TaskWizard
var (refWidth, refHeight) = GetReferenceImageSize();
var pool = _blockGenerator.GeneratePool(
DetectedBalls, PoolColumns, PoolRows, BallsPerBlock,
refWidth, refHeight);
refWidth, refHeight, ParseIndexingMode(IndexingMode));
Pool = pool;
InspectionTask = InspectionTask with { Pool = pool, Stage = InspectionTaskStage.PoolGenerated };
@@ -703,10 +715,11 @@ namespace XplorePlane.ViewModels.Inspection.TaskWizard
if (Pool != null)
{
InspectionTask = InspectionTask with { BubbleParameters = bp, Stage = InspectionTaskStage.Calibration };
InspectionTask = InspectionTask with { BubbleParameters = bp, Stage = InspectionTaskStage.BubbleParameters };
}
CurrentStage = InspectionTaskStage.Calibration;
// 参数设置完成后停留在当前步骤,使“下一步”进入首件校准;校准仍需显式执行。
CurrentStage = InspectionTaskStage.BubbleParameters;
_logger.LogInformation("✓ 气泡参数已应用");
}
@@ -1007,5 +1020,13 @@ namespace XplorePlane.ViewModels.Inspection.TaskWizard
return (bitmap.PixelWidth, bitmap.PixelHeight);
return (1024, 1024);
}
private static BgaGridIndexingMode ParseIndexingMode(string? value) => value switch
{
"AlphaNumericTopRight" => BgaGridIndexingMode.AlphaNumericTopRight,
"AlphaNumericColumnMajorTopLeft" => BgaGridIndexingMode.AlphaNumericColumnMajorTopLeft,
"AlphaNumericColumnMajorBottomLeft" => BgaGridIndexingMode.AlphaNumericColumnMajorBottomLeft,
_ => BgaGridIndexingMode.AlphaNumericTopLeft
};
}
}
@@ -219,6 +219,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@@ -240,6 +241,17 @@
<TextBlock Text="每Block球数" FontSize="{StaticResource NovaFontSizeBody}" Foreground="{StaticResource NovaOnSurfaceBrush}" />
<TextBox Text="{Binding BallsPerBlock}" FontSize="{StaticResource NovaFontSizeBody}" Height="{StaticResource NovaInputHeightSm}" Margin="0,4,0,0" />
</StackPanel>
<!-- 编号方向 -->
<StackPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Margin="0,0,0,16">
<TextBlock Text="编号方向" FontSize="{StaticResource NovaFontSizeBody}" Foreground="{StaticResource NovaOnSurfaceBrush}" />
<ComboBox SelectedValue="{Binding IndexingMode, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Tag" Height="{StaticResource NovaInputHeightSm}" Margin="0,4,0,0">
<ComboBoxItem Content="左上角起始(A1 → 右下角)" Tag="AlphaNumericTopLeft" />
<ComboBoxItem Content="右上角起始(A1 → 左下角)" Tag="AlphaNumericTopRight" />
<ComboBoxItem Content="左上角列优先" Tag="AlphaNumericColumnMajorTopLeft" />
<ComboBoxItem Content="左下角列优先" Tag="AlphaNumericColumnMajorBottomLeft" />
</ComboBox>
</StackPanel>
</Grid>
<Button Content="生成检测池" Command="{Binding GeneratePoolCommand}"
Height="{StaticResource NovaButtonHeightSm}" Width="120"
@@ -403,12 +415,15 @@
<DataTrigger Binding="{Binding CurrentStage}" Value="{x:Static task:InspectionTaskStage.PoolGenerated}">
<Setter Property="ContentTemplate" Value="{StaticResource Stage3Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentStage}" Value="{x:Static task:InspectionTaskStage.Calibration}">
<DataTrigger Binding="{Binding CurrentStage}" Value="{x:Static task:InspectionTaskStage.BubbleParameters}">
<Setter Property="ContentTemplate" Value="{StaticResource Stage4Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentStage}" Value="{x:Static task:InspectionTaskStage.ReadyToScan}">
<DataTrigger Binding="{Binding CurrentStage}" Value="{x:Static task:InspectionTaskStage.Calibration}">
<Setter Property="ContentTemplate" Value="{StaticResource Stage5Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentStage}" Value="{x:Static task:InspectionTaskStage.ReadyToScan}">
<Setter Property="ContentTemplate" Value="{StaticResource Stage6Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentStage}" Value="{x:Static task:InspectionTaskStage.Scanning}">
<Setter Property="ContentTemplate" Value="{StaticResource Stage7Template}" />
</DataTrigger>