/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2019-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include // arrayDim definition #include #include #include #include #include #include #include #include #include #include #include #include #include // for KiROUND #include "panel_board_stackup.h" #include #include "board_stackup_reporter.h" #include #include "dialog_dielectric_list_manager.h" #include #include #include #include // for UIDouble2Str() // Some wx widget ID to know what widget has fired a event: #define ID_INCREMENT 256 // space between 2 ID type. Bigger than the layer count max // The actual widget IDs are the base id + the row index. // they are used in events to know the row index of the control that fired the event enum WIDGETS_IDS { ID_ITEM_MATERIAL = 10000, // Be sure it is higher than other IDs // used in the board setup dialog ID_ITEM_THICKNESS = ID_ITEM_MATERIAL + ID_INCREMENT, ID_ITEM_THICKNESS_LOCKED = ID_ITEM_THICKNESS + ID_INCREMENT, ID_ITEM_COLOR = ID_ITEM_THICKNESS_LOCKED + ID_INCREMENT, }; // Default colors to draw icons: static wxColor copperColor( 220, 180, 30 ); static wxColor dielectricColor( 75, 120, 75 ); static wxColor pasteColor( 200, 200, 200 ); static void drawBitmap( wxBitmap& aBitmap, wxColor aColor ); PANEL_SETUP_BOARD_STACKUP::PANEL_SETUP_BOARD_STACKUP( wxWindow* aParentWindow, PCB_EDIT_FRAME* aFrame, PANEL_SETUP_LAYERS* aPanelLayers ): PANEL_SETUP_BOARD_STACKUP_BASE( aParentWindow ), m_delectricMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_DIELECTRIC ), m_solderMaskMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_SOLDERMASK ), m_silkscreenMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_SILKSCREEN ) { m_frame = aFrame; m_panelLayers = aPanelLayers; m_board = m_frame->GetBoard(); m_brdSettings = &m_board->GetDesignSettings(); m_panel1->SetBorders( false, false, true, true ); m_panelLayers->SetPhysicalStackupPanel( this ); m_enabledLayers = m_board->GetEnabledLayers() & BOARD_STACKUP::StackupAllowedBrdLayers(); // Calculates a good size for color swatches (icons) in this dialog m_colorSwatchesSize = GetTextExtent( wxT( "XX" ) ); m_colorIconsSize = GetTextExtent( wxT( "XXXX" ) ); // Calculates a good size for wxTextCtrl to enter Epsilon R and Loss tan // ("0.0000000" + margins) m_numericFieldsSize = GetTextExtent( wxT( "X.XXXXXXX" ) ); m_numericFieldsSize.y = -1; // Use default for the vertical size // Calculates a minimal size for wxTextCtrl to enter a dim with units // ("000.0000000 mils" + margins) m_numericTextCtrlSize = GetTextExtent( wxT( "XXX.XXXXXXX mils" ) ); m_numericTextCtrlSize.y = -1; // Use default for the vertical size // The grid column containing the lock checkbox is kept to a minimal // size. So we use a wxStaticBitmap: set the bitmap itself m_bitmapLockThickness->SetBitmap( KiScaledBitmap( BITMAPS::locked, aFrame ) ); // Gives a minimal size of wxTextCtrl showing dimensions+units m_tcCTValue->SetMinSize( m_numericTextCtrlSize ); // Prepare dielectric layer type: layer type keyword is "core" or "prepreg" m_core_prepreg_choice.Add( _( "Core" ) ); m_core_prepreg_choice.Add( _( "PrePreg" ) ); buildLayerStackPanel( true ); synchronizeWithBoard( true ); computeBoardThickness(); } PANEL_SETUP_BOARD_STACKUP::~PANEL_SETUP_BOARD_STACKUP() { disconnectEvents(); } void PANEL_SETUP_BOARD_STACKUP::onCopperLayersSelCount( wxCommandEvent& event ) { int oldBoardWidth = static_cast( m_frame->ValueFromString( m_tcCTValue->GetValue() ) ); updateCopperLayerCount(); showOnlyActiveLayers(); setDefaultLayerWidths( oldBoardWidth ); computeBoardThickness(); Layout(); } void PANEL_SETUP_BOARD_STACKUP::onAdjustDielectricThickness( wxCommandEvent& event ) { // The list of items that can be modified: std::vector< BOARD_STACKUP_ROW_UI_ITEM* > items_candidate; // Some dielectric layers can have a locked thickness, so calculate the min // acceptable thickness int min_thickness = 0; for( BOARD_STACKUP_ROW_UI_ITEM& ui_item : m_rowUiItemsList ) { BOARD_STACKUP_ITEM* item = ui_item.m_Item; if( !item->IsThicknessEditable() || !ui_item.m_isEnabled ) continue; // We are looking for locked thickness items only: wxCheckBox* cb_box = dynamic_cast ( ui_item.m_ThicknessLockCtrl ); if( cb_box && !cb_box->GetValue() ) { items_candidate.push_back( &ui_item ); continue; } wxTextCtrl* textCtrl = static_cast( ui_item.m_ThicknessCtrl ); int item_thickness = m_frame->ValueFromString( textCtrl->GetValue() ); min_thickness += item_thickness; } wxString title; if( min_thickness == 0 ) { title.Printf( _( "Enter board thickness in %s:" ), EDA_UNIT_UTILS::GetText( m_frame->GetUserUnits() ).Trim( false ) ); } else { title.Printf( _( "Enter expected board thickness (min value %s):" ), m_frame->StringFromValue( min_thickness, true ) ); } wxTextEntryDialog dlg( this, title, _( "Adjust Unlocked Dielectric Layers" ) ); if( dlg.ShowModal() != wxID_OK ) return; int iu_thickness = m_frame->ValueFromString( dlg.GetValue() ); if( iu_thickness < min_thickness ) { wxMessageBox( wxString::Format( _("Value too small (min value %s)." ), m_frame->StringFromValue( min_thickness, true ) ) ); return; } // Now adjust not locked dielectric thickness layers: if( items_candidate.size() ) setDefaultLayerWidths( iu_thickness ); else wxMessageBox( _( "All dielectric thickness layers are locked" ) ); computeBoardThickness(); } void PANEL_SETUP_BOARD_STACKUP::disconnectEvents() { // Disconnect Events connected to items in m_controlItemsList for( wxControl* item: m_controlItemsList ) { wxBitmapComboBox* cb = dynamic_cast( item ); if( cb ) { cb->Disconnect( wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onColorSelected ), nullptr, this ); } wxButton* matButt = dynamic_cast( item ); if( matButt ) { matButt->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onMaterialChange ), nullptr, this ); } wxTextCtrl* textCtrl = dynamic_cast( item ); if( textCtrl ) { textCtrl->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onThicknessChange ), nullptr, this ); } } } void PANEL_SETUP_BOARD_STACKUP::onAddDielectricLayer( wxCommandEvent& event ) { wxArrayString headers; headers.Add( _( "Layers" ) ); // Build Dielectric layers list: std::vector d_list; std::vector rows; // indexes of row values for each selectable item int row = -1; for( BOARD_STACKUP_ROW_UI_ITEM& item : m_rowUiItemsList ) { row++; if( !item.m_isEnabled ) continue; BOARD_STACKUP_ITEM* brd_stackup_item = item.m_Item; if( brd_stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) { wxArrayString d_item; if( brd_stackup_item->GetSublayersCount() > 1 ) { d_item.Add( wxString::Format( _( "Layer '%s' (sublayer %d/%d)" ), brd_stackup_item->FormatDielectricLayerName(), item.m_SubItem+1, brd_stackup_item->GetSublayersCount() ) ); } else { d_item.Add( brd_stackup_item->FormatDielectricLayerName() ); } d_list.emplace_back( d_item ); rows.push_back( row ); } } EDA_LIST_DIALOG dlg( PAGED_DIALOG::GetDialog( this ), _( "Add Dielectric Layer" ), headers, d_list, wxEmptyString, false /* do not sort the list: it is **expected** in stackup order */ ); dlg.SetListLabel( _( "Select layer to add:" ) ); dlg.HideFilter(); if( dlg.ShowModal() == wxID_OK && dlg.GetSelection() >= 0 ) { row = rows[ dlg.GetSelection() ]; BOARD_STACKUP_ITEM* brd_stackup_item = m_rowUiItemsList[row].m_Item; int new_sublayer = m_rowUiItemsList[row].m_SubItem; // Insert a new item after the selected item brd_stackup_item->AddDielectricPrms( new_sublayer+1 ); rebuildLayerStackPanel(); computeBoardThickness(); } } void PANEL_SETUP_BOARD_STACKUP::onRemoveDielectricLayer( wxCommandEvent& event ) { wxArrayString headers; headers.Add( _( "Layers" ) ); // Build deletable Dielectric layers list. // A layer can be deleted if there are 2 (or more) dielectric sub-layers // between 2 copper layers std::vector d_list; std::vector rows; // indexes of row values for each selectable item int row = 0; // row index in m_rowUiItemsList of items in choice list // Build the list of dielectric layers: for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() ) { if( !item->IsEnabled() || item->GetType() != BS_ITEM_TYPE_DIELECTRIC || item->GetSublayersCount() <= 1 ) { row++; continue; } for( int ii = 0; ii < item->GetSublayersCount(); ii++ ) { wxArrayString d_item; d_item.Add( wxString::Format( _( "Layer '%s' sublayer %d/%d" ), item->FormatDielectricLayerName(), ii+1, item->GetSublayersCount() ) ); d_list.emplace_back( d_item ); rows.push_back( row++ ); } } EDA_LIST_DIALOG dlg( PAGED_DIALOG::GetDialog( this ), _( "Remove Dielectric Layer" ), headers, d_list, wxEmptyString, false /* do not sort the list: it is **expected** in stackup order */ ); dlg.SetListLabel( _( "Select layer to remove:" ) ); dlg.HideFilter(); if( dlg.ShowModal() == wxID_OK && dlg.GetSelection() >= 0 ) { row = rows[ dlg.GetSelection() ]; BOARD_STACKUP_ITEM* brd_stackup_item = m_rowUiItemsList[ row ].m_Item; int sublayer = m_rowUiItemsList[ row ].m_SubItem; // Remove the selected sub item for the selected dielectric layer brd_stackup_item->RemoveDielectricPrms( sublayer ); rebuildLayerStackPanel(); computeBoardThickness(); } } void PANEL_SETUP_BOARD_STACKUP::onRemoveDielUI( wxUpdateUIEvent& event ) { // The m_buttonRemoveDielectricLayer wxButton is enabled only if a dielectric // layer can be removed, i.e. if dielectric layers have sublayers for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() ) { if( !item->IsEnabled() || item->GetType() != BS_ITEM_TYPE_DIELECTRIC ) continue; if( item->GetSublayersCount() > 1 ) { event.Enable( true ); return; } } event.Enable( false ); } void PANEL_SETUP_BOARD_STACKUP::onExportToClipboard( wxCommandEvent& event ) { if( !transferDataFromUIToStackup() ) return; // Build a ASCII representation of stackup and copy it in the clipboard wxString report = BuildStackupReport( m_stackup, m_frame->GetUserUnits() ); wxLogNull doNotLog; // disable logging of failed clipboard actions if( wxTheClipboard->Open() ) { // This data objects are held by the clipboard, // so do not delete them in the app. wxTheClipboard->SetData( new wxTextDataObject( report ) ); wxTheClipboard->Flush(); // Allow data to be available after closing KiCad wxTheClipboard->Close(); } } wxColor PANEL_SETUP_BOARD_STACKUP::GetSelectedColor( int aRow ) const { const BOARD_STACKUP_ROW_UI_ITEM& row = m_rowUiItemsList[aRow]; const BOARD_STACKUP_ITEM* item = row.m_Item; const wxBitmapComboBox* choice = dynamic_cast( row.m_ColorCtrl ); int idx = choice ? choice->GetSelection() : 0; if( IsCustomColorIdx( item->GetType(), idx ) ) return m_rowUiItemsList[aRow].m_UserColor.ToColour(); else return GetStandardColor( item->GetType(), idx ).ToColour(); } void PANEL_SETUP_BOARD_STACKUP::setDefaultLayerWidths( int targetThickness ) { // This function tries to set the PCB thickness to the parameter and uses a fixed prepreg thickness // of 0.1 mm. The core thickness is calculated accordingly as long as it also stays above 0.1mm. // If the core thickness would be smaller than the default pregreg thickness given here, // both are reduced towards zero to arrive at the correct PCB width const int prePregDefaultThickness = pcbIUScale.mmToIU( 0.1 ); int copperLayerCount = GetCopperLayerCount(); // This code is for a symmetrical PCB stackup with even copper layer count // If asymmetric stackups were to be implemented, the following layer count calculations // for dielectric/core layers might need adjustments. wxASSERT( copperLayerCount % 2 == 0 ); int dielectricLayerCount = copperLayerCount - 1; int coreLayerCount = copperLayerCount / 2 - 1; wxASSERT( dielectricLayerCount > 0 ); bool currentLayerIsCore = false; // start with prepreg layer on the outside, except when creating two-layer-board if( copperLayerCount == 2 ) { coreLayerCount = 1; currentLayerIsCore = true; } wxASSERT( coreLayerCount > 0 ); int prePregLayerCount = dielectricLayerCount - coreLayerCount; int totalWidthOfFixedItems = 0; for( BOARD_STACKUP_ROW_UI_ITEM& ui_item : m_rowUiItemsList ) { BOARD_STACKUP_ITEM* item = ui_item.m_Item; if( !item->IsThicknessEditable() || !ui_item.m_isEnabled ) continue; wxCheckBox* cbLock = dynamic_cast( ui_item.m_ThicknessLockCtrl ); wxChoice* layerType = dynamic_cast( ui_item.m_LayerTypeCtrl ); if( ( item->GetType() == BS_ITEM_TYPE_DIELECTRIC && !layerType ) || item->GetType() == BS_ITEM_TYPE_SOLDERMASK || item->GetType() == BS_ITEM_TYPE_COPPER || ( cbLock && cbLock->GetValue() ) ) { // secondary dielectric layers, mask and copper layers and locked layers will be // counted as fixed width wxTextCtrl* textCtrl = static_cast( ui_item.m_ThicknessCtrl ); int item_thickness = m_frame->ValueFromString( textCtrl->GetValue() ); totalWidthOfFixedItems += item_thickness; } } // Width that hasn't been allocated by fixed items int remainingWidth = targetThickness - totalWidthOfFixedItems - ( prePregDefaultThickness * prePregLayerCount ); int prePregThickness = prePregDefaultThickness; int coreThickness = remainingWidth / coreLayerCount; if( coreThickness < prePregThickness ) { // There's not enough room for prepreg and core layers of at least 0.1 mm, so adjust both down remainingWidth = targetThickness - totalWidthOfFixedItems; prePregThickness = coreThickness = std::max( 0, remainingWidth / dielectricLayerCount ); } for( BOARD_STACKUP_ROW_UI_ITEM& ui_item : m_rowUiItemsList ) { BOARD_STACKUP_ITEM* item = ui_item.m_Item; if( item->GetType() != BS_ITEM_TYPE_DIELECTRIC || !ui_item.m_isEnabled ) continue; wxChoice* layerType = dynamic_cast( ui_item.m_LayerTypeCtrl ); if( !layerType ) { // ignore secondary dielectric layers continue; } wxCheckBox* cbLock = dynamic_cast( ui_item.m_ThicknessLockCtrl ); if( cbLock && cbLock->GetValue() ) { currentLayerIsCore = !currentLayerIsCore; // Don't override width of locked layer continue; } int layerThickness = currentLayerIsCore ? coreThickness : prePregThickness; wxTextCtrl* textCtrl = static_cast( ui_item.m_ThicknessCtrl ); layerType->SetSelection( currentLayerIsCore ? 0 : 1 ); textCtrl->SetValue( m_frame->StringFromValue( layerThickness ) ); currentLayerIsCore = !currentLayerIsCore; } } int PANEL_SETUP_BOARD_STACKUP::computeBoardThickness() { int thickness = 0; for( BOARD_STACKUP_ROW_UI_ITEM& ui_item : m_rowUiItemsList ) { BOARD_STACKUP_ITEM* item = ui_item.m_Item; if( !item->IsThicknessEditable() || !ui_item.m_isEnabled ) continue; wxTextCtrl* textCtrl = static_cast( ui_item.m_ThicknessCtrl ); int item_thickness = m_frame->ValueFromString( textCtrl->GetValue() ); thickness += item_thickness; } wxString thicknessStr = m_frame->StringFromValue( thickness, true ); // The text in the event will translate to the value for the text control // and is only updated if it changed m_tcCTValue->ChangeValue( thicknessStr ); return thickness; } int PANEL_SETUP_BOARD_STACKUP::GetCopperLayerCount() const { return ( m_choiceCopperLayers->GetSelection() + 1 ) * 2; } void PANEL_SETUP_BOARD_STACKUP::updateCopperLayerCount() { int copperCount = GetCopperLayerCount(); wxASSERT( copperCount >= 2 ); m_enabledLayers |= LSET::ExternalCuMask(); m_enabledLayers &= ~LSET::InternalCuMask(); for( int i = 1; i < copperCount - 1; i++ ) m_enabledLayers.set( F_Cu + i ); } void PANEL_SETUP_BOARD_STACKUP::synchronizeWithBoard( bool aFullSync ) { const BOARD_STACKUP& brd_stackup = m_brdSettings->GetStackupDescriptor(); if( aFullSync ) { m_choiceCopperLayers->SetSelection( ( m_board->GetCopperLayerCount() / 2 ) - 1 ); m_impedanceControlled->SetValue( brd_stackup.m_HasDielectricConstrains ); } for( BOARD_STACKUP_ROW_UI_ITEM& ui_row_item : m_rowUiItemsList ) { BOARD_STACKUP_ITEM* item = ui_row_item.m_Item; int sub_item = ui_row_item.m_SubItem; if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) { wxChoice* choice = dynamic_cast( ui_row_item.m_LayerTypeCtrl ); if( choice ) choice->SetSelection( item->GetTypeName() == KEY_CORE ? 0 : 1 ); } if( item->IsMaterialEditable() ) { wxTextCtrl* matName = dynamic_cast( ui_row_item.m_MaterialCtrl ); if( matName ) { if( IsPrmSpecified( item->GetMaterial( sub_item ) ) ) matName->ChangeValue( item->GetMaterial( sub_item ) ); else matName->ChangeValue( wxGetTranslation( NotSpecifiedPrm() ) ); } } if( item->IsThicknessEditable() ) { wxTextCtrl* textCtrl = dynamic_cast( ui_row_item.m_ThicknessCtrl ); if( textCtrl ) textCtrl->ChangeValue( m_frame->StringFromValue( item->GetThickness( sub_item ), true ) ); if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) { wxCheckBox* cb_box = dynamic_cast ( ui_row_item.m_ThicknessLockCtrl ); if( cb_box ) cb_box->SetValue( item->IsThicknessLocked( sub_item ) ); } } if( item->IsColorEditable() ) { auto bm_combo = dynamic_cast( ui_row_item.m_ColorCtrl ); int selected = 0; // The "not specified" item if( item->GetColor( sub_item ).StartsWith( wxT( "#" ) ) ) // User defined color { COLOR4D custom_color( item->GetColor( sub_item ) ); ui_row_item.m_UserColor = custom_color; selected = GetColorUserDefinedListIdx( item->GetType() ); if( bm_combo ) // Update user color shown in the wxBitmapComboBox { bm_combo->SetString( selected, item->GetColor( sub_item ) ); wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y ); LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D(), custom_color ); bm_combo->SetItemBitmap( selected, layerbmp ); } } else { if( bm_combo ) { // Note: don't use bm_combo->FindString() because the combo strings are // translated. for( size_t ii = 0; ii < GetStandardColors( item->GetType() ).size(); ii++ ) { if( GetStandardColorName( item->GetType(), ii ) == item->GetColor( sub_item ) ) { selected = ii; break; } } } } if( bm_combo ) bm_combo->SetSelection( selected ); } if( item->HasEpsilonRValue() ) { wxString txt = UIDouble2Str( item->GetEpsilonR( sub_item ) ); wxTextCtrl* textCtrl = dynamic_cast( ui_row_item.m_EpsilonCtrl ); if( textCtrl ) textCtrl->ChangeValue( txt ); } if( item->HasLossTangentValue() ) { wxString txt = UIDouble2Str( item->GetLossTangent( sub_item ) ); wxTextCtrl* textCtrl = dynamic_cast( ui_row_item.m_LossTgCtrl ); if( textCtrl ) textCtrl->ChangeValue( txt ); } } // Now enable/disable stackup items, according to the m_enabledLayers config showOnlyActiveLayers(); updateIconColor(); } void PANEL_SETUP_BOARD_STACKUP::showOnlyActiveLayers() { // Now enable/disable stackup items, according to the m_enabledLayers config // Calculate copper layer count from m_enabledLayers, and *do not use* brd_stackup // for that, because it is not necessary up to date // (for instance after modifying the layer count from the panel layers in dialog) LSET copperMask = m_enabledLayers & ( LSET::ExternalCuMask() | LSET::InternalCuMask() ); int copperLayersCount = copperMask.count(); for( BOARD_STACKUP_ROW_UI_ITEM& ui_row_item: m_rowUiItemsList ) { bool show_item; BOARD_STACKUP_ITEM* item = ui_row_item.m_Item; if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) // the m_DielectricLayerId is not a copper layer id, it is a dielectric idx from 1 show_item = item->GetDielectricLayerId() < copperLayersCount; else show_item = m_enabledLayers[item->GetBrdLayerId()]; item->SetEnabled( show_item ); ui_row_item.m_isEnabled = show_item; if( show_item && !ui_row_item.m_Icon ) lazyBuildRowUI( ui_row_item ); if( ui_row_item.m_Icon ) { // Show or not items of this row: ui_row_item.m_Icon->Show( show_item ); ui_row_item.m_LayerName->Show( show_item ); ui_row_item.m_LayerTypeCtrl->Show( show_item ); ui_row_item.m_MaterialCtrl->Show( show_item ); if( ui_row_item.m_MaterialButt ) ui_row_item.m_MaterialButt->Show( show_item ); ui_row_item.m_ThicknessCtrl->Show( show_item ); ui_row_item.m_ThicknessLockCtrl->Show( show_item ); ui_row_item.m_ColorCtrl->Show( show_item ); ui_row_item.m_EpsilonCtrl->Show( show_item ); ui_row_item.m_LossTgCtrl->Show( show_item ); } } } void PANEL_SETUP_BOARD_STACKUP::addMaterialChooser( wxWindowID aId, const wxString* aMaterialName, BOARD_STACKUP_ROW_UI_ITEM& aUiRowItem ) { wxBoxSizer* bSizerMat = new wxBoxSizer( wxHORIZONTAL ); m_fgGridSizer->Add( bSizerMat, 1, wxRIGHT|wxEXPAND, 4 ); wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY ); if( aMaterialName ) { if( IsPrmSpecified( *aMaterialName ) ) textCtrl->ChangeValue( *aMaterialName ); else textCtrl->ChangeValue( wxGetTranslation( NotSpecifiedPrm() ) ); } textCtrl->SetMinSize( m_numericTextCtrlSize ); bSizerMat->Add( textCtrl, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); wxButton* m_buttonMat = new wxButton( m_scGridWin, aId, _( "..." ), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT ); bSizerMat->Add( m_buttonMat, 0, wxALIGN_CENTER_VERTICAL, 2 ); m_buttonMat->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onMaterialChange ), nullptr, this ); m_controlItemsList.push_back( m_buttonMat ); aUiRowItem.m_MaterialCtrl = textCtrl; aUiRowItem.m_MaterialButt = m_buttonMat; } wxControl* PANEL_SETUP_BOARD_STACKUP::addSpacer() { wxStaticText* emptyText = new wxStaticText( m_scGridWin, wxID_ANY, wxEmptyString ); m_fgGridSizer->Add( emptyText, 0, wxALIGN_CENTER_VERTICAL ); return emptyText; } void PANEL_SETUP_BOARD_STACKUP::lazyBuildRowUI( BOARD_STACKUP_ROW_UI_ITEM& ui_row_item ) { BOARD_STACKUP_ITEM* item = ui_row_item.m_Item; int sublayerIdx = ui_row_item.m_SubItem; int row = ui_row_item.m_Row; // Add color swatch icon. The color will be updated later, // when all widgets are initialized wxStaticBitmap* bitmap = new wxStaticBitmap( m_scGridWin, wxID_ANY, wxNullBitmap ); m_fgGridSizer->Add( bitmap, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 4 ); ui_row_item.m_Icon = bitmap; if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) { wxString lname = item->FormatDielectricLayerName(); if( item->GetSublayersCount() > 1 ) { lname << wxT( " (" ) << sublayerIdx +1 << wxT( "/" ) << item->GetSublayersCount() << wxT( ")" ); } wxStaticText* st_text = new wxStaticText( m_scGridWin, wxID_ANY, lname ); m_fgGridSizer->Add( st_text, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 ); ui_row_item.m_LayerName = st_text; // For a dielectric layer, the layer type choice is not for each sublayer, // only for the first (sublayerIdx = 0), and is common to all sublayers if( sublayerIdx == 0 ) { wxChoice* choice = new wxChoice( m_scGridWin, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_core_prepreg_choice ); choice->SetSelection( item->GetTypeName() == KEY_CORE ? 0 : 1 ); m_fgGridSizer->Add( choice, 1, wxEXPAND|wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 ); ui_row_item.m_LayerTypeCtrl = choice; } else { ui_row_item.m_LayerTypeCtrl = addSpacer(); } } else { item->SetLayerName( m_board->GetLayerName( item->GetBrdLayerId() ) ); wxStaticText* st_text = new wxStaticText( m_scGridWin, wxID_ANY, item->GetLayerName() ); m_fgGridSizer->Add( st_text, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 1 ); st_text->Show( true ); ui_row_item.m_LayerName = st_text; wxString lname; if( item->GetTypeName() == KEY_COPPER ) lname = _( "Copper" ); else lname = wxGetTranslation( item->GetTypeName() ); st_text = new wxStaticText( m_scGridWin, wxID_ANY, lname ); m_fgGridSizer->Add( st_text, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 ); ui_row_item.m_LayerTypeCtrl = st_text; } if( item->IsMaterialEditable() ) { wxString matName = item->GetMaterial( sublayerIdx ); addMaterialChooser( ID_ITEM_MATERIAL+row, &matName, ui_row_item ); } else { ui_row_item.m_MaterialCtrl = addSpacer(); } if( item->IsThicknessEditable() ) { wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, ID_ITEM_THICKNESS+row ); textCtrl->SetMinSize( m_numericTextCtrlSize ); textCtrl->ChangeValue( m_frame->StringFromValue( item->GetThickness( sublayerIdx ), true ) ); m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 ); m_controlItemsList.push_back( textCtrl ); textCtrl->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onThicknessChange ), nullptr, this ); ui_row_item.m_ThicknessCtrl = textCtrl; if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) { wxCheckBox* cb_box = new wxCheckBox( m_scGridWin, ID_ITEM_THICKNESS_LOCKED+row, wxEmptyString ); cb_box->SetValue( item->IsThicknessLocked( sublayerIdx ) ); m_fgGridSizer->Add( cb_box, 0, wxALIGN_CENTER_VERTICAL, 2 ); ui_row_item.m_ThicknessLockCtrl = cb_box; } else { ui_row_item.m_ThicknessLockCtrl = addSpacer(); } } else { ui_row_item.m_ThicknessCtrl = addSpacer(); ui_row_item.m_ThicknessLockCtrl = addSpacer(); } if( item->IsColorEditable() ) { if( item->GetColor( sublayerIdx ).StartsWith( wxT( "#" ) ) ) // User defined color { ui_row_item.m_UserColor = COLOR4D( item->GetColor( sublayerIdx ) ).ToColour(); } else ui_row_item.m_UserColor = GetDefaultUserColor( item->GetType() ); wxBitmapComboBox* bm_combo = createColorBox( item, row ); int selected = 0; // The "not specified" item m_fgGridSizer->Add( bm_combo, 1, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL|wxEXPAND, 2 ); if( item->GetColor( sublayerIdx ).StartsWith( wxT( "#" ) ) ) { selected = GetColorUserDefinedListIdx( item->GetType() ); bm_combo->SetString( selected, item->GetColor( sublayerIdx ) ); } else { // Note: don't use bm_combo->FindString() because the combo strings are translated. for( size_t ii = 0; ii < GetStandardColors( item->GetType() ).size(); ii++ ) { if( GetStandardColorName( item->GetType(), ii ) == item->GetColor( sublayerIdx ) ) { selected = ii; break; } } } bm_combo->SetSelection( selected ); ui_row_item.m_ColorCtrl = bm_combo; } else { ui_row_item.m_ColorCtrl = addSpacer(); } if( item->HasEpsilonRValue() ) { wxString txt = UIDouble2Str( item->GetEpsilonR( sublayerIdx ) ); wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString, wxDefaultPosition, m_numericFieldsSize ); textCtrl->ChangeValue( txt ); m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 ); ui_row_item.m_EpsilonCtrl = textCtrl; } else { ui_row_item.m_EpsilonCtrl = addSpacer(); } if( item->HasLossTangentValue() ) { wxString txt = UIDouble2Str( item->GetLossTangent( sublayerIdx ) );; wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString, wxDefaultPosition, m_numericFieldsSize ); textCtrl->ChangeValue( txt ); m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 ); ui_row_item.m_LossTgCtrl = textCtrl; } else { ui_row_item.m_LossTgCtrl = addSpacer(); } } void PANEL_SETUP_BOARD_STACKUP::rebuildLayerStackPanel( bool aRelinkItems ) { wxWindowUpdateLocker locker( m_scGridWin ); m_scGridWin->Hide(); // Rebuild the stackup for the dialog, after dielectric parameters list is modified // (added/removed): // First, delete all ui objects, because wxID values will be no longer valid for many widgets disconnectEvents(); m_controlItemsList.clear(); // Delete widgets (handled by the wxPanel parent) for( BOARD_STACKUP_ROW_UI_ITEM& ui_item: m_rowUiItemsList ) { // This remove and delete the current ui_item.m_MaterialCtrl sizer if( ui_item.m_MaterialCtrl ) ui_item.m_MaterialCtrl->SetSizer( nullptr ); // Delete other widgets delete ui_item.m_Icon; // Color icon in first column (column 1) delete ui_item.m_LayerName; // string shown in column 2 delete ui_item.m_LayerTypeCtrl; // control shown in column 3 delete ui_item.m_MaterialCtrl; // control shown in column 4, with m_MaterialButt delete ui_item.m_MaterialButt; // control shown in column 4, with m_MaterialCtrl delete ui_item.m_ThicknessCtrl; // control shown in column 5 delete ui_item.m_ThicknessLockCtrl;// control shown in column 6 delete ui_item.m_ColorCtrl; // control shown in column 7 delete ui_item.m_EpsilonCtrl; // control shown in column 8 delete ui_item.m_LossTgCtrl; // control shown in column 9 } m_rowUiItemsList.clear(); // In order to recreate a clean grid layer list, we have to delete and // recreate the sizer m_fgGridSizer (just deleting items in this size is not enough) // therefore we also have to add the "old" title items to the newly recreated m_fgGridSizer: m_scGridWin->SetSizer( nullptr ); // This remove and delete the current m_fgGridSizer m_fgGridSizer = new wxFlexGridSizer( 0, 9, 0, 2 ); m_fgGridSizer->SetFlexibleDirection( wxHORIZONTAL ); m_fgGridSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); m_fgGridSizer->SetHGap( 6 ); m_scGridWin->SetSizer( m_fgGridSizer ); // Re-add "old" title items: const int sizer_flags = wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_CENTER_HORIZONTAL; m_fgGridSizer->Add( m_staticTextLayer, 0, sizer_flags, 2 ); m_fgGridSizer->Add( m_staticTextType, 0, sizer_flags, 2 ); m_fgGridSizer->Add( m_staticTextLayerId, 0, sizer_flags, 5 ); m_fgGridSizer->Add( m_staticTextMaterial, 0, sizer_flags, 2 ); m_fgGridSizer->Add( m_staticTextThickness, 0, sizer_flags, 2 ); m_fgGridSizer->Add( m_bitmapLockThickness, 0, sizer_flags, 1 ); m_fgGridSizer->Add( m_staticTextColor, 0, sizer_flags, 2 ); m_fgGridSizer->Add( m_staticTextEpsilonR, 0, sizer_flags, 2 ); m_fgGridSizer->Add( m_staticTextLossTg, 0, sizer_flags, 2 ); // Now, rebuild the widget list from the new m_stackup items: buildLayerStackPanel( false, aRelinkItems ); // Now enable/disable stackup items, according to the m_enabledLayers config showOnlyActiveLayers(); updateIconColor(); m_scGridWin->Layout(); m_scGridWin->Show(); } void PANEL_SETUP_BOARD_STACKUP::buildLayerStackPanel( bool aCreateInitialStackup, bool aRelinkStackup ) { // Build a full stackup for the dialog, with a active copper layer count // = current board layer count to calculate a reasonable default stackup: if( aCreateInitialStackup || aRelinkStackup ) { if( aCreateInitialStackup ) { // Creates a full BOARD_STACKUP with 32 copper layers. // extra layers will be hidden later. // but if the number of layer is changed in the dialog, the corresponding // widgets will be available with their previous values. m_stackup.BuildDefaultStackupList( nullptr, m_brdSettings->GetCopperLayerCount() ); } const BOARD_STACKUP& brd_stackup = m_brdSettings->GetStackupDescriptor(); // Now initialize all stackup items to the board values, when exist for( BOARD_STACKUP_ITEM* item: m_stackup.GetList() ) { // Search for board settings: for( BOARD_STACKUP_ITEM* board_item: brd_stackup.GetList() ) { if( item->GetBrdLayerId() != UNDEFINED_LAYER ) { if( item->GetBrdLayerId() == board_item->GetBrdLayerId() ) { *item = *board_item; break; } } else // dielectric layer: see m_DielectricLayerId for identification { // Compare dielectric layer with dielectric layer if( board_item->GetBrdLayerId() != UNDEFINED_LAYER ) continue; if( item->GetDielectricLayerId() == board_item->GetDielectricLayerId() ) { *item = *board_item; break; } } } } } int row = 0; for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() ) { for( int sub_idx = 0; sub_idx < item->GetSublayersCount(); sub_idx++ ) { m_rowUiItemsList.emplace_back( item, sub_idx, row ); row++; } } } // Transfer current UI settings to m_stackup but not to the board bool PANEL_SETUP_BOARD_STACKUP::transferDataFromUIToStackup() { wxString error_msg; bool success = true; double value; int row = 0; for( BOARD_STACKUP_ROW_UI_ITEM& ui_item : m_rowUiItemsList ) { // Skip stackup items useless for the current board if( !ui_item.m_isEnabled ) { row++; continue; } BOARD_STACKUP_ITEM* item = ui_item.m_Item; int sub_item = ui_item.m_SubItem; // Add sub layer if there is a new sub layer: while( item->GetSublayersCount() <= sub_item ) item->AddDielectricPrms( item->GetSublayersCount() ); if( sub_item == 0 ) // Name only main layer item->SetLayerName( ui_item.m_LayerName->GetLabel() ); if( item->HasEpsilonRValue() ) { wxTextCtrl* textCtrl = static_cast( ui_item.m_EpsilonCtrl ); wxString txt = textCtrl->GetValue(); if( txt.ToDouble( &value ) && value >= 0.0 ) item->SetEpsilonR( value, sub_item ); else if( txt.ToCDouble( &value ) && value >= 0.0 ) item->SetEpsilonR( value, sub_item ); else { success = false; error_msg << _( "Incorrect value for Epsilon R (Epsilon R must be positive or " "null if not used)" ); } } if( item->HasLossTangentValue() ) { wxTextCtrl* textCtrl = static_cast( ui_item.m_LossTgCtrl ); wxString txt = textCtrl->GetValue(); if( txt.ToDouble( &value ) && value >= 0.0 ) item->SetLossTangent( value, sub_item ); else if( txt.ToCDouble( &value ) && value >= 0.0 ) item->SetLossTangent( value, sub_item ); else { success = false; if( !error_msg.IsEmpty() ) error_msg << wxT( "\n" ); error_msg << _( "Incorrect value for Loss tg (Loss tg must be positive or null " "if not used)" ); } } if( item->IsMaterialEditable() ) { wxTextCtrl* textCtrl = static_cast( ui_item.m_MaterialCtrl ); item->SetMaterial( textCtrl->GetValue(), sub_item ); // Ensure the not specified mat name is the keyword, not its translation // to avoid any issue is the language setting changes if( !IsPrmSpecified( item->GetMaterial( sub_item ) ) ) item->SetMaterial( NotSpecifiedPrm(), sub_item ); } if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) { // Choice is Core or Prepreg. Sublayers have no choice: wxChoice* choice = dynamic_cast( ui_item.m_LayerTypeCtrl ); if( choice ) { int idx = choice->GetSelection(); if( idx == 0 ) item->SetTypeName( KEY_CORE ); else item->SetTypeName( KEY_PREPREG ); } } if( item->IsThicknessEditable() ) { wxTextCtrl* textCtrl = static_cast( ui_item.m_ThicknessCtrl ); int new_thickness = m_frame->ValueFromString( textCtrl->GetValue() ); item->SetThickness( new_thickness, sub_item ); if( new_thickness < 0 ) { success = false; if( !error_msg.IsEmpty() ) error_msg << wxT( "\n" ); error_msg << _( "A layer thickness is < 0. Fix it" ); } if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) { // Dielectric thickness layer can have a locked thickness: wxCheckBox* cb_box = static_cast( ui_item.m_ThicknessLockCtrl ); item->SetThicknessLocked( cb_box && cb_box->GetValue(), sub_item ); } } if( item->IsColorEditable() ) { wxBitmapComboBox* choice = dynamic_cast( ui_item.m_ColorCtrl ); if( choice ) { int idx = choice->GetSelection(); if( IsCustomColorIdx( item->GetType(), idx ) ) item->SetColor( ui_item.m_UserColor.ToHexString(), sub_item ); else item->SetColor( GetStandardColorName( item->GetType(), idx ), sub_item ); } } row++; } if( !success ) { wxMessageBox( error_msg, _( "Errors" ) ); return false; } m_stackup.m_HasDielectricConstrains = m_impedanceControlled->GetValue(); return true; } bool PANEL_SETUP_BOARD_STACKUP::TransferDataFromWindow() { if( !transferDataFromUIToStackup() ) return false; // NOTE: Copper layer count is transferred via PANEL_SETUP_LAYERS even though it is configured // on this page, because the logic for confirming deletion of board items on deleted layers is // on that panel and it doesn't make sense to split it up. BOARD_STACKUP& brd_stackup = m_brdSettings->GetStackupDescriptor(); STRING_FORMATTER old_stackup; // FormatBoardStackup() (using FormatInternalUnits()) expects a "C" locale // to execute some tests. So switch to the suitable locale LOCALE_IO dummy; brd_stackup.FormatBoardStackup( &old_stackup, m_board, 0 ); brd_stackup.m_FinishType = m_stackup.m_FinishType; brd_stackup.m_HasDielectricConstrains = m_stackup.m_HasDielectricConstrains; brd_stackup.m_EdgeConnectorConstraints = m_stackup.m_EdgeConnectorConstraints; brd_stackup.m_CastellatedPads = m_stackup.m_CastellatedPads; brd_stackup.m_EdgePlating = m_stackup.m_EdgePlating; // copy enabled items to the new board stackup brd_stackup.RemoveAll(); for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() ) { if( item->IsEnabled() ) brd_stackup.Add( new BOARD_STACKUP_ITEM( *item ) ); } STRING_FORMATTER new_stackup; brd_stackup.FormatBoardStackup( &new_stackup, m_board, 0 ); bool modified = old_stackup.GetString() != new_stackup.GetString(); int thickness = brd_stackup.BuildBoardThicknessFromStackup(); if( m_brdSettings->GetBoardThickness() != thickness ) { m_brdSettings->SetBoardThickness( thickness ); modified = true; } if( !m_brdSettings->m_HasStackup ) { m_brdSettings->m_HasStackup = true; modified = true; } if( modified ) m_frame->OnModify(); return true; } void PANEL_SETUP_BOARD_STACKUP::ImportSettingsFrom( BOARD* aBoard ) { BOARD* savedBrd = m_board; m_board = aBoard; BOARD_DESIGN_SETTINGS* savedSettings = m_brdSettings; m_brdSettings = &aBoard->GetDesignSettings(); m_enabledLayers = m_board->GetEnabledLayers() & BOARD_STACKUP::StackupAllowedBrdLayers(); rebuildLayerStackPanel( true ); synchronizeWithBoard( true ); computeBoardThickness(); m_brdSettings = savedSettings; m_board = savedBrd; } void PANEL_SETUP_BOARD_STACKUP::OnLayersOptionsChanged( LSET aNewLayerSet ) { // Can be called spuriously from events before the layers page is even created if( !m_panelLayers->IsInitialized() ) return; // First, verify the list of layers currently in stackup: // if it does not mach the list of layers set in PANEL_SETUP_LAYERS // rebuild the panel // the current enabled layers in PANEL_SETUP_LAYERS // Note: the number of layer can change, but not the layers properties LSET layersList = m_panelLayers->GetUILayerMask() & BOARD_STACKUP::StackupAllowedBrdLayers(); if( m_enabledLayers != layersList ) { m_enabledLayers = layersList; synchronizeWithBoard( false ); Layout(); Refresh(); } } void PANEL_SETUP_BOARD_STACKUP::onColorSelected( wxCommandEvent& event ) { int idx = event.GetSelection(); int item_id = event.GetId(); int row = item_id - ID_ITEM_COLOR; BOARD_STACKUP_ITEM* item = m_rowUiItemsList[row].m_Item; if( IsCustomColorIdx( item->GetType(), idx ) ) // user color is the last option in list { DIALOG_COLOR_PICKER dlg( this, m_rowUiItemsList[row].m_UserColor, true, nullptr, GetDefaultUserColor( m_rowUiItemsList[row].m_Item->GetType() ) ); #ifdef __WXGTK__ // Give a time-slice to close the menu before opening the dialog. // (Only matters on some versions of GTK.) wxSafeYield(); #endif if( dlg.ShowModal() == wxID_OK ) { wxBitmapComboBox* combo = static_cast( FindWindowById( item_id ) ); COLOR4D color = dlg.GetColor(); m_rowUiItemsList[row].m_UserColor = color; combo->SetString( idx, color.ToHexString() ); wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y ); LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ), color ); combo->SetItemBitmap( combo->GetCount() - 1, layerbmp ); combo->SetSelection( idx ); } } updateIconColor( row ); } void PANEL_SETUP_BOARD_STACKUP::onMaterialChange( wxCommandEvent& event ) { // Ensure m_materialList contains all materials already in use in stackup list // and add it is missing if( !transferDataFromUIToStackup() ) return; for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() ) { DIELECTRIC_SUBSTRATE_LIST* mat_list = nullptr; if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) mat_list = &m_delectricMatList; else if( item->GetType() == BS_ITEM_TYPE_SOLDERMASK ) mat_list = &m_solderMaskMatList; else if( item->GetType() == BS_ITEM_TYPE_SILKSCREEN ) mat_list = &m_silkscreenMatList; else continue; for( int ii = 0; ii < item->GetSublayersCount(); ii++ ) { int idx = mat_list->FindSubstrate( item->GetMaterial( ii ), item->GetEpsilonR( ii ), item->GetLossTangent( ii ) ); if( idx < 0 && !item->GetMaterial().IsEmpty() ) { // This material is not in list: add it DIELECTRIC_SUBSTRATE new_mat; new_mat.m_Name = item->GetMaterial( ii ); new_mat.m_EpsilonR = item->GetEpsilonR( ii ); new_mat.m_LossTangent = item->GetLossTangent( ii ); mat_list->AppendSubstrate( new_mat ); } } } int row = event.GetId() - ID_ITEM_MATERIAL; BOARD_STACKUP_ITEM* item = m_rowUiItemsList[row].m_Item; int sub_item = m_rowUiItemsList[row].m_SubItem; DIELECTRIC_SUBSTRATE_LIST* item_mat_list = nullptr; switch( item->GetType() ) { case BS_ITEM_TYPE_DIELECTRIC: item_mat_list = &m_delectricMatList; break; case BS_ITEM_TYPE_SOLDERMASK: item_mat_list = &m_solderMaskMatList; break; case BS_ITEM_TYPE_SILKSCREEN: item_mat_list = &m_silkscreenMatList; break; default: item_mat_list = nullptr; break; } wxCHECK( item_mat_list, /* void */ ); DIALOG_DIELECTRIC_MATERIAL dlg( this, *item_mat_list ); if( dlg.ShowModal() != wxID_OK ) return; DIELECTRIC_SUBSTRATE substrate = dlg.GetSelectedSubstrate(); if( substrate.m_Name.IsEmpty() ) // No substrate specified return; // Update Name, Epsilon R and Loss tg item->SetMaterial( substrate.m_Name, sub_item ); item->SetEpsilonR( substrate.m_EpsilonR, sub_item ); item->SetLossTangent( substrate.m_LossTangent, sub_item ); wxTextCtrl* textCtrl = static_cast( m_rowUiItemsList[row].m_MaterialCtrl ); textCtrl->ChangeValue( item->GetMaterial( sub_item ) ); if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC && !item->GetColor( sub_item ).StartsWith( "#" ) /* User defined color */ ) { if( substrate.m_Name.IsSameAs( "PTFE" ) || substrate.m_Name.IsSameAs( "Teflon" ) ) { item->SetColor( "PTFE natural", sub_item ); } else if( substrate.m_Name.IsSameAs( "Polyimide" ) || substrate.m_Name.IsSameAs( "Kapton" ) ) { item->SetColor( "Polyimide", sub_item ); } else if( substrate.m_Name.IsSameAs( "Al" ) ) { item->SetColor( "Aluminum", sub_item ); } else { item->SetColor( "FR4 natural", sub_item ); } } wxBitmapComboBox* picker = static_cast( m_rowUiItemsList[row].m_ColorCtrl ); for( size_t ii = 0; ii < GetStandardColors( item->GetType() ).size(); ii++ ) { if( GetStandardColorName( item->GetType(), ii ) == item->GetColor( sub_item ) ) { picker->SetSelection( ii ); break; } } // some layers have a material choice but not EpsilonR ctrl if( item->HasEpsilonRValue() ) { textCtrl = dynamic_cast( m_rowUiItemsList[row].m_EpsilonCtrl ); if( textCtrl ) textCtrl->ChangeValue( item->FormatEpsilonR( sub_item ) ); } // some layers have a material choice but not loss tg ctrl if( item->HasLossTangentValue() ) { textCtrl = dynamic_cast( m_rowUiItemsList[row].m_LossTgCtrl ); if( textCtrl ) textCtrl->ChangeValue( item->FormatLossTangent( sub_item ) ); } } void PANEL_SETUP_BOARD_STACKUP::onThicknessChange( wxCommandEvent& event ) { int row = event.GetId() - ID_ITEM_THICKNESS; wxString value = event.GetString(); BOARD_STACKUP_ITEM* item = GetStackupItem( row ); int idx = GetSublayerId( row ); item->SetThickness( m_frame->ValueFromString( value ), idx ); computeBoardThickness(); } BOARD_STACKUP_ITEM* PANEL_SETUP_BOARD_STACKUP::GetStackupItem( int aRow ) { return m_rowUiItemsList[aRow].m_Item; } int PANEL_SETUP_BOARD_STACKUP::GetSublayerId( int aRow ) { return m_rowUiItemsList[aRow].m_SubItem; } wxColor PANEL_SETUP_BOARD_STACKUP::getColorIconItem( int aRow ) { BOARD_STACKUP_ITEM* st_item = dynamic_cast( GetStackupItem( aRow ) ); wxASSERT( st_item ); wxColor color; if( ! st_item ) return color; switch( st_item->GetType() ) { case BS_ITEM_TYPE_COPPER: color = copperColor; break; case BS_ITEM_TYPE_DIELECTRIC: color = dielectricColor; break; case BS_ITEM_TYPE_SOLDERMASK: color = GetSelectedColor( aRow ); break; case BS_ITEM_TYPE_SILKSCREEN: color = GetSelectedColor( aRow ); break; case BS_ITEM_TYPE_SOLDERPASTE: color = pasteColor; break; default: case BS_ITEM_TYPE_UNDEFINED: wxFAIL_MSG( wxT( "PANEL_SETUP_BOARD_STACKUP::getColorIconItem: unrecognized item type" ) ); break; } wxASSERT_MSG( color.IsOk(), wxT( "Invalid color in PCB stackup" ) ); return color; } void PANEL_SETUP_BOARD_STACKUP::updateIconColor( int aRow ) { // explicit depth important under MSW. We use R,V,B 24 bits/pixel bitmap const int bitmap_depth = 24; if( aRow >= 0 ) { wxStaticBitmap* st_bitmap = m_rowUiItemsList[aRow].m_Icon; wxBitmap bmp( m_colorIconsSize.x, m_colorIconsSize.y / 2, bitmap_depth ); drawBitmap( bmp, getColorIconItem( aRow ) ); st_bitmap->SetBitmap( bmp ); return; } for( unsigned row = 0; row < m_rowUiItemsList.size(); row++ ) { if( m_rowUiItemsList[row].m_Icon ) { wxBitmap bmp( m_colorIconsSize.x, m_colorIconsSize.y / 2, bitmap_depth ); drawBitmap( bmp, getColorIconItem( row ) ); m_rowUiItemsList[row].m_Icon->SetBitmap( bmp ); } } } wxBitmapComboBox* PANEL_SETUP_BOARD_STACKUP::createColorBox( BOARD_STACKUP_ITEM* aStackupItem, int aRow ) { wxBitmapComboBox* combo = new wxBitmapComboBox( m_scGridWin, ID_ITEM_COLOR + aRow, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY ); // Fills the combo box with choice list + bitmaps BOARD_STACKUP_ITEM_TYPE itemType = aStackupItem ? aStackupItem->GetType() : BS_ITEM_TYPE_SILKSCREEN; for( size_t ii = 0; ii < GetStandardColors( itemType ).size(); ii++ ) { wxString label; COLOR4D curr_color; // Defined colors have a name, the user color uses HTML notation ( i.e. #FF000080) if( IsCustomColorIdx( itemType, ii ) && aStackupItem && aStackupItem->GetColor().StartsWith( wxT( "#" ) ) ) { label = aStackupItem->GetColor(); curr_color = COLOR4D( label ); } else { label = wxGetTranslation( GetStandardColorName( itemType, ii ) ); curr_color = GetStandardColor( itemType, ii ); } wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y ); LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ), curr_color ); combo->Append( label, layerbmp ); } // Ensure the size of the widget is enough to show the text and the icon // We have to have a selected item when doing this, because otherwise GTK // will just choose a random size that might not fit the actual data // (such as in cases where the font size is very large). So we select // the longest item (which should be the last item), and size it that way. int sel = combo->GetSelection(); combo->SetSelection( combo->GetCount() - 1 ); combo->SetMinSize( wxSize( -1, -1 ) ); wxSize bestSize = combo->GetBestSize(); bestSize.x = bestSize.x + m_colorSwatchesSize.x; combo->SetMinSize( bestSize ); combo->SetSelection( sel ); // add the wxBitmapComboBox to wxControl list, to be able to disconnect the event // on exit m_controlItemsList.push_back( combo ); combo->Connect( wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onColorSelected ), nullptr, this ); combo->Bind( wxEVT_COMBOBOX_DROPDOWN, [combo]( wxCommandEvent& aEvent ) { combo->SetString( combo->GetCount() - 1, _( "Custom..." ) ); } ); return combo; } void drawBitmap( wxBitmap& aBitmap, wxColor aColor ) { wxNativePixelData data( aBitmap ); wxNativePixelData::Iterator p( data ); for( int yy = 0; yy < data.GetHeight(); yy++ ) { wxNativePixelData::Iterator rowStart = p; for( int xx = 0; xx < data.GetWidth(); xx++ ) { p.Red() = aColor.Red(); p.Green() = aColor.Green(); p.Blue() = aColor.Blue(); ++p; } p = rowStart; p.OffsetY( data, 1 ); } }