KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_geda_sch_import.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
25
27
28#include <schematic.h>
29#include <sch_sheet.h>
30#include <sch_screen.h>
31#include <sch_symbol.h>
32#include <sch_line.h>
33#include <sch_junction.h>
34#include <sch_label.h>
35#include <sch_no_connect.h>
36#include <sch_bus_entry.h>
37#include <sch_bitmap.h>
38#include <sch_shape.h>
39#include <sch_text.h>
40#include <sch_pin.h>
41#include <lib_symbol.h>
43#include <reporter.h>
44
45#include <algorithm>
46#include <map>
47#include <set>
48
49
51{
53 m_schematic( new SCHEMATIC( nullptr ) )
54 {
55 m_manager.LoadProject( "" );
56 m_schematic->SetProject( &m_manager.Prj() );
57 m_schematic->CurrentSheet().clear();
58 m_schematic->CurrentSheet().push_back( &m_schematic->Root() );
59 }
60
62 {
63 m_schematic.reset();
64 }
65
66 SCH_SHEET* LoadGedaSchematic( const std::string& aRelPath )
67 {
68 std::string dataPath = KI_TEST::GetEeschemaTestDataDir() + aRelPath;
69 return m_plugin.LoadSchematicFile( dataPath, m_schematic.get() );
70 }
71
72 SCH_SHEET* LoadGedaSchematicWithProperties( const std::string& aRelPath,
73 const std::map<std::string, UTF8>& aProps )
74 {
75 std::string dataPath = KI_TEST::GetEeschemaTestDataDir() + aRelPath;
76 return m_plugin.LoadSchematicFile( dataPath, m_schematic.get(), nullptr, &aProps );
77 }
78
80 std::unique_ptr<SCHEMATIC> m_schematic;
82};
83
84
85BOOST_FIXTURE_TEST_SUITE( GedaSchImport, GEDA_SCH_IMPORT_FIXTURE )
86
87
88// ============================================================================
89// File discrimination tests
90// ============================================================================
91
92BOOST_AUTO_TEST_CASE( CanReadSchematicFile )
93{
94 std::string goodPath = KI_TEST::GetEeschemaTestDataDir() + "/io/geda/minimal_test.sch";
95 BOOST_CHECK( m_plugin.CanReadSchematicFile( goodPath ) );
96}
97
98
99BOOST_AUTO_TEST_CASE( RejectsNonSchExtension )
100{
101 std::string txtPath = KI_TEST::GetEeschemaTestDataDir() + "/io/geda/minimal_test.txt";
102 BOOST_CHECK( !m_plugin.CanReadSchematicFile( txtPath ) );
103}
104
105
106BOOST_AUTO_TEST_CASE( RejectsNonGedaSchFiles )
107{
108 std::string legacyPath = KI_TEST::GetEeschemaTestDataDir() + "/io/geda/legacy_kicad.sch";
109 BOOST_CHECK( !m_plugin.CanReadSchematicFile( legacyPath ) );
110
111 std::string randomPath = KI_TEST::GetEeschemaTestDataDir() + "/io/geda/random.sch";
112 BOOST_CHECK( !m_plugin.CanReadSchematicFile( randomPath ) );
113}
114
115
116// ============================================================================
117// Minimal schematic tests (no external symbol libraries needed)
118// ============================================================================
119
120BOOST_AUTO_TEST_CASE( MinimalSchematicLoad )
121{
122 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/minimal_test.sch" );
123
124 BOOST_REQUIRE( sheet );
125 BOOST_REQUIRE( sheet->GetScreen() );
126
127 SCH_SCREEN* screen = sheet->GetScreen();
128
129 int symbolCount = 0;
130
131 for( SCH_ITEM* item : screen->Items() )
132 {
133 if( item->Type() == SCH_SYMBOL_T )
134 symbolCount++;
135 }
136
137 BOOST_CHECK_EQUAL( symbolCount, 2 );
138}
139
140
141BOOST_AUTO_TEST_CASE( MinimalSchematicSymbolAttributes )
142{
143 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/minimal_test.sch" );
144
145 BOOST_REQUIRE( sheet );
146
147 SCH_SCREEN* screen = sheet->GetScreen();
148
149 bool foundR1 = false;
150 bool foundC1 = false;
151
152 for( SCH_ITEM* item : screen->Items() )
153 {
154 if( item->Type() != SCH_SYMBOL_T )
155 continue;
156
157 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
158 wxString ref = sym->GetField( FIELD_T::REFERENCE )->GetText();
159
160 if( ref == wxT( "R1" ) )
161 {
162 foundR1 = true;
163 BOOST_CHECK_EQUAL( sym->GetField( FIELD_T::VALUE )->GetText(), wxT( "10k" ) );
164 }
165
166 if( ref == wxT( "C1" ) )
167 {
168 foundC1 = true;
169 BOOST_CHECK_EQUAL( sym->GetField( FIELD_T::VALUE )->GetText(), wxT( "100nF" ) );
170 }
171 }
172
173 BOOST_CHECK_MESSAGE( foundR1, "R1 symbol not found" );
174 BOOST_CHECK_MESSAGE( foundC1, "C1 symbol not found" );
175}
176
177
178BOOST_AUTO_TEST_CASE( MinimalSchematicWires )
179{
180 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/minimal_test.sch" );
181
182 BOOST_REQUIRE( sheet );
183
184 SCH_SCREEN* screen = sheet->GetScreen();
185
186 int wireCount = 0;
187
188 for( SCH_ITEM* item : screen->Items() )
189 {
190 if( item->Type() == SCH_LINE_T )
191 {
192 SCH_LINE* line = static_cast<SCH_LINE*>( item );
193
194 if( line->GetLayer() == LAYER_WIRE )
195 wireCount++;
196 }
197 }
198
199 BOOST_CHECK_EQUAL( wireCount, 4 );
200}
201
202
203BOOST_AUTO_TEST_CASE( MinimalSchematicNetLabels )
204{
205 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/minimal_test.sch" );
206
207 BOOST_REQUIRE( sheet );
208
209 SCH_SCREEN* screen = sheet->GetScreen();
210
211 int labelCount = 0;
212 bool foundInput = false;
213 bool foundMid = false;
214 bool foundGnd = false;
215
216 for( SCH_ITEM* item : screen->Items() )
217 {
218 if( item->Type() == SCH_LABEL_T )
219 {
220 labelCount++;
221 SCH_LABEL* label = static_cast<SCH_LABEL*>( item );
222
223 if( label->GetText() == wxT( "INPUT" ) )
224 foundInput = true;
225 else if( label->GetText() == wxT( "MID" ) )
226 foundMid = true;
227 else if( label->GetText() == wxT( "GND" ) )
228 foundGnd = true;
229 }
230 }
231
232 BOOST_CHECK_EQUAL( labelCount, 3 );
233 BOOST_CHECK_MESSAGE( foundInput, "INPUT label not found" );
234 BOOST_CHECK_MESSAGE( foundMid, "MID label not found" );
235 BOOST_CHECK_MESSAGE( foundGnd, "GND label not found" );
236}
237
238
239BOOST_AUTO_TEST_CASE( MinimalSchematicJunctions )
240{
241 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/minimal_test.sch" );
242
243 BOOST_REQUIRE( sheet );
244
245 SCH_SCREEN* screen = sheet->GetScreen();
246
247 int junctionCount = 0;
248
249 for( SCH_ITEM* item : screen->Items() )
250 {
251 if( item->Type() == SCH_JUNCTION_T )
252 junctionCount++;
253 }
254
255 // Without gEDA symbol libraries installed, fallback symbols have no pins,
256 // so no point reaches the 3-endpoint junction threshold from wires alone.
257 BOOST_CHECK_EQUAL( junctionCount, 0 );
258}
259
260
261// ============================================================================
262// Real-world AYAB schematic tests (uses gafrc for custom symbol discovery)
263// ============================================================================
264
265BOOST_AUTO_TEST_CASE( AyabSchematicLoad )
266{
267 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
268
269 BOOST_REQUIRE( sheet );
270 BOOST_REQUIRE( sheet->GetScreen() );
271
272 SCH_SCREEN* screen = sheet->GetScreen();
273
274 int symbolCount = 0;
275 int wireCount = 0;
276
277 for( SCH_ITEM* item : screen->Items() )
278 {
279 if( item->Type() == SCH_SYMBOL_T )
280 symbolCount++;
281 else if( item->Type() == SCH_LINE_T
282 && static_cast<SCH_LINE*>( item )->GetLayer() == LAYER_WIRE )
283 wireCount++;
284 }
285
286 // 133 C lines in file, 1 is title-D.sym (skipped), 5 are nc-* (mapped to no-connect) = 127 symbols
287 BOOST_CHECK_EQUAL( symbolCount, 127 );
288 // 355 N lines in the schematic
289 BOOST_CHECK_EQUAL( wireCount, 355 );
290}
291
292
293BOOST_AUTO_TEST_CASE( AyabSymbolAttributes )
294{
295 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
296
297 BOOST_REQUIRE( sheet );
298
299 SCH_SCREEN* screen = sheet->GetScreen();
300
301 std::map<wxString, wxString> expectedValues = {
302 { wxT( "U1" ), wxT( "METROMINI" ) },
303 { wxT( "U4" ), wxT( "" ) }, // ULN2803A has no value= attr
304 { wxT( "U2" ), wxT( "" ) }, // PCF8574T has no value= attr
305 { wxT( "R1" ), wxT( "5k" ) },
306 { wxT( "R2" ), wxT( "62" ) },
307 { wxT( "R3" ), wxT( "320" ) },
308 { wxT( "C1" ), wxT( "1 uF" ) },
309 { wxT( "C2" ), wxT( "0.1 uF" ) },
310 { wxT( "C3" ), wxT( "22 uF 16V" ) },
311 { wxT( "D1" ), wxT( "" ) }, // diode has device= but no value=
312 { wxT( "L1" ), wxT( "AT-1224-TWT-5V-2-R" ) },
313 { wxT( "Q1" ), wxT( "" ) },
314 { wxT( "S1" ), wxT( "" ) },
315 { wxT( "RN1" ), wxT( "1k" ) },
316 { wxT( "J1" ), wxT( "" ) },
317 };
318
319 std::set<wxString> foundRefs;
320
321 for( SCH_ITEM* item : screen->Items() )
322 {
323 if( item->Type() != SCH_SYMBOL_T )
324 continue;
325
326 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
327 wxString ref = sym->GetField( FIELD_T::REFERENCE )->GetText();
328
329 if( expectedValues.count( ref ) )
330 {
331 foundRefs.insert( ref );
332 wxString expectedVal = expectedValues[ref];
333
334 if( !expectedVal.IsEmpty() )
335 {
336 BOOST_CHECK_MESSAGE(
337 sym->GetField( FIELD_T::VALUE )->GetText() == expectedVal,
338 ref + " value mismatch: expected '" + expectedVal + "' got '"
339 + sym->GetField( FIELD_T::VALUE )->GetText() + "'" );
340 }
341 }
342 }
343
344 for( const auto& [ref, val] : expectedValues )
345 {
346 BOOST_CHECK_MESSAGE( foundRefs.count( ref ), ref + " symbol not found" );
347 }
348}
349
350
351BOOST_AUTO_TEST_CASE( AyabFootprintAttributes )
352{
353 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
354
355 BOOST_REQUIRE( sheet );
356
357 SCH_SCREEN* screen = sheet->GetScreen();
358
359 std::map<wxString, wxString> expectedFootprints = {
360 { wxT( "U1" ), wxT( "MetroMini" ) },
361 { wxT( "R1" ), wxT( "0805_reflow_solder_2" ) },
362 { wxT( "C2" ), wxT( "0603_reflow_solder" ) },
363 { wxT( "D1" ), wxT( "DO214AA_HSMB" ) },
364 { wxT( "S1" ), wxT( "TYCO_FSMJSM" ) },
365 { wxT( "L1" ), wxT( "buzzer.fp" ) },
366 { wxT( "Q1" ), wxT( "SOT23_3" ) },
367 { wxT( "RN1" ), wxT( "ResArray_1206x4_YC164" ) },
368 };
369
370 for( SCH_ITEM* item : screen->Items() )
371 {
372 if( item->Type() != SCH_SYMBOL_T )
373 continue;
374
375 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
376 wxString ref = sym->GetField( FIELD_T::REFERENCE )->GetText();
377
378 auto it = expectedFootprints.find( ref );
379
380 if( it != expectedFootprints.end() )
381 {
382 BOOST_CHECK_MESSAGE(
383 sym->GetField( FIELD_T::FOOTPRINT )->GetText() == it->second,
384 ref + " footprint mismatch: expected '" + it->second + "' got '"
385 + sym->GetField( FIELD_T::FOOTPRINT )->GetText() + "'" );
386 }
387 }
388}
389
390
391BOOST_AUTO_TEST_CASE( AyabCustomSymbolsLoaded )
392{
393 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
394
395 BOOST_REQUIRE( sheet );
396
397 SCH_SCREEN* screen = sheet->GetScreen();
398
399 // These custom symbols from gschem-symbols/ should be loaded with actual pin
400 // definitions (not fallback rectangles which have 0 pins).
401 std::map<wxString, int> expectedPinCounts = {
402 { wxT( "U1" ), 28 }, // MetroMini.sym has 28 pins
403 { wxT( "D3" ), 2 }, // led-small.sym has 2 pins
404 { wxT( "D1" ), 2 }, // diode-3a.sym has 2 pins
405 { wxT( "S1" ), 4 }, // switch-tact_sq.sym has 4 pins
406 { wxT( "Q1" ), 3 }, // trans_BEC_NPN.sym has 3 pins
407 { wxT( "J1" ), 3 }, // connector3-1B.sym has 3 pins
408 };
409
410 for( SCH_ITEM* item : screen->Items() )
411 {
412 if( item->Type() != SCH_SYMBOL_T )
413 continue;
414
415 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
416 wxString ref = sym->GetField( FIELD_T::REFERENCE )->GetText();
417
418 auto it = expectedPinCounts.find( ref );
419
420 if( it != expectedPinCounts.end() )
421 {
422 int pinCount = static_cast<int>( sym->GetLibPins().size() );
423
424 BOOST_CHECK_MESSAGE(
425 pinCount == it->second,
426 ref + " pin count mismatch: expected " + std::to_string( it->second )
427 + " got " + std::to_string( pinCount )
428 + " (symbol may not have loaded from gschem-symbols/)" );
429 }
430 }
431}
432
433
434BOOST_AUTO_TEST_CASE( AyabStdlibSymbolsLoaded )
435{
436 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
437
438 BOOST_REQUIRE( sheet );
439
440 SCH_SCREEN* screen = sheet->GetScreen();
441
442 // Standard symbols from our stdlib/ stubs should also have pins
443 std::map<wxString, int> expectedPinCounts = {
444 { wxT( "R1" ), 2 }, // resistor-1.sym has 2 pins
445 { wxT( "C1" ), 2 }, // capacitor-1.sym has 2 pins
446 { wxT( "C3" ), 2 }, // capacitor-2.sym has 2 pins
447 };
448
449 for( SCH_ITEM* item : screen->Items() )
450 {
451 if( item->Type() != SCH_SYMBOL_T )
452 continue;
453
454 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
455 wxString ref = sym->GetField( FIELD_T::REFERENCE )->GetText();
456
457 auto it = expectedPinCounts.find( ref );
458
459 if( it != expectedPinCounts.end() )
460 {
461 int pinCount = static_cast<int>( sym->GetLibPins().size() );
462
463 BOOST_CHECK_MESSAGE(
464 pinCount == it->second,
465 ref + " pin count mismatch: expected " + std::to_string( it->second )
466 + " got " + std::to_string( pinCount )
467 + " (stdlib stub may not have been found via gafrc)" );
468 }
469 }
470}
471
472
473BOOST_AUTO_TEST_CASE( ProjectSymbolOverridesSystem )
474{
475 // gEDA uses last-found-wins for symbol resolution. Project-local directories
476 // are scanned after system directories, so a local resistor-1.sym with 3 pins
477 // should override the builtin 2-pin version.
478 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/priority_test/priority_test.sch" );
479
480 BOOST_REQUIRE( sheet );
481 BOOST_REQUIRE( sheet->GetScreen() );
482
483 SCH_SCREEN* screen = sheet->GetScreen();
484
485 for( SCH_ITEM* item : screen->Items() )
486 {
487 if( item->Type() != SCH_SYMBOL_T )
488 continue;
489
490 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
491 int pinCount = static_cast<int>( sym->GetLibPins().size() );
492
493 BOOST_CHECK_MESSAGE( pinCount == 3,
494 "Expected 3-pin local override, got " + std::to_string( pinCount )
495 + " pins (project symbol should override system)" );
496 return;
497 }
498
499 BOOST_FAIL( "Symbol not found" );
500}
501
502
503BOOST_AUTO_TEST_CASE( AyabGlobalLabelsFromNetAttributes )
504{
505 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
506
507 BOOST_REQUIRE( sheet );
508
509 SCH_SCREEN* screen = sheet->GetScreen();
510
511 // net= attributes on output-1/input-1 symbols create global labels.
512 // Power symbols (generic-power.sym, gnd-1.sym) use power pins instead.
513 // 9V is only on generic-power.sym instances so appears as power, not label.
514 std::set<wxString> expectedNets = {
515 wxT( "5V" ), wxT( "GND" ), wxT( "SDA" ), wxT( "SCL" ),
516 wxT( "RST" ), wxT( "BUZZ" ), wxT( "D5" ), wxT( "D6" ), wxT( "AREF" ),
517 wxT( "ENCA" ), wxT( "ENCB" ), wxT( "ENCC" ), wxT( "EOLR" ), wxT( "EOLL" ),
518 };
519
520 std::set<wxString> foundNets;
521
522 for( SCH_ITEM* item : screen->Items() )
523 {
524 if( item->Type() == SCH_GLOBAL_LABEL_T )
525 {
526 SCH_GLOBALLABEL* label = static_cast<SCH_GLOBALLABEL*>( item );
527 foundNets.insert( label->GetText() );
528 }
529 }
530
531 for( const wxString& net : expectedNets )
532 {
533 BOOST_CHECK_MESSAGE( foundNets.count( net ),
534 "Global label for net '" + net + "' not found" );
535 }
536}
537
538
539BOOST_AUTO_TEST_CASE( AyabGlobalLabelCounts )
540{
541 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
542
543 BOOST_REQUIRE( sheet );
544
545 SCH_SCREEN* screen = sheet->GetScreen();
546
547 std::map<wxString, int> labelCounts;
548
549 for( SCH_ITEM* item : screen->Items() )
550 {
551 if( item->Type() == SCH_GLOBAL_LABEL_T )
552 {
553 SCH_GLOBALLABEL* label = static_cast<SCH_GLOBALLABEL*>( item );
554 labelCounts[label->GetText()]++;
555 }
556 }
557
558 // Each net= attribute on a non-power symbol produces one global label.
559 // Power symbol instances (generic-power.sym) use power pins, not labels.
560 BOOST_CHECK_EQUAL( labelCounts[wxT( "5V" )], 7 );
561 BOOST_CHECK_EQUAL( labelCounts[wxT( "SCL" )], 5 );
562 BOOST_CHECK_EQUAL( labelCounts[wxT( "SDA" )], 5 );
563 BOOST_CHECK_EQUAL( labelCounts[wxT( "BUZZ" )], 2 );
564 BOOST_CHECK_EQUAL( labelCounts[wxT( "RST" )], 2 );
565 BOOST_CHECK_EQUAL( labelCounts[wxT( "GND" )], 1 );
566}
567
568
569BOOST_AUTO_TEST_CASE( AyabPowerSymbols )
570{
571 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
572
573 BOOST_REQUIRE( sheet );
574
575 SCH_SCREEN* screen = sheet->GetScreen();
576
577 // gEDA power symbols (gnd-1.sym, generic-power.sym) should be imported
578 // as KiCad power symbols with #PWR references and net name as value.
579 std::map<wxString, int> powerCounts;
580 int totalPower = 0;
581
582 for( SCH_ITEM* item : screen->Items() )
583 {
584 if( item->Type() != SCH_SYMBOL_T )
585 continue;
586
587 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
588 const LIB_SYMBOL* libSym = sym->GetLibSymbolRef().get();
589
590 if( !libSym || !libSym->IsPower() )
591 continue;
592
593 totalPower++;
594 wxString valText = sym->GetField( FIELD_T::VALUE )->GetText();
595 powerCounts[valText]++;
596
597 wxString refText = sym->GetField( FIELD_T::REFERENCE )->GetText();
598 BOOST_CHECK_MESSAGE( refText.StartsWith( wxT( "#PWR" ) ),
599 "Power symbol ref should start with #PWR, got: " + refText );
600 BOOST_CHECK( !sym->GetField( FIELD_T::REFERENCE )->IsVisible() );
601 BOOST_CHECK( sym->GetField( FIELD_T::VALUE )->IsVisible() );
602 }
603
604 // 17 gnd-1.sym instances + 11 generic-power.sym instances = 28 power symbols
605 BOOST_CHECK_EQUAL( totalPower, 28 );
606 BOOST_CHECK_EQUAL( powerCounts[wxT( "GND" )], 17 );
607 BOOST_CHECK_EQUAL( powerCounts[wxT( "5V" )], 7 );
608 BOOST_CHECK_EQUAL( powerCounts[wxT( "9V" )], 4 );
609}
610
611
612BOOST_AUTO_TEST_CASE( AyabJunctionsPlaced )
613{
614 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
615
616 BOOST_REQUIRE( sheet );
617
618 SCH_SCREEN* screen = sheet->GetScreen();
619
620 int junctionCount = 0;
621
622 for( SCH_ITEM* item : screen->Items() )
623 {
624 if( item->Type() == SCH_JUNCTION_T )
625 junctionCount++;
626 }
627
628 // The ayab schematic has T-junctions where wire endpoints touch the
629 // interior of other wire segments.
630 BOOST_CHECK_GT( junctionCount, 0 );
631}
632
633
634BOOST_AUTO_TEST_CASE( AyabAllRefdesUnique )
635{
636 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
637
638 BOOST_REQUIRE( sheet );
639
640 SCH_SCREEN* screen = sheet->GetScreen();
641
642 std::map<wxString, int> refCounts;
643
644 for( SCH_ITEM* item : screen->Items() )
645 {
646 if( item->Type() != SCH_SYMBOL_T )
647 continue;
648
649 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
650 wxString ref = sym->GetField( FIELD_T::REFERENCE )->GetText();
651
652 if( !ref.IsEmpty() )
653 refCounts[ref]++;
654 }
655
656 // Every assigned refdes should appear exactly once
657 for( const auto& [ref, count] : refCounts )
658 {
659 BOOST_CHECK_MESSAGE( count == 1,
660 "Refdes '" + ref + "' appears " + std::to_string( count )
661 + " times (expected 1)" );
662 }
663}
664
665
666BOOST_AUTO_TEST_CASE( AyabWireEndpointsInPositiveSpace )
667{
668 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
669
670 BOOST_REQUIRE( sheet );
671
672 SCH_SCREEN* screen = sheet->GetScreen();
673
674 for( SCH_ITEM* item : screen->Items() )
675 {
676 if( item->Type() != SCH_LINE_T )
677 continue;
678
679 SCH_LINE* line = static_cast<SCH_LINE*>( item );
680
681 if( line->GetLayer() != LAYER_WIRE )
682 continue;
683
684 VECTOR2I start = line->GetStartPoint();
685 VECTOR2I end = line->GetEndPoint();
686
687 // After Y-flip, all coordinates should be in positive space
688 BOOST_CHECK_MESSAGE( start.x >= 0 && start.y >= 0,
689 "Wire start in negative space: ("
690 + std::to_string( start.x ) + ", "
691 + std::to_string( start.y ) + ")" );
692 BOOST_CHECK_MESSAGE( end.x >= 0 && end.y >= 0,
693 "Wire end in negative space: ("
694 + std::to_string( end.x ) + ", "
695 + std::to_string( end.y ) + ")" );
696 }
697}
698
699
700BOOST_AUTO_TEST_CASE( AyabSymbolPositionsInPositiveSpace )
701{
702 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
703
704 BOOST_REQUIRE( sheet );
705
706 SCH_SCREEN* screen = sheet->GetScreen();
707
708 for( SCH_ITEM* item : screen->Items() )
709 {
710 if( item->Type() != SCH_SYMBOL_T )
711 continue;
712
713 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
714 VECTOR2I pos = sym->GetPosition();
715
716 BOOST_CHECK_MESSAGE( pos.x >= 0 && pos.y >= 0,
717 "Symbol at negative position: ("
718 + std::to_string( pos.x ) + ", "
719 + std::to_string( pos.y ) + ")" );
720 }
721}
722
723
724BOOST_AUTO_TEST_CASE( AyabComponentOrientations )
725{
726 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
727
728 BOOST_REQUIRE( sheet );
729
730 SCH_SCREEN* screen = sheet->GetScreen();
731
732 int rotatedCount = 0;
733 int mirroredCount = 0;
734
735 for( SCH_ITEM* item : screen->Items() )
736 {
737 if( item->Type() != SCH_SYMBOL_T )
738 continue;
739
740 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
741 int orient = sym->GetOrientation();
742
743 if( orient & ( SYM_ORIENT_90 | SYM_ORIENT_180 | SYM_ORIENT_270 ) )
744 rotatedCount++;
745
746 if( orient & SYM_MIRROR_Y )
747 mirroredCount++;
748 }
749
750 // The ayab schematic has 48 rotated and 38 mirrored components in gEDA source.
751 // Verify the importer preserves orientation diversity.
752 BOOST_CHECK_MESSAGE( rotatedCount > 0,
753 "Expected rotated components but found none" );
754 BOOST_CHECK_MESSAGE( mirroredCount > 0,
755 "Expected mirrored components but found none" );
756}
757
758
759BOOST_AUTO_TEST_CASE( AyabTextAnnotations )
760{
761 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
762
763 BOOST_REQUIRE( sheet );
764
765 SCH_SCREEN* screen = sheet->GetScreen();
766
767 int textCount = 0;
768 bool foundTitle = false;
769 bool foundAuthor = false;
770
771 for( SCH_ITEM* item : screen->Items() )
772 {
773 if( item->Type() != SCH_TEXT_T )
774 continue;
775
776 textCount++;
777 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
778
779 if( text->GetText().Contains( wxT( "AYAB Interface" ) ) )
780 foundTitle = true;
781
782 if( text->GetText().Contains( wxT( "Windell" ) ) )
783 foundAuthor = true;
784 }
785
786 // 13 standalone T lines in ayab_rs.sch (titles, notes, annotations)
787 BOOST_CHECK_EQUAL( textCount, 13 );
788 BOOST_CHECK_MESSAGE( foundTitle, "Title text 'AYAB Interface' not found" );
789 BOOST_CHECK_MESSAGE( foundAuthor, "Author text containing 'Windell' not found" );
790}
791
792
793BOOST_AUTO_TEST_CASE( AyabWireConnectivity )
794{
795 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
796
797 BOOST_REQUIRE( sheet );
798
799 SCH_SCREEN* screen = sheet->GetScreen();
800
801 // Count wire endpoint coincidences. Connected wires share endpoints,
802 // so counting shared points measures connectivity.
803 std::map<std::pair<int, int>, int> endpointCounts;
804
805 for( SCH_ITEM* item : screen->Items() )
806 {
807 if( item->Type() != SCH_LINE_T )
808 continue;
809
810 SCH_LINE* line = static_cast<SCH_LINE*>( item );
811
812 if( line->GetLayer() != LAYER_WIRE )
813 continue;
814
815 VECTOR2I start = line->GetStartPoint();
816 VECTOR2I end = line->GetEndPoint();
817 endpointCounts[{start.x, start.y}]++;
818 endpointCounts[{end.x, end.y}]++;
819 }
820
821 int sharedPoints = 0;
822
823 for( const auto& [pos, count] : endpointCounts )
824 {
825 if( count >= 2 )
826 sharedPoints++;
827 }
828
829 // In a well-connected schematic, many wire endpoints overlap.
830 // The ayab schematic has 101 such shared points (from 355 wires).
831 BOOST_CHECK_MESSAGE( sharedPoints > 50,
832 "Expected at least 50 shared wire endpoints for connectivity, got "
833 + std::to_string( sharedPoints ) );
834}
835
836
837BOOST_AUTO_TEST_CASE( AyabGlobalLabelPositionsPositive )
838{
839 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
840
841 BOOST_REQUIRE( sheet );
842
843 SCH_SCREEN* screen = sheet->GetScreen();
844
845 for( SCH_ITEM* item : screen->Items() )
846 {
847 if( item->Type() != SCH_GLOBAL_LABEL_T )
848 continue;
849
850 SCH_GLOBALLABEL* label = static_cast<SCH_GLOBALLABEL*>( item );
851 VECTOR2I pos = label->GetPosition();
852
853 BOOST_CHECK_MESSAGE( pos.x >= 0 && pos.y >= 0,
854 "Global label '" + label->GetText()
855 + "' at negative position: ("
856 + std::to_string( pos.x ) + ", "
857 + std::to_string( pos.y ) + ")" );
858 }
859}
860
861
862BOOST_AUTO_TEST_CASE( AyabTotalGlobalLabelCount )
863{
864 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
865
866 BOOST_REQUIRE( sheet );
867
868 SCH_SCREEN* screen = sheet->GetScreen();
869
870 int totalLabels = 0;
871
872 for( SCH_ITEM* item : screen->Items() )
873 {
874 if( item->Type() == SCH_GLOBAL_LABEL_T )
875 totalLabels++;
876 }
877
878 // 45 non-power net= attributes produce global labels.
879 // 11 power symbol net= attributes (generic-power.sym) use power pins instead.
880 BOOST_CHECK_EQUAL( totalLabels, 45 );
881}
882
883
884BOOST_AUTO_TEST_CASE( RealWorldSchematicLoad )
885{
886 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/powermeter.sch" );
887
888 BOOST_REQUIRE( sheet );
889 BOOST_REQUIRE( sheet->GetScreen() );
890
891 SCH_SCREEN* screen = sheet->GetScreen();
892
893 int symbolCount = 0;
894 int wireCount = 0;
895
896 for( SCH_ITEM* item : screen->Items() )
897 {
898 if( item->Type() == SCH_SYMBOL_T )
899 symbolCount++;
900 else if( item->Type() == SCH_LINE_T
901 && static_cast<SCH_LINE*>( item )->GetLayer() == LAYER_WIRE )
902 wireCount++;
903 }
904
905 BOOST_CHECK( symbolCount > 5 );
906 BOOST_CHECK( wireCount > 10 );
907}
908
909
910// ============================================================================
911// Phase 1 feature tests
912// ============================================================================
913
914BOOST_AUTO_TEST_CASE( CommentLinesIgnored )
915{
916 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/comments_test.sch" );
917
918 BOOST_REQUIRE( sheet );
919 BOOST_REQUIRE( sheet->GetScreen() );
920
921 SCH_SCREEN* screen = sheet->GetScreen();
922
923 int symbolCount = 0;
924 int wireCount = 0;
925
926 for( SCH_ITEM* item : screen->Items() )
927 {
928 if( item->Type() == SCH_SYMBOL_T )
929 symbolCount++;
930 else if( item->Type() == SCH_LINE_T
931 && static_cast<SCH_LINE*>( item )->GetLayer() == LAYER_WIRE )
932 wireCount++;
933 }
934
935 BOOST_CHECK_EQUAL( symbolCount, 1 );
936 BOOST_CHECK_EQUAL( wireCount, 1 );
937}
938
939
940BOOST_AUTO_TEST_CASE( OldFormatGraphicsParse )
941{
942 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/old_format_test.sch" );
943
944 BOOST_REQUIRE( sheet );
945 BOOST_REQUIRE( sheet->GetScreen() );
946
947 SCH_SCREEN* screen = sheet->GetScreen();
948
949 int noteLineCount = 0;
950 int wireCount = 0;
951 int shapeCount = 0;
952
953 for( SCH_ITEM* item : screen->Items() )
954 {
955 if( item->Type() == SCH_LINE_T )
956 {
957 SCH_LINE* line = static_cast<SCH_LINE*>( item );
958
959 if( line->GetLayer() == LAYER_NOTES )
960 noteLineCount++;
961 else if( line->GetLayer() == LAYER_WIRE )
962 wireCount++;
963 }
964 else if( item->Type() == SCH_SHAPE_T )
965 {
966 shapeCount++;
967 }
968 }
969
970 // Old format file has L, B, V, A graphic objects and one N wire
971 BOOST_CHECK_EQUAL( noteLineCount, 1 );
972 BOOST_CHECK_EQUAL( wireCount, 1 );
973 BOOST_CHECK_EQUAL( shapeCount, 3 );
974}
975
976
977BOOST_AUTO_TEST_CASE( OldFormatBusPinParse )
978{
979 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/old_bus_pin_test.sch" );
980
981 BOOST_REQUIRE( sheet );
982 BOOST_REQUIRE( sheet->GetScreen() );
983
984 SCH_SCREEN* screen = sheet->GetScreen();
985
986 int busCount = 0;
987 int wireCount = 0;
988
989 for( SCH_ITEM* item : screen->Items() )
990 {
991 if( item->Type() != SCH_LINE_T )
992 continue;
993
994 SCH_LINE* line = static_cast<SCH_LINE*>( item );
995
996 if( line->GetLayer() == LAYER_BUS )
997 busCount++;
998 else if( line->GetLayer() == LAYER_WIRE )
999 wireCount++;
1000 }
1001
1002 BOOST_CHECK_EQUAL( busCount, 1 );
1003 // N line + P line (pin becomes wire stub in schematic context)
1004 BOOST_CHECK_EQUAL( wireCount, 2 );
1005}
1006
1007
1008BOOST_AUTO_TEST_CASE( OldFormatTextParse )
1009{
1010 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/old_text_test.sch" );
1011
1012 BOOST_REQUIRE( sheet );
1013 BOOST_REQUIRE( sheet->GetScreen() );
1014
1015 SCH_SCREEN* screen = sheet->GetScreen();
1016
1017 int textCount = 0;
1018 bool foundHello = false;
1019
1020 for( SCH_ITEM* item : screen->Items() )
1021 {
1022 if( item->Type() == SCH_TEXT_T )
1023 {
1024 textCount++;
1025 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
1026
1027 if( text->GetText() == wxT( "Hello World" ) )
1028 foundHello = true;
1029 }
1030 }
1031
1032 BOOST_CHECK_EQUAL( textCount, 1 );
1033 BOOST_CHECK_MESSAGE( foundHello, "Text 'Hello World' not found in old-format file" );
1034}
1035
1036
1037BOOST_AUTO_TEST_CASE( VeryOldFormatTextParse )
1038{
1039 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/very_old_text_test.sch" );
1040
1041 BOOST_REQUIRE( sheet );
1042 BOOST_REQUIRE( sheet->GetScreen() );
1043
1044 SCH_SCREEN* screen = sheet->GetScreen();
1045
1046 int textCount = 0;
1047 bool foundOldText = false;
1048
1049 for( SCH_ITEM* item : screen->Items() )
1050 {
1051 if( item->Type() == SCH_TEXT_T )
1052 {
1053 textCount++;
1054 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
1055
1056 if( text->GetText() == wxT( "Old Text" ) )
1057 foundOldText = true;
1058 }
1059 }
1060
1061 BOOST_CHECK_EQUAL( textCount, 1 );
1062 BOOST_CHECK_MESSAGE( foundOldText, "Text 'Old Text' not found in very-old-format file" );
1063}
1064
1065
1066BOOST_AUTO_TEST_CASE( BusLayerCorrect )
1067{
1068 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/bus_test.sch" );
1069
1070 BOOST_REQUIRE( sheet );
1071 BOOST_REQUIRE( sheet->GetScreen() );
1072
1073 SCH_SCREEN* screen = sheet->GetScreen();
1074
1075 int busCount = 0;
1076 int wireCount = 0;
1077 int noteCount = 0;
1078
1079 for( SCH_ITEM* item : screen->Items() )
1080 {
1081 if( item->Type() != SCH_LINE_T )
1082 continue;
1083
1084 SCH_LINE* line = static_cast<SCH_LINE*>( item );
1085
1086 if( line->GetLayer() == LAYER_BUS )
1087 busCount++;
1088 else if( line->GetLayer() == LAYER_WIRE )
1089 wireCount++;
1090 else if( line->GetLayer() == LAYER_NOTES )
1091 noteCount++;
1092 }
1093
1094 BOOST_CHECK_EQUAL( busCount, 2 );
1095 BOOST_CHECK_EQUAL( wireCount, 1 );
1096 BOOST_CHECK_EQUAL( noteCount, 0 );
1097}
1098
1099
1100BOOST_AUTO_TEST_CASE( NoConnectFromNcSymbol )
1101{
1102 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/graphical_test.sch" );
1103
1104 BOOST_REQUIRE( sheet );
1105 BOOST_REQUIRE( sheet->GetScreen() );
1106
1107 SCH_SCREEN* screen = sheet->GetScreen();
1108
1109 int symbolCount = 0;
1110 int ncCount = 0;
1111
1112 for( SCH_ITEM* item : screen->Items() )
1113 {
1114 if( item->Type() == SCH_SYMBOL_T )
1115 symbolCount++;
1116 else if( item->Type() == SCH_NO_CONNECT_T )
1117 ncCount++;
1118 }
1119
1120 // R1 is a normal symbol, nc-right-1 becomes SCH_NO_CONNECT
1121 BOOST_CHECK_EQUAL( symbolCount, 1 );
1122 BOOST_CHECK_EQUAL( ncCount, 1 );
1123}
1124
1125
1126BOOST_AUTO_TEST_CASE( AyabNoConnectsCreated )
1127{
1128 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
1129
1130 BOOST_REQUIRE( sheet );
1131 BOOST_REQUIRE( sheet->GetScreen() );
1132
1133 SCH_SCREEN* screen = sheet->GetScreen();
1134
1135 int ncCount = 0;
1136
1137 for( SCH_ITEM* item : screen->Items() )
1138 {
1139 if( item->Type() == SCH_NO_CONNECT_T )
1140 ncCount++;
1141 }
1142
1143 // 5 nc-* symbols in ayab_rs.sch should map to SCH_NO_CONNECT
1144 BOOST_CHECK_EQUAL( ncCount, 5 );
1145}
1146
1147
1148BOOST_AUTO_TEST_CASE( NoConnectAtPinPosition )
1149{
1150 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/graphical_test.sch" );
1151
1152 BOOST_REQUIRE( sheet );
1153
1154 SCH_SCREEN* screen = sheet->GetScreen();
1155
1156 // nc-right-1.sym at (44000, 44000) with angle=0, mirror=0 has its pin
1157 // connection point at local (0, 100). The SCH_NO_CONNECT should be placed
1158 // at gEDA (44000, 44100), not at the component origin (44000, 44000).
1159 for( SCH_ITEM* item : screen->Items() )
1160 {
1161 if( item->Type() != SCH_NO_CONNECT_T )
1162 continue;
1163
1164 SCH_NO_CONNECT* nc = static_cast<SCH_NO_CONNECT*>( item );
1165 VECTOR2I pos = nc->GetPosition();
1166
1167 // The NC must NOT be at the component origin. If pin offset was applied
1168 // correctly, the Y coordinate will differ from the origin.
1169 BOOST_CHECK_MESSAGE( pos.x != 0 && pos.y != 0,
1170 "NC position should be non-zero after coordinate transform" );
1171 }
1172}
1173
1174
1175BOOST_AUTO_TEST_CASE( DocumentationToDatasheet )
1176{
1177 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/documentation_test.sch" );
1178
1179 BOOST_REQUIRE( sheet );
1180 BOOST_REQUIRE( sheet->GetScreen() );
1181
1182 SCH_SCREEN* screen = sheet->GetScreen();
1183
1184 bool foundDatasheet = false;
1185
1186 for( SCH_ITEM* item : screen->Items() )
1187 {
1188 if( item->Type() != SCH_SYMBOL_T )
1189 continue;
1190
1191 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
1192 SCH_FIELD* dsField = sym->GetField( FIELD_T::DATASHEET );
1193
1194 if( dsField && dsField->GetText() == wxT( "http://www.example.com/datasheet.pdf" ) )
1195 foundDatasheet = true;
1196 }
1197
1198 BOOST_CHECK_MESSAGE( foundDatasheet,
1199 "DATASHEET field with documentation URL not found" );
1200}
1201
1202
1203BOOST_AUTO_TEST_CASE( AyabDescriptionFieldVisibility )
1204{
1205 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
1206
1207 BOOST_REQUIRE( sheet );
1208
1209 SCH_SCREEN* screen = sheet->GetScreen();
1210
1211 // D3 (led-small.sym) has description="9V Power" with visibility=1, showNV=1
1212 // (show value only). The DESCRIPTION field should be visible.
1213 for( SCH_ITEM* item : screen->Items() )
1214 {
1215 if( item->Type() != SCH_SYMBOL_T )
1216 continue;
1217
1218 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
1219 wxString ref = sym->GetField( FIELD_T::REFERENCE )->GetText();
1220
1221 if( ref != wxT( "D3" ) )
1222 continue;
1223
1224 SCH_FIELD* descField = sym->GetField( FIELD_T::DESCRIPTION );
1225 BOOST_REQUIRE( descField );
1226 BOOST_CHECK( descField->GetText() == wxT( "9V Power" ) );
1227 BOOST_CHECK( descField->IsVisible() );
1228 return;
1229 }
1230
1231 BOOST_FAIL( "D3 symbol not found" );
1232}
1233
1234
1235BOOST_AUTO_TEST_CASE( AyabBusLayerFixed )
1236{
1237 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
1238
1239 BOOST_REQUIRE( sheet );
1240 BOOST_REQUIRE( sheet->GetScreen() );
1241
1242 SCH_SCREEN* screen = sheet->GetScreen();
1243
1244 int notesLineCount = 0;
1245
1246 for( SCH_ITEM* item : screen->Items() )
1247 {
1248 if( item->Type() == SCH_LINE_T )
1249 {
1250 SCH_LINE* line = static_cast<SCH_LINE*>( item );
1251
1252 if( line->GetLayer() == LAYER_NOTES )
1253 notesLineCount++;
1254 }
1255 }
1256
1257 // The ayab schematic has 13 visible T annotations but no non-text
1258 // LAYER_NOTES lines -- former bus lines should now be LAYER_BUS.
1259 BOOST_CHECK_EQUAL( notesLineCount, 0 );
1260}
1261
1262
1263// ============================================================================
1264// Phase 2: Text overbar conversion
1265// ============================================================================
1266
1267BOOST_AUTO_TEST_CASE( TextOverbarConversion )
1268{
1269 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/overbar_test.sch" );
1270
1271 BOOST_REQUIRE( sheet );
1272 BOOST_REQUIRE( sheet->GetScreen() );
1273
1274 SCH_SCREEN* screen = sheet->GetScreen();
1275
1276 std::vector<wxString> textContents;
1277
1278 for( SCH_ITEM* item : screen->Items() )
1279 {
1280 if( item->Type() == SCH_TEXT_T )
1281 {
1282 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
1283 textContents.push_back( text->GetText() );
1284 }
1285 }
1286
1287 // We expect 6 text items from the test file
1288 BOOST_REQUIRE_GE( textContents.size(), 6u );
1289
1290 bool foundFullOverbar = false;
1291 bool foundTrailingOverbar = false;
1292 bool foundPartialOverbar = false;
1293 bool foundBackslash = false;
1294 bool foundNoOverbar = false;
1295 bool foundMultiOverbar = false;
1296
1297 for( const wxString& txt : textContents )
1298 {
1299 if( txt == wxT( "~{ACTIVE}" ) )
1300 foundFullOverbar = true;
1301
1302 if( txt == wxT( "DATA~{LOW}" ) )
1303 foundTrailingOverbar = true;
1304
1305 if( txt == wxT( "ACTIVE ~{HIGH}" ) )
1306 foundPartialOverbar = true;
1307
1308 if( txt == wxT( "\\backslash" ) )
1309 foundBackslash = true;
1310
1311 if( txt == wxT( "No overbar here" ) )
1312 foundNoOverbar = true;
1313
1314 if( txt == wxT( "~{CS} and ~{WR}" ) )
1315 foundMultiOverbar = true;
1316 }
1317
1318 BOOST_CHECK_MESSAGE( foundFullOverbar,
1319 "Full overbar \\_ACTIVE\\_ -> ~{ACTIVE} not found" );
1320 BOOST_CHECK_MESSAGE( foundTrailingOverbar,
1321 "Trailing overbar DATA\\_LOW\\_ -> DATA~{LOW} not found" );
1322 BOOST_CHECK_MESSAGE( foundPartialOverbar,
1323 "Partial overbar ACTIVE \\_HIGH\\_ -> ACTIVE ~{HIGH} not found" );
1324 BOOST_CHECK_MESSAGE( foundBackslash,
1325 "Escaped backslash \\\\\\\\ -> \\ not found" );
1326 BOOST_CHECK_MESSAGE( foundNoOverbar,
1327 "Plain text 'No overbar here' not found" );
1328 BOOST_CHECK_MESSAGE( foundMultiOverbar,
1329 "Multiple overbars \\_CS\\_ and \\_WR\\_ -> ~{CS} and ~{WR} not found" );
1330}
1331
1332
1333// ============================================================================
1334// Phase 2: Bezier curve subdivision
1335// ============================================================================
1336
1337BOOST_AUTO_TEST_CASE( BezierCurveSubdivision )
1338{
1339 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/bezier_test.sch" );
1340
1341 BOOST_REQUIRE( sheet );
1342 BOOST_REQUIRE( sheet->GetScreen() );
1343
1344 SCH_SCREEN* screen = sheet->GetScreen();
1345
1346 int bezierCount = 0;
1347 int lineCount = 0;
1348
1349 for( SCH_ITEM* item : screen->Items() )
1350 {
1351 if( item->Type() == SCH_SHAPE_T )
1352 {
1353 SCH_SHAPE* shape = static_cast<SCH_SHAPE*>( item );
1354
1355 if( shape->GetShape() == SHAPE_T::BEZIER )
1356 bezierCount++;
1357 }
1358 else if( item->Type() == SCH_LINE_T )
1359 {
1360 lineCount++;
1361 }
1362 }
1363
1364 // First path: M + C = 1 native bezier shape
1365 // Second path: M + C + L = 1 native bezier shape + 1 line segment
1366 BOOST_CHECK_EQUAL( bezierCount, 2 );
1367 BOOST_CHECK_EQUAL( lineCount, 1 );
1368}
1369
1370
1371// ============================================================================
1372// Phase 2: Embedded picture import
1373// ============================================================================
1374
1375BOOST_AUTO_TEST_CASE( EmbeddedPictureImport )
1376{
1377 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/picture_test.sch" );
1378
1379 BOOST_REQUIRE( sheet );
1380 BOOST_REQUIRE( sheet->GetScreen() );
1381
1382 SCH_SCREEN* screen = sheet->GetScreen();
1383
1384 int bitmapCount = 0;
1385
1386 for( SCH_ITEM* item : screen->Items() )
1387 {
1388 if( item->Type() == SCH_BITMAP_T )
1389 bitmapCount++;
1390 }
1391
1392 BOOST_CHECK_EQUAL( bitmapCount, 1 );
1393}
1394
1395
1396// ============================================================================
1397// Correctness audit fixes
1398// ============================================================================
1399
1400
1401BOOST_AUTO_TEST_CASE( TextSizeScaling )
1402{
1403 // gEDA text size is in points where 1 point = 10 mils.
1404 // size=10 -> 100 mils -> 100*254 = 25400 IU
1405 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/text_size_test.sch" );
1406
1407 BOOST_REQUIRE( sheet );
1408 BOOST_REQUIRE( sheet->GetScreen() );
1409
1410 SCH_SCREEN* screen = sheet->GetScreen();
1411
1412 std::vector<SCH_TEXT*> texts;
1413
1414 for( SCH_ITEM* item : screen->Items() )
1415 {
1416 if( item->Type() == SCH_TEXT_T )
1417 texts.push_back( static_cast<SCH_TEXT*>( item ) );
1418 }
1419
1420 BOOST_REQUIRE_GE( texts.size(), 2u );
1421
1422 bool foundSize10 = false;
1423 bool foundSize20 = false;
1424
1425 for( SCH_TEXT* t : texts )
1426 {
1427 int h = t->GetTextSize().y;
1428
1429 if( h == 10 * 10 * 254 )
1430 foundSize10 = true;
1431 else if( h == 20 * 10 * 254 )
1432 foundSize20 = true;
1433 }
1434
1435 BOOST_CHECK_MESSAGE( foundSize10, "Expected text with size=10 (25400 IU)" );
1436 BOOST_CHECK_MESSAGE( foundSize20, "Expected text with size=20 (50800 IU)" );
1437}
1438
1439
1440BOOST_AUTO_TEST_CASE( TextAngleNormalization )
1441{
1442 // Non-orthogonal angles should be snapped to the nearest 90-degree increment
1443 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/text_angle_test.sch" );
1444
1445 BOOST_REQUIRE( sheet );
1446 BOOST_REQUIRE( sheet->GetScreen() );
1447
1448 SCH_SCREEN* screen = sheet->GetScreen();
1449
1450 std::set<int> angles;
1451
1452 for( SCH_ITEM* item : screen->Items() )
1453 {
1454 if( item->Type() == SCH_TEXT_T )
1455 {
1456 SCH_TEXT* t = static_cast<SCH_TEXT*>( item );
1457 angles.insert( t->GetTextAngle().AsTenthsOfADegree() );
1458 }
1459 }
1460
1461 // 45->90, 135->90, 200->180, 315->0, 90->90
1462 // So we should see angles 0, 90, 180 in tenths: 0, 900, 1800
1463 BOOST_CHECK( angles.count( 900 ) > 0 ); // 90 degrees
1464 BOOST_CHECK( angles.count( 0 ) > 0 ); // 0 degrees (315 snaps to 0)
1465 BOOST_CHECK( angles.count( 1800 ) > 0 ); // 180 degrees (200 snaps to 180)
1466}
1467
1468
1469BOOST_AUTO_TEST_CASE( RelativePathCommands )
1470{
1471 // Lowercase SVG path commands (l) should produce relative offsets,
1472 // yielding the same endpoint as equivalent absolute (L) commands
1473 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/relative_path_test.sch" );
1474
1475 BOOST_REQUIRE( sheet );
1476 BOOST_REQUIRE( sheet->GetScreen() );
1477
1478 SCH_SCREEN* screen = sheet->GetScreen();
1479
1480 int lineCount = 0;
1481
1482 for( SCH_ITEM* item : screen->Items() )
1483 {
1484 if( item->Type() == SCH_LINE_T )
1485 {
1486 SCH_LINE* line = static_cast<SCH_LINE*>( item );
1487
1488 if( line->GetLayer() == LAYER_NOTES )
1489 {
1490 lineCount++;
1491 VECTOR2I start = line->GetStartPoint();
1492 VECTOR2I end = line->GetEndPoint();
1493 int dx = std::abs( end.x - start.x );
1494
1495 // Both paths draw a horizontal 500-mil segment = 500*254 = 127000 IU
1496 BOOST_CHECK_EQUAL( dx, 500 * 254 );
1497 }
1498 }
1499 }
1500
1501 BOOST_CHECK_EQUAL( lineCount, 2 );
1502}
1503
1504
1505BOOST_AUTO_TEST_CASE( EmbeddedPrefixStripped )
1506{
1507 // Components with EMBEDDED prefix should have the prefix stripped from the basename
1508 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/embedded_prefix_test.sch" );
1509
1510 BOOST_REQUIRE( sheet );
1511 BOOST_REQUIRE( sheet->GetScreen() );
1512
1513 SCH_SCREEN* screen = sheet->GetScreen();
1514
1515 int symbolCount = 0;
1516
1517 for( SCH_ITEM* item : screen->Items() )
1518 {
1519 if( item->Type() == SCH_SYMBOL_T )
1520 {
1521 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
1522 wxString name = sym->GetLibId().GetLibItemName();
1523
1524 // The EMBEDDED prefix should have been stripped
1525 BOOST_CHECK_MESSAGE( !name.StartsWith( wxT( "EMBEDDED" ) ),
1526 "Symbol name should not have EMBEDDED prefix: " + name );
1527 symbolCount++;
1528 }
1529 }
1530
1531 BOOST_CHECK_EQUAL( symbolCount, 1 );
1532}
1533
1534
1535// ============================================================================
1536// Phase 3: Lepton-EDA library path discovery
1537// ============================================================================
1538
1539BOOST_AUTO_TEST_CASE( LeptonConfLibraryDiscovery )
1540{
1541 // The lepton_conf_test.sch references lepton_resistor.sym, which lives
1542 // in lepton-symbols/ adjacent to the .sch file. A lepton.conf in the
1543 // same directory has [libs] component-library=lepton-symbols, so the
1544 // importer should discover and load the symbol with 2 pins.
1545 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/lepton_conf_test.sch" );
1546
1547 BOOST_REQUIRE( sheet );
1548 BOOST_REQUIRE( sheet->GetScreen() );
1549
1550 SCH_SCREEN* screen = sheet->GetScreen();
1551
1552 bool foundR1 = false;
1553
1554 for( SCH_ITEM* item : screen->Items() )
1555 {
1556 if( item->Type() != SCH_SYMBOL_T )
1557 continue;
1558
1559 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
1560 wxString ref = sym->GetField( FIELD_T::REFERENCE )->GetText();
1561
1562 if( ref == wxT( "R1" ) )
1563 {
1564 foundR1 = true;
1565 int pinCount = static_cast<int>( sym->GetLibPins().size() );
1566
1567 BOOST_CHECK_MESSAGE( pinCount == 2,
1568 "R1 pin count mismatch: expected 2 got "
1569 + std::to_string( pinCount )
1570 + " (lepton.conf library discovery may have failed)" );
1571 BOOST_CHECK_EQUAL( sym->GetField( FIELD_T::VALUE )->GetText(), wxT( "4.7k" ) );
1572 }
1573 }
1574
1575 BOOST_CHECK_MESSAGE( foundR1, "R1 symbol not found" );
1576}
1577
1578
1579// ============================================================================
1580// Phase 3: gschemrc library discovery
1581// ============================================================================
1582
1583BOOST_AUTO_TEST_CASE( GschemrcLibraryDiscovery )
1584{
1585 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/gschemrc_test.sch" );
1586
1587 BOOST_REQUIRE( sheet );
1588 BOOST_REQUIRE( sheet->GetScreen() );
1589
1590 SCH_SCREEN* screen = sheet->GetScreen();
1591
1592 for( SCH_ITEM* item : screen->Items() )
1593 {
1594 if( item->Type() != SCH_SYMBOL_T )
1595 continue;
1596
1597 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
1598 wxString ref = sym->GetField( FIELD_T::REFERENCE )->GetText();
1599
1600 if( ref == wxT( "R1" ) )
1601 {
1602 int pinCount = static_cast<int>( sym->GetLibPins().size() );
1603
1604 BOOST_CHECK_MESSAGE( pinCount == 2,
1605 "R1 pin count mismatch: expected 2 got "
1606 + std::to_string( pinCount )
1607 + " (gschemrc library discovery may have failed)" );
1608 return;
1609 }
1610 }
1611
1612 BOOST_FAIL( "R1 symbol not found" );
1613}
1614
1615
1616// ============================================================================
1617// Phase 3: Bus ripper direction support
1618// ============================================================================
1619
1620BOOST_AUTO_TEST_CASE( BusRipperCreation )
1621{
1622 // bus_ripper_test.sch has a vertical bus with ripper_dir=1 and two horizontal
1623 // nets whose endpoints touch the bus. The importer should create
1624 // SCH_BUS_WIRE_ENTRY objects at each intersection.
1625 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/bus_ripper_test.sch" );
1626
1627 BOOST_REQUIRE( sheet );
1628 BOOST_REQUIRE( sheet->GetScreen() );
1629
1630 SCH_SCREEN* screen = sheet->GetScreen();
1631
1632 int busEntryCount = 0;
1633 int busCount = 0;
1634 int wireCount = 0;
1635
1636 for( SCH_ITEM* item : screen->Items() )
1637 {
1638 if( item->Type() == SCH_BUS_WIRE_ENTRY_T )
1639 {
1640 busEntryCount++;
1641
1642 SCH_BUS_WIRE_ENTRY* entry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
1643 VECTOR2I size = entry->GetSize();
1644
1645 // The wire goes left from the bus, so the entry x-size should point left (negative)
1646 BOOST_CHECK_MESSAGE( size.x < 0,
1647 "Bus entry x-size should be negative (wire goes left)" );
1648 }
1649 else if( item->Type() == SCH_LINE_T )
1650 {
1651 SCH_LINE* line = static_cast<SCH_LINE*>( item );
1652
1653 if( line->GetLayer() == LAYER_BUS )
1654 busCount++;
1655 else if( line->GetLayer() == LAYER_WIRE )
1656 wireCount++;
1657 }
1658 }
1659
1660 BOOST_CHECK_EQUAL( busCount, 1 );
1661 BOOST_CHECK_EQUAL( wireCount, 2 );
1662 BOOST_CHECK_EQUAL( busEntryCount, 2 );
1663}
1664
1665
1666// ============================================================================
1667// Phase 4: Symversion mismatch warning
1668// ============================================================================
1669
1670BOOST_AUTO_TEST_CASE( SymversionMismatchWarning )
1671{
1672 // symversion_test.sch has a component with symversion=1.0 but the
1673 // corresponding symver_resistor.sym has symversion=2.0. The importer
1674 // should emit a warning about the major version mismatch.
1675 WX_STRING_REPORTER reporter;
1676 m_plugin.SetReporter( &reporter );
1677
1678 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/symversion_test.sch" );
1679
1680 BOOST_REQUIRE( sheet );
1681 BOOST_REQUIRE( sheet->GetScreen() );
1682
1683 wxString messages = reporter.GetMessages();
1684
1685 BOOST_CHECK_MESSAGE( messages.Contains( wxT( "version mismatch" ) ),
1686 "Expected symversion mismatch warning in reporter output, got: "
1687 + messages );
1688 BOOST_CHECK_MESSAGE( messages.Contains( wxT( "R1" ) ),
1689 "Expected R1 reference in warning message, got: " + messages );
1690}
1691
1692
1693BOOST_AUTO_TEST_CASE( FuzzyMatchSuggestion )
1694{
1695 WX_STRING_REPORTER reporter;
1696 m_plugin.SetReporter( &reporter );
1697
1698 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/fuzzy_match_test.sch" );
1699
1700 BOOST_REQUIRE( sheet );
1701
1702 wxString messages = reporter.GetMessages();
1703
1704 BOOST_CHECK_MESSAGE( messages.Contains( wxT( "Did you mean" ) ),
1705 "Expected fuzzy match suggestion in reporter output, got: "
1706 + messages );
1707 BOOST_CHECK_MESSAGE( messages.Contains( wxT( "resistor-1.sym" ) ),
1708 "Expected 'resistor-1.sym' as suggestion, got: " + messages );
1709}
1710
1711
1712// ============================================================================
1713// Phase 3: Hierarchical sheet import
1714// ============================================================================
1715
1716BOOST_AUTO_TEST_CASE( HierarchicalSheetCreation )
1717{
1718 // hierarchy_test.sch has a component with source=hierarchy_sub.sch,
1719 // which should create an SCH_SHEET that references the sub-schematic.
1720 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/hierarchy_test.sch" );
1721
1722 BOOST_REQUIRE( sheet );
1723 BOOST_REQUIRE( sheet->GetScreen() );
1724
1725 SCH_SCREEN* screen = sheet->GetScreen();
1726
1727 int sheetCount = 0;
1728 int wireCount = 0;
1729 bool foundS1 = false;
1730
1731 for( SCH_ITEM* item : screen->Items() )
1732 {
1733 if( item->Type() == SCH_SHEET_T )
1734 {
1735 sheetCount++;
1736 SCH_SHEET* subSheet = static_cast<SCH_SHEET*>( item );
1737 wxString name = subSheet->GetField( FIELD_T::SHEET_NAME )->GetText();
1738
1739 if( name == wxT( "S1" ) )
1740 {
1741 foundS1 = true;
1742
1743 // The sheet should reference hierarchy_sub.sch
1744 wxString filename = subSheet->GetField( FIELD_T::SHEET_FILENAME )->GetText();
1745 BOOST_CHECK_EQUAL( filename, wxT( "hierarchy_sub.sch" ) );
1746
1747 // The sub-schematic should have been loaded into the sheet's screen
1748 SCH_SCREEN* subScreen = subSheet->GetScreen();
1749 BOOST_REQUIRE_MESSAGE( subScreen, "Sub-sheet screen should not be null" );
1750
1751 int subWireCount = 0;
1752
1753 for( SCH_ITEM* subItem : subScreen->Items() )
1754 {
1755 if( subItem->Type() == SCH_LINE_T )
1756 {
1757 SCH_LINE* line = static_cast<SCH_LINE*>( subItem );
1758
1759 if( line->GetLayer() == LAYER_WIRE )
1760 subWireCount++;
1761 }
1762 }
1763
1764 BOOST_CHECK_MESSAGE( subWireCount > 0,
1765 "Sub-schematic should contain wires" );
1766 }
1767 }
1768 else if( item->Type() == SCH_LINE_T )
1769 {
1770 SCH_LINE* line = static_cast<SCH_LINE*>( item );
1771
1772 if( line->GetLayer() == LAYER_WIRE )
1773 wireCount++;
1774 }
1775 }
1776
1777 // The source= component should have become a sheet, not a symbol
1778 BOOST_CHECK_EQUAL( sheetCount, 1 );
1779 BOOST_CHECK_MESSAGE( foundS1, "Sheet S1 not found" );
1780 BOOST_CHECK_EQUAL( wireCount, 1 );
1781}
1782
1783
1784BOOST_AUTO_TEST_CASE( HierarchicalSheetMissingSource )
1785{
1786 // A component with source= pointing to a nonexistent file should
1787 // create a sheet but with an empty screen (no crash).
1788 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/hierarchy_test.sch" );
1789
1790 BOOST_REQUIRE( sheet );
1791 BOOST_REQUIRE( sheet->GetScreen() );
1792}
1793
1794
1795// ============================================================================
1796// Multi-slot pin remapping
1797// ============================================================================
1798
1799BOOST_AUTO_TEST_CASE( MultiSlotPinRemapping )
1800{
1801 // multislot_test.sch has two instances of 7400-1.sym, one with slot=1
1802 // (slotdef=1:1,2,3) and one with slot=2 (slotdef=2:4,5,6). After
1803 // remapping, slot 1's private copy should have pins 1,2,3 and slot 2's
1804 // private copy should have pins 4,5,6.
1805 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/multislot_test.sch" );
1806
1807 BOOST_REQUIRE( sheet );
1808 BOOST_REQUIRE( sheet->GetScreen() );
1809
1810 SCH_SCREEN* screen = sheet->GetScreen();
1811
1812 std::vector<SCH_SYMBOL*> symbols;
1813
1814 for( SCH_ITEM* item : screen->Items() )
1815 {
1816 if( item->Type() == SCH_SYMBOL_T )
1817 symbols.push_back( static_cast<SCH_SYMBOL*>( item ) );
1818 }
1819
1820 BOOST_REQUIRE_EQUAL( symbols.size(), 2u );
1821
1822 // Sort by position to get a deterministic order (slot=1 is at x=40000, slot=2 at x=44000)
1823 std::sort( symbols.begin(), symbols.end(),
1824 []( const SCH_SYMBOL* a, const SCH_SYMBOL* b )
1825 {
1826 return a->GetPosition().x < b->GetPosition().x;
1827 } );
1828
1829 // Slot 1 should have pins 1, 2, 3
1830 std::set<wxString> slot1Pins;
1831
1832 for( SCH_PIN* pin : symbols[0]->GetLibPins() )
1833 slot1Pins.insert( pin->GetNumber() );
1834
1835 BOOST_CHECK_MESSAGE( slot1Pins.count( wxT( "1" ) ), "Slot 1 missing pin 1" );
1836 BOOST_CHECK_MESSAGE( slot1Pins.count( wxT( "2" ) ), "Slot 1 missing pin 2" );
1837 BOOST_CHECK_MESSAGE( slot1Pins.count( wxT( "3" ) ), "Slot 1 missing pin 3" );
1838 BOOST_CHECK_MESSAGE( !slot1Pins.count( wxT( "4" ) ), "Slot 1 should not have pin 4" );
1839
1840 // Slot 2 should have pins 4, 5, 6
1841 std::set<wxString> slot2Pins;
1842
1843 for( SCH_PIN* pin : symbols[1]->GetLibPins() )
1844 slot2Pins.insert( pin->GetNumber() );
1845
1846 BOOST_CHECK_MESSAGE( slot2Pins.count( wxT( "4" ) ), "Slot 2 missing pin 4" );
1847 BOOST_CHECK_MESSAGE( slot2Pins.count( wxT( "5" ) ), "Slot 2 missing pin 5" );
1848 BOOST_CHECK_MESSAGE( slot2Pins.count( wxT( "6" ) ), "Slot 2 missing pin 6" );
1849 BOOST_CHECK_MESSAGE( !slot2Pins.count( wxT( "1" ) ), "Slot 2 should not have pin 1" );
1850}
1851
1852
1853// ============================================================================
1854// Graphical attribute exclusion
1855// ============================================================================
1856
1857BOOST_AUTO_TEST_CASE( GraphicalAttributeExclusion )
1858{
1859 // graphical_attr_test.sch has R1 (normal) and R2 (graphical=1).
1860 // R2 should be excluded from BOM, board, and simulation.
1861 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/graphical_attr_test.sch" );
1862
1863 BOOST_REQUIRE( sheet );
1864 BOOST_REQUIRE( sheet->GetScreen() );
1865
1866 SCH_SCREEN* screen = sheet->GetScreen();
1867
1868 bool foundR1 = false;
1869 bool foundR2 = false;
1870
1871 for( SCH_ITEM* item : screen->Items() )
1872 {
1873 if( item->Type() != SCH_SYMBOL_T )
1874 continue;
1875
1876 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
1877 wxString ref = sym->GetField( FIELD_T::REFERENCE )->GetText();
1878
1879 if( ref == wxT( "R1" ) )
1880 {
1881 foundR1 = true;
1882 BOOST_CHECK( !sym->GetExcludedFromBOM() );
1883 BOOST_CHECK( !sym->GetExcludedFromBoard() );
1884 BOOST_CHECK( !sym->GetExcludedFromSim() );
1885 }
1886
1887 if( ref == wxT( "R2" ) )
1888 {
1889 foundR2 = true;
1890 BOOST_CHECK( sym->GetExcludedFromBOM() );
1891 BOOST_CHECK( sym->GetExcludedFromBoard() );
1892 BOOST_CHECK( sym->GetExcludedFromSim() );
1893 }
1894 }
1895
1896 BOOST_CHECK_MESSAGE( foundR1, "R1 symbol not found" );
1897 BOOST_CHECK_MESSAGE( foundR2, "R2 symbol not found" );
1898}
1899
1900
1901// ============================================================================
1902// Phase 3: Multi-page schematic support
1903// ============================================================================
1904
1905BOOST_AUTO_TEST_CASE( MultiPageSchematicImport )
1906{
1907 // When LoadSchematicFile receives an "additional_schematics" property,
1908 // it should create sub-sheets on the root screen for each additional page.
1909 std::string page2Path = KI_TEST::GetEeschemaTestDataDir() + "/io/geda/multipage_page2.sch";
1910
1911 std::map<std::string, UTF8> props;
1912 props["additional_schematics"] = page2Path;
1913
1914 SCH_SHEET* sheet = LoadGedaSchematicWithProperties( "/io/geda/multipage_page1.sch", props );
1915
1916 BOOST_REQUIRE( sheet );
1917 BOOST_REQUIRE( sheet->GetScreen() );
1918
1919 SCH_SCREEN* screen = sheet->GetScreen();
1920
1921 int wireCount = 0;
1922 int sheetCount = 0;
1923 SCH_SHEET* subSheet = nullptr;
1924
1925 for( SCH_ITEM* item : screen->Items() )
1926 {
1927 if( item->Type() == SCH_LINE_T )
1928 {
1929 SCH_LINE* line = static_cast<SCH_LINE*>( item );
1930
1931 if( line->GetLayer() == LAYER_WIRE )
1932 wireCount++;
1933 }
1934 else if( item->Type() == SCH_SHEET_T )
1935 {
1936 sheetCount++;
1937 subSheet = static_cast<SCH_SHEET*>( item );
1938 }
1939 }
1940
1941 // Page 1 has one wire
1942 BOOST_CHECK_EQUAL( wireCount, 1 );
1943
1944 // One sub-sheet should have been created for page 2
1945 BOOST_CHECK_EQUAL( sheetCount, 1 );
1946
1947 BOOST_REQUIRE( subSheet );
1948 BOOST_CHECK_EQUAL( subSheet->GetField( FIELD_T::SHEET_NAME )->GetText(), wxT( "Page 2" ) );
1950 wxT( "multipage_page2.sch" ) );
1951
1952 // The sub-sheet should have a loaded screen with wires from page 2
1953 SCH_SCREEN* subScreen = subSheet->GetScreen();
1954 BOOST_REQUIRE_MESSAGE( subScreen, "Sub-sheet screen should not be null" );
1955
1956 int subWireCount = 0;
1957
1958 for( SCH_ITEM* subItem : subScreen->Items() )
1959 {
1960 if( subItem->Type() == SCH_LINE_T )
1961 {
1962 SCH_LINE* line = static_cast<SCH_LINE*>( subItem );
1963
1964 if( line->GetLayer() == LAYER_WIRE )
1965 subWireCount++;
1966 }
1967 }
1968
1969 BOOST_CHECK_MESSAGE( subWireCount > 0, "Sub-sheet should contain wires from page 2" );
1970}
1971
1972
1973BOOST_AUTO_TEST_CASE( BuiltinSymbolsLoad )
1974{
1975 std::vector<wxString> builtinNames = {
1976 wxT( "resistor-1.sym" ), wxT( "resistor-2.sym" ),
1977 wxT( "capacitor-1.sym" ), wxT( "capacitor-2.sym" ),
1978 wxT( "gnd-1.sym" ), wxT( "gnd-2.sym" ),
1979 wxT( "generic-power.sym" ),
1980 wxT( "input-1.sym" ), wxT( "output-1.sym" ),
1981 wxT( "nc-right-1.sym" ), wxT( "nc-left-1.sym" ),
1982 wxT( "terminal-1.sym" ),
1983 wxT( "vcc-1.sym" ), wxT( "vcc-2.sym" ),
1984 wxT( "vdd-1.sym" ), wxT( "vss-1.sym" ),
1985 wxT( "vee-1.sym" ),
1986 wxT( "5V-plus-1.sym" ), wxT( "3.3V-plus-1.sym" ),
1987 wxT( "12V-plus-1.sym" ),
1988 wxT( "diode-1.sym" ), wxT( "zener-1.sym" ),
1989 wxT( "schottky-1.sym" ), wxT( "led-1.sym" ),
1990 wxT( "npn-1.sym" ), wxT( "pnp-1.sym" ),
1991 wxT( "nmos-1.sym" ), wxT( "pmos-1.sym" ),
1992 wxT( "opamp-1.sym" ), wxT( "inductor-1.sym" ),
1993 wxT( "7400-1.sym" ), wxT( "7402-1.sym" ),
1994 wxT( "7404-1.sym" ), wxT( "7408-1.sym" ),
1995 wxT( "7432-1.sym" ), wxT( "7486-1.sym" ),
1996 wxT( "busripper-1.sym" ), wxT( "busripper-2.sym" ),
1997 wxT( "title-B.sym" ),
1998 };
1999
2000 const auto& symbols = SCH_IO_GEDA::getBuiltinSymbols();
2001
2002 for( const wxString& name : builtinNames )
2003 {
2004 BOOST_CHECK_MESSAGE( symbols.find( name ) != symbols.end(),
2005 "Builtin symbol should exist: " + name );
2006
2007 if( symbols.find( name ) != symbols.end() )
2008 {
2009 const wxString& content = symbols.at( name );
2010 BOOST_CHECK_MESSAGE( content.StartsWith( wxT( "v " ) ),
2011 "Builtin symbol should start with version line: " + name );
2012 }
2013 }
2014
2015 BOOST_CHECK_GE( symbols.size(), builtinNames.size() );
2016}
2017
2018
2019BOOST_AUTO_TEST_CASE( TJunctionDetection )
2020{
2021 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/tjunction_test.sch" );
2022
2023 BOOST_REQUIRE( sheet );
2024
2025 SCH_SCREEN* screen = sheet->GetScreen();
2026
2027 int junctionCount = 0;
2028
2029 for( SCH_ITEM* item : screen->Items() )
2030 {
2031 if( item->Type() == SCH_JUNCTION_T )
2032 junctionCount++;
2033 }
2034
2035 // Two T-junctions: vertical wires meet the interior of horizontal wires
2036 BOOST_CHECK_EQUAL( junctionCount, 2 );
2037}
2038
2039
2040BOOST_AUTO_TEST_CASE( PinOrientationCorrect )
2041{
2042 // Load a schematic using 7400-1.sym to verify pin orientation.
2043 // 7400-1.sym has:
2044 // Pin 1 (A): P 0 200 200 200 1 0 0 whichend=0 → conn=(0,200) body=(200,200)
2045 // Pin 2 (B): P 0 0 200 0 1 0 0 whichend=0 → conn=(0,0) body=(200,0)
2046 // Pin 3 (Y): P 500 100 300 100 1 0 0 whichend=0 → conn=(500,100) body=(300,100)
2047 //
2048 // Pin 1: body is to the right of connection → PIN_RIGHT
2049 // Pin 2: body is to the right of connection → PIN_RIGHT
2050 // Pin 3: body is to the left of connection → PIN_LEFT
2051 const auto& builtins = SCH_IO_GEDA::getBuiltinSymbols();
2052 auto it = builtins.find( wxT( "7400-1.sym" ) );
2053
2054 BOOST_REQUIRE( it != builtins.end() );
2055
2056 SCH_IO_GEDA io;
2057
2058 // Parse the builtin symbol to get pin orientations
2059 wxString tempPath = wxFileName::CreateTempFileName( wxT( "geda_pin_test_" ) );
2060 BOOST_REQUIRE( !tempPath.IsEmpty() );
2061
2062 {
2063 wxFile temp( tempPath, wxFile::write );
2064 BOOST_REQUIRE( temp.IsOpened() );
2065 temp.Write( it->second );
2066 }
2067
2068 SCH_IO_GEDA loader;
2069 std::unique_ptr<LIB_SYMBOL> sym;
2070
2071 // Use a known .sym file from test data instead
2072 wxString symPath( KI_TEST::GetEeschemaTestDataDir() + "/io/geda/7400-1.sym" );
2073 wxTextFile file;
2074
2075 BOOST_REQUIRE( file.Open( symPath ) );
2076
2077 wxRemoveFile( tempPath );
2078
2079 // Verify the pins have correct orientation via the symbol loaded from test data
2080 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/minimal_test.sch" );
2081
2082 BOOST_REQUIRE( sheet );
2083
2084 SCH_SCREEN* screen = sheet->GetScreen();
2085
2086 // Find R1 (resistor-1.sym) which has a horizontal pin layout.
2087 // resistor-1.sym is a horizontal resistor: pin 1 at x=0, pin 2 at x=900
2088 // Pin 1: conn=(0,200) body=(200,200) → PIN_RIGHT (body extends right from conn)
2089 // Pin 2: conn=(900,200) body=(700,200) → PIN_LEFT (body extends left from conn)
2090 for( SCH_ITEM* item : screen->Items() )
2091 {
2092 if( item->Type() != SCH_SYMBOL_T )
2093 continue;
2094
2095 SCH_SYMBOL* sym2 = static_cast<SCH_SYMBOL*>( item );
2096
2097 if( sym2->GetField( FIELD_T::REFERENCE )->GetText() != wxT( "R1" ) )
2098 continue;
2099
2100 const LIB_SYMBOL* libSym = sym2->GetLibSymbolRef().get();
2101
2102 BOOST_REQUIRE( libSym );
2103
2104 std::vector<SCH_PIN*> pins = libSym->GetPins();
2105
2106 BOOST_REQUIRE_GE( pins.size(), 2u );
2107
2108 // Check that we have both left and right pins (not all the same direction)
2109 bool hasRight = false;
2110 bool hasLeft = false;
2111
2112 for( SCH_PIN* pin : pins )
2113 {
2114 if( pin->GetOrientation() == PIN_ORIENTATION::PIN_RIGHT )
2115 hasRight = true;
2116
2117 if( pin->GetOrientation() == PIN_ORIENTATION::PIN_LEFT )
2118 hasLeft = true;
2119 }
2120
2121 BOOST_CHECK_MESSAGE( hasRight && hasLeft,
2122 "Resistor should have both PIN_RIGHT and PIN_LEFT pins" );
2123 break;
2124 }
2125}
2126
2127
2128BOOST_AUTO_TEST_CASE( PageSizeAndContentPlacement )
2129{
2130 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/ayab/ayab_rs.sch" );
2131
2132 BOOST_REQUIRE( sheet );
2133
2134 SCH_SCREEN* screen = sheet->GetScreen();
2135
2136 PAGE_INFO pageInfo = screen->GetPageSettings();
2137 VECTOR2I pageSizeIU = pageInfo.GetSizeIU( schIUScale.IU_PER_MILS );
2138
2139 int pageWidthMils = schIUScale.IUToMils( pageSizeIU.x );
2140 int pageHeightMils = schIUScale.IUToMils( pageSizeIU.y );
2141
2142 // The ayab schematic uses a D-size title block (~34x22 inches).
2143 // Page dimensions should be close to D-size, not astronomically large.
2144 BOOST_CHECK_LT( pageWidthMils, 40000 );
2145 BOOST_CHECK_LT( pageHeightMils, 30000 );
2146 BOOST_CHECK_GT( pageWidthMils, 20000 );
2147 BOOST_CHECK_GT( pageHeightMils, 15000 );
2148
2149 // Compute actual content bounding box
2150 BOX2I bbox;
2151
2152 for( SCH_ITEM* item : screen->Items() )
2153 bbox.Merge( item->GetBoundingBox() );
2154
2155 BOOST_REQUIRE( bbox.GetWidth() > 0 );
2156 BOOST_REQUIRE( bbox.GetHeight() > 0 );
2157
2158 // Content should be within the page boundaries
2159 int margin = schIUScale.MilsToIU( 500 );
2160
2161 BOOST_CHECK_GE( bbox.GetOrigin().x, -margin );
2162 BOOST_CHECK_GE( bbox.GetOrigin().y, -margin );
2163 BOOST_CHECK_LE( bbox.GetEnd().x, pageSizeIU.x + margin );
2164 BOOST_CHECK_LE( bbox.GetEnd().y, pageSizeIU.y + margin );
2165}
2166
2167
2168// ============================================================================
2169// Properties-based search paths
2170// ============================================================================
2171
2172BOOST_AUTO_TEST_CASE( PowerDetectionWithProjectOverride )
2173{
2174 // When a project provides its own gnd-1.sym without a net= attribute,
2175 // the importer should still detect it as a power symbol by inheriting
2176 // the builtin's net= attribute.
2177 SCH_SHEET* sheet = LoadGedaSchematic( "/io/geda/power_override_test/power_override_test.sch" );
2178
2179 BOOST_REQUIRE( sheet );
2180 BOOST_REQUIRE( sheet->GetScreen() );
2181
2182 bool foundPower = false;
2183
2184 for( SCH_ITEM* item : sheet->GetScreen()->Items() )
2185 {
2186 if( item->Type() != SCH_SYMBOL_T )
2187 continue;
2188
2189 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
2190 LIB_SYMBOL* libSym = sym->GetLibSymbolRef().get();
2191
2192 if( !libSym )
2193 continue;
2194
2195 if( libSym->IsGlobalPower() )
2196 {
2197 foundPower = true;
2198 BOOST_CHECK_EQUAL( sym->GetField( FIELD_T::VALUE )->GetText(), wxString( wxT( "GND" ) ) );
2199 BOOST_CHECK( !sym->GetField( FIELD_T::REFERENCE )->IsVisible() );
2200 }
2201 }
2202
2203 BOOST_CHECK_MESSAGE( foundPower,
2204 "gnd-1.sym without net= attribute should still be detected as power "
2205 "symbol when builtin has net=GND:1" );
2206}
2207
2208
2209BOOST_AUTO_TEST_CASE( PropertiesSearchPaths )
2210{
2211 std::string extraDir = KI_TEST::GetEeschemaTestDataDir() + "/io/geda/extra-syms";
2212
2213 std::map<std::string, UTF8> props;
2214 props["sym_search_paths"] = extraDir;
2215
2216 SCH_SHEET* sheet = LoadGedaSchematicWithProperties( "/io/geda/props_test.sch", props );
2217
2218 BOOST_REQUIRE( sheet );
2219 BOOST_REQUIRE( sheet->GetScreen() );
2220
2221 SCH_SCREEN* screen = sheet->GetScreen();
2222
2223 for( SCH_ITEM* item : screen->Items() )
2224 {
2225 if( item->Type() != SCH_SYMBOL_T )
2226 continue;
2227
2228 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
2229 int pinCount = static_cast<int>( sym->GetLibPins().size() );
2230
2231 BOOST_CHECK_MESSAGE( pinCount == 2,
2232 "Expected 2-pin symbol from extra-syms, got "
2233 + std::to_string( pinCount ) );
2234 return;
2235 }
2236
2237 BOOST_FAIL( "Symbol not found" );
2238}
2239
2240
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:114
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr const Vec GetEnd() const
Definition box2.h:212
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr size_type GetHeight() const
Definition box2.h:215
constexpr const Vec & GetOrigin() const
Definition box2.h:210
int AsTenthsOfADegree() const
Definition eda_angle.h:118
SHAPE_T GetShape() const
Definition eda_shape.h:169
const EDA_ANGLE & GetTextAngle() const
Definition eda_text.h:147
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:98
virtual bool IsVisible() const
Definition eda_text.h:187
const UTF8 & GetLibItemName() const
Definition lib_id.h:102
Define a library symbol object.
Definition lib_symbol.h:83
bool IsPower() const override
std::vector< SCH_PIN * > GetPins() const override
bool IsGlobalPower() const override
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:79
const VECTOR2D GetSizeIU(double aIUScale) const
Gets the page size in internal units.
Definition page_info.h:177
Holds all the data relating to one schematic.
Definition schematic.h:88
VECTOR2I GetSize() const
Class for a wire to bus entry.
virtual const wxString & GetText() const override
Return the string associated with the text object.
Definition sch_field.h:116
A SCH_IO derivation for loading gEDA/gschem schematic files (.sch).
Definition sch_io_geda.h:74
static const std::map< wxString, wxString > & getBuiltinSymbols()
Return the map of built-in gEDA symbol definitions (symbol name -> .sym content).
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:168
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition sch_item.h:341
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:42
VECTOR2I GetEndPoint() const
Definition sch_line.h:148
VECTOR2I GetStartPoint() const
Definition sch_line.h:139
VECTOR2I GetPosition() const override
const PAGE_INFO & GetPageSettings() const
Definition sch_screen.h:141
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:119
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this sheet.
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
Schematic symbol object.
Definition sch_symbol.h:76
bool GetExcludedFromSim(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
bool GetExcludedFromBOM(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
VECTOR2I GetPosition() const override
Definition sch_symbol.h:867
const LIB_ID & GetLibId() const override
Definition sch_symbol.h:165
std::vector< SCH_PIN * > GetLibPins() const
Populate a vector with all the pins from the library object that match the current unit and bodyStyle...
bool GetExcludedFromBoard(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
int GetOrientation() const override
Get the display symbol orientation.
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:184
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
VECTOR2I GetPosition() const override
Definition sch_text.h:147
A wrapper for reporting to a wxString object.
Definition reporter.h:191
const wxString & GetMessages() const
Definition reporter.cpp:78
@ LAYER_WIRE
Definition layer_ids.h:452
@ LAYER_NOTES
Definition layer_ids.h:467
@ LAYER_BUS
Definition layer_ids.h:453
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ PIN_RIGHT
The pin extends rightwards from the connection point.
Definition pin_type.h:111
@ PIN_LEFT
The pin extends leftwards from the connection point: Probably on the right side of the symbol.
Definition pin_type.h:118
SCH_SHEET * LoadGedaSchematicWithProperties(const std::string &aRelPath, const std::map< std::string, UTF8 > &aProps)
std::unique_ptr< SCHEMATIC > m_schematic
SCH_SHEET * LoadGedaSchematic(const std::string &aRelPath)
@ SYM_ORIENT_270
Definition symbol.h:42
@ SYM_MIRROR_Y
Definition symbol.h:44
@ SYM_ORIENT_180
Definition symbol.h:41
@ SYM_ORIENT_90
Definition symbol.h:40
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ DATASHEET
name of datasheet
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_CASE(CanReadSchematicFile)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
KIBIS_PIN * pin
VECTOR2I end
BOOST_CHECK_EQUAL(result, "25.4")
@ SCH_LINE_T
Definition typeinfo.h:167
@ SCH_NO_CONNECT_T
Definition typeinfo.h:164
@ SCH_SYMBOL_T
Definition typeinfo.h:176
@ SCH_LABEL_T
Definition typeinfo.h:171
@ SCH_SHEET_T
Definition typeinfo.h:179
@ SCH_SHAPE_T
Definition typeinfo.h:153
@ SCH_TEXT_T
Definition typeinfo.h:155
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:165
@ SCH_BITMAP_T
Definition typeinfo.h:168
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:172
@ SCH_JUNCTION_T
Definition typeinfo.h:163
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695