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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include <pcb_edit_frame.h>
20 #include <panel_setup_layers.h>
21 #include <panel_setup_text_and_graphics.h>
22 #include <panel_setup_constraints.h>
23 #include <dialogs/panel_setup_netclasses.h>
24 #include <panel_setup_tracks_and_vias.h>
25 #include <panel_setup_mask_and_paste.h>
26 #include <../board_stackup_manager/panel_board_stackup.h>
27 #include <../board_stackup_manager/panel_board_finish.h>
28 #include <confirm.h>
29 #include <board_design_settings.h>
30 #include <kiface_base.h>
31 #include <drc/drc_item.h>
32 #include <dialog_import_settings.h>
33 #include <io_mgr.h>
34 #include <dialogs/panel_setup_severities.h>
35 #include <panel_text_variables.h>
36 #include <project.h>
37 #include <project/project_file.h>
38 #include <settings/settings_manager.h>
39 #include <widgets/resettable_panel.h>
40 #include <widgets/wx_progress_reporters.h>
41 #include <wildcards_and_files_ext.h>
42
43 #include "dialog_board_setup.h"
44 #include "panel_setup_rules.h"
45
46 #include <wx/treebook.h>
47
48 using std::placeholders::_1;
49
50
DIALOG_BOARD_SETUP(PCB_EDIT_FRAME * aFrame)51 DIALOG_BOARD_SETUP::DIALOG_BOARD_SETUP( PCB_EDIT_FRAME* aFrame ) :
52 PAGED_DIALOG( aFrame, _( "Board Setup" ), false,
53 _( "Import Settings from Another Board..." ) ),
54 m_frame( aFrame )
55 {
56 BOARD* board = aFrame->GetBoard();
57 BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
58
59 m_layers = new PANEL_SETUP_LAYERS( this, aFrame );
60 m_textAndGraphics = new PANEL_SETUP_TEXT_AND_GRAPHICS( this, aFrame );
61 m_constraints = new PANEL_SETUP_CONSTRAINTS( this, aFrame );
62 m_rules = new PANEL_SETUP_RULES( this, aFrame );
63 m_tracksAndVias = new PANEL_SETUP_TRACKS_AND_VIAS( this, aFrame, m_constraints );
64 m_maskAndPaste = new PANEL_SETUP_MASK_AND_PASTE( this, aFrame );
65 m_physicalStackup = new PANEL_SETUP_BOARD_STACKUP( this, aFrame, m_layers );
66 m_boardFinish = new PANEL_SETUP_BOARD_FINISH( this, board );
67
68 m_severities = new PANEL_SETUP_SEVERITIES( this, DRC_ITEM::GetItemsWithSeverities(),
69 bds.m_DRCSeverities );
70
71 m_netclasses = new PANEL_SETUP_NETCLASSES( this, &bds.GetNetClasses(),
72 board->GetNetClassAssignmentCandidates(), false );
73
74 m_textVars = new PANEL_TEXT_VARIABLES( m_treebook, &Prj() );
75
76 /*
77 * WARNING: If you change page names you MUST update calls to ShowBoardSetupDialog().
78 */
79
80 m_treebook->AddPage( new wxPanel( this ), _( "Board Stackup" ) );
81
82 m_currentPage = -1;
83
84 /*
85 * WARNING: Code currently relies on the layers setup coming before the physical stackup panel,
86 * and thus transferring data to the board first. See comment in
87 * PANEL_SETUP_BOARD_STACKUP::TransferDataFromWindow and rework this logic if it is determined
88 * that the order of these pages should be changed.
89 */
90 m_treebook->AddSubPage( m_layers, _( "Board Editor Layers" ) );
91 m_layerSetupPage = 1;
92
93 m_treebook->AddSubPage( m_physicalStackup, _( "Physical Stackup" ) );
94 // Change this value if m_physicalStackup is not the page 2 of m_treebook
95 m_physicalStackupPage = 2; // The page number (from 0) to select the m_physicalStackup panel
96
97 m_treebook->AddSubPage( m_boardFinish, _( "Board Finish" ) );
98 m_treebook->AddSubPage( m_maskAndPaste, _( "Solder Mask/Paste" ) );
99
100 m_treebook->AddPage( new wxPanel( this ), _( "Text & Graphics" ) );
101 m_treebook->AddSubPage( m_textAndGraphics, _( "Defaults" ) );
102 m_treebook->AddSubPage( m_textVars, _( "Text Variables" ) );
103
104 m_treebook->AddPage( new wxPanel( this ), _( "Design Rules" ) );
105 m_treebook->AddSubPage( m_constraints, _( "Constraints" ) );
106 m_treebook->AddSubPage( m_tracksAndVias, _( "Pre-defined Sizes" ) );
107 m_treebook->AddSubPage( m_netclasses, _( "Net Classes" ) );
108 m_treebook->AddSubPage( m_rules, _( "Custom Rules" ) );
109 m_treebook->AddSubPage( m_severities, _( "Violation Severity" ) );
110
111 for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
112 m_macHack.push_back( true );
113
114 m_treebook->SetMinSize( wxSize( -1, 480 ) );
115
116 // Connect Events
117 m_treebook->Connect( wxEVT_TREEBOOK_PAGE_CHANGED,
118 wxBookCtrlEventHandler( DIALOG_BOARD_SETUP::OnPageChange ), nullptr,
119 this );
120
121 finishDialogSettings();
122
123 if( Prj().IsReadOnly() )
124 {
125 m_infoBar->ShowMessage( _( "Project is missing or read-only. Some settings will not "
126 "be editable." ), wxICON_WARNING );
127 }
128 }
129
130
~DIALOG_BOARD_SETUP()131 DIALOG_BOARD_SETUP::~DIALOG_BOARD_SETUP()
132 {
133 m_treebook->Disconnect( wxEVT_TREEBOOK_PAGE_CHANGED,
134 wxBookCtrlEventHandler( DIALOG_BOARD_SETUP::OnPageChange ), nullptr,
135 this );
136 }
137
138
OnPageChange(wxBookCtrlEvent & event)139 void DIALOG_BOARD_SETUP::OnPageChange( wxBookCtrlEvent& event )
140 {
141 int page = event.GetSelection();
142
143 // Ensure layer page always gets updated even if we aren't moving towards it
144 if( m_currentPage == m_physicalStackupPage )
145 m_layers->SyncCopperLayers( m_physicalStackup->GetCopperLayerCount() );
146
147 if( page == m_physicalStackupPage )
148 m_physicalStackup->OnLayersOptionsChanged( m_layers->GetUILayerMask() );
149 else if( Prj().IsReadOnly() )
150 KIUI::Disable( m_treebook->GetPage( page ) );
151
152 m_currentPage = page;
153
154 #ifdef __WXMAC__
155 // Work around an OSX bug where the wxGrid children don't get placed correctly until
156 // the first resize event
157 if( m_macHack[ page ] )
158 {
159 wxSize pageSize = m_treebook->GetPage( page )->GetSize();
160 pageSize.x -= 1;
161 pageSize.y += 2;
162
163 m_treebook->GetPage( page )->SetSize( pageSize );
164 m_macHack[ page ] = false;
165 }
166 #endif
167 }
168
169
OnAuxiliaryAction(wxCommandEvent & event)170 void DIALOG_BOARD_SETUP::OnAuxiliaryAction( wxCommandEvent& event )
171 {
172 DIALOG_IMPORT_SETTINGS importDlg( this, m_frame );
173
174 if( importDlg.ShowModal() == wxID_CANCEL )
175 return;
176
177 wxFileName boardFn( importDlg.GetFilePath() );
178 wxFileName projectFn( boardFn );
179
180 projectFn.SetExt( ProjectFileExtension );
181
182 if( !m_frame->GetSettingsManager()->LoadProject( projectFn.GetFullPath(), false ) )
183 {
184 wxString msg = wxString::Format( _( "Error importing settings from board:\n"
185 "Associated project file %s could not be loaded" ),
186 projectFn.GetFullPath() );
187 DisplayErrorMessage( this, msg );
188
189 return;
190 }
191
192 // Flag so user can stop work if it will result in deleted inner copper layers
193 // and still clean up this function properly.
194 bool okToProceed = true;
195
196 PROJECT* otherPrj = m_frame->GetSettingsManager()->GetProject( projectFn.GetFullPath() );
197
198 PLUGIN::RELEASER pi( IO_MGR::PluginFind( IO_MGR::KICAD_SEXP ) );
199
200 BOARD* otherBoard = nullptr;
201
202 try
203 {
204 WX_PROGRESS_REPORTER progressReporter( this, _( "Loading PCB" ), 1 );
205
206 otherBoard = pi->Load( boardFn.GetFullPath(), nullptr, nullptr, nullptr,
207 &progressReporter );
208
209 if( importDlg.m_LayersOpt->GetValue() )
210 {
211 BOARD* loadedBoard = m_frame->GetBoard();
212
213 // Check if "Import Settings" board has more layers than the current board.
214 okToProceed = m_layers->CheckCopperLayerCount( loadedBoard, otherBoard );
215 }
216 }
217 catch( const IO_ERROR& ioe )
218 {
219 // You wouldn't think boardFn.GetFullPath() would throw, but we get a stack buffer
220 // underflow from ASAN. While it's probably an ASAN error, a second try/catch doesn't
221 // cost us much.
222 try
223 {
224 if( ioe.Problem() != wxT( "CANCEL" ) )
225 {
226 wxString msg = wxString::Format( _( "Error loading board file:\n%s" ),
227 boardFn.GetFullPath() );
228 DisplayErrorMessage( this, msg, ioe.What() );
229 }
230
231 if( otherPrj != &m_frame->Prj() )
232 m_frame->GetSettingsManager()->UnloadProject( otherPrj, false );
233 }
234 catch(...)
235 {
236 // That was already our best-efforts
237 }
238
239 return;
240 }
241
242
243 if( okToProceed )
244 {
245 otherBoard->SetProject( otherPrj );
246
247 // If layers options are imported, import also the stackup
248 // layers options and stackup are linked, so they cannot be imported
249 // separately, and stackup can be imported only after layers options
250 if( importDlg.m_LayersOpt->GetValue() )
251 {
252 m_layers->ImportSettingsFrom( otherBoard );
253 m_physicalStackup->ImportSettingsFrom( otherBoard );
254 }
255
256 if( importDlg.m_TextAndGraphicsOpt->GetValue() )
257 m_textAndGraphics->ImportSettingsFrom( otherBoard );
258
259 if( importDlg.m_ConstraintsOpt->GetValue() )
260 m_constraints->ImportSettingsFrom( otherBoard );
261
262 if( importDlg.m_NetclassesOpt->GetValue() )
263 m_netclasses->ImportSettingsFrom( &otherBoard->GetDesignSettings().GetNetClasses() );
264
265 if( importDlg.m_TracksAndViasOpt->GetValue() )
266 m_tracksAndVias->ImportSettingsFrom( otherBoard );
267
268 if( importDlg.m_MaskAndPasteOpt->GetValue() )
269 m_maskAndPaste->ImportSettingsFrom( otherBoard );
270
271 if( importDlg.m_SeveritiesOpt->GetValue() )
272 m_severities->ImportSettingsFrom( otherBoard->GetDesignSettings().m_DRCSeverities );
273
274 if( otherPrj != &m_frame->Prj() )
275 otherBoard->ClearProject();
276 }
277
278 // Clean up and free memory before leaving
279 if( otherPrj != &m_frame->Prj() )
280 m_frame->GetSettingsManager()->UnloadProject( otherPrj, false );
281
282 delete otherBoard;
283 }
284