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