EA刀/短槽刀列按参数表下限区间 [0.60,0.65)/[0.65,0.70)/[0.70,0.75)/>=0.75 匹配,修复 0.706mm 等刀径查表失败;叠板计算改为 Expander 默认收折、移除计算按钮、输入变化自动计算。 Co-authored-by: Cursor <cursoragent@cursor.com>
504 lines
18 KiB
C#
504 lines
18 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
|
||
namespace DrillTools
|
||
{
|
||
/// <summary>
|
||
/// 叠板计算结果
|
||
/// </summary>
|
||
public class StackCountResult
|
||
{
|
||
/// <summary>
|
||
/// 是否计算成功
|
||
/// </summary>
|
||
public bool Success { get; set; }
|
||
|
||
/// <summary>
|
||
/// 建议叠板数(失败时为 0)
|
||
/// </summary>
|
||
public int RecommendedCount { get; set; }
|
||
|
||
/// <summary>
|
||
/// 钻咀叠板数(不存在时为 null,查表失败时为 -1)
|
||
/// </summary>
|
||
public int? DrillCount { get; set; }
|
||
|
||
/// <summary>
|
||
/// 槽刀叠板数(不存在时为 null,查表失败时为 -1)
|
||
/// </summary>
|
||
public int? SlotCount { get; set; }
|
||
|
||
/// <summary>
|
||
/// EA刀叠板数(不存在时为 null,查表失败时为 -1)
|
||
/// </summary>
|
||
public int? EACount { get; set; }
|
||
|
||
/// <summary>
|
||
/// 铜厚递减前的原始最小叠板数(-1 表示无法计算)
|
||
/// </summary>
|
||
public int RawMinCount { get; set; }
|
||
|
||
/// <summary>
|
||
/// 铜厚递减后的叠板数(-1 表示无法计算)
|
||
/// </summary>
|
||
public int AfterCopperReduction { get; set; }
|
||
|
||
/// <summary>
|
||
/// 限制原因或错误信息
|
||
/// </summary>
|
||
public string Message { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 各类别明细描述
|
||
/// </summary>
|
||
public string Detail { get; set; } = string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 钻孔叠板参数计算器
|
||
/// 规则来源:《钻孔叠板参数表》(1009B-2013钻机参数表 D2-f A1A2厂)
|
||
/// </summary>
|
||
public static class StackCountCalculator
|
||
{
|
||
#region 规则表常量
|
||
|
||
/// <summary>
|
||
/// 板厚总范围下限(mm)
|
||
/// </summary>
|
||
private const double BoardThicknessMin = 0.4;
|
||
|
||
/// <summary>
|
||
/// 板厚总范围上限(mm)
|
||
/// </summary>
|
||
private const double BoardThicknessMax = 3.4;
|
||
|
||
/// <summary>
|
||
/// 16oz 总铜厚限制
|
||
/// </summary>
|
||
private const double MaxTotalCopperOz = 16.0;
|
||
|
||
/// <summary>
|
||
/// 板厚行定义:(下限, 上限),左闭右开,最后一行左闭右闭
|
||
/// </summary>
|
||
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)
|
||
};
|
||
|
||
/// <summary>
|
||
/// 短槽刀 / EA 刀规则表(10行×4列)
|
||
/// 列顺序:D=0.60, D=0.65, D=0.70, D>=0.75
|
||
/// -1 表示 "/" 或 "-"(无法达到)
|
||
/// </summary>
|
||
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
|
||
};
|
||
|
||
/// <summary>
|
||
/// 槽刀规则表(10行×4列)
|
||
/// 列顺序:D=0.50, D=0.55, D=0.60-0.65, D>=0.70
|
||
/// null 表示 "-"(无法达到)
|
||
/// </summary>
|
||
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
|
||
};
|
||
|
||
/// <summary>
|
||
/// 钻针规则表(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
|
||
/// </summary>
|
||
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 公开方法
|
||
|
||
/// <summary>
|
||
/// 计算建议叠板数
|
||
/// </summary>
|
||
/// <param name="boardThickness">板厚(mm)</param>
|
||
/// <param name="copperThickness">单张板总铜厚(oz)</param>
|
||
/// <param name="minDrill">最小钻针直径(0 表示钻带中无钻针)</param>
|
||
/// <param name="minSlot">最小槽刀直径(0 表示钻带中无槽刀)</param>
|
||
/// <param name="minEA">最小EA刀直径(0 表示钻带中无EA刀)</param>
|
||
/// <returns>叠板计算结果</returns>
|
||
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<string>();
|
||
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<int>();
|
||
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 输入校验
|
||
|
||
/// <summary>
|
||
/// 校验输入参数,返回 null 表示通过,否则返回中文错误提示
|
||
/// </summary>
|
||
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 板厚行查找
|
||
|
||
/// <summary>
|
||
/// 查找板厚对应的行索引
|
||
/// 行区间按 PRD 定义紧密衔接(如 0.40-0.65 / 0.66-0.85),使用精确比较
|
||
/// </summary>
|
||
/// <param name="boardThickness">板厚</param>
|
||
/// <returns>行索引,-1 表示未找到</returns>
|
||
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 列匹配与查表
|
||
|
||
/// <summary>
|
||
/// 短槽刀/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,+∞)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 槽刀列匹配
|
||
/// 列定义:D=0.50, D=0.55, D=0.60-0.65, D>=0.70
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 钻针列匹配
|
||
/// 列定义:D=0.20-0.225, D=0.25, D=0.275-0.30, D=0.35-0.40, D=0.45, D>=0.50
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通用查表:根据行和列索引读取规则表值
|
||
/// </summary>
|
||
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 铜厚递减逻辑
|
||
|
||
/// <summary>
|
||
/// 套用 16oz 总铜厚递减限制
|
||
/// 每次减1至总铜厚不超过 16oz 或保留1张
|
||
/// </summary>
|
||
private static int ApplyCopperReduction(int candidateCount, double singleCopperOz)
|
||
{
|
||
int count = candidateCount;
|
||
while (count > 1 && singleCopperOz * count > MaxTotalCopperOz)
|
||
{
|
||
count--;
|
||
}
|
||
return count;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|