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