1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2017-2021 KiCad Developers, see AUTHORS.txt for contributors.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, you may find one here:
18  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19  * or you may search the http://www.gnu.org website for the version 2 license,
20  * or you may write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
22  */
23 
24 
25 #include "pad_tool.h"
26 #include <macros.h>
27 #include <class_draw_panel_gal.h>
28 #include <view/view_controls.h>
29 #include <tool/tool_manager.h>
30 #include <board_design_settings.h>
31 #include <board_item.h>
32 #include <footprint.h>
33 #include <fp_shape.h>
34 #include <pad.h>
35 #include <board_commit.h>
36 #include <dialogs/dialog_push_pad_properties.h>
37 #include <tools/pcb_actions.h>
38 #include <tools/pcb_selection_tool.h>
39 #include <tools/pcb_selection_conditions.h>
40 #include <tools/edit_tool.h>
41 #include <dialogs/dialog_enum_pads.h>
42 #include <widgets/infobar.h>
43 
PAD_TOOL()44 PAD_TOOL::PAD_TOOL() :
45         PCB_TOOL_BASE( "pcbnew.PadTool" ),
46         m_wasHighContrast( false ),
47         m_editPad( niluuid )
48 {}
49 
50 
~PAD_TOOL()51 PAD_TOOL::~PAD_TOOL()
52 {}
53 
54 
Reset(RESET_REASON aReason)55 void PAD_TOOL::Reset( RESET_REASON aReason )
56 {
57     if( aReason == MODEL_RELOAD )
58         m_lastPadNumber = wxT( "1" );
59 
60     m_editPad = niluuid;
61 }
62 
63 
Init()64 bool PAD_TOOL::Init()
65 {
66     PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
67 
68     if( selTool )
69     {
70         // Add context menu entries that are displayed when selection tool is active
71         CONDITIONAL_MENU& menu = selTool->GetToolMenu().GetMenu();
72 
73         SELECTION_CONDITION padSel = SELECTION_CONDITIONS::HasType( PCB_PAD_T );
74         SELECTION_CONDITION singlePadSel = SELECTION_CONDITIONS::Count( 1 ) &&
75                                            SELECTION_CONDITIONS::OnlyType( PCB_PAD_T );
76 
77         auto explodeCondition =
78                 [&]( const SELECTION& aSel )
79                 {
80                     return m_editPad == niluuid && aSel.Size() == 1 && aSel[0]->Type() == PCB_PAD_T;
81                 };
82 
83         auto recombineCondition =
84                 [&]( const SELECTION& aSel )
85                 {
86                     return m_editPad != niluuid;
87                 };
88 
89         menu.AddSeparator( 400 );
90 
91         if( m_isFootprintEditor )
92         {
93             menu.AddItem( PCB_ACTIONS::enumeratePads,      SELECTION_CONDITIONS::ShowAlways, 400 );
94             menu.AddItem( PCB_ACTIONS::recombinePad,       recombineCondition, 400 );
95             menu.AddItem( PCB_ACTIONS::explodePad,         explodeCondition, 400 );
96         }
97 
98         menu.AddItem( PCB_ACTIONS::copyPadSettings,        singlePadSel, 400 );
99         menu.AddItem( PCB_ACTIONS::applyPadSettings,       padSel, 400 );
100         menu.AddItem( PCB_ACTIONS::pushPadSettings,        singlePadSel, 400 );
101     }
102 
103     auto& ctxMenu = m_menu.GetMenu();
104 
105     // cancel current tool goes in main context menu at the top if present
106     ctxMenu.AddItem( ACTIONS::cancelInteractive,           SELECTION_CONDITIONS::ShowAlways, 1 );
107     ctxMenu.AddSeparator( 1 );
108 
109     ctxMenu.AddItem( PCB_ACTIONS::rotateCcw,               SELECTION_CONDITIONS::ShowAlways );
110     ctxMenu.AddItem( PCB_ACTIONS::rotateCw,                SELECTION_CONDITIONS::ShowAlways );
111     ctxMenu.AddItem( PCB_ACTIONS::flip,                    SELECTION_CONDITIONS::ShowAlways );
112     ctxMenu.AddItem( PCB_ACTIONS::mirror,                  SELECTION_CONDITIONS::ShowAlways );
113     ctxMenu.AddItem( PCB_ACTIONS::properties,              SELECTION_CONDITIONS::ShowAlways );
114 
115     // Finally, add the standard zoom/grid items
116     getEditFrame<PCB_BASE_FRAME>()->AddStandardSubMenus( m_menu );
117 
118     return true;
119 }
120 
121 
pastePadProperties(const TOOL_EVENT & aEvent)122 int PAD_TOOL::pastePadProperties( const TOOL_EVENT& aEvent )
123 {
124     PCB_SELECTION_TOOL*  selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
125     const PCB_SELECTION& selection = selTool->GetSelection();
126     const PAD*           masterPad = frame()->GetDesignSettings().m_Pad_Master.get();
127 
128     BOARD_COMMIT commit( frame() );
129 
130     // for every selected pad, paste global settings
131     for( EDA_ITEM* item : selection )
132     {
133         if( item->Type() == PCB_PAD_T )
134         {
135             commit.Modify( item );
136             static_cast<PAD&>( *item ).ImportSettingsFrom( *masterPad );
137         }
138     }
139 
140     commit.Push( _( "Paste Pad Properties" ) );
141 
142     m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
143     frame()->Refresh();
144 
145     return 0;
146 }
147 
148 
copyPadSettings(const TOOL_EVENT & aEvent)149 int PAD_TOOL::copyPadSettings( const TOOL_EVENT& aEvent )
150 {
151     PCB_SELECTION_TOOL*  selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
152     const PCB_SELECTION& selection = selTool->GetSelection();
153 
154     // can only copy from a single pad
155     if( selection.Size() == 1 )
156     {
157         EDA_ITEM* item = selection[0];
158 
159         if( item->Type() == PCB_PAD_T )
160         {
161             const PAD& selPad = static_cast<const PAD&>( *item );
162             frame()->GetDesignSettings().m_Pad_Master->ImportSettingsFrom( selPad );
163         }
164     }
165 
166     return 0;
167 }
168 
169 
doPushPadProperties(BOARD & board,const PAD & aSrcPad,BOARD_COMMIT & commit,bool aSameFootprints,bool aPadShapeFilter,bool aPadOrientFilter,bool aPadLayerFilter,bool aPadTypeFilter)170 static void doPushPadProperties( BOARD& board, const PAD& aSrcPad, BOARD_COMMIT& commit,
171                                  bool aSameFootprints,
172                                  bool aPadShapeFilter,
173                                  bool aPadOrientFilter,
174                                  bool aPadLayerFilter,
175                                  bool aPadTypeFilter )
176 {
177     const FOOTPRINT* refFootprint = aSrcPad.GetParent();
178 
179     double pad_orient = aSrcPad.GetOrientation() - refFootprint->GetOrientation();
180 
181     for( FOOTPRINT* footprint : board.Footprints() )
182     {
183         if( !aSameFootprints && ( footprint != refFootprint ) )
184             continue;
185 
186         if( footprint->GetFPID() != refFootprint->GetFPID() )
187             continue;
188 
189         for( auto pad : footprint->Pads() )
190         {
191             if( aPadShapeFilter && ( pad->GetShape() != aSrcPad.GetShape() ) )
192                 continue;
193 
194             double currpad_orient = pad->GetOrientation() - footprint->GetOrientation();
195 
196             if( aPadOrientFilter && ( currpad_orient != pad_orient ) )
197                 continue;
198 
199             if( aPadLayerFilter && ( pad->GetLayerSet() != aSrcPad.GetLayerSet() ) )
200                 continue;
201 
202             if( aPadTypeFilter && ( pad->GetAttribute() != aSrcPad.GetAttribute() ) )
203                     continue;
204 
205             // Special-case for aperture pads
206             if( aPadTypeFilter && pad->GetAttribute() == PAD_ATTRIB::CONN )
207             {
208                 if( pad->IsAperturePad() != aSrcPad.IsAperturePad() )
209                     continue;
210             }
211 
212             commit.Modify( pad );
213 
214             // Apply source pad settings to this pad
215             pad->ImportSettingsFrom( aSrcPad );
216         }
217     }
218 }
219 
220 
pushPadSettings(const TOOL_EVENT & aEvent)221 int PAD_TOOL::pushPadSettings( const TOOL_EVENT& aEvent )
222 {
223     PCB_SELECTION_TOOL*  selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
224     const PCB_SELECTION& selection = selTool->GetSelection();
225     PAD*                 srcPad;
226 
227     if( selection.Size() == 1 && selection[0]->Type() == PCB_PAD_T )
228         srcPad = static_cast<PAD*>( selection[0] );
229     else
230         return 0;
231 
232     FOOTPRINT* footprint = srcPad->GetParent();
233 
234     if( !footprint )
235         return 0;
236 
237     frame()->SetMsgPanel( footprint );
238 
239     DIALOG_PUSH_PAD_PROPERTIES dlg( frame() );
240     int dialogRet = dlg.ShowModal();
241 
242     if( dialogRet == wxID_CANCEL )
243         return 0;
244 
245     const bool edit_Same_Modules = (dialogRet == 1);
246 
247     BOARD_COMMIT commit( frame() );
248 
249     doPushPadProperties( *getModel<BOARD>(), *srcPad, commit, edit_Same_Modules,
250                          DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Shape_Filter,
251                          DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Orient_Filter,
252                          DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Layer_Filter,
253                          DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Type_Filter );
254 
255     commit.Push( _( "Push Pad Settings" ) );
256 
257     m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
258     frame()->Refresh();
259 
260     return 0;
261 }
262 
263 
EnumeratePads(const TOOL_EVENT & aEvent)264 int PAD_TOOL::EnumeratePads( const TOOL_EVENT& aEvent )
265 {
266     if( !board()->GetFirstFootprint() || board()->GetFirstFootprint()->Pads().empty() )
267         return 0;
268 
269     GENERAL_COLLECTOR collector;
270     const KICAD_T types[] = { PCB_PAD_T, EOT };
271 
272     GENERAL_COLLECTORS_GUIDE guide = frame()->GetCollectorsGuide();
273     guide.SetIgnoreMTextsMarkedNoShow( true );
274     guide.SetIgnoreMTextsOnBack( true );
275     guide.SetIgnoreMTextsOnFront( true );
276     guide.SetIgnoreModulesVals( true );
277     guide.SetIgnoreModulesRefs( true );
278 
279     DIALOG_ENUM_PADS settingsDlg( frame() );
280 
281     if( settingsDlg.ShowModal() != wxID_OK )
282         return 0;
283 
284     int             seqPadNum = settingsDlg.GetStartNumber();
285     wxString        padPrefix = settingsDlg.GetPrefix();
286     std::deque<int> storedPadNumbers;
287     std::map<wxString, std::pair<int, wxString>> oldNumbers;
288 
289     m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
290 
291     std::string tool = aEvent.GetCommandStr().get();
292     frame()->PushTool( tool );
293 
294     VECTOR2I        oldCursorPos;  // store the previous mouse cursor position, during mouse drag
295     std::list<PAD*> selectedPads;
296     BOARD_COMMIT    commit( frame() );
297     bool            isFirstPoint = true;   // make sure oldCursorPos is initialized at least once
298 
299     auto setCursor =
300             [&]()
301             {
302                 frame()->GetCanvas()->SetCurrentCursor( KICURSOR::BULLSEYE );
303             };
304 
305     Activate();
306     // Must be done after Activate() so that it gets set into the correct context
307     getViewControls()->ShowCursor( true );
308     // Set initial cursor
309     setCursor();
310 
311     STATUS_TEXT_POPUP statusPopup( frame() );
312     wxString msg = _( "Click on pad %s%d\nPress <esc> to cancel or double-click to commit" );
313     statusPopup.SetText( wxString::Format( msg, padPrefix, seqPadNum ) );
314     statusPopup.Popup();
315     statusPopup.Move( wxGetMousePosition() + wxPoint( 20, 20 ) );
316 
317     while( TOOL_EVENT* evt = Wait() )
318     {
319         setCursor();
320 
321         if( evt->IsCancelInteractive() )
322         {
323             m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
324             commit.Revert();
325 
326             frame()->PopTool( tool );
327             break;
328         }
329         else if( evt->IsActivate() )
330         {
331             commit.Push( _( "Renumber pads" ) );
332 
333             frame()->PopTool( tool );
334             break;
335         }
336         else if( evt->IsDrag( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) )
337         {
338             selectedPads.clear();
339             VECTOR2I cursorPos = getViewControls()->GetCursorPosition();
340 
341             // Be sure the old cursor mouse position was initialized:
342             if( isFirstPoint )
343             {
344                 oldCursorPos = cursorPos;
345                 isFirstPoint = false;
346             }
347 
348             // wxWidgets deliver mouse move events not frequently enough, resulting in skipping
349             // pads if the user moves cursor too fast. To solve it, create a line that approximates
350             // the mouse move and search pads that are on the line.
351             int distance = ( cursorPos - oldCursorPos ).EuclideanNorm();
352             // Search will be made every 0.1 mm:
353             int segments = distance / int( 0.1*IU_PER_MM ) + 1;
354             const wxPoint line_step( ( cursorPos - oldCursorPos ) / segments );
355 
356             collector.Empty();
357 
358             for( int j = 0; j < segments; ++j )
359             {
360                 wxPoint testpoint( cursorPos.x - j * line_step.x, cursorPos.y - j * line_step.y );
361                 collector.Collect( board(), types, testpoint, guide );
362 
363                 for( int i = 0; i < collector.GetCount(); ++i )
364                     selectedPads.push_back( static_cast<PAD*>( collector[i] ) );
365             }
366 
367             selectedPads.unique();
368 
369             for( PAD* pad : selectedPads )
370             {
371                 // If pad was not selected, then enumerate it
372                 if( !pad->IsSelected() )
373                 {
374                     commit.Modify( pad );
375 
376                     // Rename pad and store the old name
377                     int newval;
378 
379                     if( storedPadNumbers.size() > 0 )
380                     {
381                         newval = storedPadNumbers.front();
382                         storedPadNumbers.pop_front();
383                     }
384                     else
385                         newval = seqPadNum++;
386 
387                     wxString newNumber = wxString::Format( wxT( "%s%d" ), padPrefix, newval );
388                     oldNumbers[newNumber] = { newval, pad->GetNumber() };
389                     pad->SetNumber( newNumber );
390                     SetLastPadNumber( newNumber );
391                     pad->SetSelected();
392                     getView()->Update( pad );
393 
394                     // Ensure the popup text shows the correct next value
395                     if( storedPadNumbers.size() > 0 )
396                         newval = storedPadNumbers.front();
397                     else
398                         newval = seqPadNum;
399 
400                     statusPopup.SetText( wxString::Format( msg, padPrefix, newval ) );
401                 }
402 
403                 // ... or restore the old name if it was enumerated and clicked again
404                 else if( pad->IsSelected() && evt->IsClick( BUT_LEFT ) )
405                 {
406                     auto it = oldNumbers.find( pad->GetNumber() );
407                     wxASSERT( it != oldNumbers.end() );
408 
409                     if( it != oldNumbers.end() )
410                     {
411                         storedPadNumbers.push_back( it->second.first );
412                         pad->SetNumber( it->second.second );
413                         SetLastPadNumber( it->second.second );
414                         oldNumbers.erase( it );
415 
416                         int newval = storedPadNumbers.front();
417 
418                         statusPopup.SetText( wxString::Format( msg, padPrefix, newval ) );
419                     }
420 
421                     pad->ClearSelected();
422                     getView()->Update( pad );
423                 }
424             }
425         }
426         else if( ( evt->IsKeyPressed() && evt->KeyCode() == WXK_RETURN ) ||
427                  evt->IsDblClick( BUT_LEFT ) )
428         {
429             commit.Push( _( "Renumber pads" ) );
430             frame()->PopTool( tool );
431             break;
432         }
433         else if( evt->IsClick( BUT_RIGHT ) )
434         {
435             m_menu.ShowContextMenu( selection() );
436         }
437         else
438         {
439             evt->SetPassEvent();
440         }
441 
442         // Prepare the next loop by updating the old cursor mouse position
443         // to this last mouse cursor position
444         oldCursorPos = getViewControls()->GetCursorPosition();
445         statusPopup.Move( wxGetMousePosition() + wxPoint( 20, 20 ) );
446     }
447 
448     for( PAD* p : board()->GetFirstFootprint()->Pads() )
449     {
450         p->ClearSelected();
451         getView()->Update( p );
452     }
453 
454     statusPopup.Hide();
455     frame()->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
456     return 0;
457 }
458 
459 
PlacePad(const TOOL_EVENT & aEvent)460 int PAD_TOOL::PlacePad( const TOOL_EVENT& aEvent )
461 {
462     if( !board()->GetFirstFootprint() )
463         return 0;
464 
465     struct PAD_PLACER : public INTERACTIVE_PLACER_BASE
466     {
467         PAD_PLACER( PAD_TOOL* aPadTool )
468         {
469             m_padTool = aPadTool;
470         }
471 
472         virtual ~PAD_PLACER()
473         {
474         }
475 
476         std::unique_ptr<BOARD_ITEM> CreateItem() override
477         {
478             PAD* pad = new PAD( m_board->GetFirstFootprint() );
479             PAD* master = m_frame->GetDesignSettings().m_Pad_Master.get();
480 
481             pad->ImportSettingsFrom( *master );
482 
483             // If the footprint type and master pad type directly conflict then make some
484             // adjustments.  Otherwise assume the user set what they wanted.
485             if( ( m_board->GetFirstFootprint()->GetAttributes() & FP_SMD )
486                     && master->GetAttribute() == PAD_ATTRIB::PTH )
487             {
488                 pad->SetAttribute( PAD_ATTRIB::SMD );
489                 pad->SetShape( PAD_SHAPE::ROUNDRECT );
490                 pad->SetSizeX( 1.5 * pad->GetSizeY() );
491                 pad->SetLayerSet( PAD::SMDMask() );
492             }
493             else if( ( m_board->GetFirstFootprint()->GetAttributes() & FP_THROUGH_HOLE )
494                     && master->GetAttribute() == PAD_ATTRIB::SMD )
495             {
496                 pad->SetAttribute( PAD_ATTRIB::PTH );
497                 pad->SetShape( PAD_SHAPE::CIRCLE );
498                 pad->SetSize( wxSize( pad->GetSizeX(), pad->GetSizeX() ) );
499                 pad->SetLayerSet( PAD::PTHMask() );
500             }
501 
502             if( pad->CanHaveNumber() )
503             {
504                 wxString padNumber = m_padTool->GetLastPadNumber();
505                 padNumber = m_board->GetFirstFootprint()->GetNextPadNumber( padNumber );
506                 pad->SetNumber( padNumber );
507                 m_padTool->SetLastPadNumber( padNumber );
508             }
509 
510             return std::unique_ptr<BOARD_ITEM>( pad );
511         }
512 
513         bool PlaceItem( BOARD_ITEM *aItem, BOARD_COMMIT& aCommit ) override
514         {
515             PAD* pad = dynamic_cast<PAD*>( aItem );
516 
517             if( pad )
518             {
519                 m_frame->GetDesignSettings().m_Pad_Master->ImportSettingsFrom( *pad );
520                 pad->SetLocalCoord();
521                 aCommit.Add( aItem );
522                 return true;
523             }
524 
525             return false;
526         }
527 
528         PAD_TOOL* m_padTool;
529     };
530 
531     PAD_PLACER placer( this );
532 
533     doInteractiveItemPlacement( aEvent.GetCommandStr().get(), &placer,  _( "Place pad" ),
534                                 IPO_REPEAT | IPO_SINGLE_CLICK | IPO_ROTATE | IPO_FLIP );
535 
536     return 0;
537 }
538 
539 
EditPad(const TOOL_EVENT & aEvent)540 int PAD_TOOL::EditPad( const TOOL_EVENT& aEvent )
541 {
542     PCB_DISPLAY_OPTIONS opts = frame()->GetDisplayOptions();
543     WX_INFOBAR*         infoBar = frame()->GetInfoBar();
544     PCB_SELECTION&      selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
545     wxString            msg;
546 
547     if( m_editPad != niluuid )
548     {
549         PAD* pad = dynamic_cast<PAD*>( frame()->GetItem( m_editPad ) );
550 
551         if( pad )
552             recombinePad( pad );
553 
554         m_editPad = niluuid;
555     }
556     else if( selection.Size() == 1 && selection[0]->Type() == PCB_PAD_T )
557     {
558         PAD*         pad = static_cast<PAD*>( selection[0] );
559         PCB_LAYER_ID layer = explodePad( pad );
560 
561         m_wasHighContrast = ( opts.m_ContrastModeDisplay != HIGH_CONTRAST_MODE::NORMAL );
562         frame()->SetActiveLayer( layer );
563 
564         if( !m_wasHighContrast )
565             m_toolMgr->RunAction( ACTIONS::highContrastMode, false );
566 
567         if( PCB_ACTIONS::explodePad.GetHotKey() == PCB_ACTIONS::recombinePad.GetHotKey() )
568         {
569             msg.Printf( _( "Pad Edit Mode.  Press %s again to exit." ),
570                         KeyNameFromKeyCode( PCB_ACTIONS::recombinePad.GetHotKey() ) );}
571 
572         else
573         {
574             msg.Printf( _( "Pad Edit Mode.  Press %s to exit." ),
575                         KeyNameFromKeyCode( PCB_ACTIONS::recombinePad.GetHotKey() ) );
576         }
577 
578         infoBar->RemoveAllButtons();
579         infoBar->ShowMessage( msg, wxICON_INFORMATION );
580 
581         m_editPad = pad->m_Uuid;
582     }
583 
584     if( m_editPad == niluuid )
585     {
586         bool highContrast = ( opts.m_ContrastModeDisplay != HIGH_CONTRAST_MODE::NORMAL );
587 
588         if( m_wasHighContrast != highContrast )
589             m_toolMgr->RunAction( ACTIONS::highContrastMode, false );
590 
591         infoBar->Dismiss();
592     }
593 
594     return 0;
595 }
596 
597 
explodePad(PAD * aPad)598 PCB_LAYER_ID PAD_TOOL::explodePad( PAD* aPad )
599 {
600     PCB_LAYER_ID layer;
601     BOARD_COMMIT commit( frame() );
602 
603     if( aPad->IsOnLayer( F_Cu ) )
604         layer = F_Cu;
605     else if( aPad->IsOnLayer( B_Cu ) )
606         layer = B_Cu;
607     else
608         layer = *aPad->GetLayerSet().UIOrder();
609 
610     if( aPad->GetShape() == PAD_SHAPE::CUSTOM )
611     {
612         commit.Modify( aPad );
613 
614         for( const std::shared_ptr<PCB_SHAPE>& primitive : aPad->GetPrimitives() )
615         {
616             FP_SHAPE* shape = new FP_SHAPE( board()->GetFirstFootprint() );
617 
618             shape->SetShape( primitive->GetShape() );
619             shape->SetFilled( primitive->IsFilled() );
620             shape->SetWidth( primitive->GetWidth() );
621 
622             switch( shape->GetShape() )
623             {
624             case SHAPE_T::SEGMENT:
625             case SHAPE_T::RECT:
626             case SHAPE_T::CIRCLE:
627                 shape->SetStart( primitive->GetStart() );
628                 shape->SetEnd( primitive->GetEnd() );
629                 break;
630 
631             case SHAPE_T::ARC:
632                 shape->SetStart( primitive->GetStart() );
633                 shape->SetEnd( primitive->GetEnd() );
634                 shape->SetCenter( primitive->GetCenter() );
635                 break;
636 
637             case SHAPE_T::BEZIER:
638                 shape->SetStart( primitive->GetStart() );
639                 shape->SetEnd( primitive->GetEnd() );
640                 shape->SetBezierC1( primitive->GetBezierC1() );
641                 shape->SetBezierC2( primitive->GetBezierC2() );
642                 break;
643 
644             case SHAPE_T::POLY:
645                 shape->SetPolyShape( primitive->GetPolyShape() );
646                 break;
647 
648             default:
649                 UNIMPLEMENTED_FOR( shape->SHAPE_T_asString() );
650             }
651 
652             shape->SetLocalCoord();
653             shape->Move( aPad->GetPosition() );
654             shape->Rotate( aPad->GetPosition(), aPad->GetOrientation() );
655             shape->SetLayer( layer );
656 
657             commit.Add( shape );
658         }
659 
660         aPad->SetShape( aPad->GetAnchorPadShape() );
661         aPad->DeletePrimitivesList();
662         m_editPad = aPad->m_Uuid;
663     }
664 
665     commit.Push( _("Edit pad shapes") );
666     m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
667     return layer;
668 }
669 
670 
recombinePad(PAD * aPad)671 void PAD_TOOL::recombinePad( PAD* aPad )
672 {
673     int  maxError = board()->GetDesignSettings().m_MaxError;
674 
675     auto findNext =
676             [&]( PCB_LAYER_ID aLayer ) -> FP_SHAPE*
677             {
678                 SHAPE_POLY_SET padPoly;
679                 aPad->TransformShapeWithClearanceToPolygon( padPoly, aLayer, 0, maxError,
680                                                             ERROR_INSIDE );
681 
682                 for( BOARD_ITEM* item : board()->GetFirstFootprint()->GraphicalItems() )
683                 {
684                     PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
685 
686                     if( !shape || ( shape->GetEditFlags() & STRUCT_DELETED ) )
687                         continue;
688 
689                     if( shape->GetLayer() != aLayer )
690                         continue;
691 
692                     SHAPE_POLY_SET drawPoly;
693                     shape->TransformShapeWithClearanceToPolygon( drawPoly, aLayer, 0, maxError,
694                                                                  ERROR_INSIDE );
695                     drawPoly.BooleanIntersection( padPoly, SHAPE_POLY_SET::PM_FAST );
696 
697                     if( !drawPoly.IsEmpty() )
698                         return (FP_SHAPE*) item;
699                 }
700 
701                 return nullptr;
702             };
703 
704     BOARD_COMMIT commit( frame() );
705     PCB_LAYER_ID layer;
706 
707     if( aPad->IsOnLayer( F_Cu ) )
708         layer = F_Cu;
709     else if( aPad->IsOnLayer( B_Cu ) )
710         layer = B_Cu;
711     else
712         layer = *aPad->GetLayerSet().UIOrder();
713 
714     while( FP_SHAPE* fpShape = findNext( layer ) )
715     {
716         commit.Modify( aPad );
717 
718         // We've found an intersecting item.  First convert the pad to a custom-shape
719         // pad (if it isn't already)
720         //
721         if( aPad->GetShape() == PAD_SHAPE::RECT || aPad->GetShape() == PAD_SHAPE::CIRCLE )
722         {
723             aPad->SetAnchorPadShape( aPad->GetShape() );
724         }
725         else if( aPad->GetShape() != PAD_SHAPE::CUSTOM )
726         {
727             // Create a new minimally-sized circular anchor and convert existing pad
728             // to a polygon primitive
729             SHAPE_POLY_SET existingOutline;
730             aPad->TransformShapeWithClearanceToPolygon( existingOutline, layer, 0, maxError,
731                                                         ERROR_INSIDE );
732 
733             aPad->SetAnchorPadShape( PAD_SHAPE::CIRCLE );
734             if( aPad->GetSizeX() > aPad->GetSizeY() )
735                 aPad->SetSizeX( aPad->GetSizeY() );
736 
737             aPad->SetOffset( wxPoint( 0, 0 ) );
738 
739             PCB_SHAPE* shape = new PCB_SHAPE( nullptr, SHAPE_T::POLY );
740             shape->SetFilled( true );
741             shape->SetWidth( 0 );
742             shape->SetPolyShape( existingOutline );
743             shape->Move( - aPad->GetPosition() );
744             shape->Rotate( wxPoint( 0, 0 ), - aPad->GetOrientation() );
745 
746             aPad->AddPrimitive( shape );
747         }
748 
749         aPad->SetShape( PAD_SHAPE::CUSTOM );
750 
751         // Now add the new shape to the primitives list
752         //
753         PCB_SHAPE* pcbShape = new PCB_SHAPE;
754 
755         pcbShape->SetShape( fpShape->GetShape() );
756         pcbShape->SetFilled( fpShape->IsFilled() );
757         pcbShape->SetWidth( fpShape->GetWidth() );
758 
759 
760         switch( pcbShape->GetShape() )
761         {
762         case SHAPE_T::SEGMENT:
763         case SHAPE_T::RECT:
764         case SHAPE_T::CIRCLE:
765             pcbShape->SetStart( fpShape->GetStart() );
766             pcbShape->SetEnd( fpShape->GetEnd() );
767             break;
768 
769         case SHAPE_T::ARC:
770             pcbShape->SetStart( fpShape->GetStart() );
771             pcbShape->SetEnd( fpShape->GetEnd() );
772             pcbShape->SetCenter( fpShape->GetCenter() );
773             break;
774 
775         case SHAPE_T::BEZIER:
776             pcbShape->SetStart( fpShape->GetStart() );
777             pcbShape->SetEnd( fpShape->GetEnd() );
778             pcbShape->SetBezierC1( fpShape->GetBezierC1() );
779             pcbShape->SetBezierC2( fpShape->GetBezierC2() );
780             break;
781 
782         case SHAPE_T::POLY:
783             pcbShape->SetPolyShape( fpShape->GetPolyShape() );
784             break;
785 
786         default:
787             UNIMPLEMENTED_FOR( pcbShape->SHAPE_T_asString() );
788         }
789 
790         pcbShape->Move( - aPad->GetPosition() );
791         pcbShape->Rotate( wxPoint( 0, 0 ), - aPad->GetOrientation() );
792         aPad->AddPrimitive( pcbShape );
793 
794         fpShape->SetFlags( STRUCT_DELETED );
795         commit.Remove( fpShape );
796     }
797 
798     commit.Push(_("Recombine pads") );
799 }
800 
801 
setTransitions()802 void PAD_TOOL::setTransitions()
803 {
804     Go( &PAD_TOOL::pastePadProperties,      PCB_ACTIONS::applyPadSettings.MakeEvent() );
805     Go( &PAD_TOOL::copyPadSettings,         PCB_ACTIONS::copyPadSettings.MakeEvent() );
806     Go( &PAD_TOOL::pushPadSettings,         PCB_ACTIONS::pushPadSettings.MakeEvent() );
807 
808     Go( &PAD_TOOL::PlacePad,                PCB_ACTIONS::placePad.MakeEvent() );
809     Go( &PAD_TOOL::EnumeratePads,           PCB_ACTIONS::enumeratePads.MakeEvent() );
810 
811     Go( &PAD_TOOL::EditPad,                 PCB_ACTIONS::explodePad.MakeEvent() );
812     Go( &PAD_TOOL::EditPad,                 PCB_ACTIONS::recombinePad.MakeEvent() );
813 }
814