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 <string>
33#include <utility>
34#include <vector>
35
36#include <wx/string.h>
37#include <wx/translation.h>
38
39#include <compoundfilereader.h>
40#include <utf.h>
41
43#include <io/io_utils.h>
44#include <ki_exception.h>
45#include <kiid.h>
46#include <progress_reporter.h>
47
48#include <lib_symbol.h>
49#include <schematic.h>
50#include <sch_screen.h>
51#include <sch_sheet.h>
52
60
61
62std::string OrcadNormalizeCfbName( const std::string& aName )
63{
64 std::string name = aName;
65 std::replace( name.begin(), name.end(), '\x02', '/' );
66 std::replace( name.begin(), name.end(), '\x03', ':' );
67 return name;
68}
69
70
71namespace
72{
73
74std::vector<char> readStream( const ALTIUM_COMPOUND_FILE& aFile, const CFB::COMPOUND_FILE_ENTRY* aEntry )
75{
76 const CFB::CompoundFileReader& reader = aFile.GetCompoundFileReader();
77
78 // Stream cannot exceed file; corrupt entry claiming more must not drive huge allocation
79 uint64_t size = reader.GetStreamSize( aEntry );
80
81 if( size > reader.GetBufferLen() )
82 THROW_IO_ERROR( _( "OrCAD stream size exceeds the compound file" ) );
83
84 std::vector<char> data( static_cast<size_t>( size ) );
85
86 if( !data.empty() )
87 reader.ReadFile( aEntry, 0, data.data(), data.size() );
88
89 return data;
90}
91
92
93bool isLongFramedPackageStream( const std::vector<char>& aData )
94{
95 if( aData.size() < 11 )
96 return false;
97
98 ORCAD_STREAM stream( aData );
99 stream.Skip( 3 );
100
101 uint32_t bodyLength = stream.ReadU32();
102
103 return stream.ReadU32() == 0 && bodyLength <= aData.size() - 11;
104}
105
106
107// Direct children of storage, filtered to streams (aStreams) or sub-storages, in directory order.
108std::vector<std::pair<std::string, const CFB::COMPOUND_FILE_ENTRY*>>
109enumChildren( const ALTIUM_COMPOUND_FILE& aFile, const CFB::COMPOUND_FILE_ENTRY* aParent, bool aStreams )
110{
111 std::vector<std::pair<std::string, const CFB::COMPOUND_FILE_ENTRY*>> out;
112
113 const CFB::CompoundFileReader& reader = aFile.GetCompoundFileReader();
114
115 reader.EnumFiles( aParent, 1,
116 [&]( const CFB::COMPOUND_FILE_ENTRY* aEntry, const CFB::utf16string&, int ) -> int
117 {
118 if( reader.IsStream( aEntry ) == aStreams )
119 out.emplace_back( OrcadNormalizeCfbName( UTF16ToUTF8( aEntry->name ) ), aEntry );
120
121 return 0;
122 } );
123
124 return out;
125}
126
127
128void collectPartOccurrences( const ORCAD_OCC_SCOPE& aScope, std::multimap<uint32_t, uint32_t>& aOccurrences )
129{
130 for( const auto& [dbId, occurrence] : aScope.partOccurrenceIds )
131 aOccurrences.emplace( dbId, occurrence );
132
133 for( const ORCAD_OCC_BLOCK& block : aScope.blocks )
134 collectPartOccurrences( block.scope, aOccurrences );
135}
136
137
138void readCisVariants( const ALTIUM_COMPOUND_FILE& aFile, const CFB::COMPOUND_FILE_ENTRY* aRoot,
139 const std::map<std::string, UTF8>* aProperties, ORCAD_DESIGN& aDesign, REPORTER* aReporter )
140{
141 const CFB::COMPOUND_FILE_ENTRY* cisStorage = aFile.FindStreamSingleLevel( aRoot, "CIS", false );
142
143 if( !cisStorage )
144 return;
145
146 const CFB::COMPOUND_FILE_ENTRY* variantStore = aFile.FindStreamSingleLevel( cisStorage, "VariantStore", false );
147
148 if( !variantStore )
149 return;
150
151 const CFB::COMPOUND_FILE_ENTRY* bomStorage = aFile.FindStreamSingleLevel( variantStore, "BOM", false );
152
153 if( !bomStorage )
154 return;
155
156 const CFB::COMPOUND_FILE_ENTRY* bomData = aFile.FindStreamSingleLevel( bomStorage, "BOMDataStream", true );
157
158 if( !bomData )
159 return;
160
161 std::vector<std::string> names = OrcadCisParseCountedList( readStream( aFile, bomData ), 0xF9 );
162 std::optional<std::string> requested;
163
164 if( aProperties )
165 {
166 auto request = aProperties->find( "orcad_cis_variant" );
167
168 if( request != aProperties->end() )
169 requested = request->second;
170 }
171
172 std::string selected = OrcadCisSelectVariant( names, requested );
173
174 if( selected.empty() )
175 return;
176
177 // Capture shows the active variant name where the title block reads "<Core Design>"
178 auto applyVariantName = [&]( std::vector<ORCAD_RAW_PAGE>& aPages )
179 {
180 constexpr std::string_view placeholder = "<Core Design>";
181 constexpr std::string_view variantVar = "${VARIANT}";
182
183 for( ORCAD_RAW_PAGE& page : aPages )
184 {
185 for( ORCAD_GRAPHIC_INST& titleBlock : page.titleBlocks )
186 {
187 for( auto& [name, value] : titleBlock.props )
188 {
189 size_t offset = 0;
190
191 while( ( offset = value.find( placeholder, offset ) ) != std::string::npos )
192 {
193 value.replace( offset, placeholder.size(), variantVar );
194 offset += variantVar.size();
195 }
196 }
197 }
198 }
199 };
200
201 applyVariantName( aDesign.pages );
202
203 for( auto& [folder, pages] : aDesign.childFolderPages )
204 applyVariantName( pages );
205
206 for( auto& [folder, pages] : aDesign.unreferencedFolderPages )
207 applyVariantName( pages );
208
209 struct CIS_GROUP
210 {
211 const CFB::COMPOUND_FILE_ENTRY* membership = nullptr;
212 const CFB::COMPOUND_FILE_ENTRY* updates = nullptr;
213 std::string schematicName;
214 };
215
216 // Variant definitions name subgroups "<group>_<sub>"; schematic info names them "<group>-<sub>"
217 std::map<std::string, CIS_GROUP> groups;
218
219 if( const CFB::COMPOUND_FILE_ENTRY* groupsStorage =
220 aFile.FindStreamSingleLevel( variantStore, "Groups", false ) )
221 {
222 for( const auto& [groupName, groupEntry] : enumChildren( aFile, groupsStorage, false ) )
223 {
224 groups[groupName] = { aFile.FindStreamSingleLevel( groupEntry, groupName, true ),
225 aFile.FindStreamSingleLevel( groupEntry, "UpdateStorageGroupDataStream", true ),
226 groupName };
227
228 for( const auto& [subgroupName, subgroupEntry] : enumChildren( aFile, groupEntry, false ) )
229 {
230 groups[groupName + "_" + subgroupName] = {
231 aFile.FindStreamSingleLevel( subgroupEntry, subgroupName, true ),
232 aFile.FindStreamSingleLevel( subgroupEntry, "UpdateStorageSubGroupDataStream", true ),
233 groupName + "-" + subgroupName
234 };
235 }
236 }
237 }
238
239 ORCAD_CIS_SCHEMATIC_INFO schematicInfo;
240 const CFB::COMPOUND_FILE_ENTRY* viewsStorage = aFile.FindStreamSingleLevel( aRoot, "Views", false );
241
242 if( viewsStorage )
243 {
244 for( const auto& [folderName, folderEntry] : enumChildren( aFile, viewsStorage, false ) )
245 {
246 const CFB::COMPOUND_FILE_ENTRY* cisSchematic =
247 aFile.FindStreamSingleLevel( folderEntry, "CISSchematic", false );
248
249 if( !cisSchematic )
250 continue;
251
252 const CFB::COMPOUND_FILE_ENTRY* infoStorage =
253 aFile.FindStreamSingleLevel( cisSchematic, "SchematicInfoStorage", false );
254
255 if( !infoStorage )
256 continue;
257
258 for( const auto& [streamName, streamEntry] : enumChildren( aFile, infoStorage, true ) )
259 {
260 if( streamName == "SchematicInfoStream" )
261 continue;
262
263 ORCAD_CIS_SCHEMATIC_INFO pageInfo = OrcadCisParseSchematicInfo( readStream( aFile, streamEntry ) );
264
265 for( auto& [groupName, occurrences] : pageInfo )
266 {
267 auto& target = schematicInfo[groupName];
268
269 for( auto& [occurrence, properties] : occurrences )
270 target.insert_or_assign( occurrence, std::move( properties ) );
271 }
272 }
273 }
274 }
275
276 // Schematic info is keyed by placement; repeated child pages give one placement several occurrences
277 std::multimap<uint32_t, uint32_t> partOccurrences;
278 collectPartOccurrences( aDesign.occurrenceRoot, partOccurrences );
279
280 std::map<std::string, const CFB::COMPOUND_FILE_ENTRY*> variantEntries;
281
282 for( const auto& [name, entry] : enumChildren( aFile, bomStorage, false ) )
283 variantEntries.emplace( name, entry );
284
285 for( const std::string& name : names )
286 {
287 ORCAD_CIS_VARIANT variant;
288 variant.name = name;
289
290 // Capture matches property names without case, so a later spelling replaces an earlier one
291 auto overlay = [&]( uint32_t aOccurrence, const ORCAD_CIS_PROPERTIES& aProperties )
292 {
293 ORCAD_CIS_PROPERTIES& target = variant.props[aOccurrence];
294
295 for( const auto& [property, value] : aProperties )
296 {
297 auto existing = std::find_if( target.begin(), target.end(),
298 [&]( const auto& aExisting )
299 {
300 return OrcadIEquals( aExisting.first, property );
301 } );
302
303 if( existing != target.end() )
304 target.erase( existing );
305
306 target.emplace( property, value );
307 }
308 };
309
310 auto entry = variantEntries.find( name );
311 const CFB::COMPOUND_FILE_ENTRY* definition =
312 entry != variantEntries.end() ? aFile.FindStreamSingleLevel( entry->second, name, true ) : nullptr;
313
314 if( !definition )
315 THROW_IO_ERROR( wxString::Format( _( "The OrCAD CIS variant '%s' has no definition stream." ),
316 wxString::FromUTF8( name ) ) );
317
318 // Later groups override earlier ones, and schematic info overrides a group's stored updates
319 for( const std::string& groupName : OrcadCisParseCountedList( readStream( aFile, definition ), 0xF9 ) )
320 {
321 auto group = groups.find( groupName );
322
323 if( group == groups.end() )
324 {
325 if( groupName == "Common" || groupName == "CommonNI" )
326 continue;
327
328 THROW_IO_ERROR( wxString::Format( _( "The OrCAD CIS variant '%s' references an unknown "
329 "property group." ),
330 wxString::FromUTF8( name ) ) );
331 }
332
333 if( group->second.membership )
334 {
335 for( const auto& [occurrence, installed] :
336 OrcadCisParseMemberships( readStream( aFile, group->second.membership ) ) )
337 {
338 variant.installed[occurrence] = installed;
339 }
340 }
341
342 if( group->second.updates )
343 {
344 for( const auto& [occurrence, properties] :
345 OrcadCisParsePropertyUpdates( readStream( aFile, group->second.updates ) ) )
346 {
347 overlay( occurrence, properties );
348 }
349 }
350
351 auto info = schematicInfo.find( group->second.schematicName );
352
353 if( info == schematicInfo.end() )
354 continue;
355
356 for( const auto& [dbId, properties] : info->second )
357 {
358 auto [first, last] = partOccurrences.equal_range( dbId );
359
360 for( auto it = first; it != last; ++it )
361 overlay( it->second, properties );
362 }
363 }
364
365 aDesign.cisVariants.push_back( std::move( variant ) );
366 }
367
368 aDesign.cisCurrentVariant = selected;
369
370 if( aReporter )
371 {
372 aReporter->Report( wxString::Format( _( "Using OrCAD CIS variant '%s'." ), wxString::FromUTF8( selected ) ),
374 }
375}
376
377} // namespace
378
379
380bool SCH_IO_ORCAD::CanReadSchematicFile( const wxString& aFileName ) const
381{
382 if( !SCH_IO::CanReadSchematicFile( aFileName ) )
383 return false;
384
385 // .dsn also names plain-text SPECCTRA session files; OrCAD design is OLE2/CFB compound doc
387 return false;
388
389 try
390 {
391 ALTIUM_COMPOUND_FILE cfbFile( aFileName );
392
393 const CFB::CompoundFileReader& reader = cfbFile.GetCompoundFileReader();
394 const CFB::COMPOUND_FILE_ENTRY* root = reader.GetRootEntry();
395
396 if( !root )
397 return false;
398
399 if( !cfbFile.FindStreamSingleLevel( root, "Library", true ) )
400 return false;
401
402 return cfbFile.FindStreamSingleLevel( root, "Views", false ) != nullptr
403 || cfbFile.FindStreamSingleLevel( root, "Schematics", false ) != nullptr;
404 }
405 catch( const IO_ERROR& )
406 {
407 return false;
408 }
409 catch( const CFB::CFBException& )
410 {
411 return false;
412 }
413 catch( const std::exception& )
414 {
415 return false;
416 }
417}
418
419
420bool SCH_IO_ORCAD::CanReadLibrary( const wxString& aFileName ) const
421{
422 if( !SCH_IO::CanReadLibrary( aFileName )
424 {
425 return false;
426 }
427
428 try
429 {
430 ALTIUM_COMPOUND_FILE cfbFile( aFileName );
431 const CFB::CompoundFileReader& reader = cfbFile.GetCompoundFileReader();
432 const CFB::COMPOUND_FILE_ENTRY* root = reader.GetRootEntry();
433
434 return root && cfbFile.FindStreamSingleLevel( root, "Library", true );
435 }
436 catch( const std::exception& )
437 {
438 return false;
439 }
440}
441
442
443SCH_SHEET* SCH_IO_ORCAD::LoadSchematicFile( const wxString& aFileName, SCHEMATIC* aSchematic, SCH_SHEET* aAppendToMe,
444 const std::map<std::string, UTF8>* aProperties )
445{
446 wxASSERT( !aFileName.IsEmpty() && aSchematic );
447
448 // A Capture design is a whole document whose load replaces the live top-level sheets and settings
449 if( aProperties && aProperties->count( "hierarchical_sheet_load" ) )
450 {
451 THROW_IO_ERROR( wxString::Format( _( "'%s' contains a complete OrCAD Capture design and cannot be "
452 "loaded as a hierarchical sheet. Use File > Import > "
453 "Non-KiCad Schematic... instead." ),
454 aFileName ) );
455 }
456
457 std::optional<wxString> sourceHash = IO_UTILS::fileHashMMH3( aFileName );
458
459 if( !sourceHash )
460 THROW_IO_ERROR( _( "The OrCAD file could not be read." ) );
461
462 std::string sourceId( sourceHash->ToUTF8() );
463
464 SCH_SHEET* rootSheet = nullptr;
465
466 if( aAppendToMe )
467 {
468 wxCHECK_MSG( aSchematic->IsValid(), nullptr, wxS( "Can't append to a schematic with no root!" ) );
469 rootSheet = aAppendToMe;
470 }
471 else
472 {
473 rootSheet = new SCH_SHEET( aSchematic );
474 rootSheet->SetFileName( aFileName );
475 aSchematic->SetTopLevelSheets( { rootSheet } );
476 }
477
478 if( !rootSheet->GetScreen() )
479 {
480 SCH_SCREEN* screen = new SCH_SCREEN( aSchematic );
481 const_cast<KIID&>( screen->GetUuid() ) = KIID::FromName( "orcad-import:" + sourceId + ":screen:0" );
482 screen->SetFileName( aFileName );
483 rootSheet->SetScreen( screen );
484
485 // Top-level sheet UUID must match schematic file UUID
486 rootSheet->SyncUuidToScreen();
487 }
488
490 {
491 m_progressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) );
492
493 if( !m_progressReporter->KeepRefreshing() )
495 }
496
497 ORCAD_WARN_FN warnFn = [this]( const wxString& aMsg )
498 {
499 if( m_reporter )
500 m_reporter->Report( aMsg, RPT_SEVERITY_WARNING );
501 };
502
503 ORCAD_DESIGN design;
504 design.sourceId = sourceId;
505
506 try
507 {
508 ALTIUM_COMPOUND_FILE cfbFile( aFileName );
509
510 const CFB::CompoundFileReader& reader = cfbFile.GetCompoundFileReader();
511 const CFB::COMPOUND_FILE_ENTRY* root = reader.GetRootEntry();
512
513 // 'Library' stream: version, fonts, string table
514 const CFB::COMPOUND_FILE_ENTRY* libraryEntry = cfbFile.FindStreamSingleLevel( root, "Library", true );
515
516 if( !libraryEntry )
517 {
518 THROW_IO_ERROR( _( "The file does not contain the 'Library' stream of an OrCAD "
519 "Capture design." ) );
520 }
521
522 design.library = OrcadParseLibrary( readStream( cfbFile, libraryEntry ) );
523
524 // Pre-2003 designs use pre-preamble framing, read as the legacy dialect
525 bool isV2 = design.library.versionMajor < 3;
526 ORCAD_DIALECT dialect{ isV2, design.library.versionMajor < 2 };
527
528 // 'Cache' stream: symbol defs and package pin maps. The legacy cache has only ever been read
529 // with the long display-property layout
530 if( const CFB::COMPOUND_FILE_ENTRY* cacheEntry = cfbFile.FindStreamSingleLevel( root, "Cache", true ) )
531 {
532 OrcadParseCache( readStream( cfbFile, cacheEntry ), design.library.strings, warnFn, design.symbols,
533 design.packages, ORCAD_DIALECT{ isV2, false } );
534 }
535 else
536 {
537 warnFn( _( "The design has no 'Cache' stream; placeholder symbols will be "
538 "synthesized for all parts." ) );
539 }
540
541 // 'Packages/<name>' streams: locally modified parts
542 if( const CFB::COMPOUND_FILE_ENTRY* packagesStorage =
543 cfbFile.FindStreamSingleLevel( root, "Packages", false ) )
544 {
545 for( const auto& [streamName, entry] : enumChildren( cfbFile, packagesStorage, true ) )
546 {
547 std::map<std::string, ORCAD_SYMBOL_DEF> extraSymbols;
548 std::map<std::string, ORCAD_PACKAGE> extraPackages;
549
550 try
551 {
552 std::vector<char> data = readStream( cfbFile, entry );
553
554 // Modern designs can also hold short-framed package streams
555 ORCAD_DIALECT packageDialect{ isV2 || !isLongFramedPackageStream( data ),
556 dialect.shortDisplayProp };
557
558 OrcadParsePackageStream( data, design.library.strings, extraSymbols, extraPackages,
559 packageDialect );
560 }
561 catch( const IO_ERROR& e )
562 {
563 // CFB entry names UTF-16 in container, UTF-8 here
564 warnFn( wxString::Format( _( "Package stream '%s' could not be parsed: %s" ),
565 wxString::FromUTF8( streamName ), e.What() ) );
566 continue;
567 }
568
569 if( isV2 )
570 OrcadMergeSymbolGeneralProperties( design.symbols, extraSymbols );
571 else
572 OrcadMergeCacheStreams( design.symbols, design.packages, std::move( extraSymbols ),
573 std::move( extraPackages ) );
574 }
575 }
576
577 // 'Views/<folder>': one storage per schematic folder
578 const CFB::COMPOUND_FILE_ENTRY* viewsStorage = cfbFile.FindStreamSingleLevel( root, "Views", false );
579
580 if( !viewsStorage )
581 {
582 THROW_IO_ERROR( _( "The file does not contain a 'Views' storage; it is not a "
583 "supported OrCAD Capture design." ) );
584 }
585
586 std::vector<std::string> folders;
587 std::map<std::string, const CFB::COMPOUND_FILE_ENTRY*> folderEntries;
588
589 for( const auto& [folderName, entry] : enumChildren( cfbFile, viewsStorage, false ) )
590 {
591 if( folderEntries.emplace( folderName, entry ).second )
592 folders.push_back( folderName );
593 }
594
595 if( const CFB::COMPOUND_FILE_ENTRY* directoryEntry =
596 cfbFile.FindStreamSingleLevel( root, "Views Directory", true ) )
597 {
598 try
599 {
600 std::vector<std::string> visibleFolders;
601
602 for( std::string folder : OrcadParseSchematicFolderOrder( readStream( cfbFile, directoryEntry ) ) )
603 {
604 folder = OrcadNormalizeCfbName( folder );
605
606 if( folderEntries.count( folder )
607 && std::find( visibleFolders.begin(), visibleFolders.end(), folder ) == visibleFolders.end() )
608 {
609 visibleFolders.push_back( std::move( folder ) );
610 }
611 }
612
613 folders = std::move( visibleFolders );
614 }
615 catch( const IO_ERROR& e )
616 {
617 warnFn( wxString::Format( _( "The schematic folder directory could not be read (%s); all stored "
618 "folders are imported." ),
619 e.What() ) );
620 std::sort( folders.begin(), folders.end() );
621 }
622 }
623 else
624 {
625 std::sort( folders.begin(), folders.end() );
626 }
627
628 if( folders.empty() )
629 THROW_IO_ERROR( _( "The design contains no schematic folders." ) );
630
631 // Root folder = folder matching Library schematic name (any case); others are
632 // hierarchical children, skipped here
633 std::string rootFolder;
634 std::string schematicName = OrcadLower( design.library.schematicName );
635
636 if( !schematicName.empty() )
637 {
638 for( const std::string& folder : folders )
639 {
640 if( OrcadLower( folder ) == schematicName )
641 {
642 rootFolder = folder;
643 break;
644 }
645 }
646 }
647
648 if( rootFolder.empty() )
649 {
650 // A missing or unmatched root name requires a warning because it changes the sheet hierarchy.
651 if( design.library.schematicName.empty() )
652 {
653 warnFn( _( "The design does not name its root schematic; the first schematic "
654 "folder is used instead." ) );
655 }
656 else
657 {
658 warnFn( wxString::Format( _( "The design names '%s' as its root schematic, but no such "
659 "folder is present; the first schematic folder is used "
660 "instead." ),
661 wxString::FromUTF8( design.library.schematicName ) ) );
662 }
663
664 rootFolder = folders.front();
665 }
666
667 design.name = design.library.schematicName.empty() ? rootFolder : design.library.schematicName;
668
669
670 std::map<uint32_t, std::string> hierarchyLinks;
671
672 auto parseFolderPages = [&]( const std::string& aFolderName, const CFB::COMPOUND_FILE_ENTRY* aFolderEntry,
673 std::vector<ORCAD_RAW_PAGE>& aOutPages )
674 {
675 const CFB::COMPOUND_FILE_ENTRY* pagesStorage =
676 cfbFile.FindStreamSingleLevel( aFolderEntry, "Pages", false );
677
678 std::vector<std::string> available;
679 std::map<std::string, const CFB::COMPOUND_FILE_ENTRY*> pageEntries;
680
681 if( pagesStorage )
682 {
683 for( const auto& [pageName, entry] : enumChildren( cfbFile, pagesStorage, true ) )
684 {
685 if( pageEntries.emplace( pageName, entry ).second )
686 available.push_back( pageName );
687 }
688 }
689
690 // Display order from folder's 'Schematic' stream; fall back to name order if absent
691 std::vector<std::string> ordered;
692 bool orderKnown = false;
693
694 if( const CFB::COMPOUND_FILE_ENTRY* orderEntry =
695 cfbFile.FindStreamSingleLevel( aFolderEntry, "Schematic", true ) )
696 {
697 try
698 {
699 std::vector<char> orderData = readStream( cfbFile, orderEntry );
700
701 for( const std::string& pageName : OrcadParsePageOrder( orderData, dialect ) )
702 {
703 if( pageEntries.count( pageName )
704 && std::find( ordered.begin(), ordered.end(), pageName ) == ordered.end() )
705 {
706 ordered.push_back( pageName );
707 }
708 }
709
710 orderKnown = true;
711 }
712 catch( const IO_ERROR& e )
713 {
714 warnFn( wxString::Format( _( "The page display order for schematic folder '%s' could not be "
715 "read (%s); pages are imported in name order." ),
716 wxString::FromUTF8( aFolderName ), e.What() ) );
717 ordered.clear();
718 }
719 }
720
721 if( orderKnown )
722 {
723 for( const std::string& pageName : available )
724 {
725 if( std::find( ordered.begin(), ordered.end(), pageName ) == ordered.end() )
726 ordered.push_back( pageName );
727 }
728 }
729 else
730 {
731 ordered = available;
732 std::sort( ordered.begin(), ordered.end() );
733 }
734
735 for( size_t pageIndex = 0; pageIndex < ordered.size(); ++pageIndex )
736 {
737 const std::string& pageName = ordered[pageIndex];
738
739 try
740 {
741 std::vector<char> pageData = readStream( cfbFile, pageEntries[pageName] );
742 ORCAD_RAW_PAGE page = OrcadParsePage( pageData, design.library.strings, warnFn, dialect );
743 page.sourcePageNumber = pageIndex + 1;
744 page.sourcePageCount = ordered.size();
745 aOutPages.push_back( std::move( page ) );
746 }
747 catch( const IO_ERROR& e )
748 {
749 warnFn( wxString::Format( _( "Page '%s' could not be parsed and was "
750 "skipped: %s" ),
751 wxString::FromUTF8( pageName ), e.What() ) );
752 }
753 }
754 };
755
756 parseFolderPages( rootFolder, folderEntries[rootFolder], design.pages );
757
758 // Root folder's Hierarchy stream holds whole occurrence tree (part refdes + nested blocks)
759 if( const CFB::COMPOUND_FILE_ENTRY* hierarchyEntry =
760 cfbFile.FindStream( folderEntries[rootFolder], { "Hierarchy", "Hierarchy" } ) )
761 {
762 std::vector<char> hierarchyData = readStream( cfbFile, hierarchyEntry );
763
764 design.occurrenceRoot = OrcadReadOccurrenceTree( hierarchyData, design.library.strings, warnFn, dialect );
765
766 // Block instance dbId -> child folder name, from occurrence tree
767 std::function<void( const ORCAD_OCC_SCOPE& )> collectLinks = [&]( const ORCAD_OCC_SCOPE& aScope )
768 {
769 for( const ORCAD_OCC_BLOCK& block : aScope.blocks )
770 {
771 hierarchyLinks[block.targetDbId] = block.childFolder;
772 collectLinks( block.scope );
773 }
774 };
775
776 collectLinks( design.occurrenceRoot );
777
778 }
779
780 if( design.pages.empty() )
781 THROW_IO_ERROR( _( "No schematic pages could be read from the design." ) );
782
783 // Parse pages of every block-reachable folder once; instantiated per block occurrence
784 // during conversion.
785 std::map<std::string, std::string> folderByLowerName;
786
787 for( const auto& folderEntry : folderEntries )
788 folderByLowerName.emplace( OrcadLower( folderEntry.first ), folderEntry.first );
789
790 for( const auto& [dbId, childName] : hierarchyLinks )
791 {
792 std::string key = OrcadLower( childName );
793
794 if( key == OrcadLower( rootFolder ) || design.childFolderPages.count( key ) )
795 continue;
796
797 auto childIt = folderByLowerName.find( key );
798
799 if( childIt != folderByLowerName.end() )
800 parseFolderPages( childIt->second, folderEntries[childIt->second], design.childFolderPages[key] );
801 }
802
803 for( const std::string& folder : folders )
804 {
805 std::string key = OrcadLower( folder );
806
807 if( key == OrcadLower( rootFolder ) || design.childFolderPages.count( key ) )
808 continue;
809
810 std::vector<ORCAD_RAW_PAGE>& pages = design.unreferencedFolderPages[key];
811 parseFolderPages( folder, folderEntries[folder], pages );
812
813 if( pages.empty() )
814 design.unreferencedFolderPages.erase( key );
815 }
816
817 for( ORCAD_RAW_PAGE& page : design.pages )
818 {
819 if( OrcadPageHasHierarchyBlocks( page ) )
820 design.hasHierarchyBlocks = true;
821
822 for( ORCAD_DRAWN_INSTANCE& block : page.blocks )
823 {
824 if( block.childName.empty() )
825 {
826 auto it = hierarchyLinks.find( block.dbId );
827
828 if( it != hierarchyLinks.end() )
829 block.childName = it->second;
830 }
831 }
832 }
833
834 readCisVariants( cfbFile, root, aProperties, design, m_reporter );
835 }
836 catch( const CFB::CFBException& e )
837 {
838 THROW_IO_ERROR( e.what() );
839 }
840
841 ORCAD_CONVERTER converter( design, aSchematic, m_reporter, m_progressReporter );
842
843 converter.Convert( rootSheet );
844
845 // KiCad's default variant is Capture's core design; the variant selected for import becomes current
846 for( const ORCAD_CIS_VARIANT& variant : design.cisVariants )
847 aSchematic->AddVariant( FromOrcadString( variant.name ) );
848
849 if( !design.cisCurrentVariant.empty() )
850 aSchematic->SetCurrentVariant( FromOrcadString( design.cisCurrentVariant ) );
851
852 aSchematic->Settings().m_ShowDNPMarkers = false;
853
854 auto [dashRatio, gapRatio] = OrcadDashRatios( design.library.versionMajor );
855 aSchematic->Settings().m_DashedLineDashRatio = dashRatio;
856 aSchematic->Settings().m_DashedLineGapRatio = gapRatio;
857
858
860
861 return rootSheet;
862}
863
864
865const std::vector<std::unique_ptr<LIB_SYMBOL>>& SCH_IO_ORCAD::loadOlbSymbols( const wxString& aLibraryPath )
866{
867 if( auto it = m_libCache.find( aLibraryPath ); it != m_libCache.end() )
868 return it->second;
869
870 ORCAD_WARN_FN warnFn = [this]( const wxString& aMsg )
871 {
872 if( m_reporter )
873 m_reporter->Report( aMsg, RPT_SEVERITY_WARNING );
874 };
875
876 ORCAD_DESIGN design;
877
878 try
879 {
880 ALTIUM_COMPOUND_FILE cfbFile( aLibraryPath );
881
882 const CFB::CompoundFileReader& reader = cfbFile.GetCompoundFileReader();
883 const CFB::COMPOUND_FILE_ENTRY* root = reader.GetRootEntry();
884
885 const CFB::COMPOUND_FILE_ENTRY* libraryEntry = cfbFile.FindStreamSingleLevel( root, "Library", true );
886
887 if( !libraryEntry )
888 THROW_IO_ERROR( _( "The file is not an OrCAD Capture library (no 'Library' stream)." ) );
889
890 design.library = OrcadParseLibrary( readStream( cfbFile, libraryEntry ) );
891
892 bool isV2 = design.library.versionMajor < 3;
893 ORCAD_DIALECT dialect{ isV2, design.library.versionMajor < 2 };
894
895 // Parse one stream, tolerating a single bad/oversized stream without aborting the
896 // library. Modern streams use preamble-framed cache reader; v2.0 uses short-prefix readers
897
898 auto parseStream = [&]( const CFB::COMPOUND_FILE_ENTRY* aEntry, const std::string& aStreamName,
899 bool aIsPackage, bool aIsCache = false )
900 {
901 std::map<std::string, ORCAD_SYMBOL_DEF> extraSymbols;
902 std::map<std::string, ORCAD_PACKAGE> extraPackages;
903
904 try
905 {
906 std::vector<char> data = readStream( cfbFile, aEntry );
907
908 if( aIsPackage )
909 {
910 ORCAD_DIALECT packageDialect{ isV2 || !isLongFramedPackageStream( data ),
911 dialect.shortDisplayProp };
912
913 OrcadParsePackageStream( data, design.library.strings, extraSymbols, extraPackages,
914 packageDialect );
915 }
916 else if( aIsCache )
917 {
918 OrcadParseCache( data, design.library.strings, warnFn, extraSymbols, extraPackages );
919 }
920 else
921 {
922 OrcadParseSymbolStream( data, design.library.strings, extraSymbols, dialect );
923 }
924 }
925 catch( const std::exception& e )
926 {
927 // Single bad stream must not abort whole library, but a library that quietly
928 // drops a part looks complete and is not.
929 warnFn( wxString::Format( _( "The library stream '%s' could not be read and was skipped (%s)." ),
930 wxString::FromUTF8( aStreamName ), wxString::FromUTF8( e.what() ) ) );
931 return;
932 }
933
934 OrcadMergeCacheStreams( design.symbols, design.packages, std::move( extraSymbols ),
935 std::move( extraPackages ) );
936 };
937
938 // Design 'Cache' usually empty in a library; read anyway for rare cached symbol
939 if( !isV2 )
940 {
941 if( const CFB::COMPOUND_FILE_ENTRY* cacheEntry = cfbFile.FindStreamSingleLevel( root, "Cache", true ) )
942 {
943 parseStream( cacheEntry, "Cache", false, true );
944 }
945 }
946
947 // Symbols and parts live one per stream under 'Symbols' and 'Packages' storages
948 for( const char* storageName : { "Symbols", "Packages" } )
949 {
950 bool isPackage = std::string( storageName ) == "Packages";
951
952 const CFB::COMPOUND_FILE_ENTRY* storage = cfbFile.FindStreamSingleLevel( root, storageName, false );
953
954 if( storage )
955 {
956 for( const auto& [streamName, entry] : enumChildren( cfbFile, storage, true ) )
957 {
958 // '$Types$' and similar helper streams are not symbol defs
959 if( !streamName.empty() && streamName.front() == '$' )
960 continue;
961
962 parseStream( entry, streamName, isPackage );
963 }
964 }
965 }
966 }
967 catch( const CFB::CFBException& e )
968 {
969 THROW_IO_ERROR( e.what() );
970 }
971 catch( const IO_ERROR& )
972 {
973 // Reportable errors (e.g. pre-2003 version gate) propagate
974 throw;
975 }
976 catch( const std::exception& e )
977 {
978 // Malformed Library stream can drive over-sized allocation; degrade to recovered symbols
979 warnFn( wxString::Format( _( "The OrCAD library could not be fully parsed (%s); some "
980 "symbols may be missing." ),
981 wxString::FromUTF8( e.what() ) ) );
982 }
983
984 ORCAD_CONVERTER converter( design, nullptr, m_reporter, m_progressReporter );
985
986 // Build fully before caching so mid-parse failure does not cache an empty library
987 std::vector<std::unique_ptr<LIB_SYMBOL>> built;
988
989 for( LIB_SYMBOL* symbol : converter.BuildSymbolLibrary() )
990 built.emplace_back( symbol );
991
992 return m_libCache[aLibraryPath] = std::move( built );
993}
994
995
996void SCH_IO_ORCAD::EnumerateSymbolLib( wxArrayString& aSymbolNameList, const wxString& aLibraryPath,
997 const std::map<std::string, UTF8>* )
998{
999 for( const std::unique_ptr<LIB_SYMBOL>& symbol : loadOlbSymbols( aLibraryPath ) )
1000 aSymbolNameList.Add( symbol->GetName() );
1001}
1002
1003
1004void SCH_IO_ORCAD::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList, const wxString& aLibraryPath,
1005 const std::map<std::string, UTF8>* )
1006{
1007 for( const std::unique_ptr<LIB_SYMBOL>& symbol : loadOlbSymbols( aLibraryPath ) )
1008 aSymbolList.push_back( symbol.get() );
1009}
1010
1011
1012LIB_SYMBOL* SCH_IO_ORCAD::LoadSymbol( const wxString& aLibraryPath, const wxString& aAliasName,
1013 const std::map<std::string, UTF8>* )
1014{
1015 for( const std::unique_ptr<LIB_SYMBOL>& symbol : loadOlbSymbols( aLibraryPath ) )
1016 {
1017 if( symbol->GetName() == aAliasName )
1018 return symbol.get();
1019 }
1020
1021 return nullptr;
1022}
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:114
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.
The caller owns the buffer.
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:149
void SetCurrentVariant(const wxString &aVariantName)
void AddVariant(const wxString &aVariantName)
SCHEMATIC_SETTINGS & Settings() const
bool IsValid() const
A simple test if the schematic is loaded, not a complete one.
Definition schematic.h:289
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:304
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
const KIID & GetUuid() const
Definition sch_screen.h:594
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
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:392
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)
Merge the results of a 'Packages/<name>' stream (locally modified parts) into the main cache maps: a ...
void OrcadParseSymbolStream(const std::vector< char > &aData, const std::vector< std::string > &aStrings, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, ORCAD_DIALECT aDialect)
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, ORCAD_DIALECT aDialect)
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, ORCAD_DIALECT aDialect)
Package streams contain counted PartCells and LibraryParts, followed by one Package.
Parsers for the DSN 'Cache' stream and the 'Packages/<name>' streams: symbol definitions with graphic...
std::map< uint32_t, ORCAD_CIS_PROPERTIES > OrcadCisParsePropertyUpdates(const std::vector< char > &aData)
Occurrence id -> property overrides.
std::map< uint32_t, bool > OrcadCisParseMemberships(const std::vector< char > &aData)
Occurrence id -> installed state of each part in a group.
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:93
std::map< std::string, std::map< uint32_t, ORCAD_CIS_PROPERTIES > > ORCAD_CIS_SCHEMATIC_INFO
Definition orcad_cis.h:31
std::map< std::string, std::string > ORCAD_CIS_PROPERTIES
Definition orcad_cis.h:30
std::pair< double, double > OrcadDashRatios(int aFormatVersionMajor)
ORCAD_LIBRARY_INFO OrcadParseLibrary(const std::vector< char > &aData)
The Library version selects the string-count width.
ORCAD_OCC_SCOPE OrcadReadOccurrenceTree(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &aWarn, ORCAD_DIALECT aDialect)
Returns occurrence references and child scopes.
ORCAD_RAW_PAGE OrcadParsePage(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &aWarn, ORCAD_DIALECT aDialect)
A modern body failure skips that structure.
std::vector< std::string > OrcadParseSchematicFolderOrder(const std::vector< char > &aData)
Unlisted Views storages can be stale.
std::vector< std::string > OrcadParsePageOrder(const std::vector< char > &aData, ORCAD_DIALECT aDialect)
Returns display order.
bool OrcadPageHasHierarchyBlocks(const ORCAD_RAW_PAGE &aPage)
True when the page contains hierarchical block instances (DrawnInstance, type 12).
Definition orcad_page.h:52
std::function< void(const wxString &aMsg)> ORCAD_WARN_FN
Coordinates use DBU with Y down.
std::string OrcadLower(std::string_view aText)
Fold ASCII letters only.
wxString FromOrcadString(const std::string &aText)
Use an 8-bit fallback if Windows-1252 decoding fails.
std::shared_ptr< PNS_LOG_VIEWER_OVERLAY > overlay
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_INFO
std::string OrcadNormalizeCfbName(const std::string &aName)
std::string OrcadNormalizeCfbName(const std::string &aName)
A CIS BOM variant, keyed by part occurrence id.
std::map< uint32_t, bool > installed
std::map< uint32_t, std::map< std::string, std::string > > props
Child-folder pages are instantiated for each occurrence, with that occurrence's references.
std::vector< ORCAD_CIS_VARIANT > cisVariants
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::string cisCurrentVariant
CIS variant selected for the import.
std::vector< ORCAD_RAW_PAGE > pages
root schematic folder pages
Legacy (pre-v3) files have short prefixes only, u16 string indices with a 0xFFFF sentinel and nested ...
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, uint32_t > partOccurrenceIds
dbId -> part occurrence id
One parsed 'Views/<folder>/Pages/<page>' stream, raw structure lists in stream order.
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)