KiCad PCB EDA Suite
Loading...
Searching...
No Matches
diptrace_pcb_parser.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
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
28
30
31#include <board.h>
34#include <netclass.h>
35#include <footprint.h>
36#include <pad.h>
37#include <pcb_shape.h>
38#include <pcb_text.h>
39#include <pcb_track.h>
40#include <zone.h>
41#include <netinfo.h>
42#include <ki_exception.h>
43#include <layer_ids.h>
44#include <base_units.h>
45#include <string_utils.h>
47#include <stroke_params.h>
48
49#include <wx/log.h>
50#include <trace_helpers.h>
51
52#include <array>
53#include <algorithm>
54#include <cctype>
55#include <cstdlib>
56#include <cstring>
57#include <map>
58#include <set>
59#include <tuple>
60#include <unordered_set>
61
62using namespace DIPTRACE;
63
64// ---------------------------------------------------------------------------
65// Constants
66// ---------------------------------------------------------------------------
67
69static const uint8_t BOUNDARY_STD[] = {
70 0x0F, 0x42, 0x40, // int3(0)
71 0x0F, 0x42, 0x3F, // int3(-1)
72 0x0F, 0x42, 0x3F, // int3(-1)
73 0x3B, 0x9A, 0xCA, 0x00, // int4(0)
74};
75
77static const uint8_t BOUNDARY_ALT[] = {
78 0x0F, 0x42, 0x40, // int3(0)
79 0x0F, 0x42, 0x40, // int3(0)
80 0x0F, 0x42, 0x40, // int3(0)
81 0x3B, 0x9A, 0xCA, 0x00, // int4(0)
82};
83
84static constexpr size_t BOUNDARY_CORE_LEN = 13;
85
87static const uint8_t BOARD_SETTINGS_FONT_MARKER[] = {
88 0x0F, 0x42, 0x44, // int3(4)
89 0x0F, 0x42, 0x44, // int3(4)
90 0x0F, 0x42, 0x40, // int3(0)
91};
92
94static const uint8_t TEXT_SECTION_ZEROS[] = {
95 0x0F, 0x42, 0x40,
96 0x0F, 0x42, 0x40,
97 0x0F, 0x42, 0x40,
98};
99
102static const uint8_t NET_SENTINEL[] = {
103 0x0F, 0x42, 0x40, // int3(0)
104 0x0F, 0x42, 0x3F, // int3(-1)
105 0x0F, 0x42, 0x3F, // int3(-1)
106};
107
108static constexpr size_t NET_SENTINEL_LEN = 9;
109
111static const uint8_t CHAIN_HEADER[] = {
112 0x00, 0x00, 0x00, // 3 zero bytes
113 0x0F, 0x42, 0x3F, // int3(-1)
114};
115
116static constexpr size_t CHAIN_HEADER_LEN = 6;
117
118static constexpr size_t TRACK_NODE_SIZE = 41;
119
121static constexpr int ZONE_FONT_PREAMBLE_TAIL = -20000;
122
124static constexpr size_t NOT_FOUND = std::string::npos;
125
128static constexpr int FONT_BLOCK_SHAPE_VERSION = 46;
129
132static const uint8_t TAHOMA_FONT_PATTERN[] = {
133 0x00, 0x06, // uint16 char count = 6
134 0x00, 0x54, 0x00, 0x61, 0x00, 0x68, // "Tah"
135 0x00, 0x6F, 0x00, 0x6D, 0x00, 0x61, // "oma"
136};
137
138static constexpr size_t TAHOMA_FONT_PATTERN_LEN = 14;
139
143static constexpr size_t FONT_BLOCK_HEADER_SIZE = 25;
144
148static constexpr size_t FONT_PREAMBLE_LABEL_OFFSET = 163;
149static constexpr size_t FONT_PREAMBLE_FIXED_SIZE = 165;
150
154static constexpr size_t FONT_BLOCK_FIXED_SIZE = 72;
155static constexpr size_t FONT_BLOCK_TRAILER_SIZE = 28;
156
159static constexpr size_t COMPONENT_TAIL_SIZE = 37;
160
162static const uint8_t COMPONENT_TAIL_PATTERN[] = {
163 0x0F, 0x42, 0x40, // int3(0)
164 0x3B, 0x9A, 0xCA, 0x00, // int4(0)
165 0x3B, 0x9A, 0xCA, 0x00, // int4(0)
166};
167
168static constexpr size_t COMPONENT_TAIL_PATTERN_LEN = 11;
169
171static constexpr size_t PAD_PRE_HEADER_SIZE = 14; // int3(index) + int3(netIndex) + int4(x) + int4(y)
172static constexpr size_t PAD_DIMENSIONS_SIZE = 16; // int4(w) + int4(h) + int4(drillW) + int4(drillH)
173
176static constexpr size_t PAD_HEADER_PREAMBLE_UTF16 = 74; // v39+ (uint16 string lengths)
177static constexpr size_t PAD_HEADER_PREAMBLE_ASCII = 76; // v37 (int3 string lengths)
178static constexpr size_t PAD_POST_DIM_FIXED_SIZE = 36; // non-polygon post-dimension block
179static constexpr size_t PAD_POST_DIM_HEADER = 11; // fixed portion before polygon vertices
180static constexpr size_t PAD_POST_DIM_TAIL = 25; // fixed portion after polygon vertices
181static constexpr size_t PAD_POLYGON_VERTEX_SIZE = 8; // int4(x) + int4(y) per vertex
182
187static constexpr int PAD_MAX_NET_INDEX = 10000;
188
189
190static uint32_t ReadColorPacked( BINARY_READER& aReader )
191{
192 uint8_t r, g, b;
193 aReader.ReadColor( r, g, b );
194 return ( static_cast<uint32_t>( r ) << 16 )
195 | ( static_cast<uint32_t>( g ) << 8 )
196 | static_cast<uint32_t>( b );
197}
198
199
200static int ReadInt3At( const uint8_t* aData, size_t aPos )
201{
202 return ( ( static_cast<int>( aData[aPos] ) << 16 )
203 | ( static_cast<int>( aData[aPos + 1] ) << 8 )
204 | static_cast<int>( aData[aPos + 2] ) ) - INT3_BIAS;
205}
206
207
208static int ReadInt4At( const uint8_t* aData, size_t aPos )
209{
210 unsigned int raw = ( static_cast<unsigned int>( aData[aPos] ) << 24 )
211 | ( static_cast<unsigned int>( aData[aPos + 1] ) << 16 )
212 | ( static_cast<unsigned int>( aData[aPos + 2] ) << 8 )
213 | static_cast<unsigned int>( aData[aPos + 3] );
214
215 // Subtract in int64 so a raw value with the high bit set cannot overflow the intermediate int.
216 return static_cast<int>( static_cast<int64_t>( raw ) - INT4_BIAS );
217}
218
219
244static bool FindComponentRotation( const uint8_t* aData, size_t aDataSize, size_t aBoundaryOffset,
245 int& aQuarterTurns )
246{
247 static constexpr size_t MAX_LOOKBACK = 4096;
248 static constexpr size_t MAX_LOOKAHEAD = 1024;
249
250 if( aBoundaryOffset < 6 )
251 return false;
252
253 size_t scanStart = aBoundaryOffset > MAX_LOOKBACK ? aBoundaryOffset - MAX_LOOKBACK : 6;
254 size_t scanEnd = std::min( aDataSize, aBoundaryOffset + MAX_LOOKAHEAD );
255
256 bool found = false;
257 size_t bestDistance = MAX_LOOKBACK + MAX_LOOKAHEAD + 1;
258
259 // Each candidate tuple is anchored at its componentId int3 (idPos): the quarter turn is 6 bytes
260 // before it, the int3(-1) sentinel 3 bytes before, the kind int3 3 bytes after, and the small
261 // flag byte 6 bytes after.
262 for( size_t idPos = scanStart; idPos + 7 <= scanEnd; idPos++ )
263 {
264 if( ReadInt3At( aData, idPos - 3 ) != -1 )
265 continue;
266
267 int quarterTurns = ReadInt3At( aData, idPos - 6 );
268 int componentId = ReadInt3At( aData, idPos );
269 int kind = ReadInt3At( aData, idPos + 3 );
270 uint8_t flag = aData[idPos + 6];
271
272 if( quarterTurns < 0 || quarterTurns > 3 || kind < 0 || kind > 3 || flag > 1 )
273 continue;
274
275 if( componentId < 0 || componentId > 100000 )
276 continue;
277
278 size_t distance = ( idPos > aBoundaryOffset ) ? idPos - aBoundaryOffset
279 : aBoundaryOffset - idPos;
280
281 if( distance < bestDistance )
282 {
283 bestDistance = distance;
284 aQuarterTurns = quarterTurns;
285 found = true;
286 }
287 }
288
289 return found;
290}
291
292
293static bool EnvFlagEnabled( const char* aVarName )
294{
295 const char* value = std::getenv( aVarName );
296
297 return value && *value && std::strcmp( value, "0" ) != 0;
298}
299
300
301static bool ShouldDumpPadPostBlock( const wxString& aRefdes )
302{
303 if( !EnvFlagEnabled( "KICAD_DIPTRACE_DUMP_PAD_POST" ) )
304 return false;
305
306 const char* filterRaw = std::getenv( "KICAD_DIPTRACE_DUMP_PAD_REFS" );
307
308 if( !filterRaw || !*filterRaw )
309 return true;
310
311 wxString filter = wxString::FromUTF8( filterRaw ).Lower();
312
313 if( filter == wxT( "*" ) )
314 return true;
315
316 wxString haystack = wxT( "," ) + filter + wxT( "," );
317 wxString needle = wxT( "," ) + aRefdes.Lower() + wxT( "," );
318
319 return haystack.Contains( needle );
320}
321
322
323static bool ShouldDumpPadGap( const wxString& aRefdes )
324{
325 if( !EnvFlagEnabled( "KICAD_DIPTRACE_DUMP_PAD_GAP" ) )
326 return false;
327
328 const char* filterRaw = std::getenv( "KICAD_DIPTRACE_DUMP_PAD_REFS" );
329
330 if( !filterRaw || !*filterRaw )
331 return true;
332
333 wxString filter = wxString::FromUTF8( filterRaw ).Lower();
334
335 if( filter == wxT( "*" ) )
336 return true;
337
338 wxString haystack = wxT( "," ) + filter + wxT( "," );
339 wxString needle = wxT( "," ) + aRefdes.Lower() + wxT( "," );
340
341 return haystack.Contains( needle );
342}
343
344
345static bool ShouldDumpComponentHeader( const wxString& aRefdes )
346{
347 if( !EnvFlagEnabled( "KICAD_DIPTRACE_DUMP_COMPONENTS" ) )
348 return false;
349
350 const char* filterRaw = std::getenv( "KICAD_DIPTRACE_DUMP_COMPONENT_REFS" );
351
352 if( !filterRaw || !*filterRaw )
353 return true;
354
355 wxString filter = wxString::FromUTF8( filterRaw ).Lower();
356
357 if( filter == wxT( "*" ) )
358 return true;
359
360 wxString haystack = wxT( "," ) + filter + wxT( "," );
361 wxString needle = wxT( "," ) + aRefdes.Lower() + wxT( "," );
362
363 return haystack.Contains( needle );
364}
365
366
367static bool ShouldDumpFootprintOrientation( const wxString& aRefdes )
368{
369 if( !EnvFlagEnabled( "KICAD_DIPTRACE_DUMP_FOOTPRINT_ORIENT" ) )
370 return false;
371
372 const char* filterRaw = std::getenv( "KICAD_DIPTRACE_DUMP_FOOTPRINT_REFS" );
373
374 if( !filterRaw || !*filterRaw )
375 return true;
376
377 wxString filter = wxString::FromUTF8( filterRaw ).Lower();
378
379 if( filter == wxT( "*" ) )
380 return true;
381
382 wxString haystack = wxT( "," ) + filter + wxT( "," );
383 wxString needle = wxT( "," ) + aRefdes.Lower() + wxT( "," );
384
385 return haystack.Contains( needle );
386}
387
388static bool ShouldDumpNets()
389{
390 return EnvFlagEnabled( "KICAD_DIPTRACE_DUMP_NETS" );
391}
392
393
394static bool ShouldDumpZones()
395{
396 return EnvFlagEnabled( "KICAD_DIPTRACE_DUMP_ZONES" );
397}
398
399
400static void DumpComponentHeader( const DT_COMPONENT& aComp, int aFieldA, int aFieldB,
401 int aFieldC, int aFieldD, int aFieldE, int aFieldF,
402 uint8_t aSep1, uint8_t aSep2, uint8_t aSep3 )
403{
404 if( !ShouldDumpComponentHeader( aComp.refdes ) )
405 return;
406
407 wxLogTrace( traceDiptraceIo,
408 wxT( "DipTrace: component ref=%s value=%s pat=%s lib=%s flags=[%u,%u,%u,%u] "
409 "layer=%d pos=(%d,%d) rot=%d fieldA=%d fieldB=%d fieldC=%d fieldD=%d "
410 "fieldE=%d fieldF=%d sep=[%u,%u,%u] bbox=(%d,%d) "
411 "qturns=%d hasQ=%d "
412 "boundary=0x%06zX str=0x%06zX hdrEnd=0x%06zX regionEnd=0x%06zX" ),
413 aComp.refdes, aComp.value, aComp.patternName, aComp.libraryPath,
414 static_cast<unsigned int>( aComp.flags.size() > 0 ? aComp.flags[0] : 0 ),
415 static_cast<unsigned int>( aComp.flags.size() > 1 ? aComp.flags[1] : 0 ),
416 static_cast<unsigned int>( aComp.flags.size() > 2 ? aComp.flags[2] : 0 ),
417 static_cast<unsigned int>( aComp.flags.size() > 3 ? aComp.flags[3] : 0 ), aComp.layer, aComp.positionX,
418 aComp.positionY, aComp.rotation, aFieldA, aFieldB, aFieldC, aFieldD, aFieldE, aFieldF,
419 static_cast<unsigned int>( aSep1 ), static_cast<unsigned int>( aSep2 ),
420 static_cast<unsigned int>( aSep3 ), aComp.bboxWidth, aComp.bboxHeight, aComp.placementQuarterTurns,
422 aComp.headerEndOffset, aComp.regionEndOffset );
423}
424
425
426static int32_t ReadRawLE32( const uint8_t* aData, size_t aPos )
427{
428 uint32_t u = static_cast<uint32_t>( aData[aPos] )
429 | ( static_cast<uint32_t>( aData[aPos + 1] ) << 8 )
430 | ( static_cast<uint32_t>( aData[aPos + 2] ) << 16 )
431 | ( static_cast<uint32_t>( aData[aPos + 3] ) << 24 );
432 return static_cast<int32_t>( u );
433}
434
435
436static float ReadRawLEFloat32( const uint8_t* aData, size_t aPos )
437{
438 uint32_t u = static_cast<uint32_t>( aData[aPos] )
439 | ( static_cast<uint32_t>( aData[aPos + 1] ) << 8 )
440 | ( static_cast<uint32_t>( aData[aPos + 2] ) << 16 )
441 | ( static_cast<uint32_t>( aData[aPos + 3] ) << 24 );
442 float out = 0.0f;
443 std::memcpy( &out, &u, sizeof( out ) );
444 return out;
445}
446
447
448static wxString BytesToHex( const uint8_t* aData, size_t aLen );
449
450
451static void DumpComponentRawFields( const DT_COMPONENT& aComp, const uint8_t* aData,
452 size_t aPosXPos, size_t aPosYPos, size_t aRotPos,
453 size_t aFieldCPos, size_t aFieldDPos )
454{
455 if( !ShouldDumpComponentHeader( aComp.refdes ) )
456 return;
457
458 auto dumpOne = [&]( const wxString& aName, size_t aPos )
459 {
460 wxString hex = BytesToHex( aData + aPos, 4 );
461 int32_t leI32 = ReadRawLE32( aData, aPos );
462 wxString leF32 = wxString::FromCDouble( ReadRawLEFloat32( aData, aPos ) );
463 wxLogTrace( traceDiptraceIo,
464 wxT( "DipTrace: component raw ref=%s field=%s off=0x%06zX bytes=[%s] "
465 "le_i32=%d le_f32=%s int4=%d" ),
466 aComp.refdes, aName, aPos, hex, leI32, leF32, ReadInt4At( aData, aPos ) );
467 };
468
469 dumpOne( wxT( "posX" ), aPosXPos );
470 dumpOne( wxT( "posY" ), aPosYPos );
471 dumpOne( wxT( "rotation" ), aRotPos );
472 dumpOne( wxT( "fieldC" ), aFieldCPos );
473 dumpOne( wxT( "fieldD" ), aFieldDPos );
474}
475
476
477static wxString BytesToHex( const uint8_t* aData, size_t aLen )
478{
479 wxString out;
480 out.reserve( aLen * 3 );
481
482 for( size_t i = 0; i < aLen; i++ )
483 {
484 if( i > 0 )
485 out += wxT( " " );
486
487 out += wxString::Format( wxT( "%02X" ), static_cast<unsigned int>( aData[i] ) );
488 }
489
490 return out;
491}
492
493
494static bool IsAngleLikeCode( int aValue )
495{
496 static const int ANGLES[] = {
497 0, 15708, 31416, 47124, 62832, 9000000, 18000000, 27000000, 36000000
498 };
499
500 int absVal = std::abs( aValue );
501
502 for( int target : ANGLES )
503 {
504 if( std::abs( absVal - target ) <= 8 )
505 return true;
506 }
507
508 return false;
509}
510
511
512static void DumpComponentBinaryScan( const DT_COMPONENT& aComp, const uint8_t* aData, size_t aDataSize )
513{
514 if( !EnvFlagEnabled( "KICAD_DIPTRACE_DUMP_COMPONENT_SCAN" )
515 || !ShouldDumpComponentHeader( aComp.refdes ) )
516 {
517 return;
518 }
519
520 size_t scanStart = aComp.boundaryOffset;
521 size_t scanEnd = std::min( aComp.regionEndOffset, aDataSize );
522 bool fullScan = EnvFlagEnabled( "KICAD_DIPTRACE_DUMP_COMPONENT_SCAN_FULL" );
523
524 if( !fullScan && aComp.headerEndOffset > 0 )
525 scanEnd = std::min( scanEnd, aComp.headerEndOffset + 96 );
526
527 if( scanEnd <= scanStart + 4 )
528 return;
529
530 wxLogTrace( traceDiptraceIo,
531 wxT( "DipTrace: comp-scan ref=%s pat=%s boundary=0x%06zX str=0x%06zX "
532 "headerEnd=0x%06zX regionEnd=0x%06zX full=%d scan=[0x%06zX..0x%06zX)" ),
533 aComp.refdes, aComp.patternName, aComp.boundaryOffset, aComp.stringStartOffset, aComp.headerEndOffset,
534 aComp.regionEndOffset, fullScan ? 1 : 0, scanStart, scanEnd );
535
536 for( size_t off = scanStart; off + 3 <= scanEnd; off++ )
537 {
538 int i3 = ReadInt3At( aData, off );
539
540 if( !IsAngleLikeCode( i3 ) )
541 continue;
542
543 wxString hex = BytesToHex( aData + off, 3 );
544 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: comp-scan-hit-i3 ref=%s off=0x%06zX rel=%lld bytes=[%s] int3=%d" ),
545 aComp.refdes, off, static_cast<long long>( off - scanStart ), hex, i3 );
546 }
547
548 for( size_t off = scanStart; off + 4 <= scanEnd; off++ )
549 {
550 int i4 = ReadInt4At( aData, off );
551 int32_t leI32 = ReadRawLE32( aData, off );
552
553 if( !IsAngleLikeCode( i4 ) && !IsAngleLikeCode( static_cast<int>( leI32 ) ) )
554 continue;
555
556 wxString hex = BytesToHex( aData + off, 4 );
557 wxLogTrace( traceDiptraceIo,
558 wxT( "DipTrace: comp-scan-hit ref=%s off=0x%06zX rel=%lld bytes=[%s] "
559 "int4=%d le_i32=%d" ),
560 aComp.refdes, off, static_cast<long long>( off - scanStart ), hex, i4, leI32 );
561 }
562}
563
564
565static void DumpPadPostBlock( const DT_COMPONENT& aComp, const DT_PAD& aPad,
566 const uint8_t* aData, size_t aPostDimPos, size_t aPostDimSize )
567{
568 if( !ShouldDumpPadPostBlock( aComp.refdes ) )
569 return;
570
571 int fieldA = ( aPostDimSize >= 3 ) ? ReadInt3At( aData, aPostDimPos ) : 0;
572 int fieldC = ( aPostDimSize >= 7 ) ? ReadInt3At( aData, aPostDimPos + 4 ) : 0;
573 int fieldE = ( aPostDimSize >= 11 ) ? ReadInt3At( aData, aPostDimPos + 8 ) : 0;
574 int fieldG = ( aPostDimSize >= 15 ) ? ReadInt3At( aData, aPostDimPos + 12 ) : 0;
575 int fieldH = ( aPostDimSize >= 18 ) ? ReadInt3At( aData, aPostDimPos + 15 ) : 0;
576 int fieldI = ( aPostDimSize >= 21 ) ? ReadInt3At( aData, aPostDimPos + 18 ) : 0;
577 int fieldJ = ( aPostDimSize >= 24 ) ? ReadInt3At( aData, aPostDimPos + 21 ) : 0;
578 int fieldM = ( aPostDimSize >= 30 ) ? ReadInt4At( aData, aPostDimPos + 26 ) : 0;
579 int fieldN = ( aPostDimSize >= 34 ) ? ReadInt4At( aData, aPostDimPos + 30 ) : 0;
580 wxString hex = BytesToHex( aData + aPostDimPos, aPostDimSize );
581
582 wxLogTrace( traceDiptraceIo,
583 wxT( "DipTrace: pad-post ref=%s pad=%s label=%s idx=%d net=%d xy=(%d,%d) wh=(%d,%d) "
584 "drill=(%d,%d) mount=%u orient=%u len=%lu "
585 "A=%d C=%d E=%d G=%d H=%d I=%d J=%d M=%d N=%d hex=[%s]" ),
586 aComp.refdes, aPad.number, aPad.label, aPad.index, aPad.netIndex, aPad.x, aPad.y, aPad.width,
587 aPad.height, aPad.drillWidth, aPad.drillHeight, static_cast<unsigned int>( aPad.mountType ),
588 static_cast<unsigned int>( aPad.orientClass ), static_cast<unsigned long>( aPostDimSize ), fieldA,
589 fieldC, fieldE, fieldG, fieldH, fieldI, fieldJ, fieldM, fieldN, hex );
590}
591
592
593static void DumpPadGap( const DT_COMPONENT& aComp, const uint8_t* aData,
594 size_t aGapStart, size_t aGapEnd )
595{
596 if( !ShouldDumpPadGap( aComp.refdes ) )
597 return;
598
599 if( aGapEnd <= aGapStart )
600 {
601 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: pad-gap ref=%s start=0x%06zX end=0x%06zX len=0" ), aComp.refdes,
602 aGapStart, aGapEnd );
603 return;
604 }
605
606 size_t gapLen = aGapEnd - aGapStart;
607 size_t sampleLen = std::min<size_t>( gapLen, 96 );
608 wxString hex = BytesToHex( aData + aGapStart, sampleLen );
609
610 wxString int3Seq;
611 size_t int3Count = std::min<size_t>( 8, gapLen / 3 );
612
613 for( size_t i = 0; i < int3Count; i++ )
614 {
615 if( i > 0 )
616 int3Seq += wxT( "," );
617
618 int3Seq += wxString::Format( wxT( "%d" ),
619 ReadInt3At( aData, aGapStart + i * 3 ) );
620 }
621
622 wxString int4Seq;
623 size_t int4Count = std::min<size_t>( 8, gapLen / 4 );
624
625 for( size_t i = 0; i < int4Count; i++ )
626 {
627 if( i > 0 )
628 int4Seq += wxT( "," );
629
630 int4Seq += wxString::Format( wxT( "%d" ),
631 ReadInt4At( aData, aGapStart + i * 4 ) );
632 }
633
634 wxLogTrace( traceDiptraceIo,
635 wxT( "DipTrace: pad-gap ref=%s start=0x%06zX end=0x%06zX len=%lu "
636 "int3=[%s] int4=[%s] hex[%lu]=[%s]" ),
637 aComp.refdes, aGapStart, aGapEnd, static_cast<unsigned long>( gapLen ), int3Seq, int4Seq,
638 static_cast<unsigned long>( sampleLen ), hex );
639}
640
641
642static void DumpComponentTail( const DT_COMPONENT& aComp, const uint8_t* aData,
643 size_t aTailStart, int aVisibility,
644 uint8_t aSideFlag1, uint8_t aSideFlag2, int aOrderIdx,
645 int aRefdesYOffset, int aValueYOffset,
646 uint8_t aHasOffset, uint8_t aTailTerm )
647{
648 if( !ShouldDumpComponentHeader( aComp.refdes ) )
649 return;
650
651 wxString hex = BytesToHex( aData + aTailStart, COMPONENT_TAIL_SIZE );
652
653 wxLogTrace( traceDiptraceIo,
654 wxT( "DipTrace: component-tail ref=%s off=0x%06zX vis=%d side=[%u,%u] "
655 "order=%d yoff=[%d,%d] hasOffset=%u term=%u hex=[%s]" ),
656 aComp.refdes, aTailStart, aVisibility, static_cast<unsigned int>( aSideFlag1 ),
657 static_cast<unsigned int>( aSideFlag2 ), aOrderIdx, aRefdesYOffset, aValueYOffset,
658 static_cast<unsigned int>( aHasOffset ), static_cast<unsigned int>( aTailTerm ), hex );
659}
660
661
662static void DumpRulesetBlock( int aRuleSetIndex, const wxString& aRuleSetName, int aBlockIndex,
663 const std::array<int, 26>& aValues )
664{
665 if( !EnvFlagEnabled( "KICAD_DIPTRACE_DUMP_RULESETS" ) )
666 return;
667
668 wxString values;
669
670 for( size_t i = 0; i < aValues.size(); i++ )
671 {
672 if( i > 0 )
673 values += wxT( "," );
674
675 values += wxString::Format( wxT( "%d" ), aValues[i] );
676 }
677
678 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: ruleset[%d] '%s' block[%d] values=[%s]" ), aRuleSetIndex, aRuleSetName,
679 aBlockIndex, values );
680}
681
682
683static void DumpZoneHeader( int aZoneIndex, size_t aHeaderPos, const uint8_t* aData, int aFieldA,
684 int aFlags1, int aFlags2, int aFlags3, int aMinWidth, int aClearance,
685 int aMinimumArea, int aSeparator, int aLayer, int aFieldB, int aVtxCount,
686 const wxString& aNetName )
687{
688 if( !ShouldDumpZones() )
689 return;
690
691 wxString headerHex = BytesToHex( aData + aHeaderPos, 30 );
692
693 wxLogTrace( traceDiptraceIo,
694 wxT( "DipTrace: zone[%d] hdr=0x%06zX fieldA=%d flags=[%d,%d,%d] "
695 "lineWidth=%d clearance=%d minimumArea=%d sep=%d layer=%d net=%d('%s') vtx=%d "
696 "hdrHex=[%s]" ),
697 aZoneIndex, aHeaderPos, aFieldA, aFlags1, aFlags2, aFlags3, aMinWidth, aClearance, aMinimumArea,
698 aSeparator, aLayer, aFieldB, aNetName, aVtxCount, headerHex );
699}
700
701
702static void DumpZoneGap( int aZoneIndex, size_t aGapStart, size_t aGapEnd, const uint8_t* aData )
703{
704 if( !ShouldDumpZones() || aGapEnd <= aGapStart )
705 return;
706
707 size_t gapLen = aGapEnd - aGapStart;
708 size_t sampleLen = std::min<size_t>( gapLen, 96 );
709 wxString hex = BytesToHex( aData + aGapStart, sampleLen );
710
711 wxString int3Vals;
712 size_t int3Count = std::min<size_t>( 8, gapLen / 3 );
713
714 for( size_t i = 0; i < int3Count; i++ )
715 {
716 if( i > 0 )
717 int3Vals += wxT( "," );
718
719 int3Vals += wxString::Format( wxT( "%d" ),
720 ReadInt3At( aData, aGapStart + i * 3 ) );
721 }
722
723 wxString int4Vals;
724 size_t int4Count = std::min<size_t>( 6, gapLen / 4 );
725
726 for( size_t i = 0; i < int4Count; i++ )
727 {
728 if( i > 0 )
729 int4Vals += wxT( "," );
730
731 int4Vals += wxString::Format( wxT( "%d" ),
732 ReadInt4At( aData, aGapStart + i * 4 ) );
733 }
734
735 wxLogTrace( traceDiptraceIo,
736 wxT( "DipTrace: zone[%d] gap start=0x%06zX end=0x%06zX len=%lu int3=[%s] int4=[%s] hex[%lu]=[%s]" ),
737 aZoneIndex, aGapStart, aGapEnd, static_cast<unsigned long>( gapLen ), int3Vals, int4Vals,
738 static_cast<unsigned long>( sampleLen ), hex );
739}
740
741
742static void DumpZoneTail( int aZoneIndex, size_t aTailStart, size_t aSearchEnd, const uint8_t* aData )
743{
744 if( !ShouldDumpZones() || aTailStart >= aSearchEnd )
745 return;
746
747 size_t tailLen = std::min<size_t>( aSearchEnd - aTailStart, 256 );
748 size_t sampleLen = std::min<size_t>( tailLen, 96 );
749 wxString hex = BytesToHex( aData + aTailStart, sampleLen );
750
751 wxString int3Vals;
752 size_t int3Count = std::min<size_t>( 8, tailLen / 3 );
753
754 for( size_t i = 0; i < int3Count; i++ )
755 {
756 if( i > 0 )
757 int3Vals += wxT( "," );
758
759 int3Vals += wxString::Format( wxT( "%d" ),
760 ReadInt3At( aData, aTailStart + i * 3 ) );
761 }
762
763 wxString int4Vals;
764 size_t int4Count = std::min<size_t>( 6, tailLen / 4 );
765
766 for( size_t i = 0; i < int4Count; i++ )
767 {
768 if( i > 0 )
769 int4Vals += wxT( "," );
770
771 int4Vals += wxString::Format( wxT( "%d" ),
772 ReadInt4At( aData, aTailStart + i * 4 ) );
773 }
774
775 wxLogTrace( traceDiptraceIo,
776 wxT( "DipTrace: zone[%d] tail start=0x%06zX end=0x%06zX len=%lu int3=[%s] int4=[%s] hex[%lu]=[%s]" ),
777 aZoneIndex, aTailStart, aTailStart + tailLen, static_cast<unsigned long>( tailLen ), int3Vals, int4Vals,
778 static_cast<unsigned long>( sampleLen ), hex );
779}
780
781
786static size_t StringFieldSize( const uint8_t* aData, size_t aDataSize, size_t aPos, int aVersion )
787{
788 if( aVersion <= LEGACY_STRING_VERSION )
789 {
790 if( aPos + 3 > aDataSize )
791 return 0;
792
793 int byteCount = ReadInt3At( aData, aPos );
794
795 if( byteCount < 0 || byteCount > MAX_STRING_CHARS || aPos + 3 + byteCount > aDataSize )
796 return 0;
797
798 return 3 + static_cast<size_t>( byteCount );
799 }
800 else
801 {
802 if( aPos + 2 > aDataSize )
803 return 0;
804
805 int charCount = ( static_cast<int>( aData[aPos] ) << 8 )
806 | static_cast<int>( aData[aPos + 1] );
807
808 if( charCount < 0 || charCount > MAX_STRING_CHARS
809 || aPos + 2 + static_cast<size_t>( charCount ) * 2 > aDataSize )
810 {
811 return 0;
812 }
813
814 return 2 + static_cast<size_t>( charCount ) * 2;
815 }
816}
817
818
819// ===========================================================================
820// Static helpers
821// ===========================================================================
822
823bool PCB_PARSER::TryReadStringAt( const uint8_t* aData, size_t aDataSize,
824 size_t aPos, int aVersion,
825 wxString& aOut, size_t& aNewPos )
826{
827 if( aVersion <= LEGACY_STRING_VERSION )
828 {
829 // v37: int3(byte_count) + ASCII
830 if( aPos + 3 > aDataSize )
831 return false;
832
833 const uint8_t* b = aData + aPos;
834 int byteCount = static_cast<int>( ( static_cast<int>( b[0] ) << 16 )
835 | ( static_cast<int>( b[1] ) << 8 )
836 | static_cast<int>( b[2] ) ) - INT3_BIAS;
837
838 if( byteCount == 0 )
839 {
840 aOut = wxString();
841 aNewPos = aPos + 3;
842 return true;
843 }
844
845 if( byteCount < 0 || byteCount > 500
846 || aPos + 3 + static_cast<size_t>( byteCount ) > aDataSize )
847 {
848 return false;
849 }
850
851 for( int i = 0; i < byteCount; i++ )
852 {
853 char c = static_cast<char>( aData[aPos + 3 + i] );
854
855 if( !( ( c >= 0x20 && c < 0x7F ) || c == '\r' || c == '\n' || c == '\t' ) )
856 return false;
857 }
858
859 aOut = wxString::FromAscii( reinterpret_cast<const char*>( aData + aPos + 3 ),
860 byteCount );
861 aNewPos = aPos + 3 + byteCount;
862 return true;
863 }
864 else
865 {
866 // v39+: uint16-BE(char_count) + UTF-16BE
867 if( aPos + 2 > aDataSize )
868 return false;
869
870 uint16_t cc = ( static_cast<uint16_t>( aData[aPos] ) << 8 ) | aData[aPos + 1];
871
872 if( cc == 0 )
873 {
874 aOut = wxString();
875 aNewPos = aPos + 2;
876 return true;
877 }
878
879 if( cc > 500 || aPos + 2 + static_cast<size_t>( cc ) * 2 > aDataSize )
880 return false;
881
882 wxString result;
883 result.reserve( cc );
884 size_t base = aPos + 2;
885
886 for( uint16_t i = 0; i < cc; i++ )
887 {
888 uint16_t ch = ( static_cast<uint16_t>( aData[base + i * 2] ) << 8 )
889 | aData[base + i * 2 + 1];
890 wxChar wch = static_cast<wxChar>( ch );
891
892 if( !wxIsprint( wch ) && wch != '\r' && wch != '\n' && wch != '\t' )
893 return false;
894
895 result.Append( wch );
896 }
897
898 aOut = result;
899 aNewPos = aPos + 2 + static_cast<size_t>( cc ) * 2;
900 return true;
901 }
902}
903
904
905std::vector<size_t> PCB_PARSER::FindAllBoundaries( const uint8_t* aData, size_t aDataSize,
906 const uint8_t* aPattern, size_t aPatternLen,
907 size_t aStart, size_t aEnd )
908{
909 std::vector<size_t> offsets;
910
911 if( aEnd == 0 || aEnd > aDataSize )
912 aEnd = aDataSize;
913
914 size_t pos = aStart;
915
916 while( pos + aPatternLen <= aEnd && offsets.size() < 100000 )
917 {
918 const uint8_t* found = std::search( aData + pos, aData + aEnd,
919 aPattern, aPattern + aPatternLen );
920
921 if( found == aData + aEnd )
922 break;
923
924 size_t idx = static_cast<size_t>( found - aData );
925 offsets.push_back( idx );
926 pos = idx + 1;
927 }
928
929 return offsets;
930}
931
932
933// ===========================================================================
934// PCB_PARSER implementation
935// ===========================================================================
936
937PCB_PARSER::PCB_PARSER( const wxString& aFileName, BOARD* aBoard ) :
938 m_reader( aFileName ),
939 m_board( aBoard ),
940 m_version( 0 ),
941 m_hasInlineVersion( true ),
943{
944}
945
946
950
951
953{
954 try
955 {
956 // ScanLocatorUseCount() reports the scan fallbacks used by the last Parse(); zero the
957 // per-category counters up front so a reused parser instance does not accumulate them.
963
964 ParseMagic();
966 ParseOutline();
967 if( !m_outline.empty() )
968 {
969 int oxMin = m_outline[0].x;
970 int oxMax = m_outline[0].x;
971 int oyMin = m_outline[0].y;
972 int oyMax = m_outline[0].y;
973
974 for( const DT_VERTEX& v : m_outline )
975 {
976 oxMin = std::min( oxMin, v.x );
977 oxMax = std::max( oxMax, v.x );
978 oyMin = std::min( oyMin, v.y );
979 oyMax = std::max( oyMax, v.y );
980 }
981
982 wxLogTrace( traceDiptraceIo,
983 wxT( "DipTrace: board bbox=(%d,%d)-(%d,%d), outline verts=%zu bounds=(%d,%d)-(%d,%d)" ),
984 m_bboxXMin, m_bboxYMin, m_bboxXMax, m_bboxYMax, m_outline.size(), oxMin, oyMin, oxMax, oyMax );
985 }
986 else
987 {
988 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: board bbox=(%d,%d)-(%d,%d), no parsed outline vertices" ),
990 }
992 ParseLayers();
993 m_postLayersOffset = m_reader.GetOffset();
996 m_postDesignRulesOffset = m_reader.GetOffset();
1000 wxLogTrace( traceDiptraceIo,
1001 wxT( "DipTrace: post-component sections parsed; inferring routing-ref pad nets" ) );
1003
1004 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: applying board settings" ) );
1006 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: creating board outline" ) );
1008 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: creating nets" ) );
1009 CreateNets();
1010
1011 size_t footprintCompCount = 0;
1012 size_t standaloneViaCompCount = 0;
1013
1014 for( const DT_COMPONENT& comp : m_components )
1015 {
1016 if( comp.isStandaloneVia )
1017 standaloneViaCompCount++;
1018 else
1019 footprintCompCount++;
1020 }
1021
1022 wxLogTrace( traceDiptraceIo,
1023 wxT( "DipTrace: creating %zu footprints (skipping %zu standalone-via components)" ),
1024 footprintCompCount, standaloneViaCompCount );
1025
1026 for( const DT_COMPONENT& comp : m_components )
1027 {
1028 if( comp.isStandaloneVia )
1029 continue;
1030
1032 }
1033
1034 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: creating %zu text objects" ), m_textObjects.size() );
1035 for( const DT_TEXT_OBJECT& text : m_textObjects )
1037
1038 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: creating tracks and vias (%zu chains)" ), m_trackChains.size() );
1041 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: creating zones (%zu)" ), m_zones.size() );
1042 CreateZones();
1044
1045 size_t compsWithPads = 0;
1046 size_t compsWithShapes = 0;
1047
1048 for( const DT_COMPONENT& comp : m_components )
1049 {
1050 if( !comp.pads.empty() )
1051 compsWithPads++;
1052
1053 if( !comp.shapes.empty() )
1054 compsWithShapes++;
1055 }
1056
1057 wxLogTrace( traceDiptraceIo,
1058 wxT( "DipTrace v%d: %zu components (%zu with pads, %zu with shapes), "
1059 "%zu nets, %zu track chains, %zu zones" ),
1060 m_version, m_components.size(), compsWithPads, compsWithShapes, m_nets.size(), m_trackChains.size(),
1061 m_zones.size() );
1062 }
1063 catch( const IO_ERROR& )
1064 {
1065 throw;
1066 }
1067 catch( const std::exception& e )
1068 {
1069 THROW_IO_ERROR( wxString::Format(
1070 _( "DipTrace parse error at offset 0x%06zX: %s" ),
1071 m_reader.GetOffset(), wxString::FromUTF8( e.what() ) ) );
1072 }
1073}
1074
1075
1076PCB_LAYER_ID PCB_PARSER::MapLayer( int aDipTraceLayer ) const
1077{
1078 switch( aDipTraceLayer )
1079 {
1080 case 2: return F_SilkS; // Top silk
1081 case 3: return B_SilkS; // Bottom silk
1082 case 4: return F_Mask; // Top solder mask
1083 case 5: return B_Mask; // Bottom solder mask
1084 case 6: return F_Paste; // Top paste
1085 case 7: return B_Paste; // Bottom paste
1086 case 8: return F_Fab; // Top assembly
1087 case 9: return B_Fab; // Bottom assembly
1088 case 10: return Dwgs_User; // Board outline (drawing)
1089 default:
1090 // Inner copper layers: DipTrace indexes them from the top
1091 if( aDipTraceLayer == 0 )
1092 return F_Cu;
1093
1094 if( aDipTraceLayer == 1 )
1095 return B_Cu;
1096
1097 if( aDipTraceLayer >= 14 && aDipTraceLayer <= 44 )
1098 {
1099 int innerIdx = aDipTraceLayer - 14;
1100
1101 if( innerIdx <= 30 )
1102 return static_cast<PCB_LAYER_ID>( In1_Cu + innerIdx * 2 );
1103 }
1104
1105 return UNDEFINED_LAYER;
1106 }
1107}
1108
1109
1110PCB_LAYER_ID PCB_PARSER::MapCopperLayer( int aDipTraceLayer ) const
1111{
1112 auto it = m_copperLayerOrdinalById.find( aDipTraceLayer );
1113
1114 if( it == m_copperLayerOrdinalById.end() )
1115 return UNDEFINED_LAYER;
1116
1117 int ordinal = it->second;
1118 int copperCount = std::max( 2, static_cast<int>( m_layers.size() ) );
1119
1120 if( ordinal <= 0 )
1121 return F_Cu;
1122
1123 if( ordinal >= copperCount - 1 )
1124 return B_Cu;
1125
1126 if( ordinal > 30 )
1127 return UNDEFINED_LAYER;
1128
1129 return static_cast<PCB_LAYER_ID>( In1_Cu + ( ordinal - 1 ) * 2 );
1130}
1131
1132
1133int PCB_PARSER::ToKiCadCoord( int aDipTraceCoord )
1134{
1135 return static_cast<int>( static_cast<int64_t>( aDipTraceCoord ) * 100 / 3 );
1136}
1137
1138
1139double PCB_PARSER::ToKiCadAngleDeg( int aDipTraceAngle )
1140{
1141 return static_cast<double>( aDipTraceAngle ) * DIPTRACE_ANGLE_TO_DEG;
1142}
1143
1144
1145// ---------------------------------------------------------------------------
1146// Section parsers
1147// ---------------------------------------------------------------------------
1148
1150{
1151 uint8_t magicLen = m_reader.ReadByte();
1152
1153 if( magicLen != 7 && magicLen != 11 )
1154 {
1155 THROW_IO_ERROR( wxString::Format(
1156 _( "DipTrace: invalid magic length %u (expected 7 or 11)" ), magicLen ) );
1157 }
1158
1159 std::array<uint8_t, 11> magic = {};
1160 m_reader.ReadBytes( magic.data(), magicLen );
1161
1162 if( std::memcmp( magic.data(), "DTBOARD", 7 ) != 0 )
1163 {
1164 THROW_IO_ERROR( _( "DipTrace: not a valid .dip board file (bad magic)" ) );
1165 }
1166
1167 m_hasInlineVersion = ( magicLen == 7 );
1169
1170 if( !m_hasInlineVersion )
1171 {
1172 std::string magicSuffix( reinterpret_cast<const char*>( magic.data() + 7 ),
1173 magicLen - 7 );
1174
1175 if( magicSuffix.size() != 4
1176 || std::isdigit( static_cast<unsigned char>( magicSuffix[0] ) ) == 0
1177 || magicSuffix[1] != '.'
1178 || std::isdigit( static_cast<unsigned char>( magicSuffix[2] ) ) == 0
1179 || std::isdigit( static_cast<unsigned char>( magicSuffix[3] ) ) == 0 )
1180 {
1181 THROW_IO_ERROR( _( "DipTrace: invalid legacy board version suffix" ) );
1182 }
1183
1184 int parsedMinor = ( magicSuffix[2] - '0' ) * 10 + ( magicSuffix[3] - '0' );
1185
1186 m_version = parsedMinor;
1187 m_reader.SetVersion( m_version );
1188 }
1189}
1190
1191
1193{
1194 if( m_hasInlineVersion )
1195 {
1196 m_version = m_reader.ReadInt3();
1197 m_reader.SetVersion( m_version );
1198 }
1199
1200 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: file version %d" ), m_version );
1201
1202 m_reader.ReadInt4();
1203 m_reader.ReadInt3();
1204 m_reader.ReadInt3();
1205
1206 m_reader.ReadString(); // schematic path
1207
1208 m_reader.ReadByte();
1209
1210 m_bboxXMin = m_reader.ReadInt4();
1211 m_bboxYMin = m_reader.ReadInt4();
1212 m_bboxXMax = m_reader.ReadInt4();
1213 m_bboxYMax = m_reader.ReadInt4();
1214}
1215
1216
1218{
1219 int vertexCount = m_reader.ReadInt3();
1220
1221 // A negative count would sign-extend to a huge size_t in reserve() (std::length_error); a wild
1222 // positive count would over-allocate. Reject before touching the vector, like the other sections.
1223 if( vertexCount < 0 || vertexCount > 1000000 )
1224 THROW_IO_ERROR( wxString::Format( _( "DipTrace import: invalid outline vertex count %d." ),
1225 vertexCount ) );
1226
1227 m_outline.clear();
1228 m_outline.reserve( vertexCount );
1229
1230 for( int i = 0; i < vertexCount; i++ )
1231 {
1232 DT_VERTEX v;
1233 v.x = m_reader.ReadInt4();
1234 v.y = m_reader.ReadInt4();
1235 v.arc = m_reader.ReadByte();
1236 m_outline.push_back( v );
1237 }
1238}
1239
1240
1242{
1243 m_reader.ReadByte(); // end_marker
1244 m_reader.ReadInt3(); // field_a
1245 m_reader.ReadInt3(); // field_b
1246 m_reader.ReadInt4(); // grid_x
1247 m_reader.ReadInt4(); // grid_y
1248 m_reader.ReadByte(); // pad_byte
1249
1250 for( int i = 0; i < 4; i++ )
1251 m_reader.ReadInt4(); // fields_c[4]
1252
1253 for( int i = 0; i < 3; i++ )
1254 m_reader.ReadByte(); // trail_bytes[3]
1255}
1256
1257
1259{
1260 int layerCount = m_reader.ReadInt3();
1261
1262 // Guard against a negative (huge size_t in reserve) or wild positive count before allocating.
1263 if( layerCount < 0 || layerCount > 100000 )
1264 THROW_IO_ERROR( wxString::Format( _( "DipTrace import: invalid layer count %d." ),
1265 layerCount ) );
1266
1267 m_layers.clear();
1269 m_layers.reserve( layerCount );
1270
1271 for( int i = 0; i < layerCount; i++ )
1272 {
1273 DT_LAYER layer;
1274 layer.flag = m_reader.ReadByte();
1275 layer.index = m_reader.ReadInt3();
1276 layer.color = ReadColorPacked( m_reader );
1277 layer.name = m_reader.ReadString();
1278
1279 // field_a encodes the layer Type (0 = Signal, 1 = Plane); field_c carries the plane net
1280 // DipTrace index when the layer is a solid/negative plane (-1 otherwise). Matches the
1281 // CopperLayers <Lay Type=.. NetId=..> oracle.
1282 layer.type = m_reader.ReadInt3(); // field_a
1283 m_reader.ReadInt3(); // field_b
1284 layer.planeNetIndex = m_reader.ReadInt3(); // field_c
1285 layer.fieldD = m_reader.ReadInt4();
1286 m_reader.ReadByte(); // separator
1287
1288 int ordinal = static_cast<int>( m_layers.size() );
1289 m_copperLayerOrdinalById[layer.index] = ordinal;
1290 m_layers.push_back( layer );
1291 }
1292}
1293
1294
1296{
1297 for( int i = 0; i < 6; i++ )
1298 m_reader.ReadInt3();
1299
1300 m_reader.ReadString(); // font name
1301
1302 m_reader.ReadInt4(); // field_a
1303 m_reader.ReadInt4(); // field_b
1304 m_reader.ReadByte(); // flag_a
1305 m_reader.ReadInt4(); // font_height
1306 m_reader.ReadInt4(); // font_width
1307
1309 {
1310 uint8_t legacyPadding[12];
1311 m_reader.ReadBytes( legacyPadding, sizeof( legacyPadding ) );
1312 m_ruleNameCount = m_reader.ReadInt3();
1313 return;
1314 }
1315 else if( m_version <= LEGACY_STRING_VERSION )
1316 {
1317 // v37: 5-byte padding then int3 + int3 + byte before the standard triple
1318 uint8_t pad[5];
1319 m_reader.ReadBytes( pad, 5 );
1320 m_reader.ReadInt3(); // v37_extra_a
1321 m_reader.ReadInt3(); // v37_extra_b
1322 m_reader.ReadByte(); // v37_extra_flag
1323 }
1324 else
1325 {
1326 uint8_t pad[7];
1327 m_reader.ReadBytes( pad, sizeof( pad ) );
1328
1329 const size_t next = m_reader.GetOffset();
1330 const uint8_t* data = m_reader.GetData();
1331
1332 if( next + 2 <= m_reader.GetFileSize() && data[next] == 0 && data[next + 1] > 0
1333 && data[next + 1] <= 100 )
1334 {
1336 return;
1337 }
1338
1339 uint8_t padTail[3];
1340 m_reader.ReadBytes( padTail, sizeof( padTail ) );
1341 }
1342
1343 int fieldCOrGroupCount = m_reader.ReadInt3();
1344
1345 if( fieldCOrGroupCount > 0 )
1346 {
1347 ParsePatternStyleGroups( fieldCOrGroupCount );
1348 }
1349 else
1350 {
1351 int patternGroupCount = m_reader.ReadInt3();
1352 ParsePatternNameGroups( patternGroupCount );
1353 m_ruleNameCount = m_reader.ReadInt3();
1354 }
1355}
1356
1357
1359{
1360 if( aGroupCount < 0 || aGroupCount > 10000 )
1361 {
1362 THROW_IO_ERROR( wxString::Format(
1363 _( "DipTrace: invalid pattern-name group count %d at offset 0x%06zX" ),
1364 aGroupCount, m_reader.GetOffset() - 3 ) );
1365 }
1366
1367 for( int i = 0; i < aGroupCount; i++ )
1368 {
1369 m_reader.ReadString(); // group name
1370 m_reader.ReadInt3(); // field_a
1371 int blockCount = m_reader.ReadInt3();
1372
1373 if( blockCount < 0 || blockCount > 10000 )
1374 {
1375 THROW_IO_ERROR( wxString::Format(
1376 _( "DipTrace: invalid pattern-name block count %d" ), blockCount ) );
1377 }
1378
1379 for( int j = 0; j < blockCount; j++ )
1380 {
1381 m_reader.ReadInt3(); // block_id
1382 m_reader.ReadString(); // block name
1383 }
1384 }
1385}
1386
1387
1389{
1390 if( aGroupCount < 0 || aGroupCount > 10000 )
1391 {
1392 THROW_IO_ERROR( wxString::Format(
1393 _( "DipTrace: invalid pattern-style group count %d at offset 0x%06zX" ),
1394 aGroupCount, m_reader.GetOffset() - 3 ) );
1395 }
1396
1397 m_ruleNameCount = 0;
1398
1399 for( int i = 0; i < aGroupCount; i++ )
1400 {
1401 m_reader.ReadString(); // group name
1402
1403 uint8_t color[3];
1404 m_reader.ReadBytes( color, sizeof( color ) );
1405
1406 m_reader.ReadInt3(); // field_a
1407 m_reader.ReadInt3(); // field_b
1408 m_reader.ReadInt3(); // field_c
1409
1410 int entryCount = m_reader.ReadInt3();
1411
1412 if( entryCount < 0 || entryCount > 10000 )
1413 {
1414 THROW_IO_ERROR( wxString::Format(
1415 _( "DipTrace: invalid pattern-style entry count %d" ), entryCount ) );
1416 }
1417
1418 m_ruleNameCount += entryCount;
1419 }
1420}
1421
1422
1424{
1425 m_reader.ReadString(); // group name
1426 m_reader.ReadByte(); // flag
1427 m_reader.ReadInt3(); // field_a
1428 m_reader.ReadInt3(); // field_b
1429
1430 m_ruleNameCount = m_reader.ReadInt3();
1431
1433 {
1434 THROW_IO_ERROR( wxString::Format(
1435 _( "DipTrace: invalid implicit pattern-style entry count %d" ),
1436 m_ruleNameCount ) );
1437 }
1438}
1439
1440
1442{
1443 m_designRules.clear();
1444 m_viaStyles.clear();
1445 bool dumpRuleSets = EnvFlagEnabled( "KICAD_DIPTRACE_DUMP_RULESETS" );
1446
1447 // Rule names and ViaStyles share the same record structure.
1448 for( int i = 0; i < m_ruleNameCount; i++ )
1449 {
1450 wxString name = m_reader.ReadString();
1451 m_reader.ReadByte(); // flag
1452 int val1 = m_reader.ReadInt4();
1453 int val2 = m_reader.ReadInt4();
1454 int fieldA = m_reader.ReadInt3();
1455 int fieldB = m_reader.ReadInt3();
1456
1457 if( name.StartsWith( wxT( "ViaStyle" ) ) )
1458 {
1459 DT_VIA_STYLE vs;
1460 vs.name = name;
1461 vs.outerDiameter = val1;
1462 vs.drillDiameter = val2;
1463 vs.layer1 = fieldA;
1464 vs.layer2 = fieldB;
1465 m_viaStyles.push_back( vs );
1466 }
1467 else
1468 {
1469 DT_DESIGN_RULE dr;
1470 dr.name = name;
1471 dr.clearance = val1;
1472 dr.trackWidth = val2;
1473 m_designRules.push_back( dr );
1474 }
1475 }
1476
1477 // Global field_c after all entries -- total rule set count
1478 int ruleSetCount = m_reader.ReadInt3();
1479
1480 for( int i = 0; i < ruleSetCount; i++ )
1481 {
1482 wxString setName = m_reader.ReadString();
1483 int setFieldA = m_reader.ReadInt3();
1484 uint8_t flags[4] = { 0, 0, 0, 0 };
1485
1486 for( int f = 0; f < 4; f++ )
1487 flags[f] = m_reader.ReadByte();
1488
1489 int blockCount = m_reader.ReadInt3();
1490
1491 if( dumpRuleSets )
1492 {
1493 wxLogTrace( traceDiptraceIo,
1494 wxT( "DipTrace: ruleset[%d] '%s' fieldA=%d flags=[%u,%u,%u,%u] blocks=%d" ), i, setName,
1495 setFieldA, static_cast<unsigned int>( flags[0] ), static_cast<unsigned int>( flags[1] ),
1496 static_cast<unsigned int>( flags[2] ), static_cast<unsigned int>( flags[3] ), blockCount );
1497 }
1498
1499 for( int b = 0; b < blockCount; b++ )
1500 {
1501 std::array<int, 26> blockValues;
1502
1503 for( int v = 0; v < 25; v++ )
1504 blockValues[v] = m_reader.ReadInt4();
1505
1506 blockValues[25] = 0;
1507 DumpRulesetBlock( i, setName, b, blockValues );
1508 }
1509
1510 m_reader.ReadInt4(); // trailer_a
1511 m_reader.ReadInt4(); // trailer_b
1512
1513 int extraCount = m_reader.ReadInt3();
1514
1515 if( extraCount < 0 || extraCount > 10000 )
1516 {
1517 THROW_IO_ERROR( wxString::Format(
1518 _( "DipTrace: invalid design-rule extra count %d" ), extraCount ) );
1519 }
1520
1521 for( int e = 0; e < extraCount; e++ )
1522 m_reader.ReadInt3();
1523
1524 uint8_t rawPad[4];
1525 m_reader.ReadBytes( rawPad, sizeof( rawPad ) );
1526 m_reader.ReadInt3(); // field_b
1527 m_reader.ReadInt3(); // field_c
1528
1529 if( i + 1 < ruleSetCount )
1531 }
1532}
1533
1534
1536{
1537 static const uint8_t marker[] = { 0x4D, 0x7C, 0x6D, 0x00 };
1538 uint8_t actual[sizeof( marker )] = {};
1539 size_t markerOffset = m_reader.GetOffset();
1540
1541 m_reader.ReadBytes( actual, sizeof( actual ) );
1542
1543 if( std::memcmp( actual, marker, sizeof( marker ) ) != 0 )
1544 {
1545 THROW_IO_ERROR( wxString::Format(
1546 _( "DipTrace: invalid ruleset transition marker at 0x%06zX" ), markerOffset ) );
1547 }
1548
1549 m_reader.ReadInt4(); // field_a
1550 m_reader.ReadInt4(); // field_b
1551 m_reader.ReadInt3(); // field_c
1552
1553 const size_t next = m_reader.GetOffset();
1554 const uint8_t* data = m_reader.GetData();
1555
1556 if( m_version > LEGACY_STRING_VERSION && next + 5 <= m_reader.GetFileSize()
1557 && data[next] == 0x01 && data[next + 1] == 0x00 && data[next + 2] == 0x14
1558 && data[next + 3] == 0x89 && data[next + 4] == 0x03 )
1559 {
1560 uint8_t suffix[5];
1561 m_reader.ReadBytes( suffix, sizeof( suffix ) );
1562 }
1563 else
1564 {
1565 m_reader.ReadInt3(); // field_d
1566 }
1567}
1568
1569
1570// ---------------------------------------------------------------------------
1571std::vector<std::pair<size_t, size_t>>
1573{
1574 const uint8_t* data = m_reader.GetData();
1575 size_t fileSize = m_reader.GetFileSize();
1576
1577 static const int STRING_OFFSETS[] = { 14, 15, 16, 17, 18, 19, 20 };
1578
1579 auto isBoundaryCoreAt = [&]( size_t aPos ) -> bool
1580 {
1581 return aPos + BOUNDARY_CORE_LEN <= fileSize
1582 && ( std::memcmp( data + aPos, BOUNDARY_STD, BOUNDARY_CORE_LEN ) == 0
1583 || std::memcmp( data + aPos, BOUNDARY_ALT, BOUNDARY_CORE_LEN ) == 0 );
1584 };
1585
1586 auto stringOkAt = [&]( size_t aPos ) -> bool
1587 {
1588 wxString s;
1589 size_t np = 0;
1590
1591 return TryReadStringAt( data, fileSize, aPos, m_version, s, np );
1592 };
1593
1594 // The string delta (boundary core -> first header string) is fixed per file. Determine it from
1595 // the first component that carries a non-empty header string, exactly as the boundary scan does.
1596 auto cleanDeltaAt = [&]( size_t aBoundary ) -> int
1597 {
1598 for( int d : STRING_OFFSETS )
1599 {
1600 wxString s;
1601 size_t np = 0;
1602
1603 if( TryReadStringAt( data, fileSize, aBoundary + static_cast<size_t>( d ), m_version,
1604 s, np )
1605 && s.length() >= 1 )
1606 {
1607 return d;
1608 }
1609 }
1610
1611 return -1;
1612 };
1613
1614 // Locate the first component boundary and the file's string delta: the first boundary core at
1615 // or after the design-rules region whose header string parses.
1616 size_t parsedEnd = m_postDesignRulesOffset;
1617 size_t searchStart = ( parsedEnd > 200 ) ? parsedEnd - 200 : m_postLayersOffset;
1618 size_t first = 0;
1619 int globalDelta = 14;
1620
1621 for( size_t p = searchStart; p + BOUNDARY_CORE_LEN < aUpperBound; p++ )
1622 {
1623 if( !isBoundaryCoreAt( p ) )
1624 continue;
1625
1626 int d = cleanDeltaAt( p );
1627
1628 if( d < 0 )
1629 continue;
1630
1631 first = p;
1632 globalDelta = d;
1633 break;
1634 }
1635
1636 std::vector<std::pair<size_t, size_t>> out;
1637
1638 if( first == 0 )
1639 return out;
1640
1641 // Advance to the next component: the nearest boundary core after the current one that carries a
1642 // header string at the file's string delta (a non-empty path for placed parts, the empty header
1643 // of standalone-via records). The boundary core plus this header is the per-component structural
1644 // signature, mirroring the schematic importer's isComponentHeaderAt() walk -- a forward
1645 // record-by-record advance, not the global boundary-pattern scan, certified against it by the
1646 // caller.
1647 auto nextBoundaryAfter = [&]( size_t aFrom ) -> size_t
1648 {
1649 for( size_t cand = aFrom + 1; cand + BOUNDARY_CORE_LEN <= aUpperBound; cand++ )
1650 {
1651 if( isBoundaryCoreAt( cand )
1652 && stringOkAt( cand + static_cast<size_t>( globalDelta ) ) )
1653 {
1654 return cand;
1655 }
1656 }
1657
1658 return 0;
1659 };
1660
1661 size_t b = first;
1662
1663 while( b + BOUNDARY_CORE_LEN < aUpperBound && out.size() < 10000 )
1664 {
1665 out.emplace_back( b, b + static_cast<size_t>( globalDelta ) );
1666
1667 size_t next = nextBoundaryAfter( b );
1668
1669 if( next == 0 || next <= b )
1670 break;
1671
1672 b = next;
1673 }
1674
1675 // Cross-check against the optional component count stored at the post-design-rules offset
1676 // (present for several versions, absent for others). A mismatch means the walk derailed.
1677 int declaredCount = ( parsedEnd + 3 <= fileSize ) ? ReadInt3At( data, parsedEnd ) : -1;
1678
1679 if( declaredCount > 0 && declaredCount <= 10000
1680 && static_cast<int>( out.size() ) != declaredCount )
1681 {
1682 out.clear();
1683 }
1684
1685 return out;
1686}
1687
1688
1689// Component finding (hybrid boundary strategy)
1690// ---------------------------------------------------------------------------
1691
1693{
1694 m_components.clear();
1695
1696 size_t parsedEnd = m_postDesignRulesOffset;
1697
1698 // Bound the component region above by the nearest known post-component landmark.
1699 size_t projLib = m_reader.FindString( wxT( "Project Libraries" ), 0, 0 );
1700 size_t fontMarker = m_reader.FindPattern( BOARD_SETTINGS_FONT_MARKER, 9,
1701 m_postLayersOffset, 0 );
1702
1703 if( projLib != NOT_FOUND && fontMarker != NOT_FOUND )
1704 m_componentUpperBound = std::min( projLib, fontMarker );
1705 else if( projLib != NOT_FOUND )
1706 m_componentUpperBound = projLib;
1707 else if( fontMarker != NOT_FOUND )
1708 m_componentUpperBound = fontMarker;
1709 else
1710 m_componentUpperBound = m_reader.GetFileSize();
1711
1712 struct ValidatedBoundary
1713 {
1714 size_t boundaryOffset;
1715 size_t stringStart;
1716 };
1717
1718 std::vector<ValidatedBoundary> validated;
1719 bool fieldWalked = false;
1720
1721 // Deterministic component walk, certified below against the boundary scan. When the walk
1722 // reproduces the scan's boundary set exactly, the components were located by their trailer
1723 // anchors and ComponentLocatorScans() stays zero; otherwise the scan result is the
1724 // authoritative list and is counted.
1725 std::vector<std::pair<size_t, size_t>> walk =
1727
1728 {
1729 size_t searchStart = ( parsedEnd > 200 ) ? parsedEnd - 200 : m_postLayersOffset;
1730
1731 std::vector<size_t> stdOffsets = FindAllBoundaries(
1732 m_reader.GetData(), m_reader.GetFileSize(),
1734 searchStart, m_componentUpperBound );
1735
1736 std::vector<size_t> altOffsets = FindAllBoundaries(
1737 m_reader.GetData(), m_reader.GetFileSize(),
1739 searchStart, m_componentUpperBound );
1740
1741 std::set<size_t> stdSet( stdOffsets.begin(), stdOffsets.end() );
1742 std::vector<size_t> pureAlt;
1743
1744 for( size_t off : altOffsets )
1745 {
1746 if( stdSet.find( off ) == stdSet.end() )
1747 pureAlt.push_back( off );
1748 }
1749
1750 std::vector<size_t> allBoundaries( stdOffsets );
1751 allBoundaries.insert( allBoundaries.end(), pureAlt.begin(), pureAlt.end() );
1752 std::sort( allBoundaries.begin(), allBoundaries.end() );
1753
1754 allBoundaries.erase( std::unique( allBoundaries.begin(), allBoundaries.end() ),
1755 allBoundaries.end() );
1756
1757 if( allBoundaries.empty() )
1758 return;
1759
1760 // Both standard and alternate patterns have 13-byte cores, typically followed by 1 trailing
1761 // byte, so header strings usually start at +14.
1762 static const int STRING_OFFSETS[] = { 14, 15, 16, 17, 18, 19, 20 };
1763 int stringDelta = 14; // default
1764
1765 for( size_t bOff : allBoundaries )
1766 {
1767 if( parsedEnd > 50 && bOff < parsedEnd - 50 )
1768 continue;
1769
1770 bool found = false;
1771
1772 for( int delta : STRING_OFFSETS )
1773 {
1774 size_t candidate = bOff + delta;
1775 wxString str;
1776 size_t newPos;
1777
1778 if( TryReadStringAt( m_reader.GetData(), m_reader.GetFileSize(),
1779 candidate, m_version, str, newPos ) )
1780 {
1781 if( str.length() >= 1 )
1782 {
1783 stringDelta = delta;
1784 found = true;
1785 break;
1786 }
1787 }
1788 }
1789
1790 if( found )
1791 break;
1792 }
1793
1794 // Validate boundaries that have a readable header string at the chosen offset.
1795 for( size_t bOff : allBoundaries )
1796 {
1797 if( parsedEnd > 50 && bOff < parsedEnd - 50 )
1798 continue;
1799
1800 size_t candidate = bOff + stringDelta;
1801 wxString str;
1802 size_t newPos;
1803
1804 if( TryReadStringAt( m_reader.GetData(), m_reader.GetFileSize(),
1805 candidate, m_version, str, newPos ) )
1806 {
1807 validated.push_back( { bOff, candidate } );
1808 }
1809 }
1810
1811 // Some files mix component boundary variants with different trailing-byte counts.
1812 // If a single global string delta only validates a tiny subset, retry per-boundary
1813 // across all known offsets.
1814 if( validated.size() <= 1 && allBoundaries.size() > 1 )
1815 {
1816 validated.clear();
1817
1818 for( size_t bOff : allBoundaries )
1819 {
1820 if( parsedEnd > 50 && bOff < parsedEnd - 50 )
1821 continue;
1822
1823 bool found = false;
1824
1825 for( int delta : STRING_OFFSETS )
1826 {
1827 size_t candidate = bOff + delta;
1828 wxString str;
1829 size_t newPos;
1830
1831 if( TryReadStringAt( m_reader.GetData(), m_reader.GetFileSize(),
1832 candidate, m_version, str, newPos ) )
1833 {
1834 validated.push_back( { bOff, candidate } );
1835 found = true;
1836 break;
1837 }
1838 }
1839
1840 if( !found && bOff + stringDelta + 3 < m_reader.GetFileSize() )
1841 validated.push_back( { bOff, bOff + stringDelta } );
1842 }
1843 }
1844 } // end boundary scan (authoritative list)
1845
1846 // Certify the deterministic walk: it must reproduce the scan's component boundaries exactly.
1847 if( !walk.empty() && walk.size() == validated.size() )
1848 {
1849 fieldWalked = true;
1850
1851 for( size_t i = 0; i < walk.size(); i++ )
1852 {
1853 if( walk[i].first != validated[i].boundaryOffset )
1854 {
1855 fieldWalked = false;
1856 break;
1857 }
1858 }
1859 }
1860
1861
1862 if( validated.empty() )
1863 {
1864 wxLogWarning( _( "DipTrace: no validated component boundaries found" ) );
1865 return;
1866 }
1867
1868 // Parse components sequentially, stopping after a run of consecutive failures.
1869 int consecutiveFailures = 0;
1870 bool seenSuccess = false;
1871 static constexpr int MAX_CONSECUTIVE_FAILURES = 3;
1872 static constexpr int MAX_COMPONENTS = 10000;
1873
1874 for( size_t vi = 0; vi < validated.size(); vi++ )
1875 {
1876 if( static_cast<int>( m_components.size() ) >= MAX_COMPONENTS )
1877 break;
1878
1879 const ValidatedBoundary& vb = validated[vi];
1880 m_reader.SetOffset( vb.stringStart );
1881
1883 comp.boundaryOffset = vb.boundaryOffset;
1884 comp.stringStartOffset = vb.stringStart;
1885
1886 if( ParseSingleComponent( vb.boundaryOffset, m_componentUpperBound, comp ) )
1887 {
1888 // Count only when the boundary came from the recovery scan; the field-walk above
1889 // locates components by their tail anchor without a boundary-pattern scan.
1890 if( !fieldWalked )
1892
1893 size_t regionEnd = ( vi + 1 < validated.size() )
1894 ? validated[vi + 1].boundaryOffset
1896 comp.regionEndOffset = regionEnd;
1897
1898 // Pads, mount holes and shapes are field-located inside their finders, which increment
1899 // m_padLocatorScans / m_shapeLocatorScans only on a scan fallback. FindMountHolesInRegion
1900 // derives every candidate from padRegionEnd and the embedded record counts, so it has no
1901 // scan fallback to count.
1902 FindPadsInRegion( comp, vb.boundaryOffset, regionEnd );
1903 FindMountHolesInRegion( comp, vb.boundaryOffset, regionEnd );
1904 FindShapesInRegion( comp, vb.boundaryOffset, regionEnd );
1905
1906 comp.isStandaloneVia = ClassifyStandaloneVia( comp );
1907
1908 ParseComponentTail( comp, regionEnd );
1909 DumpComponentBinaryScan( comp, m_reader.GetData(), m_reader.GetFileSize() );
1910 m_components.push_back( comp );
1911 consecutiveFailures = 0;
1912 seenSuccess = true;
1913 }
1914 else
1915 {
1916 if( seenSuccess )
1917 {
1918 consecutiveFailures++;
1919
1920 if( consecutiveFailures >= MAX_CONSECUTIVE_FAILURES )
1921 break;
1922 }
1923 }
1924 }
1925
1926 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: parsed %zu components" ), m_components.size() );
1927}
1928
1929
1931{
1932 if( m_components.empty() )
1933 return;
1934
1935 const uint8_t* data = m_reader.GetData();
1936 size_t dataSize = m_reader.GetFileSize();
1937
1938 static constexpr double RAD_FIXED_TO_DEG = ( 180.0 / M_PI ) / 1.0e4;
1939 static constexpr int MAX_ANGLE_FIXED = 200000; // ~11.4 rad of cumulative turns
1940
1941 // The placement section stores one self-delimiting entry per placed object, each opening with
1942 // a header that carries the exact angle as a biased int4 (radians x 1e4) at header-4. There are
1943 // two header kinds, both followed by three small int3 fields (high two bytes 0x0F42):
1944 // FULL -- byte(1) byte(1), used by the first placement of a pattern.
1945 // COMPACT -- byte(0) byte(0) then nine zero bytes + 0x0F42, used when a pattern is reused
1946 // (this kind is why D2/D21, both reusing the SK54C pattern, key correctly).
1947 // Collect every header offset and its angle.
1948 auto isThreeInt3 = [&]( size_t aPos ) -> bool
1949 {
1950 return aPos + 9 <= dataSize
1951 && data[aPos] == 0x0F && data[aPos + 1] == 0x42
1952 && data[aPos + 3] == 0x0F && data[aPos + 4] == 0x42
1953 && data[aPos + 6] == 0x0F && data[aPos + 7] == 0x42;
1954 };
1955
1956 std::vector<std::pair<size_t, double>> headers;
1957
1958 for( size_t off = 4; off + 32 <= dataSize; off++ )
1959 {
1960 bool full = data[off] == 0x01 && data[off + 1] == 0x01 && isThreeInt3( off + 2 );
1961 bool compact = data[off] == 0x00 && data[off + 1] == 0x00 && isThreeInt3( off + 2 )
1962 && off + 22 <= dataSize
1963 && std::memcmp( data + off + 11, "\0\0\0\0\0\0\0\0\0", 9 ) == 0
1964 && data[off + 20] == 0x0F && data[off + 21] == 0x42;
1965
1966 if( !full && !compact )
1967 continue;
1968
1969 int fixed = ReadInt4At( data, off - 4 );
1970
1971 if( fixed >= -MAX_ANGLE_FIXED && fixed <= MAX_ANGLE_FIXED )
1972 headers.emplace_back( off, static_cast<double>( fixed ) * RAD_FIXED_TO_DEG );
1973 }
1974
1975 if( headers.empty() )
1976 return;
1977
1978 // Map each refdes to the angle of the nearest preceding header (first occurrence wins). A
1979 // refdes string is a UTF-16-BE record (uint16 length 1..12) that begins with a letter and
1980 // contains a digit; this skips the pattern-name and field strings the entries also carry.
1981 std::map<wxString, double> refdesAngle;
1982
1983 for( size_t off = 0; off + 4 <= dataSize; off++ )
1984 {
1985 size_t len = ( static_cast<size_t>( data[off] ) << 8 ) | data[off + 1];
1986
1987 if( len < 1 || len > 12 || off + 2 + 2 * len > dataSize )
1988 continue;
1989
1990 std::string refdes;
1991 bool asciiUtf16 = true;
1992 bool firstAlpha = false;
1993 bool hasDigit = false;
1994
1995 for( size_t c = 0; c < len; c++ )
1996 {
1997 uint8_t hi = data[off + 2 + 2 * c];
1998 uint8_t lo = data[off + 3 + 2 * c];
1999
2000 if( hi != 0 || lo < 0x20 || lo > 0x7E )
2001 {
2002 asciiUtf16 = false;
2003 break;
2004 }
2005
2006 if( c == 0 )
2007 firstAlpha = std::isalpha( lo );
2008
2009 if( std::isdigit( lo ) )
2010 hasDigit = true;
2011
2012 refdes += static_cast<char>( lo );
2013 }
2014
2015 if( !asciiUtf16 || !firstAlpha || !hasDigit )
2016 continue;
2017
2018 // Nearest header strictly preceding this refdes.
2019 auto it = std::upper_bound( headers.begin(), headers.end(), off,
2020 []( size_t aPos, const std::pair<size_t, double>& aHdr )
2021 { return aPos < aHdr.first; } );
2022
2023 if( it == headers.begin() )
2024 continue;
2025
2026 refdesAngle.emplace( wxString::FromUTF8( refdes ), ( it - 1 )->second );
2027 }
2028
2029 size_t applied = 0;
2030
2032 {
2033 auto it = refdesAngle.find( comp.refdes );
2034
2035 if( it != refdesAngle.end() )
2036 {
2037 comp.placementAngleDeg = it->second;
2038 comp.hasPlacementAngle = true;
2039 applied++;
2040 }
2041 }
2042
2043 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: keyed %zu exact placement angles by refdes" ), applied );
2044}
2045
2046
2048{
2049 // A DipTrace board serializes three "padstack-only" object kinds with an empty pattern
2050 // name and empty library path: Static Vias, single-pad Pads, and Fiducials. Only the
2051 // Static Via is a true KiCad via; the other kinds must become one-pad footprints so the
2052 // 1662 placed footprints (668 real + 990 Pad + 4 Fiducial in the reference board) are not lost.
2053 //
2054 // The component's display name carries DipTrace's internal object name and is the only
2055 // reliable discriminator (verified field-by-field against the reference board XML oracle: 1102 vias
2056 // carry "Static Via", 990 Pads carry "Pad", 4 carry "Fiducial"; older boards such as
2057 // keyboard.dip name the same object "Via"). Mount holes carry "Hole" and must stay
2058 // footprints. A real footprint always has a non-empty pattern name and library path, so
2059 // the leading guards reject it.
2060 if( !aComp.patternName.empty() || !aComp.libraryPath.empty() )
2061 return false;
2062
2063 if( aComp.displayName != wxT( "Static Via" ) && aComp.displayName != wxT( "Via" ) )
2064 return false;
2065
2066 return true;
2067}
2068
2069
2070bool PCB_PARSER::ParseSingleComponent( size_t aBoundaryOffset, size_t aUpperBound,
2071 DT_COMPONENT& aComp )
2072{
2073 bool fatalHeaderError = false;
2074
2075 try
2076 {
2077 int quarterTurns = 0;
2078
2079 if( FindComponentRotation( m_reader.GetData(), m_reader.GetFileSize(), aBoundaryOffset,
2080 quarterTurns ) )
2081 {
2082 aComp.placementQuarterTurns = quarterTurns;
2083 aComp.hasPlacementQuarterTurns = true;
2084 }
2085
2086 // Library path (may be empty for embedded components)
2087 aComp.libraryPath = m_reader.ReadString();
2088
2089 aComp.layer = 0; // Default to top
2090
2091 int fieldA = m_reader.ReadInt3();
2092 int fieldB = m_reader.ReadInt3();
2093 aComp.fieldA = fieldA;
2094
2095 aComp.flags.resize( 4 );
2096
2097 for( int i = 0; i < 4; i++ )
2098 aComp.flags[i] = m_reader.ReadByte();
2099
2100 // Validate: flag bytes should be small
2101 for( int i = 0; i < 4; i++ )
2102 {
2103 if( aComp.flags[i] > 10 )
2104 {
2105 if( aComp.flags[i] >= 0x80 && !aComp.libraryPath.empty()
2106 && ( aComp.libraryPath.Contains( wxT( "\\" ) )
2107 || aComp.libraryPath.Contains( wxT( "/" ) )
2108 || aComp.libraryPath.Contains( wxT( ":" ) ) ) )
2109 {
2110 fatalHeaderError = true;
2111 THROW_IO_ERROR( wxString::Format(
2112 _( "DipTrace: invalid component flag byte %u at boundary 0x%06zX" ),
2113 static_cast<unsigned int>( aComp.flags[i] ), aBoundaryOffset ) );
2114 }
2115
2116 return false;
2117 }
2118 }
2119
2120 // The second flag byte indicates the layer: 0=top, 1=bottom
2121 aComp.layer = aComp.flags[1];
2122
2123 size_t posXPos = m_reader.GetOffset();
2124 aComp.positionX = m_reader.ReadInt4();
2125 size_t posYPos = m_reader.GetOffset();
2126 aComp.positionY = m_reader.ReadInt4();
2127 size_t rotPos = m_reader.GetOffset();
2128 aComp.rotation = m_reader.ReadInt4();
2129 size_t fieldCPos = m_reader.GetOffset();
2130 int fieldC = m_reader.ReadInt4();
2131 aComp.fieldC = fieldC;
2132
2133 uint8_t sep1 = m_reader.ReadByte();
2134
2135 if( sep1 != 0 )
2136 return false;
2137
2138 size_t fieldDPos = m_reader.GetOffset();
2139 int fieldD = m_reader.ReadInt4();
2140 aComp.fieldD = fieldD;
2141
2142 uint8_t sep2 = m_reader.ReadByte();
2143 uint8_t sep3 = m_reader.ReadByte();
2144
2145 if( sep2 > 1 || sep3 != 0 )
2146 return false;
2147
2148 int fieldE = m_reader.ReadInt3();
2149 int fieldF = m_reader.ReadInt3();
2150 aComp.fieldF = fieldF;
2151 aComp.patternName = m_reader.ReadString();
2152
2153 // bounding box: 6 int4 values [X extent, Y extent, pad_w, pad_h, drill_w, drill_h]
2154 aComp.bboxWidth = m_reader.ReadInt4();
2155 aComp.bboxHeight = m_reader.ReadInt4();
2156 aComp.padWidthHint = m_reader.ReadInt4();
2157 aComp.padHeightHint = m_reader.ReadInt4();
2158 aComp.drillWidthHint = m_reader.ReadInt4();
2159 aComp.drillHeightHint = m_reader.ReadInt4();
2160
2161 m_reader.ReadInt3(); // field_g
2162 m_reader.ReadInt3(); // field_h
2163 aComp.displayName = m_reader.ReadString();
2164 aComp.refdes = m_reader.ReadString();
2165 aComp.value = m_reader.ReadString();
2166
2167 // Extra string field after value (always empty in observed files)
2168 m_reader.ReadString();
2169
2170 aComp.headerEndOffset = m_reader.GetOffset();
2171
2172 // Standalone-via classification is finalized after pad/mount-hole parsing.
2173 aComp.isStandaloneVia = false;
2174
2175 DumpComponentHeader( aComp, fieldA, fieldB, fieldC, fieldD, fieldE, fieldF,
2176 sep1, sep2, sep3 );
2177 DumpComponentRawFields( aComp, m_reader.GetData(), posXPos, posYPos, rotPos,
2178 fieldCPos, fieldDPos );
2179 }
2180 catch( const IO_ERROR& )
2181 {
2182 if( fatalHeaderError )
2183 throw;
2184
2185 // Standalone via components may not carry the full component header; keep them
2186 // once the pattern name has been read so they can be classified after pads.
2187 if( !aComp.patternName.empty() )
2188 return true;
2189
2190 return false;
2191 }
2192
2193 return true;
2194}
2195
2196
2197// ---------------------------------------------------------------------------
2198// Pad finding (chain-based sequential walk from pad index 1)
2199// ---------------------------------------------------------------------------
2200
2201void PCB_PARSER::FindPadsInRegion( DT_COMPONENT& aComp, size_t aRegionStart, size_t aRegionEnd )
2202{
2203 const uint8_t* data = m_reader.GetData();
2204 size_t dataSize = m_reader.GetFileSize();
2205
2206 if( aRegionEnd > dataSize )
2207 aRegionEnd = dataSize;
2208
2209 // True when a complete pad record for the component's first pad (index 1) sits at aPos.
2210 // We anchor on the index field rather than the pad name because DipTrace pad names can
2211 // be non-sequential (e.g. "2","1" or "A","K").
2212 auto isPad1At = [&]( size_t aPos ) -> bool
2213 {
2214 if( aPos < aRegionStart || aPos + PAD_PRE_HEADER_SIZE + 4 > aRegionEnd )
2215 return false;
2216
2217 if( ReadInt3At( data, aPos ) != 1 )
2218 return false;
2219
2220 int netIdx = ReadInt3At( data, aPos + 3 );
2221
2222 if( netIdx < -1 || netIdx > PAD_MAX_NET_INDEX )
2223 return false;
2224
2225 int padX = ReadInt4At( data, aPos + 6 );
2226 int padY = ReadInt4At( data, aPos + 10 );
2227
2228 if( std::abs( padX ) > 50000000 || std::abs( padY ) > 50000000 )
2229 return false;
2230
2231 size_t namePos = aPos + PAD_PRE_HEADER_SIZE;
2232 size_t nameLen = StringFieldSize( data, dataSize, namePos, m_version );
2233
2234 if( nameLen == 0 )
2235 return false;
2236
2237 size_t labelPos = namePos + nameLen;
2238 size_t labelLen = StringFieldSize( data, dataSize, labelPos, m_version );
2239
2240 if( labelLen == 0 )
2241 return false;
2242
2243 size_t dimPos = labelPos + labelLen;
2244
2245 if( dimPos + PAD_DIMENSIONS_SIZE > aRegionEnd )
2246 return false;
2247
2248 int w = ReadInt4At( data, dimPos );
2249 int h = ReadInt4At( data, dimPos + 4 );
2250
2251 return w > 0 && h > 0 && w <= 10000000 && h <= 10000000;
2252 };
2253
2254 size_t chainPos = 0;
2255
2256 // Deterministic field-derived location. The first pad record begins a fixed preamble
2257 // after the component header strings: 74 bytes for the UTF-16 string formats (v39+) and
2258 // 76 bytes for the legacy v37 ASCII format. Validate the record at that offset.
2259 if( aComp.headerEndOffset > 0 )
2260 {
2263 size_t candidate = aComp.headerEndOffset + preamble;
2264
2265 if( isPad1At( candidate ) )
2266 chainPos = candidate;
2267 }
2268
2269 // Recovery fallback: scan for the int3(1) index pattern when the record is not at the
2270 // expected offset (an unrecognised format variant). Counts toward the determinism gate.
2271 if( chainPos == 0 )
2272 {
2273 static const uint8_t IDX1_PATTERN[] = { 0x0F, 0x42, 0x41 }; // int3(1) = 1000001
2274
2275 std::vector<size_t> matches = FindAllBoundaries( data, dataSize, IDX1_PATTERN, 3,
2276 aRegionStart, aRegionEnd );
2277
2278 for( size_t pos : matches )
2279 {
2280 if( isPad1At( pos ) )
2281 {
2282 chainPos = pos;
2283 break;
2284 }
2285 }
2286
2287 if( chainPos != 0 )
2289 }
2290
2291 if( chainPos == 0 )
2292 {
2293 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: pad 1 not found in region 0x%06zX-0x%06zX for '%s'" ),
2294 aRegionStart, aRegionEnd, aComp.patternName );
2295 return;
2296 }
2297
2298 if( aComp.headerEndOffset > 0 && aComp.headerEndOffset <= chainPos )
2299 DumpPadGap( aComp, data, aComp.headerEndOffset, chainPos );
2300
2301 for( int padNum = 1; padNum < 500; padNum++ )
2302 {
2303 if( chainPos + PAD_PRE_HEADER_SIZE > aRegionEnd )
2304 break;
2305
2306 int padIndex = ReadInt3At( data, chainPos );
2307 int padNetIndex = ReadInt3At( data, chainPos + 3 );
2308 int padX = ReadInt4At( data, chainPos + 6 );
2309 int padY = ReadInt4At( data, chainPos + 10 );
2310
2311 if( padIndex != padNum )
2312 break;
2313
2314 if( padNetIndex < -1 || padNetIndex > PAD_MAX_NET_INDEX )
2315 break;
2316
2317 if( std::abs( padX ) > 50000000 || std::abs( padY ) > 50000000 )
2318 break;
2319
2320 size_t namePos = chainPos + PAD_PRE_HEADER_SIZE;
2321 size_t nameFieldLen = StringFieldSize( data, dataSize, namePos, m_version );
2322
2323 if( nameFieldLen == 0 )
2324 break;
2325
2326 size_t labelPos = namePos + nameFieldLen;
2327 size_t labelFieldLen = StringFieldSize( data, dataSize, labelPos, m_version );
2328
2329 if( labelFieldLen == 0 )
2330 break;
2331
2332 size_t dimPos = labelPos + labelFieldLen;
2333
2334 if( dimPos + PAD_DIMENSIONS_SIZE > aRegionEnd )
2335 break;
2336
2337 int padW = ReadInt4At( data, dimPos );
2338 int padH = ReadInt4At( data, dimPos + 4 );
2339 int drillW = ReadInt4At( data, dimPos + 8 );
2340 int drillH = ReadInt4At( data, dimPos + 12 );
2341
2342 if( padW <= 0 || padH <= 0 || padW > 10000000 || padH > 10000000 )
2343 break;
2344
2345 size_t postDimPos = dimPos + PAD_DIMENSIONS_SIZE;
2346 size_t postDimSize = PAD_POST_DIM_FIXED_SIZE;
2347
2348 if( postDimPos + PAD_POST_DIM_HEADER > aRegionEnd )
2349 break;
2350
2351 int padStyleC = ReadInt3At( data, postDimPos + 4 );
2352
2353 DT_PAD pad;
2354 pad.index = padIndex;
2355 pad.netIndex = padNetIndex;
2356 pad.x = padX;
2357 pad.y = padY;
2358 pad.width = padW;
2359 pad.height = padH;
2360 pad.drillWidth = drillW;
2361 pad.drillHeight = drillH;
2362 pad.style = padStyleC;
2363 pad.mountType = data[postDimPos + 3];
2364
2365 size_t afterName = 0;
2366 TryReadStringAt( data, dataSize, namePos, m_version, pad.number, afterName );
2367
2368 size_t afterLabel = 0;
2369 TryReadStringAt( data, dataSize, labelPos, m_version, pad.label, afterLabel );
2370
2371 // Some one-pad patterns serialize an empty primary pad string while the
2372 // secondary string carries the logical pad number.
2373 if( pad.number.IsEmpty() && !pad.label.IsEmpty() )
2374 pad.number = pad.label;
2375
2376 // Polygon pads (style C=3) carry inline vertex data after the post-dim header.
2377 if( padStyleC == 3 )
2378 {
2379 int vertexCount = ReadInt3At( data, postDimPos + 8 );
2380
2381 if( vertexCount > 0 && vertexCount <= 200
2382 && postDimPos + PAD_POST_DIM_HEADER
2383 + static_cast<size_t>( vertexCount ) * PAD_POLYGON_VERTEX_SIZE
2384 + PAD_POST_DIM_TAIL <= aRegionEnd )
2385 {
2386 size_t vPos = postDimPos + PAD_POST_DIM_HEADER;
2387
2388 for( int vi = 0; vi < vertexCount; vi++ )
2389 {
2390 int vx = ReadInt4At( data, vPos );
2391 int vy = ReadInt4At( data, vPos + 4 );
2392 pad.polygonVertices.emplace_back( vx, vy );
2394 }
2395
2396 postDimSize = PAD_POST_DIM_HEADER
2397 + static_cast<size_t>( vertexCount ) * PAD_POLYGON_VERTEX_SIZE
2399 }
2400 }
2401
2402 if( postDimPos + postDimSize > aRegionEnd )
2403 break;
2404
2405 pad.orientClass = data[postDimPos + postDimSize - 1];
2406 DumpPadPostBlock( aComp, pad, data, postDimPos, postDimSize );
2407
2408 aComp.pads.push_back( pad );
2409
2410 chainPos = postDimPos + postDimSize;
2411 }
2412
2413 aComp.padRegionEnd = chainPos;
2414}
2415
2416
2417// ---------------------------------------------------------------------------
2418// Footprint shape records
2419// ---------------------------------------------------------------------------
2420
2422static constexpr int FP_SHAPE_COUNT_OFFSET = 71;
2423
2425static constexpr int FP_SHAPE_DATA_OFFSET = 74;
2426
2427static constexpr int FP_SHAPE_RECORD_SIZE_V37 = 62;
2428
2429static constexpr int FP_SHAPE_RECORD_SIZE_V45 = 60;
2430
2432static constexpr int FP_CHAIN_SHAPE_COUNT_OFFSET = 69;
2433static constexpr int FP_CHAIN_SHAPE_DATA_OFFSET = 72;
2434static constexpr int FP_CHAIN_SHAPE_RECORD_SIZE = 76;
2435static constexpr int FP_CHAIN_SHAPE_WIDTH_OFFSET = 37;
2436static constexpr int FP_CHAIN_SHAPE_TYPE_OFFSET = 41;
2437static constexpr int FP_CHAIN_SHAPE_X1_OFFSET = 44;
2438static constexpr int FP_CHAIN_SHAPE_Y1_OFFSET = 48;
2439static constexpr int FP_CHAIN_SHAPE_X2_OFFSET = 52;
2440static constexpr int FP_CHAIN_SHAPE_Y2_OFFSET = 56;
2441static constexpr int FP_CHAIN_SHAPE_X3_OFFSET = 60;
2442static constexpr int FP_CHAIN_SHAPE_Y3_OFFSET = 64;
2443static constexpr int FP_CHAIN_TYPE_LINE = 2;
2444static constexpr int FP_CHAIN_TYPE_ARC = 3;
2445static constexpr int FP_CHAIN_MOUNT_HOLE_OFFSET = 44;
2446
2448static constexpr int FP_SHAPE_NORM_RANGE = 10000;
2449
2451static constexpr int FP_SHAPE_DEFAULT_WIDTH = -10000;
2452
2454static constexpr int DT_FP_LAYER_TOP_SILK = 0;
2455static constexpr int DT_FP_LAYER_TOP_ASSY = 1;
2456static constexpr int DT_FP_LAYER_TOP_MASK = 2;
2457static constexpr int DT_FP_LAYER_TOP_PASTE = 3;
2458static constexpr int DT_FP_LAYER_TOP_KEEPOUT = 9;
2459static constexpr int DT_FP_LAYER_TOP_COURTYARD = 16;
2460static constexpr int DT_FP_LAYER_TOP_OUTLINE = 18;
2461
2462
2463// ---------------------------------------------------------------------------
2464// Footprint mount-hole records
2465// ---------------------------------------------------------------------------
2466
2468static constexpr size_t MOUNT_HOLE_HEADER_SIZE = 20;
2469
2471static constexpr size_t MOUNT_HOLE_RECORD_SIZE = 18;
2472
2474static constexpr size_t MOUNT_HOLE_TERM_SIZE = 2;
2475
2477static constexpr size_t MOUNT_HOLE_TRAILER_SIZE = 16;
2478
2479
2480static bool decodeMountHoleBlockAt( const uint8_t* aData, size_t aBlockStart,
2481 size_t aSearchEnd, std::vector<DT_MOUNT_HOLE>& aHoles )
2482{
2483 static constexpr int MAX_REASONABLE_DIM = 50000000;
2484 static constexpr int MAX_HOLE_COUNT = 64;
2485
2488 {
2489 return false;
2490 }
2491
2492 int countField = ReadInt3At( aData, aBlockStart );
2493 int holeCount = countField - 2;
2494
2495 if( holeCount <= 0 || holeCount > MAX_HOLE_COUNT )
2496 return false;
2497
2498 uint8_t headerFlag = aData[aBlockStart + 3];
2499
2500 if( headerFlag > 1 )
2501 return false;
2502
2503 for( int i = 0; i < 4; i++ )
2504 {
2505 if( ReadInt4At( aData, aBlockStart + 4 + static_cast<size_t>( i ) * 4 ) != 0 )
2506 return false;
2507 }
2508
2509 size_t holeStart = aBlockStart + MOUNT_HOLE_HEADER_SIZE;
2510 size_t holesBytes = static_cast<size_t>( holeCount ) * MOUNT_HOLE_RECORD_SIZE;
2511 size_t holeEnd = holeStart + holesBytes;
2512 size_t termPos = holeEnd;
2513 size_t trailerPos = termPos + MOUNT_HOLE_TERM_SIZE;
2514
2515 if( trailerPos + MOUNT_HOLE_TRAILER_SIZE > aSearchEnd )
2516 return false;
2517
2518 std::vector<DT_MOUNT_HOLE> parsedHoles;
2519 parsedHoles.reserve( static_cast<size_t>( holeCount ) );
2520
2521 for( int hi = 0; hi < holeCount; hi++ )
2522 {
2523 size_t hp = holeStart + static_cast<size_t>( hi ) * MOUNT_HOLE_RECORD_SIZE;
2524 uint8_t holeFlagA = aData[hp];
2525 uint8_t holeFlagB = aData[hp + 1];
2526
2527 if( holeFlagA != 0 || holeFlagB > 1 )
2528 return false;
2529
2530 int x = ReadInt4At( aData, hp + 2 );
2531 int y = ReadInt4At( aData, hp + 6 );
2532 int outer = ReadInt4At( aData, hp + 10 );
2533 int drill = ReadInt4At( aData, hp + 14 );
2534
2535 if( std::abs( x ) > MAX_REASONABLE_DIM || std::abs( y ) > MAX_REASONABLE_DIM
2536 || outer <= 0 || outer > MAX_REASONABLE_DIM
2537 || drill <= 0 || drill > MAX_REASONABLE_DIM
2538 || drill > outer )
2539 {
2540 return false;
2541 }
2542
2543 DT_MOUNT_HOLE hole;
2544 hole.x = x;
2545 hole.y = y;
2546 hole.outerDiameter = outer;
2547 hole.drillDiameter = drill;
2548 parsedHoles.push_back( hole );
2549 }
2550
2551 if( aData[termPos] != 0 || aData[termPos + 1] != 0 )
2552 return false;
2553
2554 for( int i = 0; i < 4; i++ )
2555 {
2556 if( ReadInt4At( aData, trailerPos + static_cast<size_t>( i ) * 4 ) != 0 )
2557 return false;
2558 }
2559
2560 aHoles = std::move( parsedHoles );
2561 return true;
2562}
2563
2564
2565void PCB_PARSER::FindMountHolesInRegion( DT_COMPONENT& aComp, size_t aRegionStart,
2566 size_t aRegionEnd )
2567{
2568 wxUnusedVar( aRegionStart );
2569 aComp.holes.clear();
2570
2571 if( aComp.padRegionEnd == 0 || aComp.pads.empty() )
2572 return;
2573
2574 const uint8_t* data = m_reader.GetData();
2575 size_t dataSize = m_reader.GetFileSize();
2576
2577 if( aRegionEnd > dataSize )
2578 aRegionEnd = dataSize;
2579
2580 size_t searchEnd = aRegionEnd;
2581
2582 if( searchEnd > COMPONENT_TAIL_SIZE )
2583 searchEnd -= COMPONENT_TAIL_SIZE;
2584
2585 std::vector<size_t> candidates;
2586
2587 if( aComp.padRegionEnd + FP_SHAPE_DATA_OFFSET <= searchEnd )
2588 {
2591 int shapeCount = ReadInt3At( data, aComp.padRegionEnd + FP_SHAPE_COUNT_OFFSET );
2592
2593 if( shapeCount > 0 && shapeCount <= 500 )
2594 {
2595 size_t shapeStart = aComp.padRegionEnd + FP_SHAPE_DATA_OFFSET;
2596 candidates.push_back( shapeStart + static_cast<size_t>( shapeCount ) * recSize );
2597 }
2598 }
2599
2601 && aComp.padRegionEnd + FP_CHAIN_SHAPE_DATA_OFFSET <= searchEnd )
2602 {
2603 int shapeCount = ReadInt3At( data, aComp.padRegionEnd + FP_CHAIN_SHAPE_COUNT_OFFSET );
2604
2605 if( shapeCount >= 3 && shapeCount <= 200 )
2606 {
2607 size_t lastFrame = aComp.padRegionEnd + FP_CHAIN_SHAPE_DATA_OFFSET
2608 + static_cast<size_t>( shapeCount - 1 )
2610 candidates.push_back( lastFrame + FP_CHAIN_MOUNT_HOLE_OFFSET );
2611 }
2612 }
2613
2614 candidates.push_back( aComp.padRegionEnd );
2615
2616 std::vector<size_t> uniqueCandidates;
2617 uniqueCandidates.reserve( candidates.size() );
2618
2619 for( size_t pos : candidates )
2620 {
2621 if( std::find( uniqueCandidates.begin(), uniqueCandidates.end(), pos ) == uniqueCandidates.end() )
2622 uniqueCandidates.push_back( pos );
2623 }
2624
2625 size_t decodedAt = 0;
2626
2627 for( size_t pos : uniqueCandidates )
2628 {
2629 if( decodeMountHoleBlockAt( data, pos, searchEnd, aComp.holes ) )
2630 {
2631 decodedAt = pos;
2632 break;
2633 }
2634 }
2635
2636 if( decodedAt == 0 )
2637 return;
2638
2639 if( ShouldDumpComponentHeader( aComp.refdes ) )
2640 {
2641 wxLogTrace( traceDiptraceIo,
2642 wxT( "DipTrace: mount-holes ref=%s count=%zu off=0x%06zX "
2643 "padEnd=0x%06zX gap=%zu" ),
2644 aComp.refdes, aComp.holes.size(), decodedAt, aComp.padRegionEnd,
2645 decodedAt - aComp.padRegionEnd );
2646 }
2647}
2648
2649
2650void PCB_PARSER::FindShapesInRegion( DT_COMPONENT& aComp, size_t aRegionStart, size_t aRegionEnd )
2651{
2652 if( aComp.bboxWidth == 0 || aComp.bboxHeight == 0 )
2653 return;
2654
2655 const uint8_t* data = m_reader.GetData();
2656 size_t dataSize = m_reader.GetFileSize();
2657
2658 if( aRegionEnd > dataSize )
2659 aRegionEnd = dataSize;
2660
2662 {
2663 FindShapesInFontBlocks( aComp, aRegionStart, aRegionEnd );
2664
2665 if( aComp.shapes.empty() )
2666 FindShapesInChainedBlocks( aComp, aRegionStart, aRegionEnd );
2667
2668 return;
2669 }
2670
2671 if( aComp.padRegionEnd == 0 || aComp.pads.empty() )
2672 {
2673 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: no pad region end for shape finding in '%s'" ),
2674 aComp.patternName );
2675 return;
2676 }
2677
2678 size_t padRegionEnd = aComp.padRegionEnd;
2679
2680 if( padRegionEnd + FP_SHAPE_DATA_OFFSET > aRegionEnd )
2681 {
2682 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: shape region beyond component bounds for '%s'" ),
2683 aComp.patternName );
2684 return;
2685 }
2686
2689
2690 int shapeCount = ReadInt3At( data, padRegionEnd + FP_SHAPE_COUNT_OFFSET );
2691
2692 if( shapeCount <= 0 || shapeCount > 500 )
2693 return;
2694
2695 size_t shapeStart = padRegionEnd + FP_SHAPE_DATA_OFFSET;
2696 size_t shapeEnd = shapeStart + static_cast<size_t>( shapeCount ) * recSize;
2697
2698 if( shapeEnd > aRegionEnd )
2699 return;
2700
2701 for( int i = 0; i < shapeCount; i++ )
2702 {
2703 size_t rp = shapeStart + static_cast<size_t>( i ) * recSize;
2704
2705 DT_FP_SHAPE shape;
2706 shape.type = ReadInt3At( data, rp );
2707
2708 if( shape.type == DT_SHAPE_END || shape.type == DT_SHAPE_EMPTY )
2709 continue;
2710
2711 if( shape.type != DT_SHAPE_LINE && shape.type != DT_SHAPE_CIRCLE
2712 && shape.type != DT_SHAPE_ARC )
2713 {
2714 continue;
2715 }
2716
2717 shape.x1 = ReadInt4At( data, rp + 3 );
2718 shape.y1 = ReadInt4At( data, rp + 7 );
2719 shape.x2 = ReadInt4At( data, rp + 11 );
2720 shape.y2 = ReadInt4At( data, rp + 15 );
2721 shape.midX = ReadInt4At( data, rp + 19 );
2722 shape.midY = ReadInt4At( data, rp + 23 );
2723
2725 {
2726 shape.width = ReadInt4At( data, rp + 55 );
2727 shape.layer = ReadInt3At( data, rp + 59 );
2728 }
2729 else
2730 {
2731 shape.width = ReadInt4At( data, rp + 53 );
2732 shape.layer = ReadInt3At( data, rp + 57 );
2733 }
2734
2735 aComp.shapes.push_back( shape );
2736 }
2737}
2738
2739
2740void PCB_PARSER::FindShapesInFontBlocks( DT_COMPONENT& aComp, size_t aRegionStart,
2741 size_t aRegionEnd )
2742{
2743 const uint8_t* data = m_reader.GetData();
2744 size_t dataSize = m_reader.GetFileSize();
2745
2746 if( aRegionEnd > dataSize )
2747 aRegionEnd = dataSize;
2748
2749 auto isTahomaAt = [&]( size_t aPos ) -> bool
2750 {
2751 return aPos + TAHOMA_FONT_PATTERN_LEN <= aRegionEnd
2752 && std::memcmp( data + aPos, TAHOMA_FONT_PATTERN, TAHOMA_FONT_PATTERN_LEN ) == 0;
2753 };
2754
2755 auto u16beAt = [&]( size_t aPos ) -> int
2756 {
2757 return ( static_cast<int>( data[aPos] ) << 8 ) | static_cast<int>( data[aPos + 1] );
2758 };
2759
2760 // Deterministic field-walk of the self-describing font-shape blocks from the pad region
2761 // end. Each block carries its own point count and trailing label length, so its size is
2762 // computable; the run begins a fixed preamble after padRegionEnd and ends when the next
2763 // computed offset is no longer a Tahoma block (the trailing value/framing label record).
2764 std::vector<size_t> fontBlocks;
2765 bool fieldWalked = false;
2766
2767 if( aComp.padRegionEnd != 0
2768 && aComp.padRegionEnd + FONT_PREAMBLE_LABEL_OFFSET + 2 <= aRegionEnd )
2769 {
2770 size_t pre = aComp.padRegionEnd;
2771 int preLabel = u16beAt( pre + FONT_PREAMBLE_LABEL_OFFSET );
2772
2773 if( preLabel >= 0 && preLabel <= 256 )
2774 {
2775 size_t bs = pre + FONT_PREAMBLE_FIXED_SIZE + 2 * static_cast<size_t>( preLabel );
2776 bool walkOk = true;
2777
2778 while( isTahomaAt( bs ) )
2779 {
2781 int npts = -1;
2782 bool slot0InBounds = ( body + 3 <= aRegionEnd );
2783
2784 for( int n = 0; n <= 3; n++ )
2785 {
2786 if( body + static_cast<size_t>( n ) * 8 + 3 > aRegionEnd )
2787 break;
2788
2789 int st = ReadInt3At( data, body + static_cast<size_t>( n ) * 8 );
2790
2791 if( st == 0 || st == 1 || st == 2 || st == 3 || st == 5 || st == 6 || st == 7
2792 || st == 700 )
2793 {
2794 npts = n;
2795 break;
2796 }
2797 }
2798
2799 // A block whose leading slot carries shape-type 0 (no coordinate points) or no
2800 // shape-type code at all is the trailing value/reference text label that closes the
2801 // silk run. Record it and stop; its size never needs computing. Only a block
2802 // truncated past the region edge, whose structure can no longer be read, is an
2803 // abnormal break that must fall back to the scan.
2804 if( npts <= 0 )
2805 {
2806 if( slot0InBounds )
2807 fontBlocks.push_back( bs );
2808 else
2809 walkOk = false;
2810
2811 break;
2812 }
2813
2814 // npts >= 1: a real coordinate shape block. Advancing past it needs a valid trailing
2815 // label length; if that field is out of range the block size is uncomputable and the
2816 // walk would skip the following shapes, so treat it as a divergence.
2817 size_t lcPos = body + static_cast<size_t>( npts ) * 8 + 3 + FONT_BLOCK_TRAILER_SIZE;
2818
2819 if( lcPos + 2 > aRegionEnd )
2820 {
2821 walkOk = false;
2822 break;
2823 }
2824
2825 int lc = u16beAt( lcPos );
2826
2827 if( lc < 0 || lc > 256 )
2828 {
2829 walkOk = false;
2830 break;
2831 }
2832
2833 // Record the block only after its full size is validated, so an abnormal break never
2834 // leaves a truncated entry behind.
2835 fontBlocks.push_back( bs );
2836
2837 bs += FONT_BLOCK_FIXED_SIZE + static_cast<size_t>( 8 * npts )
2838 + 2 * static_cast<size_t>( lc );
2839 }
2840
2841 // Only certify the field-walk when it terminated cleanly. A mid-run break means the
2842 // block structure diverged from the model, so fall back to the Tahoma scan rather than
2843 // import a truncated silk run with the gate still reading zero.
2844 fieldWalked = walkOk && !fontBlocks.empty();
2845 }
2846 }
2847
2848 if( !fieldWalked )
2849 {
2850 // Recovery fallback: locate the font blocks by scanning for the Tahoma font literal.
2851 // Counts toward the determinism gate.
2852 fontBlocks = FindAllBoundaries( data, dataSize, TAHOMA_FONT_PATTERN, TAHOMA_FONT_PATTERN_LEN,
2853 aRegionStart, aRegionEnd );
2854 }
2855
2856 if( fontBlocks.empty() )
2857 return;
2858
2859 size_t shapesBefore = aComp.shapes.size();
2860
2861 for( size_t bi = 0; bi < fontBlocks.size(); bi++ )
2862 {
2863 size_t blockStart = fontBlocks[bi];
2864 size_t nextBoundary = ( bi + 1 < fontBlocks.size() ) ? fontBlocks[bi + 1]
2865 : aRegionEnd;
2866
2867 size_t headerEnd = blockStart + TAHOMA_FONT_PATTERN_LEN + FONT_BLOCK_HEADER_SIZE;
2868
2869 if( headerEnd + 19 > nextBoundary )
2870 continue;
2871
2872 size_t metaStart = blockStart + TAHOMA_FONT_PATTERN_LEN;
2873 int lineWidth = ReadInt4At( data, metaStart + 18 );
2874
2875 // The footprint-graphic layer is a small int3 enum stored 5 bytes ahead of the block's
2876 // "Tahoma" font literal (0 Top Silk, 1 Top Assembly, 2 Top Mask, 3 Top Paste,
2877 // 16 Top Courtyard, 18 Top Outline). The metaStart+22 field is unrelated and constant.
2878 int layerIdx = ( blockStart >= 5 ) ? ReadInt3At( data, blockStart - 5 ) : 0;
2879
2880 size_t bodyPos = headerEnd;
2881
2882 int x1 = ReadInt4At( data, bodyPos );
2883 int y1 = ReadInt4At( data, bodyPos + 4 );
2884 int x2 = ReadInt4At( data, bodyPos + 8 );
2885 int y2 = ReadInt4At( data, bodyPos + 12 );
2886
2887 if( x1 < -FP_SHAPE_NORM_RANGE || x1 > FP_SHAPE_NORM_RANGE
2888 || y1 < -FP_SHAPE_NORM_RANGE || y1 > FP_SHAPE_NORM_RANGE
2889 || x2 < -FP_SHAPE_NORM_RANGE || x2 > FP_SHAPE_NORM_RANGE
2890 || y2 < -FP_SHAPE_NORM_RANGE || y2 > FP_SHAPE_NORM_RANGE )
2891 {
2892 continue;
2893 }
2894
2895 if( x1 == 0 && y1 == 0 && x2 == 0 && y2 == 0 )
2896 continue;
2897
2898 int shapeType = ReadInt3At( data, bodyPos + 16 );
2899
2900 DT_FP_SHAPE shape;
2901 shape.x1 = x1;
2902 shape.y1 = y1;
2903 shape.x2 = x2;
2904 shape.y2 = y2;
2905 shape.width = lineWidth;
2906 shape.layer = layerIdx;
2907
2908 // v46+ font-block shape-type codes: 0 = axis-aligned rectangle, 1/5 = line,
2909 // 2/6 = arc, 3 = circle, 700 = filled obround marker (polarity / pin-1 dot).
2910 if( shapeType == 0 )
2911 {
2912 shape.type = DT_SHAPE_RECT;
2913 }
2914 else if( shapeType == 1 || shapeType == 5 )
2915 {
2916 shape.type = DT_SHAPE_LINE;
2917 }
2918 else if( shapeType == 3 )
2919 {
2920 shape.type = DT_SHAPE_CIRCLE;
2921 }
2922 else if( shapeType == 2 || shapeType == DT_SHAPE_ARC )
2923 {
2924 shape.type = DT_SHAPE_ARC;
2925
2926 if( bodyPos + 43 <= nextBoundary )
2927 {
2928 shape.midX = ReadInt4At( data, bodyPos + 35 );
2929 shape.midY = ReadInt4At( data, bodyPos + 39 );
2930 }
2931 else
2932 {
2933 continue;
2934 }
2935 }
2936 else if( shapeType == DT_SHAPE_FILLOBROUND )
2937 {
2938 shape.type = DT_SHAPE_FILLOBROUND;
2939 }
2940 else
2941 {
2942 continue;
2943 }
2944
2945 aComp.shapes.push_back( shape );
2946 }
2947
2948 if( !fieldWalked && aComp.shapes.size() > shapesBefore )
2950}
2951
2952
2953void PCB_PARSER::FindShapesInChainedBlocks( DT_COMPONENT& aComp, size_t aRegionStart,
2954 size_t aRegionEnd )
2955{
2956 wxUnusedVar( aRegionStart );
2957
2958 if( aComp.padRegionEnd == 0 || aComp.pads.empty() )
2959 return;
2960
2961 const uint8_t* data = m_reader.GetData();
2962 size_t dataSize = m_reader.GetFileSize();
2963
2964 if( aRegionEnd > dataSize )
2965 aRegionEnd = dataSize;
2966
2967 if( aComp.padRegionEnd + FP_CHAIN_SHAPE_DATA_OFFSET > aRegionEnd )
2968 return;
2969
2970 int shapeCount = ReadInt3At( data, aComp.padRegionEnd + FP_CHAIN_SHAPE_COUNT_OFFSET );
2971
2972 // Observed range in viewer examples: 4..13 (count includes a non-shape head/tail record).
2973 if( shapeCount < 3 || shapeCount > 200 )
2974 return;
2975
2976 size_t shapeStart = aComp.padRegionEnd + FP_CHAIN_SHAPE_DATA_OFFSET;
2977 size_t shapeEnd = shapeStart + static_cast<size_t>( shapeCount ) * FP_CHAIN_SHAPE_RECORD_SIZE;
2978
2979 if( shapeEnd > aRegionEnd )
2980 return;
2981
2982 std::vector<DT_FP_SHAPE> decoded;
2983 decoded.reserve( static_cast<size_t>( shapeCount ) );
2984
2985 auto inNormRange = []( int aVal ) -> bool
2986 {
2987 return aVal >= -FP_SHAPE_NORM_RANGE && aVal <= FP_SHAPE_NORM_RANGE;
2988 };
2989
2990 // Record 0 and the last record are framing entries; real graphics are in 1..N-2.
2991 for( int i = 1; i + 1 < shapeCount; i++ )
2992 {
2993 size_t rp = shapeStart + static_cast<size_t>( i ) * FP_CHAIN_SHAPE_RECORD_SIZE;
2994 int rawType = ReadInt3At( data, rp + FP_CHAIN_SHAPE_TYPE_OFFSET );
2995 int width = ReadInt4At( data, rp + FP_CHAIN_SHAPE_WIDTH_OFFSET );
2996 int x1 = ReadInt4At( data, rp + FP_CHAIN_SHAPE_X1_OFFSET );
2997 int y1 = ReadInt4At( data, rp + FP_CHAIN_SHAPE_Y1_OFFSET );
2998 int x2 = ReadInt4At( data, rp + FP_CHAIN_SHAPE_X2_OFFSET );
2999 int y2 = ReadInt4At( data, rp + FP_CHAIN_SHAPE_Y2_OFFSET );
3000
3001 if( !inNormRange( x1 ) || !inNormRange( y1 ) || !inNormRange( x2 ) || !inNormRange( y2 ) )
3002 continue;
3003
3004 DT_FP_SHAPE shape;
3005 shape.width = width;
3006 // Chained records are footprint-local silk graphics on the component side.
3007 shape.layer = ( aComp.layer == 1 ) ? 3 : 2;
3008
3009 if( rawType == FP_CHAIN_TYPE_LINE )
3010 {
3011 shape.type = DT_SHAPE_LINE;
3012 shape.x1 = x1;
3013 shape.y1 = y1;
3014 shape.x2 = x2;
3015 shape.y2 = y2;
3016 }
3017 else if( rawType == FP_CHAIN_TYPE_ARC )
3018 {
3019 int x3 = ReadInt4At( data, rp + FP_CHAIN_SHAPE_X3_OFFSET );
3020 int y3 = ReadInt4At( data, rp + FP_CHAIN_SHAPE_Y3_OFFSET );
3021
3022 if( !inNormRange( x3 ) || !inNormRange( y3 ) )
3023 continue;
3024
3025 shape.type = DT_SHAPE_ARC;
3026 shape.x1 = x1;
3027 shape.y1 = y1;
3028 shape.midX = x2;
3029 shape.midY = y2;
3030 shape.x2 = x3;
3031 shape.y2 = y3;
3032 }
3033 else
3034 {
3035 continue;
3036 }
3037
3038 decoded.push_back( shape );
3039 }
3040
3041 if( !decoded.empty() )
3042 aComp.shapes.insert( aComp.shapes.end(), decoded.begin(), decoded.end() );
3043}
3044
3045
3046// ---------------------------------------------------------------------------
3047// Component tail (text positioning)
3048// ---------------------------------------------------------------------------
3049
3050void PCB_PARSER::ParseComponentTail( DT_COMPONENT& aComp, size_t aRegionEnd )
3051{
3052 if( aRegionEnd < COMPONENT_TAIL_SIZE )
3053 return;
3054
3055 const uint8_t* data = m_reader.GetData();
3056
3057 size_t savedOffset = m_reader.GetOffset();
3058 bool parsed = false;
3059
3060 auto tryParseTailAt = [&]( size_t aTailStart ) -> bool
3061 {
3062 if( aTailStart + COMPONENT_TAIL_SIZE > m_reader.GetFileSize() )
3063 return false;
3064
3065 if( std::memcmp( data + aTailStart, COMPONENT_TAIL_PATTERN, COMPONENT_TAIL_PATTERN_LEN ) != 0 )
3066 return false;
3067
3068 // Tail layout (37 bytes total, confirmed across v37-v54):
3069 // +0: int3(0) constant
3070 // +3: int4(0) constant
3071 // +7: int4(0) constant
3072 // +11: int4(0) constant
3073 // +15: int4(0) constant
3074 // +19: byte text side flag
3075 // +20: int3 text visibility (0 = visible, -1 = hidden)
3076 // +23: byte text side flag 2 (mirrors +19)
3077 // +24: int3 ordering index
3078 // +27: int4 refdes Y offset (DipTrace units)
3079 // +31: int4 value Y offset (DipTrace units)
3080 // +35: byte has-offset flag
3081 // +36: byte(0) constant
3082
3083 try
3084 {
3085 m_reader.SetOffset( aTailStart + 11 );
3086
3087 int check1 = m_reader.ReadInt4();
3088 int check2 = m_reader.ReadInt4();
3089
3090 if( check1 != 0 || check2 != 0 )
3091 return false;
3092
3093 uint8_t sideFlag1 = m_reader.ReadByte();
3094 int visibility = m_reader.ReadInt3();
3095 uint8_t sideFlag2 = m_reader.ReadByte();
3096 int orderIdx = m_reader.ReadInt3();
3097
3098 int refdesYOffset = m_reader.ReadInt4();
3099 int valueYOffset = m_reader.ReadInt4();
3100 uint8_t hasOffset = m_reader.ReadByte();
3101 uint8_t tailTerm = m_reader.ReadByte();
3102
3103 if( visibility != 0 && visibility != -1 )
3104 return false;
3105
3106 if( hasOffset > 1 || tailTerm != 0 )
3107 return false;
3108
3109 if( std::abs( refdesYOffset ) > 50000000 || std::abs( valueYOffset ) > 50000000 )
3110 return false;
3111
3112 aComp.refdesYOffset = refdesYOffset;
3113 aComp.valueYOffset = valueYOffset;
3114 aComp.refdesVisible = ( visibility != -1 );
3115 aComp.valueVisible = ( visibility != -1 );
3116 aComp.hasTailData = true;
3117
3118 // The component side is carried by the tail mirror flags, not the
3119 // header flag byte (which is always 0 in v49+ files). Both flags are
3120 // set together for bottom-side parts. Only promote to bottom here;
3121 // never override a header-determined bottom back to top, so older
3122 // files that did populate the header flag are never regressed.
3123 if( sideFlag1 == 1 && sideFlag2 == 1 )
3124 aComp.layer = 1;
3125
3126 DumpComponentTail( aComp, data, aTailStart, visibility, sideFlag1, sideFlag2,
3127 orderIdx, refdesYOffset, valueYOffset, hasOffset, tailTerm );
3128 return true;
3129 }
3130 catch( const IO_ERROR& )
3131 {
3132 return false;
3133 }
3134 };
3135
3136 size_t canonicalTailStart = aRegionEnd - COMPONENT_TAIL_SIZE;
3137 parsed = tryParseTailAt( canonicalTailStart );
3138
3139 if( !parsed && ShouldDumpComponentHeader( aComp.refdes ) )
3140 {
3141 size_t dumpLen = std::min<size_t>( 96, aRegionEnd );
3142 size_t dumpStart = aRegionEnd - dumpLen;
3143 wxString hex = BytesToHex( data + dumpStart, dumpLen );
3144
3145 wxLogTrace( traceDiptraceIo,
3146 wxT( "DipTrace: component-tail-missing ref=%s regionEnd=0x%06zX "
3147 "tailHexStart=0x%06zX len=%lu hex=[%s]" ),
3148 aComp.refdes, aRegionEnd, dumpStart, static_cast<unsigned long>( dumpLen ), hex );
3149 }
3150
3151 m_reader.SetOffset( savedOffset );
3152}
3153
3154
3155// ---------------------------------------------------------------------------
3156// Post-component sections
3157// ---------------------------------------------------------------------------
3158
3160{
3161 size_t postComp = m_reader.GetOffset();
3162
3163 if( postComp > m_componentUpperBound )
3164 postComp = m_componentUpperBound;
3165
3166 size_t projLibOffset = m_reader.FindString( wxT( "Project Libraries" ), 0, 0 );
3167 size_t gapEnd = ( projLibOffset != NOT_FOUND ) ? projLibOffset : m_reader.GetFileSize();
3168
3169 // Each post-component section is located by its own structural anchor and counts a scan-locate
3170 // (SectionLocatorScans) only when that anchor is absent: the board TEXT section by its
3171 // nine-byte zero separator + record count + 01 00 flags + valid record walk (here);
3172 // the NET section by the record count five bytes ahead of the index-0 sentinel; the ZONE
3173 // section by its font preamble. Text records are only parsed after the full structural
3174 // validation below, so a located text section is always anchored.
3175 FindAndParseTextObjects( postComp, gapEnd );
3176 FindAndParseNets( postComp, gapEnd );
3177 FindAndParseZones( postComp, gapEnd );
3178}
3179
3180
3181void PCB_PARSER::FindAndParseTextObjects( size_t aSearchStart, size_t aSearchEnd )
3182{
3183 size_t pos = aSearchStart;
3184 auto textRecordsLookValid =
3185 [&]( size_t aRecordStart, int aCount, size_t aSectionEnd ) -> bool
3186 {
3187 size_t savedOffset = m_reader.GetOffset();
3188 m_reader.SetOffset( aRecordStart );
3189
3190 try
3191 {
3192 for( int ti = 0; ti < aCount; ti++ )
3193 {
3194 m_reader.ReadInt3(); // type_a
3195 m_reader.ReadByte(); // flag_a
3196 m_reader.ReadInt3(); // type_b
3197 m_reader.ReadInt3(); // field_a
3198 m_reader.ReadInt3(); // field_b
3199 m_reader.ReadInt3(); // field_c
3200 m_reader.ReadInt3(); // field_d
3201 m_reader.ReadInt3(); // field_e
3202
3206
3207 int lineWidth = m_reader.ReadInt4();
3208 int layer = m_reader.ReadInt3();
3209
3210 if( lineWidth < 0 || lineWidth > 10000000 || layer < -100 || layer > 100 )
3211 throw std::runtime_error( "invalid text metrics" );
3212
3213 m_reader.ReadInt4(); // x1
3214 m_reader.ReadInt4(); // y1
3215 m_reader.ReadInt4(); // x2
3216 m_reader.ReadInt4(); // y2
3217
3218 m_reader.ReadString();
3219 m_reader.ReadString();
3220
3221 m_reader.ReadByte(); // separator
3222 m_reader.ReadInt3(); // field_pf_1
3223 m_reader.ReadByte(); // flag_pf
3224 m_reader.ReadInt4(); // text_offset_1
3225 m_reader.ReadInt4(); // text_offset_2
3226 m_reader.ReadInt3(); // record_index
3227 m_reader.ReadByte(); // end_flag
3228
3229 if( ti < aCount - 1 )
3230 {
3231 m_reader.ReadByte();
3232 m_reader.ReadByte();
3233 }
3234
3235 if( m_reader.GetOffset() > aSectionEnd )
3236 throw std::runtime_error( "text section overrun" );
3237 }
3238
3239 m_reader.SetOffset( savedOffset );
3240 return true;
3241 }
3242 catch( const std::exception& )
3243 {
3244 m_reader.SetOffset( savedOffset );
3245 return false;
3246 }
3247 };
3248
3249 while( pos + 20 < aSearchEnd )
3250 {
3251 size_t idx = m_reader.FindPattern( TEXT_SECTION_ZEROS, 9, pos, aSearchEnd );
3252
3253 if( idx == NOT_FOUND )
3254 break;
3255
3256 size_t countPos = idx + 9;
3257
3258 if( countPos + 5 > aSearchEnd )
3259 break;
3260
3261 const uint8_t* data = m_reader.GetData();
3262 const uint8_t* b = data + countPos;
3263 int countVal = static_cast<int>( ( static_cast<int>( b[0] ) << 16 )
3264 | ( static_cast<int>( b[1] ) << 8 )
3265 | static_cast<int>( b[2] ) ) - INT3_BIAS;
3266
3267 if( countVal >= 1 && countVal <= 1000 )
3268 {
3269 uint8_t flag1 = data[countPos + 3];
3270 uint8_t flag2 = data[countPos + 4];
3271
3272 if( flag1 == 1 && flag2 == 0 )
3273 {
3274 size_t recordStart = countPos + 5;
3275
3276 if( textRecordsLookValid( recordStart, countVal, aSearchEnd ) )
3277 {
3278 m_reader.SetOffset( recordStart );
3279 ParseTextRecords( countVal );
3280 return;
3281 }
3282 }
3283 }
3284
3285 pos = idx + 1;
3286 }
3287}
3288
3289
3291{
3292 for( int ti = 0; ti < aCount; ti++ )
3293 {
3294 try
3295 {
3297
3298 m_reader.ReadInt3(); // type_a
3299 m_reader.ReadByte(); // flag_a
3300 m_reader.ReadInt3(); // type_b
3301 m_reader.ReadInt3(); // field_a
3302 m_reader.ReadInt3(); // field_b
3303 m_reader.ReadInt3(); // field_c
3304 m_reader.ReadInt3(); // field_d
3305 m_reader.ReadInt3(); // field_e
3306
3307 text.color = ReadColorPacked( m_reader );
3308 ReadColorPacked( m_reader ); // color2
3309 ReadColorPacked( m_reader ); // color3
3310
3311 text.lineWidth = m_reader.ReadInt4();
3312 text.layer = m_reader.ReadInt3();
3313
3314 text.x1 = m_reader.ReadInt4();
3315 text.y1 = m_reader.ReadInt4();
3316 text.x2 = m_reader.ReadInt4();
3317 text.y2 = m_reader.ReadInt4();
3318
3319 text.text = m_reader.ReadString();
3320 text.fontName = m_reader.ReadString();
3321
3322 m_reader.ReadByte(); // separator
3323 m_reader.ReadInt3(); // field_pf_1
3324 m_reader.ReadByte(); // flag_pf
3325 m_reader.ReadInt4(); // text_offset_1
3326 m_reader.ReadInt4(); // text_offset_2
3327 m_reader.ReadInt3(); // record_index
3328 m_reader.ReadByte(); // end_flag
3329
3330 // Inter-record separator bytes (observed 0x01 0x00), absent after the last record.
3331 if( ti < aCount - 1 )
3332 {
3333 m_reader.ReadByte();
3334 m_reader.ReadByte();
3335 }
3336
3337 m_textObjects.push_back( text );
3338 }
3339 catch( const IO_ERROR& e )
3340 {
3341 THROW_IO_ERROR( wxString::Format(
3342 _( "DipTrace: text object [%d] parse error: %s" ),
3343 ti, e.What() ) );
3344 }
3345 }
3346}
3347
3348
3349// ---------------------------------------------------------------------------
3350// Net name parsing
3351// ---------------------------------------------------------------------------
3352
3353void PCB_PARSER::FindAndParseNets( size_t aSearchStart, size_t aSearchEnd )
3354{
3355 m_nets.clear();
3356 m_trackChains.clear();
3357 m_routingAnchorsByNet.clear();
3359
3360 // Net records in the .dip binary format are preceded by a 9-byte sentinel:
3361 // int3(0) int3(-1) int3(-1)
3362 // After the sentinel, the record contains:
3363 // int3(net_index) int3(0) int4(trace_width) int4(field) string(net_name)
3364 //
3365 // We validate each match by checking that the second int3 is in the observed
3366 // route-flag range, the widths are bounded, and the stored net-name string
3367 // parses. DipTrace permits empty stored net names.
3368
3369 std::vector<size_t> sentinelOffsets = FindAllBoundaries(
3370 m_reader.GetData(), m_reader.GetFileSize(),
3372 aSearchStart, aSearchEnd );
3373
3374 static constexpr int MAX_NETS = 10000;
3375 static constexpr int MAX_REASONABLE_WIDTH = 5000000; // 50mm in 10nm units
3376
3377 size_t firstNetSentinel = 0;
3378
3379 for( size_t sentOff : sentinelOffsets )
3380 {
3381 if( static_cast<int>( m_nets.size() ) >= MAX_NETS )
3382 break;
3383
3384 size_t pos = sentOff + NET_SENTINEL_LEN;
3385
3386 // Need at least: int3 + int3 + int4 + int4 + 2 bytes (empty string)
3387 if( pos + 3 + 3 + 4 + 4 + 2 > m_reader.GetFileSize() )
3388 continue;
3389
3390 m_reader.SetOffset( pos );
3391
3392 DT_NET net;
3393 bool acceptedNetRecord = false;
3394
3395 try
3396 {
3397 int netIndex = m_reader.ReadInt3();
3398 int field0 = m_reader.ReadInt3();
3399 int width1 = m_reader.ReadInt4();
3400 int width2 = m_reader.ReadInt4();
3401
3402 // Validate: net index must be non-negative. field0 is a small
3403 // per-net mode/route flag (observed values include 0 and 3), so
3404 // accept a bounded range instead of hardcoding zero.
3405 if( field0 < 0 || field0 > 10 || netIndex < 0 || netIndex >= MAX_NETS )
3406 continue;
3407
3408 if( width1 < 0 || width1 > MAX_REASONABLE_WIDTH
3409 || width2 < 0 || width2 > MAX_REASONABLE_WIDTH )
3410 {
3411 continue;
3412 }
3413
3414 bool expectedNetIndex = netIndex == static_cast<int>( m_nets.size() );
3415 acceptedNetRecord = expectedNetIndex;
3416 wxString name;
3417
3418 if( !m_reader.TryReadString( name ) )
3419 {
3420 if( expectedNetIndex )
3421 {
3422 THROW_IO_ERROR( wxString::Format(
3423 _( "DipTrace import: invalid net name for net index %d at "
3424 "offset 0x%06zX." ),
3425 netIndex, m_reader.GetOffset() ) );
3426 }
3427
3428 continue;
3429 }
3430
3431 net.index = netIndex;
3432 net.name = name;
3433 net.traceWidth = width1;
3434
3435 if( ShouldDumpNets() )
3436 {
3437 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: net idx=%d name=%s width1=%d width2=%d" ), netIndex, name,
3438 width1, width2 );
3439 }
3440 }
3441 catch( const IO_ERROR& )
3442 {
3443 if( acceptedNetRecord )
3444 throw;
3445
3446 // Skip malformed false-positive sentinel records.
3447 continue;
3448 }
3449
3450 if( firstNetSentinel == 0 )
3451 firstNetSentinel = sentOff;
3452
3453 ParseNetRouting( net );
3454
3455 m_nets.push_back( std::move( net ) );
3456 }
3457
3458 // The net section is field-located when the first accepted net record (index 0) is immediately
3459 // preceded by its record count: int3(netCount) sits at firstSentinel - 5 across all observed
3460 // versions (some prepend an int3(0) and a 01 00 flag pair, which we do not require). That count
3461 // is the section's deterministic structural anchor; the sequential net-index walk above rejects
3462 // the false sentinel hits in the component region. Only count a scan-locate when it is absent.
3463 bool netSectionFieldAnchored = false;
3464
3465 if( firstNetSentinel >= 5 && !m_nets.empty() )
3466 {
3467 netSectionFieldAnchored = ReadInt3At( m_reader.GetData(), firstNetSentinel - 5 )
3468 == static_cast<int>( m_nets.size() );
3469 }
3470
3471 if( !m_nets.empty() && !netSectionFieldAnchored )
3473
3474 size_t totalNodes = 0;
3475 size_t viaStyleNodes = 0;
3476 size_t routeFlagNodes = 0;
3477 size_t viaStyleAndRouteFlagNodes = 0;
3478 size_t routeFlagOnlyNodes = 0;
3479 size_t routeMode0Nodes = 0;
3480 size_t routeMode1Nodes = 0;
3481 size_t routeMode3Nodes = 0;
3482 size_t routeModeOtherNodes = 0;
3483
3484 for( const DT_TRACK_CHAIN& chain : m_trackChains )
3485 {
3486 for( const DT_TRACK_NODE& node : chain.nodes )
3487 {
3488 totalNodes++;
3489
3490 bool hasStyle = node.viaStyleIdx >= 0;
3491 bool hasFlag = node.routeFlag != 0;
3492
3493 if( hasStyle )
3494 viaStyleNodes++;
3495
3496 if( hasFlag )
3497 routeFlagNodes++;
3498
3499 if( hasStyle && hasFlag )
3500 viaStyleAndRouteFlagNodes++;
3501 else if( hasFlag )
3502 routeFlagOnlyNodes++;
3503
3504 switch( node.routeMode )
3505 {
3506 case 0:
3507 routeMode0Nodes++;
3508 break;
3509
3510 case 1:
3511 routeMode1Nodes++;
3512 break;
3513
3514 case 3:
3515 routeMode3Nodes++;
3516 break;
3517
3518 default:
3519 routeModeOtherNodes++;
3520 break;
3521 }
3522 }
3523 }
3524
3525 wxLogTrace( traceDiptraceIo,
3526 wxT( "DipTrace: parsed %zu net names, %zu track chains, %zu nodes "
3527 "(viaStyle=%zu, routeFlag=%zu, both=%zu, flagOnly=%zu, "
3528 "routeMode[0]=%zu, routeMode[1]=%zu, routeMode[3]=%zu, routeMode[other]=%zu)" ),
3529 m_nets.size(), m_trackChains.size(), totalNodes, viaStyleNodes, routeFlagNodes,
3530 viaStyleAndRouteFlagNodes, routeFlagOnlyNodes, routeMode0Nodes, routeMode1Nodes, routeMode3Nodes,
3531 routeModeOtherNodes );
3532}
3533
3534
3535// ---------------------------------------------------------------------------
3536// Net routing (track chains and vias)
3537// ---------------------------------------------------------------------------
3538
3540{
3541 // After the net name string, the record body contains:
3542 // int4(via_od_default) int4(via_drill_default) byte(1) + 7 zero bytes
3543 // int3(0) int3(pad_ref_count) + pairs of int3(comp_idx)+int3(pad_idx)
3544 // variable routing metadata
3545 // chain headers: pattern 00 00 00 0F 42 3F + int3(chain_idx) + int3(node_count)
3546 // node_count * 41-byte track node records per chain
3547 //
3548 // We scan forward from the current offset looking for chain header patterns
3549 // within a bounded region (up to the next net sentinel or 64KB, whichever comes first).
3550
3551 size_t startPos = m_reader.GetOffset();
3552
3553 static constexpr size_t MAX_NET_BODY = 65536;
3554 size_t scanEnd = std::min( startPos + MAX_NET_BODY, m_reader.GetFileSize() );
3555
3556 // Cap the scan at the next net sentinel so it cannot read into the following net.
3557 size_t nextSentinel = m_reader.FindPattern( NET_SENTINEL, NET_SENTINEL_LEN,
3558 startPos + 20, scanEnd );
3559
3560 if( nextSentinel != std::string::npos )
3561 scanEnd = nextSentinel;
3562
3563 const uint8_t* data = m_reader.GetData();
3564 size_t fileSize = m_reader.GetFileSize();
3565
3566 size_t chainScanStart = startPos;
3567 static constexpr int MAX_REASONABLE_VIA_DIM = 5000000; // 50mm
3568 static constexpr int MAX_PADREFS_PER_COMPONENT = 12;
3569
3570 int componentCount = static_cast<int>( m_components.size() );
3571 int maxReasonablePadRefs = std::max( 512, componentCount * MAX_PADREFS_PER_COMPONENT );
3572
3573 // Parse optional net-level routing preamble:
3574 // [via OD, via drill, marker, 7x0, int3(0), int3(padRefCount), pad refs...]
3575 if( startPos + 22 <= scanEnd )
3576 {
3577 int viaOuterDefault = ReadInt4At( data, startPos );
3578 int viaDrillDefault = ReadInt4At( data, startPos + 4 );
3579 uint8_t marker = data[startPos + 8];
3580 bool zeroBlock = std::all_of( data + startPos + 9, data + startPos + 16,
3581 []( uint8_t b ) { return b == 0; } );
3582 int separator = ReadInt3At( data, startPos + 16 );
3583 int padRefCount = ReadInt3At( data, startPos + 19 );
3584
3585 if( marker <= 1 && zeroBlock && separator == 0 && padRefCount >= 0
3586 && padRefCount <= maxReasonablePadRefs )
3587 {
3588 size_t refsStart = startPos + 22;
3589 size_t refsEnd = refsStart + static_cast<size_t>( padRefCount ) * 6;
3590
3591 if( refsEnd <= scanEnd )
3592 {
3593 std::vector<DT_PAD_REF> parsedRefs;
3594 parsedRefs.reserve( static_cast<size_t>( padRefCount ) );
3595 int rangeHitsBase0 = 0;
3596 int rangeHitsBase1 = 0;
3597
3598 for( int i = 0; i < padRefCount; i++ )
3599 {
3600 size_t refPos = refsStart + static_cast<size_t>( i ) * 6;
3601 int compIndex = ReadInt3At( data, refPos );
3602 int padIndex = ReadInt3At( data, refPos + 3 );
3603
3604 if( compIndex >= 0 && padIndex > 0 )
3605 {
3606 parsedRefs.push_back( { compIndex, padIndex } );
3607
3608 if( compIndex >= 0 && compIndex < componentCount )
3609 rangeHitsBase0++;
3610
3611 if( compIndex >= 1 && compIndex <= componentCount )
3612 rangeHitsBase1++;
3613 }
3614 }
3615
3616 int requiredRangeHits = std::min( 4, static_cast<int>( parsedRefs.size() ) );
3617 bool plausibleRefs = parsedRefs.empty()
3618 || std::max( rangeHitsBase0, rangeHitsBase1 ) >= requiredRangeHits;
3619
3620 if( plausibleRefs )
3621 {
3622 if( viaOuterDefault > 0 && viaOuterDefault <= MAX_REASONABLE_VIA_DIM )
3623 aNet.defaultViaOuterDiam = viaOuterDefault;
3624
3625 if( viaDrillDefault > 0 && viaDrillDefault <= MAX_REASONABLE_VIA_DIM )
3626 aNet.defaultViaDrillDiam = viaDrillDefault;
3627
3628 aNet.padRefs = std::move( parsedRefs );
3629 chainScanStart = refsEnd;
3630 }
3631 }
3632 }
3633 }
3634
3635 size_t pos = chainScanStart;
3636
3637 while( pos + CHAIN_HEADER_LEN + 6 + TRACK_NODE_SIZE <= scanEnd )
3638 {
3639 size_t chainPos = m_reader.FindPattern( CHAIN_HEADER, CHAIN_HEADER_LEN, pos, scanEnd );
3640
3641 if( chainPos == std::string::npos )
3642 break;
3643
3644 size_t headerStart = chainPos + CHAIN_HEADER_LEN;
3645
3646 if( headerStart + 6 > fileSize )
3647 break;
3648
3649 const uint8_t* h = data + headerStart;
3650 int chainIdx = ( ( static_cast<int>( h[0] ) << 16 )
3651 | ( static_cast<int>( h[1] ) << 8 )
3652 | static_cast<int>( h[2] ) ) - INT3_BIAS;
3653 int nodeCount = ( ( static_cast<int>( h[3] ) << 16 )
3654 | ( static_cast<int>( h[4] ) << 8 )
3655 | static_cast<int>( h[5] ) ) - INT3_BIAS;
3656
3657 auto firstNodeLooksPlausible = [&]() -> bool
3658 {
3659 size_t firstNode = headerStart + 6;
3660
3661 if( firstNode + TRACK_NODE_SIZE > scanEnd )
3662 return false;
3663
3664 const uint8_t* n = data + firstNode;
3665
3666 int x = static_cast<int>(
3667 ( static_cast<unsigned int>( n[0] ) << 24 )
3668 | ( static_cast<unsigned int>( n[1] ) << 16 )
3669 | ( static_cast<unsigned int>( n[2] ) << 8 )
3670 | static_cast<unsigned int>( n[3] ) ) - INT4_BIAS;
3671
3672 int y = static_cast<int>(
3673 ( static_cast<unsigned int>( n[4] ) << 24 )
3674 | ( static_cast<unsigned int>( n[5] ) << 16 )
3675 | ( static_cast<unsigned int>( n[6] ) << 8 )
3676 | static_cast<unsigned int>( n[7] ) ) - INT4_BIAS;
3677
3678 int layer = ( ( static_cast<int>( n[8] ) << 16 )
3679 | ( static_cast<int>( n[9] ) << 8 )
3680 | static_cast<int>( n[10] ) ) - INT3_BIAS;
3681
3682 int width = static_cast<int>(
3683 ( static_cast<unsigned int>( n[14] ) << 24 )
3684 | ( static_cast<unsigned int>( n[15] ) << 16 )
3685 | ( static_cast<unsigned int>( n[16] ) << 8 )
3686 | static_cast<unsigned int>( n[17] ) ) - INT4_BIAS;
3687
3688 int viaStyleIdx = ( ( static_cast<int>( n[27] ) << 16 )
3689 | ( static_cast<int>( n[28] ) << 8 )
3690 | static_cast<int>( n[29] ) ) - INT3_BIAS;
3691
3692 int routeMode = ( ( static_cast<int>( n[37] ) << 16 )
3693 | ( static_cast<int>( n[38] ) << 8 )
3694 | static_cast<int>( n[39] ) ) - INT3_BIAS;
3695
3696 return x > -100000000 && x < 100000000
3697 && y > -100000000 && y < 100000000
3698 && layer >= 0 && layer <= 50
3699 && width > 0 && width <= 5000000
3700 && viaStyleIdx >= -1 && viaStyleIdx <= 10000
3701 && routeMode >= 0 && routeMode <= 10
3702 && n[40] <= 10;
3703 };
3704
3705 if( chainIdx < 0 || nodeCount < 1 || nodeCount > 10000 )
3706 {
3707 if( chainIdx >= 0 && firstNodeLooksPlausible() )
3708 {
3709 THROW_IO_ERROR( wxString::Format(
3710 _( "DipTrace import: invalid route-chain node count %d for net '%s' "
3711 "at offset 0x%06zX." ),
3712 nodeCount, aNet.name, headerStart + 3 ) );
3713 }
3714
3715 pos = chainPos + 1;
3716 continue;
3717 }
3718
3719 size_t nodesStart = headerStart + 6;
3720 size_t nodesEnd = nodesStart + static_cast<size_t>( nodeCount ) * TRACK_NODE_SIZE;
3721
3722 if( nodesEnd > scanEnd )
3723 {
3724 if( firstNodeLooksPlausible() )
3725 {
3726 THROW_IO_ERROR( wxString::Format(
3727 _( "DipTrace import: route-chain node count %d for net '%s' overruns "
3728 "record at offset 0x%06zX." ),
3729 nodeCount, aNet.name, headerStart + 3 ) );
3730 }
3731
3732 pos = chainPos + 1;
3733 continue;
3734 }
3735
3737 chain.netIndex = aNet.index;
3738 chain.nodes.reserve( nodeCount );
3739
3740 bool valid = true;
3741
3742 for( int i = 0; i < nodeCount; i++ )
3743 {
3744 const uint8_t* n = data + nodesStart + static_cast<size_t>( i ) * TRACK_NODE_SIZE;
3745
3746 DT_TRACK_NODE node;
3747
3748 // +0: int4 X
3749 node.x = static_cast<int>(
3750 ( static_cast<unsigned int>( n[0] ) << 24 )
3751 | ( static_cast<unsigned int>( n[1] ) << 16 )
3752 | ( static_cast<unsigned int>( n[2] ) << 8 )
3753 | static_cast<unsigned int>( n[3] ) ) - INT4_BIAS;
3754
3755 // +4: int4 Y
3756 node.y = static_cast<int>(
3757 ( static_cast<unsigned int>( n[4] ) << 24 )
3758 | ( static_cast<unsigned int>( n[5] ) << 16 )
3759 | ( static_cast<unsigned int>( n[6] ) << 8 )
3760 | static_cast<unsigned int>( n[7] ) ) - INT4_BIAS;
3761
3762 // +8: int3 layer
3763 node.layer = ( ( static_cast<int>( n[8] ) << 16 )
3764 | ( static_cast<int>( n[9] ) << 8 )
3765 | static_cast<int>( n[10] ) ) - INT3_BIAS;
3766
3767 // +14: int4 track width
3768 node.width = static_cast<int>(
3769 ( static_cast<unsigned int>( n[14] ) << 24 )
3770 | ( static_cast<unsigned int>( n[15] ) << 16 )
3771 | ( static_cast<unsigned int>( n[16] ) << 8 )
3772 | static_cast<unsigned int>( n[17] ) ) - INT4_BIAS;
3773
3774 // +27: int3 via style index
3775 node.viaStyleIdx = ( ( static_cast<int>( n[27] ) << 16 )
3776 | ( static_cast<int>( n[28] ) << 8 )
3777 | static_cast<int>( n[29] ) ) - INT3_BIAS;
3778 node.viaOuterDiam = static_cast<int>(
3779 ( static_cast<unsigned int>( n[18] ) << 24 )
3780 | ( static_cast<unsigned int>( n[19] ) << 16 )
3781 | ( static_cast<unsigned int>( n[20] ) << 8 )
3782 | static_cast<unsigned int>( n[21] ) ) - INT4_BIAS;
3783 node.routeFlag = n[22];
3784 node.viaDrillDiam = static_cast<int>(
3785 ( static_cast<unsigned int>( n[30] ) << 24 )
3786 | ( static_cast<unsigned int>( n[31] ) << 16 )
3787 | ( static_cast<unsigned int>( n[32] ) << 8 )
3788 | static_cast<unsigned int>( n[33] ) ) - INT4_BIAS;
3789 // +37: int3 route mode/class. Observed values in sample corpus are 0, 1, and 3.
3790 // +40 is a trailing byte (0 in sampled files).
3791 node.routeMode = ( ( static_cast<int>( n[37] ) << 16 )
3792 | ( static_cast<int>( n[38] ) << 8 )
3793 | static_cast<int>( n[39] ) ) - INT3_BIAS;
3794 // DipTrace route points use ViaStyle as the explicit via indicator:
3795 // ViaStyle = -1 means no via at this point.
3796 // byte(+22) is not reliable for via placement (set on many non-via nodes).
3797 node.hasVia = ( node.viaStyleIdx >= 0 );
3798
3799 if( node.width <= 0 || node.width > 5000000 )
3800 {
3801 valid = false;
3802 break;
3803 }
3804
3805 if( node.layer < 0 || node.layer > 50 )
3806 {
3807 valid = false;
3808 break;
3809 }
3810
3811 chain.nodes.push_back( node );
3812 }
3813
3814 if( valid && !chain.nodes.empty() )
3815 m_trackChains.push_back( std::move( chain ) );
3816
3817 pos = nodesEnd;
3818 }
3819
3820}
3821
3822
3824{
3825 if( m_components.empty() || m_nets.empty() )
3826 return;
3827
3828 size_t totalRefs = 0;
3829
3830 for( const DT_NET& net : m_nets )
3831 totalRefs += net.padRefs.size();
3832
3833 if( totalRefs == 0 )
3834 return;
3835
3836 size_t maxReasonableRefs = std::max<size_t>( 5000, m_components.size() * 64 );
3837
3838 if( totalRefs > maxReasonableRefs )
3839 {
3840 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: skipping routing-ref pad inference (%zu refs exceeds %zu cap)" ),
3841 totalRefs, maxReasonableRefs );
3842 return;
3843 }
3844
3845 std::vector<std::unordered_map<int, size_t>> padPosByIndex( m_components.size() );
3846
3847 for( size_t c = 0; c < m_components.size(); c++ )
3848 {
3849 const std::vector<DT_PAD>& pads = m_components[c].pads;
3850 auto& padMap = padPosByIndex[c];
3851
3852 padMap.reserve( pads.size() );
3853
3854 for( size_t p = 0; p < pads.size(); p++ )
3855 padMap.emplace( pads[p].index, p );
3856 }
3857
3858 struct SCORE
3859 {
3860 int base = 0;
3861 int refs = 0;
3862 int hits = 0;
3863 int fillable = 0;
3864 };
3865
3866 auto scoreBase = [&]( int aBase ) -> SCORE
3867 {
3868 SCORE score;
3869 score.base = aBase;
3870
3871 for( const DT_NET& net : m_nets )
3872 {
3873 for( const DT_PAD_REF& ref : net.padRefs )
3874 {
3875 score.refs++;
3876
3877 int compPos = ref.componentIndex - aBase;
3878
3879 if( compPos < 0 || compPos >= static_cast<int>( m_components.size() ) )
3880 continue;
3881
3882 const auto& padMap = padPosByIndex[compPos];
3883 auto it = padMap.find( ref.padIndex );
3884
3885 if( it == padMap.end() )
3886 continue;
3887
3888 const DT_PAD& pad = m_components[compPos].pads[it->second];
3889 score.hits++;
3890
3891 if( pad.netIndex < 0 )
3892 score.fillable++;
3893 }
3894 }
3895
3896 return score;
3897 };
3898
3899 SCORE scoreBase0 = scoreBase( 0 );
3900 SCORE scoreBase1 = scoreBase( 1 );
3901
3902 if( scoreBase0.hits == 0 && scoreBase1.hits == 0 )
3903 return;
3904
3905 if( scoreBase0.hits == scoreBase1.hits && scoreBase0.fillable == scoreBase1.fillable )
3906 return;
3907
3908 SCORE best = scoreBase0;
3909
3910 if( scoreBase1.hits > scoreBase0.hits
3911 || ( scoreBase1.hits == scoreBase0.hits && scoreBase1.fillable > scoreBase0.fillable ) )
3912 {
3913 best = scoreBase1;
3914 }
3915
3916 if( best.hits < 8 )
3917 return;
3918
3919 int assigned = 0;
3920 int conflicts = 0;
3921
3922 for( const DT_NET& net : m_nets )
3923 {
3924 for( const DT_PAD_REF& ref : net.padRefs )
3925 {
3926 int compPos = ref.componentIndex - best.base;
3927
3928 if( compPos < 0 || compPos >= static_cast<int>( m_components.size() ) )
3929 continue;
3930
3931 auto& padMap = padPosByIndex[compPos];
3932 auto it = padMap.find( ref.padIndex );
3933
3934 if( it == padMap.end() )
3935 continue;
3936
3937 DT_PAD& pad = m_components[compPos].pads[it->second];
3938
3939 if( pad.netIndex < 0 )
3940 {
3941 pad.netIndex = net.index;
3942 assigned++;
3943 }
3944 else if( pad.netIndex != net.index )
3945 {
3946 conflicts++;
3947 }
3948 }
3949 }
3950
3951 if( assigned > 0 || conflicts > 0 )
3952 {
3953 wxLogTrace( traceDiptraceIo,
3954 wxT( "DipTrace: routing-ref pad net inference (base=%d): %d refs, %d hits, "
3955 "%d assigned, %d conflicts" ),
3956 best.base, best.refs, best.hits, assigned, conflicts );
3957 }
3958}
3959
3960
3961// ---------------------------------------------------------------------------
3962// Zone parsing
3963// ---------------------------------------------------------------------------
3964
3965void PCB_PARSER::FindAndParseZones( size_t aSearchStart, size_t aSearchEnd )
3966{
3967 m_zones.clear();
3968
3969 const uint8_t* data = m_reader.GetData();
3970 size_t fileSize = m_reader.GetFileSize();
3971
3972 if( aSearchEnd > fileSize )
3973 aSearchEnd = fileSize;
3974
3975 static constexpr int MAX_ZONES = 100;
3976 static constexpr int MAX_VERTICES = 50000;
3977 static constexpr int MAX_REASONABLE_DIM = 5000000;
3978
3979 // Prepare board outline bounds for zone vertex validation.
3980 // We use these bounds for both section discovery and zone parsing.
3981 int bboxXMin = m_bboxXMin - 5000000;
3982 int bboxXMax = m_bboxXMax + 5000000;
3983 int bboxYMin = m_bboxYMin - 5000000;
3984 int bboxYMax = m_bboxYMax + 5000000;
3985
3986 if( !m_outline.empty() )
3987 {
3988 int oxMin = m_outline[0].x, oxMax = m_outline[0].x;
3989 int oyMin = m_outline[0].y, oyMax = m_outline[0].y;
3990
3991 for( const DT_VERTEX& v : m_outline )
3992 {
3993 oxMin = std::min( oxMin, v.x );
3994 oxMax = std::max( oxMax, v.x );
3995 oyMin = std::min( oyMin, v.y );
3996 oyMax = std::max( oyMax, v.y );
3997 }
3998
3999 bboxXMin = oxMin - 5000000;
4000 bboxXMax = oxMax + 5000000;
4001 bboxYMin = oyMin - 5000000;
4002 bboxYMax = oyMax + 5000000;
4003 }
4004
4005 auto headerLooksPlausible = [&]( size_t aPos, bool aCheckVertexSamples ) -> bool
4006 {
4007 if( aPos + 30 > aSearchEnd )
4008 return false;
4009
4010 int fieldA = ReadInt3At( data, aPos );
4011 int flags1 = data[aPos + 3];
4012 int flags3 = data[aPos + 5];
4013 int minWidth = ReadInt4At( data, aPos + 6 );
4014 int clearance = ReadInt4At( data, aPos + 10 );
4015 int minimumArea = ReadInt4At( data, aPos + 14 );
4016 int separator = ReadInt3At( data, aPos + 18 );
4017 int layer = ReadInt3At( data, aPos + 21 );
4018 int fieldB = ReadInt3At( data, aPos + 24 );
4019 int vtxCount = ReadInt3At( data, aPos + 27 );
4020
4021 if( fieldA < 0 || fieldA > 10000 )
4022 return false;
4023
4024 if( fieldB < -1 || fieldB > 10000 )
4025 return false;
4026
4027 if( flags1 > 2 || flags3 > 2 )
4028 return false;
4029
4030 if( clearance <= 0 || clearance > MAX_REASONABLE_DIM )
4031 return false;
4032
4033 if( minWidth <= 0 || minWidth > MAX_REASONABLE_DIM )
4034 return false;
4035
4036 if( minimumArea < 0 || minimumArea > MAX_REASONABLE_DIM )
4037 return false;
4038
4039 if( separator > 0 )
4040 return false;
4041
4042 if( layer < -10 || layer > 100 )
4043 return false;
4044
4045 if( vtxCount < 3 || vtxCount > MAX_VERTICES )
4046 return false;
4047
4048 size_t vtxStart = aPos + 30;
4049 size_t vtxEnd = vtxStart + static_cast<size_t>( vtxCount ) * 8;
4050
4051 if( vtxEnd > aSearchEnd )
4052 return false;
4053
4054 if( !aCheckVertexSamples )
4055 return true;
4056
4057 int sampleCount = std::min( 3, vtxCount );
4058
4059 auto vertexInBounds = [&]( size_t aVertexPos ) -> bool
4060 {
4061 int x = ReadInt4At( data, aVertexPos );
4062 int y = ReadInt4At( data, aVertexPos + 4 );
4063 return x >= bboxXMin && x <= bboxXMax && y >= bboxYMin && y <= bboxYMax;
4064 };
4065
4066 for( int i = 0; i < sampleCount; i++ )
4067 {
4068 size_t vp = vtxStart + static_cast<size_t>( i ) * 8;
4069
4070 if( !vertexInBounds( vp ) )
4071 return false;
4072 }
4073
4074 size_t lastVp = vtxStart + static_cast<size_t>( vtxCount - 1 ) * 8;
4075
4076 return vertexInBounds( lastVp );
4077 };
4078
4079 auto headerHasZoneSectionShape = [&]( size_t aPos ) -> bool
4080 {
4081 if( aPos + 30 > aSearchEnd )
4082 return false;
4083
4084 int fieldA = ReadInt3At( data, aPos );
4085 int flags1 = data[aPos + 3];
4086 int flags3 = data[aPos + 5];
4087 int separator = ReadInt3At( data, aPos + 18 );
4088 int layer = ReadInt3At( data, aPos + 21 );
4089 int fieldB = ReadInt3At( data, aPos + 24 );
4090 int vtxCount = ReadInt3At( data, aPos + 27 );
4091
4092 return fieldA >= 0 && fieldA <= 10000
4093 && fieldB >= -1 && fieldB <= 10000
4094 && flags1 <= 2 && flags3 <= 2
4095 && separator <= 0
4096 && layer >= -10 && layer <= 100
4097 && vtxCount >= 3 && vtxCount <= MAX_VERTICES
4098 && aPos + 30 + static_cast<size_t>( vtxCount ) * 8 <= aSearchEnd;
4099 };
4100
4101 auto parseZoneTrailer = [&]( DT_ZONE& aZone, size_t aSearchStartPos, size_t aSearchEndPos,
4102 int aZoneIndex ) -> void
4103 {
4104 // Trailer block observed near the end of each inter-zone gap:
4105 // int3(regions_counted), int4(0), int4(board_clearance),
4106 // byte(island_region), byte(island_internal), byte(island_connection),
4107 // int3(zone_id), byte(via_direct), byte(smd_separate),
4108 // int3(smd_spoke_mode), int4(smd_spoke_width),
4109 // byte(ratline_mode), byte(regions_done)
4110 static constexpr size_t TRAILER_LEN = 28;
4111 static constexpr size_t STYLE_BLOCK_LEN = 14;
4112 static constexpr int CACHED_RECORD_LEN = 23;
4113
4114 if( aSearchEndPos <= aSearchStartPos || aSearchEndPos - aSearchStartPos < TRAILER_LEN )
4115 return;
4116
4117 size_t lastStart = aSearchEndPos - TRAILER_LEN;
4118
4119 for( size_t trailerPos = lastStart + 1; trailerPos-- > aSearchStartPos; )
4120 {
4121 int lead = ReadInt3At( data, trailerPos );
4122 int zeroInt4 = ReadInt4At( data, trailerPos + 3 );
4123 int boardClr = ReadInt4At( data, trailerPos + 7 );
4124 uint8_t islandR = data[trailerPos + 11];
4125 uint8_t islandI = data[trailerPos + 12];
4126 uint8_t islandC = data[trailerPos + 13];
4127 int zoneId = ReadInt3At( data, trailerPos + 14 );
4128 uint8_t viaDir = data[trailerPos + 17];
4129 uint8_t smdSep = data[trailerPos + 18];
4130 int smdSpokeMode = ReadInt3At( data, trailerPos + 19 );
4131 int smdSpokeW = ReadInt4At( data, trailerPos + 22 );
4132 uint8_t ratMode = data[trailerPos + 26];
4133 uint8_t doneFlag = data[trailerPos + 27];
4134
4135 if( lead < 0 || lead > 100000 || zeroInt4 != -INT4_BIAS )
4136 continue;
4137
4138 if( boardClr < 0 || boardClr > MAX_REASONABLE_DIM )
4139 continue;
4140
4141 if( islandR > 1 || islandI > 1 || islandC > 1 )
4142 continue;
4143
4144 if( zoneId < 0 || zoneId > 100000 )
4145 continue;
4146
4147 if( viaDir > 1 || smdSep > 1 )
4148 continue;
4149
4150 if( smdSpokeMode < 0 || smdSpokeMode > 4 )
4151 continue;
4152
4153 if( smdSpokeW <= 0 || smdSpokeW > MAX_REASONABLE_DIM )
4154 continue;
4155
4156 if( ratMode > 2 )
4157 continue;
4158
4159 if( doneFlag > 1 )
4160 continue;
4161
4162 size_t payloadStart = aSearchStartPos;
4163
4164 if( aSearchStartPos + STYLE_BLOCK_LEN <= aSearchEndPos )
4165 {
4166 int styleLead = ReadInt3At( data, aSearchStartPos );
4167 int styleSpokeMode = ReadInt3At( data, aSearchStartPos + 3 );
4168 int styleLineSpacing = ReadInt4At( data, aSearchStartPos + 6 );
4169 int styleSpokeWidth = ReadInt4At( data, aSearchStartPos + 10 );
4170
4171 if( styleLead == 0
4172 && styleSpokeMode >= 0 && styleSpokeMode <= 4
4173 && styleLineSpacing > 0 && styleLineSpacing <= MAX_REASONABLE_DIM
4174 && styleSpokeWidth > 0 && styleSpokeWidth <= MAX_REASONABLE_DIM )
4175 {
4176 payloadStart += STYLE_BLOCK_LEN;
4177 }
4178 }
4179
4180 int cachedBytes = 0;
4181 int cachedRecords = 0;
4182 aZone.cachedFillRecords.clear();
4183
4184 if( trailerPos > payloadStart )
4185 {
4186 cachedBytes = static_cast<int>( trailerPos - payloadStart );
4187
4188 if( cachedBytes % CACHED_RECORD_LEN == 0 )
4189 {
4190 cachedRecords = cachedBytes / CACHED_RECORD_LEN;
4191 aZone.cachedFillRecords.reserve( static_cast<size_t>( cachedRecords ) );
4192
4193 for( int recIdx = 0; recIdx < cachedRecords; recIdx++ )
4194 {
4195 size_t recPos = payloadStart + static_cast<size_t>( recIdx ) * CACHED_RECORD_LEN;
4196
4197 if( recPos + CACHED_RECORD_LEN > trailerPos )
4198 break;
4199
4201 rec.field0 = ReadInt3At( data, recPos );
4202 rec.field1 = ReadInt4At( data, recPos + 3 );
4203 rec.field2 = ReadInt4At( data, recPos + 7 );
4204 rec.field3 = ReadInt4At( data, recPos + 11 );
4205 rec.field4 = ReadInt4At( data, recPos + 15 );
4206 rec.field5 = ReadInt4At( data, recPos + 19 );
4207 aZone.cachedFillRecords.push_back( rec );
4208 }
4209
4210 cachedRecords = static_cast<int>( aZone.cachedFillRecords.size() );
4211 }
4212 }
4213
4214 aZone.regionsCounted = lead;
4215 aZone.cachedFillByteLen = cachedBytes;
4216 aZone.cachedFillRecordCount = cachedRecords;
4217 aZone.boardClearance = boardClr;
4218 aZone.zoneId = zoneId;
4219 aZone.viaDirect = viaDir;
4220 aZone.smdSeparate = smdSep;
4221 aZone.smdSpokeMode = smdSpokeMode;
4222 aZone.smdSpokeWidth = smdSpokeW;
4223 aZone.islandRegion = islandR;
4224 aZone.islandInternal = islandI;
4225 aZone.islandConnection = islandC;
4226 aZone.ratlineMode = ratMode;
4227 aZone.regionsDone = doneFlag;
4228
4229 if( ShouldDumpZones() )
4230 {
4231 wxLogTrace( traceDiptraceIo,
4232 wxT( "DipTrace: zone[%d] trailer off=0x%06zX regionsCounted=%d "
4233 "cachedBytes=%d cachedRecords=%d boardClr=%d "
4234 "islands=[%u,%u,%u] id=%d viaDirect=%u smdSeparate=%u "
4235 "smdSpokeMode=%d smdSpokeWidth=%d ratMode=%u done=%u" ),
4236 aZoneIndex, trailerPos, lead, cachedBytes, cachedRecords, boardClr,
4237 static_cast<unsigned int>( islandR ), static_cast<unsigned int>( islandI ),
4238 static_cast<unsigned int>( islandC ), zoneId, static_cast<unsigned int>( viaDir ),
4239 static_cast<unsigned int>( smdSep ), smdSpokeMode, smdSpokeW,
4240 static_cast<unsigned int>( ratMode ), static_cast<unsigned int>( doneFlag ) );
4241
4242 if( !aZone.cachedFillRecords.empty() )
4243 {
4244 int zoneXMin = aZone.outline[0].first;
4245 int zoneXMax = aZone.outline[0].first;
4246 int zoneYMin = aZone.outline[0].second;
4247 int zoneYMax = aZone.outline[0].second;
4248
4249 for( const auto& p : aZone.outline )
4250 {
4251 zoneXMin = std::min( zoneXMin, p.first );
4252 zoneXMax = std::max( zoneXMax, p.first );
4253 zoneYMin = std::min( zoneYMin, p.second );
4254 zoneYMax = std::max( zoneYMax, p.second );
4255 }
4256
4257 const DT_ZONE_CACHED_FILL_RECORD& firstRec = aZone.cachedFillRecords.front();
4258 std::array<int, 6> minVals = {
4259 firstRec.field0, firstRec.field1, firstRec.field2,
4260 firstRec.field3, firstRec.field4, firstRec.field5
4261 };
4262 std::array<int, 6> maxVals = minVals;
4263 std::array<int, 6> inXHits = { 0, 0, 0, 0, 0, 0 };
4264 std::array<int, 6> inYHits = { 0, 0, 0, 0, 0, 0 };
4265 std::array<int, 6> nonNegHits = { 0, 0, 0, 0, 0, 0 };
4266 std::map<int, int> field0Hist;
4267 std::map<int, int> field5Hist;
4268 int f0EqRegions = 0;
4269 int xEqualCount = 0;
4270 int yEqualCount = 0;
4271 int bothEqualCount = 0;
4272
4273 for( const DT_ZONE_CACHED_FILL_RECORD& rec : aZone.cachedFillRecords )
4274 {
4275 std::array<int, 6> vals = {
4276 rec.field0, rec.field1, rec.field2,
4277 rec.field3, rec.field4, rec.field5
4278 };
4279
4280 field0Hist[rec.field0]++;
4281 field5Hist[rec.field5]++;
4282
4283 if( rec.field0 == aZone.regionsCounted )
4284 f0EqRegions++;
4285
4286 bool xEq = ( rec.field1 == rec.field3 );
4287 bool yEq = ( rec.field2 == rec.field4 );
4288
4289 if( xEq )
4290 xEqualCount++;
4291
4292 if( yEq )
4293 yEqualCount++;
4294
4295 if( xEq && yEq )
4296 bothEqualCount++;
4297
4298 for( size_t fi = 0; fi < vals.size(); fi++ )
4299 {
4300 minVals[fi] = std::min( minVals[fi], vals[fi] );
4301 maxVals[fi] = std::max( maxVals[fi], vals[fi] );
4302
4303 if( vals[fi] >= zoneXMin && vals[fi] <= zoneXMax )
4304 inXHits[fi]++;
4305
4306 if( vals[fi] >= zoneYMin && vals[fi] <= zoneYMax )
4307 inYHits[fi]++;
4308
4309 if( vals[fi] >= 0 )
4310 nonNegHits[fi]++;
4311 }
4312 }
4313
4314 wxLogTrace( traceDiptraceIo,
4315 wxT( "DipTrace: zone[%d] cached-range "
4316 "f0=[%d,%d] f1=[%d,%d] f2=[%d,%d] f3=[%d,%d] f4=[%d,%d] f5=[%d,%d]" ),
4317 aZoneIndex, minVals[0], maxVals[0], minVals[1], maxVals[1], minVals[2], maxVals[2],
4318 minVals[3], maxVals[3], minVals[4], maxVals[4], minVals[5], maxVals[5] );
4319
4320 wxLogTrace( traceDiptraceIo,
4321 wxT( "DipTrace: zone[%d] cached-hits "
4322 "xHits=[%d,%d,%d,%d,%d,%d] yHits=[%d,%d,%d,%d,%d,%d] "
4323 "nonNeg=[%d,%d,%d,%d,%d,%d]" ),
4324 aZoneIndex, inXHits[0], inXHits[1], inXHits[2], inXHits[3], inXHits[4], inXHits[5],
4325 inYHits[0], inYHits[1], inYHits[2], inYHits[3], inYHits[4], inYHits[5], nonNegHits[0],
4326 nonNegHits[1], nonNegHits[2], nonNegHits[3], nonNegHits[4], nonNegHits[5] );
4327
4328 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: zone[%d] cached-zone-bbox=[%d,%d,%d,%d]" ), aZoneIndex,
4329 zoneXMin, zoneXMax, zoneYMin, zoneYMax );
4330
4331 auto histToString = []( const std::map<int, int>& aHist ) -> wxString
4332 {
4333 wxString out;
4334 bool first = true;
4335
4336 for( const auto& kv : aHist )
4337 {
4338 if( !first )
4339 out += wxT( ";" );
4340
4341 first = false;
4342 out += wxString::Format( wxT( "%d:%d" ), kv.first, kv.second );
4343 }
4344
4345 return out;
4346 };
4347
4348 wxLogTrace( traceDiptraceIo,
4349 wxT( "DipTrace: zone[%d] cached-hist f0={%s} f5={%s} "
4350 "f0EqRegions=%d xEq=%d yEq=%d bothEq=%d" ),
4351 aZoneIndex, histToString( field0Hist ), histToString( field5Hist ), f0EqRegions,
4352 xEqualCount, yEqualCount, bothEqualCount );
4353
4354 size_t sampleCount = std::min<size_t>( 6, aZone.cachedFillRecords.size() );
4355
4356 for( size_t ri = 0; ri < sampleCount; ri++ )
4357 {
4358 const DT_ZONE_CACHED_FILL_RECORD& rec = aZone.cachedFillRecords[ri];
4359 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: zone[%d] cached-rec[%zu]={%d,%d,%d,%d,%d,%d}" ),
4360 aZoneIndex, ri, rec.field0, rec.field1, rec.field2, rec.field3, rec.field4,
4361 rec.field5 );
4362 }
4363 }
4364 }
4365
4366 break;
4367 }
4368 };
4369
4370 size_t zoneHeaderStart = NOT_FOUND;
4371 bool zoneViaPreamble = false;
4372
4373 auto findZoneFontPreambleDataStart = [&]( size_t aStart ) -> size_t
4374 {
4375 static const wxString fontNames[] = {
4376 wxT( "Arial" ), wxT( "Tahoma" ), wxT( "Times New Roman" ),
4377 wxT( "Courier New" ), wxT( "Verdana" ), wxT( "Calibri" )
4378 };
4379
4380 size_t bestDataStart = NOT_FOUND;
4381
4382 for( const wxString& fontName : fontNames )
4383 {
4384 size_t fontPos = m_reader.FindString( fontName, aStart, aSearchEnd );
4385
4386 while( fontPos != NOT_FOUND )
4387 {
4388 size_t strEnd;
4389
4391 {
4392 int bc = ReadInt3At( data, fontPos );
4393
4394 if( bc < 0 || bc > 500 )
4395 {
4396 fontPos = m_reader.FindString( fontName, fontPos + 3, aSearchEnd );
4397 continue;
4398 }
4399
4400 strEnd = fontPos + 3 + bc;
4401 }
4402 else
4403 {
4404 uint16_t cc = ( static_cast<uint16_t>( data[fontPos] ) << 8 )
4405 | data[fontPos + 1];
4406 strEnd = fontPos + 2 + static_cast<size_t>( cc ) * 2;
4407 }
4408
4409 if( strEnd + 16 > aSearchEnd )
4410 break;
4411
4412 int fontSize = ReadInt3At( data, strEnd );
4413 int bold = data[strEnd + 3];
4414 int fontH = ReadInt4At( data, strEnd + 4 );
4415 int fontW = ReadInt4At( data, strEnd + 8 );
4416 int tail = ReadInt4At( data, strEnd + 12 );
4417
4418 if( fontSize >= 5 && fontSize <= 30 && bold <= 1
4419 && fontH > 0 && fontH < 10000000
4420 && fontW > 0 && fontW < 10000000
4421 && tail == ZONE_FONT_PREAMBLE_TAIL )
4422 {
4423 bestDataStart = std::min( bestDataStart, strEnd + 16 );
4424 break;
4425 }
4426
4427 fontPos = m_reader.FindString( fontName, strEnd, aSearchEnd );
4428 }
4429 }
4430
4431 return bestDataStart;
4432 };
4433
4434 size_t zoneDataStart = findZoneFontPreambleDataStart( aSearchStart );
4435
4436 while( zoneDataStart != NOT_FOUND )
4437 {
4438 size_t preambleHeaderStart = zoneDataStart + 3;
4439
4440 if( preambleHeaderStart + 30 <= aSearchEnd
4441 && headerLooksPlausible( preambleHeaderStart, true ) )
4442 {
4443 zoneHeaderStart = preambleHeaderStart;
4444 zoneViaPreamble = true;
4445 break;
4446 }
4447
4448 if( preambleHeaderStart + 30 <= aSearchEnd
4449 && headerHasZoneSectionShape( preambleHeaderStart ) )
4450 {
4451 THROW_IO_ERROR( wxString::Format(
4452 _( "DipTrace import: invalid copper-pour zone header after font preamble "
4453 "at offset 0x%06zX." ),
4454 preambleHeaderStart ) );
4455 }
4456
4457 zoneDataStart = findZoneFontPreambleDataStart( zoneDataStart + 1 );
4458 }
4459
4460 // Primary locator: scan structurally for plausible zone headers.
4461 for( size_t scanPos = aSearchStart; zoneHeaderStart == NOT_FOUND
4462 && scanPos + 30 <= aSearchEnd; scanPos++ )
4463 {
4464 if( headerLooksPlausible( scanPos, true ) )
4465 {
4466 zoneHeaderStart = scanPos;
4467 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: zone section found by structural scan at 0x%06zX" ),
4468 zoneHeaderStart );
4469 break;
4470 }
4471 }
4472
4473 // Fallback locator: zone section may be preceded by a font preamble:
4474 // string(font_name) int3(font_size) byte(bold)
4475 // int4(font_height) int4(font_width) int4(-20000)
4476 //
4477 // Historically, zones follow after int3(0) separator.
4478 if( zoneHeaderStart == NOT_FOUND )
4479 {
4480 zoneDataStart = findZoneFontPreambleDataStart( aSearchStart );
4481
4482 if( zoneDataStart != NOT_FOUND )
4483 {
4484 size_t fallbackStart = zoneDataStart + 3;
4485
4486 if( fallbackStart + 30 <= aSearchEnd
4487 && headerLooksPlausible( fallbackStart, true ) )
4488 {
4489 zoneHeaderStart = fallbackStart;
4490 zoneViaPreamble = true;
4491 }
4492 else
4493 {
4494 size_t fallbackEnd = std::min( aSearchEnd, zoneDataStart + 256 );
4495
4496 for( size_t scanPos = zoneDataStart; scanPos + 30 <= fallbackEnd; scanPos++ )
4497 {
4498 if( headerLooksPlausible( scanPos, true ) )
4499 {
4500 zoneHeaderStart = scanPos;
4501 break;
4502 }
4503 }
4504 }
4505
4506 if( zoneHeaderStart != NOT_FOUND )
4507 {
4508 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: zone section found by font fallback at 0x%06zX" ),
4509 zoneHeaderStart );
4510 }
4511 }
4512 }
4513
4514 if( zoneHeaderStart == NOT_FOUND )
4515 return;
4516
4517 size_t pos = zoneHeaderStart;
4518
4519 for( int zi = 0; zi < MAX_ZONES && pos + 30 < aSearchEnd; zi++ )
4520 {
4521 // Zone header: 30 bytes
4522 // int3(fieldA) + 3 flag bytes + int4(line_width) + int4(clearance)
4523 // + int4(minimum_area) + int3(separator=-1) + int3(layer) + int3(fieldB) + int3(vertex_count)
4524 //
4525 // Empirical mapping against viewer examples:
4526 // fieldB (+24) = connected net id
4527 // fieldA (+0) = filled-region count, NOT the CopperPour priority.
4528 // The viewer XML shows zones with Priority=0 carrying
4529 // fieldA values of 1 and 5, so it tracks fill complexity.
4530 // True priority storage is not yet located; the only
4531 // non-zero-priority samples are multi-region planes,
4532 // which confounds priority with the region count.
4533 int fieldA = ReadInt3At( data, pos );
4534 int flags1 = data[pos + 3];
4535 int flags2 = data[pos + 4];
4536 int flags3 = data[pos + 5];
4537 int minWidth = ReadInt4At( data, pos + 6 );
4538 int clearance = ReadInt4At( data, pos + 10 );
4539 int minimumArea = ReadInt4At( data, pos + 14 );
4540 int separator = ReadInt3At( data, pos + 18 );
4541 int layer = ReadInt3At( data, pos + 21 );
4542 int fieldB = ReadInt3At( data, pos + 24 );
4543 int vtxCount = ReadInt3At( data, pos + 27 );
4544
4545 if( fieldA < 0 || fieldA > 10000 )
4546 break;
4547
4548 if( fieldB < -1 || fieldB > 10000 )
4549 break;
4550
4551 if( flags1 > 2 || flags3 > 2 )
4552 break;
4553
4554 if( clearance <= 0 || clearance > MAX_REASONABLE_DIM )
4555 break;
4556
4557 if( minWidth <= 0 || minWidth > MAX_REASONABLE_DIM )
4558 break;
4559
4560 if( minimumArea < 0 || minimumArea > MAX_REASONABLE_DIM )
4561 break;
4562
4563 if( separator > 0 )
4564 break;
4565
4566 if( layer < -10 || layer > 100 )
4567 break;
4568
4569 if( vtxCount < 3 || vtxCount > MAX_VERTICES )
4570 break;
4571
4572 size_t vtxStart = pos + 30;
4573 size_t vtxEnd = vtxStart + static_cast<size_t>( vtxCount ) * 8;
4574
4575 if( vtxEnd > aSearchEnd )
4576 break;
4577
4578 DT_ZONE zone;
4579 zone.netIndex = fieldB;
4580 zone.layer = layer;
4581 zone.fillMode = static_cast<uint8_t>( flags1 );
4582 zone.rawFlag2 = static_cast<uint8_t>( flags2 );
4583 zone.connectionMode = static_cast<uint8_t>( flags3 );
4584 zone.separator = separator;
4585 zone.clearance = clearance;
4586 zone.minWidth = minWidth;
4587 zone.minimumArea = minimumArea;
4588 zone.outline.reserve( vtxCount );
4589
4590 bool validOutline = true;
4591
4592 for( int vi = 0; vi < vtxCount; vi++ )
4593 {
4594 size_t vp = vtxStart + static_cast<size_t>( vi ) * 8;
4595 int x = ReadInt4At( data, vp );
4596 int y = ReadInt4At( data, vp + 4 );
4597
4598 if( x < bboxXMin || x > bboxXMax || y < bboxYMin || y > bboxYMax )
4599 {
4600 validOutline = false;
4601 break;
4602 }
4603
4604 zone.outline.emplace_back( x, y );
4605 }
4606
4607 if( !validOutline )
4608 break;
4609
4610 wxString zoneNetName;
4611
4612 for( const DT_NET& net : m_nets )
4613 {
4614 if( net.index == fieldB )
4615 {
4616 zoneNetName = net.name;
4617 break;
4618 }
4619 }
4620
4621 DumpZoneHeader( zi, pos, data, fieldA, flags1, flags2, flags3, minWidth, clearance,
4622 minimumArea, separator, layer, fieldB, vtxCount, zoneNetName );
4623
4624 m_zones.push_back( std::move( zone ) );
4625 DT_ZONE& parsedZone = m_zones.back();
4626
4627 // Skip fill segments: int3(seg_count) + seg_count * 19 bytes
4628 pos = vtxEnd;
4629
4630 if( pos + 3 > aSearchEnd )
4631 break;
4632
4633 int fillSegCount = ReadInt3At( data, pos );
4634
4635 if( fillSegCount < 0 || fillSegCount > 500000 )
4636 break;
4637
4638 if( ShouldDumpZones() )
4639 {
4640 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: zone[%d] fill-segments off=0x%06zX count=%d" ), zi, pos,
4641 fillSegCount );
4642 }
4643
4644 pos += 3 + static_cast<size_t>( fillSegCount ) * 19;
4645
4646 // Skip fill polygons: int3(poly_count) + each polygon
4647 if( pos + 3 > aSearchEnd )
4648 break;
4649
4650 int fillPolyCount = ReadInt3At( data, pos );
4651
4652 if( fillPolyCount < 0 || fillPolyCount > 50000 )
4653 break;
4654
4655 if( ShouldDumpZones() )
4656 {
4657 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: zone[%d] fill-polys off=0x%06zX count=%d" ), zi, pos,
4658 fillPolyCount );
4659 }
4660
4661 pos += 3;
4662
4663 for( int pi = 0; pi < fillPolyCount; pi++ )
4664 {
4665 if( pos + 3 > aSearchEnd )
4666 {
4667 fillPolyCount = pi;
4668 break;
4669 }
4670
4671 int pvc = ReadInt3At( data, pos );
4672
4673 if( pvc < 0 || pvc > MAX_VERTICES )
4674 {
4675 fillPolyCount = pi;
4676 break;
4677 }
4678
4679 pos += 3 + static_cast<size_t>( pvc ) * 8 + 3;
4680
4681 if( pos > aSearchEnd )
4682 {
4683 fillPolyCount = pi;
4684 break;
4685 }
4686 }
4687
4688 // Post-fill style block starts immediately after fill polygon payload.
4689 // First fields observed in viewer examples:
4690 // int3(0), int3(spoke_mode), int4(line_spacing), int4(spoke_width)
4691 // where spoke_mode maps to the UI enum:
4692 // 0=Direct, 1=2 spoke 90, 2=2 spoke, 3=4 spoke 45, 4=4 spoke.
4693 if( pos + 14 <= aSearchEnd )
4694 {
4695 int styleLead = ReadInt3At( data, pos );
4696 int spokeMode = ReadInt3At( data, pos + 3 );
4697 int lineSpacing = ReadInt4At( data, pos + 6 );
4698 int spokeWidth = ReadInt4At( data, pos + 10 );
4699
4700 if( styleLead == 0
4701 && spokeMode >= 0 && spokeMode <= 4
4702 && lineSpacing > 0 && lineSpacing <= MAX_REASONABLE_DIM
4703 && spokeWidth > 0 && spokeWidth <= MAX_REASONABLE_DIM )
4704 {
4705 parsedZone.spokeMode = spokeMode;
4706 parsedZone.lineSpacing = lineSpacing;
4707 parsedZone.spokeWidth = spokeWidth;
4708
4709 if( ShouldDumpZones() )
4710 {
4711 wxLogTrace( traceDiptraceIo,
4712 wxT( "DipTrace: zone[%d] style lead=%d spokeMode=%d lineSpacing=%d spokeWidth=%d" ), zi,
4713 styleLead, spokeMode, lineSpacing, spokeWidth );
4714 }
4715 }
4716 }
4717
4718 // Scan for the next zone header. Some boards (e.g. PCB_6) have large
4719 // filled-data blocks between zone records; a short scan window misses
4720 // later valid headers.
4721 bool foundNext = false;
4722 size_t scanStart = pos;
4723
4724 for( size_t testPos = pos; testPos + 30 <= aSearchEnd; testPos++ )
4725 {
4726 if( !headerLooksPlausible( testPos, false ) )
4727 continue;
4728
4729 if( !headerLooksPlausible( testPos, true ) )
4730 continue;
4731
4732 pos = testPos;
4733 foundNext = true;
4734 break;
4735 }
4736
4737 if( foundNext && pos > scanStart )
4738 {
4739 parseZoneTrailer( parsedZone, scanStart, pos, zi );
4740 DumpZoneGap( zi, scanStart, pos, data );
4741 }
4742
4743 if( !foundNext )
4744 {
4745 parseZoneTrailer( parsedZone, scanStart, aSearchEnd, zi );
4746 DumpZoneTail( zi, scanStart, aSearchEnd, data );
4747 break;
4748 }
4749 }
4750
4751 if( !m_zones.empty() )
4752 {
4753 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: parsed %zu copper zones" ), m_zones.size() );
4754 }
4755
4756 // The zone section is field-located when anchored by its font preamble (font name + sizing +
4757 // an int4(-20000) tail), the structural prefix that immediately precedes the zone header. Count
4758 // a scan-locate only when zones were instead recovered by the plausible-header structural scan.
4759 if( !m_zones.empty() && !zoneViaPreamble )
4761}
4762
4763
4764// ---------------------------------------------------------------------------
4765// Board object creation
4766// ---------------------------------------------------------------------------
4767
4769{
4770 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
4771
4772 // The parsed layer list corresponds to project copper layers in top-to-bottom order.
4773 int copperCount = static_cast<int>( m_layers.size() );
4774
4775 if( copperCount < 2 )
4776 copperCount = 2;
4777
4778 // KiCad requires an even copper layer count
4779 if( copperCount % 2 != 0 )
4780 copperCount++;
4781
4782 m_board->SetCopperLayerCount( copperCount );
4783
4784 BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
4785 stackup.RemoveAll();
4786 stackup.BuildDefaultStackupList( &bds, copperCount );
4787
4788 LSET enabledLayers = m_board->GetEnabledLayers();
4789
4790 for( const DT_LAYER& layer : m_layers )
4791 {
4792 PCB_LAYER_ID kiLayer = MapCopperLayer( layer.index );
4793
4794 if( kiLayer == UNDEFINED_LAYER )
4795 kiLayer = MapLayer( layer.index );
4796
4797 if( kiLayer != UNDEFINED_LAYER )
4798 {
4799 enabledLayers.set( kiLayer );
4800
4801 if( !layer.name.empty() )
4802 m_board->SetLayerName( kiLayer, layer.name );
4803 }
4804 }
4805
4806 m_board->SetEnabledLayers( enabledLayers );
4807
4808 std::shared_ptr<NETCLASS> defNetclass = bds.m_NetSettings->GetDefaultNetclass();
4809
4810 if( !m_designRules.empty() )
4811 {
4812 const DT_DESIGN_RULE& firstRule = m_designRules[0];
4813
4814 if( firstRule.trackWidth > 0 )
4815 defNetclass->SetTrackWidth( ToKiCadCoord( firstRule.trackWidth ) );
4816
4817 if( firstRule.clearance > 0 )
4818 defNetclass->SetClearance( ToKiCadCoord( firstRule.clearance ) );
4819 }
4820
4821 if( !m_viaStyles.empty() )
4822 {
4823 const DT_VIA_STYLE& firstVia = m_viaStyles[0];
4824
4825 if( firstVia.outerDiameter > 0 )
4826 defNetclass->SetViaDiameter( ToKiCadCoord( firstVia.outerDiameter ) );
4827
4828 if( firstVia.drillDiameter > 0 )
4829 defNetclass->SetViaDrill( ToKiCadCoord( firstVia.drillDiameter ) );
4830 }
4831}
4832
4833
4835{
4836 if( m_outline.empty() && m_bboxXMin == 0 && m_bboxXMax == 0 )
4837 return;
4838
4839 STROKE_PARAMS stroke( pcbIUScale.mmToIU( 0.05 ), LINE_STYLE::SOLID );
4840
4841 if( !m_outline.empty() )
4842 {
4843 size_t n = m_outline.size();
4844
4845 if( n < 2 )
4846 return;
4847
4848 std::vector<VECTOR2I> pts;
4849 std::vector<uint8_t> arcs;
4850 pts.reserve( n );
4851 arcs.reserve( n );
4852
4853 for( const DT_VERTEX& v : m_outline )
4854 {
4855 pts.push_back( VECTOR2I( ToKiCadCoord( v.x ), ToKiCadCoord( v.y ) ) );
4856 arcs.push_back( v.arc );
4857 }
4858
4859 // In DipTrace, a vertex with arc=1 is an arc midpoint: the arc runs from the
4860 // previous (non-arc) vertex through this midpoint to the next (non-arc) vertex.
4861 size_t i = 0;
4862
4863 while( i < n && arcs[i] == 1 )
4864 i++;
4865
4866 if( i == n )
4867 return;
4868
4869 size_t startIndex = i;
4870 size_t steps = 0;
4871 size_t maxSteps = n * 4;
4872
4873 struct OUTLINE_PRIM
4874 {
4875 bool isArc = false;
4876 VECTOR2I start;
4877 VECTOR2I mid;
4878 VECTOR2I end;
4879 };
4880
4881 std::vector<OUTLINE_PRIM> outlinePrims;
4882 outlinePrims.reserve( n );
4883
4884 auto addSegment = [&]( const VECTOR2I& aStart, const VECTOR2I& aEnd )
4885 {
4886 OUTLINE_PRIM prim;
4887 prim.isArc = false;
4888 prim.start = aStart;
4889 prim.end = aEnd;
4890 outlinePrims.push_back( prim );
4891 };
4892
4893 auto addArc = [&]( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
4894 {
4895 OUTLINE_PRIM prim;
4896 prim.isArc = true;
4897 prim.start = aStart;
4898 prim.mid = aMid;
4899 prim.end = aEnd;
4900 outlinePrims.push_back( prim );
4901 };
4902
4903 while( i < n && steps++ < maxSteps )
4904 {
4905 size_t next = ( i + 1 ) % n;
4906
4907 if( arcs[next] == 1 )
4908 {
4909 size_t afterArc = ( next + 1 ) % n;
4910
4911 const VECTOR2I& start = pts[i];
4912 const VECTOR2I& mid = pts[next];
4913 const VECTOR2I& end = pts[afterArc];
4914 VECTOR2I v1 = mid - start;
4915 VECTOR2I v2 = end - mid;
4916 long long cross = static_cast<long long>( v1.x ) * static_cast<long long>( v2.y )
4917 - static_cast<long long>( v1.y ) * static_cast<long long>( v2.x );
4918 bool degenerateArc = ( start == mid ) || ( mid == end ) || ( start == end )
4919 || ( std::llabs( cross ) < 100 );
4920
4921 if( degenerateArc )
4922 {
4923 addSegment( start, end );
4924 }
4925 else
4926 {
4927 addArc( start, mid, end );
4928 }
4929
4930 i = afterArc;
4931 }
4932 else
4933 {
4934 addSegment( pts[i], pts[next] );
4935
4936 i = next;
4937 }
4938
4939 // We've wrapped back to the chosen non-arc start -- outline is closed.
4940 if( i == startIndex )
4941 break;
4942 }
4943
4944 if( steps >= maxSteps )
4945 {
4946 wxLogWarning( _( "DipTrace: outline traversal aborted after %zu steps (%zu vertices)" ),
4947 steps, n );
4948
4949 // Fallback: emit a closed polyline through all outline vertices.
4950 outlinePrims.clear();
4951
4952 for( size_t v = 0; v < n; v++ )
4953 addSegment( pts[v], pts[( v + 1 ) % n] );
4954 }
4955
4956 for( const OUTLINE_PRIM& prim : outlinePrims )
4957 {
4958 if( prim.isArc )
4959 {
4960 PCB_SHAPE* arc = new PCB_SHAPE( m_board, SHAPE_T::ARC );
4961 arc->SetLayer( Edge_Cuts );
4962 arc->SetStroke( stroke );
4963 arc->SetArcGeometry( prim.start, prim.mid, prim.end );
4964 m_board->Add( arc, ADD_MODE::APPEND );
4965 }
4966 else
4967 {
4969 seg->SetLayer( Edge_Cuts );
4970 seg->SetStroke( stroke );
4971 seg->SetStart( prim.start );
4972 seg->SetEnd( prim.end );
4973 m_board->Add( seg, ADD_MODE::APPEND );
4974 }
4975 }
4976 }
4977 else
4978 {
4979 // Fall back to the board bounding box as a closed rectangular outline.
4980 int x1 = ToKiCadCoord( m_bboxXMin );
4981 int y1 = ToKiCadCoord( m_bboxYMin );
4982 int x2 = ToKiCadCoord( m_bboxXMax );
4983 int y2 = ToKiCadCoord( m_bboxYMax );
4984
4985 VECTOR2I corners[4] = {
4986 VECTOR2I( x1, y1 ), VECTOR2I( x2, y1 ),
4987 VECTOR2I( x2, y2 ), VECTOR2I( x1, y2 )
4988 };
4989
4990 for( int i = 0; i < 4; i++ )
4991 {
4993 seg->SetLayer( Edge_Cuts );
4994 seg->SetStroke( stroke );
4995 seg->SetStart( corners[i] );
4996 seg->SetEnd( corners[( i + 1 ) % 4] );
4997 m_board->Add( seg, ADD_MODE::APPEND );
4998 }
4999 }
5000}
5001
5002
5004{
5005 if( aComp.isStandaloneVia )
5006 return;
5007
5008 FOOTPRINT* footprint = new FOOTPRINT( m_board );
5009
5011 {
5012 for( const DT_TRACK_CHAIN& chain : m_trackChains )
5013 {
5014 if( chain.netIndex < 0 )
5015 continue;
5016
5017 auto& anchors = m_routingAnchorsByNet[chain.netIndex];
5018 anchors.reserve( anchors.size() + chain.nodes.size() );
5019
5020 for( const DT_TRACK_NODE& node : chain.nodes )
5021 anchors.emplace_back( ToKiCadCoord( node.x ), ToKiCadCoord( node.y ) );
5022 }
5023
5025 }
5026
5027 footprint->SetReference( aComp.refdes );
5028 footprint->SetValue( aComp.value );
5029
5030 if( !aComp.patternName.empty() )
5031 {
5032 LIB_ID libId;
5033 libId.SetLibItemName( aComp.patternName );
5034 footprint->SetFPID( libId );
5035 }
5036
5037 // Add pads while footprint is at origin so SetOrientation/SetPosition
5038 // will transform them correctly via Rotate()/Move().
5039 for( size_t padIdx = 0; padIdx < aComp.pads.size(); padIdx++ )
5040 {
5041 const DT_PAD& dtPad = aComp.pads[padIdx];
5042 PAD* pad = new PAD( footprint );
5043
5044 VECTOR2I padLocal( ToKiCadCoord( dtPad.x ), ToKiCadCoord( dtPad.y ) );
5045 pad->SetPosition( padLocal );
5046 pad->SetNumber( dtPad.number );
5047
5048 VECTOR2I padSize( ToKiCadCoord( dtPad.width ), ToKiCadCoord( dtPad.height ) );
5049 pad->SetSize( PADSTACK::ALL_LAYERS, padSize );
5050
5051 if( !dtPad.polygonVertices.empty() )
5052 {
5054 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
5055
5056 int anchorDim = std::min( ToKiCadCoord( dtPad.width ),
5057 ToKiCadCoord( dtPad.height ) );
5058 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorDim, anchorDim } );
5059
5060 std::vector<VECTOR2I> polyPts;
5061 polyPts.reserve( dtPad.polygonVertices.size() );
5062
5063 for( const auto& [vx, vy] : dtPad.polygonVertices )
5064 polyPts.emplace_back( ToKiCadCoord( vx ), ToKiCadCoord( vy ) );
5065
5066 pad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, polyPts, 0, true );
5067 }
5068 else if( dtPad.style == 2 )
5069 {
5070 // DipTrace pad style 2 = Rectangle
5072 }
5073 else if( dtPad.width == dtPad.height )
5074 {
5075 // Styles 0 (ellipse) and 1 (oval) both render as circles when w==h
5077 }
5078 else
5079 {
5080 // Style 0 (ellipse) and 1 (oval) with w!=h both map to OVAL (stadium)
5082 }
5083
5084 int drillW = dtPad.drillWidth;
5085 int drillH = dtPad.drillHeight;
5086 bool isSmd = ( dtPad.mountType == 1 );
5087
5088 if( drillW > 0 && drillH <= 0 )
5089 drillH = drillW;
5090
5091 if( drillH > 0 && drillW <= 0 )
5092 drillW = drillH;
5093
5094 if( isSmd )
5095 {
5096 pad->SetAttribute( PAD_ATTRIB::SMD );
5097 pad->SetLayerSet( PAD::SMDMask() );
5098 }
5099 else
5100 {
5101 pad->SetAttribute( PAD_ATTRIB::PTH );
5102 pad->SetLayerSet( PAD::PTHMask() );
5103
5104 if( drillW > 0 && drillH > 0 )
5105 {
5106 VECTOR2I drill( ToKiCadCoord( drillW ),
5107 ToKiCadCoord( drillH ) );
5108 pad->SetDrillSize( drill );
5109
5110 if( drillW == drillH )
5111 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
5112 else
5113 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
5114 }
5115 }
5116
5117 if( dtPad.width != dtPad.height && !aComp.pads.empty() )
5118 {
5119 // DipTrace pad post-block orientation class is serialized one step ahead
5120 // around the footprint perimeter:
5121 // pad1 uses direct class; padN>1 uses previous pad's class.
5122 uint8_t orientClass = dtPad.orientClass;
5123
5124 if( padIdx > 0 )
5125 orientClass = aComp.pads[padIdx - 1].orientClass;
5126
5127 if( orientClass == 1 )
5128 pad->SetOrientation( EDA_ANGLE( 90.0, DEGREES_T ) );
5129 else
5130 pad->SetOrientation( EDA_ANGLE( 0.0, DEGREES_T ) );
5131 }
5132
5133 if( NETINFO_ITEM* net = ResolveNetByIndex( dtPad.netIndex ) )
5134 pad->SetNet( net );
5135
5136 footprint->Add( pad, ADD_MODE::APPEND );
5137 }
5138
5139 for( const DT_MOUNT_HOLE& dtHole : aComp.holes )
5140 {
5141 PAD* holePad = new PAD( footprint );
5142 int holeOuter = std::max( dtHole.outerDiameter, dtHole.drillDiameter );
5143 int holeDrill = dtHole.drillDiameter;
5144
5145 holePad->SetPosition( VECTOR2I( ToKiCadCoord( dtHole.x ),
5146 ToKiCadCoord( dtHole.y ) ) );
5147 holePad->SetNumber( wxString() );
5149 holePad->SetSize( PADSTACK::ALL_LAYERS,
5150 VECTOR2I( ToKiCadCoord( holeOuter ),
5151 ToKiCadCoord( holeOuter ) ) );
5152 holePad->SetAttribute( PAD_ATTRIB::NPTH );
5153 holePad->SetLayerSet( PAD::UnplatedHoleMask() );
5155 holePad->SetDrillSize( VECTOR2I( ToKiCadCoord( holeDrill ),
5156 ToKiCadCoord( holeDrill ) ) );
5157 footprint->Add( holePad, ADD_MODE::APPEND );
5158 }
5159
5160 // When a footprint carries a Top Assembly body outline, DipTrace treats it as the body
5161 // graphic and suppresses the redundant Top Silk outline, leaving only silk markers such as
5162 // the polarity dot. Footprints without an assembly outline (e.g. two-terminal caps) keep
5163 // their silk outline. Detect the assembly outline up front so the silk lines can be dropped.
5164 bool hasAssemblyOutline = false;
5165
5166 for( const DT_FP_SHAPE& s : aComp.shapes )
5167 {
5168 if( s.layer == DT_FP_LAYER_TOP_ASSY )
5169 {
5170 hasAssemblyOutline = true;
5171 break;
5172 }
5173 }
5174
5175 // Add footprint outline shapes (silkscreen / fab layer graphics)
5176 if( !aComp.shapes.empty() && aComp.bboxWidth != 0 && aComp.bboxHeight != 0 )
5177 {
5178 int scaleX = aComp.bboxWidth;
5179 int scaleY = aComp.bboxHeight;
5180
5181 // The bbox stores the PLACED (rotated) extent while the shape coordinates are canonical
5182 // (the unrotated pattern frame). A 90/270-degree placement transposes the bbox width and
5183 // height, so restore the canonical axes before scaling; SetOrientation() then rotates the
5184 // shapes into place alongside the pads. Without this, a rotated connector's silk lands 90
5185 // degrees off its pad field. The quarter-turn parity drives the swap exactly.
5186 if( aComp.hasPlacementQuarterTurns )
5187 {
5188 if( ( ( aComp.placementQuarterTurns % 2 ) + 2 ) % 2 == 1 )
5189 std::swap( scaleX, scaleY );
5190 }
5191 else
5192 {
5193 // Legacy fallback when the placement metadata is absent: detect a transposed bbox from
5194 // an extreme physical aspect ratio relative to the normalized shape extents.
5195 int minSX = INT_MAX, maxSX = INT_MIN;
5196 int minSY = INT_MAX, maxSY = INT_MIN;
5197
5198 for( const DT_FP_SHAPE& s : aComp.shapes )
5199 {
5200 minSX = std::min( { minSX, s.x1, s.x2 } );
5201 maxSX = std::max( { maxSX, s.x1, s.x2 } );
5202 minSY = std::min( { minSY, s.y1, s.y2 } );
5203 maxSY = std::max( { maxSY, s.y1, s.y2 } );
5204 }
5205
5206 int shapeXRange = maxSX - minSX;
5207 int shapeYRange = maxSY - minSY;
5208
5209 if( shapeXRange > 0 && shapeYRange > 0 )
5210 {
5211 double physX = static_cast<double>( shapeXRange ) * std::abs( scaleX )
5213 double physY = static_cast<double>( shapeYRange ) * std::abs( scaleY )
5215 double aspect = physX / physY;
5216
5217 if( aspect < 0.2 || aspect > 5.0 )
5218 std::swap( scaleX, scaleY );
5219 }
5220 }
5221
5222 // Default line width when shape record uses the sentinel value
5223 static constexpr int DEFAULT_LINE_WIDTH_DT = 3000; // ~0.1mm
5224
5225 auto scaleShapeCoord = [&]( int aShapeVal, int aBboxDim ) -> int
5226 {
5227 return ToKiCadCoord(
5228 static_cast<int>( static_cast<int64_t>( aShapeVal ) * aBboxDim
5229 / FP_SHAPE_NORM_RANGE ) );
5230 };
5231
5232 for( const DT_FP_SHAPE& dtShape : aComp.shapes )
5233 {
5234 PCB_SHAPE* shape = new PCB_SHAPE( footprint );
5235
5236 int lineWidth = ( dtShape.width == FP_SHAPE_DEFAULT_WIDTH || dtShape.width <= 0 )
5237 ? ToKiCadCoord( DEFAULT_LINE_WIDTH_DT )
5238 : ToKiCadCoord( dtShape.width );
5239
5240 shape->SetWidth( lineWidth );
5241
5242 // Drop the redundant Top Silk OUTLINE when the assembly outline already describes the
5243 // body, but keep silk fill markers such as the polarity dot. Footprints without an
5244 // assembly outline (e.g. two-terminal caps) keep their silk outline.
5245 if( hasAssemblyOutline && dtShape.layer == DT_FP_LAYER_TOP_SILK
5246 && dtShape.type != DT_SHAPE_FILLOBROUND )
5247 {
5248 delete shape;
5249 continue;
5250 }
5251
5252 // Map the DipTrace footprint-graphic layer enum to a top-relative KiCad layer.
5253 // Bottom-side footprints are mirrored wholesale by Flip() below, so always assign
5254 // the front layer here; assigning the back layer too would double-flip it.
5255 PCB_LAYER_ID shapeLayer;
5256
5257 switch( dtShape.layer )
5258 {
5259 case DT_FP_LAYER_TOP_ASSY: shapeLayer = F_Fab; break;
5260 case DT_FP_LAYER_TOP_MASK: shapeLayer = F_Mask; break;
5261 case DT_FP_LAYER_TOP_PASTE: shapeLayer = F_Paste; break;
5262 case DT_FP_LAYER_TOP_KEEPOUT: shapeLayer = F_CrtYd; break;
5263 case DT_FP_LAYER_TOP_COURTYARD: shapeLayer = F_CrtYd; break;
5264 case DT_FP_LAYER_TOP_OUTLINE: shapeLayer = F_Fab; break;
5266 default: shapeLayer = F_SilkS; break;
5267 }
5268
5269 shape->SetLayer( shapeLayer );
5270
5271 VECTOR2I p1( scaleShapeCoord( dtShape.x1, scaleX ),
5272 scaleShapeCoord( dtShape.y1, scaleY ) );
5273 VECTOR2I p2( scaleShapeCoord( dtShape.x2, scaleX ),
5274 scaleShapeCoord( dtShape.y2, scaleY ) );
5275
5276 if( dtShape.type == DT_SHAPE_RECT )
5277 {
5278 delete shape;
5279
5280 const VECTOR2I corners[4] = {
5281 VECTOR2I( p1.x, p1.y ),
5282 VECTOR2I( p2.x, p1.y ),
5283 VECTOR2I( p2.x, p2.y ),
5284 VECTOR2I( p1.x, p2.y )
5285 };
5286
5287 for( int i = 0; i < 4; i++ )
5288 {
5289 PCB_SHAPE* edge = new PCB_SHAPE( footprint );
5290 edge->SetWidth( lineWidth );
5291 edge->SetLayer( shapeLayer );
5292 edge->SetShape( SHAPE_T::SEGMENT );
5293 edge->SetStart( corners[i] );
5294 edge->SetEnd( corners[( i + 1 ) % 4] );
5295 footprint->Add( edge, ADD_MODE::APPEND );
5296 }
5297
5298 continue;
5299 }
5300 else if( dtShape.type == DT_SHAPE_LINE )
5301 {
5302 shape->SetShape( SHAPE_T::SEGMENT );
5303 shape->SetStart( p1 );
5304 shape->SetEnd( p2 );
5305 }
5306 else if( dtShape.type == DT_SHAPE_CIRCLE )
5307 {
5308 shape->SetShape( SHAPE_T::CIRCLE );
5309 VECTOR2I center( ( p1.x + p2.x ) / 2, ( p1.y + p2.y ) / 2 );
5310 int radius = ( p2 - p1 ).EuclideanNorm() / 2;
5311 shape->SetCenter( center );
5312 shape->SetEnd( VECTOR2I( center.x + radius, center.y ) );
5313 }
5314 else if( dtShape.type == DT_SHAPE_ARC )
5315 {
5316 VECTOR2I mid( scaleShapeCoord( dtShape.midX, scaleX ),
5317 scaleShapeCoord( dtShape.midY, scaleY ) );
5318
5319 // A collinear midpoint yields a degenerate arc whose centre runs off to infinity
5320 // (DipTrace stores some straight edges as a zero-bulge arc). Such a centre overflows
5321 // the integer rotation math on a non-cardinal placement angle, so emit a segment.
5322 int64_t cross = static_cast<int64_t>( p2.x - p1.x ) * ( mid.y - p1.y )
5323 - static_cast<int64_t>( p2.y - p1.y ) * ( mid.x - p1.x );
5324 int64_t chordSq = static_cast<int64_t>( p2.x - p1.x ) * ( p2.x - p1.x )
5325 + static_cast<int64_t>( p2.y - p1.y ) * ( p2.y - p1.y );
5326
5327 if( chordSq == 0 || std::abs( cross ) * 1000 < chordSq )
5328 {
5329 shape->SetShape( SHAPE_T::SEGMENT );
5330 shape->SetStart( p1 );
5331 shape->SetEnd( p2 );
5332 }
5333 else
5334 {
5335 shape->SetShape( SHAPE_T::ARC );
5336 shape->SetArcGeometry( p1, mid, p2 );
5337 }
5338 }
5339 else if( dtShape.type == DT_SHAPE_FILLOBROUND )
5340 {
5341 // A small filled obround marker (the diode cathode / pin-1 dot). The two points
5342 // are the bounding box corners; render it as a filled circle of that diameter.
5343 VECTOR2I center( ( p1.x + p2.x ) / 2, ( p1.y + p2.y ) / 2 );
5344 int radius = std::min( std::abs( p2.x - p1.x ), std::abs( p2.y - p1.y ) ) / 2;
5345
5346 shape->SetShape( SHAPE_T::CIRCLE );
5347 shape->SetCenter( center );
5348 shape->SetEnd( VECTOR2I( center.x + radius, center.y ) );
5349 shape->SetFilled( true );
5350 shape->SetWidth( 0 );
5351 }
5352 else
5353 {
5354 delete shape;
5355 continue;
5356 }
5357
5358 footprint->Add( shape, ADD_MODE::APPEND );
5359 }
5360 }
5361
5362 VECTOR2I pos( ToKiCadCoord( aComp.positionX ), ToKiCadCoord( aComp.positionY ) );
5363
5364 // Prefer the exact placement-section angle; otherwise snap to the metadata quarter turn.
5365 double orientationDeg;
5366
5367 if( aComp.hasPlacementAngle )
5368 {
5369 // The stored angle is cumulative (it can exceed a full turn, e.g. 630 or 990 degrees);
5370 // reduce to a single turn for placement.
5371 orientationDeg = std::fmod( aComp.placementAngleDeg, 360.0 );
5372
5373 if( orientationDeg < 0.0 )
5374 orientationDeg += 360.0;
5375
5376 // The angle is recovered from a rounded-radian field, so a quarter-turn lands a hair off
5377 // an exact multiple of 90 (e.g. 269.9994). Snap those back to the cardinal value so the
5378 // footprint rotation takes the exact axis-swap path rather than a floating-point rotation.
5379 double nearest90 = std::round( orientationDeg / 90.0 ) * 90.0;
5380
5381 if( std::abs( orientationDeg - nearest90 ) < 0.02 )
5382 orientationDeg = std::fmod( nearest90, 360.0 );
5383 }
5384 else
5385 {
5386 int orientationQuarterTurns = aComp.hasPlacementQuarterTurns
5387 ? aComp.placementQuarterTurns
5388 : static_cast<int>( std::lround(
5389 ToKiCadAngleDeg( aComp.rotation ) / 90.0 ) );
5390 orientationDeg = ( ( orientationQuarterTurns % 4 ) + 4 ) % 4 * 90.0;
5391 }
5392
5394 {
5395 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: fp-orient ref=%s pat=%s qturn=%d hasQ=%d exact=%d chosen=%.2f" ), // format:allow
5396 aComp.refdes, aComp.patternName, aComp.placementQuarterTurns,
5397 aComp.hasPlacementQuarterTurns ? 1 : 0, aComp.hasPlacementAngle ? 1 : 0, orientationDeg );
5398 }
5399
5400 // Set layer before orientation so bottom-side flip is handled first
5401 if( aComp.layer == 1 )
5402 footprint->Flip( VECTOR2I( 0, 0 ), FLIP_DIRECTION::TOP_BOTTOM );
5403
5404 footprint->SetOrientation( EDA_ANGLE( orientationDeg, DEGREES_T ) );
5405 footprint->SetPosition( pos );
5406
5407 // Tail offsets appear to be board-global Y offsets, not local-footprint offsets.
5408 // Apply after footprint placement so orientation does not rotate text offsets.
5409 if( aComp.hasTailData )
5410 {
5411 VECTOR2I fpPos = footprint->GetPosition();
5412
5413 footprint->Reference().SetPosition(
5414 fpPos + VECTOR2I( 0, ToKiCadCoord( aComp.refdesYOffset ) ) );
5415 footprint->Reference().SetVisible( aComp.refdesVisible );
5416
5417 footprint->Value().SetPosition(
5418 fpPos + VECTOR2I( 0, ToKiCadCoord( aComp.valueYOffset ) ) );
5419 footprint->Value().SetVisible( aComp.valueVisible );
5420 }
5421
5422 m_board->Add( footprint, ADD_MODE::APPEND );
5423}
5424
5425
5427{
5428 if( aText.text.empty() )
5429 return;
5430
5431 PCB_TEXT* text = new PCB_TEXT( m_board );
5432 text->SetText( aText.text );
5433 PCB_LAYER_ID textLayer = MapLayer( aText.layer );
5434
5435 if( textLayer == UNDEFINED_LAYER )
5436 textLayer = F_SilkS;
5437
5438 text->SetLayer( textLayer );
5439
5440 // Position at the center of the text bounding box. x1/x2/y1/y2 are raw int4 values that are
5441 // not range-checked, so sum/difference them in int64 to avoid signed-int overflow (UB).
5442 int cx = ToKiCadCoord( static_cast<int>( ( static_cast<int64_t>( aText.x1 ) + aText.x2 ) / 2 ) );
5443 int cy = ToKiCadCoord( static_cast<int>( ( static_cast<int64_t>( aText.y1 ) + aText.y2 ) / 2 ) );
5444 text->SetPosition( VECTOR2I( cx, cy ) );
5445
5446 int height = std::abs(
5447 ToKiCadCoord( static_cast<int>( static_cast<int64_t>( aText.y2 ) - aText.y1 ) ) );
5448
5449 if( height > 0 )
5450 text->SetTextSize( VECTOR2I( height, height ) );
5451 else
5452 text->SetTextSize( VECTOR2I( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) ) );
5453
5454 if( aText.lineWidth > 0 )
5455 text->SetTextThickness( ToKiCadCoord( aText.lineWidth ) );
5456 else
5457 text->SetTextThickness( pcbIUScale.mmToIU( 0.15 ) );
5458
5459 m_board->Add( text, ADD_MODE::APPEND );
5460}
5461
5462
5463NETINFO_ITEM* PCB_PARSER::ResolveNetByIndex( int aDipTraceNetIndex ) const
5464{
5465 if( aDipTraceNetIndex < 0 )
5466 return nullptr;
5467
5468 auto it = m_kicadNetByDipTraceIndex.find( aDipTraceNetIndex );
5469
5470 if( it != m_kicadNetByDipTraceIndex.end() )
5471 return it->second;
5472
5473 return nullptr;
5474}
5475
5476
5477const DT_NET* PCB_PARSER::ResolveDipTraceNetByIndex( int aDipTraceNetIndex ) const
5478{
5479 if( aDipTraceNetIndex < 0 )
5480 return nullptr;
5481
5482 auto it = m_dipTraceNetByIndex.find( aDipTraceNetIndex );
5483
5484 if( it != m_dipTraceNetByIndex.end() )
5485 return it->second;
5486
5487 return nullptr;
5488}
5489
5490
5492{
5494 m_dipTraceNetByIndex.clear();
5495
5496 for( const DT_NET& net : m_nets )
5497 {
5498 wxString netName = net.name;
5499
5500 if( netName.empty() )
5501 netName = wxString::Format( wxS( "DipTrace_Net_%d" ), net.index );
5502
5503 NETINFO_ITEM* netinfo = m_board->FindNet( netName );
5504
5505 if( !netinfo )
5506 {
5507 netinfo = new NETINFO_ITEM( m_board, netName );
5508 m_board->Add( netinfo, ADD_MODE::APPEND );
5509 }
5510
5511 if( net.index >= 0 )
5512 {
5513 auto [it, inserted] = m_kicadNetByDipTraceIndex.emplace( net.index, netinfo );
5514
5515 if( !inserted && it->second != netinfo )
5516 {
5517 wxLogWarning( _( "DipTrace: net index %d maps to multiple net names (%s, %s)" ),
5518 net.index, it->second->GetNetname(), netinfo->GetNetname() );
5519 }
5520
5521 auto [dtIt, dtInserted] = m_dipTraceNetByIndex.emplace( net.index, &net );
5522
5523 if( !dtInserted && dtIt->second != &net )
5524 {
5525 wxLogWarning( _( "DipTrace: duplicate net metadata for net index %d" ),
5526 net.index );
5527 }
5528 }
5529 }
5530}
5531
5532
5534{
5535 int created = 0;
5536 int duplicates = 0;
5537 int missingGeometry = 0;
5538 std::set<std::tuple<int, int, int>> createdKeys;
5539
5540 auto hasBoardViaAt = [&]( const VECTOR2I& aPos, int aNetCode ) -> bool
5541 {
5542 for( const PCB_TRACK* track : m_board->Tracks() )
5543 {
5544 if( track->Type() != PCB_VIA_T )
5545 continue;
5546
5547 const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
5548
5549 if( via->GetPosition() == aPos && via->GetNetCode() == aNetCode )
5550 return true;
5551 }
5552
5553 return false;
5554 };
5555
5556 for( const DT_COMPONENT& comp : m_components )
5557 {
5558 if( !comp.isStandaloneVia )
5559 continue;
5560
5561 int viaX = comp.positionX;
5562 int viaY = comp.positionY;
5563 int viaOuter = std::max( comp.bboxWidth, comp.padWidthHint );
5564 int viaDrill = std::max( comp.drillWidthHint, comp.drillHeightHint );
5565 int netIndex = -1;
5566
5567 if( !comp.pads.empty() )
5568 {
5569 const DT_PAD& pad = comp.pads.front();
5570 viaX += pad.x;
5571 viaY += pad.y;
5572 netIndex = pad.netIndex;
5573
5574 if( pad.width > 0 )
5575 viaOuter = pad.width;
5576
5577 if( pad.drillWidth > 0 )
5578 viaDrill = pad.drillWidth;
5579 else if( pad.drillHeight > 0 )
5580 viaDrill = pad.drillHeight;
5581 }
5582
5583 if( viaOuter <= 0 || viaDrill <= 0 )
5584 {
5585 missingGeometry++;
5586 continue;
5587 }
5588
5589 VECTOR2I viaPos( ToKiCadCoord( viaX ), ToKiCadCoord( viaY ) );
5590 NETINFO_ITEM* net = ResolveNetByIndex( netIndex );
5591 int netCode = net ? net->GetNetCode() : -1;
5592 std::tuple<int, int, int> key( viaPos.x, viaPos.y, netCode );
5593
5594 if( createdKeys.find( key ) != createdKeys.end() || hasBoardViaAt( viaPos, netCode ) )
5595 {
5596 duplicates++;
5597 continue;
5598 }
5599
5600 PCB_VIA* via = new PCB_VIA( m_board );
5601 via->SetPosition( viaPos );
5602 via->SetWidth( ToKiCadCoord( viaOuter ) );
5603 via->SetDrill( ToKiCadCoord( viaDrill ) );
5604 via->SetLayerPair( F_Cu, B_Cu );
5605 via->SetViaType( VIATYPE::THROUGH );
5606
5607 if( net )
5608 via->SetNet( net );
5609
5610 m_board->Add( via, ADD_MODE::APPEND );
5611 createdKeys.insert( key );
5612 created++;
5613 }
5614
5615 if( created > 0 || duplicates > 0 || missingGeometry > 0 )
5616 {
5617 wxLogTrace( traceDiptraceIo,
5618 wxT( "DipTrace: created %d standalone vias from component records "
5619 "(duplicates=%d, missingGeometry=%d)" ),
5620 created, duplicates, missingGeometry );
5621 }
5622}
5623
5624
5626{
5627 int trackCount = 0;
5628 int viaCount = 0;
5629 int missingChainNets = 0;
5630 int nonCopperTrackSkips = 0;
5631 int nonCopperViaSkips = 0;
5632 int inferredLayerChangeVias = 0;
5633 int explicitViaStyleVias = 0;
5634 int duplicateViaSkips = 0;
5635 int crossNetViaSkips = 0;
5636
5637 struct POS_NET_KEY
5638 {
5639 int net = -1;
5640 int x = 0;
5641 int y = 0;
5642
5643 bool operator==( const POS_NET_KEY& aOther ) const
5644 {
5645 return net == aOther.net && x == aOther.x && y == aOther.y;
5646 }
5647 };
5648
5649 struct POS_NET_HASH
5650 {
5651 size_t operator()( const POS_NET_KEY& aKey ) const
5652 {
5653 size_t h1 = static_cast<size_t>( static_cast<uint32_t>( aKey.net ) );
5654 size_t h2 = static_cast<size_t>( static_cast<uint32_t>( aKey.x ) );
5655 size_t h3 = static_cast<size_t>( static_cast<uint32_t>( aKey.y ) );
5656 return h1 ^ ( h2 * 0x9e3779b1U ) ^ ( h3 * 0x85ebca6bU );
5657 }
5658 };
5659
5660 auto makeKey = []( int aNet, int aX, int aY ) -> POS_NET_KEY
5661 {
5662 POS_NET_KEY key;
5663 key.net = aNet;
5664 key.x = aX;
5665 key.y = aY;
5666 return key;
5667 };
5668
5669 struct POS_KEY
5670 {
5671 int x = 0;
5672 int y = 0;
5673
5674 bool operator==( const POS_KEY& aOther ) const
5675 {
5676 return x == aOther.x && y == aOther.y;
5677 }
5678 };
5679
5680 struct POS_HASH
5681 {
5682 size_t operator()( const POS_KEY& aKey ) const
5683 {
5684 size_t h1 = static_cast<size_t>( static_cast<uint32_t>( aKey.x ) );
5685 size_t h2 = static_cast<size_t>( static_cast<uint32_t>( aKey.y ) );
5686 return ( h1 * 0x9e3779b1U ) ^ ( h2 * 0x85ebca6bU );
5687 }
5688 };
5689
5690 auto makePosKey = []( int aX, int aY ) -> POS_KEY
5691 {
5692 POS_KEY key;
5693 key.x = aX;
5694 key.y = aY;
5695 return key;
5696 };
5697
5698 auto resolveCopperLayer = [&]( int aDipTraceLayer ) -> PCB_LAYER_ID
5699 {
5700 PCB_LAYER_ID layer = MapCopperLayer( aDipTraceLayer );
5701
5702 if( layer == UNDEFINED_LAYER )
5703 layer = MapLayer( aDipTraceLayer );
5704
5705 if( !IsCopperLayer( layer ) )
5706 return UNDEFINED_LAYER;
5707
5708 return layer;
5709 };
5710
5711 std::unordered_map<POS_NET_KEY, std::set<int>, POS_NET_HASH> layersByNetPos;
5712 std::unordered_set<POS_NET_KEY, POS_NET_HASH> createdVias;
5713 std::unordered_map<POS_KEY, std::set<int>, POS_HASH> netsByPos;
5714
5715 for( const DT_TRACK_CHAIN& chain : m_trackChains )
5716 {
5717 for( const DT_TRACK_NODE& node : chain.nodes )
5718 {
5719 layersByNetPos[makeKey( chain.netIndex, node.x, node.y )].insert( node.layer );
5720
5721 if( chain.netIndex >= 0 )
5722 netsByPos[makePosKey( node.x, node.y )].insert( chain.netIndex );
5723 }
5724 }
5725
5726 std::shared_ptr<NETCLASS> defNetclass =
5727 m_board->GetDesignSettings().m_NetSettings->GetDefaultNetclass();
5728
5729 for( const DT_TRACK_CHAIN& chain : m_trackChains )
5730 {
5731 NETINFO_ITEM* net = ResolveNetByIndex( chain.netIndex );
5732 const DT_NET* dtNet = ResolveDipTraceNetByIndex( chain.netIndex );
5733
5734 if( !net && chain.netIndex >= 0 )
5735 missingChainNets++;
5736
5737 for( size_t i = 0; i + 1 < chain.nodes.size(); i++ )
5738 {
5739 const DT_TRACK_NODE& n0 = chain.nodes[i];
5740 const DT_TRACK_NODE& n1 = chain.nodes[i + 1];
5741
5742 PCB_TRACK* track = new PCB_TRACK( m_board );
5743
5744 track->SetStart( VECTOR2I( ToKiCadCoord( n0.x ), ToKiCadCoord( n0.y ) ) );
5745 track->SetEnd( VECTOR2I( ToKiCadCoord( n1.x ), ToKiCadCoord( n1.y ) ) );
5746 int segWidth = ( n1.width > 0 ) ? n1.width : n0.width;
5747 track->SetWidth( ToKiCadCoord( segWidth ) );
5748 PCB_LAYER_ID trackLayer = resolveCopperLayer( n1.layer );
5749
5750 if( trackLayer == UNDEFINED_LAYER )
5751 {
5752 nonCopperTrackSkips++;
5753 delete track;
5754 continue;
5755 }
5756
5757 track->SetLayer( trackLayer );
5758
5759 if( net )
5760 track->SetNet( net );
5761
5762 m_board->Add( track, ADD_MODE::APPEND );
5763 trackCount++;
5764 }
5765
5766 for( size_t nodeIdx = 0; nodeIdx < chain.nodes.size(); nodeIdx++ )
5767 {
5768 const DT_TRACK_NODE& node = chain.nodes[nodeIdx];
5769 POS_NET_KEY key = makeKey( chain.netIndex, node.x, node.y );
5770 auto posIt = layersByNetPos.find( key );
5771 bool explicitVia = node.hasVia;
5772 bool explicitViaStyle = node.viaStyleIdx >= 0;
5773
5774 if( !explicitVia )
5775 continue;
5776
5777 auto netPosIt = netsByPos.find( makePosKey( node.x, node.y ) );
5778
5779 if( netPosIt != netsByPos.end() && netPosIt->second.size() > 1 )
5780 {
5781 crossNetViaSkips++;
5782 continue;
5783 }
5784
5785 if( createdVias.find( key ) != createdVias.end() )
5786 {
5787 duplicateViaSkips++;
5788 continue;
5789 }
5790
5791 if( explicitViaStyle )
5792 explicitViaStyleVias++;
5793
5794 int viaOuterDiam = node.viaOuterDiam;
5795 int viaDrillDiam = node.viaDrillDiam;
5796 PCB_LAYER_ID styleViaLayerA = UNDEFINED_LAYER;
5797 PCB_LAYER_ID styleViaLayerB = UNDEFINED_LAYER;
5798
5799 if( node.viaStyleIdx >= 0 && node.viaStyleIdx < static_cast<int>( m_viaStyles.size() ) )
5800 {
5801 const DT_VIA_STYLE& viaStyle = m_viaStyles[node.viaStyleIdx];
5802 viaOuterDiam = viaStyle.outerDiameter;
5803 viaDrillDiam = viaStyle.drillDiameter;
5804
5805 PCB_LAYER_ID l1 = resolveCopperLayer( viaStyle.layer1 );
5806 PCB_LAYER_ID l2 = resolveCopperLayer( viaStyle.layer2 );
5807
5808 if( l1 != UNDEFINED_LAYER && l2 != UNDEFINED_LAYER && l1 != l2 )
5809 {
5810 if( CopperLayerToOrdinal( l1 ) <= CopperLayerToOrdinal( l2 ) )
5811 {
5812 styleViaLayerA = l1;
5813 styleViaLayerB = l2;
5814 }
5815 else
5816 {
5817 styleViaLayerA = l2;
5818 styleViaLayerB = l1;
5819 }
5820 }
5821 }
5822
5823 if( dtNet )
5824 {
5825 if( viaOuterDiam <= 0 )
5826 viaOuterDiam = dtNet->defaultViaOuterDiam;
5827
5828 if( viaDrillDiam <= 0 )
5829 viaDrillDiam = dtNet->defaultViaDrillDiam;
5830 }
5831
5832 int viaWidthIU = ( viaOuterDiam > 0 ) ? ToKiCadCoord( viaOuterDiam ) : 0;
5833 int viaDrillIU = ( viaDrillDiam > 0 ) ? ToKiCadCoord( viaDrillDiam ) : 0;
5834
5835 if( viaWidthIU <= 0 && defNetclass )
5836 viaWidthIU = defNetclass->GetViaDiameter();
5837
5838 if( viaDrillIU <= 0 && defNetclass )
5839 viaDrillIU = defNetclass->GetViaDrill();
5840
5841 if( viaWidthIU <= 0 )
5842 viaWidthIU = pcbIUScale.mmToIU( 0.6 );
5843
5844 PCB_VIA* via = new PCB_VIA( m_board );
5845
5846 via->SetPosition( VECTOR2I( ToKiCadCoord( node.x ), ToKiCadCoord( node.y ) ) );
5847 via->SetWidth( viaWidthIU );
5848
5849 if( viaDrillIU > 0 )
5850 via->SetDrill( viaDrillIU );
5851
5852 auto ordinalToLayer = [&]( size_t aOrdinal ) -> PCB_LAYER_ID
5853 {
5854 int copperCount = m_board->GetCopperLayerCount();
5855
5856 if( aOrdinal == 0 )
5857 return F_Cu;
5858
5859 if( aOrdinal >= static_cast<size_t>( copperCount - 1 ) )
5860 return B_Cu;
5861
5862 int innerIdx = static_cast<int>( aOrdinal ) - 1;
5863
5864 if( innerIdx < 0 || innerIdx > 29 )
5865 return UNDEFINED_LAYER;
5866
5867 return static_cast<PCB_LAYER_ID>( In1_Cu + innerIdx * 2 );
5868 };
5869
5870 bool haveLayerRange = false;
5871 size_t minOrd = 0;
5872 size_t maxOrd = 0;
5873
5874 auto accumulateDipLayer = [&]( int aDipLayer )
5875 {
5876 PCB_LAYER_ID layer = resolveCopperLayer( aDipLayer );
5877
5878 if( layer == UNDEFINED_LAYER )
5879 return;
5880
5881 size_t ord = CopperLayerToOrdinal( layer );
5882
5883 if( !haveLayerRange )
5884 {
5885 minOrd = ord;
5886 maxOrd = ord;
5887 haveLayerRange = true;
5888 }
5889 else
5890 {
5891 minOrd = std::min( minOrd, ord );
5892 maxOrd = std::max( maxOrd, ord );
5893 }
5894 };
5895
5896 accumulateDipLayer( node.layer );
5897
5898 if( nodeIdx > 0 )
5899 accumulateDipLayer( chain.nodes[nodeIdx - 1].layer );
5900
5901 if( nodeIdx + 1 < chain.nodes.size() )
5902 accumulateDipLayer( chain.nodes[nodeIdx + 1].layer );
5903
5904 if( posIt != layersByNetPos.end() )
5905 {
5906 for( int dipLayer : posIt->second )
5907 accumulateDipLayer( dipLayer );
5908 }
5909
5910 PCB_LAYER_ID viaLayerA = F_Cu;
5911 PCB_LAYER_ID viaLayerB = B_Cu;
5912
5913 if( styleViaLayerA != UNDEFINED_LAYER && styleViaLayerB != UNDEFINED_LAYER )
5914 {
5915 viaLayerA = styleViaLayerA;
5916 viaLayerB = styleViaLayerB;
5917 }
5918 else if( haveLayerRange && minOrd < maxOrd )
5919 {
5920 PCB_LAYER_ID low = ordinalToLayer( minOrd );
5921 PCB_LAYER_ID high = ordinalToLayer( maxOrd );
5922
5923 if( low != UNDEFINED_LAYER && high != UNDEFINED_LAYER && low != high )
5924 {
5925 viaLayerA = low;
5926 viaLayerB = high;
5927 }
5928 }
5929
5930 via->SetLayerPair( viaLayerA, viaLayerB );
5931
5932 if( viaLayerA == F_Cu && viaLayerB == B_Cu )
5933 via->SetViaType( VIATYPE::THROUGH );
5934 else if( viaLayerA == F_Cu || viaLayerB == B_Cu )
5935 via->SetViaType( VIATYPE::BLIND );
5936 else
5937 via->SetViaType( VIATYPE::BURIED );
5938
5939 if( net )
5940 via->SetNet( net );
5941
5942 m_board->Add( via, ADD_MODE::APPEND );
5943 createdVias.insert( key );
5944 viaCount++;
5945 }
5946 }
5947
5948 wxLogTrace( traceDiptraceIo,
5949 wxT( "DipTrace: created %d tracks and %d vias (%d chains with unresolved nets, "
5950 "%d non-copper tracks skipped, %d non-copper vias skipped, "
5951 "%d style vias, %d inferred layer-change vias, %d duplicate vias skipped, "
5952 "%d cross-net vias skipped)" ),
5953 trackCount, viaCount, missingChainNets, nonCopperTrackSkips, nonCopperViaSkips, explicitViaStyleVias,
5954 inferredLayerChangeVias, duplicateViaSkips, crossNetViaSkips );
5955}
5956
5957
5959{
5960 std::unordered_map<int, int> zoneSpokeModeByDipNet;
5961 std::unordered_set<int> zoneSpokeModeConflicts;
5962
5963 for( const DT_ZONE& dtZone : m_zones )
5964 {
5965 if( dtZone.outline.size() < 3 )
5966 continue;
5967
5968 ZONE* zone = new ZONE( m_board );
5969
5970 PCB_LAYER_ID zoneLayer = MapCopperLayer( dtZone.layer );
5971
5972 if( zoneLayer == UNDEFINED_LAYER )
5973 zoneLayer = MapLayer( dtZone.layer );
5974
5975 if( zoneLayer == UNDEFINED_LAYER )
5976 zoneLayer = F_Cu;
5977
5978 zone->SetLayer( zoneLayer );
5979 zone->SetAssignedPriority( dtZone.priority );
5980
5981 if( dtZone.clearance > 0 )
5982 zone->SetLocalClearance( ToKiCadCoord( dtZone.clearance ) );
5983
5984 if( dtZone.minWidth > 0 )
5985 {
5986 zone->SetMinThickness( std::max( ToKiCadCoord( dtZone.minWidth ),
5987 static_cast<int>( ZONE_THICKNESS_MIN_VALUE_MM
5988 * pcbIUScale.IU_PER_MM ) ) );
5989 }
5990
5991 if( dtZone.spokeWidth > 0 )
5992 zone->SetThermalReliefSpokeWidth( ToKiCadCoord( dtZone.spokeWidth ) );
5993
5994 if( dtZone.spokeMode == 0 )
5995 {
5997 }
5998 else if( dtZone.spokeMode > 0 && dtZone.smdSpokeMode == 0 )
5999 {
6000 // DipTrace encodes independent spoke modes for THT and SMD objects.
6001 // When THT uses thermal spokes but SMD is direct, KiCad's closest
6002 // representation is THT thermal (SMD solid).
6004 }
6005 else if( dtZone.spokeMode > 0 )
6006 {
6008 }
6009
6010 // DipTrace exposes three independent island-removal toggles:
6011 // - Minimum Area (IslandRegion)
6012 // - Internal (IslandInternal)
6013 // - Unconnected (IslandConnection)
6014 // KiCad has coarser zone-level modes (ALWAYS/NEVER/AREA), so map to
6015 // the closest deterministic representation.
6016 if( dtZone.islandInternal || dtZone.islandConnection )
6017 {
6019 }
6020 else if( dtZone.islandRegion )
6021 {
6023
6024 if( dtZone.minimumArea > 0 )
6025 {
6026 long long minIslandLinearIU = static_cast<long long>( ToKiCadCoord( dtZone.minimumArea ) );
6027 zone->SetMinIslandArea( minIslandLinearIU * minIslandLinearIU );
6028 }
6029 }
6030 else
6031 {
6033 }
6034
6035 if( NETINFO_ITEM* netinfo = ResolveNetByIndex( dtZone.netIndex ) )
6036 zone->SetNet( netinfo );
6037
6038 SHAPE_POLY_SET outline;
6039 outline.NewOutline();
6040
6041 for( const auto& [x, y] : dtZone.outline )
6042 outline.Append( ToKiCadCoord( x ), ToKiCadCoord( y ) );
6043
6044 zone->AddPolygon( outline.COutline( 0 ) );
6045
6048
6049 m_board->Add( zone, ADD_MODE::APPEND );
6050
6051 if( dtZone.netIndex >= 0 && dtZone.spokeMode >= 0 )
6052 {
6053 auto [it, inserted] = zoneSpokeModeByDipNet.emplace( dtZone.netIndex, dtZone.spokeMode );
6054
6055 if( !inserted && it->second != dtZone.spokeMode )
6056 zoneSpokeModeConflicts.insert( dtZone.netIndex );
6057 }
6058 }
6059
6060 // KiCad has no zone-level thermal spoke angle; apply the parsed DipTrace
6061 // spoke mode to through-hole pads by net when the mode is unambiguous.
6062 std::unordered_map<int, int> spokeModeByNetCode;
6063
6064 for( const auto& [dipNetIdx, spokeMode] : zoneSpokeModeByDipNet )
6065 {
6066 if( zoneSpokeModeConflicts.count( dipNetIdx ) )
6067 continue;
6068
6069 auto netIt = m_kicadNetByDipTraceIndex.find( dipNetIdx );
6070
6071 if( netIt == m_kicadNetByDipTraceIndex.end() || !netIt->second )
6072 continue;
6073
6074 spokeModeByNetCode[netIt->second->GetNetCode()] = spokeMode;
6075 }
6076
6077 int adjustedPadThermalAngles = 0;
6078
6079 for( FOOTPRINT* fp : m_board->Footprints() )
6080 {
6081 for( PAD* pad : fp->Pads() )
6082 {
6083 if( pad->GetAttribute() == PAD_ATTRIB::SMD )
6084 continue;
6085
6086 auto modeIt = spokeModeByNetCode.find( pad->GetNetCode() );
6087
6088 if( modeIt == spokeModeByNetCode.end() )
6089 continue;
6090
6091 int spokeMode = modeIt->second;
6092
6093 if( spokeMode == 1 || spokeMode == 4 )
6094 {
6095 pad->SetThermalSpokeAngle( ANGLE_90 );
6096 adjustedPadThermalAngles++;
6097 }
6098 else if( spokeMode == 2 || spokeMode == 3 )
6099 {
6100 pad->SetThermalSpokeAngle( ANGLE_45 );
6101 adjustedPadThermalAngles++;
6102 }
6103 }
6104 }
6105
6106 if( adjustedPadThermalAngles > 0 )
6107 {
6108 wxLogTrace( traceDiptraceIo, wxT( "DipTrace: applied thermal spoke angle overrides to %d pads" ),
6109 adjustedPadThermalAngles );
6110 }
6111}
6112
6113
6115{
6116 // DipTrace negative/solid planes are described at the layer level (CopperLayers <Lay
6117 // Type="Plane" NetId=..>) rather than as stored CopperPour records, so they never appear in
6118 // m_zones. Synthesize a board-outline-bounded ZONE on each plane layer, tied to the plane net.
6119 // Build the bounding polygon once: prefer the real outline, else fall back to the board bbox
6120 // rectangle (mirroring CreateBoardOutline so plane fills are not lost on bbox-only boards).
6121 SHAPE_POLY_SET planeOutline;
6122 planeOutline.NewOutline();
6123
6124 if( m_outline.size() >= 3 )
6125 {
6126 for( const DT_VERTEX& v : m_outline )
6127 planeOutline.Append( ToKiCadCoord( v.x ), ToKiCadCoord( v.y ) );
6128 }
6129 else if( m_bboxXMin != 0 || m_bboxXMax != 0 )
6130 {
6131 int x1 = ToKiCadCoord( m_bboxXMin );
6132 int y1 = ToKiCadCoord( m_bboxYMin );
6133 int x2 = ToKiCadCoord( m_bboxXMax );
6134 int y2 = ToKiCadCoord( m_bboxYMax );
6135
6136 planeOutline.Append( x1, y1 );
6137 planeOutline.Append( x2, y1 );
6138 planeOutline.Append( x2, y2 );
6139 planeOutline.Append( x1, y2 );
6140 }
6141
6142 if( planeOutline.OutlineCount() == 0 || planeOutline.COutline( 0 ).PointCount() < 3 )
6143 return;
6144
6145 for( const DT_LAYER& layer : m_layers )
6146 {
6147 if( layer.type != 1 || layer.planeNetIndex < 0 )
6148 continue;
6149
6150 PCB_LAYER_ID kiLayer = MapCopperLayer( layer.index );
6151
6152 if( kiLayer == UNDEFINED_LAYER )
6153 continue;
6154
6155 NETINFO_ITEM* netinfo = ResolveNetByIndex( layer.planeNetIndex );
6156
6157 if( !netinfo )
6158 {
6159 wxLogTrace( traceDiptraceIo,
6160 wxT( "DipTrace: plane layer %d references unresolved net index %d; skipping" ),
6161 layer.index, layer.planeNetIndex );
6162 continue;
6163 }
6164
6165 SHAPE_POLY_SET outline = planeOutline;
6166
6167 ZONE* zone = new ZONE( m_board );
6168 zone->SetLayer( kiLayer );
6169 zone->SetNet( netinfo );
6170 zone->SetAssignedPriority( 0 );
6172
6173 // Mark this as a synthesized plane fill (not a stored CopperPour) so consumers can tell
6174 // it apart from explicit pours.
6175 zone->SetZoneName( wxT( "DipTrace Plane" ) );
6176
6177 zone->AddPolygon( outline.COutline( 0 ) );
6180
6181 m_board->Add( zone, ADD_MODE::APPEND );
6182
6183 wxLogTrace( traceDiptraceIo,
6184 wxT( "DipTrace: synthesized plane zone on layer %d net '%s' (dt net %d)" ),
6185 layer.index, netinfo->GetNetname(), layer.planeNetIndex );
6186 }
6187}
6188
6189
6191{
6192 // DipTrace's per-zone board-edge clearance is board-constant in practice;
6193 // collapse it to the largest value seen so a single edge_clearance rule covers
6194 // every pour.
6195 int edgeClearanceIU = 0;
6196
6197 // ViaDirect connects vias to the pour solidly instead of through thermal
6198 // spokes. KiCad has no per-zone via setting, so express it per net.
6199 std::map<wxString, bool> viaDirectNets;
6200
6201 for( const DT_ZONE& zone : m_zones )
6202 {
6203 if( zone.boardClearance > 0 )
6204 edgeClearanceIU = std::max( edgeClearanceIU, ToKiCadCoord( zone.boardClearance ) );
6205
6206 if( zone.viaDirect && zone.netIndex >= 0 )
6207 {
6208 if( NETINFO_ITEM* net = ResolveNetByIndex( zone.netIndex ) )
6209 {
6210 if( !net->GetNetname().IsEmpty() )
6211 viaDirectNets[net->GetNetname()] = true;
6212 }
6213 }
6214 }
6215
6216 if( edgeClearanceIU <= 0 && viaDirectNets.empty() )
6217 return wxEmptyString;
6218
6219 wxString rules = wxT( "(version 1)\n" );
6220
6221 if( edgeClearanceIU > 0 )
6222 {
6223 wxString mm = wxString::FromUTF8( FormatDouble2Str( pcbIUScale.IUTomm( edgeClearanceIU ) ) );
6224
6225 rules += wxString::Format( wxT( "\n(rule \"DipTrace zone board clearance\"\n" )
6226 wxT( " (condition \"A.Type == 'Zone'\")\n" )
6227 wxT( " (constraint edge_clearance (min %smm)))\n" ),
6228 mm );
6229 }
6230
6231 for( const auto& [netName, direct] : viaDirectNets )
6232 {
6233 // Net names may contain the single quotes that delimit the NetName literal.
6234 wxString escaped = netName;
6235 escaped.Replace( wxT( "'" ), wxT( "\\'" ) );
6236
6237 rules += wxString::Format( wxT( "\n(rule \"DipTrace via direct %s\"\n" )
6238 wxT( " (condition \"A.Type == 'Via' && A.NetName == '%s'\")\n" )
6239 wxT( " (constraint zone_connection solid))\n" ),
6240 netName, escaped );
6241 }
6242
6243 return rules;
6244}
const char * name
bool operator==(const wxAuiPaneInfo &aLhs, const wxAuiPaneInfo &aRhs)
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
BASE_SET & set(size_t pos)
Definition base_set.h:116
virtual void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
BOARD_STACKUP & GetStackupDescriptor()
Manage layers needed to make a physical board.
void RemoveAll()
Delete all items in list and clear the list.
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
Low-level binary reader for DipTrace file formats.
void ReadColor(uint8_t &r, uint8_t &g, uint8_t &b)
Read a 3-byte RGB color value.
PCB_PARSER(const wxString &aFileName, BOARD *aBoard)
Construct a parser for the given file.
void ParseTextRecords(int aCount)
std::vector< DT_TEXT_OBJECT > m_textObjects
static double ToKiCadAngleDeg(int aDipTraceAngle)
Convert a DipTrace angle value to degrees (tenths of degree).
std::unordered_map< int, NETINFO_ITEM * > m_kicadNetByDipTraceIndex
PCB_LAYER_ID MapCopperLayer(int aDipTraceLayer) const
std::vector< DT_VIA_STYLE > m_viaStyles
static int ToKiCadCoord(int aDipTraceCoord)
Convert a DipTrace coordinate (DipTrace units) to KiCad internal units (nm).
static std::vector< size_t > FindAllBoundaries(const uint8_t *aData, size_t aDataSize, const uint8_t *aPattern, size_t aPatternLen, size_t aStart, size_t aEnd)
Find all occurrences of a byte pattern within data[start:end].
void ParsePatternStyleGroups(int aGroupCount)
std::vector< DT_COMPONENT > m_components
void ParseComponentTail(DT_COMPONENT &aComp, size_t aRegionEnd)
Parse the 37-byte tail at the end of a component region to extract text positioning.
void CreateFootprint(const DT_COMPONENT &aComp)
void FindPadsInRegion(DT_COMPONENT &aComp, size_t aRegionStart, size_t aRegionEnd)
Search a component's data region for pad records using pad name anchors.
std::vector< DT_DESIGN_RULE > m_designRules
std::vector< std::pair< size_t, size_t > > FieldWalkComponentBoundaries(size_t aUpperBound)
Deterministically walk the component boundaries from the design-rules end, anchoring on each componen...
std::vector< DT_LAYER > m_layers
void ParsePatternNameGroups(int aGroupCount)
void ParseNetRouting(DT_NET &aNet)
void FindShapesInChainedBlocks(DT_COMPONENT &aComp, size_t aRegionStart, size_t aRegionEnd)
Parse shapes from chained fixed-size records used by some v46+ footprints.
static bool TryReadStringAt(const uint8_t *aData, size_t aDataSize, size_t aPos, int aVersion, wxString &aOut, size_t &aNewPos)
Try to read a string at a given raw data position.
std::vector< DT_NET > m_nets
std::unordered_map< int, std::vector< VECTOR2I > > m_routingAnchorsByNet
void SkipInterRulesetTransition()
Read an inter-ruleset transition block.
NETINFO_ITEM * ResolveNetByIndex(int aDipTraceNetIndex) const
Resolve a DipTrace net index to the corresponding KiCad net object.
std::vector< DT_VERTEX > m_outline
void CreatePlaneZones()
Synthesize board-outline-bounded plane fills for negative/solid-plane copper layers.
bool ParseSingleComponent(size_t aBoundaryOffset, size_t aUpperBound, DT_COMPONENT &aComp)
void FindMountHolesInRegion(DT_COMPONENT &aComp, size_t aRegionStart, size_t aRegionEnd)
Parse component-local mechanical holes (NPTH) from the post-pad region.
void CreateTextObject(const DT_TEXT_OBJECT &aText)
void ApplyPlacementAngles()
Refine component placement angles with the exact values from the placement section.
wxString GenerateDesignRules() const
Build a KiCad custom design-rule (.kicad_dru) document for the per-zone DipTrace properties that have...
static bool ClassifyStandaloneVia(const DT_COMPONENT &aComp)
Decide whether a parsed component is a standalone via rather than a placed footprint.
std::unordered_map< int, const DT_NET * > m_dipTraceNetByIndex
const DT_NET * ResolveDipTraceNetByIndex(int aDipTraceNetIndex) const
void FindAndParseNets(size_t aSearchStart, size_t aSearchEnd)
void FindAndParseTextObjects(size_t aSearchStart, size_t aSearchEnd)
void Parse()
Parse the file and populate the board. Throws IO_ERROR on failure.
std::unordered_map< int, int > m_copperLayerOrdinalById
PCB_LAYER_ID MapLayer(int aDipTraceLayer) const
Map a DipTrace layer index to a KiCad PCB_LAYER_ID.
void FindShapesInRegion(DT_COMPONENT &aComp, size_t aRegionStart, size_t aRegionEnd)
Parse footprint outline shapes from the region after pad data.
void FindAndParseZones(size_t aSearchStart, size_t aSearchEnd)
std::vector< DT_ZONE > m_zones
void FindShapesInFontBlocks(DT_COMPONENT &aComp, size_t aRegionStart, size_t aRegionEnd)
Parse shapes from per-layer font blocks (v46+ format).
std::vector< DT_TRACK_CHAIN > m_trackChains
void SetCenter(const VECTOR2I &aCenter)
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:152
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
void SetPosition(const VECTOR2I &aPos) override
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:442
void SetOrientation(const EDA_ANGLE &aNewAngle)
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:877
void SetReference(const wxString &aReference)
Definition footprint.h:847
void SetValue(const wxString &aValue)
Definition footprint.h:868
PCB_FIELD & Reference()
Definition footprint.h:878
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection) override
Flip this object, i.e.
VECTOR2I GetPosition() const override
Definition footprint.h:403
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int SetLibItemName(const UTF8 &aLibItemName)
Override the library item name portion of the LIB_ID to aLibItemName.
Definition lib_id.cpp:107
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
Handle the data for a net.
Definition netinfo.h:46
const wxString & GetNetname() const
Definition netinfo.h:100
int GetNetCode() const
Definition netinfo.h:94
std::shared_ptr< NETCLASS > GetDefaultNetclass() const
Gets the default netclass for the project.
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:61
void SetAttribute(PAD_ATTRIB aAttribute)
Definition pad.cpp:1615
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:579
void SetShape(PCB_LAYER_ID aLayer, PAD_SHAPE aShape)
Set the new shape of this pad.
Definition pad.h:193
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition pad.cpp:600
void SetNumber(const wxString &aNumber)
Set the pad number (note that it can be alphanumeric, such as the array reference "AA12").
Definition pad.h:142
void SetDrillShape(PAD_DRILL_SHAPE aShape)
Definition pad.h:428
void SetPosition(const VECTOR2I &aPos) override
Definition pad.cpp:234
void SetDrillSize(const VECTOR2I &aSize)
Definition pad.h:314
void SetSize(PCB_LAYER_ID aLayer, const VECTOR2I &aSize)
Definition pad.cpp:254
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:586
void SetLayerSet(const LSET &aLayers) override
Definition pad.cpp:1931
void SetWidth(int aWidth) override
void SetShape(SHAPE_T aShape) override
Definition pcb_shape.h:200
void SetEnd(const VECTOR2I &aEnd) override
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
void SetStroke(const STROKE_PARAMS &aStroke) override
virtual void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:95
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:89
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:92
virtual void SetWidth(int aWidth)
Definition pcb_track.h:86
int PointCount() const
Return the number of points (vertices) in this line chain.
Represent a set of closed polygons.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
Simple container to manage line stroke parameters.
Handle a list of polygons defining a copper zone.
Definition zone.h:70
void SetLocalClearance(std::optional< int > aClearance)
Definition zone.h:183
void AddPolygon(std::vector< VECTOR2I > &aPolygon)
Add a polygon to the zone outline.
Definition zone.cpp:1350
void SetBorderDisplayStyle(ZONE_BORDER_DISPLAY_STYLE aBorderHatchStyle, int aBorderHatchPitch, bool aRebuilBorderdHatch)
Set all hatch parameters for the zone.
Definition zone.cpp:1458
void SetMinThickness(int aMinThickness)
Definition zone.h:316
void SetThermalReliefSpokeWidth(int aThermalReliefSpokeWidth)
Definition zone.h:251
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:599
void SetNet(NETINFO_ITEM *aNetInfo) override
Override that drops aNetInfo when this zone is in copper-thieving fill mode.
Definition zone.cpp:590
void SetAssignedPriority(unsigned aPriority)
Definition zone.h:117
void SetPadConnection(ZONE_CONNECTION aPadConnection)
Definition zone.h:313
void SetZoneName(const wxString &aName)
Definition zone.h:161
void SetIslandRemovalMode(ISLAND_REMOVAL_MODE aRemove)
Definition zone.h:834
void SetMinIslandArea(long long int aArea)
Definition zone.h:837
static int GetDefaultHatchPitch()
Definition zone.cpp:1535
static int ReadInt3At(const uint8_t *aData, size_t aPos)
static constexpr size_t FONT_PREAMBLE_FIXED_SIZE
static constexpr size_t PAD_PRE_HEADER_SIZE
Pad record layout constants.
static constexpr size_t TRACK_NODE_SIZE
static constexpr int DT_FP_LAYER_TOP_MASK
static constexpr int DT_FP_LAYER_TOP_COURTYARD
static constexpr size_t FONT_PREAMBLE_LABEL_OFFSET
v46+ font-shape preamble (between the pad region end and the first font block).
static bool IsAngleLikeCode(int aValue)
static const uint8_t BOUNDARY_ALT[]
Alternate boundary pattern: int3(0) int3(0) int3(0) int4(0) – v41 nameplate files.
static void DumpComponentRawFields(const DT_COMPONENT &aComp, const uint8_t *aData, size_t aPosXPos, size_t aPosYPos, size_t aRotPos, size_t aFieldCPos, size_t aFieldDPos)
static void DumpZoneTail(int aZoneIndex, size_t aTailStart, size_t aSearchEnd, const uint8_t *aData)
static bool decodeMountHoleBlockAt(const uint8_t *aData, size_t aBlockStart, size_t aSearchEnd, std::vector< DT_MOUNT_HOLE > &aHoles)
static constexpr int FP_CHAIN_SHAPE_X1_OFFSET
static const uint8_t NET_SENTINEL[]
Net record sentinel: int3(0) int3(-1) int3(-1) This 9-byte pattern appears immediately before each ne...
static constexpr int FP_CHAIN_SHAPE_X3_OFFSET
static constexpr int FP_CHAIN_TYPE_LINE
static bool ShouldDumpComponentHeader(const wxString &aRefdes)
static void DumpPadPostBlock(const DT_COMPONENT &aComp, const DT_PAD &aPad, const uint8_t *aData, size_t aPostDimPos, size_t aPostDimSize)
static constexpr size_t NOT_FOUND
Sentinel for "not found" in pattern searches.
static constexpr int DT_FP_LAYER_TOP_PASTE
static constexpr int FP_CHAIN_SHAPE_Y2_OFFSET
static void DumpRulesetBlock(int aRuleSetIndex, const wxString &aRuleSetName, int aBlockIndex, const std::array< int, 26 > &aValues)
static bool ShouldDumpZones()
static constexpr int DT_FP_LAYER_TOP_KEEPOUT
static const uint8_t CHAIN_HEADER[]
Track chain header pattern: 3 zero bytes + int3(-1)
static const uint8_t BOARD_SETTINGS_FONT_MARKER[]
Board settings font marker: int3(4), int3(4), int3(0)
static constexpr int FP_CHAIN_SHAPE_Y1_OFFSET
static constexpr size_t COMPONENT_TAIL_SIZE
Size of the fixed-layout component tail found at the end of every component region.
static size_t StringFieldSize(const uint8_t *aData, size_t aDataSize, size_t aPos, int aVersion)
Compute the total byte length of a DipTrace string field at a given offset without allocating a wxStr...
static constexpr size_t PAD_POLYGON_VERTEX_SIZE
static constexpr int FP_CHAIN_SHAPE_DATA_OFFSET
static constexpr size_t PAD_POST_DIM_HEADER
static constexpr int FP_CHAIN_SHAPE_TYPE_OFFSET
static constexpr size_t TAHOMA_FONT_PATTERN_LEN
static constexpr int FP_SHAPE_RECORD_SIZE_V45
static bool EnvFlagEnabled(const char *aVarName)
static void DumpComponentHeader(const DT_COMPONENT &aComp, int aFieldA, int aFieldB, int aFieldC, int aFieldD, int aFieldE, int aFieldF, uint8_t aSep1, uint8_t aSep2, uint8_t aSep3)
static const uint8_t COMPONENT_TAIL_PATTERN[]
The component tail starts with int3(0) + int4(0) + int4(0) = 11 constant bytes.
static bool ShouldDumpPadPostBlock(const wxString &aRefdes)
static constexpr int DT_FP_LAYER_TOP_OUTLINE
static constexpr size_t PAD_POST_DIM_FIXED_SIZE
static constexpr size_t COMPONENT_TAIL_PATTERN_LEN
static constexpr size_t NET_SENTINEL_LEN
static int ReadInt4At(const uint8_t *aData, size_t aPos)
static constexpr int FP_SHAPE_COUNT_OFFSET
Shape count (int3) is at this offset past the end of the pad region.
static constexpr size_t BOUNDARY_CORE_LEN
static constexpr size_t MOUNT_HOLE_TERM_SIZE
Hole block terminator bytes: 0x00 0x00.
static constexpr size_t CHAIN_HEADER_LEN
static const uint8_t BOUNDARY_STD[]
Component boundary pattern: int3(0) int3(-1) int3(-1) int4(0)
static constexpr size_t PAD_DIMENSIONS_SIZE
static constexpr size_t FONT_BLOCK_TRAILER_SIZE
static void DumpZoneGap(int aZoneIndex, size_t aGapStart, size_t aGapEnd, const uint8_t *aData)
static constexpr size_t PAD_HEADER_PREAMBLE_UTF16
Fixed byte preamble between the component header strings and the first pad record.
static void DumpComponentBinaryScan(const DT_COMPONENT &aComp, const uint8_t *aData, size_t aDataSize)
static constexpr int FP_CHAIN_SHAPE_COUNT_OFFSET
v46+ chained-shape block layout used by some footprints (e.g. TO-92 in PCB_6).
static bool ShouldDumpFootprintOrientation(const wxString &aRefdes)
static constexpr int FP_SHAPE_RECORD_SIZE_V37
static bool ShouldDumpNets()
static constexpr int FP_CHAIN_SHAPE_Y3_OFFSET
static constexpr size_t PAD_HEADER_PREAMBLE_ASCII
static constexpr int FONT_BLOCK_SHAPE_VERSION
First version that stores per-component shape data in font (Tahoma) blocks rather than contiguous fix...
static constexpr size_t MOUNT_HOLE_HEADER_SIZE
Hole block header: int3(hole_count + 2) + byte(flag) + 4 * int4(0)
static constexpr int FP_SHAPE_DATA_OFFSET
Shape record data starts at this offset past the end of the pad region.
static int32_t ReadRawLE32(const uint8_t *aData, size_t aPos)
static constexpr int FP_SHAPE_NORM_RANGE
Normalized coordinate range used by DipTrace shape records.
static constexpr int FP_CHAIN_SHAPE_WIDTH_OFFSET
static constexpr size_t FONT_BLOCK_HEADER_SIZE
Size of the fixed metadata header following the font string in each font block.
static void DumpComponentTail(const DT_COMPONENT &aComp, const uint8_t *aData, size_t aTailStart, int aVisibility, uint8_t aSideFlag1, uint8_t aSideFlag2, int aOrderIdx, int aRefdesYOffset, int aValueYOffset, uint8_t aHasOffset, uint8_t aTailTerm)
static constexpr size_t MOUNT_HOLE_TRAILER_SIZE
Zero trailer following the hole block in observed v54 files.
static const uint8_t TAHOMA_FONT_PATTERN[]
UTF-16BE pattern for the string "Tahoma" as stored in v46+ component font blocks.
static constexpr int FP_CHAIN_TYPE_ARC
static void DumpZoneHeader(int aZoneIndex, size_t aHeaderPos, const uint8_t *aData, int aFieldA, int aFlags1, int aFlags2, int aFlags3, int aMinWidth, int aClearance, int aMinimumArea, int aSeparator, int aLayer, int aFieldB, int aVtxCount, const wxString &aNetName)
static uint32_t ReadColorPacked(BINARY_READER &aReader)
static void DumpPadGap(const DT_COMPONENT &aComp, const uint8_t *aData, size_t aGapStart, size_t aGapEnd)
static constexpr int PAD_MAX_NET_INDEX
Upper bound for a plausible pad net index, used as a chain-walk desync guard.
static constexpr size_t PAD_POST_DIM_TAIL
static constexpr int FP_CHAIN_SHAPE_RECORD_SIZE
static constexpr int DT_FP_LAYER_TOP_ASSY
static float ReadRawLEFloat32(const uint8_t *aData, size_t aPos)
static constexpr size_t MOUNT_HOLE_RECORD_SIZE
Per-hole record: byte + byte + int4(x) + int4(y) + int4(outer_diam) + int4(drill_diam)
static constexpr int FP_SHAPE_DEFAULT_WIDTH
Sentinel value for "use default line width" in shape width field.
static const uint8_t TEXT_SECTION_ZEROS[]
Text section zeros: 3x int3(0)
static wxString BytesToHex(const uint8_t *aData, size_t aLen)
static constexpr int FP_CHAIN_SHAPE_X2_OFFSET
static bool FindComponentRotation(const uint8_t *aData, size_t aDataSize, size_t aBoundaryOffset, int &aQuarterTurns)
Recover a placed component's rotation, expressed in 90-degree quarter turns.
static constexpr int DT_FP_LAYER_TOP_SILK
DipTrace footprint-graphic layer enum (int3 stored 5 bytes ahead of each shape's font block).
static constexpr int FP_CHAIN_MOUNT_HOLE_OFFSET
static constexpr size_t FONT_BLOCK_FIXED_SIZE
Fixed framing of each v46+ font-shape block, excluding its variable parts: Tahoma(14) + meta(25) + in...
static bool ShouldDumpPadGap(const wxString &aRefdes)
static constexpr int ZONE_FONT_PREAMBLE_TAIL
Zone section preamble constant: int4(-20000), the last field of the font block.
Parser for DipTrace binary .dip board files.
static int ReadInt4At(const uint8_t *aData, size_t aPos)
Decode a 4-byte big-endian biased integer from raw data at a given offset.
#define _(s)
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:412
@ SEGMENT
Definition eda_shape.h:46
const wxChar *const traceDiptraceIo
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:675
size_t CopperLayerToOrdinal(PCB_LAYER_ID aLayer)
Converts KiCad copper layer enum to an ordinal between the front and back layers.
Definition layer_ids.h:911
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ Edge_Cuts
Definition layer_ids.h:108
@ Dwgs_User
Definition layer_ids.h:103
@ F_Paste
Definition layer_ids.h:100
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ B_Paste
Definition layer_ids.h:101
@ F_Fab
Definition layer_ids.h:115
@ F_SilkS
Definition layer_ids.h:96
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ In1_Cu
Definition layer_ids.h:62
@ B_SilkS
Definition layer_ids.h:97
@ F_Cu
Definition layer_ids.h:60
@ B_Fab
Definition layer_ids.h:114
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:25
constexpr int MAX_STRING_CHARS
Maximum sane string length (in characters) accepted by the reader.
constexpr int INT4_BIAS
Bias value added to stored 4-byte unsigned integers.
constexpr int LEGACY_STRING_VERSION
Format version at or below which strings use the legacy ASCII encoding (int3 byte-count + raw ASCII b...
@ DT_SHAPE_FILLOBROUND
Filled obround marker (e.g. diode cathode / pin-1 dot)
constexpr int INT3_BIAS
Bias value added to stored 3-byte unsigned integers.
constexpr double DIPTRACE_ANGLE_TO_DEG
DipTrace stores angles with 100 000 units per degree.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:99
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ RECTANGLE
Definition padstack.h:54
static wxString makeKey(const wxString &aFirst, const wxString &aSecond)
Assemble a two part key as a simple concatenation of aFirst and aSecond parts, using a separator.
CITER next(CITER it)
Definition ptree.cpp:120
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
static bool addSegment(VRML_LAYER &model, IDF_SEGMENT *seg, int icont, int iseg)
std::string FormatDouble2Str(double aValue)
Print a float number without using scientific notation and no trailing 0 This function is intended in...
int fieldD
Raw header int4; matches Pattern.Float3 in DipXML.
std::vector< DT_FP_SHAPE > shapes
Graphics in normalized coordinates.
int rotation
Raw header int4; matches Pattern.Float1 in DipXML (not placement angle)
int positionX
DipTrace units.
int padHeightHint
Raw bbox companion field (pad height in DipTrace units)
size_t regionEndOffset
Component region end offset (next boundary / upper bound)
bool valueVisible
(Currently same flag as refdesVisible)
bool isStandaloneVia
True for explicit standalone via components.
int fieldF
Raw header int3 field F (component kind discriminator)
int drillWidthHint
Raw bbox companion field (drill width in DipTrace units)
int bboxHeight
Footprint Y extent in DipTrace units (for shape scaling)
int placementQuarterTurns
Board-placement angle snapped to 90-degree turns (metadata Id-6 int3)
size_t boundaryOffset
Boundary marker offset for this component record.
std::vector< uint8_t > flags
int padWidthHint
Raw bbox companion field (pad width in DipTrace units)
size_t stringStartOffset
Parsed start offset of library-path string.
int refdesYOffset
Refdes text Y offset from component origin (DipTrace units)
double placementAngleDeg
Exact board-placement angle in degrees (placement section), when available.
std::vector< DT_MOUNT_HOLE > holes
int bboxWidth
Footprint X extent in DipTrace units (for shape scaling)
int layer
0 = top, 1 = bottom
std::vector< DT_PAD > pads
int positionY
DipTrace units.
int drillHeightHint
Raw bbox companion field (drill height in DipTrace units)
bool hasTailData
True if the 37-byte tail was successfully parsed.
size_t headerEndOffset
Byte offset after parsed component header strings.
int fieldC
Raw header int4; matches Pattern.Float2 in DipXML.
int valueYOffset
Value text Y offset from component origin (DipTrace units)
size_t padRegionEnd
Byte offset after last pad record (for shape finding)
bool refdesVisible
False when text visibility flag is -1.
int fieldA
Raw header int3 field A.
int midY
Arc midpoint Y in normalized units.
int x2
End X in normalized units.
int type
Shape type (DT_SHAPE_TYPE)
int y1
Start Y in normalized units.
int midX
Arc midpoint X in normalized units.
int x1
Start X in normalized units (range ±5000)
int width
Line width in normalized units (-10000 = default)
int layer
DipTrace layer index.
int y2
End Y in normalized units.
int type
Layer type from record field_a (0 = Signal, 1 = Plane)
int planeNetIndex
Plane net DipTrace index from record field_c (-1 = none/Signal)
uint32_t color
0x00RRGGBB
int fieldD
Possibly default trace width.
int outerDiameter
Non-copper/clearance diameter in DipTrace units.
int drillDiameter
Drill diameter in DipTrace units.
int y
Y offset from component origin in DipTrace units.
int x
X offset from component origin in DipTrace units.
wxString name
Stored net name; may be empty in DipTrace files.
int defaultViaDrillDiam
Default via drill from net routing preamble.
int index
Sequential net index from the DipTrace file.
std::vector< DT_PAD_REF > padRefs
Optional net-to-pad links from routing metadata.
int traceWidth
Default trace width in DipTrace units; parsed but not yet used.
int defaultViaOuterDiam
Default via OD from net routing preamble.
int componentIndex
Component index referenced from net routing metadata.
int padIndex
Pad index (1-based within the component)
uint8_t orientClass
Pad orientation class from pad post-block tail byte.
int x
X offset from component origin in DipTrace units.
std::vector< std::pair< int, int > > polygonVertices
Custom polygon vertices relative to pad center (DipTrace units).
int netIndex
Net index from DipTrace file (-1 = unconnected)
int y
Y offset from component origin in DipTrace units.
uint8_t mountType
Explicit mount class from pad post-block (0=through, 1=SMD)
int style
Pad style (0=ellipse, 1=oval, 2=rectangle, 3=polygon)
int drillWidth
Drill width in DipTrace units (0 for SMD)
int height
Copper pad height in DipTrace units.
int drillHeight
Drill height in DipTrace units (0 for SMD)
wxString label
Functional label (e.g. "POS", "GND")
int index
Sequential pad index within the component (1-based)
int width
Copper pad width in DipTrace units.
wxString number
Pad number/name (e.g. "1", "2")
int y
Y coordinate in DipTrace units.
int viaDrillDiam
Via drill diameter in DipTrace units.
int x
X coordinate in DipTrace units.
int viaStyleIdx
Index into ViaStyle table (-1 = none)
int routeMode
Raw routing-point mode int3 at payload +37 (observed: 0/1/3)
uint8_t routeFlag
Raw routing-point flag byte at payload +22 (semantics unresolved)
int viaOuterDiam
Via outer diameter in DipTrace units.
bool hasVia
True if a via exists at this node.
int layer
Copper layer index (0=top, 1=bottom, 14+=inner)
int width
Track width in DipTrace units.
uint8_t arc
0 = straight segment, 1 = arc segment
int x
X coordinate in DipTrace units.
int y
Y coordinate in DipTrace units.
int field2
Raw int4 payload field 2.
int field0
Raw int3 discriminator/index.
int field5
Raw int4 payload field 5.
int field1
Raw int4 payload field 1.
int field3
Raw int4 payload field 3.
int field4
Raw int4 payload field 4.
int spokeWidth
Thermal relief spoke width in DipTrace units.
uint8_t viaDirect
Raw ViaDirect flag from zone trailer.
uint8_t smdSeparate
Raw SMD_Separate flag from zone trailer.
int regionsCounted
Raw trailer int3, likely CopperPour Regions_Counted from Pcb.exe.
int smdSpokeWidth
Raw SMD_SpokeWidth from zone trailer.
uint8_t regionsDone
Raw RegionsDone flag from zone trailer.
int zoneId
Raw per-zone id from zone trailer (matches DipXML CopperPour@Id)
int minimumArea
Minimum island area scalar in DipTrace units (DipXML: MinimumArea)
int minWidth
Copper pour line width in DipTrace units (DipXML: LineWidth)
int layer
DipTrace layer (0=top, 1=bottom)
uint8_t connectionMode
Raw zone flag byte at header +5.
std::vector< std::pair< int, int > > outline
Outline vertices (x, y) in DipTrace units.
uint8_t rawFlag2
Raw zone flag byte at header +4 (semantics unknown)
int clearance
Zone clearance in DipTrace units.
uint8_t ratlineMode
Raw ratline mode (0=Automatically, 1=All Ratlines, 2=Do Not Hide)
int lineSpacing
Copper pour line spacing in DipTrace units (DipXML: LineSpacing)
int cachedFillRecordCount
Cached-fill record count when payload is 23-byte aligned.
std::vector< DT_ZONE_CACHED_FILL_RECORD > cachedFillRecords
Raw 23-byte cached fill records.
int cachedFillByteLen
Raw bytes between style block and trailer in inter-zone gap.
int smdSpokeMode
Raw SMD_Spoke enum from zone trailer.
int separator
Raw zone separator int3 at header +18.
uint8_t islandInternal
Raw IslandInternal flag from zone trailer.
int boardClearance
Raw board-clearance field from zone trailer.
int netIndex
Net index (-1 = unconnected)
uint8_t islandConnection
Raw IslandConnection flag from zone trailer.
uint8_t islandRegion
Raw IslandRegion flag from zone trailer.
int spokeMode
Raw spoke enum from post-fill style block (0=Direct, 3=4 spoke 45, 4=4 spoke)
uint8_t fillMode
Raw zone flag byte at header +3.
KIBIS_COMPONENT * comp
VECTOR3I v1(5, 5, 5)
VECTOR2I center
const SHAPE_LINE_CHAIN chain
int radius
VECTOR2I end
int clearance
int actual
wxString result
Test unit parsing edge cases and error handling.
VECTOR2I v2(1, 0)
int delta
#define M_PI
wxLogTrace helper definitions.
#define kv
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:90
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
@ THERMAL
Use thermal relief for pads.
Definition zones.h:46
@ THT_THERMAL
Thermal relief only for THT pads.
Definition zones.h:48
@ FULL
pads are covered by copper
Definition zones.h:47
#define ZONE_THICKNESS_MIN_VALUE_MM
Definition zones.h:31