KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pads_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 (C) 2026 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "pads_binary_parser.h"
21
22#include <algorithm>
23#include <array>
24#include <cctype>
25#include <chrono>
26#include <cstdio>
27#include <cmath>
28#include <cstdlib>
29#include <cstring>
30#include <limits>
31#include <numbers>
32#include <numeric>
33#include <optional>
34#include <set>
35#include <unordered_set>
36
37#include <fmt/format.h>
38#include <geometry/eda_angle.h>
40#include <ki_exception.h>
41#include <wx/log.h>
42
43namespace PADS_IO
44{
45
46// Section 4 pad-shape codes. The finger shapes 0 (OF) and 1 (RF) carry a non-zero finLength
47// (size B); the round 2 (R) and square 3 (S) never do.
48static const std::map<uint8_t, std::string> PAD_SHAPE_NAMES = {
49 { 0x00, "OF" }, { 0x01, "RF" }, { 0x02, "R" }, { 0x03, "S" },
50 { 0x06, "RT" }, { 0x07, "ST" }, { 0x08, "RA" }, { 0x09, "SA" },
51};
52
53
54// Per-version field layout for the section-4 padstack records. As with the placements, the old
55// and new dialects carry the same geometry fields at different offsets.
57{
58 int padWidthOff = 0;
59 int drillOff = 0;
60 int finLenOff = 0;
61 int cornerOff = 0;
62 int angleOff = 0;
64 int markerOff = 0;
65 int shapeOff = 0;
67 int drillStartOff = -1;
68 int drillEndOff = -1;
69};
70
71
72static const PADSTACK_LAYOUT& padstackLayout( uint16_t aVersion )
73{
74 static constexpr PADSTACK_LAYOUT v2021{ .padWidthOff = 24,
75 .drillOff = 28,
76 .finLenOff = 32,
77 .cornerOff = 36,
78 .angleOff = 40,
79 .layerStartOff = 44,
80 .markerOff = 48,
81 .shapeOff = 49,
82 .layerCountOff = 50,
83 .drillStartOff = -1,
84 .drillEndOff = -1 };
85 static constexpr PADSTACK_LAYOUT v2022{ .padWidthOff = 20,
86 .drillOff = 24,
87 .finLenOff = 28,
88 .cornerOff = 32,
89 .angleOff = 40,
90 .layerStartOff = 44,
91 .markerOff = 48,
92 .shapeOff = 49,
93 .layerCountOff = 50,
94 .drillStartOff = 51,
95 .drillEndOff = 52 };
96 static constexpr PADSTACK_LAYOUT vNew{ .padWidthOff = 28,
97 .drillOff = 32,
98 .finLenOff = 36,
99 .cornerOff = 44,
100 .angleOff = 48,
101 .layerStartOff = 52,
102 .markerOff = 56,
103 .shapeOff = 57,
104 .layerCountOff = 58,
105 .drillStartOff = 59,
106 .drillEndOff = 60 };
107
108 if( aVersion <= 0x2021 )
109 return v2021;
110
111 if( aVersion == 0x2022 )
112 return v2022;
113
114 return vNew;
115}
116
117
118// The 112-byte header shared by the LINES/DRW owner and item records. Several decoders read the
119// same fields (origin, bounding box, run cursors, name, classifier word); one descriptor keeps
120// their offsets in a single place. The +24 word has two record-role-dependent meanings, both
121// named here: CLASS_WORD in the copper/keepout headers and PIECE_COUNT in the owner-run cursor.
122namespace DRW_ITEM
123{
124 constexpr size_t SIZE = 112;
125
126 // Ring rotation shared by every section-10 record access
127 constexpr size_t ROTATION = 68;
128
146} // namespace DRW_ITEM
147
148
149// PADS <= v2022 packs the same record into 100 bytes with its own name and origin offsets
151{
152 constexpr size_t SIZE = 100;
153 constexpr size_t NAME = 32;
154 constexpr size_t ORIGIN_X = 72;
155} // namespace DRW_ITEM_V2022
156
157
158// Block-class tags carried at DRW_ITEM::TYPE_TAG. 0x4D00 also marks the board-outline item and
159// is the library decal marker elsewhere.
160namespace DRW_TAG
161{
162 enum TAG : uint32_t
163 {
164 COPPER_FILL = 0x00004900, // filled copper region
165 COPPER_FILL_B = 0x0000FF00, // second filled-copper tag, same class/shape
166 LINE_ITEM = 0x00004D00 // line/outline item (board outline, v2026 line copper)
167 };
168} // namespace DRW_TAG
169
170
172
173
175
176
177bool BINARY_PARSER::IsBinaryPadsFile( const wxString& aFileName )
178{
179 std::vector<uint8_t> header;
180
181 if( !PADS_IO::ReadFileHeader( aFileName, header, 4 ) || header.size() < 4 )
182 return false;
183
184 if( !PADS_IO::HasSdbMagic( header, 0xFF ) )
185 return false;
186
187 return PADS_SDB::IsSupportedVersion( PADS_IO::getU16LE( header, 2 ) );
188}
189
190
192static void logParsePhase( const char* aWhat, std::chrono::steady_clock::time_point aStart )
193{
194 static const bool enabled = getenv( "KICAD_PADS_PROFILE" ) != nullptr;
195
196 if( !enabled )
197 return;
198
199 const double ms = std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - aStart ).count();
200
201 fputs( fmt::format( "PROF {:<32} {:>8.1f} ms\n", aWhat, ms ).c_str(), stderr );
202}
203
204
205void BINARY_PARSER::Parse( const wxString& aFileName )
206{
207 std::vector<uint8_t> bytes;
208
209 if( !PADS_IO::ReadFileToBuffer( aFileName, bytes ) )
210 THROW_IO_ERROR( "Cannot open or read file" );
211
212 m_sdb.Load( std::move( bytes ) );
213
214 m_version = m_sdb.Version();
215 m_originX = m_sdb.Coords().OriginX();
216 m_originY = m_sdb.Coords().OriginY();
217
218 m_parameters.origin.x = static_cast<double>( m_originX );
219 m_parameters.origin.y = static_cast<double>( m_originY );
220
221#define KITIME( call ) \
222 do \
223 { \
224 auto _t0 = std::chrono::steady_clock::now(); \
225 call; \
226 logParsePhase( #call, _t0 ); \
227 } while( 0 )
228
229 // The call order below is load-bearing; the dependency edges noted at each group fix it.
230
231 // Container state loads the version, origin and parameter block.
233
234 // Part placements and the part-cluster groups that reference them.
237
238 // Padstacks and the decal / part-type tables that linkPartsToDecals joins at the end.
243
244 // Sections 10 and 11 are circularly serialized fixed arrays. Reconstruct their direct
245 // owner/piece links before any graphic decoder consumes them.
248
251
252 // Net names first; the net-class and diff-pair passes key off the net records they produce.
256
258
259 // Route-cell coordinate order is selected by each layer's serialized routing direction.
261
262 // Structural geometry.
269
271
272#undef KITIME
273
274 m_parts.erase( std::remove_if( m_parts.begin(), m_parts.end(),
275 []( const PART& p )
276 {
277 return p.name.empty();
278 } ),
279 m_parts.end() );
280}
281
282
283const SDB_SECTION* BINARY_PARSER::getSection( int aIndex ) const
284{
285 return m_sdb.Section( aIndex );
286}
287
288
289double BINARY_PARSER::toBasicCoordX( int32_t aRawValue ) const
290{
291 return static_cast<double>( aRawValue );
292}
293
294
295double BINARY_PARSER::toBasicCoordY( int32_t aRawValue ) const
296{
297 return static_cast<double>( aRawValue );
298}
299
300
301double BINARY_PARSER::toBasicAngle( int32_t aRawAngle ) const
302{
303 if( aRawAngle == 0 )
304 return 0.0;
305
306 return static_cast<double>( aRawAngle ) / static_cast<double>( ANGLE_SCALE );
307}
308
309
311{
312 // MAXIMUMLAYER lives in the same directly framed *PCB* board-setup parameter block as the
313 // coordinate origin.
314 if( !m_sdb.Coords().Found() )
315 THROW_IO_ERROR( "Missing PADS board origin" );
316
317 uint32_t headerBase = m_sdb.Coords().HeaderBase();
318
319 if( !m_cursor.InBounds( headerBase, 20 ) )
320 THROW_IO_ERROR( "Invalid PADS board-setup parameter extent" );
321
322 // Word 4 of the fixed u32 parameter block is the maximum layer count.
323 uint32_t maxLayer = m_sdb.RecordAt( headerBase ).U32( 16 );
324
325 if( maxLayer >= 1 && maxLayer <= 64 )
326 m_parameters.layer_count = static_cast<int>( maxLayer );
327 else
328 THROW_IO_ERROR( "Invalid PADS maximum-layer field" );
329
330 // Binary coordinates are BASIC units (1/38100 mil). MILS is only the display unit;
331 // coordinate handling uses BASIC mode in the wrapper via SetBasicUnitsMode(true).
333}
334
335
336PART BINARY_PARSER::makePlacementPart( const SDB_RECORD& aRec, int aXOff, std::optional<int> aYOff, int aAngleOff,
337 int aNameOff, const std::string& aRefDes ) const
338{
339 PART part;
340 part.name = aRefDes;
341 part.location.x = toBasicCoordX( aRec.I32( aXOff ) );
342 part.location.y = aYOff ? toBasicCoordY( aRec.I32( *aYOff ) ) : 0;
343 part.rotation = toBasicAngle( aRec.I32( aAngleOff ) );
344
345 // The side flag is the word at nameOff+28 in both dialects; bit 0 marks a bottom placement.
346 part.bottom_layer = ( aRec.U8( aNameOff + 28 ) & 0x01 ) != 0;
347 part.units = "M";
348 return part;
349}
350
351
353{
354 const SDB_SECTION* section = getSection( SECTION::Placements );
355
356 constexpr size_t LOGICAL_ROTATION = 44;
357
358 if( !section )
359 THROW_IO_ERROR( "Missing PADS placement controller" );
360
361 if( section->count == 0 )
362 return;
363
364 if( section->physicalCount != section->count || section->physicalOffset < LOGICAL_ROTATION
365 || ( section->stride != 96 && section->stride != 112 ) )
366 THROW_IO_ERROR( "Invalid PADS placement-controller framing" );
367
368 const size_t logicalBase = section->physicalOffset - LOGICAL_ROTATION;
369
370 if( !m_cursor.InBounds( logicalBase, section->totalBytes + LOGICAL_ROTATION ) )
371 THROW_IO_ERROR( "Invalid PADS placement-controller extent" );
372
373 for( uint32_t index = 0; index < section->physicalCount; ++index )
374 {
375 const size_t base = logicalBase + static_cast<size_t>( index ) * section->stride;
376 SDB_RECORD record = m_sdb.RecordAt( static_cast<uint32_t>( base ) );
377 std::string refdes = record.Str( 44, 16 );
378 const size_t nextBase = logicalBase + static_cast<size_t>( index + 1 ) * section->stride;
379 SDB_RECORD nextRecord = m_sdb.RecordAt( static_cast<uint32_t>( nextBase ) );
380
381 PART part = makePlacementPart( record, 60, 64, 68, 44, refdes );
382
383 if( section->stride == 112 )
384 m_partFieldStart[m_parts.size()] = record.I32( 96 );
385
386 if( section->stride == 96 )
387 m_partDecalIndex[m_parts.size()] = nextRecord.U32( 24 );
388 else
389 {
390 m_partTypeIndex[m_parts.size()] = nextRecord.U32( 4 );
391 m_partDecalAlternate[m_parts.size()] = nextRecord.U8( 17 );
392 }
393
394 if( section->stride == 112 )
395 {
396 int32_t clusterId = record.I32( 108 );
397
398 if( clusterId > 0 )
399 m_partClusterId[m_parts.size()] = clusterId;
400 }
401
403 m_parts.push_back( std::move( part ) );
404 }
405}
406
408{
409 // A part cluster is a fixed 60-byte record, id@+0 and name@+4 (char[16], NUL-padded), in
410 // cluster order. The stored id and the record's 1-based ordinal both equal the CLSTID the
411 // +108 field references. Membership
412 // is captured during parsePartPlacements. The old 96-byte placement layout has no room for
413 // the +108 CLSTID, so old-format boards carry no clusters.
414 //
415 // Section 68 directly precedes section 69 in the physical loader stream.
416 if( isOldFormat() )
417 return;
418
419 const SDB_SECTION* sec68 = getSection( SECTION::Clusters );
420
421 if( !sec68 )
422 THROW_IO_ERROR( "Missing PADS cluster controller" );
423
424 if( sec68->count == 0 )
425 return;
426
427 if( sec68->stride != 60 )
428 THROW_IO_ERROR( "Invalid PADS cluster-controller stride" );
429
430 static constexpr size_t REC_SIZE = 60;
431 size_t sec69Rec0 = layerStackupBase();
432
433 if( sec69Rec0 == 0 )
434 THROW_IO_ERROR( "Missing PADS layer-stackup framing" );
435
436 constexpr size_t SEC69_LEAD_IN = 12;
437
438 if( sec69Rec0 < SEC69_LEAD_IN || sec68->count > ( sec69Rec0 - SEC69_LEAD_IN ) / REC_SIZE )
439 THROW_IO_ERROR( "Invalid PADS cluster-controller extent" );
440
441 size_t base = sec69Rec0 - SEC69_LEAD_IN - static_cast<size_t>( sec68->count ) * REC_SIZE;
442
443 for( uint32_t i = 0; i < sec68->count; ++i )
444 {
445 SDB_RECORD rec = m_sdb.RecordAt( base + i * REC_SIZE );
446 std::string name = rec.Str( 4, 16 );
447
448 // A misaligned base would read non-cluster bytes. The stored ordinal and printable name
449 // confirm the run without constraining retained state fields that vary among boards.
450 if( rec.U32( 0 ) != i + 1 || name.empty() )
451 THROW_IO_ERROR( "Invalid PADS cluster record" );
452
453 PART_CLUSTER cluster;
454 cluster.name = std::move( name );
455 cluster.id = static_cast<int>( i ) + 1;
456 m_clusters.push_back( std::move( cluster ) );
457 }
458}
459
460
462{
463 const SDB_SECTION* entry = getSection( SECTION::PadStacks );
464 const SDB_SECTION* layerSection = getSection( SECTION::PadShapes );
465
466 if( !entry || !layerSection )
467 THROW_IO_ERROR( "Missing PADS padstack controllers" );
468
469 const uint32_t expectedRecordSize = m_version <= 0x2021 ? 52 : m_version == 0x2022 ? 56 : 64;
470
471 if( entry->physicalCount != entry->count
472 || static_cast<uint64_t>( entry->count ) * expectedRecordSize != entry->totalBytes )
473 THROW_IO_ERROR( "Invalid PADS padstack-controller framing" );
474
475 if( entry->count == 0 )
476 return;
477
478 const uint32_t logicalRotation = m_version == 0x2022 ? 20 : m_version <= 0x2021 ? 24 : 28;
479
480 if( entry->physicalOffset < logicalRotation )
481 THROW_IO_ERROR( "Invalid PADS padstack-controller rotation" );
482
483 const PADSTACK_LAYOUT& layout = padstackLayout( m_version );
484 uint32_t recSize = entry->stride;
485
486 // Read one padstack record's default (layer 0) geometry. For finger pads (RF, OF, RC)
487 // finLength is the second dimension; round (R) and square (S) reuse sizeA.
488 auto readLayer = [&]( uint32_t aBase ) -> PAD_STACK_LAYER
489 {
490 SDB_RECORD rec = m_sdb.RecordAt( aBase );
491 std::string shapeName = "R";
492 auto shapeIt = PAD_SHAPE_NAMES.find( rec.U8( layout.shapeOff ) );
493
494 if( shapeIt != PAD_SHAPE_NAMES.end() )
495 shapeName = shapeIt->second;
496
497 int32_t padWidth = rec.I32( layout.padWidthOff );
498 int32_t drill = rec.I32( layout.drillOff );
499 int32_t finLength = rec.I32( layout.finLenOff );
500
501 PAD_STACK_LAYER psl;
502 psl.layer = -2;
503 psl.shape = shapeName;
504 psl.sizeA = static_cast<double>( padWidth );
505 psl.drill = static_cast<double>( drill );
506 psl.corner_radius = std::max( rec.I32( layout.cornerOff ), 0 );
507
508 if( m_version <= 0x2022 )
509 {
510 constexpr int OLD_NPTH_CLEARANCE = 4 * static_cast<int>( SDB_BASIC_PER_MIL );
511 psl.plated = drill > 0 && padWidth - drill > OLD_NPTH_CLEARANCE;
512 }
513 else
514 {
515 psl.plated = drill > 0 && drill < padWidth;
516 }
517
518 bool isFinger = ( shapeName == "RF" || shapeName == "OF" || shapeName == "RC" );
519
520 if( isFinger )
521 psl.rotation = toBasicAngle( rec.I32( layout.angleOff ) );
522
523 if( isFinger && finLength > 0 )
524 psl.sizeB = static_cast<double>( finLength );
525 else
526 psl.sizeB = static_cast<double>( padWidth );
527
528 return psl;
529 };
530
531 const uint32_t poolStart = entry->physicalOffset - logicalRotation;
532 const uint64_t poolBytes = static_cast<uint64_t>( entry->physicalCount ) * recSize;
533
534 if( !m_cursor.InBounds( poolStart, poolBytes ) )
535 THROW_IO_ERROR( "Invalid PADS padstack-pool extent" );
536
537 m_padStackPool.assign( entry->physicalCount, {} );
538 m_padStackDrillSpans.assign( entry->physicalCount, { 0, 0 } );
539 std::vector<int32_t> maxPadDiameter( entry->physicalCount, 0 );
540
541 const uint32_t layerRecordSize = m_version <= 0x2021 ? 20 : 24;
542 const uint32_t layerTableHeaderSize = m_version == 0x2022 ? 64 : m_version <= 0x2021 ? 20 : 24;
543 uint64_t layerTableStart64 = static_cast<uint64_t>( poolStart ) + poolBytes + layerTableHeaderSize;
544 const uint64_t layerTableBytes = static_cast<uint64_t>( layerSection->count ) * layerRecordSize;
545
546 if( layerTableBytes != layerSection->totalBytes || layerTableStart64 > m_data.size()
547 || layerTableBytes > m_data.size() - layerTableStart64 )
548 {
549 THROW_IO_ERROR( "Invalid PADS pad-layer-controller framing" );
550 }
551
552 const uint32_t layerTableStart = static_cast<uint32_t>( layerTableStart64 );
553
554 // Pad stacks are indexed by their position in the object array; part decals reference them by
555 // that stable index.
556 for( uint32_t i = 0; i < entry->physicalCount; ++i )
557 {
558 uint32_t base = poolStart + i * recSize;
559
560 if( m_sdb.RecordAt( base ).U8( layout.markerOff ) != 0xFE )
561 continue;
562
563 PAD_STACK_LAYER defaultLayer = readLayer( base );
564
565 // Slotted-drill metadata is carried by the following physical padstack record. Bit 3
566 // marks the carrier; +8 is the preceding stack's slot length and +12 its orientation.
567 if( m_version >= 0x2024 && defaultLayer.drill > 0 && i + 1 < entry->physicalCount )
568 {
569 SDB_RECORD nextRec = m_sdb.RecordAt( base + recSize );
570
571 if( ( nextRec.U32( 0 ) & 8U ) != 0 )
572 {
573 defaultLayer.slot_length = nextRec.I32( 8 );
574 defaultLayer.slot_orientation = toBasicAngle( nextRec.I32( 12 ) );
575 }
576 }
577
578 std::vector<PAD_STACK_LAYER>& layers = m_padStackPool[i];
579 layers.push_back( defaultLayer );
580 maxPadDiameter[i] = static_cast<int32_t>( defaultLayer.sizeA );
581
582 if( layout.drillStartOff >= 0 && layout.drillEndOff >= 0 )
583 {
584 m_padStackDrillSpans[i] = { m_cursor.U8At( base + layout.drillStartOff ),
585 m_cursor.U8At( base + layout.drillEndOff ) };
586 }
587
588 SDB_RECORD stackRec = m_sdb.RecordAt( base );
589 uint32_t layerStart = stackRec.U32( layout.layerStartOff );
590 uint8_t layerCount = stackRec.U8( layout.layerCountOff );
591 bool endCursor = m_version == 0x2022;
592 bool successorMetadata = endCursor || m_version == 0x2027;
593 bool rawLayerSelector = successorMetadata;
594
595 if( layerCount > 0
596 && ( layerSection->count == 0 || layerCount > layerSection->count
597 || ( endCursor
598 ? layerStart > layerSection->count
599 : layerStart >= layerSection->count || layerCount > layerSection->count - layerStart ) ) )
600 THROW_IO_ERROR( "Invalid PADS pad-layer range" );
601
602 if( layerCount > 0 )
603 {
604 const uint32_t controllerPhase = endCursor ? 2 % layerSection->count : 0;
605
606 for( uint32_t layerIdx = 0; layerIdx < layerCount; ++layerIdx )
607 {
608 // PADS 2022's controller cursor is two rows ahead of the first geometry row.
609 // Both observed dialects put selector/shape in each geometry row's successor.
610 uint32_t geometryIndex = endCursor ? ( layerStart + layerSection->count - controllerPhase + layerIdx )
611 % layerSection->count
612 : layerStart + layerIdx;
613
614 // v0x2022's table is a ring, so its successor wraps; without the modulo the last
615 // geometry row read past the table. v0x2027's successor is not a ring, and for the
616 // last row of the last padstack it lands on the section's exclusive end, which the
617 // framing check does not validate. Fall back to the row's own metadata there
618 // rather than read whatever section follows.
619 uint32_t metadataIndex = geometryIndex;
620
621 if( successorMetadata )
622 {
623 metadataIndex = endCursor ? ( geometryIndex + 1 ) % layerSection->count
624 : geometryIndex + 1;
625
626 if( metadataIndex >= layerSection->count
627 && !m_cursor.InBounds( layerTableStart + metadataIndex * layerRecordSize, layerRecordSize ) )
628 {
629 metadataIndex = geometryIndex;
630 }
631 }
632 uint32_t geometryBase = layerTableStart + geometryIndex * layerRecordSize;
633 uint32_t metadataBase = layerTableStart + metadataIndex * layerRecordSize;
634
635 SDB_RECORD geometryRec = m_sdb.RecordAt( geometryBase );
636 SDB_RECORD metadataRec = m_sdb.RecordAt( metadataBase );
637 uint8_t selector = metadataRec.U8( 0 );
638 uint8_t shapeCode = metadataRec.U8( 1 );
639 auto shapeIt = PAD_SHAPE_NAMES.find( shapeCode );
640 maxPadDiameter[i] = std::max( maxPadDiameter[i], geometryRec.I32( 4 ) );
641
642 if( shapeIt == PAD_SHAPE_NAMES.end() )
643 continue;
644
645 PAD_STACK_LAYER layer = defaultLayer;
646 layer.layer = selector == 0 ? 0
647 : selector == 0xFF ? -1
648 : static_cast<int>( selector ) + ( rawLayerSelector ? 0 : 1 );
649 layer.shape = shapeIt->second;
650 layer.sizeA = static_cast<double>( geometryRec.I32( 4 ) );
651 int32_t sizeB = geometryRec.I32( 8 );
652
653 if( layer.shape == "RT" || layer.shape == "ST" )
654 {
655 layer.sizeB = layer.sizeA;
656 layer.thermal_outer_diameter = std::max( sizeB, 0 );
657 }
658 else
659 {
660 layer.sizeB = sizeB > 0 ? static_cast<double>( sizeB ) : layer.sizeA;
661 }
662
663 layers.push_back( std::move( layer ) );
664 }
665 }
666 }
667}
668
669
671static constexpr int DECAL_NAME_OFFSET = 44;
672
675static void logResolvedBase( int aSection, const char* aWhat, size_t aResolved, uint32_t aPhysical )
676{
677 static const bool enabled = getenv( "KICAD_PADS_SECBASE" ) != nullptr;
678
679 if( !enabled )
680 return;
681
682 fprintf( stderr, "SECBASE %d %s resolved=%zu physical=%u\n", aSection, aWhat, aResolved, aPhysical );
683}
684
685
687{
688 // The complete decal-name table is the logical section-14 ring. Each record is 112 bytes
689 // with the decal NAME at the physical section cursor, a 0xFFFE
690 // sentinel at +64 and the terminal count at +72. Unlike section 10 this table includes vias,
691 // connectors and mounting holes, and is indexed directly (base 0) by a parttype's
692 // decal_index. The first record is always JMPVIA_AAAAA, used as an anchor sanity check. The
693 // +72 count is harvested into m_decalTerminalCount so passives without a section 14
694 // descriptor get exact pad counts.
695 if( m_version <= 0x2022 )
696 {
698 return;
699 }
700
701 // The table is section 14's own records, reached from the corrected payload offset, so no
702 // header-size constant is needed.
703 static constexpr int REC_SIZE = 112;
704 static constexpr int STACK_START_OFFSET = 44;
705 static constexpr int SENTINEL_OFFSET = 64;
706 static constexpr int START_OFFSET = 68;
707 static constexpr int COUNT_OFFSET = 72;
708 static constexpr int STACK_COUNT_OFFSET = 88;
709
711
712 if( !sec14 )
713 THROW_IO_ERROR( "Missing PADS decal controller" );
714
715 if( sec14->count == 0 )
716 return;
717
718 uint32_t start = sec14->physicalOffset;
719
720 if( start + 12 > m_data.size() || m_sdb.RecordAt( start ).Str( 0, 12 ) != "JMPVIA_AAAAA" )
721 THROW_IO_ERROR( "Invalid PADS decal-controller framing" );
722
723 // Bound the file-supplied count before it sizes an allocation or a loop
724 if( static_cast<uint64_t>( sec14->count ) * REC_SIZE > m_data.size() - start )
725 THROW_IO_ERROR( "Invalid PADS decal-name table extent" );
726
727 m_decalNameTable.clear();
728 m_decalNameTable.reserve( sec14->count );
729
730 for( uint32_t k = 0; k < sec14->count; ++k )
731 {
732 uint32_t off = start + k * REC_SIZE;
733 SDB_RECORD rec = m_sdb.RecordAt( off );
734
735 if( off + REC_SIZE > m_data.size() || rec.U16( SENTINEL_OFFSET ) != SDB_RECORD_SENTINEL )
736 {
737 m_decalNameTable.emplace_back();
738 continue;
739 }
740
741 std::string name = rec.Str( 0, 41 );
742 m_decalNameTable.push_back( name );
743
744 int32_t startCursor = rec.I32( START_OFFSET );
745 int32_t count = rec.I32( COUNT_OFFSET );
746
747 if( !name.empty() && count > 0 && count <= 1000 )
748 {
749 m_decalTerminalCount.emplace( name, static_cast<uint32_t>( count ) );
750
751 if( startCursor >= 0 )
752 m_decalTerminalStart.emplace( name, startCursor );
753
754 int32_t stackCount = rec.I32( STACK_COUNT_OFFSET );
755
756 if( stackCount > 0 && stackCount <= 1000 )
757 {
758 m_decalStackCount.emplace( name, stackCount );
759
760 int32_t stackStart = rec.I32( STACK_START_OFFSET );
761
762 if( stackStart >= 0 )
763 m_decalStackStart.emplace( name, stackStart );
764 }
765 }
766 }
767}
768
769
771{
772 // The v0x2017 through v0x2022 dialects carry the same complete decal-name table the newer
773 // ones do, at the same place in their section 14: record 0's name starts at the physical
774 // section cursor, 44 bytes into the logical record. Each record holds NAME @ +0, a 0xFFFE
775 // sentinel @ +64 that terminates the table, and a terminal count @ +72.
776 //
777 // The stride is the section's own declared stride, so the 100-byte old and 112-byte new
778 // records need no version branch. Verified against all 165 corpus files of every version --
779 // the JMPVIA_AAAAA name of record 0 lands exactly on this offset on every one of them, which
780 // is what retired the whole-file signature scan this used to do.
781 if( m_version > 0x2022 )
782 return;
783
784 const SDB_SECTION* sec14 = m_sdb.Section( 14 );
785
786 if( !sec14 || sec14->stride == 0 )
787 THROW_IO_ERROR( "Invalid PADS legacy decal controller" );
788
789 static constexpr int STACK_START_OFFSET = 44;
790 static constexpr int SENTINEL_OFFSET = 64;
791 static constexpr int START_OFFSET = 68;
792 static constexpr int COUNT_OFFSET = 72;
793
794 size_t start = sec14->physicalOffset;
795 size_t stride = sec14->stride;
796
797 std::vector<std::string> table;
798 std::map<std::string, uint32_t> counts;
799 std::map<std::string, int32_t> starts;
800 std::map<std::string, int32_t> stackCounts;
801 std::map<std::string, int32_t> stackStarts;
802
803 if( start > m_data.size()
804 || static_cast<uint64_t>( sec14->count ) * stride > static_cast<uint64_t>( m_data.size() - start ) )
805 {
806 THROW_IO_ERROR( "Invalid PADS legacy decal-controller extent" );
807 }
808
809 table.reserve( sec14->count );
810
811 for( size_t k = 0; k < sec14->count; ++k )
812 {
813 size_t off = start + k * stride;
814 SDB_RECORD rec = m_sdb.RecordAt( static_cast<uint32_t>( off ) );
815
816 if( rec.U16( SENTINEL_OFFSET ) != SDB_RECORD_SENTINEL )
817 {
818 table.emplace_back();
819 continue;
820 }
821
822 std::string name = rec.Str( 0, 40 );
823 table.push_back( name );
824
825 if( off + COUNT_OFFSET + 4 <= m_data.size() )
826 {
827 int32_t count = rec.I32( COUNT_OFFSET );
828 int32_t cursor = rec.I32( START_OFFSET );
829 int32_t stackCount = rec.I32( 88 );
830
831 if( !name.empty() && count > 0 && count <= 1000 )
832 {
833 counts.emplace( name, static_cast<uint32_t>( count ) );
834
835 if( cursor >= 0 )
836 {
837 // v2017/v2019 store positive terminal cursors as one-based pool ordinals;
838 // zero is the shared first slot used by the built-in via decals.
839 starts.emplace( name, m_version <= 0x2019 && cursor > 0 ? cursor - 1 : cursor );
840 }
841
842 if( stackCount > 0 && stackCount <= 1000 )
843 {
844 stackCounts.emplace( name, stackCount );
845
846 // Every dialect stores this cursor, and it has to be read rather than
847 // re-derived by accumulating counts in decal-table order: the slices form
848 // a ring, so the table's first decals do not sit at the front of the pair
849 // pool. On PSTAGE-002 the 36 decals tile 64 slots with STANDARDVIA at 0,
850 // S2 last at 61, then THERMALVIA at 62 and JMPVIA_AAAAA at 63 wrapping
851 // back to 0.
852 int32_t stackStart = rec.I32( STACK_START_OFFSET );
853
854 if( stackStart >= 0 )
855 stackStarts.emplace( name, stackStart );
856 }
857 }
858 }
859 }
860
861 // Record 0 is always the JMPVIA_AAAAA pseudo-decal, so its name doubles as a check that the
862 // structural offset landed on the table rather than on unrelated bytes.
863 if( table.empty() || table[0] != "JMPVIA_AAAAA" )
864 THROW_IO_ERROR( "Invalid PADS legacy decal-controller framing" );
865
866 m_decalNameTable = std::move( table );
867 m_decalTerminalCount = std::move( counts );
868 m_decalTerminalStart = std::move( starts );
869 m_decalStackCount = std::move( stackCounts );
870 m_decalStackStart = std::move( stackStarts );
871}
872
873
875{
876 // The parttype-definition table's record size, decal_index field offset, and header framing
877 // all differ between dialects. The logical record ring starts 44 bytes before section 17's
878 // physical cursor. Modern records are 224 bytes with decal_index at +96; legacy records
879 // are 208 bytes with decal_index at +112 (v0x2021 has no parttype-definition layer at all).
880 // Verified against 2FOC_4.pcb: the decal_index for DSPIC33FJ128MC802/TP-LC/LMC7101/
881 // MOLEX53261_0790 all land on +112 and resolve to their correct ground-truth decal names via
882 // m_decalNameTable. Note placements don't reference this table for v0x2022 -- see
883 // usesDirectDecalChain -- but it is still populated for potential future use (e.g. rules).
884 if( m_version == 0x2021 )
885 return;
886
888
889 if( !sec17 || sec17->count == 0 )
890 return;
891
892 if( sec17->totalBytes % sec17->count != 0 )
893 THROW_IO_ERROR( "Invalid PADS parttype-controller record extent" );
894
895 const uint32_t recSize = sec17->totalBytes / sec17->count;
896
897 if( recSize != 208 && recSize != 224 )
898 THROW_IO_ERROR( "Unsupported PADS parttype-controller record stride" );
899
900 const bool isLegacyLayout = recSize == 208;
901 const uint32_t decalOff = isLegacyLayout ? 112 : 96;
902
903 if( sec17->physicalOffset < DECAL_NAME_OFFSET )
904 THROW_IO_ERROR( "Invalid PADS parttype-controller framing" );
905
906 uint32_t start = sec17->physicalOffset - DECAL_NAME_OFFSET;
907
908 // The parttype's own name (the *PARTTYPE alias, e.g. a manufacturer part number such as
909 // GRM15XR71C103KA86D that resolves to a generic decal like C-0402) sits at +44 in both the
910 // 208-byte and 224-byte records.
911 const uint32_t nameOff = 44;
912
913 if( !m_cursor.InBounds( start, static_cast<size_t>( sec17->count ) * recSize ) )
914 THROW_IO_ERROR( "Invalid PADS parttype-controller extent" );
915
917 m_partTypeDecalIndices.reserve( sec17->count );
918 m_partTypeNames.clear();
919
920 m_partTypeNames.reserve( sec17->count );
921
922 for( uint32_t k = 0; k < sec17->count; ++k )
923 {
924 uint32_t off = start + k * recSize;
925
926 SDB_RECORD record = m_sdb.RecordAt( off );
927 std::vector<int32_t> decalIndices;
928
929 for( uint32_t indexOff = decalOff; !isLegacyLayout && indexOff + 8 <= recSize; indexOff += 8 )
930 {
931 int32_t decalIndex = record.I32( indexOff );
932
933 if( decalIndex < 0 || record.I32( indexOff + 4 ) != decalIndex )
934 break;
935
936 decalIndices.push_back( decalIndex );
937 }
938
939 if( isLegacyLayout && record.I32( decalOff ) >= 0 )
940 decalIndices.push_back( record.I32( decalOff ) );
941
942 m_partTypeDecalIndices.push_back( std::move( decalIndices ) );
943
944 m_partTypeNames.push_back( record.Str( nameOff, 36 ) );
945 }
946}
947
948
950{
951 // Section 14 is the complete PARTDECAL declaration table. Section 10 is the unrelated
952 // board-drawing owner ring; treating its DRW names as decals created phantom definitions.
953 for( const std::string& name : m_decalNameTable )
954 {
955 if( name.empty() )
956 continue;
957
958 PART_DECAL decal;
959 decal.name = name;
960 decal.units = "M";
961 m_decals[name] = decal;
962 }
963
965}
966
967
969{
970 const SDB_SECTION* section = getSection( SECTION::TerminalPool );
971
972 if( !section )
973 THROW_IO_ERROR( "Missing PADS terminal controller" );
974
975 if( section->count == 0 )
976 return;
977
978 const size_t stride = m_version <= 0x2019 ? 20 : 36;
979 const size_t base = static_cast<size_t>( section->physicalOffset ) + ( m_version <= 0x2019 ? 16 : 0 );
980 const size_t bytes = static_cast<size_t>( section->count ) * stride;
981
982 if( base > m_data.size() || bytes > m_data.size() - base )
983 THROW_IO_ERROR( "Invalid PADS terminal-controller extent" );
984
985 struct DIRECT_TERMINAL
986 {
987 int32_t x;
988 int32_t y;
989 std::string name;
990 };
991
992 std::vector<DIRECT_TERMINAL> terminals;
993 terminals.reserve( section->count );
994
995 for( uint32_t index = 0; index < section->count; ++index )
996 {
997 SDB_RECORD record = m_sdb.RecordAt( static_cast<uint32_t>( base + index * stride ) );
998
999 if( m_version <= 0x2019 )
1000 terminals.push_back( { record.I32( 4 ), record.I32( 8 ), {} } );
1001 else
1002 terminals.push_back( { record.I32( 0 ), record.I32( 4 ), record.Str( 20, 4 ) } );
1003 }
1004
1005 for( auto& [name, decal] : m_decals )
1006 {
1007 auto startIt = m_decalTerminalStart.find( name );
1008 auto countIt = m_decalTerminalCount.find( name );
1009
1010 if( startIt == m_decalTerminalStart.end() || countIt == m_decalTerminalCount.end() || startIt->second < 0 )
1011 {
1012 continue;
1013 }
1014
1015 const size_t start = static_cast<size_t>( startIt->second );
1016 const size_t count = countIt->second;
1017
1018 if( start > terminals.size() || count > terminals.size() - start )
1019 THROW_IO_ERROR( "Invalid PADS decal terminal range" );
1020
1021 decal.terminals.clear();
1022 decal.terminals.reserve( count );
1023
1024 for( size_t index = 0; index < count; ++index )
1025 {
1026 const DIRECT_TERMINAL& serialized = terminals[start + index];
1027 TERMINAL terminal;
1028 terminal.x = toBasicCoordX( serialized.x );
1029 terminal.y = toBasicCoordY( serialized.y );
1030 terminal.name = serialized.name.empty() ? std::to_string( index + 1 ) : serialized.name;
1031 decal.terminals.push_back( std::move( terminal ) );
1032 }
1033 }
1034
1036
1037 size_t pairCount = 0;
1038
1039 for( const auto& [name, start] : m_decalStackStart )
1040 {
1041 auto countIt = m_decalStackCount.find( name );
1042
1043 if( start >= 0 && countIt != m_decalStackCount.end() && countIt->second > 0 )
1044 {
1045 pairCount = std::max( pairCount, static_cast<size_t>( start ) + static_cast<size_t>( countIt->second ) );
1046 }
1047 }
1048
1049 const size_t pairBase = base + bytes - ( m_version <= 0x2019 ? 16 : 0 );
1050
1051 if( pairCount > 0 )
1052 {
1053 if( pairBase > m_data.size() || pairCount * 8 > m_data.size() - pairBase )
1054 THROW_IO_ERROR( "Invalid PADS per-pin padstack extent" );
1055
1056 std::vector<std::pair<int32_t, int32_t>> pairs;
1057 pairs.reserve( pairCount );
1058
1059 for( size_t index = 0; index < pairCount; ++index )
1060 {
1061 SDB_RECORD record = m_sdb.RecordAt( static_cast<uint32_t>( pairBase + index * 8 ) );
1062
1063 pairs.emplace_back( record.I32( 0 ), record.I32( 4 ) );
1064 }
1065
1066 for( const auto& [name, start] : m_decalStackStart )
1067 {
1068 auto decalIt = m_decals.find( name );
1069 auto countIt = m_decalStackCount.find( name );
1070
1071 if( decalIt != m_decals.end() && countIt != m_decalStackCount.end() )
1072 applyPadstackPairs( decalIt->second, pairs, start, countIt->second );
1073 }
1074 }
1075
1076 return;
1077}
1078
1079
1081{
1082 // Global padstack zero is the implicit default until a decal's serialized ordinal-zero pair
1083 // replaces it. Positive pair ordinals override individual one-based terminals.
1084 for( auto& [name, decal] : m_decals )
1085 {
1086 if( decal.terminals.empty() )
1087 continue;
1088
1089 if( !m_padStackPool.empty() && !m_padStackPool[0].empty() )
1090 {
1091 decal.pad_stacks[0] = m_padStackPool[0];
1092
1093 if( !m_padStackDrillSpans.empty() )
1094 decal.drill_spans[0] = m_padStackDrillSpans[0];
1095 }
1096 }
1097}
1098
1099
1100void BINARY_PARSER::applyPadstackPairs( PART_DECAL& aDecal, const std::vector<std::pair<int32_t, int32_t>>& aPairs,
1101 int32_t aStart, int32_t aCount )
1102{
1103 if( aStart < 0 || aCount <= 0 || static_cast<size_t>( aStart ) + static_cast<size_t>( aCount ) > aPairs.size() )
1104 THROW_IO_ERROR( "Invalid PADS per-pin padstack range" );
1105
1106 for( int32_t p = 0; p < aCount; ++p )
1107 {
1108 const std::pair<int32_t, int32_t>& pair = aPairs[static_cast<size_t>( aStart ) + p];
1109
1110 if( pair.first < 0 || static_cast<size_t>( pair.second ) >= m_padStackPool.size()
1111 || m_padStackPool[pair.second].empty() )
1112 {
1113 THROW_IO_ERROR( "Invalid PADS per-pin padstack reference" );
1114 }
1115
1116 if( pair.first > 0 && static_cast<size_t>( pair.first ) > aDecal.terminals.size() )
1117 THROW_IO_ERROR( "Invalid PADS per-pin terminal ordinal" );
1118
1119 aDecal.pad_stacks[pair.first] = m_padStackPool[pair.second];
1120 aDecal.drill_spans[pair.first] = m_padStackDrillSpans[pair.second];
1121 }
1122}
1123
1124
1125bool BINARY_PARSER::isValidNetName( const std::string& aName ) const
1126{
1127 return !aName.empty() && aName != "___Unassigned_Obstacles_";
1128}
1129
1130
1132{
1133 if( m_version <= 0x2022 )
1135 else
1137}
1138
1139
1141{
1142 std::unordered_set<std::string> existing;
1143
1144 // Section 23 is a 44-byte-rotated circular array. Its physical cursor is the logical base
1145 // plus 44 bytes.
1146 const uint32_t netRecordSize = m_version == 0x2024 ? 416 : 424;
1147 constexpr uint32_t NET_NAME = 76;
1148 constexpr uint32_t NET_NAME_LEN = 48;
1149 constexpr uint32_t NET_SELF_PTR = 144;
1150 constexpr uint32_t NET_CLASS_PTR = 148;
1151
1152 const SDB_SECTION* nets = getSection( SECTION::Nets );
1153
1154 if( !nets )
1155 THROW_IO_ERROR( "Missing PADS net controller" );
1156
1157 if( nets->count == 0 )
1158 return;
1159
1160 if( nets->stride != netRecordSize || nets->physicalOffset < 44
1161 || !m_cursor.InBounds( nets->physicalOffset - 44, nets->physicalBytes ) )
1162 {
1163 THROW_IO_ERROR( "Invalid PADS net-controller framing" );
1164 }
1165
1166 const size_t base = nets->physicalOffset - 44;
1167
1168 for( uint32_t i = 0; i < nets->count; ++i )
1169 {
1170 SDB_RECORD rec = m_sdb.RecordAt( base + static_cast<size_t>( i ) * netRecordSize );
1171 std::string name = rec.Str( NET_NAME, NET_NAME_LEN );
1172
1173 if( name.empty() || !isValidNetName( name ) || !existing.insert( name ).second )
1174 continue;
1175
1176 NET net;
1177 net.name = name;
1178 m_nets.push_back( net );
1179 m_sec23RecordToNet[i] = m_nets.size() - 1;
1181 m_netAnchors.push_back( { m_nets.size() - 1, rec.U32( 64 ), rec.U32( 68 ) } );
1182
1183 if( uint32_t owner = rec.U32( NET_CLASS_PTR ) )
1184 m_netClassOwner[name] = owner;
1185
1186 if( uint32_t selfPtr = rec.U32( NET_SELF_PTR ) )
1187 m_netSelfPtrToName[selfPtr] = name;
1188 }
1189
1191}
1192
1193
1195{
1196 constexpr size_t RECORD_SIZE = 68;
1197 constexpr uint32_t MARKER = 0xFE000000;
1198 constexpr uint32_t FLAG = 0x0000FFFE;
1199
1200 const SDB_SECTION* connections = getSection( SECTION::Connections );
1201 const SDB_SECTION* nets = getSection( SECTION::Nets );
1202
1203 const size_t netRecordSize = m_version == 0x2024 ? 416 : 424;
1204
1205 if( !connections || !nets )
1206 THROW_IO_ERROR( "Missing PADS net-connection controllers" );
1207
1208 if( connections->count == 0 )
1209 return;
1210
1211 if( nets->stride != netRecordSize )
1212 THROW_IO_ERROR( "Invalid PADS net-controller stride" );
1213
1214 constexpr size_t RING_PREFIX = 36;
1215 if( connections->physicalBytes != connections->physicalCount * RECORD_SIZE
1216 || connections->physicalOffset < RING_PREFIX
1217 || !m_cursor.InBounds( connections->physicalOffset - RING_PREFIX, connections->physicalBytes + RING_PREFIX ) )
1218 {
1219 THROW_IO_ERROR( "Invalid PADS net-connection ring extent" );
1220 }
1221
1222 auto connectionU32 = [&]( uint32_t aIndex, size_t aField )
1223 {
1224 size_t logical =
1225 connections->physicalOffset - RING_PREFIX + static_cast<size_t>( aIndex ) * RECORD_SIZE + aField;
1226 return m_cursor.U32At( logical );
1227 };
1228
1229 size_t runBase = static_cast<size_t>( connections->physicalOffset ) - RING_PREFIX;
1230 size_t runCount = static_cast<size_t>( connections->count ) + 1;
1231
1232 // Record zero is the topology root. The directory count is the number of edges, so the
1233 // logical ring has one more record than the physical controller: its final 36-byte head is
1234 // serialized after the controller while record zero's 36-byte prefix precedes it.
1235 for( uint32_t recordIndex = 0; recordIndex < runCount; ++recordIndex )
1236 {
1237 if( ( recordIndex > 0 && ( connectionU32( recordIndex, 20 ) & 0xFFFFFFC0U ) != MARKER )
1238 || ( recordIndex < connections->count && ( connectionU32( recordIndex, 52 ) & 0xFFFFU ) != FLAG ) )
1239 {
1240 THROW_IO_ERROR( "Invalid PADS net-connection ring framing" );
1241 }
1242 }
1243
1244 logResolvedBase( 24, "connRun", runBase, connections ? connections->physicalOffset : 0 );
1245
1246 using PIN_ID = std::pair<uint32_t, uint32_t>;
1247
1248 std::map<PIN_ID, size_t> pinIndex;
1249 std::vector<PIN_ID> pins;
1250 std::vector<size_t> parent;
1251
1252 auto intern = [&]( const PIN_ID& aPin )
1253 {
1254 auto [it, inserted] = pinIndex.emplace( aPin, parent.size() );
1255
1256 if( inserted )
1257 {
1258 pins.push_back( aPin );
1259 parent.push_back( parent.size() );
1260 }
1261
1262 return it->second;
1263 };
1264
1265 auto findRoot = [&]( size_t aNode )
1266 {
1267 while( parent[aNode] != aNode )
1268 {
1269 parent[aNode] = parent[parent[aNode]];
1270 aNode = parent[aNode];
1271 }
1272
1273 return aNode;
1274 };
1275
1276 auto join = [&]( const PIN_ID& aPin, const PIN_ID& bPin )
1277 {
1278 const size_t indexA = intern( aPin );
1279 const size_t indexB = intern( bPin );
1280 const size_t rootA = findRoot( indexA );
1281 const size_t rootB = findRoot( indexB );
1282
1283 if( rootA != rootB )
1284 parent[rootA] = rootB;
1285 };
1286
1287 for( uint32_t recordIndex = 0; recordIndex + 1 < runCount; ++recordIndex )
1288 {
1289 join( { connectionU32( recordIndex, 60 ), connectionU32( recordIndex + 1, 0 ) },
1290 { connectionU32( recordIndex, 64 ), connectionU32( recordIndex + 1, 4 ) } );
1291 }
1292
1293 std::map<size_t, size_t> componentNet;
1294
1295 for( const NET_ANCHOR& anchor : m_netAnchors )
1296 {
1297 if( anchor.netIndex >= m_nets.size() || anchor.terminalOrdinal == 0 )
1298 continue;
1299
1300 const size_t root = findRoot( intern( { anchor.placementObject, anchor.terminalOrdinal } ) );
1301 auto [it, inserted] = componentNet.emplace( root, anchor.netIndex );
1302
1303 if( !inserted && it->second != anchor.netIndex )
1304 {
1305 // PADS keeps $$$ autoroute aliases as separate net records even after joining
1306 // their pins into a named signal's connection component.
1307 const bool existingIsAlias = m_nets[it->second].name.rfind( "$$$", 0 ) == 0;
1308 const bool incomingIsAlias = m_nets[anchor.netIndex].name.rfind( "$$$", 0 ) == 0;
1309
1310 if( existingIsAlias && incomingIsAlias )
1311 continue;
1312 else if( existingIsAlias != incomingIsAlias )
1313 it->second = incomingIsAlias ? it->second : anchor.netIndex;
1314 else
1315 THROW_IO_ERROR( "Conflicting PADS net anchors '" + m_nets[it->second].name + "' and '"
1316 + m_nets[anchor.netIndex].name + "'" );
1317 }
1318 }
1319
1320 for( size_t index = 0; index < pins.size(); ++index )
1321 {
1322 auto netIt = componentNet.find( findRoot( index ) );
1323
1324 if( netIt == componentNet.end() )
1325 continue;
1326
1327 const auto [placementObject, terminalOrdinal] = pins[index];
1328 m_netConnectionEndpoints.push_back( { netIt->second, placementObject, terminalOrdinal } );
1329
1330 auto partIt = m_placementObjectToPart.find( placementObject );
1331
1332 if( partIt != m_placementObjectToPart.end() && partIt->second < m_parts.size() )
1333 m_nets[netIt->second].component_refs.push_back( m_parts[partIt->second].name );
1334 }
1335}
1336
1337
1339{
1340 using PIN_KEY = std::pair<std::string, std::string>;
1341
1342 std::map<PIN_KEY, size_t> pinOwners;
1343
1344 auto claim = [&]( const NET_ANCHOR& anchor )
1345 {
1346 if( anchor.netIndex >= m_nets.size() || anchor.terminalOrdinal == 0 )
1347 return;
1348
1349 auto partIt = m_placementObjectToPart.find( anchor.placementObject );
1350
1351 if( partIt == m_placementObjectToPart.end() || partIt->second >= m_parts.size() )
1352 return;
1353
1354 const PART& part = m_parts[partIt->second];
1355 auto decalIt = m_decals.find( part.decal );
1356
1357 if( decalIt == m_decals.end() || anchor.terminalOrdinal > decalIt->second.terminals.size() )
1358 return;
1359
1360 const std::string& terminalName = decalIt->second.terminals[anchor.terminalOrdinal - 1].name;
1361
1362 if( terminalName.empty() )
1363 return;
1364
1365 PIN_KEY key = std::make_pair( part.name, terminalName );
1366 auto [owner, inserted] = pinOwners.emplace( key, anchor.netIndex );
1367
1368 if( inserted || owner->second == anchor.netIndex )
1369 return;
1370
1371 // Zero-edge $$$ placeholders retain a named signal's anchor without owning its pin.
1372 const bool existingIsAlias = m_nets[owner->second].name.rfind( "$$$", 0 ) == 0;
1373 const bool incomingIsAlias = m_nets[anchor.netIndex].name.rfind( "$$$", 0 ) == 0;
1374
1375 if( existingIsAlias && !incomingIsAlias )
1376 owner->second = anchor.netIndex;
1377 else if( !existingIsAlias && incomingIsAlias )
1378 return;
1379 else if( existingIsAlias && incomingIsAlias )
1380 return;
1381 else
1382 THROW_IO_ERROR( "Conflicting PADS net ownership for pin '" + part.name + "." + terminalName + "'" );
1383 };
1384
1385 for( const NET_ANCHOR& endpoint : m_netConnectionEndpoints )
1386 claim( endpoint );
1387
1388 for( const NET_ANCHOR& anchor : m_netAnchors )
1389 claim( anchor );
1390
1391 for( const auto& [key, netIndex] : pinOwners )
1392 {
1393 NET_PIN pin;
1394 pin.ref_des = key.first;
1395 pin.pin_name = key.second;
1396 m_nets[netIndex].pins.push_back( std::move( pin ) );
1397 }
1398}
1399
1400
1402{
1403 std::unordered_set<std::string> existing;
1404 std::unordered_map<std::string, size_t> netIndexByName;
1405
1406 // Route and via records address nets by a dense ordinal into the serialized net table, not by
1407 // the stored net ID. Build the index from that table so a via
1408 // resolves the net PADS wrote: verified against the ASCII exports' own via nets, 2533 of 2533
1409 // vias across the v0x2021 corpus resolve to the right name, none to a wrong one.
1410 m_sec23IndexToNet.clear();
1411
1412 const std::vector<size_t> netOffsets = oldNetRecordOffsets();
1413
1414 for( size_t i = 0; i < netOffsets.size(); ++i )
1415 {
1416 SDB_RECORD rec = m_sdb.RecordAt( static_cast<uint32_t>( netOffsets[i] ) );
1417 std::string name = rec.Str( 12, 48 );
1418
1419 if( name.empty() || !isValidNetName( name ) )
1420 continue;
1421
1422 m_sec23IndexToNet[static_cast<uint32_t>( i )] = name;
1423
1424 if( existing.insert( name ).second )
1425 {
1426 NET net;
1427 net.name = name;
1428 m_nets.push_back( net );
1429 netIndexByName.emplace( name, m_nets.size() - 1 );
1430 }
1431
1432 m_sec23RecordToNet[static_cast<uint32_t>( i )] = netIndexByName.at( name );
1433
1434 if( uint32_t owner = rec.U32( 84 ) )
1435 m_netClassOwner[name] = owner;
1436 }
1437
1439}
1440
1441
1442std::vector<size_t> BINARY_PARSER::oldNetRecordOffsets() const
1443{
1444 constexpr uint32_t NET_RECORD_SIZE = 144;
1445
1446 std::vector<size_t> offsets;
1447 const SDB_SECTION* nets = getSection( SECTION::Nets );
1448
1449 if( !nets || nets->stride != NET_RECORD_SIZE )
1450 THROW_IO_ERROR( "Invalid PADS legacy net-controller framing" );
1451
1452 const uint64_t base = static_cast<uint64_t>( nets->physicalOffset ) + 20;
1453
1454 if( base + static_cast<uint64_t>( nets->count ) * NET_RECORD_SIZE > m_data.size() )
1455 THROW_IO_ERROR( "Invalid PADS legacy net-controller extent" );
1456
1457 for( size_t i = 0; i < nets->count; ++i )
1458 offsets.push_back( base + i * NET_RECORD_SIZE );
1459
1460 return offsets;
1461}
1462
1463
1465{
1466 constexpr size_t RECORD_SIZE = 68;
1467 constexpr uint32_t ENDPOINT_FLAG = 0x0000FFFE;
1468 constexpr uint32_t MARKER = 0xFE000000;
1469 constexpr uint32_t NET_RECORD_SIZE = 144;
1470 constexpr uint32_t NET_FIRST = 8; // index of this net's first connection record
1471 constexpr uint32_t NET_NAME = 12;
1472 constexpr uint32_t NET_COUNT = 92; // number of connection records in this net
1473
1474 const SDB_SECTION* connections = getSection( SECTION::Connections );
1475 const SDB_SECTION* nets = getSection( SECTION::Nets );
1476
1477 if( !connections || !nets )
1478 THROW_IO_ERROR( "Missing PADS legacy net-connection controllers" );
1479
1480 if( connections->count == 0 )
1481 return;
1482
1483 if( nets->stride != NET_RECORD_SIZE )
1484 THROW_IO_ERROR( "Invalid PADS legacy net-controller stride" );
1485
1486 const uint64_t base = static_cast<uint64_t>( connections->physicalOffset ) + 16;
1487
1488 if( base + static_cast<uint64_t>( connections->count ) * RECORD_SIZE > m_data.size() )
1489 {
1490 THROW_IO_ERROR( "Invalid PADS legacy net-connection extent" );
1491 }
1492
1493 const size_t total = connections->count;
1494
1495 // Union-find over pin identities, so each net becomes one connected component of the graph
1496 // the connection records span.
1497 std::map<std::pair<uint32_t, uint32_t>, size_t> pinIndex;
1498 std::vector<size_t> parent;
1499
1500 auto intern = [&]( uint32_t aObject, uint32_t aOrdinal )
1501 {
1502 auto [it, inserted] = pinIndex.emplace( std::make_pair( aObject, aOrdinal ), parent.size() );
1503
1504 if( inserted )
1505 parent.push_back( parent.size() );
1506
1507 return it->second;
1508 };
1509
1510 auto findRoot = [&]( size_t aNode )
1511 {
1512 while( parent[aNode] != aNode )
1513 {
1514 parent[aNode] = parent[parent[aNode]];
1515 aNode = parent[aNode];
1516 }
1517
1518 return aNode;
1519 };
1520
1521 struct CONNECTION
1522 {
1523 uint32_t objectA;
1524 uint32_t ordinalA;
1525 uint32_t objectB;
1526 uint32_t ordinalB;
1527 size_t pinA;
1528 };
1529
1530 std::vector<CONNECTION> stream;
1531 stream.reserve( total );
1532
1533 for( size_t i = 0; i < total; ++i )
1534 {
1535 SDB_RECORD rec = m_sdb.RecordAt( static_cast<uint32_t>( base + i * RECORD_SIZE ) );
1536
1537 if( ( rec.U32( 0 ) & 0xFFFFU ) != ENDPOINT_FLAG || ( rec.U32( 36 ) & 0xFFFFFFC0U ) != MARKER )
1538 {
1539 THROW_IO_ERROR( "Invalid PADS legacy net-connection ring framing" );
1540 }
1541
1542 CONNECTION conn{ rec.U32( 8 ), rec.U32( 16 ), rec.U32( 12 ), rec.U32( 20 ), 0 };
1543
1544 conn.pinA = intern( conn.objectA, conn.ordinalA );
1545 size_t pinB = intern( conn.objectB, conn.ordinalB );
1546
1547 size_t rootA = findRoot( conn.pinA );
1548 size_t rootB = findRoot( pinB );
1549
1550 if( rootA != rootB )
1551 parent[rootA] = rootB;
1552
1553 stream.push_back( conn );
1554 }
1555
1556 std::map<size_t, size_t> componentSize;
1557
1558 for( size_t i = 0; i < stream.size(); ++i )
1559 {
1560 size_t root = findRoot( stream[i].pinA );
1561
1562 ++componentSize[root];
1563 }
1564
1565 auto netRecordName = [&]( size_t aOffset, std::string& aName, uint32_t& aCount, uint32_t& aFirst,
1566 uint32_t& aAnchorObject, uint32_t& aAnchorOrdinal )
1567 {
1568 if( !m_cursor.InBounds( aOffset, NET_RECORD_SIZE ) )
1569 return false;
1570
1571 SDB_RECORD rec = m_sdb.RecordAt( static_cast<uint32_t>( aOffset ) );
1572
1573 aName = rec.Str( NET_NAME, 48 );
1574 aCount = rec.U32( NET_COUNT );
1575 aFirst = rec.U32( NET_FIRST );
1576 aAnchorObject = rec.U32( 0 );
1577 aAnchorOrdinal = rec.U32( 4 );
1578
1579 return !aName.empty() && aCount > 0 && aCount <= connections->count;
1580 };
1581
1582 std::map<std::string, size_t> netIndexByName;
1583
1584 for( size_t i = 0; i < m_nets.size(); ++i )
1585 netIndexByName.emplace( m_nets[i].name, i );
1586
1587 std::set<size_t> assignedComponents;
1588
1589 for( size_t offset : oldNetRecordOffsets() )
1590 {
1591 std::string name;
1592 uint32_t count = 0;
1593 uint32_t edgeIndex = 0;
1594 uint32_t anchorObject = 0;
1595 uint32_t anchorOrdinal = 0;
1596
1597 if( !netRecordName( offset, name, count, edgeIndex, anchorObject, anchorOrdinal ) || !isValidNetName( name ) )
1598 continue;
1599
1600 if( edgeIndex >= stream.size() )
1601 THROW_IO_ERROR( "Invalid PADS legacy net edge reference" );
1602
1603 auto anchor = pinIndex.find( { anchorObject, anchorOrdinal } );
1604
1605 if( anchor == pinIndex.end() )
1606 THROW_IO_ERROR( "Invalid PADS legacy net pin anchor" );
1607
1608 const size_t root = findRoot( anchor->second );
1609
1610 if( findRoot( stream[edgeIndex].pinA ) != root )
1611 THROW_IO_ERROR( "Conflicting PADS legacy net anchors" );
1612
1613 if( count != componentSize[root] )
1614 THROW_IO_ERROR( "Invalid PADS legacy net component size" );
1615
1616 if( !assignedComponents.insert( root ).second )
1617 THROW_IO_ERROR( "Conflicting PADS legacy net component owners" );
1618
1619 auto netIt = netIndexByName.find( name );
1620
1621 if( netIt == netIndexByName.end() )
1622 THROW_IO_ERROR( "Missing PADS legacy net record" );
1623
1624 for( const CONNECTION& conn : stream )
1625 {
1626 if( findRoot( conn.pinA ) != root )
1627 continue;
1628
1629 m_netConnectionEndpoints.push_back( { netIt->second, conn.objectA, conn.ordinalA } );
1630 m_netConnectionEndpoints.push_back( { netIt->second, conn.objectB, conn.ordinalB } );
1631 }
1632 }
1633}
1634
1635
1637{
1638 if( m_netClassOwner.empty() || m_data.size() < 24 )
1639 return;
1640
1641 // Distinct net-class owner pointers; ascending order is net-class declaration order.
1642 std::set<uint32_t> ownerSet;
1643
1644 for( const auto& [name, owner] : m_netClassOwner )
1645 ownerSet.insert( owner );
1646
1647 std::vector<uint32_t> owners( ownerSet.begin(), ownerSet.end() );
1648 std::map<uint32_t, size_t> ownerOrdinal;
1649
1650 for( size_t k = 0; k < owners.size(); ++k )
1651 ownerOrdinal[owners[k]] = k;
1652
1653 std::vector<NET_CLASS_RULE_EDGE> edges = collectNetClassRuleEdges( ownerSet );
1654
1655 if( edges.empty() )
1656 return;
1657
1658 const size_t NAME_STRIDE = m_version <= 0x2022 ? 28 : 280;
1659 constexpr size_t NAME_OFFSET = 8;
1660 const size_t NAME_LEN = m_version <= 0x2022 ? 8 : 48;
1661 const SDB_SECTION* names = getSection( 66 );
1662
1663 if( !names || names->physicalCount < owners.size()
1664 || static_cast<uint64_t>( names->physicalOffset ) + owners.size() * NAME_STRIDE > m_data.size() )
1665 {
1666 return;
1667 }
1668
1669 m_netClasses.clear();
1670 m_netClasses.resize( owners.size() );
1671
1672 for( size_t k = 0; k < owners.size(); ++k )
1673 {
1674 std::string name = m_sdb.RecordAt( names->physicalOffset + k * NAME_STRIDE ).Str( NAME_OFFSET, NAME_LEN );
1675
1676 if( name.empty() || !std::isalnum( static_cast<unsigned char>( name[0] ) ) )
1677 name = "PADS_NetClass_" + std::to_string( k + 1 );
1678
1679 m_netClasses[k].name = name;
1680 }
1681
1682 for( const auto& [net, owner] : m_netClassOwner )
1683 {
1684 auto it = ownerOrdinal.find( owner );
1685
1686 if( it != ownerOrdinal.end() )
1687 m_netClasses[it->second].nets.push_back( net );
1688 }
1689
1690 applyNetClassClearances( edges, ownerOrdinal );
1691
1692 // Sort for reproducible output.
1693 for( BIN_NET_CLASS_DEF& nc : m_netClasses )
1694 {
1695 std::sort( nc.nets.begin(), nc.nets.end() );
1696 std::sort( nc.ruleLayers.begin(), nc.ruleLayers.end() );
1697 nc.ruleLayers.erase( std::unique( nc.ruleLayers.begin(), nc.ruleLayers.end() ), nc.ruleLayers.end() );
1698 }
1699}
1700
1701
1702std::vector<NET_CLASS_RULE_EDGE> BINARY_PARSER::collectNetClassRuleEdges( const std::set<uint32_t>& aOwnerSet )
1703{
1704 constexpr size_t RECORD_SIZE = 28;
1705 constexpr size_t RULE_KIND = 0;
1706 constexpr size_t RULE_DETAIL_HANDLE = 4;
1707 constexpr size_t SCOPE_TYPE = 8;
1708 constexpr uint32_t NET_CLASS_SCOPE = 0x42;
1709 constexpr size_t SCOPE_REFERENCE = 12;
1710 constexpr size_t LAYER = 24;
1711
1712 std::vector<NET_CLASS_RULE_EDGE> edges;
1713 const SDB_SECTION* relationships = getSection( 67 );
1714
1715 if( !relationships || relationships->physicalBytes != relationships->count * RECORD_SIZE )
1716 return edges;
1717
1718 for( uint32_t i = 0; i < relationships->count; ++i )
1719 {
1720 size_t off = relationships->physicalOffset + static_cast<size_t>( i ) * RECORD_SIZE;
1721 SDB_RECORD rec = m_sdb.RecordAt( off );
1722
1723 if( rec.U32( SCOPE_TYPE ) != NET_CLASS_SCOPE )
1724 continue;
1725
1726 uint32_t owner = rec.U32( SCOPE_REFERENCE );
1727
1728 if( !aOwnerSet.count( owner ) )
1729 continue;
1730
1731 edges.push_back( { owner, rec.U32( RULE_KIND ), rec.U32( RULE_DETAIL_HANDLE ),
1732 static_cast<int>( rec.U32( LAYER ) ), off } );
1733 }
1734
1735 return edges;
1736}
1737
1738
1739void BINARY_PARSER::applyNetClassClearances( const std::vector<NET_CLASS_RULE_EDGE>& aEdges,
1740 const std::map<uint32_t, size_t>& aOwnerOrdinal )
1741{
1742 constexpr uint32_t CLEARANCE_RULE_KIND = 0x29;
1743
1744 for( const NET_CLASS_RULE_EDGE& e : aEdges )
1745 {
1746 if( e.ruleKind != CLEARANCE_RULE_KIND )
1747 continue;
1748
1749 auto owner = aOwnerOrdinal.find( e.owner );
1750
1751 if( owner != aOwnerOrdinal.end() )
1752 m_netClasses[owner->second].ruleLayers.push_back( e.layer );
1753 }
1754
1755 const SDB_SECTION* relationships = getSection( 67 );
1756 const SDB_SECTION* values = getSection( 41 );
1757
1758 if( !relationships || !values || values->physicalCount == 0 )
1759 return;
1760
1761 constexpr size_t RELATIONSHIP_SIZE = 28;
1762 uint32_t firstClearanceHandle = UINT32_MAX;
1763
1764 for( uint32_t i = 0; i < relationships->count; ++i )
1765 {
1766 SDB_RECORD rec = m_sdb.RecordAt( relationships->physicalOffset + i * RELATIONSHIP_SIZE );
1767
1768 if( rec.U32( 0 ) == CLEARANCE_RULE_KIND )
1769 firstClearanceHandle = std::min( firstClearanceHandle, rec.U32( 4 ) );
1770 }
1771
1772 if( firstClearanceHandle == UINT32_MAX )
1773 return;
1774
1775 const uint32_t valueStride = m_version == 0x2017 ? 180 : 188;
1776
1777 for( const NET_CLASS_RULE_EDGE& edge : aEdges )
1778 {
1779 if( edge.ruleKind != CLEARANCE_RULE_KIND || edge.layer != 0 || edge.rulePtr < firstClearanceHandle )
1780 continue;
1781
1782 uint32_t delta = edge.rulePtr - firstClearanceHandle;
1783
1784 if( delta % valueStride != 0 )
1785 continue;
1786
1787 uint32_t index = delta / valueStride;
1788
1789 if( index >= values->physicalCount )
1790 continue;
1791
1792 auto owner = aOwnerOrdinal.find( edge.owner );
1793
1794 if( owner == aOwnerOrdinal.end() )
1795 continue;
1796
1797 SDB_RECORD value = m_sdb.RecordAt( values->physicalOffset + index * valueStride );
1798 BIN_NET_CLASS_DEF& netClass = m_netClasses[owner->second];
1799
1800 netClass.clearance = value.I32( 12 );
1801 netClass.viaClearance = value.I32( 20 );
1802 netClass.minTrackWidth = value.I32( 144 );
1803 netClass.trackWidth = value.I32( 148 );
1804 netClass.maxTrackWidth = value.I32( 152 );
1805 netClass.hasRuleValues = true;
1806 }
1807}
1808
1809
1811{
1812 constexpr size_t OBJECT_SIZE = 864;
1813 constexpr double F64_INHERIT = -1.0;
1814 constexpr int32_t I32_INHERIT = -1;
1815
1816 // Field offsets within the 864-byte DIF_PAIR object.
1817 constexpr size_t NET_A_HANDLE = 12; // member-net A; equals a net record's +184 self-handle
1818 constexpr size_t NET_B_HANDLE = 16; // member-net B
1819 constexpr size_t GAP_INHERIT = 40; // f64 gap, used when the override is inherited
1820 constexpr size_t GAP_OVERRIDE = 56; // f64 gap override
1821 constexpr size_t WIDTH_INHERIT = 592; // i32 width, used when the override is inherited
1822 constexpr size_t WIDTH_OVERRIDE = 600; // i32 width override
1823
1824 const SDB_SECTION* records = getSection( 48 );
1825
1826 if( !records )
1827 THROW_IO_ERROR( "Missing PADS differential-pair controller" );
1828
1829 if( records->physicalCount == 0 || m_netSelfPtrToName.empty() )
1830 return;
1831
1832 if( records->physicalBytes != records->physicalCount * OBJECT_SIZE || records->physicalOffset < 8 )
1833 THROW_IO_ERROR( "Invalid PADS differential-pair framing" );
1834
1835 const size_t base = records->physicalOffset - 8;
1836
1837 if( static_cast<uint64_t>( base ) + static_cast<uint64_t>( records->physicalCount ) * OBJECT_SIZE > m_data.size() )
1838 THROW_IO_ERROR( "Invalid PADS differential-pair extent" );
1839
1840 std::set<std::pair<std::string, std::string>> seen;
1841
1842 for( uint32_t i = 0; i < records->physicalCount; ++i )
1843 {
1844 size_t objStart = base + static_cast<size_t>( i ) * OBJECT_SIZE;
1845 SDB_RECORD obj = m_sdb.RecordAt( objStart );
1846
1847 if( !m_netSelfPtrToName.count( obj.U32( NET_A_HANDLE ) )
1848 || !m_netSelfPtrToName.count( obj.U32( NET_B_HANDLE ) ) )
1849 {
1850 continue;
1851 }
1852
1853 const std::string& nameA = m_netSelfPtrToName.at( obj.U32( NET_A_HANDLE ) );
1854 const std::string& nameB = m_netSelfPtrToName.at( obj.U32( NET_B_HANDLE ) );
1855
1856 if( !seen.insert( { nameA, nameB } ).second )
1857 continue;
1858
1859 double gapOverride = obj.F64( GAP_OVERRIDE );
1860 double gap = ( gapOverride != F64_INHERIT ) ? gapOverride : obj.F64( GAP_INHERIT );
1861 int32_t widthOverride = obj.I32( WIDTH_OVERRIDE );
1862 double width = ( widthOverride != I32_INHERIT ) ? static_cast<double>( widthOverride )
1863 : static_cast<double>( obj.I32( WIDTH_INHERIT ) );
1864
1865 DIFF_PAIR_DEF dp;
1866 dp.name = nameA + "_" + nameB;
1867 dp.positive_net = nameA;
1868 dp.negative_net = nameB;
1869 dp.gap = ( gap != F64_INHERIT ) ? gap : 0.0;
1870 dp.width = ( width != static_cast<double>( I32_INHERIT ) ) ? width : 0.0;
1871
1872 m_diffPairs.push_back( std::move( dp ) );
1873 }
1874}
1875
1876
1878{
1881
1882 constexpr size_t GEOMETRY_BYTES = 36;
1883 const size_t recordSize = s8 ? s8->stride : 0;
1884 const size_t ringRotation = recordSize >= GEOMETRY_BYTES ? recordSize - GEOMETRY_BYTES : 0;
1885
1886 if( !s8 || !s9 )
1887 THROW_IO_ERROR( "Missing PADS text controllers" );
1888
1889 if( s8->physicalCount == 0 )
1890 return;
1891
1892 const bool validStringPool =
1893 ( s9->physicalBytes == 0 && s9->count == 0 ) || ( s9->stride == 1 && s9->physicalBytes == s9->count );
1894
1895 if( ( recordSize != 64 && recordSize != 72 ) || s8->physicalOffset < ringRotation || !validStringPool
1896 || s9->physicalOffset != s8->physicalOffset + s8->physicalBytes )
1897 THROW_IO_ERROR( "Invalid PADS text-controller framing" );
1898
1899 const size_t recordBase = s8->physicalOffset - ringRotation;
1900 const size_t poolBase = s9->physicalOffset;
1901 const size_t poolHi = poolBase + s9->physicalBytes;
1902
1903 // The final record's lagged metadata occupies the 36-byte circular-controller tail ending
1904 // at section 9. Only fields through +28 are consumed from it.
1905 if( !m_cursor.InBounds( recordBase, s8->physicalBytes + ringRotation )
1906 || !m_cursor.InBounds( poolBase, s9->physicalBytes ) )
1907 {
1908 THROW_IO_ERROR( "Invalid PADS text-controller extent" );
1909 }
1910
1911 parsePlacementFields( *s8, recordBase, recordSize, ringRotation );
1912 parseFreeText( *s8, recordBase, recordSize, ringRotation, poolBase, poolHi );
1913}
1914
1915
1916void BINARY_PARSER::parsePlacementFields( const SDB_SECTION& aText, size_t aRecordBase, size_t aRecordSize,
1917 size_t aRingRotation )
1918{
1919 auto fieldAttribute = [&]( uint32_t aIndex )
1920 {
1921 SDB_RECORD record = m_sdb.RecordAt( aRecordBase + static_cast<size_t>( aIndex ) * aRecordSize );
1922 ATTRIBUTE attribute;
1923 attribute.height = record.I32( aRingRotation );
1924 attribute.width = record.I32( aRingRotation + 4 );
1925 attribute.x = record.I32( aRingRotation + 8 );
1926 attribute.y = record.I32( aRingRotation + 12 );
1927 attribute.orientation = toBasicAngle( record.I32( aRingRotation + 16 ) );
1928 attribute.mirrored = record.I32( aRingRotation + 20 ) != 0;
1929 return attribute;
1930 };
1931
1932 for( const auto& [partIndex, fieldStart] : m_partFieldStart )
1933 {
1934 if( fieldStart < 0 )
1935 continue;
1936
1937 if( partIndex >= m_parts.size() || static_cast<uint32_t>( fieldStart ) >= aText.physicalCount )
1938 THROW_IO_ERROR( "Invalid PADS placement field-presentation link" );
1939
1940 std::vector<ATTRIBUTE> attributes;
1941 std::set<uint32_t> visited;
1942 uint32_t fieldIndex = static_cast<uint32_t>( fieldStart );
1943
1944 while( true )
1945 {
1946 if( !visited.insert( fieldIndex ).second )
1947 THROW_IO_ERROR( "Cyclic PADS placement field-list link" );
1948
1949 if( fieldIndex >= aText.physicalCount )
1950 THROW_IO_ERROR( "Invalid PADS placement field-list link" );
1951
1952 ATTRIBUTE attribute = fieldAttribute( fieldIndex );
1953 SDB_RECORD metadata = m_sdb.RecordAt( aRecordBase + static_cast<size_t>( fieldIndex + 1 ) * aRecordSize );
1954 uint32_t presentation = metadata.U32( 24 );
1955 uint8_t fieldKind = static_cast<uint8_t>( presentation >> 16 );
1956 attribute.visible = ( fieldKind & 0x20 ) != 0;
1957
1958 if( ( presentation & 0x11000000U ) == 0x11000000U )
1959 {
1960 attribute.hjust = "CENTER";
1961 attribute.vjust = "CENTER";
1962 }
1963 else
1964 {
1965 attribute.hjust = "LEFT";
1966 attribute.vjust = ( presentation & 0x20000000U ) ? "UP" : "DOWN";
1967 }
1968
1969 if( ( fieldKind & 0x1FU ) == 0x03 )
1970 attribute.name = "Part Type";
1971 else if( ( fieldKind & 0x1FU ) == 0x02 )
1972 attribute.name = "Ref.Des.";
1973
1974 if( !attribute.name.empty() )
1975 attributes.push_back( std::move( attribute ) );
1976
1977 uint32_t association = metadata.U32( 12 );
1978
1979 uint8_t associationKind = static_cast<uint8_t>( association >> 24 );
1980
1981 if( associationKind == 0x16 )
1982 {
1983 if( ( association & 0x00FFFFFFU ) != partIndex )
1984 THROW_IO_ERROR( "Mismatched PADS placement field-list owner" );
1985
1986 break;
1987 }
1988
1989 if( associationKind != 0x08 )
1990 THROW_IO_ERROR( "Invalid PADS placement field-list terminator" );
1991
1992 fieldIndex = association & 0x00FFFFFFU;
1993 }
1994
1995 m_parts[partIndex].attributes = std::move( attributes );
1996 }
1997}
1998
1999
2000void BINARY_PARSER::parseFreeText( const SDB_SECTION& aText, size_t aRecordBase, size_t aRecordSize,
2001 size_t aRingRotation, size_t aPoolBase, size_t aPoolHi )
2002{
2003 auto cStringStartAt = [this, aPoolHi]( size_t aAbs ) -> bool
2004 {
2005 if( aAbs >= aPoolHi )
2006 return false;
2007
2008 size_t e = aAbs;
2009
2010 while( e < aPoolHi && m_data[e] != 0 )
2011 {
2012 if( m_data[e] < 0x20 || m_data[e] >= 0x7F )
2013 return false;
2014
2015 ++e;
2016 }
2017
2018 return e > aAbs;
2019 };
2020
2021 for( uint32_t index = 0; index < aText.physicalCount; ++index )
2022 {
2023 SDB_RECORD geom = m_sdb.RecordAt( aRecordBase + static_cast<size_t>( index ) * aRecordSize );
2024 SDB_RECORD meta = m_sdb.RecordAt( aRecordBase + static_cast<size_t>( index + 1 ) * aRecordSize );
2025
2026 if( meta.U16( 4 ) != 0xFFFE || meta.U32( 12 ) != 0 || ( aRecordSize == 72 && meta.U32( 28 ) != 0x49000000 ) )
2027 continue;
2028
2029 uint32_t layerWord = meta.U32( 24 );
2030
2031 if( ( layerWord >> 16 ) != 0x0020 || geom.I32( aRingRotation ) <= 0 || geom.I32( aRingRotation + 4 ) <= 0 )
2032 continue;
2033
2034 size_t soff = aPoolBase + meta.U32( 8 );
2035
2036 if( soff < aPoolBase || soff >= aPoolHi || ( soff != aPoolBase && m_data[soff - 1] != 0 )
2037 || !cStringStartAt( soff ) )
2038 {
2039 continue;
2040 }
2041
2042 std::string content = m_sdb.RecordAt( soff ).Str( 0, aPoolHi - soff );
2043
2044 if( content.empty() )
2045 continue;
2046
2047 int32_t height = geom.I32( aRingRotation );
2048 int32_t linewidth = geom.I32( aRingRotation + 4 );
2049 int32_t x = geom.I32( aRingRotation + 8 );
2050 int32_t y = geom.I32( aRingRotation + 12 );
2051 int32_t angleRaw = geom.I32( aRingRotation + 16 );
2052
2053 TEXT text;
2054 text.content = content;
2055 text.location.x = toBasicCoordX( x );
2056 text.location.y = toBasicCoordY( y );
2057 text.height = static_cast<double>( height );
2058 text.width = static_cast<double>( linewidth );
2059 text.layer = static_cast<int>( layerWord & 0xFF );
2060 text.rotation = toBasicAngle( angleRaw );
2061
2062 m_texts.push_back( text );
2063 }
2064}
2065
2066
2067// Section 60 is a heterogeneous node table (via records, route corner nodes, free slots),
2068// discriminated by a type byte four bytes from the end of its circular fixed-stride record.
2069// Every other field is defined relative to that byte T and is stride-independent:
2070// T-27 i32 raw X T-23 i32 raw Y
2071// T+0 u8 type (0x0E = via) T+1 u16 net index, biased by 3 (see parseNetNamesNew)
2072// T+5 u8 saved start layer T+6 u8 saved end layer
2073// The layer numbers are stored in the low five bits of the following logical record's head.
2074// Valid, unequal in-range endpoints distinguish vias from other 0x0E junctions.
2075// The logical ring begins stride-31 bytes before the direct physical section-60 extent. Verified
2076// against the compiled Kaitai view on all 597 distinct corpus binaries.
2077//
2078// The T+1 net index is real and exact for vias. Grouping vias by its raw value reproduces the
2079// true per-net partition at 100% purity on BR350430B (42 vias) and OC_LTE_BASEBAND (3943),
2080// against a 7-9% shuffled-value control, so the field identifies the net even where the name
2081// lookup cannot. Only the 0-2 slots, whose nets live outside section 23, fail to resolve.
2082//
2083// The other row types are a different matter. No u16 field anywhere in the record resolves a net
2084// for the 0x16 or 0x00 rows above 4.2%, against a 0.14% random-assignment control and a
2085// section-23 name table independently verified as 979 of 980 real net names, so per-node net
2086// membership for non-via nodes is not in this table and has to come from somewhere else. The
2087// section-23 self-pointer route is untested rather than negative -- only 73 of 983 records carry
2088// that field on the file it was tried on.
2089static constexpr int VIA_TYPE = 0x0E;
2090
2091
2092static bool viaRecordValid( const BINARY_CURSOR& aCur, size_t aTypeByte )
2093{
2094 return aCur.U8At( aTypeByte ) == VIA_TYPE && aCur.U8At( aTypeByte + 4 ) == 0x17
2095 && ( aCur.U8At( aTypeByte + 5 ) & 0x02 ) != 0;
2096}
2097
2098
2099std::map<uint32_t, size_t> BINARY_PARSER::parseRouteJunctionNets() const
2100{
2101 constexpr uint32_t JUNCTION_TAG = 0x3C000000;
2102 constexpr uint32_t ROUTE_CHAIN_TAG = 0x18000000;
2103 constexpr uint32_t TAG_MASK = 0xFF000000;
2104 constexpr uint32_t INDEX_MASK = 0x00FFFFFF;
2105
2106 const SDB_SECTION* relationships = getSection( 49 );
2107 const SDB_SECTION* junctions = getSection( SECTION::Vias );
2108 const SDB_SECTION* nets = getSection( SECTION::Nets );
2109 const SDB_SECTION* routeChains = getSection( SECTION::Connections );
2110
2111 if( !relationships || !junctions || !nets || !routeChains )
2112 THROW_IO_ERROR( "Missing PADS route-relationship controllers" );
2113
2114 std::map<uint32_t, size_t> result;
2115 std::vector<std::pair<uint32_t, size_t>> junctionSignals;
2116 size_t cursor = relationships->physicalOffset;
2117 const size_t end = cursor + relationships->physicalBytes;
2118 size_t signalIndex = 0;
2119
2120 auto readU32 = [&]()
2121 {
2122 if( cursor > end || end - cursor < 4 )
2123 THROW_IO_ERROR( "Invalid PADS route-relationship extent" );
2124
2125 uint32_t value = m_cursor.U32At( cursor );
2126 cursor += 4;
2127 return value;
2128 };
2129
2130 while( cursor < end )
2131 {
2132 if( signalIndex >= nets->count )
2133 THROW_IO_ERROR( "Invalid PADS route-relationship signal count" );
2134
2135 for( int direction = 0; direction < 2; ++direction )
2136 {
2137 uint32_t numRelationships = readU32();
2138
2139 for( uint32_t relationship = 0; relationship < numRelationships; ++relationship )
2140 {
2141 uint32_t objectId = readU32();
2142 uint32_t numValues = readU32();
2143
2144 if( numValues > ( end - cursor ) / 4 )
2145 THROW_IO_ERROR( "Invalid PADS route-relationship value count" );
2146
2147 uint32_t expectedObjectTag = direction == 0 ? JUNCTION_TAG : ROUTE_CHAIN_TAG;
2148 uint32_t objectIndex = objectId & INDEX_MASK;
2149 uint32_t objectLimit = direction == 0 ? junctions->count : routeChains->count;
2150
2151 if( ( objectId & TAG_MASK ) != expectedObjectTag || objectIndex >= objectLimit )
2152 THROW_IO_ERROR( "Invalid PADS route-relationship object identifier" );
2153
2154 if( direction == 0 )
2155 {
2156 junctionSignals.emplace_back( objectIndex, signalIndex );
2157 }
2158
2159 uint32_t expectedValueTag = direction == 0 ? ROUTE_CHAIN_TAG : JUNCTION_TAG;
2160 uint32_t valueLimit = direction == 0 ? routeChains->count : junctions->count;
2161
2162 for( uint32_t valueIndex = 0; valueIndex < numValues; ++valueIndex )
2163 {
2164 uint32_t value = readU32();
2165
2166 if( ( value & TAG_MASK ) != expectedValueTag || ( value & INDEX_MASK ) >= valueLimit )
2167 THROW_IO_ERROR( "Invalid PADS route-relationship member identifier" );
2168 }
2169 }
2170 }
2171
2172 ++signalIndex;
2173 }
2174
2175 if( cursor != end || ( signalIndex != m_nets.size() && signalIndex != nets->count ) )
2176 THROW_IO_ERROR( "Invalid PADS route-relationship framing" );
2177
2178 for( const auto& [junctionIndex, sourceSignalIndex] : junctionSignals )
2179 {
2180 std::optional<size_t> netIndex;
2181
2182 if( signalIndex == nets->count )
2183 {
2184 auto netIt = m_sec23RecordToNet.find( static_cast<uint32_t>( sourceSignalIndex ) );
2185
2186 if( netIt != m_sec23RecordToNet.end() )
2187 netIndex = netIt->second;
2188 }
2189 else
2190 {
2191 netIndex = sourceSignalIndex;
2192 }
2193
2194 if( !netIndex )
2195 continue;
2196
2197 auto [it, inserted] = result.emplace( junctionIndex, *netIndex );
2198
2199 if( !inserted && it->second != *netIndex )
2200 THROW_IO_ERROR( "Conflicting PADS route-junction relationship" );
2201 }
2202
2203 return result;
2204}
2205
2206
2208{
2209 // Section 60 identifies via instances and their net/via-definition ordinals. Sections
2210 // 62-64 store routed copper as 48-byte object descriptors, a layer table, and compressed
2211 // 12-byte geometry cells; a separate 32-byte header arena carries width, layer, and links.
2212 const SDB_SECTION* entry60 = getSection( SECTION::Vias );
2213
2214 if( !entry60 )
2215 THROW_IO_ERROR( "Missing PADS via controller" );
2216
2217 if( entry60->count == 0 )
2218 return;
2219
2220 std::map<std::string, ROUTE> routes = seedRoutesFromVias( decodeViaLocations() );
2221
2222 decodeRoutedCopper( routes );
2223
2224 // Vias seeded from the junction controller are emitted even when the route-object
2225 // controller is empty, so a board with stitching but no routed copper keeps them.
2226 for( auto& [netName, route] : routes )
2227 {
2228 if( route.tracks.empty() && route.vias.empty() )
2229 continue;
2230
2231 m_routes.push_back( std::move( route ) );
2232 }
2233}
2234
2235
2236std::vector<BINARY_PARSER::VIA_LOCATION> BINARY_PARSER::decodeViaLocations()
2237{
2238 m_junctionHandleNets.clear();
2239
2240 const SDB_SECTION* entry60 = getSection( SECTION::Vias );
2241
2242 if( !entry60 )
2243 THROW_IO_ERROR( "Missing PADS via controller" );
2244
2245 if( entry60->stride == 0 )
2246 THROW_IO_ERROR( "Invalid PADS via-controller stride" );
2247
2248 uint32_t stride = entry60->stride;
2249
2250 if( stride < 31 || entry60->physicalCount != entry60->count )
2251 THROW_IO_ERROR( "Invalid PADS via-controller framing" );
2252
2253 // Section 60 is a ring rotated left to its X field. Its logical record-grid
2254 // origin is therefore stride-31 bytes before the directly framed physical range.
2255 std::vector<size_t> typeBytes;
2256 const size_t ringRotation = stride - 31;
2257
2258 if( entry60->physicalOffset >= ringRotation
2259 && m_cursor.InBounds( entry60->physicalOffset, entry60->physicalBytes ) )
2260 {
2261 const size_t base = entry60->physicalOffset - ringRotation;
2262
2263 for( uint32_t rec = 0; rec < entry60->physicalCount; ++rec )
2264 typeBytes.push_back( base + static_cast<size_t>( rec ) * stride + stride - 4 );
2265
2266 logResolvedBase( 60, "physicalRing", base, entry60->physicalOffset );
2267 }
2268
2269 // A nonempty controller must have one directly framed physical record per directory item.
2270 if( typeBytes.empty() )
2271 THROW_IO_ERROR( "Invalid PADS via-controller extent" );
2272
2273 const std::map<uint32_t, size_t> junctionNets = parseRouteJunctionNets();
2274
2275 for( size_t junction = 0; junction < typeBytes.size(); ++junction )
2276 {
2277 auto netIt = junctionNets.find( static_cast<uint32_t>( junction ) );
2278
2279 if( netIt == junctionNets.end() )
2280 continue;
2281
2282 if( netIt->second >= m_nets.size() || typeBytes[junction] < 27 )
2283 THROW_IO_ERROR( "Invalid PADS route-junction net mapping" );
2284
2285 const size_t typeByte = typeBytes[junction];
2286 uint32_t objectHandle = m_cursor.U32At( typeByte - 19 );
2287
2288 if( objectHandle != 0 )
2289 m_junctionHandleNets[objectHandle].insert( netIt->second );
2290 }
2291
2292 std::vector<VIA_LOCATION> viaLocations;
2293
2294 for( size_t junction = 0; junction < typeBytes.size(); ++junction )
2295 {
2296 size_t typeByte = typeBytes[junction];
2297 uint8_t type = m_cursor.U8At( typeByte );
2298 bool isVia = type == VIA_TYPE && viaRecordValid( m_cursor, typeByte );
2299
2300 if( !isVia )
2301 continue;
2302
2303 if( typeByte < 27 || !m_cursor.InBounds( typeByte - 27, 8 ) )
2304 continue;
2305
2306 int32_t vx = m_cursor.I32At( typeByte - 27 );
2307 int32_t vy = m_cursor.I32At( typeByte - 23 );
2308
2309 std::string netName;
2310 uint32_t netIdx = m_cursor.U16At( typeByte + 1 );
2311 auto it = m_sec23IndexToNet.find( netIdx );
2312 auto relationshipIt = junctionNets.find( static_cast<uint32_t>( junction ) );
2313
2314 if( relationshipIt != junctionNets.end() && relationshipIt->second < m_nets.size() )
2315 {
2316 netName = m_nets[relationshipIt->second].name;
2317 }
2318 else if( it != m_sec23IndexToNet.end() )
2319 {
2320 netName = it->second;
2321 }
2322
2324 via.x = vx;
2325 via.y = vy;
2326 via.netName = std::move( netName );
2327 via.viaIndex = m_cursor.U8At( typeByte - 3 );
2328 via.relationshipNet = relationshipIt != junctionNets.end();
2329 viaLocations.push_back( std::move( via ) );
2330 }
2331
2332 // Co-located vias are one physical via serialized once per junction that touches it, so the
2333 // survivor takes the strongest net evidence any of them carries.
2334 std::map<std::pair<int32_t, int32_t>, VIA_LOCATION> uniqueVias;
2335
2336 for( VIA_LOCATION& via : viaLocations )
2337 {
2338 auto [it, inserted] = uniqueVias.try_emplace( std::make_pair( via.x, via.y ) );
2339
2340 if( inserted )
2341 {
2342 it->second = std::move( via );
2343 continue;
2344 }
2345
2346 VIA_LOCATION& existing = it->second;
2347
2348 if( existing.viaIndex != via.viaIndex )
2349 THROW_IO_ERROR( "Conflicting co-located PADS via definitions" );
2350
2351 if( via.relationshipNet )
2352 {
2353 if( existing.relationshipNet && existing.netName != via.netName )
2354 THROW_IO_ERROR( "Conflicting co-located PADS via relationships" );
2355
2356 existing.netName = std::move( via.netName );
2357 existing.relationshipNet = true;
2358 }
2359 else if( !existing.relationshipNet && !via.netName.empty() )
2360 {
2361 if( !existing.netName.empty() && existing.netName != via.netName )
2362 THROW_IO_ERROR( "Conflicting co-located PADS via net state" );
2363
2364 existing.netName = std::move( via.netName );
2365 }
2366 }
2367
2368 std::vector<VIA_LOCATION> deduplicated;
2369 deduplicated.reserve( uniqueVias.size() );
2370
2371 for( auto& [coordinate, via] : uniqueVias )
2372 deduplicated.push_back( std::move( via ) );
2373
2374 return deduplicated;
2375}
2376
2377
2378std::map<std::string, ROUTE> BINARY_PARSER::seedRoutesFromVias( const std::vector<VIA_LOCATION>& aVias ) const
2379{
2380 std::map<std::string, ROUTE> routes;
2381
2382 for( const VIA_LOCATION& via : aVias )
2383 {
2384 ROUTE& route = routes[via.netName];
2385 route.net_name = via.netName;
2386
2387 VIA viaDef;
2388 viaDef.location.x = static_cast<double>( via.x );
2389 viaDef.location.y = static_cast<double>( via.y );
2390 viaDef.start_layer = 1;
2391 viaDef.end_layer = m_parameters.layer_count;
2392
2393 if( via.viaIndex >= 0 && static_cast<size_t>( via.viaIndex ) < m_decalNameTable.size() )
2394 {
2395 auto decalIt = m_decals.find( m_decalNameTable[via.viaIndex] );
2396
2397 if( decalIt != m_decals.end() )
2398 {
2399 auto stackIt = decalIt->second.pad_stacks.find( 1 );
2400
2401 if( stackIt == decalIt->second.pad_stacks.end() )
2402 stackIt = decalIt->second.pad_stacks.find( 0 );
2403
2404 if( stackIt != decalIt->second.pad_stacks.end() && !stackIt->second.empty() )
2405 {
2406 viaDef.stack = stackIt->second;
2407
2408 auto spanIt = decalIt->second.drill_spans.find( stackIt->first );
2409
2410 if( spanIt != decalIt->second.drill_spans.end() && spanIt->second.first > 0
2411 && spanIt->second.second > 0 )
2412 {
2413 // The pair is stored in either order and is not bounded by the layer
2414 // count, so normalize and range-check it here the way the ASCII path
2415 // does rather than let a stale byte surface as an unusable via span
2416 int spanStart = std::min( spanIt->second.first, spanIt->second.second );
2417 int spanEnd = std::max( spanIt->second.first, spanIt->second.second );
2418
2419 if( spanEnd <= m_parameters.layer_count )
2420 {
2421 viaDef.start_layer = spanStart;
2422 viaDef.end_layer = spanEnd;
2423 }
2424 }
2425 }
2426 }
2427 }
2428
2429 if( viaDef.stack.empty() )
2430 {
2431 const std::string name = via.viaIndex >= 0 && static_cast<size_t>( via.viaIndex ) < m_decalNameTable.size()
2432 ? m_decalNameTable[via.viaIndex]
2433 : std::string( "<out-of-range>" );
2435 wxString::Format( "Invalid PADS via padstack reference %d (%s)", via.viaIndex, name.c_str() ) );
2436 }
2437
2438 route.vias.push_back( std::move( viaDef ) );
2439 }
2440
2441 return routes;
2442}
2443
2444
2445void BINARY_PARSER::decodeRoutedCopper( std::map<std::string, ROUTE>& aRoutes )
2446{
2447 const SDB_SECTION* routeObjects = getSection( SECTION::RouteObjects );
2448 const SDB_SECTION* routeLayers = getSection( SECTION::RouteLayers );
2449 const SDB_SECTION* routeCells = getSection( SECTION::RouteCells );
2450
2451 if( !routeObjects || !routeLayers || !routeCells )
2452 THROW_IO_ERROR( "Missing PADS route controllers" );
2453
2454 if( routeLayers->count == 0 || static_cast<uint64_t>( routeLayers->count ) * 2 != routeLayers->totalBytes )
2455 {
2456 THROW_IO_ERROR( "Invalid PADS route-layer controller extent" );
2457 }
2458
2459 if( static_cast<uint64_t>( routeCells->count ) * 12 != routeCells->totalBytes )
2460 THROW_IO_ERROR( "Invalid PADS route-cell controller extent" );
2461
2462 if( routeObjects->count == 0 )
2463 {
2464 if( routeObjects->totalBytes != 0 || routeCells->count != 0 )
2465 THROW_IO_ERROR( "Invalid empty PADS route-object controller" );
2466
2467 return;
2468 }
2469
2470 if( ( routeObjects->stride != 36 && routeObjects->stride != 48 )
2471 || static_cast<uint64_t>( routeObjects->count ) * routeObjects->stride != routeObjects->totalBytes )
2472 {
2473 THROW_IO_ERROR( "Invalid PADS route-object controller extent" );
2474 }
2475
2476 struct ROUTE_OBJECT
2477 {
2478 int32_t width = 0;
2479 uint32_t style = 0;
2480 uint32_t cellCount = 0;
2481 };
2482
2483 size_t objectBytes = routeObjects->physicalBytes;
2484 size_t layerTable = routeLayers->physicalOffset;
2485 std::vector<int> serializedLayerOrder;
2486 bool legacyObjects = routeObjects->stride == 36;
2487 size_t objectStride = legacyObjects ? 36 : 48;
2488 size_t widthOffset = legacyObjects ? 8 : 20;
2489 size_t cellCountOffset = legacyObjects ? 24 : 36;
2490
2491 auto ringU32 = [&]( size_t aRingStart, size_t aOffset )
2492 {
2493 uint32_t value = 0;
2494
2495 for( size_t byte = 0; byte < 4; ++byte )
2496 {
2497 size_t physical = aRingStart + ( aOffset + byte ) % objectBytes;
2498 value |= static_cast<uint32_t>( m_cursor.U8At( physical ) ) << ( byte * 8 );
2499 }
2500
2501 return value;
2502 };
2503
2504 std::vector<bool> seenLayers( routeLayers->physicalCount, false );
2505 serializedLayerOrder.reserve( routeLayers->physicalCount );
2506
2507 for( uint32_t layer = 0; layer < routeLayers->physicalCount; ++layer )
2508 {
2509 uint16_t serializedLayer = m_cursor.U16At( layerTable + static_cast<size_t>( layer ) * 2 );
2510
2511 if( serializedLayer >= routeLayers->physicalCount || seenLayers[serializedLayer] )
2512 THROW_IO_ERROR( "Invalid PADS route-layer permutation" );
2513
2514 seenLayers[serializedLayer] = true;
2515 serializedLayerOrder.push_back( serializedLayer );
2516 }
2517
2518 if( layerTable == 0 )
2519 return;
2520
2521 size_t ringStart = routeObjects->physicalOffset;
2522 std::vector<ROUTE_OBJECT> objects;
2523
2524 for( uint32_t i = 0; i < routeObjects->physicalCount; ++i )
2525 {
2526 size_t base = ( 32 + static_cast<size_t>( i ) * objectStride ) % objectBytes;
2527 ROUTE_OBJECT object;
2528
2529 object.width = static_cast<int32_t>( ringU32( ringStart, base + widthOffset ) ) * 4;
2530 object.style = ringU32( ringStart, base + ( legacyObjects ? 20 : 32 ) );
2531 object.cellCount = ringU32( ringStart, base + cellCountOffset );
2532 objects.push_back( object );
2533 }
2534
2535 struct ROUTE_CELL
2536 {
2537 int32_t x1 = 0;
2538 int32_t y = 0;
2539 int32_t x2 = 0;
2540 };
2541
2542 size_t cellStart = routeCells->physicalOffset;
2543 std::vector<ROUTE_CELL> cells;
2544
2545 for( uint32_t i = 0; i < routeCells->count; ++i )
2546 {
2547 size_t base = cellStart + static_cast<size_t>( i ) * 12;
2548 int32_t first = m_cursor.I32At( base );
2549 int32_t second = m_cursor.I32At( base + 4 );
2550 int32_t third = m_cursor.I32At( base + 8 );
2551
2552 cells.push_back( { first, second, third } );
2553 }
2554
2555 size_t cursor = 0;
2556
2557 struct DECODED_TRACK
2558 {
2559 TRACK track;
2560 std::string netName;
2561 };
2562
2563 std::vector<DECODED_TRACK> decodedPieces;
2564
2565 // Each object's layer, its route-node handle and the nets that handle reaches come from the
2566 // section-25/26/27/29/61 allocator graph, which is decoded whole before any cell is read.
2567 const ROUTE_OBJECT_NODES nodes = resolveRouteObjectNodes( *routeLayers, objects.size(), serializedLayerOrder );
2568
2569 const std::vector<int>& objectLayers = nodes.layers;
2570 const std::vector<uint32_t>& objectHandles = nodes.handles;
2571 const std::map<uint32_t, std::set<size_t>>& routeNodeNets = nodes.handleNets;
2572
2573 struct ROUTE_CHUNK
2574 {
2575 size_t objectIndex = 0;
2576 size_t cellStart = 0;
2577 };
2578
2579 std::vector<ROUTE_CHUNK> chunks;
2580
2581 // The object array is a ring whose logical base is 32 bytes into its physical
2582 // storage. Its last descriptor therefore owns the first cell chunk, followed by
2583 // descriptors 0..N-2. The descriptor cell counts partition the cell stream exactly.
2584 for( size_t sequence = 0; sequence < objects.size(); ++sequence )
2585 {
2586 size_t match = ( sequence + objects.size() - 1 ) % objects.size();
2587
2588 if( cursor + objects[match].cellCount > cells.size() )
2589 THROW_IO_ERROR( "Invalid PADS route-cell partition" );
2590
2591 const ROUTE_OBJECT& object = objects[match];
2592
2593 // 0x100 and 0x1000 are the serialized jumper and via/special bits used by
2594 // the PADS ROUTE writer. Their cells belong to those auxiliary objects, not
2595 // ordinary routed-copper polylines.
2596 if( object.width > 0 && ( object.style & 0x1100 ) == 0 )
2597 chunks.push_back( { match, cursor } );
2598
2599 cursor += object.cellCount;
2600 }
2601
2602 if( cursor != cells.size() )
2603 THROW_IO_ERROR( "Invalid PADS route-cell extent" );
2604
2605 for( const ROUTE_CHUNK& chunk : chunks )
2606 {
2607 const ROUTE_OBJECT& object = objects[chunk.objectIndex];
2608
2609 TRACK track;
2610 track.layer = objectLayers[chunk.objectIndex];
2611 track.width = object.width;
2612
2613 if( track.layer <= 0 || static_cast<size_t>( track.layer ) >= m_layerInfos.size() )
2614 THROW_IO_ERROR( "Invalid PADS route-object layer" );
2615
2616 int routingDirection = m_layerInfos[track.layer].routing_direction;
2617
2618 if( routingDirection < 0 || routingDirection > 4 )
2619 THROW_IO_ERROR( "Invalid PADS routing direction" );
2620
2621 bool fixedIsX = routingDirection == 1;
2622
2623 for( size_t j = 0; j < object.cellCount; ++j )
2624 {
2625 const ROUTE_CELL& cell = cells[chunk.cellStart + j];
2626 double x1 = fixedIsX ? cell.x1 : cell.y;
2627 double y1 = fixedIsX ? cell.y : cell.x1;
2628 double x2 = fixedIsX ? cell.x2 : cell.y;
2629 double y2 = fixedIsX ? cell.y : cell.x2;
2630
2631 if( track.points.empty() || track.points.back().x != x1 || track.points.back().y != y1 )
2632 {
2633 track.points.emplace_back( x1, y1 );
2634 }
2635
2636 if( x1 != x2 || y1 != y2 )
2637 track.points.emplace_back( x2, y2 );
2638 }
2639
2640 if( track.points.size() < 2 )
2641 continue;
2642
2643 auto handleIt = routeNodeNets.find( objectHandles[chunk.objectIndex] );
2644 std::optional<size_t> netIndex;
2645
2646 if( handleIt == routeNodeNets.end() )
2647 THROW_IO_ERROR( "Missing PADS route-object node relationship" );
2648
2649 const std::set<size_t>& handleNets = handleIt->second;
2650
2651 if( handleNets.size() > 1 )
2652 THROW_IO_ERROR( wxString::Format( "Conflicting PADS route-object handle nets "
2653 "(object %zu, handle 0x%08X, nets %zu)",
2654 chunk.objectIndex, objectHandles[chunk.objectIndex],
2655 handleNets.size() ) );
2656
2657 if( handleNets.size() == 1 )
2658 netIndex = *handleNets.begin();
2659
2660 if( !netIndex || *netIndex >= m_nets.size() )
2661 {
2662 const ARC_POINT& first = track.points.front();
2663 const ARC_POINT& last = track.points.back();
2664 THROW_IO_ERROR( fmt::format( "Missing PADS route-object net relationship "
2665 "(object {}, handle 0x{:08X}, layer {}, cells {}, "
2666 "first {:.0f},{:.0f}, last {:.0f},{:.0f})",
2667 chunk.objectIndex, objectHandles[chunk.objectIndex], track.layer,
2668 object.cellCount, first.x, first.y, last.x, last.y ) );
2669 }
2670
2671 decodedPieces.push_back( { std::move( track ), m_nets[*netIndex].name } );
2672 }
2673
2674 for( DECODED_TRACK& decoded : decodedPieces )
2675 {
2676 ROUTE& route = aRoutes[decoded.netName];
2677 route.net_name = decoded.netName;
2678 route.tracks.push_back( std::move( decoded.track ) );
2679 }
2680}
2681
2682
2684BINARY_PARSER::resolveRouteObjectNodes( const SDB_SECTION& aRouteLayers, size_t aObjectCount,
2685 const std::vector<int>& aSerializedLayerOrder )
2686{
2687 ROUTE_OBJECT_NODES nodes;
2688 nodes.layers.assign( aObjectCount, 0 );
2689 nodes.handles.assign( aObjectCount, 0 );
2690
2691 auto padsLayerForSerializedIndex = [&]( size_t aIndex )
2692 {
2693 return aIndex < aSerializedLayerOrder.size() ? aSerializedLayerOrder[aIndex] + 1
2694 : static_cast<int>( aIndex ) + 1;
2695 };
2696
2697 const SDB_SECTION* routeController = getSection( 25 );
2698 const SDB_SECTION* allocatorDescriptors = getSection( 26 );
2699 const SDB_SECTION* layerObjectCounts = getSection( 27 );
2700 const SDB_SECTION* layerObjectHandles = getSection( 29 );
2701 const SDB_SECTION* routeNodes = getSection( 61 );
2702
2703 if( !routeController || !allocatorDescriptors || !layerObjectCounts || !layerObjectHandles || !routeNodes
2704 || !m_cursor.InBounds( routeController->physicalOffset + 180, 8 )
2705 || layerObjectCounts->physicalCount != aRouteLayers.physicalCount )
2706 {
2707 THROW_IO_ERROR( "Invalid PADS route allocator controllers" );
2708 }
2709
2710 std::array<uint16_t, 4> allocatorPageCounts;
2711
2712 for( size_t group = 0; group < allocatorPageCounts.size(); ++group )
2713 allocatorPageCounts[group] = m_cursor.U16At( routeController->physicalOffset + 180 + group * 2 );
2714
2715 size_t nodePageStart =
2716 static_cast<size_t>( allocatorPageCounts[0] ) + allocatorPageCounts[1] + allocatorPageCounts[2];
2717 size_t nodePageCount = allocatorPageCounts[3];
2718
2719 if( nodePageCount == 0 || nodePageStart + nodePageCount > allocatorDescriptors->physicalCount )
2720 THROW_IO_ERROR( "Invalid PADS route node page group" );
2721
2722 struct NODE_PAGE
2723 {
2724 uint32_t base = 0;
2725 uint32_t liveCount = 0;
2726 size_t firstOrdinal = 0;
2727 };
2728
2729 std::vector<NODE_PAGE> nodePages;
2730 size_t allocatedNodes = 0;
2731
2732 for( size_t page = 0; page < nodePageCount; ++page )
2733 {
2734 size_t descriptor = allocatorDescriptors->physicalOffset + ( nodePageStart + page ) * 12;
2735 uint32_t liveCount;
2736
2737 if( page + 1 < nodePageCount )
2738 liveCount = m_cursor.U32At( descriptor + 20 );
2739 else if( allocatedNodes <= routeNodes->physicalCount )
2740 liveCount = routeNodes->physicalCount - allocatedNodes;
2741 else
2742 THROW_IO_ERROR( "Invalid PADS route node page counts" );
2743
2744 nodePages.push_back( { m_cursor.U32At( descriptor ), liveCount, allocatedNodes } );
2745 allocatedNodes += liveCount;
2746 }
2747
2748 if( allocatedNodes != routeNodes->physicalCount )
2749 THROW_IO_ERROR( "Invalid PADS route node extent" );
2750
2751 std::vector<uint32_t> routeNodeHandles;
2752 routeNodeHandles.reserve( routeNodes->physicalCount );
2753
2754 for( const NODE_PAGE& page : nodePages )
2755 {
2756 for( uint32_t index = 0; index < page.liveCount; ++index )
2757 routeNodeHandles.push_back( page.base + index * 56 );
2758 }
2759
2760 std::map<uint32_t, size_t> routeNodeOrdinals;
2761
2762 for( size_t ordinal = 0; ordinal < routeNodeHandles.size(); ++ordinal )
2763 routeNodeOrdinals.emplace( routeNodeHandles[ordinal], ordinal );
2764
2765 std::vector<std::vector<size_t>> routeNodeLinks( routeNodeHandles.size() );
2766
2767 for( size_t ordinal = 0; ordinal < routeNodeHandles.size(); ++ordinal )
2768 {
2769 size_t nodeOffset = routeNodes->physicalOffset + ordinal * 12;
2770
2771 for( uint32_t linkedHandle : { m_cursor.U32At( nodeOffset ), m_cursor.U32At( nodeOffset + 4 ) } )
2772 {
2773 auto linkedIt = routeNodeOrdinals.find( linkedHandle );
2774
2775 if( linkedIt == routeNodeOrdinals.end() )
2776 continue;
2777
2778 routeNodeLinks[linkedIt->second].push_back( ordinal );
2779 }
2780 }
2781
2782 // A node's nets are those of the nearest junctions reachable from it, taking every junction
2783 // at that first distance and stopping there rather than draining the whole component -- a
2784 // deeper junction belongs to a different net and must not bleed into this one. The visited
2785 // marks are stamped with the root ordinal so the buffers survive across roots unallocated.
2786 std::map<uint32_t, std::set<size_t>>& routeNodeNets = nodes.handleNets;
2787 std::vector<uint32_t> visitedStamp( routeNodeHandles.size(), UINT32_MAX );
2788 std::vector<size_t> frontier;
2789 std::vector<size_t> next;
2790
2791 for( size_t root = 0; root < routeNodeHandles.size(); ++root )
2792 {
2793 std::set<size_t> componentNets;
2794
2795 visitedStamp[root] = static_cast<uint32_t>( root );
2796 frontier.assign( 1, root );
2797
2798 while( !frontier.empty() && componentNets.empty() )
2799 {
2800 next.clear();
2801
2802 for( size_t ordinal : frontier )
2803 {
2804 auto netIt = m_junctionHandleNets.find( routeNodeHandles[ordinal] );
2805
2806 if( netIt != m_junctionHandleNets.end() )
2807 componentNets.insert( netIt->second.begin(), netIt->second.end() );
2808
2809 for( size_t linked : routeNodeLinks[ordinal] )
2810 {
2811 if( visitedStamp[linked] == static_cast<uint32_t>( root ) )
2812 continue;
2813
2814 visitedStamp[linked] = static_cast<uint32_t>( root );
2815 next.push_back( linked );
2816 }
2817 }
2818
2819 frontier.swap( next );
2820 }
2821
2822 routeNodeNets.emplace( routeNodeHandles[root], std::move( componentNets ) );
2823 }
2824
2825 auto nodeOrdinal = [&]( uint32_t aHandle ) -> std::optional<size_t>
2826 {
2827 auto it = routeNodeOrdinals.find( aHandle );
2828
2829 if( it != routeNodeOrdinals.end() )
2830 return it->second;
2831
2832 return std::nullopt;
2833 };
2834
2835 std::vector<int> serializedObjectLayers;
2836 std::vector<uint32_t> serializedObjectHandles;
2837 std::map<uint32_t, int> routeHandleLayers;
2838 size_t handleOrdinal = 0;
2839
2840 for( size_t layer = 0; layer < layerObjectCounts->physicalCount; ++layer )
2841 {
2842 uint32_t count = m_cursor.U32At( layerObjectCounts->physicalOffset + layer * 4 );
2843
2844 if( handleOrdinal + count > layerObjectHandles->physicalCount )
2845 THROW_IO_ERROR( "Invalid PADS per-layer route handle counts" );
2846
2847 for( size_t index = 0; index < count; ++index, ++handleOrdinal )
2848 {
2849 uint32_t handle = m_cursor.U32At( layerObjectHandles->physicalOffset + handleOrdinal * 4 );
2850
2851 if( handle == 0 )
2852 continue;
2853
2854 int padsLayer = padsLayerForSerializedIndex( layer );
2855 auto [layerIt, inserted] = routeHandleLayers.emplace( handle, padsLayer );
2856
2857 if( !inserted && layerIt->second != padsLayer )
2858 THROW_IO_ERROR( "Conflicting PADS route-handle layers" );
2859
2860 auto ordinal = nodeOrdinal( handle );
2861
2862 if( !ordinal )
2863 THROW_IO_ERROR( "Invalid PADS route node handle" );
2864
2865 uint32_t classTag = m_cursor.U32At( routeNodes->physicalOffset + *ordinal * 12 + 8 );
2866
2867 if( ( classTag & 0x00800000 ) != 0 )
2868 {
2869 serializedObjectLayers.push_back( padsLayer );
2870 serializedObjectHandles.push_back( handle );
2871 }
2872 }
2873 }
2874
2875 if( handleOrdinal != layerObjectHandles->physicalCount || serializedObjectLayers.size() != aObjectCount )
2876 {
2877 THROW_IO_ERROR( "Invalid PADS route object-node mapping" );
2878 }
2879
2880 for( size_t sequence = 0; sequence < serializedObjectLayers.size(); ++sequence )
2881 {
2882 size_t object = ( sequence + aObjectCount - 1 ) % aObjectCount;
2883 nodes.layers[object] = serializedObjectLayers[sequence];
2884 nodes.handles[object] = serializedObjectHandles[sequence];
2885 }
2886
2887 return nodes;
2888}
2889
2890
2892{
2893 const SDB_SECTION* sec10 = getSection( SECTION::DrwItems );
2895 const SDB_SECTION* sec12 = getSection( SECTION::Vertices );
2896
2897 if( m_version <= 0x2022 )
2898 return;
2899
2900 if( !sec10 || !sec11 || !sec12 )
2901 THROW_IO_ERROR( "Missing PADS copper-shape controllers" );
2902
2903 const size_t pieceStride = m_version <= 0x2024 ? 16 : 20;
2904
2905 if( sec10->stride != DRW_ITEM::SIZE || sec11->stride != pieceStride || sec12->stride != 12 )
2906 THROW_IO_ERROR( "Invalid PADS copper-shape controller framing" );
2907
2908 constexpr size_t MAX_COPPER_SHAPE_EDGES = 80;
2909
2910 // Section 10 is a circular controller. buildOwnerRuns follows its declared count and the
2911 // serialized owner-to-piece cursors; section 12 vertices start at their physical offset.
2912 for( const auto& [name, run] : m_ownerRuns )
2913 {
2914 if( name.size() < 4 || name.substr( 0, 3 ) != "DRW" )
2915 continue;
2916
2917 const size_t ownerStride = m_version <= 0x2022 ? DRW_ITEM_V2022::SIZE : DRW_ITEM::SIZE;
2918
2919 if( ( run.itemKind & 0xFFFFU ) != 3 )
2920 continue;
2921
2922 uint32_t sec11Index = static_cast<uint32_t>( run.pieceStart );
2923
2924 if( sec11Index >= sec11->count )
2925 continue;
2926
2927 int32_t originX = ringI32( *sec10, DRW_ITEM::ROTATION, ownerStride, run.ownerIndex, DRW_ITEM::ORIGIN_X );
2928 int32_t originY = ringI32( *sec10, DRW_ITEM::ROTATION, ownerStride, run.ownerIndex, DRW_ITEM::ORIGIN_Y );
2929
2930 // fetchOwnerLoop already strips the duplicate closing point, so its own >= 3 success
2931 // condition is the true minimum for a valid polygon (a triangle). The previous minEdges
2932 // of 5 (4 for v2026) was excluding legitimate 4-corner rectangles -- PADS' own corner
2933 // count includes that closing repeat, so a "5 corner" ASCII COPCLS is a plain rectangle,
2934 // not a pentagon. Verified against MC4_PLUS_CSHAPE.pcb's DRW43215695/DRW89204466 (both
2935 // real 4-corner COPCLS rectangles, dropped by the old threshold) and the eight
2936 // DRW_TAG::COPPER_FILL_B rectangles alongside them.
2937 std::vector<VECTOR2I> loop;
2938
2939 if( fetchOwnerLoop( name, MAX_COPPER_SHAPE_EDGES, loop ) )
2940 {
2941 }
2942 else
2943 {
2944 // A circular copper fill (PADS' COPCIR piece type) stores exactly two diametrically
2945 // opposite endpoints -- fetchOwnerLoop above always rejects that shape since it
2946 // never finds a closing point. The bbox-equality cross-check still applies, now
2947 // against the circle's own derived extent (center = midpoint, radius = half the
2948 // point-to-point span): verified against MC4_PLUS_CSHAPE.pcb's DRW9467290, whose
2949 // derived circle bbox matches its declared header bbox exactly.
2950 VECTOR2I p0, p1;
2951
2952 if( !fetchOwnerCirclePoints( name, p0, p1 ) )
2953 continue;
2954
2955 const double cx = ( p0.x + p1.x ) / 2.0;
2956 const double cy = ( p0.y + p1.y ) / 2.0;
2957 const double radius = std::hypot( p1.x - p0.x, p1.y - p0.y ) / 2.0;
2958
2959 constexpr int CIRCLE_SEGMENTS = 32;
2960 loop.clear();
2961
2962 for( int i = 0; i < CIRCLE_SEGMENTS; ++i )
2963 {
2964 double angle = ( 2.0 * M_PI * static_cast<double>( i ) ) / static_cast<double>( CIRCLE_SEGMENTS );
2965 loop.emplace_back( static_cast<int32_t>( std::lround( cx + radius * std::cos( angle ) ) ),
2966 static_cast<int32_t>( std::lround( cy + radius * std::sin( angle ) ) ) );
2967 }
2968 }
2969
2970 COPPER_SHAPE copper;
2971 copper.name = name;
2972 copper.filled = true;
2973
2974 size_t pieceRotation = sec11->totalBytes - ( pieceStride - 8 );
2975 uint32_t levelIndex = ( sec11Index + 1 ) % sec11->count;
2976 uint8_t level = ringU8( *sec11, pieceRotation, pieceStride, levelIndex, 1 );
2977 copper.width =
2978 static_cast<double>( ringI32( *sec11, pieceRotation, pieceStride, sec11Index, pieceStride - 8 ) );
2979 copper.layer = level;
2980
2981 for( const VECTOR2I& pt : loop )
2982 {
2983 int32_t rawX = static_cast<int32_t>( originX + pt.x );
2984 int32_t rawY = static_cast<int32_t>( originY + pt.y );
2985 copper.outline.emplace_back( toBasicCoordX( rawX ), toBasicCoordY( rawY ) );
2986 }
2987
2988 m_copper_shapes.push_back( std::move( copper ) );
2989 }
2990}
2991
2992
2994{
2995 const SDB_SECTION* sec10 = getSection( SECTION::DrwItems );
2996
2997 if( !sec10 || m_version <= 0x2022 || m_ownerRuns.empty() )
2998 return;
2999
3000 // A dimension's leader geometry is the sec12 vertex run of its DIM* DRW owner, laid out in
3001 // sub-piece order: BASPNT1(2v) BASPNT2(2v) ARWLN1(2v) ARWHD1(4v) ARWLN2(2v) ARWHD2(4v)
3002 // EXTLN1(2v) EXTLN2(2v). The measurement endpoints are the two BASPNT first points (run rows
3003 // 0 and 2); the crossbar is the ARWLN1 first point (run row 4). The vertices are absolute
3004 // design coords (no owner-origin shift).
3005 //
3006 // The value-label text lives in a sec8 record bound to the dimension only by anchor
3007 // proximity, which is unreliable when title-block notes share the dimension layer and
3008 // overlap the leader extent. We emit only the exact geometry and leave the override text
3009 // empty, so KiCad recomputes the displayed value from start/end (which equals the PADS value).
3010 for( uint32_t rec = 0; rec < sec10->count; ++rec )
3011 {
3012 std::string name = ringStr( *sec10, DRW_ITEM::ROTATION, DRW_ITEM::SIZE, rec, DRW_ITEM::NAME, 24 );
3013
3014 if( name.size() < 4 || name.substr( 0, 3 ) != "DIM" )
3015 continue;
3016
3017 auto it = m_ownerRuns.find( name );
3018
3019 if( it == m_ownerRuns.end() )
3020 continue;
3021
3022 int32_t startRow = it->second.vertexStart;
3023
3024 int32_t bp1x = 0, bp1y = 0, bp2x = 0, bp2y = 0, arwx = 0, arwy = 0, attr = 0;
3025
3026 if( !sec12Vertex( startRow + 0, bp1x, bp1y, attr ) || !sec12Vertex( startRow + 2, bp2x, bp2y, attr )
3027 || !sec12Vertex( startRow + 4, arwx, arwy, attr ) )
3028 {
3029 continue;
3030 }
3031
3032 DIMENSION dim;
3033 dim.name = name;
3034 dim.x = toBasicCoordX( bp1x );
3035 dim.y = toBasicCoordY( bp1y );
3036
3037 POINT pt1{ toBasicCoordX( bp1x ), toBasicCoordY( bp1y ) };
3038 POINT pt2{ toBasicCoordX( bp2x ), toBasicCoordY( bp2y ) };
3039 dim.points.push_back( pt1 );
3040 dim.points.push_back( pt2 );
3041
3042 // Horizontal vs vertical from the larger BASPNT delta; crossbar_pos is the ARWLN1 first
3043 // point projected onto the measured axis.
3044 dim.is_horizontal = std::abs( bp2x - bp1x ) > std::abs( bp2y - bp1y );
3045 dim.crossbar_pos = dim.is_horizontal ? toBasicCoordY( arwy ) : toBasicCoordX( arwx );
3046
3047 m_dimensions.push_back( std::move( dim ) );
3048 }
3049}
3050
3051
3053{
3054 m_sec12CleanRows = 0;
3055
3056 const SDB_SECTION* sec12 = getSection( SECTION::Vertices );
3057
3058 if( !sec12 )
3059 THROW_IO_ERROR( "Missing PADS vertex controller" );
3060
3061 if( sec12->totalBytes != static_cast<uint64_t>( sec12->count ) * 12 )
3062 THROW_IO_ERROR( "Invalid PADS vertex-controller extent" );
3063
3064 m_sec12CleanRows = static_cast<int32_t>( sec12->count );
3065}
3066
3067
3068uint8_t BINARY_PARSER::ringU8( const SDB_SECTION& aSection, size_t aRotation, size_t aStride, uint32_t aIndex,
3069 size_t aField ) const
3070{
3071 if( aSection.totalBytes == 0 || aIndex >= aSection.count || aField >= aStride )
3072 THROW_IO_ERROR( "Invalid PADS circular-controller record access" );
3073
3074 size_t logical = static_cast<size_t>( aIndex ) * aStride + aField;
3075 size_t physical = ( aRotation + logical ) % aSection.totalBytes;
3076 return m_cursor.U8At( aSection.physicalOffset + physical );
3077}
3078
3079
3080uint32_t BINARY_PARSER::ringU32( const SDB_SECTION& aSection, size_t aRotation, size_t aStride, uint32_t aIndex,
3081 size_t aField ) const
3082{
3083 uint32_t value = 0;
3084
3085 for( size_t byte = 0; byte < 4; ++byte )
3086 value |= static_cast<uint32_t>( ringU8( aSection, aRotation, aStride, aIndex, aField + byte ) ) << ( byte * 8 );
3087
3088 return value;
3089}
3090
3091
3092int32_t BINARY_PARSER::ringI32( const SDB_SECTION& aSection, size_t aRotation, size_t aStride, uint32_t aIndex,
3093 size_t aField ) const
3094{
3095 return static_cast<int32_t>( ringU32( aSection, aRotation, aStride, aIndex, aField ) );
3096}
3097
3098
3099std::string BINARY_PARSER::ringStr( const SDB_SECTION& aSection, size_t aRotation, size_t aStride, uint32_t aIndex,
3100 size_t aField, size_t aLength ) const
3101{
3102 std::string value;
3103 value.reserve( aLength );
3104
3105 for( size_t byte = 0; byte < aLength; ++byte )
3106 {
3107 char c = static_cast<char>( ringU8( aSection, aRotation, aStride, aIndex, aField + byte ) );
3108
3109 if( c == '\0' )
3110 break;
3111
3112 value.push_back( c );
3113 }
3114
3115 return value;
3116}
3117
3118
3120{
3121 m_ownerRuns.clear();
3122
3123 const SDB_SECTION* sec10 = getSection( SECTION::DrwItems );
3124
3125 if( !sec10 )
3126 THROW_IO_ERROR( "Missing PADS drawing controller" );
3127
3128 if( sec10->count == 0 )
3129 return;
3130
3131 const size_t stride = m_version <= 0x2022 ? DRW_ITEM_V2022::SIZE : DRW_ITEM::SIZE;
3132 const size_t nameOffset = m_version <= 0x2022 ? DRW_ITEM_V2022::NAME : DRW_ITEM::NAME;
3133
3134 if( sec10->totalBytes != static_cast<uint64_t>( sec10->count ) * stride )
3135 THROW_IO_ERROR( "Invalid PADS drawing-controller extent" );
3136
3137 for( uint32_t ownerIndex = 0; ownerIndex < sec10->count; ++ownerIndex )
3138 {
3139 std::string name = ringStr( *sec10, DRW_ITEM::ROTATION, stride, ownerIndex, nameOffset, stride - nameOffset );
3140
3141 if( name.empty() || m_ownerRuns.count( name ) )
3142 continue;
3143
3144 uint32_t lagIndex = ( ownerIndex + 1 ) % sec10->count;
3145 OWNER_RUN run;
3146 run.pieceStart = ringI32( *sec10, DRW_ITEM::ROTATION, stride, lagIndex, DRW_ITEM::PIECE_START );
3147 run.vertexStart = ringI32( *sec10, DRW_ITEM::ROTATION, stride, lagIndex, DRW_ITEM::VERTEX_START );
3148 run.arcStart = ringI32( *sec10, DRW_ITEM::ROTATION, stride, lagIndex, DRW_ITEM::ARC_START );
3149 run.pieceCount = ringI32( *sec10, DRW_ITEM::ROTATION, stride, lagIndex, DRW_ITEM::PIECE_COUNT );
3150 run.itemKind = ringU32( *sec10, DRW_ITEM::ROTATION, stride, lagIndex, DRW_ITEM::SUBTYPE_WORD );
3151 run.ownerIndex = ownerIndex;
3152 m_ownerRuns.emplace( std::move( name ), run );
3153 }
3154}
3155
3156
3157SDB_RECORD BINARY_PARSER::arcRecordFor( const SDB_SECTION& aArcParameters, int32_t aArcStart, int32_t aAttr,
3158 const char* aWhat ) const
3159{
3160 uint64_t arcIndex = static_cast<uint64_t>( aArcStart ) + static_cast<uint32_t>( aAttr );
3161
3162 if( aArcStart < 0 || arcIndex >= aArcParameters.count
3163 || aArcParameters.totalBytes != static_cast<uint64_t>( aArcParameters.count ) * 20 )
3164 {
3165 THROW_IO_ERROR( wxString::Format( "Invalid PADS %s arc range", aWhat ) );
3166 }
3167
3168 return m_sdb.RecordAt( aArcParameters.physicalOffset + arcIndex * 20 );
3169}
3170
3171
3172// An arc-parameter record carries only the arc's bounding box, so the center and radius are
3173// derived from it and the sweep from the two corners' angles about that center. The corners are
3174// owner-relative, so the returned center is offset back into the same frame the caller emits.
3175static ARC deriveArc( const SDB_RECORD& aArcRecord, const ARC_VERTEX& aStart, const ARC_VERTEX& aEnd, double aOriginX,
3176 double aOriginY )
3177{
3178 double xmin = aArcRecord.I32( 0 );
3179 double ymin = aArcRecord.I32( 4 );
3180 double xmax = aArcRecord.I32( 8 );
3181 double ymax = aArcRecord.I32( 12 );
3182 double centerX = ( xmin + xmax ) / 2.0;
3183 double centerY = ( ymin + ymax ) / 2.0;
3184 double startAngle = std::atan2( aStart.y - centerY, aStart.x - centerX ) * 180.0 / M_PI;
3185 double endAngle = std::atan2( aEnd.y - centerY, aEnd.x - centerX ) * 180.0 / M_PI;
3186
3187 ARC arc{};
3188 arc.cx = centerX + aOriginX;
3189 arc.cy = centerY + aOriginY;
3190 arc.radius = ( xmax - xmin ) / 2.0;
3191 arc.start_angle = startAngle;
3192 arc.delta_angle = EDA_ANGLE( endAngle - startAngle, DEGREES_T ).Normalize180().AsDegrees();
3193
3194 return arc;
3195}
3196
3197
3199{
3200 const SDB_SECTION* owners = getSection( SECTION::DrwItems );
3202 const SDB_SECTION* vertices = getSection( SECTION::Vertices );
3203 const SDB_SECTION* arcParameters = getSection( SECTION::DecalLibrary );
3204
3205 if( !owners || !pieces || !vertices || !arcParameters )
3206 THROW_IO_ERROR( "Missing PADS outline controllers" );
3207
3208 const size_t ownerStride = m_version <= 0x2022 ? DRW_ITEM_V2022::SIZE : DRW_ITEM::SIZE;
3209 const size_t pieceStride = m_version <= 0x2024 ? 16 : 20;
3210 const size_t pieceHead = pieceStride == 16 ? 8 : 12;
3211 const size_t cornerField = pieceStride == 16 ? 12 : 16;
3212 const size_t originXField = ownerStride == DRW_ITEM_V2022::SIZE ? DRW_ITEM_V2022::ORIGIN_X : DRW_ITEM::ORIGIN_X;
3213 const size_t originYField = originXField + 4;
3214 const size_t pieceRotation = pieces->totalBytes - pieceHead;
3215
3216 for( const auto& [name, run] : m_ownerRuns )
3217 {
3218 if( ( run.itemKind & 0xFFFFU ) != 1 || run.pieceCount <= 0 || run.pieceStart < 0 || run.vertexStart < 0 )
3219 continue;
3220
3221 if( static_cast<uint64_t>( run.pieceStart ) + static_cast<uint64_t>( run.pieceCount ) > pieces->count )
3222 THROW_IO_ERROR( "Invalid PADS board-outline piece range" );
3223
3224 int32_t originX = ringI32( *owners, DRW_ITEM::ROTATION, ownerStride, run.ownerIndex, originXField );
3225 int32_t originY = ringI32( *owners, DRW_ITEM::ROTATION, ownerStride, run.ownerIndex, originYField );
3226 int32_t vertexCursor = run.vertexStart;
3227
3228 for( int32_t piece = 0; piece < run.pieceCount; ++piece )
3229 {
3230 uint32_t pieceIndex = static_cast<uint32_t>( run.pieceStart + piece );
3231 int32_t corners = ringI32( *pieces, pieceRotation, pieceStride, pieceIndex, cornerField );
3232
3233 if( corners < 1
3234 || static_cast<uint64_t>( vertexCursor ) + static_cast<uint64_t>( corners ) > vertices->count )
3235 THROW_IO_ERROR( "Invalid PADS board-outline vertex range" );
3236
3237 std::vector<ARC_VERTEX> decoded;
3238 decoded.reserve( static_cast<size_t>( corners ) );
3239
3240 for( int32_t corner = 0; corner < corners; ++corner )
3241 {
3242 size_t offset = vertices->physicalOffset + static_cast<size_t>( vertexCursor + corner ) * 12;
3243 SDB_RECORD record = m_sdb.RecordAt( offset );
3244 decoded.push_back( { record.I32( 0 ), record.I32( 4 ), record.I32( 8 ) } );
3245 }
3246
3247 vertexCursor += corners;
3248
3249 POLYLINE outline;
3250 outline.layer = 1;
3251 outline.width =
3252 static_cast<double>( ringI32( *pieces, pieceRotation, pieceStride, pieceIndex, pieceStride - 8 ) );
3253 outline.closed = decoded.size() >= 3 && decoded.front().x == decoded.back().x
3254 && decoded.front().y == decoded.back().y;
3255
3256 for( size_t index = 0; index < decoded.size(); ++index )
3257 {
3258 const ARC_VERTEX& vertex = decoded[index];
3259 double rawX = static_cast<double>( vertex.x ) + originX;
3260 double rawY = static_cast<double>( vertex.y ) + originY;
3261
3262 if( index > 0 && decoded[index - 1].attr >= 0 )
3263 {
3264 SDB_RECORD arcRecord =
3265 arcRecordFor( *arcParameters, run.arcStart, decoded[index - 1].attr, "board-outline" );
3266
3267 outline.points.emplace_back( rawX, rawY,
3268 deriveArc( arcRecord, decoded[index - 1], vertex, originX, originY ) );
3269 }
3270 else
3271 {
3272 outline.points.emplace_back( rawX, rawY );
3273 }
3274 }
3275
3276 m_boardOutlines.push_back( std::move( outline ) );
3277 }
3278 }
3279}
3280
3281
3283{
3284 const SDB_SECTION* owners = getSection( SECTION::DrwItems );
3286 const SDB_SECTION* vertices = getSection( SECTION::Vertices );
3287 const SDB_SECTION* arcParameters = getSection( SECTION::DecalLibrary );
3288
3289 if( !owners || !pieces || !vertices || !arcParameters )
3290 THROW_IO_ERROR( "Missing PADS graphic controllers" );
3291
3292 const size_t ownerStride = m_version <= 0x2022 ? DRW_ITEM_V2022::SIZE : DRW_ITEM::SIZE;
3293 const size_t pieceStride = m_version <= 0x2024 ? 16 : 20;
3294 const size_t pieceHead = pieceStride - 8;
3295 const size_t cornerField = pieceStride - 4;
3296 const size_t originXField = ownerStride == DRW_ITEM_V2022::SIZE ? DRW_ITEM_V2022::ORIGIN_X : DRW_ITEM::ORIGIN_X;
3297 const size_t originYField = originXField + 4;
3298 const size_t pieceRotation = pieces->totalBytes - pieceHead;
3299
3300 if( vertices->totalBytes != static_cast<uint64_t>( vertices->count ) * 12
3301 || arcParameters->totalBytes != static_cast<uint64_t>( arcParameters->count ) * 20 )
3302 {
3303 THROW_IO_ERROR( "Invalid PADS graphic-controller framing" );
3304 }
3305
3306 for( const auto& [name, run] : m_ownerRuns )
3307 {
3308 if( ( run.itemKind & 0xFFFFU ) != 0 || name.compare( 0, 3, "DRW" ) != 0 || run.pieceCount <= 0
3309 || run.pieceStart < 0 || run.vertexStart < 0 )
3310 {
3311 continue;
3312 }
3313
3314 if( static_cast<uint64_t>( run.pieceStart ) + static_cast<uint64_t>( run.pieceCount ) > pieces->count )
3315 THROW_IO_ERROR( "Invalid PADS graphic piece range" );
3316
3317 int32_t originX = ringI32( *owners, DRW_ITEM::ROTATION, ownerStride, run.ownerIndex, originXField );
3318 int32_t originY = ringI32( *owners, DRW_ITEM::ROTATION, ownerStride, run.ownerIndex, originYField );
3319 int32_t vertexCursor = run.vertexStart;
3320
3321 for( int32_t piece = 0; piece < run.pieceCount; ++piece )
3322 {
3323 uint32_t pieceIndex = static_cast<uint32_t>( run.pieceStart + piece );
3324 int32_t corners = ringI32( *pieces, pieceRotation, pieceStride, pieceIndex, cornerField );
3325
3326 if( corners < 1
3327 || static_cast<uint64_t>( vertexCursor ) + static_cast<uint64_t>( corners ) > vertices->count )
3328 {
3329 THROW_IO_ERROR( wxString::Format( "Invalid PADS graphic vertex range (%s piece %d, start %d, "
3330 "corners %d, count %u)",
3331 name.c_str(), piece, vertexCursor, corners, vertices->count ) );
3332 }
3333
3334 GRAPHIC_LINE graphic;
3335 graphic.name = name;
3336 graphic.width = ringI32( *pieces, pieceRotation, pieceStride, pieceIndex, pieceStride - 8 );
3337 graphic.layer = ringU8( *pieces, pieceRotation, pieceStride, ( pieceIndex + 1 ) % pieces->count, 1 );
3338 graphic.points.reserve( static_cast<size_t>( corners ) );
3339
3340 std::vector<ARC_VERTEX> decoded;
3341 decoded.reserve( static_cast<size_t>( corners ) );
3342
3343 for( int32_t corner = 0; corner < corners; ++corner )
3344 {
3345 SDB_RECORD record = m_sdb.RecordAt( vertices->physicalOffset
3346 + static_cast<uint32_t>( vertexCursor + corner ) * 12 );
3347 decoded.push_back( { record.I32( 0 ), record.I32( 4 ), record.I32( 8 ) } );
3348 }
3349
3350 vertexCursor += corners;
3351 graphic.closed = decoded.size() >= 3 && decoded.front().x == decoded.back().x
3352 && decoded.front().y == decoded.back().y;
3353
3354 uint8_t pieceType = ringU8( *pieces, pieceRotation, pieceStride, ( pieceIndex + 1 ) % pieces->count, 0 );
3355
3356 if( pieceType == 2 && decoded.size() == 2 )
3357 {
3358 double centerX = ( decoded[0].x + decoded[1].x ) / 2.0 + originX;
3359 double centerY = ( decoded[0].y + decoded[1].y ) / 2.0 + originY;
3360 double radius = std::hypot( decoded[1].x - decoded[0].x, decoded[1].y - decoded[0].y ) / 2.0;
3361 ARC arc{};
3362 arc.cx = centerX;
3363 arc.cy = centerY;
3364 arc.radius = radius;
3365 arc.start_angle = 0.0;
3366 arc.delta_angle = 360.0;
3367 graphic.closed = true;
3368 graphic.points.emplace_back( centerX + radius, centerY, arc );
3369 m_graphicLines.push_back( std::move( graphic ) );
3370 continue;
3371 }
3372
3373 for( size_t index = 0; index < decoded.size(); ++index )
3374 {
3375 const ARC_VERTEX& vertex = decoded[index];
3376 double rawX = static_cast<double>( vertex.x ) + originX;
3377 double rawY = static_cast<double>( vertex.y ) + originY;
3378
3379 if( index > 0 && decoded[index - 1].attr >= 0 )
3380 {
3381 SDB_RECORD arcRecord =
3382 arcRecordFor( *arcParameters, run.arcStart, decoded[index - 1].attr, "graphic" );
3383
3384 graphic.points.emplace_back( rawX, rawY,
3385 deriveArc( arcRecord, decoded[index - 1], vertex, originX, originY ) );
3386 }
3387 else
3388 {
3389 graphic.points.emplace_back( rawX, rawY );
3390 }
3391 }
3392
3393 m_graphicLines.push_back( std::move( graphic ) );
3394 }
3395 }
3396}
3397
3398
3399bool BINARY_PARSER::sec12Vertex( int32_t aRow, int32_t& aX, int32_t& aY, int32_t& aAttr ) const
3400{
3401 const SDB_SECTION* sec12 = getSection( SECTION::Vertices );
3402
3403 if( !sec12 || aRow < 0 || aRow >= m_sec12CleanRows )
3404 return false;
3405
3406 if( sec12->physicalOffset + static_cast<size_t>( aRow + 1 ) * 12 > m_data.size() )
3407 return false;
3408
3409 SDB_RECORD rec = m_sdb.RecordAt( sec12->physicalOffset + static_cast<uint32_t>( aRow ) * 12 );
3410 aX = rec.I32( 0 );
3411 aY = rec.I32( 4 );
3412 aAttr = rec.I32( 8 );
3413 return true;
3414}
3415
3416
3417bool BINARY_PARSER::fetchOwnerLoop( const std::string& aName, size_t aMaxVerts, std::vector<VECTOR2I>& aOut ) const
3418{
3419 aOut.clear();
3420
3421 auto it = m_ownerRuns.find( aName );
3422
3423 if( it == m_ownerRuns.end() )
3424 return false;
3425
3427
3428 if( !pieces || it->second.pieceCount < 1 || it->second.pieceStart < 0 )
3429 return false;
3430
3431 size_t pieceStride = m_version <= 0x2024 ? 16 : 20;
3432 size_t pieceHead = pieceStride == 16 ? 8 : 12;
3433 size_t cornerField = pieceStride == 16 ? 12 : 16;
3434 size_t pieceRotation = pieces->totalBytes - pieceHead;
3435 int32_t corners =
3436 ringI32( *pieces, pieceRotation, pieceStride, static_cast<uint32_t>( it->second.pieceStart ), cornerField );
3437
3438 if( corners < 4 || static_cast<size_t>( corners ) > aMaxVerts + 1 )
3439 return false;
3440
3441 int32_t startRow = it->second.vertexStart;
3442
3443 int32_t firstX = 0;
3444 int32_t firstY = 0;
3445 int32_t attr = 0;
3446
3447 if( !sec12Vertex( startRow, firstX, firstY, attr ) )
3448 return false;
3449
3450 for( int32_t corner = 0; corner < corners - 1; ++corner )
3451 {
3452 int32_t x = 0;
3453 int32_t y = 0;
3454
3455 if( !sec12Vertex( startRow + corner, x, y, attr ) )
3456 return false;
3457
3458 aOut.emplace_back( x, y );
3459 }
3460
3461 int32_t lastX = 0;
3462 int32_t lastY = 0;
3463
3464 if( !sec12Vertex( startRow + corners - 1, lastX, lastY, attr ) || lastX != firstX || lastY != firstY )
3465 {
3466 aOut.clear();
3467 return false;
3468 }
3469
3470 return true;
3471}
3472
3473
3474bool BINARY_PARSER::fetchOwnerCirclePoints( const std::string& aName, VECTOR2I& aP0, VECTOR2I& aP1 ) const
3475{
3476 auto it = m_ownerRuns.find( aName );
3477
3478 if( it == m_ownerRuns.end() )
3479 return false;
3480
3482
3483 if( !pieces || it->second.pieceCount < 1 || it->second.pieceStart < 0 )
3484 return false;
3485
3486 size_t pieceStride = m_version <= 0x2024 ? 16 : 20;
3487 size_t pieceHead = pieceStride == 16 ? 8 : 12;
3488 size_t cornerField = pieceStride == 16 ? 12 : 16;
3489 size_t pieceRotation = pieces->totalBytes - pieceHead;
3490
3491 if( ringI32( *pieces, pieceRotation, pieceStride, static_cast<uint32_t>( it->second.pieceStart ), cornerField )
3492 != 2 )
3493 {
3494 return false;
3495 }
3496
3497 int32_t startRow = it->second.vertexStart;
3498
3499 int32_t x0 = 0, y0 = 0, x1 = 0, y1 = 0, attr = 0;
3500
3501 if( !sec12Vertex( startRow, x0, y0, attr ) || !sec12Vertex( startRow + 1, x1, y1, attr ) )
3502 return false;
3503
3504 if( x0 == x1 && y0 == y1 )
3505 return false;
3506
3507 aP0 = VECTOR2I( x0, y0 );
3508 aP1 = VECTOR2I( x1, y1 );
3509 return true;
3510}
3511
3512
3514{
3515 const SDB_SECTION* sec10 = getSection( SECTION::DrwItems );
3516 const SDB_SECTION* sec12 = getSection( SECTION::Vertices );
3517
3518 if( m_version <= 0x2022 )
3519 return;
3520
3521 if( !sec10 || !sec12 )
3522 THROW_IO_ERROR( "Missing PADS keepout controllers" );
3523
3524 if( sec10->stride < DRW_ITEM::SIZE || sec12->stride < 12 )
3525 THROW_IO_ERROR( "Invalid PADS keepout-controller framing" );
3526
3527 struct Owner
3528 {
3529 std::string name;
3530 int32_t originX = 0;
3531 int32_t originY = 0;
3532 int64_t minX = 0;
3533 int64_t minY = 0;
3534 int64_t maxX = 0;
3535 int64_t maxY = 0;
3536 };
3537
3538 std::vector<Owner> owners;
3539
3540 for( const auto& [name, run] : m_ownerRuns )
3541 {
3542 if( ( run.itemKind & 0xFFFFU ) != 10 )
3543 continue;
3544
3545 Owner owner;
3546 owner.name = name;
3547 owner.originX = ringI32( *sec10, DRW_ITEM::ROTATION, DRW_ITEM::SIZE, run.ownerIndex, DRW_ITEM::ORIGIN_X );
3548 owner.originY = ringI32( *sec10, DRW_ITEM::ROTATION, DRW_ITEM::SIZE, run.ownerIndex, DRW_ITEM::ORIGIN_Y );
3549 owner.minX = static_cast<int64_t>( ringI32( *sec10, DRW_ITEM::ROTATION, DRW_ITEM::SIZE, run.ownerIndex,
3551 - owner.originX;
3552 owner.minY = static_cast<int64_t>( ringI32( *sec10, DRW_ITEM::ROTATION, DRW_ITEM::SIZE, run.ownerIndex,
3554 - owner.originY;
3555 owner.maxX = static_cast<int64_t>( ringI32( *sec10, DRW_ITEM::ROTATION, DRW_ITEM::SIZE, run.ownerIndex,
3557 - owner.originX;
3558 owner.maxY = static_cast<int64_t>( ringI32( *sec10, DRW_ITEM::ROTATION, DRW_ITEM::SIZE, run.ownerIndex,
3560 - owner.originY;
3561
3562 owners.push_back( std::move( owner ) );
3563 }
3564
3565 if( owners.empty() )
3566 return;
3567
3568 constexpr size_t MAX_KEEP_OUT_VERTICES = 80;
3569
3570 for( const Owner& owner : owners )
3571 {
3572 KEEPOUT keepout;
3573 keepout.type = KEEPOUT_TYPE::ALL;
3574
3575 // The owner's vertexStart cursor anchors a contiguous run in sec12 that closes back to
3576 // its first vertex. Vertices are design coordinates; add the DRW raw origin to get RAW.
3577 std::vector<VECTOR2I> structuralLoop;
3578
3579 if( fetchOwnerLoop( owner.name, MAX_KEEP_OUT_VERTICES, structuralLoop ) )
3580 {
3581 for( const VECTOR2I& vertex : structuralLoop )
3582 {
3583 int32_t rawX = owner.originX + static_cast<int32_t>( vertex.x );
3584 int32_t rawY = owner.originY + static_cast<int32_t>( vertex.y );
3585 keepout.outline.emplace_back( toBasicCoordX( rawX ), toBasicCoordY( rawY ) );
3586 }
3587
3588 m_keepouts.push_back( std::move( keepout ) );
3589 continue;
3590 }
3591
3592 // A circle keepout has a degenerate (2-point) sec12 run that does not close. Its geometry
3593 // is the owner record's +96..+108 bbox: center = midpoint, radius = (xmax - xmin) / 2.
3594 int64_t spanX = owner.maxX - owner.minX;
3595 int64_t spanY = owner.maxY - owner.minY;
3596
3597 if( spanX <= 0 || spanX != spanY )
3598 continue;
3599
3600 constexpr int ELLIPSE_SEGMENTS = 32;
3601 double cx = static_cast<double>( owner.minX + owner.maxX ) / 2.0;
3602 double cy = static_cast<double>( owner.minY + owner.maxY ) / 2.0;
3603 double radius = static_cast<double>( spanX ) / 2.0;
3604
3605 for( int i = 0; i < ELLIPSE_SEGMENTS; ++i )
3606 {
3607 double angle = ( 2.0 * M_PI * static_cast<double>( i ) ) / static_cast<double>( ELLIPSE_SEGMENTS );
3608 int32_t rawX = owner.originX + static_cast<int32_t>( std::lround( cx + radius * std::cos( angle ) ) );
3609 int32_t rawY = owner.originY + static_cast<int32_t>( std::lround( cy + radius * std::sin( angle ) ) );
3610 keepout.outline.emplace_back( toBasicCoordX( rawX ), toBasicCoordY( rawY ) );
3611 }
3612
3613 m_keepouts.push_back( std::move( keepout ) );
3614 }
3615}
3616
3617
3619{
3620 // Section 52 is the declared 88-byte outline-owner array. The four-byte state stream after
3621 // section 49 has one word per live section-46 slot; accounting for it places sections 52--55
3622 // directly, with no signature search or phase selection.
3623 //
3624 // Owner record (88 bytes):
3625 // +0 u32 first piece index
3626 // +4 u32 first vertex index
3627 // +8 u32 first arc index (arcs are not yet imported)
3628 // +24 i32 raw XLOC -- each pour owns its own anchor, not a shared board anchor
3629 // +28 i32 raw YLOC
3630 // +70 char name[16]
3631 //
3632 // Piece record (16 bytes), addressed by the owner's piece index:
3633 // +0 u32 corner count
3634 // +4 u32 arc count
3635 // +8 i32 width, BASIC units
3636 // +12 u8 piece type: 0x32 = polygon, 0x33 = circle (two diametrically-opposite corners,
3637 // not a 2-point polygon)
3638 // +13 u8 layer
3639 //
3640 // The vertex array is a flat run of 8-byte local (i32 x, i32 y) pairs.
3641 // Arc records decorate specific corner-to-corner segments with a curve rather than adding
3642 // extra boundary points, so the corner list alone still yields a closed (if not smoothly
3643 // curved) outline; arc-to-curve conversion is not implemented.
3644 static constexpr size_t OWNER_SIZE = 88;
3645 static constexpr size_t PIECE_SIZE = 16;
3646 static constexpr size_t VERTEX_SIZE = 8;
3647
3648 struct POUR_OWNER
3649 {
3650 uint32_t pieceStart = 0;
3651 uint32_t vertexStart = 0;
3652 uint32_t pieceCount = 0;
3653 int32_t rawX = 0;
3654 int32_t rawY = 0;
3655 std::string name;
3656 };
3657
3658 std::vector<POUR_OWNER> owners;
3659
3663
3664 if( !sec52 || !sec53 || !sec54 )
3665 THROW_IO_ERROR( "Missing PADS copper-pour controllers" );
3666
3667 if( sec52->physicalBytes != sec52->count * OWNER_SIZE || sec53->physicalBytes != sec53->count * PIECE_SIZE
3668 || sec54->physicalBytes != sec54->count * VERTEX_SIZE )
3669 THROW_IO_ERROR( "Invalid PADS copper-pour controller framing" );
3670
3671 for( uint32_t index = 0; index < sec52->count; ++index )
3672 {
3673 const size_t offset = sec52->physicalOffset + static_cast<size_t>( index ) * OWNER_SIZE;
3674 const uint8_t outlineType = m_cursor.U8At( offset + 87 );
3675 std::string name = m_cursor.StringAt( offset + 70, 14 );
3676
3677 if( outlineType != 0x32 || name.rfind( "POR", 0 ) != 0 )
3678 continue;
3679
3680 POUR_OWNER owner;
3681 owner.pieceStart = m_cursor.U32At( offset );
3682 owner.vertexStart = m_cursor.U32At( offset + 4 );
3683 owner.rawX = m_cursor.I32At( offset + 24 );
3684 owner.rawY = m_cursor.I32At( offset + 28 );
3685 owner.pieceCount = m_cursor.U32At( offset + 64 );
3686 owner.name = std::move( name );
3687 owners.push_back( std::move( owner ) );
3688 }
3689
3690 for( const POUR_OWNER& owner : owners )
3691 {
3692 uint32_t vertexIndex = owner.vertexStart;
3693
3694 for( uint32_t pieceOrdinal = 0; pieceOrdinal < owner.pieceCount; ++pieceOrdinal )
3695 {
3696 const uint32_t pieceIndex = owner.pieceStart + pieceOrdinal;
3697
3698 if( pieceIndex >= sec53->count )
3699 THROW_IO_ERROR( "Invalid PADS copper-pour piece range" );
3700
3701 const size_t pieceOff = sec53->physicalOffset + static_cast<size_t>( pieceIndex ) * PIECE_SIZE;
3702 uint32_t cornerCount = m_cursor.U32At( pieceOff );
3703 int32_t width = m_cursor.I32At( pieceOff + 8 );
3704 uint8_t pieceType = m_cursor.U8At( pieceOff + 12 );
3705 uint8_t layer = m_cursor.U8At( pieceOff + 13 );
3706
3707 if( cornerCount == 0 || vertexIndex > sec54->count || cornerCount > sec54->count - vertexIndex )
3708 THROW_IO_ERROR( "Invalid PADS copper-pour vertex range" );
3709
3710 const size_t vOff = sec54->physicalOffset + static_cast<size_t>( vertexIndex ) * VERTEX_SIZE;
3711 vertexIndex += cornerCount;
3712
3713 POUR pour;
3714 pour.owner_pour = owner.name;
3715 pour.width = static_cast<double>( width );
3716 pour.layer = static_cast<int>( layer );
3717
3718 if( pieceType == 0x33 && cornerCount == 2 )
3719 {
3720 // Circle piece: the two "corners" are diametrically opposite endpoints, not a
3721 // 2-point polygon -- the downstream zone builder requires at least 3 points and
3722 // would silently drop it. Synthesize a regular polygon approximation instead.
3723 int32_t x0 = owner.rawX + m_cursor.I32At( vOff );
3724 int32_t y0 = owner.rawY + m_cursor.I32At( vOff + 4 );
3725 int32_t x1 = owner.rawX + m_cursor.I32At( vOff + VERTEX_SIZE );
3726 int32_t y1 = owner.rawY + m_cursor.I32At( vOff + VERTEX_SIZE + 4 );
3727
3728 double cx = ( x0 + x1 ) / 2.0;
3729 double cy = ( y0 + y1 ) / 2.0;
3730 double radius = std::hypot( x1 - x0, y1 - y0 ) / 2.0;
3731
3732 static constexpr int CIRCLE_SEGMENTS = 48;
3733
3734 for( int s = 0; s < CIRCLE_SEGMENTS; ++s )
3735 {
3736 double angle = 2.0 * M_PI * s / CIRCLE_SEGMENTS;
3737 int32_t rawX = static_cast<int32_t>( std::lround( cx + radius * std::cos( angle ) ) );
3738 int32_t rawY = static_cast<int32_t>( std::lround( cy + radius * std::sin( angle ) ) );
3739
3740 pour.points.emplace_back( toBasicCoordX( rawX ), toBasicCoordY( rawY ) );
3741 }
3742 }
3743 else
3744 {
3745 for( uint32_t vertex = 0; vertex < cornerCount; ++vertex )
3746 {
3747 size_t offset = vOff + static_cast<size_t>( vertex ) * VERTEX_SIZE;
3748 int32_t localX = m_cursor.I32At( offset );
3749 int32_t localY = m_cursor.I32At( offset + 4 );
3750
3751 pour.points.emplace_back( toBasicCoordX( owner.rawX + localX ),
3752 toBasicCoordY( owner.rawY + localY ) );
3753 }
3754 }
3755
3756 m_pours.push_back( std::move( pour ) );
3757 }
3758 }
3759}
3760
3761
3764{
3765 const SDB_SECTION* section = m_sdb.Section( 69 );
3766
3767 if( !section || section->physicalCount == 0 )
3768 return 0;
3769
3770 constexpr uint64_t CONTROLLER_LEAD_IN = 12;
3771 const uint64_t base = static_cast<uint64_t>( section->physicalOffset ) + CONTROLLER_LEAD_IN;
3772 const uint64_t bytes = static_cast<uint64_t>( section->physicalCount ) * section->stride;
3773
3774 return base + bytes <= m_data.size() ? static_cast<size_t>( base ) : 0;
3775}
3776
3778{
3779 m_layerInfos.clear();
3780
3781 const SDB_SECTION* section = getSection( SECTION::LayerTable );
3782
3783 if( !section )
3784 THROW_IO_ERROR( "Missing PADS layer-stackup controller" );
3785
3786 if( section->stride != 128 && section->stride != 136 && section->stride != 152 )
3787 THROW_IO_ERROR( "Invalid PADS layer-stackup stride" );
3788
3789 static constexpr size_t NAME_LEN = 24;
3790 static constexpr size_t OFF_ROUT = 32;
3791 static constexpr size_t OFF_LAYTH = 52;
3792 static constexpr size_t OFF_COPTH = 56;
3793 static constexpr size_t OFF_DIEL = 60;
3794
3795 auto layerFunction = []( int32_t aSerializedType )
3796 {
3797 switch( aSerializedType )
3798 {
3799 case 0: return PADS_LAYER_FUNCTION::UNASSIGNED;
3800 case 1: return PADS_LAYER_FUNCTION::ROUTING;
3801 case 2: return PADS_LAYER_FUNCTION::DRILL;
3802 case 3: return PADS_LAYER_FUNCTION::SILK_SCREEN;
3803 case 4: return PADS_LAYER_FUNCTION::PASTE_MASK;
3804 case 5: return PADS_LAYER_FUNCTION::SOLDER_MASK;
3805 case 6: return PADS_LAYER_FUNCTION::ASSEMBLY;
3806 default: return PADS_LAYER_FUNCTION::UNKNOWN;
3807 }
3808 };
3809
3810 size_t recordBase = layerStackupBase();
3811
3812 if( recordBase == 0 )
3813 THROW_IO_ERROR( "Missing PADS layer-stackup framing" );
3814
3815 if( !m_cursor.InBounds( recordBase, static_cast<size_t>( section->physicalCount ) * section->stride ) )
3816 THROW_IO_ERROR( "Invalid PADS layer-stackup extent" );
3817
3818 for( size_t k = 0; k < section->physicalCount; ++k )
3819 {
3820 size_t rec = recordBase + k * section->stride;
3821
3822 SDB_RECORD layerRec = m_sdb.RecordAt( rec );
3824 info.number = static_cast<int>( k );
3825 info.name = layerRec.Str( 0, NAME_LEN );
3826
3827 int32_t routingDir = layerRec.I32( OFF_ROUT );
3828 info.routing_direction = routingDir;
3829
3830 info.layer_thickness = static_cast<double>( layerRec.I32( OFF_LAYTH ) );
3831 info.copper_thickness = static_cast<double>( layerRec.I32( OFF_COPTH ) );
3832
3833 float dielectric = 0.0f;
3834 std::memcpy( &dielectric, &m_data[rec + OFF_DIEL], sizeof( float ) );
3835 info.dielectric_constant = static_cast<double>( dielectric );
3836
3837 // The final word of record K-1 owns record K's LAYER_TYPE. The lag leaves the final
3838 // record's word as retained carrier state, like the other rotated flat controllers.
3839 const int32_t serializedType = k > 0 ? m_cursor.I32At( rec - 4 ) : 0;
3840 info.layer_type = k > 0 ? layerFunction( serializedType ) : PADS_LAYER_FUNCTION::UNASSIGNED;
3841
3842 if( info.layer_type == PADS_LAYER_FUNCTION::UNKNOWN )
3843 THROW_IO_ERROR( "Invalid PADS serialized layer type" );
3844
3845 info.is_copper = info.layer_type == PADS_LAYER_FUNCTION::ROUTING;
3846 info.required = info.is_copper;
3847
3848 m_layerInfos.push_back( std::move( info ) );
3849 }
3850}
3851
3852
3853std::vector<LAYER_INFO> BINARY_PARSER::GetLayerInfos() const
3854{
3855 return m_layerInfos;
3856}
3857
3858
3860{
3861 if( m_parts.empty() || m_decals.empty() )
3862 return;
3863
3864 if( usesDirectDecalChain() )
3865 {
3866 // Both old dialects resolve a placement's decal via the direct index in
3867 // m_partDecalIndex, against the decal-name table -- not through the parttype-index chain
3868 // below (v0x2022 does have a parttype-definition table, see parsePartTypeTable, but
3869 // placements don't reference it for their decal).
3870 if( m_partDecalIndex.empty() || m_decalNameTable.empty() )
3871 return;
3872
3873 for( size_t partIdx = 0; partIdx < m_parts.size(); ++partIdx )
3874 {
3875 PART& part = m_parts[partIdx];
3876
3877 if( !part.decal.empty() )
3878 continue;
3879
3880 auto hintIt = m_partDecalIndex.find( partIdx );
3881
3882 if( hintIt == m_partDecalIndex.end() )
3883 continue;
3884
3885 uint32_t decalIndex = hintIt->second;
3886
3887 if( decalIndex >= m_decalNameTable.size() )
3888 continue;
3889
3890 const std::string& decalName = m_decalNameTable[decalIndex];
3891
3892 if( !decalName.empty() && m_decals.count( decalName ) )
3893 part.decal = decalName;
3894 }
3895
3896 return;
3897 }
3898
3899 // Placement -> decal chain: parttype index I from m_partTypeIndex, then
3900 // m_partTypeDecalIndices[I] for the decal_index, then m_decalNameTable[decal_index] for the
3901 // name. The decal-name table covers connectors and mounting holes section 10 lacks, so this
3902 // resolves the full placed set.
3903 if( m_partTypeDecalIndices.empty() || m_decalNameTable.empty() )
3904 return;
3905
3906 for( size_t partIdx = 0; partIdx < m_parts.size(); ++partIdx )
3907 {
3908 PART& part = m_parts[partIdx];
3909
3910 if( !part.decal.empty() )
3911 continue;
3912
3913 auto hintIt = m_partTypeIndex.find( partIdx );
3914
3915 if( hintIt == m_partTypeIndex.end() )
3916 continue;
3917
3918 uint32_t partTypeIdx = hintIt->second;
3919
3920 if( partTypeIdx >= m_partTypeDecalIndices.size() )
3921 continue;
3922
3923 uint8_t alternate = 0;
3924 auto alternateIt = m_partDecalAlternate.find( partIdx );
3925
3926 if( alternateIt != m_partDecalAlternate.end() )
3927 alternate = alternateIt->second;
3928
3929 const std::vector<int32_t>& decalIndices = m_partTypeDecalIndices[partTypeIdx];
3930 int32_t decalIndex = decalIndices.empty() ? -1 : decalIndices.front();
3931
3932 if( alternate < decalIndices.size() )
3933 decalIndex = decalIndices[alternate];
3934
3935 if( decalIndex < 0 || static_cast<size_t>( decalIndex ) >= m_decalNameTable.size() )
3936 continue;
3937
3938 const std::string& decalName = m_decalNameTable[decalIndex];
3939
3940 if( !decalName.empty() && m_decals.count( decalName ) )
3941 part.decal = decalName;
3942
3943 // The *PARTTYPE alias (often a manufacturer part number) is more useful as the
3944 // footprint's BOM value than the physical decal name it resolves to -- e.g. a part
3945 // referencing PARTTYPE GRM15XR71C103KA86D that resolves to decal C-0402 should show
3946 // the part number, not "C-0402", as its value.
3947 if( part.value.empty() && partTypeIdx < m_partTypeNames.size() )
3948 {
3949 const std::string& typeName = m_partTypeNames[partTypeIdx];
3950
3951 if( !typeName.empty() && typeName != part.decal )
3952 part.value = typeName;
3953 }
3954 }
3955}
3956
3957
3958} // namespace PADS_IO
int index
const char * name
double AsDegrees() const
Definition eda_angle.h:116
EDA_ANGLE Normalize180()
Definition eda_angle.h:268
Bounds-checked little-endian read cursor over a PADS binary buffer.
uint8_t U8At(size_t aOffset) const
void decodeRoutedCopper(std::map< std::string, ROUTE > &aRoutes)
std::map< uint32_t, std::string > m_netSelfPtrToName
uint8_t ringU8(const SDB_SECTION &aSection, size_t aRotation, size_t aStride, uint32_t aIndex, size_t aField) const
double toBasicCoordY(int32_t aRawValue) const
ROUTE_OBJECT_NODES resolveRouteObjectNodes(const SDB_SECTION &aRouteLayers, size_t aObjectCount, const std::vector< int > &aSerializedLayerOrder)
std::vector< NET > m_nets
std::string ringStr(const SDB_SECTION &aSection, size_t aRotation, size_t aStride, uint32_t aIndex, size_t aField, size_t aLength) const
bool isValidNetName(const std::string &aName) const
std::map< std::string, uint32_t > m_decalTerminalCount
std::map< size_t, int > m_partClusterId
std::map< size_t, uint32_t > m_partDecalIndex
std::vector< NET_CLASS_RULE_EDGE > collectNetClassRuleEdges(const std::set< uint32_t > &aOwnerSet)
std::map< std::string, ROUTE > seedRoutesFromVias(const std::vector< VIA_LOCATION > &aVias) const
size_t layerStackupBase() const
Base of section 69's layer records after its fixed controller lead-in.
void parsePlacementFields(const SDB_SECTION &aText, size_t aRecordBase, size_t aRecordSize, size_t aRingRotation)
std::vector< VIA_LOCATION > decodeViaLocations()
std::map< uint32_t, size_t > parseRouteJunctionNets() const
std::vector< PART > m_parts
std::map< uint32_t, size_t > m_sec23RecordToNet
std::vector< NET_ANCHOR > m_netAnchors
std::map< size_t, uint8_t > m_partDecalAlternate
std::map< std::string, uint32_t > m_netClassOwner
std::vector< COPPER_SHAPE > m_copper_shapes
void applyPadstackPairs(PART_DECAL &aDecal, const std::vector< std::pair< int32_t, int32_t > > &aPairs, int32_t aStart, int32_t aCount)
static constexpr int32_t ANGLE_SCALE
std::vector< POUR > m_pours
static bool IsBinaryPadsFile(const wxString &aFileName)
Check if a file appears to be a PADS binary PCB file.
std::map< std::string, PART_DECAL > m_decals
std::vector< DIFF_PAIR_DEF > m_diffPairs
std::map< std::string, int32_t > m_decalTerminalStart
std::vector< POLYLINE > m_boardOutlines
void parseFreeText(const SDB_SECTION &aText, size_t aRecordBase, size_t aRecordSize, size_t aRingRotation, size_t aPoolBase, size_t aPoolHi)
bool fetchOwnerCirclePoints(const std::string &aName, VECTOR2I &aP0, VECTOR2I &aP1) const
std::vector< size_t > oldNetRecordOffsets() const
std::map< std::string, int32_t > m_decalStackStart
std::vector< KEEPOUT > m_keepouts
std::map< std::string, int32_t > m_decalStackCount
uint32_t ringU32(const SDB_SECTION &aSection, size_t aRotation, size_t aStride, uint32_t aIndex, size_t aField) const
std::map< size_t, uint32_t > m_partTypeIndex
std::map< uint32_t, size_t > m_placementObjectToPart
std::vector< std::pair< int, int > > m_padStackDrillSpans
const SDB_SECTION * getSection(int aIndex) const
std::vector< DIMENSION > m_dimensions
std::vector< LAYER_INFO > m_layerInfos
void Parse(const wxString &aFileName)
int32_t ringI32(const SDB_SECTION &aSection, size_t aRotation, size_t aStride, uint32_t aIndex, size_t aField) const
double toBasicCoordX(int32_t aRawValue) const
std::vector< std::vector< int32_t > > m_partTypeDecalIndices
void applyNetClassClearances(const std::vector< NET_CLASS_RULE_EDGE > &aEdges, const std::map< uint32_t, size_t > &aOwnerOrdinal)
bool fetchOwnerLoop(const std::string &aName, size_t aMaxVerts, std::vector< VECTOR2I > &aOut) const
PART makePlacementPart(const SDB_RECORD &aRec, int aXOff, std::optional< int > aYOff, int aAngleOff, int aNameOff, const std::string &aRefDes) const
bool sec12Vertex(int32_t aRow, int32_t &aX, int32_t &aY, int32_t &aAttr) const
const std::vector< uint8_t > & m_data
std::vector< std::string > m_partTypeNames
std::map< uint32_t, std::string > m_sec23IndexToNet
std::vector< NET_ANCHOR > m_netConnectionEndpoints
std::vector< std::vector< PAD_STACK_LAYER > > m_padStackPool
std::map< uint32_t, std::set< size_t > > m_junctionHandleNets
std::map< size_t, int32_t > m_partFieldStart
std::vector< ROUTE > m_routes
std::vector< TEXT > m_texts
std::vector< GRAPHIC_LINE > m_graphicLines
std::vector< PART_CLUSTER > m_clusters
std::vector< BIN_NET_CLASS_DEF > m_netClasses
std::vector< std::string > m_decalNameTable
double toBasicAngle(int32_t aRawAngle) const
std::vector< LAYER_INFO > GetLayerInfos() const
std::map< std::string, OWNER_RUN > m_ownerRuns
SDB_RECORD arcRecordFor(const SDB_SECTION &aArcParameters, int32_t aArcStart, int32_t aAttr, const char *aWhat) const
static bool IsSupportedVersion(uint16_t aVersion)
Definition pads_sdb.cpp:43
A bounds-checked reader positioned at one record inside a buffer.
uint32_t U32(size_t aOffset) const
uint8_t U8(size_t aOffset) const
uint16_t U16(size_t aOffset) const
int32_t I32(size_t aOffset) const
std::string Str(size_t aOffset, size_t aMaxLen) const
#define LAYER(n, l)
@ DEGREES_T
Definition eda_angle.h:31
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
constexpr size_t ROTATION
bool ReadFileHeader(const wxString &aFileName, std::vector< uint8_t > &aOut, size_t aCount)
Read at most aCount leading bytes of aFileName into aOut, which is sized to what was actually read.
static bool viaRecordValid(const BINARY_CURSOR &aCur, size_t aTypeByte)
bool HasSdbMagic(const std::vector< uint8_t > &aData, uint8_t aMagic1)
Check the PADS SDB container magic, a leading 0x00 followed by a format-specific second byte.
constexpr uint16_t SDB_RECORD_SENTINEL
constexpr double SDB_BASIC_PER_MIL
static constexpr int DECAL_NAME_OFFSET
Rotation between the physical section cursor and the logical record base.
static void logParsePhase(const char *aWhat, std::chrono::steady_clock::time_point aStart)
Enabled by setting KICAD_PADS_PROFILE.
static ARC deriveArc(const SDB_RECORD &aArcRecord, const ARC_VERTEX &aStart, const ARC_VERTEX &aEnd, double aOriginX, double aOriginY)
static void logResolvedBase(int aSection, const char *aWhat, size_t aResolved, uint32_t aPhysical)
Report a structurally derived physical section base for cross-checking the Kaitai grammar.
bool ReadFileToBuffer(const wxString &aFileName, std::vector< uint8_t > &aOut)
Read an entire file into aOut.
static const std::map< uint8_t, std::string > PAD_SHAPE_NAMES
uint16_t getU16LE(const std::vector< uint8_t > &aData, size_t aOffset)
static const PADSTACK_LAYOUT & padstackLayout(uint16_t aVersion)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
#define KITIME(call)
CITER next(CITER it)
Definition ptree.cpp:120
A polyline point that may instead be an arc segment.
Definition pads_parser.h:64
One board-outline vertex triplet: [i32 X, i32 Y, i32 attr] where attr -1 is a plain corner and attr >...
Arc as center, radius and angles.
Definition pads_parser.h:50
std::string hjust
std::string vjust
Every routed-copper object's PADS layer and route-node handle, indexed by object ordinal,...
std::map< uint32_t, std::set< size_t > > handleNets
One placed via recovered from the section-60 junction ring.
A PADS net class recovered from the binary design-rule graph.
Standalone copper area from the LINES section (type=COPPER), not part of a pour.
std::vector< ARC_POINT > outline
bool filled
COPCLS, COPCIR.
double width
For open polylines.
double x
Origin.
double crossbar_pos
Y for horizontal, X for vertical.
std::vector< POINT > points
Measurement endpoints.
Non-electrical drawing item from the LINES section (type=LINES).
std::vector< ARC_POINT > points
std::vector< ARC_POINT > outline
KEEPOUT_TYPE type
One type-66 net-class rule edge: its owner pointer, rule-detail page, full rule pointer (declaration ...
std::string name
double drill
0 for SMD
std::string shape
R, S, A, O, OF, RF, RT, ST, RA, SA, RC, OC.
bool plated
PTH vs NPTH.
double thermal_outer_diameter
Thermal or void in plane.
double slot_orientation
0-179.999 degrees
double sizeB
Height for rectangles/ovals.
double corner_radius
Always positive.
double sizeA
Diameter or width.
A PADS part cluster (named group of parts).
std::map< int, std::pair< int, int > > drill_spans
std::vector< TERMINAL > terminals
std::map< int, std::vector< PAD_STACK_LAYER > > pad_stacks
std::string value
std::string decal
Primary decal (first in colon-separated list)
std::string name
std::string units
A polyline that may contain arc segments, used for board outlines and graphics.
std::vector< ARC_POINT > points
std::string owner_pour
Parent pour, 7th header field.
std::vector< ARC_POINT > points
std::vector< VIA > vias
std::vector< TRACK > tracks
std::string net_name
The PADS PowerPCB .pcb file is a serialized snapshot of PADS' in-memory SDB (System DataBase) object ...
Definition pads_sdb.h:58
uint32_t physicalCount
Definition pads_sdb.h:66
uint32_t physicalOffset
Definition pads_sdb.h:64
uint32_t physicalBytes
Definition pads_sdb.h:65
uint32_t totalBytes
Definition pads_sdb.h:61
std::string name
std::vector< ARC_POINT > points
std::vector< PAD_STACK_LAYER > stack
KIBIS_PIN * pin
KIBIS_PIN * pinA
int radius
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
int delta
#define M_PI
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683