using System; using System.Collections.Generic; using System.Linq; namespace DrillTools { /// /// 叠板计算结果 /// public class StackCountResult { /// /// 是否计算成功 /// public bool Success { get; set; } /// /// 建议叠板数(失败时为 0) /// public int RecommendedCount { get; set; } /// /// 钻咀叠板数(不存在时为 null,查表失败时为 -1) /// public int? DrillCount { get; set; } /// /// 槽刀叠板数(不存在时为 null,查表失败时为 -1) /// public int? SlotCount { get; set; } /// /// EA刀叠板数(不存在时为 null,查表失败时为 -1) /// public int? EACount { get; set; } /// /// 铜厚递减前的原始最小叠板数(-1 表示无法计算) /// public int RawMinCount { get; set; } /// /// 铜厚递减后的叠板数(-1 表示无法计算) /// public int AfterCopperReduction { get; set; } /// /// 限制原因或错误信息 /// public string Message { get; set; } = string.Empty; /// /// 各类别明细描述 /// public string Detail { get; set; } = string.Empty; } /// /// 钻孔叠板参数计算器 /// 规则来源:《钻孔叠板参数表》(1009B-2013钻机参数表 D2-f A1A2厂) /// public static class StackCountCalculator { #region 规则表常量 /// /// 板厚总范围下限(mm) /// private const double BoardThicknessMin = 0.4; /// /// 板厚总范围上限(mm) /// private const double BoardThicknessMax = 3.4; /// /// 16oz 总铜厚限制 /// private const double MaxTotalCopperOz = 16.0; /// /// 板厚行定义:(下限, 上限),左闭右开,最后一行左闭右闭 /// private static readonly (double Lo, double Hi)[] BoardThicknessRows = { (0.40, 0.65), (0.66, 0.85), (0.86, 0.95), (0.96, 1.05), (1.06, 1.15), (1.16, 1.35), (1.36, 1.65), (1.66, 2.05), (2.06, 2.55), (2.56, 3.40) }; /// /// 短槽刀 / EA 刀规则表(10行×4列) /// 列顺序:D=0.60, D=0.65, D=0.70, D>=0.75 /// -1 表示 "/" 或 "-"(无法达到) /// private static readonly int?[,] EAOrShortSlotTable = { // D=0.60 D=0.65 D=0.70 D>=0.75 { 5, 5, 5, 5 }, // 0.40-0.65 { 4, 4, 5, 5 }, // 0.66-0.85 { 3, 4, 5, 5 }, // 0.86-0.95 { 3, 3, 4, 4 }, // 0.96-1.05 { 3, 3, 4, 4 }, // 1.06-1.15 { 2, 3, 4, 4 }, // 1.16-1.35 { 2, 2, 3, 3 }, // 1.36-1.65 { 1, 2, 2, 2 }, // 1.66-2.05 { 1, 1, 1, 1 }, // 2.06-2.55 { 1, 1, 1, 1 } // 2.56-3.40 }; /// /// 槽刀规则表(10行×4列) /// 列顺序:D=0.50, D=0.55, D=0.60-0.65, D>=0.70 /// null 表示 "-"(无法达到) /// private static readonly int?[,] SlotTable = { // D=0.50 D=0.55 D=0.60-0.65 D>=0.70 { 5, 5, 5, null }, // 0.40-0.65 { 5, 5, 5, 5 }, // 0.66-0.85 { 5, 5, 5, 5 }, // 0.86-0.95 { 4, 5, 5, 5 }, // 0.96-1.05 { 3, 4, 4, 5 }, // 1.06-1.15 { 3, 4, 4, 5 }, // 1.16-1.35 { 2, 3, 3, 4 }, // 1.36-1.65 { 2, 2, 2, 3 }, // 1.66-2.05 { 1, 2, 2, 2 }, // 2.06-2.55 { 1, 1, 1, 2 } // 2.56-3.40 }; /// /// 钻针规则表(10行×6列) /// 列顺序:D=0.20-0.225, D=0.25, D=0.275-0.30, D=0.35-0.40, D=0.45, D>=0.50 /// private static readonly int?[,] DrillTable = { // D=0.20-0.225 D=0.25 D=0.275-0.30 D=0.35-0.40 D=0.45 D>=0.50 { 5, 5, 5, 5, 5, 5 }, // 0.40-0.65 { 4, 5, 5, 5, 5, 5 }, // 0.66-0.85 { 3, 5, 5, 5, 5, 5 }, // 0.86-0.95 { 3, 5, 5, 5, 5, 5 }, // 0.96-1.05 { 2, 4, 4, 4, 5, 5 }, // 1.06-1.15 { 2, 3, 3, 4, 4, 5 }, // 1.16-1.35 { 2, 3, 3, 3, 3, 4 }, // 1.36-1.65 { 1, 2, 2, 2, 2, 3 }, // 1.66-2.05 { 1, 2, 2, 2, 2, 2 }, // 2.06-2.55 { 1, 1, 1, 1, 1, 2 } // 2.56-3.40 }; #endregion #region 公开方法 /// /// 计算建议叠板数 /// /// 板厚(mm) /// 单张板总铜厚(oz) /// 最小钻针直径(0 表示钻带中无钻针) /// 最小槽刀直径(0 表示钻带中无槽刀) /// 最小EA刀直径(0 表示钻带中无EA刀) /// 叠板计算结果 public static StackCountResult Calculate( double boardThickness, double copperThickness, double minDrill, double minSlot, double minEA) { // 1. 输入校验 string? validationError = ValidateInputs(boardThickness, copperThickness, minDrill, minSlot, minEA); if (validationError != null) { return new StackCountResult { Success = false, Message = validationError }; } // 2. 判断各类刀具是否存在 bool hasDrill = minDrill > 0; bool hasSlot = minSlot > 0; bool hasEA = minEA > 0; // 三类都不存在则无法计算 if (!hasDrill && !hasSlot && !hasEA) { return new StackCountResult { Success = false, Message = "当前钻带中不存在钻针、槽刀和EA刀,无法计算叠板数" }; } // 3. 查板厚行 int rowIdx = FindBoardThicknessRowIndex(boardThickness); if (rowIdx < 0) { return new StackCountResult { Success = false, Message = $"板厚 {boardThickness}mm 不在规则表范围({BoardThicknessMin}-{BoardThicknessMax}mm)内" }; } // 4. 各类分别查表 var detailParts = new List(); int? drillResult = null; int? slotResult = null; int? eaResult = null; string? drillFailReason = null; string? slotFailReason = null; string? eaFailReason = null; if (hasDrill) { var (count, reason) = LookupDrillTable(rowIdx, minDrill); drillResult = count; drillFailReason = reason; } if (hasSlot) { var (count, reason) = LookupSlotTable(rowIdx, minSlot); slotResult = count; slotFailReason = reason; } if (hasEA) { var (count, reason) = LookupEATable(rowIdx, minEA); eaResult = count; eaFailReason = reason; } // 5. 构建各类明细 // 存在但查表失败的类别使整体无法计算 bool anyExistingCategoryFailed = false; if (!hasDrill) { drillResult = null; detailParts.Add("钻针 无"); } else if (drillResult == null || drillResult < 0) { anyExistingCategoryFailed = true; detailParts.Add($"钻针 查表失败({drillFailReason})"); } else { detailParts.Add($"钻针 {drillResult}"); } if (!hasSlot) { slotResult = null; detailParts.Add("槽刀 无"); } else if (slotResult == null || slotResult < 0) { anyExistingCategoryFailed = true; detailParts.Add($"槽刀 查表失败({slotFailReason})"); } else { detailParts.Add($"槽刀 {slotResult}"); } if (!hasEA) { eaResult = null; detailParts.Add("EA刀 无"); } else if (eaResult == null || eaResult < 0) { anyExistingCategoryFailed = true; detailParts.Add($"EA刀 查表失败({eaFailReason})"); } else { detailParts.Add($"EA刀 {eaResult}"); } // 6. 存在但失败的类别使整体标记为无法计算 if (anyExistingCategoryFailed) { return new StackCountResult { Success = false, DrillCount = hasDrill ? drillResult : null, SlotCount = hasSlot ? slotResult : null, EACount = hasEA ? eaResult : null, Message = "存在无法匹配规则的刀具类别,无法计算叠板数", Detail = string.Join(" / ", detailParts) }; } // 7. 取存在且成功的类别中的最小值 var validCounts = new List(); if (hasDrill && drillResult.HasValue && drillResult.Value > 0) validCounts.Add(drillResult.Value); if (hasSlot && slotResult.HasValue && slotResult.Value > 0) validCounts.Add(slotResult.Value); if (hasEA && eaResult.HasValue && eaResult.Value > 0) validCounts.Add(eaResult.Value); int rawMin = validCounts.Min(); // 8. 套用 16oz 总铜厚递减限制 int afterCopper = ApplyCopperReduction(rawMin, copperThickness); // 9. 构建结果明细 string copperNote = afterCopper < rawMin ? $"铜厚限制({copperThickness}oz×{rawMin}张={copperThickness * rawMin}oz > {MaxTotalCopperOz}oz),递减至 {afterCopper} 张" : "铜厚通过"; detailParts.Add(copperNote); detailParts.Add($"最终取 {afterCopper} 张"); return new StackCountResult { Success = true, RecommendedCount = afterCopper, DrillCount = hasDrill ? drillResult : null, SlotCount = hasSlot ? slotResult : null, EACount = hasEA ? eaResult : null, RawMinCount = rawMin, AfterCopperReduction = afterCopper, Detail = string.Join(" / ", detailParts) }; } #endregion #region 输入校验 /// /// 校验输入参数,返回 null 表示通过,否则返回中文错误提示 /// private static string? ValidateInputs(double boardThickness, double copperThickness, double minDrill, double minSlot, double minEA) { // 板厚校验 if (double.IsNaN(boardThickness) || boardThickness <= 0) return "请输入有效的板厚(必须为大于0的数字)"; if (boardThickness < BoardThicknessMin || boardThickness > BoardThicknessMax) return $"板厚 {boardThickness}mm 不在规则表范围({BoardThicknessMin}-{BoardThicknessMax}mm)内"; // 铜厚校验 if (double.IsNaN(copperThickness) || copperThickness <= 0) return "请输入有效的铜厚(必须为大于0的数字)"; // 各类刀径校验:非 NaN、不为负 if (double.IsNaN(minDrill) || minDrill < 0) return "钻针最小直径数据异常"; if (double.IsNaN(minSlot) || minSlot < 0) return "槽刀最小直径数据异常"; if (double.IsNaN(minEA) || minEA < 0) return "EA刀最小直径数据异常"; return null; } #endregion #region 板厚行查找 /// /// 查找板厚对应的行索引 /// 行区间按 PRD 定义紧密衔接(如 0.40-0.65 / 0.66-0.85),使用精确比较 /// /// 板厚 /// 行索引,-1 表示未找到 private static int FindBoardThicknessRowIndex(double boardThickness) { for (int i = 0; i < BoardThicknessRows.Length; i++) { var (lo, hi) = BoardThicknessRows[i]; // 使用 0.001 容差处理浮点精度(如 0.6600001 应落入 0.66-0.85 行) const double eps = 0.001; if (boardThickness >= lo - eps && boardThickness <= hi + eps) return i; } return -1; } #endregion #region 列匹配与查表 /// /// 短槽刀/EA刀列匹配 /// 列定义:D=0.60, D=0.65, D=0.70, D>=0.75 /// 各列为下限区间:[0.60,0.65)、[0.65,0.70)、[0.70,0.75)、[0.75,+∞) /// private static int FindEAColumnIndex(double diameter) { const double eps = 0.001; if (diameter < 0.60 - eps) return -1; if (diameter < 0.65 - eps) return 0; if (diameter < 0.70 - eps) return 1; if (diameter < 0.75 - eps) return 2; return 3; } /// /// 槽刀列匹配 /// 列定义:D=0.50, D=0.55, D=0.60-0.65, D>=0.70 /// private static int FindSlotColumnIndex(double diameter) { const double eps = 0.001; if (Math.Abs(diameter - 0.50) < eps) return 0; if (Math.Abs(diameter - 0.55) < eps) return 1; if (diameter >= 0.60 - eps && diameter <= 0.65 + eps) return 2; if (diameter >= 0.70 - eps) return 3; return -1; } /// /// 钻针列匹配 /// 列定义:D=0.20-0.225, D=0.25, D=0.275-0.30, D=0.35-0.40, D=0.45, D>=0.50 /// private static int FindDrillColumnIndex(double diameter) { const double eps = 0.001; if (diameter >= 0.20 - eps && diameter <= 0.225 + eps) return 0; if (Math.Abs(diameter - 0.25) < eps) return 1; if (diameter >= 0.275 - eps && diameter <= 0.30 + eps) return 2; if (diameter >= 0.35 - eps && diameter <= 0.40 + eps) return 3; if (Math.Abs(diameter - 0.45) < eps) return 4; if (diameter >= 0.50 - eps) return 5; return -1; } /// /// 通用查表:根据行和列索引读取规则表值 /// private static (int? count, string? failReason) LookupTable(int?[,] table, int rowIdx, int colIdx, string toolTypeName) { if (colIdx < 0) { return (null, $"刀具直径无对应列"); } int? value = table[rowIdx, colIdx]; if (value == null) { return (null, "规则格为 / 或 -,加工能力无法达到"); } return (value, null); } #endregion #region 各类查表入口 private static (int? count, string? failReason) LookupEATable(int rowIdx, double diameter) { int colIdx = FindEAColumnIndex(diameter); return LookupTable(EAOrShortSlotTable, rowIdx, colIdx, "EA刀"); } private static (int? count, string? failReason) LookupSlotTable(int rowIdx, double diameter) { int colIdx = FindSlotColumnIndex(diameter); return LookupTable(SlotTable, rowIdx, colIdx, "槽刀"); } private static (int? count, string? failReason) LookupDrillTable(int rowIdx, double diameter) { int colIdx = FindDrillColumnIndex(diameter); return LookupTable(DrillTable, rowIdx, colIdx, "钻针"); } #endregion #region 铜厚递减逻辑 /// /// 套用 16oz 总铜厚递减限制 /// 每次减1至总铜厚不超过 16oz 或保留1张 /// private static int ApplyCopperReduction(int candidateCount, double singleCopperOz) { int count = candidateCount; while (count > 1 && singleCopperOz * count > MaxTotalCopperOz) { count--; } return count; } #endregion } }