ADDED: Pack and Move Footprints, improved footprint spread algorithm.

This commit is contained in:
Alex 2022-09-27 16:30:17 +03:00 committed by Jon Evans
parent ec6cb49570
commit 4095172259
16 changed files with 731 additions and 268 deletions

View File

@ -406,6 +406,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
aBitmapInfoCache[BITMAPS::options_schematic].emplace_back( BITMAPS::options_schematic, wxT( "options_schematic_24.png" ), 24, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::options_schematic].emplace_back( BITMAPS::options_schematic, wxT( "options_schematic_24.png" ), 24, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::opt_show_polygon].emplace_back( BITMAPS::opt_show_polygon, wxT( "opt_show_polygon_24.png" ), 24, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::opt_show_polygon].emplace_back( BITMAPS::opt_show_polygon, wxT( "opt_show_polygon_24.png" ), 24, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::ortho].emplace_back( BITMAPS::ortho, wxT( "ortho_24.png" ), 24, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::ortho].emplace_back( BITMAPS::ortho, wxT( "ortho_24.png" ), 24, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::pack_footprints].emplace_back( BITMAPS::pack_footprints, wxT( "pack_footprints_24.png" ), 24, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::pad_sketch].emplace_back( BITMAPS::pad_sketch, wxT( "pad_sketch_24.png" ), 24, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::pad_sketch].emplace_back( BITMAPS::pad_sketch, wxT( "pad_sketch_24.png" ), 24, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::pad].emplace_back( BITMAPS::pad, wxT( "pad_24.png" ), 24, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::pad].emplace_back( BITMAPS::pad, wxT( "pad_24.png" ), 24, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::pad_enumerate].emplace_back( BITMAPS::pad_enumerate, wxT( "pad_enumerate_24.png" ), 24, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::pad_enumerate].emplace_back( BITMAPS::pad_enumerate, wxT( "pad_enumerate_24.png" ), 24, wxT( "light" ) );
@ -786,6 +787,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
aBitmapInfoCache[BITMAPS::options_schematic].emplace_back( BITMAPS::options_schematic, wxT( "options_schematic_dark_24.png" ), 24, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::options_schematic].emplace_back( BITMAPS::options_schematic, wxT( "options_schematic_dark_24.png" ), 24, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::opt_show_polygon].emplace_back( BITMAPS::opt_show_polygon, wxT( "opt_show_polygon_dark_24.png" ), 24, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::opt_show_polygon].emplace_back( BITMAPS::opt_show_polygon, wxT( "opt_show_polygon_dark_24.png" ), 24, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::ortho].emplace_back( BITMAPS::ortho, wxT( "ortho_dark_24.png" ), 24, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::ortho].emplace_back( BITMAPS::ortho, wxT( "ortho_dark_24.png" ), 24, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::pack_footprints].emplace_back( BITMAPS::pack_footprints, wxT( "pack_footprints_dark_24.png" ), 24, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::pad_sketch].emplace_back( BITMAPS::pad_sketch, wxT( "pad_sketch_dark_24.png" ), 24, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::pad_sketch].emplace_back( BITMAPS::pad_sketch, wxT( "pad_sketch_dark_24.png" ), 24, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::pad].emplace_back( BITMAPS::pad, wxT( "pad_dark_24.png" ), 24, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::pad].emplace_back( BITMAPS::pad, wxT( "pad_dark_24.png" ), 24, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::pad_enumerate].emplace_back( BITMAPS::pad_enumerate, wxT( "pad_enumerate_dark_24.png" ), 24, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::pad_enumerate].emplace_back( BITMAPS::pad_enumerate, wxT( "pad_enumerate_dark_24.png" ), 24, wxT( "dark" ) );

View File

@ -373,6 +373,7 @@ enum class BITMAPS : unsigned int
options_pad, options_pad,
options_schematic, options_schematic,
ortho, ortho,
pack_footprints,
pad, pad,
pad_enumerate, pad_enumerate,
pad_number, pad_number,

View File

