KiCad PCB EDA Suite
Loading...
Searching...
No Matches
diptrace_sch_parser.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <algorithm>
23#include <cctype>
24#include <cmath>
25#include <cstdlib>
26#include <cstring>
27#include <deque>
28#include <limits>
29#include <memory>
30#include <optional>
31#include <set>
32
33#include <wx/filename.h>
34#include <wx/log.h>
35
36#include <base_units.h>
37#include <lib_id.h>
38#include <lib_symbol.h>
39#include <page_info.h>
40#include <progress_reporter.h>
41#include <project.h>
42#include <reporter.h>
43#include <sch_bus_entry.h>
44#include <sch_junction.h>
45#include <sch_label.h>
46#include <sch_line.h>
47#include <sch_pin.h>
48#include <sch_screen.h>
49#include <sch_shape.h>
50#include <sch_sheet.h>
51#include <sch_sheet_path.h>
52#include <sch_symbol.h>
53#include <schematic.h>
54#include <string_utils.h>
56
57
58using namespace DIPTRACE;
59
60
69static constexpr int V31_CUTOVER = 34;
70
72
73
77static int ReadInt4At( const uint8_t* aData, size_t aPos )
78{
79 uint32_t raw = ( static_cast<uint32_t>( aData[aPos] ) << 24 )
80 | ( static_cast<uint32_t>( aData[aPos + 1] ) << 16 )
81 | ( static_cast<uint32_t>( aData[aPos + 2] ) << 8 )
82 | static_cast<uint32_t>( aData[aPos + 3] );
83 return static_cast<int>( static_cast<int64_t>( raw ) - INT4_BIAS );
84}
85
86
87SCH_PARSER::SCH_PARSER( const wxString& aFileName, SCHEMATIC* aSchematic, SCH_SHEET* aRootSheet,
88 PROGRESS_REPORTER* aProgressReporter, REPORTER* aReporter ) :
89 m_reader( aFileName ),
90 m_schematic( aSchematic ),
91 m_rootSheet( aRootSheet ),
92 m_progressReporter( aProgressReporter ),
93 m_reporter( aReporter ),
94 m_version( 38 ),
95 m_magicMajor( 0 ),
96 m_componentCount( -1 ),
97 m_fileName( aFileName ),
98 m_numSheets( 0 ),
100 m_tailOffset( 0 )
101{
102}
103
104
108
109
110int SCH_PARSER::toKiCadCoordX( int aDipTraceCoord )
111{
112 return static_cast<int>( static_cast<int64_t>( aDipTraceCoord ) / 3 );
113}
114
115
116int SCH_PARSER::toKiCadCoordY( int aDipTraceCoord )
117{
118 // DipTrace .dch stores schematic Y already in a screen-down convention (more negative is higher
119 // on the page), matching KiCad's Y-down axis, and bakes any placement rotation into the stored
120 // pin, shape and wire coordinates. So the conversion is a plain scale with no axis flip; negating
121 // here vertically mirrors the whole sheet and was the cause of the inverted import.
122 return static_cast<int>( static_cast<int64_t>( aDipTraceCoord ) / 3 );
123}
124
125
126int SCH_PARSER::toKiCadSize( int aDipTraceCoord )
127{
128 return static_cast<int>( static_cast<int64_t>( std::abs( aDipTraceCoord ) ) / 3 );
129}
130
131
133{
134 // DipTrace stores the page as width, height and four margins, each an int4 holding mm * 30000.
135 // The record is not framed by a marker, so accept the first run that decodes as a sane page
136 // (sensible mm dimensions, all margins whole mm within the sheet, a positive left margin).
137 const uint8_t* data = m_reader.GetData();
138 size_t fileSize = m_reader.GetFileSize();
139
140 auto rdInt4 = [&]( size_t o ) -> int
141 {
142 uint32_t raw = ( static_cast<uint32_t>( data[o] ) << 24 ) | ( static_cast<uint32_t>( data[o + 1] ) << 16 )
143 | ( static_cast<uint32_t>( data[o + 2] ) << 8 ) | static_cast<uint32_t>( data[o + 3] );
144 return static_cast<int>( static_cast<int64_t>( raw ) - INT4_BIAS );
145 };
146
147 if( fileSize < 24 )
148 return;
149
150 static constexpr int UNITS_PER_MM = 30000;
151
152 for( size_t o = 0; o + 24 <= fileSize; o++ )
153 {
154 int w = rdInt4( o );
155 int h = rdInt4( o + 4 );
156
157 if( w <= 0 || h <= 0 || ( w % UNITS_PER_MM ) != 0 || ( h % UNITS_PER_MM ) != 0 )
158 continue;
159
160 int wmm = w / UNITS_PER_MM;
161 int hmm = h / UNITS_PER_MM;
162
163 if( wmm < 50 || wmm > 2000 || hmm < 50 || hmm > 2000 )
164 continue;
165
166 int margins[4] = { rdInt4( o + 8 ), rdInt4( o + 12 ), rdInt4( o + 16 ), rdInt4( o + 20 ) };
167 bool sane = margins[0] > 0;
168
169 for( int m : margins )
170 {
171 if( m < 0 || ( m % UNITS_PER_MM ) != 0 || m / UNITS_PER_MM > 100 )
172 sane = false;
173 }
174
175 if( !sane )
176 continue;
177
178 m_page.found = true;
179 m_page.widthMM = wmm;
180 m_page.heightMM = hmm;
181
182 // Place the origin-centered DipTrace content on the top-left-origin KiCad page by adding
183 // half the page in KiCad nm. Width/height share the coordinate unit, so reuse toKiCadSize().
184 m_pageOffset = VECTOR2I( toKiCadSize( w / 2 ), toKiCadSize( h / 2 ) );
185 return;
186 }
187}
188
189
191{
192 if( !m_page.found )
193 return aPos;
194
195 return aPos + m_pageOffset;
196}
197
198
200{
201 wxString libName = m_schematic->Project().GetProjectName();
202
203 if( libName.IsEmpty() )
204 {
205 wxFileName fn( m_rootSheet->GetFileName() );
206 libName = fn.GetName();
207 }
208
209 if( libName.IsEmpty() )
210 libName = wxT( "noname" );
211
212 libName += wxT( "-diptrace-import" );
213 libName = LIB_ID::FixIllegalChars( libName, true ).wx_str();
214 return libName;
215}
216
217
219{
220 try
221 {
222 parseHeader();
223 m_reader.SetVersion( m_version );
226
227 // The version threshold is only a heuristic -- DipTrace ships same-version files in both
228 // encodings -- so confirm it against the first sheet-name string, which parseHeader left
229 // the reader positioned on. Detection keeps the version default when inconclusive.
230 if( m_numSheets > 0 )
231 m_reader.DetectStringEncoding( m_reader.GetOffset() );
232
233 // Known formats run roughly 1..60; an out-of-range version (e.g. a misread/garbage header)
234 // would otherwise silently select the modern layout and walk the file with wrong field
235 // offsets. Reject up front rather than misparsing.
237 THROW_IO_ERRORF( _( "Unsupported DipTrace schematic format version %d." ), m_version );
238
243
244 size_t compSectionStart = m_reader.GetOffset();
246 bool hasBusSection = false;
247
248 if( m_magicMajor == 1 )
249 {
251 }
252 else
253 {
254 m_busSectionOffset = findBusSection( compSectionStart );
255 hasBusSection = m_busSectionOffset > 0 && m_busSectionOffset < m_reader.GetFileSize();
256 }
257
258 if( m_magicMajor != 1 && ( m_busSectionOffset == 0 || m_busSectionOffset >= m_reader.GetFileSize() ) )
260
262
263 if( hasBusSection )
264 {
265 m_reader.SetOffset( m_busSectionOffset );
267 }
268
273 }
274 catch( const IO_ERROR& )
275 {
276 throw;
277 }
278 catch( const std::exception& e )
279 {
280 THROW_IO_ERRORF( _( "DipTrace import: unexpected error at offset 0x%06zX: %s" ),
281 m_reader.GetOffset(), wxString::FromUTF8( e.what() ) );
282 }
283
285}
286
287
289{
290 uint8_t magicLen = m_reader.ReadByte();
291
292 if( magicLen != 7 && magicLen != 11 )
293 THROW_IO_ERRORF( _( "Invalid DipTrace schematic magic length: %d (expected 7 or 11)." ), (int) magicLen );
294
295 std::vector<uint8_t> magicBuf( magicLen );
296 m_reader.ReadBytes( magicBuf.data(), magicLen );
297
298 if( memcmp( magicBuf.data(), "DTSCHEM", 7 ) != 0 )
299 THROW_IO_ERROR( _( "Invalid DipTrace schematic file: bad magic header." ) );
300
301 if( magicLen == 7 )
302 {
303 m_magicMajor = 2;
304 m_version = m_reader.ReadInt3();
305 }
306 else
307 {
308 // Legacy files encode the version in the magic suffix ("DTSCHEMx.yy").
309 const uint8_t* suffix = magicBuf.data() + 7;
310
311 if( !std::isdigit( suffix[0] ) || suffix[1] != '.' || !std::isdigit( suffix[2] ) || !std::isdigit( suffix[3] ) )
312 {
313 THROW_IO_ERROR( _( "Invalid DipTrace schematic file: bad legacy version suffix." ) );
314 }
315
316 m_magicMajor = suffix[0] - '0';
317 m_version = ( suffix[2] - '0' ) * 10 + ( suffix[3] - '0' );
318 }
319
320 m_reader.ReadInt4(); // field_0B
321 m_reader.ReadInt3(); // field_0F
322 m_reader.ReadInt3(); // field_12
323 m_reader.ReadInt3(); // field_15
324 m_numSheets = m_reader.ReadInt3();
325
327 THROW_IO_ERRORF( _( "Invalid DipTrace schematic sheet count: %d." ), m_numSheets );
328}
329
330
332{
333 for( int i = 0; i < m_numSheets; i++ )
334 {
335 DCH_SHEET_DEF sheet;
336 sheet.name = m_reader.ReadString();
337 sheet.field_a = m_reader.ReadInt3();
338 m_sheetDefs.push_back( sheet );
339 }
340}
341
342
344{
345 for( int i = 0; i < 5; i++ )
346 m_reader.ReadInt3();
347
348 m_reader.ReadByte();
349 m_reader.ReadInt4();
350 m_reader.ReadInt4();
351
352 if( m_version < V31_CUTOVER )
353 {
354 m_reader.ReadInt3();
355 m_reader.ReadInt3();
356 }
357 else
358 {
359 uint8_t extraHdr[4] = {};
360 m_reader.ReadBytes( extraHdr, 4 );
361
362 uint32_t extraChars = ( static_cast<uint32_t>( extraHdr[0] ) << 24 )
363 | ( static_cast<uint32_t>( extraHdr[1] ) << 16 )
364 | ( static_cast<uint32_t>( extraHdr[2] ) << 8 ) | extraHdr[3];
365
366 // Some modern files store an optional UTF-16 payload (e.g. "url")
367 // after this 4-byte raw length field.
368 if( extraChars > 0 && extraChars < 1000 )
369 m_reader.Skip( static_cast<size_t>( extraChars ) * 2 );
370 }
371
372 m_reader.ReadByte();
373 m_reader.ReadInt4();
374 m_reader.ReadInt4();
375}
376
377
379{
380 // DTSCHEM1.x legacy files do not contain a text-style table at this location.
381 if( m_magicMajor == 1 )
382 return;
383
384 int numStyles = m_reader.ReadInt3();
385
386 if( numStyles < 0 || numStyles > 2000 )
387 THROW_IO_ERRORF( _( "Invalid text style count: %d." ), numStyles );
388
389 for( int i = 0; i < numStyles; i++ )
390 {
391 m_reader.ReadString();
392 m_reader.ReadInt3();
393 m_reader.ReadInt4();
394 m_reader.ReadInt4();
395
396 // v38+ files carry a trailing int3 per text style. In older single-style
397 // files this same byte was historically consumed as the leading "pad" int3
398 // in parsePreComponentSettings(); reading it here is byte-identical for
399 // one style but keeps multi-style v46 files in sync.
401 m_reader.ReadInt3();
402 }
403}
404
405
407{
408 if( m_magicMajor == 1 )
409 {
410 // DTSCHEM1.x: part count is the first int3 and there is no leading
411 // padding int3 before it.
412 m_componentCount = m_reader.ReadInt3();
413 m_reader.ReadByte();
414 m_reader.ReadByte();
415
416 for( int i = 0; i < 5; i++ )
417 m_reader.ReadInt3();
418
419 m_reader.ReadInt3();
420 m_reader.ReadInt3();
421 }
422 else
423 {
424 // For v38+ the per-style trailing int3 consumed in parseTextStyles() is
425 // the same byte that single-style legacy files exposed here as a leading
426 // pad int3, so the component count is the first int3 of this section.
427 // For v37 and below (no per-style trailer) the leading pad is still here.
429 m_reader.ReadInt3();
430
431 m_componentCount = m_reader.ReadInt3();
432 m_reader.ReadByte();
433 m_reader.ReadByte();
434
435 for( int i = 0; i < 5; i++ )
436 m_reader.ReadInt3();
437
438 m_reader.ReadInt3();
439 m_reader.ReadInt3();
440 }
441
443 THROW_IO_ERRORF( _( "DipTrace import: invalid component count %d." ), m_componentCount );
444}
445
446
447size_t SCH_PARSER::findBusSection( size_t aSearchStart ) const
448{
449 const uint8_t* data = m_reader.GetData();
450 size_t fileSize = m_reader.GetFileSize();
451
452 static const uint8_t marker[] = { 0x3B, 0x9A, 0xF1, 0x10, 0x3B, 0x9A, 0xF1, 0x10, 0x00, 0x00 };
453 static constexpr size_t markerLen = sizeof( marker );
454
455 if( aSearchStart + markerLen + 3 >= fileSize )
456 return 0;
457
458 for( size_t off = aSearchStart; off < fileSize - markerLen - 3; off++ )
459 {
460 if( memcmp( data + off, marker, markerLen ) == 0 )
461 {
462 int count = ( ( data[off + 10] << 16 ) | ( data[off + 11] << 8 ) | data[off + 12] ) - INT3_BIAS;
463
464 if( count >= 0 && count <= 1000 )
465 return off;
466
467 if( count > 1000 && count <= 10000 )
468 THROW_IO_ERRORF( _( "DipTrace import: invalid bus count %d." ), count );
469 }
470 }
471
472 return 0;
473}
474
475
477{
478 const uint8_t* data = m_reader.GetData();
479 size_t fileSize = m_reader.GetFileSize();
480
481 if( fileSize < 3 )
482 return fileSize;
483
484 size_t off = fileSize - 3;
485
486 while( off > 0 )
487 {
488 if( data[off] == 0x0F && data[off + 1] == 0x42 && data[off + 2] == 0x40 )
489 off -= 3;
490 else
491 break;
492 }
493
494 size_t tailStart = off + 3;
495 size_t tailCount = ( fileSize - tailStart ) / 3;
496
497 return ( tailCount > 1 ) ? tailStart : fileSize;
498}
499
500
501std::vector<size_t> SCH_PARSER::scanComponentBoundaries( size_t aFirstComp, size_t aBusSectionOffset ) const
502{
503 // Every component record starts with a placement bbox followed by five short header strings.
504 // isComponentHeaderAt() validates that signature using the correct version-specific string
505 // encoding, so walking the section byte-by-byte and accepting each header it recognises yields
506 // the exact set of record starts. A recognised header advances by its fixed 16-byte bbox so the
507 // scan cannot re-match inside it; everything else advances one byte to stay aligned.
508 std::vector<size_t> boundaries;
509
510 size_t off = aFirstComp;
511 size_t end = ( aBusSectionOffset > 20 ) ? aBusSectionOffset - 20 : 0;
512
513 while( off < end )
514 {
515 if( isComponentHeaderAt( off ) )
516 {
517 boundaries.push_back( off );
518 off += 16;
519 }
520 else
521 {
522 off++;
523 }
524 }
525
526 return boundaries;
527}
528
529
530bool SCH_PARSER::isShapeStart( size_t aOffset ) const
531{
532 const uint8_t* data = m_reader.GetData();
533 size_t fileSize = m_reader.GetFileSize();
534
535 if( m_version < V31_CUTOVER )
536 {
537 if( aOffset + 19 > fileSize )
538 return false;
539
540 int z1 = ( ( data[aOffset + 6] << 16 ) | ( data[aOffset + 7] << 8 ) | data[aOffset + 8] ) - INT3_BIAS;
541 int z2 = ( ( data[aOffset + 9] << 16 ) | ( data[aOffset + 10] << 8 ) | data[aOffset + 11] ) - INT3_BIAS;
542
543 if( z1 != 0 || z2 != 0 )
544 return false;
545
546 int w = ReadInt4At( data, aOffset + 12 );
547
548 if( w < 0 || w > 200000 )
549 return false;
550
551 int npts = ( ( data[aOffset + 16] << 16 ) | ( data[aOffset + 17] << 8 ) | data[aOffset + 18] ) - INT3_BIAS;
552
553 return npts >= 1 && npts <= 100;
554 }
555 else
556 {
557 if( aOffset + 17 > fileSize )
558 return false;
559
560 if( data[aOffset + 6] != 0 || data[aOffset + 7] != 0 || data[aOffset + 8] != 0 || data[aOffset + 9] != 0 )
561 {
562 return false;
563 }
564
565 int w = ReadInt4At( data, aOffset + 10 );
566
567 if( w < 0 || w > 200000 )
568 return false;
569
570 int npts = ( ( data[aOffset + 14] << 16 ) | ( data[aOffset + 15] << 8 ) | data[aOffset + 16] ) - INT3_BIAS;
571
572 return npts >= 1 && npts <= 100;
573 }
574}
575
576
577bool SCH_PARSER::isFontBearingShapeStart( size_t aOffset ) const
578{
579 if( m_version < V31_CUTOVER )
580 return false;
581
582 const uint8_t* data = m_reader.GetData();
583 size_t fileSize = m_reader.GetFileSize();
584
585 static constexpr uint8_t TAHOMA_FONT_PATTERN[] = { 0x00, 0x06, 0x00, 0x54, 0x00, 0x61, 0x00,
586 0x68, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61 };
587
588 if( aOffset + 29 > fileSize )
589 return false;
590
591 int sentinel = ( ( data[aOffset] << 16 ) | ( data[aOffset + 1] << 8 ) | data[aOffset + 2] ) - INT3_BIAS;
592 int shapeField = ( ( data[aOffset + 3] << 16 ) | ( data[aOffset + 4] << 8 ) | data[aOffset + 5] ) - INT3_BIAS;
593
594 if( sentinel != 1000000 || shapeField != 0 )
595 return false;
596
597 if( std::memcmp( data + aOffset + 6, TAHOMA_FONT_PATTERN, sizeof( TAHOMA_FONT_PATTERN ) ) != 0 )
598 {
599 return false;
600 }
601
602 if( data[aOffset + 20] != 0 || data[aOffset + 21] != 0 )
603 return false;
604
605 int lineWidth = ReadInt4At( data, aOffset + 22 );
606
607 if( lineWidth < 0 || lineWidth > 200000 )
608 return false;
609
610 int numPoints = ( ( data[aOffset + 26] << 16 ) | ( data[aOffset + 27] << 8 ) | data[aOffset + 28] ) - INT3_BIAS;
611
612 return numPoints >= 1 && numPoints <= 100;
613}
614
615
616void SCH_PARSER::parseComponents( size_t aBusSectionOffset )
617{
618 size_t compSectionStart = m_reader.GetOffset();
619 m_componentSectionStart = compSectionStart;
620
621 // First attempt a sequential, count-guided decode. This fully consumes each record and works
622 // for legacy formats. Modern (v34+) records embed a marking/pattern tail that is not consumed
623 // field-by-field, so the sequential walk desyncs partway through; that is expected and silent,
624 // and we fall through to the structural boundary scan below. No warning is emitted because the
625 // boundary scan is the authoritative decoder, not a degraded last resort.
626 if( m_componentCount >= 0 )
627 {
628 int parsedCount = 0;
629 bool desynced = false;
630
631 while( parsedCount < m_componentCount && m_reader.GetOffset() < aBusSectionOffset )
632 {
633 size_t compStart = m_reader.GetOffset();
634
635 try
636 {
637 parseOneComponent( aBusSectionOffset, false );
638 parsedCount++;
639 }
640 catch( const std::exception& )
641 {
642 desynced = true;
643 break;
644 }
645
646 if( m_reader.GetOffset() <= compStart )
647 {
648 desynced = true;
649 break;
650 }
651 }
652
653 if( parsedCount == m_componentCount && !desynced )
654 return;
655
656 m_components.clear();
657 m_reader.SetOffset( compSectionStart );
658 }
659
660 // Structural boundary scan: locate every component header and decode each record within its
661 // [start, next) bounds. parseOneComponent() resyncs to the record end even when its variable
662 // tail is not fully understood, so a recognised header always yields a placed component.
664
665 std::vector<size_t> compStarts = scanComponentBoundaries( compSectionStart, aBusSectionOffset );
666
667 for( size_t ci = 0; ci < compStarts.size(); ci++ )
668 {
669 size_t compEnd = ( ci + 1 < compStarts.size() ) ? compStarts[ci + 1] : aBusSectionOffset;
670
671 try
672 {
673 m_reader.SetOffset( compStarts[ci] );
674 parseOneComponent( compEnd, true );
675 }
676 catch( const IO_ERROR& )
677 {
678 throw;
679 }
680 catch( const std::exception& e )
681 {
682 THROW_IO_ERRORF( _( "DipTrace import: failed to parse component %zu at offset 0x%06zX: %s" ),
683 ci, compStarts[ci], wxString::FromUTF8( e.what() ) );
684 }
685 }
686
687 if( m_componentCount >= 0 && static_cast<int>( m_components.size() ) != m_componentCount )
688 {
689 THROW_IO_ERRORF( _( "DipTrace import: found %zu components, but the file header declares %d." ),
691 }
692}
693
694
695void SCH_PARSER::parseOneComponent( size_t aCompEnd, bool aUseCompEnd )
696{
697 static bool s_dumpComponents = std::getenv( "KICAD_DIPTRACE_DUMP_COMPONENTS" ) != nullptr;
698 static bool s_dumpComponentDetail = std::getenv( "KICAD_DIPTRACE_DUMP_COMPONENT_DETAIL" ) != nullptr;
699
701 comp.fileOffset = m_reader.GetOffset();
702
703 // The next component header bounds this record. Variable-length fields decoded
704 // below (the modern extra-tail, shapes, and the embedded pattern) must not run
705 // past it, and where field decoding cannot resolve the exact end the record still
706 // lands here deterministically. The boundary scan already supplies the true next
707 // start via aCompEnd; the count-guided sequential walk passes the far bus-section
708 // offset, so locate the boundary structurally with the same signature used to
709 // enumerate every component.
710 size_t componentCeiling = aCompEnd > 0 ? aCompEnd : m_reader.GetFileSize();
711
712 if( !aUseCompEnd )
713 {
714 for( size_t p = comp.fileOffset + 16; p < componentCeiling; p++ )
715 {
716 if( isComponentHeaderAt( p ) )
717 {
718 componentCeiling = p;
719 break;
720 }
721 }
722 }
723
724 auto dumpDetail = [&]( const wxString& aMsg )
725 {
726 if( s_dumpComponentDetail && m_reporter )
727 {
728 m_reporter->Report( wxString::Format( wxT( "DipTrace SCH detail @0x%06zX: %s" ), comp.fileOffset, aMsg ),
730 }
731 };
732
733 comp.bboxX1 = m_reader.ReadInt4();
734 comp.bboxY1 = m_reader.ReadInt4();
735 comp.bboxX2 = m_reader.ReadInt4();
736 comp.bboxY2 = m_reader.ReadInt4();
737
738 comp.compName = m_reader.ReadString();
739 comp.refdes = m_reader.ReadString();
740 comp.value = m_reader.ReadString();
741 comp.prefix = m_reader.ReadString();
742 comp.nameDup = m_reader.ReadString();
743 dumpDetail( wxString::Format( wxT( "hdr end=0x%06zX name='%s' ref='%s' value='%s' prefix='%s'" ),
744 m_reader.GetOffset(), comp.compName, comp.refdes, comp.value, comp.prefix ) );
745
746 int postA = m_reader.ReadInt3();
747 int postB = m_reader.ReadInt3();
748 int flag1 = m_reader.ReadByte();
749 int postC = m_reader.ReadInt3();
750 int postD = m_reader.ReadInt3();
751
752 if( s_dumpComponentDetail )
753 {
754 dumpDetail( wxString::Format( wxT( "post-hdr end=0x%06zX postA=%d postB=%d flag1=%d "
755 "postC=%d postD=%d" ),
756 m_reader.GetOffset(), postA, postB, flag1, postC, postD ) );
757 }
758
759 comp.partName = m_reader.ReadString();
760 comp.partNumber = m_reader.ReadString();
761
762 uint8_t pb1 = m_reader.ReadByte();
763 comp.isMultiPart = ( pb1 == 1 );
764 comp.sheetIndex = m_reader.ReadInt3();
765
766 int partFieldB = m_reader.ReadInt3();
767 int partFieldC = m_reader.ReadInt3();
768 int partBboxX1 = m_reader.ReadInt4();
769 int partBboxY1 = m_reader.ReadInt4();
770 int partBboxX2 = m_reader.ReadInt4();
771 int partBboxY2 = m_reader.ReadInt4();
772
773 int partTailInt = 0;
774 wxString partTailStr;
775
776 if( comp.isMultiPart )
777 {
778 comp.partId = m_reader.ReadString();
779 partTailStr = comp.partId;
780 }
781 else
782 {
783 if( m_version < V31_CUTOVER )
784 {
785 size_t partTailStart = m_reader.GetOffset();
786 partTailInt = m_reader.ReadInt3();
787
788 // Legacy non-multipart records can store either:
789 // - a small int3 discriminator, or
790 // - a length-prefixed ASCII token (e.g. connector family token).
791 if( partTailInt > 0 && partTailInt < 256 )
792 {
793 size_t strStart = partTailStart + 3;
794 size_t strEnd = strStart + static_cast<size_t>( partTailInt );
795 const uint8_t* data = m_reader.GetData();
796 size_t fileSize = m_reader.GetFileSize();
797
798 if( strEnd + 3 <= fileSize )
799 {
800 bool asciiPayload = true;
801
802 for( size_t i = strStart; i < strEnd; i++ )
803 {
804 uint8_t c = data[i];
805
806 if( c < 0x20 || c > 0x7E )
807 {
808 asciiPayload = false;
809 break;
810 }
811 }
812
813 if( asciiPayload )
814 {
815 int nextInt3 =
816 ( ( data[strEnd] << 16 ) | ( data[strEnd + 1] << 8 ) | data[strEnd + 2] ) - INT3_BIAS;
817
818 if( nextInt3 >= -1 && nextInt3 < 1000 )
819 {
820 m_reader.SetOffset( partTailStart );
821 partTailStr = m_reader.ReadString();
822 partTailInt = 0;
823 }
824 }
825 }
826 }
827 }
828 else
829 {
830 m_reader.ReadByte();
831 m_reader.ReadByte();
832 }
833 }
834
835 int fieldD = m_reader.ReadInt3();
836 int fieldE = m_reader.ReadInt3();
837 dumpDetail( wxString::Format( wxT( "part end=0x%06zX part='%s' partNum='%s' sheet=%d "
838 "isMulti=%d partFieldB=%d partFieldC=%d "
839 "partBBox=[%d,%d,%d,%d] fieldD=%d fieldE=%d" ),
840 m_reader.GetOffset(), comp.partName, comp.partNumber, comp.sheetIndex,
841 comp.isMultiPart ? 1 : 0, partFieldB, partFieldC, partBboxX1, partBboxY1, partBboxX2,
842 partBboxY2, fieldD, fieldE ) );
843
844 if( s_dumpComponentDetail && m_version < V31_CUTOVER )
845 {
846 dumpDetail( wxString::Format( wxT( "part-tail end=0x%06zX partTailInt=%d partTailStr='%s'" ),
847 m_reader.GetOffset(), partTailInt, partTailStr ) );
848 }
849
850 // The fieldE-count block holds the part's user-defined additional fields, each a (name, value)
851 // string pair followed by an int3 type discriminator (0 = text). DipTrace shows these in the
852 // "Configure Additional Fields" list (Unique Name, Part Number (Digi-Key), etc.).
853 if( fieldE >= 1 && fieldE < 1000 )
854 {
855 comp.additionalFields.reserve( fieldE );
856
857 for( int i = 0; i < fieldE; i++ )
858 {
859 wxString fieldName = m_reader.ReadString();
860 wxString fieldValue = m_reader.ReadString();
861 m_reader.ReadInt3();
862
863 if( !fieldName.IsEmpty() )
864 comp.additionalFields.emplace_back( fieldName, fieldValue );
865 }
866 }
867
868 int fieldF = m_reader.ReadInt3();
869 int fieldG = m_reader.ReadInt3();
870 int byte4 = m_reader.ReadByte();
871
872 // This int4 is the placement rotation in radians x 1e4 (0, 15708, 31416, 47124 for 0/90/180/
873 // 270 degrees), not a library id. The pin, shape and field coordinates are stored already
874 // rotated by it, so it is used only to keep rotated and unrotated instances of one part from
875 // sharing a single library symbol.
876 comp.rotationE4 = m_reader.ReadInt4();
877 comp.libPath = m_reader.ReadString();
878
879 int tailA = m_reader.ReadInt3();
880 int tailB = m_reader.ReadInt3();
881 size_t tailAfterB = m_reader.GetOffset();
882 wxString tailStrA = m_reader.ReadString();
883 wxString extraTail = wxEmptyString;
884 int pinMetaA = 0;
885 int pinMetaB = 0;
886 int pinMetaF = 0;
887 int pinHdrByte = 0;
888 int numPins = 0;
889 bool simpleModernTail = false;
890
891 if( m_version < V31_CUTOVER )
892 {
893 // The legacy pre-pin metadata tuple precedes the pin count, but its layout
894 // shifted across early format revisions. v22 packs two single meta bytes then
895 // the count (one int3 shorter); v23 places the count first; v24+ (incl. v31)
896 // use an int3 + byte + int3 tuple followed by the count.
897 if( m_version <= 22 )
898 {
899 pinMetaA = m_reader.ReadInt3();
900 pinMetaF = m_reader.ReadByte();
901 pinMetaB = m_reader.ReadByte();
902 numPins = m_reader.ReadInt3();
903 }
904 else if( m_version == 23 )
905 {
906 numPins = m_reader.ReadInt3();
907 pinMetaF = m_reader.ReadByte();
908 pinMetaA = m_reader.ReadInt3();
909 pinMetaB = m_reader.ReadInt3();
910 }
911 else
912 {
913 pinMetaA = m_reader.ReadInt3();
914 pinMetaF = m_reader.ReadByte();
915 pinMetaB = m_reader.ReadInt3();
916 numPins = m_reader.ReadInt3();
917 }
918
919 if( s_dumpComponentDetail )
920 {
921 dumpDetail( wxString::Format( wxT( "pre-pin-hdr end=0x%06zX fieldF=%d fieldG=%d "
922 "byte4=%d rotE4=%d libPath='%s' tailA=%d tailB=%d "
923 "tailStrA='%s' pinMetaA=%d pinMetaF=%d pinMetaB=%d "
924 "numPins=%d" ),
925 m_reader.GetOffset(), fieldF, fieldG, byte4, comp.rotationE4, comp.libPath,
926 tailA, tailB, tailStrA, pinMetaA, pinMetaF, pinMetaB, numPins ) );
927 }
928 }
929 else
930 {
931 bool usedPinSeparatorFallback = false;
932
933 auto readModernExtraAndPins = [&]( wxString& aExtraTail, int& aNumPins, int& aPinHdr,
934 bool& aUsedPinSeparatorFallback ) -> bool
935 {
936 size_t extraStart = m_reader.GetOffset();
937 uint8_t extraHdr[4] = {};
938 m_reader.ReadBytes( extraHdr, 4 );
939
940 uint32_t extraChars = ( static_cast<uint32_t>( extraHdr[0] ) << 24 )
941 | ( static_cast<uint32_t>( extraHdr[1] ) << 16 )
942 | ( static_cast<uint32_t>( extraHdr[2] ) << 8 ) | extraHdr[3];
943
944 if( extraChars >= 10000 && extraHdr[0] == 0 && extraHdr[1] == 0 )
945 {
946 THROW_IO_ERRORF( _( "DipTrace import: invalid component extra-tail length %u at offset 0x%06zX." ),
947 extraChars, extraStart );
948 }
949
950 if( extraChars > 0 )
951 {
952 size_t extraBytes = static_cast<size_t>( extraChars ) * 2;
953 bool fitsFile = m_reader.GetOffset() + extraBytes <= m_reader.GetFileSize();
954
955 if( extraChars < 10000 && fitsFile && m_reader.GetOffset() + extraBytes <= componentCeiling )
956 {
957 wxMBConvUTF16BE conv;
958 aExtraTail = wxString( reinterpret_cast<const char*>( m_reader.GetData() + m_reader.GetOffset() ),
959 conv, extraBytes );
960 m_reader.Skip( extraBytes );
961 }
962 else if( extraChars < 10000 && fitsFile )
963 {
964 // Length is plausible but the field would run into the next
965 // component, so this record has no extra tail (e.g. net ports).
966 // Leave the bytes for the pin-count read below.
967 m_reader.SetOffset( extraStart );
968 }
969 else
970 {
971 // Fallback for variants that store this field with ReadString() encoding.
972 m_reader.SetOffset( extraStart );
973
974 try
975 {
976 aExtraTail = m_reader.ReadString();
977 }
978 catch( ... )
979 {
980 return false;
981 }
982 }
983 }
984
985 try
986 {
987 size_t pinStart = m_reader.GetOffset();
988 aNumPins = m_reader.ReadInt3();
989 aPinHdr = m_reader.ReadByte();
990
991 // Some variants store a 2-byte separator before pin count.
992 if( ( aNumPins < 0 || aNumPins > 500 ) && pinStart + 6 <= m_reader.GetFileSize() )
993 {
994 m_reader.SetOffset( pinStart + 2 );
995
996 int sepPins = m_reader.ReadInt3();
997 int sepHdr = m_reader.ReadByte();
998
999 if( sepPins >= 0 && sepPins <= 500 )
1000 {
1001 aNumPins = sepPins;
1002 aPinHdr = sepHdr;
1003 aUsedPinSeparatorFallback = true;
1004 }
1005 else
1006 {
1007 m_reader.SetOffset( pinStart + 4 );
1008 }
1009 }
1010 }
1011 catch( ... )
1012 {
1013 return false;
1014 }
1015
1016 return true;
1017 };
1018
1019 bool usedTaillessFallback = false;
1020 bool canonicalPinSeparatorFallback = false;
1021 bool readCanonicalPins =
1022 readModernExtraAndPins( extraTail, numPins, pinHdrByte, canonicalPinSeparatorFallback );
1023 usedPinSeparatorFallback = canonicalPinSeparatorFallback;
1024
1025 if( !readCanonicalPins || ( numPins < 0 || numPins > 500 ) )
1026 {
1027 size_t canonicalEnd = m_reader.GetOffset();
1028 wxString canonicalTailStrA = tailStrA;
1029 wxString canonicalExtraTail = extraTail;
1030 int canonicalNumPins = numPins;
1031 int canonicalPinHdrByte = pinHdrByte;
1032
1033 // Some v41 files omit tailStrA and place the int4-length extra tail
1034 // directly after tailB.
1035 m_reader.SetOffset( tailAfterB );
1036 tailStrA = wxEmptyString;
1037 extraTail = wxEmptyString;
1038 numPins = 0;
1039 pinHdrByte = 0;
1040 usedTaillessFallback = true;
1041
1042 bool fallbackPinSeparatorFallback = false;
1043 bool readFallbackPins =
1044 readModernExtraAndPins( extraTail, numPins, pinHdrByte, fallbackPinSeparatorFallback );
1045
1046 if( readFallbackPins && numPins >= 0 && numPins <= 500 )
1047 {
1048 usedPinSeparatorFallback = fallbackPinSeparatorFallback;
1049 }
1050 else
1051 {
1052 m_reader.SetOffset( canonicalEnd );
1053 tailStrA = canonicalTailStrA;
1054 extraTail = canonicalExtraTail;
1055 numPins = canonicalNumPins;
1056 pinHdrByte = canonicalPinHdrByte;
1057 usedTaillessFallback = false;
1058 usedPinSeparatorFallback = canonicalPinSeparatorFallback;
1059 }
1060 }
1061
1062 if( s_dumpComponentDetail )
1063 {
1064 dumpDetail( wxString::Format( wxT( "pre-pin-hdr end=0x%06zX fieldF=%d fieldG=%d "
1065 "byte4=%d rotE4=%d libPath='%s' tailA=%d tailB=%d "
1066 "tailStrA='%s' extraTail='%s' numPins=%d pinHdr=%d "
1067 "taillessFallback=%d pinSeparatorFallback=%d" ),
1068 m_reader.GetOffset(), fieldF, fieldG, byte4, comp.rotationE4, comp.libPath,
1069 tailA, tailB, tailStrA, extraTail, numPins, pinHdrByte,
1070 usedTaillessFallback ? 1 : 0, usedPinSeparatorFallback ? 1 : 0 ) );
1071 }
1072
1073 simpleModernTail = tailA == 0 && tailB == 0 && tailStrA.IsEmpty() && extraTail.IsEmpty()
1074 && !usedTaillessFallback && !usedPinSeparatorFallback;
1075
1076 // The resolved extra tail string is the part's datasheet URL when present (empty for parts
1077 // that carry none, e.g. plain resistors). Capture it after the fallback settles so a stale
1078 // value from the discarded branch is never stored.
1079 comp.datasheet = extraTail;
1080 }
1081
1082 dumpDetail( wxString::Format( wxT( "pre-pin end=0x%06zX numPins=%d" ), m_reader.GetOffset(), numPins ) );
1083
1084 if( numPins < 0 || numPins > 500 )
1085 {
1086 if( aUseCompEnd && !simpleModernTail )
1087 {
1088 m_reader.SetOffset( aCompEnd );
1089 m_components.push_back( comp );
1090 return;
1091 }
1092
1093 THROW_IO_ERRORF( _( "Invalid pin count %d at component offset 0x%06zX." ), numPins, comp.fileOffset );
1094 }
1095
1096 for( int pinIdx = 0; pinIdx < numPins; pinIdx++ )
1097 {
1098 auto consumeLaterPinSeparatorIfPresent = [&]() -> bool
1099 {
1100 if( m_version < V31_CUTOVER || pinIdx == 0 )
1101 return false;
1102
1103 size_t start = m_reader.GetOffset();
1104
1105 if( start + 2 >= componentCeiling )
1106 return false;
1107
1108 const uint8_t* data = m_reader.GetData();
1109
1110 if( data[start] != 0 || data[start + 1] != 0 )
1111 return false;
1112
1113 bool currentRecordValid = false;
1114
1115 try
1116 {
1117 DCH_COMPONENT probeComp;
1118 m_reader.SetOffset( start );
1119 parsePin( pinIdx, probeComp );
1120 currentRecordValid = m_reader.GetOffset() <= componentCeiling;
1121 }
1122 catch( const std::exception& )
1123 {
1124 currentRecordValid = false;
1125 }
1126
1127 m_reader.SetOffset( start );
1128
1129 if( currentRecordValid )
1130 return false;
1131
1132 bool shiftedRecordValid = false;
1133
1134 try
1135 {
1136 DCH_COMPONENT probeComp;
1137 m_reader.SetOffset( start + 2 );
1138 parsePin( pinIdx, probeComp );
1139 shiftedRecordValid = m_reader.GetOffset() <= componentCeiling;
1140 }
1141 catch( const std::exception& )
1142 {
1143 shiftedRecordValid = false;
1144 }
1145
1146 m_reader.SetOffset( start );
1147
1148 if( !shiftedRecordValid )
1149 return false;
1150
1151 m_reader.Skip( 2 );
1152 return true;
1153 };
1154
1155 if( consumeLaterPinSeparatorIfPresent() )
1156 simpleModernTail = false;
1157
1158 try
1159 {
1160 parsePin( pinIdx, comp );
1161 }
1162 catch( const std::exception& e )
1163 {
1164 if( aUseCompEnd && !simpleModernTail )
1165 break;
1166
1167 THROW_IO_ERRORF( _( "DipTrace import: failed to parse pin %d in component at 0x%06zX (offset 0x%06zX): %s" ),
1168 pinIdx, comp.fileOffset, m_reader.GetOffset(), wxString::FromUTF8( e.what() ) );
1169 }
1170 }
1171
1172 // Recognise a shape-record prefix carrying an out-of-range point count. For the
1173 // well-framed modern formats this signals a corrupt file and must fail the load;
1174 // legacy records can false-match the embedded-pattern header here, so they simply
1175 // end the shape list and resync through the pattern/ceiling handling below.
1176 auto readShapePointCountIfHeaderPrefix = [&]( size_t aOffset, int& aPointCount ) -> bool
1177 {
1178 const uint8_t* data = m_reader.GetData();
1179 size_t fileSize = m_reader.GetFileSize();
1180 size_t limit = std::min( fileSize, aCompEnd );
1181
1182 if( aOffset + 17 > limit )
1183 return false;
1184
1185 if( data[aOffset + 6] != 0 || data[aOffset + 7] != 0 || data[aOffset + 8] != 0 || data[aOffset + 9] != 0 )
1186 {
1187 return false;
1188 }
1189
1190 int width = ReadInt4At( data, aOffset + 10 );
1191
1192 if( width < 0 || width > 200000 )
1193 return false;
1194
1195 aPointCount = ( ( data[aOffset + 14] << 16 ) | ( data[aOffset + 15] << 8 ) | data[aOffset + 16] ) - INT3_BIAS;
1196 return true;
1197 };
1198
1199 // Some components store their marking records (reference, value, name) BEFORE the graphic shapes
1200 // rather than after them (e.g. the C4D02120E diode). The shape walk below recognises shapes by
1201 // their header, so a leading marking would stop it and drop every shape. Consume any leading
1202 // marking records first. Each is 00 00 00 + int3 type (1 name, 2 reference, 3 value) + font
1203 // string + text + int4 fontSize + int3 fieldA + int4 coordX + int4 coordY + a fixed 20-byte
1204 // trailer. Reference and value markings are kept so their positions are honoured; the walk then
1205 // lands on the first real shape. Components whose markings follow the shapes consume nothing here
1206 // and are handled by the text-field loop after the shapes, as before.
1207 while( m_reader.GetOffset() + 6 < componentCeiling )
1208 {
1209 size_t markOff = m_reader.GetOffset();
1210 const uint8_t* mdata = m_reader.GetData();
1211
1212 if( mdata[markOff] != 0 || mdata[markOff + 1] != 0 || mdata[markOff + 2] != 0 )
1213 break;
1214
1215 int markType = ( ( mdata[markOff + 3] << 16 ) | ( mdata[markOff + 4] << 8 )
1216 | mdata[markOff + 5] )
1217 - INT3_BIAS;
1218
1219 if( markType < 1 || markType > 3 )
1220 break;
1221
1222 try
1223 {
1224 DCH_COMPONENT_TEXT mark;
1225 m_reader.ReadBytes( mark.flags, 3 );
1226 mark.type = m_reader.ReadInt3();
1227 mark.fontName = m_reader.ReadString();
1228
1229 if( mark.fontName.IsEmpty() || mark.fontName.size() > 64 )
1230 {
1231 m_reader.SetOffset( markOff );
1232 break;
1233 }
1234
1235 mark.text = m_reader.ReadString();
1236 mark.fontSize = m_reader.ReadInt4();
1237 mark.fieldA = m_reader.ReadInt3();
1238 mark.coordX = m_reader.ReadInt4();
1239 mark.coordY = m_reader.ReadInt4();
1240
1241 // Fixed 20-byte trailer.
1242 m_reader.Skip( 2 );
1243 m_reader.ReadInt4();
1244 m_reader.ReadInt4();
1245 m_reader.Skip( 1 );
1246 m_reader.ReadInt3();
1247 m_reader.ReadInt3();
1248 m_reader.ReadInt3();
1249
1250 if( m_reader.GetOffset() > componentCeiling )
1251 {
1252 m_reader.SetOffset( markOff );
1253 break;
1254 }
1255
1256 if( mark.type == 2 || mark.type == 3 )
1257 comp.texts.push_back( mark );
1258 }
1259 catch( const std::exception& )
1260 {
1261 m_reader.SetOffset( markOff );
1262 break;
1263 }
1264 }
1265
1266 while( m_reader.GetOffset() < aCompEnd && m_reader.GetOffset() < componentCeiling )
1267 {
1268 size_t shapeOffset = m_reader.GetOffset();
1269 int shapePointCount = 0;
1270
1271 if( isFontBearingShapeStart( shapeOffset ) )
1272 {
1273 try
1274 {
1276 continue;
1277 }
1278 catch( const std::exception& )
1279 {
1280 break;
1281 }
1282 }
1283
1284 if( !isShapeStart( shapeOffset ) )
1285 {
1286 if( m_version >= V31_CUTOVER && readShapePointCountIfHeaderPrefix( shapeOffset, shapePointCount )
1287 && ( shapePointCount < 1 || shapePointCount > 100 ) )
1288 {
1289 THROW_IO_ERRORF( _( "DipTrace import: invalid component shape point count %d at offset 0x%06zX." ),
1290 shapePointCount, shapeOffset );
1291 }
1292
1293 // End of the shape list (or a record layout not fully understood). The
1294 // embedded-pattern and end-of-record handling resync to the next header.
1295 break;
1296 }
1297
1298 try
1299 {
1300 parseShape( comp );
1301 }
1302 catch( const std::exception& )
1303 {
1304 break;
1305 }
1306 }
1307
1308 // Parse the embedded footprint pattern section that follows the shapes.
1309 // This extracts the pattern name (for the Footprint field) and consumes
1310 // the pattern bytes so count-guided parsing can advance to the next component.
1311 try
1312 {
1313 while( parseComponentTextField( comp, componentCeiling ) )
1314 {
1315 }
1316
1317 // The first marking record's trailer overran the sequential reader, so the loop stops a few
1318 // bytes inside the value record rather than on its header. Recover the value position by
1319 // scanning a small window back to the value record's 00 00 00 header and reading only up to
1320 // its coordinate, without advancing (the footprint reader still starts at the stop offset).
1321 // Without the real value position an asymmetric layout like C6 (reference above, value to the
1322 // side) would be mis-placed by the symmetric mirror fallback below.
1323 {
1324 size_t save = m_reader.GetOffset();
1325 const uint8_t* data = m_reader.GetData();
1326 size_t lim = std::min( componentCeiling > 0 ? componentCeiling : m_reader.GetFileSize(),
1327 m_reader.GetFileSize() );
1328 size_t lo = ( save > 24 ) ? save - 24 : 0;
1329
1330 for( size_t probe = lo; probe + 6 < lim && probe <= save + 4; probe++ )
1331 {
1332 if( data[probe] != 0 || data[probe + 1] != 0 || data[probe + 2] != 0 )
1333 continue;
1334
1335 m_reader.SetOffset( probe );
1336
1337 try
1338 {
1340 m_reader.ReadBytes( vt.flags, 3 );
1341 vt.type = m_reader.ReadInt3();
1342
1343 if( vt.type != 2 && vt.type != 3 )
1344 continue;
1345
1346 vt.fontName = m_reader.ReadString();
1347
1348 if( vt.fontName.IsEmpty() || vt.fontName.size() > 64 )
1349 continue;
1350
1351 vt.text = m_reader.ReadString();
1352
1353 if( vt.text.IsEmpty() || vt.text.size() > 256 )
1354 continue;
1355
1356 vt.fontSize = m_reader.ReadInt4();
1357 vt.fieldA = m_reader.ReadInt3();
1358 vt.coordX = m_reader.ReadInt4();
1359 vt.coordY = m_reader.ReadInt4();
1360
1361 bool haveType = false;
1362
1363 for( const DCH_COMPONENT_TEXT& t : comp.texts )
1364 haveType = haveType || ( t.type == vt.type );
1365
1366 if( !haveType )
1367 {
1368 comp.texts.push_back( vt );
1369 break;
1370 }
1371 }
1372 catch( const std::exception& )
1373 {
1374 }
1375 }
1376
1377 m_reader.SetOffset( save );
1378 }
1379
1380 parseEmbeddedPattern( comp, aCompEnd );
1381
1382 if( m_reader.GetOffset() < aCompEnd && m_reader.GetOffset() != m_busSectionOffset
1383 && !isComponentHeaderAt( m_reader.GetOffset() ) )
1384 {
1385 size_t afterFirstPattern = m_reader.GetOffset();
1386 parseEmbeddedPattern( comp, aCompEnd );
1387
1388 if( m_reader.GetOffset() == afterFirstPattern )
1389 m_reader.SetOffset( afterFirstPattern );
1390 }
1391 }
1392 catch( const std::exception& e )
1393 {
1394 if( s_dumpComponentDetail )
1395 {
1396 dumpDetail( wxString::Format( wxT( "pattern parse failed at 0x%06zX: %s" ), m_reader.GetOffset(),
1397 wxString::FromUTF8( e.what() ) ) );
1398 }
1399 }
1400
1401 size_t parsedEnd = m_reader.GetOffset();
1402
1403 if( s_dumpComponentDetail )
1404 {
1405 dumpDetail( wxString::Format( wxT( "post-pattern end=0x%06zX pins=%zu shapes=%zu "
1406 "pattern='%s'" ),
1407 parsedEnd, comp.pins.size(), comp.shapes.size(), comp.patternName ) );
1408
1409 if( aUseCompEnd && parsedEnd < aCompEnd )
1410 {
1411 dumpDetail( wxString::Format( wxT( "tail skipped=%zu bytes to compEnd=0x%06zX" ), aCompEnd - parsedEnd,
1412 aCompEnd ) );
1413 }
1414 }
1415
1416 if( aUseCompEnd )
1417 {
1418 m_reader.SetOffset( aCompEnd );
1419 }
1420 else if( m_reader.GetOffset() != componentCeiling
1421 && ( componentCeiling == m_busSectionOffset || isComponentHeaderAt( componentCeiling ) ) )
1422 {
1423 // The field decoder did not consume exactly to the next component. The
1424 // boundary is structurally known, so land on it to keep the count-guided
1425 // sequential walk deterministic without the global boundary scan.
1426 m_reader.SetOffset( componentCeiling );
1427 }
1428
1429 m_components.push_back( comp );
1430
1431 if( s_dumpComponents && m_reporter )
1432 {
1433 m_reporter->Report( wxString::Format( wxT( "DipTrace SCH comp @0x%06zX ref='%s' name='%s' "
1434 "sheet=%d pins=%zu shapes=%zu pattern='%s'" ),
1435 comp.fileOffset, comp.refdes, comp.compName, comp.sheetIndex,
1436 comp.pins.size(), comp.shapes.size(), comp.patternName ),
1438 }
1439}
1440
1441
1442void SCH_PARSER::parsePin( int aPinIndex, DCH_COMPONENT& aComp )
1443{
1444 DCH_PIN pin;
1445 pin.index = aPinIndex;
1446
1447 if( aPinIndex == 0 )
1448 {
1449 pin.hasHeader = true;
1450
1451 if( m_version <= 22 )
1452 {
1453 // v22 prefixes the first pin with a lead byte and four int3 header
1454 // fields; reading the wrong width misaligns every later pin field.
1455 m_reader.ReadByte();
1456 pin.headerA = m_reader.ReadInt3();
1457 pin.headerB = m_reader.ReadInt3();
1458 pin.headerC = m_reader.ReadInt3();
1459 pin.typeCode = m_reader.ReadInt3();
1460 }
1461 else if( m_version < V31_CUTOVER )
1462 {
1463 // Legacy v1/v2 schematic files use a shorter first-pin preamble.
1464 // Reading 4 int3 fields here misaligns all subsequent pin fields.
1465 pin.headerA = m_reader.ReadInt3();
1466 pin.typeCode = m_reader.ReadInt3();
1467 }
1468 else
1469 {
1470 pin.headerA = m_reader.ReadInt3();
1471 pin.headerB = m_reader.ReadInt3();
1472 pin.headerC = m_reader.ReadInt3();
1473 pin.typeCode = m_reader.ReadInt3();
1474 }
1475 }
1476
1477 pin.x = m_reader.ReadInt4();
1478 pin.y = m_reader.ReadInt4();
1479 pin.length = m_reader.ReadInt4();
1480 pin.name = m_reader.ReadString();
1481 pin.number = m_reader.ReadString();
1482
1483 pin.netFlagA = m_reader.ReadByte();
1484 pin.netFlagB = m_reader.ReadByte();
1485
1486 pin.labelXOff = m_reader.ReadInt4();
1487 pin.labelYOff = m_reader.ReadInt4();
1488 pin.numXOff = m_reader.ReadInt4();
1489 pin.numYOff = m_reader.ReadInt4();
1490 m_reader.ReadInt3(); // post_a
1491
1492 if( m_version < V31_CUTOVER )
1493 {
1494 m_reader.ReadByte();
1495 m_reader.ReadByte();
1496 m_reader.ReadInt3();
1497 m_reader.ReadByte();
1498 m_reader.ReadInt3();
1499 }
1500 else
1501 {
1502 size_t midTailStart = m_reader.GetOffset();
1503 const uint8_t* data = m_reader.GetData();
1504
1505 if( midTailStart + 5 <= m_reader.GetFileSize() && data[midTailStart] == 0 && data[midTailStart + 1] == 0 )
1506 {
1507 m_reader.Skip( 2 );
1508 pin.midTailText = m_reader.ReadString();
1509 m_reader.ReadByte();
1510 }
1511 else
1512 {
1513 m_reader.Skip( 5 );
1514 }
1515
1516 m_reader.ReadInt3();
1517 }
1518
1519 pin.stubDx = m_reader.ReadInt4();
1520 pin.stubDy = m_reader.ReadInt4();
1521
1522 pin.tailByte = m_reader.ReadByte();
1523 m_reader.ReadInt3();
1524 m_reader.ReadInt3();
1525 m_reader.ReadInt3();
1526 m_reader.ReadInt3();
1527
1528 aComp.pins.push_back( pin );
1529}
1530
1531
1533{
1534 DCH_SHAPE shape;
1535
1536 // The shape kind is carried by the int3 pair immediately preceding the all zero shape header
1537 // (the previous record's trailer ends with this same pair, so it reads as a leading
1538 // discriminator here). The header bytes themselves are all zero, so this leading pair is the
1539 // only place the line/arrow/rectangle/obround/polygon type is recorded.
1540 size_t headerStart = m_reader.GetOffset();
1541
1542 if( headerStart >= 6 )
1543 {
1544 const uint8_t* data = m_reader.GetData();
1545 shape.kindCode = ( ( data[headerStart - 6] << 16 ) | ( data[headerStart - 5] << 8 ) | data[headerStart - 4] )
1546 - INT3_BIAS;
1547 shape.kindFlag = ( ( data[headerStart - 3] << 16 ) | ( data[headerStart - 2] << 8 ) | data[headerStart - 1] )
1548 - INT3_BIAS;
1549 }
1550
1551 m_reader.ReadBytes( shape.flags, 3 );
1552 shape.shapeField = m_reader.ReadInt3();
1553
1554 if( m_version < V31_CUTOVER )
1555 {
1556 m_reader.ReadInt3();
1557 m_reader.ReadInt3();
1558 }
1559 else
1560 {
1561 m_reader.Skip( 4 );
1562 }
1563
1564 shape.lineWidth = m_reader.ReadInt4();
1565 int numPoints = m_reader.ReadInt3();
1566
1567 if( numPoints < 1 || numPoints > 100 )
1568 return;
1569
1570 for( int i = 0; i < numPoints; i++ )
1571 {
1572 int x = m_reader.ReadInt4();
1573 int y = m_reader.ReadInt4();
1574 shape.points.push_back( VECTOR2I( x, y ) );
1575 }
1576
1577 m_reader.Skip( 2 );
1578 shape.fontX = m_reader.ReadInt4();
1579 shape.fontY = m_reader.ReadInt4();
1580 m_reader.ReadByte();
1581 m_reader.ReadInt3();
1582
1583 // The trailing int3 pair is the next record's leading kind discriminator, read above for the
1584 // following shape. Consume it here so the reader lands on that next header.
1585 m_reader.ReadInt3();
1586 m_reader.ReadInt3();
1587
1588 aComp.shapes.push_back( shape );
1589}
1590
1591
1593{
1594 DCH_SHAPE shape;
1595 shape.kindCode = 1;
1596 shape.kindFlag = 0;
1597
1598 // Modern component body outlines can store an inline font name before the line-width/point
1599 // tuple. The reference schematic U1 uses this form for the four line edges of its IC body.
1600 m_reader.ReadInt3(); // sentinel, validated by isFontBearingShapeStart()
1601 shape.shapeField = m_reader.ReadInt3();
1602 m_reader.ReadString(); // observed "Tahoma"
1603 m_reader.Skip( 2 ); // zero pad
1604
1605 shape.lineWidth = m_reader.ReadInt4();
1606 int numPoints = m_reader.ReadInt3();
1607
1608 if( numPoints < 1 || numPoints > 100 )
1609 return;
1610
1611 for( int i = 0; i < numPoints; i++ )
1612 {
1613 int x = m_reader.ReadInt4();
1614 int y = m_reader.ReadInt4();
1615 shape.points.push_back( VECTOR2I( x, y ) );
1616 }
1617
1618 m_reader.Skip( 2 );
1619 shape.fontX = m_reader.ReadInt4();
1620 shape.fontY = m_reader.ReadInt4();
1621 m_reader.ReadByte();
1622 m_reader.ReadInt3();
1623 m_reader.ReadInt3();
1624 m_reader.ReadInt3();
1625
1626 aComp.shapes.push_back( shape );
1627}
1628
1629
1631{
1632 size_t startOffset = m_reader.GetOffset();
1633 const uint8_t* data = m_reader.GetData();
1634 size_t limit = std::min( aCompEnd > 0 ? aCompEnd : m_reader.GetFileSize(), m_reader.GetFileSize() );
1635
1636 if( startOffset + 6 > limit || data[startOffset] != 0 || data[startOffset + 1] != 0 || data[startOffset + 2] != 0 )
1637 {
1638 return false;
1639 }
1640
1641 int fieldType =
1642 ( ( data[startOffset + 3] << 16 ) | ( data[startOffset + 4] << 8 ) | data[startOffset + 5] ) - INT3_BIAS;
1643
1644 if( fieldType < 0 || fieldType > 100 )
1645 return false;
1646
1648
1649 try
1650 {
1651 m_reader.ReadBytes( text.flags, 3 );
1652 text.type = m_reader.ReadInt3();
1653 text.fontName = m_reader.ReadString();
1654 text.text = m_reader.ReadString();
1655
1656 if( text.fontName.IsEmpty() || text.fontName.size() > 128 || text.text.size() > 512 )
1657 throw std::runtime_error( "invalid component text field string" );
1658
1659 text.fontSize = m_reader.ReadInt4();
1660 text.fieldA = m_reader.ReadInt3();
1661 text.coordX = m_reader.ReadInt4();
1662 text.coordY = m_reader.ReadInt4();
1663 text.fieldB = m_reader.ReadInt4();
1664 text.fieldC = m_reader.ReadInt4();
1665 text.flagA = m_reader.ReadByte();
1666 text.flagB = m_reader.ReadByte();
1667 text.fieldD = m_reader.ReadInt4();
1668 text.fieldE = m_reader.ReadInt4();
1669 text.fieldF = m_reader.ReadInt3();
1670 text.fieldG = m_reader.ReadInt3();
1671 m_reader.ReadBytes( text.flags2, 4 );
1672 text.fieldH = m_reader.ReadInt3();
1673
1674 if( m_reader.GetOffset() > limit )
1675 throw std::runtime_error( "component text field overruns component" );
1676 }
1677 catch( const std::exception& )
1678 {
1679 m_reader.SetOffset( startOffset );
1680 return false;
1681 }
1682
1683 aComp.texts.push_back( text );
1684 return true;
1685}
1686
1687
1689{
1690 size_t startOffset = m_reader.GetOffset();
1691
1692 if( m_version < V31_CUTOVER )
1693 {
1694 // Legacy v1/v2 format (confirmed for v23). The pattern header begins
1695 // with int3(0) + int3(0) as a reliable sentinel.
1696 if( m_reader.PeekInt3() != 0 )
1697 {
1698 m_reader.SetOffset( startOffset );
1699 return;
1700 }
1701
1702 // Pre-name header (23 bytes): 2*int3 + 2*int4 + byte + int4 + byte + int3
1703 m_reader.ReadInt3();
1704 m_reader.ReadInt3();
1705 m_reader.ReadInt4();
1706 m_reader.ReadInt4();
1707 m_reader.ReadByte();
1708 m_reader.ReadInt4();
1709 m_reader.ReadByte();
1710 m_reader.ReadInt3();
1711
1712 // Dimensions (16 bytes): Width + Height + DefPadW + DefPadH
1713 m_reader.ReadInt4();
1714 m_reader.ReadInt4();
1715 m_reader.ReadInt4();
1716 m_reader.ReadInt4();
1717
1718 // Pre-drill (8 bytes): mountType + mountByte + Drill
1719 m_reader.ReadInt3();
1720 m_reader.ReadByte();
1721 m_reader.ReadInt4();
1722
1723 aComp.patternName = m_reader.ReadString();
1724
1725 // Post-name (23 bytes): OrgX + OrgY + 2*int4 + byte + 2*int3
1726 m_reader.ReadInt4();
1727 m_reader.ReadInt4();
1728 m_reader.ReadInt4();
1729 m_reader.ReadInt4();
1730 m_reader.ReadByte();
1731 m_reader.ReadInt3();
1732 m_reader.ReadInt3();
1733
1734 int fieldA = m_reader.ReadInt3();
1735
1736 if( fieldA < 0 || fieldA > 500 )
1737 {
1738 aComp.patternName.clear();
1739 m_reader.SetOffset( startOffset );
1740 return;
1741 }
1742
1743 if( fieldA == 0 )
1744 {
1745 // Empty pattern (net ports). 9 zero bytes footer: 3 * int3(0).
1746 m_reader.ReadInt3();
1747 m_reader.ReadInt3();
1748 m_reader.ReadInt3();
1749 return;
1750 }
1751
1752 // Skip byte(0) separator before pad template
1753 m_reader.ReadByte();
1754
1755 // Skip pad records: template + (fieldA - 2) real pads + trailing terminator.
1756 // Each pad: int3(id) + int4(X) + int4(Y) + str(Number) + str(Note)
1757 // + int4(W) + int4(H) + int4(Drill) + 11-byte tail
1758 for( int i = 0; i < fieldA; i++ )
1759 {
1760 m_reader.ReadInt3();
1761 m_reader.ReadInt4();
1762 m_reader.ReadInt4();
1763 m_reader.ReadString();
1764 m_reader.ReadString();
1765 m_reader.ReadInt4();
1766 m_reader.ReadInt4();
1767 m_reader.ReadInt4();
1768 m_reader.Skip( 11 );
1769 }
1770
1771 // 39 zero bytes trailing terminator
1772 m_reader.Skip( 39 );
1773
1774 // Pre-sentinel block (58 bytes): int3(fieldB) + 55 remaining bytes
1775 int fieldB = m_reader.ReadInt3();
1776
1777 if( fieldB < 0 || fieldB > 1000 )
1778 {
1779 aComp.patternName.clear();
1780 m_reader.SetOffset( startOffset );
1781 return;
1782 }
1783
1784 m_reader.Skip( 55 );
1785
1786 // Sentinel records: fieldB total. Last one is 49-byte footer, rest are 62 bytes.
1787 for( int i = 0; i < fieldB; i++ )
1788 {
1789 if( i == fieldB - 1 )
1790 m_reader.Skip( 49 );
1791 else
1792 m_reader.Skip( 62 );
1793 }
1794 }
1795 else
1796 {
1797 // Modern v34+ embedded patterns extend the legacy header with an
1798 // additional drill field and a pre-name tail. They then store counted
1799 // pad, drawing, and 3D-model records, so the component record can be
1800 // consumed without scanning for the next component.
1801
1802 // Hard ceiling for this pattern body: the next component header. Nothing in
1803 // the record can legitimately reach it, so it bounds every model-tail search
1804 // and keeps the decoder from consuming into a following component. The
1805 // sequential walk passes aCompEnd as the far bus-section offset, so without
1806 // this bound an unframed scan can latch onto a later record's 3D-model tail
1807 // and swallow whole components. isComponentHeaderAt() is the same structural
1808 // signature used to enumerate every component, so the nearest match is this
1809 // record's true end.
1810 size_t patternCeiling = std::min( aCompEnd > 0 ? aCompEnd : m_reader.GetFileSize(), m_reader.GetFileSize() );
1811
1812 for( size_t p = startOffset + 16; p < patternCeiling; p++ )
1813 {
1814 if( isComponentHeaderAt( p ) )
1815 {
1816 patternCeiling = p;
1817 break;
1818 }
1819 }
1820
1821 // The record end when field decoding cannot resolve the model tail. Prefer
1822 // the detected next-component boundary; only fall back to leaving the pattern
1823 // unconsumed if no boundary was identified (which would surface as a desync
1824 // rather than a silent overconsumption).
1825 auto landingForCeiling = [&]() -> size_t
1826 {
1827 if( patternCeiling == aCompEnd || patternCeiling == m_busSectionOffset
1828 || isComponentHeaderAt( patternCeiling ) )
1829 {
1830 return patternCeiling;
1831 }
1832
1833 return startOffset;
1834 };
1835
1836 if( m_reader.PeekInt3() != 0 )
1837 {
1838 const uint8_t* data = m_reader.GetData();
1839 size_t limit = patternCeiling;
1840
1841 if( startOffset + 63 <= limit && data[startOffset] == 0 && data[startOffset + 1] == 0 )
1842 {
1843 size_t tailEnd = startOffset + 63;
1844
1845 if( tailEnd == aCompEnd || tailEnd == m_busSectionOffset || isComponentHeaderAt( tailEnd ) )
1846 {
1847 m_reader.SetOffset( tailEnd );
1848 return;
1849 }
1850 }
1851
1852 for( size_t pos = startOffset; pos + 2 <= limit; pos++ )
1853 {
1854 int charCount = ( data[pos] << 8 ) | data[pos + 1];
1855
1856 if( charCount < 0 || charCount > 512 )
1857 continue;
1858
1859 size_t strEnd = pos + 2 + static_cast<size_t>( charCount ) * 2;
1860
1861 if( strEnd > limit )
1862 continue;
1863
1864 bool valid = true;
1865
1866 for( size_t i = pos + 2; i < strEnd; i += 2 )
1867 {
1868 if( data[i] != 0x00 || data[i + 1] < 0x20 || data[i + 1] > 0x7E )
1869 {
1870 valid = false;
1871 break;
1872 }
1873 }
1874
1875 if( !valid )
1876 continue;
1877
1878 wxMBConvUTF16BE conv;
1879 wxString modelName( reinterpret_cast<const char*>( data + pos + 2 ), conv,
1880 static_cast<size_t>( charCount ) * 2 );
1881 wxString lowerModel = modelName.Lower();
1882
1883 if( !modelName.IsEmpty() && !lowerModel.EndsWith( wxT( ".step" ) )
1884 && !lowerModel.EndsWith( wxT( ".wrl" ) ) )
1885 {
1886 continue;
1887 }
1888
1889 for( size_t tailSize : { static_cast<size_t>( 61 ), static_cast<size_t>( 28 ) } )
1890 {
1891 size_t tailEnd = strEnd + tailSize;
1892
1893 if( tailEnd <= limit
1894 && ( tailEnd == aCompEnd || tailEnd == m_busSectionOffset || isComponentHeaderAt( tailEnd ) ) )
1895 {
1896 m_reader.SetOffset( tailEnd );
1897 return;
1898 }
1899 }
1900 }
1901
1902 // The model string is not always stored in a form this scan recognises
1903 // (some v41 records use little-endian paths or omit the model entirely).
1904 // The next component header is reliably known, so land there: it is the
1905 // record's true end and keeps the sequential walk deterministic without
1906 // resorting to the global boundary scan.
1907 m_reader.SetOffset( landingForCeiling() );
1908 return;
1909 }
1910
1911 auto readCount = [&]( const char* aName, int aMax ) -> int
1912 {
1913 int count = m_reader.ReadInt3();
1914
1915 if( count < 0 || count > aMax )
1916 {
1917 THROW_IO_ERRORF( _( "DipTrace import: invalid embedded pattern %s count %d at offset 0x%06zX." ),
1918 wxString::FromUTF8( aName ), count, startOffset );
1919 }
1920
1921 return count;
1922 };
1923
1924 auto isValidUtf16StringAt = [&]( size_t aOffset ) -> bool
1925 {
1926 const uint8_t* data = m_reader.GetData();
1927 size_t size = std::min( patternCeiling, m_reader.GetFileSize() );
1928
1929 if( aOffset + 2 > size )
1930 return false;
1931
1932 int charCount = ( data[aOffset] << 8 ) | data[aOffset + 1];
1933
1934 if( charCount < 0 || charCount > 512 )
1935 return false;
1936
1937 size_t stringEnd = aOffset + 2 + static_cast<size_t>( charCount ) * 2;
1938
1939 if( stringEnd > size )
1940 return false;
1941
1942 for( size_t i = aOffset + 2; i < stringEnd; i += 2 )
1943 {
1944 if( data[i] != 0x00 || data[i + 1] < 0x20 || data[i + 1] > 0x7E )
1945 return false;
1946 }
1947
1948 return true;
1949 };
1950
1951 // Pre-name header: legacy preamble plus modern drill fields. Some
1952 // v41 records store the pattern name immediately after the extra drill
1953 // field; others add an int3 tail before the name.
1954 m_reader.ReadInt3();
1955 m_reader.ReadInt3();
1956 m_reader.ReadInt4();
1957 m_reader.ReadInt4();
1958 m_reader.ReadByte();
1959 m_reader.ReadInt4();
1960 m_reader.ReadByte();
1961 m_reader.ReadInt3();
1962 m_reader.ReadInt4();
1963 m_reader.ReadInt4();
1964 m_reader.ReadInt4();
1965 m_reader.ReadInt4();
1966 m_reader.ReadInt3();
1967 m_reader.ReadByte();
1968 m_reader.ReadInt4();
1969 m_reader.ReadInt4();
1970
1971 if( !isValidUtf16StringAt( m_reader.GetOffset() ) )
1972 m_reader.ReadInt3();
1973
1974 aComp.patternName = m_reader.ReadString();
1975
1976 // Post-name fields: origin/options followed by the pad record count.
1977 m_reader.ReadInt4();
1978 m_reader.ReadInt4();
1979 m_reader.ReadInt4();
1980 m_reader.ReadInt4();
1981 m_reader.ReadByte();
1982
1983 // The counted alias/pad/drawing fields are not yet decoded field-by-field for
1984 // every pattern variant (notably connectors and some v41 records). Walk them
1985 // best-effort only to detect the empty-pattern footer; any desync is recovered
1986 // by the authoritative 3D-model tail scanner below, which re-locates the record
1987 // end structurally from the pattern start.
1988 try
1989 {
1990 int aliasCount = readCount( "alias", 100 );
1991
1992 for( int i = 0; i < aliasCount; i++ )
1993 {
1994 m_reader.ReadString();
1995 m_reader.ReadString();
1996 }
1997
1998 m_reader.ReadInt3();
1999
2000 if( aliasCount > 0 )
2001 m_reader.ReadInt3();
2002
2003 int padCount = readCount( "pad", 500 );
2004
2005 if( padCount == 0 )
2006 {
2007 // Empty modern patterns use the same short zero footer as legacy
2008 // empty patterns.
2009 m_reader.ReadInt3();
2010 m_reader.ReadInt3();
2011 m_reader.ReadInt3();
2012 return;
2013 }
2014
2015 auto readModernPadRecord = [&]()
2016 {
2017 m_reader.ReadByte();
2018 m_reader.ReadInt3();
2019 m_reader.ReadInt4();
2020 m_reader.ReadInt4();
2021 m_reader.ReadString();
2022 m_reader.ReadString();
2023 m_reader.ReadInt4();
2024 m_reader.ReadInt4();
2025 m_reader.ReadInt4();
2026 m_reader.ReadInt4();
2027 m_reader.ReadInt3();
2028 };
2029
2030 auto canReadModernPadRecordAt = [&]( size_t aOffset ) -> bool
2031 {
2032 size_t save = m_reader.GetOffset();
2033 bool ok = false;
2034
2035 try
2036 {
2037 m_reader.SetOffset( aOffset );
2038 readModernPadRecord();
2039 ok = m_reader.GetOffset() <= patternCeiling;
2040 }
2041 catch( const std::exception& )
2042 {
2043 ok = false;
2044 }
2045
2046 m_reader.SetOffset( save );
2047 return ok;
2048 };
2049
2050 for( int i = 0; i < padCount; i++ )
2051 {
2052 if( m_reader.GetOffset() >= patternCeiling || !canReadModernPadRecordAt( m_reader.GetOffset() ) )
2053 {
2054 break;
2055 }
2056
2057 readModernPadRecord();
2058
2059 if( i + 1 < padCount )
2060 {
2061 size_t tailStart = m_reader.GetOffset();
2062
2063 if( tailStart + 22 <= patternCeiling && canReadModernPadRecordAt( tailStart + 22 ) )
2064 {
2065 m_reader.Skip( 22 );
2066 }
2067 }
2068 }
2069 }
2070 catch( const std::exception& )
2071 {
2072 // Best-effort walk only; the model-tail scan below recovers the end.
2073 }
2074
2075 m_reader.SetOffset( startOffset );
2076
2077 auto readInt3At = [&]( size_t aOffset ) -> int
2078 {
2079 const uint8_t* data = m_reader.GetData();
2080
2081 return ( ( data[aOffset] << 16 ) | ( data[aOffset + 1] << 8 ) | data[aOffset + 2] ) - INT3_BIAS;
2082 };
2083
2084 auto validUtf16StringAt = [&]( size_t aOffset, size_t aLimit, size_t& aEnd ) -> bool
2085 {
2086 const uint8_t* data = m_reader.GetData();
2087
2088 if( aOffset + 2 > aLimit )
2089 return false;
2090
2091 int charCount = ( data[aOffset] << 8 ) | data[aOffset + 1];
2092
2093 if( charCount < 0 || charCount > 512 )
2094 return false;
2095
2096 size_t strEnd = aOffset + 2 + static_cast<size_t>( charCount ) * 2;
2097
2098 if( strEnd > aLimit )
2099 return false;
2100
2101 for( size_t i = aOffset + 2; i < strEnd; i += 2 )
2102 {
2103 if( data[i] != 0x00 )
2104 return false;
2105
2106 if( charCount > 0 && ( data[i + 1] < 0x20 || data[i + 1] > 0x7E ) )
2107 return false;
2108 }
2109
2110 aEnd = strEnd;
2111 return true;
2112 };
2113
2114 size_t modelSectionStart = std::string::npos;
2115 size_t modelStringStart = std::string::npos;
2116 size_t patternEnd = std::string::npos;
2117 size_t limit = std::min( patternCeiling, m_reader.GetFileSize() );
2118
2119 for( size_t pos = m_reader.GetOffset(); pos + 3 <= limit; pos++ )
2120 {
2121 int modelPlacementCount = readInt3At( pos );
2122
2123 if( modelPlacementCount < 0 || modelPlacementCount > 1000 )
2124 continue;
2125
2126 size_t afterPlacements = pos + 3 + static_cast<size_t>( modelPlacementCount ) * 18;
2127
2128 if( afterPlacements + 3 > limit || readInt3At( afterPlacements ) != 0 )
2129 continue;
2130
2131 size_t strStart = afterPlacements + 3;
2132 size_t strEnd = 0;
2133
2134 if( !validUtf16StringAt( strStart, limit, strEnd ) )
2135 continue;
2136
2137 for( size_t tailSize : { static_cast<size_t>( 61 ), static_cast<size_t>( 28 ) } )
2138 {
2139 size_t tailEnd = strEnd + tailSize;
2140
2141 if( tailEnd > limit )
2142 continue;
2143
2144 if( tailEnd == aCompEnd || tailEnd == m_busSectionOffset || isComponentHeaderAt( tailEnd ) )
2145 {
2146 modelSectionStart = pos;
2147 modelStringStart = strStart;
2148 patternEnd = tailEnd;
2149 break;
2150 }
2151 }
2152
2153 if( modelSectionStart != std::string::npos )
2154 break;
2155 }
2156
2157 if( modelSectionStart == std::string::npos )
2158 {
2159 // No decodable model tail. The next component header is reliably known,
2160 // so land there to keep the sequential walk deterministic.
2161 m_reader.SetOffset( landingForCeiling() );
2162 return;
2163 }
2164
2165 m_reader.SetOffset( modelStringStart );
2166 m_reader.ReadString();
2167 m_reader.SetOffset( patternEnd );
2168 }
2169}
2170
2171
2173{
2174 m_reader.ReadInt4();
2175 m_reader.ReadInt4();
2176 m_reader.ReadByte();
2177 m_reader.ReadByte();
2178
2179 int busCount = m_reader.ReadInt3();
2180
2181 if( busCount < 0 || busCount > 1000 )
2182 THROW_IO_ERRORF( _( "DipTrace import: invalid bus count %d." ), busCount );
2183
2184 for( int i = 0; i < busCount; i++ )
2185 {
2186 try
2187 {
2188 DCH_BUS_ENTRY entry;
2189
2190 m_reader.ReadByte();
2191 m_reader.ReadByte();
2192 m_reader.ReadByte();
2193
2194 entry.coordX = m_reader.ReadInt4();
2195 entry.coordY = m_reader.ReadInt4();
2196 entry.sheetIndex = m_reader.ReadInt3();
2197 entry.busType = m_reader.ReadInt3();
2198 entry.instanceId = m_reader.ReadInt3();
2199 entry.signalCount = m_reader.ReadInt3();
2200
2201 int terminator = m_reader.ReadInt3();
2202
2203 if( terminator != -1 )
2204 THROW_IO_ERRORF( _( "DipTrace import: bus entry %d has unexpected terminator %d." ), i, terminator );
2205
2206 entry.name = m_reader.ReadString();
2207 m_reader.ReadByte();
2208
2209 m_buses.push_back( entry );
2210 }
2211 catch( const IO_ERROR& )
2212 {
2213 throw;
2214 }
2215 catch( const std::exception& e )
2216 {
2217 THROW_IO_ERRORF( _( "DipTrace import: failed to parse bus entry %d: %s" ),
2218 i, wxString::FromUTF8( e.what() ) );
2219 }
2220 }
2221}
2222
2223
2225{
2226 const uint8_t* data = m_reader.GetData();
2227 size_t fileSize = m_reader.GetFileSize();
2228 size_t searchEnd = m_tailOffset > 0 ? m_tailOffset : fileSize;
2229 size_t searchStart = m_reader.GetOffset();
2230
2231 if( searchStart >= searchEnd )
2232 return;
2233
2234 // Written as off + 5 < searchEnd (not searchEnd - 5) so a tiny searchEnd cannot underflow the
2235 // size_t bound; the body reads data[off]..data[off+2].
2236 for( size_t off = searchStart; off + 5 < searchEnd; off++ )
2237 {
2238 if( data[off] != 0x0F || data[off + 1] != 0x42 || data[off + 2] != 0x3F )
2239 continue;
2240
2241 size_t strOff = off + 3;
2242
2243 if( m_version < V31_CUTOVER )
2244 {
2245 if( strOff + 3 > searchEnd )
2246 continue;
2247
2248 int n = ( ( data[strOff] << 16 ) | ( data[strOff + 1] << 8 ) | data[strOff + 2] ) - INT3_BIAS;
2249
2250 if( n < 1 || n > 200 || strOff + 3 + (size_t) n > fileSize )
2251 continue;
2252
2253 bool valid = true;
2254
2255 for( int i = 0; i < n; i++ )
2256 {
2257 uint8_t c = data[strOff + 3 + i];
2258
2259 if( c < 0x20 || c > 0x7E )
2260 {
2261 valid = false;
2262 break;
2263 }
2264 }
2265
2266 if( !valid )
2267 continue;
2268
2269 wxString name = wxString::From8BitData( reinterpret_cast<const char*>( data + strOff + 3 ), n );
2270 size_t afterStr = strOff + 3 + n;
2271
2272 DCH_NET_ENTRY entry;
2273 entry.name = name;
2274
2275 if( afterStr + 11 <= fileSize )
2276 {
2277 entry.coordX = ReadInt4At( data, afterStr );
2278 entry.coordY = ReadInt4At( data, afterStr + 4 );
2279 entry.field1 = ( ( data[afterStr + 8] << 16 ) | ( data[afterStr + 9] << 8 ) | data[afterStr + 10] )
2280 - INT3_BIAS;
2281 }
2282
2283 m_nets.push_back( entry );
2284 }
2285 else
2286 {
2287 if( strOff + 2 > searchEnd )
2288 continue;
2289
2290 int n = ( data[strOff] << 8 ) | data[strOff + 1];
2291
2292 if( n < 1 || n > 200 || strOff + 2 + (size_t) ( n * 2 ) > fileSize )
2293 continue;
2294
2295 bool valid = true;
2296
2297 for( int i = 0; i < n; i++ )
2298 {
2299 uint8_t hi = data[strOff + 2 + static_cast<size_t>( i ) * 2];
2300 uint8_t lo = data[strOff + 2 + static_cast<size_t>( i ) * 2 + 1];
2301
2302 if( hi != 0 || lo < 0x20 || lo > 0x7E )
2303 {
2304 valid = false;
2305 break;
2306 }
2307 }
2308
2309 if( !valid )
2310 continue;
2311
2312 wxMBConvUTF16BE conv;
2313 wxString name( reinterpret_cast<const char*>( data + strOff + 2 ), conv, static_cast<size_t>( n ) * 2 );
2314 size_t afterStr = strOff + 2 + static_cast<size_t>( n ) * 2;
2315
2316 DCH_NET_ENTRY entry;
2317 entry.name = name;
2318
2319 if( afterStr + 11 <= fileSize )
2320 {
2321 entry.coordX = ReadInt4At( data, afterStr );
2322 entry.coordY = ReadInt4At( data, afterStr + 4 );
2323 entry.field1 = ( ( data[afterStr + 8] << 16 ) | ( data[afterStr + 9] << 8 ) | data[afterStr + 10] )
2324 - INT3_BIAS;
2325 }
2326
2327 m_nets.push_back( entry );
2328 }
2329 }
2330}
2331
2332
2333int SCH_PARSER::pinOrientationFromOffset( int aOffsetX, int aOffsetY, int aHalfWidth, int aHalfHeight )
2334{
2335 // The stored coordinate is the pin's body-edge anchor, offset from the symbol body center; the
2336 // pin extends outward (away from center) to its connection point where wires attach. KiCad
2337 // stores m_position at the connection point with the body root at m_position + length toward
2338 // the orientation, so the body always sits between the connection point and the center. A pin
2339 // on the right edge therefore reads as PIN_LEFT (body to the left of its connection point), and
2340 // an above-center pin reads as PIN_DOWN (body below its connection point in KiCad Y-down). A
2341 // pin exactly on center defaults to left.
2342 if( aOffsetX == 0 && aOffsetY == 0 )
2343 return 2; // PIN_LEFT
2344
2345 // Pick the edge the pin sits on, normalized by the body half-extents. A tall symbol's left-edge
2346 // pin can be farther from center in Y than in X, so the raw dominant axis would wrongly read it
2347 // as a top or bottom pin; scaling each offset by the opposite half-extent compares which edge
2348 // the pin actually reaches.
2349 int64_t halfW = aHalfWidth > 0 ? aHalfWidth : 1;
2350 int64_t halfH = aHalfHeight > 0 ? aHalfHeight : 1;
2351
2352 if( std::abs( static_cast<int64_t>( aOffsetX ) ) * halfH >= std::abs( static_cast<int64_t>( aOffsetY ) ) * halfW )
2353 {
2354 return ( aOffsetX >= 0 ) ? 2 : 0; // right edge -> PIN_LEFT, left edge -> PIN_RIGHT
2355 }
2356
2357 return ( aOffsetY >= 0 ) ? 1 : 3; // KiCad Y down: below center -> PIN_UP, above -> PIN_DOWN
2358}
2359
2360
2362{
2363 if( aSheetIndex < 0 )
2364 aSheetIndex = 0;
2365
2366 if( aSheetIndex == 0 || m_numSheets <= 1 )
2367 return m_rootSheet->GetScreen();
2368
2369 while( (int) m_sheets.size() <= aSheetIndex )
2370 m_sheets.push_back( nullptr );
2371
2372 if( m_sheets[aSheetIndex] )
2373 return m_sheets[aSheetIndex]->GetScreen();
2374
2375 wxString sheetName;
2376
2377 if( aSheetIndex < (int) m_sheetDefs.size() )
2378 sheetName = m_sheetDefs[aSheetIndex].name;
2379 else
2380 sheetName = wxString::Format( wxT( "Sheet%d" ), aSheetIndex + 1 );
2381
2382 int col = ( aSheetIndex - 1 ) % 4;
2383 int row = ( aSheetIndex - 1 ) / 4;
2384 VECTOR2I pos( 2540000 + col * 50800000, 2540000 + row * 50800000 );
2385 VECTOR2I size( 40640000, 30480000 );
2386
2387 SCH_SHEET* newSheet = new SCH_SHEET( m_schematic ? &m_schematic->Root() : m_rootSheet, pos, size );
2388 SCH_SCREEN* newScreen = new SCH_SCREEN( m_schematic );
2389
2390 wxFileName fn( m_fileName );
2391 fn.SetName( fn.GetName() + wxString::Format( wxT( "_%d" ), aSheetIndex ) );
2393
2394 newScreen->SetFileName( fn.GetFullPath() );
2395 newSheet->SetScreen( newScreen );
2396 newSheet->SetFileName( fn.GetFullName() );
2397 newSheet->SetName( sheetName );
2398
2399 if( m_schematic && m_rootSheet == &m_schematic->Root() )
2400 m_schematic->AddTopLevelSheet( newSheet );
2401
2402 m_sheets[aSheetIndex] = newSheet;
2403
2404 return newScreen;
2405}
2406
2407
2409{
2410 for( size_t i = 0; i < m_sheets.size(); ++i )
2411 {
2412 SCH_SHEET* sheet = m_sheets[i];
2413
2414 if( !sheet )
2415 continue;
2416
2417 if( sheet->IsVirtualRootSheet() )
2418 continue;
2419
2420 wxString pageNumber = wxString::Format( wxT( "%zu" ), i + 1 );
2422 path.push_back( sheet );
2423 path.SetPageNumber( pageNumber );
2424
2425 if( sheet->GetScreen() )
2426 sheet->GetScreen()->SetPageNumber( pageNumber );
2427 }
2428}
2429
2430
2432{
2433 if( !m_schematic || !m_rootSheet || m_rootSheet == &m_schematic->Root() )
2434 {
2436 return;
2437 }
2438
2439 std::vector<SCH_SHEET*> topLevelSheets;
2440 topLevelSheets.reserve( m_sheets.size() );
2441
2442 for( SCH_SHEET* sheet : m_sheets )
2443 {
2444 if( sheet )
2445 topLevelSheets.push_back( sheet );
2446 }
2447
2448 if( !topLevelSheets.empty() )
2449 m_schematic->SetTopLevelSheets( topLevelSheets );
2450
2452}
2453
2454
2455wxString SCH_PARSER::normalizedRefdes( const DCH_COMPONENT& aComp ) const
2456{
2457 wxString refdes = aComp.refdes;
2458
2459 if( !aComp.isMultiPart )
2460 return refdes;
2461
2462 int dot = refdes.Find( wxT( '.' ), true );
2463
2464 if( dot <= 0 || dot >= static_cast<int>( refdes.length() ) - 1 )
2465 return refdes;
2466
2467 long suffix = 0;
2468
2469 if( refdes.Mid( dot + 1 ).ToLong( &suffix ) && suffix >= 1 )
2470 return refdes.Left( dot );
2471
2472 return refdes;
2473}
2474
2475
2477{
2478 wxString base = aComp.isMultiPart ? normalizedRefdes( aComp ) : wxString();
2479
2480 if( base.IsEmpty() )
2481 base = aComp.compName;
2482
2483 if( base.IsEmpty() )
2484 base = normalizedRefdes( aComp );
2485
2486 if( base.IsEmpty() )
2487 base = wxT( "Unknown" );
2488
2489 if( !aComp.isMultiPart && aComp.rotationE4 != 0 )
2490 base += wxString::Format( wxT( "_r%d" ), aComp.rotationE4 );
2491
2492 return LIB_ID::FixIllegalChars( base, true ).wx_str();
2493}
2494
2495
2496static bool libSymbolHasUnit( const LIB_SYMBOL* aLibSymbol, int aUnit )
2497{
2498 for( const SCH_ITEM& drawItem : aLibSymbol->GetDrawItems() )
2499 {
2500 if( drawItem.GetUnit() == aUnit )
2501 return true;
2502 }
2503
2504 return false;
2505}
2506
2507
2508static int dipTraceMm( double aMm )
2509{
2510 return static_cast<int>( std::lround( aMm * 30000.0 ) );
2511}
2512
2513
2514static VECTOR2I dipTraceShapePoint( double aXmm, double aYmm )
2515{
2516 return VECTOR2I( dipTraceMm( aXmm ), dipTraceMm( -aYmm ) );
2517}
2518
2519
2520static DCH_SHAPE makeDipTraceShape( int aKindCode, double aLineWidthMm, std::initializer_list<VECTOR2I> aPoints )
2521{
2522 DCH_SHAPE shape;
2523 shape.kindCode = aKindCode;
2524 shape.kindFlag = 0;
2525 shape.lineWidth = dipTraceMm( aLineWidthMm );
2526 shape.fontX = -20000;
2527 shape.fontY = 10000;
2528 shape.points.assign( aPoints.begin(), aPoints.end() );
2529 return shape;
2530}
2531
2532
2533static bool needsStandardThtLedShape( const DCH_COMPONENT& aComp )
2534{
2535 wxString libPath = aComp.libPath.Lower();
2536 wxString compName = aComp.compName.Lower();
2537
2538 // These library-backed placements carry pins but no local shape stream; synthesize the
2539 // component-style graphics observed in the DipTrace XML oracle.
2540 return aComp.shapes.empty() && aComp.pins.size() == 2 && libPath.Contains( wxT( "opto_emitters_led_tht" ) )
2541 && compName.StartsWith( wxT( "led-3mm round" ) );
2542}
2543
2544
2545static std::vector<DCH_SHAPE> standardThtLedShapes()
2546{
2547 return {
2548 makeDipTraceShape( 6, 0.254, { dipTraceShapePoint( -3.175, 1.851 ), dipTraceShapePoint( 3.0416, -4.3656 ) } ),
2549 makeDipTraceShape( 1, 0.25, { dipTraceShapePoint( 1.5747, -3.0956 ), dipTraceShapePoint( 1.5747, 0.7144 ) } ),
2550 makeDipTraceShape( 3, 0.25, { dipTraceShapePoint( 1.2954, 2.486 ), dipTraceShapePoint( 2.54, 4.3656 ) } ),
2551 makeDipTraceShape( 3, 0.25, { dipTraceShapePoint( 2.2479, 1.851 ), dipTraceShapePoint( 3.4925, 3.7306 ) } ),
2552 makeDipTraceShape( 1, 0.25, { dipTraceShapePoint( 1.5747, -1.1906 ), dipTraceShapePoint( 3.81, -1.1906 ) } ),
2553 makeDipTraceShape( 1, 0.25, { dipTraceShapePoint( -3.81, -1.1906 ), dipTraceShapePoint( -1.6003, -1.1906 ) } ),
2554 makeDipTraceShape( 8, 0.25,
2555 { dipTraceShapePoint( -1.6003, 0.7144 ), dipTraceShapePoint( 1.5747, -1.1906 ),
2556 dipTraceShapePoint( -1.6003, -3.0956 ) } ),
2557 makeDipTraceShape( 9, 0.25,
2558 { dipTraceShapePoint( 1.5747, -1.1906 ), dipTraceShapePoint( -1.6003, 0.7144 ),
2559 dipTraceShapePoint( -1.6003, -3.0956 ), dipTraceShapePoint( 1.5747, -1.1906 ) } ),
2560 };
2561}
2562
2563
2564void SCH_PARSER::populateLibSymbolUnit( LIB_SYMBOL* aLibSymbol, const DCH_COMPONENT& aComp, int aUnit )
2565{
2566 if( !aLibSymbol || libSymbolHasUnit( aLibSymbol, aUnit ) )
2567 return;
2568
2569 bool isPower = aComp.refdes.StartsWith( wxT( "NetPort" ) );
2570
2571 for( const DCH_PIN& dchPin : aComp.pins )
2572 {
2573 auto pin = std::make_unique<SCH_PIN>( aLibSymbol );
2574
2575 pin->SetName( dchPin.name.IsEmpty() ? wxString( wxT( "~" ) ) : dchPin.name );
2576 pin->SetNumber( dchPin.number.IsEmpty() ? wxString( wxT( "1" ) ) : dchPin.number );
2577
2578 // The stored coordinate is the pin's body-edge anchor relative to the symbol center; the
2579 // pin extends outward from there by its length to the connection point where wires attach.
2580 VECTOR2I anchor( toKiCadCoordX( dchPin.x ), toKiCadCoordY( dchPin.y ) );
2581 int len = toKiCadSize( dchPin.length );
2582
2583 // The header bbox third and fourth values are the body width and height; halving them gives
2584 // the edge distances the pin offset is measured against.
2585 int halfW = toKiCadSize( aComp.bboxX2 ) / 2;
2586 int halfH = toKiCadSize( aComp.bboxY2 ) / 2;
2587 int orient = pinOrientationFromOffset( anchor.x, anchor.y, halfW, halfH );
2588
2589 // Move the anchor outward by the length to the KiCad connection point (m_position). The
2590 // orientation then puts the body root back at the anchor.
2591 VECTOR2I connection = anchor;
2592
2593 switch( orient )
2594 {
2595 case 0: connection.x -= len; break; // PIN_RIGHT: body right, connection left of anchor
2596 case 2: connection.x += len; break; // PIN_LEFT: body left, connection right of anchor
2597 case 1: connection.y += len; break; // PIN_UP: body up (Y-), connection below anchor
2598 case 3: connection.y -= len; break; // PIN_DOWN: body down (Y+), connection above anchor
2599 }
2600
2601 pin->SetPosition( connection );
2602 pin->SetLength( len );
2603
2604 switch( orient )
2605 {
2606 case 0: pin->SetOrientation( PIN_ORIENTATION::PIN_RIGHT ); break;
2607 case 1: pin->SetOrientation( PIN_ORIENTATION::PIN_UP ); break;
2608 case 2: pin->SetOrientation( PIN_ORIENTATION::PIN_LEFT ); break;
2609 case 3: pin->SetOrientation( PIN_ORIENTATION::PIN_DOWN ); break;
2610 }
2611
2613 pin->SetUnit( aUnit );
2614 aLibSymbol->AddDrawItem( pin.release() );
2615 }
2616
2617 std::vector<DCH_SHAPE> fallbackShapes;
2618 const std::vector<DCH_SHAPE>* componentShapes = &aComp.shapes;
2619
2620 if( needsStandardThtLedShape( aComp ) )
2621 {
2622 fallbackShapes = standardThtLedShapes();
2623 componentShapes = &fallbackShapes;
2624 }
2625
2626 for( const DCH_SHAPE& dchShape : *componentShapes )
2627 {
2628 if( dchShape.points.size() < 2 )
2629 continue;
2630
2631 // A non-positive stored width maps to 0, which KiCad renders at the default symbol line
2632 // width, matching how pins (with no stored width) are drawn.
2633 int width = toKiCadSize( dchShape.lineWidth );
2634
2635 // DipTrace marks a rectangle with leading kind code 4; its two points are opposite corners.
2636 // This is the stored type, so a diagonal conductor (a two point line on a US resistor) is
2637 // never mistaken for a rectangle and an IC body box is always a rectangle.
2638 bool isRectangle = dchShape.points.size() == 2 && dchShape.kindCode == 4 && dchShape.kindFlag == 0;
2639
2640 if( isRectangle )
2641 {
2642 auto rect = std::make_unique<SCH_SHAPE>( SHAPE_T::RECTANGLE, LAYER_DEVICE, 0, FILL_T::NO_FILL );
2643 rect->SetParent( aLibSymbol );
2644 rect->SetPosition(
2645 VECTOR2I( toKiCadCoordX( dchShape.points[0].x ), toKiCadCoordY( dchShape.points[0].y ) ) );
2646 rect->SetEnd( VECTOR2I( toKiCadCoordX( dchShape.points[1].x ), toKiCadCoordY( dchShape.points[1].y ) ) );
2647 rect->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
2648 rect->SetUnit( aUnit );
2649 aLibSymbol->AddDrawItem( rect.release() );
2650
2651 continue;
2652 }
2653
2654 // DipTrace marks a circle or ellipse (an "obround") with leading kind code 6; its two points
2655 // are opposite corners of the bounding box. A transistor's enclosing circle is stored this
2656 // way; without this branch it falls through to a two point polyline and draws as a slash.
2657 // KiCad has no ellipse, so a square box is a circle and a rectangular one a circle of the
2658 // average radius.
2659 bool isEllipse = dchShape.points.size() == 2 && dchShape.kindCode == 6 && dchShape.kindFlag == 0;
2660
2661 if( isEllipse )
2662 {
2663 VECTOR2I p0( toKiCadCoordX( dchShape.points[0].x ), toKiCadCoordY( dchShape.points[0].y ) );
2664 VECTOR2I p1( toKiCadCoordX( dchShape.points[1].x ), toKiCadCoordY( dchShape.points[1].y ) );
2665 VECTOR2I center( ( p0.x + p1.x ) / 2, ( p0.y + p1.y ) / 2 );
2666 int radius = ( std::abs( p1.x - p0.x ) + std::abs( p1.y - p0.y ) ) / 4;
2667
2668 auto circle = std::make_unique<SCH_SHAPE>( SHAPE_T::CIRCLE, LAYER_DEVICE, 0, FILL_T::NO_FILL );
2669 circle->SetParent( aLibSymbol );
2670 circle->SetCenter( center );
2671 circle->SetEnd( VECTOR2I( center.x + radius, center.y ) );
2672 circle->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
2673 circle->SetUnit( aUnit );
2674 aLibSymbol->AddDrawItem( circle.release() );
2675
2676 continue;
2677 }
2678
2679 // DipTrace marks an arc with leading kind code 2; it stores three points (start, a point on
2680 // the arc, end). An inductor's winding humps are arcs stored this way; without this branch
2681 // they fall through to a straight two-segment polyline instead of a curve.
2682 bool isArc = dchShape.points.size() == 3 && dchShape.kindCode == 2 && dchShape.kindFlag == 0;
2683
2684 if( isArc )
2685 {
2686 VECTOR2I start( toKiCadCoordX( dchShape.points[0].x ), toKiCadCoordY( dchShape.points[0].y ) );
2687 VECTOR2I mid( toKiCadCoordX( dchShape.points[1].x ), toKiCadCoordY( dchShape.points[1].y ) );
2688 VECTOR2I end( toKiCadCoordX( dchShape.points[2].x ), toKiCadCoordY( dchShape.points[2].y ) );
2689
2690 auto arc = std::make_unique<SCH_SHAPE>( SHAPE_T::ARC, LAYER_DEVICE, 0, FILL_T::NO_FILL );
2691 arc->SetParent( aLibSymbol );
2692 arc->SetArcGeometry( start, mid, end );
2693 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
2694 arc->SetUnit( aUnit );
2695 aLibSymbol->AddDrawItem( arc.release() );
2696
2697 continue;
2698 }
2699
2700 bool isFilledPolygon = dchShape.points.size() >= 3 && dchShape.kindCode == 8 && dchShape.kindFlag == 0;
2701
2702 auto poly = std::make_unique<SCH_SHAPE>( SHAPE_T::POLY, LAYER_DEVICE, 0,
2703 isFilledPolygon ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL );
2704 poly->SetParent( aLibSymbol );
2705
2706 for( const VECTOR2I& pt : dchShape.points )
2707 poly->AddPoint( VECTOR2I( toKiCadCoordX( pt.x ), toKiCadCoordY( pt.y ) ) );
2708
2709 poly->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
2710 poly->SetUnit( aUnit );
2711 aLibSymbol->AddDrawItem( poly.release() );
2712
2713 if( dchShape.kindCode == 3 && dchShape.kindFlag == 0 && dchShape.points.size() == 2 )
2714 {
2715 VECTOR2I start( toKiCadCoordX( dchShape.points[0].x ), toKiCadCoordY( dchShape.points[0].y ) );
2716 VECTOR2I end( toKiCadCoordX( dchShape.points[1].x ), toKiCadCoordY( dchShape.points[1].y ) );
2717 double dx = static_cast<double>( end.x - start.x );
2718 double dy = static_cast<double>( end.y - start.y );
2719 double len = std::sqrt( dx * dx + dy * dy );
2720
2721 if( len > 0.0 )
2722 {
2723 double unitX = dx / len;
2724 double unitY = dy / len;
2725 double arrowLength =
2726 std::min( len / 2.0, static_cast<double>( std::max( width * 4, schIUScale.MilsToIU( 35 ) ) ) );
2727 double halfWidth = arrowLength / 2.0;
2728 double baseX = static_cast<double>( end.x ) - unitX * arrowLength;
2729 double baseY = static_cast<double>( end.y ) - unitY * arrowLength;
2730 double perpX = -unitY;
2731 double perpY = unitX;
2732
2733 auto arrow = std::make_unique<SCH_SHAPE>( SHAPE_T::POLY, LAYER_DEVICE, 0, FILL_T::NO_FILL );
2734 arrow->SetParent( aLibSymbol );
2735 arrow->AddPoint( VECTOR2I( static_cast<int>( std::lround( baseX + perpX * halfWidth ) ),
2736 static_cast<int>( std::lround( baseY + perpY * halfWidth ) ) ) );
2737 arrow->AddPoint( end );
2738 arrow->AddPoint( VECTOR2I( static_cast<int>( std::lround( baseX - perpX * halfWidth ) ),
2739 static_cast<int>( std::lround( baseY - perpY * halfWidth ) ) ) );
2740 arrow->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
2741 arrow->SetUnit( aUnit );
2742 aLibSymbol->AddDrawItem( arrow.release() );
2743 }
2744 }
2745 }
2746}
2747
2748
2750{
2751 wxString symName = componentSymbolName( aComp );
2752
2753 auto it = m_libSymbols.find( symName );
2754
2755 if( it != m_libSymbols.end() )
2756 {
2757 LIB_SYMBOL* existing = it->second.get();
2758
2759 if( aUnit > existing->GetUnitCount() )
2760 existing->SetUnitCount( aUnit, false );
2761
2762 populateLibSymbolUnit( existing, aComp, aUnit );
2763 return existing;
2764 }
2765
2766 auto libSymbol = std::make_unique<LIB_SYMBOL>( symName );
2767 libSymbol->SetUnitCount( aUnit, false );
2768
2769 if( !aComp.patternName.IsEmpty() )
2770 libSymbol->GetFootprintField().SetText( aComp.patternName );
2771
2772 bool isPower = aComp.refdes.StartsWith( wxT( "NetPort" ) );
2773
2774 if( isPower )
2775 libSymbol->SetGlobalPower();
2776
2777 // DipTrace stores pin name visibility as a per-pin flag (the second flag byte after the pin
2778 // name and number). It is uniform across a component's pins in practice, so drive the symbol's
2779 // show-pin-names switch from it: ICs show their pin names, passives keep them hidden.
2780 if( !aComp.pins.empty() )
2781 libSymbol->SetShowPinNames( aComp.pins.front().netFlagB != 0 );
2782
2783 populateLibSymbolUnit( libSymbol.get(), aComp, aUnit );
2784
2785 LIB_SYMBOL* rawPtr = libSymbol.get();
2786 m_libSymbols[symName] = std::move( libSymbol );
2787 return rawPtr;
2788}
2789
2790
2792{
2793 m_wirePointSheets.clear();
2794 m_pointPartSheets.clear();
2795
2796 std::map<int, std::map<int, int>> partSheetVotes; // partId -> sheet -> count
2797
2798 for( const DCH_WIRE& wire : m_wires )
2799 {
2800 int sheetIdx = wire.sheetIndex;
2801
2802 if( sheetIdx < 0 || sheetIdx >= m_numSheets )
2803 sheetIdx = 0;
2804
2805 for( const VECTOR2I& pt : wire.points )
2806 {
2807 VECTOR2I p = applyPageOffset( pt );
2808 m_wirePointSheets[{ p.x, p.y }].insert( sheetIdx );
2809 }
2810
2811 // The two endpoints carry the part each end connects to (object1 at the first point,
2812 // object2 at the last); record them with the sheet for exact part-id recovery.
2813 if( wire.points.size() >= 2 )
2814 {
2815 if( wire.object1 >= 0 )
2816 {
2817 VECTOR2I a = applyPageOffset( wire.points.front() );
2818 m_pointPartSheets[{ a.x, a.y }].emplace_back( wire.object1, sheetIdx );
2819 partSheetVotes[wire.object1][sheetIdx]++;
2820 }
2821
2822 if( wire.object2 >= 0 )
2823 {
2824 VECTOR2I b = applyPageOffset( wire.points.back() );
2825 m_pointPartSheets[{ b.x, b.y }].emplace_back( wire.object2, sheetIdx );
2826 partSheetVotes[wire.object2][sheetIdx]++;
2827 }
2828 }
2829 }
2830
2831 // Resolve each part's sheet as the one most of its wires sit on.
2832 m_partIdSheet.clear();
2833
2834 for( const auto& [partId, sheets] : partSheetVotes )
2835 {
2836 int bestSheet = 0;
2837 int bestCount = -1;
2838
2839 for( const auto& [sheet, count] : sheets )
2840 {
2841 if( count > bestCount )
2842 {
2843 bestCount = count;
2844 bestSheet = sheet;
2845 }
2846 }
2847
2848 m_partIdSheet[partId] = bestSheet;
2849 }
2850}
2851
2852
2853bool SCH_PARSER::isComponentHeaderAt( size_t aOffset ) const
2854{
2855 const uint8_t* data = m_reader.GetData();
2856 size_t fileSize = m_reader.GetFileSize();
2857
2858 if( aOffset + 16 > fileSize )
2859 return false;
2860
2861 // Four leading int4: the placement (centerX, centerY) followed by width/height. Origin is a
2862 // valid placement, so the string header below is the record discriminator.
2863 int bbox[4];
2864
2865 for( int i = 0; i < 4; i++ )
2866 {
2867 size_t p = aOffset + static_cast<size_t>( i ) * 4;
2868 uint32_t raw = ( static_cast<uint32_t>( data[p] ) << 24 ) | ( static_cast<uint32_t>( data[p + 1] ) << 16 )
2869 | ( static_cast<uint32_t>( data[p + 2] ) << 8 ) | data[p + 3];
2870 bbox[i] = static_cast<int>( static_cast<int64_t>( raw ) - INT4_BIAS );
2871
2872 if( std::abs( bbox[i] ) > 50000000 )
2873 return false;
2874 }
2875
2876 // Five header strings (compName, refdes, value, prefix, nameDup). Some valid connector and
2877 // net-port records leave compName empty, so the fixed five-string layout is the discriminator.
2878 size_t p = aOffset + 16;
2879
2880 for( int si = 0; si < 5; si++ )
2881 {
2882 int charCount = 0;
2883 size_t dataStart = 0;
2884 bool ascii = ( m_version < SCHEMATIC_UTF16_STRING_VERSION );
2885
2886 if( ascii )
2887 {
2888 if( p + 3 > fileSize )
2889 return false;
2890
2891 charCount = ( ( data[p] << 16 ) | ( data[p + 1] << 8 ) | data[p + 2] ) - INT3_BIAS;
2892 dataStart = p + 3;
2893 }
2894 else
2895 {
2896 if( p + 2 > fileSize )
2897 return false;
2898
2899 charCount = ( data[p] << 8 ) | data[p + 1];
2900 dataStart = p + 2;
2901 }
2902
2903 if( charCount == 0 )
2904 {
2905 p = dataStart;
2906 continue;
2907 }
2908
2909 if( charCount < 0 || charCount > 64 )
2910 return false;
2911
2912 size_t byteCount = ascii ? static_cast<size_t>( charCount ) : static_cast<size_t>( charCount ) * 2;
2913
2914 if( dataStart + byteCount > fileSize )
2915 return false;
2916
2917 for( int k = 0; k < charCount; k++ )
2918 {
2919 unsigned ch = ascii ? data[dataStart + k]
2920 : ( ( data[dataStart + static_cast<size_t>( k ) * 2] << 8 )
2921 | data[dataStart + static_cast<size_t>( k ) * 2 + 1] );
2922
2923 // Header strings are user text and may be multi-line, so tab, line feed and carriage
2924 // return are valid. Any other control byte signals a misparse rather than a real header.
2925 if( ch < 0x20 && ch != 0x09 && ch != 0x0A && ch != 0x0D )
2926 return false;
2927 }
2928
2929 p = dataStart + byteCount;
2930 }
2931
2932 return true;
2933}
2934
2935
2937{
2938 m_offsetToPartId.clear();
2939
2941 return;
2942
2943 int partId = 0;
2944 size_t off = m_componentSectionStart;
2945
2946 while( off + 20 < m_busSectionOffset )
2947 {
2948 if( isComponentHeaderAt( off ) )
2949 {
2950 m_offsetToPartId[off] = partId++;
2951 off += 16;
2952 }
2953 else
2954 {
2955 off++;
2956 }
2957 }
2958}
2959
2960
2961int SCH_PARSER::resolveSheetTally( const std::map<std::pair<int, int>, int>& aTally )
2962{
2963 if( aTally.empty() )
2964 return -1;
2965
2966 int bestCount = 0;
2967
2968 for( const auto& [ps, count] : aTally )
2969 bestCount = std::max( bestCount, count );
2970
2971 // First choice: a top-tally part id strictly greater than the last assigned (monotonic; the
2972 // file stores components in part-id order, which disambiguates identical duplicate sheets).
2973 for( const auto& [ps, count] : aTally ) // std::map iterates by ascending part id
2974 {
2975 if( count == bestCount && ps.first > m_lastSymbolPartId )
2976 {
2977 m_lastSymbolPartId = ps.first;
2978 return ps.second;
2979 }
2980 }
2981
2982 // Otherwise take any top-tally pair (smallest part id).
2983 for( const auto& [ps, count] : aTally )
2984 {
2985 if( count == bestCount )
2986 {
2987 m_lastSymbolPartId = ps.first;
2988 return ps.second;
2989 }
2990 }
2991
2992 return -1;
2993}
2994
2995
2996int SCH_PARSER::sheetForComponentPins( const std::vector<VECTOR2I>& aConnectionPoints )
2997{
2998 // Tally the (partId, sheet) pairs found at the symbol's connection points. The pair matching
2999 // the most pins is the symbol's part.
3000 std::map<std::pair<int, int>, int> tally;
3001
3002 for( const VECTOR2I& p : aConnectionPoints )
3003 {
3004 auto it = m_pointPartSheets.find( { p.x, p.y } );
3005
3006 if( it == m_pointPartSheets.end() )
3007 continue;
3008
3009 std::set<std::pair<int, int>> seenHere; // count each (part, sheet) once per point
3010
3011 for( const std::pair<int, int>& ps : it->second )
3012 if( seenHere.insert( ps ).second )
3013 tally[ps]++;
3014 }
3015
3016 int sheet = resolveSheetTally( tally );
3017
3018 if( sheet >= 0 )
3019 return sheet;
3020
3021 // No endpoint matched a part; fall back to plain position voting.
3022 return sheetForPositions( aConnectionPoints, -1 );
3023}
3024
3025
3026int SCH_PARSER::sheetForPositions( const std::vector<VECTOR2I>& aPositions, int aFallback ) const
3027{
3028 std::map<int, int> votes;
3029
3030 for( const VECTOR2I& p : aPositions )
3031 {
3032 auto it = m_wirePointSheets.find( { p.x, p.y } );
3033
3034 if( it == m_wirePointSheets.end() )
3035 continue;
3036
3037 for( int sheet : it->second )
3038 votes[sheet]++;
3039 }
3040
3041 if( votes.empty() )
3042 return aFallback;
3043
3044 int bestSheet = aFallback;
3045 int bestVotes = -1;
3046
3047 for( const auto& [sheet, count] : votes )
3048 {
3049 if( count > bestVotes )
3050 {
3051 bestVotes = count;
3052 bestSheet = sheet;
3053 }
3054 }
3055
3056 return bestSheet;
3057}
3058
3059
3060void SCH_PARSER::createSymbolInstance( const DCH_COMPONENT& aComp, SCH_SCREEN* aFallbackScreen )
3061{
3062 if( aComp.refdes.IsEmpty() && aComp.compName.IsEmpty() )
3063 return;
3064
3065 // DipTrace net ports (auto_net_ports library) are connection markers, not real symbols; they
3066 // are imported as global net labels by createNetPortLabels(), so skip them here to avoid
3067 // drawing a redundant symbol on top of the label.
3068 if( aComp.libPath.Contains( wxT( "auto_net_ports" ) ) )
3069 return;
3070
3071 wxString refdes = normalizedRefdes( aComp );
3072 int unit = 1;
3073 bool explicitUnit = false;
3074
3075 if( refdes != aComp.refdes )
3076 {
3077 long suffix = 0;
3078
3079 if( aComp.refdes.Mid( refdes.length() + 1 ).ToLong( &suffix ) && suffix >= 1 )
3080 {
3081 unit = static_cast<int>( suffix ) + 1;
3082 explicitUnit = true;
3083 }
3084 }
3085
3086 if( !refdes.IsEmpty() )
3087 {
3088 auto it = m_refdesUnitMap.find( refdes );
3089
3090 if( explicitUnit )
3091 {
3092 if( it == m_refdesUnitMap.end() || unit > it->second )
3093 m_refdesUnitMap[refdes] = unit;
3094 }
3095 else if( it != m_refdesUnitMap.end() )
3096 {
3097 unit = it->second + 1;
3098 it->second = unit;
3099 }
3100 else
3101 {
3102 m_refdesUnitMap[refdes] = unit;
3103 }
3104 }
3105
3106 LIB_SYMBOL* libSym = getOrCreateLibSymbol( aComp, unit );
3107
3108 if( !libSym )
3109 return;
3110
3111 wxString symName = componentSymbolName( aComp );
3112
3113 LIB_ID libId( getLibName(), symName );
3114
3115 // The header bbox is [centerX, centerY, width, height]; the first pair is the placement point.
3116 // Offset by the page half-size so the origin-centered DipTrace placement lands on the page.
3118
3119 SCH_SYMBOL* symbol = new SCH_SYMBOL( *libSym, libId, &m_schematic->CurrentSheet(), unit, 0, pos );
3120
3121 symbol->SetLibSymbol( new LIB_SYMBOL( *libSym ) );
3122
3123 m_placedSymbolsByLibName[symName].push_back( symbol );
3124
3125 if( !refdes.IsEmpty() )
3126 {
3127 SCH_FIELD* refField = symbol->GetField( FIELD_T::REFERENCE );
3128
3129 if( refField )
3130 refField->SetText( refdes );
3131
3132 // The constructor seeds the instance with the unannotated prefix ("U?"); overwrite it with
3133 // the real reference so the per-sheet instances generated after the hierarchy is built copy
3134 // the annotated value rather than the placeholder.
3135 symbol->SetRef( &m_schematic->CurrentSheet(), refdes );
3136 }
3137
3138 if( !aComp.value.IsEmpty() )
3139 {
3140 SCH_FIELD* valField = symbol->GetField( FIELD_T::VALUE );
3141
3142 if( valField )
3143 valField->SetText( aComp.value );
3144 }
3145
3146 if( !aComp.patternName.IsEmpty() )
3147 symbol->SetFootprintFieldText( aComp.patternName );
3148
3149 if( aComp.refdes.StartsWith( wxT( "NetPort" ) ) && !aComp.compName.IsEmpty() )
3150 {
3151 SCH_FIELD* valField = symbol->GetField( FIELD_T::VALUE );
3152
3153 if( valField )
3154 valField->SetText( aComp.compName );
3155 }
3156
3157 // Import the remaining part data DipTrace stores per placement. These are metadata DipTrace
3158 // keeps hidden ("Common"), so they are added invisibly and surface in the symbol properties
3159 // rather than cluttering the canvas. Net-port pseudo-symbols carry none of this.
3160 if( !aComp.refdes.StartsWith( wxT( "NetPort" ) ) )
3161 {
3162 if( !aComp.datasheet.IsEmpty() )
3163 {
3164 if( SCH_FIELD* dsField = symbol->GetField( FIELD_T::DATASHEET ) )
3165 dsField->SetText( aComp.datasheet );
3166 }
3167
3168 // DipTrace shows ICs by their part name rather than a value; keep it as a field so the
3169 // name (e.g. AD7190BRUZ) is preserved even when KiCad displays the empty value.
3170 if( !aComp.compName.IsEmpty() && !symbol->GetField( wxT( "Name" ) ) )
3171 {
3172 SCH_FIELD nameField( symbol, FIELD_T::USER, wxT( "Name" ) );
3173 nameField.SetText( aComp.compName );
3174 nameField.SetVisible( false );
3175 symbol->AddField( nameField );
3176 }
3177
3178 for( const std::pair<wxString, wxString>& extra : aComp.additionalFields )
3179 {
3180 if( extra.first.IsEmpty() || symbol->GetField( extra.first ) )
3181 continue;
3182
3183 SCH_FIELD userField( symbol, FIELD_T::USER, extra.first );
3184 userField.SetText( extra.second );
3185 userField.SetVisible( false );
3186 symbol->AddField( userField );
3187 }
3188 }
3189
3190 // Position the reference and value fields from the stored per-instance text records. Each record
3191 // carries a field type (2 = reference, 3 = value) and an offset from the symbol origin, in the
3192 // same screen-down coordinate convention as the placement. Records without a position bearing
3193 // type are left for the auto-placement fallback below.
3194 bool refPositioned = false;
3195 bool valuePositioned = false;
3196 VECTOR2I refFieldOffset;
3197 VECTOR2I valueFieldOffset;
3198
3199 for( const DCH_COMPONENT_TEXT& txt : aComp.texts )
3200 {
3201 SCH_FIELD* field = nullptr;
3202
3203 if( txt.type == 2 )
3204 field = symbol->GetField( FIELD_T::REFERENCE );
3205 else if( txt.type == 3 )
3206 field = symbol->GetField( FIELD_T::VALUE );
3207
3208 if( !field )
3209 continue;
3210
3211 // A marking at offset zero is DipTrace's "Common" auto-layout placeholder, not an intended
3212 // position (the .dchxml shows Align="Common" X="0" Y="0" for these). Honoring it stacks the
3213 // reference and value on the symbol origin, so leave such records for the fallback below.
3214 if( txt.coordX == 0 && txt.coordY == 0 )
3215 continue;
3216
3217 VECTOR2I off( toKiCadCoordX( txt.coordX ), toKiCadCoordY( txt.coordY ) );
3218 field->SetPosition( pos + off );
3219
3220 if( txt.type == 2 )
3221 {
3222 refPositioned = true;
3223 refFieldOffset = off;
3224 }
3225 else if( txt.type == 3 )
3226 {
3227 valuePositioned = true;
3228 valueFieldOffset = off;
3229 }
3230 }
3231
3232 // A two-terminal part keeps its reference and value symmetric about the body center, and
3233 // DipTrace stores a record only for the marking it actually placed. The binary confirms the
3234 // missing partner sits at the negated X of the stored one at the same Y (e.g. the cap reference
3235 // at the left edge pairs with the value at the right edge). Mirror it across the origin so the
3236 // two markings do not collapse onto the body.
3237 if( refPositioned != valuePositioned )
3238 {
3239 if( refPositioned )
3240 {
3241 if( SCH_FIELD* valField = symbol->GetField( FIELD_T::VALUE ) )
3242 valField->SetPosition( pos + VECTOR2I( -refFieldOffset.x, refFieldOffset.y ) );
3243 }
3244 else if( SCH_FIELD* refField = symbol->GetField( FIELD_T::REFERENCE ) )
3245 {
3246 refField->SetPosition( pos + VECTOR2I( -valueFieldOffset.x, valueFieldOffset.y ) );
3247 }
3248 }
3249
3250 // When the source stored no field positions (common markings at the symbol origin), the
3251 // reference and value would otherwise stack on top of each other. Offset them above and below
3252 // the symbol body so they do not overlap, matching the source rendering intent. AutoplaceFields
3253 // is avoided here because it depends on the eeschema kiface settings, which the headless import
3254 // path does not guarantee.
3255 if( !refPositioned && !valuePositioned )
3256 {
3257 BOX2I bodyBox = libSym->GetBodyBoundingBox( unit, 0, false, false );
3258 int margin = schIUScale.MilsToIU( 40 );
3259
3260 // DipTrace bakes the placement rotation into the stored geometry rather than recording an
3261 // angle, so a rotated symbol is detectable only by its body being taller than it is wide.
3262 // Stacking the reference above and the value below works for a wide body but overlaps both
3263 // fields onto a tall one (e.g. a vertical 0805 cap), so split them to the sides instead.
3264 VECTOR2I refOffset;
3265 VECTOR2I valueOffset;
3266
3267 if( bodyBox.GetHeight() > bodyBox.GetWidth() )
3268 {
3269 refOffset = VECTOR2I( bodyBox.GetLeft() - margin, 0 );
3270 valueOffset = VECTOR2I( bodyBox.GetRight() + margin, 0 );
3271 }
3272 else
3273 {
3274 refOffset = VECTOR2I( 0, bodyBox.GetTop() - margin );
3275 valueOffset = VECTOR2I( 0, bodyBox.GetBottom() + margin );
3276 }
3277
3278 if( SCH_FIELD* refField = symbol->GetField( FIELD_T::REFERENCE ) )
3279 {
3280 refField->SetPosition( pos + refOffset );
3281
3282 if( bodyBox.GetHeight() > bodyBox.GetWidth() )
3283 refField->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
3284 }
3285
3286 if( SCH_FIELD* valField = symbol->GetField( FIELD_T::VALUE ) )
3287 {
3288 valField->SetPosition( pos + valueOffset );
3289
3290 if( bodyBox.GetHeight() > bodyBox.GetWidth() )
3291 valField->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
3292 }
3293 }
3294
3295 // DipTrace rotates a marking's text with the symbol body, so a 90 or 270 degree placement reads
3296 // its reference and value vertically. Only the text angle is derived here (from the binary
3297 // placement rotation); the position comes from the marking record, so no clearance distance is
3298 // invented. DipTrace centres every marking (Horz="Center" Vert="Center"), so centre the justify
3299 // too; the no-record fallback above side-justifies for horizontal text, which shifts vertical
3300 // text along its length and mis-aligns a rotated part like R14-R20.
3301 if( aComp.rotationE4 == 15708 || aComp.rotationE4 == 47124 )
3302 {
3303 for( FIELD_T fieldId : { FIELD_T::REFERENCE, FIELD_T::VALUE } )
3304 {
3305 if( SCH_FIELD* field = symbol->GetField( fieldId ) )
3306 {
3307 field->SetTextAngle( ANGLE_VERTICAL );
3308 field->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
3309 field->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
3310 }
3311 }
3312 }
3313
3314 // DipTrace .dch stores no decodable per-component sheet field (verified across the whole record
3315 // against the .dchxml truth), so the sheet is recovered from the wire connectivity, which is the
3316 // source DipTrace itself relies on. Every wire endpoint carries its part id and sheet, so once the
3317 // full wire walk runs nearly every part is covered. The header bbox is [centerX, centerY, width,
3318 // height]; each pin's connection point (where a wire ends) is the pin coordinate offset from that
3319 // center, extended outward by the pin length along its dominant axis. Matching those connection
3320 // points against the decoded wire geometry yields the owning sheet without needing the component
3321 // rotation (which is not parsed). Falls back to the supplied screen when no pin coincides with a
3322 // wire.
3323 // Match connection points against the wire geometry, which is offset the same way, so use the
3324 // offset center here too.
3326 std::vector<VECTOR2I> connectionPoints;
3327
3328 for( const DCH_PIN& dchPin : aComp.pins )
3329 {
3330 VECTOR2I off( toKiCadCoordX( dchPin.x ), toKiCadCoordY( dchPin.y ) );
3331 int len = toKiCadSize( dchPin.length );
3332 VECTOR2I dir( 0, 0 );
3333
3334 if( std::abs( off.x ) >= std::abs( off.y ) )
3335 dir.x = ( off.x >= 0 ) ? 1 : -1;
3336 else
3337 dir.y = ( off.y >= 0 ) ? 1 : -1;
3338
3339 connectionPoints.emplace_back( center.x + off.x + dir.x * len, center.y + off.y + dir.y * len );
3340 }
3341
3342 // Primary: match the pin connection points against the wire geometry (precise per-sheet). When
3343 // the heuristic parser mis-read the pins so nothing matches, fall back to the component's file
3344 // position, which gives its DipTrace part id, and the wire connectivity gives that part's sheet
3345 // exactly regardless of the bad pin data.
3346 int votedSheet = sheetForComponentPins( connectionPoints );
3347
3348 if( votedSheet < 0 )
3349 {
3350 auto offsetIt = m_offsetToPartId.find( aComp.fileOffset );
3351
3352 if( offsetIt != m_offsetToPartId.end() )
3353 {
3354 int partId = offsetIt->second;
3355
3356 // The part itself may have no wire (e.g. an unconnected unit of a multi-unit part), so
3357 // search the nearest part ids too: components on a sheet have consecutive part ids, so a
3358 // neighbour's sheet is the right one.
3359 for( int d = 0; d <= 12 && votedSheet < 0; d++ )
3360 {
3361 for( int candidate : { partId - d, partId + d } )
3362 {
3363 auto sheetIt = m_partIdSheet.find( candidate );
3364
3365 if( sheetIt != m_partIdSheet.end() )
3366 {
3367 votedSheet = sheetIt->second;
3368 break;
3369 }
3370 }
3371 }
3372 }
3373 }
3374
3375 SCH_SCREEN* screen = ( votedSheet >= 0 ) ? getOrCreateSheet( votedSheet ) : aFallbackScreen;
3376
3377 screen->Append( symbol );
3378}
3379
3380
3382{
3383 m_netPortNames.clear();
3385
3386 // The part-aware sheet tally tracks the last assigned part id to disambiguate duplicate sheets
3387 // in file order. Net ports are a separate pass over their own objects, so reset the running id
3388 // rather than inheriting the last symbol's, keeping port resolution independent and ordered.
3389 m_lastSymbolPartId = -1;
3390
3391 for( const DCH_COMPONENT& comp : m_components )
3392 {
3393 if( !comp.libPath.Contains( wxT( "auto_net_ports" ) ) || comp.compName.IsEmpty() )
3394 continue;
3395
3396 // The port's component name is its net name; record it for diagnostics and so any future
3397 // consumer can tell which nets carry an explicit, labelled port object.
3398 m_netPortNames.insert( comp.compName );
3399
3400 VECTOR2I pos = applyPageOffset( VECTOR2I( toKiCadCoordX( comp.bboxX1 ), toKiCadCoordY( comp.bboxY1 ) ) );
3401
3402 // Resolve the port's sheet from its single pin connection point against the wire geometry,
3403 // falling back to its file-order part id and that part's wire-derived sheet (same recovery
3404 // the symbols use), since DipTrace stores no per-component sheet field.
3405 std::vector<VECTOR2I> connectionPoints;
3406 VECTOR2I firstPinDir( 0, 0 );
3407 bool havePin = false;
3408
3409 for( const DCH_PIN& dchPin : comp.pins )
3410 {
3411 VECTOR2I off( toKiCadCoordX( dchPin.x ), toKiCadCoordY( dchPin.y ) );
3412 int len = toKiCadSize( dchPin.length );
3413 VECTOR2I dir( 0, 0 );
3414
3415 if( std::abs( off.x ) >= std::abs( off.y ) )
3416 dir.x = ( off.x >= 0 ) ? 1 : -1;
3417 else
3418 dir.y = ( off.y >= 0 ) ? 1 : -1;
3419
3420 connectionPoints.emplace_back( pos.x + off.x + dir.x * len, pos.y + off.y + dir.y * len );
3421
3422 if( !havePin )
3423 {
3424 firstPinDir = dir;
3425 havePin = true;
3426 }
3427 }
3428
3429 // Resolve the port's sheet from its pin connection point matched against the decoded wire
3430 // geometry. Plain position voting ties when the same coordinate carries wires on more than
3431 // one sheet (DipTrace centres every sheet on the same origin), which sent one of the four
3432 // GND_ANALOG ports to a coincidental neighbour sheet. The part-aware tally breaks the tie
3433 // toward the sheet whose wire endpoint connects to the port's own object, and still falls
3434 // back to position voting when no endpoint carries a part.
3435 int votedSheet = sheetForComponentPins( connectionPoints );
3436 auto offsetIt = m_offsetToPartId.find( comp.fileOffset );
3437
3438 if( votedSheet < 0 && offsetIt != m_offsetToPartId.end() )
3439 {
3440 auto sheetIt = m_partIdSheet.find( offsetIt->second );
3441
3442 if( sheetIt != m_partIdSheet.end() )
3443 votedSheet = sheetIt->second;
3444 }
3445
3446 if( votedSheet < 0 && offsetIt != m_offsetToPartId.end() )
3447 {
3448 int partId = offsetIt->second;
3449
3450 for( int d = 1; d <= 12 && votedSheet < 0; d++ )
3451 {
3452 for( int candidate : { partId - d, partId + d } )
3453 {
3454 auto sheetIt = m_partIdSheet.find( candidate );
3455
3456 if( sheetIt != m_partIdSheet.end() )
3457 {
3458 votedSheet = sheetIt->second;
3459 break;
3460 }
3461 }
3462 }
3463 }
3464
3465 // Last resort for a port whose pin geometry matched no wire and whose part id has no
3466 // wire-derived sheet (an isolated port): snap to the sheet whose wire geometry is closest
3467 // to the port's own placement, so the label still lands on the sheet DipTrace drew it on
3468 // rather than defaulting to the root.
3469 if( votedSheet < 0 )
3470 votedSheet = sheetForNearestWire( pos );
3471
3472 SCH_SCREEN* screen = getOrCreateSheet( votedSheet >= 0 ? votedSheet : 0 );
3473
3474 // A global label's origin is its connection point, so anchor it on the port's pin endpoint
3475 // where the wire lands rather than the body center, otherwise the label floats off the net.
3476 VECTOR2I labelPos = ( havePin && !connectionPoints.empty() ) ? connectionPoints.front() : pos;
3477
3478 // The pin's outward direction points along the wire, so the label text reads away from it; a
3479 // pin leaving toward +x puts the wire on the right and the text on the left, and so on.
3481
3482 if( firstPinDir.x > 0 )
3483 spin = SPIN_STYLE::LEFT;
3484 else if( firstPinDir.x < 0 )
3485 spin = SPIN_STYLE::RIGHT;
3486 else if( firstPinDir.y > 0 )
3487 spin = SPIN_STYLE::UP;
3488 else if( firstPinDir.y < 0 )
3489 spin = SPIN_STYLE::BOTTOM;
3490
3491 SCH_GLOBALLABEL* label = new SCH_GLOBALLABEL( labelPos, comp.compName );
3493 label->SetSpinStyle( spin );
3494 screen->Append( label );
3496 }
3497}
3498
3499
3501{
3502 int bestSheet = -1;
3503 int64_t bestDist = std::numeric_limits<int64_t>::max();
3504
3505 for( const auto& [pt, sheets] : m_wirePointSheets )
3506 {
3507 int64_t dx = static_cast<int64_t>( pt.first ) - aPos.x;
3508 int64_t dy = static_cast<int64_t>( pt.second ) - aPos.y;
3509 int64_t dist = dx * dx + dy * dy;
3510
3511 if( dist < bestDist )
3512 {
3513 bestDist = dist;
3514 bestSheet = *sheets.begin();
3515 }
3516 }
3517
3518 return bestSheet;
3519}
3520
3521
3522// True if the UTF-16-BE string at aData[aPos] is a plausible net name immediately followed by
3523// the fixed net-record header fields. This discriminates real net names from font blocks,
3524// footprint names, and binary noise inside the net-section preambles.
3525static bool isPlausibleNetName( const uint8_t* aData, size_t aPos, size_t aSectionEnd )
3526{
3527 if( aPos + 2 > aSectionEnd )
3528 return false;
3529
3530 int cnt = ( aData[aPos] << 8 ) | aData[aPos + 1];
3531
3532 if( cnt < 1 || cnt > 64 )
3533 return false;
3534
3535 size_t end = aPos + 2 + static_cast<size_t>( cnt ) * 2;
3536
3537 if( end + 8 + 3 + 1 + 3 > aSectionEnd )
3538 return false;
3539
3540 for( int i = 0; i < cnt; i++ )
3541 {
3542 unsigned hi = aData[aPos + 2 + static_cast<size_t>( i ) * 2];
3543 unsigned lo = aData[aPos + 2 + static_cast<size_t>( i ) * 2 + 1];
3544 unsigned ch = ( hi << 8 ) | lo;
3545
3546 bool ok = ( ch >= 0x20 && ch < 0x7F ) // ASCII printable
3547 || ( ch >= 0x00A0 && ch <= 0x024F ) // Latin-1 / Latin Extended
3548 || ( ch >= 0x0400 && ch <= 0x04FF ); // Cyrillic
3549
3550 if( !ok )
3551 return false;
3552 }
3553
3554 auto rdInt4 = [&]( size_t o ) -> int
3555 {
3556 uint32_t raw = ( static_cast<uint32_t>( aData[o] ) << 24 ) | ( static_cast<uint32_t>( aData[o + 1] ) << 16 )
3557 | ( static_cast<uint32_t>( aData[o + 2] ) << 8 ) | static_cast<uint32_t>( aData[o + 3] );
3558 return static_cast<int>( static_cast<int64_t>( raw ) - INT4_BIAS );
3559 };
3560
3561 int lx = rdInt4( end );
3562 int ly = rdInt4( end + 4 );
3563
3564 auto rdInt3 = [&]( size_t o ) -> int
3565 {
3566 return ( ( aData[o] << 16 ) | ( aData[o + 1] << 8 ) | aData[o + 2] ) - INT3_BIAS;
3567 };
3568
3569 int pad = rdInt3( end + 8 );
3570 int flag = aData[end + 11];
3571
3572 // pad is a small discriminator that is 0 for nearly every net but 1 for a few (e.g. OTG_FS_N).
3573 // Requiring it to be exactly 0 rejected those nets and aborted the sequential walk, dropping
3574 // every later net's wires, so accept 0 or 1.
3575 return lx > -2000000 && lx < 2000000 && ly > -2000000 && ly < 2000000 && ( pad == 0 || pad == 1 )
3576 && ( flag == 0 || flag == 1 );
3577}
3578
3579
3581{
3582 // Accepted wire-net records use a marker lead-in followed by a 2-byte-prefixed
3583 // UTF-16-BE name. Older ASCII-string files keep the net-label-only import from
3584 // parseNetSection().
3586 return;
3587
3588 const uint8_t* data = m_reader.GetData();
3589 size_t fileSize = m_reader.GetFileSize();
3590 size_t sectionEnd = m_tailOffset > 0 ? m_tailOffset : fileSize;
3591 size_t sectionStart = m_busSectionOffset;
3592
3593 if( sectionStart == 0 || sectionStart >= sectionEnd )
3594 return;
3595
3596 auto rdInt3 = [&]( size_t o ) -> int
3597 {
3598 return ( ( data[o] << 16 ) | ( data[o + 1] << 8 ) | data[o + 2] ) - INT3_BIAS;
3599 };
3600
3601 auto rdInt4 = [&]( size_t o ) -> int
3602 {
3603 uint32_t raw = ( static_cast<uint32_t>( data[o] ) << 24 ) | ( static_cast<uint32_t>( data[o + 1] ) << 16 )
3604 | ( static_cast<uint32_t>( data[o + 2] ) << 8 ) | static_cast<uint32_t>( data[o + 3] );
3605 return static_cast<int>( static_cast<int64_t>( raw ) - INT4_BIAS );
3606 };
3607
3608 static constexpr uint8_t WIRE_NET_MARKER[] = { 0x0F, 0x42, 0x3F };
3609 static constexpr size_t WIRE_NET_MARKER_LEN = sizeof( WIRE_NET_MARKER );
3610
3611 auto isExpectedWireNetMarker = [&]( size_t aMarkerOffset, int aExpectedIndex ) -> bool
3612 {
3613 if( aMarkerOffset < 13 || aMarkerOffset + WIRE_NET_MARKER_LEN > sectionEnd )
3614 return false;
3615
3616 if( memcmp( data + aMarkerOffset, WIRE_NET_MARKER, WIRE_NET_MARKER_LEN ) != 0 )
3617 return false;
3618
3619 if( data[aMarkerOffset - 13] != 0x01 )
3620 return false;
3621
3622 int fieldA = rdInt3( aMarkerOffset - 9 );
3623 int fieldB = rdInt3( aMarkerOffset - 6 );
3624 int netIndex = rdInt3( aMarkerOffset - 3 );
3625
3626 if( netIndex != aExpectedIndex )
3627 return false;
3628
3629 // fieldB is a small signed marker discriminator that is -1 for some nets (e.g. auto-named
3630 // "Net N" records). Requiring it to be non-negative rejected the first such net and aborted
3631 // the whole sequential walk, dropping every wire after it. Allow -1 so the walk reaches the
3632 // later sheets too.
3633 return fieldA >= -1 && fieldA <= 100000 && fieldB >= -1 && fieldB <= 100000;
3634 };
3635
3636 auto decodeWireNetName = [&]( size_t aNameOffset, wxString& aName, size_t& aAfterName, wxString& aError ) -> bool
3637 {
3638 if( aNameOffset + 2 > sectionEnd )
3639 {
3640 aError = wxT( "missing UTF-16 length" );
3641 return false;
3642 }
3643
3644 int nameLen = ( data[aNameOffset] << 8 ) | data[aNameOffset + 1];
3645
3646 if( nameLen < 1 || nameLen > 64 )
3647 {
3648 aError = wxString::Format( wxT( "invalid UTF-16 length %d" ), nameLen );
3649 return false;
3650 }
3651
3652 aAfterName = aNameOffset + 2 + static_cast<size_t>( nameLen ) * 2;
3653
3654 if( aAfterName + 8 + 3 + 1 + 3 > sectionEnd )
3655 {
3656 aError = wxT( "name overruns wire-net record" );
3657 return false;
3658 }
3659
3660 for( int i = 0; i < nameLen; i++ )
3661 {
3662 unsigned hi = data[aNameOffset + 2 + static_cast<size_t>( i ) * 2];
3663 unsigned lo = data[aNameOffset + 2 + static_cast<size_t>( i ) * 2 + 1];
3664 unsigned ch = ( hi << 8 ) | lo;
3665
3666 bool valid =
3667 ( ch >= 0x20 && ch < 0x7F ) || ( ch >= 0x00A0 && ch <= 0x024F ) || ( ch >= 0x0400 && ch <= 0x04FF );
3668
3669 if( !valid )
3670 {
3671 aError = wxString::Format( wxT( "invalid UTF-16 character 0x%04X" ), ch );
3672 return false;
3673 }
3674 }
3675
3676 wxMBConvUTF16BE conv;
3677 aName = wxString( reinterpret_cast<const char*>( data + aNameOffset + 2 ), conv,
3678 static_cast<size_t>( nameLen ) * 2 );
3679
3680 return true;
3681 };
3682
3683 auto findNextWireNetName = [&]( size_t aSearchStart, size_t aSearchEnd, int aExpectedIndex ) -> size_t
3684 {
3685 if( aSearchStart >= aSearchEnd )
3686 return 0;
3687
3688 for( size_t marker = aSearchStart; marker + WIRE_NET_MARKER_LEN <= aSearchEnd; marker++ )
3689 {
3690 if( memcmp( data + marker, WIRE_NET_MARKER, WIRE_NET_MARKER_LEN ) != 0 )
3691 continue;
3692
3693 if( !isExpectedWireNetMarker( marker, aExpectedIndex ) )
3694 continue;
3695
3696 size_t nameOffset = marker + WIRE_NET_MARKER_LEN;
3697 wxString candidateName;
3698 wxString nameError;
3699 size_t afterName = 0;
3700
3701 if( !decodeWireNetName( nameOffset, candidateName, afterName, nameError ) )
3702 {
3703 THROW_IO_ERRORF( _( "DipTrace import: invalid wire-net name for net index %d at offset 0x%06zX: %s." ),
3704 aExpectedIndex, nameOffset, nameError );
3705 }
3706
3707 if( isPlausibleNetName( data, nameOffset, sectionEnd ) )
3708 return nameOffset;
3709 }
3710
3711 return 0;
3712 };
3713
3714 // Locate the first wire-net name within the section header.
3715 int expectedWireNetIndex = 0;
3716 size_t pos = findNextWireNetName( sectionStart, sectionEnd, expectedWireNetIndex );
3717
3718 if( pos == 0 )
3719 return;
3720
3721 int safetyNets = 0;
3722 size_t lastRecordEnd = 0;
3723
3724 while( pos != 0 && pos < sectionEnd && safetyNets++ < 100000 )
3725 {
3726 size_t o = pos;
3727
3728 // Net name (UTF-16-BE), then labelX(int4) labelY(int4) pad(int3) flag(byte).
3729 wxString netName;
3730 wxString nameError;
3731 size_t afterName = 0;
3732
3733 if( !decodeWireNetName( o, netName, afterName, nameError ) )
3734 {
3735 THROW_IO_ERRORF( _( "DipTrace import: invalid wire-net name for net index %d at offset 0x%06zX: %s." ),
3736 expectedWireNetIndex, o, nameError );
3737 }
3738
3739 o = afterName;
3740 o += 4 + 4 + 3 + 1;
3741
3742 if( o + 3 > sectionEnd )
3743 break;
3744
3745 int pinCount = rdInt3( o );
3746 size_t pinCountOffset = o;
3747 o += 3;
3748
3749 if( pinCount < 0 || pinCount > 4000 || o + static_cast<size_t>( pinCount ) * 6 + 3 > sectionEnd )
3750 {
3751 THROW_IO_ERRORF( _( "DipTrace import: invalid wire-net pin count %d for net '%s' at offset 0x%06zX." ),
3752 pinCount, netName, pinCountOffset );
3753 }
3754
3755 o += static_cast<size_t>( pinCount ) * 6;
3756
3757 int wireCount = rdInt3( o );
3758 size_t wireCountOffset = o;
3759 o += 3;
3760
3761 if( wireCount < 0 || wireCount > 100000 )
3762 {
3763 THROW_IO_ERRORF( _( "DipTrace import: invalid wire count %d for net '%s' at offset 0x%06zX." ),
3764 wireCount, netName, wireCountOffset );
3765 }
3766
3767 bool brokeEarly = false;
3768
3769 for( int w = 0; w < wireCount; w++ )
3770 {
3771 if( o + 36 + 1 + 3 > sectionEnd )
3772 {
3773 brokeEarly = true;
3774 break;
3775 }
3776
3777 DCH_WIRE wire;
3778 wire.object1 = rdInt3( o + 0 );
3779 wire.object2 = rdInt3( o + 3 );
3780 wire.subObject1 = rdInt3( o + 6 );
3781 wire.subObject2 = rdInt3( o + 9 );
3782 wire.bus1 = rdInt3( o + 12 );
3783 wire.bus2 = rdInt3( o + 15 );
3784 wire.sheetIndex = rdInt3( o + 18 );
3785
3786 o += 36; // 12 int3 header tokens
3787 o += 1; // flag byte
3788
3789 int pointCount = rdInt3( o );
3790 size_t pointCountOffset = o;
3791 o += 3;
3792
3793 if( pointCount < 0 || pointCount > 4000 || o + static_cast<size_t>( pointCount ) * 11 + 8 > sectionEnd )
3794 {
3795 THROW_IO_ERRORF( _( "DipTrace import: invalid wire point count %d for net '%s' at offset 0x%06zX." ),
3796 pointCount, netName, pointCountOffset );
3797 }
3798
3799 wire.points.reserve( pointCount );
3800
3801 for( int p = 0; p < pointCount; p++ )
3802 {
3803 int dtX = rdInt4( o );
3804 int dtY = rdInt4( o + 4 );
3805
3806 // On-disk wire X/Y use the same convention as pins, so feed the raw DipTrace ints
3807 // directly through the existing transforms. This lands wire endpoints exactly on
3808 // imported pin positions.
3809 wire.points.emplace_back( toKiCadCoordX( dtX ), toKiCadCoordY( dtY ) );
3810
3811 o += 11; // X(int4) Y(int4) Dir(int3)
3812 }
3813
3814 o += 8; // per-wire trailer
3815
3816 if( wire.points.size() >= 2 )
3817 {
3818 // Labels are not synthesized per net here. DipTrace shows a label only where an
3819 // explicit net-port object is placed, so labels are emitted from those objects in
3820 // createNetPortLabels(). Auto-named internal nets ("Net 36") own no port object and
3821 // therefore carry no label, matching the source rendering.
3822 m_wires.push_back( std::move( wire ) );
3823 }
3824 }
3825
3826 if( brokeEarly )
3827 break;
3828
3829 lastRecordEnd = o;
3830 expectedWireNetIndex++;
3831
3832 // Find the next net name (skips the variable-length net preamble).
3833 pos = findNextWireNetName( o, std::min( sectionEnd, o + 400 ), expectedWireNetIndex );
3834 }
3835
3836 m_wireSectionEnd = lastRecordEnd;
3837}
3838
3839
3841{
3842 m_sheetShapes.clear();
3843
3844 if( m_version < V31_CUTOVER )
3845 return;
3846
3847 size_t sectionEnd = m_tailOffset > 0 ? m_tailOffset : m_reader.GetFileSize();
3848 size_t searchStart = m_busSectionOffset > 0 ? m_busSectionOffset : m_wireSectionEnd;
3849
3850 if( searchStart == 0 || searchStart >= sectionEnd )
3851 return;
3852
3853 size_t originalOffset = m_reader.GetOffset();
3854
3855 auto readSheetShapeRecord = [&]() -> std::optional<DCH_SHEET_SHAPE>
3856 {
3857 uint8_t flagA = m_reader.ReadByte();
3858 uint8_t flagB = m_reader.ReadByte();
3859 int fieldA = m_reader.ReadInt3();
3860 int kindCode = m_reader.ReadInt3();
3861 int drawOrder = m_reader.ReadInt3();
3862
3863 m_reader.Skip( 3 ); // fill color A
3864
3865 uint8_t color[3] = {};
3866 m_reader.ReadBytes( color, 3 );
3867
3868 m_reader.Skip( 3 ); // fill color B
3869
3870 int fieldB = m_reader.ReadInt3();
3871 int sheetIndex = m_reader.ReadInt3();
3872 int fieldC = m_reader.ReadInt3();
3873 int lineWidth = m_reader.ReadInt4();
3874 m_reader.ReadString(); // font name
3875 m_reader.ReadString(); // optional text
3876 int fieldD = m_reader.ReadInt3();
3877 int pointCount = m_reader.ReadInt3();
3878
3879 if( flagA != 1 || flagB != 0 || fieldA != 0 || fieldB != 0 || fieldC != -1 || fieldD != 0 )
3880 return std::nullopt;
3881
3882 if( kindCode != 1 && kindCode != 4 )
3883 return std::nullopt;
3884
3885 if( drawOrder < 0 || drawOrder > 1000 || sheetIndex < 0 || sheetIndex >= m_numSheets )
3886 return std::nullopt;
3887
3888 if( lineWidth < 0 || lineWidth > 200000 || pointCount < 1 || pointCount > 100 )
3889 return std::nullopt;
3890
3891 DCH_SHEET_SHAPE shape;
3892 shape.kindCode = kindCode;
3893 shape.sheetIndex = sheetIndex;
3894 shape.lineWidth = lineWidth;
3895 shape.color[0] = color[0];
3896 shape.color[1] = color[1];
3897 shape.color[2] = color[2];
3898 shape.points.reserve( pointCount );
3899
3900 for( int i = 0; i < pointCount; i++ )
3901 {
3902 int x = m_reader.ReadInt4();
3903 int y = m_reader.ReadInt4();
3904 shape.points.emplace_back( x, y );
3905 }
3906
3907 int tailA = m_reader.ReadInt3();
3908 int tailB = m_reader.ReadInt3();
3909 uint8_t tailFlagA = m_reader.ReadByte();
3910 uint8_t tailFlagB = m_reader.ReadByte();
3911 int extentX = m_reader.ReadInt4();
3912 int extentY = m_reader.ReadInt4();
3913
3914 if( tailA != -1 || tailB != -1 || tailFlagA != 0 || tailFlagB != 1 || extentX != -20000 || extentY != 10000 )
3915 return std::nullopt;
3916
3917 return shape;
3918 };
3919
3920 for( size_t offset = searchStart; offset + 3 < sectionEnd; offset++ )
3921 {
3922 try
3923 {
3924 m_reader.SetOffset( offset );
3925 int count = m_reader.ReadInt3();
3926
3927 if( count < 1 || count > 1000 )
3928 continue;
3929
3930 std::vector<DCH_SHEET_SHAPE> shapes;
3931 shapes.reserve( count );
3932
3933 bool valid = true;
3934
3935 for( int i = 0; i < count; i++ )
3936 {
3937 std::optional<DCH_SHEET_SHAPE> shape = readSheetShapeRecord();
3938
3939 if( !shape )
3940 {
3941 valid = false;
3942 break;
3943 }
3944
3945 shapes.push_back( *shape );
3946 }
3947
3948 if( valid && !shapes.empty() && m_reader.GetOffset() <= sectionEnd )
3949 {
3950 m_sheetShapes = std::move( shapes );
3951 break;
3952 }
3953 }
3954 catch( const std::exception& )
3955 {
3956 }
3957 }
3958
3959 m_reader.SetOffset( originalOffset );
3960}
3961
3962
3964{
3965 return KIGFX::COLOR4D( aShape.color[0] / 255.0, aShape.color[1] / 255.0, aShape.color[2] / 255.0, 1.0 );
3966}
3967
3968
3970{
3971 for( const DCH_SHEET_SHAPE& dchShape : m_sheetShapes )
3972 {
3973 if( dchShape.points.size() < 2 )
3974 continue;
3975
3976 SCH_SCREEN* screen = getOrCreateSheet( dchShape.sheetIndex );
3977
3978 if( !screen )
3979 continue;
3980
3981 int width = toKiCadSize( dchShape.lineWidth );
3982 STROKE_PARAMS stroke( width, LINE_STYLE::SOLID, dipTraceSheetShapeColor( dchShape ) );
3983
3984 if( dchShape.kindCode == 4 && dchShape.points.size() == 2 )
3985 {
3988 VECTOR2I( toKiCadCoordX( dchShape.points[0].x ), toKiCadCoordY( dchShape.points[0].y ) ) ) );
3989 rect->SetEnd( applyPageOffset(
3990 VECTOR2I( toKiCadCoordX( dchShape.points[1].x ), toKiCadCoordY( dchShape.points[1].y ) ) ) );
3991 rect->SetStroke( stroke );
3992 screen->Append( rect );
3993 continue;
3994 }
3995
3996 if( dchShape.kindCode == 1 )
3997 {
3999
4000 for( const VECTOR2I& pt : dchShape.points )
4001 {
4002 line->AddPoint( applyPageOffset( VECTOR2I( toKiCadCoordX( pt.x ), toKiCadCoordY( pt.y ) ) ) );
4003 }
4004
4005 line->SetStroke( stroke );
4006 screen->Append( line );
4007 }
4008 }
4009}
4010
4011
4013{
4014 for( const DCH_WIRE& wire : m_wires )
4015 {
4016 int sheetIdx = wire.sheetIndex;
4017
4018 if( sheetIdx < 0 || sheetIdx >= m_numSheets )
4019 sheetIdx = 0;
4020
4021 SCH_SCREEN* screen = getOrCreateSheet( sheetIdx );
4022
4023 if( !screen )
4024 continue;
4025
4026 for( size_t i = 1; i < wire.points.size(); i++ )
4027 {
4028 VECTOR2I a = applyPageOffset( wire.points[i - 1] );
4029 VECTOR2I b = applyPageOffset( wire.points[i] );
4030
4031 if( a == b )
4032 continue;
4033
4034 SCH_LINE* line = new SCH_LINE( a, LAYER_WIRE );
4035 line->SetEndPoint( b );
4036 screen->Append( line );
4037 }
4038 }
4039}
4040
4041
4043{
4044 // Junctions come from two sources, deduplicated per screen. Must run after wires, labels and
4045 // symbols are placed.
4046 std::set<SCH_SCREEN*> screens;
4047
4048 if( m_rootSheet && m_rootSheet->GetScreen() )
4049 screens.insert( m_rootSheet->GetScreen() );
4050
4051 for( SCH_SHEET* sheet : m_sheets )
4052 {
4053 if( sheet && sheet->GetScreen() )
4054 screens.insert( sheet->GetScreen() );
4055 }
4056
4057 std::map<SCH_SCREEN*, std::set<std::pair<int, int>>> junctions;
4058
4059 // KiCad's geometric rule covers the common cases (>=3 conductor ends coincide, or a wire end
4060 // lands on a pin tap).
4061 for( SCH_SCREEN* screen : screens )
4062 {
4063 std::deque<EDA_ITEM*> items;
4064
4065 for( SCH_ITEM* item : screen->Items() )
4066 items.push_back( item );
4067
4068 for( const VECTOR2I& pt : screen->GetNeededJunctions( items ) )
4069 junctions[screen].insert( { pt.x, pt.y } );
4070 }
4071
4072 // DipTrace records each wire endpoint's connection explicitly: a bus value of -1 marks a pin
4073 // connection, anything else marks a tap onto another wire. KiCad's geometric rule misses a tap
4074 // where the target wire's collinear segments merge through the point, so add a junction wherever
4075 // an explicit wire tap lands on another wire's interior vertex.
4076 std::map<SCH_SCREEN*, std::set<std::pair<int, int>>> interior;
4077
4078 auto screenFor = [&]( const DCH_WIRE& aWire ) -> SCH_SCREEN*
4079 {
4080 int sheetIdx = ( aWire.sheetIndex >= 0 && aWire.sheetIndex < m_numSheets ) ? aWire.sheetIndex : 0;
4081 return getOrCreateSheet( sheetIdx );
4082 };
4083
4084 for( const DCH_WIRE& wire : m_wires )
4085 {
4086 SCH_SCREEN* screen = screenFor( wire );
4087
4088 if( !screen )
4089 continue;
4090
4091 for( size_t i = 1; i + 1 < wire.points.size(); i++ )
4092 {
4093 VECTOR2I p = applyPageOffset( wire.points[i] );
4094 interior[screen].insert( { p.x, p.y } );
4095 }
4096 }
4097
4098 for( const DCH_WIRE& wire : m_wires )
4099 {
4100 SCH_SCREEN* screen = screenFor( wire );
4101
4102 if( !screen || wire.points.empty() )
4103 continue;
4104
4105 const std::set<std::pair<int, int>>& sheetInterior = interior[screen];
4106
4107 if( wire.bus1 != -1 )
4108 {
4109 VECTOR2I p = applyPageOffset( wire.points.front() );
4110
4111 if( sheetInterior.count( { p.x, p.y } ) )
4112 junctions[screen].insert( { p.x, p.y } );
4113 }
4114
4115 if( wire.bus2 != -1 )
4116 {
4117 VECTOR2I p = applyPageOffset( wire.points.back() );
4118
4119 if( sheetInterior.count( { p.x, p.y } ) )
4120 junctions[screen].insert( { p.x, p.y } );
4121 }
4122 }
4123
4124 for( const auto& [screen, pts] : junctions )
4125 {
4126 for( const std::pair<int, int>& pt : pts )
4127 screen->Append( new SCH_JUNCTION( VECTOR2I( pt.first, pt.second ) ) );
4128 }
4129}
4130
4131
4133{
4134 for( const auto& [symName, symbols] : m_placedSymbolsByLibName )
4135 {
4136 auto libIt = m_libSymbols.find( symName );
4137
4138 if( libIt == m_libSymbols.end() )
4139 continue;
4140
4141 for( SCH_SYMBOL* symbol : symbols )
4142 {
4143 if( symbol )
4144 symbol->SetLibSymbol( new LIB_SYMBOL( *libIt->second ) );
4145 }
4146 }
4147}
4148
4149
4151{
4152 m_sheets.resize( m_numSheets, nullptr );
4153
4154 if( m_numSheets > 0 )
4155 m_sheets[0] = m_rootSheet;
4156
4157 if( m_rootSheet && !m_sheetDefs.empty() )
4158 m_rootSheet->SetName( m_sheetDefs[0].name );
4159
4160 if( m_numSheets > 1 )
4161 {
4162 for( int i = 1; i < m_numSheets; i++ )
4163 getOrCreateSheet( i );
4164 }
4165
4167
4168 // Apply the decoded page size to every screen so the imported content sits on a page that
4169 // matches the source. Only done when a page record was found; otherwise the KiCad default
4170 // remains and no placement offset is applied.
4171 if( m_page.found )
4172 {
4173 PAGE_INFO pageInfo( PAGE_SIZE_TYPE::User );
4174 pageInfo.SetWidthMM( m_page.widthMM );
4175 pageInfo.SetHeightMM( m_page.heightMM );
4176
4177 std::set<SCH_SCREEN*> screens;
4178
4179 if( m_rootSheet && m_rootSheet->GetScreen() )
4180 screens.insert( m_rootSheet->GetScreen() );
4181
4182 for( SCH_SHEET* sheet : m_sheets )
4183 {
4184 if( sheet && sheet->GetScreen() )
4185 screens.insert( sheet->GetScreen() );
4186 }
4187
4188 for( SCH_SCREEN* screen : screens )
4189 screen->SetPageSettings( pageInfo );
4190 }
4191
4192 // Index the decoded wire geometry by position so each symbol and label can be routed to the
4193 // sheet it connects to (DipTrace does not record sheet membership on components).
4196 m_lastSymbolPartId = -1;
4197 m_refdesUnitMap.clear();
4199
4200 for( const DCH_COMPONENT& comp : m_components )
4201 {
4202 try
4203 {
4204 createSymbolInstance( comp, m_rootSheet->GetScreen() );
4205 }
4206 catch( const std::exception& e )
4207 {
4208 if( m_reporter )
4209 {
4210 m_reporter->Report( wxString::Format( _( "DipTrace import: failed to create symbol "
4211 "for %s (%s): %s" ),
4212 comp.refdes, comp.compName, wxString::FromUTF8( e.what() ) ),
4214 }
4215 }
4216 }
4217
4220 createWires();
4223
4224 // The decoded symbols are embedded directly in the schematic (each SCH_SYMBOL carries a
4225 // flattened LIB_SYMBOL via SetLibSymbol), so the import deliberately does not write a
4226 // standalone .kicad_sym library or register one in the project symbol library table.
4227
4228 if( m_reporter )
4229 {
4230 m_reporter->Report( wxString::Format( _( "DipTrace import: loaded %zu components, %zu buses, "
4231 "%zu net-port labels from version %d file with %d sheets." ),
4233 m_numSheets ),
4235 }
4236}
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
void SetPageNumber(const wxString &aPageNumber)
Definition base_screen.h:75
constexpr size_type GetWidth() const
Definition box2.h:210
constexpr size_type GetHeight() const
Definition box2.h:211
constexpr coord_type GetLeft() const
Definition box2.h:224
constexpr coord_type GetRight() const
Definition box2.h:213
constexpr coord_type GetTop() const
Definition box2.h:225
constexpr coord_type GetBottom() const
Definition box2.h:218
void parseOneComponent(size_t aCompEnd, bool aUseCompEnd=true)
static int toKiCadCoordY(int aDipTraceCoord)
std::map< wxString, int > m_refdesUnitMap
Map from refdes to the number of units already created for multi-unit symbols.
std::vector< DCH_SHEET_DEF > m_sheetDefs
int sheetForNearestWire(const VECTOR2I &aPos) const
Sheet whose decoded wire geometry is closest to aPos, or -1 when no wire exists.
bool isShapeStart(size_t aOffset) const
Check if the data at the given offset looks like a shape/polyline start.
void parseFontBearingShape(DCH_COMPONENT &aComp)
static int pinOrientationFromOffset(int aOffsetX, int aOffsetY, int aHalfWidth, int aHalfHeight)
Determine the pin orientation from the pin connection-point offset relative to the symbol body center...
void buildWirePointSheets()
Build the maps from wire-point position to the sheet(s) and part(s) connecting there,...
void createWires()
Emit SCH_LINE wire segments decoded from the net/wire section.
bool parseComponentTextField(DCH_COMPONENT &aComp, size_t aCompEnd)
size_t m_netPortLabelCount
Count of net-port labels emitted, for the import summary report.
wxString normalizedRefdes(const DCH_COMPONENT &aComp) const
std::vector< size_t > scanComponentBoundaries(size_t aFirstComp, size_t aBusSectionOffset) const
Pre-scan the file to find component start offsets using the bbox(4*int4) + 5-string pattern.
wxString componentSymbolName(const DCH_COMPONENT &aComp) const
Library symbol name for a component.
void parseComponents(size_t aBusSectionOffset)
void createJunctions()
Synthesize junctions where conductors coincide (DipTrace stores none explicitly).
void createSymbolInstance(const DCH_COMPONENT &aComp, SCH_SCREEN *aFallbackScreen)
Create a SCH_SYMBOL instance on the given screen from a DipTrace component.
std::vector< DCH_NET_ENTRY > m_nets
int sheetForComponentPins(const std::vector< VECTOR2I > &aConnectionPoints)
Resolve a symbol's sheet from its pin connection points.
DCH_PAGE m_page
Decoded page geometry and the resulting half-page placement offset (KiCad nm).
size_t findTailStart() const
Find where the int3(0) tail padding begins by scanning backward from the end of file.
bool isComponentHeaderAt(size_t aOffset) const
True if a component record header (placement + five header strings) starts at aOffset.
static int toKiCadSize(int aDipTraceCoord)
Convert a DipTrace length or stroke width to KiCad schematic internal units.
void buildComponentPartIds()
Enumerate every component header in the file (real components and net ports alike) so each component'...
std::map< wxString, std::vector< SCH_SYMBOL * > > m_placedSymbolsByLibName
std::map< size_t, int > m_offsetToPartId
Component start offset -> DipTrace part id (its index in the in-file component order).
size_t findBusSection(size_t aSearchStart) const
Find the bus section start offset by searching for the characteristic marker pattern: int4(10000) int...
void createNetPortLabels()
Create a global net label for every DipTrace net-port component (auto_net_ports library).
void Parse()
Parse the .dch file and populate the schematic with KiCad objects.
std::map< std::pair< int, int >, std::vector< std::pair< int, int > > > m_pointPartSheets
Wire-endpoint position (KiCad nm) -> (partId, sheet) pairs of the parts connecting there.
std::vector< DCH_WIRE > m_wires
wxString getLibName() const
Build a library name string for the import.
VECTOR2I applyPageOffset(const VECTOR2I &aPos) const
Apply the page-center offset to an absolute KiCad-nm placement so 0,0-centered DipTrace content lands...
size_t m_componentSectionStart
File offset of the component section start, used to enumerate components in part-id order.
bool isFontBearingShapeStart(size_t aOffset) const
std::set< wxString > m_netPortNames
Names of nets that own a placed net-port component; these are the only nets DipTrace draws a label fo...
std::vector< SCH_SHEET * > m_sheets
One per DipTrace sheet.
PROGRESS_REPORTER * m_progressReporter
void createKiCadObjects()
Create KiCad objects from the parsed intermediate data and add them to the appropriate schematic shee...
int m_lastSymbolPartId
Largest part id assigned to a symbol so far; enforces the monotonic part-id order used to disambiguat...
int resolveSheetTally(const std::map< std::pair< int, int >, int > &aTally)
Resolve a (partId, sheet) -> hit-count tally to a sheet, preferring the highest-count pair with a par...
size_t m_wireSectionEnd
End offset of the decoded wire section; the sheet-shape section follows it in modern files.
void parseShape(DCH_COMPONENT &aComp)
void populateLibSymbolUnit(LIB_SYMBOL *aLibSymbol, const DCH_COMPONENT &aComp, int aUnit)
void findPageGeometry()
Locate the page-geometry record (width/height/margins, each mm*30000) in the binary and fill m_page.
std::map< int, int > m_partIdSheet
DipTrace part id -> sheet index, resolved from the wire connectivity.
int sheetForPositions(const std::vector< VECTOR2I > &aPositions, int aFallback) const
Pick the sheet a placed item belongs to by matching its connection points against the decoded wire ge...
std::vector< DCH_SHEET_SHAPE > m_sheetShapes
SCH_SCREEN * getOrCreateSheet(int aSheetIndex)
Get or create the KiCad sheet and screen for the given DipTrace sheet index.
void parsePin(int aPinIndex, DCH_COMPONENT &aComp)
std::vector< DCH_COMPONENT > m_components
std::map< wxString, std::unique_ptr< LIB_SYMBOL > > m_libSymbols
Symbol library cache.
void parseEmbeddedPattern(DCH_COMPONENT &aComp, size_t aCompEnd)
LIB_SYMBOL * getOrCreateLibSymbol(const DCH_COMPONENT &aComp, int aUnit)
Create a LIB_SYMBOL from the DipTrace component data.
static int toKiCadCoordX(int aDipTraceCoord)
Convert a DipTrace coordinate to KiCad schematic internal units.
SCH_PARSER(const wxString &aFileName, SCHEMATIC *aSchematic, SCH_SHEET *aRootSheet, PROGRESS_REPORTER *aProgressReporter=nullptr, REPORTER *aReporter=nullptr)
std::vector< DCH_BUS_ENTRY > m_buses
std::map< std::pair< int, int >, std::set< int > > m_wirePointSheets
Wire-point position (KiCad nm) -> set of sheet indices carrying a wire there.
virtual void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:244
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
static UTF8 FixIllegalChars(const UTF8 &aLibItemName, bool aLib)
Replace illegal LIB_ID item name characters with underscores '_'.
Definition lib_id.cpp:205
Define a library symbol object.
Definition lib_symbol.h:114
LIB_ITEMS_CONTAINER & GetDrawItems()
Return a reference to the draw item list.
Definition lib_symbol.h:809
void SetUnitCount(int aCount, bool aDuplicateDrawItems)
Set the units per symbol count.
const BOX2I GetBodyBoundingBox(int aUnit, int aBodyStyle, bool aIncludePins, bool aIncludePrivateItems) const
Get the symbol bounding box excluding fields.
int GetUnitCount() const override
void AddDrawItem(SCH_ITEM *aItem, bool aSort=true)
Add a new draw aItem to the draw object list and sort according to aSort.
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
void SetWidthMM(double aWidthInMM)
Definition page_info.h:136
void SetHeightMM(double aHeightInMM)
Definition page_info.h:141
A progress reporter interface for use in multi-threaded environments.
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:72
Holds all the data relating to one schematic.
Definition schematic.h:90
void SetPosition(const VECTOR2I &aPosition) override
void SetText(const wxString &aText) override
void SetSpinStyle(SPIN_STYLE aSpinStyle) override
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:162
void SetShape(LABEL_FLAG_SHAPE aShape)
Definition sch_label.h:179
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:38
void SetEndPoint(const VECTOR2I &aPosition)
Definition sch_line.h:145
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
void SetPosition(const VECTOR2I &aPos) override
Definition sch_shape.h:85
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition sch_shape.cpp:98
void AddPoint(const VECTOR2I &aPosition)
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:44
void SetFileName(const wxString &aFilename)
Definition sch_sheet.h:376
void SetName(const wxString &aName)
Definition sch_sheet.h:137
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:139
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
bool IsVirtualRootSheet() const
Schematic symbol object.
Definition sch_symbol.h:69
void SetRef(const SCH_SHEET_PATH *aSheet, const wxString &aReference)
Set the reference for the given sheet path for this symbol.
void SetFootprintFieldText(const wxString &aFootprint)
SCH_FIELD * AddField(const SCH_FIELD &aField)
Add a field to the symbol.
void SetLibSymbol(LIB_SYMBOL *aLibSymbol)
Set this schematic symbol library symbol reference to aLibSymbol.
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
Simple container to manage line stroke parameters.
wxString wx_str() const
Definition utf8.cpp:41
static const uint8_t TAHOMA_FONT_PATTERN[]
UTF-16BE pattern for the string "Tahoma" as stored in v46+ component font blocks.
static constexpr int SCHEMATIC_UTF16_STRING_VERSION
static VECTOR2I dipTraceShapePoint(double aXmm, double aYmm)
static constexpr int V31_CUTOVER
Structural layout version threshold for the .dch schematic format.
static bool isPlausibleNetName(const uint8_t *aData, size_t aPos, size_t aSectionEnd)
static KIGFX::COLOR4D dipTraceSheetShapeColor(const DCH_SHEET_SHAPE &aShape)
static bool libSymbolHasUnit(const LIB_SYMBOL *aLibSymbol, int aUnit)
static int dipTraceMm(double aMm)
static bool needsStandardThtLedShape(const DCH_COMPONENT &aComp)
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 std::vector< DCH_SHAPE > standardThtLedShapes()
static DCH_SHAPE makeDipTraceShape(int aKindCode, double aLineWidthMm, std::initializer_list< VECTOR2I > aPoints)
#define _(s)
static constexpr EDA_ANGLE ANGLE_VERTICAL
Definition eda_angle.h:408
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:47
@ NO_FILL
Definition eda_shape.h:60
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:61
static std::map< FOOTPRINT *, int > componentShapes
Association between shape names (using shapeName index) and components.
static const std::string KiCadSchematicFileExtension
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_ERRORF(msg,...)
@ LAYER_DEVICE
Definition layer_ids.h:472
@ LAYER_WIRE
Definition layer_ids.h:458
@ LAYER_NOTES
Definition layer_ids.h:473
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...
constexpr int INT3_BIAS
Bias value added to stored 3-byte unsigned integers.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ PT_POWER_IN
power input (GND, VCC for ICs). Must be connected to a power output.
Definition pin_type.h:42
@ PT_PASSIVE
pin for passive symbols: must be connected, and can be connected to any pin.
Definition pin_type.h:39
@ PIN_UP
The pin extends upwards from the connection point: Probably on the bottom side of the symbol.
Definition pin_type.h:123
@ PIN_RIGHT
The pin extends rightwards from the connection point.
Definition pin_type.h:107
@ PIN_LEFT
The pin extends leftwards from the connection point: Probably on the right side of the symbol.
Definition pin_type.h:114
@ PIN_DOWN
The pin extends downwards from the connection: Probably on the top side of the symbol.
Definition pin_type.h:131
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_INFO
@ L_BIDI
Definition sch_label.h:100
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
A bus entry as read from the bus section.
int coordY
int coordX
int busType
int instanceId
int signalCount
wxString name
int sheetIndex
A stored component text field record that precedes the embedded footprint pattern.
A component as read from the .dch file.
wxString patternName
Embedded footprint pattern name (e.g. "LED100", "CR0805")
std::vector< DCH_COMPONENT_TEXT > texts
int rotationE4
Placement rotation in radians x 1e4 (0, 15708, 31416, 47124)
std::vector< DCH_SHAPE > shapes
std::vector< std::pair< wxString, wxString > > additionalFields
User-defined additional fields, as (name, value) pairs (e.g. "Part Number (Digi-Key)").
wxString datasheet
Datasheet URL stored in the placement tail.
std::vector< DCH_PIN > pins
A net label/wire entry from the net section.
int coordX
int field1
wxString name
int coordY
A component pin as stored in the .dch file.
int x
DipTrace coordinate units (100/3 nm)
A graphical shape primitive (polyline) in a component.
std::vector< VECTOR2I > points
Points in DipTrace coord units.
int kindFlag
Leading kind int3; observed 0 for decoded drawing shapes.
int kindCode
Leading kind int3: 1 line, 3 arrow, 4 rect, 6 obround, 8 filled polygon, 9 outline polygon/polyline.
Sheet definition as read from the file header.
A top-level schematic sheet graphical primitive.
int kindCode
1 line, 4 rectangle.
std::vector< VECTOR2I > points
Points in DipTrace coord units.
A single schematic wire decoded from the net/wire section.
int object1
Connected item id at endpoint 1.
int sheetIndex
DipTrace sheet index.
std::vector< VECTOR2I > points
KiCad nm, ready for SCH_LINE.
int subObject1
Pin/sub index at endpoint 1.
int bus1
Bus index at endpoint 1 (-1 = none)
FIELD_T
The set of all field indices assuming an array like sequence that a SCH_COMPONENT or LIB_PART can hol...
@ USER
The field ID hasn't been set yet; field is invalid.
@ DATASHEET
name of datasheet
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
CADSTAR_ARCHIVE_PARSER::VERTEX_TYPE vt
std::string path
KIBIS_COMPONENT * comp
KIBIS_PIN * pin
VECTOR2I center
int radius
VECTOR2I end
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_CENTER
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
Definition of file extensions used in Kicad.