KiCad PCB EDA Suite
Loading...
Searching...
No Matches
spread_footprints.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2019 Jean-Pierre Charras, [email protected]
5 * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
6 * Copyright (C) 2013 Wayne Stambaugh <[email protected]>
7 *
8 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 */
23
30
31#include "spread_footprints.h"
32
33#include <optional>
34#include <algorithm>
35
36#include <footprint.h>
37#include <refdes_utils.h>
38#include <string_utils.h>
39#include <pcb_edit_frame.h>
40#include <rectpack2d/finders_interface.h>
41
42
43constexpr bool allow_flip = true;
44
45using SPACES_T = rectpack2D::empty_spaces<allow_flip, rectpack2D::default_empty_spaces>;
46using RECT_T = rectpack2D::output_rect_t<SPACES_T>;
47
48// Use 0.01 mm units to calculate placement, to avoid long calculation time
49const int scale = (int) ( 0.01 * pcbIUScale.IU_PER_MM );
50
51
52static bool compareFootprintsbyRef( FOOTPRINT* ref, FOOTPRINT* compare )
53{
54 const wxString& refPrefix = UTIL::GetRefDesPrefix( ref->GetReference() );
55 const wxString& cmpPrefix = UTIL::GetRefDesPrefix( compare->GetReference() );
56
57 if( refPrefix != cmpPrefix )
58 {
59 return refPrefix < cmpPrefix;
60 }
61 else
62 {
63 const int refInt = GetTrailingInt( ref->GetReference() );
64 const int cmpInt = GetTrailingInt( compare->GetReference() );
65
66 return refInt < cmpInt;
67 }
68
69 return false;
70}
71
72
73// Spread a list of rectangles inside a placement area
74std::optional<rectpack2D::rect_wh> spreadRectangles( std::vector<RECT_T>& vecSubRects, int areaSizeX, int areaSizeY )
75{
76 areaSizeX /= scale;
77 areaSizeY /= scale;
78
79 std::optional<rectpack2D::rect_wh> result;
80
81 int max_side = std::max( areaSizeX, areaSizeY );
82
83 for( int i = 0; i < 2000; i++ )
84 {
85 bool anyUnsuccessful = false;
86 const int discard_step = 1;
87
88 auto report_successful =
89 [&]( RECT_T& )
90 {
91 return rectpack2D::callback_result::CONTINUE_PACKING;
92 };
93
94 auto report_unsuccessful =
95 [&]( RECT_T& r )
96 {
97 anyUnsuccessful = true;
98 return rectpack2D::callback_result::ABORT_PACKING;
99 };
100
101 result = rectpack2D::find_best_packing<SPACES_T>( vecSubRects,
102 make_finder_input( max_side, discard_step,
103 report_successful, report_unsuccessful,
104 rectpack2D::flipping_option::DISABLED ) );
105
106 if( !result || anyUnsuccessful )
107 {
108 max_side = (int) ( max_side * 1.2 );
109 continue;
110 }
111
112 break;
113 }
114
115 return result;
116}
117
118
119void SpreadFootprints( std::vector<FOOTPRINT*>* aFootprints, const VECTOR2I& aTargetBoxPosition, bool aGroupBySheet,
120 int aComponentGap, int aGroupGap )
121{
122 using FpBBoxToFootprintsPair = std::pair<BOX2I, std::vector<FOOTPRINT*>>;
123 using SheetBBoxToFootprintsMapPair = std::pair<BOX2I, std::map<VECTOR2I, FpBBoxToFootprintsPair>>;
124
125 std::map<wxString, SheetBBoxToFootprintsMapPair> sheetsMap;
126
127 // Fill in the maps
128 for( FOOTPRINT* footprint : *aFootprints )
129 {
130 wxString path = aGroupBySheet ? footprint->GetPath().AsString().BeforeLast( '/' ) : wxString( wxS( "" ) );
131
132 VECTOR2I size = footprint->GetBoundingBox( false ).GetSize();
133 size.x += aComponentGap;
134 size.y += aComponentGap;
135
136 sheetsMap[path].second[size].second.push_back( footprint );
137 }
138
139 for( auto& [sheetPath, sheetPair] : sheetsMap )
140 {
141 auto& [sheet_bbox, sizeToFpMap] = sheetPair;
142
143 for( auto& [fpSize, fpPair] : sizeToFpMap )
144 {
145 auto& [block_bbox, footprints] = fpPair;
146
147 // Find optimal arrangement of same-size footprints
148
149 double blockEstimateArea = (double) fpSize.x * fpSize.y * footprints.size();
150 double initialSide = std::sqrt( blockEstimateArea );
151 bool vertical = fpSize.x >= fpSize.y;
152
153 int initialCountPerLine = footprints.size();
154
155 const int singleLineRatio = 5;
156
157 // Wrap the line if the ratio is not satisfied
158 if( vertical )
159 {
160 if( ( fpSize.y * footprints.size() / fpSize.x ) > singleLineRatio )
161 initialCountPerLine = initialSide / fpSize.y;
162 }
163 else
164 {
165 if( ( fpSize.x * footprints.size() / fpSize.y ) > singleLineRatio )
166 initialCountPerLine = initialSide / fpSize.x;
167 }
168
169 int optimalCountPerLine = initialCountPerLine;
170 int optimalRemainder = footprints.size() % optimalCountPerLine;
171
172 if( optimalRemainder != 0 )
173 {
174 for( int i = std::max( 2, initialCountPerLine - 2 );
175 i <= std::min( (int) footprints.size() - 2, initialCountPerLine + 2 ); i++ )
176 {
177 int r = footprints.size() % i;
178
179 if( r == 0 || r >= optimalRemainder )
180 {
181 optimalCountPerLine = i;
182 optimalRemainder = r;
183 }
184 }
185 }
186
187 std::sort( footprints.begin(), footprints.end(), compareFootprintsbyRef );
188
189 // Arrange footprints in rows or columns (blocks)
190 for( unsigned i = 0; i < footprints.size(); i++ )
191 {
192 FOOTPRINT* footprint = footprints[i];
193
194 VECTOR2I position = fpSize / 2;
195
196 if( vertical )
197 {
198 position.x += fpSize.x * ( i / optimalCountPerLine );
199 position.y += fpSize.y * ( i % optimalCountPerLine );
200 }
201 else
202 {
203 position.x += fpSize.x * ( i % optimalCountPerLine );
204 position.y += fpSize.y * ( i / optimalCountPerLine );
205 }
206
207 BOX2I old_fp_bbox = footprint->GetBoundingBox( false );
208 footprint->Move( position - old_fp_bbox.GetOrigin() );
209
210 BOX2I new_fp_bbox = footprint->GetBoundingBox( false );
211 new_fp_bbox.Inflate( aComponentGap / 2 );
212 block_bbox.Merge( new_fp_bbox );
213 }
214 }
215
216 std::vector<RECT_T> vecSubRects;
217 long long blocksArea = 0;
218
219 // Fill in arrays for packing of blocks
220 for( auto& [fpSize, fpPair] : sizeToFpMap )
221 {
222 auto& [block_bbox, footprints] = fpPair;
223
224 vecSubRects.emplace_back( 0, 0, block_bbox.GetWidth() / scale, block_bbox.GetHeight() / scale, false );
225
226 blocksArea += block_bbox.GetArea();
227 }
228
229 // Pack the blocks
230 int areaSide = KiROUND( std::sqrt( blocksArea ) );
231 spreadRectangles( vecSubRects, areaSide, areaSide );
232
233 unsigned block_i = 0;
234
235 // Move footprints to the new block locations
236 for( auto& [fpSize, pair] : sizeToFpMap )
237 {
238 auto& [src_bbox, footprints] = pair;
239
240 RECT_T srect = vecSubRects[block_i];
241
242 VECTOR2I target_pos( srect.x * scale, srect.y * scale );
243 VECTOR2I target_size( srect.w * scale, srect.h * scale );
244
245 // Avoid too large coordinates: Overlapping components
246 // are better than out of screen components
247 if( (uint64_t) target_pos.x + (uint64_t) target_size.x > INT_MAX / 2 )
248 target_pos.x -= INT_MAX / 2;
249
250 if( (uint64_t) target_pos.y + (uint64_t) target_size.y > INT_MAX / 2 )
251 target_pos.y -= INT_MAX / 2;
252
253 for( FOOTPRINT* footprint : footprints )
254 {
255 footprint->Move( target_pos - src_bbox.GetPosition() );
256 sheet_bbox.Merge( footprint->GetBoundingBox( false ) );
257 }
258
259 block_i++;
260 }
261 }
262
263 std::vector<RECT_T> vecSubRects;
264 long long sheetsArea = 0;
265
266 // Fill in arrays for packing of hierarchical sheet groups
267 for( auto& [sheetPath, sheetPair] : sheetsMap )
268 {
269 auto& [sheet_bbox, sizeToFpMap] = sheetPair;
270 BOX2I rect = sheet_bbox;
271
272 // Add a margin around the sheet placement area:
273 rect.Inflate( aGroupGap );
274
275 vecSubRects.emplace_back( 0, 0, rect.GetWidth() / scale, rect.GetHeight() / scale, false );
276
277 sheetsArea += sheet_bbox.GetArea();
278 }
279
280 // Pack the hierarchical sheet groups
281 int areaSide = std::sqrt( sheetsArea );
282 spreadRectangles( vecSubRects, areaSide, areaSide );
283
284 unsigned srect_i = 0;
285
286 // Move footprints to the new hierarchical sheet group locations
287 for( auto& [sheetPath, sheetPair] : sheetsMap )
288 {
289 auto& [src_bbox, sizeToFpMap] = sheetPair;
290
291 RECT_T srect = vecSubRects[srect_i];
292
293 VECTOR2I target_pos( srect.x * scale + aTargetBoxPosition.x, srect.y * scale + aTargetBoxPosition.y );
294 VECTOR2I target_size( srect.w * scale, srect.h * scale );
295
296 // Avoid too large coordinates: Overlapping components
297 // are better than out of screen components
298 if( (int64_t) target_pos.x + (int64_t) target_size.x > INT_MAX / 2 )
299 target_pos.x -= INT_MAX / 2;
300
301 if( (int64_t) target_pos.y + (int64_t) target_size.y > INT_MAX / 2 )
302 target_pos.y -= INT_MAX / 2;
303
304 for( auto& [fpSize, fpPair] : sizeToFpMap )
305 {
306 auto& [block_bbox, footprints] = fpPair;
307
308 for( FOOTPRINT* footprint : footprints )
309 footprint->Move( target_pos - src_bbox.GetPosition() );
310 }
311
312 srect_i++;
313 }
314}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:653
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr const Vec & GetOrigin() const
Definition box2.h:207
void Move(const VECTOR2I &aMoveVector) override
Move this object.
const wxString & GetReference() const
Definition footprint.h:901
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
wxString GetRefDesPrefix(const wxString &aRefDes)
Get the (non-numeric) prefix from a refdes - e.g.
Collection of utility functions for component reference designators (refdes)
rectpack2D::empty_spaces< allow_flip, rectpack2D::default_empty_spaces > SPACES_T
void SpreadFootprints(std::vector< FOOTPRINT * > *aFootprints, const VECTOR2I &aTargetBoxPosition, bool aGroupBySheet, int aComponentGap, int aGroupGap)
Footprints (after loaded by reading a netlist for instance) are moved to be in a small free area (out...
const int scale
std::optional< rectpack2D::rect_wh > spreadRectangles(std::vector< RECT_T > &vecSubRects, int areaSizeX, int areaSizeY)
rectpack2D::output_rect_t< SPACES_T > RECT_T
static bool compareFootprintsbyRef(FOOTPRINT *ref, FOOTPRINT *compare)
constexpr bool allow_flip
int GetTrailingInt(const wxString &aStr)
Gets the trailing int, if any, from a string.
std::string path
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683