249 lines
10 KiB
C#
249 lines
10 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using System.IO;
|
||
using System.Text;
|
||
using System.Text.RegularExpressions;
|
||
|
||
namespace DrillTools
|
||
{
|
||
/// <summary>
|
||
/// DRL 钻带参数替换:将 A01-A14 参数文件中的 H/Z/S/F/U 参数替换到 .drl 文件的刀径定义行。
|
||
/// 与 ROU 不同,DRL 刀径行原本已有参数,此处为整体替换而非追加。
|
||
/// </summary>
|
||
internal static class DrlParameterReplacer
|
||
{
|
||
private const int Gb2312CodePage = 936;
|
||
private const double DiameterTolerance = 0.001;
|
||
|
||
/// <summary>
|
||
/// 匹配参数文件行:T00C<直径>H<参数...>
|
||
/// 例:T00C0.749H02500Z+0.000S105.00F105.0U1000.0
|
||
/// </summary>
|
||
private static readonly Regex ParamLinePattern = new(
|
||
@"^T00C(?<diameter>\d+(?:\.\d+)?)(?<params>H.+)$",
|
||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||
|
||
/// <summary>
|
||
/// 匹配 .drl 刀径定义行:T<刀号>C<直径> + 参数段
|
||
/// 例:T01C0.799H05000Z+0.000S060.00F105.0U0700.0
|
||
/// → prefix="T01C0.799", params="H05000Z+0.000S060.00F105.0U0700.0"
|
||
/// </summary>
|
||
private static readonly Regex ToolLinePattern = new(
|
||
@"^\s*(?<prefix>T\d+C\d+(?:\.\d+)?)(?<params>H.+)$",
|
||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||
|
||
/// <summary>
|
||
/// 匹配 .drl 刀径行直径部分,用于提取直径数值
|
||
/// </summary>
|
||
private static readonly Regex ToolLineDiameterPattern = new(
|
||
@"^T\d+C(?<diameter>\d+(?:\.\d+)?)",
|
||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||
|
||
/// <summary>
|
||
/// 匹配参数文件第一行(描述注释行):;<描述>
|
||
/// </summary>
|
||
private static readonly Regex DescriptionLinePattern = new(
|
||
@"^;(.+)$",
|
||
RegexOptions.Compiled);
|
||
|
||
/// <summary>
|
||
/// 执行参数替换。
|
||
/// </summary>
|
||
/// <param name="drlFilePath">.drl 文件完整路径</param>
|
||
/// <param name="paramFilePath">A01-A14 参数文件完整路径</param>
|
||
/// <param name="unmatchedDiameters">未在参数文件中找到的直径列表</param>
|
||
public static void ReplaceParameters(string drlFilePath, string paramFilePath, out List<double> unmatchedDiameters)
|
||
{
|
||
// 1. 读取参数文件,提取第一行描述和直径→参数字符串映射
|
||
string paramContent = CommandTypeFileReader.ReadAllText(paramFilePath);
|
||
var paramLines = DrillTapeLineParser.SplitLines(paramContent);
|
||
|
||
string paramDescription = ExtractDescription(paramLines);
|
||
if (string.IsNullOrEmpty(paramDescription))
|
||
throw new InvalidOperationException("参数文件第一行未找到有效的描述信息(格式:;<描述>)");
|
||
|
||
var paramDict = LoadParameterDictionary(paramLines);
|
||
|
||
if (paramDict.Count == 0)
|
||
throw new InvalidOperationException("参数文件中未找到有效的参数行(格式:T00C<直径>H...)");
|
||
|
||
// 2. 读取 .drl 文件
|
||
string drlContent = CommandTypeFileReader.ReadAllText(drlFilePath);
|
||
|
||
// 3. 执行替换(同时收集未匹配直径)
|
||
var unmatchedSet = new HashSet<double>();
|
||
string replacedContent = ReplaceParametersInContent(drlContent, paramDict, paramDescription, unmatchedSet);
|
||
|
||
// 4. 备份原文件
|
||
string backupFilePath = drlFilePath + ".bak";
|
||
File.Copy(drlFilePath, backupFilePath, true);
|
||
|
||
// 5. 写回文件
|
||
using var writer = new StreamWriter(drlFilePath, false, Encoding.GetEncoding(Gb2312CodePage));
|
||
writer.Write(replacedContent);
|
||
|
||
unmatchedDiameters = new List<double>(unmatchedSet);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从参数文件行中提取第一行描述(去掉前导分号)。
|
||
/// </summary>
|
||
private static string ExtractDescription(IReadOnlyList<DrillTapeLine> paramLines)
|
||
{
|
||
foreach (var line in paramLines)
|
||
{
|
||
string trimmed = line.Text.Trim();
|
||
var match = DescriptionLinePattern.Match(trimmed);
|
||
if (match.Success)
|
||
return match.Groups[1].Value.Trim();
|
||
}
|
||
|
||
return string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从参数文件加载直径→参数字符串(从 H 开始到行尾)的映射。
|
||
/// </summary>
|
||
private static Dictionary<double, string> LoadParameterDictionary(IReadOnlyList<DrillTapeLine> paramLines)
|
||
{
|
||
var dict = new Dictionary<double, string>();
|
||
|
||
foreach (var line in paramLines)
|
||
{
|
||
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;
|
||
|
||
diameter = Math.Round(diameter, 3);
|
||
|
||
string paramString = match.Groups["params"].Value;
|
||
|
||
// 同一直径后面覆盖前面
|
||
dict[diameter] = paramString;
|
||
}
|
||
|
||
return dict;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在 .drl 文件内容中执行参数替换:
|
||
/// 1. 第2行(注释行)替换为参数文件的描述
|
||
/// 2. M48~% 区域内按直径匹配替换刀径行的参数段
|
||
/// </summary>
|
||
private static string ReplaceParametersInContent(
|
||
string drlContent,
|
||
Dictionary<double, string> paramDict,
|
||
string paramDescription,
|
||
HashSet<double> unmatchedDiameters)
|
||
{
|
||
var lines = DrillTapeLineParser.SplitLines(drlContent);
|
||
|
||
// 找到头部边界
|
||
int headerStart = -1; // M48 所在行
|
||
int headerEnd = -1; // % 所在行
|
||
|
||
for (int i = 0; i < lines.Count; i++)
|
||
{
|
||
string trimmed = lines[i].Text.Trim();
|
||
if (headerStart < 0 && trimmed == "M48")
|
||
headerStart = i;
|
||
else if (headerStart >= 0 && trimmed == "%")
|
||
{
|
||
headerEnd = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (headerStart < 0 || headerEnd < 0)
|
||
throw new InvalidOperationException("DRL 文件格式异常:未找到 M48 或 % 头部标记");
|
||
|
||
var builder = new StringBuilder(drlContent.Length + 1024);
|
||
|
||
for (int i = 0; i < lines.Count; i++)
|
||
{
|
||
if (i == headerStart + 1)
|
||
{
|
||
// 第2行:替换为参数文件的描述注释行
|
||
string trailing = GetTrailingWhitespace(lines[i]);
|
||
builder.Append(';');
|
||
builder.Append(paramDescription);
|
||
builder.Append(trailing);
|
||
builder.Append(lines[i].LineEnding);
|
||
}
|
||
else if (i > headerStart && i < headerEnd)
|
||
{
|
||
// 头部区域(M48 和 % 之间,排除已处理的第2行)
|
||
string trimmed = lines[i].Text.Trim();
|
||
var match = ToolLinePattern.Match(trimmed);
|
||
|
||
if (match.Success)
|
||
{
|
||
string prefix = match.Groups["prefix"].Value;
|
||
string trailing = GetTrailingWhitespace(lines[i]);
|
||
|
||
// 从 prefix 中提取直径
|
||
var diaMatch = ToolLineDiameterPattern.Match(prefix);
|
||
if (diaMatch.Success &&
|
||
double.TryParse(diaMatch.Groups["diameter"].Value,
|
||
NumberStyles.Float, CultureInfo.InvariantCulture, out double diameter))
|
||
{
|
||
double roundedDiameter = Math.Round(diameter, 3);
|
||
|
||
if (paramDict.TryGetValue(roundedDiameter, out string? newParams))
|
||
{
|
||
builder.Append(prefix);
|
||
builder.Append(newParams);
|
||
builder.Append(trailing);
|
||
builder.Append(lines[i].LineEnding);
|
||
}
|
||
else
|
||
{
|
||
// 未匹配:保留原行
|
||
unmatchedDiameters.Add(roundedDiameter);
|
||
builder.Append(lines[i].Text);
|
||
builder.Append(lines[i].LineEnding);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 无法提取直径,保留原行
|
||
builder.Append(lines[i].Text);
|
||
builder.Append(lines[i].LineEnding);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 非刀径行(如其他注释等)原样保留
|
||
builder.Append(lines[i].Text);
|
||
builder.Append(lines[i].LineEnding);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// % 之后或其他区域原样保留
|
||
builder.Append(lines[i].Text);
|
||
builder.Append(lines[i].LineEnding);
|
||
}
|
||
}
|
||
|
||
return builder.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取行尾空白字符。
|
||
/// </summary>
|
||
private static string GetTrailingWhitespace(DrillTapeLine line)
|
||
{
|
||
string original = line.Text;
|
||
int trimmedLength = original.TrimEnd().Length;
|
||
return trimmedLength < original.Length ? original[trimmedLength..] : string.Empty;
|
||
}
|
||
}
|
||
}
|