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