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