KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_custom_pad_primitive_roundtrip.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
21
22#include <base_units.h>
23#include <board.h>
24#include <footprint.h>
25#include <pad.h>
26#include <pcb_point.h>
27#include <pcb_shape.h>
28#include <geometry/eda_angle.h>
31
33
34#include <filesystem>
35#include <memory>
36
37
38BOOST_AUTO_TEST_SUITE( CustomPadPrimitiveRoundTrip )
39
40
41namespace
42{
43
45class TEMP_FILE_HOLDER
46{
47public:
48 TEMP_FILE_HOLDER( const std::string& aPrefix, const std::string& aSuffix )
49 {
50 std::filesystem::path dir = std::filesystem::temp_directory_path() / aPrefix;
51 std::filesystem::create_directories( dir );
52 m_path = dir / ( "board" + aSuffix );
53 }
54
55 ~TEMP_FILE_HOLDER()
56 {
57 std::error_code ec;
58 std::filesystem::remove( m_path, ec );
59 std::filesystem::remove( m_path.parent_path(), ec );
60 }
61
62 const std::filesystem::path& Path() const { return m_path; }
63
64private:
65 std::filesystem::path m_path;
66};
67
68
69int mm( double aMillimetres )
70{
71 return pcbIUScale.mmToIU( aMillimetres );
72}
73
74
75PCB_SHAPE* findPrimitive( const PAD* aPad, SHAPE_T aShape )
76{
77 for( const std::shared_ptr<PCB_SHAPE>& prim : aPad->GetPrimitives( PADSTACK::ALL_LAYERS ) )
78 {
79 if( prim->GetShape() == aShape )
80 return prim.get();
81 }
82
83 return nullptr;
84}
85
86
87void checkPoly( const SHAPE_POLY_SET& aExpected, const SHAPE_POLY_SET& aGot )
88{
89 BOOST_REQUIRE_EQUAL( aExpected.OutlineCount(), aGot.OutlineCount() );
90
91 const SHAPE_LINE_CHAIN& expOutline = aExpected.COutline( 0 );
92 const SHAPE_LINE_CHAIN& gotOutline = aGot.COutline( 0 );
93
94 BOOST_REQUIRE_EQUAL( expOutline.PointCount(), gotOutline.PointCount() );
95
96 for( int ii = 0; ii < expOutline.PointCount(); ++ii )
97 {
98 BOOST_CHECK_EQUAL( gotOutline.CPoint( ii ).x, expOutline.CPoint( ii ).x );
99 BOOST_CHECK_EQUAL( gotOutline.CPoint( ii ).y, expOutline.CPoint( ii ).y );
100 }
101}
102
103} // namespace
104
105
113BOOST_AUTO_TEST_CASE( BezierAndPolyOnTransformedFootprint )
114{
115 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
116 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
117
118 fp->SetReference( "U1" );
119
120 // A non-cardinal rotation and an anisotropic scale make any spurious transform on
121 // the primitives obvious.
122 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 25.0 ) ) );
123 fp->SetOrientation( EDA_ANGLE( 30.0, DEGREES_T ) );
124 fp->SetTransformScale( 2.0, 1.5 );
125
126 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
127 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
128 pad->SetNumber( "1" );
129 pad->SetAttribute( PAD_ATTRIB::SMD );
130 pad->SetLayerSet( LSET( { F_Cu } ) );
131 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
132 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
134
135 // Bezier primitive with deliberately asymmetric control points.
136 PCB_SHAPE* bezier = new PCB_SHAPE( pad.get() );
137 bezier->SetShape( SHAPE_T::BEZIER );
138 bezier->SetStart( VECTOR2I( mm( 0.0 ), mm( 0.0 ) ) );
139 bezier->SetBezierC1( VECTOR2I( mm( 1.0 ), mm( 2.0 ) ) );
140 bezier->SetBezierC2( VECTOR2I( mm( 3.0 ), mm( -1.0 ) ) );
141 bezier->SetEnd( VECTOR2I( mm( 4.0 ), mm( 0.5 ) ) );
142 bezier->SetWidth( mm( 0.1 ) );
143 pad->AddPrimitive( PADSTACK::ALL_LAYERS, bezier );
144
145 // Polygon primitive with an asymmetric (non-rectangular) outline.
147 chain.Append( VECTOR2I( mm( 1.0 ), mm( 0.0 ) ) );
148 chain.Append( VECTOR2I( mm( 3.0 ), mm( 1.0 ) ) );
149 chain.Append( VECTOR2I( mm( 2.0 ), mm( 4.0 ) ) );
150 chain.Append( VECTOR2I( mm( -1.0 ), mm( 2.0 ) ) );
151 chain.SetClosed( true );
152
153 SHAPE_POLY_SET polySet;
154 polySet.AddOutline( chain );
155
156 PCB_SHAPE* poly = new PCB_SHAPE( pad.get() );
157 poly->SetShape( SHAPE_T::POLY );
158 poly->SetPolyShape( polySet );
159 poly->SetWidth( mm( 0.1 ) );
160 pad->AddPrimitive( PADSTACK::ALL_LAYERS, poly );
161
162 // Capture the in-memory (pre-save) geometry as the round-trip reference.
163 const VECTOR2I expBezierStart = bezier->GetLibraryStart();
164 const VECTOR2I expBezierC1 = bezier->GetLibraryBezierC1();
165 const VECTOR2I expBezierC2 = bezier->GetLibraryBezierC2();
166 const VECTOR2I expBezierEnd = bezier->GetLibraryEnd();
167 const int expBezierWidth = bezier->GetWidth();
168 const SHAPE_POLY_SET expPoly = poly->GetLibraryPolyShape();
169 const int expPolyWidth = poly->GetWidth();
170
171 fp->Add( pad.release(), ADD_MODE::APPEND, true );
172 board->Add( fp.release(), ADD_MODE::APPEND, true );
173
174 TEMP_FILE_HOLDER tmp( "kicad_qa_custom_pad_primitive_roundtrip", ".kicad_pcb" );
175 KI_TEST::DumpBoardToFile( *board, tmp.Path().string() );
176
177 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFileOrStream( tmp.Path().string() );
178 BOOST_REQUIRE( reloaded );
179
180 FOOTPRINT* fpFound = reloaded->Footprints().empty() ? nullptr : reloaded->Footprints().front();
181 BOOST_REQUIRE_MESSAGE( fpFound, "Footprint not found in reloaded board" );
182 BOOST_REQUIRE_MESSAGE( !fpFound->Pads().empty(), "Pad not found in reloaded footprint" );
183
184 PAD* padFound = fpFound->Pads().front();
185
186 PCB_SHAPE* bezierFound = findPrimitive( padFound, SHAPE_T::BEZIER );
187 BOOST_REQUIRE_MESSAGE( bezierFound, "Bezier primitive not found in reloaded pad" );
188
189 BOOST_CHECK_EQUAL( bezierFound->GetLibraryStart().x, expBezierStart.x );
190 BOOST_CHECK_EQUAL( bezierFound->GetLibraryStart().y, expBezierStart.y );
191 BOOST_CHECK_EQUAL( bezierFound->GetLibraryBezierC1().x, expBezierC1.x );
192 BOOST_CHECK_EQUAL( bezierFound->GetLibraryBezierC1().y, expBezierC1.y );
193 BOOST_CHECK_EQUAL( bezierFound->GetLibraryBezierC2().x, expBezierC2.x );
194 BOOST_CHECK_EQUAL( bezierFound->GetLibraryBezierC2().y, expBezierC2.y );
195 BOOST_CHECK_EQUAL( bezierFound->GetLibraryEnd().x, expBezierEnd.x );
196 BOOST_CHECK_EQUAL( bezierFound->GetLibraryEnd().y, expBezierEnd.y );
197 BOOST_CHECK_EQUAL( bezierFound->GetWidth(), expBezierWidth );
198
199 PCB_SHAPE* polyFound = findPrimitive( padFound, SHAPE_T::POLY );
200 BOOST_REQUIRE_MESSAGE( polyFound, "Polygon primitive not found in reloaded pad" );
201 BOOST_REQUIRE( polyFound->IsPolyShapeValid() );
202
203 checkPoly( expPoly, polyFound->GetLibraryPolyShape() );
204 BOOST_CHECK_EQUAL( polyFound->GetWidth(), expPolyWidth );
205}
206
207
208BOOST_AUTO_TEST_CASE( SimplePadPrimitivesOnTransformedFootprint )
209{
210 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
211 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
212
213 fp->SetReference( "U1" );
214
215 // A non-cardinal rotation and an anisotropic scale make any spurious transform on
216 // the primitives obvious.
217 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 25.0 ) ) );
218 fp->SetOrientation( EDA_ANGLE( 30.0, DEGREES_T ) );
219 fp->SetTransformScale( 2.0, 1.5 );
220
221 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
222 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
223 pad->SetNumber( "1" );
224 pad->SetAttribute( PAD_ATTRIB::SMD );
225 pad->SetLayerSet( LSET( { F_Cu } ) );
226 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
227 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
229
230 PCB_SHAPE* seg = new PCB_SHAPE( pad.get() );
232 seg->SetStart( VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
233 seg->SetEnd( VECTOR2I( mm( 6.0 ), mm( 2.0 ) ) );
234 seg->SetWidth( mm( 0.1 ) );
235 pad->AddPrimitive( PADSTACK::ALL_LAYERS, seg );
236
237 PCB_SHAPE* circle = new PCB_SHAPE( pad.get() );
238 circle->SetShape( SHAPE_T::CIRCLE );
239 circle->SetCenter( VECTOR2I( mm( 3.0 ), mm( 3.0 ) ) );
240 circle->SetEnd( VECTOR2I( mm( 4.0 ), mm( 3.0 ) ) );
241 circle->SetWidth( mm( 0.01 ) );
242 pad->AddPrimitive( PADSTACK::ALL_LAYERS, circle );
243
244 // Capture the in-memory (pre-save) geometry as the round-trip reference.
245 const VECTOR2I expSegStart = seg->GetLibraryStart();
246 const VECTOR2I expSegEnd = seg->GetLibraryEnd();
247 const int expSegWidth = seg->GetWidth();
248 const VECTOR2I expCircleCenter = circle->GetLibraryStart();
249 const int expCircleRadius = KiROUND( circle->GetStart().Distance( circle->GetEnd() ) );
250
251 fp->Add( pad.release(), ADD_MODE::APPEND, true );
252 board->Add( fp.release(), ADD_MODE::APPEND, true );
253
254 TEMP_FILE_HOLDER tmp( "kicad_qa_custom_pad_primitive_roundtrip2", ".kicad_pcb" );
255 KI_TEST::DumpBoardToFile( *board, tmp.Path().string() );
256
257 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFileOrStream( tmp.Path().string() );
258 BOOST_REQUIRE( reloaded );
259
260 FOOTPRINT* fpFound = reloaded->Footprints().empty() ? nullptr : reloaded->Footprints().front();
261 BOOST_REQUIRE_MESSAGE( fpFound, "Footprint not found in reloaded board" );
262 BOOST_REQUIRE_MESSAGE( !fpFound->Pads().empty(), "Pad not found in reloaded footprint" );
263
264 PAD* padFound = fpFound->Pads().front();
265
266 PCB_SHAPE* segFound = findPrimitive( padFound, SHAPE_T::SEGMENT );
267 BOOST_REQUIRE_MESSAGE( segFound, "Segment primitive not found in reloaded pad" );
268
269 BOOST_CHECK_EQUAL( segFound->GetLibraryStart().x, expSegStart.x );
270 BOOST_CHECK_EQUAL( segFound->GetLibraryStart().y, expSegStart.y );
271 BOOST_CHECK_EQUAL( segFound->GetLibraryEnd().x, expSegEnd.x );
272 BOOST_CHECK_EQUAL( segFound->GetLibraryEnd().y, expSegEnd.y );
273 BOOST_CHECK_EQUAL( segFound->GetWidth(), expSegWidth );
274
275 PCB_SHAPE* circleFound = findPrimitive( padFound, SHAPE_T::ELLIPSE );
276 BOOST_REQUIRE_MESSAGE( circleFound, "Elipse primitive not found in reloaded pad" );
277
278 BOOST_CHECK_EQUAL( circleFound->GetLibraryStart().x, expCircleCenter.x );
279 BOOST_CHECK_EQUAL( circleFound->GetLibraryStart().y, expCircleCenter.y );
280 BOOST_CHECK_EQUAL( KiROUND( circleFound->GetStart().Distance( circleFound->GetEnd() ) ), expCircleRadius );
281}
282
283
284
285BOOST_AUTO_TEST_CASE( ScaledFootprintRoundTripStable )
286{
287 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
288 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
289 fp->SetReference( "U1" );
290 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
291 fp->SetTransformScale( 2.0, 1.5 );
292
293 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
294 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
295 pad->SetNumber( "1" );
296 pad->SetAttribute( PAD_ATTRIB::SMD );
297 pad->SetLayerSet( LSET( { F_Cu } ) );
298 pad->SetLibSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
299 fp->Add( pad.release(), ADD_MODE::APPEND, true );
300 board->Add( fp.release(), ADD_MODE::APPEND, true );
301
302 TEMP_FILE_HOLDER tmp( "kicad_qa_scaled_fp_roundtrip", ".kicad_pcb" );
303 KI_TEST::DumpBoardToFile( *board, tmp.Path().string() );
304 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFileOrStream( tmp.Path().string() );
305 BOOST_REQUIRE( reloaded );
306
307 FOOTPRINT* r = reloaded->Footprints().front();
308 BOOST_CHECK_CLOSE( r->GetScaleX(), 2.0, 1e-6 );
309 BOOST_CHECK_CLOSE( r->GetScaleY(), 1.5, 1e-6 );
310}
311
312
313BOOST_AUTO_TEST_CASE( PadPositionAndOrientationRoundTripStable )
314{
315 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
316 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
317 fp->SetReference( "U1" );
318 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
319 fp->SetOrientation( EDA_ANGLE( 30.0, DEGREES_T ) );
320 fp->SetTransformScale( 2.0, 1.5 );
321
322 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
323 pad->SetNumber( "1" );
324 pad->SetAttribute( PAD_ATTRIB::SMD );
325 pad->SetLayerSet( LSET( { F_Cu } ) );
327 pad->SetLibSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 0.5 ) ) );
328 pad->SetPosition( VECTOR2I( mm( 53.0 ), mm( 51.0 ) ) );
329 pad->SetOrientation( EDA_ANGLE( 75.0, DEGREES_T ) );
330 PAD* padPtr = pad.get();
331 fp->Add( pad.release(), ADD_MODE::APPEND, true );
332 board->Add( fp.release(), ADD_MODE::APPEND, true );
333
334 const VECTOR2I posBefore = padPtr->GetPosition();
335 const EDA_ANGLE orientBefore = padPtr->GetOrientation();
336
337 TEMP_FILE_HOLDER tmp( "kicad_qa_pad_pos_orient_roundtrip", ".kicad_pcb" );
338 KI_TEST::DumpBoardToFile( *board, tmp.Path().string() );
339 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFileOrStream( tmp.Path().string() );
340 BOOST_REQUIRE( reloaded );
341
342 PAD* r = reloaded->Footprints().front()->Pads().front();
343 BOOST_CHECK_EQUAL( r->GetPosition().x, posBefore.x );
344 BOOST_CHECK_EQUAL( r->GetPosition().y, posBefore.y );
345 BOOST_CHECK_CLOSE( r->GetOrientation().AsDegrees(), orientBefore.AsDegrees(), 1e-6 );
346}
347
348
349BOOST_AUTO_TEST_CASE( PadDeltaRoundTripStable )
350{
351 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
352 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
353 fp->SetReference( "U1" );
354 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
355 fp->SetTransformScale( 2.0, 1.5 );
356
357 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
358 pad->SetNumber( "1" );
359 pad->SetAttribute( PAD_ATTRIB::SMD );
360 pad->SetLayerSet( LSET( { F_Cu } ) );
362 pad->SetLibSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
363 pad->SetDelta( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 0.4 ), mm( 0.0 ) ) );
364 PAD* padPtr = pad.get();
365 fp->Add( pad.release(), ADD_MODE::APPEND, true );
366 board->Add( fp.release(), ADD_MODE::APPEND, true );
367
368 const VECTOR2I before = padPtr->GetDelta( PADSTACK::ALL_LAYERS );
369
370 TEMP_FILE_HOLDER tmp( "kicad_qa_pad_delta_roundtrip", ".kicad_pcb" );
371 KI_TEST::DumpBoardToFile( *board, tmp.Path().string() );
372 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFileOrStream( tmp.Path().string() );
373 BOOST_REQUIRE( reloaded );
374
375 PAD* r = reloaded->Footprints().front()->Pads().front();
376 BOOST_TEST_MESSAGE( "delta before: " << before.x << "," << before.y
377 << " after: " << r->GetDelta( PADSTACK::ALL_LAYERS ).x << ","
381}
382
383
384BOOST_AUTO_TEST_CASE( PadOffsetRoundTripStable )
385{
386 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
387 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
388 fp->SetReference( "U1" );
389 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
390 fp->SetTransformScale( 2.0, 1.5 );
391
392 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
393 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
394 pad->SetNumber( "1" );
395 pad->SetAttribute( PAD_ATTRIB::SMD );
396 pad->SetLayerSet( LSET( { F_Cu } ) );
398 pad->SetLibSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
399 pad->SetLibOffset( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 0.3 ), mm( 0.2 ) ) );
400 PAD* padPtr = pad.get();
401 fp->Add( pad.release(), ADD_MODE::APPEND, true );
402 board->Add( fp.release(), ADD_MODE::APPEND, true );
403
404 const VECTOR2I before = padPtr->GetOffset( PADSTACK::ALL_LAYERS );
405
406 TEMP_FILE_HOLDER tmp( "kicad_qa_pad_offset_roundtrip", ".kicad_pcb" );
407 KI_TEST::DumpBoardToFile( *board, tmp.Path().string() );
408 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFileOrStream( tmp.Path().string() );
409 BOOST_REQUIRE( reloaded );
410
411 PAD* r = reloaded->Footprints().front()->Pads().front();
412 BOOST_TEST_MESSAGE( "offset before: " << before.x << "," << before.y
413 << " after: " << r->GetOffset( PADSTACK::ALL_LAYERS ).x << ","
417}
418
419
420BOOST_AUTO_TEST_CASE( FootprintPointRoundTripStable )
421{
422 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
423 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
424 fp->SetReference( "U1" );
425 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
426 fp->SetOrientation( EDA_ANGLE( 30.0, DEGREES_T ) );
427 fp->SetTransformScale( 2.0, 1.5 );
428
429 std::unique_ptr<PCB_POINT> pt = std::make_unique<PCB_POINT>( fp.get() );
430 pt->SetLayer( F_Cu );
431 pt->SetPosition( VECTOR2I( mm( 55.0 ), mm( 52.0 ) ) );
432 PCB_POINT* ptPtr = pt.get();
433 fp->Add( pt.release(), ADD_MODE::APPEND, true );
434 board->Add( fp.release(), ADD_MODE::APPEND, true );
435
436 const VECTOR2I before = ptPtr->GetPosition();
437
438 TEMP_FILE_HOLDER tmp( "kicad_qa_fp_point_roundtrip", ".kicad_pcb" );
439 KI_TEST::DumpBoardToFile( *board, tmp.Path().string() );
440 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFileOrStream( tmp.Path().string() );
441 BOOST_REQUIRE( reloaded );
442
443 FOOTPRINT* r = reloaded->Footprints().front();
444 BOOST_REQUIRE( !r->Points().empty() );
445 PCB_POINT* rp = r->Points().front();
446 BOOST_TEST_MESSAGE( "point before: " << before.x << "," << before.y << " after: " << rp->GetPosition().x << ","
447 << rp->GetPosition().y );
448 BOOST_CHECK_EQUAL( rp->GetPosition().x, before.x );
449 BOOST_CHECK_EQUAL( rp->GetPosition().y, before.y );
450}
451
452
453BOOST_AUTO_TEST_CASE( RotatedPadSizeRoundTripStable )
454{
455 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
456 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
457 fp->SetReference( "U1" );
458 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
459 fp->SetTransformScale( 2.0, 1.5 );
460
461 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
462 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
463 pad->SetNumber( "1" );
464 pad->SetAttribute( PAD_ATTRIB::SMD );
465 pad->SetLayerSet( LSET( { F_Cu } ) );
467 pad->SetLibSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 0.5 ) ) );
468 pad->SetOrientation( EDA_ANGLE( 90.0, DEGREES_T ) );
469 PAD* padPtr = pad.get();
470 fp->Add( pad.release(), ADD_MODE::APPEND, true );
471 board->Add( fp.release(), ADD_MODE::APPEND, true );
472
473 const VECTOR2I boardSizeBefore = padPtr->GetSize( PADSTACK::ALL_LAYERS );
474
475 TEMP_FILE_HOLDER tmp( "kicad_qa_rotated_pad_roundtrip", ".kicad_pcb" );
476 KI_TEST::DumpBoardToFile( *board, tmp.Path().string() );
477 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFileOrStream( tmp.Path().string() );
478 BOOST_REQUIRE( reloaded );
479
480 PAD* r = reloaded->Footprints().front()->Pads().front();
481 BOOST_CHECK_EQUAL( r->GetSize( PADSTACK::ALL_LAYERS ).x, boardSizeBefore.x );
482 BOOST_CHECK_EQUAL( r->GetSize( PADSTACK::ALL_LAYERS ).y, boardSizeBefore.y );
483}
484
485
486// A footprint X-scale must stretch a pad along the footprint's X axis whatever the pad's
487// own orientation, so the scale is conjugated into the pad frame (a 90deg pad swaps X/Y).
488BOOST_AUTO_TEST_CASE( PadSizeScalesAlongFootprintAxisRegardlessOfPadOrientation )
489{
490 BOARD board;
491 FOOTPRINT* fp = new FOOTPRINT( &board );
492 fp->SetPosition( VECTOR2I( 0, 0 ) );
493 fp->SetOrientation( ANGLE_0 );
494 board.Add( fp );
495
496 auto makePad =
497 [&]( const EDA_ANGLE& aRelOrient )
498 {
499 PAD* pad = new PAD( fp );
500 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
501 pad->SetAttribute( PAD_ATTRIB::SMD );
502 pad->SetLayerSet( LSET( { F_Cu } ) );
504 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 0.5 ) ) );
505 pad->SetOrientation( aRelOrient );
506 fp->Add( pad );
507 return pad;
508 };
509
510 PAD* pad0 = makePad( ANGLE_0 );
511 PAD* pad90 = makePad( EDA_ANGLE( 90.0, DEGREES_T ) );
512
513 const int xBefore0 = pad0->GetBoundingBox().GetWidth();
514 const int xBefore90 = pad90->GetBoundingBox().GetWidth();
515
516 fp->SetTransformScale( 2.0, 1.0 );
517
518 BOOST_CHECK_EQUAL( pad0->GetSize( PADSTACK::ALL_LAYERS ).x, mm( 2.0 ) );
519 BOOST_CHECK_EQUAL( pad0->GetSize( PADSTACK::ALL_LAYERS ).y, mm( 0.5 ) );
520 BOOST_CHECK_EQUAL( pad90->GetSize( PADSTACK::ALL_LAYERS ).x, mm( 1.0 ) );
521 BOOST_CHECK_EQUAL( pad90->GetSize( PADSTACK::ALL_LAYERS ).y, mm( 1.0 ) );
522
523 // Both pads double their board-X extent, regardless of orientation.
524 BOOST_CHECK_EQUAL( pad0->GetBoundingBox().GetWidth(), 2 * xBefore0 );
525 BOOST_CHECK_EQUAL( pad90->GetBoundingBox().GetWidth(), 2 * xBefore90 );
526}
527
528
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
General utilities for PCB file IO for QA programs.
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1524
constexpr size_type GetWidth() const
Definition box2.h:211
double AsDegrees() const
Definition eda_angle.h:115
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
bool IsPolyShapeValid() const
void SetPosition(const VECTOR2I &aPos) override
PCB_POINTS & Points()
Definition footprint.h:420
void SetOrientation(const EDA_ANGLE &aNewAngle)
std::deque< PAD * > & Pads()
Definition footprint.h:405
double GetScaleX() const
Definition footprint.h:456
void SetTransformScale(double aScaleX, double aScaleY)
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
double GetScaleY() const
Definition footprint.h:457
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:170
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
Definition pad.h:61
const std::vector< std::shared_ptr< PCB_SHAPE > > & GetPrimitives(PCB_LAYER_ID aLayer) const
Accessor to the basic shape list for custom-shaped pads.
Definition pad.h:375
const BOX2I GetBoundingBox() const override
The bounding box is cached, so this will be efficient most of the time.
Definition pad.cpp:1635
const VECTOR2I & GetDelta(PCB_LAYER_ID aLayer) const
Definition pad.h:307
VECTOR2I GetPosition() const override
Definition pad.cpp:256
VECTOR2I GetOffset(PCB_LAYER_ID aLayer) const
Definition pad.cpp:838
VECTOR2I GetSize(PCB_LAYER_ID aLayer) const
Definition pad.cpp:298
EDA_ANGLE GetOrientation() const
Return the rotation angle of the pad.
Definition pad.cpp:1757
A PCB_POINT is a 0-dimensional point that is used to mark a position on a PCB, or more usually a foot...
Definition pcb_point.h:39
VECTOR2I GetPosition() const override
Definition pcb_point.h:60
void SetBezierC1(const VECTOR2I &aPt) override
VECTOR2I GetLibraryBezierC1() const
void SetWidth(int aWidth) override
int GetWidth() const override
void SetShape(SHAPE_T aShape) override
Definition pcb_shape.h:209
void SetEnd(const VECTOR2I &aEnd) override
VECTOR2I GetLibraryEnd() const
Definition pcb_shape.h:230
void SetPolyShape(const SHAPE_POLY_SET &aShape) override
VECTOR2I GetLibraryStart() const
Definition pcb_shape.h:229
void SetStart(const VECTOR2I &aStart) override
void SetBezierC2(const VECTOR2I &aPt) override
SHAPE_POLY_SET GetLibraryPolyShape() const
VECTOR2I GetLibraryBezierC2() const
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
Represent a set of closed polygons.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:574
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
@ DEGREES_T
Definition eda_angle.h:30
SHAPE_T
Definition eda_shape.h:54
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
@ F_Cu
Definition layer_ids.h:60
std::unique_ptr< BOARD > ReadBoardFromFileOrStream(const std::string &aFilename, std::istream &aFallback)
Read a board from a file, or another stream, as appropriate.
void DumpBoardToFile(BOARD &board, const std::filesystem::path &aFilename)
Utility function to simply write a Board out to a file.
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:98
@ TRAPEZOID
Definition padstack.h:55
@ RECTANGLE
Definition padstack.h:53
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(BezierAndPolyOnTransformedFootprint)
A custom pad's bezier and polygon primitives must survive a save/reload unchanged,...
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
const SHAPE_LINE_CHAIN chain
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708