Browse Source

pcbnew: refactor topology matching algorithm and move it to the connectivity subsystem

jobs
Tomasz Wlostowski 1 year ago
parent
commit
0057e8b1d3
  1. 3
      pcbnew/connectivity/CMakeLists.txt
  2. 455
      pcbnew/connectivity/topo_match.cpp
  3. 178
      pcbnew/connectivity/topo_match.h
  4. 511
      pcbnew/tools/multichannel_tool.cpp
  5. 80
      pcbnew/tools/multichannel_tool.h

3
pcbnew/connectivity/CMakeLists.txt

@ -12,10 +12,11 @@ set( PCBNEW_CONN_SRCS
connectivity_data.cpp
connectivity_items.cpp
from_to_cache.cpp
topo_match.cpp
)
add_library( connectivity STATIC ${PCBNEW_CONN_SRCS} )
target_link_libraries( connectivity PRIVATE
common
)
)

455
pcbnew/connectivity/topo_match.cpp

@ -0,0 +1,455 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) Kicad Developers, see change_log.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-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 <cstdio>
#include <cstdlib>
#include <cmath>
#include <string>
#include <vector>
#include <algorithm>
#include <cassert>
#include <map>
#include <set>
#include <cctype>
#include <footprint.h>
#include <wx/string.h>
#include "topo_match.h"
namespace TMATCH
{
bool PIN::IsIsomorphic( const PIN& b ) const
{
if( m_conns.size() != b.m_conns.size() )
{
printf("[conns mismatch n1 %d n2 %d c-ref %d c-other %d thispin %s-%s otherpin %s-%s", m_netcode, b.m_netcode, (int) m_conns.size(), (int) b.m_conns.size(),
m_parent->m_reference.c_str().AsChar(), m_ref.c_str().AsChar(),
b.m_parent->m_reference.c_str().AsChar(), b.m_ref.c_str().AsChar() );
for( auto c : m_conns )
printf("%s-%s ", c->m_parent->m_reference.c_str().AsChar(), c->m_ref.c_str().AsChar() );
printf("||");
for( auto c : b.m_conns )
printf("%s-%s ", c->m_parent->m_reference.c_str().AsChar(), c->m_ref.c_str().AsChar() );
printf("] ");
return false;
}
if( m_conns.empty() )
{
printf("[conns empty]");
return true;
}
bool matches[m_conns.size()];
for( int i = 0; i < m_conns.size(); i++ )
matches[i] = false;
//printf("REF: %s\n", format().c_str() );
//printf("B : %s\n", b.format().c_str() );
int nref = 0;
for( auto& cref : m_conns )
{
//printf("[CREF: %s]", cref->Format().c_str().AsChar() );
for( int i = 0; i < m_conns.size(); i++ )
{
if( b.m_conns[i]->IsTopologicallySimilar( *cref ) )
{
//printf("[CMATCH: %s]", b.m_conns[i]->Format().c_str().AsChar() );
matches[nref] = true;
break;
}
}
nref++;
}
bool r = true;
for( int i = 0; i < m_conns.size(); i++ )
if( !matches[i] )
r = false;
//printf("pin %s vs %s iso=%d\n", format().c_str(), b.format().c_str(), r ? 1: 0 );
return r;
}
std::vector<COMPONENT*>
CONNECTION_GRAPH::findMatchingComponents( COMPONENT* aRef, const BACKTRACK_STAGE& partialMatches )
{
std::vector<COMPONENT*> matches;
for( auto cmpTarget : m_components )
{
printf("Check '%s'/'%s' ", aRef->m_reference.c_str().AsChar(), cmpTarget->m_reference.c_str().AsChar() );
if( partialMatches.m_locked.find( cmpTarget ) != partialMatches.m_locked.end() )
{
printf("discard\n");
continue;
}
if( aRef->MatchesWith( cmpTarget ) )
{
printf("match!");
//printf("possible match: %s/%s [fp %s/%s]\n", cmpTarget->reference.c_str(), ref->reference.c_str(), cmpTarget->footprintName.c_str(), ref->footprintName.c_str() );
matches.push_back( cmpTarget );
}
printf("\n");
}
return matches;
}
void COMPONENT::sortPinsByName()
{
std::sort( m_pins.begin(), m_pins.end(),
[]( PIN* a, PIN* b )
{
return a->GetReference() < b->GetReference();
} );
}
void CONNECTION_GRAPH::BuildConnectivity()
{
std::map<int, std::vector<PIN*>> nets;
sortByPinCount();
for( auto c : m_components )
{
c->sortPinsByName();
for( auto p : c->Pins() )
{
printf("NC %d pin %s\n", p->GetNetCode(), p->m_ref.c_str().AsChar() );
if( p->GetNetCode() > 0 )
nets[p->GetNetCode()].push_back( p );
}
}
for( auto iter : nets )
{
printf("net %d: %d connections\n", iter.first, iter.second.size() );
for( auto p : iter.second )
{
for( auto p2 : iter.second )
if( p != p2 && !alg::contains( p->m_conns, p2 ) )
{
p->m_conns.push_back( p2 );
}
}
}
for( auto c : m_components )
for( auto p : c->Pins() )
{
printf("pin %s: \n", p->m_ref.c_str().AsChar() );
for( auto c : p->m_conns )
printf( "%s ", c->m_ref.c_str().AsChar() );
printf("\n");
}
}
CONNECTION_GRAPH::STATUS CONNECTION_GRAPH::FindIsomorphism( CONNECTION_GRAPH* aTarget,
COMPONENT_MATCHES& aResult )
{
std::vector<BACKTRACK_STAGE> stack;
BACKTRACK_STAGE top;
//printf("Ref: %d, tgt: %d\n", m_components.size(), aTarget->m_components.size() );
if( m_components.empty()|| aTarget->m_components.empty() )
return ST_EMPTY;
if( m_components.size() != aTarget->m_components.size() )
return ST_COMPONENT_COUNT_MISMATCH;
top.m_ref = m_components.front();
top.m_matches = aTarget->findMatchingComponents( top.m_ref, top );
stack.push_back( top );
int refIndex = 1;
bool matchFound = false;
int nloops = 0;
while( !stack.empty() )
{
nloops++;
auto& current = stack.back();
if( nloops >= c_ITER_LIMIT )
{
return ST_ITERATION_COUNT_EXCEEDED;
}
if( current.m_currentMatch >= current.m_matches.size() )
{
stack.pop_back();
continue;
}
printf("Current '%s' stack %d cm %d/%d locked %d/%d candidate '%s'\n", current.m_ref->m_reference.c_str().AsChar(), (int) stack.size(), current.m_currentMatch, (int) current.m_matches.size(),(int) current.m_locked.size(), (int)m_components.size(),
current.m_matches[current.m_currentMatch]->m_reference.c_str().AsChar() );
auto& match = current.m_matches[current.m_currentMatch];
current.m_currentMatch++;
current.m_locked[match] = current.m_ref;
if( current.m_locked.size() == m_components.size() )
{
//printf("NLoops: %d\n", nloops);
current.m_nloops = nloops;
aResult.clear();
for( auto iter : current.m_locked )
aResult[ iter.second->GetParent() ] = iter.first->GetParent();
return ST_OK;
}
printf("RI %d cs %d\n", refIndex, (int) m_components.size() );
if( refIndex >= m_components.size() )
break;
//printf("ref '%s', locked %d, stack %d\n", current.locked.size(), stack.size() );
BACKTRACK_STAGE next( current );
next.m_currentMatch = 0;
next.m_ref = m_components[refIndex++];
next.m_matches = aTarget->findMatchingComponents( next.m_ref, next );
printf("Nxt '%s' matches %d\n", next.m_ref->m_reference.c_str().AsChar(), next.m_matches.size() );
printf("m: ");
for( auto l : next.m_matches )
{
printf("%s ", l->m_reference.c_str().AsChar() );
}
printf("\n");
// printf(" - matches: %d\n", (int) next.matches.size() );
if( next.m_matches.empty() )
continue;
/* printf("LOCKED: ");
for( auto l : next.locked )
{
printf("%s ", l.first->reference.c_str() );
}
printf("\n");
printf("PUSH L %d\n", (int) next.locked.size() );*/
stack.push_back( next );
//printf("NL %d/%d\n", (int) next.locked.size(), (int) cgRef->components.size() );
};
return ST_TOPOLOGY_MISMATCH;
}
#if 0
int main()
{
FILE * f = fopen("connectivity.dump","rb" );
auto cgRef = loadCGraph(f);
auto cgTarget = loadCGraph(f);
cgRef->buildConnectivity();
cgTarget->buildConnectivity();
int attempts = 0;
int max_loops = 0;
for( ;; )
{
cgRef->shuffle();
cgTarget->shuffle();
const BacktrackStage latest = cgRef->matchCGraphs( cgTarget );
if( !latest.locked.size() )
{
printf("MATCH FAIL\n");
break;
}
//printf("loops: %d\n", latest.nloops );
//printf("Locked: %d\n", latest.locked.size() );
//if (matchFound)
//{
// for( auto& iter : latest.locked )
//{
// printf("%-10s : %-10s\n", iter.first->reference.c_str(), iter.second->reference.c_str() );
//}
//}
if( latest.nloops > max_loops )
{
max_loops = latest.nloops;
}
if (attempts % 10000 == 0)
{
printf("attempts: %d maxloops: %d\n", attempts, max_loops );
}
attempts++;
}
fclose(f);
return 0;
}
#endif
COMPONENT::COMPONENT( const wxString& aRef, FOOTPRINT* aParentFp ) :
m_reference( aRef ), m_parentFootprint( aParentFp )
{
int i;
for( i = 0; i < aRef.length(); i++ )
{
if( std::iswalpha( aRef[i].GetValue() ) )
break;
}
m_prefix = aRef.substr( 0, i );
}
bool COMPONENT::IsSameKind( const COMPONENT& b ) const
{
return m_prefix == b.m_prefix && m_parentFootprint->GetFPID() == b.m_parentFootprint->GetFPID();
}
void COMPONENT::AddPin( PIN* aPin )
{
m_pins.push_back( aPin );
aPin->SetParent( this );
}
bool COMPONENT::MatchesWith( COMPONENT* b )
{
if( GetPinCount() != b->GetPinCount() )
{
printf("[cp mismatch]");
return false;
}
if( m_parentFootprint->GetFPID() != b->m_parentFootprint->GetFPID() )
{
printf("[fpid mismatch]");
return false;
}
if( m_prefix != b->m_prefix )
{
printf("[pre mismatch]");
return false;
}
bool fail = false;
for( int pin = 0; pin < b->GetPinCount(); pin++ )
{
if( !b->m_pins[pin]->IsIsomorphic( *m_pins[pin] ) )
{
printf("[iso fail p%d]", pin );
fail = true;
break;
}
}
return !fail;
}
void CONNECTION_GRAPH::AddFootprint( FOOTPRINT* aFp )
{
auto cmp = new COMPONENT( aFp->GetReference(), aFp );;
for( auto pad : aFp->Pads() )
{
//printf("pad %p\n", pad );
auto pin = new PIN( );
pin->m_netcode = pad->GetNetCode();
pin->m_ref = pad->GetNumber();
cmp->AddPin( pin );
}
m_components.push_back( cmp );
}
std::unique_ptr<CONNECTION_GRAPH> CONNECTION_GRAPH::BuildFromFootprintSet( const std::set<FOOTPRINT*>& aFps )
{
auto cgraph = std::make_unique<CONNECTION_GRAPH>();
for( auto fp : aFps )
{
cgraph->AddFootprint( fp );
}
cgraph->BuildConnectivity();
return std::move(cgraph);
}
CONNECTION_GRAPH::CONNECTION_GRAPH() {}
CONNECTION_GRAPH::~CONNECTION_GRAPH()
{
for( COMPONENT* fp : m_components )
{
delete fp;
}
}
COMPONENT::~COMPONENT()
{
for( PIN* p : m_pins )
{
delete p;
}
}
}; // namespace TMATCH

