Files
AohDrllTools/App.xaml.cs
Mr.Xia 209268ee18 fix: 还原 cmd /c type 读取并移除路径字符黑名单 — 加密钻带文件只能用 cmd type 读取
b75439c 以"命令行是多余的"为由把读取改成 FileStream+StreamReader,是错误前提:
项目存在无法用标准文件 I/O 读取的加密 txt/钻带文件(CLAUDE.md "Encrypted file reading"、
Docs/readme.md "读取加密钻带(使用cmd命令type读取)"、
Docs/刀具列表拖动排序功能说明.md "加密文件:使用 cmd 命令读取"均有记录),
该改动会导致这些文件读取失败。本次回退。

改动:
- 还原 CommandTypeFileReader.cs:cmd.exe /d /c type(ANSI/936 解码),
  失败时退出码与 stderr 保留在异常消息里
- 删除 ForbiddenPathChars 与两处字符校验(本次报错根因):
  ( ) & ^ @ 空格在双引号内对 type 安全,黑名单只会误伤 (D2-f)、(1) 等正常文件名
- 保留"非空路径""文件存在"校验与 ReadAllText(path, encoding) 重载
- 删除 AnsiFileReader.cs,15 处调用点改回
- CLAUDE.md / .trellis/spec/:恢复"加密文件必须用 cmd type"的说明,
  并标注为不可优化掉的硬约束(含 b75439c 反例)

验证:demo_drl 全部 36 个文件与 2335894 实现逐字符一致、行数一致;
含 ( ) & 空格路径实测可读(旧黑名单实现抛"文件路径包含不允许的命令行特殊字符");
cmd 失败消息含退出码与 stderr;dotnet build -p:Platform=x86 0 错误,
警告集与 HEAD 基线完全一致。
2026-09-20 13:28:37 +08:00

