KiCad PCB EDA Suite
Loading...
Searching...
No Matches
teardrop_utils.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2021 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25/*
26 * Some calculations (mainly computeCurvedForRoundShape) are derived from
27 * https://github.com/NilujePerchut/kicad_scripts/tree/master/teardrops
28 */
29
31#include <pcb_track.h>
32#include <pad.h>
33#include <zone_filler.h>
34#include <board_commit.h>
35#include <drc/drc_rtree.h>
36#include <trigo.h>
37
38#include "teardrop.h"
42#include <bezier_curves.h>
43
44#include <wx/log.h>
45
46
47void TRACK_BUFFER::AddTrack( PCB_TRACK* aTrack, int aLayer, int aNetcode )
48{
49 auto item = m_map_tracks.find( idxFromLayNet( aLayer, aNetcode ) );
50 std::vector<PCB_TRACK*>* buffer;
51
52 if( item == m_map_tracks.end() )
53 {
54 buffer = new std::vector<PCB_TRACK*>;
55 m_map_tracks[idxFromLayNet( aLayer, aNetcode )] = buffer;
56 }
57 else
58 {
59 buffer = (*item).second;
60 }
61
62 buffer->push_back( aTrack );
63}
64
65
67{
68 if( aItem->Type() == PCB_VIA_T )
69 {
70 PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
71 return via->GetWidth( aLayer );
72 }
73 else if( aItem->Type() == PCB_PAD_T )
74 {
75 PAD* pad = static_cast<PAD*>( aItem );
76 return std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y );
77 }
78 else if( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_ARC_T )
79 {
80 PCB_TRACK* track = static_cast<PCB_TRACK*>( aItem );
81 return track->GetWidth();
82 }
83
84 return 0;
85}
86
87
89{
90 if( aItem->Type() == PCB_PAD_T )
91 {
92 PAD* pad = static_cast<PAD*>( aItem );
93
94 return pad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE
95 || ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL
96 && pad->GetSize( aLayer ).x
97 == pad->GetSize( aLayer ).y );
98 }
99
100 return true;
101}
102
103
105{
106 for( PCB_TRACK* track : m_board->Tracks() )
107 {
108 if( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T )
109 {
110 m_tracksRTree.Insert( track, track->GetLayer() );
111 m_trackLookupList.AddTrack( track, track->GetLayer(), track->GetNetCode() );
112 }
113 }
114}
115
116
118{
119 PCB_LAYER_ID layer = aTrack->GetLayer();
120
121 for( ZONE* zone : m_board->Zones() )
122 {
123 // Skip teardrops
124 if( zone->IsTeardropArea() )
125 continue;
126
127 // Only consider zones on the same layer as the track
128 if( !zone->IsOnLayer( layer ) )
129 continue;
130
131 if( zone->GetNetCode() != aTrack->GetNetCode() )
132 continue;
133
134 // The zone must have filled copper on this layer to provide a connection
135 if( !zone->HasFilledPolysForLayer( layer ) )
136 continue;
137
138 std::shared_ptr<SHAPE_POLY_SET> fill = zone->GetFilledPolysList( layer );
139
140 if( !fill || fill->IsEmpty() )
141 continue;
142
143 // Check if the zone's filled copper actually contains both the pad/via and the track.
144 // The zone outline might contain these items, but the actual fill might not reach them
145 // due to thermal settings, minimum width, island removal, etc.
146 VECTOR2I padPos( aPadOrVia->GetPosition() );
147
148 if( !fill->Contains( padPos ) )
149 continue;
150
151 // Also verify the track is within the filled zone (check both endpoints)
152 if( !fill->Contains( aTrack->GetStart() ) && !fill->Contains( aTrack->GetEnd() ) )
153 continue;
154
155 // If the first item is a pad, ensure it can be connected to the zone
156 if( aPadOrVia->Type() == PCB_PAD_T )
157 {
158 PAD* pad = static_cast<PAD*>( aPadOrVia );
159
160 if( zone->GetPadConnection() == ZONE_CONNECTION::NONE
161 || pad->GetZoneConnectionOverrides( nullptr ) == ZONE_CONNECTION::NONE )
162 {
163 return false;
164 }
165 }
166
167 return true;
168 }
169
170 return false;
171}
172
173
175 const VECTOR2I& aEndPoint ) const
176{
177 int matches = 0; // Count of candidates: only 1 is acceptable
178 PCB_TRACK* candidate = nullptr; // a reference to the track connected
179
180 m_tracksRTree.QueryColliding( aTrackRef, aTrackRef->GetLayer(), aTrackRef->GetLayer(),
181 // Filter:
182 [&]( BOARD_ITEM* trackItem ) -> bool
183 {
184 return trackItem != aTrackRef;
185 },
186 // Visitor
187 [&]( BOARD_ITEM* trackItem ) -> bool
188 {
189 PCB_TRACK* curr_track = static_cast<PCB_TRACK*>( trackItem );
190
191 // IsPointOnEnds() returns 0, EDA_ITEM_FLAGS::STARTPOINT or EDA_ITEM_FLAGS::ENDPOINT
192 if( EDA_ITEM_FLAGS match = curr_track->IsPointOnEnds( aEndPoint, m_tolerance ) )
193 {
194 // if faced with a Y junction, choose the track longest segment as candidate
195 matches++;
196
197 if( matches > 1 )
198 {
199 double previous_len = candidate->GetLength();
200 double curr_len = curr_track->GetLength();
201
202 if( previous_len >= curr_len )
203 return true;
204 }
205
206 aMatchType = match;
207 candidate = curr_track;
208 }
209
210 return true;
211 },
212 0 );
213
214 return candidate;
215}
216
217
221static VECTOR2D NormalizeVector( const VECTOR2I& aVector )
222{
223 VECTOR2D vect( aVector );
224 double norm = vect.EuclideanNorm();
225 return vect / norm;
226}
227
228
229/*
230 * Compute the curve part points for teardrops connected to a round shape
231 * The Bezier curve control points are optimized for a round pad/via shape,
232 * and do not give a good curve shape for other pad shapes.
233 *
234 * For large circles where the teardrop width is constrained, the anchor points
235 * are projected onto the circle edge to ensure proper tangent calculation.
236 */
238 std::vector<VECTOR2I>& aPoly,
239 PCB_LAYER_ID aLayer,
240 int aTrackHalfWidth, const VECTOR2D& aTrackDir,
241 BOARD_ITEM* aOther, const VECTOR2I& aOtherPos,
242 std::vector<VECTOR2I>& pts ) const
243{
244 int maxError = m_board->GetDesignSettings().m_MaxError;
245
246 // in pts:
247 // A and B are points on the track ( pts[0] and pts[1] )
248 // C and E are points on the aViaPad ( pts[2] and pts[4] )
249 // D is the aViaPad centre ( pts[3] )
250 double Vpercent = aParams.m_BestWidthRatio;
251 int td_height = KiROUND( GetWidth( aOther, aLayer ) * Vpercent );
252
253 // First, calculate a aVpercent equivalent to the td_height clamped by aTdMaxHeight
254 // We cannot use the initial aVpercent because it gives bad shape with points
255 // on aViaPad calculated for a clamped aViaPad size
256 if( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < td_height )
257 Vpercent *= (double) aParams.m_TdMaxWidth / td_height;
258
259 int radius = GetWidth( aOther, aLayer ) / 2;
260
261 // Don't divide by zero. No good can come of that.
262 wxCHECK2( radius != 0, radius = 1 );
263
264 double minVpercent = double( aTrackHalfWidth ) / radius;
265 double weaken = (Vpercent - minVpercent) / ( 1 - minVpercent ) / radius;
266
267 // For large circles where teardrop width is constrained, the anchor points from the
268 // convex hull may not be exactly on the circle. Project them onto the circle edge
269 // to ensure proper tangent calculation for smooth curves.
270 VECTOR2I vecC = pts[2] - aOtherPos;
271 double distC = vecC.EuclideanNorm();
272
273 if( distC > 0 && std::abs( distC - radius ) > maxError )
274 {
275 // Point is not on the circle - project it to the circle edge
276 pts[2] = aOtherPos + vecC.Resize( radius );
277 vecC = pts[2] - aOtherPos;
278 }
279
280 VECTOR2I vecE = pts[4] - aOtherPos;
281 double distE = vecE.EuclideanNorm();
282
283 if( distE > 0 && std::abs( distE - radius ) > maxError )
284 {
285 // Point is not on the circle - project it to the circle edge
286 pts[4] = aOtherPos + vecE.Resize( radius );
287 vecE = pts[4] - aOtherPos;
288 }
289
290 double biasBC = 0.5 * SEG( pts[1], pts[2] ).Length();
291 double biasAE = 0.5 * SEG( pts[4], pts[0] ).Length();
292
293 VECTOR2I tangentC = VECTOR2I( pts[2].x - vecC.y * biasBC * weaken,
294 pts[2].y + vecC.x * biasBC * weaken );
295 VECTOR2I tangentE = VECTOR2I( pts[4].x + vecE.y * biasAE * weaken,
296 pts[4].y - vecE.x * biasAE * weaken );
297
298 VECTOR2I tangentB = VECTOR2I( pts[1].x - aTrackDir.x * biasBC, pts[1].y - aTrackDir.y * biasBC );
299 VECTOR2I tangentA = VECTOR2I( pts[0].x - aTrackDir.x * biasAE, pts[0].y - aTrackDir.y * biasAE );
300
301 std::vector<VECTOR2I> curve_pts;
302 BEZIER_POLY( pts[1], tangentB, tangentC, pts[2] ).GetPoly( curve_pts, maxError );
303
304 for( VECTOR2I& corner: curve_pts )
305 aPoly.push_back( corner );
306
307 aPoly.push_back( pts[3] );
308
309 curve_pts.clear();
310 BEZIER_POLY( pts[4], tangentE, tangentA, pts[0] ).GetPoly( curve_pts, maxError );
311
312 for( VECTOR2I& corner: curve_pts )
313 aPoly.push_back( corner );
314}
315
316
329 const VECTOR2I& aCornerCenter,
330 double aBias,
331 const VECTOR2I& aDesiredDir )
332{
333 VECTOR2I radial = aAnchor - aCornerCenter;
334
335 if( radial.EuclideanNorm() == 0 )
336 return aAnchor;
337
338 // Tangent is perpendicular to the radius. There are two perpendicular directions:
339 // (radial.y, -radial.x) and (-radial.y, radial.x)
340 // Choose the one that best aligns with the desired direction (toward the track)
341 VECTOR2I tangent1( radial.y, -radial.x );
342 VECTOR2I tangent2( -radial.y, radial.x );
343
344 // Use dot product to determine which tangent direction aligns better with desired direction
345 int64_t dot1 = static_cast<int64_t>( tangent1.x ) * aDesiredDir.x
346 + static_cast<int64_t>( tangent1.y ) * aDesiredDir.y;
347 int64_t dot2 = static_cast<int64_t>( tangent2.x ) * aDesiredDir.x
348 + static_cast<int64_t>( tangent2.y ) * aDesiredDir.y;
349
350 VECTOR2I tangent = ( dot1 > dot2 ) ? tangent1 : tangent2;
351
352 return aAnchor + tangent.Resize( KiROUND( aBias ) );
353}
354
355
367static bool isPointOnOvalEnd( const VECTOR2I& aPoint, const VECTOR2I& aPadPos,
368 const VECTOR2I& aPadSize, const EDA_ANGLE& aRotation,
369 VECTOR2I& aArcCenter )
370{
371 // Transform point to pad-local coordinates (unrotated)
372 VECTOR2I localPt = aPoint - aPadPos;
373 RotatePoint( localPt, aRotation );
374
375 int halfW = aPadSize.x / 2;
376 int halfH = aPadSize.y / 2;
377
378 // Oval geometry: semicircle radius is min dimension / 2
379 // The semicircle centers are offset along the major axis
380 int radius = std::min( halfW, halfH );
381 bool isHorizontal = halfW > halfH;
382
383 if( isHorizontal )
384 {
385 // Semicircles at left and right ends
386 int centerOffset = halfW - radius;
387
388 // Check if point is in the curved region (beyond the straight sides)
389 if( std::abs( localPt.x ) <= centerOffset )
390 return false;
391
392 // Determine which end
393 int centerX = ( localPt.x > 0 ) ? centerOffset : -centerOffset;
394 aArcCenter = VECTOR2I( centerX, 0 );
395 }
396 else
397 {
398 // Semicircles at top and bottom ends
399 int centerOffset = halfH - radius;
400
401 // Check if point is in the curved region (beyond the straight sides)
402 if( std::abs( localPt.y ) <= centerOffset )
403 return false;
404
405 // Determine which end
406 int centerY = ( localPt.y > 0 ) ? centerOffset : -centerOffset;
407 aArcCenter = VECTOR2I( 0, centerY );
408 }
409
410 // Transform arc center back to board coordinates
411 RotatePoint( aArcCenter, -aRotation );
412 aArcCenter += aPadPos;
413
414 return true;
415}
416
417
430static bool isPointOnRoundedCorner( const VECTOR2I& aPoint, const VECTOR2I& aPadPos,
431 const VECTOR2I& aPadSize, int aCornerRadius,
432 const EDA_ANGLE& aRotation, VECTOR2I& aCornerCenter )
433{
434 // Transform point to pad-local coordinates (unrotated)
435 VECTOR2I localPt = aPoint - aPadPos;
436 RotatePoint( localPt, aRotation );
437
438 // Half-sizes minus corner radius define the inner rectangle
439 int halfW = aPadSize.x / 2;
440 int halfH = aPadSize.y / 2;
441 int innerHalfW = halfW - aCornerRadius;
442 int innerHalfH = halfH - aCornerRadius;
443
444 // Point is in corner region if it's outside the inner rectangle in both dimensions
445 bool inCornerX = std::abs( localPt.x ) > innerHalfW;
446 bool inCornerY = std::abs( localPt.y ) > innerHalfH;
447
448 if( !inCornerX || !inCornerY )
449 return false;
450
451 // Determine which corner
452 int cornerX = ( localPt.x > 0 ) ? innerHalfW : -innerHalfW;
453 int cornerY = ( localPt.y > 0 ) ? innerHalfH : -innerHalfH;
454
455 aCornerCenter = VECTOR2I( cornerX, cornerY );
456
457 // Transform corner center back to board coordinates
458 RotatePoint( aCornerCenter, -aRotation );
459 aCornerCenter += aPadPos;
460
461 return true;
462}
463
464
465/*
466 * Compute the curve part points for teardrops connected to a rectangular/polygonal shape.
467 * For rounded rectangles, control points are computed to be tangent to corner arcs,
468 * preventing the teardrop curve from intersecting the pad's corner radius.
469 */
471 std::vector<VECTOR2I>& aPoly, int aTdWidth,
472 int aTrackHalfWidth,
473 std::vector<VECTOR2I>& aPts,
474 const VECTOR2I& aIntersection,
475 BOARD_ITEM* aOther,
476 const VECTOR2I& aOtherPos,
477 PCB_LAYER_ID aLayer ) const
478{
479 int maxError = m_board->GetDesignSettings().m_MaxError;
480
481 // in aPts:
482 // A and B are points on the track ( pts[0] and pts[1] )
483 // C and E are points on the pad/via ( pts[2] and pts[4] )
484 // D is the aViaPad centre ( pts[3] )
485
486 // side1 is( aPts[1], aPts[2] ); from track to via
487 VECTOR2I side1( aPts[2] - aPts[1] ); // vector from track to via
488 // side2 is ( aPts[4], aPts[0] ); from via to track
489 VECTOR2I side2( aPts[4] - aPts[0] ); // vector from track to via
490
491 VECTOR2I trackDir( aIntersection - ( aPts[0] + aPts[1] ) / 2 );
492
493 // Check if this is a rounded rectangle or oval pad (both have curved regions)
494 bool isRoundRect = false;
495 bool isOval = false;
496 int cornerRadius = 0;
497 VECTOR2I padSize;
498 EDA_ANGLE padRotation;
499
500 if( aOther && aOther->Type() == PCB_PAD_T )
501 {
502 PAD* pad = static_cast<PAD*>( aOther );
503 PAD_SHAPE shape = pad->GetShape( aLayer );
504
505 if( shape == PAD_SHAPE::ROUNDRECT )
506 {
507 isRoundRect = true;
508 cornerRadius = pad->GetRoundRectCornerRadius( aLayer );
509 padSize = pad->GetSize( aLayer );
510 padRotation = pad->GetOrientation();
511 }
512 else if( shape == PAD_SHAPE::OVAL )
513 {
514 isOval = true;
515 padSize = pad->GetSize( aLayer );
516 padRotation = pad->GetOrientation();
517 }
518 }
519
520 std::vector<VECTOR2I> curve_pts;
521
522 // Compute control points for the first Bezier curve (track point B to pad point C)
523 VECTOR2I ctrl1 = aPts[1] + trackDir.Resize( side1.EuclideanNorm() / 4 );
524 VECTOR2I ctrl2;
525
526 // Direction from pad anchor toward track (opposite of trackDir which goes pad-ward)
527 VECTOR2I towardTrack = -trackDir;
528
529 // Default control point - midpoint approach
530 ctrl2 = ( aPts[2] + aIntersection ) / 2;
531
532 if( isRoundRect && cornerRadius > 0 )
533 {
534 VECTOR2I cornerCenter;
535
536 if( isPointOnRoundedCorner( aPts[2], aOtherPos, padSize, cornerRadius,
537 padRotation, cornerCenter ) )
538 {
539 // Anchor is on a corner arc - use tangent-based control point
540 double bias = 0.5 * side1.EuclideanNorm();
541 ctrl2 = computeCornerTangentControlPoint( aPts[2], cornerCenter, bias, towardTrack );
542 }
543 }
544 else if( isOval )
545 {
546 VECTOR2I arcCenter;
547
548 if( isPointOnOvalEnd( aPts[2], aOtherPos, padSize, padRotation, arcCenter ) )
549 {
550 // Anchor is on a curved end - use tangent-based control point
551 double bias = 0.5 * side1.EuclideanNorm();
552 ctrl2 = computeCornerTangentControlPoint( aPts[2], arcCenter, bias, towardTrack );
553 }
554 }
555
556 BEZIER_POLY( aPts[1], ctrl1, ctrl2, aPts[2] ).GetPoly( curve_pts, maxError );
557
558 for( VECTOR2I& corner: curve_pts )
559 aPoly.push_back( corner );
560
561 aPoly.push_back( aPts[3] );
562
563 // Compute control points for second Bezier curve (pad point E to track point A)
564 curve_pts.clear();
565
566 // Default control point - midpoint approach
567 ctrl1 = ( aPts[4] + aIntersection ) / 2;
568
569 if( isRoundRect && cornerRadius > 0 )
570 {
571 VECTOR2I cornerCenter;
572
573 if( isPointOnRoundedCorner( aPts[4], aOtherPos, padSize, cornerRadius,
574 padRotation, cornerCenter ) )
575 {
576 // Anchor is on a corner arc - use tangent-based control point
577 double bias = 0.5 * side2.EuclideanNorm();
578 ctrl1 = computeCornerTangentControlPoint( aPts[4], cornerCenter, bias, towardTrack );
579 }
580 }
581 else if( isOval )
582 {
583 VECTOR2I arcCenter;
584
585 if( isPointOnOvalEnd( aPts[4], aOtherPos, padSize, padRotation, arcCenter ) )
586 {
587 // Anchor is on a curved end - use tangent-based control point
588 double bias = 0.5 * side2.EuclideanNorm();
589 ctrl1 = computeCornerTangentControlPoint( aPts[4], arcCenter, bias, towardTrack );
590 }
591 }
592
593 ctrl2 = aPts[0] + trackDir.Resize( side2.EuclideanNorm() / 4 );
594
595 BEZIER_POLY( aPts[4], ctrl1, ctrl2, aPts[0] ).GetPoly( curve_pts, maxError );
596
597 for( VECTOR2I& corner: curve_pts )
598 aPoly.push_back( corner );
599}
600
601
603 BOARD_ITEM* aItem, const VECTOR2I& aPos,
604 std::vector<VECTOR2I>& aPts ) const
605{
606 int maxError = m_board->GetDesignSettings().m_MaxError;
607
608 // Compute the 2 anchor points on pad/via/track of the teardrop shape
609
610 SHAPE_POLY_SET c_buffer;
611
612 // m_BestWidthRatio is the factor to calculate the teardrop preferred width.
613 // teardrop width = pad, via or track size * m_BestWidthRatio (m_BestWidthRatio <= 1.0)
614 // For rectangular (and similar) shapes, the preferred_width is calculated from the min
615 // dim of the rectangle
616
617 int preferred_width = KiROUND( GetWidth( aItem, aLayer ) * aParams.m_BestWidthRatio );
618
619 // force_clip = true to force the pad/via/track polygon to be clipped to follow
620 // constraints
621 // Clipping is also needed for rectangular shapes, because the teardrop shape is restricted
622 // to a polygonal area smaller than the pad area (the teardrop height use the smaller value
623 // of X and Y sizes).
624 bool force_clip = aParams.m_BestWidthRatio < 1.0;
625
626 // To find the anchor points on the pad/via/track shape, we build the polygonal shape, and
627 // clip the polygon to the max size (preferred_width or m_TdMaxWidth) by a rectangle
628 // centered on the axis of the expected teardrop shape.
629 // (only reduce the size of polygonal shape does not give good anchor points)
630 if( IsRound( aItem, aLayer ) )
631 {
632 TransformCircleToPolygon( c_buffer, aPos, GetWidth( aItem, aLayer ) / 2, maxError,
633 ERROR_INSIDE, 16 );
634 }
635 else // Only PADS can have a not round shape
636 {
637 wxCHECK_MSG( aItem->Type() == PCB_PAD_T, false, wxT( "Expected non-round item to be PAD" ) );
638 PAD* pad = static_cast<PAD*>( aItem );
639
640 force_clip = true;
641
642 preferred_width = KiROUND( GetWidth( pad, aLayer ) * aParams.m_BestWidthRatio );
643 pad->TransformShapeToPolygon( c_buffer, aLayer, 0, maxError, ERROR_INSIDE );
644 }
645
646 // Clip the pad/via/track shape to match the m_TdMaxWidth constraint, and for non-round pads,
647 // clip the shape to the smallest of size.x and size.y values.
648 if( force_clip || ( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < preferred_width ) )
649 {
650 int halfsize = std::min( aParams.m_TdMaxWidth, preferred_width )/2;
651
652 // teardrop_axis is the line from anchor point on the track and the end point
653 // of the teardrop in the pad/via
654 // this is the teardrop_axis of the teardrop shape to build
655 VECTOR2I ref_on_track = ( aPts[0] + aPts[1] ) / 2;
656 VECTOR2I teardrop_axis( aPts[3] - ref_on_track );
657
658 EDA_ANGLE orient( teardrop_axis );
659 int len = teardrop_axis.EuclideanNorm();
660
661 // Build the constraint polygon: a rectangle with
662 // length = dist between the point on track and the pad/via pos
663 // height = m_TdMaxWidth or aViaPad.m_Width
664 SHAPE_POLY_SET clipping_rect;
665 clipping_rect.NewOutline();
666
667 // Build a horizontal rect: it will be rotated later
668 clipping_rect.Append( 0, - halfsize );
669 clipping_rect.Append( 0, halfsize );
670 clipping_rect.Append( len, halfsize );
671 clipping_rect.Append( len, - halfsize );
672
673 clipping_rect.Rotate( -orient );
674 clipping_rect.Move( ref_on_track );
675
676 // Clip the shape to the max allowed teadrop area
677 c_buffer.BooleanIntersection( clipping_rect );
678 }
679
680 /* in aPts:
681 * A and B are points on the track ( aPts[0] and aPts[1] )
682 * C and E are points on the aViaPad ( aPts[2] and aPts[4] )
683 * D is midpoint behind the aViaPad centre ( aPts[3] )
684 */
685
686 SHAPE_LINE_CHAIN& padpoly = c_buffer.Outline(0);
687 std::vector<VECTOR2I> points = padpoly.CPoints();
688
689 std::vector<VECTOR2I> initialPoints;
690 initialPoints.push_back( aPts[0] );
691 initialPoints.push_back( aPts[1] );
692
693 for( const VECTOR2I& pt: points )
694 initialPoints.emplace_back( pt.x, pt.y );
695
696 std::vector<VECTOR2I> hull;
697 BuildConvexHull( hull, initialPoints );
698
699 // Search for end points of segments starting at aPts[0] or aPts[1]
700 // In some cases, in convex hull, only one point (aPts[0] or aPts[1]) is still in list
701 VECTOR2I PointC;
702 VECTOR2I PointE;
703 int found_start = -1; // 2 points (one start and one end) should be found
704 int found_end = -1;
705
706 VECTOR2I start = aPts[0];
707 VECTOR2I pend = aPts[1];
708
709 for( unsigned ii = 0, jj = 0; jj < hull.size(); ii++, jj++ )
710 {
711 unsigned next = ii+ 1;
712
713 if( next >= hull.size() )
714 next = 0;
715
716 int prev = ii -1;
717
718 if( prev < 0 )
719 prev = hull.size()-1;
720
721 if( hull[ii] == start )
722 {
723 // the previous or the next point is candidate:
724 if( hull[next] != pend )
725 PointE = hull[next];
726 else
727 PointE = hull[prev];
728
729 found_start = ii;
730 }
731
732 if( hull[ii] == pend )
733 {
734 if( hull[next] != start )
735 PointC = hull[next];
736 else
737 PointC = hull[prev];
738
739 found_end = ii;
740 }
741 }
742
743 if( found_start < 0 ) // PointE was not initialized, because start point does not exit
744 {
745 int ii = found_end-1;
746
747 if( ii < 0 )
748 ii = hull.size()-1;
749
750 PointE = hull[ii];
751 }
752
753 if( found_end < 0 ) // PointC was not initialized, because end point does not exit
754 {
755 int ii = found_start-1;
756
757 if( ii < 0 )
758 ii = hull.size()-1;
759
760 PointC = hull[ii];
761 }
762
763 aPts[2] = PointC;
764 aPts[4] = PointE;
765
766 // Now we have to know if the choice aPts[2] = PointC is the best, or if
767 // aPts[2] = PointE is better.
768 // A criteria is to calculate the polygon area in these 2 cases, and choose the case
769 // that gives the bigger area, because the segments starting at PointC and PointE
770 // maximize their distance.
771 SHAPE_LINE_CHAIN dummy1( aPts, true );
772 double area1 = dummy1.Area();
773
774 std::swap( aPts[2], aPts[4] );
775 SHAPE_LINE_CHAIN dummy2( aPts, true );
776 double area2 = dummy2.Area();
777
778 if( area1 > area2 ) // The first choice (without swapping) is the better.
779 std::swap( aPts[2], aPts[4] );
780
781 return true;
782}
783
784
786 VECTOR2I& aStartPoint, VECTOR2I& aEndPoint,
787 VECTOR2I& aIntersection, PCB_TRACK*& aTrack,
788 BOARD_ITEM* aOther, const VECTOR2I& aOtherPos,
789 int* aEffectiveTeardropLen ) const
790{
791 bool found = true;
792 VECTOR2I start = aTrack->GetStart(); // one reference point on the track, inside teardrop
793 VECTOR2I end = aTrack->GetEnd(); // the second reference point on the track, outside teardrop
794 PCB_LAYER_ID layer = aTrack->GetLayer();
795 int radius = GetWidth( aOther, layer ) / 2;
796 int maxError = m_board->GetDesignSettings().m_MaxError;
797
798 // Requested length of the teardrop:
799 int targetLength = KiROUND( GetWidth( aOther, layer ) * aParams.m_BestLengthRatio );
800
801 if( aParams.m_TdMaxLen > 0 )
802 targetLength = std::min( aParams.m_TdMaxLen, targetLength );
803
804 // actualTdLen is the distance between start and the teardrop point on the segment from start to end
805 int actualTdLen;
806 bool need_swap = false; // true if the start and end points of the current track are swapped
807
808 // aTrack is expected to have one end inside the via/pad and the other end outside
809 // so ensure the start point is inside the via/pad
810 if( !aOther->HitTest( start, 0 ) )
811 {
812 std::swap( start, end );
813 need_swap = true;
814 }
815
816 SHAPE_POLY_SET shapebuffer;
817
818 if( IsRound( aOther, layer ) )
819 {
820 TransformCircleToPolygon( shapebuffer, aOtherPos, radius, maxError, ERROR_INSIDE, 16 );
821 }
822 else
823 {
824 wxCHECK_MSG( aOther->Type() == PCB_PAD_T, false, wxT( "Expected non-round item to be PAD" ) );
825 static_cast<PAD*>( aOther )->TransformShapeToPolygon( shapebuffer, aTrack->GetLayer(), 0,
826 maxError, ERROR_INSIDE );
827 }
828
829 SHAPE_LINE_CHAIN& outline = shapebuffer.Outline(0);
830 outline.SetClosed( true );
831
832 // Search the intersection point between the pad/via shape and the current track
833 // This this the starting point to define the teardrop length
835 int pt_count;
836
837 if( aTrack->Type() == PCB_ARC_T )
838 {
839 // To find the starting point we convert the arc to a polyline
840 // and compute the intersection point with the pad/via shape
841 SHAPE_ARC arc( aTrack->GetStart(), static_cast<PCB_ARC*>( aTrack )->GetMid(),
842 aTrack->GetEnd(), aTrack->GetWidth() );
843
844 SHAPE_LINE_CHAIN poly = arc.ConvertToPolyline( maxError );
845 pt_count = outline.Intersect( poly, pts );
846 }
847 else
848 {
849 pt_count = outline.Intersect( SEG( start, end ), pts );
850 }
851
852 // Ensure a intersection point was found, otherwise we cannot built the teardrop
853 // using this track (it is fully outside or inside the pad/via shape)
854 if( pt_count < 1 )
855 return false;
856
857 aIntersection = pts[0].p;
858 start = aIntersection; // This is currently the reference point of the teardrop length
859
860 // actualTdLen for now the distance between start and the teardrop point on the (start end)segment
861 // It cannot be bigger than the lenght of this segment
862 actualTdLen = std::min( targetLength, SEG( start, end ).Length() );
863 VECTOR2I ref_lenght_point = start; // the reference point of actualTdLen
864
865 // If the first track is too short to allow a teardrop having the requested length
866 // explore the connected track(s), and try to find a anchor point at targetLength from initial start
867 if( actualTdLen < targetLength && aParams.m_AllowUseTwoTracks )
868 {
869 int consumed = 0;
870
871 while( actualTdLen + consumed < targetLength )
872 {
873 EDA_ITEM_FLAGS matchType;
874
875 PCB_TRACK* connected_track = findTouchingTrack( matchType, aTrack, end );
876
877 if( connected_track == nullptr )
878 break;
879
880 // TODO: stop if angle between old and new segment is > 45 deg to avoid bad shape
881 consumed += actualTdLen;
882 // actualTdLen is the new distance from new start point and the teardrop anchor point
883 actualTdLen = std::min( targetLength-consumed, int( connected_track->GetLength() ) );
884 aTrack = connected_track;
885 end = connected_track->GetEnd();
886 start = connected_track->GetStart();
887 need_swap = false;
888
889 if( matchType != STARTPOINT )
890 {
891 std::swap( start, end );
892 need_swap = true;
893 }
894
895 // If we do not want to explore more than one connected track, stop search here
896 break;
897 }
898 }
899
900 // if aTrack is an arc, find the best teardrop end point on the arc
901 // It is currently on the segment from arc start point to arc end point,
902 // therefore not really on the arc, because we have used only the track end points.
903 if( aTrack->Type() == PCB_ARC_T )
904 {
905 // To find the best start and end points to build the teardrop shape, we convert
906 // the arc to segments, and search for the segment having its start point at a dist
907 // < actualTdLen, and its end point at adist > actualTdLen:
908 SHAPE_ARC arc( aTrack->GetStart(), static_cast<PCB_ARC*>( aTrack )->GetMid(),
909 aTrack->GetEnd(), aTrack->GetWidth() );
910
911 if( need_swap )
912 arc.Reverse();
913
914 SHAPE_LINE_CHAIN poly = arc.ConvertToPolyline( maxError );
915
916 // Now, find the segment of the arc at a distance < actualTdLen from ref_lenght_point.
917 // We just search for the first segment (starting from the farest segment) with its
918 // start point at a distance < actualTdLen dist
919 // This is basic, but it is probably enough.
920 if( poly.PointCount() > 2 )
921 {
922 // Note: the first point is inside or near the pad/via shape
923 // The last point is outside and the farest from the ref_lenght_point
924 // So we explore segments from the last to the first
925 for( int ii = poly.PointCount()-1; ii >= 0 ; ii-- )
926 {
927 int dist_from_start = ( poly.CPoint( ii ) - start ).EuclideanNorm();
928
929 // The first segment at a distance of the reference point < actualTdLen is OK
930 // and is suitable to define the reference segment of the teardrop anchor.
931 if( dist_from_start < actualTdLen || ii == 0 )
932 {
933 start = poly.CPoint( ii );
934
935 if( ii < poly.PointCount()-1 )
936 end = poly.CPoint( ii+1 );
937
938 // actualTdLen is the distance between start (the reference segment start point)
939 // and the point on track of the teardrop.
940 // This is the difference between the initial actualTdLen value and the
941 // distance between start and ref_lenght_point.
942 actualTdLen -= (start - ref_lenght_point).EuclideanNorm();
943
944 // Ensure validity of actualTdLen: >= 0, and <= segment lenght
945 if( actualTdLen < 0 ) // should not happen, but...
946 actualTdLen = 0;
947
948 actualTdLen = std::min( actualTdLen, (end - start).EuclideanNorm() );
949
950 break;
951 }
952 }
953 }
954 }
955
956 // aStartPoint and aEndPoint will define later a segment to build the 2 anchors points
957 // of the teardrop on the aTrack shape.
958 // they are two points (both outside the pad/via shape) of aTrack if aTrack is a segment,
959 // or a small segment on aTrack if aTrack is an ARC
960 aStartPoint = start;
961 aEndPoint = end;
962
963 *aEffectiveTeardropLen = actualTdLen;
964 return found;
965}
966
967
969 std::vector<VECTOR2I>& aCorners, PCB_TRACK* aTrack,
970 BOARD_ITEM* aOther, const VECTOR2I& aOtherPos ) const
971{
972 VECTOR2I start, end; // Start and end points of the track anchor of the teardrop
973 // the start point is inside the teardrop shape
974 // the end point is outside.
975 VECTOR2I intersection; // Where the track centerline intersects the pad/via edge
976 int track_stub_len; // the dist between the start point and the anchor point
977 // on the track
978
979 // Note: aTrack can be modified if the initial track is too short
980 if( !findAnchorPointsOnTrack( aParams, start, end, intersection, aTrack, aOther, aOtherPos,
981 &track_stub_len ) )
982 {
983 return false;
984 }
985
986 // The start and end points must be different to calculate a valid polygon shape
987 if( start == end )
988 return false;
989
990 VECTOR2D vecT = NormalizeVector(end - start);
991
992 // find the 2 points on the track, sharp end of the teardrop
993 int track_halfwidth = aTrack->GetWidth() / 2;
994 VECTOR2I pointB = start + VECTOR2I( vecT.x * track_stub_len + vecT.y * track_halfwidth,
995 vecT.y * track_stub_len - vecT.x * track_halfwidth );
996 VECTOR2I pointA = start + VECTOR2I( vecT.x * track_stub_len - vecT.y * track_halfwidth,
997 vecT.y * track_stub_len + vecT.x * track_halfwidth );
998
999 PCB_LAYER_ID layer = aTrack->GetLayer();
1000
1001 // To build a polygonal valid shape pointA and point B must be outside the pad
1002 // It can be inside with some pad shapes having very different X and X sizes
1003 if( !IsRound( aOther, layer ) )
1004 {
1005 PAD* pad = static_cast<PAD*>( aOther );
1006
1007 if( pad->HitTest( pointA, 0, layer ) )
1008 return false;
1009
1010 if( pad->HitTest( pointB, 0, layer ) )
1011 return false;
1012 }
1013
1014 // Introduce a last point to cover the via centre to ensure it is seen as connected
1015 VECTOR2I pointD = aOtherPos;
1016 // add a small offset in order to have the aViaPad.m_Pos reference point inside
1017 // the teardrop area, just in case...
1018 int offset = pcbIUScale.mmToIU( 0.001 );
1019 pointD += VECTOR2I( int( -vecT.x*offset), int(-vecT.y*offset) );
1020
1021 VECTOR2I pointC, pointE; // Point on pad/via outlines
1022 std::vector<VECTOR2I> pts = { pointA, pointB, pointC, pointD, pointE };
1023
1024 computeAnchorPoints( aParams, aTrack->GetLayer(), aOther, aOtherPos, pts );
1025
1026 if( !aParams.m_CurvedEdges )
1027 {
1028 aCorners = std::move( pts );
1029 return true;
1030 }
1031
1032 // See if we can use curved teardrop shape
1033 if( IsRound( aOther, layer ) )
1034 {
1035 computeCurvedForRoundShape( aParams, aCorners, layer, track_halfwidth, vecT, aOther, aOtherPos, pts );
1036 }
1037 else
1038 {
1039 int td_width = KiROUND( GetWidth( aOther, layer ) * aParams.m_BestWidthRatio );
1040
1041 if( aParams.m_TdMaxWidth > 0 && aParams.m_TdMaxWidth < td_width )
1042 td_width = aParams.m_TdMaxWidth;
1043
1044 computeCurvedForRectShape( aParams, aCorners, td_width, track_halfwidth, pts, intersection,
1045 aOther, aOtherPos, layer );
1046 }
1047
1048 return true;
1049}
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:83
virtual VECTOR2I GetPosition() const
Definition eda_item.h:277
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:238
Definition pad.h:55
const VECTOR2I & GetMid() const
Definition pcb_track.h:347
virtual double GetLength() const
Get the length of the track using the hypotenuse calculation.
const VECTOR2I & GetStart() const
Definition pcb_track.h:154
const VECTOR2I & GetEnd() const
Definition pcb_track.h:151
virtual int GetWidth() const
Definition pcb_track.h:148
Definition seg.h:42
int Length() const
Return the length (this).
Definition seg.h:343
const SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError=DefaultAccuracyForPCB(), int *aActualError=nullptr) const
Construct a SHAPE_LINE_CHAIN of segments from a given arc.
void Reverse()
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int Intersect(const SEG &aSeg, INTERSECTIONS &aIp) const
Find all intersection points between our line chain and the segment aSeg.
int PointCount() const
Return the number of points (vertices) in this line chain.
double Area(bool aAbsolute=true) const
Return the area of this chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
std::vector< INTERSECTION > INTERSECTIONS
const std::vector< VECTOR2I > & CPoints() const
Represent a set of closed polygons.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
void Move(const VECTOR2I &aVector) override
BOARD * m_board
Definition teardrop.h:270
static bool IsRound(BOARD_ITEM *aItem, PCB_LAYER_ID aLayer)
bool computeAnchorPoints(const TEARDROP_PARAMETERS &aParams, PCB_LAYER_ID aLayer, BOARD_ITEM *aItem, const VECTOR2I &aPos, std::vector< VECTOR2I > &aPts) const
Compute the 2 points on pad/via of the teardrop shape.
static int GetWidth(BOARD_ITEM *aItem, PCB_LAYER_ID aLayer)
bool computeTeardropPolygon(const TEARDROP_PARAMETERS &aParams, std::vector< VECTOR2I > &aCorners, PCB_TRACK *aTrack, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos) const
Compute all teardrop points of the polygon shape.
void computeCurvedForRectShape(const TEARDROP_PARAMETERS &aParams, std::vector< VECTOR2I > &aPoly, int aTdWidth, int aTrackHalfWidth, std::vector< VECTOR2I > &aPts, const VECTOR2I &aIntersection, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos, PCB_LAYER_ID aLayer) const
Compute the curve part points for teardrops connected to a rectangular/polygonal shape The Bezier cur...
void computeCurvedForRoundShape(const TEARDROP_PARAMETERS &aParams, std::vector< VECTOR2I > &aPoly, PCB_LAYER_ID aLayer, int aTrackHalfWidth, const VECTOR2D &aTrackDir, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos, std::vector< VECTOR2I > &aPts) const
Compute the curve part points for teardrops connected to a round shape The Bezier curve control point...
PCB_TRACK * findTouchingTrack(EDA_ITEM_FLAGS &aMatchType, PCB_TRACK *aTrackRef, const VECTOR2I &aEndPoint) const
Find a track connected to the end of another track.
TRACK_BUFFER m_trackLookupList
Definition teardrop.h:275
bool areItemsInSameZone(BOARD_ITEM *aPadOrVia, PCB_TRACK *aTrack) const
DRC_RTREE m_tracksRTree
Definition teardrop.h:274
friend class TEARDROP_PARAMETERS
Definition teardrop.h:96
bool findAnchorPointsOnTrack(const TEARDROP_PARAMETERS &aParams, VECTOR2I &aStartPoint, VECTOR2I &aEndPoint, VECTOR2I &aIntersection, PCB_TRACK *&aTrack, BOARD_ITEM *aOther, const VECTOR2I &aOtherPos, int *aEffectiveTeardropLen) const
double m_BestWidthRatio
The height of a teardrop as ratio between height and size of pad/via.
int m_TdMaxLen
max allowed length for teardrops in IU. <= 0 to disable
bool m_AllowUseTwoTracks
True to create teardrops using 2 track segments if the first in too small.
int m_TdMaxWidth
max allowed height for teardrops in IU. <= 0 to disable
double m_BestLengthRatio
The length of a teardrop as ratio between length and size of pad/via.
bool m_CurvedEdges
True if the teardrop should be curved.
int idxFromLayNet(int aLayer, int aNetcode) const
Definition teardrop.h:70
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:76
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:283
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:385
Handle a list of polygons defining a copper zone.
Definition zone.h:74
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
void BuildConvexHull(std::vector< VECTOR2I > &aResult, const std::vector< VECTOR2I > &aPoly)
Calculate the convex hull of a list of points in counter-clockwise order.
std::uint32_t EDA_ITEM_FLAGS
#define STARTPOINT
When a line is selected, these flags indicate which.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
Definition padstack.h:52
@ ROUNDRECT
Definition padstack.h:57
CITER next(CITER it)
Definition ptree.cpp:124
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.
int radius
VECTOR2I end
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:96
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694
@ NONE
Pads are not covered.
Definition zones.h:49