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 (C) 2023 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>
32
33#include <pad.h>
34#include <pcb_track.h>
36
37namespace
38{
39
43bool SegmentsShareEndpoint( const SEG& aSegA, const SEG& aSegB )
44{
45 return ( aSegA.A == aSegB.A || aSegA.A == aSegB.B || aSegA.B == aSegB.A || aSegA.B == aSegB.B );
46}
47
48
49std::pair<VECTOR2I*, VECTOR2I*> GetSharedEndpoints( SEG& aSegA, SEG& aSegB )
50{
51 std::pair<VECTOR2I*, VECTOR2I*> result = { nullptr, nullptr };
52
53 if( aSegA.A == aSegB.A )
54 {
55 result = { &aSegA.A, &aSegB.A };
56 }
57 else if( aSegA.A == aSegB.B )
58 {
59 result = { &aSegA.A, &aSegB.B };
60 }
61 else if( aSegA.B == aSegB.A )
62 {
63 result = { &aSegA.B, &aSegB.A };
64 }
65 else if( aSegA.B == aSegB.B )
66 {
67 result = { &aSegA.B, &aSegB.B };
68 }
69
70 return result;
71}
72
73} // namespace
74
75
77 const std::optional<SEG>& aSeg )
78{
79 wxASSERT_MSG( aLine.GetShape() == SHAPE_T::SEGMENT, "Can only modify segments" );
80
81 const bool removed = !aSeg.has_value() || aSeg->Length() == 0;
82
83 if( !removed )
84 {
85 // Mark modified, then change it
87 aLine.SetStart( aSeg->A );
88 aLine.SetEnd( aSeg->B );
89 }
90 else
91 {
92 // The line has become zero length - delete it
93 GetHandler().DeleteItem( aLine );
94 }
95
96 return removed;
97}
98
99
101{
102 return _( "Fillet Lines" );
103}
104
105
106std::optional<wxString> LINE_FILLET_ROUTINE::GetStatusMessage() const
107{
108 if( GetSuccesses() == 0 )
109 {
110 return _( "Unable to fillet the selected lines." );
111 }
112 else if( GetFailures() > 0 )
113 {
114 return _( "Some of the lines could not be filleted." );
115 }
116 return std::nullopt;
117}
118
119
121{
122 if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 )
123 return;
124
125 SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() );
126 SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() );
127
128 auto [a_pt, b_pt] = GetSharedEndpoints( seg_a, seg_b );
129
130 if( !a_pt || !b_pt )
131 {
132 // The lines do not share an endpoint, so we can't fillet them
133 AddFailure();
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 = []( VECTOR2I& aPointToSet, SEG aSegment, VECTOR2I aVecToTest )
144 {
145 VECTOR2I segToVec = aSegment.NearestPoint( aVecToTest ) - aVecToTest;
146
147 // Find out if we are on the segment (minimum precision)
149 {
150 aPointToSet.x = aVecToTest.x;
151 aPointToSet.y = aVecToTest.y;
152 return true;
153 }
154
155 return false;
156 };
157
158 //Do not draw a fillet if the end points of the arc are not within the track segments
159 if( !setIfPointOnSeg( t1newPoint, seg_a, sArc.GetP0() )
160 && !setIfPointOnSeg( t2newPoint, seg_b, sArc.GetP0() ) )
161 {
162 AddFailure();
163 return;
164 }
165
166 if( !setIfPointOnSeg( t1newPoint, seg_a, sArc.GetP1() )
167 && !setIfPointOnSeg( t2newPoint, seg_b, sArc.GetP1() ) )
168 {
169 AddFailure();
170 return;
171 }
172
173 auto tArc = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::ARC );
174
175 tArc->SetArcGeometry( sArc.GetP0(), sArc.GetArcMid(), sArc.GetP1() );
176
177 // Copy properties from one of the source lines
178 tArc->SetWidth( aLineA.GetWidth() );
179 tArc->SetLayer( aLineA.GetLayer() );
180 tArc->SetLocked( aLineA.IsLocked() );
181
182 CHANGE_HANDLER& handler = GetHandler();
183
184 handler.AddNewItem( std::move( tArc ) );
185
186 *a_pt = t1newPoint;
187 *b_pt = t2newPoint;
188
189 ModifyLineOrDeleteIfZeroLength( aLineA, seg_a );
190 ModifyLineOrDeleteIfZeroLength( aLineB, seg_b );
191
192 AddSuccess();
193}
194
196{
197 return _( "Chamfer Lines" );
198}
199
200
201std::optional<wxString> LINE_CHAMFER_ROUTINE::GetStatusMessage() const
202{
203 if( GetSuccesses() == 0 )
204 {
205 return _( "Unable to chamfer the selected lines." );
206 }
207 else if( GetFailures() > 0 )
208 {
209 return _( "Some of the lines could not be chamfered." );
210 }
211 return std::nullopt;
212}
213
214
216{
217 if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 )
218 return;
219
220 SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() );
221 SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() );
222
223 // If the segments share an endpoint, we won't try to chamfer them
224 // (we could extend to the intersection point, but this gets complicated
225 // and inconsistent when you select more than two lines)
226 if( !SegmentsShareEndpoint( seg_a, seg_b ) )
227 {
228 // not an error, lots of lines in a 2+ line selection will not intersect
229 return;
230 }
231
232 std::optional<CHAMFER_RESULT> chamfer_result =
233 ComputeChamferPoints( seg_a, seg_b, m_chamferParams );
234
235 if( !chamfer_result )
236 {
237 AddFailure();
238 return;
239 }
240
241 auto tSegment = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::SEGMENT );
242
243 tSegment->SetStart( chamfer_result->m_chamfer.A );
244 tSegment->SetEnd( chamfer_result->m_chamfer.B );
245
246 // Copy properties from one of the source lines
247 tSegment->SetWidth( aLineA.GetWidth() );
248 tSegment->SetLayer( aLineA.GetLayer() );
249 tSegment->SetLocked( aLineA.IsLocked() );
250
251 CHANGE_HANDLER& handler = GetHandler();
252
253 handler.AddNewItem( std::move( tSegment ) );
254
255 ModifyLineOrDeleteIfZeroLength( aLineA, *chamfer_result->m_updated_seg_a );
256 ModifyLineOrDeleteIfZeroLength( aLineB, *chamfer_result->m_updated_seg_b );
257
258 AddSuccess();
259}
260
261
263{
264 return _( "Dogbone Corners" );
265}
266
267
268std::optional<wxString> DOGBONE_CORNER_ROUTINE::GetStatusMessage() const
269{
270 wxString msg;
271
272 if( GetSuccesses() == 0 )
273 {
274 msg += _( "Unable to add dogbone corners to the selected lines." );
275 }
276 else if( GetFailures() > 0 )
277 {
278 msg += _( "Some of the lines could not have dogbone corners added." );
279 }
280
282 {
283 if( !msg.empty() )
284 msg += " ";
285
286 msg += _( "Some of the dogbone corners are too narrow to fit a "
287 "cutter of the specified radius." );
288
289 if( !m_params.AddSlots )
290 msg += _( " Consider enabling the 'Add Slots' option." );
291 else
292 msg += _( " Slots were added." );
293 }
294
295 if( msg.empty() )
296 return std::nullopt;
297
298 return msg;
299}
300
301
303{
304 if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 )
305 return;
306
307 SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() );
308 SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() );
309
310 auto [a_pt, b_pt] = GetSharedEndpoints( seg_a, seg_b );
311
312 if( !a_pt || !b_pt )
313 {
314 return;
315 }
316
317 // Cannot handle parallel lines
318 if( seg_a.Angle( seg_b ).IsHorizontal() )
319 {
320 AddFailure();
321 return;
322 }
323
324 std::optional<DOGBONE_RESULT> dogbone_result =
326
327 if( !dogbone_result )
328 {
329 AddFailure();
330 return;
331 }
332
333 if( dogbone_result->m_small_arc_mouth )
334 {
335 // The arc is too small to fit the radius
336 m_haveNarrowMouths = true;
337 }
338
339 CHANGE_HANDLER& handler = GetHandler();
340
341 auto tArc = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::ARC );
342
343 const auto copyProps = [&]( PCB_SHAPE& aShape )
344 {
345 aShape.SetWidth( aLineA.GetWidth() );
346 aShape.SetLayer( aLineA.GetLayer() );
347 aShape.SetLocked( aLineA.IsLocked() );
348 };
349
350 const auto addSegment = [&]( const SEG& aSeg )
351 {
352 if( aSeg.Length() == 0 )
353 return;
354
355 auto tSegment = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::SEGMENT );
356 tSegment->SetStart( aSeg.A );
357 tSegment->SetEnd( aSeg.B );
358
359 copyProps( *tSegment );
360 handler.AddNewItem( std::move( tSegment ) );
361 };
362
363 tArc->SetArcGeometry( dogbone_result->m_arc.GetP0(), dogbone_result->m_arc.GetArcMid(),
364 dogbone_result->m_arc.GetP1() );
365
366 // Copy properties from one of the source lines
367 copyProps( *tArc );
368
369 addSegment( SEG{ dogbone_result->m_arc.GetP0(), dogbone_result->m_updated_seg_a->B } );
370 addSegment( SEG{ dogbone_result->m_arc.GetP1(), dogbone_result->m_updated_seg_b->B } );
371
372 handler.AddNewItem( std::move( tArc ) );
373
374 ModifyLineOrDeleteIfZeroLength( aLineA, dogbone_result->m_updated_seg_a );
375 ModifyLineOrDeleteIfZeroLength( aLineB, dogbone_result->m_updated_seg_b );
376
377 AddSuccess();
378}
379
380
382{
383 return _( "Extend Lines to Meet" );
384}
385
386
387std::optional<wxString> LINE_EXTENSION_ROUTINE::GetStatusMessage() const
388{
389 if( GetSuccesses() == 0 )
390 {
391 return _( "Unable to extend the selected lines to meet." );
392 }
393 else if( GetFailures() > 0 )
394 {
395 return _( "Some of the lines could not be extended to meet." );
396 }
397 return std::nullopt;
398}
399
400
402{
403 if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 )
404 return;
405
406 SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() );
407 SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() );
408
409 if( seg_a.Intersects( seg_b ) )
410 {
411 // already intersecting, nothing to do
412 return;
413 }
414
415 OPT_VECTOR2I intersection = seg_a.IntersectLines( seg_b );
416
417 if( !intersection )
418 {
419 // This might be an error, but it's also possible that the lines are
420 // parallel and don't intersect. We'll just ignore this case.
421 return;
422 }
423
424 CHANGE_HANDLER& handler = GetHandler();
425
426 const auto line_extender = [&]( const SEG& aSeg, PCB_SHAPE& aLine )
427 {
428 // If the intersection point is not already n the line, we'll extend to it
429 if( !aSeg.Contains( *intersection ) )
430 {
431 const int dist_start = ( *intersection - aSeg.A ).EuclideanNorm();
432 const int dist_end = ( *intersection - aSeg.B ).EuclideanNorm();
433
434 const VECTOR2I& furthest_pt = ( dist_start < dist_end ) ? aSeg.B : aSeg.A;
435 // Note, the drawing tool has COORDS_PADDING of 20mm, but we need a larger buffer
436 // or we are not able to select the generated segments
437 unsigned int edge_padding = static_cast<unsigned>( pcbIUScale.mmToIU( 200 ) );
438 VECTOR2I new_end = GetClampedCoords( *intersection, edge_padding );
439
440 handler.MarkItemModified( aLine );
441 aLine.SetStart( furthest_pt );
442 aLine.SetEnd( new_end );
443 }
444 };
445
446 line_extender( seg_a, aLineA );
447 line_extender( seg_b, aLineB );
448
449 AddSuccess();
450}
451
452
454{
455 std::unique_ptr<SHAPE_POLY_SET> poly;
456
457 switch( aPcbShape.GetShape() )
458 {
459 case SHAPE_T::POLY:
460 {
461 poly = std::make_unique<SHAPE_POLY_SET>( aPcbShape.GetPolyShape() );
462 break;
463 }
464 case SHAPE_T::RECTANGLE:
465 {
466 SHAPE_POLY_SET rect_poly;
467
468 const std::vector<VECTOR2I> rect_pts = aPcbShape.GetRectCorners();
469
470 rect_poly.NewOutline();
471
472 for( const VECTOR2I& pt : rect_pts )
473 {
474 rect_poly.Append( pt );
475 }
476
477 poly = std::make_unique<SHAPE_POLY_SET>( std::move( rect_poly ) );
478 break;
479 }
480 default:
481 {
482 break;
483 }
484 }
485
486 if( !poly )
487 {
488 // Not a polygon or rectangle, nothing to do
489 return;
490 }
491
492 if( m_firstPolygon )
493 {
494 m_width = aPcbShape.GetWidth();
495 m_layer = aPcbShape.GetLayer();
496 m_filled = aPcbShape.IsFilled();
497 m_workingPolygons = std::move( *poly );
498 m_firstPolygon = false;
499
500 GetHandler().DeleteItem( aPcbShape );
501 }
502 else
503 {
504 if( ProcessSubsequentPolygon( *poly ) )
505 {
506 // If we could process the polygon, delete the source
507 GetHandler().DeleteItem( aPcbShape );
508 AddSuccess();
509 }
510 else
511 {
512 AddFailure();
513 }
514 }
515}
516
517
519{
521 {
522 // Nothing to do (no polygons handled or nothing left?)
523 return;
524 }
525
526 CHANGE_HANDLER& handler = GetHandler();
527
528 // If we have disjoint polygons, we'll fix that now and create
529 // new PCB_SHAPEs for each outline
530 for( int i = 0; i < m_workingPolygons.OutlineCount(); ++i )
531 {
532 // If we handled any polygons to get any outline,
533 // there must be a layer set by now.
534 wxASSERT( m_layer >= 0 );
535
536 std::unique_ptr<PCB_SHAPE> new_poly_shape =
537 std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::POLY );
538
540 new_poly_shape->SetPolyShape( poly_set );
541
542 // Copy properties from the source polygon
543 new_poly_shape->SetWidth( m_width );
544 new_poly_shape->SetLayer( m_layer );
545 new_poly_shape->SetFilled( m_filled );
546
547 handler.AddNewItem( std::move( new_poly_shape ) );
548 }
549}
550
551
553{
554 return _( "Merge polygons." );
555}
556
557
558std::optional<wxString> POLYGON_MERGE_ROUTINE::GetStatusMessage() const
559{
560 if( GetSuccesses() == 0 )
561 {
562 return _( "Unable to merge the selected polygons." );
563 }
564 else if( GetFailures() > 0 )
565 {
566 return _( "Some of the polygons could not be merged." );
567 }
568 return std::nullopt;
569}
570
571
573{
575 GetWorkingPolygons().BooleanAdd( aPolygon, poly_mode );
576 return true;
577}
578
579
581{
582 return _( "Subtract polygons." );
583}
584
585
586std::optional<wxString> POLYGON_SUBTRACT_ROUTINE::GetStatusMessage() const
587{
588 if( GetSuccesses() == 0 )
589 {
590 return _( "Unable to subtract the selected polygons." );
591 }
592 else if( GetFailures() > 0 )
593 {
594 return _( "Some of the polygons could not be subtracted." );
595 }
596 return std::nullopt;
597}
598
599
601{
603
604 SHAPE_POLY_SET& working_polygons = GetWorkingPolygons();
605 SHAPE_POLY_SET working_copy = working_polygons;
606 working_copy.BooleanSubtract( aPolygon, poly_mode );
607
608 // Subtraction can create holes or delete the polygon
609 // In theory we can allow holes as the EDA_SHAPE will fracture for us, but that's
610 // probably not what the user has in mind (?)
611 if( working_copy.OutlineCount() != 1 || working_copy.HoleCount( 0 ) > 0
612 || working_copy.VertexCount( 0 ) == 0 )
613 {
614 // If that happens, just skip the operation
615 return false;
616 }
617
618 working_polygons = std::move( working_copy );
619 return true;
620}
621
622
624{
625 return _( "Intersect polygons." );
626}
627
628
629std::optional<wxString> POLYGON_INTERSECT_ROUTINE::GetStatusMessage() const
630{
631 if( GetSuccesses() == 0 )
632 {
633 return _( "Unable to intersect the selected polygons." );
634 }
635 else if( GetFailures() > 0 )
636 {
637 return _( "Some of the polygons could not be intersected." );
638 }
639 return std::nullopt;
640}
641
642
644{
646
647 SHAPE_POLY_SET& working_polygons = GetWorkingPolygons();
648 SHAPE_POLY_SET working_copy = working_polygons;
649 working_copy.BooleanIntersection( aPolygon, poly_mode );
650
651 // Is there anything left?
652 if( working_copy.OutlineCount() == 0 )
653 {
654 // There was no intersection. Rather than deleting the working polygon, we'll skip
655 // and report a failure.
656 return false;
657 }
658
659 working_polygons = std::move( working_copy );
660 return true;
661}
662
663
665{
666 return _( "Outset items." );
667}
668
669std::optional<wxString> OUTSET_ROUTINE::GetStatusMessage() const
670{
671 if( GetSuccesses() == 0 )
672 {
673 return _( "Unable to outset the selected items." );
674 }
675 else if( GetFailures() > 0 )
676 {
677 return _( "Some of the items could not be outset." );
678 }
679 return std::nullopt;
680}
681
682
683static SHAPE_RECT GetRectRoundedToGridOutwards( const SHAPE_RECT& aRect, int aGridSize )
684{
685 const VECTOR2I newPos = KIGEOM::RoundNW( aRect.GetPosition(), aGridSize );
686 const VECTOR2I newOpposite =
687 KIGEOM::RoundSE( aRect.GetPosition() + aRect.GetSize(), aGridSize );
688 return SHAPE_RECT( newPos, newOpposite );
689}
690
691
693{
694 /*
695 * This attempts to do exact outsetting, rather than punting to Clipper.
696 * So it can't do all shapes, but it can do the most obvious ones, which are probably
697 * the ones you want to outset anyway, most usually when making a courtyard for a footprint.
698 */
699
701
702 // Not all items have a width, even if the parameters want to copy it
703 // So fall back to the given width if we can't get one.
704 int width = m_params.lineWidth;
706 {
707 std::optional<int> item_width = GetBoardItemWidth( aItem );
708
709 if( item_width.has_value() )
710 {
711 width = *item_width;
712 }
713 }
714
715 CHANGE_HANDLER& handler = GetHandler();
716
717 const auto addPolygonalChain = [&]( const SHAPE_LINE_CHAIN& aChain )
718 {
719 SHAPE_POLY_SET new_poly( aChain );
720
721 std::unique_ptr<PCB_SHAPE> new_shape =
722 std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::POLY );
723
724 new_shape->SetPolyShape( new_poly );
725 new_shape->SetLayer( layer );
726 new_shape->SetWidth( width );
727
728 handler.AddNewItem( std::move( new_shape ) );
729 };
730
731 // Iterate the SHAPE_LINE_CHAIN in the polygon, pulling out
732 // segments and arcs to create new PCB_SHAPE primitives.
733 const auto addChain = [&]( const SHAPE_LINE_CHAIN& aChain )
734 {
735 // Prefer to add a polygonal chain if there are no arcs
736 // as this permits boolean ops
737 if( aChain.ArcCount() == 0 )
738 {
739 addPolygonalChain( aChain );
740 return;
741 }
742
743 for( size_t si = 0; si < aChain.GetSegmentCount(); ++si )
744 {
745 const SEG seg = aChain.GetSegment( si );
746
747 if( seg.Length() == 0 )
748 continue;
749
750 if( aChain.IsArcSegment( si ) )
751 continue;
752
753 std::unique_ptr<PCB_SHAPE> new_shape =
754 std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::SEGMENT );
755 new_shape->SetStart( seg.A );
756 new_shape->SetEnd( seg.B );
757 new_shape->SetLayer( layer );
758 new_shape->SetWidth( width );
759
760 handler.AddNewItem( std::move( new_shape ) );
761 }
762
763 for( size_t ai = 0; ai < aChain.ArcCount(); ++ai )
764 {
765 const SHAPE_ARC& arc = aChain.Arc( ai );
766
767 if( arc.GetRadius() == 0 || arc.GetP0() == arc.GetP1() )
768 continue;
769
770 std::unique_ptr<PCB_SHAPE> new_shape =
771 std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::ARC );
772 new_shape->SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() );
773 new_shape->SetLayer( layer );
774 new_shape->SetWidth( width );
775
776 handler.AddNewItem( std::move( new_shape ) );
777 }
778 };
779
780 const auto addPoly = [&]( const SHAPE_POLY_SET& aPoly )
781 {
782 for( int oi = 0; oi < aPoly.OutlineCount(); ++oi )
783 {
784 addChain( aPoly.Outline( oi ) );
785 }
786 };
787
788 const auto addRect = [&]( const SHAPE_RECT& aRect )
789 {
790 std::unique_ptr<PCB_SHAPE> new_shape =
791 std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::RECTANGLE );
792
793 if( !m_params.gridRounding.has_value() )
794 {
795 new_shape->SetPosition( aRect.GetPosition() );
796 new_shape->SetRectangleWidth( aRect.GetWidth() );
797 new_shape->SetRectangleHeight( aRect.GetHeight() );
798 }
799 else
800 {
801 const SHAPE_RECT grid_rect =
803 new_shape->SetPosition( grid_rect.GetPosition() );
804 new_shape->SetRectangleWidth( grid_rect.GetWidth() );
805 new_shape->SetRectangleHeight( grid_rect.GetHeight() );
806 }
807
808 new_shape->SetLayer( layer );
809 new_shape->SetWidth( width );
810
811 handler.AddNewItem( std::move( new_shape ) );
812 };
813
814 const auto addCircle = [&]( const CIRCLE& aCircle )
815 {
816 std::unique_ptr<PCB_SHAPE> new_shape =
817 std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::CIRCLE );
818 new_shape->SetCenter( aCircle.Center );
819 new_shape->SetRadius( aCircle.Radius );
820 new_shape->SetLayer( layer );
821 new_shape->SetWidth( width );
822
823 handler.AddNewItem( std::move( new_shape ) );
824 };
825
826 const auto addCircleOrRect = [&]( const CIRCLE& aCircle )
827 {
829 {
830 addCircle( aCircle );
831 }
832 else
833 {
834 const VECTOR2I rVec{ aCircle.Radius, aCircle.Radius };
835 const SHAPE_RECT rect{ aCircle.Center - rVec, aCircle.Center + rVec };
836 addRect( rect );
837 }
838 };
839
840 switch( aItem.Type() )
841 {
842 case PCB_PAD_T:
843 {
844 const PAD& pad = static_cast<const PAD&>( aItem );
845
846 // TODO(JE) padstacks
847 const PAD_SHAPE pad_shape = pad.GetShape( PADSTACK::ALL_LAYERS );
848
849 switch( pad_shape )
850 {
851 case PAD_SHAPE::RECTANGLE:
852 case PAD_SHAPE::ROUNDRECT:
853 case PAD_SHAPE::OVAL:
854 {
855 const VECTOR2I pad_size = pad.GetSize( PADSTACK::ALL_LAYERS );
856
857 BOX2I box{ pad.GetPosition() - pad_size / 2, pad_size };
858 box.Inflate( m_params.outsetDistance );
859
860 int radius = m_params.outsetDistance;
861 if( pad_shape == PAD_SHAPE::ROUNDRECT )
862 {
863 radius += pad.GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS );
864 }
865 else if( pad_shape == PAD_SHAPE::OVAL )
866 {
867 radius += std::min( pad_size.x, pad_size.y ) / 2;
868 }
869
870 radius = m_params.roundCorners ? radius : 0;
871
872 // No point doing a SHAPE_RECT as we may need to rotate it
873 ROUNDRECT rrect( box, radius );
874 SHAPE_POLY_SET poly;
875 rrect.TransformToPolygon( poly, 0, ERROR_LOC::ERROR_OUTSIDE );
876
877 poly.Rotate( pad.GetOrientation(), pad.GetPosition() );
878 addPoly( poly );
879 AddSuccess();
880 break;
881 }
882 case PAD_SHAPE::CIRCLE:
883 {
884 const int radius = pad.GetSize( PADSTACK::ALL_LAYERS ).x / 2 + m_params.outsetDistance;
885 const CIRCLE circle( pad.GetPosition(), radius );
886 addCircleOrRect( circle );
887 AddSuccess();
888 break;
889 }
890 case PAD_SHAPE::TRAPEZOID:
891 {
892 // Not handled yet, but could use a generic convex polygon outset method.
893 break;
894 }
895 default:
896 // Other pad shapes are not supported with exact outsets
897 break;
898 }
899 break;
900 }
901 case PCB_SHAPE_T:
902 {
903 const PCB_SHAPE& pcb_shape = static_cast<const PCB_SHAPE&>( aItem );
904
905 switch( pcb_shape.GetShape() )
906 {
907 case SHAPE_T::RECTANGLE:
908 {
909 BOX2I box{ pcb_shape.GetPosition(),
910 VECTOR2I{ pcb_shape.GetRectangleWidth(), pcb_shape.GetRectangleHeight() } };
911 box.Inflate( m_params.outsetDistance );
912
913 SHAPE_RECT rect( box );
915 {
916 ROUNDRECT rrect( rect, m_params.outsetDistance );
917 SHAPE_POLY_SET poly;
918 rrect.TransformToPolygon( poly, 0, ERROR_LOC::ERROR_OUTSIDE );
919 addPoly( poly );
920 }
921 else
922 {
923 addRect( rect );
924 }
925 AddSuccess();
926 break;
927 }
928 case SHAPE_T::CIRCLE:
929 {
930 const CIRCLE circle( pcb_shape.GetCenter(),
931 pcb_shape.GetRadius() + m_params.outsetDistance );
932 addCircleOrRect( circle );
933 AddSuccess();
934 break;
935 }
936 case SHAPE_T::SEGMENT:
937 {
938 // For now just make the whole stadium shape and let the user delete the unwanted bits
939 const SEG seg( pcb_shape.GetStart(), pcb_shape.GetEnd() );
940
942 {
943 const OVAL oval( seg, m_params.outsetDistance * 2 );
944 addChain( KIGEOM::ConvertToChain( oval ) );
945 }
946 else
947 {
948 SHAPE_LINE_CHAIN chain;
949 const VECTOR2I ext = ( seg.B - seg.A ).Resize( m_params.outsetDistance );
950 const VECTOR2I perp = GetRotated( ext, ANGLE_90 );
951
952 chain.Append( seg.A - ext + perp );
953 chain.Append( seg.A - ext - perp );
954 chain.Append( seg.B + ext - perp );
955 chain.Append( seg.B + ext + perp );
956 chain.SetClosed( true );
957 addChain( chain );
958 }
959
960 AddSuccess();
961 break;
962 }
963 case SHAPE_T::ARC:
964 {
965 // Not 100% sure what a sensible non-round outset of an arc is!
966 // (not sure it's that important in practice)
967
968 // Gets rather complicated if this isn't true
969 if( pcb_shape.GetRadius() >= m_params.outsetDistance )
970 {
971 // Again, include the endcaps and let the user delete the unwanted bits
972 const SHAPE_ARC arc{ pcb_shape.GetCenter(), pcb_shape.GetStart(),
973 pcb_shape.GetArcAngle(), 0 };
974
975 const VECTOR2I startNorm =
976 VECTOR2I( arc.GetP0() - arc.GetCenter() ).Resize( m_params.outsetDistance );
977
978 const SHAPE_ARC inner{ arc.GetCenter(), arc.GetP0() - startNorm,
979 arc.GetCentralAngle(), 0 };
980 const SHAPE_ARC outer{ arc.GetCenter(), arc.GetP0() + startNorm,
981 arc.GetCentralAngle(), 0 };
982
983 SHAPE_LINE_CHAIN chain;
984 chain.Append( outer );
985 // End cap at the P1 end
986 chain.Append( SHAPE_ARC{ arc.GetP1(), outer.GetP1(), ANGLE_180 } );
987
988 if( inner.GetRadius() > 0 )
989 {
990 chain.Append( inner.Reversed() );
991 }
992
993 // End cap at the P0 end back to the start
994 chain.Append( SHAPE_ARC{ arc.GetP0(), inner.GetP0(), ANGLE_180 } );
995 addChain( chain );
996 AddSuccess();
997 }
998
999 break;
1000 }
1001
1002 default:
1003 // Other shapes are not supported with exact outsets
1004 // (convex) POLY shouldn't be too traumatic and it would bring trapezoids for free.
1005 break;
1006 }
1007
1008 break;
1009 }
1010 default:
1011 // Other item types are not supported with exact outsets
1012 break;
1013 }
1014
1016 {
1017 handler.DeleteItem( aItem );
1018 }
1019}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:108
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:79
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition: board_item.h:237
virtual bool IsLocked() const
Definition: board_item.cpp:75
Represent basic circle geometry with utility geometry functions.
Definition: circle.h:33
std::optional< wxString > GetStatusMessage() 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:138
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:101
EDA_ANGLE GetArcAngle() const
Definition: eda_shape.cpp:645
int GetRectangleWidth() const
Definition: eda_shape.cpp:166
SHAPE_POLY_SET & GetPolyShape()
Definition: eda_shape.h:279
bool IsFilled() const
Definition: eda_shape.h:91
int GetRadius() const
Definition: eda_shape.cpp:575
SHAPE_T GetShape() const
Definition: eda_shape.h:125
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition: eda_shape.h:167
void SetStart(const VECTOR2I &aStart)
Definition: eda_shape.h:134
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition: eda_shape.h:130
std::vector< VECTOR2I > GetRectCorners() const
Definition: eda_shape.cpp:1134
void SetEnd(const VECTOR2I &aEnd)
Definition: eda_shape.h:171
double GetLength() const
Definition: eda_shape.cpp:123
int GetRectangleHeight() const
Definition: eda_shape.cpp:153
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.
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
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.
void ProcessLinePair(PCB_SHAPE &aLineA, PCB_SHAPE &aLineB) override
Perform the action on the pair of lines given.
wxString GetCommitDescription() const override
void ProcessLinePair(PCB_SHAPE &aLineA, PCB_SHAPE &aLineB) override
Perform the action on the pair of lines given.
std::optional< wxString > GetStatusMessage() const override
Get a status message to show when the routine is complete.
void ProcessItem(BOARD_ITEM &aItem)
std::optional< wxString > GetStatusMessage() const override
Get a status message to show when the routine is complete.
const PARAMETERS m_params
wxString GetCommitDescription() const override
Class that represents an oval shape (rectangle with semicircular end caps)
Definition: oval.h:45
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition: padstack.h:144
Definition: pad.h:54
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition: pcb_shape.h:79
int GetWidth() const override
Definition: pcb_shape.cpp:457
VECTOR2I GetPosition() const override
Definition: pcb_shape.h:77
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition: pcb_shape.h:69
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 aError, ERROR_LOC aErrorLoc) const
Get the polygonal representation of the roundrect.
Definition: roundrect.cpp:81
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:248
int Length() const
Return the length (this).
Definition: seg.h:333
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:314
EDA_ANGLE Angle(const SEG &aOther) const
Determine the smallest angle between two segments.
Definition: seg.cpp:97
const VECTOR2I & GetArcMid() const
Definition: shape_arc.h:116
const VECTOR2I & GetP1() const
Definition: shape_arc.h:115
double GetRadius() const
Definition: shape_arc.cpp:554
const VECTOR2I & GetP0() const
Definition: shape_arc.h:114
const VECTOR2I & GetCenter() const
Definition: shape_arc.cpp:523
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.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
void SetWidth(int aWidth)
Set the width of all segments in the chain.
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 BooleanSubtract(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset difference For aFastMode meaning, see function booleanOp.
POLYGON_MODE
Operations on polygons use a aFastMode param if aFastMode is PM_FAST (true) the result can be a weak ...
int VertexCount(int aOutline=-1, int aHole=-1) const
Return the number of vertices in a given outline/hole.
void BooleanAdd(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset union For aFastMode meaning, see function booleanOp.
void BooleanIntersection(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset intersection For aFastMode meaning, see function booleanOp.
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)
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.
int OutlineCount() const
Return the number of outlines in the set.
const VECTOR2I & GetPosition() const
Definition: shape_rect.h:160
const VECTOR2I GetSize() const
Definition: shape_rect.h:168
int GetWidth() const
Definition: shape_rect.h:176
int GetHeight() const
Definition: shape_rect.h:184
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
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:403
static constexpr EDA_ANGLE ANGLE_180
Definition: eda_angle.h:405
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
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.
SHAPE_LINE_CHAIN ConvertToChain(const OVAL &aOval)
Definition: oval.cpp:69
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
Definition: padstack.h:52
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
constexpr int mmToIU(double mm) const
Definition: base_units.h:88
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:691
Supplemental functions for working with vectors and simple objects that interact with vectors.