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