KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_io_orcad.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 * Based on the dsn2kicad reference implementation and on OrCAD file format
7 * documentation from the OpenOrCadParser project (MIT licensed).
8 *
9 * This program is free software: you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your
12 * option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
23
24
26
27#include <algorithm>
28#include <cctype>
29#include <cstdint>
30#include <map>
31#include <optional>
32#include <set>
33#include <string>
34#include <utility>
35#include <vector>
36
37#include <wx/string.h>
38#include <wx/translation.h>
39
40#include <compoundfilereader.h>
41#include <utf.h>
42
44#include <io/io_utils.h>
45#include <ki_exception.h>
46#include <kiid.h>
47#include <progress_reporter.h>
48
49#include <lib_symbol.h>
50#include <schematic.h>
51#include <sch_screen.h>
52#include <sch_sheet.h>
53#include <sch_sheet_path.h>
54
61
62
63std::string OrcadNormalizeCfbName( const std::string& aName )
64{
65 std::string name = aName;
66 std::replace( name.begin(), name.end(), '\x02', '/' );
67 std::replace( name.begin(), name.end(), '\x03', ':' );
68 return name;
69}
70
71
72namespace
73{
74
75void assignPostImportUuids( SCHEMATIC* aSchematic, const std::string& aSourceId )
76{
77 std::set<SCH_SCREEN*> screens;
78 size_t screenOrdinal = 0;
79
80 for( const SCH_SHEET_PATH& path : aSchematic->BuildSheetListSortedByPageNumbers() )
81 {
82 SCH_SCREEN* screen = path.LastScreen();
83
84 if( !screen || !screens.insert( screen ).second )
85 continue;
86
87 std::map<std::string, size_t> ordinals;
88
89 for( SCH_ITEM* item : screen->Items() )
90 {
91 std::string uuid = item->m_Uuid.AsStdString();
92
93 if( uuid.size() > 14 && uuid[14] == '5' )
94 continue;
95
96 VECTOR2I position = item->GetPosition();
97 std::string role = std::to_string( static_cast<int>( item->Type() ) ) + ":" + std::to_string( position.x )
98 + ":" + std::to_string( position.y );
99 size_t ordinal = ordinals[role]++;
100 const_cast<KIID&>( item->m_Uuid ) =
101 KIID::FromName( "orcad-import:" + aSourceId + ":post:" + std::to_string( screenOrdinal ) + ":"
102 + role + ":" + std::to_string( ordinal ) );
103 }
104
105 ++screenOrdinal;
106 }
107}
108
109std::vector<char> readStream( const ALTIUM_COMPOUND_FILE& aFile, const CFB::COMPOUND_FILE_ENTRY* aEntry )
110{
111 const CFB::CompoundFileReader& reader = aFile.GetCompoundFileReader();
112
113 // Stream cannot exceed file; corrupt entry claiming more must not drive huge allocation
114 uint64_t size = reader.GetStreamSize( aEntry );
115
116 if( size > reader.GetBufferLen() )
117 THROW_IO_ERROR( _( "OrCAD stream size exceeds the compound file" ) );
118
119 std::vector<char> data( static_cast<size_t>( size ) );
120
121 if( !data.empty() )
122 reader.ReadFile( aEntry, 0, data.data(), data.size() );
123
124 return data;
125}
126
127
128bool isLongFramedPackageStream( const std::vector<char>& aData )
129{
130 if( aData.size() < 11 )
131 return false;
132
133 auto byte = [&]( size_t aOffset )
134 {
135 return static_cast<uint8_t>( aData[aOffset] );
136 };
137 uint32_t bodyLength = byte( 3 ) | static_cast<uint32_t>( byte( 4 ) ) << 8 | static_cast<uint32_t>( byte( 5 ) ) << 16
138 | static_cast<uint32_t>( byte( 6 ) ) << 24;
139
140 return byte( 7 ) == 0 && byte( 8 ) == 0 && byte( 9 ) == 0 && byte( 10 ) == 0 && bodyLength <= aData.size() - 11;
141}
142
143
144// Direct children of storage, filtered to streams (aStreams) or sub-storages, in directory order.
145std::vector<std::pair<std::string, const CFB::COMPOUND_FILE_ENTRY*>>
146enumChildren( const ALTIUM_COMPOUND_FILE& aFile, const CFB::COMPOUND_FILE_ENTRY* aParent, bool aStreams )
147{
148 std::vector<std::pair<std::string, const CFB::COMPOUND_FILE_ENTRY*>> out;
149
150 const CFB::CompoundFileReader& reader = aFile.GetCompoundFileReader();
151
152 reader.EnumFiles( aParent, 1,
153 [&]( const CFB::COMPOUND_FILE_ENTRY* aEntry, const CFB::utf16string&, int ) -> int
154 {
155 if( reader.IsStream( aEntry ) == aStreams )
156 out.emplace_back( OrcadNormalizeCfbName( UTF16ToUTF8( aEntry->name ) ), aEntry );
157
158 return 0;
159 } );
160
161 return out;
162}
163
164
165std::string lowerCopy( const std::string& aText )
166{
167 std::string out = aText;
168
169 std::transform( out.begin(), out.end(), out.begin(),
170 []( unsigned char c )
171 {
172 return static_cast<char>( std::tolower( c ) );
173 } );
174
175 return out;
176}
177
178
179void mergeCisProperties( ORCAD_OCC_SCOPE& aScope, uint32_t aOccurrence,
180 const std::map<std::string, std::string>& aProperties, bool& aMatched )
181{
182 if( aScope.partRefs.count( aOccurrence ) || aScope.partProps.count( aOccurrence ) )
183 {
184 auto& target = aScope.partProps[aOccurrence];
185
186 for( const auto& [name, value] : aProperties )
187 target.insert_or_assign( name, value );
188
189 aMatched = true;
190 }
191
192 for( ORCAD_OCC_BLOCK& block : aScope.blocks )
193 mergeCisProperties( block.scope, aOccurrence, aProperties, aMatched );
194}
195
196
197void applyCisVariant( const ALTIUM_COMPOUND_FILE& aFile, const CFB::COMPOUND_FILE_ENTRY* aRoot,
198 const std::map<std::string, UTF8>* aProperties, ORCAD_DESIGN& aDesign, REPORTER* aReporter )
199{
200 const CFB::COMPOUND_FILE_ENTRY* cisStorage = aFile.FindStreamSingleLevel( aRoot, "CIS", false );
201
202 if( !cisStorage )
203 return;
204
205 const CFB::COMPOUND_FILE_ENTRY* variantStore = aFile.FindStreamSingleLevel( cisStorage, "VariantStore", false );
206
207 if( !variantStore )
208 return;
209
210 const CFB::COMPOUND_FILE_ENTRY* bomStorage = aFile.FindStreamSingleLevel( variantStore, "BOM", false );
211
212 if( !bomStorage )
213 return;
214
215 const CFB::COMPOUND_FILE_ENTRY* bomData = aFile.FindStreamSingleLevel( bomStorage, "BOMDataStream", true );
216
217 if( !bomData )
218 return;
219
220 std::vector<std::string> names = OrcadCisParseCountedList( readStream( aFile, bomData ), 0xF9 );
221 std::optional<std::string> requested;
222
223 if( aProperties )
224 {
225 auto request = aProperties->find( "orcad_cis_variant" );
226
227 if( request != aProperties->end() )
228 requested = request->second;
229 }
230
231 std::string selected = OrcadCisSelectVariant( names, requested );
232
233 if( selected.empty() )
234 return;
235
236 auto applyVariantName = [&]( std::vector<ORCAD_RAW_PAGE>& aPages )
237 {
238 constexpr std::string_view placeholder = "<Core Design>";
239
240 for( ORCAD_RAW_PAGE& page : aPages )
241 {
242 for( ORCAD_GRAPHIC_INST& titleBlock : page.titleBlocks )
243 {
244 for( auto& [name, value] : titleBlock.props )
245 {
246 size_t offset = 0;
247
248 while( ( offset = value.find( placeholder, offset ) ) != std::string::npos )
249 {
250 value.replace( offset, placeholder.size(), selected );
251 offset += selected.size();
252 }
253 }
254 }
255 }
256 };
257
258 applyVariantName( aDesign.pages );
259
260 for( auto& [folder, pages] : aDesign.childFolderPages )
261 applyVariantName( pages );
262
263 for( auto& [folder, pages] : aDesign.unreferencedFolderPages )
264 applyVariantName( pages );
265
266 std::map<std::string, const CFB::COMPOUND_FILE_ENTRY*> variantEntries;
267
268 for( const auto& [name, entry] : enumChildren( aFile, bomStorage, false ) )
269 variantEntries.emplace( name, entry );
270
271 auto selectedEntry = variantEntries.find( selected );
272
273 if( selectedEntry == variantEntries.end() )
274 THROW_IO_ERROR( _( "The selected OrCAD CIS variant has no definition storage." ) );
275
276 const CFB::COMPOUND_FILE_ENTRY* definition = aFile.FindStreamSingleLevel( selectedEntry->second, selected, true );
277
278 if( !definition )
279 THROW_IO_ERROR( _( "The selected OrCAD CIS variant has no definition stream." ) );
280
281 std::vector<std::string> selectedGroups = OrcadCisParseCountedList( readStream( aFile, definition ), 0xF9 );
282 const CFB::COMPOUND_FILE_ENTRY* groupsStorage = aFile.FindStreamSingleLevel( variantStore, "Groups", false );
283 std::map<std::string, const CFB::COMPOUND_FILE_ENTRY*> updateStreams;
284 std::map<std::string, std::string> schematicGroupNames;
285
286 if( groupsStorage )
287 {
288 for( const auto& [groupName, groupEntry] : enumChildren( aFile, groupsStorage, false ) )
289 {
290 schematicGroupNames.emplace( groupName, groupName );
291
292 if( const CFB::COMPOUND_FILE_ENTRY* update =
293 aFile.FindStreamSingleLevel( groupEntry, "UpdateStorageGroupDataStream", true ) )
294 {
295 updateStreams.emplace( groupName, update );
296 }
297
298 for( const auto& [subgroupName, subgroupEntry] : enumChildren( aFile, groupEntry, false ) )
299 {
300 schematicGroupNames.emplace( groupName + "_" + subgroupName, groupName + "-" + subgroupName );
301
302 if( const CFB::COMPOUND_FILE_ENTRY* update =
303 aFile.FindStreamSingleLevel( subgroupEntry, "UpdateStorageSubGroupDataStream", true ) )
304 {
305 updateStreams.emplace( groupName + "_" + subgroupName, update );
306 }
307 }
308 }
309 }
310
311 for( const std::string& groupName : selectedGroups )
312 {
313 if( groupName == "Common" || groupName == "CommonNI" )
314 continue;
315
316 auto stream = updateStreams.find( groupName );
317
318 if( stream == updateStreams.end() )
319 {
320 if( schematicGroupNames.count( groupName ) )
321 continue;
322
323 THROW_IO_ERROR( _( "The selected OrCAD CIS variant references an unknown property group." ) );
324 }
325
326 OrcadCisParsePropertyUpdates( readStream( aFile, stream->second ) );
327 }
328
329 ORCAD_CIS_SCHEMATIC_INFO schematicInfo;
330 const CFB::COMPOUND_FILE_ENTRY* viewsStorage = aFile.FindStreamSingleLevel( aRoot, "Views", false );
331
332 if( viewsStorage )
333 {
334 for( const auto& [folderName, folderEntry] : enumChildren( aFile, viewsStorage, false ) )
335 {
336 const CFB::COMPOUND_FILE_ENTRY* cisSchematic =
337 aFile.FindStreamSingleLevel( folderEntry, "CISSchematic", false );
338
339 if( !cisSchematic )
340 continue;
341
342 const CFB::COMPOUND_FILE_ENTRY* infoStorage =
343 aFile.FindStreamSingleLevel( cisSchematic, "SchematicInfoStorage", false );
344
345 if( !infoStorage )
346 continue;
347
348 for( const auto& [streamName, streamEntry] : enumChildren( aFile, infoStorage, true ) )
349 {
350 if( streamName == "SchematicInfoStream" )
351 continue;
352
353 ORCAD_CIS_SCHEMATIC_INFO pageInfo = OrcadCisParseSchematicInfo( readStream( aFile, streamEntry ) );
354
355 for( auto& [groupName, occurrences] : pageInfo )
356 {
357 auto& target = schematicInfo[groupName];
358
359 for( auto& [occurrence, properties] : occurrences )
360 target.insert_or_assign( occurrence, std::move( properties ) );
361 }
362 }
363 }
364 }
365
366 for( const std::string& selectedGroup : selectedGroups )
367 {
368 auto groupName = schematicGroupNames.find( selectedGroup );
369
370 if( groupName == schematicGroupNames.end() )
371 continue;
372
373 auto group = schematicInfo.find( groupName->second );
374
375 if( group == schematicInfo.end() )
376 continue;
377
378 for( const auto& [occurrence, properties] : group->second )
379 {
380 bool matched = false;
381 mergeCisProperties( aDesign.occurrenceRoot, occurrence, properties, matched );
382
383 if( !matched )
384 {
385 auto& target = aDesign.occurrenceRoot.partProps[occurrence];
386
387 for( const auto& [name, value] : properties )
388 target.insert_or_assign( name, value );
389 }
390 }
391 }
392
393 if( aReporter )
394 {
395 aReporter->Report( wxString::Format( _( "Using OrCAD CIS variant '%s'." ), wxString::FromUTF8( selected ) ),
397 }
398}
399
400} // namespace
401
402
403bool SCH_IO_ORCAD::CanReadSchematicFile( const wxString& aFileName ) const
404{
405 if( !SCH_IO::CanReadSchematicFile( aFileName ) )
406 return false;
407
408 // .dsn also names plain-text SPECCTRA session files; OrCAD design is OLE2/CFB compound doc
410 return false;
411
412 try
413 {
414 ALTIUM_COMPOUND_FILE cfbFile( aFileName );
415
416 const CFB::CompoundFileReader& reader = cfbFile.GetCompoundFileReader();
417 const CFB::COMPOUND_FILE_ENTRY* root = reader.GetRootEntry();
418
419 if( !root )
420 return false;
421
422 if( !cfbFile.FindStreamSingleLevel( root, "Library", true ) )
423 return false;
424
425 return cfbFile.FindStreamSingleLevel( root, "Views", false ) != nullptr
426 || cfbFile.FindStreamSingleLevel( root, "Schematics", false ) != nullptr;
427 }
428 catch( const IO_ERROR& )
429 {
430 return false;
431 }
432 catch( const CFB::CFBException& )
433 {
434 return false;
435 }
436 catch( const std::exception& )
437 {
438 return false;
439 }
440}
441
442
443bool SCH_IO_ORCAD::CanReadLibrary( const wxString& aFileName ) const
444{
445 if( !SCH_IO::CanReadLibrary( aFileName )
447 {
448 return false;
449 }
450
451 try
452 {
453 ALTIUM_COMPOUND_FILE cfbFile( aFileName );
454 const CFB::CompoundFileReader& reader = cfbFile.GetCompoundFileReader();
455 const CFB::COMPOUND_FILE_ENTRY* root = reader.GetRootEntry();
456
457 return root && cfbFile.FindStreamSingleLevel( root, "Library", true );
458 }
459 catch( const std::exception& )
460 {
461 return false;
462 }
463}
464
465
466SCH_SHEET* SCH_IO_ORCAD::LoadSchematicFile( const wxString& aFileName, SCHEMATIC* aSchematic, SCH_SHEET* aAppendToMe,
467 const std::map<std::string, UTF8>* aProperties )
468{
469 wxASSERT( !aFileName.IsEmpty() && aSchematic );
470
471 std::optional<wxString> sourceHash = IO_UTILS::fileHashMMH3( aFileName );
472
473 if( !sourceHash )
474 THROW_IO_ERROR( _( "The OrCAD file could not be read." ) );
475
476 std::string sourceId( sourceHash->ToUTF8() );
477
478 SCH_SHEET* rootSheet = nullptr;
479
480 if( aAppendToMe )
481 {
482 wxCHECK_MSG( aSchematic->IsValid(), nullptr, wxS( "Can't append to a schematic with no root!" ) );
483 rootSheet = aAppendToMe;
484 }
485 else
486 {
487 rootSheet = new SCH_SHEET( aSchematic );
488 rootSheet->SetFileName( aFileName );
489 aSchematic->SetTopLevelSheets( { rootSheet } );
490 }
491
492 if( !rootSheet->GetScreen() )
493 {
494 SCH_SCREEN* screen = new SCH_SCREEN( aSchematic );
495 const_cast<KIID&>( screen->GetUuid() ) = KIID::FromName( "orcad-import:" + sourceId + ":screen:0" );
496 screen->SetFileName( aFileName );
497 rootSheet->SetScreen( screen );
498
499 // Top-level sheet UUID must match schematic file UUID
500 rootSheet->SyncUuidToScreen();
501 }
502
504 {
505 m_progressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) );
506
507 if( !m_progressReporter->KeepRefreshing() )
509 }
510
511 ORCAD_WARN_FN warnFn = [this]( const wxString& aMsg )
512 {
513 if( m_reporter )
514 m_reporter->Report( aMsg, RPT_SEVERITY_WARNING );
515 };
516
517 ORCAD_DESIGN design;
518 design.sourceId = sourceId;
519
520 try
521 {
522 ALTIUM_COMPOUND_FILE cfbFile( aFileName );
523
524 const CFB::CompoundFileReader& reader = cfbFile.GetCompoundFileReader();
525 const CFB::COMPOUND_FILE_ENTRY* root = reader.GetRootEntry();
526
527 // 'Library' stream: version, fonts, string table
528 const CFB::COMPOUND_FILE_ENTRY* libraryEntry = cfbFile.FindStreamSingleLevel( root, "Library", true );
529
530 if( !libraryEntry )
531 {
532 THROW_IO_ERROR( _( "The file does not contain the 'Library' stream of an OrCAD "
533 "Capture design." ) );
534 }
535
536 design.library = OrcadParseLibrary( readStream( cfbFile, libraryEntry ) );
537
538 // Pre-2003 designs use pre-preamble framing; pages and the symbol cache each need
539 // their own reader
540 bool isV2 = design.library.versionMajor < 3;
541
542 // 'Cache' stream: symbol defs and package pin maps
543 if( const CFB::COMPOUND_FILE_ENTRY* cacheEntry = cfbFile.FindStreamSingleLevel( root, "Cache", true ) )
544 {
545 if( isV2 )
546 {
547 OrcadParseCacheV2( readStream( cfbFile, cacheEntry ), design.library.strings, warnFn, design.symbols,
548 design.packages );
549 }
550 else
551 {
552 OrcadParseCache( readStream( cfbFile, cacheEntry ), design.library.strings, warnFn, design.symbols,
553 design.packages );
554 }
555 }
556 else
557 {
558 warnFn( _( "The design has no 'Cache' stream; placeholder symbols will be "
559 "synthesized for all parts." ) );
560 }
561
562 // 'Packages/<name>' streams: locally modified parts
563 if( const CFB::COMPOUND_FILE_ENTRY* packagesStorage =
564 cfbFile.FindStreamSingleLevel( root, "Packages", false ) )
565 {
566 for( const auto& [streamName, entry] : enumChildren( cfbFile, packagesStorage, true ) )
567 {
568 std::map<std::string, ORCAD_SYMBOL_DEF> extraSymbols;
569 std::map<std::string, ORCAD_PACKAGE> extraPackages;
570
571 try
572 {
573 std::vector<char> data = readStream( cfbFile, entry );
574
575 if( isV2 || !isLongFramedPackageStream( data ) )
576 {
577 OrcadParseOlbPackageStreamV2( data, design.library.strings, extraSymbols, extraPackages,
578 design.library.versionMajor < 2 );
579 }
580 else
581 {
582 OrcadParsePackageStream( data, design.library.strings, extraSymbols, extraPackages );
583 }
584 }
585 catch( const IO_ERROR& e )
586 {
587 // CFB entry names UTF-16 in container, UTF-8 here
588 warnFn( wxString::Format( _( "Package stream '%s' could not be parsed: %s" ),
589 wxString::FromUTF8( streamName ), e.What() ) );
590 continue;
591 }
592
593 if( isV2 )
594 OrcadMergeSymbolGeneralProperties( design.symbols, extraSymbols );
595 else
596 OrcadMergeCacheStreams( design.symbols, design.packages, std::move( extraSymbols ),
597 std::move( extraPackages ) );
598 }
599 }
600
601 // 'Views/<folder>': one storage per schematic folder
602 const CFB::COMPOUND_FILE_ENTRY* viewsStorage = cfbFile.FindStreamSingleLevel( root, "Views", false );
603
604 if( !viewsStorage )
605 {
606 THROW_IO_ERROR( _( "The file does not contain a 'Views' storage; it is not a "
607 "supported OrCAD Capture design." ) );
608 }
609
610 std::vector<std::string> folders;
611 std::map<std::string, const CFB::COMPOUND_FILE_ENTRY*> folderEntries;
612
613 for( const auto& [folderName, entry] : enumChildren( cfbFile, viewsStorage, false ) )
614 {
615 if( folderEntries.emplace( folderName, entry ).second )
616 folders.push_back( folderName );
617 }
618
619 if( const CFB::COMPOUND_FILE_ENTRY* directoryEntry =
620 cfbFile.FindStreamSingleLevel( root, "Views Directory", true ) )
621 {
622 try
623 {
624 std::vector<std::string> visibleFolders;
625
626 for( std::string folder : OrcadParseSchematicFolderOrder( readStream( cfbFile, directoryEntry ) ) )
627 {
628 folder = OrcadNormalizeCfbName( folder );
629
630 if( folderEntries.count( folder )
631 && std::find( visibleFolders.begin(), visibleFolders.end(), folder ) == visibleFolders.end() )
632 {
633 visibleFolders.push_back( std::move( folder ) );
634 }
635 }
636
637 folders = std::move( visibleFolders );
638 }
639 catch( const IO_ERROR& e )
640 {
641 warnFn( wxString::Format( _( "The schematic folder directory could not be read (%s); all stored "
642 "folders are imported." ),
643 e.What() ) );
644 std::sort( folders.begin(), folders.end() );
645 }
646 }
647 else
648 {
649 std::sort( folders.begin(), folders.end() );
650 }
651
652 if( folders.empty() )
653 THROW_IO_ERROR( _( "The design contains no schematic folders." ) );
654
655 // Root folder = folder matching Library schematic name (any case); others are
656 // hierarchical children, skipped here
657 std::string rootFolder;
658 std::string schematicName = lowerCopy( design.library.schematicName );
659
660 if( !schematicName.empty() )
661 {
662 for( const std::string& folder : folders )
663 {
664 if( lowerCopy( folder ) == schematicName )
665 {
666 rootFolder = folder;
667 break;
668 }
669 }
670 }
671
672 if( rootFolder.empty() )
673 {
674 // A missing or unmatched root name requires a warning because it changes the sheet hierarchy.
675 if( design.library.schematicName.empty() )
676 {
677 warnFn( _( "The design does not name its root schematic; the first schematic "
678 "folder is used instead." ) );
679 }
680 else
681 {
682 warnFn( wxString::Format( _( "The design names '%s' as its root schematic, but no such "
683 "folder is present; the first schematic folder is used "
684 "instead." ),
685 wxString::FromUTF8( design.library.schematicName ) ) );
686 }
687
688 rootFolder = folders.front();
689 }
690
691 design.name = design.library.schematicName.empty() ? rootFolder : design.library.schematicName;
692
693
694 std::map<uint32_t, std::string> hierarchyLinks;
695
696 auto parseFolderPages = [&]( const std::string& aFolderName, const CFB::COMPOUND_FILE_ENTRY* aFolderEntry,
697 std::vector<ORCAD_RAW_PAGE>& aOutPages )
698 {
699 const CFB::COMPOUND_FILE_ENTRY* pagesStorage =
700 cfbFile.FindStreamSingleLevel( aFolderEntry, "Pages", false );
701
702 std::vector<std::string> available;
703 std::map<std::string, const CFB::COMPOUND_FILE_ENTRY*> pageEntries;
704
705 if( pagesStorage )
706 {
707 for( const auto& [pageName, entry] : enumChildren( cfbFile, pagesStorage, true ) )
708 {
709 if( pageEntries.emplace( pageName, entry ).second )
710 available.push_back( pageName );
711 }
712 }
713
714 // Display order from folder's 'Schematic' stream; fall back to name order if absent
715 std::vector<std::string> ordered;
716 bool orderKnown = false;
717
718 if( const CFB::COMPOUND_FILE_ENTRY* orderEntry =
719 cfbFile.FindStreamSingleLevel( aFolderEntry, "Schematic", true ) )
720 {
721 try
722 {
723 std::vector<char> orderData = readStream( cfbFile, orderEntry );
724
725 for( const std::string& pageName : isV2 ? OrcadParsePageOrderV2( orderData, design.library.strings )
726 : OrcadParsePageOrder( orderData ) )
727 {
728 if( pageEntries.count( pageName )
729 && std::find( ordered.begin(), ordered.end(), pageName ) == ordered.end() )
730 {
731 ordered.push_back( pageName );
732 }
733 }
734
735 orderKnown = true;
736 }
737 catch( const IO_ERROR& e )
738 {
739 warnFn( wxString::Format( _( "The page display order for schematic folder '%s' could not be "
740 "read (%s); pages are imported in name order." ),
741 wxString::FromUTF8( aFolderName ), e.What() ) );
742 ordered.clear();
743 }
744 }
745
746 if( orderKnown )
747 {
748 for( const std::string& pageName : available )
749 {
750 if( std::find( ordered.begin(), ordered.end(), pageName ) == ordered.end() )
751 ordered.push_back( pageName );
752 }
753 }
754 else
755 {
756 ordered = available;
757 std::sort( ordered.begin(), ordered.end() );
758 }
759
760 for( size_t pageIndex = 0; pageIndex < ordered.size(); ++pageIndex )
761 {
762 const std::string& pageName = ordered[pageIndex];
763
764 try
765 {
766 std::vector<char> pageData = readStream( cfbFile, pageEntries[pageName] );
767 ORCAD_RAW_PAGE page = isV2 ? OrcadParsePageV2( pageData, design.library.strings, warnFn,
768 design.library.versionMajor < 2 )
769 : OrcadParsePage( pageData, design.library.strings, warnFn );
770 page.sourcePageNumber = pageIndex + 1;
771 page.sourcePageCount = ordered.size();
772 aOutPages.push_back( std::move( page ) );
773 }
774 catch( const IO_ERROR& e )
775 {
776 warnFn( wxString::Format( _( "Page '%s' could not be parsed and was "
777 "skipped: %s" ),
778 wxString::FromUTF8( pageName ), e.What() ) );
779 }
780 }
781 };
782
783 parseFolderPages( rootFolder, folderEntries[rootFolder], design.pages );
784
785 // Root folder's Hierarchy stream holds whole occurrence tree (part refdes + nested blocks)
786 if( const CFB::COMPOUND_FILE_ENTRY* hierarchyEntry =
787 cfbFile.FindStream( folderEntries[rootFolder], { "Hierarchy", "Hierarchy" } ) )
788 {
789 std::vector<char> hierarchyData = readStream( cfbFile, hierarchyEntry );
790
791 design.occurrenceRoot = isV2 ? OrcadReadOccurrenceTreeV2( hierarchyData, design.library.strings )
792 : OrcadReadOccurrenceTree( hierarchyData, design.library.strings, warnFn );
793
794 // Block instance dbId -> child folder name, from occurrence tree
795 std::function<void( const ORCAD_OCC_SCOPE& )> collectLinks = [&]( const ORCAD_OCC_SCOPE& aScope )
796 {
797 for( const ORCAD_OCC_BLOCK& block : aScope.blocks )
798 {
799 hierarchyLinks[block.targetDbId] = block.childFolder;
800 collectLinks( block.scope );
801 }
802 };
803
804 collectLinks( design.occurrenceRoot );
805
806 }
807
808 if( design.pages.empty() )
809 THROW_IO_ERROR( _( "No schematic pages could be read from the design." ) );
810
811 // Parse pages of every block-reachable folder once; instantiated per block occurrence
812 // during conversion.
813 std::map<std::string, std::string> folderByLowerName;
814
815 for( const auto& folderEntry : folderEntries )
816 folderByLowerName.emplace( lowerCopy( folderEntry.first ), folderEntry.first );
817
818 for( const auto& [dbId, childName] : hierarchyLinks )
819 {
820 std::string key = lowerCopy( childName );
821
822 if( key == lowerCopy( rootFolder ) || design.childFolderPages.count( key ) )
823 continue;
824
825 auto childIt = folderByLowerName.find( key );
826
827 if( childIt != folderByLowerName.end() )
828 parseFolderPages( childIt->second, folderEntries[childIt->second], design.childFolderPages[key] );
829 }
830
831 for( const std::string& folder : folders )
832 {
833 std::string key = lowerCopy( folder );
834
835 if( key == lowerCopy( rootFolder ) || design.childFolderPages.count( key ) )
836 continue;
837
838 std::vector<ORCAD_RAW_PAGE>& pages = design.unreferencedFolderPages[key];
839 parseFolderPages( folder, folderEntries[folder], pages );
840
841 if( pages.empty() )
842 design.unreferencedFolderPages.erase( key );
843 }
844
845 for( ORCAD_RAW_PAGE& page : design.pages )
846 {
847 if( OrcadPageHasHierarchyBlocks( page ) )
848 design.hasHierarchyBlocks = true;
849
850 for( ORCAD_DRAWN_INSTANCE& block : page.blocks )
851 {
852 if( block.childName.empty() )
853 {
854 auto it = hierarchyLinks.find( block.dbId );
855
856 if( it != hierarchyLinks.end() )
857 block.childName = it->second;
858 }
859 }
860 }
861
862 applyCisVariant( cfbFile, root, aProperties, design, m_reporter );
863 }
864 catch( const CFB::CFBException& e )
865 {
866 THROW_IO_ERROR( e.what() );
867 }
868
869 ORCAD_CONVERTER converter( design, aSchematic, m_reporter, m_progressReporter );
870
871 converter.Convert( rootSheet );
872
873 aSchematic->Settings().m_ShowDNPMarkers = false;
874
875 auto [dashRatio, gapRatio] = OrcadDashRatios( design.library.versionMajor );
876 aSchematic->Settings().m_DashedLineDashRatio = dashRatio;
877 aSchematic->Settings().m_DashedLineGapRatio = gapRatio;
878
879
881 assignPostImportUuids( aSchematic, sourceId );
882
883 return rootSheet;
884}
885
886
887const std::vector<std::unique_ptr<LIB_SYMBOL>>& SCH_IO_ORCAD::loadOlbSymbols( const wxString& aLibraryPath )
888{
889 if( auto it = m_libCache.find( aLibraryPath ); it != m_libCache.end() )
890 return it->second;
891
892 ORCAD_WARN_FN warnFn = [this]( const wxString& aMsg )
893 {
894 if( m_reporter )
895 m_reporter->Report( aMsg, RPT_SEVERITY_WARNING );
896 };
897
898 ORCAD_DESIGN design;
899
900 try
901 {
902 ALTIUM_COMPOUND_FILE cfbFile( aLibraryPath );
903
904 const CFB::CompoundFileReader& reader = cfbFile.GetCompoundFileReader();
905 const CFB::COMPOUND_FILE_ENTRY* root = reader.GetRootEntry();
906
907 const CFB::COMPOUND_FILE_ENTRY* libraryEntry = cfbFile.FindStreamSingleLevel( root, "Library", true );
908
909 if( !libraryEntry )
910 THROW_IO_ERROR( _( "The file is not an OrCAD Capture library (no 'Library' stream)." ) );
911
912 design.library = OrcadParseLibrary( readStream( cfbFile, libraryEntry ) );
913
914 bool isV2 = design.library.versionMajor < 3;
915
916 // Parse one stream, tolerating a single bad/oversized stream without aborting the
917 // library. Modern streams use preamble-framed cache reader; v2.0 uses short-prefix readers
918 bool shortDisplayProp = design.library.versionMajor < 2;
919
920 auto parseStream = [&]( const CFB::COMPOUND_FILE_ENTRY* aEntry, const std::string& aStreamName,
921 bool aIsPackage, bool aIsCache = false )
922 {
923 std::map<std::string, ORCAD_SYMBOL_DEF> extraSymbols;
924 std::map<std::string, ORCAD_PACKAGE> extraPackages;
925
926 try
927 {
928 std::vector<char> data = readStream( cfbFile, aEntry );
929
930 if( aIsPackage && ( isV2 || !isLongFramedPackageStream( data ) ) )
931 {
932 OrcadParseOlbPackageStreamV2( data, design.library.strings, extraSymbols, extraPackages,
933 shortDisplayProp );
934 }
935 else if( isV2 )
936 {
937 OrcadParseOlbSymbolStreamV2( data, design.library.strings, extraSymbols, shortDisplayProp );
938 }
939 else if( aIsCache )
940 {
941 OrcadParseCache( data, design.library.strings, warnFn, extraSymbols, extraPackages );
942 }
943 else if( aIsPackage )
944 {
945 OrcadParsePackageStream( data, design.library.strings, extraSymbols, extraPackages );
946 }
947 else
948 {
949 OrcadParseSymbolStream( data, design.library.strings, extraSymbols );
950 }
951 }
952 catch( const std::exception& e )
953 {
954 // Single bad stream must not abort whole library, but a library that quietly
955 // drops a part looks complete and is not.
956 warnFn( wxString::Format( _( "The library stream '%s' could not be read and was skipped (%s)." ),
957 wxString::FromUTF8( aStreamName ), wxString::FromUTF8( e.what() ) ) );
958 return;
959 }
960
961 OrcadMergeCacheStreams( design.symbols, design.packages, std::move( extraSymbols ),
962 std::move( extraPackages ) );
963 };
964
965 // Design 'Cache' usually empty in a library; read anyway for rare cached symbol
966 if( !isV2 )
967 {
968 if( const CFB::COMPOUND_FILE_ENTRY* cacheEntry = cfbFile.FindStreamSingleLevel( root, "Cache", true ) )
969 {
970 parseStream( cacheEntry, "Cache", false, true );
971 }
972 }
973
974 // Symbols and parts live one per stream under 'Symbols' and 'Packages' storages
975 for( const char* storageName : { "Symbols", "Packages" } )
976 {
977 bool isPackage = std::string( storageName ) == "Packages";
978
979 const CFB::COMPOUND_FILE_ENTRY* storage = cfbFile.FindStreamSingleLevel( root, storageName, false );
980
981 if( storage )
982 {
983 for( const auto& [streamName, entry] : enumChildren( cfbFile, storage, true ) )
984 {
985 // '$Types$' and similar helper streams are not symbol defs
986 if( !streamName.empty() && streamName.front() == '$' )
987 continue;
988
989 parseStream( entry, streamName, isPackage );
990 }
991 }
992 }
993 }
994 catch( const CFB::CFBException& e )
995 {
996 THROW_IO_ERROR( e.what() );
997 }
998 catch( const IO_ERROR& )
999 {
1000 // Reportable errors (e.g. pre-2003 version gate) propagate
1001 throw;
1002 }
1003 catch( const std::exception& e )
1004 {
1005 // Malformed Library stream can drive over-sized allocation; degrade to recovered symbols
1006 warnFn( wxString::Format( _( "The OrCAD library could not be fully parsed (%s); some "
1007 "symbols may be missing." ),
1008 wxString::FromUTF8( e.what() ) ) );
1009 }
1010
1011 ORCAD_CONVERTER converter( design, nullptr, m_reporter, m_progressReporter );
1012
1013 // Build fully before caching so mid-parse failure does not cache an empty library
1014 std::vector<std::unique_ptr<LIB_SYMBOL>> built;
1015
1016 for( LIB_SYMBOL* symbol : converter.BuildSymbolLibrary() )
1017 built.emplace_back( symbol );
1018
1019 return m_libCache[aLibraryPath] = std::move( built );
1020}
1021
1022
1023void SCH_IO_ORCAD::EnumerateSymbolLib( wxArrayString& aSymbolNameList, const wxString& aLibraryPath,
1024 const std::map<std::string, UTF8>* )
1025{
1026 for( const std::unique_ptr<LIB_SYMBOL>& symbol : loadOlbSymbols( aLibraryPath ) )
1027 aSymbolNameList.Add( symbol->GetName() );
1028}
1029
1030
1031void SCH_IO_ORCAD::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList, const wxString& aLibraryPath,
1032 const std::map<std::string, UTF8>* )
1033{
1034 for( const std::unique_ptr<LIB_SYMBOL>& symbol : loadOlbSymbols( aLibraryPath ) )
1035 aSymbolList.push_back( symbol.get() );
1036}
1037
1038
1039LIB_SYMBOL* SCH_IO_ORCAD::LoadSymbol( const wxString& aLibraryPath, const wxString& aAliasName,
1040 const std::map<std::string, UTF8>* )
1041{
1042 for( const std::unique_ptr<LIB_SYMBOL>& symbol : loadOlbSymbols( aLibraryPath ) )
1043 {
1044 if( symbol->GetName() == aAliasName )
1045 return symbol.get();
1046 }
1047
1048 return nullptr;
1049}
const char * name
const CFB::CompoundFileReader & GetCompoundFileReader() const
const CFB::COMPOUND_FILE_ENTRY * FindStreamSingleLevel(const CFB::COMPOUND_FILE_ENTRY *aEntry, const std::string aName, const bool aIsStream) const
const CFB::COMPOUND_FILE_ENTRY * FindStream(const std::vector< std::string > &aStreamPath) const
REPORTER * m_reporter
Reporter to log errors/warnings to, may be nullptr.
Definition io_base.h:238
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:241
virtual bool CanReadLibrary(const wxString &aFileName) const
Checks if this IO object can read the specified library file/directory.
Definition io_base.cpp:71
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
Definition kiid.h:46
static KIID FromName(const std::string &aName)
Return a KIID derived from a name, the same name always gives the same KIID.
Definition kiid.cpp:237
Define a library symbol object.
Definition lib_symbol.h:119
SCH_SHEET * Convert(SCH_SHEET *aRootSheet)
aRootSheet must have a screen and be registered on the schematic.
std::vector< LIB_SYMBOL * > BuildSymbolLibrary()
The caller owns the returned library symbols.
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
Holds all the data relating to one schematic.
Definition schematic.h:148
SCH_SHEET_LIST BuildSheetListSortedByPageNumbers() const
SCHEMATIC_SETTINGS & Settings() const
bool IsValid() const
A simple test if the schematic is loaded, not a complete one.
Definition schematic.h:288
void SetTopLevelSheets(const std::vector< SCH_SHEET * > &aSheets)
Replace the top level sheets, rebuilding the hierarchy and connectivity around them.
SCH_SHEET_PATH & CurrentSheet() const
Definition schematic.h:303
const std::vector< std::unique_ptr< LIB_SYMBOL > > & loadOlbSymbols(const wxString &aLibraryPath)
Parse an .OLB and build its KiCad library symbols, cached by library path.
std::map< wxString, std::vector< std::unique_ptr< LIB_SYMBOL > > > m_libCache
bool CanReadSchematicFile(const wxString &aFileName) const override
The .dsn extension also identifies SPECCTRA files.
SCH_SHEET * LoadSchematicFile(const wxString &aFileName, SCHEMATIC *aSchematic, SCH_SHEET *aAppendToMe=nullptr, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load information from some input file format that this SCH_IO implementation knows about,...
bool CanReadLibrary(const wxString &aFileName) const override
Checks if this IO object can read the specified library file/directory.
LIB_SYMBOL * LoadSymbol(const wxString &aLibraryPath, const wxString &aAliasName, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load a LIB_SYMBOL object having aPartName from the aLibraryPath containing a library format that this...
void EnumerateSymbolLib(wxArrayString &aSymbolNameList, const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Populate a list of LIB_SYMBOL alias names contained within the library aLibraryPath.
virtual bool CanReadSchematicFile(const wxString &aFileName) const
Checks if this SCH_IO can read the specified schematic file.
Definition sch_io.cpp:47
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:118
const KIID & GetUuid() const
Definition sch_screen.h:540
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
void UpdateAllScreenReferences() const
Update all the symbol references for this sheet path.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
void SetFileName(const wxString &aFilename)
Definition sch_sheet.h:390
void SyncUuidToScreen()
Take the identity of the screen this sheet owns.
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_CANCELLED()
std::optional< wxString > fileHashMMH3(const wxString &aFilePath)
Calculates an MMH3 hash of a given file.
Definition io_utils.cpp:91
const std::vector< uint8_t > COMPOUND_FILE_HEADER
Definition io_utils.cpp:30
bool fileHasBinaryHeader(const wxString &aFilePath, const std::vector< uint8_t > &aHeader, size_t aOffset)
Check if a file starts with a defined binary header.
Definition io_utils.cpp:60
void OrcadMergeSymbolGeneralProperties(std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, const std::map< std::string, ORCAD_SYMBOL_DEF > &aMetadataSymbols)
void OrcadMergeCacheStreams(std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages, std::map< std::string, ORCAD_SYMBOL_DEF > &&aExtraSymbols, std::map< std::string, ORCAD_PACKAGE > &&aExtraPackages)
Existing symbols gain variants.
void OrcadParseSymbolStream(const std::vector< char > &aData, const std::vector< std::string > &aStrings, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols)
Throws IO_ERROR for invalid framing or trailing bytes.
void OrcadParseCache(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &aWarn, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages)
The first entry is the default; later entries become variants.
void OrcadParsePackageStream(const std::vector< char > &aData, const std::vector< std::string > &aStrings, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages)
Package streams contain counted PartCells and LibraryParts, followed by one Package.
std::map< uint32_t, std::map< std::string, std::string > > OrcadCisParsePropertyUpdates(const std::vector< char > &aData)
std::string OrcadCisSelectVariant(const std::vector< std::string > &aNames, const std::optional< std::string > &aRequested)
ORCAD_CIS_SCHEMATIC_INFO OrcadCisParseSchematicInfo(const std::vector< char > &aData)
std::vector< std::string > OrcadCisParseCountedList(const std::vector< char > &aData, uint8_t aSeparator)
Definition orcad_cis.cpp:92
std::map< std::string, std::map< uint32_t, ORCAD_CIS_PROPERTIES > > ORCAD_CIS_SCHEMATIC_INFO
Definition orcad_cis.h:31
std::pair< double, double > OrcadDashRatios(int aFormatVersionMajor)
ORCAD_LIBRARY_INFO OrcadParseLibrary(const std::vector< char > &aData)
The Library version selects the string-count width.
std::vector< std::string > OrcadParsePageOrderV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings)
Returns display order; throws IO_ERROR for invalid legacy framing.
void OrcadParseCacheV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &aWarn, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages)
Keep decoded entries if a framing error ends the legacy cache.
void OrcadParseOlbSymbolStreamV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, bool aShortDisplayProp)
aShortDisplayProp selects the version 1 display-property layout.
ORCAD_RAW_PAGE OrcadParsePageV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &, bool aShortDisplayProp)
Legacy records have no stop offsets.
void OrcadParseOlbPackageStreamV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages, bool aShortDisplayProp)
aShortDisplayProp selects the version 1 display-property layout.
std::vector< std::string > OrcadParsePageOrder(const std::vector< char > &aData)
Returns display order.
ORCAD_OCC_SCOPE OrcadReadOccurrenceTree(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &aWarn)
Returns occurrence references and child scopes.
ORCAD_OCC_SCOPE OrcadReadOccurrenceTreeV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings)
Parse a short-prefix-only v2.0 Hierarchy stream without scan recovery.
std::vector< std::string > OrcadParseSchematicFolderOrder(const std::vector< char > &aData)
Unlisted Views storages can be stale.
ORCAD_RAW_PAGE OrcadParsePage(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &aWarn)
A body failure skips that structure.
bool OrcadPageHasHierarchyBlocks(const ORCAD_RAW_PAGE &aPage)
True when the page contains hierarchical block instances (DrawnInstance, type 12).
Definition orcad_page.h:74
std::function< void(const wxString &aMsg)> ORCAD_WARN_FN
Coordinates use DBU with Y down.
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_INFO
std::string OrcadNormalizeCfbName(const std::string &aName)
std::string OrcadNormalizeCfbName(const std::string &aName)
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
Child-folder pages are instantiated for each occurrence, with that occurrence's references.
ORCAD_LIBRARY_INFO library
std::string sourceId
stable checksum of the input file
ORCAD_OCC_SCOPE occurrenceRoot
Occurrence references distinguish repeated placements of a child schematic.
std::map< std::string, ORCAD_SYMBOL_DEF > symbols
cache, keyed by cache name
bool hasHierarchyBlocks
std::map< std::string, std::vector< ORCAD_RAW_PAGE > > unreferencedFolderPages
Schematic folder pages not instantiated by the active hierarchy.
std::map< std::string, ORCAD_PACKAGE > packages
keyed by package name
std::string name
design (root schematic) name
std::map< std::string, std::vector< ORCAD_RAW_PAGE > > childFolderPages
Child schematic folder pages, keyed by lower-cased folder name; instantiated once per hierarchical bl...
std::vector< ORCAD_RAW_PAGE > pages
root schematic folder pages
The inline LibraryPart defines the block interface; placed pin records supply absolute positions.
std::string childName
child folder, when embedded
Free graphics use nested primitive coordinates.
std::map< std::string, std::string > props
std::vector< std::string > strings
global string table
std::string schematicName
root schematic folder name
Repeated child folders have separate scopes and reference designators.
uint32_t targetDbId
type-12 drawn-instance dbId on the parent page
ORCAD_OCC_SCOPE scope
the child's occurrences under this path
std::string childFolder
child schematic folder name
Each scope holds the references and child blocks for one instantiation path.
std::vector< ORCAD_OCC_BLOCK > blocks
hierarchical block occurrences
std::map< uint32_t, std::map< std::string, std::string > > partProps
dbId -> occurrence properties
std::map< uint32_t, std::string > partRefs
type-13 dbId -> occurrence refdes
Wire IDs refer to netmap, which supplies the source net names.
size_t sourcePageCount
pages in the OrCAD folder
size_t sourcePageNumber
1-based within the OrCAD folder
std::vector< ORCAD_DRAWN_INSTANCE > blocks
hierarchical blocks (detection only)
std::string path
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683