From bc1ff6756fce5b9ea6dedb1b2b777341728c077b Mon Sep 17 00:00:00 2001 From: dsa-t Date: Sun, 16 Jan 2022 20:29:03 +0000 Subject: [PATCH] Cross-probing/selection for multiple items (SCH->PCB) --- common/settings/app_settings.cpp | 5 +- common/string_utils.cpp | 12 + common/tool/selection_conditions.cpp | 30 +- eeschema/cross-probing.cpp | 112 +++- .../panel_eeschema_display_options.cpp | 8 +- .../panel_eeschema_display_options_base.cpp | 6 +- .../panel_eeschema_display_options_base.fbp | 69 +- .../panel_eeschema_display_options_base.h | 4 +- eeschema/sch_edit_frame.h | 8 + eeschema/tools/ee_actions.cpp | 7 +- eeschema/tools/ee_actions.h | 2 +- eeschema/tools/ee_selection_tool.cpp | 9 +- eeschema/tools/sch_editor_control.cpp | 61 +- eeschema/tools/sch_editor_control.h | 5 +- include/mail_type.h | 3 +- include/settings/app_settings.h | 3 +- include/string_utils.h | 1 + include/tool/selection_conditions.h | 13 + pcbnew/cross-probing.cpp | 246 ++++--- pcbnew/dialogs/panel_display_options.cpp | 5 +- pcbnew/dialogs/panel_display_options_base.cpp | 6 +- pcbnew/dialogs/panel_display_options_base.fbp | 69 +- pcbnew/dialogs/panel_display_options_base.h | 4 +- pcbnew/pcb_edit_frame.cpp | 11 +- pcbnew/pcb_edit_frame.h | 7 + pcbnew/tools/board_inspection_tool.cpp | 7 +- pcbnew/tools/pcb_actions.cpp | 8 +- pcbnew/tools/pcb_actions.h | 8 +- pcbnew/tools/pcb_selection_tool.cpp | 611 +++++++++++++----- pcbnew/tools/pcb_selection_tool.h | 19 +- qa/qa_utils/mocks.cpp | 4 +- 31 files changed, 974 insertions(+), 389 deletions(-) diff --git a/common/settings/app_settings.cpp b/common/settings/app_settings.cpp index 94394cfe8c..19f616cba8 100644 --- a/common/settings/app_settings.cpp +++ b/common/settings/app_settings.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 Jon Evans - * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2020-2022 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 @@ -129,6 +129,9 @@ APP_SETTINGS_BASE::APP_SETTINGS_BASE( const std::string& aFilename, int aSchemaV addParamsForWindow( &m_Window, "window" ); + m_params.emplace_back( new PARAM( "cross_probing.on_selection", + &m_CrossProbing.on_selection, true ) ); + m_params.emplace_back( new PARAM( "cross_probing.center_on_items", &m_CrossProbing.center_on_items, true ) ); diff --git a/common/string_utils.cpp b/common/string_utils.cpp index 17a7d45103..694f367a57 100644 --- a/common/string_utils.cpp +++ b/common/string_utils.cpp @@ -174,6 +174,17 @@ wxString EscapeString( const wxString& aSource, ESCAPE_CONTEXT aContext ) else converted += c; } + else if( aContext == CTX_IPC ) + { + if( c == '/' ) + converted += "{slash}"; + else if( c == ',' ) + converted += "{comma}"; + else if( c == '\"' ) + converted += "{dblquote}"; + else + converted += c; + } else if( aContext == CTX_QUOTED_STR ) { if( c == '\"' ) @@ -276,6 +287,7 @@ wxString UnescapeString( const wxString& aSource ) else if( token == wxS( "backslash" ) ) newbuf.append( wxS( "\\" ) ); else if( token == wxS( "slash" ) ) newbuf.append( wxS( "/" ) ); else if( token == wxS( "bar" ) ) newbuf.append( wxS( "|" ) ); + else if( token == wxS( "comma" ) ) newbuf.append( wxS( "," ) ); else if( token == wxS( "colon" ) ) newbuf.append( wxS( ":" ) ); else if( token == wxS( "space" ) ) newbuf.append( wxS( " " ) ); else if( token == wxS( "dollar" ) ) newbuf.append( wxS( "$" ) ); diff --git a/common/tool/selection_conditions.cpp b/common/tool/selection_conditions.cpp index 2314493abe..954ad71bd1 100644 --- a/common/tool/selection_conditions.cpp +++ b/common/tool/selection_conditions.cpp @@ -62,6 +62,12 @@ SELECTION_CONDITION SELECTION_CONDITIONS::HasType( KICAD_T aType ) } +SELECTION_CONDITION SELECTION_CONDITIONS::HasTypes( const KICAD_T aTypes[] ) +{ + return std::bind( &SELECTION_CONDITIONS::hasTypesFunc, _1, aTypes ); +} + + SELECTION_CONDITION SELECTION_CONDITIONS::OnlyType( KICAD_T aType ) { return std::bind( &SELECTION_CONDITIONS::onlyTypeFunc, _1, aType ); @@ -94,7 +100,10 @@ SELECTION_CONDITION SELECTION_CONDITIONS::LessThan( int aNumber ) bool SELECTION_CONDITIONS::hasTypeFunc( const SELECTION& aSelection, KICAD_T aType ) { - for( const auto& item : aSelection ) + if( aSelection.Empty() ) + return false; + + for( const EDA_ITEM* item : aSelection ) { if( item->Type() == aType ) return true; @@ -104,6 +113,21 @@ bool SELECTION_CONDITIONS::hasTypeFunc( const SELECTION& aSelection, KICAD_T aTy } +bool SELECTION_CONDITIONS::hasTypesFunc( const SELECTION& aSelection, const KICAD_T aTypes[] ) +{ + if( aSelection.Empty() ) + return false; + + for( const EDA_ITEM* item : aSelection ) + { + if( item->IsType( aTypes ) ) + return true; + } + + return false; +} + + bool SELECTION_CONDITIONS::onlyTypeFunc( const SELECTION& aSelection, KICAD_T aType ) { if( aSelection.Empty() ) @@ -111,7 +135,7 @@ bool SELECTION_CONDITIONS::onlyTypeFunc( const SELECTION& aSelection, KICAD_T aT KICAD_T types[] = { aType, EOT }; - for( const auto& item : aSelection ) + for( const EDA_ITEM* item : aSelection ) { if( !item->IsType( types ) ) return false; @@ -126,7 +150,7 @@ bool SELECTION_CONDITIONS::onlyTypesFunc( const SELECTION& aSelection, const KIC if( aSelection.Empty() ) return false; - for( const auto& item : aSelection ) + for( const EDA_ITEM* item : aSelection ) { if( !item->IsType( aTypes ) ) return false; diff --git a/eeschema/cross-probing.cpp b/eeschema/cross-probing.cpp index d14b79e5f0..835f88c91e 100644 --- a/eeschema/cross-probing.cpp +++ b/eeschema/cross-probing.cpp @@ -3,7 +3,7 @@ * * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2011 Wayne Stambaugh - * Copyright (C) 2004-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2004-2022 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 @@ -473,6 +473,116 @@ void SCH_EDIT_FRAME::SendMessageToPCBNEW( EDA_ITEM* aObjectToSync, SCH_SYMBOL* a } +std::string FormatProbeItems( bool aSelectConnections, const std::deque& aItems ) +{ + std::string command = ""; + std::set parts; + + for( EDA_ITEM* item : aItems ) + { + switch( item->Type() ) + { + case SCH_SYMBOL_T: + { + SCH_SYMBOL* symbol = (SCH_SYMBOL*) item; + + wxString ref = symbol->GetField( REFERENCE_FIELD )->GetText(); + + parts.insert( "F" + EscapeString( ref, CTX_IPC ) ); + + break; + } + + case SCH_SHEET_T: + { + // For cross probing, we need the full path of the sheet, because + // in complex hierarchies the sheet uuid of not unique + SCH_SHEET* sheet = (SCH_SHEET*) item; + wxString full_path; + + SCH_SHEET* parent = sheet; + + while( ( parent = dynamic_cast( parent->GetParent() ) ) ) + { + if( parent->GetParent() && parent->GetParent()->Type() == SCH_SHEET_T ) + { + // The root sheet has no parent sheet and path is just "/" + + full_path.Prepend( parent->m_Uuid.AsString() ); + full_path.Prepend( "/" ); + } + } + + full_path += "/" + sheet->m_Uuid.AsString(); + + parts.insert( "S" + full_path ); + + break; + } + + case SCH_PIN_T: + { + SCH_PIN* pin = (SCH_PIN*) item; + SCH_SYMBOL* symbol = pin->GetParentSymbol(); + + wxString ref = symbol->GetField( REFERENCE_FIELD )->GetText(); + + parts.insert( "P" + EscapeString( ref, CTX_IPC ) + "/" + + EscapeString( pin->GetShownNumber(), CTX_IPC ) ); + + break; + } + + default: break; + } + } + + + if( !parts.empty() ) + { + command = "$SELECT: "; + + if( aSelectConnections ) + command += "1"; + else + command += "0"; + + command += ","; + + for( wxString part : parts ) + { + command += part; + command += ","; + } + + command.pop_back(); + } + + return command; +} + + +void SCH_EDIT_FRAME::SendSelectItems( bool aSelectConnections, const std::deque& aItems ) +{ + std::string packet = FormatProbeItems( aSelectConnections, aItems ); + + if( !packet.empty() ) + { + if( Kiface().IsSingle() ) + { + SendCommand( MSG_TO_PCB, packet ); + } + else + { + // Typically ExpressMail is going to be s-expression packets, but since + // we have existing interpreter of the selection packet on the other + // side in place, we use that here. + Kiway().ExpressMail( FRAME_PCB_EDITOR, MAIL_SELECTION, packet, this ); + } + } +} + + void SCH_EDIT_FRAME::SendCrossProbeNetName( const wxString& aNetName ) { // The command is a keyword followed by a quoted string. diff --git a/eeschema/dialogs/panel_eeschema_display_options.cpp b/eeschema/dialogs/panel_eeschema_display_options.cpp index 24aa21ed6e..8bad59ae92 100644 --- a/eeschema/dialogs/panel_eeschema_display_options.cpp +++ b/eeschema/dialogs/panel_eeschema_display_options.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2009 Wayne Stambaugh - * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2022 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 @@ -66,6 +66,7 @@ void PANEL_EESCHEMA_DISPLAY_OPTIONS::loadEEschemaSettings( EESCHEMA_SETTINGS* cf m_selWidthCtrl->SetValue( cfg->m_Selection.selection_thickness ); m_highlightWidthCtrl->SetValue( cfg->m_Selection.highlight_thickness ); + m_checkCrossProbeOnSelection->SetValue( cfg->m_CrossProbing.on_selection ); m_checkCrossProbeCenter->SetValue( cfg->m_CrossProbing.center_on_items ); m_checkCrossProbeZoom->SetValue( cfg->m_CrossProbing.zoom_to_fit ); m_checkCrossProbeAutoHighlight->SetValue( cfg->m_CrossProbing.auto_highlight ); @@ -106,9 +107,10 @@ bool PANEL_EESCHEMA_DISPLAY_OPTIONS::TransferDataFromWindow() cfg->m_Selection.selection_thickness = KiROUND( m_selWidthCtrl->GetValue() ); cfg->m_Selection.highlight_thickness = KiROUND( m_highlightWidthCtrl->GetValue() ); + cfg->m_CrossProbing.on_selection = m_checkCrossProbeOnSelection->GetValue(); cfg->m_CrossProbing.center_on_items = m_checkCrossProbeCenter->GetValue(); - cfg->m_CrossProbing.zoom_to_fit = m_checkCrossProbeZoom->GetValue(); - cfg->m_CrossProbing.auto_highlight = m_checkCrossProbeAutoHighlight->GetValue(); + cfg->m_CrossProbing.zoom_to_fit = m_checkCrossProbeZoom->GetValue(); + cfg->m_CrossProbing.auto_highlight = m_checkCrossProbeAutoHighlight->GetValue(); m_galOptsPanel->TransferDataFromWindow(); diff --git a/eeschema/dialogs/panel_eeschema_display_options_base.cpp b/eeschema/dialogs/panel_eeschema_display_options_base.cpp index 66e877d870..10e422a017 100644 --- a/eeschema/dialogs/panel_eeschema_display_options_base.cpp +++ b/eeschema/dialogs/panel_eeschema_display_options_base.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Oct 26 2018) +// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -112,6 +112,10 @@ PANEL_EESCHEMA_DISPLAY_OPTIONS_BASE::PANEL_EESCHEMA_DISPLAY_OPTIONS_BASE( wxWind wxStaticBoxSizer* sbSizer31; sbSizer31 = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, _("Cross-probing") ), wxVERTICAL ); + m_checkCrossProbeOnSelection = new wxCheckBox( sbSizer31->GetStaticBox(), wxID_ANY, _("Cross-probe on selection"), wxDefaultPosition, wxDefaultSize, 0 ); + m_checkCrossProbeOnSelection->SetValue(true); + sbSizer31->Add( m_checkCrossProbeOnSelection, 0, wxALL, 5 ); + m_checkCrossProbeCenter = new wxCheckBox( sbSizer31->GetStaticBox(), wxID_ANY, _("Center view on cross-probed items"), wxDefaultPosition, wxDefaultSize, 0 ); m_checkCrossProbeCenter->SetValue(true); sbSizer31->Add( m_checkCrossProbeCenter, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); diff --git a/eeschema/dialogs/panel_eeschema_display_options_base.fbp b/eeschema/dialogs/panel_eeschema_display_options_base.fbp index a5bee52063..744befaf8f 100644 --- a/eeschema/dialogs/panel_eeschema_display_options_base.fbp +++ b/eeschema/dialogs/panel_eeschema_display_options_base.fbp @@ -1,6 +1,6 @@ - + C++ @@ -14,6 +14,7 @@ panel_eeschema_display_options_base 1000 none + 1 PanelEeschemaDisplayOptions @@ -25,6 +26,7 @@ 1 1 UI + 0 1 0 @@ -46,6 +48,7 @@ -1,-1 RESETTABLE_PANEL; widgets/resettable_panel.h; Not forward_declare + 0 wxTAB_TRAVERSAL @@ -1169,6 +1172,70 @@ wxVERTICAL 1 none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Cross-probe on selection + + 0 + + + 0 + + 1 + m_checkCrossProbeOnSelection + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + 5 wxBOTTOM|wxRIGHT|wxLEFT diff --git a/eeschema/dialogs/panel_eeschema_display_options_base.h b/eeschema/dialogs/panel_eeschema_display_options_base.h index 749bd7f211..b7f10161db 100644 --- a/eeschema/dialogs/panel_eeschema_display_options_base.h +++ b/eeschema/dialogs/panel_eeschema_display_options_base.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Oct 26 2018) +// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -54,6 +54,7 @@ class PANEL_EESCHEMA_DISPLAY_OPTIONS_BASE : public RESETTABLE_PANEL wxStaticText* m_highlightColorNote; wxStaticText* m_highlightWidthLabel; wxSpinCtrlDouble* m_highlightWidthCtrl; + wxCheckBox* m_checkCrossProbeOnSelection; wxCheckBox* m_checkCrossProbeCenter; wxCheckBox* m_checkCrossProbeZoom; wxCheckBox* m_checkCrossProbeAutoHighlight; @@ -61,6 +62,7 @@ class PANEL_EESCHEMA_DISPLAY_OPTIONS_BASE : public RESETTABLE_PANEL public: PANEL_EESCHEMA_DISPLAY_OPTIONS_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString ); + ~PANEL_EESCHEMA_DISPLAY_OPTIONS_BASE(); }; diff --git a/eeschema/sch_edit_frame.h b/eeschema/sch_edit_frame.h index 9a7d586ddc..3ff744642f 100644 --- a/eeschema/sch_edit_frame.h +++ b/eeschema/sch_edit_frame.h @@ -307,6 +307,14 @@ public: */ void SendMessageToPCBNEW( EDA_ITEM* aObjectToSync, SCH_SYMBOL* aPart ); + /** + * Sends items to Pcbnew for selection + * + * @param aSelectConnections - set to select connected tracks/vias too + * @param aElements are the items to select + */ + void SendSelectItems( bool aSelectConnections, const std::deque& aElements ); + /** * Sends a net name to Pcbnew for highlighting * diff --git a/eeschema/tools/ee_actions.cpp b/eeschema/tools/ee_actions.cpp index 6e2467e241..4988728b14 100644 --- a/eeschema/tools/ee_actions.cpp +++ b/eeschema/tools/ee_actions.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 CERN - * Copyright (C) 2019-2020 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2019-2022 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 @@ -641,9 +641,10 @@ TOOL_ACTION EE_ACTIONS::generateBOM( "eeschema.EditorControl.generateBOM", _( "Generate BOM..." ), _( "Generate a bill of materials for the current schematic" ), BITMAPS::post_bom ); -TOOL_ACTION EE_ACTIONS::explicitCrossProbe( "eeschema.EditorControl.explicitCrossProbe", +TOOL_ACTION EE_ACTIONS::selectOnPCB( "eeschema.EditorControl.selectOnPCB", AS_GLOBAL, 0, "", - _( "Highlight on PCB" ), _( "Highlight corresponding items in PCB editor" ), + _( "Select on PCB" ), + _( "Select corresponding items in PCB editor" ), BITMAPS::select_same_sheet ); TOOL_ACTION EE_ACTIONS::toggleHiddenPins( "eeschema.EditorControl.showHiddenPins", diff --git a/eeschema/tools/ee_actions.h b/eeschema/tools/ee_actions.h index a2523d61fc..763c0fdfc8 100644 --- a/eeschema/tools/ee_actions.h +++ b/eeschema/tools/ee_actions.h @@ -201,7 +201,7 @@ public: static TOOL_ACTION toggleERCExclusions; static TOOL_ACTION toggleSyncedPinsMode; static TOOL_ACTION restartMove; - static TOOL_ACTION explicitCrossProbe; + static TOOL_ACTION selectOnPCB; static TOOL_ACTION pushPinLength; static TOOL_ACTION pushPinNameSize; static TOOL_ACTION pushPinNumSize; diff --git a/eeschema/tools/ee_selection_tool.cpp b/eeschema/tools/ee_selection_tool.cpp index f0195a7978..7577fb0d5e 100644 --- a/eeschema/tools/ee_selection_tool.cpp +++ b/eeschema/tools/ee_selection_tool.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 CERN - * Copyright (C) 2019-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2019-2022 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 @@ -171,11 +171,14 @@ bool EE_SELECTION_TOOL::Init() SCH_PIN_T, EOT }; + static KICAD_T crossProbingTypes[] = { SCH_SYMBOL_T, SCH_PIN_T, SCH_SHEET_T, EOT }; + auto wireSelection = E_C::MoreThan( 0 ) && E_C::OnlyType( SCH_ITEM_LOCATE_WIRE_T ); auto busSelection = E_C::MoreThan( 0 ) && E_C::OnlyType( SCH_ITEM_LOCATE_BUS_T ); auto wireOrBusSelection = E_C::MoreThan( 0 ) && E_C::OnlyTypes( wireOrBusTypes ); auto connectedSelection = E_C::MoreThan( 0 ) && E_C::OnlyTypes( connectedTypes ); - auto sheetSelection = E_C::Count( 1 ) && E_C::OnlyType( SCH_SHEET_T ); + auto sheetSelection = E_C::Count( 1 ) && E_C::OnlyType( SCH_SHEET_T ); + auto crossProbingSelection = E_C::MoreThan( 0 ) && E_C::HasTypes( crossProbingTypes ); auto schEditSheetPageNumberCondition = [&] ( const SELECTION& aSel ) @@ -211,7 +214,7 @@ bool EE_SELECTION_TOOL::Init() auto& menu = m_menu.GetMenu(); menu.AddItem( EE_ACTIONS::enterSheet, sheetSelection && EE_CONDITIONS::Idle, 1 ); - menu.AddItem( EE_ACTIONS::explicitCrossProbe, sheetSelection && EE_CONDITIONS::Idle, 1 ); + menu.AddItem( EE_ACTIONS::selectOnPCB, crossProbingSelection && EE_CONDITIONS::Idle, 1 ); menu.AddItem( EE_ACTIONS::leaveSheet, belowRootSheetCondition, 1 ); menu.AddSeparator( 100 ); diff --git a/eeschema/tools/sch_editor_control.cpp b/eeschema/tools/sch_editor_control.cpp index 7aca89b7d4..3935a8be25 100644 --- a/eeschema/tools/sch_editor_control.cpp +++ b/eeschema/tools/sch_editor_control.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 CERN - * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2022 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 @@ -672,63 +672,14 @@ void SCH_EDITOR_CONTROL::doCrossProbeSchToPcb( const TOOL_EVENT& aEvent, bool aF if( m_probingPcbToSch ) return; - EE_SELECTION_TOOL* selTool = m_toolMgr->GetTool(); - SCH_ITEM* item = nullptr; - SCH_SYMBOL* symbol = nullptr; - - if( aForce ) - { - EE_SELECTION& selection = selTool->RequestSelection(); - - if( selection.GetSize() >= 1 ) - item = (SCH_ITEM*) selection.Front(); - } - else - { - EE_SELECTION& selection = selTool->GetSelection(); - - if( selection.GetSize() >= 1 ) - item = (SCH_ITEM*) selection.Front(); - } - - if( !item ) - { - if( aForce ) - m_frame->SendMessageToPCBNEW( nullptr, nullptr ); - + if( !aForce && !m_frame->eeconfig()->m_CrossProbing.on_selection ) return; - } + EE_SELECTION_TOOL* selTool = m_toolMgr->GetTool(); - switch( item->Type() ) - { - case SCH_FIELD_T: - case LIB_FIELD_T: - if( item->GetParent() && item->GetParent()->Type() == SCH_SYMBOL_T ) - { - symbol = (SCH_SYMBOL*) item->GetParent(); - m_frame->SendMessageToPCBNEW( item, symbol ); - } - break; + EE_SELECTION& selection = aForce ? selTool->RequestSelection() : selTool->GetSelection(); - case SCH_SYMBOL_T: - symbol = (SCH_SYMBOL*) item; - m_frame->SendMessageToPCBNEW( item, symbol ); - break; - - case SCH_PIN_T: - symbol = (SCH_SYMBOL*) item->GetParent(); - m_frame->SendMessageToPCBNEW( static_cast( item ), symbol ); - break; - - case SCH_SHEET_T: - if( aForce ) - m_frame->SendMessageToPCBNEW( item, nullptr ); - break; - - default: - break; - } + m_frame->SendSelectItems( false, selection.GetItems() ); } @@ -2318,7 +2269,7 @@ void SCH_EDITOR_CONTROL::setTransitions() Go( &SCH_EDITOR_CONTROL::CrossProbeToPcb, EVENTS::SelectedEvent ); Go( &SCH_EDITOR_CONTROL::CrossProbeToPcb, EVENTS::UnselectedEvent ); Go( &SCH_EDITOR_CONTROL::CrossProbeToPcb, EVENTS::ClearedEvent ); - Go( &SCH_EDITOR_CONTROL::ExplicitCrossProbeToPcb, EE_ACTIONS::explicitCrossProbe.MakeEvent() ); + Go( &SCH_EDITOR_CONTROL::ExplicitCrossProbeToPcb, EE_ACTIONS::selectOnPCB.MakeEvent() ); #ifdef KICAD_SPICE Go( &SCH_EDITOR_CONTROL::SimProbe, EE_ACTIONS::simProbe.MakeEvent() ); diff --git a/eeschema/tools/sch_editor_control.h b/eeschema/tools/sch_editor_control.h index a15670f2da..531acbf87e 100644 --- a/eeschema/tools/sch_editor_control.h +++ b/eeschema/tools/sch_editor_control.h @@ -84,8 +84,7 @@ public: ///< Notifies pcbnew about the selected item. int CrossProbeToPcb( const TOOL_EVENT& aEvent ); - ///< Equivalent to the above, but initiated by the user. We also do SCH_SHEETs on this - ///< one (they're too slow on big projects for the auto version above). + ///< Equivalent to the above, but initiated by the user. int ExplicitCrossProbeToPcb( const TOOL_EVENT& aEvent ); #ifdef KICAD_SPICE @@ -151,7 +150,7 @@ public: * Find a symbol in the schematic and an item in this symbol. * * @param aPath The symbol path to find. Pass nullptr to search by aReference. - * @param aReference The symbol reference designator to find, or to display in + * @param aReference The symbol reference designator to find, or to display in * status bar if aPath is specified * @param aSearchHierarchy If false, search the current sheet only. Otherwise, * the entire hierarchy diff --git a/include/mail_type.h b/include/mail_type.h index 9ceee272d9..12daf41c42 100644 --- a/include/mail_type.h +++ b/include/mail_type.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014 CERN - * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.TXT for contributors. + * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.TXT for contributors. * @author Maciej Suminski * * This program is free software; you can redistribute it and/or @@ -37,6 +37,7 @@ enum MAIL_T { MAIL_CROSS_PROBE, // PCB<->SCH, CVPCB->SCH cross-probing. + MAIL_SELECTION, // SCH->PCB selection synchronization. MAIL_ASSIGN_FOOTPRINTS, // CVPCB->SCH footprint stuffing MAIL_SCH_SAVE, // CVPCB->SCH save the schematic MAIL_EESCHEMA_NETLIST, // SCH->CVPCB netlist immediately after launching CVPCB diff --git a/include/settings/app_settings.h b/include/settings/app_settings.h index f5eee63526..8e23dbc42a 100644 --- a/include/settings/app_settings.h +++ b/include/settings/app_settings.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 Jon Evans - * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2020-2022 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 @@ -29,6 +29,7 @@ */ struct CROSS_PROBING_SETTINGS { + bool on_selection; ///< Synchronize the selection for multiple items too bool center_on_items; ///< Automatically pan to cross-probed items bool zoom_to_fit; ///< Zoom to fit items (ignored if center_on_items is off) bool auto_highlight; ///< Automatically turn on highlight mode in the target frame diff --git a/include/string_utils.h b/include/string_utils.h index 3e9e574541..f41e128ec8 100644 --- a/include/string_utils.h +++ b/include/string_utils.h @@ -53,6 +53,7 @@ enum ESCAPE_CONTEXT { CTX_NETNAME, CTX_LIBID, + CTX_IPC, CTX_QUOTED_STR, CTX_LINE, CTX_FILENAME, diff --git a/include/tool/selection_conditions.h b/include/tool/selection_conditions.h index d4d482764f..186c799237 100644 --- a/include/tool/selection_conditions.h +++ b/include/tool/selection_conditions.h @@ -131,6 +131,16 @@ public: */ static SELECTION_CONDITION HasType( KICAD_T aType ); + /** + * Create a functor that tests if among the selected items there is at least one of a + * given types. + * + * @param aTypes is an array containing types that are searched. It has to be ended with + * #KICAD_T::EOT as end marker. + * @return Functor testing for presence of items of a given types. + */ + static SELECTION_CONDITION HasTypes( const KICAD_T aTypes[] ); + /** * Create a functor that tests if the selected items are *only* of given type. * @@ -179,6 +189,9 @@ private: ///< Helper function used by HasType() static bool hasTypeFunc( const SELECTION& aSelection, KICAD_T aType ); + ///< Helper function used by HasTypes() + static bool hasTypesFunc( const SELECTION& aSelection, const KICAD_T aTypes[] ); + ///< Helper function used by OnlyType() static bool onlyTypeFunc( const SELECTION& aSelection, KICAD_T aType ); diff --git a/pcbnew/cross-probing.cpp b/pcbnew/cross-probing.cpp index 87dac9f59e..ed717c63e4 100644 --- a/pcbnew/cross-probing.cpp +++ b/pcbnew/cross-probing.cpp @@ -1,7 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 2019-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2019-2022 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 @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -277,133 +278,7 @@ void PCB_EDIT_FRAME::ExecuteRemoteCommand( const char* cmdline ) { if( crossProbingSettings.zoom_to_fit ) { -//#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( 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> 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>::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 + GetToolManager()->GetTool()->zoomFitCrossProbeBBox( bbox ); } FocusOnLocation( (wxPoint) bbox.Centre() ); @@ -510,6 +385,77 @@ void PCB_EDIT_FRAME::SendCrossProbeNetName( const wxString& aNetName ) } +std::vector PCB_EDIT_FRAME::FindItemsFromSyncSelection( std::string syncStr ) +{ + wxArrayString syncArray = wxStringTokenize( syncStr, "," ); + + std::vector items; + + for( FOOTPRINT* footprint : GetBoard()->Footprints() ) + { + if( footprint == nullptr ) + continue; + + wxString fpSheetPath = footprint->GetPath().AsString().BeforeLast( '/' ); + wxString fpUUID = footprint->m_Uuid.AsString(); + + if( fpSheetPath.IsEmpty() ) + fpSheetPath += '/'; + + if( fpUUID.empty() ) + continue; + + wxString fpRefEscaped = EscapeString( footprint->GetReference(), CTX_IPC ); + + for( wxString syncEntry : syncArray ) + { + if( syncEntry.empty() ) + continue; + + wxString syncData = syncEntry.substr( 1 ); + + switch( syncEntry.GetChar( 0 ).GetValue() ) + { + case 'S': // Select sheet with subsheets: S + if( fpSheetPath.StartsWith( syncData ) ) + { + items.push_back( footprint ); + } + break; + case 'F': // Select footprint: F + if( syncData == fpRefEscaped ) + { + items.push_back( footprint ); + } + break; + case 'P': // Select pad: P/ + { + if( syncData.StartsWith( fpRefEscaped ) ) + { + wxString selectPadNumberEscaped = + syncData.substr( fpRefEscaped.size() + 1 ); // Skips the slash + + wxString selectPadNumber = UnescapeString( selectPadNumberEscaped ); + + for( PAD* pad : footprint->Pads() ) + { + if( selectPadNumber == pad->GetNumber() ) + { + items.push_back( pad ); + } + } + } + break; + } + default: break; + } + } + } + + return items; +} + + void PCB_EDIT_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail ) { std::string& payload = mail.GetPayload(); @@ -575,6 +521,50 @@ void PCB_EDIT_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail ) ExecuteRemoteCommand( payload.c_str() ); break; + case MAIL_SELECTION: + { + // $SELECT: ,,, + std::string prefix = "$SELECT: "; + + if( !payload.compare( 0, prefix.size(), prefix ) ) + { + std::string del = ","; + std::string paramStr = payload.substr( prefix.size() ); + int modeEnd = paramStr.find( del ); + bool selectConnections = false; + + try + { + if( std::stoi( paramStr.substr( 0, modeEnd ) ) == 1 ) + selectConnections = true; + } + catch( std::invalid_argument& ) + { + wxFAIL; + } + + std::vector items = + FindItemsFromSyncSelection( paramStr.substr( modeEnd + 1 ) ); + + m_syncingSchToPcbSelection = true; // recursion guard + + if( selectConnections ) + { + GetToolManager()->RunAction( PCB_ACTIONS::syncSelectionWithNets, true, + static_cast( &items ) ); + } + else + { + GetToolManager()->RunAction( PCB_ACTIONS::syncSelection, true, + static_cast( &items ) ); + } + + m_syncingSchToPcbSelection = false; + } + + break; + } + case MAIL_PCB_UPDATE: m_toolManager->RunAction( ACTIONS::updatePcbFromSchematic, true ); break; diff --git a/pcbnew/dialogs/panel_display_options.cpp b/pcbnew/dialogs/panel_display_options.cpp index 19f35b9c3d..963173d4d0 100644 --- a/pcbnew/dialogs/panel_display_options.cpp +++ b/pcbnew/dialogs/panel_display_options.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2015 Jean-Pierre Charras, jean-pierre.charras at wanadoo.fr - * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2022 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 @@ -57,6 +57,7 @@ void PANEL_DISPLAY_OPTIONS::loadPCBSettings( PCBNEW_SETTINGS* aCfg ) m_OptDisplayPadNoConn->SetValue( aCfg->m_Display.m_DisplayPadNoConnects ); m_ShowNetNamesOption->SetSelection( aCfg->m_Display.m_DisplayNetNamesMode ); m_live3Drefresh->SetValue( aCfg->m_Display.m_Live3DRefresh ); + m_checkCrossProbeOnSelection->SetValue( aCfg->m_CrossProbing.on_selection ); m_checkCrossProbeCenter->SetValue( aCfg->m_CrossProbing.center_on_items ); m_checkCrossProbeZoom->SetValue( aCfg->m_CrossProbing.zoom_to_fit ); m_checkCrossProbeAutoHighlight->SetValue( aCfg->m_CrossProbing.auto_highlight ); @@ -99,7 +100,7 @@ bool PANEL_DISPLAY_OPTIONS::TransferDataFromWindow() cfg->m_Display.m_DisplayPadNoConnects = m_OptDisplayPadNoConn->GetValue(); cfg->m_Display.m_DisplayNetNamesMode = m_ShowNetNamesOption->GetSelection(); cfg->m_Display.m_Live3DRefresh = m_live3Drefresh->GetValue(); - cfg->m_CrossProbing.center_on_items = m_checkCrossProbeCenter->GetValue(); + cfg->m_CrossProbing.on_selection = m_checkCrossProbeOnSelection->GetValue(); cfg->m_CrossProbing.zoom_to_fit = m_checkCrossProbeZoom->GetValue(); cfg->m_CrossProbing.auto_highlight = m_checkCrossProbeAutoHighlight->GetValue(); } diff --git a/pcbnew/dialogs/panel_display_options_base.cpp b/pcbnew/dialogs/panel_display_options_base.cpp index aad076e38b..28d8b68711 100644 --- a/pcbnew/dialogs/panel_display_options_base.cpp +++ b/pcbnew/dialogs/panel_display_options_base.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Oct 26 2018) +// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -73,6 +73,10 @@ PANEL_DISPLAY_OPTIONS_BASE::PANEL_DISPLAY_OPTIONS_BASE( wxWindow* parent, wxWind wxStaticBoxSizer* sbSizer3; sbSizer3 = new wxStaticBoxSizer( new wxStaticBox( pcbPage, wxID_ANY, _("Cross-probing") ), wxVERTICAL ); + m_checkCrossProbeOnSelection = new wxCheckBox( sbSizer3->GetStaticBox(), wxID_ANY, _("Cross-probe on selection"), wxDefaultPosition, wxDefaultSize, 0 ); + m_checkCrossProbeOnSelection->SetValue(true); + sbSizer3->Add( m_checkCrossProbeOnSelection, 0, wxALL, 5 ); + m_checkCrossProbeCenter = new wxCheckBox( sbSizer3->GetStaticBox(), wxID_ANY, _("Scroll cross-probed items into view"), wxDefaultPosition, wxDefaultSize, 0 ); m_checkCrossProbeCenter->SetValue(true); sbSizer3->Add( m_checkCrossProbeCenter, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); diff --git a/pcbnew/dialogs/panel_display_options_base.fbp b/pcbnew/dialogs/panel_display_options_base.fbp index 986e35c77d..f7d585b70c 100644 --- a/pcbnew/dialogs/panel_display_options_base.fbp +++ b/pcbnew/dialogs/panel_display_options_base.fbp @@ -1,6 +1,6 @@ - + C++ @@ -14,6 +14,7 @@ panel_display_options_base 1000 none + 1 PanelDisplayOptions @@ -25,6 +26,7 @@ 1 1 UI + 0 1 0 @@ -46,6 +48,7 @@ -1,-1 RESETTABLE_PANEL; widgets/resettable_panel.h; Not forward_declare + 0 wxTAB_TRAVERSAL @@ -608,6 +611,70 @@ wxVERTICAL 1 none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Cross-probe on selection + + 0 + + + 0 + + 1 + m_checkCrossProbeOnSelection + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + 5 wxBOTTOM|wxRIGHT|wxLEFT diff --git a/pcbnew/dialogs/panel_display_options_base.h b/pcbnew/dialogs/panel_display_options_base.h index 8b8db94e68..f5458f31ae 100644 --- a/pcbnew/dialogs/panel_display_options_base.h +++ b/pcbnew/dialogs/panel_display_options_base.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Oct 26 2018) +// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -45,6 +45,7 @@ class PANEL_DISPLAY_OPTIONS_BASE : public RESETTABLE_PANEL wxCheckBox* m_OptDisplayPadNoConn; wxRadioBox* m_OptDisplayTracksClearance; wxCheckBox* m_OptDisplayPadClearence; + wxCheckBox* m_checkCrossProbeOnSelection; wxCheckBox* m_checkCrossProbeCenter; wxCheckBox* m_checkCrossProbeZoom; wxCheckBox* m_checkCrossProbeAutoHighlight; @@ -53,6 +54,7 @@ class PANEL_DISPLAY_OPTIONS_BASE : public RESETTABLE_PANEL public: PANEL_DISPLAY_OPTIONS_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString ); + ~PANEL_DISPLAY_OPTIONS_BASE(); }; diff --git a/pcbnew/pcb_edit_frame.cpp b/pcbnew/pcb_edit_frame.cpp index 8f7bbfa0f6..4dec609300 100644 --- a/pcbnew/pcb_edit_frame.cpp +++ b/pcbnew/pcb_edit_frame.cpp @@ -4,7 +4,7 @@ * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck * Copyright (C) 2013 Wayne Stambaugh - * Copyright (C) 2013-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2013-2022 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 @@ -175,10 +175,10 @@ END_EVENT_TABLE() PCB_EDIT_FRAME::PCB_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : - PCB_BASE_EDIT_FRAME( aKiway, aParent, FRAME_PCB_EDITOR, _( "PCB Editor" ), wxDefaultPosition, - wxDefaultSize, KICAD_DEFAULT_DRAWFRAME_STYLE, PCB_EDIT_FRAME_NAME ), - m_exportNetlistAction( nullptr ), - m_findDialog( nullptr ) + PCB_BASE_EDIT_FRAME( aKiway, aParent, FRAME_PCB_EDITOR, _( "PCB Editor" ), + wxDefaultPosition, wxDefaultSize, KICAD_DEFAULT_DRAWFRAME_STYLE, + PCB_EDIT_FRAME_NAME ), + m_exportNetlistAction( nullptr ), m_findDialog( nullptr ) { m_maximizeByDefault = true; m_showBorderAndTitleBlock = true; // true to display sheet references @@ -187,6 +187,7 @@ PCB_EDIT_FRAME::PCB_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : m_SelLayerBox = nullptr; m_show_layer_manager_tools = true; m_supportsAutoSave = true; + m_syncingSchToPcbSelection = false; // We don't know what state board was in when it was last saved, so we have to // assume dirty diff --git a/pcbnew/pcb_edit_frame.h b/pcbnew/pcb_edit_frame.h index e42c08a0bd..efecdd267b 100644 --- a/pcbnew/pcb_edit_frame.h +++ b/pcbnew/pcb_edit_frame.h @@ -121,6 +121,11 @@ public: void KiwayMailIn( KIWAY_EXPRESS& aEvent ) override; + /** + * Used to find items by selection synchronization spec string. + */ + std::vector FindItemsFromSyncSelection( std::string syncStr ); + /** * Show the Find dialog. */ @@ -782,6 +787,8 @@ public: bool m_ZoneFillsDirty; // Board has been modified since last zone fill. + bool m_syncingSchToPcbSelection; // Recursion guard when synchronizing selection from schematic + private: friend struct PCB::IFACE; friend class APPEARANCE_CONTROLS; diff --git a/pcbnew/tools/board_inspection_tool.cpp b/pcbnew/tools/board_inspection_tool.cpp index 4f60bc0ec1..7dd0d0320c 100644 --- a/pcbnew/tools/board_inspection_tool.cpp +++ b/pcbnew/tools/board_inspection_tool.cpp @@ -1,7 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 2019-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2019-2022 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 @@ -866,7 +866,10 @@ int BOARD_INSPECTION_TOOL::InspectConstraints( const TOOL_EVENT& aEvent ) int BOARD_INSPECTION_TOOL::CrossProbePcbToSch( const TOOL_EVENT& aEvent ) { // Don't get in an infinite loop PCB -> SCH -> PCB -> SCH -> ... - if( m_probingSchToPcb ) + if( m_probingSchToPcb || m_frame->m_syncingSchToPcbSelection ) + return 0; + + if( !frame()->Settings().m_CrossProbing.on_selection ) return 0; PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool(); diff --git a/pcbnew/tools/pcb_actions.cpp b/pcbnew/tools/pcb_actions.cpp index 8e82fad03c..25251bd9fd 100644 --- a/pcbnew/tools/pcb_actions.cpp +++ b/pcbnew/tools/pcb_actions.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013-2016 CERN - * Copyright (C) 2016-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2016-2022 KiCad Developers, see AUTHORS.txt for contributors. * @author Maciej Suminski * * This program is free software; you can redistribute it and/or @@ -1240,6 +1240,12 @@ TOOL_ACTION PCB_ACTIONS::selectConnection( "pcbnew.InteractiveSelection.SelectCo _( "Selects a connection or expands an existing selection to junctions, pads, or entire connections" ), BITMAPS::add_tracks ); +TOOL_ACTION PCB_ACTIONS::syncSelection( "pcbnew.InteractiveSelection.SyncSelection", + AS_GLOBAL ); + +TOOL_ACTION PCB_ACTIONS::syncSelectionWithNets( "pcbnew.InteractiveSelection.SyncSelectionWithNets", + AS_GLOBAL ); + TOOL_ACTION PCB_ACTIONS::selectNet( "pcbnew.InteractiveSelection.SelectNet", AS_GLOBAL, 0, "", _( "Select All Tracks in Net" ), diff --git a/pcbnew/tools/pcb_actions.h b/pcbnew/tools/pcb_actions.h index ca40498076..77bd02d082 100644 --- a/pcbnew/tools/pcb_actions.h +++ b/pcbnew/tools/pcb_actions.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013-2016 CERN - * Copyright (C) 2016-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2016-2022 KiCad Developers, see AUTHORS.txt for contributors. * * @author Maciej Suminski * @@ -66,6 +66,12 @@ public: static TOOL_ACTION selectItems; static TOOL_ACTION unselectItems; + /// Sets selection to specified items, zooms to fit, if enabled + static TOOL_ACTION syncSelection; + + /// Sets selection to specified items with connected nets, zooms to fit, if enabled + static TOOL_ACTION syncSelectionWithNets; + /// Run a selection menu to select from a list of items static TOOL_ACTION selectionMenu; diff --git a/pcbnew/tools/pcb_selection_tool.cpp b/pcbnew/tools/pcb_selection_tool.cpp index e9ac7b11e4..ed59071a90 100644 --- a/pcbnew/tools/pcb_selection_tool.cpp +++ b/pcbnew/tools/pcb_selection_tool.cpp @@ -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) 2018-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2018-2022 KiCad Developers, see AUTHORS.txt for contributors. * @author Tomasz Wlostowski * @author Maciej Suminski * @@ -25,6 +25,7 @@ */ #include +#include #include using namespace std::placeholders; #include @@ -62,6 +63,7 @@ using namespace std::placeholders; #include #include #include +#include class SELECT_MENU : public ACTION_MENU @@ -1100,34 +1102,49 @@ int PCB_SELECTION_TOOL::expandConnection( const TOOL_EVENT& aEvent ) for( const EDA_ITEM* item : m_selection.GetItems() ) { - if( dynamic_cast( item ) ) + 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 } ) { - // copy the selection, since we're going to iterate and modify std::deque selectedItems = m_selection.GetItems(); for( EDA_ITEM* item : selectedItems ) item->ClearTempFlags(); + std::vector startItems; + for( EDA_ITEM* item : selectedItems ) { - PCB_TRACK* trackItem = dynamic_cast( item ); + if( item->Type() == PCB_FOOTPRINT_T ) + { + FOOTPRINT* footprint = static_cast( item ); - // Track items marked SKIP_STRUCT have already been visited - if( trackItem && !( trackItem->GetFlags() & SKIP_STRUCT ) ) - selectConnectedTracks( *trackItem, stopCondition ); + for( PAD* pad : footprint->Pads() ) + { + startItems.push_back( pad ); + } + } + else if( BOARD_CONNECTED_ITEM::ClassOf( item ) ) + { + startItems.push_back( static_cast( 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 ); @@ -1136,165 +1153,220 @@ int PCB_SELECTION_TOOL::expandConnection( const TOOL_EVENT& aEvent ) } -void PCB_SELECTION_TOOL::selectConnectedTracks( BOARD_CONNECTED_ITEM& aStartItem, - STOP_CONDITION aStopCondition ) +void PCB_SELECTION_TOOL::selectAllConnectedTracks( + const std::vector& aStartItems, STOP_CONDITION aStopCondition ) { - constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, PCB_PAD_T, EOT }; - constexpr PCB_LAYER_ID ALL_LAYERS = UNDEFINED_LAYER; + constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, PCB_PAD_T, EOT }; + 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(); - auto connectedItems = connectivity->GetConnectedItems( &aStartItem, types, true ); std::map> trackMap; std::map viaMap; std::map padMap; + std::set startPadSet; + std::vector cleanupItems; + std::vector> activePts; - // Build maps of connected items - for( BOARD_CONNECTED_ITEM* item : connectedItems ) + for( BOARD_CONNECTED_ITEM* startItem : aStartItems ) { - switch( item->Type() ) + // Track starting pads + if( startItem->Type() == PCB_PAD_T ) + startPadSet.insert( static_cast( startItem ) ); + } + + for( BOARD_CONNECTED_ITEM* startItem : aStartItems ) + { + if( startItem->HasFlag( SKIP_STRUCT ) ) // Skip already visited items + continue; + + std::vector connectedItems = + connectivity->GetConnectedItems( startItem, types, 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( item ); + trackMap[track->GetStart()].push_back( track ); + trackMap[track->GetEnd()].push_back( track ); + break; + } + + case PCB_VIA_T: + { + PCB_VIA* via = static_cast( item ); + viaMap[via->GetStart()] = via; + break; + } + + case PCB_PAD_T: + { + PAD* pad = static_cast( 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( item ); - trackMap[ track->GetStart() ].push_back( track ); - trackMap[ track->GetEnd() ].push_back( track ); + PCB_TRACK* track = static_cast( startItem ); + + activePts.push_back( { track->GetStart(), track->GetLayerSet() } ); + activePts.push_back( { track->GetEnd(), track->GetLayerSet() } ); break; } case PCB_VIA_T: - { - PCB_VIA* via = static_cast( item ); - viaMap[ via->GetStart() ] = via; + activePts.push_back( { startItem->GetPosition(), startItem->GetLayerSet() } ); break; - } case PCB_PAD_T: - { - PAD* pad = static_cast( item ); - padMap[ pad->GetPosition() ] = pad; + activePts.push_back( { startItem->GetPosition(), startItem->GetLayerSet() } ); break; + + default: break; } - default: - break; - } + bool expand = true; + int failSafe = 0; - item->ClearFlags( TEMP_SELECTED ); - } - - std::vector> activePts; - - // Set up the initial active points - switch( aStartItem.Type() ) - { - case PCB_ARC_T: - case PCB_TRACE_T: - { - PCB_TRACK* track = static_cast( &aStartItem ); - - activePts.push_back( { track->GetStart(), track->GetLayer() } ); - activePts.push_back( { track->GetEnd(), track->GetLayer() } ); - } - break; - - case PCB_VIA_T: - activePts.push_back( { aStartItem.GetPosition(), ALL_LAYERS } ); - break; - - case PCB_PAD_T: - activePts.push_back( { aStartItem.GetPosition(), ALL_LAYERS } ); - 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 ) + // Iterative push from all active points + while( expand && failSafe++ < 100000 ) { - VECTOR2I pt = activePts[i].first; - PCB_LAYER_ID layer = activePts[i].second; - size_t pt_count = 0; + expand = false; - for( PCB_TRACK* track : trackMap[pt] ) + for( int i = activePts.size() - 1; i >= 0; --i ) { - if( layer == ALL_LAYERS || layer == track->GetLayer() ) - pt_count++; - } + VECTOR2I pt = activePts[i].first; + LSET layerSetCu = activePts[i].second & allCuMask; - if( aStopCondition == STOP_AT_JUNCTION ) - { - if( pt_count > 2 - || ( viaMap.count( pt ) && layer != ALL_LAYERS ) - || ( padMap.count( pt ) && layer != ALL_LAYERS ) ) + 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 ) { - activePts.erase( activePts.begin() + i ); - continue; + size_t pt_count = 0; + + for( PCB_TRACK* track : trackMap[pt] ) + { + if( 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( padMap.count( pt ) ) + else if( aStopCondition == STOP_AT_PAD ) { - activePts.erase( activePts.begin() + i ); - continue; + if( gotNonStartPad ) + { + activePts.erase( activePts.begin() + i ); + continue; + } } - } - if( padMap.count( pt ) ) - { - PAD* pad = padMap[ pt ]; - - if( !( pad->GetFlags() & TEMP_SELECTED ) ) + if( gotPad ) { - pad->SetFlags( TEMP_SELECTED ); - activePts.push_back( { pad->GetPosition(), ALL_LAYERS } ); - expand = true; + 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( layer != ALL_LAYERS && track->GetLayer() != layer ) - continue; - - if( !track->IsSelected() ) + for( PCB_TRACK* track : trackMap[pt] ) { - select( track ); + if( !layerSetCu.Contains( track->GetLayer() ) ) + continue; - if( track->GetStart() == pt ) - activePts.push_back( { track->GetEnd(), track->GetLayer() } ); - else - activePts.push_back( { track->GetStart(), track->GetLayer() } ); + if( !track->IsSelected() ) + select( track ); - expand = true; + 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() ) + if( viaMap.count( pt ) ) { - select( via ); - activePts.push_back( { via->GetPosition(), ALL_LAYERS } ); - expand = true; + 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 ); } - 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 ); + } } @@ -1348,7 +1420,7 @@ int PCB_SELECTION_TOOL::selectNet( const TOOL_EVENT& aEvent ) void PCB_SELECTION_TOOL::selectAllItemsOnSheet( wxString& aSheetPath ) { - std::list footprintList; + std::vector footprints; // store all footprints that are on that sheet path for( FOOTPRINT* footprint : board()->Footprints() ) @@ -1356,52 +1428,88 @@ void PCB_SELECTION_TOOL::selectAllItemsOnSheet( wxString& aSheetPath ) if( footprint == nullptr ) continue; - wxString footprint_path = footprint->GetPath().AsString().BeforeLast('/'); + wxString footprint_path = footprint->GetPath().AsString().BeforeLast( '/' ); if( aSheetPath.IsEmpty() ) aSheetPath += '/'; if( footprint_path == aSheetPath ) - footprintList.push_back( footprint ); + footprints.push_back( footprint ); } - // Generate a list of all pads, and of all nets they belong to. - std::list netcodeList; - std::list padList; - - for( FOOTPRINT* footprint : footprintList ) + for( BOARD_ITEM* i : footprints ) { - for( PAD* pad : footprint->Pads() ) + if( i != nullptr ) + select( i ); + } + + selectConnections( footprints ); +} + + +void PCB_SELECTION_TOOL::selectConnections( const std::vector& aItems ) +{ + // Generate a list of all pads, and of all nets they belong to. + std::list netcodeList; + std::vector padList; + + for( BOARD_ITEM* item : aItems ) + { + switch( item->Type() ) { + case PCB_FOOTPRINT_T: + { + for( PAD* pad : static_cast( item )->Pads() ) + { + if( pad->IsConnected() ) + { + netcodeList.push_back( pad->GetNetCode() ); + padList.push_back( pad ); + } + } + + break; + } + case PCB_PAD_T: + { + PAD* pad = static_cast( 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(); - for( PAD* pad : padList ) - selectConnectedTracks( *pad, STOP_NEVER ); + 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 belonging to this sheet - std::list removeCodeList; + // to determine if these footprints are in the list of footprints + std::vector removeCodeList; constexpr KICAD_T padType[] = { PCB_PAD_T, EOT }; for( int netCode : netcodeList ) { - for( BOARD_CONNECTED_ITEM* mitem : board()->GetConnectivity()->GetNetItems( netCode, - padType ) ) + for( BOARD_CONNECTED_ITEM* mitem : + board()->GetConnectivity()->GetNetItems( netCode, padType ) ) { - if( mitem->Type() == PCB_PAD_T && !alg::contains( footprintList, mitem->GetParent() ) ) + if( mitem->Type() == PCB_PAD_T + && !std::binary_search( padList.begin(), padList.end(), mitem ) ) { - // if we cannot find the footprint of the pad in the footprintList then we can - // assume that that footprint is not located in the same schematic, therefore + // 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; @@ -1409,31 +1517,21 @@ void PCB_SELECTION_TOOL::selectAllItemsOnSheet( wxString& aSheetPath ) } } - // remove all duplicates - removeCodeList.sort(); - removeCodeList.unique(); - for( int removeCode : removeCodeList ) { netcodeList.remove( removeCode ); } - std::list localConnectionList; - constexpr KICAD_T trackViaType[] = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, EOT }; + std::vector 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 ) ) + for( BOARD_CONNECTED_ITEM* item : + board()->GetConnectivity()->GetNetItems( netCode, trackViaType ) ) localConnectionList.push_back( item ); } - for( BOARD_ITEM* i : footprintList ) - { - if( i != nullptr ) - select( i ); - } - for( BOARD_CONNECTED_ITEM* i : localConnectionList ) { if( i != nullptr ) @@ -1442,27 +1540,55 @@ void PCB_SELECTION_TOOL::selectAllItemsOnSheet( wxString& aSheetPath ) } -void PCB_SELECTION_TOOL::zoomFitSelection() +int PCB_SELECTION_TOOL::syncSelection( const TOOL_EVENT& aEvent ) { - // Should recalculate the view to zoom in on the selection. - auto selectionBox = m_selection.GetBoundingBox(); - auto view = getView(); + std::vector* items = aEvent.Parameter*>(); - 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( items ) + doSyncSelection( *items, false ); - if( selectionBox.GetWidth() != 0 || selectionBox.GetHeight() != 0 ) + return 0; +} + + +int PCB_SELECTION_TOOL::syncSelectionWithNets( const TOOL_EVENT& aEvent ) +{ + std::vector* items = aEvent.Parameter*>(); + + if( items ) + doSyncSelection( *items, true ); + + return 0; +} + + +void PCB_SELECTION_TOOL::doSyncSelection( const std::vector& 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( m_frame->Settings().m_CrossProbing.center_on_items ) { - 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 ); + if( m_frame->Settings().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 ); } @@ -1519,6 +1645,158 @@ int PCB_SELECTION_TOOL::selectSameSheet( const TOOL_EVENT& aEvent ) } +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> 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>::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; @@ -2772,6 +3050,9 @@ void PCB_SELECTION_TOOL::setTransitions() 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() ); diff --git a/pcbnew/tools/pcb_selection_tool.h b/pcbnew/tools/pcb_selection_tool.h index 614a2058d7..1ff6f5fe0f 100644 --- a/pcbnew/tools/pcb_selection_tool.h +++ b/pcbnew/tools/pcb_selection_tool.h @@ -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-2021 KiCad Developers, see AUTHORS.TXT for contributors. + * Copyright (C) 2017-2022 KiCad Developers, see AUTHORS.TXT for contributors. * * @author Tomasz Wlostowski * @author Maciej Suminski @@ -181,6 +181,9 @@ public: ///< Zoom the screen to center and fit the current selection. void zoomFitSelection(); + ///< Zoom the screen to fit the bounding box for cross probing/selection sync. + void zoomFitCrossProbeBBox( EDA_RECT bbox ); + BOARD* GetBoard() const { return board(); @@ -304,7 +307,8 @@ private: * * @param aStopCondition where to stop selecting more items */ - void selectConnectedTracks( BOARD_CONNECTED_ITEM& aSourceItem, STOP_CONDITION aStopCondition ); + void selectAllConnectedTracks( const std::vector& aStartItems, + STOP_CONDITION aStopCondition ); /** * Select all items with the given net code. @@ -314,6 +318,11 @@ private: */ void selectAllItemsOnNet( int aNetCode, bool aSelect = true ); + /* + * Select tracks and vias connected to specified board items. + */ + void selectConnections( const std::vector& aItems ); + /** * Select all items with the given sheet timestamp/UUID name (the sheet path). * @@ -328,6 +337,12 @@ private: ///< (same sheet path). int selectSameSheet( const TOOL_EVENT& aEvent ); + ///< Set selection to items passed by parameter and connected nets (optionally). + ///< Zooms to fit, if enabled + int syncSelection( const TOOL_EVENT& aEvent ); + int syncSelectionWithNets( const TOOL_EVENT& aEvent ); + void doSyncSelection( const std::vector& aItems, bool aWithNets ); + ///< Invoke filter dialog and modify current selection int filterSelection( const TOOL_EVENT& aEvent ); diff --git a/qa/qa_utils/mocks.cpp b/qa/qa_utils/mocks.cpp index 1726cf39ee..8e74d655f3 100644 --- a/qa/qa_utils/mocks.cpp +++ b/qa/qa_utils/mocks.cpp @@ -435,8 +435,8 @@ int PCB_SELECTION_TOOL::expandConnection( const TOOL_EVENT& aEvent ) } -void PCB_SELECTION_TOOL::selectConnectedTracks( BOARD_CONNECTED_ITEM& aStartItem, - STOP_CONDITION aStopCondition ) +void PCB_SELECTION_TOOL::selectAllConnectedTracks( + const std::vector& aStartItems, STOP_CONDITION aStopCondition ) { }