diff --git a/RouParameterReplacer.cs b/RouParameterReplacer.cs index d279ada..1e0632d 100644 --- a/RouParameterReplacer.cs +++ b/RouParameterReplacer.cs @@ -25,10 +25,10 @@ namespace DrillTools /// /// 匹配 .rou 刀径定义行:T<刀号>C<直径> - /// 例:T01C1.8 + /// 例:T01C1.8 → prefix="T01C", diameter="1.8" /// private static readonly Regex ToolLinePattern = new( - @"^T\d+C(?\d+(?:\.\d+)?)", + @"^(?T\d+C)(?\d+(?:\.\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// @@ -84,6 +84,9 @@ namespace DrillTools NumberStyles.Float, CultureInfo.InvariantCulture, out double diameter)) continue; + // 规范化到3位小数,消除浮点精度差异 + diameter = Math.Round(diameter, 3); + string paramString = match.Groups["params"].Value; // 如果同一直径出现多次,后面的覆盖前面的 @@ -95,37 +98,32 @@ namespace DrillTools /// /// 在 .rou 文件内容中,将 M48~% 区域内的刀径行末尾追加参数。 + /// 使用两遍扫描:第一遍按基准直径分组并从参数字典中分配可用键,第二遍执行替换。 /// private static string ReplaceParametersInContent(string rouContent, Dictionary paramDict, HashSet unmatchedDiameters) { var lines = DrillTapeLineParser.SplitLines(rouContent); - var builder = new StringBuilder(rouContent.Length + 1024); + // === Pass 1: 收集头部区域所有刀径行,按基准直径分组,分配参数键 === 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") { inHeader = true; - builder.Append(line.Text); - builder.Append(line.LineEnding); continue; } - // 检测 % 头部结束 if (inHeader && trimmed == "%") { inHeader = false; - builder.Append(line.Text); - builder.Append(line.LineEnding); continue; } - // 在头部区域内匹配刀径定义行 if (inHeader) { var match = ToolLinePattern.Match(trimmed); @@ -133,54 +131,93 @@ namespace DrillTools double.TryParse(match.Groups["diameter"].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double diameter)) { - // 按直径数值匹配参数(允许浮点误差) - string? paramString = FindMatchingParameter(diameter, paramDict); - if (paramString != null) - { - // 保留原始行尾空白 - string textWithoutTrailing = line.Text.TrimEnd(); - int trailingLength = line.Text.Length - textWithoutTrailing.Length; - string trailing = trailingLength > 0 ? line.Text[^trailingLength..] : string.Empty; + diameter = Math.Round(diameter, 3); + string prefix = match.Groups["prefix"].Value; + toolEntries.Add((i, prefix, diameter)); + } + } + } - builder.Append(textWithoutTrailing); - builder.Append(paramString); - builder.Append(trailing); - builder.Append(line.LineEnding); - continue; - } - else + // 按基准直径分组,为每组分配显示直径和参数键 + // lineIndex -> (prefix, displayDiameter, assignedParamKey | null) + var toolMap = new Dictionary(); + + foreach (var group in toolEntries.GroupBy(e => e.Diameter)) + { + 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; } } } - // 其他行原样保留 - builder.Append(line.Text); - builder.Append(line.LineEnding); + // 分配:每个出现的显示直径为 baseDiam + j*0.001(3位小数补零) + // 参数键从候选列表中按序取,超出则为 null(未匹配) + 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(); } - - /// - /// 按直径查找匹配的参数字符串,允许 0.001 的浮点误差。 - /// 返回 null 表示未找到匹配。 - /// - private static string? FindMatchingParameter(double diameter, Dictionary 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; - } } }