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