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( wxT( "XX" ) );
108  m_colorIconsSize = dc.GetTextExtent( wxT( "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( wxT( "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( wxT( "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, true );
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->ChangeValue( 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->ChangeValue( thicknessStr );
465 
466  return thickness;
467 }
468 
469 
471 {
472  return ( m_choiceCopperLayers->GetSelection() + 1 ) * 2;
473 }
474 
475 
477 {
478  int copperCount = GetCopperLayerCount();
479 
480  wxASSERT( copperCount >= 2 );
481 
483  m_enabledLayers &= ~LSET::InternalCuMask();
484 
485  for( int i = 1; i < copperCount - 1; i++ )
486  m_enabledLayers.set( F_Cu + i );
487 }
488 
489 
491 {
492  const BOARD_STACKUP& brd_stackup = m_brdSettings->GetStackupDescriptor();
493  const FAB_LAYER_COLOR* color_list = GetColorStandardList();
494 
495  if( aFullSync )
496  {
497  m_choiceCopperLayers->SetSelection( ( m_board->GetCopperLayerCount() / 2 ) - 1 );
498  m_impedanceControlled->SetValue( brd_stackup.m_HasDielectricConstrains );
499  }
500 
501  for( BOARD_STACKUP_ROW_UI_ITEM& ui_row_item : m_rowUiItemsList )
502  {
503  BOARD_STACKUP_ITEM* item = ui_row_item.m_Item;
504  int sub_item = ui_row_item.m_SubItem;
505 
506  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
507  {
508  wxChoice* choice = dynamic_cast<wxChoice*>( ui_row_item.m_LayerTypeCtrl );
509 
510  if( choice )
511  choice->SetSelection( item->GetTypeName() == KEY_CORE ? 0 : 1 );
512  }
513 
514  if( item->IsMaterialEditable() )
515  {
516  wxTextCtrl* matName = dynamic_cast<wxTextCtrl*>( ui_row_item.m_MaterialCtrl );
517 
518  if( matName )
519  {
520  if( IsPrmSpecified( item->GetMaterial( sub_item ) ) )
521  matName->ChangeValue( item->GetMaterial( sub_item ) );
522  else
523  matName->ChangeValue( wxGetTranslation( NotSpecifiedPrm() ) );
524  }
525  }
526 
527  if( item->IsThicknessEditable() )
528  {
529  wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_ThicknessCtrl );
530 
531  if( textCtrl )
532  textCtrl->ChangeValue( StringFromValue( m_units,
533  item->GetThickness( sub_item ), true ) );
534 
535  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
536  {
537  wxCheckBox* cb_box = dynamic_cast<wxCheckBox*> ( ui_row_item.m_ThicknessLockCtrl );
538 
539  if( cb_box )
540  cb_box->SetValue( item->IsThicknessLocked( sub_item ) );
541  }
542  }
543 
544  if( item->IsColorEditable() )
545  {
546  auto bm_combo = dynamic_cast<wxBitmapComboBox*>( ui_row_item.m_ColorCtrl );
547 
548  if( item->GetColor().StartsWith( wxT( "#" ) ) ) // User defined color
549  {
550  ui_row_item.m_UserColor = COLOR4D( item->GetColor() ).ToColour();
551 
552  if( bm_combo ) // Update user color shown in the wxBitmapComboBox
553  {
554  bm_combo->SetString( GetColorUserDefinedListIdx(), item->GetColor() );
555  wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
557  wxColour( item->GetColor() ) );
558  bm_combo->SetItemBitmap( GetColorUserDefinedListIdx(), layerbmp );
559  bm_combo->SetSelection( GetColorUserDefinedListIdx() );
560  }
561  }
562  else
563  {
564  if( bm_combo )
565  {
566  // Note: don't use bm_combo->FindString() because the combo strings are
567  // translated.
568  for( int ii = 0; ii < GetColorStandardListCount(); ii++ )
569  {
570  if( color_list[ii].GetName() == item->GetColor() )
571  {
572  bm_combo->SetSelection( ii );
573  break;
574  }
575  }
576  }
577  }
578 
579  }
580 
581  if( item->HasEpsilonRValue() )
582  {
583  wxString txt = Double2Str( item->GetEpsilonR( sub_item ) );
584  wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_EpsilonCtrl );
585 
586  if( textCtrl )
587  textCtrl->ChangeValue( txt );
588  }
589 
590  if( item->HasLossTangentValue() )
591  {
592  wxString txt = Double2Str( item->GetLossTangent( sub_item ) );
593  wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_LossTgCtrl );
594 
595  if( textCtrl )
596  textCtrl->ChangeValue( txt );
597  }
598  }
599 
600  // Now enable/disable stackup items, according to the m_enabledLayers config
602 
603  updateIconColor();
604 }
605 
606 
608 {
609 
610  // Now enable/disable stackup items, according to the m_enabledLayers config
611  // Calculate copper layer count from m_enabledLayers, and *do not use* brd_stackup
612  // for that, because it is not necessary up to date
613  // (for instance after modifying the layer count from the panel layers in dialog)
615  int copperLayersCount = copperMask.count();
616 
617  for( BOARD_STACKUP_ROW_UI_ITEM& ui_row_item: m_rowUiItemsList )
618  {
619  bool show_item;
620  BOARD_STACKUP_ITEM* item = ui_row_item.m_Item;
621 
622  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
623  // the m_DielectricLayerId is not a copper layer id, it is a dielectric idx from 1
624  show_item = item->GetDielectricLayerId() < copperLayersCount;
625  else
626  show_item = m_enabledLayers[item->GetBrdLayerId()];
627 
628  item->SetEnabled( show_item );
629 
630  ui_row_item.m_isEnabled = show_item;
631 
632  // Show or not items of this row:
633  ui_row_item.m_Icon->Show( show_item );
634  ui_row_item.m_LayerName->Show( show_item );
635  ui_row_item.m_LayerTypeCtrl->Show( show_item );
636  ui_row_item.m_MaterialCtrl->Show( show_item );
637 
638  if( ui_row_item.m_MaterialButt )
639  ui_row_item.m_MaterialButt->Show( show_item );
640 
641  ui_row_item.m_ThicknessCtrl->Show( show_item );
642  ui_row_item.m_ThicknessLockCtrl->Show( show_item );
643  ui_row_item.m_ColorCtrl->Show( show_item );
644  ui_row_item.m_EpsilonCtrl->Show( show_item );
645  ui_row_item.m_LossTgCtrl->Show( show_item );
646  }
647 }
648 
649 
650 void PANEL_SETUP_BOARD_STACKUP::addMaterialChooser( wxWindowID aId, const wxString* aMaterialName,
651  BOARD_STACKUP_ROW_UI_ITEM& aUiRowItem )
652 {
653  wxBoxSizer* bSizerMat = new wxBoxSizer( wxHORIZONTAL );
654  m_fgGridSizer->Add( bSizerMat, 1, wxRIGHT|wxEXPAND, 4 );
655  wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY );
656 
657  if( aMaterialName )
658  {
659  if( IsPrmSpecified( *aMaterialName ) )
660  textCtrl->ChangeValue( *aMaterialName );
661  else
662  textCtrl->ChangeValue( wxGetTranslation( NotSpecifiedPrm() ) );
663  }
664 
665  textCtrl->SetMinSize( m_numericTextCtrlSize );
666  bSizerMat->Add( textCtrl, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
667 
668  wxButton* m_buttonMat = new wxButton( m_scGridWin, aId, _( "..." ), wxDefaultPosition,
669  wxDefaultSize, wxBU_EXACTFIT );
670  bSizerMat->Add( m_buttonMat, 0, wxALIGN_CENTER_VERTICAL, 2 );
671 
672  m_buttonMat->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
673  wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onMaterialChange ),
674  nullptr, this );
675  m_controlItemsList.push_back( m_buttonMat );
676 
677  aUiRowItem.m_MaterialCtrl = textCtrl;
678  aUiRowItem.m_MaterialButt = m_buttonMat;
679 }
680 
681 
683 {
684  wxStaticText* emptyText = new wxStaticText( m_scGridWin, wxID_ANY, wxEmptyString );
685  m_fgGridSizer->Add( emptyText, 0, wxALIGN_CENTER_VERTICAL );
686  return emptyText;
687 }
688 
689 
691  BOARD_STACKUP_ITEM* aStackupItem,
692  int aSublayerIdx )
693 {
694  wxASSERT( aStackupItem );
695  wxASSERT( aSublayerIdx >= 0 && aSublayerIdx < aStackupItem->GetSublayersCount() );
696 
697  BOARD_STACKUP_ROW_UI_ITEM ui_row_item( aStackupItem, aSublayerIdx );
698  BOARD_STACKUP_ITEM* item = aStackupItem;
699  int row = aRow;
700 
701  const FAB_LAYER_COLOR* color_list = GetColorStandardList();
702 
703  // Add color swatch icon. The color will be updated later,
704  // when all widgets are initialized
705  wxStaticBitmap* bitmap = new wxStaticBitmap( m_scGridWin, wxID_ANY, wxNullBitmap );
706  m_fgGridSizer->Add( bitmap, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 4 );
707  ui_row_item.m_Icon = bitmap;
708 
709  ui_row_item.m_isEnabled = true;
710 
711  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
712  {
713  wxString lname = item->FormatDielectricLayerName();
714 
715  if( item->GetSublayersCount() > 1 )
716  {
717  lname << wxT( " (" ) << aSublayerIdx+1 << wxT( "/" )
718  << item->GetSublayersCount() << wxT( ")" );
719  }
720 
721  wxStaticText* st_text = new wxStaticText( m_scGridWin, wxID_ANY, lname );
722  m_fgGridSizer->Add( st_text, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
723  ui_row_item.m_LayerName = st_text;
724 
725  // For a dielectric layer, the layer type choice is not for each sublayer,
726  // only for the first (aSublayerIdx = 0), and is common to all sublayers
727  if( aSublayerIdx == 0 )
728  {
729  wxChoice* choice = new wxChoice( m_scGridWin, wxID_ANY, wxDefaultPosition,
730  wxDefaultSize, m_core_prepreg_choice );
731  choice->SetSelection( item->GetTypeName() == KEY_CORE ? 0 : 1 );
732  m_fgGridSizer->Add( choice, 1, wxEXPAND|wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
733 
734  ui_row_item.m_LayerTypeCtrl = choice;
735  }
736  else
737  {
738  ui_row_item.m_LayerTypeCtrl = addSpacer();
739  }
740  }
741  else
742  {
743  item->SetLayerName( m_board->GetLayerName( item->GetBrdLayerId() ) );
744  wxStaticText* st_text = new wxStaticText( m_scGridWin, wxID_ANY, item->GetLayerName() );
745  m_fgGridSizer->Add( st_text, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 1 );
746  st_text->Show( true );
747  ui_row_item.m_LayerName = st_text;
748 
749  wxString lname;
750 
751  if( item->GetTypeName() == KEY_COPPER )
752  lname = _( "Copper" );
753  else
754  lname = wxGetTranslation( item->GetTypeName() );
755 
756  st_text = new wxStaticText( m_scGridWin, wxID_ANY, lname );
757  m_fgGridSizer->Add( st_text, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
758  ui_row_item.m_LayerTypeCtrl = st_text;
759  }
760 
761  if( item->IsMaterialEditable() )
762  {
763  wxString matName = item->GetMaterial( aSublayerIdx );
764  addMaterialChooser( ID_ITEM_MATERIAL+row, &matName, ui_row_item );
765  }
766  else
767  {
768  ui_row_item.m_MaterialCtrl = addSpacer();
769  }
770 
771  if( item->IsThicknessEditable() )
772  {
773  wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, ID_ITEM_THICKNESS+row );
774  textCtrl->SetMinSize( m_numericTextCtrlSize );
775  textCtrl->ChangeValue( StringFromValue( m_units, item->GetThickness( aSublayerIdx ), true ) );
776  m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
777  m_controlItemsList.push_back( textCtrl );
778  textCtrl->Connect( wxEVT_COMMAND_TEXT_UPDATED,
779  wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onThicknessChange ),
780  nullptr, this );
781  ui_row_item.m_ThicknessCtrl = textCtrl;
782 
783  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
784  {
785  wxCheckBox* cb_box = new wxCheckBox( m_scGridWin, ID_ITEM_THICKNESS_LOCKED+row,
786  wxEmptyString );
787  cb_box->SetValue( item->IsThicknessLocked( aSublayerIdx ) );
788  m_fgGridSizer->Add( cb_box, 0, wxALIGN_CENTER_VERTICAL, 2 );
789  ui_row_item.m_ThicknessLockCtrl = cb_box;
790  }
791  else
792  {
793  ui_row_item.m_ThicknessLockCtrl = addSpacer();
794  }
795  }
796  else
797  {
798  ui_row_item.m_ThicknessCtrl = addSpacer();
799  ui_row_item.m_ThicknessLockCtrl = addSpacer();
800  }
801 
802  if( item->IsColorEditable() )
803  {
804  if( item->GetColor().StartsWith( wxT( "#" ) ) ) // User defined color
805  {
806  ui_row_item.m_UserColor = COLOR4D( item->GetColor() ).ToColour();
807  }
808  else
809  ui_row_item.m_UserColor = GetDefaultUserColor( item->GetType() );
810 
811  wxBitmapComboBox* bm_combo = createColorBox( item, row );
812  m_fgGridSizer->Add( bm_combo, 1, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL|wxEXPAND, 2 );
813 
814  if( item->GetColor().StartsWith( wxT( "#" ) ) )
815  {
816  bm_combo->SetString( GetColorUserDefinedListIdx(), item->GetColor() );
817  bm_combo->SetSelection( GetColorUserDefinedListIdx() );
818  }
819  else
820  {
821  // Note: don't use bm_combo->FindString() because the combo strings are translated.
822  for( int ii = 0; ii < GetColorStandardListCount(); ii++ )
823  {
824  if( color_list[ii].GetName() == item->GetColor() )
825  {
826  bm_combo->SetSelection( ii );
827  break;
828  }
829  }
830  }
831 
832  ui_row_item.m_ColorCtrl = bm_combo;
833  }
834  else
835  {
836  ui_row_item.m_ColorCtrl = addSpacer();
837  }
838 
839  if( item->HasEpsilonRValue() )
840  {
841  wxString txt = Double2Str( item->GetEpsilonR( aSublayerIdx ) );
842  wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString,
843  wxDefaultPosition, m_numericFieldsSize );
844  textCtrl->ChangeValue( txt );
845  m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
846  ui_row_item.m_EpsilonCtrl = textCtrl;
847  }
848  else
849  {
850  ui_row_item.m_EpsilonCtrl = addSpacer();
851  }
852 
853  if( item->HasLossTangentValue() )
854  {
855  wxString txt = Double2Str( item->GetLossTangent( aSublayerIdx ) );;
856  wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString,
857  wxDefaultPosition, m_numericFieldsSize );
858  textCtrl->ChangeValue( txt );
859  m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
860  ui_row_item.m_LossTgCtrl = textCtrl;
861  }
862  else
863  {
864  ui_row_item.m_LossTgCtrl = addSpacer();
865  }
866 
867  return ui_row_item;
868 }
869 
870 
872 {
873  wxWindowUpdateLocker locker( m_scGridWin );
874  m_scGridWin->Hide();
875 
876  // Rebuild the stackup for the dialog, after dielectric parameters list is modified
877  // (added/removed):
878 
879  // First, delete all ui objects, because wxID values will be no longer valid for many widgets
881  m_controlItemsList.clear();
882 
883  // Delete widgets (handled by the wxPanel parent)
885  {
886  // This remove and delete the current ui_item.m_MaterialCtrl sizer
887  ui_item.m_MaterialCtrl->SetSizer( nullptr );
888 
889  // Delete other widgets
890  delete ui_item.m_Icon; // Color icon in first column (column 1)
891  delete ui_item.m_LayerName; // string shown in column 2
892  delete ui_item.m_LayerTypeCtrl; // control shown in column 3
893  delete ui_item.m_MaterialCtrl; // control shown in column 4, with m_MaterialButt
894  delete ui_item.m_MaterialButt; // control shown in column 4, with m_MaterialCtrl
895  delete ui_item.m_ThicknessCtrl; // control shown in column 5
896  delete ui_item.m_ThicknessLockCtrl;// control shown in column 6
897  delete ui_item.m_ColorCtrl; // control shown in column 7
898  delete ui_item.m_EpsilonCtrl; // control shown in column 8
899  delete ui_item.m_LossTgCtrl; // control shown in column 9
900  }
901 
902  m_rowUiItemsList.clear();
903 
904  // In order to recreate a clean grid layer list, we have to delete and
905  // recreate the sizer m_fgGridSizer (just deleting items in this size is not enough)
906  // therefore we also have to add the "old" title items to the newly recreated m_fgGridSizer:
907  m_scGridWin->SetSizer( nullptr ); // This remove and delete the current m_fgGridSizer
908 
909  m_fgGridSizer = new wxFlexGridSizer( 0, 9, 0, 2 );
910  m_fgGridSizer->SetFlexibleDirection( wxHORIZONTAL );
911  m_fgGridSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
912  m_fgGridSizer->SetHGap( 6 );
913  m_scGridWin->SetSizer( m_fgGridSizer );
914 
915  // Re-add "old" title items:
916  const int sizer_flags = wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_CENTER_HORIZONTAL;
917  m_fgGridSizer->Add( m_staticTextLayer, 0, sizer_flags, 2 );
918  m_fgGridSizer->Add( m_staticTextType, 0, sizer_flags, 2 );
919  m_fgGridSizer->Add( m_staticTextLayerId, 0, sizer_flags, 5 );
920  m_fgGridSizer->Add( m_staticTextMaterial, 0, sizer_flags, 2 );
921  m_fgGridSizer->Add( m_staticTextThickness, 0, sizer_flags, 2 );
922  m_fgGridSizer->Add( m_bitmapLockThickness, 0, sizer_flags, 1 );
923  m_fgGridSizer->Add( m_staticTextColor, 0, sizer_flags, 2 );
924  m_fgGridSizer->Add( m_staticTextEpsilonR, 0, sizer_flags, 2 );
925  m_fgGridSizer->Add( m_staticTextLossTg, 0, sizer_flags, 2 );
926 
927 
928  // Now, rebuild the widget list from the new m_stackup items:
929  buildLayerStackPanel( false );
930 
931  // Now enable/disable stackup items, according to the m_enabledLayers config
933 
934  m_scGridWin->Layout();
935  m_scGridWin->Show();
936 }
937 
938 
939 void PANEL_SETUP_BOARD_STACKUP::buildLayerStackPanel( bool aCreatedInitialStackup )
940 {
941  // Build a full stackup for the dialog, with a active copper layer count
942  // = current board layer count to calculate a reasonable default stackup:
943  if( aCreatedInitialStackup )
944  {
945  // Creates a full BOARD_STACKUP with 32 copper layers.
946  // extra layers will be hidden later.
947  // but if the number of layer is changed in the dialog, the corresponding
948  // widgets will be available with their previous values.
950  const BOARD_STACKUP& brd_stackup = m_brdSettings->GetStackupDescriptor();
951 
952  // Now initialize all stackup items to the board values, when exist
953  for( BOARD_STACKUP_ITEM* item: m_stackup.GetList() )
954  {
955  // Search for board settings:
956  for( BOARD_STACKUP_ITEM* board_item: brd_stackup.GetList() )
957  {
958  if( item->GetBrdLayerId() != UNDEFINED_LAYER )
959  {
960  if( item->GetBrdLayerId() == board_item->GetBrdLayerId() )
961  {
962  *item = *board_item;
963  break;
964  }
965  }
966  else // dielectric layer: see m_DielectricLayerId for identification
967  {
968  // Compare dielectric layer with dielectric layer
969  if( board_item->GetBrdLayerId() != UNDEFINED_LAYER )
970  continue;
971 
972  if( item->GetDielectricLayerId() == board_item->GetDielectricLayerId() )
973  {
974  *item = *board_item;
975  break;
976  }
977  }
978  }
979  }
980  }
981 
982  int row = 0;
983 
984  for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
985  {
986  for( int sub_idx = 0; sub_idx < item->GetSublayersCount(); sub_idx++ )
987  {
988  BOARD_STACKUP_ROW_UI_ITEM ui_row_item = createRowData( row, item, sub_idx );
989  m_rowUiItemsList.emplace_back( ui_row_item );
990 
991  row++;
992  }
993  }
994 
995  updateIconColor();
996 }
997 
998 
999 // Transfer current UI settings to m_stackup but not to the board
1001 {
1002  wxString txt;
1003  wxString error_msg;
1004  bool success = true;
1005  double value;
1006  int row = 0;
1007 
1009  {
1010  // Skip stackup items useless for the current board
1011  if( !ui_item.m_isEnabled )
1012  {
1013  row++;
1014  continue;
1015  }
1016 
1017  BOARD_STACKUP_ITEM* item = ui_item.m_Item;
1018  int sub_item = ui_item.m_SubItem;
1019 
1020  // Add sub layer if there is a new sub layer:
1021  while( item->GetSublayersCount() <= sub_item )
1022  item->AddDielectricPrms( item->GetSublayersCount() );
1023 
1024  if( sub_item == 0 ) // Name only main layer
1025  item->SetLayerName( ui_item.m_LayerName->GetLabel() );
1026 
1027  if( item->HasEpsilonRValue() )
1028  {
1029  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_EpsilonCtrl );
1030  txt = textCtrl->GetValue();
1031 
1032  if( txt.ToDouble( &value ) && value >= 0.0 )
1033  item->SetEpsilonR( value, sub_item );
1034  else if( txt.ToCDouble( &value ) && value >= 0.0 )
1035  item->SetEpsilonR( value, sub_item );
1036  else
1037  {
1038  success = false;
1039  error_msg << _( "Incorrect value for Epsilon R (Epsilon R must be positive or "
1040  "null if not used)" );
1041  }
1042  }
1043 
1044  if( item->HasLossTangentValue() )
1045  {
1046  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_LossTgCtrl );
1047  txt = textCtrl->GetValue();
1048 
1049  if( txt.ToDouble( &value ) && value >= 0.0 )
1050  item->SetLossTangent( value, sub_item );
1051  else if( txt.ToCDouble( &value ) && value >= 0.0 )
1052  item->SetLossTangent( value, sub_item );
1053  else
1054  {
1055  success = false;
1056 
1057  if( !error_msg.IsEmpty() )
1058  error_msg << wxT( "\n" );
1059 
1060  error_msg << _( "Incorrect value for Loss tg (Loss tg must be positive or null "
1061  "if not used)" );
1062  }
1063  }
1064 
1065  if( item->IsMaterialEditable() )
1066  {
1067  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_MaterialCtrl );
1068  item->SetMaterial( textCtrl->GetValue(), sub_item );
1069 
1070  // Ensure the not specified mat name is the keyword, not its translation
1071  // to avoid any issue is the language setting changes
1072  if( !IsPrmSpecified( item->GetMaterial( sub_item ) ) )
1073  item->SetMaterial( NotSpecifiedPrm(), sub_item );
1074  }
1075 
1076  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1077  {
1078  // Choice is Core or Prepreg. Sublayers have no choice:
1079  wxChoice* choice = dynamic_cast<wxChoice*>( ui_item.m_LayerTypeCtrl );
1080 
1081  if( choice )
1082  {
1083  int idx = choice->GetSelection();
1084 
1085  if( idx == 0 )
1086  item->SetTypeName( KEY_CORE );
1087  else
1088  item->SetTypeName( KEY_PREPREG );
1089  }
1090  }
1091 
1092  if( item->IsThicknessEditable() )
1093  {
1094  wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_ThicknessCtrl );
1095  txt = textCtrl->GetValue();
1096 
1097  int new_thickness = ValueFromString( m_frame->GetUserUnits(), txt );
1098  item->SetThickness( new_thickness, sub_item );
1099 
1100  if( new_thickness < 0 )
1101  {
1102  success = false;
1103 
1104  if( !error_msg.IsEmpty() )
1105  error_msg << wxT( "\n" );
1106 
1107  error_msg << _( "A layer thickness is < 0. Fix it" );
1108  }
1109 
1110  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1111  {
1112  // Dielectric thickness layer can have a locked thickness:
1113  wxCheckBox* cb_box = static_cast<wxCheckBox*>
1114  ( ui_item.m_ThicknessLockCtrl );
1115  item->SetThicknessLocked( cb_box && cb_box->GetValue(), sub_item );
1116  }
1117  }
1118 
1119  if( sub_item == 0 && item->IsColorEditable() )
1120  {
1121  const FAB_LAYER_COLOR* color_list = GetColorStandardList();
1122 
1123  wxBitmapComboBox* choice = dynamic_cast<wxBitmapComboBox*>( ui_item.m_ColorCtrl );
1124 
1125  if( choice )
1126  {
1127  int idx = choice->GetSelection();
1128 
1129  if( idx == GetColorUserDefinedListIdx() )
1130  {
1131  // NB: wxWidgets 3.0's color.GetAsString( wxC2S_HTML_SYNTAX ) pukes on alpha
1132  item->SetColor( getColourAsHexString( ui_item.m_UserColor ) );
1133  }
1134  else
1135  {
1136  item->SetColor( color_list[idx].GetName() );
1137  }
1138  }
1139  }
1140 
1141  row++;
1142  }
1143 
1144  if( !success )
1145  {
1146  wxMessageBox( error_msg, _( "Errors" ) );
1147  return false;
1148  }
1149 
1151 
1152  return true;
1153 }
1154 
1155 
1157 {
1159  return false;
1160 
1161  // NOTE: Copper layer count is transferred via PANEL_SETUP_LAYERS even though it is configured
1162  // on this page, because the logic for confirming deletion of board items on deleted layers is
1163  // on that panel and it doesn't make sense to split it up.
1164 
1166 
1167  STRING_FORMATTER old_stackup;
1168 
1169  // FormatBoardStackup() (using FormatInternalUnits()) expects a "C" locale
1170  // to execute some tests. So switch to the suitable locale
1171  LOCALE_IO dummy;
1172  brd_stackup.FormatBoardStackup( &old_stackup, m_board, 0 );
1173 
1174  brd_stackup.m_FinishType = m_stackup.m_FinishType;
1178  brd_stackup.m_EdgePlating = m_stackup.m_EdgePlating;
1179 
1180  // copy enabled items to the new board stackup
1181  brd_stackup.RemoveAll();
1182 
1183  for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
1184  {
1185  if( item->IsEnabled() )
1186  brd_stackup.Add( new BOARD_STACKUP_ITEM( *item ) );
1187  }
1188 
1189  STRING_FORMATTER new_stackup;
1190  brd_stackup.FormatBoardStackup( &new_stackup, m_board, 0 );
1191 
1192  bool modified = old_stackup.GetString() != new_stackup.GetString();
1193  int thickness = brd_stackup.BuildBoardThicknessFromStackup();
1194 
1195  if( m_brdSettings->GetBoardThickness() != thickness )
1196  {
1197  m_brdSettings->SetBoardThickness( thickness );
1198  modified = true;
1199  }
1200 
1201  if( !m_brdSettings->m_HasStackup )
1202  {
1203  m_brdSettings->m_HasStackup = true;
1204  modified = true;
1205  }
1206 
1207  if( modified )
1208  m_frame->OnModify();
1209 
1210  return true;
1211 }
1212 
1213 
1215 {
1216  BOARD* savedBrd = m_board;
1217  BOARD_DESIGN_SETTINGS* savedSettings = m_brdSettings;
1218  m_brdSettings = &aBoard->GetDesignSettings();
1219 
1221  synchronizeWithBoard( true );
1222 
1225 
1226  m_brdSettings = savedSettings;
1227  m_board = savedBrd;
1228 }
1229 
1230 
1232 {
1233  // First, verify the list of layers currently in stackup:
1234  // if it does not mach the list of layers set in PANEL_SETUP_LAYERS
1235  // rebuild the panel
1236 
1237  // the current enabled layers in PANEL_SETUP_LAYERS
1238  // Note: the number of layer can change, but not the layers properties
1240 
1241  if( m_enabledLayers != layersList )
1242  {
1243  m_enabledLayers = layersList;
1244 
1245  synchronizeWithBoard( false );
1246 
1247  Layout();
1248  Refresh();
1249  }
1250 }
1251 
1252 
1253 void PANEL_SETUP_BOARD_STACKUP::onColorSelected( wxCommandEvent& event )
1254 {
1255  int idx = event.GetSelection();
1256  int item_id = event.GetId();
1257 
1258  int row = item_id - ID_ITEM_COLOR;
1259 
1260  if( idx == GetColorStandardListCount() - 1 ) // Set user color is the last option in list
1261  {
1262  DIALOG_COLOR_PICKER dlg( this, m_rowUiItemsList[row].m_UserColor, true, nullptr,
1263  GetDefaultUserColor( m_rowUiItemsList[row].m_Item->GetType() ) );
1264 
1265 #ifdef __WXGTK__
1266  // Give a time-slice to close the menu before opening the dialog.
1267  // (Only matters on some versions of GTK.)
1268  wxSafeYield();
1269 #endif
1270 
1271  if( dlg.ShowModal() == wxID_OK )
1272  {
1273  wxBitmapComboBox* combo = static_cast<wxBitmapComboBox*>( FindWindowById( item_id ) );
1274  wxColour color = dlg.GetColor().ToColour();
1275 
1276  m_rowUiItemsList[row].m_UserColor = color;
1277 
1278  // NB: wxWidgets 3.0's color.GetAsString( wxC2S_HTML_SYNTAX ) pukes on alpha
1279  combo->SetString( idx, getColourAsHexString( color ) );
1280 
1281  wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
1282  LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ), COLOR4D( color ) );
1283  combo->SetItemBitmap( combo->GetCount()-1, layerbmp );
1284 
1285  combo->SetSelection( idx );
1286  }
1287  }
1288 
1289  updateIconColor( row );
1290 }
1291 
1292 
1293 void PANEL_SETUP_BOARD_STACKUP::onMaterialChange( wxCommandEvent& event )
1294 {
1295  // Ensure m_materialList contains all materials already in use in stackup list
1296  // and add it is missing
1298  return;
1299 
1300  for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
1301  {
1302  DIELECTRIC_SUBSTRATE_LIST* mat_list = nullptr;
1303 
1304  if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1305  mat_list = &m_delectricMatList;
1306  else if( item->GetType() == BS_ITEM_TYPE_SOLDERMASK )
1307  mat_list = &m_solderMaskMatList;
1308  else if( item->GetType() == BS_ITEM_TYPE_SILKSCREEN )
1309  mat_list = &m_silkscreenMatList;
1310 
1311  else
1312  continue;
1313 
1314  for( int ii = 0; ii < item->GetSublayersCount(); ii++ )
1315  {
1316  int idx = mat_list->FindSubstrate( item->GetMaterial( ii ),
1317  item->GetEpsilonR( ii ),
1318  item->GetLossTangent( ii ) );
1319 
1320  if( idx < 0 && !item->GetMaterial().IsEmpty() )
1321  {
1322  // This material is not in list: add it
1323  DIELECTRIC_SUBSTRATE new_mat;
1324  new_mat.m_Name = item->GetMaterial( ii );
1325  new_mat.m_EpsilonR = item->GetEpsilonR( ii );
1326  new_mat.m_LossTangent = item->GetLossTangent( ii );
1327  mat_list->AppendSubstrate( new_mat );
1328  }
1329  }
1330  }
1331 
1332  int row = event.GetId() - ID_ITEM_MATERIAL;
1333  BOARD_STACKUP_ITEM* item = m_rowUiItemsList[row].m_Item;
1334  int sub_item = m_rowUiItemsList[row].m_SubItem;
1335  DIELECTRIC_SUBSTRATE_LIST* item_mat_list = nullptr;
1336 
1337  switch( item->GetType() )
1338  {
1339  case BS_ITEM_TYPE_DIELECTRIC: item_mat_list = &m_delectricMatList; break;
1340  case BS_ITEM_TYPE_SOLDERMASK: item_mat_list = &m_solderMaskMatList; break;
1341  case BS_ITEM_TYPE_SILKSCREEN: item_mat_list = &m_silkscreenMatList; break;
1342  default: item_mat_list = nullptr; break;
1343  }
1344 
1345  wxCHECK( item_mat_list, /* void */ );
1346 
1347  DIALOG_DIELECTRIC_MATERIAL dlg( this, *item_mat_list );
1348 
1349  if( dlg.ShowModal() != wxID_OK )
1350  return;
1351 
1352  DIELECTRIC_SUBSTRATE substrate = dlg.GetSelectedSubstrate();
1353 
1354  if( substrate.m_Name.IsEmpty() ) // No substrate specified
1355  return;
1356 
1357  // Update Name, Epsilon R and Loss tg
1358  item->SetMaterial( substrate.m_Name, sub_item );
1359  item->SetEpsilonR( substrate.m_EpsilonR, sub_item );
1360  item->SetLossTangent( substrate.m_LossTangent, sub_item );
1361 
1362  wxTextCtrl* textCtrl;
1363  textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_MaterialCtrl );
1364  textCtrl->ChangeValue( item->GetMaterial( sub_item ) );
1365 
1366  // some layers have a material choice but not EpsilonR ctrl
1367  if( item->HasEpsilonRValue() )
1368  {
1369  textCtrl = dynamic_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_EpsilonCtrl );
1370 
1371  if( textCtrl )
1372  textCtrl->ChangeValue( item->FormatEpsilonR( sub_item ) );
1373  }
1374 
1375  // some layers have a material choice but not loss tg ctrl
1376  if( item->HasLossTangentValue() )
1377  {
1378  textCtrl = dynamic_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_LossTgCtrl );
1379 
1380  if( textCtrl )
1381  textCtrl->ChangeValue( item->FormatLossTangent( sub_item ) );
1382  }
1383 }
1384 
1385 
1387 {
1388  int row = event.GetId() - ID_ITEM_THICKNESS;
1389  wxString value = event.GetString();
1390 
1391  BOARD_STACKUP_ITEM* item = GetStackupItem( row );
1392  int idx = GetSublayerId( row );
1393 
1394  item->SetThickness( ValueFromString( m_frame->GetUserUnits(), value ), idx );
1395 
1397 }
1398 
1399 
1401 {
1402  return m_rowUiItemsList[aRow].m_Item;
1403 }
1404 
1405 
1407 {
1408  return m_rowUiItemsList[aRow].m_SubItem;
1409 }
1410 
1411 
1413 {
1414  BOARD_STACKUP_ITEM* st_item = dynamic_cast<BOARD_STACKUP_ITEM*>( GetStackupItem( aRow ) );
1415 
1416  wxASSERT( st_item );
1417  wxColor color;
1418 
1419  if( ! st_item )
1420  return color;
1421 
1422  switch( st_item->GetType() )
1423  {
1424  case BS_ITEM_TYPE_COPPER: color = copperColor; break;
1426  case BS_ITEM_TYPE_SOLDERMASK: color = GetSelectedColor( aRow ); break;
1427  case BS_ITEM_TYPE_SILKSCREEN: color = GetSelectedColor( aRow ); break;
1428  case BS_ITEM_TYPE_SOLDERPASTE: color = pasteColor; break;
1429 
1430  default:
1432  wxFAIL_MSG( wxT( "PANEL_SETUP_BOARD_STACKUP::getColorIconItem: unrecognized item type" ) );
1433  break;
1434  }
1435 
1436  wxASSERT_MSG( color.IsOk(), wxT( "Invalid color in PCB stackup" ) );
1437 
1438  return color;
1439 }
1440 
1441 
1443 {
1444  // explicit depth important under MSW. We use R,V,B 24 bits/pixel bitmap
1445  const int bitmap_depth = 24;
1446 
1447  if( aRow >= 0 )
1448  {
1449  wxStaticBitmap* st_bitmap = m_rowUiItemsList[aRow].m_Icon;
1450 
1451  wxBitmap bmp( m_colorIconsSize.x, m_colorIconsSize.y / 2, bitmap_depth );
1452  drawBitmap( bmp, getColorIconItem( aRow ) );
1453  st_bitmap->SetBitmap( bmp );
1454  return;
1455  }
1456 
1457  for( unsigned row = 0; row < m_rowUiItemsList.size(); row++ )
1458  {
1459  wxBitmap bmp( m_colorIconsSize.x, m_colorIconsSize.y / 2, bitmap_depth );
1460  drawBitmap( bmp, getColorIconItem( row ) );
1461  m_rowUiItemsList[row].m_Icon->SetBitmap( bmp );
1462  }
1463 }
1464 
1465 
1467  int aRow )
1468 {
1469  wxBitmapComboBox* combo = new wxBitmapComboBox( m_scGridWin, ID_ITEM_COLOR + aRow,
1470  wxEmptyString, wxDefaultPosition,
1471  wxDefaultSize, 0, nullptr, wxCB_READONLY );
1472 
1473  // Fills the combo box with choice list + bitmaps
1474  const FAB_LAYER_COLOR* color_list = GetColorStandardList();
1475  BOARD_STACKUP_ITEM_TYPE itemType = aStackupItem ? aStackupItem->GetType()
1477 
1478  for( int ii = 0; ii < GetColorStandardListCount(); ii++ )
1479  {
1480  wxColor curr_color;
1481  wxString label;
1482 
1483  // Defined colors have a name, the user color uses HTML notation ( i.e. #FF000080)
1484  if( ii == GetColorUserDefinedListIdx()
1485  && aStackupItem && aStackupItem->GetColor().StartsWith( wxT( "#" ) ) )
1486  {
1487  curr_color = wxColour( COLOR4D( aStackupItem->GetColor() ).ToColour() );
1488 
1489  // NB: wxWidgets 3.0's color.GetAsString( wxC2S_HTML_SYNTAX ) pukes on alpha
1490  label = getColourAsHexString( curr_color );
1491  }
1492  else
1493  {
1494  curr_color = color_list[ii].GetColor( itemType );
1495  label = _( color_list[ii].GetName() );
1496  }
1497 
1498  wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
1499  LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ), COLOR4D( curr_color ) );
1500 
1501  combo->Append( label, layerbmp );
1502  }
1503 
1504  // Ensure the size of the widget is enough to show the text and the icon
1505  // We have to have a selected item when doing this, because otherwise GTK
1506  // will just choose a random size that might not fit the actual data
1507  // (such as in cases where the font size is very large). So we select
1508  // the longest item (which should be the last item), and size it that way.
1509  int sel = combo->GetSelection();
1510  combo->SetSelection( combo->GetCount() - 1 );
1511 
1512  combo->SetMinSize( wxSize( -1, -1 ) );
1513  wxSize bestSize = combo->GetBestSize();
1514 
1515  bestSize.x = bestSize.x + m_colorSwatchesSize.x;
1516  combo->SetMinSize( bestSize );
1517  combo->SetSelection( sel );
1518 
1519  // add the wxBitmapComboBox to wxControl list, to be able to disconnect the event
1520  // on exit
1521  m_controlItemsList.push_back( combo );
1522 
1523  combo->Connect( wxEVT_COMMAND_COMBOBOX_SELECTED,
1524  wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onColorSelected ),
1525  nullptr, this );
1526 
1527  combo->Bind( wxEVT_COMBOBOX_DROPDOWN,
1528  [combo]( wxCommandEvent& aEvent )
1529  {
1530  combo->SetString( combo->GetCount() - 1, _( "Custom..." ) );
1531  } );
1532 
1533  return combo;
1534 }
1535 
1536 
1537 void drawBitmap( wxBitmap& aBitmap, wxColor aColor )
1538 {
1539  wxNativePixelData data( aBitmap );
1540  wxNativePixelData::Iterator p( data );
1541 
1542  for( int yy = 0; yy < data.GetHeight(); yy++ )
1543  {
1544  wxNativePixelData::Iterator rowStart = p;
1545 
1546  for( int xx = 0; xx < data.GetWidth(); xx++ )
1547  {
1548  p.Red() = aColor.Red();
1549  p.Green() = aColor.Green();
1550  p.Blue() = aColor.Blue();
1551  ++p;
1552  }
1553 
1554  p = rowStart;
1555  p.OffsetY( data, 1 );
1556  }
1557 }
1558 
1559 
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:362
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
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:467
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()
int computeBoardThickness()
Recompute the board thickness and update the textbox.
void onRemoveDielUI(wxUpdateUIEvent &event) override
void SetBoardThickness(int aThickness)
void onCopperLayersSelCount(wxCommandEvent &event) override
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:590
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:516
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:719
static LSET ExternalCuMask()
Return a mask holding the Front and Bottom layers.
Definition: lset.cpp:789
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:191
Definition: layer_ids.h:71
int GetCopperLayerCount() const
Definition: board.cpp:455
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