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