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