當PCB外形是直角時,通常工程製作外形(鑼帶)時,會將直角或尖角的地方倒成圓角,主要是為了防止板邊容易劃傷板且容易扎傷人
所以當客戶沒有特殊要求時,PCB外形是直角一般會預設倒角0.5mm圓角(如下圖所示)
原PCB外形 如下圖圖示:看了這個PCB外形,產生有2個問題點.
1.外形中哪些點需倒圓角?
2.如何怎麼倒圓角?
1.外形中哪些點需倒圓角?
看下圖: PCB外形倒圓角的點,剛好就是我們凸包需求出的點,接下來我們將玩轉凸包了,只要求出凸包,那麼就可以實現PCB板邊倒圓角啦。
求凸包的演算法:我們可以借鑑演算法導論中的查詢凸包的演算法(加以改進得到新的求凸包方法,詳見【方法一】與【方法二】)
2.如何怎麼倒圓角?
在下面有說明倒角方法.
方法一求凸點:【採用多輪遍歷,一遍一遍將凹點踢除,剩於的即是凸點】
方法一求凸點: 程式碼
/// <summary> /// 求最大多邊形最大凸包1 【採用多輪遍歷將凹點踢除,剩於的即是凸點】 /// </summary> /// <param name="gSur_Point_list"></param> /// <returns></returns> public List<gSur_Point> s_convex_polyon1(List<gSur_Point> gSur_Point_list) { add addCOM = new add(); bool isOK = true; List<gSur_Point> PointList = new List<gSur_Point>(); var isCCW = s_isCCW(gSur_Point_list); int sum = gSur_Point_list.Count() - 1; int n = gSur_Point_list.Count(); for (int i = 0; i < n; i++) { int IndexPre = (i - 1) % sum; if (IndexPre == -1) IndexPre = sum - 1; int IndexCurrent = i % sum; int IndexNext = (i + 1) % sum; if (gSur_Point_list[IndexPre].type_point > 0) continue; if (gSur_Point_list[IndexCurrent].type_point > 0) continue; var multiVal = multi(gSur_Point_list[IndexPre].p, gSur_Point_list[IndexCurrent].p, gSur_Point_list[IndexNext].p); if ((isCCW && multiVal > 0) || (!isCCW && multiVal < 0)) PointList.Add(gSur_Point_list[IndexCurrent]); else isOK = false; } List<gSur_Point> Point2List = new List<gSur_Point>(PointList); while (!isOK) { isOK = true; PointList.Clear(); PointList.AddRange(Point2List); Point2List.Clear(); sum = PointList.Count() - 1; n = PointList.Count(); for (int i = 0; i < n; i++) { int IndexPre = (i - 1) % sum; if (IndexPre == -1) IndexPre = sum - 1; int IndexCurrent = i % sum; int IndexNext = (i + 1) % sum; var multiVal = multi(PointList[IndexPre].p, PointList[IndexCurrent].p, PointList[IndexNext].p); if ((isCCW && multiVal > 0) || (!isCCW && multiVal < 0)) Point2List.Add(PointList[IndexCurrent]); else isOK = false; } } return Point2List; }
方法二求凸包:【採用一邊遍歷找出凸點並加入佇列,並同時將佇列中的凸點佇列中找出凹點踢除】
方法二求凸包程式碼:
/// <summary> /// 求最大多邊形最大凸包2 【採用一邊遍歷找出凸點並加入佇列,並同時將佇列中的凸點佇列中找出凹點踢除】 /// </summary> /// <param name="gSur_Point_list"></param> /// <returns></returns> public List<gSur_Point> s_convex_polyon2(List<gSur_Point> gSur_Point_list) { Stack<gSur_Point> StackPoint = new Stack<gSur_Point>(); var isCCW = s_isCCW(gSur_Point_list); int sum = gSur_Point_list.Count() - 1; int n = gSur_Point_list.Count(); for (int i = 0; i < n; i++) { int IndexPre = (i - 1) % sum; if (IndexPre == -1) IndexPre = sum - 1; int IndexCurrent = i % sum; int IndexNext = (i + 1) % sum; if (gSur_Point_list[IndexPre].type_point > 0) continue; if (gSur_Point_list[IndexCurrent].type_point > 0) continue; var multiVal = multi(gSur_Point_list[IndexPre].p, gSur_Point_list[IndexCurrent].p, gSur_Point_list[IndexNext].p); if ((isCCW && multiVal > 0) || (!isCCW && multiVal < 0)) { L1: if (StackPoint.Count > 1) { var Top1Point = StackPoint.Pop(); var Top2Point = StackPoint.Peek(); multiVal = multi(Top2Point.p, Top1Point.p, gSur_Point_list[IndexCurrent].p); if ((isCCW && multiVal > 0) || (!isCCW && multiVal < 0)) StackPoint.Push(Top1Point); else goto L1; } StackPoint.Push(gSur_Point_list[IndexCurrent]); } } return StackPoint.Reverse().ToList(); }
方法三求凸包:【按演算法導論Graham掃描法 各節點按方位角+距離 逆時針排序 依次檢查,當不屬凸點於則彈出】
方法三求凸包程式碼
/// <summary> /// 求最大多邊形最大凸包5 【按演算法導論Graham掃描法 各節點按方位角+距離 逆時針排序 依次檢查,當不屬凸點於則彈出】 /// 由於把各點的排列順序重新排序了,只支援折線節點(當存在弧節點時會出異常 !!!) /// </summary> /// <param name="gSur_Point_list"></param> /// <returns></returns> public List<gSur_Point> s_convex_polyon3(List<gSur_Point> gSur_Point_list) { var LeftBottomPoint = gSur_Point_list.OrderBy(tt => tt.p.y).ThenBy(tt => tt.p.x).FirstOrDefault(); gSur_Point_list.RemoveAt(gSur_Point_list.Count - 1); gSur_Point_list.ForEach(tt => { tt.Value = p2p_di(LeftBottomPoint.p, tt.p); tt.Angle = p_ang(LeftBottomPoint.p, tt.p); } ); gSur_Point_list = gSur_Point_list.OrderBy(tt => tt.Angle).ThenBy(tt => tt.Value).ToList(); gSur_Point_list.Add(gSur_Point_list[0]); Stack<gSur_Point> StackPoint = new Stack<gSur_Point>(); var isCCW = true; int sum = gSur_Point_list.Count() - 1; int n = gSur_Point_list.Count(); for (int i = 0; i < n; i++) { int IndexPre = (i - 1) % sum; if (IndexPre == -1) IndexPre = sum - 1; int IndexCurrent = i % sum; int IndexNext = (i + 1) % sum; var multiVal = multi(gSur_Point_list[IndexPre].p, gSur_Point_list[IndexCurrent].p, gSur_Point_list[IndexNext].p); if (isCCW && multiVal > 0) { L1: if (StackPoint.Count > 1) { var Top1Point = StackPoint.Pop(); var Top2Point = StackPoint.Peek(); multiVal = multi(Top2Point.p, Top1Point.p, gSur_Point_list[IndexCurrent].p); if (isCCW && multiVal > 0) StackPoint.Push(Top1Point); else goto L1; } StackPoint.Push(gSur_Point_list[IndexCurrent]); } } return StackPoint.Reverse().ToList(); }
公共方法與資料結構
/// <summary> /// Surface 座標泛型集類1 /// </summary> public class gSur_Point { public gSur_Point() { } public gSur_Point(double x_val, double y_val, byte type_point_) { this.p.x = x_val; this.p.y = y_val; this.type_point = type_point_; } public gSur_Point(gPoint p, byte type_point_) { this.p = p; this.type_point = type_point_; } public gPoint p; /// <summary> /// 0為折點 1為順時針 2為逆時針 /// </summary> public byte type_point { get; set; } = 0; /// <summary> /// 值 /// </summary> public double Value { get; set; } = 0; /// <summary> /// 角度 /// </summary> public double Angle { get; set; } = 0; /// <summary> /// 標記 /// </summary> public bool isFalg { get; set; } } /// <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; } public static gPoint operator +(gPoint p1, double val) { p1.x += val; p1.y += val; return p1; } public static bool operator ==(gPoint p1, gPoint p2) { return (p1.x == p2.x && p1.y == p2.y); } public static bool operator !=(gPoint p1, gPoint p2) { return !(p1.x == p2.x && p1.y == p2.y); } } /// <summary> /// 求叉積 判斷【點P與線L】位置關係【小於0】在右邊 【大於0】在左邊 【等於0】共線 /// </summary> /// <param name="ps"></param> /// <param name="pe"></param> /// <param name="p"></param> /// <returns>【小於0】在右邊 【大於0】在左邊 【等於0】共線</returns> public double multi(gPoint ps, gPoint pe, gPoint p) { return ((ps.x - p.x) * (pe.y - p.y) - (pe.x - p.x) * (ps.y - p.y)); } /// <summary> /// 檢測 Surface是否逆時針 /// </summary> /// <param name="gSur_Point_list"></param> /// <returns></returns> public bool s_isCCW(List<gSur_Point> gSur_Point_list) { double d = 0; int n = gSur_Point_list.Count() - 1; for (int i = 0; i < n; i++) { if (gSur_Point_list[i].type_point > 0) continue; int NextI = i + 1 + (gSur_Point_list[i + 1].type_point > 0 ? 1 : 0); d += -0.5 * (gSur_Point_list[NextI].p.y + gSur_Point_list[i].p.y) * (gSur_Point_list[NextI].p.x - gSur_Point_list[i].p.x); } return d > 0; } /// <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> /// 求方位角 /// </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; } }
方法一.也最簡單的倒角方法,我們將PCB板邊凸點找出來後,可以直接藉助genesis倒角功能就可以實現了
當然但偶爾會報錯的, 且當N個小線段組成的尖角倒角會出錯(要實現完美效果只有自己寫倒角演算法啦)
方法二:自己寫倒角演算法,這個演算法和加內角孔演算法類似(這裡只是介紹簡單的倒角)考慮特殊的需要擴充套件
可以參考這篇文章: https://www.cnblogs.com/pcbren/p/9665304.html