178
pcbnew/connectivity/topo_match.h

@ -0,0 +1,178 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) Kicad Developers, see change_log.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-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
*/
#ifndef __TOPO_MATCH_H
#define __TOPO_MATCH_H
#include <vector>
#include <map>
#include <wx/string.h>
class FOOTPRINT;
namespace TMATCH
{
class PIN;
class CONNECTION_GRAPH;
class COMPONENT
{
friend class PIN;
friend class CONNECTION_GRAPH;
public:
COMPONENT( const wxString& aRef, FOOTPRINT* aParentFp );
~COMPONENT();
bool IsSameKind( const COMPONENT& b ) const;
void AddPin( PIN* p );
int GetPinCount() const { return m_pins.size(); }
bool MatchesWith( COMPONENT* b );
std::vector<PIN*>& Pins() { return m_pins; }
FOOTPRINT* GetParent() const { return m_parentFootprint; }
private:
void sortPinsByName();
wxString m_reference;
wxString m_prefix;
FOOTPRINT* m_parentFootprint;
std::vector<PIN*> m_pins;
};
class PIN
{
friend class CONNECTION_GRAPH;
public:
PIN() : m_parent( nullptr ) {}
~PIN() {}
void SetParent( COMPONENT* parent ) { m_parent = parent; }
const wxString Format() const { return m_parent->m_reference + wxT( "-" ) + m_ref; }
void AddConnection( PIN* pin ) { m_conns.push_back( pin ); }
bool IsTopologicallySimilar( const PIN& b ) const
{
wxASSERT( m_parent != b.m_parent );
if( !m_parent->IsSameKind( *b.m_parent ) )
return false;
//printf("Cmpt '%s'/'%s' %p %p similar=%d\n", ref.c_str(), b.ref.c_str(), parent, b.parent, ref==b.ref ? 1 :0 );
return m_ref == b.m_ref;
}
bool IsIsomorphic( const PIN& b ) const;
int GetNetCode() const { return m_netcode; }
const wxString& GetReference() const { return m_ref; }
private:
wxString m_ref;
int m_netcode;
COMPONENT* m_parent;
std::vector<PIN*> m_conns;
};
class BACKTRACK_STAGE
{
friend class CONNECTION_GRAPH;
public:
BACKTRACK_STAGE()
{
m_ref = nullptr;
m_currentMatch = 0;
}
BACKTRACK_STAGE( const BACKTRACK_STAGE& other )
{
m_currentMatch = other.m_currentMatch;
m_ref = other.m_ref;
m_matches = other.m_matches;
m_locked = other.m_locked;
m_nloops = other.m_nloops;
}
const std::map<COMPONENT*, COMPONENT*>& GetMatchingComponentPairs() const { return m_locked; }
private:
COMPONENT* m_ref;
int m_currentMatch = 0;
int m_nloops;
std::vector<COMPONENT*> m_matches;
std::map<COMPONENT*, COMPONENT*> m_locked;
};
typedef std::map<FOOTPRINT*, FOOTPRINT*> COMPONENT_MATCHES;
class CONNECTION_GRAPH
{
public:
const int c_ITER_LIMIT = 10000;
enum STATUS
{
ST_TOPOLOGY_MISMATCH = -10,
ST_ITERATION_COUNT_EXCEEDED,
ST_COMPONENT_COUNT_MISMATCH,
ST_EMPTY,
ST_OK = 0
};
CONNECTION_GRAPH();
~CONNECTION_GRAPH();
void BuildConnectivity();
void AddFootprint( FOOTPRINT* aFp );
STATUS FindIsomorphism( CONNECTION_GRAPH* target, COMPONENT_MATCHES& result );
static std::unique_ptr<CONNECTION_GRAPH> BuildFromFootprintSet( const std::set<FOOTPRINT*>& aFps );
private:
void sortByPinCount()
{
std::sort( m_components.begin(), m_components.end(),
[]( COMPONENT* a, COMPONENT* b )
{
return a->GetPinCount() > b->GetPinCount();
} );
}
std::vector<COMPONENT*> findMatchingComponents( COMPONENT* ref,
const BACKTRACK_STAGE& partialMatches );
std::vector<COMPONENT*> m_components;
};
}; // namespace TMATCH
#endif

