KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_footprint.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
22
23#include <utility>
24
25#include <board.h>
26#include <core/kicad_algo.h>
27#include <embedded_files.h>
29#include <footprint.h>
30#include <mmh3_hash.h>
31#include <pcb_field.h>
32#include <pcb_shape.h>
33
34
35static bool CourtyardEqualPredicate( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b )
36{
37 // The courtyards get a tiny epsilon to handle polygonisaton errors
38 const int courtyardEpsilon = pcbIUScale.mmToIU( 0.005 );
39
40 if( a.OutlineCount() != b.OutlineCount() )
41 return false;
42
43 // We can only use this predicate on single-outline polys unless we do more work.
44 // Because we don't know the sub-outlines are in the same order.
45 BOOST_REQUIRE( a.OutlineCount() == 1 );
46
47 return KI_TEST::ChainsAreCyclicallyEqual( a.Outline( 0 ), b.Outline( 0 ), courtyardEpsilon );
48};
49
50
51BOOST_AUTO_TEST_SUITE( Footprint )
52
53
54BOOST_AUTO_TEST_CASE( FootprintCourtyardAndHull )
55{
56 // Footprint courtyards are cached internally. Some operations manipulate the
57 // cache efficiently, some rebuild it. In any case, the courtyard should always
58 // be consistent with the geometry of the footprint. Same for the bounding hull.
59
60 BOARD board;
61 FOOTPRINT fp( &board );
62
63 const int lineW = pcbIUScale.mmToIU( 0.05 );
64 const int courtyardH = pcbIUScale.mmToIU( 1.0 );
65 const int courtyardW = pcbIUScale.mmToIU( 2.0 );
66
67 const std::vector<VECTOR2I> courtyardPoints = {
68 { 0, 0 },
69 { courtyardW, 0 },
70 { courtyardW, courtyardH },
71 { 0, courtyardH },
72 };
73
74 const auto assertCourtyardMatches = [&]( PCB_LAYER_ID layer, const SHAPE_LINE_CHAIN& aExpectedCourtyard )
75 {
76 const SHAPE_POLY_SET& courtyard = fp.GetCourtyard( layer );
77
78 BOOST_REQUIRE( courtyard.OutlineCount() == 1 );
79 BOOST_CHECK_PREDICATE( CourtyardEqualPredicate, ( courtyard.Outline( 0 ) )( aExpectedCourtyard ) );
80 };
81
82 const auto assertNoCourtyard = [&]( PCB_LAYER_ID layer )
83 {
84 const SHAPE_POLY_SET& courtyard = fp.GetCourtyard( layer );
85
86 BOOST_TEST( courtyard.OutlineCount() == 0 );
87 };
88
89 const auto assertHullMatch = [&]( const SHAPE_LINE_CHAIN& aExpectedHull )
90 {
91 const SHAPE_POLY_SET& hull = fp.GetBoundingHull();
92
93 BOOST_REQUIRE( hull.OutlineCount() == 1 );
94 BOOST_CHECK_PREDICATE( KI_TEST::ChainsAreCyclicallyEqual, ( hull.Outline( 0 ) )(aExpectedHull) ( 0 ) );
95 };
96
97 {
98 std::unique_ptr<PCB_SHAPE> courtyardPoly = std::make_unique<PCB_SHAPE>( &fp, SHAPE_T::POLY );
99 courtyardPoly->SetLayer( F_CrtYd );
100 courtyardPoly->SetPolyPoints( courtyardPoints );
101 courtyardPoly->SetWidth( lineW );
102
103 fp.Add( courtyardPoly.release() );
104 }
105
106 // We'll modify this in lock-step with the footprint
107 SHAPE_LINE_CHAIN expectedCourtyard( courtyardPoints, true );
108 // The hull is hard to calculate - we'll take the initial one as a given
109 SHAPE_LINE_CHAIN expectedHull = fp.GetBoundingHull().Outline( 0 );
110
111 BOOST_TEST_CONTEXT( "Initial courtyard" )
112 {
113 assertCourtyardMatches( F_CrtYd, expectedCourtyard );
114 assertNoCourtyard( B_CrtYd );
115 }
116
117 const VECTOR2I moveVector = VECTOR2I( courtyardW, 0 );
118
119 fp.Move( moveVector );
120 expectedCourtyard.Move( moveVector );
121 expectedHull.Move( moveVector );
122
123 BOOST_TEST_CONTEXT( "Moved courtyard" )
124 {
125 assertCourtyardMatches( F_CrtYd, expectedCourtyard );
126 assertNoCourtyard( B_CrtYd );
127 assertHullMatch( expectedHull );
128 }
129
130 fp.Rotate( VECTOR2I( 0, 0 ), EDA_ANGLE( 90.0 ) );
131 expectedCourtyard.Rotate( EDA_ANGLE( 90.0 ), VECTOR2I( 0, 0 ) );
132 expectedHull.Rotate( EDA_ANGLE( 90.0 ), VECTOR2I( 0, 0 ) );
133
134 BOOST_TEST_CONTEXT( "Rotated courtyard" )
135 {
136 assertCourtyardMatches( F_CrtYd, expectedCourtyard );
137 assertNoCourtyard( B_CrtYd );
138 assertHullMatch( expectedHull );
139 }
140
142 expectedCourtyard.Mirror( VECTOR2I( 0, 0 ), FLIP_DIRECTION::LEFT_RIGHT );
143 expectedHull.Mirror( VECTOR2I( 0, 0 ), FLIP_DIRECTION::LEFT_RIGHT );
144
145 const BOX2I flippedExpectedBox = BOX2I::ByCorners( VECTOR2I( -courtyardW, 0 ), VECTOR2I( 0, courtyardH ) );
146
147 BOOST_TEST_CONTEXT( "Flipped courtyard" )
148 {
149 assertCourtyardMatches( B_CrtYd, expectedCourtyard );
150 assertNoCourtyard( F_CrtYd );
151 assertHullMatch( expectedHull );
152 }
153}
154
155
156// Regression test for GitLab issue #24345. Cloning a footprint with embedded files (3D models,
157// fonts, etc.) must not duplicate the embedded payloads. BOARD_NETLIST_UPDATER clones each
158// footprint up to four times per "Update PCB from schematic" pass, so deep-copying embedded
159// data per clone blows up memory on boards with hundreds of footprints carrying large
160// (multi-megabyte) embedded 3D models.
161BOOST_AUTO_TEST_CASE( FootprintCloneSharesEmbeddedFiles )
162{
163 BOARD board;
164 FOOTPRINT fp( &board );
165
166 auto* file = new EMBEDDED_FILES::EMBEDDED_FILE();
167 file->name = wxS( "model.step" );
169
170 // Keep the test payload small so the suite stays fast, but use enough data to exercise
171 // the compression/encode path.
172 std::string payload( 4096, 'k' );
173 file->decompressedData.assign( payload.begin(), payload.end() );
174
176 hash.add( file->decompressedData );
177 file->data_hash = hash.digest().ToString();
178
180
181 fp.AddFile( file );
182
183 EMBEDDED_FILES::EMBEDDED_FILE* originalFile = fp.GetEmbeddedFile( wxS( "model.step" ) );
184 BOOST_REQUIRE( originalFile );
185
186 // Clone the footprint several times, mimicking the BOARD_NETLIST_UPDATER undo-snapshot
187 // pattern. Each clone must reference the same payload, not allocate a fresh copy.
188 std::vector<std::unique_ptr<FOOTPRINT>> clones;
189
190 for( int ii = 0; ii < 4; ++ii )
191 {
192 clones.emplace_back( static_cast<FOOTPRINT*>( fp.Clone() ) );
194 clones.back()->GetEmbeddedFile( wxS( "model.step" ) );
195 BOOST_REQUIRE( cloneFile );
196 BOOST_CHECK_EQUAL( cloneFile, originalFile );
197 }
198
199 // Releasing all clones must leave the source footprint's embedded file intact.
200 clones.clear();
201 BOOST_CHECK( fp.HasFile( wxS( "model.step" ) ) );
202 BOOST_CHECK_EQUAL( fp.GetEmbeddedFile( wxS( "model.step" ) ), originalFile );
203}
204
205
206// Moving a footprint leaves the source with no fields at all, and the damage bounding box is
207// computed for footprints in that state
208BOOST_AUTO_TEST_CASE( FootprintBoundingBoxWithoutMandatoryFields )
209{
210 BOARD board;
211 FOOTPRINT fp( &board );
212
213 const int shapeSize = pcbIUScale.mmToIU( 3.0 );
214
215 {
216 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &fp, SHAPE_T::RECTANGLE );
217 shape->SetLayer( F_Cu );
218 shape->SetStart( VECTOR2I( 0, 0 ) );
219 shape->SetEnd( VECTOR2I( shapeSize, shapeSize ) );
220 shape->SetWidth( pcbIUScale.mmToIU( 0.1 ) );
221
222 fp.Add( shape.release() );
223 }
224
225 // Keep the annotations out of the bounding box so that dropping them cannot change it for
226 // a legitimate reason.
228 fp.GetField( id )->SetVisible( false );
229
230 const BOX2I withFields = fp.GetBoundingBox();
231
233 {
234 std::unique_ptr<PCB_FIELD> field( fp.GetField( id ) );
235 BOOST_REQUIRE( field );
236 fp.Remove( field.get() );
237 }
238
239 const FOOTPRINT& constFp = fp;
242
243 BOOST_CHECK( fp.GetBoundingBox() == withFields );
244
245 // Text variables resolve against the same fields, and are read while the footprint is in
246 // this state to draw it.
247 wxString token = wxS( "VALUE" );
248 BOOST_CHECK( fp.ResolveTextVar( &token ) );
249 BOOST_CHECK( token.IsEmpty() );
250}
251
252
253// The properties dialogs write the whole grid back at once, and the mandatory fields have to
254// come through it as the same objects
255BOOST_AUTO_TEST_CASE( FootprintUpdateFieldsReusesMandatoryFields )
256{
257 BOARD board;
258 FOOTPRINT* fp = new FOOTPRINT( &board );
259
260 board.Add( fp );
261
262 fp->GetField( FIELD_T::VALUE )->SetText( wxS( "old value" ) );
263
264 PCB_FIELD* userField = new PCB_FIELD( fp, FIELD_T::USER, wxS( "MPN" ) );
265 userField->SetText( wxS( "PESD5V0S1BL" ) );
266 fp->Add( userField );
267
268 PCB_FIELD* reference = fp->GetField( FIELD_T::REFERENCE );
269 PCB_FIELD* value = fp->GetField( FIELD_T::VALUE );
270 const KIID referenceId = reference->m_Uuid;
271 const KIID userFieldId = userField->m_Uuid;
272
273 // What the grid hands back: the mandatory fields edited, the user field dropped and a
274 // different one added
275 std::vector<PCB_FIELD> newFields;
276
279 {
280 newFields.push_back( *fp->GetField( id ) );
281 }
282
283 newFields[1].SetText( wxS( "new value" ) );
284 newFields.emplace_back( fp, FIELD_T::USER, wxS( "LCSC" ) );
285
286 std::vector<PCB_FIELD*> added;
287 std::vector<PCB_FIELD*> detached;
288
289 fp->UpdateFields( newFields, added, detached );
290
291 BOOST_CHECK( fp->GetField( FIELD_T::REFERENCE ) == reference );
292 BOOST_CHECK( fp->GetField( FIELD_T::VALUE ) == value );
293 BOOST_CHECK_EQUAL( value->GetText(), wxS( "new value" ) );
294 BOOST_CHECK( value->GetParent() == fp );
295
296 BOOST_REQUIRE_EQUAL( added.size(), 1 );
297 BOOST_CHECK_EQUAL( added.front()->GetName(), wxS( "LCSC" ) );
298 BOOST_CHECK( added.front()->GetParent() == fp );
299
300 BOOST_REQUIRE_EQUAL( detached.size(), 1 );
301 BOOST_CHECK( detached.front() == userField );
302
303 BOOST_CHECK_EQUAL( fp->GetFields().size(), newFields.size() );
304 BOOST_CHECK( !alg::contains( fp->GetFields(), userField ) );
305
306 // The board indexes items by KIID, so it has to learn about the new field and forget the
307 // dropped one. ResolveItem falls back to a linear scan, so the index itself is what tells
308 // us the new field was registered.
309 BOOST_CHECK( board.ResolveItem( referenceId, true ) == reference );
310 BOOST_CHECK( added.front()->IsIndexedInBoard() );
311 BOOST_CHECK( board.ResolveItem( userFieldId, true ) == nullptr );
312
313 for( PCB_FIELD* field : detached )
314 delete field;
315}
316
317
318// A grid write that only edits the mandatory fields adds and removes nothing, so nothing else
319// bumps the board timestamp the bounding box cache is keyed on
320BOOST_AUTO_TEST_CASE( FootprintUpdateFieldsInvalidatesGeometryCache )
321{
322 BOARD board;
323 FOOTPRINT* fp = new FOOTPRINT( &board );
324
325 board.Add( fp );
326
327 fp->GetField( FIELD_T::VALUE )->SetText( wxS( "V" ) );
328
329 const BOX2I before = fp->GetBoundingBox();
330
331 std::vector<PCB_FIELD> newFields;
332
333 for( PCB_FIELD* field : fp->GetFields() )
334 newFields.push_back( *field );
335
336 for( PCB_FIELD& field : newFields )
337 {
338 if( field.GetId() == FIELD_T::VALUE )
339 field.SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50.0 ), 0 ) );
340 }
341
342 std::vector<PCB_FIELD*> added;
343 std::vector<PCB_FIELD*> detached;
344
345 fp->UpdateFields( newFields, added, detached );
346
347 BOOST_REQUIRE( added.empty() );
348 BOOST_REQUIRE( detached.empty() );
349 BOOST_CHECK( fp->GetBoundingBox() != before );
350}
351
352
353// A footprint that has already lost a mandatory field gets it back as a new object, which the
354// caller has to hand to the view itself
355BOOST_AUTO_TEST_CASE( FootprintUpdateFieldsRestoresMissingMandatoryField )
356{
357 BOARD board;
358 FOOTPRINT fp( &board );
359
360 std::vector<PCB_FIELD> newFields;
361
364 {
365 newFields.push_back( *fp.GetField( id ) );
366 }
367
368 std::unique_ptr<PCB_FIELD> orphan( fp.GetField( FIELD_T::VALUE ) );
369 fp.Remove( orphan.get() );
370 BOOST_REQUIRE( !std::as_const( fp ).GetField( FIELD_T::VALUE ) );
371
372 std::vector<PCB_FIELD*> added;
373 std::vector<PCB_FIELD*> detached;
374
375 fp.UpdateFields( newFields, added, detached );
376
377 BOOST_CHECK( detached.empty() );
378 BOOST_REQUIRE_EQUAL( added.size(), 1 );
379 BOOST_CHECK( added.front() == std::as_const( fp ).GetField( FIELD_T::VALUE ) );
380 BOOST_CHECK( added.front() != orphan.get() );
381}
382
383
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:266
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1497
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Definition board.cpp:2116
static constexpr BOX2< VECTOR2I > ByCorners(const VECTOR2I &aCorner1, const VECTOR2I &aCorner2)
Definition box2.h:67
const KIID m_Uuid
Definition eda_item.h:597
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:118
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:342
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:231
EMBEDDED_FILE * GetEmbeddedFile(const wxString &aName) const
Returns the embedded file with the given name or nullptr if it does not exist.
bool HasFile(const wxString &name) const
EMBEDDED_FILE * AddFile(const wxFileName &aName, bool aOverwrite)
Load a file from disk and adds it to the collection.
static uint32_t Seed()
static RETURN_CODE CompressAndEncode(EMBEDDED_FILE &aFile)
Take data from the #decompressedData buffer and compresses it using ZSTD into the #compressedEncodedD...
bool ResolveTextVar(wxString *token, int aDepth=0) const
Resolve any references to system tokens supported by the component.
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
EDA_ITEM * Clone() const override
Invoke a function on all children.
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
void UpdateFields(const std::vector< PCB_FIELD > &aFields, std::vector< PCB_FIELD * > &aAdded, std::vector< PCB_FIELD * > &aDetached)
Replace the fields with aFields, reusing the existing mandatory field objects.
PCB_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this footprint.
SHAPE_POLY_SET GetBoundingHull() const
Return a bounding polygon for the shapes and pads in the footprint.
void Move(const VECTOR2I &aMoveVector) override
Move this object.
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection) override
Flip this object, i.e.
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
const SHAPE_POLY_SET & GetCourtyard(PCB_LAYER_ID aLayer) const
Used in DRC to test the courtyard area (a complex polygon).
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition kiid.h:46
A streaming C++ equivalent for MurmurHash3_x64_128.
Definition mmh3_hash.h:56
FORCE_INLINE void add(const std::string &input)
Definition mmh3_hash.h:117
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:136
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void Move(const VECTOR2I &aVector) override
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
void Mirror(const VECTOR2I &aRef, FLIP_DIRECTION aFlipDirection)
Mirror the line points about y or x (or both).
Represent a set of closed polygons.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int OutlineCount() const
Return the number of outlines in the set.
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ B_CrtYd
Definition layer_ids.h:111
@ F_Cu
Definition layer_ids.h:60
@ LEFT_RIGHT
Flip left to right (around the Y axis)
Definition mirror.h:24
bool ChainsAreCyclicallyEqual(const SHAPE_LINE_CHAIN &aChainA, const SHAPE_LINE_CHAIN &aChainB, int aTol)
Check that two chains are cyclically equal.
Definition geometry.cpp:40
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
Utility functions for working with shapes.
std::string ToString() const
Definition hash_128.h:43
FIELD_T
The set of all field indices assuming an array like sequence that a SCH_COMPONENT or LIB_PART can hol...
@ USER
The field ID hasn't been set yet; field is invalid.
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
@ DATASHEET
name of datasheet
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(FootprintCourtyardAndHull)
static bool CourtyardEqualPredicate(const SHAPE_POLY_SET &a, const SHAPE_POLY_SET &b)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST(netlist.find("R_G1 ARM_OUT1 DIE_B R='0.001 / ((SW_STATE)") !=std::string::npos)
BOOST_CHECK_PREDICATE(ArePolylineEndPointsNearCircle,(chain)(c.m_geom.m_center_point)(radius)(accuracy+epsilon))
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683