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
20#include <qa_utils/file_utils.h>
22
23#include <base_units.h>
24#include <board.h>
25#include <footprint.h>
26#include <pad.h>
27#include <pcb_point.h>
28#include <pcb_shape.h>
29#include <geometry/eda_angle.h>
32
34
35#include <filesystem>
36#include <memory>
37
38
39BOOST_AUTO_TEST_SUITE( CustomPadPrimitiveRoundTrip )
40
41
42namespace
43{
44
45int mm( double aMillimetres )
46{
47 return pcbIUScale.mmToIU( aMillimetres );
48}
49
50
51PCB_SHAPE* findPrimitive( const PAD* aPad, SHAPE_T aShape )
52{
53 for( const std::shared_ptr<PCB_SHAPE>& prim : aPad->GetPrimitives( PADSTACK::ALL_LAYERS ) )
54 {
55 if( prim->GetShape() == aShape )
56 return prim.get();
57 }
58
59 return nullptr;
60}
61
62
63void checkPoly( const SHAPE_POLY_SET& aExpected, const SHAPE_POLY_SET& aGot )
64{
65 BOOST_REQUIRE_EQUAL( aExpected.OutlineCount(), aGot.OutlineCount() );
66
67 const SHAPE_LINE_CHAIN& expOutline = aExpected.COutline( 0 );
68 const SHAPE_LINE_CHAIN& gotOutline = aGot.COutline( 0 );
69
70 BOOST_REQUIRE_EQUAL( expOutline.PointCount(), gotOutline.PointCount() );
71
72 for( int ii = 0; ii < expOutline.PointCount(); ++ii )
73 {
74 BOOST_CHECK_EQUAL( gotOutline.CPoint( ii ).x, expOutline.CPoint( ii ).x );
75 BOOST_CHECK_EQUAL( gotOutline.CPoint( ii ).y, expOutline.CPoint( ii ).y );
76 }
77}
78
79} // namespace
80
81
89BOOST_AUTO_TEST_CASE( BezierAndPolyOnTransformedFootprint )
90{
91 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
92 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
93
94 fp->SetReference( "U1" );
95
96 // A non-cardinal rotation and an anisotropic scale make any spurious transform on
97 // the primitives obvious.
98 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 25.0 ) ) );
99 fp->SetOrientation( EDA_ANGLE( 30.0, DEGREES_T ) );
100 fp->SetTransformScale( 2.0, 1.5 );
101
102 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
103 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
104 pad->SetNumber( "1" );
105 pad->SetAttribute( PAD_ATTRIB::SMD );
106 pad->SetLayerSet( LSET( { F_Cu } ) );
107 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
108 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
110
111 // Bezier primitive with deliberately asymmetric control points.
112 PCB_SHAPE* bezier = new PCB_SHAPE( pad.get() );
113 bezier->SetShape( SHAPE_T::BEZIER );
114 bezier->SetStart( VECTOR2I( mm( 0.0 ), mm( 0.0 ) ) );
115 bezier->SetBezierC1( VECTOR2I( mm( 1.0 ), mm( 2.0 ) ) );
116 bezier->SetBezierC2( VECTOR2I( mm( 3.0 ), mm( -1.0 ) ) );
117 bezier->SetEnd( VECTOR2I( mm( 4.0 ), mm( 0.5 ) ) );
118 bezier->SetWidth( mm( 0.1 ) );
119 pad->AddPrimitive( PADSTACK::ALL_LAYERS, bezier );
120
121 // Polygon primitive with an asymmetric (non-rectangular) outline.
123 chain.Append( VECTOR2I( mm( 1.0 ), mm( 0.0 ) ) );
124 chain.Append( VECTOR2I( mm( 3.0 ), mm( 1.0 ) ) );
125 chain.Append( VECTOR2I( mm( 2.0 ), mm( 4.0 ) ) );
126 chain.Append( VECTOR2I( mm( -1.0 ), mm( 2.0 ) ) );
127 chain.SetClosed( true );
128
129 SHAPE_POLY_SET polySet;
130 polySet.AddOutline( chain );
131
132 PCB_SHAPE* poly = new PCB_SHAPE( pad.get() );
133 poly->SetShape( SHAPE_T::POLY );
134 poly->SetPolyShape( polySet );
135 poly->SetWidth( mm( 0.1 ) );
136 pad->AddPrimitive( PADSTACK::ALL_LAYERS, poly );
137
138 // Capture the in-memory (pre-save) geometry as the round-trip reference.
139 const VECTOR2I expBezierStart = bezier->GetLibraryStart();
140 const VECTOR2I expBezierC1 = bezier->GetLibraryBezierC1();
141 const VECTOR2I expBezierC2 = bezier->GetLibraryBezierC2();
142 const VECTOR2I expBezierEnd = bezier->GetLibraryEnd();
143 const int expBezierWidth = bezier->GetWidth();
144 const SHAPE_POLY_SET expPoly = poly->GetLibraryPolyShape();
145 const int expPolyWidth = poly->GetWidth();
146
147 fp->Add( pad.release(), ADD_MODE::APPEND, true );
148 board->Add( fp.release(), ADD_MODE::APPEND, true );
149
150 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_custom_pad_primitive_roundtrip" );
151 std::filesystem::path boardFile = tmp.Path() / "board.kicad_pcb";
152
153 KI_TEST::DumpBoardToFile( *board, boardFile );
154
155 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFile( boardFile );
156 BOOST_REQUIRE( reloaded );
157
158 FOOTPRINT* fpFound = reloaded->Footprints().empty() ? nullptr : reloaded->Footprints().front();
159 BOOST_REQUIRE_MESSAGE( fpFound, "Footprint not found in reloaded board" );
160 BOOST_REQUIRE_MESSAGE( !fpFound->Pads().empty(), "Pad not found in reloaded footprint" );
161
162 PAD* padFound = fpFound->Pads().front();
163
164 PCB_SHAPE* bezierFound = findPrimitive( padFound, SHAPE_T::BEZIER );
165 BOOST_REQUIRE_MESSAGE( bezierFound, "Bezier primitive not found in reloaded pad" );
166
167 BOOST_CHECK_EQUAL( bezierFound->GetLibraryStart().x, expBezierStart.x );
168 BOOST_CHECK_EQUAL( bezierFound->GetLibraryStart().y, expBezierStart.y );
169 BOOST_CHECK_EQUAL( bezierFound->GetLibraryBezierC1().x, expBezierC1.x );
170 BOOST_CHECK_EQUAL( bezierFound->GetLibraryBezierC1().y, expBezierC1.y );
171 BOOST_CHECK_EQUAL( bezierFound->GetLibraryBezierC2().x, expBezierC2.x );
172 BOOST_CHECK_EQUAL( bezierFound->GetLibraryBezierC2().y, expBezierC2.y );
173 BOOST_CHECK_EQUAL( bezierFound->GetLibraryEnd().x, expBezierEnd.x );
174 BOOST_CHECK_EQUAL( bezierFound->GetLibraryEnd().y, expBezierEnd.y );
175 BOOST_CHECK_EQUAL( bezierFound->GetWidth(), expBezierWidth );
176
177 PCB_SHAPE* polyFound = findPrimitive( padFound, SHAPE_T::POLY );
178 BOOST_REQUIRE_MESSAGE( polyFound, "Polygon primitive not found in reloaded pad" );
179 BOOST_REQUIRE( polyFound->IsPolyShapeValid() );
180
181 checkPoly( expPoly, polyFound->GetLibraryPolyShape() );
182 BOOST_CHECK_EQUAL( polyFound->GetWidth(), expPolyWidth );
183}
184
185
186BOOST_AUTO_TEST_CASE( SimplePadPrimitivesOnTransformedFootprint )
187{
188 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
189 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
190
191 fp->SetReference( "U1" );
192
193 // A non-cardinal rotation and an anisotropic scale make any spurious transform on
194 // the primitives obvious.
195 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 25.0 ) ) );
196 fp->SetOrientation( EDA_ANGLE( 30.0, DEGREES_T ) );
197 fp->SetTransformScale( 2.0, 1.5 );
198
199 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
200 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
201 pad->SetNumber( "1" );
202 pad->SetAttribute( PAD_ATTRIB::SMD );
203 pad->SetLayerSet( LSET( { F_Cu } ) );
204 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
205 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
207
208 PCB_SHAPE* seg = new PCB_SHAPE( pad.get() );
210 seg->SetStart( VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
211 seg->SetEnd( VECTOR2I( mm( 6.0 ), mm( 2.0 ) ) );
212 seg->SetWidth( mm( 0.1 ) );
213 pad->AddPrimitive( PADSTACK::ALL_LAYERS, seg );
214
215 PCB_SHAPE* circle = new PCB_SHAPE( pad.get() );
216 circle->SetShape( SHAPE_T::CIRCLE );
217 circle->SetCenter( VECTOR2I( mm( 3.0 ), mm( 3.0 ) ) );
218 circle->SetEnd( VECTOR2I( mm( 4.0 ), mm( 3.0 ) ) );
219 circle->SetWidth( mm( 0.01 ) );
220 pad->AddPrimitive( PADSTACK::ALL_LAYERS, circle );
221
222 // Capture the in-memory (pre-save) geometry as the round-trip reference.
223 const VECTOR2I expSegStart = seg->GetLibraryStart();
224 const VECTOR2I expSegEnd = seg->GetLibraryEnd();
225 const int expSegWidth = seg->GetWidth();
226 const VECTOR2I expCircleCenter = circle->GetLibraryStart();
227 const int expCircleRadius = KiROUND( circle->GetStart().Distance( circle->GetEnd() ) );
228
229 fp->Add( pad.release(), ADD_MODE::APPEND, true );
230 board->Add( fp.release(), ADD_MODE::APPEND, true );
231
232 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_custom_pad_primitive_roundtrip2" );
233 std::filesystem::path boardFile = tmp.Path() / "board.kicad_pcb";
234
235 KI_TEST::DumpBoardToFile( *board, boardFile );
236
237 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFile( boardFile );
238 BOOST_REQUIRE( reloaded );
239
240 FOOTPRINT* fpFound = reloaded->Footprints().empty() ? nullptr : reloaded->Footprints().front();
241 BOOST_REQUIRE_MESSAGE( fpFound, "Footprint not found in reloaded board" );
242 BOOST_REQUIRE_MESSAGE( !fpFound->Pads().empty(), "Pad not found in reloaded footprint" );
243
244 PAD* padFound = fpFound->Pads().front();
245
246 PCB_SHAPE* segFound = findPrimitive( padFound, SHAPE_T::SEGMENT );
247 BOOST_REQUIRE_MESSAGE( segFound, "Segment primitive not found in reloaded pad" );
248
249 BOOST_CHECK_EQUAL( segFound->GetLibraryStart().x, expSegStart.x );
250 BOOST_CHECK_EQUAL( segFound->GetLibraryStart().y, expSegStart.y );
251 BOOST_CHECK_EQUAL( segFound->GetLibraryEnd().x, expSegEnd.x );
252 BOOST_CHECK_EQUAL( segFound->GetLibraryEnd().y, expSegEnd.y );
253 BOOST_CHECK_EQUAL( segFound->GetWidth(), expSegWidth );
254
255 PCB_SHAPE* circleFound = findPrimitive( padFound, SHAPE_T::ELLIPSE );
256 BOOST_REQUIRE_MESSAGE( circleFound, "Elipse primitive not found in reloaded pad" );
257
258 BOOST_CHECK_EQUAL( circleFound->GetLibraryStart().x, expCircleCenter.x );
259 BOOST_CHECK_EQUAL( circleFound->GetLibraryStart().y, expCircleCenter.y );
260 BOOST_CHECK_EQUAL( KiROUND( circleFound->GetStart().Distance( circleFound->GetEnd() ) ), expCircleRadius );
261}
262
263
264
265BOOST_AUTO_TEST_CASE( ScaledFootprintRoundTripStable )
266{
267 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
268 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
269 fp->SetReference( "U1" );
270 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
271 fp->SetTransformScale( 2.0, 1.5 );
272
273 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
274 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
275 pad->SetNumber( "1" );
276 pad->SetAttribute( PAD_ATTRIB::SMD );
277 pad->SetLayerSet( LSET( { F_Cu } ) );
278 pad->SetLibSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
279 fp->Add( pad.release(), ADD_MODE::APPEND, true );
280 board->Add( fp.release(), ADD_MODE::APPEND, true );
281
282 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_scaled_fp_roundtrip" );
283 std::filesystem::path boardFile = tmp.Path() / "board.kicad_pcb";
284
285 KI_TEST::DumpBoardToFile( *board, boardFile );
286 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFile( boardFile );
287 BOOST_REQUIRE( reloaded );
288
289 FOOTPRINT* r = reloaded->Footprints().front();
290 BOOST_CHECK_CLOSE( r->GetScaleX(), 2.0, 1e-6 );
291 BOOST_CHECK_CLOSE( r->GetScaleY(), 1.5, 1e-6 );
292}
293
294
295BOOST_AUTO_TEST_CASE( PadPositionAndOrientationRoundTripStable )
296{
297 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
298 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
299 fp->SetReference( "U1" );
300 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
301 fp->SetOrientation( EDA_ANGLE( 30.0, DEGREES_T ) );
302 fp->SetTransformScale( 2.0, 1.5 );
303
304 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
305 pad->SetNumber( "1" );
306 pad->SetAttribute( PAD_ATTRIB::SMD );
307 pad->SetLayerSet( LSET( { F_Cu } ) );
309 pad->SetLibSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 0.5 ) ) );
310 pad->SetPosition( VECTOR2I( mm( 53.0 ), mm( 51.0 ) ) );
311 pad->SetOrientation( EDA_ANGLE( 75.0, DEGREES_T ) );
312 PAD* padPtr = pad.get();
313 fp->Add( pad.release(), ADD_MODE::APPEND, true );
314 board->Add( fp.release(), ADD_MODE::APPEND, true );
315
316 const VECTOR2I posBefore = padPtr->GetPosition();
317 const EDA_ANGLE orientBefore = padPtr->GetOrientation();
318
319 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_pad_pos_orient_roundtrip" );
320 std::filesystem::path boardFile = tmp.Path() / "board.kicad_pcb";
321
322 KI_TEST::DumpBoardToFile( *board, boardFile );
323 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFile( boardFile );
324 BOOST_REQUIRE( reloaded );
325
326 PAD* r = reloaded->Footprints().front()->Pads().front();
327 BOOST_CHECK_EQUAL( r->GetPosition().x, posBefore.x );
328 BOOST_CHECK_EQUAL( r->GetPosition().y, posBefore.y );
329 BOOST_CHECK_CLOSE( r->GetOrientation().AsDegrees(), orientBefore.AsDegrees(), 1e-6 );
330}
331
332
333BOOST_AUTO_TEST_CASE( PadDeltaRoundTripStable )
334{
335 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
336 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
337 fp->SetReference( "U1" );
338 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
339 fp->SetTransformScale( 2.0, 1.5 );
340
341 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
342 pad->SetNumber( "1" );
343 pad->SetAttribute( PAD_ATTRIB::SMD );
344 pad->SetLayerSet( LSET( { F_Cu } ) );
346 pad->SetLibSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
347 pad->SetDelta( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 0.4 ), mm( 0.0 ) ) );
348 PAD* padPtr = pad.get();
349 fp->Add( pad.release(), ADD_MODE::APPEND, true );
350 board->Add( fp.release(), ADD_MODE::APPEND, true );
351
352 const VECTOR2I before = padPtr->GetDelta( PADSTACK::ALL_LAYERS );
353
354 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_pad_delta_roundtrip" );
355 std::filesystem::path boardFile = tmp.Path() / "board.kicad_pcb";
356
357 KI_TEST::DumpBoardToFile( *board, boardFile );
358 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFile( boardFile );
359 BOOST_REQUIRE( reloaded );
360
361 PAD* r = reloaded->Footprints().front()->Pads().front();
362 BOOST_TEST_MESSAGE( "delta before: " << before.x << "," << before.y
363 << " after: " << r->GetDelta( PADSTACK::ALL_LAYERS ).x << ","
367}
368
369
370BOOST_AUTO_TEST_CASE( PadOffsetRoundTripStable )
371{
372 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
373 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
374 fp->SetReference( "U1" );
375 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
376 fp->SetTransformScale( 2.0, 1.5 );
377
378 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
379 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
380 pad->SetNumber( "1" );
381 pad->SetAttribute( PAD_ATTRIB::SMD );
382 pad->SetLayerSet( LSET( { F_Cu } ) );
384 pad->SetLibSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 1.0 ) ) );
385 pad->SetLibOffset( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 0.3 ), mm( 0.2 ) ) );
386 PAD* padPtr = pad.get();
387 fp->Add( pad.release(), ADD_MODE::APPEND, true );
388 board->Add( fp.release(), ADD_MODE::APPEND, true );
389
390 const VECTOR2I before = padPtr->GetOffset( PADSTACK::ALL_LAYERS );
391
392 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_pad_offset_roundtrip" );
393 std::filesystem::path boardFile = tmp.Path() / "board.kicad_pcb";
394
395 KI_TEST::DumpBoardToFile( *board, boardFile );
396 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFile( boardFile );
397 BOOST_REQUIRE( reloaded );
398
399 PAD* r = reloaded->Footprints().front()->Pads().front();
400 BOOST_TEST_MESSAGE( "offset before: " << before.x << "," << before.y
401 << " after: " << r->GetOffset( PADSTACK::ALL_LAYERS ).x << ","
405}
406
407
408BOOST_AUTO_TEST_CASE( FootprintPointRoundTripStable )
409{
410 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
411 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
412 fp->SetReference( "U1" );
413 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
414 fp->SetOrientation( EDA_ANGLE( 30.0, DEGREES_T ) );
415 fp->SetTransformScale( 2.0, 1.5 );
416
417 std::unique_ptr<PCB_POINT> pt = std::make_unique<PCB_POINT>( fp.get() );
418 pt->SetLayer( F_Cu );
419 pt->SetPosition( VECTOR2I( mm( 55.0 ), mm( 52.0 ) ) );
420 PCB_POINT* ptPtr = pt.get();
421 fp->Add( pt.release(), ADD_MODE::APPEND, true );
422 board->Add( fp.release(), ADD_MODE::APPEND, true );
423
424 const VECTOR2I before = ptPtr->GetPosition();
425
426 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_fp_point_roundtrip" );
427 std::filesystem::path boardFile = tmp.Path() / "board.kicad_pcb";
428
429 KI_TEST::DumpBoardToFile( *board, boardFile );
430 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFile( boardFile );
431 BOOST_REQUIRE( reloaded );
432
433 FOOTPRINT* r = reloaded->Footprints().front();
434 BOOST_REQUIRE( !r->Points().empty() );
435 PCB_POINT* rp = r->Points().front();
436 BOOST_TEST_MESSAGE( "point before: " << before.x << "," << before.y << " after: " << rp->GetPosition().x << ","
437 << rp->GetPosition().y );
438 BOOST_CHECK_EQUAL( rp->GetPosition().x, before.x );
439 BOOST_CHECK_EQUAL( rp->GetPosition().y, before.y );
440}
441
442
443BOOST_AUTO_TEST_CASE( RotatedPadSizeRoundTripStable )
444{
445 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
446 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( board.get() );
447 fp->SetReference( "U1" );
448 fp->SetPosition( VECTOR2I( mm( 50.0 ), mm( 50.0 ) ) );
449 fp->SetTransformScale( 2.0, 1.5 );
450
451 std::unique_ptr<PAD> pad = std::make_unique<PAD>( fp.get() );
452 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
453 pad->SetNumber( "1" );
454 pad->SetAttribute( PAD_ATTRIB::SMD );
455 pad->SetLayerSet( LSET( { F_Cu } ) );
457 pad->SetLibSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 0.5 ) ) );
458 pad->SetOrientation( EDA_ANGLE( 90.0, DEGREES_T ) );
459 PAD* padPtr = pad.get();
460 fp->Add( pad.release(), ADD_MODE::APPEND, true );
461 board->Add( fp.release(), ADD_MODE::APPEND, true );
462
463 const VECTOR2I boardSizeBefore = padPtr->GetSize( PADSTACK::ALL_LAYERS );
464
465 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_rotated_pad_roundtrip" );
466 std::filesystem::path boardFile = tmp.Path() / "board.kicad_pcb";
467
468 KI_TEST::DumpBoardToFile( *board, boardFile );
469 std::unique_ptr<BOARD> reloaded = KI_TEST::ReadBoardFromFile( boardFile );
470 BOOST_REQUIRE( reloaded );
471
472 PAD* r = reloaded->Footprints().front()->Pads().front();
473 BOOST_CHECK_EQUAL( r->GetSize( PADSTACK::ALL_LAYERS ).x, boardSizeBefore.x );
474 BOOST_CHECK_EQUAL( r->GetSize( PADSTACK::ALL_LAYERS ).y, boardSizeBefore.y );
475}
476
477
478// A footprint X-scale must stretch a pad along the footprint's X axis whatever the pad's
479// own orientation, so the scale is conjugated into the pad frame (a 90deg pad swaps X/Y).
480BOOST_AUTO_TEST_CASE( PadSizeScalesAlongFootprintAxisRegardlessOfPadOrientation )
481{
482 BOARD board;
483 FOOTPRINT* fp = new FOOTPRINT( &board );
484 fp->SetPosition( VECTOR2I( 0, 0 ) );
485 fp->SetOrientation( ANGLE_0 );
486 board.Add( fp );
487
488 auto makePad =
489 [&]( const EDA_ANGLE& aRelOrient )
490 {
491 PAD* pad = new PAD( fp );
492 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
493 pad->SetAttribute( PAD_ATTRIB::SMD );
494 pad->SetLayerSet( LSET( { F_Cu } ) );
496 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( mm( 1.0 ), mm( 0.5 ) ) );
497 pad->SetOrientation( aRelOrient );
498 fp->Add( pad );
499 return pad;
500 };
501
502 PAD* pad0 = makePad( ANGLE_0 );
503 PAD* pad90 = makePad( EDA_ANGLE( 90.0, DEGREES_T ) );
504
505 const int xBefore0 = pad0->GetBoundingBox().GetWidth();
506 const int xBefore90 = pad90->GetBoundingBox().GetWidth();
507
508 fp->SetTransformScale( 2.0, 1.0 );
509
510 BOOST_CHECK_EQUAL( pad0->GetSize( PADSTACK::ALL_LAYERS ).x, mm( 2.0 ) );
511 BOOST_CHECK_EQUAL( pad0->GetSize( PADSTACK::ALL_LAYERS ).y, mm( 0.5 ) );
512 BOOST_CHECK_EQUAL( pad90->GetSize( PADSTACK::ALL_LAYERS ).x, mm( 1.0 ) );
513 BOOST_CHECK_EQUAL( pad90->GetSize( PADSTACK::ALL_LAYERS ).y, mm( 1.0 ) );
514
515 // Both pads double their board-X extent, regardless of orientation.
516 BOOST_CHECK_EQUAL( pad0->GetBoundingBox().GetWidth(), 2 * xBefore0 );
517 BOOST_CHECK_EQUAL( pad90->GetBoundingBox().GetWidth(), 2 * xBefore90 );
518}
519
520
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: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
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:333
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:283
bool IsPolyShapeValid() const
void SetPosition(const VECTOR2I &aPos) override
PCB_POINTS & Points()
Definition footprint.h:370
void SetOrientation(const EDA_ANGLE &aNewAngle)
std::deque< PAD * > & Pads()
Definition footprint.h:355
double GetScaleX() const
Definition footprint.h:399
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:400
const std::filesystem::path & Path() const
Get the path to the temporary directory as a std::filesystem::path.
Definition file_utils.h:61
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:1673
const VECTOR2I & GetDelta(PCB_LAYER_ID aLayer) const
Definition pad.h:307
VECTOR2I GetPosition() const override
Definition pad.cpp:255
VECTOR2I GetOffset(PCB_LAYER_ID aLayer) const
Definition pad.cpp:837
VECTOR2I GetSize(PCB_LAYER_ID aLayer) const
Definition pad.cpp:297
EDA_ANGLE GetOrientation() const
Return the rotation angle of the pad.
Definition pad.cpp:1795
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:212
void SetEnd(const VECTOR2I &aEnd) override
VECTOR2I GetLibraryEnd() const
Definition pcb_shape.h:233
void SetPolyShape(const SHAPE_POLY_SET &aShape) override
VECTOR2I GetLibraryStart() const
Definition pcb_shape.h:232
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 > ReadBoardFromFile(const std::filesystem::path &aFilename)
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