2019-07-27 19:09:43 +00:00
|
|
|
/*
|
|
|
|
* 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
|
2023-01-11 01:00:47 +00:00
|
|
|
* Copyright (C) 2019-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
2019-07-27 19:09:43 +00:00
|
|
|
*
|
|
|
|
* 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 <macros.h> // arrayDim definition
|
|
|
|
#include <pcb_edit_frame.h>
|
2020-11-12 20:19:22 +00:00
|
|
|
#include <board.h>
|
2021-06-06 19:03:10 +00:00
|
|
|
#include <board_design_settings.h>
|
2020-06-28 01:42:09 +00:00
|
|
|
#include <dialogs/dialog_color_picker.h>
|
2019-07-27 19:09:43 +00:00
|
|
|
#include <widgets/paged_dialog.h>
|
|
|
|
#include <widgets/layer_box_selector.h>
|
2023-01-11 01:00:47 +00:00
|
|
|
#include <widgets/wx_panel.h>
|
2021-06-03 12:11:15 +00:00
|
|
|
#include <wx/log.h>
|
2019-07-27 19:09:43 +00:00
|
|
|
#include <wx/rawbmp.h>
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <math/util.h> // for KiROUND
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
#include "panel_board_stackup.h"
|
|
|
|
#include <panel_setup_layers.h>
|
2019-09-06 09:26:27 +00:00
|
|
|
#include "board_stackup_reporter.h"
|
2019-09-05 19:00:14 +00:00
|
|
|
#include <bitmaps.h>
|
2019-09-06 09:26:27 +00:00
|
|
|
#include <wx/clipbrd.h>
|
|
|
|
#include <wx/dataobj.h>
|
2019-09-09 18:30:43 +00:00
|
|
|
#include "dialog_dielectric_list_manager.h"
|
2019-11-14 15:26:05 +00:00
|
|
|
#include <wx/wupdlock.h>
|
2020-08-05 14:49:07 +00:00
|
|
|
#include <wx/richmsgdlg.h>
|
2021-01-25 15:18:28 +00:00
|
|
|
#include <wx/dcclient.h>
|
2021-06-03 15:41:26 +00:00
|
|
|
#include <wx/treebook.h>
|
2021-07-16 18:36:45 +00:00
|
|
|
#include <wx/textdlg.h>
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2021-04-12 07:57:40 +00:00
|
|
|
#include <locale_io.h>
|
2021-08-03 00:11:11 +00:00
|
|
|
#include <eda_list_dialog.h>
|
2022-09-17 03:35:16 +00:00
|
|
|
#include <string_utils.h> // for UIDouble2Str()
|
2021-04-12 07:57:40 +00:00
|
|
|
|
2021-07-16 18:36:45 +00:00
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
// Some wx widget ID to know what widget has fired a event:
|
2019-11-14 15:26:05 +00:00
|
|
|
#define ID_INCREMENT 256 // space between 2 ID type. Bigger than the layer count max
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
ID_ITEM_MATERIAL = 10000, // Be sure it is higher than other IDs
|
|
|
|
// used in the board setup dialog
|
2019-07-27 19:09:43 +00:00
|
|
|
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 );
|
2019-09-06 18:59:13 +00:00
|
|
|
static wxColor dielectricColor( 75, 120, 75 );
|
2019-07-27 19:09:43 +00:00
|
|
|
static wxColor pasteColor( 200, 200, 200 );
|
|
|
|
|
|
|
|
static void drawBitmap( wxBitmap& aBitmap, wxColor aColor );
|
|
|
|
|
|
|
|
|
|
|
|
PANEL_SETUP_BOARD_STACKUP::PANEL_SETUP_BOARD_STACKUP( PAGED_DIALOG* aParent, PCB_EDIT_FRAME* aFrame,
|
|
|
|
PANEL_SETUP_LAYERS* aPanelLayers ):
|
2019-09-24 12:33:28 +00:00
|
|
|
PANEL_SETUP_BOARD_STACKUP_BASE( aParent->GetTreebook() ),
|
|
|
|
m_delectricMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_DIELECTRIC ),
|
|
|
|
m_solderMaskMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_SOLDERMASK ),
|
|
|
|
m_silkscreenMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_SILKSCREEN )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2020-08-04 23:40:59 +00:00
|
|
|
m_parentDialog = aParent;
|
2019-07-27 19:09:43 +00:00
|
|
|
m_frame = aFrame;
|
|
|
|
m_panelLayers = aPanelLayers;
|
|
|
|
m_board = m_frame->GetBoard();
|
|
|
|
m_brdSettings = &m_board->GetDesignSettings();
|
|
|
|
|
2023-01-11 01:00:47 +00:00
|
|
|
m_panel1->SetBorders( false, false, true, true );
|
|
|
|
|
2021-02-24 02:31:12 +00:00
|
|
|
m_panelLayers->SetPhysicalStackupPanel( this );
|
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
m_enabledLayers = m_board->GetEnabledLayers() & BOARD_STACKUP::StackupAllowedBrdLayers();
|
|
|
|
|
|
|
|
// Calculates a good size for color swatches (icons) in this dialog
|
|
|
|
wxClientDC dc( this );
|
2022-02-04 22:44:59 +00:00
|
|
|
m_colorSwatchesSize = dc.GetTextExtent( wxT( "XX" ) );
|
|
|
|
m_colorIconsSize = dc.GetTextExtent( wxT( "XXXX" ) );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-09-06 18:59:13 +00:00
|
|
|
// Calculates a good size for wxTextCtrl to enter Epsilon R and Loss tan
|
2021-08-23 18:58:18 +00:00
|
|
|
// ("0.0000000" + margins)
|
2022-02-04 22:44:59 +00:00
|
|
|
m_numericFieldsSize = dc.GetTextExtent( wxT( "X.XXXXXXX" ) );
|
2019-07-27 19:09:43 +00:00
|
|
|
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)
|
2022-02-04 22:44:59 +00:00
|
|
|
m_numericTextCtrlSize = dc.GetTextExtent( wxT( "XXX.XXXXXXX mils" ) );
|
2019-07-27 19:09:43 +00:00
|
|
|
m_numericTextCtrlSize.y = -1; // Use default for the vertical size
|
|
|
|
|
2019-09-06 18:59:13 +00:00
|
|
|
// The grid column containing the lock checkbox is kept to a minimal
|
2019-09-05 19:00:14 +00:00
|
|
|
// size. So we use a wxStaticBitmap: set the bitmap itself
|
2021-03-08 02:59:07 +00:00
|
|
|
m_bitmapLockThickness->SetBitmap( KiScaledBitmap( BITMAPS::locked, aFrame ) );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
// Gives a minimal size of wxTextCtrl showing dimensions+units
|
|
|
|
m_tcCTValue->SetMinSize( m_numericTextCtrlSize );
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
// Prepare dielectric layer type: layer type keyword is "core" or "prepreg"
|
|
|
|
m_core_prepreg_choice.Add( _( "Core" ) );
|
|
|
|
m_core_prepreg_choice.Add( _( "PrePreg" ) );
|
2019-11-11 18:34:48 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
buildLayerStackPanel( true );
|
|
|
|
synchronizeWithBoard( true );
|
2021-04-08 19:53:33 +00:00
|
|
|
computeBoardThickness();
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PANEL_SETUP_BOARD_STACKUP::~PANEL_SETUP_BOARD_STACKUP()
|
|
|
|
{
|
|
|
|
disconnectEvents();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-07-16 18:36:45 +00:00
|
|
|
void PANEL_SETUP_BOARD_STACKUP::onCopperLayersSelCount( wxCommandEvent& event )
|
|
|
|
{
|
2022-09-27 06:40:41 +00:00
|
|
|
int oldBoardWidth = static_cast<int>( m_frame->ValueFromString( m_tcCTValue->GetValue() ) );
|
2021-07-16 18:36:45 +00:00
|
|
|
updateCopperLayerCount();
|
|
|
|
showOnlyActiveLayers();
|
2022-09-27 06:40:41 +00:00
|
|
|
setDefaultLayerWidths( oldBoardWidth );
|
2021-07-16 18:36:45 +00:00
|
|
|
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<wxCheckBox*> ( ui_item.m_ThicknessLockCtrl );
|
|
|
|
|
|
|
|
if( cb_box && !cb_box->GetValue() )
|
|
|
|
{
|
|
|
|
items_candidate.push_back( &ui_item );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_ThicknessCtrl );
|
|
|
|
|
2022-10-05 16:42:03 +00:00
|
|
|
int item_thickness = m_frame->ValueFromString( textCtrl->GetValue() );
|
2021-07-16 18:36:45 +00:00
|
|
|
min_thickness += item_thickness;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxString title;
|
|
|
|
|
|
|
|
if( min_thickness == 0 )
|
2021-09-07 09:48:42 +00:00
|
|
|
{
|
|
|
|
title.Printf( _( "Enter board thickness in %s:" ),
|
2022-09-19 09:25:20 +00:00
|
|
|
EDA_UNIT_UTILS::GetText( m_frame->GetUserUnits() ).Trim( false ) );
|
2021-09-07 09:48:42 +00:00
|
|
|
}
|
2021-07-16 18:36:45 +00:00
|
|
|
else
|
2021-09-07 09:48:42 +00:00
|
|
|
{
|
|
|
|
title.Printf( _( "Enter expected board thickness (min value %s):" ),
|
2022-09-19 09:25:20 +00:00
|
|
|
m_frame->StringFromValue( min_thickness, true ) );
|
2021-09-07 09:48:42 +00:00
|
|
|
}
|
2021-07-16 18:36:45 +00:00
|
|
|
|
2021-09-07 09:48:42 +00:00
|
|
|
wxTextEntryDialog dlg( this, title, _( "Adjust Unlocked Dielectric Layers" ) );
|
2021-07-16 18:36:45 +00:00
|
|
|
|
|
|
|
if( dlg.ShowModal() != wxID_OK )
|
|
|
|
return;
|
|
|
|
|
2022-09-19 09:25:20 +00:00
|
|
|
int iu_thickness = m_frame->ValueFromString( dlg.GetValue() );
|
2021-07-16 18:36:45 +00:00
|
|
|
|
2022-09-27 06:40:41 +00:00
|
|
|
if( iu_thickness < min_thickness )
|
2021-07-16 18:36:45 +00:00
|
|
|
{
|
2021-09-07 09:48:42 +00:00
|
|
|
wxMessageBox( wxString::Format( _("Value too small (min value %s)." ),
|
2022-09-19 09:25:20 +00:00
|
|
|
m_frame->StringFromValue( min_thickness, true ) ) );
|
2021-07-16 18:36:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now adjust not locked dielectric thickness layers:
|
|
|
|
|
|
|
|
if( items_candidate.size() )
|
|
|
|
{
|
2022-09-27 06:40:41 +00:00
|
|
|
setDefaultLayerWidths( iu_thickness );
|
2021-07-16 18:36:45 +00:00
|
|
|
}
|
|
|
|
else
|
2021-07-19 23:56:05 +00:00
|
|
|
{
|
2021-07-16 18:36:45 +00:00
|
|
|
wxMessageBox( _( "All dielectric thickness layers are locked" ) );
|
2021-07-19 23:56:05 +00:00
|
|
|
}
|
2021-07-16 18:36:45 +00:00
|
|
|
|
|
|
|
computeBoardThickness();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
void PANEL_SETUP_BOARD_STACKUP::disconnectEvents()
|
|
|
|
{
|
|
|
|
// Disconnect Events connected to items in m_controlItemsList
|
|
|
|
for( wxControl* item: m_controlItemsList )
|
|
|
|
{
|
|
|
|
wxBitmapComboBox* cb = dynamic_cast<wxBitmapComboBox*>( item );
|
|
|
|
|
|
|
|
if( cb )
|
2021-06-26 23:33:12 +00:00
|
|
|
{
|
2019-07-27 19:09:43 +00:00
|
|
|
cb->Disconnect( wxEVT_COMMAND_COMBOBOX_SELECTED,
|
|
|
|
wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onColorSelected ),
|
2021-07-19 23:56:05 +00:00
|
|
|
nullptr, this );
|
2021-06-26 23:33:12 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-09-09 18:30:43 +00:00
|
|
|
wxButton* matButt = dynamic_cast<wxButton*>( item );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-09-09 18:30:43 +00:00
|
|
|
if( matButt )
|
2021-06-26 23:33:12 +00:00
|
|
|
{
|
2019-09-09 18:30:43 +00:00
|
|
|
matButt->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED,
|
|
|
|
wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onMaterialChange ),
|
2021-07-19 23:56:05 +00:00
|
|
|
nullptr, this );
|
2021-06-26 23:33:12 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( item );
|
|
|
|
|
|
|
|
if( textCtrl )
|
2021-06-26 23:33:12 +00:00
|
|
|
{
|
2019-09-25 07:27:36 +00:00
|
|
|
textCtrl->Disconnect( wxEVT_COMMAND_TEXT_UPDATED,
|
|
|
|
wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onThicknessChange ),
|
2021-07-19 23:56:05 +00:00
|
|
|
nullptr, this );
|
2021-06-26 23:33:12 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-11-11 18:34:48 +00:00
|
|
|
void PANEL_SETUP_BOARD_STACKUP::onAddDielectricLayer( wxCommandEvent& event )
|
|
|
|
{
|
2021-06-26 23:33:12 +00:00
|
|
|
wxArrayString headers;
|
|
|
|
headers.Add( _( "Layers" ) );
|
|
|
|
|
2019-11-11 18:34:48 +00:00
|
|
|
// Build Dielectric layers list:
|
2021-06-26 23:33:12 +00:00
|
|
|
std::vector<wxArrayString> d_list;
|
|
|
|
std::vector<int> rows; // indexes of row values for each selectable item
|
|
|
|
int row = -1;
|
2019-11-11 18:34:48 +00:00
|
|
|
|
|
|
|
for( BOARD_STACKUP_ROW_UI_ITEM& item : m_rowUiItemsList )
|
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
row++;
|
|
|
|
|
2019-11-11 18:34:48 +00:00
|
|
|
if( !item.m_isEnabled )
|
|
|
|
continue;
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
BOARD_STACKUP_ITEM* brd_stackup_item = item.m_Item;
|
|
|
|
|
|
|
|
if( brd_stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
|
|
|
|
{
|
2021-06-26 23:33:12 +00:00
|
|
|
wxArrayString d_item;
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( brd_stackup_item->GetSublayersCount() > 1 )
|
|
|
|
{
|
2021-06-26 23:33:12 +00:00
|
|
|
d_item.Add( wxString::Format( _( "Layer '%s' (sublayer %d/%d)" ),
|
|
|
|
brd_stackup_item->FormatDielectricLayerName(),
|
|
|
|
item.m_SubItem+1,
|
|
|
|
brd_stackup_item->GetSublayersCount() ) );
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
|
|
|
else
|
2021-06-26 23:33:12 +00:00
|
|
|
{
|
|
|
|
d_item.Add( brd_stackup_item->FormatDielectricLayerName() );
|
|
|
|
}
|
2019-11-14 15:26:05 +00:00
|
|
|
|
2021-06-26 23:33:12 +00:00
|
|
|
d_list.emplace_back( d_item );
|
2019-11-14 15:26:05 +00:00
|
|
|
rows.push_back( row );
|
|
|
|
}
|
2019-11-11 18:34:48 +00:00
|
|
|
}
|
|
|
|
|
2022-10-20 09:27:35 +00:00
|
|
|
EDA_LIST_DIALOG dlg( m_parentDialog, _( "Add Dielectric Layer" ), headers, d_list,
|
|
|
|
wxEmptyString, false /* do not sort the list: it is **expected** in stack order */);
|
2021-06-26 23:33:12 +00:00
|
|
|
dlg.SetListLabel( _( "Select layer to add:" ) );
|
|
|
|
dlg.HideFilter();
|
2019-11-14 15:26:05 +00:00
|
|
|
|
2021-06-26 23:33:12 +00:00
|
|
|
if( dlg.ShowModal() == wxID_OK && dlg.GetSelection() >= 0 )
|
|
|
|
{
|
|
|
|
row = rows[ dlg.GetSelection() ];
|
2019-11-14 15:26:05 +00:00
|
|
|
|
2021-06-26 23:33:12 +00:00
|
|
|
BOARD_STACKUP_ITEM* brd_stackup_item = m_rowUiItemsList[row].m_Item;
|
|
|
|
int new_sublayer = m_rowUiItemsList[row].m_SubItem;
|
2019-11-14 15:26:05 +00:00
|
|
|
|
2021-06-26 23:33:12 +00:00
|
|
|
// Insert a new item after the selected item
|
|
|
|
brd_stackup_item->AddDielectricPrms( new_sublayer+1 );
|
2019-11-14 15:26:05 +00:00
|
|
|
|
2021-06-26 23:33:12 +00:00
|
|
|
rebuildLayerStackPanel();
|
|
|
|
computeBoardThickness();
|
|
|
|
}
|
2019-11-11 18:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PANEL_SETUP_BOARD_STACKUP::onRemoveDielectricLayer( wxCommandEvent& event )
|
|
|
|
{
|
2021-06-26 23:33:12 +00:00
|
|
|
wxArrayString headers;
|
|
|
|
headers.Add( _( "Layers" ) );
|
|
|
|
|
2019-11-11 18:34:48 +00:00
|
|
|
// Build deletable Dielectric layers list.
|
2019-11-14 15:26:05 +00:00
|
|
|
// A layer can be deleted if there are 2 (or more) dielectric sub-layers
|
2019-11-11 18:34:48 +00:00
|
|
|
// between 2 copper layers
|
2021-06-26 23:33:12 +00:00
|
|
|
std::vector<wxArrayString> d_list;
|
|
|
|
std::vector<int> rows; // indexes of row values for each selectable item
|
|
|
|
int row = 0; // row index in m_rowUiItemsList of items in choice list
|
2019-11-11 18:34:48 +00:00
|
|
|
|
2019-11-25 12:30:48 +00:00
|
|
|
// Build the list of dielectric layers:
|
2021-03-04 14:13:45 +00:00
|
|
|
for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
|
2019-11-11 18:34:48 +00:00
|
|
|
{
|
2019-11-25 12:30:48 +00:00
|
|
|
if( !item->IsEnabled() || item->GetType() != BS_ITEM_TYPE_DIELECTRIC ||
|
|
|
|
item->GetSublayersCount() <= 1 )
|
|
|
|
{
|
2021-06-26 23:33:12 +00:00
|
|
|
row++;
|
2019-11-14 15:26:05 +00:00
|
|
|
continue;
|
2019-11-25 12:30:48 +00:00
|
|
|
}
|
2019-11-11 18:34:48 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
for( int ii = 0; ii < item->GetSublayersCount(); ii++ )
|
|
|
|
{
|
2021-06-26 23:33:12 +00:00
|
|
|
wxArrayString d_item;
|
|
|
|
|
|
|
|
d_item.Add( wxString::Format( _( "Layer '%s' sublayer %d/%d" ),
|
|
|
|
item->FormatDielectricLayerName(),
|
|
|
|
ii+1,
|
|
|
|
item->GetSublayersCount() ) );
|
2019-11-11 18:34:48 +00:00
|
|
|
|
2021-06-26 23:33:12 +00:00
|
|
|
d_list.emplace_back( d_item );
|
|
|
|
rows.push_back( row++ );
|
2019-11-11 18:34:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-20 09:27:35 +00:00
|
|
|
EDA_LIST_DIALOG dlg( m_parentDialog, _( "Remove Dielectric Layer" ), headers, d_list,
|
|
|
|
wxEmptyString, false /* do not sort the list: it is **expected** in stack order */ );
|
2021-06-26 23:33:12 +00:00
|
|
|
dlg.SetListLabel( _( "Select layer to remove:" ) );
|
|
|
|
dlg.HideFilter();
|
2019-11-11 18:34:48 +00:00
|
|
|
|
2021-06-26 23:33:12 +00:00
|
|
|
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;
|
2019-11-14 15:26:05 +00:00
|
|
|
|
2021-06-26 23:33:12 +00:00
|
|
|
// Remove the selected sub item for the selected dielectric layer
|
|
|
|
brd_stackup_item->RemoveDielectricPrms( sublayer );
|
2019-11-14 15:26:05 +00:00
|
|
|
|
2021-06-26 23:33:12 +00:00
|
|
|
rebuildLayerStackPanel();
|
|
|
|
computeBoardThickness();
|
|
|
|
}
|
2019-11-11 18:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PANEL_SETUP_BOARD_STACKUP::onRemoveDielUI( wxUpdateUIEvent& event )
|
|
|
|
{
|
|
|
|
// The m_buttonRemoveDielectricLayer wxButton is enabled only if a dielectric
|
2019-11-14 15:26:05 +00:00
|
|
|
// layer can be removed, i.e. if dielectric layers have sublayers
|
2021-03-04 14:13:45 +00:00
|
|
|
for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
|
2019-11-11 18:34:48 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
if( !item->IsEnabled() || item->GetType() != BS_ITEM_TYPE_DIELECTRIC )
|
|
|
|
continue;
|
2019-11-11 18:34:48 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->GetSublayersCount() > 1 )
|
2019-11-11 18:34:48 +00:00
|
|
|
{
|
2021-04-07 16:35:12 +00:00
|
|
|
event.Enable( true );
|
2019-11-11 18:34:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-07 16:35:12 +00:00
|
|
|
event.Enable( false );
|
2019-11-11 18:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-06 09:26:27 +00:00
|
|
|
void PANEL_SETUP_BOARD_STACKUP::onExportToClipboard( wxCommandEvent& event )
|
|
|
|
{
|
|
|
|
if( !transferDataFromUIToStackup() )
|
|
|
|
return;
|
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
// Build a ASCII representation of stackup and copy it in the clipboard
|
2022-09-19 09:25:20 +00:00
|
|
|
wxString report = BuildStackupReport( m_stackup, m_frame->GetUserUnits() );
|
2019-09-06 09:26:27 +00:00
|
|
|
|
2021-01-29 19:13:12 +00:00
|
|
|
wxLogNull doNotLog; // disable logging of failed clipboard actions
|
|
|
|
|
2019-09-06 09:26:27 +00:00
|
|
|
if( wxTheClipboard->Open() )
|
|
|
|
{
|
|
|
|
// This data objects are held by the clipboard,
|
|
|
|
// so do not delete them in the app.
|
|
|
|
wxTheClipboard->SetData( new wxTextDataObject( report ) );
|
2021-05-01 22:00:08 +00:00
|
|
|
wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
|
2019-09-06 09:26:27 +00:00
|
|
|
wxTheClipboard->Close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
wxColor PANEL_SETUP_BOARD_STACKUP::GetSelectedColor( int aRow ) const
|
|
|
|
{
|
2021-08-23 18:58:18 +00:00
|
|
|
const BOARD_STACKUP_ROW_UI_ITEM& row = m_rowUiItemsList[aRow];
|
|
|
|
const BOARD_STACKUP_ITEM* item = row.m_Item;
|
|
|
|
const wxBitmapComboBox* choice = dynamic_cast<wxBitmapComboBox*>( row.m_ColorCtrl );
|
2019-07-27 19:09:43 +00:00
|
|
|
wxASSERT( choice );
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
int idx = choice ? choice->GetSelection() : 0;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2021-08-16 18:57:15 +00:00
|
|
|
if( IsCustomColorIdx( item->GetType(), idx ) )
|
|
|
|
return m_rowUiItemsList[aRow].m_UserColor.ToColour();
|
2021-08-23 18:58:18 +00:00
|
|
|
else
|
2021-08-16 18:57:15 +00:00
|
|
|
return GetStandardColor( item->GetType(), idx ).ToColour();
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-09-27 06:40:41 +00:00
|
|
|
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<wxCheckBox*>( ui_item.m_ThicknessLockCtrl );
|
|
|
|
wxChoice* layerType = dynamic_cast<wxChoice*>( 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<wxTextCtrl*>( 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<wxChoice*>( ui_item.m_LayerTypeCtrl );
|
|
|
|
|
|
|
|
if( !layerType )
|
|
|
|
{
|
|
|
|
// ignore secondary dielectric layers
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxCheckBox* cbLock = dynamic_cast<wxCheckBox*>( 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<wxTextCtrl*>( ui_item.m_ThicknessCtrl );
|
|
|
|
layerType->SetSelection( currentLayerIsCore ? 0 : 1 );
|
|
|
|
textCtrl->SetValue( m_frame->StringFromValue( layerThickness ) );
|
|
|
|
|
|
|
|
currentLayerIsCore = !currentLayerIsCore;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-02-18 17:09:02 +00:00
|
|
|
int PANEL_SETUP_BOARD_STACKUP::computeBoardThickness()
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
|
|
|
int thickness = 0;
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
for( BOARD_STACKUP_ROW_UI_ITEM& ui_item : m_rowUiItemsList )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
BOARD_STACKUP_ITEM* item = ui_item.m_Item;
|
|
|
|
|
|
|
|
if( !item->IsThicknessEditable() || !ui_item.m_isEnabled )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_ThicknessCtrl );
|
2022-09-19 09:25:20 +00:00
|
|
|
int item_thickness = m_frame->ValueFromString( textCtrl->GetValue() );
|
2019-11-14 15:26:05 +00:00
|
|
|
|
|
|
|
thickness += item_thickness;
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
2022-09-19 09:25:20 +00:00
|
|
|
wxString thicknessStr = m_frame->StringFromValue( thickness, true );
|
2021-03-04 14:13:45 +00:00
|
|
|
|
2021-04-07 16:35:12 +00:00
|
|
|
// The text in the event will translate to the value for the text control
|
|
|
|
// and is only updated if it changed
|
2021-12-19 09:09:27 +00:00
|
|
|
m_tcCTValue->ChangeValue( thicknessStr );
|
2022-02-18 17:09:02 +00:00
|
|
|
|
|
|
|
return thickness;
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-24 02:31:12 +00:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
void PANEL_SETUP_BOARD_STACKUP::synchronizeWithBoard( bool aFullSync )
|
|
|
|
{
|
2021-08-23 18:58:18 +00:00
|
|
|
const BOARD_STACKUP& brd_stackup = m_brdSettings->GetStackupDescriptor();
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
if( aFullSync )
|
|
|
|
{
|
2021-02-24 02:31:12 +00:00
|
|
|
m_choiceCopperLayers->SetSelection( ( m_board->GetCopperLayerCount() / 2 ) - 1 );
|
2021-02-24 21:20:11 +00:00
|
|
|
m_impedanceControlled->SetValue( brd_stackup.m_HasDielectricConstrains );
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
for( BOARD_STACKUP_ROW_UI_ITEM& ui_row_item : m_rowUiItemsList )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
BOARD_STACKUP_ITEM* item = ui_row_item.m_Item;
|
|
|
|
int sub_item = ui_row_item.m_SubItem;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
wxChoice* choice = dynamic_cast<wxChoice*>( ui_row_item.m_LayerTypeCtrl );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( choice )
|
|
|
|
choice->SetSelection( item->GetTypeName() == KEY_CORE ? 0 : 1 );
|
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->IsMaterialEditable() )
|
|
|
|
{
|
|
|
|
wxTextCtrl* matName = dynamic_cast<wxTextCtrl*>( ui_row_item.m_MaterialCtrl );
|
|
|
|
|
|
|
|
if( matName )
|
|
|
|
{
|
|
|
|
if( IsPrmSpecified( item->GetMaterial( sub_item ) ) )
|
2021-12-19 09:09:27 +00:00
|
|
|
matName->ChangeValue( item->GetMaterial( sub_item ) );
|
2019-11-14 15:26:05 +00:00
|
|
|
else
|
2021-12-19 09:09:27 +00:00
|
|
|
matName->ChangeValue( wxGetTranslation( NotSpecifiedPrm() ) );
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->IsThicknessEditable() )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_ThicknessCtrl );
|
|
|
|
|
|
|
|
if( textCtrl )
|
2022-09-19 09:25:20 +00:00
|
|
|
textCtrl->ChangeValue( m_frame->StringFromValue( item->GetThickness( sub_item ), true ) );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-12 07:59:02 +00:00
|
|
|
if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
|
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
wxCheckBox* cb_box = dynamic_cast<wxCheckBox*> ( ui_row_item.m_ThicknessLockCtrl );
|
2019-11-12 07:59:02 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( cb_box )
|
|
|
|
cb_box->SetValue( item->IsThicknessLocked( sub_item ) );
|
2019-11-12 07:59:02 +00:00
|
|
|
}
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if( item->IsColorEditable() )
|
|
|
|
{
|
|
|
|
auto bm_combo = dynamic_cast<wxBitmapComboBox*>( ui_row_item.m_ColorCtrl );
|
2021-08-16 18:57:15 +00:00
|
|
|
int selected = 0; // The "not specified" item
|
2019-11-12 07:59:02 +00:00
|
|
|
|
2022-12-01 15:01:26 +00:00
|
|
|
if( item->GetColor( sub_item ).StartsWith( wxT( "#" ) ) ) // User defined color
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2022-12-01 15:01:26 +00:00
|
|
|
COLOR4D custom_color( item->GetColor( sub_item ) );
|
2021-08-16 18:57:15 +00:00
|
|
|
|
|
|
|
ui_row_item.m_UserColor = custom_color;
|
|
|
|
|
|
|
|
selected = GetColorUserDefinedListIdx( item->GetType() );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( bm_combo ) // Update user color shown in the wxBitmapComboBox
|
2019-09-27 15:12:24 +00:00
|
|
|
{
|
2022-12-01 15:01:26 +00:00
|
|
|
bm_combo->SetString( selected, item->GetColor( sub_item ) );
|
2019-11-14 15:26:05 +00:00
|
|
|
wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
|
2021-08-16 18:57:15 +00:00
|
|
|
LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D(), custom_color );
|
|
|
|
bm_combo->SetItemBitmap( selected, layerbmp );
|
2019-09-27 15:12:24 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
2019-11-14 15:26:05 +00:00
|
|
|
else
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2021-08-23 18:58:18 +00:00
|
|
|
if( bm_combo )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2021-08-23 18:58:18 +00:00
|
|
|
// Note: don't use bm_combo->FindString() because the combo strings are
|
|
|
|
// translated.
|
2022-12-06 16:01:25 +00:00
|
|
|
for( size_t ii = 0; ii < GetStandardColors( item->GetType() ).size(); ii++ )
|
2019-11-14 15:26:05 +00:00
|
|
|
{
|
2022-12-01 15:01:26 +00:00
|
|
|
if( GetStandardColorName( item->GetType(), ii ) == item->GetColor( sub_item ) )
|
2021-08-23 18:58:18 +00:00
|
|
|
{
|
2021-08-16 18:57:15 +00:00
|
|
|
selected = ii;
|
2021-08-23 18:58:18 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-16 18:57:15 +00:00
|
|
|
if( bm_combo )
|
|
|
|
bm_combo->SetSelection( selected );
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->HasEpsilonRValue() )
|
|
|
|
{
|
2022-09-17 03:35:16 +00:00
|
|
|
wxString txt = UIDouble2Str( item->GetEpsilonR( sub_item ) );
|
2019-11-14 15:26:05 +00:00
|
|
|
wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_EpsilonCtrl );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( textCtrl )
|
2021-12-19 09:09:27 +00:00
|
|
|
textCtrl->ChangeValue( txt );
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->HasLossTangentValue() )
|
|
|
|
{
|
2022-09-17 03:35:16 +00:00
|
|
|
wxString txt = UIDouble2Str( item->GetLossTangent( sub_item ) );
|
2019-11-14 15:26:05 +00:00
|
|
|
wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_LossTgCtrl );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( textCtrl )
|
2021-12-19 09:09:27 +00:00
|
|
|
textCtrl->ChangeValue( txt );
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
// Now enable/disable stackup items, according to the m_enabledLayers config
|
|
|
|
showOnlyActiveLayers();
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
updateIconColor();
|
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
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 )
|
|
|
|
{
|
2019-07-27 19:09:43 +00:00
|
|
|
bool show_item;
|
2019-11-14 15:26:05 +00:00
|
|
|
BOARD_STACKUP_ITEM* item = ui_row_item.m_Item;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-11 18:34:48 +00:00
|
|
|
if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
|
2019-07-27 19:09:43 +00:00
|
|
|
// the m_DielectricLayerId is not a copper layer id, it is a dielectric idx from 1
|
2019-11-11 18:34:48 +00:00
|
|
|
show_item = item->GetDielectricLayerId() < copperLayersCount;
|
2019-07-27 19:09:43 +00:00
|
|
|
else
|
2019-11-11 18:34:48 +00:00
|
|
|
show_item = m_enabledLayers[item->GetBrdLayerId()];
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-11 18:34:48 +00:00
|
|
|
item->SetEnabled( show_item );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
ui_row_item.m_isEnabled = show_item;
|
|
|
|
|
|
|
|
// 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 );
|
2019-09-09 18:30:43 +00:00
|
|
|
|
|
|
|
if( ui_row_item.m_MaterialButt )
|
|
|
|
ui_row_item.m_MaterialButt->Show( show_item );
|
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
void PANEL_SETUP_BOARD_STACKUP::addMaterialChooser( wxWindowID aId, const wxString* aMaterialName,
|
2019-09-09 18:30:43 +00:00
|
|
|
BOARD_STACKUP_ROW_UI_ITEM& aUiRowItem )
|
|
|
|
{
|
|
|
|
wxBoxSizer* bSizerMat = new wxBoxSizer( wxHORIZONTAL );
|
2021-02-24 21:20:11 +00:00
|
|
|
m_fgGridSizer->Add( bSizerMat, 1, wxRIGHT|wxEXPAND, 4 );
|
2019-09-09 18:30:43 +00:00
|
|
|
wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY );
|
|
|
|
|
|
|
|
if( aMaterialName )
|
2019-09-27 15:12:24 +00:00
|
|
|
{
|
|
|
|
if( IsPrmSpecified( *aMaterialName ) )
|
2021-12-19 09:09:27 +00:00
|
|
|
textCtrl->ChangeValue( *aMaterialName );
|
2019-09-27 15:12:24 +00:00
|
|
|
else
|
2021-12-19 09:09:27 +00:00
|
|
|
textCtrl->ChangeValue( wxGetTranslation( NotSpecifiedPrm() ) );
|
2019-09-27 15:12:24 +00:00
|
|
|
}
|
2019-09-09 18:30:43 +00:00
|
|
|
|
|
|
|
textCtrl->SetMinSize( m_numericTextCtrlSize );
|
2021-02-24 21:20:11 +00:00
|
|
|
bSizerMat->Add( textCtrl, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
|
2019-09-09 18:30:43 +00:00
|
|
|
|
2021-02-24 21:20:11 +00:00
|
|
|
wxButton* m_buttonMat = new wxButton( m_scGridWin, aId, _( "..." ), wxDefaultPosition,
|
2021-07-19 23:56:05 +00:00
|
|
|
wxDefaultSize, wxBU_EXACTFIT );
|
2019-09-09 18:30:43 +00:00
|
|
|
bSizerMat->Add( m_buttonMat, 0, wxALIGN_CENTER_VERTICAL, 2 );
|
|
|
|
|
|
|
|
m_buttonMat->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
|
2020-11-11 17:02:11 +00:00
|
|
|
wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onMaterialChange ),
|
2021-07-19 23:56:05 +00:00
|
|
|
nullptr, this );
|
2019-09-09 18:30:43 +00:00
|
|
|
m_controlItemsList.push_back( m_buttonMat );
|
|
|
|
|
|
|
|
aUiRowItem.m_MaterialCtrl = textCtrl;
|
|
|
|
aUiRowItem.m_MaterialButt = m_buttonMat;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-06 18:59:13 +00:00
|
|
|
wxControl* PANEL_SETUP_BOARD_STACKUP::addSpacer()
|
|
|
|
{
|
|
|
|
wxStaticText* emptyText = new wxStaticText( m_scGridWin, wxID_ANY, wxEmptyString );
|
2021-02-24 21:20:11 +00:00
|
|
|
m_fgGridSizer->Add( emptyText, 0, wxALIGN_CENTER_VERTICAL );
|
2019-09-06 18:59:13 +00:00
|
|
|
return emptyText;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
BOARD_STACKUP_ROW_UI_ITEM PANEL_SETUP_BOARD_STACKUP::createRowData( int aRow,
|
2021-02-24 21:20:11 +00:00
|
|
|
BOARD_STACKUP_ITEM* aStackupItem,
|
|
|
|
int aSublayerIdx )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
wxASSERT( aStackupItem );
|
|
|
|
wxASSERT( aSublayerIdx >= 0 && aSublayerIdx < aStackupItem->GetSublayersCount() );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
BOARD_STACKUP_ROW_UI_ITEM ui_row_item( aStackupItem, aSublayerIdx );
|
|
|
|
BOARD_STACKUP_ITEM* item = aStackupItem;
|
|
|
|
int row = aRow;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
// Add color swatch icon. The color will be updated later,
|
|
|
|
// when all widgets are initialized
|
|
|
|
wxStaticBitmap* bitmap = new wxStaticBitmap( m_scGridWin, wxID_ANY, wxNullBitmap );
|
2021-02-24 21:20:11 +00:00
|
|
|
m_fgGridSizer->Add( bitmap, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 4 );
|
2019-11-14 15:26:05 +00:00
|
|
|
ui_row_item.m_Icon = bitmap;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
ui_row_item.m_isEnabled = true;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
|
|
|
|
{
|
|
|
|
wxString lname = item->FormatDielectricLayerName();
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->GetSublayersCount() > 1 )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2022-02-04 22:44:59 +00:00
|
|
|
lname << wxT( " (" ) << aSublayerIdx+1 << wxT( "/" )
|
|
|
|
<< item->GetSublayersCount() << wxT( ")" );
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
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 (aSublayerIdx = 0), and is common to all sublayers
|
|
|
|
if( aSublayerIdx == 0 )
|
|
|
|
{
|
2019-07-27 19:09:43 +00:00
|
|
|
wxChoice* choice = new wxChoice( m_scGridWin, wxID_ANY, wxDefaultPosition,
|
|
|
|
wxDefaultSize, m_core_prepreg_choice );
|
2019-11-11 18:34:48 +00:00
|
|
|
choice->SetSelection( item->GetTypeName() == KEY_CORE ? 0 : 1 );
|
2021-02-24 21:20:11 +00:00
|
|
|
m_fgGridSizer->Add( choice, 1, wxEXPAND|wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
ui_row_item.m_LayerTypeCtrl = choice;
|
|
|
|
}
|
|
|
|
else
|
2021-02-24 21:20:11 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
ui_row_item.m_LayerTypeCtrl = addSpacer();
|
2021-02-24 21:20:11 +00:00
|
|
|
}
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
item->SetLayerName( m_board->GetLayerName( item->GetBrdLayerId() ) );
|
|
|
|
wxStaticText* st_text = new wxStaticText( m_scGridWin, wxID_ANY, item->GetLayerName() );
|
2021-02-24 21:20:11 +00:00
|
|
|
m_fgGridSizer->Add( st_text, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 1 );
|
2019-11-14 15:26:05 +00:00
|
|
|
st_text->Show( true );
|
|
|
|
ui_row_item.m_LayerName = st_text;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
wxString lname;
|
2019-09-06 18:59:13 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->GetTypeName() == KEY_COPPER )
|
|
|
|
lname = _( "Copper" );
|
|
|
|
else
|
|
|
|
lname = wxGetTranslation( item->GetTypeName() );
|
2019-09-06 18:59:13 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
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;
|
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->IsMaterialEditable() )
|
|
|
|
{
|
|
|
|
wxString matName = item->GetMaterial( aSublayerIdx );
|
|
|
|
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 );
|
2022-09-19 09:25:20 +00:00
|
|
|
textCtrl->ChangeValue( m_frame->StringFromValue( item->GetThickness( aSublayerIdx ), true ) );
|
2019-11-14 15:26:05 +00:00
|
|
|
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 ),
|
2021-07-19 23:56:05 +00:00
|
|
|
nullptr, this );
|
2019-11-14 15:26:05 +00:00
|
|
|
ui_row_item.m_ThicknessCtrl = textCtrl;
|
|
|
|
|
|
|
|
if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
wxCheckBox* cb_box = new wxCheckBox( m_scGridWin, ID_ITEM_THICKNESS_LOCKED+row,
|
|
|
|
wxEmptyString );
|
|
|
|
cb_box->SetValue( item->IsThicknessLocked( aSublayerIdx ) );
|
|
|
|
m_fgGridSizer->Add( cb_box, 0, wxALIGN_CENTER_VERTICAL, 2 );
|
|
|
|
ui_row_item.m_ThicknessLockCtrl = cb_box;
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
2019-09-06 18:59:13 +00:00
|
|
|
else
|
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
ui_row_item.m_ThicknessLockCtrl = addSpacer();
|
2019-09-06 18:59:13 +00:00
|
|
|
}
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ui_row_item.m_ThicknessCtrl = addSpacer();
|
|
|
|
ui_row_item.m_ThicknessLockCtrl = addSpacer();
|
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->IsColorEditable() )
|
|
|
|
{
|
2022-12-01 15:01:26 +00:00
|
|
|
if( item->GetColor( aSublayerIdx ).StartsWith( wxT( "#" ) ) ) // User defined color
|
2021-12-14 00:03:19 +00:00
|
|
|
{
|
2022-12-01 15:01:26 +00:00
|
|
|
ui_row_item.m_UserColor = COLOR4D( item->GetColor( aSublayerIdx ) ).ToColour();
|
2021-12-14 00:03:19 +00:00
|
|
|
}
|
2021-08-23 18:58:18 +00:00
|
|
|
else
|
|
|
|
ui_row_item.m_UserColor = GetDefaultUserColor( item->GetType() );
|
2021-04-08 23:51:30 +00:00
|
|
|
|
2021-08-23 18:58:18 +00:00
|
|
|
wxBitmapComboBox* bm_combo = createColorBox( item, row );
|
2021-08-16 18:57:15 +00:00
|
|
|
int selected = 0; // The "not specified" item
|
|
|
|
|
2021-08-23 18:58:18 +00:00
|
|
|
m_fgGridSizer->Add( bm_combo, 1, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL|wxEXPAND, 2 );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2022-12-01 15:01:26 +00:00
|
|
|
if( item->GetColor( aSublayerIdx ).StartsWith( wxT( "#" ) ) )
|
2019-11-14 15:26:05 +00:00
|
|
|
{
|
2021-08-16 18:57:15 +00:00
|
|
|
selected = GetColorUserDefinedListIdx( item->GetType() );
|
2022-12-01 15:01:26 +00:00
|
|
|
bm_combo->SetString( selected, item->GetColor( aSublayerIdx ) );
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-08-23 18:58:18 +00:00
|
|
|
// Note: don't use bm_combo->FindString() because the combo strings are translated.
|
2022-12-06 16:01:25 +00:00
|
|
|
for( size_t ii = 0; ii < GetStandardColors( item->GetType() ).size(); ii++ )
|
2019-11-14 15:26:05 +00:00
|
|
|
{
|
2022-12-01 15:01:26 +00:00
|
|
|
if( GetStandardColorName( item->GetType(), ii ) == item->GetColor( aSublayerIdx ) )
|
2019-11-14 15:26:05 +00:00
|
|
|
{
|
2021-08-16 18:57:15 +00:00
|
|
|
selected = ii;
|
2019-11-14 15:26:05 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
2021-08-16 18:57:15 +00:00
|
|
|
bm_combo->SetSelection( selected );
|
2019-11-14 15:26:05 +00:00
|
|
|
ui_row_item.m_ColorCtrl = bm_combo;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ui_row_item.m_ColorCtrl = addSpacer();
|
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->HasEpsilonRValue() )
|
|
|
|
{
|
2022-09-17 03:35:16 +00:00
|
|
|
wxString txt = UIDouble2Str( item->GetEpsilonR( aSublayerIdx ) );
|
2019-11-14 15:26:05 +00:00
|
|
|
wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString,
|
|
|
|
wxDefaultPosition, m_numericFieldsSize );
|
2021-12-19 09:09:27 +00:00
|
|
|
textCtrl->ChangeValue( txt );
|
2019-11-14 15:26:05 +00:00
|
|
|
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() )
|
|
|
|
{
|
2022-09-17 03:35:16 +00:00
|
|
|
wxString txt = UIDouble2Str( item->GetLossTangent( aSublayerIdx ) );;
|
2019-11-14 15:26:05 +00:00
|
|
|
wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString,
|
|
|
|
wxDefaultPosition, m_numericFieldsSize );
|
2021-12-19 09:09:27 +00:00
|
|
|
textCtrl->ChangeValue( txt );
|
2019-11-14 15:26:05 +00:00
|
|
|
m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
|
|
|
|
ui_row_item.m_LossTgCtrl = textCtrl;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ui_row_item.m_LossTgCtrl = addSpacer();
|
|
|
|
}
|
|
|
|
|
|
|
|
return ui_row_item;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PANEL_SETUP_BOARD_STACKUP::rebuildLayerStackPanel()
|
|
|
|
{
|
2021-06-27 08:38:11 +00:00
|
|
|
wxWindowUpdateLocker locker( m_scGridWin );
|
|
|
|
m_scGridWin->Hide();
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
// 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 )
|
|
|
|
{
|
2019-11-25 08:53:14 +00:00
|
|
|
// This remove and delete the current ui_item.m_MaterialCtrl sizer
|
|
|
|
ui_item.m_MaterialCtrl->SetSizer( nullptr );
|
|
|
|
|
|
|
|
// Delete other widgets
|
2019-11-14 15:26:05 +00:00
|
|
|
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:
|
2019-11-25 08:53:14 +00:00
|
|
|
m_scGridWin->SetSizer( nullptr ); // This remove and delete the current m_fgGridSizer
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
m_fgGridSizer = new wxFlexGridSizer( 0, 9, 0, 2 );
|
|
|
|
m_fgGridSizer->SetFlexibleDirection( wxHORIZONTAL );
|
|
|
|
m_fgGridSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
|
2021-02-24 21:20:11 +00:00
|
|
|
m_fgGridSizer->SetHGap( 6 );
|
2019-11-14 15:26:05 +00:00
|
|
|
m_scGridWin->SetSizer( m_fgGridSizer );
|
|
|
|
|
|
|
|
// Re-add "old" title items:
|
2021-02-24 21:20:11 +00:00
|
|
|
const int sizer_flags = wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_CENTER_HORIZONTAL;
|
2019-11-25 08:53:14 +00:00
|
|
|
m_fgGridSizer->Add( m_staticTextLayer, 0, sizer_flags, 2 );
|
|
|
|
m_fgGridSizer->Add( m_staticTextType, 0, sizer_flags, 2 );
|
2021-02-24 21:20:11 +00:00
|
|
|
m_fgGridSizer->Add( m_staticTextLayerId, 0, sizer_flags, 5 );
|
2019-11-25 08:53:14 +00:00
|
|
|
m_fgGridSizer->Add( m_staticTextMaterial, 0, sizer_flags, 2 );
|
|
|
|
m_fgGridSizer->Add( m_staticTextThickness, 0, sizer_flags, 2 );
|
2021-02-24 21:20:11 +00:00
|
|
|
m_fgGridSizer->Add( m_bitmapLockThickness, 0, sizer_flags, 1 );
|
2019-11-25 08:53:14 +00:00
|
|
|
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 );
|
2019-11-14 15:26:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
// Now, rebuild the widget list from the new m_stackup items:
|
|
|
|
buildLayerStackPanel( false );
|
|
|
|
|
|
|
|
// Now enable/disable stackup items, according to the m_enabledLayers config
|
|
|
|
showOnlyActiveLayers();
|
|
|
|
|
|
|
|
m_scGridWin->Layout();
|
2021-06-27 08:38:11 +00:00
|
|
|
m_scGridWin->Show();
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PANEL_SETUP_BOARD_STACKUP::buildLayerStackPanel( bool aCreatedInitialStackup )
|
|
|
|
{
|
|
|
|
// Build a full stackup for the dialog, with a active copper layer count
|
|
|
|
// = current board layer count to calculate a reasonable default stackup:
|
|
|
|
if( aCreatedInitialStackup )
|
|
|
|
{
|
|
|
|
// Creates a full BOARD_STACKUP with 32 copper layers.
|
2020-11-11 17:02:11 +00:00
|
|
|
// extra layers will be hidden later.
|
2019-11-14 15:26:05 +00:00
|
|
|
// 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() );
|
2020-12-20 18:44:13 +00:00
|
|
|
const BOARD_STACKUP& brd_stackup = m_brdSettings->GetStackupDescriptor();
|
2019-11-14 15:26:05 +00:00
|
|
|
|
|
|
|
// 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() )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->GetBrdLayerId() != UNDEFINED_LAYER )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->GetBrdLayerId() == board_item->GetBrdLayerId() )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
*item = *board_item;
|
2019-07-27 19:09:43 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-11-14 15:26:05 +00:00
|
|
|
else // dielectric layer: see m_DielectricLayerId for identification
|
|
|
|
{
|
|
|
|
// Compare dielectric layer with dielectric layer
|
|
|
|
if( board_item->GetBrdLayerId() != UNDEFINED_LAYER )
|
|
|
|
continue;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( item->GetDielectricLayerId() == board_item->GetDielectricLayerId() )
|
|
|
|
{
|
|
|
|
*item = *board_item;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-06 18:59:13 +00:00
|
|
|
}
|
2019-11-14 15:26:05 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
int row = 0;
|
|
|
|
|
|
|
|
for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
|
|
|
|
{
|
|
|
|
for( int sub_idx = 0; sub_idx < item->GetSublayersCount(); sub_idx++ )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
BOARD_STACKUP_ROW_UI_ITEM ui_row_item = createRowData( row, item, sub_idx );
|
|
|
|
m_rowUiItemsList.emplace_back( ui_row_item );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
row++;
|
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
updateIconColor();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-06 09:26:27 +00:00
|
|
|
// Transfer current UI settings to m_stackup but not to the board
|
|
|
|
bool PANEL_SETUP_BOARD_STACKUP::transferDataFromUIToStackup()
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
|
|
|
wxString error_msg;
|
|
|
|
bool success = true;
|
|
|
|
double value;
|
2019-11-14 15:26:05 +00:00
|
|
|
int row = 0;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
for( BOARD_STACKUP_ROW_UI_ITEM& ui_item : m_rowUiItemsList )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
|
|
|
// Skip stackup items useless for the current board
|
2019-11-14 15:26:05 +00:00
|
|
|
if( !ui_item.m_isEnabled )
|
2020-06-28 01:42:09 +00:00
|
|
|
{
|
|
|
|
row++;
|
2019-07-27 19:09:43 +00:00
|
|
|
continue;
|
2020-06-28 01:42:09 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
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() );
|
2019-09-06 09:26:27 +00:00
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
if( item->HasEpsilonRValue() )
|
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_EpsilonCtrl );
|
2022-10-05 16:42:03 +00:00
|
|
|
wxString txt = textCtrl->GetValue();
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-09-09 18:30:43 +00:00
|
|
|
if( txt.ToDouble( &value ) && value >= 0.0 )
|
2019-11-14 15:26:05 +00:00
|
|
|
item->SetEpsilonR( value, sub_item );
|
2019-09-09 18:30:43 +00:00
|
|
|
else if( txt.ToCDouble( &value ) && value >= 0.0 )
|
2019-11-14 15:26:05 +00:00
|
|
|
item->SetEpsilonR( value, sub_item );
|
2019-07-27 19:09:43 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
success = false;
|
2021-07-19 23:56:05 +00:00
|
|
|
error_msg << _( "Incorrect value for Epsilon R (Epsilon R must be positive or "
|
|
|
|
"null if not used)" );
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( item->HasLossTangentValue() )
|
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_LossTgCtrl );
|
2022-10-05 16:42:03 +00:00
|
|
|
wxString txt = textCtrl->GetValue();
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-09-09 18:30:43 +00:00
|
|
|
if( txt.ToDouble( &value ) && value >= 0.0 )
|
2019-11-14 15:26:05 +00:00
|
|
|
item->SetLossTangent( value, sub_item );
|
2019-09-09 18:30:43 +00:00
|
|
|
else if( txt.ToCDouble( &value ) && value >= 0.0 )
|
2019-11-14 15:26:05 +00:00
|
|
|
item->SetLossTangent( value, sub_item );
|
2019-07-27 19:09:43 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
success = false;
|
2020-11-11 17:02:11 +00:00
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
if( !error_msg.IsEmpty() )
|
2022-02-04 22:44:59 +00:00
|
|
|
error_msg << wxT( "\n" );
|
2020-11-11 17:02:11 +00:00
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
error_msg << _( "Incorrect value for Loss tg (Loss tg must be positive or null "
|
|
|
|
"if not used)" );
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( item->IsMaterialEditable() )
|
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_MaterialCtrl );
|
|
|
|
item->SetMaterial( textCtrl->GetValue(), sub_item );
|
2019-09-27 15:12:24 +00:00
|
|
|
|
|
|
|
// Ensure the not specified mat name is the keyword, not its translation
|
|
|
|
// to avoid any issue is the language setting changes
|
2019-11-14 15:26:05 +00:00
|
|
|
if( !IsPrmSpecified( item->GetMaterial( sub_item ) ) )
|
|
|
|
item->SetMaterial( NotSpecifiedPrm(), sub_item );
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
2019-11-11 18:34:48 +00:00
|
|
|
if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
// Choice is Core or Prepreg. Sublayers have no choice:
|
|
|
|
wxChoice* choice = dynamic_cast<wxChoice*>( ui_item.m_LayerTypeCtrl );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( choice )
|
|
|
|
{
|
|
|
|
int idx = choice->GetSelection();
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( idx == 0 )
|
|
|
|
item->SetTypeName( KEY_CORE );
|
|
|
|
else
|
|
|
|
item->SetTypeName( KEY_PREPREG );
|
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if( item->IsThicknessEditable() )
|
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( ui_item.m_ThicknessCtrl );
|
2022-10-05 16:42:03 +00:00
|
|
|
int new_thickness = m_frame->ValueFromString( textCtrl->GetValue() );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
item->SetThickness( new_thickness, sub_item );
|
|
|
|
|
|
|
|
if( new_thickness < 0 )
|
|
|
|
{
|
|
|
|
success = false;
|
|
|
|
|
|
|
|
if( !error_msg.IsEmpty() )
|
2022-02-04 22:44:59 +00:00
|
|
|
error_msg << wxT( "\n" );
|
2019-11-14 15:26:05 +00:00
|
|
|
|
|
|
|
error_msg << _( "A layer thickness is < 0. Fix it" );
|
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-11 18:34:48 +00:00
|
|
|
if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
|
|
|
// Dielectric thickness layer can have a locked thickness:
|
2021-08-16 18:57:15 +00:00
|
|
|
wxCheckBox* cb_box = static_cast<wxCheckBox*>( ui_item.m_ThicknessLockCtrl );
|
2019-11-14 15:26:05 +00:00
|
|
|
item->SetThicknessLocked( cb_box && cb_box->GetValue(), sub_item );
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-01 15:01:26 +00:00
|
|
|
if( item->IsColorEditable() )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2020-06-28 01:42:09 +00:00
|
|
|
wxBitmapComboBox* choice = dynamic_cast<wxBitmapComboBox*>( ui_item.m_ColorCtrl );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2020-07-19 00:47:03 +00:00
|
|
|
if( choice )
|
2020-06-28 01:42:09 +00:00
|
|
|
{
|
2020-07-19 00:47:03 +00:00
|
|
|
int idx = choice->GetSelection();
|
|
|
|
|
2021-08-16 18:57:15 +00:00
|
|
|
if( IsCustomColorIdx( item->GetType(), idx ) )
|
2022-12-01 15:01:26 +00:00
|
|
|
item->SetColor( ui_item.m_UserColor.ToHexString(), sub_item );
|
2020-07-19 00:47:03 +00:00
|
|
|
else
|
2022-12-01 15:01:26 +00:00
|
|
|
item->SetColor( GetStandardColorName( item->GetType(), idx ), sub_item );
|
2020-06-28 01:42:09 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
2019-11-14 15:26:05 +00:00
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
row++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !success )
|
|
|
|
{
|
|
|
|
wxMessageBox( error_msg, _( "Errors" ) );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-02-24 21:20:11 +00:00
|
|
|
m_stackup.m_HasDielectricConstrains = m_impedanceControlled->GetValue();
|
2019-09-06 09:26:27 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PANEL_SETUP_BOARD_STACKUP::TransferDataFromWindow()
|
|
|
|
{
|
|
|
|
if( !transferDataFromUIToStackup() )
|
|
|
|
return false;
|
|
|
|
|
2021-02-24 02:31:12 +00:00
|
|
|
// 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.
|
|
|
|
|
2019-09-06 09:26:27 +00:00
|
|
|
BOARD_STACKUP& brd_stackup = m_brdSettings->GetStackupDescriptor();
|
|
|
|
|
2020-10-13 22:05:15 +00:00
|
|
|
STRING_FORMATTER old_stackup;
|
2021-04-12 07:57:40 +00:00
|
|
|
|
|
|
|
// FormatBoardStackup() (using FormatInternalUnits()) expects a "C" locale
|
|
|
|
// to execute some tests. So switch to the suitable locale
|
|
|
|
LOCALE_IO dummy;
|
2020-10-13 22:05:15 +00:00
|
|
|
brd_stackup.FormatBoardStackup( &old_stackup, m_board, 0 );
|
|
|
|
|
2019-09-06 09:26:27 +00:00
|
|
|
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;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
// copy enabled items to the new board stackup
|
|
|
|
brd_stackup.RemoveAll();
|
|
|
|
|
2020-10-13 22:05:15 +00:00
|
|
|
for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-11 18:34:48 +00:00
|
|
|
if( item->IsEnabled() )
|
2019-11-14 15:26:05 +00:00
|
|
|
brd_stackup.Add( new BOARD_STACKUP_ITEM( *item ) );
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
2020-10-13 22:05:15 +00:00
|
|
|
STRING_FORMATTER new_stackup;
|
|
|
|
brd_stackup.FormatBoardStackup( &new_stackup, m_board, 0 );
|
|
|
|
|
|
|
|
bool modified = old_stackup.GetString() != new_stackup.GetString();
|
2021-02-11 19:25:49 +00:00
|
|
|
int thickness = brd_stackup.BuildBoardThicknessFromStackup();
|
2020-10-13 22:05:15 +00:00
|
|
|
|
2021-02-11 19:25:49 +00:00
|
|
|
if( m_brdSettings->GetBoardThickness() != thickness )
|
2020-10-13 22:05:15 +00:00
|
|
|
{
|
2021-02-11 19:25:49 +00:00
|
|
|
m_brdSettings->SetBoardThickness( thickness );
|
2020-10-13 22:05:15 +00:00
|
|
|
modified = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !m_brdSettings->m_HasStackup )
|
|
|
|
{
|
|
|
|
m_brdSettings->m_HasStackup = true;
|
|
|
|
modified = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( modified )
|
|
|
|
m_frame->OnModify();
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PANEL_SETUP_BOARD_STACKUP::ImportSettingsFrom( BOARD* aBoard )
|
|
|
|
{
|
|
|
|
BOARD* savedBrd = m_board;
|
2022-10-16 17:28:58 +00:00
|
|
|
m_board = aBoard;
|
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
BOARD_DESIGN_SETTINGS* savedSettings = m_brdSettings;
|
|
|
|
m_brdSettings = &aBoard->GetDesignSettings();
|
|
|
|
|
2022-10-16 17:28:58 +00:00
|
|
|
m_enabledLayers = m_board->GetEnabledLayers() & BOARD_STACKUP::StackupAllowedBrdLayers();
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
rebuildLayerStackPanel();
|
2022-10-16 17:28:58 +00:00
|
|
|
synchronizeWithBoard( true );
|
2021-04-08 19:53:33 +00:00
|
|
|
computeBoardThickness();
|
2021-10-11 18:46:02 +00:00
|
|
|
|
|
|
|
m_brdSettings = savedSettings;
|
|
|
|
m_board = savedBrd;
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PANEL_SETUP_BOARD_STACKUP::OnLayersOptionsChanged( LSET aNewLayerSet )
|
|
|
|
{
|
2023-01-12 14:16:11 +00:00
|
|
|
// Can be called spuriously from events before the layers page is even created
|
|
|
|
if( !m_panelLayers->IsInitialized() )
|
|
|
|
return;
|
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
// 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
|
2019-11-14 15:26:05 +00:00
|
|
|
// Note: the number of layer can change, but not the layers properties
|
2019-07-27 19:09:43 +00:00
|
|
|
LSET layersList = m_panelLayers->GetUILayerMask() & BOARD_STACKUP::StackupAllowedBrdLayers();
|
|
|
|
|
|
|
|
if( m_enabledLayers != layersList )
|
|
|
|
{
|
|
|
|
m_enabledLayers = layersList;
|
|
|
|
|
2021-12-15 10:01:46 +00:00
|
|
|
synchronizeWithBoard( false );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
Layout();
|
|
|
|
Refresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PANEL_SETUP_BOARD_STACKUP::onColorSelected( wxCommandEvent& event )
|
|
|
|
{
|
2021-08-16 18:57:15 +00:00
|
|
|
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;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2022-12-06 16:01:25 +00:00
|
|
|
if( IsCustomColorIdx( item->GetType(), idx ) ) // user color is the last option in list
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2021-08-23 18:58:18 +00:00
|
|
|
DIALOG_COLOR_PICKER dlg( this, m_rowUiItemsList[row].m_UserColor, true, nullptr,
|
|
|
|
GetDefaultUserColor( m_rowUiItemsList[row].m_Item->GetType() ) );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2021-08-23 18:58:18 +00:00
|
|
|
#ifdef __WXGTK__
|
2021-04-08 23:51:30 +00:00
|
|
|
// Give a time-slice to close the menu before opening the dialog.
|
|
|
|
// (Only matters on some versions of GTK.)
|
|
|
|
wxSafeYield();
|
2021-08-23 18:58:18 +00:00
|
|
|
#endif
|
2021-04-08 23:51:30 +00:00
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
if( dlg.ShowModal() == wxID_OK )
|
|
|
|
{
|
|
|
|
wxBitmapComboBox* combo = static_cast<wxBitmapComboBox*>( FindWindowById( item_id ) );
|
2021-08-16 18:57:15 +00:00
|
|
|
COLOR4D color = dlg.GetColor();
|
2021-04-08 23:51:30 +00:00
|
|
|
|
|
|
|
m_rowUiItemsList[row].m_UserColor = color;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2021-08-16 18:57:15 +00:00
|
|
|
combo->SetString( idx, color.ToHexString() );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
|
2021-08-16 18:57:15 +00:00
|
|
|
LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ), color );
|
|
|
|
combo->SetItemBitmap( combo->GetCount() - 1, layerbmp );
|
2021-08-23 18:58:18 +00:00
|
|
|
|
|
|
|
combo->SetSelection( idx );
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateIconColor( row );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PANEL_SETUP_BOARD_STACKUP::onMaterialChange( wxCommandEvent& event )
|
|
|
|
{
|
2019-09-24 12:33:28 +00:00
|
|
|
// Ensure m_materialList contains all materials already in use in stackup list
|
2019-09-09 18:30:43 +00:00
|
|
|
// and add it is missing
|
|
|
|
if( !transferDataFromUIToStackup() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
|
|
|
|
{
|
2019-09-24 12:33:28 +00:00
|
|
|
DIELECTRIC_SUBSTRATE_LIST* mat_list = nullptr;
|
|
|
|
|
2019-11-11 18:34:48 +00:00
|
|
|
if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
|
2019-09-24 12:33:28 +00:00
|
|
|
mat_list = &m_delectricMatList;
|
2019-11-11 18:34:48 +00:00
|
|
|
else if( item->GetType() == BS_ITEM_TYPE_SOLDERMASK )
|
2019-09-24 12:33:28 +00:00
|
|
|
mat_list = &m_solderMaskMatList;
|
2019-11-11 18:34:48 +00:00
|
|
|
else if( item->GetType() == BS_ITEM_TYPE_SILKSCREEN )
|
2019-09-24 12:33:28 +00:00
|
|
|
mat_list = &m_silkscreenMatList;
|
2019-09-09 18:30:43 +00:00
|
|
|
|
2019-09-24 12:33:28 +00:00
|
|
|
else
|
|
|
|
continue;
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
for( int ii = 0; ii < item->GetSublayersCount(); ii++ )
|
2019-09-24 12:33:28 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
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 );
|
|
|
|
}
|
2019-09-09 18:30:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-24 12:33:28 +00:00
|
|
|
int row = event.GetId() - ID_ITEM_MATERIAL;
|
2019-11-14 15:26:05 +00:00
|
|
|
BOARD_STACKUP_ITEM* item = m_rowUiItemsList[row].m_Item;
|
|
|
|
int sub_item = m_rowUiItemsList[row].m_SubItem;
|
2019-09-24 12:33:28 +00:00
|
|
|
DIELECTRIC_SUBSTRATE_LIST* item_mat_list = nullptr;
|
|
|
|
|
2019-11-11 18:34:48 +00:00
|
|
|
switch( item->GetType() )
|
2019-09-24 12:33:28 +00:00
|
|
|
{
|
2021-02-24 21:20:11 +00:00
|
|
|
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;
|
2019-09-24 12:33:28 +00:00
|
|
|
}
|
|
|
|
|
2021-02-25 03:55:11 +00:00
|
|
|
wxCHECK( item_mat_list, /* void */ );
|
|
|
|
|
2019-09-24 12:33:28 +00:00
|
|
|
DIALOG_DIELECTRIC_MATERIAL dlg( this, *item_mat_list );
|
2019-09-09 18:30:43 +00:00
|
|
|
|
|
|
|
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
|
2019-11-14 15:26:05 +00:00
|
|
|
item->SetMaterial( substrate.m_Name, sub_item );
|
|
|
|
item->SetEpsilonR( substrate.m_EpsilonR, sub_item );
|
|
|
|
item->SetLossTangent( substrate.m_LossTangent, sub_item );
|
2019-09-09 18:30:43 +00:00
|
|
|
|
2021-08-16 18:57:15 +00:00
|
|
|
wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_MaterialCtrl );
|
2021-12-19 09:09:27 +00:00
|
|
|
textCtrl->ChangeValue( item->GetMaterial( sub_item ) );
|
2019-09-09 14:45:21 +00:00
|
|
|
|
2021-08-16 18:57:15 +00:00
|
|
|
if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC
|
2022-12-01 15:01:26 +00:00
|
|
|
&& !item->GetColor( sub_item ).StartsWith( "#" ) /* User defined color */ )
|
2021-08-16 18:57:15 +00:00
|
|
|
{
|
|
|
|
if( substrate.m_Name.IsSameAs( "PTFE" )
|
|
|
|
|| substrate.m_Name.IsSameAs( "Teflon" ) )
|
|
|
|
{
|
2022-12-01 15:01:26 +00:00
|
|
|
item->SetColor( "PTFE natural", sub_item );
|
2021-08-16 18:57:15 +00:00
|
|
|
}
|
|
|
|
else if( substrate.m_Name.IsSameAs( "Polyimide" )
|
|
|
|
|| substrate.m_Name.IsSameAs( "Kapton" ) )
|
|
|
|
{
|
2022-12-01 15:01:26 +00:00
|
|
|
item->SetColor( "Polyimide", sub_item );
|
2021-08-16 18:57:15 +00:00
|
|
|
}
|
|
|
|
else if( substrate.m_Name.IsSameAs( "Al" ) )
|
|
|
|
{
|
2022-12-01 15:01:26 +00:00
|
|
|
item->SetColor( "Aluminum", sub_item );
|
2021-08-16 18:57:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-12-01 15:01:26 +00:00
|
|
|
item->SetColor( "FR4 natural", sub_item );
|
2021-08-16 18:57:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wxBitmapComboBox* picker = static_cast<wxBitmapComboBox*>( m_rowUiItemsList[row].m_ColorCtrl );
|
|
|
|
|
2022-12-06 16:01:25 +00:00
|
|
|
for( size_t ii = 0; ii < GetStandardColors( item->GetType() ).size(); ii++ )
|
2021-08-16 18:57:15 +00:00
|
|
|
{
|
2022-12-01 15:01:26 +00:00
|
|
|
if( GetStandardColorName( item->GetType(), ii ) == item->GetColor( sub_item ) )
|
2021-08-16 18:57:15 +00:00
|
|
|
{
|
|
|
|
picker->SetSelection( ii );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-26 18:48:28 +00:00
|
|
|
// some layers have a material choice but not EpsilonR ctrl
|
|
|
|
if( item->HasEpsilonRValue() )
|
|
|
|
{
|
|
|
|
textCtrl = dynamic_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_EpsilonCtrl );
|
|
|
|
|
|
|
|
if( textCtrl )
|
2021-12-19 09:09:27 +00:00
|
|
|
textCtrl->ChangeValue( item->FormatEpsilonR( sub_item ) );
|
2019-09-26 18:48:28 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2019-09-26 18:48:28 +00:00
|
|
|
// some layers have a material choice but not loss tg ctrl
|
|
|
|
if( item->HasLossTangentValue() )
|
|
|
|
{
|
|
|
|
textCtrl = dynamic_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_LossTgCtrl );
|
|
|
|
|
|
|
|
if( textCtrl )
|
2021-12-19 09:09:27 +00:00
|
|
|
textCtrl->ChangeValue( item->FormatLossTangent( sub_item ) );
|
2019-09-26 18:48:28 +00:00
|
|
|
}
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 );
|
2019-11-14 15:26:05 +00:00
|
|
|
int idx = GetSublayerId( row );
|
|
|
|
|
2022-10-05 16:42:03 +00:00
|
|
|
item->SetThickness( m_frame->ValueFromString( value ), idx );
|
2021-04-08 19:53:33 +00:00
|
|
|
|
|
|
|
computeBoardThickness();
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
BOARD_STACKUP_ITEM* PANEL_SETUP_BOARD_STACKUP::GetStackupItem( int aRow )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
return m_rowUiItemsList[aRow].m_Item;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PANEL_SETUP_BOARD_STACKUP::GetSublayerId( int aRow )
|
|
|
|
{
|
|
|
|
return m_rowUiItemsList[aRow].m_SubItem;
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wxColor PANEL_SETUP_BOARD_STACKUP::getColorIconItem( int aRow )
|
|
|
|
{
|
2019-11-14 15:26:05 +00:00
|
|
|
BOARD_STACKUP_ITEM* st_item = dynamic_cast<BOARD_STACKUP_ITEM*>( GetStackupItem( aRow ) );
|
|
|
|
|
|
|
|
wxASSERT( st_item );
|
2019-07-27 19:09:43 +00:00
|
|
|
wxColor color;
|
|
|
|
|
2019-11-14 15:26:05 +00:00
|
|
|
if( ! st_item )
|
|
|
|
return color;
|
|
|
|
|
|
|
|
switch( st_item->GetType() )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2020-08-05 14:49:07 +00:00
|
|
|
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;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2020-08-05 14:49:07 +00:00
|
|
|
default:
|
|
|
|
case BS_ITEM_TYPE_UNDEFINED:
|
2022-02-04 22:44:59 +00:00
|
|
|
wxFAIL_MSG( wxT( "PANEL_SETUP_BOARD_STACKUP::getColorIconItem: unrecognized item type" ) );
|
2019-07-27 19:09:43 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-02-04 22:44:59 +00:00
|
|
|
wxASSERT_MSG( color.IsOk(), wxT( "Invalid color in PCB stackup" ) );
|
2021-04-08 23:51:30 +00:00
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
return color;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PANEL_SETUP_BOARD_STACKUP::updateIconColor( int aRow )
|
|
|
|
{
|
2021-12-18 14:13:34 +00:00
|
|
|
// explicit depth important under MSW. We use R,V,B 24 bits/pixel bitmap
|
|
|
|
const int bitmap_depth = 24;
|
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
if( aRow >= 0 )
|
|
|
|
{
|
|
|
|
wxStaticBitmap* st_bitmap = m_rowUiItemsList[aRow].m_Icon;
|
2021-04-08 23:51:30 +00:00
|
|
|
|
2021-12-18 14:13:34 +00:00
|
|
|
wxBitmap bmp( m_colorIconsSize.x, m_colorIconsSize.y / 2, bitmap_depth );
|
2019-07-27 19:09:43 +00:00
|
|
|
drawBitmap( bmp, getColorIconItem( aRow ) );
|
|
|
|
st_bitmap->SetBitmap( bmp );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for( unsigned row = 0; row < m_rowUiItemsList.size(); row++ )
|
|
|
|
{
|
2021-12-18 14:13:34 +00:00
|
|
|
wxBitmap bmp( m_colorIconsSize.x, m_colorIconsSize.y / 2, bitmap_depth );
|
2019-07-27 19:09:43 +00:00
|
|
|
drawBitmap( bmp, getColorIconItem( row ) );
|
|
|
|
m_rowUiItemsList[row].m_Icon->SetBitmap( bmp );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-23 18:58:18 +00:00
|
|
|
wxBitmapComboBox* PANEL_SETUP_BOARD_STACKUP::createColorBox( BOARD_STACKUP_ITEM* aStackupItem,
|
|
|
|
int aRow )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2021-02-24 21:20:11 +00:00
|
|
|
wxBitmapComboBox* combo = new wxBitmapComboBox( m_scGridWin, ID_ITEM_COLOR + aRow,
|
2019-07-27 19:09:43 +00:00
|
|
|
wxEmptyString, wxDefaultPosition,
|
|
|
|
wxDefaultSize, 0, nullptr, wxCB_READONLY );
|
2021-04-08 23:51:30 +00:00
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
// Fills the combo box with choice list + bitmaps
|
2021-08-23 18:58:18 +00:00
|
|
|
BOARD_STACKUP_ITEM_TYPE itemType = aStackupItem ? aStackupItem->GetType()
|
|
|
|
: BS_ITEM_TYPE_SILKSCREEN;
|
2019-09-27 15:12:24 +00:00
|
|
|
|
2022-12-06 16:01:25 +00:00
|
|
|
for( size_t ii = 0; ii < GetStandardColors( itemType ).size(); ii++ )
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
|
|
|
wxString label;
|
2021-08-16 18:57:15 +00:00
|
|
|
COLOR4D curr_color;
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2021-08-25 01:14:59 +00:00
|
|
|
// Defined colors have a name, the user color uses HTML notation ( i.e. #FF000080)
|
2021-08-16 18:57:15 +00:00
|
|
|
if( IsCustomColorIdx( itemType, ii )
|
2022-02-04 22:44:59 +00:00
|
|
|
&& aStackupItem && aStackupItem->GetColor().StartsWith( wxT( "#" ) ) )
|
2021-02-24 21:20:11 +00:00
|
|
|
{
|
2021-08-16 18:57:15 +00:00
|
|
|
label = aStackupItem->GetColor();
|
|
|
|
curr_color = COLOR4D( label );
|
2021-02-24 21:20:11 +00:00
|
|
|
}
|
2021-08-25 01:14:59 +00:00
|
|
|
else
|
2019-07-27 19:09:43 +00:00
|
|
|
{
|
2021-08-16 18:57:15 +00:00
|
|
|
label = wxGetTranslation( GetStandardColorName( itemType, ii ) );
|
|
|
|
curr_color = GetStandardColor( itemType, ii );
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
|
2021-08-16 18:57:15 +00:00
|
|
|
LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ), curr_color );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
combo->Append( label, layerbmp );
|
|
|
|
}
|
|
|
|
|
2021-04-08 23:51:30 +00:00
|
|
|
// 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 );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
// 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 ),
|
2021-07-19 23:56:05 +00:00
|
|
|
nullptr, this );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
2021-08-23 18:58:18 +00:00
|
|
|
combo->Bind( wxEVT_COMBOBOX_DROPDOWN,
|
|
|
|
[combo]( wxCommandEvent& aEvent )
|
|
|
|
{
|
|
|
|
combo->SetString( combo->GetCount() - 1, _( "Custom..." ) );
|
|
|
|
} );
|
|
|
|
|
2019-07-27 19:09:43 +00:00
|
|
|
return combo;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void drawBitmap( wxBitmap& aBitmap, wxColor aColor )
|
|
|
|
{
|
|
|
|
wxNativePixelData data( aBitmap );
|
2020-11-11 17:02:11 +00:00
|
|
|
wxNativePixelData::Iterator p( data );
|
2019-07-27 19:09:43 +00:00
|
|
|
|
|
|
|
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;
|
2021-07-19 23:56:05 +00:00
|
|
|
p.OffsetY( data, 1 );
|
2019-07-27 19:09:43 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-05 14:49:07 +00:00
|
|
|
|
|
|
|
|