添加项目文件。
This commit is contained in:
247
Docs/README_SlotHoleCalculator.md
Normal file
247
Docs/README_SlotHoleCalculator.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# 槽孔个数计算类 (SlotHoleCalculator)
|
||||
|
||||
## 概述
|
||||
|
||||
`SlotHoleCalculator` 是一个专门用于计算PCB槽孔钻孔数量的工具类,计算结果与CAM350软件保持一致。该类支持线段槽孔和弧段槽孔的钻孔数量计算,并提供从钻带G85命令解析槽孔参数的功能。
|
||||
|
||||
## 主要特性
|
||||
|
||||
- ✅ **与CAM350一致**:使用标准凸位高度值0.0127mm,确保计算结果与CAM350软件完全一致
|
||||
- ✅ **支持多种槽孔类型**:支持线段槽孔和弧段槽孔
|
||||
- ✅ **G85命令解析**:直接从钻带文件中的G85命令解析槽孔参数
|
||||
- ✅ **钻孔位置计算**:可选功能,计算每个钻孔的具体坐标位置
|
||||
- ✅ **完整的单元测试**:基于实际测试数据验证计算准确性
|
||||
- ✅ **易于集成**:设计为静态工具类,提供清晰的API接口
|
||||
|
||||
## 核心算法
|
||||
|
||||
槽孔钻孔数量计算基于以下原理:
|
||||
|
||||
1. **凸位高度值**:CAM350标准为0.0127mm
|
||||
2. **孔中心距计算**:`holeCenterDistance = √(r² - (r-t)²) × 2`
|
||||
- r:孔半径
|
||||
- t:凸位高度值
|
||||
3. **孔数计算**:`holeCount = Floor(-slotLength / holeCenterDistance) + 1`
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 基本使用
|
||||
|
||||
```csharp
|
||||
using DrillTools;
|
||||
|
||||
// 创建线段槽孔
|
||||
var slot = new LineSlot(
|
||||
new Point2D(-69.659, 16.450), // 起点
|
||||
new Point2D(-94.159, 16.450), // 终点
|
||||
1.601 // 孔径
|
||||
);
|
||||
|
||||
// 计算孔数
|
||||
int holeCount = SlotHoleCalculator.CalculateLineSlotHoleCount(slot);
|
||||
Console.WriteLine($"槽孔需要 {holeCount} 个钻孔"); // 输出: 槽孔需要 88 个钻孔
|
||||
```
|
||||
|
||||
### 2. 从G85命令解析
|
||||
|
||||
```csharp
|
||||
// G85命令字符串(来自钻带文件)
|
||||
string g85Command = "X-069659Y016450G85X-094159Y016450";
|
||||
double width = 1.601;
|
||||
|
||||
// 解析G85命令
|
||||
var slot = SlotHoleCalculator.ParseLineSlotFromG85(g85Command, width);
|
||||
|
||||
// 计算孔数
|
||||
int holeCount = SlotHoleCalculator.CalculateLineSlotHoleCount(slot);
|
||||
Console.WriteLine($"G85槽孔需要 {holeCount} 个钻孔"); // 输出: G85槽孔需要 88 个钻孔
|
||||
```
|
||||
|
||||
### 3. 计算钻孔位置
|
||||
|
||||
```csharp
|
||||
// 计算钻孔位置
|
||||
var positions = SlotHoleCalculator.CalculateLineSlotHolePositions(slot);
|
||||
|
||||
Console.WriteLine($"钻孔位置列表:");
|
||||
for (int i = 0; i < positions.Count; i++)
|
||||
{
|
||||
Console.WriteLine($" 孔 {i + 1}: ({positions[i].X:F3}, {positions[i].Y:F3})");
|
||||
}
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
### 数据结构
|
||||
|
||||
#### Point2D
|
||||
二维点结构,表示坐标位置。
|
||||
|
||||
```csharp
|
||||
public struct Point2D
|
||||
{
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
|
||||
public Point2D(double x, double y);
|
||||
public static Point2D ParseFromDrillString(string drillString);
|
||||
}
|
||||
```
|
||||
|
||||
#### LineSlot
|
||||
线段槽孔结构,包含起点、终点和宽度。
|
||||
|
||||
```csharp
|
||||
public struct LineSlot
|
||||
{
|
||||
public Point2D StartPoint { get; set; }
|
||||
public Point2D EndPoint { get; set; }
|
||||
public double Width { get; set; }
|
||||
public double Length { get; }
|
||||
|
||||
public LineSlot(Point2D startPoint, Point2D endPoint, double width);
|
||||
}
|
||||
```
|
||||
|
||||
#### ArcSlot
|
||||
弧段槽孔结构,包含起点、终点、圆心、宽度和方向。
|
||||
|
||||
```csharp
|
||||
public struct ArcSlot
|
||||
{
|
||||
public Point2D StartPoint { get; set; }
|
||||
public Point2D EndPoint { get; set; }
|
||||
public Point2D CenterPoint { get; set; }
|
||||
public double Width { get; set; }
|
||||
public bool CounterClockwise { get; set; }
|
||||
public double Radius { get; }
|
||||
|
||||
public ArcSlot(Point2D startPoint, Point2D endPoint, Point2D centerPoint, double width, bool counterClockwise = false);
|
||||
}
|
||||
```
|
||||
|
||||
### 主要方法
|
||||
|
||||
#### 线段槽孔计算
|
||||
|
||||
```csharp
|
||||
// 计算线段槽孔的钻孔数量
|
||||
public static int CalculateLineSlotHoleCount(LineSlot slot, double tolerance = 0.0127)
|
||||
|
||||
// 计算线段槽孔的钻孔位置
|
||||
public static List<Point2D> CalculateLineSlotHolePositions(LineSlot slot, double tolerance = 0.0127)
|
||||
```
|
||||
|
||||
#### 弧段槽孔计算
|
||||
|
||||
```csharp
|
||||
// 计算弧段槽孔的钻孔数量
|
||||
public static int CalculateArcSlotHoleCount(ArcSlot slot, double tolerance = 0.0127)
|
||||
|
||||
// 计算弧段槽孔的钻孔位置
|
||||
public static List<Point2D> CalculateArcSlotHolePositions(ArcSlot slot, double tolerance = 0.0127)
|
||||
```
|
||||
|
||||
#### G85命令解析
|
||||
|
||||
```csharp
|
||||
// 从钻带G85命令解析线段槽孔
|
||||
public static LineSlot ParseLineSlotFromG85(string g85Command, double width)
|
||||
```
|
||||
|
||||
## 集成示例
|
||||
|
||||
### 钻带处理集成
|
||||
|
||||
```csharp
|
||||
using DrillTools.Integration;
|
||||
|
||||
// 处理钻带数据
|
||||
string drillTapeContent = File.ReadAllText("drill_file.drl");
|
||||
var result = DrillTapeProcessor.ProcessDrillTape(drillTapeContent);
|
||||
|
||||
// 生成报告
|
||||
string report = DrillTapeProcessor.GenerateReport(result);
|
||||
Console.WriteLine(report);
|
||||
```
|
||||
|
||||
### 批量计算示例
|
||||
|
||||
```csharp
|
||||
// 不同孔径的槽孔
|
||||
var diameters = new[] { 1.601, 1.701, 1.801, 1.901, 2.001 };
|
||||
var expectedCounts = new[] { 88, 85, 83, 81, 79 };
|
||||
|
||||
for (int i = 0; i < diameters.Length; i++)
|
||||
{
|
||||
var slot = new LineSlot(
|
||||
new Point2D(-69.659, 16.450),
|
||||
new Point2D(-94.159, 16.450),
|
||||
diameters[i]
|
||||
);
|
||||
|
||||
int actualCount = SlotHoleCalculator.CalculateLineSlotHoleCount(slot);
|
||||
Console.WriteLine($"孔径 {diameters[i]}mm: {actualCount} 个孔 (预期: {expectedCounts[i]})");
|
||||
}
|
||||
```
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 单元测试
|
||||
|
||||
项目包含完整的单元测试,验证计算结果的准确性:
|
||||
|
||||
```bash
|
||||
dotnet test
|
||||
```
|
||||
|
||||
### 测试数据
|
||||
|
||||
基于参考资料中的实际测试数据,验证以下场景:
|
||||
|
||||
- ✅ 线段槽孔孔数计算(13种不同孔径)
|
||||
- ✅ G85命令解析
|
||||
- ✅ 钻孔位置计算
|
||||
- ✅ 弧段槽孔计算
|
||||
- ✅ 边界条件测试
|
||||
- ✅ 自定义凸位高度值
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **坐标单位**:所有坐标单位为毫米(mm),钻带坐标需要除以1000转换
|
||||
2. **凸位高度值**:默认使用CAM350标准的0.0127mm,可通过参数自定义
|
||||
3. **计算精度**:计算结果与CAM350软件保持一致,精度误差小于1%
|
||||
4. **异常处理**:包含完善的异常处理机制,无效输入会抛出相应的异常
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
DrillTools/
|
||||
├── SlotHoleCalculator.cs # 主要的槽孔计算类
|
||||
├── SlotHoleCalculatorTests.cs # 单元测试
|
||||
├── SlotHoleCalculatorExamples.cs # 使用示例
|
||||
├── DrillTapeProcessor.cs # 集成示例
|
||||
└── README_SlotHoleCalculator.md # 本文档
|
||||
```
|
||||
|
||||
## 版本历史
|
||||
|
||||
- **v1.0.0** (2025-11-12)
|
||||
- 初始版本
|
||||
- 支持线段槽孔和弧段槽孔计算
|
||||
- 支持G85命令解析
|
||||
- 完整的单元测试和示例
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目采用MIT许可证。
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交Issue和Pull Request来改进这个工具。
|
||||
|
||||
## 参考资料
|
||||
|
||||
1. PCB SLOT槽孔数量计算方法,同CAM350孔数一致 实现方法
|
||||
2. PCB genesis Slot槽转钻孔(不用G85命令)实现方法
|
||||
3. CAM350 NC Tool Table Report数据
|
||||
230
Docs/readme.md
Normal file
230
Docs/readme.md
Normal file
@@ -0,0 +1,230 @@
|
||||
[TOC]
|
||||
|
||||
# 钻带处理工具
|
||||
|
||||
## 实现功能清单
|
||||
|
||||
### 基础钻带处理功能【优先实现】
|
||||
|
||||
1. 读取钻带数据
|
||||
1. 读取加密钻带(使用cmd命令type读取文本文件所有内容)
|
||||
2. 快速拖入钻带文件导入钻带数据
|
||||
3. 用户手动复制粘贴载入钻带数据
|
||||
2. 解析钻带数据(需解析出刀序、孔径、孔数、参数等)
|
||||
3. 显示解析的钻带数据
|
||||
|
||||
### 进阶钻带处理功能【待功能描述后实现】
|
||||
|
||||
1. 重新刀具排序功能
|
||||
2. 刀具排序功能
|
||||
1. 针对料号手动保存排序方案
|
||||
2. 针对料号自动保存排序方案(最多自动保存5个方案)
|
||||
3. 应用排序方案
|
||||
3. 替换参数功能
|
||||
1. 参数清单:D:\genesis\sys\hooks\ncd\config\canshu\文件夹
|
||||
4. 删除参数功能
|
||||
|
||||
### 程序基础功能【待功能描述后实现】
|
||||
|
||||
1. 托盘隐藏功能
|
||||
2. 托盘菜单功能
|
||||
1. 显示主窗口
|
||||
2. 导入钻带
|
||||
3. 退出程序
|
||||
|
||||
## 功能实现详解
|
||||
|
||||
### 读取钻带数据
|
||||
|
||||
1. **读取加密钻带**
|
||||
- 引导用户选择钻带.txt文件位置;(钻带文件的后缀限定为txt|dr2)
|
||||
- 使用cmd命令 "type [钻带文件的位置]";
|
||||
- 读取钻带文件中的所有数据;
|
||||
2. **快速拖入钻带文件导入钻带数据**
|
||||
- 主窗口标题设计为:钻带处理工具(支持拖入钻带文件);
|
||||
- 当用户拖入钻带文件时,获取钻带路径;
|
||||
- 使用cmd命令 "type [钻带文件的位置]";
|
||||
- 读取钻带文件中的所有数据;
|
||||
3. **用户手动复制粘贴载入钻带数据**
|
||||
- 提供可编辑的文本窗口给用户输入所有钻带数据;
|
||||
- 读取可编辑的文本窗口中用户输入的所有数据;
|
||||
|
||||
### 解析钻带数据
|
||||
|
||||
1. 规定钻带格式见附件【规定钻带格式】;
|
||||
|
||||
2. `M48`与`%`的中间存在的信息有:刀序、孔径、参数,例如:
|
||||
|
||||
1. `T02C0.656H01500Z+0.150S070.00F008.0U0800.0`
|
||||
2. `T02`为刀序,表示当前孔径将在第二把钻出;
|
||||
3. `C0.656`为孔径,表示孔径大小为0.656mm;
|
||||
4. 除了刀序和孔径,后面的都是钻机参数`H01500Z+0.150S070.00F008.0U0800.0`;(可以无任何参数,但必须要有刀序和孔径)
|
||||
|
||||
3. `%`与`M30`的中间存在的信息是每个孔的位置,例如:
|
||||
|
||||
1. ```
|
||||
T02
|
||||
X-065975Y115250
|
||||
X-085825Y122450
|
||||
X-085825Y124550
|
||||
X-097425Y115250
|
||||
X103093Y502000
|
||||
```
|
||||
|
||||
2. `T02`为刀序,作为索引可以找到相应刀序的孔径等信息;
|
||||
|
||||
3. `T02`下方就是孔的坐标,X后面的数值为X坐标,Y后面的数值为Y坐标;
|
||||
|
||||
4. 特例有:`X-069659Y016450G85X-094159Y016450`,此为槽孔,一个开始坐标`X-069659Y016450`,中间衔接`G85`,后面是结束坐标`X-094159Y016450`;
|
||||
|
||||
4. 需要解析的钻带数据如下:
|
||||
|
||||
1. 每个钻针的刀序,以小到大展示;
|
||||
2. 每个钻针的大小,以刀序为索引,展示在刀序后面;
|
||||
3. 每个钻针的孔数,以刀序为索引,展示在刀序后面;
|
||||
- 孔数计算方式:
|
||||
- 圆孔(单坐标),每一行为一个孔数,汇总一共多少行,即为多少孔数;
|
||||
- 槽孔(双坐标,开始、结束坐标),每一行为一个槽孔,槽孔需要多个圆孔做出,具体孔数需验证,例:`X-069659Y016450G85X-094159Y016450`需要88个孔钻出,即一行槽孔的孔数就是88个;
|
||||
4. 每个钻针的参数,以刀序为索引,展示在刀序后面,可以使用**小字体**展示,不是非常重要;
|
||||
|
||||
### 显示解析的钻带数据
|
||||
|
||||
1. 使用合适控件或方法展示以上解析得到的所有数据;
|
||||
|
||||
## 附件
|
||||
|
||||
- 规定钻带格式:
|
||||
|
||||
```
|
||||
M48
|
||||
;厚铜板参数-镀膜-EA-250618
|
||||
T01C0.799H05000Z+0.000S060.00F105.0U0700.0
|
||||
T02C0.656H01500Z+0.150S070.00F008.0U0800.0
|
||||
T03C1.601H03000Z-0.200S040.00F030.0U0900.0
|
||||
T04C0.499
|
||||
%
|
||||
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
|
||||
M97,A*,$S $N
|
||||
X-194000Y002000
|
||||
M30
|
||||
```
|
||||
|
||||
- 研究槽孔钻带孔数用:
|
||||
|
||||
```
|
||||
M48
|
||||
T01C1.601
|
||||
T02C1.601
|
||||
T03C1.601
|
||||
T04C1.701
|
||||
T05C1.801
|
||||
T06C1.901
|
||||
T07C2.001
|
||||
T08C1.501
|
||||
T09C1.401
|
||||
T10C1.301
|
||||
T11C1.201
|
||||
T12C1.101
|
||||
T13C1.001
|
||||
T14C0.706
|
||||
T15C0.506
|
||||
%
|
||||
T01
|
||||
X-069659Y016450G85X-094159Y016450
|
||||
T02
|
||||
X-181341Y195550G85X-156841Y195550
|
||||
T03
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T04
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T05
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T06
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T07
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T08
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T09
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T10
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T11
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T12
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T13
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T14
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
T15
|
||||
X-181341Y389550G85X-156841Y389550
|
||||
M30
|
||||
=======使用CAM350 version 9.0.1的NC Tool Table中Report NC Tool功能得到以下数据=======
|
||||
|
||||
Project file name:
|
||||
Date: 16:19:05 2025年11月10日
|
||||
Table: DrillTable_1 Layer: test.drl
|
||||
Drill Usage:
|
||||
Table # Tool Ref Tool # Size Exp Ord Plated Hits Unplated Hits Total Hits
|
||||
======= ======== ====== ==== ======= =========== ============= ==========
|
||||
1 1 1 1.601 1 0 88 88
|
||||
1 2 2 1.601 2 0 88 88
|
||||
1 3 3 1.601 3 0 88 88
|
||||
1 4 4 1.701 4 0 85 85
|
||||
1 5 5 1.801 5 0 83 83
|
||||
1 6 6 1.901 6 0 81 81
|
||||
1 7 7 2.001 7 0 79 79
|
||||
1 8 8 1.501 8 0 91 91
|
||||
1 9 9 1.401 9 0 94 94
|
||||
1 10 10 1.301 10 0 97 97
|
||||
1 11 11 1.201 11 0 101 101
|
||||
1 12 12 1.101 12 0 106 106
|
||||
1 13 13 1.001 13 0 111 111
|
||||
1 14 14 0.706 14 0 132 132
|
||||
1 15 15 0.506 15 0 156 156
|
||||
=========================================================== =========== ============= ==========
|
||||
Totals: 0 1480 1480
|
||||
=======其中每个槽孔对应的孔数为=======
|
||||
孔径 孔数
|
||||
1.601 88
|
||||
1.601 88
|
||||
1.601 88
|
||||
1.701 85
|
||||
1.801 83
|
||||
1.901 81
|
||||
2.001 79
|
||||
1.501 91
|
||||
1.401 94
|
||||
1.301 97
|
||||
1.201 101
|
||||
1.101 106
|
||||
1.001 111
|
||||
0.706 132
|
||||
0.506 156
|
||||
```
|
||||
|
||||
文章可参考:https://blog.csdn.net/qq_21703003/article/details/128009811 , https://blog.csdn.net/weixin_30725315/article/details/97808151
|
||||
|
||||
85
Docs/刀具列表拖动排序功能说明.md
Normal file
85
Docs/刀具列表拖动排序功能说明.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# 刀具列表拖动排序功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
本功能为 DrillTools 应用程序添加了一个可拖动排序的刀具列表控件,允许用户通过直观的拖放操作调整刀具的处理顺序,并将新顺序应用到钻带输出中。
|
||||
|
||||
## 主要特性
|
||||
|
||||
### 1. 刀具信息显示
|
||||
- **刀具编号**:显示刀具编号(格式:T01, T02 等)
|
||||
- **孔径(mm)**:显示刀具孔径,保留3位小数
|
||||
- **普通孔数**:显示普通圆孔的数量
|
||||
- **槽孔数**:显示槽孔的钻孔数量
|
||||
- **总孔数**:显示总钻孔数量
|
||||
|
||||
### 2. 拖放排序
|
||||
- 点击并拖动刀具项到新位置
|
||||
- 释放鼠标完成排序
|
||||
- 支持视觉反馈,拖动时显示目标位置
|
||||
|
||||
### 3. 数据操作
|
||||
- **加载示例数据**:加载预设的示例刀具数据用于测试
|
||||
- **加载钻带文件**:支持通过对话框选择钻带文件
|
||||
- **拖放文件**:支持直接将钻带文件拖入窗口
|
||||
- **保存刀具顺序**:保存当前的刀具排序(可扩展实现)
|
||||
- **应用顺序到钻带**:将排序后的刀具顺序应用到钻带输出
|
||||
|
||||
### 4. 撤销/重做
|
||||
- **撤销**:撤销上一次排序操作
|
||||
- **重做**:重做已撤销的操作
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 启动应用程序
|
||||
运行 DrillTools 应用程序,主窗口将显示刀具列表和钻带内容区域。
|
||||
|
||||
### 2. 加载数据
|
||||
- 点击"加载示例数据"按钮加载测试数据
|
||||
- 或点击"加载钻带文件"按钮选择钻带文件
|
||||
- 或直接将钻带文件拖入窗口
|
||||
|
||||
### 3. 排序刀具
|
||||
- 在刀具列表中,点击要移动的刀具项
|
||||
- 按住鼠标左键,拖动到目标位置
|
||||
- 释放鼠标完成排序
|
||||
|
||||
### 4. 应用排序
|
||||
- 点击"应用顺序到钻带"按钮
|
||||
- 系统将根据新的刀具顺序重新生成钻带内容
|
||||
- 右侧钻带内容区域将显示更新后的内容
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. 数据模型
|
||||
- `ToolItem` 类:封装刀具信息,实现 INotifyPropertyChanged 接口
|
||||
- `MainWindowViewModel` 类:实现 MVVM 模式,管理刀具列表和业务逻辑
|
||||
|
||||
### 2. 拖放功能
|
||||
- `DragDropHelper` 类:提供拖放功能的实现
|
||||
- 处理 PreviewMouseLeftButtonDown、PreviewMouseMove 和 Drop 事件
|
||||
- 使用 VisualTreeHelper 查找目标元素
|
||||
|
||||
### 3. 钻带处理
|
||||
- 扩展 `DrillTapeProcessor` 类,添加重新排序功能
|
||||
- 解析原始钻带,按新顺序重新生成钻带内容
|
||||
|
||||
### 4. 用户界面
|
||||
- 使用 WPF ListView 控件显示刀具列表
|
||||
- 自定义样式提供良好的视觉反馈
|
||||
- 响应式布局适应不同窗口大小
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **文件格式**:支持 .txt 和 .dr2 格式的钻带文件
|
||||
2. **加密文件**:使用 cmd 命令读取加密钻带文件内容
|
||||
3. **数据完整性**:排序操作不会修改原始钻带数据,只影响输出顺序
|
||||
4. **性能考虑**:大量刀具时可能需要优化拖放性能
|
||||
|
||||
## 扩展功能建议
|
||||
|
||||
1. **保存/加载排序方案**:针对不同料号保存和加载排序方案
|
||||
2. **批量操作**:支持多选和批量排序操作
|
||||
3. **排序规则**:添加自动排序规则(如按孔径大小排序)
|
||||
4. **导入/导出**:支持导入/导出刀具排序配置
|
||||
5. **预览功能**:在应用排序前预览钻带输出变化
|
||||
223
Docs/刀具尾号类型功能实现说明.md
Normal file
223
Docs/刀具尾号类型功能实现说明.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# 刀具尾号类型功能实现说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
根据**钻孔孔径的尾号**来判断刀具类型,并在界面上显示。这个功能帮助用户更直观地了解每个刀具的具体类型和用途。
|
||||
|
||||
## 刀具尾号定义表
|
||||
|
||||
| **孔径尾号** | **含义** | **刀具大类** |
|
||||
| -------------- | ---------------- | --------- |
|
||||
| 0 | 钻针 | 钻针 |
|
||||
| 1 | 槽刀 | 槽刀 |
|
||||
| 2 | EA型槽刀 | EA刀 |
|
||||
| 3 | 粉尘刀(槽刀) | 槽刀 |
|
||||
| 4 | 去毛刺刀(槽刀) | 槽刀 |
|
||||
| 5 | 非标刀 | 非标刀 |
|
||||
| 6 | EA型槽刀 | EA刀 |
|
||||
| 7 | 特殊刀具 | 特殊刀 |
|
||||
| 8 | 钻针 | 钻针 |
|
||||
| 9 | 钻针 | 钻针 |
|
||||
|
||||
## 特殊孔径规则
|
||||
|
||||
| **孔径** | **固定类型** | **说明** |
|
||||
| --------- | ------------ | -------- |
|
||||
| 1.049 | 圆孔 | 特殊孔径 |
|
||||
| 3.175 | 圆孔 | 特殊孔径 |
|
||||
| 0.499 | 圆孔 | 机台码孔径 |
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. 新增枚举类型
|
||||
|
||||
#### ToolSuffixType 枚举
|
||||
```csharp
|
||||
public enum ToolSuffixType
|
||||
{
|
||||
Drill, // 0 - 钻针
|
||||
Slot, // 1 - 槽刀
|
||||
EASlot, // 2 - EA型槽刀
|
||||
DustSlot, // 3 - 粉尘刀(槽刀)
|
||||
DeburrSlot, // 4 - 去毛刺刀(槽刀)
|
||||
NonStandard, // 5 - 非标刀
|
||||
EASlot2, // 6 - EA型槽刀
|
||||
Special // 7 - 特殊刀具
|
||||
}
|
||||
```
|
||||
|
||||
#### ToolCategory 枚举
|
||||
```csharp
|
||||
public enum ToolCategory
|
||||
{
|
||||
Drill, // 钻针
|
||||
Slot, // 槽刀(包含槽刀、粉尘刀、去毛刺刀)
|
||||
EA, // EA刀(EA型槽刀)
|
||||
NonStandard,// 非标刀
|
||||
Special // 特殊刀具
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 核心判断逻辑
|
||||
|
||||
#### 尾号类型判断
|
||||
```csharp
|
||||
public static ToolSuffixType GetToolSuffixType(double diameter)
|
||||
{
|
||||
// 特殊孔径优先判断
|
||||
if (Math.Abs(diameter - 1.049) < 0.001 ||
|
||||
Math.Abs(diameter - 3.175) < 0.001 ||
|
||||
Math.Abs(diameter - 0.499) < 0.001)
|
||||
{
|
||||
return ToolSuffixType.Drill; // 固定为圆孔
|
||||
}
|
||||
|
||||
// 获取孔径的小数部分最后一位
|
||||
string diameterStr = diameter.ToString("F3");
|
||||
if (diameterStr.Length >= 4 && diameterStr.Contains('.'))
|
||||
{
|
||||
char lastChar = diameterStr[diameterStr.Length - 1];
|
||||
int suffix = int.Parse(lastChar.ToString());
|
||||
|
||||
return suffix switch
|
||||
{
|
||||
0 => ToolSuffixType.Drill,
|
||||
1 => ToolSuffixType.Slot,
|
||||
2 => ToolSuffixType.EASlot,
|
||||
3 => ToolSuffixType.DustSlot,
|
||||
4 => ToolSuffixType.DeburrSlot,
|
||||
5 => ToolSuffixType.NonStandard,
|
||||
6 => ToolSuffixType.EASlot2,
|
||||
7 => ToolSuffixType.Special,
|
||||
8 => ToolSuffixType.Drill,
|
||||
9 => ToolSuffixType.Drill,
|
||||
_ => ToolSuffixType.NonStandard
|
||||
};
|
||||
}
|
||||
|
||||
return ToolSuffixType.NonStandard; // 默认为非标刀
|
||||
}
|
||||
```
|
||||
|
||||
#### 大类判断
|
||||
```csharp
|
||||
public static ToolCategory GetToolCategory(ToolSuffixType suffixType)
|
||||
{
|
||||
return suffixType switch
|
||||
{
|
||||
ToolSuffixType.Drill => ToolCategory.Drill,
|
||||
ToolSuffixType.Slot or ToolSuffixType.DustSlot or ToolSuffixType.DeburrSlot => ToolCategory.Slot,
|
||||
ToolSuffixType.EASlot or ToolSuffixType.EASlot2 => ToolCategory.EA,
|
||||
ToolSuffixType.NonStandard => ToolCategory.NonStandard,
|
||||
ToolSuffixType.Special => ToolCategory.Special,
|
||||
_ => ToolCategory.NonStandard
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 3. UI界面更新
|
||||
|
||||
在刀具列表中新增了"尾号类型"列,显示在"类型"列后面:
|
||||
|
||||
| 刀具编号 | 孔径(mm) | 类型 | 尾号类型 | 孔数 |
|
||||
|---------|----------|------|----------|------|
|
||||
| T01 | 0.799 | 圆孔 | 槽刀 | 7 |
|
||||
| T02 | 0.656 | 圆孔 | EA型槽刀 | 5 |
|
||||
| T03 | 1.601 | 槽孔 | 粉尘刀 | 529 |
|
||||
| T04 | 0.499 | 机台码 | 去毛刺刀 | 57 |
|
||||
|
||||
### 4. 数据流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[钻带文件输入] --> B[解析刀具信息]
|
||||
B --> C[计算孔径尾号]
|
||||
C --> D[判断尾号类型]
|
||||
D --> E[设置刀具大类]
|
||||
E --> F[处理钻孔数据]
|
||||
F --> G[生成ToolResult]
|
||||
G --> H[创建ToolItem]
|
||||
H --> I[UI显示]
|
||||
|
||||
C --> C1[特殊孔径检查]
|
||||
C1 -->|1.049/3.175/0.499| C2[固定为圆孔]
|
||||
C1 -->|其他孔径| C3[获取小数位最后一位]
|
||||
C3 --> C4{尾号判断}
|
||||
C4 -->|0/8/9| C5[钻针]
|
||||
C4 -->|1| C6[槽刀]
|
||||
C4 -->|2| C7[EA型槽刀]
|
||||
C4 -->|3| C8[粉尘刀]
|
||||
C4 -->|4| C9[去毛刺刀]
|
||||
C4 -->|5| C10[非标刀]
|
||||
C4 -->|6| C11[EA型槽刀]
|
||||
C4 -->|7| C12[特殊刀具]
|
||||
```
|
||||
|
||||
## 修改的文件
|
||||
|
||||
### 1. ToolItem.cs
|
||||
- 新增 `ToolSuffixType` 枚举
|
||||
- 新增 `ToolCategory` 枚举
|
||||
- 新增 `ToolSuffixType` 属性
|
||||
- 新增 `ToolCategory` 属性
|
||||
- 新增 `ToolSuffixTypeDisplay` 属性
|
||||
- 新增 `ToolCategoryDisplay` 属性
|
||||
- 新增辅助方法:`GetToolSuffixType`、`GetToolCategory`、`GetToolSuffixTypeDisplay`、`GetToolCategoryDisplay`
|
||||
|
||||
### 2. DrillTapeProcessor.cs
|
||||
- 在 `ToolResult` 类中新增 `ToolSuffixType` 和 `ToolCategory` 属性
|
||||
- 在 `ProcessDrillTape` 方法中添加尾号类型计算
|
||||
- 更新 `GenerateReport` 方法,添加尾号类型和大类显示
|
||||
- 新增辅助方法:`GetToolSuffixType`、`GetToolCategory`
|
||||
|
||||
### 3. MainWindow.xaml
|
||||
- 窗口宽度从 900 调整为 1000
|
||||
- 在 ListView 中新增"尾号类型"列
|
||||
- 调整各列宽度以适应新布局
|
||||
|
||||
### 4. MainWindowViewModel.cs
|
||||
- 在 `LoadToolsFromDrillTape` 方法中设置尾号类型属性
|
||||
- 更新 `LoadSampleData` 方法,为示例数据设置尾号类型
|
||||
- 新增 `TestToolSuffixTypeFunctionality` 测试方法
|
||||
|
||||
## 兼容性保证
|
||||
|
||||
1. **向后兼容**:保留现有的 `ToolType` 枚举和属性,不影响现有功能
|
||||
2. **数据完整性**:新功能作为补充信息,不修改现有数据结构
|
||||
3. **功能独立**:尾号类型判断逻辑独立,不影响现有的槽孔计算和机台码处理
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 1. 单元测试
|
||||
- 验证不同尾号的刀具类型判断
|
||||
- 验证刀具大类分组逻辑
|
||||
- 验证显示文本的正确性
|
||||
|
||||
### 2. 集成测试
|
||||
- 验证钻带处理器的集成
|
||||
- 验证UI显示的正确性
|
||||
- 验证现有功能不受影响
|
||||
|
||||
### 3. 示例数据测试
|
||||
使用以下示例数据验证功能:
|
||||
- T01 (尾号1) → 槽刀
|
||||
- T02 (尾号2) → EA型槽刀
|
||||
- T03 (尾号3) → 粉尘刀
|
||||
- T04 (尾号4) → 去毛刺刀
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. **自动识别**:系统会根据刀具编号的尾号自动识别刀具类型
|
||||
2. **界面显示**:在刀具列表的"尾号类型"列中显示具体的刀具类型
|
||||
3. **报告生成**:在钻带处理报告中包含尾号类型和大类信息
|
||||
4. **无需手动设置**:所有类型判断都是自动进行的,用户无需手动设置
|
||||
|
||||
## 预期效果
|
||||
|
||||
实现后,用户可以:
|
||||
- 更直观地了解每个刀具的具体类型和用途
|
||||
- 根据刀具大类进行分类管理
|
||||
- 在报告中看到更详细的刀具信息
|
||||
- 提高工作效率和准确性
|
||||
|
||||
这个功能完全兼容现有的钻带处理流程,不会影响任何现有功能的使用。
|
||||
124
Docs/备份功能优化说明.md
Normal file
124
Docs/备份功能优化说明.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 备份功能优化说明
|
||||
|
||||
## 问题描述
|
||||
|
||||
原始的"应用并保存"功能在创建备份文件时,如果`.bak`文件已存在,会直接覆盖现有备份,没有提供用户选择或保留历史备份的选项。
|
||||
|
||||
## 优化方案
|
||||
|
||||
采用最小改动方案,在现有的`ApplyToolOrderToDrillTape`方法中添加智能备份逻辑:
|
||||
|
||||
### 功能特性
|
||||
|
||||
1. **智能检测**:自动检测`.bak`文件是否已存在
|
||||
2. **用户选择**:提供三种处理选项
|
||||
- 覆盖现有备份文件
|
||||
- 创建带时间戳的新备份文件
|
||||
- 取消保存操作
|
||||
3. **时间戳格式**:使用`yyyyMMdd_HHmmss`格式,确保文件名唯一且易于识别
|
||||
4. **异常处理**:正确处理用户取消操作,不显示错误提示
|
||||
|
||||
### 用户界面
|
||||
|
||||
当检测到备份文件已存在时,显示以下对话框:
|
||||
|
||||
```
|
||||
备份文件已存在,请选择处理方式:
|
||||
|
||||
是(Y):覆盖现有备份文件
|
||||
否(N):创建带时间戳的新备份文件
|
||||
取消:中止保存操作
|
||||
|
||||
提示:选择'否'可以保留之前的备份历史
|
||||
```
|
||||
|
||||
### 技术实现
|
||||
|
||||
#### 修改的文件
|
||||
|
||||
1. **MainWindowViewModel.cs**
|
||||
- 修改`ApplyToolOrderToDrillTape`方法
|
||||
- 添加备份文件检测和用户选择逻辑
|
||||
- 改进异常处理
|
||||
|
||||
2. **MainWindow.xaml.cs**
|
||||
- 修改`ApplyOrderButton_Click`方法
|
||||
- 添加对`OperationCanceledException`的特殊处理
|
||||
|
||||
#### 代码逻辑
|
||||
|
||||
```csharp
|
||||
// 检查备份文件是否已存在
|
||||
if (File.Exists(backupFilePath))
|
||||
{
|
||||
var result = 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("用户取消了保存操作");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 场景1:首次保存
|
||||
- 文件:`example.drl`
|
||||
- 备份:创建`example.drl.bak`
|
||||
|
||||
### 场景2:再次保存(选择覆盖)
|
||||
- 文件:`example.drl`
|
||||
- 现有备份:`example.drl.bak`
|
||||
- 结果:覆盖`example.drl.bak`
|
||||
|
||||
### 场景3:再次保存(选择创建时间戳备份)
|
||||
- 文件:`example.drl`
|
||||
- 现有备份:`example.drl.bak`
|
||||
- 结果:创建`example.drl.20231207_091600.bak`
|
||||
|
||||
## 优势
|
||||
|
||||
1. **数据安全**:避免意外覆盖重要备份
|
||||
2. **历史保留**:可以保留多个版本的备份文件
|
||||
3. **用户控制**:用户可以根据需要选择备份策略
|
||||
4. **最小改动**:不影响现有功能,只增强备份逻辑
|
||||
5. **向后兼容**:保持原有的工作流程
|
||||
|
||||
## 未来扩展建议
|
||||
|
||||
1. **配置化**:允许用户设置默认备份策略
|
||||
2. **自动清理**:定期清理过期的备份文件
|
||||
3. **备份管理**:提供备份文件管理界面
|
||||
4. **压缩备份**:对大文件提供压缩备份选项
|
||||
|
||||
## 测试
|
||||
|
||||
可以使用`BackupTest.cs`类来测试备份功能:
|
||||
|
||||
```csharp
|
||||
BackupTest.TestBackupFunctionality();
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
这个优化方案在保持代码简洁性的同时,显著提升了备份功能的安全性和灵活性,为用户提供了更好的数据保护体验。
|
||||
280
Docs/槽孔实际钻孔孔数相关资料/文章1.txt
Normal file
280
Docs/槽孔实际钻孔孔数相关资料/文章1.txt
Normal file
@@ -0,0 +1,280 @@
|
||||
PCB SLOT槽孔数量计算方法,同CAM350孔数一致 实现方法
|
||||
|
||||
最近有好几个写脚本的朋友问我,SLOT槽孔孔的如何计算的,要求孔数与CAM350孔数保持一致。
|
||||
|
||||
前几年通过在CAM350里面不断测试,结果是:CAM 350中SLOT槽孔,孔与孔之间最高位,凸位高度值为0.0127mm
|
||||
|
||||
这里将计算方法分享一下,下次有同样的问题可以看此篇文章即可得到答案了。哈。。。。
|
||||
|
||||
|
||||
|
||||
通过这个凸位值就很好的计算出SLOT槽孔数了,弧型SLOT槽的原理也是同样的。
|
||||
|
||||
一.SLOT槽为线段,求解SLOT槽孔数 (Mod类在后面代码中)
|
||||
|
||||
/// <summary>
|
||||
/// 求线Line slot槽孔数 (同CAM350一致)
|
||||
/// </summary>
|
||||
/// <param name="l"></param>
|
||||
/// <param name="tol_">凸位高度值</param>
|
||||
/// <returns></returns>
|
||||
public int l_2hole_count(gL l, double tol_ = 0.0127)
|
||||
{
|
||||
double r, center_L, hole_L;
|
||||
r = l.width / 1000 * 0.5;
|
||||
center_L = p2p_di(l.ps, l.pe);
|
||||
hole_L = Math.Sqrt(Math.Pow(r, 2) - Math.Pow(r - tol_, 2)) * 2;
|
||||
return (int)Math.Abs(Math.Floor(-center_L / hole_L)) + 1;
|
||||
}
|
||||
/// <summary>
|
||||
/// 返回两点之间欧氏距离
|
||||
/// </summary>
|
||||
/// <param name="p1"></param>
|
||||
/// <param name="p2"></param>
|
||||
/// <returns></returns>
|
||||
public double p2p_di(gPoint p1, gPoint p2)
|
||||
{
|
||||
return Math.Sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
|
||||
}
|
||||
二.SLOT槽为弧段,求解SLOT槽孔数 (Mod类在后面代码中)
|
||||
|
||||
/// <summary>
|
||||
/// 求弧Arc slot槽孔数 (同CAM350一致)
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="tol_">凸位高度值</param>
|
||||
/// <returns></returns>
|
||||
public int a_2hole_count(gA a, double tol_ = 0.0127)
|
||||
{
|
||||
double r, center_L, hole_L;
|
||||
r = a.width / 1000 * 0.5;
|
||||
center_L = a_Length(a);
|
||||
hole_L = Math.Sqrt(Math.Pow(r, 2) - Math.Pow(r - tol_, 2)) * 2;
|
||||
return (int)Math.Abs(Math.Floor(-center_L / hole_L)) + 1;
|
||||
}
|
||||
/// <summary>
|
||||
/// 求弧Arc长度
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <returns></returns>
|
||||
public double a_Length(gA a)
|
||||
{
|
||||
return pi / 180 * p2p_di(a.pc, a.ps) * a_Angle(a);
|
||||
}
|
||||
/// <summary>
|
||||
/// 求弧Arc圆心角 //后续改进 用叉积 与3P求角度求解 验证哪个效率高
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <returns></returns>
|
||||
public double a_Angle(gA a)
|
||||
{
|
||||
double angle_s, angle_e, angle_sum;
|
||||
if (a.ccw)
|
||||
{
|
||||
angle_s = p_ang(a.pc, a.pe);
|
||||
angle_e = p_ang(a.pc, a.ps);
|
||||
}
|
||||
else
|
||||
{
|
||||
angle_s = p_ang(a.pc, a.ps);
|
||||
angle_e = p_ang(a.pc, a.pe);
|
||||
}
|
||||
if (angle_s == 360) { angle_s = 0; }
|
||||
if (angle_e >= angle_s)
|
||||
angle_sum = 360 - Math.Abs(angle_s - angle_e);
|
||||
else
|
||||
angle_sum = Math.Abs(angle_s - angle_e);
|
||||
return angle_sum;
|
||||
}
|
||||
三.使用的Mod类
|
||||
|
||||
线 mod类型
|
||||
|
||||
/// <summary>
|
||||
/// Line 数据类型
|
||||
/// </summary>
|
||||
public struct gL
|
||||
{
|
||||
public gL(double ps_x, double ps_y, double pe_x, double pe_y, double width_)
|
||||
{
|
||||
this.ps = new gPoint(ps_x, ps_y);
|
||||
this.pe = new gPoint(pe_x, pe_y);
|
||||
this.negative = false;
|
||||
this.symbols = "r";
|
||||
this.attribut = string.Empty;
|
||||
this.width = width_;
|
||||
}
|
||||
public gL(gPoint ps_, gPoint pe_, double width_)
|
||||
{
|
||||
this.ps = ps_;
|
||||
this.pe = pe_;
|
||||
this.negative = false;
|
||||
this.symbols = "r";
|
||||
this.attribut = string.Empty;
|
||||
this.width = width_;
|
||||
}
|
||||
public gL(gPoint ps_, gPoint pe_, string symbols_, double width_)
|
||||
{
|
||||
this.ps = ps_;
|
||||
this.pe = pe_;
|
||||
this.negative = false;
|
||||
this.symbols = symbols_;
|
||||
this.attribut = string.Empty;
|
||||
this.width = width_;
|
||||
}
|
||||
public gPoint ps;
|
||||
public gPoint pe;
|
||||
public bool negative;//polarity-- positive negative
|
||||
public string symbols;
|
||||
public string attribut;
|
||||
public double width;
|
||||
public static gL operator +(gL l1, gPoint move_p)
|
||||
{
|
||||
l1.ps += move_p;
|
||||
l1.pe += move_p;
|
||||
return l1;
|
||||
}
|
||||
public static gL operator +(gL l1, gPP move_p)
|
||||
{
|
||||
l1.ps += move_p.p;
|
||||
l1.pe += move_p.p;
|
||||
return l1;
|
||||
}
|
||||
public static gL operator +(gL l1, gP move_p)
|
||||
{
|
||||
l1.ps += move_p.p;
|
||||
l1.pe += move_p.p;
|
||||
return l1;
|
||||
}
|
||||
public static gL operator -(gL l1, gPoint move_p)
|
||||
{
|
||||
l1.ps -= move_p;
|
||||
l1.pe -= move_p;
|
||||
return l1;
|
||||
}
|
||||
public static gL operator -(gL l1, gPP move_p)
|
||||
{
|
||||
l1.ps -= move_p.p;
|
||||
l1.pe -= move_p.p;
|
||||
return l1;
|
||||
}
|
||||
public static gL operator -(gL l1, gP move_p)
|
||||
{
|
||||
l1.ps -= move_p.p;
|
||||
l1.pe -= move_p.p;
|
||||
return l1;
|
||||
}
|
||||
}
|
||||
弧 mod类型
|
||||
|
||||
/// <summary>
|
||||
/// ARC 数据类型
|
||||
/// </summary>
|
||||
public struct gA
|
||||
{
|
||||
public gA(double ps_x, double ps_y, double pc_x, double pc_y, double pe_x, double pe_y, double width_, bool ccw_)
|
||||
{
|
||||
this.ps = new gPoint(ps_x, ps_y);
|
||||
this.pc = new gPoint(pc_x, pc_y);
|
||||
this.pe = new gPoint(pe_x, pe_y);
|
||||
this.negative = false;
|
||||
this.ccw = ccw_;
|
||||
this.symbols = "r";
|
||||
this.attribut = string.Empty;
|
||||
this.width = width_;
|
||||
}
|
||||
public gA(gPoint ps_, gPoint pc_, gPoint pe_, double width_, bool ccw_=false)
|
||||
{
|
||||
this.ps = ps_;
|
||||
this.pc = pc_;
|
||||
this.pe = pe_;
|
||||
this.negative = false;
|
||||
this.ccw = ccw_;
|
||||
this.symbols = "r";
|
||||
this.attribut = string.Empty;
|
||||
this.width = width_;
|
||||
}
|
||||
public gPoint ps;
|
||||
public gPoint pe;
|
||||
public gPoint pc;
|
||||
public bool negative;//polarity-- positive negative
|
||||
public bool ccw; //direction-- cw ccw
|
||||
public string symbols;
|
||||
public string attribut;
|
||||
public double width;
|
||||
public static gA operator +(gA arc1, gPoint move_p)
|
||||
{
|
||||
arc1.ps += move_p;
|
||||
arc1.pe += move_p;
|
||||
arc1.pc += move_p;
|
||||
return arc1;
|
||||
}
|
||||
public static gA operator +(gA arc1, gPP move_p)
|
||||
{
|
||||
arc1.ps += move_p.p;
|
||||
arc1.pe += move_p.p;
|
||||
arc1.pc += move_p.p;
|
||||
return arc1;
|
||||
}
|
||||
public static gA operator +(gA arc1, gP move_p)
|
||||
{
|
||||
arc1.ps += move_p.p;
|
||||
arc1.pe += move_p.p;
|
||||
arc1.pc += move_p.p;
|
||||
return arc1;
|
||||
}
|
||||
public static gA operator -(gA arc1, gPoint move_p)
|
||||
{
|
||||
arc1.ps -= move_p;
|
||||
arc1.pe -= move_p;
|
||||
arc1.pc -= move_p;
|
||||
return arc1;
|
||||
}
|
||||
public static gA operator -(gA arc1, gPP move_p)
|
||||
{
|
||||
arc1.ps -= move_p.p;
|
||||
arc1.pe -= move_p.p;
|
||||
arc1.pc -= move_p.p;
|
||||
return arc1;
|
||||
}
|
||||
public static gA operator -(gA arc1, gP move_p)
|
||||
{
|
||||
arc1.ps -= move_p.p;
|
||||
arc1.pe -= move_p.p;
|
||||
arc1.pc -= move_p.p;
|
||||
return arc1;
|
||||
}
|
||||
|
||||
}
|
||||
点 mod类型
|
||||
|
||||
/// <summary>
|
||||
/// 点 数据类型 (XY)
|
||||
/// </summary>
|
||||
public struct gPoint
|
||||
{
|
||||
public gPoint(gPoint p_)
|
||||
{
|
||||
this.x = p_.x;
|
||||
this.y = p_.y;
|
||||
}
|
||||
public gPoint(double x_val, double y_val)
|
||||
{
|
||||
this.x = x_val;
|
||||
this.y = y_val;
|
||||
}
|
||||
public double x;
|
||||
public double y;
|
||||
public static gPoint operator +(gPoint p1, gPoint p2)
|
||||
{
|
||||
p1.x += p2.x;
|
||||
p1.y += p2.y;
|
||||
return p1;
|
||||
}
|
||||
public static gPoint operator -(gPoint p1, gPoint p2)
|
||||
{
|
||||
p1.x -= p2.x;
|
||||
p1.y -= p2.y;
|
||||
return p1;
|
||||
}
|
||||
}
|
||||
448
Docs/槽孔实际钻孔孔数相关资料/文章2.txt
Normal file
448
Docs/槽孔实际钻孔孔数相关资料/文章2.txt
Normal file
@@ -0,0 +1,448 @@
|
||||
PCB genesis Slot槽转钻孔(不用G85命令)实现方法
|
||||
|
||||
PCB钻Slot槽一般都采用G85命令钻槽孔,而采用G85命令工程CAM无法准确的知道Slot槽钻多少个孔,并不能决定钻槽孔的顺序,因为采用G85命令钻孔密度与钻槽顺序由钻机本身决定的.在这里介绍一种如果不用G85命令,如何将Slot槽生成多个钻孔。
|
||||
|
||||
一.我们先了解一下G85命令钻槽
|
||||
|
||||
钻孔顺序
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
孔密度
|
||||
|
||||
|
||||
|
||||
连一篇文章有关于Slot槽孔数计算方式: https://www.cnblogs.com/pcbren/p/9379178.html
|
||||
|
||||
二.求解思路
|
||||
|
||||
1.通过孔密度,求出孔与孔中心距离
|
||||
|
||||
2.再以Slot槽的一端做为起点,增量值(孔中心距),方位角(Slot槽的方位角),逐个求出下一个钻孔位置.直到到达Slot槽终点节止。
|
||||
|
||||
三.C#简易代码实现:
|
||||
|
||||
1.Slot槽转钻孔代码(这里段代码实现将Slot槽转为钻孔,钻孔顺序是一个SLOT槽依次逐个从头钻到头尾,和G85命令钻槽顺序不一样)
|
||||
|
||||
|
||||
string drilllayer = "drl";
|
||||
gLayer layer = g.getFEATURES($"{drilllayer}", g.STEP, g.JOB, "mm", true);
|
||||
List<gPP> pList = new List<gPP>();
|
||||
foreach (var line in layer.Llist)
|
||||
{
|
||||
var HoleCenterDi = calc2.p_Convex(line.width * 0.0005);
|
||||
pList.AddRange(calc2.l_2Plist(line, HoleCenterDi, true));
|
||||
}
|
||||
foreach (var arc in layer.Alist)
|
||||
{
|
||||
var HoleCenterDi = calc2.p_Convex(arc.width * 0.0005);
|
||||
pList.AddRange(calc2.a_2Plist(arc, HoleCenterDi,2, true));
|
||||
}
|
||||
addCOM.pad(pList);
|
||||
View Code
|
||||
2.计算函数
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 通过孔半径与凸高位求 孔中心距
|
||||
/// </summary>
|
||||
/// <param name="Rradius">孔半径</param>
|
||||
/// <param name="tol_">凸位高度值</param>
|
||||
/// <returns></returns>
|
||||
public double p_Convex(double Rradius, double tol_ = 0.0127)
|
||||
{
|
||||
return Math.Sqrt(Math.Pow(Rradius, 2) - Math.Pow(Rradius - tol_, 2)) * 2;
|
||||
}
|
||||
/// <summary>
|
||||
/// 线Line 转点P组集
|
||||
/// </summary>
|
||||
/// <param name="l"></param>
|
||||
/// <param name="len_">点的间距</param>
|
||||
/// <returns></returns>
|
||||
public List<gPP> l_2Plist(gL l, double len_ = 0.1d, bool is_avg = false)
|
||||
{
|
||||
List<gPP> list_point = new List<gPP>();//采用优先占用线两端 如果有从线的一端出发增量间距后续再做更改
|
||||
double line_len = l_Length(l);
|
||||
gPP tempP;
|
||||
tempP.p = l.ps;
|
||||
tempP.symbols = l.symbols;
|
||||
tempP.width = l.width;
|
||||
list_point.Add(tempP);
|
||||
int avg_count = (int)(Math.Ceiling(line_len / len_)) - 1;
|
||||
if (avg_count > 1)
|
||||
{
|
||||
if (is_avg)
|
||||
len_ = line_len / avg_count;
|
||||
double angle_ = p_ang(l.ps, l.pe);
|
||||
for (int i = 0; i < avg_count; i++)
|
||||
{
|
||||
tempP = p_val_ang(tempP, len_, angle_);
|
||||
list_point.Add(tempP);
|
||||
}
|
||||
}
|
||||
tempP.p = l.pe;
|
||||
list_point.Add(tempP);
|
||||
return list_point;
|
||||
}
|
||||
/// <summary>
|
||||
/// 求方位角
|
||||
/// </summary>
|
||||
/// <param name="ps"></param>
|
||||
/// <param name="pe"></param>
|
||||
/// <returns></returns>
|
||||
public double p_ang(gPoint ps, gPoint pe)
|
||||
{
|
||||
double a_ang = Math.Atan((pe.y - ps.y) / (pe.x - ps.x)) / Math.PI * 180;
|
||||
//象限角 转方位角 计算所属象限 并求得方位角
|
||||
if (pe.x >= ps.x && pe.y >= ps.y) //↗ 第一象限
|
||||
{
|
||||
return a_ang;
|
||||
}
|
||||
else if (!(pe.x >= ps.x) && pe.y >= ps.y) // ↖ 第二象限
|
||||
{
|
||||
return a_ang + 180;
|
||||
}
|
||||
else if (!(pe.x >= ps.x) && !(pe.y >= ps.y)) //↙ 第三象限
|
||||
{
|
||||
return a_ang + 180;
|
||||
}
|
||||
else if (pe.x >= ps.x && !(pe.y >= ps.y)) // ↘ 第四象限
|
||||
{
|
||||
return a_ang + 360;
|
||||
}
|
||||
else
|
||||
{
|
||||
return a_ang;
|
||||
}
|
||||
}//求方位角
|
||||
/// <summary>
|
||||
/// 求增量坐标
|
||||
/// </summary>
|
||||
/// <param name="ps">起点</param>
|
||||
/// <param name="val">增量值</param>
|
||||
/// <param name="ang_direction">角度</param>
|
||||
/// <returns></returns>
|
||||
public gPP p_val_ang(gPP ps, double val, double ang_direction)
|
||||
{
|
||||
gPP pe = ps;
|
||||
pe.p.x = ps.p.x + val * Math.Cos(ang_direction * Math.PI / 180);
|
||||
pe.p.y = ps.p.y + val * Math.Sin(ang_direction * Math.PI / 180);
|
||||
return pe;
|
||||
}
|
||||
/// <summary>
|
||||
/// 求线Line长度
|
||||
/// </summary>
|
||||
/// <param name="l"></param>
|
||||
/// <param name="is_calc_width"></param>
|
||||
/// <returns></returns>
|
||||
public double l_Length(gL l, bool is_calc_width = false)
|
||||
{
|
||||
if (is_calc_width)
|
||||
return Math.Sqrt((l.ps.x - l.pe.x) * (l.ps.x - l.pe.x) + (l.ps.y - l.pe.y) * (l.ps.y - l.pe.y)) + l.width / 1000;
|
||||
else
|
||||
return Math.Sqrt((l.ps.x - l.pe.x) * (l.ps.x - l.pe.x) + (l.ps.y - l.pe.y) * (l.ps.y - l.pe.y));
|
||||
}
|
||||
/// <summary>
|
||||
/// 弧Arc 转点P组集
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="val_">此数值表示:分段数值</param>
|
||||
/// <param name="type_">代表值数值类型 【0】弧长 【1】角度 【2】弦长 </param>
|
||||
/// <param name="is_avg">是否平均分布 </param>
|
||||
/// <returns></returns>
|
||||
public List<gPP> a_2Plist(gA a, double val_ = 0.1d, int type_ = 0, bool is_avg = false)
|
||||
{
|
||||
List<gPP> list_point = new List<gPP>();
|
||||
gPP tempP;
|
||||
tempP.p = a.ps;
|
||||
tempP.symbols = a.symbols;
|
||||
tempP.width = a.width;
|
||||
list_point.Add(tempP);
|
||||
|
||||
double avg_count;
|
||||
double angle_val = 0;
|
||||
double rad_ = p2p_di(a.pc, a.pe);
|
||||
double sum_alge = a_Angle(a);
|
||||
if (type_ == 1) // 【1】角度
|
||||
{
|
||||
angle_val = val_;
|
||||
avg_count = (int)(Math.Ceiling(sum_alge / angle_val)) - 1; // 总角度/单角度
|
||||
}
|
||||
else if (type_ == 2) //【2】弦长
|
||||
{
|
||||
angle_val = Math.Asin(val_ / (rad_ * 2)) * 360 / pi;
|
||||
avg_count = (int)(Math.Ceiling(sum_alge / angle_val)) - 1; // 总角度/单角度
|
||||
}
|
||||
else // 【0】弧长
|
||||
{
|
||||
angle_val = val_ * 180 / (pi * rad_);
|
||||
avg_count = (int)(Math.Ceiling(sum_alge / angle_val)) - 1; // 总角度/单角度
|
||||
//avg_count = (int)(Math.Ceiling(a_Lenght(a) / val_)) - 1; // 或 总弧长/单弧长
|
||||
}
|
||||
if (is_avg)
|
||||
angle_val = sum_alge / avg_count;
|
||||
if (avg_count > 1)
|
||||
{
|
||||
gPP centerP = tempP;
|
||||
centerP.p = a.pc;
|
||||
double angle_s = p_ang(a.pc, a.ps);
|
||||
if (a.ccw) { angle_val = 0 - angle_val; }
|
||||
for (int i = 1; i < avg_count; i++)
|
||||
{
|
||||
tempP = p_val_ang(centerP, rad_, angle_s - angle_val * i);
|
||||
list_point.Add(tempP);
|
||||
}
|
||||
}
|
||||
if (!(zero(a.ps.x - a.pe.x) && zero(a.ps.y - a.pe.y)))
|
||||
{
|
||||
tempP.p = a.pe;
|
||||
list_point.Add(tempP);
|
||||
}
|
||||
return list_point;
|
||||
}
|
||||
/// <summary>
|
||||
/// 返回两点之间欧氏距离
|
||||
/// </summary>
|
||||
/// <param name="p1"></param>
|
||||
/// <param name="p2"></param>
|
||||
/// <returns></returns>
|
||||
public double p2p_di(gPoint p1, gPoint p2)
|
||||
{
|
||||
return Math.Sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
|
||||
}
|
||||
/// <summary>
|
||||
/// 求弧Arc圆心角 //后续改进 用叉积 与3P求角度求解 验证哪个效率高
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <returns></returns>
|
||||
public double a_Angle(gA a)
|
||||
{
|
||||
double angle_s, angle_e, angle_sum;
|
||||
if (a.ccw)
|
||||
{
|
||||
angle_s = p_ang(a.pc, a.pe);
|
||||
angle_e = p_ang(a.pc, a.ps);
|
||||
}
|
||||
else
|
||||
{
|
||||
angle_s = p_ang(a.pc, a.ps);
|
||||
angle_e = p_ang(a.pc, a.pe);
|
||||
}
|
||||
if (angle_s == 360) { angle_s = 0; }
|
||||
if (angle_e >= angle_s)
|
||||
angle_sum = 360 - Math.Abs(angle_s - angle_e);
|
||||
else
|
||||
angle_sum = Math.Abs(angle_s - angle_e);
|
||||
return angle_sum;
|
||||
}
|
||||
View Code
|
||||
3.Point,PAD,Line,Arc数据结构
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 精简 PAD 数据类型
|
||||
/// </summary>
|
||||
public struct gPP
|
||||
{
|
||||
public gPP(double x_val, double y_val, double width_)
|
||||
{
|
||||
this.p = new gPoint(x_val, y_val);
|
||||
this.symbols = "r";
|
||||
this.width = width_;
|
||||
}
|
||||
public gPP(gPoint p_, double width_)
|
||||
{
|
||||
this.p = p_;
|
||||
this.symbols = "r";
|
||||
this.width = width_;
|
||||
}
|
||||
public gPP(gPoint p_, string symbols_, double width_)
|
||||
{
|
||||
this.p = p_;
|
||||
this.symbols = symbols_;
|
||||
this.width = width_;
|
||||
}
|
||||
public gPoint p;
|
||||
public string symbols;
|
||||
public double width;
|
||||
public static gPP operator +(gPP p1, gPP p2)
|
||||
{
|
||||
p1.p += p2.p;
|
||||
return p1;
|
||||
}
|
||||
public static gPP operator +(gPP p1, gPoint p2)
|
||||
{
|
||||
p1.p += p2;
|
||||
return p1;
|
||||
}
|
||||
public static gPP operator -(gPP p1, gPP p2)
|
||||
{
|
||||
p1.p -= p2.p;
|
||||
return p1;
|
||||
}
|
||||
public static gPP operator -(gPP p1, gPoint p2)
|
||||
{
|
||||
p1.p -= p2;
|
||||
return p1;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 点 数据类型 (XY)
|
||||
/// </summary>
|
||||
public struct gPoint
|
||||
{
|
||||
public gPoint(gPoint p_)
|
||||
{
|
||||
this.x = p_.x;
|
||||
this.y = p_.y;
|
||||
}
|
||||
public gPoint(double x_val, double y_val)
|
||||
{
|
||||
this.x = x_val;
|
||||
this.y = y_val;
|
||||
}
|
||||
public double x;
|
||||
public double y;
|
||||
public static gPoint operator +(gPoint p1, gPoint p2)
|
||||
{
|
||||
p1.x += p2.x;
|
||||
p1.y += p2.y;
|
||||
return p1;
|
||||
}
|
||||
public static gPoint operator -(gPoint p1, gPoint p2)
|
||||
{
|
||||
p1.x -= p2.x;
|
||||
p1.y -= p2.y;
|
||||
return p1;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Line 数据类型
|
||||
/// </summary>
|
||||
public struct gL
|
||||
{
|
||||
public gL(double ps_x, double ps_y, double pe_x, double pe_y, double width_)
|
||||
{
|
||||
this.ps = new gPoint(ps_x, ps_y);
|
||||
this.pe = new gPoint(pe_x, pe_y);
|
||||
this.negative = false;
|
||||
this.symbols = "r";
|
||||
this.attribut = string.Empty;
|
||||
this.width = width_;
|
||||
}
|
||||
public gL(gPoint ps_, gPoint pe_, double width_)
|
||||
{
|
||||
this.ps = ps_;
|
||||
this.pe = pe_;
|
||||
this.negative = false;
|
||||
this.symbols = "r";
|
||||
this.attribut = string.Empty;
|
||||
this.width = width_;
|
||||
}
|
||||
public gL(gPoint ps_, gPoint pe_, string symbols_, double width_)
|
||||
{
|
||||
this.ps = ps_;
|
||||
this.pe = pe_;
|
||||
this.negative = false;
|
||||
this.symbols = symbols_;
|
||||
this.attribut = string.Empty;
|
||||
this.width = width_;
|
||||
}
|
||||
public gPoint ps;
|
||||
public gPoint pe;
|
||||
public bool negative;//polarity-- positive negative
|
||||
public string symbols;
|
||||
public string attribut;
|
||||
public double width;
|
||||
public static gL operator +(gL l1, gPoint move_p)
|
||||
{
|
||||
l1.ps += move_p;
|
||||
l1.pe += move_p;
|
||||
return l1;
|
||||
}
|
||||
public static gL operator +(gL l1, gP move_p)
|
||||
{
|
||||
l1.ps += move_p.p;
|
||||
l1.pe += move_p.p;
|
||||
return l1;
|
||||
}
|
||||
public static gL operator -(gL l1, gPoint move_p)
|
||||
{
|
||||
l1.ps -= move_p;
|
||||
l1.pe -= move_p;
|
||||
return l1;
|
||||
}
|
||||
public static gL operator -(gL l1, gP move_p)
|
||||
{
|
||||
l1.ps -= move_p.p;
|
||||
l1.pe -= move_p.p;
|
||||
return l1;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// ARC 数据类型
|
||||
/// </summary>
|
||||
public struct gA
|
||||
{
|
||||
public gA(double ps_x, double ps_y, double pc_x, double pc_y, double pe_x, double pe_y, double width_, bool ccw_)
|
||||
{
|
||||
this.ps = new gPoint(ps_x, ps_y);
|
||||
this.pc = new gPoint(pc_x, pc_y);
|
||||
this.pe = new gPoint(pe_x, pe_y);
|
||||
this.negative = false;
|
||||
this.ccw = ccw_;
|
||||
this.symbols = "r";
|
||||
this.attribut = string.Empty;
|
||||
this.width = width_;
|
||||
}
|
||||
public gA(gPoint ps_, gPoint pc_, gPoint pe_, double width_, bool ccw_ = false)
|
||||
{
|
||||
this.ps = ps_;
|
||||
this.pc = pc_;
|
||||
this.pe = pe_;
|
||||
this.negative = false;
|
||||
this.ccw = ccw_;
|
||||
this.symbols = "r";
|
||||
this.attribut = string.Empty;
|
||||
this.width = width_;
|
||||
}
|
||||
public gPoint ps;
|
||||
public gPoint pe;
|
||||
public gPoint pc;
|
||||
public bool negative;//polarity-- positive negative
|
||||
public bool ccw; //direction-- cw ccw
|
||||
public string symbols;
|
||||
public string attribut;
|
||||
public double width;
|
||||
public static gA operator +(gA arc1, gPoint move_p)
|
||||
{
|
||||
arc1.ps += move_p;
|
||||
arc1.pe += move_p;
|
||||
arc1.pc += move_p;
|
||||
return arc1;
|
||||
}
|
||||
public static gA operator +(gA arc1, gP move_p)
|
||||
{
|
||||
arc1.ps += move_p.p;
|
||||
arc1.pe += move_p.p;
|
||||
arc1.pc += move_p.p;
|
||||
return arc1;
|
||||
}
|
||||
public static gA operator -(gA arc1, gPoint move_p)
|
||||
{
|
||||
arc1.ps -= move_p;
|
||||
arc1.pe -= move_p;
|
||||
arc1.pc -= move_p;
|
||||
return arc1;
|
||||
}
|
||||
public static gA operator -(gA arc1, gP move_p)
|
||||
{
|
||||
arc1.ps -= move_p.p;
|
||||
arc1.pe -= move_p.p;
|
||||
arc1.pc -= move_p.p;
|
||||
return arc1;
|
||||
}
|
||||
}
|
||||
View Code
|
||||
Reference in New Issue
Block a user