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