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 const std::filesystem::path savePath = std::filesystem::temp_directory_path() / "barcode_roundtrip.kicad_pcb";
60
61 std::filesystem::remove( savePath );
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 const std::filesystem::path savePath = std::filesystem::temp_directory_path() / "barcode_roundtrip.kicad_mod";
99
100 std::filesystem::remove( savePath );
101 KI_TEST::DumpFootprintToFile( footprint, savePath.string() );
102 std::unique_ptr<FOOTPRINT> footprint2 = KI_TEST::ReadFootprintFromFileOrStream( savePath.string() );
103
104 PCB_BARCODE* loaded = nullptr;
105
106 for( BOARD_ITEM* item : footprint2->GraphicalItems() )
107 {
108 if( item->Type() == PCB_BARCODE_T && item->m_Uuid == id )
109 {
110 loaded = static_cast<PCB_BARCODE*>( item );
111 break;
112 }
113 }
114
115 BOOST_REQUIRE( loaded != nullptr );
116 BOOST_CHECK( loaded->GetText() == barcode->GetText() );
117 BOOST_CHECK_EQUAL( (int) loaded->GetKind(), (int) barcode->GetKind() );
118 BOOST_CHECK_EQUAL( (int) loaded->GetErrorCorrection(), (int) barcode->GetErrorCorrection() );
119 BOOST_CHECK_EQUAL( loaded->GetWidth(), barcode->GetWidth() );
120 BOOST_CHECK_EQUAL( loaded->GetHeight(), barcode->GetHeight() );
121 BOOST_CHECK_EQUAL( loaded->GetTextSize(), barcode->GetTextSize() );
122 BOOST_CHECK_EQUAL( loaded->GetPolyShape().BBox().Centre(), barcode->GetPolyShape().BBox().Centre() );
123}
124
125
126BOOST_AUTO_TEST_CASE( BarcodePositioningAlignment )
127{
128 SETTINGS_MANAGER settingsManager;
129
130 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
131
132 // Test multiple barcode types and positions to ensure consistent alignment
133 struct TestCase
134 {
135 BARCODE_T kind;
136 VECTOR2I position;
137 int width;
138 int height;
139 bool withText;
140 bool knockout;
141 double angle;
142 wxString text;
143 };
144
145 std::vector<TestCase> testCases = {
146 // Basic QR codes at different positions
147 { BARCODE_T::QR_CODE, VECTOR2I( 0, 0 ), 2000000, 2000000, false, false, 0.0, "TEST1" },
148 { BARCODE_T::QR_CODE, VECTOR2I( 5000000, 3000000 ), 3000000, 3000000, false, false, 0.0, "TEST2" },
149 { BARCODE_T::QR_CODE, VECTOR2I( -2000000, -1000000 ), 1500000, 1500000, false, false, 0.0, "TEST3" },
150
151 // With text
152 { BARCODE_T::QR_CODE, VECTOR2I( 1000000, 2000000 ), 2500000, 2500000, true, false, 0.0, "WITHTEXT" },
153
154 // With knockout
155 { BARCODE_T::QR_CODE, VECTOR2I( 2000000, 1000000 ), 2000000, 2000000, false, true, 0.0, "KNOCKOUT" },
156
157 // With rotation
158 { BARCODE_T::QR_CODE, VECTOR2I( 3000000, 2000000 ), 2000000, 2000000, false, false, 45.0, "ROTATED" },
159
160 // Different barcode types
161 { BARCODE_T::CODE_39, VECTOR2I( 4000000, 1000000 ), 3000000, 800000, false, false, 0.0, "CODE39TEST" },
162 { BARCODE_T::CODE_128, VECTOR2I( 1000000, 4000000 ), 3500000, 1000000, false, false, 0.0, "CODE128" },
163 { BARCODE_T::DATA_MATRIX, VECTOR2I( 3000000, 3000000 ), 1800000, 1800000, false, false, 0.0, "DATAMATRIX" },
164 { BARCODE_T::MICRO_QR_CODE, VECTOR2I( 2000000, 4000000 ), 1200000, 1200000, false, false, 0.0, "microQR" },
165
166 // Combined scenarios
167 { BARCODE_T::QR_CODE, VECTOR2I( 1500000, 1500000 ), 2200000, 2200000, true, true, 90.0, "COMPLEX" },
168 };
169
170 for( size_t i = 0; i < testCases.size(); ++i )
171 {
172 const auto& tc = testCases[i];
173
174 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
175 barcode->SetText( tc.text );
176 barcode->Text().SetVisible( false );
177 barcode->SetLayer( F_SilkS );
178 barcode->SetWidth( tc.width );
179 barcode->SetHeight( tc.height );
180 barcode->SetKind( tc.kind );
182
183 barcode->AssembleBarcode();
184 SHAPE_POLY_SET canonicalPoly = barcode->GetPolyShape();
185
186 barcode->SetPosition( tc.position );
187
188 if( tc.angle != 0.0 )
189 barcode->Rotate( tc.position, EDA_ANGLE( tc.angle, DEGREES_T ) );
190
191 barcode->Text().SetVisible( tc.withText );
192 barcode->SetIsKnockout( tc.knockout );
193
194 barcode->AssembleBarcode();
195 SHAPE_POLY_SET barcodePoly = barcode->GetPolyShape();
196
197 // Barcode poly should completely cover canonical poly
198 canonicalPoly.Rotate( barcode->GetAngle() );
199 canonicalPoly.Move( barcode->GetPosition() );
200
201 SHAPE_POLY_SET noHolesPoly;
202 barcodePoly.Unfracture();
203
204 for( int ii = 0; ii < barcodePoly.OutlineCount(); ++ii )
205 noHolesPoly.AddOutline( barcodePoly.Outline( ii ) );
206
207 // Handle rounding errors
208 if( tc.angle != 0.0 )
210
211 canonicalPoly.BooleanSubtract( noHolesPoly );
212 BOOST_CHECK_MESSAGE( canonicalPoly.IsEmpty(),
213 "Test case " << i << " (" << tc.text.ToStdString() << "): "
214 "barcode poly isn't aligned with canonical shape" );
215
216 board->Add( barcode, ADD_MODE::APPEND, true );
217 }
218}
219
220
221BOOST_AUTO_TEST_CASE( BarcodeTextVariableExpansion )
222{
223 SETTINGS_MANAGER settingsManager;
224
225 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
226
227 // Set up board-level text variables that should be expanded
228 std::map<wxString, wxString> properties;
229 properties[wxT( "PART_NUMBER" )] = wxT( "PN12345" );
230 properties[wxT( "VERSION" )] = wxT( "1.0" );
231 board->SetProperties( properties );
232
233 // Create a barcode with text variables in the content
234 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
235 barcode->SetText( wxT( "${PART_NUMBER}_${VERSION}" ) );
236 barcode->SetLayer( F_SilkS );
237 barcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
238 barcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
239 barcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
240 barcode->SetKind( BARCODE_T::QR_CODE );
242
243 board->Add( barcode, ADD_MODE::APPEND, true );
244
245 // Verify GetText returns the raw text with variables
246 BOOST_CHECK_EQUAL( barcode->GetText(), wxT( "${PART_NUMBER}_${VERSION}" ) );
247
248 // Verify GetShownText returns the expanded text
249 BOOST_CHECK_EQUAL( barcode->GetShownText(), wxT( "PN12345_1.0" ) );
250
251 // Assemble the barcode and verify the QR code encodes the expanded text
252 barcode->AssembleBarcode();
253
254 // Create a reference barcode with the literal expanded text to compare polygon shapes
255 PCB_BARCODE* refBarcode = new PCB_BARCODE( board.get() );
256 refBarcode->SetText( wxT( "PN12345_1.0" ) );
257 refBarcode->SetLayer( F_SilkS );
258 refBarcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 100.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
259 refBarcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
260 refBarcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
261 refBarcode->SetKind( BARCODE_T::QR_CODE );
263 refBarcode->AssembleBarcode();
264
265 board->Add( refBarcode, ADD_MODE::APPEND, true );
266
267 // The symbol polygons should have the same structure since they encode the same content.
268 // Compare the number of outlines as a proxy for encoding the same data.
270 refBarcode->GetSymbolPoly().OutlineCount() );
271
272 // The bounding boxes should be the same size since they encode the same content
273 BOX2I varBbox = barcode->GetSymbolPoly().BBox();
274 BOX2I refBbox = refBarcode->GetSymbolPoly().BBox();
275 BOOST_CHECK_EQUAL( varBbox.GetWidth(), refBbox.GetWidth() );
276 BOOST_CHECK_EQUAL( varBbox.GetHeight(), refBbox.GetHeight() );
277}
278
279
280BOOST_AUTO_TEST_CASE( BarcodeUndefinedVariable )
281{
282 SETTINGS_MANAGER settingsManager;
283
284 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
285
286 // Don't set any properties - variable won't be resolvable
287
288 // Create a barcode with an undefined variable
289 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
290 barcode->SetText( wxT( "${UNDEFINED_VAR}" ) );
291 barcode->SetLayer( F_SilkS );
292 barcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
293 barcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
294 barcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
295 barcode->SetKind( BARCODE_T::QR_CODE );
297
298 board->Add( barcode, ADD_MODE::APPEND, true );
299
300 // Verify GetText returns the raw text with the variable reference
301 BOOST_CHECK_EQUAL( barcode->GetText(), wxT( "${UNDEFINED_VAR}" ) );
302
303 // Verify GetShownText returns the unexpanded text (since variable is undefined)
304 BOOST_CHECK_EQUAL( barcode->GetShownText(), wxT( "${UNDEFINED_VAR}" ) );
305
306 // Assemble the barcode - for QR codes this should still work since QR can encode any text
307 barcode->AssembleBarcode();
308
309 // QR codes should still generate a polygon even with ${} in the text
310 BOOST_CHECK( barcode->GetSymbolPoly().OutlineCount() > 0 );
311}
312
313
314BOOST_AUTO_TEST_CASE( BarcodeCode39UndefinedVariable )
315{
316 SETTINGS_MANAGER settingsManager;
317
318 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
319
320 // Don't set any properties - variable won't be resolvable
321
322 // Create a CODE_39 barcode with an undefined variable
323 // CODE_39 cannot encode $ { } characters, so this should fail to generate
324 PCB_BARCODE* barcode = new PCB_BARCODE( board.get() );
325 barcode->SetText( wxT( "${UNDEFINED_VAR}" ) );
326 barcode->SetLayer( F_SilkS );
327 barcode->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
328 barcode->SetWidth( pcbIUScale.mmToIU( 20.0 ) );
329 barcode->SetHeight( pcbIUScale.mmToIU( 5.0 ) );
330 barcode->SetKind( BARCODE_T::CODE_39 );
331
332 board->Add( barcode, ADD_MODE::APPEND, true );
333
334 // Assemble the barcode
335 barcode->AssembleBarcode();
336
337 // CODE_39 cannot encode ${} so the polygon should be empty
338 // This is expected behavior - invalid characters cause encoding to fail
340}
341
342
343BOOST_AUTO_TEST_CASE( BarcodeDialogEditFlow )
344{
345 SETTINGS_MANAGER settingsManager;
346
347 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
348
349 // Set up board-level text variables
350 std::map<wxString, wxString> properties;
351 properties[wxT( "PART_NUMBER" )] = wxT( "PN12345" );
352 board->SetProperties( properties );
353
354 // Create a barcode and add it to the board (simulates existing barcode)
355 PCB_BARCODE* currentBarcode = new PCB_BARCODE( board.get() );
356 currentBarcode->SetText( wxT( "INITIAL_TEXT" ) );
357 currentBarcode->SetKind( BARCODE_T::QR_CODE );
358 currentBarcode->SetWidth( pcbIUScale.mmToIU( 10.0 ) );
359 currentBarcode->SetHeight( pcbIUScale.mmToIU( 10.0 ) );
360 currentBarcode->AssembleBarcode();
361 board->Add( currentBarcode, ADD_MODE::APPEND, true );
362
363 // Simulate dialog creating a dummy barcode for preview
364 PCB_BARCODE* dummyBarcode = new PCB_BARCODE( board.get() );
365
366 // Simulate copying current to dummy (as in initValues)
367 *dummyBarcode = *currentBarcode;
368
369 // Verify dummy's m_text parent chain is correct after copy
370 BOOST_CHECK( dummyBarcode->Text().GetParent() == static_cast<EDA_ITEM*>( dummyBarcode ) );
371 BOOST_CHECK( dummyBarcode->GetBoard() == board.get() );
372 BOOST_CHECK( dummyBarcode->Text().GetBoard() == board.get() );
373
374 // Simulate user changing text to use a variable
375 dummyBarcode->SetText( wxT( "${PART_NUMBER}" ) );
376 dummyBarcode->AssembleBarcode();
377
378 // Verify variable expansion works in dummy
379 BOOST_CHECK_EQUAL( dummyBarcode->GetShownText(), wxT( "PN12345" ) );
380 BOOST_CHECK( dummyBarcode->GetSymbolPoly().OutlineCount() > 0 );
381
382 // Simulate dialog closing and applying changes to current barcode
383 currentBarcode->SetText( wxT( "${PART_NUMBER}" ) );
384 currentBarcode->AssembleBarcode();
385
386 // Verify variable expansion works in current barcode
387 BOOST_CHECK_EQUAL( currentBarcode->GetShownText(), wxT( "PN12345" ) );
388 BOOST_CHECK( currentBarcode->GetSymbolPoly().OutlineCount() > 0 );
389
390 delete dummyBarcode;
391}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
constexpr int ARC_LOW_DEF
Definition base_units.h:128
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:83
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:214
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
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
const KIID m_Uuid
Definition eda_item.h:521
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:398
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition kiid.h:49
PCB_TEXT & Text()
Access the internal PCB_TEXT object used for showing the human-readable text.
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 SetWidth(int aWidth)
void SetHeight(int aHeight)
void AssembleBarcode()
Assemble the barcode polygon and text polygons into a single polygonal representation.
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:43
BOOST_AUTO_TEST_CASE(BarcodeWriteRead)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:101
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695