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
384BOOST_AUTO_TEST_CASE( FootprintRotationKeepsPolygonArcs )
385{
386 BOARD board;
387 FOOTPRINT footprint( &board );
388
389 PCB_SHAPE* poly = new PCB_SHAPE( &footprint, SHAPE_T::POLY );
390
392 circle.Append( SHAPE_ARC( VECTOR2I( 0, 0 ), VECTOR2I( pcbIUScale.mmToIU( 1 ), 0 ), ANGLE_360 ) );
393 circle.SetClosed( true );
394
396 poly->SetLayer( Cmts_User );
397 footprint.Add( poly );
398
399 footprint.SetOrientation( ANGLE_180 );
400
401 BOOST_CHECK_EQUAL( poly->GetPolyShape().Outline( 0 ).ArcCount(), 1 );
403
404 footprint.SetOrientation( ANGLE_0 );
405
406 BOOST_CHECK_EQUAL( poly->GetPolyShape().Outline( 0 ).ArcCount(), 1 );
407
408 // flipping mirrors the library copy, which must keep its arc for the next rotation
409 footprint.Flip( footprint.GetPosition(), FLIP_DIRECTION::LEFT_RIGHT );
410
412}
413
414
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:267
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Definition board.cpp:2142
static constexpr BOX2< VECTOR2I > ByCorners(const VECTOR2I &aCorner1, const VECTOR2I &aCorner2)
Definition box2.h:67
const KIID m_Uuid
Definition eda_item.h:599
SHAPE_POLY_SET & GetPolyShape()
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:347
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:236
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 compressedEncodedDat...
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
void SetOrientation(const EDA_ANGLE &aNewAngle)
EDA_ITEM * Clone() const override
Create a duplicate of this item with linked list members set to NULL.
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.
bool ResolveTextVar(wxString *aToken, int aDepth=0) const
Resolve any references to system tokens supported by the component.
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
Adds an item to 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).
VECTOR2I GetPosition() const override
Definition footprint.h:379
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:61
FORCE_INLINE void add(const std::string &input)
Definition mmh3_hash.h:138
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:157
void SetPolyShape(const SHAPE_POLY_SET &aShape) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
SHAPE_POLY_SET GetLibraryPolyShape() const
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).
size_t ArcCount() const
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.
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:454
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:452
@ 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
@ Cmts_User
Definition layer_ids.h:104
@ 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:44
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")
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708