511
pcbnew/tools/multichannel_tool.cpp

@ -36,22 +36,19 @@
#include <geometry/convex_hull.h>
#include <pcb_group.h>
#include <connectivity/connectivity_data.h>
#include <connectivity/topo_match.h>
#include <optional>
#include <algorithm>
#include <random>
#include <core/profile.h>
#include <wx/log.h>
#include <pgm_base.h>
#undef MULTICHANNEL_EXTRA_DEBUG
#ifdef MULTICHANNEL_EXTRA_DEBUG
#define DBG( level, fmt, ...) \
fprintf( stderr, "%s", wxString::Format( fmt, __VA_ARGS__ ).c_str().AsChar() )
#define DBGn( level, fmt ) \
fprintf( stderr, "%s", wxString(fmt).c_str().AsChar() )
#else
#define DBG( level, fmt, ...)
#define DBGn( level, fmt )
#endif
#define MULTICHANNEL_EXTRA_DEBUG
static const wxString traceMultichannelTool = wxT( "MULTICHANNEL_TOOL" );
MULTICHANNEL_TOOL::MULTICHANNEL_TOOL() : PCB_TOOL_BASE( "pcbnew.Multichannel" )
{
@ -67,7 +64,7 @@ MULTICHANNEL_TOOL::~MULTICHANNEL_TOOL()
void MULTICHANNEL_TOOL::setTransitions()
{
Go( &MULTICHANNEL_TOOL::autogenerateRuleAreas, PCB_ACTIONS::generatePlacementRuleAreas.MakeEvent() );
Go( &MULTICHANNEL_TOOL::AutogenerateRuleAreas, PCB_ACTIONS::generatePlacementRuleAreas.MakeEvent() );
Go( &MULTICHANNEL_TOOL::repeatLayout, PCB_ACTIONS::repeatLayout.MakeEvent() );
}
@ -181,7 +178,7 @@ const SHAPE_LINE_CHAIN MULTICHANNEL_TOOL::buildRAOutline( std::set<FOOTPRINT*>&
}
void MULTICHANNEL_TOOL::querySheets()
void MULTICHANNEL_TOOL::QuerySheets()
{
using PathAndName = std::pair<wxString, wxString>;
std::set<PathAndName> uniqueSheets;
@ -196,16 +193,20 @@ void MULTICHANNEL_TOOL::querySheets()
for( const PathAndName& sheet : uniqueSheets )
{
RULE_AREA ent;
ent.m_generateEnabled = false;
ent.m_sheetPath = sheet.first;
ent.m_sheetName = sheet.second;
ent.m_sheetComponents = queryComponentsInSheet( ent.m_sheetPath );
m_areas.m_areas.push_back( ent );
wxLogTrace( traceMultichannelTool, wxT("found sheet '%s' @ '%s' s %d\n"),
ent.m_sheetName, ent.m_sheetPath, (int)m_areas.m_areas.size() );
}
}
void MULTICHANNEL_TOOL::findExistingRuleAreas()
void MULTICHANNEL_TOOL::FindExistingRuleAreas()
{
m_areas.m_areas.clear();
@ -216,31 +217,21 @@ void MULTICHANNEL_TOOL::findExistingRuleAreas()
if( zone->GetRuleAreaType() != RULE_AREA_TYPE::PLACEMENT )
continue;
std::set<FOOTPRINT*> components;
identifyComponentsInRuleArea( zone, components );
RULE_AREA area;
area.m_existsAlready = true;
area.m_area = zone;
for( FOOTPRINT* fp : components )
{
FP_WITH_CONNECTIONS fpc;
fpc.fp = fp;
area.m_raFootprints.push_back( fpc );
identifyComponentsInRuleArea( zone, area.m_raFootprints );
for( PAD* pad : fp->Pads() )
{
area.m_fpPads[pad] = fp;
}
}
area.m_ruleName = zone->GetZoneName();
area.m_center = zone->Outline()->COutline( 0 ).Centre();
m_areas.m_areas.push_back( area );
wxLogTrace( traceMultichannelTool, wxT("RA '%s', %d footprints\n"), area.m_ruleName, (int) area.m_raFootprints.size() );
}
DBG( 1, "Total RAs found: %d\n", (int) m_areas.m_areas.size() );
wxLogTrace( traceMultichannelTool, wxT("Total RAs found: %d\n"), (int) m_areas.m_areas.size() );
}
@ -258,9 +249,6 @@ RULE_AREA* MULTICHANNEL_TOOL::findRAByName( const wxString& aName )
int MULTICHANNEL_TOOL::repeatLayout( const TOOL_EVENT& aEvent )
{
//KI_TEST::CONSOLE_LOG consoleLog;
//m_reporter.reset( new KI_TEST::CONSOLE_MSG_REPORTER ( &consoleLog ) );
std::vector<ZONE*> refRAs;
for( EDA_ITEM* item : selection() )
@ -280,17 +268,34 @@ int MULTICHANNEL_TOOL::repeatLayout( const TOOL_EVENT& aEvent )
if( refRAs.size() != 1 )
{
frame()->ShowInfoBarError( _( "Please select a single reference Rule Area to copy from" ),
true );
true );
return 0;
}
findExistingRuleAreas();
FindExistingRuleAreas();
int status = CheckRACompatibility( refRAs.front() );
if( status < 0 )
return status;
DIALOG_MULTICHANNEL_REPEAT_LAYOUT dialog( frame(), this );
int ret = dialog.ShowModal();
if( ret != wxID_OK )
return 0;
return RepeatLayout( aEvent, refRAs.front() );
}
int MULTICHANNEL_TOOL::CheckRACompatibility( ZONE *aRefZone )
{
m_areas.m_refRA = nullptr;
for( RULE_AREA& ra : m_areas.m_areas )
{
if( ra.m_area == refRAs.front() )
if( ra.m_area == aRefZone )
{
m_areas.m_refRA = &ra;
break;
@ -312,14 +317,16 @@ int MULTICHANNEL_TOOL::repeatLayout( const TOOL_EVENT& aEvent )
resolveConnectionTopology( m_areas.m_refRA, &ra, m_areas.m_compatMap[&ra] );
}
DIALOG_MULTICHANNEL_REPEAT_LAYOUT dialog( frame(), this );
int ret = dialog.ShowModal();
return 0;
}
if( ret != wxID_OK )
return 0;
int MULTICHANNEL_TOOL::RepeatLayout( const TOOL_EVENT& aEvent, ZONE* aRefZone )
{
//KI_TEST::CONSOLE_LOG consoleLog;
//m_reporter.reset( new KI_TEST::CONSOLE_MSG_REPORTER ( &consoleLog ) );
BOARD_COMMIT commit( frame()->GetToolManager(),
true ); //<PNS_LOG_VIEWER_FRAME>()->GetToolManager(), true );
BOARD_COMMIT commit( GetManager(), true );
int totalCopied = 0;
@ -327,7 +334,7 @@ int MULTICHANNEL_TOOL::repeatLayout( const TOOL_EVENT& aEvent )
{
if( !targetArea.second.m_doCopy )
{
DBG( 1, "skipping copy to RA '%s' (disabled in dialog)\n",
wxLogTrace( traceMultichannelTool, wxT("skipping copy to RA '%s' (disabled in dialog)\n"),
targetArea.first->m_ruleName );
continue;
}
@ -335,7 +342,7 @@ int MULTICHANNEL_TOOL::repeatLayout( const TOOL_EVENT& aEvent )
if( !targetArea.second.m_isOk )
continue;
if( !copyRuleAreaContents( targetArea.second.m_matchingFootprints, &commit, m_areas.m_refRA,
if( !copyRuleAreaContents( targetArea.second.m_matchingComponents, &commit, m_areas.m_refRA,
targetArea.first, m_areas.m_options ) )
{
auto errMsg = wxString::Format(
@ -344,9 +351,12 @@ int MULTICHANNEL_TOOL::repeatLayout( const TOOL_EVENT& aEvent )
targetArea.first->m_area->GetZoneName() );
commit.Revert();
frame()->ShowInfoBarError( errMsg, true );
if( Pgm().IsGUI() )
{
frame()->ShowInfoBarError( errMsg, true );
}
return 0;
return -1;
}
totalCopied++;
@ -354,8 +364,12 @@ int MULTICHANNEL_TOOL::repeatLayout( const TOOL_EVENT& aEvent )
commit.Push( _( "Repeat layout" ) );
frame()->ShowInfoBarMsg( wxString::Format( _( "Copied to %d Rule Areas." ), totalCopied ),
if( Pgm().IsGUI() )
{
frame()->ShowInfoBarMsg( wxString::Format( _( "Copied to %d Rule Areas." ), totalCopied ),
true );
}
return 0;
}
@ -422,7 +436,7 @@ int MULTICHANNEL_TOOL::findRoutedConnections( std::set<BOARD_ITEM*>&
}
bool MULTICHANNEL_TOOL::copyRuleAreaContents( FP_PAIRS& aMatches, BOARD_COMMIT* aCommit,
bool MULTICHANNEL_TOOL::copyRuleAreaContents( TMATCH::COMPONENT_MATCHES& aMatches, BOARD_COMMIT* aCommit,
RULE_AREA* aRefArea, RULE_AREA* aTargetArea,
REPEAT_LAYOUT_OPTIONS aOpts )
{
@ -452,7 +466,7 @@ bool MULTICHANNEL_TOOL::copyRuleAreaContents( FP_PAIRS& aMatches, BOARD_COMMIT*
std::set<BOARD_ITEM*> refRouting;
std::set<BOARD_ITEM*> targetRouting;
DBG( 1, "copying routing: %d fps\n", aMatches.size() );
wxLogTrace( traceMultichannelTool, wxT("copying routing: %d fps\n"), (int) aMatches.size() );
for( auto& fpPair : aMatches )
{
@ -461,20 +475,35 @@ bool MULTICHANNEL_TOOL::copyRuleAreaContents( FP_PAIRS& aMatches, BOARD_COMMIT*
findRoutedConnections( refRouting, connectivity, refPoly, aRefArea, fpPair.first,
aOpts );
DBG( 2, "target-routes %d\n", (int) targetRouting.size() );
wxLogTrace( traceMultichannelTool, wxT("target-routes %d\n"), (int) targetRouting.size() );
}
printf("ref-ls: %s\n", aRefArea->m_area->GetLayerSet().FmtHex().c_str() );
printf("target-ls: %s\n", aTargetArea->m_area->GetLayerSet().FmtHex().c_str() );
for( BOARD_ITEM* item : targetRouting )
{
if( item->IsLocked() && !aOpts.m_includeLockedItems )
continue;
aCommit->Remove( item );
// item already removed
if( aCommit->GetStatus( item ) != 0 )
continue;
if( aTargetArea->m_area->GetLayerSet().Contains( item->GetLayer() ) )
aCommit->Remove( item );
}
for( BOARD_ITEM* item : refRouting )
{
if( !aRefArea->m_area->GetLayerSet().Contains( item->GetLayer() ) )
continue;
if( !aTargetArea->m_area->GetLayerSet().Contains( item->GetLayer() ) )
continue;
BOARD_ITEM* copied = static_cast<BOARD_ITEM*>( item->Clone() );
copied->Move( disp );
aCommit->Add( copied );
}
@ -493,19 +522,21 @@ bool MULTICHANNEL_TOOL::copyRuleAreaContents( FP_PAIRS& aMatches, BOARD_COMMIT*
FOOTPRINT* refFP = fpPair.first;
FOOTPRINT* targetFP = fpPair.second;
#if 0
printf("ref-fp-l: %s\n", refFP->GetLayerSet().FmtHex().c_str() );
#if 1
//fixme: respect layers assigned to RAs
//printf("ref-ls: %s\n", aRefArea->m_area->GetLayerSet().FmtHex().c_str() );
//printf("target-ls: %s\n", aRefArea->m_area->GetLayerSet().FmtHex().c_str() );
printf("ref-ls: %s\n", aRefArea->m_area->GetLayerSet().FmtHex().c_str() );
printf("target-ls: %s\n", aRefArea->m_area->GetLayerSet().FmtHex().c_str() );
if( ! aRefArea->m_area->GetLayerSet().Contains( refFP->GetLayer() ) );
if( ! aRefArea->m_area->GetLayerSet().Contains( refFP->GetLayer() ) )
{
DBG(2, wxT("discard ref:%s (ref layer)\n"), refFP->GetReference() );
wxLogTrace( traceMultichannelTool, wxT("discard ref:%s (ref layer)\n"), refFP->GetReference() );
continue;
}
if( ! aTargetArea->m_area->GetLayerSet().Contains( refFP->GetLayer() ) );
if( ! aTargetArea->m_area->GetLayerSet().Contains( refFP->GetLayer() ) )
{
DBG(2, wxT("discard ref:%s (target layer)\n"), refFP->GetReference() );
wxLogTrace( traceMultichannelTool, wxT("discard ref:%s (target layer)\n"), refFP->GetReference() );
continue;
}
#endif
@ -530,350 +561,70 @@ bool MULTICHANNEL_TOOL::copyRuleAreaContents( FP_PAIRS& aMatches, BOARD_COMMIT*
}
bool MULTICHANNEL_TOOL::checkIfPadNetsMatch( FP_WITH_CONNECTIONS& aRef, FP_WITH_CONNECTIONS& aTgt,
FP_PAIRS& aMatches ) const
{
#ifdef MULTICHANNEL_EXTRA_DEBUG
DBG( 2, "ref: %d pads\n", (int) aRef.connsWithinRA.size() );
DBG( 2, "matches so far: %d\n", (int) aMatches.size() );
#endif
std::map<PAD*, PAD*> pairs;
std::vector<PAD*> pref, ptgt;
for( auto& m : aMatches )
{
for( PAD* p : m.first->Pads() )
pref.push_back( p );
for( PAD* p : m.second->Pads() )
ptgt.push_back( p );
}
for( PAD* p : aRef.fp->Pads() )
pref.push_back( p );
for( PAD* p : aTgt.fp->Pads() )
ptgt.push_back( p );
if( pref.size() != ptgt.size() )
return false;
for( unsigned int i = 0; i < pref.size(); i++ )
pairs[pref[i]] = ptgt[i];
for( auto& ref : aRef.connsWithinRA )
{
DBG( 2, wxT( "pad %s: %s -> " ), ref.first->GetNumber(), ref.first->GetNetname() );
std::optional<int> prevNet;
for( auto& pc : ref.second )
{
auto tpad = pairs.find( pc.pad );
if( tpad != pairs.end() )
{
int nc = tpad->second->GetNetCode();
DBG( 3, wxT( " %s[%d]" ), tpad->second->GetNetname(), tpad->second->GetNetCode() );
if( prevNet && ( *prevNet != nc ) )
{
return false;
}
prevNet = nc;
}
else
{
DBGn( 3, wxT( " ?" ) );
}
}
DBGn( 3, wxT( "\n" ) );
}
return true;
}
bool MULTICHANNEL_TOOL::resolveConnectionTopology( RULE_AREA* aRefArea, RULE_AREA* aTargetArea, RULE_AREA_COMPAT_DATA& aMatches )
{
std::map<NETINFO_ITEM*, std::vector<PAD*>> allPads;
for( FOOTPRINT* fp : board()->Footprints() )
{
for( PAD* pad : fp->Pads() )
{
auto iter = allPads.find( pad->GetNet() );
if( iter == allPads.end() )
allPads[pad->GetNet()] = { pad };
else
allPads[pad->GetNet()].push_back( pad );
}
}
auto belongsToRAFootprint = []( RULE_AREA* ra, PAD* aPad ) -> FOOTPRINT*
{
auto iter = ra->m_fpPads.find( aPad );
if( iter == ra->m_fpPads.end() )
return nullptr;
return iter->second;
};
auto findPadConnectionsWithinRA = [&]( RULE_AREA* ra,
PAD* aPad ) -> std::vector<PAD_PREFIX_ENTRY>
{
std::vector<PAD_PREFIX_ENTRY> rv;
using namespace TMATCH;
for( auto cpad : allPads[aPad->GetNet()] )
{
if( belongsToRAFootprint( ra, cpad ) )
{
rv.push_back( PAD_PREFIX_ENTRY(
cpad, stripComponentIndex( cpad->GetParentFootprint()->GetReference() ) ) );
}
}
std::unique_ptr<CONNECTION_GRAPH> cgRef ( CONNECTION_GRAPH::BuildFromFootprintSet( aRefArea->m_raFootprints ) );
std::unique_ptr<CONNECTION_GRAPH> cgTarget ( CONNECTION_GRAPH::BuildFromFootprintSet( aTargetArea->m_raFootprints ) );
return rv;
};
auto status = cgRef->FindIsomorphism( cgTarget.get(), aMatches.m_matchingComponents );
auto buildPadConnectionsWithinRA = [&]( RULE_AREA* ra ) -> bool
switch( status )
{
for( auto& fp : ra->m_raFootprints )
{
for( auto pad : fp.fp->Pads() )
{
fp.connsWithinRA[pad] = findPadConnectionsWithinRA( ra, pad );
DBG( 3, wxT( "p %s-%s ->" ), pad->GetParentAsString(), pad->GetNumber() );
for( auto p : fp.connsWithinRA[pad] )
{
DBG( 3, wxT( " %s-%s" ), p.pad->GetParentAsString(), p.pad->GetNumber() );
}
DBGn( 3, wxT( "\n" ) );
}
}
return true;
};
auto matchConnections = [&]( FP_WITH_CONNECTIONS* aRef, FP_WITH_CONNECTIONS* aTarget ) -> bool
{
for( auto ref : aRef->connsWithinRA )
{
DBG( 2, wxT( "ref [%s]: " ), ref.first->GetNumber() );
for( auto conn : ref.second )
DBG( 2, wxT( "%s[%s] " ), conn.format(), conn.pad->GetNetname() );
DBGn( 2, wxT( "\n" ) );
}
for( auto tgt : aTarget->connsWithinRA )
{
DBG( 2, wxT( "tgt [%s]: " ), tgt.first->GetNumber() );
for( auto conn : tgt.second )
DBG( 2, wxT( "%s[%s]" ), conn.format(), conn.pad->GetNetname() );
DBGn( 2, wxT( "\n" ) );
}
bool matchFound = false;
for( auto ref : aRef->connsWithinRA )
{
for( auto tgt : aTarget->connsWithinRA )
{
bool padsMatch = true;
if( ref.second.size() != tgt.second.size() )
{
padsMatch = false;
continue;
}
for( unsigned int i = 0; i < ref.second.size(); i++ )
{
PAD_PREFIX_ENTRY& eref = ref.second[i];
PAD_PREFIX_ENTRY& etgt = tgt.second[i];
if( !eref.matchesPadNumberAndPrefix( etgt ) )
{
padsMatch = false;
break;
}
}
if( padsMatch )
{
matchFound = true;
}
}
if( !matchFound )
{
return false;
}
}
return matchFound;
};
if( aRefArea->m_raFootprints.size() != aTargetArea->m_raFootprints.size() )
{
aMatches.m_isOk = false;
aMatches.m_errorMsg = wxString::Format(
wxT( "Component count mismatch (reference area has %d, target area has %d)" ),
(int) aRefArea->m_raFootprints.size(), (int) aTargetArea->m_raFootprints.size() );
m_reporter->Report( aMatches.m_errorMsg );
return false;
}
buildPadConnectionsWithinRA( aRefArea );
buildPadConnectionsWithinRA( aTargetArea );
for( auto& refFP : aRefArea->m_raFootprints )
{
refFP.sortByPadNumbers();
refFP.processed = false;
}
for( auto& targetFP : aTargetArea->m_raFootprints )
{
targetFP.sortByPadNumbers();
targetFP.processed = false;
}
//int targetsRemaining = aTargetArea->m_raFootprints.size();
std::sort( aRefArea->m_raFootprints.begin(), aRefArea->m_raFootprints.end(),
[]( const FP_WITH_CONNECTIONS& a, const FP_WITH_CONNECTIONS& b ) -> int
{
return a.fp->GetPadCount() > b.fp->GetPadCount();
} );
const int MATCH_MAX_ATTEMPTS = 100;
FOOTPRINT* failingRefFP = nullptr;
for( int attempt = 0; attempt < MATCH_MAX_ATTEMPTS; attempt++ )
{
aMatches.m_matchingFootprints.clear();
for( auto& targetFP : aTargetArea->m_raFootprints )
{
targetFP.processed = false;
}
for( auto& refFP : aRefArea->m_raFootprints )
{
std::vector<FP_WITH_CONNECTIONS*> candidates;
bool anyMatchesFound = false;
for( auto& targetFP : aTargetArea->m_raFootprints )
{
if( stripComponentIndex( refFP.fp->GetReference() )
!= stripComponentIndex( targetFP.fp->GetReference() ) )
continue;
if( refFP.fp->GetValue() != targetFP.fp->GetValue() )
continue;
if( refFP.fp->GetFPID() != targetFP.fp->GetFPID() )
continue;
if( !targetFP.processed )
{
bool matches = matchConnections( &refFP, &targetFP );
DBG( 2, wxT( "testFP %s vs %s\n" ), refFP.fp->GetReference().c_str().AsChar(),
targetFP.fp->GetReference().c_str().AsChar() );
if( matches )
{
DBG( 2, wxT( "%s: matches %s\n" ),
refFP.fp->GetReference().c_str().AsChar(),
targetFP.fp->GetReference().c_str().AsChar() );
candidates.push_back( &targetFP );
}
}
}
DBG( 2, wxT( "Candidates for %s: %d\n" ), refFP.fp->GetReference(),
(int) candidates.size() );
FP_WITH_CONNECTIONS* best = nullptr;
for( auto& c : candidates )
{
bool chk = checkIfPadNetsMatch( refFP, *c, aMatches.m_matchingFootprints );
DBG( 2, wxT( "\n %s om %d\n" ), c->fp->GetReference(), chk ? 1 : 0 );
if( chk )
{
anyMatchesFound = true;
best = c;
}
}
if( !anyMatchesFound )
{
failingRefFP = refFP.fp;
break;
}
else
{
best->processed = true;
aMatches.m_matchingFootprints.push_back( { refFP.fp, best->fp } );
}
}
if( aMatches.m_matchingFootprints.size() == aTargetArea->m_raFootprints.size() )
{
case CONNECTION_GRAPH::ST_OK:
aMatches.m_isOk = true;
return true;
}
auto rng = std::default_random_engine{};
std::shuffle( aTargetArea->m_raFootprints.begin(), aTargetArea->m_raFootprints.end(), rng );
aMatches.m_errorMsg = _("OK");
break;
case CONNECTION_GRAPH::ST_EMPTY:
aMatches.m_isOk = false;
aMatches.m_errorMsg = _("One or both of the areas has no components assigned.");
break;
case CONNECTION_GRAPH::ST_COMPONENT_COUNT_MISMATCH:
aMatches.m_isOk = false;
aMatches.m_errorMsg = _("Component count mismatch");
break;
case CONNECTION_GRAPH::ST_ITERATION_COUNT_EXCEEDED:
aMatches.m_isOk = false;
aMatches.m_errorMsg = _("Iteration count exceeded (timeout)");
break;
case CONNECTION_GRAPH::ST_TOPOLOGY_MISMATCH:
aMatches.m_isOk = false;
aMatches.m_errorMsg = _("Topology mismatch");
break;
default:
break;
}
aMatches.m_errorMsg =
wxString::Format( wxT( "Topology mismatch (no counterpart found for component %s)" ),
failingRefFP->GetReference() );
aMatches.m_isOk = false;
DBG( 2, wxT( "%s: no match\n" ), failingRefFP->GetReference() );
return false;
return (status == TMATCH::CONNECTION_GRAPH::ST_OK );
}
int MULTICHANNEL_TOOL::autogenerateRuleAreas( const TOOL_EVENT& aEvent )
int MULTICHANNEL_TOOL::AutogenerateRuleAreas( const TOOL_EVENT& aEvent )
{
//KI_TEST::CONSOLE_LOG consoleLog;
//m_reporter.reset( new KI_TEST::CONSOLE_MSG_REPORTER ( &consoleLog ) );
querySheets();
if( m_areas.m_areas.size() <= 1 )
if( Pgm().IsGUI() )
{
frame()->ShowInfoBarError( _( "Cannot auto-generate any placement areas because the "
"schematic has only one or no hierarchical sheet(s)." ),
true );
return 0;
}
QuerySheets();
if( m_areas.m_areas.size() <= 1 )
{
frame()->ShowInfoBarError( _( "Cannot auto-generate any placement areas because the "
"schematic has only one or no hierarchical sheet(s)." ),
true );
return 0;
}
DIALOG_MULTICHANNEL_GENERATE_RULE_AREAS dialog( frame(), this );
int ret = dialog.ShowModal();
DIALOG_MULTICHANNEL_GENERATE_RULE_AREAS dialog( frame(), this );
int ret = dialog.ShowModal();
if( ret != wxID_OK )
return 0;
if( ret != wxID_OK )
return 0;
}
for( ZONE* zone : board()->Zones() )
{
@ -905,7 +656,7 @@ int MULTICHANNEL_TOOL::autogenerateRuleAreas( const TOOL_EVENT& aEvent )
m_reporter->Report(
wxString::Format( wxT( "%d placement areas found\n" ), (int) m_areas.m_areas.size() ) );
BOARD_COMMIT commit( frame()->GetToolManager(), true );
BOARD_COMMIT commit( GetManager(), true );
for( RULE_AREA& ra : m_areas.m_areas )
{
@ -937,7 +688,7 @@ int MULTICHANNEL_TOOL::autogenerateRuleAreas( const TOOL_EVENT& aEvent )
if( m_areas.m_groupItems )
{
// fixme: sth gets weird when creating new zones & grouping them within a single COMMIT
BOARD_COMMIT grpCommit( frame()->GetToolManager(), true );
BOARD_COMMIT grpCommit( GetManager(), true );
PCB_GROUP* grp = new PCB_GROUP( board() );

80
pcbnew/tools/multichannel_tool.h

@ -30,13 +30,12 @@
#include <unordered_set>
#include <tools/pcb_tool_base.h>
#include <connectivity/topo_match.h>
#include <pad.h>
#include <footprint.h>
#include <reporter.h>
typedef std::vector<std::pair<FOOTPRINT*, FOOTPRINT*>> FP_PAIRS;
enum class REPEAT_LAYOUT_EDGE_MODE
{
INSIDE = 0,
@ -55,63 +54,6 @@ struct REPEAT_LAYOUT_OPTIONS
REPEAT_LAYOUT_EDGE_MODE m_edgeMode;
};
struct PAD_PREFIX_ENTRY
{
PAD_PREFIX_ENTRY( PAD* pad_, wxString pfx_ ) :
pad( pad_ ), componentPrefix( pfx_ ), processed( false ){};
PAD* pad;
wxString componentPrefix;
bool processed;
wxString format() const
{
return wxString::Format( wxT( "%s-%s[%s]" ), componentPrefix, pad->GetNumber(),
pad->GetNetname().c_str().AsChar() );
}
bool matchesPadNumberAndPrefix( const PAD_PREFIX_ENTRY& aOther ) const
{
if( componentPrefix != aOther.componentPrefix )
return false;
if( pad->GetNumber() != aOther.pad->GetNumber() )
return false;
return true;
}
};
struct FP_WITH_CONNECTIONS
{
FOOTPRINT* fp;
typedef std::unordered_map<PAD*, std::vector<PAD_PREFIX_ENTRY>> PAD_CONNECTION_MAP;
PAD_CONNECTION_MAP connsWithinRA;
bool processed;
void sortByPadNumbers()
{
// fixme
PAD_CONNECTION_MAP sorted;
for( auto& ent : connsWithinRA )
{
std::vector<PAD_PREFIX_ENTRY> v( ent.second );
auto compare = []( const PAD_PREFIX_ENTRY& a, const PAD_PREFIX_ENTRY& b ) -> int
{
if( a.pad->GetNumber() > b.pad->GetNumber() )
return 0;
else if( a.pad->GetNumber() == b.pad->GetNumber() )
return a.componentPrefix < b.componentPrefix;
else
return a.pad->GetNumber() < b.pad->GetNumber();
};
std::sort( v.begin(), v.end(), compare );
sorted[ent.first] = v;
}
connsWithinRA = sorted;
}
};
struct RULE_AREA;
@ -121,15 +63,14 @@ struct RULE_AREA_COMPAT_DATA
bool m_isOk;
bool m_doCopy;
wxString m_errorMsg;
std::vector<std::pair<FOOTPRINT*, FOOTPRINT*>> m_matchingFootprints;
TMATCH::COMPONENT_MATCHES m_matchingComponents;
};
struct RULE_AREA
{
ZONE* m_area;
std::vector<FP_WITH_CONNECTIONS> m_raFootprints;
std::set<FOOTPRINT*> m_raFootprints;
std::set<FOOTPRINT*> m_sheetComponents;
std::map<PAD*, FOOTPRINT*> m_fpPads;
bool m_existsAlready;
bool m_generateEnabled;
wxString m_sheetPath;
@ -166,31 +107,30 @@ public:
~MULTICHANNEL_TOOL();
RULE_AREAS_DATA* GetData() { return &m_areas; }
int AutogenerateRuleAreas( const TOOL_EVENT& aEvent );
int RepeatLayout( const TOOL_EVENT& aEvent, ZONE* aRefZone );
void QuerySheets();
void FindExistingRuleAreas();
int CheckRACompatibility( ZONE *aRefZone );
private:
void setTransitions() override;
int autogenerateRuleAreas( const TOOL_EVENT& aEvent );
int repeatLayout( const TOOL_EVENT& aEvent );
wxString stripComponentIndex( wxString aRef ) const;
bool identifyComponentsInRuleArea( ZONE* aRuleArea, std::set<FOOTPRINT*>& aComponents );
const SHAPE_LINE_CHAIN buildRAOutline( std::set<FOOTPRINT*>& aFootprints, int aMargin );
std::set<FOOTPRINT*> queryComponentsInSheet( wxString aSheetName );
void findExistingRuleAreas();
void querySheets();
RULE_AREA* findRAByName( const wxString& aName );
bool resolveConnectionTopology( RULE_AREA* aRefArea, RULE_AREA* aTargetArea,
RULE_AREA_COMPAT_DATA& aMatches );
bool copyRuleAreaContents( FP_PAIRS& aMatches, BOARD_COMMIT* aCommit, RULE_AREA* aRefArea,
bool copyRuleAreaContents( TMATCH::COMPONENT_MATCHES& aMatches, BOARD_COMMIT* aCommit, RULE_AREA* aRefArea,
RULE_AREA* aTargetArea, REPEAT_LAYOUT_OPTIONS aOpts );
int findRoutedConnections( std::set<BOARD_ITEM*>& aOutput,
std::shared_ptr<CONNECTIVITY_DATA> aConnectivity,
const SHAPE_POLY_SET& aRAPoly, RULE_AREA* aRA, FOOTPRINT* aFp,
const REPEAT_LAYOUT_OPTIONS& aOpts ) const;
bool checkIfPadNetsMatch( FP_WITH_CONNECTIONS& aRef, FP_WITH_CONNECTIONS& aTgt,
FP_PAIRS& aMatches ) const;
std::unique_ptr<REPORTER> m_reporter;
RULE_AREAS_DATA m_areas;
};

Loading…
Cancel
Save