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 The 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, see <https://www.gnu.org/licenses/>.
19 */
20
21/*
22 * Some calculations (mainly computeCurvedForRoundShape) are derived from
23 * https://github.com/NilujePerchut/kicad_scripts/tree/master/teardrops
24 */
25
27#include <pcb_track.h>
28#include <pad.h>
29#include <zone_filler.h>
30#include <board_commit.h>
31#include <drc/drc_rtree.h>
32#include <trigo.h>
33
34#include "teardrop.h"
38#include <bezier_curves.h>
39
40#include <wx/log.h>
41
42
43void TRACK_BUFFER::AddTrack( PCB_TRACK* aTrack, int aLayer, int aNetcode )
44{
45 auto item = m_map_tracks.find( idxFromLayNet( aLayer, aNetcode ) );
46 std::vector<PCB_TRACK*>* buffer;
47
48 if( item == m_map_tracks.end() )
49 {
50 buffer = new std::vector<PCB_TRACK*>;
51 m_map_tracks[idxFromLayNet( aLayer, aNetcode )] = buffer;
52 }
53 else
54 {
55 buffer = (*item).second;
56 }
57
58 buffer->push_back( aTrack );
59}
60
61
63{
64 if( aItem->Type() == PCB_VIA_T )
65 {
66 PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
67 return via->GetWidth( aLayer );
68 }
69 else if( aItem->Type() == PCB_PAD_T )
70 {
71 PAD* pad = static_cast<PAD*>( aItem );
72 return std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y );
73 }
74 else if( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_ARC_T )
75 {
76 PCB_TRACK* track = static_cast<PCB_TRACK*>( aItem );
77 return track->GetWidth();
78 }
79
80 return 0;
81}
82
83
85{
86 if( aItem->Type() == PCB_PAD_T )
87 {
88 PAD* pad = static_cast<PAD*>( aItem );
89
90 return pad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE
91 || ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL
92 && pad->GetSize( aLayer ).x
93 == pad->GetSize( aLayer ).y );
94 }
95
96 return true;
97}
98
99
101{
102 for( PCB_TRACK* track : m_board->Tracks() )
103 {
104 if( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T )
105 {
106 m_tracksRTree.Insert( track, track->GetLayer() );
107 m_trackLookupList.AddTrack( track, track->GetLayer(), track->GetNetCode() );
108 }
109 }
110
111 m_tracksRTree.Build();
112}
113
114
116{
117 PCB_LAYER_ID layer = aTrack->GetLayer();
118
119 for( ZONE* zone : m_board->Zones() )
120 {
121 // Skip teardrops
122 if( zone->IsTeardropArea() )
123 continue;
124
125 // Only consider zones on the same layer as the track
126 if( !zone->IsOnLayer( layer ) )
127 continue;
128
129 if( zone->GetNetCode() != aTrack->GetNetCode() )
130 continue;
131
132 // The zone must have filled copper on this layer to provide a connection
133 if( !zone->HasFilledPolysForLayer( layer ) )
134 continue;
135
136 std::shared_ptr<SHAPE_POLY_SET> fill = zone->GetFilledPolysList( layer );
137
138 if( !fill || fill->IsEmpty() )
139 continue;
140
141 // Check if the zone's filled copper actually contains both the pad/via and the track.
142 // The zone outline might contain these items, but the actual fill might not reach them
143 // due to thermal settings, minimum width, island removal, etc.
144 VECTOR2I padPos( aPadOrVia->GetPosition() );
145
146 if( !fill->Contains( padPos ) )
147 continue;
148
149 // Also verify the track is within the filled zone (check both endpoints)
150 if( !fill->Contains( aTrack->GetStart() ) && !fill->Contains( aTrack->GetEnd() ) )
151 continue;
152
153 // If the first item is a pad, ensure it can be connected to the zone
154 if( aPadOrVia->Type() == PCB_PAD_T )
155 {
156 PAD* pad = static_cast<PAD*>( aPadOrVia );
157
158 if( zone->GetPadConnection() == ZONE_CONNECTION::NONE
159 || pad->GetZoneConnectionOverrides( nullptr ) == ZONE_CONNECTION::NONE )
160 {
161 return false;
162 }
163 }
164
165 return true;
166 }
167
168 return false;
169}
170
171
173 PCB_LAYER_ID aLayer ) const
174{
175 VECTOR2I start = aTrack->GetStart();
176 VECTOR2I end = aTrack->GetEnd();
177 bool startInside = aOther->HitTest( start, 0 );
178 bool endInside = aOther->HitTest( end, 0 );
179
180 if( startInside && endInside )
181 return 0;
182
183 // Fully outside: caller handles crossing geometry separately; report full length so
184 // the emergence filter never rejects those.
185 if( !startInside && !endInside )
186 return KiROUND( SEG( start, end ).Length() );
187
188 // Exactly one endpoint inside: normalize so start is outside, end is inside.
189 if( startInside )
190 std::swap( start, end );
191
192 int maxError = m_board->GetDesignSettings().m_MaxError;
193 int radius = GetWidth( aOther, aLayer ) / 2;
194 SHAPE_POLY_SET shapebuffer;
195
196 if( IsRound( aOther, aLayer ) )
197 {
198 TransformCircleToPolygon( shapebuffer, aOther->GetPosition(), radius, maxError,
199 ERROR_INSIDE, 16 );
200 }
201 else
202 {
203 wxCHECK_MSG( aOther->Type() == PCB_PAD_T, 0, wxT( "Expected non-round item to be PAD" ) );
204 static_cast<PAD*>( aOther )->TransformShapeToPolygon( shapebuffer, aLayer, 0, maxError,
205 ERROR_INSIDE );
206 }
207
208 SHAPE_LINE_CHAIN& outline = shapebuffer.Outline( 0 );
209 outline.SetClosed( true );
210
212 int pt_count = 0;
213
214 if( aTrack->Type() == PCB_ARC_T )
215 {
216 SHAPE_ARC arc( aTrack->GetStart(), static_cast<PCB_ARC*>( aTrack )->GetMid(),
217 aTrack->GetEnd(), aTrack->GetWidth() );
218 SHAPE_LINE_CHAIN poly = arc.ConvertToPolyline( maxError );
219 pt_count = outline.Intersect( poly, pts );
220 }
221 else
222 {
223 pt_count = outline.Intersect( SEG( start, end ), pts );
224 }
225
226 if( pt_count < 1 )
227 return 0;
228
229 double minDist = std::numeric_limits<double>::max();
230
231 for( const SHAPE_LINE_CHAIN::INTERSECTION& hit : pts )
232 {
233 double d = ( hit.p - start ).EuclideanNorm();
234
235 if( d < minDist )
236 minDist = d;
237 }
238
239 return KiROUND( minDist );
240}
241
242
244 const VECTOR2I& aEndPoint ) const
245{
246 int matches = 0; // Count of candidates: only 1 is acceptable
247 PCB_TRACK* candidate = nullptr; // a reference to the track connected
248
249 m_tracksRTree.QueryColliding( aTrackRef, aTrackRef->GetLayer(), aTrackRef->GetLayer(),
250 // Filter:
251 [&]( BOARD_ITEM* trackItem ) -> bool
252 {
253 return trackItem != aTrackRef;
254 },
255 // Visitor
256 [&]( BOARD_ITEM* trackItem ) -> bool
257 {
258 PCB_TRACK* curr_track = static_cast<PCB_TRACK*>( trackItem );
259
260 // IsPointOnEnds() returns 0, EDA_ITEM_FLAGS::STARTPOINT or EDA_ITEM_FLAGS::ENDPOINT
261 if( EDA_ITEM_FLAGS match = curr_track->IsPointOnEnds( aEndPoint, m_tolerance ) )
262 {
263 // if faced with a Y junction, choose the track longest segment as candidate
264 matches++;
265
266 if( matches > 1 )
267 {
268 double previous_len = candidate->GetLength();
269 double curr_len = curr_track->GetLength();
270
271 if( previous_len >= curr_len )
272 return true;
273 }
274
275 aMatchType = match;
276 candidate = curr_track;
277 }
278
279 return true;
280 },
281 0 );
282
283 return candidate;
284}
285
286
290static VECTOR2D NormalizeVector( const VECTOR2I& aVector )
291{
292 VECTOR2D vect( aVector );
293 double norm = vect.EuclideanNorm();
294 return vect / norm;
295}
296
297
298/*
299 * Compute the curve part points for teardrops connected to a round shape
300 * The Bezier curve control points are optimized for a round pad/via shape,
301 * and do not give a good curve shape for other pad shapes.
302 *
303 * For large circles where the teardrop width is constrained, the anchor points
304 * are projected onto the circle edge to ensure proper tangent calculation.
305 */
307 std::vector<VECTOR2I>& aPoly,
308 PCB_LAYER_ID aLayer,
309 int aTrackHalfWidth, const VECTOR2D& aTrackDir,
310 BOARD_ITEM* aOther, const VECTOR2I& aOtherPos,
311 std::vector<VECTOR2I>& pts ) const
312{
313 int maxError = m_board->GetDesignSettings().m_MaxError;
314
315 // in pts:
316 // A and B are points on the track ( pts[0] and pts[1] )
317 // C and E are points on the aViaPad ( pts[2] and pts[4] )
318 // D is the aViaPad centre ( pts[3] )
319 double Vpercent = aParams.m_BestWidthRatio;
320 int td_height = KiROUND( GetWidth( aOther, aLayer ) * Vpercent );
321
322 // First, calculate a aVpercent equivalent to the td_height clamped by aTdMaxHeight
323 // We cannot use the initial aVpercent because it gives bad shape with points
324 // on aViaPad calculated for a clamped aViaPad size
325 if( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < td_height )
326 Vpercent *= (double) aParams.m_TdMaxWidth / td_height;
327
328 int radius = GetWidth( aOther, aLayer ) / 2;
329
330 // Don't divide by zero. No good can come of that.
331 wxCHECK2( radius != 0, radius = 1 );
332
333 double minVpercent = double( aTrackHalfWidth ) / radius;
334 double weaken = (Vpercent - minVpercent) / ( 1 - minVpercent ) / radius;
335
336 // For large circles where teardrop width is constrained, the anchor points from the
337 // convex hull may not be exactly on the circle. Project them onto the circle edge
338 // to ensure proper tangent calculation for smooth curves.
339 VECTOR2I vecC = pts[2] - aOtherPos;
340 double distC = vecC.EuclideanNorm();
341
342 if( distC > 0 && std::abs( distC - radius ) > maxError )
343 {
344 // Point is not on the circle - project it to the circle edge
345 pts[2] = aOtherPos + vecC.Resize( radius );
346 vecC = pts[2] - aOtherPos;
347 }
348
349 VECTOR2I vecE = pts[4] - aOtherPos;
350 double distE = vecE.EuclideanNorm();
351
352 if( distE > 0 && std::abs( distE - radius ) > maxError )
353 {
354 // Point is not on the circle - project it to the circle edge
355 pts[4] = aOtherPos + vecE.Resize( radius );
356 vecE = pts[4] - aOtherPos;
357 }
358
359 double biasBC = 0.5 * SEG( pts[1], pts[2] ).Length();
360 double biasAE = 0.5 * SEG( pts[4], pts[0] ).Length();
361
362 VECTOR2I tangentC = VECTOR2I( pts[2].x - vecC.y * biasBC * weaken,
363 pts[2].y + vecC.x * biasBC * weaken );
364 VECTOR2I tangentE = VECTOR2I( pts[4].x + vecE.y * biasAE * weaken,
365 pts[4].y - vecE.x * biasAE * weaken );
366
367 VECTOR2I tangentB = VECTOR2I( pts[1].x - aTrackDir.x * biasBC, pts[1].y - aTrackDir.y * biasBC );
368 VECTOR2I tangentA = VECTOR2I( pts[0].x - aTrackDir.x * biasAE, pts[0].y - aTrackDir.y * biasAE );
369
370 std::vector<VECTOR2I> curve_pts;
371 BEZIER_POLY( pts[1], tangentB, tangentC, pts[2] ).GetPoly( curve_pts, maxError );
372
373 for( VECTOR2I& corner: curve_pts )
374 aPoly.push_back( corner );
375
376 aPoly.push_back( pts[3] );
377
378 curve_pts.clear();
379 BEZIER_POLY( pts[4], tangentE, tangentA, pts[0] ).GetPoly( curve_pts, maxError );
380
381 for( VECTOR2I& corner: curve_pts )
382 aPoly.push_back( corner );
383}
384
385
398 const VECTOR2I& aCornerCenter,
399 double aBias,
400 const VECTOR2I& aDesiredDir )
401{
402 VECTOR2I radial = aAnchor - aCornerCenter;
403
404 if( radial.EuclideanNorm() == 0 )
405 return aAnchor;
406
407 // Tangent is perpendicular to the radius. There are two perpendicular directions:
408 // (radial.y, -radial.x) and (-radial.y, radial.x)
409 // Choose the one that best aligns with the desired direction (toward the track)
410 VECTOR2I tangent1( radial.y, -radial.x );
411 VECTOR2I tangent2( -radial.y, radial.x );
412
413 // Use dot product to determine which tangent direction aligns better with desired direction
414 int64_t dot1 = static_cast<int64_t>( tangent1.x ) * aDesiredDir.x
415 + static_cast<int64_t>( tangent1.y ) * aDesiredDir.y;
416 int64_t dot2 = static_cast<int64_t>( tangent2.x ) * aDesiredDir.x
417 + static_cast<int64_t>( tangent2.y ) * aDesiredDir.y;
418
419 VECTOR2I tangent = ( dot1 > dot2 ) ? tangent1 : tangent2;
420
421 return aAnchor + tangent.Resize( KiROUND( aBias ) );
422}
423
424
436static bool isPointOnOvalEnd( const VECTOR2I& aPoint, const VECTOR2I& aPadPos,
437 const VECTOR2I& aPadSize, const EDA_ANGLE& aRotation,
438 VECTOR2I& aArcCenter )
439{
440 // Transform point to pad-local coordinates (unrotated)
441 VECTOR2I localPt = aPoint - aPadPos;
442 RotatePoint( localPt, aRotation );
443
444 int halfW = aPadSize.x / 2;
445 int halfH = aPadSize.y / 2;
446
447 // Oval geometry: semicircle radius is min dimension / 2
448 // The semicircle centers are offset along the major axis
449 int radius = std::min( halfW, halfH );
450 bool isHorizontal = halfW > halfH;
451
452 if( isHorizontal )
453 {
454 // Semicircles at left and right ends
455 int centerOffset = halfW - radius;
456
457 // Check if point is in the curved region (beyond the straight sides)
458 if( std::abs( localPt.x ) <= centerOffset )
459 return false;
460
461 // Determine which end
462 int centerX = ( localPt.x > 0 ) ? centerOffset : -centerOffset;
463 aArcCenter = VECTOR2I( centerX, 0 );
464 }
465 else
466 {
467 // Semicircles at top and bottom ends
468 int centerOffset = halfH - radius;
469
470 // Check if point is in the curved region (beyond the straight sides)
471 if( std::abs( localPt.y ) <= centerOffset )
472 return false;
473
474 // Determine which end
475 int centerY = ( localPt.y > 0 ) ? centerOffset : -centerOffset;
476 aArcCenter = VECTOR2I( 0, centerY );
477 }
478
479 // Transform arc center back to board coordinates
480 RotatePoint( aArcCenter, -aRotation );
481 aArcCenter += aPadPos;
482
483 return true;
484}
485
486
499static bool isPointOnRoundedCorner( const VECTOR2I& aPoint, const VECTOR2I& aPadPos,
500 const VECTOR2I& aPadSize, int aCornerRadius,
501 const EDA_ANGLE& aRotation, VECTOR2I& aCornerCenter )
502{
503 // Transform point to pad-local coordinates (unrotated)
504 VECTOR2I localPt = aPoint - aPadPos;
505 RotatePoint( localPt, aRotation );
506
507 // Half-sizes minus corner radius define the inner rectangle
508 int halfW = aPadSize.x / 2;
509 int halfH = aPadSize.y / 2;
510 int innerHalfW = halfW - aCornerRadius;
511 int innerHalfH = halfH - aCornerRadius;
512
513 // Point is in corner region if it's outside the inner rectangle in both dimensions
514 bool inCornerX = std::abs( localPt.x ) > innerHalfW;
515 bool inCornerY = std::abs( localPt.y ) > innerHalfH;
516
517 if( !inCornerX || !inCornerY )
518 return false;
519
520 // Determine which corner
521 int cornerX = ( localPt.x > 0 ) ? innerHalfW : -innerHalfW;
522 int cornerY = ( localPt.y > 0 ) ? innerHalfH : -innerHalfH;
523
524 aCornerCenter = VECTOR2I( cornerX, cornerY );
525
526 // Transform corner center back to board coordinates
527 RotatePoint( aCornerCenter, -aRotation );
528 aCornerCenter += aPadPos;
529
530 return true;
531}
532
533
534/*
535 * Compute the curve part points for teardrops connected to a rectangular/polygonal shape.
536 * For rounded rectangles, control points are computed to be tangent to corner arcs,
537 * preventing the teardrop curve from intersecting the pad's corner radius.
538 */
540 std::vector<VECTOR2I>& aPoly, int aTdWidth,
541 int aTrackHalfWidth,
542 std::vector<VECTOR2I>& aPts,
543 const VECTOR2I& aIntersection,
544 BOARD_ITEM* aOther,
545 const VECTOR2I& aOtherPos,
546 PCB_LAYER_ID aLayer ) const
547{
548 int maxError = m_board->GetDesignSettings().m_MaxError;
549
550 // in aPts:
551 // A and B are points on the track ( pts[0] and pts[1] )
552 // C and E are points on the pad/via ( pts[2] and pts[4] )
553 // D is the aViaPad centre ( pts[3] )
554
555 // side1 is( aPts[1], aPts[2] ); from track to via
556 VECTOR2I side1( aPts[2] - aPts[1] ); // vector from track to via
557 // side2 is ( aPts[4], aPts[0] ); from via to track
558 VECTOR2I side2( aPts[4] - aPts[0] ); // vector from track to via
559
560 VECTOR2I trackDir( aIntersection - ( aPts[0] + aPts[1] ) / 2 );
561
562 // Check if this is a rounded rectangle or oval pad (both have curved regions)
563 bool isRoundRect = false;
564 bool isOval = false;
565 int cornerRadius = 0;
566 VECTOR2I padSize;
567 EDA_ANGLE padRotation;
568
569 if( aOther && aOther->Type() == PCB_PAD_T )
570 {
571 PAD* pad = static_cast<PAD*>( aOther );
572 PAD_SHAPE shape = pad->GetShape( aLayer );
573
574 if( shape == PAD_SHAPE::ROUNDRECT )
575 {
576 isRoundRect = true;
577 cornerRadius = pad->GetRoundRectCornerRadius( aLayer );
578 padSize = pad->GetSize( aLayer );
579 padRotation = pad->GetOrientation();
580 }
581 else if( shape == PAD_SHAPE::OVAL )
582 {
583 isOval = true;
584 padSize = pad->GetSize( aLayer );
585 padRotation = pad->GetOrientation();
586 }
587 }
588
589 std::vector<VECTOR2I> curve_pts;
590
591 // Compute control points for the first Bezier curve (track point B to pad point C)
592 VECTOR2I ctrl1 = aPts[1] + trackDir.Resize( side1.EuclideanNorm() / 4 );
593 VECTOR2I ctrl2;
594
595 // Direction from pad anchor toward track (opposite of trackDir which goes pad-ward)
596 VECTOR2I towardTrack = -trackDir;
597
598 // Default control point - midpoint approach
599 ctrl2 = ( aPts[2] + aIntersection ) / 2;
600
601 if( isRoundRect && cornerRadius > 0 )
602 {
603 VECTOR2I cornerCenter;
604
605 if( isPointOnRoundedCorner( aPts[2], aOtherPos, padSize, cornerRadius,
606 padRotation, cornerCenter ) )
607 {
608 // Anchor is on a corner arc - use tangent-based control point
609 double bias = 0.5 * side1.EuclideanNorm();
610 ctrl2 = computeCornerTangentControlPoint( aPts[2], cornerCenter, bias, towardTrack );
611 }
612 }
613 else if( isOval )
614 {
615 VECTOR2I arcCenter;
616
617 if( isPointOnOvalEnd( aPts[2], aOtherPos, padSize, padRotation, arcCenter ) )
618 {
619 // Anchor is on a curved end - use tangent-based control point
620 double bias = 0.5 * side1.EuclideanNorm();
621 ctrl2 = computeCornerTangentControlPoint( aPts[2], arcCenter, bias, towardTrack );
622 }
623 }
624
625 BEZIER_POLY( aPts[1], ctrl1, ctrl2, aPts[2] ).GetPoly( curve_pts, maxError );
626
627 for( VECTOR2I& corner: curve_pts )
628 aPoly.push_back( corner );
629
630 aPoly.push_back( aPts[3] );
631
632 // Compute control points for second Bezier curve (pad point E to track point A)
633 curve_pts.clear();
634
635 // Default control point - midpoint approach
636 ctrl1 = ( aPts[4] + aIntersection ) / 2;
637
638 if( isRoundRect && cornerRadius > 0 )
639 {
640 VECTOR2I cornerCenter;
641
642 if( isPointOnRoundedCorner( aPts[4], aOtherPos, padSize, cornerRadius,
643 padRotation, cornerCenter ) )
644 {
645 // Anchor is on a corner arc - use tangent-based control point
646 double bias = 0.5 * side2.EuclideanNorm();
647 ctrl1 = computeCornerTangentControlPoint( aPts[4], cornerCenter, bias, towardTrack );
648 }
649 }
650 else if( isOval )
651 {
652 VECTOR2I arcCenter;
653
654 if( isPointOnOvalEnd( aPts[4], aOtherPos, padSize, padRotation, arcCenter ) )
655 {
656 // Anchor is on a curved end - use tangent-based control point
657 double bias = 0.5 * side2.EuclideanNorm();
658 ctrl1 = computeCornerTangentControlPoint( aPts[4], arcCenter, bias, towardTrack );
659 }
660 }
661
662 ctrl2 = aPts[0] + trackDir.Resize( side2.EuclideanNorm() / 4 );
663
664 BEZIER_POLY( aPts[4], ctrl1, ctrl2, aPts[0] ).GetPoly( curve_pts, maxError );
665
666 for( VECTOR2I& corner: curve_pts )
667 aPoly.push_back( corner );
668}
669
670
672 BOARD_ITEM* aItem, const VECTOR2I& aPos,
673 std::vector<VECTOR2I>& aPts ) const
674{
675 int maxError = m_board->GetDesignSettings().m_MaxError;
676
677 // Compute the 2 anchor points on pad/via/track of the teardrop shape
678
679 SHAPE_POLY_SET c_buffer;
680
681 // m_BestWidthRatio is the factor to calculate the teardrop preferred width.
682 // teardrop width = pad, via or track size * m_BestWidthRatio (m_BestWidthRatio <= 1.0)
683 // For rectangular (and similar) shapes, the preferred_width is calculated from the min
684 // dim of the rectangle
685
686 int preferred_width = KiROUND( GetWidth( aItem, aLayer ) * aParams.m_BestWidthRatio );
687
688 // force_clip = true to force the pad/via/track polygon to be clipped to follow
689 // constraints
690 // Clipping is also needed for rectangular shapes, because the teardrop shape is restricted
691 // to a polygonal area smaller than the pad area (the teardrop height use the smaller value
692 // of X and Y sizes).
693 bool force_clip = aParams.m_BestWidthRatio < 1.0;
694
695 // To find the anchor points on the pad/via/track shape, we build the polygonal shape, and
696 // clip the polygon to the max size (preferred_width or m_TdMaxWidth) by a rectangle
697 // centered on the axis of the expected teardrop shape.
698 // (only reduce the size of polygonal shape does not give good anchor points)
699 if( IsRound( aItem, aLayer ) )
700 {
701 TransformCircleToPolygon( c_buffer, aPos, GetWidth( aItem, aLayer ) / 2, maxError,
702 ERROR_INSIDE, 16 );
703 }
704 else // Only PADS can have a not round shape
705 {
706 wxCHECK_MSG( aItem->Type() == PCB_PAD_T, false, wxT( "Expected non-round item to be PAD" ) );
707 PAD* pad = static_cast<PAD*>( aItem );
708
709 force_clip = true;
710
711 preferred_width = KiROUND( GetWidth( pad, aLayer ) * aParams.m_BestWidthRatio );
712 pad->TransformShapeToPolygon( c_buffer, aLayer, 0, maxError, ERROR_INSIDE );
713 }
714
715 // Clip the pad/via/track shape to match the m_TdMaxWidth constraint, and for non-round pads,
716 // clip the shape to the smallest of size.x and size.y values.
717 if( force_clip || ( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < preferred_width ) )
718 {
719 int halfsize = std::min( aParams.m_TdMaxWidth, preferred_width )/2;
720
721 // teardrop_axis is the line from anchor point on the track and the end point
722 // of the teardrop in the pad/via
723 // this is the teardrop_axis of the teardrop shape to build
724 VECTOR2I ref_on_track = ( aPts[0] + aPts[1] ) / 2;
725 VECTOR2I teardrop_axis( aPts[3] - ref_on_track );
726
727 EDA_ANGLE orient( teardrop_axis );
728 int len = teardrop_axis.EuclideanNorm();
729
730 // Build the constraint polygon: a rectangle with
731 // length = dist between the point on track and the pad/via pos
732 // height = m_TdMaxWidth or aViaPad.m_Width
733 SHAPE_POLY_SET clipping_rect;
734 clipping_rect.NewOutline();
735
736 // Build a horizontal rect: it will be rotated later
737 clipping_rect.Append( 0, - halfsize );
738 clipping_rect.Append( 0, halfsize );
739 clipping_rect.Append( len, halfsize );
740 clipping_rect.Append( len, - halfsize );
741
742 clipping_rect.Rotate( -orient );
743 clipping_rect.Move( ref_on_track );
744
745 // Clip the shape to the max allowed teadrop area
746 c_buffer.BooleanIntersection( clipping_rect );
747 }
748
749 /* in aPts:
750 * A and B are points on the track ( aPts[0] and aPts[1] )
751 * C and E are points on the aViaPad ( aPts[2] and aPts[4] )
752 * D is midpoint behind the aViaPad centre ( aPts[3] )
753 */
754
755 if( c_buffer.OutlineCount() == 0 )
756 return false;
757
758 SHAPE_LINE_CHAIN& padpoly = c_buffer.Outline(0);
759 std::vector<VECTOR2I> points = padpoly.CPoints();
760
761 std::vector<VECTOR2I> initialPoints;
762 initialPoints.push_back( aPts[0] );
763 initialPoints.push_back( aPts[1] );
764
765 for( const VECTOR2I& pt: points )
766 initialPoints.emplace_back( pt.x, pt.y );
767
768 std::vector<VECTOR2I> hull;
769 BuildConvexHull( hull, initialPoints );
770
771 // Search for end points of segments starting at aPts[0] or aPts[1]
772 // In some cases, in convex hull, only one point (aPts[0] or aPts[1]) is still in list
773 VECTOR2I PointC;
774 VECTOR2I PointE;
775 int found_start = -1; // 2 points (one start and one end) should be found
776 int found_end = -1;
777
778 VECTOR2I start = aPts[0];
779 VECTOR2I pend = aPts[1];
780
781 for( unsigned ii = 0, jj = 0; jj < hull.size(); ii++, jj++ )
782 {
783 unsigned next = ii+ 1;
784
785 if( next >= hull.size() )
786 next = 0;
787
788 int prev = ii -1;
789
790 if( prev < 0 )
791 prev = hull.size()-1;
792
793 if( hull[ii] == start )
794 {
795 // the previous or the next point is candidate:
796 if( hull[next] != pend )
797 PointE = hull[next];
798 else
799 PointE = hull[prev];
800
801 found_start = ii;
802 }
803
804 if( hull[ii] == pend )
805 {
806 if( hull[next] != start )
807 PointC = hull[next];
808 else
809 PointC = hull[prev];
810
811 found_end = ii;
812 }
813 }
814
815 if( found_start < 0 ) // PointE was not initialized, because start point does not exit
816 {
817 int ii = found_end-1;
818
819 if( ii < 0 )
820 ii = hull.size()-1;
821
822 PointE = hull[ii];
823 }
824
825 if( found_end < 0 ) // PointC was not initialized, because end point does not exit
826 {
827 int ii = found_start-1;
828
829 if( ii < 0 )
830 ii = hull.size()-1;
831
832 PointC = hull[ii];
833 }
834
835 aPts[2] = PointC;
836 aPts[4] = PointE;
837
838 // Now we have to know if the choice aPts[2] = PointC is the best, or if
839 // aPts[2] = PointE is better.
840 // A criteria is to calculate the polygon area in these 2 cases, and choose the case
841 // that gives the bigger area, because the segments starting at PointC and PointE
842 // maximize their distance.
843 SHAPE_LINE_CHAIN dummy1( aPts, true );
844 double area1 = dummy1.Area();
845
846 std::swap( aPts[2], aPts[4] );
847 SHAPE_LINE_CHAIN dummy2( aPts, true );
848 double area2 = dummy2.Area();
849
850 if( area1 > area2 ) // The first choice (without swapping) is the better.
851 std::swap( aPts[2], aPts[4] );
852
853 return true;
854}
855
856
858 VECTOR2I& aStartPoint, VECTOR2I& aEndPoint,
859 VECTOR2I& aIntersection, PCB_TRACK*& aTrack,
860 BOARD_ITEM* aOther, const VECTOR2I& aOtherPos,
861 int* aEffectiveTeardropLen ) const
862{
863 bool found = true;
864 VECTOR2I start = aTrack->GetStart(); // one reference point on the track, inside teardrop
865 VECTOR2I end = aTrack->GetEnd(); // the second reference point on the track, outside teardrop
866 PCB_LAYER_ID layer = aTrack->GetLayer();
867 int radius = GetWidth( aOther, layer ) / 2;
868 int maxError = m_board->GetDesignSettings().m_MaxError;
869
870 // Requested length of the teardrop:
871 int targetLength = KiROUND( GetWidth( aOther, layer ) * aParams.m_BestLengthRatio );
872
873 if( aParams.m_TdMaxLen > 0 )
874 targetLength = std::min( aParams.m_TdMaxLen, targetLength );
875
876 // actualTdLen is the distance between start and the teardrop point on the segment from start to end
877 int actualTdLen;
878 bool need_swap = false; // true if the start and end points of the current track are swapped
879
880 // aTrack is expected to have one end inside the via/pad and the other end outside
881 // so ensure the start point is inside the via/pad
882 if( !aOther->HitTest( start, 0 ) )
883 {
884 std::swap( start, end );
885 need_swap = true;
886 }
887
888 SHAPE_POLY_SET shapebuffer;
889
890 if( IsRound( aOther, layer ) )
891 {
892 TransformCircleToPolygon( shapebuffer, aOtherPos, radius, maxError, ERROR_INSIDE, 16 );
893 }
894 else
895 {
896 wxCHECK_MSG( aOther->Type() == PCB_PAD_T, false, wxT( "Expected non-round item to be PAD" ) );
897 static_cast<PAD*>( aOther )->TransformShapeToPolygon( shapebuffer, aTrack->GetLayer(), 0,
898 maxError, ERROR_INSIDE );
899 }
900
901 SHAPE_LINE_CHAIN& outline = shapebuffer.Outline(0);
902 outline.SetClosed( true );
903
904 // Search the intersection point between the pad/via shape and the current track
905 // This this the starting point to define the teardrop length
907 int pt_count;
908
909 if( aTrack->Type() == PCB_ARC_T )
910 {
911 // To find the starting point we convert the arc to a polyline
912 // and compute the intersection point with the pad/via shape
913 SHAPE_ARC arc( aTrack->GetStart(), static_cast<PCB_ARC*>( aTrack )->GetMid(),
914 aTrack->GetEnd(), aTrack->GetWidth() );
915
916 SHAPE_LINE_CHAIN poly = arc.ConvertToPolyline( maxError );
917 pt_count = outline.Intersect( poly, pts );
918 }
919 else
920 {
921 pt_count = outline.Intersect( SEG( start, end ), pts );
922 }
923
924 // Ensure a intersection point was found, otherwise we cannot built the teardrop
925 // using this track (it is fully outside or inside the pad/via shape)
926 if( pt_count < 1 )
927 return false;
928
929 aIntersection = pts[0].p;
930 start = aIntersection; // This is currently the reference point of the teardrop length
931
932 // actualTdLen for now the distance between start and the teardrop point on the (start end)segment
933 // It cannot be bigger than the lenght of this segment
934 actualTdLen = std::min( targetLength, SEG( start, end ).Length() );
935 VECTOR2I ref_lenght_point = start; // the reference point of actualTdLen
936
937 // If the first track is too short to allow a teardrop having the requested length
938 // explore the connected track(s), and try to find a anchor point at targetLength from initial start
939 if( actualTdLen < targetLength && aParams.m_AllowUseTwoTracks )
940 {
941 int consumed = 0;
942
943 while( actualTdLen + consumed < targetLength )
944 {
945 EDA_ITEM_FLAGS matchType;
946
947 PCB_TRACK* connected_track = findTouchingTrack( matchType, aTrack, end );
948
949 if( connected_track == nullptr )
950 break;
951
952 // Reject the extension if the angle between segments is too large.
953 // Large angles cause the teardrop shape to bend sharply at the junction.
954 // The junction transition code handles bends up to ~60 degrees, so use
955 // cos(60) = 0.5 as the threshold.
956 constexpr double kMinCosForTwoSegmentExtension = 0.5;
957
958 VECTOR2D firstDir = NormalizeVector( end - ref_lenght_point );
959 VECTOR2D secondDir;
960
961 if( matchType == STARTPOINT )
962 {
963 secondDir = NormalizeVector( connected_track->GetEnd()
964 - connected_track->GetStart() );
965 }
966 else
967 {
968 secondDir = NormalizeVector( connected_track->GetStart()
969 - connected_track->GetEnd() );
970 }
971
972 double cosAngle = firstDir.x * secondDir.x + firstDir.y * secondDir.y;
973
974 if( cosAngle < kMinCosForTwoSegmentExtension )
975 break;
976
977 consumed += actualTdLen;
978 // actualTdLen is the new distance from new start point and the teardrop anchor point
979 actualTdLen = std::min( targetLength-consumed, int( connected_track->GetLength() ) );
980 aTrack = connected_track;
981 end = connected_track->GetEnd();
982 start = connected_track->GetStart();
983 need_swap = false;
984
985 if( matchType != STARTPOINT )
986 {
987 std::swap( start, end );
988 need_swap = true;
989 }
990
991 // If we do not want to explore more than one connected track, stop search here
992 break;
993 }
994 }
995
996 // if aTrack is an arc, find the best teardrop end point on the arc
997 // It is currently on the segment from arc start point to arc end point,
998 // therefore not really on the arc, because we have used only the track end points.
999 if( aTrack->Type() == PCB_ARC_T )
1000 {
1001 // To find the best start and end points to build the teardrop shape, we convert
1002 // the arc to segments, and search for the segment having its start point at a dist
1003 // < actualTdLen, and its end point at adist > actualTdLen:
1004 SHAPE_ARC arc( aTrack->GetStart(), static_cast<PCB_ARC*>( aTrack )->GetMid(),
1005 aTrack->GetEnd(), aTrack->GetWidth() );
1006
1007 if( need_swap )
1008 arc.Reverse();
1009
1010 SHAPE_LINE_CHAIN poly = arc.ConvertToPolyline( maxError );
1011
1012 // Now, find the segment of the arc at a distance < actualTdLen from ref_lenght_point.
1013 // We just search for the first segment (starting from the farest segment) with its
1014 // start point at a distance < actualTdLen dist
1015 // This is basic, but it is probably enough.
1016 if( poly.PointCount() > 2 )
1017 {
1018 // Note: the first point is inside or near the pad/via shape
1019 // The last point is outside and the farest from the ref_lenght_point
1020 // So we explore segments from the last to the first
1021 for( int ii = poly.PointCount()-1; ii >= 0 ; ii-- )
1022 {
1023 int dist_from_start = ( poly.CPoint( ii ) - start ).EuclideanNorm();
1024
1025 // The first segment at a distance of the reference point < actualTdLen is OK
1026 // and is suitable to define the reference segment of the teardrop anchor.
1027 if( dist_from_start < actualTdLen || ii == 0 )
1028 {
1029 start = poly.CPoint( ii );
1030
1031 if( ii < poly.PointCount()-1 )
1032 end = poly.CPoint( ii+1 );
1033
1034 // actualTdLen is the distance between start (the reference segment start point)
1035 // and the point on track of the teardrop.
1036 // This is the difference between the initial actualTdLen value and the
1037 // distance between start and ref_lenght_point.
1038 actualTdLen -= (start - ref_lenght_point).EuclideanNorm();
1039
1040 // Ensure validity of actualTdLen: >= 0, and <= segment lenght
1041 if( actualTdLen < 0 ) // should not happen, but...
1042 actualTdLen = 0;
1043
1044 actualTdLen = std::min( actualTdLen, (end - start).EuclideanNorm() );
1045
1046 break;
1047 }
1048 }
1049 }
1050 }
1051
1052 // aStartPoint and aEndPoint will define later a segment to build the 2 anchors points
1053 // of the teardrop on the aTrack shape.
1054 // they are two points (both outside the pad/via shape) of aTrack if aTrack is a segment,
1055 // or a small segment on aTrack if aTrack is an ARC
1056 aStartPoint = start;
1057 aEndPoint = end;
1058
1059 *aEffectiveTeardropLen = actualTdLen;
1060 return found;
1061}
1062
1063
1065 std::vector<VECTOR2I>& aCorners, PCB_TRACK* aTrack,
1066 BOARD_ITEM* aOther, const VECTOR2I& aOtherPos ) const
1067{
1068 VECTOR2I start, end; // Start and end points of the track anchor of the teardrop
1069 // the start point is inside the teardrop shape
1070 // the end point is outside.
1071 VECTOR2I intersection; // Where the track centerline intersects the pad/via edge
1072 int track_stub_len; // the dist between the start point and the anchor point
1073 // on the track
1074
1075 // Note: aTrack can be modified if the initial track is too short.
1076 // Save the original pointer so we can detect two-segment extension.
1077 PCB_TRACK* originalTrack = aTrack;
1078
1079 if( !findAnchorPointsOnTrack( aParams, start, end, intersection, aTrack, aOther, aOtherPos,
1080 &track_stub_len ) )
1081 {
1082 return false;
1083 }
1084
1085 // The start and end points must be different to calculate a valid polygon shape
1086 if( start == end )
1087 return false;
1088
1089 VECTOR2D vecT = NormalizeVector(end - start);
1090
1091 // When spanning two segments, findAnchorPointsOnTrack replaces aTrack with the
1092 // connected track. The start point becomes the junction between the two segments,
1093 // which differs from intersection (where the first segment meets the pad/via edge).
1094 // Use the first segment's direction for all via-side geometry so that the teardrop
1095 // shape is oriented correctly relative to how the track enters the pad/via.
1096 // Note: for arcs, start also moves away from intersection during arc refinement, but
1097 // aTrack remains the same pointer, so arc tracks are correctly excluded here.
1098 bool twoSegments = ( aTrack != originalTrack );
1099
1100 // vecVia is the direction the track enters the pad/via, used for via-side geometry.
1101 // When the first segment is so short that the junction coincides with the pad edge
1102 // intersection, start == intersection produces a zero vector whose normalization is
1103 // NaN. Fall back to the second segment's direction in that case.
1104 VECTOR2D vecVia = vecT;
1105
1106 if( twoSegments && start != intersection )
1107 vecVia = NormalizeVector( start - intersection );
1108
1109 // find the 2 points on the track, sharp end of the teardrop
1110 int track_halfwidth = aTrack->GetWidth() / 2;
1111 VECTOR2I pointB = start + VECTOR2I( vecT.x * track_stub_len + vecT.y * track_halfwidth,
1112 vecT.y * track_stub_len - vecT.x * track_halfwidth );
1113 VECTOR2I pointA = start + VECTOR2I( vecT.x * track_stub_len - vecT.y * track_halfwidth,
1114 vecT.y * track_stub_len + vecT.x * track_halfwidth );
1115
1116 PCB_LAYER_ID layer = aTrack->GetLayer();
1117
1118 // To build a polygonal valid shape pointA and point B must be outside the pad
1119 // It can be inside with some pad shapes having very different X and X sizes
1120 if( !IsRound( aOther, layer ) )
1121 {
1122 PAD* pad = static_cast<PAD*>( aOther );
1123
1124 if( pad->HitTest( pointA, 0, layer ) )
1125 return false;
1126
1127 if( pad->HitTest( pointB, 0, layer ) )
1128 return false;
1129 }
1130
1131 // Compute pointD, the "back" point of the teardrop behind the pad/via center.
1132 // For off-center track connections (where the track doesn't pass through the pad center),
1133 // we project the pad center onto the track axis so the teardrop is built symmetrically
1134 // about the track rather than being skewed toward the pad center.
1135 int padRadius = GetWidth( aOther, layer ) / 2;
1136 VECTOR2D intToPad = VECTOR2D( aOtherPos - intersection );
1137 double projOnTrack = -( intToPad.x * vecVia.x + intToPad.y * vecVia.y );
1138 double effectiveDist = std::max( projOnTrack, static_cast<double>( padRadius ) );
1139 int offset = pcbIUScale.mmToIU( 0.001 );
1140
1141 // For non-round pads, clamp effectiveDist so pointD stays within the pad outline.
1142 // pointD is placed at effectiveDist from the intersection along -vecVia (into the pad).
1143 // The intersection lies on the pad edge, so the segment from the intersection into the pad
1144 // must exit again through the far edge. Clamp effectiveDist to that far edge so pointD can
1145 // never escape the pad outline. This is essential for oblique connections where the track
1146 // only grazes a corner of an elongated pad. There the track axis crosses the pad rather
1147 // than entering its body, and an unclamped projection sends pointD spiking out the side.
1148 if( !IsRound( aOther, layer ) && aOther->Type() == PCB_PAD_T )
1149 {
1150 PAD* pad = static_cast<PAD*>( aOther );
1151 int maxError = m_board->GetDesignSettings().m_MaxError;
1152 SHAPE_POLY_SET padPoly;
1153 pad->TransformShapeToPolygon( padPoly, layer, 0, maxError, ERROR_INSIDE );
1154
1155 SHAPE_LINE_CHAIN& padOutline = padPoly.Outline( 0 );
1156 padOutline.SetClosed( true );
1157
1158 // Cast the into-pad ray from the intersection well past the candidate point so a chord
1159 // through the pad always produces a far-edge crossing to clamp against. The reach must
1160 // span the longest possible chord from the entry, so use the pad's circumscribed radius
1161 // rather than the minor half-axis (padRadius). On an elongated pad entered along its long
1162 // axis the far edge sits up to two major half-axes away, and a reach scaled by the minor
1163 // axis stops short of it, leaving farEdge == 0 and wrongly collapsing the teardrop.
1164 double reach = effectiveDist + 2.0 * pad->GetBoundingRadius() + offset;
1165 VECTOR2I rayEnd = intersection + VECTOR2I( KiROUND( -vecVia.x * reach ),
1166 KiROUND( -vecVia.y * reach ) );
1167
1169 padOutline.Intersect( SEG( intersection, rayEnd ), hits );
1170
1171 double farEdge = 0;
1172
1173 for( const SHAPE_LINE_CHAIN::INTERSECTION& hit : hits )
1174 {
1175 // Ignore the crossing at the intersection point itself.
1176 double d = ( hit.p - intersection ).EuclideanNorm();
1177
1178 if( d > offset )
1179 farEdge = std::max( farEdge, d );
1180 }
1181
1182 // farEdge == 0 means -vecVia does not penetrate the pad (a tangential graze); collapse
1183 // pointD onto the entry so the teardrop simply flares from the track to the pad edge.
1184 effectiveDist = std::min( effectiveDist, std::max( 0.0, farEdge - 2.0 * offset ) );
1185 }
1186 else
1187 {
1188 // For round pads/vias, clamp effectiveDist so pointD stays inside the pad circle.
1189 // The minimum-of-padRadius floor used for projOnTrack overshoots the pad when the
1190 // teardrop axis (vecVia) is not radial: e.g. a two-segment teardrop where the first
1191 // segment grazes the pad tangentially produces a projection close to zero, but the
1192 // floor still pushes pointD outward by padRadius along -vecVia, creating a spike.
1193 // Solve for the far intersection of the ray (intersection, -vecVia) with the pad
1194 // circle and use that as the upper bound.
1195 double R = static_cast<double>( padRadius );
1196 double cx = intToPad.x; // (aOtherPos - intersection)
1197 double cy = intToPad.y;
1198 double distCenterSq = cx * cx + cy * cy;
1199
1200 // Quadratic for ||intersection + (-vecVia)*t - aOtherPos||^2 = R^2
1201 // expands to t^2 - 2*projOnTrack*t + (distCenterSq - R^2) = 0.
1202 // The far root is projOnTrack + sqrt(projOnTrack^2 - (distCenterSq - R^2)).
1203 double disc = projOnTrack * projOnTrack - ( distCenterSq - R * R );
1204
1205 if( disc >= 0 )
1206 {
1207 double farEdge = projOnTrack + std::sqrt( disc );
1208 double maxAllowed = std::max( 0.0, farEdge - 2.0 * offset );
1209
1210 if( effectiveDist > maxAllowed )
1211 effectiveDist = maxAllowed;
1212 }
1213 }
1214
1215 VECTOR2I pointD = intersection + VECTOR2I( KiROUND( -vecVia.x * ( effectiveDist + offset ) ),
1216 KiROUND( -vecVia.y * ( effectiveDist + offset ) ) );
1217
1218 VECTOR2I pointC, pointE; // Point on pad/via outlines
1219
1220 // For two-segment teardrops, compute junction edge points where the track
1221 // changes direction, and use the first segment side of the junction for
1222 // the convex hull anchor so that C and E are oriented to the via entry axis.
1223 VECTOR2I junctionB_seg2, junctionB_seg1, junctionA_seg2, junctionA_seg1;
1224
1225 if( twoSegments )
1226 {
1227 junctionB_seg2 = start + VECTOR2I( KiROUND( vecT.y * track_halfwidth ),
1228 KiROUND( -vecT.x * track_halfwidth ) );
1229 junctionA_seg2 = start + VECTOR2I( KiROUND( -vecT.y * track_halfwidth ),
1230 KiROUND( vecT.x * track_halfwidth ) );
1231 junctionB_seg1 = start + VECTOR2I( KiROUND( vecVia.y * track_halfwidth ),
1232 KiROUND( -vecVia.x * track_halfwidth ) );
1233 junctionA_seg1 = start + VECTOR2I( KiROUND( -vecVia.y * track_halfwidth ),
1234 KiROUND( vecVia.x * track_halfwidth ) );
1235 }
1236
1237 // On the inside of a bend, the seg2 junction point backtracks relative to
1238 // seg1 and causes self-intersection. Detect with a dot product test and skip
1239 // the seg2 point on whichever side would backtrack.
1240 bool skipJunctionA = false;
1241 bool skipJunctionB = false;
1242
1243 if( twoSegments )
1244 {
1245 VECTOR2D transA = VECTOR2D( junctionA_seg2 - junctionA_seg1 );
1246 VECTOR2D anchorDirA = VECTOR2D( pointA - junctionA_seg1 );
1247 skipJunctionA = ( transA.x * anchorDirA.x + transA.y * anchorDirA.y ) < 0;
1248
1249 VECTOR2D transB = VECTOR2D( junctionB_seg2 - junctionB_seg1 );
1250 VECTOR2D anchorDirB = VECTOR2D( pointB - junctionB_seg1 );
1251 skipJunctionB = ( transB.x * anchorDirB.x + transB.y * anchorDirB.y ) < 0;
1252 }
1253
1254 VECTOR2I anchorA = twoSegments ? junctionA_seg1 : pointA;
1255 VECTOR2I anchorB = twoSegments ? junctionB_seg1 : pointB;
1256
1257 std::vector<VECTOR2I> pts = { anchorA, anchorB, pointC, pointD, pointE };
1258
1259 computeAnchorPoints( aParams, aTrack->GetLayer(), aOther, aOtherPos, pts );
1260
1261 // For off-center track connections, the convex hull produces asymmetric anchor points
1262 // (C and E at different distances from the track axis). Recompute them to be symmetric
1263 // so the teardrop flares out evenly from the track on both sides.
1264 if( IsRound( aOther, layer ) )
1265 {
1266 VECTOR2D perpVia( -vecVia.y, vecVia.x );
1267
1268 // Perpendicular distance from pad center to the track axis
1269 VECTOR2D padOffset = VECTOR2D( aOtherPos - intersection );
1270 double perpDistToCenter = padOffset.x * perpVia.x + padOffset.y * perpVia.y;
1271
1272 // Only apply the symmetric adjustment when the track is significantly off-center.
1273 if( std::abs( perpDistToCenter ) > padRadius * 0.1 )
1274 {
1275 double d = std::abs( perpDistToCenter );
1276
1277 if( d < padRadius )
1278 {
1279 // The maximum symmetric half-width is limited by the shorter side, which is
1280 // the distance from the track axis to the nearest circle edge (R - d).
1281 double maxSymmetric = static_cast<double>( padRadius ) - d;
1282
1283 // Apply the configured width ratio and max width constraints
1284 int preferred_width = KiROUND( GetWidth( aOther, layer ) * aParams.m_BestWidthRatio );
1285 int maxHalfWidth = preferred_width / 2;
1286
1287 if( aParams.m_TdMaxWidth > 0 )
1288 maxHalfWidth = std::min( maxHalfWidth, aParams.m_TdMaxWidth / 2 );
1289
1290 double symHalfWidth = std::min( maxSymmetric,
1291 static_cast<double>( maxHalfWidth ) );
1292
1293 if( symHalfWidth > track_halfwidth )
1294 {
1295 VECTOR2D center = VECTOR2D( aOtherPos );
1296 double R = static_cast<double>( padRadius );
1297
1298 // Find C on the circle at perpendicular distance +symHalfWidth from track.
1299 // Line: p = (perpFoot + perpVia*symHalfWidth) + t * vecVia
1300 // Intersect with circle (center, R) and pick the point closest to
1301 // the intersection point (track entry side).
1302 auto findCircleLineIntersection =
1303 [&]( double perpDist ) -> VECTOR2I
1304 {
1305 double projAlongTrack = padOffset.x * vecVia.x
1306 + padOffset.y * vecVia.y;
1307 VECTOR2D lineOrigin = VECTOR2D( intersection )
1308 + vecVia * projAlongTrack
1309 + perpVia * perpDist;
1310
1311 VECTOR2D oc = lineOrigin - center;
1312 double b_coeff = oc.x * vecVia.x + oc.y * vecVia.y;
1313 double c_coeff = oc.x * oc.x + oc.y * oc.y - R * R;
1314 double disc = b_coeff * b_coeff - c_coeff;
1315
1316 if( disc < 0 )
1317 return VECTOR2I( KiROUND( lineOrigin.x ),
1318 KiROUND( lineOrigin.y ) );
1319
1320 double sqrtDisc = std::sqrt( disc );
1321 double t1 = -b_coeff - sqrtDisc;
1322 double t2 = -b_coeff + sqrtDisc;
1323
1324 // Pick the point on the intersection side (closer to the track entry)
1325 VECTOR2D p1 = lineOrigin + vecVia * t1;
1326 VECTOR2D p2 = lineOrigin + vecVia * t2;
1327 VECTOR2D intPt = VECTOR2D( intersection );
1328
1329 if( ( p1 - intPt ).EuclideanNorm()
1330 < ( p2 - intPt ).EuclideanNorm() )
1331 {
1332 return VECTOR2I( KiROUND( p1.x ), KiROUND( p1.y ) );
1333 }
1334
1335 return VECTOR2I( KiROUND( p2.x ), KiROUND( p2.y ) );
1336 };
1337
1338 // pointA is offset in +perpVia from the track axis, pointB in -perpVia
1339 // (see the VECTOR2I pointA/pointB construction above). pts[2] is C,
1340 // which lies adjacent to pointB in the teardrop walk A->B->C->D->E->A,
1341 // so it must sit on pointB's (-perpVia) side. Likewise pts[4] is E,
1342 // adjacent to pointA on the +perpVia side. Assigning the opposite signs
1343 // folds the polygon into a bowtie and produces self-intersecting edges
1344 // whenever the track is off-center enough to trigger this branch.
1345 pts[2] = findCircleLineIntersection( -symHalfWidth );
1346 pts[4] = findCircleLineIntersection( symHalfWidth );
1347 }
1348 }
1349 }
1350 }
1351
1352 if( !aParams.m_CurvedEdges )
1353 {
1354 if( twoSegments )
1355 {
1356 aCorners.push_back( pointA );
1357 aCorners.push_back( pointB );
1358
1359 if( !skipJunctionB )
1360 aCorners.push_back( junctionB_seg2 );
1361
1362 aCorners.push_back( pts[1] ); // junctionB_seg1
1363 aCorners.push_back( pts[2] ); // C
1364 aCorners.push_back( pts[3] ); // D
1365 aCorners.push_back( pts[4] ); // E
1366 aCorners.push_back( pts[0] ); // junctionA_seg1
1367
1368 if( !skipJunctionA )
1369 aCorners.push_back( junctionA_seg2 );
1370 }
1371 else
1372 {
1373 aCorners = std::move( pts );
1374 }
1375
1376 return true;
1377 }
1378
1379 // See if we can use curved teardrop shape
1380 if( IsRound( aOther, layer ) )
1381 {
1382 if( twoSegments )
1383 {
1384 std::vector<VECTOR2I> curvePoly;
1385 computeCurvedForRoundShape( aParams, curvePoly, layer, track_halfwidth,
1386 vecVia, aOther, aOtherPos, pts );
1387
1388 aCorners.push_back( pointB );
1389
1390 if( !skipJunctionB )
1391 aCorners.push_back( junctionB_seg2 );
1392
1393 for( const VECTOR2I& pt : curvePoly )
1394 aCorners.push_back( pt );
1395
1396 if( !skipJunctionA )
1397 aCorners.push_back( junctionA_seg2 );
1398
1399 aCorners.push_back( pointA );
1400 }
1401 else
1402 {
1403 computeCurvedForRoundShape( aParams, aCorners, layer, track_halfwidth,
1404 vecT, aOther, aOtherPos, pts );
1405 }
1406 }
1407 else
1408 {
1409 int td_width = KiROUND( GetWidth( aOther, layer ) * aParams.m_BestWidthRatio );
1410
1411 if( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < td_width )
1412 td_width = aParams.m_TdMaxWidth;
1413
1414 if( twoSegments )
1415 {
1416 std::vector<VECTOR2I> curvePoly;
1417 computeCurvedForRectShape( aParams, curvePoly, td_width, track_halfwidth, pts,
1418 intersection, aOther, aOtherPos, layer );
1419
1420 aCorners.push_back( pointB );
1421
1422 if( !skipJunctionB )
1423 aCorners.push_back( junctionB_seg2 );
1424
1425 for( const VECTOR2I& pt : curvePoly )
1426 aCorners.push_back( pt );
1427
1428 if( !skipJunctionA )
1429 aCorners.push_back( junctionA_seg2 );
1430
1431 aCorners.push_back( pointA );
1432 }
1433 else
1434 {
1435 computeCurvedForRectShape( aParams, aCorners, td_width, track_halfwidth, pts,
1436 intersection, aOther, aOtherPos, layer );
1437 }
1438 }
1439
1440 return true;
1441}
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
virtual VECTOR2I GetPosition() const
Definition eda_item.h:282
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
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:243
Definition pad.h:61
const VECTOR2I & GetMid() const
Definition pcb_track.h:286
virtual double GetLength() const
Get the length of the track using the hypotenuse calculation.
const VECTOR2I & GetStart() const
Definition pcb_track.h:93
const VECTOR2I & GetEnd() const
Definition pcb_track.h:90
virtual int GetWidth() const
Definition pcb_track.h:87
Definition seg.h:38
int Length() const
Return the length (this).
Definition seg.h:339
const SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError=DefaultAccuracyForPCB(), int *aActualError=nullptr) const
Construct a SHAPE_LINE_CHAIN of segments from a given arc.
void Reverse()
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.
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 BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int OutlineCount() const
Return the number of outlines in the set.
void Move(const VECTOR2I &aVector) override
BOARD * m_board
Definition teardrop.h:275
static bool IsRound(BOARD_ITEM *aItem, PCB_LAYER_ID aLayer)
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.
static int GetWidth(BOARD_ITEM *aItem, PCB_LAYER_ID aLayer)
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.
void computeCurvedForRectShape(const TEARDROP_PARAMETERS &aParams, std::vector< VECTOR2I > &aPoly, int aTdWidth, int aTrackHalfWidth, std::vector< VECTOR2I > &aPts, const VECTOR2I &aIntersection, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos, PCB_LAYER_ID aLayer) const
Compute the curve part points for teardrops connected to a rectangular/polygonal shape The Bezier cur...
void computeCurvedForRoundShape(const TEARDROP_PARAMETERS &aParams, std::vector< VECTOR2I > &aPoly, PCB_LAYER_ID aLayer, 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...
PCB_TRACK * findTouchingTrack(EDA_ITEM_FLAGS &aMatchType, PCB_TRACK *aTrackRef, const VECTOR2I &aEndPoint) const
Find a track connected to the end of another track.
TRACK_BUFFER m_trackLookupList
Definition teardrop.h:280
bool areItemsInSameZone(BOARD_ITEM *aPadOrVia, PCB_TRACK *aTrack) const
DRC_RTREE m_tracksRTree
Definition teardrop.h:279
friend class TEARDROP_PARAMETERS
Definition teardrop.h:91
bool findAnchorPointsOnTrack(const TEARDROP_PARAMETERS &aParams, VECTOR2I &aStartPoint, VECTOR2I &aEndPoint, VECTOR2I &aIntersection, PCB_TRACK *&aTrack, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos, int *aEffectiveTeardropLen) const
int computeEmergingTrackLength(PCB_TRACK *aTrack, BOARD_ITEM *aOther, PCB_LAYER_ID aLayer) const
Return the length of the portion of aTrack that lies outside aOther's copper shape on aLayer.
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.
bool m_CurvedEdges
True if the teardrop should be curved.
int idxFromLayNet(int aLayer, int aNetcode) const
Definition teardrop.h:65
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:71
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:279
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:381
Handle a list of polygons defining a copper zone.
Definition zone.h:70
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.
std::uint32_t EDA_ITEM_FLAGS
#define STARTPOINT
When a line is selected, these flags indicate which.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
Definition padstack.h:52
@ ROUNDRECT
Definition padstack.h:57
CITER next(CITER it)
Definition ptree.cpp:120
Represent an intersection between two line segments.
static bool isPointOnRoundedCorner(const VECTOR2I &aPoint, const VECTOR2I &aPadPos, const VECTOR2I &aPadSize, int aCornerRadius, const EDA_ANGLE &aRotation, VECTOR2I &aCornerCenter)
Check if a point is within a rounded corner region of a rounded rectangle pad.
static bool isPointOnOvalEnd(const VECTOR2I &aPoint, const VECTOR2I &aPadPos, const VECTOR2I &aPadSize, const EDA_ANGLE &aRotation, VECTOR2I &aArcCenter)
Check if a point is on the curved (semicircular) end of an oval pad.
static VECTOR2D NormalizeVector(const VECTOR2I &aVector)
static VECTOR2I computeCornerTangentControlPoint(const VECTOR2I &aAnchor, const VECTOR2I &aCornerCenter, double aBias, const VECTOR2I &aDesiredDir)
Helper to compute a control point for a teardrop anchor on a rounded rectangle corner.
VECTOR2I center
int radius
VECTOR2I end
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:225
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:90
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:91
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:89
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682
@ NONE
Pads are not covered.
Definition zones.h:45