feat: ROU成型参数替换功能 — 支持 .rou/.rou1/.rou2/.roux 文件从参数 txt 自动补充 S/F/R/Z/H

This commit is contained in:
2026-08-03 18:19:33 +08:00
parent b51a5cecf8
commit 5c9a70c9f0
8 changed files with 409 additions and 51 deletions

View File

@@ -51,6 +51,7 @@ namespace DrillTools
bool canGeneratePpDrillTape = viewModel.CanGeneratePpDrillTape; bool canGeneratePpDrillTape = viewModel.CanGeneratePpDrillTape;
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";
double minDrill = viewModel.MinDrillDiameter; double minDrill = viewModel.MinDrillDiameter;
double minSlot = viewModel.MinSlotDiameter; double minSlot = viewModel.MinSlotDiameter;
double minEA = viewModel.MinEADiameter; double minEA = viewModel.MinEADiameter;
@@ -82,7 +83,8 @@ namespace DrillTools
selectionViewModel, selectionViewModel,
canClearParameters, canClearParameters,
canGeneratePpDrillTape, canGeneratePpDrillTape,
canScaleDrillTape); canScaleDrillTape,
canReplaceRouParameters: isRouFile);
selectionWindow.ShowDialog(); selectionWindow.ShowDialog();
switch (selectionWindow.SelectedAction) switch (selectionWindow.SelectedAction)
@@ -107,6 +109,10 @@ namespace DrillTools
PerformDrillScaling(filePath); PerformDrillScaling(filePath);
Shutdown(); Shutdown();
break; break;
case StartupAction.ReplaceRouParameters:
PerformRouParameterReplace(filePath);
Shutdown();
break;
default: default:
Shutdown(); Shutdown();
break; break;
@@ -222,12 +228,14 @@ namespace DrillTools
string content = CommandTypeFileReader.ReadAllText(filePath); string content = CommandTypeFileReader.ReadAllText(filePath);
string scaledContent = Integration.ExcellonScaler.Scale(content, result.Kx, result.Ky); string scaledContent = Integration.ExcellonScaler.Scale(content, result.Kx, result.Ky);
// 备份原文件 // 在源文件所在目录下新建子文件夹 dr2 x{InputX} y{InputY}
string backupFilePath = filePath + ".bak"; string sourceDir = Path.GetDirectoryName(filePath)!;
File.Copy(filePath, backupFilePath, true); string outputDir = Path.Combine(sourceDir, $"dr2 x{result.InputX} y{result.InputY}");
Directory.CreateDirectory(outputDir);
// 写入缩放后的内容 // 将涨缩后的钻带写入子文件夹,文件名不变
using var writer = new StreamWriter(filePath, false, Encoding.GetEncoding(936)); string outputFilePath = Path.Combine(outputDir, Path.GetFileName(filePath));
using var writer = new StreamWriter(outputFilePath, false, Encoding.GetEncoding(936));
writer.Write(scaledContent); writer.Write(scaledContent);
// 系数记忆:紧跟钻带写回立即落盘,不滞后到完成提示之后。 // 系数记忆:紧跟钻带写回立即落盘,不滞后到完成提示之后。
@@ -242,7 +250,7 @@ namespace DrillTools
memoryWarning = $"\n(系数记忆保存失败:{ex.Message})"; memoryWarning = $"\n(系数记忆保存失败:{ex.Message})";
} }
string completionMessage = $"涨缩完成,原文件已备份为:\n{Path.GetFileName(filePath)}.bak"; string completionMessage = $"涨缩完成,输出至:\n{outputFilePath}";
if (memoryWarning != null) if (memoryWarning != null)
completionMessage += memoryWarning; completionMessage += memoryWarning;
@@ -264,7 +272,7 @@ namespace DrillTools
try try
{ {
string exePath = Process.GetCurrentProcess().MainModule!.FileName!; string exePath = Process.GetCurrentProcess().MainModule!.FileName!;
string[] extensions = { ".drl", ".dr2", ".dr3", ".trg", ".dpin", ".txt" }; string[] extensions = { ".drl", ".dr2", ".dr3", ".trg", ".dpin", ".txt", ".rou", ".rou1", ".rou2", ".roux" };
string menuText = "用DrillTools打开"; string menuText = "用DrillTools打开";
foreach (string ext in extensions) foreach (string ext in extensions)
@@ -295,11 +303,57 @@ namespace DrillTools
// 检查文件扩展名是否为支持的钻带文件格式 // 检查文件扩展名是否为支持的钻带文件格式
string extension = Path.GetExtension(filePath).ToLowerInvariant(); string extension = Path.GetExtension(filePath).ToLowerInvariant();
string[] supportedExtensions = { ".txt", ".drl", ".dr2", ".dr3", ".trg", ".dpin" }; string[] supportedExtensions = { ".txt", ".drl", ".dr2", ".dr3", ".trg", ".dpin", ".rou", ".rou1", ".rou2", ".roux" };
return supportedExtensions.Contains(extension); return supportedExtensions.Contains(extension);
} }
private static void PerformRouParameterReplace(string filePath)
{
try
{
#if DEBUG
string paramDir = @"C:\Users\14244\source\repos\Aoh\DrillTools\ROU-canshu\";
#else
string paramDir = @"\\aht-file-1\AHT-workdatatemp\泰国澳弘的相关说明资料及作业指引\钻锣带\成型\";
#endif
if (!Directory.Exists(paramDir))
{
System.Windows.MessageBox.Show(
$"参数文件目录不存在:\n{paramDir}",
"错误",
MessageBoxButton.OK,
MessageBoxImage.Error);
return;
}
var selectionWindow = new ParameterFileSelectionWindow(paramDir);
selectionWindow.ShowDialog();
if (selectionWindow.SelectedFilePath == null)
return;
RouParameterReplacer.ReplaceParameters(filePath, selectionWindow.SelectedFilePath);
System.Windows.MessageBox.Show(
$"参数替换完成:\n{filePath}\n原文件已备份为 .bak",
"替换完成",
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;

57
DrillTapeLineParser.cs Normal file
View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
namespace DrillTools
{
/// <summary>
/// 钻带文本行结构,保留原始文本和行尾换行符。
/// </summary>
internal readonly record struct DrillTapeLine(string Text, string LineEnding);
/// <summary>
/// 钻带行解析器,将钻带文本按行拆分,保留原始换行符。
/// </summary>
internal static class DrillTapeLineParser
{
private static readonly char[] LineEndChars = { '\r', '\n' };
/// <summary>
/// 将文本内容按行拆分,保留每行的换行符。
/// </summary>
public static List<DrillTapeLine> SplitLines(string content)
{
var lines = new List<DrillTapeLine>();
int index = 0;
while (index < content.Length)
{
int lineEndIndex = content.IndexOfAny(LineEndChars, index);
if (lineEndIndex < 0)
{
lines.Add(new DrillTapeLine(content[index..], string.Empty));
return lines;
}
string text = content[index..lineEndIndex];
string lineEnding;
if (content[lineEndIndex] == '\r' && lineEndIndex + 1 < content.Length && content[lineEndIndex + 1] == '\n')
{
lineEnding = "\r\n";
index = lineEndIndex + 2;
}
else
{
lineEnding = content[lineEndIndex].ToString();
index = lineEndIndex + 1;
}
lines.Add(new DrillTapeLine(text, lineEnding));
}
if (content.Length == 0)
lines.Add(new DrillTapeLine(string.Empty, string.Empty));
return lines;
}
}
}

View File

@@ -9,7 +9,6 @@ namespace DrillTools
internal static class DrillTapeParameterCleaner internal static class DrillTapeParameterCleaner
{ {
private const int Gb2312CodePage = 936; private const int Gb2312CodePage = 936;
private static readonly char[] LineEndChars = { '\r', '\n' };
private static readonly Regex HeaderToolDefinitionStartPattern = new( private static readonly Regex HeaderToolDefinitionStartPattern = new(
@"^T\d+C", @"^T\d+C",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -19,7 +18,7 @@ namespace DrillTools
public static bool CanClearParameters(string drillTapeContent) public static bool CanClearParameters(string drillTapeContent)
{ {
var lines = SplitLines(drillTapeContent); var lines = DrillTapeLineParser.SplitLines(drillTapeContent);
if (lines.Count < 2) if (lines.Count < 2)
return false; return false;
@@ -45,7 +44,7 @@ namespace DrillTools
if (!CanClearParameters(drillTapeContent)) if (!CanClearParameters(drillTapeContent))
throw new InvalidOperationException("钻带文件不符合清空参数条件"); throw new InvalidOperationException("钻带文件不符合清空参数条件");
var lines = SplitLines(drillTapeContent); var lines = DrillTapeLineParser.SplitLines(drillTapeContent);
var builder = new StringBuilder(drillTapeContent.Length); var builder = new StringBuilder(drillTapeContent.Length);
for (int i = 0; i < lines.Count; i++) for (int i = 0; i < lines.Count; i++)
@@ -81,42 +80,6 @@ namespace DrillTools
writer.Write(cleanedContent); writer.Write(cleanedContent);
} }
private static List<DrillTapeLine> SplitLines(string content)
{
var lines = new List<DrillTapeLine>();
int index = 0;
while (index < content.Length)
{
int lineEndIndex = content.IndexOfAny(LineEndChars, index);
if (lineEndIndex < 0)
{
lines.Add(new DrillTapeLine(content[index..], string.Empty));
return lines;
}
string text = content[index..lineEndIndex];
string lineEnding;
if (content[lineEndIndex] == '\r' && lineEndIndex + 1 < content.Length && content[lineEndIndex + 1] == '\n')
{
lineEnding = "\r\n";
index = lineEndIndex + 2;
}
else
{
lineEnding = content[lineEndIndex].ToString();
index = lineEndIndex + 1;
}
lines.Add(new DrillTapeLine(text, lineEnding));
}
if (content.Length == 0)
lines.Add(new DrillTapeLine(string.Empty, string.Empty));
return lines;
}
private static List<DrillTapeLine> GetHeaderToolDefinitionLines(IReadOnlyList<DrillTapeLine> lines) private static List<DrillTapeLine> GetHeaderToolDefinitionLines(IReadOnlyList<DrillTapeLine> lines)
{ {
var toolDefinitionLines = new List<DrillTapeLine>(); var toolDefinitionLines = new List<DrillTapeLine>();
@@ -150,6 +113,5 @@ namespace DrillTools
return Encoding.GetEncoding(Gb2312CodePage); return Encoding.GetEncoding(Gb2312CodePage);
} }
private readonly record struct DrillTapeLine(string Text, string LineEnding);
} }
} }

View File

@@ -0,0 +1,32 @@
<Window x:Class="DrillTools.ParameterFileSelectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="选择参数文件"
Width="420"
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="200"
Margin="0,0,0,15"
DisplayMemberPath="Name" />
<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,66 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
namespace DrillTools
{
/// <summary>
/// 参数文件选择窗口,列出指定目录下所有 .txt 文件供用户选择。
/// </summary>
public partial class ParameterFileSelectionWindow : Window
{
/// <summary>
/// 用户选中的文件完整路径;若取消则为 null。
/// </summary>
public string? SelectedFilePath { get; private set; }
public ParameterFileSelectionWindow(string paramDirectory)
{
InitializeComponent();
if (Directory.Exists(paramDirectory))
{
var files = Directory.GetFiles(paramDirectory, "*.txt")
.Select(f => new FileInfo(f))
.OrderBy(f => f.Name)
.ToList();
FileListBox.ItemsSource = files;
}
OkButton.IsEnabled = false;
FileListBox.SelectionChanged += (_, _) =>
{
OkButton.IsEnabled = FileListBox.SelectedItem != null;
};
// 双击直接确认
FileListBox.MouseDoubleClick += (_, _) =>
{
if (FileListBox.SelectedItem is FileInfo file)
{
SelectedFilePath = file.FullName;
DialogResult = true;
Close();
}
};
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
if (FileListBox.SelectedItem is FileInfo file)
{
SelectedFilePath = file.FullName;
DialogResult = true;
Close();
}
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
}
}

178
RouParameterReplacer.cs Normal file
View File

@@ -0,0 +1,178 @@
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
/// </summary>
private static readonly Regex ToolLinePattern = new(
@"^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)
{
// 1. 读取参数文件,构建直径→参数字符串映射
var paramDict = LoadParameterDictionary(paramFilePath);
if (paramDict.Count == 0)
throw new InvalidOperationException("参数文件中未找到有效的参数行(格式:T00C<直径>S...)");
// 2. 读取 .rou 文件
string rouContent = CommandTypeFileReader.ReadAllText(rouFilePath);
// 3. 执行替换
string replacedContent = ReplaceParametersInContent(rouContent, paramDict);
// 4. 备份原文件
string backupFilePath = rouFilePath + ".bak";
File.Copy(rouFilePath, backupFilePath, true);
// 5. 写回文件
using var writer = new StreamWriter(rouFilePath, false, Encoding.GetEncoding(Gb2312CodePage));
writer.Write(replacedContent);
}
/// <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;
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)
{
var lines = DrillTapeLineParser.SplitLines(rouContent);
var builder = new StringBuilder(rouContent.Length + 1024);
bool inHeader = false;
foreach (var line in lines)
{
string trimmed = line.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);
if (match.Success &&
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;
builder.Append(textWithoutTrailing);
builder.Append(paramString);
builder.Append(trailing);
builder.Append(line.LineEnding);
continue;
}
}
}
// 其他行原样保留
builder.Append(line.Text);
builder.Append(line.LineEnding);
}
return builder.ToString();
}
/// <summary>
/// 按直径查找匹配的参数字符串,允许 0.001 的浮点误差。
/// 返回 null 表示未找到匹配。
/// </summary>
private static string? FindMatchingParameter(double diameter, Dictionary<double, string> 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;
}
}
}

