summaryrefslogtreecommitdiff
path: root/pcbnew/zone_filling_algorithm.cpp
blob: e56e786eb94ae5f89edecf3697063d34709d73b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
/**
 * @file zone_filling_algorithm.cpp:
 * Algorithms used to fill a zone defined by a polygon and a filling starting point.
 */
/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2012 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr
 * Copyright (C) 1992-2012 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */


#include <algorithm> // sort

#include <fctsys.h>
#include <trigo.h>
#include <wxPcbStruct.h>

#include <class_zone.h>

#include <pcbnew.h>
#include <zones.h>

/* Build the filled solid areas data from real outlines (stored in m_Poly)
 * The solid areas can be more than one on copper layers, and do not have holes
  ( holes are linked by overlapping segments to the main outline)
 * aPcb: the current board (can be NULL for non copper zones)
 * aCornerBuffer: A reference to a buffer to store polygon corners, or NULL
 * if aCornerBuffer == NULL:
 * - m_FilledPolysList is used to store solid areas polygons.
 * - on copper layers, tracks and other items shapes of other nets are
 * removed from solid areas
 * if not null:
 * Only the zone outline (with holes, if any) are stored in aCornerBuffer
 * with holes linked. Therefore only one polygon is created
 * This function calls AddClearanceAreasPolygonsToPolysList()
 * to add holes for pads and tracks and other items not in net.
 */

bool ZONE_CONTAINER::BuildFilledSolidAreasPolygons( BOARD* aPcb, SHAPE_POLY_SET* aOutlineBuffer )
{
    /* convert outlines + holes to outlines without holes (adding extra segments if necessary)
     * m_Poly data is expected normalized, i.e. NormalizeAreaOutlines was used after building
     * this zone
     */

    if( GetNumCorners() <= 2 )  // malformed zone. polygon calculations do not like it ...
        return 0;

    // Make a smoothed polygon out of the user-drawn polygon if required
    if( m_smoothedPoly )
    {
        delete m_smoothedPoly;
        m_smoothedPoly = NULL;
    }

    switch( m_cornerSmoothingType )
    {
    case ZONE_SETTINGS::SMOOTHING_CHAMFER:
        m_smoothedPoly = m_Poly->Chamfer( m_cornerRadius );
        break;

    case ZONE_SETTINGS::SMOOTHING_FILLET:
        m_smoothedPoly = m_Poly->Fillet( m_cornerRadius, m_ArcToSegmentsCount );
        break;

    default:
        // Acute angles between adjacent edges can create issues in calculations,
        // in inflate/deflate outlines transforms, especially when the angle is very small.
        // We can avoid issues by creating a very small chamfer which remove acute angles,
        // or left it without chamfer and use only CPOLYGONS_LIST::InflateOutline to create
        // clearance areas
        m_smoothedPoly = m_Poly->Chamfer( Millimeter2iu( 0.0 ) );
        break;
    }

    if( aOutlineBuffer )
        aOutlineBuffer->Append( ConvertPolyListToPolySet( m_smoothedPoly->m_CornersList ) );

    /* For copper layers, we now must add holes in the Polygon list.
     * holes are pads and tracks with their clearance area
     * for non copper layers just recalculate the m_FilledPolysList
     * with m_ZoneMinThickness taken in account
     */
    else
    {
        m_FilledPolysList.RemoveAllContours();

        if( IsOnCopperLayer() )
        {
            AddClearanceAreasPolygonsToPolysList_NG( aPcb );
        }
        else
        {
            int margin = m_ZoneMinThickness / 2;
            m_FilledPolysList = ConvertPolyListToPolySet( m_smoothedPoly->m_CornersList );
            m_FilledPolysList.Inflate( -margin, 16 );
            m_FilledPolysList.Fracture();
        }

        if( m_FillMode )   // if fill mode uses segments, create them:
            FillZoneAreasWithSegments();

        m_IsFilled = true;
    }

    return true;
}


// Sort function to build filled zones
static bool SortByXValues( const int& a, const int &b )
{
    return a < b;
}


