添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MainShell.Common;
|
||||
using SemiconductorVisionAlgorithm.SemiParams;
|
||||
|
||||
namespace MainShell.Motion
|
||||
{
|
||||
#region 数据结构
|
||||
|
||||
/// <summary>
|
||||
/// 逼近对位的单轴配置
|
||||
/// </summary>
|
||||
public class ApproachAlignmentAxis
|
||||
{
|
||||
public ApproachAlignmentAxis(string axisName, double toleranceValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(axisName))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(axisName));
|
||||
}
|
||||
|
||||
if (toleranceValue <= 0)
|
||||
{
|
||||
throw new ArgumentException("Tolerance value must be positive.", nameof(toleranceValue));
|
||||
}
|
||||
|
||||
AxisName = axisName;
|
||||
ToleranceValue = toleranceValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轴名称(如 "X", "Y")
|
||||
/// </summary>
|
||||
public string AxisName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 该轴的允许误差(单位与轴位置一致,通常为 mm)
|
||||
/// </summary>
|
||||
public double ToleranceValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 轴的描述信息(用于日志)
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
}
|
||||
|
||||
public enum CenterRecognitionType
|
||||
{
|
||||
Template,
|
||||
EdgeCircle
|
||||
}
|
||||
|
||||
public class CenterRecognitionParameters
|
||||
{
|
||||
|
||||
|
||||
public CenterRecognitionParameters()
|
||||
{
|
||||
MinScore = 0.8d;
|
||||
TemplatePath = "Template/Default";
|
||||
Type = CenterRecognitionType.Template;
|
||||
}
|
||||
|
||||
public string TemplatePath { get; set; }
|
||||
|
||||
public double MinScore { get; set; }
|
||||
|
||||
public bool UseRoi { get; set; }
|
||||
|
||||
public string RoiName { get; set; }
|
||||
|
||||
public CenterRecognitionType Type { get; set; }
|
||||
|
||||
public Rectangle1 rectangle { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 逼近对位请求参数
|
||||
/// </summary>
|
||||
public class ApproachAlignmentRequest
|
||||
{
|
||||
private IReadOnlyList<ApproachAlignmentAxis> _axes;
|
||||
|
||||
public ApproachAlignmentRequest(IEnumerable<ApproachAlignmentAxis> axes, CameraType camera = CameraType.TopPositionCamera)
|
||||
{
|
||||
if (axes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(axes));
|
||||
}
|
||||
|
||||
var axesList = axes.ToList();
|
||||
if (axesList.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one axis is required.", nameof(axes));
|
||||
}
|
||||
|
||||
Axes = axesList.AsReadOnly();
|
||||
Camera = camera;
|
||||
MaxIterations = 5;
|
||||
MoveTimeoutMilliseconds = 30000;
|
||||
RecognitionTimeoutMilliseconds = 100000;
|
||||
RecognitionParameters = new CenterRecognitionParameters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 逼近轴配置列表
|
||||
/// </summary>
|
||||
public IReadOnlyList<ApproachAlignmentAxis> Axes
|
||||
{
|
||||
get { return _axes; }
|
||||
private set { _axes = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 相机标识(用于采图和识别)
|
||||
/// </summary>
|
||||
public CameraType Camera { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大循环次数(默认 5)
|
||||
/// </summary>
|
||||
public int MaxIterations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 轴运动的超时时间(毫秒,默认 30000)
|
||||
/// </summary>
|
||||
public int MoveTimeoutMilliseconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 识别中心的超时时间(毫秒,默认 10000)
|
||||
/// </summary>
|
||||
public int RecognitionTimeoutMilliseconds { get; set; }
|
||||
|
||||
public CenterRecognitionParameters RecognitionParameters { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 逼近对位的结果
|
||||
/// </summary>
|
||||
public class ApproachAlignmentResult
|
||||
{
|
||||
public ApproachAlignmentResult()
|
||||
{
|
||||
FinalErrors = new Dictionary<string, double>();
|
||||
FinalAxisPositions = new Dictionary<string, double>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否成功(所有轴都在允许误差内)
|
||||
/// </summary>
|
||||
public bool Succeeded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实际完成的迭代次数
|
||||
/// </summary>
|
||||
public int CompletedIterations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最终的每轴误差(轴名 -> 误差值)
|
||||
/// </summary>
|
||||
public Dictionary<string, double> FinalErrors { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 最终的轴位置(轴名 -> 位置值,用于调试)
|
||||
/// </summary>
|
||||
public Dictionary<string, double> FinalAxisPositions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 异常信息(如果失败)
|
||||
/// </summary>
|
||||
public Exception Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取出错消息
|
||||
/// </summary>
|
||||
public string Message => Exception?.Message ?? (Succeeded ? "Approach alignment succeeded." : "Approach alignment failed.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 坐标转换结果
|
||||
/// </summary>
|
||||
public class CoordinateTransformResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 转换后的目标点坐标
|
||||
/// </summary>
|
||||
public System.Windows.Point TargetPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 转换是否成功
|
||||
/// </summary>
|
||||
public bool Succeeded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误或警告信息
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 转换过程中的异常
|
||||
/// </summary>
|
||||
public Exception Exception { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 接口定义
|
||||
|
||||
/// <summary>
|
||||
/// 中心识别器接口:通过图像算法识别目标中心
|
||||
/// </summary>
|
||||
public interface ICenterRecognizer
|
||||
{
|
||||
/// <summary>
|
||||
/// 识别图像中心点
|
||||
/// </summary>
|
||||
/// <param name="camera">相机标识(用于指定采图源)</param>
|
||||
/// <param name="timeoutMilliseconds">识别超时时间(毫秒)</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>中心点 (CenterX, CenterY) 或 null 表示识别失败</returns>
|
||||
Task<(double CenterX, double CenterY)?> RecognizeCenterAsync(
|
||||
CameraType camera,
|
||||
int timeoutMilliseconds,
|
||||
CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
Task<(double CenterX, double CenterY)?> RecognizeCenterAsync(
|
||||
CameraType camera,
|
||||
int timeoutMilliseconds,
|
||||
CenterRecognitionParameters parameters,
|
||||
CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 坐标转换器接口:将图像中心点转换为各轴的目标位置
|
||||
/// </summary>
|
||||
public interface ICoordinateTransformer
|
||||
{
|
||||
/// <summary>
|
||||
/// 将图像中心点坐标转换为轴位置
|
||||
/// </summary>
|
||||
/// <param name="centerX">图像中心 X 坐标</param>
|
||||
/// <param name="centerY">图像中心 Y 坐标</param>
|
||||
/// <param name="axes">目标轴列表</param>
|
||||
/// <returns>转换结果,包含各轴的目标位置</returns>
|
||||
Task<CoordinateTransformResult> TransformAsync(
|
||||
System.Windows. Point ruler, System.Windows.Point pixel, string cameraName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Log;
|
||||
using MaxwellFramework.Core.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace MainShell.Motion
|
||||
{
|
||||
/// <summary>
|
||||
/// <20>ƽ<EFBFBD><C6BD><EFBFBD>λ<EFBFBD><CEBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD>ʽ<EFBFBD>Ķ<EFBFBD><C4B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>㷨
|
||||
/// </summary>
|
||||
|
||||
public class ApproachAlignmentService
|
||||
{
|
||||
private readonly SafeAxisMotion _safeAxisMotion;
|
||||
private readonly ICenterRecognizer _centerRecognizer;
|
||||
private readonly ICoordinateTransformer _coordinateTransformer;
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
|
||||
public ApproachAlignmentService(
|
||||
SafeAxisMotion safeAxisMotion,
|
||||
ICenterRecognizer centerRecognizer,
|
||||
ICoordinateTransformer coordinateTransformer,
|
||||
HardwareManager hardwareManager)
|
||||
{
|
||||
_safeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
|
||||
_centerRecognizer = centerRecognizer ?? throw new ArgumentNullException(nameof(centerRecognizer));
|
||||
_coordinateTransformer = coordinateTransformer ?? throw new ArgumentNullException(nameof(coordinateTransformer));
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ִ<>бƽ<D0B1><C6BD><EFBFBD>λ
|
||||
/// </summary>
|
||||
/// <param name="request"><3E>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
|
||||
/// <param name="cancellationToken">ȡ<><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
|
||||
/// <returns><3E>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD><EFBFBD></returns>
|
||||
public async Task<ApproachAlignmentResult> ApproachAlignmentAsync(
|
||||
ApproachAlignmentRequest request,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
ApproachAlignmentResult result = new ApproachAlignmentResult();
|
||||
|
||||
try
|
||||
{
|
||||
(double CenterX, double CenterY)? center = null;
|
||||
|
||||
// ѭ<><D1AD>ִ<EFBFBD>бƽ<D0B1>
|
||||
for (int iteration = 0; iteration < request.MaxIterations; iteration++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
$"Approach alignment iteration {iteration + 1}/{request.MaxIterations} started.".LogInfo();
|
||||
|
||||
// <20><>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD>ġ<EFBFBD><C4A1>״ε<D7B4><CEB5><EFBFBD>ִ<EFBFBD><D6B4>ʶ<EFBFBD>𣬺<EFBFBD><F0A3ACBA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>ƶ<EFBFBD><C6B6><EFBFBD><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
if (center == null)
|
||||
{
|
||||
center = await RecognizeCenterAsync(
|
||||
request.Camera,
|
||||
request.RecognitionTimeoutMilliseconds,
|
||||
request.RecognitionParameters,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
if (center == null)
|
||||
{
|
||||
throw new InvalidOperationException("Center recognition failed.");
|
||||
}
|
||||
|
||||
$"Center recognized: X={center.Value.CenterX:F4}, Y={center.Value.CenterY:F4}".LogInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
$"Center reused from previous iteration: X={center.Value.CenterX:F4}, Y={center.Value.CenterY:F4}".LogInfo();
|
||||
}
|
||||
|
||||
// <20>ڶ<EFBFBD><DAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ת<EFBFBD><D7AA>
|
||||
Point rulerPoint = BuildRulerPoint(request.Axes);
|
||||
Point pixelPoint = new Point(center.Value.CenterX, center.Value.CenterY);
|
||||
string cameraName = GetCameraName(request.Camera);
|
||||
|
||||
CoordinateTransformResult transformResult = await _coordinateTransformer.TransformAsync(
|
||||
rulerPoint,
|
||||
pixelPoint,
|
||||
cameraName).ConfigureAwait(false);
|
||||
|
||||
if (!transformResult.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException($"Coordinate transformation failed: {transformResult.Message}");
|
||||
}
|
||||
|
||||
$"Coordinate transformation completed: Target=({transformResult.TargetPoint.X:F4}, {transformResult.TargetPoint.Y:F4}).".LogInfo();
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƶ<EFBFBD><C6B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
MotionMoveRequest[] moveRequests = CreateMoveRequests(
|
||||
request.Axes,
|
||||
transformResult.TargetPoint,
|
||||
request.MoveTimeoutMilliseconds);
|
||||
|
||||
MotionBatchResult moveResult = await _safeAxisMotion.SafeMoveAsync(cancellationToken, moveRequests).ConfigureAwait(false);
|
||||
moveResult.EnsureSuccess();
|
||||
|
||||
$"Axes moved successfully: {moveResult.Results.Count} axes completed.".LogInfo();
|
||||
|
||||
// <20><><EFBFBD>IJ<EFBFBD><C4B2><EFBFBD><EFBFBD>ٴ<EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
(double CenterX, double CenterY)? newCenter = await RecognizeCenterAsync(
|
||||
request.Camera,
|
||||
request.RecognitionTimeoutMilliseconds,
|
||||
request.RecognitionParameters,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
if (newCenter == null)
|
||||
{
|
||||
throw new InvalidOperationException("Center recognition failed on second attempt.");
|
||||
}
|
||||
|
||||
$"Center recognized again: X={newCenter.Value.CenterX:F4}, Y={newCenter.Value.CenterY:F4}".LogInfo();
|
||||
|
||||
// <20><><EFBFBD>岽<EFBFBD><E5B2BD>ת<EFBFBD><D7AA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>λ<EFBFBD><CEBB>
|
||||
Point newRulerPoint = BuildRulerPoint(request.Axes);
|
||||
Point newPixelPoint = new Point(newCenter.Value.CenterX, newCenter.Value.CenterY);
|
||||
CoordinateTransformResult newTransform = await _coordinateTransformer.TransformAsync(
|
||||
newRulerPoint,
|
||||
newPixelPoint,
|
||||
cameraName).ConfigureAwait(false);
|
||||
|
||||
if (!newTransform.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException($"Coordinate transformation failed on second attempt: {newTransform.Message}");
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
Dictionary<string, double> errors = new Dictionary<string, double>();
|
||||
bool allWithinTolerance = true;
|
||||
|
||||
foreach (ApproachAlignmentAxis axis in request.Axes)
|
||||
{
|
||||
double currentAxisPos = GetCurrentAxisPosition(axis.AxisName);
|
||||
double expectedAxisPos = GetExpectedAxisPosition(axis.AxisName, newTransform.TargetPoint);
|
||||
double error = Math.Abs(currentAxisPos - expectedAxisPos);
|
||||
|
||||
errors[axis.AxisName] = error;
|
||||
result.FinalAxisPositions[axis.AxisName] = currentAxisPos;
|
||||
|
||||
if (error > axis.ToleranceValue)
|
||||
{
|
||||
allWithinTolerance = false;
|
||||
}
|
||||
|
||||
$"Axis: {axis.AxisName}, Current: {currentAxisPos:F4}, Expected: {expectedAxisPos:F4}, Error: {error:F6}, Tolerance: {axis.ToleranceValue:F4}, WithinTolerance: {error <= axis.ToleranceValue}".LogInfo();
|
||||
}
|
||||
|
||||
result.FinalErrors.Clear();
|
||||
foreach (KeyValuePair<string, double> error in errors)
|
||||
{
|
||||
result.FinalErrors.Add(error.Key, error.Value);
|
||||
}
|
||||
|
||||
result.CompletedIterations = iteration + 1;
|
||||
|
||||
if (allWithinTolerance)
|
||||
{
|
||||
result.Succeeded = true;
|
||||
$"Approach alignment succeeded after {result.CompletedIterations} iteration(s).".LogInfo();
|
||||
break;
|
||||
}
|
||||
|
||||
center = newCenter;
|
||||
$"Approach alignment iteration {iteration + 1} completed. Not all axes within tolerance, continuing...".LogInfo();
|
||||
}
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
$"Approach alignment failed after maximum iterations ({request.MaxIterations}).".LogInfo();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
result.Exception = new OperationCanceledException("Approach alignment was cancelled.");
|
||||
"Approach alignment cancelled.".LogInfo();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Exception = ex;
|
||||
$"Approach alignment error: {ex.Message}".LogSysError();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<ApproachAlignmentResult> SingleAlignmentAsync(
|
||||
ApproachAlignmentRequest request,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
ApproachAlignmentResult result = new ApproachAlignmentResult();
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
"Single alignment started.".LogInfo();
|
||||
|
||||
(double CenterX, double CenterY)? center = await RecognizeCenterAsync(
|
||||
request.Camera,
|
||||
request.RecognitionTimeoutMilliseconds,
|
||||
request.RecognitionParameters,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
if (center == null)
|
||||
{
|
||||
throw new InvalidOperationException("Center recognition failed.");
|
||||
}
|
||||
|
||||
Point rulerPoint = BuildRulerPoint(request.Axes);
|
||||
Point pixelPoint = new Point(center.Value.CenterX, center.Value.CenterY);
|
||||
string cameraName = GetCameraName(request.Camera);
|
||||
|
||||
CoordinateTransformResult transformResult = await _coordinateTransformer.TransformAsync(
|
||||
rulerPoint,
|
||||
pixelPoint,
|
||||
cameraName).ConfigureAwait(false);
|
||||
if (!transformResult.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException($"Coordinate transformation failed: {transformResult.Message}");
|
||||
}
|
||||
|
||||
MotionMoveRequest[] moveRequests = CreateMoveRequests(
|
||||
request.Axes,
|
||||
transformResult.TargetPoint,
|
||||
request.MoveTimeoutMilliseconds);
|
||||
|
||||
MotionBatchResult moveResult = await _safeAxisMotion.SafeMoveAsync(cancellationToken, moveRequests).ConfigureAwait(false);
|
||||
moveResult.EnsureSuccess();
|
||||
|
||||
result.Succeeded = ProcessAlignmentResult(request.Axes, transformResult.TargetPoint, result);
|
||||
result.CompletedIterations = 1;
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
"Single alignment completed successfully.".LogInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Exception = new InvalidOperationException("Single alignment completed but result exceeded tolerance.");
|
||||
"Single alignment completed but result exceeded tolerance.".LogInfo();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
result.Exception = new OperationCanceledException("Single alignment was cancelled.");
|
||||
"Single alignment cancelled.".LogInfo();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Exception = ex;
|
||||
$"Single alignment error: {ex.Message}".LogSysError();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<(double CenterX, double CenterY)?> RecognizeCenterAsync(
|
||||
CameraType camera,
|
||||
int timeoutMilliseconds,
|
||||
CenterRecognitionParameters parameters,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _centerRecognizer.RecognizeCenterAsync(
|
||||
camera,
|
||||
timeoutMilliseconds,
|
||||
parameters,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
$"Center recognition exception: {ex.Message}".LogSysError();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private double GetCurrentAxisPosition(string axisName)
|
||||
{
|
||||
try
|
||||
{
|
||||
MwFramework.Device.IAxis axis = _hardwareManager.GetAxisByName(axisName);
|
||||
if (axis == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Axis '{axisName}' not found.");
|
||||
}
|
||||
|
||||
return axis.State != null ? axis.State.ActualPos : axis.GetPositionImmediate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
$"Failed to get current position for axis '{axisName}': {ex.Message}".LogSysError();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private MotionMoveRequest[] CreateMoveRequests(
|
||||
IReadOnlyList<ApproachAlignmentAxis> axes,
|
||||
Point targetPoint,
|
||||
int moveTimeoutMilliseconds)
|
||||
{
|
||||
List<MotionMoveRequest> moveRequests = new List<MotionMoveRequest>();
|
||||
|
||||
foreach (ApproachAlignmentAxis axis in axes)
|
||||
{
|
||||
double targetPosition = GetExpectedAxisPosition(axis.AxisName, targetPoint);
|
||||
moveRequests.Add(MotionMoveRequest.ForAxisName(axis.AxisName, targetPosition, moveTimeoutMilliseconds));
|
||||
}
|
||||
|
||||
return moveRequests.ToArray();
|
||||
}
|
||||
|
||||
private bool ProcessAlignmentResult(
|
||||
IReadOnlyList<ApproachAlignmentAxis> axes,
|
||||
Point targetPoint,
|
||||
ApproachAlignmentResult result)
|
||||
{
|
||||
bool allWithinTolerance = true;
|
||||
|
||||
result.FinalErrors.Clear();
|
||||
|
||||
foreach (ApproachAlignmentAxis axis in axes)
|
||||
{
|
||||
double currentAxisPos = GetCurrentAxisPosition(axis.AxisName);
|
||||
double expectedAxisPos = GetExpectedAxisPosition(axis.AxisName, targetPoint);
|
||||
double error = Math.Abs(currentAxisPos - expectedAxisPos);
|
||||
|
||||
result.FinalErrors[axis.AxisName] = error;
|
||||
result.FinalAxisPositions[axis.AxisName] = currentAxisPos;
|
||||
|
||||
if (error > axis.ToleranceValue)
|
||||
{
|
||||
allWithinTolerance = false;
|
||||
}
|
||||
|
||||
$"Axis: {axis.AxisName}, Current: {currentAxisPos:F4}, Expected: {expectedAxisPos:F4}, Error: {error:F6}, Tolerance: {axis.ToleranceValue:F4}, WithinTolerance: {error <= axis.ToleranceValue}".LogInfo();
|
||||
}
|
||||
|
||||
return allWithinTolerance;
|
||||
}
|
||||
|
||||
private Point BuildRulerPoint(IReadOnlyList<ApproachAlignmentAxis> axes)
|
||||
{
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
bool hasX = false;
|
||||
bool hasY = false;
|
||||
|
||||
foreach (ApproachAlignmentAxis axis in axes)
|
||||
{
|
||||
if (axis == null || string.IsNullOrWhiteSpace(axis.AxisName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double currentPosition = GetCurrentAxisPosition(axis.AxisName);
|
||||
if (IsYAxis(axis.AxisName))
|
||||
{
|
||||
if (!hasY)
|
||||
{
|
||||
y = currentPosition;
|
||||
hasY = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!hasX)
|
||||
{
|
||||
x = currentPosition;
|
||||
hasX = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
private double GetExpectedAxisPosition(string axisName, Point targetPoint)
|
||||
{
|
||||
return IsYAxis(axisName) ? targetPoint.Y : targetPoint.X;
|
||||
}
|
||||
|
||||
private static bool IsYAxis(string axisName)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(axisName) && axisName.IndexOf("Y", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
private string GetCameraName(CameraType camera)
|
||||
{
|
||||
return _hardwareManager.GetCameraName(camera);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Log;
|
||||
using MainShell.Vision;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace MainShell.Motion
|
||||
{
|
||||
/// <summary>
|
||||
/// 中心识别实现
|
||||
/// 通过模板匹配服务识别目标中心
|
||||
/// </summary>
|
||||
public class CenterRecognizer : ICenterRecognizer
|
||||
{
|
||||
private readonly IFindTemplateService _findTemplateService;
|
||||
private readonly IFindEdgeCircleService _findEdgeCircleService;
|
||||
|
||||
public CenterRecognizer(
|
||||
IFindTemplateService findTemplateService,
|
||||
IFindEdgeCircleService findEdgeCircleService)
|
||||
{
|
||||
_findTemplateService = findTemplateService ?? throw new ArgumentNullException(nameof(findTemplateService));
|
||||
_findEdgeCircleService = findEdgeCircleService ?? throw new ArgumentNullException(nameof(findEdgeCircleService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 识别图像中心坐标
|
||||
/// </summary>
|
||||
public Task<(double CenterX, double CenterY)?> RecognizeCenterAsync(
|
||||
CameraType camera,
|
||||
int timeoutMilliseconds,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return RecognizeCenterAsync(
|
||||
camera,
|
||||
timeoutMilliseconds,
|
||||
new CenterRecognitionParameters(),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 识别图像中心坐标
|
||||
/// </summary>
|
||||
public async Task<(double CenterX, double CenterY)?> RecognizeCenterAsync(
|
||||
CameraType camera,
|
||||
int timeoutMilliseconds,
|
||||
CenterRecognitionParameters parameters,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (parameters == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parameters));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (CancellationTokenSource cts = new CancellationTokenSource(timeoutMilliseconds))
|
||||
using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token))
|
||||
{
|
||||
string.Format(
|
||||
"CenterRecognizer: Starting template recognition from camera '{0}' (timeout={1}ms, template='{2}', minScore={3}).",
|
||||
camera,
|
||||
timeoutMilliseconds,
|
||||
parameters.TemplatePath,
|
||||
parameters.MinScore).LogInfo();
|
||||
VisionProcessResult result = null;
|
||||
double centerX = 0;
|
||||
double centerY = 0;
|
||||
switch (parameters.Type)
|
||||
{
|
||||
case CenterRecognitionType.Template:
|
||||
{
|
||||
FindTemplateRequest request = BuildFindTemplateRequest(camera, timeoutMilliseconds,parameters);
|
||||
var templateResult = await _findTemplateService.ProcessAsync(request,linkedCts.Token).ConfigureAwait(false);
|
||||
if (templateResult.Succeeded && templateResult.Data != null)
|
||||
{
|
||||
centerX = templateResult.Data.CenterX;
|
||||
centerY = templateResult.Data.CenterY;
|
||||
}
|
||||
else
|
||||
{
|
||||
string.Format(
|
||||
"CenterRecognizer: Template recognition failed for camera '{0}': {1}",
|
||||
camera,
|
||||
templateResult.Message).LogSysError();
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CenterRecognitionType.EdgeCircle:
|
||||
{
|
||||
FindEdgeCircleRequest request = BuildFindEdgeCircleRequest(camera,timeoutMilliseconds, parameters);
|
||||
var circleResult = await _findEdgeCircleService.ProcessAsync(request,linkedCts.Token).ConfigureAwait(false);
|
||||
if (circleResult.Succeeded && circleResult.Data != null)
|
||||
{
|
||||
centerX = circleResult.Data.CenterX;
|
||||
centerY = circleResult.Data.CenterY;
|
||||
}
|
||||
else
|
||||
{
|
||||
string.Format(
|
||||
"CenterRecognizer: Edge circle recognition failed for camera '{0}': {1}",
|
||||
camera,
|
||||
circleResult.Message).LogSysError();
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string.Format(
|
||||
"CenterRecognizer: Recognition completed: X={0:F4}, Y={1:F4}",
|
||||
centerX,
|
||||
centerY).LogInfo();
|
||||
|
||||
return (centerX, centerY);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
string.Format(
|
||||
"CenterRecognizer: Template recognition timed out for camera '{0}'.",
|
||||
camera).LogSysError();
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string.Format(
|
||||
"CenterRecognizer: Template recognition failed for camera '{0}': {1}",
|
||||
camera,
|
||||
ex.Message).LogSysError();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static FindTemplateRequest BuildFindTemplateRequest(
|
||||
CameraType camera,
|
||||
int timeoutMilliseconds,
|
||||
CenterRecognitionParameters parameters)
|
||||
{
|
||||
FindTemplateParameters findTemplateParameters = new FindTemplateParameters();
|
||||
findTemplateParameters.MinScore = parameters.MinScore;
|
||||
findTemplateParameters.TemplatePath = parameters.TemplatePath;
|
||||
findTemplateParameters.UseRoi = parameters.UseRoi;
|
||||
findTemplateParameters.RoiName = parameters.RoiName;
|
||||
|
||||
FindTemplateRequest request = new FindTemplateRequest();
|
||||
request.CameraSource = camera;
|
||||
request.CaptureOptions = CameraCaptureOptions.CreateStream(timeoutMilliseconds);
|
||||
request.Parameters = findTemplateParameters;
|
||||
request.TimeoutMilliseconds = timeoutMilliseconds;
|
||||
return request;
|
||||
}
|
||||
|
||||
private static FindEdgeCircleRequest BuildFindEdgeCircleRequest(
|
||||
CameraType camera,
|
||||
int timeoutMilliseconds,
|
||||
CenterRecognitionParameters parameters)
|
||||
{
|
||||
FindEdgeCircleParameters findEdgeCircleParameters = new FindEdgeCircleParameters();
|
||||
findEdgeCircleParameters.RoiRect = parameters.rectangle;
|
||||
|
||||
FindEdgeCircleRequest request = new FindEdgeCircleRequest();
|
||||
request.CameraSource = camera;
|
||||
request.CaptureOptions = CameraCaptureOptions.CreateStream(timeoutMilliseconds);
|
||||
request.Parameters = findEdgeCircleParameters;
|
||||
request.TimeoutMilliseconds = timeoutMilliseconds;
|
||||
return request;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using MainShell.Log;
|
||||
using SemiconductorVisionAlgorithm.SemiCalib;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace MainShell.Motion
|
||||
{
|
||||
/// <summary>
|
||||
/// 坐标转换实现
|
||||
/// 此实现为基础版本,实际应用中应由标定系统提供具体实现
|
||||
/// </summary>
|
||||
public class CoordinateTransformer : ICoordinateTransformer
|
||||
{
|
||||
/// <summary>
|
||||
/// 将图像坐标转换为轴位置
|
||||
/// </summary>
|
||||
public async Task<CoordinateTransformResult> TransformAsync(
|
||||
Point ruler, Point pixel, string cameraName)
|
||||
{
|
||||
try
|
||||
{
|
||||
CoordinateTransformResult result = new CoordinateTransformResult();
|
||||
|
||||
// 模拟异步操作
|
||||
await Task.Delay(10).ConfigureAwait(false);
|
||||
|
||||
// 此处应根据机器标定数据进行坐标变换
|
||||
// 示例流程:
|
||||
// 1. 获取相机标定参数(内参、外参)
|
||||
// 2. 获取机械臂/轴的标定数据
|
||||
// 3. 将图像坐标(centerX, centerY)转换为各轴的目标位置
|
||||
|
||||
SemiconductorVisionAlgorithm.SemiParams.Point semiRulerPoint = new SemiconductorVisionAlgorithm.SemiParams.Point(ruler.X, ruler.Y);
|
||||
SemiconductorVisionAlgorithm.SemiParams.Point semiPixelPoint = new SemiconductorVisionAlgorithm.SemiParams.Point(pixel.X, pixel.Y);
|
||||
|
||||
|
||||
CalibManager.Instance.get_real_by_pixel(cameraName, 0, semiRulerPoint, semiPixelPoint
|
||||
, out SemiconductorVisionAlgorithm.SemiParams.Point point);
|
||||
|
||||
result.TargetPoint = new Point(point.X, point.Y);
|
||||
result.Succeeded = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string.Format("Coordinate transformation failed: {0}", ex.Message).LogSysError();
|
||||
return new CoordinateTransformResult
|
||||
{
|
||||
Succeeded = false,
|
||||
Message = ex.Message,
|
||||
Exception = ex
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
# 逼近对位功能快速参考
|
||||
|
||||
## 1. 文档范围
|
||||
|
||||
本文档基于当前仓库 `main` 分支最新已提交代码整理,描述 [`MainShell/Motion/ApproachAlignment`](D:/ww/盘古/PanGu.DieBonderApp/MainShell/Motion/ApproachAlignment) 目录下逼近对位模块的真实实现、调用方式和接入注意事项。
|
||||
|
||||
说明:
|
||||
- 本文档以 `git` 最新已提交代码为准。
|
||||
- 若旧 README、注释或设计文档与代码不一致,以代码为准。
|
||||
- 当前模块的流程框架已具备,但坐标变换仍需业务侧补齐真实实现。
|
||||
|
||||
## 2. 文件清单
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `ApproachAlignment.cs` | 数据结构和接口定义 |
|
||||
| `ApproachAlignmentService.cs` | 逼近对位主流程 |
|
||||
| `CenterRecognizer.cs` | 中心识别实现 |
|
||||
| `CoordinateTransformer.cs` | 坐标转换实现 |
|
||||
|
||||
## 3. 核心类型
|
||||
|
||||
当前代码中,以下类型定义在 [`ApproachAlignment.cs`](D:/ww/盘古/PanGu.DieBonderApp/MainShell/Motion/ApproachAlignment/ApproachAlignment.cs) 中:
|
||||
|
||||
- `ApproachAlignmentAxis`
|
||||
- `ApproachAlignmentRequest`
|
||||
- `ApproachAlignmentResult`
|
||||
- `CoordinateTransformResult`
|
||||
- `ICenterRecognizer`
|
||||
- `ICoordinateTransformer`
|
||||
|
||||
### 3.1 ApproachAlignmentAxis
|
||||
|
||||
用于描述参与逼近对位的单根轴:
|
||||
|
||||
- `AxisName`:轴名
|
||||
- `ToleranceValue`:该轴允许误差
|
||||
- `Description`:可选描述,用于日志或调试
|
||||
|
||||
### 3.2 ApproachAlignmentRequest
|
||||
|
||||
用于描述一次逼近对位请求:
|
||||
|
||||
- `Axes`:参与对位的轴列表
|
||||
- `Camera`:相机类型,默认 `CameraType.UpCamera`
|
||||
- `MaxIterations`:最大迭代次数,默认 5
|
||||
- `MoveTimeoutMilliseconds`:运动超时,默认 30000
|
||||
- `RecognitionTimeoutMilliseconds`:识别超时,默认 10000
|
||||
|
||||
### 3.3 ApproachAlignmentResult
|
||||
|
||||
用于描述逼近对位结果:
|
||||
|
||||
- `Succeeded`:是否成功收敛
|
||||
- `CompletedIterations`:实际完成的迭代次数
|
||||
- `FinalErrors`:各轴最终误差
|
||||
- `FinalAxisPositions`:各轴最终位置
|
||||
- `Exception`:失败或取消时的异常
|
||||
- `Message`:结果消息
|
||||
|
||||
## 4. 当前真实入口
|
||||
|
||||
当前 `git` 最新代码中,逼近对位的真实调用入口是:
|
||||
|
||||
- [`ApproachAlignmentService.cs`](D:/ww/盘古/PanGu.DieBonderApp/MainShell/Motion/ApproachAlignment/ApproachAlignmentService.cs)
|
||||
|
||||
主要方法:
|
||||
|
||||
```csharp
|
||||
Task<ApproachAlignmentResult> ApproachAlignmentAsync(
|
||||
ApproachAlignmentRequest request,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
```
|
||||
|
||||
注意:
|
||||
- 当前 `SafeAxisMotion` 中并没有 `ApproachAlignmentAsync()` 或 `ApproachAlignment()` 这组方法。
|
||||
- 旧 README 中写到 `SafeAxisMotion.ApproachAlignmentAsync()` 的内容,和当前代码不一致。
|
||||
- 实际接入时应直接注入并调用 `ApproachAlignmentService`。
|
||||
|
||||
## 5. 工作流程
|
||||
|
||||
[`ApproachAlignmentService.cs`](D:/ww/盘古/PanGu.DieBonderApp/MainShell/Motion/ApproachAlignment/ApproachAlignmentService.cs) 当前实现的是一个迭代式流程:
|
||||
|
||||
1. 根据请求进入循环,最多执行 `MaxIterations` 次。
|
||||
2. 首次迭代调用 `ICenterRecognizer.RecognizeCenterAsync()` 识别中心。
|
||||
3. 调用 `ICoordinateTransformer.TransformAsync()` 将视觉中心转换为各轴目标位置。
|
||||
4. 把转换结果封装为 `MotionMoveRequest` 数组。
|
||||
5. 调用 `SafeAxisMotion.SafeMoveAsync()` 执行多轴移动。
|
||||
6. 再次识别中心。
|
||||
7. 再次做坐标转换。
|
||||
8. 用“当前轴位置”和“新转换目标位置”计算误差。
|
||||
9. 如果所有轴误差都在各自 `ToleranceValue` 内,则成功结束。
|
||||
10. 否则继续下一轮迭代,直到达到最大迭代次数。
|
||||
|
||||
## 6. 必须实现或理解的接口
|
||||
|
||||
### 6.1 ICenterRecognizer
|
||||
|
||||
接口定义:
|
||||
|
||||
```csharp
|
||||
Task<(double CenterX, double CenterY)?> RecognizeCenterAsync(
|
||||
CameraType camera,
|
||||
int timeoutMilliseconds,
|
||||
CancellationToken cancellationToken = default(CancellationToken));
|
||||
```
|
||||
|
||||
当前默认实现是 [`CenterRecognizer.cs`](D:/ww/盘古/PanGu.DieBonderApp/MainShell/Motion/ApproachAlignment/CenterRecognizer.cs)。
|
||||
|
||||
它的真实行为是:
|
||||
- 依赖 `IVisionAlgorithmService`
|
||||
- 构造 `VisionProcessRequest`
|
||||
- `AlgorithmType = VisionAlgorithmType.FindCenter`
|
||||
- `CaptureOptions = CameraCaptureOptions.CreateStream(timeoutMilliseconds)`
|
||||
- 从结果里读取 `OffsetX` / `OffsetY`
|
||||
- 识别失败时返回 `null`
|
||||
|
||||
如果你们已有自己的视觉流程,也可以替换成自定义的 `ICenterRecognizer` 实现。
|
||||
|
||||
### 6.2 ICoordinateTransformer
|
||||
|
||||
接口定义:
|
||||
|
||||
```csharp
|
||||
Task<CoordinateTransformResult> TransformAsync(
|
||||
double centerX,
|
||||
double centerY,
|
||||
IEnumerable<ApproachAlignmentAxis> axes);
|
||||
```
|
||||
|
||||
当前默认实现是 [`CoordinateTransformer.cs`](D:/ww/盘古/PanGu.DieBonderApp/MainShell/Motion/ApproachAlignment/CoordinateTransformer.cs)。
|
||||
|
||||
但需要特别注意:
|
||||
- 当前实现只是占位版本
|
||||
- 它会给每根轴写入 `0.0` 作为目标位置
|
||||
- 然后直接返回 `Succeeded = true`
|
||||
|
||||
这表示:
|
||||
- 逼近对位的流程框架是完整的
|
||||
- 但真实标定坐标转换还没有完成
|
||||
- 若直接用于生产逻辑,目标位置会被错误地写成固定值
|
||||
|
||||
因此,接入前必须替换为你们设备真实可用的坐标变换实现。
|
||||
|
||||
## 7. 依赖关系
|
||||
|
||||
当前 `ApproachAlignmentService` 构造函数依赖:
|
||||
|
||||
- `SafeAxisMotion`
|
||||
- `ICenterRecognizer`
|
||||
- `ICoordinateTransformer`
|
||||
- `HardwareManager`
|
||||
|
||||
依赖注入示例:
|
||||
|
||||
```csharp
|
||||
protected override void ConfigureIoC(IStyletIoCBuilder builder)
|
||||
{
|
||||
builder.Bind<SafeAxisMotion>().ToSelf().InSingletonScope();
|
||||
builder.Bind<ICenterRecognizer>().To<CenterRecognizer>().InSingletonScope();
|
||||
builder.Bind<ICoordinateTransformer>().To<CalibratedCoordinateTransformer>().InSingletonScope();
|
||||
builder.Bind<ApproachAlignmentService>().ToSelf().InSingletonScope();
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
- `CenterRecognizer` 可以直接复用当前实现
|
||||
- `ICoordinateTransformer` 建议替换为你们自己的真实标定实现,不建议直接使用当前占位版 `CoordinateTransformer`
|
||||
|
||||
## 8. 推荐接入方式
|
||||
|
||||
### 8.1 典型调用流程
|
||||
|
||||
```text
|
||||
1. 创建 ApproachAlignmentAxis 列表
|
||||
2. 创建 ApproachAlignmentRequest
|
||||
3. 调用 ApproachAlignmentService.ApproachAlignmentAsync()
|
||||
4. 检查 ApproachAlignmentResult.Succeeded
|
||||
5. 如失败,查看 Exception 或 Message
|
||||
6. 如成功,读取 FinalAxisPositions 和 FinalErrors
|
||||
```
|
||||
|
||||
### 8.2 示例代码
|
||||
|
||||
```csharp
|
||||
public async Task<ApproachAlignmentResult> RunApproachAlignmentAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var request = new ApproachAlignmentRequest(
|
||||
new[]
|
||||
{
|
||||
new ApproachAlignmentAxis("Axis_X", 0.01) { Description = "X axis" },
|
||||
new ApproachAlignmentAxis("Axis_Y", 0.01) { Description = "Y axis" }
|
||||
},
|
||||
camera: CameraType.UpCamera)
|
||||
{
|
||||
MaxIterations = 5,
|
||||
MoveTimeoutMilliseconds = 30000,
|
||||
RecognitionTimeoutMilliseconds = 10000
|
||||
};
|
||||
|
||||
var result = await _approachAlignmentService.ApproachAlignmentAsync(
|
||||
request,
|
||||
cancellationToken);
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw result.Exception ?? new InvalidOperationException(result.Message);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 自定义坐标转换器示例
|
||||
|
||||
以下示例展示如何替换当前占位版 `CoordinateTransformer`:
|
||||
|
||||
```csharp
|
||||
public class CalibratedCoordinateTransformer : ICoordinateTransformer
|
||||
{
|
||||
public Task<CoordinateTransformResult> TransformAsync(
|
||||
double centerX,
|
||||
double centerY,
|
||||
IEnumerable<ApproachAlignmentAxis> axes)
|
||||
{
|
||||
var result = new CoordinateTransformResult();
|
||||
|
||||
foreach (var axis in axes)
|
||||
{
|
||||
var target = CalculateTarget(axis.AxisName, centerX, centerY);
|
||||
result.AxisPositions[axis.AxisName] = target;
|
||||
}
|
||||
|
||||
result.Succeeded = true;
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
private double CalculateTarget(string axisName, double centerX, double centerY)
|
||||
{
|
||||
// 替换为实际标定变换逻辑
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 10. 日志输出示例
|
||||
|
||||
当前代码里,`ApproachAlignmentService` 会输出类似这样的日志:
|
||||
|
||||
```text
|
||||
[INFO] Approach alignment iteration 1/5 started.
|
||||
[INFO] Center recognized: X=1024.5600, Y=768.3200
|
||||
[INFO] Coordinate transformation completed: 2 axes.
|
||||
[INFO] Axes moved successfully: 2 axes completed.
|
||||
[INFO] Center recognized again: X=1023.9800, Y=768.1500
|
||||
[INFO] Axis: Axis_X, Current: 100.0250, Expected: 100.0200, Error: 0.005000, Tolerance: 0.0100, WithinTolerance: True
|
||||
[INFO] Axis: Axis_Y, Current: 200.0150, Expected: 200.0100, Error: 0.005000, Tolerance: 0.0100, WithinTolerance: True
|
||||
[INFO] Approach alignment succeeded after 1 iteration(s).
|
||||
```
|
||||
|
||||
如果异常发生,也会记录失败日志,例如:
|
||||
- `Center recognition failed.`
|
||||
- `Coordinate transformation failed: ...`
|
||||
- `Failed to get current position for axis ...`
|
||||
|
||||
## 11. 常见问题
|
||||
|
||||
### Q: 如何修改最大迭代次数?
|
||||
A: 设置 `ApproachAlignmentRequest.MaxIterations`。
|
||||
|
||||
### Q: 如何设置不同轴的允许误差?
|
||||
A: 为每个 `ApproachAlignmentAxis` 设置不同的 `ToleranceValue`。
|
||||
|
||||
### Q: 能否在执行中途取消?
|
||||
A: 可以,通过 `CancellationToken` 取消。取消后结果中的 `Exception` 会记录取消异常。
|
||||
|
||||
### Q: 如何查看每个轴的最终误差?
|
||||
A: 查看 `ApproachAlignmentResult.FinalErrors`。
|
||||
|
||||
### Q: 逼近失败时如何获取原因?
|
||||
A: 查看 `ApproachAlignmentResult.Exception` 或 `ApproachAlignmentResult.Message`。
|
||||
|
||||
### Q: 当前默认实现能直接用于生产吗?
|
||||
A: 不建议。`CenterRecognizer` 基本可接真实视觉流程,但 `CoordinateTransformer` 仍是占位实现,必须先替换。
|
||||
|
||||
## 12. 当前状态总结
|
||||
|
||||
当前 `ApproachAlignment` 模块的真实状态是:
|
||||
|
||||
- 数据结构已经完整
|
||||
- 主流程已经完整
|
||||
- 视觉识别已有可接真实系统的实现
|
||||
- 运动执行复用了 `SafeAxisMotion`
|
||||
- 坐标变换仍需业务侧补齐真实算法
|
||||
- README 中旧的 `SafeAxisMotion.ApproachAlignmentAsync()` 说法已经不适用
|
||||
|
||||
接入建议:
|
||||
- 直接注入 `ApproachAlignmentService`
|
||||
- 保留当前 `CenterRecognizer` 或替换成自定义实现
|
||||
- 优先补齐真实的 `ICoordinateTransformer`
|
||||
- 在真实设备上联调前,不要使用默认 `CoordinateTransformer` 做生产动作
|
||||
Reference in New Issue
Block a user