fix: ROU重复直径去重 — 两遍扫描分配参数槽位 + 输出修改直径文本为三位小数
- 两遍扫描算法:Pass1 收集头部所有刀径并分配参数键,Pass2 重建行并追加参数 - 输出直径文本规范化到三位小数,重复直径依次 +0.001 - 参数字典键和ROU直径均 Math.Round(x,3) 消除浮点精度问题 - 匹配策略:在 [baseDiam, baseDiam+0.01) 内找候选键,按序分配
This commit is contained in:
@@ -25,10 +25,10 @@ namespace DrillTools
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 匹配 .rou 刀径定义行:T<刀号>C<直径>
|
/// 匹配 .rou 刀径定义行:T<刀号>C<直径>
|
||||||
/// 例:T01C1.8
|
/// 例:T01C1.8 → prefix="T01C", diameter="1.8"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Regex ToolLinePattern = new(
|
private static readonly Regex ToolLinePattern = new(
|
||||||
@"^T\d+C(?<diameter>\d+(?:\.\d+)?)",
|
@"^(?<prefix>T\d+C)(?<diameter>\d+(?:\.\d+)?)",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -84,6 +84,9 @@ namespace DrillTools
|
|||||||
NumberStyles.Float, CultureInfo.InvariantCulture, out double diameter))
|
NumberStyles.Float, CultureInfo.InvariantCulture, out double diameter))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// 规范化到3位小数,消除浮点精度差异
|
||||||
|
diameter = Math.Round(diameter, 3);
|
||||||
|
|
||||||
string paramString = match.Groups["params"].Value;
|
string paramString = match.Groups["params"].Value;
|
||||||
|
|
||||||
// 如果同一直径出现多次,后面的覆盖前面的
|
// 如果同一直径出现多次,后面的覆盖前面的
|
||||||
@@ -95,37 +98,32 @@ namespace DrillTools
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 在 .rou 文件内容中,将 M48~% 区域内的刀径行末尾追加参数。
|
/// 在 .rou 文件内容中,将 M48~% 区域内的刀径行末尾追加参数。
|
||||||
|
/// 使用两遍扫描:第一遍按基准直径分组并从参数字典中分配可用键,第二遍执行替换。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static string ReplaceParametersInContent(string rouContent, Dictionary<double, string> paramDict, HashSet<double> unmatchedDiameters)
|
private static string ReplaceParametersInContent(string rouContent, Dictionary<double, string> paramDict, HashSet<double> unmatchedDiameters)
|
||||||
{
|
{
|
||||||
var lines = DrillTapeLineParser.SplitLines(rouContent);
|
var lines = DrillTapeLineParser.SplitLines(rouContent);
|
||||||
var builder = new StringBuilder(rouContent.Length + 1024);
|
|
||||||
|
|
||||||
|
// === Pass 1: 收集头部区域所有刀径行,按基准直径分组,分配参数键 ===
|
||||||
bool inHeader = false;
|
bool inHeader = false;
|
||||||
|
var toolEntries = new List<(int Index, string Prefix, double Diameter)>();
|
||||||
|
|
||||||
foreach (var line in lines)
|
for (int i = 0; i < lines.Count; i++)
|
||||||
{
|
{
|
||||||
string trimmed = line.Text.Trim();
|
string trimmed = lines[i].Text.Trim();
|
||||||
|
|
||||||
// 检测 M48 头部开始
|
|
||||||
if (!inHeader && trimmed == "M48")
|
if (!inHeader && trimmed == "M48")
|
||||||
{
|
{
|
||||||
inHeader = true;
|
inHeader = true;
|
||||||
builder.Append(line.Text);
|
|
||||||
builder.Append(line.LineEnding);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测 % 头部结束
|
|
||||||
if (inHeader && trimmed == "%")
|
if (inHeader && trimmed == "%")
|
||||||
{
|
{
|
||||||
inHeader = false;
|
inHeader = false;
|
||||||
builder.Append(line.Text);
|
|
||||||
builder.Append(line.LineEnding);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在头部区域内匹配刀径定义行
|
|
||||||
if (inHeader)
|
if (inHeader)
|
||||||
{
|
{
|
||||||
var match = ToolLinePattern.Match(trimmed);
|
var match = ToolLinePattern.Match(trimmed);
|
||||||
@@ -133,54 +131,93 @@ namespace DrillTools
|
|||||||
double.TryParse(match.Groups["diameter"].Value,
|
double.TryParse(match.Groups["diameter"].Value,
|
||||||
NumberStyles.Float, CultureInfo.InvariantCulture, out double diameter))
|
NumberStyles.Float, CultureInfo.InvariantCulture, out double diameter))
|
||||||
{
|
{
|
||||||
// 按直径数值匹配参数(允许浮点误差)
|
diameter = Math.Round(diameter, 3);
|
||||||
string? paramString = FindMatchingParameter(diameter, paramDict);
|
string prefix = match.Groups["prefix"].Value;
|
||||||
if (paramString != null)
|
toolEntries.Add((i, prefix, diameter));
|
||||||
{
|
}
|
||||||
// 保留原始行尾空白
|
}
|
||||||
string textWithoutTrailing = line.Text.TrimEnd();
|
}
|
||||||
int trailingLength = line.Text.Length - textWithoutTrailing.Length;
|
|
||||||
string trailing = trailingLength > 0 ? line.Text[^trailingLength..] : string.Empty;
|
|
||||||
|
|
||||||
builder.Append(textWithoutTrailing);
|
// 按基准直径分组,为每组分配显示直径和参数键
|
||||||
builder.Append(paramString);
|
// lineIndex -> (prefix, displayDiameter, assignedParamKey | null)
|
||||||
builder.Append(trailing);
|
var toolMap = new Dictionary<int, (string Prefix, double DisplayDiameter, double? ParamKey)>();
|
||||||
builder.Append(line.LineEnding);
|
|
||||||
continue;
|
foreach (var group in toolEntries.GroupBy(e => e.Diameter))
|
||||||
}
|
{
|
||||||
else
|
double baseDiam = group.Key;
|
||||||
|
var occurrences = group.ToList();
|
||||||
|
|
||||||
|
// 在 [baseDiam, baseDiam + 0.01) 范围内查找候选参数键
|
||||||
|
var candidates = paramDict.Keys
|
||||||
|
.Where(k => k >= baseDiam && k < baseDiam + 0.01)
|
||||||
|
.OrderBy(k => k)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 如果没有候选键,尝试容差回退(仅对首个出现)
|
||||||
|
if (candidates.Count == 0)
|
||||||
|
{
|
||||||
|
foreach (var kvp in paramDict)
|
||||||
|
{
|
||||||
|
if (Math.Abs(kvp.Key - baseDiam) < DiameterTolerance)
|
||||||
{
|
{
|
||||||
unmatchedDiameters.Add(diameter);
|
candidates.Add(kvp.Key);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他行原样保留
|
// 分配:每个出现的显示直径为 baseDiam + j*0.001(3位小数补零)
|
||||||
builder.Append(line.Text);
|
// 参数键从候选列表中按序取,超出则为 null(未匹配)
|
||||||
builder.Append(line.LineEnding);
|
for (int j = 0; j < occurrences.Count; j++)
|
||||||
|
{
|
||||||
|
double displayDiameter = Math.Round(baseDiam + j * 0.001, 3);
|
||||||
|
double? assignedKey = j < candidates.Count ? candidates[j] : null;
|
||||||
|
toolMap[occurrences[j].Index] = (occurrences[j].Prefix, displayDiameter, assignedKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Pass 2: 执行替换(修改直径文本 + 追加参数) ===
|
||||||
|
var builder = new StringBuilder(rouContent.Length + 1024);
|
||||||
|
|
||||||
|
for (int i = 0; i < lines.Count; i++)
|
||||||
|
{
|
||||||
|
if (toolMap.TryGetValue(i, out var toolInfo))
|
||||||
|
{
|
||||||
|
if (toolInfo.ParamKey.HasValue)
|
||||||
|
{
|
||||||
|
string paramString = paramDict[toolInfo.ParamKey.Value];
|
||||||
|
|
||||||
|
// 重建行:prefix + 显示直径 + 参数
|
||||||
|
string displayDiam = toolInfo.DisplayDiameter.ToString("F3", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
// 保留行尾空白
|
||||||
|
string originalLine = lines[i].Text.TrimEnd();
|
||||||
|
int trailingLength = lines[i].Text.Length - originalLine.Length;
|
||||||
|
string trailing = trailingLength > 0 ? lines[i].Text[^trailingLength..] : string.Empty;
|
||||||
|
|
||||||
|
builder.Append(toolInfo.Prefix);
|
||||||
|
builder.Append(displayDiam);
|
||||||
|
builder.Append(paramString);
|
||||||
|
builder.Append(trailing);
|
||||||
|
builder.Append(lines[i].LineEnding);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 未匹配:保留原始行,记录直径
|
||||||
|
unmatchedDiameters.Add(toolInfo.DisplayDiameter);
|
||||||
|
builder.Append(lines[i].Text);
|
||||||
|
builder.Append(lines[i].LineEnding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 非刀径行原样保留
|
||||||
|
builder.Append(lines[i].Text);
|
||||||
|
builder.Append(lines[i].LineEnding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 按直径查找匹配的参数字符串,允许 0.001 的浮点误差。
|
|
||||||
/// 返回 null 表示未找到匹配。
|
|
||||||
/// </summary>
|
|
||||||
private static string? FindMatchingParameter(double diameter, Dictionary<double, string> paramDict)
|
|
||||||
{
|
|
||||||
// 精确匹配优先
|
|
||||||
if (paramDict.TryGetValue(diameter, out string? paramString))
|
|
||||||
return paramString;
|
|
||||||
|
|
||||||
// 浮点容差匹配
|
|
||||||
foreach (var kvp in paramDict)
|
|
||||||
{
|
|
||||||
if (Math.Abs(kvp.Key - diameter) < DiameterTolerance)
|
|
||||||
return kvp.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user