using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; using System.Text.RegularExpressions; namespace DrillTools { /// /// ROU 成型参数替换:将参数文件中的 S/F/R/Z/H 参数追加到 .rou 文件的刀径定义行。 /// internal static class RouParameterReplacer { private const int Gb2312CodePage = 936; private const double DiameterTolerance = 0.001; /// /// 匹配参数行:T00C<直径>S<参数...> /// 例:T00C1.800S55.0F5.0R25Z0H600 /// private static readonly Regex ParamLinePattern = new( @"^T00C(?\d+(?:\.\d+)?)(?S.+?)\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// /// 匹配 .rou 刀径定义行:T<刀号>C<直径> /// 例:T01C1.8 → prefix="T01C", diameter="1.8" /// private static readonly Regex ToolLinePattern = new( @"^(?T\d+C)(?\d+(?:\.\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// /// 执行参数替换。 /// /// .rou 文件完整路径 /// 参数 .txt 文件完整路径 public static void ReplaceParameters(string rouFilePath, string paramFilePath, out List unmatchedDiameters) { // 1. 读取参数文件,构建直径→参数字符串映射 var paramDict = LoadParameterDictionary(paramFilePath); if (paramDict.Count == 0) throw new InvalidOperationException("参数文件中未找到有效的参数行(格式:T00C<直径>S...)"); // 2. 读取 .rou 文件 string rouContent = CommandTypeFileReader.ReadAllText(rouFilePath); // 3. 执行替换(同时收集未匹配直径) var unmatchedSet = new HashSet(); string replacedContent = ReplaceParametersInContent(rouContent, paramDict, unmatchedSet); // 4. 备份原文件 string backupFilePath = rouFilePath + ".bak"; File.Copy(rouFilePath, backupFilePath, true); // 5. 写回文件 using var writer = new StreamWriter(rouFilePath, false, Encoding.GetEncoding(Gb2312CodePage)); writer.Write(replacedContent); // 6. 输出未匹配直径 unmatchedDiameters = new List(unmatchedSet); } /// /// 从参数文件加载直径→参数字符串(从 S 开始到行尾)的映射。 /// private static Dictionary LoadParameterDictionary(string paramFilePath) { var dict = new Dictionary(); string content = CommandTypeFileReader.ReadAllText(paramFilePath); var lines = DrillTapeLineParser.SplitLines(content); foreach (var line in lines) { string trimmed = line.Text.Trim(); var match = ParamLinePattern.Match(trimmed); if (!match.Success) continue; if (!double.TryParse(match.Groups["diameter"].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double diameter)) continue; // 规范化到3位小数,消除浮点精度差异 diameter = Math.Round(diameter, 3); string paramString = match.Groups["params"].Value; // 如果同一直径出现多次,后面的覆盖前面的 dict[diameter] = paramString; } return dict; } /// /// 在 .rou 文件内容中,将 M48~% 区域内的刀径行末尾追加参数。 /// 使用两遍扫描:第一遍按基准直径分组并从参数字典中分配可用键,第二遍执行替换。 /// private static string ReplaceParametersInContent(string rouContent, Dictionary paramDict, HashSet unmatchedDiameters) { var lines = DrillTapeLineParser.SplitLines(rouContent); // === Pass 1: 收集头部区域所有刀径行,按基准直径分组,分配参数键 === bool inHeader = false; var toolEntries = new List<(int Index, string Prefix, double Diameter)>(); for (int i = 0; i < lines.Count; i++) { string trimmed = lines[i].Text.Trim(); if (!inHeader && trimmed == "M48") { inHeader = true; continue; } if (inHeader && trimmed == "%") { inHeader = false; continue; } if (inHeader) { var match = ToolLinePattern.Match(trimmed); if (match.Success && double.TryParse(match.Groups["diameter"].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double diameter)) { diameter = Math.Round(diameter, 3); string prefix = match.Groups["prefix"].Value; toolEntries.Add((i, prefix, diameter)); } } } // 按基准直径分组,为每组分配显示直径和参数键 // 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) { candidates.Add(kvp.Key); break; } } } // 分配:每个出现的显示直径为 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(); } } }