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