@ -277,7 +277,6 @@ set( PCBNEW_CLASS_SRCS
${PCBNEW_NETLIST_SRCS} ${PCBNEW_NETLIST_SRCS}
${PCBNEW_BRDSTACKUP_MGR} ${PCBNEW_BRDSTACKUP_MGR}
autorouter/rect_placement/rect_placement.cpp
autorouter/spread_footprints.cpp autorouter/spread_footprints.cpp
autorouter/ar_autoplacer.cpp autorouter/ar_autoplacer.cpp
autorouter/ar_matrix.cpp autorouter/ar_matrix.cpp
@ -639,6 +638,7 @@ target_link_libraries( pcbnew
gal gal
scripting scripting
nlohmann_json nlohmann_json
rectpack2d
${wxWidgets_LIBRARIES} ${wxWidgets_LIBRARIES}
) )
@ -673,6 +673,7 @@ target_link_libraries( pcbnew_kiface_objects
nanosvg nanosvg
tinyspline_lib tinyspline_lib
nlohmann_json nlohmann_json
rectpack2d
) )
target_include_directories( pcbnew_kiface_objects PRIVATE target_include_directories( pcbnew_kiface_objects PRIVATE

View File

@ -5,7 +5,7 @@
* Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com> * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 2013 Wayne Stambaugh <stambaughw@verizon.net> * Copyright (C) 2013 Wayne Stambaugh <stambaughw@verizon.net>
* *
* Copyright (C) 1992-2019 KiCad Developers, see AUTHORS.txt for contributors. * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -34,309 +34,291 @@
* their selection. * their selection.
*/ */
#include <spread_footprints.h>
#include <algorithm> #include <algorithm>
#include <refdes_utils.h>
#include <string_utils.h>
#include <confirm.h> #include <confirm.h>
#include <pcb_edit_frame.h> #include <pcb_edit_frame.h>
#include <board.h> #include <board.h>
#include <footprint.h> #include <rectpack2d/finders_interface.h>
#include <rect_placement/rect_placement.h>
struct TSubRect : public CRectPlacement::TRect
{
int n; // Original index of this subrect, before sorting
TSubRect() : TRect(), constexpr bool allow_flip = true;
n( 0 )
{
}
TSubRect( int _w, int _h, int _n ) : using spaces_type = rectpack2D::empty_spaces<allow_flip, rectpack2D::default_empty_spaces>;
TRect( 0, 0, _w, _h ), n( _n ) { } using rect_type = rectpack2D::output_rect_t<spaces_type>;
}; using rect_ptr = rect_type*;
using rect_vector = std::vector<rect_type>;
typedef std::vector<TSubRect> CSubRectArray;
// Use 0.01 mm units to calculate placement, to avoid long calculation time // Use 0.01 mm units to calculate placement, to avoid long calculation time
const int scale = (int) ( 0.01 * pcbIUScale.IU_PER_MM ); const int scale = (int) ( 0.01 * pcbIUScale.IU_PER_MM );
const int PADDING = (int) ( 1 * pcbIUScale.IU_PER_MM );
// Populates a list of rectangles, from a list of footprints static bool compareFootprintsbyRef( FOOTPRINT* ref, FOOTPRINT* compare )
void fillRectList( CSubRectArray& vecSubRects, std::vector <FOOTPRINT*>& aFootprintList )
{ {
vecSubRects.clear(); const wxString& refPrefix = UTIL::GetRefDesPrefix( ref->GetReference() );
const wxString& cmpPrefix = UTIL::GetRefDesPrefix( compare->GetReference() );
for( unsigned ii = 0; ii < aFootprintList.size(); ii++ ) if( refPrefix != cmpPrefix )
{ {
BOX2I fpBox = aFootprintList[ii]->GetBoundingBox( false, false ); return refPrefix < cmpPrefix;
TSubRect fpRect( ( fpBox.GetWidth() + PADDING ) / scale,
( fpBox.GetHeight() + PADDING ) / scale, ii );
vecSubRects.push_back( fpRect );
} }
else
{
const int refInt = GetTrailingInt( ref->GetReference() );
const int cmpInt = GetTrailingInt( compare->GetReference() );
return refInt < cmpInt;
} }
// Populates a list of rectangles, from a list of BOX2I return false;
void fillRectList( CSubRectArray& vecSubRects, std::vector<BOX2I>& aRectList )
{
vecSubRects.clear();
for( unsigned ii = 0; ii < aRectList.size(); ii++ )
{
BOX2I& rect = aRectList[ii];
TSubRect fpRect( rect.GetWidth()/scale, rect.GetHeight()/scale, ii );
vecSubRects.push_back( fpRect );
} }
}
// Spread a list of rectangles inside a placement area // Spread a list of rectangles inside a placement area
void spreadRectangles( CRectPlacement& aPlacementArea, rectpack2D::rect_wh spreadRectangles( rect_vector& vecSubRects, int areaSizeX, int areaSizeY )
CSubRectArray& vecSubRects,
int areaSizeX, int areaSizeY )
{ {
areaSizeX /= scale; areaSizeX /= scale;
areaSizeY /= scale; areaSizeY /= scale;
// Sort the subRects based on dimensions, larger dimension goes first. rectpack2D::rect_wh result;
std::sort( vecSubRects.begin(), vecSubRects.end(), CRectPlacement::TRect::Greater );
// gives the initial size to the area int max_side = std::max( areaSizeX, areaSizeY );
aPlacementArea.Init( areaSizeX, areaSizeY );
// Add all subrects while( true )
CSubRectArray::iterator it;
for( it = vecSubRects.begin(); it != vecSubRects.end(); )
{ {
CRectPlacement::TRect r( 0, 0, it->w, it->h ); bool anyUnsuccessful = false;
const int discard_step = 1;
bool bPlaced = aPlacementArea.AddAtEmptySpotAutoGrow( &r, areaSizeX, areaSizeY ); auto report_successful = [&]( rect_type& )
if( !bPlaced ) // No room to place the rectangle: enlarge area and retry
{ {
bool retry = false; return rectpack2D::callback_result::CONTINUE_PACKING;
};
if( areaSizeX < INT_MAX/2 ) auto report_unsuccessful = [&]( rect_type& r )
{ {
retry = true; anyUnsuccessful = true;
areaSizeX = areaSizeX * 1.2; return rectpack2D::callback_result::ABORT_PACKING;
} };
if( areaSizeX < INT_MAX/2 ) result = rectpack2D::find_best_packing<spaces_type>(
{ vecSubRects,
retry = true; make_finder_input( max_side, discard_step, report_successful, report_unsuccessful,
areaSizeY = areaSizeY * 1.2; rectpack2D::flipping_option::DISABLED ) );
}
if( retry ) if( anyUnsuccessful )
{ {
aPlacementArea.Init( areaSizeX, areaSizeY ); max_side = (int) ( max_side * 1.2 );
it = vecSubRects.begin();
continue; continue;
} }
break;
} }
// When correctly placed in a placement area, the coords are returned in r.x and r.y return result;
// Store them.
it->x = r.x;
it->y = r.y;
it++;
}
} }
void moveFootprintsInArea( CRectPlacement& aPlacementArea, std::vector<FOOTPRINT*>& aFootprintList, void SpreadFootprints( std::vector<FOOTPRINT*>* aFootprints, VECTOR2I aTargetBoxPosition,
const BOX2I& aFreeArea, bool aFindAreaOnly ) bool aGroupBySheet, int aComponentGap, int aGroupGap )
{ {
CSubRectArray vecSubRects; using FpBBoxToFootprintsPair = std::pair<BOX2I, std::vector<FOOTPRINT*>>;
using SheetBBoxToFootprintsMapPair =
std::pair<BOX2I, std::map<VECTOR2I, FpBBoxToFootprintsPair>>;
fillRectList( vecSubRects, aFootprintList ); std::map<wxString, SheetBBoxToFootprintsMapPair> sheetsMap;
spreadRectangles( aPlacementArea, vecSubRects, aFreeArea.GetWidth(), aFreeArea.GetHeight() ); std::vector<BOX2I> blockMap;
if( aFindAreaOnly )
return;
for( unsigned it = 0; it < vecSubRects.size(); ++it )
{
VECTOR2I pos( vecSubRects[it].x, vecSubRects[it].y );
pos.x *= scale;
pos.y *= scale;
FOOTPRINT* footprint = aFootprintList[vecSubRects[it].n];
BOX2I fpBBox = footprint->GetBoundingBox( false, false );
VECTOR2I mod_pos = pos + ( footprint->GetPosition() - fpBBox.GetOrigin() )
+ aFreeArea.GetOrigin();
footprint->Move( mod_pos - footprint->GetPosition() );
}
}
static bool sortFootprintsbySheetPath( FOOTPRINT* ref, FOOTPRINT* compare );
/**
* Footprints (after loaded by reading a netlist for instance) are moved
* to be in a small free area (outside the current board) without overlapping.
* @param aBoard is the board to edit.
* @param aFootprints: a list of footprints to be spread out.
* @param aSpreadAreaPosition the position of the upper left corner of the
* area allowed to spread footprints
*/
void SpreadFootprints( std::vector<FOOTPRINT*>* aFootprints, VECTOR2I aSpreadAreaPosition )
{
// Build candidate list
// calculate also the area needed by these footprints
std::vector <FOOTPRINT*> footprintList;
// Fill in the maps
for( FOOTPRINT* footprint : *aFootprints ) for( FOOTPRINT* footprint : *aFootprints )
{ {
if( footprint->IsLocked() ) wxString path =
continue; aGroupBySheet ? footprint->GetPath().AsString().BeforeLast( '/' ) : wxS( "" );
footprintList.push_back( footprint ); VECTOR2I size = footprint->GetBoundingBox( false, false ).GetSize();
size.x += aComponentGap;
size.y += aComponentGap;
sheetsMap[path].second[size].second.push_back( footprint );
} }
if( footprintList.empty() ) for( auto& [sheetPath, sheetPair] : sheetsMap )
return;
// sort footprints by sheet path. we group them later by sheet
sort( footprintList.begin(), footprintList.end(), sortFootprintsbySheetPath );
// Extract and place footprints by sheet
std::vector<FOOTPRINT*> footprintListBySheet;
std::vector<BOX2I> placementSheetAreas;
double subsurface;
double placementsurface = 0.0;
// The placement uses 2 passes:
// the first pass creates the rectangular areas to place footprints
// each sheet in schematic creates one rectangular area.
// the second pass moves footprints inside these areas
for( int pass = 0; pass < 2; pass++ )
{ {
int subareaIdx = 0; auto& [sheet_bbox, sizeToFpMap] = sheetPair;
footprintListBySheet.clear();
subsurface = 0.0;
int fp_max_width = 0; for( auto& [fpSize, fpPair] : sizeToFpMap )
int fp_max_height = 0;
for( unsigned ii = 0; ii < footprintList.size(); ii++ )
{ {
FOOTPRINT* footprint = footprintList[ii]; auto& [block_bbox, footprints] = fpPair;
bool islastItem = false;
if( ii == footprintList.size() - 1 || // Find optimal arrangement of same-size footprints
( footprintList[ii]->GetPath().AsString().BeforeLast( '/' ) !=
footprintList[ii+1]->GetPath().AsString().BeforeLast( '/' ) ) )
islastItem = true;
footprintListBySheet.push_back( footprint ); double blockEstimateArea = (double) fpSize.x * fpSize.y * footprints.size();
subsurface += footprint->GetArea( PADDING ); double initialSide = std::sqrt( blockEstimateArea );
bool vertical = fpSize.x >= fpSize.y;
// Calculate min size of placement area: int initialCountPerLine = footprints.size();
BOX2I bbox = footprint->GetBoundingBox( false, false );
fp_max_width = std::max( fp_max_width, bbox.GetWidth() );
fp_max_height = std::max( fp_max_height, bbox.GetHeight() );
if( islastItem ) const int singleLineRatio = 5;
// Wrap the line if the ratio is not satisfied
if( vertical )
{ {
// end of the footprint sublist relative to the same sheet path if( ( fpSize.y * footprints.size() / fpSize.x ) > singleLineRatio )
// calculate placement of the current sublist initialCountPerLine = initialSide / fpSize.y;
BOX2I freeArea; }
int Xsize_allowed = (int) ( sqrt( subsurface ) * 4.0 / 3.0 ); else
Xsize_allowed = std::max( fp_max_width, Xsize_allowed );
int Ysize_allowed = (int) ( subsurface / Xsize_allowed );
Ysize_allowed = std::max( fp_max_height, Ysize_allowed );
freeArea.SetWidth( Xsize_allowed );
freeArea.SetHeight( Ysize_allowed );
CRectPlacement placementArea;
if( pass == 1 )
{ {
VECTOR2I areapos = if( ( fpSize.x * footprints.size() / fpSize.y ) > singleLineRatio )
placementSheetAreas[subareaIdx].GetOrigin() initialCountPerLine = initialSide / fpSize.x;
+ aSpreadAreaPosition;
freeArea.SetOrigin( areapos );
} }
bool findAreaOnly = pass == 0; int optimalCountPerLine = initialCountPerLine;
moveFootprintsInArea( placementArea, footprintListBySheet, freeArea, findAreaOnly ); int optimalRemainder = footprints.size() % optimalCountPerLine;
if( pass == 0 ) if( optimalRemainder != 0 )
{ {
// Populate sheet placement areas list for( int i = std::max( 2, initialCountPerLine - 2 );
BOX2I sub_area; i <= std::min( (int) footprints.size() - 2, initialCountPerLine + 2 ); i++ )
sub_area.SetWidth( placementArea.GetW()*scale ); {
sub_area.SetHeight( placementArea.GetH()*scale ); int r = footprints.size() % i;
// Add a margin around the sheet placement area:
sub_area.Inflate( pcbIUScale.mmToIU( 1.5 ) );
placementSheetAreas.push_back( sub_area ); if( r == 0 || r >= optimalRemainder )
{
placementsurface += (double) sub_area.GetWidth()* optimalCountPerLine = i;
sub_area.GetHeight(); optimalRemainder = r;
} }
// Prepare buffers for next sheet
subsurface = 0.0;
footprintListBySheet.clear();
subareaIdx++;
} }
} }
// End of pass: std::sort( footprints.begin(), footprints.end(), compareFootprintsbyRef );
// At the end of the first pass, we have to find position of each sheet
// placement area // Arrange footprints in rows or columns (blocks)
if( pass == 0 ) for( unsigned i = 0; i < footprints.size(); i++ )
{ {
int Xsize_allowed = (int) ( sqrt( placementsurface ) * 4.0 / 3.0 ); FOOTPRINT* footprint = footprints[i];
if( Xsize_allowed <= 0 || Xsize_allowed > INT_MAX/2 ) VECTOR2I position = fpSize / 2;
Xsize_allowed = INT_MAX/2;
int Ysize_allowed = (int) ( placementsurface / Xsize_allowed ); if( vertical )
if( Ysize_allowed <= 0 || Ysize_allowed > INT_MAX/2 )
Ysize_allowed = INT_MAX/2;
CRectPlacement placementArea;
CSubRectArray vecSubRects;
fillRectList( vecSubRects, placementSheetAreas );
spreadRectangles( placementArea, vecSubRects, Xsize_allowed, Ysize_allowed );
for( unsigned it = 0; it < vecSubRects.size(); ++it )
{ {
TSubRect& srect = vecSubRects[it]; position.x += fpSize.x * ( i / optimalCountPerLine );
VECTOR2I pos( srect.x * scale, srect.y * scale ); position.y += fpSize.y * ( i % optimalCountPerLine );
VECTOR2I size( srect.w * scale, srect.h * scale ); }
else
{
position.x += fpSize.x * ( i % optimalCountPerLine );
position.y += fpSize.y * ( i / optimalCountPerLine );
}
BOX2I old_fp_bbox = footprint->GetBoundingBox( false, false );
footprint->Move( position - old_fp_bbox.GetOrigin() );
BOX2I new_fp_bbox = footprint->GetBoundingBox( false, false );
new_fp_bbox.Inflate( aComponentGap / 2 );
block_bbox.Merge( new_fp_bbox );
}
}
rect_vector vecSubRects;
long long blocksArea = 0;
// Fill in arrays for packing of blocks
for( auto& [fpSize, fpPair] : sizeToFpMap )
{
auto& [block_bbox, footprints] = fpPair;
vecSubRects.emplace_back( 0, 0, block_bbox.GetWidth() / scale,
block_bbox.GetHeight() / scale, false );
blocksArea += block_bbox.GetArea();
}
// Pack the blocks
int areaSide = std::sqrt( blocksArea );
spreadRectangles( vecSubRects, areaSide, areaSide );
unsigned block_i = 0;
// Move footprints to the new block locations
for( auto& [fpSize, pair] : sizeToFpMap )
{
auto& [src_bbox, footprints] = pair;
rect_type srect = vecSubRects[block_i];
VECTOR2I target_pos( srect.x * scale, srect.y * scale );
VECTOR2I target_size( srect.w * scale, srect.h * scale );
// Avoid too large coordinates: Overlapping components // Avoid too large coordinates: Overlapping components
// are better than out of screen components // are better than out of screen components
if( (uint64_t)pos.x + (uint64_t)size.x > INT_MAX/2 ) if( (uint64_t) target_pos.x + (uint64_t) target_size.x > INT_MAX / 2 )
pos.x = 0; target_pos.x -= INT_MAX / 2;
if( (uint64_t)pos.y + (uint64_t)size.y > INT_MAX/2 ) if( (uint64_t) target_pos.y + (uint64_t) target_size.y > INT_MAX / 2 )
pos.y = 0; target_pos.y -= INT_MAX / 2;
placementSheetAreas[srect.n].SetOrigin( pos ); for( FOOTPRINT* footprint : footprints )
placementSheetAreas[srect.n].SetSize( size );
}
}
} // End pass
}
// Sort function, used to group footprints by sheet.
// Footprints are sorted by their sheet path.
// (the full sheet path restricted to the time stamp of the sheet itself,
// without the time stamp of the footprint ).
static bool sortFootprintsbySheetPath( FOOTPRINT* ref, FOOTPRINT* compare )
{ {
return ref->GetPath() < compare->GetPath(); footprint->Move( target_pos - src_bbox.GetPosition() );
sheet_bbox.Merge( footprint->GetBoundingBox( false, false ) );
}
block_i++;
}
}
rect_vector vecSubRects;
long long sheetsArea = 0;
// Fill in arrays for packing of hierarchical sheet groups
for( auto& [sheetPath, sheetPair] : sheetsMap )
{
auto& [sheet_bbox, sizeToFpMap] = sheetPair;
BOX2I rect = sheet_bbox;
// Add a margin around the sheet placement area:
rect.Inflate( aGroupGap );
vecSubRects.emplace_back( 0, 0, rect.GetWidth() / scale, rect.GetHeight() / scale, false );
sheetsArea += sheet_bbox.GetArea();
}
// Pack the hierarchical sheet groups
int areaSide = std::sqrt( sheetsArea );
spreadRectangles( vecSubRects, areaSide, areaSide );
unsigned srect_i = 0;
// Move footprints to the new hierarchical sheet group locations
for( auto& [sheetPath, sheetPair] : sheetsMap )
{
auto& [src_bbox, sizeToFpMap] = sheetPair;
rect_type srect = vecSubRects[srect_i];
VECTOR2I target_pos( srect.x * scale + aTargetBoxPosition.x,
srect.y * scale + aTargetBoxPosition.y );
VECTOR2I target_size( srect.w * scale, srect.h * scale );
// Avoid too large coordinates: Overlapping components
// are better than out of screen components
if( (uint64_t) target_pos.x + (uint64_t) target_size.x > INT_MAX / 2 )
target_pos.x -= INT_MAX / 2;
if( (uint64_t) target_pos.y + (uint64_t) target_size.y > INT_MAX / 2 )
target_pos.y -= INT_MAX / 2;
for( auto& [fpSize, fpPair] : sizeToFpMap )
{
auto& [block_bbox, footprints] = fpPair;
for( FOOTPRINT* footprint : footprints )
{
footprint->Move( target_pos - src_bbox.GetPosition() );
}
}
srect_i++;
}
} }

View File

@ -0,0 +1,48 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef __SPREAD_FOOTPRINTS_H
#define __SPREAD_FOOTPRINTS_H
#include <vector>
#include <footprint.h>
#include <base_units.h>
#include <math/vector2d.h>
#include <board.h>
/**
* Footprints (after loaded by reading a netlist for instance) are moved
* to be in a small free area (outside the current board) without overlapping.
* @param aBoard is the board to edit.
* @param aFootprints: a list of footprints to be spread out.
* @param aTargetBoxPosition the position of the upper left corner of the
* area allowed to spread footprints
*/
void SpreadFootprints( std::vector<FOOTPRINT*>* aFootprints, VECTOR2I aTargetBoxPosition,
bool aGroupBySheet = true, int aComponentGap = pcbIUScale.mmToIU( 1 ),
int aGroupGap = pcbIUScale.mmToIU( 1.5 ) );
#endif

View File

@ -36,6 +36,7 @@ using namespace std::placeholders;
#include <fp_lib_table.h> #include <fp_lib_table.h>
#include <board.h> #include <board.h>
#include <footprint.h> #include <footprint.h>
#include <spread_footprints.h>
#include <ratsnest/ratsnest_data.h> #include <ratsnest/ratsnest_data.h>
#include <io_mgr.h> #include <io_mgr.h>
#include "board_netlist_updater.h" #include "board_netlist_updater.h"
@ -46,9 +47,6 @@ using namespace std::placeholders;
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
extern void SpreadFootprints( std::vector<FOOTPRINT*>* aFootprints, VECTOR2I aSpreadAreaPosition );
bool PCB_EDIT_FRAME::ReadNetlistFromFile( const wxString &aFilename, NETLIST& aNetlist, bool PCB_EDIT_FRAME::ReadNetlistFromFile( const wxString &aFilename, NETLIST& aNetlist,
REPORTER& aReporter ) REPORTER& aReporter )
{ {
@ -96,11 +94,10 @@ void PCB_EDIT_FRAME::OnNetlistChanged( BOARD_NETLIST_UPDATER& aUpdater, bool* aR
// Spread new footprints. // Spread new footprints.
std::vector<FOOTPRINT*> newFootprints = aUpdater.GetAddedFootprints(); std::vector<FOOTPRINT*> newFootprints = aUpdater.GetAddedFootprints();
VECTOR2I areaPosition = GetCanvas()->GetViewControls()->GetCursorPosition();
GetToolManager()->RunAction( PCB_ACTIONS::selectionClear, true ); GetToolManager()->RunAction( PCB_ACTIONS::selectionClear, true );
SpreadFootprints( &newFootprints, areaPosition ); SpreadFootprints( &newFootprints, { 0, 0 }, true );
// Start drag command for new footprints // Start drag command for new footprints
if( !newFootprints.empty() ) if( !newFootprints.empty() )
@ -109,13 +106,6 @@ void PCB_EDIT_FRAME::OnNetlistChanged( BOARD_NETLIST_UPDATER& aUpdater, bool* aR
GetToolManager()->RunAction( PCB_ACTIONS::selectItem, true, footprint ); GetToolManager()->RunAction( PCB_ACTIONS::selectItem, true, footprint );
*aRunDragCommand = true; *aRunDragCommand = true;
// Now fix a reference point to move the footprints.
// We use the first footprint in list as reference point
// The graphic cursor will be on this fp when moving the footprints.
PCB_SELECTION_TOOL* selTool = GetToolManager()->GetTool<PCB_SELECTION_TOOL>();
PCB_SELECTION& selection = selTool->GetSelection();
selection.SetReferencePoint( newFootprints[0]->GetPosition() );
} }
Compile_Ratsnest( true ); Compile_Ratsnest( true );

View File

@ -204,6 +204,8 @@ bool EDIT_TOOL::Init()
menu.AddItem( PCB_ACTIONS::mirrorH, SELECTION_CONDITIONS::NotEmpty ); menu.AddItem( PCB_ACTIONS::mirrorH, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::mirrorV, SELECTION_CONDITIONS::NotEmpty ); menu.AddItem( PCB_ACTIONS::mirrorV, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::swap, SELECTION_CONDITIONS::MoreThan( 1 ) ); menu.AddItem( PCB_ACTIONS::swap, SELECTION_CONDITIONS::MoreThan( 1 ) );
menu.AddItem( PCB_ACTIONS::packAndMoveFootprints, SELECTION_CONDITIONS::MoreThan( 1 )
&& SELECTION_CONDITIONS::HasType( PCB_FOOTPRINT_T ) );
menu.AddItem( PCB_ACTIONS::properties, propertiesCondition ); menu.AddItem( PCB_ACTIONS::properties, propertiesCondition );
@ -2258,6 +2260,7 @@ void EDIT_TOOL::setTransitions()
Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorH.MakeEvent() ); Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorH.MakeEvent() );
Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorV.MakeEvent() ); Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorV.MakeEvent() );
Go( &EDIT_TOOL::Swap, PCB_ACTIONS::swap.MakeEvent() ); Go( &EDIT_TOOL::Swap, PCB_ACTIONS::swap.MakeEvent() );
Go( &EDIT_TOOL::PackAndMoveFootprints, PCB_ACTIONS::packAndMoveFootprints.MakeEvent() );
Go( &EDIT_TOOL::ChangeTrackWidth, PCB_ACTIONS::changeTrackWidth.MakeEvent() ); Go( &EDIT_TOOL::ChangeTrackWidth, PCB_ACTIONS::changeTrackWidth.MakeEvent() );
Go( &EDIT_TOOL::FilletTracks, PCB_ACTIONS::filletTracks.MakeEvent() ); Go( &EDIT_TOOL::FilletTracks, PCB_ACTIONS::filletTracks.MakeEvent() );

