PCB 線路銅皮面積(殘銅率)計算的實現方法

pcbren發表於2019-05-23

     一個多月沒更新部落格園了,這裡繼續分享關於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 座標泛型集類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平方毫米

 

 

相關文章