/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 CERN
* Copyright (C) 2023-2024 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 3 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, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include <../3d_rendering/opengl/render_3d_opengl.h>
#define RR APPEARANCE_CONTROLS_3D::APPEARANCE_SETTING_3D // Render Row abbreviation to reduce source width
/// Template for object appearance settings
const APPEARANCE_CONTROLS_3D::APPEARANCE_SETTING_3D APPEARANCE_CONTROLS_3D::s_layerSettings[] = {
// text id tooltip
RR( _HKI( "Board Body" ), LAYER_3D_BOARD, _HKI( "Show board body" ) ),
RR( _HKI( "F.Cu" ), LAYER_3D_COPPER_TOP, _HKI( "Show front copper / surface finish color" ) ),
RR( _HKI( "B.Cu" ), LAYER_3D_COPPER_BOTTOM, _HKI( "Show back copper / surface finish color" ) ),
RR( _HKI( "Adhesive" ), LAYER_3D_ADHESIVE, _HKI( "Show adhesive" ) ),
RR( _HKI( "Solder Paste" ), LAYER_3D_SOLDERPASTE, _HKI( "Show solder paste" ) ),
RR( _HKI( "F.Silkscreen" ), LAYER_3D_SILKSCREEN_TOP, _HKI( "Show front silkscreen" ) ),
RR( _HKI( "B.Silkscreen" ), LAYER_3D_SILKSCREEN_BOTTOM, _HKI( "Show back silkscreen" ) ),
RR( _HKI( "F.Mask" ), LAYER_3D_SOLDERMASK_TOP, _HKI( "Show front solder mask" ) ),
RR( _HKI( "B.Mask" ), LAYER_3D_SOLDERMASK_BOTTOM, _HKI( "Show back solder mask" ) ),
RR( _HKI( "User.Drawings" ), LAYER_3D_USER_DRAWINGS, _HKI( "Show user drawings layer" ) ),
RR( _HKI( "User.Comments" ), LAYER_3D_USER_COMMENTS, _HKI( "Show user comments layer" ) ),
RR( _HKI( "User.Eco1" ), LAYER_3D_USER_ECO1, _HKI( "Show user ECO1 layer" ) ),
RR( _HKI( "User.Eco2" ), LAYER_3D_USER_ECO2, _HKI( "Show user ECO2 layer" ) ),
RR(),
RR( _HKI( "Through-hole Models" ), LAYER_3D_TH_MODELS, EDA_3D_ACTIONS::showTHT ),
RR( _HKI( "SMD Models" ), LAYER_3D_SMD_MODELS, EDA_3D_ACTIONS::showSMD ),
RR( _HKI( "Virtual Models" ), LAYER_3D_VIRTUAL_MODELS, EDA_3D_ACTIONS::showVirtual ),
RR( _HKI( "Models not in POS File" ), LAYER_3D_MODELS_NOT_IN_POS, EDA_3D_ACTIONS::showNotInPosFile ),
RR( _HKI( "Models marked DNP" ), LAYER_3D_MODELS_MARKED_DNP, EDA_3D_ACTIONS::showDNP ),
RR( _HKI( "Model Bounding Boxes" ), LAYER_3D_BOUNDING_BOXES, EDA_3D_ACTIONS::showBBoxes ),
RR(),
RR( _HKI( "Values" ), LAYER_FP_VALUES, _HKI( "Show footprint values" ) ),
RR( _HKI( "References" ), LAYER_FP_REFERENCES, _HKI( "Show footprint references" ) ),
RR( _HKI( "Footprint Text" ), LAYER_FP_TEXT, _HKI( "Show all footprint text" ) ),
RR( _HKI( "Off-board Silkscreen" ), LAYER_3D_OFF_BOARD_SILK, _HKI( "Do not clip silk layers to board outline" ) ),
RR(),
RR( _HKI( "3D Axis" ), LAYER_3D_AXES, EDA_3D_ACTIONS::showAxis ),
RR( _HKI( "Background Start" ), LAYER_3D_BACKGROUND_TOP, _HKI( "Background gradient start color" ) ),
RR( _HKI( "Background End" ), LAYER_3D_BACKGROUND_BOTTOM, _HKI( "Background gradient end color" ) ),
};
// The list of IDs that can have colors coming from the board stackup, and cannot be
// modified if use colors from stackup is activated
static std::vector inStackupColors{ LAYER_3D_BOARD, LAYER_3D_COPPER_TOP,
LAYER_3D_COPPER_BOTTOM, LAYER_3D_SOLDERPASTE,
LAYER_3D_SILKSCREEN_TOP, LAYER_3D_SILKSCREEN_BOTTOM,
LAYER_3D_SOLDERMASK_TOP, LAYER_3D_SOLDERMASK_BOTTOM
};
APPEARANCE_CONTROLS_3D::APPEARANCE_CONTROLS_3D( EDA_3D_VIEWER_FRAME* aParent,
wxWindow* aFocusOwner ) :
APPEARANCE_CONTROLS_3D_BASE( aParent ),
m_frame( aParent ),
m_focusOwner( aFocusOwner ),
m_lastSelectedViewport( nullptr )
{
DPI_SCALING_COMMON dpi( nullptr, m_frame );
int screenHeight = wxSystemSettings::GetMetric( wxSYS_SCREEN_Y );
m_pointSize = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT ).GetPointSize();
m_layerPanelColour = m_panelLayers->GetBackgroundColour().ChangeLightness( 110 );
SetBorders( true, false, false, false );
m_layersOuterSizer = new wxBoxSizer( wxVERTICAL );
m_windowLayers->SetSizer( m_layersOuterSizer );
m_windowLayers->SetScrollRate( 0, 5 );
m_windowLayers->Bind( wxEVT_SET_FOCUS, &APPEARANCE_CONTROLS_3D::OnSetFocus, this );
m_envOuterSizer = new wxBoxSizer( wxVERTICAL );
wxFont infoFont = KIUI::GetInfoFont( this );
m_panelLayers->SetFont( infoFont );
m_windowLayers->SetFont( infoFont );
m_presetsLabel->SetFont( infoFont );
m_viewportsLabel->SetFont( infoFont );
// Create display options
m_cbUseBoardStackupColors = new wxCheckBox( m_panelLayers, wxID_ANY,
_( "Use board stackup colors" ) );
m_cbUseBoardStackupColors->SetFont( infoFont );
m_cbUseBoardStackupColors->Bind( wxEVT_CHECKBOX,
[this]( wxCommandEvent& aEvent )
{
EDA_3D_VIEWER_SETTINGS* cfg = m_frame->GetAdapter().m_Cfg;
cfg->m_UseStackupColors = aEvent.IsChecked();
UpdateLayerCtls();
syncLayerPresetSelection();
m_frame->NewDisplay( true );
} );
m_panelLayersSizer->Add( m_cbUseBoardStackupColors, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 7 );
m_cbLayerPresets->SetToolTip( wxString::Format( _( "Save and restore color and visibility "
"combinations.\n"
"Use %s+Tab to activate selector.\n"
"Successive Tabs while holding %s down will "
"cycle through presets in the popup." ),
KeyNameFromKeyCode( PRESET_SWITCH_KEY ),
KeyNameFromKeyCode( PRESET_SWITCH_KEY ) ) );
m_cbViewports->SetToolTip( wxString::Format( _( "Save and restore camera position and zoom.\n"
"Use %s+Tab to activate selector.\n"
"Successive Tabs while holding %s down will "
"cycle through viewports in the popup." ),
KeyNameFromKeyCode( VIEWPORT_SWITCH_KEY ),
KeyNameFromKeyCode( VIEWPORT_SWITCH_KEY ) ) );
if( screenHeight <= 900 && m_pointSize >= FromDIP( KIUI::c_IndicatorSizeDIP ) )
m_pointSize = m_pointSize * 8 / 10;
m_cbLayerPresets->Bind( wxEVT_CHOICE, &APPEARANCE_CONTROLS_3D::onLayerPresetChanged, this );
m_toggleGridRenderer = new GRID_BITMAP_TOGGLE_RENDERER(
KiBitmapBundle( BITMAPS::visibility ), KiBitmapBundle( BITMAPS::visibility_off ) );
m_frame->Bind( EDA_LANG_CHANGED, &APPEARANCE_CONTROLS_3D::OnLanguageChanged, this );
}
APPEARANCE_CONTROLS_3D::~APPEARANCE_CONTROLS_3D()
{
m_frame->Unbind( EDA_LANG_CHANGED, &APPEARANCE_CONTROLS_3D::OnLanguageChanged, this );
}
wxSize APPEARANCE_CONTROLS_3D::GetBestSize() const
{
DPI_SCALING_COMMON dpi( nullptr, m_frame );
wxSize size( 220 * dpi.GetScaleFactor(), 480 * dpi.GetScaleFactor() );
return size;
}
void APPEARANCE_CONTROLS_3D::OnSetFocus( wxFocusEvent& aEvent )
{
#ifdef __WXMSW__
// In wxMSW, buttons won't process events unless they have focus, so we'll let it take the
// focus and give it back to the parent in the button event handler.
if( wxBitmapButton* btn = dynamic_cast( aEvent.GetEventObject() ) )
{
wxCommandEvent evt( wxEVT_BUTTON );
wxPostEvent( btn, evt );
}
#endif
passOnFocus();
aEvent.Skip();
}
void APPEARANCE_CONTROLS_3D::OnSize( wxSizeEvent& aEvent )
{
aEvent.Skip();
}
void APPEARANCE_CONTROLS_3D::rebuildControls()
{
Freeze();
rebuildLayers();
m_cbUseBoardStackupColors->SetLabel( _( "Use board stackup colors" ) );
rebuildLayerPresetsWidget();
rebuildViewportsWidget();
Thaw();
Refresh();
}
void APPEARANCE_CONTROLS_3D::OnLanguageChanged( wxCommandEvent& aEvent )
{
rebuildControls();
aEvent.Skip();
}
void APPEARANCE_CONTROLS_3D::OnDarkModeToggle()
{
// This is essentially a list of hacks because DarkMode isn't yet implemented inside
// wxWidgets.
//
// The individual wxPanels, COLOR_SWATCHes and GRID_CELL_COLOR_RENDERERs should really be
// overriding some virtual method or responding to some wxWidgets event so that the parent
// doesn't have to know what it contains. But, that's not where we are, so... :shrug:
m_layerPanelColour = m_panelLayers->GetBackgroundColour().ChangeLightness( 110 );
m_windowLayers->SetBackgroundColour( m_layerPanelColour );
for( wxSizerItem* child : m_layersOuterSizer->GetChildren() )
{
if( child && child->GetWindow() )
child->GetWindow()->SetBackgroundColour( m_layerPanelColour );
}
}
void APPEARANCE_CONTROLS_3D::CommonSettingsChanged()
{
rebuildControls();
UpdateLayerCtls();
syncLayerPresetSelection();
}
void APPEARANCE_CONTROLS_3D::ApplyLayerPreset( const wxString& aPresetName )
{
if( aPresetName == FOLLOW_PCB || aPresetName == FOLLOW_PLOT_SETTINGS )
{
m_frame->GetAdapter().m_Cfg->m_CurrentPreset = aPresetName;
UpdateLayerCtls();
m_frame->NewDisplay( true );
}
else if( LAYER_PRESET_3D* preset = m_frame->GetAdapter().m_Cfg->FindPreset( aPresetName ) )
{
doApplyLayerPreset( *preset );
}
// Move to front of MRU list
if( m_presetMRU.Index( aPresetName ) != wxNOT_FOUND )
m_presetMRU.Remove( aPresetName );
m_presetMRU.Insert( aPresetName, 0 );
updateLayerPresetWidget( aPresetName );
}
std::vector APPEARANCE_CONTROLS_3D::GetUserViewports() const
{
std::vector ret;
for( const auto& [name, viewport] : m_viewports )
ret.emplace_back( viewport );
return ret;
}
void APPEARANCE_CONTROLS_3D::SetUserViewports( std::vector& aViewportList )
{
m_viewports.clear();
for( const VIEWPORT3D& viewport : aViewportList )
{
if( m_viewports.count( viewport.name ) )
continue;
m_viewports[viewport.name] = viewport;
m_viewportMRU.Add( viewport.name );
}
rebuildViewportsWidget();
// Now is as good a time as any to initialize the layer presets as well.
rebuildLayerPresetsWidget();
m_presetMRU.Add( FOLLOW_PCB );
m_presetMRU.Add( FOLLOW_PLOT_SETTINGS );
for( const LAYER_PRESET_3D& preset : m_frame->GetAdapter().m_Cfg->m_LayerPresets )
m_presetMRU.Add( preset.name );
}
void APPEARANCE_CONTROLS_3D::ApplyViewport( const wxString& aViewportName )
{
int idx = m_cbViewports->FindString( aViewportName );
if( idx >= 0 && idx < (int)m_cbViewports->GetCount() - 3 /* separator */ )
{
m_cbViewports->SetSelection( idx );
m_lastSelectedViewport = static_cast( m_cbViewports->GetClientData( idx ) );
}
else
{
m_cbViewports->SetSelection( m_cbViewports->GetCount() - 3 ); // separator
m_lastSelectedViewport = nullptr;
}
if( m_lastSelectedViewport )
doApplyViewport( *m_lastSelectedViewport );
}
void APPEARANCE_CONTROLS_3D::OnLayerVisibilityChanged( int aLayer, bool isVisible )
{
std::bitset visibleLayers = m_frame->GetAdapter().GetVisibleLayers();
const std::map& colors = m_frame->GetAdapter().GetLayerColors();
bool killFollow = false;
bool doFastRefresh = false; // true to just refresh the display
// Special-case controls
switch( aLayer )
{
case LAYER_FP_TEXT:
// Because Footprint Text is a meta-control that also can disable values/references,
// drag them along here so that the user is less likely to be confused.
if( !isVisible )
{
visibleLayers.set( LAYER_FP_REFERENCES, false );
visibleLayers.set( LAYER_FP_VALUES, false );
}
visibleLayers.set( LAYER_FP_TEXT, isVisible );
killFollow = true;
break;
case LAYER_FP_REFERENCES:
case LAYER_FP_VALUES:
// In case that user changes Footprint Value/References when the Footprint Text
// meta-control is disabled, we should put it back on.
if( isVisible )
visibleLayers.set( LAYER_FP_TEXT, true );
visibleLayers.set( aLayer, isVisible );
killFollow = true;
break;
case LAYER_3D_BOARD:
case LAYER_3D_COPPER_TOP:
case LAYER_3D_COPPER_BOTTOM:
case LAYER_3D_SILKSCREEN_BOTTOM:
case LAYER_3D_SILKSCREEN_TOP:
case LAYER_3D_SOLDERMASK_BOTTOM:
case LAYER_3D_SOLDERMASK_TOP:
case LAYER_3D_SOLDERPASTE:
case LAYER_3D_ADHESIVE:
case LAYER_3D_USER_COMMENTS:
case LAYER_3D_USER_DRAWINGS:
case LAYER_3D_USER_ECO1:
case LAYER_3D_USER_ECO2:
visibleLayers.set( aLayer, isVisible );
killFollow = true;
break;
case LAYER_3D_TH_MODELS:
case LAYER_3D_SMD_MODELS:
case LAYER_3D_VIRTUAL_MODELS:
case LAYER_3D_MODELS_NOT_IN_POS:
case LAYER_3D_MODELS_MARKED_DNP:
doFastRefresh = true;
visibleLayers.set( aLayer, isVisible );
break;
default:
visibleLayers.set( aLayer, isVisible );
break;
}
m_frame->GetAdapter().SetVisibleLayers( visibleLayers );
m_frame->GetAdapter().SetLayerColors( colors );
const wxString& currentPreset = m_frame->GetAdapter().m_Cfg->m_CurrentPreset;
if( ( currentPreset != FOLLOW_PCB && currentPreset != FOLLOW_PLOT_SETTINGS ) || killFollow )
syncLayerPresetSelection();
UpdateLayerCtls();
if( doFastRefresh && m_frame->GetAdapter().m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
{
RENDER_3D_OPENGL* renderer =
static_cast( m_frame->GetCanvas()->GetCurrentRender() );
renderer->Load3dModelsIfNeeded();
m_frame->GetCanvas()->Request_refresh();
}
else
{
m_frame->NewDisplay( true );
}
}
void APPEARANCE_CONTROLS_3D::onColorSwatchChanged( COLOR_SWATCH* aSwatch )
{
std::bitset visibleLayers = m_frame->GetAdapter().GetVisibleLayers();
std::map colors = m_frame->GetAdapter().GetLayerColors();
m_frame->GetAdapter().SetVisibleLayers( visibleLayers );
m_frame->GetAdapter().SetLayerColors( colors );
int layer = aSwatch->GetId();
COLOR4D newColor = aSwatch->GetSwatchColor();
colors[ layer ] = newColor;
if( layer == LAYER_3D_COPPER_TOP )
colors[ LAYER_3D_COPPER_BOTTOM ] = newColor;
else if( layer == LAYER_3D_COPPER_BOTTOM )
colors[ LAYER_3D_COPPER_TOP ] = newColor;
m_frame->GetAdapter().SetLayerColors( colors );
syncLayerPresetSelection();
m_frame->NewDisplay( true );
}
void APPEARANCE_CONTROLS_3D::rebuildLayers()
{
int swatchWidth = m_windowLayers->ConvertDialogToPixels( wxSize( 8, 0 ) ).x;
std::bitset visibleLayers = m_frame->GetAdapter().GetVisibleLayers();
std::map colors = m_frame->GetAdapter().GetLayerColors();
std::map defaultColors = m_frame->GetAdapter().GetDefaultColors();
m_layerSettings.clear();
m_layersOuterSizer->Clear( true );
m_layersOuterSizer->AddSpacer( 5 );
m_envOuterSizer->Clear( true );
auto appendLayer =
[&]( const std::unique_ptr& aSetting )
{
wxBoxSizer* sizer = new wxBoxSizer( wxHORIZONTAL );
int layer = aSetting->m_Id;
aSetting->m_Visible = visibleLayers.test( layer );
if( colors.count( layer ) )
{
COLOR_SWATCH* swatch = new COLOR_SWATCH( m_windowLayers, colors[ layer ], layer,
COLOR4D::WHITE, defaultColors[ layer ],
SWATCH_SMALL );
swatch->SetToolTip( _( "Left double click or middle click to change color" ) );
swatch->SetReadOnlyCallback(
[this]()
{
WX_INFOBAR* infobar = m_frame->GetInfoBar();
infobar->RemoveAllButtons();
infobar->AddCloseButton();
infobar->ShowMessageFor( _( "Uncheck 'Use board stackup colors' to "
"allow color editing." ),
10000, wxICON_INFORMATION );
} );
sizer->Add( swatch, 0, wxALIGN_CENTER_VERTICAL, 0 );
aSetting->m_Ctl_color = swatch;
swatch->Bind( COLOR_SWATCH_CHANGED,
[this]( wxCommandEvent& event )
{
auto swatch = static_cast( event.GetEventObject() );
onColorSwatchChanged( swatch );
passOnFocus();
} );
}
else
{
sizer->AddSpacer( swatchWidth );
}
sizer->AddSpacer( 5 );
wxStaticText* label = new wxStaticText( m_windowLayers, layer, aSetting->GetLabel() );
label->Wrap( -1 );
label->SetToolTip( aSetting->GetTooltip() );
if( layer == LAYER_3D_BACKGROUND_TOP || layer == LAYER_3D_BACKGROUND_BOTTOM )
{
sizer->AddSpacer( swatchWidth );
}
else
{
BITMAP_TOGGLE* btn_visible = new BITMAP_TOGGLE(
m_windowLayers, layer, KiBitmapBundle( BITMAPS::visibility ),
KiBitmapBundle( BITMAPS::visibility_off ), aSetting->m_Visible );
btn_visible->Bind( TOGGLE_CHANGED,
[this]( wxCommandEvent& aEvent )
{
int id = static_cast( aEvent.GetEventObject() )->GetId();
bool isVisible = aEvent.GetInt();
OnLayerVisibilityChanged( id, isVisible );
passOnFocus();
} );
wxString tip;
tip.Printf( _( "Show or hide %s" ), aSetting->GetLabel().Lower() );
btn_visible->SetToolTip( tip );
aSetting->m_Ctl_visibility = btn_visible;
sizer->Add( btn_visible, 0, wxALIGN_CENTER_VERTICAL, 0 );
}
sizer->AddSpacer( 5 );
sizer->Add( label, 0, wxALIGN_CENTER_VERTICAL, 0 );
m_layersOuterSizer->Add( sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 5 );
m_layersOuterSizer->AddSpacer( 2 );
};
for( const APPEARANCE_SETTING_3D& s_setting : s_layerSettings )
{
m_layerSettings.emplace_back( std::make_unique( s_setting ) );
std::unique_ptr& setting = m_layerSettings.back();
if( setting->m_Spacer )
m_layersOuterSizer->AddSpacer( m_pointSize );
else
appendLayer( setting );
m_layerSettingsMap[setting->m_Id] = setting.get();
}
m_sizerOuter->Layout();
}
void APPEARANCE_CONTROLS_3D::UpdateLayerCtls()
{
EDA_3D_VIEWER_SETTINGS* cfg = m_frame->GetAdapter().m_Cfg;
std::bitset visibleLayers = m_frame->GetAdapter().GetVisibleLayers();
std::map colors = m_frame->GetAdapter().GetLayerColors();
for( std::unique_ptr& setting : m_layerSettings )
{
if( setting->m_Spacer )
continue;
if( setting->m_Ctl_visibility )
setting->m_Ctl_visibility->SetValue( visibleLayers.test( setting->m_Id ) );
if( setting->m_Ctl_color )
{
setting->m_Ctl_color->SetSwatchColor( colors[ setting->m_Id ], false );
// if cfg->m_UseStackupColors is set, board colors cannot be modified locally, but
// other colors can be
if( std::find( inStackupColors.begin(), inStackupColors.end(), setting->m_Id )
!= inStackupColors.end() )
{
if( cfg )
setting->m_Ctl_color->SetReadOnly( cfg->m_UseStackupColors );
}
}
}
if( cfg )
m_cbUseBoardStackupColors->SetValue( cfg->m_UseStackupColors );
}
void APPEARANCE_CONTROLS_3D::rebuildLayerPresetsWidget()
{
m_presetsLabel->SetLabel( wxString::Format( _( "Presets (%s+Tab):" ),
KeyNameFromKeyCode( PRESET_SWITCH_KEY ) ) );
m_cbLayerPresets->Clear();
// Build the layers preset list.
m_cbLayerPresets->Append( _( "Follow PCB Editor" ) );
m_cbLayerPresets->Append( _( "Follow PCB Plot Settings" ) );
for( const LAYER_PRESET_3D& preset : m_frame->GetAdapter().m_Cfg->m_LayerPresets )
m_cbLayerPresets->Append( preset.name );
m_cbLayerPresets->Append( wxT( "---" ) );
m_cbLayerPresets->Append( _( "Save preset..." ) );
m_cbLayerPresets->Append( _( "Delete preset..." ) );
updateLayerPresetWidget( m_frame->GetAdapter().m_Cfg->m_CurrentPreset );
}
void APPEARANCE_CONTROLS_3D::syncLayerPresetSelection()
{
m_frame->GetAdapter().m_Cfg->m_CurrentPreset = wxEmptyString;
std::vector& presets = m_frame->GetAdapter().m_Cfg->m_LayerPresets;
std::bitset visibleLayers = m_frame->GetAdapter().GetVisibleLayers();
std::map colors = m_frame->GetAdapter().GetLayerColors();
auto it = std::find_if(
presets.begin(), presets.end(),
[&]( const LAYER_PRESET_3D& aPreset )
{
if( aPreset.name.Lower() == _( "legacy colors" )
&& m_cbUseBoardStackupColors->GetValue() )
{
return false;
}
for( int layer = LAYER_3D_BOARD; layer < LAYER_3D_END; ++layer )
{
if( aPreset.layers.test( layer ) != visibleLayers.test( layer ) )
return false;
}
for( int layer : { LAYER_FP_REFERENCES, LAYER_FP_VALUES, LAYER_FP_TEXT } )
{
if( aPreset.layers.test( layer ) != visibleLayers.test( layer ) )
return false;
}
for( int layer = LAYER_3D_START + 1; layer < LAYER_3D_END; ++layer )
{
auto it1 = aPreset.colors.find( layer );
auto it2 = colors.find( layer );
if( it1 != aPreset.colors.end() && it2 != colors.end() && *it1 != *it2 )
return false;
}
return true;
} );
if( it != presets.end() )
{
m_frame->GetAdapter().m_Cfg->m_CurrentPreset = it->name;
m_cbLayerPresets->SetStringSelection( it->name );
}
else
{
m_cbLayerPresets->SetSelection( m_cbLayerPresets->GetCount() - 3 ); // separator
}
}
void APPEARANCE_CONTROLS_3D::updateLayerPresetWidget( const wxString& aName )
{
if( aName == FOLLOW_PCB )
m_cbLayerPresets->SetSelection( 0 );
else if( aName == FOLLOW_PLOT_SETTINGS )
m_cbLayerPresets->SetSelection( 1 );
else if( !m_cbLayerPresets->SetStringSelection( aName ) )
m_cbLayerPresets->SetSelection( m_cbLayerPresets->GetCount() - 3 ); // separator
}
void APPEARANCE_CONTROLS_3D::onLayerPresetChanged( wxCommandEvent& aEvent )
{
EDA_3D_VIEWER_SETTINGS* cfg = m_frame->GetAdapter().m_Cfg;
int count = m_cbLayerPresets->GetCount();
int index = m_cbLayerPresets->GetSelection();
wxString name;
auto resetSelection =
[&]()
{
updateLayerPresetWidget( cfg->m_CurrentPreset );
};
if( index == 0 )
{
name = FOLLOW_PCB;
cfg->m_CurrentPreset = name;
UpdateLayerCtls();
m_frame->NewDisplay( true );
}
else if( index == 1 )
{
name = FOLLOW_PLOT_SETTINGS;
cfg->m_CurrentPreset = name;
UpdateLayerCtls();
m_frame->NewDisplay( true );
}
else if( index == count - 3 )
{
// Separator: reject the selection
resetSelection();
return;
}
else if( index == count - 2 )
{
wxTextEntryDialog dlg( wxGetTopLevelParent( this ), _( "Layer preset name:" ),
_( "Save Layer Preset" ) );
if( dlg.ShowModal() != wxID_OK )
{
resetSelection();
return;
}
std::bitset visibleLayers = m_frame->GetAdapter().GetVisibleLayers();
std::map colors = m_frame->GetAdapter().GetLayerColors();
name = dlg.GetValue();
if( LAYER_PRESET_3D* preset = cfg->FindPreset( name ) )
{
if( !IsOK( wxGetTopLevelParent( this ), _( "Overwrite existing preset?" ) ) )
{
resetSelection();
return;
}
preset->layers = visibleLayers;
preset->colors = colors;
m_cbLayerPresets->SetSelection( m_cbLayerPresets->FindString( name ) );
}
else
{
cfg->m_LayerPresets.emplace_back( name, visibleLayers, colors );
m_cbLayerPresets->SetSelection( m_cbLayerPresets->Insert( name, index - 1 ) );
}
cfg->m_CurrentPreset = name;
m_presetMRU.Insert( name, 0 );
return;
}
else if( index == count - 1 )
{
wxArrayString headers;
std::vector items;
headers.Add( _( "Presets" ) );
for( LAYER_PRESET_3D& preset : cfg->m_LayerPresets )
{
wxArrayString item;
item.Add( preset.name );
items.emplace_back( item );
}
EDA_LIST_DIALOG dlg( m_frame, _( "Delete Preset" ), headers, items );
dlg.SetListLabel( _( "Select preset:" ) );
if( dlg.ShowModal() == wxID_OK )
{
name = dlg.GetTextSelection();
if( m_cbLayerPresets->FindString( name ) != wxNOT_FOUND )
m_cbLayerPresets->Delete( m_cbLayerPresets->FindString( name ) );
alg::delete_if( cfg->m_LayerPresets,
[name]( const LAYER_PRESET_3D& preset )
{
return preset.name == name;
} );
if( cfg->m_CurrentPreset == name )
cfg->m_CurrentPreset = wxEmptyString;
m_presetMRU.Remove( name );
}
resetSelection();
return;
}
else if( LAYER_PRESET_3D* preset = cfg->FindPreset( m_cbLayerPresets->GetStringSelection() ) )
{
name = preset->name;
doApplyLayerPreset( *preset );
}
// Move to front of MRU list
if( m_presetMRU.Index( name ) != wxNOT_FOUND )
m_presetMRU.Remove( name );
m_presetMRU.Insert( name, 0 );
passOnFocus();
}
void APPEARANCE_CONTROLS_3D::doApplyLayerPreset( const LAYER_PRESET_3D& aPreset )
{
BOARD_ADAPTER& adapter = m_frame->GetAdapter();
adapter.m_Cfg->m_CurrentPreset = aPreset.name;
adapter.SetVisibleLayers( aPreset.layers );
adapter.SetLayerColors( aPreset.colors );
if( aPreset.name.Lower() == _( "legacy colors" ) )
adapter.m_Cfg->m_UseStackupColors = false;
UpdateLayerCtls();
m_frame->NewDisplay( true );
}
void APPEARANCE_CONTROLS_3D::rebuildViewportsWidget()
{
m_viewportsLabel->SetLabel( wxString::Format( _( "Viewports (%s+Tab):" ),
KeyNameFromKeyCode( VIEWPORT_SWITCH_KEY ) ) );
m_cbViewports->Clear();
for( std::pair& pair : m_viewports )
m_cbViewports->Append( pair.first, static_cast( &pair.second ) );
m_cbViewports->Append( wxT( "---" ) );
m_cbViewports->Append( _( "Save viewport..." ) );
m_cbViewports->Append( _( "Delete viewport..." ) );
m_cbViewports->SetSelection( m_cbViewports->GetCount() - 3 );
m_lastSelectedViewport = nullptr;
}
void APPEARANCE_CONTROLS_3D::onViewportChanged( wxCommandEvent& aEvent )
{
int count = m_cbViewports->GetCount();
int index = m_cbViewports->GetSelection();
if( index >= 0 && index < count - 3 )
{
VIEWPORT3D* viewport = static_cast( m_cbViewports->GetClientData( index ) );
wxCHECK( viewport, /* void */ );
doApplyViewport( *viewport );
if( !viewport->name.IsEmpty() )
{
m_viewportMRU.Remove( viewport->name );
m_viewportMRU.Insert( viewport->name, 0 );
}
}
else if( index == count - 2 )
{
// Save current state to new preset
wxString name;
wxTextEntryDialog dlg( wxGetTopLevelParent( this ), _( "Viewport name:" ),
_( "Save Viewport" ), name );
if( dlg.ShowModal() != wxID_OK )
{
if( m_lastSelectedViewport )
m_cbViewports->SetStringSelection( m_lastSelectedViewport->name );
else
m_cbViewports->SetSelection( m_cbViewports->GetCount() - 3 );
return;
}
name = dlg.GetValue();
bool exists = m_viewports.count( name );
if( !exists )
{
m_viewports[name] = VIEWPORT3D( name, m_frame->GetCurrentCamera().GetViewMatrix() );
index = m_cbViewports->Insert( name, index-1, static_cast( &m_viewports[name] ) );
}
else
{
m_viewports[name].matrix = m_frame->GetCurrentCamera().GetViewMatrix();
index = m_cbViewports->FindString( name );
m_viewportMRU.Remove( name );
}
m_cbViewports->SetSelection( index );
m_viewportMRU.Insert( name, 0 );
return;
}
else if( index == count - 1 )
{
// Delete an existing viewport
wxArrayString headers;
std::vector items;
headers.Add( _( "Viewports" ) );
for( std::pair& pair : m_viewports )
{
wxArrayString item;
item.Add( pair.first );
items.emplace_back( item );
}
EDA_LIST_DIALOG dlg( m_frame, _( "Delete Viewport" ), headers, items );
dlg.SetListLabel( _( "Select viewport:" ) );
if( dlg.ShowModal() == wxID_OK )
{
wxString viewportName = dlg.GetTextSelection();
int idx = m_cbViewports->FindString( viewportName );
if( idx != wxNOT_FOUND )
{
m_viewports.erase( viewportName );
m_cbViewports->Delete( idx );
m_viewportMRU.Remove( viewportName );
}
}
if( m_lastSelectedViewport )
m_cbViewports->SetStringSelection( m_lastSelectedViewport->name );
else
m_cbViewports->SetSelection( m_cbViewports->GetCount() - 3 );
return;
}
passOnFocus();
}
void APPEARANCE_CONTROLS_3D::onUpdateViewportsCb( wxUpdateUIEvent& aEvent )
{
int count = m_cbViewports->GetCount();
int index = m_cbViewports->GetSelection();
if( index >= 0 && index < count - 3 )
{
VIEWPORT3D* viewport = static_cast( m_cbViewports->GetClientData( index ) );
wxCHECK( viewport, /* void */ );
if( m_frame->GetCurrentCamera().GetViewMatrix() != viewport->matrix )
m_cbViewports->SetSelection( -1 );
}
}
void APPEARANCE_CONTROLS_3D::doApplyViewport( const VIEWPORT3D& aViewport )
{
m_frame->GetCurrentCamera().SetViewMatrix( aViewport.matrix );
if( m_frame->GetAdapter().m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
m_frame->GetCanvas()->Request_refresh();
else
m_frame->GetCanvas()->RenderRaytracingRequest();
}
void APPEARANCE_CONTROLS_3D::passOnFocus()
{
m_focusOwner->SetFocus();
}