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