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