KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pads_sch_binary_parser.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
21
22#include "pads_sch_sdb.h"
23
24#include <sch_io/ole_image.h>
25
26#include <algorithm>
27#include <array>
28#include <functional>
29#include <iterator>
30#include <map>
31#include <ranges>
32#include <set>
33#include <tuple>
34#include <unordered_map>
35#include <unordered_set>
36
37#include <ki_exception.h>
38#include <wx/strconv.h>
39
40namespace PADS_SCH_BINARY
41{
42
43namespace
44{
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 };
49
50
51 uint16_t terminalJustification( uint16_t aNibble, bool aRotated )
52 {
53 return aRotated ? JUSTIFICATION_BY_NIBBLE_90[aNibble] : JUSTIFICATION_BY_NIBBLE_0[aNibble];
54 }
55
56
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;
89
90 struct GLOBAL_NET_RECORD
91 {
92 SOURCE_PROVENANCE source;
93 SOURCE_STRING name;
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;
103 };
104
105
106 struct CONNECTIVITY_GLOBALS
107 {
108 size_t membershipBase = 0;
109 uint32_t membershipCount = 0;
110 size_t fontBase = 0;
111 uint32_t fontCount = 0;
112 std::vector<uint16_t> membershipSheets;
113 std::vector<GLOBAL_NET_RECORD> nets;
114 };
115
116 struct PLACEMENT_LAYOUT
117 {
118 uint16_t version;
119 bool decoded;
120 size_t placementBytes;
121 size_t placedPinBytes;
122 size_t fieldBytes;
123 size_t pinStart;
124 size_t componentIdentity;
125 size_t componentGroup;
126 size_t x;
127 size_t y;
128 size_t angle;
129 size_t mirror;
130 size_t partType;
131 size_t decal;
132 size_t gate;
133 size_t pinCount;
134 size_t fieldCount;
135 size_t reference;
136 size_t referenceFont;
137 size_t partTypeFont;
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;
148 size_t customFont;
149 size_t customX;
150 size_t customAngle;
151 size_t customJustification;
152 size_t customAttributeIndex;
153 size_t customHeight;
154 size_t customWidth;
155 size_t customDisplayFlags;
156 size_t customTail;
157 };
158
159 constexpr PLACEMENT_LAYOUT PLACEMENT_LAYOUTS[] = { []
160 {
161 PLACEMENT_LAYOUT layout{};
162 layout.version = 0x000C;
163 return layout;
164 }(),
165 { .version = 0x000D,
166 .decoded = true,
167 .placementBytes = 136,
168 .placedPinBytes = 12,
169 .fieldBytes = 24,
170 .pinStart = 0x14,
171 .componentIdentity = 0x18,
172 .componentGroup = 0x1C,
173 .x = 0x20,
174 .y = 0x22,
175 .angle = 0x24,
176 .mirror = 0x26,
177 .partType = 0x42,
178 .decal = 0x44,
179 .gate = 0x4A,
180 .pinCount = 0x4C,
181 .fieldCount = 0x4E,
182 .reference = 0x5E,
183 .referenceFont = 0,
184 .partTypeFont = 2,
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,
195 .customFont = 0,
196 .customX = 8,
197 .customAngle = 12,
198 .customJustification = 14,
199 .customAttributeIndex = 16,
200 .customHeight = 18,
201 .customWidth = 20,
202 .customDisplayFlags = 21,
203 .customTail = 22 } };
204
205 const PLACEMENT_LAYOUT& placementLayout( uint16_t aVersion )
206 {
207 auto layout = std::ranges::find_if( PLACEMENT_LAYOUTS,
208 [&]( const PLACEMENT_LAYOUT& aLayout )
209 {
210 return aLayout.version == aVersion;
211 } );
212
213 if( layout == std::end( PLACEMENT_LAYOUTS ) )
214 THROW_IO_ERROR( wxString::Format( wxS( "unsupported PADS placement layout v0x%04X" ), aVersion ) );
215
216 return *layout;
217 }
218
219
220 size_t outerControllerOffset( const PADS_SCH_SDB& aSdb, size_t aController )
221 {
222 size_t offset = aSdb.PayloadOffset() + 4;
223
224 for( size_t controller = 1; controller < aController; ++controller )
225 offset += aSdb.Pools()[controller].usedBytes;
226
227 return offset;
228 }
229
230
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 )
233 {
234 return { aFile, aVersion, aObjectClass, aController, aRecord, aOffset, aLength, aSheet };
235 }
236
237
238 [[noreturn]] void throwDecodeError( const SOURCE_PROVENANCE& aSource, const wxString& aMessage )
239 {
240 THROW_IO_ERROR( FormatParserError( aSource, aMessage ) );
241 }
242
243
244 int64_t decodeCoordinate( uint16_t aRaw )
245 {
246 return static_cast<int64_t>( aRaw ) * 4 - 198144;
247 }
248
249
250 int64_t decodeDatabaseCoordinate( int32_t aRaw, const SOURCE_PROVENANCE& aSource )
251 {
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" ) );
254
255 return decodeCoordinate( static_cast<uint16_t>( static_cast<int16_t>( aRaw ) ) );
256 }
257
258
259 int64_t decodeLocalCoordinate( uint16_t aRaw )
260 {
261 return static_cast<int64_t>( static_cast<int16_t>( aRaw ) ) * 4;
262 }
263
264
265 int64_t decodeTerminalCoordinate( uint16_t aRaw )
266 {
267 return static_cast<int64_t>( static_cast<int16_t>( aRaw ) ) * 4;
268 }
269
270
271 SOURCE_STRING decodeFixedString( const std::vector<uint8_t>& aBytes, size_t aOffset, size_t aBytesAvailable,
272 const SOURCE_PROVENANCE& aSource, std::vector<PARSER_DIAGNOSTIC>& aDiagnostics )
273 {
274 size_t end = aOffset;
275
276 while( end < aOffset + aBytesAvailable && aBytes[end] != 0 )
277 ++end;
278
279 if( end == aOffset + aBytesAvailable )
280 throwDecodeError( aSource, wxS( "unterminated fixed string" ) );
281
282 return PADS_SCH_BINARY_PARSER::DecodeString( { aBytes.begin() + aOffset, aBytes.begin() + end },
283 DEFAULT_CODE_PAGE, aSource, aDiagnostics );
284 }
285
286
287 SOURCE_PROPERTY sourceProperty( const wxString& aName, const wxString& aValue, const SOURCE_PROVENANCE& aSource )
288 {
289 SOURCE_PROPERTY property;
290 property.name.text = aName;
291 property.name.source = aSource;
292 property.value.text = aValue;
293 property.value.source = aSource;
294 property.disposition = PROPERTY_DISPOSITION::EXACT;
295 property.source = aSource;
296 return property;
297 }
298
299
300 bool samePointValue( const SOURCE_POINT& aLeft, const SOURCE_POINT& aRight )
301 {
302 return aLeft.x == aRight.x && aLeft.y == aRight.y;
303 }
304
305
306 bool samePresentationValue( const MODEL_TEXT_PRESENTATION& aLeft, const MODEL_TEXT_PRESENTATION& aRight )
307 {
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;
313 }
314
315
316 bool sameGraphicValue( const MODEL_GRAPHIC& aLeft, const MODEL_GRAPHIC& aRight )
317 {
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 );
326 }
327
328
329 bool sameWorksheetValue( const MODEL_WORKSHEET& aLeft, const MODEL_WORKSHEET& aRight )
330 {
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;
335
336 for( const MODEL_GRAPHIC& graphic : aLeft.graphics )
337 ( graphic.kind == MODEL_GRAPHIC_KIND::TEXT ? leftText : leftDrawing ).push_back( &graphic );
338
339 for( const MODEL_GRAPHIC& graphic : aRight.graphics )
340 ( graphic.kind == MODEL_GRAPHIC_KIND::TEXT ? rightText : rightDrawing ).push_back( &graphic );
341
342 const auto equalPointers = []( const MODEL_GRAPHIC* aLeftGraphic, const MODEL_GRAPHIC* aRightGraphic )
343 {
344 return sameGraphicValue( *aLeftGraphic, *aRightGraphic );
345 };
346
347 return std::ranges::equal( leftDrawing, rightDrawing, equalPointers )
348 && std::ranges::is_permutation( leftText, rightText, equalPointers );
349 }
350
351
352 bool isProvenGraphicStrokeWidth( uint8_t aWidth )
353 {
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 );
356 }
357
358
359 void decodeGraphicStrokeWidth( uint8_t aRawWidth, uint8_t aPackedPresentation,
360 const SOURCE_PROVENANCE& aGraphicSource, MODEL_GRAPHIC& aGraphic,
361 std::vector<PARSER_DIAGNOSTIC>& aDiagnostics )
362 {
363 SOURCE_PROVENANCE packedSource = aGraphicSource;
364 packedSource.absoluteOffset += 5;
365 packedSource.length = 1;
366 SOURCE_PROPERTY packed = sourceProperty( wxS( "preserved_graphic_presentation" ),
367 wxString::Format( wxS( "%u" ), aPackedPresentation ), packedSource );
368 packed.disposition = PROPERTY_DISPOSITION::PRESERVED;
369 aGraphic.properties.push_back( std::move( packed ) );
370
371 if( isProvenGraphicStrokeWidth( aRawWidth ) )
372 {
373 aGraphic.strokeWidth = static_cast<int64_t>( aRawWidth ) * 2;
374 return;
375 }
376
377 SOURCE_PROVENANCE strokeSource = aGraphicSource;
378 strokeSource.absoluteOffset += 4;
379 strokeSource.length = 1;
380 SOURCE_PROPERTY stroke = sourceProperty( wxS( "unsupported_graphic_stroke_width" ),
381 wxString::Format( wxS( "%u" ), aRawWidth ), strokeSource );
382 stroke.disposition = PROPERTY_DISPOSITION::UNSUPPORTED;
383 aDiagnostics.push_back( MakePropertyDiagnostic(
384 RPT_SEVERITY_WARNING, stroke, wxS( "unproved graphic stroke width preserved; using hairline" ) ) );
385 aGraphic.properties.push_back( std::move( stroke ) );
386 }
387
388
389 SOURCE_STRING decodedDefinitionFont( int16_t aHandle, const SOURCE_PROVENANCE& aSource )
390 {
391 SOURCE_STRING font;
392 font.source = aSource;
393
394 if( aHandle == -1 || aHandle == -4 )
395 {
396 font.text = wxS( "Default Font" );
397 font.encoding = STRING_ENCODING_STATUS::CODE_PAGE;
398 font.codePage = DEFAULT_CODE_PAGE;
399 font.codePageName = wxS( "windows-1252" );
400 }
401
402 return font;
403 }
404
405
406 uint32_t pinElectricalType( uint8_t aType, const SOURCE_PROVENANCE& aSource,
407 std::vector<PARSER_DIAGNOSTIC>& aDiagnostics )
408 {
409 switch( aType )
410 {
411 case 'U': return 0;
412 case 'L': return 1;
413 case 'S': return 2;
414 case 'B': return 3;
415 case 'T': return 4;
416 case 'C': return 5;
417 case 'E': return 6;
418 case 'P':
419 case 'G': return 7;
420 case 'Z': return 8;
421 default:
422 PADS_SCH_BINARY_PARSER::RecordUnknownEnum( wxS( "pin electrical type" ), aType, aSource, aDiagnostics );
423 return 8;
424 }
425 }
426
427
428 SOURCE_POINT pageExtent( uint8_t aPage, const SOURCE_PROVENANCE& aSource )
429 {
430 switch( aPage )
431 {
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" ) );
438 }
439 }
440
441
442 MODEL_JUSTIFICATION horizontalJustification( uint16_t aValue )
443 {
444 const uint16_t justification = aValue & 0x00FF;
445 const uint16_t horizontal = justification >= 8 ? justification - 8
446 : justification >= 2 ? justification - 2
447 : justification;
448
449 switch( horizontal )
450 {
451 default:
452 case 0: return MODEL_JUSTIFICATION::LEFT;
453 case 1: return MODEL_JUSTIFICATION::RIGHT;
454 case 4: return MODEL_JUSTIFICATION::CENTER;
455 }
456 }
457
458
459 MODEL_JUSTIFICATION verticalJustification( uint16_t aValue )
460 {
461 const uint16_t justification = aValue & 0x00FF;
462
463 if( justification >= 8 )
465
466 if( justification >= 2 )
468
470 }
471
472
473 MODEL_JUSTIFICATION freeTextHorizontalJustification( uint16_t aValue )
474 {
475 switch( aValue & 0x000F )
476 {
477 case 0:
478 case 2:
479 case 8: return MODEL_JUSTIFICATION::LEFT;
480
481 case 4:
482 case 6:
483 case 10:
484 case 12:
485 case 14: return MODEL_JUSTIFICATION::CENTER;
486
487 default: return MODEL_JUSTIFICATION::RIGHT;
488 }
489 }
490
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 };
495
496
497 wxString decodeWindows1252( const std::vector<uint8_t>& aBytes )
498 {
499 wxString result;
500
501 for( uint8_t byte : aBytes )
502 {
503 uint32_t codePoint = byte;
504
505 if( byte >= 0x80 && byte <= 0x9F )
506 codePoint = CP1252_HIGH[byte - 0x80];
507
508 result += wxUniChar( codePoint );
509 }
510
511 return result;
512 }
513
514
515 wxString decodeUnknownCodePage( const std::vector<uint8_t>& aBytes )
516 {
517 wxString result;
518
519 for( uint8_t byte : aBytes )
520 result += wxUniChar( byte < 0x80 ? byte : 0xFFFD );
521
522 return result;
523 }
524
525
526 wxString decodeUtf8( const std::vector<uint8_t>& aBytes, bool& aHadInvalidBytes )
527 {
528 wxString result;
529 aHadInvalidBytes = false;
530
531 for( size_t i = 0; i < aBytes.size(); )
532 {
533 uint8_t lead = aBytes[i];
534 uint32_t codePoint = 0;
535 size_t continuationCount = 0;
536 uint32_t minimum = 0;
537
538 if( lead < 0x80 )
539 {
540 result += wxUniChar( lead );
541 ++i;
542 continue;
543 }
544 else if( lead >= 0xC2 && lead <= 0xDF )
545 {
546 codePoint = lead & 0x1F;
547 continuationCount = 1;
548 minimum = 0x80;
549 }
550 else if( lead >= 0xE0 && lead <= 0xEF )
551 {
552 codePoint = lead & 0x0F;
553 continuationCount = 2;
554 minimum = 0x800;
555 }
556 else if( lead >= 0xF0 && lead <= 0xF4 )
557 {
558 codePoint = lead & 0x07;
559 continuationCount = 3;
560 minimum = 0x10000;
561 }
562 else
563 {
564 result += wxUniChar( 0xFFFD );
565 aHadInvalidBytes = true;
566 ++i;
567 continue;
568 }
569
570 bool valid = i + continuationCount < aBytes.size();
571
572 for( size_t j = 1; valid && j <= continuationCount; ++j )
573 {
574 uint8_t continuation = aBytes[i + j];
575 valid = ( continuation & 0xC0 ) == 0x80;
576
577 if( valid )
578 codePoint = ( codePoint << 6 ) | ( continuation & 0x3F );
579 }
580
581 valid = valid && codePoint >= minimum && codePoint <= 0x10FFFF
582 && !( codePoint >= 0xD800 && codePoint <= 0xDFFF );
583
584 if( !valid )
585 {
586 result += wxUniChar( 0xFFFD );
587 aHadInvalidBytes = true;
588 ++i;
589 continue;
590 }
591
592 result += wxUniChar( codePoint );
593 i += continuationCount + 1;
594 }
595
596 return result;
597 }
598
599 [[noreturn]] void throwValidationError( const SOURCE_PROVENANCE& aSource, const wxString& aMessage );
600
601
602 template <typename Item, typename IdAccessor, typename SourceAccessor>
603 void validateUniqueIds( const std::vector<Item>& aItems, const wxString& aObjectClass, IdAccessor aId,
604 SourceAccessor aSource )
605 {
606 std::map<uint32_t, SOURCE_PROVENANCE> declarations;
607
608 for( const Item& item : aItems )
609 {
610 const auto& id = aId( item );
611
612 if( !id.IsValid() )
613 throwValidationError( aSource( item ), wxString::Format( wxS( "invalid %s ID" ), aObjectClass ) );
614
615 auto [first, inserted] = declarations.emplace( id.Value(), aSource( item ) );
616
617 if( !inserted )
618 {
619 const SOURCE_PROVENANCE& firstSource = first->second;
620 wxString detail = wxString::Format(
621 wxS( "duplicate %s ID %u; first at v0x%04X %s controller %d record %llu sheet %d "
622 "offset 0x%llX" ),
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 );
627 }
628 }
629 }
630
631
632 [[noreturn]] void throwValidationError( const SOURCE_PROVENANCE& aSource, const wxString& aMessage )
633 {
634 THROW_IO_ERROR( FormatParserError( aSource, aMessage ) );
635 }
636
637
638 constexpr auto itemId = []( const auto& aItem ) -> const auto&
639 {
640 return aItem.id;
641 };
642
643
644 constexpr auto itemProvenance = []( const auto& aItem ) -> const SOURCE_PROVENANCE&
645 {
646 return aItem.source;
647 };
648
649
650 template <typename Item, typename Declarations>
651 void validateNestedId( const Item& aItem, const wxString& aObjectClass, Declarations& aDeclarations )
652 {
653 if( !aItem.id.IsValid() )
654 throwValidationError( aItem.source, wxString::Format( wxS( "invalid %s ID" ), aObjectClass ) );
655
656 auto [first, inserted] = aDeclarations.emplace( aItem.id.Value(), aItem.source );
657
658 if( !inserted )
659 {
660 throwValidationError(
661 aItem.source,
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 ) ) );
668 }
669 }
670
671
672 struct MODEL_REFERENCE_INDEX
673 {
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;
683
684 explicit MODEL_REFERENCE_INDEX( const PADS_SCH_MODEL& aModel )
685 {
686 for( const MODEL_SHEET& sheet : aModel.sheets )
687 sheets.emplace( sheet.id.Value(), &sheet );
688
689 for( const MODEL_SYMBOL_DEFINITION& definition : aModel.definitions )
690 {
691 definitions.emplace( definition.id.Value(), &definition );
692
693 for( const MODEL_PIN_DEFINITION& pin : definition.pins )
694 {
695 pins.emplace( pin.id.Value(), &pin );
696 pinOwners.emplace( pin.id.Value(), &definition );
697 }
698 }
699
700 for( const MODEL_PART_TYPE& partType : aModel.partTypes )
701 {
702 partTypes.emplace( partType.id.Value(), &partType );
703
704 for( const MODEL_GATE& gate : partType.gates )
705 {
706 gates.emplace( gate.id.Value(), &gate );
707 gateOwners.emplace( gate.id.Value(), &partType );
708 }
709 }
710
711 for( const MODEL_PLACEMENT& placement : aModel.placements )
712 placements.emplace( placement.id.Value(), &placement );
713
714 for( const MODEL_NET& net : aModel.nets )
715 nets.emplace( net.id.Value(), &net );
716 }
717 };
718
719
720 bool endpointIsValid( const MODEL_REFERENCE_INDEX& aIndex, const MODEL_CONNECTION_ENDPOINT& aEndpoint )
721 {
722 if( aEndpoint.kind == MODEL_ENDPOINT_KIND::POINT )
723 return !aEndpoint.placement && !aEndpoint.pin;
724
725 if( aEndpoint.kind != MODEL_ENDPOINT_KIND::PIN || !aEndpoint.placement || !aEndpoint.pin )
726 return false;
727
728 auto placement = aIndex.placements.find( aEndpoint.placement->id.Value() );
729
730 if( placement == aIndex.placements.end() )
731 return false;
732
733 return aIndex.pins.contains( aEndpoint.pin->id.Value() )
734 && std::ranges::any_of( placement->second->pins,
735 [&]( const PIN_REFERENCE& aPin )
736 {
737 return aPin.id == aEndpoint.pin->id;
738 } );
739 }
740
741
742 struct SHEET_CONTROLLERS
743 {
744 std::array<SCH_SDB_POOL, 23> pools;
745 std::array<size_t, 23> offsets;
746 };
747
748
749 struct PLACEMENT_GLOBALS
750 {
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;
757 size_t fontBase = 0;
758 uint32_t fontCount = 0;
759 };
760
761
762 PLACEMENT_GLOBALS placementGlobals( const PADS_SCH_SDB& aSdb, const wxString& aSourceName )
763 {
764 PLACEMENT_GLOBALS result;
765
766 auto requireOuterStride = [&]( size_t aController, size_t aStride )
767 {
768 const SCH_SDB_POOL& pool = aSdb.Pools()[aController];
769
770 if( pool.usedBytes != pool.count * aStride )
771 {
772 SOURCE_PROVENANCE source = sourceAt(
773 aSourceName, aSdb.Version(), wxS( "outer controller directory" ), aController, 0,
774 OUTER_DIRECTORY_OFFSET + aController * OUTER_DESCRIPTOR_BYTES + OUTER_USED_BYTES_OFFSET, 4,
775 -1 );
776 throwDecodeError( source,
777 wxString::Format( wxS( "controller byte count does not match %llu-byte records" ),
778 static_cast<unsigned long long>( aStride ) ) );
779 }
780 };
781
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 );
788 result.groupCount = aSdb.Pools()[6].count;
789 result.attributeOffsetBase = outerControllerOffset( aSdb, 7 );
790 result.attributeOffsetCount = aSdb.Pools()[7].count;
791 result.fontBase = outerControllerOffset( aSdb, 19 );
792 result.fontCount = aSdb.Pools()[19].count;
793 return result;
794 }
795
796
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,
799 int16_t aHandle, const SOURCE_PROVENANCE& aHandleSource,
800 MODEL_TEXT_PRESENTATION& aPresentation, bool aStrictHandle,
801 std::vector<PARSER_DIAGNOSTIC>& aDiagnostics )
802 {
803 if( aHandle == -1 || aHandle == -4 )
804 {
805 aPresentation.font = decodedDefinitionFont( aHandle, aHandleSource );
806 return;
807 }
808
809 if( aHandle < 0 || static_cast<uint32_t>( aHandle ) >= aGlobals.fontCount )
810 {
811 if( aStrictHandle )
812 throwDecodeError( aHandleSource, wxS( "placement font handle leaves outer controller 19" ) );
813
814 SOURCE_PROPERTY property = sourceProperty(
815 wxS( "inline_font_payload" ), wxString::Format( wxS( "%u" ), uint16_t( aHandle ) ), aHandleSource );
816 property.disposition = PROPERTY_DISPOSITION::UNSUPPORTED;
817 aDiagnostics.push_back( MakePropertyDiagnostic(
818 RPT_SEVERITY_WARNING, property, wxS( "unsupported inline placement font payload preserved" ) ) );
819 aPresentation.properties.push_back( std::move( property ) );
820 return;
821 }
822
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 );
827
828 SOURCE_PROVENANCE nameSource = fontSource;
829 nameSource.absoluteOffset += 4;
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;
836
837 if( aPresentation.bold )
838 aPresentation.font.text.Prepend( wxS( "Bold " ) );
839
840 if( aPresentation.italic )
841 aPresentation.font.text.Prepend( wxS( "Italic " ) );
842
843 aPresentation.properties.push_back(
844 sourceProperty( wxS( "font_handle" ), wxString::Format( wxS( "%d" ), aHandle ), fontSource ) );
845
846 if( ( style & ~uint32_t{ 7 } ) != 0 )
847 {
848 SOURCE_PROPERTY property = sourceProperty( wxS( "unsupported_font_style_flags" ),
849 wxString::Format( wxS( "%u" ), style & ~7U ), fontSource );
850 property.disposition = PROPERTY_DISPOSITION::UNSUPPORTED;
851 aDiagnostics.push_back( MakePropertyDiagnostic(
852 RPT_SEVERITY_WARNING, property, wxS( "unsupported placement font style flags preserved" ) ) );
853 aPresentation.properties.push_back( std::move( property ) );
854 }
855 }
856
857
858 CONNECTIVITY_GLOBALS connectivityGlobals( const std::vector<uint8_t>& aBytes, const PADS_IO::BINARY_CURSOR& aCursor,
859 const PADS_SCH_SDB& aSdb, const wxString& aSourceName,
860 PADS_SCH_MODEL& aModel )
861 {
862 CONNECTIVITY_GLOBALS result;
863 const SCH_SDB_POOL& membershipPool = aSdb.Pools()[4];
864 const SCH_SDB_POOL& netPool = aSdb.Pools()[8];
865 const SCH_SDB_POOL& fontPool = aSdb.Pools()[19];
866
867 if( membershipPool.usedBytes != membershipPool.count * NET_MEMBERSHIP_BYTES )
868 {
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" ) );
872 }
873
874 if( netPool.usedBytes != netPool.count * NET_RECORD_BYTES )
875 {
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" ) );
879 }
880
881 if( fontPool.usedBytes != fontPool.count * FONT_RECORD_BYTES )
882 {
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" ) );
886 }
887
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 );
893
894 for( size_t record = 0; record < membershipPool.count; ++record )
895 result.membershipSheets.push_back( aCursor.U16At( result.membershipBase + record * 2 ) );
896
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 );
901
902 result.nets.reserve( netPool.count );
903
904 for( size_t record = 0; record < netPool.count; ++record )
905 {
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;
910 net.source = source;
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;
919 SOURCE_PROVENANCE nameSource = source;
920 nameSource.absoluteOffset += 24;
921 nameSource.length = 56;
922 net.name = decodeFixedString( aBytes, offset + 24, 56, nameSource, aModel.diagnostics );
923
924 if( aCursor.U16At( offset + 20 ) != 0 || aCursor.U32At( offset + 80 ) != 0xFFFFFFFF )
925 {
926 throwDecodeError( source, wxS( "global net record has nonzero padding or a live reserved link" ) );
927 }
928
929 if( !net.tombstone
930 && ( net.membershipStart > membershipPool.count
931 || net.membershipCount > membershipPool.count - net.membershipStart ) )
932 {
933 throwDecodeError( source, wxS( "net sheet-membership slice leaves outer controller 4" ) );
934 }
935
936 for( uint32_t membership = net.membershipStart;
937 !net.tombstone && membership < net.membershipStart + net.membershipCount; ++membership )
938 {
939 if( claimedMemberships[membership] )
940 throwDecodeError( source, wxS( "duplicate net sheet-membership ownership" ) );
941
942 if( result.membershipSheets[membership] >= aModel.sheets.size() )
943 throwDecodeError( source, wxS( "net membership references the wrong sheet object class" ) );
944
945 claimedMemberships[membership] = true;
946 }
947
948 if( net.aliasCount == 0 )
949 {
950 if( net.aliasStringOffset != 0xFFFFFFFF )
951 throwDecodeError( source, wxS( "non-alias net has an alias-string handle" ) );
952 }
953 else
954 {
955 if( net.aliasStringOffset >= aliasHeapBytes )
956 throwDecodeError( source, wxS( "bus-alias string handle leaves outer controller 1" ) );
957
958 size_t stringOffset = net.aliasStringOffset;
959
960 for( size_t alias = 0; alias < net.aliasCount; ++alias )
961 {
962 SOURCE_PROVENANCE aliasSource =
963 sourceAt( aSourceName, aModel.version, wxS( "bus alias member" ), 1, alias,
964 aliasHeapBase + stringOffset, aliasHeapBytes - stringOffset, -1 );
965 SOURCE_STRING member =
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 ) );
972 }
973 }
974
975 result.nets.push_back( std::move( net ) );
976 }
977
978 return result;
979 }
980
981
982 SHEET_CONTROLLERS sheetControllers( const PADS_IO::BINARY_CURSOR& aCursor, const SCH_SDB_BLOCK& aBlock )
983 {
984 SHEET_CONTROLLERS result;
985 size_t descriptor = aBlock.offset + SHEET_HEADER_BYTES;
986 size_t payload = descriptor + 24 * SHEET_DESCRIPTOR_BYTES;
987
988 for( size_t i = 0; i < result.pools.size(); ++i )
989 {
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;
994 }
995
996 return result;
997 }
998
999
1000 void requireFixedController( const SHEET_CONTROLLERS& aControllers, size_t aController, size_t aStride,
1001 const wxString& aFile, uint16_t aVersion, int aSheet )
1002 {
1003 const SCH_SDB_POOL& pool = aControllers.pools[aController - 1];
1004
1005 if( pool.usedBytes != pool.count * aStride )
1006 {
1007 SOURCE_PROVENANCE source =
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 ) ) );
1012 }
1013 }
1014
1015
1016 enum class TEXT_ROLE
1017 {
1018 DEFINITION_FIELD,
1019 EMBEDDED_SYMBOL_TEXT,
1020 PAGE_TEXT
1021 };
1022
1023
1024 struct DEFINITION_TEXT_HEAP
1025 {
1026 size_t recordBase = 0;
1027 uint32_t recordCount = 0;
1028 size_t stringBase = 0;
1029 uint32_t stringBytes = 0;
1030 };
1031
1032
1033 MODEL_TEXT_PRESENTATION decodeTextPresentation( const PADS_IO::BINARY_CURSOR& aCursor, size_t aOffset,
1034 const SOURCE_PROVENANCE& aSource, TEXT_ROLE aRole,
1035 int16_t& aRelationship )
1036 {
1037 MODEL_TEXT_PRESENTATION presentation;
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 ) );
1045
1046 SOURCE_PROVENANCE fontSource = aSource;
1047 fontSource.absoluteOffset += 28;
1048 fontSource.length = 2;
1049 aRelationship = static_cast<int16_t>( aCursor.U16At( aOffset + 28 ) );
1050
1051 // Page text spends word 28 on the successor ordinal, so it always falls back to the default font.
1052 const bool pageText = aRole == TEXT_ROLE::PAGE_TEXT;
1053
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 ),
1057 fontSource ) );
1058 return presentation;
1059 }
1060
1061
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,
1064 size_t aRecord, TEXT_ROLE aRole, MODEL_SYMBOL_DEFINITION& aDefinition,
1065 PADS_SCH_MODEL& aModel )
1066 {
1067 if( aRecord >= aHeap.recordCount )
1068 throwDecodeError( aDefinition.source, wxS( "definition field handle leaves controller 1" ) );
1069
1070 const bool isField = aRole == TEXT_ROLE::DEFINITION_FIELD;
1071 const size_t offset = aHeap.recordBase + aRecord * TEXT_RECORD_BYTES;
1072 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version,
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 );
1077
1078 if( stringBytes == 0 || stringOffset > aHeap.stringBytes || stringBytes > aHeap.stringBytes - stringOffset
1079 || aBytes[aHeap.stringBase + stringOffset + stringBytes - 1] != 0 )
1080 {
1081 throwDecodeError( source, wxS( "definition field string leaves controller 2" ) );
1082 }
1083
1084 SOURCE_PROVENANCE stringSource =
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 );
1091
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 };
1096 const int angle = NormalizeAngle( aCursor.U16At( offset + 16 ) );
1097
1098 if( isField )
1099 {
1100 MODEL_FIELD field;
1101 field.source = 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 ) );
1107 return;
1108 }
1109
1110 MODEL_GRAPHIC graphic;
1111 graphic.source = source;
1112 graphic.kind = MODEL_GRAPHIC_KIND::TEXT;
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 ) );
1118 }
1119
1120
1121 struct DEFINITION_LAYOUT
1122 {
1123 size_t symbolBase = 0;
1124 size_t pieceBase = 0;
1125 size_t vertexBase = 0;
1126 size_t arcBase = 0;
1127 size_t usedDecalBase = 0;
1128 size_t terminalBase = 0;
1129 size_t partBase = 0;
1130 size_t gateBase = 0;
1131 size_t pinBase = 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;
1139
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;
1144 };
1145
1146
1147 struct USED_DECAL
1148 {
1149 size_t record = 0;
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;
1155 };
1156
1157
1158 void preserveRawDefinitionControllers( const std::vector<uint8_t>& aBytes, const SHEET_CONTROLLERS& aControllers,
1159 size_t aSheetIndex, const wxString& aSourceName, PADS_SCH_MODEL& aModel )
1160 {
1161 for( size_t controller = 3; controller <= 23; ++controller )
1162 {
1163 const SCH_SDB_POOL& pool = aControllers.pools[controller - 1];
1164
1165 if( pool.usedBytes == 0 )
1166 continue;
1167
1168 SOURCE_PROVENANCE source =
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(
1172 { source,
1174 { aBytes.begin() + source.absoluteOffset,
1175 aBytes.begin() + source.absoluteOffset + source.length } } );
1176 }
1177 }
1178
1179
1180 DEFINITION_LAYOUT definitionLayout( const PADS_IO::BINARY_CURSOR& aCursor, const SHEET_CONTROLLERS& aControllers,
1181 size_t aSheetIndex, const wxString& aSourceName, uint16_t aVersion )
1182 {
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 );
1193
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 };
1213
1214 // Prefix sums over file-supplied per-record counts. A 32-bit accumulator wraps on a
1215 // crafted file and the exact-total checks below then compare small against their pools
1216 // while the stored offsets are already nonsense, so accumulate in 64 bits
1217 uint64_t vertexCursor = 0;
1218 uint64_t pieceCursor = 0;
1219
1220 for( size_t definition = 0; definition < aControllers.pools[2].count; ++definition )
1221 {
1222 layout.definitionPieceStart.push_back( static_cast<uint32_t>( pieceCursor ) );
1223 pieceCursor += aCursor.U16At( layout.symbolBase + definition * SYMBOL_RECORD_BYTES + 0x2A );
1224 }
1225
1226 if( pieceCursor != aControllers.pools[3].count )
1227 {
1228 SOURCE_PROVENANCE source =
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" ) );
1232 }
1233
1234 for( size_t piece = 0; piece < aControllers.pools[3].count; ++piece )
1235 {
1236 layout.pieceVertexStart.push_back( static_cast<uint32_t>( vertexCursor ) );
1237 vertexCursor += aCursor.U16At( layout.pieceBase + piece * SYMBOL_PIECE_BYTES + 2 );
1238 }
1239
1240 if( vertexCursor != aControllers.pools[4].count )
1241 {
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" ) );
1245 }
1246
1247 for( size_t definition = 0; definition < aControllers.pools[2].count; ++definition )
1248 {
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 )
1263 == vertexEnd;
1264
1265 if( !emptyMatches && !ownedMatches )
1266 {
1267 SOURCE_PROVENANCE source =
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" ) );
1271 }
1272 }
1273
1274 layout.pieceArcRecords.resize( aControllers.pools[3].count );
1275 size_t discoveredArcCount = 0;
1276
1277 for( size_t piece = 0; piece < aControllers.pools[3].count; ++piece )
1278 {
1279 const size_t pieceOffset = layout.pieceBase + piece * SYMBOL_PIECE_BYTES;
1280 const uint16_t pointCount = aCursor.U16At( pieceOffset + 2 );
1281
1282 for( size_t point = 0; point < pointCount; ++point )
1283 {
1284 const size_t vertexOffset =
1285 layout.vertexBase + ( layout.pieceVertexStart[piece] + point ) * SYMBOL_VERTEX_BYTES;
1286
1287 if( static_cast<int16_t>( aCursor.U16At( vertexOffset + 4 ) ) >= 0 )
1288 layout.pieceArcRecords[piece].push_back( discoveredArcCount++ );
1289 }
1290 }
1291
1292 if( discoveredArcCount != aControllers.pools[5].count )
1293 {
1294 SOURCE_PROVENANCE source =
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" ) );
1298 }
1299
1300 return layout;
1301 }
1302
1303
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 )
1309 {
1310 // Definitions must not reallocate; aDefinitionsByRecord holds pointers into them
1311 aModel.definitions.reserve( aModel.definitions.size() + aControllers.pools[2].count );
1312
1313 for( size_t record = 0; record < aControllers.pools[2].count; ++record )
1314 {
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 ) );
1318 SOURCE_PROVENANCE nameSource = source;
1319 nameSource.length = 38;
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;
1326
1327 if( vertexStart > vertexEnd || vertexEnd > aControllers.pools[4].count )
1328 throwDecodeError( source, wxS( "symbol definition vertex slice leaves controller 5" ) );
1329
1330 if( objectClass == 0 )
1331 {
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 );
1342 SOURCE_PROVENANCE originSource = source;
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 };
1348
1349 if( aCursor.U16At( offset + 42 ) != pieceEnd - firstPiece )
1350 throwDecodeError( source, wxS( "page-graphic piece count does not match controller 4 slice" ) );
1351
1352 for( size_t piece = firstPiece; piece < pieceEnd; ++piece )
1353 {
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];
1359 SOURCE_PROVENANCE graphicSource =
1360 sourceAt( aSourceName, aModel.version, wxS( "page graphic" ), 4, piece, pieceOffset,
1361 SYMBOL_PIECE_BYTES, static_cast<int>( aSheetIndex ) );
1362
1363 if( firstVertex + pointCount > vertexEnd )
1364 throwDecodeError( graphicSource, wxS( "page graphic crosses its controller-5 slice" ) );
1365
1366 MODEL_GRAPHIC graphic;
1367 graphic.source = graphicSource;
1368 decodeGraphicStrokeWidth( aCursor.U8At( pieceOffset + 4 ), aCursor.U8At( pieceOffset + 5 ),
1369 graphicSource, graphic, aModel.diagnostics );
1370
1371 switch( lineStyle )
1372 {
1373 case 0xFF: graphic.lineStyle = MODEL_LINE_STYLE::SOLID; break;
1374 case 0: graphic.lineStyle = MODEL_LINE_STYLE::DASH; break;
1375 case 1: graphic.lineStyle = MODEL_LINE_STYLE::DOT; break;
1376 default:
1377 PADS_SCH_BINARY_PARSER::RecordUnknownEnum( wxS( "page graphic line style" ), lineStyle,
1378 graphicSource, aModel.diagnostics );
1379 graphic.lineStyle = MODEL_LINE_STYLE::DEFAULT;
1380 break;
1381 }
1382
1383 switch( pieceKind )
1384 {
1385 case 0:
1386 graphic.kind = pointCount == 2 ? MODEL_GRAPHIC_KIND::LINE : MODEL_GRAPHIC_KIND::POLYLINE;
1387 break;
1388 case 1: graphic.kind = MODEL_GRAPHIC_KIND::POLYLINE; break;
1389 case 2: graphic.kind = MODEL_GRAPHIC_KIND::CIRCLE; break;
1390 case 4:
1391 graphic.kind = MODEL_GRAPHIC_KIND::POLYLINE;
1392 graphic.fill = MODEL_FILL_STYLE::FILLED;
1393 break;
1394 default:
1395 PADS_SCH_BINARY_PARSER::RecordUnknownEnum( wxS( "page graphic kind" ), pieceKind, graphicSource,
1396 aModel.diagnostics );
1397 graphic.kind = MODEL_GRAPHIC_KIND::POLYLINE;
1398 break;
1399 }
1400
1401 for( size_t point = 0; point < pointCount; ++point )
1402 {
1403 const size_t pointOffset = aLayout.vertexBase + ( firstVertex + point ) * SYMBOL_VERTEX_BYTES;
1404 SOURCE_PROVENANCE pointSource = sourceAt(
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 ) ),
1409 pointSource } );
1410 }
1411
1412 const int16_t arcMarker = static_cast<int16_t>(
1413 aCursor.U16At( aLayout.vertexBase + firstVertex * SYMBOL_VERTEX_BYTES + 4 ) );
1414
1415 if( pieceKind == 0 && arcMarker >= 0 )
1416 {
1417 if( aLayout.pieceArcRecords[piece].empty() )
1418 throwDecodeError( graphicSource, wxS( "page arc has no controller-6 record" ) );
1419
1420 const size_t arcRecord = aLayout.pieceArcRecords[piece].front();
1421 const size_t arcOffset = aLayout.arcBase + arcRecord * SYMBOL_ARC_BYTES;
1422 SOURCE_PROVENANCE arcSource =
1423 sourceAt( aSourceName, aModel.version, wxS( "page arc" ), 6, arcRecord, arcOffset,
1424 SYMBOL_ARC_BYTES, static_cast<int>( aSheetIndex ) );
1425 graphic.kind = MODEL_GRAPHIC_KIND::ARC;
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 };
1434 }
1435
1436 for( SOURCE_POINT& point : graphic.points )
1437 {
1438 point.x += groupOrigin.x;
1439 point.y += groupOrigin.y;
1440 }
1441
1442 if( graphic.kind == MODEL_GRAPHIC_KIND::ARC )
1443 {
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;
1450 }
1451
1452 graphic.properties.push_back( sourceProperty( wxS( "page_graphic_group" ), name.text, source ) );
1453
1454 if( worksheetCandidate )
1455 graphic.properties.push_back( sourceProperty( wxS( "worksheet_group" ), name.text, source ) );
1456
1457 if( !hasCircularTextOwnership )
1458 {
1459 SOURCE_PROVENANCE relationshipSource = source;
1460 relationshipSource.absoluteOffset += 64;
1461 relationshipSource.length = 4;
1462 SOURCE_PROPERTY relationship = sourceProperty(
1463 wxS( "preserved_drawing_text_relationship" ),
1464 wxString::Format( wxS( "%u,%u" ), groupTextCount, groupLastText ), relationshipSource );
1465 relationship.disposition = PROPERTY_DISPOSITION::UNSUPPORTED;
1466 aModel.diagnostics.push_back( MakePropertyDiagnostic(
1467 RPT_SEVERITY_WARNING, relationship,
1468 wxS( "unsupported class-zero drawing text relationship preserved" ) ) );
1469 graphic.properties.push_back( std::move( relationship ) );
1470 }
1471 aModel.graphics.push_back(
1472 { graphicSource, { aModel.sheets[aSheetIndex].id, graphicSource }, std::move( graphic ) } );
1473 }
1474
1475 if( hasCircularTextOwnership )
1476 aPageGraphicRecords.push_back( record );
1477 continue;
1478 }
1479
1480 if( objectClass != 0x06 )
1481 continue;
1482
1483 MODEL_SYMBOL_DEFINITION definition;
1484 definition.id = DEFINITION_ID( aLayout.definitionIdBase + record );
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;
1491
1492 for( size_t piece = firstPiece; piece < pieceEnd; ++piece )
1493 {
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];
1498
1499 if( firstVertex + pointCount > vertexEnd )
1500 throwDecodeError( source, wxS( "symbol piece crosses its definition vertex slice" ) );
1501
1502 SOURCE_PROVENANCE graphicSource =
1503 sourceAt( aSourceName, aModel.version, wxS( "symbol graphic" ), 4, piece, pieceOffset,
1504 SYMBOL_PIECE_BYTES, static_cast<int>( aSheetIndex ) );
1505 MODEL_GRAPHIC graphic;
1506 graphic.source = graphicSource;
1507 decodeGraphicStrokeWidth( aCursor.U8At( pieceOffset + 4 ), aCursor.U8At( pieceOffset + 5 ),
1508 graphicSource, graphic, aModel.diagnostics );
1509 graphic.lineStyle = MODEL_LINE_STYLE::SOLID;
1510
1511 switch( pieceKind )
1512 {
1513 case 0: graphic.kind = pointCount == 2 ? MODEL_GRAPHIC_KIND::LINE : MODEL_GRAPHIC_KIND::POLYLINE; break;
1514 case 1: graphic.kind = MODEL_GRAPHIC_KIND::POLYLINE; break;
1515 case 2: graphic.kind = MODEL_GRAPHIC_KIND::CIRCLE; break;
1516 case 4:
1517 graphic.kind = MODEL_GRAPHIC_KIND::POLYLINE;
1518 graphic.fill = MODEL_FILL_STYLE::FILLED;
1519 break;
1520 default:
1521 PADS_SCH_BINARY_PARSER::RecordUnknownEnum( wxS( "symbol graphic kind" ), pieceKind, graphicSource,
1522 aModel.diagnostics );
1523 graphic.kind = MODEL_GRAPHIC_KIND::POLYLINE;
1524 break;
1525 }
1526
1527 for( size_t point = 0; point < pointCount; ++point )
1528 {
1529 const size_t pointOffset = aLayout.vertexBase + ( firstVertex + point ) * SYMBOL_VERTEX_BYTES;
1530 SOURCE_PROVENANCE pointSource =
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 ) ),
1535 pointSource } );
1536 }
1537
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 )
1540 {
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,
1545 [&]( const SOURCE_POINT& aPoint )
1546 {
1547 return ( aPoint.x == minX.x || aPoint.x == maxX.x )
1548 && ( aPoint.y == minY.y || aPoint.y == maxY.y );
1549 } );
1550
1551 if( cornersOnly )
1552 {
1553 graphic.kind = MODEL_GRAPHIC_KIND::RECTANGLE;
1554 graphic.points = { { minX.x, minY.y, graphicSource }, { maxX.x, maxY.y, graphicSource } };
1555 }
1556 }
1557
1558 const int16_t arcMarker = static_cast<int16_t>(
1559 aCursor.U16At( aLayout.vertexBase + firstVertex * SYMBOL_VERTEX_BYTES + 4 ) );
1560
1561 if( pieceKind == 0 && arcMarker >= 0 )
1562 {
1563 if( aLayout.pieceArcRecords[piece].empty() )
1564 throwDecodeError( graphicSource, wxS( "symbol arc has no controller-6 record" ) );
1565
1566 const size_t arcRecord = aLayout.pieceArcRecords[piece].front();
1567 const size_t arcOffset = aLayout.arcBase + arcRecord * SYMBOL_ARC_BYTES;
1568 graphic.kind = MODEL_GRAPHIC_KIND::ARC;
1569 SOURCE_PROVENANCE arcSource =
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 ) ) ),
1586 arcSource ) );
1587 }
1588
1589 definition.graphics.push_back( std::move( graphic ) );
1590 }
1591
1592 aModel.definitions.push_back( std::move( definition ) );
1593 aDefinitionsByRecord[record] = &aModel.definitions.back();
1594 }
1595 }
1596
1597
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,
1603 PADS_SCH_MODEL& aModel )
1604 {
1605 for( size_t record = 0; record < aUsedDecals.size(); ++record )
1606 {
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 ) );
1610 SOURCE_PROVENANCE nameSource = source;
1611 nameSource.length = 40;
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 );
1619
1620 if( name.text.empty() || decal.definitionRecord == 0xFFFFFFFF )
1621 continue;
1622
1623 if( decal.definitionRecord >= aDefinitionsByRecord.size() )
1624 throwDecodeError( source, wxS( "unresolved symbol definition reference" ) );
1625
1626 const size_t definitionOffset = aLayout.symbolBase + decal.definitionRecord * SYMBOL_RECORD_BYTES;
1627 SOURCE_PROVENANCE targetNameSource =
1628 sourceAt( aSourceName, aModel.version, wxS( "symbol definition" ), 3, decal.definitionRecord,
1629 definitionOffset, 38, static_cast<int>( aSheetIndex ) );
1630 SOURCE_STRING targetName =
1631 decodeFixedString( aBytes, definitionOffset, 38, targetNameSource, aModel.diagnostics );
1632
1633 if( targetName.text != name.text )
1634 {
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" ) );
1638 }
1639
1640 decal.definition = aDefinitionsByRecord[decal.definitionRecord];
1641
1642 if( !decal.definition )
1643 continue;
1644
1645 const uint16_t embeddedTextCount =
1646 aCursor.U16At( aLayout.symbolBase + decal.definitionRecord * SYMBOL_RECORD_BYTES + 0x40 );
1647
1648 if( embeddedTextCount != 0 && decal.fieldStart > aLayout.textHeap.recordCount
1649 && decal.fieldStart < 0x80000000 )
1650 throwDecodeError( source, wxS( "embedded definition text handle leaves controller 1" ) );
1651
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" ) );
1654
1655 aSemanticDecals.push_back( &decal );
1656
1657 for( size_t pin = 0; pin < decal.terminalCount; ++pin )
1658 {
1659 const size_t terminalRecord = decal.terminalStart + pin;
1660 const size_t terminalOffset = aLayout.terminalBase + terminalRecord * TERMINAL_BYTES;
1661 SOURCE_PROVENANCE pinSource =
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 );
1665 MODEL_PIN_DEFINITION definitionPin;
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 ) ),
1689 pinSource };
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;
1696
1697 definitionPin.angle = ( presentationFlags & 0x0001 ) != 0 ? 900 : 0;
1698 definitionPin.nameAngle = ( presentationFlags & 0x0100 ) != 0 ? 900 : 0;
1699 definitionPin.numberAngle = ( visibilityFlags & 0x0001 ) != 0 ? 900 : 0;
1700
1701 definitionPin.nameJustification =
1702 terminalJustification( ( presentationFlags >> 12 ) & 0x0F, definitionPin.nameAngle != 0 );
1703
1704 if( ( presentationFlags & 0x00F8 ) != 0 )
1705 PADS_SCH_BINARY_PARSER::RecordUnknownEnum( wxS( "terminal side" ), presentationFlags, pinSource,
1706 aModel.diagnostics );
1707
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;
1713
1714 switch( visibilityFlags & 0x0F00 )
1715 {
1716 case 0x0000:
1717 case 0x0800: definitionPin.nameOffsetJustification = 0; break;
1718 case 0x0400:
1719 case 0x0C00: definitionPin.nameOffsetJustification = 1; break;
1720 case 0x0500:
1721 case 0x0D00:
1722 case 0x0F00: definitionPin.nameOffsetJustification = 2; break;
1723 default:
1724 PADS_SCH_BINARY_PARSER::RecordUnknownEnum( wxS( "terminal name-offset presentation" ),
1725 visibilityFlags & 0x0F00, pinSource,
1726 aModel.diagnostics );
1727 break;
1728 }
1729
1730 switch( visibilityFlags & 0x0F00 )
1731 {
1732 case 0x0000: definitionPin.numberOffsetJustification = 0; break;
1733 case 0x0800:
1734 case 0x0C00: definitionPin.numberOffsetJustification = 1; break;
1735 case 0x0D00: definitionPin.numberOffsetJustification = 8; break;
1736 case 0x0F00: definitionPin.numberOffsetJustification = 2; break;
1737 default:
1738 PADS_SCH_BINARY_PARSER::RecordUnknownEnum( wxS( "terminal number-offset presentation" ),
1739 visibilityFlags & 0x0F00, pinSource,
1740 aModel.diagnostics );
1741 break;
1742 }
1743
1744 definitionPin.visibilityFlags = ( visibilityFlags >> 8 ) & 0x00C0;
1745
1746 if( pinDecalHandle == 0xFFFF )
1747 {
1748 definitionPin.length = 0;
1749 }
1750 else if( pinDecalHandle >= aUsedDecals.size() )
1751 throwDecodeError( pinSource, wxS( "unresolved pin-decal handle" ) );
1752
1753 if( pinDecalHandle != 0xFFFF )
1754 {
1755 MODEL_SYMBOL_DEFINITION* pinDecal = aUsedDecals[pinDecalHandle].definition;
1756
1757 if( !pinDecal )
1758 {
1759 const size_t handleOffset = aLayout.usedDecalBase + pinDecalHandle * USED_DECAL_BYTES;
1760 const uint32_t definitionRecord = aCursor.U32At( handleOffset + 48 );
1761
1762 if( definitionRecord >= aDefinitionsByRecord.size() || !aDefinitionsByRecord[definitionRecord] )
1763 throwDecodeError( pinSource, wxS( "unresolved pin-decal handle" ) );
1764
1765 pinDecal = aDefinitionsByRecord[definitionRecord];
1766 }
1767
1768 const wxString pinDecalName = pinDecal->name.text;
1769 definitionPin.decalName = pinDecal->name;
1770
1771 int64_t provenLength = 0;
1772
1773 for( const MODEL_GRAPHIC& pinGraphic : pinDecal->graphics )
1774 {
1775 const bool containsOrigin = std::ranges::any_of( pinGraphic.points,
1776 []( const SOURCE_POINT& aPoint )
1777 {
1778 return aPoint.x == 0 && aPoint.y == 0;
1779 } );
1780
1781 if( !containsOrigin )
1782 continue;
1783
1784 for( const SOURCE_POINT& point : pinGraphic.points )
1785 provenLength =
1786 std::max( provenLength, std::max( std::abs( point.x ), std::abs( point.y ) ) );
1787 }
1788
1789 if( provenLength != 0 )
1790 definitionPin.length = provenLength;
1791 else
1792 {
1793 SOURCE_PROPERTY lengthProperty =
1794 sourceProperty( wxS( "pin_length" ), wxS( "unknown" ), pinSource );
1795 lengthProperty.disposition = PROPERTY_DISPOSITION::UNSUPPORTED;
1796 definitionPin.properties.push_back( std::move( lengthProperty ) );
1797 }
1798
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 );
1803 }
1804
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 ) );
1820 }
1821 }
1822 }
1823
1824
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 )
1828 {
1829 std::vector<USED_DECAL*> fieldDecals;
1830
1831 std::ranges::copy_if( aSemanticDecals, std::back_inserter( fieldDecals ),
1832 [&]( const USED_DECAL* aDecal )
1833 {
1834 return aDecal->fieldStart < aLayout.textHeap.recordCount;
1835 } );
1836 std::ranges::sort( fieldDecals,
1837 []( const USED_DECAL* aLeft, const USED_DECAL* aRight )
1838 {
1839 return aLeft->fieldStart < aRight->fieldStart;
1840 } );
1841
1842 for( size_t i = 1; i < fieldDecals.size(); ++i )
1843 {
1844 if( fieldDecals[i - 1]->fieldStart != fieldDecals[i]->fieldStart )
1845 continue;
1846
1847 SOURCE_PROVENANCE first =
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 ) );
1855 throwDecodeError(
1856 duplicate,
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 ) ) );
1862 }
1863
1864 return fieldDecals;
1865 }
1866
1867
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 )
1871 {
1872 for( size_t record : aPageGraphicRecords )
1873 {
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 ) );
1877 SOURCE_PROVENANCE nameSource = source;
1878 nameSource.length = 38;
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;
1883
1884 if( textCountForGroup > aLayout.textHeap.recordCount
1885 || ( textCountForGroup != 0 && lastTextRecord >= aLayout.textHeap.recordCount ) )
1886 throwDecodeError( source, wxS( "page-text ownership leaves controller 1" ) );
1887
1888 MODEL_SYMBOL_DEFINITION textOwner;
1889 textOwner.source = source;
1890
1891 if( textCountForGroup != 0 )
1892 {
1893 std::vector<size_t> textRecords( textCountForGroup );
1894 std::vector<bool> visitedTextRecords( aLayout.textHeap.recordCount, false );
1895 size_t textRecord = lastTextRecord;
1896
1897 for( size_t reverseIndex = textCountForGroup; reverseIndex != 0; --reverseIndex )
1898 {
1899 if( textRecord >= aLayout.textHeap.recordCount )
1900 throwDecodeError( source, wxS( "page-text predecessor leaves controller 1" ) );
1901
1902 if( visitedTextRecords[textRecord] )
1903 throwDecodeError( source, wxS( "page-text predecessor repeats controller 1 record" ) );
1904
1905 visitedTextRecords[textRecord] = true;
1906
1907 textRecords[reverseIndex - 1] = textRecord;
1908
1909 if( reverseIndex != 1 )
1910 {
1911 const size_t predecessor =
1912 aCursor.U16At( aLayout.textHeap.recordBase + textRecord * TEXT_RECORD_BYTES + 24 );
1913
1914 if( predecessor >= aLayout.textHeap.recordCount )
1915 throwDecodeError( source, wxS( "page-text predecessor leaves controller 1" ) );
1916
1917 textRecord = predecessor;
1918 }
1919 }
1920
1921 for( size_t textRecordIndex : textRecords )
1922 decodeDefinitionTextRecord( aBytes, aCursor, aSourceName, aSheetIndex, aLayout.textHeap,
1923 textRecordIndex, TEXT_ROLE::PAGE_TEXT, textOwner, aModel );
1924 }
1925
1926 if( textOwner.graphics.size() != textCountForGroup )
1927 throwDecodeError( source, wxS( "page-text records do not exactly match declared count" ) );
1928
1929 for( MODEL_GRAPHIC& graphic : textOwner.graphics )
1930 {
1931 graphic.source.objectClass = wxS( "page text" );
1932 const SOURCE_POINT groupOrigin{ decodeCoordinate( aCursor.U16At( offset + 60 ) ),
1933 decodeCoordinate( aCursor.U16At( offset + 62 ) ), source };
1934
1935 for( SOURCE_POINT& point : graphic.points )
1936 {
1937 point.x += groupOrigin.x;
1938 point.y += groupOrigin.y;
1939 }
1940
1941 graphic.properties.push_back( sourceProperty( wxS( "page_graphic_group" ), groupName.text, source ) );
1942
1943 if( worksheetCandidate )
1944 graphic.properties.push_back( sourceProperty( wxS( "worksheet_group" ), groupName.text, source ) );
1945
1946 aModel.graphics.push_back(
1947 { graphic.source, { aModel.sheets[aSheetIndex].id, graphic.source }, std::move( graphic ) } );
1948 }
1949
1950 auto belongsToGroup = [&]( const MODEL_PAGE_GRAPHIC& aGraphic )
1951 {
1952 if( aGraphic.sheet.id != aModel.sheets[aSheetIndex].id )
1953 return false;
1954
1955 return std::ranges::any_of( aGraphic.graphic.properties,
1956 [&]( const SOURCE_PROPERTY& aProperty )
1957 {
1958 return aProperty.name.text == wxS( "page_graphic_group" )
1959 && aProperty.value.text == groupName.text;
1960 } );
1961 };
1962
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;
1970
1971 for( const MODEL_PAGE_GRAPHIC& pageGraphic : aModel.graphics )
1972 {
1973 if( !belongsToGroup( pageGraphic ) )
1974 continue;
1975
1976 if( pageGraphic.graphic.kind != MODEL_GRAPHIC_KIND::TEXT )
1977 {
1978 ++drawingGraphics;
1979 continue;
1980 }
1981
1982 ++textGraphics;
1983 wxString text = pageGraphic.graphic.text.text.Upper();
1984
1985 if( text.length() == 1 && text[0] >= '0' && text[0] <= '9' )
1986 ++numericEdgeMarkers;
1987
1988 if( text.length() == 1 && text[0] >= 'A' && text[0] <= 'Z' )
1989 ++alphabeticEdgeMarkers;
1990
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 " ) );
1996 }
1997
1998 const bool worksheet = drawingGraphics >= 30 && textGraphics >= 30 && numericEdgeMarkers >= 4
1999 && alphabeticEdgeMarkers >= 4 && hasTitleAnchor && hasSheetAnchor
2000 && hasRevisionAnchor;
2001
2002 if( worksheet )
2003 {
2004 MODEL_WORKSHEET modelWorksheet;
2005 modelWorksheet.source = source;
2006 modelWorksheet.sheet = { aModel.sheets[aSheetIndex].id, source };
2007 modelWorksheet.name = groupName;
2008
2009 for( auto graphic = aModel.graphics.begin(); graphic != aModel.graphics.end(); )
2010 {
2011 if( belongsToGroup( *graphic ) )
2012 {
2013 modelWorksheet.graphics.push_back( std::move( graphic->graphic ) );
2014 graphic = aModel.graphics.erase( graphic );
2015 }
2016 else
2017 {
2018 ++graphic;
2019 }
2020 }
2021
2022 auto existing = std::ranges::find_if( aModel.worksheets,
2023 [&]( const MODEL_WORKSHEET& aWorksheet )
2024 {
2025 return aWorksheet.sheet.id == modelWorksheet.sheet.id;
2026 } );
2027
2028 if( existing == aModel.worksheets.end() )
2029 {
2030 aModel.worksheets.push_back( std::move( modelWorksheet ) );
2031 }
2032 else if( !sameWorksheetValue( *existing, modelWorksheet ) )
2033 {
2034 SOURCE_PROPERTY distinct =
2035 sourceProperty( wxS( "distinct_worksheet_layout" ), groupName.text, source );
2036 distinct.disposition = PROPERTY_DISPOSITION::UNSUPPORTED;
2037 aModel.diagnostics.push_back( MakePropertyDiagnostic(
2038 RPT_SEVERITY_WARNING, distinct,
2039 wxS( "distinct worksheet layout preserved as schematic page graphics" ) ) );
2040
2041 for( MODEL_GRAPHIC& graphic : modelWorksheet.graphics )
2042 {
2043 graphic.properties.push_back( distinct );
2044 aModel.graphics.push_back( { graphic.source,
2045 { aModel.sheets[aSheetIndex].id, graphic.source },
2046 std::move( graphic ) } );
2047 }
2048 }
2049 }
2050 }
2051
2052 auto canonicalWorksheet = std::ranges::find_if( aModel.worksheets,
2053 []( const MODEL_WORKSHEET& aWorksheet )
2054 {
2055 return aWorksheet.name.text == wxS( "DRW5982" );
2056 } );
2057
2058 if( canonicalWorksheet != aModel.worksheets.end() )
2059 {
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;
2063
2064 for( const MODEL_GRAPHIC& graphic : canonicalWorksheet->graphics )
2065 {
2066 if( graphic.kind != MODEL_GRAPHIC_KIND::TEXT )
2067 canonicalGeometry.push_back( &graphic );
2068 }
2069
2070 for( const MODEL_PAGE_GRAPHIC& pageGraphic : aModel.graphics )
2071 {
2072 auto groupProperty = std::ranges::find_if( pageGraphic.graphic.properties,
2073 []( const SOURCE_PROPERTY& aProperty )
2074 {
2075 return aProperty.name.text == wxS( "worksheet_group" );
2076 } );
2077
2078 if( groupProperty != pageGraphic.graphic.properties.end()
2079 && pageGraphic.graphic.kind != MODEL_GRAPHIC_KIND::TEXT )
2080 {
2081 candidates[{ pageGraphic.sheet.id.Value(), groupProperty->value.text }].push_back(
2082 &pageGraphic.graphic );
2083 }
2084 }
2085
2086 std::set<GROUP_KEY> equivalentGroups;
2087
2088 for( const auto& [key, geometry] : candidates )
2089 {
2090 if( geometry.size() == canonicalGeometry.size()
2091 && std::ranges::equal( geometry, canonicalGeometry,
2092 [&]( const MODEL_GRAPHIC* aLeft, const MODEL_GRAPHIC* aRight )
2093 {
2094 return sameGraphicValue( *aLeft, *aRight );
2095 } ) )
2096 {
2097 equivalentGroups.insert( key );
2098 }
2099 }
2100
2101 std::erase_if( aModel.graphics,
2102 [&]( const MODEL_PAGE_GRAPHIC& aGraphic )
2103 {
2104 return std::ranges::any_of(
2105 aGraphic.graphic.properties,
2106 [&]( const SOURCE_PROPERTY& aProperty )
2107 {
2108 return aProperty.name.text == wxS( "worksheet_group" )
2109 && equivalentGroups.contains(
2110 { aGraphic.sheet.id.Value(), aProperty.value.text } );
2111 } );
2112 } );
2113 }
2114 }
2115
2116
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 )
2120 {
2121 for( size_t i = 0; i < aFieldDecals.size(); ++i )
2122 {
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;
2128
2129 if( decal.fieldStart > fieldEnd )
2130 throwDecodeError( decal.definition->source, wxS( "definition field slice is not monotone" ) );
2131
2132 if( decal.definition->fields.empty() )
2133 {
2134 for( size_t standard = 0; standard < 2; ++standard )
2135 {
2136 const size_t usedOffset = aLayout.usedDecalBase + decal.record * USED_DECAL_BYTES;
2137 SOURCE_PROVENANCE fieldSource =
2138 sourceAt( aSourceName, aModel.version, wxS( "standard definition field" ), 7, decal.record,
2139 usedOffset + 60 + standard * 8, 8, static_cast<int>( aSheetIndex ) );
2140 MODEL_FIELD field;
2141 field.source = fieldSource;
2142 field.name = { {},
2143 standard == 0 ? wxS( "REF-DES" ) : wxS( "PART-TYPE" ),
2145 fieldSource };
2146 field.position = { decodeLocalCoordinate( aCursor.U16At( usedOffset + 60 + standard * 8 ) ),
2147 decodeLocalCoordinate( aCursor.U16At( usedOffset + 62 + standard * 8 ) ),
2148 fieldSource };
2149 field.angle = NormalizeAngle( aCursor.U16At( usedOffset + 64 + 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 );
2153 SOURCE_PROVENANCE fontSource = fieldSource;
2154 fontSource.absoluteOffset = usedOffset + 100 + standard * 2;
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 ) );
2164 }
2165 }
2166
2167 if( embeddedCount != 0 )
2168 {
2169 const uint16_t lastEmbeddedRecord = aCursor.U16At( definitionOffset + 0x42 );
2170
2171 if( embeddedCount > aLayout.textHeap.recordCount || lastEmbeddedRecord >= aLayout.textHeap.recordCount )
2172 throwDecodeError( decal.definition->source,
2173 wxS( "embedded definition text ownership leaves controller 1" ) );
2174
2175 const size_t firstEmbeddedRecord =
2176 ( static_cast<size_t>( lastEmbeddedRecord ) + aLayout.textHeap.recordCount + 1 - embeddedCount )
2177 % aLayout.textHeap.recordCount;
2178
2179 for( size_t textIndex = 0; textIndex < embeddedCount; ++textIndex )
2180 {
2181 decodeDefinitionTextRecord( aBytes, aCursor, aSourceName, aSheetIndex, aLayout.textHeap,
2182 ( firstEmbeddedRecord + textIndex ) % aLayout.textHeap.recordCount,
2183 TEXT_ROLE::EMBEDDED_SYMBOL_TEXT, *decal.definition, aModel );
2184 }
2185 }
2186
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 );
2190 }
2191 }
2192
2193
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,
2197 PADS_SCH_MODEL& aModel )
2198 {
2199 size_t signalPinCursor = 0;
2200
2201 for( size_t record = 0; record < aControllers.pools[8].count; ++record )
2202 {
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 ) );
2206 SOURCE_PROVENANCE nameSource = source;
2207 nameSource.length = 44;
2208 MODEL_PART_TYPE part;
2209 part.id = PART_TYPE_ID( aLayout.partIdBase + record );
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;
2220
2221 if( gateStart > gateEnd || gateEnd > aControllers.pools[9].count || pinStart > pinEnd
2222 || pinEnd > aControllers.pools[10].count )
2223 {
2224 throwDecodeError( source, wxS( "part-type gate or pin slice leaves its controller" ) );
2225 }
2226
2227 if( aCursor.U16At( offset + 68 ) != gateEnd - gateStart )
2228 throwDecodeError( source, wxS( "stored gate count does not match controller-10 slice" ) );
2229
2230 size_t partPinCursor = pinStart;
2231 uint32_t unitCursor = 0;
2232 const uint32_t pinNameHeapBase = aCursor.U32At( offset + 60 );
2233
2234 if( pinNameHeapBase > aLayout.pinNameBytes )
2235 throwDecodeError( source, wxS( "part-type pin-name base leaves controller 14" ) );
2236
2237 auto decodePartPin = [&]( MODEL_PIN_DEFINITION& aDefinitionPin, size_t aPinRecord, MODEL_GATE& aGate )
2238 {
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 ) );
2242 SOURCE_PROVENANCE numberSource = pinSource;
2243 numberSource.absoluteOffset += 4;
2244 numberSource.length = 16;
2245 aDefinitionPin.number =
2246 decodeFixedString( aBytes, pinOffset + 4, 16, numberSource, aModel.diagnostics );
2247 const uint32_t nameOffset = aCursor.U32At( pinOffset );
2248
2249 if( nameOffset != 0xFFFFFFFF )
2250 {
2251 if( nameOffset >= aLayout.pinNameBytes - pinNameHeapBase )
2252 throwDecodeError( pinSource, wxS( "pin-name offset leaves controller 14" ) );
2253
2254 SOURCE_PROVENANCE pinNameSource = sourceAt(
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 );
2261 }
2262
2263 aDefinitionPin.electricalType =
2264 pinElectricalType( aCursor.U8At( pinOffset + 21 ), pinSource, aModel.diagnostics );
2265 MODEL_GATE_PIN logicalPin;
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 ) );
2278 };
2279
2280 auto decodeConnectorPin = [&]( size_t aPinRecord )
2281 {
2282 const size_t pinOffset = aLayout.pinBase + aPinRecord * PIN_BYTES;
2283 SOURCE_PROVENANCE pinSource =
2284 sourceAt( aSourceName, aModel.version, wxS( "connector logical pin" ), 11, aPinRecord,
2285 pinOffset, PIN_BYTES, static_cast<int>( aSheetIndex ) );
2286 SOURCE_PROVENANCE numberSource = pinSource;
2287 numberSource.absoluteOffset += 4;
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 );
2293
2294 if( nameOffset != 0xFFFFFFFF )
2295 {
2296 if( nameOffset >= aLayout.pinNameBytes - pinNameHeapBase )
2297 throwDecodeError( pinSource, wxS( "pin-name offset leaves controller 14" ) );
2298
2299 SOURCE_PROVENANCE pinNameSource = sourceAt(
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 );
2306 }
2307
2308 pin.swapGroup = aCursor.U8At( pinOffset + 20 );
2309 pin.electricalType = pinElectricalType( aCursor.U8At( pinOffset + 21 ), pinSource, aModel.diagnostics );
2310 pin.flags = aCursor.U16At( pinOffset + 22 );
2311 return pin;
2312 };
2313
2314 for( size_t gateRecord = gateStart; gateRecord < gateEnd; ++gateRecord )
2315 {
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 ) );
2319 MODEL_GATE gate;
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 );
2325
2326 if( pinCount == 0 )
2327 continue;
2328
2329 gate.unit = ++unitCursor;
2330
2331 if( primaryHandle == 0xFFFF || primaryHandle >= aUsedDecals.size()
2332 || !aUsedDecals[primaryHandle].definition )
2333 {
2334 bool pinDecalGroup = primaryHandle == 0xFFFF && gateRecord + 1 < gateEnd;
2335
2336 for( size_t member = gateRecord + 1; pinDecalGroup && member < gateEnd; ++member )
2337 {
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;
2342 }
2343
2344 if( !pinDecalGroup || partPinCursor + pinCount > pinEnd )
2345 throwDecodeError( gateSource, wxS( "unresolved symbol definition reference" ) );
2346
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 ) );
2351
2352 for( size_t member = gateRecord + 1; member < gateEnd; ++member )
2353 {
2354 const size_t memberOffset = aLayout.gateBase + member * GATE_BYTES;
2355 const uint16_t memberHandle = aCursor.U16At( memberOffset );
2356
2357 if( aCursor.U16At( memberOffset + 8 ) != 0 )
2358 break;
2359
2360 SOURCE_PROVENANCE memberSource =
2361 sourceAt( aSourceName, aModel.version, wxS( "pin-decal group member" ), 10, member,
2362 memberOffset, GATE_BYTES, static_cast<int>( aSheetIndex ) );
2363
2364 if( memberHandle >= aUsedDecals.size() || !aUsedDecals[memberHandle].definition )
2365 throwDecodeError( memberSource, wxS( "unresolved pin-decal group member" ) );
2366
2367 gate.decalGroupMembers.push_back( { aUsedDecals[memberHandle].definition->id, memberSource } );
2368 }
2369
2370 for( size_t pin = 0; pin < pinCount; ++pin, ++partPinCursor )
2371 gate.connectorPins.push_back( decodeConnectorPin( partPinCursor ) );
2372
2373 part.gates.push_back( std::move( gate ) );
2374 continue;
2375 }
2376
2377 MODEL_SYMBOL_DEFINITION* definition = aUsedDecals[primaryHandle].definition;
2378 gate.definition = { definition->id, gateSource };
2379 gate.properties.push_back(
2380 sourceProperty( wxS( "swap_group" ), wxString::Format( wxS( "%u" ), swapGroup ), gateSource ) );
2381
2382 for( size_t alternate = 1; alternate < 4; ++alternate )
2383 {
2384 const uint16_t handle = aCursor.U16At( gateOffset + alternate * 2 );
2385
2386 if( handle == 0xFFFF )
2387 continue;
2388
2389 if( handle >= aUsedDecals.size() || !aUsedDecals[handle].definition )
2390 throwDecodeError( gateSource, wxS( "unresolved alternate symbol definition reference" ) );
2391
2392 gate.alternateDefinitions.push_back( { aUsedDecals[handle].definition->id, gateSource } );
2393 }
2394
2395 if( partPinCursor + pinCount > pinEnd || pinCount > definition->pins.size() )
2396 throwDecodeError( gateSource, wxS( "gate pin slice leaves part type or symbol definition" ) );
2397
2398 for( size_t pin = 0; pin < pinCount; ++pin, ++partPinCursor )
2399 {
2400 MODEL_PIN_DEFINITION& definitionPin = definition->pins[pin];
2401 decodePartPin( definitionPin, partPinCursor, gate );
2402 }
2403
2404 part.gates.push_back( std::move( gate ) );
2405 }
2406
2407 if( partPinCursor != pinEnd )
2408 throwDecodeError( source, wxS( "part-type gates do not consume its pin slice" ) );
2409
2410 if( !part.gates.empty() && part.gates.front().definition.id.IsValid() )
2411 {
2412 auto defaultDefinition =
2413 std::ranges::find_if( aModel.definitions,
2414 [&]( const MODEL_SYMBOL_DEFINITION& aDefinition )
2415 {
2416 return aDefinition.id == part.gates.front().definition.id;
2417 } );
2418
2419 if( defaultDefinition == aModel.definitions.end() )
2420 throwDecodeError( source, wxS( "unresolved part-type default definition" ) );
2421
2422 part.fields = defaultDefinition->fields;
2423 }
2424
2425 const uint16_t signalPinCount = aCursor.U16At( offset + 70 );
2426
2427 if( signalPinCursor + signalPinCount > aControllers.pools[11].count )
2428 throwDecodeError( source, wxS( "part-type signal-pin slice leaves controller 12" ) );
2429
2430 for( size_t signal = 0; signal < signalPinCount; ++signal, ++signalPinCursor )
2431 {
2432 const size_t signalOffset = aLayout.signalPinBase + signalPinCursor * SIGNAL_PIN_BYTES;
2433 SOURCE_PROVENANCE signalSource =
2434 sourceAt( aSourceName, aModel.version, wxS( "signal pin" ), 12, signalPinCursor, signalOffset,
2435 SIGNAL_PIN_BYTES, static_cast<int>( aSheetIndex ) );
2436 SOURCE_PROVENANCE numberSource = signalSource;
2437 numberSource.length = 16;
2438 SOURCE_PROVENANCE nameSource2 = signalSource;
2439 nameSource2.absoluteOffset += 16;
2440 nameSource2.length = 48;
2441 SOURCE_STRING number = decodeFixedString( aBytes, signalOffset, 16, numberSource, aModel.diagnostics );
2442 SOURCE_STRING signalName =
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 ) );
2447 }
2448
2449 aModel.partTypes.push_back( std::move( part ) );
2450 }
2451
2452 if( signalPinCursor != aControllers.pools[11].count )
2453 {
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" ) );
2458 }
2459 }
2460
2461
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,
2464 PADS_SCH_MODEL& aModel )
2465 {
2466 const SHEET_CONTROLLERS controllers = sheetControllers( aCursor, aBlock );
2467
2468 if( aModel.version == 0x000C )
2469 {
2470 preserveRawDefinitionControllers( aBytes, controllers, aSheetIndex, aSourceName, aModel );
2471 return;
2472 }
2473
2474 const DEFINITION_LAYOUT layout =
2475 definitionLayout( aCursor, controllers, aSheetIndex, aSourceName, aModel.version );
2476
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;
2481
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 );
2486
2487 const std::vector<USED_DECAL*> fieldDecals =
2488 buildFieldDecals( semanticDecals, layout, aSheetIndex, aSourceName, aModel.version );
2489
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 );
2493 }
2494
2495
2496 struct PLACEMENT_DECODE
2497 {
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;
2504 size_t sheetIndex;
2505 };
2506
2507
2508 struct PLACEMENT_TARGET
2509 {
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;
2519 };
2520
2521
2522 struct INLINE_FIELD_LAYOUT
2523 {
2524 size_t position;
2525 size_t angle;
2526 size_t font;
2527 size_t height;
2528 size_t width;
2529 };
2530
2531
2532 std::pair<SOURCE_STRING, SOURCE_STRING> decodePlacementAttribute( const PLACEMENT_DECODE& aDecode,
2533 uint32_t aOffsetIndex, bool aRequireValue,
2534 PADS_SCH_MODEL& aModel )
2535 {
2536 if( aOffsetIndex >= aDecode.globals.attributeOffsetCount )
2537 {
2538 SOURCE_PROVENANCE source =
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" ) );
2542 }
2543
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 );
2548
2549 if( heapOffset >= aDecode.globals.attributeHeapBytes )
2550 throwDecodeError( source, wxS( "attribute string offset leaves outer controller 2" ) );
2551
2552 size_t end = source.absoluteOffset;
2553 const size_t heapEnd = aDecode.globals.attributeHeapBase + aDecode.globals.attributeHeapBytes;
2554
2555 while( end < heapEnd && aDecode.bytes[end] != 0 )
2556 ++end;
2557
2558 if( end == heapEnd )
2559 throwDecodeError( source, wxS( "placement attribute is not NUL terminated" ) );
2560
2561 source.length = end - source.absoluteOffset;
2562 size_t separator = source.absoluteOffset;
2563
2564 while( separator < end && aDecode.bytes[separator] != 1 )
2565 ++separator;
2566
2567 if( aRequireValue && separator == end )
2568 throwDecodeError( source, wxS( "placement attribute lacks key/value separator" ) );
2569
2571 { aDecode.bytes.begin() + source.absoluteOffset, aDecode.bytes.begin() + separator }, DEFAULT_CODE_PAGE,
2572 source, aModel.diagnostics );
2573 SOURCE_STRING value;
2574
2575 if( separator != end )
2576 {
2577 SOURCE_PROVENANCE valueSource = source;
2578 valueSource.absoluteOffset = separator + 1;
2579 valueSource.length = end - separator - 1;
2581 { aDecode.bytes.begin() + valueSource.absoluteOffset, aDecode.bytes.begin() + end },
2582 DEFAULT_CODE_PAGE, valueSource, aModel.diagnostics );
2583 }
2584
2585 return std::pair<SOURCE_STRING, SOURCE_STRING>{ std::move( name ), std::move( value ) };
2586 }
2587
2588
2589 PLACEMENT_TARGET resolvePlacementTarget( const PLACEMENT_DECODE& aDecode, size_t aOffset,
2590 const SOURCE_PROVENANCE& aSource, PADS_SCH_MODEL& aModel )
2591 {
2592 PLACEMENT_TARGET target;
2593 target.componentIdentity = aDecode.cursor.U32At( aOffset + aDecode.layout.componentIdentity );
2594
2595 const uint16_t partHandle = aDecode.cursor.U16At( aOffset + aDecode.layout.partType );
2596 auto part = std::ranges::find_if( aModel.partTypes,
2597 [&]( const MODEL_PART_TYPE& aPart )
2598 {
2599 return aPart.source.sheet == static_cast<int>( aDecode.sheetIndex )
2600 && aPart.source.recordIndex == partHandle;
2601 } );
2602
2603 if( part == aModel.partTypes.end() )
2604 {
2605 const bool definitionClass =
2606 std::ranges::any_of( aModel.definitions,
2607 [&]( const MODEL_SYMBOL_DEFINITION& aDefinition )
2608 {
2609 return aDefinition.source.sheet == static_cast<int>( aDecode.sheetIndex )
2610 && aDefinition.source.recordIndex == partHandle;
2611 } );
2612 throwDecodeError( aSource, definitionClass
2613 ? wxS( "placement part-type handle targets definition object class" )
2614 : wxS( "unresolved placement part-type reference" ) );
2615 }
2616
2617 const uint32_t groupHandle = aDecode.cursor.U32At( aOffset + aDecode.layout.componentGroup );
2618
2619 if( groupHandle >= aDecode.globals.groupCount )
2620 throwDecodeError( aSource, wxS( "placement component-group handle leaves outer controller 6" ) );
2621
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 );
2625
2626 if( attributeCount < 2 || attributeStart > aDecode.globals.attributeOffsetCount
2627 || attributeCount > aDecode.globals.attributeOffsetCount - attributeStart )
2628 {
2629 throwDecodeError( aSource, wxS( "placement component-group attribute slice leaves controller 7" ) );
2630 }
2631
2632 auto [groupPartName, unusedGroupPartValue] =
2633 decodePlacementAttribute( aDecode, attributeStart + 1, false, aModel );
2634
2635 if( groupPartName.text != part->name.text )
2636 throwDecodeError( aSource, wxS( "placement component-group targets wrong part-type object class" ) );
2637
2638 const uint16_t decalHandle = aDecode.cursor.U16At( aOffset + aDecode.layout.decal );
2639
2640 if( decalHandle >= aDecode.controllers.pools[6].count )
2641 throwDecodeError( aSource, wxS( "unresolved placement decal reference" ) );
2642
2643 const size_t decalOffset = aDecode.controllers.offsets[6] + decalHandle * USED_DECAL_BYTES;
2644 const uint32_t definitionHandle = aDecode.cursor.U32At( decalOffset + 48 );
2645 auto definition =
2646 std::ranges::find_if( aModel.definitions,
2647 [&]( const MODEL_SYMBOL_DEFINITION& aDefinition )
2648 {
2649 return aDefinition.source.sheet == static_cast<int>( aDecode.sheetIndex )
2650 && aDefinition.source.recordIndex == definitionHandle;
2651 } );
2652
2653 if( definition == aModel.definitions.end() )
2654 throwDecodeError( aSource, wxS( "placement decal targets wrong definition object class" ) );
2655
2656 const uint16_t unitIndex = aDecode.cursor.U16At( aOffset + aDecode.layout.gate );
2657
2658 const MODEL_GATE* gate = nullptr;
2659
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() )
2665 gate = nullptr;
2666 else
2667 {
2668 const bool definitionClass =
2669 std::ranges::any_of( aModel.definitions,
2670 [&]( const MODEL_SYMBOL_DEFINITION& aDefinition )
2671 {
2672 return aDefinition.source.sheet == static_cast<int>( aDecode.sheetIndex )
2673 && aDefinition.source.recordIndex == unitIndex;
2674 } );
2675 throwDecodeError( aSource, definitionClass ? wxS( "placement gate handle targets definition object class" )
2676 : wxS( "unresolved placement gate reference" ) );
2677 }
2678
2679 auto gateHasDefinition = [&]( const DEFINITION_REFERENCE& aReference )
2680 {
2681 return aReference.id == definition->id;
2682 };
2683
2684 if( gate && !gateHasDefinition( gate->definition )
2685 && std::ranges::none_of( gate->alternateDefinitions, gateHasDefinition )
2686 && std::ranges::none_of( gate->decalGroupMembers, gateHasDefinition ) )
2687 {
2688 throwDecodeError( aSource, wxS( "placement decal and gate reference target different object classes" ) );
2689 }
2690
2691 target.part = &*part;
2692 target.definition = &*definition;
2693 target.gate = gate;
2694 target.groupHandle = groupHandle;
2695 target.attributeStart = attributeStart;
2696 target.attributeCount = attributeCount;
2697 target.decalHandle = decalHandle;
2698 target.unitIndex = unitIndex;
2699 return target;
2700 }
2701
2702
2703 void decodePlacedPins( const PLACEMENT_DECODE& aDecode, uint32_t aPinStart, uint16_t aPinCount,
2704 const MODEL_SYMBOL_DEFINITION& aDefinition, MODEL_PLACEMENT& aPlacement,
2705 PADS_SCH_MODEL& aModel )
2706 {
2707 const size_t placedPinBase = aDecode.controllers.offsets[15];
2708
2709 for( size_t pin = 0; pin < aPinCount; ++pin )
2710 {
2711 const size_t pinOffset = placedPinBase + ( aPinStart + pin ) * aDecode.layout.placedPinBytes;
2712 SOURCE_PROVENANCE pinSource =
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 );
2716
2717 if( pinOrdinal >= aDefinition.pins.size() || pinOrdinal != pin )
2718 throwDecodeError( pinSource, wxS( "placed-pin handle leaves placement definition" ) );
2719
2720 PLACED_PIN_REFERENCE pinReference{ aDefinition.pins[pinOrdinal].id, pinSource };
2721 pinReference.numberOffset = {
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
2724 };
2725 pinReference.numberPresentationFlags = aDecode.cursor.U16At( pinOffset + 10 );
2726 pinReference.numberAngle = ( pinReference.numberPresentationFlags & 0x0001 ) != 0 ? 900 : 0;
2727
2728 pinReference.numberJustification = terminalJustification(
2729 ( pinReference.numberPresentationFlags >> 4 ) & 0x0F, pinReference.numberAngle != 0 );
2730
2731 pinReference.hasNumberPlacement = true;
2732 aPlacement.pins.push_back( std::move( pinReference ) );
2733 }
2734 }
2735
2736
2737 void decodeInlineField( const PLACEMENT_DECODE& aDecode, size_t aOffset, const SOURCE_PROVENANCE& aSource,
2738 const wxString& aName, const SOURCE_STRING& aValue, bool aVisible,
2739 const INLINE_FIELD_LAYOUT& aFieldLayout, MODEL_PLACEMENT& aPlacement,
2740 PADS_SCH_MODEL& aModel )
2741 {
2742 SOURCE_PROVENANCE fieldSource = aSource;
2743 fieldSource.objectClass = wxS( "placement field" );
2744 fieldSource.absoluteOffset += aFieldLayout.position;
2745 fieldSource.length = 8;
2746 MODEL_FIELD field;
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 ) ),
2753 fieldSource };
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 );
2763 SOURCE_PROVENANCE fontSource = fieldSource;
2764 fontSource.absoluteOffset = aOffset + aFieldLayout.font;
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 ) );
2770 }
2771
2772
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,
2775 PADS_SCH_MODEL& aModel )
2776 {
2777 const size_t fieldBase = aDecode.controllers.offsets[16];
2778
2779 const uint16_t customFieldCount = aDecode.cursor.U16At( aOffset + aDecode.layout.fieldCount );
2780
2781 if( aFieldCursor > aDecode.controllers.pools[16].count
2782 || customFieldCount > aDecode.controllers.pools[16].count - aFieldCursor )
2783 {
2784 throwDecodeError( aSource, wxS( "placement field ownership does not match controller 17" ) );
2785 }
2786
2787 for( size_t fieldOrdinal = 0; fieldOrdinal < customFieldCount; ++fieldOrdinal )
2788 {
2789 const size_t fieldOffset = fieldBase + aFieldCursor * aDecode.layout.fieldBytes;
2790 SOURCE_PROVENANCE fieldSource =
2791 sourceAt( aDecode.sourceName, aModel.version, wxS( "placement field" ), 17, aFieldCursor,
2792 fieldOffset, aDecode.layout.fieldBytes, static_cast<int>( aDecode.sheetIndex ) );
2794 SOURCE_STRING value;
2795
2796 const uint8_t displayFlags = aDecode.cursor.U8At( fieldOffset + aDecode.layout.customDisplayFlags );
2797 const uint16_t attributeIndex = aDecode.cursor.U16At( fieldOffset + aDecode.layout.customAttributeIndex );
2798
2799 if( attributeIndex == 0xFFFF )
2800 {
2801 name.text = wxS( "*" );
2802 name.source = fieldSource;
2803 value.source = fieldSource;
2804 }
2805 else
2806 {
2807 if( attributeIndex >= aTarget.attributeCount )
2808 throwDecodeError( fieldSource, wxS( "placement field attribute index leaves component group" ) );
2809
2810 std::tie( name, value ) =
2811 decodePlacementAttribute( aDecode, aTarget.attributeStart + attributeIndex, true, aModel );
2812 }
2813 MODEL_FIELD field;
2814 field.source = fieldSource;
2815 field.name = std::move( name );
2816 field.value = std::move( value );
2817 field.position = {
2818 decodeLocalCoordinate( aDecode.cursor.U16At( fieldOffset + aDecode.layout.customX ) ),
2819 decodeLocalCoordinate( aDecode.cursor.U16At( fieldOffset + aDecode.layout.customX + 2 ) ), fieldSource
2820 };
2821 field.angle = NormalizeAngle( aDecode.cursor.U16At( fieldOffset + aDecode.layout.customAngle ) );
2822
2823 if( field.angle % 900 != 0 )
2824 throwDecodeError( fieldSource, wxS( "unsupported placement-field rotation" ) );
2825
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;
2834
2835 SOURCE_PROVENANCE fontSource = fieldSource;
2836 fontSource.length = 2;
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 ) );
2842 SOURCE_PROVENANCE attributeIndexSource = 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 ) );
2850 SOURCE_PROPERTY preservedTail = sourceProperty(
2851 wxS( "preserved_field_tail" ),
2852 wxString::Format( wxS( "%u" ), aDecode.cursor.U16At( fieldOffset + aDecode.layout.customTail ) ),
2853 fieldSource );
2854 preservedTail.disposition = PROPERTY_DISPOSITION::PRESERVED;
2855 field.properties.push_back( std::move( preservedTail ) );
2856 aPlacement.fields.push_back( std::move( field ) );
2857 ++aFieldCursor;
2858 }
2859 }
2860
2861
2862 void decodeGroupAttributeFields( const PLACEMENT_DECODE& aDecode, const PLACEMENT_TARGET& aTarget,
2863 MODEL_PLACEMENT& aPlacement, PADS_SCH_MODEL& aModel )
2864 {
2865 for( uint16_t attributeIndex = 2; attributeIndex < aTarget.attributeCount; ++attributeIndex )
2866 {
2867 auto [name, value] =
2868 decodePlacementAttribute( aDecode, aTarget.attributeStart + attributeIndex, true, aModel );
2869
2870 if( name.text.CmpNoCase( wxS( "PCB DECAL" ) ) == 0
2871 || std::ranges::any_of( aPlacement.fields,
2872 [&]( const MODEL_FIELD& aField )
2873 {
2874 return aField.name.text.CmpNoCase( name.text ) == 0;
2875 } ) )
2876 {
2877 continue;
2878 }
2879
2880 MODEL_FIELD field;
2881 field.source = name.source;
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 ) );
2888 }
2889 }
2890
2891
2892 void recordPlacementProperties( const PLACEMENT_DECODE& aDecode, const SOURCE_PROVENANCE& aSource,
2893 const PLACEMENT_TARGET& aTarget, uint16_t aRawAngle, MODEL_PLACEMENT& aPlacement )
2894 {
2895 SOURCE_PROVENANCE rawAngleSource = aSource;
2896 rawAngleSource.absoluteOffset += aDecode.layout.angle;
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 ) );
2906 SOURCE_PROVENANCE rawMirrorSource = aSource;
2907 rawMirrorSource.absoluteOffset += aDecode.layout.mirror;
2908 rawMirrorSource.length = 2;
2909 aPlacement.properties.push_back( sourceProperty(
2910 wxS( "raw_mirror" ), wxString::Format( wxS( "%u" ), aPlacement.mirrorFlags ), rawMirrorSource ) );
2911 SOURCE_PROVENANCE visibilitySource = aSource;
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 ) );
2917 }
2918
2919
2920 void decodePlacementRecord( const PLACEMENT_DECODE& aDecode, size_t aRecord, uint32_t& aExpectedPinStart,
2921 uint32_t& aFieldCursor, PADS_SCH_MODEL& aModel )
2922 {
2923 const PLACEMENT_LAYOUT& layout = aDecode.layout;
2924 const size_t offset = aDecode.controllers.offsets[14] + aRecord * layout.placementBytes;
2925
2926 SOURCE_PROVENANCE source = sourceAt( aDecode.sourceName, aModel.version, wxS( "placement" ), 15, aRecord,
2927 offset, layout.placementBytes, static_cast<int>( aDecode.sheetIndex ) );
2928
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 );
2932
2933 if( pinStart != aExpectedPinStart || pinStart > aDecode.controllers.pools[15].count
2934 || pinCount > aDecode.controllers.pools[15].count - pinStart || pinCount != target.definition->pins.size() )
2935 {
2936 throwDecodeError( source, wxS( "placement pin ownership does not match controller 16" ) );
2937 }
2938
2939 MODEL_PLACEMENT placement;
2940
2941 if( aDecode.sheetIndex >= 0x0FFF || aRecord >= 0x100000 )
2942 throwDecodeError( source, wxS( "placement identity exceeds sheet/controller namespace" ) );
2943
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 };
2948
2949 if( target.gate )
2950 placement.gate = GATE_REFERENCE{ target.gate->id, source };
2951
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 );
2957 placement.angle = NormalizeAngle( rawAngle );
2958 placement.mirrorFlags = aDecode.cursor.U16At( offset + layout.mirror );
2959
2960 auto recordTransformEnum = [&]( size_t aFieldOffset )
2961 {
2962 SOURCE_PROVENANCE enumSource = source;
2963 enumSource.absoluteOffset += aFieldOffset;
2964 enumSource.length = 2;
2965 aModel.diagnostics.push_back(
2966 { RPT_SEVERITY_WARNING, enumSource, wxS( "unknown placement transform enum preserved" ) } );
2967 };
2968
2969 const bool knownAngle = rawAngle == 0 || rawAngle == 900 || rawAngle == 1800 || rawAngle == 2700;
2970
2971 if( !knownAngle )
2972 recordTransformEnum( layout.angle );
2973
2974 const bool knownMirror = placement.mirrorFlags <= 3;
2975
2976 if( !knownMirror )
2977 recordTransformEnum( layout.mirror );
2978
2979 placement.mirrored = placement.mirrorFlags != 0;
2980 SOURCE_PROVENANCE referenceSource = source;
2981 referenceSource.absoluteOffset += layout.reference;
2982 referenceSource.length = 40;
2983 placement.reference =
2984 decodeFixedString( aDecode.bytes, offset + layout.reference, 40, referenceSource, aModel.diagnostics );
2985
2986 decodePlacedPins( aDecode, pinStart, pinCount, *target.definition, placement, aModel );
2987 aExpectedPinStart += pinCount;
2988
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;
2994
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 ) );
3007 }
3008
3009
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,
3012 const PLACEMENT_GLOBALS& aGlobals, PADS_SCH_MODEL& aModel )
3013 {
3014 const PLACEMENT_LAYOUT& layout = placementLayout( aModel.version );
3015
3016 if( !layout.decoded )
3017 throwDecodeError( aModel.source, wxS( "placement decoder selected for raw-preserved version" ) );
3018
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 );
3023
3024 const PLACEMENT_DECODE decode{ aBytes, aCursor, controllers, layout, aGlobals, aSourceName, aSheetIndex };
3025 uint32_t expectedPinStart = 0;
3026 uint32_t fieldCursor = 0;
3027
3028 for( size_t record = 0; record < controllers.pools[14].count; ++record )
3029 decodePlacementRecord( decode, record, expectedPinStart, fieldCursor, aModel );
3030
3031 if( expectedPinStart != controllers.pools[15].count )
3032 {
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" ) );
3037 }
3038
3039 if( fieldCursor != controllers.pools[16].count )
3040 {
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" ) );
3045 }
3046 }
3047
3048
3049 struct CONNECTIVITY_DECODE
3050 {
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;
3056 size_t sheetIndex;
3057 size_t busBase;
3058 size_t junctionBase;
3059 size_t offpageBase;
3060 size_t connectionBase;
3061 size_t vertexBase;
3062 size_t netNameBase;
3063 };
3064
3065
3066 struct VERTEX_TILING
3067 {
3068 std::vector<uint32_t> starts;
3069 std::vector<uint32_t> ends;
3070 };
3071
3072
3073 struct SHEET_CONNECTIVITY
3074 {
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;
3080 };
3081
3082
3083 std::pair<int64_t, int64_t> transformedPinPosition( const MODEL_PLACEMENT& aPlacement,
3084 const MODEL_PIN_DEFINITION& aPin )
3085 {
3086 int64_t x = aPin.position.x;
3087 int64_t y = aPin.position.y;
3088
3089 switch( NormalizeAngle( aPlacement.angle ) )
3090 {
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;
3094 default: break;
3095 }
3096
3097 if( aPlacement.mirrorFlags & 1 )
3098 x = -x;
3099
3100 if( aPlacement.mirrorFlags & 2 )
3101 y = -y;
3102
3103 return std::pair{ aPlacement.position.x + x, aPlacement.position.y + y };
3104 }
3105
3106
3107 SOURCE_POINT offpagePosition( const CONNECTIVITY_DECODE& aDecode, size_t aRecord, uint16_t aVersion )
3108 {
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 };
3114 }
3115
3116
3117 SOURCE_POINT junctionPosition( const CONNECTIVITY_DECODE& aDecode, size_t aRecord, uint16_t aVersion )
3118 {
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 };
3124 }
3125
3126
3127 std::unordered_set<uint32_t> decodeBusGlobalRecords( const CONNECTIVITY_DECODE& aDecode, PADS_SCH_MODEL& aModel )
3128 {
3129 std::unordered_set<uint32_t> busGlobalRecords;
3130
3131 for( size_t record = 0; record < aDecode.controllers.pools[17].count; ++record )
3132 {
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
3136 : 0;
3137
3138 if( globalRecord >= aDecode.globals.nets.size() || aDecode.globals.nets[globalRecord].tombstone
3139 || ( netKind != 1 && netKind != 5 ) )
3140 {
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" ) );
3145 }
3146
3147 busGlobalRecords.insert( globalRecord );
3148 }
3149
3150 return busGlobalRecords;
3151 }
3152
3153
3154 std::vector<MODEL_NET*> materializeSheetNets( const CONNECTIVITY_DECODE& aDecode,
3155 const std::unordered_set<uint32_t>& aBusGlobalRecords,
3156 PADS_SCH_MODEL& aModel )
3157 {
3158 std::vector<MODEL_NET*> sheetNets( aDecode.globals.nets.size(), nullptr );
3159 // sheetNets holds pointers into nets, and one global record can own several memberships on
3160 // this sheet, so bound the reserve by the disjoint membership slices
3161 aModel.nets.reserve( aModel.nets.size() + aDecode.globals.membershipCount );
3162
3163 for( size_t globalRecord = 0; globalRecord < aDecode.globals.nets.size(); ++globalRecord )
3164 {
3165 const GLOBAL_NET_RECORD& global = aDecode.globals.nets[globalRecord];
3166
3167 if( global.tombstone )
3168 continue;
3169
3170 for( uint32_t membership = global.membershipStart;
3171 membership < global.membershipStart + global.membershipCount; ++membership )
3172 {
3173 if( aDecode.globals.membershipSheets[membership] != aDecode.sheetIndex )
3174 continue;
3175
3176 if( global.aliasCount != 0 || aBusGlobalRecords.contains( globalRecord ) )
3177 continue;
3178
3179 MODEL_NET net;
3180 net.id = NET_ID( membership );
3181 net.source = global.source;
3182 net.source.sheet = static_cast<int>( aDecode.sheetIndex );
3183 SOURCE_PROVENANCE membershipSource =
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 ) );
3191 SOURCE_PROPERTY identity =
3192 sourceProperty( wxS( "preserved_net_identity" ),
3193 wxString::Format( wxS( "%u" ), global.preservedIdentity ), global.source );
3194 identity.disposition = PROPERTY_DISPOSITION::PRESERVED;
3195 net.properties.push_back( std::move( identity ) );
3196 SOURCE_PROVENANCE relationshipSource = global.source;
3197 relationshipSource.absoluteOffset += 84;
3198 relationshipSource.length = 4;
3199 SOURCE_PROPERTY relationship = sourceProperty(
3200 wxS( "preserved_net_relationship" ),
3201 wxString::Format( wxS( "%u" ), global.preservedRelationship ), relationshipSource );
3202 relationship.disposition = PROPERTY_DISPOSITION::PRESERVED;
3203 net.properties.push_back( std::move( relationship ) );
3204 aModel.nets.push_back( std::move( net ) );
3205 sheetNets[globalRecord] = &aModel.nets.back();
3206 }
3207 }
3208
3209 return sheetNets;
3210 }
3211
3212
3213 std::unordered_map<uint32_t, const MODEL_PIN_DEFINITION*> indexDefinitionPins( const PADS_SCH_MODEL& aModel )
3214 {
3215 std::unordered_map<uint32_t, const MODEL_PIN_DEFINITION*> definitionPins;
3216
3217 for( const MODEL_SYMBOL_DEFINITION& definition : aModel.definitions )
3218 {
3219 for( const MODEL_PIN_DEFINITION& pin : definition.pins )
3220 definitionPins.emplace( pin.id.Value(), &pin );
3221 }
3222
3223 return definitionPins;
3224 }
3225
3226
3227 std::vector<MODEL_PLACEMENT*> indexSheetPlacements( const CONNECTIVITY_DECODE& aDecode, PADS_SCH_MODEL& aModel )
3228 {
3229 std::vector<MODEL_PLACEMENT*> placements( aDecode.controllers.pools[14].count, nullptr );
3230
3231 for( MODEL_PLACEMENT& placement : aModel.placements )
3232 {
3233 if( placement.source.sheet != static_cast<int>( aDecode.sheetIndex ) )
3234 continue;
3235
3236 if( placement.source.recordIndex >= placements.size() || placements[placement.source.recordIndex] )
3237 throwDecodeError( placement.source, wxS( "placement endpoint identity leaves controller 15" ) );
3238
3239 placements[placement.source.recordIndex] = &placement;
3240 }
3241
3242 return placements;
3243 }
3244
3245
3246 void decodeJunctions( const CONNECTIVITY_DECODE& aDecode, PADS_SCH_MODEL& aModel )
3247 {
3248 for( size_t record = 0; record < aDecode.controllers.pools[18].count; ++record )
3249 {
3250 const size_t offset = aDecode.junctionBase + record * JUNCTION_RECORD_BYTES;
3251 SOURCE_PROVENANCE source =
3252 sourceAt( aDecode.sourceName, aModel.version, wxS( "junction" ), 19, record, offset,
3253 JUNCTION_RECORD_BYTES, static_cast<int>( aDecode.sheetIndex ) );
3254
3255 const uint16_t status = aDecode.cursor.U16At( offset + 10 );
3256
3257 if( status != 0 && status != 0x00FC && status != 0x00FD )
3258 throwDecodeError( source, wxS( "invalid junction object-class marker" ) );
3259
3260 MODEL_JUNCTION junction;
3261 junction.source = source;
3262 junction.sheet = { aModel.sheets[aDecode.sheetIndex].id, source };
3263 junction.position = junctionPosition( aDecode, record, aModel.version );
3264 SOURCE_PROVENANCE connectionSource = source;
3265 connectionSource.absoluteOffset += 8;
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 ) );
3271 }
3272 }
3273
3274
3275 VERTEX_TILING tileConnectivityVertices( const CONNECTIVITY_DECODE& aDecode, PADS_SCH_MODEL& aModel )
3276 {
3277 VERTEX_TILING tiling;
3278
3279
3280 tiling.starts.reserve( aDecode.controllers.pools[17].count + aDecode.controllers.pools[20].count );
3281
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 ) );
3284
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 ) );
3288
3289 std::vector<size_t> objectOrder( tiling.starts.size() );
3290
3291 for( size_t object = 0; object < objectOrder.size(); ++object )
3292 objectOrder[object] = object;
3293
3294 std::ranges::sort( objectOrder,
3295 [&]( size_t aLeft, size_t aRight )
3296 {
3297 return tiling.starts[aLeft] < tiling.starts[aRight];
3298 } );
3299 tiling.ends.resize( tiling.starts.size() );
3300
3301 for( size_t ordinal = 0; ordinal < objectOrder.size(); ++ordinal )
3302 {
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;
3306
3307 if( ( ordinal == 0 && tiling.starts[object] != 0 ) || tiling.starts[object] >= end
3308 || end > aDecode.controllers.pools[21].count )
3309 {
3310 SOURCE_PROVENANCE source =
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" ) );
3315 }
3316
3317 tiling.ends[object] = end;
3318 }
3319
3320 if( tiling.starts.empty() && aDecode.controllers.pools[21].count != 0 )
3321 {
3322 SOURCE_PROVENANCE source =
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" ) );
3326 }
3327
3328 return tiling;
3329 }
3330
3331
3332 void appendVertices( const CONNECTIVITY_DECODE& aDecode, const VERTEX_TILING& aTiling, size_t aObject,
3333 uint16_t aVersion, std::vector<SOURCE_POINT>& aVertices )
3334 {
3335 const uint32_t start = aTiling.starts[aObject];
3336 const uint32_t end = aTiling.ends[aObject];
3337
3338 for( uint32_t vertex = start; vertex < end; ++vertex )
3339 {
3340 const size_t offset = aDecode.vertexBase + vertex * CONNECTION_VERTEX_BYTES;
3341 SOURCE_PROVENANCE source =
3342 sourceAt( aDecode.sourceName, aVersion, wxS( "connection vertex" ), 22, vertex, offset,
3343 CONNECTION_VERTEX_BYTES, static_cast<int>( aDecode.sheetIndex ) );
3344
3345 if( aDecode.cursor.U32At( offset ) != 0 )
3346 throwDecodeError( source, wxS( "nonzero connection-vertex padding" ) );
3347
3348 aVertices.push_back( { decodeCoordinate( aDecode.cursor.U16At( offset + 4 ) ),
3349 decodeCoordinate( aDecode.cursor.U16At( offset + 6 ) ), source } );
3350 }
3351 }
3352
3353
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,
3359 PADS_SCH_MODEL& aModel )
3360 {
3361 SOURCE_PROVENANCE endpointSource = aSource;
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;
3368 SOURCE_PROVENANCE relationshipSource = aSource;
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 );
3374 result.source = endpointSource;
3375
3376 switch( objectClass )
3377 {
3378 case 0:
3379 if( objectRecord >= aPlacements.size() || !aPlacements[objectRecord] )
3380 throwDecodeError( endpointSource, wxS( "unresolved placement endpoint" ) );
3381
3382 {
3383 MODEL_PLACEMENT& placement = *aPlacements[objectRecord];
3384 const PIN_REFERENCE* matchedPin = nullptr;
3385
3386 for( const PIN_REFERENCE& pin : placement.pins )
3387 {
3388 auto definitionPin = aDefinitionPins.find( pin.id.Value() );
3389
3390 if( definitionPin == aDefinitionPins.end() )
3391 throwDecodeError( pin.source, wxS( "placement pin definition is unresolved" ) );
3392
3393 const auto position = transformedPinPosition( placement, *definitionPin->second );
3394
3395 if( position.first != aWirePoint.x || position.second != aWirePoint.y )
3396 continue;
3397
3398 if( matchedPin )
3399 throwDecodeError( endpointSource,
3400 wxS( "placement has duplicate pins at connection endpoint" ) );
3401
3402 matchedPin = &pin;
3403 }
3404
3405 if( !matchedPin )
3406 throwDecodeError( endpointSource,
3407 wxString::Format( wxS( "placement has no pin at connection endpoint; "
3408 "angle %d mirror %u" ),
3409 placement.angle, placement.mirrorFlags ) );
3410
3412 result.placement = PLACEMENT_REFERENCE{ placement.id, endpointSource };
3413 result.pin = PIN_REFERENCE{ matchedPin->id, endpointSource };
3414 result.point = aWirePoint;
3415 }
3416 break;
3417
3418 case 2:
3419 if( objectRecord >= aDecode.controllers.pools[19].count )
3420 throwDecodeError( endpointSource, wxS( "unresolved off-page endpoint" ) );
3421
3423 result.point = offpagePosition( aDecode, objectRecord, aModel.version );
3424 break;
3425
3426 case 3:
3427 if( objectRecord >= aDecode.controllers.pools[18].count )
3428 throwDecodeError( endpointSource, wxS( "unresolved junction endpoint" ) );
3429
3431 result.point = junctionPosition( aDecode, objectRecord, aModel.version );
3432 break;
3433
3434 default: throwDecodeError( endpointSource, wxS( "wrong endpoint object class" ) );
3435 }
3436
3437 result.properties.push_back(
3438 sourceProperty( wxS( "raw_endpoint_handle" ), wxString::Format( wxS( "%u" ), raw ), endpointSource ) );
3439 SOURCE_PROPERTY relationshipProperty = sourceProperty(
3440 wxS( "raw_endpoint_relationship" ), wxString::Format( wxS( "%u" ), relationship ), relationshipSource );
3441 relationshipProperty.disposition = PROPERTY_DISPOSITION::PRESERVED;
3442 result.properties.push_back( std::move( relationshipProperty ) );
3443 return result;
3444 }
3445
3446
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,
3450 SHEET_CONNECTIVITY& aConnectivity, PADS_SCH_MODEL& aModel )
3451 {
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 );
3458
3459 for( size_t record = 0; record < aDecode.controllers.pools[20].count; ++record )
3460 {
3461 const uint32_t netHandle =
3462 aDecode.cursor.U32At( aDecode.connectionBase + record * CONNECTION_RECORD_BYTES + 8 );
3463
3464 if( netHandle < connectionCounts.size() )
3465 ++connectionCounts[netHandle];
3466 }
3467
3468 for( size_t net = 0; net < aConnectivity.sheetNets.size(); ++net )
3469 {
3470 if( aConnectivity.sheetNets[net] )
3471 aConnectivity.sheetNets[net]->connections.reserve( connectionCounts[net] );
3472 }
3473
3474 for( size_t record = 0; record < aDecode.controllers.pools[20].count; ++record )
3475 {
3476 const size_t offset = aDecode.connectionBase + record * CONNECTION_RECORD_BYTES;
3477 SOURCE_PROVENANCE source =
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 );
3481
3482 if( netHandle >= aConnectivity.sheetNets.size() || !aConnectivity.sheetNets[netHandle] )
3483 throwDecodeError( source, wxS( "connection net handle targets wrong or unresolved object class" ) );
3484
3485 const uint16_t marker = aDecode.cursor.U16At( offset + 34 );
3486
3487 const uint8_t markerStatus = marker & 0xFF;
3488
3489 if( marker >> 8 < 2 || marker >> 8 > 6
3490 || ( markerStatus != 0 && markerStatus != 0xFC && markerStatus != 0xFD ) )
3491 throwDecodeError( source, wxS( "invalid connection object-class marker" ) );
3492
3493 MODEL_CONNECTION connection;
3494 connection.source = source;
3495
3496 appendVertices( aDecode, aTiling, aDecode.controllers.pools[17].count + record, aModel.version,
3497 connection.vertices );
3498
3499 if( connection.vertices.size() < 2 )
3500 throwDecodeError( source, wxS( "connection lacks explicit endpoint vertices" ) );
3501
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 ) );
3506
3507 for( size_t endpointIndex = 0; endpointIndex < connection.endpoints.size(); ++endpointIndex )
3508 {
3509 MODEL_CONNECTION_ENDPOINT& decodedEndpoint = connection.endpoints[endpointIndex];
3510 const SOURCE_POINT& wirePoint =
3511 endpointIndex == 0 ? connection.vertices.front() : connection.vertices.back();
3512
3513 if( decodedEndpoint.kind == MODEL_ENDPOINT_KIND::POINT
3514 && ( decodedEndpoint.point.x != wirePoint.x || decodedEndpoint.point.y != wirePoint.y ) )
3515 {
3516 throwDecodeError( decodedEndpoint.source,
3517 wxS( "typed endpoint position does not match wire endpoint vertex" ) );
3518 }
3519
3520 decodedEndpoint.point = { wirePoint.x, wirePoint.y, wirePoint.source };
3521 }
3522
3523 connectionEndpointHandles[record] = { aDecode.cursor.U16At( offset + 12 ),
3524 aDecode.cursor.U16At( offset + 14 ) };
3525
3526 for( size_t endpointIndex = 0; endpointIndex < connectionEndpointHandles[record].size(); ++endpointIndex )
3527 {
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;
3532
3533 if( objectClass == 2 )
3534 backlinks = &aConnectivity.offpageBacklinks[objectRecord];
3535 else if( objectClass == 3 )
3536 backlinks = &aConnectivity.junctionBacklinks[objectRecord];
3537
3538 if( backlinks )
3539 {
3540 if( endpointIndex == 1 && connectionEndpointHandles[record][0] == raw )
3541 {
3542 SOURCE_PROVENANCE endpointSource = source;
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" ) );
3547 }
3548
3549 backlinks->push_back( record );
3550 }
3551 }
3552
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];
3558 }
3559 }
3560
3561
3562 void validateJunctionBacklinks( const CONNECTIVITY_DECODE& aDecode, const SHEET_CONNECTIVITY& aConnectivity,
3563 PADS_SCH_MODEL& aModel )
3564 {
3565 for( size_t record = 0; record < aDecode.controllers.pools[18].count; ++record )
3566 {
3567 const size_t offset = aDecode.junctionBase + record * JUNCTION_RECORD_BYTES;
3568 SOURCE_PROVENANCE source =
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 );
3572 SOURCE_PROVENANCE ownerSource = source;
3573 ownerSource.absoluteOffset += 8;
3574 ownerSource.length = 2;
3575
3576 if( owner >= aConnectivity.connections.size() || !aConnectivity.connections[owner] )
3577 throwDecodeError( ownerSource, wxS( "junction connection handle leaves controller 21" ) );
3578
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" ) );
3582
3583 for( size_t connection : aConnectivity.junctionBacklinks[record] )
3584 {
3585 if( aConnectivity.connectionNets[connection] != aConnectivity.connectionNets[owner] )
3586 {
3587 throwDecodeError( ownerSource, wxS( "junction is shared across different nets" ) );
3588 }
3589 }
3590 }
3591 }
3592
3593
3594 void validateOffpageBacklinks( const CONNECTIVITY_DECODE& aDecode, const SHEET_CONNECTIVITY& aConnectivity,
3595 PADS_SCH_MODEL& aModel )
3596 {
3597 for( size_t record = 0; record < aDecode.controllers.pools[19].count; ++record )
3598 {
3599 const size_t offset = aDecode.offpageBase + record * OFFPAGE_RECORD_BYTES;
3600 SOURCE_PROVENANCE source =
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 );
3604 SOURCE_PROVENANCE ownerSource = source;
3605 ownerSource.absoluteOffset += 8;
3606 ownerSource.length = 2;
3607
3608 if( owner >= aConnectivity.connections.size() || !aConnectivity.connections[owner] )
3609 throwDecodeError( ownerSource, wxS( "off-page net handle leaves controller 21" ) );
3610
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" ) );
3614
3615 for( size_t connection : aConnectivity.offpageBacklinks[record] )
3616 {
3617 if( aConnectivity.connectionNets[connection] != aConnectivity.connectionNets[owner] )
3618 throwDecodeError( ownerSource, wxS( "off-page reference is shared across different nets" ) );
3619 }
3620 }
3621 }
3622
3623
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 )
3627 {
3628 std::vector<bool> claimedBusEntries( aDecode.controllers.pools[19].count, false );
3629
3630 for( size_t record = 0; record < aDecode.controllers.pools[17].count; ++record )
3631 {
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 );
3636
3637 const uint8_t markerStatus = marker & 0xFF;
3638
3639 if( marker >> 8 < 2 || marker >> 8 > 4 || ( markerStatus != 0 && markerStatus != 0xFD ) )
3640 throwDecodeError( source, wxS( "invalid bus object-class marker" ) );
3641
3642 const size_t globalRecord = aDecode.cursor.U32At( offset + 8 );
3643
3644 if( globalRecord >= aDecode.globals.nets.size() || aDecode.globals.nets[globalRecord].tombstone
3645 || !aBusGlobalRecords.contains( globalRecord ) )
3646 {
3647 throwDecodeError( source, wxS( "bus global-net handle targets wrong or unresolved object class" ) );
3648 }
3649
3650 const GLOBAL_NET_RECORD& global = aDecode.globals.nets[globalRecord];
3651 uint32_t membership = global.membershipStart;
3652
3653 while( membership < global.membershipStart + global.membershipCount
3654 && aDecode.globals.membershipSheets[membership] != aDecode.sheetIndex )
3655 {
3656 ++membership;
3657 }
3658
3659 if( membership == global.membershipStart + global.membershipCount )
3660 throwDecodeError( source, wxS( "bus global-net handle does not belong to this sheet" ) );
3661
3662 MODEL_BUS bus;
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;
3669 SOURCE_PROPERTY identity =
3670 sourceProperty( wxS( "preserved_net_identity" ),
3671 wxString::Format( wxS( "%u" ), global.preservedIdentity ), global.source );
3672 identity.disposition = PROPERTY_DISPOSITION::PRESERVED;
3673 bus.properties.push_back( std::move( identity ) );
3674 SOURCE_PROVENANCE relationshipSource = global.source;
3675 relationshipSource.absoluteOffset += 84;
3676 relationshipSource.length = 4;
3677 SOURCE_PROPERTY relationship =
3678 sourceProperty( wxS( "preserved_net_relationship" ),
3679 wxString::Format( wxS( "%u" ), global.preservedRelationship ), relationshipSource );
3680 relationship.disposition = PROPERTY_DISPOSITION::PRESERVED;
3681 bus.properties.push_back( std::move( relationship ) );
3682 appendVertices( aDecode, aTiling, record, aModel.version, bus.vertices );
3683
3684 std::vector<size_t> entryRecords;
3685 uint16_t entryHandle = aDecode.cursor.U16At( offset + 24 );
3686 std::unordered_set<size_t> chain;
3687
3688 while( ( entryHandle >> 12 ) == 2 )
3689 {
3690 const size_t entryRecord = entryHandle & 0x0FFF;
3691
3692 if( entryRecord >= aDecode.controllers.pools[19].count )
3693 throwDecodeError( source, wxS( "unresolved bus-entry handle" ) );
3694
3695 if( !chain.insert( entryRecord ).second )
3696 throwDecodeError( source, wxS( "cyclic bus-entry handle chain" ) );
3697
3698 entryRecords.push_back( entryRecord );
3699 entryHandle = aDecode.cursor.U16At( aDecode.offpageBase + entryRecord * OFFPAGE_RECORD_BYTES + 4 );
3700 }
3701
3702 if( record > 0x0FFF || entryHandle != 0xBFFF - record )
3703 throwDecodeError( source, wxS( "bus-entry chain terminates in wrong object class" ) );
3704
3705 std::ranges::reverse( entryRecords );
3706
3707 const bool exactAliasMapping = entryRecords.size() == global.aliasMembers.size();
3708
3709 for( size_t entry = 0; entry < entryRecords.size(); ++entry )
3710 {
3711 const size_t entryRecord = entryRecords[entry];
3712 const size_t entryOffset = aDecode.offpageBase + entryRecord * OFFPAGE_RECORD_BYTES;
3713 SOURCE_PROVENANCE entrySource =
3714 sourceAt( aDecode.sourceName, aModel.version, wxS( "bus entry" ), 20, entryRecord, entryOffset,
3715 OFFPAGE_RECORD_BYTES, static_cast<int>( aDecode.sheetIndex ) );
3716
3717 if( aDecode.cursor.U8At( entryOffset + 30 ) != 0xFF )
3718 throwDecodeError( entrySource, wxS( "bus-entry handle targets wrong off-page object class" ) );
3719
3720 if( claimedBusEntries[entryRecord] )
3721 throwDecodeError( entrySource, wxS( "duplicate bus-entry membership" ) );
3722
3723 const uint16_t connectionHandle = aDecode.cursor.U16At( entryOffset + 8 );
3724
3725 if( connectionHandle >= aConnectivity.connections.size()
3726 || !aConnectivity.connections[connectionHandle] )
3727 throwDecodeError( entrySource, wxS( "unresolved bus-entry connection reference" ) );
3728
3729 MODEL_CONNECTION* entryConnection = aConnectivity.connections[connectionHandle];
3730 MODEL_NET* memberNet = aConnectivity.connectionNets[connectionHandle];
3731
3732 if( !memberNet )
3733 throwDecodeError( entrySource, wxS( "bus-entry connection targets wrong member-net class" ) );
3734
3735 if( exactAliasMapping && memberNet->name.text != global.aliasMembers[entry].text )
3736 throwDecodeError( entrySource, wxS( "bus alias member does not match connected net" ) );
3737
3738 const bool ownsConnection = std::ranges::any_of( memberNet->connections,
3739 [&]( const MODEL_CONNECTION& aConnection )
3740 {
3741 return &aConnection == entryConnection;
3742 } );
3743
3744 if( !ownsConnection )
3745 throwDecodeError( entrySource, wxS( "bus-entry connection targets wrong member-net class" ) );
3746
3747 claimedBusEntries[entryRecord] = true;
3748 MODEL_BUS_ENTRY busEntry;
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 } );
3754 }
3755
3756 aModel.buses.push_back( std::move( bus ) );
3757 }
3758
3759 return claimedBusEntries;
3760 }
3761
3762
3763 void decodeOffpageLabels( const CONNECTIVITY_DECODE& aDecode, const SHEET_CONNECTIVITY& aConnectivity,
3764 const std::vector<bool>& aClaimedBusEntries, PADS_SCH_MODEL& aModel )
3765 {
3766 for( size_t record = 0; record < aDecode.controllers.pools[19].count; ++record )
3767 {
3768 const size_t offset = aDecode.offpageBase + record * OFFPAGE_RECORD_BYTES;
3769 SOURCE_PROVENANCE source =
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 );
3773
3774 if( rawKind == 0xFF )
3775 {
3776 if( !aClaimedBusEntries[record] )
3777 throwDecodeError( source, wxS( "unclaimed bus-entry record" ) );
3778
3779 continue;
3780 }
3781
3782 const uint16_t connectionHandle = aDecode.cursor.U16At( offset + 8 );
3783
3784 if( connectionHandle >= aConnectivity.connections.size() || !aConnectivity.connections[connectionHandle] )
3785 throwDecodeError( source, wxS( "off-page net handle leaves controller 21" ) );
3786
3787 MODEL_NET* ownerNet = aConnectivity.connectionNets[connectionHandle];
3788
3789 if( !ownerNet )
3790 throwDecodeError( source, wxS( "off-page record targets wrong net object class" ) );
3791
3792 MODEL_LABEL label;
3793 label.source = source;
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;
3799
3800 if( rawKind == 0xFE )
3801 {
3802 label.kind = MODEL_LABEL_KIND::LOCAL;
3803 }
3804 else
3805 {
3806 const uint16_t decalHandle = aDecode.cursor.U16At( offset + 4 );
3807
3808 if( decalHandle == 0xFFFF )
3809 {
3810 label.kind = MODEL_LABEL_KIND::GLOBAL;
3811 }
3812 else
3813 {
3814 if( decalHandle >= aDecode.controllers.pools[6].count )
3815 throwDecodeError( source, wxS( "off-page decal handle leaves controller 7" ) );
3816
3817 SOURCE_PROVENANCE decalSource =
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 );
3824
3825 if( definitionRecord >= aDecode.controllers.pools[2].count )
3826 throwDecodeError( decalSource, wxS( "off-page decal definition leaves controller 3" ) );
3827
3828 const DEFINITION_ID definitionId(
3829 static_cast<uint32_t>( aDecode.sheetIndex * 0x100000 + 1 + definitionRecord ) );
3830 auto specialPartOwnsDefinition = [&]( const wxString& aPartName )
3831 {
3832 return std::ranges::any_of(
3833 aModel.partTypes,
3834 [&]( const MODEL_PART_TYPE& aPart )
3835 {
3836 if( aPart.name.text != aPartName )
3837 return false;
3838
3839 return std::ranges::any_of(
3840 aPart.gates,
3841 [&]( const MODEL_GATE& aGate )
3842 {
3843 return aGate.definition.id == definitionId
3844 || std::ranges::any_of( aGate.alternateDefinitions,
3845 [&]( const DEFINITION_REFERENCE& aRef )
3846 {
3847 return aRef.id == definitionId;
3848 } );
3849 } );
3850 } );
3851 };
3852
3853 if( specialPartOwnsDefinition( wxS( "$OSR_SYMS" ) ) )
3854 label.kind = MODEL_LABEL_KIND::GLOBAL;
3855 else if( specialPartOwnsDefinition( wxS( "$GND_SYMS" ) ) )
3856 label.kind = MODEL_LABEL_KIND::GROUND;
3857 else if( specialPartOwnsDefinition( wxS( "$PWR_SYMS" ) ) )
3858 label.kind = MODEL_LABEL_KIND::POWER;
3859 else
3860 {
3861 label.kind = MODEL_LABEL_KIND::UNSUPPORTED;
3862 SOURCE_PROPERTY unsupportedDecal =
3863 sourceProperty( wxS( "unsupported_offpage_decal" ), decalName.text, decalSource );
3864 unsupportedDecal.disposition = PROPERTY_DISPOSITION::UNSUPPORTED;
3865 aModel.diagnostics.push_back(
3867 wxS( "unsupported off-page decal class preserved" ) ) );
3868 label.properties.push_back( std::move( unsupportedDecal ) );
3869 }
3870 }
3871 }
3872
3873 if( label.kind == MODEL_LABEL_KIND::GLOBAL )
3874 {
3875 for( uint32_t membership =
3876 aDecode.globals
3877 .nets[aDecode.cursor.U32At( aDecode.connectionBase
3878 + connectionHandle * CONNECTION_RECORD_BYTES + 8 )]
3879 .membershipStart;
3880 membership
3881 < aDecode.globals.nets[aDecode.cursor.U32At( aDecode.connectionBase
3882 + connectionHandle * CONNECTION_RECORD_BYTES + 8 )]
3883 .membershipStart
3884 + aDecode.globals
3885 .nets[aDecode.cursor.U32At( aDecode.connectionBase
3886 + connectionHandle * CONNECTION_RECORD_BYTES + 8 )]
3887 .membershipCount;
3888 ++membership )
3889 {
3890 const uint16_t peerSheet = aDecode.globals.membershipSheets[membership];
3891
3892 if( peerSheet != aDecode.sheetIndex )
3893 label.linkedSheets.push_back( { aModel.sheets[peerSheet].id, source } );
3894 }
3895 }
3896
3897 label.properties.push_back(
3898 sourceProperty( wxS( "offpage_variant" ), wxString::Format( wxS( "%u" ), rawKind ), source ) );
3899 aModel.labels.push_back( std::move( label ) );
3900 }
3901 }
3902
3903
3904 MODEL_TEXT_PRESENTATION netNamePresentation( const CONNECTIVITY_DECODE& aDecode, size_t aOffset,
3905 const SOURCE_PROVENANCE& aSource, PADS_SCH_MODEL& aModel )
3906 {
3907 MODEL_TEXT_PRESENTATION presentation;
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 ) );
3914 SOURCE_PROVENANCE fontSource = aSource;
3915 fontSource.length = 2;
3916
3917 if( fontHandle == -1 )
3918 {
3919 presentation.font = decodedDefinitionFont( -1, fontSource );
3920 }
3921 else
3922 {
3923 if( fontHandle < 0 || static_cast<uint32_t>( fontHandle ) >= aDecode.globals.fontCount )
3924 throwDecodeError( fontSource, wxS( "net-name font handle leaves outer controller 19" ) );
3925
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;
3933
3934 if( style & ~3U )
3935 {
3936 SOURCE_PROPERTY property = sourceProperty( wxS( "unsupported_font_style_flags" ),
3937 wxString::Format( wxS( "%u" ), style & ~3U ), fontSource );
3938 property.disposition = PROPERTY_DISPOSITION::UNSUPPORTED;
3939 presentation.properties.push_back( property );
3940 aModel.diagnostics.push_back( MakePropertyDiagnostic(
3941 RPT_SEVERITY_WARNING, property, wxS( "unsupported net-name font style flags preserved" ) ) );
3942 }
3943 }
3944
3945 presentation.properties.push_back(
3946 sourceProperty( wxS( "font_handle" ), wxString::Format( wxS( "%d" ), fontHandle ), fontSource ) );
3947 return presentation;
3948 }
3949
3950
3951 void decodeNetNames( const CONNECTIVITY_DECODE& aDecode, const SHEET_CONNECTIVITY& aConnectivity,
3952 PADS_SCH_MODEL& aModel )
3953 {
3954 for( size_t record = 0; record < aDecode.controllers.pools[22].count; ++record )
3955 {
3956 const size_t offset = aDecode.netNameBase + record * NET_NAME_RECORD_BYTES;
3957 SOURCE_PROVENANCE source =
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 )
3965 {
3966 SOURCE_PROVENANCE propertySource = source;
3967 propertySource.absoluteOffset += aRelativeOffset;
3968 propertySource.length = aLength;
3969 SOURCE_PROPERTY property = sourceProperty( aName, aValue, propertySource );
3970 property.disposition = PROPERTY_DISPOSITION::PRESERVED;
3971 preservedPresentation.push_back( std::move( property ) );
3972 };
3973 wxString presentation06;
3974
3975 for( size_t index = 6; index < 16; ++index )
3976 presentation06 += wxString::Format( wxS( "%02x" ), aDecode.bytes[offset + index] );
3977
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 ) ) ),
3982 28, 4 );
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 );
3995
3996 if( ( ownerHandle & 0xF000 ) == 0x4000 )
3997 {
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" ) );
4000
4001 const size_t busRecord = ownerHandle & 0x0FFF;
4002 auto bus = std::ranges::find_if( aModel.buses,
4003 [&]( const MODEL_BUS& aBus )
4004 {
4005 return aBus.sheet.id == aModel.sheets[aDecode.sheetIndex].id
4006 && aBus.source.recordIndex == busRecord;
4007 } );
4008
4009 if( bus == aModel.buses.end() || childHandle == 0 || childHandle > bus->memberNets.size()
4010 || bus->name.text != aDecode.globals.nets[globalRecord].name.text )
4011 {
4012 throwDecodeError( source, wxS( "net-name bus owner targets wrong object class" ) );
4013 }
4014
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() ) );
4019 continue;
4020 }
4021
4022 if( globalRecord >= aConnectivity.sheetNets.size() || !aConnectivity.sheetNets[globalRecord] )
4023 throwDecodeError( source, wxS( "net-name record targets wrong or unresolved net object class" ) );
4024
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 };
4028 MODEL_TEXT_PRESENTATION presentation = netNamePresentation( aDecode, offset, source, aModel );
4029 presentation.properties.insert( presentation.properties.end(),
4030 std::make_move_iterator( preservedPresentation.begin() ),
4031 std::make_move_iterator( preservedPresentation.end() ) );
4032
4033 if( ( ownerHandle & 0xF000 ) == 0x2000 )
4034 {
4035 const size_t offpageRecord = ownerHandle & 0x0FFF;
4036 auto label = std::ranges::find_if( aModel.labels,
4037 [&]( const MODEL_LABEL& aLabel )
4038 {
4039 return aLabel.sheet.id == aModel.sheets[aDecode.sheetIndex].id
4040 && aLabel.source.controller == 20
4041 && aLabel.source.recordIndex == offpageRecord;
4042 } );
4043
4044 if( label == aModel.labels.end() )
4045 {
4046 const MODEL_BUS_ENTRY* busEntry = nullptr;
4047
4048 for( const MODEL_BUS& bus : aModel.buses )
4049 {
4050 if( bus.sheet.id != aModel.sheets[aDecode.sheetIndex].id )
4051 continue;
4052
4053 auto candidate = std::ranges::find_if( bus.entries,
4054 [&]( const MODEL_BUS_ENTRY& aEntry )
4055 {
4056 return aEntry.source.recordIndex == offpageRecord
4057 && aEntry.memberNet.id == ownerNet.id;
4058 } );
4059
4060 if( candidate != bus.entries.end() )
4061 {
4062 if( busEntry )
4063 throwDecodeError( source, wxS( "net-name bus-entry owner is ambiguous" ) );
4064
4065 busEntry = &*candidate;
4066 }
4067 }
4068
4069 if( !busEntry )
4070 throwDecodeError( source, wxS( "net-name off-page owner targets wrong object class" ) );
4071
4072 MODEL_TEXT busText;
4073 busText.source = source;
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,
4077 source };
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 ) );
4084 continue;
4085 }
4086
4087 if( label->text.text != ownerNet.name.text )
4088 throwDecodeError( source, wxS( "net-name off-page owner targets wrong net object" ) );
4089
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 ) );
4095 continue;
4096 }
4097
4098 auto placement =
4099 std::ranges::find_if( aModel.placements,
4100 [&]( const MODEL_PLACEMENT& aPlacement )
4101 {
4102 return aPlacement.sheet.id == aModel.sheets[aDecode.sheetIndex].id
4103 && aPlacement.source.recordIndex == ownerHandle;
4104 } );
4105
4106 if( placement == aModel.placements.end() )
4107 throwDecodeError( source, wxS( "net-name placement owner targets wrong object class" ) );
4108
4109 if( childHandle >= placement->pins.size() )
4110 throwDecodeError( source, wxS( "net-name placement pin ordinal leaves placement" ) );
4111
4112 const PIN_ID ownerPin = placement->pins[childHandle].id;
4113
4114 const MODEL_CONNECTION_ENDPOINT* endpoint = nullptr;
4115
4116 for( const MODEL_CONNECTION& connection : ownerNet.connections )
4117 {
4118 for( const MODEL_CONNECTION_ENDPOINT& candidate : connection.endpoints )
4119 {
4120 if( candidate.kind != MODEL_ENDPOINT_KIND::PIN || !candidate.placement || !candidate.pin
4121 || candidate.placement->id != placement->id || candidate.pin->id != ownerPin )
4122 {
4123 continue;
4124 }
4125
4126 if( endpoint )
4127 throwDecodeError( source, wxS( "net-name placement pin owner is ambiguous" ) );
4128
4129 endpoint = &candidate;
4130 }
4131 }
4132
4133 if( !endpoint )
4134 throwDecodeError( source, wxS( "net-name placement pin owner is unresolved" ) );
4135
4137 text.source = source;
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 };
4141 text.angle = NormalizeAngle( aDecode.cursor.U16At( offset + 24 ) );
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 ) );
4147 }
4148 }
4149
4150
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,
4153 const CONNECTIVITY_GLOBALS& aGlobals, PADS_SCH_MODEL& aModel )
4154 {
4155 if( aModel.version != 0x000D )
4156 throwDecodeError( aModel.source, wxS( "connectivity decoder selected for raw-preserved version" ) );
4157
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 );
4165
4166 const CONNECTIVITY_DECODE decode{ aBytes,
4167 aCursor,
4168 controllers,
4169 aGlobals,
4170 aSourceName,
4171 aSheetIndex,
4172 controllers.offsets[17],
4173 controllers.offsets[18],
4174 controllers.offsets[19],
4175 controllers.offsets[20],
4176 controllers.offsets[21],
4177 controllers.offsets[22] };
4178
4179 const std::unordered_set<uint32_t> busGlobalRecords = decodeBusGlobalRecords( decode, aModel );
4180 SHEET_CONNECTIVITY connectivity;
4181 connectivity.sheetNets = materializeSheetNets( decode, busGlobalRecords, aModel );
4182
4183 const std::unordered_map<uint32_t, const MODEL_PIN_DEFINITION*> definitionPins = indexDefinitionPins( aModel );
4184 const std::vector<MODEL_PLACEMENT*> placements = indexSheetPlacements( decode, aModel );
4185
4186 decodeJunctions( decode, aModel );
4187
4188 const VERTEX_TILING tiling = tileConnectivityVertices( decode, aModel );
4189
4190 decodeConnections( decode, tiling, placements, definitionPins, connectivity, aModel );
4191 validateJunctionBacklinks( decode, connectivity, aModel );
4192 validateOffpageBacklinks( decode, connectivity, aModel );
4193
4194 const std::vector<bool> claimedBusEntries =
4195 decodeBuses( decode, tiling, busGlobalRecords, connectivity, aModel );
4196
4197 decodeOffpageLabels( decode, connectivity, claimedBusEntries, aModel );
4198 decodeNetNames( decode, connectivity, aModel );
4199 }
4200
4201
4202 void assignFieldIds( PADS_SCH_MODEL& aModel )
4203 {
4204 auto assign = []( MODEL_FIELD& aField, FIELD_ID_DOMAIN aDomain, uint32_t aOwner, size_t aOrdinal )
4205 {
4206 if( aOrdinal > FIELD_ID_MAX_ORDINAL )
4207 throwDecodeError( aField.source, wxS( "field ordinal exceeds identity capacity" ) );
4208
4209 aField.id = MakeFieldId( aDomain, aOwner, static_cast<uint32_t>( aOrdinal ) );
4210 };
4211
4212 for( MODEL_SHEET& sheet : aModel.sheets )
4213 {
4214 for( size_t ordinal = 0; ordinal < sheet.titleBlockFields.size(); ++ordinal )
4215 {
4216 MODEL_FIELD& field = sheet.titleBlockFields[ordinal];
4217 assign( field, FIELD_ID_DOMAIN::SHEET, sheet.id.Value(), ordinal );
4218 }
4219 }
4220
4221 for( MODEL_SYMBOL_DEFINITION& definition : aModel.definitions )
4222 {
4223 for( size_t ordinal = 0; ordinal < definition.fields.size(); ++ordinal )
4224 {
4225 MODEL_FIELD& field = definition.fields[ordinal];
4226 assign( field, FIELD_ID_DOMAIN::DEFINITION, definition.id.Value(), ordinal );
4227 }
4228 }
4229
4230 for( MODEL_PART_TYPE& partType : aModel.partTypes )
4231 {
4232 for( size_t ordinal = 0; ordinal < partType.fields.size(); ++ordinal )
4233 {
4234 MODEL_FIELD& field = partType.fields[ordinal];
4235 assign( field, FIELD_ID_DOMAIN::PART_TYPE, partType.id.Value(), ordinal );
4236 }
4237 }
4238
4239 for( MODEL_PLACEMENT& placement : aModel.placements )
4240 {
4241 for( size_t ordinal = 0; ordinal < placement.fields.size(); ++ordinal )
4242 {
4243 MODEL_FIELD& field = placement.fields[ordinal];
4244 assign( field, FIELD_ID_DOMAIN::PLACEMENT, placement.id.Value(), ordinal );
4245 }
4246 }
4247 }
4248
4249
4250 template <typename Item>
4251 void validateSheetRef( const Item& aItem, const MODEL_REFERENCE_INDEX& aIndex, const wxString& aObjectClass )
4252 {
4253 if( !aIndex.sheets.contains( aItem.sheet.id.Value() ) )
4254 {
4255 throwValidationError( aItem.sheet.source,
4256 wxString::Format( wxS( "unresolved %s sheet reference" ), aObjectClass ) );
4257 }
4258 }
4259
4260
4261 void validateIdentity( const PADS_SCH_MODEL& aModel )
4262 {
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 );
4270
4271 std::vector<bool> sheetIndexes( aModel.sheets.size() );
4272
4273 for( const MODEL_SHEET& sheet : aModel.sheets )
4274 {
4275 if( sheet.index >= sheetIndexes.size() )
4276 throwValidationError( sheet.source, wxS( "sheet source index leaves the declared sheet range" ) );
4277
4278 if( sheetIndexes[sheet.index] )
4279 throwValidationError( sheet.source, wxS( "duplicate sheet source index" ) );
4280
4281 sheetIndexes[sheet.index] = true;
4282 }
4283
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;
4287
4288 for( const MODEL_PART_TYPE& partType : aModel.partTypes )
4289 {
4290 for( const MODEL_GATE& gate : partType.gates )
4291 validateNestedId( gate, wxS( "gate" ), gateDeclarations );
4292 }
4293
4294 for( const MODEL_SYMBOL_DEFINITION& definition : aModel.definitions )
4295 {
4296 for( const MODEL_PIN_DEFINITION& pin : definition.pins )
4297 validateNestedId( pin, wxS( "pin" ), pinDeclarations );
4298
4299 for( const MODEL_FIELD& field : definition.fields )
4300 validateNestedId( field, wxS( "field" ), fieldDeclarations );
4301 }
4302
4303 for( const MODEL_SHEET& sheet : aModel.sheets )
4304 {
4305 for( const MODEL_FIELD& field : sheet.titleBlockFields )
4306 validateNestedId( field, wxS( "field" ), fieldDeclarations );
4307 }
4308
4309 for( const MODEL_PART_TYPE& partType : aModel.partTypes )
4310 {
4311 for( const MODEL_FIELD& field : partType.fields )
4312 validateNestedId( field, wxS( "field" ), fieldDeclarations );
4313 }
4314
4315 for( const MODEL_PLACEMENT& placement : aModel.placements )
4316 {
4317 for( const MODEL_FIELD& field : placement.fields )
4318 validateNestedId( field, wxS( "field" ), fieldDeclarations );
4319 }
4320 }
4321
4322
4323 void validateSymbolGraph( const PADS_SCH_MODEL& aModel, const MODEL_REFERENCE_INDEX& aIndex )
4324 {
4325 for( const MODEL_SHEET& sheet : aModel.sheets )
4326 {
4327 if( sheet.parent && !aIndex.sheets.contains( sheet.parent->id.Value() ) )
4328 throwValidationError( sheet.parent->source, wxS( "unresolved sheet reference" ) );
4329 }
4330
4331 for( const MODEL_PART_TYPE& partType : aModel.partTypes )
4332 {
4333 for( const MODEL_GATE& gate : partType.gates )
4334 {
4335 auto definition = aIndex.definitions.find( gate.definition.id.Value() );
4336
4337 if( definition == aIndex.definitions.end() && gate.decalGroupMembers.empty() )
4338 throwValidationError( gate.definition.source, wxS( "unresolved symbol definition reference" ) );
4339
4340 for( const DEFINITION_REFERENCE& alternate : gate.alternateDefinitions )
4341 {
4342 if( !aIndex.definitions.contains( alternate.id.Value() ) )
4343 throwValidationError( alternate.source, wxS( "unresolved alternate definition reference" ) );
4344 }
4345
4346 for( const DEFINITION_REFERENCE& member : gate.decalGroupMembers )
4347 {
4348 if( !aIndex.definitions.contains( member.id.Value() ) )
4349 throwValidationError( member.source, wxS( "unresolved pin-decal group member" ) );
4350 }
4351
4352 if( definition == aIndex.definitions.end() )
4353 continue;
4354
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" ) );
4357
4358 for( size_t pinOrdinal = 0; pinOrdinal < gate.pins.size(); ++pinOrdinal )
4359 {
4360 const PIN_REFERENCE& pin = gate.pins[pinOrdinal];
4361 auto pinOwner = aIndex.pinOwners.find( pin.id.Value() );
4362
4363 if( pinOwner == aIndex.pinOwners.end() || pinOwner->second != definition->second )
4364 throwValidationError( pin.source, wxS( "pin does not belong to gate definition" ) );
4365
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" ) );
4369 }
4370 }
4371 }
4372
4373 struct DEFINITION_EDGE
4374 {
4375 uint32_t target;
4376 SOURCE_PROVENANCE source;
4377 };
4378
4379 std::unordered_map<uint32_t, std::vector<DEFINITION_EDGE>> definitionEdges;
4380
4381 for( const MODEL_PART_TYPE& partType : aModel.partTypes )
4382 {
4383 for( const MODEL_GATE& gate : partType.gates )
4384 {
4385 if( !gate.definition.id.IsValid() )
4386 continue;
4387
4388 for( const DEFINITION_REFERENCE& alternate : gate.alternateDefinitions )
4389 definitionEdges[gate.definition.id.Value()].push_back( { alternate.id.Value(), alternate.source } );
4390 }
4391 }
4392
4393 std::unordered_map<uint32_t, uint8_t> definitionColors;
4394 std::function<void( uint32_t )> visitDefinition =
4395 [&]( uint32_t aDefinition )
4396 {
4397 definitionColors[aDefinition] = 1;
4398
4399 auto edges = definitionEdges.find( aDefinition );
4400
4401 if( edges == definitionEdges.end() )
4402 {
4403 definitionColors[aDefinition] = 2;
4404 return;
4405 }
4406
4407 for( const DEFINITION_EDGE& edge : edges->second )
4408 {
4409 if( definitionColors[edge.target] == 1 )
4410 throwValidationError( edge.source, wxS( "cyclic symbol definition reference" ) );
4411
4412 if( definitionColors[edge.target] == 0 )
4413 visitDefinition( edge.target );
4414 }
4415
4416 definitionColors[aDefinition] = 2;
4417 };
4418
4419 for( const auto& [definition, edges] : definitionEdges )
4420 {
4421 if( definitionColors[definition] == 0 )
4422 visitDefinition( definition );
4423 }
4424 }
4425
4426
4427 void validatePlacements( const PADS_SCH_MODEL& aModel, const MODEL_REFERENCE_INDEX& aIndex )
4428 {
4429 for( const MODEL_PLACEMENT& placement : aModel.placements )
4430 {
4431 if( !aIndex.sheets.contains( placement.sheet.id.Value() ) )
4432 throwValidationError( placement.sheet.source, wxS( "unresolved placement sheet reference" ) );
4433
4434 auto partType = aIndex.partTypes.find( placement.partType.id.Value() );
4435
4436 if( partType == aIndex.partTypes.end() )
4437 throwValidationError( placement.partType.source, wxS( "unresolved placement part-type reference" ) );
4438
4439 auto placementDefinition = aIndex.definitions.find( placement.definition.id.Value() );
4440
4441 if( placementDefinition == aIndex.definitions.end() )
4442 throwValidationError( placement.definition.source, wxS( "unresolved placement definition" ) );
4443
4444 for( const PIN_REFERENCE& pin : placement.pins )
4445 {
4446 auto owner = aIndex.pinOwners.find( pin.id.Value() );
4447
4448 if( owner == aIndex.pinOwners.end() || owner->second != placementDefinition->second )
4449 throwValidationError( pin.source, wxS( "placement pin does not belong to selected definition" ) );
4450 }
4451
4452 if( !placement.gate )
4453 continue;
4454
4455 auto gate = aIndex.gates.find( placement.gate->id.Value() );
4456 auto gateOwner = aIndex.gateOwners.find( placement.gate->id.Value() );
4457
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" ) );
4462
4463 const MODEL_GATE& selectedGate = *gate->second;
4464 const bool definitionMatches = selectedGate.definition.id == placement.definition.id
4465 || std::ranges::any_of( selectedGate.alternateDefinitions,
4466 [&]( const DEFINITION_REFERENCE& aDefinition )
4467 {
4468 return aDefinition.id == placement.definition.id;
4469 } )
4470 || std::ranges::any_of( selectedGate.decalGroupMembers,
4471 [&]( const DEFINITION_REFERENCE& aDefinition )
4472 {
4473 return aDefinition.id == placement.definition.id;
4474 } );
4475
4476 if( !definitionMatches )
4477 throwValidationError( placement.definition.source,
4478 wxS( "placement definition does not belong to selected gate" ) );
4479
4480 auto definition = aIndex.definitions.find( placement.definition.id.Value() );
4481
4482 if( definition == aIndex.definitions.end() )
4483 throwValidationError( placement.definition.source, wxS( "unresolved placement definition" ) );
4484
4485 for( const PIN_REFERENCE& pin : placement.pins )
4486 {
4487 auto owner = aIndex.pinOwners.find( pin.id.Value() );
4488
4489 if( owner == aIndex.pinOwners.end() || owner->second != definition->second )
4490 throwValidationError( pin.source, wxS( "placement pin does not belong to selected definition" ) );
4491 }
4492 }
4493 }
4494
4495
4496 void validateSheetBoundContent( const PADS_SCH_MODEL& aModel, const MODEL_REFERENCE_INDEX& aIndex )
4497 {
4498 for( const MODEL_NET& net : aModel.nets )
4499 {
4500 validateSheetRef( net, aIndex, wxS( "net" ) );
4501
4502 for( const MODEL_CONNECTION& connection : net.connections )
4503 {
4504 if( connection.endpoints.empty() )
4505 throwValidationError( connection.source, wxS( "connection has no endpoints" ) );
4506
4507 for( const MODEL_CONNECTION_ENDPOINT& endpoint : connection.endpoints )
4508 {
4509 if( !endpointIsValid( aIndex, endpoint ) )
4510 {
4511 throwValidationError( endpoint.source,
4512 wxS( "empty, mixed, or unresolved connection endpoint" ) );
4513 }
4514
4515 if( !endpoint.placement )
4516 continue;
4517
4518 auto placement = aIndex.placements.find( endpoint.placement->id.Value() );
4519
4520 if( placement != aIndex.placements.end() && placement->second->sheet.id != net.sheet.id )
4521 {
4522 throwValidationError( endpoint.source,
4523 wxS( "connection endpoint placement sheet does not match net sheet" ) );
4524 }
4525 }
4526 }
4527 }
4528
4529 for( const MODEL_BUS& bus : aModel.buses )
4530 {
4531 validateSheetRef( bus, aIndex, wxS( "bus" ) );
4532
4533 for( const NET_REFERENCE& member : bus.memberNets )
4534 {
4535 auto net = aIndex.nets.find( member.id.Value() );
4536
4537 if( net == aIndex.nets.end() )
4538 throwValidationError( member.source, wxS( "unresolved bus member-net reference" ) );
4539
4540 if( net->second->sheet.id != bus.sheet.id )
4541 throwValidationError( member.source, wxS( "bus member-net sheet does not match bus sheet" ) );
4542 }
4543
4544 for( const MODEL_BUS_ENTRY& entry : bus.entries )
4545 {
4546 auto net = aIndex.nets.find( entry.memberNet.id.Value() );
4547
4548 if( net == aIndex.nets.end() )
4549 throwValidationError( entry.memberNet.source, wxS( "unresolved bus-entry net reference" ) );
4550
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" ) );
4554
4555 if( std::ranges::none_of( bus.memberNets,
4556 [&]( const NET_REFERENCE& aMember )
4557 {
4558 return aMember.id == entry.memberNet.id;
4559 } ) )
4560 {
4561 throwValidationError( entry.source, wxS( "bus-entry net is absent from bus member nets" ) );
4562 }
4563 }
4564 }
4565
4566 for( const MODEL_LABEL& label : aModel.labels )
4567 {
4568 validateSheetRef( label, aIndex, wxS( "label" ) );
4569
4570 for( const SHEET_REFERENCE& linkedSheet : label.linkedSheets )
4571 {
4572 if( !aIndex.sheets.contains( linkedSheet.id.Value() ) )
4573 throwValidationError( linkedSheet.source, wxS( "unresolved cross-sheet label reference" ) );
4574 }
4575 }
4576
4577 for( const MODEL_JUNCTION& junction : aModel.junctions )
4578 validateSheetRef( junction, aIndex, wxS( "junction" ) );
4579
4580 for( const MODEL_TEXT& text : aModel.texts )
4581 validateSheetRef( text, aIndex, wxS( "text" ) );
4582
4583 for( const MODEL_PAGE_GRAPHIC& graphic : aModel.graphics )
4584 validateSheetRef( graphic, aIndex, wxS( "page-graphic" ) );
4585
4586 std::set<uint32_t> worksheetSheets;
4587
4588 for( const MODEL_WORKSHEET& worksheet : aModel.worksheets )
4589 {
4590 validateSheetRef( worksheet, aIndex, wxS( "worksheet" ) );
4591
4592 if( !worksheetSheets.insert( worksheet.sheet.id.Value() ).second )
4593 throwValidationError( worksheet.source, wxS( "duplicate worksheet for sheet" ) );
4594
4595 if( worksheet.graphics.empty() )
4596 throwValidationError( worksheet.source, wxS( "worksheet has no graphics" ) );
4597 }
4598
4599 for( const MODEL_EMBEDDED_IMAGE& image : aModel.images )
4600 {
4601 validateSheetRef( image, aIndex, wxS( "embedded-image" ) );
4602
4603 if( image.type != MODEL_EMBEDDED_IMAGE_TYPE::UNSUPPORTED && image.data.empty() )
4604 throwValidationError( image.source, wxS( "embedded image has no decoded payload" ) );
4605 }
4606 }
4607
4608
4609 void validateSheetHierarchy( const PADS_SCH_MODEL& aModel, const MODEL_REFERENCE_INDEX& aIndex )
4610 {
4611 enum class VISIT_STATE : uint8_t
4612 {
4613 VISITING,
4614 COMPLETE
4615 };
4616 std::unordered_map<uint32_t, VISIT_STATE> visitStates;
4617
4618 for( const MODEL_SHEET& sheet : aModel.sheets )
4619 {
4620 const MODEL_SHEET* current = &sheet;
4621 std::vector<const MODEL_SHEET*> path;
4622
4623 while( current && !visitStates.contains( current->id.Value() ) )
4624 {
4625 visitStates.emplace( current->id.Value(), VISIT_STATE::VISITING );
4626 path.push_back( current );
4627
4628 if( !current->parent )
4629 {
4630 current = nullptr;
4631 break;
4632 }
4633
4634 auto parent = aIndex.sheets.find( current->parent->id.Value() );
4635
4636 if( parent == aIndex.sheets.end() )
4637 break;
4638
4639 current = parent->second;
4640 }
4641
4642 if( current && visitStates.at( current->id.Value() ) == VISIT_STATE::VISITING )
4643 throwValidationError( current->source, wxS( "cyclic sheet hierarchy" ) );
4644
4645 for( const MODEL_SHEET* visited : path )
4646 visitStates[visited->id.Value()] = VISIT_STATE::COMPLETE;
4647 }
4648 }
4649
4650
4651 std::vector<MODEL_FIELD> decodeDesignSettings( const std::vector<uint8_t>& aBytes,
4652 const PADS_IO::BINARY_CURSOR& aCursor, const PADS_SCH_SDB& aSdb,
4653 const wxString& aSourceName, PADS_SCH_MODEL& aModel )
4654 {
4655 const SCH_SDB_POOL& sheetPool = aSdb.Pools()[3];
4656
4657 if( sheetPool.usedBytes != sheetPool.count * SHEET_RECORD_BYTES )
4658 {
4659 SOURCE_PROVENANCE source =
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" ) );
4663 }
4664
4665 const SCH_SDB_POOL& settingsPool = aSdb.Pools()[5];
4666
4667 if( settingsPool.count != 100 || settingsPool.usedBytes != 400 )
4668 {
4669 SOURCE_PROVENANCE source =
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" ) );
4673 }
4674
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;
4679
4680 uint8_t pageDesignator = 0;
4681
4682 if( std::equal( aBytes.begin() + settingsOffset + 264, aBytes.begin() + settingsOffset + 268, "SIZE" )
4683 && aBytes[settingsOffset + 269] == 0 )
4684 {
4685 pageDesignator = aBytes[settingsOffset + 268];
4686 }
4687 else if( std::equal( aBytes.begin() + settingsOffset + 264, aBytes.begin() + settingsOffset + 273, "WDITBSIZE" )
4688 && aBytes[settingsOffset + 274] == 0 )
4689 {
4690 pageDesignator = aBytes[settingsOffset + 273];
4691 }
4692 else
4693 {
4694 SOURCE_PROVENANCE source = settingsSource;
4695 source.absoluteOffset += 264;
4696 source.length = 11;
4697 throwDecodeError( source, wxS( "invalid design page-size field" ) );
4698 }
4699
4700 SOURCE_PROVENANCE pageSource = settingsSource;
4701 pageSource.absoluteOffset += 264;
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;
4706
4707 std::vector<MODEL_FIELD> titleFields;
4708 const SCH_SDB_POOL& titleFieldPool = aSdb.Pools()[1];
4709 size_t titleOffset = outerControllerOffset( aSdb, 1 );
4710 const size_t titleEnd = titleOffset + titleFieldPool.usedBytes;
4711 size_t titleRecord = 0;
4712
4713 while( titleOffset + 6 <= titleEnd
4714 && std::equal( aBytes.begin() + titleOffset, aBytes.begin() + titleOffset + 6, "Field\n" ) )
4715 {
4716 size_t terminator = titleOffset;
4717
4718 while( terminator < titleEnd && aBytes[terminator] != 0 )
4719 ++terminator;
4720
4721 SOURCE_PROVENANCE source = sourceAt( aSourceName, aModel.version, wxS( "title field" ), 1, titleRecord,
4722 titleOffset, terminator - titleOffset + 1, -1 );
4723
4724 size_t separator = titleOffset + 6;
4725
4726 while( separator < terminator && aBytes[separator] != 1 )
4727 ++separator;
4728
4729 if( terminator == titleEnd || terminator - titleOffset < 7 || separator == terminator
4730 || !std::equal( aBytes.begin() + titleOffset, aBytes.begin() + titleOffset + 6, "Field\n" ) )
4731 {
4732 throwDecodeError( source, wxS( "invalid title-field name record" ) );
4733 }
4734
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 );
4737 SOURCE_PROVENANCE nameSource = source;
4738 nameSource.absoluteOffset += 6;
4739 nameSource.length = nameBytes.size();
4740 SOURCE_PROVENANCE valueSource = source;
4741 valueSource.absoluteOffset = separator + 1;
4742 valueSource.length = valueBytes.size();
4743 MODEL_FIELD field;
4744 field.source = source;
4745 field.name = PADS_SCH_BINARY_PARSER::DecodeString( nameBytes, DEFAULT_CODE_PAGE, nameSource,
4746 aModel.diagnostics );
4747 field.value = PADS_SCH_BINARY_PARSER::DecodeString( valueBytes, DEFAULT_CODE_PAGE, valueSource,
4748 aModel.diagnostics );
4749 field.presentation.source = source;
4750 titleFields.push_back( std::move( field ) );
4751 titleOffset = terminator + 1;
4752 ++titleRecord;
4753 }
4754
4755 if( titleFields.empty() )
4756 {
4757 SOURCE_PROVENANCE source =
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" ) );
4761 }
4762
4763 return titleFields;
4764 }
4765
4766
4767 void decodeSheets( const std::vector<uint8_t>& aBytes, const PADS_IO::BINARY_CURSOR& aCursor,
4768 const PADS_SCH_SDB& aSdb, const wxString& aSourceName,
4769 const std::vector<MODEL_FIELD>& aTitleFields, PADS_SCH_MODEL& aModel )
4770 {
4771 const size_t sheetIndexOffset = outerControllerOffset( aSdb, 3 );
4772 auto sheetBlocks = aSdb.Blocks()
4773 | std::views::filter(
4774 []( const SCH_SDB_BLOCK& aBlock )
4775 {
4776 return aBlock.kind == SCH_SDB_BLOCK_KIND::SHEET;
4777 } );
4778 auto sheetBlock = sheetBlocks.begin();
4779
4780 for( size_t index = 0; index < aSdb.Pools()[3].count; ++index, ++sheetBlock )
4781 {
4782 size_t recordOffset = sheetIndexOffset + index * SHEET_RECORD_BYTES;
4783 SOURCE_PROVENANCE provenance = sourceAt( aSourceName, aModel.version, wxS( "sheet" ), 3, index,
4784 recordOffset, SHEET_RECORD_BYTES, static_cast<int>( index ) );
4785
4786 if( sheetBlock == sheetBlocks.end() || aCursor.U32At( recordOffset ) != sheetBlock->offset
4787 || aCursor.U32At( recordOffset + 4 ) != sheetBlock->bytes )
4788 {
4789 throwDecodeError( provenance, wxS( "sheet-index record references the wrong SDB object class" ) );
4790 }
4791
4792 constexpr size_t nameOffset = 14;
4793
4794 if( aCursor.U16At( recordOffset + 10 ) != 0xFFFF || aCursor.U16At( recordOffset + 12 ) != 0xFFFF )
4795 throwDecodeError( provenance, wxS( "invalid sheet-index class marker" ) );
4796
4797 size_t nameEnd = recordOffset + nameOffset;
4798
4799 while( nameEnd < recordOffset + SHEET_RECORD_BYTES && aBytes[nameEnd] != 0 )
4800 ++nameEnd;
4801
4802 if( nameEnd == recordOffset + SHEET_RECORD_BYTES )
4803 throwDecodeError( provenance, wxS( "unterminated sheet name" ) );
4804
4805 std::vector<uint8_t> nameBytes( aBytes.begin() + recordOffset + nameOffset, aBytes.begin() + nameEnd );
4806 SOURCE_PROVENANCE nameSource = provenance;
4807 nameSource.absoluteOffset += nameOffset;
4808 nameSource.length = nameBytes.size();
4809
4810 MODEL_SHEET sheet;
4811 sheet.id = SHEET_ID( aCursor.U16At( recordOffset + 8 ) );
4812 sheet.index = sheet.id.IsValid() ? sheet.id.Value() - 1 : std::numeric_limits<size_t>::max();
4813 sheet.source = provenance;
4814 sheet.name = PADS_SCH_BINARY_PARSER::DecodeString( nameBytes, DEFAULT_CODE_PAGE, nameSource,
4815 aModel.diagnostics );
4816
4817 if( sheet.name.text.empty() )
4818 sheet.name.text = wxS( "$$$NONE" );
4819
4820 sheet.pageSize = aModel.settings.pageSize;
4821 sheet.defaultLineWidth = aModel.settings.defaultLineWidth;
4822 sheet.defaultBusWidth = aModel.settings.defaultBusWidth;
4823 sheet.titleBlockFields = aTitleFields;
4824
4825 auto title = std::ranges::find_if( sheet.titleBlockFields,
4826 []( const MODEL_FIELD& aField )
4827 {
4828 return aField.name.text == wxS( "Title" );
4829 } );
4830
4831 if( title != sheet.titleBlockFields.end() )
4832 sheet.title = title->value;
4833
4834 aModel.sheets.push_back( std::move( sheet ) );
4835 }
4836 }
4837
4838
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,
4842 PADS_SCH_MODEL& aModel )
4843 {
4844 for( size_t record = 0; record < aTextCount; ++record )
4845 {
4846 size_t recordOffset = aTextBase + record * TEXT_RECORD_BYTES;
4847
4848 if( aCursor.U16At( recordOffset + 24 ) != aCursor.U16At( recordOffset + 26 ) )
4849 continue;
4850
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 );
4855
4856 if( stringOffset > aHeapBytes )
4857 {
4858 SOURCE_PROVENANCE offsetSource = textSource;
4859 offsetSource.absoluteOffset += 8;
4860 offsetSource.length = 4;
4861 throwDecodeError( offsetSource, wxS( "free-text string offset leaves controller 2" ) );
4862 }
4863
4864 if( stringBytes == 0 || stringBytes > aHeapBytes - stringOffset )
4865 {
4866 SOURCE_PROVENANCE lengthSource = textSource;
4867 lengthSource.absoluteOffset += 20;
4868 lengthSource.length = 2;
4869 throwDecodeError( lengthSource, wxS( "free-text string length leaves controller 2" ) );
4870 }
4871
4872 if( aBytes[aHeapBase + stringOffset + stringBytes - 1] != 0 )
4873 {
4874 SOURCE_PROVENANCE terminatorSource =
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" ) );
4878 }
4879
4880 SOURCE_PROVENANCE stringSource =
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 );
4886
4888 text.source = textSource;
4889 text.sheet = { aModel.sheets[aSheetIndex].id, textSource };
4890 text.text =
4891 PADS_SCH_BINARY_PARSER::DecodeString( string, DEFAULT_CODE_PAGE, stringSource, aModel.diagnostics );
4892 text.position = { decodeCoordinate( aCursor.U16At( recordOffset + 12 ) ),
4893 decodeCoordinate( aCursor.U16At( recordOffset + 14 ) ), textSource };
4894 text.angle = NormalizeAngle( aCursor.U16At( recordOffset + 16 ) );
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 );
4902
4903 SOURCE_PROVENANCE fontHandleSource = textSource;
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 );
4908
4909 SOURCE_PROVENANCE displaySource = textSource;
4910 displaySource.absoluteOffset += 31;
4911 displaySource.length = 1;
4912 text.presentation.properties.push_back( sourceProperty(
4913 wxS( "display_flags" ), wxString::Format( wxS( "%u" ), displayFlags ), displaySource ) );
4914
4915 SOURCE_PROVENANCE relationshipSource = textSource;
4916 relationshipSource.objectClass = wxS( "free text relationship" );
4917 relationshipSource.absoluteOffset += 28;
4918 relationshipSource.length = 2;
4919 uint16_t relationship = aCursor.U16At( relationshipSource.absoluteOffset );
4920 SOURCE_PROPERTY relationshipProperty;
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 );
4926 relationshipProperty.value.encoding = STRING_ENCODING_STATUS::CODE_PAGE;
4927 relationshipProperty.value.source = relationshipSource;
4928 relationshipProperty.value.codePage = DEFAULT_CODE_PAGE;
4929 relationshipProperty.value.codePageName = wxS( "windows-1252" );
4930 relationshipProperty.disposition = PROPERTY_DISPOSITION::PRESERVED;
4931 relationshipProperty.source = relationshipSource;
4932 text.properties.push_back( std::move( relationshipProperty ) );
4933 aModel.texts.push_back( std::move( text ) );
4934 }
4935 }
4936
4937
4938 void decodeSheetBlocks( const std::vector<uint8_t>& aBytes, const PADS_IO::BINARY_CURSOR& aCursor,
4939 const PADS_SCH_SDB& aSdb, const wxString& aSourceName, PADS_SCH_MODEL& aModel )
4940 {
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 );
4945
4946 if( placementSchema.decoded )
4947 {
4948 placementData = placementGlobals( aSdb, aSourceName );
4949 connectivityData = connectivityGlobals( aBytes, aCursor, aSdb, aSourceName, aModel );
4950 }
4951
4952 for( const SCH_SDB_BLOCK& block : aSdb.Blocks() )
4953 {
4954 if( block.kind != SCH_SDB_BLOCK_KIND::SHEET )
4955 continue;
4956
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 );
4962
4963 SOURCE_PROVENANCE controllerSource = sourceAt( aSourceName, aModel.version, wxS( "text controller" ), 1, 0,
4964 payload, textBytes, static_cast<int>( sheetIndex ) );
4965
4966 if( textBytes != textCount * TEXT_RECORD_BYTES )
4967 throwDecodeError( controllerSource,
4968 wxS( "text-controller byte count does not match 32-byte records" ) );
4969
4970 size_t heapOffset = payload + textBytes;
4971
4972 if( !placementSchema.decoded )
4973 {
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(
4977 { controllerSource,
4979 { aBytes.begin() + payload, aBytes.begin() + payload + textBytes } } );
4980 aModel.preservedControllerPayloads.push_back(
4981 { heapSource,
4983 { aBytes.begin() + heapOffset, aBytes.begin() + heapOffset + heapBytes } } );
4984 decodeDefinitionsAndParts( aBytes, aCursor, block, sheetIndex, aSourceName, aModel );
4985 ++sheetIndex;
4986 continue;
4987 }
4988
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 );
4994 ++sheetIndex;
4995 }
4996 }
4997
4998
4999 void decodeOleImages( const std::vector<uint8_t>& aBytes, const PADS_SCH_SDB& aSdb, const wxString& aSourceName,
5000 PADS_SCH_MODEL& aModel )
5001 {
5002 for( size_t index = 0; index < aSdb.OleItems().size(); ++index )
5003 {
5004 const SCH_SDB_OLE_ITEM& item = aSdb.OleItems()[index];
5005 SOURCE_PROVENANCE source =
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 );
5010 image.id = IMAGE_ID( index );
5011 image.source = source;
5012 image.sheet = { aModel.sheets[item.sheetPlane].id, source };
5013 image.streamName = wxString::FromUTF8( payload.streamName );
5014 image.extent = item.extent;
5015 image.databaseBox = { item.left, item.bottom, item.right, item.top };
5016 SOURCE_PROVENANCE boxSource = source;
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 );
5024
5025 image.position = { left + ( right - left ) / 2, top + ( bottom - top ) / 2, boxSource };
5026 image.size = { std::abs( right - left ), std::abs( bottom - top ), boxSource };
5027 image.mirrorHorizontal = right < left;
5028 image.mirrorVertical = bottom < top;
5029 image.flags = item.flags;
5030 image.data = std::move( payload.data );
5031
5032 switch( payload.type )
5033 {
5039 aModel.diagnostics.emplace_back(
5040 RPT_SEVERITY_WARNING, source,
5041 wxS( "embedded OLE object has no supported BMP, DIB, or WMF stream" ) );
5042 break;
5043 }
5044
5045 if( right == left || bottom == top )
5046 {
5048 aModel.diagnostics.emplace_back(
5049 RPT_SEVERITY_WARNING, boxSource,
5050 wxS( "embedded OLE image has a zero-size database box and was skipped" ) );
5051 }
5052
5053 if( item.flags != 1 )
5054 {
5055 SOURCE_PROVENANCE flagsSource = source;
5056 flagsSource.objectClass = wxS( "embedded OLE image flags" );
5057 flagsSource.absoluteOffset = item.boxOffset + 20;
5058 flagsSource.length = 4;
5059 PADS_SCH_BINARY_PARSER::RecordUnknownEnum( wxS( "embedded OLE image flags" ), item.flags, flagsSource,
5060 aModel.diagnostics );
5061 }
5062
5063 aModel.images.push_back( std::move( image ) );
5064 }
5065 }
5066
5067
5068} // namespace
5069
5070
5071wxString FormatParserError( const SOURCE_PROVENANCE& aSource, const wxString& aMessage )
5072{
5073 return wxString::Format(
5074 wxS( "%s: PADS schematic v0x%04X %s (controller %d, record %llu, sheet %d) at offset 0x%llX: %s" ),
5075 aSource.file, aSource.version, aSource.objectClass, aSource.controller,
5076 static_cast<unsigned long long>( aSource.recordIndex ), aSource.sheet,
5077 static_cast<unsigned long long>( aSource.absoluteOffset ), aMessage );
5078}
5079
5080
5082{
5083 return std::tie( source.file, source.version, source.objectClass, source.controller, source.recordIndex,
5084 source.absoluteOffset, source.length, source.sheet, property.name, property.disposition )
5085 < std::tie( aOther.source.file, aOther.source.version, aOther.source.objectClass, aOther.source.controller,
5086 aOther.source.recordIndex, aOther.source.absoluteOffset, aOther.source.length,
5087 aOther.source.sheet, aOther.property.name, aOther.property.disposition );
5088}
5089
5090
5091std::optional<DIAGNOSTIC_PROPERTY_KEY> DiagnosticPropertyKey( const PARSER_DIAGNOSTIC& aDiagnostic )
5092{
5093 if( !aDiagnostic.property )
5094 return std::nullopt;
5095
5096 return DIAGNOSTIC_PROPERTY_KEY{ aDiagnostic.source, *aDiagnostic.property };
5097}
5098
5099
5101 const wxString& aMessage )
5102{
5103 return MakePropertyDiagnostic( aSeverity, aProperty.source, aProperty.name.text, aProperty.disposition, aMessage );
5104}
5105
5106
5107PARSER_DIAGNOSTIC MakePropertyDiagnostic( SEVERITY aSeverity, const SOURCE_PROVENANCE& aSource, const wxString& aName,
5108 PROPERTY_DISPOSITION aDisposition, const wxString& aMessage )
5109{
5110 return { aSeverity, aSource, aMessage, DIAGNOSTIC_PROPERTY_IDENTITY{ aName, aDisposition } };
5111}
5112
5113
5115{
5116 validateIdentity( *this );
5117
5118 const MODEL_REFERENCE_INDEX index( *this );
5119
5120 validateSymbolGraph( *this, index );
5121 validatePlacements( *this, index );
5122 validateSheetBoundContent( *this, index );
5123 validateSheetHierarchy( *this, index );
5124}
5125
5126
5127PADS_SCH_MODEL PADS_SCH_BINARY_PARSER::Parse( const std::vector<uint8_t>& aBytes, const wxString& aSourceName ) const
5128{
5129 PADS_SCH_SDB sdb;
5130 sdb.Load( aBytes );
5131
5132 const PADS_IO::BINARY_CURSOR& cursor = sdb.Cursor();
5134 model.version = sdb.Version();
5135 model.subversion = cursor.U16At( 6 );
5136 model.source = { aSourceName, model.version, wxS( "model" ), -1, 0, 0, aBytes.size(), -1 };
5137 model.settings.codePage = DEFAULT_CODE_PAGE;
5138
5139 const std::vector<MODEL_FIELD> titleFields = decodeDesignSettings( aBytes, cursor, sdb, aSourceName, model );
5140
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();
5146 return model;
5147}
5148
5149
5150SOURCE_STRING PADS_SCH_BINARY_PARSER::DecodeString( const std::vector<uint8_t>& aBytes, uint32_t aCodePage,
5151 const SOURCE_PROVENANCE& aSource,
5152 std::vector<PARSER_DIAGNOSTIC>& aDiagnostics,
5153 const wxString& aRecordedCodePageName )
5154{
5155 SOURCE_STRING result{ aBytes, {}, STRING_ENCODING_STATUS::UTF8, aSource };
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;
5161
5162 if( aCodePage == 1252 )
5164 else if( aCodePage != 65001 )
5166
5167 if( aCodePage != 65001 && aCodePage != 1252 )
5168 {
5169 aDiagnostics.push_back( { RPT_SEVERITY_WARNING, aSource,
5170 wxString::Format( wxS( "unknown code page %u; bytes preserved and "
5171 "non-ASCII bytes decoded as U+FFFD" ),
5172 aCodePage ) } );
5173 }
5174
5175 if( aBytes.empty() )
5176 return result;
5177
5178 if( aCodePage == 1252 )
5179 {
5180 result.text = decodeWindows1252( aBytes );
5181 return result;
5182 }
5183
5184 if( aCodePage != 65001 )
5185 {
5186 result.text = decodeUnknownCodePage( aBytes );
5187 return result;
5188 }
5189
5190 bool invalid = false;
5191 result.text = decodeUtf8( aBytes, invalid );
5192
5193 if( invalid )
5194 {
5196 aDiagnostics.push_back( { RPT_SEVERITY_WARNING, aSource,
5197 wxS( "invalid UTF-8 bytes replaced with U+FFFD; original bytes preserved" ) } );
5198 }
5199
5200 return result;
5201}
5202
5203
5204void PADS_SCH_BINARY_PARSER::RecordUnknownEnum( const wxString& aEnumName, uint32_t aValue,
5205 const SOURCE_PROVENANCE& aSource,
5206 std::vector<PARSER_DIAGNOSTIC>& aDiagnostics )
5207{
5208 aDiagnostics.push_back( { RPT_SEVERITY_WARNING, aSource,
5209 wxString::Format( wxS( "unknown %s %u preserved" ), aEnumName, aValue ) } );
5210}
5211
5212} // namespace PADS_SCH_BINARY
int index
const char * name
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
uint16_t Version() const
pinElectricalType
#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)
Definition base64.cpp:113
std::variant< double, std::string > Value
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
OLE_IMAGE_PAYLOAD ExtractOleImage(const uint8_t *aCfb, size_t aSize)
Prefer CONTENTS, then OlePres000, then the native stream.
SEVERITY
@ RPT_SEVERITY_WARNING
std::vector< uint8_t > data
Definition ole_image.h:48
std::string streamName
Definition ole_image.h:49
OLE_IMAGE_TYPE type
Definition ole_image.h:47
bool operator<(const DIAGNOSTIC_PROPERTY_KEY &aOther) const
SOURCE_PROVENANCE source
std::optional< DIAGNOSTIC_PROPERTY_IDENTITY > property
std::string path
KIBIS top(path, &reporter)
KIBIS_MODEL * model
KIBIS_PIN * pin
const SHAPE_LINE_CHAIN chain
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.