合并 Feature/TURBO-586-FillRate 分支:新增BGA空隙测量和气泡测量功能
This commit is contained in:
@@ -41,178 +41,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Emgu.CV.runtime.windows/4.10.0.5680": {
|
|
||||||
"dependencies": {
|
|
||||||
"Emgu.CV": "4.10.0.5680",
|
|
||||||
"Emgu.runtime.windows.msvc.rt.arm64": "19.42.34435",
|
|
||||||
"Emgu.runtime.windows.msvc.rt.x64": "19.42.34435",
|
|
||||||
"Emgu.runtime.windows.msvc.rt.x86": "19.42.34435"
|
|
||||||
},
|
|
||||||
"runtimeTargets": {
|
|
||||||
"runtimes/win-arm64/native/cvextern.dll": {
|
|
||||||
"rid": "win-arm64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "4.10.0.5680"
|
|
||||||
},
|
|
||||||
"runtimes/win-x64/native/cvextern.dll": {
|
|
||||||
"rid": "win-x64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "4.10.0.5680"
|
|
||||||
},
|
|
||||||
"runtimes/win-x64/native/libusb-1.0.dll": {
|
|
||||||
"rid": "win-x64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "0.0.0.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x64/native/opencv_videoio_ffmpeg4100_64.dll": {
|
|
||||||
"rid": "win-x64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "2024.5.0.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x86/native/cvextern.dll": {
|
|
||||||
"rid": "win-x86",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "4.10.0.5680"
|
|
||||||
},
|
|
||||||
"runtimes/win-x86/native/libusb-1.0.dll": {
|
|
||||||
"rid": "win-x86",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "0.0.0.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x86/native/opencv_videoio_ffmpeg4100.dll": {
|
|
||||||
"rid": "win-x86",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "2024.5.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Emgu.runtime.windows.msvc.rt.arm64/19.42.34435": {
|
|
||||||
"runtimeTargets": {
|
|
||||||
"runtimes/win-arm64/native/concrt140.dll": {
|
|
||||||
"rid": "win-arm64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-arm64/native/msvcp140.dll": {
|
|
||||||
"rid": "win-arm64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-arm64/native/msvcp140_1.dll": {
|
|
||||||
"rid": "win-arm64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-arm64/native/msvcp140_2.dll": {
|
|
||||||
"rid": "win-arm64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-arm64/native/msvcp140_atomic_wait.dll": {
|
|
||||||
"rid": "win-arm64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-arm64/native/msvcp140_codecvt_ids.dll": {
|
|
||||||
"rid": "win-arm64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-arm64/native/vcruntime140.dll": {
|
|
||||||
"rid": "win-arm64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-arm64/native/vcruntime140_1.dll": {
|
|
||||||
"rid": "win-arm64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Emgu.runtime.windows.msvc.rt.x64/19.42.34435": {
|
|
||||||
"runtimeTargets": {
|
|
||||||
"runtimes/win-x64/native/concrt140.dll": {
|
|
||||||
"rid": "win-x64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x64/native/msvcp140.dll": {
|
|
||||||
"rid": "win-x64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x64/native/msvcp140_1.dll": {
|
|
||||||
"rid": "win-x64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x64/native/msvcp140_2.dll": {
|
|
||||||
"rid": "win-x64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x64/native/msvcp140_atomic_wait.dll": {
|
|
||||||
"rid": "win-x64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x64/native/msvcp140_codecvt_ids.dll": {
|
|
||||||
"rid": "win-x64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x64/native/vcruntime140.dll": {
|
|
||||||
"rid": "win-x64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x64/native/vcruntime140_1.dll": {
|
|
||||||
"rid": "win-x64",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Emgu.runtime.windows.msvc.rt.x86/19.42.34435": {
|
|
||||||
"runtimeTargets": {
|
|
||||||
"runtimes/win-x86/native/concrt140.dll": {
|
|
||||||
"rid": "win-x86",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x86/native/msvcp140.dll": {
|
|
||||||
"rid": "win-x86",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x86/native/msvcp140_1.dll": {
|
|
||||||
"rid": "win-x86",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x86/native/msvcp140_2.dll": {
|
|
||||||
"rid": "win-x86",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x86/native/msvcp140_atomic_wait.dll": {
|
|
||||||
"rid": "win-x86",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x86/native/msvcp140_codecvt_ids.dll": {
|
|
||||||
"rid": "win-x86",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
},
|
|
||||||
"runtimes/win-x86/native/vcruntime140.dll": {
|
|
||||||
"rid": "win-x86",
|
|
||||||
"assetType": "native",
|
|
||||||
"fileVersion": "14.42.34433.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Bcl.AsyncInterfaces/1.1.1": {
|
"Microsoft.Bcl.AsyncInterfaces/1.1.1": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": {
|
"lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": {
|
||||||
@@ -1868,7 +1696,6 @@
|
|||||||
"XP.Common/1.0.0": {
|
"XP.Common/1.0.0": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Emgu.CV": "4.10.0.5680",
|
"Emgu.CV": "4.10.0.5680",
|
||||||
"Emgu.CV.runtime.windows": "4.10.0.5680",
|
|
||||||
"Microsoft.Data.Sqlite": "10.0.3",
|
"Microsoft.Data.Sqlite": "10.0.3",
|
||||||
"Prism.Wpf": "9.0.537",
|
"Prism.Wpf": "9.0.537",
|
||||||
"Serilog": "4.3.1",
|
"Serilog": "4.3.1",
|
||||||
@@ -1910,34 +1737,6 @@
|
|||||||
"path": "emgu.cv/4.10.0.5680",
|
"path": "emgu.cv/4.10.0.5680",
|
||||||
"hashPath": "emgu.cv.4.10.0.5680.nupkg.sha512"
|
"hashPath": "emgu.cv.4.10.0.5680.nupkg.sha512"
|
||||||
},
|
},
|
||||||
"Emgu.CV.runtime.windows/4.10.0.5680": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-WzBJWWENbF1oQKBo92ODtt9V/Eh4MFo8Y3MxxpWdoKJnZAjStxlE5epy3rB7442ZPAIRAiAaqPPZfrdml5Q9vQ==",
|
|
||||||
"path": "emgu.cv.runtime.windows/4.10.0.5680",
|
|
||||||
"hashPath": "emgu.cv.runtime.windows.4.10.0.5680.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"Emgu.runtime.windows.msvc.rt.arm64/19.42.34435": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-tb+JdhPLkX0MsMweKhL8zY/XUm+opxl6FLP7fyMf93EqUrUmR3YAyxmzcTyDyndh4XQ4bl+Eain9ugm7y/e26g==",
|
|
||||||
"path": "emgu.runtime.windows.msvc.rt.arm64/19.42.34435",
|
|
||||||
"hashPath": "emgu.runtime.windows.msvc.rt.arm64.19.42.34435.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"Emgu.runtime.windows.msvc.rt.x64/19.42.34435": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-3aIW16hRIV05a9oRW899LjPhdea/FJamPwZulwNB2P9YItwq+6XMqMeV3lg80giIQalAHQlVRVbwC54N6q3NMw==",
|
|
||||||
"path": "emgu.runtime.windows.msvc.rt.x64/19.42.34435",
|
|
||||||
"hashPath": "emgu.runtime.windows.msvc.rt.x64.19.42.34435.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"Emgu.runtime.windows.msvc.rt.x86/19.42.34435": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-NrCzZrq+S9jJOLWgh0ybVeHEQgillrASOFhlvV7FRrW1ufxCI3uZgySIpsGYSWQaJYpi5A4jfTP6Ekcjl3shDw==",
|
|
||||||
"path": "emgu.runtime.windows.msvc.rt.x86/19.42.34435",
|
|
||||||
"hashPath": "emgu.runtime.windows.msvc.rt.x86.19.42.34435.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"Microsoft.Bcl.AsyncInterfaces/1.1.1": {
|
"Microsoft.Bcl.AsyncInterfaces/1.1.1": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
|
|||||||
@@ -41,7 +41,8 @@
|
|||||||
MouseLeftButtonDown="Canvas_MouseLeftButtonDown"
|
MouseLeftButtonDown="Canvas_MouseLeftButtonDown"
|
||||||
MouseLeftButtonUp="Canvas_MouseLeftButtonUp"
|
MouseLeftButtonUp="Canvas_MouseLeftButtonUp"
|
||||||
MouseMove="Canvas_MouseMove"
|
MouseMove="Canvas_MouseMove"
|
||||||
MouseRightButtonDown="Canvas_MouseRightButtonDown">
|
MouseRightButtonDown="Canvas_MouseRightButtonDown"
|
||||||
|
PreviewMouseRightButtonUp="Canvas_PreviewMouseRightButtonUp">
|
||||||
|
|
||||||
<!-- 背景图像 -->
|
<!-- 背景图像 -->
|
||||||
<Image x:Name="backgroundImage"
|
<Image x:Name="backgroundImage"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace XP.ImageProcessing.RoiControl.Models
|
||||||
|
{
|
||||||
|
/// <summary>一次角度测量的所有视觉元素(顶点V + 射线端点A/B + 两条射线 + 弧线 + 标签)</summary>
|
||||||
|
public class AngleGroup
|
||||||
|
{
|
||||||
|
public Ellipse DotV { get; set; } // 顶点
|
||||||
|
public Ellipse DotA { get; set; } // 射线端点A
|
||||||
|
public Ellipse DotB { get; set; } // 射线端点B
|
||||||
|
public Line LineA { get; set; } // 射线VA
|
||||||
|
public Line LineB { get; set; } // 射线VB
|
||||||
|
public Path Arc { get; set; } // 角度弧线
|
||||||
|
public TextBlock Label { get; set; }
|
||||||
|
public Point V { get; set; }
|
||||||
|
public Point A { get; set; }
|
||||||
|
public Point B { get; set; }
|
||||||
|
public int Index { get; set; }
|
||||||
|
|
||||||
|
public double AngleDeg
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
double vax = A.X - V.X, vay = A.Y - V.Y;
|
||||||
|
double vbx = B.X - V.X, vby = B.Y - V.Y;
|
||||||
|
double lenA = Math.Sqrt(vax * vax + vay * vay);
|
||||||
|
double lenB = Math.Sqrt(vbx * vbx + vby * vby);
|
||||||
|
if (lenA < 0.001 || lenB < 0.001) return 0;
|
||||||
|
double dot = vax * vbx + vay * vby;
|
||||||
|
double cos = Math.Clamp(dot / (lenA * lenB), -1.0, 1.0);
|
||||||
|
return Math.Acos(cos) * 180.0 / Math.PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateVisuals()
|
||||||
|
{
|
||||||
|
// 射线
|
||||||
|
LineA.X1 = V.X; LineA.Y1 = V.Y; LineA.X2 = A.X; LineA.Y2 = A.Y;
|
||||||
|
LineA.Visibility = Visibility.Visible;
|
||||||
|
LineB.X1 = V.X; LineB.Y1 = V.Y; LineB.X2 = B.X; LineB.Y2 = B.Y;
|
||||||
|
LineB.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
// 弧线
|
||||||
|
double vax = A.X - V.X, vay = A.Y - V.Y;
|
||||||
|
double vbx = B.X - V.X, vby = B.Y - V.Y;
|
||||||
|
double lenA = Math.Sqrt(vax * vax + vay * vay);
|
||||||
|
double lenB = Math.Sqrt(vbx * vbx + vby * vby);
|
||||||
|
|
||||||
|
if (lenA < 1 || lenB < 1) { Arc.Visibility = Visibility.Collapsed; return; }
|
||||||
|
|
||||||
|
double arcRadius = Math.Min(30, Math.Min(lenA, lenB) * 0.3);
|
||||||
|
if (arcRadius < 8) arcRadius = 8;
|
||||||
|
|
||||||
|
double angleARad = Math.Atan2(vay, vax);
|
||||||
|
double angleBRad = Math.Atan2(vby, vbx);
|
||||||
|
double cross = vax * vby - vay * vbx;
|
||||||
|
double angleDeg = AngleDeg;
|
||||||
|
bool isLargeArc = angleDeg > 180;
|
||||||
|
var sweepDir = cross >= 0 ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
|
||||||
|
|
||||||
|
var startPt = new Point(V.X + arcRadius * Math.Cos(angleARad), V.Y + arcRadius * Math.Sin(angleARad));
|
||||||
|
var endPt = new Point(V.X + arcRadius * Math.Cos(angleBRad), V.Y + arcRadius * Math.Sin(angleBRad));
|
||||||
|
|
||||||
|
var arcSeg = new ArcSegment(endPt, new Size(arcRadius, arcRadius), 0, isLargeArc, sweepDir, true);
|
||||||
|
var fig = new PathFigure(startPt, new[] { arcSeg }, false);
|
||||||
|
Arc.Data = new PathGeometry(new[] { fig });
|
||||||
|
Arc.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
// 标签位置
|
||||||
|
double midAngle = (angleARad + angleBRad) / 2.0;
|
||||||
|
double testX = Math.Cos(midAngle), testY = Math.Sin(midAngle);
|
||||||
|
double testCross = vax * testY - vay * testX;
|
||||||
|
if ((cross >= 0 && testCross < 0) || (cross < 0 && testCross >= 0))
|
||||||
|
midAngle += Math.PI;
|
||||||
|
|
||||||
|
double labelDist = arcRadius + 16;
|
||||||
|
Label.Text = (Index > 0 ? $"#{Index} " : "") + $"{angleDeg:F1}°";
|
||||||
|
Canvas.SetLeft(Label, V.X + labelDist * Math.Cos(midAngle) - 15);
|
||||||
|
Canvas.SetTop(Label, V.Y + labelDist * Math.Sin(midAngle) - 8);
|
||||||
|
Label.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace XP.ImageProcessing.RoiControl.Models
|
||||||
|
{
|
||||||
|
/// <summary>BGA 空隙测量中的一个圆(气泡或焊球)</summary>
|
||||||
|
public class BgaCircle
|
||||||
|
{
|
||||||
|
public Ellipse Shape { get; set; } // 圆形轮廓
|
||||||
|
public Ellipse CenterDot { get; set; } // 中心拖拽点
|
||||||
|
public Ellipse EdgeDot { get; set; } // 边缘拖拽点(调半径)
|
||||||
|
public Point Center { get; set; }
|
||||||
|
public double Radius { get; set; }
|
||||||
|
public bool IsBall { get; set; } // true=焊球, false=气泡
|
||||||
|
|
||||||
|
public double Area => Math.PI * Radius * Radius;
|
||||||
|
|
||||||
|
public void UpdateVisuals()
|
||||||
|
{
|
||||||
|
double d = Radius * 2;
|
||||||
|
Shape.Width = d; Shape.Height = d;
|
||||||
|
Canvas.SetLeft(Shape, Center.X - Radius);
|
||||||
|
Canvas.SetTop(Shape, Center.Y - Radius);
|
||||||
|
|
||||||
|
Canvas.SetLeft(CenterDot, Center.X - CenterDot.Width / 2);
|
||||||
|
Canvas.SetTop(CenterDot, Center.Y - CenterDot.Height / 2);
|
||||||
|
|
||||||
|
var edgePt = new Point(Center.X + Radius, Center.Y);
|
||||||
|
Canvas.SetLeft(EdgeDot, edgePt.X - EdgeDot.Width / 2);
|
||||||
|
Canvas.SetTop(EdgeDot, edgePt.Y - EdgeDot.Height / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>一次 BGA 空隙测量组(1个焊球 + N个气泡 + 标签)</summary>
|
||||||
|
public class BgaVoidGroup
|
||||||
|
{
|
||||||
|
public BgaCircle Ball { get; set; }
|
||||||
|
public List<BgaCircle> Voids { get; } = new();
|
||||||
|
public TextBlock Label { get; set; }
|
||||||
|
public int Index { get; set; }
|
||||||
|
public double VoidLimit { get; set; } = 25.0;
|
||||||
|
|
||||||
|
public double VoidRate
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Ball == null || Ball.Area < 1) return 0;
|
||||||
|
double totalVoid = 0;
|
||||||
|
foreach (var v in Voids) totalVoid += v.Area;
|
||||||
|
return Math.Clamp(totalVoid / Ball.Area * 100.0, 0, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Classification => VoidRate <= VoidLimit ? "PASS" : "FAIL";
|
||||||
|
|
||||||
|
public void UpdateLabel()
|
||||||
|
{
|
||||||
|
if (Label == null || Ball == null) return;
|
||||||
|
double rate = VoidRate;
|
||||||
|
string cls = Classification;
|
||||||
|
Label.Text = (Index > 0 ? $"#{Index} " : "") +
|
||||||
|
$"Void: {rate:F1}% | Limit: {VoidLimit:F1}% | {cls}";
|
||||||
|
Label.Foreground = cls == "PASS" ? Brushes.Lime : Brushes.Red;
|
||||||
|
|
||||||
|
Canvas.SetLeft(Label, Ball.Center.X + Ball.Radius + 10);
|
||||||
|
Canvas.SetTop(Label, Ball.Center.Y - 10);
|
||||||
|
Label.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>获取所有 UI 元素</summary>
|
||||||
|
public List<UIElement> AllElements
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var list = new List<UIElement>();
|
||||||
|
if (Ball != null) { list.Add(Ball.Shape); list.Add(Ball.CenterDot); list.Add(Ball.EdgeDot); }
|
||||||
|
foreach (var v in Voids) { list.Add(v.Shape); list.Add(v.CenterDot); list.Add(v.EdgeDot); }
|
||||||
|
if (Label != null) list.Add(Label);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace XP.ImageProcessing.RoiControl.Models
|
||||||
|
{
|
||||||
|
/// <summary>通孔填锡率测量组(4个椭圆中心 + 连线 + 标签)</summary>
|
||||||
|
public class FillRateGroup
|
||||||
|
{
|
||||||
|
// 四个椭圆中心的可拖拽圆点
|
||||||
|
public Ellipse DotE1 { get; set; } // 底部(蓝)
|
||||||
|
public Ellipse DotE2 { get; set; } // 顶部(青)
|
||||||
|
public Ellipse DotE3 { get; set; } // 填锡起点(黄)
|
||||||
|
public Ellipse DotE4 { get; set; } // 填锡终点(绿)
|
||||||
|
|
||||||
|
// 长轴/短轴手柄(每个椭圆2个)
|
||||||
|
public Ellipse E1AH { get; set; }
|
||||||
|
public Ellipse E1BH { get; set; }
|
||||||
|
public Ellipse E2AH { get; set; }
|
||||||
|
public Ellipse E2BH { get; set; }
|
||||||
|
public Ellipse E3AH { get; set; }
|
||||||
|
public Ellipse E3BH { get; set; }
|
||||||
|
public Ellipse E4AH { get; set; }
|
||||||
|
public Ellipse E4BH { get; set; }
|
||||||
|
|
||||||
|
// 椭圆路径
|
||||||
|
public Path PathE1 { get; set; }
|
||||||
|
public Path PathE2 { get; set; }
|
||||||
|
public Path PathE3 { get; set; }
|
||||||
|
public Path PathE4 { get; set; }
|
||||||
|
|
||||||
|
// 连线:E1→E2(全高度),E3→E4(填锡高度)
|
||||||
|
public Line FullLine { get; set; }
|
||||||
|
public Line FillLine { get; set; }
|
||||||
|
|
||||||
|
public TextBlock Label { get; set; }
|
||||||
|
|
||||||
|
// 四个椭圆中心坐标
|
||||||
|
public Point E1 { get; set; }
|
||||||
|
public Point E2 { get; set; }
|
||||||
|
public Point E3 { get; set; }
|
||||||
|
public Point E4 { get; set; }
|
||||||
|
|
||||||
|
// 椭圆轴参数(默认值,后续可拖拽调整)
|
||||||
|
public double E1A { get; set; } = 60;
|
||||||
|
public double E1B { get; set; } = 50;
|
||||||
|
public double E1Angle { get; set; }
|
||||||
|
public double E2A { get; set; } = 60;
|
||||||
|
public double E2B { get; set; } = 50;
|
||||||
|
public double E2Angle { get; set; }
|
||||||
|
public double E3A { get; set; } = 60;
|
||||||
|
public double E3B { get; set; } = 50;
|
||||||
|
public double E3Angle { get; set; }
|
||||||
|
public double E4A { get; set; } = 55;
|
||||||
|
public double E4B { get; set; } = 45;
|
||||||
|
public double E4Angle { get; set; }
|
||||||
|
|
||||||
|
public double THTLimit { get; set; } = 75.0;
|
||||||
|
public int Index { get; set; }
|
||||||
|
|
||||||
|
public double FillRate
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
double fullDx = E2.X - E1.X, fullDy = E2.Y - E1.Y;
|
||||||
|
double fullDist = Math.Sqrt(fullDx * fullDx + fullDy * fullDy);
|
||||||
|
double fillDx = E4.X - E3.X, fillDy = E4.Y - E3.Y;
|
||||||
|
double fillDist = Math.Sqrt(fillDx * fillDx + fillDy * fillDy);
|
||||||
|
return fullDist > 0 ? Math.Clamp(fillDist / fullDist * 100.0, 0, 100) : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Classification => FillRate >= THTLimit ? "PASS" : "FAIL";
|
||||||
|
|
||||||
|
public void UpdateVisuals()
|
||||||
|
{
|
||||||
|
UpdateEllipsePath(PathE1, E1, E1A, E1B, E1Angle);
|
||||||
|
UpdateEllipsePath(PathE2, E2, E2A, E2B, E2Angle);
|
||||||
|
UpdateEllipsePath(PathE3, E3, E3A, E3B, E3Angle);
|
||||||
|
UpdateEllipsePath(PathE4, E4, E4A, E4B, E4Angle);
|
||||||
|
|
||||||
|
FullLine.X1 = E1.X; FullLine.Y1 = E1.Y;
|
||||||
|
FullLine.X2 = E2.X; FullLine.Y2 = E2.Y;
|
||||||
|
FullLine.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
FillLine.X1 = E3.X; FillLine.Y1 = E3.Y;
|
||||||
|
FillLine.X2 = E4.X; FillLine.Y2 = E4.Y;
|
||||||
|
FillLine.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
// 轴手柄定位
|
||||||
|
SetHandle(E1AH, EdgePoint(E1, E1A, E1Angle));
|
||||||
|
SetHandle(E1BH, EdgePoint(E1, E1B, E1Angle + 90));
|
||||||
|
SetHandle(E2AH, EdgePoint(E2, E2A, E2Angle));
|
||||||
|
SetHandle(E2BH, EdgePoint(E2, E2B, E2Angle + 90));
|
||||||
|
SetHandle(E3AH, EdgePoint(E3, E3A, E3Angle));
|
||||||
|
SetHandle(E3BH, EdgePoint(E3, E3B, E3Angle + 90));
|
||||||
|
SetHandle(E4AH, EdgePoint(E4, E4A, E4Angle));
|
||||||
|
SetHandle(E4BH, EdgePoint(E4, E4B, E4Angle + 90));
|
||||||
|
|
||||||
|
double rate = FillRate;
|
||||||
|
string cls = Classification;
|
||||||
|
Label.Text = (Index > 0 ? $"#{Index} " : "") + $"Fill: {rate:F1}% | THTLimit: {THTLimit:F1}% | {cls}";
|
||||||
|
Label.Foreground = cls == "PASS" ? Brushes.Lime : Brushes.Red;
|
||||||
|
double labelX = Math.Max(Math.Max(E1.X, E2.X), Math.Max(E3.X, E4.X)) + 15;
|
||||||
|
double labelY = (E1.Y + E2.Y) / 2;
|
||||||
|
Canvas.SetLeft(Label, labelX);
|
||||||
|
Canvas.SetTop(Label, labelY - 10);
|
||||||
|
Label.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>获取所有 UI 元素(用于添加/移除)</summary>
|
||||||
|
public UIElement[] AllElements => new UIElement[]
|
||||||
|
{
|
||||||
|
PathE1, PathE2, PathE3, PathE4, FullLine, FillLine, Label,
|
||||||
|
DotE1, DotE2, DotE3, DotE4,
|
||||||
|
E1AH, E1BH, E2AH, E2BH, E3AH, E3BH, E4AH, E4BH
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Point EdgePoint(Point center, double radius, double angleDeg)
|
||||||
|
{
|
||||||
|
double rad = angleDeg * Math.PI / 180.0;
|
||||||
|
return new Point(center.X + radius * Math.Cos(rad), center.Y + radius * Math.Sin(rad));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetHandle(Ellipse h, Point pos)
|
||||||
|
{
|
||||||
|
if (h == null) return;
|
||||||
|
Canvas.SetLeft(h, pos.X - h.Width / 2);
|
||||||
|
Canvas.SetTop(h, pos.Y - h.Height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double Dist(Point a, Point b)
|
||||||
|
{
|
||||||
|
double dx = b.X - a.X, dy = b.Y - a.Y;
|
||||||
|
return Math.Sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateEllipsePath(Path path, Point center, double a, double b, double angleDeg)
|
||||||
|
{
|
||||||
|
var eg = new EllipseGeometry(center, a, b);
|
||||||
|
if (Math.Abs(angleDeg) > 0.01)
|
||||||
|
eg.Transform = new RotateTransform(angleDeg, center.X, center.Y);
|
||||||
|
path.Data = eg;
|
||||||
|
path.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,10 +34,12 @@ namespace XP.ImageProcessing.RoiControl.Models
|
|||||||
|
|
||||||
public void UpdateLabel(string distanceText = null)
|
public void UpdateLabel(string distanceText = null)
|
||||||
{
|
{
|
||||||
Label.Text = distanceText ?? $"{Distance:F2} px";
|
Label.Text = (Index > 0 ? $"#{Index} " : "") + (distanceText ?? $"{Distance:F2} px");
|
||||||
Canvas.SetLeft(Label, (P1.X + P2.X) / 2 + 8);
|
Canvas.SetLeft(Label, (P1.X + P2.X) / 2 + 8);
|
||||||
Canvas.SetTop(Label, (P1.Y + P2.Y) / 2 - 18);
|
Canvas.SetTop(Label, (P1.Y + P2.Y) / 2 - 18);
|
||||||
Label.Visibility = Visibility.Visible;
|
Label.Visibility = Visibility.Visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int Index { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ namespace XP.ImageProcessing.RoiControl.Models
|
|||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
PointDistance,
|
PointDistance,
|
||||||
PointToLine
|
PointToLine,
|
||||||
|
Angle,
|
||||||
|
FillRate,
|
||||||
|
BgaVoid,
|
||||||
|
BubbleMeasure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace XP.ImageProcessing.RoiControl.Models
|
|||||||
public Point L1 { get; set; }
|
public Point L1 { get; set; }
|
||||||
public Point L2 { get; set; }
|
public Point L2 { get; set; }
|
||||||
public Point P { get; set; }
|
public Point P { get; set; }
|
||||||
|
public int Index { get; set; }
|
||||||
|
|
||||||
public double Distance
|
public double Distance
|
||||||
{
|
{
|
||||||
@@ -85,7 +86,7 @@ namespace XP.ImageProcessing.RoiControl.Models
|
|||||||
FootDot.Visibility = Visibility.Visible;
|
FootDot.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
// 标签
|
// 标签
|
||||||
Label.Text = distanceText ?? $"{Distance:F2} px";
|
Label.Text = (Index > 0 ? $"#{Index} " : "") + (distanceText ?? $"{Distance:F2} px");
|
||||||
Canvas.SetLeft(Label, (P.X + foot.X) / 2 + 8);
|
Canvas.SetLeft(Label, (P.X + foot.X) / 2 + 8);
|
||||||
Canvas.SetTop(Label, (P.Y + foot.Y) / 2 - 18);
|
Canvas.SetTop(Label, (P.Y + foot.Y) / 2 - 18);
|
||||||
Label.Visibility = Visibility.Visible;
|
Label.Visibility = Visibility.Visible;
|
||||||
|
|||||||
@@ -378,7 +378,8 @@ namespace XplorePlane
|
|||||||
|
|
||||||
// 注册视图和视图模型
|
// 注册视图和视图模型
|
||||||
containerRegistry.RegisterForNavigation<MainWindow>();
|
containerRegistry.RegisterForNavigation<MainWindow>();
|
||||||
containerRegistry.Register<MainViewModel>();
|
containerRegistry.RegisterSingleton<MainViewModel>();
|
||||||
|
containerRegistry.RegisterSingleton<ViewportPanelViewModel>();
|
||||||
containerRegistry.RegisterSingleton<NavigationPropertyPanelViewModel>();
|
containerRegistry.RegisterSingleton<NavigationPropertyPanelViewModel>();
|
||||||
|
|
||||||
// 注册图像处理服务与视图
|
// 注册图像处理服务与视图
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ namespace XplorePlane.Events
|
|||||||
PointDistance,
|
PointDistance,
|
||||||
PointLineDistance,
|
PointLineDistance,
|
||||||
Angle,
|
Angle,
|
||||||
ThroughHoleFillRate
|
ThroughHoleFillRate,
|
||||||
|
BgaVoid,
|
||||||
|
BubbleMeasure
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using XplorePlane.ViewModels.Cnc;
|
|||||||
using XplorePlane.Views;
|
using XplorePlane.Views;
|
||||||
using XplorePlane.Views.Cnc;
|
using XplorePlane.Views.Cnc;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
|
using XP.Common.GeneralForm.Views;
|
||||||
using XP.Common.PdfViewer.Interfaces;
|
using XP.Common.PdfViewer.Interfaces;
|
||||||
using XP.Hardware.MotionControl.Abstractions;
|
using XP.Hardware.MotionControl.Abstractions;
|
||||||
|
|
||||||
@@ -31,6 +32,97 @@ namespace XplorePlane.ViewModels
|
|||||||
private readonly CncEditorViewModel _cncEditorViewModel;
|
private readonly CncEditorViewModel _cncEditorViewModel;
|
||||||
private readonly CncPageView _cncPageView;
|
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; }
|
||||||
|
|
||||||
|
/// <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 _motionDebugWindow;
|
||||||
private Window _detectorConfigWindow;
|
private Window _detectorConfigWindow;
|
||||||
private Window _plcAddrConfigWindow;
|
private Window _plcAddrConfigWindow;
|
||||||
@@ -92,6 +184,11 @@ namespace XplorePlane.ViewModels
|
|||||||
PointLineDistanceMeasureCommand = new DelegateCommand(ExecutePointLineDistanceMeasure);
|
PointLineDistanceMeasureCommand = new DelegateCommand(ExecutePointLineDistanceMeasure);
|
||||||
AngleMeasureCommand = new DelegateCommand(ExecuteAngleMeasure);
|
AngleMeasureCommand = new DelegateCommand(ExecuteAngleMeasure);
|
||||||
ThroughHoleFillRateMeasureCommand = new DelegateCommand(ExecuteThroughHoleFillRateMeasure);
|
ThroughHoleFillRateMeasureCommand = new DelegateCommand(ExecuteThroughHoleFillRateMeasure);
|
||||||
|
BgaVoidMeasureCommand = new DelegateCommand(ExecuteBgaVoidMeasure);
|
||||||
|
BubbleMeasureCommand = new DelegateCommand(ExecuteBubbleMeasure);
|
||||||
|
|
||||||
|
// 辅助线命令
|
||||||
|
|
||||||
ToggleCrosshairCommand = new DelegateCommand(() =>
|
ToggleCrosshairCommand = new DelegateCommand(() =>
|
||||||
_eventAggregator.GetEvent<ToggleCrosshairEvent>().Publish());
|
_eventAggregator.GetEvent<ToggleCrosshairEvent>().Publish());
|
||||||
|
|
||||||
@@ -113,92 +210,6 @@ namespace XplorePlane.ViewModels
|
|||||||
_logger.Info("MainViewModel 已初始化");
|
_logger.Info("MainViewModel 已初始化");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string LicenseInfo
|
|
||||||
{
|
|
||||||
get => _licenseInfo;
|
|
||||||
set => SetProperty(ref _licenseInfo, 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 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;
|
|
||||||
|
|
||||||
public object ImagePanelContent
|
|
||||||
{
|
|
||||||
get => _imagePanelContent;
|
|
||||||
set => SetProperty(ref _imagePanelContent, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GridLength ImagePanelWidth
|
|
||||||
{
|
|
||||||
get => _imagePanelWidth;
|
|
||||||
set => SetProperty(ref _imagePanelWidth, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GridLength ViewportPanelWidth
|
|
||||||
{
|
|
||||||
get => _viewportPanelWidth;
|
|
||||||
set => SetProperty(ref _viewportPanelWidth, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowWindow(Window window, string name)
|
private void ShowWindow(Window window, string name)
|
||||||
{
|
{
|
||||||
window.Owner = Application.Current.MainWindow;
|
window.Owner = Application.Current.MainWindow;
|
||||||
@@ -436,30 +447,106 @@ namespace XplorePlane.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#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()
|
private void ExecutePointDistanceMeasure()
|
||||||
{
|
{
|
||||||
|
if (!CheckImageLoaded()) return;
|
||||||
_logger.Info("点点距测量功能已触发");
|
_logger.Info("点点距测量功能已触发");
|
||||||
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.PointDistance);
|
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.PointDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecutePointLineDistanceMeasure()
|
private void ExecutePointLineDistanceMeasure()
|
||||||
{
|
{
|
||||||
|
if (!CheckImageLoaded()) return;
|
||||||
_logger.Info("点线距测量功能已触发");
|
_logger.Info("点线距测量功能已触发");
|
||||||
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.PointLineDistance);
|
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.PointLineDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteAngleMeasure()
|
private void ExecuteAngleMeasure()
|
||||||
{
|
{
|
||||||
|
if (!CheckImageLoaded()) return;
|
||||||
_logger.Info("角度测量功能已触发");
|
_logger.Info("角度测量功能已触发");
|
||||||
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.Angle);
|
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.Angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteThroughHoleFillRateMeasure()
|
private void ExecuteThroughHoleFillRateMeasure()
|
||||||
{
|
{
|
||||||
|
if (!CheckImageLoaded()) return;
|
||||||
_logger.Info("通孔填锡率测量功能已触发");
|
_logger.Info("通孔填锡率测量功能已触发");
|
||||||
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.ThroughHoleFillRate);
|
_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()
|
private void ExecuteOpenLanguageSwitcher()
|
||||||
{
|
{
|
||||||
try
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -444,6 +444,24 @@
|
|||||||
SmallImage="/Assets/Icons/tools.png"
|
SmallImage="/Assets/Icons/tools.png"
|
||||||
Text="PLC 地址" />
|
Text="PLC 地址" />
|
||||||
</StackPanel>
|
</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>
|
||||||
<telerik:RadRibbonGroup Header="多语言">
|
<telerik:RadRibbonGroup Header="多语言">
|
||||||
<telerik:RadRibbonGroup.Variants>
|
<telerik:RadRibbonGroup.Variants>
|
||||||
@@ -554,7 +572,7 @@
|
|||||||
FontFamily="Microsoft YaHei UI"
|
FontFamily="Microsoft YaHei UI"
|
||||||
FontSize="11"
|
FontSize="11"
|
||||||
Foreground="White"
|
Foreground="White"
|
||||||
Text="就绪" />
|
Text="{Binding StatusMessage}" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
|
|||||||
@@ -15,25 +15,51 @@ namespace XplorePlane.Views
|
|||||||
{
|
{
|
||||||
public partial class ViewportPanelView : UserControl
|
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()
|
public ViewportPanelView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContextChanged += OnDataContextChanged;
|
DataContextChanged += OnDataContextChanged;
|
||||||
|
|
||||||
// 测量事件 → 更新状态栏
|
// 测量事件 → 更新主界面状态栏
|
||||||
RoiCanvas.MeasureCompleted += (s, e) =>
|
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 switch
|
||||||
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} 条测量";
|
"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) =>
|
RoiCanvas.MeasureStatusChanged += (s, e) =>
|
||||||
{
|
{
|
||||||
if (e is MeasureStatusEventArgs args && DataContext is ViewportPanelViewModel vm)
|
if (e is MeasureStatusEventArgs args)
|
||||||
vm.ImageInfo = args.Message;
|
SetStatus(args.Message);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 十字辅助线:直接订阅 Prism 事件
|
// 十字辅助线:直接订阅 Prism 事件
|
||||||
@@ -52,6 +78,10 @@ namespace XplorePlane.Views
|
|||||||
{
|
{
|
||||||
MeasurementToolMode.PointDistance => XP.ImageProcessing.RoiControl.Models.MeasureMode.PointDistance,
|
MeasurementToolMode.PointDistance => XP.ImageProcessing.RoiControl.Models.MeasureMode.PointDistance,
|
||||||
MeasurementToolMode.PointLineDistance => XP.ImageProcessing.RoiControl.Models.MeasureMode.PointToLine,
|
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
|
_ => XP.ImageProcessing.RoiControl.Models.MeasureMode.None
|
||||||
};
|
};
|
||||||
}, Prism.Events.ThreadOption.UIThread);
|
}, Prism.Events.ThreadOption.UIThread);
|
||||||
@@ -82,10 +112,8 @@ namespace XplorePlane.Views
|
|||||||
{
|
{
|
||||||
RoiCanvas.ClearMeasurements();
|
RoiCanvas.ClearMeasurements();
|
||||||
if (DataContext is ViewportPanelViewModel vm)
|
if (DataContext is ViewportPanelViewModel vm)
|
||||||
{
|
|
||||||
vm.ResetMeasurementState();
|
vm.ResetMeasurementState();
|
||||||
vm.ImageInfo = "已清除所有测量";
|
SetStatus("已清除所有测量");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveOriginalImage_Click(object sender, RoutedEventArgs e)
|
private void SaveOriginalImage_Click(object sender, RoutedEventArgs e)
|
||||||
|
|||||||
Reference in New Issue
Block a user