View File

@ -121,6 +121,11 @@ public:
*/ */
int Swap( const TOOL_EVENT& aEvent ); int Swap( const TOOL_EVENT& aEvent );
/**
* Try to fit selected footprints inside a minimal area and start movement.
*/
int PackAndMoveFootprints( const TOOL_EVENT& aEvent );
int ChangeTrackWidth( const TOOL_EVENT& aEvent ); int ChangeTrackWidth( const TOOL_EVENT& aEvent );
/** /**

View File

@ -37,6 +37,7 @@
#include <kiway.h> #include <kiway.h>
#include <array_creator.h> #include <array_creator.h>
#include <pcbnew_settings.h> #include <pcbnew_settings.h>
#include <spread_footprints.h>
#include <status_popup.h> #include <status_popup.h>
#include <tool/selection_conditions.h> #include <tool/selection_conditions.h>
#include <tool/tool_manager.h> #include <tool/tool_manager.h>
@ -380,6 +381,49 @@ int EDIT_TOOL::Swap( const TOOL_EVENT& aEvent )
} }
int EDIT_TOOL::PackAndMoveFootprints( const TOOL_EVENT& aEvent )
{
PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
PCB_SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
{
// Iterate from the back so we don't have to worry about removals.
for( int i = aCollector.GetCount() - 1; i >= 0; --i )
{
BOARD_ITEM* item = aCollector[i];
if( !dynamic_cast<FOOTPRINT*>( item ) )
aCollector.Remove( item );
}
},
true /* prompt user regarding locked items */ );
std::vector<FOOTPRINT*> footprintsToPack;
for( EDA_ITEM* item : selection )
footprintsToPack.push_back( static_cast<FOOTPRINT*>( item ) );
if( footprintsToPack.empty() )
return 0;
BOARD_COMMIT commit( editFrame );
BOX2I footprintsBbox;
for( FOOTPRINT* item : footprintsToPack )
{
commit.Modify( item );
footprintsBbox.Merge( item->GetBoundingBox( false, false ) );
}
SpreadFootprints( &footprintsToPack, footprintsBbox.Normalize().GetOrigin(), false );
commit.Push( _( "Pack footprints" ) );
return doMoveSelection( aEvent );
}
int EDIT_TOOL::Move( const TOOL_EVENT& aEvent ) int EDIT_TOOL::Move( const TOOL_EVENT& aEvent )
{ {
if( isRouterActive() ) if( isRouterActive() )

View File

@ -342,6 +342,13 @@ TOOL_ACTION PCB_ACTIONS::swap( "pcbnew.InteractiveEdit.swap",
_( "Swap" ), _( "Swaps selected items' positions" ), _( "Swap" ), _( "Swaps selected items' positions" ),
BITMAPS::swap ); BITMAPS::swap );
TOOL_ACTION PCB_ACTIONS::packAndMoveFootprints( "pcbnew.InteractiveEdit.packAndMoveFootprints",
AS_GLOBAL,
'P', "",
_( "Pack and Move Footprints" ),
_( "Sorts selected footprints by reference, packs based on size and initiates movement" ),
BITMAPS::pack_footprints );
TOOL_ACTION PCB_ACTIONS::changeTrackWidth( "pcbnew.InteractiveEdit.changeTrackWidth", TOOL_ACTION PCB_ACTIONS::changeTrackWidth( "pcbnew.InteractiveEdit.changeTrackWidth",
AS_GLOBAL, 0, "", AS_GLOBAL, 0, "",
_( "Change Track Width" ), _( "Updates selected track & via sizes" ) ); _( "Change Track Width" ), _( "Updates selected track & via sizes" ) );

View File

@ -132,6 +132,9 @@ public:
/// Swapping of selected items /// Swapping of selected items
static TOOL_ACTION swap; static TOOL_ACTION swap;
/// Pack and start moving selected footprints
static TOOL_ACTION packAndMoveFootprints;
/// Update selected tracks & vias to the current track & via dimensions /// Update selected tracks & vias to the current track & via dimensions
static TOOL_ACTION changeTrackWidth; static TOOL_ACTION changeTrackWidth;

View File

@ -383,6 +383,7 @@ set( BMAPS_MID
options_schematic options_schematic
opt_show_polygon opt_show_polygon
ortho ortho
pack_footprints
pad_sketch pad_sketch
pad pad
pad_enumerate pad_enumerate

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

View File

@ -0,0 +1,188 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="Слой_1"
data-name="Слой 1"
viewBox="0 0 24 24"
version="1.1"
sodipodi:docname="pack_footprints.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1380"
id="namedview30"
showgrid="true"
inkscape:zoom="22.627417"
inkscape:cx="3.1897126"
inkscape:cy="14.798326"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:document-rotation="0"
inkscape:current-layer="Слой_1">
<inkscape:grid
type="xygrid"
id="grid_kicad"
spacingx="0.5"
spacingy="0.5"
color="#9999ff"
opacity="0.13"
empspacing="2" />
</sodipodi:namedview>
<metadata
id="metadata43">
<rdf:RDF>
<cc:Work
rdf:about="">
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>array</dc:title>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<defs
id="defs115922">
<style
id="style115920">.cls-1{fill:#1a81c4;}.cls-2{fill:#545454;}</style>
</defs>
<title
id="title115924">array</title>
<rect
class="cls-2"
x="2"
y="2"
width="8"
height="6"
id="rect115932"
style="fill:#ded3dd;stroke-width:0.76980036;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="2"
y="9"
width="8"
height="6"
id="rect115932-3"
style="fill:#ded3dd;stroke-width:0.76980036;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="2"
y="16"
width="8"
height="6"
id="rect115932-3-6"
style="fill:#ded3dd;stroke-width:0.76980036;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="11"
y="2"
width="5"
height="3"
id="rect115932-7"
style="fill:#ded3dd;stroke-width:0.4303315;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="11"
y="6"
width="5"
height="3"
id="rect115932-7-5"
style="fill:#ded3dd;stroke-width:0.4303315;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="11"
y="10"
width="5"
height="3"
id="rect115932-7-3"
style="fill:#ded3dd;stroke-width:0.4303315;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="11"
y="14"
width="5"
height="3"
id="rect115932-7-5-5"
style="fill:#ded3dd;stroke-width:0.4303315;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="11"
y="18"
width="5"
height="3"
id="rect115932-7-3-6"
style="fill:#ded3dd;stroke-width:0.4303315;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="17"
y="2"
width="5"
height="3"
id="rect115932-7-2"
style="fill:#ded3dd;stroke-width:0.4303315;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="17"
y="6"
width="5"
height="3"
id="rect115932-7-5-9"
style="fill:#ded3dd;stroke-width:0.4303315;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="17"
y="10"
width="5"
height="3"
id="rect115932-7-3-1"
style="fill:#ded3dd;stroke-width:0.4303315;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="17"
y="14"
width="5"
height="3"
id="rect115932-7-5-5-2"
style="fill:#ded3dd;stroke-width:0.4303315;stroke:none;fill-opacity:1" />
<rect
class="cls-2"
x="0.49999997"
y="0.49999997"
width="23"
height="23"
id="rect157605"
style="fill:none;fill-opacity:1;stroke:#42b8eb;stroke-width:1.00156999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:transform-center-x="-37.586826"
inkscape:transform-center-y="-34.435834" />
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1,188 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="Слой_1"
data-name="Слой 1"
viewBox="0 0 24 24"
version="1.1"
sodipodi:docname="pack_footprints.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1380"
id="namedview30"
showgrid="true"
inkscape:zoom="22.627417"
inkscape:cx="10.658528"
inkscape:cy="14.798326"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:document-rotation="0"
inkscape:current-layer="Слой_1">
<inkscape:grid
type="xygrid"
id="grid_kicad"
spacingx="0.5"
spacingy="0.5"
color="#9999ff"
opacity="0.13"
empspacing="2" />
</sodipodi:namedview>
<metadata
id="metadata43">
<rdf:RDF>
<cc:Work
rdf:about="">
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>array</dc:title>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<defs
id="defs115922">
<style
id="style115920">.cls-1{fill:#1a81c4;}.cls-2{fill:#545454;}</style>
</defs>
<title
id="title115924">array</title>
<rect
class="cls-2"
x="2"
y="2"
width="8"
height="6"
id="rect115932"
style="fill:#545454;stroke-width:0.76980036" />
<rect
class="cls-2"
x="2"
y="9"
width="8"
height="6"
id="rect115932-3"
style="fill:#545454;stroke-width:0.76980036" />
<rect
class="cls-2"
x="2"
y="16"
width="8"
height="6"
id="rect115932-3-6"
style="fill:#545454;stroke-width:0.76980036" />
<rect
class="cls-2"
x="11"
y="2"
width="5"
height="3"
id="rect115932-7"
style="fill:#545454;stroke-width:0.4303315" />
<rect
class="cls-2"
x="11"
y="6"
width="5"
height="3"
id="rect115932-7-5"
style="fill:#545454;stroke-width:0.4303315" />
<rect
class="cls-2"
x="11"
y="10"
width="5"
height="3"
id="rect115932-7-3"
style="fill:#545454;stroke-width:0.4303315" />
<rect
class="cls-2"
x="11"
y="14"
width="5"
height="3"
id="rect115932-7-5-5"
style="fill:#545454;stroke-width:0.4303315" />
<rect
class="cls-2"
x="11"
y="18"
width="5"
height="3"
id="rect115932-7-3-6"
style="fill:#545454;stroke-width:0.4303315" />
<rect
class="cls-2"
x="17"
y="2"
width="5"
height="3"
id="rect115932-7-2"
style="fill:#545454;stroke-width:0.4303315" />
<rect
class="cls-2"
x="17"
y="6"
width="5"
height="3"
id="rect115932-7-5-9"
style="fill:#545454;stroke-width:0.4303315" />
<rect
class="cls-2"
x="17"
y="10"
width="5"
height="3"
id="rect115932-7-3-1"
style="fill:#545454;stroke-width:0.4303315" />
<rect
class="cls-2"
x="17"
y="14"
width="5"
height="3"
id="rect115932-7-5-5-2"
style="fill:#545454;stroke-width:0.4303315" />
<rect
class="cls-2"
x="0.49999997"
y="0.49999997"
width="23"
height="23"
id="rect157605"
style="fill:none;fill-opacity:1;stroke:#1a81c4;stroke-width:1.00156999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:transform-center-x="-37.586826"
inkscape:transform-center-y="-34.435834" />
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB