KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_orcad_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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
22
28
29#include <schematic.h>
30#include <connection_graph.h>
31#include <sch_screen.h>
32#include <sch_sheet.h>
33#include <sch_sheet_pin.h>
34#include <sch_sheet_path.h>
35#include <sch_symbol.h>
36#include <sch_label.h>
37#include <sch_line.h>
38#include <sch_bitmap.h>
39#include <sch_shape.h>
40#include <sch_pin.h>
41#include <lib_symbol.h>
42#include <reporter.h>
44
45#include <wx/ffile.h>
46#include <wx/filefn.h>
47#include <wx/filename.h>
48
49#include <algorithm>
50#include <filesystem>
51#include <fstream>
52#include <map>
53#include <memory>
54#include <set>
55#include <sstream>
56#include <string>
57
58
59namespace
60{
61
62static void appendLe32( std::vector<uint8_t>& aBytes, uint32_t aValue )
63{
64 for( int shift = 0; shift < 32; shift += 8 )
65 aBytes.push_back( static_cast<uint8_t>( aValue >> shift ) );
66}
67
68
69static void appendLe16( std::vector<uint8_t>& aBytes, uint16_t aValue )
70{
71 aBytes.push_back( static_cast<uint8_t>( aValue ) );
72 aBytes.push_back( static_cast<uint8_t>( aValue >> 8 ) );
73}
74
75
76static void appendLzt( std::vector<uint8_t>& aBytes, const std::string& aValue )
77{
78 appendLe16( aBytes, static_cast<uint16_t>( aValue.size() ) );
79 aBytes.insert( aBytes.end(), aValue.begin(), aValue.end() );
80 aBytes.push_back( 0 );
81}
82
83
84static void writeLe16( std::vector<uint8_t>& aBytes, size_t aOffset, uint16_t aValue )
85{
86 aBytes[aOffset] = static_cast<uint8_t>( aValue );
87 aBytes[aOffset + 1] = static_cast<uint8_t>( aValue >> 8 );
88}
89
90
91static void writeLe32( std::vector<uint8_t>& aBytes, size_t aOffset, uint32_t aValue )
92{
93 for( int shift = 0; shift < 32; shift += 8 )
94 aBytes[aOffset++] = static_cast<uint8_t>( aValue >> shift );
95}
96
97
98static std::vector<uint8_t> makeOlePreviewCfb( const std::vector<uint16_t>& aName, const std::vector<uint8_t>& aStream )
99{
100 constexpr uint32_t FREE_SECTOR = 0xFFFFFFFF;
101 constexpr uint32_t END_OF_CHAIN = 0xFFFFFFFE;
102 constexpr uint32_t FAT_SECTOR = 0xFFFFFFFD;
103 constexpr size_t SECTOR_SIZE = 512;
104 constexpr size_t STREAM_SECTORS = 8;
105
106 std::vector<uint8_t> cfb( SECTOR_SIZE * ( 3 + STREAM_SECTORS ), 0 );
107 const uint8_t magic[] = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
108 std::copy( std::begin( magic ), std::end( magic ), cfb.begin() );
109 writeLe16( cfb, 24, 0x003E );
110 writeLe16( cfb, 26, 3 );
111 writeLe16( cfb, 28, 0xFFFE );
112 writeLe16( cfb, 30, 9 );
113 writeLe16( cfb, 32, 6 );
114 writeLe32( cfb, 44, 1 );
115 writeLe32( cfb, 48, 1 );
116 writeLe32( cfb, 56, 4096 );
117 writeLe32( cfb, 60, END_OF_CHAIN );
118 writeLe32( cfb, 68, END_OF_CHAIN );
119
120 for( size_t i = 0; i < 109; ++i )
121 writeLe32( cfb, 76 + 4 * i, i == 0 ? 0 : FREE_SECTOR );
122
123 size_t fat = SECTOR_SIZE;
124 writeLe32( cfb, fat, FAT_SECTOR );
125 writeLe32( cfb, fat + 4, END_OF_CHAIN );
126
127 for( size_t i = 0; i < STREAM_SECTORS; ++i )
128 writeLe32( cfb, fat + 4 * ( 2 + i ), i + 1 == STREAM_SECTORS ? END_OF_CHAIN : 3 + i );
129
130 for( size_t i = 2 + STREAM_SECTORS; i < SECTOR_SIZE / 4; ++i )
131 writeLe32( cfb, fat + 4 * i, FREE_SECTOR );
132
133 size_t root = 2 * SECTOR_SIZE;
134 writeLe16( cfb, root, 'R' );
135 writeLe16( cfb, root + 2, 0 );
136 writeLe16( cfb, root + 64, 4 );
137 cfb[root + 66] = 5;
138 writeLe32( cfb, root + 68, FREE_SECTOR );
139 writeLe32( cfb, root + 72, FREE_SECTOR );
140 writeLe32( cfb, root + 76, 1 );
141 writeLe32( cfb, root + 116, END_OF_CHAIN );
142
143 size_t entry = root + 128;
144
145 for( size_t i = 0; i < aName.size(); ++i )
146 writeLe16( cfb, entry + 2 * i, aName[i] );
147
148 writeLe16( cfb, entry + 64, static_cast<uint16_t>( 2 * aName.size() ) );
149 cfb[entry + 66] = 2;
150 writeLe32( cfb, entry + 68, FREE_SECTOR );
151 writeLe32( cfb, entry + 72, FREE_SECTOR );
152 writeLe32( cfb, entry + 76, FREE_SECTOR );
153 writeLe32( cfb, entry + 116, 2 );
154 writeLe32( cfb, entry + 120, STREAM_SECTORS * SECTOR_SIZE );
155
156 std::copy_n( aStream.begin(), std::min( aStream.size(), STREAM_SECTORS * SECTOR_SIZE ),
157 cfb.begin() + 3 * SECTOR_SIZE );
158 return cfb;
159}
160
162struct TEMP_TEST_FILE
163{
164 TEMP_TEST_FILE( const wxString& aFileName, const wxString& aContents ) :
165 m_path( wxFileName( wxFileName::GetTempDir(), aFileName ).GetFullPath() )
166 {
167 wxFFile file( m_path, wxS( "w" ) );
168
169 if( file.IsOpened() )
170 file.Write( aContents );
171 }
172
173 ~TEMP_TEST_FILE() { wxRemoveFile( m_path ); }
174
175 wxString m_path;
176};
177
178} // namespace
179
180
182{
184 m_schematic( new SCHEMATIC( nullptr ) )
185 {
186 m_manager.LoadProject( "" );
187 m_schematic->SetProject( &m_manager.Prj() );
188 m_schematic->CurrentSheet().clear();
189 m_schematic->CurrentSheet().push_back( &m_schematic->Root() );
190 }
191
193 {
194 m_schematic.reset();
195 }
196
197 std::string dataPath( const std::string& aRelPath ) const
198 {
199 return KI_TEST::GetEeschemaTestDataDir() + "io/orcad/" + aRelPath;
200 }
201
202 SCH_SHEET* LoadOrcadSchematic( const std::string& aRelPath )
203 {
204 return m_plugin.LoadSchematicFile( dataPath( aRelPath ), m_schematic.get() );
205 }
206
208 std::unique_ptr<SCHEMATIC> m_schematic;
210};
211
212
213BOOST_FIXTURE_TEST_SUITE( OrcadSchImport, ORCAD_SCH_IMPORT_FIXTURE )
214
215
216BOOST_AUTO_TEST_CASE( PrimitiveLineWidth )
217{
218 std::vector<uint8_t> bytes = { ORCAD_PRIM_LINE, ORCAD_PRIM_LINE };
219 appendLe32( bytes, 32 );
220 appendLe32( bytes, 0 );
221 appendLe32( bytes, 10 );
222 appendLe32( bytes, 20 );
223 appendLe32( bytes, 30 );
224 appendLe32( bytes, 40 );
225 appendLe32( bytes, 1 );
226 appendLe32( bytes, 2 );
227
228 ORCAD_STREAM stream( bytes.data(), bytes.size() );
229 std::optional<ORCAD_PRIMITIVE> primitive = OrcadReadPrimitive( stream );
230
231 BOOST_REQUIRE( primitive );
232 BOOST_CHECK( primitive->kind == ORCAD_PRIM_KIND::LINE );
233 BOOST_CHECK_EQUAL( primitive->lineWidth, 2 );
234}
235
236
237BOOST_AUTO_TEST_CASE( CaptureColorPalette )
238{
239 BOOST_CHECK( OrcadColor( 8 ) == KIGFX::COLOR4D( 1.0, 0.0, 0.0, 1.0 ) );
240 BOOST_CHECK( OrcadColor( 18 ) == KIGFX::COLOR4D( 0.0, 1.0, 0.0, 1.0 ) );
241 BOOST_CHECK( OrcadColor( 28 ) == KIGFX::COLOR4D( 0.0, 0.0, 1.0, 1.0 ) );
242 BOOST_CHECK( OrcadColor( 40 ) == KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 ) );
243 BOOST_CHECK( OrcadColor( 47 ) == KIGFX::COLOR4D( 1.0, 1.0, 1.0, 1.0 ) );
244 BOOST_CHECK( OrcadColor( 48 ) == KIGFX::COLOR4D::UNSPECIFIED );
245 BOOST_CHECK( OrcadColor( 0 ) == KIGFX::COLOR4D::UNSPECIFIED );
246}
247
248
249BOOST_AUTO_TEST_CASE( CaptureCompoundFileName )
250{
251 BOOST_CHECK_EQUAL( OrcadNormalizeCfbName( std::string( "Sch 2" ) + '\x03' + " PCI Connector" ),
252 "Sch 2: PCI Connector" );
253}
254
255
256BOOST_AUTO_TEST_CASE( CaptureStrokeAndFillSemantics )
257{
258 BOOST_CHECK_EQUAL( OrcadLineWidthIu( 0 ), schIUScale.MilsToIU( 5 ) );
259 BOOST_CHECK_EQUAL( OrcadLineWidthIu( 1 ), schIUScale.MilsToIU( 10 ) );
260 BOOST_CHECK_EQUAL( OrcadLineWidthIu( 2 ), schIUScale.MilsToIU( 15 ) );
262
263 BOOST_CHECK( OrcadLineStyle( 0 ) == LINE_STYLE::SOLID );
264 BOOST_CHECK( OrcadLineStyle( 4 ) == LINE_STYLE::DASHDOTDOT );
265 BOOST_CHECK( OrcadLineStyle( 5 ) == LINE_STYLE::DEFAULT );
266
267 BOOST_CHECK( OrcadFillType( 0, 0 ) == FILL_T::FILLED_WITH_BG_BODYCOLOR );
268 BOOST_CHECK( OrcadFillType( 1, 0 ) == FILL_T::NO_FILL );
269 BOOST_CHECK( OrcadFillType( 2, 0 ) == FILL_T::HATCH );
270 BOOST_CHECK( OrcadFillType( 2, 4 ) == FILL_T::REVERSE_HATCH );
271 BOOST_CHECK( OrcadFillType( 2, 5 ) == FILL_T::CROSS_HATCH );
272}
273
274
275BOOST_AUTO_TEST_CASE( CapturePageOrder )
276{
277 wxString dashed = wxS( "03 - CAN" );
278 wxString dotted = wxS( "02.uC" );
279 wxString colon = wxS( "13:IMU" );
280 wxString folder = wxS( "Sch 7: CAN Drivers" );
281 wxString pager = wxS( "PAGER 8" );
282 wxString plain = wxS( "Overview" );
283
284 BOOST_CHECK_EQUAL( OrcadPageOrder( dashed ), 3 );
285 BOOST_CHECK_EQUAL( dashed, wxS( "CAN" ) );
286 BOOST_CHECK_EQUAL( OrcadPageOrder( dotted ), 2 );
287 BOOST_CHECK_EQUAL( dotted, wxS( "02.uC" ) );
288 BOOST_CHECK_EQUAL( OrcadPageOrder( colon ), 13 );
289 BOOST_CHECK_EQUAL( colon, wxS( "13:IMU" ) );
290 BOOST_CHECK_EQUAL( OrcadPageOrder( folder ), 7 );
291 BOOST_CHECK_EQUAL( folder, wxS( "Sch 7: CAN Drivers" ) );
292 BOOST_CHECK_EQUAL( OrcadPageOrder( pager ), -1 );
293 BOOST_CHECK_EQUAL( pager, wxS( "PAGER 8" ) );
294 BOOST_CHECK_EQUAL( OrcadPageOrder( plain ), -1 );
295 BOOST_CHECK_EQUAL( plain, wxS( "Overview" ) );
296}
297
298
299BOOST_AUTO_TEST_CASE( PrimitiveStrokeAndFillStyles )
300{
301 std::vector<uint8_t> bytes = { ORCAD_PRIM_RECT, ORCAD_PRIM_RECT };
302 appendLe32( bytes, 40 );
303 appendLe32( bytes, 0 );
304 appendLe32( bytes, 10 );
305 appendLe32( bytes, 20 );
306 appendLe32( bytes, 30 );
307 appendLe32( bytes, 40 );
308 appendLe32( bytes, 2 );
309 appendLe32( bytes, 3 );
310 appendLe32( bytes, 2 );
311 appendLe32( bytes, 5 );
312
313 ORCAD_STREAM stream( bytes.data(), bytes.size() );
314 std::optional<ORCAD_PRIMITIVE> primitive = OrcadReadPrimitive( stream );
315
316 BOOST_REQUIRE( primitive );
317 BOOST_CHECK_EQUAL( primitive->lineStyle, 2 );
318 BOOST_CHECK_EQUAL( primitive->lineWidth, 3 );
319 BOOST_CHECK_EQUAL( primitive->fillStyle, 2 );
320 BOOST_CHECK_EQUAL( primitive->hatchStyle, 5 );
321}
322
323
324BOOST_AUTO_TEST_CASE( PrimitivePolygonStylesBeforePoints )
325{
326 std::vector<uint8_t> bytes = { ORCAD_PRIM_POLYGON, ORCAD_PRIM_POLYGON };
327 appendLe32( bytes, 34 );
328 appendLe32( bytes, 0 );
329 appendLe32( bytes, 1 );
330 appendLe32( bytes, 2 );
331 appendLe32( bytes, 2 );
332 appendLe32( bytes, 4 );
333 appendLe16( bytes, 2 );
334 appendLe16( bytes, 20 );
335 appendLe16( bytes, 10 );
336 appendLe16( bytes, 40 );
337 appendLe16( bytes, 30 );
338
339 ORCAD_STREAM stream( bytes.data(), bytes.size() );
340 std::optional<ORCAD_PRIMITIVE> primitive = OrcadReadPrimitive( stream );
341
342 BOOST_REQUIRE( primitive );
343 BOOST_CHECK_EQUAL( primitive->lineStyle, 1 );
344 BOOST_CHECK_EQUAL( primitive->lineWidth, 2 );
345 BOOST_CHECK_EQUAL( primitive->fillStyle, 2 );
346 BOOST_CHECK_EQUAL( primitive->hatchStyle, 4 );
347 BOOST_REQUIRE_EQUAL( primitive->points.size(), 2u );
348 BOOST_CHECK( ( primitive->points[0] == ORCAD_POINT{ 10, 20 } ) );
349 BOOST_CHECK( ( primitive->points[1] == ORCAD_POINT{ 30, 40 } ) );
350}
351
352
353BOOST_AUTO_TEST_CASE( PrimitiveSymbolVectorContents )
354{
355 std::vector<uint8_t> bytes = { ORCAD_PRIM_SYMBOL_VECTOR, ORCAD_PRIM_SYMBOL_VECTOR };
356 appendLe32( bytes, 63 );
357 appendLe32( bytes, 0 );
358 bytes.push_back( ORCAD_PRIM_SYMBOL_VECTOR );
359 appendLe16( bytes, 0 );
360 bytes.insert( bytes.end(), std::begin( ORCAD_STREAM::PREAMBLE ), std::end( ORCAD_STREAM::PREAMBLE ) );
361 appendLe32( bytes, 0 );
362 appendLe16( bytes, 12 );
363 appendLe16( bytes, 26 );
364 appendLe16( bytes, 1 );
365 bytes.insert( bytes.end(), { ORCAD_PRIM_POLYLINE, 0, ORCAD_PRIM_POLYLINE } );
366 appendLe32( bytes, 30 );
367 appendLe32( bytes, 0 );
368 appendLe32( bytes, 0 );
369 appendLe32( bytes, 0 );
370 appendLe16( bytes, 3 );
371 appendLe16( bytes, 8 );
372 appendLe16( bytes, 4 );
373 appendLe16( bytes, 0 );
374 appendLe16( bytes, 4 );
375 appendLe16( bytes, 0 );
376 appendLe16( bytes, 16 );
377 appendLe16( bytes, 10 );
378 bytes.insert( bytes.end(), { 'H', 'y', 's', 't', 'e', 'r', 'e', 's', 'i', 's', 0 } );
379
380 ORCAD_STREAM stream( bytes.data(), bytes.size() );
381 std::optional<ORCAD_PRIMITIVE> primitive = OrcadReadPrimitive( stream );
382
383 BOOST_REQUIRE( primitive );
384 BOOST_CHECK( primitive->kind == ORCAD_PRIM_KIND::GROUP );
385 BOOST_CHECK_EQUAL( primitive->x1, 12 );
386 BOOST_CHECK_EQUAL( primitive->y1, 26 );
387 BOOST_REQUIRE_EQUAL( primitive->children.size(), 1u );
388 BOOST_CHECK( primitive->children[0].kind == ORCAD_PRIM_KIND::POLYLINE );
389 BOOST_REQUIRE_EQUAL( primitive->children[0].points.size(), 3u );
390 BOOST_CHECK( ( primitive->children[0].points[2] == ORCAD_POINT{ 16, 0 } ) );
391}
392
393
394BOOST_AUTO_TEST_CASE( OleMetafilePreviewExtraction )
395{
396 std::vector<uint8_t> presentation( 4096, 0 );
397 writeLe32( presentation, 4, 14 );
398 presentation[40] = 1;
399 presentation[41] = 0;
400 presentation[42] = 9;
401 presentation[43] = 0;
402
403 std::vector<uint16_t> name = { 2, 'O', 'l', 'e', 'P', 'r', 'e', 's', '0', '0', '0', 0 };
404 ORCAD_OLE_PREVIEW preview = OrcadExtractOlePreview( makeOlePreviewCfb( name, presentation ) );
405
406 BOOST_CHECK( preview.type == ORCAD_OLE_PREVIEW_TYPE::WMF );
407 BOOST_REQUIRE_EQUAL( preview.data.size(), presentation.size() - 40 );
408 BOOST_CHECK_EQUAL( preview.data[0], 1 );
409 BOOST_CHECK_EQUAL( preview.data[2], 9 );
410}
411
412
413BOOST_AUTO_TEST_CASE( OleNativeBitmapExtraction )
414{
415 std::vector<uint8_t> native( 4096, 0 );
416 writeLe32( native, 0, 58 );
417 native[4] = 'B';
418 native[5] = 'M';
419 writeLe32( native, 6, 58 );
420
421 std::vector<uint16_t> name = { 1, 'O', 'l', 'e', '1', '0', 'N', 'a', 't', 'i', 'v', 'e', 0 };
422 ORCAD_OLE_PREVIEW preview = OrcadExtractOlePreview( makeOlePreviewCfb( name, native ) );
423
424 BOOST_CHECK( preview.type == ORCAD_OLE_PREVIEW_TYPE::BMP );
425 BOOST_REQUIRE_EQUAL( preview.data.size(), native.size() - 4 );
426 BOOST_CHECK_EQUAL( preview.data[0], 'B' );
427 BOOST_CHECK_EQUAL( preview.data[1], 'M' );
428}
429
430
431BOOST_AUTO_TEST_CASE( LegacyPageNetGroups )
432{
433 std::vector<uint8_t> bytes = { ORCAD_ST_PAGE, 0, 0 };
434 appendLzt( bytes, "PAGE" );
435 appendLzt( bytes, "C" );
436 bytes.resize( bytes.size() + 156 );
437 appendLe16( bytes, 0 );
438 appendLe16( bytes, 0 );
439 appendLe16( bytes, 1 );
440 appendLe32( bytes, 0x12345678 );
441 appendLzt( bytes, "BUS[1:0]" );
442 appendLe16( bytes, 2 );
443 appendLe32( bytes, 0x11111111 );
444 appendLe32( bytes, 0x22222222 );
445
446 for( int i = 0; i < 10; ++i )
447 appendLe16( bytes, 0 );
448
449 std::vector<char> data( bytes.begin(), bytes.end() );
450 ORCAD_RAW_PAGE page = OrcadParsePageV2( data, {},
451 []( const wxString& )
452 {
453 } );
454
455 BOOST_REQUIRE_EQUAL( page.netGroups.size(), 1u );
456 BOOST_CHECK_EQUAL( page.netGroups[0].id, 0x12345678u );
457 BOOST_CHECK_EQUAL( page.netGroups[0].name, "BUS[1:0]" );
458 BOOST_REQUIRE_EQUAL( page.netGroups[0].members.size(), 2u );
459 BOOST_CHECK_EQUAL( page.netGroups[0].members[1], 0x22222222u );
460}
461
462
463// ============================================================================
464// File discrimination tests
465//
466// .dsn shared with SPECCTRA PCB text files; OrCAD Capture is OLE2/CFB (magic
467// D0 CF 11 E0...) with a "Library" stream and "Views"/"Schematics" storage.
468// Anything failing those checks rejected.
469// ============================================================================
470
471BOOST_AUTO_TEST_CASE( RejectsSpecctraTextDsn )
472{
473 TEMP_TEST_FILE specctra( wxS( "qa_orcad_specctra_impostor.dsn" ),
474 wxS( "(pcb \"impostor.dsn\"\n (parser\n (string_quote \")\n )\n)\n" ) );
475
476 BOOST_REQUIRE( wxFileName::FileExists( specctra.m_path ) );
477 BOOST_CHECK( !m_plugin.CanReadSchematicFile( specctra.m_path ) );
478}
479
480
481BOOST_AUTO_TEST_CASE( RejectsNonexistentFile )
482{
483 wxFileName missing( wxFileName::GetTempDir(), wxS( "qa_orcad_does_not_exist.dsn" ) );
484
485 BOOST_REQUIRE( !missing.FileExists() );
486 BOOST_CHECK( !m_plugin.CanReadSchematicFile( missing.GetFullPath() ) );
487}
488
489
490BOOST_AUTO_TEST_CASE( RejectsWrongExtension )
491{
492 TEMP_TEST_FILE textFile( wxS( "qa_orcad_impostor.txt" ),
493 wxS( "Just some text, not a schematic.\n" ) );
494
495 BOOST_REQUIRE( wxFileName::FileExists( textFile.m_path ) );
496 BOOST_CHECK( !m_plugin.CanReadSchematicFile( textFile.m_path ) );
497}
498
499
500// No positive-load test until a redistributable .dsn fixture exists under qa/data/eeschema/io/orcad/.
501
502
503// ============================================================================
504// Corpus validation (opt-in via KICAD_ORCAD_CORPUS)
505//
506// Sample designs not redistributable; skipped unless KICAD_ORCAD_CORPUS names a
507// tree of .DSN files. Each design imported, its placed-component refdes set
508// cross-checked against OrCAD-exported ground truth beside it: .NET (Cadstar
509// RINF, ".ADD_COM <ref>" per part) and/or .BOM (tab-separated, comma-joined
510// reference cell). Refdes coverage is the strongest available import check.
511// ============================================================================
512
513static std::string trimCell( std::string aText )
514{
515 auto notSpace = []( unsigned char c ) { return !std::isspace( c ); };
516 aText.erase( aText.begin(), std::find_if( aText.begin(), aText.end(), notSpace ) );
517 aText.erase( std::find_if( aText.rbegin(), aText.rend(), notSpace ).base(), aText.end() );
518
519 if( aText.size() >= 2 && aText.front() == '"' && aText.back() == '"' )
520 aText = aText.substr( 1, aText.size() - 2 );
521
522 return aText;
523}
524
525
526static std::vector<std::string> splitRefs( const std::string& aCell )
527{
528 std::vector<std::string> refs;
529 std::string token;
530
531 for( char c : aCell )
532 {
533 if( c == ',' )
534 {
535 std::string r = trimCell( token );
536
537 if( !r.empty() )
538 refs.push_back( r );
539
540 token.clear();
541 }
542 else
543 {
544 token += c;
545 }
546 }
547
548 std::string r = trimCell( token );
549
550 if( !r.empty() )
551 refs.push_back( r );
552
553 return refs;
554}
555
556
557static std::set<std::string> parseBomRefs( const std::string& aPath )
558{
559 std::set<std::string> refs;
560 std::ifstream in( aPath );
561 std::string line;
562 int refCol = -1;
563
564 while( std::getline( in, line ) )
565 {
566 if( !line.empty() && line.back() == '\r' )
567 line.pop_back();
568
569 std::vector<std::string> cols;
570 std::string cell;
571
572 for( char c : line )
573 {
574 if( c == '\t' ) { cols.push_back( cell ); cell.clear(); }
575 else { cell += c; }
576 }
577
578 cols.push_back( cell );
579
580 // Header row names reference column; capture index once
581 if( refCol < 0 )
582 {
583 for( size_t i = 0; i < cols.size(); ++i )
584 {
585 if( trimCell( cols[i] ) == "Reference" )
586 {
587 refCol = static_cast<int>( i );
588 break;
589 }
590 }
591
592 continue;
593 }
594
595 if( refCol < static_cast<int>( cols.size() ) )
596 {
597 for( const std::string& r : splitRefs( trimCell( cols[refCol] ) ) )
598 refs.insert( r );
599 }
600 }
601
602 return refs;
603}
604
605
606static std::set<std::string> parseNetComs( const std::string& aPath )
607{
608 std::set<std::string> refs;
609 std::ifstream in( aPath );
610 std::string line;
611
612 while( std::getline( in, line ) )
613 {
614 if( line.rfind( ".ADD_COM", 0 ) != 0 )
615 continue;
616
617 // .ADD_COM <ref> "<footprint>"
618 std::string rest = trimCell( line.substr( 8 ) );
619 std::string ref;
620
621 for( char c : rest )
622 {
623 if( std::isspace( static_cast<unsigned char>( c ) ) )
624 break;
625
626 ref += c;
627 }
628
629 if( !ref.empty() )
630 refs.insert( ref );
631 }
632
633 return refs;
634}
635
636
639static std::set<std::string> collectImportedRefs( SCHEMATIC& aSchematic )
640{
641 std::set<std::string> refs;
643
644 for( const SCH_SHEET_PATH& path : sheets )
645 {
646 SCH_SCREEN* screen = path.LastScreen();
647
648 if( !screen )
649 continue;
650
651 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
652 {
653 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
654 wxString ref = symbol->GetRef( &path, false );
655
656 // Leading '#' = power/hidden pseudo-part, not BOM; trailing '?' = unannotated.
657 if( ref.IsEmpty() || ref.StartsWith( wxS( "#" ) ) || ref.EndsWith( wxS( "?" ) ) )
658 continue;
659
660 refs.insert( std::string( ref.ToUTF8() ) );
661 }
662 }
663
664 return refs;
665}
666
667
668static std::string terminalToken( const std::string& aRef, const std::string& aPin )
669{
670 return trimCell( aRef ) + "." + trimCell( aPin );
671}
672
673
676static std::vector<std::set<std::string>> parseNetTerminals( const std::string& aPath )
677{
678 std::vector<std::set<std::string>> nets;
679 std::set<std::string> current;
680 std::ifstream in( aPath );
681 std::string line;
682
683 auto flush = [&]()
684 {
685 if( current.size() >= 2 )
686 nets.push_back( current );
687
688 current.clear();
689 };
690
691 while( std::getline( in, line ) )
692 {
693 if( !line.empty() && line.back() == '\r' )
694 line.pop_back();
695
696 bool addTer = line.rfind( ".ADD_TER", 0 ) == 0;
697 bool ter = line.rfind( ".TER", 0 ) == 0;
698 bool cont = !line.empty() && std::isspace( static_cast<unsigned char>( line[0] ) );
699
700 if( line.rfind( ".END", 0 ) == 0 )
701 break;
702
703 if( addTer )
704 flush();
705
706 if( addTer || ter || cont )
707 {
708 std::istringstream ss( addTer ? line.substr( 8 ) : ter ? line.substr( 4 ) : line );
709 std::string ref, pin;
710
711 if( ss >> ref >> pin )
712 current.insert( terminalToken( ref, pin ) );
713 }
714 }
715
716 flush();
717 return nets;
718}
719
720
723static std::pair<int, int> checkConnectivity( SCHEMATIC& aSchematic, const std::vector<std::set<std::string>>& aNets,
724 std::vector<std::set<std::string>>* aInconsistent = nullptr )
725{
727 aSchematic.ConnectionGraph()->Recalculate( sheets, true );
728
729 std::map<std::string, int> pinNet;
730 int netId = 0;
731
732 for( const auto& [key, subgraphs] : aSchematic.ConnectionGraph()->GetNetMap() )
733 {
734 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
735 {
736 for( SCH_ITEM* item : subgraph->GetItems() )
737 {
738 if( item->Type() != SCH_PIN_T )
739 continue;
740
741 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
742 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
743
744 if( !symbol )
745 continue;
746
747 wxString ref = symbol->GetRef( &subgraph->GetSheet(), false );
748
749 if( ref.IsEmpty() || ref.StartsWith( wxS( "#" ) ) || ref.EndsWith( wxS( "?" ) ) )
750 continue;
751
752 pinNet[terminalToken( std::string( ref.ToUTF8() ),
753 std::string( pin->GetNumber().ToUTF8() ) )] = netId;
754 }
755 }
756
757 ++netId;
758 }
759
760 struct CHECKED_NET
761 {
762 const std::set<std::string>* terminals;
763 std::set<int> ids;
764 };
765
766 std::vector<CHECKED_NET> checkedNets;
767 std::map<int, int> sourceNetsPerImportedNet;
768
769 for( const std::set<std::string>& net : aNets )
770 {
771 std::set<int> ids;
772 int resolved = 0;
773
774 for( const std::string& term : net )
775 {
776 auto it = pinNet.find( term );
777
778 if( it != pinNet.end() )
779 {
780 ids.insert( it->second );
781 ++resolved;
782 }
783 }
784
785 if( resolved >= 2 )
786 {
787 if( ids.size() == 1 )
788 sourceNetsPerImportedNet[*ids.begin()]++;
789
790 checkedNets.push_back( { &net, std::move( ids ) } );
791 }
792 }
793
794 int consistent = 0;
795
796 for( const CHECKED_NET& net : checkedNets )
797 {
798 bool exact = net.ids.size() == 1 && sourceNetsPerImportedNet[*net.ids.begin()] == 1;
799
800 if( exact )
801 ++consistent;
802 else if( aInconsistent )
803 aInconsistent->push_back( *net.terminals );
804 }
805
806 return { consistent, static_cast<int>( checkedNets.size() ) };
807}
808
809
811static std::set<std::string> expectedRefsFor( const std::filesystem::path& aDsn,
812 std::string& aSource )
813{
814 for( const char* ext : { ".NET", ".net", ".BOM", ".bom" } )
815 {
816 std::filesystem::path candidate = aDsn;
817 candidate.replace_extension( ext );
818
819 if( std::filesystem::exists( candidate ) )
820 {
821 aSource = candidate.filename().string();
822
823 bool isNet = std::string( ext ) == ".NET" || std::string( ext ) == ".net";
824 return isNet ? parseNetComs( candidate.string() )
825 : parseBomRefs( candidate.string() );
826 }
827 }
828
829 aSource.clear();
830 return {};
831}
832
833
834BOOST_AUTO_TEST_CASE( CorpusValidation )
835{
836 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
837
838 if( !corpusEnv || !*corpusEnv )
839 {
840 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping OrCAD corpus validation." );
841 return;
842 }
843
844 namespace fs = std::filesystem;
845 fs::path root( corpusEnv );
846
847 BOOST_REQUIRE_MESSAGE( fs::exists( root ), "KICAD_ORCAD_CORPUS path does not exist." );
848
849 std::vector<fs::path> designs;
850
851 for( auto it = fs::recursive_directory_iterator( root,
852 fs::directory_options::skip_permission_denied );
853 it != fs::recursive_directory_iterator(); ++it )
854 {
855 if( !it->is_regular_file() )
856 continue;
857
858 std::string ext = it->path().extension().string();
859 std::transform( ext.begin(), ext.end(), ext.begin(),
860 []( unsigned char c ) { return std::tolower( c ); } );
861
862 if( ext == ".dsn" )
863 designs.push_back( it->path() );
864 }
865
866 std::sort( designs.begin(), designs.end() );
867
868 BOOST_TEST_MESSAGE( "OrCAD corpus: " << designs.size() << " .DSN files under " << root );
869
870 int imported = 0, crashed = 0, unsupported = 0, rejected = 0, checked = 0;
871 unsigned int totalExpected = 0, totalMatched = 0, totalMissing = 0, totalExtra = 0;
872 int netConsistent = 0, netCheckable = 0, netTotal = 0;
873 uint64_t importedPages = 0, importedComponents = 0, importedPowerSymbols = 0;
874 uint64_t importedPins = 0, importedWires = 0, importedBuses = 0;
875 uint64_t importedLabels = 0, importedShapes = 0, importedTexts = 0, importedBitmaps = 0;
876
877 const char* debugEnv = std::getenv( "KICAD_ORCAD_DEBUG" );
878 std::string debugFilter = debugEnv ? debugEnv : "";
879 const char* filterEnv = std::getenv( "KICAD_ORCAD_FILTER" );
880 std::string designFilter = filterEnv ? filterEnv : "";
881
882 for( const fs::path& dsn : designs )
883 {
884 std::string rel = fs::relative( dsn, root ).string();
885
886 if( !designFilter.empty() && rel.find( designFilter ) == std::string::npos )
887 continue;
888
889 SCH_IO_ORCAD plugin;
890 uint64_t perDesignPages = 0, perDesignComponents = 0, perDesignPowerSymbols = 0;
891 uint64_t perDesignPins = 0, perDesignWires = 0, perDesignBuses = 0;
892 uint64_t perDesignLabels = 0, perDesignShapes = 0, perDesignTexts = 0;
893 uint64_t perDesignBitmaps = 0;
894 uint64_t perDesignRedWires = 0;
895 uint64_t perDesignVddmPowerSymbols = 0;
896
897 bool debug = !debugFilter.empty() && rel.find( debugFilter ) != std::string::npos;
898
899 if( !plugin.CanReadSchematicFile( dsn.string() ) )
900 {
901 ++rejected;
902 continue;
903 }
904
905 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
906 SETTINGS_MANAGER manager;
907 manager.LoadProject( "" );
908 schematic->SetProject( &manager.Prj() );
909 schematic->CurrentSheet().clear();
910 schematic->CurrentSheet().push_back( &schematic->Root() );
911
913 plugin.SetReporter( &reporter );
914
915 try
916 {
917 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
918 schematic->CurrentSheet().UpdateAllScreenReferences();
919 }
920 catch( const std::exception& e )
921 {
922 // Pre-2003 designs out of scope, rejected cleanly
923 if( std::string( e.what() ).find( "pre-2003" ) != std::string::npos )
924 ++unsupported;
925 else
926 ++crashed;
927
928 BOOST_TEST_MESSAGE( " THROW " << rel << " : " << e.what() );
929 continue;
930 }
931
932 ++imported;
933
934 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
935 {
936 SCH_SCREEN* screen = path.LastScreen();
937
938 if( !screen )
939 continue;
940
941 ++importedPages;
942 ++perDesignPages;
943
944 for( SCH_ITEM* item : screen->Items() )
945 {
946 switch( item->Type() )
947 {
948 case SCH_SYMBOL_T:
949 {
950 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
951 wxString ref = symbol->GetRef( &path, false );
952
953 if( ref.StartsWith( wxS( "#" ) ) )
954 {
955 ++importedPowerSymbols;
956 ++perDesignPowerSymbols;
957
958 if( symbol->GetValue( false, &path, false ) == wxS( "VDDM" ) )
959 ++perDesignVddmPowerSymbols;
960 }
961 else
962 {
963 ++importedComponents;
964 ++perDesignComponents;
965 }
966
967 size_t pinCount = symbol->GetPins( &path ).size();
968 importedPins += pinCount;
969 perDesignPins += pinCount;
970 break;
971 }
972
973 case SCH_LINE_T:
974 {
975 SCH_LINE* line = static_cast<SCH_LINE*>( item );
976
977 if( line->GetLayer() == LAYER_BUS )
978 {
979 ++importedBuses;
980 ++perDesignBuses;
981 }
982 else if( line->GetLayer() == LAYER_WIRE )
983 {
984 ++importedWires;
985 ++perDesignWires;
986
987 if( line->GetLineColor() == OrcadColor( 8 ) )
988 ++perDesignRedWires;
989 }
990
991 break;
992 }
993
994 case SCH_LABEL_T:
996 case SCH_HIER_LABEL_T:
997 ++importedLabels;
998 ++perDesignLabels;
999 break;
1000
1001 case SCH_SHAPE_T:
1002 ++importedShapes;
1003 ++perDesignShapes;
1004 break;
1005
1006 case SCH_TEXT_T:
1007 ++importedTexts;
1008 ++perDesignTexts;
1009 break;
1010
1011 case SCH_BITMAP_T:
1012 ++importedBitmaps;
1013 ++perDesignBitmaps;
1014 break;
1015
1016 default: break;
1017 }
1018 }
1019 }
1020
1021 BOOST_TEST_MESSAGE( " AUDIT " << rel << "|pages=" << perDesignPages << "|components=" << perDesignComponents
1022 << "|power=" << perDesignPowerSymbols << "|pins=" << perDesignPins
1023 << "|wires=" << perDesignWires << "|buses=" << perDesignBuses
1024 << "|labels=" << perDesignLabels << "|shapes=" << perDesignShapes
1025 << "|texts=" << perDesignTexts << "|bitmaps=" << perDesignBitmaps );
1026
1027 if( rel == "allegro/beagleboard-xm/SCH/BeagleBoard-xM_ORCAD.DSN" )
1028 {
1029 BOOST_CHECK_EQUAL( perDesignBitmaps, 10u );
1030 BOOST_CHECK_EQUAL( perDesignShapes, 108u );
1031 BOOST_CHECK_EQUAL( perDesignTexts, 175u );
1032 }
1033
1034 if( rel
1035 == "allegro/OpenCellular-LED/Rev-C/schematic/"
1036 "OpenCellular_Connect-1_LED_Life-3_Schematic.DSN" )
1037 {
1038 BOOST_CHECK_EQUAL( perDesignVddmPowerSymbols, 31u );
1039 }
1040
1041 if( rel
1042 == "orcad/OpenCellular-GBC-Elgon_ARM/Rev-A/schematics/"
1043 "CN81XX_GBCV2_sch_0530.DSN" )
1044 {
1045 BOOST_CHECK_EQUAL( perDesignRedWires, 35u );
1046 }
1047
1048 if( debug )
1049 BOOST_TEST_MESSAGE( " DEBUG " << rel << " warnings:\n"
1050 << std::string( reporter.GetMessages().ToUTF8() ) );
1051
1052 std::set<std::string> got = collectImportedRefs( *schematic );
1053 std::string source;
1054 std::set<std::string> expected = expectedRefsFor( dsn, source );
1055
1056 if( debug )
1057 BOOST_TEST_MESSAGE( " DEBUG " << rel << " imported " << got.size() << " refs" );
1058
1059 if( expected.empty() )
1060 continue;
1061
1062 std::set<std::string> missing, extra;
1063 std::set_difference( expected.begin(), expected.end(), got.begin(), got.end(),
1064 std::inserter( missing, missing.begin() ) );
1065 std::set_difference( got.begin(), got.end(), expected.begin(), expected.end(),
1066 std::inserter( extra, extra.begin() ) );
1067
1068 unsigned int matched = static_cast<unsigned int>( expected.size() - missing.size() );
1069
1070 ++checked;
1071 totalExpected += expected.size();
1072 totalMatched += matched;
1073 totalMissing += missing.size();
1074 totalExtra += extra.size();
1075
1076 BOOST_TEST_MESSAGE( " CHECK " << rel << " : " << matched << "/" << expected.size() << " refs ("
1077 << int( 100.0 * matched / expected.size() ) << "%), extra " << extra.size()
1078 << " [" << source << "]" );
1079
1080 if( debug )
1081 {
1082 for( const std::string& ref : missing )
1083 BOOST_TEST_MESSAGE( " missing ref: " << ref );
1084
1085 for( const std::string& ref : extra )
1086 BOOST_TEST_MESSAGE( " extra ref: " << ref );
1087 }
1088
1089 // .NET ground truth carries terminal connectivity; verify pins group per net after rebuild.
1090 std::filesystem::path net = dsn;
1091 net.replace_extension( source.size() >= 4 && source.substr( source.size() - 4 ) == ".net"
1092 ? ".net"
1093 : ".NET" );
1094
1095 if( std::filesystem::exists( net ) )
1096 {
1097 std::vector<std::set<std::string>> nets = parseNetTerminals( net.string() );
1098
1099 if( !nets.empty() )
1100 {
1101 std::vector<std::set<std::string>> inconsistent;
1102 auto [consistent, checkableNets] = checkConnectivity( *schematic, nets, &inconsistent );
1103 netConsistent += consistent;
1104 netCheckable += checkableNets;
1105 netTotal += static_cast<int>( nets.size() );
1106
1107 BOOST_TEST_MESSAGE( " connectivity: " << consistent << "/" << checkableNets
1108 << " nets consistent" );
1109
1110 if( debug )
1111 {
1112 for( const std::set<std::string>& terminals : inconsistent )
1113 {
1114 std::string joined;
1115
1116 for( const std::string& terminal : terminals )
1117 {
1118 if( !joined.empty() )
1119 joined += ", ";
1120
1121 joined += terminal;
1122 }
1123
1124 BOOST_TEST_MESSAGE( " inconsistent net: " << joined );
1125 }
1126 }
1127 }
1128 }
1129 }
1130
1131 BOOST_TEST_MESSAGE( "==== OrCAD corpus summary ====" );
1132 BOOST_TEST_MESSAGE( " designs: " << designs.size() << " imported: " << imported << " crashed: " << crashed
1133 << " unsupported: " << unsupported << " rejected: " << rejected );
1134 BOOST_TEST_MESSAGE( " objects: pages "
1135 << importedPages << " components " << importedComponents << " power " << importedPowerSymbols
1136 << " pins " << importedPins << " wires " << importedWires << " buses " << importedBuses
1137 << " labels " << importedLabels << " shapes " << importedShapes << " texts " << importedTexts
1138 << " bitmaps " << importedBitmaps );
1139
1140 if( checked )
1141 {
1142 BOOST_TEST_MESSAGE( " refdes coverage: " << totalMatched << "/" << totalExpected << " ("
1143 << int( 100.0 * totalMatched / totalExpected )
1144 << "%) missing: " << totalMissing
1145 << " extra: " << totalExtra );
1146 }
1147
1148 if( netCheckable )
1149 {
1150 BOOST_TEST_MESSAGE( " net connectivity: " << netConsistent << "/" << netCheckable << " ("
1151 << int( 100.0 * netConsistent / netCheckable )
1152 << "%)" );
1153 }
1154
1155 // Only pre-2003 format may throw; anything else is a crash
1156 BOOST_CHECK_MESSAGE( crashed == 0, crashed << " design(s) crashed during import." );
1157
1158 const char* snapshotEnv = std::getenv( "KICAD_ORCAD_CORPUS_SNAPSHOT" );
1159
1160 // Guard against vacuous pass when no companion files present.
1161 if( designFilter.empty() )
1162 BOOST_REQUIRE_MESSAGE( checked > 0, "No ground-truth .BOM/.NET companions were validated." );
1163
1164 if( designFilter.empty() && snapshotEnv && *snapshotEnv )
1165 {
1166 BOOST_CHECK_EQUAL( imported, 92 );
1167 BOOST_CHECK_EQUAL( rejected, 1 );
1168 BOOST_CHECK_EQUAL( importedPages, 854u );
1169 BOOST_CHECK_EQUAL( importedBitmaps, 616u );
1170 }
1171
1172 // Occurrence-annotation decode holds this above 95%; dropped Hierarchy-stream ref overlay collapses it.
1173 if( checked )
1174 BOOST_CHECK_GE( 100.0 * totalMatched / totalExpected, 95.0 );
1175
1176 // Pin-placement/geometry regression breaking connectivity collapses this. Checkable floor
1177 // (>= 2 resolvable terminals per net) stops broad pin loss passing vacuously.
1178 if( netTotal )
1179 {
1180 BOOST_CHECK_GE( 100.0 * netCheckable / netTotal, 80.0 );
1181 BOOST_CHECK_EQUAL( netConsistent, netCheckable );
1182 }
1183}
1184
1185
1186static std::filesystem::path findCorpusDesign( const std::filesystem::path& aRoot,
1187 const std::string& aFileName )
1188{
1189 for( const std::filesystem::directory_entry& entry :
1190 std::filesystem::recursive_directory_iterator( aRoot ) )
1191 {
1192 if( entry.is_regular_file() && entry.path().filename() == aFileName )
1193 return entry.path();
1194 }
1195
1196 return {};
1197}
1198
1199
1200BOOST_AUTO_TEST_CASE( Issue25005Hierarchy )
1201{
1202 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
1203
1204 if( !corpusEnv || !*corpusEnv )
1205 {
1206 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping issue 25005." );
1207 return;
1208 }
1209
1210 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CFW-002.DSN" );
1211
1212 if( dsn.empty() )
1213 {
1214 BOOST_TEST_MESSAGE( "CFW-002.DSN not present in corpus; skipping issue 25005." );
1215 return;
1216 }
1217
1218 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
1219 SETTINGS_MANAGER manager;
1220 manager.LoadProject( "" );
1221 schematic->SetProject( &manager.Prj() );
1222 schematic->CurrentSheet().clear();
1223 schematic->CurrentSheet().push_back( &schematic->Root() );
1224
1225 SCH_IO_ORCAD plugin;
1226 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
1227
1228 std::vector<SCH_SHEET*> topSheets = schematic->GetTopLevelSheets();
1229 BOOST_REQUIRE_EQUAL( topSheets.size(), 1u );
1230
1231 SCH_SCREEN* rootScreen = topSheets.front()->GetScreen();
1232 size_t sheets = 0;
1233 size_t sheetPins = 0;
1234
1235 for( SCH_ITEM* item : rootScreen->Items().OfType( SCH_SHEET_T ) )
1236 {
1237 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
1238 ++sheets;
1239 sheetPins += sheet->GetPins().size();
1240 }
1241
1242 const std::vector<wxString> expectedNames = { wxS( "PAG_2" ), wxS( "PAG_3" ), wxS( "PAG_4" ),
1243 wxS( "PAG_5" ), wxS( "PAG_6" ), wxS( "PAG_7" ),
1244 wxS( "PAG_8" ), wxS( "PAG_9" ) };
1245 const std::vector<size_t> expectedPinCounts = { 31, 24, 39, 47, 40, 33, 26, 10 };
1246 SCH_SHEET_LIST hierarchy = schematic->BuildSheetListSortedByPageNumbers();
1247 std::vector<wxString> sheetNames;
1248 std::vector<size_t> pinCounts;
1249
1250 for( auto it = std::next( hierarchy.begin() ); it != hierarchy.end(); ++it )
1251 {
1252 SCH_SHEET* sheet = it->Last();
1253 std::set<wxString> sheetPinNames;
1254 std::set<wxString> hierarchicalLabelNames;
1255
1256 sheetNames.push_back( sheet->GetField( FIELD_T::SHEET_NAME )->GetText() );
1257 pinCounts.push_back( sheet->GetPins().size() );
1258
1259 for( const SCH_SHEET_PIN* pin : sheet->GetPins() )
1260 sheetPinNames.insert( pin->GetText() );
1261
1262 for( SCH_ITEM* item : sheet->GetScreen()->Items().OfType( SCH_HIER_LABEL_T ) )
1263 hierarchicalLabelNames.insert( static_cast<SCH_HIERLABEL*>( item )->GetText() );
1264
1265 BOOST_CHECK_EQUAL_COLLECTIONS( sheetPinNames.begin(), sheetPinNames.end(),
1266 hierarchicalLabelNames.begin(), hierarchicalLabelNames.end() );
1267 }
1268
1269 schematic->ConnectionGraph()->Recalculate( hierarchy, true );
1270
1271 BOOST_CHECK_EQUAL( hierarchy.size(), 9u );
1272 BOOST_CHECK_EQUAL( sheets, 8u );
1273 BOOST_CHECK_EQUAL( sheetPins, 250u );
1274 BOOST_CHECK_EQUAL_COLLECTIONS( sheetNames.begin(), sheetNames.end(), expectedNames.begin(),
1275 expectedNames.end() );
1276 BOOST_CHECK_EQUAL_COLLECTIONS( pinCounts.begin(), pinCounts.end(), expectedPinCounts.begin(),
1277 expectedPinCounts.end() );
1278}
1279
1280
1281BOOST_AUTO_TEST_CASE( Issue25009PageOrderAndGraphics )
1282{
1283 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
1284
1285 if( !corpusEnv || !*corpusEnv )
1286 {
1287 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping issue 25009." );
1288 return;
1289 }
1290
1291 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SE_NGFOC-L_01.DSN" );
1292
1293 if( dsn.empty() )
1294 {
1295 BOOST_TEST_MESSAGE( "SE_NGFOC-L_01.DSN not present in corpus; skipping issue 25009." );
1296 return;
1297 }
1298
1299 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
1300 SETTINGS_MANAGER manager;
1301 manager.LoadProject( "" );
1302 schematic->SetProject( &manager.Prj() );
1303 schematic->CurrentSheet().clear();
1304 schematic->CurrentSheet().push_back( &schematic->Root() );
1305
1306 SCH_IO_ORCAD plugin;
1307 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
1308
1309 const std::vector<wxString> expectedNames = {
1310 wxS( "01.REV.HISTORY" ), wxS( "02.uC" ), wxS( "03.CAN" ),
1311 wxS( "04. Ethercat" ), wxS( "05.EtherSynch" ), wxS( "06.RS-485" ),
1312 wxS( "11.GPIO" ), wxS( "12.Analog" ), wxS( "13:IMU" ),
1313 wxS( "14.Bridge" ), wxS( "15.Encoder" ), wxS( "29.uCPower" ),
1314 wxS( "30.PowerSupply" ), wxS( "31.Expansion" )
1315 };
1316
1317 std::vector<SCH_SHEET*> sheets = schematic->GetTopLevelSheets();
1318 BOOST_REQUIRE_EQUAL( sheets.size(), expectedNames.size() );
1319
1320 size_t wires = 0;
1321 size_t shapes = 0;
1322 size_t texts = 0;
1323 size_t tables = 0;
1324
1325 for( size_t i = 0; i < sheets.size(); ++i )
1326 {
1327 BOOST_CHECK_EQUAL( sheets[i]->GetField( FIELD_T::SHEET_NAME )->GetText(), expectedNames[i] );
1328
1329 for( SCH_ITEM* item : sheets[i]->GetScreen()->Items() )
1330 {
1331 if( item->Type() == SCH_LINE_T )
1332 {
1333 SCH_LINE* line = static_cast<SCH_LINE*>( item );
1334 BOOST_CHECK_EQUAL( line->GetLineWidth(), 0 );
1335 ++wires;
1336 }
1337 else if( item->Type() == SCH_SHAPE_T )
1338 {
1339 SCH_SHAPE* shape = static_cast<SCH_SHAPE*>( item );
1340
1341 if( i == 0 )
1342 BOOST_CHECK( shape->GetFillMode() == FILL_T::NO_FILL );
1343
1344 ++shapes;
1345 }
1346 else if( item->Type() == SCH_TEXT_T )
1347 {
1348 ++texts;
1349 }
1350 else if( item->Type() == SCH_TABLE_T )
1351 {
1352 ++tables;
1353 }
1354 }
1355 }
1356
1357 BOOST_CHECK_EQUAL( wires, 1921u );
1358 BOOST_CHECK_EQUAL( shapes, 168u );
1359 BOOST_CHECK_EQUAL( texts, 206u );
1360 BOOST_CHECK_EQUAL( tables, 0u );
1361}
1362
1363
1364// ============================================================================
1365// OLB symbol-library import (opt-in via KICAD_ORCAD_CORPUS)
1366//
1367// Each .OLB must enumerate symbols carrying pins or graphics; empty symbol means
1368// cache decode silently produced nothing.
1369// ============================================================================
1370
1371BOOST_AUTO_TEST_CASE( OlbLibraryImport )
1372{
1373 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
1374
1375 if( !corpusEnv || !*corpusEnv )
1376 {
1377 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping OrCAD OLB library import." );
1378 return;
1379 }
1380
1381 namespace fs = std::filesystem;
1382 std::vector<fs::path> libs;
1383
1384 for( auto it = fs::recursive_directory_iterator( fs::path( corpusEnv ),
1385 fs::directory_options::skip_permission_denied );
1386 it != fs::recursive_directory_iterator(); ++it )
1387 {
1388 if( !it->is_regular_file() )
1389 continue;
1390
1391 std::string ext = it->path().extension().string();
1392 std::transform( ext.begin(), ext.end(), ext.begin(),
1393 []( unsigned char c ) { return std::tolower( c ); } );
1394
1395 if( ext == ".olb" )
1396 libs.push_back( it->path() );
1397 }
1398
1399 std::sort( libs.begin(), libs.end() );
1400 BOOST_TEST_MESSAGE( "OrCAD OLB libraries: " << libs.size() );
1401
1402 int totalSymbols = 0, emptySymbols = 0, checkedLibs = 0, crashedLibs = 0;
1403
1404 for( const fs::path& lib : libs )
1405 {
1406 SCH_IO_ORCAD plugin;
1407 std::vector<LIB_SYMBOL*> symbols;
1408
1409 try
1410 {
1411 // Vector overload materializes all symbols O(n); per-name LoadSymbol rescans O(n^2).
1412 plugin.EnumerateSymbolLib( symbols, lib.string() );
1413 }
1414 catch( const std::exception& e )
1415 {
1416 ++crashedLibs;
1417 BOOST_TEST_MESSAGE( " THROW " << lib.filename().string() << " : " << e.what() );
1418 continue;
1419 }
1420
1421 ++checkedLibs;
1422 int withGeometry = 0;
1423
1424 for( LIB_SYMBOL* symbol : symbols )
1425 {
1426 BOOST_REQUIRE( symbol );
1427 ++totalSymbols;
1428
1429 if( symbol->GetPinCount() > 0 || !symbol->GetDrawItems().empty() )
1430 ++withGeometry;
1431 else
1432 ++emptySymbols;
1433 }
1434
1435 BOOST_TEST_MESSAGE( " " << lib.filename().string() << " : " << symbols.size()
1436 << " symbols, " << withGeometry << " with pins/graphics" );
1437 }
1438
1439 BOOST_TEST_MESSAGE( "OLB summary: " << checkedLibs << " libs, " << crashedLibs
1440 << " crashed, " << totalSymbols << " symbols, "
1441 << emptySymbols << " empty" );
1442
1443 // Bad streams must degrade gracefully, not throw; wholesale empty result means decode broke.
1444 BOOST_CHECK_EQUAL( crashedLibs, 0 );
1445 BOOST_CHECK_GT( totalSymbols, 0 );
1446
1447 if( totalSymbols )
1448 BOOST_CHECK_LT( emptySymbols, totalSymbols / 2 );
1449}
1450
1451
1452// CutiePi (3 pages) imports as three sibling top-level sheets; off-page connectors keep own
1453// names; reference/value fields honor source display positions.
1454BOOST_AUTO_TEST_CASE( MultiPageFlatImport )
1455{
1456 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
1457
1458 if( !corpusEnv || !*corpusEnv )
1459 {
1460 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping OrCAD multi-page import." );
1461 return;
1462 }
1463
1464 namespace fs = std::filesystem;
1465 fs::path dsn = fs::path( corpusEnv ) / "cutiepi-board" / "CutiePi_V2.3-20210409.DSN";
1466
1467 if( !fs::exists( dsn ) )
1468 {
1469 BOOST_TEST_MESSAGE( "CutiePi design not present in corpus; skipping." );
1470 return;
1471 }
1472
1473 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
1474 SETTINGS_MANAGER manager;
1475 manager.LoadProject( "" );
1476 schematic->SetProject( &manager.Prj() );
1477 schematic->CurrentSheet().clear();
1478 schematic->CurrentSheet().push_back( &schematic->Root() );
1479
1480 SCH_IO_ORCAD plugin;
1481 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
1482
1483 // Pages become flat ordered top-level sheets, not a stitching root w/ children; "N - " prefix orders them.
1484 std::vector<SCH_SHEET*> tops = schematic->GetTopLevelSheets();
1485 BOOST_REQUIRE_EQUAL( tops.size(), 3u );
1486
1487 BOOST_CHECK_EQUAL( tops[0]->GetField( FIELD_T::SHEET_NAME )->GetText(), wxS( "CONTENTS" ) );
1488 BOOST_CHECK_EQUAL( tops[1]->GetField( FIELD_T::SHEET_NAME )->GetText(),
1489 wxS( "CM4,USB HUB,AUDIO,MIC" ) );
1490 BOOST_CHECK_EQUAL( tops[2]->GetField( FIELD_T::SHEET_NAME )->GetText(),
1491 wxS( "CSI, DSI, HDMI, MCU" ) );
1492
1493 std::set<wxString> globalLabels;
1494
1495 for( SCH_SHEET* top : tops )
1496 {
1497 for( SCH_ITEM* item : top->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
1498 globalLabels.insert( static_cast<SCH_LABEL_BASE*>( item )->GetText() );
1499 }
1500
1501 // Off-page connectors carry own name (CAM0_IO1), not the local wire net (GPIO19) they sit on.
1502 BOOST_CHECK( globalLabels.count( wxS( "CAM0_IO1" ) ) );
1503 BOOST_CHECK( globalLabels.count( wxS( "AMP_SHUTDOWN" ) ) );
1504
1505 // R3197 reference honors OrCAD display position (left of body), not computed fallback (right).
1506 bool checkedField = false;
1507
1508 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
1509 {
1510 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
1511 {
1512 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
1513
1514 if( sym->GetRef( &path, false ) == wxS( "R3197" ) )
1515 {
1516 BOOST_CHECK_LT( sym->GetField( FIELD_T::REFERENCE )->GetPosition().x,
1517 sym->GetPosition().x );
1518 checkedField = true;
1519 }
1520 }
1521 }
1522
1523 BOOST_CHECK( checkedField );
1524}
1525
1526
1527// CutiePi component fidelity: pin number/name visibility, off-page label orientation, hidden
1528// fields, no-connect markers.
1529BOOST_AUTO_TEST_CASE( ComponentDetailImport )
1530{
1531 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
1532
1533 if( !corpusEnv || !*corpusEnv )
1534 {
1535 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping OrCAD component detail." );
1536 return;
1537 }
1538
1539 namespace fs = std::filesystem;
1540 fs::path dsn = fs::path( corpusEnv ) / "cutiepi-board" / "CutiePi_V2.3-20210409.DSN";
1541
1542 if( !fs::exists( dsn ) )
1543 {
1544 BOOST_TEST_MESSAGE( "CutiePi design not present in corpus; skipping." );
1545 return;
1546 }
1547
1548 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
1549 SETTINGS_MANAGER manager;
1550 manager.LoadProject( "" );
1551 schematic->SetProject( &manager.Prj() );
1552 schematic->CurrentSheet().clear();
1553 schematic->CurrentSheet().push_back( &schematic->Root() );
1554
1555 SCH_IO_ORCAD plugin;
1556 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
1557
1558 std::map<wxString, SCH_SYMBOL*> symbols;
1559 std::multimap<wxString, int> labelSpins;
1560 int noConnects = 0;
1561
1562 for( SCH_SHEET* top : schematic->GetTopLevelSheets() )
1563 {
1565 path.push_back( top );
1566
1567 for( SCH_ITEM* item : top->GetScreen()->Items() )
1568 {
1569 if( item->Type() == SCH_SYMBOL_T )
1570 {
1571 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
1572 symbols[sym->GetRef( &path, false )] = sym;
1573 }
1574 else if( item->Type() == SCH_GLOBAL_LABEL_T )
1575 {
1576 SCH_LABEL_BASE* lbl = static_cast<SCH_LABEL_BASE*>( item );
1577 labelSpins.emplace( lbl->GetText(), (int) lbl->GetSpinStyle() );
1578 }
1579 else if( item->Type() == SCH_NO_CONNECT_T )
1580 {
1581 ++noConnects;
1582 }
1583 }
1584 }
1585
1586 // Pin numbers/names show on ICs (flags 0x3), hide on passives (0x6)
1587 BOOST_REQUIRE( symbols.count( wxS( "U3" ) ) );
1588 BOOST_CHECK( symbols[wxS( "U3" )]->GetShowPinNumbers() );
1589 BOOST_CHECK( symbols[wxS( "U3" )]->GetShowPinNames() );
1590 BOOST_REQUIRE( symbols.count( wxS( "R3174" ) ) );
1591 BOOST_CHECK( !symbols[wxS( "R3174" )]->GetShowPinNumbers() );
1592 BOOST_CHECK( !symbols[wxS( "R3174" )]->GetShowPinNames() );
1593
1594 // Ferrite bead value hidden, reference visible
1595 BOOST_REQUIRE( symbols.count( wxS( "FB8" ) ) );
1596 BOOST_CHECK( !symbols[wxS( "FB8" )]->GetField( FIELD_T::VALUE )->IsVisible() );
1597 BOOST_CHECK( symbols[wxS( "FB8" )]->GetField( FIELD_T::REFERENCE )->IsVisible() );
1598
1599 // Display-prop field positions are canvas-space (anchor + offset), not through body-orientation
1600 // transform. FB8 (90-deg ferrite) reference lands right of origin; rotation transform would flip left.
1601 SCH_FIELD* fb8Ref = symbols[wxS( "FB8" )]->GetField( FIELD_T::REFERENCE );
1602 BOOST_CHECK_GT( fb8Ref->GetPosition().x, symbols[wxS( "FB8" )]->GetPosition().x );
1603 BOOST_CHECK( fb8Ref->GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT );
1604
1605 // FB8 stored angle compensates for KiCad re-rotating fields on 90-deg symbol, so text stays horizontal.
1606 BOOST_CHECK( fb8Ref->GetDrawRotation() == ANGLE_HORIZONTAL );
1607
1608 // References render horizontal even on rotated symbols (ferrites, vertical R/C).
1609 for( const wxString& ref : { wxS( "R3186" ), wxS( "C2517" ), wxS( "R3189" ),
1610 wxS( "FB13" ), wxS( "FB9" ) } )
1611 {
1612 BOOST_REQUIRE_MESSAGE( symbols.count( ref ), ref );
1613 BOOST_CHECK( symbols[ref]->GetField( FIELD_T::REFERENCE )->GetDrawRotation()
1614 == ANGLE_HORIZONTAL );
1615 }
1616
1617 // Value rotation is per-field from source: FB9 part number horizontal, C2517 "47pF" stays vertical.
1618 BOOST_CHECK( symbols[wxS( "FB9" )]->GetField( FIELD_T::VALUE )->GetDrawRotation()
1619 == ANGLE_HORIZONTAL );
1620 BOOST_CHECK( symbols[wxS( "C2517" )]->GetField( FIELD_T::VALUE )->GetDrawRotation()
1621 == ANGLE_VERTICAL );
1622
1623 // Power net names read horizontal even on rotated power symbols (REG1V8/REG3V3).
1624 bool checkedPower = false;
1625
1626 for( SCH_SHEET* top : schematic->GetTopLevelSheets() )
1627 {
1628 for( SCH_ITEM* item : top->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
1629 {
1630 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
1631 wxString val = sym->GetField( FIELD_T::VALUE )->GetText();
1632
1633 if( val == wxS( "REG1V8" ) || val == wxS( "REG3V3" ) )
1634 {
1635 BOOST_CHECK( sym->GetField( FIELD_T::VALUE )->GetDrawRotation()
1636 == ANGLE_HORIZONTAL );
1637 checkedPower = true;
1638 }
1639 }
1640 }
1641
1642 BOOST_CHECK( checkedPower );
1643
1644 // Unconnected IC pins get no-connect markers (U3 15 NC + U580 NC/ORG)
1645 BOOST_CHECK_GE( noConnects, 17 );
1646
1647 // Off-page connectors on vertical wires point up/down, not left/right.
1648 auto hasSpin = [&]( const wxString& aText, SPIN_STYLE::SPIN aSpin )
1649 {
1650 auto range = labelSpins.equal_range( aText );
1651
1652 for( auto it = range.first; it != range.second; ++it )
1653 {
1654 if( it->second == (int) aSpin )
1655 return true;
1656 }
1657
1658 return false;
1659 };
1660
1661 BOOST_CHECK( hasSpin( wxS( "VOLDN" ), SPIN_STYLE::UP ) );
1662 BOOST_CHECK( hasSpin( wxS( "MUTEP" ), SPIN_STYLE::BOTTOM ) );
1663}
1664
1665
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
const NET_MAP & GetNetMap() const
void Recalculate(const SCH_SHEET_LIST &aSheetList, bool aUnconditional=false, std::function< void(SCH_ITEM *)> *aChangedItemHandler=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Update the connection graph for the given list of sheets.
A subgraph is a set of items that are electrically connected on a single sheet.
FILL_T GetFillMode() const
Definition eda_shape.h:158
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:110
GR_TEXT_H_ALIGN_T GetHorizJustify() const
Definition eda_text.h:221
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:221
virtual void SetReporter(REPORTER *aReporter)
Set an optional reporter for warnings/errors.
Definition io_base.h:89
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:398
Define a library symbol object.
Definition lib_symbol.h:114
Little-endian byte cursor over one OrCAD Capture DSN stream.
static constexpr uint8_t PREAMBLE[4]
Magic preceding every framed structure body: FF E4 5C 39.
Holds all the data relating to one schematic.
Definition schematic.h:90
SCH_SHEET_LIST BuildSheetListSortedByPageNumbers() const
CONNECTION_GRAPH * ConnectionGraph() const
Definition schematic.h:201
VECTOR2I GetPosition() const override
EDA_ANGLE GetDrawRotation() const override
Adjusters to allow EDA_TEXT to draw/print/etc.
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 OrCAD Capture schematic designs (.dsn).
bool CanReadSchematicFile(const wxString &aFileName) const override
The .dsn extension collides with SPECCTRA PCB session files (plain text), so beyond the extension gat...
SCH_SHEET * LoadSchematicFile(const wxString &aFileName, SCHEMATIC *aSchematic, SCH_SHEET *aAppendToMe=nullptr, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Orchestration: root sheet/screen boilerplate (honoring aAppendToMe), CFB open, Library parse + versio...
void EnumerateSymbolLib(wxArrayString &aSymbolNameList, const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Enumerate the symbols of an OrCAD .OLB library (an OLE2/CFB compound document with the same Library/C...
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
SPIN_STYLE GetSpinStyle() const
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:38
int GetLineWidth() const
Definition sch_line.h:194
COLOR4D GetLineColor() const
Return COLOR4D::UNSPECIFIED if a custom color hasn't been set for this line.
Definition sch_line.cpp:343
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:115
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Define a sheet pin (label) used in sheets to create hierarchical schematics.
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
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition sch_sheet.h:227
Schematic symbol object.
Definition sch_symbol.h:69
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
const wxString GetValue(bool aResolve, const SCH_SHEET_PATH *aPath, bool aAllowExtraText, const wxString &aVariantName=wxEmptyString) const override
VECTOR2I GetPosition() const override
Definition sch_symbol.h:914
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
A wrapper for reporting to a wxString object.
Definition reporter.h:225
static constexpr EDA_ANGLE ANGLE_VERTICAL
Definition eda_angle.h:408
static constexpr EDA_ANGLE ANGLE_HORIZONTAL
Definition eda_angle.h:407
@ NO_FILL
Definition eda_shape.h:60
@ REVERSE_HATCH
Definition eda_shape.h:65
@ HATCH
Definition eda_shape.h:64
@ FILLED_WITH_BG_BODYCOLOR
Definition eda_shape.h:62
@ CROSS_HATCH
Definition eda_shape.h:66
@ LAYER_WIRE
Definition layer_ids.h:458
@ LAYER_BUS
Definition layer_ids.h:459
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
std::optional< ORCAD_PRIMITIVE > OrcadReadPrimitive(ORCAD_STREAM &aStream)
Read one graphic primitive including its doubled u8 type-pair prefix, at the current cursor.
Parsers for the DSN 'Cache' stream and the 'Packages/<name>' streams: symbol definitions with graphic...
ORCAD_CONVERTER turns a parsed ORCAD_DESIGN into KiCad schematic objects.
FILL_T OrcadFillType(int aFillStyle, int aHatchStyle)
int OrcadLineWidthIu(int aWidth)
int OrcadPageOrder(wxString &aName)
Return a numeric page prefix (or -1); strip only the "N - title" convention.
KIGFX::COLOR4D OrcadColor(int aColorIndex)
LINE_STYLE OrcadLineStyle(int aStyle)
ORCAD_OLE_PREVIEW OrcadExtractOlePreview(const std::vector< uint8_t > &aPayload)
Definition orcad_ole.cpp:78
ORCAD_RAW_PAGE OrcadParsePageV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &)
Parse one v2.0 (pre-2003) 'Views/<folder>/Pages/<page>' stream.
Parsers for the per-page streams 'Views/<folder>/Pages/<page>', the per-folder page-order stream 'Vie...
@ ORCAD_PRIM_LINE
@ ORCAD_PRIM_POLYGON
@ ORCAD_PRIM_RECT
@ ORCAD_PRIM_SYMBOL_VECTOR
nested prefix-framed vector graphic
@ ORCAD_ST_PAGE
std::string OrcadNormalizeCfbName(const std::string &aName)
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
std::vector< uint8_t > data
Definition orcad_ole.h:31
ORCAD_OLE_PREVIEW_TYPE type
Definition orcad_ole.h:30
Integer point in OrCAD DBU.
One parsed 'Views/<folder>/Pages/<page>' stream, raw structure lists in stream order.
std::vector< ORCAD_NET_GROUP > netGroups
bus net id -> member net ids
SCH_SHEET * LoadOrcadSchematic(const std::string &aRelPath)
std::unique_ptr< SCHEMATIC > m_schematic
std::string dataPath(const std::string &aRelPath) const
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
IbisParser parser & reporter
KIBIS top(path, &reporter)
KIBIS_PIN * pin
VECTOR3I expected(15, 30, 45)
static std::vector< std::string > splitRefs(const std::string &aCell)
static std::filesystem::path findCorpusDesign(const std::filesystem::path &aRoot, const std::string &aFileName)
static std::set< std::string > parseBomRefs(const std::string &aPath)
static std::vector< std::set< std::string > > parseNetTerminals(const std::string &aPath)
Net terminal sets from .NET.
static std::set< std::string > expectedRefsFor(const std::filesystem::path &aDsn, std::string &aSource)
Ground-truth companion beside .DSN, .NET preferred over .BOM.
static std::string trimCell(std::string aText)
static std::set< std::string > parseNetComs(const std::string &aPath)
static std::string terminalToken(const std::string &aRef, const std::string &aPin)
static std::pair< int, int > checkConnectivity(SCHEMATIC &aSchematic, const std::vector< std::set< std::string > > &aNets, std::vector< std::set< std::string > > *aInconsistent=nullptr)
Count ground-truth nets whose resolvable terminals all land on one KiCad net after connectivity rebui...
static std::set< std::string > collectImportedRefs(SCHEMATIC &aSchematic)
Unique refdes of real (non-power) parts.
BOOST_AUTO_TEST_CASE(PrimitiveLineWidth)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
BOOST_CHECK_EQUAL(result, "25.4")
@ GR_TEXT_H_ALIGN_LEFT
@ SCH_TABLE_T
Definition typeinfo.h:162
@ 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_HIER_LABEL_T
Definition typeinfo.h:166
@ SCH_TEXT_T
Definition typeinfo.h:148
@ SCH_BITMAP_T
Definition typeinfo.h:161
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:165
@ SCH_PIN_T
Definition typeinfo.h:150