KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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-2024 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#include <drc/drc_rtree.h>
36
37#include "teardrop.h"
41#include <bezier_curves.h>
42
43#include <wx/log.h>
44
45
46void TRACK_BUFFER::AddTrack( PCB_TRACK* aTrack, int aLayer, int aNetcode )
47{
48 auto item = m_map_tracks.find( idxFromLayNet( aLayer, aNetcode ) );
49 std::vector<PCB_TRACK*>* buffer;
50
51 if( item == m_map_tracks.end() )
52 {
53 buffer = new std::vector<PCB_TRACK*>;
54 m_map_tracks[idxFromLayNet( aLayer, aNetcode )] = buffer;
55 }
56 else
57 {
58 buffer = (*item).second;
59 }
60
61 buffer->push_back( aTrack );
62}
63
64
66{
67 if( aItem->Type() == PCB_VIA_T )
68 {
69 PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
70 return via->GetWidth();
71 }
72 else if( aItem->Type() == PCB_PAD_T )
73 {
74 PAD* pad = static_cast<PAD*>( aItem );
75 return std::min( pad->GetSize().x, pad->GetSize().y );
76 }
77 else if( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_ARC_T )
78 {
79 PCB_TRACK* track = static_cast<PCB_TRACK*>( aItem );
80 return track->GetWidth();
81 }
82
83 return 0;
84}
85
86
88{
89 if( aItem->Type() == PCB_PAD_T )
90 {
91 PAD* pad = static_cast<PAD*>( aItem );
92
93 return pad->GetShape() == PAD_SHAPE::CIRCLE
94 || ( pad->GetShape() == PAD_SHAPE::OVAL && pad->GetSize().x == pad->GetSize().y );
95 }
96
97 return true;
98}
99
100
102{
103 for( PCB_TRACK* track : m_board->Tracks() )
104 {
105 if( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T )
106 {
107 m_tracksRTree.Insert( track, track->GetLayer() );
108 m_trackLookupList.AddTrack( track, track->GetLayer(), track->GetNetCode() );
109 }
110 }
111}
112
113
115{
116 for( ZONE* zone: m_board->Zones() )
117 {
118 // Skip teardrops
119 if( zone->IsTeardropArea() )
120 continue;
121
122 // Only consider zones on the same layer
123 if( !zone->IsOnLayer( aTrack->GetLayer() ) )
124 continue;
125
126 if( zone->GetNetCode() == aTrack->GetNetCode() )
127 {
128 if( zone->Outline()->Contains( VECTOR2I( aPadOrVia->GetPosition() ) ) )
129 {
130 // If the first item is a pad, ensure it can be connected to the zone
131 if( aPadOrVia->Type() == PCB_PAD_T )
132 {
133 PAD *pad = static_cast<PAD*>( aPadOrVia );
134
135 if( zone->GetPadConnection() == ZONE_CONNECTION::NONE
136 || pad->GetZoneConnectionOverrides( nullptr ) == ZONE_CONNECTION::NONE )
137 {
138 return false;
139 }
140 }
141
142 return true;
143 }
144 }
145 }
146
147 return false;
148}
149
150
152 const VECTOR2I& aEndPoint ) const
153{
154 int matches = 0; // Count of candidates: only 1 is acceptable
155 PCB_TRACK* candidate = nullptr; // a reference to the track connected
156
157 m_tracksRTree.QueryColliding( aTrackRef, aTrackRef->GetLayer(), aTrackRef->GetLayer(),
158 // Filter:
159 [&]( BOARD_ITEM* trackItem ) -> bool
160 {
161 return trackItem != aTrackRef;
162 },
163 // Visitor
164 [&]( BOARD_ITEM* trackItem ) -> bool
165 {
166 PCB_TRACK* curr_track = static_cast<PCB_TRACK*>( trackItem );
167
168 // IsPointOnEnds() returns 0, EDA_ITEM_FLAGS::STARTPOINT or EDA_ITEM_FLAGS::ENDPOINT
169 if( EDA_ITEM_FLAGS match = curr_track->IsPointOnEnds( aEndPoint, m_tolerance ) )
170 {
171 // if faced with a Y junction, choose the track longest segment as candidate
172 matches++;
173
174 if( matches > 1 )
175 {
176 double previous_len = candidate->GetLength();
177 double curr_len = curr_track->GetLength();
178
179 if( previous_len >= curr_len )
180 return true;
181 }
182
183 aMatchType = match;
184 candidate = curr_track;
185 }
186
187 return true;
188 },
189 0 );
190
191 return candidate;
192}
193
194
198static VECTOR2D NormalizeVector( const VECTOR2I& aVector )
199{
200 VECTOR2D vect( aVector );
201 double norm = vect.EuclideanNorm();
202 return vect / norm;
203}
204
205
206/*
207 * Compute the curve part points for teardrops connected to a round shape
208 * The Bezier curve control points are optimized for a round pad/via shape,
209 * and do not give a good curve shape for other pad shapes
210 */
212 std::vector<VECTOR2I>& aPoly,
213 int aTrackHalfWidth, const VECTOR2D& aTrackDir,
214 BOARD_ITEM* aOther, const VECTOR2I& aOtherPos,
215 std::vector<VECTOR2I>& pts ) const
216{
217 // in pts:
218 // A and B are points on the track ( pts[0] and pts[1] )
219 // C and E are points on the aViaPad ( pts[2] and pts[4] )
220 // D is the aViaPad centre ( pts[3] )
221 double Vpercent = aParams.m_BestWidthRatio;
222 int td_height = KiROUND( GetWidth( aOther ) * Vpercent );
223
224 // First, calculate a aVpercent equivalent to the td_height clamped by aTdMaxHeight
225 // We cannot use the initial aVpercent because it gives bad shape with points
226 // on aViaPad calculated for a clamped aViaPad size
227 if( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < td_height )
228 Vpercent *= (double) aParams.m_TdMaxWidth / td_height;
229
230 int radius = GetWidth( aOther ) / 2;
231
232 // Don't divide by zero. No good can come of that.
233 wxCHECK2( radius != 0, radius = 1 );
234
235 double minVpercent = double( aTrackHalfWidth ) / radius;
236 double weaken = (Vpercent - minVpercent) / ( 1 - minVpercent ) / radius;
237
238 double biasBC = 0.5 * SEG( pts[1], pts[2] ).Length();
239 double biasAE = 0.5 * SEG( pts[4], pts[0] ).Length();
240
241 VECTOR2I vecC = (VECTOR2I)pts[2] - aOtherPos;
242 VECTOR2I tangentC = VECTOR2I( pts[2].x - vecC.y * biasBC * weaken,
243 pts[2].y + vecC.x * biasBC * weaken );
244 VECTOR2I vecE = (VECTOR2I)pts[4] - aOtherPos;
245 VECTOR2I tangentE = VECTOR2I( pts[4].x + vecE.y * biasAE * weaken,
246 pts[4].y - vecE.x * biasAE * weaken );
247
248 VECTOR2I tangentB = VECTOR2I( pts[1].x - aTrackDir.x * biasBC, pts[1].y - aTrackDir.y * biasBC );
249 VECTOR2I tangentA = VECTOR2I( pts[0].x - aTrackDir.x * biasAE, pts[0].y - aTrackDir.y * biasAE );
250
251 std::vector<VECTOR2I> curve_pts;
252 curve_pts.reserve( aParams.m_CurveSegCount );
253 BEZIER_POLY( pts[1], tangentB, tangentC, pts[2] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount );
254
255 for( VECTOR2I& corner: curve_pts )
256 aPoly.push_back( corner );
257
258 aPoly.push_back( pts[3] );
259
260 curve_pts.clear();
261 BEZIER_POLY( pts[4], tangentE, tangentA, pts[0] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount );
262
263 for( VECTOR2I& corner: curve_pts )
264 aPoly.push_back( corner );
265}
266
267
268/*
269 * Compute the curve part points for teardrops connected to a rectangular/polygonal shape
270 * The Bezier curve control points are not optimized for a special shape
271 */
273 std::vector<VECTOR2I>& aPoly, int aTdWidth,
274 int aTrackHalfWidth,
275 std::vector<VECTOR2I>& aPts ) const
276{
277 // in aPts:
278 // A and B are points on the track ( pts[0] and pts[1] )
279 // C and E are points on the aViaPad ( pts[2] and pts[4] )
280 // D is the aViaPad centre ( pts[3] )
281
282 // side1 is( aPts[1], aPts[2] ); from track to via
283 VECTOR2I side1( aPts[2] - aPts[1] ); // vector from track to via
284 // side2 is ( aPts[4], aPts[0] ); from via to track
285 VECTOR2I side2( aPts[4] - aPts[0] ); // vector from track to via
286
287 std::vector<VECTOR2I> curve_pts;
288 curve_pts.reserve( aParams.m_CurveSegCount );
289
290 // Note: This side is from track to via
291 VECTOR2I ctrl1 = ( aPts[1] + aPts[1] + aPts[2] ) / 3;
292 VECTOR2I ctrl2 = ( aPts[1] + aPts[2] + aPts[2] ) / 3;
293
294 // The control points must be moved toward the polygon inside, in order to give a curved shape
295 // The move vector is perpendicular to the vertex (side 1 or side 2), and its
296 // value is delta, depending on the sizes of via and track
297 int delta = ( aTdWidth / 2 - aTrackHalfWidth );
298
299 delta /= 4; // A scaling factor giving a fine shape, defined from tests.
300 // However for short sides, the value of delta must be reduced, depending
301 // on the side length
302 // We use here a max delta value = side_length/8, defined from tests
303
304 int side_length = side1.EuclideanNorm();
305 int delta_effective = std::min( delta, side_length/8 );
306 // The move vector depend on the quadrant: it must be always defined to create a
307 // curve with a direction toward the track
308 EDA_ANGLE angle1( side1 );
309 int sign = std::abs( angle1 ) >= ANGLE_90 ? 1 : -1;
310 VECTOR2I bias( 0, sign * delta_effective );
311
312 // Does not works well with the current algo, due to an initial bug.
313 // but I (JPC) keep it here because probably it will gives a better shape
314 // if the algo is refined.
315 // RotatePoint( bias, angle1 );
316
317 ctrl1.x += bias.x;
318 ctrl1.y += bias.y;
319 ctrl2.x += bias.x;
320 ctrl2.y += bias.y;
321
322 BEZIER_POLY( aPts[1], ctrl1, ctrl2, aPts[2] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount );
323
324 for( VECTOR2I& corner: curve_pts )
325 aPoly.push_back( corner );
326
327 aPoly.push_back( aPts[3] );
328
329 // Note: This side is from via to track
330 curve_pts.clear();
331 ctrl1 = ( aPts[4] + aPts[4] + aPts[0] ) / 3;
332 ctrl2 = ( aPts[4] + aPts[0] + aPts[0] ) / 3;
333
334 side_length = side2.EuclideanNorm();
335 delta_effective = std::min( delta, side_length/8 );
336
337 EDA_ANGLE angle2( side2 );
338 sign = std::abs( angle2 ) <= ANGLE_90 ? 1 : -1;
339
340 bias = VECTOR2I( 0, sign * delta_effective );
341
342 // Does not works well with the current algo
343 // RotatePoint( bias, angle2 );
344
345 ctrl1.x += bias.x;
346 ctrl1.y += bias.y;
347 ctrl2.x += bias.x;
348 ctrl2.y += bias.y;
349
350 BEZIER_POLY( aPts[4], ctrl1, ctrl2, aPts[0] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount );
351
352 for( VECTOR2I& corner: curve_pts )
353 aPoly.push_back( corner );
354}
355
356
358 BOARD_ITEM* aItem, const VECTOR2I& aPos,
359 std::vector<VECTOR2I>& aPts ) const
360{
361 // Compute the 2 anchor points on pad/via/track of the teardrop shape
362
363 SHAPE_POLY_SET c_buffer;
364
365 // m_BestWidthRatio is the factor to calculate the teardrop preferred width.
366 // teardrop width = pad, via or track size * m_BestWidthRatio (m_BestWidthRatio <= 1.0)
367 // For rectangular (and similar) shapes, the preferred_width is calculated from the min
368 // dim of the rectangle
369
370 int preferred_width = KiROUND( GetWidth( aItem ) * aParams.m_BestWidthRatio );
371
372 // force_clip = true to force the pad/via/track polygon to be clipped to follow
373 // constraints
374 // Clipping is also needed for rectangular shapes, because the teardrop shape is restricted
375 // to a polygonal area smaller than the pad area (the teardrop height use the smaller value
376 // of X and Y sizes).
377 bool force_clip = aParams.m_BestWidthRatio < 1.0;
378
379 // To find the anchor points on the pad/via/track shape, we build the polygonal shape, and
380 // clip the polygon to the max size (preferred_width or m_TdMaxWidth) by a rectangle
381 // centered on the axis of the expected teardrop shape.
382 // (only reduce the size of polygonal shape does not give good anchor points)
383 if( IsRound( aItem ) )
384 {
385 TransformCircleToPolygon( c_buffer, aPos, GetWidth( aItem ) / 2, ARC_LOW_DEF,
386 ERROR_INSIDE, 16 );
387 }
388 else // Only PADS can have a not round shape
389 {
390 PAD* pad = static_cast<PAD*>( aItem );
391
392 force_clip = true;
393
394 preferred_width = KiROUND( GetWidth( pad ) * aParams.m_BestWidthRatio );
395 pad->TransformShapeToPolygon( c_buffer, aLayer, 0, ARC_LOW_DEF, ERROR_INSIDE );
396 }
397
398 // Clip the pad/via/track shape to match the m_TdMaxWidth constraint, and for non-round pads,
399 // clip the shape to the smallest of size.x and size.y values.
400 if( force_clip || ( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < preferred_width ) )
401 {
402 int halfsize = std::min( aParams.m_TdMaxWidth, preferred_width )/2;
403
404 // teardrop_axis is the line from anchor point on the track and the end point
405 // of the teardrop in the pad/via
406 // this is the teardrop_axis of the teardrop shape to build
407 VECTOR2I ref_on_track = ( aPts[0] + aPts[1] ) / 2;
408 VECTOR2I teardrop_axis( aPts[3] - ref_on_track );
409
410 EDA_ANGLE orient( teardrop_axis );
411 int len = teardrop_axis.EuclideanNorm();
412
413 // Build the constraint polygon: a rectangle with
414 // length = dist between the point on track and the pad/via pos
415 // height = m_TdMaxWidth or aViaPad.m_Width
416 SHAPE_POLY_SET clipping_rect;
417 clipping_rect.NewOutline();
418
419 // Build a horizontal rect: it will be rotated later
420 clipping_rect.Append( 0, - halfsize );
421 clipping_rect.Append( 0, halfsize );
422 clipping_rect.Append( len, halfsize );
423 clipping_rect.Append( len, - halfsize );
424
425 clipping_rect.Rotate( -orient );
426 clipping_rect.Move( ref_on_track );
427
428 // Clip the shape to the max allowed teadrop area
429 c_buffer.BooleanIntersection( clipping_rect, SHAPE_POLY_SET::PM_FAST );
430 }
431
432 /* in aPts:
433 * A and B are points on the track ( aPts[0] and aPts[1] )
434 * C and E are points on the aViaPad ( aPts[2] and aPts[4] )
435 * D is midpoint behind the aViaPad centre ( aPts[3] )
436 */
437
438 SHAPE_LINE_CHAIN& padpoly = c_buffer.Outline(0);
439 std::vector<VECTOR2I> points = padpoly.CPoints();
440
441 std::vector<VECTOR2I> initialPoints;
442 initialPoints.push_back( aPts[0] );
443 initialPoints.push_back( aPts[1] );
444
445 for( const VECTOR2I& pt: points )
446 initialPoints.emplace_back( pt.x, pt.y );
447
448 std::vector<VECTOR2I> hull;
449 BuildConvexHull( hull, initialPoints );
450
451 // Search for end points of segments starting at aPts[0] or aPts[1]
452 // In some cases, in convex hull, only one point (aPts[0] or aPts[1]) is still in list
453 VECTOR2I PointC;
454 VECTOR2I PointE;
455 int found_start = -1; // 2 points (one start and one end) should be found
456 int found_end = -1;
457
458 VECTOR2I start = aPts[0];
459 VECTOR2I pend = aPts[1];
460
461 for( unsigned ii = 0, jj = 0; jj < hull.size(); ii++, jj++ )
462 {
463 unsigned next = ii+ 1;
464
465 if( next >= hull.size() )
466 next = 0;
467
468 int prev = ii -1;
469
470 if( prev < 0 )
471 prev = hull.size()-1;
472
473 if( hull[ii] == start )
474 {
475 // the previous or the next point is candidate:
476 if( hull[next] != pend )
477 PointE = hull[next];
478 else
479 PointE = hull[prev];
480
481 found_start = ii;
482 }
483
484 if( hull[ii] == pend )
485 {
486 if( hull[next] != start )
487 PointC = hull[next];
488 else
489 PointC = hull[prev];
490
491 found_end = ii;
492 }
493 }
494
495 if( found_start < 0 ) // PointE was not initialized, because start point does not exit
496 {
497 int ii = found_end-1;
498
499 if( ii < 0 )
500 ii = hull.size()-1;
501
502 PointE = hull[ii];
503 }
504
505 if( found_end < 0 ) // PointC was not initialized, because end point does not exit
506 {
507 int ii = found_start-1;
508
509 if( ii < 0 )
510 ii = hull.size()-1;
511
512 PointC = hull[ii];
513 }
514
515 aPts[2] = PointC;
516 aPts[4] = PointE;
517
518 // Now we have to know if the choice aPts[2] = PointC is the best, or if
519 // aPts[2] = PointE is better.
520 // A criteria is to calculate the polygon area in these 2 cases, and choose the case
521 // that gives the bigger area, because the segments starting at PointC and PointE
522 // maximize their distance.
523 SHAPE_LINE_CHAIN dummy1( aPts, true );
524 double area1 = dummy1.Area();
525
526 std::swap( aPts[2], aPts[4] );
527 SHAPE_LINE_CHAIN dummy2( aPts, true );
528 double area2 = dummy2.Area();
529
530 if( area1 > area2 ) // The first choice (without swapping) is the better.
531 std::swap( aPts[2], aPts[4] );
532
533 return true;
534}
535
536
538 VECTOR2I& aStartPoint, VECTOR2I& aEndPoint,
539 PCB_TRACK*& aTrack, BOARD_ITEM* aOther,
540 const VECTOR2I& aOtherPos,
541 int* aEffectiveTeardropLen ) const
542{
543 bool found = true;
544 VECTOR2I start = aTrack->GetStart(); // one reference point on the track, inside teardrop
545 VECTOR2I end = aTrack->GetEnd(); // the second reference point on the track, outside teardrop
546 int radius = GetWidth( aOther ) / 2;
547
548 // Requested length of the teardrop:
549 int targetLength = KiROUND( GetWidth( aOther ) * aParams.m_BestLengthRatio );
550
551 if( aParams.m_TdMaxLen > 0 )
552 targetLength = std::min( aParams.m_TdMaxLen, targetLength );
553
554 // actualTdLen is the distance between start and the teardrop point on the segment from start to end
555 int actualTdLen;
556 bool need_swap = false; // true if the start and end points of the current track are swapped
557
558 // aTrack is expected to have one end inside the via/pad and the other end outside
559 // so ensure the start point is inside the via/pad
560 if( !aOther->HitTest( start, 0 ) )
561 {
562 std::swap( start, end );
563 need_swap = true;
564 }
565
566 SHAPE_POLY_SET shapebuffer;
567
568 if( IsRound( aOther ) )
569 {
570 TransformCircleToPolygon( shapebuffer, aOtherPos, radius, ARC_LOW_DEF, ERROR_INSIDE, 16 );
571 }
572 else
573 {
574 static_cast<PAD*>( aOther )->TransformShapeToPolygon( shapebuffer, aTrack->GetLayer(), 0,
576 }
577
578 SHAPE_LINE_CHAIN& outline = shapebuffer.Outline(0);
579 outline.SetClosed( true );
580
581 // Search the intersection point between the pad/via shape and the current track
582 // This this the starting point to define the teardrop length
584 int pt_count;
585
586 if( aTrack->Type() == PCB_ARC_T )
587 {
588 // To find the starting point we convert the arc to a polyline
589 // and compute the intersection point with the pad/via shape
590 SHAPE_ARC arc( aTrack->GetStart(), static_cast<PCB_ARC*>( aTrack )->GetMid(),
591 aTrack->GetEnd(), aTrack->GetWidth() );
592
594 pt_count = outline.Intersect( poly, pts );
595 }
596 else
597 pt_count = outline.Intersect( SEG( start, end ), pts );
598
599 // Ensure a intersection point was found, otherwise we cannot built the teardrop
600 // using this track (it is fully outside or inside the pad/via shape)
601 if( pt_count < 1 )
602 return false;
603
604 VECTOR2I intersect = pts[0].p;
605 start = intersect; // This is currently the reference point of the teardrop lenght
606
607 // actualTdLen for now the distance between start and the teardrop point on the (start end)segment
608 // It cannot be bigger than the lenght of this segment
609 actualTdLen = std::min( targetLength, SEG( start, end ).Length() );
610 VECTOR2I ref_lenght_point = start; // the reference point of actualTdLen
611
612 // If the first track is too short to allow a teardrop having the requested length
613 // explore the connected track(s), and try to find a anchor point at targetLength from initial start
614 if( actualTdLen < targetLength && aParams.m_AllowUseTwoTracks )
615 {
616 int consumed = 0;
617
618 while( actualTdLen + consumed < targetLength )
619 {
620 EDA_ITEM_FLAGS matchType;
621
622 PCB_TRACK* connected_track = findTouchingTrack( matchType, aTrack, end );
623
624 if( connected_track == nullptr )
625 break;
626
627 // TODO: stop if angle between old and new segment is > 45 deg to avoid bad shape
628 consumed += actualTdLen;
629 // actualTdLen is the new distance from new start point and the teardrop anchor point
630 actualTdLen = std::min( targetLength-consumed, int( connected_track->GetLength() ) );
631 aTrack = connected_track;
632 end = connected_track->GetEnd();
633 start = connected_track->GetStart();
634 need_swap = false;
635
636 if( matchType != STARTPOINT )
637 {
638 std::swap( start, end );
639 need_swap = true;
640 }
641
642 // If we do not want to explore more than one connected track, stop search here
643 break;
644 }
645 }
646
647 // if aTrack is an arc, find the best teardrop end point on the arc
648 // It is currently on the segment from arc start point to arc end point,
649 // therefore not really on the arc, because we have used only the track end points.
650 if( aTrack->Type() == PCB_ARC_T )
651 {
652 // To find the best start and end points to build the teardrop shape, we convert
653 // the arc to segments, and search for the segment having its start point at a dist
654 // < actualTdLen, and its end point at adist > actualTdLen:
655 SHAPE_ARC arc( aTrack->GetStart(), static_cast<PCB_ARC*>( aTrack )->GetMid(),
656 aTrack->GetEnd(), aTrack->GetWidth() );
657
658 if( need_swap )
659 arc.Reverse();
660
662
663 // Now, find the segment of the arc at a distance < actualTdLen from ref_lenght_point.
664 // We just search for the first segment (starting from the farest segment) with its
665 // start point at a distance < actualTdLen dist
666 // This is basic, but it is probably enough.
667 if( poly.PointCount() > 2 )
668 {
669 // Note: the first point is inside or near the pad/via shape
670 // The last point is outside and the farest from the ref_lenght_point
671 // So we explore segments from the last to the first
672 for( int ii = poly.PointCount()-1; ii >= 0 ; ii-- )
673 {
674 int dist_from_start = ( poly.CPoint( ii ) - start ).EuclideanNorm();
675
676 // The first segment at a distance of the reference point < actualTdLen is OK
677 // and is suitable to define the reference segment of the teardrop anchor.
678 if( dist_from_start < actualTdLen || ii == 0 )
679 {
680 start = poly.CPoint( ii );
681
682 if( ii < poly.PointCount()-1 )
683 end = poly.CPoint( ii+1 );
684
685 // actualTdLen is the distance between start (the reference segment start point)
686 // and the point on track of the teardrop.
687 // This is the difference between the initial actualTdLen value and the
688 // distance between start and ref_lenght_point.
689 actualTdLen -= (start - ref_lenght_point).EuclideanNorm();
690
691 // Ensure validity of actualTdLen: >= 0, and <= segment lenght
692 if( actualTdLen < 0 ) // should not happen, but...
693 actualTdLen = 0;
694
695 actualTdLen = std::min( actualTdLen, (end - start).EuclideanNorm() );
696
697 break;
698 }
699 }
700 }
701 }
702
703 // aStartPoint and aEndPoint will define later a segment to build the 2 anchors points
704 // of the teardrop on the aTrack shape.
705 // they are two points (both outside the pad/via shape) of aTrack if aTrack is a segment,
706 // or a small segment on aTrack if aTrack is an ARC
707 aStartPoint = start;
708 aEndPoint = end;
709
710 *aEffectiveTeardropLen = actualTdLen;
711 return found;
712}
713
714
716 std::vector<VECTOR2I>& aCorners, PCB_TRACK* aTrack,
717 BOARD_ITEM* aOther, const VECTOR2I& aOtherPos ) const
718{
719 VECTOR2I start, end; // Start and end points of the track anchor of the teardrop
720 // the start point is inside the teardrop shape
721 // the end point is outside.
722 int track_stub_len; // the dist between the start point and the anchor point
723 // on the track
724
725 // Note: aTrack can be modified if the initial track is too short
726 if( !findAnchorPointsOnTrack( aParams, start, end, aTrack, aOther, aOtherPos, &track_stub_len ) )
727 return false;
728
729 // The start and end points must be different to calculate a valid polygon shape
730 if( start == end )
731 return false;
732
733 VECTOR2D vecT = NormalizeVector(end - start);
734
735 // find the 2 points on the track, sharp end of the teardrop
736 int track_halfwidth = aTrack->GetWidth() / 2;
737 VECTOR2I pointB = start + VECTOR2I( vecT.x * track_stub_len + vecT.y * track_halfwidth,
738 vecT.y * track_stub_len - vecT.x * track_halfwidth );
739 VECTOR2I pointA = start + VECTOR2I( vecT.x * track_stub_len - vecT.y * track_halfwidth,
740 vecT.y * track_stub_len + vecT.x * track_halfwidth );
741
742 // To build a polygonal valid shape pointA and point B must be outside the pad
743 // It can be inside with some pad shapes having very different X and X sizes
744 if( !IsRound( aOther ) )
745 {
746 PAD* pad = static_cast<PAD*>( aOther );
747
748 if( pad->HitTest( pointA ) )
749 return false;
750
751 if( pad->HitTest( pointB ) )
752 return false;
753 }
754
755 // Introduce a last point to cover the via centre to ensure it is seen as connected
756 VECTOR2I pointD = aOtherPos;
757 // add a small offset in order to have the aViaPad.m_Pos reference point inside
758 // the teardrop area, just in case...
759 int offset = pcbIUScale.mmToIU( 0.001 );
760 pointD += VECTOR2I( int( -vecT.x*offset), int(-vecT.y*offset) );
761
762 VECTOR2I pointC, pointE; // Point on PADVIA outlines
763 std::vector<VECTOR2I> pts = {pointA, pointB, pointC, pointD, pointE};
764
765 computeAnchorPoints( aParams, aTrack->GetLayer(), aOther, aOtherPos, pts );
766
767 if( !aParams.IsCurved() )
768 {
769 aCorners = pts;
770 return true;
771 }
772
773 // See if we can use curved teardrop shape
774 if( IsRound( aOther ) )
775 {
776 computeCurvedForRoundShape( aParams, aCorners, track_halfwidth, vecT, aOther, aOtherPos, pts );
777 }
778 else
779 {
780 int td_width = KiROUND( GetWidth( aOther ) * aParams.m_BestWidthRatio );
781
782 if( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < td_width )
783 td_width = aParams.m_TdMaxWidth;
784
785 computeCurvedForRectShape( aParams, aCorners, td_width, track_halfwidth, pts );
786 }
787
788 return true;
789}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:108
constexpr int ARC_LOW_DEF
Definition: base_units.h:119
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.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:77
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition: board_item.h:226
const ZONES & Zones() const
Definition: board.h:326
const TRACKS & Tracks() const
Definition: board.h:320
void Insert(BOARD_ITEM *aItem, PCB_LAYER_ID aLayer, int aWorstClearance=0)
Insert an item into the tree on a particular layer with an optional worst clearance.
Definition: drc_rtree.h:104
int QueryColliding(BOARD_ITEM *aRefItem, PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer, std::function< bool(BOARD_ITEM *)> aFilter=nullptr, std::function< bool(BOARD_ITEM *)> aVisitor=nullptr, int aClearance=0) const
This is a fast test which essentially does bounding-box overlap given a worst-case clearance.
Definition: drc_rtree.h:214
virtual VECTOR2I GetPosition() const
Definition: eda_item.h:242
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:100
virtual bool HitTest(const VECTOR2I &aPosition, int aAccuracy=0) const
Test if aPosition is inside or on the boundary of this item.
Definition: eda_item.h:215
Definition: pad.h:59
const VECTOR2I & GetMid() const
Definition: pcb_track.h:315
virtual double GetLength() const
Get the length of the track using the hypotenuse calculation.
Definition: pcb_track.cpp:692
int GetWidth() const
Definition: pcb_track.h:107
const VECTOR2I & GetStart() const
Definition: pcb_track.h:113
const VECTOR2I & GetEnd() const
Definition: pcb_track.h:110
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:512
void Reverse()
Definition: shape_arc.cpp:627
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 intersection For aFastMode meaning, see function booleanOp.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void Move(const VECTOR2I &aVector) override
BOARD * m_board
Definition: teardrop.h:236
bool computeAnchorPoints(const TEARDROP_PARAMETERS &aParams, PCB_LAYER_ID aLayer, BOARD_ITEM *aItem, const VECTOR2I &aPos, std::vector< VECTOR2I > &aPts) const
Compute the 2 points on pad/via of the teardrop shape.
bool computeTeardropPolygon(const TEARDROP_PARAMETERS &aParams, std::vector< VECTOR2I > &aCorners, PCB_TRACK *aTrack, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos) const
Compute all teardrop points of the polygon shape.
PCB_TRACK * findTouchingTrack(EDA_ITEM_FLAGS &aMatchType, PCB_TRACK *aTrackRef, const VECTOR2I &aEndPoint) const
Find a track connected to the end of another track.
bool findAnchorPointsOnTrack(const TEARDROP_PARAMETERS &aParams, VECTOR2I &aStartPoint, VECTOR2I &aEndPoint, PCB_TRACK *&aTrack, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos, int *aEffectiveTeardropLen) const
TRACK_BUFFER m_trackLookupList
Definition: teardrop.h:241
static bool IsRound(BOARD_ITEM *aItem)
bool areItemsInSameZone(BOARD_ITEM *aPadOrVia, PCB_TRACK *aTrack) const
void computeCurvedForRectShape(const TEARDROP_PARAMETERS &aParams, std::vector< VECTOR2I > &aPoly, int aTdWidth, int aTrackHalfWidth, std::vector< VECTOR2I > &aPts) const
Compute the curve part points for teardrops connected to a rectangular/polygonal shape The Bezier cur...
static int GetWidth(BOARD_ITEM *aItem)
DRC_RTREE m_tracksRTree
Definition: teardrop.h:240
void computeCurvedForRoundShape(const TEARDROP_PARAMETERS &aParams, std::vector< VECTOR2I > &aPoly, int aTrackHalfWidth, const VECTOR2D &aTrackDir, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos, std::vector< VECTOR2I > &aPts) const
Compute the curve part points for teardrops connected to a round shape The Bezier curve control point...
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_BestWidthRatio
The height of a teardrop as ratio between height and size of pad/via.
int m_TdMaxLen
max allowed length for teardrops in IU. <= 0 to disable
bool m_AllowUseTwoTracks
True to create teardrops using 2 track segments if the first in too small.
int m_TdMaxWidth
max allowed height for teardrops in IU. <= 0 to disable
double m_BestLengthRatio
The length of a teardrop as ratio between length and size of pad/via.
int idxFromLayNet(int aLayer, int aNetcode) const
Definition: teardrop.h:72
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:78
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:72
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:437
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:60
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:424
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:88
static VECTOR2D NormalizeVector(const VECTOR2I &aVector)
constexpr int delta
double EuclideanNorm(const VECTOR2I &vector)
Definition: trigo.h:128
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:97
@ 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:98
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition: typeinfo.h:96
int sign(T val)
Definition: util.h:168
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:118
VECTOR2< int > VECTOR2I
Definition: vector2d.h:588