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 rawPathPoints = BuildRawPathPoints(scanArea, scanSettings, stepX, stepY, scanColumnCount, scanRowCount); List adjustments = new List(); List 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 BuildRawPathPoints(WaferScanArea scanArea, WaferScanSettings scanSettings, double stepX, double stepY, int scanColumnCount, int scanRowCount) { List rawPathPoints = new List(); 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 BuildAdjustedPathPoints(List rawPathPoints, WaferDiePositionContext context, WaferScanSettings scanSettings, List adjustments) { List adjustedPathPoints = new List(); 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; } } }