KiCad PCB EDA Suite
Loading...
Searching...
No Matches
item_modification_routine.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
27#include <geometry/circle.h>
28#include <geometry/oval.h>
29#include <geometry/roundrect.h>
30#include <geometry/shape_rect.h>
33#include <math.h>
34
35#include <pad.h>
36#include <pcb_track.h>
38#include <confirm.h>
39#include <board.h>
40#include <wx/log.h>
41
42namespace
43{
44
48bool SegmentsShareEndpoint( const SEG& aSegA, const SEG& aSegB )
49{
50 return ( aSegA.A == aSegB.A || aSegA.A == aSegB.B || aSegA.B == aSegB.A || aSegA.B == aSegB.B );
51}
52
53
54std::pair<VECTOR2I*, VECTOR2I*> GetSharedEndpoints( SEG& aSegA, SEG& aSegB )
55{
56 std::pair<VECTOR2I*, VECTOR2I*> result = { nullptr, nullptr };
57
58 if( aSegA.A == aSegB.A )
59 {
60 result = { &aSegA.A, &aSegB.A };
61 }
62 else if( aSegA.A == aSegB.B )
63 {
64 result = { &aSegA.A, &aSegB.B };
65 }
66 else if( aSegA.B == aSegB.A )
67 {
68 result = { &aSegA.B, &aSegB.A };
69 }
70 else if( aSegA.B == aSegB.B )
71 {
72 result = { &aSegA.B, &aSegB.B };
73 }
74
75 return result;
76}
77
78} // namespace
79
80
81bool ITEM_MODIFICATION_ROUTINE::ModifyLineOrDeleteIfZeroLength( PCB_SHAPE& aLine, const std::optional<SEG>& aSeg )
82{
83 wxASSERT_MSG( aLine.GetShape() == SHAPE_T::SEGMENT, "Can only modify segments" );
84
85 const bool removed = !aSeg.has_value() || aSeg->Length() == 0;
86
87 if( !removed )
88 {
89 // Mark modified, then change it
91 aLine.SetStart( aSeg->A );
92 aLine.SetEnd( aSeg->B );
93 }
94 else
95 {
96 // The line has become zero length - delete it
97 GetHandler().DeleteItem( aLine );
98 }
99
100 return removed;
101}
102
103
105{
106 return _( "Fillet Lines" );
107}
108
109
110std::optional<wxString> LINE_FILLET_ROUTINE::GetStatusMessage( int aSegmentCount ) const
111{
112 if( GetSuccesses() == 0 )
113 return _( "Unable to fillet the selected lines." );
114 else if( GetFailures() > 0 || (int) GetSuccesses() < aSegmentCount - 1 )
115 return _( "Some of the lines could not be filleted." );
116
117 return std::nullopt;
118}
119
120
122{
123 if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 )
124 return;
125
126 SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() );
127 SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() );
128
129 auto [a_pt, b_pt] = GetSharedEndpoints( seg_a, seg_b );
130
131 if( !a_pt || !b_pt )
132 {
133 // The lines do not share an endpoint, so we can't fillet them
134 return;
135 }
136
137 if( seg_a.Angle( seg_b ).IsHorizontal() )
138 return;
139
140 SHAPE_ARC sArc( seg_a, seg_b, m_filletRadiusIU );
141 VECTOR2I t1newPoint, t2newPoint;
142
143 auto setIfPointOnSeg =
144 []( VECTOR2I& aPointToSet, SEG aSegment, VECTOR2I aVecToTest )
145 {
146 VECTOR2I segToVec = aSegment.NearestPoint( aVecToTest ) - aVecToTest;
147
148 // Find out if we are on the segment (minimum precision)
150 {
151 aPointToSet.x = aVecToTest.x;
152 aPointToSet.y = aVecToTest.y;
153 return true;
154 }
155
156 return false;
157 };
158
159 //Do not draw a fillet if the end points of the arc are not within the track segments
160 if( !setIfPointOnSeg( t1newPoint, seg_a, sArc.GetP0() )
161 && !setIfPointOnSeg( t2newPoint, seg_b, sArc.GetP0() ) )
162 {
163 AddFailure();
164 return;
165 }
166
167 if( !setIfPointOnSeg( t1newPoint, seg_a, sArc.GetP1() )
168 && !setIfPointOnSeg( t2newPoint, seg_b, sArc.GetP1() ) )
169 {
170 AddFailure();
171 return;
172 }
173
174 auto tArc = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::ARC );
175
176 tArc->SetArcGeometry( sArc.GetP0(), sArc.GetArcMid(), sArc.GetP1() );
177
178 // Copy properties from one of the source lines
179 tArc->SetWidth( aLineA.GetWidth() );
180 tArc->SetLayer( aLineA.GetLayer() );
181 tArc->SetLocked( aLineA.IsLocked() );
182
183 CHANGE_HANDLER& handler = GetHandler();
184
185 handler.AddNewItem( std::move( tArc ) );
186
187 *a_pt = t1newPoint;
188 *b_pt = t2newPoint;
189
190 ModifyLineOrDeleteIfZeroLength( aLineA, seg_a );
191 ModifyLineOrDeleteIfZeroLength( aLineB, seg_b );
192
193 AddSuccess();
194}
195
196
198{
199 return _( "Chamfer Lines" );
200}
201
202
203std::optional<wxString> LINE_CHAMFER_ROUTINE::GetStatusMessage( int aSegmentCount ) const
204{
205 if( GetSuccesses() == 0 )
206 return _( "Unable to chamfer the selected lines." );
207 else if( GetFailures() > 0 || (int) GetSuccesses() < aSegmentCount - 1 )
208 return _( "Some of the lines could not be chamfered." );
209
210 return std::nullopt;
211}
212
213
215{
216 if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 )
217 return;
218
219 SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() );
220 SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() );
221
222 // If the segments share an endpoint, we won't try to chamfer them
223 // (we could extend to the intersection point, but this gets complicated
224 // and inconsistent when you select more than two lines)
225 if( !SegmentsShareEndpoint( seg_a, seg_b ) )
226 {
227 // not an error, lots of lines in a 2+ line selection will not intersect
228 return;
229 }
230
231 std::optional<CHAMFER_RESULT> chamfer_result = ComputeChamferPoints( seg_a, seg_b, m_chamferParams );
232
233 if( !chamfer_result )
234 {
235 AddFailure();
236 return;
237 }
238
239 std::unique_ptr<PCB_SHAPE> tSegment = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::SEGMENT );
240
241 tSegment->SetStart( chamfer_result->m_chamfer.A );
242 tSegment->SetEnd( chamfer_result->m_chamfer.B );
243
244 // Copy properties from one of the source lines
245 tSegment->SetWidth( aLineA.GetWidth() );
246 tSegment->SetLayer( aLineA.GetLayer() );
247 tSegment->SetLocked( aLineA.IsLocked() );
248
249 CHANGE_HANDLER& handler = GetHandler();
250
251 handler.AddNewItem( std::move( tSegment ) );
252
253 ModifyLineOrDeleteIfZeroLength( aLineA, chamfer_result->m_updated_seg_a );
254 ModifyLineOrDeleteIfZeroLength( aLineB, chamfer_result->m_updated_seg_b );
255
256 AddSuccess();
257}
258
259
261{
262 return _( "Dogbone Corners" );
263}
264
265
266std::optional<wxString> DOGBONE_CORNER_ROUTINE::GetStatusMessage( int aSegmentCount ) const
267{
268 wxString msg;
269
270 if( GetSuccesses() == 0 )
271 msg += _( "Unable to add dogbone corners to the selected lines." );
272 else if( GetFailures() > 0 || (int) GetSuccesses() < aSegmentCount - 1 )
273 msg += _( "Some of the lines could not have dogbone corners added." );
274
276 {
277 if( !msg.empty() )
278 msg += " ";
279
280 msg += _( "Some of the dogbone corners are too narrow to fit a cutter of the specified radius." );
281
282 if( !m_params.AddSlots )
283 msg += _( " Consider enabling the 'Add Slots' option." );
284 else
285 msg += _( " Slots were added." );
286 }
287
288 if( msg.empty() )
289 return std::nullopt;
290
291 return msg;
292}
293
294
296{
297 if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 )
298 {
299 wxLogTrace( "DOGBONE", "Skip: zero-length line(s) (A len=%f, B len=%f)",
300 aLineA.GetLength(), aLineB.GetLength() );
301 return;
302 }
303
304 if( !EnsureBoardOutline() )
305 {
306 wxLogTrace( "DOGBONE", "Skip: board outline unavailable" );
307 return;
308 }
309
310 SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() );
311 SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() );
312
313 auto [a_pt, b_pt] = GetSharedEndpoints( seg_a, seg_b );
314
315 if( !a_pt || !b_pt )
316 {
317 wxLogTrace( "DOGBONE", "Skip: segments do not share endpoint" );
318 return;
319 }
320
321 // Cannot handle parallel lines
322 if( seg_a.Angle( seg_b ).IsHorizontal() )
323 {
324 wxLogTrace( "DOGBONE", "Skip: parallel segments" );
325 AddFailure();
326 return;
327 }
328
329 // Determine if this corner points into the board outline: we construct the bisector
330 // vector (as done in ComputeDogbone) and test a point a small distance in the opposite
331 // direction (i.e. exterior). If that opposite point is INSIDE the board outline, the
332 // corner indentation points inward (needs dogbone). If the opposite test point is
333 // outside, skip.
334
335 const VECTOR2I corner = *a_pt; // shared endpoint
336 // Build vectors from corner toward other ends
337 const VECTOR2I vecA = ( seg_a.A == corner ? seg_a.B - corner : seg_a.A - corner );
338 const VECTOR2I vecB = ( seg_b.A == corner ? seg_b.B - corner : seg_b.A - corner );
339 // Normalize (resize) to common length to form bisector reliably
340 int maxLen = std::max( vecA.EuclideanNorm(), vecB.EuclideanNorm() );
341
342 if( maxLen == 0 )
343 {
344 wxLogTrace( "DOGBONE", "Skip: degenerate corner (maxLen==0)" );
345 return;
346 }
347
348 VECTOR2I vecAn = vecA.Resize( maxLen );
349 VECTOR2I vecBn = vecB.Resize( maxLen );
350 VECTOR2I bisectorOutward = vecAn + vecBn; // direction inside angle region
351
352 // If vectors are nearly opposite, no meaningful dogbone
353 if( bisectorOutward.EuclideanNorm() == 0 )
354 {
355 wxLogTrace( "DOGBONE", "Skip: bisector zero (vectors opposite)" );
356 return;
357 }
358
359 // Opposite direction of bisector (points "outside" if angle is convex, or further into
360 // material if reflex). We'll sample a point a small distance along -bisectorOutward.
361 VECTOR2I sampleDir = ( -bisectorOutward ).Resize( std::min( 1000, m_params.DogboneRadiusIU ) );
362 VECTOR2I samplePoint = corner + sampleDir; // test point opposite bisector
363
364 bool oppositeInside = m_boardOutline.Contains( samplePoint );
365
366 if( !oppositeInside )
367 {
368 wxLogTrace( "DOGBONE", "Skip: corner not inward (sample outside polygon)" );
369 return;
370 }
371
372 std::optional<DOGBONE_RESULT> dogbone_result = ComputeDogbone( seg_a, seg_b, m_params.DogboneRadiusIU,
373 m_params.AddSlots );
374
375 if( !dogbone_result )
376 {
377 wxLogTrace( "DOGBONE", "Skip: ComputeDogbone failed (radius=%d slots=%d)",
378 m_params.DogboneRadiusIU, (int) m_params.AddSlots );
379 AddFailure();
380 return;
381 }
382
383 if( dogbone_result->m_small_arc_mouth )
384 {
385 wxLogTrace( "DOGBONE", "Info: small arc mouth (slots %s)", m_params.AddSlots ? "enabled" : "disabled" );
386 // The arc is too small to fit the radius
387 m_haveNarrowMouths = true;
388 }
389
390 CHANGE_HANDLER& handler = GetHandler();
391
392 auto tArc = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::ARC );
393
394 const auto copyProps = [&]( PCB_SHAPE& aShape )
395 {
396 aShape.SetWidth( aLineA.GetWidth() );
397 aShape.SetLayer( aLineA.GetLayer() );
398 aShape.SetLocked( aLineA.IsLocked() );
399 };
400
401 const auto addSegment =
402 [&]( const SEG& aSeg )
403 {
404 if( aSeg.Length() == 0 )
405 return;
406
407 auto tSegment = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::SEGMENT );
408 tSegment->SetStart( aSeg.A );
409 tSegment->SetEnd( aSeg.B );
410
411 copyProps( *tSegment );
412 handler.AddNewItem( std::move( tSegment ) );
413 };
414
415 tArc->SetArcGeometry( dogbone_result->m_arc.GetP0(), dogbone_result->m_arc.GetArcMid(),
416 dogbone_result->m_arc.GetP1() );
417
418 // Copy properties from one of the source lines
419 copyProps( *tArc );
420
421 addSegment( SEG{ dogbone_result->m_arc.GetP0(), dogbone_result->m_updated_seg_a->B } );
422 addSegment( SEG{ dogbone_result->m_arc.GetP1(), dogbone_result->m_updated_seg_b->B } );
423
424 handler.AddNewItem( std::move( tArc ) );
425
426 ModifyLineOrDeleteIfZeroLength( aLineA, dogbone_result->m_updated_seg_a );
427 ModifyLineOrDeleteIfZeroLength( aLineB, dogbone_result->m_updated_seg_b );
428
429 wxLogTrace( "DOGBONE", "Success: dogbone added at (%d,%d)", corner.x, corner.y );
430 AddSuccess();
431}
432
434{
436 return !m_boardOutline.IsEmpty();
437
439
440 BOARD* board = dynamic_cast<BOARD*>( GetBoard() );
441
442 if( !board )
443 {
444 wxLogTrace( "DOGBONE", "EnsureBoardOutline: board cast failed" );
445 return false;
446 }
447
448 // Build outlines; ignore errors and arcs for this classification
449 if( !board->GetBoardPolygonOutlines( m_boardOutline, false ) )
450 {
451 wxLogTrace( "DOGBONE", "EnsureBoardOutline: GetBoardPolygonOutlines failed" );
452 return false;
453 }
454
455 bool ok = !m_boardOutline.IsEmpty();
456 wxLogTrace( "DOGBONE", "EnsureBoardOutline: outline %s", ok ? "ready" : "empty" );
457 return ok;
458}
459
460
462{
463 return _( "Extend Lines to Meet" );
464}
465
466
467std::optional<wxString> LINE_EXTENSION_ROUTINE::GetStatusMessage( int aSegmentCount ) const
468{
469 if( GetSuccesses() == 0 )
470 return _( "Unable to extend the selected lines to meet." );
471 else if( GetFailures() > 0 || (int) GetSuccesses() < aSegmentCount - 1 )
472 return _( "Some of the lines could not be extended to meet." );
473
474 return std::nullopt;
475}
476
477
479{
480 if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 )
481 return;
482
483 SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() );
484 SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() );
485
486 if( seg_a.Intersects( seg_b ) )
487 {
488 // already intersecting, nothing to do
489 return;
490 }
491
492 OPT_VECTOR2I intersection = seg_a.IntersectLines( seg_b );
493
494 if( !intersection )
495 {
496 // This might be an error, but it's also possible that the lines are
497 // parallel and don't intersect. We'll just ignore this case.
498 return;
499 }
500
501 CHANGE_HANDLER& handler = GetHandler();
502
503 const auto line_extender =
504 [&]( const SEG& aSeg, PCB_SHAPE& aLine )
505 {
506 // If the intersection point is not already n the line, we'll extend to it
507 if( !aSeg.Contains( *intersection ) )
508 {
509 const int dist_start = ( *intersection - aSeg.A ).EuclideanNorm();
510 const int dist_end = ( *intersection - aSeg.B ).EuclideanNorm();
511
512 const VECTOR2I& furthest_pt = ( dist_start < dist_end ) ? aSeg.B : aSeg.A;
513 // Note, the drawing tool has COORDS_PADDING of 20mm, but we need a larger buffer
514 // or we are not able to select the generated segments
515 unsigned int edge_padding = static_cast<unsigned>( pcbIUScale.mmToIU( 200 ) );
516 VECTOR2I new_end = GetClampedCoords( *intersection, edge_padding );
517
518 handler.MarkItemModified( aLine );
519 aLine.SetStart( furthest_pt );
520 aLine.SetEnd( new_end );
521 }
522 };
523
524 line_extender( seg_a, aLineA );
525 line_extender( seg_b, aLineB );
526
527 AddSuccess();
528}
529
530
532{
533 std::unique_ptr<SHAPE_POLY_SET> poly;
534
535 switch( aPcbShape.GetShape() )
536 {
537 case SHAPE_T::POLY:
538 poly = std::make_unique<SHAPE_POLY_SET>( aPcbShape.GetPolyShape() );
539 // Arcs cannot be handled by polygon boolean transforms
540 poly->ClearArcs();
541 break;
542
544 {
545 poly = std::make_unique<SHAPE_POLY_SET>();
546
547 const std::vector<VECTOR2I> rect_pts = aPcbShape.GetRectCorners();
548
549 poly->NewOutline();
550
551 for( const VECTOR2I& pt : rect_pts )
552 poly->Append( pt );
553
554 break;
555 }
556
557 case SHAPE_T::CIRCLE:
558 {
559 poly = std::make_unique<SHAPE_POLY_SET>();
560 const SHAPE_ARC arc{ aPcbShape.GetCenter(), aPcbShape.GetCenter() + VECTOR2I{ aPcbShape.GetRadius(), 0 },
561 FULL_CIRCLE, 0 };
562
563 poly->NewOutline();
564 poly->Append( arc );
565 break;
566 }
567
568 default:
569 break;
570 }
571
572 if( !poly )
573 {
574 // Not a polygon or rectangle, nothing to do
575 return;
576 }
577
578 if( m_firstPolygon )
579 {
580 m_width = aPcbShape.GetWidth();
581 m_layer = aPcbShape.GetLayer();
582 m_fillMode = aPcbShape.GetFillMode();
583 m_workingPolygons = std::move( *poly );
584 m_firstPolygon = false;
585
586 // Boolean ops work, but assert on arcs
587 m_workingPolygons.ClearArcs();
588
589 GetHandler().DeleteItem( aPcbShape );
590 }
591 else
592 {
593 if( ProcessSubsequentPolygon( *poly ) )
594 {
595 // If we could process the polygon, delete the source
596 GetHandler().DeleteItem( aPcbShape );
597 AddSuccess();
598 }
599 else
600 {
601 AddFailure();
602 }
603 }
604}
605
606
608{
609 if( m_workingPolygons.OutlineCount() == 0 || m_firstPolygon )
610 {
611 // Nothing to do (no polygons handled or nothing left?)
612 return;
613 }
614
615 CHANGE_HANDLER& handler = GetHandler();
616
617 // If we have disjoint polygons, we'll fix that now and create
618 // new PCB_SHAPEs for each outline
619 for( int i = 0; i < m_workingPolygons.OutlineCount(); ++i )
620 {
621 // If we handled any polygons to get any outline,
622 // there must be a layer set by now.
623 wxASSERT( m_layer >= 0 );
624
625 std::unique_ptr<PCB_SHAPE> new_poly_shape = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::POLY );
626
627 SHAPE_POLY_SET poly_set = m_workingPolygons.UnitSet( i );
628
629 new_poly_shape->SetPolyShape( poly_set );
630
631 // Copy properties from the source polygon
632 new_poly_shape->SetWidth( m_width );
633 new_poly_shape->SetLayer( m_layer );
634 new_poly_shape->SetFillMode( m_fillMode );
635
636 handler.AddNewItem( std::move( new_poly_shape ) );
637 }
638}
639
640
642{
643 return _( "Merge Polygons" );
644}
645
646
647std::optional<wxString> POLYGON_MERGE_ROUTINE::GetStatusMessage() const
648{
649 if( GetSuccesses() == 0 )
650 return _( "Unable to merge the selected polygons." );
651 else if( GetFailures() > 0 )
652 return _( "Some of the polygons could not be merged." );
653
654 return std::nullopt;
655}
656
657
659{
660 SHAPE_POLY_SET no_arcs_poly = aPolygon;
661 no_arcs_poly.ClearArcs();
662
663 GetWorkingPolygons().BooleanAdd( no_arcs_poly );
664 return true;
665}
666
667
669{
670 return _( "Subtract Polygons" );
671}
672
673
674std::optional<wxString> POLYGON_SUBTRACT_ROUTINE::GetStatusMessage() const
675{
676 if( GetSuccesses() == 0 )
677 return _( "Unable to subtract the selected polygons." );
678 else if( GetFailures() > 0 )
679 return _( "Some of the polygons could not be subtracted." );
680
681 return std::nullopt;
682}
683
684
686{
687 SHAPE_POLY_SET& working_polygons = GetWorkingPolygons();
688 SHAPE_POLY_SET working_copy = working_polygons;
689
690 SHAPE_POLY_SET no_arcs_poly = aPolygon;
691 no_arcs_poly.ClearArcs();
692
693 working_copy.BooleanSubtract( no_arcs_poly );
694
695 working_polygons = std::move( working_copy );
696 return true;
697}
698
699
701{
702 return _( "Intersect Polygons" );
703}
704
705
706std::optional<wxString> POLYGON_INTERSECT_ROUTINE::GetStatusMessage() const
707{
708 if( GetSuccesses() == 0 )
709 return _( "Unable to intersect the selected polygons." );
710 else if( GetFailures() > 0 )
711 return _( "Some of the polygons could not be intersected." );
712
713 return std::nullopt;
714}
715
716
718{
719 SHAPE_POLY_SET& working_polygons = GetWorkingPolygons();
720 SHAPE_POLY_SET working_copy = working_polygons;
721
722 SHAPE_POLY_SET no_arcs_poly = aPolygon;
723 no_arcs_poly.ClearArcs();
724
725 working_copy.BooleanIntersection( no_arcs_poly );
726
727 // Is there anything left?
728 if( working_copy.OutlineCount() == 0 )
729 {
730 // There was no intersection. Rather than deleting the working polygon, we'll skip
731 // and report a failure.
732 return false;
733 }
734
735 working_polygons = std::move( working_copy );
736 return true;
737}
738
739
741{
742 return _( "Outset Items" );
743}
744
745std::optional<wxString> OUTSET_ROUTINE::GetStatusMessage() const
746{
747 if( GetSuccesses() == 0 )
748 return _( "Unable to outset the selected items." );
749 else if( GetFailures() > 0 )
750 return _( "Some of the items could not be outset." );
751
752 return std::nullopt;
753}
754
755
756static SHAPE_RECT GetRectRoundedToGridOutwards( const SHAPE_RECT& aRect, int aGridSize )
757{
758 const VECTOR2I newPos = KIGEOM::RoundNW( aRect.GetPosition(), aGridSize );
759 const VECTOR2I newOpposite = KIGEOM::RoundSE( aRect.GetPosition() + aRect.GetSize(), aGridSize );
760 return SHAPE_RECT( newPos, newOpposite );
761}
762
763
765{
766 /*
767 * This attempts to do exact outsetting, rather than punting to Clipper.
768 * So it can't do all shapes, but it can do the most obvious ones, which are probably
769 * the ones you want to outset anyway, most usually when making a courtyard for a footprint.
770 */
771
772 PCB_LAYER_ID layer = m_params.useSourceLayers ? aItem.GetLayer() : m_params.layer;
773
774 // Not all items have a width, even if the parameters want to copy it
775 // So fall back to the given width if we can't get one.
776 int width = m_params.lineWidth;
777
778 if( m_params.useSourceWidths )
779 {
780 std::optional<int> item_width = GetBoardItemWidth( aItem );
781
782 if( item_width.has_value() )
783 width = *item_width;
784 }
785
786 CHANGE_HANDLER& handler = GetHandler();
787
788 const auto addPolygonalChain =
789 [&]( const SHAPE_LINE_CHAIN& aChain )
790 {
791 SHAPE_POLY_SET new_poly( aChain );
792
793 std::unique_ptr<PCB_SHAPE> new_shape = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::POLY );
794
795 new_shape->SetPolyShape( new_poly );
796 new_shape->SetLayer( layer );
797 new_shape->SetWidth( width );
798
799 handler.AddNewItem( std::move( new_shape ) );
800 };
801
802 // Iterate the SHAPE_LINE_CHAIN in the polygon, pulling out
803 // segments and arcs to create new PCB_SHAPE primitives.
804 const auto addChain =
805 [&]( const SHAPE_LINE_CHAIN& aChain )
806 {
807 // Prefer to add a polygonal chain if there are no arcs
808 // as this permits boolean ops
809 if( aChain.ArcCount() == 0 )
810 {
811 addPolygonalChain( aChain );
812 return;
813 }
814
815 for( size_t si = 0; si < aChain.GetSegmentCount(); ++si )
816 {
817 const SEG seg = aChain.GetSegment( si );
818
819 if( seg.Length() == 0 )
820 continue;
821
822 if( aChain.IsArcSegment( si ) )
823 continue;
824
825 std::unique_ptr<PCB_SHAPE> new_shape = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::SEGMENT );
826 new_shape->SetStart( seg.A );
827 new_shape->SetEnd( seg.B );
828 new_shape->SetLayer( layer );
829 new_shape->SetWidth( width );
830
831 handler.AddNewItem( std::move( new_shape ) );
832 }
833
834 for( size_t ai = 0; ai < aChain.ArcCount(); ++ai )
835 {
836 const SHAPE_ARC& arc = aChain.Arc( ai );
837
838 if( arc.GetRadius() == 0 || arc.GetP0() == arc.GetP1() )
839 continue;
840
841 std::unique_ptr<PCB_SHAPE> new_shape = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::ARC );
842 new_shape->SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() );
843 new_shape->SetLayer( layer );
844 new_shape->SetWidth( width );
845
846 handler.AddNewItem( std::move( new_shape ) );
847 }
848 };
849
850 const auto addPoly =
851 [&]( const SHAPE_POLY_SET& aPoly )
852 {
853 for( int oi = 0; oi < aPoly.OutlineCount(); ++oi )
854 {
855 addChain( aPoly.Outline( oi ) );
856 }
857 };
858
859 const auto addRect =
860 [&]( const SHAPE_RECT& aRect )
861 {
862 std::unique_ptr<PCB_SHAPE> new_shape = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::RECTANGLE );
863
864 if( !m_params.gridRounding.has_value() )
865 {
866 new_shape->SetPosition( aRect.GetPosition() );
867 new_shape->SetRectangleWidth( aRect.GetWidth() );
868 new_shape->SetRectangleHeight( aRect.GetHeight() );
869 }
870 else
871 {
872 const SHAPE_RECT grid_rect = GetRectRoundedToGridOutwards( aRect, *m_params.gridRounding );
873 new_shape->SetPosition( grid_rect.GetPosition() );
874 new_shape->SetRectangleWidth( grid_rect.GetWidth() );
875 new_shape->SetRectangleHeight( grid_rect.GetHeight() );
876 }
877
878 new_shape->SetLayer( layer );
879 new_shape->SetWidth( width );
880
881 handler.AddNewItem( std::move( new_shape ) );
882 };
883
884 const auto addCircle =
885 [&]( const CIRCLE& aCircle )
886 {
887 std::unique_ptr<PCB_SHAPE> new_shape = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::CIRCLE );
888 new_shape->SetCenter( aCircle.Center );
889 new_shape->SetRadius( aCircle.Radius );
890 new_shape->SetLayer( layer );
891 new_shape->SetWidth( width );
892
893 handler.AddNewItem( std::move( new_shape ) );
894 };
895
896 const auto addCircleOrRect =
897 [&]( const CIRCLE& aCircle )
898 {
899 if( m_params.roundCorners )
900 {
901 addCircle( aCircle );
902 }
903 else
904 {
905 const VECTOR2I rVec{ aCircle.Radius, aCircle.Radius };
906 const SHAPE_RECT rect{ aCircle.Center - rVec, aCircle.Center + rVec };
907 addRect( rect );
908 }
909 };
910
911 switch( aItem.Type() )
912 {
913 case PCB_PAD_T:
914 {
915 const PAD& pad = static_cast<const PAD&>( aItem );
916
917 // TODO(JE) padstacks
918 const PAD_SHAPE pad_shape = pad.GetShape( PADSTACK::ALL_LAYERS );
919
920 switch( pad_shape )
921 {
924 case PAD_SHAPE::OVAL:
925 {
926 const VECTOR2I pad_size = pad.GetSize( PADSTACK::ALL_LAYERS );
927
928 BOX2I box{ pad.GetPosition() - pad_size / 2, pad_size };
929 box.Inflate( m_params.outsetDistance );
930
931 if( box.GetWidth() <= 0 || box.GetHeight() <= 0 )
932 {
933 AddFailure();
934 break;
935 }
936
937 int radius = m_params.outsetDistance;
938
939 if( pad_shape == PAD_SHAPE::ROUNDRECT )
940 radius += pad.GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS );
941 else if( pad_shape == PAD_SHAPE::OVAL )
942 radius += std::min( pad_size.x, pad_size.y ) / 2;
943
944 radius = m_params.roundCorners ? std::max( radius, 0 ) : 0;
945
946 // No point doing a SHAPE_RECT as we may need to rotate it
947 ROUNDRECT rrect( box, radius );
948 SHAPE_POLY_SET poly;
949 rrect.TransformToPolygon( poly, pad.GetMaxError() );
950
951 poly.Rotate( pad.GetOrientation(), pad.GetPosition() );
952 addPoly( poly );
953 AddSuccess();
954 break;
955 }
956
958 {
959 const int radius = pad.GetSize( PADSTACK::ALL_LAYERS ).x / 2 + m_params.outsetDistance;
960
961 if( radius <= 0 )
962 {
963 AddFailure();
964 break;
965 }
966
967 const CIRCLE circle( pad.GetPosition(), radius );
968 addCircleOrRect( circle );
969 AddSuccess();
970 break;
971 }
972
974 // Not handled yet, but could use a generic convex polygon outset method.
975 break;
976
977 default:
978 // Other pad shapes are not supported with exact outsets
979 break;
980 }
981 break;
982 }
983 case PCB_SHAPE_T:
984 {
985 const PCB_SHAPE& pcb_shape = static_cast<const PCB_SHAPE&>( aItem );
986
987 switch( pcb_shape.GetShape() )
988 {
990 {
991 BOX2I box{ pcb_shape.GetPosition(),
992 VECTOR2I{ pcb_shape.GetRectangleWidth(), pcb_shape.GetRectangleHeight() } };
993 box.Inflate( m_params.outsetDistance );
994
995 if( box.GetWidth() <= 0 || box.GetHeight() <= 0 )
996 {
997 AddFailure();
998 break;
999 }
1000
1001 box.Normalize();
1002
1003 SHAPE_RECT rect( box );
1004 int cornerRadius = pcb_shape.GetCornerRadius();
1005
1006 if( m_params.roundCorners )
1007 cornerRadius = std::max( cornerRadius + m_params.outsetDistance, 0 );
1008
1009 if( m_params.gridRounding.has_value() )
1010 rect = GetRectRoundedToGridOutwards( rect, *m_params.gridRounding );
1011
1012 if( cornerRadius > 0 )
1013 {
1014 ROUNDRECT rrect( rect, cornerRadius );
1015 SHAPE_POLY_SET poly;
1016 rrect.TransformToPolygon( poly, pcb_shape.GetMaxError() );
1017 addChain( poly.Outline( 0 ) );
1018 }
1019 else
1020 {
1021 addRect( rect );
1022 }
1023
1024 AddSuccess();
1025 break;
1026 }
1027
1028 case SHAPE_T::CIRCLE:
1029 {
1030 const int newRadius = pcb_shape.GetRadius() + m_params.outsetDistance;
1031
1032 if( newRadius <= 0 )
1033 {
1034 AddFailure();
1035 break;
1036 }
1037
1038 const CIRCLE circle( pcb_shape.GetCenter(), newRadius );
1039 addCircleOrRect( circle );
1040 AddSuccess();
1041 break;
1042 }
1043
1044 case SHAPE_T::SEGMENT:
1045 {
1046 if( m_params.outsetDistance <= 0 )
1047 {
1048 AddFailure();
1049 break;
1050 }
1051
1052 // For now just make the whole stadium shape and let the user delete the unwanted bits
1053 const SEG seg( pcb_shape.GetStart(), pcb_shape.GetEnd() );
1054
1055 if( m_params.roundCorners )
1056 {
1057 const SHAPE_SEGMENT oval( seg, m_params.outsetDistance * 2 );
1058 addChain( KIGEOM::ConvertToChain( oval ) );
1059 }
1060 else
1061 {
1063 const VECTOR2I ext = ( seg.B - seg.A ).Resize( m_params.outsetDistance );
1064 const VECTOR2I perp = GetRotated( ext, ANGLE_90 );
1065
1066 chain.Append( seg.A - ext + perp );
1067 chain.Append( seg.A - ext - perp );
1068 chain.Append( seg.B + ext - perp );
1069 chain.Append( seg.B + ext + perp );
1070 chain.SetClosed( true );
1071 addChain( chain );
1072 }
1073
1074 AddSuccess();
1075 break;
1076 }
1077
1078 case SHAPE_T::ARC:
1079 // Not 100% sure what a sensible non-round outset of an arc is!
1080 // (not sure it's that important in practice)
1081
1082 // Gets rather complicated if this isn't true
1083 if( pcb_shape.GetRadius() >= m_params.outsetDistance )
1084 {
1085 // Again, include the endcaps and let the user delete the unwanted bits
1086 const SHAPE_ARC arc{ pcb_shape.GetCenter(), pcb_shape.GetStart(),
1087 pcb_shape.GetArcAngle(), 0 };
1088
1089 const VECTOR2I startNorm =
1090 VECTOR2I( arc.GetP0() - arc.GetCenter() ).Resize( m_params.outsetDistance );
1091
1092 const SHAPE_ARC inner{ arc.GetCenter(), arc.GetP0() - startNorm,
1093 arc.GetCentralAngle(), 0 };
1094 const SHAPE_ARC outer{ arc.GetCenter(), arc.GetP0() + startNorm,
1095 arc.GetCentralAngle(), 0 };
1096
1098 chain.Append( outer );
1099 // End cap at the P1 end
1100 chain.Append( SHAPE_ARC{ arc.GetP1(), outer.GetP1(), ANGLE_180 } );
1101
1102 if( inner.GetRadius() > 0 )
1103 {
1104 chain.Append( inner.Reversed() );
1105 }
1106
1107 // End cap at the P0 end back to the start
1108 chain.Append( SHAPE_ARC{ arc.GetP0(), inner.GetP0(), ANGLE_180 } );
1109 addChain( chain );
1110 AddSuccess();
1111 }
1112
1113 break;
1114
1115 default:
1116 // Other shapes are not supported with exact outsets
1117 // (convex) POLY shouldn't be too traumatic and it would bring trapezoids for free.
1118 break;
1119 }
1120
1121 break;
1122 }
1123
1124 default:
1125 // Other item types are not supported with exact outsets
1126 break;
1127 }
1128
1129 // It would be nice if we could differentiate which items went with which in the mixed success/failure
1130 // case, but since we can't it's better to err on the side of safety.
1131 if( m_params.deleteSourceItems && GetSuccesses() > 0 && GetFailures() == 0 )
1132 handler.DeleteItem( aItem );
1133}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:237
bool IsLocked() const override
int GetMaxError() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
bool GetBoardPolygonOutlines(SHAPE_POLY_SET &aOutlines, bool aInferOutlineIfNecessary, OUTLINE_ERROR_HANDLER *aErrorHandler=nullptr, bool aAllowUseArcsInPolygons=false, bool aIncludeNPTHAsOutlines=false)
Extract the board outlines and build a closed polygon from lines, arcs and circle items on edge cut l...
Definition board.cpp:3049
Represent basic circle geometry with utility geometry functions.
Definition circle.h:33
SHAPE_POLY_SET m_boardOutline
Cached board outline polygons.
std::optional< wxString > GetStatusMessage(int aSegmentCount) const override
Get a status message to show when the routine is complete.
wxString GetCommitDescription() const override
void ProcessLinePair(PCB_SHAPE &aLineA, PCB_SHAPE &aLineB) override
Perform the action on the pair of lines given.
bool IsHorizontal() const
Definition eda_angle.h:142
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:111
EDA_ANGLE GetArcAngle() const
FILL_T GetFillMode() const
Definition eda_shape.h:142
int GetRectangleWidth() const
SHAPE_POLY_SET & GetPolyShape()
Definition eda_shape.h:337
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:169
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:216
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:178
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:174
std::vector< VECTOR2I > GetRectCorners() const
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:220
double GetLength() const
int GetRectangleHeight() const
int GetCornerRadius() const
virtual void MarkItemModified(BOARD_ITEM &aItem)=0
Report that the tool has modified an item on the board.
virtual void DeleteItem(BOARD_ITEM &aItem)=0
Report that the tool has deleted an item on the board.
virtual void AddNewItem(std::unique_ptr< BOARD_ITEM > aItem)=0
Report that the tools wants to add a new item to the board.
void AddFailure()
Mark that one of the actions failed.
void AddSuccess()
Mark that one of the actions succeeded.
bool ModifyLineOrDeleteIfZeroLength(PCB_SHAPE &aItem, const std::optional< SEG > &aSeg)
Helper function useful for multiple tools: modify a line or delete it if it has zero length.
BOARD_ITEM * GetBoard() const
The BOARD used when creating new shapes.
CHANGE_HANDLER & GetHandler()
Access the handler for making changes to the board.
std::optional< wxString > GetStatusMessage(int aSegmentCount) const override
Get a status message to show when the routine is complete.
wxString GetCommitDescription() const override
void ProcessLinePair(PCB_SHAPE &aLineA, PCB_SHAPE &aLineB) override
Perform the action on the pair of lines given.
const CHAMFER_PARAMS m_chamferParams
wxString GetCommitDescription() const override
std::optional< wxString > GetStatusMessage(int aSegmentCount) const override
Get a status message to show when the routine is complete.
void ProcessLinePair(PCB_SHAPE &aLineA, PCB_SHAPE &aLineB) override
Perform the action on the pair of lines given.
std::optional< wxString > GetStatusMessage(int aSegmentCount) const override
Get a status message to show when the routine is complete.
wxString GetCommitDescription() const override
void ProcessLinePair(PCB_SHAPE &aLineA, PCB_SHAPE &aLineB) override
Perform the action on the pair of lines given.
void ProcessItem(BOARD_ITEM &aItem)
wxString GetCommitDescription() const override
std::optional< wxString > GetStatusMessage() const
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:55
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:81
int GetWidth() const override
VECTOR2I GetPosition() const override
Definition pcb_shape.h:79
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition pcb_shape.h:71
SHAPE_POLY_SET m_workingPolygons
This can be disjoint, which will be fixed at the end.
void Finalize()
Clear up any outstanding work.
void ProcessShape(PCB_SHAPE &aPcbShape)
virtual bool ProcessSubsequentPolygon(const SHAPE_POLY_SET &aPolygon)=0
std::optional< wxString > GetStatusMessage() const override
Get a status message to show when the routine is complete.
wxString GetCommitDescription() const override
bool ProcessSubsequentPolygon(const SHAPE_POLY_SET &aPolygon) override
bool ProcessSubsequentPolygon(const SHAPE_POLY_SET &aPolygon) override
wxString GetCommitDescription() const override
std::optional< wxString > GetStatusMessage() const override
Get a status message to show when the routine is complete.
wxString GetCommitDescription() const override
std::optional< wxString > GetStatusMessage() const override
Get a status message to show when the routine is complete.
bool ProcessSubsequentPolygon(const SHAPE_POLY_SET &aPolygon) override
A round rectangle shape, based on a rectangle and a radius.
Definition roundrect.h:36
void TransformToPolygon(SHAPE_POLY_SET &aBuffer, int aMaxError) const
Get the polygonal representation of the roundrect.
Definition roundrect.cpp:83
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I B
Definition seg.h:50
bool Intersects(const SEG &aSeg) const
Definition seg.cpp:440
int Length() const
Return the length (this).
Definition seg.h:343
OPT_VECTOR2I IntersectLines(const SEG &aSeg) const
Compute the intersection point of lines passing through ends of (this) and aSeg.
Definition seg.h:220
bool Contains(const SEG &aSeg) const
Definition seg.h:324
EDA_ANGLE Angle(const SEG &aOther) const
Determine the smallest angle between two segments.
Definition seg.cpp:111
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:120
const VECTOR2I & GetP1() const
Definition shape_arc.h:119
double GetRadius() const
const VECTOR2I & GetP0() const
Definition shape_arc.h:118
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
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.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int OutlineCount() const
Return the number of outlines in the set.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
int GetWidth() const override
Definition shape_rect.h:185
const VECTOR2I & GetPosition() const
Definition shape_rect.h:169
const VECTOR2I GetSize() const
Definition shape_rect.h:177
int GetHeight() const
Definition shape_rect.h:193
static const int MIN_PRECISION_IU
This is the minimum precision for all the points in a shape.
Definition shape.h:131
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
This file is part of the common library.
std::optional< CHAMFER_RESULT > ComputeChamferPoints(const SEG &aSegA, const SEG &aSegB, const CHAMFER_PARAMS &aChamferParams)
Compute the chamfer points for a given line pair and chamfer parameters.
std::optional< DOGBONE_RESULT > ComputeDogbone(const SEG &aSegA, const SEG &aSegB, int aDogboneRadius, bool aAddSlots)
Compute the dogbone geometry for a given line pair and dogbone parameters.
#define _(s)
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
static constexpr EDA_ANGLE FULL_CIRCLE
Definition eda_angle.h:409
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:415
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
a few functions useful in geometry calculations.
VECTOR2< ret_type > GetClampedCoords(const VECTOR2< in_type > &aCoords, pad_type aPadding=1u)
Clamps a vector to values that can be negated, respecting numeric limits of coordinates data type wit...
static SHAPE_RECT GetRectRoundedToGridOutwards(const SHAPE_RECT &aRect, int aGridSize)
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
SHAPE_LINE_CHAIN ConvertToChain(const SHAPE_SEGMENT &aOval)
Definition oval.cpp:35
VECTOR2I RoundNW(const VECTOR2I &aVec, int aGridSize)
Round a vector to the nearest grid point in the NW direction.
VECTOR2I RoundSE(const VECTOR2I &aVec, int aGridSize)
Round a vector to the nearest grid point in the SE direction.
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
Definition padstack.h:52
@ ROUNDRECT
Definition padstack.h:57
@ TRAPEZOID
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:54
std::optional< int > GetBoardItemWidth(const BOARD_ITEM &aItem)
Gets the width of a BOARD_ITEM, for items that have a meaningful width.
Utility functions that can be shared by PCB tools.
static bool addSegment(VRML_LAYER &model, IDF_SEGMENT *seg, int icont, int iseg)
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:39
Utility functions for working with shapes.
const SHAPE_LINE_CHAIN chain
int radius
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
VECTOR2I GetRotated(const VECTOR2I &aVector, const EDA_ANGLE &aAngle)
Return a new VECTOR2I that is the result of rotating aVector by aAngle.
Definition trigo.h:77
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:88
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
Supplemental functions for working with vectors and simple objects that interact with vectors.