From 03d9b8baa23051ef9dccfc7c70cf9d84f4f8148d Mon Sep 17 00:00:00 2001
From: "Mr.Xia" <1424473282@qq.com>
Date: Sat, 16 May 2026 10:23:55 +0800
Subject: [PATCH] Refactor drill tape reading and reorder logic
---
.claude/settings.local.json | 13 +++
AGENTS.md | 67 ++++++++++++
CLAUDE.md | 67 ++++++++++++
CommandTypeFileReader.cs | 92 ++++++++++++++++
DrillTapeProcessor.cs | 4 +-
MainWindow.xaml.cs | 54 +---------
MainWindowViewModel.cs | 203 +++---------------------------------
7 files changed, 262 insertions(+), 238 deletions(-)
create mode 100644 .claude/settings.local.json
create mode 100644 AGENTS.md
create mode 100644 CLAUDE.md
create mode 100644 CommandTypeFileReader.cs
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..e9df405
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,13 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(git config:*)",
+ "Bash(git log:*)",
+ "Bash(git add:*)",
+ "Bash(git commit:*)",
+ "Bash(git push:*)"
+ ],
+ "deny": [],
+ "ask": []
+ }
+}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..96a85ac
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,67 @@
+# AGENTS.md
+
+This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
+
+## Project Overview
+
+DrillTools (钻带处理工具) is a Windows desktop application for processing PCB drill tape files. It parses drill tape data, displays tool information (tool number, diameter, hole count, parameters), and supports reordering tools with various sorting methods.
+
+## Build & Run
+
+```bash
+dotnet build # Build the project
+dotnet build -c Release # Build in Release mode
+dotnet publish # Publish as single-file exe (win-x86)
+```
+
+Target: .NET 6.0, WPF, x86 only. No external NuGet dependencies.
+
+No test framework is configured. Do not write unit tests for this project.
+
+## Architecture
+
+**MVVM pattern** with three windows:
+
+- **MainWindow** — primary UI for loading, viewing, and reordering drill tape tools
+- **ToolDetailWindow** — displays detailed tool parameters
+- **ToolReorderConfirmationWindow** — confirmation dialog for reorder operations
+
+**Key files:**
+
+| File | Role |
+|---|---|
+| `MainWindowViewModel.cs` (~2400 lines) | Central business logic: tool management, drill tape parsing/generation, sort seed management, reference tape comparison |
+| `DrillTapeProcessor.cs` | Core drill tape parsing engine (`DrillTools.Integration` namespace) |
+| `ToolItem.cs` | Data model for drill tools (implements `INotifyPropertyChanged`) |
+| `SlotHoleCalculator.cs` | Geometry calculations for slot hole counts and positions |
+| `DragDropHelper.cs` | WPF ListView drag-drop with visual insertion indicator |
+
+**Namespaces:** `DrillTools` (UI/models), `DrillTools.Integration` (parsing logic).
+
+## Drill Tape Format
+
+The app parses a specific PCB drill tape format:
+
+```
+M48 ← Header start
+T01C0.799H05000Z+0.000S060.00 ← Tool definitions (Txx=tool#, C=diameter, rest=machine params)
+T02C0.656H01500Z+0.150S070.00
+% ← Header end / data start
+T01 ← Active tool
+X-167525Y013500 ← Hole coordinates
+X-069659Y016450G85X-094159Y016450 ← Slot hole (start G85 end)
+M30 ← End of file
+```
+
+Supported file extensions: `.txt`, `.drl`, `.dr2`, `.dpin`
+
+**Tool types by diameter suffix digit:** 0/8/9=Drill, 1=Slot, 2/6=EA Slot, 3=Dust Slot, 4=Deburr, 5=NonStandard, 7=Special.
+
+## Important Technical Details
+
+- **File encoding**: ANSI/GB2312 (code page 936) — drill tape files use this encoding, not UTF-8
+- **Encrypted file reading**: Uses `cmd.exe /c type` to read drill tape files (some files are "encrypted" and cannot be read directly via standard file I/O)
+- **Slot hole calculation**: Uses CAM350 standard tolerance (0.0127mm) for computing hole counts along slot paths
+- **Backup strategy**: Creates `.bak` files when saving modified drill tape data
+- **Command-line support**: App accepts a file path argument to auto-load a drill tape file on startup
+- **Documentation**: All docs under `Docs/` are in Chinese (Simplified). The UI is also in Chinese.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..8b57453
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,67 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+DrillTools (钻带处理工具) is a Windows desktop application for processing PCB drill tape files. It parses drill tape data, displays tool information (tool number, diameter, hole count, parameters), and supports reordering tools with various sorting methods.
+
+## Build & Run
+
+```bash
+dotnet build # Build the project
+dotnet build -c Release # Build in Release mode
+dotnet publish # Publish as single-file exe (win-x86)
+```
+
+Target: .NET 6.0, WPF, x86 only. No external NuGet dependencies.
+
+No test framework is configured. Do not write unit tests for this project.
+
+## Architecture
+
+**MVVM pattern** with three windows:
+
+- **MainWindow** — primary UI for loading, viewing, and reordering drill tape tools
+- **ToolDetailWindow** — displays detailed tool parameters
+- **ToolReorderConfirmationWindow** — confirmation dialog for reorder operations
+
+**Key files:**
+
+| File | Role |
+|---|---|
+| `MainWindowViewModel.cs` (~2400 lines) | Central business logic: tool management, drill tape parsing/generation, sort seed management, reference tape comparison |
+| `DrillTapeProcessor.cs` | Core drill tape parsing engine (`DrillTools.Integration` namespace) |
+| `ToolItem.cs` | Data model for drill tools (implements `INotifyPropertyChanged`) |
+| `SlotHoleCalculator.cs` | Geometry calculations for slot hole counts and positions |
+| `DragDropHelper.cs` | WPF ListView drag-drop with visual insertion indicator |
+
+**Namespaces:** `DrillTools` (UI/models), `DrillTools.Integration` (parsing logic).
+
+## Drill Tape Format
+
+The app parses a specific PCB drill tape format:
+
+```
+M48 ← Header start
+T01C0.799H05000Z+0.000S060.00 ← Tool definitions (Txx=tool#, C=diameter, rest=machine params)
+T02C0.656H01500Z+0.150S070.00
+% ← Header end / data start
+T01 ← Active tool
+X-167525Y013500 ← Hole coordinates
+X-069659Y016450G85X-094159Y016450 ← Slot hole (start G85 end)
+M30 ← End of file
+```
+
+Supported file extensions: `.txt`, `.drl`, `.dr2`, `.dpin`
+
+**Tool types by diameter suffix digit:** 0/8/9=Drill, 1=Slot, 2/6=EA Slot, 3=Dust Slot, 4=Deburr, 5=NonStandard, 7=Special.
+
+## Important Technical Details
+
+- **File encoding**: ANSI/GB2312 (code page 936) — drill tape files use this encoding, not UTF-8
+- **Encrypted file reading**: Uses `cmd.exe /c type` to read drill tape files (some files are "encrypted" and cannot be read directly via standard file I/O)
+- **Slot hole calculation**: Uses CAM350 standard tolerance (0.0127mm) for computing hole counts along slot paths
+- **Backup strategy**: Creates `.bak` files when saving modified drill tape data
+- **Command-line support**: App accepts a file path argument to auto-load a drill tape file on startup
+- **Documentation**: All docs under `Docs/` are in Chinese (Simplified). The UI is also in Chinese.
diff --git a/CommandTypeFileReader.cs b/CommandTypeFileReader.cs
new file mode 100644
index 0000000..948cafb
--- /dev/null
+++ b/CommandTypeFileReader.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+
+namespace DrillTools
+{
+ internal static class CommandTypeFileReader
+ {
+ private const int Gb2312CodePage = 936;
+
+ private static readonly char[] ForbiddenPathChars =
+ {
+ '&', '|', '<', '>', '^', '%', '!', '(', ')', '@', '"', '\r', '\n'
+ };
+
+ public static string ReadAllText(string filePath)
+ {
+ return ReadAllText(filePath, CreateAnsiEncoding());
+ }
+
+ public static string ReadAllText(string filePath, Encoding encoding)
+ {
+ string normalizedPath = ValidateAndNormalizePath(filePath);
+
+ using var process = new Process
+ {
+ StartInfo = CreateStartInfo(normalizedPath, encoding)
+ };
+
+ if (!process.Start())
+ throw new InvalidOperationException("无法启动文件读取进程");
+
+ var outputTask = process.StandardOutput.ReadToEndAsync();
+ var errorTask = process.StandardError.ReadToEndAsync();
+ process.WaitForExit();
+
+ string output = outputTask.GetAwaiter().GetResult();
+ string error = errorTask.GetAwaiter().GetResult();
+
+ if (process.ExitCode != 0)
+ throw new InvalidOperationException($"读取文件失败,退出代码:{process.ExitCode},错误:{error.Trim()}");
+
+ return output;
+ }
+
+ private static ProcessStartInfo CreateStartInfo(string filePath, Encoding encoding)
+ {
+ return new ProcessStartInfo
+ {
+ FileName = "cmd.exe",
+ Arguments = $"/d /c type \"{filePath}\"",
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ StandardOutputEncoding = encoding,
+ StandardErrorEncoding = encoding
+ };
+ }
+
+ private static string ValidateAndNormalizePath(string filePath)
+ {
+ if (string.IsNullOrWhiteSpace(filePath))
+ throw new ArgumentException("文件路径不能为空", nameof(filePath));
+
+ if (filePath.IndexOfAny(ForbiddenPathChars) >= 0)
+ throw new ArgumentException("文件路径包含不允许的命令行特殊字符", nameof(filePath));
+
+ string normalizedPath = Path.GetFullPath(filePath);
+ if (normalizedPath.IndexOfAny(ForbiddenPathChars) >= 0)
+ throw new ArgumentException("文件路径包含不允许的命令行特殊字符", nameof(filePath));
+
+ if (!File.Exists(normalizedPath))
+ throw new FileNotFoundException($"文件不存在:{normalizedPath}", normalizedPath);
+
+ return normalizedPath;
+ }
+
+ private static Encoding CreateAnsiEncoding()
+ {
+ try
+ {
+ return Encoding.GetEncoding(Gb2312CodePage);
+ }
+ catch
+ {
+ return Encoding.Default;
+ }
+ }
+ }
+}
diff --git a/DrillTapeProcessor.cs b/DrillTapeProcessor.cs
index d6dcff0..35a6b82 100644
--- a/DrillTapeProcessor.cs
+++ b/DrillTapeProcessor.cs
@@ -549,7 +549,7 @@ namespace DrillTools.Integration
/// 刀具编号映射
/// 重新排序的刀具列表
/// 更新后的钻带内容
- private static string UpdateDrillTapeWithNewToolNumbers(string originalDrillTape, Dictionary toolNumberMapping, List reorderedTools)
+ public static string UpdateDrillTapeWithNewToolNumbers(string originalDrillTape, Dictionary toolNumberMapping, List reorderedTools)
{
var lines = originalDrillTape.Split(new[] { '\r', '\n' }, StringSplitOptions.None);
var result = new List();
@@ -709,4 +709,4 @@ namespace DrillTools.Integration
};
}
}
-}
\ No newline at end of file
+}
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
index 031845b..a9614ba 100644
--- a/MainWindow.xaml.cs
+++ b/MainWindow.xaml.cs
@@ -1,6 +1,4 @@
using System;
-using System.Diagnostics;
-using System.Text;
using System.Windows;
using System.Windows.Controls;
@@ -70,22 +68,7 @@ namespace DrillTools
// 异步加载文件以避免阻塞UI
await System.Threading.Tasks.Task.Run(() =>
{
- // 使用与现有代码相同的方式读取文件
- var process = new System.Diagnostics.Process
- {
- StartInfo = new System.Diagnostics.ProcessStartInfo
- {
- FileName = "cmd.exe",
- Arguments = $"/c type \"{filePath}\"",
- RedirectStandardOutput = true,
- UseShellExecute = false,
- CreateNoWindow = true
- }
- };
-
- process.Start();
- string drillTapeContent = process.StandardOutput.ReadToEnd();
- process.WaitForExit();
+ string drillTapeContent = CommandTypeFileReader.ReadAllText(filePath);
// 在UI线程中更新界面
this.Dispatcher.Invoke(() =>
@@ -160,22 +143,7 @@ namespace DrillTools
// 保存原始文件路径
ViewModel.OriginalFilePath = openFileDialog.FileName;
- // 使用 cmd 命令读取加密钻带文件
- var process = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = "cmd.exe",
- Arguments = $"/c type \"{openFileDialog.FileName}\"",
- RedirectStandardOutput = true,
- UseShellExecute = false,
- CreateNoWindow = true
- }
- };
-
- process.Start();
- string drillTapeContent = process.StandardOutput.ReadToEnd();
- process.WaitForExit();
+ string drillTapeContent = CommandTypeFileReader.ReadAllText(openFileDialog.FileName);
ViewModel.LoadToolsFromDrillTape(drillTapeContent);
}
@@ -342,21 +310,7 @@ namespace DrillTools
// 保存原始文件路径
ViewModel.OriginalFilePath = files[0];
- var process = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = "cmd.exe",
- Arguments = $"/c type \"{files[0]}\"",
- RedirectStandardOutput = true,
- UseShellExecute = false,
- CreateNoWindow = true
- }
- };
-
- process.Start();
- string drillTapeContent = process.StandardOutput.ReadToEnd();
- process.WaitForExit();
+ string drillTapeContent = CommandTypeFileReader.ReadAllText(files[0]);
ViewModel.LoadToolsFromDrillTape(drillTapeContent);
}
@@ -369,4 +323,4 @@ namespace DrillTools
base.OnDrop(e);
}
}
-}
\ No newline at end of file
+}
diff --git a/MainWindowViewModel.cs b/MainWindowViewModel.cs
index 536a578..a8ef920 100644
--- a/MainWindowViewModel.cs
+++ b/MainWindowViewModel.cs
@@ -594,19 +594,22 @@ namespace DrillTools
MachineCodeType = t.MachineCodeType
}).ToList();
- //重新排一下 reorderedTools 的刀序Txx
+ //检查是否所有刀序没有变化
+ bool hasOrderChanged = originalTools.Count != reorderedTools.Count;
+ for (int i = 0; i < originalTools.Count; i++)
+ {
+ if (hasOrderChanged)
+ break;
+
+ hasOrderChanged = reorderedTools[i].ToolNumber != originalTools[i].ToolNumber;
+ }
+
+ //重新排一下 reorderedTools 的刀序Txx,用于确认窗口展示
int number = 1;
foreach (var item in reorderedTools)
item.ToolNumber = number++;
- //检查是否所有刀序没有变化
- int checkindex = 1;
- for (int i = 0; i < originalTools.Count; i++)
- {
- if (reorderedTools[i].ToolNumber == originalTools[i].ToolNumber)
- checkindex++;
- }
- if (checkindex == originalTools.Count)
+ if (!hasOrderChanged)
{
if (isApply)
{
@@ -648,7 +651,8 @@ namespace DrillTools
}
// 3. 更新钻带内容中的刀具编号和孔位数据
- string updatedDrillTape = UpdateDrillTapeWithNewToolNumbers(DrillTapeContent, toolNumberMapping);
+ string updatedDrillTape = DrillTapeProcessorExtensions.UpdateDrillTapeWithNewToolNumbers(
+ DrillTapeContent, toolNumberMapping, Tools.ToList());
// 4. 更新钻带内容
DrillTapeContent = updatedDrillTape;
@@ -662,132 +666,6 @@ namespace DrillTools
return updatedDrillTape;
}
- ///
- /// 使用新的刀具编号更新钻带内容
- ///
- /// 原始钻带内容
- /// 刀具编号映射
- /// 更新后的钻带内容
- private string UpdateDrillTapeWithNewToolNumbers(string originalDrillTape, Dictionary toolNumberMapping)
- {
- var lines = originalDrillTape.Split(new[] { '\r', '\n' }, StringSplitOptions.None);
- var result = new List();
-
- // 创建新编号到刀具对象的映射,以便获取对应的坐标数据
- var newNumberToToolMap = new Dictionary();
- foreach (var tool in Tools)
- {
- if (toolNumberMapping.ContainsValue(tool.ToolNumber))
- {
- newNumberToToolMap[tool.ToolNumber] = tool;
- }
- }
-
- // 首先解析原始钻带中的刀具定义行,保存参数信息
- var originalToolDefinitions = new Dictionary();
- var headerLines = new List();
-
- foreach (var line in lines)
- {
- string trimmedLine = line.Trim();
-
- if (trimmedLine == "%")
- {
- //headerLines.Add(line);
- break;
- }
-
- // 处理刀具定义行(如 T01C1.049H05000Z+0.000S060.00F105.0U0700.0)
- if (Regex.IsMatch(trimmedLine, @"^T(\d+)C"))
- {
- var match = Regex.Match(trimmedLine, @"^T(\d+)C(.*)$");
- if (match.Success)
- {
- int originalToolNumber = int.Parse(match.Groups[1].Value);
- originalToolDefinitions[originalToolNumber] = match.Groups[2].Value;
- }
- }
- else
- {
- if (line != "")
- headerLines.Add(line);
- }
- }
-
- // 添加头部信息
- result.AddRange(headerLines);
-
- // 按照新的刀具编号顺序输出刀具定义部分
- var sortedTools = Tools.OrderBy(t => t.ToolNumber).ToList();
- foreach (var tool in sortedTools)
- {
- // 查找原始刀具编号
- int originalToolNumber = -1;
- foreach (var kvp in toolNumberMapping)
- {
- if (kvp.Value == tool.ToolNumber)
- {
- originalToolNumber = kvp.Key;
- break;
- }
- }
-
- if (originalToolNumber != -1 && originalToolDefinitions.ContainsKey(originalToolNumber))
- {
- string toolDef = $"T{tool.ToolNumber:D2}C{originalToolDefinitions[originalToolNumber]}";
- result.Add(toolDef);
- }
- }
-
- // 添加 % 符号
- result.Add("%");
-
- // 处理刀具切换行和坐标数据部分
- // 按新刀具编号顺序输出刀具切换行和对应的坐标数据
- var sortedToolsForData = Tools.OrderBy(t => t.ToolNumber).ToList();
-
- foreach (var tool in sortedToolsForData)
- {
- // 添加刀具切换行
- result.Add($"T{tool.ToolNumber:D2}");
-
- // 添加该刀具对应的所有坐标数据
- // HoleLocations现在应该已经保持了原始顺序
- if (tool.ToolType != ToolType.MachineCode && tool.HoleLocations != null && tool.HoleLocations.Count > 0)
- {
- foreach (var location in tool.HoleLocations)
- {
- result.Add(location);
- }
- }
-
- // 如果是机台码刀具,添加机台码命令和坐标
- if (tool.ToolType == ToolType.MachineCode)
- {
- if (!string.IsNullOrEmpty(tool.MachineCodeCommand) && !string.IsNullOrEmpty(tool.MachineCodeType))
- {
- result.Add($"{tool.MachineCodeCommand},{tool.MachineCodeType},$S $N");
-
- // 添加机台码的坐标数据
- // HoleLocations现在应该已经保持了原始顺序
- if (tool.HoleLocations != null && tool.HoleLocations.Count > 0)
- {
- foreach (var location in tool.HoleLocations)
- {
- result.Add(location);
- }
- }
- }
- }
- }
-
- // 添加结束标记
- result.Add("M30");
-
- // 在M30后添加空行,符合标准钻带文件格式
- return string.Join("\r\n", result) + "\r\n";
- }
-
///
/// 加载示例数据
///
@@ -1428,34 +1306,7 @@ M30";
try
{
- // 使用ANSI编码
- Encoding ansiEncoding;
- try
- {
- ansiEncoding = Encoding.GetEncoding(936); // 936是GB2312的代码页
- }
- catch
- {
- ansiEncoding = Encoding.Default; // 如果获取失败,使用系统默认编码
- }
-
- // 使用 cmd 命令读取-sort.txt文件,参考钻带文件读取方法
- var process = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = "cmd.exe",
- Arguments = $"/c type \"{sortFilePath}\"",
- RedirectStandardOutput = true,
- UseShellExecute = false,
- CreateNoWindow = true,
- StandardOutputEncoding = ansiEncoding
- }
- };
-
- process.Start();
- string fileContent = process.StandardOutput.ReadToEnd();
- process.WaitForExit();
+ string fileContent = CommandTypeFileReader.ReadAllText(sortFilePath);
// 按行分割内容
string[] lines = fileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
@@ -1953,27 +1804,7 @@ M30";
throw new System.IO.FileNotFoundException($"参考钻带文件不存在:{referenceFilePath}");
}
- // 使用与加载钻带文件相同的方式读取文件内容
- var process = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = "cmd.exe",
- Arguments = $"/c type \"{referenceFilePath}\"",
- RedirectStandardOutput = true,
- UseShellExecute = false,
- CreateNoWindow = true
- }
- };
-
- process.Start();
- string drillTapeContent = process.StandardOutput.ReadToEnd();
- process.WaitForExit();
-
- if (process.ExitCode != 0)
- {
- throw new InvalidOperationException($"读取参考钻带文件失败,退出代码:{process.ExitCode}");
- }
+ string drillTapeContent = CommandTypeFileReader.ReadAllText(referenceFilePath);
if (string.IsNullOrWhiteSpace(drillTapeContent))
{
@@ -2401,4 +2232,4 @@ M30";
return (true, "刀具匹配验证通过", warnings);
}
}
-}
\ No newline at end of file
+}