2024-01-03 19:48:27 +01:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
2025-01-01 13:30:11 -08:00
|
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
2024-01-03 19:48:27 +01:00
|
|
|
*
|
|
|
|
* 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>
|
2024-11-08 19:40:57 +08:00
|
|
|
#include <geometry/shape_utils.h>
|
2024-01-03 19:48:27 +01:00
|
|
|
#include <pcb_group.h>
|
2025-03-09 20:46:34 +00:00
|
|
|
#include <component_classes/component_class.h>
|
2024-01-03 19:48:27 +01:00
|
|
|
#include <connectivity/connectivity_data.h>
|
2024-07-30 18:08:11 +02:00
|
|
|
#include <connectivity/topo_match.h>
|
2024-01-08 22:37:56 +01:00
|
|
|
#include <optional>
|
|
|
|
#include <algorithm>
|
2024-11-03 21:43:55 +00:00
|
|
|
#include <pcbnew_scripting_helpers.h>
|
2024-11-24 12:10:06 -05:00
|
|
|
#include <pcb_track.h>
|
2024-11-24 12:00:20 -05:00
|
|
|
#include <tool/tool_manager.h>
|
|
|
|
#include <tools/pcb_picker_tool.h>
|
2024-01-08 22:37:56 +01:00
|
|
|
#include <random>
|
2024-01-09 18:45:39 +01:00
|
|
|
#include <core/profile.h>
|
2024-07-30 18:08:11 +02:00
|
|
|
#include <wx/log.h>
|
|
|
|
#include <pgm_base.h>
|
2024-01-08 22:37:56 +01:00
|
|
|
|
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
#define MULTICHANNEL_EXTRA_DEBUG
|
|
|
|
|
|
|
|
static const wxString traceMultichannelTool = wxT( "MULTICHANNEL_TOOL" );
|
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
MULTICHANNEL_TOOL::MULTICHANNEL_TOOL() : PCB_TOOL_BASE( "pcbnew.Multichannel" )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MULTICHANNEL_TOOL::~MULTICHANNEL_TOOL()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MULTICHANNEL_TOOL::setTransitions()
|
|
|
|
{
|
2024-07-30 18:08:11 +02:00
|
|
|
Go( &MULTICHANNEL_TOOL::AutogenerateRuleAreas, PCB_ACTIONS::generatePlacementRuleAreas.MakeEvent() );
|
2024-01-03 19:48:27 +01:00
|
|
|
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
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
auto reportError =
|
|
|
|
[&]( const wxString& aMessage, int aOffset )
|
|
|
|
{
|
|
|
|
wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage );
|
|
|
|
};
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
ctx.SetErrorCallback( reportError );
|
|
|
|
preflightCtx.SetErrorCallback( reportError );
|
|
|
|
compiler.SetErrorCallback( reportError );
|
|
|
|
//compiler.SetDebugReporter( m_reporter );
|
|
|
|
|
2024-07-15 23:33:40 +02:00
|
|
|
wxLogTrace( traceMultichannelTool, wxT( "rule area '%s'"), aRuleArea->GetZoneName() );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-10-09 01:06:11 +01:00
|
|
|
wxString ruleText;
|
|
|
|
|
2025-06-07 17:22:08 +01:00
|
|
|
switch( aRuleArea->GetPlacementAreaSourceType() )
|
2024-10-09 01:06:11 +01:00
|
|
|
{
|
2025-06-07 17:22:08 +01:00
|
|
|
case PLACEMENT_SOURCE_T::SHEETNAME:
|
|
|
|
ruleText = wxT( "A.memberOfSheetOrChildren('" ) + aRuleArea->GetPlacementAreaSource() + wxT( "')" );
|
2024-10-09 01:06:11 +01:00
|
|
|
break;
|
2025-06-07 17:22:08 +01:00
|
|
|
case PLACEMENT_SOURCE_T::COMPONENT_CLASS:
|
|
|
|
ruleText = wxT( "A.hasComponentClass('" ) + aRuleArea->GetPlacementAreaSource() + wxT( "')" );
|
2024-10-09 22:43:40 +01:00
|
|
|
break;
|
2025-06-07 17:22:08 +01:00
|
|
|
case PLACEMENT_SOURCE_T::GROUP_PLACEMENT:
|
|
|
|
ruleText = wxT( "A.memberOfGroup('" ) + aRuleArea->GetPlacementAreaSource() + wxT( "')" );
|
2025-05-07 09:03:28 -04:00
|
|
|
break;
|
2024-10-09 01:06:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
auto ok = compiler.Compile( ruleText, &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 );
|
2025-07-29 11:32:54 +01:00
|
|
|
LIBEVAL::VALUE* val = ucode.Run( &ctx );
|
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
if( val->AsDouble() != 0.0 )
|
|
|
|
{
|
2025-07-29 11:32:54 +01:00
|
|
|
wxLogTrace( traceMultichannelTool, wxT( " - %s [sheet %s]" ),
|
|
|
|
fp->GetReference(),
|
|
|
|
fp->GetSheetname() );
|
2024-07-15 23:33:40 +02:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
aComponents.insert( fp );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-11-24 11:29:17 -05:00
|
|
|
bool MULTICHANNEL_TOOL::findOtherItemsInRuleArea( ZONE* aRuleArea, std::set<BOARD_ITEM*>& aItems )
|
|
|
|
{
|
|
|
|
std::vector<BOARD_ITEM*> result;
|
|
|
|
|
|
|
|
PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER );
|
|
|
|
PCBEXPR_UCODE ucode;
|
|
|
|
PCBEXPR_CONTEXT ctx, preflightCtx;
|
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
auto reportError =
|
|
|
|
[&]( const wxString& aMessage, int aOffset )
|
|
|
|
{
|
|
|
|
wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage );
|
|
|
|
};
|
2024-11-24 11:29:17 -05:00
|
|
|
|
|
|
|
ctx.SetErrorCallback( reportError );
|
|
|
|
preflightCtx.SetErrorCallback( reportError );
|
|
|
|
compiler.SetErrorCallback( reportError );
|
|
|
|
|
|
|
|
bool restoreBlankName = false;
|
|
|
|
|
|
|
|
if( aRuleArea->GetZoneName().IsEmpty() )
|
|
|
|
{
|
|
|
|
restoreBlankName = true;
|
|
|
|
aRuleArea->SetZoneName( aRuleArea->m_Uuid.AsString() );
|
|
|
|
}
|
|
|
|
|
|
|
|
wxString ruleText = wxString::Format( wxT( "A.enclosedByArea('%s')" ), aRuleArea->GetZoneName() );
|
|
|
|
|
|
|
|
if( !compiler.Compile( ruleText, &ucode, &preflightCtx ) )
|
|
|
|
{
|
|
|
|
if( restoreBlankName )
|
|
|
|
aRuleArea->SetZoneName( wxEmptyString );
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto testAndAdd =
|
2025-07-29 11:32:54 +01:00
|
|
|
[&]( BOARD_ITEM* aItem )
|
|
|
|
{
|
|
|
|
ctx.SetItems( aItem, aItem );
|
|
|
|
auto val = ucode.Run( &ctx );
|
2024-11-24 11:29:17 -05:00
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
if( val->AsDouble() != 0.0 )
|
|
|
|
aItems.insert( aItem );
|
|
|
|
};
|
2024-11-24 11:29:17 -05:00
|
|
|
|
|
|
|
for( ZONE* zone : board()->Zones() )
|
|
|
|
{
|
|
|
|
if( zone == aRuleArea )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
testAndAdd( zone );
|
|
|
|
}
|
|
|
|
|
|
|
|
for( BOARD_ITEM* drawing : board()->Drawings() )
|
|
|
|
testAndAdd( drawing );
|
|
|
|
|
|
|
|
for( PCB_GROUP* group : board()->Groups() )
|
|
|
|
{
|
|
|
|
// A group is cloned in its entirety if *all* children are contained
|
|
|
|
bool addGroup = true;
|
|
|
|
|
2025-03-27 10:43:44 -04:00
|
|
|
group->RunOnChildren(
|
2024-11-24 11:29:17 -05:00
|
|
|
[&]( BOARD_ITEM* aItem )
|
|
|
|
{
|
|
|
|
if( aItem->IsType( { PCB_ZONE_T, PCB_SHAPE_T, PCB_DIMENSION_T } ) )
|
|
|
|
{
|
|
|
|
ctx.SetItems( aItem, aItem );
|
2025-07-29 11:32:54 +01:00
|
|
|
LIBEVAL::VALUE* val = ucode.Run( &ctx );
|
2024-11-24 11:29:17 -05:00
|
|
|
|
|
|
|
if( val->AsDouble() == 0.0 )
|
|
|
|
addGroup = false;
|
|
|
|
}
|
2025-03-27 10:43:44 -04:00
|
|
|
},
|
|
|
|
RECURSE_MODE::RECURSE );
|
2024-11-24 11:29:17 -05:00
|
|
|
|
|
|
|
if( addGroup )
|
|
|
|
aItems.insert( group );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( restoreBlankName )
|
|
|
|
aRuleArea->SetZoneName( wxEmptyString );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-11-03 21:43:55 +00:00
|
|
|
std::set<FOOTPRINT*> MULTICHANNEL_TOOL::queryComponentsInSheet( wxString aSheetName ) const
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
std::set<FOOTPRINT*> rv;
|
2025-07-29 11:32:54 +01:00
|
|
|
|
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
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
for( FOOTPRINT* fp : board()->Footprints() )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
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 )
|
|
|
|
rv.insert( fp );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-11-03 21:43:55 +00:00
|
|
|
std::set<FOOTPRINT*>
|
|
|
|
MULTICHANNEL_TOOL::queryComponentsInComponentClass( const wxString& aComponentClassName ) const
|
|
|
|
{
|
|
|
|
std::set<FOOTPRINT*> rv;
|
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
for( FOOTPRINT* fp : board()->Footprints() )
|
2024-11-03 21:43:55 +00:00
|
|
|
{
|
|
|
|
if( fp->GetComponentClass()->ContainsClassName( aComponentClassName ) )
|
|
|
|
rv.insert( fp );
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
std::set<FOOTPRINT*> MULTICHANNEL_TOOL::queryComponentsInGroup( const wxString& aGroupName ) const
|
2025-05-07 09:03:28 -04:00
|
|
|
{
|
|
|
|
std::set<FOOTPRINT*> rv;
|
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
for( PCB_GROUP* group : board()->Groups() )
|
2025-05-07 09:03:28 -04:00
|
|
|
{
|
|
|
|
if( group->GetName() == aGroupName )
|
|
|
|
{
|
|
|
|
for( EDA_ITEM* item : group->GetItems() )
|
|
|
|
{
|
|
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
|
|
rv.insert( static_cast<FOOTPRINT*>( item ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
{
|
2024-11-08 19:40:57 +08:00
|
|
|
std::vector<VECTOR2I> bbCorners;
|
|
|
|
bbCorners.reserve( aFootprints.size() * 4 );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
for( FOOTPRINT* fp : aFootprints )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-11-08 19:40:57 +08:00
|
|
|
const BOX2I bb = fp->GetBoundingBox( false ).GetInflated( aMargin );
|
|
|
|
KIGEOM::CollectBoxCorners( bb, bbCorners );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
2024-11-08 19:40:57 +08:00
|
|
|
std::vector<VECTOR2I> hullVertices;
|
2024-01-03 19:48:27 +01:00
|
|
|
BuildConvexHull( hullVertices, bbCorners );
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
SHAPE_LINE_CHAIN hull( hullVertices );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-11-08 19:40:57 +08:00
|
|
|
// Make the newly computed convex hull use only 90 degree segments
|
|
|
|
return KIGEOM::RectifyPolygon( hull );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-11-03 21:43:55 +00:00
|
|
|
void MULTICHANNEL_TOOL::QuerySheetsAndComponentClasses()
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
using PathAndName = std::pair<wxString, wxString>;
|
|
|
|
std::set<PathAndName> uniqueSheets;
|
2024-11-03 21:43:55 +00:00
|
|
|
std::set<wxString> uniqueComponentClasses;
|
2025-05-07 09:03:28 -04:00
|
|
|
std::set<wxString> uniqueGroups;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
m_areas.m_areas.clear();
|
|
|
|
|
2024-11-03 21:43:55 +00:00
|
|
|
for( const FOOTPRINT* fp : board()->Footprints() )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
uniqueSheets.insert( PathAndName( fp->GetSheetname(), fp->GetSheetfile() ) );
|
2024-11-03 21:43:55 +00:00
|
|
|
|
|
|
|
const COMPONENT_CLASS* compClass = fp->GetComponentClass();
|
|
|
|
|
|
|
|
for( const COMPONENT_CLASS* singleClass : compClass->GetConstituentClasses() )
|
|
|
|
uniqueComponentClasses.insert( singleClass->GetName() );
|
2025-05-07 09:03:28 -04:00
|
|
|
|
|
|
|
if( fp->GetParentGroup() && !fp->GetParentGroup()->GetName().IsEmpty() )
|
|
|
|
uniqueGroups.insert( fp->GetParentGroup()->GetName() );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
for( const PathAndName& sheet : uniqueSheets )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
RULE_AREA ent;
|
2024-07-30 18:08:11 +02:00
|
|
|
|
2025-06-07 17:22:08 +01:00
|
|
|
ent.m_sourceType = PLACEMENT_SOURCE_T::SHEETNAME;
|
2024-01-03 19:48:27 +01:00
|
|
|
ent.m_generateEnabled = false;
|
|
|
|
ent.m_sheetPath = sheet.first;
|
|
|
|
ent.m_sheetName = sheet.second;
|
2024-11-03 21:43:55 +00:00
|
|
|
ent.m_components = queryComponentsInSheet( ent.m_sheetPath );
|
2024-01-03 19:48:27 +01:00
|
|
|
m_areas.m_areas.push_back( ent );
|
2024-07-30 18:08:11 +02:00
|
|
|
|
|
|
|
wxLogTrace( traceMultichannelTool, wxT("found sheet '%s' @ '%s' s %d\n"),
|
2025-07-29 11:32:54 +01:00
|
|
|
ent.m_sheetName,
|
|
|
|
ent.m_sheetPath,
|
|
|
|
(int) m_areas.m_areas.size() );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
2024-11-03 21:43:55 +00:00
|
|
|
|
|
|
|
for( const wxString& compClass : uniqueComponentClasses )
|
|
|
|
{
|
|
|
|
RULE_AREA ent;
|
|
|
|
|
2025-06-07 17:22:08 +01:00
|
|
|
ent.m_sourceType = PLACEMENT_SOURCE_T::COMPONENT_CLASS;
|
2024-11-03 21:43:55 +00:00
|
|
|
ent.m_generateEnabled = false;
|
|
|
|
ent.m_componentClass = compClass;
|
|
|
|
ent.m_components = queryComponentsInComponentClass( ent.m_componentClass );
|
|
|
|
m_areas.m_areas.push_back( ent );
|
|
|
|
|
|
|
|
wxLogTrace( traceMultichannelTool, wxT( "found component class '%s' s %d\n" ),
|
2025-07-29 11:32:54 +01:00
|
|
|
ent.m_componentClass,
|
|
|
|
static_cast<int>( m_areas.m_areas.size() ) );
|
2024-11-03 21:43:55 +00:00
|
|
|
}
|
2025-05-07 09:03:28 -04:00
|
|
|
|
|
|
|
for( const wxString& groupName : uniqueGroups )
|
|
|
|
{
|
|
|
|
RULE_AREA ent;
|
|
|
|
|
2025-06-07 17:22:08 +01:00
|
|
|
ent.m_sourceType = PLACEMENT_SOURCE_T::GROUP_PLACEMENT;
|
2025-05-07 09:03:28 -04:00
|
|
|
ent.m_generateEnabled = false;
|
|
|
|
ent.m_groupName = groupName;
|
|
|
|
ent.m_components = queryComponentsInGroup( ent.m_groupName );
|
|
|
|
m_areas.m_areas.push_back( ent );
|
|
|
|
|
|
|
|
wxLogTrace( traceMultichannelTool, wxT( "found group '%s' s %d\n" ),
|
2025-07-29 11:32:54 +01:00
|
|
|
ent.m_componentClass,
|
|
|
|
static_cast<int>( m_areas.m_areas.size() ) );
|
2025-05-07 09:03:28 -04:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
void MULTICHANNEL_TOOL::FindExistingRuleAreas()
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
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;
|
2025-07-29 11:32:54 +01:00
|
|
|
|
2025-06-07 17:22:08 +01:00
|
|
|
if( !zone->GetPlacementAreaEnabled() )
|
2024-01-03 19:48:27 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
RULE_AREA area;
|
|
|
|
|
|
|
|
area.m_existsAlready = true;
|
|
|
|
area.m_area = zone;
|
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
identifyComponentsInRuleArea( zone, area.m_raFootprints );
|
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-07-30 18:08:11 +02:00
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
wxLogTrace( traceMultichannelTool, wxT("RA '%s', %d footprints\n"),
|
|
|
|
area.m_ruleName,
|
|
|
|
(int) area.m_raFootprints.size() );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
wxLogTrace( traceMultichannelTool, wxT("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-11-24 12:00:20 -05:00
|
|
|
void MULTICHANNEL_TOOL::UpdatePickedItem( const EDA_ITEM* aItem )
|
|
|
|
{
|
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::repeatLayout );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
int MULTICHANNEL_TOOL::repeatLayout( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
std::vector<ZONE*> refRAs;
|
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
auto isSelectedItemAnRA =
|
|
|
|
[]( EDA_ITEM* aItem ) -> ZONE*
|
|
|
|
{
|
|
|
|
if( !aItem || aItem->Type() != PCB_ZONE_T )
|
|
|
|
return nullptr;
|
2024-08-28 14:54:14 -04:00
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
ZONE* zone = static_cast<ZONE*>( aItem );
|
2024-08-28 14:54:14 -04:00
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
if( !zone->GetIsRuleArea() )
|
|
|
|
return nullptr;
|
2024-08-28 14:54:14 -04:00
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
if( !zone->GetPlacementAreaEnabled() )
|
|
|
|
return nullptr;
|
2024-08-28 14:54:14 -04:00
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
return zone;
|
|
|
|
};
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-08-13 00:04:27 +02:00
|
|
|
for( EDA_ITEM* item : selection() )
|
|
|
|
{
|
|
|
|
if( auto zone = isSelectedItemAnRA( item ) )
|
|
|
|
{
|
|
|
|
refRAs.push_back(zone);
|
|
|
|
}
|
|
|
|
else if ( item->Type() == PCB_GROUP_T )
|
|
|
|
{
|
|
|
|
PCB_GROUP *group = static_cast<PCB_GROUP*>( item );
|
2024-08-28 14:54:14 -04:00
|
|
|
|
2025-04-01 14:20:03 -04:00
|
|
|
for( EDA_ITEM* grpItem : group->GetItems() )
|
2024-08-13 00:04:27 +02:00
|
|
|
{
|
|
|
|
if( auto grpZone = isSelectedItemAnRA( grpItem ) )
|
|
|
|
refRAs.push_back( grpZone );
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( refRAs.size() != 1 )
|
|
|
|
{
|
2024-11-24 12:00:20 -05:00
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectItemInteractively,
|
|
|
|
PCB_PICKER_TOOL::INTERACTIVE_PARAMS {
|
|
|
|
this,
|
|
|
|
_( "Select a reference Rule Area to copy from..." ),
|
|
|
|
[&]( EDA_ITEM* aItem )
|
|
|
|
{
|
|
|
|
return isSelectedItemAnRA( aItem ) != nullptr;
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
FindExistingRuleAreas();
|
|
|
|
|
|
|
|
int status = CheckRACompatibility( refRAs.front() );
|
|
|
|
|
|
|
|
if( status < 0 )
|
|
|
|
return status;
|
|
|
|
|
2024-08-13 00:04:27 +02:00
|
|
|
if( m_areas.m_areas.size() <= 1 )
|
|
|
|
{
|
2025-07-29 11:32:54 +01:00
|
|
|
frame()->ShowInfoBarError( _( "No Rule Areas to repeat layout to have been found." ), true );
|
2024-08-13 00:04:27 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
DIALOG_MULTICHANNEL_REPEAT_LAYOUT dialog( frame(), this );
|
2024-08-28 14:54:14 -04:00
|
|
|
int ret = dialog.ShowModal();
|
2024-07-30 18:08:11 +02:00
|
|
|
|
|
|
|
if( ret != wxID_OK )
|
|
|
|
return 0;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
return RepeatLayout( aEvent, refRAs.front() );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int MULTICHANNEL_TOOL::CheckRACompatibility( ZONE *aRefZone )
|
|
|
|
{
|
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
|
|
|
{
|
2024-07-30 18:08:11 +02:00
|
|
|
if( ra.m_area == aRefZone )
|
2024-01-04 17:33:38 +01:00
|
|
|
{
|
|
|
|
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-07-30 18:08:11 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-08-28 14:54:14 -04:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
int MULTICHANNEL_TOOL::RepeatLayout( const TOOL_EVENT& aEvent, ZONE* aRefZone )
|
|
|
|
{
|
2024-01-08 22:37:56 +01:00
|
|
|
int totalCopied = 0;
|
|
|
|
|
2024-11-24 12:20:55 -05:00
|
|
|
BOARD_COMMIT commit( GetManager(), true );
|
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-07-30 18:08:11 +02:00
|
|
|
wxLogTrace( traceMultichannelTool, wxT("skipping copy to RA '%s' (disabled in dialog)\n"),
|
2024-01-09 18:45:39 +01:00
|
|
|
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
|
|
|
|
2025-04-19 16:33:08 +02:00
|
|
|
if( !copyRuleAreaContents( targetArea.second.m_matchingComponents, &commit, m_areas.m_refRA, targetArea.first,
|
|
|
|
m_areas.m_options, targetArea.second.m_affectedItems,
|
2025-03-09 21:59:29 +01:00
|
|
|
targetArea.second.m_groupableItems ) )
|
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();
|
2024-11-24 12:20:55 -05:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
if( Pgm().IsGUI() )
|
|
|
|
{
|
|
|
|
frame()->ShowInfoBarError( errMsg, true );
|
|
|
|
}
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
return -1;
|
2024-01-04 17:33:38 +01:00
|
|
|
}
|
2025-03-09 21:59:29 +01:00
|
|
|
totalCopied++;
|
2025-04-19 16:33:08 +02:00
|
|
|
wxSafeYield();
|
2025-03-09 21:59:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if( m_areas.m_options.m_groupItems )
|
|
|
|
{
|
2025-05-19 21:36:46 +01:00
|
|
|
for( const auto& [targetArea, compatData] : m_areas.m_compatMap )
|
2024-07-20 23:31:32 +02:00
|
|
|
{
|
2025-05-19 21:36:46 +01:00
|
|
|
pruneExistingGroups( commit, compatData.m_affectedItems );
|
2024-07-20 23:31:32 +02:00
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
PCB_GROUP* group = new PCB_GROUP( board() );
|
2024-07-20 23:31:32 +02:00
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
commit.Add( group );
|
2024-09-11 09:53:26 -04:00
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
for( BOARD_ITEM* item : compatData.m_groupableItems )
|
2024-07-20 23:31:32 +02:00
|
|
|
{
|
2025-05-19 21:36:46 +01:00
|
|
|
commit.Modify( item );
|
|
|
|
group->AddItem( item );
|
2024-07-20 23:31:32 +02:00
|
|
|
}
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
commit.Push( _( "Repeat layout" ) );
|
2024-11-24 12:20:55 -05:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
if( Pgm().IsGUI() )
|
2025-07-29 11:32:54 +01:00
|
|
|
frame()->ShowInfoBarMsg( wxString::Format( _( "Copied to %d Rule Areas." ), totalCopied ), true );
|
2025-05-19 21:36:46 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-08-28 14:54:14 -04:00
|
|
|
wxString MULTICHANNEL_TOOL::stripComponentIndex( const wxString& aRef ) const
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
|
|
|
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;
|
2025-07-29 11:32:54 +01:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
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
|
|
|
|
2025-04-28 21:46:04 +02:00
|
|
|
int MULTICHANNEL_TOOL::findRouting( std::set<BOARD_CONNECTED_ITEM*>& aOutput,
|
2024-01-09 18:45:39 +01:00
|
|
|
std::shared_ptr<CONNECTIVITY_DATA> aConnectivity,
|
2025-02-26 10:59:03 +00:00
|
|
|
const SHAPE_POLY_SET& aRAPoly,
|
|
|
|
RULE_AREA* aRA,
|
2024-01-09 18:45:39 +01:00
|
|
|
const REPEAT_LAYOUT_OPTIONS& aOpts ) const
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-11-24 12:10:06 -05:00
|
|
|
// The user also will consider tracks and vias that are inside the source area but
|
|
|
|
// not connected to any of the source pads to count as "routing" (e.g. stitching vias)
|
|
|
|
|
2025-04-27 07:39:09 +02:00
|
|
|
int count = 0;
|
|
|
|
|
2024-11-24 12:10:06 -05:00
|
|
|
PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER );
|
|
|
|
PCBEXPR_UCODE ucode;
|
|
|
|
PCBEXPR_CONTEXT ctx, preflightCtx;
|
|
|
|
|
|
|
|
auto reportError = [&]( const wxString& aMessage, int aOffset )
|
|
|
|
{
|
|
|
|
wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage );
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.SetErrorCallback( reportError );
|
|
|
|
preflightCtx.SetErrorCallback( reportError );
|
|
|
|
compiler.SetErrorCallback( reportError );
|
|
|
|
|
|
|
|
bool restoreBlankName = false;
|
|
|
|
|
|
|
|
if( aRA->m_area->GetZoneName().IsEmpty() )
|
|
|
|
{
|
|
|
|
restoreBlankName = true;
|
2024-11-24 18:24:09 -05:00
|
|
|
aRA->m_area->SetZoneName( aRA->m_area->m_Uuid.AsString() );
|
2024-11-24 12:10:06 -05:00
|
|
|
}
|
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
wxString ruleText = wxString::Format( wxT( "A.enclosedByArea('%s')" ), aRA->m_area->GetZoneName() );
|
2024-11-24 12:10:06 -05:00
|
|
|
|
|
|
|
auto testAndAdd =
|
2025-07-29 11:32:54 +01:00
|
|
|
[&]( BOARD_CONNECTED_ITEM* aItem )
|
|
|
|
{
|
|
|
|
if( aOutput.contains( aItem ) )
|
|
|
|
return;
|
2024-11-24 12:10:06 -05:00
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
ctx.SetItems( aItem, aItem );
|
|
|
|
LIBEVAL::VALUE* val = ucode.Run( &ctx );
|
2024-11-24 12:10:06 -05:00
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
if( val->AsDouble() != 0.0 )
|
|
|
|
{
|
|
|
|
aOutput.insert( aItem );
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
};
|
2024-11-24 12:10:06 -05:00
|
|
|
|
|
|
|
if( compiler.Compile( ruleText, &ucode, &preflightCtx ) )
|
|
|
|
{
|
|
|
|
for( PCB_TRACK* track : board()->Tracks() )
|
|
|
|
testAndAdd( track );
|
|
|
|
}
|
|
|
|
|
2024-11-24 18:24:09 -05:00
|
|
|
if( restoreBlankName )
|
|
|
|
aRA->m_area->SetZoneName( wxEmptyString );
|
|
|
|
|
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
|
|
|
|
2025-04-19 16:33:08 +02:00
|
|
|
bool MULTICHANNEL_TOOL::copyRuleAreaContents( TMATCH::COMPONENT_MATCHES& aMatches, BOARD_COMMIT* aCommit,
|
|
|
|
RULE_AREA* aRefArea, RULE_AREA* aTargetArea, REPEAT_LAYOUT_OPTIONS aOpts,
|
2024-07-20 23:31:32 +02:00
|
|
|
std::unordered_set<BOARD_ITEM*>& aAffectedItems,
|
|
|
|
std::unordered_set<BOARD_ITEM*>& aGroupableItems )
|
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
|
|
|
|
2025-04-19 16:33:08 +02:00
|
|
|
FOOTPRINT* targetAnchorFp = nullptr;
|
2024-01-03 19:48:27 +01:00
|
|
|
VECTOR2I disp = aTargetArea->m_center - aRefArea->m_center;
|
2025-04-19 16:33:08 +02:00
|
|
|
EDA_ANGLE rot = EDA_ANGLE( 0 );
|
2025-07-29 11:32:54 +01:00
|
|
|
|
2025-04-19 16:33:08 +02:00
|
|
|
if( aOpts.m_anchorFp )
|
|
|
|
{
|
|
|
|
for( auto& fpPair : aMatches )
|
|
|
|
{
|
|
|
|
if( fpPair.first->GetReference() == aOpts.m_anchorFp->GetReference() )
|
|
|
|
targetAnchorFp = fpPair.second;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( targetAnchorFp )
|
|
|
|
{
|
|
|
|
VECTOR2I oldpos = aOpts.m_anchorFp->GetPosition();
|
|
|
|
rot = EDA_ANGLE( targetAnchorFp->GetOrientationDegrees() - aOpts.m_anchorFp->GetOrientationDegrees() );
|
|
|
|
aOpts.m_anchorFp->Rotate( VECTOR2( 0, 0 ), EDA_ANGLE( rot ) );
|
|
|
|
oldpos = aOpts.m_anchorFp->GetPosition();
|
|
|
|
VECTOR2I newpos = targetAnchorFp->GetPosition();
|
|
|
|
disp = newpos - oldpos;
|
|
|
|
aOpts.m_anchorFp->Rotate( VECTOR2( 0, 0 ), EDA_ANGLE( -rot ) );
|
|
|
|
}
|
|
|
|
}
|
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 );
|
2025-04-19 16:33:08 +02:00
|
|
|
newTargetOutline.Rotate( rot, VECTOR2( 0, 0 ) );
|
2024-01-04 17:33:38 +01:00
|
|
|
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-07-20 23:31:32 +02:00
|
|
|
aAffectedItems.insert( aTargetArea->m_area );
|
|
|
|
aGroupableItems.insert( aTargetArea->m_area );
|
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
if( aOpts.m_copyRouting )
|
|
|
|
{
|
2025-04-28 21:46:04 +02:00
|
|
|
std::set<BOARD_CONNECTED_ITEM*> refRouting;
|
|
|
|
std::set<BOARD_CONNECTED_ITEM*> targetRouting;
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
wxLogTrace( traceMultichannelTool, wxT("copying routing: %d fps\n"), (int) aMatches.size() );
|
2024-01-05 15:51:54 +01:00
|
|
|
|
2025-04-27 07:40:40 +02:00
|
|
|
std::set<int> refc;
|
|
|
|
std::set<int> targc;
|
|
|
|
|
|
|
|
for( auto& fpPair : aMatches )
|
|
|
|
{
|
|
|
|
for( PAD* pad : fpPair.first->Pads() )
|
|
|
|
refc.insert( pad->GetNetCode() );
|
|
|
|
|
|
|
|
for( PAD* pad : fpPair.second->Pads() )
|
|
|
|
targc.insert( pad->GetNetCode() );
|
|
|
|
}
|
|
|
|
|
2025-04-27 07:39:09 +02:00
|
|
|
findRouting( targetRouting, connectivity, targetPoly, aTargetArea, aOpts );
|
|
|
|
findRouting( refRouting, connectivity, refPoly, aRefArea, aOpts );
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2025-04-28 21:46:04 +02:00
|
|
|
for( BOARD_CONNECTED_ITEM* item : targetRouting )
|
2024-01-09 18:45:39 +01:00
|
|
|
{
|
|
|
|
if( item->IsLocked() && !aOpts.m_includeLockedItems )
|
|
|
|
continue;
|
2025-04-28 21:46:04 +02:00
|
|
|
if( aOpts.m_connectedRoutingOnly && !targc.contains( item->GetNetCode() ) )
|
2025-04-27 07:40:40 +02:00
|
|
|
continue;
|
2024-07-30 18:08:11 +02:00
|
|
|
// item already removed
|
|
|
|
if( aCommit->GetStatus( item ) != 0 )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if( aTargetArea->m_area->GetLayerSet().Contains( item->GetLayer() ) )
|
2024-07-20 23:31:32 +02:00
|
|
|
{
|
|
|
|
aAffectedItems.insert( item );
|
2024-07-30 18:08:11 +02:00
|
|
|
aCommit->Remove( item );
|
2024-07-20 23:31:32 +02:00
|
|
|
}
|
2024-01-09 18:45:39 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2025-04-28 21:46:04 +02:00
|
|
|
for( BOARD_CONNECTED_ITEM* item : refRouting )
|
2024-01-09 18:45:39 +01:00
|
|
|
{
|
2025-04-27 07:40:40 +02:00
|
|
|
if( item->IsLocked() && !aOpts.m_includeLockedItems )
|
|
|
|
continue;
|
2025-04-28 21:46:04 +02:00
|
|
|
if( aOpts.m_connectedRoutingOnly && !refc.contains( item->GetNetCode() ) )
|
2025-04-27 07:40:40 +02:00
|
|
|
continue;
|
2024-07-30 18:08:11 +02:00
|
|
|
if( !aRefArea->m_area->GetLayerSet().Contains( item->GetLayer() ) )
|
|
|
|
continue;
|
2024-11-24 11:29:17 -05:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
if( !aTargetArea->m_area->GetLayerSet().Contains( item->GetLayer() ) )
|
|
|
|
continue;
|
|
|
|
|
2025-04-28 21:46:04 +02:00
|
|
|
BOARD_CONNECTED_ITEM* copied = static_cast<BOARD_CONNECTED_ITEM*>( item->Clone() );
|
|
|
|
|
|
|
|
fixupNet( item, copied, aMatches );
|
2024-07-30 18:08:11 +02:00
|
|
|
|
2025-04-19 16:33:08 +02:00
|
|
|
copied->Rotate( VECTOR2( 0, 0 ), rot );
|
2024-01-09 18:45:39 +01:00
|
|
|
copied->Move( disp );
|
2024-11-24 11:29:17 -05:00
|
|
|
copied->SetParentGroup( nullptr );
|
2025-04-16 02:00:42 -04:00
|
|
|
const_cast<KIID&>( copied->m_Uuid ) = KIID();
|
2024-07-20 23:31:32 +02:00
|
|
|
aGroupableItems.insert( copied );
|
2024-01-09 18:45:39 +01:00
|
|
|
aCommit->Add( copied );
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
2024-11-24 11:29:17 -05:00
|
|
|
if( aOpts.m_copyOtherItems )
|
|
|
|
{
|
|
|
|
std::set<BOARD_ITEM*> sourceItems;
|
2024-12-30 14:40:47 +00:00
|
|
|
std::set<BOARD_ITEM*> targetItems;
|
2024-11-24 11:29:17 -05:00
|
|
|
|
|
|
|
findOtherItemsInRuleArea( aRefArea->m_area, sourceItems );
|
2024-12-30 14:40:47 +00:00
|
|
|
findOtherItemsInRuleArea( aTargetArea->m_area, targetItems );
|
2024-11-24 11:29:17 -05:00
|
|
|
|
2024-12-30 14:40:47 +00:00
|
|
|
for( BOARD_ITEM* item : targetItems )
|
2024-11-24 11:29:17 -05:00
|
|
|
{
|
2025-07-29 11:32:54 +01:00
|
|
|
if( item->Type() == PCB_TEXT_T && item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T )
|
2024-11-24 11:29:17 -05:00
|
|
|
continue;
|
|
|
|
|
2024-12-30 14:40:47 +00:00
|
|
|
if( item->IsLocked() && !aOpts.m_includeLockedItems )
|
2024-11-24 11:29:17 -05:00
|
|
|
continue;
|
|
|
|
|
2024-12-30 14:40:47 +00:00
|
|
|
// item already removed
|
|
|
|
if( aCommit->GetStatus( item ) != 0 )
|
2024-11-24 11:29:17 -05:00
|
|
|
continue;
|
|
|
|
|
2024-12-30 14:40:47 +00:00
|
|
|
if( item->Type() != PCB_ZONE_T )
|
2024-11-24 11:29:17 -05:00
|
|
|
{
|
2024-12-30 14:40:47 +00:00
|
|
|
if( aTargetArea->m_area->GetLayerSet().Contains( item->GetLayer() ) )
|
|
|
|
{
|
|
|
|
aAffectedItems.insert( item );
|
|
|
|
aCommit->Remove( item );
|
|
|
|
}
|
2024-11-24 11:29:17 -05:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-12-30 14:40:47 +00:00
|
|
|
ZONE* zone = static_cast<ZONE*>( item );
|
|
|
|
|
|
|
|
// Check all zone layers are included in the rule area
|
|
|
|
bool layerMismatch = false;
|
|
|
|
LSET zoneLayers = zone->GetLayerSet();
|
|
|
|
|
|
|
|
for( const PCB_LAYER_ID& layer : zoneLayers )
|
|
|
|
{
|
|
|
|
if( !aTargetArea->m_area->GetLayerSet().Contains( layer ) )
|
|
|
|
layerMismatch = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !layerMismatch )
|
|
|
|
{
|
|
|
|
aAffectedItems.insert( zone );
|
|
|
|
aCommit->Remove( zone );
|
|
|
|
}
|
2024-11-24 11:29:17 -05:00
|
|
|
}
|
2024-12-30 14:40:47 +00:00
|
|
|
}
|
2024-11-24 11:29:17 -05:00
|
|
|
|
2024-12-30 14:40:47 +00:00
|
|
|
for( BOARD_ITEM* item : sourceItems )
|
|
|
|
{
|
2025-07-29 11:32:54 +01:00
|
|
|
if( item->Type() == PCB_TEXT_T && item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T )
|
2024-12-30 14:40:47 +00:00
|
|
|
continue;
|
|
|
|
|
2025-04-27 07:40:40 +02:00
|
|
|
if( item->IsLocked() && !aOpts.m_includeLockedItems )
|
|
|
|
continue;
|
|
|
|
|
2024-12-30 14:40:47 +00:00
|
|
|
BOARD_ITEM* copied = nullptr;
|
|
|
|
|
|
|
|
if( item->Type() != PCB_ZONE_T )
|
|
|
|
{
|
|
|
|
if( !aRefArea->m_area->GetLayerSet().Contains( item->GetLayer() ) )
|
|
|
|
continue;
|
2025-07-29 11:32:54 +01:00
|
|
|
|
2024-12-30 14:40:47 +00:00
|
|
|
if( !aTargetArea->m_area->GetLayerSet().Contains( item->GetLayer() ) )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if( item->Type() == PCB_GROUP_T )
|
|
|
|
copied = static_cast<PCB_GROUP*>( item )->DeepClone();
|
|
|
|
else
|
|
|
|
copied = static_cast<BOARD_ITEM*>( item->Clone() );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ZONE* zone = static_cast<ZONE*>( item );
|
|
|
|
|
|
|
|
// Check all zone layers are included in the rule area
|
|
|
|
bool layerMismatch = false;
|
|
|
|
LSET zoneLayers = zone->GetLayerSet();
|
2024-11-24 11:29:17 -05:00
|
|
|
|
2024-12-30 14:40:47 +00:00
|
|
|
for( const PCB_LAYER_ID& layer : zoneLayers )
|
2024-11-24 11:29:17 -05:00
|
|
|
{
|
2024-12-30 14:40:47 +00:00
|
|
|
if( !aRefArea->m_area->GetLayerSet().Contains( layer )
|
|
|
|
|| !aTargetArea->m_area->GetLayerSet().Contains( layer ) )
|
|
|
|
{
|
|
|
|
layerMismatch = true;
|
|
|
|
}
|
|
|
|
}
|
2024-11-24 11:29:17 -05:00
|
|
|
|
2024-12-30 14:40:47 +00:00
|
|
|
if( layerMismatch )
|
|
|
|
continue;
|
2024-11-24 11:29:17 -05:00
|
|
|
|
2024-12-30 14:40:47 +00:00
|
|
|
ZONE* targetZone = static_cast<ZONE*>( item->Clone() );
|
2025-03-29 07:16:28 -04:00
|
|
|
fixupNet( zone, targetZone, aMatches );
|
2024-12-30 14:40:47 +00:00
|
|
|
|
|
|
|
copied = targetZone;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( copied )
|
|
|
|
{
|
|
|
|
copied->ClearFlags();
|
|
|
|
copied->SetParentGroup( nullptr );
|
2025-04-16 02:00:42 -04:00
|
|
|
const_cast<KIID&>( copied->m_Uuid ) = KIID();
|
2025-04-19 16:33:08 +02:00
|
|
|
copied->Rotate( VECTOR2( 0, 0 ), rot );
|
2024-12-30 14:40:47 +00:00
|
|
|
copied->Move( disp );
|
|
|
|
aGroupableItems.insert( copied );
|
|
|
|
aCommit->Add( copied );
|
|
|
|
}
|
2024-11-24 11:29:17 -05: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-07-15 23:33:40 +02:00
|
|
|
if( !aRefArea->m_area->GetLayerSet().Contains( refFP->GetLayer() ) )
|
|
|
|
{
|
|
|
|
wxLogTrace( traceMultichannelTool, wxT( "discard ref:%s (ref layer)\n" ),
|
|
|
|
refFP->GetReference() );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if( !aTargetArea->m_area->GetLayerSet().Contains( refFP->GetLayer() ) )
|
|
|
|
{
|
|
|
|
wxLogTrace( traceMultichannelTool, wxT( "discard ref:%s (target layer)\n" ),
|
|
|
|
refFP->GetReference() );
|
|
|
|
continue;
|
|
|
|
}
|
2024-01-08 22:37:56 +01:00
|
|
|
|
2025-01-10 11:23:56 +00:00
|
|
|
// Ignore footprints outside of the rule area
|
|
|
|
if( !refFP->GetEffectiveShape( refFP->GetLayer() )->Collide( &refPoly, 0 ) )
|
|
|
|
continue;
|
|
|
|
|
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() );
|
2025-04-19 16:33:08 +02:00
|
|
|
targetFP->SetPosition( refFP->GetPosition() );
|
|
|
|
targetFP->Rotate( VECTOR2( 0, 0 ), rot );
|
|
|
|
targetFP->Move( disp );
|
2024-12-28 20:40:57 +00:00
|
|
|
for( PCB_FIELD* refField : refFP->GetFields() )
|
2024-11-24 10:24:58 -05:00
|
|
|
{
|
|
|
|
if( !refField->IsVisible() )
|
|
|
|
continue;
|
|
|
|
|
2025-02-08 13:45:57 +00:00
|
|
|
PCB_FIELD* targetField = targetFP->GetField( refField->GetName() );
|
2024-11-24 10:24:58 -05:00
|
|
|
wxCHECK2( targetField, continue );
|
|
|
|
|
|
|
|
targetField->SetAttributes( refField->GetAttributes() );
|
2025-04-19 16:33:08 +02:00
|
|
|
targetField->SetPosition( refField->GetPosition() );
|
|
|
|
targetField->Rotate( VECTOR2( 0, 0 ), rot );
|
|
|
|
targetField->Move( disp );
|
2024-11-24 10:24:58 -05:00
|
|
|
targetField->SetIsKnockout( refField->IsKnockout() );
|
|
|
|
}
|
2024-07-20 23:31:32 +02:00
|
|
|
|
|
|
|
aAffectedItems.insert( targetFP );
|
|
|
|
aGroupableItems.insert( targetFP );
|
2024-01-09 18:45:39 +01:00
|
|
|
}
|
2024-01-04 17:33:38 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2025-04-19 16:33:08 +02:00
|
|
|
aTargetArea->m_area->RemoveAllContours();
|
|
|
|
aTargetArea->m_area->AddPolygon( newTargetOutline );
|
|
|
|
aTargetArea->m_area->UnHatchBorder();
|
|
|
|
aTargetArea->m_area->HatchBorder();
|
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-12-30 14:40:47 +00:00
|
|
|
/**
|
2025-04-28 21:46:04 +02:00
|
|
|
* @brief Attempts to make sure copied items are assigned the right net
|
2024-12-30 14:40:47 +00:00
|
|
|
*
|
|
|
|
*/
|
2025-03-29 07:16:28 -04:00
|
|
|
void MULTICHANNEL_TOOL::fixupNet( BOARD_CONNECTED_ITEM* aRef, BOARD_CONNECTED_ITEM* aTarget,
|
|
|
|
TMATCH::COMPONENT_MATCHES& aComponentMatches )
|
2024-12-30 14:40:47 +00:00
|
|
|
{
|
|
|
|
auto connectivity = board()->GetConnectivity();
|
2025-07-29 11:32:54 +01:00
|
|
|
const std::vector<BOARD_CONNECTED_ITEM*> refConnectedPads = connectivity->GetNetItems( aRef->GetNetCode(),
|
|
|
|
{ PCB_PAD_T } );
|
2024-12-30 14:40:47 +00:00
|
|
|
|
2025-03-29 07:16:28 -04:00
|
|
|
for( const BOARD_CONNECTED_ITEM* refConItem : refConnectedPads )
|
2024-12-30 14:40:47 +00:00
|
|
|
{
|
|
|
|
if( refConItem->Type() != PCB_PAD_T )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const PAD* refPad = static_cast<const PAD*>( refConItem );
|
|
|
|
FOOTPRINT* sourceFootprint = refPad->GetParentFootprint();
|
|
|
|
|
|
|
|
if( aComponentMatches.contains( sourceFootprint ) )
|
|
|
|
{
|
|
|
|
const FOOTPRINT* targetFootprint = aComponentMatches[sourceFootprint];
|
|
|
|
std::vector<const PAD*> targetFpPads = targetFootprint->GetPads( refPad->GetNumber() );
|
|
|
|
|
|
|
|
if( !targetFpPads.empty() )
|
|
|
|
{
|
|
|
|
int targetNetCode = targetFpPads[0]->GetNet()->GetNetCode();
|
2025-03-29 07:16:28 -04:00
|
|
|
aTarget->SetNetCode( targetNetCode );
|
2024-12-30 14:40:47 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-08-28 14:54:14 -04: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-07-30 18:08:11 +02:00
|
|
|
using namespace TMATCH;
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
std::unique_ptr<CONNECTION_GRAPH> cgRef( CONNECTION_GRAPH::BuildFromFootprintSet( aRefArea->m_raFootprints ) );
|
|
|
|
std::unique_ptr<CONNECTION_GRAPH> cgTarget( CONNECTION_GRAPH::BuildFromFootprintSet( aTargetArea->m_raFootprints ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
auto status = cgRef->FindIsomorphism( cgTarget.get(), aMatches.m_matchingComponents );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
switch( status )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-07-30 18:08:11 +02:00
|
|
|
case CONNECTION_GRAPH::ST_OK:
|
2024-01-08 22:37:56 +01:00
|
|
|
aMatches.m_isOk = true;
|
2024-07-30 18:08:11 +02:00
|
|
|
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;
|
2024-01-08 22:37:56 +01:00
|
|
|
}
|
2024-01-04 17:33:38 +01:00
|
|
|
|
2024-09-11 09:53:26 -04:00
|
|
|
return ( status == TMATCH::CONNECTION_GRAPH::ST_OK );
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
|
2024-07-20 23:31:32 +02:00
|
|
|
|
2024-08-28 14:54:14 -04:00
|
|
|
bool MULTICHANNEL_TOOL::pruneExistingGroups( COMMIT& aCommit,
|
|
|
|
const std::unordered_set<BOARD_ITEM*>& aItemsToRemove )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2025-05-19 21:36:46 +01:00
|
|
|
// Note: groups are only collections, not "real" hierarchy. A group's members are still parented
|
|
|
|
// by the board (and therefore nested groups are still in the board's list of groups).
|
|
|
|
for( PCB_GROUP* group : board()->Groups() )
|
2024-07-15 23:33:40 +02:00
|
|
|
{
|
2025-05-19 21:36:46 +01:00
|
|
|
std::vector<EDA_ITEM*> pruneList;
|
2024-07-20 23:31:32 +02:00
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
for( EDA_ITEM* refItem : group->GetItems() )
|
2024-07-20 23:31:32 +02:00
|
|
|
{
|
2024-08-28 14:54:14 -04:00
|
|
|
for( BOARD_ITEM* testItem : aItemsToRemove )
|
2024-07-20 23:31:32 +02:00
|
|
|
{
|
|
|
|
if( refItem->m_Uuid == testItem->m_Uuid )
|
2025-05-19 21:36:46 +01:00
|
|
|
pruneList.push_back( refItem );
|
2024-07-20 23:31:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
if( !pruneList.empty() )
|
2024-07-20 23:31:32 +02:00
|
|
|
{
|
2025-05-19 21:36:46 +01:00
|
|
|
aCommit.Modify( group );
|
|
|
|
|
|
|
|
for( EDA_ITEM* item : pruneList )
|
|
|
|
group->RemoveItem( item );
|
2024-07-20 23:31:32 +02:00
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
if( group->GetItems().empty() )
|
|
|
|
aCommit.Remove( group );
|
|
|
|
}
|
2024-07-15 23:33:40 +02:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-07-15 23:33:40 +02:00
|
|
|
return false;
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-08-28 14:54:14 -04:00
|
|
|
|
2024-07-15 23:33:40 +02:00
|
|
|
int MULTICHANNEL_TOOL::AutogenerateRuleAreas( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
2024-07-30 18:08:11 +02:00
|
|
|
if( Pgm().IsGUI() )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-11-03 21:43:55 +00:00
|
|
|
QuerySheetsAndComponentClasses();
|
2024-07-30 18:08:11 +02:00
|
|
|
|
|
|
|
if( m_areas.m_areas.size() <= 1 )
|
|
|
|
{
|
|
|
|
frame()->ShowInfoBarError( _( "Cannot auto-generate any placement areas because the "
|
2024-11-03 21:43:55 +00:00
|
|
|
"schematic has only one or no hierarchical sheet(s) or "
|
|
|
|
"component classes." ),
|
|
|
|
true );
|
2024-07-30 18:08:11 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
DIALOG_MULTICHANNEL_GENERATE_RULE_AREAS dialog( frame(), this );
|
2024-08-28 14:54:14 -04:00
|
|
|
int ret = dialog.ShowModal();
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
if( ret != wxID_OK )
|
|
|
|
return 0;
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
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;
|
2025-07-29 11:32:54 +01:00
|
|
|
|
2025-06-07 17:22:08 +01:00
|
|
|
if( !zone->GetPlacementAreaEnabled() )
|
2024-01-03 19:48:27 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
std::set<FOOTPRINT*> components;
|
|
|
|
identifyComponentsInRuleArea( zone, components );
|
|
|
|
|
|
|
|
if( components.empty() )
|
|
|
|
continue;
|
|
|
|
|
2024-08-28 14:54:14 -04:00
|
|
|
for( RULE_AREA& ra : m_areas.m_areas )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-11-03 21:43:55 +00:00
|
|
|
if( components == ra.m_components )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2025-06-07 17:22:08 +01:00
|
|
|
if( zone->GetPlacementAreaSourceType() == PLACEMENT_SOURCE_T::SHEETNAME )
|
2024-11-03 21:43:55 +00:00
|
|
|
{
|
2025-06-07 17:22:08 +01:00
|
|
|
wxLogTrace( traceMultichannelTool,
|
|
|
|
wxT( "Placement rule area for sheet '%s' already exists as '%s'\n" ),
|
|
|
|
ra.m_sheetPath, zone->GetZoneName() );
|
2024-11-03 21:43:55 +00:00
|
|
|
}
|
2025-06-07 17:22:08 +01:00
|
|
|
else if( zone->GetPlacementAreaSourceType() == PLACEMENT_SOURCE_T::COMPONENT_CLASS )
|
2024-11-03 21:43:55 +00:00
|
|
|
{
|
|
|
|
wxLogTrace( traceMultichannelTool,
|
2025-06-07 17:22:08 +01:00
|
|
|
wxT( "Placement rule area for component class '%s' already exists as '%s'\n" ),
|
2024-11-03 21:43:55 +00:00
|
|
|
ra.m_componentClass, zone->GetZoneName() );
|
|
|
|
}
|
2025-05-07 09:03:28 -04:00
|
|
|
else
|
|
|
|
{
|
|
|
|
wxLogTrace( traceMultichannelTool,
|
2025-06-07 17:22:08 +01:00
|
|
|
wxT( "Placement rule area for group '%s' already exists as '%s'\n" ),
|
2025-05-07 09:03:28 -04:00
|
|
|
ra.m_groupName, zone->GetZoneName() );
|
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-07-20 23:31:32 +02:00
|
|
|
ra.m_oldArea = zone;
|
2024-01-03 19:48:27 +01:00
|
|
|
ra.m_existsAlready = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-07 17:22:08 +01:00
|
|
|
wxLogTrace( traceMultichannelTool, wxT( "%d placement areas found\n" ), (int) m_areas.m_areas.size() );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-07-30 18:08:11 +02:00
|
|
|
BOARD_COMMIT commit( GetManager(), 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-08-28 14:54:14 -04:00
|
|
|
|
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;
|
|
|
|
|
2025-05-07 14:10:22 -04:00
|
|
|
if( ra.m_components.empty() )
|
|
|
|
continue;
|
|
|
|
|
2024-08-28 14:54:14 -04:00
|
|
|
SHAPE_LINE_CHAIN raOutline = buildRAOutline( ra.m_components, 100000 );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2024-01-09 18:45:39 +01:00
|
|
|
std::unique_ptr<ZONE> newZone( new ZONE( board() ) );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2025-06-07 17:22:08 +01:00
|
|
|
if( ra.m_sourceType == PLACEMENT_SOURCE_T::SHEETNAME )
|
|
|
|
newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_sheetPath ) );
|
|
|
|
else if( ra.m_sourceType == PLACEMENT_SOURCE_T::COMPONENT_CLASS )
|
|
|
|
newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_componentClass ) );
|
2025-05-07 09:03:28 -04:00
|
|
|
else
|
|
|
|
newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_groupName ) );
|
2024-07-15 23:33:40 +02:00
|
|
|
|
2024-11-03 21:43:55 +00:00
|
|
|
wxLogTrace( traceMultichannelTool, wxT( "Generated rule area '%s' (%d components)\n" ),
|
2025-07-29 11:32:54 +01:00
|
|
|
newZone->GetZoneName(),
|
|
|
|
(int) ra.m_components.size() );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
|
|
|
newZone->SetIsRuleArea( true );
|
|
|
|
newZone->SetLayerSet( LSET::AllCuMask() );
|
2025-06-07 17:22:08 +01:00
|
|
|
newZone->SetPlacementAreaEnabled( true );
|
2025-03-25 12:02:25 +00:00
|
|
|
newZone->SetDoNotAllowZoneFills( false );
|
2024-10-09 22:43:40 +01:00
|
|
|
newZone->SetDoNotAllowVias( false );
|
|
|
|
newZone->SetDoNotAllowTracks( false );
|
|
|
|
newZone->SetDoNotAllowPads( false );
|
|
|
|
newZone->SetDoNotAllowFootprints( false );
|
2024-11-03 21:43:55 +00:00
|
|
|
|
2025-06-07 17:22:08 +01:00
|
|
|
if( ra.m_sourceType == PLACEMENT_SOURCE_T::SHEETNAME )
|
2024-11-03 21:43:55 +00:00
|
|
|
{
|
2025-06-07 17:22:08 +01:00
|
|
|
newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::SHEETNAME );
|
|
|
|
newZone->SetPlacementAreaSource( ra.m_sheetPath );
|
2024-11-03 21:43:55 +00:00
|
|
|
}
|
2025-06-07 17:22:08 +01:00
|
|
|
else if( ra.m_sourceType == PLACEMENT_SOURCE_T::COMPONENT_CLASS )
|
2024-11-03 21:43:55 +00:00
|
|
|
{
|
2025-06-07 17:22:08 +01:00
|
|
|
newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::COMPONENT_CLASS );
|
|
|
|
newZone->SetPlacementAreaSource( ra.m_componentClass );
|
2024-11-03 21:43:55 +00:00
|
|
|
}
|
2025-05-07 09:03:28 -04:00
|
|
|
else
|
|
|
|
{
|
2025-06-07 17:22:08 +01:00
|
|
|
newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::GROUP_PLACEMENT );
|
|
|
|
newZone->SetPlacementAreaSource( ra.m_groupName );
|
2025-05-07 09:03:28 -04:00
|
|
|
}
|
2024-11-03 21:43:55 +00:00
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
newZone->AddPolygon( raOutline );
|
|
|
|
newZone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH );
|
2024-07-20 23:31:32 +02:00
|
|
|
|
|
|
|
if( ra.m_existsAlready )
|
|
|
|
commit.Remove( ra.m_oldArea );
|
2024-07-15 23:33:40 +02:00
|
|
|
|
2025-06-02 13:17:08 +01:00
|
|
|
ra.m_area = newZone.release();
|
|
|
|
commit.Add( ra.m_area );
|
2024-07-15 23:33:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// fixme: handle corner cases where the items belonging to a Rule Area already
|
|
|
|
// belong to other groups.
|
|
|
|
|
2024-11-01 21:24:07 +01:00
|
|
|
if( m_areas.m_options.m_groupItems )
|
2024-07-15 23:33:40 +02:00
|
|
|
{
|
|
|
|
for( RULE_AREA& ra : m_areas.m_areas )
|
2024-01-03 19:48:27 +01:00
|
|
|
{
|
2024-07-15 23:33:40 +02:00
|
|
|
if( !ra.m_generateEnabled )
|
|
|
|
continue;
|
2024-08-28 14:54:14 -04:00
|
|
|
|
2024-07-15 23:33:40 +02:00
|
|
|
if( ra.m_existsAlready && !m_areas.m_replaceExisting )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
std::unordered_set<BOARD_ITEM*> toPrune;
|
|
|
|
|
2025-07-29 11:32:54 +01:00
|
|
|
std::copy( ra.m_components.begin(), ra.m_components.end(), std::inserter( toPrune, toPrune.begin() ) );
|
2024-07-15 23:33:40 +02:00
|
|
|
|
2024-07-20 23:31:32 +02:00
|
|
|
if( ra.m_existsAlready )
|
|
|
|
toPrune.insert( ra.m_area );
|
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
pruneExistingGroups( commit, toPrune );
|
|
|
|
|
|
|
|
PCB_GROUP* group = new PCB_GROUP( board() );
|
2024-01-05 15:51:54 +01:00
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
commit.Add( group );
|
2024-01-03 19:48:27 +01:00
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
commit.Modify( ra.m_area );
|
|
|
|
group->AddItem( ra.m_area );
|
2024-01-09 18:45:39 +01:00
|
|
|
|
2024-08-28 14:54:14 -04:00
|
|
|
for( FOOTPRINT* fp : ra.m_components )
|
2024-01-05 15:51:54 +01:00
|
|
|
{
|
2025-05-19 21:36:46 +01:00
|
|
|
commit.Modify( fp );
|
|
|
|
group->AddItem( fp );
|
2024-01-05 15:51:54 +01:00
|
|
|
}
|
2024-01-03 19:48:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
commit.Push( _( "Auto-generate placement rule areas" ) );
|
|
|
|
|
2024-01-03 19:48:27 +01:00
|
|
|
return true;
|
|
|
|
}
|