using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using DrillTools.Integration;
using System.Diagnostics;
namespace DrillTools
{
///
/// 主窗口视图模型
///
public class MainWindowViewModel : INotifyPropertyChanged
{
private ObservableCollection _tools = new();
private ObservableCollection _originalTools = new(); // 保存原始刀具顺序
private ToolItem? _selectedTool;
private string _drillTapeContent = string.Empty;
private bool _canMoveUp;
private bool _canMoveDown;
private string _originalFilePath = string.Empty;
private string _fileNameWithoutExtension = string.Empty;
private double _minDrillDiameter;
private double _minSlotDiameter;
private double _minEADiameter;
private bool _isStartupDrillTapeFile;
private bool _canGeneratePpDrillTape;
private bool _shouldCheckSortFileOnLoad = true;
private bool _isPpDrillTape;
private double _ppXSpacing;
private double _ppYSpacing;
private bool _hasOuter3175Spacing;
private double _outer3175XSpacing;
private double _outer3175YSpacing;
private const int SwRestore = 9;
private const int SvsiSelect = 0x1;
private const int SvsiDeselectOthers = 0x4;
private const int SvsiEnsureVisible = 0x8;
private const int SvsiFocused = 0x10;
private const int SelectFileFlags = SvsiSelect | SvsiDeselectOthers | SvsiEnsureVisible | SvsiFocused;
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
///
/// 刀具列表
///
public ObservableCollection Tools
{
get => _tools;
set => SetProperty(ref _tools, value);
}
///
/// 选中的刀具
///
public ToolItem? SelectedTool
{
get => _selectedTool;
set
{
if (SetProperty(ref _selectedTool, value))
{
UpdateMoveButtonsState();
}
}
}
///
/// 钻带内容
///
public string DrillTapeContent
{
get => _drillTapeContent;
set => SetProperty(ref _drillTapeContent, value);
}
///
/// 是否可以上移
///
public bool CanMoveUp
{
get => _canMoveUp;
set => SetProperty(ref _canMoveUp, value);
}
///
/// 是否可以下移
///
public bool CanMoveDown
{
get => _canMoveDown;
set => SetProperty(ref _canMoveDown, value);
}
///
/// 原始文件路径
///
public string OriginalFilePath
{
get => _originalFilePath;
set
{
if (SetProperty(ref _originalFilePath, value))
{
// 当原始文件路径改变时,通知 HasOriginalFile 属性也已更改
OnPropertyChanged(nameof(HasOriginalFile));
// 更新文件名(不包含后缀)
if (!string.IsNullOrEmpty(value))
{
FileNameWithoutExtension = System.IO.Path.GetFileNameWithoutExtension(value);
}
else
{
FileNameWithoutExtension = string.Empty;
}
UpdatePpDrillTapeState();
}
}
}
///
/// 原始刀具顺序集合(按文件中的出现顺序)
///
public ObservableCollection OriginalTools
{
get => _originalTools;
set => SetProperty(ref _originalTools, value);
}
///
/// 是否有原始文件
///
public bool HasOriginalFile => !string.IsNullOrEmpty(OriginalFilePath);
///
/// 当前文件是否来自启动参数
///
public bool IsStartupDrillTapeFile
{
get => _isStartupDrillTapeFile;
set
{
if (SetProperty(ref _isStartupDrillTapeFile, value))
{
UpdatePpDrillTapeState();
}
}
}
///
/// 是否可以生成 PP 钻带
///
public bool CanGeneratePpDrillTape
{
get => _canGeneratePpDrillTape;
private set => SetProperty(ref _canGeneratePpDrillTape, value);
}
///
/// 加载钻带后是否自动检查同目录排序种子文件。
///
public bool ShouldCheckSortFileOnLoad
{
get => _shouldCheckSortFileOnLoad;
set => SetProperty(ref _shouldCheckSortFileOnLoad, value);
}
///
/// 文件名(不包含后缀)
///
public string FileNameWithoutExtension
{
get => _fileNameWithoutExtension;
set => SetProperty(ref _fileNameWithoutExtension, value);
}
///
/// 最小钻咀直径
///
public double MinDrillDiameter
{
get => _minDrillDiameter;
set => SetProperty(ref _minDrillDiameter, value);
}
///
/// 最小槽刀直径
///
public double MinSlotDiameter
{
get => _minSlotDiameter;
set => SetProperty(ref _minSlotDiameter, value);
}
///
/// 最小EA刀直径
///
public double MinEADiameter
{
get => _minEADiameter;
set => SetProperty(ref _minEADiameter, value);
}
///
/// 是否为PP钻带
///
public bool IsPpDrillTape
{
get => _isPpDrillTape;
set => SetProperty(ref _isPpDrillTape, value);
}
///
/// PP钻带X间距(左右孔距离,单位mm)
///
public double PpXSpacing
{
get => _ppXSpacing;
set => SetProperty(ref _ppXSpacing, value);
}
///
/// PP钻带Y间距(上下孔距离,单位mm)
///
public double PpYSpacing
{
get => _ppYSpacing;
set => SetProperty(ref _ppYSpacing, value);
}
///
/// 是否存在3.175外围孔间距信息
///
public bool HasOuter3175Spacing
{
get => _hasOuter3175Spacing;
set => SetProperty(ref _hasOuter3175Spacing, value);
}
///
/// 3.175外围孔X间距(单位mm)
///
public double Outer3175XSpacing
{
get => _outer3175XSpacing;
set => SetProperty(ref _outer3175XSpacing, value);
}
///
/// 3.175外围孔Y间距(单位mm)
///
public double Outer3175YSpacing
{
get => _outer3175YSpacing;
set => SetProperty(ref _outer3175YSpacing, value);
}
///
/// 从钻带内容加载刀具信息
///
/// 钻带内容
public void LoadToolsFromDrillTape(string drillTapeContent)
{
DrillTapeContent = drillTapeContent;
Tools.Clear();
OriginalTools.Clear(); // 清空原始刀具顺序
UpdateOuter3175SpacingInfo();
try
{
// 使用现有的 DrillTapeProcessor 处理钻带数据
var result = DrillTapeProcessor.ProcessDrillTape(drillTapeContent);
if (result.Success)
{
foreach (var toolResult in result.ToolResults)
{
var toolItem = new ToolItem
{
ToolNumber = toolResult.ToolNumber,
Diameter = toolResult.Diameter,
RegularHoles = toolResult.RegularHoles,
SlotHoles = toolResult.SlotHoles,
TotalHoles = toolResult.TotalHoles,
SlotCount = toolResult.SlotCount,
ToolType = toolResult.ToolType,
ToolSuffixType = toolResult.ToolSuffixType,
ToolCategory = toolResult.ToolCategory,
HoleLocations = toolResult.Locations ?? new List()
};
// 如果是机台码,使用DrillTapeProcessor已经解析出的机台码信息
if (toolResult.ToolType == ToolType.MachineCode)
{
toolItem.MachineCodeCommand = toolResult.MachineCodeCommand;
toolItem.MachineCodeType = toolResult.MachineCodeType;
// 添加日志验证机台码信息传递
System.Diagnostics.Debug.WriteLine($"[机台码传递] T{toolResult.ToolNumber:D2}: 命令={toolItem.MachineCodeCommand}, 类型={toolItem.MachineCodeType}");
// 如果 toolResult.Locations 已经包含坐标数据,则使用它
// 否则从原始钻带内容中提取坐标行
if (toolItem.HoleLocations == null || toolItem.HoleLocations.Count == 0)
{
// 从钻带内容中提取机台码坐标行
var toolPattern = $@"%.+?T{toolResult.ToolNumber:D2}(.*?)(?=T\d{{2}}|M30)";
var match = System.Text.RegularExpressions.Regex.Match(DrillTapeContent, toolPattern, System.Text.RegularExpressions.RegexOptions.Singleline);
if (match.Success)
{
string holeSection = match.Groups[1].Value;
// 按行分割孔位数据,保持原始顺序
var lines = holeSection.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
toolItem.HoleLocations = new List();
// 按原始顺序处理坐标行
foreach (var line in lines)
{
var trimmedLine = line.Trim();
if (string.IsNullOrEmpty(trimmedLine)) continue;
// 检查是否是坐标行
var coordinatePattern = @"X([+-]?\d+\.?\d*)Y([+-]?\d+\.?\d*)";
var coordinateMatch = System.Text.RegularExpressions.Regex.Match(trimmedLine, coordinatePattern);
if (coordinateMatch.Success)
{
toolItem.HoleLocations.Add(coordinateMatch.Value);
}
}
System.Diagnostics.Debug.WriteLine($"[机台码坐标] T{toolResult.ToolNumber:D2}: 找到{toolItem.HoleLocations.Count}个坐标");
}
}
}
Tools.Add(toolItem);
// 同时添加到原始刀具顺序集合中
var originalToolItem = new ToolItem
{
ToolNumber = toolResult.ToolNumber,
Diameter = toolResult.Diameter,
RegularHoles = toolResult.RegularHoles,
SlotHoles = toolResult.SlotHoles,
TotalHoles = toolResult.TotalHoles,
SlotCount = toolResult.SlotCount,
ToolType = toolResult.ToolType,
ToolSuffixType = toolResult.ToolSuffixType,
ToolCategory = toolResult.ToolCategory,
HoleLocations = toolResult.Locations ?? new List(),
MachineCodeCommand = toolResult.MachineCodeCommand,
MachineCodeType = toolResult.MachineCodeType
};
OriginalTools.Add(originalToolItem);
}
// 更新按钮状态
UpdateMoveButtonsState();
// 计算并更新最小直径信息
UpdateMinDiameterInfo();
// 计算PP钻带间距
UpdatePpSpacingInfo();
UpdateOuter3175SpacingInfo();
// 启动菜单的预检查和非调整刀序动作会关闭此开关,避免排序提示抢在菜单前弹出。
if (ShouldCheckSortFileOnLoad && !string.IsNullOrEmpty(OriginalFilePath))
{
CheckAndApplySortFile(OriginalFilePath, Tools);
}
UpdatePpDrillTapeState();
}
else
{
System.Windows.MessageBox.Show($"解析钻带失败: {result.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
UpdatePpDrillTapeState();
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"解析钻带时发生异常: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
UpdatePpDrillTapeState();
}
}
///
/// 重新排序刀具
///
/// 原索引
/// 新索引
public void ReorderTools(int oldIndex, int newIndex)
{
if (oldIndex < 0 || oldIndex >= Tools.Count || newIndex < 0 || newIndex >= Tools.Count)
return;
var tool = Tools[oldIndex];
// 检查是否是机台码刀具,如果是则不允许移动
if (tool.ToolType == ToolType.MachineCode)
{
System.Windows.MessageBox.Show("机台码刀具不允许移动位置", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
// 检查目标位置是否是机台码刀具
var targetTool = Tools[newIndex];
if (targetTool.ToolType == ToolType.MachineCode)
{
System.Windows.MessageBox.Show("不能移动到机台码刀具位置", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
Tools.RemoveAt(oldIndex);
Tools.Insert(newIndex, tool);
}
///
/// 上移选中的刀具
///
public void MoveSelectedToolUp()
{
if (SelectedTool == null || !CanMoveUp)
return;
int currentIndex = Tools.IndexOf(SelectedTool);
System.Diagnostics.Debug.WriteLine($"[MoveUp] 开始上移操作,选中项索引: {currentIndex}, 刀具编号: T{SelectedTool.ToolNumber:D2}");
if (currentIndex > 0)
{
// 检查是否是机台码刀具
if (SelectedTool.ToolType == ToolType.MachineCode)
{
System.Windows.MessageBox.Show("机台码刀具不允许移动位置", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
// 检查目标位置是否是机台码刀具
var targetTool = Tools[currentIndex - 1];
if (targetTool.ToolType == ToolType.MachineCode)
{
System.Windows.MessageBox.Show("不能移动到机台码刀具位置", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
// 保存选中的刀具引用
var selectedTool = SelectedTool;
int newIndex = currentIndex - 1;
// 执行上移操作
Tools.RemoveAt(currentIndex);
Tools.Insert(newIndex, selectedTool);
System.Diagnostics.Debug.WriteLine($"[MoveUp] 上移完成,新索引: {newIndex}");
// 使用延迟方法重新选中移动后的项
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
System.Diagnostics.Debug.WriteLine($"[MoveUp] 延迟设置选中项,刀具编号: T{selectedTool.ToolNumber:D2}");
SelectedTool = selectedTool;
UpdateMoveButtonsState();
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
///
/// 下移选中的刀具
///
public void MoveSelectedToolDown()
{
if (SelectedTool == null || !CanMoveDown)
return;
int currentIndex = Tools.IndexOf(SelectedTool);
System.Diagnostics.Debug.WriteLine($"[MoveDown] 开始下移操作,选中项索引: {currentIndex}, 刀具编号: T{SelectedTool.ToolNumber:D2}");
if (currentIndex >= 0 && currentIndex < Tools.Count - 1)
{
// 检查是否是机台码刀具
if (SelectedTool.ToolType == ToolType.MachineCode)
{
System.Windows.MessageBox.Show("机台码刀具不允许移动位置", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
// 检查目标位置是否是机台码刀具
var targetTool = Tools[currentIndex + 1];
if (targetTool.ToolType == ToolType.MachineCode)
{
System.Windows.MessageBox.Show("不能移动到机台码刀具位置", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
// 保存选中的刀具引用
var selectedTool = SelectedTool;
int newIndex = currentIndex + 1;
// 执行下移操作
Tools.RemoveAt(currentIndex);
Tools.Insert(newIndex, selectedTool);
System.Diagnostics.Debug.WriteLine($"[MoveDown] 下移完成,新索引: {newIndex}");
// 使用延迟方法重新选中移动后的项
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
System.Diagnostics.Debug.WriteLine($"[MoveDown] 延迟设置选中项,刀具编号: T{selectedTool.ToolNumber:D2}");
SelectedTool = selectedTool;
UpdateMoveButtonsState();
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
///
/// 更新上移/下移按钮的状态
///
private void UpdateMoveButtonsState()
{
if (SelectedTool == null)
{
CanMoveUp = false;
CanMoveDown = false;
System.Diagnostics.Debug.WriteLine("[UpdateButtons] 没有选中项,禁用所有按钮");
return;
}
int index = Tools.IndexOf(SelectedTool);
CanMoveUp = index > 0;
CanMoveDown = index >= 0 && index < Tools.Count - 1;
System.Diagnostics.Debug.WriteLine($"[UpdateButtons] 选中项索引: {index}, CanMoveUp: {CanMoveUp}, CanMoveDown: {CanMoveDown}");
}
///
/// 保存刀具顺序并应用到钻带处理
///
/// 重新排序后的钻带内容
public string ApplyToolOrderToDrillTape()
{
if (string.IsNullOrEmpty(OriginalFilePath))
{
throw new InvalidOperationException("没有原始文件路径,请先加载钻带文件");
}
if (Tools.Count == 0)
{
throw new InvalidOperationException("没有刀具数据,请先加载钻带文件");
}
try
{
// 1. 使用现有的扩展方法生成重新排序后的钻带内容
string reorderedDrillTape = DrillTapeProcessorExtensions.GenerateReorderedDrillTape(DrillTapeContent, Tools.ToList());
// 2. 改进的备份逻辑
if (File.Exists(OriginalFilePath))
{
string backupFilePath = OriginalFilePath + ".bak";
// 检查备份文件是否已存在
if (File.Exists(backupFilePath))
{
var result = System.Windows.MessageBox.Show(
"备份文件已存在,请选择处理方式:\n\n" +
"是(Y):覆盖现有备份文件\n" +
"否(N):创建带时间戳的新备份文件\n" +
"取消:中止保存操作\n\n" +
"提示:选择'否'可以保留之前的备份历史",
"备份选项",
MessageBoxButton.YesNoCancel,
MessageBoxImage.Question);
switch (result)
{
case MessageBoxResult.Yes:
// 覆盖现有备份文件
File.Copy(OriginalFilePath, backupFilePath, true);
break;
case MessageBoxResult.No:
// 创建带时间戳的新备份文件
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string timestampBackupPath = $"{OriginalFilePath}.{timestamp}.bak";
File.Copy(OriginalFilePath, timestampBackupPath);
break;
case MessageBoxResult.Cancel:
// 用户取消操作
throw new OperationCanceledException("用户取消了保存操作");
}
}
else
{
// 备份文件不存在,直接创建
File.Copy(OriginalFilePath, backupFilePath);
}
}
// 3. 将重新排序后的钻带内容写入原始文件,使用ANSI编码
// 使用StreamWriter写入文件,确保编码正确应用
using (var writer = new StreamWriter(OriginalFilePath, false, CreateGb2312Encoding()))
{
writer.Write(reorderedDrillTape);
writer.Flush(); // 确保所有数据都被写入
}
// 4. 更新当前钻带内容
DrillTapeContent = reorderedDrillTape;
// 5. 生成刀具直径列表文件
GenerateToolDiameterListFile(OriginalFilePath, Tools);
// 6. 打开文件资源管理器并选中文件
OpenFileExplorerAndSelectFile(OriginalFilePath);
return reorderedDrillTape;
}
catch (OperationCanceledException)
{
// 重新抛出取消异常,不包装
throw;
}
catch (Exception ex)
{
throw new InvalidOperationException($"保存钻带文件失败: {ex.Message}", ex);
}
}
///
/// 重排刀序并重新编号
///
/// 是否直接应用到文件
/// 是否跳过确认窗口
/// 重排后的钻带内容
public string ReorderAndRenumberTools(bool isApply = false, bool skipConfirmation = false)
{
if (Tools.Count == 0)
{
throw new InvalidOperationException("没有可重排的刀具");
}
// 保存原始刀具列表(按文件中的原始顺序)
var originalTools = OriginalTools.Select(t => new ToolItem
{
ToolNumber = t.ToolNumber,
Diameter = t.Diameter,
RegularHoles = t.RegularHoles,
SlotHoles = t.SlotHoles,
TotalHoles = t.TotalHoles,
SlotCount = t.SlotCount,
ToolType = t.ToolType,
ToolSuffixType = t.ToolSuffixType,
ToolCategory = t.ToolCategory,
HoleLocations = new List(t.HoleLocations ?? new List()),
MachineCodeCommand = t.MachineCodeCommand,
MachineCodeType = t.MachineCodeType
}).ToList();
// 1. 创建原始刀具编号到新编号的映射
// 创建重排后的刀具列表(基于用户已调整的顺序)
// 注意:Tools 列表已经反映了用户通过拖放操作调整后的顺序
var reorderedTools = new List();
reorderedTools = Tools.Select(t => new ToolItem
{
ToolNumber = t.ToolNumber,
Diameter = t.Diameter,
RegularHoles = t.RegularHoles,
SlotHoles = t.SlotHoles,
TotalHoles = t.TotalHoles,
SlotCount = t.SlotCount,
ToolType = t.ToolType,
ToolSuffixType = t.ToolSuffixType,
ToolCategory = t.ToolCategory,
HoleLocations = new List(t.HoleLocations ?? new List()),
MachineCodeCommand = t.MachineCodeCommand,
MachineCodeType = t.MachineCodeType
}).ToList();
//检查是否所有刀序没有变化
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++;
if (!hasOrderChanged)
{
if (isApply)
{
ApplyToolOrderToDrillTape();
return DrillTapeContent;
}
throw new InvalidOperationException("刀具顺序未发生变化,无需重排");
}
// 显示确认窗口(除非跳过确认)
if (!skipConfirmation)
{
var confirmationWindow = new ToolReorderConfirmationWindow(originalTools, reorderedTools);
confirmationWindow.Owner = System.Windows.Application.Current.MainWindow;
confirmationWindow.ShowDialog();
if (!confirmationWindow.IsConfirmed)
throw new OperationCanceledException("取消刀序重排操作");
}
// 2. 按当前列表顺序重新编号(T01, T02, T03...)
var toolNumberMapping = new Dictionary();
var originalToNewMapping = new Dictionary(); // 保存原始映射关系
int newToolNumber = 1;
foreach (var tool in Tools.ToList())
{
// 机台码刀具不允许重新编号
if (tool.ToolType != ToolType.MachineCode)
{
originalToNewMapping[tool.ToolNumber] = newToolNumber;
toolNumberMapping[tool.ToolNumber] = newToolNumber;
tool.ToolNumber = newToolNumber++;
}
else
{
// 机台码刀具保持原始编号,但也要加入映射
toolNumberMapping[tool.ToolNumber] = tool.ToolNumber;
}
}
// 3. 更新钻带内容中的刀具编号和孔位数据
string updatedDrillTape = DrillTapeProcessorExtensions.UpdateDrillTapeWithNewToolNumbers(
DrillTapeContent, toolNumberMapping, Tools.ToList());
// 4. 更新钻带内容
DrillTapeContent = updatedDrillTape;
// 5. 更新界面显示
OnPropertyChanged(nameof(Tools));
if (isApply)
ApplyToolOrderToDrillTape();
return updatedDrillTape;
}
///
/// 加载示例数据
///
public void LoadSampleData()
{
// 清空原始文件路径,因为这是示例数据
OriginalFilePath = string.Empty;
Tools.Clear();
OriginalTools.Clear(); // 清空原始刀具顺序
// 添加示例刀具数据
Tools.Add(new ToolItem
{
ToolNumber = 1,
Diameter = 1.049, // 尾号9,特殊孔径固定为圆孔
RegularHoles = 7,
SlotHoles = 0,
TotalHoles = 7,
ToolType = ToolType.Regular,
ToolSuffixType = ToolItem.GetToolSuffixType(1.049), // 特殊孔径
ToolCategory = ToolItem.GetToolCategory(ToolItem.GetToolSuffixType(1.049)),
HoleLocations = new List
{
"X-167525Y013500",
"X-167525Y018500",
"X-167525Y023500",
"X167525Y013500",
"X167525Y018500",
"X167525Y023500",
"X099366Y502000"
}
});
Tools.Add(new ToolItem
{
ToolNumber = 2,
Diameter = 1.550, // 尾号0 -> 钻针
RegularHoles = 5,
SlotHoles = 0,
TotalHoles = 5,
ToolType = ToolType.Regular,
ToolSuffixType = ToolItem.GetToolSuffixType(1.550), // 基于孔径计算
ToolCategory = ToolItem.GetToolCategory(ToolItem.GetToolSuffixType(1.550)),
HoleLocations = new List
{
"X-065975Y115250",
"X-085825Y122450",
"X-085825Y124550",
"X-097425Y115250",
"X103093Y502000"
}
});
Tools.Add(new ToolItem
{
ToolNumber = 3,
Diameter = 1.156, // 尾号6 -> EA型槽刀
RegularHoles = 1,
SlotHoles = 528,
TotalHoles = 529,
SlotCount = 6,
ToolType = ToolType.Slot,
ToolSuffixType = ToolItem.GetToolSuffixType(1.156), // 基于孔径计算
ToolCategory = ToolItem.GetToolCategory(ToolItem.GetToolSuffixType(1.156)),
HoleLocations = new List
{
"X-069659Y016450G85X-094159Y016450",
"X-181341Y195550G85X-156841Y195550",
"X-069659Y210450G85X-094159Y210450",
"X-181341Y389550G85X-156841Y389550",
"X-069659Y404450G85X-094159Y404450",
"X-181341Y583550G85X-156841Y583550",
"X162939Y596000"
}
});
Tools.Add(new ToolItem
{
ToolNumber = 4,
Diameter = 1.451, // 尾号1 -> 槽刀
RegularHoles = 57,
SlotHoles = 0,
TotalHoles = 57,
ToolType = ToolType.Regular,
ToolSuffixType = ToolItem.GetToolSuffixType(1.451), // 基于孔径计算
ToolCategory = ToolItem.GetToolCategory(ToolItem.GetToolSuffixType(1.451)),
HoleLocations = new List
{
"X-065975Y115250",
"X-085825Y122450",
"X-085825Y124550",
"X-097425Y115250",
"X103093Y502000"
}
});
Tools.Add(new ToolItem
{
ToolNumber = 5,
Diameter = 1.153, // 尾号3 -> 粉尘刀
RegularHoles = 10,
SlotHoles = 0,
TotalHoles = 10,
ToolType = ToolType.Regular,
ToolSuffixType = ToolItem.GetToolSuffixType(1.153), // 基于孔径计算
ToolCategory = ToolItem.GetToolCategory(ToolItem.GetToolSuffixType(1.153)),
HoleLocations = new List
{
"X-065975Y115250",
"X-085825Y122450",
"X-085825Y124550",
"X-097425Y115250",
"X103093Y502000"
}
});
Tools.Add(new ToolItem
{
ToolNumber = 6,
Diameter = 0.499, // 特殊孔径固定为圆孔(机台码)
RegularHoles = 57,
SlotHoles = 0,
TotalHoles = 57,
ToolType = ToolType.MachineCode,
MachineCodeCommand = "M97",
MachineCodeType = "A*",
ToolSuffixType = ToolItem.GetToolSuffixType(0.499), // 特殊孔径
ToolCategory = ToolItem.GetToolCategory(ToolItem.GetToolSuffixType(0.499)),
HoleLocations = new List { "X-194000Y002000" } // 机台码刀具的坐标数据
});
// 同时添加到原始刀具顺序集合中
foreach (var tool in Tools)
{
var originalToolItem = new ToolItem
{
ToolNumber = tool.ToolNumber,
Diameter = tool.Diameter,
RegularHoles = tool.RegularHoles,
SlotHoles = tool.SlotHoles,
TotalHoles = tool.TotalHoles,
SlotCount = tool.SlotCount,
ToolType = tool.ToolType,
ToolSuffixType = tool.ToolSuffixType,
ToolCategory = tool.ToolCategory,
HoleLocations = new List(tool.HoleLocations ?? new List()),
MachineCodeCommand = tool.MachineCodeCommand,
MachineCodeType = tool.MachineCodeType
};
OriginalTools.Add(originalToolItem);
}
// 更新按钮状态
UpdateMoveButtonsState();
// 计算并更新最小直径信息
UpdateMinDiameterInfo();
// 计算3.175外围孔间距
UpdateOuter3175SpacingInfo();
// 加载示例钻带内容
DrillTapeContent = @"M48
;厚铜板参数-镀膜-EA-250618
T01C1.049H05000Z+0.000S060.00F105.0U0700.0
T02C1.550H01500Z+0.150S070.00F008.0U0800.0
T03C1.156H03000Z-0.200S040.00F030.0U0900.0
T04C1.451H01500Z+0.150S070.00F008.0U0800.0
T05C1.153H01500Z+0.150S070.00F008.0U0800.0
T06C0.499H01500Z+0.150S070.00F008.0U0800.0
%
T01
X-167525Y013500
X-167525Y018500
X-167525Y023500
X167525Y013500
X167525Y018500
X167525Y023500
X099366Y502000
T02
X-065975Y115250
X-085825Y122450
X-085825Y124550
X-097425Y115250
X103093Y502000
T03
X-069659Y016450G85X-094159Y016450
X-181341Y195550G85X-156841Y195550
X-069659Y210450G85X-094159Y210450
X-181341Y389550G85X-156841Y389550
X-069659Y404450G85X-094159Y404450
X-181341Y583550G85X-156841Y583550
X162939Y596000
T04
X-065975Y115250
X-085825Y122450
X-085825Y124550
X-097425Y115250
X103093Y502000
T05
X-065975Y115250
X-085825Y122450
X-085825Y124550
X-097425Y115250
X103093Y502000
T06
M97,A*,$S $N
X-194000Y002000
M30";
}
public event PropertyChangedEventHandler? PropertyChanged;
protected bool SetProperty(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer.Default.Equals(field, value))
return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void VerifyResults(DrillTapeResult result)
{
// CAM350的预期结果
var expectedResults = new[]
{
new { ToolNumber = 1, Diameter = 0.799, ExpectedHoles = 7 },
new { ToolNumber = 2, Diameter = 0.656, ExpectedHoles = 5 },
new { ToolNumber = 3, Diameter = 1.601, ExpectedHoles = 529 },
new { ToolNumber = 4, Diameter = 0.499, ExpectedHoles = 57 }
};
bool allMatch = true;
foreach (var expected in expectedResults)
{
var actual = result.ToolResults.Find(t => t.ToolNumber == expected.ToolNumber);
if (actual != null)
{
bool match = Math.Abs(actual.Diameter - expected.Diameter) < 0.001 &&
actual.TotalHoles == expected.ExpectedHoles;
Console.WriteLine($"T{expected.ToolNumber:D2} ({expected.Diameter:F3}mm): " +
$"预期={expected.ExpectedHoles}, 实际={actual.TotalHoles}, " +
$"{(match ? "✓" : "✗")}");
if (!match)
allMatch = false;
}
else
{
Console.WriteLine($"T{expected.ToolNumber:D2}: 未找到结果 ✗");
allMatch = false;
}
}
Console.WriteLine($"\n总体结果: {(allMatch ? "✓ 所有结果与CAM350一致" : "✗ 存在不一致的结果")}");
}
///
/// 显示刀具详情窗口
///
/// 要显示详情的刀具
public void ShowToolDetail(ToolItem tool)
{
if (tool == null)
return;
try
{
var detailWindow = new ToolDetailWindow(tool);
detailWindow.Owner = System.Windows.Application.Current.MainWindow;
detailWindow.ShowDialog();
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"显示刀具详情失败: {ex.Message}", "错误",
System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
}
}
///
/// 测试重排刀序功能
///
public void TestReorderAndRenumberTools()
{
Console.WriteLine("=== 重排刀序功能测试 ===");
// 1. 加载测试数据
LoadSampleData();
// 2. 记录原始状态
var originalOrder = Tools.Select(t => new
{
t.ToolNumber,
t.Diameter,
LocationCount = t.HoleLocations?.Count ?? 0,
FirstLocation = t.HoleLocations?.FirstOrDefault() ?? ""
}).ToList();
Console.WriteLine("原始刀具顺序:");
foreach (var tool in originalOrder)
{
Console.WriteLine($"T{tool.ToolNumber:D2}({tool.Diameter:F3}): {tool.LocationCount}个坐标, 首个坐标: {tool.FirstLocation}");
}
// 3. 执行重排操作(将T02移到第一位)
if (Tools.Count >= 2)
{
var toolToMove = Tools.FirstOrDefault(t => t.ToolNumber == 2);
if (toolToMove != null && toolToMove.ToolType != ToolType.MachineCode)
{
int oldIndex = Tools.IndexOf(toolToMove);
ReorderTools(oldIndex, 0);
}
}
// 4. 执行重新编号
string reorderedDrillTape = ReorderAndRenumberTools();
// 5. 验证结果
Console.WriteLine("\n重排后刀具顺序:");
var reorderedOrder = Tools.Select(t => new
{
t.ToolNumber,
t.Diameter,
LocationCount = t.HoleLocations?.Count ?? 0,
FirstLocation = t.HoleLocations?.FirstOrDefault() ?? ""
}).ToList();
foreach (var tool in reorderedOrder)
{
Console.WriteLine($"T{tool.ToolNumber:D2}({tool.Diameter:F3}): {tool.LocationCount}个坐标, 首个坐标: {tool.FirstLocation}");
}
// 6. 验证钻带内容
Console.WriteLine("\n重排后的钻带内容:");
Console.WriteLine(reorderedDrillTape);
// 7. 验证坐标跟随
VerifyCoordinateBinding(originalOrder.ToList(), reorderedOrder.ToList());
Console.WriteLine("\n重排刀序功能测试完成!");
}
///
/// 验证坐标绑定关系
///
private void VerifyCoordinateBinding(List originalOrder, List reorderedOrder)
{
Console.WriteLine("\n=== 验证坐标绑定关系 ===");
// 创建直径到坐标的映射
var originalDiameterToLocation = new Dictionary();
foreach (var tool in originalOrder)
{
originalDiameterToLocation[tool.Diameter] = tool.FirstLocation;
}
// 验证重排后的坐标绑定
bool allMatch = true;
foreach (var tool in reorderedOrder)
{
if (originalDiameterToLocation.ContainsKey(tool.Diameter))
{
string originalLocation = originalDiameterToLocation[tool.Diameter];
if (tool.FirstLocation != originalLocation)
{
Console.WriteLine($"✗ 刀具{tool.Diameter:F3}的坐标不匹配: 原始{originalLocation}, 重排后{tool.FirstLocation}");
allMatch = false;
}
else
{
Console.WriteLine($"✓ 刀具{tool.Diameter:F3}的坐标正确跟随: {tool.FirstLocation}");
}
}
}
Console.WriteLine($"\n坐标绑定验证结果: {(allMatch ? "✓ 全部正确" : "✗ 存在问题")}");
}
///
/// 导出 CAM350 风格孔数报表
///
public void ExportDrillUsageReport()
{
if (!HasOriginalFile)
throw new InvalidOperationException("没有原始文件路径,请先加载钻带文件");
if (Tools.Count == 0)
throw new InvalidOperationException("没有可导出的刀具数据,请先加载钻带文件");
string outputFilePath = OriginalFilePath + ".rpt";
ConfirmOverwriteReport(outputFilePath);
string report = DrillUsageReportExporter.GenerateReport(
Tools.ToList(),
FileNameWithoutExtension,
DateTime.Now);
File.WriteAllText(outputFilePath, report, CreateGb2312Encoding());
OpenFileExplorerAndSelectFile(outputFilePath);
}
///
/// 生成 PP 钻带文件
///
/// 生成的文件路径
public string GeneratePpDrillTape()
{
var sourceTool = GetPpSourceTool();
if (sourceTool == null)
throw new InvalidOperationException("当前钻带不满足生成 PP 钻带条件");
var locations = sourceTool.HoleLocations
.Where(location => !string.IsNullOrWhiteSpace(location))
.Take(2)
.Select(location => location.Trim())
.ToList();
if (locations.Count < 2)
throw new InvalidOperationException("3.203 刀具坐标不足,无法生成 PP 钻带");
string outputFilePath = GetPpDrillTapeOutputPath();
var ppDrillTape = new StringBuilder();
ppDrillTape.AppendLine("M48");
ppDrillTape.AppendLine("T01C4.");
ppDrillTape.AppendLine("%");
ppDrillTape.AppendLine("T01");
ppDrillTape.AppendLine(locations[0]);
ppDrillTape.AppendLine(locations[1]);
ppDrillTape.AppendLine("M30");
File.WriteAllText(outputFilePath, ppDrillTape.ToString(), CreateGb2312Encoding());
OpenFileExplorerAndSelectFile(outputFilePath);
return outputFilePath;
}
private void UpdatePpDrillTapeState()
{
CanGeneratePpDrillTape = GetPpSourceTool() != null;
}
private ToolItem? GetPpSourceTool()
{
if (!IsStartupDrillTapeFile || !HasOriginalFile || OriginalTools.Count < 2)
return null;
var sourceTool = OriginalTools[OriginalTools.Count - 2];
if (Math.Abs(sourceTool.Diameter - 3.203) >= 0.001)
return null;
if (sourceTool.TotalHoles != 3)
return null;
if (sourceTool.HoleLocations == null || sourceTool.HoleLocations.Count < 2)
return null;
return sourceTool;
}
private string GetPpDrillTapeOutputPath()
{
string directory = Path.GetDirectoryName(OriginalFilePath) ?? string.Empty;
string partNumber = ExtractPpPartNumber(FileNameWithoutExtension);
return Path.Combine(directory, $"{partNumber}-pp.drl");
}
private static string ExtractPpPartNumber(string fileNameWithoutExtension)
{
var parts = fileNameWithoutExtension
.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries)
.Where(part => !part.Equals("cs", StringComparison.OrdinalIgnoreCase)
&& !part.Equals("jp", StringComparison.OrdinalIgnoreCase))
.ToList();
return parts.Count > 0 ? string.Join("-", parts) : fileNameWithoutExtension;
}
private static void ConfirmOverwriteReport(string outputFilePath)
{
if (!File.Exists(outputFilePath))
return;
var result = System.Windows.MessageBox.Show(
$"孔数报表文件已存在,是否覆盖?\n\n{outputFilePath}",
"文件已存在",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
if (result != MessageBoxResult.Yes)
throw new OperationCanceledException("用户取消了导出操作");
}
private static Encoding CreateGb2312Encoding()
{
return Encoding.GetEncoding(936);
}
///
/// 打开文件资源管理器并选中指定文件
///
/// 要选中的文件路径
private void OpenFileExplorerAndSelectFile(string filePath)
{
try
{
if (!File.Exists(filePath))
{
System.Windows.MessageBox.Show($"文件不存在: {filePath}", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
string fullFilePath = Path.GetFullPath(filePath);
if (TrySelectFileInOpenExplorerWindow(fullFilePath))
return;
OpenNewExplorerWindowAndSelectFile(fullFilePath);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"打开文件资源管理器失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static bool TrySelectFileInOpenExplorerWindow(string filePath)
{
string? directory = Path.GetDirectoryName(filePath);
if (string.IsNullOrWhiteSpace(directory))
return false;
string normalizedDirectory = NormalizeDirectoryPath(directory);
Type? shellType = Type.GetTypeFromProgID("Shell.Application");
if (shellType == null)
return false;
object? shell = null;
try
{
shell = Activator.CreateInstance(shellType);
if (shell == null)
return false;
dynamic shellApplication = shell;
foreach (dynamic window in shellApplication.Windows())
{
if (!IsExplorerWindow(window))
continue;
string? windowDirectory = GetExplorerWindowDirectory(window);
if (string.IsNullOrWhiteSpace(windowDirectory)
|| !string.Equals(NormalizeDirectoryPath(windowDirectory), normalizedDirectory, StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (SelectFileInExplorerWindow(window, filePath))
return true;
}
}
catch
{
return false;
}
finally
{
if (shell != null && Marshal.IsComObject(shell))
Marshal.FinalReleaseComObject(shell);
}
return false;
}
private static bool IsExplorerWindow(dynamic window)
{
try
{
string fullName = Convert.ToString(window.FullName) ?? string.Empty;
return string.Equals(Path.GetFileName(fullName), "explorer.exe", StringComparison.OrdinalIgnoreCase);
}
catch
{
return false;
}
}
private static string? GetExplorerWindowDirectory(dynamic window)
{
try
{
string path = Convert.ToString(window.Document.Folder.Self.Path) ?? string.Empty;
if (!string.IsNullOrWhiteSpace(path))
return path;
}
catch
{
// 回退到 LocationURL,兼容部分 Explorer 窗口取不到 Folder.Self.Path 的情况。
}
try
{
string locationUrl = Convert.ToString(window.LocationURL) ?? string.Empty;
if (Uri.TryCreate(locationUrl, UriKind.Absolute, out Uri? uri) && uri.IsFile)
return uri.LocalPath;
}
catch
{
return null;
}
return null;
}
private static bool SelectFileInExplorerWindow(dynamic window, string filePath)
{
try
{
dynamic document = window.Document;
dynamic fileItem = document.Folder.ParseName(Path.GetFileName(filePath));
if (fileItem == null)
{
document.Refresh();
fileItem = document.Folder.ParseName(Path.GetFileName(filePath));
}
if (fileItem == null)
return false;
document.SelectItem(fileItem, SelectFileFlags);
IntPtr handle = new IntPtr(Convert.ToInt64(window.HWND));
ShowWindow(handle, SwRestore);
SetForegroundWindow(handle);
return true;
}
catch
{
return false;
}
}
private static string NormalizeDirectoryPath(string path)
{
return Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
private static void OpenNewExplorerWindowAndSelectFile(string filePath)
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = $"/select,\"{filePath}\"",
UseShellExecute = true
};
Process.Start(startInfo);
}
///
/// 生成刀具直径列表文件
///
/// 原始文件路径
/// 刀具列表
private void GenerateToolDiameterListFile(string originalFilePath, ObservableCollection tools)
{
try
{
// 获取原文件的目录和文件名(不含扩展名)
string directory = Path.GetDirectoryName(originalFilePath) ?? "";
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(originalFilePath);
string outputFilePath = Path.Combine(directory, $"{fileNameWithoutExt}-sort.txt");
// 按刀具编号排序获取最终顺序
var sortedTools = tools.OrderBy(t => t.ToolNumber).ToList();
// 生成刀具直径列表内容
var diameterList = new StringBuilder();
foreach (var tool in sortedTools)
{
diameterList.AppendLine($"{tool.Diameter:F3}");
}
// 使用ANSI编码写入文件
File.WriteAllText(outputFilePath, diameterList.ToString(), CreateGb2312Encoding());
// 记录日志
System.Diagnostics.Debug.WriteLine($"刀具直径列表文件已生成: {outputFilePath}");
}
catch (Exception ex)
{
// 记录错误但不影响主要功能
System.Diagnostics.Debug.WriteLine($"生成刀具直径列表文件失败: {ex.Message}");
}
}
///
/// 生成通用排序种子文件
///
/// 刀具列表
public void GenerateGeneralSortSeedFile(ObservableCollection tools)
{
if (tools.Count == 0)
{
throw new InvalidOperationException("没有刀具数据,无法生成排序种子文件");
}
try
{
// 获取原文件的目录
string directory = Path.GetDirectoryName(OriginalFilePath) ?? "";
string outputFilePath = Path.Combine(directory, "General_sort.txt");
// 检查文件是否已存在
if (File.Exists(outputFilePath))
{
var result = System.Windows.MessageBox.Show(
"General_sort.txt文件已存在,是否覆盖?\n\n" +
"是(Y):覆盖现有文件\n" +
"否(N):取消操作",
"文件已存在",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
if (result != MessageBoxResult.Yes)
{
throw new OperationCanceledException("用户取消了覆盖操作");
}
}
// 按刀具编号排序获取最终顺序
var sortedTools = tools.OrderBy(t => t.ToolNumber).ToList();
// 生成刀具直径列表内容
var diameterList = new StringBuilder();
foreach (var tool in sortedTools)
{
diameterList.AppendLine($"{tool.Diameter:F3}");
}
// 使用ANSI编码写入文件
File.WriteAllText(outputFilePath, diameterList.ToString(), CreateGb2312Encoding());
// 记录日志
System.Diagnostics.Debug.WriteLine($"通用排序种子文件已生成: {outputFilePath}");
// 打开文件资源管理器并选中文件
OpenFileExplorerAndSelectFile(outputFilePath);
}
catch (OperationCanceledException)
{
// 重新抛出取消异常,不包装
throw;
}
catch (Exception ex)
{
throw new InvalidOperationException($"生成通用排序种子文件失败: {ex.Message}", ex);
}
}
///
/// 检查并应用-sort.txt文件中的刀具排序
///
/// 原始文件路径
/// 当前刀具列表
private void CheckAndApplySortFile(string originalFilePath, ObservableCollection tools)
{
try
{
// 获取-sort.txt文件路径
string directory = Path.GetDirectoryName(originalFilePath) ?? "";
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(originalFilePath);
string sortFilePath = Path.Combine(directory, $"{fileNameWithoutExt}-sort.txt");
// 获取General_sort.txt文件路径
string generalSortFilePath = Path.Combine(directory, "General_sort.txt");
// 检查是否存在排序种子文件
bool hasSpecificSortFile = File.Exists(sortFilePath);
bool hasGeneralSortFile = File.Exists(generalSortFilePath);
if (!hasSpecificSortFile && !hasGeneralSortFile)
return;
// 优先使用特定文件名的种子,如果两者都存在
string selectedFilePath = "";
string selectedFileName = "";
List sortDiameters = new List();
if (hasSpecificSortFile)
{
selectedFilePath = sortFilePath;
selectedFileName = Path.GetFileName(sortFilePath);
sortDiameters = ReadSortFile(sortFilePath);
}
else if (hasGeneralSortFile)
{
selectedFilePath = generalSortFilePath;
selectedFileName = Path.GetFileName(generalSortFilePath);
sortDiameters = ReadSortFile(generalSortFilePath);
}
// 检查是否成功读取到直径数据
if (sortDiameters.Count == 0)
return;
// 检查直径数量是否一致
if (sortDiameters.Count != tools.Count)
return;
// 检查排序种子与当前刀序是否完全一致,一致则无需提示
var currentDiameters = tools.Select(t => t.Diameter).ToList();
if (currentDiameters.SequenceEqual(sortDiameters))
return;
// 构建提示消息
string message = hasSpecificSortFile && hasGeneralSortFile
? $"检测到两个排序种子文件:\n" +
$"1. 特定文件种子:{Path.GetFileName(sortFilePath)}\n" +
$"2. 通用排序种子:{Path.GetFileName(generalSortFilePath)}\n\n" +
$"系统将优先使用特定文件种子:{selectedFileName}\n\n" +
$"是否按该记录文件进行排序?\n\n" +
"是(Y):按记录文件排序\n" +
"否(N):保持当前顺序"
: $"检测到排序种子文件:{selectedFileName}\n\n" +
$"是否按该记录文件进行排序?\n\n" +
"是(Y):按记录文件排序\n" +
"否(N):保持当前顺序";
// 提示用户是否应用排序
var result = System.Windows.MessageBox.Show(
message,
"刀具排序提示",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
// 按照排序文件中的直径顺序重新排列刀具
SortToolsByDiameterList(tools, sortDiameters);
// 执行完整的重排刀序流程,包括重新编号和更新钻带内容
try
{
string reorderedDrillTape = ReorderAndRenumberTools();
System.Diagnostics.Debug.WriteLine($"已按{selectedFileName}文件重新排序刀具并更新钻带内容");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"重排刀序失败: {ex.Message}");
// 即使重排失败,也保持列表的排序
OnPropertyChanged(nameof(Tools));
}
}
}
catch (Exception ex)
{
// 记录错误但不影响主要功能
System.Diagnostics.Debug.WriteLine($"检查排序种子文件失败: {ex.Message}");
}
}
///
/// 读取-sort.txt文件内容并解析直径列表
///
/// -sort.txt文件路径
/// 直径列表
private List ReadSortFile(string sortFilePath)
{
var diameters = new List();
try
{
string fileContent = CommandTypeFileReader.ReadAllText(sortFilePath);
// 按行分割内容
string[] lines = fileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines)
{
string trimmedLine = line.Trim();
if (!string.IsNullOrEmpty(trimmedLine))
{
if (double.TryParse(trimmedLine, out double diameter))
{
diameters.Add(diameter);
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取-sort.txt文件失败: {ex.Message}");
}
return diameters;
}
///
/// 根据直径列表排序刀具
///
/// 刀具列表
/// 直径列表(目标顺序)
private void SortToolsByDiameterList(ObservableCollection tools, List diameterList)
{
// 创建直径到刀具的映射
var diameterToTools = new Dictionary>();
foreach (var tool in tools)
{
double roundedDiameter = Math.Round(tool.Diameter, 3);
if (!diameterToTools.ContainsKey(roundedDiameter))
{
diameterToTools[roundedDiameter] = new List();
}
diameterToTools[roundedDiameter].Add(tool);
}
// 按照直径列表的顺序重新排列刀具
var sortedTools = new List();
foreach (double diameter in diameterList)
{
double roundedDiameter = Math.Round(diameter, 3);
if (diameterToTools.ContainsKey(roundedDiameter))
{
sortedTools.AddRange(diameterToTools[roundedDiameter]);
}
}
// 清空原列表并添加排序后的刀具
tools.Clear();
foreach (var tool in sortedTools)
{
tools.Add(tool);
}
}
///
/// 更新最小直径信息
///
private void UpdateMinDiameterInfo()
{
// 初始化为最大值,以便找到最小值
MinDrillDiameter = double.MaxValue;
MinSlotDiameter = double.MaxValue;
MinEADiameter = double.MaxValue;
// 添加调试日志
System.Diagnostics.Debug.WriteLine("=== 开始计算最小直径信息 ===");
foreach (var tool in Tools)
{
// 添加详细的刀具信息日志
System.Diagnostics.Debug.WriteLine($"[刀具分析] T{tool.ToolNumber:D2}: 直径={tool.Diameter:F3}, 类型={tool.ToolType}, 尾号类型={tool.ToolSuffixType}, 大类={tool.ToolCategory}");
// 根据刀具类型分类计算最小直径
if (tool.ToolType == ToolType.Regular)
{
// 普通钻咀
if (tool.Diameter < MinDrillDiameter)
MinDrillDiameter = tool.Diameter;
System.Diagnostics.Debug.WriteLine($"[钻咀] 当前最小钻咀直径: {MinDrillDiameter:F3}");
}
else if (tool.ToolType == ToolType.Slot)
{
// 槽刀 - 修复:只有真正的槽刀(非EA型槽刀)才计入最小槽刀直径
// EA型槽刀不应该计入最小槽刀直径的计算
if (tool.ToolSuffixType != ToolSuffixType.EASlot && tool.ToolSuffixType != ToolSuffixType.EASlot2)
{
if (tool.Diameter < MinSlotDiameter)
MinSlotDiameter = tool.Diameter;
System.Diagnostics.Debug.WriteLine($"[槽刀] T{tool.ToolNumber:D2}是真正的槽刀,当前最小槽刀直径: {MinSlotDiameter:F3}");
}
else
{
System.Diagnostics.Debug.WriteLine($"[EA刀] T{tool.ToolNumber:D2}是EA型槽刀,不计入最小槽刀直径计算");
}
// 检查是否是EA型槽刀(尾号为2或6)
if ((tool.ToolSuffixType == ToolSuffixType.EASlot || tool.ToolSuffixType == ToolSuffixType.EASlot2)
&& tool.Diameter < MinEADiameter)
MinEADiameter = tool.Diameter;
// 特别标记EA型槽刀
if (tool.ToolSuffixType == ToolSuffixType.EASlot || tool.ToolSuffixType == ToolSuffixType.EASlot2)
{
System.Diagnostics.Debug.WriteLine($"[EA刀] T{tool.ToolNumber:D2}是EA型槽刀,直径={tool.Diameter:F3}");
}
}
}
// 如果没有找到对应的刀具类型,将值设为0
if (MinDrillDiameter == double.MaxValue)
MinDrillDiameter = 0;
if (MinSlotDiameter == double.MaxValue)
MinSlotDiameter = 0;
if (MinEADiameter == double.MaxValue)
MinEADiameter = 0;
System.Diagnostics.Debug.WriteLine($"=== 最终结果 ===");
System.Diagnostics.Debug.WriteLine($"最小钻咀: {MinDrillDiameter:F3}");
System.Diagnostics.Debug.WriteLine($"最小槽刀: {MinSlotDiameter:F3}");
System.Diagnostics.Debug.WriteLine($"最小EA刀: {MinEADiameter:F3}");
System.Diagnostics.Debug.WriteLine($"=== 计算完成 ===");
}
///
/// 更新3.175外围孔间距信息
///
private void UpdateOuter3175SpacingInfo()
{
HasOuter3175Spacing = false;
Outer3175XSpacing = 0;
Outer3175YSpacing = 0;
var targetTool = Tools.FirstOrDefault(tool => Math.Abs(tool.Diameter - 3.175) < 0.001);
if (targetTool?.HoleLocations == null || targetTool.HoleLocations.Count == 0)
return;
var points = ExtractCoordinatePoints(targetTool.HoleLocations);
if (points.Count < 4)
return;
double topY = points.Max(point => point.Y);
double bottomY = points.Min(point => point.Y);
var topRowPoints = points.Where(point => Math.Abs(point.Y - topY) < 0.0001).ToList();
var bottomRowPoints = points.Where(point => Math.Abs(point.Y - bottomY) < 0.0001).ToList();
if (topRowPoints.Count < 2 || bottomRowPoints.Count < 2)
return;
double leftX = Math.Min(topRowPoints.Min(point => point.X), bottomRowPoints.Min(point => point.X));
double rightX = Math.Max(topRowPoints.Max(point => point.X), bottomRowPoints.Max(point => point.X));
Outer3175XSpacing = Math.Round(rightX - leftX, 3, MidpointRounding.AwayFromZero);
Outer3175YSpacing = Math.Round(topY - bottomY, 3, MidpointRounding.AwayFromZero);
HasOuter3175Spacing = true;
Debug.WriteLine($"[3.175外围孔] X间距={Outer3175XSpacing:F3}, Y间距={Outer3175YSpacing:F3}");
}
///
/// 从孔位文本中提取坐标点,统一转换为毫米
///
private static List<(double X, double Y)> ExtractCoordinatePoints(IEnumerable holeLocations)
{
var points = new List<(double X, double Y)>();
foreach (var location in holeLocations)
{
var match = Regex.Match(location, @"X([+-]?\d+)Y([+-]?\d+)");
if (!match.Success)
continue;
double x = double.Parse(match.Groups[1].Value) / 1000.0;
double y = double.Parse(match.Groups[2].Value) / 1000.0;
points.Add((x, y));
}
return points;
}
///
/// 更新PP钻带间距信息
///
private void UpdatePpSpacingInfo()
{
IsPpDrillTape = false;
PpXSpacing = 0;
PpYSpacing = 0;
// 检测条件:文件名以-pp结尾,恰好1个刀具,4个孔,直径4.000
if (string.IsNullOrEmpty(OriginalFilePath))
return;
var fileName = System.IO.Path.GetFileNameWithoutExtension(OriginalFilePath);
if (!fileName.EndsWith("-pp", StringComparison.OrdinalIgnoreCase))
return;
if (Tools.Count != 1)
return;
var tool = Tools[0];
if (tool.TotalHoles != 4 || Math.Abs(tool.Diameter - 4.000) > 0.001)
return;
// 解析孔位坐标
var points = ExtractCoordinatePoints(tool.HoleLocations);
if (points.Count != 4)
return;
IsPpDrillTape = true;
// 将4个点配对为矩形的两组对边,取X跨度+Y跨度最大的方案
int[][][] combos = { new[] { new[] { 0, 1 }, new[] { 2, 3 } }, new[] { new[] { 0, 2 }, new[] { 1, 3 } }, new[] { new[] { 0, 3 }, new[] { 1, 2 } } };
double bestRange = -1;
int bestIdx = 0;
for (int i = 0; i < combos.Length; i++)
{
var a = points[combos[i][0][0]];
var b = points[combos[i][0][1]];
var c = points[combos[i][1][0]];
var d = points[combos[i][1][1]];
double xRange = Math.Max(Math.Abs(a.X - b.X), Math.Abs(c.X - d.X));
double yRange = Math.Max(Math.Abs(a.Y - b.Y), Math.Abs(c.Y - d.Y));
double totalRange = xRange + yRange;
if (totalRange > bestRange)
{
bestRange = totalRange;
bestIdx = i;
}
}
var p1 = points[combos[bestIdx][0][0]];
var p2 = points[combos[bestIdx][0][1]];
var p3 = points[combos[bestIdx][1][0]];
var p4 = points[combos[bestIdx][1][1]];
double dist1 = Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y));
double dist2 = Math.Sqrt((p3.X - p4.X) * (p3.X - p4.X) + (p3.Y - p4.Y) * (p3.Y - p4.Y));
// 跨度更大的为X间距(上下孔),另一组为Y间距(左右孔)
double xSpan1 = Math.Abs(p1.X - p2.X);
double xSpan2 = Math.Abs(p3.X - p4.X);
if (xSpan1 >= xSpan2)
{
PpXSpacing = Math.Round(dist1, 3, MidpointRounding.AwayFromZero);
PpYSpacing = Math.Round(dist2, 3, MidpointRounding.AwayFromZero);
}
else
{
PpXSpacing = Math.Round(dist2, 3, MidpointRounding.AwayFromZero);
PpYSpacing = Math.Round(dist1, 3, MidpointRounding.AwayFromZero);
}
System.Diagnostics.Debug.WriteLine($"[PP钻带] X间距={PpXSpacing:F3}, Y间距={PpYSpacing:F3}");
}
///
/// 测试使用参考钻带重排功能
///
public void TestReorderByReferenceDrillTape()
{
System.Diagnostics.Debug.WriteLine("=== 测试使用参考钻带重排功能 ===");
try
{
// 1. 加载测试数据
LoadSampleData();
System.Diagnostics.Debug.WriteLine($"已加载{Tools.Count}把刀具的测试数据");
// 2. 创建模拟的参考钻带内容
string mockReferenceContent = CreateMockReferenceDrillTape(Tools.ToList());
string tempReferenceFile = System.IO.Path.GetTempFileName();
System.IO.File.WriteAllText(tempReferenceFile, mockReferenceContent, CreateGb2312Encoding());
try
{
// 3. 读取参考钻带的刀序
var referenceDiameters = ReadToolOrderFromReferenceDrillTape(tempReferenceFile);
System.Diagnostics.Debug.WriteLine($"从参考钻带读取到{referenceDiameters.Count}把刀具的顺序");
System.Diagnostics.Debug.WriteLine($"参考钻带刀序:{string.Join(", ", referenceDiameters.Select(d => d.ToString("F3")))}");
// 4. 验证刀具匹配
var (isValid, message, warnings) = ValidateToolMatch(Tools, referenceDiameters);
System.Diagnostics.Debug.WriteLine($"验证结果:{(isValid ? "通过" : "失败")}");
System.Diagnostics.Debug.WriteLine($"验证消息:{message}");
if (warnings.Any())
{
System.Diagnostics.Debug.WriteLine($"警告信息:{string.Join("; ", warnings)}");
}
if (isValid)
{
// 5. 执行重排
var originalOrder = Tools.Select(t => new { t.ToolNumber, t.Diameter }).ToList();
System.Diagnostics.Debug.WriteLine($"原始刀序:{string.Join(", ", originalOrder.Select(t => $"T{t.ToolNumber:D2}({t.Diameter:F3})"))}");
SortToolsByDiameterList(Tools, referenceDiameters);
var newOrder = Tools.Select(t => new { t.ToolNumber, t.Diameter }).ToList();
System.Diagnostics.Debug.WriteLine($"重排后刀序:{string.Join(", ", newOrder.Select(t => $"T{t.ToolNumber:D2}({t.Diameter:F3})"))}");
// 验证重排是否正确
bool isCorrectOrder = true;
for (int i = 0; i < referenceDiameters.Count; i++)
{
double expectedDiameter = Math.Round(referenceDiameters[i], 3);
double actualDiameter = Math.Round(Tools[i].Diameter, 3);
if (expectedDiameter != actualDiameter)
{
isCorrectOrder = false;
break;
}
}
System.Diagnostics.Debug.WriteLine($"重排验证:{(isCorrectOrder ? "成功" : "失败")}");
}
}
finally
{
// 清理临时文件
if (System.IO.File.Exists(tempReferenceFile))
{
System.IO.File.Delete(tempReferenceFile);
}
}
System.Diagnostics.Debug.WriteLine("测试完成!");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"测试失败:{ex.Message}");
}
}
///
/// 创建模拟的参考钻带内容
///
/// 刀具列表
/// 模拟的钻带内容
private string CreateMockReferenceDrillTape(List tools)
{
var content = new System.Text.StringBuilder();
content.AppendLine("M48");
content.AppendLine(";模拟参考钻带文件");
// 添加刀具定义
foreach (var tool in tools.OrderBy(t => t.ToolNumber))
{
content.AppendLine($"T{tool.ToolNumber:D2}C{tool.Diameter:F3}H01500Z+0.150S070.00F008.0U0800.0");
}
content.AppendLine("%");
// 添加刀具切换和坐标数据
foreach (var tool in tools.OrderBy(t => t.ToolNumber))
{
content.AppendLine($"T{tool.ToolNumber:D2}");
if (tool.HoleLocations != null && tool.HoleLocations.Any())
{
foreach (var location in tool.HoleLocations.Take(3)) // 只取前3个坐标作为示例
{
content.AppendLine(location);
}
}
else
{
content.AppendLine("X000000Y000000"); // 默认坐标
}
}
content.AppendLine("M30");
return content.ToString();
}
///
/// 使用参考钻带的刀序重排当前刀具
///
public void ReorderToolsByReferenceDrillTape()
{
try
{
// 前置检查
if (Tools.Count == 0)
{
System.Windows.MessageBox.Show("没有可重排的刀具,请先加载钻带文件", "提示",
MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
// 1. 选择参考钻带文件
var openFileDialog = new Microsoft.Win32.OpenFileDialog
{
Filter = "钻带文件 (*.txt;*.drl;*.dr2;*.dpin)|*.txt;*.drl;*.dr2;*.dpin|所有文件 (*.*)|*.*",
Title = "选择参考钻带文件"
};
if (openFileDialog.ShowDialog() != true)
return;
// 显示进度提示
var progressDialog = new System.Windows.Window
{
Title = "处理中",
Width = 300,
Height = 100,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = System.Windows.Application.Current.MainWindow,
ResizeMode = ResizeMode.NoResize
};
var progressText = new System.Windows.Controls.TextBlock
{
Text = "正在读取参考钻带文件...",
HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(20)
};
progressDialog.Content = progressText;
// 异步处理以避免界面冻结
var progressDialogShown = false;
System.Threading.Tasks.Task.Run(() =>
{
try
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressDialog.Show();
progressDialogShown = true;
progressText.Text = "正在读取参考钻带文件...";
});
List referenceDiameters;
string referenceFileName = System.IO.Path.GetFileName(openFileDialog.FileName);
// 2. 读取参考钻带的刀序
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressText.Text = "正在解析刀具信息...";
});
referenceDiameters = ReadToolOrderFromReferenceDrillTape(openFileDialog.FileName);
if (referenceDiameters.Count == 0)
{
throw new InvalidOperationException("参考钻带文件中没有找到有效的刀具信息");
}
// 3. 验证刀具匹配
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressText.Text = "正在验证刀具匹配...";
});
var (isValid, message, warnings) = ValidateToolMatch(Tools, referenceDiameters);
if (!isValid)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressDialog.Close();
System.Windows.MessageBox.Show(message, "刀具匹配失败",
MessageBoxButton.OK, MessageBoxImage.Warning);
});
return;
}
// 显示警告信息(如果有)
if (warnings.Any())
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
var warningResult = System.Windows.MessageBox.Show(
$"{string.Join("\n", warnings)}\n\n是否继续重排操作?",
"警告",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (warningResult == MessageBoxResult.No)
{
progressDialog.Close();
return;
}
});
}
// 4. 保存原始刀具顺序用于对比
var originalTools = Tools.Select(t => new ToolItem
{
ToolNumber = t.ToolNumber,
Diameter = t.Diameter,
RegularHoles = t.RegularHoles,
SlotHoles = t.SlotHoles,
TotalHoles = t.TotalHoles,
SlotCount = t.SlotCount,
ToolType = t.ToolType,
ToolSuffixType = t.ToolSuffixType,
ToolCategory = t.ToolCategory,
HoleLocations = new List(t.HoleLocations ?? new List()),
MachineCodeCommand = t.MachineCodeCommand,
MachineCodeType = t.MachineCodeType
}).ToList();
// 5. 按照参考钻带的顺序重排刀具
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressText.Text = "正在重排刀具顺序...";
SortToolsByDiameterList(Tools, referenceDiameters);
});
// 6. 创建重排后的刀具列表用于确认
var reorderedTools = Tools.Select(t => new ToolItem
{
ToolNumber = t.ToolNumber,
Diameter = t.Diameter,
RegularHoles = t.RegularHoles,
SlotHoles = t.SlotHoles,
TotalHoles = t.TotalHoles,
SlotCount = t.SlotCount,
ToolType = t.ToolType,
ToolSuffixType = t.ToolSuffixType,
ToolCategory = t.ToolCategory,
HoleLocations = new List(t.HoleLocations ?? new List()),
MachineCodeCommand = t.MachineCodeCommand,
MachineCodeType = t.MachineCodeType
}).ToList();
// 7. 显示确认窗口
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressDialog.Close();
var confirmationWindow = new ToolReorderConfirmationWindow(originalTools, reorderedTools);
confirmationWindow.Owner = System.Windows.Application.Current.MainWindow;
confirmationWindow.Title = $"使用参考钻带刀序确认 - {referenceFileName}";
confirmationWindow.ShowDialog();
if (!confirmationWindow.IsConfirmed)
{
// 用户取消,恢复原始顺序
Tools.Clear();
foreach (var tool in originalTools)
{
Tools.Add(tool);
}
return;
}
// 8. 执行重排和重新编号
try
{
string reorderedDrillTape = ReorderAndRenumberTools(skipConfirmation: true);
DrillTapeContent = reorderedDrillTape;
System.Windows.MessageBox.Show(
$"已成功使用参考钻带文件重排刀具顺序\n\n参考文件:{referenceFileName}\n重排刀具数量:{Tools.Count}把",
"重排完成",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(
$"重排过程中发生错误:{ex.Message}\n\n刀具顺序已更新,但钻带内容未能重新生成。",
"部分完成",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
});
}
catch (Exception ex)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
if (progressDialogShown)
progressDialog.Close();
System.Windows.MessageBox.Show(
$"使用参考钻带重排失败:{ex.Message}",
"错误",
MessageBoxButton.OK,
MessageBoxImage.Error);
});
}
});
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(
$"启动重排操作失败:{ex.Message}",
"错误",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
///
/// 从参考钻带文件读取刀具顺序
///
/// 参考钻带文件路径
/// 按顺序排列的刀具直径列表
private List ReadToolOrderFromReferenceDrillTape(string referenceFilePath)
{
var diameters = new List();
try
{
// 检查文件是否存在
if (!System.IO.File.Exists(referenceFilePath))
{
throw new System.IO.FileNotFoundException($"参考钻带文件不存在:{referenceFilePath}");
}
string drillTapeContent = CommandTypeFileReader.ReadAllText(referenceFilePath);
if (string.IsNullOrWhiteSpace(drillTapeContent))
{
throw new InvalidOperationException("参考钻带文件内容为空");
}
// 解析刀具定义部分(M48到%之间的内容)
var toolPattern = @"T(\d+)C(\d+\.?\d*)";
var matches = System.Text.RegularExpressions.Regex.Matches(drillTapeContent, toolPattern);
if (matches.Count == 0)
{
throw new InvalidOperationException("参考钻带文件中未找到刀具定义信息(TxxCxxx格式)");
}
// 创建刀具编号到直径的映射
var toolNumberToDiameter = new Dictionary();
foreach (System.Text.RegularExpressions.Match match in matches)
{
int toolNumber = int.Parse(match.Groups[1].Value);
double diameter = double.Parse(match.Groups[2].Value);
if (toolNumberToDiameter.ContainsKey(toolNumber))
{
throw new InvalidOperationException($"参考钻带文件中存在重复的刀具编号:T{toolNumber:D2}");
}
toolNumberToDiameter[toolNumber] = diameter;
}
// 按刀具编号顺序提取直径(T01, T02, T03...)
int maxToolNumber = toolNumberToDiameter.Keys.Max();
for (int i = 1; i <= maxToolNumber; i++)
{
if (toolNumberToDiameter.ContainsKey(i))
{
diameters.Add(toolNumberToDiameter[i]);
}
}
if (diameters.Count == 0)
{
throw new InvalidOperationException("无法从参考钻带文件中提取有效的刀具顺序");
}
}
catch (System.IO.FileNotFoundException)
{
throw;
}
catch (InvalidOperationException)
{
throw;
}
catch (System.Text.RegularExpressions.RegexMatchTimeoutException ex)
{
throw new InvalidOperationException($"解析参考钻带文件时超时:{ex.Message}", ex);
}
catch (Exception ex)
{
throw new InvalidOperationException($"读取参考钻带文件时发生未知错误:{ex.Message}", ex);
}
return diameters;
}
///
/// 验证当前刀具与参考钻带刀具的匹配性
///
/// 当前刀具列表
/// 参考钻带的直径列表
/// 验证结果和详细信息
private (bool IsValid, string Message, List Warnings) ValidateToolMatch(
ObservableCollection currentTools,
List referenceDiameters)
{
var warnings = new List();
// 检查刀具数量是否一致
if (currentTools.Count != referenceDiameters.Count)
{
return (false,
$"刀具数量不匹配:当前钻带有{currentTools.Count}把刀具,参考钻带有{referenceDiameters.Count}把刀具",
warnings);
}
// 创建当前刀具直径的集合(考虑浮点数精度)
var currentDiameters = currentTools.Select(t => Math.Round(t.Diameter, 3)).ToHashSet();
var referenceDiametersRounded = referenceDiameters.Select(d => Math.Round(d, 3)).ToHashSet();
// 检查直径是否完全匹配
var missingInCurrent = referenceDiametersRounded.Except(currentDiameters).ToList();
var missingInReference = currentDiameters.Except(referenceDiametersRounded).ToList();
if (missingInCurrent.Any() || missingInReference.Any())
{
var message = "刀具直径不匹配:\n";
if (missingInCurrent.Any())
{
message += $"参考钻带中存在但当前钻带中不存在的刀具:{string.Join(", ", missingInCurrent)}\n";
}
if (missingInReference.Any())
{
message += $"当前钻带中存在但参考钻带中不存在的刀具:{string.Join(", ", missingInReference)}";
}
return (false, message, warnings);
}
// 检查机台码刀具位置是否一致
var currentMachineCodeTools = currentTools.Where(t => t.ToolType == ToolType.MachineCode).ToList();
var referenceMachineCodeIndices = new List();
for (int i = 0; i < referenceDiameters.Count; i++)
{
double diameter = Math.Round(referenceDiameters[i], 3);
var matchingTool = currentTools.FirstOrDefault(t => Math.Round(t.Diameter, 3) == diameter);
if (matchingTool != null && matchingTool.ToolType == ToolType.MachineCode)
{
referenceMachineCodeIndices.Add(i);
}
}
var currentMachineCodeIndices = new List();
for (int i = 0; i < currentTools.Count; i++)
{
if (currentTools[i].ToolType == ToolType.MachineCode)
{
currentMachineCodeIndices.Add(i);
}
}
if (!referenceMachineCodeIndices.SequenceEqual(currentMachineCodeIndices))
{
warnings.Add("注意:机台码刀具的位置在参考钻带和当前钻带中不一致,这可能会影响加工顺序");
}
return (true, "刀具匹配验证通过", warnings);
}
///
/// 使用指定种子文件的刀序重排当前刀具
///
public void ReorderToolsBySortSeedFile()
{
try
{
// 前置检查
if (Tools.Count == 0)
{
System.Windows.MessageBox.Show("没有可重排的刀具,请先加载钻带文件", "提示",
MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
// 1. 选择种子文件
var openFileDialog = new Microsoft.Win32.OpenFileDialog
{
Filter = "排序种子文件 (*.txt;*-sort.txt)|*.txt;*-sort.txt|所有文件 (*.*)|*.*",
Title = "选择排序种子文件"
};
if (openFileDialog.ShowDialog() != true)
return;
// 显示进度提示
var progressDialog = new System.Windows.Window
{
Title = "处理中",
Width = 300,
Height = 100,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = System.Windows.Application.Current.MainWindow,
ResizeMode = ResizeMode.NoResize
};
var progressText = new System.Windows.Controls.TextBlock
{
Text = "正在读取种子文件...",
HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(20)
};
progressDialog.Content = progressText;
// 异步处理以避免界面冻结
var progressDialogShown = false;
System.Threading.Tasks.Task.Run(() =>
{
try
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressDialog.Show();
progressDialogShown = true;
progressText.Text = "正在读取种子文件...";
});
List seedDiameters;
string seedFileName = System.IO.Path.GetFileName(openFileDialog.FileName);
// 2. 读取种子文件的刀序
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressText.Text = "正在解析种子文件...";
});
seedDiameters = ReadSortFile(openFileDialog.FileName);
if (seedDiameters.Count == 0)
{
throw new InvalidOperationException("种子文件中没有找到有效的刀具直径信息");
}
// 3. 验证刀具匹配
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressText.Text = "正在验证刀具匹配...";
});
var (isValid, message, warnings) = ValidateSortSeedMatch(Tools, seedDiameters);
if (!isValid)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressDialog.Close();
System.Windows.MessageBox.Show(message, "刀具匹配失败",
MessageBoxButton.OK, MessageBoxImage.Warning);
});
return;
}
// 显示警告信息(如果有)
if (warnings.Any())
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
var warningResult = System.Windows.MessageBox.Show(
$"{string.Join("\n", warnings)}\n\n是否继续重排操作?",
"警告",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (warningResult == MessageBoxResult.No)
{
progressDialog.Close();
return;
}
});
}
// 4. 保存原始刀具顺序用于对比
var originalTools = Tools.Select(t => new ToolItem
{
ToolNumber = t.ToolNumber,
Diameter = t.Diameter,
RegularHoles = t.RegularHoles,
SlotHoles = t.SlotHoles,
TotalHoles = t.TotalHoles,
SlotCount = t.SlotCount,
ToolType = t.ToolType,
ToolSuffixType = t.ToolSuffixType,
ToolCategory = t.ToolCategory,
HoleLocations = new List(t.HoleLocations ?? new List()),
MachineCodeCommand = t.MachineCodeCommand,
MachineCodeType = t.MachineCodeType
}).ToList();
// 5. 按照种子文件的顺序重排刀具
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressText.Text = "正在重排刀具顺序...";
SortToolsByDiameterList(Tools, seedDiameters);
});
// 6. 创建重排后的刀具列表用于确认
var reorderedTools = Tools.Select(t => new ToolItem
{
ToolNumber = t.ToolNumber,
Diameter = t.Diameter,
RegularHoles = t.RegularHoles,
SlotHoles = t.SlotHoles,
TotalHoles = t.TotalHoles,
SlotCount = t.SlotCount,
ToolType = t.ToolType,
ToolSuffixType = t.ToolSuffixType,
ToolCategory = t.ToolCategory,
HoleLocations = new List(t.HoleLocations ?? new List()),
MachineCodeCommand = t.MachineCodeCommand,
MachineCodeType = t.MachineCodeType
}).ToList();
// 7. 显示确认窗口
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progressDialog.Close();
var confirmationWindow = new ToolReorderConfirmationWindow(originalTools, reorderedTools);
confirmationWindow.Owner = System.Windows.Application.Current.MainWindow;
confirmationWindow.Title = $"使用种子文件刀序确认 - {seedFileName}";
confirmationWindow.ShowDialog();
if (!confirmationWindow.IsConfirmed)
{
// 用户取消,恢复原始顺序
Tools.Clear();
foreach (var tool in originalTools)
{
Tools.Add(tool);
}
return;
}
// 8. 执行重排和重新编号
try
{
string reorderedDrillTape = ReorderAndRenumberTools(skipConfirmation: true);
DrillTapeContent = reorderedDrillTape;
System.Windows.MessageBox.Show(
$"已成功使用种子文件重排刀具顺序\n\n种子文件:{seedFileName}\n重排刀具数量:{Tools.Count}把",
"重排完成",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(
$"重排过程中发生错误:{ex.Message}\n\n刀具顺序已更新,但钻带内容未能重新生成。",
"部分完成",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
});
}
catch (Exception ex)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
if (progressDialogShown)
progressDialog.Close();
System.Windows.MessageBox.Show(
$"使用种子文件重排失败:{ex.Message}",
"错误",
MessageBoxButton.OK,
MessageBoxImage.Error);
});
}
});
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(
$"启动重排操作失败:{ex.Message}",
"错误",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
///
/// 验证当前刀具与种子文件的匹配性
///
/// 当前刀具列表
/// 种子文件的直径列表
/// 验证结果和详细信息
private (bool IsValid, string Message, List Warnings) ValidateSortSeedMatch(
ObservableCollection currentTools,
List seedDiameters)
{
var warnings = new List();
// 检查刀具数量是否一致
if (currentTools.Count != seedDiameters.Count)
{
return (false,
$"刀具数量不匹配:当前钻带有{currentTools.Count}把刀具,种子文件中有{seedDiameters.Count}把刀具",
warnings);
}
// 创建当前刀具直径的集合(考虑浮点数精度)
var currentDiameters = currentTools.Select(t => Math.Round(t.Diameter, 3)).ToHashSet();
var seedDiametersRounded = seedDiameters.Select(d => Math.Round(d, 3)).ToHashSet();
// 检查直径是否完全匹配
var missingInCurrent = seedDiametersRounded.Except(currentDiameters).ToList();
var missingInSeed = currentDiameters.Except(seedDiametersRounded).ToList();
if (missingInCurrent.Any() || missingInSeed.Any())
{
var message = "刀具直径不匹配:\n";
if (missingInCurrent.Any())
{
message += $"种子文件中存在但当前钻带中不存在的刀具:{string.Join(", ", missingInCurrent)}\n";
}
if (missingInSeed.Any())
{
message += $"当前钻带中存在但种子文件中不存在的刀具:{string.Join(", ", missingInSeed)}";
}
return (false, message, warnings);
}
// 检查机台码刀具位置是否一致
var currentMachineCodeTools = currentTools.Where(t => t.ToolType == ToolType.MachineCode).ToList();
var seedMachineCodeIndices = new List();
for (int i = 0; i < seedDiameters.Count; i++)
{
double diameter = Math.Round(seedDiameters[i], 3);
var matchingTool = currentTools.FirstOrDefault(t => Math.Round(t.Diameter, 3) == diameter);
if (matchingTool != null && matchingTool.ToolType == ToolType.MachineCode)
{
seedMachineCodeIndices.Add(i);
}
}
var currentMachineCodeIndices = new List();
for (int i = 0; i < currentTools.Count; i++)
{
if (currentTools[i].ToolType == ToolType.MachineCode)
{
currentMachineCodeIndices.Add(i);
}
}
if (!seedMachineCodeIndices.SequenceEqual(currentMachineCodeIndices))
{
warnings.Add("注意:机台码刀具的位置在种子文件和当前钻带中不一致,这可能会影响加工顺序");
}
return (true, "刀具匹配验证通过", warnings);
}
}
}