31#include <wx/translation.h>
39bool isPrimType(
int aType )
54 default:
return false;
59bool isSymbolType(
int aTypeId )
73 default:
return false;
78std::optional<ORCAD_PRIMITIVE> readPrimitiveBody(
ORCAD_STREAM& aStream,
int aType );
83std::optional<ORCAD_PRIMITIVE> readSymbolVector(
ORCAD_STREAM& aStream )
96 uint16_t count = aStream.
ReadU16();
98 for( uint16_t i = 0; i < count; i++ )
102 aStream.
ExpectByte( 0x00, wxS(
"symbol vector prim pad" ) );
104 if( !isPrimType( t ) )
107 std::optional<ORCAD_PRIMITIVE> child;
111 child = readSymbolVector( aStream );
115 int t2 = aStream.
ReadU8();
120 child = readPrimitiveBody( aStream, t );
124 group.children.push_back( std::move( *child ) );
191 uint16_t pointCount = aStream.
ReadU16();
193 for( uint16_t i = 0; i < pointCount; i++ )
198 aPrim.
points.push_back( pt );
227 default:
return false;
232std::optional<ORCAD_PRIMITIVE> readPrimitiveBody(
ORCAD_STREAM& aStream,
int t1 )
235 size_t size = aStream.
ReadU32();
236 size_t available = aStream.
Size() - start;
238 if( size > available )
239 THROW_IO_ERRORF( wxS(
"primitive type %d at 0x%zx exceeds its enclosing structure" ), t1, start );
243 size_t recordSize = size;
248 std::optional<ORCAD_PRIMITIVE> prim;
253 static const uint8_t
pad[4] = { 0x00, 0x00, 0x00, 0x00 };
254 aStream.
Expect(
pad, 4, wxS(
"primitive pad" ) );
269 size_t to = start + size;
272 THROW_IO_ERRORF( wxS(
"OLE primitive at 0x%zx is shorter than its header" ), start );
275 p.
data.assign( aStream.
Data() + from, aStream.
Data() + to );
278 prim = std::move( p );
280 else if( readPrimitiveFields( aStream, t1, p ) )
282 prim = std::move( p );
289 aStream.
Seek( start + recordSize );
293 size_t physicalSize = aStream.
GetOffset() - start;
295 : size == physicalSize || size + 8 == physicalSize;
299 THROW_IO_ERRORF( wxS(
"primitive type %d stores %zu bytes but consumes %zu" ), t1, size, physicalSize );
320 int16_t propertyCount = stream.
ReadI16();
321 stream.
Skip( 4 * std::max<int>( propertyCount, 0 ) );
330 for( uint16_t i = 0; i < count; i++ )
332 int type = stream.
ReadU8();
333 stream.
ExpectByte( 0x00, wxS(
"legacy vector prim pad" ) );
334 group.children.push_back( readLegacyPrimitive( aReader, type ) );
347 return readLegacySymbolVector( aReader );
351 if( !readPrimitiveFields( aReader.
Stream(), aType, prim ) )
353 THROW_IO_ERRORF( wxS(
"OrCAD legacy primitive: unhandled type %d at 0x%zx" ), aType,
369 return readLegacyPrimitive( aReader, t1 );
371 if( !isPrimType( t1 ) )
375 return readSymbolVector( stream );
382 return readPrimitiveBody( stream, t1 );
391 if( stream.
PeekU8() == 0x00 )
411 uint32_t portType = stream.
ReadU32();
418 stream.
ExpectByte(
static_cast<uint8_t
>( pfx.
typeId ), wxS(
"symbol pin type echo" ) );
434 std::vector<size_t> stops = aPrefixes.
stops;
435 std::sort( stops.begin(), stops.end() );
445 uint16_t primCount = aReader.
ReadCount();
447 for( uint16_t i = 0; i < primCount; i++ )
452 sym.
primitives.push_back( std::move( *prim ) );
455 static const uint8_t zeroTrailer[8] = {};
457 if( i + 1 < primCount && stream.
PeekMatches( zeroTrailer,
sizeof( zeroTrailer ) ) )
462 auto nextStopIt = std::upper_bound( stops.begin(), stops.end(), stream.
GetOffset() );
477 else if( nextStopIt != stops.end() )
479 size_t nextStop = *nextStopIt;
480 size_t gap = nextStop - stream.
GetOffset();
484 stream.
Seek( nextStop - 8 );
491 if( x1 <= x2 && y1 <= y2 && x2 - x1 <= 4000 && y2 - y1 <= 4000 )
503 stream.
Seek( nextStop );
511 for( uint16_t i = 0; i < pinCount; i++ )
518 sym.
pins.push_back( std::move( *
pin ) );
522 uint16_t propCount = aReader.
ReadCount();
524 for( uint16_t i = 0; i < propCount; i++ )
531 size_t tailStart = aPrefixes.
stops[1];
533 if( stream.
GetOffset() == tailStart && tailStart < aPrefixes.
end )
535 std::string implementationPath = stream.
ReadLzt();
536 std::string implementation = stream.
ReadLzt();
545 if( !implementationPath.empty() )
546 sym.
props[
"Implementation Path"] = std::move( implementationPath );
548 if( !implementation.empty() )
549 sym.
props[
"Implementation"] = std::move( implementation );
579 uint32_t dbId = stream.
ReadU32();
590 uint8_t orientation = stream.
ReadU8();
600 THROW_IO_ERRORF( wxS(
"drawn instance nested flag %d at 0x%zx" ),
static_cast<int>(
flag ),
615 bool quarterTurn = ( orientation & 1 ) != 0;
616 block.
w = quarterTurn ? bbox.
y2 - bbox.
y1 : bbox.
x2 - bbox.
x1;
617 block.
h = quarterTurn ? bbox.
x2 - bbox.
x1 : bbox.
y2 - bbox.
y1;
628 uint32_t implementationIdx = aReader.
ReadStrIdx();
631 block.
props[
"Implementation"] = aReader.
Resolve( implementationIdx );
636 std::vector<size_t> stops = aPrefixes.
stops;
637 std::sort( stops.begin(), stops.end() );
639 if( stops.size() >= 2 && stops[stops.size() - 2] >= stream.
GetOffset() )
640 stream.
Seek( stops[stops.size() - 2] );
649 std::vector<ORCAD_PIN_INST> pinInsts;
651 for( uint16_t i = 0; i < pinCount; i++ )
656 pinInsts.push_back( std::move( *
pin ) );
666 const std::vector<ORCAD_PIN_INST>& aPlaced )
669 std::set<std::pair<int, int>> placedPoints;
670 std::set<std::pair<int, int>> definitionPoints;
673 placedPoints.emplace(
pin.x,
pin.y );
676 definitionPoints.emplace(
pin.hotptX,
pin.hotptY );
678 bool useDefinitionGeometry = placedPoints.size() <= 1 && definitionPoints.size() > 1;
680 std::vector<ORCAD_BLOCK_PIN> pins;
682 for(
size_t i = 0; i < aPlaced.size(); i++ )
688 auto definition = std::lower_bound( aDefinition.
pins.begin(), aDefinition.
pins.end(), slot,
691 return aPin.position < aSlot;
694 if( definition == aDefinition.
pins.end() || definition->position != slot )
698 pin.name = definition->name;
699 pin.portType = definition->portType;
704 if( useDefinitionGeometry )
707 definition->hotptX - box.
x1, definition->hotptY - box.
y1 );
712 pins.push_back( std::move(
pin ) );
730 for( uint16_t i = 0; i < pinCount; i++ )
733 static const uint8_t emptyMarker[2] = { 0xFF, 0xFF };
767 uint16_t deviceCount = aReader.
ReadCount();
769 for( uint16_t i = 0; i < deviceCount; i++ )
784 std::map<std::string, ORCAD_SYMBOL_DEF>& aSymbols,
785 std::map<std::string, ORCAD_PACKAGE>& aPackages )
789 if( isSymbolType( aPrefixes.
typeId ) )
799 aReader.
SkipStructure( aPrefixes, wxString::Format( wxS(
"type %d" ), aPrefixes.
typeId ) );
823 std::string implementationPath = aStream.
ReadLzt();
825 if( !implementationPath.empty() )
826 aDef.
props[
"Implementation Path"] = std::move( implementationPath );
829 std::string refDesPrefix = aStream.
ReadLzt();
839 std::map<std::string, ORCAD_SYMBOL_DEF>& aSymbols,
840 std::map<std::string, ORCAD_PACKAGE>& aPackages )
850 uint16_t viewCount = aReader.
ReadCount();
852 for( uint16_t i = 0; i < viewCount; ++i )
866 if( !def.
name.empty() )
878void OrcadParseCache(
const std::vector<char>& aData,
const std::vector<std::string>& aStrings,
879 const ORCAD_WARN_FN& aWarn, std::map<std::string, ORCAD_SYMBOL_DEF>& aSymbols,
880 std::map<std::string, ORCAD_PACKAGE>& aPackages,
ORCAD_DIALECT aDialect )
893 for(
int section = 0; section < 4; ++section )
895 uint16_t groupCount = reader.
ReadCount();
900 uint16_t variantCount = reader.
ReadCount();
902 for( uint16_t variant = 0; variant < variantCount; ++variant )
908 int typeId = stream.
ReadU8();
910 stream.
ExpectByte( 0, wxS(
"Cache entry pad" ) );
920 THROW_IO_ERRORF( wxS(
"OrCAD Cache: section %d cannot hold structure type %d" ), section,
926 if( prefixes.
end == 0 || prefixes.
end > stream.
Size() )
927 THROW_IO_ERROR( wxS(
"OrCAD Cache: entry frame runs past the stream" ) );
939 aWarn( wxString::Format( wxS(
"cache struct type %d at 0x%zx: %s" ), typeId,
949 if( !stream.
AtEnd() )
954 if( aWarn && aDialect.
legacy )
956 aWarn( wxString::Format(
_(
"The legacy design cache could not be read past 0x%zx (%s); the "
957 "remaining symbols fall back to placeholders." ),
962 aWarn( wxString::Format( wxS(
"OrCAD Cache: stopped at 0x%zx (%s); symbols after this point fall "
963 "back to synthesized placeholders" ),
971 std::map<std::string, ORCAD_SYMBOL_DEF>& aSymbols,
ORCAD_DIALECT aDialect )
987 if( !def.
name.empty() )
993 if( !isSymbolType( prefixes.
typeId ) )
994 THROW_IO_ERRORF( wxS(
"OrCAD symbol stream: expected a symbol structure, got type %d" ), prefixes.
typeId );
996 std::map<std::string, ORCAD_PACKAGE> unusedPackages;
999 if( !stream.
AtEnd() )
1005 std::map<std::string, ORCAD_SYMBOL_DEF>& aSymbols,
1006 std::map<std::string, ORCAD_PACKAGE>& aPackages,
ORCAD_DIALECT aDialect )
1010 std::string refDesPrefix;
1012 uint16_t partCellCount = reader.
ReadCount();
1014 if( partCellCount > 1000 )
1015 THROW_IO_ERROR( wxS(
"OrCAD package stream: implausible PartCell count" ) );
1017 for( uint16_t i = 0; i < partCellCount; ++i )
1021 std::map<std::string, std::string> cellProps = reader.
PropsDict( cellPfx );
1025 uint16_t viewCount = reader.
ReadCount();
1027 if( viewCount > 1000 )
1028 THROW_IO_ERROR( wxS(
"OrCAD package stream: implausible view count" ) );
1030 for( uint16_t view = 0; view < viewCount; ++view )
1036 THROW_IO_ERROR( wxS(
"OrCAD package stream: PartCell exceeds its frame" ) );
1041 uint16_t symbolCount = reader.
ReadCount();
1043 if( symbolCount > 1000 )
1044 THROW_IO_ERROR( wxS(
"OrCAD package stream: implausible LibraryPart count" ) );
1046 std::string cellRefDesPrefix;
1048 for( uint16_t symbolIndex = 0; symbolIndex < symbolCount; ++symbolIndex )
1056 for(
const auto& [
name, value] : cellProps )
1057 symbol.
props.try_emplace(
name, value );
1059 if( !aDialect.
legacy || !symbol.
name.empty() )
1063 if( refDesPrefix.empty() )
1064 refDesPrefix = cellRefDesPrefix;
1068 ORCAD_PACKAGE package = OrcadReadPackage( reader, packagePfx );
1073 if( package.refDes.empty() )
1074 package.refDes = refDesPrefix;
1076 if( !aDialect.
legacy || !package.name.empty() )
1082 std::map<std::string, ORCAD_PACKAGE>& aPackages,
1083 std::map<std::string, ORCAD_SYMBOL_DEF>&& aExtraSymbols,
1084 std::map<std::string, ORCAD_PACKAGE>&& aExtraPackages )
1086 for(
auto& [
name, sym] : aExtraSymbols )
1088 auto it = aSymbols.find(
name );
1090 if( it == aSymbols.end() )
1092 aSymbols.emplace(
name, std::move( sym ) );
1097 std::vector<ORCAD_SYMBOL_DEF> extraVariants = std::move( sym.variants );
1098 sym.variants.clear();
1100 if( it->second.generalFlags < 0 && sym.generalFlags >= 0 )
1101 it->second.generalFlags = sym.generalFlags;
1103 it->second.variants.push_back( std::move( sym ) );
1106 it->second.variants.push_back( std::move( variant ) );
1110 for(
auto& [
name, pkg] : aExtraPackages )
1112 auto it = aPackages.find(
name );
1114 if( it == aPackages.end() )
1116 aPackages.emplace(
name, std::move( pkg ) );
1120 std::vector<ORCAD_PACKAGE> variants = std::move( pkg.variants );
1121 pkg.variants.clear();
1122 it->second.variants.push_back( std::move( pkg ) );
1125 it->second.variants.push_back( std::move( variant ) );
1132 const std::map<std::string, ORCAD_SYMBOL_DEF>& aMetadataSymbols )
1134 for(
const auto& [
name, metadata] : aMetadataSymbols )
1136 auto symbol = aSymbols.find(
name );
1138 if( symbol != aSymbols.end() && symbol->second.generalFlags < 0 && metadata.generalFlags >= 0 )
1139 symbol->second.generalFlags = metadata.generalFlags;
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()
virtual const wxString Problem() const
what was the problem?
Limit reads to one record so a malformed body cannot consume the next record.
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.
int PeekU8(size_t aAhead=0) const
– non-throwing lookahead ---------------------------------------------------—
void SkipOptionalPreambleBlock()
Legacy primitives have no trailing preamble.
uint8_t ReadU8()
– scalars (little-endian, bounds-checked, throw IO_ERROR on overrun) ----—
static constexpr size_t npos
Generic invalid offset sentinel.
std::vector< uint8_t > ReadBytes(size_t aCount)
– buffers / cursor ---------------------------------------------------------—
void Skip(size_t aCount)
Advance the cursor; throws IO_ERROR when aCount exceeds the remaining bytes.
std::string ReadLzt()
– strings ----------------------------------------------------------------—
const uint8_t * Data() const
Raw buffer access for bounded lexical lookahead and payload extraction.
bool PeekMatches(const uint8_t *aBytes, size_t aCount, size_t aAhead=0) const
Returns false if fewer than aCount bytes remain; does not advance the cursor.
void Seek(size_t aOffset)
Set the absolute cursor position; throws IO_ERROR when aOffset exceeds Size().
void ExpectByte(uint8_t aValue, const wxString &aWhat)
Consume one byte and require the given value.
void Expect(const uint8_t *aBytes, size_t aCount, const wxString &aWhat)
– validated reads ------------------------------------------------------------—
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.
void SkipStructure(const ORCAD_PREFIXES &aPrefixes, const wxString &aWhat)
Throws IO_ERROR if the end is unknown or behind the cursor.
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
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,...)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
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.
ORCAD_DEVICE OrcadReadDevice(ORCAD_STRUCT_READER &aReader)
std::optional< ORCAD_SYMBOL_PIN > OrcadReadSymbolPin(ORCAD_STRUCT_READER &aReader)
A zero byte marks an empty pin slot and returns nullopt.
static void storeLegacyCacheRecord(ORCAD_STRUCT_READER &aReader, int aTypeId, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages)
static bool cacheSectionAcceptsType(int aSection, int aTypeId)
std::optional< ORCAD_PRIMITIVE > OrcadReadPrimitive(ORCAD_STRUCT_READER &aReader)
Consumes one primitive and its optional preamble.
std::vector< ORCAD_BLOCK_PIN > OrcadResolveBlockPins(int aOrient, int aX1, int aY1, const ORCAD_SYMBOL_DEF &aDefinition, const std::vector< ORCAD_PIN_INST > &aPlaced)
Pair placed pin records with the inline definition by pin index.
ORCAD_PACKAGE OrcadReadPackage(ORCAD_STRUCT_READER &aReader, const ORCAD_PREFIXES &aPrefixes)
ORCAD_DRAWN_INSTANCE OrcadReadDrawnInstance(ORCAD_STRUCT_READER &aReader, const ORCAD_PREFIXES &aPrefixes)
static void storeFramedRecord(ORCAD_STRUCT_READER &aReader, const ORCAD_PREFIXES &aPrefixes, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages)
static std::string readLegacyLibraryPartTail(ORCAD_STREAM &aStream, ORCAD_SYMBOL_DEF &aDef)
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.
ORCAD_SYMBOL_DEF OrcadReadSymbolDef(ORCAD_STRUCT_READER &aReader, const ORCAD_PREFIXES &aPrefixes, bool aWithPins)
Read a symbol definition body (LibraryPart / GlobalSymbol / PortSymbol / OffPageSymbol / TitleBlockSy...
Parsers for the DSN 'Cache' stream and the 'Packages/<name>' streams: symbol definitions with graphic...
VECTOR2I OrcadTransformPoint(int aOrient, int aWidth, int aHeight, int aBaseX, int aBaseY, int aPx, int aPy)
Parts use the instance anchor as the base.
std::function< void(const wxString &aMsg)> ORCAD_WARN_FN
Coordinates use DBU with Y down.
T & OrcadAddOrVariant(std::map< std::string, T > &aMap, T &&aRecord)
Store aRecord under its name, or append it as a variant of the entry already there.
@ ORCAD_PRIM_COMMENT_TEXT
@ ORCAD_PRIM_BITMAP
plain DIB (BITMAPINFOHEADER + pixels)
@ ORCAD_PRIM_OLE_IMAGE
OLE compound document embed.
@ ORCAD_PRIM_SYMBOL_VECTOR
nested prefix-framed vector graphic
@ ORCAD_ST_TITLEBLOCK_SYMBOL
@ ORCAD_ST_SYMBOL_PIN_SCALAR
@ ORCAD_ST_GLOBAL_SYMBOL
power symbol definition
@ ORCAD_ST_BOOKMARK_SYMBOL
@ ORCAD_ST_SYMBOL_PIN_BUS
@ ORCAD_ST_OFFPAGE_SYMBOL
std::vector< ORCAD_DISPLAY_PROP > OrcadReadDisplayPropList(ORCAD_STRUCT_READER &aReader)
Keep only display properties that ReadStructure decodes.
Axis-aligned box in OrCAD DBU; corner order as stored (not normalized).
One interface pin of a hierarchical block, at its absolute page position.
Device unit names omit the view suffix.
std::vector< std::string > pinNumbers
std::vector< bool > pinIgnore
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 name
intrinsic Name property used for flat-net scoping
int x1
block rectangle top-left, page DBU
std::vector< ORCAD_BLOCK_PIN > pins
std::vector< ORCAD_DISPLAY_PROP > displayProps
std::map< std::string, std::string > props
std::map< std::string, std::string > props
Part-level properties shared by every placement (Description, Tolerance, ...).
std::vector< ORCAD_DEVICE > devices
Pin positions are absolute page connection points.
Integer point in OrCAD DBU.
Prefix lengths supply structure bounds.
std::vector< size_t > stops
< (from the outermost long prefix); < 0 when unknown
int typeId
ORCAD_ST value (u8 in the stream)
size_t end
offset right after the whole structure
size_t start
stream offset where the chain began
Primitive byte lengths can include or exclude the eight-byte size envelope.
int fillStyle
0 solid, 1 none, 2 hatch pattern
std::string text
kind == TEXT
std::vector< ORCAD_POINT > points
polygon/polyline/bezier vertices
std::optional< ORCAD_POINT > textBoundsStart
std::vector< uint8_t > data
kind == IMAGE: raw embedded payload
int lineStyle
0 solid, 1 dash, 2 dot, 3 dash-dot, 4 dash-dot-dot, 5 default
std::optional< ORCAD_POINT > start
arc start point
std::optional< ORCAD_POINT > end
arc end point
int lineWidth
Capture width enum: 0 thin, 1 medium, 2 wide, 3 default.
The bounding box occupies the final eight bytes before the next prefix stop.
std::string name
cache name, e.g. "C.Normal"
std::vector< ORCAD_SYMBOL_PIN > pins
std::vector< ORCAD_PRIMITIVE > primitives
std::map< std::string, std::string > props
int typeId
ORCAD_ST value.
std::optional< ORCAD_BBOX > bbox
symbol-space body box
int generalFlags
LibraryPart GeneralProperties flags (-1 = absent); bit0 = pin names visible, bit1 = pin text rotates ...
Pin coordinates use symbol space with Y down.
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< int32_t > VECTOR2I