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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <filesystem>
21
23#include <boost/test/unit_test.hpp>
24
25#include <board.h>
26#include <footprint.h>
27#include <pcb_text.h>
28#include <pcb_barcode.h>
33
34BOOST_AUTO_TEST_CASE( BarcodeWriteRead )
35{
36 SETTINGS_MANAGER settingsManager;
37
38 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
39
40 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
41 barcode->SetText( wxT( "12345" ) );
42 barcode->SetLayer( F_SilkS );
43 barcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 2.0 ) ) );
44 barcode->SetWidth( pcbIUScale.mmToIU( 3.0 ) );
45 barcode->SetHeight( pcbIUScale.mmToIU( 3.0 ) );
46 barcode->SetTextSize( pcbIUScale.mmToIU( 1.5 ) );
47 barcode->SetKind( BARCODE_T::QR_CODE );
49 barcode->AssembleBarcode();
50
51 const KIID id = barcode->m_Uuid;
52
53 board->Add( barcode, ADD_MODE::APPEND, true );
54
55 KI_TEST::TEMPORARY_DIRECTORY tempDir( "kicad_qa_barcode_roundtrip", "" );
56 const std::filesystem::path savePath = tempDir.GetPath() / "barcode_roundtrip.kicad_pcb";
57
58 KI_TEST::DumpBoardToFile( *board, savePath.string() );
59 std::unique_ptr<BOARD> board2 = KI_TEST::ReadBoardFromFileOrStream( savePath.string() );
61 PCB_BARCODE& loaded = static_cast<PCB_BARCODE&>( item2 );
62
63 BOOST_CHECK( loaded.GetText() == barcode->GetText() );
64 BOOST_CHECK_EQUAL( (int) loaded.GetKind(), (int) barcode->GetKind() );
65 BOOST_CHECK_EQUAL( (int) loaded.GetErrorCorrection(), (int) barcode->GetErrorCorrection() );
66 BOOST_CHECK_EQUAL( loaded.GetWidth(), barcode->GetWidth() );
67 BOOST_CHECK_EQUAL( loaded.GetHeight(), barcode->GetHeight() );
68 BOOST_CHECK_EQUAL( loaded.GetTextSize(), barcode->GetTextSize() );
69 BOOST_CHECK_EQUAL( loaded.GetPolyShape().BBox().Centre(), barcode->GetPolyShape().BBox().Centre() );
70}
71
72
73BOOST_AUTO_TEST_CASE( BarcodeFootprintWriteRead )
74{
75 SETTINGS_MANAGER settingsManager;
76
77 FOOTPRINT footprint( nullptr );
78
79 PCB_BARCODE* barcode = new PCB_BARCODE( &footprint );
80 barcode->SetText( wxT( "12345" ) );
81 barcode->SetLayer( F_SilkS );
82 barcode->SetPosition( VECTOR2I( 1000000, 2000000 ) );
83 barcode->SetWidth( 3000000 );
84 barcode->SetHeight( 3000000 );
85 barcode->SetTextSize( pcbIUScale.mmToIU( 1.5 ) );
86 barcode->SetKind( BARCODE_T::QR_CODE );
88 barcode->AssembleBarcode();
89
90 const KIID id = barcode->m_Uuid;
91
92 footprint.Add( barcode, ADD_MODE::APPEND, true );
93
94 // Saving a footprint validates its whole containing directory as a library, so use a private
95 // temp directory rather than littering (and reading stray files from) the system temp root.
96 KI_TEST::TEMPORARY_DIRECTORY tempLib( "kicad_qa_barcode_roundtrip", ".pretty" );
97 const std::filesystem::path savePath = tempLib.GetPath() / "barcode_roundtrip.kicad_mod";
98
99 KI_TEST::DumpFootprintToFile( footprint, savePath.string() );
100 std::unique_ptr<FOOTPRINT> footprint2 = KI_TEST::ReadFootprintFromFileOrStream( savePath.string() );
101
102 PCB_BARCODE* loaded = nullptr;
103
104 for( BOARD_ITEM* item : footprint2->GraphicalItems() )
105 {
106 if( item->Type() == PCB_BARCODE_T && item->m_Uuid == id )
107 {
108 loaded = static_cast<PCB_BARCODE*>( item );
109 break;
110 }
111 }
112
113 BOOST_REQUIRE( loaded != nullptr );
114 BOOST_CHECK( loaded->GetText() == barcode->GetText() );
115 BOOST_CHECK_EQUAL( (int) loaded->GetKind(), (int) barcode->GetKind() );
116 BOOST_CHECK_EQUAL( (int) loaded->GetErrorCorrection(), (int) barcode->GetErrorCorrection() );
117 BOOST_CHECK_EQUAL( loaded->GetWidth(), barcode->GetWidth() );
118 BOOST_CHECK_EQUAL( loaded->GetHeight(), barcode->GetHeight() );
119 BOOST_CHECK_EQUAL( loaded->GetTextSize(), barcode->GetTextSize() );
120 BOOST_CHECK_EQUAL( loaded->GetPolyShape().BBox().Centre(), barcode->GetPolyShape().BBox().Centre() );
121}
122
123
124BOOST_AUTO_TEST_CASE( BarcodePositioningAlignment )
125{
126 SETTINGS_MANAGER settingsManager;
127
128 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
129
130 // Test multiple barcode types and positions to ensure consistent alignment
131 struct TestCase
132 {
133 BARCODE_T kind;
134 VECTOR2I position;
135 int width;
136 int height;
137 bool withText;
138 bool knockout;
139 double angle;
140 wxString text;
141 };
142
143 std::vector<TestCase> testCases = {
144 // Basic QR codes at different positions
145 { BARCODE_T::QR_CODE, VECTOR2I( 0, 0 ), 2000000, 2000000, false, false, 0.0, "TEST1" },
146 { BARCODE_T::QR_CODE, VECTOR2I( 5000000, 3000000 ), 3000000, 3000000, false, false, 0.0, "TEST2" },
147 { BARCODE_T::QR_CODE, VECTOR2I( -2000000, -1000000 ), 1500000, 1500000, false, false, 0.0, "TEST3" },
148
149 // With text
150 { BARCODE_T::QR_CODE, VECTOR2I( 1000000, 2000000 ), 2500000, 2500000, true, false, 0.0, "WITHTEXT" },
151
152 // With knockout
153 { BARCODE_T::QR_CODE, VECTOR2I( 2000000, 1000000 ), 2000000, 2000000, false, true, 0.0, "KNOCKOUT" },
154
155 // With rotation
156 { BARCODE_T::QR_CODE, VECTOR2I( 3000000, 2000000 ), 2000000, 2000000, false, false, 45.0, "ROTATED" },
157
158 // Different barcode types
159 { BARCODE_T::CODE_39, VECTOR2I( 4000000, 1000000 ), 3000000, 800000, false, false, 0.0, "CODE39TEST" },
160 { BARCODE_T::CODE_128, VECTOR2I( 1000000, 4000000 ), 3500000, 1000000, false, false, 0.0, "CODE128" },
161 { BARCODE_T::DATA_MATRIX, VECTOR2I( 3000000, 3000000 ), 1800000, 1800000, false, false, 0.0, "DATAMATRIX" },
162 { BARCODE_T::MICRO_QR_CODE, VECTOR2I( 2000000, 4000000 ), 1200000, 1200000, false, false, 0.0, "microQR" },
163
164 // Combined scenarios
165 { BARCODE_T::QR_CODE, VECTOR2I( 1500000, 1500000 ), 2200000, 2200000, true, true, 90.0, "COMPLEX" },
166 };
167
168 for( size_t i = 0; i < testCases.size(); ++i )
169 {
170 const auto& tc = testCases[i];
171
172 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
173 barcode->SetText( tc.text );
174 barcode->SetShowText( false );
175 barcode->SetLayer( F_SilkS );
176 barcode->SetWidth( tc.width );
177 barcode->SetHeight( tc.height );
178 barcode->SetKind( tc.kind );
180
181 barcode->AssembleBarcode();
182 SHAPE_POLY_SET canonicalPoly = barcode->GetPolyShape();
183
184 barcode->SetPosition( tc.position );
185
186 if( tc.angle != 0.0 )
187 barcode->Rotate( tc.position, EDA_ANGLE( tc.angle, DEGREES_T ) );
188
189 barcode->SetShowText( tc.withText );
190 barcode->SetIsKnockout( tc.knockout );
191
192 barcode->AssembleBarcode();
193 SHAPE_POLY_SET barcodePoly = barcode->GetPolyShape();
194
195 // Barcode poly should completely cover canonical poly
196 canonicalPoly.Rotate( barcode->GetAngle() );
197 canonicalPoly.Move( barcode->GetPosition() );
198
199 SHAPE_POLY_SET noHolesPoly;
200 barcodePoly.Unfracture();
201
202 for( int ii = 0; ii < barcodePoly.OutlineCount(); ++ii )
203 noHolesPoly.AddOutline( barcodePoly.Outline( ii ) );
204
205 // Handle rounding errors
206 if( tc.angle != 0.0 )
208
209 canonicalPoly.BooleanSubtract( noHolesPoly );
210 BOOST_CHECK_MESSAGE( canonicalPoly.IsEmpty(),
211 "Test case " << i << " (" << tc.text.ToStdString() << "): "
212 "barcode poly isn't aligned with canonical shape" );
213
214 board->Add( barcode, ADD_MODE::APPEND, true );
215 }
216}
217
218
219BOOST_AUTO_TEST_CASE( BarcodeTextVariableExpansion )
220{
221 SETTINGS_MANAGER settingsManager;
222
223 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
224
225 // Set up board-level text variables that should be expanded
226 std::map<wxString, wxString> properties;
227 properties[wxT( "PART_NUMBER" )] = wxT( "PN12345" );
228 properties[wxT( "VERSION" )] = wxT( "1.0" );
229 board->SetProperties( properties );
230
231 // Create a barcode with text variables in the content
232 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
233 barcode->SetText( wxT( "${PART_NUMBER}_${VERSION}" ) );
234 barcode->SetLayer( F_SilkS );
235 barcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
236 barcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
237 barcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
238 barcode->SetKind( BARCODE_T::QR_CODE );
240
241 board->Add( barcode, ADD_MODE::APPEND, true );
242
243 // Verify GetText returns the raw text with variables
244 BOOST_CHECK_EQUAL( barcode->GetText(), wxT( "${PART_NUMBER}_${VERSION}" ) );
245
246 // Verify GetShownText returns the expanded text
247 BOOST_CHECK_EQUAL( barcode->GetShownText(), wxT( "PN12345_1.0" ) );
248
249 // Assemble the barcode and verify the QR code encodes the expanded text
250 barcode->AssembleBarcode();
251
252 // Create a reference barcode with the literal expanded text to compare polygon shapes
253 PCB_BARCODE* refBarcode = new PCB_BARCODE( board.get() );
254 refBarcode->SetText( wxT( "PN12345_1.0" ) );
255 refBarcode->SetLayer( F_SilkS );
256 refBarcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 100.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
257 refBarcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
258 refBarcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
259 refBarcode->SetKind( BARCODE_T::QR_CODE );
261 refBarcode->AssembleBarcode();
262
263 board->Add( refBarcode, ADD_MODE::APPEND, true );
264
265 // The symbol polygons should have the same structure since they encode the same content.
266 // Compare the number of outlines as a proxy for encoding the same data.
268 refBarcode->GetSymbolPoly().OutlineCount() );
269
270 // The bounding boxes should be the same size since they encode the same content
271 BOX2I varBbox = barcode->GetSymbolPoly().BBox();
272 BOX2I refBbox = refBarcode->GetSymbolPoly().BBox();
273 BOOST_CHECK_EQUAL( varBbox.GetWidth(), refBbox.GetWidth() );
274 BOOST_CHECK_EQUAL( varBbox.GetHeight(), refBbox.GetHeight() );
275}
276
277
278BOOST_AUTO_TEST_CASE( BarcodeUndefinedVariable )
279{
280 SETTINGS_MANAGER settingsManager;
281
282 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
283
284 // Don't set any properties - variable won't be resolvable
285
286 // Create a barcode with an undefined variable
287 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
288 barcode->SetText( wxT( "${UNDEFINED_VAR}" ) );
289 barcode->SetLayer( F_SilkS );
290 barcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
291 barcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
292 barcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
293 barcode->SetKind( BARCODE_T::QR_CODE );
295
296 board->Add( barcode, ADD_MODE::APPEND, true );
297
298 // Verify GetText returns the raw text with the variable reference
299 BOOST_CHECK_EQUAL( barcode->GetText(), wxT( "${UNDEFINED_VAR}" ) );
300
301 // Verify GetShownText returns the unexpanded text (since variable is undefined)
302 BOOST_CHECK_EQUAL( barcode->GetShownText(), wxT( "${UNDEFINED_VAR}" ) );
303
304 // Assemble the barcode - for QR codes this should still work since QR can encode any text
305 barcode->AssembleBarcode();
306
307 // QR codes should still generate a polygon even with ${} in the text
308 BOOST_CHECK( barcode->GetSymbolPoly().OutlineCount() > 0 );
309}
310
311
312BOOST_AUTO_TEST_CASE( BarcodeCode39UndefinedVariable )
313{
314 SETTINGS_MANAGER settingsManager;
315
316 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
317
318 // Don't set any properties - variable won't be resolvable
319
320 // Create a CODE_39 barcode with an undefined variable
321 // CODE_39 cannot encode $ { } characters, so this should fail to generate
322 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
323 barcode->SetText( wxT( "${UNDEFINED_VAR}" ) );
324 barcode->SetLayer( F_SilkS );
325 barcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
326 barcode->SetWidth( pcbIUScale.mmToIU( 20.0 ) );
327 barcode->SetHeight( pcbIUScale.mmToIU( 5.0 ) );
328 barcode->SetKind( BARCODE_T::CODE_39 );
329
330 board->Add( barcode, ADD_MODE::APPEND, true );
331
332 // Assemble the barcode
333 barcode->AssembleBarcode();
334
335 // CODE_39 cannot encode ${} so the polygon should be empty
336 // This is expected behavior - invalid characters cause encoding to fail
338}
339
340
341BOOST_AUTO_TEST_CASE( BarcodeDialogEditFlow )
342{
343 SETTINGS_MANAGER settingsManager;
344
345 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
346
347 // Set up board-level text variables
348 std::map<wxString, wxString> properties;
349 properties[wxT( "PART_NUMBER" )] = wxT( "PN12345" );
350 board->SetProperties( properties );
351
352 // Create a barcode and add it to the board (simulates existing barcode)
353 PCB_BARCODE* currentBarcode = new PCB_BARCODE( board.get() );
354 currentBarcode->SetText( wxT( "INITIAL_TEXT" ) );
355 currentBarcode->SetKind( BARCODE_T::QR_CODE );
356 currentBarcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
357 currentBarcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
358 currentBarcode->AssembleBarcode();
359 board->Add( currentBarcode, ADD_MODE::APPEND, true );
360
361 // Simulate dialog creating a dummy barcode for preview
362 PCB_BARCODE* dummyBarcode = new PCB_BARCODE( board.get() );
363
364 // Simulate copying current to dummy (as in initValues)
365 *dummyBarcode = *currentBarcode;
366
367 BOOST_CHECK( dummyBarcode->GetBoard() == board.get() );
368
369 // Simulate user changing text to use a variable
370 dummyBarcode->SetText( wxT( "${PART_NUMBER}" ) );
371 dummyBarcode->AssembleBarcode();
372
373 // Verify variable expansion works in dummy
374 BOOST_CHECK_EQUAL( dummyBarcode->GetShownText(), wxT( "PN12345" ) );
375 BOOST_CHECK( dummyBarcode->GetSymbolPoly().OutlineCount() > 0 );
376
377 // Simulate dialog closing and applying changes to current barcode
378 currentBarcode->SetText( wxT( "${PART_NUMBER}" ) );
379 currentBarcode->AssembleBarcode();
380
381 // Verify variable expansion works in current barcode
382 BOOST_CHECK_EQUAL( currentBarcode->GetShownText(), wxT( "PN12345" ) );
383 BOOST_CHECK( currentBarcode->GetSymbolPoly().OutlineCount() > 0 );
384
385 delete dummyBarcode;
386}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
constexpr int ARC_LOW_DEF
Definition base_units.h:136
General utilities for PCB file IO for QA programs.
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
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:210
constexpr Vec Centre() const
Definition box2.h:93
constexpr size_type GetHeight() const
Definition box2.h:211
const KIID m_Uuid
Definition eda_item.h:531
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition kiid.h:44
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:96
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:40
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:94
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683