feat: DRL钻带更改参数功能 — 支持.drl文件从A01-A14参数文件按直径替换H/Z/S/F/U参数段

This commit is contained in:
2026-08-07 07:18:23 +08:00
parent 8a0222e903
commit d234bb06dc
6 changed files with 458 additions and 3 deletions

View File

@@ -53,6 +53,7 @@ namespace DrillTools
string extension = Path.GetExtension(filePath).ToLowerInvariant(); string extension = Path.GetExtension(filePath).ToLowerInvariant();
bool canScaleDrillTape = extension == ".dr2" || extension == ".dpin"; bool canScaleDrillTape = extension == ".dr2" || extension == ".dpin";
bool isRouFile = extension == ".rou" || extension == ".rou1" || extension == ".rou2" || extension == ".roux"; bool isRouFile = extension == ".rou" || extension == ".rou1" || extension == ".rou2" || extension == ".roux";
bool isDrlFile = extension == ".drl";
double minDrill = viewModel.MinDrillDiameter; double minDrill = viewModel.MinDrillDiameter;
double minSlot = viewModel.MinSlotDiameter; double minSlot = viewModel.MinSlotDiameter;
double minEA = viewModel.MinEADiameter; double minEA = viewModel.MinEADiameter;
@@ -85,7 +86,8 @@ namespace DrillTools
canClearParameters, canClearParameters,
canGeneratePpDrillTape, canGeneratePpDrillTape,
canScaleDrillTape, canScaleDrillTape,
canReplaceRouParameters: isRouFile); canReplaceRouParameters: isRouFile,
canReplaceDrlParameters: isDrlFile);
selectionWindow.ShowDialog(); selectionWindow.ShowDialog();
switch (selectionWindow.SelectedAction) switch (selectionWindow.SelectedAction)
@@ -114,6 +116,10 @@ namespace DrillTools
PerformRouParameterReplace(filePath); PerformRouParameterReplace(filePath);
Shutdown(); Shutdown();
break; break;
case StartupAction.ReplaceDrlParameters:
PerformDrlParameterReplace(filePath);
Shutdown();
break;
default: default:
Shutdown(); Shutdown();
break; break;
@@ -363,6 +369,60 @@ namespace DrillTools
} }
} }
private static void PerformDrlParameterReplace(string filePath)
{
try
{
#if DEBUG
string paramDir = @"C:\Users\14244\source\repos\Aoh\DrillTools\DRL-canshu";
#else
string paramDir = @"\\ah-file-2\workdata\Drill\DRILLING\etc";
#endif
if (!Directory.Exists(paramDir))
{
System.Windows.MessageBox.Show(
$"参数文件目录不存在:\n{paramDir}",
"错误",
MessageBoxButton.OK,
MessageBoxImage.Error);
return;
}
var selectionWindow = new DrlParameterSelectionWindow(paramDir);
selectionWindow.ShowDialog();
if (selectionWindow.SelectedFilePath == null)
return;
DrlParameterReplacer.ReplaceParameters(filePath, selectionWindow.SelectedFilePath, out var unmatchedDiameters);
string message = $"参数替换完成:\n{filePath}\n原文件已备份为:\n{Path.GetFileName(filePath)}.bak";
if (unmatchedDiameters.Count > 0)
{
var diaStrings = unmatchedDiameters.Select(d => d.ToString("F3", CultureInfo.InvariantCulture)).ToList();
message += $"\n\n⚠ 以下直径未在参数文件中找到匹配,已保留原行:\n{string.Join(", ", diaStrings)}";
}
System.Windows.MessageBox.Show(
message,
"参数替换完成",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(
$"参数替换失败:\n{ex.Message}",
"错误",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
/// <summary> /// <summary>
/// 从 MainWindowViewModel.Tools 独立遍历计算叠板用最小刀径。 /// 从 MainWindowViewModel.Tools 独立遍历计算叠板用最小刀径。
/// 分类逻辑与 UpdateMinDiameterInfo 一致(按尾号使用标准),不排除 0.749mm; /// 分类逻辑与 UpdateMinDiameterInfo 一致(按尾号使用标准),不排除 0.749mm;

248
DrlParameterReplacer.cs Normal file
View File

@@ -0,0 +1,248 @@
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;
}
}
}

View File

@@ -0,0 +1,32 @@
<Window x:Class="DrillTools.DrlParameterSelectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="选择钻带参数"
Width="500"
SizeToContent="Height"
ResizeMode="NoResize"
Topmost="True"
WindowStartupLocation="CenterScreen">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="请选择要使用的钻带参数文件:" Margin="0,0,0,10" />
<ListBox Grid.Row="1"
Name="FileListBox"
Height="280"
Margin="0,0,0,15"
DisplayMemberPath="Description" />
<StackPanel Grid.Row="2" HorizontalAlignment="Center" Orientation="Horizontal">
<Button Name="OkButton" Width="80" Height="28" Content="确定" Margin="0,0,15,0"
Click="OkButton_Click" IsDefault="True" />
<Button Name="CancelButton" Width="80" Height="28" Content="取消"
Click="CancelButton_Click" IsCancel="True" />
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,105 @@
using System.Collections.Generic;
using System.IO;
using System.Windows;
namespace DrillTools
{
/// <summary>
/// 钻带参数文件选择窗口,列出 A01-A14 文件并以第一行描述文本展示供用户选择。
/// </summary>
public partial class DrlParameterSelectionWindow : Window
{
/// <summary>
/// 用户选中的文件完整路径;若取消则为 null。
/// </summary>
public string? SelectedFilePath { get; private set; }
public DrlParameterSelectionWindow(string paramDirectory)
{
InitializeComponent();
var items = new List<ParamFileItem>();
for (int i = 1; i <= 14; i++)
{
string fileName = $"A{i:D2}";
string filePath = Path.Combine(paramDirectory, fileName);
if (!File.Exists(filePath))
continue;
string description = ReadFirstLineDescription(filePath);
items.Add(new ParamFileItem
{
FileName = fileName,
FilePath = filePath,
Description = $"{fileName} — {description}"
});
}
FileListBox.ItemsSource = items;
OkButton.IsEnabled = false;
FileListBox.SelectionChanged += (_, _) =>
{
OkButton.IsEnabled = FileListBox.SelectedItem != null;
};
FileListBox.MouseDoubleClick += (_, _) =>
{
if (FileListBox.SelectedItem is ParamFileItem item)
{
SelectedFilePath = item.FilePath;
DialogResult = true;
Close();
}
};
}
/// <summary>
/// 读取参数文件的第一行描述文本(去掉前导分号)。
/// </summary>
private static string ReadFirstLineDescription(string filePath)
{
try
{
string content = CommandTypeFileReader.ReadAllText(filePath);
var lines = DrillTapeLineParser.SplitLines(content);
if (lines.Count > 0)
{
string firstLine = lines[0].Text.Trim();
if (firstLine.StartsWith(";"))
return firstLine[1..].Trim();
return firstLine;
}
return "(无法读取)";
}
catch
{
return "(无法读取)";
}
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
if (FileListBox.SelectedItem is ParamFileItem item)
{
SelectedFilePath = item.FilePath;
DialogResult = true;
Close();
}
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private class ParamFileItem
{
public string FileName { get; init; } = string.Empty;
public string FilePath { get; init; } = string.Empty;
public string Description { get; init; } = string.Empty;
}
}
}

View File

@@ -138,6 +138,7 @@
<Button Width="120" Height="30" Content="导出孔数" Margin="0,0,15,0" Click="ExportHoleCount_Click"/> <Button Width="120" Height="30" Content="导出孔数" Margin="0,0,15,0" Click="ExportHoleCount_Click"/>
<Button Name="ClearParametersButton" Width="120" Height="30" Content="清空参数" Margin="0,0,15,0" Click="ClearParameters_Click"/> <Button Name="ClearParametersButton" Width="120" Height="30" Content="清空参数" Margin="0,0,15,0" Click="ClearParameters_Click"/>
<Button Name="ReplaceRouParametersButton" Width="120" Height="30" Content="更改参数" Margin="0,0,15,0" Click="ReplaceRouParameters_Click" Visibility="Collapsed"/> <Button Name="ReplaceRouParametersButton" Width="120" Height="30" Content="更改参数" Margin="0,0,15,0" Click="ReplaceRouParameters_Click" Visibility="Collapsed"/>
<Button Name="ReplaceDrlParametersButton" Width="120" Height="30" Content="更改参数" Margin="0,0,15,0" Click="ReplaceDrlParameters_Click" Visibility="Collapsed"/>
<Button Name="GeneratePpDrillTapeButton" Width="120" Height="30" Content="生成PP钻带" Margin="0,0,15,0" Click="GeneratePpDrillTape_Click"/> <Button Name="GeneratePpDrillTapeButton" Width="120" Height="30" Content="生成PP钻带" Margin="0,0,15,0" Click="GeneratePpDrillTape_Click"/>
<Button Name="ScaleDrillTapeButton" Width="120" Height="30" Content="二钻涨缩" Click="ScaleDrillTape_Click"/> <Button Name="ScaleDrillTapeButton" Width="120" Height="30" Content="二钻涨缩" Click="ScaleDrillTape_Click"/>
</StackPanel> </StackPanel>

View File

@@ -10,7 +10,8 @@ namespace DrillTools
ClearParameters, ClearParameters,
GeneratePpDrillTape, GeneratePpDrillTape,
ScaleDrillTape, ScaleDrillTape,
ReplaceRouParameters ReplaceRouParameters,
ReplaceDrlParameters
} }
public partial class StartupSelectionWindow : Window public partial class StartupSelectionWindow : Window
@@ -19,7 +20,8 @@ namespace DrillTools
public StartupSelectionWindow(StartupSelectionViewModel viewModel, public StartupSelectionWindow(StartupSelectionViewModel viewModel,
bool canClearParameters = false, bool canGeneratePpDrillTape = false, bool canClearParameters = false, bool canGeneratePpDrillTape = false,
bool canScaleDrillTape = false, bool canReplaceRouParameters = false) bool canScaleDrillTape = false, bool canReplaceRouParameters = false,
bool canReplaceDrlParameters = false)
{ {
InitializeComponent(); InitializeComponent();
DataContext = viewModel; DataContext = viewModel;
@@ -27,6 +29,7 @@ namespace DrillTools
GeneratePpDrillTapeButton.Visibility = canGeneratePpDrillTape ? Visibility.Visible : Visibility.Collapsed; GeneratePpDrillTapeButton.Visibility = canGeneratePpDrillTape ? Visibility.Visible : Visibility.Collapsed;
ScaleDrillTapeButton.Visibility = canScaleDrillTape ? Visibility.Visible : Visibility.Collapsed; ScaleDrillTapeButton.Visibility = canScaleDrillTape ? Visibility.Visible : Visibility.Collapsed;
ReplaceRouParametersButton.Visibility = canReplaceRouParameters ? Visibility.Visible : Visibility.Collapsed; ReplaceRouParametersButton.Visibility = canReplaceRouParameters ? Visibility.Visible : Visibility.Collapsed;
ReplaceDrlParametersButton.Visibility = canReplaceDrlParameters ? Visibility.Visible : Visibility.Collapsed;
} }
private void AdjustToolOrder_Click(object sender, RoutedEventArgs e) private void AdjustToolOrder_Click(object sender, RoutedEventArgs e)
@@ -64,5 +67,11 @@ namespace DrillTools
SelectedAction = StartupAction.ReplaceRouParameters; SelectedAction = StartupAction.ReplaceRouParameters;
Close(); Close();
} }
private void ReplaceDrlParameters_Click(object sender, RoutedEventArgs e)
{
SelectedAction = StartupAction.ReplaceDrlParameters;
Close();
}
} }
} }