2024-01-03 19:48:27 +01:00
|
|
|
/*
|
|
|
|
* 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 <board_commit.h>
|
|
|
|
#include <tools/pcb_actions.h>
|
|
|
|
|
|
|
|
#include <dialogs/dialog_multichannel_generate_rule_areas.h>
|
|
|
|
#include <dialogs/dialog_multichannel_repeat_layout.h>
|
|
|
|
|
|
|
|
#include "multichannel_tool.h"
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
#include <pcbexpr_evaluator.h>
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
#include <zone.h>
|
|
|
|
#include <geometry/convex_hull.h>
|
|
|
|
#include <pcb_group.h>
|
|
|
|
#include <connectivity/connectivity_data.h>
|
2024-01-08 22:37:56 +01:00
|
|
|
#include <optional>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <random>
|
2024-01-09 18:45:39 +01:00
|
|
|
#include <core/profile.h>
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
#undef MULTICHANNEL_EXTRA_DEBUG
|
2024-01-08 22:37:56 +01:00
|
|
|
|
|
|
|
#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
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
MULTICHANNEL_TOOL::MULTICHANNEL_TOOL() : PCB_TOOL_BASE( "pcbnew.Multichannel" )
|
|
|
|
{
|
|
|
|
m_reporter.reset( new NULL_REPORTER );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MULTICHANNEL_TOOL::~MULTICHANNEL_TOOL()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MULTICHANNEL_TOOL::setTransitions()
|
|
|
|
{
|
|
|
|
Go( &MULTICHANNEL_TOOL::autogenerateRuleAreas, PCB_ACTIONS::generatePlacementRuleAreas.MakeEvent() );
|
|
|
|
Go( &MULTICHANNEL_TOOL::repeatLayout, PCB_ACTIONS::repeatLayout.MakeEvent() );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
bool MULTICHANNEL_TOOL::identifyComponentsInRuleArea( ZONE* aRuleArea,
|
|
|
|
std::set<FOOTPRINT*>& aComponents )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER );
|
|
|
|
PCBEXPR_UCODE ucode;
|
|
|
|
PCBEXPR_CONTEXT ctx, preflightCtx;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
auto reportError = [&]( const wxString& aMessage, int aOffset )
|
|
|
|
{
|
|
|
|
m_reporter->Report( _( "ERROR:" ) + wxS( " " ) + aMessage );
|
|
|
|
};
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
ctx.SetErrorCallback( reportError );
|
|
|
|
preflightCtx.SetErrorCallback( reportError );
|
|
|
|
compiler.SetErrorCallback( reportError );
|
|
|
|
//compiler.SetDebugReporter( m_reporter );
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
m_reporter->Report(
|
|
|
|
wxString::Format( wxT( "Process rule area '%s'" ), aRuleArea->GetZoneName() ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
auto ok = compiler.Compile( aRuleArea->GetRuleAreaExpression(), &ucode, &preflightCtx );
|
2024-01-09 18:45:39 +01:00
|
|
|
|
|
|
|
|
|
|
|
if( !ok )
|
|
|
|
{
|
2024-01-03 19:48:27 +01:00
|
|
|
return false;
|
2024-01-09 18:45:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for( FOOTPRINT* fp : board()->Footprints() )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
ctx.SetItems( fp, fp );
|
|
|
|
auto val = ucode.Run( &ctx );
|
|
|
|
if( val->AsDouble() != 0.0 )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
m_reporter->Report( wxString::Format( wxT( "- %s [sheet %s]" ), fp->GetReference(),
|
|
|
|
fp->GetSheetname() ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
aComponents.insert( fp );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::set<FOOTPRINT*> MULTICHANNEL_TOOL::queryComponentsInSheet( wxString aSheetName )
|
|
|
|
{
|
|
|
|
std::set<FOOTPRINT*> rv;
|
2024-01-09 18:45:39 +01:00
|
|
|
if( aSheetName.EndsWith( wxT( "/" ) ) )
|
2024-01-03 19:48:27 +01:00
|
|
|
aSheetName.RemoveLast();
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
for( auto& fp : board()->Footprints() )
|
|
|
|
{
|
|
|
|
auto sn = fp->GetSheetname();
|
2024-01-09 18:45:39 +01:00
|
|
|
if( sn.EndsWith( wxT( "/" ) ) )
|
2024-01-03 19:48:27 +01:00
|
|
|
sn.RemoveLast();
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( sn == aSheetName )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
rv.insert( fp );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
const SHAPE_LINE_CHAIN MULTICHANNEL_TOOL::buildRAOutline( std::set<FOOTPRINT*>& aFootprints,
|
|
|
|
int aMargin )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
std::vector<VECTOR2I> bbCorners, hullVertices;
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( auto fp : aFootprints )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
auto bb = fp->GetBoundingBox( false, false );
|
|
|
|
bb.Inflate( aMargin );
|
|
|
|
|
|
|
|
bbCorners.push_back( { bb.GetX(), bb.GetY() } );
|
|
|
|
bbCorners.push_back( { bb.GetX() + bb.GetWidth(), bb.GetY() } );
|
|
|
|
bbCorners.push_back( { bb.GetX() + bb.GetWidth(), bb.GetY() + bb.GetHeight() } );
|
|
|
|
bbCorners.push_back( { bb.GetX(), bb.GetY() + bb.GetHeight() } );
|
|
|
|
}
|
|
|
|
|
|
|
|
BuildConvexHull( hullVertices, bbCorners );
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
SHAPE_LINE_CHAIN hull( hullVertices );
|
2024-01-03 19:48:27 +01:00
|
|
|
SHAPE_LINE_CHAIN raOutline;
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
// make the newly computed convex hull use only 90 degree segments
|
|
|
|
hull.SetClosed( true );
|
2024-01-03 19:48:27 +01:00
|
|
|
for( int i = 0; i < hull.SegmentCount(); i++ )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
const auto& seg = hull.CSegment( i );
|
2024-01-03 19:48:27 +01:00
|
|
|
const VECTOR2I p0( seg.A.x, seg.B.y );
|
|
|
|
const VECTOR2I p1( seg.B.x, seg.A.y );
|
|
|
|
|
|
|
|
raOutline.Append( seg.A );
|
|
|
|
if( hull.PointInside( p0 ) )
|
|
|
|
raOutline.Append( p0 );
|
|
|
|
else
|
|
|
|
raOutline.Append( p1 );
|
|
|
|
}
|
|
|
|
|
|
|
|
raOutline.SetClosed( true );
|
|
|
|
raOutline.Simplify();
|
|
|
|
|
|
|
|
return raOutline;
|
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
void MULTICHANNEL_TOOL::querySheets()
|
|
|
|
{
|
|
|
|
using PathAndName = std::pair<wxString, wxString>;
|
|
|
|
std::set<PathAndName> uniqueSheets;
|
|
|
|
|
|
|
|
m_areas.m_areas.clear();
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( FOOTPRINT* fp : board()->Footprints() )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
uniqueSheets.insert( PathAndName( fp->GetSheetname(), fp->GetSheetfile() ) );
|
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( const PathAndName& sheet : uniqueSheets )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
RULE_AREA ent;
|
|
|
|
ent.m_generateEnabled = false;
|
|
|
|
ent.m_sheetPath = sheet.first;
|
|
|
|
ent.m_sheetName = sheet.second;
|
2024-01-09 18:45:39 +01:00
|
|
|
ent.m_sheetComponents = queryComponentsInSheet( ent.m_sheetPath );
|
2024-01-03 19:48:27 +01:00
|
|
|
m_areas.m_areas.push_back( ent );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
void MULTICHANNEL_TOOL::findExistingRuleAreas()
|
|
|
|
{
|
|
|
|
m_areas.m_areas.clear();
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( ZONE* zone : board()->Zones() )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
if( !zone->GetIsRuleArea() )
|
|
|
|
continue;
|
|
|
|
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;
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( FOOTPRINT* fp : components )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
FP_WITH_CONNECTIONS fpc;
|
|
|
|
fpc.fp = fp;
|
|
|
|
area.m_raFootprints.push_back( fpc );
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( PAD* pad : fp->Pads() )
|
2024-01-04 17:33:38 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
area.m_fpPads[pad] = fp;
|
2024-01-04 17:33:38 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
area.m_ruleName = zone->GetZoneName();
|
2024-01-09 18:45:39 +01:00
|
|
|
area.m_center = zone->Outline()->COutline( 0 ).Centre();
|
2024-01-03 19:48:27 +01:00
|
|
|
m_areas.m_areas.push_back( area );
|
|
|
|
}
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 1, "Total RAs found: %d\n", (int) m_areas.m_areas.size() );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RULE_AREA* MULTICHANNEL_TOOL::findRAByName( const wxString& aName )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
for( RULE_AREA& ra : m_areas.m_areas )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
if( ra.m_ruleName == aName )
|
|
|
|
return &ra;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
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() )
|
|
|
|
{
|
|
|
|
if( item->Type() == PCB_ZONE_T )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
ZONE* zone = static_cast<ZONE*>( item );
|
2024-01-03 19:48:27 +01:00
|
|
|
if( !zone->GetIsRuleArea() )
|
|
|
|
continue;
|
|
|
|
if( zone->GetRuleAreaType() != RULE_AREA_TYPE::PLACEMENT )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
refRAs.push_back( zone );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( refRAs.size() != 1 )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
frame()->ShowInfoBarError( _( "Please select a single reference Rule Area to copy from" ),
|
|
|
|
true );
|
2024-01-03 19:48:27 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
findExistingRuleAreas();
|
|
|
|
|
2024-01-04 17:33:38 +01:00
|
|
|
m_areas.m_refRA = nullptr;
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( RULE_AREA& ra : m_areas.m_areas )
|
2024-01-04 17:33:38 +01:00
|
|
|
{
|
|
|
|
if( ra.m_area == refRAs.front() )
|
|
|
|
{
|
|
|
|
m_areas.m_refRA = &ra;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !m_areas.m_refRA )
|
|
|
|
return -1;
|
|
|
|
|
2024-01-08 22:37:56 +01:00
|
|
|
m_areas.m_compatMap.clear();
|
2024-01-09 18:45:39 +01:00
|
|
|
|
|
|
|
for( RULE_AREA& ra : m_areas.m_areas )
|
2024-01-04 17:33:38 +01:00
|
|
|
{
|
|
|
|
if( ra.m_area == m_areas.m_refRA->m_area )
|
|
|
|
continue;
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
m_areas.m_compatMap[&ra] = RULE_AREA_COMPAT_DATA();
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
resolveConnectionTopology( m_areas.m_refRA, &ra, m_areas.m_compatMap[&ra] );
|
2024-01-04 17:33:38 +01:00
|
|
|
}
|
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
DIALOG_MULTICHANNEL_REPEAT_LAYOUT dialog( frame(), this );
|
2024-01-09 18:45:39 +01:00
|
|
|
int ret = dialog.ShowModal();
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
if( ret != wxID_OK )
|
|
|
|
return 0;
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
BOARD_COMMIT commit( frame()->GetToolManager(),
|
|
|
|
true ); //<PNS_LOG_VIEWER_FRAME>()->GetToolManager(), true );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-08 22:37:56 +01:00
|
|
|
int totalCopied = 0;
|
|
|
|
|
2024-01-04 17:33:38 +01:00
|
|
|
for( auto& targetArea : m_areas.m_compatMap )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-08 22:37:56 +01:00
|
|
|
if( !targetArea.second.m_doCopy )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 1, "skipping copy to RA '%s' (disabled in dialog)\n",
|
|
|
|
targetArea.first->m_ruleName );
|
2024-01-08 22:37:56 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-01-04 17:33:38 +01:00
|
|
|
if( !targetArea.second.m_isOk )
|
|
|
|
continue;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( !copyRuleAreaContents( targetArea.second.m_matchingFootprints, &commit, m_areas.m_refRA,
|
|
|
|
targetArea.first, m_areas.m_options ) )
|
2024-01-04 17:33:38 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
auto errMsg = wxString::Format(
|
|
|
|
_( "Copy Rule Area contents failed between rule areas '%s' and '%s'." ),
|
|
|
|
m_areas.m_refRA->m_area->GetZoneName(),
|
|
|
|
targetArea.first->m_area->GetZoneName() );
|
2024-01-04 17:33:38 +01:00
|
|
|
|
|
|
|
commit.Revert();
|
|
|
|
frame()->ShowInfoBarError( errMsg, true );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2024-01-08 22:37:56 +01:00
|
|
|
|
|
|
|
totalCopied++;
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
commit.Push( _( "Repeat layout" ) );
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
frame()->ShowInfoBarMsg( wxString::Format( _( "Copied to %d Rule Areas." ), totalCopied ),
|
|
|
|
true );
|
2024-01-03 19:48:27 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
wxString MULTICHANNEL_TOOL::stripComponentIndex( wxString aRef ) const
|
|
|
|
{
|
|
|
|
wxString rv;
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
// fixme: i'm pretty sure this can be written in a simpler way, but I really suck at figuring
|
|
|
|
// out which wx's built in functions would do it for me. And I hate regexps :-)
|
|
|
|
for( auto k : aRef )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
if( !k.IsAscii() )
|
|
|
|
break;
|
|
|
|
char c;
|
2024-01-09 18:45:39 +01:00
|
|
|
k.GetAsChar( &c );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || ( c == '_' ) )
|
|
|
|
rv.Append( k );
|
2024-01-03 19:48:27 +01:00
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2024-01-05 15:51:54 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
int MULTICHANNEL_TOOL::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
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-05 15:51:54 +01:00
|
|
|
std::set<BOARD_ITEM*> conns;
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( PAD* pad : aFp->Pads() )
|
2024-01-04 17:33:38 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
const std::vector<BOARD_CONNECTED_ITEM*> connItems = aConnectivity->GetConnectedItems(
|
2024-01-08 22:37:56 +01:00
|
|
|
pad, { PCB_PAD_T, PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T }, true );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( BOARD_CONNECTED_ITEM* item : connItems )
|
2024-01-05 15:51:54 +01:00
|
|
|
conns.insert( item );
|
|
|
|
}
|
|
|
|
|
|
|
|
int count = 0;
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( BOARD_ITEM* item : conns )
|
2024-01-05 15:51:54 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
// fixme: respect layer sets assigned to each RA
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( item->Type() == PCB_PAD_T )
|
|
|
|
continue;
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
std::shared_ptr<SHAPE> effShape = item->GetEffectiveShape( item->GetLayer() );
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( effShape->Collide( &aRAPoly, 0 ) )
|
|
|
|
{
|
|
|
|
aOutput.insert( item );
|
|
|
|
count++;
|
|
|
|
}
|
2024-01-04 17:33:38 +01:00
|
|
|
}
|
|
|
|
|
2024-01-05 15:51:54 +01:00
|
|
|
return count;
|
2024-01-04 17:33:38 +01:00
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
|
|
|
|
bool MULTICHANNEL_TOOL::copyRuleAreaContents( FP_PAIRS& aMatches, BOARD_COMMIT* aCommit,
|
|
|
|
RULE_AREA* aRefArea, RULE_AREA* aTargetArea,
|
|
|
|
REPEAT_LAYOUT_OPTIONS aOpts )
|
2024-01-04 17:33:38 +01:00
|
|
|
{
|
|
|
|
// copy RA shapes first
|
2024-01-09 18:45:39 +01:00
|
|
|
SHAPE_LINE_CHAIN refOutline = aRefArea->m_area->Outline()->COutline( 0 );
|
|
|
|
SHAPE_LINE_CHAIN targetOutline = aTargetArea->m_area->Outline()->COutline( 0 );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
VECTOR2I disp = aTargetArea->m_center - aRefArea->m_center;
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
SHAPE_POLY_SET refPoly;
|
|
|
|
refPoly.AddOutline( refOutline );
|
|
|
|
refPoly.CacheTriangulation( false );
|
|
|
|
|
2024-01-04 17:33:38 +01:00
|
|
|
SHAPE_POLY_SET targetPoly;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-04 17:33:38 +01:00
|
|
|
SHAPE_LINE_CHAIN newTargetOutline( refOutline );
|
|
|
|
newTargetOutline.Move( disp );
|
|
|
|
targetPoly.AddOutline( newTargetOutline );
|
|
|
|
targetPoly.CacheTriangulation( false );
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
auto connectivity = board()->GetConnectivity();
|
|
|
|
|
2024-01-08 22:37:56 +01:00
|
|
|
aCommit->Modify( aTargetArea->m_area );
|
2024-01-05 15:51:54 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
if( aOpts.m_copyRouting )
|
|
|
|
{
|
2024-01-05 15:51:54 +01:00
|
|
|
std::set<BOARD_ITEM*> refRouting;
|
2024-01-08 22:37:56 +01:00
|
|
|
std::set<BOARD_ITEM*> targetRouting;
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 1, "copying routing: %d fps\n", aMatches.size() );
|
2024-01-05 15:51:54 +01:00
|
|
|
|
2024-01-04 17:33:38 +01:00
|
|
|
for( auto& fpPair : aMatches )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
findRoutedConnections( targetRouting, connectivity, targetPoly, aTargetArea,
|
|
|
|
fpPair.second, aOpts );
|
|
|
|
findRoutedConnections( refRouting, connectivity, refPoly, aRefArea, fpPair.first,
|
|
|
|
aOpts );
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 2, "target-routes %d\n", (int) targetRouting.size() );
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( BOARD_ITEM* item : targetRouting )
|
|
|
|
{
|
|
|
|
if( item->IsLocked() && !aOpts.m_includeLockedItems )
|
|
|
|
continue;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
aCommit->Remove( item );
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( BOARD_ITEM* item : refRouting )
|
|
|
|
{
|
|
|
|
BOARD_ITEM* copied = static_cast<BOARD_ITEM*>( item->Clone() );
|
|
|
|
copied->Move( disp );
|
|
|
|
aCommit->Add( copied );
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
aTargetArea->m_area->RemoveAllContours();
|
2024-01-04 17:33:38 +01:00
|
|
|
aTargetArea->m_area->AddPolygon( newTargetOutline );
|
2024-01-08 22:37:56 +01:00
|
|
|
aTargetArea->m_area->UnHatchBorder();
|
|
|
|
aTargetArea->m_area->HatchBorder();
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-04 17:33:38 +01:00
|
|
|
if( aOpts.m_copyPlacement )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
for( auto& fpPair : aMatches )
|
|
|
|
{
|
|
|
|
FOOTPRINT* refFP = fpPair.first;
|
|
|
|
FOOTPRINT* targetFP = fpPair.second;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-08 22:37:56 +01:00
|
|
|
#if 0
|
2024-01-09 18:45:39 +01:00
|
|
|
//fixme: respect layers assigned to RAs
|
2024-01-05 15:51:54 +01:00
|
|
|
//printf("ref-ls: %s\n", aRefArea->m_area->GetLayerSet().FmtHex().c_str() );
|
|
|
|
//printf("target-ls: %s\n", aRefArea->m_area->GetLayerSet().FmtHex().c_str() );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
if( ! aRefArea->m_area->GetLayerSet().Contains( refFP->GetLayer() ) );
|
|
|
|
{
|
2024-01-08 22:37:56 +01:00
|
|
|
DBG(2, wxT("discard ref:%s (ref layer)\n"), refFP->GetReference() );
|
2024-01-03 19:48:27 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if( ! aTargetArea->m_area->GetLayerSet().Contains( refFP->GetLayer() ) );
|
|
|
|
{
|
2024-01-08 22:37:56 +01:00
|
|
|
DBG(2, wxT("discard ref:%s (target layer)\n"), refFP->GetReference() );
|
2024-01-03 19:48:27 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
#endif
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( targetFP->IsLocked() && !aOpts.m_includeLockedItems )
|
|
|
|
continue;
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
aCommit->Modify( targetFP );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-02-02 00:08:46 +01:00
|
|
|
targetFP->SetLayerAndFlip( refFP->GetLayer() );
|
2024-01-09 18:45:39 +01:00
|
|
|
targetFP->SetOrientation( refFP->GetOrientation() );
|
|
|
|
VECTOR2I targetPos = refFP->GetPosition() + disp;
|
|
|
|
targetFP->SetPosition( targetPos );
|
|
|
|
targetFP->Reference().SetTextAngle( refFP->Reference().GetTextAngle() );
|
|
|
|
targetFP->Reference().SetPosition( refFP->Reference().GetPosition() + disp );
|
|
|
|
targetFP->Value().SetTextAngle( refFP->Value().GetTextAngle() );
|
|
|
|
targetFP->Value().SetPosition( refFP->Value().GetPosition() + disp );
|
|
|
|
}
|
2024-01-04 17:33:38 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
bool MULTICHANNEL_TOOL::checkIfPadNetsMatch( FP_WITH_CONNECTIONS& aRef, FP_WITH_CONNECTIONS& aTgt,
|
|
|
|
FP_PAIRS& aMatches ) const
|
2024-01-08 22:37:56 +01:00
|
|
|
{
|
|
|
|
#ifdef MULTICHANNEL_EXTRA_DEBUG
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 2, "ref: %d pads\n", (int) aRef.connsWithinRA.size() );
|
|
|
|
DBG( 2, "matches so far: %d\n", (int) aMatches.size() );
|
2024-01-08 22:37:56 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
std::map<PAD*, PAD*> pairs;
|
2024-01-09 18:45:39 +01:00
|
|
|
std::vector<PAD*> pref, ptgt;
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( auto& m : aMatches )
|
2024-01-08 22:37:56 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
for( PAD* p : m.first->Pads() )
|
2024-01-08 22:37:56 +01:00
|
|
|
pref.push_back( p );
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( PAD* p : m.second->Pads() )
|
2024-01-08 22:37:56 +01:00
|
|
|
ptgt.push_back( p );
|
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( PAD* p : aRef.fp->Pads() )
|
2024-01-08 22:37:56 +01:00
|
|
|
pref.push_back( p );
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( PAD* p : aTgt.fp->Pads() )
|
2024-01-08 22:37:56 +01:00
|
|
|
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 )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 2, wxT( "pad %s: %s -> " ), ref.first->GetNumber(), ref.first->GetNetname() );
|
2024-01-08 22:37:56 +01:00
|
|
|
|
|
|
|
std::optional<int> prevNet;
|
|
|
|
|
|
|
|
for( auto& pc : ref.second )
|
|
|
|
{
|
|
|
|
auto tpad = pairs.find( pc.pad );
|
|
|
|
|
|
|
|
if( tpad != pairs.end() )
|
|
|
|
{
|
|
|
|
int nc = tpad->second->GetNetCode();
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 3, wxT( " %s[%d]" ), tpad->second->GetNetname(), tpad->second->GetNetCode() );
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( prevNet && ( *prevNet != nc ) )
|
2024-01-08 22:37:56 +01:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
prevNet = nc;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
DBGn( 3, wxT( " ?" ) );
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
|
|
|
}
|
2024-01-09 18:45:39 +01:00
|
|
|
DBGn( 3, wxT( "\n" ) );
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-01-04 17:33:38 +01:00
|
|
|
bool MULTICHANNEL_TOOL::resolveConnectionTopology( RULE_AREA* aRefArea, RULE_AREA* aTargetArea, RULE_AREA_COMPAT_DATA& aMatches )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
std::map<NETINFO_ITEM*, std::vector<PAD*>> allPads;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( FOOTPRINT* fp : board()->Footprints() )
|
2024-01-08 22:37:56 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
for( PAD* pad : fp->Pads() )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
auto iter = allPads.find( pad->GetNet() );
|
|
|
|
|
|
|
|
if( iter == allPads.end() )
|
2024-01-09 18:45:39 +01:00
|
|
|
allPads[pad->GetNet()] = { pad };
|
2024-01-03 19:48:27 +01:00
|
|
|
else
|
2024-01-09 18:45:39 +01:00
|
|
|
allPads[pad->GetNet()].push_back( pad );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
2024-01-04 17:33:38 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
auto belongsToRAFootprint = []( RULE_AREA* ra, PAD* aPad ) -> FOOTPRINT*
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-04 17:33:38 +01:00
|
|
|
auto iter = ra->m_fpPads.find( aPad );
|
2024-01-09 18:45:39 +01:00
|
|
|
if( iter == ra->m_fpPads.end() )
|
2024-01-04 17:33:38 +01:00
|
|
|
return nullptr;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-04 17:33:38 +01:00
|
|
|
return iter->second;
|
2024-01-03 19:48:27 +01:00
|
|
|
};
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
auto findPadConnectionsWithinRA = [&]( RULE_AREA* ra,
|
|
|
|
PAD* aPad ) -> std::vector<PAD_PREFIX_ENTRY>
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
std::vector<PAD_PREFIX_ENTRY> rv;
|
|
|
|
|
|
|
|
for( auto cpad : allPads[aPad->GetNet()] )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
if( belongsToRAFootprint( ra, cpad ) )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
rv.push_back( PAD_PREFIX_ENTRY(
|
|
|
|
cpad, stripComponentIndex( cpad->GetParentFootprint()->GetReference() ) ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
};
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
auto buildPadConnectionsWithinRA = [&]( RULE_AREA* ra ) -> bool
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
for( auto& fp : ra->m_raFootprints )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
for( auto pad : fp.fp->Pads() )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
fp.connsWithinRA[pad] = findPadConnectionsWithinRA( ra, pad );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 3, wxT( "p %s-%s ->" ), pad->GetParentAsString(), pad->GetNumber() );
|
|
|
|
for( auto p : fp.connsWithinRA[pad] )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 3, wxT( " %s-%s" ), p.pad->GetParentAsString(), p.pad->GetNumber() );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
DBGn( 3, wxT( "\n" ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
auto matchConnections = [&]( FP_WITH_CONNECTIONS* aRef, FP_WITH_CONNECTIONS* aTarget ) -> bool
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
for( auto ref : aRef->connsWithinRA )
|
2024-01-08 22:37:56 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 2, wxT( "ref [%s]: " ), ref.first->GetNumber() );
|
2024-01-03 19:48:27 +01:00
|
|
|
for( auto conn : ref.second )
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 2, wxT( "%s[%s] " ), conn.format(), conn.pad->GetNetname() );
|
|
|
|
DBGn( 2, wxT( "\n" ) );
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
for( auto tgt : aTarget->connsWithinRA )
|
2024-01-08 22:37:56 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
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" ) );
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool matchFound = false;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
for( auto ref : aRef->connsWithinRA )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
for( auto tgt : aTarget->connsWithinRA )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
bool padsMatch = true;
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
if( ref.second.size() != tgt.second.size() )
|
2024-01-08 22:37:56 +01:00
|
|
|
{
|
|
|
|
padsMatch = false;
|
2024-01-03 19:48:27 +01:00
|
|
|
continue;
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-08 22:37:56 +01:00
|
|
|
for( unsigned int i = 0; i < ref.second.size(); i++ )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
PAD_PREFIX_ENTRY& eref = ref.second[i];
|
|
|
|
PAD_PREFIX_ENTRY& etgt = tgt.second[i];
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( !eref.matchesPadNumberAndPrefix( etgt ) )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
padsMatch = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
if( padsMatch )
|
2024-01-08 22:37:56 +01:00
|
|
|
{
|
2024-01-03 19:48:27 +01:00
|
|
|
matchFound = true;
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if( !matchFound )
|
2024-01-08 22:37:56 +01:00
|
|
|
{
|
2024-01-03 19:48:27 +01:00
|
|
|
return false;
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
2024-01-08 22:37:56 +01:00
|
|
|
return matchFound;
|
2024-01-03 19:48:27 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if( aRefArea->m_raFootprints.size() != aTargetArea->m_raFootprints.size() )
|
|
|
|
{
|
2024-01-04 17:33:38 +01:00
|
|
|
aMatches.m_isOk = false;
|
2024-01-09 18:45:39 +01:00
|
|
|
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() );
|
2024-01-04 17:33:38 +01:00
|
|
|
|
|
|
|
m_reporter->Report( aMatches.m_errorMsg );
|
2024-01-05 15:51:54 +01:00
|
|
|
return false;
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-01-08 22:37:56 +01:00
|
|
|
//int targetsRemaining = aTargetArea->m_raFootprints.size();
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
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();
|
|
|
|
} );
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
const int MATCH_MAX_ATTEMPTS = 100;
|
2024-01-08 22:37:56 +01:00
|
|
|
FOOTPRINT* failingRefFP = nullptr;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( int attempt = 0; attempt < MATCH_MAX_ATTEMPTS; attempt++ )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
aMatches.m_matchingFootprints.clear();
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( auto& targetFP : aTargetArea->m_raFootprints )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
targetFP.processed = false;
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( auto& refFP : aRefArea->m_raFootprints )
|
|
|
|
{
|
|
|
|
std::vector<FP_WITH_CONNECTIONS*> candidates;
|
2024-01-09 17:52:58 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
bool anyMatchesFound = false;
|
2024-01-09 17:52:58 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( auto& targetFP : aTargetArea->m_raFootprints )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
if( stripComponentIndex( refFP.fp->GetReference() )
|
|
|
|
!= stripComponentIndex( targetFP.fp->GetReference() ) )
|
|
|
|
continue;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( refFP.fp->GetValue() != targetFP.fp->GetValue() )
|
|
|
|
continue;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( refFP.fp->GetFPID() != targetFP.fp->GetFPID() )
|
|
|
|
continue;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( !targetFP.processed )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
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 );
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
DBG( 2, wxT( "Candidates for %s: %d\n" ), refFP.fp->GetReference(),
|
|
|
|
(int) candidates.size() );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
FP_WITH_CONNECTIONS* best = nullptr;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( auto& c : candidates )
|
2024-01-08 22:37:56 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
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;
|
|
|
|
}
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( !anyMatchesFound )
|
|
|
|
{
|
|
|
|
failingRefFP = refFP.fp;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
best->processed = true;
|
|
|
|
aMatches.m_matchingFootprints.push_back( { refFP.fp, best->fp } );
|
|
|
|
}
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
if( aMatches.m_matchingFootprints.size() == aTargetArea->m_raFootprints.size() )
|
2024-01-08 22:37:56 +01:00
|
|
|
{
|
|
|
|
aMatches.m_isOk = true;
|
|
|
|
return true;
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
auto rng = std::default_random_engine{};
|
|
|
|
std::shuffle( aTargetArea->m_raFootprints.begin(), aTargetArea->m_raFootprints.end(), rng );
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
aMatches.m_errorMsg =
|
|
|
|
wxString::Format( wxT( "Topology mismatch (no counterpart found for component %s)" ),
|
|
|
|
failingRefFP->GetReference() );
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
aMatches.m_isOk = false;
|
|
|
|
DBG( 2, wxT( "%s: no match\n" ), failingRefFP->GetReference() );
|
|
|
|
return false;
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
frame()->ShowInfoBarError( _( "Cannot auto-generate any placement areas because the "
|
|
|
|
"schematic has only one or no hierarchical sheet(s)." ),
|
|
|
|
true );
|
2024-01-03 19:48:27 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DIALOG_MULTICHANNEL_GENERATE_RULE_AREAS dialog( frame(), this );
|
2024-01-09 18:45:39 +01:00
|
|
|
int ret = dialog.ShowModal();
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
if( ret != wxID_OK )
|
|
|
|
return 0;
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( ZONE* zone : board()->Zones() )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
if( !zone->GetIsRuleArea() )
|
|
|
|
continue;
|
|
|
|
if( zone->GetRuleAreaType() != RULE_AREA_TYPE::PLACEMENT )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
std::set<FOOTPRINT*> components;
|
|
|
|
identifyComponentsInRuleArea( zone, components );
|
|
|
|
|
|
|
|
if( components.empty() )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for( auto& ra : m_areas.m_areas )
|
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
if( components == ra.m_sheetComponents )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
m_reporter->Report( wxString::Format(
|
|
|
|
wxT( "Placement rule area for sheet '%s' already exists as '%s'\n" ),
|
|
|
|
ra.m_sheetPath, zone->GetZoneName() ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
ra.m_area = zone;
|
|
|
|
ra.m_existsAlready = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
m_reporter->Report(
|
|
|
|
wxString::Format( wxT( "%d placement areas found\n" ), (int) m_areas.m_areas.size() ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
BOARD_COMMIT commit( frame()->GetToolManager(), true );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( RULE_AREA& ra : m_areas.m_areas )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-01-09 18:45:39 +01:00
|
|
|
if( !ra.m_generateEnabled )
|
2024-01-03 19:48:27 +01:00
|
|
|
continue;
|
2024-01-09 18:45:39 +01:00
|
|
|
if( ra.m_existsAlready && !m_areas.m_replaceExisting )
|
2024-01-03 19:48:27 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
auto raOutline = buildRAOutline( ra.m_sheetComponents, 100000 );
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
std::unique_ptr<ZONE> newZone( new ZONE( board() ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_sheetPath ) );
|
|
|
|
m_reporter->Report( wxString::Format( wxT( "Generated rule area '%s' (%d components)\n" ),
|
|
|
|
newZone->GetZoneName(),
|
|
|
|
(int) ra.m_sheetComponents.size() ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
newZone->SetIsRuleArea( true );
|
|
|
|
newZone->SetLayerSet( LSET::AllCuMask() );
|
|
|
|
newZone->SetRuleAreaType( RULE_AREA_TYPE::PLACEMENT );
|
2024-01-09 18:45:39 +01:00
|
|
|
newZone->SetRuleAreaExpression(
|
|
|
|
wxString::Format( wxT( "A.memberOfSheet('%s')" ), ra.m_sheetPath ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
newZone->AddPolygon( raOutline );
|
|
|
|
newZone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH );
|
|
|
|
//aBoard->Add( newZone.release() );
|
|
|
|
commit.Add( newZone.get() );
|
2024-01-09 18:45:39 +01:00
|
|
|
commit.Push( _( "Auto-generate placement rule areas" ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
if( m_areas.m_groupItems )
|
|
|
|
{
|
2024-01-05 15:51:54 +01:00
|
|
|
// fixme: sth gets weird when creating new zones & grouping them within a single COMMIT
|
|
|
|
BOARD_COMMIT grpCommit( frame()->GetToolManager(), true );
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
PCB_GROUP* grp = new PCB_GROUP( board() );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-05 15:51:54 +01:00
|
|
|
grpCommit.Add( grp );
|
|
|
|
|
|
|
|
grpCommit.Stage( newZone.get(), CHT_GROUP );
|
|
|
|
grp->AddItem( newZone.get() );
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
for( auto fp : ra.m_sheetComponents )
|
2024-01-05 15:51:54 +01:00
|
|
|
{
|
|
|
|
grpCommit.Stage( fp, CHT_GROUP );
|
2024-01-03 19:48:27 +01:00
|
|
|
grp->AddItem( fp );
|
2024-01-05 15:51:54 +01:00
|
|
|
}
|
2024-01-09 18:45:39 +01:00
|
|
|
grpCommit.Push( _( "Group components with their placement rule areas" ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
newZone.release();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|