1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2013-2017 CERN
5 * Copyright (C) 2017-2021 KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <maciej.suminski@cern.ch>
7 * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
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 <advanced_config.h>
28 #include <limits>
29 #include <board.h>
30 #include <board_design_settings.h>
31 #include <footprint.h>
32 #include <fp_shape.h>
33 #include <collectors.h>
34 #include <pcb_edit_frame.h>
35 #include <drawing_sheet/ds_proxy_view_item.h>
36 #include <kiway.h>
37 #include <array_creator.h>
38 #include <pcbnew_settings.h>
39 #include <status_popup.h>
40 #include <tool/selection_conditions.h>
41 #include <tool/tool_manager.h>
42 #include <tools/pcb_actions.h>
43 #include <tools/pcb_selection_tool.h>
44 #include <tools/edit_tool.h>
45 #include <tools/pcb_picker_tool.h>
46 #include <tools/tool_event_utils.h>
47 #include <tools/pcb_grid_helper.h>
48 #include <tools/pad_tool.h>
49 #include <view/view_controls.h>
50 #include <connectivity/connectivity_algo.h>
51 #include <connectivity/connectivity_items.h>
52 #include <bitmaps.h>
53 #include <cassert>
54 #include <functional>
55 using namespace std::placeholders;
56 #include "kicad_clipboard.h"
57 #include <wx/hyperlink.h>
58 #include <router/router_tool.h>
59 #include <dialogs/dialog_move_exact.h>
60 #include <dialogs/dialog_track_via_properties.h>
61 #include <dialogs/dialog_unit_entry.h>
62 #include <board_commit.h>
63 #include <pcb_group.h>
64 #include <pcb_target.h>
65 #include <zone_filler.h>
66
67
EDIT_TOOL()68 EDIT_TOOL::EDIT_TOOL() :
69 PCB_TOOL_BASE( "pcbnew.InteractiveEdit" ),
70 m_selectionTool( nullptr ),
71 m_dragging( false )
72 {
73 }
74
75
Reset(RESET_REASON aReason)76 void EDIT_TOOL::Reset( RESET_REASON aReason )
77 {
78 m_dragging = false;
79
80 m_statusPopup = std::make_unique<STATUS_TEXT_POPUP>( getEditFrame<PCB_BASE_EDIT_FRAME>() );
81
82 if( aReason != RUN )
83 m_commit.reset( new BOARD_COMMIT( this ) );
84 }
85
86
SPECIAL_TOOLS_CONTEXT_MENU(TOOL_INTERACTIVE * aTool)87 SPECIAL_TOOLS_CONTEXT_MENU::SPECIAL_TOOLS_CONTEXT_MENU( TOOL_INTERACTIVE* aTool ) :
88 CONDITIONAL_MENU( aTool )
89 {
90 SetIcon( BITMAPS::special_tools );
91 SetTitle( _( "Special Tools" ) );
92
93 AddItem( PCB_ACTIONS::moveExact, SELECTION_CONDITIONS::ShowAlways );
94 AddItem( PCB_ACTIONS::moveWithReference, SELECTION_CONDITIONS::ShowAlways );
95 AddItem( PCB_ACTIONS::positionRelative, SELECTION_CONDITIONS::ShowAlways );
96 AddItem( PCB_ACTIONS::createArray, SELECTION_CONDITIONS::ShowAlways );
97 }
98
99
Init()100 bool EDIT_TOOL::Init()
101 {
102 // Find the selection tool, so they can cooperate
103 m_selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
104
105 auto propertiesCondition =
106 [&]( const SELECTION& aSel )
107 {
108 if( aSel.GetSize() == 0 )
109 {
110 if( getView()->IsLayerVisible( LAYER_SCHEMATIC_DRAWINGSHEET ) )
111 {
112 DS_PROXY_VIEW_ITEM* ds = frame()->GetCanvas()->GetDrawingSheet();
113 VECTOR2D cursor = getViewControls()->GetCursorPosition( false );
114
115 if( ds && ds->HitTestDrawingSheetItems( getView(), (wxPoint) cursor ) )
116 return true;
117 }
118
119 return false;
120 }
121
122 if( aSel.GetSize() == 1 )
123 return true;
124
125 for( EDA_ITEM* item : aSel )
126 {
127 if( !dynamic_cast<PCB_TRACK*>( item ) )
128 return false;
129 }
130
131 return true;
132 };
133
134 auto inFootprintEditor =
135 [ this ]( const SELECTION& aSelection )
136 {
137 return m_isFootprintEditor;
138 };
139
140 auto singleFootprintCondition = SELECTION_CONDITIONS::OnlyType( PCB_FOOTPRINT_T )
141 && SELECTION_CONDITIONS::Count( 1 );
142
143 auto noActiveToolCondition =
144 [ this ]( const SELECTION& aSelection )
145 {
146 return frame()->ToolStackIsEmpty();
147 };
148
149 auto notMovingCondition =
150 [ this ]( const SELECTION& aSelection )
151 {
152 return !frame()->IsCurrentTool( PCB_ACTIONS::move )
153 && !frame()->IsCurrentTool( PCB_ACTIONS::moveWithReference );
154 };
155
156 auto noItemsCondition =
157 [ this ]( const SELECTION& aSelections ) -> bool
158 {
159 return frame()->GetBoard() && !frame()->GetBoard()->IsEmpty();
160 };
161
162 // Add context menu entries that are displayed when selection tool is active
163 CONDITIONAL_MENU& menu = m_selectionTool->GetToolMenu().GetMenu();
164
165 menu.AddItem( PCB_ACTIONS::move, SELECTION_CONDITIONS::NotEmpty
166 && notMovingCondition );
167 menu.AddItem( PCB_ACTIONS::breakTrack, SELECTION_CONDITIONS::Count( 1 )
168 && SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
169 menu.AddItem( PCB_ACTIONS::drag45Degree, SELECTION_CONDITIONS::Count( 1 )
170 && SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::DraggableItems ) );
171 menu.AddItem( PCB_ACTIONS::dragFreeAngle, SELECTION_CONDITIONS::Count( 1 )
172 && SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::DraggableItems )
173 && !SELECTION_CONDITIONS::OnlyType( PCB_FOOTPRINT_T ) );
174 menu.AddItem( PCB_ACTIONS::filletTracks, SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
175 menu.AddItem( PCB_ACTIONS::rotateCcw, SELECTION_CONDITIONS::NotEmpty );
176 menu.AddItem( PCB_ACTIONS::rotateCw, SELECTION_CONDITIONS::NotEmpty );
177 menu.AddItem( PCB_ACTIONS::flip, SELECTION_CONDITIONS::NotEmpty );
178 menu.AddItem( PCB_ACTIONS::mirror, inFootprintEditor && SELECTION_CONDITIONS::NotEmpty );
179
180 menu.AddItem( PCB_ACTIONS::properties, propertiesCondition );
181
182 // Footprint actions
183 menu.AddSeparator();
184 menu.AddItem( PCB_ACTIONS::editFpInFpEditor, singleFootprintCondition );
185 menu.AddItem( PCB_ACTIONS::updateFootprint, singleFootprintCondition );
186 menu.AddItem( PCB_ACTIONS::changeFootprint, singleFootprintCondition );
187
188 // Add the submenu for create array and special move
189 auto specialToolsSubMenu = std::make_shared<SPECIAL_TOOLS_CONTEXT_MENU>( this );
190 menu.AddSeparator();
191 m_selectionTool->GetToolMenu().AddSubMenu( specialToolsSubMenu );
192 menu.AddMenu( specialToolsSubMenu.get(), SELECTION_CONDITIONS::NotEmpty, 100 );
193
194 menu.AddSeparator( 150 );
195 menu.AddItem( ACTIONS::cut, SELECTION_CONDITIONS::NotEmpty, 150 );
196 menu.AddItem( ACTIONS::copy, SELECTION_CONDITIONS::NotEmpty, 150 );
197
198 // Selection tool handles the context menu for some other tools, such as the Picker.
199 // Don't add things like Paste when another tool is active.
200 menu.AddItem( ACTIONS::paste, noActiveToolCondition, 150 );
201 menu.AddItem( ACTIONS::pasteSpecial, noActiveToolCondition && !inFootprintEditor, 150 );
202 menu.AddItem( ACTIONS::duplicate, SELECTION_CONDITIONS::NotEmpty, 150 );
203 menu.AddItem( ACTIONS::doDelete, SELECTION_CONDITIONS::NotEmpty, 150 );
204
205 menu.AddSeparator( 150 );
206 menu.AddItem( ACTIONS::selectAll, noItemsCondition, 150 );
207
208 return true;
209 }
210
211
GetAndPlace(const TOOL_EVENT & aEvent)212 int EDIT_TOOL::GetAndPlace( const TOOL_EVENT& aEvent )
213 {
214 // GetAndPlace makes sense only in board editor, although it is also called
215 // in fpeditor, that shares the same EDIT_TOOL list
216 if( !getEditFrame<PCB_BASE_FRAME>()->IsType( FRAME_PCB_EDITOR ) )
217 return 0;
218
219 PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
220 FOOTPRINT* fp = getEditFrame<PCB_BASE_FRAME>()->GetFootprintFromBoardByReference();
221
222 if( fp )
223 {
224 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
225 m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, (void*) fp );
226
227 selectionTool->GetSelection().SetReferencePoint( fp->GetPosition() );
228 m_toolMgr->RunAction( PCB_ACTIONS::move, false );
229 }
230
231 return 0;
232 }
233
234
invokeInlineRouter(int aDragMode)235 bool EDIT_TOOL::invokeInlineRouter( int aDragMode )
236 {
237 ROUTER_TOOL* theRouter = m_toolMgr->GetTool<ROUTER_TOOL>();
238
239 if( !theRouter )
240 return false;
241
242 // don't allow switch from moving to dragging
243 if( m_dragging )
244 {
245 wxBell();
246 return false;
247 }
248
249 // make sure we don't accidentally invoke inline routing mode while the router is already
250 // active!
251 if( theRouter->IsToolActive() )
252 return false;
253
254 if( theRouter->CanInlineDrag( aDragMode ) )
255 {
256 m_toolMgr->RunAction( PCB_ACTIONS::routerInlineDrag, true,
257 static_cast<intptr_t>( aDragMode ) );
258 return true;
259 }
260
261 return false;
262 }
263
264
265
isRouterActive() const266 bool EDIT_TOOL::isRouterActive() const
267 {
268 ROUTER_TOOL* router = m_toolMgr->GetTool<ROUTER_TOOL>();
269
270 return router && router->IsToolActive();
271 }
272
273
Drag(const TOOL_EVENT & aEvent)274 int EDIT_TOOL::Drag( const TOOL_EVENT& aEvent )
275 {
276 if( !m_toolMgr->GetTool<ROUTER_TOOL>() )
277 return false; // don't drag when no router tool (i.e. fp editor)
278
279 if( m_toolMgr->GetTool<ROUTER_TOOL>()->IsToolActive() )
280 return false; // don't drag when router is already active
281
282 int mode = PNS::DM_ANY;
283
284 if( aEvent.IsAction( &PCB_ACTIONS::dragFreeAngle ) )
285 mode |= PNS::DM_FREE_ANGLE;
286
287 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
288 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
289 {
290 sTool->FilterCollectorForFreePads( aCollector );
291 sTool->FilterCollectorForHierarchy( aCollector, true );
292 },
293 true /* prompt user regarding locked items */ );
294
295 if( selection.Empty() )
296 return 0;
297
298 if( selection.Size() == 1 && selection.Front()->Type() == PCB_ARC_T )
299 {
300 // TODO: This really should be done in PNS to ensure DRC is maintained, but for now
301 // it allows interactive editing of an arc track
302 return DragArcTrack( aEvent );
303 }
304 else
305 {
306 invokeInlineRouter( mode );
307 }
308
309 return 0;
310 }
311
312
DragArcTrack(const TOOL_EVENT & aEvent)313 int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent )
314 {
315 PCB_SELECTION& selection = m_selectionTool->GetSelection();
316
317 if( selection.Size() != 1 || selection.Front()->Type() != PCB_ARC_T )
318 return 0;
319
320 PCB_ARC* theArc = static_cast<PCB_ARC*>( selection.Front() );
321 double arcAngleDegrees = std::abs( theArc->GetAngle() ) / 10.0;
322
323 if( arcAngleDegrees + ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation >= 180.0 )
324 {
325 frame()->ShowInfoBarError(
326 wxString::Format( _( "Unable to resize arc tracks %.1f degrees or greater." ),
327 180.0 - ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation ) );
328 return 0; // don't bother with > 180 degree arcs
329 }
330
331 KIGFX::VIEW_CONTROLS* controls = getViewControls();
332
333 Activate();
334 // Must be done after Activate() so that it gets set into the correct context
335 controls->ShowCursor( true );
336 controls->SetAutoPan( true );
337
338 bool restore_state = false;
339 VECTOR2I arcCenter = theArc->GetCenter();
340 SEG tanStart = SEG( arcCenter, theArc->GetStart() ).PerpendicularSeg( theArc->GetStart() );
341 SEG tanEnd = SEG( arcCenter, theArc->GetEnd() ).PerpendicularSeg( theArc->GetEnd() );
342
343 //Ensure the tangent segments are in the correct orientation
344 VECTOR2I tanIntersect = tanStart.IntersectLines( tanEnd ).get();
345 tanStart.A = tanIntersect;
346 tanStart.B = theArc->GetStart();
347 tanEnd.A = tanIntersect;
348 tanEnd.B = theArc->GetEnd();
349
350 KICAD_T track_types[] = { PCB_PAD_T, PCB_VIA_T, PCB_TRACE_T, PCB_ARC_T, EOT };
351
352 auto getUniqueTrackAtAnchorCollinear =
353 [&]( const VECTOR2I& aAnchor, const SEG& aCollinearSeg ) -> PCB_TRACK*
354 {
355 auto conn = board()->GetConnectivity();
356
357 // Allow items at a distance within the width of the arc track
358 int allowedDeviation = theArc->GetWidth();
359
360 std::vector<BOARD_CONNECTED_ITEM*> itemsOnAnchor;
361
362 for( int i = 0; i < 3; i++ )
363 {
364 itemsOnAnchor = conn->GetConnectedItemsAtAnchor( theArc, aAnchor, track_types,
365 allowedDeviation );
366 allowedDeviation /= 2;
367
368 if( itemsOnAnchor.size() == 1 )
369 break;
370 }
371
372 PCB_TRACK* retval = nullptr;
373
374 if( itemsOnAnchor.size() == 1 && itemsOnAnchor.front()->Type() == PCB_TRACE_T )
375 {
376 retval = static_cast<PCB_TRACK*>( itemsOnAnchor.front() );
377 SEG trackSeg( retval->GetStart(), retval->GetEnd() );
378
379 // Allow deviations in colinearity as defined in ADVANCED_CFG
380 if( trackSeg.AngleDegrees( aCollinearSeg )
381 > ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation )
382 {
383 retval = nullptr;
384 }
385 }
386
387 if( !retval )
388 {
389 retval = new PCB_TRACK( theArc->GetParent() );
390 retval->SetStart( (wxPoint) aAnchor );
391 retval->SetEnd( (wxPoint) aAnchor );
392 retval->SetNet( theArc->GetNet() );
393 retval->SetLayer( theArc->GetLayer() );
394 retval->SetWidth( theArc->GetWidth() );
395 retval->SetLocked( theArc->IsLocked() );
396 retval->SetFlags( IS_NEW );
397 getView()->Add( retval );
398 }
399
400 return retval;
401 };
402
403 PCB_TRACK* trackOnStart = getUniqueTrackAtAnchorCollinear( theArc->GetStart(), tanStart);
404 PCB_TRACK* trackOnEnd = getUniqueTrackAtAnchorCollinear( theArc->GetEnd(), tanEnd );
405
406 // Make copies of items to be edited
407 PCB_ARC* theArcCopy = new PCB_ARC( *theArc );
408 PCB_TRACK* trackOnStartCopy = new PCB_TRACK( *trackOnStart );
409 PCB_TRACK* trackOnEndCopy = new PCB_TRACK( *trackOnEnd );
410
411 if( trackOnStart->GetLength() != 0 )
412 {
413 tanStart.A = trackOnStart->GetStart();
414 tanStart.B = trackOnStart->GetEnd();
415 }
416
417 if( trackOnEnd->GetLength() != 0 )
418 {
419 tanEnd.A = trackOnEnd->GetStart();
420 tanEnd.B = trackOnEnd->GetEnd();
421 }
422
423 // Recalculate intersection point
424 tanIntersect = tanStart.IntersectLines( tanEnd ).get();
425
426 auto isTrackStartClosestToArcStart =
427 [&]( PCB_TRACK* aTrack ) -> bool
428 {
429 double trackStartToArcStart = GetLineLength( aTrack->GetStart(), theArc->GetStart() );
430 double trackEndToArcStart = GetLineLength( aTrack->GetEnd(), theArc->GetStart() );
431
432 return trackStartToArcStart < trackEndToArcStart;
433 };
434
435 bool isStartTrackOnStartPt = isTrackStartClosestToArcStart( trackOnStart );
436 bool isEndTrackOnStartPt = isTrackStartClosestToArcStart( trackOnEnd );
437
438 // Calculate constraints
439 //======================
440 // maxTanCircle is the circle with maximum radius that is tangent to the two adjacent straight
441 // tracks and whose tangent points are constrained within the original tracks and their
442 // projected intersection points.
443 //
444 // The cursor will be constrained first within the isosceles triangle formed by the segments
445 // cSegTanStart, cSegTanEnd and cSegChord. After that it will be constrained to be outside
446 // maxTanCircle.
447 //
448 //
449 // ____________ <-cSegTanStart
450 // / * . ' *
451 // cSegTanEnd-> / * . ' *
452 // /* . ' <-cSegChord *
453 // /. '
454 // /* *
455 //
456 // * c * <-maxTanCircle
457 //
458 // * *
459 //
460 // * *
461 // * *
462 // * *
463 //
464
465 auto getFurthestPointToTanInterstect =
466 [&]( VECTOR2I& aPointA, VECTOR2I& aPointB ) -> VECTOR2I
467 {
468 if( ( aPointA - tanIntersect ).EuclideanNorm()
469 > ( aPointB - tanIntersect ).EuclideanNorm() )
470 {
471 return aPointA;
472 }
473 else
474 {
475 return aPointB;
476 }
477 };
478
479 CIRCLE maxTanCircle;
480 VECTOR2I tanStartPoint = getFurthestPointToTanInterstect( tanStart.A, tanStart.B );
481 VECTOR2I tanEndPoint = getFurthestPointToTanInterstect( tanEnd.A, tanEnd.B );
482 VECTOR2I tempTangentPoint = tanEndPoint;
483
484 if( getFurthestPointToTanInterstect( tanStartPoint, tanEndPoint ) == tanEndPoint )
485 tempTangentPoint = tanStartPoint;
486
487 maxTanCircle.ConstructFromTanTanPt( tanStart, tanEnd, tempTangentPoint );
488 VECTOR2I maxTanPtStart = tanStart.LineProject( maxTanCircle.Center );
489 VECTOR2I maxTanPtEnd = tanEnd.LineProject( maxTanCircle.Center );
490
491 SEG cSegTanStart( maxTanPtStart, tanIntersect );
492 SEG cSegTanEnd( maxTanPtEnd, tanIntersect );
493 SEG cSegChord( maxTanPtStart, maxTanPtEnd );
494
495 int cSegTanStartSide = cSegTanStart.Side( theArc->GetMid() );
496 int cSegTanEndSide = cSegTanEnd.Side( theArc->GetMid() );
497 int cSegChordSide = cSegChord.Side( theArc->GetMid() );
498
499 bool eatFirstMouseUp = true;
500
501 // Start the tool loop
502 while( TOOL_EVENT* evt = Wait() )
503 {
504 m_cursor = controls->GetMousePosition();
505
506 // Constrain cursor within the isosceles triangle
507 if( cSegTanStartSide != cSegTanStart.Side( m_cursor )
508 || cSegTanEndSide != cSegTanEnd.Side( m_cursor )
509 || cSegChordSide != cSegChord.Side( m_cursor ) )
510 {
511 std::vector<VECTOR2I> possiblePoints;
512
513 possiblePoints.push_back( cSegTanEnd.NearestPoint( m_cursor ) );
514 possiblePoints.push_back( cSegChord.NearestPoint( m_cursor ) );
515
516 VECTOR2I closest = cSegTanStart.NearestPoint( m_cursor );
517
518 for( VECTOR2I candidate : possiblePoints )
519 {
520 if( ( candidate - m_cursor ).SquaredEuclideanNorm()
521 < ( closest - m_cursor ).SquaredEuclideanNorm() )
522 {
523 closest = candidate;
524 }
525 }
526
527 m_cursor = closest;
528 }
529
530 // Constrain cursor to be outside maxTanCircle
531 if( ( m_cursor - maxTanCircle.Center ).EuclideanNorm() < maxTanCircle.Radius )
532 m_cursor = maxTanCircle.NearestPoint( m_cursor );
533
534 controls->ForceCursorPosition( true, m_cursor );
535
536 // Calculate resulting object coordinates
537 CIRCLE circlehelper;
538 circlehelper.ConstructFromTanTanPt( cSegTanStart, cSegTanEnd, m_cursor );
539
540 VECTOR2I newCenter = circlehelper.Center;
541 VECTOR2I newStart = cSegTanStart.LineProject( newCenter );
542 VECTOR2I newEnd = cSegTanEnd.LineProject( newCenter );
543 VECTOR2I newMid = CalcArcMid( newStart, newEnd, newCenter );
544
545 // Update objects
546 theArc->SetStart( (wxPoint) newStart );
547 theArc->SetEnd( (wxPoint) newEnd );
548 theArc->SetMid( (wxPoint) newMid );
549
550 if( isStartTrackOnStartPt )
551 trackOnStart->SetStart( (wxPoint) newStart );
552 else
553 trackOnStart->SetEnd( (wxPoint) newStart );
554
555 if( isEndTrackOnStartPt )
556 trackOnEnd->SetStart( (wxPoint) newEnd );
557 else
558 trackOnEnd->SetEnd( (wxPoint) newEnd );
559
560 // Update view
561 getView()->Update( trackOnStart );
562 getView()->Update( trackOnEnd );
563 getView()->Update( theArc );
564
565 // Handle events
566 if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
567 {
568 eatFirstMouseUp = false;
569 }
570 else if( evt->IsCancelInteractive() || evt->IsActivate() )
571 {
572 restore_state = true; // Canceling the tool means that items have to be restored
573 break; // Finish
574 }
575 else if( evt->IsAction( &ACTIONS::undo ) )
576 {
577 restore_state = true; // Perform undo locally
578 break; // Finish
579 }
580 else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT )
581 || evt->IsDblClick( BUT_LEFT ) )
582 {
583 // Eat mouse-up/-click events that leaked through from the lock dialog
584 if( eatFirstMouseUp && evt->Parameter<intptr_t>() != ACTIONS::CURSOR_CLICK )
585 {
586 eatFirstMouseUp = false;
587 continue;
588 }
589
590 break; // Finish
591 }
592 }
593
594 // Ensure we only do one commit operation on each object
595 auto processTrack =
596 [&]( PCB_TRACK* aTrack, PCB_TRACK* aTrackCopy, int aMaxLengthIU ) -> bool
597 {
598 if( aTrack->IsNew() )
599 {
600 getView()->Remove( aTrack );
601
602 if( aTrack->GetLength() <= aMaxLengthIU )
603 {
604 delete aTrack;
605 delete aTrackCopy;
606 aTrack = nullptr;
607 aTrackCopy = nullptr;
608 return false;
609 }
610 else
611 {
612 m_commit->Add( aTrack );
613 delete aTrackCopy;
614 aTrackCopy = nullptr;
615 return true;
616 }
617 }
618 else if( aTrack->GetLength() <= aMaxLengthIU )
619 {
620 aTrack->SwapData( aTrackCopy ); //restore the original before notifying COMMIT
621 m_commit->Remove( aTrack );
622 delete aTrackCopy;
623 aTrackCopy = nullptr;
624 return false;
625 }
626 else
627 {
628 m_commit->Modified( aTrack, aTrackCopy );
629 }
630
631 return true;
632 };
633
634 // Amend the end points of the arc if we delete the joining tracks
635 wxPoint newStart = trackOnStart->GetStart();
636 wxPoint newEnd = trackOnEnd->GetStart();
637
638 if( isStartTrackOnStartPt )
639 newStart = trackOnStart->GetEnd();
640
641 if( isEndTrackOnStartPt )
642 newEnd = trackOnEnd->GetEnd();
643
644 int maxLengthIU = KiROUND( ADVANCED_CFG::GetCfg().m_MaxTrackLengthToKeep * IU_PER_MM );
645
646 if( !processTrack( trackOnStart, trackOnStartCopy, maxLengthIU ) )
647 theArc->SetStart( newStart );
648
649 if( !processTrack( trackOnEnd, trackOnEndCopy, maxLengthIU ) )
650 theArc->SetEnd( newEnd );
651
652 processTrack( theArc, theArcCopy, 0 ); // only delete the arc if start and end points coincide
653
654 // Should we commit?
655 if( restore_state )
656 m_commit->Revert();
657 else
658 m_commit->Push( _( "Drag Arc Track" ) );
659
660 return 0;
661 }
662
663
Move(const TOOL_EVENT & aEvent)664 int EDIT_TOOL::Move( const TOOL_EVENT& aEvent )
665 {
666 if( isRouterActive() )
667 {
668 wxBell();
669 return 0;
670 }
671
672 return doMoveSelection( aEvent );
673 }
674
675
MoveWithReference(const TOOL_EVENT & aEvent)676 int EDIT_TOOL::MoveWithReference( const TOOL_EVENT& aEvent )
677 {
678 if( isRouterActive() )
679 {
680 wxBell();
681 return 0;
682 }
683
684 return doMoveSelection( aEvent, true );
685 }
686
687
688 // Note: aEvent MUST NOT be const&; the source will get de-allocated if we go into the picker's
689 // event loop.
doMoveSelection(TOOL_EVENT aEvent,bool aPickReference)690 int EDIT_TOOL::doMoveSelection( TOOL_EVENT aEvent, bool aPickReference )
691 {
692 PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
693 KIGFX::VIEW_CONTROLS* controls = getViewControls();
694 VECTOR2I originalCursorPos = controls->GetCursorPosition();
695
696 // Be sure that there is at least one item that we can modify. If nothing was selected before,
697 // try looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection)
698 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
699 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
700 {
701 sTool->FilterCollectorForMarkers( aCollector );
702 },
703 // Prompt user regarding locked items if in board editor and in free-pad-mode (if
704 // we're not in free-pad mode we delay this until the second RequestSelection()).
705 frame()->Settings().m_AllowFreePads && !m_isFootprintEditor );
706
707 if( m_dragging || selection.Empty() )
708 return 0;
709
710 LSET item_layers = selection.GetSelectionLayers();
711 bool is_hover = selection.IsHover(); // N.B. This must be saved before the second call
712 // to RequestSelection() below
713 VECTOR2I pickedReferencePoint;
714
715 // Now filter out pads if not in free pads mode. We cannot do this in the first
716 // RequestSelection() as we need the item_layers when a pad is the selection front.
717 if( !m_isFootprintEditor && !frame()->Settings().m_AllowFreePads )
718 {
719 selection = m_selectionTool->RequestSelection(
720 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
721 {
722 sTool->FilterCollectorForMarkers( aCollector );
723 sTool->FilterCollectorForFreePads( aCollector );
724 },
725 true /* prompt user regarding locked items */ );
726 }
727
728 if( selection.Empty() )
729 return 0;
730
731 std::string tool = aEvent.GetCommandStr().get();
732 editFrame->PushTool( tool );
733
734 Activate();
735 // Must be done after Activate() so that it gets set into the correct context
736 controls->ShowCursor( true );
737 controls->SetAutoPan( true );
738
739 if( aPickReference && !pickReferencePoint( _( "Select reference point for move..." ), "", "",
740 pickedReferencePoint ) )
741 {
742 if( is_hover )
743 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
744
745 editFrame->PopTool( tool );
746 return 0;
747 }
748
749 std::vector<BOARD_ITEM*> sel_items; // All the items operated on by the move below
750 std::vector<BOARD_ITEM*> orig_items; // All the original items in the selection
751
752 for( EDA_ITEM* item : selection )
753 {
754 BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( item );
755 FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( item );
756
757 if( boardItem )
758 {
759 orig_items.push_back( boardItem );
760 sel_items.push_back( boardItem );
761 }
762
763 if( footprint )
764 {
765 for( PAD* pad : footprint->Pads() )
766 sel_items.push_back( pad );
767
768 // Clear this flag here; it will be set by the netlist updater if the footprint is new
769 // so that it was skipped in the initial connectivity update in OnNetlistChanged
770 footprint->SetAttributes( footprint->GetAttributes() & ~FP_JUST_ADDED );
771 }
772 }
773
774 bool restore_state = false;
775 VECTOR2I originalPos;
776 VECTOR2I totalMovement;
777 PCB_GRID_HELPER grid( m_toolMgr, editFrame->GetMagneticItemsSettings() );
778 TOOL_EVENT* evt = &aEvent;
779 VECTOR2I prevPos;
780
781 bool lock45 = false;
782 bool eatFirstMouseUp = true;
783 bool hasRedrawn3D = false;
784 bool allowRedraw3D = editFrame->GetDisplayOptions().m_Live3DRefresh;
785
786 // Prime the pump
787 m_toolMgr->RunAction( ACTIONS::refreshPreview );
788
789 // Main loop: keep receiving events
790 do
791 {
792 VECTOR2I movement;
793 editFrame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
794 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
795 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
796
797 if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
798 eatFirstMouseUp = false;
799
800 if( evt->IsAction( &PCB_ACTIONS::move ) || evt->IsMotion() || evt->IsDrag( BUT_LEFT )
801 || evt->IsAction( &ACTIONS::refreshPreview )
802 || evt->IsAction( &PCB_ACTIONS::moveWithReference ) )
803 {
804 if( m_dragging && evt->Category() == TC_MOUSE )
805 {
806 bool redraw3D = false;
807
808 VECTOR2I mousePos( controls->GetMousePosition() );
809
810 m_cursor = grid.BestSnapAnchor( mousePos, item_layers, sel_items );
811
812 if( controls->GetSettings().m_lastKeyboardCursorPositionValid )
813 {
814 long action = controls->GetSettings().m_lastKeyboardCursorCommand;
815
816 // The arrow keys are by definition SINGLE AXIS. Do not allow the other
817 // axis to be snapped to the grid.
818 if( action == ACTIONS::CURSOR_LEFT || action == ACTIONS::CURSOR_RIGHT )
819 m_cursor.y = prevPos.y;
820 else if( action == ACTIONS::CURSOR_UP || action == ACTIONS::CURSOR_DOWN )
821 m_cursor.x = prevPos.x;
822 }
823
824 if( !selection.HasReferencePoint() )
825 originalPos = m_cursor;
826
827 if( lock45 )
828 {
829 VECTOR2I moveVector = m_cursor - originalPos;
830 m_cursor = originalPos + GetVectorSnapped45( moveVector );
831 }
832
833 controls->ForceCursorPosition( true, m_cursor );
834 selection.SetReferencePoint( m_cursor );
835
836 movement = m_cursor - prevPos;
837 prevPos = m_cursor;
838 totalMovement += movement;
839
840 // Drag items to the current cursor position
841 for( EDA_ITEM* item : sel_items )
842 {
843 // Don't double move footprint pads, fields, etc.
844 //
845 // For PCB_GROUP_T, we make sure the selection includes only the top level
846 // group and not its descendants.
847 if( !item->GetParent() || !item->GetParent()->IsSelected() )
848 static_cast<BOARD_ITEM*>( item )->Move( movement );
849
850 if( !redraw3D && item->Type() == PCB_FOOTPRINT_T )
851 redraw3D = true;
852 }
853
854 if( redraw3D && allowRedraw3D )
855 {
856 editFrame->Update3DView( false, true );
857 hasRedrawn3D = true;
858 }
859
860 m_toolMgr->PostEvent( EVENTS::SelectedItemsMoved );
861 }
862 else if( !m_dragging && !evt->IsAction( &ACTIONS::refreshPreview ) )
863 {
864 // Prepare to start dragging
865 if( !( evt->IsAction( &PCB_ACTIONS::move )
866 || evt->IsAction( &PCB_ACTIONS::moveWithReference ) )
867 && ( editFrame->Settings().m_TrackDragAction != TRACK_DRAG_ACTION::MOVE ) )
868 {
869 if( invokeInlineRouter( PNS::DM_ANY ) )
870 break;
871 }
872
873 m_dragging = true;
874
875 // When editing footprints, all items have the same parent
876 if( IsFootprintEditor() )
877 {
878 m_commit->Modify( selection.Front() );
879 }
880 else
881 {
882 // Save items, so changes can be undone
883 for( EDA_ITEM* item : selection )
884 {
885 // Don't double move footprint pads, fields, etc.
886 //
887 // For PCB_GROUP_T, the parent is the board.
888 if( item->GetParent() && item->GetParent()->IsSelected() )
889 continue;
890
891 m_commit->Modify( item );
892
893 // If moving a group, record position of all the descendants for undo
894 if( item->Type() == PCB_GROUP_T )
895 {
896 PCB_GROUP* group = static_cast<PCB_GROUP*>( item );
897 group->RunOnDescendants( [&]( BOARD_ITEM* bItem )
898 {
899 m_commit->Modify( bItem );
900 });
901 }
902 }
903 }
904
905 editFrame->UndoRedoBlock( true );
906 m_cursor = controls->GetCursorPosition();
907
908 if( selection.HasReferencePoint() )
909 {
910 // start moving with the reference point attached to the cursor
911 grid.SetAuxAxes( false );
912
913 if( lock45 )
914 {
915 VECTOR2I moveVector = m_cursor - originalPos;
916 m_cursor = originalPos + GetVectorSnapped45( moveVector );
917 }
918
919 movement = m_cursor - selection.GetReferencePoint();
920
921 // Drag items to the current cursor position
922 for( EDA_ITEM* item : selection )
923 {
924 // Don't double move footprint pads, fields, etc.
925 if( item->GetParent() && item->GetParent()->IsSelected() )
926 continue;
927
928 static_cast<BOARD_ITEM*>( item )->Move( movement );
929 }
930
931 selection.SetReferencePoint( m_cursor );
932 }
933 else
934 {
935 std::vector<BOARD_ITEM*> items;
936
937 for( EDA_ITEM* item : selection )
938 items.push_back( static_cast<BOARD_ITEM*>( item ) );
939
940 m_cursor = grid.BestDragOrigin( originalCursorPos, items );
941
942 // Set the current cursor position to the first dragged item origin, so the
943 // movement vector could be computed later
944 if( aPickReference )
945 {
946 selection.SetReferencePoint( pickedReferencePoint );
947 controls->ForceCursorPosition( true, pickedReferencePoint );
948 m_cursor = pickedReferencePoint;
949 }
950 else
951 {
952 // Check if user wants to warp the mouse to origin of moved object
953 if( !editFrame->GetMoveWarpsCursor() )
954 m_cursor = originalCursorPos; // No, so use original mouse pos instead
955
956 selection.SetReferencePoint( m_cursor );
957 grid.SetAuxAxes( true, m_cursor );
958 }
959
960 originalPos = m_cursor;
961
962 }
963
964 controls->SetCursorPosition( m_cursor, false );
965
966 prevPos = m_cursor;
967 controls->SetAutoPan( true );
968 m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
969 }
970
971 m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false,
972 new VECTOR2I( movement ) );
973 }
974 else if( evt->IsCancelInteractive() || evt->IsActivate() )
975 {
976 if( m_dragging && evt->IsCancelInteractive() )
977 evt->SetPassEvent( false );
978
979 restore_state = true; // Canceling the tool means that items have to be restored
980 break; // Finish
981 }
982 else if( evt->IsAction( &ACTIONS::undo ) )
983 {
984 restore_state = true; // Perform undo locally
985 break; // Finish
986 }
987 else if( evt->IsAction( &ACTIONS::doDelete ) || evt->IsAction( &ACTIONS::cut ) )
988 {
989 // Dispatch TOOL_ACTIONs
990 evt->SetPassEvent();
991 break; // finish -- there is no further processing for removed items
992 }
993 else if( evt->IsAction( &ACTIONS::duplicate ) )
994 {
995 evt->SetPassEvent();
996 break; // finish -- Duplicate tool will start a new Move with the dup'ed items
997 }
998 else if( evt->IsAction( &PCB_ACTIONS::rotateCw )
999 || evt->IsAction( &PCB_ACTIONS::rotateCcw )
1000 || evt->IsAction( &PCB_ACTIONS::flip )
1001 || evt->IsAction( &PCB_ACTIONS::mirror ) )
1002 {
1003 eatFirstMouseUp = false;
1004 evt->SetPassEvent();
1005 }
1006 else if( evt->IsAction( &PCB_ACTIONS::moveExact ) )
1007 {
1008 // Reset positions so the Move Exactly is from the start.
1009 for( EDA_ITEM* item : selection )
1010 {
1011 BOARD_ITEM* i = static_cast<BOARD_ITEM*>( item );
1012 i->Move( -totalMovement );
1013 }
1014
1015 break; // finish -- we moved exactly, so we are finished
1016 }
1017 else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) )
1018 {
1019 // Eat mouse-up/-click events that leaked through from the lock dialog
1020 if( eatFirstMouseUp && evt->Parameter<intptr_t>() != ACTIONS::CURSOR_CLICK )
1021 {
1022 eatFirstMouseUp = false;
1023 continue;
1024 }
1025
1026 break; // finish
1027 }
1028 else if( evt->IsAction( &PCB_ACTIONS::toggle45 ) )
1029 {
1030 lock45 = !lock45;
1031 evt->SetPassEvent( false );
1032 }
1033 else
1034 {
1035 evt->SetPassEvent();
1036 }
1037
1038 } while( ( evt = Wait() ) ); // Assignment (instead of equality test) is intentional
1039
1040 controls->ForceCursorPosition( false );
1041 controls->ShowCursor( false );
1042 controls->SetAutoPan( false );
1043
1044 m_dragging = false;
1045 editFrame->UndoRedoBlock( false );
1046
1047 if( hasRedrawn3D && restore_state )
1048 editFrame->Update3DView( false, true );
1049
1050 // Discard reference point when selection is "dropped" onto the board
1051 selection.ClearReferencePoint();
1052
1053 // TODO: there's an encapsulation leak here: this commit often has more than just the move
1054 // in it; for instance it might have a paste, append board, etc. as well.
1055 if( restore_state )
1056 m_commit->Revert();
1057 else
1058 m_commit->Push( _( "Drag" ) );
1059
1060 // Remove the dynamic ratsnest from the screen
1061 m_toolMgr->RunAction( PCB_ACTIONS::hideDynamicRatsnest, true );
1062
1063 // Unselect all items to update flags
1064 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1065
1066 // Reselect items if they were already selected and we completed the move
1067 if( !is_hover && !restore_state )
1068 m_toolMgr->RunAction( PCB_ACTIONS::selectItems, true, &orig_items );
1069
1070 editFrame->PopTool( tool );
1071
1072 return restore_state ? -1 : 0;
1073 }
1074
1075
ChangeTrackWidth(const TOOL_EVENT & aEvent)1076 int EDIT_TOOL::ChangeTrackWidth( const TOOL_EVENT& aEvent )
1077 {
1078 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1079 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1080 {
1081 // Iterate from the back so we don't have to worry about removals.
1082 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1083 {
1084 BOARD_ITEM* item = aCollector[ i ];
1085
1086 if( !dynamic_cast<PCB_TRACK*>( item ) )
1087 aCollector.Remove( item );
1088 }
1089 },
1090 true /* prompt user regarding locked items */ );
1091
1092 for( EDA_ITEM* item : selection )
1093 {
1094 if( item->Type() == PCB_VIA_T )
1095 {
1096 PCB_VIA* via = static_cast<PCB_VIA*>( item );
1097
1098 m_commit->Modify( via );
1099
1100 int new_width;
1101 int new_drill;
1102
1103 if( via->GetViaType() == VIATYPE::MICROVIA )
1104 {
1105 NETCLASS* netClass = via->GetNetClass();
1106
1107 new_width = netClass->GetuViaDiameter();
1108 new_drill = netClass->GetuViaDrill();
1109 }
1110 else
1111 {
1112 new_width = board()->GetDesignSettings().GetCurrentViaSize();
1113 new_drill = board()->GetDesignSettings().GetCurrentViaDrill();
1114 }
1115
1116 via->SetDrill( new_drill );
1117 via->SetWidth( new_width );
1118 }
1119 else if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
1120 {
1121 PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item );
1122
1123 wxCHECK( track, 0 );
1124
1125 m_commit->Modify( track );
1126
1127 int new_width = board()->GetDesignSettings().GetCurrentTrackWidth();
1128 track->SetWidth( new_width );
1129 }
1130 }
1131
1132 m_commit->Push( _("Edit track width/via size") );
1133
1134 if( selection.IsHover() )
1135 {
1136 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1137
1138 // Notify other tools of the changes -- This updates the visual ratsnest
1139 m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1140 }
1141
1142 return 0;
1143 }
1144
1145
FilletTracks(const TOOL_EVENT & aEvent)1146 int EDIT_TOOL::FilletTracks( const TOOL_EVENT& aEvent )
1147 {
1148 // Store last used fillet radius to allow pressing "enter" if repeat fillet is required
1149 static long long filletRadiusIU = 0;
1150
1151 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1152 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1153 {
1154 // Iterate from the back so we don't have to worry about removals.
1155 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1156 {
1157 BOARD_ITEM* item = aCollector[i];
1158
1159 if( !dynamic_cast<PCB_TRACK*>( item ) )
1160 aCollector.Remove( item );
1161 }
1162 },
1163 true /* prompt user regarding locked items */ );
1164
1165 if( selection.Size() < 2 )
1166 {
1167 frame()->ShowInfoBarMsg( _( "At least two straight track segments must be selected." ) );
1168 return 0;
1169 }
1170
1171 WX_UNIT_ENTRY_DIALOG dia( frame(), _( "Enter fillet radius:" ), _( "Fillet Tracks" ),
1172 filletRadiusIU );
1173
1174 if( dia.ShowModal() == wxID_CANCEL )
1175 return 0;
1176
1177 filletRadiusIU = dia.GetValue();
1178
1179 if( filletRadiusIU == 0 )
1180 {
1181 frame()->ShowInfoBarMsg( _( "A radius of zero was entered.\n"
1182 "The fillet operation was not performed." ) );
1183 return 0;
1184 }
1185
1186 struct FILLET_OP
1187 {
1188 PCB_TRACK* t1;
1189 PCB_TRACK* t2;
1190 // Start point of track is modified after PCB_ARC is added, otherwise the end point:
1191 bool t1Start = true;
1192 bool t2Start = true;
1193 };
1194
1195 std::vector<FILLET_OP> filletOperations;
1196 KICAD_T track_types[] = { PCB_PAD_T, PCB_VIA_T, PCB_TRACE_T, PCB_ARC_T, EOT };
1197 bool operationPerformedOnAtLeastOne = false;
1198 bool didOneAttemptFail = false;
1199 std::set<PCB_TRACK*> processedTracks;
1200
1201 for( auto it = selection.begin(); it != selection.end(); it++ )
1202 {
1203 PCB_TRACK* track = dyn_cast<PCB_TRACK*>( *it );
1204
1205 if( !track || track->Type() != PCB_TRACE_T || track->GetLength() == 0 )
1206 {
1207 continue;
1208 }
1209
1210 auto processFilletOp =
1211 [&]( bool aStartPoint )
1212 {
1213 wxPoint anchor = ( aStartPoint ) ? track->GetStart() : track->GetEnd();
1214 auto connectivity = board()->GetConnectivity();
1215 auto itemsOnAnchor = connectivity->GetConnectedItemsAtAnchor( track, anchor,
1216 track_types );
1217
1218 if( itemsOnAnchor.size() > 0
1219 && selection.Contains( itemsOnAnchor.at( 0 ) )
1220 && itemsOnAnchor.at( 0 )->Type() == PCB_TRACE_T )
1221 {
1222 PCB_TRACK* trackOther = dyn_cast<PCB_TRACK*>( itemsOnAnchor.at( 0 ) );
1223
1224 // Make sure we don't fillet the same pair of tracks twice
1225 if( processedTracks.find( trackOther ) == processedTracks.end() )
1226 {
1227 if( itemsOnAnchor.size() == 1 )
1228 {
1229 FILLET_OP filletOp;
1230 filletOp.t1 = track;
1231 filletOp.t2 = trackOther;
1232 filletOp.t1Start = aStartPoint;
1233 filletOp.t2Start = track->IsPointOnEnds( filletOp.t2->GetStart() );
1234 filletOperations.push_back( filletOp );
1235 }
1236 else
1237 {
1238 // User requested to fillet these two tracks but not possible as
1239 // there are other elements connected at that point
1240 didOneAttemptFail = true;
1241 }
1242 }
1243 }
1244 };
1245
1246 processFilletOp( true ); // on the start point of track
1247 processFilletOp( false ); // on the end point of track
1248
1249 processedTracks.insert( track );
1250 }
1251
1252 std::vector<BOARD_ITEM*> itemsToAddToSelection;
1253
1254 for( FILLET_OP filletOp : filletOperations )
1255 {
1256 PCB_TRACK* track1 = filletOp.t1;
1257 PCB_TRACK* track2 = filletOp.t2;
1258
1259 bool trackOnStart = track1->IsPointOnEnds( track2->GetStart() );
1260 bool trackOnEnd = track1->IsPointOnEnds( track2->GetEnd() );
1261
1262 if( trackOnStart && trackOnEnd )
1263 continue; // Ignore duplicate tracks
1264
1265 if( ( trackOnStart || trackOnEnd ) && track1->GetLayer() == track2->GetLayer() )
1266 {
1267 SEG t1Seg( track1->GetStart(), track1->GetEnd() );
1268 SEG t2Seg( track2->GetStart(), track2->GetEnd() );
1269
1270 if( t1Seg.ApproxCollinear( t2Seg ) )
1271 continue;
1272
1273 SHAPE_ARC sArc( t1Seg, t2Seg, filletRadiusIU );
1274
1275 wxPoint t1newPoint, t2newPoint;
1276
1277 auto setIfPointOnSeg =
1278 []( wxPoint& aPointToSet, SEG aSegment, VECTOR2I aVecToTest )
1279 {
1280 VECTOR2I segToVec = aSegment.NearestPoint( aVecToTest ) - aVecToTest;
1281
1282 // Find out if we are on the segment (minimum precision)
1283 if( segToVec.EuclideanNorm() < SHAPE_ARC::MIN_PRECISION_IU )
1284 {
1285 aPointToSet.x = aVecToTest.x;
1286 aPointToSet.y = aVecToTest.y;
1287 return true;
1288 }
1289
1290 return false;
1291 };
1292
1293 //Do not draw a fillet if the end points of the arc are not within the track segments
1294 if( !setIfPointOnSeg( t1newPoint, t1Seg, sArc.GetP0() )
1295 && !setIfPointOnSeg( t2newPoint, t2Seg, sArc.GetP0() ) )
1296 {
1297 didOneAttemptFail = true;
1298 continue;
1299 }
1300
1301 if( !setIfPointOnSeg( t1newPoint, t1Seg, sArc.GetP1() )
1302 && !setIfPointOnSeg( t2newPoint, t2Seg, sArc.GetP1() ) )
1303 {
1304 didOneAttemptFail = true;
1305 continue;
1306 }
1307
1308 PCB_ARC* tArc = new PCB_ARC( frame()->GetBoard(), &sArc );
1309 tArc->SetLayer( track1->GetLayer() );
1310 tArc->SetWidth( track1->GetWidth() );
1311 tArc->SetNet( track1->GetNet() );
1312 tArc->SetLocked( track1->IsLocked() );
1313 m_commit->Add( tArc );
1314 itemsToAddToSelection.push_back( tArc );
1315
1316 m_commit->Modify( track1 );
1317 m_commit->Modify( track2 );
1318
1319 if( filletOp.t1Start )
1320 track1->SetStart( t1newPoint );
1321 else
1322 track1->SetEnd( t1newPoint );
1323
1324 if( filletOp.t2Start )
1325 track2->SetStart( t2newPoint );
1326 else
1327 track2->SetEnd( t2newPoint );
1328
1329 operationPerformedOnAtLeastOne = true;
1330 }
1331 }
1332
1333 m_commit->Push( _( "Fillet Tracks" ) );
1334
1335 //select the newly created arcs
1336 for( BOARD_ITEM* item : itemsToAddToSelection )
1337 m_selectionTool->AddItemToSel( item );
1338
1339 if( !operationPerformedOnAtLeastOne )
1340 frame()->ShowInfoBarMsg( _( "Unable to fillet the selected track segments." ) );
1341 else if( didOneAttemptFail )
1342 frame()->ShowInfoBarMsg( _( "Some of the track segments could not be filleted." ) );
1343
1344 return 0;
1345 }
1346
1347
Properties(const TOOL_EVENT & aEvent)1348 int EDIT_TOOL::Properties( const TOOL_EVENT& aEvent )
1349 {
1350 PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
1351 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1352 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1353 {
1354 } );
1355
1356 // Tracks & vias are treated in a special way:
1357 if( ( SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) )( selection ) )
1358 {
1359 DIALOG_TRACK_VIA_PROPERTIES dlg( editFrame, selection, *m_commit );
1360 dlg.ShowQuasiModal(); // QuasiModal required for NET_SELECTOR
1361 }
1362 else if( selection.Size() == 1 )
1363 {
1364 // Display properties dialog
1365 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
1366
1367 // Do not handle undo buffer, it is done by the properties dialogs
1368 editFrame->OnEditItemRequest( item );
1369
1370 // Notify other tools of the changes
1371 m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1372 }
1373 else if( selection.Size() == 0 && getView()->IsLayerVisible( LAYER_DRAWINGSHEET ) )
1374 {
1375 DS_PROXY_VIEW_ITEM* ds = editFrame->GetCanvas()->GetDrawingSheet();
1376 VECTOR2D cursorPos = getViewControls()->GetCursorPosition( false );
1377
1378 if( ds && ds->HitTestDrawingSheetItems( getView(), (wxPoint) cursorPos ) )
1379 m_toolMgr->RunAction( ACTIONS::pageSettings );
1380 else
1381 m_toolMgr->RunAction( PCB_ACTIONS::footprintProperties, true );
1382 }
1383
1384 if( selection.IsHover() )
1385 {
1386 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1387 }
1388 else
1389 {
1390 // Check for items becoming invisible and drop them from the selection.
1391
1392 LSET visible = editFrame->GetBoard()->GetVisibleLayers();
1393
1394 for( EDA_ITEM* eda_item : selection )
1395 {
1396 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( eda_item );
1397
1398 if( !( item->GetLayerSet() & visible ).any() )
1399 m_selectionTool->RemoveItemFromSel( item );
1400 }
1401 }
1402
1403 return 0;
1404 }
1405
1406
Rotate(const TOOL_EVENT & aEvent)1407 int EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent )
1408 {
1409 if( isRouterActive() )
1410 {
1411 wxBell();
1412 return 0;
1413 }
1414
1415 PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
1416
1417 // Be sure that there is at least one item that we can modify. If nothing was selected before,
1418 // try looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection)
1419 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1420 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1421 {
1422 sTool->FilterCollectorForMarkers( aCollector );
1423 },
1424 // Prompt user regarding locked items if in board editor and in free-pad-mode (if
1425 // we're not in free-pad mode we delay this until the second RequestSelection()).
1426 frame()->Settings().m_AllowFreePads && !m_isFootprintEditor );
1427
1428 if( selection.Empty() )
1429 return 0;
1430
1431 OPT<VECTOR2I> oldRefPt = boost::make_optional<VECTOR2I>( false, VECTOR2I( 0, 0 ) );
1432
1433 if( selection.HasReferencePoint() )
1434 oldRefPt = selection.GetReferencePoint();
1435
1436 // Now filter out pads if not in free pads mode. We cannot do this in the first
1437 // RequestSelection() as we need the reference point when a pad is the selection front.
1438 if( !m_isFootprintEditor && !frame()->Settings().m_AllowFreePads )
1439 {
1440 selection = m_selectionTool->RequestSelection(
1441 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1442 {
1443 sTool->FilterCollectorForMarkers( aCollector );
1444 sTool->FilterCollectorForHierarchy( aCollector, true );
1445 sTool->FilterCollectorForFreePads( aCollector );
1446 },
1447 true /* prompt user regarding locked items */ );
1448 }
1449
1450 updateModificationPoint( selection );
1451
1452 VECTOR2I refPt = selection.GetReferencePoint();
1453 const int rotateAngle = TOOL_EVT_UTILS::GetEventRotationAngle( *editFrame, aEvent );
1454
1455 // When editing footprints, all items have the same parent
1456 if( IsFootprintEditor() )
1457 m_commit->Modify( selection.Front() );
1458
1459 for( EDA_ITEM* item : selection )
1460 {
1461 if( !item->IsNew() && !IsFootprintEditor() )
1462 {
1463 m_commit->Modify( item );
1464
1465 // If rotating a group, record position of all the descendants for undo
1466 if( item->Type() == PCB_GROUP_T )
1467 {
1468 static_cast<PCB_GROUP*>( item )->RunOnDescendants(
1469 [&]( BOARD_ITEM* bItem )
1470 {
1471 m_commit->Modify( bItem );
1472 });
1473 }
1474 }
1475
1476 static_cast<BOARD_ITEM*>( item )->Rotate( refPt, rotateAngle );
1477 }
1478
1479 if( !m_dragging )
1480 m_commit->Push( _( "Rotate" ) );
1481
1482 if( selection.IsHover() && !m_dragging )
1483 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1484
1485 m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1486
1487 if( m_dragging )
1488 m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
1489
1490 // Restore the old reference so any mouse dragging that occurs doesn't make the selection jump
1491 // to this now invalid reference
1492 if( oldRefPt )
1493 selection.SetReferencePoint( *oldRefPt );
1494 else
1495 selection.ClearReferencePoint();
1496
1497 return 0;
1498 }
1499
1500
1501 /**
1502 * Mirror a point about the vertical axis passing through another point.
1503 */
mirrorPointX(const wxPoint & aPoint,const wxPoint & aMirrorPoint)1504 static wxPoint mirrorPointX( const wxPoint& aPoint, const wxPoint& aMirrorPoint )
1505 {
1506 wxPoint mirrored = aPoint;
1507
1508 mirrored.x -= aMirrorPoint.x;
1509 mirrored.x = -mirrored.x;
1510 mirrored.x += aMirrorPoint.x;
1511
1512 return mirrored;
1513 }
1514
1515
1516 /**
1517 * Mirror a pad in the vertical axis passing through a point (mirror left to right).
1518 */
mirrorPadX(PAD & aPad,const wxPoint & aMirrorPoint)1519 static void mirrorPadX( PAD& aPad, const wxPoint& aMirrorPoint )
1520 {
1521 if( aPad.GetShape() == PAD_SHAPE::CUSTOM )
1522 aPad.FlipPrimitives( true ); // mirror primitives left to right
1523
1524 wxPoint tmpPt = mirrorPointX( aPad.GetPosition(), aMirrorPoint );
1525 aPad.SetPosition( tmpPt );
1526
1527 aPad.SetX0( aPad.GetPosition().x );
1528
1529 tmpPt = aPad.GetOffset();
1530 tmpPt.x = -tmpPt.x;
1531 aPad.SetOffset( tmpPt );
1532
1533 auto tmpz = aPad.GetDelta();
1534 tmpz.x = -tmpz.x;
1535 aPad.SetDelta( tmpz );
1536
1537 aPad.SetOrientation( -aPad.GetOrientation() );
1538 }
1539
1540
Mirror(const TOOL_EVENT & aEvent)1541 int EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent )
1542 {
1543 if( isRouterActive() )
1544 {
1545 wxBell();
1546 return 0;
1547 }
1548
1549 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1550 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1551 {
1552 sTool->FilterCollectorForMarkers( aCollector );
1553 sTool->FilterCollectorForHierarchy( aCollector, true );
1554 sTool->FilterCollectorForFreePads( aCollector );
1555 },
1556 !m_dragging /* prompt user regarding locked items */ );
1557
1558 if( selection.Empty() )
1559 return 0;
1560
1561 updateModificationPoint( selection );
1562 auto refPoint = selection.GetReferencePoint();
1563 wxPoint mirrorPoint( refPoint.x, refPoint.y );
1564
1565 // When editing footprints, all items have the same parent
1566 if( IsFootprintEditor() )
1567 m_commit->Modify( selection.Front() );
1568
1569 for( EDA_ITEM* item : selection )
1570 {
1571 // only modify items we can mirror
1572 switch( item->Type() )
1573 {
1574 case PCB_FP_SHAPE_T:
1575 case PCB_FP_TEXT_T:
1576 case PCB_FP_ZONE_T:
1577 case PCB_PAD_T:
1578 // Only create undo entry for items on the board
1579 if( !item->IsNew() && !IsFootprintEditor() )
1580 m_commit->Modify( item );
1581
1582 break;
1583 default:
1584 continue;
1585 }
1586
1587 // modify each object as necessary
1588 switch( item->Type() )
1589 {
1590 case PCB_FP_SHAPE_T:
1591 {
1592 FP_SHAPE* shape = static_cast<FP_SHAPE*>( item );
1593 shape->Mirror( mirrorPoint, false );
1594 break;
1595 }
1596
1597 case PCB_FP_ZONE_T:
1598 {
1599 FP_ZONE* zone = static_cast<FP_ZONE*>( item );
1600 zone->Mirror( mirrorPoint, false );
1601 break;
1602 }
1603
1604 case PCB_FP_TEXT_T:
1605 {
1606 FP_TEXT* text = static_cast<FP_TEXT*>( item );
1607 text->Mirror( mirrorPoint, false );
1608 break;
1609 }
1610
1611 case PCB_PAD_T:
1612 {
1613 PAD* pad = static_cast<PAD*>( item );
1614 mirrorPadX( *pad, mirrorPoint );
1615 break;
1616 }
1617
1618 default:
1619 // it's likely the commit object is wrong if you get here
1620 // Unsure if PCB_GROUP_T needs special attention here.
1621 assert( false );
1622 break;
1623 }
1624 }
1625
1626 if( !m_dragging )
1627 m_commit->Push( _( "Mirror" ) );
1628
1629 if( selection.IsHover() && !m_dragging )
1630 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1631
1632 m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1633
1634 if( m_dragging )
1635 m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
1636
1637 return 0;
1638 }
1639
1640
Flip(const TOOL_EVENT & aEvent)1641 int EDIT_TOOL::Flip( const TOOL_EVENT& aEvent )
1642 {
1643 if( isRouterActive() )
1644 {
1645 wxBell();
1646 return 0;
1647 }
1648
1649 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1650 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1651 {
1652 sTool->FilterCollectorForMarkers( aCollector );
1653 sTool->FilterCollectorForHierarchy( aCollector, true );
1654 sTool->FilterCollectorForFreePads( aCollector );
1655 },
1656 !m_dragging /* prompt user regarding locked items */ );
1657
1658 if( selection.Empty() )
1659 return 0;
1660
1661 OPT<VECTOR2I> oldRefPt = boost::make_optional<VECTOR2I>( false, VECTOR2I( 0, 0 ) );
1662
1663 if( selection.HasReferencePoint() )
1664 oldRefPt = selection.GetReferencePoint();
1665
1666 updateModificationPoint( selection );
1667
1668 // Flip around the anchor for footprints, and the bounding box center for board items
1669 VECTOR2I refPt = IsFootprintEditor() ? VECTOR2I( 0, 0 ) : selection.GetCenter();
1670
1671 // If only one item selected, flip around the selection or item anchor point (instead
1672 // of the bounding box center) to avoid moving the item anchor
1673 if( selection.GetSize() == 1 )
1674 {
1675 if( m_dragging && selection.HasReferencePoint() )
1676 refPt = selection.GetReferencePoint();
1677 else
1678 refPt = static_cast<BOARD_ITEM*>( selection.GetItem( 0 ) )->GetPosition();
1679 }
1680
1681 bool leftRight = frame()->Settings().m_FlipLeftRight;
1682
1683 // When editing footprints, all items have the same parent
1684 if( IsFootprintEditor() )
1685 m_commit->Modify( selection.Front() );
1686
1687 for( EDA_ITEM* item : selection )
1688 {
1689 if( !item->IsNew() && !IsFootprintEditor() )
1690 m_commit->Modify( item );
1691
1692 if( item->Type() == PCB_GROUP_T )
1693 {
1694 static_cast<PCB_GROUP*>( item )->RunOnDescendants( [&]( BOARD_ITEM* bItem )
1695 {
1696 m_commit->Modify( bItem );
1697 });
1698 }
1699
1700 static_cast<BOARD_ITEM*>( item )->Flip( refPt, leftRight );
1701 }
1702
1703 if( !m_dragging )
1704 m_commit->Push( _( "Change Side / Flip" ) );
1705
1706 if( selection.IsHover() && !m_dragging )
1707 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1708
1709 m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1710
1711 if( m_dragging )
1712 m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
1713
1714 // Restore the old reference so any mouse dragging that occurs doesn't make the selection jump
1715 // to this now invalid reference
1716 if( oldRefPt )
1717 selection.SetReferencePoint( *oldRefPt );
1718 else
1719 selection.ClearReferencePoint();
1720
1721 return 0;
1722 }
1723
1724
Remove(const TOOL_EVENT & aEvent)1725 int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent )
1726 {
1727 if( isRouterActive() )
1728 {
1729 m_toolMgr->RunAction( PCB_ACTIONS::routerUndoLastSegment, true );
1730 return 0;
1731 }
1732
1733 std::vector<BOARD_ITEM*> lockedItems;
1734 Activate();
1735
1736 // get a copy instead of reference (as we're going to clear the selection before removing items)
1737 PCB_SELECTION selectionCopy;
1738 bool isCut = aEvent.Parameter<PCB_ACTIONS::REMOVE_FLAGS>() == PCB_ACTIONS::REMOVE_FLAGS::CUT;
1739 bool isAlt = aEvent.Parameter<PCB_ACTIONS::REMOVE_FLAGS>() == PCB_ACTIONS::REMOVE_FLAGS::ALT;
1740
1741 // If we are in a "Cut" operation, then the copied selection exists already and we want to
1742 // delete exactly that; no more, no fewer. Any filtering for locked items must be done in
1743 // the copyToClipboard() routine.
1744 if( isCut )
1745 {
1746 selectionCopy = m_selectionTool->GetSelection();
1747 }
1748 else
1749 {
1750 // When not in free-pad mode we normally auto-promote selected pads to their parent
1751 // footprints. But this is probably a little too dangerous for a destructive operation,
1752 // so we just do the promotion but not the deletion (allowing for a second delete to do
1753 // it if that's what the user wanted).
1754 selectionCopy = m_selectionTool->RequestSelection(
1755 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1756 {
1757 } );
1758
1759 size_t beforeFPCount = selectionCopy.CountType( PCB_FOOTPRINT_T );
1760
1761 m_selectionTool->RequestSelection(
1762 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1763 {
1764 sTool->FilterCollectorForFreePads( aCollector );
1765 } );
1766
1767 if( !selectionCopy.IsHover()
1768 && m_selectionTool->GetSelection().CountType( PCB_FOOTPRINT_T ) > beforeFPCount )
1769 {
1770 wxBell();
1771 canvas()->Refresh();
1772 return 0;
1773 }
1774
1775 // In "alternative" mode, we expand selected track items to their full connection.
1776 if( isAlt && ( selectionCopy.HasType( PCB_TRACE_T ) || selectionCopy.HasType( PCB_VIA_T ) ) )
1777 {
1778 m_toolMgr->RunAction( PCB_ACTIONS::selectConnection, true );
1779 }
1780
1781 // Finally run RequestSelection() one more time to find out what user wants to do about
1782 // locked objects.
1783 selectionCopy = m_selectionTool->RequestSelection(
1784 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1785 {
1786 sTool->FilterCollectorForFreePads( aCollector );
1787 },
1788 true /* prompt user regarding locked items */ );
1789 }
1790
1791 // As we are about to remove items, they have to be removed from the selection first
1792 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1793
1794 for( EDA_ITEM* item : selectionCopy )
1795 {
1796 PCB_GROUP* parentGroup = static_cast<BOARD_ITEM*>( item )->GetParentGroup();
1797
1798 if( parentGroup )
1799 {
1800 m_commit->Modify( parentGroup );
1801 parentGroup->RemoveItem( static_cast<BOARD_ITEM*>( item ) );
1802 }
1803
1804 switch( item->Type() )
1805 {
1806 case PCB_FP_TEXT_T:
1807 {
1808 FP_TEXT* text = static_cast<FP_TEXT*>( item );
1809 FOOTPRINT* parent = static_cast<FOOTPRINT*>( item->GetParent() );
1810
1811 if( text->GetType() == FP_TEXT::TEXT_is_DIVERS )
1812 {
1813 m_commit->Modify( parent );
1814 getView()->Remove( text );
1815 parent->Remove( text );
1816 }
1817
1818 break;
1819 }
1820
1821 case PCB_PAD_T:
1822 if( IsFootprintEditor() || frame()->Settings().m_AllowFreePads )
1823 {
1824 PAD* pad = static_cast<PAD*>( item );
1825 FOOTPRINT* parent = static_cast<FOOTPRINT*>( item->GetParent() );
1826
1827 m_commit->Modify( parent );
1828 getView()->Remove( pad );
1829 parent->Remove( pad );
1830 }
1831
1832 break;
1833
1834 case PCB_FP_ZONE_T:
1835 {
1836 FP_ZONE* zone = static_cast<FP_ZONE*>( item );
1837 FOOTPRINT* parent = static_cast<FOOTPRINT*>( item->GetParent() );
1838
1839 m_commit->Modify( parent );
1840 getView()->Remove( zone );
1841 parent->Remove( zone );
1842 break;
1843 }
1844
1845 case PCB_ZONE_T:
1846 // We process the zones special so that cutouts can be deleted when the delete tool
1847 // is called from inside a cutout when the zone is selected.
1848 {
1849 // Only interact with cutouts when deleting and a single item is selected
1850 if( !isCut && selectionCopy.GetSize() == 1 )
1851 {
1852 VECTOR2I curPos = getViewControls()->GetCursorPosition();
1853 ZONE* zone = static_cast<ZONE*>( item );
1854
1855 int outlineIdx, holeIdx;
1856
1857 if( zone->HitTestCutout( curPos, &outlineIdx, &holeIdx ) )
1858 {
1859 // Remove the cutout
1860 m_commit->Modify( zone );
1861 zone->RemoveCutout( outlineIdx, holeIdx );
1862 zone->UnFill();
1863
1864 // TODO Refill zone when KiCad supports auto re-fill
1865
1866 // Update the display
1867 zone->HatchBorder();
1868 canvas()->Refresh();
1869
1870 // Restore the selection on the original zone
1871 m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, zone );
1872
1873 break;
1874 }
1875 }
1876
1877 // Remove the entire zone otherwise
1878 m_commit->Remove( item );
1879 break;
1880 }
1881
1882 case PCB_GROUP_T:
1883 {
1884 PCB_GROUP* group = static_cast<PCB_GROUP*>( item );
1885
1886 auto removeItem =
1887 [&]( BOARD_ITEM* bItem )
1888 {
1889 if( bItem->GetParent() && bItem->GetParent()->Type() == PCB_FOOTPRINT_T )
1890 {
1891 // Silently ignore delete of Reference or Value if they happen to be
1892 // in group.
1893 if( bItem->Type() == PCB_FP_TEXT_T )
1894 {
1895 FP_TEXT* textItem = static_cast<FP_TEXT*>( bItem );
1896
1897 if( textItem->GetType() != FP_TEXT::TEXT_is_DIVERS )
1898 return;
1899 }
1900 else if( bItem->Type() == PCB_PAD_T )
1901 {
1902 if( !IsFootprintEditor() && !frame()->Settings().m_AllowFreePads )
1903 return;
1904 }
1905
1906 m_commit->Modify( bItem->GetParent() );
1907 getView()->Remove( bItem );
1908 bItem->GetParent()->Remove( bItem );
1909 }
1910 else
1911 {
1912 m_commit->Remove( bItem );
1913 }
1914 };
1915
1916 removeItem( group );
1917
1918 group->RunOnDescendants( [&]( BOARD_ITEM* aDescendant )
1919 {
1920 removeItem( aDescendant );
1921 });
1922 break;
1923 }
1924
1925 default:
1926 m_commit->Remove( item );
1927 break;
1928 }
1929 }
1930
1931 // If the entered group has been emptied then leave it.
1932 PCB_GROUP* enteredGroup = m_selectionTool->GetEnteredGroup();
1933
1934 if( enteredGroup && enteredGroup->GetItems().empty() )
1935 m_selectionTool->ExitGroup();
1936
1937 if( isCut )
1938 m_commit->Push( _( "Cut" ) );
1939 else
1940 m_commit->Push( _( "Delete" ) );
1941
1942 return 0;
1943 }
1944
1945
MoveExact(const TOOL_EVENT & aEvent)1946 int EDIT_TOOL::MoveExact( const TOOL_EVENT& aEvent )
1947 {
1948 if( isRouterActive() )
1949 {
1950 wxBell();
1951 return 0;
1952 }
1953
1954 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1955 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1956 {
1957 sTool->FilterCollectorForMarkers( aCollector );
1958 sTool->FilterCollectorForHierarchy( aCollector, true );
1959 },
1960 true /* prompt user regarding locked items */ );
1961
1962 if( selection.Empty() )
1963 return 0;
1964
1965 wxPoint translation;
1966 double rotation;
1967 ROTATION_ANCHOR rotationAnchor = selection.Size() > 1 ? ROTATE_AROUND_SEL_CENTER
1968 : ROTATE_AROUND_ITEM_ANCHOR;
1969
1970 // TODO: Implement a visible bounding border at the edge
1971 auto sel_box = selection.GetBoundingBox();
1972
1973 DIALOG_MOVE_EXACT dialog( frame(), translation, rotation, rotationAnchor, sel_box );
1974 int ret = dialog.ShowModal();
1975
1976 if( ret == wxID_OK )
1977 {
1978 VECTOR2I rp = selection.GetCenter();
1979 wxPoint selCenter( rp.x, rp.y );
1980
1981 // Make sure the rotation is from the right reference point
1982 selCenter += translation;
1983
1984 if( !frame()->GetDisplayOptions().m_DisplayInvertYAxis )
1985 rotation *= -1.0;
1986
1987 // When editing footprints, all items have the same parent
1988 if( IsFootprintEditor() )
1989 m_commit->Modify( selection.Front() );
1990
1991 for( EDA_ITEM* selItem : selection )
1992 {
1993 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selItem );
1994
1995 if( !item->IsNew() && !IsFootprintEditor() )
1996 {
1997 m_commit->Modify( item );
1998
1999 if( item->Type() == PCB_GROUP_T )
2000 {
2001 PCB_GROUP* group = static_cast<PCB_GROUP*>( item );
2002
2003 group->RunOnDescendants( [&]( BOARD_ITEM* bItem )
2004 {
2005 m_commit->Modify( bItem );
2006 });
2007 }
2008 }
2009
2010 if( !item->GetParent() || !item->GetParent()->IsSelected() )
2011 item->Move( translation );
2012
2013 switch( rotationAnchor )
2014 {
2015 case ROTATE_AROUND_ITEM_ANCHOR:
2016 item->Rotate( item->GetPosition(), rotation );
2017 break;
2018 case ROTATE_AROUND_SEL_CENTER:
2019 item->Rotate( selCenter, rotation );
2020 break;
2021 case ROTATE_AROUND_USER_ORIGIN:
2022 item->Rotate( (wxPoint) frame()->GetScreen()->m_LocalOrigin, rotation );
2023 break;
2024 case ROTATE_AROUND_AUX_ORIGIN:
2025 item->Rotate( board()->GetDesignSettings().GetAuxOrigin(), rotation );
2026 break;
2027 }
2028
2029 if( !m_dragging )
2030 getView()->Update( item );
2031 }
2032
2033 m_commit->Push( _( "Move exact" ) );
2034
2035 if( selection.IsHover() )
2036 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
2037
2038 m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
2039
2040 if( m_dragging )
2041 m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
2042 }
2043
2044 return 0;
2045 }
2046
2047
Duplicate(const TOOL_EVENT & aEvent)2048 int EDIT_TOOL::Duplicate( const TOOL_EVENT& aEvent )
2049 {
2050 if( isRouterActive() )
2051 {
2052 wxBell();
2053 return 0;
2054 }
2055
2056 bool increment = aEvent.IsAction( &PCB_ACTIONS::duplicateIncrement );
2057
2058 // Be sure that there is at least one item that we can modify
2059 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2060 []( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2061 {
2062 sTool->FilterCollectorForMarkers( aCollector );
2063 sTool->FilterCollectorForHierarchy( aCollector, true );
2064 } );
2065
2066 if( selection.Empty() )
2067 return 0;
2068
2069 // we have a selection to work on now, so start the tool process
2070 PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
2071
2072 // If the selection was given a hover, we do not keep the selection after completion
2073 bool is_hover = selection.IsHover();
2074
2075 std::vector<BOARD_ITEM*> new_items;
2076 new_items.reserve( selection.Size() );
2077
2078 // Each selected item is duplicated and pushed to new_items list
2079 // Old selection is cleared, and new items are then selected.
2080 for( EDA_ITEM* item : selection )
2081 {
2082 BOARD_ITEM* dupe_item = nullptr;
2083 BOARD_ITEM* orig_item = static_cast<BOARD_ITEM*>( item );
2084
2085 if( m_isFootprintEditor )
2086 {
2087 FOOTPRINT* parentFootprint = editFrame->GetBoard()->GetFirstFootprint();
2088 dupe_item = parentFootprint->DuplicateItem( orig_item );
2089
2090 if( increment && dupe_item->Type() == PCB_PAD_T
2091 && static_cast<PAD*>( dupe_item )->CanHaveNumber() )
2092 {
2093 PAD_TOOL* padTool = m_toolMgr->GetTool<PAD_TOOL>();
2094 wxString padNumber = padTool->GetLastPadNumber();
2095 padNumber = parentFootprint->GetNextPadNumber( padNumber );
2096 padTool->SetLastPadNumber( padNumber );
2097 static_cast<PAD*>( dupe_item )->SetNumber( padNumber );
2098 }
2099 }
2100 else if( orig_item->GetParent() && orig_item->GetParent()->Type() == PCB_FOOTPRINT_T )
2101 {
2102 FOOTPRINT* parentFootprint = static_cast<FOOTPRINT*>( orig_item->GetParent() );
2103
2104 m_commit->Modify( parentFootprint );
2105 dupe_item = parentFootprint->DuplicateItem( orig_item, true /* add to parent */ );
2106 }
2107 else
2108 {
2109 switch( orig_item->Type() )
2110 {
2111 case PCB_FOOTPRINT_T:
2112 case PCB_TEXT_T:
2113 case PCB_SHAPE_T:
2114 case PCB_TRACE_T:
2115 case PCB_ARC_T:
2116 case PCB_VIA_T:
2117 case PCB_ZONE_T:
2118 case PCB_TARGET_T:
2119 case PCB_DIM_ALIGNED_T:
2120 case PCB_DIM_CENTER_T:
2121 case PCB_DIM_ORTHOGONAL_T:
2122 case PCB_DIM_LEADER_T:
2123 dupe_item = orig_item->Duplicate();
2124 break;
2125
2126 case PCB_GROUP_T:
2127 dupe_item = static_cast<PCB_GROUP*>( orig_item )->DeepDuplicate();
2128 break;
2129
2130 default:
2131 wxASSERT_MSG( false, wxString::Format( "Unhandled item type %d",
2132 orig_item->Type() ) );
2133 break;
2134 }
2135 }
2136
2137 if( dupe_item )
2138 {
2139 if( dupe_item->Type() == PCB_GROUP_T )
2140 {
2141 static_cast<PCB_GROUP*>( dupe_item )->RunOnDescendants(
2142 [&]( BOARD_ITEM* bItem )
2143 {
2144 m_commit->Add( bItem );
2145 });
2146 }
2147
2148 // Clear the selection flag here, otherwise the PCB_SELECTION_TOOL
2149 // will not properly select it later on
2150 dupe_item->ClearSelected();
2151
2152 new_items.push_back( dupe_item );
2153 m_commit->Add( dupe_item );
2154 }
2155 }
2156
2157 // Clear the old selection first
2158 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
2159
2160 // Select the new items
2161 m_toolMgr->RunAction( PCB_ACTIONS::selectItems, true, &new_items );
2162
2163 // record the new items as added
2164 if( !selection.Empty() )
2165 {
2166 editFrame->DisplayToolMsg( wxString::Format( _( "Duplicated %d item(s)" ),
2167 (int) new_items.size() ) );
2168
2169 // TODO(ISM): This line can't be used to activate the tool until we allow multiple
2170 // activations.
2171 // m_toolMgr->RunAction( PCB_ACTIONS::move, true );
2172 // Instead we have to create the event and call the tool's function
2173 // directly
2174
2175 // If items were duplicated, pick them up
2176 // this works well for "dropping" copies around and pushes the commit
2177 TOOL_EVENT evt = PCB_ACTIONS::move.MakeEvent();
2178 Move( evt );
2179
2180 // Deslect the duplicated item if we originally started as a hover selection
2181 if( is_hover )
2182 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
2183 }
2184
2185 return 0;
2186 }
2187
2188
CreateArray(const TOOL_EVENT & aEvent)2189 int EDIT_TOOL::CreateArray( const TOOL_EVENT& aEvent )
2190 {
2191 if( isRouterActive() )
2192 {
2193 wxBell();
2194 return 0;
2195 }
2196
2197 // Be sure that there is at least one item that we can modify
2198 const auto& selection = m_selectionTool->RequestSelection(
2199 []( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2200 {
2201 sTool->FilterCollectorForMarkers( aCollector );
2202 sTool->FilterCollectorForHierarchy( aCollector, true );
2203 } );
2204
2205 if( selection.Empty() )
2206 return 0;
2207
2208 // we have a selection to work on now, so start the tool process
2209 PCB_BASE_FRAME* editFrame = getEditFrame<PCB_BASE_FRAME>();
2210 ARRAY_CREATOR array_creator( *editFrame, m_isFootprintEditor, selection, m_toolMgr );
2211 array_creator.Invoke();
2212
2213 return 0;
2214 }
2215
2216
PadFilter(const VECTOR2I &,GENERAL_COLLECTOR & aCollector,PCB_SELECTION_TOOL * sTool)2217 void EDIT_TOOL::PadFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector,
2218 PCB_SELECTION_TOOL* sTool )
2219 {
2220 for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
2221 {
2222 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aCollector[i] );
2223
2224 if( item->Type() != PCB_PAD_T )
2225 aCollector.Remove( i );
2226 }
2227 }
2228
2229
FootprintFilter(const VECTOR2I &,GENERAL_COLLECTOR & aCollector,PCB_SELECTION_TOOL * sTool)2230 void EDIT_TOOL::FootprintFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector,
2231 PCB_SELECTION_TOOL* sTool )
2232 {
2233 for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
2234 {
2235 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aCollector[i] );
2236
2237 if( item->Type() != PCB_FOOTPRINT_T )
2238 aCollector.Remove( i );
2239 }
2240 }
2241
2242
updateModificationPoint(PCB_SELECTION & aSelection)2243 bool EDIT_TOOL::updateModificationPoint( PCB_SELECTION& aSelection )
2244 {
2245 if( m_dragging && aSelection.HasReferencePoint() )
2246 return false;
2247
2248 // When there is only one item selected, the reference point is its position...
2249 if( aSelection.Size() == 1 )
2250 {
2251 auto item = static_cast<BOARD_ITEM*>( aSelection.Front() );
2252 auto pos = item->GetPosition();
2253 aSelection.SetReferencePoint( VECTOR2I( pos.x, pos.y ) );
2254 }
2255 // ...otherwise modify items with regard to the grid-snapped center position
2256 else
2257 {
2258 PCB_GRID_HELPER grid( m_toolMgr, frame()->GetMagneticItemsSettings() );
2259 aSelection.SetReferencePoint( grid.BestSnapAnchor( aSelection.GetCenter(), nullptr ) );
2260 }
2261
2262 return true;
2263 }
2264
2265
pickReferencePoint(const wxString & aTooltip,const wxString & aSuccessMessage,const wxString & aCanceledMessage,VECTOR2I & aReferencePoint)2266 bool EDIT_TOOL::pickReferencePoint( const wxString& aTooltip, const wxString& aSuccessMessage,
2267 const wxString& aCanceledMessage, VECTOR2I& aReferencePoint )
2268 {
2269 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
2270 OPT<VECTOR2I> pickedPoint;
2271 bool done = false;
2272
2273 m_statusPopup->SetText( aTooltip );
2274
2275 /// This allow the option of snapping in the tool
2276 picker->SetSnapping( true );
2277
2278 picker->SetClickHandler(
2279 [&]( const VECTOR2D& aPoint ) -> bool
2280 {
2281 pickedPoint = aPoint;
2282
2283 if( !aSuccessMessage.empty() )
2284 {
2285 m_statusPopup->SetText( aSuccessMessage );
2286 m_statusPopup->Expire( 800 );
2287 }
2288 else
2289 {
2290 m_statusPopup->Hide();
2291 }
2292
2293 return false; // we don't need any more points
2294 } );
2295
2296 picker->SetMotionHandler(
2297 [&]( const VECTOR2D& aPos )
2298 {
2299 m_statusPopup->Move( wxGetMousePosition() + wxPoint( 20, -50 ) );
2300 } );
2301
2302 picker->SetCancelHandler(
2303 [&]()
2304 {
2305 if( !aCanceledMessage.empty() )
2306 {
2307 m_statusPopup->SetText( aCanceledMessage );
2308 m_statusPopup->Expire( 800 );
2309 }
2310 else
2311 {
2312 m_statusPopup->Hide();
2313 }
2314 } );
2315
2316 picker->SetFinalizeHandler(
2317 [&]( const int& aFinalState )
2318 {
2319 done = true;
2320 } );
2321
2322 m_statusPopup->Move( wxGetMousePosition() + wxPoint( 20, -50 ) );
2323 m_statusPopup->Popup();
2324
2325 std::string tool = "";
2326 m_toolMgr->RunAction( ACTIONS::pickerTool, true, &tool );
2327
2328 while( !done )
2329 {
2330 // Pass events unless we receive a null event, then we must shut down
2331 if( TOOL_EVENT* evt = Wait() )
2332 evt->SetPassEvent();
2333 else
2334 break;
2335 }
2336
2337 // Ensure statusPopup is hidden after use and before deleting it:
2338 m_statusPopup->Hide();
2339
2340 if( pickedPoint.is_initialized() )
2341 aReferencePoint = pickedPoint.get();
2342
2343 return pickedPoint.is_initialized();
2344 }
2345
2346
copyToClipboard(const TOOL_EVENT & aEvent)2347 int EDIT_TOOL::copyToClipboard( const TOOL_EVENT& aEvent )
2348 {
2349 std::string tool = "pcbnew.InteractiveEdit.selectReferencePoint";
2350 CLIPBOARD_IO io;
2351 PCB_GRID_HELPER grid( m_toolMgr,
2352 getEditFrame<PCB_BASE_EDIT_FRAME>()->GetMagneticItemsSettings() );
2353
2354 frame()->PushTool( tool );
2355 Activate();
2356
2357 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2358 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2359 {
2360 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
2361 {
2362 BOARD_ITEM* item = aCollector[i];
2363
2364 // We can't copy both a footprint and its text in the same operation, so if
2365 // both are selected, remove the text
2366 if( item->Type() == PCB_FP_TEXT_T && aCollector.HasItem( item->GetParent() ) )
2367 aCollector.Remove( item );
2368 }
2369 },
2370
2371 // Prompt user regarding locked items.
2372 aEvent.IsAction( &ACTIONS::cut ) && !m_isFootprintEditor );
2373
2374 if( !selection.Empty() )
2375 {
2376 std::vector<BOARD_ITEM*> items;
2377
2378 for( EDA_ITEM* item : selection )
2379 items.push_back( static_cast<BOARD_ITEM*>( item ) );
2380
2381 VECTOR2I refPoint;
2382
2383 if( aEvent.IsAction( &PCB_ACTIONS::copyWithReference ) )
2384 {
2385 if( !pickReferencePoint( _( "Select reference point for the copy..." ),
2386 _( "Selection copied" ),
2387 _( "Copy canceled" ),
2388 refPoint ) )
2389 {
2390 frame()->PopTool( tool );
2391 return 0;
2392 }
2393 }
2394 else
2395 {
2396 refPoint = grid.BestDragOrigin( getViewControls()->GetCursorPosition(), items );
2397 }
2398
2399 selection.SetReferencePoint( refPoint );
2400
2401 io.SetBoard( board() );
2402 io.SaveSelection( selection, m_isFootprintEditor );
2403 frame()->SetStatusText( _( "Selection copied" ) );
2404 }
2405
2406 frame()->PopTool( tool );
2407
2408 return 0;
2409 }
2410
2411
cutToClipboard(const TOOL_EVENT & aEvent)2412 int EDIT_TOOL::cutToClipboard( const TOOL_EVENT& aEvent )
2413 {
2414 if( !copyToClipboard( aEvent ) )
2415 {
2416 // N.B. Setting the CUT flag prevents lock filtering as we only want to delete the items
2417 // that were copied to the clipboard, no more, no fewer. Filtering for locked item, if
2418 // any will be done in the copyToClipboard() routine
2419 TOOL_EVENT evt( aEvent.Category(), aEvent.Action(), TOOL_ACTION_SCOPE::AS_GLOBAL );
2420 evt.SetParameter( PCB_ACTIONS::REMOVE_FLAGS::CUT );
2421 Remove( evt );
2422 }
2423
2424 return 0;
2425 }
2426
2427
setTransitions()2428 void EDIT_TOOL::setTransitions()
2429 {
2430 Go( &EDIT_TOOL::GetAndPlace, PCB_ACTIONS::getAndPlace.MakeEvent() );
2431 Go( &EDIT_TOOL::Move, PCB_ACTIONS::move.MakeEvent() );
2432 Go( &EDIT_TOOL::Drag, PCB_ACTIONS::drag45Degree.MakeEvent() );
2433 Go( &EDIT_TOOL::Drag, PCB_ACTIONS::dragFreeAngle.MakeEvent() );
2434 Go( &EDIT_TOOL::Rotate, PCB_ACTIONS::rotateCw.MakeEvent() );
2435 Go( &EDIT_TOOL::Rotate, PCB_ACTIONS::rotateCcw.MakeEvent() );
2436 Go( &EDIT_TOOL::Flip, PCB_ACTIONS::flip.MakeEvent() );
2437 Go( &EDIT_TOOL::Remove, ACTIONS::doDelete.MakeEvent() );
2438 Go( &EDIT_TOOL::Remove, PCB_ACTIONS::deleteFull.MakeEvent() );
2439 Go( &EDIT_TOOL::Properties, PCB_ACTIONS::properties.MakeEvent() );
2440 Go( &EDIT_TOOL::MoveExact, PCB_ACTIONS::moveExact.MakeEvent() );
2441 Go( &EDIT_TOOL::MoveWithReference, PCB_ACTIONS::moveWithReference.MakeEvent() );
2442 Go( &EDIT_TOOL::Duplicate, ACTIONS::duplicate.MakeEvent() );
2443 Go( &EDIT_TOOL::Duplicate, PCB_ACTIONS::duplicateIncrement.MakeEvent() );
2444 Go( &EDIT_TOOL::CreateArray, PCB_ACTIONS::createArray.MakeEvent() );
2445 Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirror.MakeEvent() );
2446 Go( &EDIT_TOOL::ChangeTrackWidth, PCB_ACTIONS::changeTrackWidth.MakeEvent() );
2447 Go( &EDIT_TOOL::FilletTracks, PCB_ACTIONS::filletTracks.MakeEvent() );
2448
2449 Go( &EDIT_TOOL::copyToClipboard, ACTIONS::copy.MakeEvent() );
2450 Go( &EDIT_TOOL::copyToClipboard, PCB_ACTIONS::copyWithReference.MakeEvent() );
2451 Go( &EDIT_TOOL::cutToClipboard, ACTIONS::cut.MakeEvent() );
2452 }
2453