View File

@@ -137,6 +137,7 @@
<Button Width="120" Height="30" Content="调整刀序" Margin="0,0,15,0" Click="AdjustToolOrder_Click"/> <Button Width="120" Height="30" Content="调整刀序" Margin="0,0,15,0" Click="AdjustToolOrder_Click"/>
<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="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

@@ -9,7 +9,8 @@ namespace DrillTools
ExportHoleCount, ExportHoleCount,
ClearParameters, ClearParameters,
GeneratePpDrillTape, GeneratePpDrillTape,
ScaleDrillTape ScaleDrillTape,
ReplaceRouParameters
} }
public partial class StartupSelectionWindow : Window public partial class StartupSelectionWindow : Window
@@ -18,13 +19,14 @@ 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 canScaleDrillTape = false, bool canReplaceRouParameters = false)
{ {
InitializeComponent(); InitializeComponent();
DataContext = viewModel; DataContext = viewModel;
ClearParametersButton.Visibility = canClearParameters ? Visibility.Visible : Visibility.Collapsed; ClearParametersButton.Visibility = canClearParameters ? Visibility.Visible : Visibility.Collapsed;
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;
} }
private void AdjustToolOrder_Click(object sender, RoutedEventArgs e) private void AdjustToolOrder_Click(object sender, RoutedEventArgs e)
@@ -56,5 +58,11 @@ namespace DrillTools
SelectedAction = StartupAction.ScaleDrillTape; SelectedAction = StartupAction.ScaleDrillTape;
Close(); Close();
} }
private void ReplaceRouParameters_Click(object sender, RoutedEventArgs e)
{
SelectedAction = StartupAction.ReplaceRouParameters;
Close();
}
} }
} }