KiCad PCB EDA Suite
panel_board_stackup.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) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
5  * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors.
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 <convert_to_biu.h>
26 #include <macros.h> // arrayDim definition
27 #include <pcb_edit_frame.h>
28 #include <class_board.h>
30 #include <widgets/paged_dialog.h>
32 #include <wx/rawbmp.h>
33 #include <math/util.h> // for KiROUND
34 
35 #include "panel_board_stackup.h"
36 #include <panel_setup_layers.h>
37 #include "board_stackup_reporter.h"
38 #include <bitmaps.h>
39 #include <wx/clipbrd.h>
40 #include <wx/dataobj.h>
42 #include <wx/wupdlock.h>
43 #include <wx/richmsgdlg.h>
44 
45 // Some wx widget ID to know what widget has fired a event:
46 #define ID_INCREMENT 256 // space between 2 ID type. Bigger than the layer count max
47 
48 // The actual widget IDs are the base id + the row index.
49 // they are used in events to know the row index of the control that fired the event
51 {
52  ID_ITEM_MATERIAL = 10000, // Be sure it is higher than other IDs
53  // used in the board setup dialog
57 };
58 
59 // Default colors to draw icons:
60 static wxColor copperColor( 220, 180, 30 );
61 static wxColor dielectricColor( 75, 120, 75 );
62 static wxColor pasteColor( 200, 200, 200 );
63 
64 static void drawBitmap( wxBitmap& aBitmap, wxColor aColor );
65 
66 
68  PANEL_SETUP_LAYERS* aPanelLayers ):
69  PANEL_SETUP_BOARD_STACKUP_BASE( aParent->GetTreebook() ),
70  m_delectricMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_DIELECTRIC ),
71  m_solderMaskMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_SOLDERMASK ),
72  m_silkscreenMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_SILKSCREEN )
73 {
74  m_parentDialog = aParent;
75  m_frame = aFrame;
76  m_panelLayers = aPanelLayers;
79  m_units = aFrame->GetUserUnits();
80 
82  m_stackupMismatch = false;
83 
84  // Calculates a good size for color swatches (icons) in this dialog
85  wxClientDC dc( this );
86  m_colorSwatchesSize = dc.GetTextExtent( "XX" );
87  m_colorComboSize = dc.GetTextExtent( wxString::Format( "XXX %s XXX",
88  wxGetTranslation( NotSpecifiedPrm() ) ) );
89  m_colorIconsSize = dc.GetTextExtent( "XXXX" );
90 
91  // Calculates a good size for wxTextCtrl to enter Epsilon R and Loss tan
92  // ("0.000000" + margins)
93  m_numericFieldsSize = dc.GetTextExtent( "X.XXXXXXX" );
94  m_numericFieldsSize.y = -1; // Use default for the vertical size
95 
96  // Calculates a minimal size for wxTextCtrl to enter a dim with units
97  // ("000.0000000 mils" + margins)
98  m_numericTextCtrlSize = dc.GetTextExtent( "XXX.XXXXXXX mils" );
99  m_numericTextCtrlSize.y = -1; // Use default for the vertical size
100 
101  // The grid column containing the lock checkbox is kept to a minimal
102  // size. So we use a wxStaticBitmap: set the bitmap itself
103  m_bitmapLockThickness->SetBitmap( KiScaledBitmap( locked_xpm, aFrame ) );
104 
105  // Gives a minimal size of wxTextCtrl showing dimensions+units
106  m_thicknessCtrl->SetMinSize( m_numericTextCtrlSize );
107  m_tcCTValue->SetMinSize( m_numericTextCtrlSize );
108 
109  // Prepare dielectric layer type: layer type keyword is "core" or "prepreg"
110  m_core_prepreg_choice.Add( _( "Core" ) );
111  m_core_prepreg_choice.Add( _( "PrePreg" ) );
112 
113  buildLayerStackPanel( true );
114  synchronizeWithBoard( true );
115 }
116 
117 
119 {
121 }
122 
123 
125 {
126  // Disconnect Events connected to items in m_controlItemsList
127  for( wxControl* item: m_controlItemsList )
128  {
129  wxBitmapComboBox* cb = dynamic_cast<wxBitmapComboBox*>( item );
130 
131  if( cb )
132  cb->Disconnect( wxEVT_COMMAND_COMBOBOX_SELECTED,
133  wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onColorSelected ),
134  NULL, this );
135 
136  wxButton* matButt = dynamic_cast<wxButton*>( item );
137 
138  if( matButt )
139  matButt->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED,
140  wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onMaterialChange ),
141  NULL, this );
142 
143  wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( item );
144 
145  if( textCtrl )
146  textCtrl->Disconnect( wxEVT_COMMAND_TEXT_UPDATED,
147  wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onThicknessChange ),
148  NULL, this );
149  }
150 }
151 
152 
154 {
155  // Build Dielectric layers list:
156  wxArrayString d_list;
157  std::vector<int> rows; // indexes of row values for each selectable item
158  int row = -1;
159 
161  {
162  row++;
163 
164  if( !item.m_isEnabled )
165  continue;
166 
167  BOARD_STACKUP_ITEM* brd_stackup_item = item.m_Item;
168 
169  if( brd_stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
170  {
171  if( brd_stackup_item->GetSublayersCount() > 1 )
172  {
173  d_list.Add( wxString::Format( _( "Layer \"%s\" (sublayer %d/%d)" ),
174  brd_stackup_item->FormatDielectricLayerName(),
175  item.m_SubItem+1, brd_stackup_item->GetSublayersCount() ) );
176  }
177  else
178  d_list.Add( brd_stackup_item->FormatDielectricLayerName() );
179 
180  rows.push_back( row );
181  }
182  }
183 
184  // Show list
185  int index = wxGetSingleChoiceIndex( wxEmptyString, _("Dielectric Layers List"),
186  d_list);
187 
188  if( index < 0 )
189  return;
190 
191  row = rows[index];
192 
193  BOARD_STACKUP_ITEM* brd_stackup_item = m_rowUiItemsList[row].m_Item;
194  int new_sublayer = m_rowUiItemsList[row].m_SubItem;
195 
196  // Insert a new item after the selected item
197  brd_stackup_item->AddDielectricPrms( new_sublayer+1 );
198 
200 }
201 
202 
204 {
205  // Build deletable Dielectric layers list.
206  // A layer can be deleted if there are 2 (or more) dielectric sub-layers
207  // between 2 copper layers
208  wxArrayString d_list;
209  std::vector<int> rows; // indexes of row values for each selectable item
210 
211  int ui_row = 0; // The row index in m_rowUiItemsList of items in choice list
212 
213  // Build the list of dielectric layers:
214  for( auto item : m_stackup.GetList() )
215  {
216  if( !item->IsEnabled() || item->GetType() != BS_ITEM_TYPE_DIELECTRIC ||
217  item->GetSublayersCount() <= 1 )
218  {
219  ui_row++;
220  continue;
221  }
222 
223  for( int ii = 0; ii < item->GetSublayersCount(); ii++ )
224  {
225  d_list.Add( wxString::Format( "Layer \"%s\" sublayer %d/%d",
226  item->FormatDielectricLayerName(), ii+1,
227  item->GetSublayersCount() ) );
228 
229  rows.push_back( ui_row++ );
230  }
231  }
232 
233  // Show choice list
234  int index = wxGetSingleChoiceIndex( wxEmptyString, _("Dielectric Layers List"),
235  d_list );
236 
237  if( index < 0 )
238  return;
239 
240  ui_row = rows[index];
241 
242  BOARD_STACKUP_ITEM* brd_stackup_item = m_rowUiItemsList[ui_row].m_Item;
243  int sublayer = m_rowUiItemsList[ui_row].m_SubItem;
244 
245  // Remove the selected sub item for the selected dielectric layer
246  brd_stackup_item->RemoveDielectricPrms( sublayer );
247 
249 }
250 
251 
252 void PANEL_SETUP_BOARD_STACKUP::onRemoveDielUI( wxUpdateUIEvent& event )
253 {
254  // The m_buttonRemoveDielectricLayer wxButton is enabled only if a dielectric
255  // layer can be removed, i.e. if dielectric layers have sublayers
256  for( auto item : m_stackup.GetList() )
257  {
258  if( !item->IsEnabled() || item->GetType() != BS_ITEM_TYPE_DIELECTRIC )
259  continue;
260 
261  if( item->GetSublayersCount() > 1 )
262  {
263  m_buttonRemoveDielectricLayer->Enable( true );
264  return;
265  }
266  }
267 
268  m_buttonRemoveDielectricLayer->Enable( false );
269 }
270 
271 
273 {
275  return;
276 
277  // Build a ascii representation of stackup and copy it in the clipboard
278  wxString report = BuildStackupReport( m_stackup, m_units );
279 
280  if( wxTheClipboard->Open() )
281  {
282  // This data objects are held by the clipboard,
283  // so do not delete them in the app.
284  wxTheClipboard->SetData( new wxTextDataObject( report ) );
285  wxTheClipboard->Close();
286  }
287 }
288 
289 
291 {
292  wxBitmapComboBox* choice = dynamic_cast<wxBitmapComboBox*>( m_rowUiItemsList[aRow].m_ColorCtrl );
293  wxASSERT( choice );
294 
295  int idx = choice ? choice->GetSelection() : 0;
296 
297  if( idx != GetColorUserDefinedListIdx() ) // a standard color is selected
298  return GetColorStandardList()[idx].m_Color;
299  else if( m_UserColors.count( aRow ) )
300  return m_UserColors.at( aRow );
301  else
302  return wxNullColour;
303 }
304 
305 
307 {
308  int thickness = 0;
309 
311  {
312  BOARD_STACKUP_ITEM* item = ui_item.m_Item;
313 
314  if( !item->IsThicknessEditable() || !ui_item.m_isEnabled )
315  continue;
316 
317  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_ThicknessCtrl );
318  wxString txt = textCtrl->GetValue();
319 
320  int item_thickness = ValueFromString( m_frame->GetUserUnits(), txt );
321  thickness += item_thickness;
322  }
323 
324  m_tcCTValue->SetValue( StringFromValue( m_units, thickness, true ) );
325 }
326 
327 
329 {
330  return ValueFromString( m_units, m_thicknessCtrl->GetValue() );
331 }
332 
333 
335 {
337 
338  if( aFullSync )
339  {
340  int thickness = m_brdSettings->GetBoardThickness();
341  m_thicknessCtrl->SetValue( StringFromValue( m_units, thickness, true ) );
342 
343  m_rbDielectricConstraint->SetSelection( brd_stackup.m_HasDielectricConstrains ? 1 : 0 );
344  m_choiceEdgeConn->SetSelection( brd_stackup.m_EdgeConnectorConstraints );
345  m_cbCastellatedPads->SetValue( brd_stackup.m_CastellatedPads );
346  m_cbEgdesPlated->SetValue( brd_stackup.m_EdgePlating );
347 
348  // find the choice depending on the initial finish setting
349  wxArrayString initial_finish_list = GetCopperFinishStandardList( false );
350  unsigned idx;
351 
352  for( idx = 0; idx < initial_finish_list.GetCount(); idx++ )
353  {
354  if( initial_finish_list[idx] == brd_stackup.m_FinishType )
355  break;
356  }
357 
358  // Now init the choice (use last choice: "User defined" if not found )
359  if( idx >= initial_finish_list.GetCount() )
360  idx = initial_finish_list.GetCount()-1;
361 
362  m_choiceFinish->SetSelection( idx );
363  }
364 
365  int row = 0;
366 
367  for( BOARD_STACKUP_ROW_UI_ITEM& ui_row_item : m_rowUiItemsList )
368  {
369  BOARD_STACKUP_ITEM* item = ui_row_item.m_Item;
370  int sub_item = ui_row_item.m_SubItem;
371 
372  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
373  {
374  wxChoice* choice = dynamic_cast<wxChoice*>( ui_row_item.m_LayerTypeCtrl );
375 
376  if( choice )
377  choice->SetSelection( item->GetTypeName() == KEY_CORE ? 0 : 1 );
378  }
379 
380  if( item->IsMaterialEditable() )
381  {
382  wxTextCtrl* matName = dynamic_cast<wxTextCtrl*>( ui_row_item.m_MaterialCtrl );
383 
384  if( matName )
385  {
386  if( IsPrmSpecified( item->GetMaterial( sub_item ) ) )
387  matName->SetValue( item->GetMaterial( sub_item ) );
388  else
389  matName->SetValue( wxGetTranslation( NotSpecifiedPrm() ) );
390  }
391  }
392 
393  if( item->IsThicknessEditable() )
394  {
395  wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_ThicknessCtrl );
396 
397  if( textCtrl )
398  textCtrl->SetValue( StringFromValue( m_units,
399  item->GetThickness( sub_item ), true ) );
400 
401  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
402  {
403  wxCheckBox* cb_box = dynamic_cast<wxCheckBox*> ( ui_row_item.m_ThicknessLockCtrl );
404 
405  if( cb_box )
406  cb_box->SetValue( item->IsThicknessLocked( sub_item ) );
407  }
408  }
409 
410  if( item->IsColorEditable() )
411  {
412  auto bm_combo = dynamic_cast<wxBitmapComboBox*>( ui_row_item.m_ColorCtrl );
413  int color_idx = 0;
414 
415  if( item->GetColor().StartsWith( "#" ) ) // User defined color
416  {
417  wxColour color( item->GetColor() );
418  m_UserColors[row] = color;
419  color_idx = GetColorUserDefinedListIdx();
420 
421  if( bm_combo ) // Update user color shown in the wxBitmapComboBox
422  {
423  bm_combo->SetString( color_idx, color.GetAsString( wxC2S_HTML_SYNTAX ) );
424  wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
426  bm_combo->SetItemBitmap( color_idx, layerbmp );
427  }
428  }
429  else
430  {
431  const FAB_LAYER_COLOR* color_list = GetColorStandardList();
432 
433  for( int ii = 0; ii < GetColorStandardListCount(); ii++ )
434  {
435  if( color_list[ii].m_ColorName == item->GetColor() )
436  {
437  color_idx = ii;
438  break;
439  }
440  }
441  }
442 
443  if( bm_combo )
444  bm_combo->SetSelection( color_idx );
445  }
446 
447  if( item->HasEpsilonRValue() )
448  {
449  wxString txt;
450  txt.Printf( "%.2f", item->GetEpsilonR( sub_item ) );
451  wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_EpsilonCtrl );
452 
453  if( textCtrl )
454  textCtrl->SetValue( txt );
455  }
456 
457  if( item->HasLossTangentValue() )
458  {
459  wxString txt;
460  txt.Printf( "%g", item->GetLossTangent( sub_item ) );
461  wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_LossTgCtrl );
462 
463  if( textCtrl )
464  textCtrl->SetValue( txt );
465  }
466  }
467 
468  // Now enable/disable stackup items, according to the m_enabledLayers config
470 
471  updateIconColor();
472 }
473 
474 
476 {
477 
478  // Now enable/disable stackup items, according to the m_enabledLayers config
479  // Calculate copper layer count from m_enabledLayers, and *do not use* brd_stackup
480  // for that, because it is not necessary up to date
481  // (for instance after modifying the layer count from the panel layers in dialog)
483  int copperLayersCount = copperMask.count();
484 
485  for( BOARD_STACKUP_ROW_UI_ITEM& ui_row_item: m_rowUiItemsList )
486  {
487  bool show_item;
488  BOARD_STACKUP_ITEM* item = ui_row_item.m_Item;
489 
490  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
491  // the m_DielectricLayerId is not a copper layer id, it is a dielectric idx from 1
492  show_item = item->GetDielectricLayerId() < copperLayersCount;
493  else
494  show_item = m_enabledLayers[item->GetBrdLayerId()];
495 
496  item->SetEnabled( show_item );
497 
498  ui_row_item.m_isEnabled = show_item;
499 
500  // Show or not items of this row:
501  ui_row_item.m_Icon->Show( show_item );
502  ui_row_item.m_LayerName->Show( show_item );
503  ui_row_item.m_LayerTypeCtrl->Show( show_item );
504  ui_row_item.m_MaterialCtrl->Show( show_item );
505 
506  if( ui_row_item.m_MaterialButt )
507  ui_row_item.m_MaterialButt->Show( show_item );
508 
509  ui_row_item.m_ThicknessCtrl->Show( show_item );
510  ui_row_item.m_ThicknessLockCtrl->Show( show_item );
511  ui_row_item.m_ColorCtrl->Show( show_item );
512  ui_row_item.m_EpsilonCtrl->Show( show_item );
513  ui_row_item.m_LossTgCtrl->Show( show_item );
514  }
515 }
516 
517 
519  const wxString * aMaterialName,
520  BOARD_STACKUP_ROW_UI_ITEM& aUiRowItem )
521 {
522  wxBoxSizer* bSizerMat = new wxBoxSizer( wxHORIZONTAL );
523  m_fgGridSizer->Add( bSizerMat, 1, wxEXPAND, 2 );
524  wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY );
525 
526  if( aMaterialName )
527  {
528  if( IsPrmSpecified( *aMaterialName ) )
529  textCtrl->SetValue( *aMaterialName );
530  else
531  textCtrl->SetValue( wxGetTranslation( NotSpecifiedPrm() ) );
532  }
533 
534  textCtrl->SetMinSize( m_numericTextCtrlSize );
535  bSizerMat->Add( textCtrl, 0, wxTOP|wxBOTTOM|wxLEFT, 5 );
536 
537  wxButton* m_buttonMat = new wxButton( m_scGridWin, aId, _("..."),
538  wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
539  bSizerMat->Add( m_buttonMat, 0, wxALIGN_CENTER_VERTICAL, 2 );
540 
541  m_buttonMat->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
542  wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onMaterialChange ),
543  NULL, this );
544  m_controlItemsList.push_back( m_buttonMat );
545 
546  aUiRowItem.m_MaterialCtrl = textCtrl;
547  aUiRowItem.m_MaterialButt = m_buttonMat;
548 }
549 
550 
552 {
553  wxStaticText* emptyText = new wxStaticText( m_scGridWin, wxID_ANY, wxEmptyString );
554  m_fgGridSizer->Add( emptyText, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND );
555  return emptyText;
556 }
557 
558 
560  BOARD_STACKUP_ITEM* aStackupItem, int aSublayerIdx )
561 {
562  wxASSERT( aStackupItem );
563  wxASSERT( aSublayerIdx >= 0 && aSublayerIdx < aStackupItem->GetSublayersCount() );
564 
565  BOARD_STACKUP_ROW_UI_ITEM ui_row_item( aStackupItem, aSublayerIdx );
566  BOARD_STACKUP_ITEM* item = aStackupItem;
567  int row = aRow;
568 
569  const FAB_LAYER_COLOR* color_list = GetColorStandardList();
570 
571  // Add color swatch icon. The color will be updated later,
572  // when all widgets are initialized
573  wxStaticBitmap* bitmap = new wxStaticBitmap( m_scGridWin, wxID_ANY, wxNullBitmap );
574  m_fgGridSizer->Add( bitmap, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT );
575  ui_row_item.m_Icon = bitmap;
576 
577  ui_row_item.m_isEnabled = true;
578 
579  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
580  {
581  wxString lname = item->FormatDielectricLayerName();
582 
583  if( item->GetSublayersCount() > 1 )
584  {
585  lname << " (" << aSublayerIdx+1 << "/" << item->GetSublayersCount() << ")";
586  }
587 
588  wxStaticText* st_text = new wxStaticText( m_scGridWin, wxID_ANY, lname );
589  m_fgGridSizer->Add( st_text, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
590  ui_row_item.m_LayerName = st_text;
591 
592  // For a dielectric layer, the layer type choice is not for each sublayer,
593  // only for the first (aSublayerIdx = 0), and is common to all sublayers
594  if( aSublayerIdx == 0 )
595  {
596  wxChoice* choice = new wxChoice( m_scGridWin, wxID_ANY, wxDefaultPosition,
597  wxDefaultSize, m_core_prepreg_choice );
598  choice->SetSelection( item->GetTypeName() == KEY_CORE ? 0 : 1 );
599  m_fgGridSizer->Add( choice, 0, wxEXPAND|wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
600 
601  ui_row_item.m_LayerTypeCtrl = choice;
602  }
603  else
604  ui_row_item.m_LayerTypeCtrl = addSpacer();
605  }
606  else
607  {
608  item->SetLayerName( m_board->GetLayerName( item->GetBrdLayerId() ) );
609  wxStaticText* st_text = new wxStaticText( m_scGridWin, wxID_ANY, item->GetLayerName() );
610  m_fgGridSizer->Add( st_text, 0, wxALL|wxALIGN_CENTER_VERTICAL, 1 );
611  st_text->Show( true );
612  ui_row_item.m_LayerName = st_text;
613 
614  wxString lname;
615 
616  if( item->GetTypeName() == KEY_COPPER )
617  lname = _( "Copper" );
618  else
619  lname = wxGetTranslation( item->GetTypeName() );
620 
621  st_text = new wxStaticText( m_scGridWin, wxID_ANY, lname );
622  m_fgGridSizer->Add( st_text, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
623  ui_row_item.m_LayerTypeCtrl = st_text;
624  }
625 
626  if( item->IsMaterialEditable() )
627  {
628  wxString matName = item->GetMaterial( aSublayerIdx );
629  addMaterialChooser( ID_ITEM_MATERIAL+row, &matName, ui_row_item );
630  }
631  else
632  {
633  ui_row_item.m_MaterialCtrl = addSpacer();
634  }
635 
636  if( item->IsThicknessEditable() )
637  {
638  wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, ID_ITEM_THICKNESS+row );
639  textCtrl->SetMinSize( m_numericTextCtrlSize );
640  textCtrl->SetValue( StringFromValue( m_units, item->GetThickness( aSublayerIdx ), true ) );
641  m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
642  m_controlItemsList.push_back( textCtrl );
643  textCtrl->Connect( wxEVT_COMMAND_TEXT_UPDATED,
644  wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onThicknessChange ),
645  NULL, this );
646  ui_row_item.m_ThicknessCtrl = textCtrl;
647 
648  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
649  {
650  wxCheckBox* cb_box = new wxCheckBox( m_scGridWin, ID_ITEM_THICKNESS_LOCKED+row,
651  wxEmptyString );
652  cb_box->SetValue( item->IsThicknessLocked( aSublayerIdx ) );
653  m_fgGridSizer->Add( cb_box, 0, wxALIGN_CENTER_VERTICAL, 2 );
654  ui_row_item.m_ThicknessLockCtrl = cb_box;
655  }
656  else
657  {
658  ui_row_item.m_ThicknessLockCtrl = addSpacer();
659  }
660  }
661  else
662  {
663  ui_row_item.m_ThicknessCtrl = addSpacer();
664  ui_row_item.m_ThicknessLockCtrl = addSpacer();
665  }
666 
667  if( item->IsColorEditable() )
668  {
669  int color_idx = 0;
670 
671  if( item->GetColor().StartsWith( "#" ) ) // User defined color
672  {
673  wxColour color( item->GetColor() );
674  m_UserColors[row] = color;
675  color_idx = GetColorUserDefinedListIdx();
676  }
677  else
678  {
679  for( int ii = 0; ii < GetColorStandardListCount(); ii++ )
680  {
681  if( color_list[ii].m_ColorName == item->GetColor() )
682  {
683  color_idx = ii;
684  break;
685  }
686  }
687  }
688 
689  wxBitmapComboBox* bm_combo = createBmComboBox( item, row );
690  m_colorComboSize.y = bm_combo->GetSize().y;
691  bm_combo->SetMinSize( m_colorComboSize );
692  m_fgGridSizer->Add( bm_combo, 0, wxEXPAND|wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
693  bm_combo->SetSelection( color_idx );
694  ui_row_item.m_ColorCtrl = bm_combo;
695  }
696  else
697  {
698  ui_row_item.m_ColorCtrl = addSpacer();
699  }
700 
701  if( item->HasEpsilonRValue() )
702  {
703  wxString txt;
704  txt.Printf( "%.2f", item->GetEpsilonR( aSublayerIdx ) );
705  wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString,
706  wxDefaultPosition, m_numericFieldsSize );
707  textCtrl->SetValue( txt );
708  m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
709  ui_row_item.m_EpsilonCtrl = textCtrl;
710  }
711  else
712  {
713  ui_row_item.m_EpsilonCtrl = addSpacer();
714  }
715 
716  if( item->HasLossTangentValue() )
717  {
718  wxString txt;
719  txt.Printf( "%g", item->GetLossTangent( aSublayerIdx ) );
720  wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString,
721  wxDefaultPosition, m_numericFieldsSize );
722  textCtrl->SetValue( txt );
723  m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
724  ui_row_item.m_LossTgCtrl = textCtrl;
725  }
726  else
727  {
728  ui_row_item.m_LossTgCtrl = addSpacer();
729  }
730 
731  return ui_row_item;
732 }
733 
734 
736 {
737  // Rebuild the stackup for the dialog, after dielectric parameters list is modified
738  // (added/removed):
739 
740  // First, delete all ui objects, because wxID values will be no longer valid for many widgets
742  m_controlItemsList.clear();
743 
744  // Delete widgets (handled by the wxPanel parent)
746  {
747  // This remove and delete the current ui_item.m_MaterialCtrl sizer
748  ui_item.m_MaterialCtrl->SetSizer( nullptr );
749 
750  // Delete other widgets
751  delete ui_item.m_Icon; // Color icon in first column (column 1)
752  delete ui_item.m_LayerName; // string shown in column 2
753  delete ui_item.m_LayerTypeCtrl; // control shown in column 3
754  delete ui_item.m_MaterialCtrl; // control shown in column 4, with m_MaterialButt
755  delete ui_item.m_MaterialButt; // control shown in column 4, with m_MaterialCtrl
756  delete ui_item.m_ThicknessCtrl; // control shown in column 5
757  delete ui_item.m_ThicknessLockCtrl;// control shown in column 6
758  delete ui_item.m_ColorCtrl; // control shown in column 7
759  delete ui_item.m_EpsilonCtrl; // control shown in column 8
760  delete ui_item.m_LossTgCtrl; // control shown in column 9
761  }
762 
763  m_rowUiItemsList.clear();
764  m_UserColors.clear();
765 
766  // In order to recreate a clean grid layer list, we have to delete and
767  // recreate the sizer m_fgGridSizer (just deleting items in this size is not enough)
768  // therefore we also have to add the "old" title items to the newly recreated m_fgGridSizer:
769  m_scGridWin->SetSizer( nullptr ); // This remove and delete the current m_fgGridSizer
770 
771  m_fgGridSizer = new wxFlexGridSizer( 0, 9, 0, 2 );
772  m_fgGridSizer->SetFlexibleDirection( wxHORIZONTAL );
773  m_fgGridSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
774  m_scGridWin->SetSizer( m_fgGridSizer );
775 
776  // Re-add "old" title items:
777  const int sizer_flags = wxALIGN_CENTER_VERTICAL | wxTOP|wxBOTTOM |
778  wxLEFT | wxALIGN_CENTER_HORIZONTAL;
779  m_fgGridSizer->Add( m_staticTextLayer, 0, sizer_flags, 2 );
780  m_fgGridSizer->Add( m_staticTextType, 0, sizer_flags, 2 );
781  m_fgGridSizer->Add( m_staticTextLayerId, 0, wxALL|sizer_flags, 5 );
782  m_fgGridSizer->Add( m_staticTextMaterial, 0, sizer_flags, 2 );
783  m_fgGridSizer->Add( m_staticTextThickness, 0, sizer_flags, 2 );
785  wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 );
786  m_fgGridSizer->Add( m_staticTextColor, 0, sizer_flags, 2 );
787  m_fgGridSizer->Add( m_staticTextEpsilonR, 0, sizer_flags, 2 );
788  m_fgGridSizer->Add( m_staticTextLossTg, 0, sizer_flags, 2 );
789 
790 
791  // Now, rebuild the widget list from the new m_stackup items:
792  buildLayerStackPanel( false );
793 
794  // Now enable/disable stackup items, according to the m_enabledLayers config
796 
797  m_scGridWin->Layout();
798 }
799 
800 
801 void PANEL_SETUP_BOARD_STACKUP::buildLayerStackPanel( bool aCreatedInitialStackup )
802 {
803  wxWindowUpdateLocker locker( m_scGridWin );
804 
805  // Build a full stackup for the dialog, with a active copper layer count
806  // = current board layer count to calculate a reasonable default stackup:
807  if( aCreatedInitialStackup )
808  {
809  // Creates a full BOARD_STACKUP with 32 copper layers.
810  // extra layers will be hiden later.
811  // but if the number of layer is changed in the dialog, the corresponding
812  // widgets will be available with their previous values.
815 
816  // Now initialize all stackup items to the board values, when exist
817  for( BOARD_STACKUP_ITEM* item: m_stackup.GetList() )
818  {
819  // Search for board settings:
820  for( BOARD_STACKUP_ITEM* board_item: brd_stackup.GetList() )
821  {
822  if( item->GetBrdLayerId() != UNDEFINED_LAYER )
823  {
824  if( item->GetBrdLayerId() == board_item->GetBrdLayerId() )
825  {
826  *item = *board_item;
827  break;
828  }
829  }
830  else // dielectric layer: see m_DielectricLayerId for identification
831  {
832  // Compare dielectric layer with dielectric layer
833  if( board_item->GetBrdLayerId() != UNDEFINED_LAYER )
834  continue;
835 
836  if( item->GetDielectricLayerId() == board_item->GetDielectricLayerId() )
837  {
838  *item = *board_item;
839  break;
840  }
841  }
842  }
843  }
844  }
845 
846  int row = 0;
847 
848  for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
849  {
850  for( int sub_idx = 0; sub_idx < item->GetSublayersCount(); sub_idx++ )
851  {
852  BOARD_STACKUP_ROW_UI_ITEM ui_row_item = createRowData( row, item, sub_idx );
853  m_rowUiItemsList.emplace_back( ui_row_item );
854 
855  row++;
856  }
857  }
858 
859  if( aCreatedInitialStackup )
860  {
861  // Get the translated list of choices and init m_choiceFinish
862  wxArrayString finish_list = GetCopperFinishStandardList( true );
863  m_choiceFinish->Append( finish_list );
864  m_choiceFinish->SetSelection( 0 ); // Will be correctly set later
865  }
866 
867  updateIconColor();
868  m_scGridWin->Layout();
869 }
870 
871 
872 
873 // Transfer current UI settings to m_stackup but not to the board
875 {
876  // First, verify the list of layers currently in stackup: if it doesn't match the list
877  // of layers set in PANEL_SETUP_LAYERS prompt the user to update the stackup
879 
880  if( m_enabledLayers != layersList )
881  {
882  for( size_t i = 0; i < m_parentDialog->GetTreebook()->GetPageCount(); ++i )
883  {
884  if( m_parentDialog->GetTreebook()->GetPage( i ) == this )
885  {
886  m_parentDialog->GetTreebook()->SetSelection( i );
887  break;
888  }
889  }
890 
891  m_stackupMismatch = true;
892  return false;
893  }
894 
895  // The board thickness and the thickness from stackup settings should be compatible
896  // so verify that compatibility
897  int pcbThickness = GetPcbThickness() ;
898  int stackup_thickness = 0;
899 
900  wxString txt;
901  wxString error_msg;
902  bool success = true;
903  double value;
904  int row = 0;
905 
907  {
908  // Skip stackup items useless for the current board
909  if( !ui_item.m_isEnabled )
910  {
911  row++;
912  continue;
913  }
914 
915  BOARD_STACKUP_ITEM* item = ui_item.m_Item;
916  int sub_item = ui_item.m_SubItem;
917 
918  // Add sub layer if there is a new sub layer:
919  while( item->GetSublayersCount() <= sub_item )
920  item->AddDielectricPrms( item->GetSublayersCount() );
921 
922  if( sub_item == 0 ) // Name only main layer
923  item->SetLayerName( ui_item.m_LayerName->GetLabel() );
924 
925  if( item->HasEpsilonRValue() )
926  {
927  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_EpsilonCtrl );
928  txt = textCtrl->GetValue();
929 
930  if( txt.ToDouble( &value ) && value >= 0.0 )
931  item->SetEpsilonR( value, sub_item );
932  else if( txt.ToCDouble( &value ) && value >= 0.0 )
933  item->SetEpsilonR( value, sub_item );
934  else
935  {
936  success = false;
937  error_msg << _( "Incorrect value for Epsilon R (Epsilon R must be positive or null if not used)" );
938  }
939  }
940 
941  if( item->HasLossTangentValue() )
942  {
943  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_LossTgCtrl );
944  txt = textCtrl->GetValue();
945 
946  if( txt.ToDouble( &value ) && value >= 0.0 )
947  item->SetLossTangent( value, sub_item );
948  else if( txt.ToCDouble( &value ) && value >= 0.0 )
949  item->SetLossTangent( value, sub_item );
950  else
951  {
952  success = false;
953  if( !error_msg.IsEmpty() )
954  error_msg << "\n";
955  error_msg << _( "Incorrect value for Loss tg (Loss tg must be positive or null if not used)" );
956  }
957  }
958 
959  if( item->IsMaterialEditable() )
960  {
961  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_MaterialCtrl );
962  item->SetMaterial( textCtrl->GetValue(), sub_item );
963 
964  // Ensure the not specified mat name is the keyword, not its translation
965  // to avoid any issue is the language setting changes
966  if( !IsPrmSpecified( item->GetMaterial( sub_item ) ) )
967  item->SetMaterial( NotSpecifiedPrm(), sub_item );
968  }
969 
970  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
971  {
972  // Choice is Core or Prepreg. Sublayers have no choice:
973  wxChoice* choice = dynamic_cast<wxChoice*>( ui_item.m_LayerTypeCtrl );
974 
975  if( choice )
976  {
977  int idx = choice->GetSelection();
978 
979  if( idx == 0 )
980  item->SetTypeName( KEY_CORE );
981  else
982  item->SetTypeName( KEY_PREPREG );
983  }
984  }
985 
986  if( item->IsThicknessEditable() )
987  {
988  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_ThicknessCtrl );
989  txt = textCtrl->GetValue();
990 
991  int new_thickness = ValueFromString( m_frame->GetUserUnits(), txt );
992  item->SetThickness( new_thickness, sub_item );
993  stackup_thickness += new_thickness;
994 
995  if( new_thickness < 0 )
996  {
997  success = false;
998 
999  if( !error_msg.IsEmpty() )
1000  error_msg << "\n";
1001 
1002  error_msg << _( "A layer thickness is < 0. Fix it" );
1003  }
1004 
1005  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1006  {
1007  // Dielectric thickness layer can have a locked thickness:
1008  wxCheckBox* cb_box = static_cast<wxCheckBox*>
1009  ( ui_item.m_ThicknessLockCtrl );
1010  item->SetThicknessLocked( cb_box && cb_box->GetValue(), sub_item );
1011  }
1012  }
1013 
1014  if( sub_item == 0 && item->IsColorEditable() )
1015  {
1016  const FAB_LAYER_COLOR* color_list = GetColorStandardList();
1017 
1018  wxBitmapComboBox* choice = dynamic_cast<wxBitmapComboBox*>( ui_item.m_ColorCtrl );
1019 
1020  if( choice )
1021  {
1022  int idx = choice->GetSelection();
1023 
1024  if( idx == GetColorUserDefinedListIdx() )
1025  {
1026  wxASSERT( m_UserColors.count( row ) );
1027  wxColour color = m_UserColors[row];
1028  item->SetColor( color.GetAsString( wxC2S_HTML_SYNTAX ) );
1029  }
1030  else
1031  item->SetColor( color_list[idx].m_ColorName );
1032  }
1033  }
1034 
1035  row++;
1036  }
1037 
1038  int delta = std::abs( stackup_thickness - pcbThickness );
1039  double relative_error = pcbThickness ? (double)delta / pcbThickness : 1;
1040  const double relative_error_max = 0.01;
1041 
1042  // warn user if relative_error > 0.01
1043  if( relative_error > relative_error_max )
1044  {
1045  wxString msg;
1046  msg.Printf( _( "Board thickness %s differs from stackup thickness %s\n"
1047  "Allowed max error %s" ),
1048  StringFromValue( m_units, pcbThickness ),
1049  StringFromValue( m_units, stackup_thickness ),
1050  StringFromValue( m_units, KiROUND( relative_error_max * pcbThickness) ) );
1051 
1052  if( !error_msg.IsEmpty() )
1053  error_msg << "\n";
1054 
1055  error_msg << msg;
1056 
1057  success = false;
1058  }
1059 
1060  if( !success )
1061  {
1062  wxMessageBox( error_msg, _( "Errors" ) );
1063  return false;
1064  }
1065 
1066  wxArrayString finish_list = GetCopperFinishStandardList( false );
1067  int finish = m_choiceFinish->GetSelection() >= 0 ? m_choiceFinish->GetSelection() : 0;
1068  m_stackup.m_FinishType = finish_list[finish];
1072  m_stackup.m_EdgePlating = m_cbEgdesPlated->GetValue();
1073 
1074  return true;
1075 }
1076 
1077 
1079 {
1081  return false;
1082 
1084 
1085  STRING_FORMATTER old_stackup;
1086  brd_stackup.FormatBoardStackup( &old_stackup, m_board, 0 );
1087 
1088  brd_stackup.m_FinishType = m_stackup.m_FinishType;
1092  brd_stackup.m_EdgePlating = m_stackup.m_EdgePlating;
1093 
1094  // copy enabled items to the new board stackup
1095  brd_stackup.RemoveAll();
1096 
1097  for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
1098  {
1099  if( item->IsEnabled() )
1100  brd_stackup.Add( new BOARD_STACKUP_ITEM( *item ) );
1101  }
1102 
1103  STRING_FORMATTER new_stackup;
1104  brd_stackup.FormatBoardStackup( &new_stackup, m_board, 0 );
1105 
1106  bool modified = old_stackup.GetString() != new_stackup.GetString();
1107 
1109  {
1111  modified = true;
1112  }
1113 
1114  if( !m_brdSettings->m_HasStackup )
1115  {
1116  m_brdSettings->m_HasStackup = true;
1117  modified = true;
1118  }
1119 
1120  if( modified )
1121  m_frame->OnModify();
1122 
1123  return true;
1124 }
1125 
1126 
1128 {
1129  BOARD* savedBrd = m_board;
1130  BOARD_DESIGN_SETTINGS* savedSettings = m_brdSettings;
1131  m_brdSettings = &aBoard->GetDesignSettings();
1132 
1134  synchronizeWithBoard( true );
1135 
1136  m_brdSettings = savedSettings;
1137  m_board = savedBrd;
1138 
1140 }
1141 
1142 
1144 {
1145  // First, verify the list of layers currently in stackup:
1146  // if it does not mach the list of layers set in PANEL_SETUP_LAYERS
1147  // rebuild the panel
1148 
1149  // the current enabled layers in PANEL_SETUP_LAYERS
1150  // Note: the number of layer can change, but not the layers properties
1152 
1153  if( m_enabledLayers != layersList )
1154  {
1155  m_enabledLayers = layersList;
1156 
1157  synchronizeWithBoard( false );
1158 
1159  Layout();
1160  Refresh();
1161  }
1162 }
1163 
1164 
1166 {
1167  // Collect thickness of all layers but dielectric
1168  int thickness = 0;
1169  int fixed_thickness_cnt = 0;
1170  bool thickness_error = false; // True if a locked thickness value in list is < 0
1171  int dielectricCount = 0;
1172 
1174  {
1175  BOARD_STACKUP_ITEM* item = ui_item.m_Item;
1176  int sublayer_idx = ui_item.m_SubItem;
1177 
1178  if( !item->IsThicknessEditable() || !ui_item.m_isEnabled )
1179  continue;
1180 
1181  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1182  {
1183  dielectricCount++;
1184 
1185  wxCheckBox* checkBox = static_cast<wxCheckBox*>( ui_item.m_ThicknessLockCtrl );
1186 
1187  if( !checkBox->GetValue() ) // Only not locked dielectric thickness can be modified
1188  continue;
1189  else
1190  {
1191  fixed_thickness_cnt++;
1192 
1193  if( item->GetThickness( sublayer_idx ) < 0 )
1194  thickness_error = true;
1195  }
1196  }
1197 
1198  thickness += item->GetThickness( sublayer_idx );
1199  }
1200 
1201  if( thickness_error )
1202  {
1203  wxMessageBox( _( "A locked dielectric thickness is < 0\n"
1204  "Unlock it or change its thickness") );
1205  return;
1206  }
1207 
1208  // the number of adjustable dielectric layers must obvioulsly be > 0
1209  // So verify the user has at least one dielectric layer free
1210  int adjustableDielectricCount = dielectricCount - fixed_thickness_cnt;
1211 
1212  if( adjustableDielectricCount <= 0 )
1213  {
1214  wxMessageBox( _( "Cannot calculate dielectric thickness\n"
1215  "At least one dielectric layer must be not locked") );
1216  return;
1217  }
1218 
1219  int dielectric_thickness = GetPcbThickness() - thickness;
1220 
1221  if( dielectric_thickness <= 0 ) // fixed thickness is too big: cannot calculate free thickness
1222  {
1223  wxMessageBox( _( "Cannot calculate dielectric thickness\n"
1224  "Fixed thickness too big or board thickness too small") );
1225  return;
1226  }
1227 
1228  dielectric_thickness /= adjustableDielectricCount;
1229 
1230  // Update items thickness, and the values displayed on screen
1232  {
1233  BOARD_STACKUP_ITEM* item = ui_item.m_Item;
1234  int sublayer_idx = ui_item.m_SubItem;
1235 
1236  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC && ui_item.m_isEnabled )
1237  {
1238  wxCheckBox* checkBox = static_cast<wxCheckBox*>( ui_item.m_ThicknessLockCtrl );
1239 
1240  if( !checkBox->GetValue() ) // Not locked thickness: can be modified
1241  {
1242  item->SetThickness( dielectric_thickness, sublayer_idx );
1243  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_ThicknessCtrl );
1244  textCtrl->SetValue( StringFromValue( m_units, item->GetThickness( sublayer_idx ) ) );
1245  }
1246  }
1247  }
1248 }
1249 
1250 
1251 void PANEL_SETUP_BOARD_STACKUP::onColorSelected( wxCommandEvent& event )
1252 {
1253  int idx = event.GetSelection();
1254  int item_id = event.GetId();
1255 
1256  int row = item_id - ID_ITEM_COLOR;
1257 
1258  if( GetColorStandardListCount()-1 == idx ) // Set user color is the last option in list
1259  {
1260  COLOR4D defaultColor( GetColorStandardList()[GetColorUserDefinedListIdx()].m_Color );
1261  COLOR4D currentColor(
1262  m_UserColors.count( row ) ? m_UserColors[row] : COLOR4D( 0.5, 0.5, 0.5, 1.0 ) );
1263 
1264  DIALOG_COLOR_PICKER dlg( this, currentColor, false, nullptr, defaultColor );
1265 
1266  if( dlg.ShowModal() == wxID_OK )
1267  {
1268  wxBitmapComboBox* combo = static_cast<wxBitmapComboBox*>( FindWindowById( item_id ) );
1269 
1270  wxColour color = dlg.GetColor().ToColour();
1271  m_UserColors[row] = color;
1272 
1273  combo->SetString( idx, color.GetAsString( wxC2S_HTML_SYNTAX ) );
1274 
1275  wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
1276  LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ),
1277  COLOR4D( color ) );
1278  combo->SetItemBitmap( combo->GetCount()-1, layerbmp );
1279  }
1280  }
1281 
1282  updateIconColor( row );
1283 }
1284 
1285 
1286 void PANEL_SETUP_BOARD_STACKUP::onMaterialChange( wxCommandEvent& event )
1287 {
1288  // Ensure m_materialList contains all materials already in use in stackup list
1289  // and add it is missing
1291  return;
1292 
1293  for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
1294  {
1295  DIELECTRIC_SUBSTRATE_LIST* mat_list = nullptr;
1296 
1297  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1298  mat_list = &m_delectricMatList;
1299  else if( item->GetType() == BS_ITEM_TYPE_SOLDERMASK )
1300  mat_list = &m_solderMaskMatList;
1301  else if( item->GetType() == BS_ITEM_TYPE_SILKSCREEN )
1302  mat_list = &m_silkscreenMatList;
1303 
1304  else
1305  continue;
1306 
1307  for( int ii = 0; ii < item->GetSublayersCount(); ii++ )
1308  {
1309  int idx = mat_list->FindSubstrate( item->GetMaterial( ii ),
1310  item->GetEpsilonR( ii ),
1311  item->GetLossTangent( ii ) );
1312 
1313  if( idx < 0 && !item->GetMaterial().IsEmpty() )
1314  {
1315  // This material is not in list: add it
1316  DIELECTRIC_SUBSTRATE new_mat;
1317  new_mat.m_Name = item->GetMaterial( ii );
1318  new_mat.m_EpsilonR = item->GetEpsilonR( ii );
1319  new_mat.m_LossTangent = item->GetLossTangent( ii );
1320  mat_list->AppendSubstrate( new_mat );
1321  }
1322  }
1323  }
1324 
1325  int row = event.GetId() - ID_ITEM_MATERIAL;
1326  BOARD_STACKUP_ITEM* item = m_rowUiItemsList[row].m_Item;
1327  int sub_item = m_rowUiItemsList[row].m_SubItem;
1328  DIELECTRIC_SUBSTRATE_LIST* item_mat_list = nullptr;
1329 
1330  switch( item->GetType() )
1331  {
1333  item_mat_list = &m_delectricMatList;
1334  break;
1335 
1337  item_mat_list = &m_solderMaskMatList;
1338  break;
1339 
1341  item_mat_list = &m_silkscreenMatList;
1342  break;
1343 
1344  default:
1345  item_mat_list = nullptr;
1346  break;
1347  }
1348 
1349  DIALOG_DIELECTRIC_MATERIAL dlg( this, *item_mat_list );
1350 
1351  if( dlg.ShowModal() != wxID_OK )
1352  return;
1353 
1354  DIELECTRIC_SUBSTRATE substrate = dlg.GetSelectedSubstrate();
1355 
1356  if( substrate.m_Name.IsEmpty() ) // No substrate specified
1357  return;
1358 
1359  // Update Name, Epsilon R and Loss tg
1360  item->SetMaterial( substrate.m_Name, sub_item );
1361  item->SetEpsilonR( substrate.m_EpsilonR, sub_item );
1362  item->SetLossTangent( substrate.m_LossTangent, sub_item );
1363 
1364  wxTextCtrl* textCtrl;
1365  textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_MaterialCtrl );
1366  textCtrl->SetValue( item->GetMaterial( sub_item ) );
1367 
1368  // some layers have a material choice but not EpsilonR ctrl
1369  if( item->HasEpsilonRValue() )
1370  {
1371  textCtrl = dynamic_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_EpsilonCtrl );
1372 
1373  if( textCtrl )
1374  textCtrl->SetValue( item->FormatEpsilonR( sub_item ) );
1375  }
1376 
1377  // some layers have a material choice but not loss tg ctrl
1378  if( item->HasLossTangentValue() )
1379  {
1380  textCtrl = dynamic_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_LossTgCtrl );
1381 
1382  if( textCtrl )
1383  textCtrl->SetValue( item->FormatLossTangent( sub_item ) );
1384  }
1385 }
1386 
1387 
1389 {
1390  int row = event.GetId() - ID_ITEM_THICKNESS;
1391  wxString value = event.GetString();
1392 
1393  BOARD_STACKUP_ITEM* item = GetStackupItem( row );
1394  int idx = GetSublayerId( row );
1395 
1396  item->SetThickness( ValueFromString( m_frame->GetUserUnits(), value ), idx );
1397 }
1398 
1399 
1401 {
1402  return m_rowUiItemsList[aRow].m_Item;
1403 }
1404 
1405 
1407 {
1408  return m_rowUiItemsList[aRow].m_SubItem;
1409 }
1410 
1411 
1413 {
1414  BOARD_STACKUP_ITEM* st_item = dynamic_cast<BOARD_STACKUP_ITEM*>( GetStackupItem( aRow ) );
1415 
1416  wxASSERT( st_item );
1417  wxColor color;
1418 
1419  if( ! st_item )
1420  return color;
1421 
1422  switch( st_item->GetType() )
1423  {
1424  case BS_ITEM_TYPE_COPPER: color = copperColor; break;
1426  case BS_ITEM_TYPE_SOLDERMASK: color = GetSelectedColor( aRow ); break;
1427  case BS_ITEM_TYPE_SILKSCREEN: color = GetSelectedColor( aRow ); break;
1428  case BS_ITEM_TYPE_SOLDERPASTE: color = pasteColor; break;
1429 
1430  default:
1432  wxFAIL_MSG( "PANEL_SETUP_BOARD_STACKUP::getColorIconItem: unrecognized item type" );
1433  break;
1434  }
1435 
1436  return color;
1437 }
1438 
1439 
1441 {
1442  if( aRow >= 0 )
1443  {
1444  wxStaticBitmap* st_bitmap = m_rowUiItemsList[aRow].m_Icon;
1445  // explicit depth important under MSW
1446  wxBitmap bmp(m_colorIconsSize.x, m_colorIconsSize.y / 2, 24);
1447  drawBitmap( bmp, getColorIconItem( aRow ) );
1448  st_bitmap->SetBitmap( bmp );
1449  return;
1450  }
1451 
1452  for( unsigned row = 0; row < m_rowUiItemsList.size(); row++ )
1453  {
1454  // explicit depth important under MSW
1455  wxBitmap bmp(m_colorIconsSize.x, m_colorIconsSize.y / 2, 24);
1456  drawBitmap( bmp, getColorIconItem( row ) );
1457  m_rowUiItemsList[row].m_Icon->SetBitmap( bmp );
1458  }
1459 }
1460 
1461 
1463  int aRow )
1464 {
1465  wxBitmapComboBox* combo = new wxBitmapComboBox( m_scGridWin, ID_ITEM_COLOR+aRow,
1466  wxEmptyString, wxDefaultPosition,
1467  wxDefaultSize, 0, nullptr, wxCB_READONLY );
1468  // Fills the combo box with choice list + bitmaps
1469  const FAB_LAYER_COLOR* color_list = GetColorStandardList();
1470 
1471  for( int ii = 0; ii < GetColorStandardListCount(); ii++ )
1472  {
1473  const FAB_LAYER_COLOR& item = color_list[ii];
1474 
1475  wxColor curr_color = item.m_Color;
1476  wxString label;
1477 
1478  // Defined colors have a name, the user color uses the HTML notation ( i.e. #FF0000)
1479  if( GetColorStandardListCount()-1 > (int)combo->GetCount() )
1480  label = wxGetTranslation( item.m_ColorName );
1481  else // Append the user color, if specified, else add a default user color
1482  {
1483  if( aStackupItem && aStackupItem->GetColor().StartsWith( "#" ) )
1484  {
1485  curr_color = wxColour( aStackupItem->GetColor() );
1486  label = aStackupItem->GetColor();
1487  }
1488  else
1489  label = curr_color.GetAsString( wxC2S_HTML_SYNTAX );
1490  }
1491 
1492  wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
1493  LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ),
1494  COLOR4D( curr_color ) );
1495 
1496  combo->Append( label, layerbmp );
1497  }
1498 
1499 #ifdef __WXGTK__
1500  // Adjust the minimal width. On GTK, the size calculated by wxWidgets is not good
1501  // bitmaps are ignored
1502  combo->Fit();
1503  int min_width = combo->GetSize().x;
1504  min_width += m_colorSwatchesSize.x;
1505  combo->SetMinSize( wxSize( min_width, -1 ) );
1506 #endif
1507 
1508  // add the wxBitmapComboBox to wxControl list, to be able to disconnect the event
1509  // on exit
1510  m_controlItemsList.push_back( combo );
1511 
1512  combo->Connect( wxEVT_COMMAND_COMBOBOX_SELECTED,
1513  wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onColorSelected ),
1514  NULL, this );
1515 
1516  return combo;
1517 }
1518 
1519 
1520 void drawBitmap( wxBitmap& aBitmap, wxColor aColor )
1521 {
1522  wxNativePixelData data( aBitmap );
1523  wxNativePixelData::Iterator p(data);
1524 
1525  for( int yy = 0; yy < data.GetHeight(); yy++ )
1526  {
1527  wxNativePixelData::Iterator rowStart = p;
1528 
1529  for( int xx = 0; xx < data.GetWidth(); xx++ )
1530  {
1531  p.Red() = aColor.Red();
1532  p.Green() = aColor.Green();
1533  p.Blue() = aColor.Blue();
1534  ++p;
1535  }
1536 
1537  p = rowStart;
1538  p.OffsetY(data, 1);
1539  }
1540 }
1541 
1542 
1543 void PANEL_SETUP_BOARD_STACKUP::OnUpdateUI( wxUpdateUIEvent& event )
1544 {
1545  // Handle an error. This is delayed to OnUpdateUI so that we can change the focus
1546  // even when the original validation was triggered from a killFocus event, and so
1547  // that the corresponding notebook page can be shown in the background when triggered
1548  // from an OK.
1549  if( m_stackupMismatch )
1550  {
1551  m_stackupMismatch = false;
1552 
1553  wxRichMessageDialog dlg( this,
1554  _( "Physical stackup has not been updated to match layer count." ),
1555  _( "Update Physical Stackup" ),
1556  wxOK | wxCENTER | wxICON_WARNING );
1557  dlg.ShowCheckBox( _( "Update dielectric thickness from board thickness" ), true );
1558 
1559  dlg.ShowModal();
1560 
1561  if( dlg.IsCheckBoxChecked() )
1562  {
1563  wxCommandEvent dummy;
1565  }
1566  }
1567 }
1568 
1569 
void FormatBoardStackup(OUTPUTFORMATTER *aFormatter, BOARD *aBoard, int aNestLevel) const
Writes the stackup info on board file.
BOARD_STACKUP_ITEM_TYPE GetType() const
void onExportToClipboard(wxCommandEvent &event) override
static wxColor pasteColor(200, 200, 200)
std::vector< wxControl * > m_controlItemsList
void OnModify() override
Function OnModify must be called after a board change to set the modified flag.
void SetTypeName(const wxString &aName)
a Dialog to select/change/add a dielectric material from a material list
std::vector< BOARD_STACKUP_ITEM * > & GetList()
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
this class manage the layers needed to make a physical board they are solder mask,...
int AppendSubstrate(DIELECTRIC_SUBSTRATE &aItem)
Append a item in list similar to aItem.
PANEL_SETUP_LAYERS * m_panelLayers
wxString m_FinishType
The name of external copper finish.
bool m_EdgePlating
True if the edge board is plated.
bool IsPrmSpecified(const wxString &aPrmValue)
int color
Definition: DXF_plotter.cpp:60
bool IsThicknessLocked(int aDielectricSubLayer=0) const
BS_EDGE_CONNECTOR_CONSTRAINTS m_EdgeConnectorConstraints
If the board has edge connector cards, some constrains can be specified in job file: BS_EDGE_CONNECTO...
void onAddDielectricLayer(wxCommandEvent &event) override
wxBitmapComboBox * createBmComboBox(BOARD_STACKUP_ITEM *aStackupItem, int aRow)
creates a bitmap combobox to select a layer color
bool m_CastellatedPads
True if castellated pads exist.
#define KEY_COPPER
wxString GetColor() const
int GetColorStandardListCount()
void onUpdateThicknessValue(wxUpdateUIEvent &event) override
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: class_board.h:558
DIELECTRIC_SUBSTRATE_LIST m_silkscreenMatList
LSET GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings Returns a bit-mask of all t...
static wxColor dielectricColor(75, 120, 75)
int GetColorUserDefinedListIdx()
void onRemoveDielUI(wxUpdateUIEvent &event) override
void SetBoardThickness(int aThickness)
wxBitmap KiScaledBitmap(BITMAP_DEF aBitmap, wxWindow *aWindow)
Construct a wxBitmap from a memory record, scaling it if device DPI demands it.
Definition: bitmap.cpp:118
double GetLossTangent(int aDielectricSubLayer=0) const
void buildLayerStackPanel(bool aCreatedInitialStackup)
Populate m_fgGridSizer with items to handle stackup parameters This is a full list: all copper layers...
void disconnectEvents()
disconnect event handlers connected to wxControl items found in list m_controlItemsList
This file contains miscellaneous commonly used macros and functions.
BOARD_STACKUP_ITEM * GetStackupItem(int aRow)
void OnLayersOptionsChanged(LSET aNewLayerSet)
Must be called if the copper layers count has changed or solder mask, solder paste or silkscreen laye...
DIELECTRIC_SUBSTRATE_LIST m_solderMaskMatList
wxTreebook * GetTreebook()
Definition: paged_dialog.h:50
#define ID_INCREMENT
bool m_HasDielectricConstrains
True if some layers have impedance controlled tracks or have specific constrains for micro-wave appli...
void SetLayerName(const wxString &aName)
void addMaterialChooser(wxWindowID aId, const wxString *aMaterialName, BOARD_STACKUP_ROW_UI_ITEM &aUiRowItem)
add a control (a wxTextCtrl + a button) in m_fgGridSizer to select a material
BOARD_STACKUP & GetStackupDescriptor()
Class PANEL_SETUP_BOARD_STACKUP_BASE.
BOARD_STACKUP_ROW_UI_ITEM createRowData(int aRow, BOARD_STACKUP_ITEM *aStackupItem, int aSublayerIdx)
Creates a BOARD_STACKUP_ROW_UI_ITEM relative to the aStackupItem.
BS_EDGE_CONNECTOR_CONSTRAINTS
bool IsThicknessEditable() const
void onThicknessChange(wxCommandEvent &event)
LSET is a set of PCB_LAYER_IDs.
std::map< int, wxColor > m_UserColors
void synchronizeWithBoard(bool aFullSync)
Synchronize the full stackup shown in m_fgGridSizer according to the stackup of the current board and...
#define NULL
void Refresh()
Update the board display after modifying it by a python script (note: it is automatically called by a...
static LSET StackupAllowedBrdLayers()
long long int ValueFromString(EDA_UNITS aUnits, const wxString &aTextValue, EDA_DATA_TYPE aType)
Function ValueFromString converts aTextValue in aUnits to internal units used by the application.
Definition: base_units.cpp:444
wxString NotSpecifiedPrm()
int GetThickness(int aDielectricSubLayer=0) const
bool HasLossTangentValue() const
BOARD_DESIGN_SETTINGS * m_brdSettings
void ImportSettingsFrom(BOARD *aBoard)
double GetEpsilonR(int aDielectricSubLayer=0) const
wxString FormatEpsilonR(int aDielectricSubLayer=0) const
static LSET InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition: lset.cpp:709
static LSET ExternalCuMask()
Return a mask holding the Front and Bottom layers.
Definition: lset.cpp:779
wxString GetTypeName() const
wxString FormatLossTangent(int aDielectricSubLayer=0) const
wxString BuildStackupReport(BOARD_STACKUP &aStackup, EDA_UNITS aUnits)
void SetMaterial(const wxString &aName, int aDielectricSubLayer=0)
const std::string & GetString()
Definition: richio.h:476
#define KEY_PREPREG
this class manage one layer needed to make a physical board it can be a solder mask,...
void rebuildLayerStackPanel()
Populate m_fgGridSizer with items to handle stackup parameters If previous items are in list,...
void SetEpsilonR(double aEpsilon, int aDielectricSubLayer=0)
PCB_LAYER_ID GetBrdLayerId() const
void AddDielectricPrms(int aDielectricPrmsIdx)
true if this stackup item must be taken in account, false to ignore it.
void SetThickness(int aThickness, int aDielectricSubLayer=0)
static void DrawColorSwatch(wxBitmap &aLayerbmp, COLOR4D aBackground, COLOR4D aColor)
wxString GetLayerName() const
wxControl * addSpacer()
add a Spacer in m_fgGridSizer when a empty cell is needed
void SetThicknessLocked(bool aLocked, int aDielectricSubLayer=0)
wxArrayString GetCopperFinishStandardList(bool aTranslate)
PANEL_SETUP_BOARD_STACKUP(PAGED_DIALOG *aParent, PCB_EDIT_FRAME *aFrame, PANEL_SETUP_LAYERS *aPanelLayers)
static void drawBitmap(wxBitmap &aBitmap, wxColor aColor)
void RemoveAll()
Delete all items in list and clear the list.
void onCalculateDielectricThickness(wxCommandEvent &event) override
int FindSubstrate(DIELECTRIC_SUBSTRATE *aItem)
Find a item in list similar to aItem.
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:201
const BITMAP_OPAQUE locked_xpm[1]
Definition: locked.cpp:42
int GetDielectricLayerId() const
static LIB_PART * dummy()
Used to draw a dummy shape when a LIB_PART is not found in library.
Information pertinent to a Pcbnew printed circuit board.
Definition: class_board.h:186
#define _(s)
Definition: 3d_actions.cpp:33
PCB_EDIT_FRAME is the main frame for Pcbnew.
const FAB_LAYER_COLOR * GetColorStandardList()
void updateIconColor(int aRow=-1)
Update the icons color (swatches in first grid column)
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:68
void onColorSelected(wxCommandEvent &event)
static wxColor copperColor(220, 180, 30)
wxColor GetSelectedColor(int aRow) const
Return the color currently selected for the row aRow.
bool transferDataFromUIToStackup()
Transfer current UI settings to m_stackup but not to the board.
wxString StringFromValue(EDA_UNITS aUnits, double aValue, bool aAddUnitSymbol, EDA_DATA_TYPE aType)
Function StringFromValue returns the string from aValue according to units (inch, mm ....
Definition: base_units.cpp:220
void showOnlyActiveLayers()
Show or do not show items in m_fgGridSizer according to the stackup of the current board.
DIELECTRIC_SUBSTRATE_LIST m_delectricMatList
void SetColor(const wxString &aColorName)
BOARD * GetBoard() const
#define KEY_CORE
void Add(BOARD_STACKUP_ITEM *aItem)
Add a new item in stackup layer.
void OnUpdateUI(wxUpdateUIEvent &event) override
STRING_FORMATTER implements OUTPUTFORMATTER to a memory buffer.
Definition: richio.h:446
void onRemoveDielectricLayer(wxCommandEvent &event) override
int GetCopperLayerCount() const
Function GetCopperLayerCount.
void SetLossTangent(double aTg, int aDielectricSubLayer=0)
wxString GetMaterial(int aDielectricSubLayer=0) const
void onMaterialChange(wxCommandEvent &event)
EDA_UNITS GetUserUnits() const
Return the user units currently in use.
wxString FormatDielectricLayerName() const
void BuildDefaultStackupList(BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Creates a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
BOARD_DESIGN_SETTINGS contains design settings for a BOARD object.
COLOR4D is the color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:100
void SetEnabled(bool aEnable)
void RemoveDielectricPrms(int aDielectricPrmsIdx)
Remove a DIELECTRIC_PRMS item from m_DielectricPrmsList.
std::vector< BOARD_STACKUP_ROW_UI_ITEM > m_rowUiItemsList