34#include <unordered_map>
35#include <unordered_set>
38#include <wx/strconv.h>
45 static constexpr std::array<uint16_t, 16> JUSTIFICATION_BY_NIBBLE_0 = { 0, 4, 1, 5, 8, 12, 9, 13,
46 2, 6, 3, 7, 10, 14, 11, 15 };
47 static constexpr std::array<uint16_t, 16> JUSTIFICATION_BY_NIBBLE_90 = { 0, 4, 2, 6, 8, 12, 10, 14,
48 1, 5, 3, 7, 9, 13, 11, 15 };
51 uint16_t terminalJustification( uint16_t aNibble,
bool aRotated )
53 return aRotated ? JUSTIFICATION_BY_NIBBLE_90[aNibble] : JUSTIFICATION_BY_NIBBLE_0[aNibble];
57 constexpr size_t OUTER_DIRECTORY_OFFSET = 0x20;
58 constexpr size_t OUTER_DESCRIPTOR_BYTES = 28;
59 constexpr size_t OUTER_USED_BYTES_OFFSET = 12;
60 constexpr size_t SHEET_HEADER_BYTES = 20;
61 constexpr size_t SHEET_DESCRIPTOR_COUNT = 24;
62 constexpr size_t SHEET_DESCRIPTOR_BYTES = 28;
63 constexpr size_t SHEET_COUNT_OFFSET = 12;
64 constexpr size_t SHEET_USED_BYTES_OFFSET = 16;
65 constexpr size_t SHEET_RECORD_BYTES = 48;
66 constexpr size_t TEXT_RECORD_BYTES = 32;
67 constexpr size_t SYMBOL_RECORD_BYTES = 80;
68 constexpr size_t SYMBOL_PIECE_BYTES = 6;
69 constexpr size_t SYMBOL_VERTEX_BYTES = 6;
70 constexpr size_t SYMBOL_ARC_BYTES = 14;
71 constexpr size_t USED_DECAL_BYTES = 108;
72 constexpr size_t TERMINAL_BYTES = 26;
73 constexpr size_t PART_TYPE_BYTES = 76;
74 constexpr size_t GATE_BYTES = 12;
75 constexpr size_t PIN_BYTES = 24;
76 constexpr size_t SIGNAL_PIN_BYTES = 64;
77 constexpr size_t NET_MEMBERSHIP_BYTES = 2;
78 constexpr size_t NET_RECORD_BYTES = 88;
79 constexpr size_t BUS_RECORD_BYTES = 44;
80 constexpr size_t JUNCTION_RECORD_BYTES = 12;
81 constexpr size_t OFFPAGE_RECORD_BYTES = 32;
82 constexpr size_t CONNECTION_RECORD_BYTES = 40;
83 constexpr size_t CONNECTION_VERTEX_BYTES = 8;
84 constexpr size_t NET_NAME_RECORD_BYTES = 48;
85 constexpr size_t PLACEMENT_GROUP_BYTES = 24;
86 constexpr size_t ATTRIBUTE_OFFSET_BYTES = 4;
87 constexpr size_t FONT_RECORD_BYTES = 36;
88 constexpr uint32_t DEFAULT_CODE_PAGE = 1252;
90 struct GLOBAL_NET_RECORD
92 SOURCE_PROVENANCE source;
94 uint32_t preservedIdentity = 0;
95 uint32_t preservedRelationship = 0xFFFFFFFF;
96 uint32_t membershipStart = 0;
97 uint16_t membershipCount = 0;
98 uint32_t aliasStringOffset = 0xFFFFFFFF;
99 uint16_t aliasCount = 0;
100 uint16_t kindFlags = 0;
101 bool tombstone =
false;
102 std::vector<SOURCE_STRING> aliasMembers;
106 struct CONNECTIVITY_GLOBALS
108 size_t membershipBase = 0;
109 uint32_t membershipCount = 0;
111 uint32_t fontCount = 0;
112 std::vector<uint16_t> membershipSheets;
113 std::vector<GLOBAL_NET_RECORD> nets;
116 struct PLACEMENT_LAYOUT
120 size_t placementBytes;
121 size_t placedPinBytes;
124 size_t componentIdentity;
125 size_t componentGroup;
136 size_t referenceFont;
138 size_t referenceField;
139 size_t referenceFieldAngle;
140 size_t partTypeField;
141 size_t partTypeFieldAngle;
142 size_t referenceHeight;
143 size_t partTypeHeight;
144 size_t referenceWidth;
145 size_t partTypeWidth;
146 size_t itemVisibility;
147 size_t placedPinOrdinal;
151 size_t customJustification;
152 size_t customAttributeIndex;
155 size_t customDisplayFlags;
159 constexpr PLACEMENT_LAYOUT PLACEMENT_LAYOUTS[] = { []
161 PLACEMENT_LAYOUT layout{};
162 layout.version = 0x000C;
167 .placementBytes = 136,
168 .placedPinBytes = 12,
171 .componentIdentity = 0x18,
172 .componentGroup = 0x1C,
185 .referenceField = 0x28,
186 .referenceFieldAngle = 0x2C,
187 .partTypeField = 0x30,
188 .partTypeFieldAngle = 0x34,
189 .referenceHeight = 0x50,
190 .partTypeHeight = 0x52,
191 .referenceWidth = 0x58,
192 .partTypeWidth = 0x59,
193 .itemVisibility = 0x87,
194 .placedPinOrdinal = 4,
198 .customJustification = 14,
199 .customAttributeIndex = 16,
202 .customDisplayFlags = 21,
203 .customTail = 22 } };
205 const PLACEMENT_LAYOUT& placementLayout( uint16_t aVersion )
207 auto layout = std::ranges::find_if( PLACEMENT_LAYOUTS,
208 [&](
const PLACEMENT_LAYOUT& aLayout )
210 return aLayout.version == aVersion;
213 if( layout == std::end( PLACEMENT_LAYOUTS ) )
214 THROW_IO_ERROR( wxString::Format( wxS(
"unsupported PADS placement layout v0x%04X" ), aVersion ) );
220 size_t outerControllerOffset(
const PADS_SCH_SDB& aSdb,
size_t aController )
224 for(
size_t controller = 1; controller < aController; ++controller )
225 offset += aSdb.
Pools()[controller].usedBytes;
231 SOURCE_PROVENANCE sourceAt(
const wxString& aFile, uint16_t aVersion,
const wxString& aObjectClass,
int aController,
232 size_t aRecord,
size_t aOffset,
size_t aLength,
int aSheet )
234 return { aFile, aVersion, aObjectClass, aController, aRecord, aOffset, aLength, aSheet };
238 [[noreturn]]
void throwDecodeError(
const SOURCE_PROVENANCE& aSource,
const wxString& aMessage )
244 int64_t decodeCoordinate( uint16_t aRaw )
246 return static_cast<int64_t
>( aRaw ) * 4 - 198144;
250 int64_t decodeDatabaseCoordinate( int32_t aRaw,
const SOURCE_PROVENANCE& aSource )
252 if( aRaw < std::numeric_limits<int16_t>::min() || aRaw > std::numeric_limits<int16_t>::max() )
253 throwDecodeError( aSource, wxS(
"embedded OLE database coordinate is not sign-extended 16-bit" ) );
255 return decodeCoordinate(
static_cast<uint16_t
>(
static_cast<int16_t
>( aRaw ) ) );
259 int64_t decodeLocalCoordinate( uint16_t aRaw )
261 return static_cast<int64_t
>(
static_cast<int16_t
>( aRaw ) ) * 4;
265 int64_t decodeTerminalCoordinate( uint16_t aRaw )
267 return static_cast<int64_t
>(
static_cast<int16_t
>( aRaw ) ) * 4;
271 SOURCE_STRING decodeFixedString(
const std::vector<uint8_t>& aBytes,
size_t aOffset,
size_t aBytesAvailable,
274 size_t end = aOffset;
276 while(
end < aOffset + aBytesAvailable && aBytes[
end] != 0 )
279 if(
end == aOffset + aBytesAvailable )
280 throwDecodeError( aSource, wxS(
"unterminated fixed string" ) );
283 DEFAULT_CODE_PAGE, aSource, aDiagnostics );
291 property.name.source = aSource;
292 property.value.text = aValue;
293 property.value.source = aSource;
295 property.source = aSource;
302 return aLeft.x == aRight.x && aLeft.y == aRight.y;
308 return aLeft.height == aRight.height && aLeft.width == aRight.width && aLeft.font.text == aRight.font.text
309 && aLeft.horizontalJustification == aRight.horizontalJustification
310 && aLeft.verticalJustification == aRight.verticalJustification && aLeft.bold == aRight.bold
311 && aLeft.italic == aRight.italic && aLeft.underline == aRight.underline
312 && aLeft.visible == aRight.visible;
318 return aLeft.kind == aRight.kind && aLeft.text.text == aRight.text.text && aLeft.lineStyle == aRight.lineStyle
319 && aLeft.strokeWidth == aRight.strokeWidth && aLeft.fill == aRight.fill
320 && samePresentationValue( aLeft.presentation, aRight.presentation ) && aLeft.angle == aRight.angle
321 && aLeft.arcSweepAngle == aRight.arcSweepAngle && aLeft.arcClockwise == aRight.arcClockwise
322 && samePointValue( aLeft.arcCenter, aRight.arcCenter )
323 && samePointValue( aLeft.arcBoundsStart, aRight.arcBoundsStart )
324 && samePointValue( aLeft.arcBoundsEnd, aRight.arcBoundsEnd )
325 && std::ranges::equal( aLeft.points, aRight.points, samePointValue );
331 std::vector<const MODEL_GRAPHIC*> leftDrawing;
332 std::vector<const MODEL_GRAPHIC*> rightDrawing;
333 std::vector<const MODEL_GRAPHIC*> leftText;
334 std::vector<const MODEL_GRAPHIC*> rightText;
344 return sameGraphicValue( *aLeftGraphic, *aRightGraphic );
347 return std::ranges::equal( leftDrawing, rightDrawing, equalPointers )
348 && std::ranges::is_permutation( leftText, rightText, equalPointers );
352 bool isProvenGraphicStrokeWidth( uint8_t aWidth )
354 constexpr std::array<uint16_t, 13> widths{ 1, 2, 5, 7, 8, 10, 11, 15, 20, 25, 30, 31, 40 };
355 return std::ranges::binary_search( widths, aWidth );
359 void decodeGraphicStrokeWidth( uint8_t aRawWidth, uint8_t aPackedPresentation,
361 std::vector<PARSER_DIAGNOSTIC>& aDiagnostics )
365 packedSource.length = 1;
366 SOURCE_PROPERTY packed = sourceProperty( wxS(
"preserved_graphic_presentation" ),
367 wxString::Format( wxS(
"%u" ), aPackedPresentation ), packedSource );
369 aGraphic.properties.push_back( std::move( packed ) );
371 if( isProvenGraphicStrokeWidth( aRawWidth ) )
373 aGraphic.strokeWidth =
static_cast<int64_t
>( aRawWidth ) * 2;
379 strokeSource.length = 1;
380 SOURCE_PROPERTY stroke = sourceProperty( wxS(
"unsupported_graphic_stroke_width" ),
381 wxString::Format( wxS(
"%u" ), aRawWidth ), strokeSource );
384 RPT_SEVERITY_WARNING, stroke, wxS(
"unproved graphic stroke width preserved; using hairline" ) ) );
385 aGraphic.properties.push_back( std::move( stroke ) );
394 if( aHandle == -1 || aHandle == -4 )
396 font.text = wxS(
"Default Font" );
398 font.codePage = DEFAULT_CODE_PAGE;
399 font.codePageName = wxS(
"windows-1252" );
407 std::vector<PARSER_DIAGNOSTIC>& aDiagnostics )
432 case 'A':
return { 22000, 17000, aSource };
433 case 'B':
return { 34000, 22000, aSource };
434 case 'C':
return { 44000, 34000, aSource };
435 case 'D':
return { 68000, 44000, aSource };
436 case 'E':
return { 88000, 68000, aSource };
437 default: throwDecodeError( aSource, wxS(
"invalid design page-size token" ) );
444 const uint16_t justification = aValue & 0x00FF;
445 const uint16_t horizontal = justification >= 8 ? justification - 8
446 : justification >= 2 ? justification - 2
461 const uint16_t justification = aValue & 0x00FF;
463 if( justification >= 8 )
466 if( justification >= 2 )
475 switch( aValue & 0x000F )
491 constexpr uint32_t CP1252_HIGH[] = { 0x20AC, 0xFFFD, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
492 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0xFFFD, 0x017D, 0xFFFD,
493 0xFFFD, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
494 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0xFFFD, 0x017E, 0x0178 };
497 wxString decodeWindows1252(
const std::vector<uint8_t>& aBytes )
501 for( uint8_t
byte : aBytes )
503 uint32_t codePoint = byte;
505 if(
byte >= 0x80 &&
byte <= 0x9F )
506 codePoint = CP1252_HIGH[
byte - 0x80];
508 result += wxUniChar( codePoint );
515 wxString decodeUnknownCodePage(
const std::vector<uint8_t>& aBytes )
519 for( uint8_t
byte : aBytes )
520 result += wxUniChar(
byte < 0x80 ?
byte : 0xFFFD );
526 wxString decodeUtf8(
const std::vector<uint8_t>& aBytes,
bool& aHadInvalidBytes )
529 aHadInvalidBytes =
false;
531 for(
size_t i = 0; i < aBytes.size(); )
533 uint8_t lead = aBytes[i];
534 uint32_t codePoint = 0;
535 size_t continuationCount = 0;
536 uint32_t minimum = 0;
540 result += wxUniChar( lead );
544 else if( lead >= 0xC2 && lead <= 0xDF )
546 codePoint = lead & 0x1F;
547 continuationCount = 1;
550 else if( lead >= 0xE0 && lead <= 0xEF )
552 codePoint = lead & 0x0F;
553 continuationCount = 2;
556 else if( lead >= 0xF0 && lead <= 0xF4 )
558 codePoint = lead & 0x07;
559 continuationCount = 3;
564 result += wxUniChar( 0xFFFD );
565 aHadInvalidBytes =
true;
570 bool valid = i + continuationCount < aBytes.size();
572 for(
size_t j = 1; valid && j <= continuationCount; ++j )
574 uint8_t continuation = aBytes[i + j];
575 valid = ( continuation & 0xC0 ) == 0x80;
578 codePoint = ( codePoint << 6 ) | ( continuation & 0x3F );
581 valid = valid && codePoint >= minimum && codePoint <= 0x10FFFF
582 && !( codePoint >= 0xD800 && codePoint <= 0xDFFF );
586 result += wxUniChar( 0xFFFD );
587 aHadInvalidBytes =
true;
592 result += wxUniChar( codePoint );
593 i += continuationCount + 1;
599 [[noreturn]]
void throwValidationError(
const SOURCE_PROVENANCE& aSource,
const wxString& aMessage );
602 template <
typename Item,
typename IdAccessor,
typename SourceAccessor>
603 void validateUniqueIds(
const std::vector<Item>& aItems,
const wxString& aObjectClass, IdAccessor aId,
604 SourceAccessor aSource )
606 std::map<uint32_t, SOURCE_PROVENANCE> declarations;
608 for(
const Item& item : aItems )
610 const auto&
id = aId( item );
613 throwValidationError( aSource( item ), wxString::Format( wxS(
"invalid %s ID" ), aObjectClass ) );
615 auto [first, inserted] = declarations.emplace(
id.
Value(), aSource( item ) );
620 wxString detail = wxString::Format(
621 wxS(
"duplicate %s ID %u; first at v0x%04X %s controller %d record %llu sheet %d "
623 aObjectClass,
id.
Value(), firstSource.version, firstSource.objectClass, firstSource.controller,
624 static_cast<unsigned long long>( firstSource.recordIndex ), firstSource.sheet,
625 static_cast<unsigned long long>( firstSource.absoluteOffset ) );
626 throwValidationError( aSource( item ), detail );
632 [[noreturn]]
void throwValidationError(
const SOURCE_PROVENANCE& aSource,
const wxString& aMessage )
638 constexpr auto itemId = [](
const auto& aItem ) ->
const auto&
644 constexpr auto itemProvenance = [](
const auto& aItem ) ->
const SOURCE_PROVENANCE&
650 template <
typename Item,
typename Declarations>
651 void validateNestedId(
const Item& aItem,
const wxString& aObjectClass, Declarations& aDeclarations )
653 if( !aItem.id.IsValid() )
654 throwValidationError( aItem.source, wxString::Format( wxS(
"invalid %s ID" ), aObjectClass ) );
656 auto [first, inserted] = aDeclarations.emplace( aItem.id.Value(), aItem.source );
660 throwValidationError(
662 wxString::Format( wxS(
"duplicate %s ID %llu; first at v0x%04X %s controller %d record %llu "
663 "sheet %d offset 0x%llX" ),
664 aObjectClass,
static_cast<unsigned long long>( aItem.id.Value() ),
665 first->second.version, first->second.objectClass, first->second.controller,
666 static_cast<unsigned long long>( first->second.recordIndex ), first->second.sheet,
667 static_cast<unsigned long long>( first->second.absoluteOffset ) ) );
672 struct MODEL_REFERENCE_INDEX
674 std::unordered_map<uint32_t, const MODEL_SHEET*> sheets;
675 std::unordered_map<uint32_t, const MODEL_SYMBOL_DEFINITION*> definitions;
676 std::unordered_map<uint32_t, const MODEL_PART_TYPE*> partTypes;
677 std::unordered_map<uint32_t, const MODEL_GATE*> gates;
678 std::unordered_map<uint32_t, const MODEL_PART_TYPE*> gateOwners;
679 std::unordered_map<uint32_t, const MODEL_PIN_DEFINITION*> pins;
680 std::unordered_map<uint32_t, const MODEL_SYMBOL_DEFINITION*> pinOwners;
681 std::unordered_map<uint32_t, const MODEL_PLACEMENT*> placements;
682 std::unordered_map<uint32_t, const MODEL_NET*> nets;
684 explicit MODEL_REFERENCE_INDEX(
const PADS_SCH_MODEL& aModel )
686 for(
const MODEL_SHEET& sheet : aModel.sheets )
687 sheets.emplace( sheet.id.Value(), &sheet );
689 for(
const MODEL_SYMBOL_DEFINITION& definition : aModel.definitions )
691 definitions.emplace( definition.id.Value(), &definition );
693 for(
const MODEL_PIN_DEFINITION&
pin : definition.pins )
695 pins.emplace(
pin.id.Value(), &
pin );
696 pinOwners.emplace(
pin.id.Value(), &definition );
700 for(
const MODEL_PART_TYPE& partType : aModel.partTypes )
702 partTypes.emplace( partType.id.Value(), &partType );
704 for(
const MODEL_GATE& gate : partType.gates )
706 gates.emplace( gate.id.Value(), &gate );
707 gateOwners.emplace( gate.id.Value(), &partType );
711 for(
const MODEL_PLACEMENT& placement : aModel.placements )
712 placements.emplace( placement.id.Value(), &placement );
714 for(
const MODEL_NET& net : aModel.nets )
715 nets.emplace( net.id.Value(), &net );
723 return !aEndpoint.placement && !aEndpoint.pin;
728 auto placement = aIndex.placements.find( aEndpoint.placement->id.Value() );
730 if( placement == aIndex.placements.end() )
733 return aIndex.pins.contains( aEndpoint.pin->id.Value() )
734 && std::ranges::any_of( placement->second->pins,
737 return aPin.id == aEndpoint.pin->id;
742 struct SHEET_CONTROLLERS
744 std::array<SCH_SDB_POOL, 23> pools;
745 std::array<size_t, 23> offsets;
749 struct PLACEMENT_GLOBALS
751 size_t attributeHeapBase = 0;
752 uint32_t attributeHeapBytes = 0;
753 size_t groupBase = 0;
754 uint32_t groupCount = 0;
755 size_t attributeOffsetBase = 0;
756 uint32_t attributeOffsetCount = 0;
758 uint32_t fontCount = 0;
762 PLACEMENT_GLOBALS placementGlobals(
const PADS_SCH_SDB& aSdb,
const wxString& aSourceName )
766 auto requireOuterStride = [&](
size_t aController,
size_t aStride )
770 if( pool.usedBytes != pool.count * aStride )
773 aSourceName, aSdb.
Version(), wxS(
"outer controller directory" ), aController, 0,
774 OUTER_DIRECTORY_OFFSET + aController * OUTER_DESCRIPTOR_BYTES + OUTER_USED_BYTES_OFFSET, 4,
776 throwDecodeError( source,
777 wxString::Format( wxS(
"controller byte count does not match %llu-byte records" ),
778 static_cast<unsigned long long>( aStride ) ) );
782 requireOuterStride( 6, PLACEMENT_GROUP_BYTES );
783 requireOuterStride( 7, ATTRIBUTE_OFFSET_BYTES );
784 requireOuterStride( 19, FONT_RECORD_BYTES );
785 result.attributeHeapBase = outerControllerOffset( aSdb, 2 );
786 result.attributeHeapBytes = aSdb.
Pools()[2].usedBytes;
787 result.groupBase = outerControllerOffset( aSdb, 6 );
789 result.attributeOffsetBase = outerControllerOffset( aSdb, 7 );
790 result.attributeOffsetCount = aSdb.
Pools()[7].count;
791 result.fontBase = outerControllerOffset( aSdb, 19 );
797 void decodeGlobalFont(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
798 const PLACEMENT_GLOBALS& aGlobals,
const wxString& aSourceName, uint16_t aVersion,
801 std::vector<PARSER_DIAGNOSTIC>& aDiagnostics )
803 if( aHandle == -1 || aHandle == -4 )
805 aPresentation.font = decodedDefinitionFont( aHandle, aHandleSource );
809 if( aHandle < 0 ||
static_cast<uint32_t
>( aHandle ) >= aGlobals.fontCount )
812 throwDecodeError( aHandleSource, wxS(
"placement font handle leaves outer controller 19" ) );
815 wxS(
"inline_font_payload" ), wxString::Format( wxS(
"%u" ), uint16_t( aHandle ) ), aHandleSource );
818 RPT_SEVERITY_WARNING, property, wxS(
"unsupported inline placement font payload preserved" ) ) );
819 aPresentation.properties.push_back( std::move( property ) );
823 const size_t fontOffset = aGlobals.fontBase +
static_cast<size_t>( aHandle ) * FONT_RECORD_BYTES;
824 SOURCE_PROVENANCE fontSource = sourceAt( aSourceName, aVersion, wxS(
"placement font" ), 19, aHandle,
825 fontOffset, FONT_RECORD_BYTES, -1 );
826 const uint32_t style = aCursor.
U32At( fontOffset );
830 nameSource.length = 32;
831 SOURCE_STRING name = decodeFixedString( aBytes, fontOffset + 4, 32, nameSource, aDiagnostics );
832 aPresentation.bold = ( style & 2 ) != 0;
833 aPresentation.italic = ( style & 1 ) != 0;
834 aPresentation.underline = ( style & 4 ) != 0;
835 aPresentation.font =
name;
837 if( aPresentation.bold )
838 aPresentation.font.text.Prepend( wxS(
"Bold " ) );
840 if( aPresentation.italic )
841 aPresentation.font.text.Prepend( wxS(
"Italic " ) );
843 aPresentation.properties.push_back(
844 sourceProperty( wxS(
"font_handle" ), wxString::Format( wxS(
"%d" ), aHandle ), fontSource ) );
846 if( ( style & ~uint32_t{ 7 } ) != 0 )
848 SOURCE_PROPERTY property = sourceProperty( wxS(
"unsupported_font_style_flags" ),
849 wxString::Format( wxS(
"%u" ), style & ~7U ), fontSource );
853 aPresentation.properties.push_back( std::move( property ) );
858 CONNECTIVITY_GLOBALS connectivityGlobals(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
862 CONNECTIVITY_GLOBALS
result;
867 if( membershipPool.usedBytes != membershipPool.count * NET_MEMBERSHIP_BYTES )
869 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"net membership directory" ), 4, 0,
870 outerControllerOffset( aSdb, 4 ), membershipPool.usedBytes, -1 );
871 throwDecodeError( source, wxS(
"controller byte count does not match 2-byte records" ) );
874 if( netPool.usedBytes != netPool.count * NET_RECORD_BYTES )
876 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"net directory" ), 8, 0,
877 outerControllerOffset( aSdb, 8 ), netPool.usedBytes, -1 );
878 throwDecodeError( source, wxS(
"controller byte count does not match 88-byte records" ) );
881 if( fontPool.usedBytes != fontPool.count * FONT_RECORD_BYTES )
883 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"font directory" ), 19, 0,
884 outerControllerOffset( aSdb, 19 ), fontPool.usedBytes, -1 );
885 throwDecodeError( source, wxS(
"controller byte count does not match 36-byte records" ) );
888 result.membershipBase = outerControllerOffset( aSdb, 4 );
889 result.membershipCount = membershipPool.count;
890 result.fontBase = outerControllerOffset( aSdb, 19 );
891 result.fontCount = fontPool.count;
892 result.membershipSheets.reserve( membershipPool.count );
894 for(
size_t record = 0; record < membershipPool.count; ++record )
895 result.membershipSheets.push_back( aCursor.
U16At(
result.membershipBase + record * 2 ) );
897 const size_t netBase = outerControllerOffset( aSdb, 8 );
898 const size_t aliasHeapBase = outerControllerOffset( aSdb, 1 );
899 const uint32_t aliasHeapBytes = aSdb.
Pools()[1].usedBytes;
900 std::vector<bool> claimedMemberships( membershipPool.count,
false );
902 result.nets.reserve( netPool.count );
904 for(
size_t record = 0; record < netPool.count; ++record )
906 const size_t offset = netBase + record * NET_RECORD_BYTES;
907 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"global net" ), 8, record, offset,
908 NET_RECORD_BYTES, -1 );
909 GLOBAL_NET_RECORD net;
911 net.preservedIdentity = aCursor.
U32At( offset );
912 net.preservedRelationship = aCursor.
U32At( offset + 84 );
913 net.membershipStart = aCursor.
U32At( offset + 4 );
914 net.aliasStringOffset = aCursor.
U32At( offset + 8 );
915 net.membershipCount = aCursor.
U16At( offset + 16 );
916 net.aliasCount = aCursor.
U16At( offset + 18 );
917 net.kindFlags = aCursor.
U16At( offset + 22 );
918 net.tombstone = net.membershipCount == 0xFFFF;
921 nameSource.length = 56;
922 net.name = decodeFixedString( aBytes, offset + 24, 56, nameSource, aModel.diagnostics );
924 if( aCursor.
U16At( offset + 20 ) != 0 || aCursor.
U32At( offset + 80 ) != 0xFFFFFFFF )
926 throwDecodeError( source, wxS(
"global net record has nonzero padding or a live reserved link" ) );
930 && ( net.membershipStart > membershipPool.count
931 || net.membershipCount > membershipPool.count - net.membershipStart ) )
933 throwDecodeError( source, wxS(
"net sheet-membership slice leaves outer controller 4" ) );
936 for( uint32_t membership = net.membershipStart;
937 !net.tombstone && membership < net.membershipStart + net.membershipCount; ++membership )
939 if( claimedMemberships[membership] )
940 throwDecodeError( source, wxS(
"duplicate net sheet-membership ownership" ) );
942 if(
result.membershipSheets[membership] >= aModel.sheets.size() )
943 throwDecodeError( source, wxS(
"net membership references the wrong sheet object class" ) );
945 claimedMemberships[membership] =
true;
948 if( net.aliasCount == 0 )
950 if( net.aliasStringOffset != 0xFFFFFFFF )
951 throwDecodeError( source, wxS(
"non-alias net has an alias-string handle" ) );
955 if( net.aliasStringOffset >= aliasHeapBytes )
956 throwDecodeError( source, wxS(
"bus-alias string handle leaves outer controller 1" ) );
958 size_t stringOffset = net.aliasStringOffset;
960 for(
size_t alias = 0; alias < net.aliasCount; ++alias )
963 sourceAt( aSourceName, aModel.version, wxS(
"bus alias member" ), 1, alias,
964 aliasHeapBase + stringOffset, aliasHeapBytes - stringOffset, -1 );
966 decodeFixedString( aBytes, aliasHeapBase + stringOffset, aliasHeapBytes - stringOffset,
967 aliasSource, aModel.diagnostics );
968 aliasSource.length = member.raw.size();
969 member.source.length = member.raw.size();
970 stringOffset += member.raw.size() + 1;
971 net.aliasMembers.push_back( std::move( member ) );
975 result.nets.push_back( std::move( net ) );
982 SHEET_CONTROLLERS sheetControllers(
const PADS_IO::BINARY_CURSOR& aCursor,
const SCH_SDB_BLOCK& aBlock )
985 size_t descriptor = aBlock.offset + SHEET_HEADER_BYTES;
986 size_t payload = descriptor + 24 * SHEET_DESCRIPTOR_BYTES;
988 for(
size_t i = 0; i <
result.pools.size(); ++i )
990 result.pools[i].count = aCursor.
U32At( descriptor + i * SHEET_DESCRIPTOR_BYTES + 12 );
991 result.pools[i].usedBytes = aCursor.
U32At( descriptor + i * SHEET_DESCRIPTOR_BYTES + 16 );
992 result.offsets[i] = payload;
993 payload +=
result.pools[i].usedBytes;
1000 void requireFixedController(
const SHEET_CONTROLLERS& aControllers,
size_t aController,
size_t aStride,
1001 const wxString& aFile, uint16_t aVersion,
int aSheet )
1003 const SCH_SDB_POOL& pool = aControllers.pools[aController - 1];
1005 if( pool.usedBytes != pool.count * aStride )
1008 sourceAt( aFile, aVersion, wxS(
"controller directory" ),
static_cast<int>( aController ), 0,
1009 aControllers.offsets[aController - 1], pool.usedBytes, aSheet );
1010 throwDecodeError( source, wxString::Format( wxS(
"controller byte count does not match %llu-byte records" ),
1011 static_cast<unsigned long long>( aStride ) ) );
1016 enum class TEXT_ROLE
1019 EMBEDDED_SYMBOL_TEXT,
1024 struct DEFINITION_TEXT_HEAP
1026 size_t recordBase = 0;
1027 uint32_t recordCount = 0;
1028 size_t stringBase = 0;
1029 uint32_t stringBytes = 0;
1035 int16_t& aRelationship )
1038 presentation.
source = aSource;
1039 presentation.height = aCursor.
U16At( aOffset + 22 );
1040 presentation.width = aCursor.
U8At( aOffset + 30 );
1041 presentation.properties.push_back( sourceProperty(
1042 wxS(
"display_flags" ), wxString::Format( wxS(
"%u" ), aCursor.
U8At( aOffset + 31 ) ), aSource ) );
1043 presentation.horizontalJustification = horizontalJustification( aCursor.
U16At( aOffset + 18 ) );
1044 presentation.verticalJustification = verticalJustification( aCursor.
U16At( aOffset + 18 ) );
1048 fontSource.length = 2;
1049 aRelationship =
static_cast<int16_t
>( aCursor.
U16At( aOffset + 28 ) );
1052 const bool pageText = aRole == TEXT_ROLE::PAGE_TEXT;
1054 presentation.font = decodedDefinitionFont( pageText ? int16_t( -1 ) : aRelationship, fontSource );
1055 presentation.properties.push_back( sourceProperty( pageText ? wxS(
"successor_ordinal" ) : wxS(
"font_handle" ),
1056 wxString::Format( wxS(
"%d" ), aRelationship ),
1058 return presentation;
1062 void decodeDefinitionTextRecord(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
1063 const wxString& aSourceName,
size_t aSheetIndex,
const DEFINITION_TEXT_HEAP& aHeap,
1067 if( aRecord >= aHeap.recordCount )
1068 throwDecodeError( aDefinition.source, wxS(
"definition field handle leaves controller 1" ) );
1070 const bool isField = aRole == TEXT_ROLE::DEFINITION_FIELD;
1071 const size_t offset = aHeap.recordBase + aRecord * TEXT_RECORD_BYTES;
1073 isField ? wxS(
"definition field" ) : wxS(
"embedded symbol text" ), 1,
1074 aRecord, offset, TEXT_RECORD_BYTES,
static_cast<int>( aSheetIndex ) );
1075 const uint32_t stringOffset = aCursor.
U32At( offset + 8 );
1076 const uint16_t stringBytes = aCursor.
U16At( offset + 20 );
1078 if( stringBytes == 0 || stringOffset > aHeap.stringBytes || stringBytes > aHeap.stringBytes - stringOffset
1079 || aBytes[aHeap.stringBase + stringOffset + stringBytes - 1] != 0 )
1081 throwDecodeError( source, wxS(
"definition field string leaves controller 2" ) );
1085 sourceAt( aSourceName, aModel.version, source.objectClass + wxS(
" string" ), 2, aRecord,
1086 aHeap.stringBase + stringOffset, stringBytes - 1,
static_cast<int>( aSheetIndex ) );
1088 { aBytes.begin() + stringSource.absoluteOffset,
1089 aBytes.begin() + stringSource.absoluteOffset + stringSource.length },
1090 DEFAULT_CODE_PAGE, stringSource, aModel.diagnostics );
1092 int16_t relationship = 0;
1093 MODEL_TEXT_PRESENTATION presentation = decodeTextPresentation( aCursor, offset, source, aRole, relationship );
1094 const SOURCE_POINT position{ decodeLocalCoordinate( aCursor.
U16At( offset + 12 ) ),
1095 decodeLocalCoordinate( aCursor.
U16At( offset + 14 ) ), source };
1102 field.name = std::move(
string );
1103 field.position = position;
1104 field.angle = angle;
1105 field.presentation = std::move( presentation );
1106 aDefinition.fields.push_back( std::move( field ) );
1113 graphic.text = std::move(
string );
1114 graphic.points.push_back( position );
1115 graphic.presentation = std::move( presentation );
1116 graphic.angle = angle;
1117 aDefinition.graphics.push_back( std::move( graphic ) );
1121 struct DEFINITION_LAYOUT
1123 size_t symbolBase = 0;
1124 size_t pieceBase = 0;
1125 size_t vertexBase = 0;
1127 size_t usedDecalBase = 0;
1128 size_t terminalBase = 0;
1129 size_t partBase = 0;
1130 size_t gateBase = 0;
1132 size_t signalPinBase = 0;
1133 size_t pinNameBase = 0;
1134 uint32_t pinNameBytes = 0;
1135 uint32_t definitionIdBase = 0;
1136 uint32_t pinIdBase = 0;
1137 uint32_t partIdBase = 0;
1138 uint32_t gateIdBase = 0;
1140 DEFINITION_TEXT_HEAP textHeap;
1141 std::vector<uint32_t> definitionPieceStart;
1142 std::vector<uint32_t> pieceVertexStart;
1143 std::vector<std::vector<size_t>> pieceArcRecords;
1150 uint32_t definitionRecord = 0;
1151 uint16_t terminalStart = 0;
1152 uint8_t terminalCount = 0;
1153 uint32_t fieldStart = 0;
1154 MODEL_SYMBOL_DEFINITION* definition =
nullptr;
1158 void preserveRawDefinitionControllers(
const std::vector<uint8_t>& aBytes,
const SHEET_CONTROLLERS& aControllers,
1159 size_t aSheetIndex,
const wxString& aSourceName,
PADS_SCH_MODEL& aModel )
1161 for(
size_t controller = 3; controller <= 23; ++controller )
1163 const SCH_SDB_POOL& pool = aControllers.pools[controller - 1];
1165 if( pool.usedBytes == 0 )
1169 sourceAt( aSourceName, aModel.version, wxS(
"definition controller" ), controller, 0,
1170 aControllers.offsets[controller - 1], pool.usedBytes,
static_cast<int>( aSheetIndex ) );
1171 aModel.preservedControllerPayloads.push_back(
1174 { aBytes.begin() + source.absoluteOffset,
1175 aBytes.begin() + source.absoluteOffset + source.length } } );
1180 DEFINITION_LAYOUT definitionLayout(
const PADS_IO::BINARY_CURSOR& aCursor,
const SHEET_CONTROLLERS& aControllers,
1181 size_t aSheetIndex,
const wxString& aSourceName, uint16_t aVersion )
1183 requireFixedController( aControllers, 3, SYMBOL_RECORD_BYTES, aSourceName, aVersion, aSheetIndex );
1184 requireFixedController( aControllers, 4, SYMBOL_PIECE_BYTES, aSourceName, aVersion, aSheetIndex );
1185 requireFixedController( aControllers, 5, SYMBOL_VERTEX_BYTES, aSourceName, aVersion, aSheetIndex );
1186 requireFixedController( aControllers, 6, SYMBOL_ARC_BYTES, aSourceName, aVersion, aSheetIndex );
1187 requireFixedController( aControllers, 7, USED_DECAL_BYTES, aSourceName, aVersion, aSheetIndex );
1188 requireFixedController( aControllers, 8, TERMINAL_BYTES, aSourceName, aVersion, aSheetIndex );
1189 requireFixedController( aControllers, 9, PART_TYPE_BYTES, aSourceName, aVersion, aSheetIndex );
1190 requireFixedController( aControllers, 10, GATE_BYTES, aSourceName, aVersion, aSheetIndex );
1191 requireFixedController( aControllers, 11, PIN_BYTES, aSourceName, aVersion, aSheetIndex );
1192 requireFixedController( aControllers, 12, SIGNAL_PIN_BYTES, aSourceName, aVersion, aSheetIndex );
1194 DEFINITION_LAYOUT layout;
1195 layout.symbolBase = aControllers.offsets[2];
1196 layout.pieceBase = aControllers.offsets[3];
1197 layout.vertexBase = aControllers.offsets[4];
1198 layout.arcBase = aControllers.offsets[5];
1199 layout.usedDecalBase = aControllers.offsets[6];
1200 layout.terminalBase = aControllers.offsets[7];
1201 layout.partBase = aControllers.offsets[8];
1202 layout.gateBase = aControllers.offsets[9];
1203 layout.pinBase = aControllers.offsets[10];
1204 layout.signalPinBase = aControllers.offsets[11];
1205 layout.pinNameBase = aControllers.offsets[13];
1206 layout.pinNameBytes = aControllers.pools[13].usedBytes;
1207 layout.definitionIdBase =
static_cast<uint32_t
>( aSheetIndex * 0x100000 + 1 );
1208 layout.pinIdBase =
static_cast<uint32_t
>( aSheetIndex * 0x100000 + 0x10000 );
1209 layout.partIdBase =
static_cast<uint32_t
>( aSheetIndex * 0x100000 + 0x20000 );
1210 layout.gateIdBase =
static_cast<uint32_t
>( aSheetIndex * 0x100000 + 0x30000 );
1211 layout.textHeap = { aControllers.offsets[0], aControllers.pools[0].count, aControllers.offsets[1],
1212 aControllers.pools[1].usedBytes };
1217 uint64_t vertexCursor = 0;
1218 uint64_t pieceCursor = 0;
1220 for(
size_t definition = 0; definition < aControllers.pools[2].count; ++definition )
1222 layout.definitionPieceStart.push_back(
static_cast<uint32_t
>( pieceCursor ) );
1223 pieceCursor += aCursor.
U16At( layout.symbolBase + definition * SYMBOL_RECORD_BYTES + 0x2A );
1226 if( pieceCursor != aControllers.pools[3].count )
1229 sourceAt( aSourceName, aVersion, wxS(
"symbol definition" ), 3, 0, layout.symbolBase,
1230 aControllers.pools[2].usedBytes,
static_cast<int>( aSheetIndex ) );
1231 throwDecodeError( source, wxS(
"symbol graphic-piece counts leave controller 4" ) );
1234 for(
size_t piece = 0; piece < aControllers.pools[3].count; ++piece )
1236 layout.pieceVertexStart.push_back(
static_cast<uint32_t
>( vertexCursor ) );
1237 vertexCursor += aCursor.
U16At( layout.pieceBase + piece * SYMBOL_PIECE_BYTES + 2 );
1240 if( vertexCursor != aControllers.pools[4].count )
1242 SOURCE_PROVENANCE source = sourceAt( aSourceName, aVersion, wxS(
"symbol piece" ), 4, 0, layout.pieceBase,
1243 aControllers.pools[3].usedBytes,
static_cast<int>( aSheetIndex ) );
1244 throwDecodeError( source, wxS(
"symbol piece vertex counts do not consume controller 5" ) );
1247 for(
size_t definition = 0; definition < aControllers.pools[2].count; ++definition )
1249 const size_t definitionOffset = layout.symbolBase + definition * SYMBOL_RECORD_BYTES;
1250 const uint32_t firstPiece = layout.definitionPieceStart[definition];
1251 const uint32_t pieceEnd = definition + 1 < layout.definitionPieceStart.size()
1252 ? layout.definitionPieceStart[definition + 1]
1253 : aControllers.pools[3].count;
1254 const uint32_t firstVertex = aCursor.
U32At( definitionOffset + 0x34 );
1255 const uint32_t vertexEnd = definition + 1 < aControllers.pools[2].count
1256 ? aCursor.
U32At( definitionOffset + SYMBOL_RECORD_BYTES + 0x34 )
1257 : aControllers.pools[4].count;
1258 const bool emptyMatches = firstPiece == pieceEnd && firstVertex == vertexEnd;
1259 const bool ownedMatches =
1260 firstPiece < pieceEnd && layout.pieceVertexStart[firstPiece] == firstVertex
1261 && layout.pieceVertexStart[pieceEnd - 1]
1262 + aCursor.
U16At( layout.pieceBase + ( pieceEnd - 1 ) * SYMBOL_PIECE_BYTES + 2 )
1265 if( !emptyMatches && !ownedMatches )
1268 sourceAt( aSourceName, aVersion, wxS(
"symbol definition" ), 3, definition, definitionOffset,
1269 SYMBOL_RECORD_BYTES,
static_cast<int>( aSheetIndex ) );
1270 throwDecodeError( source, wxS(
"symbol piece/vertex ownership mismatch" ) );
1274 layout.pieceArcRecords.resize( aControllers.pools[3].count );
1275 size_t discoveredArcCount = 0;
1277 for(
size_t piece = 0; piece < aControllers.pools[3].count; ++piece )
1279 const size_t pieceOffset = layout.pieceBase + piece * SYMBOL_PIECE_BYTES;
1280 const uint16_t pointCount = aCursor.
U16At( pieceOffset + 2 );
1282 for(
size_t point = 0; point < pointCount; ++point )
1284 const size_t vertexOffset =
1285 layout.vertexBase + ( layout.pieceVertexStart[piece] + point ) * SYMBOL_VERTEX_BYTES;
1287 if(
static_cast<int16_t
>( aCursor.
U16At( vertexOffset + 4 ) ) >= 0 )
1288 layout.pieceArcRecords[piece].push_back( discoveredArcCount++ );
1292 if( discoveredArcCount != aControllers.pools[5].count )
1295 sourceAt( aSourceName, aVersion, wxS(
"symbol arc" ), 6, discoveredArcCount, layout.arcBase,
1296 aControllers.pools[5].usedBytes,
static_cast<int>( aSheetIndex ) );
1297 throwDecodeError( source, wxS(
"arc markers do not consume controller 6" ) );
1304 void decodeSymbolDefinitions(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
1305 const SHEET_CONTROLLERS& aControllers,
const DEFINITION_LAYOUT& aLayout,
1306 size_t aSheetIndex,
const wxString& aSourceName,
1307 std::vector<MODEL_SYMBOL_DEFINITION*>& aDefinitionsByRecord,
1308 std::vector<size_t>& aPageGraphicRecords,
PADS_SCH_MODEL& aModel )
1311 aModel.definitions.reserve( aModel.definitions.size() + aControllers.pools[2].count );
1313 for(
size_t record = 0; record < aControllers.pools[2].count; ++record )
1315 const size_t offset = aLayout.symbolBase + record * SYMBOL_RECORD_BYTES;
1316 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"symbol definition" ), 3, record,
1317 offset, SYMBOL_RECORD_BYTES,
static_cast<int>( aSheetIndex ) );
1320 SOURCE_STRING name = decodeFixedString( aBytes, offset, 38, nameSource, aModel.diagnostics );
1321 const uint8_t objectClass = aCursor.
U8At( offset + 0x29 );
1322 const uint32_t vertexStart = aCursor.
U32At( offset + 0x34 );
1323 const uint32_t vertexEnd = record + 1 < aControllers.pools[2].count
1324 ? aCursor.
U32At( offset + SYMBOL_RECORD_BYTES + 0x34 )
1325 : aControllers.pools[4].count;
1327 if( vertexStart > vertexEnd || vertexEnd > aControllers.pools[4].count )
1328 throwDecodeError( source, wxS(
"symbol definition vertex slice leaves controller 5" ) );
1330 if( objectClass == 0 )
1332 const uint32_t firstPiece = aLayout.definitionPieceStart[record];
1333 const uint32_t pieceEnd = record + 1 < aLayout.definitionPieceStart.size()
1334 ? aLayout.definitionPieceStart[record + 1]
1335 : aControllers.pools[3].count;
1336 const uint16_t groupTextCount = aCursor.
U16At( offset + 64 );
1337 const uint16_t groupLastText = aCursor.
U16At( offset + 66 );
1338 const bool worksheetCandidate = pieceEnd - firstPiece == 69 && groupTextCount == 58;
1339 const bool hasCircularTextOwnership = groupTextCount == 0
1340 || ( groupTextCount <= aLayout.textHeap.recordCount
1341 && groupLastText < aLayout.textHeap.recordCount );
1343 originSource.
objectClass = wxS(
"page graphic group origin" );
1344 originSource.absoluteOffset += 60;
1345 originSource.length = 4;
1346 const SOURCE_POINT groupOrigin{ decodeCoordinate( aCursor.
U16At( offset + 60 ) ),
1347 decodeCoordinate( aCursor.
U16At( offset + 62 ) ), originSource };
1349 if( aCursor.
U16At( offset + 42 ) != pieceEnd - firstPiece )
1350 throwDecodeError( source, wxS(
"page-graphic piece count does not match controller 4 slice" ) );
1352 for(
size_t piece = firstPiece; piece < pieceEnd; ++piece )
1354 const size_t pieceOffset = aLayout.pieceBase + piece * SYMBOL_PIECE_BYTES;
1355 const uint8_t pieceKind = aCursor.
U8At( pieceOffset );
1356 const uint8_t lineStyle = aCursor.
U8At( pieceOffset + 1 );
1357 const uint16_t pointCount = aCursor.
U16At( pieceOffset + 2 );
1358 const uint32_t firstVertex = aLayout.pieceVertexStart[piece];
1360 sourceAt( aSourceName, aModel.version, wxS(
"page graphic" ), 4, piece, pieceOffset,
1361 SYMBOL_PIECE_BYTES,
static_cast<int>( aSheetIndex ) );
1363 if( firstVertex + pointCount > vertexEnd )
1364 throwDecodeError( graphicSource, wxS(
"page graphic crosses its controller-5 slice" ) );
1367 graphic.
source = graphicSource;
1368 decodeGraphicStrokeWidth( aCursor.
U8At( pieceOffset + 4 ), aCursor.
U8At( pieceOffset + 5 ),
1369 graphicSource, graphic, aModel.diagnostics );
1378 graphicSource, aModel.diagnostics );
1396 aModel.diagnostics );
1401 for(
size_t point = 0; point < pointCount; ++point )
1403 const size_t pointOffset = aLayout.vertexBase + ( firstVertex + point ) * SYMBOL_VERTEX_BYTES;
1405 aSourceName, aModel.version, wxS(
"page graphic vertex" ), 5, firstVertex + point,
1406 pointOffset, SYMBOL_VERTEX_BYTES,
static_cast<int>( aSheetIndex ) );
1407 graphic.points.push_back( { decodeLocalCoordinate( aCursor.
U16At( pointOffset ) ),
1408 decodeLocalCoordinate( aCursor.
U16At( pointOffset + 2 ) ),
1412 const int16_t arcMarker =
static_cast<int16_t
>(
1413 aCursor.
U16At( aLayout.vertexBase + firstVertex * SYMBOL_VERTEX_BYTES + 4 ) );
1415 if( pieceKind == 0 && arcMarker >= 0 )
1417 if( aLayout.pieceArcRecords[piece].empty() )
1418 throwDecodeError( graphicSource, wxS(
"page arc has no controller-6 record" ) );
1420 const size_t arcRecord = aLayout.pieceArcRecords[piece].front();
1421 const size_t arcOffset = aLayout.arcBase + arcRecord * SYMBOL_ARC_BYTES;
1423 sourceAt( aSourceName, aModel.version, wxS(
"page arc" ), 6, arcRecord, arcOffset,
1424 SYMBOL_ARC_BYTES,
static_cast<int>( aSheetIndex ) );
1426 graphic.arcSweepAngle = aCursor.
U16At( arcOffset );
1427 graphic.arcClockwise =
static_cast<int16_t
>( aCursor.
U16At( arcOffset + 2 ) ) < 0;
1428 graphic.arcBoundsStart = { decodeLocalCoordinate( aCursor.
U16At( arcOffset + 6 ) ),
1429 decodeLocalCoordinate( aCursor.
U16At( arcOffset + 8 ) ), arcSource };
1430 graphic.arcBoundsEnd = { decodeLocalCoordinate( aCursor.
U16At( arcOffset + 10 ) ),
1431 decodeLocalCoordinate( aCursor.
U16At( arcOffset + 12 ) ), arcSource };
1432 graphic.arcCenter = { ( graphic.arcBoundsStart.x + graphic.arcBoundsEnd.x ) / 2,
1433 ( graphic.arcBoundsStart.y + graphic.arcBoundsEnd.y ) / 2, arcSource };
1438 point.x += groupOrigin.x;
1439 point.y += groupOrigin.y;
1444 graphic.arcBoundsStart.x += groupOrigin.x;
1445 graphic.arcBoundsStart.y += groupOrigin.y;
1446 graphic.arcBoundsEnd.x += groupOrigin.x;
1447 graphic.arcBoundsEnd.y += groupOrigin.y;
1448 graphic.arcCenter.x += groupOrigin.x;
1449 graphic.arcCenter.y += groupOrigin.y;
1452 graphic.properties.push_back( sourceProperty( wxS(
"page_graphic_group" ),
name.text, source ) );
1454 if( worksheetCandidate )
1455 graphic.properties.push_back( sourceProperty( wxS(
"worksheet_group" ),
name.text, source ) );
1457 if( !hasCircularTextOwnership )
1461 relationshipSource.length = 4;
1463 wxS(
"preserved_drawing_text_relationship" ),
1464 wxString::Format( wxS(
"%u,%u" ), groupTextCount, groupLastText ), relationshipSource );
1468 wxS(
"unsupported class-zero drawing text relationship preserved" ) ) );
1469 graphic.properties.push_back( std::move( relationship ) );
1471 aModel.graphics.push_back(
1472 { graphicSource, { aModel.sheets[aSheetIndex].id, graphicSource }, std::move( graphic ) } );
1475 if( hasCircularTextOwnership )
1476 aPageGraphicRecords.push_back( record );
1480 if( objectClass != 0x06 )
1485 definition.source = source;
1486 definition.name = std::move(
name );
1487 const uint32_t firstPiece = aLayout.definitionPieceStart[record];
1488 const uint32_t pieceEnd = record + 1 < aLayout.definitionPieceStart.size()
1489 ? aLayout.definitionPieceStart[record + 1]
1490 : aControllers.pools[3].count;
1492 for(
size_t piece = firstPiece; piece < pieceEnd; ++piece )
1494 const size_t pieceOffset = aLayout.pieceBase + piece * SYMBOL_PIECE_BYTES;
1495 const uint8_t pieceKind = aCursor.
U8At( pieceOffset );
1496 const uint16_t pointCount = aCursor.
U16At( pieceOffset + 2 );
1497 const uint32_t firstVertex = aLayout.pieceVertexStart[piece];
1499 if( firstVertex + pointCount > vertexEnd )
1500 throwDecodeError( source, wxS(
"symbol piece crosses its definition vertex slice" ) );
1503 sourceAt( aSourceName, aModel.version, wxS(
"symbol graphic" ), 4, piece, pieceOffset,
1504 SYMBOL_PIECE_BYTES,
static_cast<int>( aSheetIndex ) );
1506 graphic.
source = graphicSource;
1507 decodeGraphicStrokeWidth( aCursor.
U8At( pieceOffset + 4 ), aCursor.
U8At( pieceOffset + 5 ),
1508 graphicSource, graphic, aModel.diagnostics );
1522 aModel.diagnostics );
1527 for(
size_t point = 0; point < pointCount; ++point )
1529 const size_t pointOffset = aLayout.vertexBase + ( firstVertex + point ) * SYMBOL_VERTEX_BYTES;
1531 sourceAt( aSourceName, aModel.version, wxS(
"symbol vertex" ), 5, firstVertex + point,
1532 pointOffset, SYMBOL_VERTEX_BYTES,
static_cast<int>( aSheetIndex ) );
1533 graphic.points.push_back( { decodeLocalCoordinate( aCursor.
U16At( pointOffset ) ),
1534 decodeLocalCoordinate( aCursor.
U16At( pointOffset + 2 ) ),
1538 if( pieceKind == 1 && graphic.points.size() == 5 && graphic.points.front().x == graphic.points.back().x
1539 && graphic.points.front().y == graphic.points.back().y )
1541 const auto [minX, maxX] = std::ranges::minmax( graphic.points, {}, &
SOURCE_POINT::x );
1542 const auto [minY, maxY] = std::ranges::minmax( graphic.points, {}, &
SOURCE_POINT::y );
1543 const bool cornersOnly =
1544 std::ranges::all_of( graphic.points,
1547 return ( aPoint.x == minX.x || aPoint.x == maxX.x )
1548 && ( aPoint.y == minY.y || aPoint.y == maxY.y );
1554 graphic.points = { { minX.x, minY.y, graphicSource }, { maxX.x, maxY.y, graphicSource } };
1558 const int16_t arcMarker =
static_cast<int16_t
>(
1559 aCursor.
U16At( aLayout.vertexBase + firstVertex * SYMBOL_VERTEX_BYTES + 4 ) );
1561 if( pieceKind == 0 && arcMarker >= 0 )
1563 if( aLayout.pieceArcRecords[piece].empty() )
1564 throwDecodeError( graphicSource, wxS(
"symbol arc has no controller-6 record" ) );
1566 const size_t arcRecord = aLayout.pieceArcRecords[piece].front();
1567 const size_t arcOffset = aLayout.arcBase + arcRecord * SYMBOL_ARC_BYTES;
1570 sourceAt( aSourceName, aModel.version, wxS(
"symbol arc" ), 6, arcRecord, arcOffset,
1571 SYMBOL_ARC_BYTES,
static_cast<int>( aSheetIndex ) );
1572 graphic.arcSweepAngle = aCursor.
U16At( arcOffset );
1573 graphic.arcClockwise =
static_cast<int16_t
>( aCursor.
U16At( arcOffset + 2 ) ) < 0;
1574 graphic.arcBoundsStart = { decodeLocalCoordinate( aCursor.
U16At( arcOffset + 6 ) ),
1575 decodeLocalCoordinate( aCursor.
U16At( arcOffset + 8 ) ), arcSource };
1576 graphic.arcBoundsEnd = { decodeLocalCoordinate( aCursor.
U16At( arcOffset + 10 ) ),
1577 decodeLocalCoordinate( aCursor.
U16At( arcOffset + 12 ) ), arcSource };
1578 graphic.arcCenter = { ( graphic.arcBoundsStart.x + graphic.arcBoundsEnd.x ) / 2,
1579 ( graphic.arcBoundsStart.y + graphic.arcBoundsEnd.y ) / 2, arcSource };
1580 graphic.properties.push_back( sourceProperty(
1581 wxS(
"arc_direction" ),
1582 graphic.arcClockwise ? wxS(
"clockwise" ) : wxS(
"counterclockwise" ), arcSource ) );
1583 graphic.properties.push_back( sourceProperty(
1584 wxS(
"arc_marker" ),
1585 wxString::Format( wxS(
"%d" ),
static_cast<int16_t
>( aCursor.
U16At( arcOffset + 4 ) ) ),
1589 definition.graphics.push_back( std::move( graphic ) );
1592 aModel.definitions.push_back( std::move( definition ) );
1593 aDefinitionsByRecord[record] = &aModel.definitions.back();
1598 void decodeUsedDecals(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
1599 const SHEET_CONTROLLERS& aControllers,
const DEFINITION_LAYOUT& aLayout,
size_t aSheetIndex,
1600 const wxString& aSourceName,
1601 const std::vector<MODEL_SYMBOL_DEFINITION*>& aDefinitionsByRecord,
1602 std::vector<USED_DECAL>& aUsedDecals, std::vector<USED_DECAL*>& aSemanticDecals,
1605 for(
size_t record = 0; record < aUsedDecals.size(); ++record )
1607 const size_t offset = aLayout.usedDecalBase + record * USED_DECAL_BYTES;
1608 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"used decal" ), 7, record, offset,
1609 USED_DECAL_BYTES,
static_cast<int>( aSheetIndex ) );
1612 SOURCE_STRING name = decodeFixedString( aBytes, offset, 40, nameSource, aModel.diagnostics );
1613 USED_DECAL& decal = aUsedDecals[record];
1614 decal.record = record;
1615 decal.terminalCount = aCursor.
U8At( offset + 42 );
1616 decal.terminalStart = aCursor.
U16At( offset + 44 );
1617 decal.definitionRecord = aCursor.
U32At( offset + 48 );
1618 decal.fieldStart = aCursor.
U32At( offset + 52 );
1620 if(
name.text.empty() || decal.definitionRecord == 0xFFFFFFFF )
1623 if( decal.definitionRecord >= aDefinitionsByRecord.size() )
1624 throwDecodeError( source, wxS(
"unresolved symbol definition reference" ) );
1626 const size_t definitionOffset = aLayout.symbolBase + decal.definitionRecord * SYMBOL_RECORD_BYTES;
1628 sourceAt( aSourceName, aModel.version, wxS(
"symbol definition" ), 3, decal.definitionRecord,
1629 definitionOffset, 38,
static_cast<int>( aSheetIndex ) );
1631 decodeFixedString( aBytes, definitionOffset, 38, targetNameSource, aModel.diagnostics );
1633 if( targetName.text !=
name.text )
1635 const uint8_t targetClass = aCursor.
U8At( definitionOffset + 0x29 );
1636 throwDecodeError( source, targetClass == 0x06 ? wxS(
"used-decal handle name mismatch" )
1637 : wxS(
"used-decal handle targets wrong object class" ) );
1640 decal.definition = aDefinitionsByRecord[decal.definitionRecord];
1642 if( !decal.definition )
1645 const uint16_t embeddedTextCount =
1646 aCursor.
U16At( aLayout.symbolBase + decal.definitionRecord * SYMBOL_RECORD_BYTES + 0x40 );
1648 if( embeddedTextCount != 0 && decal.fieldStart > aLayout.textHeap.recordCount
1649 && decal.fieldStart < 0x80000000 )
1650 throwDecodeError( source, wxS(
"embedded definition text handle leaves controller 1" ) );
1652 if(
static_cast<uint32_t
>( decal.terminalStart ) + decal.terminalCount > aControllers.pools[7].count )
1653 throwDecodeError( source, wxS(
"used-decal terminal slice leaves controller 8" ) );
1655 aSemanticDecals.push_back( &decal );
1657 for(
size_t pin = 0;
pin < decal.terminalCount; ++
pin )
1659 const size_t terminalRecord = decal.terminalStart +
pin;
1660 const size_t terminalOffset = aLayout.terminalBase + terminalRecord * TERMINAL_BYTES;
1662 sourceAt( aSourceName, aModel.version, wxS(
"symbol pin" ), 8, terminalRecord, terminalOffset,
1663 TERMINAL_BYTES,
static_cast<int>( aSheetIndex ) );
1664 const uint16_t pinDecalHandle = aCursor.
U16At( terminalOffset );
1666 definitionPin.
id =
PIN_ID( aLayout.pinIdBase + terminalRecord );
1667 definitionPin.source = pinSource;
1668 definitionPin.position = { decodeTerminalCoordinate( aCursor.
U16At( terminalOffset + 2 ) ),
1669 decodeTerminalCoordinate( aCursor.
U16At( terminalOffset + 4 ) ), pinSource };
1670 definitionPin.presentation.source = pinSource;
1671 definitionPin.presentation.height =
1672 static_cast<int64_t
>(
static_cast<int16_t
>( aCursor.
U16At( terminalOffset + 6 ) ) ) * 2;
1673 definitionPin.presentation.width =
1674 static_cast<int64_t
>(
static_cast<int16_t
>( aCursor.
U16At( terminalOffset + 8 ) ) ) * 2;
1675 definitionPin.presentation.visible = ( aCursor.
U16At( terminalOffset + 24 ) & 0x8000 ) == 0;
1676 definitionPin.namePresentation = definitionPin.presentation;
1677 definitionPin.numberPresentation.source = pinSource;
1678 definitionPin.numberPresentation.height =
1679 static_cast<int64_t
>(
static_cast<int16_t
>( aCursor.
U16At( terminalOffset + 10 ) ) ) * 2;
1680 definitionPin.numberPresentation.width =
1681 static_cast<int64_t
>(
static_cast<int16_t
>( aCursor.
U16At( terminalOffset + 12 ) ) ) * 2;
1682 definitionPin.namePresentation.visible = definitionPin.namePresentation.height != 0
1683 && ( aCursor.
U16At( terminalOffset + 24 ) & 0x8000 ) == 0;
1684 definitionPin.numberPresentation.visible = definitionPin.numberPresentation.height != 0;
1685 definitionPin.nameOffset = { decodeLocalCoordinate( aCursor.
U16At( terminalOffset + 14 ) ),
1686 decodeLocalCoordinate( aCursor.
U16At( terminalOffset + 16 ) ), pinSource };
1687 definitionPin.numberOffset = { decodeLocalCoordinate( aCursor.
U16At( terminalOffset + 18 ) ),
1688 decodeLocalCoordinate( aCursor.
U16At( terminalOffset + 20 ) ),
1690 const uint16_t presentationFlags = aCursor.
U16At( terminalOffset + 22 );
1691 const uint16_t visibilityFlags = aCursor.
U16At( terminalOffset + 24 );
1692 const uint16_t side = presentationFlags & 0x0006;
1693 definitionPin.presentationFlags = presentationFlags;
1694 definitionPin.visibilityAndNumberPresentationFlags = visibilityFlags;
1695 definitionPin.side = side / 2;
1697 definitionPin.angle = ( presentationFlags & 0x0001 ) != 0 ? 900 : 0;
1698 definitionPin.nameAngle = ( presentationFlags & 0x0100 ) != 0 ? 900 : 0;
1699 definitionPin.numberAngle = ( visibilityFlags & 0x0001 ) != 0 ? 900 : 0;
1701 definitionPin.nameJustification =
1702 terminalJustification( ( presentationFlags >> 12 ) & 0x0F, definitionPin.nameAngle != 0 );
1704 if( ( presentationFlags & 0x00F8 ) != 0 )
1706 aModel.diagnostics );
1708 definitionPin.numberJustification =
1709 terminalJustification( ( visibilityFlags >> 4 ) & 0x0F, definitionPin.numberAngle != 0 );
1710 const uint16_t nameOffsetFlags = visibilityFlags & 0x0F00;
1711 definitionPin.nameOffsetAngle = ( nameOffsetFlags & 0x0100 ) != 0 ? 900 : 0;
1712 definitionPin.numberOffsetAngle = ( nameOffsetFlags & 0x0200 ) != 0 ? 900 : 0;
1714 switch( visibilityFlags & 0x0F00 )
1717 case 0x0800: definitionPin.nameOffsetJustification = 0;
break;
1719 case 0x0C00: definitionPin.nameOffsetJustification = 1;
break;
1722 case 0x0F00: definitionPin.nameOffsetJustification = 2;
break;
1725 visibilityFlags & 0x0F00, pinSource,
1726 aModel.diagnostics );
1730 switch( visibilityFlags & 0x0F00 )
1732 case 0x0000: definitionPin.numberOffsetJustification = 0;
break;
1734 case 0x0C00: definitionPin.numberOffsetJustification = 1;
break;
1735 case 0x0D00: definitionPin.numberOffsetJustification = 8;
break;
1736 case 0x0F00: definitionPin.numberOffsetJustification = 2;
break;
1739 visibilityFlags & 0x0F00, pinSource,
1740 aModel.diagnostics );
1744 definitionPin.visibilityFlags = ( visibilityFlags >> 8 ) & 0x00C0;
1746 if( pinDecalHandle == 0xFFFF )
1748 definitionPin.length = 0;
1750 else if( pinDecalHandle >= aUsedDecals.size() )
1751 throwDecodeError( pinSource, wxS(
"unresolved pin-decal handle" ) );
1753 if( pinDecalHandle != 0xFFFF )
1759 const size_t handleOffset = aLayout.usedDecalBase + pinDecalHandle * USED_DECAL_BYTES;
1760 const uint32_t definitionRecord = aCursor.
U32At( handleOffset + 48 );
1762 if( definitionRecord >= aDefinitionsByRecord.size() || !aDefinitionsByRecord[definitionRecord] )
1763 throwDecodeError( pinSource, wxS(
"unresolved pin-decal handle" ) );
1765 pinDecal = aDefinitionsByRecord[definitionRecord];
1768 const wxString pinDecalName = pinDecal->name.text;
1769 definitionPin.decalName = pinDecal->name;
1771 int64_t provenLength = 0;
1775 const bool containsOrigin = std::ranges::any_of( pinGraphic.points,
1778 return aPoint.x == 0 && aPoint.y == 0;
1781 if( !containsOrigin )
1786 std::max( provenLength, std::max(
std::abs( point.x ),
std::abs( point.y ) ) );
1789 if( provenLength != 0 )
1790 definitionPin.length = provenLength;
1794 sourceProperty( wxS(
"pin_length" ), wxS(
"unknown" ), pinSource );
1796 definitionPin.properties.push_back( std::move( lengthProperty ) );
1799 const bool inverted = pinDecalName == wxS(
"PINB" ) || pinDecalName == wxS(
"PINORB" )
1800 || pinDecalName == wxS(
"PCLKB" ) || pinDecalName == wxS(
"PINIEB" );
1801 const bool clock = pinDecalName == wxS(
"PCLK" ) || pinDecalName == wxS(
"PCLKB" );
1802 definitionPin.graphicStyle = ( inverted ? 1U : 0U ) | ( clock ? 2U : 0U );
1805 definitionPin.properties.push_back( sourceProperty(
1806 wxS(
"pin_name_height_half_mils" ),
1807 wxString::Format( wxS(
"%lld" ), definitionPin.presentation.height ), pinSource ) );
1808 definitionPin.properties.push_back( sourceProperty(
1809 wxS(
"pin_number_height_half_mils" ),
1810 wxString::Format( wxS(
"%lld" ), definitionPin.numberPresentation.height ), pinSource ) );
1811 definitionPin.properties.push_back( sourceProperty(
1812 wxS(
"pin_name_width_half_mils" ),
1813 wxString::Format( wxS(
"%lld" ), definitionPin.namePresentation.width ), pinSource ) );
1814 definitionPin.properties.push_back( sourceProperty(
1815 wxS(
"pin_number_width_half_mils" ),
1816 wxString::Format( wxS(
"%lld" ), definitionPin.numberPresentation.width ), pinSource ) );
1817 definitionPin.properties.push_back(
1818 sourceProperty( wxS(
"terminal_side" ), wxString::Format( wxS(
"%u" ), side ), pinSource ) );
1819 decal.definition->pins.push_back( std::move( definitionPin ) );
1825 std::vector<USED_DECAL*> buildFieldDecals(
const std::vector<USED_DECAL*>& aSemanticDecals,
1826 const DEFINITION_LAYOUT& aLayout,
size_t aSheetIndex,
1827 const wxString& aSourceName, uint16_t aVersion )
1829 std::vector<USED_DECAL*> fieldDecals;
1831 std::ranges::copy_if( aSemanticDecals, std::back_inserter( fieldDecals ),
1832 [&](
const USED_DECAL* aDecal )
1834 return aDecal->fieldStart < aLayout.textHeap.recordCount;
1836 std::ranges::sort( fieldDecals,
1837 [](
const USED_DECAL* aLeft,
const USED_DECAL* aRight )
1839 return aLeft->fieldStart < aRight->fieldStart;
1842 for(
size_t i = 1; i < fieldDecals.size(); ++i )
1844 if( fieldDecals[i - 1]->fieldStart != fieldDecals[i]->fieldStart )
1848 sourceAt( aSourceName, aVersion, wxS(
"used decal" ), 7, fieldDecals[i - 1]->record,
1849 aLayout.usedDecalBase + fieldDecals[i - 1]->record * USED_DECAL_BYTES, USED_DECAL_BYTES,
1850 static_cast<int>( aSheetIndex ) );
1852 sourceAt( aSourceName, aVersion, wxS(
"used decal" ), 7, fieldDecals[i]->record,
1853 aLayout.usedDecalBase + fieldDecals[i]->record * USED_DECAL_BYTES, USED_DECAL_BYTES,
1854 static_cast<int>( aSheetIndex ) );
1857 wxString::Format( wxS(
"duplicate field ID; first at v0x%04X %s controller %d record %llu "
1858 "sheet %d offset 0x%llX" ),
1859 first.version, first.objectClass, first.controller,
1860 static_cast<unsigned long long>( first.recordIndex ), first.sheet,
1861 static_cast<unsigned long long>( first.absoluteOffset ) ) );
1868 void decodePageGraphics(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
1869 const DEFINITION_LAYOUT& aLayout,
size_t aSheetIndex,
const wxString& aSourceName,
1870 const std::vector<size_t>& aPageGraphicRecords,
PADS_SCH_MODEL& aModel )
1872 for(
size_t record : aPageGraphicRecords )
1874 const size_t offset = aLayout.symbolBase + record * SYMBOL_RECORD_BYTES;
1875 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"page graphic group" ), 3, record,
1876 offset, SYMBOL_RECORD_BYTES,
static_cast<int>( aSheetIndex ) );
1879 SOURCE_STRING groupName = decodeFixedString( aBytes, offset, 38, nameSource, aModel.diagnostics );
1880 const uint16_t textCountForGroup = aCursor.
U16At( offset + 64 );
1881 const uint16_t lastTextRecord = aCursor.
U16At( offset + 66 );
1882 const bool worksheetCandidate = aCursor.
U16At( offset + 42 ) == 69 && textCountForGroup == 58;
1884 if( textCountForGroup > aLayout.textHeap.recordCount
1885 || ( textCountForGroup != 0 && lastTextRecord >= aLayout.textHeap.recordCount ) )
1886 throwDecodeError( source, wxS(
"page-text ownership leaves controller 1" ) );
1889 textOwner.
source = source;
1891 if( textCountForGroup != 0 )
1893 std::vector<size_t> textRecords( textCountForGroup );
1894 std::vector<bool> visitedTextRecords( aLayout.textHeap.recordCount,
false );
1895 size_t textRecord = lastTextRecord;
1897 for(
size_t reverseIndex = textCountForGroup; reverseIndex != 0; --reverseIndex )
1899 if( textRecord >= aLayout.textHeap.recordCount )
1900 throwDecodeError( source, wxS(
"page-text predecessor leaves controller 1" ) );
1902 if( visitedTextRecords[textRecord] )
1903 throwDecodeError( source, wxS(
"page-text predecessor repeats controller 1 record" ) );
1905 visitedTextRecords[textRecord] =
true;
1907 textRecords[reverseIndex - 1] = textRecord;
1909 if( reverseIndex != 1 )
1911 const size_t predecessor =
1912 aCursor.
U16At( aLayout.textHeap.recordBase + textRecord * TEXT_RECORD_BYTES + 24 );
1914 if( predecessor >= aLayout.textHeap.recordCount )
1915 throwDecodeError( source, wxS(
"page-text predecessor leaves controller 1" ) );
1917 textRecord = predecessor;
1921 for(
size_t textRecordIndex : textRecords )
1922 decodeDefinitionTextRecord( aBytes, aCursor, aSourceName, aSheetIndex, aLayout.textHeap,
1923 textRecordIndex, TEXT_ROLE::PAGE_TEXT, textOwner, aModel );
1926 if( textOwner.graphics.size() != textCountForGroup )
1927 throwDecodeError( source, wxS(
"page-text records do not exactly match declared count" ) );
1931 graphic.source.objectClass = wxS(
"page text" );
1932 const SOURCE_POINT groupOrigin{ decodeCoordinate( aCursor.
U16At( offset + 60 ) ),
1933 decodeCoordinate( aCursor.
U16At( offset + 62 ) ), source };
1937 point.x += groupOrigin.x;
1938 point.y += groupOrigin.y;
1941 graphic.properties.push_back( sourceProperty( wxS(
"page_graphic_group" ), groupName.text, source ) );
1943 if( worksheetCandidate )
1944 graphic.properties.push_back( sourceProperty( wxS(
"worksheet_group" ), groupName.text, source ) );
1946 aModel.graphics.push_back(
1947 { graphic.source, { aModel.sheets[aSheetIndex].id, graphic.source }, std::move( graphic ) } );
1952 if( aGraphic.sheet.id != aModel.sheets[aSheetIndex].id )
1955 return std::ranges::any_of( aGraphic.graphic.properties,
1958 return aProperty.name.text == wxS(
"page_graphic_group" )
1959 && aProperty.value.text == groupName.text;
1963 size_t numericEdgeMarkers = 0;
1964 size_t alphabeticEdgeMarkers = 0;
1965 size_t textGraphics = 0;
1966 size_t drawingGraphics = 0;
1967 bool hasTitleAnchor =
false;
1968 bool hasSheetAnchor =
false;
1969 bool hasRevisionAnchor =
false;
1973 if( !belongsToGroup( pageGraphic ) )
1983 wxString
text = pageGraphic.graphic.text.text.Upper();
1985 if(
text.length() == 1 &&
text[0] >=
'0' &&
text[0] <=
'9' )
1986 ++numericEdgeMarkers;
1988 if(
text.length() == 1 &&
text[0] >=
'A' &&
text[0] <=
'Z' )
1989 ++alphabeticEdgeMarkers;
1991 hasTitleAnchor |=
text == wxS(
"TITLE" ) ||
text.StartsWith( wxS(
"TITLE:" ) );
1992 hasSheetAnchor |=
text == wxS(
"SHEET NUMBER" ) ||
text == wxS(
"NUMBER OF SHEETS" )
1993 ||
text.StartsWith( wxS(
"SHEET:" ) );
1994 hasRevisionAnchor |=
text == wxS(
"REVISION" ) ||
text.StartsWith( wxS(
"REV:" ) )
1995 ||
text.StartsWith( wxS(
"REVISION " ) );
1998 const bool worksheet = drawingGraphics >= 30 && textGraphics >= 30 && numericEdgeMarkers >= 4
1999 && alphabeticEdgeMarkers >= 4 && hasTitleAnchor && hasSheetAnchor
2000 && hasRevisionAnchor;
2005 modelWorksheet.
source = source;
2006 modelWorksheet.
sheet = { aModel.sheets[aSheetIndex].id, source };
2007 modelWorksheet.name = groupName;
2009 for(
auto graphic = aModel.graphics.begin(); graphic != aModel.graphics.end(); )
2011 if( belongsToGroup( *graphic ) )
2013 modelWorksheet.graphics.push_back( std::move( graphic->graphic ) );
2014 graphic = aModel.graphics.erase( graphic );
2022 auto existing = std::ranges::find_if( aModel.worksheets,
2025 return aWorksheet.sheet.id == modelWorksheet.sheet.id;
2028 if( existing == aModel.worksheets.end() )
2030 aModel.worksheets.push_back( std::move( modelWorksheet ) );
2032 else if( !sameWorksheetValue( *existing, modelWorksheet ) )
2035 sourceProperty( wxS(
"distinct_worksheet_layout" ), groupName.text, source );
2039 wxS(
"distinct worksheet layout preserved as schematic page graphics" ) ) );
2043 graphic.properties.push_back( distinct );
2044 aModel.graphics.push_back( { graphic.source,
2045 { aModel.sheets[aSheetIndex].id, graphic.source },
2046 std::move( graphic ) } );
2052 auto canonicalWorksheet = std::ranges::find_if( aModel.worksheets,
2055 return aWorksheet.name.text == wxS(
"DRW5982" );
2058 if( canonicalWorksheet != aModel.worksheets.end() )
2060 using GROUP_KEY = std::pair<uint32_t, wxString>;
2061 std::map<GROUP_KEY, std::vector<const MODEL_GRAPHIC*>> candidates;
2062 std::vector<const MODEL_GRAPHIC*> canonicalGeometry;
2064 for(
const MODEL_GRAPHIC& graphic : canonicalWorksheet->graphics )
2067 canonicalGeometry.push_back( &graphic );
2072 auto groupProperty = std::ranges::find_if( pageGraphic.graphic.properties,
2075 return aProperty.name.text == wxS(
"worksheet_group" );
2078 if( groupProperty != pageGraphic.graphic.properties.end()
2081 candidates[{ pageGraphic.sheet.id.Value(), groupProperty->value.text }].push_back(
2082 &pageGraphic.graphic );
2086 std::set<GROUP_KEY> equivalentGroups;
2088 for(
const auto& [key, geometry] : candidates )
2090 if( geometry.size() == canonicalGeometry.size()
2091 && std::ranges::equal( geometry, canonicalGeometry,
2094 return sameGraphicValue( *aLeft, *aRight );
2097 equivalentGroups.insert( key );
2101 std::erase_if( aModel.graphics,
2104 return std::ranges::any_of(
2105 aGraphic.graphic.properties,
2106 [&]( const SOURCE_PROPERTY& aProperty )
2108 return aProperty.name.text == wxS(
"worksheet_group" )
2109 && equivalentGroups.contains(
2110 { aGraphic.sheet.id.Value(), aProperty.value.text } );
2117 void decodeDefinitionFields(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
2118 const DEFINITION_LAYOUT& aLayout,
size_t aSheetIndex,
const wxString& aSourceName,
2119 const std::vector<USED_DECAL*>& aFieldDecals,
PADS_SCH_MODEL& aModel )
2121 for(
size_t i = 0; i < aFieldDecals.size(); ++i )
2123 USED_DECAL& decal = *aFieldDecals[i];
2124 const size_t definitionOffset = aLayout.symbolBase + decal.definitionRecord * SYMBOL_RECORD_BYTES;
2125 const uint16_t embeddedCount = aCursor.
U16At( definitionOffset + 0x40 );
2126 const uint32_t fieldEnd =
2127 i + 1 < aFieldDecals.size() ? aFieldDecals[i + 1]->fieldStart : aLayout.textHeap.recordCount;
2129 if( decal.fieldStart > fieldEnd )
2130 throwDecodeError( decal.definition->source, wxS(
"definition field slice is not monotone" ) );
2132 if( decal.definition->fields.empty() )
2134 for(
size_t standard = 0; standard < 2; ++standard )
2136 const size_t usedOffset = aLayout.usedDecalBase + decal.record * USED_DECAL_BYTES;
2138 sourceAt( aSourceName, aModel.version, wxS(
"standard definition field" ), 7, decal.record,
2139 usedOffset + 60 + standard * 8, 8,
static_cast<int>( aSheetIndex ) );
2141 field.
source = fieldSource;
2143 standard == 0 ? wxS(
"REF-DES" ) : wxS(
"PART-TYPE" ),
2146 field.position = { decodeLocalCoordinate( aCursor.
U16At( usedOffset + 60 + standard * 8 ) ),
2147 decodeLocalCoordinate( aCursor.
U16At( usedOffset + 62 + standard * 8 ) ),
2150 field.presentation.source = fieldSource;
2151 field.presentation.height = aCursor.
U16At( usedOffset + 88 + standard * 2 );
2152 field.presentation.width = aCursor.
U8At( usedOffset + 96 + standard );
2155 fontSource.length = 2;
2156 const int16_t fontHandle =
static_cast<int16_t
>( aCursor.
U16At( fontSource.absoluteOffset ) );
2157 field.presentation.font = decodedDefinitionFont( fontHandle, fontSource );
2158 field.presentation.properties.push_back( sourceProperty(
2159 wxS(
"font_handle" ), wxString::Format( wxS(
"%d" ), fontHandle ), fontSource ) );
2160 const uint16_t justification = aCursor.
U16At( usedOffset + 66 + standard * 8 );
2161 field.presentation.horizontalJustification = horizontalJustification( justification );
2162 field.presentation.verticalJustification = verticalJustification( justification );
2163 decal.definition->fields.push_back( std::move( field ) );
2167 if( embeddedCount != 0 )
2169 const uint16_t lastEmbeddedRecord = aCursor.
U16At( definitionOffset + 0x42 );
2171 if( embeddedCount > aLayout.textHeap.recordCount || lastEmbeddedRecord >= aLayout.textHeap.recordCount )
2172 throwDecodeError( decal.definition->source,
2173 wxS(
"embedded definition text ownership leaves controller 1" ) );
2175 const size_t firstEmbeddedRecord =
2176 (
static_cast<size_t>( lastEmbeddedRecord ) + aLayout.textHeap.recordCount + 1 - embeddedCount )
2177 % aLayout.textHeap.recordCount;
2179 for(
size_t textIndex = 0; textIndex < embeddedCount; ++textIndex )
2181 decodeDefinitionTextRecord( aBytes, aCursor, aSourceName, aSheetIndex, aLayout.textHeap,
2182 ( firstEmbeddedRecord + textIndex ) % aLayout.textHeap.recordCount,
2183 TEXT_ROLE::EMBEDDED_SYMBOL_TEXT, *decal.definition, aModel );
2187 for(
size_t record = decal.fieldStart; record < fieldEnd; ++record )
2188 decodeDefinitionTextRecord( aBytes, aCursor, aSourceName, aSheetIndex, aLayout.textHeap, record,
2189 TEXT_ROLE::DEFINITION_FIELD, *decal.definition, aModel );
2194 void decodePartTypes(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
2195 const SHEET_CONTROLLERS& aControllers,
const DEFINITION_LAYOUT& aLayout,
size_t aSheetIndex,
2196 const wxString& aSourceName,
const std::vector<USED_DECAL>& aUsedDecals,
2199 size_t signalPinCursor = 0;
2201 for(
size_t record = 0; record < aControllers.pools[8].count; ++record )
2203 const size_t offset = aLayout.partBase + record * PART_TYPE_BYTES;
2204 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"part type" ), 9, record, offset,
2205 PART_TYPE_BYTES,
static_cast<int>( aSheetIndex ) );
2210 part.source = source;
2211 part.name = decodeFixedString( aBytes, offset, 44, nameSource, aModel.diagnostics );
2212 const uint32_t gateStart = aCursor.
U32At( offset + 44 );
2213 const uint32_t pinStart = aCursor.
U32At( offset + 48 );
2214 const uint32_t gateEnd = record + 1 < aControllers.pools[8].count
2215 ? aCursor.
U32At( offset + PART_TYPE_BYTES + 44 )
2216 : aControllers.pools[9].count;
2217 const uint32_t pinEnd = record + 1 < aControllers.pools[8].count
2218 ? aCursor.
U32At( offset + PART_TYPE_BYTES + 48 )
2219 : aControllers.pools[10].count;
2221 if( gateStart > gateEnd || gateEnd > aControllers.pools[9].count || pinStart > pinEnd
2222 || pinEnd > aControllers.pools[10].count )
2224 throwDecodeError( source, wxS(
"part-type gate or pin slice leaves its controller" ) );
2227 if( aCursor.
U16At( offset + 68 ) != gateEnd - gateStart )
2228 throwDecodeError( source, wxS(
"stored gate count does not match controller-10 slice" ) );
2230 size_t partPinCursor = pinStart;
2231 uint32_t unitCursor = 0;
2232 const uint32_t pinNameHeapBase = aCursor.
U32At( offset + 60 );
2234 if( pinNameHeapBase > aLayout.pinNameBytes )
2235 throwDecodeError( source, wxS(
"part-type pin-name base leaves controller 14" ) );
2239 const size_t pinOffset = aLayout.pinBase + aPinRecord * PIN_BYTES;
2240 SOURCE_PROVENANCE pinSource = sourceAt( aSourceName, aModel.version, wxS(
"part pin" ), 11, aPinRecord,
2241 pinOffset, PIN_BYTES,
static_cast<int>( aSheetIndex ) );
2244 numberSource.length = 16;
2245 aDefinitionPin.number =
2246 decodeFixedString( aBytes, pinOffset + 4, 16, numberSource, aModel.diagnostics );
2247 const uint32_t nameOffset = aCursor.
U32At( pinOffset );
2249 if( nameOffset != 0xFFFFFFFF )
2251 if( nameOffset >= aLayout.pinNameBytes - pinNameHeapBase )
2252 throwDecodeError( pinSource, wxS(
"pin-name offset leaves controller 14" ) );
2255 aSourceName, aModel.version, wxS(
"pin name" ), 14, aPinRecord,
2256 aLayout.pinNameBase + pinNameHeapBase + nameOffset,
2257 aLayout.pinNameBytes - pinNameHeapBase - nameOffset,
static_cast<int>( aSheetIndex ) );
2258 aDefinitionPin.name = decodeFixedString( aBytes, aLayout.pinNameBase + pinNameHeapBase + nameOffset,
2259 aLayout.pinNameBytes - pinNameHeapBase - nameOffset,
2260 pinNameSource, aModel.diagnostics );
2263 aDefinitionPin.electricalType =
2266 logicalPin.
source = pinSource;
2267 logicalPin.definitionPin = { aDefinitionPin.id, pinSource };
2268 logicalPin.number = aDefinitionPin.number;
2269 logicalPin.name = aDefinitionPin.name;
2270 logicalPin.electricalType = aDefinitionPin.electricalType;
2271 logicalPin.swapGroup = aCursor.
U8At( pinOffset + 20 );
2272 logicalPin.flags = aCursor.
U16At( pinOffset + 22 );
2273 aDefinitionPin.properties.push_back(
2274 sourceProperty( wxS(
"swap_group" ),
2275 wxString::Format( wxS(
"%u" ), aCursor.
U8At( pinOffset + 20 ) ), pinSource ) );
2276 aGate.pins.push_back( { aDefinitionPin.id, pinSource } );
2277 aGate.logicalPins.push_back( std::move( logicalPin ) );
2280 auto decodeConnectorPin = [&](
size_t aPinRecord )
2282 const size_t pinOffset = aLayout.pinBase + aPinRecord * PIN_BYTES;
2284 sourceAt( aSourceName, aModel.version, wxS(
"connector logical pin" ), 11, aPinRecord,
2285 pinOffset, PIN_BYTES,
static_cast<int>( aSheetIndex ) );
2288 numberSource.length = 16;
2290 pin.source = pinSource;
2291 pin.number = decodeFixedString( aBytes, pinOffset + 4, 16, numberSource, aModel.diagnostics );
2292 const uint32_t nameOffset = aCursor.
U32At( pinOffset );
2294 if( nameOffset != 0xFFFFFFFF )
2296 if( nameOffset >= aLayout.pinNameBytes - pinNameHeapBase )
2297 throwDecodeError( pinSource, wxS(
"pin-name offset leaves controller 14" ) );
2300 aSourceName, aModel.version, wxS(
"connector pin name" ), 14, aPinRecord,
2301 aLayout.pinNameBase + pinNameHeapBase + nameOffset,
2302 aLayout.pinNameBytes - pinNameHeapBase - nameOffset,
static_cast<int>( aSheetIndex ) );
2303 pin.name = decodeFixedString( aBytes, aLayout.pinNameBase + pinNameHeapBase + nameOffset,
2304 aLayout.pinNameBytes - pinNameHeapBase - nameOffset, pinNameSource,
2305 aModel.diagnostics );
2308 pin.swapGroup = aCursor.
U8At( pinOffset + 20 );
2310 pin.flags = aCursor.
U16At( pinOffset + 22 );
2314 for(
size_t gateRecord = gateStart; gateRecord < gateEnd; ++gateRecord )
2316 const size_t gateOffset = aLayout.gateBase + gateRecord * GATE_BYTES;
2317 SOURCE_PROVENANCE gateSource = sourceAt( aSourceName, aModel.version, wxS(
"gate" ), 10, gateRecord,
2318 gateOffset, GATE_BYTES,
static_cast<int>( aSheetIndex ) );
2320 gate.
id =
GATE_ID( aLayout.gateIdBase + gateRecord );
2321 gate.source = gateSource;
2322 const uint16_t pinCount = aCursor.
U16At( gateOffset + 8 );
2323 const uint16_t swapGroup = aCursor.
U16At( gateOffset + 10 );
2324 const uint16_t primaryHandle = aCursor.
U16At( gateOffset );
2329 gate.unit = ++unitCursor;
2331 if( primaryHandle == 0xFFFF || primaryHandle >= aUsedDecals.size()
2332 || !aUsedDecals[primaryHandle].definition )
2334 bool pinDecalGroup = primaryHandle == 0xFFFF && gateRecord + 1 < gateEnd;
2336 for(
size_t member = gateRecord + 1; pinDecalGroup && member < gateEnd; ++member )
2338 const size_t memberOffset = aLayout.gateBase + member * GATE_BYTES;
2339 const uint16_t memberHandle = aCursor.
U16At( memberOffset );
2340 pinDecalGroup = aCursor.
U16At( memberOffset + 8 ) == 0 && memberHandle < aUsedDecals.size()
2341 && aUsedDecals[memberHandle].definition;
2344 if( !pinDecalGroup || partPinCursor + pinCount > pinEnd )
2345 throwDecodeError( gateSource, wxS(
"unresolved symbol definition reference" ) );
2347 part.properties.push_back( sourceProperty(
2348 wxString::Format( wxS(
"pin_decal_group_%llu" ),
2349 static_cast<unsigned long long>( gateRecord - gateStart + 1 ) ),
2350 wxString::Format( wxS(
"%u" ), pinCount ), gateSource ) );
2352 for(
size_t member = gateRecord + 1; member < gateEnd; ++member )
2354 const size_t memberOffset = aLayout.gateBase + member * GATE_BYTES;
2355 const uint16_t memberHandle = aCursor.
U16At( memberOffset );
2357 if( aCursor.
U16At( memberOffset + 8 ) != 0 )
2361 sourceAt( aSourceName, aModel.version, wxS(
"pin-decal group member" ), 10, member,
2362 memberOffset, GATE_BYTES,
static_cast<int>( aSheetIndex ) );
2364 if( memberHandle >= aUsedDecals.size() || !aUsedDecals[memberHandle].definition )
2365 throwDecodeError( memberSource, wxS(
"unresolved pin-decal group member" ) );
2367 gate.decalGroupMembers.push_back( { aUsedDecals[memberHandle].definition->id, memberSource } );
2370 for(
size_t pin = 0;
pin < pinCount; ++
pin, ++partPinCursor )
2371 gate.connectorPins.push_back( decodeConnectorPin( partPinCursor ) );
2373 part.gates.push_back( std::move( gate ) );
2378 gate.definition = { definition->id, gateSource };
2379 gate.properties.push_back(
2380 sourceProperty( wxS(
"swap_group" ), wxString::Format( wxS(
"%u" ), swapGroup ), gateSource ) );
2382 for(
size_t alternate = 1; alternate < 4; ++alternate )
2384 const uint16_t handle = aCursor.
U16At( gateOffset + alternate * 2 );
2386 if( handle == 0xFFFF )
2389 if( handle >= aUsedDecals.size() || !aUsedDecals[handle].definition )
2390 throwDecodeError( gateSource, wxS(
"unresolved alternate symbol definition reference" ) );
2392 gate.alternateDefinitions.push_back( { aUsedDecals[handle].definition->id, gateSource } );
2395 if( partPinCursor + pinCount > pinEnd || pinCount > definition->pins.size() )
2396 throwDecodeError( gateSource, wxS(
"gate pin slice leaves part type or symbol definition" ) );
2398 for(
size_t pin = 0;
pin < pinCount; ++
pin, ++partPinCursor )
2401 decodePartPin( definitionPin, partPinCursor, gate );
2404 part.gates.push_back( std::move( gate ) );
2407 if( partPinCursor != pinEnd )
2408 throwDecodeError( source, wxS(
"part-type gates do not consume its pin slice" ) );
2410 if( !part.gates.empty() && part.gates.front().definition.id.IsValid() )
2412 auto defaultDefinition =
2413 std::ranges::find_if( aModel.definitions,
2416 return aDefinition.id == part.gates.front().definition.id;
2419 if( defaultDefinition == aModel.definitions.end() )
2420 throwDecodeError( source, wxS(
"unresolved part-type default definition" ) );
2422 part.fields = defaultDefinition->fields;
2425 const uint16_t signalPinCount = aCursor.
U16At( offset + 70 );
2427 if( signalPinCursor + signalPinCount > aControllers.pools[11].count )
2428 throwDecodeError( source, wxS(
"part-type signal-pin slice leaves controller 12" ) );
2430 for(
size_t signal = 0; signal < signalPinCount; ++signal, ++signalPinCursor )
2432 const size_t signalOffset = aLayout.signalPinBase + signalPinCursor * SIGNAL_PIN_BYTES;
2434 sourceAt( aSourceName, aModel.version, wxS(
"signal pin" ), 12, signalPinCursor, signalOffset,
2435 SIGNAL_PIN_BYTES,
static_cast<int>( aSheetIndex ) );
2437 numberSource.
length = 16;
2440 nameSource2.length = 48;
2441 SOURCE_STRING number = decodeFixedString( aBytes, signalOffset, 16, numberSource, aModel.diagnostics );
2443 decodeFixedString( aBytes, signalOffset + 16, 48, nameSource2, aModel.diagnostics );
2444 part.signalPins.push_back( { signalSource, number, signalName } );
2445 part.properties.push_back(
2446 sourceProperty( wxS(
"signal_pin_" ) + number.text, signalName.text, signalSource ) );
2449 aModel.partTypes.push_back( std::move( part ) );
2452 if( signalPinCursor != aControllers.pools[11].count )
2454 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"signal pin" ), 12, signalPinCursor,
2455 aLayout.signalPinBase, aControllers.pools[11].usedBytes,
2456 static_cast<int>( aSheetIndex ) );
2457 throwDecodeError( source, wxS(
"unowned signal-pin record" ) );
2462 void decodeDefinitionsAndParts(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
2463 const SCH_SDB_BLOCK& aBlock,
size_t aSheetIndex,
const wxString& aSourceName,
2466 const SHEET_CONTROLLERS controllers = sheetControllers( aCursor, aBlock );
2468 if( aModel.version == 0x000C )
2470 preserveRawDefinitionControllers( aBytes, controllers, aSheetIndex, aSourceName, aModel );
2474 const DEFINITION_LAYOUT layout =
2475 definitionLayout( aCursor, controllers, aSheetIndex, aSourceName, aModel.version );
2477 std::vector<MODEL_SYMBOL_DEFINITION*> definitionsByRecord( controllers.pools[2].count );
2478 std::vector<size_t> pageGraphicRecords;
2479 std::vector<USED_DECAL> usedDecals( controllers.pools[6].count );
2480 std::vector<USED_DECAL*> semanticDecals;
2482 decodeSymbolDefinitions( aBytes, aCursor, controllers, layout, aSheetIndex, aSourceName, definitionsByRecord,
2483 pageGraphicRecords, aModel );
2484 decodeUsedDecals( aBytes, aCursor, controllers, layout, aSheetIndex, aSourceName, definitionsByRecord,
2485 usedDecals, semanticDecals, aModel );
2487 const std::vector<USED_DECAL*> fieldDecals =
2488 buildFieldDecals( semanticDecals, layout, aSheetIndex, aSourceName, aModel.version );
2490 decodePageGraphics( aBytes, aCursor, layout, aSheetIndex, aSourceName, pageGraphicRecords, aModel );
2491 decodeDefinitionFields( aBytes, aCursor, layout, aSheetIndex, aSourceName, fieldDecals, aModel );
2492 decodePartTypes( aBytes, aCursor, controllers, layout, aSheetIndex, aSourceName, usedDecals, aModel );
2496 struct PLACEMENT_DECODE
2498 const std::vector<uint8_t>& bytes;
2499 const PADS_IO::BINARY_CURSOR& cursor;
2500 const SHEET_CONTROLLERS& controllers;
2501 const PLACEMENT_LAYOUT& layout;
2502 const PLACEMENT_GLOBALS& globals;
2503 const wxString& sourceName;
2508 struct PLACEMENT_TARGET
2510 const MODEL_PART_TYPE* part =
nullptr;
2511 const MODEL_SYMBOL_DEFINITION* definition =
nullptr;
2512 const MODEL_GATE* gate =
nullptr;
2513 uint32_t componentIdentity = 0;
2514 uint32_t groupHandle = 0;
2515 uint32_t attributeStart = 0;
2516 uint16_t attributeCount = 0;
2517 uint16_t decalHandle = 0;
2518 uint16_t unitIndex = 0;
2522 struct INLINE_FIELD_LAYOUT
2532 std::pair<SOURCE_STRING, SOURCE_STRING> decodePlacementAttribute(
const PLACEMENT_DECODE& aDecode,
2533 uint32_t aOffsetIndex,
bool aRequireValue,
2536 if( aOffsetIndex >= aDecode.globals.attributeOffsetCount )
2539 sourceAt( aDecode.sourceName, aModel.version, wxS(
"attribute offset" ), 7, aOffsetIndex,
2540 aDecode.globals.attributeOffsetBase, ATTRIBUTE_OFFSET_BYTES, -1 );
2541 throwDecodeError( source, wxS(
"attribute offset index leaves outer controller 7" ) );
2544 const size_t offsetRecord = aDecode.globals.attributeOffsetBase + aOffsetIndex * ATTRIBUTE_OFFSET_BYTES;
2545 const uint32_t heapOffset = aDecode.cursor.U32At( offsetRecord );
2546 SOURCE_PROVENANCE source = sourceAt( aDecode.sourceName, aModel.version, wxS(
"placement attribute" ), 2,
2547 aOffsetIndex, aDecode.globals.attributeHeapBase + heapOffset, 0, -1 );
2549 if( heapOffset >= aDecode.globals.attributeHeapBytes )
2550 throwDecodeError( source, wxS(
"attribute string offset leaves outer controller 2" ) );
2552 size_t end = source.absoluteOffset;
2553 const size_t heapEnd = aDecode.globals.attributeHeapBase + aDecode.globals.attributeHeapBytes;
2555 while(
end < heapEnd && aDecode.bytes[
end] != 0 )
2558 if(
end == heapEnd )
2559 throwDecodeError( source, wxS(
"placement attribute is not NUL terminated" ) );
2561 source.length =
end - source.absoluteOffset;
2562 size_t separator = source.absoluteOffset;
2564 while( separator <
end && aDecode.bytes[separator] != 1 )
2567 if( aRequireValue && separator ==
end )
2568 throwDecodeError( source, wxS(
"placement attribute lacks key/value separator" ) );
2571 { aDecode.bytes.begin() + source.absoluteOffset, aDecode.bytes.begin() + separator }, DEFAULT_CODE_PAGE,
2572 source, aModel.diagnostics );
2575 if( separator !=
end )
2579 valueSource.length =
end - separator - 1;
2581 { aDecode.bytes.begin() + valueSource.absoluteOffset, aDecode.bytes.begin() +
end },
2582 DEFAULT_CODE_PAGE, valueSource, aModel.diagnostics );
2585 return std::pair<SOURCE_STRING, SOURCE_STRING>{ std::move(
name ), std::move( value ) };
2589 PLACEMENT_TARGET resolvePlacementTarget(
const PLACEMENT_DECODE& aDecode,
size_t aOffset,
2592 PLACEMENT_TARGET target;
2593 target.componentIdentity = aDecode.cursor.U32At( aOffset + aDecode.layout.componentIdentity );
2595 const uint16_t partHandle = aDecode.cursor.U16At( aOffset + aDecode.layout.partType );
2596 auto part = std::ranges::find_if( aModel.partTypes,
2599 return aPart.source.sheet == static_cast<int>( aDecode.sheetIndex )
2600 && aPart.source.recordIndex == partHandle;
2603 if( part == aModel.partTypes.end() )
2605 const bool definitionClass =
2606 std::ranges::any_of( aModel.definitions,
2609 return aDefinition.source.sheet == static_cast<int>( aDecode.sheetIndex )
2610 && aDefinition.source.recordIndex == partHandle;
2612 throwDecodeError( aSource, definitionClass
2613 ? wxS(
"placement part-type handle targets definition object class" )
2614 : wxS(
"unresolved placement part-type reference" ) );
2617 const uint32_t groupHandle = aDecode.cursor.U32At( aOffset + aDecode.layout.componentGroup );
2619 if( groupHandle >= aDecode.globals.groupCount )
2620 throwDecodeError( aSource, wxS(
"placement component-group handle leaves outer controller 6" ) );
2622 const size_t groupOffset = aDecode.globals.groupBase + groupHandle * PLACEMENT_GROUP_BYTES;
2623 const uint32_t attributeStart = aDecode.cursor.U32At( groupOffset );
2624 const uint16_t attributeCount = aDecode.cursor.U16At( groupOffset + 20 );
2626 if( attributeCount < 2 || attributeStart > aDecode.globals.attributeOffsetCount
2627 || attributeCount > aDecode.globals.attributeOffsetCount - attributeStart )
2629 throwDecodeError( aSource, wxS(
"placement component-group attribute slice leaves controller 7" ) );
2632 auto [groupPartName, unusedGroupPartValue] =
2633 decodePlacementAttribute( aDecode, attributeStart + 1,
false, aModel );
2635 if( groupPartName.text != part->name.text )
2636 throwDecodeError( aSource, wxS(
"placement component-group targets wrong part-type object class" ) );
2638 const uint16_t decalHandle = aDecode.cursor.U16At( aOffset + aDecode.layout.decal );
2640 if( decalHandle >= aDecode.controllers.pools[6].count )
2641 throwDecodeError( aSource, wxS(
"unresolved placement decal reference" ) );
2643 const size_t decalOffset = aDecode.controllers.offsets[6] + decalHandle * USED_DECAL_BYTES;
2644 const uint32_t definitionHandle = aDecode.cursor.U32At( decalOffset + 48 );
2646 std::ranges::find_if( aModel.definitions,
2649 return aDefinition.source.sheet == static_cast<int>( aDecode.sheetIndex )
2650 && aDefinition.source.recordIndex == definitionHandle;
2653 if( definition == aModel.definitions.end() )
2654 throwDecodeError( aSource, wxS(
"placement decal targets wrong definition object class" ) );
2656 const uint16_t unitIndex = aDecode.cursor.U16At( aOffset + aDecode.layout.gate );
2660 if( unitIndex < part->gates.size() )
2661 gate = &part->gates[unitIndex];
2662 else if( part->gates.size() == 1 && !part->gates.front().decalGroupMembers.empty() )
2663 gate = &part->gates.front();
2664 else if( part->gates.empty() && unitIndex == 0 && definition->pins.empty() )
2668 const bool definitionClass =
2669 std::ranges::any_of( aModel.definitions,
2672 return aDefinition.source.sheet == static_cast<int>( aDecode.sheetIndex )
2673 && aDefinition.source.recordIndex == unitIndex;
2675 throwDecodeError( aSource, definitionClass ? wxS(
"placement gate handle targets definition object class" )
2676 : wxS(
"unresolved placement gate reference" ) );
2681 return aReference.id == definition->id;
2684 if( gate && !gateHasDefinition( gate->definition )
2685 && std::ranges::none_of( gate->alternateDefinitions, gateHasDefinition )
2686 && std::ranges::none_of( gate->decalGroupMembers, gateHasDefinition ) )
2688 throwDecodeError( aSource, wxS(
"placement decal and gate reference target different object classes" ) );
2691 target.part = &*part;
2692 target.definition = &*definition;
2694 target.groupHandle = groupHandle;
2695 target.attributeStart = attributeStart;
2696 target.attributeCount = attributeCount;
2697 target.decalHandle = decalHandle;
2698 target.unitIndex = unitIndex;
2703 void decodePlacedPins(
const PLACEMENT_DECODE& aDecode, uint32_t aPinStart, uint16_t aPinCount,
2707 const size_t placedPinBase = aDecode.controllers.offsets[15];
2709 for(
size_t pin = 0;
pin < aPinCount; ++
pin )
2711 const size_t pinOffset = placedPinBase + ( aPinStart +
pin ) * aDecode.layout.placedPinBytes;
2713 sourceAt( aDecode.sourceName, aModel.version, wxS(
"placed pin" ), 16, aPinStart +
pin, pinOffset,
2714 aDecode.layout.placedPinBytes,
static_cast<int>( aDecode.sheetIndex ) );
2715 const uint16_t pinOrdinal = aDecode.cursor.U16At( pinOffset + aDecode.layout.placedPinOrdinal );
2717 if( pinOrdinal >= aDefinition.pins.size() || pinOrdinal !=
pin )
2718 throwDecodeError( pinSource, wxS(
"placed-pin handle leaves placement definition" ) );
2722 static_cast<int64_t
>(
static_cast<int16_t
>( aDecode.cursor.U16At( pinOffset + 6 ) ) ) * 2,
2723 static_cast<int64_t
>(
static_cast<int16_t
>( aDecode.cursor.U16At( pinOffset + 8 ) ) ) * 2, pinSource
2725 pinReference.numberPresentationFlags = aDecode.cursor.U16At( pinOffset + 10 );
2726 pinReference.numberAngle = ( pinReference.numberPresentationFlags & 0x0001 ) != 0 ? 900 : 0;
2728 pinReference.numberJustification = terminalJustification(
2729 ( pinReference.numberPresentationFlags >> 4 ) & 0x0F, pinReference.numberAngle != 0 );
2731 pinReference.hasNumberPlacement =
true;
2732 aPlacement.pins.push_back( std::move( pinReference ) );
2737 void decodeInlineField(
const PLACEMENT_DECODE& aDecode,
size_t aOffset,
const SOURCE_PROVENANCE& aSource,
2738 const wxString& aName,
const SOURCE_STRING& aValue,
bool aVisible,
2743 fieldSource.
objectClass = wxS(
"placement field" );
2744 fieldSource.absoluteOffset += aFieldLayout.position;
2745 fieldSource.length = 8;
2747 field.
source = fieldSource;
2748 field.name.text = aName;
2749 field.name.source = fieldSource;
2750 field.value = aValue;
2751 field.position = { decodeLocalCoordinate( aDecode.cursor.U16At( aOffset + aFieldLayout.position ) ),
2752 decodeLocalCoordinate( aDecode.cursor.U16At( aOffset + aFieldLayout.position + 2 ) ),
2754 field.angle =
NormalizeAngle( aDecode.cursor.U16At( aOffset + aFieldLayout.angle ) );
2755 field.presentation.source = fieldSource;
2756 field.visible = aVisible;
2757 field.presentation.visible = aVisible;
2758 field.presentation.height = aDecode.cursor.U16At( aOffset + aFieldLayout.height );
2759 field.presentation.width = aDecode.cursor.U8At( aOffset + aFieldLayout.width );
2760 const uint16_t justification = aDecode.cursor.U16At( aOffset + aFieldLayout.angle + 2 );
2761 field.presentation.horizontalJustification = horizontalJustification( justification );
2762 field.presentation.verticalJustification = verticalJustification( justification );
2765 fontSource.length = 2;
2766 decodeGlobalFont( aDecode.bytes, aDecode.cursor, aDecode.globals, aDecode.sourceName, aModel.version,
2767 static_cast<int16_t
>( aDecode.cursor.U16At( aOffset + aFieldLayout.font ) ), fontSource,
2768 field.presentation,
false, aModel.diagnostics );
2769 aPlacement.fields.push_back( std::move( field ) );
2773 void decodeCustomFields(
const PLACEMENT_DECODE& aDecode,
size_t aOffset,
const SOURCE_PROVENANCE& aSource,
2774 const PLACEMENT_TARGET& aTarget, uint32_t& aFieldCursor,
MODEL_PLACEMENT& aPlacement,
2777 const size_t fieldBase = aDecode.controllers.offsets[16];
2779 const uint16_t customFieldCount = aDecode.cursor.U16At( aOffset + aDecode.layout.fieldCount );
2781 if( aFieldCursor > aDecode.controllers.pools[16].count
2782 || customFieldCount > aDecode.controllers.pools[16].count - aFieldCursor )
2784 throwDecodeError( aSource, wxS(
"placement field ownership does not match controller 17" ) );
2787 for(
size_t fieldOrdinal = 0; fieldOrdinal < customFieldCount; ++fieldOrdinal )
2789 const size_t fieldOffset = fieldBase + aFieldCursor * aDecode.layout.fieldBytes;
2791 sourceAt( aDecode.sourceName, aModel.version, wxS(
"placement field" ), 17, aFieldCursor,
2792 fieldOffset, aDecode.layout.fieldBytes,
static_cast<int>( aDecode.sheetIndex ) );
2796 const uint8_t displayFlags = aDecode.cursor.U8At( fieldOffset + aDecode.layout.customDisplayFlags );
2797 const uint16_t attributeIndex = aDecode.cursor.U16At( fieldOffset + aDecode.layout.customAttributeIndex );
2799 if( attributeIndex == 0xFFFF )
2801 name.text = wxS(
"*" );
2802 name.source = fieldSource;
2803 value.source = fieldSource;
2807 if( attributeIndex >= aTarget.attributeCount )
2808 throwDecodeError( fieldSource, wxS(
"placement field attribute index leaves component group" ) );
2810 std::tie(
name, value ) =
2811 decodePlacementAttribute( aDecode, aTarget.attributeStart + attributeIndex,
true, aModel );
2814 field.
source = fieldSource;
2815 field.name = std::move(
name );
2816 field.value = std::move( value );
2818 decodeLocalCoordinate( aDecode.cursor.U16At( fieldOffset + aDecode.layout.customX ) ),
2819 decodeLocalCoordinate( aDecode.cursor.U16At( fieldOffset + aDecode.layout.customX + 2 ) ), fieldSource
2821 field.angle =
NormalizeAngle( aDecode.cursor.U16At( fieldOffset + aDecode.layout.customAngle ) );
2823 if( field.angle % 900 != 0 )
2824 throwDecodeError( fieldSource, wxS(
"unsupported placement-field rotation" ) );
2826 const uint8_t justification = aDecode.cursor.U8At( fieldOffset + aDecode.layout.customJustification );
2827 field.presentation.source = fieldSource;
2828 field.presentation.horizontalJustification = horizontalJustification( justification );
2829 field.presentation.verticalJustification = verticalJustification( justification );
2830 field.presentation.height = aDecode.cursor.U16At( fieldOffset + aDecode.layout.customHeight );
2831 field.presentation.width = aDecode.cursor.U8At( fieldOffset + aDecode.layout.customWidth );
2832 field.presentation.visible = ( displayFlags & 8 ) == 0;
2833 field.visible = field.presentation.visible;
2837 decodeGlobalFont( aDecode.bytes, aDecode.cursor, aDecode.globals, aDecode.sourceName, aModel.version,
2838 static_cast<int16_t
>( aDecode.cursor.U16At( fieldOffset + aDecode.layout.customFont ) ),
2839 fontSource, field.presentation,
true, aModel.diagnostics );
2840 field.properties.push_back( sourceProperty( wxS(
"display_flags" ),
2841 wxString::Format( wxS(
"%u" ), displayFlags ), fieldSource ) );
2843 attributeIndexSource.
absoluteOffset += aDecode.layout.customAttributeIndex;
2844 attributeIndexSource.length = 2;
2845 field.properties.push_back( sourceProperty(
2846 wxS(
"component_attribute_index" ),
2847 wxString::Format( wxS(
"%u" ),
2848 aDecode.cursor.U16At( fieldOffset + aDecode.layout.customAttributeIndex ) ),
2849 attributeIndexSource ) );
2851 wxS(
"preserved_field_tail" ),
2852 wxString::Format( wxS(
"%u" ), aDecode.cursor.U16At( fieldOffset + aDecode.layout.customTail ) ),
2855 field.properties.push_back( std::move( preservedTail ) );
2856 aPlacement.fields.push_back( std::move( field ) );
2862 void decodeGroupAttributeFields(
const PLACEMENT_DECODE& aDecode,
const PLACEMENT_TARGET& aTarget,
2865 for( uint16_t attributeIndex = 2; attributeIndex < aTarget.attributeCount; ++attributeIndex )
2867 auto [
name, value] =
2868 decodePlacementAttribute( aDecode, aTarget.attributeStart + attributeIndex,
true, aModel );
2870 if(
name.text.CmpNoCase( wxS(
"PCB DECAL" ) ) == 0
2871 || std::ranges::any_of( aPlacement.fields,
2874 return aField.name.text.CmpNoCase( name.text ) == 0;
2882 field.name = std::move(
name );
2883 field.value = std::move( value );
2884 field.visible =
false;
2885 field.presentation.source = field.source;
2886 field.presentation.visible =
false;
2887 aPlacement.fields.push_back( std::move( field ) );
2892 void recordPlacementProperties(
const PLACEMENT_DECODE& aDecode,
const SOURCE_PROVENANCE& aSource,
2893 const PLACEMENT_TARGET& aTarget, uint16_t aRawAngle,
MODEL_PLACEMENT& aPlacement )
2897 rawAngleSource.length = 2;
2898 aPlacement.properties.push_back(
2899 sourceProperty( wxS(
"raw_angle" ), wxString::Format( wxS(
"%u" ), aRawAngle ), rawAngleSource ) );
2900 aPlacement.properties.push_back( sourceProperty(
2901 wxS(
"component_identity" ), wxString::Format( wxS(
"%u" ), aTarget.componentIdentity ), aSource ) );
2902 aPlacement.properties.push_back( sourceProperty(
2903 wxS(
"component_group_handle" ), wxString::Format( wxS(
"%u" ), aTarget.groupHandle ), aSource ) );
2904 aPlacement.properties.push_back( sourceProperty(
2905 wxS(
"decal_handle" ), wxString::Format( wxS(
"%u" ), aTarget.decalHandle ), aSource ) );
2908 rawMirrorSource.length = 2;
2909 aPlacement.properties.push_back( sourceProperty(
2910 wxS(
"raw_mirror" ), wxString::Format( wxS(
"%u" ), aPlacement.mirrorFlags ), rawMirrorSource ) );
2912 visibilitySource.
absoluteOffset += aDecode.layout.itemVisibility;
2913 visibilitySource.length = 1;
2914 aPlacement.properties.push_back(
2915 sourceProperty( wxS(
"item_visibility_flags" ),
2916 wxString::Format( wxS(
"%u" ), aPlacement.itemVisibilityFlags ), visibilitySource ) );
2920 void decodePlacementRecord(
const PLACEMENT_DECODE& aDecode,
size_t aRecord, uint32_t& aExpectedPinStart,
2923 const PLACEMENT_LAYOUT& layout = aDecode.layout;
2924 const size_t offset = aDecode.controllers.offsets[14] + aRecord * layout.placementBytes;
2926 SOURCE_PROVENANCE source = sourceAt( aDecode.sourceName, aModel.version, wxS(
"placement" ), 15, aRecord,
2927 offset, layout.placementBytes,
static_cast<int>( aDecode.sheetIndex ) );
2929 const PLACEMENT_TARGET target = resolvePlacementTarget( aDecode, offset, source, aModel );
2930 const uint32_t pinStart = aDecode.cursor.U32At( offset + layout.pinStart );
2931 const uint16_t pinCount = aDecode.cursor.U16At( offset + layout.pinCount );
2933 if( pinStart != aExpectedPinStart || pinStart > aDecode.controllers.pools[15].count
2934 || pinCount > aDecode.controllers.pools[15].count - pinStart || pinCount != target.definition->pins.size() )
2936 throwDecodeError( source, wxS(
"placement pin ownership does not match controller 16" ) );
2941 if( aDecode.sheetIndex >= 0x0FFF || aRecord >= 0x100000 )
2942 throwDecodeError( source, wxS(
"placement identity exceeds sheet/controller namespace" ) );
2944 placement.id =
PLACEMENT_ID(
static_cast<uint32_t
>( aDecode.sheetIndex * 0x100000 + aRecord + 1 ) );
2945 placement.source = source;
2946 placement.sheet = { aModel.sheets[aDecode.sheetIndex].id, source };
2947 placement.partType = { target.part->id, source };
2952 placement.definition = { target.definition->id, source };
2953 placement.unit = target.unitIndex + 1;
2954 placement.position = { decodeCoordinate( aDecode.cursor.U16At( offset + layout.x ) ),
2955 decodeCoordinate( aDecode.cursor.U16At( offset + layout.y ) ), source };
2956 const uint16_t rawAngle = aDecode.cursor.U16At( offset + layout.angle );
2958 placement.mirrorFlags = aDecode.cursor.U16At( offset + layout.mirror );
2960 auto recordTransformEnum = [&](
size_t aFieldOffset )
2964 enumSource.length = 2;
2965 aModel.diagnostics.push_back(
2969 const bool knownAngle = rawAngle == 0 || rawAngle == 900 || rawAngle == 1800 || rawAngle == 2700;
2972 recordTransformEnum( layout.angle );
2974 const bool knownMirror = placement.mirrorFlags <= 3;
2977 recordTransformEnum( layout.mirror );
2979 placement.mirrored = placement.mirrorFlags != 0;
2982 referenceSource.length = 40;
2983 placement.reference =
2984 decodeFixedString( aDecode.bytes, offset + layout.reference, 40, referenceSource, aModel.diagnostics );
2986 decodePlacedPins( aDecode, pinStart, pinCount, *target.definition, placement, aModel );
2987 aExpectedPinStart += pinCount;
2989 placement.itemVisibilityFlags = aDecode.cursor.U8At( offset + layout.itemVisibility );
2990 placement.referenceVisible = ( placement.itemVisibilityFlags & 0x01 ) == 0;
2991 placement.partTypeVisible = ( placement.itemVisibilityFlags & 0x02 ) == 0;
2992 placement.pinNamesVisible = ( placement.itemVisibilityFlags & 0x08 ) == 0;
2993 placement.pinNumbersVisible = ( placement.itemVisibilityFlags & 0x10 ) == 0;
2995 decodeInlineField( aDecode, offset, source, wxS(
"REF-DES" ), placement.reference, placement.referenceVisible,
2996 { layout.referenceField, layout.referenceFieldAngle, layout.referenceFont,
2997 layout.referenceHeight, layout.referenceWidth },
2998 placement, aModel );
2999 decodeInlineField( aDecode, offset, source, wxS(
"PART-TYPE" ), target.part->name, placement.partTypeVisible,
3000 { layout.partTypeField, layout.partTypeFieldAngle, layout.partTypeFont,
3001 layout.partTypeHeight, layout.partTypeWidth },
3002 placement, aModel );
3003 decodeCustomFields( aDecode, offset, source, target, aFieldCursor, placement, aModel );
3004 decodeGroupAttributeFields( aDecode, target, placement, aModel );
3005 recordPlacementProperties( aDecode, source, target, rawAngle, placement );
3006 aModel.placements.push_back( std::move( placement ) );
3010 void decodePlacements(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
3011 const SCH_SDB_BLOCK& aBlock,
size_t aSheetIndex,
const wxString& aSourceName,
3014 const PLACEMENT_LAYOUT& layout = placementLayout( aModel.version );
3016 if( !layout.decoded )
3017 throwDecodeError( aModel.source, wxS(
"placement decoder selected for raw-preserved version" ) );
3019 const SHEET_CONTROLLERS controllers = sheetControllers( aCursor, aBlock );
3020 requireFixedController( controllers, 15, layout.placementBytes, aSourceName, aModel.version, aSheetIndex );
3021 requireFixedController( controllers, 16, layout.placedPinBytes, aSourceName, aModel.version, aSheetIndex );
3022 requireFixedController( controllers, 17, layout.fieldBytes, aSourceName, aModel.version, aSheetIndex );
3024 const PLACEMENT_DECODE
decode{ aBytes, aCursor, controllers, layout, aGlobals, aSourceName, aSheetIndex };
3025 uint32_t expectedPinStart = 0;
3026 uint32_t fieldCursor = 0;
3028 for(
size_t record = 0; record < controllers.pools[14].count; ++record )
3029 decodePlacementRecord( decode, record, expectedPinStart, fieldCursor, aModel );
3031 if( expectedPinStart != controllers.pools[15].count )
3033 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"placed pin" ), 16, expectedPinStart,
3034 controllers.offsets[15], controllers.pools[15].usedBytes,
3035 static_cast<int>( aSheetIndex ) );
3036 throwDecodeError( source, wxS(
"unowned placed-pin record" ) );
3039 if( fieldCursor != controllers.pools[16].count )
3041 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"placement field" ), 17, fieldCursor,
3042 controllers.offsets[16], controllers.pools[16].usedBytes,
3043 static_cast<int>( aSheetIndex ) );
3044 throwDecodeError( source, wxS(
"unowned placement-field record" ) );
3049 struct CONNECTIVITY_DECODE
3051 const std::vector<uint8_t>& bytes;
3052 const PADS_IO::BINARY_CURSOR& cursor;
3053 const SHEET_CONTROLLERS& controllers;
3054 const CONNECTIVITY_GLOBALS& globals;
3055 const wxString& sourceName;
3058 size_t junctionBase;
3060 size_t connectionBase;
3066 struct VERTEX_TILING
3068 std::vector<uint32_t> starts;
3069 std::vector<uint32_t> ends;
3073 struct SHEET_CONNECTIVITY
3075 std::vector<MODEL_NET*> sheetNets;
3076 std::vector<MODEL_CONNECTION*> connections;
3077 std::vector<MODEL_NET*> connectionNets;
3078 std::vector<std::vector<size_t>> junctionBacklinks;
3079 std::vector<std::vector<size_t>> offpageBacklinks;
3083 std::pair<int64_t, int64_t> transformedPinPosition(
const MODEL_PLACEMENT& aPlacement,
3086 int64_t x = aPin.position.x;
3087 int64_t y = aPin.position.y;
3091 case 900: std::tie( x, y ) = std::pair{ -y, x };
break;
3092 case 1800: std::tie( x, y ) = std::pair{ -x, -y };
break;
3093 case 2700: std::tie( x, y ) = std::pair{ y, -x };
break;
3097 if( aPlacement.mirrorFlags & 1 )
3100 if( aPlacement.mirrorFlags & 2 )
3103 return std::pair{ aPlacement.position.x + x, aPlacement.position.y + y };
3107 SOURCE_POINT offpagePosition(
const CONNECTIVITY_DECODE& aDecode,
size_t aRecord, uint16_t aVersion )
3109 const size_t offset = aDecode.offpageBase + aRecord * OFFPAGE_RECORD_BYTES;
3110 SOURCE_PROVENANCE source = sourceAt( aDecode.sourceName, aVersion, wxS(
"off-page reference" ), 20, aRecord,
3111 offset, OFFPAGE_RECORD_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3112 return SOURCE_POINT{ decodeCoordinate( aDecode.cursor.U16At( offset + 22 ) ),
3113 decodeCoordinate( aDecode.cursor.U16At( offset + 24 ) ), source };
3117 SOURCE_POINT junctionPosition(
const CONNECTIVITY_DECODE& aDecode,
size_t aRecord, uint16_t aVersion )
3119 const size_t offset = aDecode.junctionBase + aRecord * JUNCTION_RECORD_BYTES;
3120 SOURCE_PROVENANCE source = sourceAt( aDecode.sourceName, aVersion, wxS(
"junction" ), 19, aRecord, offset,
3121 JUNCTION_RECORD_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3122 return SOURCE_POINT{ decodeCoordinate( aDecode.cursor.U16At( offset + 4 ) ),
3123 decodeCoordinate( aDecode.cursor.U16At( offset + 6 ) ), source };
3127 std::unordered_set<uint32_t> decodeBusGlobalRecords(
const CONNECTIVITY_DECODE& aDecode,
PADS_SCH_MODEL& aModel )
3129 std::unordered_set<uint32_t> busGlobalRecords;
3131 for(
size_t record = 0; record < aDecode.controllers.pools[17].count; ++record )
3133 const uint32_t globalRecord = aDecode.cursor.U32At( aDecode.busBase + record * BUS_RECORD_BYTES + 8 );
3134 const uint8_t netKind = globalRecord < aDecode.globals.nets.size()
3135 ? aDecode.globals.nets[globalRecord].kindFlags & 0xFF
3138 if( globalRecord >= aDecode.globals.nets.size() || aDecode.globals.nets[globalRecord].tombstone
3139 || ( netKind != 1 && netKind != 5 ) )
3141 SOURCE_PROVENANCE source = sourceAt( aDecode.sourceName, aModel.version, wxS(
"bus" ), 18, record,
3142 aDecode.busBase + record * BUS_RECORD_BYTES, BUS_RECORD_BYTES,
3143 static_cast<int>( aDecode.sheetIndex ) );
3144 throwDecodeError( source, wxS(
"bus global-net handle targets wrong or unresolved object class" ) );
3147 busGlobalRecords.insert( globalRecord );
3150 return busGlobalRecords;
3154 std::vector<MODEL_NET*> materializeSheetNets(
const CONNECTIVITY_DECODE& aDecode,
3155 const std::unordered_set<uint32_t>& aBusGlobalRecords,
3158 std::vector<MODEL_NET*> sheetNets( aDecode.globals.nets.size(),
nullptr );
3161 aModel.nets.reserve( aModel.nets.size() + aDecode.globals.membershipCount );
3163 for(
size_t globalRecord = 0; globalRecord < aDecode.globals.nets.size(); ++globalRecord )
3165 const GLOBAL_NET_RECORD& global = aDecode.globals.nets[globalRecord];
3167 if( global.tombstone )
3170 for( uint32_t membership = global.membershipStart;
3171 membership < global.membershipStart + global.membershipCount; ++membership )
3173 if( aDecode.globals.membershipSheets[membership] != aDecode.sheetIndex )
3176 if( global.aliasCount != 0 || aBusGlobalRecords.contains( globalRecord ) )
3180 net.id =
NET_ID( membership );
3181 net.source = global.source;
3182 net.source.sheet =
static_cast<int>( aDecode.sheetIndex );
3184 sourceAt( aDecode.sourceName, aModel.version, wxS(
"net sheet membership" ), 4, membership,
3185 aDecode.globals.membershipBase + membership * NET_MEMBERSHIP_BYTES,
3186 NET_MEMBERSHIP_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3187 net.sheet = { aModel.sheets[aDecode.sheetIndex].id, membershipSource };
3188 net.name = global.name;
3189 net.properties.push_back( sourceProperty(
3190 wxS(
"global_net_record" ), wxString::Format( wxS(
"%llu" ), globalRecord ), global.source ) );
3192 sourceProperty( wxS(
"preserved_net_identity" ),
3193 wxString::Format( wxS(
"%u" ), global.preservedIdentity ), global.source );
3195 net.properties.push_back( std::move( identity ) );
3197 relationshipSource.absoluteOffset += 84;
3198 relationshipSource.length = 4;
3200 wxS(
"preserved_net_relationship" ),
3201 wxString::Format( wxS(
"%u" ), global.preservedRelationship ), relationshipSource );
3203 net.properties.push_back( std::move( relationship ) );
3204 aModel.nets.push_back( std::move( net ) );
3205 sheetNets[globalRecord] = &aModel.nets.back();
3213 std::unordered_map<uint32_t, const MODEL_PIN_DEFINITION*> indexDefinitionPins(
const PADS_SCH_MODEL& aModel )
3215 std::unordered_map<uint32_t, const MODEL_PIN_DEFINITION*> definitionPins;
3220 definitionPins.emplace(
pin.id.Value(), &
pin );
3223 return definitionPins;
3227 std::vector<MODEL_PLACEMENT*> indexSheetPlacements(
const CONNECTIVITY_DECODE& aDecode,
PADS_SCH_MODEL& aModel )
3229 std::vector<MODEL_PLACEMENT*> placements( aDecode.controllers.pools[14].count,
nullptr );
3233 if( placement.source.sheet !=
static_cast<int>( aDecode.sheetIndex ) )
3236 if( placement.source.recordIndex >= placements.size() || placements[placement.source.recordIndex] )
3237 throwDecodeError( placement.source, wxS(
"placement endpoint identity leaves controller 15" ) );
3239 placements[placement.source.recordIndex] = &placement;
3246 void decodeJunctions(
const CONNECTIVITY_DECODE& aDecode,
PADS_SCH_MODEL& aModel )
3248 for(
size_t record = 0; record < aDecode.controllers.pools[18].count; ++record )
3250 const size_t offset = aDecode.junctionBase + record * JUNCTION_RECORD_BYTES;
3252 sourceAt( aDecode.sourceName, aModel.version, wxS(
"junction" ), 19, record, offset,
3253 JUNCTION_RECORD_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3255 const uint16_t status = aDecode.cursor.U16At( offset + 10 );
3257 if( status != 0 && status != 0x00FC && status != 0x00FD )
3258 throwDecodeError( source, wxS(
"invalid junction object-class marker" ) );
3261 junction.
source = source;
3262 junction.
sheet = { aModel.sheets[aDecode.sheetIndex].id, source };
3263 junction.position = junctionPosition( aDecode, record, aModel.version );
3266 connectionSource.length = 2;
3267 junction.properties.push_back( sourceProperty(
3268 wxS(
"connection_record" ), wxString::Format( wxS(
"%u" ), aDecode.cursor.U16At( offset + 8 ) ),
3269 connectionSource ) );
3270 aModel.junctions.push_back( std::move( junction ) );
3275 VERTEX_TILING tileConnectivityVertices(
const CONNECTIVITY_DECODE& aDecode,
PADS_SCH_MODEL& aModel )
3277 VERTEX_TILING tiling;
3280 tiling.starts.reserve( aDecode.controllers.pools[17].count + aDecode.controllers.pools[20].count );
3282 for(
size_t record = 0; record < aDecode.controllers.pools[17].count; ++record )
3283 tiling.starts.push_back( aDecode.cursor.U32At( aDecode.busBase + record * BUS_RECORD_BYTES + 4 ) );
3285 for(
size_t record = 0; record < aDecode.controllers.pools[20].count; ++record )
3286 tiling.starts.push_back(
3287 aDecode.cursor.U32At( aDecode.connectionBase + record * CONNECTION_RECORD_BYTES + 4 ) );
3289 std::vector<size_t> objectOrder( tiling.starts.size() );
3291 for(
size_t object = 0;
object < objectOrder.size(); ++object )
3292 objectOrder[
object] =
object;
3294 std::ranges::sort( objectOrder,
3295 [&](
size_t aLeft,
size_t aRight )
3297 return tiling.starts[aLeft] < tiling.starts[aRight];
3299 tiling.ends.resize( tiling.starts.size() );
3301 for(
size_t ordinal = 0; ordinal < objectOrder.size(); ++ordinal )
3303 const size_t object = objectOrder[ordinal];
3304 const uint32_t
end = ordinal + 1 < objectOrder.size() ? tiling.starts[objectOrder[ordinal + 1]]
3305 : aDecode.controllers.pools[21].count;
3307 if( ( ordinal == 0 && tiling.starts[
object] != 0 ) || tiling.starts[
object] >=
end
3308 ||
end > aDecode.controllers.pools[21].count )
3311 sourceAt( aDecode.sourceName, aModel.version, wxS(
"connectivity vertex ownership" ),
3312 object < aDecode.controllers.pools[17].count ? 18 : 21,
object, aDecode.vertexBase,
3313 aDecode.controllers.pools[21].usedBytes,
static_cast<int>( aDecode.sheetIndex ) );
3314 throwDecodeError( source, wxS(
"connectivity vertex slices do not exactly tile controller 22" ) );
3317 tiling.ends[object] =
end;
3320 if( tiling.starts.empty() && aDecode.controllers.pools[21].count != 0 )
3323 sourceAt( aDecode.sourceName, aModel.version, wxS(
"connection vertex" ), 22, 0, aDecode.vertexBase,
3324 aDecode.controllers.pools[21].usedBytes,
static_cast<int>( aDecode.sheetIndex ) );
3325 throwDecodeError( source, wxS(
"unclaimed connection vertices" ) );
3332 void appendVertices(
const CONNECTIVITY_DECODE& aDecode,
const VERTEX_TILING& aTiling,
size_t aObject,
3333 uint16_t aVersion, std::vector<SOURCE_POINT>& aVertices )
3335 const uint32_t start = aTiling.starts[aObject];
3336 const uint32_t
end = aTiling.ends[aObject];
3338 for( uint32_t vertex = start; vertex <
end; ++vertex )
3340 const size_t offset = aDecode.vertexBase + vertex * CONNECTION_VERTEX_BYTES;
3342 sourceAt( aDecode.sourceName, aVersion, wxS(
"connection vertex" ), 22, vertex, offset,
3343 CONNECTION_VERTEX_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3345 if( aDecode.cursor.U32At( offset ) != 0 )
3346 throwDecodeError( source, wxS(
"nonzero connection-vertex padding" ) );
3348 aVertices.push_back( { decodeCoordinate( aDecode.cursor.U16At( offset + 4 ) ),
3349 decodeCoordinate( aDecode.cursor.U16At( offset + 6 ) ), source } );
3355 decodeConnectionEndpoint(
const CONNECTIVITY_DECODE& aDecode,
const SOURCE_PROVENANCE& aSource,
size_t aFieldOffset,
3356 size_t aRelationshipOffset,
const SOURCE_POINT& aWirePoint,
3357 const std::vector<MODEL_PLACEMENT*>& aPlacements,
3358 const std::unordered_map<uint32_t, const MODEL_PIN_DEFINITION*>& aDefinitionPins,
3362 endpointSource.
objectClass = wxS(
"connection endpoint" );
3363 endpointSource.absoluteOffset += aFieldOffset;
3364 endpointSource.length = 2;
3365 const uint16_t raw = aDecode.cursor.U16At( endpointSource.absoluteOffset );
3366 const uint16_t objectClass = raw >> 12;
3367 const uint16_t objectRecord = raw & 0x0FFF;
3369 relationshipSource.
objectClass = wxS(
"connection endpoint relationship" );
3370 relationshipSource.absoluteOffset += aRelationshipOffset;
3371 relationshipSource.length = 4;
3372 const uint32_t relationship = aDecode.cursor.U32At( relationshipSource.absoluteOffset );
3376 switch( objectClass )
3379 if( objectRecord >= aPlacements.size() || !aPlacements[objectRecord] )
3380 throwDecodeError( endpointSource, wxS(
"unresolved placement endpoint" ) );
3388 auto definitionPin = aDefinitionPins.find(
pin.id.Value() );
3390 if( definitionPin == aDefinitionPins.end() )
3391 throwDecodeError(
pin.source, wxS(
"placement pin definition is unresolved" ) );
3393 const auto position = transformedPinPosition( placement, *definitionPin->second );
3395 if( position.first != aWirePoint.x || position.second != aWirePoint.y )
3399 throwDecodeError( endpointSource,
3400 wxS(
"placement has duplicate pins at connection endpoint" ) );
3406 throwDecodeError( endpointSource,
3407 wxString::Format( wxS(
"placement has no pin at connection endpoint; "
3408 "angle %d mirror %u" ),
3409 placement.angle, placement.mirrorFlags ) );
3414 result.point = aWirePoint;
3419 if( objectRecord >= aDecode.controllers.pools[19].count )
3420 throwDecodeError( endpointSource, wxS(
"unresolved off-page endpoint" ) );
3423 result.point = offpagePosition( aDecode, objectRecord, aModel.version );
3427 if( objectRecord >= aDecode.controllers.pools[18].count )
3428 throwDecodeError( endpointSource, wxS(
"unresolved junction endpoint" ) );
3431 result.point = junctionPosition( aDecode, objectRecord, aModel.version );
3434 default: throwDecodeError( endpointSource, wxS(
"wrong endpoint object class" ) );
3437 result.properties.push_back(
3438 sourceProperty( wxS(
"raw_endpoint_handle" ), wxString::Format( wxS(
"%u" ), raw ), endpointSource ) );
3440 wxS(
"raw_endpoint_relationship" ), wxString::Format( wxS(
"%u" ), relationship ), relationshipSource );
3442 result.properties.push_back( std::move( relationshipProperty ) );
3447 void decodeConnections(
const CONNECTIVITY_DECODE& aDecode,
const VERTEX_TILING& aTiling,
3448 const std::vector<MODEL_PLACEMENT*>& aPlacements,
3449 const std::unordered_map<uint32_t, const MODEL_PIN_DEFINITION*>& aDefinitionPins,
3452 aConnectivity.connections.assign( aDecode.controllers.pools[20].count,
nullptr );
3453 aConnectivity.connectionNets.assign( aDecode.controllers.pools[20].count,
nullptr );
3454 aConnectivity.junctionBacklinks.assign( aDecode.controllers.pools[18].count, {} );
3455 aConnectivity.offpageBacklinks.assign( aDecode.controllers.pools[19].count, {} );
3456 std::vector<std::array<uint16_t, 2>> connectionEndpointHandles( aDecode.controllers.pools[20].count );
3457 std::vector<size_t> connectionCounts( aConnectivity.sheetNets.size(), 0 );
3459 for(
size_t record = 0; record < aDecode.controllers.pools[20].count; ++record )
3461 const uint32_t netHandle =
3462 aDecode.cursor.U32At( aDecode.connectionBase + record * CONNECTION_RECORD_BYTES + 8 );
3464 if( netHandle < connectionCounts.size() )
3465 ++connectionCounts[netHandle];
3468 for(
size_t net = 0; net < aConnectivity.sheetNets.size(); ++net )
3470 if( aConnectivity.sheetNets[net] )
3471 aConnectivity.sheetNets[net]->connections.reserve( connectionCounts[net] );
3474 for(
size_t record = 0; record < aDecode.controllers.pools[20].count; ++record )
3476 const size_t offset = aDecode.connectionBase + record * CONNECTION_RECORD_BYTES;
3478 sourceAt( aDecode.sourceName, aModel.version, wxS(
"connection" ), 21, record, offset,
3479 CONNECTION_RECORD_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3480 const uint32_t netHandle = aDecode.cursor.U32At( offset + 8 );
3482 if( netHandle >= aConnectivity.sheetNets.size() || !aConnectivity.sheetNets[netHandle] )
3483 throwDecodeError( source, wxS(
"connection net handle targets wrong or unresolved object class" ) );
3485 const uint16_t marker = aDecode.cursor.U16At( offset + 34 );
3487 const uint8_t markerStatus = marker & 0xFF;
3489 if( marker >> 8 < 2 || marker >> 8 > 6
3490 || ( markerStatus != 0 && markerStatus != 0xFC && markerStatus != 0xFD ) )
3491 throwDecodeError( source, wxS(
"invalid connection object-class marker" ) );
3494 connection.
source = source;
3496 appendVertices( aDecode, aTiling, aDecode.controllers.pools[17].count + record, aModel.version,
3497 connection.vertices );
3499 if( connection.vertices.size() < 2 )
3500 throwDecodeError( source, wxS(
"connection lacks explicit endpoint vertices" ) );
3502 connection.endpoints.push_back( decodeConnectionEndpoint(
3503 aDecode, source, 12, 16, connection.vertices.front(), aPlacements, aDefinitionPins, aModel ) );
3504 connection.endpoints.push_back( decodeConnectionEndpoint(
3505 aDecode, source, 14, 20, connection.vertices.back(), aPlacements, aDefinitionPins, aModel ) );
3507 for(
size_t endpointIndex = 0; endpointIndex < connection.endpoints.size(); ++endpointIndex )
3511 endpointIndex == 0 ? connection.vertices.front() : connection.vertices.back();
3514 && ( decodedEndpoint.point.x != wirePoint.x || decodedEndpoint.point.y != wirePoint.y ) )
3516 throwDecodeError( decodedEndpoint.source,
3517 wxS(
"typed endpoint position does not match wire endpoint vertex" ) );
3520 decodedEndpoint.point = { wirePoint.x, wirePoint.y, wirePoint.source };
3523 connectionEndpointHandles[record] = { aDecode.cursor.U16At( offset + 12 ),
3524 aDecode.cursor.U16At( offset + 14 ) };
3526 for(
size_t endpointIndex = 0; endpointIndex < connectionEndpointHandles[record].size(); ++endpointIndex )
3528 const uint16_t raw = connectionEndpointHandles[record][endpointIndex];
3529 const size_t objectClass = raw >> 12;
3530 const size_t objectRecord = raw & 0x0FFF;
3531 std::vector<size_t>* backlinks =
nullptr;
3533 if( objectClass == 2 )
3534 backlinks = &aConnectivity.offpageBacklinks[objectRecord];
3535 else if( objectClass == 3 )
3536 backlinks = &aConnectivity.junctionBacklinks[objectRecord];
3540 if( endpointIndex == 1 && connectionEndpointHandles[record][0] == raw )
3543 endpointSource.
objectClass = wxS(
"connection endpoint" );
3544 endpointSource.absoluteOffset += endpointIndex == 0 ? 12 : 14;
3545 endpointSource.length = 2;
3546 throwDecodeError( endpointSource, wxS(
"duplicate typed endpoint backlink" ) );
3549 backlinks->push_back( record );
3553 connection.properties.push_back(
3554 sourceProperty( wxS(
"raw_connection_marker" ), wxString::Format( wxS(
"%u" ), marker ), source ) );
3555 aConnectivity.sheetNets[netHandle]->connections.push_back( std::move( connection ) );
3556 aConnectivity.connections[record] = &aConnectivity.sheetNets[netHandle]->connections.back();
3557 aConnectivity.connectionNets[record] = aConnectivity.sheetNets[netHandle];
3562 void validateJunctionBacklinks(
const CONNECTIVITY_DECODE& aDecode,
const SHEET_CONNECTIVITY& aConnectivity,
3565 for(
size_t record = 0; record < aDecode.controllers.pools[18].count; ++record )
3567 const size_t offset = aDecode.junctionBase + record * JUNCTION_RECORD_BYTES;
3569 sourceAt( aDecode.sourceName, aModel.version, wxS(
"junction" ), 19, record, offset,
3570 JUNCTION_RECORD_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3571 const uint16_t owner = aDecode.cursor.U16At( offset + 8 );
3574 ownerSource.length = 2;
3576 if( owner >= aConnectivity.connections.size() || !aConnectivity.connections[owner] )
3577 throwDecodeError( ownerSource, wxS(
"junction connection handle leaves controller 21" ) );
3579 if( std::ranges::find( aConnectivity.junctionBacklinks[record], owner )
3580 == aConnectivity.junctionBacklinks[record].end() )
3581 throwDecodeError( ownerSource, wxS(
"junction connection handle does not point back" ) );
3583 for(
size_t connection : aConnectivity.junctionBacklinks[record] )
3585 if( aConnectivity.connectionNets[connection] != aConnectivity.connectionNets[owner] )
3587 throwDecodeError( ownerSource, wxS(
"junction is shared across different nets" ) );
3594 void validateOffpageBacklinks(
const CONNECTIVITY_DECODE& aDecode,
const SHEET_CONNECTIVITY& aConnectivity,
3597 for(
size_t record = 0; record < aDecode.controllers.pools[19].count; ++record )
3599 const size_t offset = aDecode.offpageBase + record * OFFPAGE_RECORD_BYTES;
3601 sourceAt( aDecode.sourceName, aModel.version, wxS(
"off-page reference" ), 20, record, offset,
3602 OFFPAGE_RECORD_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3603 const uint16_t owner = aDecode.cursor.U16At( offset + 8 );
3606 ownerSource.length = 2;
3608 if( owner >= aConnectivity.connections.size() || !aConnectivity.connections[owner] )
3609 throwDecodeError( ownerSource, wxS(
"off-page net handle leaves controller 21" ) );
3611 if( std::ranges::find( aConnectivity.offpageBacklinks[record], owner )
3612 == aConnectivity.offpageBacklinks[record].end() )
3613 throwDecodeError( ownerSource, wxS(
"off-page connection handle does not point back" ) );
3615 for(
size_t connection : aConnectivity.offpageBacklinks[record] )
3617 if( aConnectivity.connectionNets[connection] != aConnectivity.connectionNets[owner] )
3618 throwDecodeError( ownerSource, wxS(
"off-page reference is shared across different nets" ) );
3624 std::vector<bool> decodeBuses(
const CONNECTIVITY_DECODE& aDecode,
const VERTEX_TILING& aTiling,
3625 const std::unordered_set<uint32_t>& aBusGlobalRecords,
3626 const SHEET_CONNECTIVITY& aConnectivity,
PADS_SCH_MODEL& aModel )
3628 std::vector<bool> claimedBusEntries( aDecode.controllers.pools[19].count,
false );
3630 for(
size_t record = 0; record < aDecode.controllers.pools[17].count; ++record )
3632 const size_t offset = aDecode.busBase + record * BUS_RECORD_BYTES;
3633 SOURCE_PROVENANCE source = sourceAt( aDecode.sourceName, aModel.version, wxS(
"bus" ), 18, record, offset,
3634 BUS_RECORD_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3635 const uint16_t marker = aDecode.cursor.U16At( offset + 38 );
3637 const uint8_t markerStatus = marker & 0xFF;
3639 if( marker >> 8 < 2 || marker >> 8 > 4 || ( markerStatus != 0 && markerStatus != 0xFD ) )
3640 throwDecodeError( source, wxS(
"invalid bus object-class marker" ) );
3642 const size_t globalRecord = aDecode.cursor.U32At( offset + 8 );
3644 if( globalRecord >= aDecode.globals.nets.size() || aDecode.globals.nets[globalRecord].tombstone
3645 || !aBusGlobalRecords.contains( globalRecord ) )
3647 throwDecodeError( source, wxS(
"bus global-net handle targets wrong or unresolved object class" ) );
3650 const GLOBAL_NET_RECORD& global = aDecode.globals.nets[globalRecord];
3651 uint32_t membership = global.membershipStart;
3653 while( membership < global.membershipStart + global.membershipCount
3654 && aDecode.globals.membershipSheets[membership] != aDecode.sheetIndex )
3659 if( membership == global.membershipStart + global.membershipCount )
3660 throwDecodeError( source, wxS(
"bus global-net handle does not belong to this sheet" ) );
3663 bus.
id =
BUS_ID(
static_cast<uint32_t
>( aDecode.sheetIndex * 0x100000 + record + 1 ) );
3664 bus.source = source;
3665 bus.sheet = { aModel.sheets[aDecode.sheetIndex].id, source };
3666 bus.name = global.name;
3667 bus.aliases.push_back( global.name );
3668 bus.declaredMembers = global.aliasMembers;
3670 sourceProperty( wxS(
"preserved_net_identity" ),
3671 wxString::Format( wxS(
"%u" ), global.preservedIdentity ), global.source );
3673 bus.properties.push_back( std::move( identity ) );
3675 relationshipSource.absoluteOffset += 84;
3676 relationshipSource.length = 4;
3678 sourceProperty( wxS(
"preserved_net_relationship" ),
3679 wxString::Format( wxS(
"%u" ), global.preservedRelationship ), relationshipSource );
3681 bus.properties.push_back( std::move( relationship ) );
3682 appendVertices( aDecode, aTiling, record, aModel.version, bus.vertices );
3684 std::vector<size_t> entryRecords;
3685 uint16_t entryHandle = aDecode.cursor.U16At( offset + 24 );
3686 std::unordered_set<size_t>
chain;
3688 while( ( entryHandle >> 12 ) == 2 )
3690 const size_t entryRecord = entryHandle & 0x0FFF;
3692 if( entryRecord >= aDecode.controllers.pools[19].count )
3693 throwDecodeError( source, wxS(
"unresolved bus-entry handle" ) );
3695 if( !
chain.insert( entryRecord ).second )
3696 throwDecodeError( source, wxS(
"cyclic bus-entry handle chain" ) );
3698 entryRecords.push_back( entryRecord );
3699 entryHandle = aDecode.cursor.U16At( aDecode.offpageBase + entryRecord * OFFPAGE_RECORD_BYTES + 4 );
3702 if( record > 0x0FFF || entryHandle != 0xBFFF - record )
3703 throwDecodeError( source, wxS(
"bus-entry chain terminates in wrong object class" ) );
3705 std::ranges::reverse( entryRecords );
3707 const bool exactAliasMapping = entryRecords.size() == global.aliasMembers.size();
3709 for(
size_t entry = 0; entry < entryRecords.size(); ++entry )
3711 const size_t entryRecord = entryRecords[entry];
3712 const size_t entryOffset = aDecode.offpageBase + entryRecord * OFFPAGE_RECORD_BYTES;
3714 sourceAt( aDecode.sourceName, aModel.version, wxS(
"bus entry" ), 20, entryRecord, entryOffset,
3715 OFFPAGE_RECORD_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3717 if( aDecode.cursor.U8At( entryOffset + 30 ) != 0xFF )
3718 throwDecodeError( entrySource, wxS(
"bus-entry handle targets wrong off-page object class" ) );
3720 if( claimedBusEntries[entryRecord] )
3721 throwDecodeError( entrySource, wxS(
"duplicate bus-entry membership" ) );
3723 const uint16_t connectionHandle = aDecode.cursor.U16At( entryOffset + 8 );
3725 if( connectionHandle >= aConnectivity.connections.size()
3726 || !aConnectivity.connections[connectionHandle] )
3727 throwDecodeError( entrySource, wxS(
"unresolved bus-entry connection reference" ) );
3729 MODEL_CONNECTION* entryConnection = aConnectivity.connections[connectionHandle];
3730 MODEL_NET* memberNet = aConnectivity.connectionNets[connectionHandle];
3733 throwDecodeError( entrySource, wxS(
"bus-entry connection targets wrong member-net class" ) );
3735 if( exactAliasMapping && memberNet->name.text != global.aliasMembers[entry].text )
3736 throwDecodeError( entrySource, wxS(
"bus alias member does not match connected net" ) );
3738 const bool ownsConnection = std::ranges::any_of( memberNet->connections,
3741 return &aConnection == entryConnection;
3744 if( !ownsConnection )
3745 throwDecodeError( entrySource, wxS(
"bus-entry connection targets wrong member-net class" ) );
3747 claimedBusEntries[entryRecord] =
true;
3749 busEntry.
source = entrySource;
3750 busEntry.position = offpagePosition( aDecode, entryRecord, aModel.version );
3751 busEntry.memberNet = { memberNet->id, entrySource };
3752 bus.entries.push_back( std::move( busEntry ) );
3753 bus.memberNets.push_back( { memberNet->id, entrySource } );
3756 aModel.buses.push_back( std::move( bus ) );
3759 return claimedBusEntries;
3763 void decodeOffpageLabels(
const CONNECTIVITY_DECODE& aDecode,
const SHEET_CONNECTIVITY& aConnectivity,
3764 const std::vector<bool>& aClaimedBusEntries,
PADS_SCH_MODEL& aModel )
3766 for(
size_t record = 0; record < aDecode.controllers.pools[19].count; ++record )
3768 const size_t offset = aDecode.offpageBase + record * OFFPAGE_RECORD_BYTES;
3770 sourceAt( aDecode.sourceName, aModel.version, wxS(
"off-page reference" ), 20, record, offset,
3771 OFFPAGE_RECORD_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3772 const uint8_t rawKind = aDecode.cursor.U8At( offset + 30 );
3774 if( rawKind == 0xFF )
3776 if( !aClaimedBusEntries[record] )
3777 throwDecodeError( source, wxS(
"unclaimed bus-entry record" ) );
3782 const uint16_t connectionHandle = aDecode.cursor.U16At( offset + 8 );
3784 if( connectionHandle >= aConnectivity.connections.size() || !aConnectivity.connections[connectionHandle] )
3785 throwDecodeError( source, wxS(
"off-page net handle leaves controller 21" ) );
3787 MODEL_NET* ownerNet = aConnectivity.connectionNets[connectionHandle];
3790 throwDecodeError( source, wxS(
"off-page record targets wrong net object class" ) );
3794 label.
sheet = { aModel.sheets[aDecode.sheetIndex].id, source };
3795 label.text = ownerNet->name;
3796 label.position = offpagePosition( aDecode, record, aModel.version );
3797 label.angle =
NormalizeAngle( aDecode.cursor.U16At( offset + 26 ) );
3798 label.symbolVariant = rawKind;
3800 if( rawKind == 0xFE )
3806 const uint16_t decalHandle = aDecode.cursor.U16At( offset + 4 );
3808 if( decalHandle == 0xFFFF )
3814 if( decalHandle >= aDecode.controllers.pools[6].count )
3815 throwDecodeError( source, wxS(
"off-page decal handle leaves controller 7" ) );
3818 sourceAt( aDecode.sourceName, aModel.version, wxS(
"used decal" ), 7, decalHandle,
3819 aDecode.controllers.offsets[6] + decalHandle * USED_DECAL_BYTES, 40,
3820 static_cast<int>( aDecode.sheetIndex ) );
3821 SOURCE_STRING decalName = decodeFixedString( aDecode.bytes, decalSource.absoluteOffset,
3822 decalSource.length, decalSource, aModel.diagnostics );
3823 const uint32_t definitionRecord = aDecode.cursor.U32At( decalSource.absoluteOffset + 48 );
3825 if( definitionRecord >= aDecode.controllers.pools[2].count )
3826 throwDecodeError( decalSource, wxS(
"off-page decal definition leaves controller 3" ) );
3829 static_cast<uint32_t
>( aDecode.sheetIndex * 0x100000 + 1 + definitionRecord ) );
3830 auto specialPartOwnsDefinition = [&](
const wxString& aPartName )
3832 return std::ranges::any_of(
3836 if( aPart.name.text != aPartName )
3839 return std::ranges::any_of(
3841 [&]( const MODEL_GATE& aGate )
3843 return aGate.definition.id == definitionId
3844 || std::ranges::any_of( aGate.alternateDefinitions,
3845 [&]( const DEFINITION_REFERENCE& aRef )
3847 return aRef.id == definitionId;
3853 if( specialPartOwnsDefinition( wxS(
"$OSR_SYMS" ) ) )
3855 else if( specialPartOwnsDefinition( wxS(
"$GND_SYMS" ) ) )
3857 else if( specialPartOwnsDefinition( wxS(
"$PWR_SYMS" ) ) )
3863 sourceProperty( wxS(
"unsupported_offpage_decal" ), decalName.text, decalSource );
3865 aModel.diagnostics.push_back(
3867 wxS(
"unsupported off-page decal class preserved" ) ) );
3868 label.properties.push_back( std::move( unsupportedDecal ) );
3875 for( uint32_t membership =
3877 .nets[aDecode.cursor.U32At( aDecode.connectionBase
3878 + connectionHandle * CONNECTION_RECORD_BYTES + 8 )]
3881 < aDecode.globals.nets[aDecode.cursor.U32At( aDecode.connectionBase
3882 + connectionHandle * CONNECTION_RECORD_BYTES + 8 )]
3885 .nets[aDecode.cursor.U32At( aDecode.connectionBase
3886 + connectionHandle * CONNECTION_RECORD_BYTES + 8 )]
3890 const uint16_t peerSheet = aDecode.globals.membershipSheets[membership];
3892 if( peerSheet != aDecode.sheetIndex )
3893 label.linkedSheets.push_back( { aModel.sheets[peerSheet].id, source } );
3897 label.properties.push_back(
3898 sourceProperty( wxS(
"offpage_variant" ), wxString::Format( wxS(
"%u" ), rawKind ), source ) );
3899 aModel.labels.push_back( std::move( label ) );
3908 presentation.
source = aSource;
3909 presentation.height = aDecode.cursor.U16At( aOffset + 2 );
3910 presentation.width = aDecode.cursor.U16At( aOffset + 4 );
3911 presentation.horizontalJustification = horizontalJustification( aDecode.cursor.U16At( aOffset + 26 ) );
3912 presentation.verticalJustification = verticalJustification( aDecode.cursor.U16At( aOffset + 26 ) );
3913 const int16_t fontHandle =
static_cast<int16_t
>( aDecode.cursor.U16At( aOffset ) );
3917 if( fontHandle == -1 )
3919 presentation.font = decodedDefinitionFont( -1, fontSource );
3923 if( fontHandle < 0 ||
static_cast<uint32_t
>( fontHandle ) >= aDecode.globals.fontCount )
3924 throwDecodeError( fontSource, wxS(
"net-name font handle leaves outer controller 19" ) );
3926 const size_t fontOffset = aDecode.globals.fontBase +
static_cast<size_t>( fontHandle ) * FONT_RECORD_BYTES;
3927 SOURCE_PROVENANCE nameSource = sourceAt( aDecode.sourceName, aModel.version, wxS(
"net-name font" ), 19,
3928 fontHandle, fontOffset + 4, 32, -1 );
3929 presentation.font = decodeFixedString( aDecode.bytes, fontOffset + 4, 32, nameSource, aModel.diagnostics );
3930 const uint32_t style = aDecode.cursor.U32At( fontOffset );
3931 presentation.bold = style & 1;
3932 presentation.italic = style & 2;
3936 SOURCE_PROPERTY property = sourceProperty( wxS(
"unsupported_font_style_flags" ),
3937 wxString::Format( wxS(
"%u" ), style & ~3U ), fontSource );
3939 presentation.properties.push_back( property );
3945 presentation.properties.push_back(
3946 sourceProperty( wxS(
"font_handle" ), wxString::Format( wxS(
"%d" ), fontHandle ), fontSource ) );
3947 return presentation;
3951 void decodeNetNames(
const CONNECTIVITY_DECODE& aDecode,
const SHEET_CONNECTIVITY& aConnectivity,
3954 for(
size_t record = 0; record < aDecode.controllers.pools[22].count; ++record )
3956 const size_t offset = aDecode.netNameBase + record * NET_NAME_RECORD_BYTES;
3958 sourceAt( aDecode.sourceName, aModel.version, wxS(
"net-name presentation" ), 23, record, offset,
3959 NET_NAME_RECORD_BYTES,
static_cast<int>( aDecode.sheetIndex ) );
3960 const uint32_t globalRecord = aDecode.cursor.U32At( offset + 16 );
3961 const uint16_t ownerHandle = aDecode.cursor.U16At( offset + 38 );
3962 const uint16_t childHandle = aDecode.cursor.U16At( offset + 40 );
3963 std::vector<SOURCE_PROPERTY> preservedPresentation;
3964 auto preserve = [&](
const wxString& aName,
const wxString& aValue,
size_t aRelativeOffset,
size_t aLength )
3968 propertySource.length = aLength;
3969 SOURCE_PROPERTY property = sourceProperty( aName, aValue, propertySource );
3971 preservedPresentation.push_back( std::move( property ) );
3973 wxString presentation06;
3976 presentation06 += wxString::Format( wxS(
"%02x" ), aDecode.bytes[offset +
index] );
3978 preserve( wxS(
"preserved_net_name_presentation_06" ), presentation06, 6, 10 );
3979 preserve( wxS(
"preserved_net_name_secondary_offset" ),
3980 wxString::Format( wxS(
"%d,%d" ),
static_cast<int16_t
>( aDecode.cursor.U16At( offset + 28 ) ),
3981 static_cast<int16_t
>( aDecode.cursor.U16At( offset + 30 ) ) ),
3983 preserve( wxS(
"preserved_net_name_presentation_20" ),
3984 wxString::Format( wxS(
"%u" ), aDecode.cursor.U16At( offset + 32 ) ), 32, 2 );
3985 preserve( wxS(
"preserved_net_name_presentation_flags" ),
3986 wxString::Format( wxS(
"%u" ), aDecode.cursor.U16At( offset + 34 ) ), 34, 2 );
3987 preserve( wxS(
"preserved_net_name_predecessor_handle" ),
3988 wxString::Format( wxS(
"%u" ), aDecode.cursor.U16At( offset + 36 ) ), 36, 2 );
3989 preserve( wxS(
"preserved_net_name_predecessor_record" ),
3990 wxString::Format( wxS(
"%u" ), aDecode.cursor.U16At( offset + 42 ) ), 42, 2 );
3991 preserve( wxS(
"preserved_net_name_successor_record" ),
3992 wxString::Format( wxS(
"%u" ), aDecode.cursor.U16At( offset + 44 ) ), 44, 2 );
3993 preserve( wxS(
"preserved_net_name_tail" ),
3994 wxString::Format( wxS(
"%u" ), aDecode.cursor.U16At( offset + 46 ) ), 46, 2 );
3996 if( ( ownerHandle & 0xF000 ) == 0x4000 )
3998 if( globalRecord >= aDecode.globals.nets.size() || aDecode.globals.nets[globalRecord].tombstone )
3999 throwDecodeError( source, wxS(
"bus net-name record targets wrong global-net object class" ) );
4001 const size_t busRecord = ownerHandle & 0x0FFF;
4002 auto bus = std::ranges::find_if( aModel.buses,
4005 return aBus.sheet.id == aModel.sheets[aDecode.sheetIndex].id
4006 && aBus.source.recordIndex == busRecord;
4009 if( bus == aModel.buses.end() || childHandle == 0 || childHandle > bus->memberNets.size()
4010 || bus->name.text != aDecode.globals.nets[globalRecord].name.text )
4012 throwDecodeError( source, wxS(
"net-name bus owner targets wrong object class" ) );
4015 bus->properties.push_back( sourceProperty( wxS(
"net_name_presentation_record" ),
4016 wxString::Format( wxS(
"%llu" ), record ), source ) );
4017 bus->properties.insert( bus->properties.end(), std::make_move_iterator( preservedPresentation.begin() ),
4018 std::make_move_iterator( preservedPresentation.end() ) );
4022 if( globalRecord >= aConnectivity.sheetNets.size() || !aConnectivity.sheetNets[globalRecord] )
4023 throwDecodeError( source, wxS(
"net-name record targets wrong or unresolved net object class" ) );
4025 MODEL_NET& ownerNet = *aConnectivity.sheetNets[globalRecord];
4026 const SOURCE_POINT textOffset{ decodeTerminalCoordinate( aDecode.cursor.U16At( offset + 20 ) ),
4027 decodeTerminalCoordinate( aDecode.cursor.U16At( offset + 22 ) ), source };
4029 presentation.properties.insert( presentation.properties.end(),
4030 std::make_move_iterator( preservedPresentation.begin() ),
4031 std::make_move_iterator( preservedPresentation.end() ) );
4033 if( ( ownerHandle & 0xF000 ) == 0x2000 )
4035 const size_t offpageRecord = ownerHandle & 0x0FFF;
4036 auto label = std::ranges::find_if( aModel.labels,
4039 return aLabel.sheet.id == aModel.sheets[aDecode.sheetIndex].id
4040 && aLabel.source.controller == 20
4041 && aLabel.source.recordIndex == offpageRecord;
4044 if( label == aModel.labels.end() )
4048 for(
const MODEL_BUS& bus : aModel.buses )
4050 if( bus.sheet.id != aModel.sheets[aDecode.sheetIndex].id )
4053 auto candidate = std::ranges::find_if( bus.entries,
4056 return aEntry.source.recordIndex == offpageRecord
4057 && aEntry.memberNet.id == ownerNet.id;
4060 if( candidate != bus.entries.end() )
4063 throwDecodeError( source, wxS(
"net-name bus-entry owner is ambiguous" ) );
4065 busEntry = &*candidate;
4070 throwDecodeError( source, wxS(
"net-name off-page owner targets wrong object class" ) );
4074 busText.
sheet = { aModel.sheets[aDecode.sheetIndex].id, source };
4075 busText.text = ownerNet.name;
4076 busText.position = { busEntry->position.x + textOffset.x, busEntry->position.y + textOffset.y,
4078 busText.angle =
NormalizeAngle( aDecode.cursor.U16At( offset + 24 ) );
4079 busText.presentation = std::move( presentation );
4080 busText.properties.push_back( sourceProperty(
4081 wxS(
"net_name_text_offset" ),
4082 wxString::Format( wxS(
"%lld,%lld" ), textOffset.x, textOffset.y ), source ) );
4083 aModel.texts.push_back( std::move( busText ) );
4087 if( label->text.text != ownerNet.name.text )
4088 throwDecodeError( source, wxS(
"net-name off-page owner targets wrong net object" ) );
4090 label->presentation = std::move( presentation );
4091 label->textOffset = textOffset;
4092 label->properties.push_back(
4093 sourceProperty( wxS(
"net_name_text_offset" ),
4094 wxString::Format( wxS(
"%lld,%lld" ), textOffset.x, textOffset.y ), source ) );
4099 std::ranges::find_if( aModel.placements,
4102 return aPlacement.sheet.id == aModel.sheets[aDecode.sheetIndex].id
4103 && aPlacement.source.recordIndex == ownerHandle;
4106 if( placement == aModel.placements.end() )
4107 throwDecodeError( source, wxS(
"net-name placement owner targets wrong object class" ) );
4109 if( childHandle >= placement->pins.size() )
4110 throwDecodeError( source, wxS(
"net-name placement pin ordinal leaves placement" ) );
4112 const PIN_ID ownerPin = placement->pins[childHandle].id;
4121 || candidate.placement->id != placement->id || candidate.pin->id != ownerPin )
4127 throwDecodeError( source, wxS(
"net-name placement pin owner is ambiguous" ) );
4129 endpoint = &candidate;
4134 throwDecodeError( source, wxS(
"net-name placement pin owner is unresolved" ) );
4138 text.
sheet = { aModel.sheets[aDecode.sheetIndex].id, source };
4139 text.text = ownerNet.name;
4140 text.position = { endpoint->point.x + textOffset.x, endpoint->point.y + textOffset.y, source };
4142 text.presentation = std::move( presentation );
4143 text.properties.push_back(
4144 sourceProperty( wxS(
"net_name_text_offset" ),
4145 wxString::Format( wxS(
"%lld,%lld" ), textOffset.x, textOffset.y ), source ) );
4146 aModel.texts.push_back( std::move(
text ) );
4151 void decodeConnectivity(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
4152 const SCH_SDB_BLOCK& aBlock,
size_t aSheetIndex,
const wxString& aSourceName,
4155 if( aModel.version != 0x000D )
4156 throwDecodeError( aModel.source, wxS(
"connectivity decoder selected for raw-preserved version" ) );
4158 const SHEET_CONTROLLERS controllers = sheetControllers( aCursor, aBlock );
4159 requireFixedController( controllers, 18, BUS_RECORD_BYTES, aSourceName, aModel.version, aSheetIndex );
4160 requireFixedController( controllers, 19, JUNCTION_RECORD_BYTES, aSourceName, aModel.version, aSheetIndex );
4161 requireFixedController( controllers, 20, OFFPAGE_RECORD_BYTES, aSourceName, aModel.version, aSheetIndex );
4162 requireFixedController( controllers, 21, CONNECTION_RECORD_BYTES, aSourceName, aModel.version, aSheetIndex );
4163 requireFixedController( controllers, 22, CONNECTION_VERTEX_BYTES, aSourceName, aModel.version, aSheetIndex );
4164 requireFixedController( controllers, 23, NET_NAME_RECORD_BYTES, aSourceName, aModel.version, aSheetIndex );
4166 const CONNECTIVITY_DECODE
decode{ aBytes,
4172 controllers.offsets[17],
4173 controllers.offsets[18],
4174 controllers.offsets[19],
4175 controllers.offsets[20],
4176 controllers.offsets[21],
4177 controllers.offsets[22] };
4179 const std::unordered_set<uint32_t> busGlobalRecords = decodeBusGlobalRecords( decode, aModel );
4180 SHEET_CONNECTIVITY connectivity;
4181 connectivity.sheetNets = materializeSheetNets( decode, busGlobalRecords, aModel );
4183 const std::unordered_map<uint32_t, const MODEL_PIN_DEFINITION*> definitionPins = indexDefinitionPins( aModel );
4184 const std::vector<MODEL_PLACEMENT*> placements = indexSheetPlacements( decode, aModel );
4186 decodeJunctions( decode, aModel );
4188 const VERTEX_TILING tiling = tileConnectivityVertices( decode, aModel );
4190 decodeConnections( decode, tiling, placements, definitionPins, connectivity, aModel );
4191 validateJunctionBacklinks( decode, connectivity, aModel );
4192 validateOffpageBacklinks( decode, connectivity, aModel );
4194 const std::vector<bool> claimedBusEntries =
4195 decodeBuses( decode, tiling, busGlobalRecords, connectivity, aModel );
4197 decodeOffpageLabels( decode, connectivity, claimedBusEntries, aModel );
4198 decodeNetNames( decode, connectivity, aModel );
4207 throwDecodeError( aField.source, wxS(
"field ordinal exceeds identity capacity" ) );
4209 aField.id =
MakeFieldId( aDomain, aOwner,
static_cast<uint32_t
>( aOrdinal ) );
4214 for(
size_t ordinal = 0; ordinal < sheet.titleBlockFields.size(); ++ordinal )
4216 MODEL_FIELD& field = sheet.titleBlockFields[ordinal];
4223 for(
size_t ordinal = 0; ordinal < definition.fields.size(); ++ordinal )
4232 for(
size_t ordinal = 0; ordinal < partType.fields.size(); ++ordinal )
4241 for(
size_t ordinal = 0; ordinal < placement.fields.size(); ++ordinal )
4250 template <
typename Item>
4251 void validateSheetRef(
const Item& aItem,
const MODEL_REFERENCE_INDEX& aIndex,
const wxString& aObjectClass )
4253 if( !aIndex.sheets.contains( aItem.sheet.id.Value() ) )
4255 throwValidationError( aItem.sheet.source,
4256 wxString::Format( wxS(
"unresolved %s sheet reference" ), aObjectClass ) );
4263 validateUniqueIds( aModel.sheets, wxS(
"sheet" ), itemId, itemProvenance );
4264 validateUniqueIds( aModel.definitions, wxS(
"definition" ), itemId, itemProvenance );
4265 validateUniqueIds( aModel.partTypes, wxS(
"part type" ), itemId, itemProvenance );
4266 validateUniqueIds( aModel.placements, wxS(
"placement" ), itemId, itemProvenance );
4267 validateUniqueIds( aModel.nets, wxS(
"net" ), itemId, itemProvenance );
4268 validateUniqueIds( aModel.buses, wxS(
"bus" ), itemId, itemProvenance );
4269 validateUniqueIds( aModel.images, wxS(
"embedded image" ), itemId, itemProvenance );
4271 std::vector<bool> sheetIndexes( aModel.sheets.size() );
4275 if( sheet.index >= sheetIndexes.size() )
4276 throwValidationError( sheet.source, wxS(
"sheet source index leaves the declared sheet range" ) );
4278 if( sheetIndexes[sheet.index] )
4279 throwValidationError( sheet.source, wxS(
"duplicate sheet source index" ) );
4281 sheetIndexes[sheet.index] =
true;
4284 std::unordered_map<uint32_t, SOURCE_PROVENANCE> gateDeclarations;
4285 std::unordered_map<uint32_t, SOURCE_PROVENANCE> pinDeclarations;
4286 std::unordered_map<uint64_t, SOURCE_PROVENANCE> fieldDeclarations;
4290 for(
const MODEL_GATE& gate : partType.gates )
4291 validateNestedId( gate, wxS(
"gate" ), gateDeclarations );
4297 validateNestedId(
pin, wxS(
"pin" ), pinDeclarations );
4299 for(
const MODEL_FIELD& field : definition.fields )
4300 validateNestedId( field, wxS(
"field" ), fieldDeclarations );
4305 for(
const MODEL_FIELD& field : sheet.titleBlockFields )
4306 validateNestedId( field, wxS(
"field" ), fieldDeclarations );
4312 validateNestedId( field, wxS(
"field" ), fieldDeclarations );
4317 for(
const MODEL_FIELD& field : placement.fields )
4318 validateNestedId( field, wxS(
"field" ), fieldDeclarations );
4323 void validateSymbolGraph(
const PADS_SCH_MODEL& aModel,
const MODEL_REFERENCE_INDEX& aIndex )
4327 if( sheet.parent && !aIndex.sheets.contains( sheet.parent->id.Value() ) )
4328 throwValidationError( sheet.parent->source, wxS(
"unresolved sheet reference" ) );
4333 for(
const MODEL_GATE& gate : partType.gates )
4335 auto definition = aIndex.definitions.find( gate.definition.id.Value() );
4337 if( definition == aIndex.definitions.end() && gate.decalGroupMembers.empty() )
4338 throwValidationError( gate.definition.source, wxS(
"unresolved symbol definition reference" ) );
4342 if( !aIndex.definitions.contains( alternate.id.Value() ) )
4343 throwValidationError( alternate.source, wxS(
"unresolved alternate definition reference" ) );
4348 if( !aIndex.definitions.contains( member.id.Value() ) )
4349 throwValidationError( member.source, wxS(
"unresolved pin-decal group member" ) );
4352 if( definition == aIndex.definitions.end() )
4355 if( !gate.logicalPins.empty() && gate.logicalPins.size() != gate.pins.size() )
4356 throwValidationError( gate.source, wxS(
"gate logical-pin count does not match definition pins" ) );
4358 for(
size_t pinOrdinal = 0; pinOrdinal < gate.pins.size(); ++pinOrdinal )
4361 auto pinOwner = aIndex.pinOwners.find(
pin.id.Value() );
4363 if( pinOwner == aIndex.pinOwners.end() || pinOwner->second != definition->second )
4364 throwValidationError(
pin.source, wxS(
"pin does not belong to gate definition" ) );
4366 if( !gate.logicalPins.empty() && gate.logicalPins[pinOrdinal].definitionPin.id !=
pin.id )
4367 throwValidationError( gate.logicalPins[pinOrdinal].definitionPin.source,
4368 wxS(
"logical pin does not belong to gate definition pin" ) );
4373 struct DEFINITION_EDGE
4376 SOURCE_PROVENANCE source;
4379 std::unordered_map<uint32_t, std::vector<DEFINITION_EDGE>> definitionEdges;
4383 for(
const MODEL_GATE& gate : partType.gates )
4385 if( !gate.definition.id.IsValid() )
4389 definitionEdges[gate.definition.id.Value()].push_back( { alternate.id.Value(), alternate.source } );
4393 std::unordered_map<uint32_t, uint8_t> definitionColors;
4394 std::function<void( uint32_t )> visitDefinition =
4395 [&]( uint32_t aDefinition )
4397 definitionColors[aDefinition] = 1;
4399 auto edges = definitionEdges.find( aDefinition );
4401 if( edges == definitionEdges.end() )
4403 definitionColors[aDefinition] = 2;
4407 for(
const DEFINITION_EDGE& edge : edges->second )
4409 if( definitionColors[edge.target] == 1 )
4410 throwValidationError( edge.source, wxS(
"cyclic symbol definition reference" ) );
4412 if( definitionColors[edge.target] == 0 )
4413 visitDefinition( edge.target );
4416 definitionColors[aDefinition] = 2;
4419 for(
const auto& [definition, edges] : definitionEdges )
4421 if( definitionColors[definition] == 0 )
4422 visitDefinition( definition );
4427 void validatePlacements(
const PADS_SCH_MODEL& aModel,
const MODEL_REFERENCE_INDEX& aIndex )
4431 if( !aIndex.sheets.contains( placement.sheet.id.Value() ) )
4432 throwValidationError( placement.sheet.source, wxS(
"unresolved placement sheet reference" ) );
4434 auto partType = aIndex.partTypes.find( placement.partType.id.Value() );
4436 if( partType == aIndex.partTypes.end() )
4437 throwValidationError( placement.partType.source, wxS(
"unresolved placement part-type reference" ) );
4439 auto placementDefinition = aIndex.definitions.find( placement.definition.id.Value() );
4441 if( placementDefinition == aIndex.definitions.end() )
4442 throwValidationError( placement.definition.source, wxS(
"unresolved placement definition" ) );
4446 auto owner = aIndex.pinOwners.find(
pin.id.Value() );
4448 if( owner == aIndex.pinOwners.end() || owner->second != placementDefinition->second )
4449 throwValidationError(
pin.source, wxS(
"placement pin does not belong to selected definition" ) );
4452 if( !placement.gate )
4455 auto gate = aIndex.gates.find( placement.gate->id.Value() );
4456 auto gateOwner = aIndex.gateOwners.find( placement.gate->id.Value() );
4458 if( gate == aIndex.gates.end() || gateOwner == aIndex.gateOwners.end()
4459 || gateOwner->second != partType->second
4460 || ( gate->second->unit != placement.unit && gate->second->decalGroupMembers.empty() ) )
4461 throwValidationError( placement.gate->source, wxS(
"placement gate or unit mismatch" ) );
4463 const MODEL_GATE& selectedGate = *gate->second;
4464 const bool definitionMatches = selectedGate.
definition.
id == placement.definition.id
4465 || std::ranges::any_of( selectedGate.alternateDefinitions,
4468 return aDefinition.id == placement.definition.id;
4470 || std::ranges::any_of( selectedGate.decalGroupMembers,
4473 return aDefinition.id == placement.definition.id;
4476 if( !definitionMatches )
4477 throwValidationError( placement.definition.source,
4478 wxS(
"placement definition does not belong to selected gate" ) );
4480 auto definition = aIndex.definitions.find( placement.definition.id.Value() );
4482 if( definition == aIndex.definitions.end() )
4483 throwValidationError( placement.definition.source, wxS(
"unresolved placement definition" ) );
4487 auto owner = aIndex.pinOwners.find(
pin.id.Value() );
4489 if( owner == aIndex.pinOwners.end() || owner->second != definition->second )
4490 throwValidationError(
pin.source, wxS(
"placement pin does not belong to selected definition" ) );
4496 void validateSheetBoundContent(
const PADS_SCH_MODEL& aModel,
const MODEL_REFERENCE_INDEX& aIndex )
4498 for(
const MODEL_NET& net : aModel.nets )
4500 validateSheetRef( net, aIndex, wxS(
"net" ) );
4504 if( connection.endpoints.empty() )
4505 throwValidationError( connection.source, wxS(
"connection has no endpoints" ) );
4509 if( !endpointIsValid( aIndex, endpoint ) )
4511 throwValidationError( endpoint.source,
4512 wxS(
"empty, mixed, or unresolved connection endpoint" ) );
4515 if( !endpoint.placement )
4518 auto placement = aIndex.placements.find( endpoint.placement->id.Value() );
4520 if( placement != aIndex.placements.end() && placement->second->sheet.id != net.sheet.id )
4522 throwValidationError( endpoint.source,
4523 wxS(
"connection endpoint placement sheet does not match net sheet" ) );
4529 for(
const MODEL_BUS& bus : aModel.buses )
4531 validateSheetRef( bus, aIndex, wxS(
"bus" ) );
4535 auto net = aIndex.nets.find( member.id.Value() );
4537 if( net == aIndex.nets.end() )
4538 throwValidationError( member.source, wxS(
"unresolved bus member-net reference" ) );
4540 if( net->second->sheet.id != bus.sheet.id )
4541 throwValidationError( member.source, wxS(
"bus member-net sheet does not match bus sheet" ) );
4546 auto net = aIndex.nets.find( entry.memberNet.id.Value() );
4548 if( net == aIndex.nets.end() )
4549 throwValidationError( entry.memberNet.source, wxS(
"unresolved bus-entry net reference" ) );
4551 if( net->second->sheet.id != bus.sheet.id )
4552 throwValidationError( entry.memberNet.source,
4553 wxS(
"bus-entry net sheet does not match bus sheet" ) );
4555 if( std::ranges::none_of( bus.memberNets,
4558 return aMember.id == entry.memberNet.id;
4561 throwValidationError( entry.source, wxS(
"bus-entry net is absent from bus member nets" ) );
4568 validateSheetRef( label, aIndex, wxS(
"label" ) );
4572 if( !aIndex.sheets.contains( linkedSheet.id.Value() ) )
4573 throwValidationError( linkedSheet.source, wxS(
"unresolved cross-sheet label reference" ) );
4578 validateSheetRef( junction, aIndex, wxS(
"junction" ) );
4581 validateSheetRef(
text, aIndex, wxS(
"text" ) );
4584 validateSheetRef( graphic, aIndex, wxS(
"page-graphic" ) );
4586 std::set<uint32_t> worksheetSheets;
4590 validateSheetRef( worksheet, aIndex, wxS(
"worksheet" ) );
4592 if( !worksheetSheets.insert( worksheet.sheet.id.Value() ).second )
4593 throwValidationError( worksheet.source, wxS(
"duplicate worksheet for sheet" ) );
4595 if( worksheet.graphics.empty() )
4596 throwValidationError( worksheet.source, wxS(
"worksheet has no graphics" ) );
4601 validateSheetRef(
image, aIndex, wxS(
"embedded-image" ) );
4604 throwValidationError(
image.source, wxS(
"embedded image has no decoded payload" ) );
4609 void validateSheetHierarchy(
const PADS_SCH_MODEL& aModel,
const MODEL_REFERENCE_INDEX& aIndex )
4611 enum class VISIT_STATE : uint8_t
4616 std::unordered_map<uint32_t, VISIT_STATE> visitStates;
4621 std::vector<const MODEL_SHEET*>
path;
4623 while( current && !visitStates.contains( current->id.Value() ) )
4625 visitStates.emplace( current->id.Value(), VISIT_STATE::VISITING );
4626 path.push_back( current );
4628 if( !current->parent )
4634 auto parent = aIndex.sheets.find( current->parent->id.Value() );
4636 if( parent == aIndex.sheets.end() )
4639 current = parent->second;
4642 if( current && visitStates.at( current->id.Value() ) == VISIT_STATE::VISITING )
4643 throwValidationError( current->source, wxS(
"cyclic sheet hierarchy" ) );
4646 visitStates[visited->id.Value()] = VISIT_STATE::COMPLETE;
4651 std::vector<MODEL_FIELD> decodeDesignSettings(
const std::vector<uint8_t>& aBytes,
4652 const PADS_IO::BINARY_CURSOR& aCursor,
const PADS_SCH_SDB& aSdb,
4657 if( sheetPool.usedBytes != sheetPool.count * SHEET_RECORD_BYTES )
4660 sourceAt( aSourceName, aModel.version, wxS(
"sheet index" ), 3, 0,
4661 OUTER_DIRECTORY_OFFSET + 3 * OUTER_DESCRIPTOR_BYTES + OUTER_USED_BYTES_OFFSET, 4, -1 );
4662 throwDecodeError( source, wxS(
"sheet-index byte count does not match 48-byte record count" ) );
4667 if( settingsPool.count != 100 || settingsPool.usedBytes != 400 )
4670 sourceAt( aSourceName, aModel.version, wxS(
"design settings" ), 5, 0,
4671 OUTER_DIRECTORY_OFFSET + 5 * OUTER_DESCRIPTOR_BYTES + OUTER_USED_BYTES_OFFSET, 4, -1 );
4672 throwDecodeError( source, wxS(
"design-settings controller is not the required 400-byte record" ) );
4675 const size_t settingsOffset = outerControllerOffset( aSdb, 5 );
4676 SOURCE_PROVENANCE settingsSource = sourceAt( aSourceName, aModel.version, wxS(
"design settings" ), 5, 0,
4677 settingsOffset, settingsPool.usedBytes, -1 );
4678 aModel.settings.source = settingsSource;
4680 uint8_t pageDesignator = 0;
4682 if( std::equal( aBytes.begin() + settingsOffset + 264, aBytes.begin() + settingsOffset + 268,
"SIZE" )
4683 && aBytes[settingsOffset + 269] == 0 )
4685 pageDesignator = aBytes[settingsOffset + 268];
4687 else if( std::equal( aBytes.begin() + settingsOffset + 264, aBytes.begin() + settingsOffset + 273,
"WDITBSIZE" )
4688 && aBytes[settingsOffset + 274] == 0 )
4690 pageDesignator = aBytes[settingsOffset + 273];
4697 throwDecodeError( source, wxS(
"invalid design page-size field" ) );
4702 pageSource.length = 11;
4703 aModel.settings.pageSize = pageExtent( pageDesignator, pageSource );
4704 aModel.settings.defaultLineWidth =
static_cast<int64_t
>( aCursor.
U32At( settingsOffset + 12 ) ) * 2;
4705 aModel.settings.defaultBusWidth =
static_cast<int64_t
>( aCursor.
U32At( settingsOffset + 16 ) ) * 2;
4707 std::vector<MODEL_FIELD> titleFields;
4709 size_t titleOffset = outerControllerOffset( aSdb, 1 );
4710 const size_t titleEnd = titleOffset + titleFieldPool.usedBytes;
4711 size_t titleRecord = 0;
4713 while( titleOffset + 6 <= titleEnd
4714 && std::equal( aBytes.begin() + titleOffset, aBytes.begin() + titleOffset + 6,
"Field\n" ) )
4716 size_t terminator = titleOffset;
4718 while( terminator < titleEnd && aBytes[terminator] != 0 )
4721 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS(
"title field" ), 1, titleRecord,
4722 titleOffset, terminator - titleOffset + 1, -1 );
4724 size_t separator = titleOffset + 6;
4726 while( separator < terminator && aBytes[separator] != 1 )
4729 if( terminator == titleEnd || terminator - titleOffset < 7 || separator == terminator
4730 || !std::equal( aBytes.begin() + titleOffset, aBytes.begin() + titleOffset + 6,
"Field\n" ) )
4732 throwDecodeError( source, wxS(
"invalid title-field name record" ) );
4735 std::vector<uint8_t> nameBytes( aBytes.begin() + titleOffset + 6, aBytes.begin() + separator );
4736 std::vector<uint8_t> valueBytes( aBytes.begin() + separator + 1, aBytes.begin() + terminator );
4739 nameSource.length = nameBytes.size();
4742 valueSource.length = valueBytes.size();
4746 aModel.diagnostics );
4748 aModel.diagnostics );
4749 field.presentation.source = source;
4750 titleFields.push_back( std::move( field ) );
4751 titleOffset = terminator + 1;
4755 if( titleFields.empty() )
4758 sourceAt( aSourceName, aModel.version, wxS(
"title field" ), 1, titleFields.size(),
4759 outerControllerOffset( aSdb, 1 ), titleFieldPool.usedBytes, -1 );
4760 throwDecodeError( source, wxS(
"title-field controller is empty" ) );
4767 void decodeSheets(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
4769 const std::vector<MODEL_FIELD>& aTitleFields,
PADS_SCH_MODEL& aModel )
4771 const size_t sheetIndexOffset = outerControllerOffset( aSdb, 3 );
4772 auto sheetBlocks = aSdb.
Blocks()
4773 | std::views::filter(
4778 auto sheetBlock = sheetBlocks.begin();
4782 size_t recordOffset = sheetIndexOffset +
index * SHEET_RECORD_BYTES;
4784 recordOffset, SHEET_RECORD_BYTES,
static_cast<int>(
index ) );
4786 if( sheetBlock == sheetBlocks.end() || aCursor.
U32At( recordOffset ) != sheetBlock->offset
4787 || aCursor.
U32At( recordOffset + 4 ) != sheetBlock->bytes )
4789 throwDecodeError( provenance, wxS(
"sheet-index record references the wrong SDB object class" ) );
4792 constexpr size_t nameOffset = 14;
4794 if( aCursor.
U16At( recordOffset + 10 ) != 0xFFFF || aCursor.
U16At( recordOffset + 12 ) != 0xFFFF )
4795 throwDecodeError( provenance, wxS(
"invalid sheet-index class marker" ) );
4797 size_t nameEnd = recordOffset + nameOffset;
4799 while( nameEnd < recordOffset + SHEET_RECORD_BYTES && aBytes[nameEnd] != 0 )
4802 if( nameEnd == recordOffset + SHEET_RECORD_BYTES )
4803 throwDecodeError( provenance, wxS(
"unterminated sheet name" ) );
4805 std::vector<uint8_t> nameBytes( aBytes.begin() + recordOffset + nameOffset, aBytes.begin() + nameEnd );
4808 nameSource.length = nameBytes.size();
4812 sheet.index = sheet.id.IsValid() ? sheet.id.Value() - 1 : std::numeric_limits<size_t>::max();
4813 sheet.source = provenance;
4815 aModel.diagnostics );
4817 if( sheet.name.text.empty() )
4818 sheet.name.text = wxS(
"$$$NONE" );
4820 sheet.pageSize = aModel.settings.pageSize;
4821 sheet.defaultLineWidth = aModel.settings.defaultLineWidth;
4822 sheet.defaultBusWidth = aModel.settings.defaultBusWidth;
4823 sheet.titleBlockFields = aTitleFields;
4825 auto title = std::ranges::find_if( sheet.titleBlockFields,
4828 return aField.name.text == wxS(
"Title" );
4831 if( title != sheet.titleBlockFields.end() )
4832 sheet.title = title->value;
4834 aModel.sheets.push_back( std::move( sheet ) );
4839 void decodeFreeText(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
4840 const wxString& aSourceName,
size_t aSheetIndex,
size_t aTextBase, uint32_t aTextCount,
4841 size_t aHeapBase, uint32_t aHeapBytes,
const PLACEMENT_GLOBALS& aGlobals,
4844 for(
size_t record = 0; record < aTextCount; ++record )
4846 size_t recordOffset = aTextBase + record * TEXT_RECORD_BYTES;
4848 if( aCursor.
U16At( recordOffset + 24 ) != aCursor.
U16At( recordOffset + 26 ) )
4851 SOURCE_PROVENANCE textSource = sourceAt( aSourceName, aModel.version, wxS(
"free text" ), 1, record,
4852 recordOffset, TEXT_RECORD_BYTES,
static_cast<int>( aSheetIndex ) );
4853 uint32_t stringOffset = aCursor.
U32At( recordOffset + 8 );
4854 uint16_t stringBytes = aCursor.
U16At( recordOffset + 20 );
4856 if( stringOffset > aHeapBytes )
4860 offsetSource.length = 4;
4861 throwDecodeError( offsetSource, wxS(
"free-text string offset leaves controller 2" ) );
4864 if( stringBytes == 0 || stringBytes > aHeapBytes - stringOffset )
4868 lengthSource.length = 2;
4869 throwDecodeError( lengthSource, wxS(
"free-text string length leaves controller 2" ) );
4872 if( aBytes[aHeapBase + stringOffset + stringBytes - 1] != 0 )
4875 sourceAt( aSourceName, aModel.version, wxS(
"free text string terminator" ), 2, record,
4876 aHeapBase + stringOffset + stringBytes - 1, 1,
static_cast<int>( aSheetIndex ) );
4877 throwDecodeError( terminatorSource, wxS(
"free-text string is not NUL terminated" ) );
4881 sourceAt( aSourceName, aModel.version, wxS(
"free text string" ), 2, record,
4882 aHeapBase + stringOffset, stringBytes - 1,
static_cast<int>( aSheetIndex ) );
4883 std::vector<uint8_t> string( aBytes.begin() + stringSource.absoluteOffset,
4884 aBytes.begin() + stringSource.absoluteOffset + stringSource.length );
4885 uint16_t justification = aCursor.
U16At( recordOffset + 18 );
4889 text.
sheet = { aModel.sheets[aSheetIndex].id, textSource };
4892 text.position = { decodeCoordinate( aCursor.
U16At( recordOffset + 12 ) ),
4893 decodeCoordinate( aCursor.
U16At( recordOffset + 14 ) ), textSource };
4895 text.presentation.source = textSource;
4896 text.presentation.height = aCursor.
U16At( recordOffset + 22 );
4897 text.presentation.width = aCursor.
U8At( recordOffset + 30 );
4898 const uint8_t displayFlags = aCursor.
U8At( recordOffset + 31 );
4899 text.presentation.visible = ( displayFlags & 1 ) == 0;
4900 text.presentation.horizontalJustification = freeTextHorizontalJustification( justification );
4901 text.presentation.verticalJustification = verticalJustification( justification );
4904 fontHandleSource.
length = 2;
4905 decodeGlobalFont( aBytes, aCursor, aGlobals, aSourceName, aModel.version,
4906 static_cast<int16_t
>( aCursor.
U16At( recordOffset ) ), fontHandleSource,
4907 text.presentation,
false, aModel.diagnostics );
4911 displaySource.length = 1;
4912 text.presentation.properties.push_back( sourceProperty(
4913 wxS(
"display_flags" ), wxString::Format( wxS(
"%u" ), displayFlags ), displaySource ) );
4916 relationshipSource.
objectClass = wxS(
"free text relationship" );
4917 relationshipSource.absoluteOffset += 28;
4918 relationshipSource.length = 2;
4919 uint16_t relationship = aCursor.
U16At( relationshipSource.absoluteOffset );
4921 relationshipProperty.
name.
text = wxS(
"controller_1_relationship_word_28" );
4922 relationshipProperty.name.source = relationshipSource;
4923 relationshipProperty.value.raw = {
static_cast<uint8_t
>( relationship ),
4924 static_cast<uint8_t
>( relationship >> 8 ) };
4925 relationshipProperty.value.text = wxString::Format( wxS(
"%u" ), relationship );
4927 relationshipProperty.value.source = relationshipSource;
4928 relationshipProperty.value.codePage = DEFAULT_CODE_PAGE;
4929 relationshipProperty.value.codePageName = wxS(
"windows-1252" );
4931 relationshipProperty.source = relationshipSource;
4932 text.properties.push_back( std::move( relationshipProperty ) );
4933 aModel.texts.push_back( std::move(
text ) );
4938 void decodeSheetBlocks(
const std::vector<uint8_t>& aBytes,
const PADS_IO::BINARY_CURSOR& aCursor,
4941 size_t sheetIndex = 0;
4942 std::optional<PLACEMENT_GLOBALS> placementData;
4943 std::optional<CONNECTIVITY_GLOBALS> connectivityData;
4944 const PLACEMENT_LAYOUT& placementSchema = placementLayout( aModel.version );
4946 if( placementSchema.decoded )
4948 placementData = placementGlobals( aSdb, aSourceName );
4949 connectivityData = connectivityGlobals( aBytes, aCursor, aSdb, aSourceName, aModel );
4957 size_t descriptors = block.offset + SHEET_HEADER_BYTES;
4958 size_t payload = descriptors + SHEET_DESCRIPTOR_COUNT * SHEET_DESCRIPTOR_BYTES;
4959 uint32_t textCount = aCursor.
U32At( descriptors + SHEET_COUNT_OFFSET );
4960 uint32_t textBytes = aCursor.
U32At( descriptors + SHEET_USED_BYTES_OFFSET );
4961 uint32_t heapBytes = aCursor.
U32At( descriptors + SHEET_DESCRIPTOR_BYTES + SHEET_USED_BYTES_OFFSET );
4963 SOURCE_PROVENANCE controllerSource = sourceAt( aSourceName, aModel.version, wxS(
"text controller" ), 1, 0,
4964 payload, textBytes,
static_cast<int>( sheetIndex ) );
4966 if( textBytes != textCount * TEXT_RECORD_BYTES )
4967 throwDecodeError( controllerSource,
4968 wxS(
"text-controller byte count does not match 32-byte records" ) );
4970 size_t heapOffset = payload + textBytes;
4972 if( !placementSchema.decoded )
4974 SOURCE_PROVENANCE heapSource = sourceAt( aSourceName, aModel.version, wxS(
"text string controller" ),
4975 2, 0, heapOffset, heapBytes,
static_cast<int>( sheetIndex ) );
4976 aModel.preservedControllerPayloads.push_back(
4979 { aBytes.begin() + payload, aBytes.begin() + payload + textBytes } } );
4980 aModel.preservedControllerPayloads.push_back(
4983 { aBytes.begin() + heapOffset, aBytes.begin() + heapOffset + heapBytes } } );
4984 decodeDefinitionsAndParts( aBytes, aCursor, block, sheetIndex, aSourceName, aModel );
4989 decodeFreeText( aBytes, aCursor, aSourceName, sheetIndex, payload, textCount, heapOffset, heapBytes,
4990 *placementData, aModel );
4991 decodeDefinitionsAndParts( aBytes, aCursor, block, sheetIndex, aSourceName, aModel );
4992 decodePlacements( aBytes, aCursor, block, sheetIndex, aSourceName, *placementData, aModel );
4993 decodeConnectivity( aBytes, aCursor, block, sheetIndex, aSourceName, *connectivityData, aModel );
4999 void decodeOleImages(
const std::vector<uint8_t>& aBytes,
const PADS_SCH_SDB& aSdb,
const wxString& aSourceName,
5006 sourceAt( aSourceName, aModel.version, wxS(
"embedded OLE image" ), item.cfb.controller,
index,
5007 item.cfb.offset, item.cfb.bytes,
static_cast<int>( item.sheetPlane ) );
5008 OLE_IMAGE_PAYLOAD payload =
ExtractOleImage( aBytes.data() + item.cfb.offset, item.cfb.bytes );
5011 image.source = source;
5012 image.sheet = { aModel.sheets[item.sheetPlane].id, source };
5014 image.extent = item.extent;
5015 image.databaseBox = { item.left, item.bottom, item.right, item.top };
5017 boxSource.
objectClass = wxS(
"embedded OLE image database box" );
5018 boxSource.absoluteOffset = item.boxOffset;
5019 boxSource.length = 16;
5020 int64_t
left = decodeDatabaseCoordinate( item.left, boxSource );
5021 int64_t bottom = decodeDatabaseCoordinate( item.bottom, boxSource );
5022 int64_t
right = decodeDatabaseCoordinate( item.right, boxSource );
5023 int64_t
top = decodeDatabaseCoordinate( item.top, boxSource );
5028 image.mirrorVertical = bottom <
top;
5029 image.flags = item.flags;
5032 switch( payload.
type )
5039 aModel.diagnostics.emplace_back(
5041 wxS(
"embedded OLE object has no supported BMP, DIB, or WMF stream" ) );
5048 aModel.diagnostics.emplace_back(
5050 wxS(
"embedded OLE image has a zero-size database box and was skipped" ) );
5053 if( item.flags != 1 )
5056 flagsSource.
objectClass = wxS(
"embedded OLE image flags" );
5057 flagsSource.absoluteOffset = item.boxOffset + 20;
5058 flagsSource.length = 4;
5060 aModel.diagnostics );
5063 aModel.images.push_back( std::move(
image ) );
5073 return wxString::Format(
5074 wxS(
"%s: PADS schematic v0x%04X %s (controller %d, record %llu, sheet %d) at offset 0x%llX: %s" ),
5077 static_cast<unsigned long long>( aSource.
absoluteOffset ), aMessage );
5094 return std::nullopt;
5101 const wxString& aMessage )
5116 validateIdentity( *
this );
5118 const MODEL_REFERENCE_INDEX
index( *
this );
5120 validateSymbolGraph( *
this,
index );
5121 validatePlacements( *
this,
index );
5122 validateSheetBoundContent( *
this,
index );
5123 validateSheetHierarchy( *
this,
index );
5136 model.source = { aSourceName,
model.version, wxS(
"model" ), -1, 0, 0, aBytes.size(), -1 };
5137 model.settings.codePage = DEFAULT_CODE_PAGE;
5139 const std::vector<MODEL_FIELD> titleFields = decodeDesignSettings( aBytes,
cursor, sdb, aSourceName,
model );
5141 decodeSheets( aBytes,
cursor, sdb, aSourceName, titleFields,
model );
5142 decodeSheetBlocks( aBytes,
cursor, sdb, aSourceName,
model );
5143 decodeOleImages( aBytes, sdb, aSourceName,
model );
5144 assignFieldIds(
model );
5145 model.ValidateOrThrow();
5152 std::vector<PARSER_DIAGNOSTIC>& aDiagnostics,
5153 const wxString& aRecordedCodePageName )
5156 result.codePage = aCodePage;
5157 const wxString defaultName = aCodePage == 65001 ? wxString( wxS(
"UTF-8" ) )
5158 : aCodePage == 1252 ? wxString( wxS(
"windows-1252" ) )
5159 : wxString::Format( wxS(
"unknown-%u" ), aCodePage );
5160 result.codePageName = aRecordedCodePageName.empty() ? defaultName : aRecordedCodePageName;
5162 if( aCodePage == 1252 )
5164 else if( aCodePage != 65001 )
5167 if( aCodePage != 65001 && aCodePage != 1252 )
5170 wxString::Format( wxS(
"unknown code page %u; bytes preserved and "
5171 "non-ASCII bytes decoded as U+FFFD" ),
5175 if( aBytes.empty() )
5178 if( aCodePage == 1252 )
5180 result.text = decodeWindows1252( aBytes );
5184 if( aCodePage != 65001 )
5186 result.text = decodeUnknownCodePage( aBytes );
5190 bool invalid =
false;
5191 result.text = decodeUtf8( aBytes, invalid );
5197 wxS(
"invalid UTF-8 bytes replaced with U+FFFD; original bytes preserved" ) } );
5206 std::vector<PARSER_DIAGNOSTIC>& aDiagnostics )
5209 wxString::Format( wxS(
"unknown %s %u preserved" ), aEnumName, aValue ) } );
Bounds-checked little-endian read cursor over a PADS binary buffer.
uint8_t U8At(size_t aOffset) const
uint16_t U16At(size_t aOffset) const
uint32_t U32At(size_t aOffset) const
PADS_SCH_MODEL Parse(const std::vector< uint8_t > &aBytes, const wxString &aSourceName={}) const
static SOURCE_STRING DecodeString(const std::vector< uint8_t > &aBytes, uint32_t aCodePage, const SOURCE_PROVENANCE &aSource, std::vector< PARSER_DIAGNOSTIC > &aDiagnostics, const wxString &aRecordedCodePageName={})
static void RecordUnknownEnum(const wxString &aEnumName, uint32_t aValue, const SOURCE_PROVENANCE &aSource, std::vector< PARSER_DIAGNOSTIC > &aDiagnostics)
const PADS_IO::BINARY_CURSOR & Cursor() const
void Load(std::vector< uint8_t > aBytes)
const std::array< SCH_SDB_POOL, 20 > & Pools() const
size_t PayloadOffset() const
const std::vector< SCH_SDB_BLOCK > & Blocks() const
const std::vector< SCH_SDB_OLE_ITEM > & OleItems() const
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
CONTROLLER_REFERENCE< DEFINITION_ID > DEFINITION_REFERENCE
constexpr int NormalizeAngle(int aAngle)
CONTROLLER_ID< NET_ID_TAG > NET_ID
CONTROLLER_ID< IMAGE_ID_TAG > IMAGE_ID
CONTROLLER_ID< DEFINITION_ID_TAG > DEFINITION_ID
CONTROLLER_REFERENCE< PLACEMENT_ID > PLACEMENT_REFERENCE
CONTROLLER_REFERENCE< SHEET_ID > SHEET_REFERENCE
CONTROLLER_ID< SHEET_ID_TAG > SHEET_ID
constexpr uint32_t FIELD_ID_MAX_ORDINAL
CONTROLLER_ID< PIN_ID_TAG > PIN_ID
constexpr FIELD_ID MakeFieldId(FIELD_ID_DOMAIN aDomain, uint32_t aOwner, uint32_t aOrdinal)
CONTROLLER_ID< PART_TYPE_ID_TAG > PART_TYPE_ID
wxString FormatParserError(const SOURCE_PROVENANCE &aSource, const wxString &aMessage)
CONTROLLER_ID< PLACEMENT_ID_TAG > PLACEMENT_ID
CONTROLLER_ID< GATE_ID_TAG > GATE_ID
CONTROLLER_REFERENCE< NET_ID > NET_REFERENCE
CONTROLLER_REFERENCE< GATE_ID > GATE_REFERENCE
std::optional< DIAGNOSTIC_PROPERTY_KEY > DiagnosticPropertyKey(const PARSER_DIAGNOSTIC &aDiagnostic)
PARSER_DIAGNOSTIC MakePropertyDiagnostic(SEVERITY aSeverity, const SOURCE_PROPERTY &aProperty, const wxString &aMessage)
CONTROLLER_ID< BUS_ID_TAG > BUS_ID
bool IsValid(const std::string &aString, SIM_VALUE::TYPE aValueType=SIM_VALUE::TYPE_FLOAT, NOTATION aNotation=NOTATION::SI)
std::chrono::steady_clock clock
void decode(const std::vector< uint8_t > &aInput, std::vector< uint8_t > &aOutput)
std::variant< double, std::string > Value
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
OLE_IMAGE_PAYLOAD ExtractOleImage(const uint8_t *aCfb, size_t aSize)
Prefer CONTENTS, then OlePres000, then the native stream.
std::vector< uint8_t > data
PROPERTY_DISPOSITION disposition
DIAGNOSTIC_PROPERTY_IDENTITY property
bool operator<(const DIAGNOSTIC_PROPERTY_KEY &aOther) const
DEFINITION_REFERENCE definition
void ValidateOrThrow() const
std::optional< DIAGNOSTIC_PROPERTY_IDENTITY > property
SOURCE_POINT numberOffset
PROPERTY_DISPOSITION disposition
KIBIS top(path, &reporter)
const SHAPE_LINE_CHAIN chain
wxString result
Test unit parsing edge cases and error handling.