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, true );
321  thickness += item_thickness;
322  }
323 
324  m_tcCTValue->SetValue( StringFromValue( m_units, thickness, true, true ) );
325 }
326 
327 
329 {
330  return ValueFromString( m_units, m_thicknessCtrl->GetValue(), true );
331 }
332 
333 
335 {
337 
338  if( aFullSync )
339  {
340  int thickness = m_brdSettings->GetBoardThickness();
341  m_thicknessCtrl->SetValue( StringFromValue( m_units, thickness, true, 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, 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 ),
641  true, true ) );
642  m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
643  m_controlItemsList.push_back( textCtrl );
644  textCtrl->Connect( wxEVT_COMMAND_TEXT_UPDATED,
645  wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onThicknessChange ),
646  NULL, this );
647  ui_row_item.m_ThicknessCtrl = textCtrl;
648 
649  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
650  {
651  wxCheckBox* cb_box = new wxCheckBox( m_scGridWin, ID_ITEM_THICKNESS_LOCKED+row,
652  wxEmptyString );
653  cb_box->SetValue( item->IsThicknessLocked( aSublayerIdx ) );
654  m_fgGridSizer->Add( cb_box, 0, wxALIGN_CENTER_VERTICAL, 2 );
655  ui_row_item.m_ThicknessLockCtrl = cb_box;
656  }
657  else
658  {
659  ui_row_item.m_ThicknessLockCtrl = addSpacer();
660  }
661  }
662  else
663  {
664  ui_row_item.m_ThicknessCtrl = addSpacer();
665  ui_row_item.m_ThicknessLockCtrl = addSpacer();
666  }
667 
668  if( item->IsColorEditable() )
669  {
670  int color_idx = 0;
671 
672  if( item->GetColor().StartsWith( "#" ) ) // User defined color
673  {
674  wxColour color( item->GetColor() );
675  m_UserColors[row] = color;
676  color_idx = GetColorUserDefinedListIdx();
677  }
678  else
679  {
680  for( int ii = 0; ii < GetColorStandardListCount(); ii++ )
681  {
682  if( color_list[ii].m_ColorName == item->GetColor() )
683  {
684  color_idx = ii;
685  break;
686  }
687  }
688  }
689 
690  wxBitmapComboBox* bm_combo = createBmComboBox( item, row );
691  m_colorComboSize.y = bm_combo->GetSize().y;
692  bm_combo->SetMinSize( m_colorComboSize );
693  m_fgGridSizer->Add( bm_combo, 0, wxEXPAND|wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
694  bm_combo->SetSelection( color_idx );
695  ui_row_item.m_ColorCtrl = bm_combo;
696  }
697  else
698  {
699  ui_row_item.m_ColorCtrl = addSpacer();
700  }
701 
702  if( item->HasEpsilonRValue() )
703  {
704  wxString txt;
705  txt.Printf( "%.2f", item->GetEpsilonR( aSublayerIdx ) );
706  wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString,
707  wxDefaultPosition, m_numericFieldsSize );
708  textCtrl->SetValue( txt );
709  m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
710  ui_row_item.m_EpsilonCtrl = textCtrl;
711  }
712  else
713  {
714  ui_row_item.m_EpsilonCtrl = addSpacer();
715  }
716 
717  if( item->HasLossTangentValue() )
718  {
719  wxString txt;
720  txt.Printf( "%g", item->GetLossTangent( aSublayerIdx ) );
721  wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString,
722  wxDefaultPosition, m_numericFieldsSize );
723  textCtrl->SetValue( txt );
724  m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
725  ui_row_item.m_LossTgCtrl = textCtrl;
726  }
727  else
728  {
729  ui_row_item.m_LossTgCtrl = addSpacer();
730  }
731 
732  return ui_row_item;
733 }
734 
735 
737 {
738  // Rebuild the stackup for the dialog, after dielectric parameters list is modified
739  // (added/removed):
740 
741  // First, delete all ui objects, because wxID values will be no longer valid for many widgets
743  m_controlItemsList.clear();
744 
745  // Delete widgets (handled by the wxPanel parent)
747  {
748  // This remove and delete the current ui_item.m_MaterialCtrl sizer
749  ui_item.m_MaterialCtrl->SetSizer( nullptr );
750 
751  // Delete other widgets
752  delete ui_item.m_Icon; // Color icon in first column (column 1)
753  delete ui_item.m_LayerName; // string shown in column 2
754  delete ui_item.m_LayerTypeCtrl; // control shown in column 3
755  delete ui_item.m_MaterialCtrl; // control shown in column 4, with m_MaterialButt
756  delete ui_item.m_MaterialButt; // control shown in column 4, with m_MaterialCtrl
757  delete ui_item.m_ThicknessCtrl; // control shown in column 5
758  delete ui_item.m_ThicknessLockCtrl;// control shown in column 6
759  delete ui_item.m_ColorCtrl; // control shown in column 7
760  delete ui_item.m_EpsilonCtrl; // control shown in column 8
761  delete ui_item.m_LossTgCtrl; // control shown in column 9
762  }
763 
764  m_rowUiItemsList.clear();
765  m_UserColors.clear();
766 
767  // In order to recreate a clean grid layer list, we have to delete and
768  // recreate the sizer m_fgGridSizer (just deleting items in this size is not enough)
769  // therefore we also have to add the "old" title items to the newly recreated m_fgGridSizer:
770  m_scGridWin->SetSizer( nullptr ); // This remove and delete the current m_fgGridSizer
771 
772  m_fgGridSizer = new wxFlexGridSizer( 0, 9, 0, 2 );
773  m_fgGridSizer->SetFlexibleDirection( wxHORIZONTAL );
774  m_fgGridSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
775  m_scGridWin->SetSizer( m_fgGridSizer );
776 
777  // Re-add "old" title items:
778  const int sizer_flags = wxALIGN_CENTER_VERTICAL | wxTOP|wxBOTTOM |
779  wxLEFT | wxALIGN_CENTER_HORIZONTAL;
780  m_fgGridSizer->Add( m_staticTextLayer, 0, sizer_flags, 2 );
781  m_fgGridSizer->Add( m_staticTextType, 0, sizer_flags, 2 );
782  m_fgGridSizer->Add( m_staticTextLayerId, 0, wxALL|sizer_flags, 5 );
783  m_fgGridSizer->Add( m_staticTextMaterial, 0, sizer_flags, 2 );
784  m_fgGridSizer->Add( m_staticTextThickness, 0, sizer_flags, 2 );
786  wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 );
787  m_fgGridSizer->Add( m_staticTextColor, 0, sizer_flags, 2 );
788  m_fgGridSizer->Add( m_staticTextEpsilonR, 0, sizer_flags, 2 );
789  m_fgGridSizer->Add( m_staticTextLossTg, 0, sizer_flags, 2 );
790 
791 
792  // Now, rebuild the widget list from the new m_stackup items:
793  buildLayerStackPanel( false );
794 
795  // Now enable/disable stackup items, according to the m_enabledLayers config
797 
798  m_scGridWin->Layout();
799 }
800 
801 
802 void PANEL_SETUP_BOARD_STACKUP::buildLayerStackPanel( bool aCreatedInitialStackup )
803 {
804  wxWindowUpdateLocker locker( m_scGridWin );
805 
806  // Build a full stackup for the dialog, with a active copper layer count
807  // = current board layer count to calculate a reasonable default stackup:
808  if( aCreatedInitialStackup )
809  {
810  // Creates a full BOARD_STACKUP with 32 copper layers.
811  // extra layers will be hiden later.
812  // but if the number of layer is changed in the dialog, the corresponding
813  // widgets will be available with their previous values.
816 
817  // Now initialize all stackup items to the board values, when exist
818  for( BOARD_STACKUP_ITEM* item: m_stackup.GetList() )
819  {
820  // Search for board settings:
821  for( BOARD_STACKUP_ITEM* board_item: brd_stackup.GetList() )
822  {
823  if( item->GetBrdLayerId() != UNDEFINED_LAYER )
824  {
825  if( item->GetBrdLayerId() == board_item->GetBrdLayerId() )
826  {
827  *item = *board_item;
828  break;
829  }
830  }
831  else // dielectric layer: see m_DielectricLayerId for identification
832  {
833  // Compare dielectric layer with dielectric layer
834  if( board_item->GetBrdLayerId() != UNDEFINED_LAYER )
835  continue;
836 
837  if( item->GetDielectricLayerId() == board_item->GetDielectricLayerId() )
838  {
839  *item = *board_item;
840  break;
841  }
842  }
843  }
844  }
845  }
846 
847  int row = 0;
848 
849  for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
850  {
851  for( int sub_idx = 0; sub_idx < item->GetSublayersCount(); sub_idx++ )
852  {
853  BOARD_STACKUP_ROW_UI_ITEM ui_row_item = createRowData( row, item, sub_idx );
854  m_rowUiItemsList.emplace_back( ui_row_item );
855 
856  row++;
857  }
858  }
859 
860  if( aCreatedInitialStackup )
861  {
862  // Get the translated list of choices and init m_choiceFinish
863  wxArrayString finish_list = GetCopperFinishStandardList( true );
864  m_choiceFinish->Append( finish_list );
865  m_choiceFinish->SetSelection( 0 ); // Will be correctly set later
866  }
867 
868  updateIconColor();
869  m_scGridWin->Layout();
870 }
871 
872 
873 
874 // Transfer current UI settings to m_stackup but not to the board
876 {
877  // First, verify the list of layers currently in stackup: if it doesn't match the list
878  // of layers set in PANEL_SETUP_LAYERS prompt the user to update the stackup
880 
881  if( m_enabledLayers != layersList )
882  {
883  for( size_t i = 0; i < m_parentDialog->GetTreebook()->GetPageCount(); ++i )
884  {
885  if( m_parentDialog->GetTreebook()->GetPage( i ) == this )
886  {
887  m_parentDialog->GetTreebook()->SetSelection( i );
888  break;
889  }
890  }
891 
892  m_stackupMismatch = true;
893  return false;
894  }
895 
896  // The board thickness and the thickness from stackup settings should be compatible
897  // so verify that compatibility
898  int pcbThickness = GetPcbThickness() ;
899  int stackup_thickness = 0;
900 
901  wxString txt;
902  wxString error_msg;
903  bool success = true;
904  double value;
905  int row = 0;
906 
908  {
909  // Skip stackup items useless for the current board
910  if( !ui_item.m_isEnabled )
911  {
912  row++;
913  continue;
914  }
915 
916  BOARD_STACKUP_ITEM* item = ui_item.m_Item;
917  int sub_item = ui_item.m_SubItem;
918 
919  // Add sub layer if there is a new sub layer:
920  while( item->GetSublayersCount() <= sub_item )
921  item->AddDielectricPrms( item->GetSublayersCount() );
922 
923  if( sub_item == 0 ) // Name only main layer
924  item->SetLayerName( ui_item.m_LayerName->GetLabel() );
925 
926  if( item->HasEpsilonRValue() )
927  {
928  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_EpsilonCtrl );
929  txt = textCtrl->GetValue();
930 
931  if( txt.ToDouble( &value ) && value >= 0.0 )
932  item->SetEpsilonR( value, sub_item );
933  else if( txt.ToCDouble( &value ) && value >= 0.0 )
934  item->SetEpsilonR( value, sub_item );
935  else
936  {
937  success = false;
938  error_msg << _( "Incorrect value for Epsilon R (Epsilon R must be positive or null if not used)" );
939  }
940  }
941 
942  if( item->HasLossTangentValue() )
943  {
944  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_LossTgCtrl );
945  txt = textCtrl->GetValue();
946 
947  if( txt.ToDouble( &value ) && value >= 0.0 )
948  item->SetLossTangent( value, sub_item );
949  else if( txt.ToCDouble( &value ) && value >= 0.0 )
950  item->SetLossTangent( value, sub_item );
951  else
952  {
953  success = false;
954  if( !error_msg.IsEmpty() )
955  error_msg << "\n";
956  error_msg << _( "Incorrect value for Loss tg (Loss tg must be positive or null if not used)" );
957  }
958  }
959 
960  if( item->IsMaterialEditable() )
961  {
962  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_MaterialCtrl );
963  item->SetMaterial( textCtrl->GetValue(), sub_item );
964 
965  // Ensure the not specified mat name is the keyword, not its translation
966  // to avoid any issue is the language setting changes
967  if( !IsPrmSpecified( item->GetMaterial( sub_item ) ) )
968  item->SetMaterial( NotSpecifiedPrm(), sub_item );
969  }
970 
971  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
972  {
973  // Choice is Core or Prepreg. Sublayers have no choice:
974  wxChoice* choice = dynamic_cast<wxChoice*>( ui_item.m_LayerTypeCtrl );
975 
976  if( choice )
977  {
978  int idx = choice->GetSelection();
979 
980  if( idx == 0 )
981  item->SetTypeName( KEY_CORE );
982  else
983  item->SetTypeName( KEY_PREPREG );
984  }
985  }
986 
987  if( item->IsThicknessEditable() )
988  {
989  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_ThicknessCtrl );
990  txt = textCtrl->GetValue();
991 
992  int new_thickness = ValueFromString( m_frame->GetUserUnits(), txt, true );
993  item->SetThickness( new_thickness, sub_item );
994  stackup_thickness += new_thickness;
995 
996  if( new_thickness < 0 )
997  {
998  success = false;
999 
1000  if( !error_msg.IsEmpty() )
1001  error_msg << "\n";
1002 
1003  error_msg << _( "A layer thickness is < 0. Fix it" );
1004  }
1005 
1006  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1007  {
1008  // Dielectric thickness layer can have a locked thickness:
1009  wxCheckBox* cb_box = static_cast<wxCheckBox*>
1010  ( ui_item.m_ThicknessLockCtrl );
1011  item->SetThicknessLocked( cb_box && cb_box->GetValue(), sub_item );
1012  }
1013  }
1014 
1015  if( sub_item == 0 && item->IsColorEditable() )
1016  {
1017  const FAB_LAYER_COLOR* color_list = GetColorStandardList();
1018 
1019  wxBitmapComboBox* choice = dynamic_cast<wxBitmapComboBox*>( ui_item.m_ColorCtrl );
1020 
1021  if( choice )
1022  {
1023  int idx = choice->GetSelection();
1024 
1025  if( idx == GetColorUserDefinedListIdx() )
1026  {
1027  wxASSERT( m_UserColors.count( row ) );
1028  wxColour color = m_UserColors[row];
1029  item->SetColor( color.GetAsString( wxC2S_HTML_SYNTAX ) );
1030  }
1031  else
1032  item->SetColor( color_list[idx].m_ColorName );
1033  }
1034  }
1035 
1036  row++;
1037  }
1038 
1039  int delta = std::abs( stackup_thickness - pcbThickness );
1040  double relative_error = pcbThickness ? (double)delta / pcbThickness : 1;
1041  const double relative_error_max = 0.01;
1042 
1043  // warn user if relative_error > 0.01
1044  if( relative_error > relative_error_max )
1045  {
1046  wxString msg;
1047  msg.Printf( _( "Board thickness %s differs from stackup thickness %s\n"
1048  "Allowed max error %s" ),
1049  StringFromValue( m_units, pcbThickness, true, true ),
1050  StringFromValue( m_units, stackup_thickness, true, true ),
1051  StringFromValue( m_units, KiROUND( relative_error_max * pcbThickness),
1052  true, true ) );
1053 
1054  if( !error_msg.IsEmpty() )
1055  error_msg << "\n";
1056 
1057  error_msg << msg;
1058 
1059  success = false;
1060  }
1061 
1062  if( !success )
1063  {
1064  wxMessageBox( error_msg, _( "Errors" ) );
1065  return false;
1066  }
1067 
1068  wxArrayString finish_list = GetCopperFinishStandardList( false );
1069  int finish = m_choiceFinish->GetSelection() >= 0 ? m_choiceFinish->GetSelection() : 0;
1070  m_stackup.m_FinishType = finish_list[finish];
1074  m_stackup.m_EdgePlating = m_cbEgdesPlated->GetValue();
1075 
1076  return true;
1077 }
1078 
1079 
1081 {
1083  return false;
1084 
1086 
1087  brd_stackup.m_FinishType = m_stackup.m_FinishType;
1091  brd_stackup.m_EdgePlating = m_stackup.m_EdgePlating;
1092 
1093  // copy enabled items to the new board stackup
1094  brd_stackup.RemoveAll();
1095 
1096  for( auto item : m_stackup.GetList() )
1097  {
1098  if( item->IsEnabled() )
1099  brd_stackup.Add( new BOARD_STACKUP_ITEM( *item ) );
1100  }
1101 
1103  m_brdSettings->m_HasStackup = true;
1104 
1105  return true;
1106 }
1107 
1108 
1110 {
1111  BOARD* savedBrd = m_board;
1112  BOARD_DESIGN_SETTINGS* savedSettings = m_brdSettings;
1113  m_brdSettings = &aBoard->GetDesignSettings();
1114 
1116  synchronizeWithBoard( true );
1117 
1118  m_brdSettings = savedSettings;
1119  m_board = savedBrd;
1120 
1122 }
1123 
1124 
1126 {
1127  // First, verify the list of layers currently in stackup:
1128  // if it does not mach the list of layers set in PANEL_SETUP_LAYERS
1129  // rebuild the panel
1130 
1131  // the current enabled layers in PANEL_SETUP_LAYERS
1132  // Note: the number of layer can change, but not the layers properties
1134 
1135  if( m_enabledLayers != layersList )
1136  {
1137  m_enabledLayers = layersList;
1138 
1139  synchronizeWithBoard( false );
1140 
1141  Layout();
1142  Refresh();
1143  }
1144 }
1145 
1146 
1148 {
1149  // Collect thickness of all layers but dielectric
1150  int thickness = 0;
1151  int fixed_thickness_cnt = 0;
1152  bool thickness_error = false; // True if a locked thickness value in list is < 0
1153  int dielectricCount = 0;
1154 
1156  {
1157  BOARD_STACKUP_ITEM* item = ui_item.m_Item;
1158  int sublayer_idx = ui_item.m_SubItem;
1159 
1160  if( !item->IsThicknessEditable() || !ui_item.m_isEnabled )
1161  continue;
1162 
1163  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1164  {
1165  dielectricCount++;
1166 
1167  wxCheckBox* checkBox = static_cast<wxCheckBox*>( ui_item.m_ThicknessLockCtrl );
1168 
1169  if( !checkBox->GetValue() ) // Only not locked dielectric thickness can be modified
1170  continue;
1171  else
1172  {
1173  fixed_thickness_cnt++;
1174 
1175  if( item->GetThickness( sublayer_idx ) < 0 )
1176  thickness_error = true;
1177  }
1178  }
1179 
1180  thickness += item->GetThickness( sublayer_idx );
1181  }
1182 
1183  if( thickness_error )
1184  {
1185  wxMessageBox( _( "A locked dielectric thickness is < 0\n"
1186  "Unlock it or change its thickness") );
1187  return;
1188  }
1189 
1190  // the number of adjustable dielectric layers must obvioulsly be > 0
1191  // So verify the user has at least one dielectric layer free
1192  int adjustableDielectricCount = dielectricCount - fixed_thickness_cnt;
1193 
1194  if( adjustableDielectricCount <= 0 )
1195  {
1196  wxMessageBox( _( "Cannot calculate dielectric thickness\n"
1197  "At least one dielectric layer must be not locked") );
1198  return;
1199  }
1200 
1201  int dielectric_thickness = GetPcbThickness() - thickness;
1202 
1203  if( dielectric_thickness <= 0 ) // fixed thickness is too big: cannot calculate free thickness
1204  {
1205  wxMessageBox( _( "Cannot calculate dielectric thickness\n"
1206  "Fixed thickness too big or board thickness too small") );
1207  return;
1208  }
1209 
1210  dielectric_thickness /= adjustableDielectricCount;
1211 
1212  // Update items thickness, and the values displayed on screen
1214  {
1215  BOARD_STACKUP_ITEM* item = ui_item.m_Item;
1216  int sublayer_idx = ui_item.m_SubItem;
1217 
1218  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC && ui_item.m_isEnabled )
1219  {
1220  wxCheckBox* checkBox = static_cast<wxCheckBox*>( ui_item.m_ThicknessLockCtrl );
1221 
1222  if( !checkBox->GetValue() ) // Not locked thickness: can be modified
1223  {
1224  item->SetThickness( dielectric_thickness, sublayer_idx );
1225  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_ThicknessCtrl );
1226  textCtrl->SetValue( StringFromValue( m_units, item->GetThickness( sublayer_idx ),
1227  true, true ) );
1228  }
1229  }
1230  }
1231 }
1232 
1233 
1234 void PANEL_SETUP_BOARD_STACKUP::onColorSelected( wxCommandEvent& event )
1235 {
1236  int idx = event.GetSelection();
1237  int item_id = event.GetId();
1238 
1239  int row = item_id - ID_ITEM_COLOR;
1240 
1241  if( GetColorStandardListCount()-1 == idx ) // Set user color is the last option in list
1242  {
1243  COLOR4D defaultColor( GetColorStandardList()[GetColorUserDefinedListIdx()].m_Color );
1244  COLOR4D currentColor(
1245  m_UserColors.count( row ) ? m_UserColors[row] : COLOR4D( 0.5, 0.5, 0.5, 1.0 ) );
1246 
1247  DIALOG_COLOR_PICKER dlg( this, currentColor, false, nullptr, defaultColor );
1248 
1249  if( dlg.ShowModal() == wxID_OK )
1250  {
1251  wxBitmapComboBox* combo = static_cast<wxBitmapComboBox*>( FindWindowById( item_id ) );
1252 
1253  wxColour color = dlg.GetColor().ToColour();
1254  m_UserColors[row] = color;
1255 
1256  combo->SetString( idx, color.GetAsString( wxC2S_HTML_SYNTAX ) );
1257 
1258  wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
1259  LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ),
1260  COLOR4D( color ) );
1261  combo->SetItemBitmap( combo->GetCount()-1, layerbmp );
1262  }
1263  }
1264 
1265  updateIconColor( row );
1266 }
1267 
1268 
1269 void PANEL_SETUP_BOARD_STACKUP::onMaterialChange( wxCommandEvent& event )
1270 {
1271  // Ensure m_materialList contains all materials already in use in stackup list
1272  // and add it is missing
1274  return;
1275 
1276  for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
1277  {
1278  DIELECTRIC_SUBSTRATE_LIST* mat_list = nullptr;
1279 
1280  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1281  mat_list = &m_delectricMatList;
1282  else if( item->GetType() == BS_ITEM_TYPE_SOLDERMASK )
1283  mat_list = &m_solderMaskMatList;
1284  else if( item->GetType() == BS_ITEM_TYPE_SILKSCREEN )
1285  mat_list = &m_silkscreenMatList;
1286 
1287  else
1288  continue;
1289 
1290  for( int ii = 0; ii < item->GetSublayersCount(); ii++ )
1291  {
1292  int idx = mat_list->FindSubstrate( item->GetMaterial( ii ),
1293  item->GetEpsilonR( ii ),
1294  item->GetLossTangent( ii ) );
1295 
1296  if( idx < 0 && !item->GetMaterial().IsEmpty() )
1297  {
1298  // This material is not in list: add it
1299  DIELECTRIC_SUBSTRATE new_mat;
1300  new_mat.m_Name = item->GetMaterial( ii );
1301  new_mat.m_EpsilonR = item->GetEpsilonR( ii );
1302  new_mat.m_LossTangent = item->GetLossTangent( ii );
1303  mat_list->AppendSubstrate( new_mat );
1304  }
1305  }
1306  }
1307 
1308  int row = event.GetId() - ID_ITEM_MATERIAL;
1309  BOARD_STACKUP_ITEM* item = m_rowUiItemsList[row].m_Item;
1310  int sub_item = m_rowUiItemsList[row].m_SubItem;
1311  DIELECTRIC_SUBSTRATE_LIST* item_mat_list = nullptr;
1312 
1313  switch( item->GetType() )
1314  {
1316  item_mat_list = &m_delectricMatList;
1317  break;
1318 
1320  item_mat_list = &m_solderMaskMatList;
1321  break;
1322 
1324  item_mat_list = &m_silkscreenMatList;
1325  break;
1326 
1327  default:
1328  item_mat_list = nullptr;
1329  break;
1330  }
1331 
1332  DIALOG_DIELECTRIC_MATERIAL dlg( this, *item_mat_list );
1333 
1334  if( dlg.ShowModal() != wxID_OK )
1335  return;
1336 
1337  DIELECTRIC_SUBSTRATE substrate = dlg.GetSelectedSubstrate();
1338 
1339  if( substrate.m_Name.IsEmpty() ) // No substrate specified
1340  return;
1341 
1342  // Update Name, Epsilon R and Loss tg
1343  item->SetMaterial( substrate.m_Name, sub_item );
1344  item->SetEpsilonR( substrate.m_EpsilonR, sub_item );
1345  item->SetLossTangent( substrate.m_LossTangent, sub_item );
1346 
1347  wxTextCtrl* textCtrl;
1348  textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_MaterialCtrl );
1349  textCtrl->SetValue( item->GetMaterial( sub_item ) );
1350 
1351  // some layers have a material choice but not EpsilonR ctrl
1352  if( item->HasEpsilonRValue() )
1353  {
1354  textCtrl = dynamic_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_EpsilonCtrl );
1355 
1356  if( textCtrl )
1357  textCtrl->SetValue( item->FormatEpsilonR( sub_item ) );
1358  }
1359 
1360  // some layers have a material choice but not loss tg ctrl
1361  if( item->HasLossTangentValue() )
1362  {
1363  textCtrl = dynamic_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_LossTgCtrl );
1364 
1365  if( textCtrl )
1366  textCtrl->SetValue( item->FormatLossTangent( sub_item ) );
1367  }
1368 }
1369 
1370 
1372 {
1373  int row = event.GetId() - ID_ITEM_THICKNESS;
1374  wxString value = event.GetString();
1375 
1376  BOARD_STACKUP_ITEM* item = GetStackupItem( row );
1377  int idx = GetSublayerId( row );
1378 
1379  item->SetThickness( ValueFromString( m_frame->GetUserUnits(), value, true ), idx );
1380 }
1381 
1382 
1384 {
1385  return m_rowUiItemsList[aRow].m_Item;
1386 }
1387 
1388 
1390 {
1391  return m_rowUiItemsList[aRow].m_SubItem;
1392 }
1393 
1394 
1396 {
1397  BOARD_STACKUP_ITEM* st_item = dynamic_cast<BOARD_STACKUP_ITEM*>( GetStackupItem( aRow ) );
1398 
1399  wxASSERT( st_item );
1400  wxColor color;
1401 
1402  if( ! st_item )
1403  return color;
1404 
1405  switch( st_item->GetType() )
1406  {
1407  case BS_ITEM_TYPE_COPPER: color = copperColor; break;
1409  case BS_ITEM_TYPE_SOLDERMASK: color = GetSelectedColor( aRow ); break;
1410  case BS_ITEM_TYPE_SILKSCREEN: color = GetSelectedColor( aRow ); break;
1411  case BS_ITEM_TYPE_SOLDERPASTE: color = pasteColor; break;
1412 
1413  default:
1415  wxFAIL_MSG( "PANEL_SETUP_BOARD_STACKUP::getColorIconItem: unrecognized item type" );
1416  break;
1417  }
1418 
1419  return color;
1420 }
1421 
1422 
1424 {
1425  if( aRow >= 0 )
1426  {
1427  wxStaticBitmap* st_bitmap = m_rowUiItemsList[aRow].m_Icon;
1428  // explicit depth important under MSW
1429  wxBitmap bmp(m_colorIconsSize.x, m_colorIconsSize.y / 2, 24);
1430  drawBitmap( bmp, getColorIconItem( aRow ) );
1431  st_bitmap->SetBitmap( bmp );
1432  return;
1433  }
1434 
1435  for( unsigned row = 0; row < m_rowUiItemsList.size(); row++ )
1436  {
1437  // explicit depth important under MSW
1438  wxBitmap bmp(m_colorIconsSize.x, m_colorIconsSize.y / 2, 24);
1439  drawBitmap( bmp, getColorIconItem( row ) );
1440  m_rowUiItemsList[row].m_Icon->SetBitmap( bmp );
1441  }
1442 }
1443 
1444 
1446  int aRow )
1447 {
1448  wxBitmapComboBox* combo = new wxBitmapComboBox( m_scGridWin, ID_ITEM_COLOR+aRow,
1449  wxEmptyString, wxDefaultPosition,
1450  wxDefaultSize, 0, nullptr, wxCB_READONLY );
1451  // Fills the combo box with choice list + bitmaps
1452  const FAB_LAYER_COLOR* color_list = GetColorStandardList();
1453 
1454  for( int ii = 0; ii < GetColorStandardListCount(); ii++ )
1455  {
1456  const FAB_LAYER_COLOR& item = color_list[ii];
1457 
1458  wxColor curr_color = item.m_Color;
1459  wxString label;
1460 
1461  // Defined colors have a name, the user color uses the HTML notation ( i.e. #FF0000)
1462  if( GetColorStandardListCount()-1 > (int)combo->GetCount() )
1463  label = wxGetTranslation( item.m_ColorName );
1464  else // Append the user color, if specified, else add a default user color
1465  {
1466  if( aStackupItem && aStackupItem->GetColor().StartsWith( "#" ) )
1467  {
1468  curr_color = wxColour( aStackupItem->GetColor() );
1469  label = aStackupItem->GetColor();
1470  }
1471  else
1472  label = curr_color.GetAsString( wxC2S_HTML_SYNTAX );
1473  }
1474 
1475  wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
1476  LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ),
1477  COLOR4D( curr_color ) );
1478 
1479  combo->Append( label, layerbmp );
1480  }
1481 
1482 #ifdef __WXGTK__
1483  // Adjust the minimal width. On GTK, the size calculated by wxWidgets is not good
1484  // bitmaps are ignored
1485  combo->Fit();
1486  int min_width = combo->GetSize().x;
1487  min_width += m_colorSwatchesSize.x;
1488  combo->SetMinSize( wxSize( min_width, -1 ) );
1489 #endif
1490 
1491  // add the wxBitmapComboBox to wxControl list, to be able to disconnect the event
1492  // on exit
1493  m_controlItemsList.push_back( combo );
1494 
1495  combo->Connect( wxEVT_COMMAND_COMBOBOX_SELECTED,
1496  wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onColorSelected ),
1497  NULL, this );
1498 
1499  return combo;
1500 }
1501 
1502 
1503 void drawBitmap( wxBitmap& aBitmap, wxColor aColor )
1504 {
1505  wxNativePixelData data( aBitmap );
1506  wxNativePixelData::Iterator p(data);
1507 
1508  for( int yy = 0; yy < data.GetHeight(); yy++ )
1509  {
1510  wxNativePixelData::Iterator rowStart = p;
1511 
1512  for( int xx = 0; xx < data.GetWidth(); xx++ )
1513  {
1514  p.Red() = aColor.Red();
1515  p.Green() = aColor.Green();
1516  p.Blue() = aColor.Blue();
1517  ++p;
1518  }
1519 
1520  p = rowStart;
1521  p.OffsetY(data, 1);
1522  }
1523 }
1524 
1525 
1526 void PANEL_SETUP_BOARD_STACKUP::OnUpdateUI( wxUpdateUIEvent& event )
1527 {
1528  // Handle an error. This is delayed to OnUpdateUI so that we can change the focus
1529  // even when the original validation was triggered from a killFocus event, and so
1530  // that the corresponding notebook page can be shown in the background when triggered
1531  // from an OK.
1532  if( m_stackupMismatch )
1533  {
1534  m_stackupMismatch = false;
1535 
1536  wxRichMessageDialog dlg( this,
1537  _( "Physical stackup has not been updated to match layer count." ),
1538  _( "Update Physical Stackup" ),
1539  wxOK | wxCENTER | wxICON_WARNING );
1540  dlg.ShowCheckBox( _( "Update dielectric thickness from board thickness" ), true );
1541 
1542  dlg.ShowModal();
1543 
1544  if( dlg.IsCheckBoxChecked() )
1545  {
1546  wxCommandEvent dummy;
1548  }
1549  }
1550 }
1551 
1552 
BOARD_STACKUP_ITEM_TYPE GetType() const
void onExportToClipboard(wxCommandEvent &event) override
static wxColor pasteColor(200, 200, 200)
std::vector< wxControl * > m_controlItemsList
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
Function GetLayerName returns the name of a layer.
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:61
bool IsThicknessLocked(int aDielectricSubLayer=0) const
BS_EDGE_CONNECTOR_CONSTRAINTS m_EdgeConnectorConstraints
If the board has edge connector cards, some constrains can be specifed in job file: BS_EDGE_CONNECTOR...
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
Function GetDesignSettings.
Definition: class_board.h:553
DIELECTRIC_SUBSTRATE_LIST m_silkscreenMatList
LSET GetEnabledLayers() const
Function GetEnabledLayers is a proxy function that calls the corresponding function in m_BoardSetting...
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:116
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
#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()
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()
Function InternalCuMask() returns a complete set of internal copper layers, which is all Cu layers ex...
Definition: lset.cpp:672
static LSET ExternalCuMask()
Function ExternalCuMask returns a mask holding the Front and Bottom layers.
Definition: lset.cpp:742
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)
#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:205
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.
BOARD holds information pertinent to a Pcbnew printed circuit board.
Definition: class_board.h:180
#define _(s)
Definition: 3d_actions.cpp:33
PAGED_TREEBOOK * GetTreebook()
Definition: paged_dialog.h:61
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.
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
bool m_HasStackup
Set to true if the board has a stackup management.
#define KEY_CORE
void Add(BOARD_STACKUP_ITEM *aItem)
Add a new item in stackup layer.
void OnUpdateUI(wxUpdateUIEvent &event) override
void onRemoveDielectricLayer(wxCommandEvent &event) override
int GetCopperLayerCount() const
Function GetCopperLayerCount.
void SetLossTangent(double aTg, int aDielectricSubLayer=0)
wxString GetMaterial(int aDielectricSubLayer=0) const
wxString StringFromValue(EDA_UNITS aUnits, double aValue, bool aAddUnitSymbol, bool aUseMils, EDA_DATA_TYPE aType)
Function StringFromValue returns the string from aValue according to units (inch, mm ....
Definition: base_units.cpp:233
void onMaterialChange(wxCommandEvent &event)
EDA_UNITS GetUserUnits() const
Return the user units currently in use.
long long int ValueFromString(EDA_UNITS aUnits, const wxString &aTextValue, bool aUseMils, EDA_DATA_TYPE aType)
Function ValueFromString converts aTextValue in aUnits to internal units used by the application.
Definition: base_units.cpp:471
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:99
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