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