KiCad PCB EDA Suite
teardrop_utils.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2021 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25/*
26 * Some calculations (mainly computeCurvedForRoundShape) are derived from
27 * https://github.com/NilujePerchut/kicad_scripts/tree/master/teardrops
28 */
29
31#include <pcb_track.h>
32#include <pad.h>
33#include <zone_filler.h>
34#include <board_commit.h>
35
36#include "teardrop.h"
40#include <bezier_curves.h>
41
42#include <wx/log.h>
43
44
45void TRACK_BUFFER::AddTrack( PCB_TRACK* aTrack, int aLayer, int aNetcode )
46{
47 auto item = m_map_tracks.find( idxFromLayNet( aLayer, aNetcode ) );
48 std::vector<PCB_TRACK*>* buffer;
49
50 if( item == m_map_tracks.end() )
51 {
52 buffer = new std::vector<PCB_TRACK*>;
53 m_map_tracks[idxFromLayNet( aLayer, aNetcode )] = buffer;
54 }
55 else
56 {
57 buffer = (*item).second;
58 }
59
60 buffer->push_back( aTrack );
61}
62
63
65 m_Parent( aVia )
66{
67 m_Pos = aVia->GetPosition();
68 m_Width = aVia->GetWidth();
69 m_Drill = aVia->GetDrillValue();
70 m_NetCode = aVia->GetNetCode();
71 m_IsRound = true;
72 m_IsPad = false;
73}
74
75
77 m_Parent( aPad )
78{
79 m_Pos = aPad->GetPosition();
80 m_Width = std::min( aPad->GetSize().x, aPad->GetSize().y );
81 m_Drill = std::min( aPad->GetDrillSizeX(), aPad->GetDrillSizeY() );
82 m_NetCode = aPad->GetNetCode();
84 ( aPad->GetShape() == PAD_SHAPE::OVAL
85 && aPad->GetSize().x == aPad->GetSize().y );
86 m_IsPad = true;
87}
88
89
90VIAPAD::VIAPAD( PCB_TRACK* aTrack, ENDPOINT_T aEndPoint ) :
91 m_Parent( aTrack )
92{
93 m_Pos = aEndPoint == ENDPOINT_START ? aTrack->GetStart() : aTrack->GetEnd();
94 m_Width =aTrack->GetWidth();
95 m_Drill = 0;
96 m_NetCode = aTrack->GetNetCode();
97 m_IsRound = true;
98 m_IsPad = false;
99}
100
101
102void TEARDROP_MANAGER::collectVias( std::vector< VIAPAD >& aList ) const
103{
104 for( PCB_TRACK* item : m_board->Tracks() )
105 {
106 if( item->Type() != PCB_VIA_T )
107 continue;
108
109 aList.emplace_back( static_cast<PCB_VIA*>( item ) );
110 }
111}
112
113
114void TEARDROP_MANAGER::collectPadsCandidate( std::vector< VIAPAD >& aList,
115 bool aDrilledViaPad,
116 bool aRoundShapesOnly,
117 bool aIncludeNotDrilled ) const
118{
119 for( FOOTPRINT* fp : m_board->Footprints() )
120 {
121 for( PAD* pad : fp->Pads() )
122 {
123 if( !pad->IsOnCopperLayer() )
124 continue;
125
126 if( pad->GetNetCode() <= 0 ) // Not connected, so a teardrop cannot be attached
127 continue;
128
129 if( pad->GetShape() == PAD_SHAPE::CUSTOM ) // A teardrop shape cannot be built
130 continue;
131
132 if( aRoundShapesOnly )
133 {
134 bool round_shape = pad->GetShape() == PAD_SHAPE::CIRCLE
135 || ( pad->GetShape() == PAD_SHAPE::OVAL
136 && pad->GetSize().x == pad->GetSize().y );
137 if( !round_shape )
138 continue;
139 }
140
141 if( pad->HasHole() && !aDrilledViaPad )
142 continue;
143
144 if( pad->HasHole() || aIncludeNotDrilled )
145 aList.emplace_back( pad );
146 }
147 }
148}
149
150
151void TEARDROP_MANAGER::collectTeardrops( std::vector< ZONE* >& aList ) const
152{
153 for( ZONE* zone : m_board->Zones() )
154 {
155 if( zone->IsTeardropArea() )
156 aList.push_back( zone );
157 }
158}
159
160
162{
163 for( ZONE* zone: m_board->Zones() )
164 {
165 // Skip teardrops
166 if( zone->IsTeardropArea() )
167 continue;
168
169 // Only consider zones on the same layer
170 if( !zone->IsOnLayer( aTrack->GetLayer() ) )
171 continue;
172
173 if( zone->GetNetCode() == aTrack->GetNetCode() )
174 {
175 if( zone->Outline()->Contains( VECTOR2I( aViapad.m_Pos ) ) )
176 {
177 // Ensure the pad (if it is a pad) can be connected by the zone
178 if( aViapad.m_IsPad )
179 {
180 PAD *pad = static_cast< PAD* >( aViapad.m_Parent );
181
182 if( zone->GetPadConnection() == ZONE_CONNECTION::NONE
183 || pad->GetZoneConnection() == ZONE_CONNECTION::NONE )
184 {
185 return false;
186 }
187 }
188
189 return true;
190 }
191 }
192 }
193
194 return false;
195}
196
197
199 const VECTOR2I& aEndPoint,
200 TRACK_BUFFER& aTrackLookupList ) const
201{
202 EDA_ITEM_FLAGS match = 0; // to return the end point EDA_ITEM_FLAGS:
203 // 0, STARTPOINT, ENDPOINT
204 int matches = 0; // Count of candidates: only 1 is acceptable
205 PCB_TRACK* candidate = nullptr; // a reference to the track connected
206
207 std::vector<PCB_TRACK*>* currlist = aTrackLookupList.GetTrackList( aTrackRef->GetLayer(),
208 aTrackRef->GetNetCode() );
209
210 for( PCB_TRACK* curr_track: *currlist )
211 {
212 if( curr_track == aTrackRef )
213 continue;
214
215 match = curr_track->IsPointOnEnds( aEndPoint, m_tolerance);
216
217 if( match )
218 {
219 // if faced with a Y junction, choose the track longest segment as candidate
220 matches++;
221
222 if( matches > 1 )
223 {
224 double previous_len = candidate->GetLength();
225 double curr_len = curr_track->GetLength();
226
227 if( previous_len >= curr_len )
228 continue;
229 }
230
231 aMatchType = match;
232 candidate = curr_track;
233 }
234 }
235
236 return candidate;
237}
238
239
240
245{
246 VECTOR2D vect( aVector );
247 double norm = vect.EuclideanNorm();
248 return vect / norm;
249}
250
251
252/*
253 * Compute the curve part points for teardrops connected to a round shape
254 * The Bezier curve control points are optimized for a round pad/via shape,
255 * and do not give a good curve shape for other pad shapes
256 */
258 std::vector<VECTOR2I>& aPoly,
259 int aTrackHalfWidth,
260 VECTOR2D aTrackDir, VIAPAD& aViaPad,
261 std::vector<VECTOR2I>& pts ) const
262{
263 // in pts:
264 // A and B are points on the track ( pts[0] and pts[1] )
265 // C and E are points on the aViaPad ( pts[2] and pts[4] )
266 // D is the aViaPad centre ( pts[3] )
267 double Vpercent = aCurrParams->m_HeightRatio;
268 int td_height = aViaPad.m_Width * Vpercent;
269
270 // First, calculate a aVpercent equivalent to the td_height clamped by aTdMaxHeight
271 // We cannot use the initial aVpercent because it gives bad shape with points
272 // on aViaPad calculated for a clamped aViaPad size
273 if( aCurrParams->m_TdMaxHeight > 0 && aCurrParams->m_TdMaxHeight < td_height )
274 Vpercent *= (double)aCurrParams->m_TdMaxHeight / td_height;
275
276 int radius = aViaPad.m_Width / 2;
277 double minVpercent = double( aTrackHalfWidth ) / radius;
278 double weaken = (Vpercent - minVpercent) / ( 1 - minVpercent ) / radius;
279
280 double biasBC = 0.5 * SEG( pts[1], pts[2] ).Length();
281 double biasAE = 0.5 * SEG( pts[4], pts[0] ).Length();
282
283 VECTOR2I vecC = (VECTOR2I)pts[2] - aViaPad.m_Pos;
284 VECTOR2I tangentC = VECTOR2I( pts[2].x - vecC.y * biasBC * weaken,
285 pts[2].y + vecC.x * biasBC * weaken );
286 VECTOR2I vecE = (VECTOR2I)pts[4] - aViaPad.m_Pos;
287 VECTOR2I tangentE = VECTOR2I( pts[4].x + vecE.y * biasAE * weaken,
288 pts[4].y - vecE.x * biasAE * weaken );
289
290 VECTOR2I tangentB = VECTOR2I( pts[1].x - aTrackDir.x * biasBC, pts[1].y - aTrackDir.y * biasBC );
291 VECTOR2I tangentA = VECTOR2I( pts[0].x - aTrackDir.x * biasAE, pts[0].y - aTrackDir.y * biasAE );
292
293 std::vector<VECTOR2I> curve_pts;
294 curve_pts.reserve( aCurrParams->m_CurveSegCount );
295 BEZIER_POLY( pts[1], tangentB, tangentC, pts[2] ).GetPoly( curve_pts, 0,
296 aCurrParams->m_CurveSegCount );
297
298 for( VECTOR2I& corner: curve_pts )
299 aPoly.push_back( corner );
300
301 aPoly.push_back( pts[3] );
302
303 curve_pts.clear();
304 BEZIER_POLY( pts[4], tangentE, tangentA, pts[0] ).GetPoly( curve_pts, 0,
305 aCurrParams->m_CurveSegCount );
306
307 for( VECTOR2I& corner: curve_pts )
308 aPoly.push_back( corner );
309}
310
311
312/*
313 * Compute the curve part points for teardrops connected to a rectangular/polygonal shape
314 * The Bezier curve control points are not optimized for a special shape
315 */
317 std::vector<VECTOR2I>& aPoly, int aTdHeight,
318 int aTrackHalfWidth, VIAPAD& aViaPad,
319 std::vector<VECTOR2I>& aPts ) const
320{
321 // in aPts:
322 // A and B are points on the track ( pts[0] and pts[1] )
323 // C and E are points on the aViaPad ( pts[2] and pts[4] )
324 // D is the aViaPad centre ( pts[3] )
325
326 // side1 is( aPts[1], aPts[2] ); from track to via
327 VECTOR2I side1( aPts[2] - aPts[1] ); // vector from track to via
328 // side2 is ( aPts[4], aPts[0] ); from via to track
329 VECTOR2I side2( aPts[4] - aPts[0] ); // vector from track to via
330
331 std::vector<VECTOR2I> curve_pts;
332 curve_pts.reserve( aCurrParams->m_CurveSegCount );
333
334 // Note: This side is from track to via
335 VECTOR2I ctrl1 = ( aPts[1] + aPts[1] + aPts[2] ) / 3;
336 VECTOR2I ctrl2 = ( aPts[1] + aPts[2] + aPts[2] ) / 3;
337
338 // The control points must be moved toward the polygon inside, in order to give a curved shape
339 // The move vector is perpendicular to the vertex (side 1 or side 2), and its
340 // value is delta, depending on the sizes of via and track
341 int delta = ( aTdHeight / 2 - aTrackHalfWidth );
342
343 delta /= 4; // A scaling factor giving a fine shape, defined from tests.
344 // However for short sides, the value of delta must be reduced, depending
345 // on the side length
346 // We use here a max delta value = side_length/8, defined from tests
347
348 int side_length = side1.EuclideanNorm();
349 int delta_effective = std::min( delta, side_length/8 );
350 // The move vector depend on the quadrant: it must be always defined to create a
351 // curve with a direction toward the track
352 EDA_ANGLE angle1( side1 );
353 int sign = std::abs( angle1 ) >= ANGLE_90 ? 1 : -1;
354 VECTOR2I bias( 0, sign * delta_effective );
355
356 // Does not works well with the current algo, due to an initial bug.
357 // but I (JPC) keep it here because probably it will gives a better shape
358 // if the algo is refined.
359 // RotatePoint( bias, angle1 );
360
361 ctrl1.x += bias.x;
362 ctrl1.y += bias.y;
363 ctrl2.x += bias.x;
364 ctrl2.y += bias.y;
365
366 BEZIER_POLY( aPts[1], ctrl1, ctrl2, aPts[2] ).GetPoly( curve_pts, 0,
367 aCurrParams->m_CurveSegCount );
368
369 for( VECTOR2I& corner: curve_pts )
370 aPoly.push_back( corner );
371
372 aPoly.push_back( aPts[3] );
373
374 // Note: This side is from via to track
375 curve_pts.clear();
376 ctrl1 = ( aPts[4] + aPts[4] + aPts[0] ) / 3;
377 ctrl2 = ( aPts[4] + aPts[0] + aPts[0] ) / 3;
378
379 side_length = side2.EuclideanNorm();
380 delta_effective = std::min( delta, side_length/8 );
381
382 EDA_ANGLE angle2( side2 );
383 sign = std::abs( angle2 ) <= ANGLE_90 ? 1 : -1;
384
385 bias = VECTOR2I( 0, sign * delta_effective );
386
387 // Does not works well with the current algo
388 // RotatePoint( bias, angle2 );
389
390 ctrl1.x += bias.x;
391 ctrl1.y += bias.y;
392 ctrl2.x += bias.x;
393 ctrl2.y += bias.y;
394
395 BEZIER_POLY( aPts[4], ctrl1, ctrl2, aPts[0] ).GetPoly( curve_pts, 0,
396 aCurrParams->m_CurveSegCount );
397
398 for( VECTOR2I& corner: curve_pts )
399 aPoly.push_back( corner );
400}
401
402
404 PCB_LAYER_ID aLayer,
405 VIAPAD& aViaPad,
406 std::vector<VECTOR2I>& aPts ) const
407{
408 // Compute the 2 anchor points on pad/via of the teardrop shape
409
410 PAD* pad = ( aViaPad.m_Parent->Type() == PCB_PAD_T ) ? static_cast<PAD*>(aViaPad.m_Parent)
411 : nullptr;
412 SHAPE_POLY_SET c_buffer;
413
414 // aHeightRatio is the factor to calculate the aViaPad teardrop preferred height
415 // teardrop height = aViaPad size * aHeightRatio (aHeightRatio <= 1.0)
416 // For rectangular (and similar) shapes, the preferred_height is calculated from the min
417 // dim of the rectangle = aViaPad.m_Width
418
419 int preferred_height = aViaPad.m_Width * aCurrParams->m_HeightRatio;
420
421 // force_clip_shape = true to force the via/pad polygon to be clipped to follow
422 // constraints
423 // Clipping is also needed for rectangular shapes, because the teardrop shape is
424 // restricted to a polygonal area smaller than the pad area (the teardrop height
425 // use the smaller value of X and Y sizes).
426 bool force_clip_shape = aCurrParams->m_HeightRatio < 1.0;
427
428 // To find the anchor points on via/pad shape, we build the polygonal shape, and clip the polygon
429 // to the max size (preferred_height or m_TdMaxHeight) by a rectangle centered on the
430 // axis of the expected teardrop shape.
431 // (only reduce the size of polygonal shape does not give good anchor points)
432 if( aViaPad.m_IsRound )
433 {
434 TransformCircleToPolygon( c_buffer, aViaPad.m_Pos, aViaPad.m_Width/2 ,
436 }
437 else // Only PADS can have a not round shape
438 {
439 wxCHECK( pad, false );
440
441 force_clip_shape = true;
442
443 preferred_height = aViaPad.m_Width * aCurrParams->m_HeightRatio;
444 pad->TransformShapeToPolygon( c_buffer, aLayer, 0, ARC_LOW_DEF, ERROR_INSIDE );
445 }
446
447 // Clip the pad/via shape to match the m_TdMaxHeight constraint, and for
448 // not rounded pad, clip the shape at the aViaPad.m_Width, i.e. the value
449 // of the smallest value between size.x and size.y values.
450 if( force_clip_shape || ( aCurrParams->m_TdMaxHeight > 0
451 && aCurrParams->m_TdMaxHeight < preferred_height ) )
452 {
453 int halfsize = std::min( aCurrParams->m_TdMaxHeight, preferred_height )/2;
454
455 // teardrop_axis is the line from anchor point on the track and the end point
456 // of the teardrop in the pad/via
457 // this is the teardrop_axis of the teardrop shape to build
458 VECTOR2I ref_on_track = ( aPts[0] + aPts[1] ) / 2;
459 VECTOR2I teardrop_axis( aPts[3] - ref_on_track );
460
461 EDA_ANGLE orient( teardrop_axis );
462 int len = teardrop_axis.EuclideanNorm();
463
464 // Build the constraint polygon: a rectangle with
465 // length = dist between the point on track and the pad/via pos
466 // height = m_TdMaxHeight or aViaPad.m_Width
467 SHAPE_POLY_SET clipping_rect;
468 clipping_rect.NewOutline();
469
470 // Build a horizontal rect: it will be rotated later
471 clipping_rect.Append( 0, - halfsize );
472 clipping_rect.Append( 0, halfsize );
473 clipping_rect.Append( len, halfsize );
474 clipping_rect.Append( len, - halfsize );
475
476 clipping_rect.Rotate( -orient );
477 clipping_rect.Move( ref_on_track );
478
479 // Clip the shape to the max allowed teadrop area
480 c_buffer.BooleanIntersection( clipping_rect, SHAPE_POLY_SET::PM_FAST );
481 }
482
483 /* in aPts:
484 * A and B are points on the track ( aPts[0] and aPts[1] )
485 * C and E are points on the aViaPad ( aPts[2] and aPts[4] )
486 * D is midpoint behind the aViaPad centre ( aPts[3] )
487 */
488
489 SHAPE_LINE_CHAIN& padpoly = c_buffer.Outline(0);
490 std::vector<VECTOR2I> points = padpoly.CPoints();
491
492 std::vector<VECTOR2I> initialPoints;
493 initialPoints.push_back( aPts[0] );
494 initialPoints.push_back( aPts[1] );
495
496 for( const VECTOR2I& pt: points )
497 initialPoints.emplace_back( pt.x, pt.y );
498
499 std::vector<VECTOR2I> hull;
500 BuildConvexHull( hull, initialPoints );
501
502 // Search for end points of segments starting at aPts[0] or aPts[1]
503 // In some cases, in convex hull, only one point (aPts[0] or aPts[1]) is still in list
504 VECTOR2I PointC;
505 VECTOR2I PointE;
506 int found_start = -1; // 2 points (one start and one end) should be found
507 int found_end = -1;
508
509 VECTOR2I start = aPts[0];
510 VECTOR2I pend = aPts[1];
511
512 for( unsigned ii = 0, jj = 0; jj < hull.size(); ii++, jj++ )
513 {
514 unsigned next = ii+ 1;
515
516 if( next >= hull.size() )
517 next = 0;
518
519 int prev = ii -1;
520
521 if( prev < 0 )
522 prev = hull.size()-1;
523
524 if( hull[ii] == start )
525 {
526 // the previous or the next point is candidate:
527 if( hull[next] != pend )
528 PointE = hull[next];
529 else
530 PointE = hull[prev];
531
532 found_start = ii;
533 }
534
535 if( hull[ii] == pend )
536 {
537 if( hull[next] != start )
538 PointC = hull[next];
539 else
540 PointC = hull[prev];
541
542 found_end = ii;
543 }
544 }
545
546 if( found_start < 0 ) // PointE was not initialized, because start point does not exit
547 {
548 int ii = found_end-1;
549
550 if( ii < 0 )
551 ii = hull.size()-1;
552
553 PointE = hull[ii];
554 }
555
556 if( found_end < 0 ) // PointC was not initialized, because end point does not exit
557 {
558 int ii = found_start-1;
559
560 if( ii < 0 )
561 ii = hull.size()-1;
562
563 PointC = hull[ii];
564 }
565
566 aPts[2] = PointC;
567 aPts[4] = PointE;
568
569 // Now we have to know if the choice aPts[2] = PointC is the best, or if
570 // aPts[2] = PointE is better.
571 // A criteria is to calculate the polygon area in these 2 cases, and choose the case
572 // that gives the bigger area, because the segments starting at PointC and PointE
573 // maximize their distance.
574 SHAPE_LINE_CHAIN dummy1( aPts, true );
575 double area1 = dummy1.Area();
576
577 std::swap( aPts[2], aPts[4] );
578 SHAPE_LINE_CHAIN dummy2( aPts, true );
579 double area2 = dummy2.Area();
580
581 if( area1 > area2 ) // The first choice (without swapping) is the better.
582 std::swap( aPts[2], aPts[4] );
583
584 return true;
585}
586
587
589 VECTOR2I& aStartPoint, VECTOR2I& aEndPoint,
590 PCB_TRACK*& aTrack, VIAPAD& aViaPad,
591 int* aEffectiveTeardropLen,
592 bool aFollowTracks,
593 TRACK_BUFFER& aTrackLookupList ) const
594{
595 bool found = true;
596 VECTOR2I start = aTrack->GetStart(); // one reference point on the track, inside teardrop
597 VECTOR2I end = aTrack->GetEnd(); // the second reference point on the track, outside teardrop
598 int radius = aViaPad.m_Width / 2;
599
600 // Requested length of the teardrop:
601 int targetLength = aViaPad.m_Width * aCurrParams->m_LengthRatio;
602
603 if( aCurrParams->m_TdMaxLen > 0 )
604 targetLength = std::min( aCurrParams->m_TdMaxLen, targetLength );
605
606 // actualTdLen is the distance between start and the teardrop point on the segment from start to end
607 int actualTdLen;
608 bool need_swap = false; // true if the start and end points of the current track are swapped
609
610 // ensure that start is at the via/pad end
611 if( SEG( end, aViaPad.m_Pos ).Length() < radius )
612 {
613 std::swap( start, end );
614 need_swap = true;
615 }
616
617 SHAPE_POLY_SET shapebuffer;
618
619 if( aViaPad.m_IsRound )
620 {
621 TransformCircleToPolygon( shapebuffer, aViaPad.m_Pos, radius,
623 }
624 else
625 {
626 PAD* pad = static_cast<PAD*>( aViaPad.m_Parent );
627 pad->TransformShapeToPolygon( shapebuffer, aTrack->GetLayer(), 0,
629 }
630
631 SHAPE_LINE_CHAIN& outline = shapebuffer.Outline(0);
632 outline.SetClosed( true );
633
634 // Search the intersection point between the pad/via shape and the current track
635 // This this the starting point to define the teardrop length
637 int pt_count = outline.Intersect( SEG( start, end ), pts );
638
639 // Ensure a intersection point was found, otherwise we cannot built the teardrop
640 // using this track (it is fully outside or inside the pad/via shape)
641 if( pt_count < 1 )
642 return false;
643
644 VECTOR2I intersect = pts[0].p;
645 start = intersect; // This is currently the reference point of the teardrop lenght
646
647 // actualTdLen for now the distance between start and the teardrop point on the (start end)segment
648 // It cannot be bigger than the lenght of this segment
649 actualTdLen = std::min( targetLength, SEG( start, end ).Length() );
650 VECTOR2I ref_lenght_point = start; // the reference point of actualTdLen
651
652 // If the first track is too short to allow a teardrop having the requested length
653 // explore the connected track(s), and try to find a anchor point at targetLength from initial start
654 if( actualTdLen < targetLength && aFollowTracks )
655 {
656 int consumed = 0;
657
658 while( actualTdLen+consumed < targetLength )
659 {
660 EDA_ITEM_FLAGS matchType;
661
662 PCB_TRACK* connected_track = findTouchingTrack( matchType, aTrack, end, aTrackLookupList );
663
664 if( connected_track == nullptr )
665 break;
666
667 // TODO: stop if angle between old and new segment is > 45 deg to avoid bad shape
668 consumed += actualTdLen;
669 // actualTdLen is the new distance from new start point and the teardrop anchor point
670 actualTdLen = std::min( targetLength-consumed, int( connected_track->GetLength() ) );
671 aTrack = connected_track;
672 end = connected_track->GetEnd();
673 start = connected_track->GetStart();
674 need_swap = false;
675
676 if( matchType != STARTPOINT )
677 {
678 std::swap( start, end );
679 need_swap = true;
680 }
681
682 // If we do not want to explore more than one connected track, stop search here
683 break;
684 }
685 }
686
687 // if aTrack is an arc, find the best teardrop end point on the arc
688 // It is currently on the segment from arc start point to arc end point,
689 // therefore not really on the arc, because we have used only the track end points.
690 if( aTrack->Type() == PCB_ARC_T )
691 {
692 // To find the best start and end points to build the teardrop shape, we convert
693 // the arc to segments, and search for the segment havig its start point at a dist
694 // < actualTdLen, and its end point at adist > actualTdLen:
695 SHAPE_ARC arc( aTrack->GetStart(), static_cast<PCB_ARC*>( aTrack )->GetMid(),
696 aTrack->GetEnd(), aTrack->GetWidth() );
697
698 if( need_swap )
699 arc.Reverse();
700
702
703 // Now, find the segment of the arc at a distance < actualTdLen from ref_lenght_point.
704 // We just search for the first segment (starting from the farest segment) with its
705 // start point at a distance < actualTdLen dist
706 // This is basic, but it is probably enough.
707 if( poly.PointCount() > 2 )
708 {
709 // Note: the first point is inside or near the pad/via shape
710 // The last point is outside and the farest from the ref_lenght_point
711 // So we explore segments from the last to the first
712 for( int ii = poly.PointCount()-1; ii >= 0 ; ii-- )
713 {
714 int dist_from_start = ( poly.CPoint( ii ) - start ).EuclideanNorm();
715
716 // The first segment at a distance of the reference point < actualTdLen is OK
717 // and is suitable to define the reference segment of the teardrop anchor.
718 if( dist_from_start < actualTdLen || ii == 0 )
719 {
720 start = poly.CPoint( ii );
721
722 if( ii < poly.PointCount()-1 )
723 end = poly.CPoint( ii+1 );
724
725 // actualTdLen is the distance between start (the reference segment start point)
726 // and the point on track of the teardrop.
727 // This is the difference between the initial actualTdLen value and the
728 // distance between start and ref_lenght_point.
729 actualTdLen -= (start - ref_lenght_point).EuclideanNorm();
730
731 // Ensure validity of actualTdLen: >= 0, and <= segment lenght
732 if( actualTdLen < 0 ) // should not happen, but...
733 actualTdLen = 0;
734
735 actualTdLen = std::min( actualTdLen, (end - start).EuclideanNorm() );
736
737 break;
738 }
739 }
740 }
741 }
742
743 // aStartPoint and aEndPoint will define later a segment to build the 2 anchors points
744 // of the teardrop on the aTrack shape.
745 // they are two points (both outside the pad/via shape) of aTrack if aTrack is a segment,
746 // or a small segment on aTrack if aTrack is an ARC
747 aStartPoint = start;
748 aEndPoint = end;
749
750 *aEffectiveTeardropLen = actualTdLen;
751 return found;
752}
753
754
756 std::vector<VECTOR2I>& aCorners,
757 PCB_TRACK* aTrack, VIAPAD& aViaPad,
758 bool aFollowTracks,
759 TRACK_BUFFER& aTrackLookupList ) const
760{
761 VECTOR2I start, end; // Start and end points of the track anchor of the teardrop
762 // the start point is inside the teardrop shape
763 // the end point is outside.
764 int track_stub_len; // the dist between the start point and the anchor point
765 // on the track
766
767 // Note: aTrack can be modified if the initial track is too short
768 if( !findAnchorPointsOnTrack( aCurrParams, start, end, aTrack, aViaPad, &track_stub_len,
769 aFollowTracks, aTrackLookupList ) )
770 return false;
771
772 // The start and end points must be different to calculate a valid polygon shape
773 if( start == end )
774 return false;
775
776 VECTOR2D vecT = NormalizeVector(end - start);
777
778 // find the 2 points on the track, sharp end of the teardrop
779 int track_halfwidth = aTrack->GetWidth() / 2;
780 VECTOR2I pointB = start + VECTOR2I( vecT.x * track_stub_len + vecT.y * track_halfwidth,
781 vecT.y * track_stub_len - vecT.x * track_halfwidth );
782 VECTOR2I pointA = start + VECTOR2I( vecT.x * track_stub_len - vecT.y * track_halfwidth,
783 vecT.y * track_stub_len + vecT.x * track_halfwidth );
784
785 // To build a polygonal valid shape pointA and point B must be outside the pad
786 // It can be inside with some pad shapes having very different X and X sizes
787 if( !aViaPad.m_IsRound )
788 {
789 PAD* pad = static_cast<PAD*>( aViaPad.m_Parent );
790
791 if( pad->HitTest( pointA ) )
792 return false;
793
794 if( pad->HitTest( pointB ) )
795 return false;
796 }
797
798 // Introduce a last point to cover the via centre to ensure it is seen as connected
799 VECTOR2I pointD = aViaPad.m_Pos;
800 // add a small offset in order to have the aViaPad.m_Pos reference point inside
801 // the teardrop area, just in case...
802 int offset = pcbIUScale.mmToIU( 0.001 );
803 pointD += VECTOR2I( int( -vecT.x*offset), int(-vecT.y*offset) );
804
805 VECTOR2I pointC, pointE; // Point on PADVIA outlines
806 std::vector<VECTOR2I> pts = {pointA, pointB, pointC, pointD, pointE};
807
808 ComputePointsOnPadVia( aCurrParams, aTrack->GetLayer(), aViaPad, pts );
809
810 if( !aCurrParams->IsCurved() )
811 {
812 aCorners = pts;
813 return true;
814 }
815
816 // See if we can use curved teardrop shape
817 if( aViaPad.m_IsRound )
818 {
819 computeCurvedForRoundShape( aCurrParams, aCorners, track_halfwidth, vecT, aViaPad, pts );
820 }
821 else
822 {
823 int td_height = aViaPad.m_Width * aCurrParams->m_HeightRatio;
824
825 if( aCurrParams->m_TdMaxHeight > 0 && aCurrParams->m_TdMaxHeight < td_height )
826 td_height = aCurrParams->m_TdMaxHeight;
827
828 computeCurvedForRectShape( aCurrParams, aCorners, td_height, track_halfwidth,
829 aViaPad, pts );
830 }
831
832 return true;
833}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:109
constexpr int ARC_LOW_DEF
Definition: base_units.h:120
Bezier curves to polygon converter.
Definition: bezier_curves.h:38
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMinSegLen=0, int aMaxSegCount=32)
Convert a Bezier curve to a polygon.
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition: board_item.h:192
ZONES & Zones()
Definition: board.h:317
FOOTPRINTS & Footprints()
Definition: board.h:311
TRACKS & Tracks()
Definition: board.h:308
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:97
Definition: pad.h:60
int GetDrillSizeY() const
Definition: pad.h:272
VECTOR2I GetPosition() const override
Definition: pad.h:203
int GetDrillSizeX() const
Definition: pad.h:270
PAD_SHAPE GetShape() const
Definition: pad.h:195
const VECTOR2I & GetSize() const
Definition: pad.h:258
const VECTOR2I & GetMid() const
Definition: pcb_track.h:313
virtual double GetLength() const
Function GetLength returns the length of the track using the hypotenuse calculation.
Definition: pcb_track.cpp:314
int GetWidth() const
Definition: pcb_track.h:108
const VECTOR2I & GetStart() const
Definition: pcb_track.h:114
const VECTOR2I & GetEnd() const
Definition: pcb_track.h:111
VECTOR2I GetPosition() const override
Definition: pcb_track.h:442
int GetDrillValue() const
Function GetDrillValue "calculates" the drill value for vias (m-Drill if > 0, or default drill value ...
Definition: pcb_track.cpp:221
Definition: seg.h:42
int Length() const
Return the length (this).
Definition: seg.h:326
const SHAPE_LINE_CHAIN ConvertToPolyline(double aAccuracy=DefaultAccuracyForPCB(), double *aEffectiveAccuracy=nullptr) const
Construct a SHAPE_LINE_CHAIN of segments from a given arc.
Definition: shape_arc.cpp:470
void Reverse()
Definition: shape_arc.cpp:581
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int Intersect(const SEG &aSeg, INTERSECTIONS &aIp) const
Find all intersection points between our line chain and the segment aSeg.
int PointCount() const
Return the number of points (vertices) in this line chain.
double Area(bool aAbsolute=true) const
Return the area of this chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
std::vector< INTERSECTION > INTERSECTIONS
const std::vector< VECTOR2I > & CPoints() const
Represent a set of closed polygons.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
void BooleanIntersection(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset union between a and b, store the result in it self For aFastMode meaning,...
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Add a new vertex to the contour indexed by aOutline and aHole (defaults to the outline of the last po...
SHAPE_LINE_CHAIN & Outline(int aIndex)
int NewOutline()
Creates a new hole in a given outline.
void Move(const VECTOR2I &aVector) override
bool ComputePointsOnPadVia(TEARDROP_PARAMETERS *aCurrParams, PCB_LAYER_ID aLayer, VIAPAD &aViaPad, std::vector< VECTOR2I > &aPts) const
Compute the 2 points on pad/via of the teardrop shape.
BOARD * m_board
Definition: teardrop.h:249
void collectTeardrops(std::vector< ZONE * > &aList) const
Build a list of all teardrops on the current board.
void collectVias(std::vector< VIAPAD > &aList) const
Collect and build the list of all vias from the given board.
void collectPadsCandidate(std::vector< VIAPAD > &aList, bool aDrilledViaPad, bool aRoundShapesOnly, bool aIncludeNotDrilled) const
Build a list of pads candidate for teardrops from the given board Pads with no net are not candidate ...
bool findAnchorPointsOnTrack(TEARDROP_PARAMETERS *aCurrParams, VECTOR2I &aStartPoint, VECTOR2I &aEndPoint, PCB_TRACK *&aTrack, VIAPAD &aViaPad, int *aEffectiveTeardropLen, bool aFollowTracks, TRACK_BUFFER &aTrackLookupList) const
void computeCurvedForRoundShape(TEARDROP_PARAMETERS *aCurrParams, std::vector< VECTOR2I > &aPoly, int aTrackHalfWidth, VECTOR2D aTrackDir, VIAPAD &aViaPad, std::vector< VECTOR2I > &aPts) const
Compute the curve part points for teardrops connected to a round shape The Bezier curve control point...
bool isViaAndTrackInSameZone(VIAPAD &aVia, PCB_TRACK *aTrack) const
bool computeTeardropPolygonPoints(TEARDROP_PARAMETERS *aCurrParams, std::vector< VECTOR2I > &aCorners, PCB_TRACK *aTrack, VIAPAD &aVia, bool aFollowTracks, TRACK_BUFFER &aTrackLookupList) const
Compute all teardrop points of the polygon shape.
PCB_TRACK * findTouchingTrack(EDA_ITEM_FLAGS &aMatchType, PCB_TRACK *aTrackRef, const VECTOR2I &aEndPoint, TRACK_BUFFER &aTrackLookupList) const
Find a track connected to the end of another track.
void computeCurvedForRectShape(TEARDROP_PARAMETERS *aCurrParams, std::vector< VECTOR2I > &aPoly, int aTdHeight, int aTrackHalfWidth, VIAPAD &aViaPad, std::vector< VECTOR2I > &aPts) const
Compute the curve part points for teardrops connected to a rectangular/polygonal shape The Bezier cur...
TEARDROP_PARAMETARS is a helper class to handle parameters needed to build teardrops for a board thes...
int m_CurveSegCount
number of segments to build the curved sides of a teardrop area must be > 2.
double m_HeightRatio
The height of a teardrop as ratio between height and size of pad/via.
int m_TdMaxHeight
max allowed height for teardrops in IU. <= 0 to disable
int m_TdMaxLen
the type of target for these parameters
double m_LengthRatio
The length of a teardrop as ratio between length and size of pad/via.
int idxFromLayNet(int aLayer, int aNetcode) const
Definition: teardrop.h:287
void AddTrack(PCB_TRACK *aTrack, int aLayer, int aNetcode)
Add a track in buffer, in space grouping tracks having the same netcode and the same layer.
std::map< int, std::vector< PCB_TRACK * > * > m_map_tracks
Definition: teardrop.h:293
std::vector< PCB_TRACK * > * GetTrackList(int aLayer, int aNetcode)
Definition: teardrop.h:269
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition: vector2d.h:265
Handle a list of polygons defining a copper zone.
Definition: zone.h:57
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
void BuildConvexHull(std::vector< VECTOR2I > &aResult, const std::vector< VECTOR2I > &aPoly)
Calculate the convex hull of a list of points in counter-clockwise order.
Definition: convex_hull.cpp:87
static constexpr EDA_ANGLE & ANGLE_90
Definition: eda_angle.h:431
std::uint32_t EDA_ITEM_FLAGS
#define STARTPOINT
When a line is selected, these flags indicate which.
@ ERROR_INSIDE
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:59
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:418
ENDPOINT_T
Definition: pcb_track.h:57
@ ENDPOINT_START
Definition: pcb_track.h:58
static bool intersect(const SEGMENT_WITH_NORMALS &aSeg, const SFVEC2F &aStart, const SFVEC2F &aEnd)
Definition: polygon_2d.cpp:273
CITER next(CITER it)
Definition: ptree.cpp:126
constexpr int mmToIU(double mm) const
Definition: base_units.h:89
Stores info of a pad, via or track end to build a teardrop.
Definition: teardrop.h:45
int m_Width
Definition: teardrop.h:56
VIAPAD(PCB_VIA *aVia)
BOARD_CONNECTED_ITEM * m_Parent
Definition: teardrop.h:61
int m_Drill
Definition: teardrop.h:57
VECTOR2I m_Pos
Definition: teardrop.h:55
bool m_IsPad
Definition: teardrop.h:60
bool m_IsRound
Definition: teardrop.h:59
int m_NetCode
Definition: teardrop.h:58
static VECTOR2D NormalizeVector(VECTOR2I aVector)
constexpr int delta
double EuclideanNorm(const VECTOR2I &vector)
Definition: trigo.h:129
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:102
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition: typeinfo.h:87
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition: typeinfo.h:103
int sign(T val)
Definition: util.h:124
VECTOR2< int > VECTOR2I
Definition: vector2d.h:590
@ NONE
Pads are not covered.