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