mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
1) Move a bunch of std::map's to std::unordered_map to get constant-time look-ups 2) Lengthen progress-reporting intervals to spend more time doing work and less time talking about it 3) Reverse order of SHAPE_LINE_CHAINs in thermal intersection checks to make (much) better use of bbox caches 4) Don't re-generate bboxes we already have 5) Fix some autos that weren't by reference (and were therefore copying large datasets) 6) Rename delta progressDelta so it's easier to search for in future 7) Get rid of a few more autos (because I don't like them) 8) Pass large items to lambdas by reference Fixes https://gitlab.com/kicad/code/kicad/issues/12130
2953 lines
91 KiB
C++
2953 lines
91 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2013-2017 CERN
|
|
* Copyright (C) 2018-2022 KiCad Developers, see AUTHORS.txt for contributors.
|
|
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
|
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
|
*
|
|
* 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 <limits>
|
|
#include <cmath>
|
|
#include <functional>
|
|
using namespace std::placeholders;
|
|
#include <core/kicad_algo.h>
|
|
#include <board.h>
|
|
#include <board_design_settings.h>
|
|
#include <board_item.h>
|
|
#include <clipper.hpp>
|
|
#include <pcb_bitmap.h>
|
|
#include <pcb_track.h>
|
|
#include <footprint.h>
|
|
#include <pad.h>
|
|
#include <pcb_group.h>
|
|
#include <pcb_shape.h>
|
|
#include <pcb_text.h>
|
|
#include <pcb_textbox.h>
|
|
#include <fp_textbox.h>
|
|
#include <pcb_marker.h>
|
|
#include <zone.h>
|
|
#include <collectors.h>
|
|
#include <dialog_filter_selection.h>
|
|
#include <dialogs/dialog_locked_items_query.h>
|
|
#include <class_draw_panel_gal.h>
|
|
#include <view/view_controls.h>
|
|
#include <preview_items/selection_area.h>
|
|
#include <painter.h>
|
|
#include <router/router_tool.h>
|
|
#include <pcbnew_settings.h>
|
|
#include <tool/tool_event.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <tools/tool_event_utils.h>
|
|
#include <tools/pcb_point_editor.h>
|
|
#include <tools/pcb_selection_tool.h>
|
|
#include <tools/pcb_actions.h>
|
|
#include <connectivity/connectivity_data.h>
|
|
#include <footprint_viewer_frame.h>
|
|
#include <wx/event.h>
|
|
#include <wx/timer.h>
|
|
#include <wx/log.h>
|
|
#include <profile.h>
|
|
|
|
|
|
class SELECT_MENU : public ACTION_MENU
|
|
{
|
|
public:
|
|
SELECT_MENU() :
|
|
ACTION_MENU( true )
|
|
{
|
|
SetTitle( _( "Select" ) );
|
|
|
|
Add( PCB_ACTIONS::filterSelection );
|
|
|
|
AppendSeparator();
|
|
|
|
Add( PCB_ACTIONS::selectConnection );
|
|
Add( PCB_ACTIONS::selectNet );
|
|
|
|
// This could be enabled if we have better logic for picking the target net with the mouse
|
|
// Add( PCB_ACTIONS::deselectNet );
|
|
Add( PCB_ACTIONS::selectSameSheet );
|
|
}
|
|
|
|
private:
|
|
ACTION_MENU* create() const override
|
|
{
|
|
return new SELECT_MENU();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Private implementation of firewalled private data.
|
|
*/
|
|
class PCB_SELECTION_TOOL::PRIV
|
|
{
|
|
public:
|
|
DIALOG_FILTER_SELECTION::OPTIONS m_filterOpts;
|
|
};
|
|
|
|
|
|
PCB_SELECTION_TOOL::PCB_SELECTION_TOOL() :
|
|
SELECTION_TOOL( "pcbnew.InteractiveSelection" ),
|
|
m_frame( nullptr ),
|
|
m_isFootprintEditor( false ),
|
|
m_nonModifiedCursor( KICURSOR::ARROW ),
|
|
m_enteredGroup( nullptr ),
|
|
m_priv( std::make_unique<PRIV>() )
|
|
{
|
|
m_filter.lockedItems = false;
|
|
m_filter.footprints = true;
|
|
m_filter.text = true;
|
|
m_filter.tracks = true;
|
|
m_filter.vias = true;
|
|
m_filter.pads = true;
|
|
m_filter.graphics = true;
|
|
m_filter.zones = true;
|
|
m_filter.keepouts = true;
|
|
m_filter.dimensions = true;
|
|
m_filter.otherItems = true;
|
|
}
|
|
|
|
|
|
PCB_SELECTION_TOOL::~PCB_SELECTION_TOOL()
|
|
{
|
|
getView()->Remove( &m_selection );
|
|
getView()->Remove( &m_enteredGroupOverlay );
|
|
|
|
Disconnect( wxEVT_TIMER, wxTimerEventHandler( PCB_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
|
|
}
|
|
|
|
|
|
bool PCB_SELECTION_TOOL::Init()
|
|
{
|
|
PCB_BASE_FRAME* frame = getEditFrame<PCB_BASE_FRAME>();
|
|
|
|
if( frame && ( frame->IsType( FRAME_FOOTPRINT_VIEWER )
|
|
|| frame->IsType( FRAME_FOOTPRINT_VIEWER_MODAL ) ) )
|
|
{
|
|
frame->AddStandardSubMenus( m_menu );
|
|
return true;
|
|
}
|
|
|
|
auto selectMenu = std::make_shared<SELECT_MENU>();
|
|
selectMenu->SetTool( this );
|
|
m_menu.AddSubMenu( selectMenu );
|
|
|
|
auto& menu = m_menu.GetMenu();
|
|
|
|
auto activeToolCondition =
|
|
[ frame ] ( const SELECTION& aSel )
|
|
{
|
|
return !frame->ToolStackIsEmpty();
|
|
};
|
|
|
|
auto haveHighlight =
|
|
[&]( const SELECTION& sel )
|
|
{
|
|
KIGFX::RENDER_SETTINGS* cfg = m_toolMgr->GetView()->GetPainter()->GetSettings();
|
|
|
|
return !cfg->GetHighlightNetCodes().empty();
|
|
};
|
|
|
|
auto inGroupCondition =
|
|
[this] ( const SELECTION& )
|
|
{
|
|
return m_enteredGroup != nullptr;
|
|
};
|
|
|
|
if( frame && frame->IsType( FRAME_PCB_EDITOR ) )
|
|
{
|
|
menu.AddMenu( selectMenu.get(), SELECTION_CONDITIONS::NotEmpty );
|
|
menu.AddSeparator( 1000 );
|
|
}
|
|
|
|
// "Cancel" goes at the top of the context menu when a tool is active
|
|
menu.AddItem( ACTIONS::cancelInteractive, activeToolCondition, 1 );
|
|
menu.AddItem( PCB_ACTIONS::groupLeave, inGroupCondition, 1 );
|
|
menu.AddItem( PCB_ACTIONS::clearHighlight, haveHighlight, 1 );
|
|
|
|
menu.AddSeparator( 1 );
|
|
|
|
if( frame )
|
|
frame->AddStandardSubMenus( m_menu );
|
|
|
|
m_disambiguateTimer.SetOwner( this );
|
|
Connect( wxEVT_TIMER, wxTimerEventHandler( PCB_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::Reset( RESET_REASON aReason )
|
|
{
|
|
m_frame = getEditFrame<PCB_BASE_FRAME>();
|
|
m_isFootprintEditor = m_frame->IsType( FRAME_FOOTPRINT_EDITOR );
|
|
|
|
if( m_enteredGroup )
|
|
ExitGroup();
|
|
|
|
if( aReason == TOOL_BASE::MODEL_RELOAD )
|
|
{
|
|
// Deselect any item being currently in edit, to avoid unexpected behavior
|
|
// and remove pointers to the selected items from containers
|
|
// without changing their properties (as they are already deleted
|
|
// while a new board is loaded)
|
|
ClearSelection( true );
|
|
|
|
getView()->GetPainter()->GetSettings()->SetHighlight( false );
|
|
}
|
|
else
|
|
{
|
|
// Restore previous properties of selected items and remove them from containers
|
|
ClearSelection( true );
|
|
}
|
|
|
|
// Reinsert the VIEW_GROUP, in case it was removed from the VIEW
|
|
view()->Remove( &m_selection );
|
|
view()->Add( &m_selection );
|
|
|
|
view()->Remove( &m_enteredGroupOverlay );
|
|
view()->Add( &m_enteredGroupOverlay );
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::OnIdle( wxIdleEvent& aEvent )
|
|
{
|
|
if( m_frame->ToolStackIsEmpty() && !m_multiple )
|
|
{
|
|
wxMouseState keyboardState = wxGetMouseState();
|
|
|
|
setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(),
|
|
keyboardState.AltDown() );
|
|
|
|
if( m_additive )
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ADD );
|
|
else if( m_subtractive )
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SUBTRACT );
|
|
else if( m_exclusive_or )
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::XOR );
|
|
else
|
|
m_frame->GetCanvas()->SetCurrentCursor( m_nonModifiedCursor );
|
|
}
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
|
|
{
|
|
// Main loop: keep receiving events
|
|
while( TOOL_EVENT* evt = Wait() )
|
|
{
|
|
MOUSE_DRAG_ACTION dragAction = m_frame->GetDragAction();
|
|
TRACK_DRAG_ACTION trackDragAction = m_frame->GetPcbNewSettings()->m_TrackDragAction;
|
|
|
|
// on left click, a selection is made, depending on modifiers ALT, SHIFT, CTRL:
|
|
setModifiersState( evt->Modifier( MD_SHIFT ), evt->Modifier( MD_CTRL ),
|
|
evt->Modifier( MD_ALT ) );
|
|
|
|
bool modifier_enabled = m_subtractive || m_additive || m_exclusive_or;
|
|
PCB_BASE_FRAME* frame = getEditFrame<PCB_BASE_FRAME>();
|
|
bool brd_editor = frame && frame->IsType( FRAME_PCB_EDITOR );
|
|
ROUTER_TOOL* router = m_toolMgr->GetTool<ROUTER_TOOL>();
|
|
|
|
// If the router tool is active, don't override
|
|
if( router && router->IsToolActive() && router->RoutingInProgress() )
|
|
{
|
|
evt->SetPassEvent();
|
|
}
|
|
else if( evt->IsMouseDown( BUT_LEFT ) )
|
|
{
|
|
// Avoid triggering when running under other tools
|
|
PCB_POINT_EDITOR *pt_tool = m_toolMgr->GetTool<PCB_POINT_EDITOR>();
|
|
|
|
if( m_frame->ToolStackIsEmpty() && pt_tool && !pt_tool->HasPoint() )
|
|
{
|
|
m_originalCursor = m_toolMgr->GetMousePosition();
|
|
m_disambiguateTimer.StartOnce( 500 );
|
|
}
|
|
}
|
|
else if( evt->IsClick( BUT_LEFT ) )
|
|
{
|
|
// If there is no disambiguation, this routine is still running and will
|
|
// register a `click` event when released
|
|
if( m_disambiguateTimer.IsRunning() )
|
|
{
|
|
m_disambiguateTimer.Stop();
|
|
|
|
// Single click? Select single object
|
|
if( m_highlight_modifier && brd_editor )
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::highlightNet, true );
|
|
}
|
|
else
|
|
{
|
|
m_frame->FocusOnItem( nullptr );
|
|
selectPoint( evt->Position() );
|
|
}
|
|
}
|
|
|
|
m_canceledMenu = false;
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
m_disambiguateTimer.Stop();
|
|
|
|
// Right click? if there is any object - show the context menu
|
|
bool selectionCancelled = false;
|
|
|
|
if( m_selection.Empty() )
|
|
{
|
|
selectPoint( evt->Position(), false, &selectionCancelled );
|
|
m_selection.SetIsHover( true );
|
|
}
|
|
|
|
if( !selectionCancelled )
|
|
m_menu.ShowContextMenu( m_selection );
|
|
}
|
|
else if( evt->IsDblClick( BUT_LEFT ) )
|
|
{
|
|
m_disambiguateTimer.Stop();
|
|
|
|
// Double click? Display the properties window
|
|
m_frame->FocusOnItem( nullptr );
|
|
|
|
if( m_selection.Empty() )
|
|
selectPoint( evt->Position() );
|
|
|
|
if( m_selection.GetSize() == 1 && m_selection[0]->Type() == PCB_GROUP_T )
|
|
{
|
|
EnterGroup();
|
|
}
|
|
else
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::properties, true );
|
|
}
|
|
}
|
|
else if( evt->IsDblClick( BUT_MIDDLE ) )
|
|
{
|
|
// Middle double click? Do zoom to fit or zoom to objects
|
|
if( evt->Modifier( MD_CTRL ) ) // Is CTRL key down?
|
|
m_toolMgr->RunAction( ACTIONS::zoomFitObjects, true );
|
|
else
|
|
m_toolMgr->RunAction( ACTIONS::zoomFitScreen, true );
|
|
}
|
|
else if( evt->IsDrag( BUT_LEFT ) )
|
|
{
|
|
m_disambiguateTimer.Stop();
|
|
|
|
// Is another tool already moving a new object? Don't allow a drag start
|
|
if( !m_selection.Empty() && m_selection[0]->HasFlag( IS_NEW | IS_MOVING ) )
|
|
{
|
|
evt->SetPassEvent();
|
|
continue;
|
|
}
|
|
|
|
// Drag with LMB? Select multiple objects (or at least draw a selection box)
|
|
// or drag them
|
|
m_frame->FocusOnItem( nullptr );
|
|
m_toolMgr->ProcessEvent( EVENTS::InhibitSelectionEditing );
|
|
|
|
if( modifier_enabled || dragAction == MOUSE_DRAG_ACTION::SELECT )
|
|
{
|
|
selectMultiple();
|
|
}
|
|
else if( m_selection.Empty() && dragAction != MOUSE_DRAG_ACTION::DRAG_ANY )
|
|
{
|
|
selectMultiple();
|
|
}
|
|
else
|
|
{
|
|
// Don't allow starting a drag from a zone filled area that isn't already selected
|
|
auto zoneFilledAreaFilter =
|
|
[]( const VECTOR2I& aWhere, GENERAL_COLLECTOR& aCollector,
|
|
PCB_SELECTION_TOOL* aTool )
|
|
{
|
|
VECTOR2I location = aWhere;
|
|
int accuracy = KiROUND( 5 * aCollector.GetGuide()->OnePixelInIU() );
|
|
std::set<EDA_ITEM*> remove;
|
|
|
|
for( EDA_ITEM* item : aCollector )
|
|
{
|
|
if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
|
|
{
|
|
ZONE* zone = static_cast<ZONE*>( item );
|
|
|
|
if( !zone->HitTestForCorner( location, accuracy * 2 )
|
|
&& !zone->HitTestForEdge( location, accuracy ) )
|
|
{
|
|
remove.insert( zone );
|
|
}
|
|
}
|
|
}
|
|
|
|
for( EDA_ITEM* item : remove )
|
|
aCollector.Remove( item );
|
|
};
|
|
|
|
// Selection is empty? try to start dragging the item under the point where drag
|
|
// started
|
|
if( m_selection.Empty() && selectCursor( false, zoneFilledAreaFilter ) )
|
|
m_selection.SetIsHover( true );
|
|
|
|
// Check if dragging has started within any of selected items bounding box.
|
|
// We verify "HasPosition()" first to protect against edge case involving
|
|
// moving off menus that causes problems (issue #5250)
|
|
if( evt->HasPosition() && selectionContains( evt->Position() ) )
|
|
{
|
|
// Yes -> run the move tool and wait till it finishes
|
|
PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( m_selection.GetItem( 0 ) );
|
|
|
|
// If there is only item in the selection and it's a track, then we need
|
|
// to route it.
|
|
bool doRouting = ( track && ( 1 == m_selection.GetSize() ) );
|
|
|
|
if( doRouting && trackDragAction == TRACK_DRAG_ACTION::DRAG )
|
|
m_toolMgr->RunAction( PCB_ACTIONS::drag45Degree, true );
|
|
else if( doRouting && trackDragAction == TRACK_DRAG_ACTION::DRAG_FREE_ANGLE )
|
|
m_toolMgr->RunAction( PCB_ACTIONS::dragFreeAngle, true );
|
|
else
|
|
m_toolMgr->RunAction( PCB_ACTIONS::move, true );
|
|
}
|
|
else
|
|
{
|
|
// No -> drag a selection box
|
|
selectMultiple();
|
|
}
|
|
}
|
|
}
|
|
else if( evt->IsCancel() )
|
|
{
|
|
m_disambiguateTimer.Stop();
|
|
m_frame->FocusOnItem( nullptr );
|
|
|
|
if( m_enteredGroup )
|
|
ExitGroup();
|
|
|
|
ClearSelection();
|
|
}
|
|
else
|
|
{
|
|
evt->SetPassEvent();
|
|
}
|
|
|
|
|
|
if( m_frame->ToolStackIsEmpty() )
|
|
{
|
|
// move cursor prediction
|
|
if( !modifier_enabled
|
|
&& dragAction == MOUSE_DRAG_ACTION::DRAG_SELECTED
|
|
&& !m_selection.Empty()
|
|
&& evt->HasPosition()
|
|
&& selectionContains( evt->Position() ) )
|
|
{
|
|
m_nonModifiedCursor = KICURSOR::MOVING;
|
|
}
|
|
else
|
|
{
|
|
m_nonModifiedCursor = KICURSOR::ARROW;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Shutting down; clear the selection
|
|
m_selection.Clear();
|
|
m_disambiguateTimer.Stop();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::EnterGroup()
|
|
{
|
|
wxCHECK_RET( m_selection.GetSize() == 1 && m_selection[0]->Type() == PCB_GROUP_T,
|
|
wxT( "EnterGroup called when selection is not a single group" ) );
|
|
PCB_GROUP* aGroup = static_cast<PCB_GROUP*>( m_selection[0] );
|
|
|
|
if( m_enteredGroup != nullptr )
|
|
ExitGroup();
|
|
|
|
ClearSelection();
|
|
m_enteredGroup = aGroup;
|
|
m_enteredGroup->SetFlags( ENTERED );
|
|
m_enteredGroup->RunOnChildren( [&]( BOARD_ITEM* titem )
|
|
{
|
|
select( titem );
|
|
} );
|
|
|
|
m_enteredGroupOverlay.Add( m_enteredGroup );
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::ExitGroup( bool aSelectGroup )
|
|
{
|
|
// Only continue if there is a group entered
|
|
if( m_enteredGroup == nullptr )
|
|
return;
|
|
|
|
m_enteredGroup->ClearFlags( ENTERED );
|
|
ClearSelection();
|
|
|
|
if( aSelectGroup )
|
|
select( m_enteredGroup );
|
|
|
|
m_enteredGroupOverlay.Clear();
|
|
m_enteredGroup = nullptr;
|
|
}
|
|
|
|
|
|
PCB_SELECTION& PCB_SELECTION_TOOL::GetSelection()
|
|
{
|
|
return m_selection;
|
|
}
|
|
|
|
|
|
PCB_SELECTION& PCB_SELECTION_TOOL::RequestSelection( CLIENT_SELECTION_FILTER aClientFilter,
|
|
bool aConfirmLockedItems )
|
|
{
|
|
bool selectionEmpty = m_selection.Empty();
|
|
m_selection.SetIsHover( selectionEmpty );
|
|
|
|
if( selectionEmpty )
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true, aClientFilter );
|
|
m_selection.ClearReferencePoint();
|
|
}
|
|
|
|
if( aClientFilter )
|
|
{
|
|
enum DISPOSITION { BEFORE = 1, AFTER, BOTH };
|
|
|
|
std::map<EDA_ITEM*, DISPOSITION> itemDispositions;
|
|
GENERAL_COLLECTOR collector;
|
|
|
|
for( EDA_ITEM* item : m_selection )
|
|
{
|
|
collector.Append( item );
|
|
itemDispositions[ item ] = BEFORE;
|
|
}
|
|
|
|
aClientFilter( VECTOR2I(), collector, this );
|
|
|
|
for( EDA_ITEM* item : collector )
|
|
{
|
|
if( itemDispositions.count( item ) )
|
|
itemDispositions[ item ] = BOTH;
|
|
else
|
|
itemDispositions[ item ] = AFTER;
|
|
}
|
|
|
|
// Unhighlight the BEFORE items before highlighting the AFTER items.
|
|
// This is so that in the case of groups, if aClientFilter replaces a selection
|
|
// with the enclosing group, the unhighlight of the element doesn't undo the
|
|
// recursive highlighting of that element by the group.
|
|
|
|
for( std::pair<EDA_ITEM* const, DISPOSITION> itemDisposition : itemDispositions )
|
|
{
|
|
EDA_ITEM* item = itemDisposition.first;
|
|
DISPOSITION disposition = itemDisposition.second;
|
|
|
|
if( disposition == BEFORE )
|
|
unhighlight( item, SELECTED, &m_selection );
|
|
}
|
|
|
|
for( std::pair<EDA_ITEM* const, DISPOSITION> itemDisposition : itemDispositions )
|
|
{
|
|
EDA_ITEM* item = itemDisposition.first;
|
|
DISPOSITION disposition = itemDisposition.second;
|
|
|
|
// Note that we must re-highlight even previously-highlighted items
|
|
// (ie: disposition BOTH) in case we removed any of their children.
|
|
if( disposition == AFTER || disposition == BOTH )
|
|
highlight( item, SELECTED, &m_selection );
|
|
}
|
|
|
|
m_frame->GetCanvas()->ForceRefresh();
|
|
}
|
|
|
|
if( aConfirmLockedItems )
|
|
{
|
|
std::vector<BOARD_ITEM*> lockedItems;
|
|
|
|
for( EDA_ITEM* item : m_selection )
|
|
{
|
|
BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
|
|
|
|
if( boardItem->Type() == PCB_GROUP_T )
|
|
{
|
|
PCB_GROUP* group = static_cast<PCB_GROUP*>( boardItem );
|
|
bool lockedDescendant = false;
|
|
|
|
group->RunOnDescendants(
|
|
[&lockedDescendant]( BOARD_ITEM* child )
|
|
{
|
|
if( child->IsLocked() )
|
|
lockedDescendant = true;
|
|
} );
|
|
|
|
if( lockedDescendant )
|
|
lockedItems.push_back( group );
|
|
}
|
|
else if( boardItem->IsLocked() )
|
|
{
|
|
lockedItems.push_back( boardItem );
|
|
}
|
|
}
|
|
|
|
if( !lockedItems.empty() )
|
|
{
|
|
DIALOG_LOCKED_ITEMS_QUERY dlg( frame(), lockedItems.size() );
|
|
|
|
switch( dlg.ShowModal() )
|
|
{
|
|
case wxID_OK:
|
|
// remove locked items from selection
|
|
for( BOARD_ITEM* item : lockedItems )
|
|
unselect( item );
|
|
|
|
break;
|
|
|
|
case wxID_CANCEL:
|
|
// cancel operation
|
|
ClearSelection();
|
|
break;
|
|
|
|
case wxID_APPLY:
|
|
// continue with operation with current selection
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return m_selection;
|
|
}
|
|
|
|
|
|
const GENERAL_COLLECTORS_GUIDE PCB_SELECTION_TOOL::getCollectorsGuide() const
|
|
{
|
|
GENERAL_COLLECTORS_GUIDE guide( board()->GetVisibleLayers(),
|
|
(PCB_LAYER_ID) view()->GetTopLayer(), view() );
|
|
|
|
bool padsDisabled = !board()->IsElementVisible( LAYER_PADS );
|
|
|
|
// account for the globals
|
|
guide.SetIgnoreMTextsMarkedNoShow( ! board()->IsElementVisible( LAYER_MOD_TEXT_INVISIBLE ) );
|
|
guide.SetIgnoreMTextsOnBack( ! board()->IsElementVisible( LAYER_MOD_TEXT ) );
|
|
guide.SetIgnoreMTextsOnFront( ! board()->IsElementVisible( LAYER_MOD_TEXT ) );
|
|
guide.SetIgnoreModulesOnBack( ! board()->IsElementVisible( LAYER_MOD_BK ) );
|
|
guide.SetIgnoreModulesOnFront( ! board()->IsElementVisible( LAYER_MOD_FR ) );
|
|
guide.SetIgnorePadsOnBack( padsDisabled || ! board()->IsElementVisible( LAYER_PAD_BK ) );
|
|
guide.SetIgnorePadsOnFront( padsDisabled || ! board()->IsElementVisible( LAYER_PAD_FR ) );
|
|
guide.SetIgnoreThroughHolePads( padsDisabled || ! board()->IsElementVisible( LAYER_PADS_TH ) );
|
|
guide.SetIgnoreModulesVals( ! board()->IsElementVisible( LAYER_MOD_VALUES ) );
|
|
guide.SetIgnoreModulesRefs( ! board()->IsElementVisible( LAYER_MOD_REFERENCES ) );
|
|
guide.SetIgnoreThroughVias( ! board()->IsElementVisible( LAYER_VIAS ) );
|
|
guide.SetIgnoreBlindBuriedVias( ! board()->IsElementVisible( LAYER_VIAS ) );
|
|
guide.SetIgnoreMicroVias( ! board()->IsElementVisible( LAYER_VIAS ) );
|
|
guide.SetIgnoreTracks( ! board()->IsElementVisible( LAYER_TRACKS ) );
|
|
|
|
return guide;
|
|
}
|
|
|
|
|
|
bool PCB_SELECTION_TOOL::ctrlClickHighlights()
|
|
{
|
|
return m_frame && m_frame->GetPcbNewSettings()->m_CtrlClickHighlight && !m_isFootprintEditor;
|
|
}
|
|
|
|
|
|
bool PCB_SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag,
|
|
bool* aSelectionCancelledFlag,
|
|
CLIENT_SELECTION_FILTER aClientFilter )
|
|
{
|
|
GENERAL_COLLECTORS_GUIDE guide = getCollectorsGuide();
|
|
GENERAL_COLLECTOR collector;
|
|
const PCB_DISPLAY_OPTIONS& displayOpts = m_frame->GetDisplayOptions();
|
|
|
|
guide.SetIgnoreZoneFills( displayOpts.m_ZoneDisplayMode != ZONE_DISPLAY_MODE::SHOW_FILLED );
|
|
|
|
if( m_enteredGroup && !m_enteredGroup->GetBoundingBox().Contains( aWhere ) )
|
|
ExitGroup();
|
|
|
|
collector.Collect( board(), m_isFootprintEditor ? GENERAL_COLLECTOR::FootprintItems
|
|
: GENERAL_COLLECTOR::AllBoardItems,
|
|
aWhere, guide );
|
|
|
|
// Remove unselectable items
|
|
for( int i = collector.GetCount() - 1; i >= 0; --i )
|
|
{
|
|
if( !Selectable( collector[ i ] ) || ( aOnDrag && collector[i]->IsLocked() ) )
|
|
collector.Remove( i );
|
|
}
|
|
|
|
m_selection.ClearReferencePoint();
|
|
|
|
// Allow the client to do tool- or action-specific filtering to see if we can get down
|
|
// to a single item
|
|
if( aClientFilter )
|
|
aClientFilter( aWhere, collector, this );
|
|
|
|
FilterCollectorForHierarchy( collector, false );
|
|
|
|
// Apply the stateful filter
|
|
FilterCollectedItems( collector, false );
|
|
|
|
// Apply some ugly heuristics to avoid disambiguation menus whenever possible
|
|
if( collector.GetCount() > 1 && !m_skip_heuristics )
|
|
GuessSelectionCandidates( collector, aWhere );
|
|
|
|
// If still more than one item we're going to have to ask the user.
|
|
if( collector.GetCount() > 1 )
|
|
{
|
|
if( aOnDrag )
|
|
Wait( TOOL_EVENT( TC_ANY, TA_MOUSE_UP, BUT_LEFT ) );
|
|
|
|
if( !doSelectionMenu( &collector ) )
|
|
{
|
|
if( aSelectionCancelledFlag )
|
|
*aSelectionCancelledFlag = true;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool anyAdded = false;
|
|
bool anySubtracted = false;
|
|
|
|
if( !m_additive && !m_subtractive && !m_exclusive_or )
|
|
{
|
|
if( m_selection.GetSize() > 0 )
|
|
{
|
|
ClearSelection( true /*quiet mode*/ );
|
|
anySubtracted = true;
|
|
}
|
|
}
|
|
|
|
if( collector.GetCount() > 0 )
|
|
{
|
|
for( int i = 0; i < collector.GetCount(); ++i )
|
|
{
|
|
if( m_subtractive || ( m_exclusive_or && collector[i]->IsSelected() ) )
|
|
{
|
|
unselect( collector[i] );
|
|
anySubtracted = true;
|
|
}
|
|
else
|
|
{
|
|
select( collector[i] );
|
|
anyAdded = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( anyAdded )
|
|
{
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
return true;
|
|
}
|
|
else if( anySubtracted )
|
|
{
|
|
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool PCB_SELECTION_TOOL::selectCursor( bool aForceSelect, CLIENT_SELECTION_FILTER aClientFilter )
|
|
{
|
|
if( aForceSelect || m_selection.Empty() )
|
|
{
|
|
ClearSelection( true /*quiet mode*/ );
|
|
selectPoint( getViewControls()->GetCursorPosition( false ), false, nullptr, aClientFilter );
|
|
}
|
|
|
|
return !m_selection.Empty();
|
|
}
|
|
|
|
|
|
// Some navigation actions are allowed in selectMultiple
|
|
const TOOL_ACTION* allowedActions[] = { &ACTIONS::panUp, &ACTIONS::panDown,
|
|
&ACTIONS::panLeft, &ACTIONS::panRight,
|
|
&ACTIONS::cursorUp, &ACTIONS::cursorDown,
|
|
&ACTIONS::cursorLeft, &ACTIONS::cursorRight,
|
|
&ACTIONS::cursorUpFast, &ACTIONS::cursorDownFast,
|
|
&ACTIONS::cursorLeftFast, &ACTIONS::cursorRightFast,
|
|
&ACTIONS::zoomIn, &ACTIONS::zoomOut,
|
|
&ACTIONS::zoomInCenter, &ACTIONS::zoomOutCenter,
|
|
&ACTIONS::zoomCenter, &ACTIONS::zoomFitScreen,
|
|
&ACTIONS::zoomFitObjects, nullptr };
|
|
|
|
|
|
bool PCB_SELECTION_TOOL::selectMultiple()
|
|
{
|
|
bool cancelled = false; // Was the tool cancelled while it was running?
|
|
m_multiple = true; // Multiple selection mode is active
|
|
KIGFX::VIEW* view = getView();
|
|
|
|
KIGFX::PREVIEW::SELECTION_AREA area;
|
|
view->Add( &area );
|
|
|
|
bool anyAdded = false;
|
|
bool anySubtracted = false;
|
|
|
|
while( TOOL_EVENT* evt = Wait() )
|
|
{
|
|
int width = area.GetEnd().x - area.GetOrigin().x;
|
|
|
|
/* Selection mode depends on direction of drag-selection:
|
|
* Left > Right : Select objects that are fully enclosed by selection
|
|
* Right > Left : Select objects that are crossed by selection
|
|
*/
|
|
bool greedySelection = width >= 0 ? false : true;
|
|
|
|
if( view->IsMirroredX() )
|
|
greedySelection = !greedySelection;
|
|
|
|
m_frame->GetCanvas()->SetCurrentCursor( !greedySelection ? KICURSOR::SELECT_WINDOW
|
|
: KICURSOR::SELECT_LASSO );
|
|
|
|
if( evt->IsCancelInteractive() || evt->IsActivate() )
|
|
{
|
|
cancelled = true;
|
|
break;
|
|
}
|
|
|
|
if( evt->IsDrag( BUT_LEFT ) )
|
|
{
|
|
if( !m_drag_additive && !m_drag_subtractive )
|
|
{
|
|
if( m_selection.GetSize() > 0 )
|
|
{
|
|
anySubtracted = true;
|
|
ClearSelection( true /*quiet mode*/ );
|
|
}
|
|
}
|
|
|
|
// Start drawing a selection box
|
|
area.SetOrigin( evt->DragOrigin() );
|
|
area.SetEnd( evt->Position() );
|
|
area.SetAdditive( m_drag_additive );
|
|
area.SetSubtractive( m_drag_subtractive );
|
|
area.SetExclusiveOr( false );
|
|
|
|
view->SetVisible( &area, true );
|
|
view->Update( &area );
|
|
getViewControls()->SetAutoPan( true );
|
|
}
|
|
|
|
if( evt->IsMouseUp( BUT_LEFT ) )
|
|
{
|
|
getViewControls()->SetAutoPan( false );
|
|
|
|
// End drawing the selection box
|
|
view->SetVisible( &area, false );
|
|
|
|
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> candidates;
|
|
BOX2I selectionBox = area.ViewBBox();
|
|
view->Query( selectionBox, candidates ); // Get the list of nearby items
|
|
|
|
int height = area.GetEnd().y - area.GetOrigin().y;
|
|
|
|
// Construct an EDA_RECT to determine BOARD_ITEM selection
|
|
EDA_RECT selectionRect( (wxPoint) area.GetOrigin(), wxSize( width, height ) );
|
|
|
|
selectionRect.Normalize();
|
|
|
|
GENERAL_COLLECTOR collector;
|
|
std::set<BOARD_ITEM*> group_items;
|
|
|
|
for( PCB_GROUP* group : board()->Groups() )
|
|
{
|
|
// The currently entered group does not get limited
|
|
if( m_enteredGroup == group )
|
|
continue;
|
|
|
|
std::unordered_set<BOARD_ITEM*>& newset = group->GetItems();
|
|
|
|
// If we are not greedy and have selected the whole group, add just one item
|
|
// to allow it to be promoted to the group later
|
|
if( !greedySelection && selectionRect.Contains( group->GetBoundingBox() )
|
|
&& newset.size() )
|
|
{
|
|
collector.Append( *newset.begin() );
|
|
}
|
|
|
|
for( BOARD_ITEM* group_item : newset )
|
|
group_items.emplace( group_item );
|
|
}
|
|
|
|
for( auto it = candidates.begin(), it_end = candidates.end(); it != it_end; ++it )
|
|
{
|
|
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( it->first );
|
|
|
|
if( item && Selectable( item ) && item->HitTest( selectionRect, !greedySelection )
|
|
&& ( greedySelection || !group_items.count( item ) ) )
|
|
{
|
|
collector.Append( item );
|
|
}
|
|
}
|
|
|
|
// Apply the stateful filter
|
|
FilterCollectedItems( collector, true );
|
|
|
|
FilterCollectorForHierarchy( collector, true );
|
|
|
|
for( EDA_ITEM* i : collector )
|
|
{
|
|
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
|
|
|
|
if( m_subtractive || ( m_exclusive_or && item->IsSelected() ) )
|
|
{
|
|
unselect( item );
|
|
anySubtracted = true;
|
|
}
|
|
else
|
|
{
|
|
select( item );
|
|
anyAdded = true;
|
|
}
|
|
}
|
|
|
|
m_selection.SetIsHover( false );
|
|
|
|
// Inform other potentially interested tools
|
|
if( anyAdded )
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
else if( anySubtracted )
|
|
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
|
|
|
|
break; // Stop waiting for events
|
|
}
|
|
|
|
// Allow some actions for navigation
|
|
for( int i = 0; allowedActions[i]; ++i )
|
|
{
|
|
if( evt->IsAction( allowedActions[i] ) )
|
|
{
|
|
evt->SetPassEvent();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
getViewControls()->SetAutoPan( false );
|
|
|
|
// Stop drawing the selection box
|
|
view->Remove( &area );
|
|
m_multiple = false; // Multiple selection mode is inactive
|
|
|
|
if( !cancelled )
|
|
m_selection.ClearReferencePoint();
|
|
|
|
m_toolMgr->ProcessEvent( EVENTS::UninhibitSelectionEditing );
|
|
|
|
return cancelled;
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::disambiguateCursor( const TOOL_EVENT& aEvent )
|
|
{
|
|
wxMouseState keyboardState = wxGetMouseState();
|
|
|
|
setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(),
|
|
keyboardState.AltDown() );
|
|
|
|
m_skip_heuristics = true;
|
|
selectPoint( m_originalCursor, false, &m_canceledMenu );
|
|
m_skip_heuristics = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int PCB_SELECTION_TOOL::CursorSelection( const TOOL_EVENT& aEvent )
|
|
{
|
|
CLIENT_SELECTION_FILTER aClientFilter = aEvent.Parameter<CLIENT_SELECTION_FILTER>();
|
|
|
|
selectCursor( false, aClientFilter );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent )
|
|
{
|
|
ClearSelection();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::SelectAll( const TOOL_EVENT& aEvent )
|
|
{
|
|
KIGFX::VIEW* view = getView();
|
|
|
|
// hold all visible items
|
|
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> selectedItems;
|
|
|
|
// Filter the view items based on the selection box
|
|
BOX2I selectionBox;
|
|
|
|
// Intermediate step to allow filtering against hierarchy
|
|
GENERAL_COLLECTOR collection;
|
|
|
|
selectionBox.SetMaximum();
|
|
view->Query( selectionBox, selectedItems ); // Get the list of selected items
|
|
|
|
for( const KIGFX::VIEW::LAYER_ITEM_PAIR& item_pair : selectedItems )
|
|
{
|
|
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( item_pair.first );
|
|
|
|
if( !item || !Selectable( item ) || !itemPassesFilter( item, true ) )
|
|
continue;
|
|
|
|
collection.Append( item );
|
|
}
|
|
|
|
FilterCollectorForHierarchy( collection, true );
|
|
|
|
for( EDA_ITEM* item : collection )
|
|
select( item );
|
|
|
|
m_frame->GetCanvas()->ForceRefresh();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void connectedItemFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector,
|
|
PCB_SELECTION_TOOL* sTool )
|
|
{
|
|
// Narrow the collection down to a single BOARD_CONNECTED_ITEM for each represented net.
|
|
// All other items types are removed.
|
|
std::set<int> representedNets;
|
|
|
|
for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
|
|
{
|
|
BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( aCollector[i] );
|
|
|
|
if( !item )
|
|
aCollector.Remove( i );
|
|
else if ( representedNets.count( item->GetNetCode() ) )
|
|
aCollector.Remove( i );
|
|
else
|
|
representedNets.insert( item->GetNetCode() );
|
|
}
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::expandConnection( const TOOL_EVENT& aEvent )
|
|
{
|
|
unsigned initialCount = 0;
|
|
|
|
for( const EDA_ITEM* item : m_selection.GetItems() )
|
|
{
|
|
if( item->Type() == PCB_FOOTPRINT_T || BOARD_CONNECTED_ITEM::ClassOf( item ) )
|
|
initialCount++;
|
|
}
|
|
|
|
if( initialCount == 0 )
|
|
selectCursor( true, connectedItemFilter );
|
|
|
|
m_frame->SetStatusText( _( "Select/Expand Connection..." ) );
|
|
|
|
for( STOP_CONDITION stopCondition : { STOP_AT_JUNCTION, STOP_AT_PAD, STOP_NEVER } )
|
|
{
|
|
std::deque<EDA_ITEM*> selectedItems = m_selection.GetItems();
|
|
|
|
for( EDA_ITEM* item : selectedItems )
|
|
item->ClearTempFlags();
|
|
|
|
std::vector<BOARD_CONNECTED_ITEM*> startItems;
|
|
|
|
for( EDA_ITEM* item : selectedItems )
|
|
{
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
{
|
|
FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
|
|
|
|
for( PAD* pad : footprint->Pads() )
|
|
startItems.push_back( pad );
|
|
}
|
|
else if( BOARD_CONNECTED_ITEM::ClassOf( item ) )
|
|
{
|
|
startItems.push_back( static_cast<BOARD_CONNECTED_ITEM*>( item ) );
|
|
}
|
|
}
|
|
|
|
selectAllConnectedTracks( startItems, stopCondition );
|
|
|
|
if( m_selection.GetItems().size() > initialCount )
|
|
break;
|
|
}
|
|
|
|
m_frame->SetStatusText( wxEmptyString );
|
|
|
|
// Inform other potentially interested tools
|
|
if( m_selection.Size() > 0 )
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::selectAllConnectedTracks(
|
|
const std::vector<BOARD_CONNECTED_ITEM*>& aStartItems, STOP_CONDITION aStopCondition )
|
|
{
|
|
const LSET allCuMask = LSET::AllCuMask();
|
|
|
|
PROF_TIMER refreshTimer;
|
|
double refreshIntervalMs = 500; // Refresh display with this interval to indicate progress
|
|
int lastSelectionSize = m_selection.GetSize();
|
|
|
|
auto connectivity = board()->GetConnectivity();
|
|
|
|
std::unordered_map<VECTOR2I, std::vector<PCB_TRACK*>> trackMap;
|
|
std::unordered_map<VECTOR2I, PCB_VIA*> viaMap;
|
|
std::unordered_map<VECTOR2I, PAD*> padMap;
|
|
std::set<PAD*> startPadSet;
|
|
std::vector<BOARD_CONNECTED_ITEM*> cleanupItems;
|
|
std::vector<std::pair<VECTOR2I, LSET>> activePts;
|
|
|
|
for( BOARD_CONNECTED_ITEM* startItem : aStartItems )
|
|
{
|
|
// Track starting pads
|
|
if( startItem->Type() == PCB_PAD_T )
|
|
startPadSet.insert( static_cast<PAD*>( startItem ) );
|
|
}
|
|
|
|
for( BOARD_CONNECTED_ITEM* startItem : aStartItems )
|
|
{
|
|
if( startItem->HasFlag( SKIP_STRUCT ) ) // Skip already visited items
|
|
continue;
|
|
|
|
auto connectedItems = connectivity->GetConnectedItems( startItem,
|
|
{ PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, PCB_PAD_T }, true );
|
|
|
|
// Build maps of connected items
|
|
for( BOARD_CONNECTED_ITEM* item : connectedItems )
|
|
{
|
|
switch( item->Type() )
|
|
{
|
|
case PCB_ARC_T:
|
|
case PCB_TRACE_T:
|
|
{
|
|
PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
|
|
trackMap[track->GetStart()].push_back( track );
|
|
trackMap[track->GetEnd()].push_back( track );
|
|
break;
|
|
}
|
|
|
|
case PCB_VIA_T:
|
|
{
|
|
PCB_VIA* via = static_cast<PCB_VIA*>( item );
|
|
viaMap[via->GetStart()] = via;
|
|
break;
|
|
}
|
|
|
|
case PCB_PAD_T:
|
|
{
|
|
PAD* pad = static_cast<PAD*>( item );
|
|
padMap[pad->GetPosition()] = pad;
|
|
break;
|
|
}
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
// Set up the initial active points
|
|
switch( startItem->Type() )
|
|
{
|
|
case PCB_ARC_T:
|
|
case PCB_TRACE_T:
|
|
{
|
|
PCB_TRACK* track = static_cast<PCB_TRACK*>( startItem );
|
|
|
|
activePts.push_back( { track->GetStart(), track->GetLayerSet() } );
|
|
activePts.push_back( { track->GetEnd(), track->GetLayerSet() } );
|
|
break;
|
|
}
|
|
|
|
case PCB_VIA_T:
|
|
activePts.push_back( { startItem->GetPosition(), startItem->GetLayerSet() } );
|
|
break;
|
|
|
|
case PCB_PAD_T:
|
|
activePts.push_back( { startItem->GetPosition(), startItem->GetLayerSet() } );
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
bool expand = true;
|
|
int failSafe = 0;
|
|
|
|
// Iterative push from all active points
|
|
while( expand && failSafe++ < 100000 )
|
|
{
|
|
expand = false;
|
|
|
|
for( int i = activePts.size() - 1; i >= 0; --i )
|
|
{
|
|
VECTOR2I pt = activePts[i].first;
|
|
LSET layerSetCu = activePts[i].second & allCuMask;
|
|
|
|
auto viaIt = viaMap.find( pt );
|
|
auto padIt = padMap.find( pt );
|
|
|
|
bool gotVia = ( viaIt != viaMap.end() )
|
|
&& ( layerSetCu & ( viaIt->second->GetLayerSet() ) ).any();
|
|
|
|
bool gotPad = ( padIt != padMap.end() )
|
|
&& ( layerSetCu & ( padIt->second->GetLayerSet() ) ).any();
|
|
|
|
bool gotNonStartPad =
|
|
gotPad && ( startPadSet.find( padIt->second ) == startPadSet.end() );
|
|
|
|
if( aStopCondition == STOP_AT_JUNCTION )
|
|
{
|
|
size_t pt_count = 0;
|
|
|
|
for( PCB_TRACK* track : trackMap[pt] )
|
|
{
|
|
if( track->GetStart() != track->GetEnd()
|
|
&& layerSetCu.Contains( track->GetLayer() ) )
|
|
{
|
|
pt_count++;
|
|
}
|
|
}
|
|
|
|
if( pt_count > 2 || gotVia || gotNonStartPad )
|
|
{
|
|
activePts.erase( activePts.begin() + i );
|
|
continue;
|
|
}
|
|
}
|
|
else if( aStopCondition == STOP_AT_PAD )
|
|
{
|
|
if( gotNonStartPad )
|
|
{
|
|
activePts.erase( activePts.begin() + i );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if( gotPad )
|
|
{
|
|
PAD* pad = padIt->second;
|
|
|
|
if( !pad->HasFlag( SKIP_STRUCT ) )
|
|
{
|
|
pad->SetFlags( SKIP_STRUCT );
|
|
cleanupItems.push_back( pad );
|
|
|
|
activePts.push_back( { pad->GetPosition(), pad->GetLayerSet() } );
|
|
expand = true;
|
|
}
|
|
}
|
|
|
|
for( PCB_TRACK* track : trackMap[pt] )
|
|
{
|
|
if( !layerSetCu.Contains( track->GetLayer() ) )
|
|
continue;
|
|
|
|
if( !track->IsSelected() )
|
|
select( track );
|
|
|
|
if( !track->HasFlag( SKIP_STRUCT ) )
|
|
{
|
|
track->SetFlags( SKIP_STRUCT );
|
|
cleanupItems.push_back( track );
|
|
|
|
if( track->GetStart() == pt )
|
|
activePts.push_back( { track->GetEnd(), track->GetLayerSet() } );
|
|
else
|
|
activePts.push_back( { track->GetStart(), track->GetLayerSet() } );
|
|
|
|
expand = true;
|
|
}
|
|
}
|
|
|
|
if( viaMap.count( pt ) )
|
|
{
|
|
PCB_VIA* via = viaMap[pt];
|
|
|
|
if( !via->IsSelected() )
|
|
select( via );
|
|
|
|
if( !via->HasFlag( SKIP_STRUCT ) )
|
|
{
|
|
via->SetFlags( SKIP_STRUCT );
|
|
cleanupItems.push_back( via );
|
|
|
|
activePts.push_back( { via->GetPosition(), via->GetLayerSet() } );
|
|
expand = true;
|
|
}
|
|
}
|
|
|
|
activePts.erase( activePts.begin() + i );
|
|
}
|
|
|
|
// Refresh display for the feel of progress
|
|
if( refreshTimer.msecs() >= refreshIntervalMs )
|
|
{
|
|
if( m_selection.Size() != lastSelectionSize )
|
|
{
|
|
m_frame->GetCanvas()->ForceRefresh();
|
|
lastSelectionSize = m_selection.Size();
|
|
}
|
|
|
|
refreshTimer.Start();
|
|
}
|
|
}
|
|
}
|
|
|
|
for( BOARD_CONNECTED_ITEM* item : cleanupItems )
|
|
{
|
|
item->ClearFlags( SKIP_STRUCT );
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::selectAllItemsOnNet( int aNetCode, bool aSelect )
|
|
{
|
|
constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, EOT };
|
|
auto connectivity = board()->GetConnectivity();
|
|
|
|
for( BOARD_CONNECTED_ITEM* item : connectivity->GetNetItems( aNetCode, types ) )
|
|
{
|
|
if( itemPassesFilter( item, true ) )
|
|
aSelect ? select( item ) : unselect( item );
|
|
}
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::selectNet( const TOOL_EVENT& aEvent )
|
|
{
|
|
bool select = aEvent.IsAction( &PCB_ACTIONS::selectNet );
|
|
|
|
// If we've been passed an argument, just select that netcode1
|
|
int netcode = aEvent.Parameter<intptr_t>();
|
|
|
|
if( netcode > 0 )
|
|
{
|
|
selectAllItemsOnNet( netcode, select );
|
|
return 0;
|
|
}
|
|
|
|
if( !selectCursor() )
|
|
return 0;
|
|
|
|
// copy the selection, since we're going to iterate and modify
|
|
auto selection = m_selection.GetItems();
|
|
|
|
for( EDA_ITEM* i : selection )
|
|
{
|
|
BOARD_CONNECTED_ITEM* connItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( i );
|
|
|
|
if( connItem )
|
|
selectAllItemsOnNet( connItem->GetNetCode(), select );
|
|
}
|
|
|
|
// Inform other potentially interested tools
|
|
if( m_selection.Size() > 0 )
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::selectAllItemsOnSheet( wxString& aSheetPath )
|
|
{
|
|
std::vector<BOARD_ITEM*> footprints;
|
|
|
|
// store all footprints that are on that sheet path
|
|
for( FOOTPRINT* footprint : board()->Footprints() )
|
|
{
|
|
if( footprint == nullptr )
|
|
continue;
|
|
|
|
wxString footprint_path = footprint->GetPath().AsString().BeforeLast( '/' );
|
|
|
|
if( aSheetPath.IsEmpty() )
|
|
aSheetPath += '/';
|
|
|
|
if( footprint_path == aSheetPath )
|
|
footprints.push_back( footprint );
|
|
}
|
|
|
|
for( BOARD_ITEM* i : footprints )
|
|
{
|
|
if( i != nullptr )
|
|
select( i );
|
|
}
|
|
|
|
selectConnections( footprints );
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::selectConnections( const std::vector<BOARD_ITEM*>& aItems )
|
|
{
|
|
// Generate a list of all pads, and of all nets they belong to.
|
|
std::list<int> netcodeList;
|
|
std::vector<BOARD_CONNECTED_ITEM*> padList;
|
|
|
|
for( BOARD_ITEM* item : aItems )
|
|
{
|
|
switch( item->Type() )
|
|
{
|
|
case PCB_FOOTPRINT_T:
|
|
{
|
|
for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
|
|
{
|
|
if( pad->IsConnected() )
|
|
{
|
|
netcodeList.push_back( pad->GetNetCode() );
|
|
padList.push_back( pad );
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case PCB_PAD_T:
|
|
{
|
|
PAD* pad = static_cast<PAD*>( item );
|
|
|
|
if( pad->IsConnected() )
|
|
{
|
|
netcodeList.push_back( pad->GetNetCode() );
|
|
padList.push_back( pad );
|
|
}
|
|
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
// Sort for binary search
|
|
std::sort( padList.begin(), padList.end() );
|
|
|
|
// remove all duplicates
|
|
netcodeList.sort();
|
|
netcodeList.unique();
|
|
|
|
selectAllConnectedTracks( padList, STOP_AT_PAD );
|
|
|
|
// now we need to find all footprints that are connected to each of these nets then we need
|
|
// to determine if these footprints are in the list of footprints
|
|
std::vector<int> removeCodeList;
|
|
constexpr KICAD_T padType[] = { PCB_PAD_T, EOT };
|
|
|
|
for( int netCode : netcodeList )
|
|
{
|
|
for( BOARD_CONNECTED_ITEM* mitem :
|
|
board()->GetConnectivity()->GetNetItems( netCode, padType ) )
|
|
{
|
|
if( mitem->Type() == PCB_PAD_T
|
|
&& !std::binary_search( padList.begin(), padList.end(), mitem ) )
|
|
{
|
|
// if we cannot find the pad in the padList then we can
|
|
// assume that that pad should not be used, therefore
|
|
// invalidate this netcode.
|
|
removeCodeList.push_back( netCode );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for( int removeCode : removeCodeList )
|
|
{
|
|
netcodeList.remove( removeCode );
|
|
}
|
|
|
|
std::vector<BOARD_CONNECTED_ITEM*> localConnectionList;
|
|
constexpr KICAD_T trackViaType[] = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, EOT };
|
|
|
|
for( int netCode : netcodeList )
|
|
{
|
|
for( BOARD_CONNECTED_ITEM* item :
|
|
board()->GetConnectivity()->GetNetItems( netCode, trackViaType ) )
|
|
localConnectionList.push_back( item );
|
|
}
|
|
|
|
for( BOARD_CONNECTED_ITEM* i : localConnectionList )
|
|
{
|
|
if( i != nullptr )
|
|
select( i );
|
|
}
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::syncSelection( const TOOL_EVENT& aEvent )
|
|
{
|
|
std::vector<BOARD_ITEM*>* items = aEvent.Parameter<std::vector<BOARD_ITEM*>*>();
|
|
|
|
if( items )
|
|
doSyncSelection( *items, false );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::syncSelectionWithNets( const TOOL_EVENT& aEvent )
|
|
{
|
|
std::vector<BOARD_ITEM*>* items = aEvent.Parameter<std::vector<BOARD_ITEM*>*>();
|
|
|
|
if( items )
|
|
doSyncSelection( *items, true );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::doSyncSelection( const std::vector<BOARD_ITEM*>& aItems, bool aWithNets )
|
|
{
|
|
ClearSelection( true /*quiet mode*/ );
|
|
|
|
// Perform individual selection of each item before processing the event.
|
|
for( BOARD_ITEM* item : aItems )
|
|
select( item );
|
|
|
|
if( aWithNets )
|
|
selectConnections( aItems );
|
|
|
|
EDA_RECT bbox = m_selection.GetBoundingBox();
|
|
|
|
if( bbox.GetWidth() > 0 && bbox.GetHeight() > 0 )
|
|
{
|
|
if( m_frame->GetPcbNewSettings()->m_CrossProbing.center_on_items )
|
|
{
|
|
if( m_frame->GetPcbNewSettings()->m_CrossProbing.zoom_to_fit )
|
|
zoomFitCrossProbeBBox( bbox );
|
|
|
|
m_frame->FocusOnLocation( bbox.Centre() );
|
|
}
|
|
}
|
|
|
|
view()->UpdateAllLayersColor();
|
|
|
|
m_frame->GetCanvas()->ForceRefresh();
|
|
|
|
if( m_selection.Size() > 0 )
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::selectSheetContents( const TOOL_EVENT& aEvent )
|
|
{
|
|
ClearSelection( true /*quiet mode*/ );
|
|
wxString sheetPath = *aEvent.Parameter<wxString*>();
|
|
|
|
selectAllItemsOnSheet( sheetPath );
|
|
|
|
zoomFitSelection();
|
|
|
|
if( m_selection.Size() > 0 )
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::selectSameSheet( const TOOL_EVENT& aEvent )
|
|
{
|
|
// this function currently only supports footprints since they are only on one sheet.
|
|
EDA_ITEM* item = m_selection.Front();
|
|
|
|
if( !item )
|
|
return 0;
|
|
|
|
if( item->Type() != PCB_FOOTPRINT_T )
|
|
return 0;
|
|
|
|
FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( item );
|
|
|
|
if( footprint->GetPath().empty() )
|
|
return 0;
|
|
|
|
ClearSelection( true /*quiet mode*/ );
|
|
|
|
// get the sheet path only.
|
|
wxString sheetPath = footprint->GetPath().AsString().BeforeLast( '/' );
|
|
|
|
if( sheetPath.IsEmpty() )
|
|
sheetPath += '/';
|
|
|
|
selectAllItemsOnSheet( sheetPath );
|
|
|
|
// Inform other potentially interested tools
|
|
if( m_selection.Size() > 0 )
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::zoomFitSelection()
|
|
{
|
|
// Should recalculate the view to zoom in on the selection.
|
|
auto selectionBox = m_selection.GetBoundingBox();
|
|
auto view = getView();
|
|
|
|
VECTOR2D screenSize = view->ToWorld( m_frame->GetCanvas()->GetClientSize(), false );
|
|
screenSize.x = std::max( 10.0, screenSize.x );
|
|
screenSize.y = std::max( 10.0, screenSize.y );
|
|
|
|
if( selectionBox.GetWidth() != 0 || selectionBox.GetHeight() != 0 )
|
|
{
|
|
VECTOR2D vsize = selectionBox.GetSize();
|
|
double scale = view->GetScale()
|
|
/ std::max( fabs( vsize.x / screenSize.x ), fabs( vsize.y / screenSize.y ) );
|
|
view->SetScale( scale );
|
|
view->SetCenter( selectionBox.Centre() );
|
|
view->Add( &m_selection );
|
|
}
|
|
|
|
m_frame->GetCanvas()->ForceRefresh();
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::zoomFitCrossProbeBBox( EDA_RECT bbox )
|
|
{
|
|
// Should recalculate the view to zoom in on the bbox.
|
|
auto view = getView();
|
|
|
|
if( bbox.GetWidth() == 0 && bbox.GetHeight() != 0 )
|
|
return;
|
|
|
|
//#define DEFAULT_PCBNEW_CODE // Un-comment for normal full zoom KiCad algorithm
|
|
#ifdef DEFAULT_PCBNEW_CODE
|
|
auto bbSize = bbox.Inflate( bbox.GetWidth() * 0.2f ).GetSize();
|
|
auto screenSize = view->ToWorld( GetCanvas()->GetClientSize(), false );
|
|
|
|
// The "fabs" on x ensures the right answer when the view is flipped
|
|
screenSize.x = std::max( 10.0, fabs( screenSize.x ) );
|
|
screenSize.y = std::max( 10.0, screenSize.y );
|
|
double ratio = std::max( fabs( bbSize.x / screenSize.x ), fabs( bbSize.y / screenSize.y ) );
|
|
|
|
// Try not to zoom on every cross-probe; it gets very noisy
|
|
if( crossProbingSettings.zoom_to_fit && ( ratio < 0.5 || ratio > 1.0 ) )
|
|
view->SetScale( view->GetScale() / ratio );
|
|
#endif // DEFAULT_PCBNEW_CODE
|
|
|
|
#ifndef DEFAULT_PCBNEW_CODE // Do the scaled zoom
|
|
auto bbSize = bbox.Inflate( bbox.GetWidth() * 0.2f ).GetSize();
|
|
auto screenSize = view->ToWorld( m_frame->GetCanvas()->GetClientSize(), false );
|
|
|
|
// This code tries to come up with a zoom factor that doesn't simply zoom in
|
|
// to the cross probed component, but instead shows a reasonable amount of the
|
|
// circuit around it to provide context. This reduces or eliminates the need
|
|
// to manually change the zoom because it's too close.
|
|
|
|
// Using the default text height as a constant to compare against, use the
|
|
// height of the bounding box of visible items for a footprint to figure out
|
|
// if this is a big footprint (like a processor) or a small footprint (like a resistor).
|
|
// This ratio is not useful by itself as a scaling factor. It must be "bent" to
|
|
// provide good scaling at varying component sizes. Bigger components need less
|
|
// scaling than small ones.
|
|
double currTextHeight = Millimeter2iu( DEFAULT_TEXT_SIZE );
|
|
|
|
double compRatio = bbSize.y / currTextHeight; // Ratio of component to text height
|
|
|
|
// This will end up as the scaling factor we apply to "ratio".
|
|
double compRatioBent = 1.0;
|
|
|
|
// This is similar to the original KiCad code that scaled the zoom to make sure
|
|
// components were visible on screen. It's simply a ratio of screen size to
|
|
// component size, and its job is to zoom in to make the component fullscreen.
|
|
// Earlier in the code the component BBox is given a 20% margin to add some
|
|
// breathing room. We compare the height of this enlarged component bbox to the
|
|
// default text height. If a component will end up with the sides clipped, we
|
|
// adjust later to make sure it fits on screen.
|
|
//
|
|
// The "fabs" on x ensures the right answer when the view is flipped
|
|
screenSize.x = std::max( 10.0, fabs( screenSize.x ) );
|
|
screenSize.y = std::max( 10.0, screenSize.y );
|
|
double ratio = std::max( -1.0, fabs( bbSize.y / screenSize.y ) );
|
|
|
|
// Original KiCad code for how much to scale the zoom
|
|
double kicadRatio =
|
|
std::max( fabs( bbSize.x / screenSize.x ), fabs( bbSize.y / screenSize.y ) );
|
|
|
|
// LUT to scale zoom ratio to provide reasonable schematic context. Must work
|
|
// with footprints of varying sizes (e.g. 0402 package and 200 pin BGA).
|
|
// "first" is used as the input and "second" as the output
|
|
//
|
|
// "first" = compRatio (footprint height / default text height)
|
|
// "second" = Amount to scale ratio by
|
|
std::vector<std::pair<double, double>> lut{
|
|
{ 1, 8 }, { 1.5, 5 }, { 3, 3 }, { 4.5, 2.5 }, { 8, 2.0 },
|
|
{ 12, 1.7 }, { 16, 1.5 }, { 24, 1.3 }, { 32, 1.0 },
|
|
};
|
|
|
|
|
|
std::vector<std::pair<double, double>>::iterator it;
|
|
|
|
compRatioBent = lut.back().second; // Large component default
|
|
|
|
if( compRatio >= lut.front().first )
|
|
{
|
|
// Use LUT to do linear interpolation of "compRatio" within "first", then
|
|
// use that result to linearly interpolate "second" which gives the scaling
|
|
// factor needed.
|
|
|
|
for( it = lut.begin(); it < lut.end() - 1; it++ )
|
|
{
|
|
if( it->first <= compRatio && next( it )->first >= compRatio )
|
|
{
|
|
double diffx = compRatio - it->first;
|
|
double diffn = next( it )->first - it->first;
|
|
|
|
compRatioBent = it->second + ( next( it )->second - it->second ) * diffx / diffn;
|
|
break; // We have our interpolated value
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
compRatioBent = lut.front().second; // Small component default
|
|
}
|
|
|
|
// If the width of the part we're probing is bigger than what the screen width will be
|
|
// after the zoom, then punt and use the KiCad zoom algorithm since it guarantees the
|
|
// part's width will be encompassed within the screen. This will apply to parts that
|
|
// are much wider than they are tall.
|
|
|
|
if( bbSize.x > screenSize.x * ratio * compRatioBent )
|
|
{
|
|
// Use standard KiCad zoom algorithm for parts too wide to fit screen/
|
|
ratio = kicadRatio;
|
|
compRatioBent = 1.0; // Reset so we don't modify the "KiCad" ratio
|
|
wxLogTrace( "CROSS_PROBE_SCALE",
|
|
"Part TOO WIDE for screen. Using normal KiCad zoom ratio: %1.5f", ratio );
|
|
}
|
|
|
|
// Now that "compRatioBent" holds our final scaling factor we apply it to the original
|
|
// fullscreen zoom ratio to arrive at the final ratio itself.
|
|
ratio *= compRatioBent;
|
|
|
|
bool alwaysZoom = false; // DEBUG - allows us to minimize zooming or not
|
|
|
|
// Try not to zoom on every cross-probe; it gets very noisy
|
|
if( ( ratio < 0.5 || ratio > 1.0 ) || alwaysZoom )
|
|
view->SetScale( view->GetScale() / ratio );
|
|
#endif // ifndef DEFAULT_PCBNEW_CODE
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::FindItem( BOARD_ITEM* aItem )
|
|
{
|
|
bool cleared = false;
|
|
|
|
if( m_selection.GetSize() > 0 )
|
|
{
|
|
// Don't fire an event now; most of the time it will be redundant as we're about to
|
|
// fire a SelectedEvent.
|
|
cleared = true;
|
|
ClearSelection( true /*quiet mode*/ );
|
|
}
|
|
|
|
if( aItem )
|
|
{
|
|
switch( aItem->Type() )
|
|
{
|
|
case PCB_NETINFO_T:
|
|
{
|
|
int netCode = static_cast<NETINFO_ITEM*>( aItem )->GetNetCode();
|
|
|
|
if( netCode > 0 )
|
|
{
|
|
selectAllItemsOnNet( netCode, true );
|
|
m_frame->FocusOnLocation( aItem->GetCenter() );
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
select( aItem );
|
|
m_frame->FocusOnLocation( aItem->GetPosition() );
|
|
}
|
|
|
|
// If the item has a bounding box, then zoom out if needed
|
|
if( aItem->GetBoundingBox().GetHeight() > 0 && aItem->GetBoundingBox().GetWidth() > 0 )
|
|
{
|
|
// This adds some margin
|
|
double marginFactor = 2;
|
|
|
|
KIGFX::PCB_VIEW* pcbView = canvas()->GetView();
|
|
BOX2D screenBox = pcbView->GetViewport();
|
|
wxSize screenSize = wxSize( screenBox.GetWidth(), screenBox.GetHeight() );
|
|
screenSize /= marginFactor;
|
|
|
|
wxPoint screenPos = wxPoint( screenBox.GetOrigin() );
|
|
EDA_RECT* screenRect = new EDA_RECT( screenPos, screenSize );
|
|
|
|
if( !screenRect->Contains( aItem->GetBoundingBox() ) )
|
|
{
|
|
double scaleX = screenSize.GetWidth()
|
|
/ static_cast<double>( aItem->GetBoundingBox().GetWidth() );
|
|
double scaleY = screenSize.GetHeight()
|
|
/ static_cast<double>( aItem->GetBoundingBox().GetHeight() );
|
|
|
|
|
|
scaleX /= marginFactor;
|
|
scaleY /= marginFactor;
|
|
|
|
double scale = scaleX > scaleY ? scaleY : scaleX;
|
|
|
|
if( scale < 1 ) // Don't zoom in, only zoom out
|
|
{
|
|
pcbView->SetScale( pcbView->GetScale() * ( scale ) );
|
|
|
|
//Let's refocus because there is an algorithm to avoid dialogs in there.
|
|
m_frame->FocusOnLocation( aItem->GetCenter() );
|
|
}
|
|
}
|
|
|
|
delete screenRect;
|
|
}
|
|
// Inform other potentially interested tools
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
}
|
|
else if( cleared )
|
|
{
|
|
m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
|
|
}
|
|
|
|
m_frame->GetCanvas()->ForceRefresh();
|
|
}
|
|
|
|
|
|
/**
|
|
* Determine if an item is included by the filter specified.
|
|
*
|
|
* @return true if aItem should be selected by this filter (i..e not filtered out)
|
|
*/
|
|
static bool itemIsIncludedByFilter( const BOARD_ITEM& aItem, const BOARD& aBoard,
|
|
const DIALOG_FILTER_SELECTION::OPTIONS& aFilterOptions )
|
|
{
|
|
bool include = true;
|
|
const PCB_LAYER_ID layer = aItem.GetLayer();
|
|
|
|
// if the item needs to be checked against the options
|
|
if( include )
|
|
{
|
|
switch( aItem.Type() )
|
|
{
|
|
case PCB_FOOTPRINT_T:
|
|
{
|
|
const FOOTPRINT& footprint = static_cast<const FOOTPRINT&>( aItem );
|
|
|
|
include = aFilterOptions.includeModules;
|
|
|
|
if( include && !aFilterOptions.includeLockedModules )
|
|
include = !footprint.IsLocked();
|
|
|
|
break;
|
|
}
|
|
case PCB_TRACE_T:
|
|
case PCB_ARC_T:
|
|
include = aFilterOptions.includeTracks;
|
|
break;
|
|
|
|
case PCB_VIA_T:
|
|
include = aFilterOptions.includeVias;
|
|
break;
|
|
|
|
case PCB_FP_ZONE_T:
|
|
case PCB_ZONE_T:
|
|
include = aFilterOptions.includeZones;
|
|
break;
|
|
|
|
case PCB_SHAPE_T:
|
|
case PCB_TARGET_T:
|
|
case PCB_DIM_ALIGNED_T:
|
|
case PCB_DIM_CENTER_T:
|
|
case PCB_DIM_RADIAL_T:
|
|
case PCB_DIM_ORTHOGONAL_T:
|
|
case PCB_DIM_LEADER_T:
|
|
case PCB_FP_DIM_ALIGNED_T:
|
|
case PCB_FP_DIM_CENTER_T:
|
|
case PCB_FP_DIM_RADIAL_T:
|
|
case PCB_FP_DIM_ORTHOGONAL_T:
|
|
case PCB_FP_DIM_LEADER_T:
|
|
if( layer == Edge_Cuts )
|
|
include = aFilterOptions.includeBoardOutlineLayer;
|
|
else
|
|
include = aFilterOptions.includeItemsOnTechLayers;
|
|
break;
|
|
|
|
case PCB_FP_TEXT_T:
|
|
case PCB_FP_TEXTBOX_T:
|
|
case PCB_TEXT_T:
|
|
case PCB_TEXTBOX_T:
|
|
include = aFilterOptions.includePcbTexts;
|
|
break;
|
|
|
|
default:
|
|
// no filtering, just select it
|
|
break;
|
|
}
|
|
}
|
|
|
|
return include;
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::filterSelection( const TOOL_EVENT& aEvent )
|
|
{
|
|
const BOARD& board = *getModel<BOARD>();
|
|
DIALOG_FILTER_SELECTION::OPTIONS& opts = m_priv->m_filterOpts;
|
|
DIALOG_FILTER_SELECTION dlg( m_frame, opts );
|
|
|
|
const int cmd = dlg.ShowModal();
|
|
|
|
if( cmd != wxID_OK )
|
|
return 0;
|
|
|
|
// copy current selection
|
|
std::deque<EDA_ITEM*> selection = m_selection.GetItems();
|
|
|
|
ClearSelection( true /*quiet mode*/ );
|
|
|
|
// re-select items from the saved selection according to the dialog options
|
|
for( EDA_ITEM* i : selection )
|
|
{
|
|
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
|
|
bool include = itemIsIncludedByFilter( *item, board, opts );
|
|
|
|
if( include )
|
|
select( item );
|
|
}
|
|
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect )
|
|
{
|
|
if( aCollector.GetCount() == 0 )
|
|
return;
|
|
|
|
std::set<BOARD_ITEM*> rejected;
|
|
|
|
for( EDA_ITEM* i : aCollector )
|
|
{
|
|
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
|
|
|
|
if( !itemPassesFilter( item, aMultiSelect ) )
|
|
rejected.insert( item );
|
|
}
|
|
|
|
for( BOARD_ITEM* item : rejected )
|
|
aCollector.Remove( item );
|
|
}
|
|
|
|
|
|
bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect )
|
|
{
|
|
if( !m_filter.lockedItems )
|
|
{
|
|
if( aItem->IsLocked() || ( aItem->GetParent() && aItem->GetParent()->IsLocked() ) )
|
|
{
|
|
if( aItem->Type() == PCB_PAD_T && !aMultiSelect )
|
|
{
|
|
// allow a single pad to be selected -- there are a lot of operations that
|
|
// require this so we allow this one inconsistency
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch( aItem->Type() )
|
|
{
|
|
case PCB_FOOTPRINT_T:
|
|
if( !m_filter.footprints )
|
|
return false;
|
|
|
|
break;
|
|
|
|
case PCB_PAD_T:
|
|
if( !m_filter.pads )
|
|
return false;
|
|
|
|
break;
|
|
|
|
case PCB_TRACE_T:
|
|
case PCB_ARC_T:
|
|
if( !m_filter.tracks )
|
|
return false;
|
|
|
|
break;
|
|
|
|
case PCB_VIA_T:
|
|
if( !m_filter.vias )
|
|
return false;
|
|
|
|
break;
|
|
|
|
case PCB_FP_ZONE_T:
|
|
case PCB_ZONE_T:
|
|
{
|
|
ZONE* zone = static_cast<ZONE*>( aItem );
|
|
|
|
if( ( !m_filter.zones && !zone->GetIsRuleArea() )
|
|
|| ( !m_filter.keepouts && zone->GetIsRuleArea() ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PCB_FP_SHAPE_T:
|
|
case PCB_SHAPE_T:
|
|
case PCB_TARGET_T:
|
|
case PCB_BITMAP_T:
|
|
if( !m_filter.graphics )
|
|
return false;
|
|
|
|
break;
|
|
|
|
case PCB_FP_TEXT_T:
|
|
case PCB_FP_TEXTBOX_T:
|
|
case PCB_TEXT_T:
|
|
case PCB_TEXTBOX_T:
|
|
if( !m_filter.text )
|
|
return false;
|
|
|
|
break;
|
|
|
|
case PCB_DIM_ALIGNED_T:
|
|
case PCB_DIM_CENTER_T:
|
|
case PCB_DIM_RADIAL_T:
|
|
case PCB_DIM_ORTHOGONAL_T:
|
|
case PCB_DIM_LEADER_T:
|
|
case PCB_FP_DIM_ALIGNED_T:
|
|
case PCB_FP_DIM_CENTER_T:
|
|
case PCB_FP_DIM_RADIAL_T:
|
|
case PCB_FP_DIM_ORTHOGONAL_T:
|
|
case PCB_FP_DIM_LEADER_T:
|
|
if( !m_filter.dimensions )
|
|
return false;
|
|
|
|
break;
|
|
|
|
default:
|
|
if( !m_filter.otherItems )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::ClearSelection( bool aQuietMode )
|
|
{
|
|
if( m_selection.Empty() )
|
|
return;
|
|
|
|
while( m_selection.GetSize() )
|
|
unhighlight( m_selection.Front(), SELECTED, &m_selection );
|
|
|
|
view()->Update( &m_selection );
|
|
|
|
m_selection.SetIsHover( false );
|
|
m_selection.ClearReferencePoint();
|
|
|
|
// Inform other potentially interested tools
|
|
if( !aQuietMode )
|
|
{
|
|
m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
|
|
m_toolMgr->RunAction( PCB_ACTIONS::hideDynamicRatsnest, true );
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::RebuildSelection()
|
|
{
|
|
m_selection.Clear();
|
|
|
|
bool enteredGroupFound = false;
|
|
|
|
INSPECTOR_FUNC inspector =
|
|
[&]( EDA_ITEM* item, void* testData )
|
|
{
|
|
if( item->IsSelected() )
|
|
{
|
|
EDA_ITEM* parent = item->GetParent();
|
|
|
|
// Let selected parents handle their children.
|
|
if( parent && parent->IsSelected() )
|
|
return INSPECT_RESULT::CONTINUE;
|
|
|
|
highlight( item, SELECTED, &m_selection );
|
|
}
|
|
|
|
if( item == m_enteredGroup )
|
|
{
|
|
item->SetFlags( ENTERED );
|
|
enteredGroupFound = true;
|
|
}
|
|
else
|
|
{
|
|
item->ClearFlags( ENTERED );
|
|
}
|
|
|
|
return INSPECT_RESULT::CONTINUE;
|
|
};
|
|
|
|
board()->Visit( inspector, nullptr, m_isFootprintEditor ? GENERAL_COLLECTOR::FootprintItems
|
|
: GENERAL_COLLECTOR::AllBoardItems );
|
|
|
|
if( !enteredGroupFound )
|
|
{
|
|
m_enteredGroupOverlay.Clear();
|
|
m_enteredGroup = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibilityOnly ) const
|
|
{
|
|
const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
|
|
|
|
auto visibleLayers =
|
|
[&]()
|
|
{
|
|
if( m_isFootprintEditor )
|
|
{
|
|
LSET set;
|
|
|
|
for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() )
|
|
set.set( layer, view()->IsLayerVisible( layer ) );
|
|
|
|
return set;
|
|
}
|
|
else
|
|
{
|
|
return board()->GetVisibleLayers();
|
|
}
|
|
};
|
|
|
|
if( settings->GetHighContrast() )
|
|
{
|
|
std::set<unsigned int> activeLayers = settings->GetHighContrastLayers();
|
|
bool onActiveLayer = false;
|
|
|
|
for( unsigned int layer : activeLayers )
|
|
{
|
|
// NOTE: Only checking the regular layers (not GAL meta-layers)
|
|
if( layer < PCB_LAYER_ID_COUNT && aItem->IsOnLayer( ToLAYER_ID( layer ) ) )
|
|
{
|
|
onActiveLayer = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !onActiveLayer ) // We do not want to select items that are in the background
|
|
return false;
|
|
}
|
|
|
|
if( aItem->Type() == PCB_FOOTPRINT_T )
|
|
{
|
|
// In footprint editor, we do not want to select the footprint itself.
|
|
if( m_isFootprintEditor )
|
|
return false;
|
|
|
|
// Allow selection of footprints if some part of the footprint is visible.
|
|
const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( aItem );
|
|
|
|
// If the footprint has no items except the reference and value fields, include the
|
|
// footprint in the selections.
|
|
if( footprint->GraphicalItems().empty()
|
|
&& footprint->Pads().empty()
|
|
&& footprint->Zones().empty() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
for( const BOARD_ITEM* item : footprint->GraphicalItems() )
|
|
{
|
|
if( Selectable( item, true ) )
|
|
return true;
|
|
}
|
|
|
|
for( const PAD* pad : footprint->Pads() )
|
|
{
|
|
if( Selectable( pad, true ) )
|
|
return true;
|
|
}
|
|
|
|
for( const ZONE* zone : footprint->Zones() )
|
|
{
|
|
if( Selectable( zone, true ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
else if( aItem->Type() == PCB_GROUP_T )
|
|
{
|
|
PCB_GROUP* group = const_cast<PCB_GROUP*>( static_cast<const PCB_GROUP*>( aItem ) );
|
|
|
|
// Similar to logic for footprint, a group is selectable if any of its members are.
|
|
// (This recurses.)
|
|
for( BOARD_ITEM* item : group->GetItems() )
|
|
{
|
|
if( Selectable( item, true ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const ZONE* zone = nullptr;
|
|
const PCB_VIA* via = nullptr;
|
|
const PAD* pad = nullptr;
|
|
const FP_TEXT* text = nullptr;
|
|
|
|
switch( aItem->Type() )
|
|
{
|
|
case PCB_ZONE_T:
|
|
case PCB_FP_ZONE_T:
|
|
if( !board()->IsElementVisible( LAYER_ZONES ) )
|
|
return false;
|
|
|
|
zone = static_cast<const ZONE*>( aItem );
|
|
|
|
// A footprint zone is only selectable within the footprint editor
|
|
if( zone->GetParent()
|
|
&& zone->GetParent()->Type() == PCB_FOOTPRINT_T
|
|
&& !m_isFootprintEditor
|
|
&& !checkVisibilityOnly )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// zones can exist on multiple layers!
|
|
if( !( zone->GetLayerSet() & visibleLayers() ).any() )
|
|
return false;
|
|
|
|
break;
|
|
|
|
case PCB_TRACE_T:
|
|
case PCB_ARC_T:
|
|
if( !board()->IsElementVisible( LAYER_TRACKS ) )
|
|
return false;
|
|
|
|
if( m_isFootprintEditor )
|
|
{
|
|
if( !view()->IsLayerVisible( aItem->GetLayer() ) )
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if( !board()->IsLayerVisible( aItem->GetLayer() ) )
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
|
|
case PCB_VIA_T:
|
|
if( !board()->IsElementVisible( LAYER_VIAS ) )
|
|
return false;
|
|
|
|
via = static_cast<const PCB_VIA*>( aItem );
|
|
|
|
// For vias it is enough if only one of its layers is visible
|
|
if( !( visibleLayers() & via->GetLayerSet() ).any() )
|
|
return false;
|
|
|
|
break;
|
|
|
|
case PCB_FP_TEXT_T:
|
|
if( m_isFootprintEditor )
|
|
{
|
|
text = static_cast<const FP_TEXT*>( aItem );
|
|
|
|
if( !text->IsVisible() && !view()->IsLayerVisible( LAYER_MOD_TEXT_INVISIBLE ) )
|
|
return false;
|
|
|
|
if( !view()->IsLayerVisible( aItem->GetLayer() ) )
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if( !view()->IsVisible( aItem ) )
|
|
return false;
|
|
|
|
if( !board()->IsLayerVisible( aItem->GetLayer() ) )
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
|
|
case PCB_FP_SHAPE_T:
|
|
case PCB_FP_TEXTBOX_T:
|
|
if( m_isFootprintEditor )
|
|
{
|
|
if( !view()->IsLayerVisible( aItem->GetLayer() ) )
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Footprint shape selections are only allowed in footprint editor mode.
|
|
if( !checkVisibilityOnly )
|
|
return false;
|
|
|
|
if( !board()->IsLayerVisible( aItem->GetLayer() ) )
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
|
|
case PCB_PAD_T:
|
|
// Multiple selection is only allowed in footprint editor mode. In pcbnew, you have to
|
|
// select footprint subparts one by one, rather than with a drag selection. This is so
|
|
// you can pick up items under an (unlocked) footprint without also moving the
|
|
// footprint's sub-parts.
|
|
if( !m_isFootprintEditor && !checkVisibilityOnly )
|
|
{
|
|
if( m_multiple )
|
|
return false;
|
|
}
|
|
|
|
pad = static_cast<const PAD*>( aItem );
|
|
|
|
if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
|
|
{
|
|
// Check render mode (from the Items tab) first
|
|
if( !board()->IsElementVisible( LAYER_PADS_TH ) )
|
|
return false;
|
|
|
|
// A pad's hole is visible on every layer the pad is visible on plus many layers the
|
|
// pad is not visible on -- so we only need to check for any visible hole layers.
|
|
if( !( visibleLayers() & LSET::PhysicalLayersMask() ).any() )
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Check render mode (from the Items tab) first
|
|
if( pad->IsOnLayer( F_Cu ) && !board()->IsElementVisible( LAYER_PAD_FR ) )
|
|
return false;
|
|
else if( pad->IsOnLayer( B_Cu ) && !board()->IsElementVisible( LAYER_PAD_BK ) )
|
|
return false;
|
|
|
|
if( !( pad->GetLayerSet() & visibleLayers() ).any() )
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
|
|
// These are not selectable
|
|
case PCB_NETINFO_T:
|
|
case NOT_USED:
|
|
case TYPE_NOT_INIT:
|
|
return false;
|
|
|
|
default: // Suppress warnings
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::select( EDA_ITEM* aItem )
|
|
{
|
|
if( aItem->IsSelected() )
|
|
return;
|
|
|
|
if( aItem->Type() == PCB_PAD_T )
|
|
{
|
|
FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem->GetParent() );
|
|
|
|
if( m_selection.Contains( footprint ) )
|
|
return;
|
|
}
|
|
|
|
highlight( aItem, SELECTED, &m_selection );
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::unselect( EDA_ITEM* aItem )
|
|
{
|
|
unhighlight( aItem, SELECTED, &m_selection );
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
|
|
{
|
|
if( aGroup )
|
|
aGroup->Add( aItem );
|
|
|
|
highlightInternal( aItem, aMode, aGroup != nullptr );
|
|
view()->Update( aItem, KIGFX::REPAINT );
|
|
|
|
// Many selections are very temporal and updating the display each time just
|
|
// creates noise.
|
|
if( aMode == BRIGHTENED )
|
|
getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::highlightInternal( EDA_ITEM* aItem, int aMode, bool aUsingOverlay )
|
|
{
|
|
if( aMode == SELECTED )
|
|
aItem->SetSelected();
|
|
else if( aMode == BRIGHTENED )
|
|
aItem->SetBrightened();
|
|
|
|
if( aUsingOverlay )
|
|
view()->Hide( aItem, true ); // Hide the original item, so it is shown only on overlay
|
|
|
|
if( aItem->Type() == PCB_FOOTPRINT_T )
|
|
{
|
|
static_cast<FOOTPRINT*>( aItem )->RunOnChildren(
|
|
[&]( BOARD_ITEM* aChild )
|
|
{
|
|
highlightInternal( aChild, aMode, aUsingOverlay );
|
|
} );
|
|
}
|
|
else if( aItem->Type() == PCB_GROUP_T )
|
|
{
|
|
static_cast<PCB_GROUP*>( aItem )->RunOnChildren(
|
|
[&]( BOARD_ITEM* aChild )
|
|
{
|
|
highlightInternal( aChild, aMode, aUsingOverlay );
|
|
} );
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
|
|
{
|
|
if( aGroup )
|
|
aGroup->Remove( aItem );
|
|
|
|
unhighlightInternal( aItem, aMode, aGroup != nullptr );
|
|
view()->Update( aItem, KIGFX::REPAINT );
|
|
|
|
// Many selections are very temporal and updating the display each time just creates noise.
|
|
if( aMode == BRIGHTENED )
|
|
getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::unhighlightInternal( EDA_ITEM* aItem, int aMode, bool aUsingOverlay )
|
|
{
|
|
if( aMode == SELECTED )
|
|
aItem->ClearSelected();
|
|
else if( aMode == BRIGHTENED )
|
|
aItem->ClearBrightened();
|
|
|
|
if( aUsingOverlay )
|
|
view()->Hide( aItem, false ); // // Restore original item visibility
|
|
|
|
if( aItem->Type() == PCB_FOOTPRINT_T )
|
|
{
|
|
static_cast<FOOTPRINT*>( aItem )->RunOnChildren(
|
|
[&]( BOARD_ITEM* aChild )
|
|
{
|
|
unhighlightInternal( aChild, aMode, aUsingOverlay );
|
|
} );
|
|
}
|
|
else if( aItem->Type() == PCB_GROUP_T )
|
|
{
|
|
static_cast<PCB_GROUP*>( aItem )->RunOnChildren(
|
|
[&]( BOARD_ITEM* aChild )
|
|
{
|
|
unhighlightInternal( aChild, aMode, aUsingOverlay );
|
|
} );
|
|
}
|
|
}
|
|
|
|
|
|
bool PCB_SELECTION_TOOL::selectionContains( const VECTOR2I& aPoint ) const
|
|
{
|
|
GENERAL_COLLECTORS_GUIDE guide = getCollectorsGuide();
|
|
GENERAL_COLLECTOR collector;
|
|
|
|
// Since we're just double-checking, we want a considerably sloppier check than the initial
|
|
// selection (for which most tools use 5 pixels). So we increase this to an effective 20
|
|
// pixels by artificially inflating the value of a pixel by 4X.
|
|
guide.SetOnePixelInIU( guide.OnePixelInIU() * 4 );
|
|
|
|
collector.Collect( board(), m_isFootprintEditor ? GENERAL_COLLECTOR::FootprintItems
|
|
: GENERAL_COLLECTOR::AllBoardItems,
|
|
(wxPoint) aPoint, guide );
|
|
|
|
for( int i = collector.GetCount() - 1; i >= 0; --i )
|
|
{
|
|
BOARD_ITEM* item = collector[i];
|
|
|
|
if( item->IsSelected() && item->HitTest( (wxPoint) aPoint, 5 * guide.OnePixelInIU() ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::hitTestDistance( const wxPoint& aWhere, BOARD_ITEM* aItem,
|
|
int aMaxDistance ) const
|
|
{
|
|
BOX2D viewportD = getView()->GetViewport();
|
|
BOX2I viewport( VECTOR2I( viewportD.GetPosition() ), VECTOR2I( viewportD.GetSize() ) );
|
|
int distance = INT_MAX;
|
|
SEG loc( aWhere, aWhere );
|
|
|
|
switch( aItem->Type() )
|
|
{
|
|
case PCB_TEXT_T:
|
|
{
|
|
PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
|
|
text->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance );
|
|
break;
|
|
}
|
|
|
|
case PCB_TEXTBOX_T:
|
|
{
|
|
PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( aItem );
|
|
textbox->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance );
|
|
break;
|
|
}
|
|
|
|
case PCB_FP_TEXT_T:
|
|
{
|
|
FP_TEXT* text = static_cast<FP_TEXT*>( aItem );
|
|
text->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance );
|
|
break;
|
|
}
|
|
|
|
case PCB_FP_TEXTBOX_T:
|
|
{
|
|
FP_TEXTBOX* textbox = static_cast<FP_TEXTBOX*>( aItem );
|
|
textbox->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance );
|
|
break;
|
|
}
|
|
|
|
case PCB_ZONE_T:
|
|
{
|
|
ZONE* zone = static_cast<ZONE*>( aItem );
|
|
|
|
// Zone borders are very specific
|
|
if( zone->HitTestForEdge( aWhere, aMaxDistance / 2 ) )
|
|
distance = 0;
|
|
else if( zone->HitTestForEdge( aWhere, aMaxDistance ) )
|
|
distance = aMaxDistance / 2;
|
|
else
|
|
aItem->GetEffectiveShape()->Collide( loc, aMaxDistance, &distance );
|
|
|
|
break;
|
|
}
|
|
|
|
case PCB_FOOTPRINT_T:
|
|
{
|
|
FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
|
|
EDA_RECT bbox = footprint->GetBoundingBox( false, false );
|
|
|
|
try
|
|
{
|
|
footprint->GetBoundingHull().Collide( loc, aMaxDistance, &distance );
|
|
}
|
|
catch( const ClipperLib::clipperException& exc )
|
|
{
|
|
// This may be overkill and could be an assertion but we are more likely to find
|
|
// any clipper errors this way.
|
|
wxLogError( wxT( "Clipper library exception '%s' occurred." ), exc.what() );
|
|
}
|
|
|
|
// Consider footprints larger than the viewport only as a last resort
|
|
if( bbox.GetHeight() > viewport.GetHeight() || bbox.GetWidth() > viewport.GetWidth() )
|
|
distance = INT_MAX / 2;
|
|
|
|
break;
|
|
}
|
|
|
|
case PCB_MARKER_T:
|
|
{
|
|
PCB_MARKER* marker = static_cast<PCB_MARKER*>( aItem );
|
|
SHAPE_LINE_CHAIN polygon;
|
|
|
|
marker->ShapeToPolygon( polygon );
|
|
polygon.Move( marker->GetPos() );
|
|
polygon.Collide( loc, aMaxDistance, &distance );
|
|
break;
|
|
}
|
|
|
|
case PCB_GROUP_T:
|
|
{
|
|
PCB_GROUP* group = static_cast<PCB_GROUP*>( aItem );
|
|
|
|
for( BOARD_ITEM* member : group->GetItems() )
|
|
distance = std::min( distance, hitTestDistance( aWhere, member, aMaxDistance ) );
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
aItem->GetEffectiveShape()->Collide( loc, aMaxDistance, &distance );
|
|
break;
|
|
}
|
|
|
|
return distance;
|
|
}
|
|
|
|
|
|
// 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
|
|
// larger footprints, and vias within either larger pads or longer tracks.
|
|
//
|
|
// These "guesses" presume there is area within the larger item to click in to select it. If
|
|
// an item is mostly covered by smaller items within it, then the guesses are inappropriate as
|
|
// there might not be any area left to click to select the larger item. In this case we must
|
|
// leave the items in the collector and bring up a Selection Clarification menu.
|
|
//
|
|
// We currently check for pads and text mostly covering a footprint, but we don't check for
|
|
// smaller footprints mostly covering a larger footprint.
|
|
//
|
|
void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector,
|
|
const VECTOR2I& aWhere ) const
|
|
{
|
|
std::set<BOARD_ITEM*> preferred;
|
|
std::set<BOARD_ITEM*> rejected;
|
|
wxPoint where( aWhere.x, aWhere.y );
|
|
|
|
PCB_LAYER_ID activeLayer = m_frame->GetActiveLayer();
|
|
LSET silkLayers( 2, B_SilkS, F_SilkS );
|
|
|
|
if( silkLayers[activeLayer] )
|
|
{
|
|
for( int i = 0; i < aCollector.GetCount(); ++i )
|
|
{
|
|
BOARD_ITEM* item = aCollector[i];
|
|
KICAD_T type = item->Type();
|
|
|
|
if( ( type == PCB_TEXT_T || type == PCB_TEXTBOX_T || type == PCB_SHAPE_T )
|
|
&& silkLayers[item->GetLayer()] )
|
|
{
|
|
preferred.insert( item );
|
|
}
|
|
}
|
|
|
|
if( preferred.size() > 0 )
|
|
{
|
|
aCollector.Empty();
|
|
|
|
for( BOARD_ITEM* item : preferred )
|
|
aCollector.Append( item );
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Prefer exact hits to sloppy ones
|
|
constexpr int MAX_SLOP = 5;
|
|
|
|
int pixel = (int) aCollector.GetGuide()->OnePixelInIU();
|
|
int minSlop = INT_MAX;
|
|
|
|
std::map<BOARD_ITEM*, int> itemsBySloppiness;
|
|
|
|
for( int i = 0; i < aCollector.GetCount(); ++i )
|
|
{
|
|
BOARD_ITEM* item = aCollector[i];
|
|
int itemSlop = hitTestDistance( where, item, MAX_SLOP * pixel );
|
|
|
|
itemsBySloppiness[ item ] = itemSlop;
|
|
|
|
if( itemSlop < minSlop )
|
|
minSlop = itemSlop;
|
|
}
|
|
|
|
// Prune sloppier items
|
|
if( minSlop < INT_MAX )
|
|
{
|
|
for( std::pair<BOARD_ITEM*, int> pair : itemsBySloppiness )
|
|
{
|
|
if( pair.second > minSlop + pixel )
|
|
aCollector.Transfer( pair.first );
|
|
}
|
|
}
|
|
|
|
// If the user clicked on a small item within a much larger one then it's pretty clear
|
|
// they're trying to select the smaller one.
|
|
constexpr double sizeRatio = 1.5;
|
|
|
|
std::vector<std::pair<BOARD_ITEM*, double>> itemsByArea;
|
|
|
|
for( int i = 0; i < aCollector.GetCount(); ++i )
|
|
{
|
|
BOARD_ITEM* item = aCollector[i];
|
|
double area = 0.0;
|
|
|
|
if( ( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
|
|
&& static_cast<ZONE*>( item )->HitTestForEdge( where, MAX_SLOP * pixel / 2 ) )
|
|
{
|
|
// Zone borders are very specific, so make them "small"
|
|
area = MAX_SLOP * SEG::Square( pixel );
|
|
}
|
|
else if( item->Type() == PCB_VIA_T )
|
|
{
|
|
// Vias rarely hide other things, and we don't want them deferring to short track
|
|
// segments underneath them -- so artificially reduce their size from πr² to 1.5r².
|
|
area = SEG::Square( static_cast<PCB_VIA*>( item )->GetDrill() / 2 ) * 1.5;
|
|
}
|
|
else if( item->Type() == PCB_BITMAP_T )
|
|
{
|
|
VECTOR2I size = static_cast<const PCB_BITMAP*>( item )->GetSize();
|
|
area = size.x * size.y;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
area = FOOTPRINT::GetCoverageArea( item, aCollector );
|
|
}
|
|
catch( const ClipperLib::clipperException& e )
|
|
{
|
|
wxLogError( wxT( "A clipper exception %s was detected." ), e.what() );
|
|
}
|
|
}
|
|
|
|
itemsByArea.emplace_back( item, area );
|
|
}
|
|
|
|
std::sort( itemsByArea.begin(), itemsByArea.end(),
|
|
[]( const std::pair<BOARD_ITEM*, double>& lhs,
|
|
const std::pair<BOARD_ITEM*, double>& rhs ) -> bool
|
|
{
|
|
return lhs.second < rhs.second;
|
|
} );
|
|
|
|
bool rejecting = false;
|
|
|
|
for( int i = 1; i < (int) itemsByArea.size(); ++i )
|
|
{
|
|
if( itemsByArea[i].second > itemsByArea[i-1].second * sizeRatio )
|
|
rejecting = true;
|
|
|
|
if( rejecting )
|
|
rejected.insert( itemsByArea[i].first );
|
|
}
|
|
|
|
// Special case: if a footprint is completely covered with other features then there's no
|
|
// way to select it -- so we need to leave it in the list for user disambiguation.
|
|
constexpr double maxCoverRatio = 0.70;
|
|
|
|
for( int i = 0; i < aCollector.GetCount(); ++i )
|
|
{
|
|
if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aCollector[i] ) )
|
|
{
|
|
if( footprint->CoverageRatio( aCollector ) > maxCoverRatio )
|
|
rejected.erase( footprint );
|
|
}
|
|
}
|
|
|
|
// Hopefully we've now got what the user wanted.
|
|
if( (unsigned) aCollector.GetCount() > rejected.size() ) // do not remove everything
|
|
{
|
|
for( BOARD_ITEM* item : rejected )
|
|
aCollector.Transfer( item );
|
|
}
|
|
|
|
// Finally, what we are left with is a set of items of similar coverage area. We now reject
|
|
// any that are not on the active layer, to reduce the number of disambiguation menus shown.
|
|
// If the user wants to force-disambiguate, they can either switch layers or use the modifier
|
|
// key to force the menu.
|
|
if( aCollector.GetCount() > 1 )
|
|
{
|
|
bool haveItemOnActive = false;
|
|
rejected.clear();
|
|
|
|
for( int i = 0; i < aCollector.GetCount(); ++i )
|
|
{
|
|
if( !aCollector[i]->IsOnLayer( activeLayer ) )
|
|
rejected.insert( aCollector[i] );
|
|
else
|
|
haveItemOnActive = true;
|
|
}
|
|
|
|
if( haveItemOnActive )
|
|
for( BOARD_ITEM* item : rejected )
|
|
aCollector.Transfer( item );
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::FilterCollectorForHierarchy( GENERAL_COLLECTOR& aCollector,
|
|
bool aMultiselect ) const
|
|
{
|
|
std::unordered_set<BOARD_ITEM*> toAdd;
|
|
|
|
// Set CANDIDATE on all parents which are included in the GENERAL_COLLECTOR. This
|
|
// algorithm is O3n, whereas checking for the parent inclusion could potentially be On^2.
|
|
for( int j = 0; j < aCollector.GetCount(); j++ )
|
|
{
|
|
if( aCollector[j]->GetParent() )
|
|
aCollector[j]->GetParent()->ClearFlags( CANDIDATE );
|
|
}
|
|
|
|
if( aMultiselect )
|
|
{
|
|
for( int j = 0; j < aCollector.GetCount(); j++ )
|
|
aCollector[j]->SetFlags( CANDIDATE );
|
|
}
|
|
|
|
for( int j = 0; j < aCollector.GetCount(); )
|
|
{
|
|
BOARD_ITEM* item = aCollector[j];
|
|
BOARD_ITEM* parent = item->GetParent();
|
|
BOARD_ITEM* start = item;
|
|
|
|
if( !m_isFootprintEditor && parent && parent->Type() == PCB_FOOTPRINT_T )
|
|
start = parent;
|
|
|
|
// If any element is a member of a group, replace those elements with the top containing
|
|
// group.
|
|
PCB_GROUP* aTop = PCB_GROUP::TopLevelGroup( start, m_enteredGroup, m_isFootprintEditor );
|
|
|
|
if( aTop )
|
|
{
|
|
if( aTop != item )
|
|
{
|
|
toAdd.insert( aTop );
|
|
aTop->SetFlags( CANDIDATE );
|
|
|
|
aCollector.Remove( item );
|
|
continue;
|
|
}
|
|
}
|
|
else if( m_enteredGroup
|
|
&& !PCB_GROUP::WithinScope( item, m_enteredGroup, m_isFootprintEditor ) )
|
|
{
|
|
// If a group is entered, disallow selections of objects outside the group.
|
|
aCollector.Remove( item );
|
|
continue;
|
|
}
|
|
|
|
// Footprints are a bit easier as they can't be nested.
|
|
if( parent && ( parent->GetFlags() & CANDIDATE ) )
|
|
{
|
|
// Remove children of selected items
|
|
aCollector.Remove( item );
|
|
continue;
|
|
}
|
|
|
|
++j;
|
|
}
|
|
|
|
for( BOARD_ITEM* item : toAdd )
|
|
{
|
|
if( !aCollector.HasItem( item ) )
|
|
aCollector.Append( item );
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::FilterCollectorForFreePads( GENERAL_COLLECTOR& aCollector ) const
|
|
{
|
|
std::set<BOARD_ITEM*> to_add;
|
|
|
|
// Iterate from the back so we don't have to worry about removals.
|
|
for( int i = aCollector.GetCount() - 1; i >= 0; --i )
|
|
{
|
|
BOARD_ITEM* item = aCollector[i];
|
|
|
|
if( !m_isFootprintEditor && item->Type() == PCB_PAD_T
|
|
&& !frame()->GetPcbNewSettings()->m_AllowFreePads )
|
|
{
|
|
if( !aCollector.HasItem( item->GetParent() ) )
|
|
to_add.insert( item->GetParent() );
|
|
|
|
aCollector.Remove( item );
|
|
}
|
|
}
|
|
|
|
for( BOARD_ITEM* item : to_add )
|
|
aCollector.Append( item );
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::FilterCollectorForMarkers( GENERAL_COLLECTOR& aCollector ) const
|
|
{
|
|
// Iterate from the back so we don't have to worry about removals.
|
|
for( int i = aCollector.GetCount() - 1; i >= 0; --i )
|
|
{
|
|
BOARD_ITEM* item = aCollector[i];
|
|
|
|
if( item->Type() == PCB_MARKER_T )
|
|
aCollector.Remove( item );
|
|
}
|
|
}
|
|
|
|
|
|
int PCB_SELECTION_TOOL::updateSelection( const TOOL_EVENT& aEvent )
|
|
{
|
|
getView()->Update( &m_selection );
|
|
getView()->Update( &m_enteredGroupOverlay );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PCB_SELECTION_TOOL::setTransitions()
|
|
{
|
|
Go( &PCB_SELECTION_TOOL::UpdateMenu, ACTIONS::updateMenu.MakeEvent() );
|
|
|
|
Go( &PCB_SELECTION_TOOL::Main, PCB_ACTIONS::selectionActivate.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::CursorSelection, PCB_ACTIONS::selectionCursor.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::ClearSelection, PCB_ACTIONS::selectionClear.MakeEvent() );
|
|
|
|
Go( &PCB_SELECTION_TOOL::AddItemToSel, PCB_ACTIONS::selectItem.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::AddItemsToSel, PCB_ACTIONS::selectItems.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::RemoveItemFromSel, PCB_ACTIONS::unselectItem.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::RemoveItemsFromSel, PCB_ACTIONS::unselectItems.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::SelectionMenu, PCB_ACTIONS::selectionMenu.MakeEvent() );
|
|
|
|
Go( &PCB_SELECTION_TOOL::filterSelection, PCB_ACTIONS::filterSelection.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::expandConnection, PCB_ACTIONS::selectConnection.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::selectNet, PCB_ACTIONS::selectNet.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::selectNet, PCB_ACTIONS::deselectNet.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::syncSelection, PCB_ACTIONS::syncSelection.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::syncSelectionWithNets,
|
|
PCB_ACTIONS::syncSelectionWithNets.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::selectSameSheet, PCB_ACTIONS::selectSameSheet.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::selectSheetContents,
|
|
PCB_ACTIONS::selectOnSheetFromEeschema.MakeEvent() );
|
|
Go( &PCB_SELECTION_TOOL::updateSelection, EVENTS::SelectedItemsModified );
|
|
Go( &PCB_SELECTION_TOOL::updateSelection, EVENTS::SelectedItemsMoved );
|
|
|
|
Go( &PCB_SELECTION_TOOL::SelectAll, ACTIONS::selectAll.MakeEvent() );
|
|
|
|
Go( &PCB_SELECTION_TOOL::disambiguateCursor, EVENTS::DisambiguatePoint );
|
|
}
|