Files
AohDrllTools/StackCountCalculator.cs
Mr.Xia fa697c9fd0 feat: 基础信息叠板计算功能
- 新增 StackCountCalculator: 数字化规则表(短槽刀/EA刀10×4、槽刀10×4、钻针10×6),公开Calculate方法,输入校验,查表逻辑,16oz铜厚递减
- 新增 StartupSelectionViewModel: 替换匿名DataContext,保留所有显示属性,新增板厚/铜厚输入、计算结果/明细、CanCalculate
- 新增 NonEmptyToVisibilityConverter
- 修改 StartupSelectionWindow.xaml: 增宽680px,新增3行(输入+结果+明细),计算按钮在基础信息内部
- 修改 StartupSelectionWindow.xaml.cs: 构造函数接收ViewModel,新增计算按钮事件
- 修改 App.xaml.cs: 构建ViewModel实例,CalculateStackMinDiameters独立遍历Tools(含0.749mm,排除机台码)
- 修改 App.xaml: 注册NonEmptyToVisibilityConverter
2026-06-27 16:24:45 +08:00

503 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
/// </summary>
private static int FindEAColumnIndex(double diameter)
{
const double eps = 0.001;
if (Math.Abs(diameter - 0.60) < eps) return 0;
if (Math.Abs(diameter - 0.65) < eps) return 1;
if (Math.Abs(diameter - 0.70) < eps) return 2;
if (diameter >= 0.75 - eps) return 3;
return -1;
}
/// <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
}
}