442 lines
17 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.Configuration;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Collections.ObjectModel;
namespace DrillTools
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : System.Windows.Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
RegisterShellContextMenu();
string? filePath = null;
if (e.Args.Length > 0)
{
filePath = e.Args[0];
if (!IsValidDrillTapeFile(filePath))
filePath = null;
}
if (filePath == null)
{
ShowMainWindow(null);
return;
}
// 防止选择窗口关闭后触发 OnLastWindowClose 导致应用退出
ShutdownMode = ShutdownMode.OnExplicitShutdown;
// 只读取和解析文件一次,提取所有需要的信息
string content = CommandTypeFileReader.ReadAllText(filePath);
var viewModel = new MainWindowViewModel
{
IsStartupDrillTapeFile = true,
OriginalFilePath = filePath,
ShouldCheckSortFileOnLoad = false
};
viewModel.LoadToolsFromDrillTape(content);
bool canClearParameters = DrillTapeParameterCleaner.CanClearParameters(content);
bool canGeneratePpDrillTape = viewModel.CanGeneratePpDrillTape;
string extension = Path.GetExtension(filePath).ToLowerInvariant();
bool canScaleDrillTape = extension == ".dr2" || extension == ".dpin";
bool isRouFile = extension == ".rou" || extension == ".rou1" || extension == ".rou2" || extension == ".roux";
bool isDrlFile = extension == ".drl";
double minDrill = viewModel.MinDrillDiameter;
double minSlot = viewModel.MinSlotDiameter;
double minEA = viewModel.MinEADiameter;
bool isPpDrillTape = viewModel.IsPpDrillTape;
double ppXSpacing = viewModel.PpXSpacing;
double ppYSpacing = viewModel.PpYSpacing;
bool hasOuter3175Spacing = viewModel.HasOuter3175Spacing;
double outer3175XSpacing = viewModel.Outer3175XSpacing;
double outer3175YSpacing = viewModel.Outer3175YSpacing;
var selectionViewModel = new StartupSelectionViewModel
{
FileName = Path.GetFileNameWithoutExtension(filePath),
MinDrillDiameter = minDrill,
MinSlotDiameter = minSlot,
MinEADiameter = minEA,
IsPpDrillTape = isPpDrillTape,
PpXSpacing = ppXSpacing,
PpYSpacing = ppYSpacing,
HasOuter3175Spacing = hasOuter3175Spacing,
Outer3175XSpacing = outer3175XSpacing,
Outer3175YSpacing = outer3175YSpacing
};
// 叠板计算用的最小刀径独立遍历 Tools 计算,包含 0.749mm
CalculateStackMinDiameters(viewModel.Tools, selectionViewModel);
var selectionWindow = new StartupSelectionWindow(
selectionViewModel,
canClearParameters,
canGeneratePpDrillTape,
canScaleDrillTape,
canReplaceRouParameters: isRouFile,
canReplaceDrlParameters: isDrlFile);
selectionWindow.ShowDialog();
switch (selectionWindow.SelectedAction)
{
case StartupAction.AdjustToolOrder:
ShutdownMode = ShutdownMode.OnLastWindowClose;
ShowMainWindow(filePath);
break;
case StartupAction.ExportHoleCount:
PerformHeadlessExport(filePath);
Shutdown();
break;
case StartupAction.ClearParameters:
PerformParameterCleanup(filePath);
Shutdown();
break;
case StartupAction.GeneratePpDrillTape:
PerformPpDrillTapeGeneration(filePath);
Shutdown();
break;
case StartupAction.ScaleDrillTape:
PerformDrillScaling(filePath);
Shutdown();
break;
case StartupAction.ReplaceRouParameters:
PerformRouParameterReplace(filePath);
Shutdown();
break;
case StartupAction.ReplaceDrlParameters:
PerformDrlParameterReplace(filePath);
Shutdown();
break;
default:
Shutdown();
break;
}
}
private static void ShowMainWindow(string? filePath)
{
try
{
MainWindow mainWindow = new MainWindow(filePath);
mainWindow.Show();
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"创建MainWindow时发生异常:\n{ex.GetType().Name} - {ex.Message}\n\n{ex.StackTrace}",
"启动失败", MessageBoxButton.OK, MessageBoxImage.Error);
throw;
}
}
private static void PerformHeadlessExport(string filePath)
{
try
{
var viewModel = new MainWindowViewModel
{
ShouldCheckSortFileOnLoad = false
};
string content = CommandTypeFileReader.ReadAllText(filePath);
viewModel.OriginalFilePath = filePath;
viewModel.LoadToolsFromDrillTape(content);
if (viewModel.Tools.Count == 0)
{
System.Windows.MessageBox.Show("钻带文件中未找到有效的刀具数据,无法导出孔数报表。",
"提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
viewModel.ExportDrillUsageReport();
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"导出孔数报表失败:\n{ex.Message}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static void PerformPpDrillTapeGeneration(string filePath)
{
try
{
var viewModel = new MainWindowViewModel
{
IsStartupDrillTapeFile = true,
OriginalFilePath = filePath,
ShouldCheckSortFileOnLoad = false
};
string content = CommandTypeFileReader.ReadAllText(filePath);
viewModel.LoadToolsFromDrillTape(content);
string outputFilePath = viewModel.GeneratePpDrillTape();
System.Windows.MessageBox.Show(
$"PP钻带已生成:\n{outputFilePath}",
"生成PP钻带完成",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"生成PP钻带失败:\n{ex.Message}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static void PerformParameterCleanup(string filePath)
{
try
{
DrillTapeParameterCleaner.ClearParametersAndSave(filePath);
System.Windows.MessageBox.Show(
$"参数已清空,原文件已备份为:\n{Path.GetFileName(filePath)}.bak",
"清空参数完成",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"清空参数失败:\n{ex.Message}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static void PerformDrillScaling(string filePath)
{
try
{
var scalingWindow = new DrillScalingWindow();
scalingWindow.ShowDialog();
if (scalingWindow.ScalingResult == null)
return;
var result = scalingWindow.ScalingResult.Value;
string content = CommandTypeFileReader.ReadAllText(filePath);
string scaledContent = Integration.ExcellonScaler.Scale(content, result.Kx, result.Ky);
// 在源文件所在目录下新建子文件夹 dr2 x{InputX} y{InputY}
string sourceDir = Path.GetDirectoryName(filePath)!;
string outputDir = Path.Combine(sourceDir, $"dr2 x{result.InputX} y{result.InputY}");
Directory.CreateDirectory(outputDir);
// 将涨缩后的钻带写入子文件夹,文件名不变
string outputFilePath = Path.Combine(outputDir, Path.GetFileName(filePath));
using var writer = new StreamWriter(outputFilePath, false, Encoding.GetEncoding(936));
writer.Write(scaledContent);
// 系数记忆:紧跟钻带写回立即落盘,不滞后到完成提示之后。
// 属辅助功能,失败不回滚已完成的涨缩,失败原因随完成提示一并告知。
string? memoryWarning = null;
try
{
ScalingProfileStore.Save(result.Mode, result.InputX, result.InputY, Path.GetFileName(filePath));
}
catch (Exception ex)
{
memoryWarning = $"\n(系数记忆保存失败:{ex.Message})";
}
string completionMessage = $"涨缩完成,输出至:\n{outputFilePath}";
if (memoryWarning != null)
completionMessage += memoryWarning;
System.Windows.MessageBox.Show(
completionMessage,
"涨缩完成",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"涨缩失败:\n{ex.Message}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static void RegisterShellContextMenu()
{
try
{
string exePath = Process.GetCurrentProcess().MainModule!.FileName!;
string[] extensions = { ".drl", ".dr2", ".dr3", ".trg", ".dpin", ".txt", ".rou", ".rou1", ".rou2", ".roux" };
string menuText = "用DrillTools打开";
foreach (string ext in extensions)
{
string keyPath = $@"SystemFileAssociations\{ext}\shell\{menuText}\command";
using var key = Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(keyPath);
key.SetValue("", $"\"{exePath}\" \"%1\"");
}
}
catch
{
// 非管理员权限时注册可能失败,静默忽略不影响主功能
}
}
/// <summary>
/// 验证是否为有效的钻带文件
/// </summary>
/// <param name="filePath">文件路径</param>
/// <returns>是否为有效钻带文件</returns>
private static bool IsValidDrillTapeFile(string filePath)
{
if (string.IsNullOrEmpty(filePath))
return false;
if (!File.Exists(filePath))
return false;
// 检查文件扩展名是否为支持的钻带文件格式
string extension = Path.GetExtension(filePath).ToLowerInvariant();
string[] supportedExtensions = { ".txt", ".drl", ".dr2", ".dr3", ".trg", ".dpin", ".rou", ".rou1", ".rou2", ".roux" };
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, 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);
}
}
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>
/// 从 MainWindowViewModel.Tools 独立遍历计算叠板用最小刀径。
/// 分类逻辑与 UpdateMinDiameterInfo 一致(按尾号使用标准),不排除 0.749mm;
/// 特殊固定孔径(3.203 等)不参与叠板计算。
/// </summary>
private static void CalculateStackMinDiameters(
ObservableCollection<ToolItem> tools,
StartupSelectionViewModel selectionViewModel)
{
var (minDrill, minSlot, minEA) = ToolItem.CalculateMinDiameters(tools, exclude749ForDisplay: false);
selectionViewModel.StackCalcMinDrill = minDrill;
selectionViewModel.StackCalcMinSlot = minSlot;
selectionViewModel.StackCalcMinEA = minEA;
}
}
}