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, see <https://www.gnu.org/licenses/>.
21 */
22
23#include <macros.h>
24#include <advanced_config.h>
25#include <clipboard.h>
26#include <limits>
27#include <kiplatform/ui.h>
29#include <board.h>
31#include <collectors.h>
32#include <footprint.h>
33#include <increment.h>
34#include <pcb_shape.h>
35#include <pcb_group.h>
38#include <pcb_point.h>
39#include <pcb_reference_image.h>
40#include <pcb_target.h>
41#include <pcb_textbox.h>
42#include <pcb_table.h>
43#include <pcb_generator.h>
45#include <zone.h>
46#include <pad.h>
47#include <pcb_edit_frame.h>
49#include <kiway.h>
50#include <status_popup.h>
51#include <tool/action_manager.h>
53#include <tool/tool_manager.h>
54#include <tools/pcb_actions.h>
57#include <tools/edit_tool.h>
59#include <tools/graphic_edit.h>
65#include <tools/pad_tool.h>
66#include <view/view_controls.h>
68#include <pcbnew_id.h>
69#include <core/kicad_algo.h>
70#include <fix_board_shape.h>
71#include <bitmaps.h>
72#include <functional>
73using namespace std::placeholders;
74#include "kicad_clipboard.h"
75#include <wx/hyperlink.h>
76#include <router/router_tool.h>
78#include <widgets/wx_infobar.h>
86#include <pcb_reference_image.h>
87
88const unsigned int EDIT_TOOL::COORDS_PADDING = pcbIUScale.mmToIU( 20 );
89
91{
92 if( !aItem )
93 return false;
94
95 if( aItem->Type() == PCB_SHAPE_T )
96 {
97 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
98 return shape->GetShape() == SHAPE_T::POLY;
99 }
100
101 if( aItem->Type() == PCB_ZONE_T )
102 {
103 ZONE* zone = static_cast<ZONE*>( aItem );
104
105 if( zone->IsTeardropArea() )
106 return false;
107
108 return true;
109 }
110
111 return false;
112}
113
114static bool selectionHasEditableCorners( const SELECTION& aSelection )
115{
116 if( aSelection.GetSize() != 1 )
117 return false;
118
119 BOARD_ITEM* item = dynamic_cast<BOARD_ITEM*>( aSelection.Front() );
120 return itemHasEditableCorners( item );
121}
122
123static const std::vector<KICAD_T> padTypes = { PCB_PAD_T };
124
125static const std::vector<KICAD_T> footprintTypes = { PCB_FOOTPRINT_T };
126
127static const std::vector<KICAD_T> groupTypes = { PCB_GROUP_T };
128
129static const std::vector<KICAD_T> trackTypes = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T };
130
131static const std::vector<KICAD_T> baseConnectedTypes = { PCB_PAD_T, PCB_VIA_T, PCB_TRACE_T, PCB_ARC_T };
132
133static const std::vector<KICAD_T> connectedTypes = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, PCB_PAD_T, PCB_ZONE_T };
134
135static const std::vector<KICAD_T> routableTypes = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, PCB_PAD_T, PCB_FOOTPRINT_T };
136
137
138// Types with no Mirror() override, which would fall through to the warning-dialog
139// BOARD_ITEM::Mirror. Free pads are handled specially by the tool but not by PCB_GROUP::Mirror.
140static const std::vector<KICAD_T> nonMirrorableTypes = {
142};
143
144
145// A group is mirrorable only if none of its members hit BOARD_ITEM::Mirror.
146static bool groupMirrorable( const PCB_GROUP* aGroup )
147{
148 bool ok = true;
149
150 aGroup->RunOnChildren(
151 [&]( BOARD_ITEM* aChild )
152 {
153 if( aChild->IsType( nonMirrorableTypes ) )
154 ok = false;
155 },
157
158 return ok;
159}
160
161
162// True if at least one selected item can be mirrored. A group counts only if all its members can.
163static bool selectionMirrorable( const SELECTION& aSelection )
164{
165 for( EDA_ITEM* item : aSelection )
166 {
167 if( item->Type() == PCB_GROUP_T )
168 {
169 if( groupMirrorable( static_cast<PCB_GROUP*>( item ) ) )
170 return true;
171 }
172 else if( item->IsType( EDIT_TOOL::MirrorableItems ) )
173 {
174 return true;
175 }
176 }
177
178 return false;
179}
180
181
183 PCB_TOOL_BASE( "pcbnew.InteractiveEdit" ),
184 m_selectionTool( nullptr ),
185 m_dragging( false ),
186 m_inMoveWithReference( false )
187{
188}
189
190
192{
193 m_dragging = false;
194 m_inMoveWithReference = false;
195
196 m_statusPopup = std::make_unique<STATUS_TEXT_POPUP>( getEditFrame<PCB_BASE_EDIT_FRAME>() );
197}
198
199
200static std::shared_ptr<CONDITIONAL_MENU> makeMirrorRotateMenu( EDIT_TOOL* aEditTool )
201{
202 std::shared_ptr<CONDITIONAL_MENU> menu = std::make_shared<CONDITIONAL_MENU>( aEditTool );
203
204 menu->SetIcon( BITMAPS::special_tools );
205 menu->SetUntranslatedTitle( _HKI( "Mirror / Rotate" ) );
206
207 bool isBoardEditor = aEditTool && aEditTool->IsBoardEditor();
208
209 auto canMirror =
210 [isBoardEditor]( const SELECTION& aSelection )
211 {
212 if( isBoardEditor && SELECTION_CONDITIONS::OnlyTypes( padTypes )( aSelection ) )
213 return false;
214
215 return selectionMirrorable( aSelection );
216 };
217
220 menu->AddItem( PCB_ACTIONS::mirrorH, canMirror );
221 menu->AddItem( PCB_ACTIONS::mirrorV, canMirror );
222
223 return menu;
224}
225
226
227static std::shared_ptr<CONDITIONAL_MENU> makeRoutingToolsMenu( TOOL_INTERACTIVE* aTool )
228{
229 std::shared_ptr<CONDITIONAL_MENU> menu = std::make_shared<CONDITIONAL_MENU>( aTool );
230
231 menu->SetIcon( BITMAPS::special_tools );
232 menu->SetUntranslatedTitle( _HKI( "Routing" ) );
233
234 auto notMovingCondition =
235 []( const SELECTION& aSelection )
236 {
237 return aSelection.Empty() || !aSelection.Front()->IsMoving();
238 };
239
242 && notMovingCondition;
243
244 menu->AddItem( PCB_ACTIONS::routerRouteSelected, isRoutable );
245 menu->AddItem( PCB_ACTIONS::routerRouteSelectedFromEnd, isRoutable );
246 menu->AddItem( PCB_ACTIONS::unrouteSelected, isRoutable );
247 menu->AddItem( PCB_ACTIONS::unrouteSegment, isRoutable );
248 menu->AddItem( PCB_ACTIONS::routerAutorouteSelected, isRoutable );
249
250 const SELECTION_CONDITION isOptimizable = SELECTION_CONDITIONS::HasTypes( trackTypes ) && notMovingCondition;
251 menu->AddItem( PCB_ACTIONS::routerOptimizeSelected, isOptimizable );
252
253 return menu;
254}
255
256
257static std::shared_ptr<CONDITIONAL_MENU> makePositioningToolsMenu( TOOL_INTERACTIVE* aTool )
258{
259 std::shared_ptr<CONDITIONAL_MENU> menu = std::make_shared<CONDITIONAL_MENU>( aTool );
260
261 menu->SetIcon( BITMAPS::special_tools );
262 menu->SetUntranslatedTitle( _HKI( "Position" ) );
263
264 auto notMovingCondition =
265 []( const SELECTION& aSelection )
266 {
267 return aSelection.Empty() || !aSelection.Front()->IsMoving();
268 };
269
270 menu->AddItem( PCB_ACTIONS::moveExact, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
271 menu->AddItem( PCB_ACTIONS::moveWithReference, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
272 menu->AddItem( PCB_ACTIONS::moveIndividually, SELECTION_CONDITIONS::MoreThan( 1 ) && notMovingCondition );
273 menu->AddItem( PCB_ACTIONS::positionRelative, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
274 menu->AddItem( PCB_ACTIONS::interactiveOffsetTool, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
275 return menu;
276};
277
278
279static std::shared_ptr<CONDITIONAL_MENU> makeShapeModificationMenu( TOOL_INTERACTIVE* aTool )
280{
281 std::shared_ptr<CONDITIONAL_MENU> menu = std::make_shared<CONDITIONAL_MENU>( aTool );
282
283 menu->SetUntranslatedTitle( _HKI( "Shape Modification" ) );
284
285 static const std::vector<KICAD_T> filletChamferTypes = { PCB_SHAPE_LOCATE_POLY_T, PCB_SHAPE_LOCATE_RECT_T,
287
288 static const std::vector<KICAD_T> healShapesTypes = { PCB_SHAPE_LOCATE_SEGMENT_T, PCB_SHAPE_LOCATE_ARC_T,
290
291 static const std::vector<KICAD_T> lineExtendTypes = { PCB_SHAPE_LOCATE_SEGMENT_T };
292
293 static const std::vector<KICAD_T> polygonBooleanTypes = { PCB_SHAPE_LOCATE_RECT_T, PCB_SHAPE_LOCATE_POLY_T,
295
296 static const std::vector<KICAD_T> polygonSimplifyTypes = { PCB_SHAPE_LOCATE_POLY_T, PCB_ZONE_T };
297
298 auto hasCornerCondition =
299 [aTool]( const SELECTION& aSelection )
300 {
301 PCB_POINT_EDITOR* pt_tool = aTool->GetManager()->GetTool<PCB_POINT_EDITOR>();
302
303 return pt_tool && pt_tool->HasCorner();
304 };
305
306 auto hasMidpointCondition =
307 [aTool]( const SELECTION& aSelection )
308 {
309 PCB_POINT_EDITOR* pt_tool = aTool->GetManager()->GetTool<PCB_POINT_EDITOR>();
310
311 return pt_tool && pt_tool->HasMidpoint();
312 };
313
314 auto canAddCornerCondition =
315 []( const SELECTION& aSelection )
316 {
317 const EDA_ITEM* item = aSelection.Front();
318
319 return item && PCB_POINT_EDITOR::CanAddCorner( *item );
320 };
321
322 auto canChamferCornerCondition =
323 []( const SELECTION& aSelection )
324 {
325 const EDA_ITEM* item = aSelection.Front();
326
327 return item && PCB_POINT_EDITOR::CanChamferCorner( *item );
328 };
329
330 auto canRemoveCornerCondition =
331 [aTool]( const SELECTION& aSelection )
332 {
333 PCB_POINT_EDITOR* pt_tool = aTool->GetManager()->GetTool<PCB_POINT_EDITOR>();
334
335 return pt_tool && pt_tool->CanRemoveCorner( aSelection );
336 };
337
338 // clang-format off
339
340 // Shape cleanup
341 menu->AddItem( PCB_ACTIONS::healShapes, SELECTION_CONDITIONS::HasTypes( healShapesTypes ) );
342 menu->AddItem( PCB_ACTIONS::simplifyPolygons, SELECTION_CONDITIONS::HasTypes( polygonSimplifyTypes ) );
343
344 menu->AddSeparator( SELECTION_CONDITIONS::OnlyTypes( filletChamferTypes ) );
345
346 // Shape corner modifications
347 menu->AddItem( PCB_ACTIONS::filletLines, SELECTION_CONDITIONS::OnlyTypes( filletChamferTypes ) );
348 menu->AddItem( PCB_ACTIONS::chamferLines, SELECTION_CONDITIONS::OnlyTypes( filletChamferTypes ) );
349 menu->AddItem( PCB_ACTIONS::dogboneCorners, SELECTION_CONDITIONS::OnlyTypes( filletChamferTypes ) );
350 menu->AddItem( PCB_ACTIONS::extendLines, SELECTION_CONDITIONS::OnlyTypes( lineExtendTypes )
352 // Both pick their own shape from under the pointer, so the selection says nothing about them.
355
356 menu->AddSeparator( SELECTION_CONDITIONS::Count( 1 ) );
357
358 // Point editor corner operations
359 menu->AddItem( PCB_ACTIONS::pointEditorMoveCorner, hasCornerCondition );
360 menu->AddItem( PCB_ACTIONS::pointEditorMoveMidpoint, hasMidpointCondition );
361 menu->AddItem( PCB_ACTIONS::pointEditorAddCorner, SELECTION_CONDITIONS::Count( 1 ) && canAddCornerCondition );
362 menu->AddItem( PCB_ACTIONS::pointEditorRemoveCorner, SELECTION_CONDITIONS::Count( 1 ) && canRemoveCornerCondition );
363 menu->AddItem( PCB_ACTIONS::pointEditorChamferCorner, SELECTION_CONDITIONS::Count( 1 ) && canChamferCornerCondition );
365
366 menu->AddSeparator( SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
368
369 // Polygon boolean operations
370 menu->AddItem( PCB_ACTIONS::mergePolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
372 menu->AddItem( PCB_ACTIONS::subtractPolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
374 menu->AddItem( PCB_ACTIONS::intersectPolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
376 // clang-format on
377
378 return menu;
379};
380
381
382// Gate-swap submenu and helpers
384{
385public:
387 ACTION_MENU( true )
388 {
390 SetTitle( _( "Swap Gate Nets..." ) );
391 }
392
393
394 // We're looking for a selection of pad(s) that belong to a single footprint with multiple units.
395 // Ignore non-pad items since we might have grabbed some traces inside the pad, etc.
396 static const FOOTPRINT* GetSingleEligibleFootprint( const SELECTION& aSelection )
397 {
398 const FOOTPRINT* single = nullptr;
399
400 for( const EDA_ITEM* it : aSelection )
401 {
402 if( it->Type() != PCB_PAD_T )
403 continue;
404
405 const PAD* pad = static_cast<const PAD*>( static_cast<const BOARD_ITEM*>( it ) );
406 const FOOTPRINT* fp = pad->GetParentFootprint();
407
408 if( !fp )
409 continue;
410
411 const std::vector<FOOTPRINT::FP_UNIT_INFO>& units = fp->GetUnitInfo();
412
413 if( units.size() < 2 )
414 continue;
415
416 const wxString& padNum = pad->GetNumber();
417 bool inAnyUnit = false;
418
419 for( const FOOTPRINT::FP_UNIT_INFO& u : units )
420 {
421 for( const wxString& pnum : u.m_pins )
422 {
423 if( pnum == padNum )
424 {
425 inAnyUnit = true;
426 break;
427 }
428 }
429
430 if( inAnyUnit )
431 break;
432 }
433
434 if( !inAnyUnit )
435 continue;
436
437 if( !single )
438 single = fp;
439 else if( single != fp )
440 return nullptr;
441 }
442
443 return single;
444 }
445
446
447 static std::unordered_set<wxString> CollectSelectedPadNumbers( const SELECTION& aSelection,
448 const FOOTPRINT* aFootprint )
449 {
450 std::unordered_set<wxString> padNums;
451
452 for( const EDA_ITEM* it : aSelection )
453 {
454 if( it->Type() != PCB_PAD_T )
455 continue;
456
457 const PAD* pad = static_cast<const PAD*>( static_cast<const BOARD_ITEM*>( it ) );
458
459 if( pad->GetParentFootprint() != aFootprint )
460 continue;
461
462 padNums.insert( pad->GetNumber() );
463 }
464
465 return padNums;
466 }
467
468
469 // Make a list of the unit names that have any pad selected
470 static std::vector<int> GetUnitsHitIndices( const FOOTPRINT* aFootprint,
471 const std::unordered_set<wxString>& aSelPadNums )
472 {
473 std::vector<int> indices;
474
475 const std::vector<FOOTPRINT::FP_UNIT_INFO>& units = aFootprint->GetUnitInfo();
476
477 for( size_t i = 0; i < units.size(); ++i )
478 {
479 bool hasAny = false;
480
481 for( const wxString& pn : units[i].m_pins )
482 {
483 if( aSelPadNums.count( pn ) )
484 {
485 hasAny = true;
486 break;
487 }
488 }
489
490 if( hasAny )
491 indices.push_back( static_cast<int>( i ) );
492 }
493
494 return indices;
495 }
496
497
498 // Gate swapping requires the swapped units to have equal pin counts
499 static bool EqualPinCounts( const FOOTPRINT* aFootprint, const std::vector<int>& aUnitIndices )
500 {
501 if( aUnitIndices.empty() )
502 return false;
503
504 const std::vector<FOOTPRINT::FP_UNIT_INFO>& units = aFootprint->GetUnitInfo();
505 const size_t cnt = units[static_cast<size_t>( aUnitIndices.front() )].m_pins.size();
506
507 for( int idx : aUnitIndices )
508 {
509 if( units[static_cast<size_t>( idx )].m_pins.size() != cnt )
510 return false;
511 }
512
513 return true;
514 }
515
516
517 // Used when we have exactly one source unit selected; find all other units with equal pin counts
518 static std::vector<int> GetCompatibleTargets( const FOOTPRINT* aFootprint, int aSourceIdx )
519 {
520 std::vector<int> targets;
521
522 const std::vector<FOOTPRINT::FP_UNIT_INFO>& units = aFootprint->GetUnitInfo();
523 const size_t pinCount = units[static_cast<size_t>( aSourceIdx )].m_pins.size();
524
525 for( size_t i = 0; i < units.size(); ++i )
526 {
527 if( static_cast<int>( i ) == aSourceIdx )
528 continue;
529
530 if( units[i].m_pins.size() != pinCount )
531 continue;
532
533 targets.push_back( static_cast<int>( i ) );
534 }
535
536 return targets;
537 }
538
539protected:
540 ACTION_MENU* create() const override { return new GATE_SWAP_MENU(); }
541
542 // The gate swap menu dynamically populates itself based on current selection of pads
543 // on a single multi-unit footprint.
544 //
545 // If there is exactly one unit with any pad selected, we build a menu of available swaps
546 // with all other units with equal pin counts.
547 void update() override
548 {
549 Clear();
550
552 const SELECTION& sel = selTool->GetSelection();
553
554 const FOOTPRINT* fp = GetSingleEligibleFootprint( sel );
555
556 if( !fp )
557 return;
558
559 std::unordered_set<wxString> selPadNums = CollectSelectedPadNumbers( sel, fp );
560
561 std::vector<int> unitsHit = GetUnitsHitIndices( fp, selPadNums );
562
563 if( unitsHit.size() != 1 )
564 return;
565
566 const int sourceIdx = unitsHit.front();
567 std::vector<int> targets = GetCompatibleTargets( fp, sourceIdx );
568
569 for( int idx : targets )
570 {
571 wxString label;
572 label.Printf( _( "Swap with %s" ), fp->GetUnitInfo()[static_cast<size_t>( idx )].m_unitName );
573 Append( ID_POPUP_PCB_SWAP_UNIT_BASE + idx, label );
574 }
575 }
576
577
578 OPT_TOOL_EVENT eventHandler( const wxMenuEvent& aEvent ) override
579 {
580 int id = aEvent.GetId();
581
583 {
585 const SELECTION& sel = selTool->GetSelection();
586
587 const FOOTPRINT* fp = GetSingleEligibleFootprint( sel );
588
589 if( !fp )
590 return OPT_TOOL_EVENT();
591
592 const std::vector<FOOTPRINT::FP_UNIT_INFO>& units = fp->GetUnitInfo();
593 const int targetIdx = id - ID_POPUP_PCB_SWAP_UNIT_BASE;
594
595 if( targetIdx < 0 || targetIdx >= static_cast<int>( units.size() ) )
596 return OPT_TOOL_EVENT();
597
598 TOOL_EVENT evt = PCB_ACTIONS::swapGateNets.MakeEvent();
599 evt.SetParameter( units[targetIdx].m_unitName );
600
601 return OPT_TOOL_EVENT( evt );
602 }
603
604 return OPT_TOOL_EVENT();
605 }
606};
607
608
609static std::shared_ptr<ACTION_MENU> makeGateSwapMenu( TOOL_INTERACTIVE* aTool )
610{
611 std::shared_ptr<GATE_SWAP_MENU> menu = std::make_shared<GATE_SWAP_MENU>();
612 menu->SetTool( aTool );
613 return menu;
614};
615
616
618{
619 // Find the selection tool, so they can cooperate
621
622 std::shared_ptr<CONDITIONAL_MENU> routingSubMenu = makeRoutingToolsMenu( this );
623 m_selectionTool->GetToolMenu().RegisterSubMenu( routingSubMenu );
624
625 std::shared_ptr<CONDITIONAL_MENU> positioningToolsSubMenu = makePositioningToolsMenu( this );
626 m_selectionTool->GetToolMenu().RegisterSubMenu( positioningToolsSubMenu );
627
628 std::shared_ptr<CONDITIONAL_MENU> mirrorRotateSubMenu = makeMirrorRotateMenu( this );
629 m_selectionTool->GetToolMenu().RegisterSubMenu( mirrorRotateSubMenu );
630
631 std::shared_ptr<CONDITIONAL_MENU> shapeModificationSubMenu = makeShapeModificationMenu( this );
632 m_selectionTool->GetToolMenu().RegisterSubMenu( shapeModificationSubMenu );
633
634 std::shared_ptr<ACTION_MENU> gateSwapSubMenu = makeGateSwapMenu( this );
635 m_selectionTool->GetToolMenu().RegisterSubMenu( gateSwapSubMenu );
636
637 std::shared_ptr<CONDITIONAL_MENU> fpAttributesMenu = std::make_shared<CONDITIONAL_MENU>( this );
638 fpAttributesMenu->SetUntranslatedTitle( _HKI( "Attributes" ) );
642 m_selectionTool->GetToolMenu().RegisterSubMenu( fpAttributesMenu );
643
644 auto positioningToolsCondition =
645 [this]( const SELECTION& aSel )
646 {
647 std::shared_ptr<CONDITIONAL_MENU> subMenu = makePositioningToolsMenu( this );
648 subMenu->Evaluate( aSel );
649 return subMenu->GetMenuItemCount() > 0;
650 };
651
652 auto shapeModificationCondition =
653 [this]( const SELECTION& aSel )
654 {
655 std::shared_ptr<CONDITIONAL_MENU> subMenu = makeShapeModificationMenu( this );
656 subMenu->Evaluate( aSel );
657 return subMenu->GetMenuItemCount() > 0;
658 };
659
660 // Does selection map to a single eligible footprint and exactly one unit?
661 auto gateSwapSingleUnitOnOneFootprint =
662 []( const SELECTION& aSelection )
663 {
665
666 if( !fp )
667 return false;
668
669 std::unordered_set<wxString> selPadNums = GATE_SWAP_MENU::CollectSelectedPadNumbers( aSelection, fp );
670
671 std::vector<int> unitsHit = GATE_SWAP_MENU::GetUnitsHitIndices( fp, selPadNums );
672
673 if( unitsHit.size() != 1 )
674 return false;
675
676 const int sourceIdx = unitsHit.front();
677 std::vector<int> targets = GATE_SWAP_MENU::GetCompatibleTargets( fp, sourceIdx );
678 return !targets.empty();
679 };
680
681 // Does selection map to a single eligible footprint and more than one unit with equal pin counts?
682 auto gateSwapMultipleUnitsOnOneFootprint =
683 []( const SELECTION& aSelection )
684 {
686
687 if( !fp )
688 return false;
689
690 std::unordered_set<wxString> selPadNums = GATE_SWAP_MENU::CollectSelectedPadNumbers( aSelection, fp );
691
692 std::vector<int> unitsHit = GATE_SWAP_MENU::GetUnitsHitIndices( fp, selPadNums );
693
694 if( unitsHit.size() < 2 )
695 return false;
696
697 return GATE_SWAP_MENU::EqualPinCounts( fp, unitsHit );
698 };
699
700 auto propertiesCondition =
701 [this]( const SELECTION& aSel )
702 {
703 if( aSel.GetSize() == 0 )
704 {
705 if( getView()->IsLayerVisible( LAYER_SCHEMATIC_DRAWINGSHEET ) )
706 {
707 DS_PROXY_VIEW_ITEM* ds = canvas()->GetDrawingSheet();
708 VECTOR2D cursor = getViewControls()->GetCursorPosition( false );
709
710 if( ds && ds->HitTestDrawingSheetItems( getView(), cursor ) )
711 return true;
712 }
713
714 return false;
715 }
716
717 if( aSel.GetSize() == 1 )
718 return true;
719
720 for( EDA_ITEM* item : aSel )
721 {
722 if( !dynamic_cast<PCB_TRACK*>( item ) )
723 return false;
724 }
725
726 return true;
727 };
728
729 auto inFootprintEditor =
730 [this]( const SELECTION& aSelection )
731 {
732 return m_isFootprintEditor;
733 };
734
735 auto canMirror =
736 [this]( const SELECTION& aSelection )
737 {
739 {
740 return false;
741 }
742
743 // Gates the whole "Mirror / Rotate" submenu, so keep it open for any group. Rotate works
744 // on a group with footprints, only the mirror items inside disable (see makeMirrorRotateMenu).
745 if( SELECTION_CONDITIONS::HasTypes( groupTypes )( aSelection ) )
746 return true;
747
749 };
750
751 auto singleFootprintCondition = SELECTION_CONDITIONS::OnlyTypes( footprintTypes )
753
754 auto multipleFootprintsCondition =
755 []( const SELECTION& aSelection )
756 {
757 bool foundFirst = false;
758
759 for( EDA_ITEM* item : aSelection )
760 {
761 if( item->Type() == PCB_FOOTPRINT_T )
762 {
763 if( foundFirst )
764 return true;
765 else
766 foundFirst = true;
767 }
768 }
769
770 return false;
771 };
772
773 auto excludeFromBOMCond =
774 [this]( const SELECTION& aSel )
775 {
776 wxString variantName;
777 int checked = 0, unchecked = 0;
778
779 if( BOARD* board = frame()->GetBoard() )
780 variantName = board->GetCurrentVariant();
781
782 for( const EDA_ITEM* item : aSel )
783 {
784 if( item->Type() == PCB_FOOTPRINT_T )
785 {
786 if( static_cast<const FOOTPRINT*>( item )->GetExcludedFromBOMForVariant( variantName ) )
787 checked++;
788 else
789 unchecked++;
790 }
791 }
792
793 return checked > 0 && unchecked == 0;
794 };
795
796 auto excludeFromPosFilesCond =
797 [this]( const SELECTION& aSel )
798 {
799 wxString variantName;
800 int checked = 0, unchecked = 0;
801
802 if( BOARD* board = frame()->GetBoard() )
803 variantName = board->GetCurrentVariant();
804
805 for( const EDA_ITEM* item : aSel )
806 {
807 if( item->Type() == PCB_FOOTPRINT_T )
808 {
809 if( static_cast<const FOOTPRINT*>( item )->GetExcludedFromPosFilesForVariant( variantName ) )
810 checked++;
811 else
812 unchecked++;
813 }
814 }
815
816 return checked > 0 && unchecked == 0;
817 };
818
819 auto excludeFromSimCond =
820 [this]( const SELECTION& aSel )
821 {
822 wxString variantName;
823 int checked = 0, unchecked = 0;
824
825 if( BOARD* board = frame()->GetBoard() )
826 variantName = board->GetCurrentVariant();
827
828 for( const EDA_ITEM* item : aSel )
829 {
830 if( item->Type() == PCB_FOOTPRINT_T )
831 {
832 if( static_cast<const FOOTPRINT*>( item )->GetExcludedFromSimForVariant( variantName ) )
833 checked++;
834 else
835 unchecked++;
836 }
837 }
838
839 return checked > 0 && unchecked == 0;
840 };
841
842 auto noActiveToolCondition =
843 [this]( const SELECTION& aSelection )
844 {
845 return frame()->ToolStackIsEmpty();
846 };
847
848 auto notMovingCondition =
849 []( const SELECTION& aSelection )
850 {
851 return aSelection.Empty() || !aSelection.Front()->IsMoving();
852 };
853
854 auto noItemsCondition =
855 [this]( const SELECTION& aSelections ) -> bool
856 {
857 return frame()->GetBoard() && !frame()->GetBoard()->IsEmpty();
858 };
859
860 auto isSkippable =
861 [this]( const SELECTION& aSelection )
862 {
863 return frame()->IsCurrentTool( PCB_ACTIONS::moveIndividually );
864 };
865
868 && notMovingCondition && !inFootprintEditor;
869
870 const auto canCopyAsText = SELECTION_CONDITIONS::NotEmpty
883 } );
884
885 // Add context menu entries that are displayed when selection tool is active
886 CONDITIONAL_MENU& menu = m_selectionTool->GetToolMenu().GetMenu();
887
888 // clang-format off
889 menu.AddItem( ACTIONS::selectAll, noItemsCondition );
890 menu.AddItem( ACTIONS::unselectAll, noItemsCondition );
891 menu.AddSeparator();
892
893 menu.AddItem( PCB_ACTIONS::skip, isSkippable );
894 menu.AddItem( PCB_ACTIONS::move, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
895
902
906 menu.AddItem( PCB_ACTIONS::swapGateNets, gateSwapMultipleUnitsOnOneFootprint );
907 menu.AddMenu( gateSwapSubMenu.get(), gateSwapSingleUnitOnOneFootprint );
908
909 menu.AddSeparator();
910
913
915
916 menu.AddSeparator();
917
920
922 && !inFootprintEditor );
924
925 // Footprint actions
926 menu.AddSeparator();
927 menu.AddItem( PCB_ACTIONS::editFpInFpEditor, singleFootprintCondition );
928 menu.AddItem( PCB_ACTIONS::updateFootprint, singleFootprintCondition );
929 menu.AddItem( PCB_ACTIONS::updateFootprints, multipleFootprintsCondition );
930 menu.AddItem( PCB_ACTIONS::changeFootprint, singleFootprintCondition );
931 menu.AddItem( PCB_ACTIONS::changeFootprints, multipleFootprintsCondition );
932 menu.AddMenu( fpAttributesMenu.get(), singleFootprintCondition || multipleFootprintsCondition );
933
934 // Add the submenu for the special tools: modfiers and positioning tools
935 menu.AddSeparator( 100 );
936 menu.AddMenu( routingSubMenu.get(), isRoutable, 100 );
937 menu.AddMenu( mirrorRotateSubMenu.get(), canMirror, 100 );
938 menu.AddMenu( shapeModificationSubMenu.get(), shapeModificationCondition, 100 );
939 menu.AddMenu( positioningToolsSubMenu.get(), positioningToolsCondition, 100 );
940
941 menu.AddSeparator( 150 );
942 menu.AddItem( ACTIONS::cut, SELECTION_CONDITIONS::NotEmpty, 150 );
943 menu.AddItem( ACTIONS::copy, SELECTION_CONDITIONS::NotEmpty, 150 );
944 menu.AddItem( PCB_ACTIONS::copyWithReference, SELECTION_CONDITIONS::NotEmpty && notMovingCondition, 150 );
945 menu.AddItem( ACTIONS::copyAsText, canCopyAsText, 150 );
946
947 // Selection tool handles the context menu for some other tools, such as the Picker.
948 // Don't add things like Paste when another tool is active.
949 menu.AddItem( ACTIONS::paste, noActiveToolCondition, 150 );
950 menu.AddItem( ACTIONS::pasteSpecial, noActiveToolCondition && !inFootprintEditor, 150 );
953
954 menu.AddSeparator( 2000 );
955 menu.AddItem( PCB_ACTIONS::properties, propertiesCondition, 2000 );
956 // clang-format on
957
958 ACTION_MANAGER* mgr = m_toolMgr->GetActionManager();
959 mgr->SetConditions( PCB_ACTIONS::toggleExcludeFromBOM, ACTION_CONDITIONS().Check( excludeFromBOMCond ) );
960 mgr->SetConditions( PCB_ACTIONS::toggleExcludeFromSim, ACTION_CONDITIONS().Check( excludeFromSimCond ) );
961 mgr->SetConditions( PCB_ACTIONS::toggleExcludeFromPosFiles, ACTION_CONDITIONS().Check( excludeFromPosFilesCond ) );
962
963 return true;
964}
965
966
973{
974 wxString footprintName;
975 wxArrayString fplist;
976 const FOOTPRINTS& footprints = aFrame.GetBoard()->Footprints();
977
978 // Build list of available fp references, to display them in dialog
979 for( FOOTPRINT* fp : footprints )
980 fplist.Add( fp->GetReference() + wxT( " ( " ) + fp->GetValue() + wxT( " )" ) );
981
982 fplist.Sort();
983
984 DIALOG_GET_FOOTPRINT_BY_NAME dlg( &aFrame, fplist );
985
986 if( dlg.ShowModal() != wxID_OK ) //Aborted by user
987 return nullptr;
988
989 footprintName = dlg.GetValue();
990 footprintName.Trim( true );
991 footprintName.Trim( false );
992
993 if( !footprintName.IsEmpty() )
994 {
995 for( FOOTPRINT* fp : footprints )
996 {
997 if( fp->GetReference().CmpNoCase( footprintName ) == 0 )
998 return fp;
999 }
1000 }
1001
1002 return nullptr;
1003}
1004
1005
1007{
1008 // GetAndPlace makes sense only in board editor, although it is also called
1009 // in fpeditor, that shares the same EDIT_TOOL list
1010 if( IsFootprintEditor() )
1011 return 0;
1012
1013 PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
1015
1016 if( fp )
1017 {
1018 m_toolMgr->RunAction( ACTIONS::selectionClear );
1019 m_toolMgr->RunAction<EDA_ITEM*>( ACTIONS::selectItem, fp );
1020
1021 selectionTool->GetSelection().SetReferencePoint( fp->GetPosition() );
1022 m_toolMgr->PostAction( PCB_ACTIONS::move );
1023 }
1024
1025 return 0;
1026}
1027
1028
1030{
1031 ROUTER_TOOL* theRouter = m_toolMgr->GetTool<ROUTER_TOOL>();
1032
1033 if( !theRouter )
1034 return false;
1035
1036 // don't allow switch from moving to dragging
1037 if( m_dragging )
1038 {
1039 wxBell();
1040 return false;
1041 }
1042
1043 // make sure we don't accidentally invoke inline routing mode while the router is already
1044 // active!
1045 if( theRouter->IsToolActive() )
1046 return false;
1047
1048 if( theRouter->CanInlineDrag( aDragMode ) )
1049 {
1050 m_toolMgr->RunAction( PCB_ACTIONS::routerInlineDrag, aDragMode );
1051 return true;
1052 }
1053
1054 frame()->ShowInfoBarMsg( _( "This selection cannot be dragged in this mode." ) );
1055 return false;
1056}
1057
1058
1060{
1061 ROUTER_TOOL* router = m_toolMgr->GetTool<ROUTER_TOOL>();
1062
1063 return router && router->RoutingInProgress();
1064}
1065
1066
1067int EDIT_TOOL::Drag( const TOOL_EVENT& aEvent )
1068{
1069 if( !m_toolMgr->GetTool<ROUTER_TOOL>() )
1070 {
1071 wxBell();
1072 return false; // don't drag when no router tool (i.e. fp editor)
1073 }
1074
1075 if( m_toolMgr->GetTool<ROUTER_TOOL>()->IsToolActive() )
1076 {
1077 wxBell();
1078 return false; // don't drag when router is already active
1079 }
1080
1081 if( m_dragging )
1082 {
1083 wxBell();
1084 return false; // don't do a router drag when already in an EDIT_TOOL drag
1085 }
1086
1087 int mode = PNS::DM_ANY;
1088
1089 if( aEvent.IsAction( &PCB_ACTIONS::dragFreeAngle ) )
1090 mode |= PNS::DM_FREE_ANGLE;
1091
1092 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1093 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1094 {
1095 sTool->FilterCollectorForFreePads( aCollector );
1096 sTool->FilterCollectorForHierarchy( aCollector, true );
1097
1098 std::vector<PCB_TRACK*> tracks;
1099 std::vector<PCB_TRACK*> vias;
1100 std::vector<FOOTPRINT*> footprints;
1101
1102 // Gather items from the collector into per-type vectors
1103 const auto gatherItemsByType =
1104 [&]()
1105 {
1106 for( EDA_ITEM* item : aCollector )
1107 {
1108 if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
1109 {
1110 if( track->Type() == PCB_VIA_T )
1111 vias.push_back( track );
1112 else
1113 tracks.push_back( track );
1114 }
1115 else if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( item ) )
1116 {
1117 footprints.push_back( footprint );
1118 }
1119 }
1120 };
1121
1122 // Initial gathering of items
1123 gatherItemsByType();
1124
1125 // The heuristics below answer "what did the user mean by clicking here", so
1126 // they only apply to a hover. An explicit selection already said what it wants,
1127 // and trimming it would drop items the block drag is meant to carry.
1128 if( !sTool->GetSelection().IsHover() )
1129 {
1130 const auto draggable = []( KICAD_T aType )
1131 {
1132 return aType == PCB_FOOTPRINT_T || aType == PCB_TRACE_T || aType == PCB_ARC_T
1133 || aType == PCB_VIA_T || aType == PCB_PAD_T;
1134 };
1135
1136 bool anyDraggable = false;
1137
1138 for( int ii = 0; ii < aCollector.GetCount(); ++ii )
1139 anyDraggable |= draggable( aCollector[ii]->Type() );
1140
1141 // With nothing draggable the selection is left alone, so the router can
1142 // refuse it with a message rather than silently finding nothing to do
1143 if( anyDraggable )
1144 {
1145 for( int ii = aCollector.GetCount() - 1; ii >= 0; --ii )
1146 {
1147 if( !draggable( aCollector[ii]->Type() ) )
1148 aCollector.Remove( ii );
1149 }
1150 }
1151 }
1152 else if( tracks.size() || vias.size() )
1153 {
1154 /*
1155 * First trim down selection to active layer, tracks vs zones, etc.
1156 */
1157 if( aCollector.GetCount() > 1 )
1158 {
1159 sTool->GuessSelectionCandidates( aCollector, aPt );
1160
1161 // Re-gather items after trimming to update counts
1162 tracks.clear();
1163 vias.clear();
1164 footprints.clear();
1165
1166 gatherItemsByType();
1167 }
1168
1169 /*
1170 * If we have a knee between two tracks, or a via attached to two tracks,
1171 * then drop the selection to a single item. We don't want a selection
1172 * disambiguation menu when it doesn't matter which items is picked.
1173 */
1174 auto connected =
1175 []( PCB_TRACK* track, const VECTOR2I& pt )
1176 {
1177 return track->GetStart() == pt || track->GetEnd() == pt;
1178 };
1179
1180 if( tracks.size() == 2 && vias.size() == 0 )
1181 {
1182 if( connected( tracks[0], tracks[1]->GetStart() )
1183 || connected( tracks[0], tracks[1]->GetEnd() ) )
1184 {
1185 aCollector.Remove( tracks[1] );
1186 }
1187 }
1188 else if( tracks.size() == 2 && vias.size() == 1 )
1189 {
1190 if( connected( tracks[0], vias[0]->GetPosition() )
1191 && connected( tracks[1], vias[0]->GetPosition() ) )
1192 {
1193 aCollector.Remove( tracks[0] );
1194 aCollector.Remove( tracks[1] );
1195 }
1196 }
1197 }
1198
1199 sTool->FilterCollectorForLockedItems( aCollector );
1200 } );
1201
1202 m_selectionTool->ReportFilteredLockedItems();
1203
1204 if( selection.Empty() )
1205 return 0;
1206
1207 invokeInlineRouter( mode );
1208
1209 return 0;
1210}
1211
1212
1214{
1216
1217 if( selection.Empty() )
1218 return 0;
1219
1220 wxString variantName;
1221
1222 if( BOARD* board = frame()->GetBoard() )
1223 variantName = board->GetCurrentVariant();
1224
1225 bool new_state = false;
1226
1227 for( const EDA_ITEM* item : selection )
1228 {
1229 const FOOTPRINT* fp = static_cast<const FOOTPRINT*>( item );
1230
1232 && !fp->GetExcludedFromBOMForVariant( variantName ) )
1234 && !fp->GetExcludedFromSimForVariant( variantName ) )
1236 && !fp->GetExcludedFromPosFilesForVariant( variantName ) ) )
1237 {
1238 new_state = true;
1239 break;
1240 }
1241 }
1242
1243 BOARD_COMMIT commit( this );
1244
1245 for( EDA_ITEM* item : selection )
1246 {
1247 FOOTPRINT* fp = static_cast<FOOTPRINT*>( item );
1248 commit.Modify( fp );
1249
1250 if( !variantName.IsEmpty() )
1251 {
1252 FOOTPRINT_VARIANT* variant = fp->GetVariant( variantName );
1253
1254 if( !variant )
1255 variant = fp->AddVariant( variantName );
1256
1257 if( variant )
1258 {
1260 variant->SetExcludedFromBOM( new_state );
1261 else if( aEvent.IsAction( &PCB_ACTIONS::toggleExcludeFromSim ) )
1262 variant->SetExcludedFromSim( new_state );
1264 variant->SetExcludedFromPosFiles( new_state );
1265
1266 continue;
1267 }
1268 }
1269
1271 fp->SetExcludedFromBOM( new_state );
1272 else if( aEvent.IsAction( &PCB_ACTIONS::toggleExcludeFromSim ) )
1273 fp->SetExcludedFromSim( new_state );
1275 fp->SetExcludedFromPosFiles( new_state );
1276 }
1277
1278 if( !commit.Empty() )
1279 commit.Push( _( "Toggle Attribute" ) );
1280
1281 if( selection.IsHover() )
1282 m_toolMgr->RunAction( ACTIONS::selectionClear );
1283
1284 return 0;
1285}
1286
1287
1289{
1290 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1291 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1292 {
1293 // Iterate from the back so we don't have to worry about removals.
1294 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1295 {
1296 BOARD_ITEM* item = aCollector[i];
1297
1298 if( !dynamic_cast<PCB_TRACK*>( item ) )
1299 aCollector.Remove( item );
1300 }
1301
1302 sTool->FilterCollectorForLockedItems( aCollector );
1303 } );
1304
1305 m_selectionTool->ReportFilteredLockedItems();
1306
1307 BOARD_COMMIT commit( this );
1308
1309 for( EDA_ITEM* item : selection )
1310 {
1311 if( item->Type() == PCB_VIA_T )
1312 {
1313 PCB_VIA* via = static_cast<PCB_VIA*>( item );
1314
1315 commit.Modify( via );
1316
1317 if( via->GetViaType() == VIATYPE::MICROVIA )
1318 {
1319 NETCLASS* netClass = via->GetEffectiveNetClass();
1320
1321 via->SetSizeFromRules( netClass->GetuViaDiameter(), netClass->GetuViaDrill() );
1322 }
1323 else
1324 {
1325 via->SetDrill( board()->GetDesignSettings().GetCurrentViaDrill() );
1326 via->SetPadstackMode( PADSTACK::MODE::NORMAL );
1327 via->SetWidth( PADSTACK::ALL_LAYERS, board()->GetDesignSettings().GetCurrentViaSize() );
1328 }
1329 }
1330 else if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
1331 {
1332 PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item );
1333
1334 wxCHECK( track, 0 );
1335
1336 commit.Modify( track );
1337
1338 int new_width = board()->GetDesignSettings().GetCurrentTrackWidth();
1339 track->SetWidth( new_width );
1340 }
1341 }
1342
1343 commit.Push( _( "Edit Track Width/Via Size" ) );
1344
1345 if( selection.IsHover() )
1346 {
1347 m_toolMgr->RunAction( ACTIONS::selectionClear );
1348
1349 // Notify other tools of the changes -- This updates the visual ratsnest
1351 }
1352
1353 return 0;
1354}
1355
1356
1358{
1359 if( m_toolMgr->GetTool<ROUTER_TOOL>() && m_toolMgr->GetTool<ROUTER_TOOL>()->IsToolActive() )
1360 return 0;
1361
1362 bool isNext = aEvent.IsAction( &PCB_ACTIONS::changeTrackLayerNext );
1363
1364 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1365 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1366 {
1367 // Iterate from the back so we don't have to worry about removals.
1368 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1369 {
1370 BOARD_ITEM* item = aCollector[i];
1371
1372 if( !dynamic_cast<PCB_TRACK*>( item ) )
1373 aCollector.Remove( item );
1374 }
1375
1376 sTool->FilterCollectorForLockedItems( aCollector );
1377 } );
1378
1379 m_selectionTool->ReportFilteredLockedItems();
1380
1381 PCB_LAYER_ID origLayer = frame()->GetActiveLayer();
1382
1383 if( isNext )
1384 m_toolMgr->RunAction( PCB_ACTIONS::layerNext );
1385 else
1386 m_toolMgr->RunAction( PCB_ACTIONS::layerPrev );
1387
1388 PCB_LAYER_ID newLayer = frame()->GetActiveLayer();
1389
1390 if( newLayer == origLayer )
1391 return 0;
1392
1393 BOARD_COMMIT commit( this );
1394
1395 for( EDA_ITEM* item : selection )
1396 {
1397 if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
1398 {
1399 PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item );
1400
1401 wxCHECK( track, 0 );
1402
1403 commit.Modify( track );
1404
1405 track->SetLayer( newLayer );
1406 }
1407 }
1408
1409 commit.Push( _( "Edit Track Layer" ) );
1410
1411 if( selection.IsHover() )
1412 {
1413 m_toolMgr->RunAction( ACTIONS::selectionClear );
1414
1415 // Notify other tools of the changes -- This updates the visual ratsnest
1417 }
1418
1419 return 0;
1420}
1421
1422
1424{
1425 // Store last used fillet radius to allow pressing "enter" if repeat fillet is required
1426 static int filletRadius = 0;
1427
1428 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1429 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1430 {
1431 // Iterate from the back so we don't have to worry about removals.
1432 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1433 {
1434 BOARD_ITEM* item = aCollector[i];
1435
1436 if( !dynamic_cast<PCB_TRACK*>( item ) )
1437 aCollector.Remove( item );
1438 }
1439
1440 sTool->FilterCollectorForLockedItems( aCollector );
1441 } );
1442
1443 if( m_selectionTool->ReportFilteredLockedItems() )
1444 return 0;
1445
1446 if( selection.Size() < 2 )
1447 {
1448 frame()->ShowInfoBarMsg( _( "At least two straight track segments must be selected." ) );
1449 return 0;
1450 }
1451
1452 WX_UNIT_ENTRY_DIALOG dlg( frame(), _( "Fillet Tracks" ), _( "Radius:" ), filletRadius );
1453
1454 if( dlg.ShowModal() == wxID_CANCEL || dlg.GetValue() == 0 )
1455 return 0;
1456
1457 filletRadius = dlg.GetValue();
1458
1459 struct FILLET_OP
1460 {
1461 PCB_TRACK* t1;
1462 PCB_TRACK* t2;
1463 // Start point of track is modified after PCB_ARC is added, otherwise the end point:
1464 bool t1Start = true;
1465 bool t2Start = true;
1466 };
1467
1468 std::vector<FILLET_OP> filletOperations;
1469 bool operationPerformedOnAtLeastOne = false;
1470 bool didOneAttemptFail = false;
1471 std::set<PCB_TRACK*> processedTracks;
1472
1473 auto processFilletOp =
1474 [&]( PCB_TRACK* aTrack, bool aStartPoint )
1475 {
1476 std::shared_ptr<CONNECTIVITY_DATA> c = board()->GetConnectivity();
1477 VECTOR2I anchor = aStartPoint ? aTrack->GetStart() : aTrack->GetEnd();
1478 std::vector<BOARD_CONNECTED_ITEM*> itemsOnAnchor;
1479
1480 itemsOnAnchor = c->GetConnectedItemsAtAnchor( aTrack, anchor, baseConnectedTypes );
1481
1482 if( itemsOnAnchor.size() > 0 && selection.Contains( itemsOnAnchor.at( 0 ) )
1483 && itemsOnAnchor.at( 0 )->Type() == PCB_TRACE_T )
1484 {
1485 PCB_TRACK* trackOther = static_cast<PCB_TRACK*>( itemsOnAnchor.at( 0 ) );
1486
1487 // Make sure we don't fillet the same pair of tracks twice
1488 if( processedTracks.find( trackOther ) == processedTracks.end() )
1489 {
1490 if( itemsOnAnchor.size() == 1 )
1491 {
1492 FILLET_OP filletOp;
1493 filletOp.t1 = aTrack;
1494 filletOp.t2 = trackOther;
1495 filletOp.t1Start = aStartPoint;
1496 filletOp.t2Start = aTrack->IsPointOnEnds( filletOp.t2->GetStart() );
1497 filletOperations.push_back( filletOp );
1498 }
1499 else
1500 {
1501 // User requested to fillet these two tracks but not possible as
1502 // there are other elements connected at that point
1503 didOneAttemptFail = true;
1504 }
1505 }
1506 }
1507 };
1508
1509 for( EDA_ITEM* item : selection )
1510 {
1511 if( item->Type() == PCB_TRACE_T )
1512 {
1513 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
1514
1515 if( track->GetLength() > 0 )
1516 {
1517 processFilletOp( track, true ); // on the start point of track
1518 processFilletOp( track, false ); // on the end point of track
1519
1520 processedTracks.insert( track );
1521 }
1522 }
1523 }
1524
1525 BOARD_COMMIT commit( this );
1526 std::vector<BOARD_ITEM*> itemsToAddToSelection;
1527
1528 for( FILLET_OP filletOp : filletOperations )
1529 {
1530 PCB_TRACK* track1 = filletOp.t1;
1531 PCB_TRACK* track2 = filletOp.t2;
1532
1533 bool trackOnStart = track1->IsPointOnEnds( track2->GetStart() );
1534 bool trackOnEnd = track1->IsPointOnEnds( track2->GetEnd() );
1535
1536 if( trackOnStart && trackOnEnd )
1537 continue; // Ignore duplicate tracks
1538
1539 if( ( trackOnStart || trackOnEnd ) && track1->GetLayer() == track2->GetLayer() )
1540 {
1541 SEG t1Seg( track1->GetStart(), track1->GetEnd() );
1542 SEG t2Seg( track2->GetStart(), track2->GetEnd() );
1543
1544 if( t1Seg.ApproxCollinear( t2Seg ) )
1545 continue;
1546
1547 SHAPE_ARC sArc( t1Seg, t2Seg, filletRadius );
1548 VECTOR2I t1newPoint, t2newPoint;
1549
1550 auto setIfPointOnSeg =
1551 []( VECTOR2I& aPointToSet, const SEG& aSegment, const VECTOR2I& aVecToTest )
1552 {
1553 VECTOR2I segToVec = aSegment.NearestPoint( aVecToTest ) - aVecToTest;
1554
1555 // Find out if we are on the segment (minimum precision)
1557 {
1558 aPointToSet.x = aVecToTest.x;
1559 aPointToSet.y = aVecToTest.y;
1560 return true;
1561 }
1562
1563 return false;
1564 };
1565
1566 //Do not draw a fillet if the end points of the arc are not within the track segments
1567 if( !setIfPointOnSeg( t1newPoint, t1Seg, sArc.GetP0() )
1568 && !setIfPointOnSeg( t2newPoint, t2Seg, sArc.GetP0() ) )
1569 {
1570 didOneAttemptFail = true;
1571 continue;
1572 }
1573
1574 if( !setIfPointOnSeg( t1newPoint, t1Seg, sArc.GetP1() )
1575 && !setIfPointOnSeg( t2newPoint, t2Seg, sArc.GetP1() ) )
1576 {
1577 didOneAttemptFail = true;
1578 continue;
1579 }
1580
1581 PCB_ARC* tArc = new PCB_ARC( frame()->GetBoard(), &sArc );
1582 tArc->SetLayer( track1->GetLayer() );
1583 tArc->SetWidth( track1->GetWidth() );
1584 tArc->SetNet( track1->GetNet() );
1585 tArc->SetLocked( track1->IsLocked() );
1586 tArc->SetHasSolderMask( track1->HasSolderMask() );
1588 commit.Add( tArc );
1589 itemsToAddToSelection.push_back( tArc );
1590
1591 commit.Modify( track1 );
1592 commit.Modify( track2 );
1593
1594 if( filletOp.t1Start )
1595 track1->SetStart( t1newPoint );
1596 else
1597 track1->SetEnd( t1newPoint );
1598
1599 if( filletOp.t2Start )
1600 track2->SetStart( t2newPoint );
1601 else
1602 track2->SetEnd( t2newPoint );
1603
1604 operationPerformedOnAtLeastOne = true;
1605 }
1606 }
1607
1608 commit.Push( _( "Fillet Tracks" ) );
1609
1610 //select the newly created arcs
1611 for( BOARD_ITEM* item : itemsToAddToSelection )
1612 m_selectionTool->AddItemToSel( item );
1613
1614 if( !operationPerformedOnAtLeastOne )
1615 frame()->ShowInfoBarMsg( _( "Unable to fillet the selected track segments." ) );
1616 else if( didOneAttemptFail )
1617 frame()->ShowInfoBarMsg( _( "Some of the track segments could not be filleted." ) );
1618
1619 return 0;
1620}
1621
1622
1632static std::optional<int> GetRadiusParams( PCB_BASE_EDIT_FRAME& aFrame, const wxString& aTitle, int& aPersitentRadius )
1633{
1634 WX_UNIT_ENTRY_DIALOG dlg( &aFrame, aTitle, _( "Radius:" ), aPersitentRadius );
1635
1636 if( dlg.ShowModal() == wxID_CANCEL || dlg.GetValue() == 0 )
1637 return std::nullopt;
1638
1639 aPersitentRadius = dlg.GetValue();
1640
1641 return aPersitentRadius;
1642}
1643
1644
1645static std::optional<DOGBONE_CORNER_ROUTINE::PARAMETERS> GetDogboneParams( PCB_BASE_EDIT_FRAME& aFrame )
1646{
1647 // Persistent parameters
1648 static DOGBONE_CORNER_ROUTINE::PARAMETERS s_dogBoneParams{
1649 pcbIUScale.mmToIU( 1 ),
1650 true,
1651 };
1652
1653 std::vector<WX_MULTI_ENTRY_DIALOG::ENTRY> entries{
1654 {
1655 _( "Arc radius:" ),
1656 WX_MULTI_ENTRY_DIALOG::UNIT_BOUND{ s_dogBoneParams.DogboneRadiusIU },
1657 wxEmptyString,
1658 },
1659 {
1660 _( "Add slots in acute corners" ),
1661 WX_MULTI_ENTRY_DIALOG::CHECKBOX{ s_dogBoneParams.AddSlots },
1662 _( "Add slots in acute corners to allow access to a cutter of the given radius" ),
1663 },
1664 };
1665
1666 WX_MULTI_ENTRY_DIALOG dlg( &aFrame, _( "Dogbone Corner Settings" ), entries );
1667
1668 if( dlg.ShowModal() == wxID_CANCEL )
1669 return std::nullopt;
1670
1671 std::vector<WX_MULTI_ENTRY_DIALOG::RESULT> results = dlg.GetValues();
1672 wxCHECK( results.size() == 2, std::nullopt );
1673
1674 try
1675 {
1676 s_dogBoneParams.DogboneRadiusIU = std::get<long long int>( results[0] );
1677 s_dogBoneParams.AddSlots = std::get<bool>( results[1] );
1678 }
1679 catch( const std::bad_variant_access& )
1680 {
1681 wxASSERT( false );
1682 return std::nullopt;
1683 }
1684
1685 return s_dogBoneParams;
1686}
1687
1694static std::optional<CHAMFER_PARAMS> GetChamferParams( PCB_BASE_EDIT_FRAME& aFrame )
1695{
1696 // Non-zero and the KLC default for Fab layer chamfers
1697 const int default_setback = pcbIUScale.mmToIU( 1 );
1698 // Store last used setback to allow pressing "enter" if repeat chamfer is required
1699 static CHAMFER_PARAMS params{ default_setback, default_setback };
1700
1701 WX_UNIT_ENTRY_DIALOG dlg( &aFrame, _( "Chamfer Lines" ), _( "Chamfer setback:" ), params.m_chamfer_setback_a );
1702
1703 if( dlg.ShowModal() == wxID_CANCEL || dlg.GetValue() == 0 )
1704 return std::nullopt;
1705
1706 params.m_chamfer_setback_a = dlg.GetValue();
1707 // It's hard to easily specify an asymmetric chamfer (which line gets the longer setback?),
1708 // so we just use the same setback for each
1709 params.m_chamfer_setback_b = params.m_chamfer_setback_a;
1710
1711 return params;
1712}
1713
1714
1716{
1717 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1718 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1719 {
1720 std::vector<VECTOR2I> pts;
1721
1722 // Iterate from the back so we don't have to worry about removals.
1723 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1724 {
1725 BOARD_ITEM* item = aCollector[i];
1726
1727 // We've converted the polygon and rectangle to segments, so drop everything
1728 // that isn't a segment at this point
1729 if( !item->IsType(
1730 { PCB_SHAPE_LOCATE_SEGMENT_T, PCB_SHAPE_LOCATE_POLY_T, PCB_SHAPE_LOCATE_RECT_T } ) )
1731 {
1732 aCollector.Remove( item );
1733 }
1734 }
1735
1736 sTool->FilterCollectorForLockedItems( aCollector );
1737 } );
1738
1739 m_selectionTool->ReportFilteredLockedItems();
1740
1741 std::set<PCB_SHAPE*> lines_to_add;
1742 std::vector<PCB_SHAPE*> items_to_remove;
1743
1744 for( EDA_ITEM* item : selection )
1745 {
1746 std::vector<VECTOR2I> pts;
1747 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( item );
1748 PCB_LAYER_ID layer = graphic->GetLayer();
1749 int width = graphic->GetWidth();
1750
1751 if( graphic->GetShape() == SHAPE_T::RECTANGLE )
1752 {
1753 items_to_remove.push_back( graphic );
1754 VECTOR2I start( graphic->GetStart() );
1755 VECTOR2I end( graphic->GetEnd() );
1756 pts.emplace_back( start );
1757 pts.emplace_back( VECTOR2I( end.x, start.y ) );
1758 pts.emplace_back( end );
1759 pts.emplace_back( VECTOR2I( start.x, end.y ) );
1760 }
1761
1762 if( graphic->GetShape() == SHAPE_T::POLY )
1763 {
1764 items_to_remove.push_back( graphic );
1765
1766 for( int jj = 0; jj < graphic->GetPolyShape().VertexCount(); ++jj )
1767 pts.emplace_back( graphic->GetPolyShape().CVertex( jj ) );
1768 }
1769
1770 for( size_t jj = 1; jj < pts.size(); ++jj )
1771 {
1772 PCB_SHAPE* line = new PCB_SHAPE( frame()->GetModel(), SHAPE_T::SEGMENT );
1773
1774 line->SetStart( pts[jj - 1] );
1775 line->SetEnd( pts[jj] );
1776 line->SetWidth( width );
1777 line->SetLayer( layer );
1778 lines_to_add.insert( line );
1779 }
1780
1781 if( pts.size() > 1 )
1782 {
1783 PCB_SHAPE* line = new PCB_SHAPE( frame()->GetModel(), SHAPE_T::SEGMENT );
1784
1785 line->SetStart( pts.back() );
1786 line->SetEnd( pts.front() );
1787 line->SetWidth( width );
1788 line->SetLayer( layer );
1789 lines_to_add.insert( line );
1790 }
1791 }
1792
1793 int segmentCount = selection.CountType( PCB_SHAPE_LOCATE_SEGMENT_T ) + lines_to_add.size();
1794
1795 if( aEvent.IsAction( &PCB_ACTIONS::extendLines ) && segmentCount != 2 )
1796 {
1797 frame()->ShowInfoBarMsg( _( "Exactly two lines must be selected to extend them." ) );
1798
1799 for( PCB_SHAPE* line : lines_to_add )
1800 delete line;
1801
1802 return 0;
1803 }
1804 else if( segmentCount < 2 )
1805 {
1806 frame()->ShowInfoBarMsg( _( "A shape with at least two lines must be selected." ) );
1807
1808 for( PCB_SHAPE* line : lines_to_add )
1809 delete line;
1810
1811 return 0;
1812 }
1813
1814 BOARD_COMMIT commit( this );
1815
1816 // Items created like lines from a rectangle
1817 for( PCB_SHAPE* item : lines_to_add )
1818 {
1819 commit.Add( item );
1820 selection.Add( item );
1821 }
1822
1823 // Remove items like rectangles that we decomposed into lines
1824 for( PCB_SHAPE* item : items_to_remove )
1825 {
1826 selection.Remove( item );
1827 commit.Remove( item );
1828 }
1829
1830 for( EDA_ITEM* item : selection )
1831 item->ClearFlags( STRUCT_DELETED );
1832
1833 // List of thing to select at the end of the operation
1834 // (doing it as we go will invalidate the iterator)
1835 std::vector<BOARD_ITEM*> items_to_select_on_success;
1836
1837 // And same for items to deselect
1838 std::vector<BOARD_ITEM*> items_to_deselect_on_success;
1839
1840 // Handle modifications to existing items by the routine
1841 // How to deal with this depends on whether we're in the footprint editor or not
1842 // and whether the item was conjured up by decomposing a polygon or rectangle
1843 auto item_modification_handler =
1844 [&]( BOARD_ITEM& aItem )
1845 {
1846 // If the item was "conjured up" it will be added later separately
1847 if( !alg::contains( lines_to_add, &aItem ) )
1848 {
1849 commit.Modify( &aItem );
1850 items_to_select_on_success.push_back( &aItem );
1851 }
1852 };
1853
1854 bool any_items_created = !lines_to_add.empty();
1855 auto item_creation_handler =
1856 [&]( std::unique_ptr<BOARD_ITEM> aItem )
1857 {
1858 any_items_created = true;
1859 items_to_select_on_success.push_back( aItem.get() );
1860 commit.Add( aItem.release() );
1861 };
1862
1863 bool any_items_removed = !items_to_remove.empty();
1864 auto item_removal_handler =
1865 [&]( BOARD_ITEM& aItem )
1866 {
1867 aItem.SetFlags( STRUCT_DELETED );
1868 any_items_removed = true;
1869 items_to_deselect_on_success.push_back( &aItem );
1870 commit.Remove( &aItem );
1871 };
1872
1873 // Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
1874 ITEM_MODIFICATION_ROUTINE::CALLABLE_BASED_HANDLER change_handler( item_creation_handler, item_modification_handler,
1875 item_removal_handler );
1876
1877 // Construct an appropriate tool
1878 std::unique_ptr<PAIRWISE_LINE_ROUTINE> pairwise_line_routine;
1879
1880 if( aEvent.IsAction( &PCB_ACTIONS::filletLines ) )
1881 {
1882 static int s_filletRadius = pcbIUScale.mmToIU( 1 );
1883 std::optional<int> filletRadiusIU = GetRadiusParams( *frame(), _( "Fillet Lines" ), s_filletRadius );
1884
1885 if( filletRadiusIU.has_value() )
1886 {
1887 pairwise_line_routine = std::make_unique<LINE_FILLET_ROUTINE>( frame()->GetModel(), change_handler,
1888 *filletRadiusIU );
1889 }
1890 }
1891 else if( aEvent.IsAction( &PCB_ACTIONS::dogboneCorners ) )
1892 {
1893 std::optional<DOGBONE_CORNER_ROUTINE::PARAMETERS> dogboneParams = GetDogboneParams( *frame() );
1894
1895 if( dogboneParams.has_value() )
1896 {
1897 pairwise_line_routine = std::make_unique<DOGBONE_CORNER_ROUTINE>( frame()->GetModel(), change_handler,
1898 *dogboneParams );
1899 }
1900 }
1901 else if( aEvent.IsAction( &PCB_ACTIONS::chamferLines ) )
1902 {
1903 std::optional<CHAMFER_PARAMS> chamfer_params = GetChamferParams( *frame() );
1904
1905 if( chamfer_params.has_value() )
1906 {
1907 pairwise_line_routine = std::make_unique<LINE_CHAMFER_ROUTINE>( frame()->GetModel(), change_handler,
1908 *chamfer_params );
1909 }
1910 }
1911 else if( aEvent.IsAction( &PCB_ACTIONS::extendLines ) )
1912 {
1913 pairwise_line_routine = std::make_unique<LINE_EXTENSION_ROUTINE>( frame()->GetModel(), change_handler );
1914 }
1915
1916 if( !pairwise_line_routine )
1917 {
1918 // Didn't construct any mofication routine - user must have cancelled
1919 commit.Revert();
1920 return 0;
1921 }
1922
1923 // Apply the tool to every line pair
1924 alg::for_all_pairs( selection.begin(), selection.end(),
1925 [&]( EDA_ITEM* a, EDA_ITEM* b )
1926 {
1927 if( ( a->GetFlags() & STRUCT_DELETED ) == 0 && ( b->GetFlags() & STRUCT_DELETED ) == 0 )
1928 {
1929 PCB_SHAPE* line_a = static_cast<PCB_SHAPE*>( a );
1930 PCB_SHAPE* line_b = static_cast<PCB_SHAPE*>( b );
1931
1932 pairwise_line_routine->ProcessLinePair( *line_a, *line_b );
1933 }
1934 } );
1935
1936 // Select added and modified items
1937 for( BOARD_ITEM* item : items_to_select_on_success )
1938 m_selectionTool->AddItemToSel( item, true );
1939
1940 // Deselect removed items
1941 for( BOARD_ITEM* item : items_to_deselect_on_success )
1942 m_selectionTool->RemoveItemFromSel( item, true );
1943
1944 if( any_items_removed )
1945 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1946
1947 if( any_items_created )
1948 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1949
1950 // Notify other tools of the changes
1951 m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1952
1953 commit.Push( pairwise_line_routine->GetCommitDescription() );
1954
1955 if( const std::optional<wxString> msg = pairwise_line_routine->GetStatusMessage( segmentCount ) )
1956 frame()->ShowInfoBarMsg( *msg );
1957
1958 return 0;
1959}
1960
1961
1963{
1964 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1965 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1966 {
1967 std::vector<VECTOR2I> pts;
1968
1969 // Iterate from the back so we don't have to worry about removals.
1970 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1971 {
1972 BOARD_ITEM* item = aCollector[i];
1973
1974 if( !item->IsType( { PCB_SHAPE_LOCATE_POLY_T, PCB_ZONE_T } ) )
1975 aCollector.Remove( item );
1976
1977 if( ZONE* zone = dyn_cast<ZONE*>( item ) )
1978 {
1979 if( zone->IsTeardropArea() )
1980 aCollector.Remove( item );
1981 }
1982 }
1983
1984 sTool->FilterCollectorForLockedItems( aCollector );
1985 } );
1986
1987 m_selectionTool->ReportFilteredLockedItems();
1988
1989 // Store last used value
1990 static int s_toleranceValue = pcbIUScale.mmToIU( 3 );
1991
1992 WX_UNIT_ENTRY_DIALOG dlg( frame(), _( "Simplify Shapes" ), _( "Tolerance value:" ), s_toleranceValue );
1993
1994 if( dlg.ShowModal() == wxID_CANCEL )
1995 return 0;
1996
1997 s_toleranceValue = dlg.GetValue();
1998
1999 if( s_toleranceValue <= 0 )
2000 return 0;
2001
2002 BOARD_COMMIT commit{ this };
2003
2004 std::vector<PCB_SHAPE*> shapeList;
2005
2006 for( EDA_ITEM* item : selection )
2007 {
2008 commit.Modify( item );
2009
2010 if( PCB_SHAPE* shape = dyn_cast<PCB_SHAPE*>( item ) )
2011 {
2012 SHAPE_POLY_SET& poly = shape->GetPolyShape();
2013
2014 poly.SimplifyOutlines( s_toleranceValue );
2015 }
2016
2017 if( ZONE* zone = dyn_cast<ZONE*>( item ) )
2018 {
2019 SHAPE_POLY_SET* poly = zone->Outline();
2020
2021 poly->SimplifyOutlines( s_toleranceValue );
2022 zone->HatchBorder();
2023 }
2024 }
2025
2026 commit.Push( _( "Simplify Polygons" ) );
2027
2028 // Notify other tools of the changes
2030
2031 return 0;
2032}
2033
2034
2036{
2037 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2038 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2039 {
2040 std::vector<VECTOR2I> pts;
2041
2042 // Iterate from the back so we don't have to worry about removals.
2043 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
2044 {
2045 BOARD_ITEM* item = aCollector[i];
2046
2047 // We've converted the polygon and rectangle to segments, so drop everything
2048 // that isn't a segment at this point
2049 if( !item->IsType( { PCB_SHAPE_LOCATE_SEGMENT_T,
2050 PCB_SHAPE_LOCATE_ARC_T,
2051 PCB_SHAPE_LOCATE_BEZIER_T } ) )
2052 {
2053 aCollector.Remove( item );
2054 }
2055 }
2056
2057 sTool->FilterCollectorForLockedItems( aCollector );
2058 } );
2059
2060 m_selectionTool->ReportFilteredLockedItems();
2061
2062 // Store last used value
2063 static int s_toleranceValue = pcbIUScale.mmToIU( 3 );
2064
2065 WX_UNIT_ENTRY_DIALOG dlg( frame(), _( "Heal Shapes" ), _( "Tolerance value:" ), s_toleranceValue );
2066
2067 if( dlg.ShowModal() == wxID_CANCEL )
2068 return 0;
2069
2070 s_toleranceValue = dlg.GetValue();
2071
2072 if( s_toleranceValue <= 0 )
2073 return 0;
2074
2075 BOARD_COMMIT commit{ this };
2076
2077 std::vector<PCB_SHAPE*> shapeList;
2078
2079 for( EDA_ITEM* item : selection )
2080 {
2081 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
2082 {
2083 shapeList.push_back( shape );
2084 commit.Modify( shape );
2085 }
2086 }
2087
2088 ConnectBoardShapes( shapeList, s_toleranceValue );
2089
2090 commit.Push( _( "Heal Shapes" ) );
2091
2092 // Notify other tools of the changes
2094
2095 return 0;
2096}
2097
2098
2100{
2101 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2102 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2103 {
2104 // Iterate from the back so we don't have to worry about removals.
2105 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
2106 {
2107 BOARD_ITEM* item = aCollector[i];
2108
2109 static const std::vector<KICAD_T> polygonBooleanTypes = {
2113 };
2114
2115 if( !item->IsType( polygonBooleanTypes ) )
2116 aCollector.Remove( item );
2117 }
2118
2119 sTool->FilterCollectorForLockedItems( aCollector );
2120 } );
2121
2122 m_selectionTool->ReportFilteredLockedItems();
2123
2124 const EDA_ITEM* const last_item = selection.GetLastAddedItem();
2125
2126 // Gather or construct polygon source shapes to merge
2127 std::vector<PCB_SHAPE*> items_to_process;
2128
2129 for( EDA_ITEM* item : selection )
2130 {
2131 items_to_process.push_back( static_cast<PCB_SHAPE*>( item ) );
2132
2133 // put the last one in the selection at the front of the vector
2134 // so it can be used as the property donor and as the basis for the
2135 // boolean operation
2136 if( item == last_item )
2137 std::swap( items_to_process.back(), items_to_process.front() );
2138 }
2139
2140 BOARD_COMMIT commit{ this };
2141
2142 // Handle modifications to existing items by the routine
2143 auto item_modification_handler =
2144 [&]( BOARD_ITEM& aItem )
2145 {
2146 commit.Modify( &aItem );
2147 };
2148
2149 std::vector<BOARD_ITEM*> items_to_select_on_success;
2150
2151 auto item_creation_handler =
2152 [&]( std::unique_ptr<BOARD_ITEM> aItem )
2153 {
2154 items_to_select_on_success.push_back( aItem.get() );
2155 commit.Add( aItem.release() );
2156 };
2157
2158 auto item_removal_handler =
2159 [&]( BOARD_ITEM& aItem )
2160 {
2161 commit.Remove( &aItem );
2162 };
2163
2164 // Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
2165 ITEM_MODIFICATION_ROUTINE::CALLABLE_BASED_HANDLER change_handler( item_creation_handler, item_modification_handler,
2166 item_removal_handler );
2167
2168 // Construct an appropriate routine
2169 std::unique_ptr<POLYGON_BOOLEAN_ROUTINE> boolean_routine;
2170
2171 const auto create_routine =
2172 [&]() -> std::unique_ptr<POLYGON_BOOLEAN_ROUTINE>
2173 {
2174 // (Re-)construct the boolean routine based on the action
2175 // This is done here so that we can re-init the routine if we need to
2176 // go again in the reverse order.
2177
2178 BOARD_ITEM_CONTAINER* const model = frame()->GetModel();
2179 wxCHECK( model, nullptr );
2180
2181 if( aEvent.IsAction( &PCB_ACTIONS::mergePolygons ) )
2182 {
2183 return std::make_unique<POLYGON_MERGE_ROUTINE>( model, change_handler );
2184 }
2185 else if( aEvent.IsAction( &PCB_ACTIONS::subtractPolygons ) )
2186 {
2187 return std::make_unique<POLYGON_SUBTRACT_ROUTINE>( model, change_handler );
2188 }
2189 else if( aEvent.IsAction( &PCB_ACTIONS::intersectPolygons ) )
2190 {
2191 return std::make_unique<POLYGON_INTERSECT_ROUTINE>( model, change_handler );
2192 }
2193 return nullptr;
2194 };
2195
2196 const auto run_routine =
2197 [&]()
2198 {
2199 // Perform the operation on each polygon
2200 for( PCB_SHAPE* shape : items_to_process )
2201 boolean_routine->ProcessShape( *shape );
2202
2203 boolean_routine->Finalize();
2204 };
2205
2206 boolean_routine = create_routine();
2207
2208 wxCHECK_MSG( boolean_routine, 0, "Could not find a polygon routine for this action" );
2209
2210 // First run the routine and see what we get
2211 run_routine();
2212
2213 // If we are doing a non-commutative operation (e.g. subtract), and we just got null,
2214 // assume the user meant go in a different opposite order
2215 if( !boolean_routine->IsCommutative() && items_to_select_on_success.empty() )
2216 {
2217 // Clear the commit and the selection
2218 commit.Revert();
2219 items_to_select_on_success.clear();
2220
2221 std::map<const PCB_SHAPE*, VECTOR2I::extended_type> items_area;
2222
2223 for( PCB_SHAPE* shape : items_to_process )
2224 {
2225 VECTOR2I::extended_type area = shape->GetBoundingBox().GetArea();
2226 items_area[shape] = area;
2227 }
2228
2229 // Sort the shapes by their bounding box area in descending order
2230 // This way we will start with the largest shape first and subtract the smaller ones
2231 // This may not work perfectly in all cases, but it works well when the larger
2232 // shape completely contains the smaller ones, which is probably the most common case.
2233 // In other cases, the user will need to select the shapes in the correct order (i.e.
2234 // the largest shape last), or do the subtractions in multiple steps.
2235 std::sort( items_to_process.begin(), items_to_process.end(),
2236 [&]( const PCB_SHAPE* a, const PCB_SHAPE* b )
2237 {
2238 return items_area[a] > items_area[b];
2239 } );
2240
2241 // Run the routine again
2242 boolean_routine = create_routine();
2243 run_routine();
2244 }
2245
2246 // Select new items
2247 for( BOARD_ITEM* item : items_to_select_on_success )
2248 m_selectionTool->AddItemToSel( item, true );
2249
2250 // Notify other tools of the changes
2252
2253 commit.Push( boolean_routine->GetCommitDescription() );
2254
2255 if( const std::optional<wxString> msg = boolean_routine->GetStatusMessage() )
2256 frame()->ShowInfoBarMsg( *msg );
2257
2258 return 0;
2259}
2260
2261
2263{
2265
2266 // A constraint selected by clicking its badge has no board selection, so Edit targets it here
2267 // before the normal properties path (mirrors the Delete hook for a badge-selected constraint).
2268 if( CONSTRAINT_EDIT_TOOL* constraintTool = m_toolMgr->GetTool<CONSTRAINT_EDIT_TOOL>();
2269 constraintTool && constraintTool->TryEditSelectedConstraint() )
2270 {
2271 return 0;
2272 }
2273
2274 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2275 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2276 {
2277 } );
2278
2279 // Snapshot undo depth to detect whether dialog actually committed
2280 // Cancel leaves it unchanged and must not trigger a constraint resolve
2281 const int undoBefore = editFrame->GetUndoCommandCount();
2282
2283 // Detector to reject attempting to edit generated items that may be selectable
2284 // but we don't want to remain read-only to outside-the-generator editing
2285 auto containsReadOnlyGenChild =
2286 []( const SELECTION& aSel ) -> bool
2287 {
2288 for( EDA_ITEM* item : aSel )
2289 {
2290 if( !item->IsBOARD_ITEM() )
2291 continue;
2292
2293 EDA_GROUP* parent = static_cast<BOARD_ITEM*>( item )->GetParentGroup();
2294
2295 if( !parent || parent->AsEdaItem()->Type() != PCB_GENERATOR_T )
2296 continue;
2297
2298 PCB_GENERATOR* gen = static_cast<PCB_GENERATOR*>( parent->AsEdaItem() );
2299
2300 if( gen->ChildrenAreReadOnly() )
2301 return true;
2302 }
2303
2304 return false;
2305 };
2306
2307 if( containsReadOnlyGenChild( selection ) )
2308 {
2309 wxBell();
2310 }
2311 // Tracks & vias are treated in a special way:
2313 {
2314 DIALOG_TRACK_VIA_PROPERTIES dlg( editFrame, selection );
2315 dlg.ShowQuasiModal(); // QuasiModal required for NET_SELECTOR
2316 }
2318 {
2319 std::vector<PCB_TABLECELL*> cells;
2320
2321 for( EDA_ITEM* item : selection.Items() )
2322 cells.push_back( static_cast<PCB_TABLECELL*>( item ) );
2323
2324 DIALOG_TABLECELL_PROPERTIES dlg( editFrame, cells );
2325
2326 // QuasiModal required for syntax help and Scintilla auto-complete
2327 dlg.ShowQuasiModal();
2328
2330 {
2331 PCB_TABLE* table = static_cast<PCB_TABLE*>( cells[0]->GetParent() );
2332 DIALOG_TABLE_PROPERTIES tableDlg( frame(), table );
2333
2334 tableDlg.ShowQuasiModal(); // Scintilla's auto-complete requires quasiModal
2335 }
2336 }
2337 else if( selection.Size() == 1 && selection.Front()->Type() == PCB_GROUP_T
2338 && MULTICHANNEL_TOOL::ChannelZone( static_cast<PCB_GROUP*>( selection.Front() ) ) )
2339 {
2340 // A repeated channel is edited through the Repeat Layout dialog, which applies to the whole set
2342 }
2343 else if( selection.Size() == 1 && selection.Front()->IsBOARD_ITEM() )
2344 {
2345 // Display properties dialog
2346 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
2347
2348 // Do not handle undo buffer, it is done by the properties dialogs
2349 editFrame->OnEditItemRequest( item );
2350
2351 // Notify other tools of the changes
2353 }
2354 else if( selection.Size() == 0 && getView()->IsLayerVisible( LAYER_DRAWINGSHEET ) )
2355 {
2356 DS_PROXY_VIEW_ITEM* ds = editFrame->GetCanvas()->GetDrawingSheet();
2357 VECTOR2D cursorPos = getViewControls()->GetCursorPosition( false );
2358
2359 if( ds && ds->HitTestDrawingSheetItems( getView(), cursorPos ) )
2360 m_toolMgr->PostAction( ACTIONS::pageSettings );
2361 else
2363 }
2364 else if( selection.Size() > 1 )
2365 {
2366 WX_INFOBAR* infobar = frame()->GetInfoBar();
2367
2368 infobar->RemoveAllButtons();
2369
2370 if( !frame()->GetPropertiesPanel()->IsShownOnScreen() )
2371 {
2372 infobar->AddLink( _( "Show Properties panel" ),
2373 [this]( wxHyperlinkEvent& aHyperlinkEvent )
2374 {
2375 frame()->ToggleProperties();
2376 } );
2377 }
2378
2379 infobar->AddCloseButton();
2380 infobar->ShowMessageFor( _( "Use Properties panel to edit properties common to selected items." ),
2381 8000, wxICON_INFORMATION );
2382 }
2383
2384 // Position or geometry edit via these dialogs settles constraints as if item were dragged
2385 // holding edited item and moving neighbors gated on real commit so canceled dialog skips the solve
2386 if( editFrame->GetUndoCommandCount() > undoBefore )
2387 {
2388 if( CONSTRAINT_EDIT_TOOL* constraintTool = m_toolMgr->GetTool<CONSTRAINT_EDIT_TOOL>() )
2389 {
2390 std::vector<PCB_SHAPE*> shapes;
2392 constraintTool->SolveAfterEdit( shapes );
2393 }
2394 }
2395
2396 if( selection.IsHover() )
2397 {
2398 m_toolMgr->RunAction( ACTIONS::selectionClear );
2399 }
2400 else
2401 {
2402 // Check for items becoming invisible and drop them from the selection.
2403
2404 PCB_SELECTION selCopy = selection;
2405 LSET visible = editFrame->GetBoard()->GetVisibleLayers();
2406
2407 for( EDA_ITEM* eda_item : selCopy )
2408 {
2409 if( !eda_item->IsBOARD_ITEM() )
2410 continue;
2411
2412 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( eda_item );
2413
2414 if( !( item->GetLayerSet() & visible ).any() )
2415 m_selectionTool->RemoveItemFromSel( item );
2416 }
2417 }
2418
2419 if( m_dragging )
2420 {
2423 }
2424
2425 return 0;
2426}
2427
2428
2430{
2431 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2432 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2433 {
2434 sTool->FilterCollectorForMarkers( aCollector );
2435 sTool->FilterCollectorForHierarchy( aCollector, true );
2436 sTool->FilterCollectorForTableCells( aCollector );
2437 sTool->FilterCollectorForLockedItems( aCollector );
2438 } );
2439
2440 m_selectionTool->ReportFilteredLockedItems();
2441
2443 {
2444 wxBell();
2445 return 0;
2446 }
2447
2449 BOARD_ITEM* item = dynamic_cast<BOARD_ITEM*>( selection.Front() );
2450
2451 if( editFrame && item )
2452 editFrame->OpenVertexEditor( item );
2453
2454 return 0;
2455}
2456
2457
2458void EDIT_TOOL::collectConstraintShapes( const SELECTION& aSelection, std::vector<PCB_SHAPE*>& aShapes )
2459{
2460 // Recurse so constrained shapes carried inside a transformed footprint or group seed their
2461 // clusters too; a top-level-only walk would leave those constraints silently violated.
2462 // PCB_GROUP::RunOnChildren only descends into groups and generators, so recurse through every
2463 // container ourselves; the visited set guards against overlapping ownership paths.
2464 std::unordered_set<BOARD_ITEM*> visited;
2465
2466 std::function<void( BOARD_ITEM* )> collect =
2467 [&]( BOARD_ITEM* aItem )
2468 {
2469 if( !aItem || !visited.insert( aItem ).second )
2470 return;
2471
2472 if( aItem->Type() == PCB_SHAPE_T )
2473 aShapes.push_back( static_cast<PCB_SHAPE*>( aItem ) );
2474
2475 aItem->RunOnChildren( collect, RECURSE_MODE::NO_RECURSE );
2476 };
2477
2478 for( EDA_ITEM* item : aSelection )
2479 {
2480 if( item->IsBOARD_ITEM() )
2481 collect( static_cast<BOARD_ITEM*>( item ) );
2482 }
2483}
2484
2485
2487{
2488 CONSTRAINT_EDIT_TOOL* constraintTool = m_toolMgr->GetTool<CONSTRAINT_EDIT_TOOL>();
2489
2490 if( !constraintTool )
2491 return;
2492
2493 std::vector<PCB_SHAPE*> shapes;
2494 collectConstraintShapes( aSelection, shapes );
2495
2496 constraintTool->SolveAfterMove( shapes );
2497}
2498
2499
2500int EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent )
2501{
2502 if( isRouterActive() )
2503 {
2504 wxBell();
2505 return 0;
2506 }
2507
2509 BOARD_COMMIT localCommit( this );
2510 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
2511
2512 if( !commit )
2513 commit = &localCommit;
2514
2515 // Be sure that there is at least one item that we can modify. If nothing was selected before,
2516 // try looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection)
2517 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2518 [&]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2519 {
2520 sTool->FilterCollectorForMarkers( aCollector );
2521 sTool->FilterCollectorForHierarchy( aCollector, true );
2522 sTool->FilterCollectorForFreePads( aCollector, false );
2523 sTool->FilterCollectorForTableCells( aCollector );
2524
2525 // Filter locked items if in board editor and in free-pad-mode. (If we're not in
2526 // free-pad mode we delay this until the second RequestSelection().)
2527 if( !m_isFootprintEditor && frame()->GetPcbNewSettings()->m_AllowFreePads )
2528 sTool->FilterCollectorForLockedItems( aCollector );
2529 } );
2530
2531 if( m_selectionTool->ReportFilteredLockedItems() )
2532 return 0;
2533
2534 if( selection.Empty() )
2535 return 0;
2536
2537 std::optional<VECTOR2I> oldRefPt;
2538 bool is_hover = selection.IsHover(); // N.B. This must be saved before the second
2539 // call to RequestSelection() below
2540
2541 if( selection.HasReferencePoint() )
2542 oldRefPt = selection.GetReferencePoint();
2543
2544 // Now filter out pads if not in free pads mode. We cannot do this in the first
2545 // RequestSelection() as we need the reference point when a pad is the selection front.
2546 if( !m_isFootprintEditor && !frame()->GetPcbNewSettings()->m_AllowFreePads )
2547 {
2548 PCB_SELECTION& filtered = m_selectionTool->RequestSelection(
2549 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2550 {
2551 sTool->FilterCollectorForMarkers( aCollector );
2552 sTool->FilterCollectorForHierarchy( aCollector, true );
2553 sTool->FilterCollectorForFreePads( aCollector );
2554 sTool->FilterCollectorForTableCells( aCollector );
2555 sTool->FilterCollectorForLockedItems( aCollector );
2556 } );
2557
2558 if( m_selectionTool->ReportFilteredLockedItems() )
2559 return 0;
2560
2561 selection = filtered;
2562 }
2563
2564 // Did we filter everything out? If so, don't try to operate further
2565 if( selection.Empty() )
2566 return 0;
2567
2568 // Some PCB_SHAPE must be rotated around their center instead of their start point in
2569 // order to stay to the same place (at least RECT and POLY)
2570 // Note a RECT shape rotated by a not cardinal angle is a POLY shape
2571 bool usePcbShapeCenter = false;
2572
2573 if( selection.Size() == 1 && !m_dragging && dynamic_cast<PCB_SHAPE*>( selection.Front() ) )
2574 {
2575 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( selection.Front() );
2576
2577 if( shape->GetShape() == SHAPE_T::RECTANGLE || shape->GetShape() == SHAPE_T::POLY )
2578 usePcbShapeCenter = true;
2579 }
2580
2581 if( selection.Size() == 1 && !m_dragging && dynamic_cast<PCB_TABLE*>( selection.Front() ) )
2582 usePcbShapeCenter = true;
2583
2584 if( selection.Size() == 1 && !m_dragging && dynamic_cast<PCB_TEXTBOX*>( selection.Front() ) )
2585 {
2586 selection.SetReferencePoint( static_cast<PCB_TEXTBOX*>( selection.Front() )->GetCenter() );
2587 }
2588 else if( usePcbShapeCenter )
2589 {
2590 selection.SetReferencePoint( static_cast<PCB_SHAPE*>( selection.Front() )->GetCenter() );
2591 }
2592 else
2593 {
2595 }
2596
2597 VECTOR2I refPt = selection.GetReferencePoint();
2598 EDA_ANGLE rotateAngle = TOOL_EVT_UTILS::GetEventRotationAngle( *editFrame, aEvent );
2599
2600 if( frame()->GetCanvas()->GetView()->GetGAL()->IsFlippedX() )
2601 rotateAngle = -rotateAngle;
2602
2603 // Calculate view bounding box
2604 BOX2I viewBBox = selection.Front()->ViewBBox();
2605
2606 for( EDA_ITEM* item : selection )
2607 viewBBox.Merge( item->ViewBBox() );
2608
2609 // Check if the view bounding box will go out of bounds
2610 VECTOR2D rotPos = viewBBox.GetPosition();
2611 VECTOR2D rotEnd = viewBBox.GetEnd();
2612
2613 RotatePoint( &rotPos.x, &rotPos.y, refPt.x, refPt.y, rotateAngle );
2614 RotatePoint( &rotEnd.x, &rotEnd.y, refPt.x, refPt.y, rotateAngle );
2615
2616 typedef std::numeric_limits<int> coord_limits;
2617
2618 int max = coord_limits::max() - COORDS_PADDING;
2619 int min = -max;
2620
2621 bool outOfBounds = rotPos.x < min || rotPos.x > max || rotPos.y < min || rotPos.y > max || rotEnd.x < min
2622 || rotEnd.x > max || rotEnd.y < min || rotEnd.y > max;
2623
2624 if( !outOfBounds )
2625 {
2626 for( EDA_ITEM* item : selection )
2627 {
2628 commit->Modify( item, nullptr, RECURSE_MODE::RECURSE );
2629
2630 if( item->IsBOARD_ITEM() )
2631 {
2632 BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
2633
2634 board_item->Rotate( refPt, rotateAngle );
2635 board_item->Normalize();
2636
2637 if( board_item->Type() == PCB_FOOTPRINT_T )
2638 static_cast<FOOTPRINT*>( board_item )->InvalidateComponentClassCache();
2639 }
2640 }
2641
2642 // Don't push a separate undo entry when we're in the middle of a move operation.
2643 // The parent move will handle the commit.
2644 if( !localCommit.Empty() && !m_dragging )
2645 {
2646 localCommit.Push( _( "Rotate" ) );
2648 }
2649
2650 if( is_hover && !m_dragging )
2651 m_toolMgr->RunAction( ACTIONS::selectionClear );
2652
2654
2655 if( m_dragging )
2656 {
2659 }
2660 }
2661
2662 // Restore the old reference so any mouse dragging that occurs doesn't make the selection jump
2663 // to this now invalid reference
2664 if( oldRefPt )
2665 selection.SetReferencePoint( *oldRefPt );
2666 else
2667 selection.ClearReferencePoint();
2668
2669 return 0;
2670}
2671
2672
2676static void mirrorPad( PAD& aPad, const VECTOR2I& aMirrorPoint, FLIP_DIRECTION aFlipDirection )
2677{
2679 [&]( PCB_LAYER_ID layer )
2680 {
2681 if( aPad.GetShape( layer ) == PAD_SHAPE::CUSTOM )
2682 aPad.FlipPrimitives( aFlipDirection );
2683 } );
2684
2685 VECTOR2I tmpPt = aPad.GetPosition();
2686 MIRROR( tmpPt, aMirrorPoint, aFlipDirection );
2687 aPad.SetPosition( tmpPt );
2688
2690 [&]( PCB_LAYER_ID layer )
2691 {
2692 tmpPt = aPad.GetOffset( layer );
2693 MIRROR( tmpPt, VECTOR2I{ 0, 0 }, aFlipDirection );
2694 aPad.SetOffset( layer, tmpPt );
2695
2696 VECTOR2I tmpz = aPad.GetDelta( layer );
2697 MIRROR( tmpz, VECTOR2I{ 0, 0 }, aFlipDirection );
2698 aPad.SetDelta( layer, tmpz );
2699 } );
2700
2701 aPad.SetOrientation( -aPad.GetOrientation() );
2702}
2703
2704
2705const std::vector<KICAD_T> EDIT_TOOL::MirrorableItems = {
2708 PCB_TEXT_T,
2710 PCB_ZONE_T,
2711 PCB_PAD_T,
2713 PCB_ARC_T,
2714 PCB_VIA_T,
2721};
2722
2723
2724int EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent )
2725{
2726 if( isRouterActive() )
2727 {
2728 wxBell();
2729 return 0;
2730 }
2731
2732 BOARD_COMMIT localCommit( this );
2733 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
2734
2735 if( !commit )
2736 commit = &localCommit;
2737
2738 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2739 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2740 {
2741 sTool->FilterCollectorForMarkers( aCollector );
2742 sTool->FilterCollectorForHierarchy( aCollector, true );
2743 sTool->FilterCollectorForFreePads( aCollector );
2744 sTool->FilterCollectorForLockedItems( aCollector );
2745 } );
2746
2747 m_selectionTool->ReportFilteredLockedItems();
2748
2749 if( selection.Empty() )
2750 return 0;
2751
2753 VECTOR2I mirrorPoint = selection.GetReferencePoint();
2754
2757
2758 int skippedFootprints = 0;
2759 int skippedGroups = 0;
2760
2761 for( EDA_ITEM* item : selection )
2762 {
2763 if( !item->IsType( MirrorableItems ) )
2764 {
2765 if( item->Type() == PCB_FOOTPRINT_T )
2766 skippedFootprints++;
2767
2768 continue;
2769 }
2770
2771 // Skip groups that hold non-mirrorable items, else the rest would tear away from them.
2772 if( item->Type() == PCB_GROUP_T && !groupMirrorable( static_cast<PCB_GROUP*>( item ) ) )
2773 {
2774 skippedGroups++;
2775 continue;
2776 }
2777
2778 commit->Modify( item, nullptr, RECURSE_MODE::RECURSE );
2779
2780 // modify each object as necessary
2781 switch( item->Type() )
2782 {
2783 case PCB_SHAPE_T:
2784 static_cast<PCB_SHAPE*>( item )->Mirror( mirrorPoint, flipDirection );
2785 break;
2786
2787 case PCB_ZONE_T:
2788 static_cast<ZONE*>( item )->Mirror( mirrorPoint, flipDirection );
2789 break;
2790
2791 case PCB_FIELD_T:
2792 case PCB_TEXT_T:
2793 static_cast<PCB_TEXT*>( item )->Mirror( mirrorPoint, flipDirection );
2794 break;
2795
2796 case PCB_TEXTBOX_T:
2797 static_cast<PCB_TEXTBOX*>( item )->Mirror( mirrorPoint, flipDirection );
2798 break;
2799
2800 case PCB_TABLE_T:
2801 case PCB_DRILL_CHART_T:
2802 static_cast<PCB_TABLE*>( item )->Mirror( mirrorPoint, flipDirection );
2803 break;
2804
2805 case PCB_PAD_T:
2806 mirrorPad( *static_cast<PAD*>( item ), mirrorPoint, flipDirection );
2807 break;
2808
2809 case PCB_TRACE_T:
2810 case PCB_ARC_T:
2811 case PCB_VIA_T:
2812 static_cast<PCB_TRACK*>( item )->Mirror( mirrorPoint, flipDirection );
2813 break;
2814
2815 case PCB_GROUP_T:
2816 static_cast<PCB_GROUP*>( item )->Mirror( mirrorPoint, flipDirection );
2817 break;
2818
2819 case PCB_GENERATOR_T:
2820 static_cast<PCB_GENERATOR*>( item )->Mirror( mirrorPoint, flipDirection );
2821 break;
2822
2823 case PCB_POINT_T:
2824
2825 static_cast<PCB_POINT*>( item )->Mirror( mirrorPoint, flipDirection ); break;
2826
2828 static_cast<PCB_REFERENCE_IMAGE*>( item )->Mirror( mirrorPoint, flipDirection );
2829 break;
2830
2831 default:
2832 // it's likely the commit object is wrong if you get here
2833 UNIMPLEMENTED_FOR( item->GetClass() );
2834 }
2835 }
2836
2837 // Don't push a separate undo entry when we're in the middle of a move operation.
2838 // The parent move will handle the commit.
2839 if( !localCommit.Empty() && !m_dragging )
2840 {
2841 localCommit.Push( _( "Mirror" ) );
2843 }
2844
2845 if( skippedFootprints > 0 && !m_dragging )
2846 {
2847 frame()->ShowInfoBarMsg( _( "Footprints cannot be mirrored. Use Flip to move them to "
2848 "the other side of the board." ) );
2849 }
2850 else if( skippedGroups > 0 && !m_dragging )
2851 {
2852 frame()->ShowInfoBarMsg( _( "Groups containing footprints or other items that cannot be "
2853 "mirrored were skipped." ) );
2854 }
2855
2856 if( selection.IsHover() && !m_dragging )
2857 m_toolMgr->RunAction( ACTIONS::selectionClear );
2858
2860
2861 if( m_dragging )
2862 {
2865 }
2866
2867 return 0;
2868}
2869
2870
2872{
2873 if( isRouterActive() )
2874 {
2875 wxBell();
2876 return 0;
2877 }
2878
2879 BOARD_COMMIT localCommit( this );
2880 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
2881
2882 if( !commit )
2883 commit = &localCommit;
2884
2885 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2886 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2887 {
2888 sTool->FilterCollectorForHierarchy( aCollector, true );
2889 sTool->FilterCollectorForLockedItems( aCollector );
2890 } );
2891
2892 m_selectionTool->ReportFilteredLockedItems();
2893
2894 if( selection.Empty() )
2895 return 0;
2896
2897 auto setJustify =
2898 [&]( EDA_TEXT* aTextItem )
2899 {
2900 if( aEvent.Matches( ACTIONS::leftJustify.MakeEvent() ) )
2901 aTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
2902 else if( aEvent.Matches( ACTIONS::centerJustify.MakeEvent() ) )
2903 aTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
2904 else
2905 aTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
2906 };
2907
2908 for( EDA_ITEM* item : selection )
2909 {
2910 if( item->Type() == PCB_FIELD_T || item->Type() == PCB_TEXT_T )
2911 {
2912 commit->Modify( item );
2913 setJustify( static_cast<PCB_TEXT*>( item ) );
2914 }
2915 else if( item->Type() == PCB_TEXTBOX_T )
2916 {
2917 commit->Modify( item );
2918 setJustify( static_cast<PCB_TEXTBOX*>( item ) );
2919 }
2920 }
2921
2922 if( !localCommit.Empty() )
2923 {
2924 if( aEvent.Matches( ACTIONS::leftJustify.MakeEvent() ) )
2925 localCommit.Push( _( "Left Justify" ) );
2926 else if( aEvent.Matches( ACTIONS::centerJustify.MakeEvent() ) )
2927 localCommit.Push( _( "Center Justify" ) );
2928 else
2929 localCommit.Push( _( "Right Justify" ) );
2930 }
2931
2932 if( selection.IsHover() && !m_dragging )
2933 m_toolMgr->RunAction( ACTIONS::selectionClear );
2934
2936
2937 if( m_dragging )
2938 {
2941 }
2942
2943 return 0;
2944}
2945
2946
2947int EDIT_TOOL::Flip( const TOOL_EVENT& aEvent )
2948{
2949 if( isRouterActive() )
2950 {
2951 wxBell();
2952 return 0;
2953 }
2954
2955 BOARD_COMMIT localCommit( this );
2956 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
2957
2958 if( !commit )
2959 commit = &localCommit;
2960
2961 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2962 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2963 {
2964 sTool->FilterCollectorForMarkers( aCollector );
2965 sTool->FilterCollectorForHierarchy( aCollector, true );
2966 sTool->FilterCollectorForFreePads( aCollector );
2967 sTool->FilterCollectorForTableCells( aCollector );
2968 sTool->FilterCollectorForLockedItems( aCollector );
2969 } );
2970
2971 m_selectionTool->ReportFilteredLockedItems();
2972
2973 if( selection.Empty() )
2974 return 0;
2975
2976 std::optional<VECTOR2I> oldRefPt;
2977
2978 if( selection.HasReferencePoint() )
2979 oldRefPt = selection.GetReferencePoint();
2980
2982
2983 // Flip around the anchor for footprints, and the bounding box center for board items
2984 VECTOR2I refPt = IsFootprintEditor() ? VECTOR2I( 0, 0 ) : selection.GetCenter();
2985
2986 if( m_dragging && m_inMoveWithReference && oldRefPt )
2987 {
2988 refPt = *oldRefPt;
2989 }
2990 else if( selection.GetSize() == 1 )
2991 {
2992 // If only one item selected, flip around the selection or item anchor point (instead
2993 // of the bounding box center) to avoid moving the item anchor
2994 // but only if the item is not a PCB_SHAPE with SHAPE_T::RECTANGLE shape, because
2995 // for this shape the flip transform swap start and end coordinates and move the shape.
2996 // So using the center of the shape is better (the shape does not move)
2997 // (Tables are a bunch of rectangles, so exclude them too)
2998 PCB_SHAPE* rect = dynamic_cast<PCB_SHAPE*>( selection.GetItem( 0 ) );
2999 PCB_TABLE* table = dynamic_cast<PCB_TABLE*>( selection.GetItem( 0 ) );
3000
3001 if( !table && ( !rect || rect->GetShape() != SHAPE_T::RECTANGLE ) )
3002 refPt = selection.GetReferencePoint();
3003 }
3004
3005 const FLIP_DIRECTION flipDirection = frame()->GetPcbNewSettings()->m_FlipDirection;
3006
3007 for( EDA_ITEM* item : selection )
3008 {
3009 if( !item->IsBOARD_ITEM() )
3010 continue;
3011
3012 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
3013
3014 commit->Modify( boardItem, nullptr, RECURSE_MODE::RECURSE );
3015
3016 boardItem->Flip( refPt, flipDirection );
3017 boardItem->Normalize();
3018
3019 if( boardItem->Type() == PCB_FOOTPRINT_T )
3020 static_cast<FOOTPRINT*>( boardItem )->InvalidateComponentClassCache();
3021 }
3022
3023 // Don't push a separate undo entry when we're in the middle of a move operation.
3024 // The parent move will handle the commit.
3025 if( !localCommit.Empty() && !m_dragging )
3026 {
3027 localCommit.Push( _( "Change Side / Flip" ) );
3029 }
3030
3031 if( selection.IsHover() && !m_dragging )
3032 m_toolMgr->RunAction( ACTIONS::selectionClear );
3033
3035
3036 if( m_dragging )
3037 {
3040 }
3041
3042 // Restore the old reference so any mouse dragging that occurs doesn't make the selection jump
3043 // to this now invalid reference
3044 if( oldRefPt )
3045 selection.SetReferencePoint( *oldRefPt );
3046 else
3047 selection.ClearReferencePoint();
3048
3049 return 0;
3050}
3051
3052
3053void EDIT_TOOL::DeleteItems( const PCB_SELECTION& aItems, bool aIsCut )
3054{
3056 BOARD_COMMIT commit( this );
3057 int commitFlags = 0;
3058
3059 // As we are about to remove items, they have to be removed from the selection first
3060 m_toolMgr->RunAction( ACTIONS::selectionClear );
3061
3062 int itemsDeleted = 0;
3063 int fieldsHidden = 0;
3064 int fieldsAlreadyHidden = 0;
3065
3066 for( EDA_ITEM* item : aItems )
3067 {
3068 if( !item->IsBOARD_ITEM() )
3069 continue;
3070
3071 BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
3072 FOOTPRINT* parentFP = board_item->GetParentFootprint();
3073
3074 switch( item->Type() )
3075 {
3076 case PCB_FIELD_T:
3077 {
3078 PCB_FIELD* field = static_cast<PCB_FIELD*>( board_item );
3079
3080 wxASSERT( parentFP );
3081 commit.Modify( parentFP );
3082
3083 if( field->IsVisible() )
3084 {
3085 field->SetVisible( false );
3086 fieldsHidden++;
3087 }
3088 else
3089 {
3090 fieldsAlreadyHidden++;
3091 }
3092
3093 getView()->Update( parentFP );
3094 break;
3095 }
3096
3097 case PCB_TEXT_T:
3098 case PCB_SHAPE_T:
3099 case PCB_TEXTBOX_T:
3100 case PCB_BARCODE_T:
3101 case PCB_TABLE_T:
3102 case PCB_DRILL_CHART_T:
3104 case PCB_DIMENSION_T:
3105 case PCB_DIM_ALIGNED_T:
3106 case PCB_DIM_LEADER_T:
3107 case PCB_DIM_CENTER_T:
3108 case PCB_DIM_RADIAL_T:
3110 case PCB_POINT_T:
3111 commit.Remove( board_item );
3112 itemsDeleted++;
3113 break;
3114
3115 case PCB_TABLECELL_T:
3116 // A generated table's cells report the board, so there is no user text to clear
3117 if( IsGeneratedTableCell( board_item ) )
3118 break;
3119
3120 // Clear contents of table cell
3121 commit.Modify( board_item );
3122 static_cast<PCB_TABLECELL*>( board_item )->SetText( wxEmptyString );
3123 itemsDeleted++;
3124 break;
3125
3126 case PCB_GROUP_T:
3127 board_item->RunOnChildren(
3128 [&commit]( BOARD_ITEM* aItem )
3129 {
3130 commit.Remove( aItem );
3131 },
3133
3134 commit.Remove( board_item );
3135 itemsDeleted++;
3136 break;
3137
3138 case PCB_PAD_T:
3139 if( IsFootprintEditor() || frame()->GetPcbNewSettings()->m_AllowFreePads )
3140 {
3141 commit.Remove( board_item );
3142 itemsDeleted++;
3143 }
3144
3145 break;
3146
3147 case PCB_ZONE_T:
3148 // We process the zones special so that cutouts can be deleted when the delete
3149 // tool is called from inside a cutout when the zone is selected.
3150 // Only interact with cutouts when deleting and a single item is selected
3151 if( !aIsCut && aItems.GetSize() == 1 )
3152 {
3153 VECTOR2I curPos = getViewControls()->GetCursorPosition();
3154 ZONE* zone = static_cast<ZONE*>( board_item );
3155
3156 int outlineIdx, holeIdx;
3157
3158 if( zone->HitTestCutout( curPos, &outlineIdx, &holeIdx ) )
3159 {
3160 // Remove the cutout
3161 commit.Modify( zone );
3162 zone->RemoveCutout( outlineIdx, holeIdx );
3163 zone->UnFill();
3164
3165 // Update the display
3166 zone->HatchBorder();
3167 canvas()->Refresh();
3168
3169 // Restore the selection on the original zone
3170 m_toolMgr->RunAction<EDA_ITEM*>( ACTIONS::selectItem, zone );
3171
3172 break;
3173 }
3174 }
3175
3176 // Remove the entire zone otherwise
3177 commit.Remove( board_item );
3178 itemsDeleted++;
3179 break;
3180
3181 case PCB_VIA_T:
3182 {
3183 if( !aIsCut )
3184 {
3185 EDA_GROUP* parent = board_item->GetParentGroup();
3186 PCB_VIA_STITCH* stitch =
3187 parent ? dynamic_cast<PCB_VIA_STITCH*>( parent->AsEdaItem() ) : nullptr;
3188
3189 if( stitch && !aItems.Contains( stitch ) )
3190 {
3191 // We need to mark the via as excluded on deletion
3192 commit.Modify( stitch );
3193 stitch->ExcludePosition( board_item->GetPosition() );
3194 }
3195 }
3196
3197 commit.Remove( board_item );
3198 itemsDeleted++;
3199 break;
3200 }
3201
3202 case PCB_GENERATOR_T:
3203 {
3204 PCB_GENERATOR* generator = static_cast<PCB_GENERATOR*>( board_item );
3205
3206 if( ( SELECTION_CONDITIONS::OnlyTypes( { PCB_GENERATOR_T } ) )( aItems ) )
3207 {
3208 m_toolMgr->RunSynchronousAction<PCB_GENERATOR*>( PCB_ACTIONS::genRemove, &commit, generator );
3209 commit.Push( _( "Delete" ), commitFlags );
3210 commitFlags |= APPEND_UNDO;
3211 }
3212 else
3213 {
3214 for( EDA_ITEM* member : generator->GetItems() )
3215 commit.Remove( member );
3216
3217 commit.Remove( board_item );
3218 }
3219
3220 itemsDeleted++;
3221 break;
3222 }
3223
3224 default:
3225 commit.Remove( board_item );
3226 itemsDeleted++;
3227 break;
3228 }
3229 }
3230
3231 // If the entered group has been emptied then leave it.
3232 PCB_GROUP* enteredGroup = m_selectionTool->GetEnteredGroup();
3233
3234 if( enteredGroup && enteredGroup->GetItems().empty() )
3235 m_selectionTool->ExitGroup();
3236
3237 if( aIsCut )
3238 {
3239 commit.Push( _( "Cut" ), commitFlags );
3240 }
3241 else if( itemsDeleted == 0 )
3242 {
3243 if( fieldsHidden == 1 )
3244 commit.Push( _( "Hide Field" ), commitFlags );
3245 else if( fieldsHidden > 1 )
3246 commit.Push( _( "Hide Fields" ), commitFlags );
3247 else if( fieldsAlreadyHidden > 0 )
3248 editFrame->ShowInfoBarError( _( "Use the Footprint Properties dialog to remove fields." ) );
3249 }
3250 else
3251 {
3252 commit.Push( _( "Delete" ), commitFlags );
3253 }
3254}
3255
3256
3257int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent )
3258{
3260
3261 // A geometric constraint selected by clicking its on-canvas badge has no board selection, so a
3262 // plain Delete targets it here before the normal item-removal path. Cut is excluded: the
3263 // constraint is not on the clipboard, so it must not be silently removed (#2329).
3265
3266 if( !isCut )
3267 {
3268 if( CONSTRAINT_EDIT_TOOL* constraintTool = m_toolMgr->GetTool<CONSTRAINT_EDIT_TOOL>();
3269 constraintTool && constraintTool->TryDeleteSelectedConstraint() )
3270 {
3271 return 0;
3272 }
3273 }
3274
3275 SCOPED_TOOL_PUSHER raii( editFrame, aEvent );
3276
3277 std::vector<BOARD_ITEM*> lockedItems;
3278 Activate();
3279
3280 // get a copy instead of reference (as we're going to clear the selection before removing items)
3281 PCB_SELECTION selectionCopy;
3283
3284 // If we are in a "Cut" operation, then the copied selection exists already and we want to
3285 // delete exactly that; no more, no fewer. Any filtering for locked items must be done in
3286 // the copyToClipboard() routine.
3287 if( isCut )
3288 {
3289 selectionCopy = m_selectionTool->GetSelection();
3290 }
3291 else
3292 {
3293 // Hover-pick is only a fallback for an empty selection.
3294 const bool hadInitialSelection = !m_selectionTool->GetSelection().Empty();
3295
3296 // When not in free-pad mode we normally auto-promote selected pads to their parent
3297 // footprints. But this is probably a little too dangerous for a destructive operation,
3298 // so we just do the promotion but not the deletion (allowing for a second delete to do
3299 // it if that's what the user wanted).
3300 selectionCopy = m_selectionTool->RequestSelection(
3301 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
3302 {
3303 sTool->FilterCollectorForHierarchy( aCollector, true );
3304 sTool->FilterCollectorForLockedItems( aCollector );
3305 } );
3306
3307 m_selectionTool->ReportFilteredLockedItems();
3308
3309 if( hadInitialSelection && selectionCopy.Empty() )
3310 return 0;
3311
3312 size_t beforeFPCount = selectionCopy.CountType( PCB_FOOTPRINT_T );
3313
3314 selectionCopy = m_selectionTool->RequestSelection(
3315 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
3316 {
3317 sTool->FilterCollectorForHierarchy( aCollector, true );
3318 sTool->FilterCollectorForFreePads( aCollector );
3319 sTool->FilterCollectorForLockedItems( aCollector );
3320 } );
3321
3322 if( !selectionCopy.IsHover() && m_selectionTool->GetSelection().CountType( PCB_FOOTPRINT_T ) > beforeFPCount )
3323 {
3324 wxBell();
3325 canvas()->Refresh();
3326 return 0;
3327 }
3328
3329 // In "alternative" mode, we expand selected track items to their full connection.
3330 if( isAlt && ( selectionCopy.HasType( PCB_TRACE_T ) || selectionCopy.HasType( PCB_VIA_T ) ) )
3332
3333 selectionCopy = m_selectionTool->GetSelection();
3334
3335 if( selectionCopy.Empty() )
3336 return 0;
3337 }
3338
3339 DeleteItems( selectionCopy, isCut );
3340 canvas()->Refresh();
3341 return 0;
3342}
3343
3344
3346{
3347 if( isRouterActive() )
3348 {
3349 wxBell();
3350 return 0;
3351 }
3352
3353 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
3354 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
3355 {
3356 sTool->FilterCollectorForMarkers( aCollector );
3357 sTool->FilterCollectorForHierarchy( aCollector, true );
3358 sTool->FilterCollectorForFreePads( aCollector, false );
3359 sTool->FilterCollectorForTableCells( aCollector );
3360 sTool->FilterCollectorForLockedItems( aCollector );
3361 } );
3362
3363 m_selectionTool->ReportFilteredLockedItems();
3364
3365 if( selection.Empty() )
3366 return 0;
3367
3368 VECTOR2I translation;
3369 EDA_ANGLE rotation;
3371
3372 // TODO: Implement a visible bounding border at the edge
3373 BOX2I sel_box = selection.GetBoundingBox();
3374
3375 DIALOG_MOVE_EXACT dialog( frame(), translation, rotation, rotationAnchor, sel_box );
3376 int ret = dialog.ShowModal();
3377
3378 if( ret == wxID_OK )
3379 {
3380 BOARD_COMMIT commit( this );
3381 EDA_ANGLE angle = rotation;
3382 VECTOR2I rp = selection.GetCenter();
3383 VECTOR2I selCenter( rp.x, rp.y );
3384
3385 // Make sure the rotation is from the right reference point
3386 selCenter += translation;
3387
3388 if( !frame()->GetPcbNewSettings()->m_Display.m_DisplayInvertYAxis )
3389 rotation = -rotation;
3390
3391 for( EDA_ITEM* item : selection )
3392 {
3393 if( !item->IsBOARD_ITEM() )
3394 continue;
3395
3396 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
3397
3398 commit.Modify( boardItem, nullptr, RECURSE_MODE::RECURSE );
3399
3400 if( !boardItem->GetParent() || !boardItem->GetParent()->IsSelected() )
3401 boardItem->Move( translation );
3402
3403 switch( rotationAnchor )
3404 {
3406 boardItem->Rotate( boardItem->GetPosition(), angle );
3407 break;
3409 boardItem->Rotate( selCenter, angle );
3410 break;
3412 boardItem->Rotate( frame()->GetScreen()->m_LocalOrigin, angle );
3413 break;
3415 boardItem->Rotate( board()->GetDesignSettings().GetAuxOrigin(), angle );
3416 break;
3417 }
3418
3419 if( !m_dragging )
3420 getView()->Update( boardItem );
3421 }
3422
3423 commit.Push( _( "Move Exactly" ) );
3425
3426 if( selection.IsHover() )
3427 m_toolMgr->RunAction( ACTIONS::selectionClear );
3428
3430
3431 if( m_dragging )
3432 {
3435 }
3436 }
3437
3438 return 0;
3439}
3440
3441
3443{
3444 // Some generators cannot be duplicated on their own, and neither can several of them
3445 return std::any_of( aSelection.begin(), aSelection.end(),
3446 []( EDA_ITEM* aItem )
3447 {
3448 PCB_GENERATOR* generator = dynamic_cast<PCB_GENERATOR*>( aItem );
3449 return !generator || generator->CanDuplicate();
3450 } );
3451}
3452
3453
3458static void recordDuplicatedItems( TOOL_MANAGER* aToolMgr, const std::vector<BOARD_ITEM*>& aItems )
3459{
3460 PAD_TOOL* padTool = aToolMgr->GetTool<PAD_TOOL>();
3461
3462 if( !padTool )
3463 return;
3464
3465 for( BOARD_ITEM* item : aItems )
3466 {
3467 if( PAD* pad = dynamic_cast<PAD*>( item ) )
3468 padTool->RecordMasterPad( *pad );
3469 }
3470}
3471
3472
3474{
3475 if( isRouterActive() )
3476 {
3477 wxBell();
3478 return 0;
3479 }
3480
3481 bool increment = aEvent.IsAction( &PCB_ACTIONS::duplicateIncrement );
3482
3483 // Be sure that there is at least one item that we can modify
3484 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
3485 []( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
3486 {
3487 sTool->FilterCollectorForMarkers( aCollector );
3488 sTool->FilterCollectorForHierarchy( aCollector, true );
3489 sTool->FilterCollectorForFreePads( aCollector, true );
3490 sTool->FilterCollectorForTableCells( aCollector );
3491 } );
3492
3493 if( selection.Empty() )
3494 return 0;
3495
3497 return 0;
3498
3499 // we have a selection to work on now, so start the tool process
3501 BOARD_COMMIT commit( this );
3502 FOOTPRINT* parentFootprint = nullptr;
3503
3505 parentFootprint = editFrame->GetBoard()->GetFirstFootprint();
3506
3507 // If the selection was given a hover, we do not keep the selection after completion
3508 bool is_hover = selection.IsHover();
3509
3510 std::vector<BOARD_ITEM*> new_items;
3511 new_items.reserve( selection.Size() );
3512
3513 // Maps each duplicated original KIID to its duplicate so constraints between them
3514 // can be repointed at the copies once duplication finishes
3515 std::map<KIID, KIID> idMap;
3516
3517 // Each selected item is duplicated and pushed to new_items list
3518 // Old selection is cleared, and new items are then selected.
3519 for( EDA_ITEM* item : selection )
3520 {
3521 if( !item->IsBOARD_ITEM() )
3522 continue;
3523
3524 BOARD_ITEM* dupe_item = nullptr;
3525 BOARD_ITEM* orig_item = static_cast<BOARD_ITEM*>( item );
3526
3527 if( !m_isFootprintEditor && orig_item->GetParentFootprint() )
3528 {
3529 // No sub-footprint modifications allowed outside of footprint editor
3530 }
3531 else
3532 {
3533 switch( orig_item->Type() )
3534 {
3535 case PCB_FOOTPRINT_T:
3536 case PCB_TEXT_T:
3537 case PCB_TEXTBOX_T:
3538 case PCB_TABLE_T:
3539 case PCB_BARCODE_T:
3541 case PCB_SHAPE_T:
3542 case PCB_TRACE_T:
3543 case PCB_ARC_T:
3544 case PCB_VIA_T:
3545 case PCB_ZONE_T:
3546 case PCB_TARGET_T:
3547 case PCB_POINT_T:
3548 case PCB_DIM_ALIGNED_T:
3549 case PCB_DIM_CENTER_T:
3550 case PCB_DIM_RADIAL_T:
3552 case PCB_DIM_LEADER_T:
3553 case PCB_GRID_ITEM_T:
3555 dupe_item = parentFootprint->DuplicateItem( true, &commit, orig_item );
3556 else
3557 dupe_item = orig_item->Duplicate( true, &commit );
3558
3559 // Clear the selection flag here, otherwise the PCB_SELECTION_TOOL
3560 // will not properly select it later on
3561 dupe_item->ClearSelected();
3562
3563 if( dupe_item->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( dupe_item )->IsHatchedFill() )
3564 dupe_item->SetFlags( IS_NEW );
3565
3566 idMap[orig_item->m_Uuid] = dupe_item->m_Uuid;
3567 new_items.push_back( dupe_item );
3568 commit.Add( dupe_item );
3569 break;
3570
3571 case PCB_FIELD_T:
3572 // PCB_FIELD items are specific items (not only graphic, but are properies)
3573 // and cannot be duplicated like other footprint items. So skip it:
3574 orig_item->ClearSelected();
3575 break;
3576
3577 case PCB_PAD_T:
3578 dupe_item = parentFootprint->DuplicateItem( true, &commit, orig_item );
3579
3580 if( increment && static_cast<PAD*>( dupe_item )->CanHaveNumber() )
3581 {
3582 PAD_TOOL* padTool = m_toolMgr->GetTool<PAD_TOOL>();
3583 wxString padNumber = padTool->GetLastPadNumber();
3584 padNumber = parentFootprint->GetNextPadNumber( padNumber );
3585 padTool->SetLastPadNumber( padNumber );
3586 static_cast<PAD*>( dupe_item )->SetNumber( padNumber );
3587 }
3588
3589 // Clear the selection flag here, otherwise the PCB_SELECTION_TOOL
3590 // will not properly select it later on
3591 dupe_item->ClearSelected();
3592
3593 idMap[orig_item->m_Uuid] = dupe_item->m_Uuid;
3594 new_items.push_back( dupe_item );
3595 commit.Add( dupe_item );
3596 break;
3597
3598 case PCB_DRILL_CHART_T:
3599 // A second chart of the same holes says nothing the first does not, and it
3600 // would land on top of it
3601 break;
3602
3603 case PCB_GENERATOR_T:
3604 case PCB_GROUP_T:
3605 {
3606 // DeepDuplicate maps original to duplicate KIID per descendant while cloning the group
3607 // so grouped constraints can be repointed unordered iteration blocks a later pairing
3608 dupe_item = static_cast<PCB_GROUP*>( orig_item )->DeepDuplicate( true, &commit, &idMap );
3609
3610 dupe_item->RunOnChildren(
3611 [&]( BOARD_ITEM* aItem )
3612 {
3613 aItem->ClearSelected();
3614 new_items.push_back( aItem );
3615 commit.Add( aItem );
3616 },
3618
3619 dupe_item->ClearSelected();
3620 new_items.push_back( dupe_item );
3621 commit.Add( dupe_item );
3622 break;
3623 }
3624
3625 default:
3626 UNIMPLEMENTED_FOR( orig_item->GetClass() );
3627 break;
3628 }
3629 }
3630 }
3631
3632 // Carry constraints whose members were all duplicated repointed at the copies
3633 // constraints are not selectable so add them to commit but keep them out of new selection
3634 const CONSTRAINTS& sourceConstraints = parentFootprint ? parentFootprint->Constraints()
3635 : board()->Constraints();
3636
3637 for( PCB_CONSTRAINT* clone : CloneFullySelectedConstraints( sourceConstraints, idMap ) )
3638 commit.Add( clone );
3639
3640 // Clear the old selection first
3641 m_toolMgr->RunAction( ACTIONS::selectionClear );
3642
3643 // Select the new items
3644 EDA_ITEMS nItems( new_items.begin(), new_items.end() );
3645 m_toolMgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &nItems );
3646
3647 // record the new items as added
3648 if( !selection.Empty() )
3649 {
3650 editFrame->DisplayToolMsg( wxString::Format( _( "Duplicated %d item(s)" ), (int) new_items.size() ) );
3651
3652 // If items were duplicated, pick them up
3653 if( doMoveSelection( aEvent, &commit, true ) )
3654 {
3655 commit.Push( _( "Duplicate" ) );
3656
3657 recordDuplicatedItems( m_toolMgr, new_items );
3658 }
3659 else
3660 {
3661 commit.Revert();
3662 }
3663
3664 // Deselect the duplicated item if we originally started as a hover selection
3665 if( is_hover )
3666 m_toolMgr->RunAction( ACTIONS::selectionClear );
3667 }
3668
3669 return 0;
3670}
3671
3672
3674{
3675 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
3676 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
3677 {
3678 for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
3679 {
3680 switch( aCollector[i]->Type() )
3681 {
3682 case PCB_PAD_T:
3683 case PCB_TEXT_T: break;
3684 default: aCollector.Remove( i ); break;
3685 }
3686 }
3687
3688 sTool->FilterCollectorForLockedItems( aCollector );
3689 } );
3690
3691 m_selectionTool->ReportFilteredLockedItems();
3692
3693 if( selection.Empty() )
3694 return 0;
3695
3696 ACTIONS::INCREMENT param = { 1, 0 };
3697
3698 if( aEvent.HasParameter() )
3699 param = aEvent.Parameter<ACTIONS::INCREMENT>();
3700
3702 incrementer.SetSkipIOSQXZ( true );
3703
3704 // If we're coming via another action like 'Move', use that commit
3705 BOARD_COMMIT localCommit( m_toolMgr );
3706 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
3707
3708 if( !commit )
3709 commit = &localCommit;
3710
3711 for( EDA_ITEM* item : selection )
3712 {
3713 switch( item->Type() )
3714 {
3715 case PCB_PAD_T:
3716 {
3717 // Only increment pad numbers in the footprint editor
3718 if( !m_isFootprintEditor )
3719 break;
3720
3721 PAD& pad = static_cast<PAD&>( *item );
3722
3723 if( !pad.CanHaveNumber() )
3724 continue;
3725
3726 // Increment on the pad numbers
3727 std::optional<wxString> newNumber = incrementer.Increment( pad.GetNumber(), param.Delta, param.Index );
3728
3729 if( newNumber )
3730 {
3731 commit->Modify( &pad );
3732 pad.SetNumber( *newNumber );
3733 }
3734
3735 break;
3736 }
3737 case PCB_TEXT_T:
3738 {
3739 PCB_TEXT& text = static_cast<PCB_TEXT&>( *item );
3740
3741 std::optional<wxString> newText = incrementer.Increment( text.GetText(), param.Delta, param.Index );
3742
3743 if( newText )
3744 {
3745 commit->Modify( &text );
3746 text.SetText( *newText );
3747 }
3748
3749 break;
3750 }
3751 default: break;
3752 }
3753 }
3754
3755 if( selection.Front()->IsMoving() )
3756 m_toolMgr->PostAction( ACTIONS::refreshPreview );
3757
3758 commit->Push( _( "Increment" ) );
3759
3760 return 0;
3761}
3762
3763
3765{
3766 for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
3767 {
3768 if( aCollector[i]->Type() != PCB_PAD_T )
3769 aCollector.Remove( i );
3770 }
3771}
3772
3773
3775{
3776 for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
3777 {
3778 if( aCollector[i]->Type() != PCB_FOOTPRINT_T )
3779 aCollector.Remove( i );
3780 }
3781}
3782
3783
3785{
3786 // Can't modify an empty group
3787 if( aSelection.Empty() )
3788 return false;
3789
3790 if( ( m_dragging || aSelection[0]->IsMoving() ) && aSelection.HasReferencePoint() )
3791 return false;
3792
3793 // When there is only one item selected, the reference point is its position...
3794 if( aSelection.Size() == 1 && BaseType( aSelection.Front()->Type() ) != PCB_TABLE_T )
3795 {
3796 if( aSelection.Front()->IsBOARD_ITEM() )
3797 {
3798 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aSelection.Front() );
3799 aSelection.SetReferencePoint( item->GetPosition() );
3800 }
3801 }
3802 // ...otherwise modify items with regard to the grid-snapped center position
3803 else
3804 {
3805 PCB_GRID_HELPER grid( m_toolMgr, frame()->GetMagneticItemsSettings() );
3806 VECTOR2I refPt = aSelection.GetCenter();
3807
3808 // Exclude text in the footprint editor if there's anything else selected
3810 {
3811 BOX2I nonFieldsBBox;
3812
3813 for( EDA_ITEM* item : aSelection.Items() )
3814 {
3815 if( !item->IsType( { PCB_TEXT_T, PCB_FIELD_T } ) )
3816 nonFieldsBBox.Merge( item->GetBoundingBox() );
3817 }
3818
3819 if( nonFieldsBBox.IsValid() )
3820 refPt = nonFieldsBBox.GetCenter();
3821 }
3822
3823 aSelection.SetReferencePoint( grid.ResolveSnap( refPt, nullptr ).position );
3824 }
3825
3826 return true;
3827}
3828
3829
3830bool EDIT_TOOL::pickReferencePoint( const wxString& aTooltip, const wxString& aSuccessMessage,
3831 const wxString& aCanceledMessage, VECTOR2I& aReferencePoint )
3832{
3833 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
3835 std::optional<VECTOR2I> pickedPoint;
3836 bool done = false;
3837
3838 m_statusPopup->SetText( aTooltip );
3839
3841 picker->SetSnapping( true );
3842 picker->SetCursor( KICURSOR::PLACE );
3843 picker->ClearHandlers();
3844
3845 const auto setPickerLayerSet =
3846 [&]()
3847 {
3848 MAGNETIC_SETTINGS* magSettings = editFrame->GetMagneticItemsSettings();
3849 LSET layerFilter;
3850
3851 if( !magSettings->allLayers )
3852 layerFilter = LSET( { editFrame->GetActiveLayer() } );
3853 else
3854 layerFilter = LSET::AllLayersMask();
3855
3856 picker->SetLayerSet( layerFilter );
3857 };
3858
3859 // Initial set
3860 setPickerLayerSet();
3861
3862 picker->SetClickHandler(
3863 [&]( const VECTOR2D& aPoint ) -> bool
3864 {
3865 pickedPoint = aPoint;
3866
3867 if( !aSuccessMessage.empty() )
3868 {
3869 m_statusPopup->SetText( aSuccessMessage );
3870 m_statusPopup->Expire( 800 );
3871 }
3872 else
3873 {
3874 m_statusPopup->Hide();
3875 }
3876
3877 return false; // we don't need any more points
3878 } );
3879
3880 picker->SetMotionHandler(
3881 [&]( const VECTOR2D& aPos )
3882 {
3883 m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, -50 ) );
3884 } );
3885
3886 picker->SetCancelHandler(
3887 [&]()
3888 {
3889 if( !aCanceledMessage.empty() )
3890 {
3891 m_statusPopup->SetText( aCanceledMessage );
3892 m_statusPopup->Expire( 800 );
3893 }
3894 else
3895 {
3896 m_statusPopup->Hide();
3897 }
3898 } );
3899
3900 picker->SetFinalizeHandler(
3901 [&]( const int& aFinalState )
3902 {
3903 done = true;
3904 } );
3905
3906 m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, -50 ) );
3907 m_statusPopup->Popup();
3908 canvas()->SetStatusPopup( m_statusPopup->GetPanel() );
3909
3910 m_toolMgr->RunAction( ACTIONS::pickerSubTool );
3911
3912 while( !done )
3913 {
3914 // Pass events unless we receive a null event, then we must shut down
3915 if( TOOL_EVENT* evt = Wait() )
3916 {
3917 if( evt->Matches( PCB_EVENTS::SnappingModeChangedByKeyEvent() ) )
3918 {
3919 // Update the layer set when the snapping mode changes
3920 setPickerLayerSet();
3921 }
3922
3923 evt->SetPassEvent();
3924 }
3925 else
3926 {
3927 break;
3928 }
3929 }
3930
3931 picker->ClearHandlers();
3932
3933 // Ensure statusPopup is hidden after use and before deleting it:
3934 canvas()->SetStatusPopup( nullptr );
3935 m_statusPopup->Hide();
3936
3937 if( pickedPoint )
3938 aReferencePoint = *pickedPoint;
3939
3940 return pickedPoint.has_value();
3941}
3942
3943
3945{
3946 CLIPBOARD_IO io;
3947 PCB_GRID_HELPER grid( m_toolMgr, getEditFrame<PCB_BASE_EDIT_FRAME>()->GetMagneticItemsSettings() );
3948 TOOL_EVENT selectReferencePoint( aEvent.Category(), aEvent.Action(), "pcbnew.InteractiveEdit.selectReferencePoint",
3950
3951 SCOPED_TOOL_PUSHER raii( frame(), selectReferencePoint );
3952 Activate();
3953
3954 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
3955 [&]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
3956 {
3957 sTool->FilterCollectorForHierarchy( aCollector, true );
3958 sTool->FilterCollectorForMarkers( aCollector );
3959
3960 if( aEvent.IsAction( &ACTIONS::cut ) )
3961 sTool->FilterCollectorForLockedItems( aCollector );
3962 } );
3963
3964 if( m_selectionTool->ReportFilteredLockedItems() )
3965 return 1;
3966
3967 if( !selection.Empty() )
3968 {
3969 std::vector<BOARD_ITEM*> items;
3970
3971 for( EDA_ITEM* item : selection )
3972 {
3973 if( item->IsBOARD_ITEM() )
3974 items.push_back( static_cast<BOARD_ITEM*>( item ) );
3975 }
3976
3977 VECTOR2I refPoint;
3978
3980 {
3981 if( !pickReferencePoint( _( "Select reference point for the copy..." ), _( "Selection copied" ),
3982 _( "Copy canceled" ), refPoint ) )
3983 {
3984 return 0;
3985 }
3986 }
3987 else
3988 {
3989 refPoint = grid.BestDragOrigin( getViewControls()->GetCursorPosition(), items );
3990 }
3991
3992 selection.SetReferencePoint( refPoint );
3993
3994 io.SetBoard( board() );
3996 frame()->SetStatusText( _( "Selection copied" ) );
3997 }
3998
3999 // Cut needs this selection to delete exactly the items copied to the clipboard.
4000 if( selection.IsHover() && !aEvent.IsAction( &ACTIONS::cut ) )
4001 m_selectionTool->ClearSelection();
4002
4003 return 0;
4004}
4005
4006
4008{
4009 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
4010 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
4011 {
4012 // Anything unsupported will just be ignored
4013 } );
4014
4015 if( selection.IsHover() )
4016 m_selectionTool->ClearSelection();
4017
4018 const auto getItemText =
4019 [&]( const BOARD_ITEM& aItem ) -> wxString
4020 {
4021 switch( aItem.Type() )
4022 {
4023 case PCB_TEXT_T:
4024 case PCB_FIELD_T:
4025 case PCB_DIM_ALIGNED_T:
4026 case PCB_DIM_LEADER_T:
4027 case PCB_DIM_CENTER_T:
4028 case PCB_DIM_RADIAL_T:
4030 {
4031 // These can all go via the PCB_TEXT class
4032 const PCB_TEXT& text = static_cast<const PCB_TEXT&>( aItem );
4033 return text.GetShownText( FOR_CANVAS );
4034 }
4035 case PCB_TEXTBOX_T:
4036 case PCB_TABLECELL_T:
4037 {
4038 // This one goes via EDA_TEXT
4039 const PCB_TEXTBOX& textBox = static_cast<const PCB_TEXTBOX&>( aItem );
4040 return textBox.GetShownText( FOR_CANVAS );
4041 }
4042 case PCB_TABLE_T:
4043 case PCB_DRILL_CHART_T:
4044 {
4045 const PCB_TABLE& table = static_cast<const PCB_TABLE&>( aItem );
4046 wxString s;
4047
4048 for( int row = 0; row < table.GetRowCount(); ++row )
4049 {
4050 for( int col = 0; col < table.GetColCount(); ++col )
4051 {
4052 const PCB_TABLECELL* cell = table.GetCell( row, col );
4053 s << cell->GetShownText( FOR_CANVAS );
4054
4055 if( col < table.GetColCount() - 1 )
4056 s << '\t';
4057 }
4058
4059 if( row < table.GetRowCount() - 1 )
4060 s << '\n';
4061 }
4062 return s;
4063 }
4064 default:
4065 // No string representation for this item type
4066 break;
4067 }
4068
4069 return wxEmptyString;
4070 };
4071
4072 wxArrayString itemTexts;
4073
4074 for( EDA_ITEM* item : selection )
4075 {
4076 if( item->IsBOARD_ITEM() )
4077 {
4078 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
4079 wxString itemText = getItemText( *boardItem );
4080
4081 itemText.Trim( false ).Trim( true );
4082
4083 if( !itemText.IsEmpty() )
4084 itemTexts.Add( std::move( itemText ) );
4085 }
4086 }
4087
4088 // Send the text to the clipboard
4089 if( !itemTexts.empty() )
4090 SaveClipboard( wxJoin( itemTexts, '\n', '\0' ).ToStdString() );
4091
4092 return 0;
4093}
4094
4095
4097{
4098 if( !copyToClipboard( aEvent ) )
4099 {
4100 // N.B. Setting the CUT flag prevents lock filtering as we only want to delete the items
4101 // that were copied to the clipboard, no more, no fewer. Filtering for locked item, if
4102 // any will be done in the copyToClipboard() routine
4103 TOOL_EVENT evt = aEvent;
4105 Remove( evt );
4106 }
4107
4108 return 0;
4109}
4110
4111
4113{
4114 board()->BuildConnectivity();
4116 canvas()->RedrawRatsnest();
4117}
4118
4119
4120// clang-format off
4122{
4124 Go( &EDIT_TOOL::Move, PCB_ACTIONS::move.MakeEvent() );
4130 Go( &EDIT_TOOL::Flip, PCB_ACTIONS::flip.MakeEvent() );
4131 Go( &EDIT_TOOL::Remove, ACTIONS::doDelete.MakeEvent() );
4138 Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorH.MakeEvent() );
4139 Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorV.MakeEvent() );
4140 Go( &EDIT_TOOL::Swap, PCB_ACTIONS::swap.MakeEvent() );
4158
4164
4168
4172
4176 Go( &EDIT_TOOL::cutToClipboard, ACTIONS::cut.MakeEvent() );
4177}
4178// clang-format on
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
static TOOL_ACTION decrementPrimary
Definition actions.h:92
static TOOL_ACTION paste
Definition actions.h:76
static TOOL_ACTION pickerSubTool
Definition actions.h:250
static TOOL_ACTION unselectAll
Definition actions.h:79
static TOOL_ACTION decrementSecondary
Definition actions.h:94
static TOOL_ACTION selectItem
Select an item (specified as the event parameter).
Definition actions.h:223
static TOOL_ACTION copy
Definition actions.h:74
static TOOL_ACTION pasteSpecial
Definition actions.h:77
static TOOL_ACTION rightJustify
Definition actions.h:85
static TOOL_ACTION pageSettings
Definition actions.h:59
static TOOL_ACTION incrementSecondary
Definition actions.h:93
static TOOL_ACTION duplicate
Definition actions.h:80
static TOOL_ACTION incrementPrimary
Definition actions.h:91
static TOOL_ACTION doDelete
Definition actions.h:81
REMOVE_FLAGS
Remove event modifier flags.
Definition actions.h:321
static TOOL_ACTION increment
Definition actions.h:90
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION leftJustify
Definition actions.h:83
static TOOL_ACTION cut
Definition actions.h:73
static TOOL_ACTION copyAsText
Definition actions.h:75
static TOOL_ACTION refreshPreview
Definition actions.h:155
static TOOL_ACTION selectAll
Definition actions.h:78
static TOOL_ACTION selectItems
Select a list of items (specified as the event parameter)
Definition actions.h:228
static TOOL_ACTION centerJustify
Definition actions.h:84
Manage TOOL_ACTION objects.
void SetConditions(const TOOL_ACTION &aAction, const ACTION_CONDITIONS &aConditions)
Set the conditions the UI elements for activating a specific tool action should use for determining t...
ACTION_MENU(bool isContextMenu, TOOL_INTERACTIVE *aTool=nullptr)
Default constructor.
TOOL_MANAGER * getToolManager() const
Return an instance of TOOL_MANAGER class.
void Clear()
Remove all the entries from the menu (as well as its title).
void SetTitle(const wxString &aTitle) override
Set title for the menu.
void SetIcon(BITMAPS aIcon)
Assign an icon for the entry.
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
void Revert() override
Revert the commit by restoring the modified items state.
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
virtual void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
NETINFO_ITEM * GetNet() const
Return NETINFO_ITEM object for a given 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:85
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:427
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:445
FOOTPRINT * GetParentFootprint() const
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition board_item.h:355
virtual void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const
Invoke a function on all children.
Definition board_item.h:265
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:267
virtual void Normalize()
Perform any normalization required after a user rotate and/or flip.
Definition board_item.h:498
virtual void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection)
Flip this object, i.e.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
const LSET & GetVisibleLayers() const
A proxy function that calls the correspondent function in m_BoardSettings.
Definition board.cpp:1188
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:713
const FOOTPRINTS & Footprints() const
Definition board.h:465
constexpr const Vec & GetPosition() const
Definition box2.h:208
constexpr const Vec GetEnd() const
Definition box2.h:209
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr const Vec GetCenter() const
Definition box2.h:227
constexpr bool IsValid() const
Definition box2.h:857
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:79
void Remove(int aIndex)
Remove the item at aIndex (first position is 0).
Definition collector.h:107
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
bool Empty() const
Definition commit.h:145
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:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
Interactive authoring of geometric constraints (issue #2329).
bool TryDeleteSelectedConstraint()
Delete the currently badge-selected constraint; returns true if one was selected and removed.
bool TryEditSelectedConstraint()
Edit the currently badge-selected constraint's value; returns true if one was selected.
void SolveAfterMove(const std::vector< PCB_SHAPE * > &aShapes)
Re-solve the clusters of aShapes after a whole-shape edit, folded into that edit's undo.
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, INFOBAR_MESSAGE_TYPE aType=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...
virtual int GetUndoCommandCount() const
void DisplayToolMsg(const wxString &msg) override
A set of EDA_ITEMs (i.e., without duplicates).
Definition eda_group.h:43
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:78
virtual EDA_ITEM * AsEdaItem()=0
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:348
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:158
const KIID m_Uuid
Definition eda_item.h:599
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:116
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
void ClearSelected()
Definition eda_item.h:153
bool IsSelected() const
Definition eda_item.h:134
virtual bool IsType(const std::vector< KICAD_T > &aScanTypes) const
Check whether the item is one of the listed types.
Definition eda_item.h:214
SHAPE_POLY_SET & GetPolyShape()
SHAPE_T GetShape() const
Definition eda_shape.h:177
bool IsHatchedFill() const
Definition eda_shape.h:132
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:333
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:283
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:94
virtual bool IsVisible() const
Definition eda_text.h:226
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:347
static void collectConstraintShapes(const SELECTION &aSelection, std::vector< PCB_SHAPE * > &aShapes)
Collect constrainable PCB_SHAPEs in aSelection recursing groups and footprints so contained shapes se...
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.
int SwapGateNets(const TOOL_EVENT &aEvent)
int Swap(const TOOL_EVENT &aEvent)
Swap currently selected items' positions.
bool m_inMoveWithReference
Definition edit_tool.h:252
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 doMoveSelection(const TOOL_EVENT &aEvent, BOARD_COMMIT *aCommit, bool aAutoStart, std::vector< PCB_SHAPE * > *aConstraintShapes=nullptr)
Runs interactive move drag when aConstraintShapes is non null previews constraint solve live each tic...
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.
int EditVertices(const TOOL_EVENT &aEvent)
int ToggleFootprintAttribute(const TOOL_EVENT &aEvent)
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:251
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:257
int JustifyText(const TOOL_EVENT &aEvent)
Set the justification on any text items (or fields) in the current selection.
bool updateModificationPoint(PCB_SELECTION &aSelection)
Return the right modification point (e.g.
static bool CanDuplicateSelection(const SELECTION &aSelection)
int ChangeTrackLayer(const TOOL_EVENT &aEvent)
std::unique_ptr< STATUS_TEXT_POPUP > m_statusPopup
Definition edit_tool.h:255
int copyToClipboard(const TOOL_EVENT &aEvent)
Send the current selection to the clipboard by formatting it as a fake pcb.
int SwapPadNets(const TOOL_EVENT &aEvent)
Swap nets between selected pads and propagate to connected copper items (tracks, arcs,...
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.
static const std::vector< KICAD_T > MirrorableItems
Definition edit_tool.h:2705
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.
bool invokeInlineRouter(int aDragMode)
void rebuildConnectivity()
Rebuilds the ratsnest for operations that require it outside the commit rebuild.
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.
void reSolveConstraintsAfterEdit(const PCB_SELECTION &aSelection)
Re-solve the geometric constraints of any shapes in aSelection after a transform.
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)
Find an item and start moving.
int FilletTracks(const TOOL_EVENT &aEvent)
Fillet (i.e.
PCB_SELECTION_TOOL * m_selectionTool
Definition edit_tool.h:250
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_FOOTPRINT_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 item had a property changed (except movement)
Definition actions.h:353
static const TOOL_EVENT ConnectivityChangedEvent
Definition actions.h:350
static const TOOL_EVENT UnselectedEvent
Definition actions.h:347
Variant information for a footprint.
Definition footprint.h:178
void SetExcludedFromPosFiles(bool aExclude)
Definition footprint.h:202
void SetExcludedFromSim(bool aExclude)
Definition footprint.h:199
void SetExcludedFromBOM(bool aExclude)
Definition footprint.h:196
bool GetExcludedFromSimForVariant(const wxString &aVariantName) const
Get the exclude-from-simulation status for a specific variant.
void SetExcludedFromSim(bool aExclude=true)
Definition footprint.h:939
void SetExcludedFromBOM(bool aExclude=true)
Definition footprint.h:930
const std::vector< FP_UNIT_INFO > & GetUnitInfo() const
Definition footprint.h:909
void SetExcludedFromPosFiles(bool aExclude=true)
Definition footprint.h:921
const FOOTPRINT_VARIANT * GetVariant(const wxString &aVariantName) const
Get a variant by name.
CONSTRAINTS & Constraints()
Definition footprint.h:367
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.
bool GetExcludedFromPosFilesForVariant(const wxString &aVariantName) const
Get the exclude-from-position-files status for a specific variant.
FOOTPRINT_VARIANT * AddVariant(const wxString &aVariantName)
Add a new variant with the given name.
bool GetExcludedFromBOMForVariant(const wxString &aVariantName) const
Get the exclude-from-BOM status for a specific variant.
wxString GetNextPadNumber(const wxString &aLastPadName) const
Return the next available pad number in the footprint.
ACTION_MENU * create() const override
Return an instance of this class. It has to be overridden in inheriting classes.
static bool EqualPinCounts(const FOOTPRINT *aFootprint, const std::vector< int > &aUnitIndices)
void update() override
Update menu state stub.
static std::vector< int > GetCompatibleTargets(const FOOTPRINT *aFootprint, int aSourceIdx)
OPT_TOOL_EVENT eventHandler(const wxMenuEvent &aEvent) override
Event handler stub.
static std::unordered_set< wxString > CollectSelectedPadNumbers(const SELECTION &aSelection, const FOOTPRINT *aFootprint)
static const FOOTPRINT * GetSingleEligibleFootprint(const SELECTION &aSelection)
static std::vector< int > GetUnitsHitIndices(const FOOTPRINT *aFootprint, const std::unordered_set< wxString > &aSelPadNums)
Used when the right click button is pressed, or when the select tool is in effect.
Definition collectors.h:203
static const std::vector< KICAD_T > DraggableItems
A scan list for items that can be dragged.
Definition collectors.h:148
A handler that is based on a set of callbacks provided by the user of the ITEM_MODIFICATION_ROUTINE.
bool IsBOARD_ITEM() const
Definition view_item.h:98
virtual wxString GetClass() const =0
Return the class name.
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllLayersMask()
Definition lset.cpp:637
static ZONE * ChannelZone(const PCB_GROUP *aGroup)
The rule area zone that makes aGroup a repeated channel, or null for any other group.
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:44
int GetuViaDrill() const
Definition netclass.h:182
int GetuViaDiameter() const
Definition netclass.h:174
void ForEachUniqueLayer(const std::function< void(PCB_LAYER_ID)> &aMethod) const
Runs the given callable for each active unique copper layer in this padstack, meaning F_Cu for MODE::...
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:170
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
void RecordMasterPad(const PAD &aPad)
Record aPad as the template for the next pad of its type placed.
Definition pad_tool.cpp:386
void SetLastPadNumber(const wxString &aPadNumber)
Definition pad_tool.h:67
wxString GetLastPadNumber() const
Definition pad_tool.h:66
Definition pad.h:61
const VECTOR2I & GetDelta(PCB_LAYER_ID aLayer) const
Definition pad.h:307
VECTOR2I GetPosition() const override
Definition pad.cpp:255
void SetDelta(PCB_LAYER_ID aLayer, const VECTOR2I &aSize)
Definition pad.h:301
VECTOR2I GetOffset(PCB_LAYER_ID aLayer) const
Definition pad.cpp:837
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:1888
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition pad.h:205
void SetOffset(PCB_LAYER_ID aLayer, const VECTOR2I &aOffset)
Definition pad.cpp:826
void SetPosition(const VECTOR2I &aPos) override
Definition pad.cpp:244
const PADSTACK & Padstack() const
Definition pad.h:331
EDA_ANGLE GetOrientation() const
Return the rotation angle of the pad.
Definition pad.cpp:1795
void SetOrientation(const EDA_ANGLE &aAngle)
Set the rotation angle of the pad.
Definition pad.cpp:1770
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 repeatLayout
static TOOL_ACTION routerRouteSelectedFromEnd
static TOOL_ACTION toggleExcludeFromSim
static TOOL_ACTION swapPadNets
Swap nets between selected pads/gates (and connected copper)
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 routerOptimizeSelected
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 toggleExcludeFromBOM
static TOOL_ACTION extendGraphic
Extend one graphical line or arc to the nearest boundary.
static TOOL_ACTION dragFreeAngle
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 toggleExcludeFromPosFiles
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 interactiveOffsetTool
static TOOL_ACTION footprintProperties
static TOOL_ACTION pointEditorChamferCorner
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 pointEditorRemoveCorner
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 pointEditorAddCorner
static TOOL_ACTION editVertices
Edit polygon vertices in a table.
static TOOL_ACTION swapGateNets
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
static TOOL_ACTION trimGraphic
Trim a section from one graphical line or arc.
Common, abstract interface for edit frames.
virtual void OnEditItemRequest(BOARD_ITEM *aItem)
Install the corresponding dialog editor for the given item.
void OpenVertexEditor(BOARD_ITEM *aItem)
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
A geometric constraint between board items (issue #2329).
DS_PROXY_VIEW_ITEM * GetDrawingSheet() const
static const TOOL_EVENT & SnappingModeChangedByKeyEvent()
Hotkey feedback.
virtual bool ChildrenAreReadOnly() const
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const override
Invoke a function on all children.
Generic tool for picking an item.
void SetLayerSet(const LSET &aLayerSet)
Set the tool's snap layer set.
void ClearHandlers()
Handlers only.
Tool that displays edit points allowing to modify items by dragging the points.
bool CanRemoveCorner(const SELECTION &aSelection)
Condition to display "Remove Corner" context menu entry.
static bool CanChamferCorner(const EDA_ITEM &aItem)
Check if a corner of the given item can be chamfered (zones, polys only).
static bool CanAddCorner(const EDA_ITEM &aItem)
Check if a corner can be added to the given item (zones, polys, segments, arcs).
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:39
Object to handle a bitmap image that can be inserted in a PCB.
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:80
void SetWidth(int aWidth) override
int GetWidth() const override
void SetEnd(const VECTOR2I &aEnd) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition pcb_shape.h:70
wxString GetShownText(RESOLUTION_CONTEXT aContext, int aDepth=0) const override
Return the string actually shown after processing of the base text.
wxString GetShownText(RESOLUTION_CONTEXT aContext, int aDepth=0) const override
Return the string actually shown after processing of the base text.
T * frame() const
bool IsFootprintEditor() const
bool IsBoardEditor() 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:121
virtual double GetLength() const
Get the length of the track using the hypotenuse calculation.
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:94
bool HasSolderMask() const
Definition pcb_track.h:122
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:97
void SetLocalSolderMaskMargin(std::optional< int > aMargin)
Definition pcb_track.h:124
std::optional< int > GetLocalSolderMaskMargin() const
Definition pcb_track.h:125
const VECTOR2I & GetStart() const
Definition pcb_track.h:98
const VECTOR2I & GetEnd() const
Definition pcb_track.h:95
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:91
virtual int GetWidth() const
Definition pcb_track.h:92
void ExcludePosition(const VECTOR2I &aPos)
Add a board position to the exclusion set so the next Update() skips it.
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:65
void SetCursor(KICURSOR aCursor)
Definition picker_tool.h:63
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:38
bool ApproxCollinear(const SEG &aSeg, int aDistanceThreshold=1) const
Definition seg.cpp:759
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 bool ShowAlways(const SELECTION &aSelection)
The default condition function (always returns true).
static SELECTION_CONDITION OnlyTypes(std::vector< KICAD_T > aTypes)
Create a functor that tests if the selected items are only of given types.
ITER end()
Definition selection.h:76
ITER begin()
Definition selection.h:75
virtual VECTOR2I GetCenter() const
Returns the center point of the selection area bounding box.
Definition selection.cpp:92
bool IsHover() const
Definition selection.h:85
virtual unsigned int GetSize() const override
Return the number of stored items.
Definition selection.h:104
EDA_ITEM * Front() const
Definition selection.h:176
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:120
std::deque< EDA_ITEM * > & Items()
Definition selection.h:181
void SetReferencePoint(const VECTOR2I &aP)
bool Empty() const
Checks if there is anything selected.
Definition selection.h:114
bool HasReferencePoint() const
Definition selection.h:215
size_t CountType(KICAD_T aType) const
bool Contains(EDA_ITEM *aItem) const
Definition selection.cpp:84
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
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:129
Heuristically increment a string's n'th part from the right.
Definition increment.h:44
TOOL_MANAGER * GetManager() const
Return the instance of TOOL_MANAGER that takes care of the tool.
Definition tool_base.h:142
T * getEditFrame() const
Return the application window object, casted to requested user type.
Definition tool_base.h:182
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition tool_base.cpp:40
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
KIGFX::VIEW * getView() const
Return the instance of KIGFX::VIEW object used in the application.
Definition tool_base.cpp:34
bool IsToolActive() const
Definition tool_base.cpp:28
RESET_REASON
Determine the reason of reset for a tool.
Definition tool_base.h:74
Generic, UI-independent tool event.
Definition tool_event.h:167
bool HasParameter() const
Definition tool_event.h:460
TOOL_ACTIONS Action() const
Returns more specific information about the type of an event.
Definition tool_event.h:246
bool Matches(const TOOL_EVENT &aEvent) const
Test whether two events match in terms of category & action or command.
Definition tool_event.h:388
COMMIT * Commit() const
Definition tool_event.h:279
void SetParameter(T aParam)
Set a non-standard parameter assigned to the event.
Definition tool_event.h:539
TOOL_EVENT_CATEGORY Category() const
Return the category (eg. mouse/keyboard/action) of an event.
Definition tool_event.h:243
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:484
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.
Master controller class:
T * GetTool()
Return the tool of given type or nullptr if there is no such tool registered.
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
VECTOR2_TRAITS< int32_t >::extended_type extended_type
Definition vector2d.h:69
A modified version of the wxInfoBar class that allows us to:
Definition wx_infobar.h:76
void RemoveAllButtons()
Remove all the buttons that have been added by the user.
void ShowMessageFor(const wxString &aMessage, int aTime, int aFlags=wxICON_INFORMATION, MESSAGE_TYPE aType=WX_INFOBAR::MESSAGE_TYPE::GENERIC)
Show the infobar with the provided message and icon for a specific period of time.
void AddLink(const wxString &aLinkText, const std::function< void(wxHyperlinkEvent &)> &aFn)
Add a hypertext link to the infobar.
void AddCloseButton(const wxString &aTooltip=_("Hide this message."))
Add the default close button to the infobar on the right side.
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:70
bool UnFill()
Removes the zone filling.
Definition zone.cpp:563
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:1132
void HatchBorder()
Compute the hatch lines depending on the hatch parameters and stores it in the zone's attribute m_bor...
Definition zone.cpp:1614
void RemoveCutout(int aOutlineIdx, int aHoleIdx)
Remove a cutout from the zone.
Definition zone.cpp:1450
bool IsTeardropArea() const
Definition zone.h:807
bool SaveClipboard(const std::string &aTextUTF8)
Store information to the system clipboard.
Definition clipboard.cpp:32
@ FOR_CANVAS
Definition common.h:92
std::vector< PCB_CONSTRAINT * > CloneFullySelectedConstraints(const CONSTRAINTS &aSource, const std::map< KIID, KIID > &aIdMap)
Clone the constraints in aSource that a duplicate should carry.
@ PLACE
Definition cursors.h:95
ROTATION_ANCHOR
@ ROTATE_AROUND_USER_ORIGIN
@ ROTATE_AROUND_SEL_CENTER
@ ROTATE_AROUND_AUX_ORIGIN
@ ROTATE_AROUND_ITEM_ANCHOR
static std::string ToStdString(const wxString &aStr)
#define _(s)
@ RECURSE
Definition eda_item.h:51
@ NO_RECURSE
Definition eda_item.h:52
#define IS_NEW
New item, just created.
#define STRUCT_DELETED
flag indication structures to be erased
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
static const std::vector< KICAD_T > baseConnectedTypes
static std::shared_ptr< ACTION_MENU > makeGateSwapMenu(TOOL_INTERACTIVE *aTool)
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 void recordDuplicatedItems(TOOL_MANAGER *aToolMgr, const std::vector< BOARD_ITEM * > &aItems)
Handle items that have been duplicated.
static std::optional< CHAMFER_PARAMS > GetChamferParams(PCB_BASE_EDIT_FRAME &aFrame)
Prompt the user for chamfer parameters.
static std::shared_ptr< CONDITIONAL_MENU > makeMirrorRotateMenu(EDIT_TOOL *aEditTool)
static bool itemHasEditableCorners(BOARD_ITEM *aItem)
Definition edit_tool.cpp:90
static bool groupMirrorable(const PCB_GROUP *aGroup)
static const std::vector< KICAD_T > footprintTypes
static std::shared_ptr< CONDITIONAL_MENU > makeRoutingToolsMenu(TOOL_INTERACTIVE *aTool)
static const std::vector< KICAD_T > groupTypes
static const std::vector< KICAD_T > padTypes
static const std::vector< KICAD_T > nonMirrorableTypes
static bool selectionMirrorable(const SELECTION &aSelection)
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 bool selectionHasEditableCorners(const SELECTION &aSelection)
static const std::vector< KICAD_T > trackTypes
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:274
@ LAYER_SCHEMATIC_DRAWINGSHEET
Definition layer_ids.h:524
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
This file contains miscellaneous commonly used macros and functions.
#define UNIMPLEMENTED_FOR(type)
Definition macros.h:92
constexpr void MIRROR(T &aPoint, const T &aMirrorRef)
Updates aPoint with the mirror of aPoint relative to the aMirrorRef.
Definition mirror.h:41
FLIP_DIRECTION
Definition mirror.h:23
@ LEFT_RIGHT
Flip left to right (around the Y axis)
Definition mirror.h:24
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:25
wxPoint GetMousePosition()
Returns the mouse position in screen coordinates.
Definition wxgtk/ui.cpp:958
@ DM_ANY
Definition pns_router.h:83
@ DM_FREE_ANGLE
Definition pns_router.h:81
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:96
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:80
#define _HKI(x)
Definition page_info.cpp:40
Class to handle a set of BOARD_ITEMs.
std::deque< FOOTPRINT * > FOOTPRINTS
std::deque< PCB_CONSTRAINT * > CONSTRAINTS
bool IsGeneratedTableCell(const EDA_ITEM *aItem)
True for a cell whose text is generated from the board, so only its formatting is the user's.
Definition pcb_table.h:348
@ ID_POPUP_PCB_SWAP_UNIT_LAST
Definition pcbnew_id.h:59
@ ID_POPUP_PCB_SWAP_UNIT_BASE
Definition pcbnew_id.h:58
#define APPEND_UNDO
Definition sch_commit.h:39
std::vector< EDA_ITEM * > EDA_ITEMS
static std::vector< KICAD_T > connectedTypes
std::function< bool(const SELECTION &)> SELECTION_CONDITION
Functor type that checks a specific condition for selected items.
Increment event parameters.
Definition actions.h:329
Functors that can be used to figure out how the action controls should be displayed in the UI and if ...
Parameters that define a simple chamfer operation.
#define EDIT_TOOL(tool)
STRING_INCREMENTER incrementer
KIBIS_MODEL * model
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:45
std::optional< TOOL_EVENT > OPT_TOOL_EVENT
Definition tool_event.h:652
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:227
constexpr KICAD_T BaseType(const KICAD_T aType)
Return the underlying type of the given type.
Definition typeinfo.h:260
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:70
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:98
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:95
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:83
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:96
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:103
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:85
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:100
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:84
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:81
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:82
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:93
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition typeinfo.h:99
@ PCB_SHAPE_LOCATE_CIRCLE_T
Definition typeinfo.h:131
@ PCB_SHAPE_LOCATE_SEGMENT_T
Definition typeinfo.h:129
@ PCB_SHAPE_LOCATE_RECT_T
Definition typeinfo.h:130
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:87
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
@ PCB_GRID_ITEM_T
a subgrid placed on a board
Definition typeinfo.h:238
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:94
@ PCB_SHAPE_LOCATE_BEZIER_T
Definition typeinfo.h:134
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_SHAPE_LOCATE_POLY_T
Definition typeinfo.h:133
@ PCB_SHAPE_LOCATE_ARC_T
Definition typeinfo.h:132
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:92
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:86
@ PCB_POINT_T
class PCB_POINT, a 0-dimensional point
Definition typeinfo.h:105
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:97
@ PCB_DRILL_CHART_T
class PCB_DRILL_CHART, a live drill chart derived from PCB_TABLE
Definition typeinfo.h:239
Casted dyn_cast(From aObject)
A lightweight dynamic downcast.
Definition typeinfo.h:55
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707