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