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