集成通孔填锡率测量:四椭圆交互、中心拖拽、长短轴手柄调整、填锡率实时计算与PASS/FAIL判定

This commit is contained in:
李伟
2026-04-24 15:38:25 +08:00
parent 0b2c5da4ca
commit faa4e61d98
4 changed files with 285 additions and 4 deletions
@@ -345,6 +345,7 @@ namespace XP.ImageProcessing.RoiControl.Controls
private readonly System.Collections.Generic.List<Models.MeasureGroup> _ppGroups = new();
private readonly System.Collections.Generic.List<Models.PointToLineGroup> _ptlGroups = new();
private readonly System.Collections.Generic.List<Models.AngleGroup> _angleGroups = new();
private readonly System.Collections.Generic.List<Models.FillRateGroup> _frGroups = new();
// 点点距临时状态
private Ellipse _pendingDot;
@@ -409,7 +410,7 @@ namespace XP.ImageProcessing.RoiControl.Controls
private void RemoveMeasureOverlay()
{
if (_measureOverlay != null) { mainCanvas.Children.Remove(_measureOverlay); _measureOverlay = null; }
_ppGroups.Clear(); _ptlGroups.Clear(); _angleGroups.Clear();
_ppGroups.Clear(); _ptlGroups.Clear(); _angleGroups.Clear(); _frGroups.Clear();
_pendingDot = null; _pendingPoint = null;
_ptlTempDot1 = _ptlTempDot2 = null; _ptlTempLine = null;
_ptlTempL1 = _ptlTempL2 = null; _ptlClickCount = 0;
@@ -419,7 +420,7 @@ namespace XP.ImageProcessing.RoiControl.Controls
}
public void ClearMeasurements() => RemoveMeasureOverlay();
public int MeasureCount => _ppGroups.Count + _ptlGroups.Count + _angleGroups.Count;
public int MeasureCount => _ppGroups.Count + _ptlGroups.Count + _angleGroups.Count + _frGroups.Count;
// ── 点击分发 ──
@@ -434,6 +435,8 @@ namespace XP.ImageProcessing.RoiControl.Controls
HandlePointToLineClick(pos);
else if (CurrentMeasureMode == Models.MeasureMode.Angle)
HandleAngleClick(pos);
else if (CurrentMeasureMode == Models.MeasureMode.FillRate)
HandleFillRateClick(pos);
}
// ── 点点距 ──
@@ -595,6 +598,77 @@ namespace XP.ImageProcessing.RoiControl.Controls
return g;
}
// ── 通孔填锡率 ──
private void HandleFillRateClick(Point pos)
{
// 单击放置:自动生成4个椭圆的初始位置
double offset = 70;
var e1 = new Point(pos.X, pos.Y + offset / 2);
var e2 = new Point(pos.X + 20, pos.Y - offset / 2);
var e3 = e1; // 起点=底部
var e4 = new Point(pos.X + 10, pos.Y - offset / 4);
var g = CreateFillRateGroup(e1, e2, e3, e4);
_frGroups.Add(g);
double rate = g.FillRate;
RaiseMeasureCompleted(g.E3, g.E4, rate, MeasureCount, "FillRate");
CurrentMeasureMode = Models.MeasureMode.None;
}
private Models.FillRateGroup CreateFillRateGroup(Point e1, Point e2, Point e3, Point e4)
{
var g = new Models.FillRateGroup { E1 = e1, E2 = e2, E3 = e3, E4 = e4 };
g.PathE1 = CreateEllipsePath(Brushes.DodgerBlue, false);
g.PathE2 = CreateEllipsePath(Brushes.Cyan, false);
g.PathE3 = CreateEllipsePath(Brushes.Yellow, true);
g.PathE4 = CreateEllipsePath(Brushes.Lime, false);
g.FullLine = new Line { Stroke = Brushes.Red, StrokeThickness = 1, IsHitTestVisible = false };
g.FillLine = new Line { Stroke = Brushes.Lime, StrokeThickness = 2, IsHitTestVisible = false };
g.Label = new TextBlock { FontSize = 13, FontWeight = FontWeights.Bold, IsHitTestVisible = false };
g.DotE1 = CreateMDot(Brushes.DodgerBlue);
g.DotE2 = CreateMDot(Brushes.Cyan);
g.DotE3 = CreateMDot(Brushes.Yellow);
g.DotE4 = CreateMDot(Brushes.Lime);
// 轴手柄(小方块样式区分)
g.E1AH = CreateAxisHandle(Brushes.DodgerBlue); g.E1BH = CreateAxisHandle(Brushes.DodgerBlue);
g.E2AH = CreateAxisHandle(Brushes.Cyan); g.E2BH = CreateAxisHandle(Brushes.Cyan);
g.E3AH = CreateAxisHandle(Brushes.Yellow); g.E3BH = CreateAxisHandle(Brushes.Yellow);
g.E4AH = CreateAxisHandle(Brushes.Lime); g.E4BH = CreateAxisHandle(Brushes.Lime);
foreach (var el in g.AllElements)
_measureOverlay.Children.Add(el);
SetDotPos(g.DotE1, e1); SetDotPos(g.DotE2, e2);
SetDotPos(g.DotE3, e3); SetDotPos(g.DotE4, e4);
g.UpdateVisuals();
return g;
}
private Ellipse CreateAxisHandle(Brush fill)
{
var h = new Ellipse { Width = 8, Height = 8, Fill = fill, Stroke = Brushes.White, StrokeThickness = 1, Cursor = Cursors.SizeAll };
h.SetValue(ContextMenuService.IsEnabledProperty, false);
h.MouseLeftButtonDown += MDot_Down;
h.MouseMove += MDot_Move;
h.MouseLeftButtonUp += MDot_Up;
h.PreviewMouseRightButtonUp += MDot_RightClick;
return h;
}
private static Path CreateEllipsePath(Brush stroke, bool dashed)
{
var p = new Path { Stroke = stroke, StrokeThickness = 1.5, IsHitTestVisible = false };
if (dashed) p.StrokeDashArray = new DoubleCollection { 4, 2 };
return p;
}
// ── 共用:圆点创建、定位、拖拽、删除 ──
private Ellipse CreateMDot(Brush fill)
@@ -643,6 +717,25 @@ namespace XP.ImageProcessing.RoiControl.Controls
if (g.DotB == dot) { _mDraggingOwner = g; _mDraggingRole = "DotB"; break; }
}
}
// 查找填锡率组
if (_mDraggingOwner == null)
{
foreach (var g in _frGroups)
{
if (g.DotE1 == dot) { _mDraggingOwner = g; _mDraggingRole = "E1"; break; }
if (g.DotE2 == dot) { _mDraggingOwner = g; _mDraggingRole = "E2"; break; }
if (g.DotE3 == dot) { _mDraggingOwner = g; _mDraggingRole = "E3"; break; }
if (g.DotE4 == dot) { _mDraggingOwner = g; _mDraggingRole = "E4"; break; }
if (g.E1AH == dot) { _mDraggingOwner = g; _mDraggingRole = "E1A"; break; }
if (g.E1BH == dot) { _mDraggingOwner = g; _mDraggingRole = "E1B"; break; }
if (g.E2AH == dot) { _mDraggingOwner = g; _mDraggingRole = "E2A"; break; }
if (g.E2BH == dot) { _mDraggingOwner = g; _mDraggingRole = "E2B"; break; }
if (g.E3AH == dot) { _mDraggingOwner = g; _mDraggingRole = "E3A"; break; }
if (g.E3BH == dot) { _mDraggingOwner = g; _mDraggingRole = "E3B"; break; }
if (g.E4AH == dot) { _mDraggingOwner = g; _mDraggingRole = "E4A"; break; }
if (g.E4BH == dot) { _mDraggingOwner = g; _mDraggingRole = "E4B"; break; }
}
}
if (_mDraggingOwner != null) { _mDraggingDot = dot; dot.CaptureMouse(); e.Handled = true; }
}
@@ -676,6 +769,23 @@ namespace XP.ImageProcessing.RoiControl.Controls
ag.UpdateVisuals();
RaiseMeasureCompleted(ag.V, ag.B, ag.AngleDeg, MeasureCount, "Angle");
}
else if (_mDraggingOwner is Models.FillRateGroup frg)
{
if (_mDraggingRole == "E1") frg.E1 = pos;
else if (_mDraggingRole == "E2") frg.E2 = pos;
else if (_mDraggingRole == "E3") frg.E3 = pos;
else if (_mDraggingRole == "E4") frg.E4 = pos;
else if (_mDraggingRole == "E1A") frg.E1A = Math.Max(10, Models.FillRateGroup.Dist(pos, frg.E1));
else if (_mDraggingRole == "E1B") frg.E1B = Math.Max(10, Models.FillRateGroup.Dist(pos, frg.E1));
else if (_mDraggingRole == "E2A") frg.E2A = Math.Max(10, Models.FillRateGroup.Dist(pos, frg.E2));
else if (_mDraggingRole == "E2B") frg.E2B = Math.Max(10, Models.FillRateGroup.Dist(pos, frg.E2));
else if (_mDraggingRole == "E3A") frg.E3A = Math.Max(10, Models.FillRateGroup.Dist(pos, frg.E3));
else if (_mDraggingRole == "E3B") frg.E3B = Math.Max(10, Models.FillRateGroup.Dist(pos, frg.E3));
else if (_mDraggingRole == "E4A") frg.E4A = Math.Max(10, Models.FillRateGroup.Dist(pos, frg.E4));
else if (_mDraggingRole == "E4B") frg.E4B = Math.Max(10, Models.FillRateGroup.Dist(pos, frg.E4));
frg.UpdateVisuals();
RaiseMeasureCompleted(frg.E3, frg.E4, frg.FillRate, MeasureCount, "FillRate");
}
e.Handled = true;
}
@@ -724,6 +834,21 @@ namespace XP.ImageProcessing.RoiControl.Controls
e.Handled = true; return;
}
}
// 填锡率删除
foreach (var g in _frGroups)
{
bool match = g.DotE1 == dot || g.DotE2 == dot || g.DotE3 == dot || g.DotE4 == dot
|| g.E1AH == dot || g.E1BH == dot || g.E2AH == dot || g.E2BH == dot
|| g.E3AH == dot || g.E3BH == dot || g.E4AH == dot || g.E4BH == dot;
if (match)
{
foreach (var el in g.AllElements)
_measureOverlay.Children.Remove(el);
_frGroups.Remove(g);
RaiseMeasureStatusChanged($"已删除测量 | 剩余 {MeasureCount} 条");
e.Handled = true; return;
}
}
}
// ── 事件 ──