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 <core/kicad_algo.h>
31#include <footprint.h>
32#include <netinfo.h>
33#include <pcb_track.h>
34#include <pad.h>
35#include <zone.h>
36#include <zone_filler.h>
37#include <board_commit.h>
39#include <drc/drc_engine.h>
40#include <drc/drc_rtree.h>
41#include <trigo.h>
42
43#include "teardrop.h"
47#include <bezier_curves.h>
48
49#include <wx/log.h>
50
51
52void TRACK_BUFFER::AddTrack( PCB_TRACK* aTrack, int aLayer, int aNetcode )
53{
54 m_map_tracks[idxFromLayNet( aLayer, aNetcode )].push_back( aTrack );
55}
56
57
59{
60 if( aItem->Type() == PCB_VIA_T )
61 {
62 PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
63 return via->GetWidth( aLayer );
64 }
65 else if( aItem->Type() == PCB_PAD_T )
66 {
67 PAD* pad = static_cast<PAD*>( aItem );
68 return std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y );
69 }
70 else if( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_ARC_T )
71 {
72 PCB_TRACK* track = static_cast<PCB_TRACK*>( aItem );
73 return track->GetWidth();
74 }
75
76 return 0;
77}
78
79
81{
82 if( aItem->Type() == PCB_VIA_T )
83 return true;
84
85 if( aItem->Type() == PCB_PAD_T )
86 {
87 PAD* pad = static_cast<PAD*>( aItem );
88 VECTOR2I size = pad->GetSize( aLayer );
89
90 return pad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE
91 || ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL && size.x == size.y );
92 }
93
94 return true;
95}
96
97
99{
100 if( aItem->Type() == PCB_VIA_T )
101 return true;
102
103 bool nonRound = false;
104
105 if( aItem->Type() == PCB_PAD_T )
106 {
107 static_cast<PAD*>( aItem )->Padstack().ForEachUniqueLayer(
108 [&]( PCB_LAYER_ID aLayer )
109 {
110 if( !TEARDROP_MANAGER::IsRound( aItem, aLayer ) )
111 nonRound = true;
112 } );
113 }
114
115 return !nonRound;
116}
117
118
120{
121 for( PCB_TRACK* track : m_board->Tracks() )
122 {
123 if( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T )
124 {
125 m_tracksRTree.Insert( track, track->GetLayer(), CLEARANCE_CONSTRAINT );
126 m_trackLookupList.AddTrack( track, track->GetLayer(), track->GetNetCode() );
127 }
128 }
129
130 m_tracksRTree.Build();
131}
132
133
135{
136 if( m_copperIndexed )
137 return;
138
139 m_copperIndexed = true;
140
141 auto indexCopper =
142 [&]( BOARD_ITEM* aItem )
143 {
144 // The filler's priority rules knock a different-net pour back around a teardrop,
145 // but nothing keeps two teardrops out of the same gap.
146 if( aItem->Type() == PCB_GROUP_T )
147 return;
148
149 if( aItem->Type() == PCB_ZONE_T && !static_cast<ZONE*>( aItem )->IsTeardropArea() )
150 return;
151
152 for( PCB_LAYER_ID layer : aItem->GetLayerSet().CuStack() )
153 m_copperRTree.Insert( aItem, layer, CLEARANCE_CONSTRAINT );
154 };
155
156 for( PCB_TRACK* track : m_board->Tracks() )
157 indexCopper( track );
158
159 for( ZONE* zone : m_board->Zones() )
160 indexCopper( zone );
161
162 // Graphics, text and dimensions all plot as copper when they sit on a copper layer.
163 for( BOARD_ITEM* drawing : m_board->Drawings() )
164 {
165 indexCopper( drawing );
166 drawing->RunOnChildren( indexCopper, RECURSE_MODE::RECURSE );
167 }
168
169 for( FOOTPRINT* footprint : m_board->Footprints() )
170 footprint->RunOnChildren( indexCopper, RECURSE_MODE::RECURSE );
171
172 m_copperRTree.Build();
173}
174
175
177{
178 if( const BOARD_CONNECTED_ITEM* connected = dynamic_cast<const BOARD_CONNECTED_ITEM*>( aItem ) )
179 return connected->GetNetCode();
180
182}
183
184
185int TEARDROP_MANAGER::pairClearance( PCB_TRACK* aSourceTrack, BOARD_ITEM* aItem, PCB_LAYER_ID aLayer ) const
186{
187 PTR_PTR_LAYER_CACHE_KEY key = { aSourceTrack, aItem, aLayer };
188
189 if( auto it = m_pairClearanceCache.find( key ); it != m_pairClearanceCache.end() )
190 return it->second;
191
192 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
193 std::shared_ptr<DRC_ENGINE> drcEngine = bds.m_DRCEngine;
194 int clearance = 0;
195
196 if( drcEngine )
197 {
198 DRC_CONSTRAINT constraint = drcEngine->EvalRules( CLEARANCE_CONSTRAINT, aSourceTrack, aItem, aLayer );
199
200 if( constraint.Value().HasMin() )
201 clearance = constraint.Value().Min();
202
203 // DRC allows geometry exactly at the limit; rejecting it here would silently delete a
204 // teardrop the rules permit.
205 clearance = std::max( 0, clearance - bds.GetDRCEpsilon() );
206 }
207
208 m_pairClearanceCache.emplace( key, clearance );
209
210 return clearance;
211}
212
213
214const std::vector<std::set<const BOARD_ITEM*>>&
216{
217 PTR_LAYER_CACHE_KEY key = { aZone, aLayer };
218
219 if( auto it = m_zoneConnectionCache.find( key ); it != m_zoneConnectionCache.end() )
220 return it->second;
221
222 std::vector<std::set<const BOARD_ITEM*>> islands;
223
224 m_board->GetConnectivity()->GetZoneIslandConnections( aZone, aLayer, &islands );
225
226 return m_zoneConnectionCache.emplace( key, std::move( islands ) ).first->second;
227}
228
229
231{
232 PCB_LAYER_ID layer = aTrack->GetLayer();
233
234 for( ZONE* zone : m_board->Zones() )
235 {
236 // Skip teardrops
237 if( zone->IsTeardropArea() )
238 continue;
239
240 // Only consider zones on the same layer as the track
241 if( !zone->IsOnLayer( layer ) )
242 continue;
243
244 if( zone->GetNetCode() != aTrack->GetNetCode() )
245 continue;
246
247 // No fill on this layer means no copper here, so it joins nothing here.
248 if( !zone->HasFilledPolysForLayer( layer ) )
249 continue;
250
251 // One island has to reach both; a pad here and a track the pour reaches elsewhere
252 // are not connected by it.
253 for( const std::set<const BOARD_ITEM*>& island : zoneConnections( zone, layer ) )
254 {
255 if( island.count( aPadOrVia ) && island.count( aTrack ) )
256 return true;
257 }
258 }
259
260 return false;
261}
262
263
264bool TEARDROP_MANAGER::collidesWithOtherNets( const std::vector<VECTOR2I>& aPoints, PCB_TRACK* aSourceTrack,
265 const std::vector<const BOARD_ITEM*>& aExempt ) const
266{
267 if( aPoints.size() < 3 )
268 return false;
269
270 SHAPE_POLY_SET teardrop;
271 teardrop.NewOutline();
272
273 for( const VECTOR2I& pt : aPoints )
274 teardrop.Append( pt.x, pt.y );
275
276 // The shape can self-intersect on awkward entries, and the collision test triangulates.
277 teardrop.Simplify();
278
279 if( teardrop.OutlineCount() == 0 )
280 return false;
281
283
284 PCB_LAYER_ID layer = aSourceTrack->GetLayer();
285 int netcode = aSourceTrack->GetNetCode();
286
287 // Net 0 is no net at all, so geometry stands in for it: copper touching the anchor or the
288 // track is the same conductor, and anything carrying a net is foreign whatever it touches.
289 std::vector<std::shared_ptr<SHAPE>> anchorShapes;
290 std::map<const BOARD_ITEM*, bool> touchesAnchor;
291
292 if( netcode <= 0 )
293 {
294 for( const BOARD_ITEM* item : aExempt )
295 {
296 if( std::shared_ptr<SHAPE> shape = item->GetEffectiveShape( layer ) )
297 anchorShapes.push_back( shape );
298 }
299 }
300
301 auto touchesTeardropAnchor =
302 [&]( BOARD_ITEM* aItem ) -> bool
303 {
304 auto it = touchesAnchor.find( aItem );
305
306 if( it == touchesAnchor.end() )
307 {
308 std::shared_ptr<SHAPE> shape = aItem->GetEffectiveShape( layer );
309 bool touches = false;
310
311 if( shape )
312 {
313 touches = std::any_of( anchorShapes.begin(), anchorShapes.end(),
314 [&]( const std::shared_ptr<SHAPE>& aAnchor )
315 {
316 return aAnchor->Collide( shape.get() );
317 } );
318 }
319
320 it = touchesAnchor.emplace( aItem, touches ).first;
321 }
322
323 return it->second;
324 };
325
326 auto isSameConductor =
327 [&]( BOARD_ITEM* aItem ) -> bool
328 {
329 int itemNet = copperNetcode( aItem );
330
331 if( netcode > 0 )
332 return itemNet == netcode;
333
334 return itemNet <= 0 && touchesTeardropAnchor( aItem );
335 };
336
337 auto collides =
338 [&]( BOARD_ITEM* aItem, const SHAPE* aShape ) -> bool
339 {
340 if( alg::contains( aExempt, aItem ) || isSameConductor( aItem ) )
341 return false;
342
343 return teardrop.Collide( aShape, pairClearance( aSourceTrack, aItem, layer ) );
344 };
345
346 // The radius has to cover the widest clearance anything can demand, or an obstacle with a
347 // generous one of its own is never tested.
348 BOX2I box = teardrop.BBox();
349 box.Inflate( m_board->GetMaxClearanceValue() );
350
351 for( DRC_RTREE::ITEM_WITH_SHAPE* item : m_copperRTree.Overlapping( layer, box ).m_items )
352 {
353 if( collides( item->parent, item->shape ) )
354 return true;
355 }
356
357 auto it = m_createdTdRTree.find( layer );
358
359 if( it == m_createdTdRTree.end() )
360 return false;
361
362 const int bmin[2] = { box.GetX(), box.GetY() };
363 const int bmax[2] = { box.GetRight(), box.GetBottom() };
364 bool collision = false;
365
366 auto visit =
367 [&]( ZONE* aCreated ) -> bool
368 {
369 collision = collides( aCreated, aCreated->GetEffectiveShape( layer ).get() );
370 return !collision;
371 };
372
373 it->second.Search( bmin, bmax, visit );
374 return collision;
375}
376
377
379 std::vector<VECTOR2I>& aPoints,
380 PCB_TRACK* aTrack, PCB_TRACK* aSourceTrack,
381 BOARD_ITEM* aOther, const VECTOR2I& aOtherPos ) const
382{
383 // A teardrop overlaps its anchor and its track by construction. aTrack is a stub or a
384 // two-segment extension when it differs from aSourceTrack, so both have to be named.
385 const std::vector<const BOARD_ITEM*> exempt = { aOther, aSourceTrack, aTrack };
386
387 if( !computeTeardropPolygon( aParams, aPoints, aTrack, aSourceTrack, aOther, aOtherPos ) )
388 return false;
389
390 if( !collidesWithOtherNets( aPoints, aSourceTrack, exempt ) )
391 return true;
392
393 // Bisect between the requested width and the track width, the narrowest worth keeping.
394 PCB_LAYER_ID layer = aTrack->GetLayer();
395 int preferred = KiROUND( GetWidth( aOther, layer ) * aParams.m_BestWidthRatio );
396 int hi = aParams.m_TdMaxWidth > 0 ? std::min( aParams.m_TdMaxWidth, preferred )
397 : preferred;
398 int lo = aTrack->GetWidth();
399
400 // Nothing narrower than the track is kept, so a track at or over the bound leaves nothing
401 // that fits. Only the pad and via paths reject this before they get here.
402 if( lo >= hi )
403 return false;
404
405 TEARDROP_PARAMETERS params = aParams;
406 std::vector<VECTOR2I> fitted;
407
408 auto tryWidth =
409 [&]( int aWidth ) -> bool
410 {
411 std::vector<VECTOR2I> candidate;
412
413 params.m_TdMaxWidth = aWidth;
414
415 if( !computeTeardropPolygon( params, candidate, aTrack, aSourceTrack, aOther, aOtherPos )
416 || collidesWithOtherNets( candidate, aSourceTrack, exempt ) )
417 {
418 return false;
419 }
420
421 fitted = std::move( candidate );
422 return true;
423 };
424
425 // Midpoints alone never probe the bottom of the range, losing a teardrop that only fits
426 // close to the track width.
427 if( !tryWidth( lo ) )
428 return false;
429
430 for( int ii = 0; ii < 4 && hi - lo > 1; ++ii )
431 {
432 int mid = lo + ( hi - lo ) / 2;
433
434 if( tryWidth( mid ) )
435 lo = mid;
436 else
437 hi = mid;
438 }
439
440 aPoints = std::move( fitted );
441
442 return !aPoints.empty();
443}
444
445
447 const VECTOR2I& aInsidePoint ) const
448{
449 // Arcs are genuine entries, not the short straight grazes this filter targets.
450 if( aTrack->Type() == PCB_ARC_T )
451 return std::numeric_limits<int>::max();
452
453 VECTOR2D delta( aTrack->GetEnd() - aTrack->GetStart() );
454 double len = delta.EuclideanNorm();
455
456 if( len == 0.0 )
457 return std::numeric_limits<int>::max();
458
459 int maxError = m_board->GetDesignSettings().m_MaxError;
460 int radius = GetWidth( aOther, aLayer ) / 2;
461 SHAPE_POLY_SET shapebuffer;
462
463 if( IsRound( aOther, aLayer ) )
464 {
465 TransformCircleToPolygon( shapebuffer, aOther->GetPosition(), radius, maxError, ERROR_INSIDE, 16 );
466 }
467 else
468 {
469 wxCHECK_MSG( aOther->Type() == PCB_PAD_T, 0, wxT( "Expected non-round item to be PAD" ) );
470 static_cast<PAD*>( aOther )->TransformShapeToPolygon( shapebuffer, aLayer, 0, maxError, ERROR_INSIDE );
471 }
472
473 // Measure the chord on the extended centerline, not the short track segment.
474 // The bbox-diagonal reach spans rotated elongated pads.
475 VECTOR2D dir = delta / len;
476 VECTOR2I mid = ( aTrack->GetStart() + aTrack->GetEnd() ) / 2;
477 int reach = KiROUND( shapebuffer.BBox().Diagonal() + len );
478 VECTOR2I extStart = mid - VECTOR2I( KiROUND( dir.x * reach ), KiROUND( dir.y * reach ) );
479 VECTOR2I extEnd = mid + VECTOR2I( KiROUND( dir.x * reach ), KiROUND( dir.y * reach ) );
480
481 // Include every contour and hole in the boundary crossings.
482 std::vector<SHAPE_LINE_CHAIN::INTERSECTION> pts;
483
484 for( int ii = 0; ii < shapebuffer.OutlineCount(); ++ii )
485 {
486 SHAPE_LINE_CHAIN& outline = shapebuffer.Outline( ii );
487 outline.SetClosed( true );
488 outline.Intersect( SEG( extStart, extEnd ), pts );
489
490 for( int jj = 0; jj < shapebuffer.HoleCount( ii ); ++jj )
491 {
492 SHAPE_LINE_CHAIN& hole = shapebuffer.Hole( ii, jj );
493 hole.SetClosed( true );
494 hole.Intersect( SEG( extStart, extEnd ), pts );
495 }
496 }
497
498 // Degenerate/tangent-only crossings should not drop the teardrop.
499 if( pts.size() < 2 )
500 return std::numeric_limits<int>::max();
501
502 // Adjacent projected crossings bound copper/air spans.
503 // Use the copper span bracketing the inside endpoint.
504 std::vector<double> proj;
505 proj.reserve( pts.size() );
506
507 for( const SHAPE_LINE_CHAIN::INTERSECTION& hit : pts )
508 proj.push_back( ( hit.p - extStart ).Dot( dir ) );
509
510 std::sort( proj.begin(), proj.end() );
511
512 double insideProj = ( VECTOR2D( aInsidePoint ) - VECTOR2D( extStart ) ).Dot( dir );
513
514 for( size_t ii = 0; ii + 1 < proj.size(); ++ii )
515 {
516 VECTOR2I spanMid = extStart + VECTOR2I( KiROUND( dir.x * ( proj[ii] + proj[ii + 1] ) / 2 ),
517 KiROUND( dir.y * ( proj[ii] + proj[ii + 1] ) / 2 ) );
518
519 if( !shapebuffer.Contains( spanMid ) )
520 continue;
521
522 if( insideProj >= proj[ii] && insideProj <= proj[ii + 1] )
523 return KiROUND( proj[ii + 1] - proj[ii] );
524 }
525
526 // Boundary-touch fallback: keep the teardrop.
527 return std::numeric_limits<int>::max();
528}
529
530
532 PCB_TRACK* aSourceTrack, const VECTOR2I& aEndPoint ) const
533{
534 int matches = 0; // Count of candidates: only 1 is acceptable
535 PCB_TRACK* candidate = nullptr; // a reference to the track connected
536
537 m_tracksRTree.QueryColliding( aTrackRef, aTrackRef->GetLayer(), aTrackRef->GetLayer(),
538 // Filter:
539 [&]( BOARD_ITEM* trackItem ) -> bool
540 {
541 // A stub shares an endpoint with the track it stands in for, and continuing
542 // back onto that is not a continuation.
543 return trackItem != aTrackRef && trackItem != aSourceTrack;
544 },
545 // Visitor
546 [&]( BOARD_ITEM* trackItem ) -> bool
547 {
548 PCB_TRACK* curr_track = static_cast<PCB_TRACK*>( trackItem );
549
550 // IsPointOnEnds() returns 0, EDA_ITEM_FLAGS::STARTPOINT or EDA_ITEM_FLAGS::ENDPOINT
551 if( EDA_ITEM_FLAGS match = curr_track->IsPointOnEnds( aEndPoint, m_tolerance ) )
552 {
553 // if faced with a Y junction, choose the track longest segment as candidate
554 matches++;
555
556 if( matches > 1 )
557 {
558 double previous_len = candidate->GetLength();
559 double curr_len = curr_track->GetLength();
560
561 if( previous_len >= curr_len )
562 return true;
563 }
564
565 aMatchType = match;
566 candidate = curr_track;
567 }
568
569 return true;
570 },
571 0 );
572
573 return candidate;
574}
575
576
580static VECTOR2D NormalizeVector( const VECTOR2I& aVector )
581{
582 VECTOR2D vect( aVector );
583 double norm = vect.EuclideanNorm();
584 return vect / norm;
585}
586
587
588/*
589 * Compute the curve part points for teardrops connected to a round shape
590 * The Bezier curve control points are optimized for a round pad/via shape,
591 * and do not give a good curve shape for other pad shapes.
592 *
593 * For large circles where the teardrop width is constrained, the anchor points
594 * are projected onto the circle edge to ensure proper tangent calculation.
595 */
596void TEARDROP_MANAGER::computeCurvedForRoundShape( const TEARDROP_PARAMETERS& aParams, std::vector<VECTOR2I>& aPoly,
597 PCB_LAYER_ID aLayer, int aTrackHalfWidth,
598 const VECTOR2D& aTrackDir, BOARD_ITEM* aOther,
599 const VECTOR2I& aOtherPos, std::vector<VECTOR2I>& pts ) const
600{
601 int maxError = m_board->GetDesignSettings().m_MaxError;
602
603 // in pts:
604 // A and B are points on the track ( pts[0] and pts[1] )
605 // C and E are points on the aViaPad ( pts[2] and pts[4] )
606 // D is the aViaPad centre ( pts[3] )
607 double Vpercent = aParams.m_BestWidthRatio;
608 int td_height = KiROUND( GetWidth( aOther, aLayer ) * Vpercent );
609
610 // First, calculate a aVpercent equivalent to the td_height clamped by aTdMaxHeight
611 // We cannot use the initial aVpercent because it gives bad shape with points
612 // on aViaPad calculated for a clamped aViaPad size
613 if( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < td_height )
614 Vpercent *= (double) aParams.m_TdMaxWidth / td_height;
615
616 int radius = GetWidth( aOther, aLayer ) / 2;
617
618 // Don't divide by zero. No good can come of that.
619 wxCHECK2( radius != 0, radius = 1 );
620
621 double minVpercent = double( aTrackHalfWidth ) / radius;
622 double weaken = (Vpercent - minVpercent) / ( 1 - minVpercent ) / radius;
623
624 // For large circles where teardrop width is constrained, the anchor points from the
625 // convex hull may not be exactly on the circle. Project them onto the circle edge
626 // to ensure proper tangent calculation for smooth curves.
627 VECTOR2I vecC = pts[2] - aOtherPos;
628 double distC = vecC.EuclideanNorm();
629
630 if( distC > 0 && std::abs( distC - radius ) > maxError )
631 {
632 // Point is not on the circle - project it to the circle edge
633 pts[2] = aOtherPos + vecC.Resize( radius );
634 vecC = pts[2] - aOtherPos;
635 }
636
637 VECTOR2I vecE = pts[4] - aOtherPos;
638 double distE = vecE.EuclideanNorm();
639
640 if( distE > 0 && std::abs( distE - radius ) > maxError )
641 {
642 // Point is not on the circle - project it to the circle edge
643 pts[4] = aOtherPos + vecE.Resize( radius );
644 vecE = pts[4] - aOtherPos;
645 }
646
647 double biasBC = 0.5 * SEG( pts[1], pts[2] ).Length();
648 double biasAE = 0.5 * SEG( pts[4], pts[0] ).Length();
649
650 VECTOR2I tangentC = VECTOR2I( pts[2].x - vecC.y * biasBC * weaken, pts[2].y + vecC.x * biasBC * weaken );
651 VECTOR2I tangentE = VECTOR2I( pts[4].x + vecE.y * biasAE * weaken, pts[4].y - vecE.x * biasAE * weaken );
652
653 VECTOR2I tangentB = VECTOR2I( pts[1].x - aTrackDir.x * biasBC, pts[1].y - aTrackDir.y * biasBC );
654 VECTOR2I tangentA = VECTOR2I( pts[0].x - aTrackDir.x * biasAE, pts[0].y - aTrackDir.y * biasAE );
655
656 std::vector<VECTOR2I> curve_pts;
657 BEZIER_POLY( pts[1], tangentB, tangentC, pts[2] ).GetPoly( curve_pts, maxError );
658
659 for( VECTOR2I& corner: curve_pts )
660 aPoly.push_back( corner );
661
662 aPoly.push_back( pts[3] );
663
664 curve_pts.clear();
665 BEZIER_POLY( pts[4], tangentE, tangentA, pts[0] ).GetPoly( curve_pts, maxError );
666
667 for( VECTOR2I& corner: curve_pts )
668 aPoly.push_back( corner );
669}
670
671
683static VECTOR2I computeCornerTangentControlPoint( const VECTOR2I& aAnchor, const VECTOR2I& aCornerCenter,
684 double aBias, const VECTOR2I& aDesiredDir )
685{
686 VECTOR2I radial = aAnchor - aCornerCenter;
687
688 if( radial.EuclideanNorm() == 0 )
689 return aAnchor;
690
691 // Tangent is perpendicular to the radius. There are two perpendicular directions:
692 // (radial.y, -radial.x) and (-radial.y, radial.x)
693 // Choose the one that best aligns with the desired direction (toward the track)
694 VECTOR2I tangent1( radial.y, -radial.x );
695 VECTOR2I tangent2( -radial.y, radial.x );
696
697 // Use dot product to determine which tangent direction aligns better with desired direction
698 int64_t dot1 = static_cast<int64_t>( tangent1.x ) * aDesiredDir.x
699 + static_cast<int64_t>( tangent1.y ) * aDesiredDir.y;
700 int64_t dot2 = static_cast<int64_t>( tangent2.x ) * aDesiredDir.x
701 + static_cast<int64_t>( tangent2.y ) * aDesiredDir.y;
702
703 VECTOR2I tangent = ( dot1 > dot2 ) ? tangent1 : tangent2;
704
705 return aAnchor + tangent.Resize( KiROUND( aBias ) );
706}
707
708
720static bool isPointOnOvalEnd( const VECTOR2I& aPoint, const VECTOR2I& aPadPos, const VECTOR2I& aPadSize,
721 const EDA_ANGLE& aRotation, VECTOR2I& aArcCenter )
722{
723 // Transform point to pad-local coordinates (unrotated)
724 VECTOR2I localPt = aPoint - aPadPos;
725 RotatePoint( localPt, aRotation );
726
727 int halfW = aPadSize.x / 2;
728 int halfH = aPadSize.y / 2;
729
730 // Oval geometry: semicircle radius is min dimension / 2
731 // The semicircle centers are offset along the major axis
732 int radius = std::min( halfW, halfH );
733 bool isHorizontal = halfW > halfH;
734
735 if( isHorizontal )
736 {
737 // Semicircles at left and right ends
738 int centerOffset = halfW - radius;
739
740 // Check if point is in the curved region (beyond the straight sides)
741 if( std::abs( localPt.x ) <= centerOffset )
742 return false;
743
744 // Determine which end
745 int centerX = ( localPt.x > 0 ) ? centerOffset : -centerOffset;
746 aArcCenter = VECTOR2I( centerX, 0 );
747 }
748 else
749 {
750 // Semicircles at top and bottom ends
751 int centerOffset = halfH - radius;
752
753 // Check if point is in the curved region (beyond the straight sides)
754 if( std::abs( localPt.y ) <= centerOffset )
755 return false;
756
757 // Determine which end
758 int centerY = ( localPt.y > 0 ) ? centerOffset : -centerOffset;
759 aArcCenter = VECTOR2I( 0, centerY );
760 }
761
762 // Transform arc center back to board coordinates
763 RotatePoint( aArcCenter, -aRotation );
764 aArcCenter += aPadPos;
765
766 return true;
767}
768
769
782static bool isPointOnRoundedCorner( const VECTOR2I& aPoint, const VECTOR2I& aPadPos, const VECTOR2I& aPadSize,
783 int aCornerRadius, const EDA_ANGLE& aRotation, VECTOR2I& aCornerCenter )
784{
785 // Transform point to pad-local coordinates (unrotated)
786 VECTOR2I localPt = aPoint - aPadPos;
787 RotatePoint( localPt, aRotation );
788
789 // Half-sizes minus corner radius define the inner rectangle
790 int halfW = aPadSize.x / 2;
791 int halfH = aPadSize.y / 2;
792 int innerHalfW = halfW - aCornerRadius;
793 int innerHalfH = halfH - aCornerRadius;
794
795 // Point is in corner region if it's outside the inner rectangle in both dimensions
796 bool inCornerX = std::abs( localPt.x ) > innerHalfW;
797 bool inCornerY = std::abs( localPt.y ) > innerHalfH;
798
799 if( !inCornerX || !inCornerY )
800 return false;
801
802 // Determine which corner
803 int cornerX = ( localPt.x > 0 ) ? innerHalfW : -innerHalfW;
804 int cornerY = ( localPt.y > 0 ) ? innerHalfH : -innerHalfH;
805
806 aCornerCenter = VECTOR2I( cornerX, cornerY );
807
808 // Transform corner center back to board coordinates
809 RotatePoint( aCornerCenter, -aRotation );
810 aCornerCenter += aPadPos;
811
812 return true;
813}
814
815
816/*
817 * Compute the curve part points for teardrops connected to a rectangular/polygonal shape.
818 * For rounded rectangles, control points are computed to be tangent to corner arcs,
819 * preventing the teardrop curve from intersecting the pad's corner radius.
820 */
821void TEARDROP_MANAGER::computeCurvedForRectShape( const TEARDROP_PARAMETERS& aParams, std::vector<VECTOR2I>& aPoly,
822 int aTdWidth, int aTrackHalfWidth, std::vector<VECTOR2I>& aPts,
823 const VECTOR2I& aIntersection, BOARD_ITEM* aOther,
824 const VECTOR2I& aOtherPos, PCB_LAYER_ID aLayer ) const
825{
826 int maxError = m_board->GetDesignSettings().m_MaxError;
827
828 // in aPts:
829 // A and B are points on the track ( pts[0] and pts[1] )
830 // C and E are points on the pad/via ( pts[2] and pts[4] )
831 // D is the aViaPad centre ( pts[3] )
832
833 // side1 is( aPts[1], aPts[2] ); from track to via
834 VECTOR2I side1( aPts[2] - aPts[1] ); // vector from track to via
835 // side2 is ( aPts[4], aPts[0] ); from via to track
836 VECTOR2I side2( aPts[4] - aPts[0] ); // vector from track to via
837
838 VECTOR2I trackDir( aIntersection - ( aPts[0] + aPts[1] ) / 2 );
839
840 // Check if this is a rounded rectangle or oval pad (both have curved regions)
841 bool isRoundRect = false;
842 bool isOval = false;
843 int cornerRadius = 0;
844 VECTOR2I padSize;
845 EDA_ANGLE padRotation;
846
847 if( aOther && aOther->Type() == PCB_PAD_T )
848 {
849 PAD* pad = static_cast<PAD*>( aOther );
850 PAD_SHAPE shape = pad->GetShape( aLayer );
851
852 if( shape == PAD_SHAPE::ROUNDRECT )
853 {
854 isRoundRect = true;
855 cornerRadius = pad->GetRoundRectCornerRadius( aLayer );
856 padSize = pad->GetSize( aLayer );
857 padRotation = pad->GetOrientation();
858 }
859 else if( shape == PAD_SHAPE::OVAL )
860 {
861 isOval = true;
862 padSize = pad->GetSize( aLayer );
863 padRotation = pad->GetOrientation();
864 }
865 }
866
867 std::vector<VECTOR2I> curve_pts;
868
869 // Compute control points for the first Bezier curve (track point B to pad point C)
870 VECTOR2I ctrl1 = aPts[1] + trackDir.Resize( side1.EuclideanNorm() / 4 );
871 VECTOR2I ctrl2;
872
873 // Direction from pad anchor toward track (opposite of trackDir which goes pad-ward)
874 VECTOR2I towardTrack = -trackDir;
875
876 // Default control point - midpoint approach
877 ctrl2 = ( aPts[2] + aIntersection ) / 2;
878
879 if( isRoundRect && cornerRadius > 0 )
880 {
881 VECTOR2I cornerCenter;
882
883 if( isPointOnRoundedCorner( aPts[2], aOtherPos, padSize, cornerRadius, padRotation, cornerCenter ) )
884 {
885 // Anchor is on a corner arc - use tangent-based control point
886 double bias = 0.5 * side1.EuclideanNorm();
887 ctrl2 = computeCornerTangentControlPoint( aPts[2], cornerCenter, bias, towardTrack );
888 }
889 }
890 else if( isOval )
891 {
892 VECTOR2I arcCenter;
893
894 if( isPointOnOvalEnd( aPts[2], aOtherPos, padSize, padRotation, arcCenter ) )
895 {
896 // Anchor is on a curved end - use tangent-based control point
897 double bias = 0.5 * side1.EuclideanNorm();
898 ctrl2 = computeCornerTangentControlPoint( aPts[2], arcCenter, bias, towardTrack );
899 }
900 }
901
902 BEZIER_POLY( aPts[1], ctrl1, ctrl2, aPts[2] ).GetPoly( curve_pts, maxError );
903
904 for( VECTOR2I& corner: curve_pts )
905 aPoly.push_back( corner );
906
907 aPoly.push_back( aPts[3] );
908
909 // Compute control points for second Bezier curve (pad point E to track point A)
910 curve_pts.clear();
911
912 // Default control point - midpoint approach
913 ctrl1 = ( aPts[4] + aIntersection ) / 2;
914
915 if( isRoundRect && cornerRadius > 0 )
916 {
917 VECTOR2I cornerCenter;
918
919 if( isPointOnRoundedCorner( aPts[4], aOtherPos, padSize, cornerRadius, padRotation, cornerCenter ) )
920 {
921 // Anchor is on a corner arc - use tangent-based control point
922 double bias = 0.5 * side2.EuclideanNorm();
923 ctrl1 = computeCornerTangentControlPoint( aPts[4], cornerCenter, bias, towardTrack );
924 }
925 }
926 else if( isOval )
927 {
928 VECTOR2I arcCenter;
929
930 if( isPointOnOvalEnd( aPts[4], aOtherPos, padSize, padRotation, arcCenter ) )
931 {
932 // Anchor is on a curved end - use tangent-based control point
933 double bias = 0.5 * side2.EuclideanNorm();
934 ctrl1 = computeCornerTangentControlPoint( aPts[4], arcCenter, bias, towardTrack );
935 }
936 }
937
938 ctrl2 = aPts[0] + trackDir.Resize( side2.EuclideanNorm() / 4 );
939
940 BEZIER_POLY( aPts[4], ctrl1, ctrl2, aPts[0] ).GetPoly( curve_pts, maxError );
941
942 for( VECTOR2I& corner: curve_pts )
943 aPoly.push_back( corner );
944}
945
946
948 BOARD_ITEM* aItem, const VECTOR2I& aPos,
949 std::vector<VECTOR2I>& aPts ) const
950{
951 int maxError = m_board->GetDesignSettings().m_MaxError;
952
953 // Compute the 2 anchor points on pad/via/track of the teardrop shape
954
955 SHAPE_POLY_SET c_buffer;
956
957 // m_BestWidthRatio is the factor to calculate the teardrop preferred width.
958 // teardrop width = pad, via or track size * m_BestWidthRatio (m_BestWidthRatio <= 1.0)
959 // For rectangular (and similar) shapes, the preferred_width is calculated from the min
960 // dim of the rectangle
961
962 int preferred_width = KiROUND( GetWidth( aItem, aLayer ) * aParams.m_BestWidthRatio );
963
964 // force_clip = true to force the pad/via/track polygon to be clipped to follow
965 // constraints
966 // Clipping is also needed for rectangular shapes, because the teardrop shape is restricted
967 // to a polygonal area smaller than the pad area (the teardrop height use the smaller value
968 // of X and Y sizes).
969 bool force_clip = aParams.m_BestWidthRatio < 1.0;
970
971 // To find the anchor points on the pad/via/track shape, we build the polygonal shape, and
972 // clip the polygon to the max size (preferred_width or m_TdMaxWidth) by a rectangle
973 // centered on the axis of the expected teardrop shape.
974 // (only reduce the size of polygonal shape does not give good anchor points)
975 if( IsRound( aItem, aLayer ) )
976 {
977 TransformCircleToPolygon( c_buffer, aPos, GetWidth( aItem, aLayer ) / 2, maxError, ERROR_INSIDE, 16 );
978 }
979 else // Only PADS can have a not round shape
980 {
981 wxCHECK_MSG( aItem->Type() == PCB_PAD_T, false, wxT( "Expected non-round item to be PAD" ) );
982 PAD* pad = static_cast<PAD*>( aItem );
983
984 force_clip = true;
985
986 preferred_width = KiROUND( GetWidth( pad, aLayer ) * aParams.m_BestWidthRatio );
987 pad->TransformShapeToPolygon( c_buffer, aLayer, 0, maxError, ERROR_INSIDE );
988 }
989
990 // Clip the pad/via/track shape to match the m_TdMaxWidth constraint, and for non-round pads,
991 // clip the shape to the smallest of size.x and size.y values.
992 if( force_clip || ( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < preferred_width ) )
993 {
994 // A max width of 0 means no limit, so a min against it would clip the shape to nothing.
995 int halfsize = ( aParams.m_TdMaxWidth > 0 ? std::min( aParams.m_TdMaxWidth, preferred_width )
996 : preferred_width ) / 2;
997
998 // teardrop_axis is the line from anchor point on the track and the end point
999 // of the teardrop in the pad/via
1000 // this is the teardrop_axis of the teardrop shape to build
1001 VECTOR2I ref_on_track = ( aPts[0] + aPts[1] ) / 2;
1002 VECTOR2I teardrop_axis( aPts[3] - ref_on_track );
1003
1004 EDA_ANGLE orient( teardrop_axis );
1005 int len = teardrop_axis.EuclideanNorm();
1006
1007 // Build the constraint polygon: a rectangle with
1008 // length = dist between the point on track and the pad/via pos
1009 // height = m_TdMaxWidth or aViaPad.m_Width
1010 SHAPE_POLY_SET clipping_rect;
1011 clipping_rect.NewOutline();
1012
1013 // Build a horizontal rect: it will be rotated later
1014 clipping_rect.Append( 0, - halfsize );
1015 clipping_rect.Append( 0, halfsize );
1016 clipping_rect.Append( len, halfsize );
1017 clipping_rect.Append( len, - halfsize );
1018
1019 clipping_rect.Rotate( -orient );
1020 clipping_rect.Move( ref_on_track );
1021
1022 // Clip the shape to the max allowed teadrop area
1023 c_buffer.BooleanIntersection( clipping_rect );
1024 }
1025
1026 /* in aPts:
1027 * A and B are points on the track ( aPts[0] and aPts[1] )
1028 * C and E are points on the aViaPad ( aPts[2] and aPts[4] )
1029 * D is midpoint behind the aViaPad centre ( aPts[3] )
1030 */
1031
1032 if( c_buffer.OutlineCount() == 0 )
1033 return false;
1034
1035 SHAPE_LINE_CHAIN& padpoly = c_buffer.Outline(0);
1036 std::vector<VECTOR2I> points = padpoly.CPoints();
1037
1038 std::vector<VECTOR2I> initialPoints;
1039 initialPoints.push_back( aPts[0] );
1040 initialPoints.push_back( aPts[1] );
1041
1042 for( const VECTOR2I& pt: points )
1043 initialPoints.emplace_back( pt.x, pt.y );
1044
1045 std::vector<VECTOR2I> hull;
1046 BuildConvexHull( hull, initialPoints );
1047
1048 // Search for end points of segments starting at aPts[0] or aPts[1]
1049 // In some cases, in convex hull, only one point (aPts[0] or aPts[1]) is still in list
1050 VECTOR2I PointC;
1051 VECTOR2I PointE;
1052 int found_start = -1; // 2 points (one start and one end) should be found
1053 int found_end = -1;
1054
1055 VECTOR2I start = aPts[0];
1056 VECTOR2I pend = aPts[1];
1057
1058 for( unsigned ii = 0, jj = 0; jj < hull.size(); ii++, jj++ )
1059 {
1060 unsigned next = ii+ 1;
1061
1062 if( next >= hull.size() )
1063 next = 0;
1064
1065 int prev = ii -1;
1066
1067 if( prev < 0 )
1068 prev = hull.size()-1;
1069
1070 if( hull[ii] == start )
1071 {
1072 // the previous or the next point is candidate:
1073 if( hull[next] != pend )
1074 PointE = hull[next];
1075 else
1076 PointE = hull[prev];
1077
1078 found_start = ii;
1079 }
1080
1081 if( hull[ii] == pend )
1082 {
1083 if( hull[next] != start )
1084 PointC = hull[next];
1085 else
1086 PointC = hull[prev];
1087
1088 found_end = ii;
1089 }
1090 }
1091
1092 if( found_start < 0 ) // PointE was not initialized, because start point does not exit
1093 {
1094 int ii = found_end-1;
1095
1096 if( ii < 0 )
1097 ii = hull.size()-1;
1098
1099 PointE = hull[ii];
1100 }
1101
1102 if( found_end < 0 ) // PointC was not initialized, because end point does not exit
1103 {
1104 int ii = found_start-1;
1105
1106 if( ii < 0 )
1107 ii = hull.size()-1;
1108
1109 PointC = hull[ii];
1110 }
1111
1112 aPts[2] = PointC;
1113 aPts[4] = PointE;
1114
1115 // Now we have to know if the choice aPts[2] = PointC is the best, or if
1116 // aPts[2] = PointE is better.
1117 // A criteria is to calculate the polygon area in these 2 cases, and choose the case
1118 // that gives the bigger area, because the segments starting at PointC and PointE
1119 // maximize their distance.
1120 SHAPE_LINE_CHAIN dummy1( aPts, true );
1121 double area1 = dummy1.Area();
1122
1123 std::swap( aPts[2], aPts[4] );
1124 SHAPE_LINE_CHAIN dummy2( aPts, true );
1125 double area2 = dummy2.Area();
1126
1127 if( area1 > area2 ) // The first choice (without swapping) is the better.
1128 std::swap( aPts[2], aPts[4] );
1129
1130 return true;
1131}
1132
1133
1135 VECTOR2I& aStartPoint, VECTOR2I& aEndPoint,
1136 VECTOR2I& aIntersection, PCB_TRACK*& aTrack,
1137 PCB_TRACK* aSourceTrack, BOARD_ITEM* aOther,
1138 const VECTOR2I& aOtherPos,
1139 int* aEffectiveTeardropLen ) const
1140{
1141 bool found = true;
1142 VECTOR2I start = aTrack->GetStart(); // one reference point on the track, inside teardrop
1143 VECTOR2I end = aTrack->GetEnd(); // the second reference point on the track, outside teardrop
1144 PCB_LAYER_ID layer = aTrack->GetLayer();
1145 int radius = GetWidth( aOther, layer ) / 2;
1146 int maxError = m_board->GetDesignSettings().m_MaxError;
1147
1148 // Requested length of the teardrop:
1149 int targetLength = KiROUND( GetWidth( aOther, layer ) * aParams.m_BestLengthRatio );
1150
1151 if( aParams.m_TdMaxLen > 0 )
1152 targetLength = std::min( aParams.m_TdMaxLen, targetLength );
1153
1154 // actualTdLen is the distance between start and the teardrop point on the segment from start to end
1155 int actualTdLen;
1156 bool need_swap = false; // true if the start and end points of the current track are swapped
1157
1158 // aTrack is expected to have one end inside the via/pad and the other end outside
1159 // so ensure the start point is inside the via/pad
1160 if( !aOther->HitTest( start, 0 ) )
1161 {
1162 std::swap( start, end );
1163 need_swap = true;
1164 }
1165
1166 SHAPE_POLY_SET shapebuffer;
1167
1168 if( IsRound( aOther, layer ) )
1169 {
1170 TransformCircleToPolygon( shapebuffer, aOtherPos, radius, maxError, ERROR_INSIDE, 16 );
1171 }
1172 else
1173 {
1174 wxCHECK_MSG( aOther->Type() == PCB_PAD_T, false, wxT( "Expected non-round item to be PAD" ) );
1175 static_cast<PAD*>( aOther )->TransformShapeToPolygon( shapebuffer, aTrack->GetLayer(), 0,
1176 maxError, ERROR_INSIDE );
1177 }
1178
1179 SHAPE_LINE_CHAIN& outline = shapebuffer.Outline(0);
1180 outline.SetClosed( true );
1181
1182 // Search the intersection point between the pad/via shape and the current track
1183 // This this the starting point to define the teardrop length
1184 std::vector<SHAPE_LINE_CHAIN::INTERSECTION> pts;
1185 int pt_count;
1186
1187 if( aTrack->Type() == PCB_ARC_T )
1188 {
1189 // To find the starting point we convert the arc to a polyline
1190 // and compute the intersection point with the pad/via shape
1191 SHAPE_ARC arc( aTrack->GetStart(), static_cast<PCB_ARC*>( aTrack )->GetMid(), aTrack->GetEnd(),
1192 aTrack->GetWidth() );
1193
1194 SHAPE_LINE_CHAIN poly = arc.ConvertToPolyline( maxError );
1195 pt_count = outline.Intersect( poly, pts );
1196 }
1197 else
1198 {
1199 pt_count = outline.Intersect( SEG( start, end ), pts );
1200 }
1201
1202 // Ensure a intersection point was found, otherwise we cannot built the teardrop
1203 // using this track (it is fully outside or inside the pad/via shape)
1204 if( pt_count < 1 )
1205 return false;
1206
1207 aIntersection = pts[0].p;
1208 start = aIntersection; // This is currently the reference point of the teardrop length
1209
1210 // actualTdLen for now the distance between start and the teardrop point on the (start end)segment
1211 // It cannot be bigger than the lenght of this segment
1212 actualTdLen = std::min( targetLength, SEG( start, end ).Length() );
1213 VECTOR2I ref_lenght_point = start; // the reference point of actualTdLen
1214
1215 // If the first track is too short to allow a teardrop having the requested length
1216 // explore the connected track(s), and try to find a anchor point at targetLength from initial start
1217 if( actualTdLen < targetLength && aParams.m_AllowUseTwoTracks )
1218 {
1219 int consumed = 0;
1220
1221 while( actualTdLen + consumed < targetLength )
1222 {
1223 EDA_ITEM_FLAGS matchType;
1224
1225 PCB_TRACK* connected_track = findTouchingTrack( matchType, aTrack, aSourceTrack, end );
1226
1227 if( connected_track == nullptr )
1228 break;
1229
1230 // Reject the extension if the angle between segments is too large.
1231 // Large angles cause the teardrop shape to bend sharply at the junction.
1232 // The junction transition code handles bends up to ~60 degrees, so use
1233 // cos(60) = 0.5 as the threshold.
1234 constexpr double kMinCosForTwoSegmentExtension = 0.5;
1235
1236 VECTOR2D firstDir = NormalizeVector( end - ref_lenght_point );
1237 VECTOR2D secondDir;
1238
1239 if( matchType == STARTPOINT )
1240 secondDir = NormalizeVector( connected_track->GetEnd() - connected_track->GetStart() );
1241 else
1242 secondDir = NormalizeVector( connected_track->GetStart() - connected_track->GetEnd() );
1243
1244 double cosAngle = firstDir.x * secondDir.x + firstDir.y * secondDir.y;
1245
1246 if( cosAngle < kMinCosForTwoSegmentExtension )
1247 break;
1248
1249 consumed += actualTdLen;
1250 // actualTdLen is the new distance from new start point and the teardrop anchor point
1251 actualTdLen = std::min( targetLength-consumed, int( connected_track->GetLength() ) );
1252 aTrack = connected_track;
1253 end = connected_track->GetEnd();
1254 start = connected_track->GetStart();
1255 need_swap = false;
1256
1257 if( matchType != STARTPOINT )
1258 {
1259 std::swap( start, end );
1260 need_swap = true;
1261 }
1262
1263 // If we do not want to explore more than one connected track, stop search here
1264 break;
1265 }
1266 }
1267
1268 // if aTrack is an arc, find the best teardrop end point on the arc
1269 // It is currently on the segment from arc start point to arc end point,
1270 // therefore not really on the arc, because we have used only the track end points.
1271 if( aTrack->Type() == PCB_ARC_T )
1272 {
1273 // To find the best start and end points to build the teardrop shape, we convert
1274 // the arc to segments, and search for the segment having its start point at a dist
1275 // < actualTdLen, and its end point at adist > actualTdLen:
1276 SHAPE_ARC arc( aTrack->GetStart(), static_cast<PCB_ARC*>( aTrack )->GetMid(), aTrack->GetEnd(),
1277 aTrack->GetWidth() );
1278
1279 if( need_swap )
1280 arc.Reverse();
1281
1282 SHAPE_LINE_CHAIN poly = arc.ConvertToPolyline( maxError );
1283
1284 // The conversion places its corner points slightly outside the arc. Move them
1285 // onto the arc so they can be used as points on the track centerline.
1286 for( int ii = 0; ii < poly.PointCount(); ++ii )
1287 poly.SetPoint( ii, arc.NearestPoint( poly.CPoint( ii ) ) );
1288
1289 // Now, find the segment of the arc at a distance < actualTdLen from ref_lenght_point.
1290 // We just search for the first segment (starting from the farest segment) with its
1291 // start point at a distance < actualTdLen dist
1292 // This is basic, but it is probably enough.
1293 if( poly.PointCount() > 2 )
1294 {
1295 // Note: the first point is inside or near the pad/via shape
1296 // The last point is outside and the farest from the ref_lenght_point
1297 // So we explore segments from the last to the first
1298 for( int ii = poly.PointCount()-1; ii >= 0 ; ii-- )
1299 {
1300 int dist_from_start = ( poly.CPoint( ii ) - start ).EuclideanNorm();
1301
1302 // The first segment at a distance of the reference point < actualTdLen is OK
1303 // and is suitable to define the reference segment of the teardrop anchor.
1304 if( dist_from_start < actualTdLen || ii == 0 )
1305 {
1306 start = poly.CPoint( ii );
1307
1308 if( ii < poly.PointCount()-1 )
1309 end = poly.CPoint( ii+1 );
1310
1311 // actualTdLen is the distance between start (the reference segment start point)
1312 // and the point on track of the teardrop.
1313 // This is the difference between the initial actualTdLen value and the
1314 // distance between start and ref_lenght_point.
1315 actualTdLen -= (start - ref_lenght_point).EuclideanNorm();
1316
1317 // Ensure validity of actualTdLen: >= 0, and <= segment lenght
1318 if( actualTdLen < 0 ) // should not happen, but...
1319 actualTdLen = 0;
1320
1321 actualTdLen = std::min( actualTdLen, (end - start).EuclideanNorm() );
1322
1323 break;
1324 }
1325 }
1326 }
1327 }
1328
1329 // aStartPoint and aEndPoint will define later a segment to build the 2 anchors points
1330 // of the teardrop on the aTrack shape.
1331 // they are two points (both outside the pad/via shape) of aTrack if aTrack is a segment,
1332 // or a small segment on aTrack if aTrack is an ARC
1333 aStartPoint = start;
1334 aEndPoint = end;
1335
1336 *aEffectiveTeardropLen = actualTdLen;
1337 return found;
1338}
1339
1340
1342 std::vector<VECTOR2I>& aCorners, PCB_TRACK* aTrack,
1343 PCB_TRACK* aSourceTrack, BOARD_ITEM* aOther,
1344 const VECTOR2I& aOtherPos ) const
1345{
1346 VECTOR2I start, end; // Start and end points of the track anchor of the teardrop
1347 // the start point is inside the teardrop shape
1348 // the end point is outside.
1349 VECTOR2I intersection; // Where the track centerline intersects the pad/via edge
1350 int track_stub_len; // the dist between the start point and the anchor point
1351 // on the track
1352
1353 // Note: aTrack can be modified if the initial track is too short.
1354 // Save the original pointer so we can detect two-segment extension.
1355 PCB_TRACK* originalTrack = aTrack;
1356
1357 if( !findAnchorPointsOnTrack( aParams, start, end, intersection, aTrack, aSourceTrack, aOther,
1358 aOtherPos, &track_stub_len ) )
1359 return false;
1360
1361 // The start and end points must be different to calculate a valid polygon shape
1362 if( start == end )
1363 return false;
1364
1365 VECTOR2D vecT = NormalizeVector(end - start);
1366
1367 // When spanning two segments, findAnchorPointsOnTrack replaces aTrack with the
1368 // connected track. The start point becomes the junction between the two segments,
1369 // which differs from intersection (where the first segment meets the pad/via edge).
1370 // Use the first segment's direction for all via-side geometry so that the teardrop
1371 // shape is oriented correctly relative to how the track enters the pad/via.
1372 // Note: for arcs, start also moves away from intersection during arc refinement, but
1373 // aTrack remains the same pointer, so arc tracks are correctly excluded here.
1374 bool twoSegments = ( aTrack != originalTrack );
1375
1376 // vecVia is the direction the track enters the pad/via, used for via-side geometry.
1377 // When the first segment is so short that the junction coincides with the pad edge
1378 // intersection, start == intersection produces a zero vector whose normalization is
1379 // NaN. Fall back to the second segment's direction in that case.
1380 VECTOR2D vecVia = vecT;
1381
1382 if( twoSegments && start != intersection )
1383 vecVia = NormalizeVector( start - intersection );
1384
1385 // find the 2 points on the track, sharp end of the teardrop
1386 int track_halfwidth = aTrack->GetWidth() / 2;
1387
1388 // The canvas draws an arc track as short straight lines that can sit up to the max
1389 // deviation inside the true edge. Keep the corners inside the track by that amount
1390 // so they cannot show past the drawn edge.
1391 if( aTrack->Type() == PCB_ARC_T )
1392 {
1393 int maxError = m_board->GetDesignSettings().m_MaxError;
1394 track_halfwidth = std::max( aTrack->GetWidth() / 4, aTrack->GetWidth() / 2 - maxError );
1395 }
1396
1397 VECTOR2I pointB = start + VECTOR2I( vecT.x * track_stub_len + vecT.y * track_halfwidth,
1398 vecT.y * track_stub_len - vecT.x * track_halfwidth );
1399 VECTOR2I pointA = start + VECTOR2I( vecT.x * track_stub_len - vecT.y * track_halfwidth,
1400 vecT.y * track_stub_len + vecT.x * track_halfwidth );
1401
1402 PCB_LAYER_ID layer = aTrack->GetLayer();
1403
1404 // To build a polygonal valid shape pointA and point B must be outside the pad
1405 // It can be inside with some pad shapes having very different X and X sizes
1406 if( !IsRound( aOther, layer ) )
1407 {
1408 PAD* pad = static_cast<PAD*>( aOther );
1409
1410 if( pad->HitTest( pointA, 0, layer ) )
1411 return false;
1412
1413 if( pad->HitTest( pointB, 0, layer ) )
1414 return false;
1415 }
1416
1417 // Compute pointD, the "back" point of the teardrop behind the pad/via center.
1418 // For off-center track connections (where the track doesn't pass through the pad center),
1419 // we project the pad center onto the track axis so the teardrop is built symmetrically
1420 // about the track rather than being skewed toward the pad center.
1421 int padRadius = GetWidth( aOther, layer ) / 2;
1422 VECTOR2D intToPad = VECTOR2D( aOtherPos - intersection );
1423 double projOnTrack = -( intToPad.x * vecVia.x + intToPad.y * vecVia.y );
1424 int offset = pcbIUScale.mmToIU( 0.001 );
1425
1426 // A custom pad's position is only its anchor, so projecting it yields a depth unrelated to the
1427 // lobe the track enters; padRadius comes from that same anchor and is the consistent bound
1428 bool isCustomPad = aOther->Type() == PCB_PAD_T
1429 && static_cast<PAD*>( aOther )->GetShape( layer ) == PAD_SHAPE::CUSTOM;
1430
1431 double effectiveDist = isCustomPad ? static_cast<double>( padRadius )
1432 : std::max( projOnTrack, static_cast<double>( padRadius ) );
1433
1434 // For non-round pads, clamp effectiveDist so pointD stays inside the copper the track enters
1435 // rather than spiking out the far side on an oblique entry that only grazes a corner
1436 if( !IsRound( aOther, layer ) && aOther->Type() == PCB_PAD_T )
1437 {
1438 PAD* pad = static_cast<PAD*>( aOther );
1439 int maxError = m_board->GetDesignSettings().m_MaxError;
1440 SHAPE_POLY_SET padPoly;
1441 pad->TransformShapeToPolygon( padPoly, layer, 0, maxError, ERROR_INSIDE );
1442
1443 // Cast the into-pad ray from the intersection well past the candidate point so a chord
1444 // through the pad always produces an exit crossing to clamp against. The reach must
1445 // span the longest possible chord from the entry, so use the pad's circumscribed radius
1446 // rather than the minor half-axis (padRadius). On an elongated pad entered along its long
1447 // axis the exit sits up to two major half-axes away, and a reach scaled by the minor
1448 // axis stops short of it, leaving no crossing and wrongly collapsing the teardrop.
1449 double reach = effectiveDist + 2.0 * pad->GetBoundingRadius() + offset;
1450 VECTOR2I rayEnd = intersection + VECTOR2I( KiROUND( -vecVia.x * reach ),
1451 KiROUND( -vecVia.y * reach ) );
1452
1453 // A custom pad's copper can be several disjoint outlines and the ray may cross a hole, so
1454 // gather crossings from every contour rather than outline 0 alone
1455 std::vector<SHAPE_LINE_CHAIN::INTERSECTION> hits;
1456
1457 for( int ii = 0; ii < padPoly.OutlineCount(); ++ii )
1458 {
1459 SHAPE_LINE_CHAIN& padOutline = padPoly.Outline( ii );
1460 padOutline.SetClosed( true );
1461 padOutline.Intersect( SEG( intersection, rayEnd ), hits );
1462
1463 for( int jj = 0; jj < padPoly.HoleCount( ii ); ++jj )
1464 {
1465 SHAPE_LINE_CHAIN& hole = padPoly.Hole( ii, jj );
1466 hole.SetClosed( true );
1467 hole.Intersect( SEG( intersection, rayEnd ), hits );
1468 }
1469 }
1470
1471 std::vector<double> crossings;
1472 crossings.reserve( hits.size() );
1473
1474 for( const SHAPE_LINE_CHAIN::INTERSECTION& hit : hits )
1475 {
1476 // Ignore the crossing at the intersection point itself.
1477 double d = ( hit.p - intersection ).EuclideanNorm();
1478
1479 if( d > offset )
1480 crossings.push_back( d );
1481 }
1482
1483 std::sort( crossings.begin(), crossings.end() );
1484
1485 // Concave copper is re-entered further along the ray, so the last crossing can sit in an
1486 // unrelated lobe; probe past each crossing so a vertex graze does not count as the exit
1487 double exitEdge = 0;
1488
1489 for( double d : crossings )
1490 {
1491 VECTOR2I probe = intersection + VECTOR2I( KiROUND( -vecVia.x * ( d + offset ) ),
1492 KiROUND( -vecVia.y * ( d + offset ) ) );
1493
1494 if( !padPoly.Contains( probe ) )
1495 {
1496 exitEdge = d;
1497 break;
1498 }
1499 }
1500
1501 // exitEdge == 0 means -vecVia does not penetrate the pad (a tangential graze); collapse
1502 // pointD onto the entry so the teardrop simply flares from the track to the pad edge.
1503 effectiveDist = std::min( effectiveDist, std::max( 0.0, exitEdge - 2.0 * offset ) );
1504 }
1505 else
1506 {
1507 // For round pads/vias, clamp effectiveDist so pointD stays inside the pad circle.
1508 // The minimum-of-padRadius floor used for projOnTrack overshoots the pad when the
1509 // teardrop axis (vecVia) is not radial: e.g. a two-segment teardrop where the first
1510 // segment grazes the pad tangentially produces a projection close to zero, but the
1511 // floor still pushes pointD outward by padRadius along -vecVia, creating a spike.
1512 // Solve for the far intersection of the ray (intersection, -vecVia) with the pad
1513 // circle and use that as the upper bound.
1514 double R = static_cast<double>( padRadius );
1515 double cx = intToPad.x; // (aOtherPos - intersection)
1516 double cy = intToPad.y;
1517 double distCenterSq = cx * cx + cy * cy;
1518
1519 // Quadratic for ||intersection + (-vecVia)*t - aOtherPos||^2 = R^2
1520 // expands to t^2 - 2*projOnTrack*t + (distCenterSq - R^2) = 0.
1521 // The far root is projOnTrack + sqrt(projOnTrack^2 - (distCenterSq - R^2)).
1522 double disc = projOnTrack * projOnTrack - ( distCenterSq - R * R );
1523
1524 if( disc >= 0 )
1525 {
1526 double farEdge = projOnTrack + std::sqrt( disc );
1527 double maxAllowed = std::max( 0.0, farEdge - 2.0 * offset );
1528
1529 if( effectiveDist > maxAllowed )
1530 effectiveDist = maxAllowed;
1531 }
1532 }
1533
1534 VECTOR2I pointD = intersection + VECTOR2I( KiROUND( -vecVia.x * ( effectiveDist + offset ) ),
1535 KiROUND( -vecVia.y * ( effectiveDist + offset ) ) );
1536
1537 VECTOR2I pointC, pointE; // Point on pad/via outlines
1538
1539 // For two-segment teardrops, compute junction edge points where the track
1540 // changes direction, and use the first segment side of the junction for
1541 // the convex hull anchor so that C and E are oriented to the via entry axis.
1542 VECTOR2I junctionB_seg2, junctionB_seg1, junctionA_seg2, junctionA_seg1;
1543
1544 if( twoSegments )
1545 {
1546 junctionB_seg2 = start + VECTOR2I( KiROUND( vecT.y * track_halfwidth ),
1547 KiROUND( -vecT.x * track_halfwidth ) );
1548 junctionA_seg2 = start + VECTOR2I( KiROUND( -vecT.y * track_halfwidth ),
1549 KiROUND( vecT.x * track_halfwidth ) );
1550 junctionB_seg1 = start + VECTOR2I( KiROUND( vecVia.y * track_halfwidth ),
1551 KiROUND( -vecVia.x * track_halfwidth ) );
1552 junctionA_seg1 = start + VECTOR2I( KiROUND( -vecVia.y * track_halfwidth ),
1553 KiROUND( vecVia.x * track_halfwidth ) );
1554 }
1555
1556 // On the inside of a bend, the seg2 junction point backtracks relative to
1557 // seg1 and causes self-intersection. Detect with a dot product test and skip
1558 // the seg2 point on whichever side would backtrack.
1559 bool skipJunctionA = false;
1560 bool skipJunctionB = false;
1561
1562 if( twoSegments )
1563 {
1564 VECTOR2D transA = VECTOR2D( junctionA_seg2 - junctionA_seg1 );
1565 VECTOR2D anchorDirA = VECTOR2D( pointA - junctionA_seg1 );
1566 skipJunctionA = ( transA.x * anchorDirA.x + transA.y * anchorDirA.y ) < 0;
1567
1568 VECTOR2D transB = VECTOR2D( junctionB_seg2 - junctionB_seg1 );
1569 VECTOR2D anchorDirB = VECTOR2D( pointB - junctionB_seg1 );
1570 skipJunctionB = ( transB.x * anchorDirB.x + transB.y * anchorDirB.y ) < 0;
1571 }
1572
1573 VECTOR2I anchorA = twoSegments ? junctionA_seg1 : pointA;
1574 VECTOR2I anchorB = twoSegments ? junctionB_seg1 : pointB;
1575
1576 std::vector<VECTOR2I> pts = { anchorA, anchorB, pointC, pointD, pointE };
1577
1578 // On failure the pad-side anchors are left at the origin, stretching the shape to (0, 0).
1579 if( !computeAnchorPoints( aParams, aTrack->GetLayer(), aOther, aOtherPos, pts ) )
1580 return false;
1581
1582 // For off-center track connections, the convex hull produces asymmetric anchor points
1583 // (C and E at different distances from the track axis). Recompute them to be symmetric
1584 // so the teardrop flares out evenly from the track on both sides.
1585 if( IsRound( aOther, layer ) )
1586 {
1587 VECTOR2D perpVia( -vecVia.y, vecVia.x );
1588
1589 // Perpendicular distance from pad center to the track axis
1590 VECTOR2D padOffset = VECTOR2D( aOtherPos - intersection );
1591 double perpDistToCenter = padOffset.x * perpVia.x + padOffset.y * perpVia.y;
1592
1593 // Only apply the symmetric adjustment when the track is significantly off-center.
1594 if( std::abs( perpDistToCenter ) > padRadius * 0.1 )
1595 {
1596 double d = std::abs( perpDistToCenter );
1597
1598 if( d < padRadius )
1599 {
1600 // The maximum symmetric half-width is limited by the shorter side, which is
1601 // the distance from the track axis to the nearest circle edge (R - d).
1602 double maxSymmetric = static_cast<double>( padRadius ) - d;
1603
1604 // Apply the configured width ratio and max width constraints
1605 int preferred_width = KiROUND( GetWidth( aOther, layer ) * aParams.m_BestWidthRatio );
1606 int maxHalfWidth = preferred_width / 2;
1607
1608 if( aParams.m_TdMaxWidth > 0 )
1609 maxHalfWidth = std::min( maxHalfWidth, aParams.m_TdMaxWidth / 2 );
1610
1611 double symHalfWidth = std::min( maxSymmetric, static_cast<double>( maxHalfWidth ) );
1612
1613 if( symHalfWidth > track_halfwidth )
1614 {
1615 VECTOR2D center = VECTOR2D( aOtherPos );
1616 double R = static_cast<double>( padRadius );
1617
1618 // Find C on the circle at perpendicular distance +symHalfWidth from track.
1619 // Line: p = (perpFoot + perpVia*symHalfWidth) + t * vecVia
1620 // Intersect with circle (center, R) and pick the point closest to
1621 // the intersection point (track entry side).
1622 auto findCircleLineIntersection =
1623 [&]( double perpDist ) -> VECTOR2I
1624 {
1625 double projAlongTrack = padOffset.x * vecVia.x
1626 + padOffset.y * vecVia.y;
1627 VECTOR2D lineOrigin = VECTOR2D( intersection )
1628 + vecVia * projAlongTrack
1629 + perpVia * perpDist;
1630
1631 VECTOR2D oc = lineOrigin - center;
1632 double b_coeff = oc.x * vecVia.x + oc.y * vecVia.y;
1633 double c_coeff = oc.x * oc.x + oc.y * oc.y - R * R;
1634 double disc = b_coeff * b_coeff - c_coeff;
1635
1636 if( disc < 0 )
1637 return VECTOR2I( KiROUND( lineOrigin.x ), KiROUND( lineOrigin.y ) );
1638
1639 double sqrtDisc = std::sqrt( disc );
1640 double t1 = -b_coeff - sqrtDisc;
1641 double t2 = -b_coeff + sqrtDisc;
1642
1643 // Pick the point on the intersection side (closer to the track entry)
1644 VECTOR2D p1 = lineOrigin + vecVia * t1;
1645 VECTOR2D p2 = lineOrigin + vecVia * t2;
1646 VECTOR2D intPt = VECTOR2D( intersection );
1647
1648 if( ( p1 - intPt ).EuclideanNorm() < ( p2 - intPt ).EuclideanNorm() )
1649 return VECTOR2I( KiROUND( p1.x ), KiROUND( p1.y ) );
1650
1651 return VECTOR2I( KiROUND( p2.x ), KiROUND( p2.y ) );
1652 };
1653
1654 // pointA is offset in +perpVia from the track axis, pointB in -perpVia
1655 // (see the VECTOR2I pointA/pointB construction above). pts[2] is C,
1656 // which lies adjacent to pointB in the teardrop walk A->B->C->D->E->A,
1657 // so it must sit on pointB's (-perpVia) side. Likewise pts[4] is E,
1658 // adjacent to pointA on the +perpVia side. Assigning the opposite signs
1659 // folds the polygon into a bowtie and produces self-intersecting edges
1660 // whenever the track is off-center enough to trigger this branch.
1661 pts[2] = findCircleLineIntersection( -symHalfWidth );
1662 pts[4] = findCircleLineIntersection( symHalfWidth );
1663 }
1664 }
1665 }
1666 }
1667
1668 if( !aParams.m_CurvedEdges )
1669 {
1670 if( twoSegments )
1671 {
1672 aCorners.push_back( pointA );
1673 aCorners.push_back( pointB );
1674
1675 if( !skipJunctionB )
1676 aCorners.push_back( junctionB_seg2 );
1677
1678 aCorners.push_back( pts[1] ); // junctionB_seg1
1679 aCorners.push_back( pts[2] ); // C
1680 aCorners.push_back( pts[3] ); // D
1681 aCorners.push_back( pts[4] ); // E
1682 aCorners.push_back( pts[0] ); // junctionA_seg1
1683
1684 if( !skipJunctionA )
1685 aCorners.push_back( junctionA_seg2 );
1686 }
1687 else
1688 {
1689 aCorners = std::move( pts );
1690 }
1691
1692 return true;
1693 }
1694
1695 // See if we can use curved teardrop shape
1696 if( IsRound( aOther, layer ) )
1697 {
1698 if( twoSegments )
1699 {
1700 std::vector<VECTOR2I> curvePoly;
1701 computeCurvedForRoundShape( aParams, curvePoly, layer, track_halfwidth, vecVia, aOther, aOtherPos, pts );
1702
1703 aCorners.push_back( pointB );
1704
1705 if( !skipJunctionB )
1706 aCorners.push_back( junctionB_seg2 );
1707
1708 for( const VECTOR2I& pt : curvePoly )
1709 aCorners.push_back( pt );
1710
1711 if( !skipJunctionA )
1712 aCorners.push_back( junctionA_seg2 );
1713
1714 aCorners.push_back( pointA );
1715 }
1716 else
1717 {
1718 computeCurvedForRoundShape( aParams, aCorners, layer, track_halfwidth, vecT, aOther, aOtherPos, pts );
1719 }
1720 }
1721 else
1722 {
1723 int td_width = KiROUND( GetWidth( aOther, layer ) * aParams.m_BestWidthRatio );
1724
1725 if( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < td_width )
1726 td_width = aParams.m_TdMaxWidth;
1727
1728 if( twoSegments )
1729 {
1730 std::vector<VECTOR2I> curvePoly;
1731 computeCurvedForRectShape( aParams, curvePoly, td_width, track_halfwidth, pts, intersection, aOther,
1732 aOtherPos, layer );
1733
1734 aCorners.push_back( pointB );
1735
1736 if( !skipJunctionB )
1737 aCorners.push_back( junctionB_seg2 );
1738
1739 for( const VECTOR2I& pt : curvePoly )
1740 aCorners.push_back( pt );
1741
1742 if( !skipJunctionA )
1743 aCorners.push_back( junctionA_seg2 );
1744
1745 aCorners.push_back( pointA );
1746 }
1747 else
1748 {
1749 computeCurvedForRectShape( aParams, aCorners, td_width, track_halfwidth, pts, intersection, aOther,
1750 aOtherPos, layer );
1751 }
1752 }
1753
1754 return true;
1755}
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Container for design settings for a BOARD object.
std::shared_ptr< DRC_ENGINE > m_DRCEngine
int GetDRCEpsilon() const
Return an epsilon which accounts for rounding errors, etc.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
ecoord_type Diagonal() const
Return the length of the diagonal of the rectangle.
Definition box2.h:707
constexpr coord_type GetY() const
Definition box2.h:205
constexpr coord_type GetX() const
Definition box2.h:204
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetBottom() const
Definition box2.h:219
MINOPTMAX< int > & Value()
Definition drc_rule.h:130
virtual VECTOR2I GetPosition() const
Definition eda_item.h:348
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
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:309
T Min() const
Definition minoptmax.h:29
bool HasMin() const
Definition minoptmax.h:37
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:281
Definition pad.h:61
const VECTOR2I & GetMid() const
Definition pcb_track.h:300
virtual double GetLength() const
Get the length of the track using the hypotenuse calculation.
const VECTOR2I & GetStart() const
Definition pcb_track.h:98
const VECTOR2I & GetEnd() const
Definition pcb_track.h:95
virtual int GetWidth() const
Definition pcb_track.h:92
Definition seg.h:38
int Length() const
Return the length (this).
Definition seg.h:340
const SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError=DefaultAccuracyForPCB(), int *aActualError=nullptr) const
Construct a SHAPE_LINE_CHAIN of segments from a given arc.
VECTOR2I NearestPoint(const VECTOR2I &aP) const
void Reverse()
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int Intersect(const SEG &aSeg, std::vector< INTERSECTION > &aIp) const
Find all intersection points between our line chain and the segment aSeg.
void SetPoint(int aIndex, const VECTOR2I &aPos)
Move a point to a specific location.
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
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.
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.
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
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)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
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.
An abstract shape on 2D plane.
Definition shape.h:124
BOARD * m_board
Definition teardrop.h:334
PCB_TRACK * findTouchingTrack(EDA_ITEM_FLAGS &aMatchType, PCB_TRACK *aTrackRef, PCB_TRACK *aSourceTrack, const VECTOR2I &aEndPoint) const
Find a track connected to the end of another track.
std::map< PCB_LAYER_ID, KIRTREE::DYNAMIC_RTREE< ZONE *, int, 2 > > m_createdTdRTree
The teardrops of this pass by layer, since m_copperRTree cannot take inserts once built.
Definition teardrop.h:343
static bool IsRound(BOARD_ITEM *aItem, PCB_LAYER_ID aLayer)
static bool IsUniformlyRound(BOARD_ITEM *aItem)
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)
std::unordered_map< PTR_PTR_LAYER_CACHE_KEY, int > m_pairClearanceCache
Definition teardrop.h:352
bool computeTeardropPolygon(const TEARDROP_PARAMETERS &aParams, std::vector< VECTOR2I > &aCorners, PCB_TRACK *aTrack, PCB_TRACK *aSourceTrack, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos) const
Compute all teardrop points of the polygon shape.
static int copperNetcode(const BOARD_ITEM *aItem)
std::unordered_map< PTR_LAYER_CACHE_KEY, std::vector< std::set< const BOARD_ITEM * > > > m_zoneConnectionCache
Definition teardrop.h:350
DRC_RTREE m_copperRTree
Every copper item on the board before this pass, to keep teardrops off other nets.
Definition teardrop.h:346
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...
int pairClearance(PCB_TRACK *aSourceTrack, BOARD_ITEM *aItem, PCB_LAYER_ID aLayer) const
TRACK_BUFFER m_trackLookupList
Definition teardrop.h:339
bool collidesWithOtherNets(const std::vector< VECTOR2I > &aPoints, PCB_TRACK *aSourceTrack, const std::vector< const BOARD_ITEM * > &aExempt) const
bool findAnchorPointsOnTrack(const TEARDROP_PARAMETERS &aParams, VECTOR2I &aStartPoint, VECTOR2I &aEndPoint, VECTOR2I &aIntersection, PCB_TRACK *&aTrack, PCB_TRACK *aSourceTrack, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos, int *aEffectiveTeardropLen) const
bool areItemsInSameZone(BOARD_ITEM *aPadOrVia, PCB_TRACK *aTrack) const
bool computeFittedTeardropPolygon(const TEARDROP_PARAMETERS &aParams, std::vector< VECTOR2I > &aPoints, PCB_TRACK *aTrack, PCB_TRACK *aSourceTrack, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos) const
Widen a teardrop as far as the surrounding copper allows.
void ensureCopperIndex() const
Build the copper collision index, deferred so a commit with no teardrop candidate never pays for it.
DRC_RTREE m_tracksRTree
Definition teardrop.h:338
friend class TEARDROP_PARAMETERS
Definition teardrop.h:88
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.
const std::vector< std::set< const BOARD_ITEM * > > & zoneConnections(ZONE *aZone, PCB_LAYER_ID aLayer) 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:61
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:67
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:406
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool IsTeardropArea() const
Definition zone.h:807
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.
@ CLEARANCE_CONSTRAINT
@ RECURSE
Definition eda_item.h:51
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
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
Definition padstack.h:51
@ ROUNDRECT
Definition padstack.h:56
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 clearance
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:227
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:103
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:100
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707