KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_barcode_load_save.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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <filesystem>
25
27#include <boost/test/unit_test.hpp>
28
29#include <board.h>
30#include <footprint.h>
31#include <pcb_text.h>
32#include <pcb_barcode.h>
37
38BOOST_AUTO_TEST_CASE( BarcodeWriteRead )
39{
40 SETTINGS_MANAGER settingsManager;
41
42 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
43
44 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
45 barcode->SetText( wxT( "12345" ) );
46 barcode->SetLayer( F_SilkS );
47 barcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 2.0 ) ) );
48 barcode->SetWidth( pcbIUScale.mmToIU( 3.0 ) );
49 barcode->SetHeight( pcbIUScale.mmToIU( 3.0 ) );
50 barcode->SetTextSize( pcbIUScale.mmToIU( 1.5 ) );
51 barcode->SetKind( BARCODE_T::QR_CODE );
53 barcode->AssembleBarcode();
54
55 const KIID id = barcode->m_Uuid;
56
57 board->Add( barcode, ADD_MODE::APPEND, true );
58
59 KI_TEST::TEMPORARY_DIRECTORY tempDir( "kicad_qa_barcode_roundtrip", "" );
60 const std::filesystem::path savePath = tempDir.GetPath() / "barcode_roundtrip.kicad_pcb";
61
62 KI_TEST::DumpBoardToFile( *board, savePath.string() );
63 std::unique_ptr<BOARD> board2 = KI_TEST::ReadBoardFromFileOrStream( savePath.string() );
65 PCB_BARCODE& loaded = static_cast<PCB_BARCODE&>( item2 );
66
67 BOOST_CHECK( loaded.GetText() == barcode->GetText() );
68 BOOST_CHECK_EQUAL( (int) loaded.GetKind(), (int) barcode->GetKind() );
69 BOOST_CHECK_EQUAL( (int) loaded.GetErrorCorrection(), (int) barcode->GetErrorCorrection() );
70 BOOST_CHECK_EQUAL( loaded.GetWidth(), barcode->GetWidth() );
71 BOOST_CHECK_EQUAL( loaded.GetHeight(), barcode->GetHeight() );
72 BOOST_CHECK_EQUAL( loaded.GetTextSize(), barcode->GetTextSize() );
73 BOOST_CHECK_EQUAL( loaded.GetPolyShape().BBox().Centre(), barcode->GetPolyShape().BBox().Centre() );
74}
75
76
77BOOST_AUTO_TEST_CASE( BarcodeFootprintWriteRead )
78{
79 SETTINGS_MANAGER settingsManager;
80
81 FOOTPRINT footprint( nullptr );
82
83 PCB_BARCODE* barcode = new PCB_BARCODE( &footprint );
84 barcode->SetText( wxT( "12345" ) );
85 barcode->SetLayer( F_SilkS );
86 barcode->SetPosition( VECTOR2I( 1000000, 2000000 ) );
87 barcode->SetWidth( 3000000 );
88 barcode->SetHeight( 3000000 );
89 barcode->SetTextSize( pcbIUScale.mmToIU( 1.5 ) );
90 barcode->SetKind( BARCODE_T::QR_CODE );
92 barcode->AssembleBarcode();
93
94 const KIID id = barcode->m_Uuid;
95
96 footprint.Add( barcode, ADD_MODE::APPEND, true );
97
98 // Saving a footprint validates its whole containing directory as a library, so use a private
99 // temp directory rather than littering (and reading stray files from) the system temp root.
100 KI_TEST::TEMPORARY_DIRECTORY tempLib( "kicad_qa_barcode_roundtrip", ".pretty" );
101 const std::filesystem::path savePath = tempLib.GetPath() / "barcode_roundtrip.kicad_mod";
102
103 KI_TEST::DumpFootprintToFile( footprint, savePath.string() );
104 std::unique_ptr<FOOTPRINT> footprint2 = KI_TEST::ReadFootprintFromFileOrStream( savePath.string() );
105
106 PCB_BARCODE* loaded = nullptr;
107
108 for( BOARD_ITEM* item : footprint2->GraphicalItems() )
109 {
110 if( item->Type() == PCB_BARCODE_T && item->m_Uuid == id )
111 {
112 loaded = static_cast<PCB_BARCODE*>( item );
113 break;
114 }
115 }
116
117 BOOST_REQUIRE( loaded != nullptr );
118 BOOST_CHECK( loaded->GetText() == barcode->GetText() );
119 BOOST_CHECK_EQUAL( (int) loaded->GetKind(), (int) barcode->GetKind() );
120 BOOST_CHECK_EQUAL( (int) loaded->GetErrorCorrection(), (int) barcode->GetErrorCorrection() );
121 BOOST_CHECK_EQUAL( loaded->GetWidth(), barcode->GetWidth() );
122 BOOST_CHECK_EQUAL( loaded->GetHeight(), barcode->GetHeight() );
123 BOOST_CHECK_EQUAL( loaded->GetTextSize(), barcode->GetTextSize() );
124 BOOST_CHECK_EQUAL( loaded->GetPolyShape().BBox().Centre(), barcode->GetPolyShape().BBox().Centre() );
125}
126
127
128BOOST_AUTO_TEST_CASE( BarcodePositioningAlignment )
129{
130 SETTINGS_MANAGER settingsManager;
131
132 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
133
134 // Test multiple barcode types and positions to ensure consistent alignment
135 struct TestCase
136 {
137 BARCODE_T kind;
138 VECTOR2I position;
139 int width;
140 int height;
141 bool withText;
142 bool knockout;
143 double angle;
144 wxString text;
145 };
146
147 std::vector<TestCase> testCases = {
148 // Basic QR codes at different positions
149 { BARCODE_T::QR_CODE, VECTOR2I( 0, 0 ), 2000000, 2000000, false, false, 0.0, "TEST1" },
150 { BARCODE_T::QR_CODE, VECTOR2I( 5000000, 3000000 ), 3000000, 3000000, false, false, 0.0, "TEST2" },
151 { BARCODE_T::QR_CODE, VECTOR2I( -2000000, -1000000 ), 1500000, 1500000, false, false, 0.0, "TEST3" },
152
153 // With text
154 { BARCODE_T::QR_CODE, VECTOR2I( 1000000, 2000000 ), 2500000, 2500000, true, false, 0.0, "WITHTEXT" },
155
156 // With knockout
157 { BARCODE_T::QR_CODE, VECTOR2I( 2000000, 1000000 ), 2000000, 2000000, false, true, 0.0, "KNOCKOUT" },
158
159 // With rotation
160 { BARCODE_T::QR_CODE, VECTOR2I( 3000000, 2000000 ), 2000000, 2000000, false, false, 45.0, "ROTATED" },
161
162 // Different barcode types
163 { BARCODE_T::CODE_39, VECTOR2I( 4000000, 1000000 ), 3000000, 800000, false, false, 0.0, "CODE39TEST" },
164 { BARCODE_T::CODE_128, VECTOR2I( 1000000, 4000000 ), 3500000, 1000000, false, false, 0.0, "CODE128" },
165 { BARCODE_T::DATA_MATRIX, VECTOR2I( 3000000, 3000000 ), 1800000, 1800000, false, false, 0.0, "DATAMATRIX" },
166 { BARCODE_T::MICRO_QR_CODE, VECTOR2I( 2000000, 4000000 ), 1200000, 1200000, false, false, 0.0, "microQR" },
167
168 // Combined scenarios
169 { BARCODE_T::QR_CODE, VECTOR2I( 1500000, 1500000 ), 2200000, 2200000, true, true, 90.0, "COMPLEX" },
170 };
171
172 for( size_t i = 0; i < testCases.size(); ++i )
173 {
174 const auto& tc = testCases[i];
175
176 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
177 barcode->SetText( tc.text );
178 barcode->SetShowText( false );
179 barcode->SetLayer( F_SilkS );
180 barcode->SetWidth( tc.width );
181 barcode->SetHeight( tc.height );
182 barcode->SetKind( tc.kind );
184
185 barcode->AssembleBarcode();
186 SHAPE_POLY_SET canonicalPoly = barcode->GetPolyShape();
187
188 barcode->SetPosition( tc.position );
189
190 if( tc.angle != 0.0 )
191 barcode->Rotate( tc.position, EDA_ANGLE( tc.angle, DEGREES_T ) );
192
193 barcode->SetShowText( tc.withText );
194 barcode->SetIsKnockout( tc.knockout );
195
196 barcode->AssembleBarcode();
197 SHAPE_POLY_SET barcodePoly = barcode->GetPolyShape();
198
199 // Barcode poly should completely cover canonical poly
200 canonicalPoly.Rotate( barcode->GetAngle() );
201 canonicalPoly.Move( barcode->GetPosition() );
202
203 SHAPE_POLY_SET noHolesPoly;
204 barcodePoly.Unfracture();
205
206 for( int ii = 0; ii < barcodePoly.OutlineCount(); ++ii )
207 noHolesPoly.AddOutline( barcodePoly.Outline( ii ) );
208
209 // Handle rounding errors
210 if( tc.angle != 0.0 )
212
213 canonicalPoly.BooleanSubtract( noHolesPoly );
214 BOOST_CHECK_MESSAGE( canonicalPoly.IsEmpty(),
215 "Test case " << i << " (" << tc.text.ToStdString() << "): "
216 "barcode poly isn't aligned with canonical shape" );
217
218 board->Add( barcode, ADD_MODE::APPEND, true );
219 }
220}
221
222
223BOOST_AUTO_TEST_CASE( BarcodeTextVariableExpansion )
224{
225 SETTINGS_MANAGER settingsManager;
226
227 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
228
229 // Set up board-level text variables that should be expanded
230 std::map<wxString, wxString> properties;
231 properties[wxT( "PART_NUMBER" )] = wxT( "PN12345" );
232 properties[wxT( "VERSION" )] = wxT( "1.0" );
233 board->SetProperties( properties );
234
235 // Create a barcode with text variables in the content
236 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
237 barcode->SetText( wxT( "${PART_NUMBER}_${VERSION}" ) );
238 barcode->SetLayer( F_SilkS );
239 barcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
240 barcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
241 barcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
242 barcode->SetKind( BARCODE_T::QR_CODE );
244
245 board->Add( barcode, ADD_MODE::APPEND, true );
246
247 // Verify GetText returns the raw text with variables
248 BOOST_CHECK_EQUAL( barcode->GetText(), wxT( "${PART_NUMBER}_${VERSION}" ) );
249
250 // Verify GetShownText returns the expanded text
251 BOOST_CHECK_EQUAL( barcode->GetShownText(), wxT( "PN12345_1.0" ) );
252
253 // Assemble the barcode and verify the QR code encodes the expanded text
254 barcode->AssembleBarcode();
255
256 // Create a reference barcode with the literal expanded text to compare polygon shapes
257 PCB_BARCODE* refBarcode = new PCB_BARCODE( board.get() );
258 refBarcode->SetText( wxT( "PN12345_1.0" ) );
259 refBarcode->SetLayer( F_SilkS );
260 refBarcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 100.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
261 refBarcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
262 refBarcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
263 refBarcode->SetKind( BARCODE_T::QR_CODE );
265 refBarcode->AssembleBarcode();
266
267 board->Add( refBarcode, ADD_MODE::APPEND, true );
268
269 // The symbol polygons should have the same structure since they encode the same content.
270 // Compare the number of outlines as a proxy for encoding the same data.
272 refBarcode->GetSymbolPoly().OutlineCount() );
273
274 // The bounding boxes should be the same size since they encode the same content
275 BOX2I varBbox = barcode->GetSymbolPoly().BBox();
276 BOX2I refBbox = refBarcode->GetSymbolPoly().BBox();
277 BOOST_CHECK_EQUAL( varBbox.GetWidth(), refBbox.GetWidth() );
278 BOOST_CHECK_EQUAL( varBbox.GetHeight(), refBbox.GetHeight() );
279}
280
281
282BOOST_AUTO_TEST_CASE( BarcodeUndefinedVariable )
283{
284 SETTINGS_MANAGER settingsManager;
285
286 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
287
288 // Don't set any properties - variable won't be resolvable
289
290 // Create a barcode with an undefined variable
291 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
292 barcode->SetText( wxT( "${UNDEFINED_VAR}" ) );
293 barcode->SetLayer( F_SilkS );
294 barcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
295 barcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
296 barcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
297 barcode->SetKind( BARCODE_T::QR_CODE );
299
300 board->Add( barcode, ADD_MODE::APPEND, true );
301
302 // Verify GetText returns the raw text with the variable reference
303 BOOST_CHECK_EQUAL( barcode->GetText(), wxT( "${UNDEFINED_VAR}" ) );
304
305 // Verify GetShownText returns the unexpanded text (since variable is undefined)
306 BOOST_CHECK_EQUAL( barcode->GetShownText(), wxT( "${UNDEFINED_VAR}" ) );
307
308 // Assemble the barcode - for QR codes this should still work since QR can encode any text
309 barcode->AssembleBarcode();
310
311 // QR codes should still generate a polygon even with ${} in the text
312 BOOST_CHECK( barcode->GetSymbolPoly().OutlineCount() > 0 );
313}
314
315
316BOOST_AUTO_TEST_CASE( BarcodeCode39UndefinedVariable )
317{
318 SETTINGS_MANAGER settingsManager;
319
320 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
321
322 // Don't set any properties - variable won't be resolvable
323
324 // Create a CODE_39 barcode with an undefined variable
325 // CODE_39 cannot encode $ { } characters, so this should fail to generate
326 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
327 barcode->SetText( wxT( "${UNDEFINED_VAR}" ) );
328 barcode->SetLayer( F_SilkS );
329 barcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
330 barcode->SetWidth( pcbIUScale.mmToIU( 20.0 ) );
331 barcode->SetHeight( pcbIUScale.mmToIU( 5.0 ) );
332 barcode->SetKind( BARCODE_T::CODE_39 );
333
334 board->Add( barcode, ADD_MODE::APPEND, true );
335
336 // Assemble the barcode
337 barcode->AssembleBarcode();
338
339 // CODE_39 cannot encode ${} so the polygon should be empty
340 // This is expected behavior - invalid characters cause encoding to fail
342}
343
344
345BOOST_AUTO_TEST_CASE( BarcodeDialogEditFlow )
346{
347 SETTINGS_MANAGER settingsManager;
348
349 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
350
351 // Set up board-level text variables
352 std::map<wxString, wxString> properties;
353 properties[wxT( "PART_NUMBER" )] = wxT( "PN12345" );
354 board->SetProperties( properties );
355
356 // Create a barcode and add it to the board (simulates existing barcode)
357 PCB_BARCODE* currentBarcode = new PCB_BARCODE( board.get() );
358 currentBarcode->SetText( wxT( "INITIAL_TEXT" ) );
359 currentBarcode->SetKind( BARCODE_T::QR_CODE );
360 currentBarcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
361 currentBarcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
362 currentBarcode->AssembleBarcode();
363 board->Add( currentBarcode, ADD_MODE::APPEND, true );
364
365 // Simulate dialog creating a dummy barcode for preview
366 PCB_BARCODE* dummyBarcode = new PCB_BARCODE( board.get() );
367
368 // Simulate copying current to dummy (as in initValues)
369 *dummyBarcode = *currentBarcode;
370
371 BOOST_CHECK( dummyBarcode->GetBoard() == board.get() );
372
373 // Simulate user changing text to use a variable
374 dummyBarcode->SetText( wxT( "${PART_NUMBER}" ) );
375 dummyBarcode->AssembleBarcode();
376
377 // Verify variable expansion works in dummy
378 BOOST_CHECK_EQUAL( dummyBarcode->GetShownText(), wxT( "PN12345" ) );
379 BOOST_CHECK( dummyBarcode->GetSymbolPoly().OutlineCount() > 0 );
380
381 // Simulate dialog closing and applying changes to current barcode
382 currentBarcode->SetText( wxT( "${PART_NUMBER}" ) );
383 currentBarcode->AssembleBarcode();
384
385 // Verify variable expansion works in current barcode
386 BOOST_CHECK_EQUAL( currentBarcode->GetShownText(), wxT( "PN12345" ) );
387 BOOST_CHECK( currentBarcode->GetSymbolPoly().OutlineCount() > 0 );
388
389 delete dummyBarcode;
390}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
constexpr int ARC_LOW_DEF
Definition base_units.h:140
General utilities for PCB file IO for QA programs.
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr Vec Centre() const
Definition box2.h:97
constexpr size_type GetHeight() const
Definition box2.h:215
const KIID m_Uuid
Definition eda_item.h:535
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition kiid.h:48
A temporary directory that will be deleted when it goes out of scope.
const std::filesystem::path & GetPath() const
void SetKind(BARCODE_T aKind)
void SetTextSize(int aTextSize)
Change the height of the human-readable text displayed below the barcode.
void SetErrorCorrection(BARCODE_ECC_T aErrorCorrection)
Set the error correction level used for QR codes.
void SetShowText(bool aShow)
void SetWidth(int aWidth)
void AssembleBarcode() const
Assemble the barcode polygon and text polygons into a single polygonal representation.
void SetHeight(int aHeight)
VECTOR2I GetPosition() const override
Get the position (center) of the barcode in internal units.
wxString GetText() const
void SetPosition(const VECTOR2I &aPos) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the drawing layer for the barcode and its text.
int GetTextSize() const
int GetHeight() const
Get the barcode height (in internal units).
wxString GetShownText() const
void SetIsKnockout(bool aEnable) override
const SHAPE_POLY_SET & GetPolyShape() const
Access the underlying polygonal representation generated for the barcode.
const SHAPE_POLY_SET & GetSymbolPoly() const
Access the cached polygon for the barcode symbol only (no text, no margins/knockout).
BARCODE_ECC_T GetErrorCorrection() const
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate the barcode around a given centre by the given angle.
EDA_ANGLE GetAngle() const
BARCODE_T GetKind() const
Returns the type of the barcode (QR, CODE_39, etc.).
int GetWidth() const
Get the barcode width (in internal units).
void SetText(const wxString &aText)
Set the barcode content text to encode.
Represent a set of closed polygons.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
void Unfracture()
Convert a single outline slitted ("fractured") polygon into a set ouf outlines with holes.
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.
void Move(const VECTOR2I &aVector) override
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
@ ROUND_ALL_CORNERS
All angles are rounded.
@ DEGREES_T
Definition eda_angle.h:31
@ F_SilkS
Definition layer_ids.h:100
std::unique_ptr< BOARD > ReadBoardFromFileOrStream(const std::string &aFilename, std::istream &aFallback)
Read a board from a file, or another stream, as appropriate.
std::unique_ptr< FOOTPRINT > ReadFootprintFromFileOrStream(const std::string &aFilename, std::istream &aFallback)
void DumpBoardToFile(BOARD &board, const std::string &aFilename)
Utility function to simply write a Board out to a file.
void DumpFootprintToFile(const FOOTPRINT &aFootprint, const std::string &aLibraryPath)
Same as DumpBoardToFile, but for footprints.
BOARD_ITEM & RequireBoardItemWithTypeAndId(const BOARD &aBoard, KICAD_T aItemType, const KIID &aID)
Get an item from the given board with a certain type and UUID.
BARCODE class definition.
BARCODE_T
Definition pcb_barcode.h:44
BOOST_AUTO_TEST_CASE(BarcodeWriteRead)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:98
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687