kicad/pcbnew/cross-probing.cpp

487 lines
15 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2019-2020 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 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-3.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
*/
/**
* @file pcbnew/cross-probing.cpp
* @brief Cross probing functions to handle communication to and from Eeschema.
* Handle messages between Pcbnew and Eeschema via a socket, the port numbers are
* KICAD_PCB_PORT_SERVICE_NUMBER (currently 4242) (Eeschema to Pcbnew)
* KICAD_SCH_PORT_SERVICE_NUMBER (currently 4243) (Pcbnew to Eeschema)
* Note: these ports must be enabled for firewall protection
*/
#include <class_board.h>
#include <class_module.h>
#include <class_track.h>
#include <collectors.h>
#include <eda_dde.h>
#include <fctsys.h>
#include <kiface_i.h>
#include <kiway_express.h>
#include <macros.h>
#include <netlist_reader/pcb_netlist.h>
#include <netlist_reader/board_netlist_updater.h>
#include <pcb_edit_frame.h>
#include <pcbnew_settings.h>
#include <tool/tool_manager.h>
#include <tools/pcb_actions.h>
#include <tools/selection_tool.h>
#include <netlist_reader/netlist_reader.h>
/* Execute a remote command send by Eeschema via a socket,
* port KICAD_PCB_PORT_SERVICE_NUMBER
* cmdline = received command from Eeschema
* Commands are
* $PART: "reference" put cursor on component
* $PIN: "pin name" $PART: "reference" put cursor on the footprint pin
* $NET: "net name" highlight the given net (if highlight tool is active)
* $CLEAR Clear existing highlight
* They are a keyword followed by a quoted string.
*/
void PCB_EDIT_FRAME::ExecuteRemoteCommand( const char* cmdline )
{
char line[1024];
wxString msg;
wxString modName;
char* idcmd;
char* text;
int netcode = -1;
bool multiHighlight = false;
MODULE* module = NULL;
D_PAD* pad = NULL;
BOARD* pcb = GetBoard();
CROSS_PROBING_SETTINGS& crossProbingSettings = GetPcbNewSettings()->m_CrossProbing;
KIGFX::VIEW* view = m_toolManager->GetView();
KIGFX::RENDER_SETTINGS* renderSettings = view->GetPainter()->GetSettings();
strncpy( line, cmdline, sizeof(line) - 1 );
line[sizeof(line) - 1] = 0;
idcmd = strtok( line, " \n\r" );
text = strtok( NULL, "\"\n\r" );
if( idcmd == NULL )
return;
if( strcmp( idcmd, "$NET:" ) == 0 )
{
if( !crossProbingSettings.auto_highlight )
return;
wxString net_name = FROM_UTF8( text );
NETINFO_ITEM* netinfo = pcb->FindNet( net_name );
if( netinfo )
{
netcode = netinfo->GetNet();
MSG_PANEL_ITEMS items;
netinfo->GetMsgPanelInfo( this, items );
SetMsgPanel( items );
}
}
if( strcmp( idcmd, "$NETS:" ) == 0 )
{
if( !crossProbingSettings.auto_highlight )
return;
wxStringTokenizer netsTok = wxStringTokenizer( FROM_UTF8( text ), "," );
bool first = true;
while( netsTok.HasMoreTokens() )
{
NETINFO_ITEM* netinfo = pcb->FindNet( netsTok.GetNextToken() );
if( netinfo )
{
if( first )
{
// TODO: Once buses are included in netlist, show bus name
MSG_PANEL_ITEMS items;
netinfo->GetMsgPanelInfo( this, items );
SetMsgPanel( items );
first = false;
pcb->SetHighLightNet( netinfo->GetNet() );
renderSettings->SetHighlight( true, netinfo->GetNet() );
multiHighlight = true;
}
else
{
pcb->SetHighLightNet( netinfo->GetNet(), true );
renderSettings->SetHighlight( true, netinfo->GetNet(), true );
}
}
}
netcode = -1;
}
else if( strcmp( idcmd, "$PIN:" ) == 0 )
{
wxString pinName = FROM_UTF8( text );
text = strtok( NULL, " \n\r" );
if( text && strcmp( text, "$PART:" ) == 0 )
text = strtok( NULL, "\"\n\r" );
modName = FROM_UTF8( text );
module = pcb->FindModuleByReference( modName );
if( module )
pad = module->FindPadByName( pinName );
if( pad )
netcode = pad->GetNetCode();
if( module == NULL )
msg.Printf( _( "%s not found" ), modName );
else if( pad == NULL )
msg.Printf( _( "%s pin %s not found" ), modName, pinName );
else
msg.Printf( _( "%s pin %s found" ), modName, pinName );
SetStatusText( msg );
}
else if( strcmp( idcmd, "$PART:" ) == 0 )
{
pcb->ResetNetHighLight();
modName = FROM_UTF8( text );
module = pcb->FindModuleByReference( modName );
if( module )
msg.Printf( _( "%s found" ), modName );
else
msg.Printf( _( "%s not found" ), modName );
SetStatusText( msg );
}
else if( strcmp( idcmd, "$SHEET:" ) == 0 )
{
msg.Printf( _( "Selecting all from sheet \"%s\"" ), FROM_UTF8( text ) );
wxString sheetUIID( FROM_UTF8( text ) );
SetStatusText( msg );
GetToolManager()->RunAction( PCB_ACTIONS::selectOnSheetFromEeschema, true,
static_cast<void*>( &sheetUIID ) );
return;
}
else if( strcmp( idcmd, "$CLEAR" ) == 0 )
{
if( renderSettings->IsHighlightEnabled() )
{
renderSettings->SetHighlight( false );
view->UpdateAllLayersColor();
}
if( pcb->IsHighLightNetON() )
{
pcb->ResetNetHighLight();
SetMsgPanel( pcb );
}
GetCanvas()->Refresh();
return;
}
BOX2I bbox = { { 0, 0 }, { 0, 0 } };
if( module )
{
bbox = module->GetBoundingBox();
if( pad )
m_toolManager->RunAction( PCB_ACTIONS::highlightItem, true, (void*) pad );
else
m_toolManager->RunAction( PCB_ACTIONS::highlightItem, true, (void*) module );
}
else if( netcode > 0 || multiHighlight )
{
if( !multiHighlight )
{
renderSettings->SetHighlight( ( netcode >= 0 ), netcode );
pcb->SetHighLightNet( netcode );
}
else
{
// Just pick the first one for area calculation
netcode = *pcb->GetHighLightNetCodes().begin();
}
pcb->HighLightON();
auto merge_area = [netcode, &bbox]( BOARD_CONNECTED_ITEM* aItem )
{
if( aItem->GetNetCode() == netcode )
{
if( bbox.GetWidth() == 0 )
bbox = aItem->GetBoundingBox();
else
bbox.Merge( aItem->GetBoundingBox() );
}
};
if( crossProbingSettings.center_on_items )
{
for( auto zone : pcb->Zones() )
merge_area( zone );
for( auto track : pcb->Tracks() )
merge_area( track );
for( auto mod : pcb->Modules() )
for( auto mod_pad : mod->Pads() )
merge_area( mod_pad );
}
}
else
{
renderSettings->SetHighlight( false );
}
if( crossProbingSettings.center_on_items && bbox.GetWidth() > 0 && bbox.GetHeight() > 0 )
{
auto bbSize = bbox.Inflate( bbox.GetWidth() * 0.2f ).GetSize();
auto screenSize = view->ToWorld( GetCanvas()->GetClientSize(), false );
screenSize.x = std::max( 10.0, 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 );
view->SetCenter( bbox.Centre() );
}
view->UpdateAllLayersColor();
// Ensure the display is refreshed, because in some installs the refresh is done only
// when the gal canvas has the focus, and that is not the case when crossprobing from
// Eeschema:
GetCanvas()->Refresh();
}
std::string FormatProbeItem( BOARD_ITEM* aItem )
{
MODULE* module;
if( !aItem )
return "$CLEAR: \"HIGHLIGHTED\""; // message to clear highlight state
switch( aItem->Type() )
{
case PCB_MODULE_T:
module = (MODULE*) aItem;
return StrPrintf( "$PART: \"%s\"", TO_UTF8( module->GetReference() ) );
case PCB_PAD_T:
{
module = (MODULE*) aItem->GetParent();
wxString pad = ((D_PAD*)aItem)->GetName();
return StrPrintf( "$PART: \"%s\" $PAD: \"%s\"",
TO_UTF8( module->GetReference() ),
TO_UTF8( pad ) );
}
case PCB_MODULE_TEXT_T:
{
module = static_cast<MODULE*>( aItem->GetParent() );
TEXTE_MODULE* text_mod = static_cast<TEXTE_MODULE*>( aItem );
const char* text_key;
/* This can't be a switch since the break need to pull out
* from the outer switch! */
if( text_mod->GetType() == TEXTE_MODULE::TEXT_is_REFERENCE )
text_key = "$REF:";
else if( text_mod->GetType() == TEXTE_MODULE::TEXT_is_VALUE )
text_key = "$VAL:";
else
break;
return StrPrintf( "$PART: \"%s\" %s \"%s\"",
TO_UTF8( module->GetReference() ),
text_key,
TO_UTF8( text_mod->GetText() ) );
}
default:
break;
}
return "";
}
/* Send a remote command to Eeschema via a socket,
* aSyncItem = item to be located on schematic (module, pin or text)
* Commands are
* $PART: "reference" put cursor on component anchor
* $PART: "reference" $PAD: "pad number" put cursor on the component pin
* $PART: "reference" $REF: "reference" put cursor on the component ref
* $PART: "reference" $VAL: "value" put cursor on the component value
*/
void PCB_EDIT_FRAME::SendMessageToEESCHEMA( BOARD_ITEM* aSyncItem )
{
std::string packet = FormatProbeItem( aSyncItem );
if( !packet.empty() )
{
if( Kiface().IsSingle() )
SendCommand( MSG_TO_SCH, packet.c_str() );
else
{
// Typically ExpressMail is going to be s-expression packets, but since
// we have existing interpreter of the cross probe packet on the other
// side in place, we use that here.
Kiway().ExpressMail( FRAME_SCH, MAIL_CROSS_PROBE, packet, this );
}
}
}
void PCB_EDIT_FRAME::SendCrossProbeNetName( const wxString& aNetName )
{
std::string packet = StrPrintf( "$NET: \"%s\"", TO_UTF8( aNetName ) );
if( !packet.empty() )
{
if( Kiface().IsSingle() )
SendCommand( MSG_TO_SCH, packet.c_str() );
else
{
// Typically ExpressMail is going to be s-expression packets, but since
// we have existing interpreter of the cross probe packet on the other
// side in place, we use that here.
Kiway().ExpressMail( FRAME_SCH, MAIL_CROSS_PROBE, packet, this );
}
}
}
void PCB_EDIT_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail )
{
std::string& payload = mail.GetPayload();
switch( mail.Command() )
{
case MAIL_PCB_GET_NETLIST:
{
NETLIST netlist;
STRING_FORMATTER sf;
for( MODULE* module : this->GetBoard()->Modules() )
{
COMPONENT* component = new COMPONENT( module->GetFPID(), module->GetReference(),
module->GetValue(), module->GetPath() );
for( D_PAD* pad : module->Pads() )
{
const wxString& netname = pad->GetShortNetname();
if( !netname.IsEmpty() )
component->AddNet( pad->GetName(), netname, wxEmptyString );
}
netlist.AddComponent( component );
}
netlist.Format( "pcb_netlist", &sf, 0, CTL_OMIT_FILTERS );
payload = sf.GetString();
}
break;
case MAIL_PCB_UPDATE_LINKS:
try
{
NETLIST netlist;
FetchNetlistFromSchematic( netlist, NO_ANNOTATION );
BOARD_NETLIST_UPDATER updater( this, GetBoard() );
updater.SetLookupByTimestamp( false );
updater.SetDeleteUnusedComponents ( false );
updater.SetReplaceFootprints( false );
updater.SetDeleteSinglePadNets( false );
updater.SetWarnPadNoNetInNetlist( false );
updater.UpdateNetlist( netlist );
bool dummy;
OnNetlistChanged( updater, &dummy );
}
catch( const IO_ERROR& )
{
assert( false ); // should never happen
return;
}
break;
case MAIL_CROSS_PROBE:
ExecuteRemoteCommand( payload.c_str() );
break;
case MAIL_PCB_UPDATE:
m_toolManager->RunAction( ACTIONS::updatePcbFromSchematic, true );
break;
case MAIL_IMPORT_FILE:
{
// Extract file format type and path (plugin type and path separated with \n)
size_t split = payload.find( '\n' );
wxCHECK( split != std::string::npos, /*void*/ );
int importFormat;
try
{
importFormat = std::stoi( payload.substr( 0, split ) );
}
catch( std::invalid_argument& )
{
wxFAIL;
importFormat = -1;
}
std::string path = payload.substr( split + 1 );
wxASSERT( !path.empty() );
if( importFormat >= 0 )
importFile( path, importFormat );
}
break;
// many many others.
default:
;
}
}