2013-11-27 13:20:42 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
2019-08-10 18:29:54 +00:00
|
|
|
* Copyright (C) 2019 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr
|
2013-11-27 13:20:42 +00:00
|
|
|
* Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
|
|
|
|
* Copyright (C) 2013 Wayne Stambaugh <stambaughw@verizon.net>
|
|
|
|
*
|
2019-08-10 18:29:54 +00:00
|
|
|
* Copyright (C) 1992-2019 KiCad Developers, see AUTHORS.txt for contributors.
|
2013-11-27 13:20:42 +00: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
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @file spread_footprints.cpp
|
|
|
|
* @brief functions to spread footprints on free areas outside a board.
|
2021-06-09 19:32:58 +00:00
|
|
|
* this is useful after reading a netlist, when new footprints are loaded
|
2013-11-27 13:20:42 +00:00
|
|
|
* and stacked at 0,0 coordinate.
|
|
|
|
* Often, spread them on a free area near the board being edited make more easy
|
|
|
|
* their selection.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <convert_to_biu.h>
|
|
|
|
#include <confirm.h>
|
2018-01-29 20:58:58 +00:00
|
|
|
#include <pcb_edit_frame.h>
|
2020-11-12 20:19:22 +00:00
|
|
|
#include <board.h>
|
|
|
|
#include <footprint.h>
|
2013-11-27 13:20:42 +00:00
|
|
|
#include <rect_placement/rect_placement.h>
|
|
|
|
|
|
|
|
struct TSubRect : public CRectPlacement::TRect
|
|
|
|
{
|
|
|
|
int n; // Original index of this subrect, before sorting
|
|
|
|
|
2015-02-22 21:25:29 +00:00
|
|
|
TSubRect() : TRect(),
|
|
|
|
n( 0 )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-11-27 13:20:42 +00:00
|
|
|
TSubRect( int _w, int _h, int _n ) :
|
|
|
|
TRect( 0, 0, _w, _h ), n( _n ) { }
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef std::vector<TSubRect> CSubRectArray;
|
|
|
|
|
|
|
|
// Use 0.01 mm units to calculate placement, to avoid long calculation time
|
|
|
|
const int scale = (int)(0.01 * IU_PER_MM);
|
|
|
|
|
2018-09-13 21:29:52 +00:00
|
|
|
const int PADDING = (int)(1 * IU_PER_MM);
|
|
|
|
|
2020-10-21 03:48:06 +00:00
|
|
|
// Populates a list of rectangles, from a list of footprints
|
2020-11-13 15:15:52 +00:00
|
|
|
void fillRectList( CSubRectArray& vecSubRects, std::vector <FOOTPRINT*>& aFootprintList )
|
2013-11-27 13:20:42 +00:00
|
|
|
{
|
|
|
|
vecSubRects.clear();
|
|
|
|
|
2020-11-13 01:33:30 +00:00
|
|
|
for( unsigned ii = 0; ii < aFootprintList.size(); ii++ )
|
2013-11-27 13:20:42 +00:00
|
|
|
{
|
2022-08-31 22:57:24 +00:00
|
|
|
BOX2I fpBox = aFootprintList[ii]->GetBoundingBox( false, false );
|
2018-09-13 21:29:52 +00:00
|
|
|
TSubRect fpRect( ( fpBox.GetWidth() + PADDING ) / scale,
|
|
|
|
( fpBox.GetHeight() + PADDING ) / scale, ii );
|
2013-11-27 13:20:42 +00:00
|
|
|
vecSubRects.push_back( fpRect );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-31 22:57:24 +00:00
|
|
|
// Populates a list of rectangles, from a list of BOX2I
|
|
|
|
void fillRectList( CSubRectArray& vecSubRects, std::vector<BOX2I>& aRectList )
|
2013-11-27 13:20:42 +00:00
|
|
|
{
|
|
|
|
vecSubRects.clear();
|
|
|
|
|
|
|
|
for( unsigned ii = 0; ii < aRectList.size(); ii++ )
|
|
|
|
{
|
2022-08-31 22:57:24 +00:00
|
|
|
BOX2I& rect = aRectList[ii];
|
2013-11-27 13:20:42 +00:00
|
|
|
TSubRect fpRect( rect.GetWidth()/scale, rect.GetHeight()/scale, ii );
|
|
|
|
vecSubRects.push_back( fpRect );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Spread a list of rectangles inside a placement area
|
|
|
|
void spreadRectangles( CRectPlacement& aPlacementArea,
|
2016-01-10 21:24:13 +00:00
|
|
|
CSubRectArray& vecSubRects,
|
|
|
|
int areaSizeX, int areaSizeY )
|
2013-11-27 13:20:42 +00:00
|
|
|
{
|
|
|
|
areaSizeX/= scale;
|
|
|
|
areaSizeY/= scale;
|
|
|
|
|
|
|
|
// Sort the subRects based on dimensions, larger dimension goes first.
|
|
|
|
std::sort( vecSubRects.begin(), vecSubRects.end(), CRectPlacement::TRect::Greater );
|
|
|
|
|
|
|
|
// gives the initial size to the area
|
|
|
|
aPlacementArea.Init( areaSizeX, areaSizeY );
|
|
|
|
|
|
|
|
// Add all subrects
|
|
|
|
CSubRectArray::iterator it;
|
2020-02-29 16:25:52 +00:00
|
|
|
|
2013-11-27 13:20:42 +00:00
|
|
|
for( it = vecSubRects.begin(); it != vecSubRects.end(); )
|
|
|
|
{
|
|
|
|
CRectPlacement::TRect r( 0, 0, it->w, it->h );
|
|
|
|
|
|
|
|
bool bPlaced = aPlacementArea.AddAtEmptySpotAutoGrow( &r, areaSizeX, areaSizeY );
|
|
|
|
|
|
|
|
if( !bPlaced ) // No room to place the rectangle: enlarge area and retry
|
|
|
|
{
|
2020-02-29 16:25:52 +00:00
|
|
|
bool retry = false;
|
|
|
|
|
|
|
|
if( areaSizeX < INT_MAX/2 )
|
|
|
|
{
|
|
|
|
retry = true;
|
|
|
|
areaSizeX = areaSizeX * 1.2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( areaSizeX < INT_MAX/2 )
|
|
|
|
{
|
|
|
|
retry = true;
|
|
|
|
areaSizeY = areaSizeY * 1.2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( retry )
|
|
|
|
{
|
|
|
|
aPlacementArea.Init( areaSizeX, areaSizeY );
|
|
|
|
it = vecSubRects.begin();
|
|
|
|
continue;
|
|
|
|
}
|
2013-11-27 13:20:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// When correctly placed in a placement area, the coords are returned in r.x and r.y
|
|
|
|
// Store them.
|
|
|
|
it->x = r.x;
|
|
|
|
it->y = r.y;
|
|
|
|
|
|
|
|
it++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-08-31 22:57:24 +00:00
|
|
|
void moveFootprintsInArea( CRectPlacement& aPlacementArea, std::vector<FOOTPRINT*>& aFootprintList,
|
|
|
|
const BOX2I& aFreeArea, bool aFindAreaOnly )
|
2013-11-27 13:20:42 +00:00
|
|
|
{
|
|
|
|
CSubRectArray vecSubRects;
|
|
|
|
|
2020-11-13 01:33:30 +00:00
|
|
|
fillRectList( vecSubRects, aFootprintList );
|
|
|
|
spreadRectangles( aPlacementArea, vecSubRects, aFreeArea.GetWidth(), aFreeArea.GetHeight() );
|
2013-11-27 13:20:42 +00:00
|
|
|
|
|
|
|
if( aFindAreaOnly )
|
|
|
|
return;
|
|
|
|
|
|
|
|
for( unsigned it = 0; it < vecSubRects.size(); ++it )
|
|
|
|
{
|
2022-01-01 06:04:08 +00:00
|
|
|
VECTOR2I pos( vecSubRects[it].x, vecSubRects[it].y );
|
2013-11-27 13:20:42 +00:00
|
|
|
pos.x *= scale;
|
|
|
|
pos.y *= scale;
|
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
FOOTPRINT* footprint = aFootprintList[vecSubRects[it].n];
|
2013-11-27 13:20:42 +00:00
|
|
|
|
2022-08-31 22:57:24 +00:00
|
|
|
BOX2I fpBBox = footprint->GetBoundingBox( false, false );
|
|
|
|
VECTOR2I mod_pos = pos + ( footprint->GetPosition() - fpBBox.GetOrigin() )
|
|
|
|
+ aFreeArea.GetOrigin();
|
2013-11-27 13:20:42 +00:00
|
|
|
|
2020-11-13 02:57:11 +00:00
|
|
|
footprint->Move( mod_pos - footprint->GetPosition() );
|
2013-11-27 13:20:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
static bool sortFootprintsbySheetPath( FOOTPRINT* ref, FOOTPRINT* compare );
|
2013-11-27 13:20:42 +00:00
|
|
|
|
2019-08-10 18:29:54 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2013-11-27 13:20:42 +00:00
|
|
|
*/
|
2022-01-04 01:00:40 +00:00
|
|
|
void SpreadFootprints( std::vector<FOOTPRINT*>* aFootprints, VECTOR2I aSpreadAreaPosition )
|
2013-11-27 13:20:42 +00:00
|
|
|
{
|
|
|
|
// Build candidate list
|
|
|
|
// calculate also the area needed by these footprints
|
2020-11-13 15:15:52 +00:00
|
|
|
std::vector <FOOTPRINT*> footprintList;
|
2013-11-27 13:20:42 +00:00
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
for( FOOTPRINT* footprint : *aFootprints )
|
2013-11-27 13:20:42 +00:00
|
|
|
{
|
2016-01-10 21:24:13 +00:00
|
|
|
if( footprint->IsLocked() )
|
2013-11-27 13:20:42 +00:00
|
|
|
continue;
|
|
|
|
|
2016-01-10 21:24:13 +00:00
|
|
|
footprintList.push_back( footprint );
|
2013-11-27 13:20:42 +00:00
|
|
|
}
|
|
|
|
|
2016-01-10 21:24:13 +00:00
|
|
|
if( footprintList.empty() )
|
2013-11-27 13:20:42 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// sort footprints by sheet path. we group them later by sheet
|
2016-01-10 21:24:13 +00:00
|
|
|
sort( footprintList.begin(), footprintList.end(), sortFootprintsbySheetPath );
|
2013-11-27 13:20:42 +00:00
|
|
|
|
|
|
|
// Extract and place footprints by sheet
|
2022-08-31 22:57:24 +00:00
|
|
|
std::vector<FOOTPRINT*> footprintListBySheet;
|
|
|
|
std::vector<BOX2I> placementSheetAreas;
|
2020-11-13 15:15:52 +00:00
|
|
|
double subsurface;
|
|
|
|
double placementsurface = 0.0;
|
2013-11-27 13:20:42 +00:00
|
|
|
|
|
|
|
// 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;
|
2016-01-10 21:24:13 +00:00
|
|
|
footprintListBySheet.clear();
|
2013-11-27 13:20:42 +00:00
|
|
|
subsurface = 0.0;
|
|
|
|
|
2020-02-29 16:25:52 +00:00
|
|
|
int fp_max_width = 0;
|
|
|
|
int fp_max_height = 0;
|
|
|
|
|
2016-01-10 21:24:13 +00:00
|
|
|
for( unsigned ii = 0; ii < footprintList.size(); ii++ )
|
2013-11-27 13:20:42 +00:00
|
|
|
{
|
2020-11-13 15:15:52 +00:00
|
|
|
FOOTPRINT* footprint = footprintList[ii];
|
|
|
|
bool islastItem = false;
|
2013-11-27 13:20:42 +00:00
|
|
|
|
2016-01-10 21:24:13 +00:00
|
|
|
if( ii == footprintList.size() - 1 ||
|
2020-02-20 12:11:04 +00:00
|
|
|
( footprintList[ii]->GetPath().AsString().BeforeLast( '/' ) !=
|
|
|
|
footprintList[ii+1]->GetPath().AsString().BeforeLast( '/' ) ) )
|
2014-03-05 17:40:23 +00:00
|
|
|
islastItem = true;
|
|
|
|
|
2016-01-10 21:24:13 +00:00
|
|
|
footprintListBySheet.push_back( footprint );
|
2018-09-13 21:29:52 +00:00
|
|
|
subsurface += footprint->GetArea( PADDING );
|
2013-11-27 13:20:42 +00:00
|
|
|
|
2020-02-29 16:25:52 +00:00
|
|
|
// Calculate min size of placement area:
|
2022-08-31 22:57:24 +00:00
|
|
|
BOX2I bbox = footprint->GetBoundingBox( false, false );
|
2020-02-29 16:25:52 +00:00
|
|
|
fp_max_width = std::max( fp_max_width, bbox.GetWidth() );
|
|
|
|
fp_max_height = std::max( fp_max_height, bbox.GetHeight() );
|
|
|
|
|
2014-03-05 17:40:23 +00:00
|
|
|
if( islastItem )
|
2013-11-27 13:20:42 +00:00
|
|
|
{
|
|
|
|
// end of the footprint sublist relative to the same sheet path
|
|
|
|
// calculate placement of the current sublist
|
2022-08-31 22:57:24 +00:00
|
|
|
BOX2I freeArea;
|
2013-11-27 13:20:42 +00:00
|
|
|
int Xsize_allowed = (int) ( sqrt( subsurface ) * 4.0 / 3.0 );
|
2020-02-29 16:25:52 +00:00
|
|
|
Xsize_allowed = std::max( fp_max_width, Xsize_allowed );
|
|
|
|
|
2013-11-27 13:20:42 +00:00
|
|
|
int Ysize_allowed = (int) ( subsurface / Xsize_allowed );
|
2020-02-29 16:25:52 +00:00
|
|
|
Ysize_allowed = std::max( fp_max_height, Ysize_allowed );
|
2013-11-27 13:20:42 +00:00
|
|
|
|
|
|
|
freeArea.SetWidth( Xsize_allowed );
|
|
|
|
freeArea.SetHeight( Ysize_allowed );
|
|
|
|
CRectPlacement placementArea;
|
|
|
|
|
|
|
|
if( pass == 1 )
|
|
|
|
{
|
2021-12-29 21:30:11 +00:00
|
|
|
VECTOR2I areapos =
|
|
|
|
placementSheetAreas[subareaIdx].GetOrigin()
|
2019-08-10 18:29:54 +00:00
|
|
|
+ aSpreadAreaPosition;
|
2013-11-27 13:20:42 +00:00
|
|
|
freeArea.SetOrigin( areapos );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool findAreaOnly = pass == 0;
|
2020-11-13 01:33:30 +00:00
|
|
|
moveFootprintsInArea( placementArea, footprintListBySheet, freeArea, findAreaOnly );
|
2013-11-27 13:20:42 +00:00
|
|
|
|
|
|
|
if( pass == 0 )
|
|
|
|
{
|
|
|
|
// Populate sheet placement areas list
|
2022-08-31 22:57:24 +00:00
|
|
|
BOX2I sub_area;
|
2013-11-27 13:20:42 +00:00
|
|
|
sub_area.SetWidth( placementArea.GetW()*scale );
|
|
|
|
sub_area.SetHeight( placementArea.GetH()*scale );
|
|
|
|
// Add a margin around the sheet placement area:
|
|
|
|
sub_area.Inflate( Millimeter2iu( 1.5 ) );
|
|
|
|
|
|
|
|
placementSheetAreas.push_back( sub_area );
|
|
|
|
|
|
|
|
placementsurface += (double) sub_area.GetWidth()*
|
|
|
|
sub_area.GetHeight();
|
|
|
|
}
|
|
|
|
|
2014-03-05 17:40:23 +00:00
|
|
|
// Prepare buffers for next sheet
|
2013-11-27 13:20:42 +00:00
|
|
|
subsurface = 0.0;
|
2016-01-10 21:24:13 +00:00
|
|
|
footprintListBySheet.clear();
|
2013-11-27 13:20:42 +00:00
|
|
|
subareaIdx++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// End of pass:
|
|
|
|
// At the end of the first pass, we have to find position of each sheet
|
|
|
|
// placement area
|
|
|
|
if( pass == 0 )
|
|
|
|
{
|
|
|
|
int Xsize_allowed = (int) ( sqrt( placementsurface ) * 4.0 / 3.0 );
|
2020-02-29 16:25:52 +00:00
|
|
|
|
2022-02-05 16:23:05 +00:00
|
|
|
if( Xsize_allowed <= 0 || Xsize_allowed > INT_MAX/2 )
|
2020-02-29 16:25:52 +00:00
|
|
|
Xsize_allowed = INT_MAX/2;
|
|
|
|
|
2013-11-27 13:20:42 +00:00
|
|
|
int Ysize_allowed = (int) ( placementsurface / Xsize_allowed );
|
2020-02-29 16:25:52 +00:00
|
|
|
|
2022-02-05 16:23:05 +00:00
|
|
|
if( Ysize_allowed <= 0 || Ysize_allowed > INT_MAX/2 )
|
2020-02-29 16:25:52 +00:00
|
|
|
Ysize_allowed = INT_MAX/2;
|
|
|
|
|
2013-11-27 13:20:42 +00:00
|
|
|
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];
|
2022-01-04 01:00:40 +00:00
|
|
|
VECTOR2I pos( srect.x * scale, srect.y * scale );
|
|
|
|
VECTOR2I size( srect.w * scale, srect.h * scale );
|
2020-02-29 16:25:52 +00:00
|
|
|
|
|
|
|
// Avoid too large coordinates: Overlapping components
|
|
|
|
// are better than out of screen components
|
|
|
|
if( (uint64_t)pos.x + (uint64_t)size.x > INT_MAX/2 )
|
|
|
|
pos.x = 0;
|
|
|
|
|
|
|
|
if( (uint64_t)pos.y + (uint64_t)size.y > INT_MAX/2 )
|
|
|
|
pos.y = 0;
|
|
|
|
|
2013-11-27 13:20:42 +00:00
|
|
|
placementSheetAreas[srect.n].SetOrigin( pos );
|
|
|
|
placementSheetAreas[srect.n].SetSize( size );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // End pass
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-05 17:40:23 +00:00
|
|
|
// 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 ).
|
2020-11-13 15:15:52 +00:00
|
|
|
static bool sortFootprintsbySheetPath( FOOTPRINT* ref, FOOTPRINT* compare )
|
2013-11-27 13:20:42 +00:00
|
|
|
{
|
2020-02-20 12:11:04 +00:00
|
|
|
return ref->GetPath() < compare->GetPath();
|
2013-11-27 13:20:42 +00:00
|
|
|
}
|