int ZONE_CONTAINER::FillZoneAreasWithSegments()
{
    int count = 0;
    std::vector <int> x_coordinates;
    bool error = false;
    int margin = m_ZoneMinThickness * 2 / 10;
    int minwidth = Mils2iu( 2 );
    margin = std::max ( minwidth, margin );
    int step = m_ZoneMinThickness - margin;
    step = std::max( step, minwidth );

    // Read all filled areas in m_FilledPolysList
    m_FillSegmList.clear();

    for ( int index = 0; index < m_FilledPolysList.OutlineCount(); index++ )
    {
        const SHAPE_LINE_CHAIN& outline = m_FilledPolysList.COutline( index );
        const BOX2I& rect = outline.BBox();

        // Calculate the y limits of the zone
        for( int refy = rect.GetY(), endy = rect.GetBottom(); refy < endy; refy += step )
        {
            // find all intersection points of an infinite line with polyline sides
            x_coordinates.clear();

            for( int v = 0; v < outline.PointCount(); v++ )
            {

                int seg_startX = outline.CPoint( v ).x;
                int seg_startY = outline.CPoint( v ).y;
                int seg_endX   = outline.CPoint( v + 1 ).x;
                int seg_endY   = outline.CPoint( v + 1 ).y;

                /* Trivial cases: skip if ref above or below the segment to test */
                if( ( seg_startY > refy ) && ( seg_endY > refy ) )
                    continue;

                // segment below ref point, or its Y end pos on Y coordinate ref point: skip
                if( ( seg_startY <= refy ) && (seg_endY <= refy ) )
                    continue;

                /* at this point refy is between seg_startY and seg_endY
                 * see if an horizontal line at Y = refy is intersecting this segment
                 */
                // calculate the x position of the intersection of this segment and the
                // infinite line this is more easier if we move the X,Y axis origin to
                // the segment start point:

                seg_endX -= seg_startX;
                seg_endY -= seg_startY;
                double newrefy = (double) ( refy - seg_startY );
                double intersec_x;

                if ( seg_endY == 0 )    // horizontal segment on the same line: skip
                    continue;

                // Now calculate the x intersection coordinate of the horizontal line at
                // y = newrefy and the segment from (0,0) to (seg_endX,seg_endY) with the
                // horizontal line at the new refy position the line slope is:
                // slope = seg_endY/seg_endX; and inv_slope = seg_endX/seg_endY
                // and the x pos relative to the new origin is:
                // intersec_x = refy/slope = refy * inv_slope
                // Note: because horizontal segments are already tested and skipped, slope
                // exists (seg_end_y not O)
                double inv_slope = (double) seg_endX / seg_endY;
                intersec_x = newrefy * inv_slope;
                x_coordinates.push_back( (int) intersec_x + seg_startX );
            }

            // A line scan is finished: build list of segments

            // Sort intersection points by increasing x value:
            // So 2 consecutive points are the ends of a segment
            sort( x_coordinates.begin(), x_coordinates.end(), SortByXValues );

            // Create segments

            if( !error && ( x_coordinates.size() & 1 ) != 0 )
            {   // An even number of coordinates is expected, because a segment has 2 ends.
                // An if this algorithm always works, it must always find an even count.
                wxString msg = wxT( "Fill Zone: odd number of points at y = " );
                msg << refy;
                wxMessageBox( msg );
                error = true;
            }

            if( error )
                break;

            int iimax = x_coordinates.size() - 1;

            for( int ii = 0; ii < iimax; ii += 2 )
            {
                wxPoint  seg_start, seg_end;
                count++;
                seg_start.x = x_coordinates[ii];
                seg_start.y = refy;
                seg_end.x = x_coordinates[ii + 1];
                seg_end.y = refy;
                SEGMENT segment( seg_start, seg_end );
                m_FillSegmList.push_back( segment );
            }
        }   //End examine segments in one area

        if( error )
            break;
    }

    if( !error )
        m_IsFilled = true;

    return count;
}