Fix obscured object selection issue in board and footprint editors.
This selection improvement feature is hidden behind the advanced configuration key "PcbSelectionVisibilityRatio". It is turned off (1.0) by default. Value values are from 0.0 to less that 1.0. From testing, using a value between 0.1 and 0.3 produces the best results. This fix uses normal alpha blending described in the link below. The current design only uses the alpha of the object's color. It could be improved by doing a full color alpha blending but using the color alpha alone seems to result in satisfactory results. https://en.wikipedia.org/wiki/Alpha_compositing Fixes https://gitlab.com/kicad/code/kicad/-/issues/16126
This commit is contained in:
parent
1b0cc82c56
commit
eb3fd10af8
|
@ -221,6 +221,8 @@ static const wxChar EnableEeschemaPrintCairo[] = wxT( "EnableEeschemaPrintCairo"
|
|||
* The time in milliseconds to wait before displaying a disambiguation menu.
|
||||
*/
|
||||
static const wxChar DisambiguationTime[] = wxT( "DisambiguationTime" );
|
||||
|
||||
static const wxChar PcbSelectionVisibilityRatio[] = wxT( "PcbSelectionVisibilityRatio" );
|
||||
} // namespace KEYS
|
||||
|
||||
|
||||
|
@ -360,6 +362,8 @@ ADVANCED_CFG::ADVANCED_CFG()
|
|||
|
||||
m_DisambiguationMenuDelay = 300;
|
||||
|
||||
m_PcbSelectionVisibilityRatio = 1.0;
|
||||
|
||||
loadFromConfigFile();
|
||||
}
|
||||
|
||||
|
@ -528,6 +532,10 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
|
|||
&m_EnableEeschemaPrintCairo, m_EnableEeschemaPrintCairo ) );
|
||||
|
||||
|
||||
configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::PcbSelectionVisibilityRatio,
|
||||
&m_PcbSelectionVisibilityRatio,
|
||||
m_PcbSelectionVisibilityRatio, 0.0, 1.0 ) );
|
||||
|
||||
// Special case for trace mask setting...we just grab them and set them immediately
|
||||
// Because we even use wxLogTrace inside of advanced config
|
||||
wxString traceMasks;
|
||||
|
|
|
@ -284,6 +284,18 @@ public:
|
|||
*/
|
||||
bool m_EnableEeschemaPrintCairo;
|
||||
|
||||
/**
|
||||
* Board object selection visibility limit.
|
||||
*
|
||||
* This ratio is used to determine if an object in a selected object layer stack is
|
||||
* visible. All alpha ratios less or equal to this value are considered invisible
|
||||
* to the user and will be pruned from the list of selections. Valid values are
|
||||
* between 0 and less than 1. A value of 1 disables this feature. Reasonable values
|
||||
* are between 0.01 and 0.03 depending on the layer colors.
|
||||
*
|
||||
* The setting name is "PcbSelectionVisibilityRatio".
|
||||
*/
|
||||
double m_PcbSelectionVisibilityRatio;
|
||||
///@}
|
||||
|
||||
|
||||
|
|
|
@ -540,6 +540,17 @@ public:
|
|||
{
|
||||
return at( m_index ); // throws std::out_of_range
|
||||
}
|
||||
|
||||
int TestLayers( PCB_LAYER_ID aRhs, PCB_LAYER_ID aLhs ) const
|
||||
{
|
||||
if( aRhs == aLhs )
|
||||
return 0;
|
||||
|
||||
auto itRhs = std::find( begin(), end(), aRhs );
|
||||
auto itLhs = std::find( begin(), end(), aLhs );
|
||||
|
||||
return std::distance( itRhs, itLhs );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -74,6 +74,14 @@ using namespace std::placeholders;
|
|||
#include <math/vector2wx.h>
|
||||
|
||||
|
||||
struct LAYER_OPACITY_ITEM
|
||||
{
|
||||
PCB_LAYER_ID m_Layer;
|
||||
double m_Opacity;
|
||||
const BOARD_ITEM* m_Item;;
|
||||
};
|
||||
|
||||
|
||||
class SELECT_MENU : public ACTION_MENU
|
||||
{
|
||||
public:
|
||||
|
@ -2548,6 +2556,7 @@ void PCB_SELECTION_TOOL::RebuildSelection()
|
|||
bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibilityOnly ) const
|
||||
{
|
||||
const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
|
||||
const PCB_DISPLAY_OPTIONS& options = frame()->GetDisplayOptions();
|
||||
|
||||
auto visibleLayers =
|
||||
[&]()
|
||||
|
@ -2652,7 +2661,7 @@ bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibili
|
|||
switch( aItem->Type() )
|
||||
{
|
||||
case PCB_ZONE_T:
|
||||
if( !board()->IsElementVisible( LAYER_ZONES ) )
|
||||
if( !board()->IsElementVisible( LAYER_ZONES ) || ( options.m_ZoneOpacity == 0.00 ) )
|
||||
return false;
|
||||
|
||||
zone = static_cast<const ZONE*>( aItem );
|
||||
|
@ -2679,7 +2688,7 @@ bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibili
|
|||
|
||||
case PCB_TRACE_T:
|
||||
case PCB_ARC_T:
|
||||
if( !board()->IsElementVisible( LAYER_TRACKS ) )
|
||||
if( !board()->IsElementVisible( LAYER_TRACKS ) || ( options.m_TrackOpacity == 0.00 ) )
|
||||
return false;
|
||||
|
||||
if( m_isFootprintEditor )
|
||||
|
@ -2696,7 +2705,7 @@ bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibili
|
|||
break;
|
||||
|
||||
case PCB_VIA_T:
|
||||
if( !board()->IsElementVisible( LAYER_VIAS ) )
|
||||
if( !board()->IsElementVisible( LAYER_VIAS ) || ( options.m_ViaOpacity == 0.00 ) )
|
||||
return false;
|
||||
|
||||
via = static_cast<const PCB_VIA*>( aItem );
|
||||
|
@ -2750,9 +2759,14 @@ bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibili
|
|||
|
||||
break;
|
||||
|
||||
case PCB_REFERENCE_IMAGE_T:
|
||||
if( options.m_ImageOpacity == 0.00 )
|
||||
return false;
|
||||
|
||||
KI_FALLTHROUGH;
|
||||
|
||||
case PCB_SHAPE_T:
|
||||
case PCB_TEXTBOX_T:
|
||||
case PCB_REFERENCE_IMAGE_T:
|
||||
if( m_isFootprintEditor )
|
||||
{
|
||||
if( !view()->IsLayerVisible( aItem->GetLayer() ) )
|
||||
|
@ -2793,6 +2807,9 @@ bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibili
|
|||
break;
|
||||
|
||||
case PCB_PAD_T:
|
||||
if( options.m_PadOpacity == 0.00 )
|
||||
return false;
|
||||
|
||||
pad = static_cast<const PAD*>( aItem );
|
||||
|
||||
if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
|
||||
|
@ -3068,6 +3085,153 @@ int PCB_SELECTION_TOOL::hitTestDistance( const VECTOR2I& aWhere, BOARD_ITEM* aIt
|
|||
}
|
||||
|
||||
|
||||
void PCB_SELECTION_TOOL::pruneObscuredSelectionCandidates( GENERAL_COLLECTOR& aCollector ) const
|
||||
{
|
||||
wxCHECK( m_frame, /* void */ );
|
||||
|
||||
if( aCollector.GetCount() < 2 )
|
||||
return;
|
||||
|
||||
const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
|
||||
|
||||
wxCHECK( settings, /* void */ );
|
||||
|
||||
PCB_LAYER_ID activeLayer = m_frame->GetActiveLayer();
|
||||
LSET visibleLayers = m_frame->GetBoard()->GetVisibleLayers();
|
||||
LSET enabledLayers = m_frame->GetBoard()->GetEnabledLayers();
|
||||
LSEQ enabledLayerStack = enabledLayers.SeqStackupTop2Bottom( activeLayer );
|
||||
|
||||
wxCHECK( !enabledLayerStack.empty(), /* void */ );
|
||||
|
||||
auto isCopperPourKeepoutZone = []( const BOARD_ITEM* aItem ) -> bool
|
||||
{
|
||||
if( aItem->Type() == PCB_ZONE_T )
|
||||
{
|
||||
const ZONE* zone = static_cast<const ZONE*>( aItem );
|
||||
|
||||
wxCHECK( zone, false );
|
||||
|
||||
if( zone->GetIsRuleArea()
|
||||
&& zone->GetDoNotAllowCopperPour() )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
std::vector<LAYER_OPACITY_ITEM> opacityStackup;
|
||||
|
||||
for( int i = 0; i < aCollector.GetCount(); i++ )
|
||||
{
|
||||
const BOARD_ITEM* item = aCollector[i];
|
||||
|
||||
LSET itemLayers = item->GetLayerSet() & enabledLayers & visibleLayers;
|
||||
LSEQ itemLayerSeq = itemLayers.Seq( enabledLayerStack );
|
||||
|
||||
for( PCB_LAYER_ID layer : itemLayerSeq )
|
||||
{
|
||||
COLOR4D color = settings->GetColor( item, layer );
|
||||
|
||||
if( color.a == 0 )
|
||||
continue;
|
||||
|
||||
LAYER_OPACITY_ITEM opacityItem;
|
||||
|
||||
opacityItem.m_Layer = layer;
|
||||
opacityItem.m_Opacity = color.a;
|
||||
opacityItem.m_Item = item;
|
||||
|
||||
if( isCopperPourKeepoutZone( item ) )
|
||||
opacityItem.m_Opacity = 0.0;
|
||||
|
||||
opacityStackup.emplace_back( opacityItem );
|
||||
}
|
||||
}
|
||||
|
||||
std::sort( opacityStackup.begin(), opacityStackup.end(),
|
||||
[&]( const LAYER_OPACITY_ITEM& aLhs, const LAYER_OPACITY_ITEM& aRhs ) -> bool
|
||||
{
|
||||
int retv = enabledLayerStack.TestLayers( aLhs.m_Layer, aRhs.m_Layer );
|
||||
|
||||
if( retv )
|
||||
return retv > 0;
|
||||
|
||||
return aLhs.m_Opacity > aRhs.m_Opacity;
|
||||
} );
|
||||
|
||||
std::set<const BOARD_ITEM*> visibleItems;
|
||||
std::set<const BOARD_ITEM*> itemsToRemove;
|
||||
double minAlphaLimit = ADVANCED_CFG::GetCfg().m_PcbSelectionVisibilityRatio;
|
||||
double currentStackupOpacity = 0.0;
|
||||
PCB_LAYER_ID lastVisibleLayer = PCB_LAYER_ID::UNDEFINED_LAYER;
|
||||
|
||||
for( const LAYER_OPACITY_ITEM& opacityItem : opacityStackup )
|
||||
{
|
||||
if( lastVisibleLayer == PCB_LAYER_ID::UNDEFINED_LAYER )
|
||||
{
|
||||
currentStackupOpacity = opacityItem.m_Opacity;
|
||||
lastVisibleLayer = opacityItem.m_Layer;
|
||||
visibleItems.emplace( opacityItem.m_Item );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Objects to ignore and fallback to the old selection behavior.
|
||||
auto ignoreItem = [&]()
|
||||
{
|
||||
const BOARD_ITEM* item = opacityItem.m_Item;
|
||||
|
||||
wxCHECK( item, false );
|
||||
|
||||
// Check items that span multiple layers for visibility.
|
||||
if( visibleItems.count( item ) )
|
||||
return true;
|
||||
|
||||
// Don't prune child items of a footprint that is already visible.
|
||||
if( item->GetParent()
|
||||
&& ( item->GetParent()->Type() == PCB_FOOTPRINT_T )
|
||||
&& visibleItems.count( item->GetParent() ) )
|
||||
return true;
|
||||
|
||||
// Keepout zones are transparent but for some reason,
|
||||
// PCB_PAINTER::GetColor() returns the color of the zone it
|
||||
// prevents from filling.
|
||||
if( isCopperPourKeepoutZone( item ) )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Everything on the currently selected layer is visible;
|
||||
if( opacityItem.m_Layer == enabledLayerStack[0] )
|
||||
{
|
||||
visibleItems.emplace( opacityItem.m_Item );
|
||||
}
|
||||
else
|
||||
{
|
||||
double itemVisibility = opacityItem.m_Opacity * ( 1.0 - currentStackupOpacity );
|
||||
|
||||
if( ( itemVisibility <= minAlphaLimit ) && !ignoreItem() )
|
||||
itemsToRemove.emplace( opacityItem.m_Item );
|
||||
else
|
||||
visibleItems.emplace( opacityItem.m_Item );
|
||||
}
|
||||
|
||||
if( opacityItem.m_Layer != lastVisibleLayer )
|
||||
{
|
||||
currentStackupOpacity += opacityItem.m_Opacity * ( 1.0 - currentStackupOpacity );
|
||||
currentStackupOpacity = std::min( currentStackupOpacity, 1.0 );
|
||||
lastVisibleLayer = opacityItem.m_Layer;
|
||||
}
|
||||
}
|
||||
|
||||
for( const BOARD_ITEM* itemToRemove : itemsToRemove )
|
||||
{
|
||||
wxCHECK( aCollector.GetCount() > 1, /* void */ );
|
||||
aCollector.Remove( itemToRemove );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The general idea here is that if the user clicks directly on a small item inside a larger
|
||||
// one, then they want the small item. The quintessential case of this is clicking on a pad
|
||||
// within a footprint, but we also apply it for text within a footprint, footprints within
|
||||
|
@ -3087,6 +3251,12 @@ void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector
|
|||
static const LSET silkLayers( 2, B_SilkS, F_SilkS );
|
||||
static const LSET courtyardLayers( 2, B_CrtYd, F_CrtYd );
|
||||
|
||||
if( ADVANCED_CFG::GetCfg().m_PcbSelectionVisibilityRatio != 1.0 )
|
||||
pruneObscuredSelectionCandidates( aCollector );
|
||||
|
||||
if( aCollector.GetCount() == 1 )
|
||||
return;
|
||||
|
||||
std::set<BOARD_ITEM*> preferred;
|
||||
std::set<BOARD_ITEM*> rejected;
|
||||
VECTOR2I where( aWhere.x, aWhere.y );
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2013-2017 CERN
|
||||
* Copyright (C) 2017-2022 KiCad Developers, see AUTHORS.TXT for contributors.
|
||||
* Copyright (C) 2017-2023 KiCad Developers, see AUTHORS.TXT for contributors.
|
||||
*
|
||||
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
||||
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
||||
|
@ -422,6 +422,8 @@ private:
|
|||
*/
|
||||
int updateSelection( const TOOL_EVENT& aEvent );
|
||||
|
||||
void pruneObscuredSelectionCandidates( GENERAL_COLLECTOR& aCollector ) const;
|
||||
|
||||
const GENERAL_COLLECTORS_GUIDE getCollectorsGuide() const;
|
||||
|
||||
private:
|
||||
|
|
|
@ -41,6 +41,7 @@ set( QA_COMMON_SRCS
|
|||
test_kicad_string.cpp
|
||||
test_kicad_stroke_font.cpp
|
||||
test_kiid.cpp
|
||||
test_layer_ids.cpp
|
||||
test_property.cpp
|
||||
test_refdes_utils.cpp
|
||||
test_richio.cpp
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2023 Wayne Stambaugh <stambaughw@gmail.com>
|
||||
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <layer_ids.h>
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE( LayerIds )
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE( LseqTestLayers )
|
||||
{
|
||||
LSET allLayers = LSET::AllLayersMask();
|
||||
LSEQ seq1 = allLayers.SeqStackupTop2Bottom();
|
||||
|
||||
BOOST_CHECK_EQUAL( seq1.TestLayers( PCB_LAYER_ID::F_Cu, PCB_LAYER_ID::F_Cu ), 0 );
|
||||
BOOST_CHECK_GT( seq1.TestLayers( PCB_LAYER_ID::F_Cu, PCB_LAYER_ID::In1_Cu ), 0 );
|
||||
BOOST_CHECK_LT( seq1.TestLayers( PCB_LAYER_ID::In1_Cu, PCB_LAYER_ID::F_Cu ), 0 );
|
||||
|
||||
// Pretend like inner copper layer one is the currently selected layer.
|
||||
LSEQ seq2 = allLayers.SeqStackupTop2Bottom( PCB_LAYER_ID::In1_Cu );
|
||||
BOOST_CHECK_LT( seq2.TestLayers( PCB_LAYER_ID::F_Cu, PCB_LAYER_ID::In1_Cu ), 0 );
|
||||
BOOST_CHECK_GT( seq2.TestLayers( PCB_LAYER_ID::In1_Cu, PCB_LAYER_ID::F_Cu ), 0 );
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
Loading…
Reference in New Issue