Files
AohDrllTools/RouParameterReplacer.cs
Mr.Xia 8a0222e903 fix: ROU重复直径去重 — 两遍扫描分配参数槽位 + 输出修改直径文本为三位小数
- 两遍扫描算法:Pass1 收集头部所有刀径并分配参数键,Pass2 重建行并追加参数
- 输出直径文本规范化到三位小数,重复直径依次 +0.001
- 参数字典键和ROU直径均 Math.Round(x,3) 消除浮点精度问题
- 匹配策略:在 [baseDiam, baseDiam+0.01) 内找候选键,按序分配
2026-08-03 20:31:32 +08:00

224 lines
9.1 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.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace DrillTools
{
/// <summary>
/// ROU 成型参数替换:将参数文件中的 S/F/R/Z/H 参数追加到 .rou 文件的刀径定义行。
/// </summary>
internal static class RouParameterReplacer
{
private const int Gb2312CodePage = 936;
private const double DiameterTolerance = 0.001;
/// <summary>
/// 匹配参数行:T00C<直径>S<参数...>
/// 例:T00C1.800S55.0F5.0R25Z0H600
/// </summary>
private static readonly Regex ParamLinePattern = new(
@"^T00C(?<diameter>\d+(?:\.\d+)?)(?<params>S.+?)\s*$",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// 匹配 .rou 刀径定义行:T<刀号>C<直径>
/// 例:T01C1.8 → prefix="T01C", diameter="1.8"
/// </summary>
private static readonly Regex ToolLinePattern = new(
@"^(?<prefix>T\d+C)(?<diameter>\d+(?:\.\d+)?)",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// 执行参数替换。
/// </summary>
/// <param name="rouFilePath">.rou 文件完整路径</param>
/// <param name="paramFilePath">参数 .txt 文件完整路径</param>
public static void ReplaceParameters(string rouFilePath, string paramFilePath, out List<double> 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<double>();
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<double>(unmatchedSet);
}
/// <summary>
/// 从参数文件加载直径→参数字符串(从 S 开始到行尾)的映射。
/// </summary>
private static Dictionary<double, string> LoadParameterDictionary(string paramFilePath)
{
var dict = new Dictionary<double, string>();
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;
}
/// <summary>
/// 在 .rou 文件内容中,将 M48~% 区域内的刀径行末尾追加参数。
/// 使用两遍扫描:第一遍按基准直径分组并从参数字典中分配可用键,第二遍执行替换。
/// </summary>
private static string ReplaceParametersInContent(string rouContent, Dictionary<double, string> paramDict, HashSet<double> 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<int, (string Prefix, double DisplayDiameter, double? ParamKey)>();
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();
}
}
}