KiCad PCB EDA Suite
Loading...
Searching...
No Matches
edit_tool.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2013-2023 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <[email protected]>
7 * @author Tomasz Wlostowski <[email protected]>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, you may find one here:
21 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22 * or you may search the http://www.gnu.org website for the version 2 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
27#include <macros.h>
28#include <advanced_config.h>
29#include <clipboard.h>
30#include <limits>
31#include <kiplatform/ui.h>
33#include <board.h>
35#include <footprint.h>
36#include <increment.h>
37#include <pcb_shape.h>
38#include <pcb_group.h>
39#include <pcb_point.h>
40#include <pcb_target.h>
41#include <pcb_textbox.h>
42#include <pcb_table.h>
43#include <pcb_generator.h>
44#include <pcb_edit_frame.h>
46#include <kiway.h>
47#include <status_popup.h>
49#include <tool/tool_manager.h>
50#include <tools/pcb_actions.h>
52#include <tools/edit_tool.h>
58#include <tools/pad_tool.h>
59#include <view/view_controls.h>
61#include <core/kicad_algo.h>
62#include <fix_board_shape.h>
63#include <bitmaps.h>
64#include <functional>
65using namespace std::placeholders;
66#include "kicad_clipboard.h"
67#include <wx/hyperlink.h>
68#include <router/router_tool.h>
72#include <dialogs/dialog_tablecell_properties.h>
73#include <dialogs/dialog_table_properties.h>
76#include <pcb_reference_image.h>
77
78const unsigned int EDIT_TOOL::COORDS_PADDING = pcbIUScale.mmToIU( 20 );
79
80static const std::vector<KICAD_T> padTypes = { PCB_PAD_T };
81
82static const std::vector<KICAD_T> footprintTypes = { PCB_FOOTPRINT_T };
83
84static const std::vector<KICAD_T> groupTypes = { PCB_GROUP_T };
85
86static const std::vector<KICAD_T> trackTypes = { PCB_TRACE_T,
88 PCB_VIA_T };
89
90static const std::vector<KICAD_T> baseConnectedTypes = { PCB_PAD_T,
93 PCB_ARC_T };
94
95static const std::vector<KICAD_T> connectedTypes = { PCB_TRACE_T,
99 PCB_ZONE_T };
100
101static const std::vector<KICAD_T> routableTypes = { PCB_TRACE_T,
102 PCB_ARC_T,
103 PCB_VIA_T,
104 PCB_PAD_T,
106
107
109 PCB_TOOL_BASE( "pcbnew.InteractiveEdit" ),
110 m_selectionTool( nullptr ),
111 m_dragging( false )
112{
113}
114
115
117{
118 m_dragging = false;
119
120 m_statusPopup = std::make_unique<STATUS_TEXT_POPUP>( getEditFrame<PCB_BASE_EDIT_FRAME>() );
121}
122
123
124static std::shared_ptr<CONDITIONAL_MENU> makePositioningToolsMenu( TOOL_INTERACTIVE* aTool )
125{
126 auto menu = std::make_shared<CONDITIONAL_MENU>( aTool );
127
128 menu->SetIcon( BITMAPS::special_tools );
129 menu->SetTitle( _( "Positioning Tools" ) );
130
131 auto notMovingCondition = []( const SELECTION& aSelection )
132 {
133 return aSelection.Empty() || !aSelection.Front()->IsMoving();
134 };
135
136 // clang-format off
137 menu->AddItem( PCB_ACTIONS::moveExact, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
138 menu->AddItem( PCB_ACTIONS::moveWithReference, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
139 menu->AddItem( PCB_ACTIONS::copyWithReference, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
140 menu->AddItem( PCB_ACTIONS::positionRelative, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
142 // clang-format on
143 return menu;
144};
145
146
147static std::shared_ptr<CONDITIONAL_MENU> makeShapeModificationMenu( TOOL_INTERACTIVE* aTool )
148{
149 auto menu = std::make_shared<CONDITIONAL_MENU>( aTool );
150
151 menu->SetTitle( _( "Shape Modification" ) );
152
153 static const std::vector<KICAD_T> filletChamferTypes = { PCB_SHAPE_LOCATE_POLY_T,
156
157 static const std::vector<KICAD_T> healShapesTypes = { PCB_SHAPE_LOCATE_SEGMENT_T,
160
161 static const std::vector<KICAD_T> lineExtendTypes = { PCB_SHAPE_LOCATE_SEGMENT_T };
162
163 static const std::vector<KICAD_T> polygonBooleanTypes = { PCB_SHAPE_LOCATE_RECT_T,
166
167 static const std::vector<KICAD_T> polygonSimplifyTypes = { PCB_SHAPE_LOCATE_POLY_T,
168 PCB_ZONE_T };
169
170 auto hasCornerCondition =
171 [aTool]( const SELECTION& aSelection )
172 {
173 PCB_POINT_EDITOR *pt_tool = aTool->GetManager()->GetTool<PCB_POINT_EDITOR>();
174
175 return pt_tool && pt_tool->HasCorner();
176 };
177
178 auto hasMidpointCondition =
179 [aTool]( const SELECTION& aSelection )
180 {
181 PCB_POINT_EDITOR *pt_tool = aTool->GetManager()->GetTool<PCB_POINT_EDITOR>();
182
183 return pt_tool && pt_tool->HasMidpoint();
184 };
185
186 // clang-format off
187 menu->AddItem( PCB_ACTIONS::healShapes, SELECTION_CONDITIONS::HasTypes( healShapesTypes ) );
188 menu->AddItem( PCB_ACTIONS::simplifyPolygons, SELECTION_CONDITIONS::HasTypes( polygonSimplifyTypes ) );
189 menu->AddItem( PCB_ACTIONS::filletLines, SELECTION_CONDITIONS::OnlyTypes( filletChamferTypes ) );
190 menu->AddItem( PCB_ACTIONS::chamferLines, SELECTION_CONDITIONS::OnlyTypes( filletChamferTypes ) );
191 menu->AddItem( PCB_ACTIONS::dogboneCorners, SELECTION_CONDITIONS::OnlyTypes( filletChamferTypes ) );
192 menu->AddItem( PCB_ACTIONS::extendLines, SELECTION_CONDITIONS::OnlyTypes( lineExtendTypes )
194 menu->AddItem( PCB_ACTIONS::pointEditorMoveCorner, hasCornerCondition );
195 menu->AddItem( PCB_ACTIONS::pointEditorMoveMidpoint, hasMidpointCondition );
196
197 menu->AddSeparator( SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
199
200 menu->AddItem( PCB_ACTIONS::mergePolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
202 menu->AddItem( PCB_ACTIONS::subtractPolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
204 menu->AddItem( PCB_ACTIONS::intersectPolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
206 // clang-format on
207
208 return menu;
209};
210
212{
213 // Find the selection tool, so they can cooperate
215
216 std::shared_ptr<CONDITIONAL_MENU> positioningToolsSubMenu = makePositioningToolsMenu( this );
217 m_selectionTool->GetToolMenu().RegisterSubMenu( positioningToolsSubMenu );
218
219 std::shared_ptr<CONDITIONAL_MENU> shapeModificationSubMenu = makeShapeModificationMenu( this );
220 m_selectionTool->GetToolMenu().RegisterSubMenu( shapeModificationSubMenu );
221
222 auto positioningToolsCondition =
223 [this]( const SELECTION& aSel )
224 {
225 std::shared_ptr<CONDITIONAL_MENU> subMenu = makePositioningToolsMenu( this );
226 subMenu->Evaluate( aSel );
227 return subMenu->GetMenuItemCount() > 0;
228 };
229
230 auto shapeModificationCondition =
231 [this]( const SELECTION& aSel )
232 {
233 std::shared_ptr<CONDITIONAL_MENU> subMenu = makeShapeModificationMenu( this );
234 subMenu->Evaluate( aSel );
235 return subMenu->GetMenuItemCount() > 0;
236 };
237
238 auto propertiesCondition =
239 [this]( const SELECTION& aSel )
240 {
241 if( aSel.GetSize() == 0 )
242 {
243 if( getView()->IsLayerVisible( LAYER_SCHEMATIC_DRAWINGSHEET ) )
244 {
245 DS_PROXY_VIEW_ITEM* ds = canvas()->GetDrawingSheet();
246 VECTOR2D cursor = getViewControls()->GetCursorPosition( false );
247
248 if( ds && ds->HitTestDrawingSheetItems( getView(), cursor ) )
249 return true;
250 }
251
252 return false;
253 }
254
255 if( aSel.GetSize() == 1 )
256 return true;
257
258 for( EDA_ITEM* item : aSel )
259 {
260 if( !dynamic_cast<PCB_TRACK*>( item ) )
261 return false;
262 }
263
264 return true;
265 };
266
267 auto inFootprintEditor =
268 [this]( const SELECTION& aSelection )
269 {
270 return m_isFootprintEditor;
271 };
272
273 auto canMirror =
274 [this]( const SELECTION& aSelection )
275 {
277 && SELECTION_CONDITIONS::OnlyTypes( padTypes )( aSelection ) )
278 {
279 return false;
280 }
281
282 if( SELECTION_CONDITIONS::HasTypes( groupTypes )( aSelection ) )
283 return true;
284
286 };
287
288 auto singleFootprintCondition = SELECTION_CONDITIONS::OnlyTypes( footprintTypes )
290
291 auto multipleFootprintsCondition =
292 []( const SELECTION& aSelection )
293 {
294 bool foundFirst = false;
295
296 for( EDA_ITEM* item : aSelection )
297 {
298 if( item->Type() == PCB_FOOTPRINT_T )
299 {
300 if( foundFirst )
301 return true;
302 else
303 foundFirst = true;
304 }
305 }
306
307 return false;
308 };
309
310 auto noActiveToolCondition =
311 [this]( const SELECTION& aSelection )
312 {
313 return frame()->ToolStackIsEmpty();
314 };
315
316 auto notMovingCondition =
317 []( const SELECTION& aSelection )
318 {
319 return aSelection.Empty() || !aSelection.Front()->IsMoving();
320 };
321
322 auto noItemsCondition =
323 [this]( const SELECTION& aSelections ) -> bool
324 {
325 return frame()->GetBoard() && !frame()->GetBoard()->IsEmpty();
326 };
327
328 auto isSkippable =
329 [this]( const SELECTION& aSelection )
330 {
331 return frame()->IsCurrentTool( PCB_ACTIONS::moveIndividually );
332 };
333
334 SELECTION_CONDITION isRoutable =
337 && notMovingCondition
338 && !inFootprintEditor;
339
340
341 const auto canCopyAsText = SELECTION_CONDITIONS::NotEmpty
353 } );
354
355 // Add context menu entries that are displayed when selection tool is active
356 CONDITIONAL_MENU& menu = m_selectionTool->GetToolMenu().GetMenu();
357
358 // clang-format off
360 && notMovingCondition );
361
362 menu.AddItem( PCB_ACTIONS::routerRouteSelected, isRoutable );
363 menu.AddItem( PCB_ACTIONS::routerRouteSelectedFromEnd, isRoutable );
364 menu.AddItem( PCB_ACTIONS::unrouteSelected, isRoutable );
365 menu.AddItem( PCB_ACTIONS::unrouteSegment, isRoutable );
366 menu.AddItem( PCB_ACTIONS::routerAutorouteSelected, isRoutable );
368 && notMovingCondition );
369 menu.AddItem( PCB_ACTIONS::skip, isSkippable );
381 menu.AddItem( PCB_ACTIONS::mirrorH, canMirror );
382 menu.AddItem( PCB_ACTIONS::mirrorV, canMirror );
386
387 menu.AddItem( PCB_ACTIONS::properties, propertiesCondition );
388
390 && !inFootprintEditor );
392
393 // Footprint actions
394 menu.AddSeparator();
395 menu.AddItem( PCB_ACTIONS::editFpInFpEditor, singleFootprintCondition );
396 menu.AddItem( PCB_ACTIONS::updateFootprint, singleFootprintCondition );
397 menu.AddItem( PCB_ACTIONS::updateFootprints, multipleFootprintsCondition );
398 menu.AddItem( PCB_ACTIONS::changeFootprint, singleFootprintCondition );
399 menu.AddItem( PCB_ACTIONS::changeFootprints, multipleFootprintsCondition );
400
401 // Add the submenu for the special tools: modfiers and positioning tools
402 menu.AddSeparator( 100 );
403 menu.AddMenu( shapeModificationSubMenu.get(), shapeModificationCondition, 100 );
404 menu.AddMenu( positioningToolsSubMenu.get(), positioningToolsCondition, 100 );
405
406 menu.AddSeparator( 150 );
407 menu.AddItem( ACTIONS::cut, SELECTION_CONDITIONS::NotEmpty, 150 );
408 menu.AddItem( ACTIONS::copy, SELECTION_CONDITIONS::NotEmpty, 150 );
409 menu.AddItem( ACTIONS::copyAsText, canCopyAsText, 150 );
410
411 // Selection tool handles the context menu for some other tools, such as the Picker.
412 // Don't add things like Paste when another tool is active.
413 menu.AddItem( ACTIONS::paste, noActiveToolCondition, 150 );
414 menu.AddItem( ACTIONS::pasteSpecial, noActiveToolCondition && !inFootprintEditor, 150 );
417
418 menu.AddSeparator( 150 );
419 menu.AddItem( ACTIONS::selectAll, noItemsCondition, 150 );
420 menu.AddItem( ACTIONS::unselectAll, noItemsCondition, 150 );
421 // clang-format on
422
423 return true;
424}
425
426
433{
434 wxString footprintName;
435 wxArrayString fplist;
436 const FOOTPRINTS& footprints = aFrame.GetBoard()->Footprints();
437
438 // Build list of available fp references, to display them in dialog
439 for( FOOTPRINT* fp : footprints )
440 fplist.Add( fp->GetReference() + wxT( " ( " ) + fp->GetValue() + wxT( " )" ) );
441
442 fplist.Sort();
443
444 DIALOG_GET_FOOTPRINT_BY_NAME dlg( &aFrame, fplist );
445
446 if( dlg.ShowModal() != wxID_OK ) //Aborted by user
447 return nullptr;
448
449 footprintName = dlg.GetValue();
450 footprintName.Trim( true );
451 footprintName.Trim( false );
452
453 if( !footprintName.IsEmpty() )
454 {
455 for( FOOTPRINT* fp : footprints )
456 {
457 if( fp->GetReference().CmpNoCase( footprintName ) == 0 )
458 return fp;
459 }
460 }
461
462 return nullptr;
463}
464
465
467{
468 // GetAndPlace makes sense only in board editor, although it is also called
469 // in fpeditor, that shares the same EDIT_TOOL list
470 if( IsFootprintEditor() )
471 return 0;
472
473 PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
475
476 if( fp )
477 {
479 m_toolMgr->RunAction<EDA_ITEM*>( ACTIONS::selectItem, fp );
480
481 selectionTool->GetSelection().SetReferencePoint( fp->GetPosition() );
482 m_toolMgr->PostAction( PCB_ACTIONS::move );
483 }
484
485 return 0;
486}
487
488
489bool EDIT_TOOL::invokeInlineRouter( int aDragMode )
490{
491 ROUTER_TOOL* theRouter = m_toolMgr->GetTool<ROUTER_TOOL>();
492
493 if( !theRouter )
494 return false;
495
496 // don't allow switch from moving to dragging
497 if( m_dragging )
498 {
499 wxBell();
500 return false;
501 }
502
503 // make sure we don't accidentally invoke inline routing mode while the router is already
504 // active!
505 if( theRouter->IsToolActive() )
506 return false;
507
508 if( theRouter->CanInlineDrag( aDragMode ) )
509 {
510 m_toolMgr->RunAction( PCB_ACTIONS::routerInlineDrag, aDragMode );
511 return true;
512 }
513
514 return false;
515}
516
517
519{
520 ROUTER_TOOL* router = m_toolMgr->GetTool<ROUTER_TOOL>();
521
522 return router && router->RoutingInProgress();
523}
524
525
526int EDIT_TOOL::Drag( const TOOL_EVENT& aEvent )
527{
528 if( !m_toolMgr->GetTool<ROUTER_TOOL>() )
529 {
530 wxBell();
531 return false; // don't drag when no router tool (i.e. fp editor)
532 }
533
534 if( m_toolMgr->GetTool<ROUTER_TOOL>()->IsToolActive() )
535 {
536 wxBell();
537 return false; // don't drag when router is already active
538 }
539
540 if( m_dragging )
541 {
542 wxBell();
543 return false; // don't do a router drag when already in an EDIT_TOOL drag
544 }
545
546 int mode = PNS::DM_ANY;
547
548 if( aEvent.IsAction( &PCB_ACTIONS::dragFreeAngle ) )
549 mode |= PNS::DM_FREE_ANGLE;
550
551 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
552 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
553 {
554 sTool->FilterCollectorForFreePads( aCollector );
555 sTool->FilterCollectorForHierarchy( aCollector, true );
556
557 std::vector<PCB_TRACK*> tracks;
558 std::vector<PCB_TRACK*> vias;
559 std::vector<FOOTPRINT*> footprints;
560
561 // Gather items from the collector into per-type vectors
562 const auto gatherItemsByType =
563 [&]()
564 {
565 for( EDA_ITEM* item : aCollector )
566 {
567 if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
568 {
569 if( track->Type() == PCB_VIA_T )
570 vias.push_back( track );
571 else
572 tracks.push_back( track );
573 }
574 else if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( item ) )
575 {
576 footprints.push_back( footprint );
577 }
578 }
579 };
580
581 // Initial gathering of items
582 gatherItemsByType();
583
584 if( !sTool->GetSelection().IsHover() && footprints.size() )
585 {
586 // Remove non-footprints so box-selection will drag footprints.
587 for( int ii = aCollector.GetCount() - 1; ii >= 0; --ii )
588 {
589 if( aCollector[ii]->Type() != PCB_FOOTPRINT_T )
590 aCollector.Remove( ii );
591 }
592 }
593 else if( tracks.size() || vias.size() )
594 {
595 /*
596 * First trim down selection to active layer, tracks vs zones, etc.
597 */
598 if( aCollector.GetCount() > 1 )
599 {
600 sTool->GuessSelectionCandidates( aCollector, aPt );
601
602 // Re-gather items after trimming to update counts
603 tracks.clear();
604 vias.clear();
605 footprints.clear();
606
607 gatherItemsByType();
608 }
609
610 /*
611 * If we have a knee between two tracks, or a via attached to two tracks,
612 * then drop the selection to a single item. We don't want a selection
613 * disambiguation menu when it doesn't matter which items is picked.
614 */
615 auto connected = []( PCB_TRACK* track, const VECTOR2I& pt )
616 {
617 return track->GetStart() == pt || track->GetEnd() == pt;
618 };
619
620 if( tracks.size() == 2 && vias.size() == 0 )
621 {
622 if( connected( tracks[0], tracks[1]->GetStart() )
623 || connected( tracks[0], tracks[1]->GetEnd() ) )
624 {
625 aCollector.Remove( tracks[1] );
626 }
627 }
628 else if( tracks.size() == 2 && vias.size() == 1 )
629 {
630 if( connected( tracks[0], vias[0]->GetPosition() )
631 && connected( tracks[1], vias[0]->GetPosition() ) )
632 {
633 aCollector.Remove( tracks[0] );
634 aCollector.Remove( tracks[1] );
635 }
636 }
637 }
638
639 sTool->FilterCollectorForLockedItems( aCollector );
640 } );
641
642 if( selection.Empty() )
643 return 0;
644
645 if( selection.Size() == 1 && selection.Front()->Type() == PCB_ARC_T )
646 {
647 // TODO: This really should be done in PNS to ensure DRC is maintained, but for now
648 // it allows interactive editing of an arc track
649 return DragArcTrack( aEvent );
650 }
651 else
652 {
653 invokeInlineRouter( mode );
654 }
655
656 return 0;
657}
658
659
661{
662 PCB_SELECTION& selection = m_selectionTool->GetSelection();
663
664 if( selection.Size() != 1 || selection.Front()->Type() != PCB_ARC_T )
665 return 0;
666
667 PCB_ARC* theArc = static_cast<PCB_ARC*>( selection.Front() );
668 EDA_ANGLE maxTangentDeviation( ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation, DEGREES_T );
669
670 if( theArc->GetAngle() + maxTangentDeviation >= ANGLE_180 )
671 {
672 wxString msg = wxString::Format( _( "Unable to resize arc tracks of %s or greater." ),
673 EDA_UNIT_UTILS::UI::MessageTextFromValue( ANGLE_180 - maxTangentDeviation ) );
674 frame()->ShowInfoBarError( msg );
675
676 return 0; // don't bother with > 180 degree arcs
677 }
678
680
681 Activate();
682 // Must be done after Activate() so that it gets set into the correct context
683 controls->ShowCursor( true );
684 controls->SetAutoPan( true );
685
686 BOARD_COMMIT commit( this );
687 bool restore_state = false;
688
689 commit.Modify( theArc );
690
691 VECTOR2I arcCenter = theArc->GetCenter();
692 SEG tanStart = SEG( arcCenter, theArc->GetStart() ).PerpendicularSeg( theArc->GetStart() );
693 SEG tanEnd = SEG( arcCenter, theArc->GetEnd() ).PerpendicularSeg( theArc->GetEnd() );
694
695 //Ensure the tangent segments are in the correct orientation
696 OPT_VECTOR2I tanIntersect = tanStart.IntersectLines( tanEnd );
697
698 if( !tanIntersect )
699 return 0;
700
701 tanStart.A = *tanIntersect;
702 tanStart.B = theArc->GetStart();
703 tanEnd.A = *tanIntersect;
704 tanEnd.B = theArc->GetEnd();
705
706 std::set<PCB_TRACK*> addedTracks;
707
708 auto getUniqueTrackAtAnchorCollinear =
709 [&]( const VECTOR2I& aAnchor, const SEG& aCollinearSeg ) -> PCB_TRACK*
710 {
711 std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
712
713 // Allow items at a distance within the width of the arc track
714 int allowedDeviation = theArc->GetWidth();
715
716 std::vector<BOARD_CONNECTED_ITEM*> itemsOnAnchor;
717
718 for( int i = 0; i < 3; i++ )
719 {
720 itemsOnAnchor = conn->GetConnectedItemsAtAnchor( theArc, aAnchor,
722 allowedDeviation );
723 allowedDeviation /= 2;
724
725 if( itemsOnAnchor.size() == 1 )
726 break;
727 }
728
729 PCB_TRACK* track = nullptr;
730
731 if( itemsOnAnchor.size() == 1 && itemsOnAnchor.front()->Type() == PCB_TRACE_T )
732 {
733 track = static_cast<PCB_TRACK*>( itemsOnAnchor.front() );
734 commit.Modify( track );
735
736 SEG trackSeg( track->GetStart(), track->GetEnd() );
737
738 // Allow deviations in colinearity as defined in ADVANCED_CFG
739 if( trackSeg.Angle( aCollinearSeg ) > maxTangentDeviation )
740 track = nullptr;
741 }
742
743 if( !track )
744 {
745 track = new PCB_TRACK( theArc->GetParent() );
746 track->SetStart( aAnchor );
747 track->SetEnd( aAnchor );
748 track->SetNet( theArc->GetNet() );
749 track->SetLayer( theArc->GetLayer() );
750 track->SetWidth( theArc->GetWidth() );
751 track->SetLocked( theArc->IsLocked() );
752 track->SetHasSolderMask( theArc->HasSolderMask() );
754 track->SetFlags( IS_NEW );
755 getView()->Add( track );
756 addedTracks.insert( track );
757 }
758
759 return track;
760 };
761
762 PCB_TRACK* trackOnStart = getUniqueTrackAtAnchorCollinear( theArc->GetStart(), tanStart);
763 PCB_TRACK* trackOnEnd = getUniqueTrackAtAnchorCollinear( theArc->GetEnd(), tanEnd );
764
765 if( trackOnStart->GetLength() != 0 )
766 {
767 tanStart.A = trackOnStart->GetStart();
768 tanStart.B = trackOnStart->GetEnd();
769 }
770
771 if( trackOnEnd->GetLength() != 0 )
772 {
773 tanEnd.A = trackOnEnd->GetStart();
774 tanEnd.B = trackOnEnd->GetEnd();
775 }
776
777 // Recalculate intersection point
778 if( tanIntersect = tanStart.IntersectLines( tanEnd ); !tanIntersect )
779 return 0;
780
781 auto isTrackStartClosestToArcStart =
782 [&]( PCB_TRACK* aTrack ) -> bool
783 {
784 double trackStartToArcStart = aTrack->GetStart().Distance( theArc->GetStart() );
785 double trackEndToArcStart = aTrack->GetEnd().Distance( theArc->GetStart() );
786
787 return trackStartToArcStart < trackEndToArcStart;
788 };
789
790 bool isStartTrackOnStartPt = isTrackStartClosestToArcStart( trackOnStart );
791 bool isEndTrackOnStartPt = isTrackStartClosestToArcStart( trackOnEnd );
792
793 // Calculate constraints
794 //======================
795 // maxTanCircle is the circle with maximum radius that is tangent to the two adjacent straight
796 // tracks and whose tangent points are constrained within the original tracks and their
797 // projected intersection points.
798 //
799 // The cursor will be constrained first within the isosceles triangle formed by the segments
800 // cSegTanStart, cSegTanEnd and cSegChord. After that it will be constrained to be outside
801 // maxTanCircle.
802 //
803 //
804 // ____________ <-cSegTanStart
805 // / * . ' *
806 // cSegTanEnd-> / * . ' *
807 // /* . ' <-cSegChord *
808 // /. '
809 // /* *
810 //
811 // * c * <-maxTanCircle
812 //
813 // * *
814 //
815 // * *
816 // * *
817 // * *
818 //
819
820 auto getFurthestPointToTanInterstect =
821 [&]( VECTOR2I& aPointA, VECTOR2I& aPointB ) -> VECTOR2I
822 {
823 if( ( aPointA - *tanIntersect ).EuclideanNorm()
824 > ( aPointB - *tanIntersect ).EuclideanNorm() )
825 {
826 return aPointA;
827 }
828 else
829 {
830 return aPointB;
831 }
832 };
833
834 CIRCLE maxTanCircle;
835 VECTOR2I tanStartPoint = getFurthestPointToTanInterstect( tanStart.A, tanStart.B );
836 VECTOR2I tanEndPoint = getFurthestPointToTanInterstect( tanEnd.A, tanEnd.B );
837 VECTOR2I tempTangentPoint = tanEndPoint;
838
839 if( getFurthestPointToTanInterstect( tanStartPoint, tanEndPoint ) == tanEndPoint )
840 tempTangentPoint = tanStartPoint;
841
842 maxTanCircle.ConstructFromTanTanPt( tanStart, tanEnd, tempTangentPoint );
843 VECTOR2I maxTanPtStart = tanStart.LineProject( maxTanCircle.Center );
844 VECTOR2I maxTanPtEnd = tanEnd.LineProject( maxTanCircle.Center );
845
846 SEG cSegTanStart( maxTanPtStart, *tanIntersect );
847 SEG cSegTanEnd( maxTanPtEnd, *tanIntersect );
848 SEG cSegChord( maxTanPtStart, maxTanPtEnd );
849
850 int cSegTanStartSide = cSegTanStart.Side( theArc->GetMid() );
851 int cSegTanEndSide = cSegTanEnd.Side( theArc->GetMid() );
852 int cSegChordSide = cSegChord.Side( theArc->GetMid() );
853
854 bool eatFirstMouseUp = true;
855
856 // Start the tool loop
857 while( TOOL_EVENT* evt = Wait() )
858 {
859 m_cursor = controls->GetMousePosition();
860
861 // Constrain cursor within the isosceles triangle
862 if( cSegTanStartSide != cSegTanStart.Side( m_cursor )
863 || cSegTanEndSide != cSegTanEnd.Side( m_cursor )
864 || cSegChordSide != cSegChord.Side( m_cursor ) )
865 {
866 std::vector<VECTOR2I> possiblePoints;
867
868 possiblePoints.push_back( cSegTanEnd.NearestPoint( m_cursor ) );
869 possiblePoints.push_back( cSegChord.NearestPoint( m_cursor ) );
870
871 VECTOR2I closest = cSegTanStart.NearestPoint( m_cursor );
872
873 for( const VECTOR2I& candidate : possiblePoints )
874 {
875 if( ( candidate - m_cursor ).SquaredEuclideanNorm()
876 < ( closest - m_cursor ).SquaredEuclideanNorm() )
877 {
878 closest = candidate;
879 }
880 }
881
882 m_cursor = closest;
883 }
884
885 // Constrain cursor to be outside maxTanCircle
886 if( ( m_cursor - maxTanCircle.Center ).EuclideanNorm() < maxTanCircle.Radius )
887 m_cursor = maxTanCircle.NearestPoint( m_cursor );
888
889 controls->ForceCursorPosition( true, m_cursor );
890
891 // Calculate resulting object coordinates
892 CIRCLE circlehelper;
893 circlehelper.ConstructFromTanTanPt( cSegTanStart, cSegTanEnd, m_cursor );
894
895 VECTOR2I newCenter = circlehelper.Center;
896 VECTOR2I newStart = cSegTanStart.LineProject( newCenter );
897 VECTOR2I newEnd = cSegTanEnd.LineProject( newCenter );
898 VECTOR2I newMid = CalcArcMid( newStart, newEnd, newCenter );
899
900 // Update objects
901 theArc->SetStart( newStart );
902 theArc->SetEnd( newEnd );
903 theArc->SetMid( newMid );
904
905 if( isStartTrackOnStartPt )
906 trackOnStart->SetStart( newStart );
907 else
908 trackOnStart->SetEnd( newStart );
909
910 if( isEndTrackOnStartPt )
911 trackOnEnd->SetStart( newEnd );
912 else
913 trackOnEnd->SetEnd( newEnd );
914
915 // Update view
916 getView()->Update( trackOnStart );
917 getView()->Update( trackOnEnd );
918 getView()->Update( theArc );
919
920 // Handle events
921 if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
922 {
923 eatFirstMouseUp = false;
924 }
925 else if( evt->IsCancelInteractive() || evt->IsActivate() )
926 {
927 restore_state = true; // Canceling the tool means that items have to be restored
928 break; // Finish
929 }
930 else if( evt->IsAction( &ACTIONS::undo ) )
931 {
932 restore_state = true; // Perform undo locally
933 break; // Finish
934 }
935 else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT )
936 || evt->IsDblClick( BUT_LEFT ) )
937 {
938 // Eat mouse-up/-click events that leaked through from the lock dialog
939 if( eatFirstMouseUp && !evt->IsAction( &ACTIONS::cursorClick ) )
940 {
941 eatFirstMouseUp = false;
942 continue;
943 }
944
945 break; // Finish
946 }
947 }
948
949 // Amend the end points of the arc if we delete the joining tracks
950 VECTOR2I newStart = trackOnStart->GetStart();
951 VECTOR2I newEnd = trackOnEnd->GetStart();
952
953 if( isStartTrackOnStartPt )
954 newStart = trackOnStart->GetEnd();
955
956 if( isEndTrackOnStartPt )
957 newEnd = trackOnEnd->GetEnd();
958
959 int maxLengthIU =
960 KiROUND( ADVANCED_CFG::GetCfg().m_MaxTrackLengthToKeep * pcbIUScale.IU_PER_MM );
961
962 if( trackOnStart->GetLength() <= maxLengthIU )
963 {
964 if( addedTracks.count( trackOnStart ) )
965 {
966 getView()->Remove( trackOnStart );
967 addedTracks.erase( trackOnStart );
968 delete trackOnStart;
969 }
970 else
971 {
972 commit.Remove( trackOnStart );
973 }
974
975 theArc->SetStart( newStart );
976 }
977
978 if( trackOnEnd->GetLength() <= maxLengthIU )
979 {
980 if( addedTracks.count( trackOnEnd ) )
981 {
982 getView()->Remove( trackOnEnd );
983 addedTracks.erase( trackOnEnd );
984 delete trackOnEnd;
985 }
986 else
987 {
988 commit.Remove( trackOnEnd );
989 }
990
991 theArc->SetEnd( newEnd );
992 }
993
994 if( theArc->GetLength() <= 0 )
995 commit.Remove( theArc );
996
997 for( PCB_TRACK* added : addedTracks )
998 {
999 getView()->Remove( added );
1000 commit.Add( added );
1001 }
1002
1003 // Should we commit?
1004 if( restore_state )
1005 commit.Revert();
1006 else
1007 commit.Push( _( "Drag Arc Track" ) );
1008
1009 return 0;
1010}
1011
1012
1014{
1015 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1016 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1017 {
1018 // Iterate from the back so we don't have to worry about removals.
1019 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1020 {
1021 BOARD_ITEM* item = aCollector[ i ];
1022
1023 if( !dynamic_cast<PCB_TRACK*>( item ) )
1024 aCollector.Remove( item );
1025 }
1026
1027 sTool->FilterCollectorForLockedItems( aCollector );
1028 } );
1029
1030 BOARD_COMMIT commit( this );
1031
1032 for( EDA_ITEM* item : selection )
1033 {
1034 if( item->Type() == PCB_VIA_T )
1035 {
1036 PCB_VIA* via = static_cast<PCB_VIA*>( item );
1037
1038 commit.Modify( via );
1039
1040 int new_width;
1041 int new_drill;
1042
1043 if( via->GetViaType() == VIATYPE::MICROVIA )
1044 {
1045 NETCLASS* netClass = via->GetEffectiveNetClass();
1046
1047 new_width = netClass->GetuViaDiameter();
1048 new_drill = netClass->GetuViaDrill();
1049 }
1050 else
1051 {
1052 new_width = board()->GetDesignSettings().GetCurrentViaSize();
1053 new_drill = board()->GetDesignSettings().GetCurrentViaDrill();
1054 }
1055
1056 via->SetDrill( new_drill );
1057 // TODO(JE) padstacks - is this correct behavior already? If so, also change stack mode
1058 via->SetWidth( PADSTACK::ALL_LAYERS, new_width );
1059 }
1060 else if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
1061 {
1062 PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item );
1063
1064 wxCHECK( track, 0 );
1065
1066 commit.Modify( track );
1067
1068 int new_width = board()->GetDesignSettings().GetCurrentTrackWidth();
1069 track->SetWidth( new_width );
1070 }
1071 }
1072
1073 commit.Push( _( "Edit Track Width/Via Size" ) );
1074
1075 if( selection.IsHover() )
1076 {
1077 m_toolMgr->RunAction( ACTIONS::selectionClear );
1078
1079 // Notify other tools of the changes -- This updates the visual ratsnest
1081 }
1082
1083 return 0;
1084}
1085
1086
1088{
1089 if( m_toolMgr->GetTool<ROUTER_TOOL>() && m_toolMgr->GetTool<ROUTER_TOOL>()->IsToolActive() )
1090 return 0;
1091
1092 bool isNext = aEvent.IsAction( &PCB_ACTIONS::changeTrackLayerNext );
1093
1094 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1095 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1096 {
1097 // Iterate from the back so we don't have to worry about removals.
1098 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1099 {
1100 BOARD_ITEM* item = aCollector[i];
1101
1102 if( !dynamic_cast<PCB_TRACK*>( item ) )
1103 aCollector.Remove( item );
1104 }
1105
1106 sTool->FilterCollectorForLockedItems( aCollector );
1107 } );
1108
1109 PCB_LAYER_ID origLayer = frame()->GetActiveLayer();
1110
1111 if( isNext )
1112 m_toolMgr->RunAction( PCB_ACTIONS::layerNext );
1113 else
1114 m_toolMgr->RunAction( PCB_ACTIONS::layerPrev );
1115
1116 PCB_LAYER_ID newLayer = frame()->GetActiveLayer();
1117
1118 if( newLayer == origLayer )
1119 return 0;
1120
1121 BOARD_COMMIT commit( this );
1122
1123 for( EDA_ITEM* item : selection )
1124 {
1125 if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
1126 {
1127 PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item );
1128
1129 wxCHECK( track, 0 );
1130
1131 commit.Modify( track );
1132
1133 track->SetLayer( newLayer );
1134 }
1135 }
1136
1137 commit.Push( _( "Edit Track Layer" ) );
1138
1139 if( selection.IsHover() )
1140 {
1141 m_toolMgr->RunAction( ACTIONS::selectionClear );
1142
1143 // Notify other tools of the changes -- This updates the visual ratsnest
1145 }
1146
1147 return 0;
1148}
1149
1150
1152{
1153 // Store last used fillet radius to allow pressing "enter" if repeat fillet is required
1154 static int filletRadius = 0;
1155
1156 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1157 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1158 {
1159 // Iterate from the back so we don't have to worry about removals.
1160 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1161 {
1162 BOARD_ITEM* item = aCollector[i];
1163
1164 if( !dynamic_cast<PCB_TRACK*>( item ) )
1165 aCollector.Remove( item );
1166 }
1167
1168 sTool->FilterCollectorForLockedItems( aCollector );
1169 } );
1170
1171 if( selection.Size() < 2 )
1172 {
1173 frame()->ShowInfoBarMsg( _( "At least two straight track segments must be selected." ) );
1174 return 0;
1175 }
1176
1177 WX_UNIT_ENTRY_DIALOG dlg( frame(), _( "Fillet Tracks" ), _( "Radius:" ), filletRadius );
1178
1179 if( dlg.ShowModal() == wxID_CANCEL || dlg.GetValue() == 0 )
1180 return 0;
1181
1182 filletRadius = dlg.GetValue();
1183
1184 struct FILLET_OP
1185 {
1186 PCB_TRACK* t1;
1187 PCB_TRACK* t2;
1188 // Start point of track is modified after PCB_ARC is added, otherwise the end point:
1189 bool t1Start = true;
1190 bool t2Start = true;
1191 };
1192
1193 std::vector<FILLET_OP> filletOperations;
1194 bool operationPerformedOnAtLeastOne = false;
1195 bool didOneAttemptFail = false;
1196 std::set<PCB_TRACK*> processedTracks;
1197
1198 auto processFilletOp =
1199 [&]( PCB_TRACK* aTrack, bool aStartPoint )
1200 {
1201 std::shared_ptr<CONNECTIVITY_DATA> c = board()->GetConnectivity();
1202 VECTOR2I anchor = aStartPoint ? aTrack->GetStart() : aTrack->GetEnd();
1203 std::vector<BOARD_CONNECTED_ITEM*> itemsOnAnchor;
1204
1205 itemsOnAnchor = c->GetConnectedItemsAtAnchor( aTrack, anchor, baseConnectedTypes );
1206
1207 if( itemsOnAnchor.size() > 0
1208 && selection.Contains( itemsOnAnchor.at( 0 ) )
1209 && itemsOnAnchor.at( 0 )->Type() == PCB_TRACE_T )
1210 {
1211 PCB_TRACK* trackOther = static_cast<PCB_TRACK*>( itemsOnAnchor.at( 0 ) );
1212
1213 // Make sure we don't fillet the same pair of tracks twice
1214 if( processedTracks.find( trackOther ) == processedTracks.end() )
1215 {
1216 if( itemsOnAnchor.size() == 1 )
1217 {
1218 FILLET_OP filletOp;
1219 filletOp.t1 = aTrack;
1220 filletOp.t2 = trackOther;
1221 filletOp.t1Start = aStartPoint;
1222 filletOp.t2Start = aTrack->IsPointOnEnds( filletOp.t2->GetStart() );
1223 filletOperations.push_back( filletOp );
1224 }
1225 else
1226 {
1227 // User requested to fillet these two tracks but not possible as
1228 // there are other elements connected at that point
1229 didOneAttemptFail = true;
1230 }
1231 }
1232 }
1233 };
1234
1235 for( EDA_ITEM* item : selection )
1236 {
1237 if( item->Type() == PCB_TRACE_T )
1238 {
1239 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
1240
1241 if( track->GetLength() > 0 )
1242 {
1243 processFilletOp( track, true ); // on the start point of track
1244 processFilletOp( track, false ); // on the end point of track
1245
1246 processedTracks.insert( track );
1247 }
1248 }
1249 }
1250
1251 BOARD_COMMIT commit( this );
1252 std::vector<BOARD_ITEM*> itemsToAddToSelection;
1253
1254 for( FILLET_OP filletOp : filletOperations )
1255 {
1256 PCB_TRACK* track1 = filletOp.t1;
1257 PCB_TRACK* track2 = filletOp.t2;
1258
1259 bool trackOnStart = track1->IsPointOnEnds( track2->GetStart() );
1260 bool trackOnEnd = track1->IsPointOnEnds( track2->GetEnd() );
1261
1262 if( trackOnStart && trackOnEnd )
1263 continue; // Ignore duplicate tracks
1264
1265 if( ( trackOnStart || trackOnEnd ) && track1->GetLayer() == track2->GetLayer() )
1266 {
1267 SEG t1Seg( track1->GetStart(), track1->GetEnd() );
1268 SEG t2Seg( track2->GetStart(), track2->GetEnd() );
1269
1270 if( t1Seg.ApproxCollinear( t2Seg ) )
1271 continue;
1272
1273 SHAPE_ARC sArc( t1Seg, t2Seg, filletRadius );
1274 VECTOR2I t1newPoint, t2newPoint;
1275
1276 auto setIfPointOnSeg =
1277 []( VECTOR2I& aPointToSet, const SEG& aSegment, const VECTOR2I& aVecToTest )
1278 {
1279 VECTOR2I segToVec = aSegment.NearestPoint( aVecToTest ) - aVecToTest;
1280
1281 // Find out if we are on the segment (minimum precision)
1283 {
1284 aPointToSet.x = aVecToTest.x;
1285 aPointToSet.y = aVecToTest.y;
1286 return true;
1287 }
1288
1289 return false;
1290 };
1291
1292 //Do not draw a fillet if the end points of the arc are not within the track segments
1293 if( !setIfPointOnSeg( t1newPoint, t1Seg, sArc.GetP0() )
1294 && !setIfPointOnSeg( t2newPoint, t2Seg, sArc.GetP0() ) )
1295 {
1296 didOneAttemptFail = true;
1297 continue;
1298 }
1299
1300 if( !setIfPointOnSeg( t1newPoint, t1Seg, sArc.GetP1() )
1301 && !setIfPointOnSeg( t2newPoint, t2Seg, sArc.GetP1() ) )
1302 {
1303 didOneAttemptFail = true;
1304 continue;
1305 }
1306
1307 PCB_ARC* tArc = new PCB_ARC( frame()->GetBoard(), &sArc );
1308 tArc->SetLayer( track1->GetLayer() );
1309 tArc->SetWidth( track1->GetWidth() );
1310 tArc->SetNet( track1->GetNet() );
1311 tArc->SetLocked( track1->IsLocked() );
1312 tArc->SetHasSolderMask( track1->HasSolderMask() );
1314 commit.Add( tArc );
1315 itemsToAddToSelection.push_back( tArc );
1316
1317 commit.Modify( track1 );
1318 commit.Modify( track2 );
1319
1320 if( filletOp.t1Start )
1321 track1->SetStart( t1newPoint );
1322 else
1323 track1->SetEnd( t1newPoint );
1324
1325 if( filletOp.t2Start )
1326 track2->SetStart( t2newPoint );
1327 else
1328 track2->SetEnd( t2newPoint );
1329
1330 operationPerformedOnAtLeastOne = true;
1331 }
1332 }
1333
1334 commit.Push( _( "Fillet Tracks" ) );
1335
1336 //select the newly created arcs
1337 for( BOARD_ITEM* item : itemsToAddToSelection )
1338 m_selectionTool->AddItemToSel( item );
1339
1340 if( !operationPerformedOnAtLeastOne )
1341 frame()->ShowInfoBarMsg( _( "Unable to fillet the selected track segments." ) );
1342 else if( didOneAttemptFail )
1343 frame()->ShowInfoBarMsg( _( "Some of the track segments could not be filleted." ) );
1344
1345 return 0;
1346}
1347
1348
1358static std::optional<int> GetRadiusParams( PCB_BASE_EDIT_FRAME& aFrame, const wxString& aTitle,
1359 int& aPersitentRadius )
1360{
1361 WX_UNIT_ENTRY_DIALOG dlg( &aFrame, aTitle, _( "Radius:" ), aPersitentRadius );
1362
1363 if( dlg.ShowModal() == wxID_CANCEL || dlg.GetValue() == 0 )
1364 return std::nullopt;
1365
1366 aPersitentRadius = dlg.GetValue();
1367
1368 return aPersitentRadius;
1369}
1370
1371
1372static std::optional<DOGBONE_CORNER_ROUTINE::PARAMETERS>
1374{
1375 // Persistent parameters
1376 static DOGBONE_CORNER_ROUTINE::PARAMETERS s_dogBoneParams{
1377 pcbIUScale.mmToIU( 1 ),
1378 true,
1379 };
1380
1381 std::vector<WX_MULTI_ENTRY_DIALOG::ENTRY> entries{
1382 {
1383 _( "Arc radius:" ),
1384 WX_MULTI_ENTRY_DIALOG::UNIT_BOUND{ s_dogBoneParams.DogboneRadiusIU },
1385 wxEmptyString,
1386 },
1387 {
1388 _( "Add slots in acute corners" ),
1389 WX_MULTI_ENTRY_DIALOG::CHECKBOX{ s_dogBoneParams.AddSlots },
1390 _( "Add slots in acute corners to allow access to a cutter of the given radius" ),
1391 },
1392 };
1393
1394 WX_MULTI_ENTRY_DIALOG dlg( &aFrame, _( "Dogbone Corner Settings" ), entries );
1395
1396 if( dlg.ShowModal() == wxID_CANCEL )
1397 return std::nullopt;
1398
1399 std::vector<WX_MULTI_ENTRY_DIALOG::RESULT> results = dlg.GetValues();
1400 wxCHECK( results.size() == 2, std::nullopt );
1401
1402 try
1403 {
1404 s_dogBoneParams.DogboneRadiusIU = std::get<long long int>( results[0] );
1405 s_dogBoneParams.AddSlots = std::get<bool>( results[1] );
1406 }
1407 catch( const std::bad_variant_access& )
1408 {
1409 wxASSERT( false );
1410 return std::nullopt;
1411 }
1412
1413 return s_dogBoneParams;
1414}
1415
1424static std::optional<CHAMFER_PARAMS> GetChamferParams( PCB_BASE_EDIT_FRAME& aFrame )
1425{
1426 // Non-zero and the KLC default for Fab layer chamfers
1427 const int default_setback = pcbIUScale.mmToIU( 1 );
1428 // Store last used setback to allow pressing "enter" if repeat chamfer is required
1429 static CHAMFER_PARAMS params{ default_setback, default_setback };
1430
1431 WX_UNIT_ENTRY_DIALOG dlg( &aFrame, _( "Chamfer Lines" ), _( "Chamfer setback:" ),
1432 params.m_chamfer_setback_a );
1433
1434 if( dlg.ShowModal() == wxID_CANCEL || dlg.GetValue() == 0 )
1435 return std::nullopt;
1436
1437 params.m_chamfer_setback_a = dlg.GetValue();
1438 // It's hard to easily specify an asymmetric chamfer (which line gets the longer setback?),
1439 // so we just use the same setback for each
1440 params.m_chamfer_setback_b = params.m_chamfer_setback_a;
1441
1442 return params;
1443}
1444
1445
1447{
1448 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1449 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1450 {
1451 std::vector<VECTOR2I> pts;
1452
1453 // Iterate from the back so we don't have to worry about removals.
1454 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1455 {
1456 BOARD_ITEM* item = aCollector[i];
1457
1458 // We've converted the polygon and rectangle to segments, so drop everything
1459 // that isn't a segment at this point
1460 if( !item->IsType( { PCB_SHAPE_LOCATE_SEGMENT_T,
1461 PCB_SHAPE_LOCATE_POLY_T,
1462 PCB_SHAPE_LOCATE_RECT_T } ) )
1463 {
1464 aCollector.Remove( item );
1465 }
1466 }
1467
1468 sTool->FilterCollectorForLockedItems( aCollector );
1469 } );
1470
1471 std::set<PCB_SHAPE*> lines_to_add;
1472 std::vector<PCB_SHAPE*> items_to_remove;
1473
1474 for( EDA_ITEM* item : selection )
1475 {
1476 std::vector<VECTOR2I> pts;
1477 PCB_SHAPE *graphic = static_cast<PCB_SHAPE*>( item );
1478 PCB_LAYER_ID layer = graphic->GetLayer();
1479 int width = graphic->GetWidth();
1480
1481 if( graphic->GetShape() == SHAPE_T::RECTANGLE )
1482 {
1483 items_to_remove.push_back( graphic );
1484 VECTOR2I start( graphic->GetStart() );
1485 VECTOR2I end( graphic->GetEnd() );
1486 pts.emplace_back( start );
1487 pts.emplace_back( VECTOR2I( end.x, start.y ) );
1488 pts.emplace_back( end );
1489 pts.emplace_back( VECTOR2I( start.x, end.y ) );
1490 }
1491
1492 if( graphic->GetShape() == SHAPE_T::POLY )
1493 {
1494 items_to_remove.push_back( graphic );
1495
1496 for( int jj = 0; jj < graphic->GetPolyShape().VertexCount(); ++jj )
1497 pts.emplace_back( graphic->GetPolyShape().CVertex( jj ) );
1498 }
1499
1500 for( size_t jj = 1; jj < pts.size(); ++jj )
1501 {
1502 PCB_SHAPE *line = new PCB_SHAPE( frame()->GetModel(), SHAPE_T::SEGMENT );
1503
1504 line->SetStart( pts[jj - 1] );
1505 line->SetEnd( pts[jj] );
1506 line->SetWidth( width );
1507 line->SetLayer( layer );
1508 lines_to_add.insert( line );
1509 }
1510
1511 if( pts.size() > 1 )
1512 {
1513 PCB_SHAPE *line = new PCB_SHAPE( frame()->GetModel(), SHAPE_T::SEGMENT );
1514
1515 line->SetStart( pts.back() );
1516 line->SetEnd( pts.front() );
1517 line->SetWidth( width );
1518 line->SetLayer( layer );
1519 lines_to_add.insert( line );
1520 }
1521 }
1522
1523 int segmentCount = selection.CountType( PCB_SHAPE_LOCATE_SEGMENT_T ) + lines_to_add.size();
1524
1525 if( aEvent.IsAction( &PCB_ACTIONS::extendLines ) && segmentCount != 2 )
1526 {
1527 frame()->ShowInfoBarMsg( _( "Exactly two lines must be selected to extend them." ) );
1528
1529 for( PCB_SHAPE* line : lines_to_add )
1530 delete line;
1531
1532 return 0;
1533 }
1534 else if( segmentCount < 2 )
1535 {
1536 frame()->ShowInfoBarMsg( _( "A shape with at least two lines must be selected." ) );
1537
1538 for( PCB_SHAPE* line : lines_to_add )
1539 delete line;
1540
1541 return 0;
1542 }
1543
1544 BOARD_COMMIT commit( this );
1545
1546 // Items created like lines from a rectangle
1547 for( PCB_SHAPE* item : lines_to_add )
1548 {
1549 commit.Add( item );
1550 selection.Add( item );
1551 }
1552
1553 // Remove items like rectangles that we decomposed into lines
1554 for( PCB_SHAPE* item : items_to_remove )
1555 {
1556 selection.Remove( item );
1557 commit.Remove( item );
1558 }
1559
1560 for( EDA_ITEM* item : selection )
1561 item->ClearFlags( STRUCT_DELETED );
1562
1563 // List of thing to select at the end of the operation
1564 // (doing it as we go will invalidate the iterator)
1565 std::vector<BOARD_ITEM*> items_to_select_on_success;
1566
1567 // And same for items to deselect
1568 std::vector<BOARD_ITEM*> items_to_deselect_on_success;
1569
1570 // Handle modifications to existing items by the routine
1571 // How to deal with this depends on whether we're in the footprint editor or not
1572 // and whether the item was conjured up by decomposing a polygon or rectangle
1573 auto item_modification_handler =
1574 [&]( BOARD_ITEM& aItem )
1575 {
1576 // If the item was "conjured up" it will be added later separately
1577 if( !alg::contains( lines_to_add, &aItem ) )
1578 {
1579 commit.Modify( &aItem );
1580 items_to_select_on_success.push_back( &aItem );
1581 }
1582 };
1583
1584 bool any_items_created = !lines_to_add.empty();
1585 auto item_creation_handler =
1586 [&]( std::unique_ptr<BOARD_ITEM> aItem )
1587 {
1588 any_items_created = true;
1589 items_to_select_on_success.push_back( aItem.get() );
1590 commit.Add( aItem.release() );
1591 };
1592
1593 bool any_items_removed = !items_to_remove.empty();
1594 auto item_removal_handler =
1595 [&]( BOARD_ITEM& aItem )
1596 {
1597 aItem.SetFlags( STRUCT_DELETED );
1598 any_items_removed = true;
1599 items_to_deselect_on_success.push_back( &aItem );
1600 commit.Remove( &aItem );
1601 };
1602
1603 // Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
1605 item_creation_handler, item_modification_handler, item_removal_handler );
1606
1607 // Construct an appropriate tool
1608 std::unique_ptr<PAIRWISE_LINE_ROUTINE> pairwise_line_routine;
1609
1610 if( aEvent.IsAction( &PCB_ACTIONS::filletLines ) )
1611 {
1612 static int s_filletRadius = pcbIUScale.mmToIU( 1 );
1613 std::optional<int> filletRadiusIU =
1614 GetRadiusParams( *frame(), _( "Fillet Lines" ), s_filletRadius );
1615
1616 if( filletRadiusIU.has_value() )
1617 {
1618 pairwise_line_routine = std::make_unique<LINE_FILLET_ROUTINE>(
1619 frame()->GetModel(), change_handler, *filletRadiusIU );
1620 }
1621 }
1622 else if( aEvent.IsAction( &PCB_ACTIONS::dogboneCorners ) )
1623 {
1624 std::optional<DOGBONE_CORNER_ROUTINE::PARAMETERS> dogboneParams =
1625 GetDogboneParams( *frame() );
1626
1627 if( dogboneParams.has_value() )
1628 {
1629 pairwise_line_routine = std::make_unique<DOGBONE_CORNER_ROUTINE>(
1630 frame()->GetModel(), change_handler, *dogboneParams );
1631 }
1632 }
1633 else if( aEvent.IsAction( &PCB_ACTIONS::chamferLines ) )
1634 {
1635 std::optional<CHAMFER_PARAMS> chamfer_params = GetChamferParams( *frame() );
1636
1637 if( chamfer_params.has_value() )
1638 {
1639 pairwise_line_routine = std::make_unique<LINE_CHAMFER_ROUTINE>( frame()->GetModel(),
1640 change_handler,
1641 *chamfer_params );
1642 }
1643 }
1644 else if( aEvent.IsAction( &PCB_ACTIONS::extendLines ) )
1645 {
1646 pairwise_line_routine = std::make_unique<LINE_EXTENSION_ROUTINE>( frame()->GetModel(),
1647 change_handler );
1648 }
1649
1650 if( !pairwise_line_routine )
1651 {
1652 // Didn't construct any mofication routine - user must have cancelled
1653 commit.Revert();
1654 return 0;
1655 }
1656
1657 // Apply the tool to every line pair
1658 alg::for_all_pairs( selection.begin(), selection.end(),
1659 [&]( EDA_ITEM* a, EDA_ITEM* b )
1660 {
1661 if( ( a->GetFlags() & STRUCT_DELETED ) == 0
1662 && ( b->GetFlags() & STRUCT_DELETED ) == 0 )
1663 {
1664 PCB_SHAPE* line_a = static_cast<PCB_SHAPE*>( a );
1665 PCB_SHAPE* line_b = static_cast<PCB_SHAPE*>( b );
1666
1667 pairwise_line_routine->ProcessLinePair( *line_a, *line_b );
1668 }
1669 } );
1670
1671 // Select added and modified items
1672 for( BOARD_ITEM* item : items_to_select_on_success )
1673 m_selectionTool->AddItemToSel( item, true );
1674
1675 // Deselect removed items
1676 for( BOARD_ITEM* item : items_to_deselect_on_success )
1677 m_selectionTool->RemoveItemFromSel( item, true );
1678
1679 if( any_items_removed )
1680 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1681
1682 if( any_items_created )
1683 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1684
1685 // Notify other tools of the changes
1686 m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1687
1688 commit.Push( pairwise_line_routine->GetCommitDescription() );
1689
1690 if( const std::optional<wxString> msg = pairwise_line_routine->GetStatusMessage( segmentCount ) )
1691 frame()->ShowInfoBarMsg( *msg );
1692
1693 return 0;
1694}
1695
1696
1698{
1699 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1700 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1701 {
1702 std::vector<VECTOR2I> pts;
1703
1704 // Iterate from the back so we don't have to worry about removals.
1705 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1706 {
1707 BOARD_ITEM* item = aCollector[i];
1708
1709 if( !item->IsType( { PCB_SHAPE_LOCATE_POLY_T, PCB_ZONE_T } ) )
1710 aCollector.Remove( item );
1711
1712 if( ZONE* zone = dyn_cast<ZONE*>( item ) )
1713 {
1714 if( zone->IsTeardropArea() )
1715 aCollector.Remove( item );
1716 }
1717 }
1718
1719 sTool->FilterCollectorForLockedItems( aCollector );
1720 } );
1721
1722 // Store last used value
1723 static int s_toleranceValue = pcbIUScale.mmToIU( 3 );
1724
1725 WX_UNIT_ENTRY_DIALOG dlg( frame(), _( "Simplify Shapes" ), _( "Tolerance value:" ),
1726 s_toleranceValue );
1727
1728 if( dlg.ShowModal() == wxID_CANCEL )
1729 return 0;
1730
1731 s_toleranceValue = dlg.GetValue();
1732
1733 if( s_toleranceValue <= 0 )
1734 return 0;
1735
1736 BOARD_COMMIT commit{ this };
1737
1738 std::vector<PCB_SHAPE*> shapeList;
1739
1740 for( EDA_ITEM* item : selection )
1741 {
1742 commit.Modify( item );
1743
1744 if( PCB_SHAPE* shape = dyn_cast<PCB_SHAPE*>( item ) )
1745 {
1746 SHAPE_POLY_SET& poly = shape->GetPolyShape();
1747
1748 poly.SimplifyOutlines( s_toleranceValue );
1749 }
1750
1751 if( ZONE* zone = dyn_cast<ZONE*>( item ) )
1752 {
1753 SHAPE_POLY_SET* poly = zone->Outline();
1754
1755 poly->SimplifyOutlines( s_toleranceValue );
1756 }
1757 }
1758
1759 commit.Push( _( "Simplify Polygons" ) );
1760
1761 // Notify other tools of the changes
1763
1764 return 0;
1765}
1766
1767
1769{
1770 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1771 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1772 {
1773 std::vector<VECTOR2I> pts;
1774
1775 // Iterate from the back so we don't have to worry about removals.
1776 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1777 {
1778 BOARD_ITEM* item = aCollector[i];
1779
1780 // We've converted the polygon and rectangle to segments, so drop everything
1781 // that isn't a segment at this point
1782 if( !item->IsType( { PCB_SHAPE_LOCATE_SEGMENT_T, PCB_SHAPE_LOCATE_ARC_T,
1783 PCB_SHAPE_LOCATE_BEZIER_T } ) )
1784 {
1785 aCollector.Remove( item );
1786 }
1787 }
1788
1789 sTool->FilterCollectorForLockedItems( aCollector );
1790 } );
1791
1792 // Store last used value
1793 static int s_toleranceValue = pcbIUScale.mmToIU( 3 );
1794
1795 WX_UNIT_ENTRY_DIALOG dlg( frame(), _( "Heal Shapes" ), _( "Tolerance value:" ),
1796 s_toleranceValue );
1797
1798 if( dlg.ShowModal() == wxID_CANCEL )
1799 return 0;
1800
1801 s_toleranceValue = dlg.GetValue();
1802
1803 if( s_toleranceValue <= 0 )
1804 return 0;
1805
1806 BOARD_COMMIT commit{ this };
1807
1808 std::vector<PCB_SHAPE*> shapeList;
1809
1810 for( EDA_ITEM* item : selection )
1811 {
1812 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
1813 {
1814 shapeList.push_back( shape );
1815 commit.Modify( shape );
1816 }
1817 }
1818
1819 ConnectBoardShapes( shapeList, s_toleranceValue );
1820
1821 commit.Push( _( "Heal Shapes" ) );
1822
1823 // Notify other tools of the changes
1825
1826 return 0;
1827}
1828
1829
1831{
1832 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1833 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1834 {
1835 // Iterate from the back so we don't have to worry about removals.
1836 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1837 {
1838 BOARD_ITEM* item = aCollector[i];
1839
1840 static const std::vector<KICAD_T> polygonBooleanTypes = {
1844 };
1845
1846 if( !item->IsType( polygonBooleanTypes ) )
1847 aCollector.Remove( item );
1848 }
1849
1850 sTool->FilterCollectorForLockedItems( aCollector );
1851 } );
1852
1853 const EDA_ITEM* const last_item = selection.GetLastAddedItem();
1854
1855 // Gather or construct polygon source shapes to merge
1856 std::vector<PCB_SHAPE*> items_to_process;
1857
1858 for( EDA_ITEM* item : selection )
1859 {
1860 items_to_process.push_back( static_cast<PCB_SHAPE*>( item ) );
1861
1862 // put the last one in the selection at the front of the vector
1863 // so it can be used as the property donor and as the basis for the
1864 // boolean operation
1865 if( item == last_item )
1866 std::swap( items_to_process.back(), items_to_process.front() );
1867 }
1868
1869 BOARD_COMMIT commit{ this };
1870
1871 // Handle modifications to existing items by the routine
1872 auto item_modification_handler =
1873 [&]( BOARD_ITEM& aItem )
1874 {
1875 commit.Modify( &aItem );
1876 };
1877
1878 std::vector<BOARD_ITEM*> items_to_select_on_success;
1879
1880 auto item_creation_handler =
1881 [&]( std::unique_ptr<BOARD_ITEM> aItem )
1882 {
1883 items_to_select_on_success.push_back( aItem.get() );
1884 commit.Add( aItem.release() );
1885 };
1886
1887 auto item_removal_handler =
1888 [&]( BOARD_ITEM& aItem )
1889 {
1890 commit.Remove( &aItem );
1891 };
1892
1893 // Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
1894 ITEM_MODIFICATION_ROUTINE::CALLABLE_BASED_HANDLER change_handler( item_creation_handler,
1895 item_modification_handler,
1896 item_removal_handler );
1897
1898 // Construct an appropriate routine
1899 std::unique_ptr<POLYGON_BOOLEAN_ROUTINE> boolean_routine;
1900
1901 const auto create_routine = [&]() -> std::unique_ptr<POLYGON_BOOLEAN_ROUTINE>
1902 {
1903 // (Re-)construct the boolean routine based on the action
1904 // This is done here so that we can re-init the routine if we need to
1905 // go again in the reverse order.
1906
1907 BOARD_ITEM_CONTAINER* const model = frame()->GetModel();
1908 wxCHECK( model, nullptr );
1909
1910 if( aEvent.IsAction( &PCB_ACTIONS::mergePolygons ) )
1911 {
1912 return std::make_unique<POLYGON_MERGE_ROUTINE>( model, change_handler );
1913 }
1914 else if( aEvent.IsAction( &PCB_ACTIONS::subtractPolygons ) )
1915 {
1916 return std::make_unique<POLYGON_SUBTRACT_ROUTINE>( model, change_handler );
1917 }
1918 else if( aEvent.IsAction( &PCB_ACTIONS::intersectPolygons ) )
1919 {
1920 return std::make_unique<POLYGON_INTERSECT_ROUTINE>( model, change_handler );
1921 }
1922 return nullptr;
1923 };
1924
1925 const auto run_routine = [&]()
1926 {
1927 // Perform the operation on each polygon
1928 for( PCB_SHAPE* shape : items_to_process )
1929 boolean_routine->ProcessShape( *shape );
1930
1931 boolean_routine->Finalize();
1932 };
1933
1934 boolean_routine = create_routine();
1935
1936 wxCHECK_MSG( boolean_routine, 0, "Could not find a polygon routine for this action" );
1937
1938 // First run the routine and see what we get
1939 run_routine();
1940
1941 // If we are doing a non-commutative operation (e.g. subtract), and we just got null,
1942 // assume the user meant go in a different opposite order
1943 if( !boolean_routine->IsCommutative() && items_to_select_on_success.empty() )
1944 {
1945 // Clear the commit and the selection
1946 commit.Revert();
1947 items_to_select_on_success.clear();
1948
1949 std::map<const PCB_SHAPE*, VECTOR2I::extended_type> items_area;
1950
1951 for( PCB_SHAPE* shape : items_to_process )
1952 {
1953 VECTOR2I::extended_type area = shape->GetBoundingBox().GetArea();
1954 items_area[shape] = area;
1955 }
1956
1957 // Sort the shapes by their bounding box area in descending order
1958 // This way we will start with the largest shape first and subtract the smaller ones
1959 // This may not work perfectly in all cases, but it works well when the larger
1960 // shape completely contains the smaller ones, which is probably the most common case.
1961 // In other cases, the user will need to select the shapes in the correct order (i.e.
1962 // the largest shape last), or do the subtractions in multiple steps.
1963 std::sort( items_to_process.begin(), items_to_process.end(),
1964 [&]( const PCB_SHAPE* a, const PCB_SHAPE* b )
1965 {
1966 return items_area[a] > items_area[b];
1967 } );
1968
1969 // Run the routine again
1970 boolean_routine = create_routine();
1971 run_routine();
1972 }
1973
1974 // Select new items
1975 for( BOARD_ITEM* item : items_to_select_on_success )
1976 m_selectionTool->AddItemToSel( item, true );
1977
1978 // Notify other tools of the changes
1980
1981 commit.Push( boolean_routine->GetCommitDescription() );
1982
1983 if( const std::optional<wxString> msg = boolean_routine->GetStatusMessage() )
1984 frame()->ShowInfoBarMsg( *msg );
1985
1986 return 0;
1987}
1988
1989
1991{
1993 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1994 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1995 {
1996 } );
1997
1998 // Tracks & vias are treated in a special way:
2000 {
2001 DIALOG_TRACK_VIA_PROPERTIES dlg( editFrame, selection );
2002 dlg.ShowQuasiModal(); // QuasiModal required for NET_SELECTOR
2003 }
2005 {
2006 std::vector<PCB_TABLECELL*> cells;
2007
2008 for( EDA_ITEM* item : selection.Items() )
2009 cells.push_back( static_cast<PCB_TABLECELL*>( item ) );
2010
2011 DIALOG_TABLECELL_PROPERTIES dlg( editFrame, cells );
2012
2013 dlg.ShowModal();
2014
2016 {
2017 PCB_TABLE* table = static_cast<PCB_TABLE*>( cells[0]->GetParent() );
2018 DIALOG_TABLE_PROPERTIES tableDlg( frame(), table );
2019
2020 tableDlg.ShowQuasiModal(); // Scintilla's auto-complete requires quasiModal
2021 }
2022 }
2023 else if( selection.Size() == 1 && selection.Front()->IsBOARD_ITEM() )
2024 {
2025 // Display properties dialog
2026 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
2027
2028 // Do not handle undo buffer, it is done by the properties dialogs
2029 editFrame->OnEditItemRequest( item );
2030
2031 // Notify other tools of the changes
2033 }
2034 else if( selection.Size() == 0 && getView()->IsLayerVisible( LAYER_DRAWINGSHEET ) )
2035 {
2036 DS_PROXY_VIEW_ITEM* ds = editFrame->GetCanvas()->GetDrawingSheet();
2037 VECTOR2D cursorPos = getViewControls()->GetCursorPosition( false );
2038
2039 if( ds && ds->HitTestDrawingSheetItems( getView(), cursorPos ) )
2040 m_toolMgr->PostAction( ACTIONS::pageSettings );
2041 else
2043 }
2044
2045 if( selection.IsHover() )
2046 {
2047 m_toolMgr->RunAction( ACTIONS::selectionClear );
2048 }
2049 else
2050 {
2051 // Check for items becoming invisible and drop them from the selection.
2052
2053 PCB_SELECTION selCopy = selection;
2054 LSET visible = editFrame->GetBoard()->GetVisibleLayers();
2055
2056 for( EDA_ITEM* eda_item : selCopy )
2057 {
2058 if( !eda_item->IsBOARD_ITEM() )
2059 continue;
2060
2061 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( eda_item );
2062
2063 if( !( item->GetLayerSet() & visible ).any() )
2064 m_selectionTool->RemoveItemFromSel( item );
2065 }
2066 }
2067
2068 if( m_dragging )
2069 {
2072 }
2073
2074 return 0;
2075}
2076
2077
2078int EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent )
2079{
2080 if( isRouterActive() )
2081 {
2082 wxBell();
2083 return 0;
2084 }
2085
2087 BOARD_COMMIT localCommit( this );
2088 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
2089
2090 if( !commit )
2091 commit = &localCommit;
2092
2093 // Be sure that there is at least one item that we can modify. If nothing was selected before,
2094 // try looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection)
2095 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2096 [&]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2097 {
2098 sTool->FilterCollectorForMarkers( aCollector );
2099 sTool->FilterCollectorForHierarchy( aCollector, true );
2100 sTool->FilterCollectorForFreePads( aCollector, false );
2101 sTool->FilterCollectorForTableCells( aCollector );
2102
2103 // Filter locked items if in board editor and in free-pad-mode. (If we're not in
2104 // free-pad mode we delay this until the second RequestSelection().)
2105 if( !m_isFootprintEditor && frame()->GetPcbNewSettings()->m_AllowFreePads )
2106 sTool->FilterCollectorForLockedItems( aCollector );
2107 } );
2108
2109 if( selection.Empty() )
2110 return 0;
2111
2112 std::optional<VECTOR2I> oldRefPt;
2113 bool is_hover = selection.IsHover(); // N.B. This must be saved before the second
2114 // call to RequestSelection() below
2115
2116 if( selection.HasReferencePoint() )
2117 oldRefPt = selection.GetReferencePoint();
2118
2119 // Now filter out pads if not in free pads mode. We cannot do this in the first
2120 // RequestSelection() as we need the reference point when a pad is the selection front.
2121 if( !m_isFootprintEditor && !frame()->GetPcbNewSettings()->m_AllowFreePads )
2122 {
2123 selection = m_selectionTool->RequestSelection(
2124 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2125 {
2126 sTool->FilterCollectorForMarkers( aCollector );
2127 sTool->FilterCollectorForHierarchy( aCollector, true );
2128 sTool->FilterCollectorForFreePads( aCollector );
2129 sTool->FilterCollectorForTableCells( aCollector );
2130 sTool->FilterCollectorForLockedItems( aCollector );
2131 } );
2132 }
2133
2134 // Did we filter everything out? If so, don't try to operate further
2135 if( selection.Empty() )
2136 return 0;
2137
2138 // Some PCB_SHAPE must be rotated around their center instead of their start point in
2139 // order to stay to the same place (at least RECT and POLY)
2140 // Note a RECT shape rotated by a not cardinal angle is a POLY shape
2141 bool usePcbShapeCenter = false;
2142
2143 if( selection.Size() == 1 && dynamic_cast<PCB_SHAPE*>( selection.Front() ) )
2144 {
2145 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( selection.Front() );
2146
2147 if( shape->GetShape() == SHAPE_T::RECTANGLE || shape->GetShape() == SHAPE_T::POLY )
2148 usePcbShapeCenter = true;
2149 }
2150
2151 if( selection.Size() == 1 && dynamic_cast<PCB_TEXTBOX*>( selection.Front() ) )
2152 {
2153 selection.SetReferencePoint( static_cast<PCB_TEXTBOX*>( selection.Front() )->GetCenter() );
2154 }
2155 else if( usePcbShapeCenter )
2156 {
2157 selection.SetReferencePoint( static_cast<PCB_SHAPE*>( selection.Front() )->GetCenter() );
2158 }
2159 else
2160 {
2162 }
2163
2164 VECTOR2I refPt = selection.GetReferencePoint();
2165 EDA_ANGLE rotateAngle = TOOL_EVT_UTILS::GetEventRotationAngle( *editFrame, aEvent );
2166
2167 if( frame()->GetCanvas()->GetView()->GetGAL()->IsFlippedX() )
2168 rotateAngle = -rotateAngle;
2169
2170 // Calculate view bounding box
2171 BOX2I viewBBox = selection.Front()->ViewBBox();
2172
2173 for( EDA_ITEM* item : selection )
2174 viewBBox.Merge( item->ViewBBox() );
2175
2176 // Check if the view bounding box will go out of bounds
2177 VECTOR2D rotPos = viewBBox.GetPosition();
2178 VECTOR2D rotEnd = viewBBox.GetEnd();
2179
2180 RotatePoint( &rotPos.x, &rotPos.y, refPt.x, refPt.y, rotateAngle );
2181 RotatePoint( &rotEnd.x, &rotEnd.y, refPt.x, refPt.y, rotateAngle );
2182
2183 typedef std::numeric_limits<int> coord_limits;
2184
2185 int max = coord_limits::max() - COORDS_PADDING;
2186 int min = -max;
2187
2188 bool outOfBounds = rotPos.x < min || rotPos.x > max || rotPos.y < min || rotPos.y > max
2189 || rotEnd.x < min || rotEnd.x > max || rotEnd.y < min || rotEnd.y > max;
2190
2191 if( !outOfBounds )
2192 {
2193 for( EDA_ITEM* item : selection )
2194 {
2195 commit->Modify( item, nullptr, RECURSE_MODE::RECURSE );
2196
2197 if( item->IsBOARD_ITEM() )
2198 {
2199 BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
2200
2201 board_item->Rotate( refPt, rotateAngle );
2202 board_item->Normalize();
2203
2204 if( board_item->Type() == PCB_FOOTPRINT_T )
2205 static_cast<FOOTPRINT*>( board_item )->InvalidateComponentClassCache();
2206 }
2207 }
2208
2209 if( !localCommit.Empty() )
2210 localCommit.Push( _( "Rotate" ) );
2211
2212 if( is_hover && !m_dragging )
2213 m_toolMgr->RunAction( ACTIONS::selectionClear );
2214
2216
2217 if( m_dragging )
2218 {
2221 }
2222 }
2223
2224 // Restore the old reference so any mouse dragging that occurs doesn't make the selection jump
2225 // to this now invalid reference
2226 if( oldRefPt )
2227 selection.SetReferencePoint( *oldRefPt );
2228 else
2229 selection.ClearReferencePoint();
2230
2231 return 0;
2232}
2233
2234
2238static void mirrorPad( PAD& aPad, const VECTOR2I& aMirrorPoint, FLIP_DIRECTION aFlipDirection )
2239{
2240 // TODO(JE) padstacks
2242 aPad.FlipPrimitives( aFlipDirection );
2243
2244 VECTOR2I tmpPt = aPad.GetPosition();
2245 MIRROR( tmpPt, aMirrorPoint, aFlipDirection );
2246 aPad.SetPosition( tmpPt );
2247
2248 tmpPt = aPad.GetOffset( PADSTACK::ALL_LAYERS );
2249 MIRROR( tmpPt, VECTOR2I{ 0, 0 }, aFlipDirection );
2250 aPad.SetOffset( PADSTACK::ALL_LAYERS, tmpPt );
2251
2252 VECTOR2I tmpz = aPad.GetDelta( PADSTACK::ALL_LAYERS );
2253 MIRROR( tmpz, VECTOR2I{ 0, 0 }, aFlipDirection );
2254 aPad.SetDelta( PADSTACK::ALL_LAYERS, tmpz );
2255
2256 aPad.SetOrientation( -aPad.GetOrientation() );
2257}
2258
2259
2260const std::vector<KICAD_T> EDIT_TOOL::MirrorableItems = {
2263 PCB_TEXT_T,
2265 PCB_ZONE_T,
2266 PCB_PAD_T,
2268 PCB_ARC_T,
2269 PCB_VIA_T,
2273};
2274
2275
2276int EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent )
2277{
2278 if( isRouterActive() )
2279 {
2280 wxBell();
2281 return 0;
2282 }
2283
2284 BOARD_COMMIT localCommit( this );
2285 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
2286
2287 if( !commit )
2288 commit = &localCommit;
2289
2290 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2291 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2292 {
2293 sTool->FilterCollectorForMarkers( aCollector );
2294 sTool->FilterCollectorForHierarchy( aCollector, true );
2295 sTool->FilterCollectorForFreePads( aCollector );
2296 sTool->FilterCollectorForLockedItems( aCollector );
2297 } );
2298
2299 if( selection.Empty() )
2300 return 0;
2301
2303 VECTOR2I mirrorPoint = selection.GetReferencePoint();
2304
2307
2308 for( EDA_ITEM* item : selection )
2309 {
2310 if( !item->IsType( MirrorableItems ) )
2311 continue;
2312
2313 commit->Modify( item, nullptr, RECURSE_MODE::RECURSE );
2314
2315 // modify each object as necessary
2316 switch( item->Type() )
2317 {
2318 case PCB_SHAPE_T:
2319 static_cast<PCB_SHAPE*>( item )->Mirror( mirrorPoint, flipDirection );
2320 break;
2321
2322 case PCB_ZONE_T:
2323 static_cast<ZONE*>( item )->Mirror( mirrorPoint, flipDirection );
2324 break;
2325
2326 case PCB_FIELD_T:
2327 case PCB_TEXT_T:
2328 static_cast<PCB_TEXT*>( item )->Mirror( mirrorPoint, flipDirection );
2329 break;
2330
2331 case PCB_TEXTBOX_T:
2332 static_cast<PCB_TEXTBOX*>( item )->Mirror( mirrorPoint, flipDirection );
2333 break;
2334
2335 case PCB_TABLE_T:
2336 // JEY TODO: tables
2337 break;
2338
2339 case PCB_PAD_T:
2340 mirrorPad( *static_cast<PAD*>( item ), mirrorPoint, flipDirection );
2341 break;
2342
2343 case PCB_TRACE_T:
2344 case PCB_ARC_T:
2345 case PCB_VIA_T:
2346 static_cast<PCB_TRACK*>( item )->Mirror( mirrorPoint, flipDirection );
2347 break;
2348
2349 case PCB_GROUP_T:
2350 static_cast<PCB_GROUP*>( item )->Mirror( mirrorPoint, flipDirection );
2351 break;
2352
2353 case PCB_GENERATOR_T:
2354 static_cast<PCB_GENERATOR*>( item )->Mirror( mirrorPoint, flipDirection );
2355 break;
2356
2357 case PCB_POINT_T:
2358 static_cast<PCB_POINT*>( item )->Mirror( mirrorPoint, flipDirection );
2359 break;
2360
2361 default:
2362 // it's likely the commit object is wrong if you get here
2363 UNIMPLEMENTED_FOR( item->GetClass() );
2364 }
2365 }
2366
2367 if( !localCommit.Empty() )
2368 localCommit.Push( _( "Mirror" ) );
2369
2370 if( selection.IsHover() && !m_dragging )
2371 m_toolMgr->RunAction( ACTIONS::selectionClear );
2372
2374
2375 if( m_dragging )
2376 {
2379 }
2380
2381 return 0;
2382}
2383
2384
2386{
2387 if( isRouterActive() )
2388 {
2389 wxBell();
2390 return 0;
2391 }
2392
2393 BOARD_COMMIT localCommit( this );
2394 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
2395
2396 if( !commit )
2397 commit = &localCommit;
2398
2399 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2400 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2401 {
2402 sTool->FilterCollectorForHierarchy( aCollector, true );
2403 sTool->FilterCollectorForLockedItems( aCollector );
2404 } );
2405
2406 if( selection.Empty() )
2407 return 0;
2408
2409 auto setJustify =
2410 [&]( EDA_TEXT* aTextItem )
2411 {
2412 if( aEvent.Matches( ACTIONS::leftJustify.MakeEvent() ) )
2413 aTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
2414 else if( aEvent.Matches( ACTIONS::centerJustify.MakeEvent() ) )
2415 aTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
2416 else
2417 aTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
2418 };
2419
2420 for( EDA_ITEM* item : selection )
2421 {
2422 if( item->Type() == PCB_FIELD_T || item->Type() == PCB_TEXT_T )
2423 {
2424 commit->Modify( item );
2425 setJustify( static_cast<PCB_TEXT*>( item ) );
2426 }
2427 else if( item->Type() == PCB_TEXTBOX_T )
2428 {
2429 commit->Modify( item );
2430 setJustify( static_cast<PCB_TEXTBOX*>( item ) );
2431 }
2432 }
2433
2434 if( !localCommit.Empty() )
2435 {
2436 if( aEvent.Matches( ACTIONS::leftJustify.MakeEvent() ) )
2437 localCommit.Push( _( "Left Justify" ) );
2438 else if( aEvent.Matches( ACTIONS::centerJustify.MakeEvent() ) )
2439 localCommit.Push( _( "Center Justify" ) );
2440 else
2441 localCommit.Push( _( "Right Justify" ) );
2442 }
2443
2444 if( selection.IsHover() && !m_dragging )
2445 m_toolMgr->RunAction( ACTIONS::selectionClear );
2446
2448
2449 if( m_dragging )
2450 {
2453 }
2454
2455 return 0;
2456}
2457
2458
2459int EDIT_TOOL::Flip( const TOOL_EVENT& aEvent )
2460{
2461 if( isRouterActive() )
2462 {
2463 wxBell();
2464 return 0;
2465 }
2466
2467 BOARD_COMMIT localCommit( this );
2468 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
2469
2470 if( !commit )
2471 commit = &localCommit;
2472
2473 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2474 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2475 {
2476 sTool->FilterCollectorForMarkers( aCollector );
2477 sTool->FilterCollectorForHierarchy( aCollector, true );
2478 sTool->FilterCollectorForFreePads( aCollector );
2479 sTool->FilterCollectorForTableCells( aCollector );
2480 sTool->FilterCollectorForLockedItems( aCollector );
2481 } );
2482
2483 if( selection.Empty() )
2484 return 0;
2485
2486 std::optional<VECTOR2I> oldRefPt;
2487
2488 if( selection.HasReferencePoint() )
2489 oldRefPt = selection.GetReferencePoint();
2490
2492
2493 // Flip around the anchor for footprints, and the bounding box center for board items
2494 VECTOR2I refPt = IsFootprintEditor() ? VECTOR2I( 0, 0 ) : selection.GetCenter();
2495
2496 // If only one item selected, flip around the selection or item anchor point (instead
2497 // of the bounding box center) to avoid moving the item anchor
2498 // but only if the item is not a PCB_SHAPE with SHAPE_T::RECTANGLE shape, because
2499 // for this shape the flip transform swap start and end coordinates and move the shape.
2500 // So using the center of the shape is better (the shape does not move)
2501 if( selection.GetSize() == 1 )
2502 {
2503 PCB_SHAPE* item = dynamic_cast<PCB_SHAPE*>( selection.GetItem( 0 ) );
2504
2505 if( !item || item->GetShape() != SHAPE_T::RECTANGLE )
2506 refPt = selection.GetReferencePoint();
2507 }
2508
2509 const FLIP_DIRECTION flipDirection = frame()->GetPcbNewSettings()->m_FlipDirection;
2510
2511 for( EDA_ITEM* item : selection )
2512 {
2513 if( !item->IsBOARD_ITEM() )
2514 continue;
2515
2516 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
2517
2518 commit->Modify( boardItem, nullptr, RECURSE_MODE::RECURSE );
2519
2520 boardItem->Flip( refPt, flipDirection );
2521 boardItem->Normalize();
2522
2523 if( boardItem->Type() == PCB_FOOTPRINT_T )
2524 static_cast<FOOTPRINT*>( boardItem )->InvalidateComponentClassCache();
2525 }
2526
2527 if( !localCommit.Empty() )
2528 localCommit.Push( _( "Change Side / Flip" ) );
2529
2530 if( selection.IsHover() && !m_dragging )
2531 m_toolMgr->RunAction( ACTIONS::selectionClear );
2532
2534
2535 if( m_dragging )
2536 {
2539 }
2540
2541 // Restore the old reference so any mouse dragging that occurs doesn't make the selection jump
2542 // to this now invalid reference
2543 if( oldRefPt )
2544 selection.SetReferencePoint( *oldRefPt );
2545 else
2546 selection.ClearReferencePoint();
2547
2548 return 0;
2549}
2550
2551
2552void EDIT_TOOL::DeleteItems( const PCB_SELECTION& aItems, bool aIsCut )
2553{
2555 BOARD_COMMIT commit( this );
2556 int commitFlags = 0;
2557
2558 // As we are about to remove items, they have to be removed from the selection first
2559 m_toolMgr->RunAction( ACTIONS::selectionClear );
2560
2561 int itemsDeleted = 0;
2562 int fieldsHidden = 0;
2563 int fieldsAlreadyHidden = 0;
2564
2565 for( EDA_ITEM* item : aItems )
2566 {
2567 if( !item->IsBOARD_ITEM() )
2568 continue;
2569
2570 BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
2571 FOOTPRINT* parentFP = board_item->GetParentFootprint();
2572
2573 switch( item->Type() )
2574 {
2575 case PCB_FIELD_T:
2576 {
2577 PCB_FIELD* field = static_cast<PCB_FIELD*>( board_item );
2578
2579 wxASSERT( parentFP );
2580 commit.Modify( parentFP );
2581
2582 if( field->IsVisible() )
2583 {
2584 field->SetVisible( false );
2585 fieldsHidden++;
2586 }
2587 else
2588 {
2589 fieldsAlreadyHidden++;
2590 }
2591
2592 getView()->Update( parentFP );
2593 break;
2594 }
2595
2596 case PCB_TEXT_T:
2597 case PCB_SHAPE_T:
2598 case PCB_TEXTBOX_T:
2599 case PCB_TABLE_T:
2601 case PCB_DIMENSION_T:
2602 case PCB_DIM_ALIGNED_T:
2603 case PCB_DIM_LEADER_T:
2604 case PCB_DIM_CENTER_T:
2605 case PCB_DIM_RADIAL_T:
2607 case PCB_POINT_T:
2608 commit.Remove( board_item );
2609 itemsDeleted++;
2610 break;
2611
2612 case PCB_TABLECELL_T:
2613 // Clear contents of table cell
2614 commit.Modify( board_item );
2615 static_cast<PCB_TABLECELL*>( board_item )->SetText( wxEmptyString );
2616 itemsDeleted++;
2617 break;
2618
2619 case PCB_GROUP_T:
2620 board_item->RunOnChildren(
2621 [&commit]( BOARD_ITEM* aItem )
2622 {
2623 commit.Remove( aItem );
2624 },
2626
2627 commit.Remove( board_item );
2628 itemsDeleted++;
2629 break;
2630
2631 case PCB_PAD_T:
2632 if( IsFootprintEditor() || frame()->GetPcbNewSettings()->m_AllowFreePads )
2633 {
2634 commit.Remove( board_item );
2635 itemsDeleted++;
2636 }
2637
2638 break;
2639
2640 case PCB_ZONE_T:
2641 // We process the zones special so that cutouts can be deleted when the delete
2642 // tool is called from inside a cutout when the zone is selected.
2643 // Only interact with cutouts when deleting and a single item is selected
2644 if( !aIsCut && aItems.GetSize() == 1 )
2645 {
2646 VECTOR2I curPos = getViewControls()->GetCursorPosition();
2647 ZONE* zone = static_cast<ZONE*>( board_item );
2648
2649 int outlineIdx, holeIdx;
2650
2651 if( zone->HitTestCutout( curPos, &outlineIdx, &holeIdx ) )
2652 {
2653 // Remove the cutout
2654 commit.Modify( zone );
2655 zone->RemoveCutout( outlineIdx, holeIdx );
2656 zone->UnFill();
2657
2658 // Update the display
2659 zone->HatchBorder();
2660 canvas()->Refresh();
2661
2662 // Restore the selection on the original zone
2663 m_toolMgr->RunAction<EDA_ITEM*>( ACTIONS::selectItem, zone );
2664
2665 break;
2666 }
2667 }
2668
2669 // Remove the entire zone otherwise
2670 commit.Remove( board_item );
2671 itemsDeleted++;
2672 break;
2673
2674 case PCB_GENERATOR_T:
2675 {
2676 PCB_GENERATOR* generator = static_cast<PCB_GENERATOR*>( board_item );
2677
2679 {
2680 m_toolMgr->RunSynchronousAction<PCB_GENERATOR*>( PCB_ACTIONS::genRemove, &commit, generator );
2681 commit.Push( _( "Delete" ), commitFlags );
2682 commitFlags |= APPEND_UNDO;
2683 }
2684 else
2685 {
2686 for( EDA_ITEM* member : generator->GetItems() )
2687 commit.Remove( member );
2688
2689 commit.Remove( board_item );
2690 }
2691
2692 itemsDeleted++;
2693 break;
2694 }
2695
2696 default:
2697 commit.Remove( board_item );
2698 itemsDeleted++;
2699 break;
2700 }
2701 }
2702
2703 // If the entered group has been emptied then leave it.
2704 PCB_GROUP* enteredGroup = m_selectionTool->GetEnteredGroup();
2705
2706 if( enteredGroup && enteredGroup->GetItems().empty() )
2707 m_selectionTool->ExitGroup();
2708
2709 if( aIsCut )
2710 {
2711 commit.Push( _( "Cut" ), commitFlags );
2712 }
2713 else if( itemsDeleted == 0 )
2714 {
2715 if( fieldsHidden == 1 )
2716 commit.Push( _( "Hide Field" ), commitFlags );
2717 else if( fieldsHidden > 1 )
2718 commit.Push( _( "Hide Fields" ), commitFlags );
2719 else if( fieldsAlreadyHidden > 0 )
2720 editFrame->ShowInfoBarError( _( "Use the Footprint Properties dialog to remove fields." ) );
2721 }
2722 else
2723 {
2724 commit.Push( _( "Delete" ), commitFlags );
2725 }
2726}
2727
2728
2729int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent )
2730{
2732
2733 editFrame->PushTool( aEvent );
2734
2735 std::vector<BOARD_ITEM*> lockedItems;
2736 Activate();
2737
2738 // get a copy instead of reference (as we're going to clear the selection before removing items)
2739 PCB_SELECTION selectionCopy;
2742
2743 // If we are in a "Cut" operation, then the copied selection exists already and we want to
2744 // delete exactly that; no more, no fewer. Any filtering for locked items must be done in
2745 // the copyToClipboard() routine.
2746 if( isCut )
2747 {
2748 selectionCopy = m_selectionTool->GetSelection();
2749 }
2750 else
2751 {
2752 // When not in free-pad mode we normally auto-promote selected pads to their parent
2753 // footprints. But this is probably a little too dangerous for a destructive operation,
2754 // so we just do the promotion but not the deletion (allowing for a second delete to do
2755 // it if that's what the user wanted).
2756 selectionCopy = m_selectionTool->RequestSelection(
2757 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2758 {
2759 sTool->FilterCollectorForHierarchy( aCollector, true );
2760 sTool->FilterCollectorForLockedItems( aCollector );
2761 } );
2762
2763 size_t beforeFPCount = selectionCopy.CountType( PCB_FOOTPRINT_T );
2764
2765 selectionCopy = m_selectionTool->RequestSelection(
2766 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2767 {
2768 sTool->FilterCollectorForHierarchy( aCollector, true );
2769 sTool->FilterCollectorForFreePads( aCollector );
2770 sTool->FilterCollectorForLockedItems( aCollector );
2771 } );
2772
2773 if( !selectionCopy.IsHover()
2774 && m_selectionTool->GetSelection().CountType( PCB_FOOTPRINT_T ) > beforeFPCount )
2775 {
2776 wxBell();
2777 canvas()->Refresh();
2778 editFrame->PopTool( aEvent );
2779 return 0;
2780 }
2781
2782 // In "alternative" mode, we expand selected track items to their full connection.
2783 if( isAlt && ( selectionCopy.HasType( PCB_TRACE_T ) || selectionCopy.HasType( PCB_VIA_T ) ) )
2785 }
2786
2787 DeleteItems( selectionCopy, isCut );
2788 canvas()->Refresh();
2789
2790 editFrame->PopTool( aEvent );
2791 return 0;
2792}
2793
2794
2796{
2797 if( isRouterActive() )
2798 {
2799 wxBell();
2800 return 0;
2801 }
2802
2803 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2804 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2805 {
2806 sTool->FilterCollectorForMarkers( aCollector );
2807 sTool->FilterCollectorForHierarchy( aCollector, true );
2808 sTool->FilterCollectorForFreePads( aCollector, false );
2809 sTool->FilterCollectorForTableCells( aCollector );
2810 sTool->FilterCollectorForLockedItems( aCollector );
2811 } );
2812
2813 if( selection.Empty() )
2814 return 0;
2815
2816 VECTOR2I translation;
2817 EDA_ANGLE rotation;
2818 ROTATION_ANCHOR rotationAnchor = selection.Size() > 1 ? ROTATE_AROUND_SEL_CENTER
2820
2821 // TODO: Implement a visible bounding border at the edge
2822 BOX2I sel_box = selection.GetBoundingBox();
2823
2824 DIALOG_MOVE_EXACT dialog( frame(), translation, rotation, rotationAnchor, sel_box );
2825 int ret = dialog.ShowModal();
2826
2827 if( ret == wxID_OK )
2828 {
2829 BOARD_COMMIT commit( this );
2830 EDA_ANGLE angle = rotation;
2831 VECTOR2I rp = selection.GetCenter();
2832 VECTOR2I selCenter( rp.x, rp.y );
2833
2834 // Make sure the rotation is from the right reference point
2835 selCenter += translation;
2836
2837 if( !frame()->GetPcbNewSettings()->m_Display.m_DisplayInvertYAxis )
2838 rotation = -rotation;
2839
2840 for( EDA_ITEM* item : selection )
2841 {
2842 if( !item->IsBOARD_ITEM() )
2843 continue;
2844
2845 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
2846
2847 commit.Modify( boardItem, nullptr, RECURSE_MODE::RECURSE );
2848
2849 if( !boardItem->GetParent() || !boardItem->GetParent()->IsSelected() )
2850 boardItem->Move( translation );
2851
2852 switch( rotationAnchor )
2853 {
2855 boardItem->Rotate( boardItem->GetPosition(), angle );
2856 break;
2858 boardItem->Rotate( selCenter, angle );
2859 break;
2861 boardItem->Rotate( frame()->GetScreen()->m_LocalOrigin, angle );
2862 break;
2864 boardItem->Rotate( board()->GetDesignSettings().GetAuxOrigin(), angle );
2865 break;
2866 }
2867
2868 if( !m_dragging )
2869 getView()->Update( boardItem );
2870 }
2871
2872 commit.Push( _( "Move Exactly" ) );
2873
2874 if( selection.IsHover() )
2875 m_toolMgr->RunAction( ACTIONS::selectionClear );
2876
2878
2879 if( m_dragging )
2880 {
2883 }
2884 }
2885
2886 return 0;
2887}
2888
2889
2891{
2892 if( isRouterActive() )
2893 {
2894 wxBell();
2895 return 0;
2896 }
2897
2898 bool increment = aEvent.IsAction( &PCB_ACTIONS::duplicateIncrement );
2899
2900 // Be sure that there is at least one item that we can modify
2901 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2902 []( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2903 {
2904 sTool->FilterCollectorForMarkers( aCollector );
2905 sTool->FilterCollectorForHierarchy( aCollector, true );
2906 sTool->FilterCollectorForFreePads( aCollector, true );
2907 sTool->FilterCollectorForTableCells( aCollector );
2908 } );
2909
2910 if( selection.Empty() )
2911 return 0;
2912
2913 // Duplicating tuning patterns alone is not supported
2914 if( selection.Size() == 1 && selection.CountType( PCB_GENERATOR_T ) )
2915 return 0;
2916
2917 // we have a selection to work on now, so start the tool process
2919 BOARD_COMMIT commit( this );
2920 FOOTPRINT* parentFootprint = nullptr;
2921
2923 parentFootprint = editFrame->GetBoard()->GetFirstFootprint();
2924
2925 // If the selection was given a hover, we do not keep the selection after completion
2926 bool is_hover = selection.IsHover();
2927
2928 std::vector<BOARD_ITEM*> new_items;
2929 new_items.reserve( selection.Size() );
2930
2931 // Each selected item is duplicated and pushed to new_items list
2932 // Old selection is cleared, and new items are then selected.
2933 for( EDA_ITEM* item : selection )
2934 {
2935 if( !item->IsBOARD_ITEM() )
2936 continue;
2937
2938 BOARD_ITEM* dupe_item = nullptr;
2939 BOARD_ITEM* orig_item = static_cast<BOARD_ITEM*>( item );
2940
2941 if( !m_isFootprintEditor && orig_item->GetParentFootprint() )
2942 {
2943 // No sub-footprint modifications allowed outside of footprint editor
2944 }
2945 else
2946 {
2947 switch( orig_item->Type() )
2948 {
2949 case PCB_FOOTPRINT_T:
2950 case PCB_TEXT_T:
2951 case PCB_TEXTBOX_T:
2953 case PCB_SHAPE_T:
2954 case PCB_TRACE_T:
2955 case PCB_ARC_T:
2956 case PCB_VIA_T:
2957 case PCB_ZONE_T:
2958 case PCB_TARGET_T:
2959 case PCB_POINT_T:
2960 case PCB_DIM_ALIGNED_T:
2961 case PCB_DIM_CENTER_T:
2962 case PCB_DIM_RADIAL_T:
2964 case PCB_DIM_LEADER_T:
2966 dupe_item = parentFootprint->DuplicateItem( true, &commit, orig_item );
2967 else
2968 dupe_item = orig_item->Duplicate( true, &commit );
2969
2970 // Clear the selection flag here, otherwise the PCB_SELECTION_TOOL
2971 // will not properly select it later on
2972 dupe_item->ClearSelected();
2973
2974 new_items.push_back( dupe_item );
2975 commit.Add( dupe_item );
2976 break;
2977
2978 case PCB_FIELD_T:
2979 // PCB_FIELD items are specific items (not only graphic, but are properies)
2980 // and cannot be duplicated like other footprint items. So skip it:
2981 orig_item->ClearSelected();
2982 break;
2983
2984 case PCB_PAD_T:
2985 dupe_item = parentFootprint->DuplicateItem( true, &commit, orig_item );
2986
2987 if( increment && static_cast<PAD*>( dupe_item )->CanHaveNumber() )
2988 {
2989 PAD_TOOL* padTool = m_toolMgr->GetTool<PAD_TOOL>();
2990 wxString padNumber = padTool->GetLastPadNumber();
2991 padNumber = parentFootprint->GetNextPadNumber( padNumber );
2992 padTool->SetLastPadNumber( padNumber );
2993 static_cast<PAD*>( dupe_item )->SetNumber( padNumber );
2994 }
2995
2996 // Clear the selection flag here, otherwise the PCB_SELECTION_TOOL
2997 // will not properly select it later on
2998 dupe_item->ClearSelected();
2999
3000 new_items.push_back( dupe_item );
3001 commit.Add( dupe_item );
3002 break;
3003
3004 case PCB_TABLE_T:
3005 // JEY TODO: tables
3006 break;
3007
3008 case PCB_GENERATOR_T:
3009 case PCB_GROUP_T:
3010 dupe_item = static_cast<PCB_GROUP*>( orig_item )->DeepDuplicate( true, &commit );
3011
3012 dupe_item->RunOnChildren(
3013 [&]( BOARD_ITEM* aItem )
3014 {
3015 aItem->ClearSelected();
3016 new_items.push_back( aItem );
3017 commit.Add( aItem );
3018 },
3020
3021 dupe_item->ClearSelected();
3022 new_items.push_back( dupe_item );
3023 commit.Add( dupe_item );
3024 break;
3025
3026 default:
3027 UNIMPLEMENTED_FOR( orig_item->Type() );
3028 break;
3029 }
3030 }
3031 }
3032
3033 // Clear the old selection first
3034 m_toolMgr->RunAction( ACTIONS::selectionClear );
3035
3036 // Select the new items
3037 EDA_ITEMS nItems( new_items.begin(), new_items.end() );
3038 m_toolMgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &nItems );
3039
3040 // record the new items as added
3041 if( !selection.Empty() )
3042 {
3043 editFrame->DisplayToolMsg( wxString::Format( _( "Duplicated %d item(s)" ), (int) new_items.size() ) );
3044
3045 // If items were duplicated, pick them up
3046 if( doMoveSelection( aEvent, &commit, true ) )
3047 commit.Push( _( "Duplicate" ) );
3048 else
3049 commit.Revert();
3050
3051 // Deselect the duplicated item if we originally started as a hover selection
3052 if( is_hover )
3053 m_toolMgr->RunAction( ACTIONS::selectionClear );
3054 }
3055
3056 return 0;
3057}
3058
3059
3061{
3062 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
3063 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
3064 {
3065 for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
3066 {
3067 switch( aCollector[i]->Type() )
3068 {
3069 case PCB_PAD_T:
3070 case PCB_TEXT_T:
3071 break;
3072 default:
3073 aCollector.Remove( i );
3074 break;
3075 }
3076 }
3077
3078 sTool->FilterCollectorForLockedItems( aCollector );
3079 } );
3080
3081 if( selection.Empty() )
3082 return 0;
3083
3084 ACTIONS::INCREMENT param = { 1, 0 };
3085
3086 if( aEvent.HasParameter() )
3087 param = aEvent.Parameter<ACTIONS::INCREMENT>();
3088
3089 STRING_INCREMENTER incrementer;
3090 incrementer.SetSkipIOSQXZ( true );
3091
3092 // If we're coming via another action like 'Move', use that commit
3093 BOARD_COMMIT localCommit( m_toolMgr );
3094 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
3095
3096 if( !commit )
3097 commit = &localCommit;
3098
3099 for( EDA_ITEM* item : selection )
3100 {
3101 switch( item->Type() )
3102 {
3103 case PCB_PAD_T:
3104 {
3105 // Only increment pad numbers in the footprint editor
3106 if( !m_isFootprintEditor )
3107 break;
3108
3109 PAD& pad = static_cast<PAD&>( *item );
3110
3111 if( !pad.CanHaveNumber() )
3112 continue;
3113
3114 // Increment on the pad numbers
3115 std::optional<wxString> newNumber = incrementer.Increment( pad.GetNumber(), param.Delta, param.Index );
3116
3117 if( newNumber )
3118 {
3119 commit->Modify( &pad );
3120 pad.SetNumber( *newNumber );
3121 }
3122
3123 break;
3124 }
3125 case PCB_TEXT_T:
3126 {
3127 PCB_TEXT& text = static_cast<PCB_TEXT&>( *item );
3128
3129 std::optional<wxString> newText = incrementer.Increment( text.GetText(), param.Delta, param.Index );
3130
3131 if( newText )
3132 {
3133 commit->Modify( &text );
3134 text.SetText( *newText );
3135 }
3136
3137 break;
3138 }
3139 default:
3140 break;
3141 }
3142 }
3143
3144 if( selection.Front()->IsMoving() )
3145 m_toolMgr->PostAction( ACTIONS::refreshPreview );
3146
3147 commit->Push( _( "Increment" ) );
3148
3149 return 0;
3150}
3151
3152
3154{
3155 for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
3156 {
3157 if( aCollector[i]->Type() != PCB_PAD_T )
3158 aCollector.Remove( i );
3159 }
3160}
3161
3162
3164{
3165 for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
3166 {
3167 if( aCollector[i]->Type() != PCB_FOOTPRINT_T )
3168 aCollector.Remove( i );
3169 }
3170}
3171
3172
3174{
3175 // Can't modify an empty group
3176 if( aSelection.Empty() )
3177 return false;
3178
3179 if( ( m_dragging || aSelection[0]->IsMoving() ) && aSelection.HasReferencePoint() )
3180 return false;
3181
3182 // When there is only one item selected, the reference point is its position...
3183 if( aSelection.Size() == 1 && aSelection.Front()->Type() != PCB_TABLE_T )
3184 {
3185 if( aSelection.Front()->IsBOARD_ITEM() )
3186 {
3187 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aSelection.Front() );
3188 aSelection.SetReferencePoint( item->GetPosition() );
3189 }
3190 }
3191 // ...otherwise modify items with regard to the grid-snapped center position
3192 else
3193 {
3194 PCB_GRID_HELPER grid( m_toolMgr, frame()->GetMagneticItemsSettings() );
3195 VECTOR2I refPt = aSelection.GetCenter();
3196
3197 // Exclude text in the footprint editor if there's anything else selected
3199 {
3200 BOX2I nonFieldsBBox;
3201
3202 for( EDA_ITEM* item : aSelection.Items() )
3203 {
3204 if( !item->IsType( { PCB_TEXT_T, PCB_FIELD_T } ) )
3205 nonFieldsBBox.Merge( item->GetBoundingBox() );
3206 }
3207
3208 if( nonFieldsBBox.IsValid() )
3209 refPt = nonFieldsBBox.GetCenter();
3210 }
3211
3212 aSelection.SetReferencePoint( grid.BestSnapAnchor( refPt, nullptr ) );
3213 }
3214
3215 return true;
3216}
3217
3218
3219bool EDIT_TOOL::pickReferencePoint( const wxString& aTooltip, const wxString& aSuccessMessage,
3220 const wxString& aCanceledMessage, VECTOR2I& aReferencePoint )
3221{
3222 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
3224 std::optional<VECTOR2I> pickedPoint;
3225 bool done = false;
3226
3227 m_statusPopup->SetText( aTooltip );
3228
3230 picker->SetSnapping( true );
3231 picker->SetCursor( KICURSOR::PLACE );
3232 picker->ClearHandlers();
3233
3234 const auto setPickerLayerSet =
3235 [&]()
3236 {
3237 MAGNETIC_SETTINGS* magSettings = editFrame->GetMagneticItemsSettings();
3238 LSET layerFilter;
3239
3240 if( !magSettings->allLayers )
3241 layerFilter = LSET( { editFrame->GetActiveLayer() } );
3242 else
3243 layerFilter = LSET::AllLayersMask();
3244
3245 picker->SetLayerSet( layerFilter );
3246 };
3247
3248 // Initial set
3249 setPickerLayerSet();
3250
3251 picker->SetClickHandler(
3252 [&]( const VECTOR2D& aPoint ) -> bool
3253 {
3254 pickedPoint = aPoint;
3255
3256 if( !aSuccessMessage.empty() )
3257 {
3258 m_statusPopup->SetText( aSuccessMessage );
3259 m_statusPopup->Expire( 800 );
3260 }
3261 else
3262 {
3263 m_statusPopup->Hide();
3264 }
3265
3266 return false; // we don't need any more points
3267 } );
3268
3269 picker->SetMotionHandler(
3270 [&]( const VECTOR2D& aPos )
3271 {
3272 m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, -50 ) );
3273 } );
3274
3275 picker->SetCancelHandler(
3276 [&]()
3277 {
3278 if( !aCanceledMessage.empty() )
3279 {
3280 m_statusPopup->SetText( aCanceledMessage );
3281 m_statusPopup->Expire( 800 );
3282 }
3283 else
3284 {
3285 m_statusPopup->Hide();
3286 }
3287 } );
3288
3289 picker->SetFinalizeHandler(
3290 [&]( const int& aFinalState )
3291 {
3292 done = true;
3293 } );
3294
3295 m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, -50 ) );
3296 m_statusPopup->Popup();
3297 canvas()->SetStatusPopup( m_statusPopup->GetPanel() );
3298
3299 m_toolMgr->RunAction( ACTIONS::pickerSubTool );
3300
3301 while( !done )
3302 {
3303 // Pass events unless we receive a null event, then we must shut down
3304 if( TOOL_EVENT* evt = Wait() )
3305 {
3306 if( evt->Matches( PCB_EVENTS::SnappingModeChangedByKeyEvent() ) )
3307 {
3308 // Update the layer set when the snapping mode changes
3309 setPickerLayerSet();
3310 }
3311
3312 evt->SetPassEvent();
3313 }
3314 else
3315 {
3316 break;
3317 }
3318 }
3319
3320 picker->ClearHandlers();
3321
3322 // Ensure statusPopup is hidden after use and before deleting it:
3323 canvas()->SetStatusPopup( nullptr );
3324 m_statusPopup->Hide();
3325
3326 if( pickedPoint )
3327 aReferencePoint = *pickedPoint;
3328
3329 return pickedPoint.has_value();
3330}
3331
3332
3334{
3335 CLIPBOARD_IO io;
3336 PCB_GRID_HELPER grid( m_toolMgr, getEditFrame<PCB_BASE_EDIT_FRAME>()->GetMagneticItemsSettings() );
3337 TOOL_EVENT selectReferencePoint( aEvent.Category(), aEvent.Action(),
3338 "pcbnew.InteractiveEdit.selectReferencePoint",
3340
3341 frame()->PushTool( selectReferencePoint );
3342 Activate();
3343
3344 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
3345 [&]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
3346 {
3347 sTool->FilterCollectorForHierarchy( aCollector, true );
3348 sTool->FilterCollectorForMarkers( aCollector );
3349
3350 if( aEvent.IsAction( &ACTIONS::cut ) )
3351 sTool->FilterCollectorForLockedItems( aCollector );
3352 } );
3353
3354 if( !selection.Empty() )
3355 {
3356 std::vector<BOARD_ITEM*> items;
3357
3358 for( EDA_ITEM* item : selection )
3359 {
3360 if( item->IsBOARD_ITEM() )
3361 items.push_back( static_cast<BOARD_ITEM*>( item ) );
3362 }
3363
3364 VECTOR2I refPoint;
3365
3367 {
3368 if( !pickReferencePoint( _( "Select reference point for the copy..." ),
3369 _( "Selection copied" ), _( "Copy canceled" ), refPoint ) )
3370 {
3371 frame()->PopTool( selectReferencePoint );
3372 return 0;
3373 }
3374 }
3375 else
3376 {
3377 refPoint = grid.BestDragOrigin( getViewControls()->GetCursorPosition(), items );
3378 }
3379
3380 selection.SetReferencePoint( refPoint );
3381
3382 io.SetBoard( board() );
3384 frame()->SetStatusText( _( "Selection copied" ) );
3385 }
3386
3387 frame()->PopTool( selectReferencePoint );
3388
3389 if( selection.IsHover() )
3390 m_selectionTool->ClearSelection();
3391
3392 return 0;
3393}
3394
3395
3397{
3398 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
3399 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
3400 {
3401 // Anything unsupported will just be ignored
3402 } );
3403
3404 if( selection.IsHover() )
3405 m_selectionTool->ClearSelection();
3406
3407 const auto getItemText = [&]( const BOARD_ITEM& aItem ) -> wxString
3408 {
3409 switch( aItem.Type() )
3410 {
3411 case PCB_TEXT_T:
3412 case PCB_FIELD_T:
3413 case PCB_DIM_ALIGNED_T:
3414 case PCB_DIM_LEADER_T:
3415 case PCB_DIM_CENTER_T:
3416 case PCB_DIM_RADIAL_T:
3418 {
3419 // These can all go via the PCB_TEXT class
3420 const PCB_TEXT& text = static_cast<const PCB_TEXT&>( aItem );
3421 return text.GetShownText( true );
3422 }
3423 case PCB_TEXTBOX_T:
3424 case PCB_TABLECELL_T:
3425 {
3426 // This one goes via EDA_TEXT
3427 const PCB_TEXTBOX& textBox = static_cast<const PCB_TEXTBOX&>( aItem );
3428 return textBox.GetShownText( true );
3429 }
3430 case PCB_TABLE_T:
3431 {
3432 const PCB_TABLE& table = static_cast<const PCB_TABLE&>( aItem );
3433 wxString s;
3434
3435 for( int row = 0; row < table.GetRowCount(); ++row )
3436 {
3437 for( int col = 0; col < table.GetColCount(); ++col )
3438 {
3439 const PCB_TABLECELL* cell = table.GetCell( row, col );
3440 s << cell->GetShownText( true );
3441
3442 if( col < table.GetColCount() - 1 )
3443 {
3444 s << '\t';
3445 }
3446 }
3447
3448 if( row < table.GetRowCount() - 1 )
3449 {
3450 s << '\n';
3451 }
3452 }
3453 return s;
3454 }
3455 default:
3456 // No string representation for this item type
3457 break;
3458 }
3459 return wxEmptyString;
3460 };
3461
3462 wxArrayString itemTexts;
3463
3464 for( EDA_ITEM* item : selection )
3465 {
3466 if( item->IsBOARD_ITEM() )
3467 {
3468 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
3469 wxString itemText = getItemText( *boardItem );
3470
3471 itemText.Trim( false ).Trim( true );
3472
3473 if( !itemText.IsEmpty() )
3474 {
3475 itemTexts.Add( std::move( itemText ) );
3476 }
3477 }
3478 }
3479
3480 // Send the text to the clipboard
3481 if( !itemTexts.empty() )
3482 {
3483 SaveClipboard( wxJoin( itemTexts, '\n', '\0' ).ToStdString() );
3484 }
3485
3486 return 0;
3487}
3488
3489
3491{
3492 if( !copyToClipboard( aEvent ) )
3493 {
3494 // N.B. Setting the CUT flag prevents lock filtering as we only want to delete the items
3495 // that were copied to the clipboard, no more, no fewer. Filtering for locked item, if
3496 // any will be done in the copyToClipboard() routine
3497 TOOL_EVENT evt = aEvent;
3499 Remove( evt );
3500 }
3501
3502 return 0;
3503}
3504
3505
3507{
3508 board()->BuildConnectivity();
3510 canvas()->RedrawRatsnest();
3511}
3512
3513
3514// clang-format off
3516{
3518 Go( &EDIT_TOOL::Move, PCB_ACTIONS::move.MakeEvent() );
3524 Go( &EDIT_TOOL::Flip, PCB_ACTIONS::flip.MakeEvent() );
3525 Go( &EDIT_TOOL::Remove, ACTIONS::doDelete.MakeEvent() );
3532 Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorH.MakeEvent() );
3533 Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorV.MakeEvent() );
3534 Go( &EDIT_TOOL::Swap, PCB_ACTIONS::swap.MakeEvent() );
3546
3552
3556
3560
3564 Go( &EDIT_TOOL::cutToClipboard, ACTIONS::cut.MakeEvent() );
3565}
3566// clang-format on
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
static TOOL_ACTION decrementPrimary
Definition actions.h:96
static TOOL_ACTION paste
Definition actions.h:80
static TOOL_ACTION pickerSubTool
Definition actions.h:253
static TOOL_ACTION unselectAll
Definition actions.h:83
static TOOL_ACTION decrementSecondary
Definition actions.h:98
static TOOL_ACTION selectItem
Select an item (specified as the event parameter).
Definition actions.h:226
static TOOL_ACTION copy
Definition actions.h:78
static TOOL_ACTION pasteSpecial
Definition actions.h:81
static TOOL_ACTION rightJustify
Definition actions.h:89
static TOOL_ACTION pageSettings
Definition actions.h:63
static TOOL_ACTION undo
Definition actions.h:75
static TOOL_ACTION incrementSecondary
Definition actions.h:97
static TOOL_ACTION duplicate
Definition actions.h:84
static TOOL_ACTION incrementPrimary
Definition actions.h:95
static TOOL_ACTION doDelete
Definition actions.h:85
static TOOL_ACTION cursorClick
Definition actions.h:179
REMOVE_FLAGS
Definition actions.h:321
static TOOL_ACTION increment
Definition actions.h:94
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:223
static TOOL_ACTION leftJustify
Definition actions.h:87
static TOOL_ACTION cut
Definition actions.h:77
static TOOL_ACTION copyAsText
Definition actions.h:79
static TOOL_ACTION refreshPreview
Definition actions.h:158
static TOOL_ACTION selectAll
Definition actions.h:82
static TOOL_ACTION selectItems
Select a list of items (specified as the event parameter)
Definition actions.h:231
static TOOL_ACTION centerJustify
Definition actions.h:88
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
virtual void Revert() override
Revert the commit by restoring the modified items state.
NETINFO_ITEM * GetNet() const
Return #NET_INFO object for a given item.
void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
Abstract interface for BOARD_ITEMs capable of storing other items inside.
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
virtual BOARD_ITEM * Duplicate(bool addToParentGroup, BOARD_COMMIT *aCommit=nullptr) const
Create a copy of this BOARD_ITEM.
void SetLocked(bool aLocked) override
Definition board_item.h:323
bool IsLocked() const override
virtual void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle)
Rotate this object.
virtual void Move(const VECTOR2I &aMoveVector)
Move this object.
Definition board_item.h:339
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:280
FOOTPRINT * GetParentFootprint() const
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition board_item.h:252
virtual void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const
Invoke a function on all children.
Definition board_item.h:208
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:210
virtual void Normalize()
Perform any normalization required after a user rotate and/or flip.
Definition board_item.h:370
virtual void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection)
Flip this object, i.e.
const LSET & GetVisibleLayers() const
A proxy function that calls the correspondent function in m_BoardSettings.
Definition board.cpp:937
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:488
const FOOTPRINTS & Footprints() const
Definition board.h:358
constexpr const Vec & GetPosition() const
Definition box2.h:211
constexpr const Vec GetEnd() const
Definition box2.h:212
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr const Vec GetCenter() const
Definition box2.h:230
constexpr bool IsValid() const
Definition box2.h:909
Represent basic circle geometry with utility geometry functions.
Definition circle.h:33
VECTOR2I Center
Public to make access simpler.
Definition circle.h:130
int Radius
Public to make access simpler.
Definition circle.h:129
CIRCLE & ConstructFromTanTanPt(const SEG &aLineA, const SEG &aLineB, const VECTOR2I &aP)
Construct this circle such that it is tangent to the given segments and passes through the given poin...
Definition circle.cpp:51
VECTOR2I NearestPoint(const VECTOR2I &aP) const
Compute the point on the circumference of the circle that is the closest to aP.
Definition circle.cpp:197
void SaveSelection(const PCB_SELECTION &selected, bool isFootprintEditor)
void SetBoard(BOARD *aBoard)
int GetCount() const
Return the number of objects in the list.
Definition collector.h:83
void Remove(int aIndex)
Remove the item at aIndex (first position is 0).
Definition collector.h:111
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:90
bool Empty() const
Definition commit.h:137
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:106
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:78
DIALOG_GET_FOOTPRINT_BY_NAME is a helper dialog to select a footprint by its reference One can enter ...
int ShowModal() override
bool HitTestDrawingSheetItems(KIGFX::VIEW *aView, const VECTOR2I &aPosition)
void ShowInfoBarError(const wxString &aErrorMsg, bool aShowCloseButton=false, WX_INFOBAR::MESSAGE_TYPE aType=WX_INFOBAR::MESSAGE_TYPE::GENERIC)
Show the WX_INFOBAR displayed on the top of the canvas with a message and an error icon on the left o...
void DisplayToolMsg(const wxString &msg) override
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:54
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
virtual VECTOR2I GetPosition() const
Definition eda_item.h:272
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:142
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
void ClearSelected()
Definition eda_item.h:137
bool IsSelected() const
Definition eda_item.h:127
virtual bool IsType(const std::vector< KICAD_T > &aScanTypes) const
Check whether the item is one of the listed types.
Definition eda_item.h:192
SHAPE_POLY_SET & GetPolyShape()
Definition eda_shape.h:337
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
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:219
void SetWidth(int aWidth)
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:79
virtual bool IsVisible() const
Definition eda_text.h:186
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:387
bool isRouterActive() const
int Duplicate(const TOOL_EVENT &aItem)
Duplicate the current selection and starts a move action.
int Drag(const TOOL_EVENT &aEvent)
Invoke the PNS router to drag tracks or do an offline resizing of an arc track if a single arc track ...
int Flip(const TOOL_EVENT &aEvent)
Rotate currently selected items.
bool doMoveSelection(const TOOL_EVENT &aEvent, BOARD_COMMIT *aCommit, bool aAutoStart)
Rebuilds the ratsnest for operations that require it outside the commit rebuild.
int Swap(const TOOL_EVENT &aEvent)
Swap currently selected items' positions.
int PackAndMoveFootprints(const TOOL_EVENT &aEvent)
Try to fit selected footprints inside a minimal area and start movement.
int Mirror(const TOOL_EVENT &aEvent)
Mirror the current selection.
int Increment(const TOOL_EVENT &aEvent)
Increment some aspect of the selected items.q.
bool pickReferencePoint(const wxString &aTooltip, const wxString &aSuccessMessage, const wxString &aCanceledMessage, VECTOR2I &aReferencePoint)
bool Init() override
Init() is called once upon a registration of the tool.
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
int ModifyLines(const TOOL_EVENT &aEvent)
"Modify" graphical lines.
bool m_dragging
Definition edit_tool.h:232
int MoveExact(const TOOL_EVENT &aEvent)
Invoke a dialog box to allow moving of the item by an exact amount.
int Move(const TOOL_EVENT &aEvent)
Main loop in which events are handled.
static const unsigned int COORDS_PADDING
Definition edit_tool.h:237
int JustifyText(const TOOL_EVENT &aEvent)
Set the justification on any text items (or fields) in the current selection.
bool updateModificationPoint(PCB_SELECTION &aSelection)
int ChangeTrackLayer(const TOOL_EVENT &aEvent)
std::unique_ptr< STATUS_TEXT_POPUP > m_statusPopup
Definition edit_tool.h:235
int DragArcTrack(const TOOL_EVENT &aTrack)
Drag-resize an arc (and change end points of connected straight segments).
int copyToClipboard(const TOOL_EVENT &aEvent)
Send the current selection to the clipboard by formatting it as a fake pcb see #AppendBoardFromClipbo...
int Remove(const TOOL_EVENT &aEvent)
Delete currently selected items.
int cutToClipboard(const TOOL_EVENT &aEvent)
Cut the current selection to the clipboard by formatting it as a fake pcb see #AppendBoardFromClipboa...
static const std::vector< KICAD_T > MirrorableItems
Definition edit_tool.h:2260
void DeleteItems(const PCB_SELECTION &aItem, bool aIsCut)
static void PadFilter(const VECTOR2I &, GENERAL_COLLECTOR &aCollector, PCB_SELECTION_TOOL *sTool)
A selection filter which prunes the selection to contain only items of type PCB_PAD_T.
VECTOR2I m_cursor
Definition edit_tool.h:233
bool invokeInlineRouter(int aDragMode)
void rebuildConnectivity()
void setTransitions() override
< Set up handlers for various events.
int HealShapes(const TOOL_EVENT &aEvent)
Make ends of selected shapes meet by extending or cutting them, or adding extra geometry.
int ChangeTrackWidth(const TOOL_EVENT &aEvent)
int BooleanPolygons(const TOOL_EVENT &aEvent)
Modify selected polygons into a single polygon using boolean operations such as merge (union) or subt...
int copyToClipboardAsText(const TOOL_EVENT &aEvent)
Send the current selection to the clipboard as text.
int GetAndPlace(const TOOL_EVENT &aEvent)
int FilletTracks(const TOOL_EVENT &aEvent)
Fillet (i.e.
PCB_SELECTION_TOOL * m_selectionTool
Definition edit_tool.h:231
int SimplifyPolygons(const TOOL_EVENT &aEvent)
Simplify the outlines of selected polygon objects.
int Properties(const TOOL_EVENT &aEvent)
Display properties window for the selected object.
static void FootprintFilter(const VECTOR2I &, GENERAL_COLLECTOR &aCollector, PCB_SELECTION_TOOL *sTool)
A selection filter which prunes the selection to contain only items of type #PCB_MODULE_T.
int Rotate(const TOOL_EVENT &aEvent)
Rotate currently selected items.
static const TOOL_EVENT SelectedEvent
Definition actions.h:346
static const TOOL_EVENT SelectedItemsModified
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition actions.h:353
static const TOOL_EVENT ConnectivityChangedEvent
Selected item had a property changed (except movement)
Definition actions.h:350
static const TOOL_EVENT UnselectedEvent
Definition actions.h:347
BOARD_ITEM * DuplicateItem(bool addToParentGroup, BOARD_COMMIT *aCommit, const BOARD_ITEM *aItem, bool addToFootprint=false)
Duplicate a given item within the footprint, optionally adding it to the board.
wxString GetNextPadNumber(const wxString &aLastPadName) const
Return the next available pad number in the footprint.
Used when the right click button is pressed, or when the select tool is in effect.
Definition collectors.h:207
static const std::vector< KICAD_T > DraggableItems
A scan list for items that can be dragged.
Definition collectors.h:142
A handler that is based on a set of callbacks provided by the user of the ITEM_MODIFICATION_ROUTINE.
An interface for classes handling user events controlling the view behavior such as zooming,...
bool IsBOARD_ITEM() const
Definition view_item.h:102
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllLayersMask()
Definition lset.cpp:624
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:45
int GetuViaDrill() const
Definition netclass.h:157
int GetuViaDiameter() const
Definition netclass.h:149
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:145
void SetLastPadNumber(const wxString &aPadNumber)
Definition pad_tool.h:67
wxString GetLastPadNumber() const
Definition pad_tool.h:66
Definition pad.h:54
const VECTOR2I & GetDelta(PCB_LAYER_ID aLayer) const
Definition pad.h:299
VECTOR2I GetPosition() const override
Definition pad.h:208
void SetDelta(PCB_LAYER_ID aLayer, const VECTOR2I &aSize)
Definition pad.h:293
void FlipPrimitives(FLIP_DIRECTION aFlipDirection)
Flip (mirror) the primitives left to right or top to bottom, around the anchor position in custom pad...
Definition pad.cpp:1050
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition pad.h:195
void SetOffset(PCB_LAYER_ID aLayer, const VECTOR2I &aOffset)
Definition pad.h:311
void SetPosition(const VECTOR2I &aPos) override
Definition pad.h:202
const VECTOR2I & GetOffset(PCB_LAYER_ID aLayer) const
Definition pad.h:317
EDA_ANGLE GetOrientation() const
Return the rotation angle of the pad.
Definition pad.h:408
void SetOrientation(const EDA_ANGLE &aAngle)
Set the rotation angle of the pad.
Definition pad.cpp:958
static TOOL_ACTION drag45Degree
static TOOL_ACTION duplicateIncrement
Activation of the duplication tool with incrementing (e.g. pad number)
static TOOL_ACTION layerPrev
static TOOL_ACTION changeTrackWidth
Update selected tracks & vias to the current track & via dimensions.
static TOOL_ACTION unrouteSelected
Removes all tracks from the selected items to the first pad.
Definition pcb_actions.h:76
static TOOL_ACTION mirrorH
Mirroring of selected items.
static TOOL_ACTION updateFootprint
static TOOL_ACTION breakTrack
Break a single track into two segments at the cursor.
static TOOL_ACTION pointEditorMoveMidpoint
static TOOL_ACTION getAndPlace
Find an item and start moving.
static TOOL_ACTION routerRouteSelectedFromEnd
static TOOL_ACTION properties
Activation of the edit tool.
static TOOL_ACTION editFpInFpEditor
static TOOL_ACTION moveWithReference
move with a reference point
static TOOL_ACTION changeTrackLayerPrev
static TOOL_ACTION swap
Swapping of selected items.
static TOOL_ACTION routerAutorouteSelected
static TOOL_ACTION moveExact
Activation of the exact move tool.
static TOOL_ACTION intersectPolygons
Intersection of multiple polygons.
static TOOL_ACTION pointEditorMoveCorner
static TOOL_ACTION genRemove
static TOOL_ACTION selectConnection
Select tracks between junctions or expands an existing selection to pads or the entire connection.
Definition pcb_actions.h:73
static TOOL_ACTION assignNetClass
static TOOL_ACTION packAndMoveFootprints
Pack and start moving selected footprints.
static TOOL_ACTION copyWithReference
copy command with manual reference point selection
static TOOL_ACTION healShapes
Connect selected shapes, possibly extending or cutting them, or adding extra geometry.
static TOOL_ACTION dragFreeAngle
static TOOL_ACTION positionRelativeInteractively
static TOOL_ACTION inspectClearance
static TOOL_ACTION updateLocalRatsnest
static TOOL_ACTION updateFootprints
static TOOL_ACTION deleteFull
static TOOL_ACTION unrouteSegment
Removes track segment from the selected item to the next segment.
Definition pcb_actions.h:79
static TOOL_ACTION moveIndividually
move items one-by-one
static TOOL_ACTION changeFootprints
static TOOL_ACTION chamferLines
Chamfer (i.e. adds a straight line) all selected straight lines by a user defined setback.
static TOOL_ACTION dogboneCorners
Add "dogbone" corners to selected lines to allow routing with a cutter radius.
static TOOL_ACTION filletTracks
Fillet (i.e. adds an arc tangent to) all selected straight tracks by a user defined radius.
static TOOL_ACTION simplifyPolygons
Simplify polygon outlines.
static TOOL_ACTION footprintProperties
static TOOL_ACTION filletLines
Fillet (i.e. adds an arc tangent to) all selected straight lines by a user defined radius.
static TOOL_ACTION changeFootprint
static TOOL_ACTION routerInlineDrag
Activation of the Push and Shove router (inline dragging mode)
static TOOL_ACTION positionRelative
static TOOL_ACTION skip
static TOOL_ACTION move
move or drag an item
static TOOL_ACTION mirrorV
static TOOL_ACTION mergePolygons
Merge multiple polygons into a single polygon.
static TOOL_ACTION subtractPolygons
Subtract polygons from other polygons.
static TOOL_ACTION changeTrackLayerNext
static TOOL_ACTION flip
Flipping of selected objects.
static TOOL_ACTION layerNext
static TOOL_ACTION extendLines
Extend selected lines to meet at a point.
static TOOL_ACTION routerRouteSelected
static TOOL_ACTION rotateCw
Rotation of selected objects.
static TOOL_ACTION rotateCcw
virtual double GetLength() const override
Return the length of the arc track.
Definition pcb_track.h:379
void SetMid(const VECTOR2I &aMid)
Definition pcb_track.h:344
EDA_ANGLE GetAngle() const
const VECTOR2I & GetMid() const
Definition pcb_track.h:345
virtual VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_track.h:352
Common, abstract interface for edit frames.
virtual void OnEditItemRequest(BOARD_ITEM *aItem)
Install the corresponding dialog editor for the given item.
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
PCB_DRAW_PANEL_GAL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
BOARD * GetBoard() const
DS_PROXY_VIEW_ITEM * GetDrawingSheet() const
static const TOOL_EVENT & SnappingModeChangedByKeyEvent()
Hotkey feedback.
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:53
Generic tool for picking an item.
void SetLayerSet(const LSET &aLayerSet)
Set the tool's snap layer set.
Tool that displays edit points allowing to modify items by dragging the points.
A PCB_POINT is a 0-dimensional point that is used to mark a position on a PCB, or more usually a foot...
Definition pcb_point.h:43
The selection tool: currently supports:
void GuessSelectionCandidates(GENERAL_COLLECTOR &aCollector, const VECTOR2I &aWhere) const
Try to guess best selection candidates in case multiple items are clicked, by doing some brain-dead h...
void FilterCollectorForMarkers(GENERAL_COLLECTOR &aCollector) const
Drop any PCB_MARKERs from the collector.
void FilterCollectorForFreePads(GENERAL_COLLECTOR &aCollector, bool aForcePromotion=false) const
Check the "allow free pads" setting and if disabled, replace any pads in the collector with their par...
void FilterCollectorForHierarchy(GENERAL_COLLECTOR &aCollector, bool aMultiselect) const
In general we don't want to select both a parent and any of it's children.
PCB_SELECTION & GetSelection()
void FilterCollectorForLockedItems(GENERAL_COLLECTOR &aCollector)
In the PCB editor strip out any locked items unless the OverrideLocks checkbox is set.
void FilterCollectorForTableCells(GENERAL_COLLECTOR &aCollector) const
Promote any table cell selections to the whole table.
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
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition pcb_shape.h:71
wxString GetShownText(bool aAllowExtraText, int aDepth=0) const override
Return the string actually shown after processing of the base text.
T * frame() const
bool IsFootprintEditor() const
KIGFX::VIEW_CONTROLS * controls() const
PCB_TOOL_BASE(TOOL_ID aId, const std::string &aName)
Constructor.
BOARD * board() const
PCB_DRAW_PANEL_GAL * canvas() const
const PCB_SELECTION & selection() const
FOOTPRINT * footprint() const
void SetHasSolderMask(bool aVal)
Definition pcb_track.h:175
virtual double GetLength() const
Get the length of the track using the hypotenuse calculation.
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:148
bool HasSolderMask() const
Definition pcb_track.h:176
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:151
void SetLocalSolderMaskMargin(std::optional< int > aMargin)
Definition pcb_track.h:178
std::optional< int > GetLocalSolderMaskMargin() const
Definition pcb_track.h:179
const VECTOR2I & GetStart() const
Definition pcb_track.h:152
const VECTOR2I & GetEnd() const
Definition pcb_track.h:149
EDA_ITEM_FLAGS IsPointOnEnds(const VECTOR2I &point, int min_dist=0) const
Return STARTPOINT if point if near (dist = min_dist) start point, ENDPOINT if point if near (dist = m...
virtual void SetWidth(int aWidth)
Definition pcb_track.h:145
virtual int GetWidth() const
Definition pcb_track.h:146
void SetMotionHandler(MOTION_HANDLER aHandler)
Set a handler for mouse motion.
Definition picker_tool.h:92
void SetClickHandler(CLICK_HANDLER aHandler)
Set a handler for mouse click event.
Definition picker_tool.h:81
void SetSnapping(bool aSnap)
Definition picker_tool.h:66
void SetCursor(KICURSOR aCursor)
Definition picker_tool.h:64
void SetCancelHandler(CANCEL_HANDLER aHandler)
Set a handler for cancel events (ESC or context-menu Cancel).
void SetFinalizeHandler(FINALIZE_HANDLER aHandler)
Set a handler for the finalize event.
bool RoutingInProgress()
Returns whether routing is currently active.
bool CanInlineDrag(int aDragMode)
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I B
Definition seg.h:50
const VECTOR2I NearestPoint(const VECTOR2I &aP) const
Compute a point on the segment (this) that is closest to point aP.
Definition seg.cpp:604
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 ApproxCollinear(const SEG &aSeg, int aDistanceThreshold=1) const
Definition seg.cpp:766
VECTOR2I LineProject(const VECTOR2I &aP) const
Compute the perpendicular projection point of aP on a line passing through ends of the segment.
Definition seg.cpp:656
SEG PerpendicularSeg(const VECTOR2I &aP) const
Compute a segment perpendicular to this one, passing through point aP.
Definition seg.cpp:518
int Side(const VECTOR2I &aP) const
Determine on which side of directed line passing via segment ends point aP lies.
Definition seg.h:143
EDA_ANGLE Angle(const SEG &aOther) const
Determine the smallest angle between two segments.
Definition seg.cpp:102
static SELECTION_CONDITION HasTypes(std::vector< KICAD_T > aTypes)
Create a functor that tests if among the selected items there is at least one of a given types.
static SELECTION_CONDITION HasType(KICAD_T aType)
Create a functor that tests if among the selected items there is at least one of a given type.
static bool NotEmpty(const SELECTION &aSelection)
Test if there are any items selected.
static SELECTION_CONDITION MoreThan(int aNumber)
Create a functor that tests if the number of selected items is greater than the value given as parame...
static SELECTION_CONDITION Count(int aNumber)
Create a functor that tests if the number of selected items is equal to the value given as parameter.
static SELECTION_CONDITION OnlyTypes(std::vector< KICAD_T > aTypes)
Create a functor that tests if the selected items are only of given types.
virtual VECTOR2I GetCenter() const
Returns the center point of the selection area bounding box.
Definition selection.cpp:92
bool IsHover() const
Definition selection.h:89
virtual unsigned int GetSize() const override
Return the number of stored items.
Definition selection.h:105
EDA_ITEM * Front() const
Definition selection.h:177
bool HasType(KICAD_T aType) const
Checks if there is at least one item of requested kind.
int Size() const
Returns the number of selected parts.
Definition selection.h:121
std::deque< EDA_ITEM * > & Items()
Definition selection.h:182
void SetReferencePoint(const VECTOR2I &aP)
bool Empty() const
Checks if there is anything selected.
Definition selection.h:115
bool HasReferencePoint() const
Definition selection.h:216
size_t CountType(KICAD_T aType) const
const VECTOR2I & GetP1() const
Definition shape_arc.h:117
const VECTOR2I & GetP0() const
Definition shape_arc.h:116
Represent a set of closed polygons.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
void SimplifyOutlines(int aMaxError=0)
Simplifies the lines in the polyset.
const VECTOR2I & CVertex(int aIndex, int aOutline, int aHole) const
Return the index-th vertex in a given hole outline within a given outline.
static const int MIN_PRECISION_IU
This is the minimum precision for all the points in a shape.
Definition shape.h:131
Heuristically increment a string's n'th part from the right.
Definition increment.h:48
void SetSkipIOSQXZ(bool aSkip)
If a alphabetic part is found, skip the letters I, O, S, Q, X, Z.
Definition increment.h:54
virtual void PopTool(const TOOL_EVENT &aEvent)
Pops a tool from the stack.
virtual void PushTool(const TOOL_EVENT &aEvent)
NB: the definition of "tool" is different at the user level.
TOOL_MANAGER * GetManager() const
Return the instance of TOOL_MANAGER that takes care of the tool.
Definition tool_base.h:146
T * getEditFrame() const
Return the application window object, casted to requested user type.
Definition tool_base.h:186
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition tool_base.cpp:44
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
KIGFX::VIEW * getView() const
Returns the instance of #VIEW object used in the application.
Definition tool_base.cpp:38
bool IsToolActive() const
Definition tool_base.cpp:32
RESET_REASON
Determine the reason of reset for a tool.
Definition tool_base.h:78
Generic, UI-independent tool event.
Definition tool_event.h:171
bool HasParameter() const
Definition tool_event.h:464
TOOL_ACTIONS Action() const
Returns more specific information about the type of an event.
Definition tool_event.h:250
bool Matches(const TOOL_EVENT &aEvent) const
Test whether two events match in terms of category & action or command.
Definition tool_event.h:392
COMMIT * Commit() const
Definition tool_event.h:283
void SetParameter(T aParam)
Set a non-standard parameter assigned to the event.
Definition tool_event.h:528
TOOL_EVENT_CATEGORY Category() const
Return the category (eg. mouse/keyboard/action) of an event.
Definition tool_event.h:247
bool IsAction(const TOOL_ACTION *aAction) const
Test if the event contains an action issued upon activation of the given TOOL_ACTION.
T Parameter() const
Return a parameter assigned to the event.
Definition tool_event.h:473
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Define which state (aStateFunc) to go when a certain event arrives (aConditions).
TOOL_EVENT * Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
Suspend execution of the tool until an event specified in aEventList arrives.
void Activate()
Run the tool.
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:283
VECTOR2_TRAITS< int32_t >::extended_type extended_type
Definition vector2d.h:73
A dialog like WX_UNIT_ENTRY_DIALOG, but with multiple entries.
std::vector< RESULT > GetValues() const
Returns the values in the order they were added.
An extension of WX_TEXT_ENTRY_DIALOG that uses UNIT_BINDER to request a dimension (e....
int GetValue()
Return the value in internal units.
Handle a list of polygons defining a copper zone.
Definition zone.h:74
bool UnFill()
Removes the zone filling.
Definition zone.cpp:442
bool HitTestCutout(const VECTOR2I &aRefPos, int *aOutlineIdx=nullptr, int *aHoleIdx=nullptr) const
Test if the given point is contained within a cutout of the zone.
Definition zone.cpp:834
void HatchBorder()
Compute the hatch lines depending on the hatch parameters and stores it in the zone's attribute m_bor...
Definition zone.cpp:1244
void RemoveCutout(int aOutlineIdx, int aHoleIdx)
Remove a cutout from the zone.
Definition zone.cpp:1088
bool SaveClipboard(const std::string &aTextUTF8)
Store information to the system clipboard.
Definition clipboard.cpp:38
@ PLACE
Definition cursors.h:96
ROTATION_ANCHOR
@ ROTATE_AROUND_USER_ORIGIN
@ ROTATE_AROUND_SEL_CENTER
@ ROTATE_AROUND_AUX_ORIGIN
@ ROTATE_AROUND_ITEM_ANCHOR
#define _(s)
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:415
@ RECURSE
Definition eda_item.h:51
std::vector< EDA_ITEM * > EDA_ITEMS
Define list of drawing items for screens.
Definition eda_item.h:566
#define IS_NEW
New item, just created.
#define STRUCT_DELETED
flag indication structures to be erased
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
static const std::vector< KICAD_T > baseConnectedTypes
Definition edit_tool.cpp:90
static const std::vector< KICAD_T > routableTypes
static std::shared_ptr< CONDITIONAL_MENU > makePositioningToolsMenu(TOOL_INTERACTIVE *aTool)
static FOOTPRINT * GetFootprintFromBoardByReference(PCB_BASE_FRAME &aFrame)
static std::shared_ptr< CONDITIONAL_MENU > makeShapeModificationMenu(TOOL_INTERACTIVE *aTool)
static std::optional< CHAMFER_PARAMS > GetChamferParams(PCB_BASE_EDIT_FRAME &aFrame)
Prompt the user for chamfer parameters.
static const std::vector< KICAD_T > footprintTypes
Definition edit_tool.cpp:82
static const std::vector< KICAD_T > groupTypes
Definition edit_tool.cpp:84
static const std::vector< KICAD_T > padTypes
Definition edit_tool.cpp:80
static std::optional< int > GetRadiusParams(PCB_BASE_EDIT_FRAME &aFrame, const wxString &aTitle, int &aPersitentRadius)
Prompt the user for a radius and return it.
static void mirrorPad(PAD &aPad, const VECTOR2I &aMirrorPoint, FLIP_DIRECTION aFlipDirection)
Mirror a pad in the H/V axis passing through a point.
static const std::vector< KICAD_T > trackTypes
Definition edit_tool.cpp:86
static std::optional< DOGBONE_CORNER_ROUTINE::PARAMETERS > GetDogboneParams(PCB_BASE_EDIT_FRAME &aFrame)
void ConnectBoardShapes(std::vector< PCB_SHAPE * > &aShapeList, int aChainingEpsilon)
Connects shapes to each other, making continious contours (adjacent shapes will have a common vertex)...
@ LAYER_DRAWINGSHEET
Sheet frame and title block.
Definition layer_ids.h:277
@ LAYER_SCHEMATIC_DRAWINGSHEET
Definition layer_ids.h:494
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
This file contains miscellaneous commonly used macros and functions.
#define UNIMPLEMENTED_FOR(type)
Definition macros.h:96
constexpr void MIRROR(T &aPoint, const T &aMirrorRef)
Updates aPoint with the mirror of aPoint relative to the aMirrorRef.
Definition mirror.h:45
FLIP_DIRECTION
Definition mirror.h:27
@ LEFT_RIGHT
Flip left to right (around the Y axis)
Definition mirror.h:28
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:29
KICOMMON_API wxString MessageTextFromValue(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, double aValue, bool aAddUnitsText=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
A helper to convert the double length aValue to a string in inches, millimeters, or unscaled units.
wxPoint GetMousePosition()
Returns the mouse position in screen coordinates.
Definition wxgtk/ui.cpp:689
@ DM_ANY
Definition pns_router.h:77
@ DM_FREE_ANGLE
Definition pns_router.h:75
EDA_ANGLE GetEventRotationAngle(const PCB_BASE_EDIT_FRAME &aFrame, const TOOL_EVENT &aEvent)
Function getEventRotationAngle()
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:100
void for_all_pairs(_InputIterator __first, _InputIterator __last, _Function __f)
Apply a function to every possible pair of elements of a sequence.
Definition kicad_algo.h:84
Class to handle a set of BOARD_ITEMs.
@ MICROVIA
Definition pcb_track.h:69
BOARD * GetBoard()
#define APPEND_UNDO
Definition sch_commit.h:41
static std::vector< KICAD_T > connectedTypes
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:39
std::function< bool(const SELECTION &)> SELECTION_CONDITION
Functor type that checks a specific condition for selected items.
Parameters that define a simple chamfer operation.
VECTOR2I end
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ AS_GLOBAL
Global action (toolbar/main menu event, global shortcut)
Definition tool_action.h:49
@ BUT_LEFT
Definition tool_event.h:132
const VECTOR2I CalcArcMid(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCenter, bool aMinArcAngle=true)
Return the middle point of an arc, half-way between aStart and aEnd.
Definition trigo.cpp:209
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:88
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:105
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:102
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:91
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:103
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:110
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:93
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:107
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:92
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:89
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:90
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition typeinfo.h:106
@ PCB_SHAPE_LOCATE_CIRCLE_T
Definition typeinfo.h:137
@ PCB_SHAPE_LOCATE_SEGMENT_T
Definition typeinfo.h:135
@ PCB_SHAPE_LOCATE_RECT_T
Definition typeinfo.h:136
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:95
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:86
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:101
@ PCB_SHAPE_LOCATE_BEZIER_T
Definition typeinfo.h:140
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
@ PCB_SHAPE_LOCATE_POLY_T
Definition typeinfo.h:139
@ PCB_SHAPE_LOCATE_ARC_T
Definition typeinfo.h:138
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:100
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:94
@ PCB_POINT_T
class PCB_POINT, a 0-dimensional point
Definition typeinfo.h:112
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:96
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:104
Casted dyn_cast(From aObject)
A lightweight dynamic downcast.
Definition typeinfo.h:61
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694