KiCad PCB EDA Suite
footprint_editor_tools.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2014-2015 CERN
5  * @author Maciej Suminski <maciej.suminski@cern.ch>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #include "footprint_editor_tools.h"
26 #include "kicad_clipboard.h"
27 #include "selection_tool.h"
28 #include "pcb_actions.h"
29 
30 #include <core/optional.h>
31 
32 #include <tool/tool_manager.h>
33 
34 #include <class_draw_panel_gal.h>
35 #include <view/view_controls.h>
36 #include <view/view_group.h>
37 #include <pcb_painter.h>
38 #include <origin_viewitem.h>
39 
40 #include <kicad_plugin.h>
41 #include <pcbnew_id.h>
42 #include <collectors.h>
43 #include <confirm.h>
45 #include <hotkeys.h>
46 #include <bitmaps.h>
47 
48 #include <pcb_edit_frame.h>
49 #include <class_board.h>
50 #include <class_module.h>
51 #include <class_edge_mod.h>
52 #include <board_commit.h>
53 
54 #include <tools/tool_event_utils.h>
55 
56 #include <functional>
57 using namespace std::placeholders;
58 #include <wx/defs.h>
59 
60 // Module editor tools
61 TOOL_ACTION PCB_ACTIONS::placePad( "pcbnew.ModuleEditor.placePad",
62  AS_GLOBAL, 0,
63  _( "Add Pad" ), _( "Add a pad" ), NULL, AF_ACTIVATE );
64 
65 TOOL_ACTION PCB_ACTIONS::createPadFromShapes( "pcbnew.ModuleEditor.createPadFromShapes",
66  AS_CONTEXT, 0,
67  _( "Create Pad from Selected Shapes" ),
68  _( "Creates a custom-shaped pads from a set of selected shapes" ),
69  primitives_to_custom_pad_xpm );
70 
71 TOOL_ACTION PCB_ACTIONS::explodePadToShapes( "pcbnew.ModuleEditor.explodePadToShapes",
72  AS_CONTEXT, 0,
73  _( "Explode Pad to Graphic Shapes" ),
74  _( "Converts a custom-shaped pads to a set of graphical shapes" ),
75  custom_pad_to_primitives_xpm );
76 
77 TOOL_ACTION PCB_ACTIONS::enumeratePads( "pcbnew.ModuleEditor.enumeratePads",
78  AS_GLOBAL, 0,
79  _( "Renumber Pads..." ), _( "Renumber pads by clicking on them in the desired order" ), pad_enumerate_xpm, AF_ACTIVATE );
80 
81 
83  PCB_TOOL( "pcbnew.ModuleEditor" )
84 {
85 }
86 
87 
89 {
90 }
91 
92 
94 {
95 }
96 
97 
98 
100 {
101  struct PAD_PLACER : public INTERACTIVE_PLACER_BASE
102  {
103  std::unique_ptr<BOARD_ITEM> CreateItem() override
104  {
105  D_PAD* pad = new D_PAD ( m_board->m_Modules );
106  m_frame->Import_Pad_Settings( pad, false ); // use the global settings for pad
107  pad->IncrementPadName( true, true );
108  return std::unique_ptr<BOARD_ITEM>( pad );
109  }
110 
111  void PlaceItem( BOARD_ITEM *aItem, BOARD_COMMIT& aCommit ) override
112  {
113  auto pad = dynamic_cast<D_PAD*>( aItem );
114  m_frame->Export_Pad_Settings( pad );
115  aCommit.Add( aItem );
116  }
117  };
118 
119  PAD_PLACER placer;
120 
121  frame()->SetToolID( ID_MODEDIT_PAD_TOOL, wxCURSOR_PENCIL, _( "Add pads" ) );
122 
123  assert( board()->m_Modules );
124 
125  doInteractiveItemPlacement( &placer, _( "Place pad" ), IPO_REPEAT | IPO_SINGLE_CLICK | IPO_ROTATE | IPO_FLIP );
126 
128 
129  return 0;
130 }
131 
132 
134 {
135  if( !board()->m_Modules || !board()->m_Modules->PadsList() )
136  return 0;
137 
138  DIALOG_ENUM_PADS settingsDlg( frame() );
139 
140  if( settingsDlg.ShowModal() != wxID_OK )
141  return 0;
142 
143  Activate();
144 
145  GENERAL_COLLECTOR collector;
146  const KICAD_T types[] = { PCB_PAD_T, EOT };
147 
149  guide.SetIgnoreMTextsMarkedNoShow( true );
150  guide.SetIgnoreMTextsOnBack( true );
151  guide.SetIgnoreMTextsOnFront( true );
152  guide.SetIgnoreModulesVals( true );
153  guide.SetIgnoreModulesRefs( true );
154 
155  int padNumber = settingsDlg.GetStartNumber();
156  wxString padPrefix = settingsDlg.GetPrefix();
157 
158  frame()->SetToolID( ID_MODEDIT_PAD_TOOL, wxCURSOR_HAND,
159  _( "Click on successive pads to renumber them" ) );
160 
162  getViewControls()->ShowCursor( true );
163 
165  VECTOR2I oldCursorPos; // store the previous mouse cursor position, during mouse drag
166  std::list<D_PAD*> selectedPads;
167  BOARD_COMMIT commit( frame() );
168  std::map<wxString, wxString> oldNames;
169  bool isFirstPoint = true; // used to be sure oldCursorPos will be initialized at least once.
170 
171  while( OPT_TOOL_EVENT evt = Wait() )
172  {
173  if( evt->IsDrag( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) )
174  {
175  selectedPads.clear();
176  VECTOR2I cursorPos = getViewControls()->GetCursorPosition();
177 
178  // Be sure the old cursor mouse position was initialized:
179  if( isFirstPoint )
180  {
181  oldCursorPos = cursorPos;
182  isFirstPoint = false;
183  }
184 
185  // wxWidgets deliver mouse move events not frequently enough, resulting in skipping
186  // pads if the user moves cursor too fast. To solve it, create a line that approximates
187  // the mouse move and search pads that are on the line.
188  int distance = ( cursorPos - oldCursorPos ).EuclideanNorm();
189  // Search will be made every 0.1 mm:
190  int segments = distance / int( 0.1*IU_PER_MM ) + 1;
191  const wxPoint line_step( ( cursorPos - oldCursorPos ) / segments );
192 
193  collector.Empty();
194 
195  for( int j = 0; j < segments; ++j )
196  {
197  wxPoint testpoint( cursorPos.x - j * line_step.x,
198  cursorPos.y - j * line_step.y );
199  collector.Collect( board(), types, testpoint, guide );
200 
201  for( int i = 0; i < collector.GetCount(); ++i )
202  {
203  selectedPads.push_back( static_cast<D_PAD*>( collector[i] ) );
204  }
205  }
206 
207  selectedPads.unique();
208 
209  for( D_PAD* pad : selectedPads )
210  {
211  // If pad was not selected, then enumerate it
212  if( !pad->IsSelected() )
213  {
214  commit.Modify( pad );
215 
216  // Rename pad and store the old name
217  wxString newName = wxString::Format( wxT( "%s%d" ), padPrefix.c_str(), padNumber++ );
218  oldNames[newName] = pad->GetName();
219  pad->SetName( newName );
220  pad->SetSelected();
221  getView()->Update( pad );
222  }
223 
224  // ..or restore the old name if it was enumerated and clicked again
225  else if( pad->IsSelected() && evt->IsClick( BUT_LEFT ) )
226  {
227  auto it = oldNames.find( pad->GetName() );
228  wxASSERT( it != oldNames.end() );
229 
230  if( it != oldNames.end() )
231  {
232  pad->SetName( it->second );
233  oldNames.erase( it );
234  }
235 
236  pad->ClearSelected();
237  getView()->Update( pad );
238  }
239  }
240  }
241 
242  else if( ( evt->IsKeyPressed() && evt->KeyCode() == WXK_RETURN ) ||
243  evt->IsDblClick( BUT_LEFT ) )
244  {
245  commit.Push( _( "Renumber pads" ) );
246  break;
247  }
248 
249  // This is a cancel-current-action (ie: <esc>).
250  // Note that this must go before IsCancelInteractive() as it also checks IsCancel().
251  else if( evt->IsCancel() )
252  {
253  // Clear current selection list to avoid selection of deleted items
255 
256  commit.Revert();
257  break;
258  }
259 
260  // Now that cancel-current-action has been handled, check for cancel-tool.
261  else if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
262  {
263  commit.Push( _( "Renumber pads" ) );
264  break;
265  }
266 
267  else if( evt->IsClick( BUT_RIGHT ) )
268  {
270  }
271 
272  // Prepare the next loop by updating the old cursor mouse position
273  // to this last mouse cursor position
274  oldCursorPos = getViewControls()->GetCursorPosition();
275  }
276 
277  for( auto p : board()->m_Modules->Pads() )
278  {
279  p->ClearSelected();
280  view->Update( p );
281  }
282 
284  frame()->GetGalCanvas()->SetCursor( wxCURSOR_ARROW );
285 
286  return 0;
287 }
288 
289 
291 {
292  SELECTION& selection = m_toolMgr->GetTool<SELECTION_TOOL>()->GetSelection();
293  BOARD_COMMIT commit( frame() );
294 
295  if( selection.Size() != 1 )
296  return 0;
297 
298  if( selection[0]->Type() != PCB_PAD_T )
299  return 0;
300 
301  auto pad = static_cast<D_PAD*>( selection[0] );
302 
303  if( pad->GetShape() != PAD_SHAPE_CUSTOM )
304  return 0;
305 
306  commit.Modify( pad );
307 
308  wxPoint anchor = pad->GetPosition();
309 
310  for( auto prim : pad->GetPrimitives() )
311  {
312  auto ds = new EDGE_MODULE( board()->m_Modules );
313 
314  prim.ExportTo( ds ); // ExportTo exports to a DRAWSEGMENT
315  // Fix an arbitray draw layer for this EDGE_MODULE
316  ds->SetLayer( Dwgs_User ); //pad->GetLayer() );
317  ds->Move( anchor );
318 
319  commit.Add( ds );
320  }
321 
322  pad->SetShape( pad->GetAnchorPadShape() );
323  // Cleanup the pad primitives data, because the initial pad was a custom
324  // shaped pad, and it contains primitives, that does not exist in non custom pads,
325  // and can create issues later:
326  if( pad->GetShape() != PAD_SHAPE_CUSTOM ) // should be always the case
327  {
328  pad->DeletePrimitivesList();
329  }
330 
331  commit.Push( _("Explode pad to shapes") );
332 
334 
335  return 0;
336 }
337 
338 
340 {
341  SELECTION& selection = m_toolMgr->GetTool<SELECTION_TOOL>()->GetSelection();
342 
343  std::unique_ptr<D_PAD> pad ( new D_PAD ( board()->m_Modules ) );
344  D_PAD *refPad = nullptr;
345  bool multipleRefPadsFound = false;
346  bool illegalItemsFound = false;
347 
348  std::vector<PAD_CS_PRIMITIVE> shapes;
349 
350  BOARD_COMMIT commit( frame() );
351 
352  for( auto item : selection )
353  {
354  switch( item->Type() )
355  {
356  case PCB_PAD_T:
357  {
358  if( refPad )
359  multipleRefPadsFound = true;
360 
361  refPad = static_cast<D_PAD*>( item );
362  break;
363  }
364 
365  case PCB_MODULE_EDGE_T:
366  {
367  auto em = static_cast<EDGE_MODULE*> ( item );
368 
369  // Currently, S_CURVE shape is not supported. so warn the user
370  if( em->GetShape() == S_CURVE )
371  {
372  illegalItemsFound = true;
373  break;
374  }
375 
376  PAD_CS_PRIMITIVE shape( em->GetShape() );
377  shape.m_Start = em->GetStart();
378  shape.m_End = em->GetEnd();
379  shape.m_Radius = em->GetRadius();
380  shape.m_Thickness = em->GetWidth();
381  shape.m_ArcAngle = em->GetAngle();
382  shape.m_Poly = em->BuildPolyPointsList();
383 
384  shapes.push_back(shape);
385 
386  break;
387  }
388 
389  default:
390  {
391  illegalItemsFound = true;
392  break;
393  }
394  }
395  }
396 
397  if( refPad && selection.Size() == 1 )
398  {
399  // don't convert a pad into itself...
400  return 0;
401  }
402 
403  if( multipleRefPadsFound )
404  {
406  _( "Cannot convert items to a custom-shaped pad:\n"
407  "selection contains more than one reference pad." ) );
408  return 0;
409  }
410 
411  if( illegalItemsFound )
412  {
414  _( "Cannot convert items to a custom-shaped pad:\n"
415  "selection contains unsupported items.\n"
416  "Only graphical lines, circles, arcs and polygons are allowed." ) );
417  return 0;
418  }
419 
420  if( refPad )
421  {
422  pad.reset( static_cast<D_PAD*>( refPad->Clone() ) );
423 
424  if( refPad->GetShape() == PAD_SHAPE_RECT )
425  pad->SetAnchorPadShape( PAD_SHAPE_RECT );
426 
427  // ignore the pad orientation and offset for the moment. Makes more trouble than it's worth.
428  pad->SetOrientation( 0 );
429  pad->SetOffset( wxPoint( 0, 0 ) );
430  }
431  else
432  {
433  // Create a default pad anchor:
434  pad->SetAnchorPadShape( PAD_SHAPE_CIRCLE );
435  pad->SetAttribute( PAD_ATTRIB_SMD );
436  pad->SetLayerSet( D_PAD::SMDMask() );
437  int radius = Millimeter2iu( 0.2 );
438  pad->SetSize ( wxSize( radius, radius ) );
439  pad->IncrementPadName( true, true );
440  pad->SetOrientation( 0 );
441  }
442 
443  pad->SetShape ( PAD_SHAPE_CUSTOM );
444 
445  OPT<VECTOR2I> anchor;
446  VECTOR2I tmp;
447 
448  if( refPad )
449  {
450  anchor = VECTOR2I( pad->GetPosition() );
451  }
452  else if( pad->GetBestAnchorPosition( tmp ) )
453  {
454  anchor = tmp;
455  }
456 
457  if( !anchor )
458  {
460  _( "Cannot convert items to a custom-shaped pad:\n"
461  "unable to determine the anchor point position.\n"
462  "Consider adding a small anchor pad to the selection and try again.") );
463  return 0;
464  }
465 
466 
467  // relocate the shapes, they are relative to the anchor pad position
468  for( auto& shape : shapes )
469  {
470  shape.Move( wxPoint( -anchor->x, -anchor->y ) );
471  }
472 
473 
474  pad->SetPosition( wxPoint( anchor->x, anchor->y ) );
475  pad->AddPrimitives( shapes );
476  pad->ClearFlags();
477 
478  bool result = pad->MergePrimitivesAsPolygon();
479 
480  if( !result )
481  {
483  _( "Cannot convert items to a custom-shaped pad:\n"
484  "selected items do not form a single solid shape.") );
485  return 0;
486  }
487 
488  auto padPtr = pad.release();
489 
490  commit.Add( padPtr );
491  for ( auto item : selection )
492  {
493  commit.Remove( item );
494  }
495 
497  commit.Push(_("Create Pad from Selected Shapes") );
498  m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, padPtr );
499 
500  return 0;
501 }
502 
504 {
509 }
double EuclideanNorm(const wxPoint &vector)
Euclidean norm of a 2D vector.
Definition: trigo.h:112
void Empty()
Function Empty sets the list to empty.
Definition: collector.h:123
static TOOL_ACTION selectionClear
Clears the current selection.
Definition: pcb_actions.h:53
virtual void ShowCursor(bool aEnabled)
Function ShowCursor() Enables or disables display of cursor.
bool IncrementPadName(bool aSkipUnconnectable, bool aFillSequenceGaps)
Function IncrementPadName.
Definition: class_pad.cpp:531
void Reset(RESET_REASON aReason) override
Function Reset() Brings the tool to a known, initial state.
int GetCount() const
Function GetCount returns the number of objects in the list.
Definition: collector.h:114
EDA_ITEM * Clone() const override
Function Clone creates a duplicate of this item with linked list members set to NULL.
Definition: class_pad.cpp:1243
COMMIT & Modify(EDA_ITEM *aItem)
Modifies a given item in the model.
Definition: commit.h:103
virtual void SetToolID(int aId, int aCursor, const wxString &aToolMsg) override
Set the tool command ID to aId and sets the cursor to aCursor.
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Function DisplayErrorMessage displays an error message with aMessage.
Definition: confirm.cpp:259
TOOL_MENU m_menu
Menu model displayed by the tool.
Definition: pcb_tool.h:148
This file is part of the common library.
Class BOARD_ITEM is a base class for any item which can be embedded within the BOARD container class...
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition: class_pad.cpp:105
COMMIT & Add(EDA_ITEM *aItem)
Adds a new item to the model
Definition: commit.h:78
VIEW_CONTROLS class definition.
void Collect(BOARD_ITEM *aItem, const KICAD_T aScanList[], const wxPoint &aRefPos, const COLLECTORS_GUIDE &aGuide)
Scan a BOARD_ITEM using this class&#39;s Inspector method, which does the collection. ...
Definition: collectors.cpp:482
void SetIgnoreModulesVals(bool ignore)
Definition: collectors.h:595
Class SELECTION_TOOL.
Class BOARD to handle a board.
virtual void Revert() override
Revertes the commit by restoring the modifed items state.
wxPoint m_Start
angle of an arc, from its starting point, in 0.1 deg
Definition: class_pad.h:100
static TOOL_ACTION explodePadToShapes
Definition: pcb_actions.h:317
TOOL_MANAGER * m_toolMgr
Definition: tool_base.h:215
static TOOL_ACTION enumeratePads
Tool for quick pad enumeration.
Definition: pcb_actions.h:320
Smd pad, appears on the solder paste layer (default)
Definition: pad_shapes.h:61
OPT_TOOL_EVENT Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
Function Wait()
wxString GetPrefix() const
Returns common prefix for all enumerated pads.
void SetIgnoreModulesRefs(bool ignore)
Definition: collectors.h:601
bool RunAction(const std::string &aActionName, bool aNow=false, T aParam=NULL)
Function RunAction() Runs the specified action.
Definition: tool_manager.h:125
static TOOL_ACTION placePad
Activation of the drawing tool (placing a PAD)
Definition: pcb_actions.h:314
class D_PAD, a pad in a footprint
Definition: typeinfo.h:90
int CreatePadFromShapes(const TOOL_EVENT &aEvent)
Function CreatePadFromShapes()
VECTOR2< int > VECTOR2I
Definition: vector2d.h:587
PAD_SHAPE_T GetShape() const
Function GetShape.
Definition: class_pad.h:216
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Function Go()
class EDGE_MODULE, a footprint edge
Definition: typeinfo.h:94
Implementing DIALOG_ENUM_PADS_BASE.
search types array terminator (End Of Types)
Definition: typeinfo.h:82
KICAD_T
Enum KICAD_T is the set of class identification values, stored in EDA_ITEM::m_StructType.
Definition: typeinfo.h:78
Pcbnew hotkeys.
int ExplodePadToShapes(const TOOL_EVENT &aEvent)
Function ExplodePadToShapes()
GENERAL_COLLECTORS_GUIDE GetCollectorsGuide()
Function GetCollectorsGuide.
int PlacePad(const TOOL_EVENT &aEvent)
Function PlacePad() Places a pad in module editor.
void SetIgnoreMTextsOnBack(bool ignore)
Definition: collectors.h:553
void SetIgnoreMTextsOnFront(bool ignore)
Definition: collectors.h:559
VECTOR2D GetCursorPosition() const
Returns the current cursor position in world coordinates.
PCB_EDIT_FRAME * frame() const
Definition: pcb_tool.h:139
Class TOOL_EVENT.
Definition: tool_event.h:168
Helper class to handle a primitive (basic shape: polygon, segment, circle or arc) to build a custom p...
Definition: class_pad.h:91
void setTransitions() override
Sets up handlers for various events.
VIEW_GROUP extends VIEW_ITEM by possibility of grouping items into a single object.
COMMIT & Remove(EDA_ITEM *aItem)
Removes a new item from the model
Definition: commit.h:90
const SELECTION & selection() const
Definition: pcb_tool.cpp:245
Bezier Curve.
bool IsCancelInteractive(const TOOL_EVENT &aEvt)
Function IsCancelInteractive()
All active tools
Definition: tool_event.h:144
void SetIgnoreMTextsMarkedNoShow(bool ignore)
Definition: collectors.h:547
virtual void Update(VIEW_ITEM *aItem, int aUpdateFlags)
For dynamic VIEWs, informs the associated VIEW that the graphical representation of this item has cha...
Definition: view.cpp:1538
Subclass of DIALOG_ENUM_PADS_BASE, which is generated by wxFormBuilder.
KIGFX::VIEW_CONTROLS * getViewControls() const
Function getViewControls()
Definition: tool_base.cpp:41
KIGFX::VIEW * getView() const
Function getView()
Definition: tool_base.cpp:35
static TOOL_ACTION createPadFromShapes
Definition: pcb_actions.h:316
virtual void SetNoToolSelected()
Select the ID_NO_TOOL_SELECTED id tool (Idle tool)
int GetStartNumber() const
Returns the starting number that is going to be used for the first enumerated pad.
int EnumeratePads(const TOOL_EVENT &aEvent)
Function EnumeratePads() Tool for quick pad enumeration.
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:205
KIGFX::PCB_VIEW * view() const
Definition: pcb_tool.h:137
Used when the right click button is pressed, or when the select tool is in effect.
Definition: collectors.h:245
Class TOOL_ACTION.
Definition: tool_action.h:46
size_t i
Definition: json11.cpp:597
static TOOL_ACTION selectItem
Selects an item (specified as the event parameter).
Definition: pcb_actions.h:56
virtual void Push(const wxString &aMessage=wxT("A commit"), bool aCreateUndoEntry=true, bool aSetDirtyBit=true) override
Executes the changes.
RESET_REASON
Determines the reason of reset for a tool
Definition: tool_base.h:80
boost::optional< T > OPT
Definition: optional.h:7
void Activate()
Function Activate() Runs the tool.
EDA_DRAW_PANEL_GAL * GetGalCanvas() const
Return a pointer to GAL-based canvas of given EDA draw frame.
Definition: draw_frame.h:925
int Size() const
Returns the number of selected parts.
Definition: selection.h:122
Module description (excepted pads)
void doInteractiveItemPlacement(INTERACTIVE_PLACER_BASE *aPlacer, const wxString &aCommitMessage, int aOptions=IPO_ROTATE|IPO_FLIP|IPO_REPEAT)
Helper function for performing a common interactive idiom: wait for a left click, place an item there...
Definition: pcb_tool.cpp:39
Class VIEW.
Definition: view.h:61
A general implementation of a COLLECTORS_GUIDE.
Definition: collectors.h:391
KIGFX::VIEW * GetView() const
Definition: tool_manager.h:252
EDGE_MODULE class definition.
void ShowContextMenu(SELECTION &aSelection)
Function ShowContextMenu.
Definition: tool_menu.cpp:62
OPT< TOOL_EVENT > OPT_TOOL_EVENT
Definition: tool_event.h:495
BOARD * board() const
Definition: pcb_tool.h:140