KiCad PCB EDA Suite
Loading...
Searching...
No Matches
orcad_page.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
24
25#include <algorithm>
26#include <utility>
27
28#include <ki_exception.h>
29
33
34
35namespace
36{
37
38template <typename T>
39bool takeRecord( ORCAD_READ_RESULT& aResult, std::vector<T>& aTarget )
40{
41 if( T* record = std::get_if<T>( &aResult.record ) )
42 {
43 aTarget.push_back( std::move( *record ) );
44 return true;
45 }
46
47 return false;
48}
49
50
51template <typename T>
52void readStructureList( ORCAD_STRUCT_READER& aReader, std::vector<T>& aTarget )
53{
54 uint16_t count = aReader.Stream().ReadU16();
55
56 for( uint16_t i = 0; i < count; i++ )
57 {
59 takeRecord( result, aTarget );
60 }
61}
62
63} // namespace
64
65
66ORCAD_RAW_PAGE OrcadParsePage( const std::vector<char>& aData, const std::vector<std::string>& aStrings,
67 const ORCAD_WARN_FN& aWarn, ORCAD_DIALECT aDialect )
68{
69 ORCAD_STREAM stream( aData );
70 ORCAD_STRUCT_READER reader( stream, &aStrings, aWarn, aDialect );
71 ORCAD_RAW_PAGE page;
72
73 ORCAD_PREFIXES prefixes = reader.ReadPrefixes( ORCAD_ST_PAGE );
74
75 page.props = reader.PropsDict( prefixes );
76 page.name = stream.ReadLzt();
77 page.pageSize = stream.ReadLzt();
78
79 page.settings = OrcadParsePageSettings( stream );
80
81 readStructureList( reader, page.titleBlocks );
82
83 if( aDialect.legacy )
84 {
85 // Legacy pages list every net id with an empty name, then the bus net groups
86 uint16_t count = stream.ReadU16();
87
88 for( uint16_t i = 0; i < count; i++ )
89 {
90 stream.ReadU32();
91 stream.ReadLzt();
92 }
93
94 count = stream.ReadU16();
95
96 for( uint16_t i = 0; i < count; i++ )
97 {
99 group.id = stream.ReadU32();
100 group.name = stream.ReadLzt();
101
102 uint16_t memberCount = reader.ReadCount();
103
104 for( uint16_t j = 0; j < memberCount; j++ )
105 group.members.push_back( stream.ReadU32() );
106
107 page.netGroups.push_back( std::move( group ) );
108 }
109 }
110 else
111 {
112 // T0x34/T0x35 raw (not prefix-framed); read only to stay aligned.
113 uint16_t count = stream.ReadU16();
114
115 for( uint16_t i = 0; i < count; i++ )
116 page.netGroups.push_back( OrcadReadT0x34Raw( stream ) );
117
118 count = stream.ReadU16();
119
120 for( uint16_t i = 0; i < count; i++ )
121 page.netGroups.push_back( OrcadReadT0x35Raw( stream ) );
122 }
123
124 // Net table = lzt net name + u32 net db id; authoritative for names/junctions, wires key it.
125 uint16_t count = stream.ReadU16();
126
127 for( uint16_t i = 0; i < count; i++ )
128 {
129 std::string netName = stream.ReadLzt();
130 uint32_t netId = stream.ReadU32();
131 page.netAliases[netId].push_back( netName );
132 page.netmap[netId] = std::move( netName );
133 }
134
135 readStructureList( reader, page.wires );
136
137 // Placed parts; type 13 = part instance, type 12 = hier block instance.
138 count = stream.ReadU16();
139
140 for( uint16_t i = 0; i < count; i++ )
141 {
143
144 if( !takeRecord( result, page.instances ) )
145 takeRecord( result, page.blocks );
146 }
147
148 readStructureList( reader, page.ports );
149
150 // Each Global and OffPageConnector list entry followed by 5 bytes.
151 count = stream.ReadU16();
152
153 for( uint16_t i = 0; i < count; i++ )
154 {
156 takeRecord( result, page.globals );
157 stream.Skip( 5 );
158 }
159
160 count = stream.ReadU16();
161
162 for( uint16_t i = 0; i < count; i++ )
163 {
165 takeRecord( result, page.offpage );
166 stream.Skip( 5 );
167 }
168
169 readStructureList( reader, page.ercObjects );
170 readStructureList( reader, page.busEntries );
171 readStructureList( reader, page.graphics );
172
173 if( prefixes.end != 0 && stream.GetOffset() > prefixes.end )
174 THROW_IO_ERROR( wxS( "OrCAD page stream: page body exceeds its outer stop" ) );
175
176 return page;
177}
178
179
180static std::vector<std::string> pageOrderBody( ORCAD_STREAM& aStream )
181{
182 aStream.ReadLzt(); // schematic folder name
183 aStream.Skip( 4 );
184
185 uint16_t count = aStream.ReadU16();
186
187 std::vector<std::string> names;
188 names.reserve( count );
189
190 for( uint16_t i = 0; i < count; i++ )
191 names.push_back( aStream.ReadLzt() );
192
193 std::reverse( names.begin(), names.end() );
194
195 return names;
196}
197
198
199std::vector<std::string> OrcadParsePageOrder( const std::vector<char>& aData, ORCAD_DIALECT aDialect )
200{
201 ORCAD_STREAM stream( aData );
202 ORCAD_STRUCT_READER reader( stream, nullptr, nullptr, aDialect );
203
204 // The legacy root type was never checked and is not known
205 reader.ReadPrefixes( aDialect.legacy ? -1 : ORCAD_ST_SCH_LIB );
206
207 return pageOrderBody( stream );
208}
209
210
211std::vector<std::string> OrcadParseSchematicFolderOrder( const std::vector<char>& aData )
212{
213 ORCAD_STREAM stream( aData );
214 stream.Skip( 4 );
215
216 uint16_t count = stream.ReadU16();
217 std::vector<std::string> names;
218 names.reserve( count );
219
220 for( uint16_t i = 0; i < count; ++i )
221 {
222 names.push_back( stream.ReadLzt() );
223
224 if( stream.ReadU16() != 9 )
225 THROW_IO_ERROR( wxS( "OrCAD Views Directory: invalid folder record" ) );
226
227 stream.Skip( 20 );
228 }
229
230 if( !stream.AtEnd() )
231 THROW_IO_ERROR( wxS( "OrCAD Views Directory: trailing data" ) );
232
233 return names;
234}
235
236
237// Occurrence headers always use one long prefix, regardless of the normal type depth.
238static uint8_t readOccHeader( ORCAD_STRUCT_READER& aReader, int aExpectType,
239 std::map<std::string, std::string>* aProps = nullptr )
240{
241 ORCAD_PREFIXES prefixes = aReader.ReadPrefixes( aExpectType, ORCAD_STREAM::npos, 1 );
242
243 if( aProps )
244 *aProps = aReader.PropsDict( prefixes );
245
246 return static_cast<uint8_t>( prefixes.typeId );
247}
248
249
251
252
253static void readOccurrence( ORCAD_STRUCT_READER& aReader, ORCAD_OCC_SCOPE& aScope )
254{
255 ORCAD_STREAM& stream = aReader.Stream();
256 std::map<std::string, std::string> props;
257 readOccHeader( aReader, 0x42, &props );
258
259 uint32_t occurrenceId = stream.ReadU32();
260 uint32_t blockObjectId = stream.ReadU32();
261
262 if( !aReader.Dialect().legacy )
263 {
264 stream.ExpectByte( 0x42, wxS( "occurrence inner marker" ) );
265
266 stream.ReadU32(); // C (symbol-complexity correlated)
267 stream.ReadU32(); // D (zero observed)
268 }
269
270 std::string child = stream.ReadLzt(); // child folder name; empty for parts
271 std::string ref = stream.ReadLzt(); // occurrence refdes; empty when none
272
273 std::string unitRef = aReader.Resolve( aReader.ReadStrIdx() );
274
275 uint16_t pinCount = aReader.ReadCount();
276
277 for( uint16_t i = 0; i < pinCount; i++ )
278 {
279 readOccHeader( aReader, -1 ); // 0x44 scalar / 0x45 bus pin occurrence
280 stream.ReadU32(); // pin occurrence db id
281 stream.ReadU16(); // pin index
282 }
283
284 ORCAD_OCC_SCOPE nested = readOccScope( aReader );
285
286 if( !child.empty() )
287 {
288 ORCAD_OCC_BLOCK block;
289 block.targetDbId = blockObjectId;
290 block.childFolder = child;
291 block.scope = std::move( nested );
292 aScope.blocks.push_back( std::move( block ) );
293 }
294 else
295 {
296 if( !ref.empty() )
297 aScope.partRefs[blockObjectId] = ref;
298
299 if( !props.empty() )
300 aScope.partProps[blockObjectId] = std::move( props );
301
302 if( !unitRef.empty() )
303 aScope.partUnitRefs[blockObjectId] = std::move( unitRef );
304
305 aScope.partOccurrenceIds[blockObjectId] = occurrenceId;
306 }
307}
308
309
310// Scope = net occ (0x43), title-block occ (0x52), global/off-page occ (0x5b, u32-counted),
311// optional separator preamble, then part/block occ; only part/block kept, rest read to stay aligned.
313{
314 ORCAD_STREAM& stream = aReader.Stream();
315 ORCAD_STREAM::NEST_GUARD guard( stream, wxS( "occurrence scope" ) );
316
317 ORCAD_OCC_SCOPE scope;
318
319 uint16_t netCount = aReader.ReadCount();
320
321 for( uint16_t i = 0; i < netCount; i++ )
322 {
323 readOccHeader( aReader, 0x43 );
324 uint32_t occurrenceId = stream.ReadU32();
325 std::string name = stream.ReadLzt();
326 scope.netNames[occurrenceId] = std::move( name );
327 }
328
329 uint16_t titleBlockCount = aReader.ReadCount();
330
331 for( uint16_t i = 0; i < titleBlockCount; i++ )
332 {
333 readOccHeader( aReader, 0x52 );
334 stream.ReadU32();
335 stream.ReadU32();
336 }
337
338 // Legacy scopes have neither global occurrences nor the separator
339 if( !aReader.Dialect().legacy )
340 {
341 uint32_t globalCount = stream.ReadU32();
342
343 for( uint32_t i = 0; i < globalCount; i++ )
344 {
345 readOccHeader( aReader, 0x5b );
346 stream.ReadU32();
347 stream.ReadU32();
348 }
349
350 if( stream.AtPreamble() )
351 {
352 stream.Skip( 4 );
353 stream.Skip( stream.ReadU32() );
354 }
355 }
356
357 uint16_t occCount = aReader.ReadCount();
358
359 for( uint16_t i = 0; i < occCount; i++ )
360 readOccurrence( aReader, scope );
361
362 return scope;
363}
364
365
367{
368 ORCAD_STREAM& stream = aReader.Stream();
369
370 stream.ReadLzt(); // view name
371 stream.Skip( aReader.Dialect().legacy ? 5 : 7 ); // zeros
372
373 uint16_t powerCount = aReader.ReadCount();
374
375 for( uint16_t i = 0; i < powerCount; ++i )
376 {
377 readOccHeader( aReader, 0x44 );
378 stream.ReadU32();
379 stream.ReadLzt();
380 }
381
382 return readOccScope( aReader );
383}
384
385
386ORCAD_OCC_SCOPE OrcadReadOccurrenceTree( const std::vector<char>& aData, const std::vector<std::string>& aStrings,
387 const ORCAD_WARN_FN& aWarn, ORCAD_DIALECT aDialect )
388{
389 ORCAD_STREAM stream( aData );
390 ORCAD_STRUCT_READER reader( stream, &aStrings, nullptr, aDialect );
391
392 // A legacy tree has no scan recovery, so a framing error fails the import
393 if( aDialect.legacy )
394 {
395 ORCAD_OCC_SCOPE root = readOccTree( reader );
396
397 if( stream.Remaining() != 0 )
398 THROW_IO_ERRORF( wxS( "OrCAD legacy occurrence tree: %zu trailing bytes" ), stream.Remaining() );
399
400 return root;
401 }
402
403 // Modern streams open w/ type-0x42 long prefix (u8 0x42, u32 bodyLen, u32 0); legacy pre-preamble
404 // streams start at view-name lzt with only instance-annotated refs, so empty tree is correct.
405 if( aData.size() < 9 || (uint8_t) aData[0] != 0x42 || aData[5] || aData[6] || aData[7] || aData[8] )
406 {
407 return {};
408 }
409
410 try
411 {
412 stream.ReadU8(); // 0x42
413 stream.ReadU32(); // bodyLen
414 stream.ReadU32(); // 0
415
416 return readOccTree( reader );
417 }
418 catch( const IO_ERROR& e )
419 {
420 aWarn( wxString::Format( wxS( "The design occurrence tree could not be fully read (%s); "
421 "reference designators fall back to the placed instances." ),
422 e.What() ) );
423 return {};
424 }
425}
const char * name
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()
Bound shared parser recursion; entering a level above MAX_NESTING throws IO_ERROR.
The caller owns the buffer.
size_t Remaining() const
Bytes left; 0 when the cursor is at or past the end.
bool AtPreamble(size_t aAhead=0) const
True when the 4 preamble bytes FF E4 5C 39 sit at cursor + aAhead.
uint8_t ReadU8()
– scalars (little-endian, bounds-checked, throw IO_ERROR on overrun) ----—
static constexpr size_t npos
Generic invalid offset sentinel.
size_t GetOffset() const
uint16_t ReadU16()
void Skip(size_t aCount)
Advance the cursor; throws IO_ERROR when aCount exceeds the remaining bytes.
bool AtEnd() const
std::string ReadLzt()
– strings ----------------------------------------------------------------—
void ExpectByte(uint8_t aValue, const wxString &aWhat)
Consume one byte and require the given value.
uint32_t ReadU32()
ReadStructure can recover from a body error when the prefix supplies a valid end offset.
std::map< std::string, std::string > PropsDict(const ORCAD_PREFIXES &aPrefixes) const
Resolve the short-prefix (nameIdx, valueIdx) pairs.
ORCAD_PREFIXES ReadPrefixes(int aExpectedType=-1, size_t aEnclosingEnd=ORCAD_STREAM::npos, size_t aLongPrefixCount=ORCAD_STREAM::npos)
aLongPrefixCount overrides the type depth; aEnclosingEnd bounds all stops.
const ORCAD_DIALECT & Dialect() const
ORCAD_STREAM & Stream()
uint32_t ReadStrIdx()
A string-table index in the dialect's width.
std::string Resolve(uint32_t aIndex) const
String-table lookup; returns "" for out-of-range indices and the legacy sentinel.
uint16_t ReadCount()
A u16 list count.
ORCAD_READ_RESULT ReadStructure()
Always resumes at the prefix end, skipping unknown bodies and undecoded trailers.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_ERRORF(msg,...)
ORCAD_PAGE_SETTINGS OrcadParsePageSettings(ORCAD_STREAM &aStream)
Consumes 156 bytes; throws IO_ERROR on overrun.
static ORCAD_OCC_SCOPE readOccScope(ORCAD_STRUCT_READER &aReader)
static void readOccurrence(ORCAD_STRUCT_READER &aReader, ORCAD_OCC_SCOPE &aScope)
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.
static ORCAD_OCC_SCOPE readOccTree(ORCAD_STRUCT_READER &aReader)
static std::vector< std::string > pageOrderBody(ORCAD_STREAM &aStream)
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.
static uint8_t readOccHeader(ORCAD_STRUCT_READER &aReader, int aExpectType, std::map< std::string, std::string > *aProps=nullptr)
std::function< void(const wxString &aMsg)> ORCAD_WARN_FN
Coordinates use DBU with Y down.
@ ORCAD_ST_PAGE
@ ORCAD_ST_SCH_LIB
ORCAD_NET_GROUP OrcadReadT0x34Raw(ORCAD_STREAM &aStream)
T0x34 records have no prefixes; consume their fixed layout to preserve alignment.
ORCAD_NET_GROUP OrcadReadT0x35Raw(ORCAD_STREAM &aStream)
T0x35 = the T0x34 raw layout followed by u16 n and 4 * n bytes.
Legacy (pre-v3) files have short prefixes only, u16 string indices with a 0xFFFF sentinel and nested ...
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
std::map< uint32_t, std::map< std::string, std::string > > partProps
dbId -> occurrence properties
std::map< uint32_t, std::string > netNames
occurrence net id -> effective net name
std::map< uint32_t, std::string > partRefs
type-13 dbId -> occurrence refdes
std::map< uint32_t, std::string > partUnitRefs
dbId -> package unit reference
Prefix lengths supply structure bounds.
int typeId
ORCAD_ST value (u8 in the stream)
size_t end
offset right after the whole structure
One parsed 'Views/<folder>/Pages/<page>' stream, raw structure lists in stream order.
std::vector< ORCAD_GRAPHIC_INST > ports
std::map< uint32_t, std::vector< std::string > > netAliases
every name recorded for a net db id
std::string name
std::vector< ORCAD_GRAPHIC_INST > globals
placed power symbols
std::map< uint32_t, std::string > netmap
net db id -> net name
std::map< std::string, std::string > props
std::vector< ORCAD_WIRE > wires
std::vector< ORCAD_NET_GROUP > netGroups
bus net id -> member net ids
std::vector< ORCAD_DRAWN_INSTANCE > blocks
hierarchical blocks (detection only)
std::vector< ORCAD_PLACED_INSTANCE > instances
std::vector< ORCAD_GRAPHIC_INST > graphics
free comment text/shapes/images
std::vector< ORCAD_GRAPHIC_INST > offpage
off-page connectors
std::vector< ORCAD_GRAPHIC_INST > titleBlocks
std::vector< ORCAD_BUS_ENTRY > busEntries
std::string pageSize
page-size name string, e.g. "B"
ORCAD_PAGE_SETTINGS settings
std::vector< ORCAD_GRAPHIC_INST > ercObjects
saved design-rule-check markers
ORCAD_RECORD_VARIANT record
wxString result
Test unit parsing edge cases and error handling.