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