1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2014 CERN
5 * Copyright (C) 2014-2021 KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <maciej.suminski@cern.ch>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26 #include <functional>
27 #include <memory>
28
29 #include <advanced_config.h>
30 #include "board_editor_control.h"
31 #include <bitmaps.h>
32 #include <board.h>
33 #include <board_commit.h>
34 #include <board_design_settings.h>
35 #include <pcb_group.h>
36 #include <footprint.h>
37 #include <pad.h>
38 #include <pcb_target.h>
39 #include <pcb_track.h>
40 #include <zone.h>
41 #include <pcb_marker.h>
42 #include <confirm.h>
43 #include <dialogs/dialog_page_settings.h>
44 #include <dialogs/dialog_update_pcb.h>
45 #include <kiface_base.h>
46 #include <kiway.h>
47 #include <netlist_reader/pcb_netlist.h>
48 #include <origin_viewitem.h>
49 #include <pcb_edit_frame.h>
50 #include <pcbnew_id.h>
51 #include <pcbnew_settings.h>
52 #include <project.h>
53 #include <project/project_file.h> // LAST_PATH_TYPE
54 #include <tool/tool_manager.h>
55 #include <tool/tool_event.h>
56 #include <tools/drawing_tool.h>
57 #include <tools/pcb_actions.h>
58 #include <tools/pcb_picker_tool.h>
59 #include <tools/pcb_selection_tool.h>
60 #include <tools/edit_tool.h>
61 #include <tools/tool_event_utils.h>
62 #include <router/router_tool.h>
63 #include <view/view_controls.h>
64 #include <view/view_group.h>
65 #include <wildcards_and_files_ext.h>
66 #include <drawing_sheet/ds_proxy_undo_item.h>
67 #include <footprint_edit_frame.h>
68 #include <wx/filedlg.h>
69 #include <wx/log.h>
70
71 using namespace std::placeholders;
72
73
74 class ZONE_CONTEXT_MENU : public ACTION_MENU
75 {
76 public:
ZONE_CONTEXT_MENU()77 ZONE_CONTEXT_MENU() :
78 ACTION_MENU( true )
79 {
80 SetIcon( BITMAPS::add_zone );
81 SetTitle( _( "Zones" ) );
82
83 Add( PCB_ACTIONS::zoneFill );
84 Add( PCB_ACTIONS::zoneFillAll );
85 Add( PCB_ACTIONS::zoneUnfill );
86 Add( PCB_ACTIONS::zoneUnfillAll );
87
88 AppendSeparator();
89
90 Add( PCB_ACTIONS::zoneMerge );
91 Add( PCB_ACTIONS::zoneDuplicate );
92 Add( PCB_ACTIONS::drawZoneCutout );
93 Add( PCB_ACTIONS::drawSimilarZone );
94 }
95
96
97 protected:
create() const98 ACTION_MENU* create() const override
99 {
100 return new ZONE_CONTEXT_MENU();
101 }
102 };
103
104
105 class LOCK_CONTEXT_MENU : public ACTION_MENU
106 {
107 public:
LOCK_CONTEXT_MENU()108 LOCK_CONTEXT_MENU() :
109 ACTION_MENU( true )
110 {
111 SetIcon( BITMAPS::locked );
112 SetTitle( _( "Locking" ) );
113
114 Add( PCB_ACTIONS::lock );
115 Add( PCB_ACTIONS::unlock );
116 Add( PCB_ACTIONS::toggleLock );
117 }
118
create() const119 ACTION_MENU* create() const override
120 {
121 return new LOCK_CONTEXT_MENU();
122 }
123 };
124
125
126 /**
127 * Helper widget to add controls to a wxFileDialog to set netlist configuration options.
128 */
129 class NETLIST_OPTIONS_HELPER : public wxPanel
130 {
131 public:
NETLIST_OPTIONS_HELPER(wxWindow * aParent)132 NETLIST_OPTIONS_HELPER( wxWindow* aParent )
133 : wxPanel( aParent )
134 {
135 m_cbOmitExtras = new wxCheckBox( this, wxID_ANY, _( "Omit extra information" ) );
136 m_cbOmitNets = new wxCheckBox( this, wxID_ANY, _( "Omit nets" ) );
137 m_cbOmitFpUuids = new wxCheckBox( this, wxID_ANY,
138 _( "Do not prefix path with footprint UUID." ) );
139
140 wxBoxSizer* sizer = new wxBoxSizer( wxHORIZONTAL );
141 sizer->Add( m_cbOmitExtras, 0, wxALL, 5 );
142 sizer->Add( m_cbOmitNets, 0, wxALL, 5 );
143 sizer->Add( m_cbOmitFpUuids, 0, wxALL, 5 );
144
145 SetSizerAndFit( sizer );
146 }
147
GetNetlistOptions() const148 int GetNetlistOptions() const
149 {
150 int options = 0;
151
152 if( m_cbOmitExtras->GetValue() )
153 options |= CTL_OMIT_EXTRA;
154
155 if( m_cbOmitNets->GetValue() )
156 options |= CTL_OMIT_NETS;
157
158 if( m_cbOmitFpUuids->GetValue() )
159 options |= CTL_OMIT_FP_UUID;
160
161 return options;
162 }
163
Create(wxWindow * aParent)164 static wxWindow* Create( wxWindow* aParent )
165 {
166 return new NETLIST_OPTIONS_HELPER( aParent );
167 }
168
169 protected:
170 wxCheckBox* m_cbOmitExtras;
171 wxCheckBox* m_cbOmitNets;
172 wxCheckBox* m_cbOmitFpUuids;
173 };
174
175
BOARD_EDITOR_CONTROL()176 BOARD_EDITOR_CONTROL::BOARD_EDITOR_CONTROL() :
177 PCB_TOOL_BASE( "pcbnew.EditorControl" ),
178 m_frame( nullptr ),
179 m_inPlaceFootprint( false ),
180 m_placingFootprint( false ),
181 m_inPlaceTarget( false )
182 {
183 m_placeOrigin = std::make_unique<KIGFX::ORIGIN_VIEWITEM>( KIGFX::COLOR4D( 0.8, 0.0, 0.0, 1.0 ),
184 KIGFX::ORIGIN_VIEWITEM::CIRCLE_CROSS );
185 }
186
187
~BOARD_EDITOR_CONTROL()188 BOARD_EDITOR_CONTROL::~BOARD_EDITOR_CONTROL()
189 {
190 }
191
192
Reset(RESET_REASON aReason)193 void BOARD_EDITOR_CONTROL::Reset( RESET_REASON aReason )
194 {
195 m_frame = getEditFrame<PCB_EDIT_FRAME>();
196
197 if( aReason == MODEL_RELOAD || aReason == GAL_SWITCH )
198 {
199 m_placeOrigin->SetPosition( getModel<BOARD>()->GetDesignSettings().GetAuxOrigin() );
200 getView()->Remove( m_placeOrigin.get() );
201 getView()->Add( m_placeOrigin.get() );
202 }
203 }
204
205
Init()206 bool BOARD_EDITOR_CONTROL::Init()
207 {
208 auto activeToolCondition =
209 [this]( const SELECTION& aSel )
210 {
211 return ( !m_frame->ToolStackIsEmpty() );
212 };
213
214 auto inactiveStateCondition =
215 [this]( const SELECTION& aSel )
216 {
217 return ( m_frame->ToolStackIsEmpty() && aSel.Size() == 0 );
218 };
219
220 auto placeModuleCondition =
221 [this]( const SELECTION& aSel )
222 {
223 return m_frame->IsCurrentTool( PCB_ACTIONS::placeFootprint ) && aSel.GetSize() == 0;
224 };
225
226 auto& ctxMenu = m_menu.GetMenu();
227
228 // "Cancel" goes at the top of the context menu when a tool is active
229 ctxMenu.AddItem( ACTIONS::cancelInteractive, activeToolCondition, 1 );
230 ctxMenu.AddSeparator( 1 );
231
232 // "Get and Place Footprint" should be available for Place Footprint tool
233 ctxMenu.AddItem( PCB_ACTIONS::getAndPlace, placeModuleCondition, 1000 );
234 ctxMenu.AddSeparator( 1000 );
235
236 // Finally, add the standard zoom & grid items
237 getEditFrame<PCB_BASE_FRAME>()->AddStandardSubMenus( m_menu );
238
239 auto zoneMenu = std::make_shared<ZONE_CONTEXT_MENU>();
240 zoneMenu->SetTool( this );
241
242 auto lockMenu = std::make_shared<LOCK_CONTEXT_MENU>();
243 lockMenu->SetTool( this );
244
245 // Add the PCB control menus to relevant other tools
246
247 PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
248
249 if( selTool )
250 {
251 auto& toolMenu = selTool->GetToolMenu();
252 auto& menu = toolMenu.GetMenu();
253
254 // Add "Get and Place Footprint" when Selection tool is in an inactive state
255 menu.AddItem( PCB_ACTIONS::getAndPlace, inactiveStateCondition );
256 menu.AddSeparator();
257
258 toolMenu.AddSubMenu( zoneMenu );
259 toolMenu.AddSubMenu( lockMenu );
260
261 menu.AddMenu( lockMenu.get(), SELECTION_CONDITIONS::NotEmpty, 100 );
262
263 menu.AddMenu( zoneMenu.get(), SELECTION_CONDITIONS::OnlyType( PCB_ZONE_T ), 200 );
264 }
265
266 DRAWING_TOOL* drawingTool = m_toolMgr->GetTool<DRAWING_TOOL>();
267
268 if( drawingTool )
269 {
270 auto& toolMenu = drawingTool->GetToolMenu();
271 auto& menu = toolMenu.GetMenu();
272
273 toolMenu.AddSubMenu( zoneMenu );
274
275 // Functor to say if the PCB_EDIT_FRAME is in a given mode
276 // Capture the tool pointer and tool mode by value
277 auto toolActiveFunctor =
278 [=]( DRAWING_TOOL::MODE aMode )
279 {
280 return [=]( const SELECTION& sel )
281 {
282 return drawingTool->GetDrawingMode() == aMode;
283 };
284 };
285
286 menu.AddMenu( zoneMenu.get(), toolActiveFunctor( DRAWING_TOOL::MODE::ZONE ), 200 );
287 }
288
289 return true;
290 }
291
292
New(const TOOL_EVENT & aEvent)293 int BOARD_EDITOR_CONTROL::New( const TOOL_EVENT& aEvent )
294 {
295 m_frame->Files_io_from_id( ID_NEW_BOARD );
296 return 0;
297 }
298
299
Open(const TOOL_EVENT & aEvent)300 int BOARD_EDITOR_CONTROL::Open( const TOOL_EVENT& aEvent )
301 {
302 m_frame->Files_io_from_id( ID_LOAD_FILE );
303 return 0;
304 }
305
306
Save(const TOOL_EVENT & aEvent)307 int BOARD_EDITOR_CONTROL::Save( const TOOL_EVENT& aEvent )
308 {
309 m_frame->Files_io_from_id( ID_SAVE_BOARD );
310 return 0;
311 }
312
313
SaveAs(const TOOL_EVENT & aEvent)314 int BOARD_EDITOR_CONTROL::SaveAs( const TOOL_EVENT& aEvent )
315 {
316 m_frame->Files_io_from_id( ID_SAVE_BOARD_AS );
317 return 0;
318 }
319
320
SaveCopyAs(const TOOL_EVENT & aEvent)321 int BOARD_EDITOR_CONTROL::SaveCopyAs( const TOOL_EVENT& aEvent )
322 {
323 m_frame->Files_io_from_id( ID_COPY_BOARD_AS );
324 return 0;
325 }
326
327
PageSettings(const TOOL_EVENT & aEvent)328 int BOARD_EDITOR_CONTROL::PageSettings( const TOOL_EVENT& aEvent )
329 {
330 PICKED_ITEMS_LIST undoCmd;
331 DS_PROXY_UNDO_ITEM* undoItem = new DS_PROXY_UNDO_ITEM( m_frame );
332 ITEM_PICKER wrapper( nullptr, undoItem, UNDO_REDO::PAGESETTINGS );
333
334 undoCmd.PushItem( wrapper );
335 m_frame->SaveCopyInUndoList( undoCmd, UNDO_REDO::PAGESETTINGS );
336
337 DIALOG_PAGES_SETTINGS dlg( m_frame, IU_PER_MILS, wxSize( MAX_PAGE_SIZE_PCBNEW_MILS,
338 MAX_PAGE_SIZE_PCBNEW_MILS ) );
339 dlg.SetWksFileName( BASE_SCREEN::m_DrawingSheetFileName );
340
341 if( dlg.ShowModal() == wxID_OK )
342 m_frame->OnModify();
343 else
344 m_frame->RollbackFromUndo();
345
346 return 0;
347 }
348
349
Plot(const TOOL_EVENT & aEvent)350 int BOARD_EDITOR_CONTROL::Plot( const TOOL_EVENT& aEvent )
351 {
352 m_frame->ToPlotter( ID_GEN_PLOT );
353 return 0;
354 }
355
356
Find(const TOOL_EVENT & aEvent)357 int BOARD_EDITOR_CONTROL::Find( const TOOL_EVENT& aEvent )
358 {
359 m_frame->ShowFindDialog();
360 return 0;
361 }
362
363
FindNext(const TOOL_EVENT & aEvent)364 int BOARD_EDITOR_CONTROL::FindNext( const TOOL_EVENT& aEvent )
365 {
366 m_frame->FindNext();
367 return 0;
368 }
369
370
BoardSetup(const TOOL_EVENT & aEvent)371 int BOARD_EDITOR_CONTROL::BoardSetup( const TOOL_EVENT& aEvent )
372 {
373 getEditFrame<PCB_EDIT_FRAME>()->ShowBoardSetupDialog();
374 return 0;
375 }
376
377
ImportNetlist(const TOOL_EVENT & aEvent)378 int BOARD_EDITOR_CONTROL::ImportNetlist( const TOOL_EVENT& aEvent )
379 {
380 getEditFrame<PCB_EDIT_FRAME>()->InstallNetlistFrame();
381 return 0;
382 }
383
384
ImportSpecctraSession(const TOOL_EVENT & aEvent)385 int BOARD_EDITOR_CONTROL::ImportSpecctraSession( const TOOL_EVENT& aEvent )
386 {
387 wxString fullFileName = frame()->GetBoard()->GetFileName();
388 wxString path;
389 wxString name;
390 wxString ext;
391
392 wxFileName::SplitPath( fullFileName, &path, &name, &ext );
393 name += wxT( "." ) + SpecctraSessionFileExtension;
394
395 fullFileName = wxFileSelector( _( "Specctra Session File" ), path, name,
396 wxT( "." ) + SpecctraSessionFileExtension,
397 SpecctraSessionFileWildcard(), wxFD_OPEN | wxFD_CHANGE_DIR,
398 frame() );
399
400 if( !fullFileName.IsEmpty() )
401 getEditFrame<PCB_EDIT_FRAME>()->ImportSpecctraSession( fullFileName );
402
403 return 0;
404 }
405
406
ExportSpecctraDSN(const TOOL_EVENT & aEvent)407 int BOARD_EDITOR_CONTROL::ExportSpecctraDSN( const TOOL_EVENT& aEvent )
408 {
409 wxString fullFileName = m_frame->GetLastPath( LAST_PATH_SPECCTRADSN );
410 wxFileName fn;
411
412 if( fullFileName.IsEmpty() )
413 {
414 fn = m_frame->GetBoard()->GetFileName();
415 fn.SetExt( SpecctraDsnFileExtension );
416 }
417 else
418 {
419 fn = fullFileName;
420 }
421
422 fullFileName = wxFileSelector( _( "Specctra DSN File" ), fn.GetPath(), fn.GetFullName(),
423 SpecctraDsnFileExtension, SpecctraDsnFileWildcard(),
424 wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxFD_CHANGE_DIR, frame() );
425
426 if( !fullFileName.IsEmpty() )
427 {
428 m_frame->SetLastPath( LAST_PATH_SPECCTRADSN, fullFileName );
429 getEditFrame<PCB_EDIT_FRAME>()->ExportSpecctraFile( fullFileName );
430 }
431
432 return 0;
433 }
434
435
ExportNetlist(const TOOL_EVENT & aEvent)436 int BOARD_EDITOR_CONTROL::ExportNetlist( const TOOL_EVENT& aEvent )
437 {
438 wxCHECK( m_frame, 0 );
439
440 wxFileName fn = m_frame->Prj().GetProjectFullName();
441
442 // Use a different file extension for the board netlist so the schematic netlist file
443 // is accidentally overwritten.
444 fn.SetExt( "pcb_net" );
445
446 wxFileDialog dlg( m_frame, _( "Export Board Netlist" ), fn.GetPath(), fn.GetFullName(),
447 _( "KiCad board netlist files" ) + AddFileExtListToFilter( { "pcb_net" } ),
448 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
449
450 dlg.SetExtraControlCreator( &NETLIST_OPTIONS_HELPER::Create );
451
452 if( dlg.ShowModal() == wxID_CANCEL )
453 return 0;
454
455 fn = dlg.GetPath();
456
457 if( !fn.IsDirWritable() )
458 {
459 wxString msg;
460
461 msg.Printf( _( "Path `%s` is read only." ), fn.GetPath() );
462 wxMessageDialog( m_frame, msg, _( "I/O Error" ), wxOK | wxCENTER | wxICON_EXCLAMATION );
463 return 0;
464 }
465
466 const NETLIST_OPTIONS_HELPER* noh =
467 dynamic_cast<const NETLIST_OPTIONS_HELPER*>( dlg.GetExtraControl() );
468 wxCHECK( noh, 0 );
469
470 NETLIST netlist;
471
472 for( const FOOTPRINT* footprint : board()->Footprints() )
473 {
474 COMPONENT* component = new COMPONENT( footprint->GetFPID(), footprint->GetReference(),
475 footprint->GetValue(), footprint->GetPath(),
476 { footprint->m_Uuid } );
477
478 for( const PAD* pad : footprint->Pads() )
479 {
480 const wxString& netname = pad->GetShortNetname();
481
482 if( !netname.IsEmpty() )
483 {
484 component->AddNet( pad->GetNumber(), netname, pad->GetPinFunction(),
485 pad->GetPinType() );
486 }
487 }
488
489 netlist.AddComponent( component );
490 }
491
492 FILE_OUTPUTFORMATTER formatter( fn.GetFullPath() );
493
494 netlist.Format( "pcb_netlist", &formatter, 0, noh->GetNetlistOptions() );
495
496 return 0;
497 }
498
499
GenerateFabFiles(const TOOL_EVENT & aEvent)500 int BOARD_EDITOR_CONTROL::GenerateFabFiles( const TOOL_EVENT& aEvent )
501 {
502 wxCommandEvent dummy;
503
504 if( aEvent.IsAction( &PCB_ACTIONS::generateGerbers ) )
505 m_frame->ToPlotter( ID_GEN_PLOT_GERBER );
506 else if( aEvent.IsAction( &PCB_ACTIONS::generateReportFile ) )
507 m_frame->GenFootprintsReport( dummy );
508 else if( aEvent.IsAction( &PCB_ACTIONS::generateD356File ) )
509 m_frame->GenD356File( dummy );
510 else if( aEvent.IsAction( &PCB_ACTIONS::generateBOM ) )
511 m_frame->RecreateBOMFileFromBoard( dummy );
512 else
513 wxFAIL_MSG( "GenerateFabFiles(): unexpected request" );
514
515 return 0;
516 }
517
518
RepairBoard(const TOOL_EVENT & aEvent)519 int BOARD_EDITOR_CONTROL::RepairBoard( const TOOL_EVENT& aEvent )
520 {
521 int errors = 0;
522 wxString details;
523 bool quiet = aEvent.Parameter<bool>();
524
525 // Repair duplicate IDs and missing nets.
526 std::set<KIID> ids;
527 int duplicates = 0;
528
529 auto processItem =
530 [&]( EDA_ITEM* aItem )
531 {
532 if( ids.count( aItem->m_Uuid ) )
533 {
534 duplicates++;
535 const_cast<KIID&>( aItem->m_Uuid ) = KIID();
536 }
537
538 ids.insert( aItem->m_Uuid );
539
540 BOARD_CONNECTED_ITEM* cItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( aItem );
541
542 if( cItem && cItem->GetNetCode() )
543 {
544 NETINFO_ITEM* netinfo = cItem->GetNet();
545
546 if( netinfo && !board()->FindNet( netinfo->GetNetname() ) )
547 {
548 board()->Add( netinfo );
549
550 details += wxString::Format( _( "Orphaned net %s re-parented.\n" ),
551 netinfo->GetNetname() );
552 errors++;
553 }
554 }
555 };
556
557 // Footprint IDs are the most important, so give them the first crack at "claiming" a
558 // particular KIID.
559
560 for( FOOTPRINT* footprint : board()->Footprints() )
561 processItem( footprint );
562
563 // After that the principal use is for DRC marker pointers, which are most likely to pads
564 // or tracks.
565
566 for( FOOTPRINT* footprint : board()->Footprints() )
567 {
568 for( PAD* pad : footprint->Pads() )
569 processItem( pad );
570 }
571
572 for( PCB_TRACK* track : board()->Tracks() )
573 processItem( track );
574
575 // From here out I don't think order matters much.
576
577 for( FOOTPRINT* footprint : board()->Footprints() )
578 {
579 processItem( &footprint->Reference() );
580 processItem( &footprint->Value() );
581
582 for( BOARD_ITEM* item : footprint->GraphicalItems() )
583 processItem( item );
584
585 for( ZONE* zone : footprint->Zones() )
586 processItem( zone );
587
588 for( PCB_GROUP* group : footprint->Groups() )
589 processItem( group );
590 }
591
592 for( BOARD_ITEM* drawing : board()->Drawings() )
593 processItem( drawing );
594
595 for( ZONE* zone : board()->Zones() )
596 processItem( zone );
597
598 for( PCB_MARKER* marker : board()->Markers() )
599 processItem( marker );
600
601 for( PCB_GROUP* group : board()->Groups() )
602 processItem( group );
603
604 if( duplicates )
605 {
606 errors += duplicates;
607 details += wxString::Format( _( "%d duplicate IDs replaced.\n" ), duplicates );
608 }
609
610 /*******************************
611 * Your test here
612 */
613
614 /*******************************
615 * Inform the user
616 */
617
618 if( errors )
619 {
620 m_frame->OnModify();
621
622 wxString msg = wxString::Format( _( "%d potential problems repaired." ), errors );
623
624 if( !quiet )
625 DisplayInfoMessage( m_frame, msg, details );
626 }
627 else if( !quiet )
628 {
629 DisplayInfoMessage( m_frame, _( "No board problems found." ) );
630 }
631
632 return 0;
633 }
634
635
UpdatePCBFromSchematic(const TOOL_EVENT & aEvent)636 int BOARD_EDITOR_CONTROL::UpdatePCBFromSchematic( const TOOL_EVENT& aEvent )
637 {
638 NETLIST netlist;
639
640 if( m_frame->FetchNetlistFromSchematic( netlist, _( "Updating PCB requires a fully annotated "
641 "schematic." ) ) )
642 {
643 DIALOG_UPDATE_PCB updateDialog( m_frame, &netlist );
644 updateDialog.ShowModal();
645 }
646
647 return 0;
648 }
649
UpdateSchematicFromPCB(const TOOL_EVENT & aEvent)650 int BOARD_EDITOR_CONTROL::UpdateSchematicFromPCB( const TOOL_EVENT& aEvent )
651 {
652 if( Kiface().IsSingle() )
653 {
654 DisplayErrorMessage(
655 m_frame, _( "Cannot update schematic because Pcbnew is opened in stand-alone "
656 "mode. In order to create or update PCBs from schematics, you "
657 "must launch the KiCad project manager and create a project." ) );
658 return 0;
659 }
660
661 m_frame->RunEeschema();
662 KIWAY_PLAYER* frame = m_frame->Kiway().Player( FRAME_SCH, false );
663
664 if( frame )
665 {
666 std::string payload;
667 m_frame->Kiway().ExpressMail( FRAME_SCH, MAIL_SCH_UPDATE, payload, m_frame );
668 }
669 return 0;
670 }
671
672
ShowEeschema(const TOOL_EVENT & aEvent)673 int BOARD_EDITOR_CONTROL::ShowEeschema( const TOOL_EVENT& aEvent )
674 {
675 m_frame->RunEeschema();
676 return 0;
677 }
678
679
ToggleLayersManager(const TOOL_EVENT & aEvent)680 int BOARD_EDITOR_CONTROL::ToggleLayersManager( const TOOL_EVENT& aEvent )
681 {
682 getEditFrame<PCB_EDIT_FRAME>()->ToggleLayersManager();
683 return 0;
684 }
685
686
TogglePythonConsole(const TOOL_EVENT & aEvent)687 int BOARD_EDITOR_CONTROL::TogglePythonConsole( const TOOL_EVENT& aEvent )
688 {
689 m_frame->ScriptingConsoleEnableDisable();
690 return 0;
691 }
692
693
694 // Track & via size control
TrackWidthInc(const TOOL_EVENT & aEvent)695 int BOARD_EDITOR_CONTROL::TrackWidthInc( const TOOL_EVENT& aEvent )
696 {
697 BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
698 constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
699 PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
700
701 if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
702 {
703 BOARD_COMMIT commit( this );
704
705 for( EDA_ITEM* item : selection )
706 {
707 if( item->Type() == PCB_TRACE_T )
708 {
709 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
710
711 // Note: skip first entry which is the current netclass value
712 for( int i = 1; i < (int) designSettings.m_TrackWidthList.size(); ++i )
713 {
714 int candidate = designSettings.m_TrackWidthList[ i ];
715
716 if( candidate > track->GetWidth() )
717 {
718 commit.Modify( track );
719 track->SetWidth( candidate );
720 break;
721 }
722 }
723 }
724 }
725
726 commit.Push( "Increase Track Width" );
727 return 0;
728 }
729
730 ROUTER_TOOL* routerTool = m_toolMgr->GetTool<ROUTER_TOOL>();
731
732 if( routerTool && routerTool->IsToolActive()
733 && routerTool->Router()->Mode() == PNS::PNS_MODE_ROUTE_DIFF_PAIR )
734 {
735 int widthIndex = designSettings.GetDiffPairIndex() + 1;
736
737 // If we go past the last track width entry in the list, start over at the beginning
738 if( widthIndex >= (int) designSettings.m_DiffPairDimensionsList.size() )
739 widthIndex = 0;
740
741 designSettings.SetDiffPairIndex( widthIndex );
742 designSettings.UseCustomDiffPairDimensions( false );
743
744 m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
745 }
746 else
747 {
748 int widthIndex = designSettings.GetTrackWidthIndex();
749
750 if( routerTool && routerTool->IsToolActive()
751 && routerTool->Router()->GetState() == PNS::ROUTER::RouterState::ROUTE_TRACK
752 && designSettings.m_UseConnectedTrackWidth && !designSettings.m_TempOverrideTrackWidth )
753 {
754 designSettings.m_TempOverrideTrackWidth = true;
755 }
756 else
757 {
758 widthIndex++;
759 }
760
761 // If we go past the last track width entry in the list, start over at the beginning
762 if( widthIndex >= (int) designSettings.m_TrackWidthList.size() )
763 widthIndex = 0;
764
765 designSettings.SetTrackWidthIndex( widthIndex );
766 designSettings.UseCustomTrackViaSize( false );
767
768 m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
769 }
770
771 return 0;
772 }
773
774
TrackWidthDec(const TOOL_EVENT & aEvent)775 int BOARD_EDITOR_CONTROL::TrackWidthDec( const TOOL_EVENT& aEvent )
776 {
777 BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
778 constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
779 PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
780
781 if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
782 {
783 BOARD_COMMIT commit( this );
784
785 for( EDA_ITEM* item : selection )
786 {
787 if( item->Type() == PCB_TRACE_T )
788 {
789 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
790
791 // Note: skip first entry which is the current netclass value
792 for( int i = designSettings.m_TrackWidthList.size() - 1; i >= 1; --i )
793 {
794 int candidate = designSettings.m_TrackWidthList[ i ];
795
796 if( candidate < track->GetWidth() )
797 {
798 commit.Modify( track );
799 track->SetWidth( candidate );
800 break;
801 }
802 }
803 }
804 }
805
806 commit.Push( "Decrease Track Width" );
807 return 0;
808 }
809
810 ROUTER_TOOL* routerTool = m_toolMgr->GetTool<ROUTER_TOOL>();
811
812 if( routerTool && routerTool->IsToolActive()
813 && routerTool->Router()->Mode() == PNS::PNS_MODE_ROUTE_DIFF_PAIR )
814 {
815 int widthIndex = designSettings.GetDiffPairIndex() - 1;
816
817 // If we get to the lowest entry start over at the highest
818 if( widthIndex < 0 )
819 widthIndex = designSettings.m_DiffPairDimensionsList.size() - 1;
820
821 designSettings.SetDiffPairIndex( widthIndex );
822 designSettings.UseCustomDiffPairDimensions( false );
823
824 m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
825 }
826 else
827 {
828 int widthIndex = designSettings.GetTrackWidthIndex();
829
830 if( routerTool && routerTool->IsToolActive()
831 && routerTool->Router()->GetState() == PNS::ROUTER::RouterState::ROUTE_TRACK
832 && designSettings.m_UseConnectedTrackWidth && !designSettings.m_TempOverrideTrackWidth )
833 {
834 designSettings.m_TempOverrideTrackWidth = true;
835 }
836 else
837 {
838 widthIndex--;
839 }
840
841 // If we get to the lowest entry start over at the highest
842 if( widthIndex < 0 )
843 widthIndex = designSettings.m_TrackWidthList.size() - 1;
844
845 designSettings.SetTrackWidthIndex( widthIndex );
846 designSettings.UseCustomTrackViaSize( false );
847
848 m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
849 }
850
851 return 0;
852 }
853
854
ViaSizeInc(const TOOL_EVENT & aEvent)855 int BOARD_EDITOR_CONTROL::ViaSizeInc( const TOOL_EVENT& aEvent )
856 {
857 BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
858 constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
859 PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
860
861 if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
862 {
863 BOARD_COMMIT commit( this );
864
865 for( EDA_ITEM* item : selection )
866 {
867 if( item->Type() == PCB_VIA_T )
868 {
869 PCB_VIA* via = static_cast<PCB_VIA*>( item );
870
871 for( VIA_DIMENSION candidate : designSettings.m_ViasDimensionsList )
872 {
873 if( candidate.m_Diameter > via->GetWidth() )
874 {
875 commit.Modify( via );
876 via->SetWidth( candidate.m_Diameter );
877 via->SetDrill( candidate.m_Drill );
878 break;
879 }
880 }
881 }
882 }
883
884 commit.Push( "Increase Via Size" );
885 }
886 else
887 {
888 int sizeIndex = designSettings.GetViaSizeIndex() + 1;
889
890 // If we go past the last via entry in the list, start over at the beginning
891 if( sizeIndex >= (int) designSettings.m_ViasDimensionsList.size() )
892 sizeIndex = 0;
893
894 designSettings.SetViaSizeIndex( sizeIndex );
895 designSettings.UseCustomTrackViaSize( false );
896
897 m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
898 }
899
900 return 0;
901 }
902
903
ViaSizeDec(const TOOL_EVENT & aEvent)904 int BOARD_EDITOR_CONTROL::ViaSizeDec( const TOOL_EVENT& aEvent )
905 {
906 BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
907 constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
908 PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
909
910 if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
911 {
912 BOARD_COMMIT commit( this );
913
914 for( EDA_ITEM* item : selection )
915 {
916 if( item->Type() == PCB_VIA_T )
917 {
918 PCB_VIA* via = static_cast<PCB_VIA*>( item );
919
920 for( int i = designSettings.m_ViasDimensionsList.size() - 1; i >= 0; --i )
921 {
922 VIA_DIMENSION candidate = designSettings.m_ViasDimensionsList[ i ];
923
924 if( candidate.m_Diameter < via->GetWidth() )
925 {
926 commit.Modify( via );
927 via->SetWidth( candidate.m_Diameter );
928 via->SetDrill( candidate.m_Drill );
929 break;
930 }
931 }
932 }
933 }
934
935 commit.Push( "Decrease Via Size" );
936 }
937 else
938 {
939 int sizeIndex = 0; // Assume we only have a single via size entry
940
941 // If there are more, cycle through them backwards
942 if( designSettings.m_ViasDimensionsList.size() > 0 )
943 {
944 sizeIndex = designSettings.GetViaSizeIndex() - 1;
945
946 // If we get to the lowest entry start over at the highest
947 if( sizeIndex < 0 )
948 sizeIndex = designSettings.m_ViasDimensionsList.size() - 1;
949 }
950
951 designSettings.SetViaSizeIndex( sizeIndex );
952 designSettings.UseCustomTrackViaSize( false );
953
954 m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
955 }
956
957 return 0;
958 }
959
960
PlaceFootprint(const TOOL_EVENT & aEvent)961 int BOARD_EDITOR_CONTROL::PlaceFootprint( const TOOL_EVENT& aEvent )
962 {
963 if( m_inPlaceFootprint )
964 return 0;
965
966 REENTRANCY_GUARD guard( &m_inPlaceFootprint );
967
968 FOOTPRINT* fp = aEvent.Parameter<FOOTPRINT*>();
969 KIGFX::VIEW_CONTROLS* controls = getViewControls();
970 BOARD_COMMIT commit( m_frame );
971 BOARD* board = getModel<BOARD>();
972
973 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
974
975 std::string tool = aEvent.GetCommandStr().get();
976 m_frame->PushTool( tool );
977
978 auto setCursor =
979 [&]()
980 {
981 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
982 };
983
984 Activate();
985 // Must be done after Activate() so that it gets set into the correct context
986 controls->ShowCursor( true );
987 // Set initial cursor
988 setCursor();
989
990 VECTOR2I cursorPos = controls->GetCursorPosition();
991 bool reselect = false;
992 bool fromOtherCommand = fp != nullptr;
993 bool resetCursor = aEvent.HasPosition(); // Detect if activated from a hotkey.
994
995 // Prime the pump
996 if( fp )
997 {
998 m_placingFootprint = true;
999 fp->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
1000 m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, fp );
1001 m_toolMgr->RunAction( ACTIONS::refreshPreview );
1002 }
1003 else if( !aEvent.IsReactivate() )
1004 {
1005 m_toolMgr->RunAction( PCB_ACTIONS::cursorClick );
1006 }
1007
1008 // Main loop: keep receiving events
1009 while( TOOL_EVENT* evt = Wait() )
1010 {
1011 setCursor();
1012 cursorPos = controls->GetCursorPosition( !evt->DisableGridSnapping() );
1013
1014 if( reselect && fp )
1015 m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, fp );
1016
1017 auto cleanup =
1018 [&] ()
1019 {
1020 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1021 commit.Revert();
1022
1023 if( fromOtherCommand )
1024 {
1025 PICKED_ITEMS_LIST* undo = m_frame->PopCommandFromUndoList();
1026
1027 if( undo )
1028 {
1029 m_frame->PutDataInPreviousState( undo );
1030 undo->ClearListAndDeleteItems();
1031 delete undo;
1032 }
1033 }
1034
1035 fp = nullptr;
1036 m_placingFootprint = false;
1037 };
1038
1039 if( evt->IsCancelInteractive() )
1040 {
1041 if( fp )
1042 {
1043 cleanup();
1044 }
1045 else
1046 {
1047 m_frame->PopTool( tool );
1048 break;
1049 }
1050 }
1051 else if( evt->IsActivate() )
1052 {
1053 if( fp )
1054 cleanup();
1055
1056 if( evt->IsMoveTool() )
1057 {
1058 // leave ourselves on the stack so we come back after the move
1059 break;
1060 }
1061 else
1062 {
1063 frame()->PopTool( tool );
1064 break;
1065 }
1066 }
1067 else if( evt->IsClick( BUT_LEFT ) )
1068 {
1069 if( !fp )
1070 {
1071 // Pick the footprint to be placed
1072 fp = m_frame->SelectFootprintFromLibTree();
1073
1074 if( fp == nullptr )
1075 continue;
1076
1077 m_placingFootprint = true;
1078
1079 fp->SetLink( niluuid );
1080
1081 fp->SetFlags(IS_NEW ); // whatever
1082
1083 // Set parent so that clearance can be loaded
1084 fp->SetParent( board );
1085
1086 for( PAD* pad : fp->Pads() )
1087 {
1088 pad->SetLocalRatsnestVisible( m_frame->GetDisplayOptions().m_ShowGlobalRatsnest );
1089
1090 // Pads in the library all have orphaned nets. Replace with Default.
1091 pad->SetNetCode( 0 );
1092 }
1093
1094 // Put it on FRONT layer,
1095 // (Can be stored flipped if the lib is an archive built from a board)
1096 if( fp->IsFlipped() )
1097 fp->Flip( fp->GetPosition(), m_frame->Settings().m_FlipLeftRight );
1098
1099 fp->SetOrientation( 0 );
1100 fp->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
1101
1102 commit.Add( fp );
1103 m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, fp );
1104
1105 // Reset cursor to the position before the dialog opened if activated from hotkey
1106 if( resetCursor )
1107 controls->SetCursorPosition( cursorPos, false );
1108
1109 // Other events must be from hotkeys or mouse clicks, so always reset cursor
1110 resetCursor = true;
1111
1112 m_toolMgr->RunAction( ACTIONS::refreshPreview );
1113 }
1114 else
1115 {
1116 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1117 commit.Push( _( "Place a footprint" ) );
1118 fp = nullptr; // to indicate that there is no footprint that we currently modify
1119 m_placingFootprint = false;
1120 }
1121 }
1122 else if( evt->IsClick( BUT_RIGHT ) )
1123 {
1124 m_menu.ShowContextMenu( selection() );
1125 }
1126 else if( fp && ( evt->IsMotion() || evt->IsAction( &ACTIONS::refreshPreview ) ) )
1127 {
1128 fp->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
1129 selection().SetReferencePoint( cursorPos );
1130 getView()->Update( &selection() );
1131 getView()->Update( fp );
1132 }
1133 else if( fp && evt->IsAction( &PCB_ACTIONS::properties ) )
1134 {
1135 // Calling 'Properties' action clears the selection, so we need to restore it
1136 reselect = true;
1137 }
1138 else
1139 {
1140 evt->SetPassEvent();
1141 }
1142
1143 // Enable autopanning and cursor capture only when there is a footprint to be placed
1144 controls->SetAutoPan( fp != nullptr );
1145 controls->CaptureCursor( fp != nullptr );
1146 }
1147
1148 controls->SetAutoPan( false );
1149 controls->CaptureCursor( false );
1150 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
1151
1152 return 0;
1153 }
1154
1155
ToggleLockSelected(const TOOL_EVENT & aEvent)1156 int BOARD_EDITOR_CONTROL::ToggleLockSelected( const TOOL_EVENT& aEvent )
1157 {
1158 return modifyLockSelected( TOGGLE );
1159 }
1160
1161
LockSelected(const TOOL_EVENT & aEvent)1162 int BOARD_EDITOR_CONTROL::LockSelected( const TOOL_EVENT& aEvent )
1163 {
1164 return modifyLockSelected( ON );
1165 }
1166
1167
UnlockSelected(const TOOL_EVENT & aEvent)1168 int BOARD_EDITOR_CONTROL::UnlockSelected( const TOOL_EVENT& aEvent )
1169 {
1170 return modifyLockSelected( OFF );
1171 }
1172
1173
modifyLockSelected(MODIFY_MODE aMode)1174 int BOARD_EDITOR_CONTROL::modifyLockSelected( MODIFY_MODE aMode )
1175 {
1176 PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
1177 const PCB_SELECTION& selection = selTool->GetSelection();
1178 BOARD_COMMIT commit( m_frame );
1179
1180 if( selection.Empty() )
1181 m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
1182
1183 // Resolve TOGGLE mode
1184 if( aMode == TOGGLE )
1185 {
1186 aMode = ON;
1187
1188 for( EDA_ITEM* item : selection )
1189 {
1190 BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
1191
1192 if( board_item->IsLocked() )
1193 {
1194 aMode = OFF;
1195 break;
1196 }
1197 }
1198 }
1199
1200 bool modified = false;
1201
1202 for( EDA_ITEM* item : selection )
1203 {
1204 BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
1205
1206 commit.Modify( board_item );
1207
1208 if( aMode == ON )
1209 {
1210 modified |= !board_item->IsLocked();
1211 board_item->SetLocked( true );
1212 }
1213 else
1214 {
1215 modified |= board_item->IsLocked();
1216 board_item->SetLocked( false );
1217 }
1218 }
1219
1220 if( modified )
1221 {
1222 commit.Push( aMode == ON ? _( "Lock" ) : _( "Unlock" ) );
1223
1224 m_toolMgr->PostEvent( EVENTS::SelectedEvent );
1225 m_frame->UpdateMsgPanel();
1226 m_frame->OnModify();
1227 }
1228
1229 return 0;
1230 }
1231
1232
PlaceTarget(const TOOL_EVENT & aEvent)1233 int BOARD_EDITOR_CONTROL::PlaceTarget( const TOOL_EVENT& aEvent )
1234 {
1235 if( m_inPlaceTarget )
1236 return 0;
1237
1238 REENTRANCY_GUARD guard( &m_inPlaceTarget );
1239
1240 KIGFX::VIEW* view = getView();
1241 KIGFX::VIEW_CONTROLS* controls = getViewControls();
1242 BOARD* board = getModel<BOARD>();
1243 PCB_TARGET* target = new PCB_TARGET( board );
1244
1245 // Init the new item attributes
1246 target->SetLayer( Edge_Cuts );
1247 target->SetWidth( board->GetDesignSettings().GetLineThickness( Edge_Cuts ) );
1248 target->SetSize( Millimeter2iu( 5 ) );
1249 VECTOR2I cursorPos = controls->GetCursorPosition();
1250 target->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
1251
1252 // Add a VIEW_GROUP that serves as a preview for the new item
1253 KIGFX::VIEW_GROUP preview( view );
1254 preview.Add( target );
1255 view->Add( &preview );
1256
1257 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1258
1259 std::string tool = aEvent.GetCommandStr().get();
1260 m_frame->PushTool( tool );
1261 Activate();
1262
1263 auto setCursor =
1264 [&]()
1265 {
1266 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
1267 };
1268
1269 // Set initial cursor
1270 setCursor();
1271
1272 // Main loop: keep receiving events
1273 while( TOOL_EVENT* evt = Wait() )
1274 {
1275 setCursor();
1276 cursorPos = controls->GetCursorPosition( !evt->DisableGridSnapping() );
1277
1278 if( evt->IsCancelInteractive() )
1279 {
1280 frame()->PopTool( tool );
1281 break;
1282 }
1283 else if( evt->IsActivate() )
1284 {
1285 if( evt->IsMoveTool() )
1286 {
1287 // leave ourselves on the stack so we come back after the move
1288 break;
1289 }
1290 else
1291 {
1292 frame()->PopTool( tool );
1293 break;
1294 }
1295 }
1296 else if( evt->IsAction( &PCB_ACTIONS::incWidth ) )
1297 {
1298 target->SetWidth( target->GetWidth() + WIDTH_STEP );
1299 view->Update( &preview );
1300 }
1301 else if( evt->IsAction( &PCB_ACTIONS::decWidth ) )
1302 {
1303 int width = target->GetWidth();
1304
1305 if( width > WIDTH_STEP )
1306 {
1307 target->SetWidth( width - WIDTH_STEP );
1308 view->Update( &preview );
1309 }
1310 }
1311 else if( evt->IsClick( BUT_LEFT ) )
1312 {
1313 assert( target->GetSize() > 0 );
1314 assert( target->GetWidth() > 0 );
1315
1316 BOARD_COMMIT commit( m_frame );
1317 commit.Add( target );
1318 commit.Push( "Place a layer alignment target" );
1319
1320 preview.Remove( target );
1321
1322 // Create next PCB_TARGET
1323 target = new PCB_TARGET( *target );
1324 preview.Add( target );
1325 }
1326 else if( evt->IsClick( BUT_RIGHT ) )
1327 {
1328 m_menu.ShowContextMenu( selection() );
1329 }
1330 else if( evt->IsMotion() )
1331 {
1332 target->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
1333 view->Update( &preview );
1334 }
1335 else
1336 {
1337 evt->SetPassEvent();
1338 }
1339 }
1340
1341 preview.Clear();
1342 delete target;
1343 view->Remove( &preview );
1344
1345 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
1346
1347 return 0;
1348 }
1349
1350
mergeZones(BOARD_COMMIT & aCommit,std::vector<ZONE * > & aOriginZones,std::vector<ZONE * > & aMergedZones)1351 static bool mergeZones( BOARD_COMMIT& aCommit, std::vector<ZONE*>& aOriginZones,
1352 std::vector<ZONE*>& aMergedZones )
1353 {
1354 aCommit.Modify( aOriginZones[0] );
1355
1356 for( unsigned int i = 1; i < aOriginZones.size(); i++ )
1357 {
1358 aOriginZones[0]->Outline()->BooleanAdd( *aOriginZones[i]->Outline(),
1359 SHAPE_POLY_SET::PM_FAST );
1360 }
1361
1362 aOriginZones[0]->Outline()->Simplify( SHAPE_POLY_SET::PM_FAST );
1363
1364 // We should have one polygon with hole
1365 // We can have 2 polygons with hole, if the 2 initial polygons have only one common corner
1366 // and therefore cannot be merged (they are detected as intersecting)
1367 // but we should never have more than 2 polys
1368 if( aOriginZones[0]->Outline()->OutlineCount() > 1 )
1369 {
1370 wxLogMessage( "BOARD::mergeZones error: more than 2 polys after merging" );
1371 return false;
1372 }
1373
1374 for( unsigned int i = 1; i < aOriginZones.size(); i++ )
1375 aCommit.Remove( aOriginZones[i] );
1376
1377 aMergedZones.push_back( aOriginZones[0] );
1378
1379 aOriginZones[0]->SetLocalFlags( 1 );
1380 aOriginZones[0]->HatchBorder();
1381 aOriginZones[0]->CacheTriangulation();
1382
1383 return true;
1384 }
1385
1386
ZoneMerge(const TOOL_EVENT & aEvent)1387 int BOARD_EDITOR_CONTROL::ZoneMerge( const TOOL_EVENT& aEvent )
1388 {
1389 const PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
1390 BOARD* board = getModel<BOARD>();
1391 BOARD_COMMIT commit( m_frame );
1392
1393 if( selection.Size() < 2 )
1394 return 0;
1395
1396 int netcode = -1;
1397
1398 ZONE* firstZone = nullptr;
1399 std::vector<ZONE*> toMerge, merged;
1400
1401 for( EDA_ITEM* item : selection )
1402 {
1403 ZONE* curr_area = dynamic_cast<ZONE*>( item );
1404
1405 if( !curr_area )
1406 continue;
1407
1408 if( !firstZone )
1409 firstZone = curr_area;
1410
1411 netcode = curr_area->GetNetCode();
1412
1413 if( firstZone->GetNetCode() != netcode )
1414 continue;
1415
1416 if( curr_area->GetPriority() != firstZone->GetPriority() )
1417 continue;
1418
1419 if( curr_area->GetIsRuleArea() != firstZone->GetIsRuleArea() )
1420 continue;
1421
1422 if( curr_area->GetLayer() != firstZone->GetLayer() )
1423 continue;
1424
1425 if( !board->TestZoneIntersection( curr_area, firstZone ) )
1426 continue;
1427
1428 toMerge.push_back( curr_area );
1429 }
1430
1431 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1432
1433 if( mergeZones( commit, toMerge, merged ) )
1434 {
1435 commit.Push( "Merge zones" );
1436
1437 for( EDA_ITEM* item : merged )
1438 m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, item );
1439 }
1440
1441 return 0;
1442 }
1443
1444
ZoneDuplicate(const TOOL_EVENT & aEvent)1445 int BOARD_EDITOR_CONTROL::ZoneDuplicate( const TOOL_EVENT& aEvent )
1446 {
1447 PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
1448 const PCB_SELECTION& selection = selTool->GetSelection();
1449
1450 // because this pops up the zone editor, it would be confusing to handle multiple zones,
1451 // so just handle single selections containing exactly one zone
1452 if( selection.Size() != 1 )
1453 return 0;
1454
1455 ZONE* oldZone = dyn_cast<ZONE*>( selection[0] );
1456
1457 if( !oldZone )
1458 return 0;
1459
1460 ZONE_SETTINGS zoneSettings;
1461 zoneSettings << *oldZone;
1462 int dialogResult;
1463
1464 if( oldZone->GetIsRuleArea() )
1465 dialogResult = InvokeRuleAreaEditor( m_frame, &zoneSettings );
1466 else if( oldZone->IsOnCopperLayer() )
1467 dialogResult = InvokeCopperZonesEditor( m_frame, &zoneSettings );
1468 else
1469 dialogResult = InvokeNonCopperZonesEditor( m_frame, &zoneSettings );
1470
1471 if( dialogResult != wxID_OK )
1472 return 0;
1473
1474 // duplicate the zone
1475 BOARD_COMMIT commit( m_frame );
1476
1477 std::unique_ptr<ZONE> newZone = std::make_unique<ZONE>( *oldZone );
1478 newZone->ClearSelected();
1479 newZone->UnFill();
1480 zoneSettings.ExportSetting( *newZone );
1481
1482 // If the new zone is on the same layer(s) as the initial zone,
1483 // offset it a bit so it can more easily be picked.
1484 if( oldZone->GetIsRuleArea() && ( oldZone->GetLayerSet() == zoneSettings.m_Layers ) )
1485 newZone->Move( wxPoint( IU_PER_MM, IU_PER_MM ) );
1486 else if( !oldZone->GetIsRuleArea() && zoneSettings.m_Layers.test( oldZone->GetLayer() ) )
1487 newZone->Move( wxPoint( IU_PER_MM, IU_PER_MM ) );
1488
1489 commit.Add( newZone.release() );
1490 commit.Push( _( "Duplicate zone" ) );
1491
1492 return 0;
1493 }
1494
1495
EditFpInFpEditor(const TOOL_EVENT & aEvent)1496 int BOARD_EDITOR_CONTROL::EditFpInFpEditor( const TOOL_EVENT& aEvent )
1497 {
1498 PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
1499 const PCB_SELECTION& selection = selTool->RequestSelection( EDIT_TOOL::FootprintFilter );
1500
1501 if( selection.Empty() )
1502 return 0;
1503
1504 FOOTPRINT* fp = selection.FirstOfKind<FOOTPRINT>();
1505
1506 if( !fp )
1507 return 0;
1508
1509 PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
1510
1511 auto editor = (FOOTPRINT_EDIT_FRAME*) editFrame->Kiway().Player( FRAME_FOOTPRINT_EDITOR, true );
1512
1513 if( aEvent.IsAction( &PCB_ACTIONS::editFpInFpEditor ) )
1514 editor->LoadFootprintFromBoard( fp );
1515 else if( aEvent.IsAction( &PCB_ACTIONS::editLibFpInFpEditor ) )
1516 editor->LoadFootprintFromLibrary( fp->GetFPID() );
1517
1518 editor->Show( true );
1519 editor->Raise(); // Iconize( false );
1520
1521 if( selection.IsHover() )
1522 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1523
1524 return 0;
1525 }
1526
1527
DoSetDrillOrigin(KIGFX::VIEW * aView,PCB_BASE_FRAME * aFrame,EDA_ITEM * originViewItem,const VECTOR2D & aPosition)1528 void BOARD_EDITOR_CONTROL::DoSetDrillOrigin( KIGFX::VIEW* aView, PCB_BASE_FRAME* aFrame,
1529 EDA_ITEM* originViewItem, const VECTOR2D& aPosition )
1530 {
1531 aFrame->GetDesignSettings().SetAuxOrigin( wxPoint( aPosition ) );
1532 originViewItem->SetPosition( (wxPoint) aPosition );
1533 aView->MarkDirty();
1534 aFrame->OnModify();
1535 }
1536
1537
DrillOrigin(const TOOL_EVENT & aEvent)1538 int BOARD_EDITOR_CONTROL::DrillOrigin( const TOOL_EVENT& aEvent )
1539 {
1540 std::string tool = aEvent.GetCommandStr().get();
1541 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
1542
1543 // Deactivate other tools; particularly important if another PICKER is currently running
1544 Activate();
1545
1546 picker->SetClickHandler(
1547 [this] ( const VECTOR2D& pt ) -> bool
1548 {
1549 m_frame->SaveCopyInUndoList( m_placeOrigin.get(), UNDO_REDO::DRILLORIGIN );
1550 DoSetDrillOrigin( getView(), m_frame, m_placeOrigin.get(), pt );
1551 return false; // drill origin is a one-shot; don't continue with tool
1552 } );
1553
1554 m_toolMgr->RunAction( ACTIONS::pickerTool, true, &tool );
1555
1556 return 0;
1557 }
1558
1559
setTransitions()1560 void BOARD_EDITOR_CONTROL::setTransitions()
1561 {
1562 Go( &BOARD_EDITOR_CONTROL::New, ACTIONS::doNew.MakeEvent() );
1563 Go( &BOARD_EDITOR_CONTROL::Open, ACTIONS::open.MakeEvent() );
1564 Go( &BOARD_EDITOR_CONTROL::Save, ACTIONS::save.MakeEvent() );
1565 Go( &BOARD_EDITOR_CONTROL::SaveAs, ACTIONS::saveAs.MakeEvent() );
1566 Go( &BOARD_EDITOR_CONTROL::SaveCopyAs, ACTIONS::saveCopyAs.MakeEvent() );
1567 Go( &BOARD_EDITOR_CONTROL::PageSettings, ACTIONS::pageSettings.MakeEvent() );
1568 Go( &BOARD_EDITOR_CONTROL::Plot, ACTIONS::plot.MakeEvent() );
1569
1570 Go( &BOARD_EDITOR_CONTROL::Find, ACTIONS::find.MakeEvent() );
1571 Go( &BOARD_EDITOR_CONTROL::FindNext, ACTIONS::findNext.MakeEvent() );
1572
1573 Go( &BOARD_EDITOR_CONTROL::BoardSetup, PCB_ACTIONS::boardSetup.MakeEvent() );
1574 Go( &BOARD_EDITOR_CONTROL::ImportNetlist, PCB_ACTIONS::importNetlist.MakeEvent() );
1575 Go( &BOARD_EDITOR_CONTROL::ImportSpecctraSession,
1576 PCB_ACTIONS::importSpecctraSession.MakeEvent() );
1577 Go( &BOARD_EDITOR_CONTROL::ExportSpecctraDSN, PCB_ACTIONS::exportSpecctraDSN.MakeEvent() );
1578
1579 if( ADVANCED_CFG::GetCfg().m_ShowPcbnewExportNetlist && m_frame &&
1580 m_frame->GetExportNetlistAction() )
1581 Go( &BOARD_EDITOR_CONTROL::ExportNetlist, m_frame->GetExportNetlistAction()->MakeEvent() );
1582
1583 Go( &BOARD_EDITOR_CONTROL::GenerateDrillFiles,
1584 PCB_ACTIONS::generateDrillFiles.MakeEvent() );
1585 Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles, PCB_ACTIONS::generateGerbers.MakeEvent() );
1586 Go( &BOARD_EDITOR_CONTROL::GeneratePosFile, PCB_ACTIONS::generatePosFile.MakeEvent() );
1587 Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles,
1588 PCB_ACTIONS::generateReportFile.MakeEvent() );
1589 Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles, PCB_ACTIONS::generateD356File.MakeEvent() );
1590 Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles, PCB_ACTIONS::generateBOM.MakeEvent() );
1591
1592 // Track & via size control
1593 Go( &BOARD_EDITOR_CONTROL::TrackWidthInc, PCB_ACTIONS::trackWidthInc.MakeEvent() );
1594 Go( &BOARD_EDITOR_CONTROL::TrackWidthDec, PCB_ACTIONS::trackWidthDec.MakeEvent() );
1595 Go( &BOARD_EDITOR_CONTROL::ViaSizeInc, PCB_ACTIONS::viaSizeInc.MakeEvent() );
1596 Go( &BOARD_EDITOR_CONTROL::ViaSizeDec, PCB_ACTIONS::viaSizeDec.MakeEvent() );
1597
1598 // Zone actions
1599 Go( &BOARD_EDITOR_CONTROL::ZoneMerge, PCB_ACTIONS::zoneMerge.MakeEvent() );
1600 Go( &BOARD_EDITOR_CONTROL::ZoneDuplicate, PCB_ACTIONS::zoneDuplicate.MakeEvent() );
1601
1602 // Placing tools
1603 Go( &BOARD_EDITOR_CONTROL::PlaceTarget, PCB_ACTIONS::placeTarget.MakeEvent() );
1604 Go( &BOARD_EDITOR_CONTROL::PlaceFootprint, PCB_ACTIONS::placeFootprint.MakeEvent() );
1605 Go( &BOARD_EDITOR_CONTROL::DrillOrigin, PCB_ACTIONS::drillOrigin.MakeEvent() );
1606
1607 Go( &BOARD_EDITOR_CONTROL::EditFpInFpEditor, PCB_ACTIONS::editFpInFpEditor.MakeEvent() );
1608 Go( &BOARD_EDITOR_CONTROL::EditFpInFpEditor, PCB_ACTIONS::editLibFpInFpEditor.MakeEvent() );
1609
1610 // Other
1611 Go( &BOARD_EDITOR_CONTROL::ToggleLockSelected, PCB_ACTIONS::toggleLock.MakeEvent() );
1612 Go( &BOARD_EDITOR_CONTROL::LockSelected, PCB_ACTIONS::lock.MakeEvent() );
1613 Go( &BOARD_EDITOR_CONTROL::UnlockSelected, PCB_ACTIONS::unlock.MakeEvent() );
1614
1615 Go( &BOARD_EDITOR_CONTROL::UpdatePCBFromSchematic,
1616 ACTIONS::updatePcbFromSchematic.MakeEvent() );
1617 Go( &BOARD_EDITOR_CONTROL::UpdateSchematicFromPCB,
1618 ACTIONS::updateSchematicFromPcb.MakeEvent() );
1619 Go( &BOARD_EDITOR_CONTROL::ShowEeschema, PCB_ACTIONS::showEeschema.MakeEvent() );
1620 Go( &BOARD_EDITOR_CONTROL::ToggleLayersManager, PCB_ACTIONS::showLayersManager.MakeEvent() );
1621 Go( &BOARD_EDITOR_CONTROL::TogglePythonConsole, PCB_ACTIONS::showPythonConsole.MakeEvent() );
1622 Go( &BOARD_EDITOR_CONTROL::RepairBoard, PCB_ACTIONS::repairBoard.MakeEvent() );
1623 }
1624
1625
1626 const int BOARD_EDITOR_CONTROL::WIDTH_STEP = 100000;
1627