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