PCB 線路銅皮面積(殘銅率)計算的實現方法
一個多月沒更新部落格園了,這裡繼續分享關於PCB工程相關一些知識,做過PCB工程都知道用使用genesis或incam是可以非常方便的計算得到銅皮面積這個引數【下圖】,但實際這個軟體是通過什麼演算法計算出銅面積的呢,這個我們不得而知,但接下來這裡介紹一種可以將【線路殘銅率(銅皮面積)】計算得出來的方法.
一.計算銅皮面積----公式與引數1.銅面積公式
公式=【銅面的多邊形面積】+【銅的多邊形周長*銅厚】-【孔的底面積】+【孔的圓柱面積】
注:看看計算公式是多麼簡單呀,是吧。下面重點講【銅面的多邊形面積】引數計算方法,因為其它引數過於簡單就不寫了
2.銅面積引數
1.表面銅面積【銅面的多邊形面積】
2.表面橫截面積【銅的多邊形周長*銅厚】
3.有銅孔孔徑面積【孔的底面積】
4.有銅孔孔壁面積保【孔的圓柱面積】
二.【銅面的多邊形面積】--計算方法
【銅面的多邊形面積】計算公式用 Shoelace公式 【鞋帶公式】,此公式可以計算任意凸凹多邊形,剛好是滿足計算需求的,但對於PCB 銅皮(Surface)來說,銅皮點節點存在弧節點,直接用此公式計算當然不行啦,需要改造一下才行的。接一來用兩種方法(丟失精度與精度)實現計算【銅面的多邊形面積】
1.【丟失精度】計算銅面積
將銅皮節點含有弧節點,全部轉為折線節點, 轉換後的弧長長度控制在0.1mm左右,當然弧長的長度值越小精度就越高,這樣一來計算量就上去了,經測試弧長控制0.1mm比較合適。銅面積計算不會丟失太多。
2.【精度】計算銅面積
將原有銅皮銅邊形分為2部份
1.分解第1部份 折線多邊形鞋帶公式求解
2.分解第2部份 圓弧多邊形扇形面積求解 (如何判斷,圓弧多邊形是刪除,還是增加呢,下面有說明)
上面帶來一個新的問題? 圖形面積合併計算,如何判斷,哪些弧形多邊形是【加】還是【減】呢
按下表的關係進行加減計算合併銅皮面積
三.【銅面的多邊形面積】--計算程式碼
1.計算銅面積呼叫程式碼
//獲取gtl 線路層(計算前轉為Surface銅皮屬性) gLayer workLayerInfo = g.getFEATURES("gtl"); //1.【丟失精度】計算銅面積 var areaLayer = calc2.s_area(workLayerInfo.Slist); //2. 【精度】計算銅面積 var areaLayer2 = calc2.s_area2(workLayerInfo.Slist); //計算銅的多邊形周長 var copperLenght = calc2.s_Length(workLayerInfo.Slist);
2.計算銅面積函式
/// <summary> /// 【丟失精度】計算銅面積 /// </summary> /// <param name="gS_list"></param> /// <returns></returns> public double s_area(List<gS> gS_list) { double SurfaceArea = 0; foreach (var gS_item in gS_list) { foreach (var Polyline in gS_item.sur_group) { var sur_list = s_2gSur_Point(Polyline.sur_list); if (Polyline.is_hole) SurfaceArea -= s_area(sur_list); else SurfaceArea += s_area(sur_list); } } return SurfaceArea; } /// <summary> /// 【丟失精度】計算銅面積 /// </summary> /// <param name="gSur_Point_list"></param> /// <returns></returns> public double s_area(List<gSur_Point> gSur_Point_list) { int Point_Count = gSur_Point_list.Count() - 1; if (Point_Count < 2) return 0; double PolylineArea = 0; double ArcArea = 0; for (int i = 1; i <= Point_Count; i++) { PolylineArea += gSur_Point_list[i - 1].p.x * gSur_Point_list[i].p.y - gSur_Point_list[i - 1].p.y * gSur_Point_list[i].p.x; } PolylineArea = Math.Abs(PolylineArea * 0.5); return PolylineArea; } /// <summary> /// 【精度】計算銅面積 /// </summary> /// <param name="gSur_Point_list"></param> /// <returns></returns> public double s_area2(List<gSur_Point> gSur_Point_list) { int Point_Count = gSur_Point_list.Count() - 1; if (Point_Count < 2) return 0; double PolylineArea = 0; double ArcArea = 0; bool isCCW = s_isCCW(gSur_Point_list); for (int i = 1; i <= Point_Count; i++) { if (gSur_Point_list[i].type_point > 0) { double a_area = a_Area(gSur_Point_list[i - 1].p, gSur_Point_list[i].p, gSur_Point_list[i + 1].p, gSur_Point_list[i].type_point == 2); if (isCCW) { if (gSur_Point_list[i].type_point == 2) ArcArea += a_area; else ArcArea -= a_area; } else { if (gSur_Point_list[i].type_point == 2) ArcArea -= a_area; else ArcArea += a_area; } } PolylineArea += gSur_Point_list[i - 1].p.x * gSur_Point_list[i].p.y - gSur_Point_list[i - 1].p.y * gSur_Point_list[i].p.x; } PolylineArea = Math.Abs(PolylineArea * 0.5); PolylineArea += ArcArea; //var isCW = s_isCW(gSur_Point_list); //PolylineArea += (isCCW ? -ArcArea : ArcArea); return PolylineArea; } /// <summary> /// 求弧Arc 扇形面積 /// </summary> /// <param name="a"></param> /// <returns></returns> public double a_Area(gPoint ps, gPoint pc, gPoint pe, bool ccw, bool islg180deg = false) { double r_ = p2p_di(pc, ps); return pi * r_ * r_ * a_Angle(ps, pc, pe, ccw, islg180deg) / 360; } /// <summary> /// 求弧Arc圓心角 3點 //後續改進 用叉積 與3P求角度求解 驗證哪個效率高 /// </summary> /// <param name="ps"></param> /// <param name="pc"></param> /// <param name="pe"></param> /// <param name="ccw"></param> /// <returns></returns> public double a_Angle(gPoint ps, gPoint pc, gPoint pe, bool ccw, bool islg180deg = false) { double angle_s, angle_e, angle_sum; if (ccw) { angle_s = p_ang(pc, pe); angle_e = p_ang(pc, ps); } else { angle_s = p_ang(pc, ps); angle_e = p_ang(pc, pe); } if (angle_s == 360) { angle_s = 0; } if (angle_e >= angle_s) { angle_sum = 360 - (angle_e - angle_s); //360 - Math.Abs(angle_s - angle_e); } else { angle_sum = angle_s - angle_e;//Math.Abs(angle_s - angle_e); } if (islg180deg && angle_sum > 180) { angle_sum = 360 - angle_sum; } return angle_sum; } /// <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> /// 將gSur_Point中含弧的節點轉為線 /// </summary> /// <param name="gSur_Point_list"></param> /// <param name="val_">此數值表示:分段數值</param> /// <param name="type_">代表值數值型別 【0】弧長 【1】角度 【2】弦長 </param> /// <returns></returns> public List<gSur_Point> s_2gSur_Point(List<gSur_Point> gSur_Point_list, double val_ = 1d, int type_ = 1) { List<gSur_Point> resultList = new List<gSur_Point>(); if (gSur_Point_list.Count > 2) { bool is_flag = false; resultList.Add(gSur_Point_list[0]); for (int j = 1; j < gSur_Point_list.Count; j++) { if (is_flag) { is_flag = false; continue; } if (gSur_Point_list[j].type_point > 0) { var aData = new gA(gSur_Point_list[j - 1].p, gSur_Point_list[j].p, gSur_Point_list[j + 1].p, 100, gSur_Point_list[j].type_point == 2 ? true : false); var PlistData = a_2Plist(aData, val_, type_, true); resultList.AddRange(PlistData.Select(tt => new gSur_Point(tt.p, 0)).ToList()); is_flag = true; } else { resultList.Add(gSur_Point_list[j]); } } } return resultList; } /// <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.Floor(sum_alge / angle_val)); // 總角度/單角度 } else if (type_ == 2) //【2】弦長 { angle_val = Math.Asin(val_ / (rad_ * 2)) * 360 / pi; avg_count = (int)(Math.Ceiling(sum_alge / angle_val) + eps) - 1; // 總角度/單絃長 } else // 【0】弧長 { angle_val = val_ * 180 / (pi * rad_); avg_count = (int)(Math.Ceiling(sum_alge / angle_val) + eps) - 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); // } 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; } /// <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; }View Code
3.計算銅的多邊形周長函式
/// <summary> /// 求Surface 總周長 /// </summary> /// <param name="gS_list"></param> /// <returns></returns> public double s_Length(List<gS> gS_list) { int Surface_Count = gS_list.Count(); double SurfaceArea = 0; foreach (var gS_item in gS_list) { foreach (var Polyline in gS_item.sur_group) { SurfaceArea += s_Length(Polyline.sur_list); } } return SurfaceArea; } /// <summary> /// 求Surface 總周長 /// </summary> /// <param name="gSur_Point_list"></param> /// <returns></returns> public double s_Length(List<gSur_Point> gSur_Point_list) { double sum_lenght = 0; bool is_flag = false; bool ccw = false; for (int i = 1; i < gSur_Point_list.Count; i++) { if (is_flag) { is_flag = false; continue; } if (gSur_Point_list[i].type_point > 0) { if (gSur_Point_list[i].type_point == 2) ccw = true; else ccw = false; sum_lenght += a_Length(gSur_Point_list[i - 1].p, gSur_Point_list[i].p, gSur_Point_list[i + 1].p, ccw); is_flag = true; } else { sum_lenght += l_Length(gSur_Point_list[i - 1].p, gSur_Point_list[i].p); } } return sum_lenght; } /// <summary> /// 求弧Arc長度 3點 /// </summary> /// <param name="ps"></param> /// <param name="pc"></param> /// <param name="pe"></param> /// <returns></returns> public double a_Length(gPoint ps, gPoint pc, gPoint pe, bool ccw = false) { return pi / 180 * p2p_di(pc, ps) * a_Angle(ps, pc, pe, ccw); } /// <summary> /// 求線Line長度 2點 /// </summary> /// <param name="ps"></param> /// <param name="pe"></param> /// <returns></returns> public double l_Length(gPoint ps, gPoint pe) { return Math.Sqrt((ps.x - pe.x) * (ps.x - pe.x) + (ps.y - pe.y) * (ps.y - pe.y)); }View Code
4.資料結構
/// <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> /// Surface 座標泛型集類2 /// </summary> public class gSur_list { public List<gSur_Point> sur_list = new List<gSur_Point>(); /// <summary> /// 是否為空洞 /// </summary> public bool is_hole { get; set; } /// <summary> /// 是否逆時針 /// </summary> public bool is_ccw { get; set; } } /// <summary> /// Surface 座標泛型集類3 /// </summary> public class gS { public List<gSur_list> sur_group = new List<gSur_list>(); /// <summary> /// 是否為負 polarity-- P N /// </summary> public bool negative { get; set; } public string attribut { 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; } } /// <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" + width_.ToString(); 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" + width_.ToString(); 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; } }View Code
四.【銅面的多邊形面積】--實現效果
經測試,發現程式計算出來銅面積與genesis銅面積計算存在少量的偏差(猜側奧寶為了達到越大規模銅面積計算,採用丟失精度計算銅面積達到快速計算銅面積的目的)
小結: 採用程式計算在小規模銅面積計算,不管是計算速度還是銅面積計算精度已超過genesis.
下例:genesis計算銅面積存一定偏差,實際PAD尺寸為4X3mm 面積為:12平方毫米 而genesis計算得到面積為12.004平方毫米
&n