191 lines
8.6 KiB
C#
191 lines
8.6 KiB
C#
|
|
using MaxwellFramework.Core.Attributes;
|
||
|
|
using System;
|
||
|
|
using MainShell.Common;
|
||
|
|
using MainShell.Recipe.Models;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Globalization;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Windows;
|
||
|
|
|
||
|
|
namespace MainShell.Process
|
||
|
|
{
|
||
|
|
[Singleton]
|
||
|
|
public class WaferScanPlanner : IWaferScanPlanner
|
||
|
|
{
|
||
|
|
private readonly IWaferMachineAdapter _waferMachineAdapter;
|
||
|
|
|
||
|
|
public WaferScanPlanner(IWaferMachineAdapter waferMachineAdapter)
|
||
|
|
{
|
||
|
|
_waferMachineAdapter = waferMachineAdapter ?? throw new ArgumentNullException(nameof(waferMachineAdapter));
|
||
|
|
}
|
||
|
|
|
||
|
|
public WaferScanPlan CreatePlan(WaferDiePositionContext context)
|
||
|
|
{
|
||
|
|
if (context == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(context));
|
||
|
|
}
|
||
|
|
|
||
|
|
WaferScanSettings scanSettings = context.ScanSettings ?? throw new InvalidOperationException("DiePosition: scan settings are required for scan planning.");
|
||
|
|
Point cameraFieldOfView = _waferMachineAdapter.GetCameraFieldOfView(context);
|
||
|
|
ValidateCameraFieldOfView(cameraFieldOfView);
|
||
|
|
|
||
|
|
WaferScanArea scanArea = BuildScanArea(context, scanSettings, cameraFieldOfView);
|
||
|
|
double stepX = CalculateStep(cameraFieldOfView.X, scanSettings.OverlapRateX);
|
||
|
|
double stepY = CalculateStep(cameraFieldOfView.Y, scanSettings.OverlapRateY);
|
||
|
|
int scanColumnCount = CalculateScanCount(scanArea.WaferWidth, cameraFieldOfView.X, stepX);
|
||
|
|
int scanRowCount = CalculateScanCount(scanArea.WaferHeight, cameraFieldOfView.Y, stepY);
|
||
|
|
List<Point> rawPathPoints = BuildRawPathPoints(scanArea, scanSettings, stepX, stepY, scanColumnCount, scanRowCount);
|
||
|
|
List<WaferPlannerPointAdjustment> adjustments = new List<WaferPlannerPointAdjustment>();
|
||
|
|
List<Point> finalPathPoints = BuildAdjustedPathPoints(rawPathPoints, context, scanSettings, adjustments);
|
||
|
|
|
||
|
|
Point startPoint = finalPathPoints.Count > 0 ? finalPathPoints[0] : scanArea.StartPoint;
|
||
|
|
Point endPoint = finalPathPoints.Count > 0 ? finalPathPoints[finalPathPoints.Count - 1] : scanArea.EndPoint;
|
||
|
|
|
||
|
|
WaferScanPlan scanPlan = new WaferScanPlan();
|
||
|
|
scanPlan.ScanRowCount = scanRowCount;
|
||
|
|
scanPlan.ScanColumnCount = scanColumnCount;
|
||
|
|
scanPlan.PathPoints = finalPathPoints;
|
||
|
|
scanPlan.RawPathPoints = rawPathPoints;
|
||
|
|
scanPlan.FinalPathPoints = finalPathPoints;
|
||
|
|
scanPlan.ScanArea = scanArea;
|
||
|
|
scanPlan.StartPoint = startPoint;
|
||
|
|
scanPlan.EndPoint = endPoint;
|
||
|
|
scanPlan.StepX = stepX;
|
||
|
|
scanPlan.StepY = stepY;
|
||
|
|
scanPlan.OverlapX = NormalizeOverlap(scanSettings.OverlapRateX);
|
||
|
|
scanPlan.OverlapY = NormalizeOverlap(scanSettings.OverlapRateY);
|
||
|
|
scanPlan.Adjustments = adjustments;
|
||
|
|
scanPlan.PathGenerationMessage = string.Format(
|
||
|
|
CultureInfo.InvariantCulture,
|
||
|
|
"DiePosition: generated serpentine scan plan with {0} points ({1}x{2}), stepX={3:F3}, stepY={4:F3}, adjusted={5}.",
|
||
|
|
scanPlan.PathPointCount,
|
||
|
|
scanColumnCount,
|
||
|
|
scanRowCount,
|
||
|
|
stepX,
|
||
|
|
stepY,
|
||
|
|
adjustments.Count(x => x.WasAdjusted));
|
||
|
|
scanPlan.SoftLimitAdjustedCount = adjustments.Count(x => x.WasAdjusted);
|
||
|
|
scanPlan.AddedCoveragePointCount = 0;
|
||
|
|
context.ScanArea = scanArea;
|
||
|
|
return scanPlan;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void ValidateCameraFieldOfView(Point cameraFieldOfView)
|
||
|
|
{
|
||
|
|
if (cameraFieldOfView.X <= 0d || cameraFieldOfView.Y <= 0d)
|
||
|
|
{
|
||
|
|
throw new InvalidOperationException("DiePosition: camera field of view must be greater than zero.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static WaferScanArea BuildScanArea(WaferDiePositionContext context, WaferScanSettings scanSettings, Point cameraFieldOfView)
|
||
|
|
{
|
||
|
|
double waferWidth = context.WaferRecipe != null && context.WaferRecipe.WaferInfo != null ? context.WaferRecipe.WaferInfo.WaferWidth : 0d;
|
||
|
|
double waferHeight = context.WaferRecipe != null && context.WaferRecipe.WaferInfo != null ? context.WaferRecipe.WaferInfo.WaferHeight : 0d;
|
||
|
|
double effectiveWaferWidth = Math.Max(cameraFieldOfView.X, waferWidth);
|
||
|
|
double effectiveWaferHeight = Math.Max(cameraFieldOfView.Y, waferHeight);
|
||
|
|
double endX = scanSettings.StartPointX + ResolveDirectionSign(scanSettings.ScanDirectionX) * Math.Max(0d, effectiveWaferWidth - cameraFieldOfView.X);
|
||
|
|
double endY = scanSettings.StartPointY + ResolveDirectionSign(scanSettings.ScanDirectionY) * Math.Max(0d, effectiveWaferHeight - cameraFieldOfView.Y);
|
||
|
|
|
||
|
|
WaferScanArea scanArea = new WaferScanArea();
|
||
|
|
scanArea.StartPoint = new Point(scanSettings.StartPointX, scanSettings.StartPointY);
|
||
|
|
scanArea.EndPoint = new Point(endX, endY);
|
||
|
|
scanArea.MinX = Math.Min(scanArea.StartPoint.X, scanArea.EndPoint.X);
|
||
|
|
scanArea.MaxX = Math.Max(scanArea.StartPoint.X, scanArea.EndPoint.X);
|
||
|
|
scanArea.MinY = Math.Min(scanArea.StartPoint.Y, scanArea.EndPoint.Y);
|
||
|
|
scanArea.MaxY = Math.Max(scanArea.StartPoint.Y, scanArea.EndPoint.Y);
|
||
|
|
scanArea.WaferWidth = effectiveWaferWidth;
|
||
|
|
scanArea.WaferHeight = effectiveWaferHeight;
|
||
|
|
return scanArea;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double CalculateStep(double fov, double overlapRate)
|
||
|
|
{
|
||
|
|
double normalizedOverlap = NormalizeOverlap(overlapRate);
|
||
|
|
double step = fov * (1d - normalizedOverlap);
|
||
|
|
if (step <= 0d)
|
||
|
|
{
|
||
|
|
return fov;
|
||
|
|
}
|
||
|
|
|
||
|
|
return step;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double NormalizeOverlap(double overlapRate)
|
||
|
|
{
|
||
|
|
if (overlapRate < 0d)
|
||
|
|
{
|
||
|
|
return 0d;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (overlapRate >= 1d)
|
||
|
|
{
|
||
|
|
return 0.95d;
|
||
|
|
}
|
||
|
|
|
||
|
|
return overlapRate;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static int CalculateScanCount(double waferSpan, double fov, double step)
|
||
|
|
{
|
||
|
|
if (waferSpan <= fov)
|
||
|
|
{
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (int)Math.Ceiling((waferSpan - fov) / step) + 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static List<Point> BuildRawPathPoints(WaferScanArea scanArea, WaferScanSettings scanSettings, double stepX, double stepY, int scanColumnCount, int scanRowCount)
|
||
|
|
{
|
||
|
|
List<Point> rawPathPoints = new List<Point>();
|
||
|
|
int directionX = ResolveDirectionSign(scanSettings.ScanDirectionX);
|
||
|
|
int directionY = ResolveDirectionSign(scanSettings.ScanDirectionY);
|
||
|
|
|
||
|
|
for (int rowIndex = 0; rowIndex < scanRowCount; rowIndex++)
|
||
|
|
{
|
||
|
|
double y = scanArea.StartPoint.Y + rowIndex * stepY * directionY;
|
||
|
|
bool reverseColumns = rowIndex % 2 == 1;
|
||
|
|
for (int columnIndex = 0; columnIndex < scanColumnCount; columnIndex++)
|
||
|
|
{
|
||
|
|
int serpentineColumnIndex = reverseColumns ? (scanColumnCount - 1 - columnIndex) : columnIndex;
|
||
|
|
double x = scanArea.StartPoint.X + serpentineColumnIndex * stepX * directionX;
|
||
|
|
rawPathPoints.Add(new Point(x, y));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return rawPathPoints;
|
||
|
|
}
|
||
|
|
|
||
|
|
private List<Point> BuildAdjustedPathPoints(List<Point> rawPathPoints, WaferDiePositionContext context, WaferScanSettings scanSettings, List<WaferPlannerPointAdjustment> adjustments)
|
||
|
|
{
|
||
|
|
List<Point> adjustedPathPoints = new List<Point>();
|
||
|
|
foreach (Point rawPathPoint in rawPathPoints)
|
||
|
|
{
|
||
|
|
Point adjustedPoint = _waferMachineAdapter.AdjustPointWithinSoftLimit(
|
||
|
|
rawPathPoint,
|
||
|
|
context,
|
||
|
|
scanSettings.SoftLimitOffsetMm,
|
||
|
|
out bool adjusted,
|
||
|
|
out string reason);
|
||
|
|
|
||
|
|
WaferPlannerPointAdjustment adjustment = new WaferPlannerPointAdjustment();
|
||
|
|
adjustment.OriginalPoint = rawPathPoint;
|
||
|
|
adjustment.AdjustedPoint = adjustedPoint;
|
||
|
|
adjustment.WasAdjusted = adjusted;
|
||
|
|
adjustment.Reason = reason;
|
||
|
|
adjustments.Add(adjustment);
|
||
|
|
adjustedPathPoints.Add(adjustedPoint);
|
||
|
|
}
|
||
|
|
|
||
|
|
return adjustedPathPoints;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static int ResolveDirectionSign(TransPathDirection direction)
|
||
|
|
{
|
||
|
|
return direction == TransPathDirection.Negative ? -1 : 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|