KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pads_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 (C) 2025 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 3
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
20#include "pads_sch_parser.h"
21
22#include <io/pads/pads_common.h>
23#include <ki_exception.h>
24#include <reporter.h>
25
26#include <algorithm>
27#include <cmath>
28#include <fstream>
29#include <sstream>
30#include <regex>
31
32namespace PADS_SCH
33{
34
35VECTOR2I padsSchArcMidpoint( const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& aCenter )
36{
37 double sx = aStart.x - aCenter.x;
38 double sy = aStart.y - aCenter.y;
39 double ex = aEnd.x - aCenter.x;
40 double ey = aEnd.y - aCenter.y;
41 double radius = std::sqrt( sx * sx + sy * sy );
42
43 double mx = sx + ex;
44 double my = sy + ey;
45 double mlen = std::sqrt( mx * mx + my * my );
46
47 VECTOR2I midPt;
48
49 if( mlen > 0.001 )
50 {
51 midPt.x = aCenter.x + static_cast<int>( radius * mx / mlen );
52 midPt.y = aCenter.y + static_cast<int>( radius * my / mlen );
53 }
54 else
55 {
56 midPt.x = aCenter.x + static_cast<int>( -sy * radius / std::max( radius, 1.0 ) );
57 midPt.y = aCenter.y + static_cast<int>( sx * radius / std::max( radius, 1.0 ) );
58 }
59
60 return midPt;
61}
62
63
65 m_reporter( nullptr ),
66 m_lineNumber( 0 ),
68{
69}
70
71
75
76
77bool PADS_SCH_PARSER::isSectionMarker( const std::string& aLine ) const
78{
79 if( aLine.size() < 3 || aLine[0] != '*' )
80 return false;
81
82 size_t endPos = aLine.find( '*', 1 );
83 return endPos != std::string::npos && endPos > 1;
84}
85
86
87std::string PADS_SCH_PARSER::extractSectionName( const std::string& aLine ) const
88{
89 if( aLine.size() < 3 || aLine[0] != '*' )
90 return "";
91
92 size_t endPos = aLine.find( '*', 1 );
93
94 if( endPos == std::string::npos || endPos <= 1 )
95 return "";
96
97 return aLine.substr( 1, endPos - 1 );
98}
99
100
101bool PADS_SCH_PARSER::Parse( const std::string& aFileName )
102{
105 m_symbolDefs.clear();
106 m_partPlacements.clear();
107 m_signals.clear();
108 m_offPageConnectors.clear();
109 m_buses.clear();
110 m_partTypes.clear();
111 m_tiedDots.clear();
112 m_sheetHeaders.clear();
113 m_textItems.clear();
114 m_linesItems.clear();
115 m_netNameLabels.clear();
116 m_lineNumber = 0;
117 m_currentSheet = 0;
118
119 std::ifstream file( aFileName );
120
121 if( !file.is_open() )
122 {
123 if( m_reporter )
124 m_reporter->Report( wxString::Format( "Cannot open file: %s", aFileName ), RPT_SEVERITY_ERROR );
125
126 return false;
127 }
128
129 std::vector<std::string> lines;
130 std::string line;
131
132 while( std::getline( file, line ) )
133 {
134 if( !line.empty() && line.back() == '\r' )
135 line.pop_back();
136
137 lines.push_back( line );
138 }
139
140 file.close();
141
142 if( lines.empty() )
143 {
144 if( m_reporter )
145 m_reporter->Report( "File is empty", RPT_SEVERITY_ERROR );
146
147 return false;
148 }
149
150 if( !parseHeader( lines[0] ) )
151 {
152 if( m_reporter )
153 m_reporter->Report( "Invalid PADS Logic file header", RPT_SEVERITY_ERROR );
154
155 return false;
156 }
157
158 m_header.valid = true;
159
160 for( size_t i = 1; i < lines.size(); i++ )
161 {
162 m_lineNumber = static_cast<int>( i + 1 );
163 const std::string& currentLine = lines[i];
164
165 if( currentLine.empty() )
166 continue;
167
168 if( currentLine.find( "*REMARK*" ) == 0 )
169 continue;
170
171 if( !isSectionMarker( currentLine ) )
172 continue;
173
174 std::string sectionName = extractSectionName( currentLine );
175
176 if( sectionName == "SCH" )
177 {
178 i = parseSectionSCH( lines, i );
179 }
180 else if( sectionName == "CAM" || sectionName == "MISC" )
181 {
182 i = skipBraceDelimitedSection( lines, i );
183 }
184 else if( sectionName == "FIELDS" )
185 {
186 i = parseSectionFIELDS( lines, i );
187 }
188 else if( sectionName == "SHT" )
189 {
190 i = parseSectionSHT( lines, i );
191 }
192 else if( sectionName == "CAE" )
193 {
194 i = parseSectionCAE( lines, i );
195 }
196 else if( sectionName == "TEXT" )
197 {
198 i = parseSectionTEXT( lines, i );
199 }
200 else if( sectionName == "LINES" )
201 {
202 i = parseSectionLINES( lines, i );
203 }
204 else if( sectionName == "CAEDECAL" )
205 {
206 i = parseSectionCAEDECAL( lines, i );
207 }
208 else if( sectionName == "PARTTYPE" )
209 {
210 i = parseSectionPARTTYPE( lines, i );
211 }
212 else if( sectionName == "PART" )
213 {
214 i = parseSectionPART( lines, i );
215 }
216 else if( sectionName == "BUSSES" )
217 {
218 i = parseSectionBUSSES( lines, i );
219 }
220 else if( sectionName == "OFFPAGE REFS" )
221 {
222 i = parseSectionOFFPAGEREFS( lines, i );
223 }
224 else if( sectionName == "TIEDOTS" )
225 {
226 i = parseSectionTIEDOTS( lines, i );
227 }
228 else if( sectionName == "CONNECTION" )
229 {
230 i = parseSectionCONNECTION( lines, i );
231 }
232 else if( sectionName == "NETNAMES" )
233 {
234 i = parseSectionNETNAMES( lines, i );
235 }
236 else if( sectionName == "END" )
237 {
238 break;
239 }
240 }
241
243
244 return true;
245}
246
247
249{
250 // Pin data is applied at symbol build time via GATE_DEF::pins rather than mutated on the
251 // shared SYMBOL_DEF, since several PARTTYPEs can reference one decal with different pin
252 // mappings.
253
254 for( auto& part : m_partPlacements )
255 {
256 for( auto& attr : part.attributes )
257 {
258 if( attr.name == "Ref.Des." && attr.value.empty() )
259 attr.value = part.reference;
260
261 auto ovr = part.attr_overrides.find( attr.name );
262
263 if( ovr != part.attr_overrides.end() && attr.value.empty() )
264 attr.value = ovr->second;
265 }
266 }
267}
268
269
270bool PADS_SCH_PARSER::CheckFileHeader( const std::string& aFileName )
271{
272 std::ifstream file( aFileName );
273
274 if( !file.is_open() )
275 return false;
276
277 std::string firstLine;
278
279 if( !std::getline( file, firstLine ) )
280 return false;
281
282 if( !firstLine.empty() && firstLine.back() == '\r' )
283 firstLine.pop_back();
284
285 if( firstLine.find( "*PADS-LOGIC" ) == 0 )
286 return true;
287
288 if( firstLine.find( "*PADS-POWERLOGIC" ) == 0 )
289 return true;
290
291 return false;
292}
293
294
295bool PADS_SCH_PARSER::parseHeader( const std::string& aLine )
296{
297 if( aLine.empty() || aLine[0] != '*' )
298 return false;
299
300 size_t endPos = aLine.find( '*', 1 );
301
302 if( endPos == std::string::npos )
303 return false;
304
305 std::string headerTag = aLine.substr( 1, endPos - 1 );
306
307 // The optional trailing suffix is an ANSI code page for non-ASCII strings.
308 std::regex headerRegex( R"(PADS-(POWER)?LOGIC-V(\d+\.\d+)(?:-([A-Za-z0-9]+))?)" );
309 std::smatch match;
310
311 if( !std::regex_match( headerTag, match, headerRegex ) )
312 return false;
313
314 if( match[1].matched )
315 m_header.product = "PADS-POWERLOGIC";
316 else
317 m_header.product = "PADS-LOGIC";
318
319 m_header.version = "V" + match[2].str();
320
321 if( match[3].matched )
322 m_header.codepage = match[3].str();
323
324 if( endPos + 1 < aLine.size() )
325 {
326 std::string desc = aLine.substr( endPos + 1 );
327 size_t start = desc.find_first_not_of( ' ' );
328
329 if( start != std::string::npos )
330 m_header.description = desc.substr( start );
331 }
332
333 return true;
334}
335
336
337size_t PADS_SCH_PARSER::parseSectionSCH( const std::vector<std::string>& aLines, size_t aStartLine )
338{
339 size_t i = aStartLine + 1;
340
341 while( i < aLines.size() )
342 {
343 const std::string& line = aLines[i];
344
345 if( isSectionMarker( line ) )
346 return i - 1;
347
348 if( line.empty() )
349 {
350 i++;
351 continue;
352 }
353
354 std::istringstream iss( line );
355 std::string keyword;
356 iss >> keyword;
357
358 if( keyword == "UNITS" )
359 {
360 int unitsVal = 0;
361 iss >> unitsVal;
362
363 switch( unitsVal )
364 {
365 case 0: m_parameters.units = UNIT_TYPE::MILS; break;
366 case 1: m_parameters.units = UNIT_TYPE::METRIC; break;
367 case 2: m_parameters.units = UNIT_TYPE::INCHES; break;
368 default: m_parameters.units = UNIT_TYPE::MILS; break;
369 }
370 }
371 else if( keyword == "CUR" )
372 {
373 std::string second;
374 iss >> second;
375
376 if( second == "SHEET" )
377 iss >> m_parameters.cur_sheet;
378 }
379 else if( keyword == "SHEET" )
380 {
381 std::string second;
382 iss >> second;
383
384 if( second == "SIZE" )
385 {
386 std::string sizeCode;
387 iss >> sizeCode;
388
389 m_parameters.sheet_size.name = sizeCode;
390
391 if( sizeCode == "A" )
392 {
393 m_parameters.sheet_size.width = 11000.0;
394 m_parameters.sheet_size.height = 8500.0;
395 }
396 else if( sizeCode == "B" )
397 {
398 m_parameters.sheet_size.width = 17000.0;
399 m_parameters.sheet_size.height = 11000.0;
400 }
401 else if( sizeCode == "C" )
402 {
403 m_parameters.sheet_size.width = 22000.0;
404 m_parameters.sheet_size.height = 17000.0;
405 }
406 else if( sizeCode == "D" )
407 {
408 m_parameters.sheet_size.width = 34000.0;
409 m_parameters.sheet_size.height = 22000.0;
410 }
411 else if( sizeCode == "E" )
412 {
413 m_parameters.sheet_size.width = 44000.0;
414 m_parameters.sheet_size.height = 34000.0;
415 }
416 else if( sizeCode == "A0" )
417 {
418 m_parameters.sheet_size.width = 46811.0;
419 m_parameters.sheet_size.height = 33110.0;
420 }
421 else if( sizeCode == "A1" )
422 {
423 m_parameters.sheet_size.width = 33110.0;
424 m_parameters.sheet_size.height = 23386.0;
425 }
426 else if( sizeCode == "A2" )
427 {
428 m_parameters.sheet_size.width = 23386.0;
429 m_parameters.sheet_size.height = 16535.0;
430 }
431 else if( sizeCode == "A3" )
432 {
433 m_parameters.sheet_size.width = 16535.0;
434 m_parameters.sheet_size.height = 11693.0;
435 }
436 else if( sizeCode == "A4" )
437 {
438 m_parameters.sheet_size.width = 11693.0;
439 m_parameters.sheet_size.height = 8268.0;
440 }
441 }
442 }
443 else if( keyword == "USERGRID" )
444 {
445 iss >> m_parameters.grid_x >> m_parameters.grid_y;
446 }
447 else if( keyword == "LINEWIDTH" )
448 {
449 iss >> m_parameters.line_width;
450 }
451 else if( keyword == "CONNWIDTH" )
452 {
453 iss >> m_parameters.conn_width;
454 }
455 else if( keyword == "BUSWIDTH" )
456 {
457 iss >> m_parameters.bus_width;
458 }
459 else if( keyword == "BUSANGLE" )
460 {
461 iss >> m_parameters.bus_angle;
462 }
463 else if( keyword == "TEXTSIZE" )
464 {
465 iss >> m_parameters.text_h >> m_parameters.text_w;
466 m_parameters.text_size = m_parameters.text_h;
467 }
468 else if( keyword == "PINNAMESIZE" )
469 {
470 iss >> m_parameters.pin_name_h >> m_parameters.pin_name_w;
471 }
472 else if( keyword == "REFNAMESIZE" )
473 {
474 iss >> m_parameters.ref_name_h >> m_parameters.ref_name_w;
475 }
476 else if( keyword == "PARTNAMESIZE" )
477 {
478 iss >> m_parameters.part_name_h >> m_parameters.part_name_w;
479 }
480 else if( keyword == "PINNOSIZE" )
481 {
482 iss >> m_parameters.pin_no_h >> m_parameters.pin_no_w;
483 }
484 else if( keyword == "NETNAMESIZE" )
485 {
486 iss >> m_parameters.net_name_h >> m_parameters.net_name_w;
487 }
488 else if( keyword == "DOTGRID" )
489 {
490 iss >> m_parameters.dot_grid;
491 }
492 else if( keyword == "TIEDOTSIZE" )
493 {
494 iss >> m_parameters.tied_dot_size;
495 }
496 else if( keyword == "REAL" )
497 {
498 std::string second;
499 iss >> second;
500
501 if( second == "WIDTH" )
502 iss >> m_parameters.real_width;
503 }
504 else if( keyword == "FONT" )
505 {
506 std::string second;
507 iss >> second;
508
509 if( second == "MODE" )
510 iss >> m_parameters.font_mode;
511 }
512 else if( keyword == "DEFAULT" )
513 {
514 std::string second;
515 iss >> second;
516
517 if( second == "FONT" )
518 {
519 std::string rest;
520 std::getline( iss, rest );
521 size_t start = rest.find( '"' );
522
523 if( start != std::string::npos )
524 {
525 size_t end = rest.find( '"', start + 1 );
526
527 if( end != std::string::npos )
528 m_parameters.default_font = rest.substr( start + 1, end - start - 1 );
529 }
530 }
531 }
532 else if( keyword == "BORDER" )
533 {
534 std::string second;
535 iss >> second;
536
537 if( second == "NAME" )
538 {
539 std::string name;
540 iss >> name;
541 m_parameters.border_template = name;
542 }
543 else
544 {
545 m_parameters.border_template = second;
546 }
547 }
548 else if( keyword == "JOBNAME" )
549 {
550 std::string rest;
551 std::getline( iss, rest );
552 size_t start = rest.find_first_not_of( " \t" );
553
554 if( start != std::string::npos )
555 {
556 rest = rest.substr( start );
557
558 if( rest.size() >= 2 && rest.front() == '"' && rest.back() == '"' )
559 rest = rest.substr( 1, rest.size() - 2 );
560
561 m_parameters.job_name = rest;
562 }
563 }
564 // Color and other numeric keywords we ignore
565 else if( keyword == "NTXCOL" || keyword == "HIRCOL" || keyword == "LINCOL" || keyword == "TXTCOL"
566 || keyword == "CONCOL" || keyword == "BUSCOL" || keyword == "PTXCOL" || keyword == "COMCOL"
567 || keyword == "NMCOL" || keyword == "PNMCOL" || keyword == "PINCOL" || keyword == "NETCOL"
568 || keyword == "FBGCOL" || keyword == "FIELDCOL" || keyword == "NNVISPWRGND" || keyword == "PCBFLAGS"
569 || keyword == "JOBTIME" || keyword == "BACKUPTIME" || keyword == "OFFREFVIEW" || keyword == "OFFREFNUM"
570 || keyword == "SHEETNUMSEP" )
571 {
572 }
573
574 i++;
575 }
576
577 return aLines.size() - 1;
578}
579
580
581size_t PADS_SCH_PARSER::parseSectionFIELDS( const std::vector<std::string>& aLines, size_t aStartLine )
582{
583 size_t i = aStartLine + 1;
584
585 while( i < aLines.size() )
586 {
587 const std::string& line = aLines[i];
588
589 if( isSectionMarker( line ) )
590 return i - 1;
591
592 if( line.empty() || line[0] != '"' )
593 {
594 i++;
595 continue;
596 }
597
598 // Line is "Field Name" followed by optional value text
599 size_t closeQuote = line.find( '"', 1 );
600
601 if( closeQuote != std::string::npos )
602 {
603 std::string fieldName = line.substr( 1, closeQuote - 1 );
604 std::string fieldValue;
605
606 if( closeQuote + 1 < line.size() )
607 {
608 fieldValue = line.substr( closeQuote + 1 );
609 size_t start = fieldValue.find_first_not_of( " \t" );
610
611 if( start != std::string::npos )
612 fieldValue = fieldValue.substr( start );
613 else
614 fieldValue.clear();
615 }
616
617 m_parameters.fields[fieldName] = fieldValue;
618 }
619
620 i++;
621 }
622
623 return aLines.size() - 1;
624}
625
626
627size_t PADS_SCH_PARSER::parseSectionSHT( const std::vector<std::string>& aLines, size_t aStartLine )
628{
629 // Fields: sheet_num sheet_name parent_num parent_name
630 const std::string& line = aLines[aStartLine];
631 std::string afterMarker = line.substr( line.find( '*', 1 ) + 1 );
632
633 std::istringstream iss( afterMarker );
634 SHEET_HEADER header;
635
636 iss >> header.sheet_num >> header.sheet_name >> header.parent_num >> header.parent_name;
637
638 m_currentSheet = header.sheet_num;
639 m_sheetHeaders.push_back( header );
640
641 return aStartLine;
642}
643
644
645size_t PADS_SCH_PARSER::parseSectionCAE( const std::vector<std::string>& aLines, size_t aStartLine )
646{
647 // The CAE section holds only viewport parameters, so skip it
648 size_t i = aStartLine + 1;
649
650 while( i < aLines.size() )
651 {
652 const std::string& line = aLines[i];
653
654 if( isSectionMarker( line ) )
655 return i - 1;
656
657 i++;
658 }
659
660 return aLines.size() - 1;
661}
662
663
664size_t PADS_SCH_PARSER::parseSectionTEXT( const std::vector<std::string>& aLines, size_t aStartLine )
665{
666 size_t i = aStartLine + 1;
667
668 while( i < aLines.size() )
669 {
670 const std::string& line = aLines[i];
671
672 if( line.empty() )
673 {
674 i++;
675 continue;
676 }
677
678 if( isSectionMarker( line ) )
679 return i - 1;
680
681 // A text item spans two lines: an attribute line then a content line
682 TEXT_ITEM item;
684 std::istringstream iss( line );
685
686 int x = 0, y = 0;
687 iss >> x >> y >> item.rotation >> item.justification >> item.height >> item.width_factor >> item.attr_flag;
688
689 item.position.x = x;
690 item.position.y = y;
691
692 std::string rest;
693 std::getline( iss, rest );
694 size_t qStart = rest.find( '"' );
695
696 if( qStart != std::string::npos )
697 {
698 size_t qEnd = rest.find( '"', qStart + 1 );
699
700 if( qEnd != std::string::npos )
701 item.font_name = rest.substr( qStart + 1, qEnd - qStart - 1 );
702 }
703
704 i++;
705
706 if( i < aLines.size() )
707 item.content = aLines[i];
708
709 m_textItems.push_back( item );
710 i++;
711 }
712
713 return aLines.size() - 1;
714}
715
716
717size_t PADS_SCH_PARSER::parseSectionLINES( const std::vector<std::string>& aLines, size_t aStartLine )
718{
719 size_t i = aStartLine + 1;
720
721 while( i < aLines.size() )
722 {
723 const std::string& line = aLines[i];
724
725 if( line.empty() )
726 {
727 i++;
728 continue;
729 }
730
731 if( isSectionMarker( line ) )
732 return i - 1;
733
734 // LINES item header: name LINES x y param1 param2
735 if( line.find( "LINES" ) != std::string::npos )
736 {
737 LINES_ITEM item;
738 std::istringstream iss( line );
739 std::string name, keyword;
740 int x = 0, y = 0;
741
742 iss >> name >> keyword >> x >> y >> item.param1 >> item.param2;
743
744 if( keyword == "LINES" )
745 {
746 item.name = name;
747 item.origin.x = x;
748 item.origin.y = y;
750
751 i++;
752
753 // Read primitives and embedded text until the next LINES item or section
754 while( i < aLines.size() )
755 {
756 const std::string& pline = aLines[i];
757
758 if( pline.empty() )
759 {
760 i++;
761 continue;
762 }
763
764 if( isSectionMarker( pline ) )
765 break;
766
767 if( pline.find( "LINES" ) != std::string::npos )
768 {
769 std::istringstream tiss( pline );
770 std::string tname, tkw;
771 tiss >> tname >> tkw;
772
773 if( tkw == "LINES" )
774 break;
775 }
776
777 std::istringstream piss( pline );
778 std::string firstToken;
779 piss >> firstToken;
780
781 if( firstToken == "OPEN" || firstToken == "CLOSED" || firstToken == "CIRCLE"
782 || firstToken == "COPCLS" )
783 {
784 SYMBOL_GRAPHIC graphic;
785 i = parseGraphicPrimitive( aLines, i, graphic );
786 item.primitives.push_back( graphic );
787 i++;
788 continue;
789 }
790
791 // A leading number marks a text item (coordinate attribute line)
792 bool isNumber =
793 !firstToken.empty()
794 && ( std::isdigit( firstToken[0] ) || firstToken[0] == '-' || firstToken[0] == '+' );
795
796 if( isNumber && pline.find( '"' ) != std::string::npos )
797 {
799 text.sheet_number = m_currentSheet;
800 std::istringstream tiss( pline );
801 int tx = 0, ty = 0;
802
803 tiss >> tx >> ty >> text.rotation >> text.justification >> text.height >> text.width_factor;
804
805 text.position.x = tx;
806 text.position.y = ty;
807
808 std::string trest;
809 std::getline( tiss, trest );
810 size_t qStart = trest.find( '"' );
811
812 if( qStart != std::string::npos )
813 {
814 size_t qEnd = trest.find( '"', qStart + 1 );
815
816 if( qEnd != std::string::npos )
817 text.font_name = trest.substr( qStart + 1, qEnd - qStart - 1 );
818 }
819
820 i++;
821
822 if( i < aLines.size() )
823 text.content = aLines[i];
824
825 item.texts.push_back( text );
826 }
827
828 i++;
829 }
830
831 m_linesItems.push_back( item );
832 continue;
833 }
834 }
835
836 i++;
837 }
838
839 return aLines.size() - 1;
840}
841
842
843size_t PADS_SCH_PARSER::parseSectionCAEDECAL( const std::vector<std::string>& aLines, size_t aStartLine )
844{
845 size_t i = aStartLine + 1;
846
847 while( i < aLines.size() )
848 {
849 const std::string& line = aLines[i];
850
851 if( line.empty() )
852 {
853 i++;
854 continue;
855 }
856
857 if( isSectionMarker( line ) )
858 break;
859
860 SYMBOL_DEF symbol;
861 i = parseSymbolDef( aLines, i, symbol );
862
863 if( !symbol.name.empty() )
864 m_symbolDefs.push_back( std::move( symbol ) );
865
866 i++;
867 }
868
869 // Resolve pin lengths from pin-decal geometry. The SHORT/LONG name heuristic covers only
870 // standard decals; custom names need their length measured from the graphics.
871 std::map<std::string, double> pinDecalLengths;
872
873 for( const auto& sym : m_symbolDefs )
874 {
875 if( !sym.is_pin_decal )
876 continue;
877
878 for( const auto& graphic : sym.graphics )
879 {
880 if( graphic.type != GRAPHIC_TYPE::LINE && graphic.type != GRAPHIC_TYPE::POLYLINE )
881 continue;
882
883 if( graphic.points.size() < 2 )
884 continue;
885
886 const auto& first = graphic.points.front().coord;
887 const auto& last = graphic.points.back().coord;
888 double dx = last.x - first.x;
889 double dy = last.y - first.y;
890 pinDecalLengths[sym.name] = std::sqrt( dx * dx + dy * dy );
891 break;
892 }
893 }
894
895 for( auto& sym : m_symbolDefs )
896 {
897 if( sym.is_pin_decal )
898 continue;
899
900 for( auto& pin : sym.pins )
901 {
902 if( pin.pin_decal_name.empty() )
903 continue;
904
905 auto it = pinDecalLengths.find( pin.pin_decal_name );
906
907 if( it != pinDecalLengths.end() )
908 pin.length = it->second;
909 }
910 }
911
912 return i > 0 ? i - 1 : aLines.size() - 1;
913}
914
915
916size_t PADS_SCH_PARSER::parseSymbolDef( const std::vector<std::string>& aLines, size_t aStartLine, SYMBOL_DEF& aSymbol )
917{
918 if( aStartLine >= aLines.size() )
919 return aStartLine;
920
921 const std::string& headerLine = aLines[aStartLine];
922
923 // Header fields: name f1 f2 height width h2 w2 num_attrs num_pieces has_polarity num_pins
924 // pin_origin_code is_pin_decal
925 std::istringstream iss( headerLine );
926 std::string name;
927 iss >> name;
928
929 if( name.empty() )
930 return aStartLine;
931
932 aSymbol.name = name;
933
934 std::vector<std::string> tokens;
935 std::string token;
936
937 while( iss >> token )
938 tokens.push_back( token );
939
940 if( tokens.size() >= 12 )
941 {
942 aSymbol.f1 = PADS_COMMON::ParseInt( tokens[0], 0, "CAEDECAL header" );
943 aSymbol.f2 = PADS_COMMON::ParseInt( tokens[1], 0, "CAEDECAL header" );
944 aSymbol.height = PADS_COMMON::ParseInt( tokens[2], 0, "CAEDECAL header" );
945 aSymbol.width = PADS_COMMON::ParseInt( tokens[3], 0, "CAEDECAL header" );
946 aSymbol.h2 = PADS_COMMON::ParseInt( tokens[4], 0, "CAEDECAL header" );
947 aSymbol.w2 = PADS_COMMON::ParseInt( tokens[5], 0, "CAEDECAL header" );
948 aSymbol.num_attrs = PADS_COMMON::ParseInt( tokens[6], 0, "CAEDECAL header" );
949 aSymbol.num_pieces = PADS_COMMON::ParseInt( tokens[7], 0, "CAEDECAL header" );
950 aSymbol.has_polarity = PADS_COMMON::ParseInt( tokens[8], 0, "CAEDECAL header" );
951 aSymbol.num_pins = PADS_COMMON::ParseInt( tokens[9], 0, "CAEDECAL header" );
952 aSymbol.pin_origin_code = PADS_COMMON::ParseInt( tokens[10], 0, "CAEDECAL header" );
953 aSymbol.is_pin_decal = PADS_COMMON::ParseInt( tokens[11], 0, "CAEDECAL header" );
954 }
955 else
956 {
957 // Simplified format: name num_pieces num_pins gate_count
958 if( tokens.size() >= 3 )
959 {
960 aSymbol.num_pieces = PADS_COMMON::ParseInt( tokens[0], 0, "CAEDECAL simplified" );
961 aSymbol.num_pins = PADS_COMMON::ParseInt( tokens[1], 0, "CAEDECAL simplified" );
962 aSymbol.gate_count = PADS_COMMON::ParseInt( tokens[2], 0, "CAEDECAL simplified" );
963 }
964 else
965 {
966 return aStartLine;
967 }
968
969 size_t idx = aStartLine + 1;
970
971 for( int p = 0; p < aSymbol.num_pieces && idx < aLines.size(); p++ )
972 {
973 const std::string& gline = aLines[idx];
974
975 if( gline.empty() || isSectionMarker( gline ) )
976 break;
977
978 SYMBOL_GRAPHIC graphic;
979 std::istringstream giss( gline );
980 std::string typeStr;
981 giss >> typeStr;
982
983 if( typeStr == "OPEN" || typeStr == "LINE" )
984 {
985 graphic.type = GRAPHIC_TYPE::LINE;
986 POINT p1, p2;
987 giss >> p1.x >> p1.y >> p2.x >> p2.y;
988 graphic.points.push_back( { p1, std::nullopt } );
989 graphic.points.push_back( { p2, std::nullopt } );
990 giss >> graphic.line_width;
991 }
992 else if( typeStr == "CLOSED" || typeStr == "RECT" )
993 {
995 POINT p1, p2;
996 giss >> p1.x >> p1.y >> p2.x >> p2.y;
997 graphic.points.push_back( { p1, std::nullopt } );
998 graphic.points.push_back( { p2, std::nullopt } );
999 giss >> graphic.line_width;
1000 }
1001 else if( typeStr == "CIRCLE" )
1002 {
1003 graphic.type = GRAPHIC_TYPE::CIRCLE;
1004 giss >> graphic.center.x >> graphic.center.y >> graphic.radius;
1005 giss >> graphic.line_width;
1006 }
1007
1008 aSymbol.graphics.push_back( graphic );
1009 idx++;
1010 }
1011
1012 for( int p = 0; p < aSymbol.num_pins && idx < aLines.size(); p++ )
1013 {
1014 const std::string& pline = aLines[idx];
1015
1016 if( pline.empty() || isSectionMarker( pline ) )
1017 break;
1018
1020 std::istringstream piss( pline );
1021 double orientation = 0;
1022
1023 piss >> pin.position.x >> pin.position.y >> orientation >> pin.length;
1024 piss >> pin.number >> pin.name;
1025
1026 pin.rotation = orientation;
1027
1028 std::string typeStr;
1029
1030 if( piss >> typeStr )
1031 pin.type = parsePinType( typeStr );
1032
1033 aSymbol.pins.push_back( pin );
1034 idx++;
1035 }
1036
1037 return idx - 1;
1038 }
1039
1040 size_t idx = aStartLine + 1;
1041
1042 if( idx < aLines.size() && aLines[idx].find( "TIMESTAMP" ) == 0 )
1043 {
1044 std::istringstream tiss( aLines[idx] );
1045 std::string kw;
1046 tiss >> kw >> aSymbol.timestamp;
1047 idx++;
1048 }
1049
1050 // Two optional font-name lines, absent in older formats
1051 if( idx < aLines.size() && aLines[idx].size() >= 2 && aLines[idx][0] == '"' )
1052 {
1053 size_t qEnd = aLines[idx].find( '"', 1 );
1054
1055 if( qEnd != std::string::npos )
1056 aSymbol.font1 = aLines[idx].substr( 1, qEnd - 1 );
1057
1058 idx++;
1059 }
1060
1061 if( idx < aLines.size() && aLines[idx].size() >= 2 && aLines[idx][0] == '"' )
1062 {
1063 size_t qEnd = aLines[idx].find( '"', 1 );
1064
1065 if( qEnd != std::string::npos )
1066 aSymbol.font2 = aLines[idx].substr( 1, qEnd - 1 );
1067
1068 idx++;
1069 }
1070
1071 // Attribute label pairs, each a position line then a name line
1072 for( int a = 0; a < aSymbol.num_attrs && idx + 1 < aLines.size(); a++ )
1073 {
1074 CAEDECAL_ATTR attr;
1075 std::istringstream aiss( aLines[idx] );
1076 int x = 0, y = 0;
1077
1078 aiss >> x >> y >> attr.angle >> attr.justification >> attr.height >> attr.width;
1079 attr.position.x = x;
1080 attr.position.y = y;
1081
1082 std::string rest;
1083 std::getline( aiss, rest );
1084 size_t qStart = rest.find( '"' );
1085
1086 if( qStart != std::string::npos )
1087 {
1088 size_t qEnd = rest.find( '"', qStart + 1 );
1089
1090 if( qEnd != std::string::npos )
1091 attr.font_name = rest.substr( qStart + 1, qEnd - qStart - 1 );
1092 }
1093
1094 idx++;
1095
1096 if( idx < aLines.size() )
1097 attr.attr_name = aLines[idx];
1098
1099 aSymbol.attrs.push_back( attr );
1100 idx++;
1101 }
1102
1103 for( int p = 0; p < aSymbol.num_pieces && idx < aLines.size(); p++ )
1104 {
1105 if( aLines[idx].empty() )
1106 {
1107 idx++;
1108 p--;
1109 continue;
1110 }
1111
1112 SYMBOL_GRAPHIC graphic;
1113 idx = parseGraphicPrimitive( aLines, idx, graphic );
1114 aSymbol.graphics.push_back( graphic );
1115 idx++;
1116 }
1117
1118 // Embedded text labels sit between graphics and pins. Scan until a T-prefixed pin line; a
1119 // blank line ends the entry, which matters for entries with num_pins == 0 like pin decals.
1120 while( idx < aLines.size() )
1121 {
1122 const std::string& tline = aLines[idx];
1123
1124 if( tline.empty() )
1125 break;
1126
1127 if( isSectionMarker( tline ) )
1128 break;
1129
1130 // A pin T-line starts with T followed by a digit or minus sign
1131 if( tline.size() > 1 && tline[0] == 'T'
1132 && ( std::isdigit( static_cast<unsigned char>( tline[1] ) ) || tline[1] == '-' ) )
1133 {
1134 break;
1135 }
1136
1138 std::istringstream tiss( tline );
1139 int tx = 0, ty = 0;
1140
1141 tiss >> tx >> ty >> text.rotation >> text.justification;
1142
1143 int height = 0, width = 0;
1144 tiss >> height >> width;
1145 text.size = height;
1146 text.width_factor = width;
1147
1148 text.position.x = tx;
1149 text.position.y = ty;
1150
1151 idx++;
1152
1153 if( idx < aLines.size() )
1154 {
1155 text.content = aLines[idx];
1156 idx++;
1157 }
1158
1159 aSymbol.texts.push_back( text );
1160 }
1161
1162 // Each pin is a T line followed by a P line
1163 for( int p = 0; p < aSymbol.num_pins && idx < aLines.size(); p++ )
1164 {
1165 const std::string& tLine = aLines[idx];
1166
1167 if( tLine.empty() )
1168 {
1169 idx++;
1170 p--;
1171 continue;
1172 }
1173
1174 if( isSectionMarker( tLine ) )
1175 break;
1176
1178
1179 // T line: T<x> y angle side pn_h pn_w pn_angle pn_just pl_h pl_w pl_angle pl_just
1180 // pin_decal_name
1181 if( tLine.size() > 1 && tLine[0] == 'T' )
1182 {
1183 std::string tContent = tLine.substr( 1 );
1184 std::istringstream tiss( tContent );
1185 int tx = 0, ty = 0, angle = 0;
1186
1187 tiss >> tx >> ty >> angle >> pin.side >> pin.pn_h >> pin.pn_w >> pin.pn_angle >> pin.pn_just >> pin.pl_h
1188 >> pin.pl_w >> pin.pl_angle >> pin.pl_just >> pin.pin_decal_name;
1189
1190 pin.position.x = tx;
1191 pin.position.y = ty;
1192 pin.rotation = angle;
1193
1194 // The pin decal name encodes inverted/clock styles
1195 if( pin.pin_decal_name == "PINB" || pin.pin_decal_name == "PINORB" || pin.pin_decal_name == "PCLKB"
1196 || pin.pin_decal_name == "PINIEB" || pin.pin_decal_name == "PINCLKB" )
1197 {
1198 pin.inverted = true;
1199 }
1200
1201 if( pin.pin_decal_name == "PCLK" || pin.pin_decal_name == "PCLKB" || pin.pin_decal_name == "PINCLK"
1202 || pin.pin_decal_name == "PINCLKB" )
1203 {
1204 pin.clock = true;
1205 }
1206
1207 // A self-contained pin decal (no name) draws its own graphics, so its stub length
1208 // is zero; named decals map to a fixed stub length.
1209 if( pin.pin_decal_name.empty() )
1210 pin.length = 0.0;
1211 else if( pin.pin_decal_name.find( "SHORT" ) != std::string::npos )
1212 pin.length = 100.0;
1213 else if( pin.pin_decal_name.find( "LONG" ) != std::string::npos )
1214 pin.length = 300.0;
1215 }
1216
1217 idx++;
1218
1219 // P line: P<x1> y1 angle1 just1 x2 y2 angle2 just2 flags
1220 if( idx < aLines.size() )
1221 {
1222 const std::string& pLine = aLines[idx];
1223
1224 if( pLine.size() > 1 && pLine[0] == 'P' )
1225 {
1226 std::string pContent = pLine.substr( 1 );
1227 std::istringstream piss( pContent );
1228 int px1 = 0, py1 = 0, px2 = 0, py2 = 0;
1229
1230 piss >> px1 >> py1 >> pin.pn_off_angle >> pin.pn_off_just >> px2 >> py2 >> pin.pl_off_angle
1231 >> pin.pl_off_just >> pin.p_flags;
1232
1233 pin.pn_offset.x = px1;
1234 pin.pn_offset.y = py1;
1235 pin.pl_offset.x = px2;
1236 pin.pl_offset.y = py2;
1237
1238 // Pin name hidden if flags bit 128 set
1239 if( pin.p_flags & 128 )
1240 pin.name = "";
1241 }
1242
1243 idx++;
1244 }
1245
1246 // Placeholder number; the real assignment comes from the PARTTYPE section
1247 pin.number = std::to_string( p + 1 );
1248
1249 aSymbol.pins.push_back( pin );
1250 }
1251
1252 return idx > 0 ? idx - 1 : 0;
1253}
1254
1255
1256size_t PADS_SCH_PARSER::parseGraphicPrimitive( const std::vector<std::string>& aLines, size_t aStartLine,
1257 SYMBOL_GRAPHIC& aGraphic )
1258{
1259 if( aStartLine >= aLines.size() )
1260 return aStartLine;
1261
1262 const std::string& headerLine = aLines[aStartLine];
1263 std::istringstream iss( headerLine );
1264 std::string typeStr;
1265 int pointCount = 0, lineWidth = 0, lineStyle = 255;
1266
1267 iss >> typeStr >> pointCount >> lineWidth >> lineStyle;
1268
1269 aGraphic.line_width = lineWidth;
1270 aGraphic.line_style = lineStyle;
1271
1272 if( typeStr == "OPEN" )
1273 {
1274 aGraphic.type = GRAPHIC_TYPE::POLYLINE;
1275 aGraphic.filled = false;
1276 }
1277 else if( typeStr == "CLOSED" )
1278 {
1279 aGraphic.type = GRAPHIC_TYPE::RECTANGLE;
1280 aGraphic.filled = false;
1281 }
1282 else if( typeStr == "CIRCLE" )
1283 {
1284 aGraphic.type = GRAPHIC_TYPE::CIRCLE;
1285 aGraphic.filled = false;
1286 }
1287 else if( typeStr == "COPCLS" )
1288 {
1289 aGraphic.type = GRAPHIC_TYPE::RECTANGLE;
1290 aGraphic.filled = true;
1291 }
1292
1293 size_t idx = aStartLine + 1;
1294
1295 for( int p = 0; p < pointCount && idx < aLines.size(); p++ )
1296 {
1297 const std::string& ptLine = aLines[idx];
1298
1299 if( ptLine.empty() || isSectionMarker( ptLine ) )
1300 break;
1301
1302 std::istringstream piss( ptLine );
1303 GRAPHIC_POINT gpt;
1304 piss >> gpt.coord.x >> gpt.coord.y;
1305
1306 std::vector<std::string> extraTokens;
1307 std::string tok;
1308
1309 while( piss >> tok )
1310 extraTokens.push_back( tok );
1311
1312 if( extraTokens.size() >= 6 )
1313 {
1314 ARC_DATA arcData;
1315 arcData.bulge = PADS_COMMON::ParseDouble( extraTokens[0], 0.0, "arc data" );
1316 arcData.angle = PADS_COMMON::ParseDouble( extraTokens[1], 0.0, "arc data" );
1317 arcData.bbox_x1 = PADS_COMMON::ParseDouble( extraTokens[2], 0.0, "arc data" );
1318 arcData.bbox_y1 = PADS_COMMON::ParseDouble( extraTokens[3], 0.0, "arc data" );
1319 arcData.bbox_x2 = PADS_COMMON::ParseDouble( extraTokens[4], 0.0, "arc data" );
1320 arcData.bbox_y2 = PADS_COMMON::ParseDouble( extraTokens[5], 0.0, "arc data" );
1321 gpt.arc = arcData;
1322 }
1323
1324 aGraphic.points.push_back( gpt );
1325
1326 idx++;
1327 }
1328
1329 // A closed polygon reduces to two corner points when it is an axis-aligned rectangle;
1330 // otherwise it becomes a POLYLINE.
1331 if( aGraphic.type == GRAPHIC_TYPE::RECTANGLE && aGraphic.points.size() >= 4 )
1332 {
1333 std::set<double> uniqueX, uniqueY;
1334
1335 for( const auto& pt : aGraphic.points )
1336 {
1337 uniqueX.insert( pt.coord.x );
1338 uniqueY.insert( pt.coord.y );
1339 }
1340
1341 bool isRect = ( uniqueX.size() == 2 && uniqueY.size() == 2 );
1342
1343 if( isRect )
1344 {
1345 double minX = *uniqueX.begin();
1346 double maxX = *uniqueX.rbegin();
1347 double minY = *uniqueY.begin();
1348 double maxY = *uniqueY.rbegin();
1349
1350 aGraphic.points.clear();
1351 aGraphic.points.push_back( { { minX, minY }, std::nullopt } );
1352 aGraphic.points.push_back( { { maxX, maxY }, std::nullopt } );
1353 }
1354 else
1355 {
1356 aGraphic.type = GRAPHIC_TYPE::POLYLINE;
1357 }
1358 }
1359
1360 // A two-point circle gives its diameter; derive center and radius
1361 if( aGraphic.type == GRAPHIC_TYPE::CIRCLE && aGraphic.points.size() == 2 )
1362 {
1363 aGraphic.center.x = ( aGraphic.points[0].coord.x + aGraphic.points[1].coord.x ) / 2.0;
1364 aGraphic.center.y = ( aGraphic.points[0].coord.y + aGraphic.points[1].coord.y ) / 2.0;
1365 double dx = aGraphic.points[1].coord.x - aGraphic.points[0].coord.x;
1366 double dy = aGraphic.points[1].coord.y - aGraphic.points[0].coord.y;
1367 aGraphic.radius = std::sqrt( dx * dx + dy * dy ) / 2.0;
1368 }
1369
1370 return idx > 0 ? idx - 1 : aStartLine;
1371}
1372
1373
1374size_t PADS_SCH_PARSER::parseSectionPARTTYPE( const std::vector<std::string>& aLines, size_t aStartLine )
1375{
1376 size_t i = aStartLine + 1;
1377
1378 while( i < aLines.size() )
1379 {
1380 const std::string& line = aLines[i];
1381
1382 if( line.empty() )
1383 {
1384 i++;
1385 continue;
1386 }
1387
1388 if( isSectionMarker( line ) )
1389 return i - 1;
1390
1391 // Header fields: name category num_physical num_sigpins unused num_swap_groups
1392 PARTTYPE_DEF pt;
1393 std::istringstream iss( line );
1394 iss >> pt.name >> pt.category >> pt.num_physical >> pt.num_sigpins >> pt.unused >> pt.num_swap_groups;
1395
1396 if( pt.name.empty() )
1397 {
1398 i++;
1399 continue;
1400 }
1401
1402 // PADS marks connector part types with a "CN" or "CON" category. Connectors
1403 // number their pins regardless of the gate keyword that follows, so flag them
1404 // here rather than only in the per-keyword branches below.
1405 if( pt.category == "CN" || pt.category == "CON" )
1406 pt.is_connector = true;
1407
1408 i++;
1409
1410 if( i < aLines.size() && aLines[i].find( "TIMESTAMP" ) == 0 )
1411 {
1412 std::istringstream tiss( aLines[i] );
1413 std::string kw;
1414 tiss >> kw >> pt.timestamp;
1415 i++;
1416 }
1417
1418 bool isSpecial = ( pt.name == "$GND_SYMS" || pt.name == "$PWR_SYMS" || pt.name == "$OSR_SYMS" );
1419
1420 // Older files use "G:decal swap num_pins" gate lines; newer ones use
1421 // "GATE num_variants num_pins swap" followed by decal lines. A leading "G:" marks the
1422 // older form.
1423 bool isV52Gates = ( i < aLines.size() && aLines[i].size() >= 3 && aLines[i][0] == 'G' && aLines[i][1] == ':' );
1424
1425 if( isSpecial && !isV52Gates )
1426 {
1427 // Newer special-symbol form: keyword num_variants, then variant lines
1428 if( i < aLines.size() )
1429 {
1430 std::istringstream siss( aLines[i] );
1431 int numVariants = 0;
1432 siss >> pt.special_keyword >> numVariants;
1433 i++;
1434
1435 for( int v = 0; v < numVariants && i < aLines.size(); v++ )
1436 {
1438 std::istringstream viss( aLines[i] );
1439 viss >> sv.decal_name >> sv.pin_type;
1440
1441 std::string rest;
1442
1443 if( viss >> rest )
1444 sv.net_suffix = rest;
1445
1446 pt.special_variants.push_back( sv );
1447 i++;
1448 }
1449 }
1450 }
1451 else if( i < aLines.size() )
1452 {
1453 if( isSpecial )
1454 {
1455 if( pt.name == "$GND_SYMS" )
1456 pt.special_keyword = "GND";
1457 else if( pt.name == "$PWR_SYMS" )
1458 pt.special_keyword = "PWR";
1459 else
1460 pt.special_keyword = "OSR";
1461 }
1462
1463 // Standard parts hold GATE, CONN or G: blocks
1464 while( i < aLines.size() )
1465 {
1466 const std::string& gline = aLines[i];
1467
1468 if( gline.empty() )
1469 {
1470 break;
1471 }
1472
1473 if( isSectionMarker( gline ) )
1474 break;
1475
1476 std::istringstream giss( gline );
1477 std::string keyword;
1478 giss >> keyword;
1479
1480 if( keyword == "GATE" )
1481 {
1482 GATE_DEF gate;
1483 giss >> gate.num_decal_variants >> gate.num_pins >> gate.swap_flag;
1484 i++;
1485
1486 for( int d = 0; d < gate.num_decal_variants && i < aLines.size(); d++ )
1487 {
1488 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1489 break;
1490
1491 gate.decal_names.push_back( aLines[i] );
1492 i++;
1493 }
1494
1495 for( int p = 0; p < gate.num_pins && i < aLines.size(); p++ )
1496 {
1497 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1498 break;
1499
1501 std::istringstream piss( aLines[i] );
1502 std::string pinType;
1503
1504 piss >> pin.pin_id >> pin.swap_group >> pinType;
1505
1506 if( !pinType.empty() )
1507 pin.pin_type = pinType[0];
1508
1509 std::string pinName;
1510
1511 if( piss >> pinName )
1512 pin.pin_name = pinName;
1513
1514 gate.pins.push_back( pin );
1515 i++;
1516 }
1517
1518 pt.gates.push_back( gate );
1519 continue;
1520 }
1521 else if( keyword.size() >= 3 && keyword[0] == 'G' && keyword[1] == ':' )
1522 {
1523 // Gate form G:decal1[:decal2:...] swap_flag num_pins, with dot-separated
1524 // pin fields packed multiple per line.
1525 if( pt.category == "CON" )
1526 pt.is_connector = true;
1527
1528 GATE_DEF gate;
1529
1530 std::string decalStr = keyword.substr( 2 );
1531 std::istringstream diss( decalStr );
1532 std::string decalName;
1533
1534 while( std::getline( diss, decalName, ':' ) )
1535 {
1536 if( !decalName.empty() )
1537 gate.decal_names.push_back( decalName );
1538 }
1539
1540 gate.num_decal_variants = static_cast<int>( gate.decal_names.size() );
1541 giss >> gate.swap_flag >> gate.num_pins;
1542 i++;
1543
1544 int pinsRead = 0;
1545
1546 while( pinsRead < gate.num_pins && i < aLines.size() )
1547 {
1548 const std::string& pline = aLines[i];
1549
1550 if( pline.empty() || isSectionMarker( pline ) )
1551 break;
1552
1553 if( ( pline[0] == 'G' && pline.size() >= 2 && pline[1] == ':' ) || pline.find( "SIGPIN" ) == 0 )
1554 {
1555 break;
1556 }
1557
1558 std::istringstream piss( pline );
1559 std::string pinToken;
1560
1561 while( piss >> pinToken && pinsRead < gate.num_pins )
1562 {
1564 std::vector<std::string> fields;
1565 std::istringstream fiss( pinToken );
1566 std::string field;
1567
1568 while( std::getline( fiss, field, '.' ) )
1569 fields.push_back( field );
1570
1571 if( fields.size() >= 1 )
1572 pin.pin_id = fields[0];
1573
1574 if( fields.size() >= 2 )
1575 {
1576 pin.swap_group = PADS_COMMON::ParseInt( fields[1], 0, "V5.2 pin" );
1577 }
1578
1579 if( fields.size() >= 3 && !fields[2].empty() )
1580 pin.pin_type = fields[2][0];
1581
1582 if( fields.size() >= 4 )
1583 pin.pin_name = fields[3];
1584
1585 gate.pins.push_back( pin );
1586 pinsRead++;
1587 }
1588
1589 i++;
1590 }
1591
1592 if( isSpecial )
1593 {
1595 sv.decal_name = gate.decal_names.empty() ? "" : gate.decal_names[0];
1596
1597 if( !gate.pins.empty() )
1598 sv.pin_type = std::string( 1, gate.pins[0].pin_type );
1599
1600 pt.special_variants.push_back( sv );
1601 }
1602
1603 pt.gates.push_back( gate );
1604 continue;
1605 }
1606 else if( keyword == "CONN" )
1607 {
1608 pt.is_connector = true;
1609 GATE_DEF gate;
1610 int numPins = 0;
1611 giss >> gate.num_decal_variants >> numPins;
1612 gate.num_pins = numPins;
1613 i++;
1614
1615 for( int d = 0; d < gate.num_decal_variants && i < aLines.size(); d++ )
1616 {
1617 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1618 break;
1619
1620 std::istringstream diss( aLines[i] );
1621 std::string decalName, pinType;
1622 diss >> decalName >> pinType;
1623 gate.decal_names.push_back( decalName );
1624 i++;
1625 }
1626
1627 for( int p = 0; p < numPins && i < aLines.size(); p++ )
1628 {
1629 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1630 break;
1631
1633 std::istringstream piss( aLines[i] );
1634 std::string pinType;
1635
1636 piss >> pin.pin_id >> pin.swap_group >> pinType;
1637
1638 if( !pinType.empty() )
1639 pin.pin_type = pinType[0];
1640
1641 gate.pins.push_back( pin );
1642 i++;
1643 }
1644
1645 pt.gates.push_back( gate );
1646 continue;
1647 }
1648 else if( keyword == "SIGPIN" )
1649 {
1651 std::string token;
1652 giss >> token;
1653
1654 // Older files pack the fields dot-separated (e.g. "1.50.DGND"); newer ones
1655 // use space-separated "pin_number net_name".
1656 if( token.find( '.' ) != std::string::npos )
1657 {
1658 std::vector<std::string> fields;
1659 std::istringstream fiss( token );
1660 std::string field;
1661
1662 while( std::getline( fiss, field, '.' ) )
1663 fields.push_back( field );
1664
1665 if( fields.size() >= 1 )
1666 sp.pin_number = fields[0];
1667
1668 if( fields.size() >= 3 )
1669 sp.net_name = fields.back();
1670 }
1671 else
1672 {
1673 sp.pin_number = token;
1674 giss >> sp.net_name;
1675 }
1676
1677 pt.sigpins.push_back( sp );
1678 i++;
1679 continue;
1680 }
1681 else
1682 {
1683 // Swap-group or unrecognized line; keep it
1684 pt.swap_lines.push_back( gline );
1685 i++;
1686 continue;
1687 }
1688 }
1689 }
1690
1691 m_partTypes[pt.name] = pt;
1692 }
1693
1694 return aLines.size() - 1;
1695}
1696
1697
1698size_t PADS_SCH_PARSER::parseSectionPART( const std::vector<std::string>& aLines, size_t aStartLine )
1699{
1700 size_t i = aStartLine + 1;
1701
1702 while( i < aLines.size() && aLines[i].empty() )
1703 i++;
1704
1705 while( i < aLines.size() )
1706 {
1707 const std::string& line = aLines[i];
1708
1709 if( line.empty() )
1710 {
1711 i++;
1712 continue;
1713 }
1714
1715 if( isSectionMarker( line ) )
1716 return i - 1;
1717
1718 // A part header begins with its alpha reference designator
1719 if( std::isalpha( static_cast<unsigned char>( line[0] ) ) )
1720 {
1721 PART_PLACEMENT part;
1722 i = parsePartPlacement( aLines, i, part );
1723
1724 if( !part.reference.empty() )
1725 {
1727 m_partPlacements.push_back( std::move( part ) );
1728 }
1729
1730 i++;
1731 continue;
1732 }
1733
1734 i++;
1735 }
1736
1737 return aLines.size() - 1;
1738}
1739
1740
1741size_t PADS_SCH_PARSER::parsePartPlacement( const std::vector<std::string>& aLines, size_t aStartLine,
1742 PART_PLACEMENT& aPart )
1743{
1744 if( aStartLine >= aLines.size() )
1745 return aStartLine;
1746
1747 const std::string& headerLine = aLines[aStartLine];
1748 std::istringstream iss( headerLine );
1749
1750 // Two header forms, distinguished by whether the third token is numeric:
1751 // Normal: ref part_type x y angle mirror h1 w1 h2 w2 attrs disp pins u1 gate u2
1752 // Power: ref net_name $part_type x y angle mirror variant_index
1753 std::string refdes, partType;
1754 int x = 0, y = 0, angleCode = 0, mirrorFlag = 0;
1755
1756 iss >> refdes >> partType;
1757
1758 if( !( iss >> x ) )
1759 {
1760 // Non-numeric third field (e.g. "$PWR_SYMS") means a power symbol entry
1761 iss.clear();
1762 std::string actualPartType;
1763 iss >> actualPartType >> x >> y >> angleCode >> mirrorFlag;
1764
1765 aPart.power_net_name = partType;
1766 partType = actualPartType;
1767 }
1768 else
1769 {
1770 iss >> y >> angleCode >> mirrorFlag;
1771 }
1772
1773 aPart.reference = refdes;
1774 aPart.part_type = partType;
1775 aPart.symbol_name = partType;
1776 aPart.position.x = x;
1777 aPart.position.y = y;
1778
1779 switch( angleCode )
1780 {
1781 case 0: aPart.rotation = 0.0; break;
1782 case 1: aPart.rotation = 90.0; break;
1783 case 2: aPart.rotation = 180.0; break;
1784 case 3: aPart.rotation = 270.0; break;
1785 default: aPart.rotation = angleCode; break;
1786 }
1787
1788 aPart.mirror_flags = mirrorFlag;
1789
1790 if( !aPart.power_net_name.empty() )
1791 {
1792 // The remaining field is the variant index
1793 int variantIdx = 0;
1794
1795 if( iss >> variantIdx )
1796 aPart.gate_index = variantIdx;
1797 }
1798 else
1799 {
1800 int numAttrs = 0, numDisplayedValues = 0, numPins = 0, unused1 = 0, gateIdx = 0;
1801 int unused2 = 0;
1802
1803 if( iss >> aPart.h1 >> aPart.w1 >> aPart.h2 >> aPart.w2 >> numAttrs >> numDisplayedValues >> numPins >> unused1
1804 >> gateIdx >> unused2 )
1805 {
1806 aPart.num_attrs = numAttrs;
1807 aPart.num_displayed_values = numDisplayedValues;
1808 aPart.num_pins = numPins;
1809 aPart.gate_index = gateIdx;
1810 aPart.gate_number = gateIdx + 1;
1811 }
1812 else
1813 {
1814 // Simplified format: ref part_type x y angle mirror sheet gate
1815 std::istringstream iss2( headerLine );
1816 std::string dummy;
1817 iss2 >> dummy >> dummy >> x >> y;
1818
1819 double rotDeg = 0;
1820 iss2 >> rotDeg;
1821 aPart.rotation = rotDeg;
1822
1823 std::string mirrorStr;
1824
1825 if( iss2 >> mirrorStr )
1826 {
1827 if( mirrorStr == "M" || mirrorStr == "Y" || mirrorStr == "1" )
1828 aPart.mirror_flags = 1;
1829 }
1830
1831 iss2 >> aPart.sheet_number >> aPart.gate_number;
1832 }
1833 }
1834
1835 // Multi-gate components encode the gate in the reference suffix (U17-A, U1.B)
1836 size_t sepPos = refdes.rfind( '-' );
1837
1838 if( sepPos == std::string::npos )
1839 sepPos = refdes.rfind( '.' );
1840
1841 if( sepPos != std::string::npos && sepPos + 1 < refdes.size() )
1842 {
1843 char gateLetter = refdes[sepPos + 1];
1844
1845 if( std::isalpha( static_cast<unsigned char>( gateLetter ) ) )
1846 {
1847 // Derive the gate index from the letter only when the header gave none
1848 if( aPart.gate_index == 0 )
1849 {
1850 aPart.gate_index = std::toupper( static_cast<unsigned char>( gateLetter ) ) - 'A';
1851 aPart.gate_number = aPart.gate_index + 1;
1852 }
1853 }
1854 }
1855
1856 auto partTypeDefinition = m_partTypes.find( aPart.part_type );
1857
1858 if( partTypeDefinition != m_partTypes.end() )
1859 {
1860 const PARTTYPE_DEF& definition = partTypeDefinition->second;
1861
1862 if( aPart.gate_index >= 0 && static_cast<size_t>( aPart.gate_index ) < definition.gates.size()
1863 && !definition.gates[aPart.gate_index].decal_names.empty() )
1864 {
1865 aPart.decal_name = definition.gates[aPart.gate_index].decal_names.front();
1866 }
1867 else if( definition.is_connector && !definition.special_variants.empty() )
1868 {
1869 aPart.decal_name = definition.special_variants.front().decal_name;
1870 }
1871 }
1872
1873 size_t i = aStartLine + 1;
1874
1875 // Full format carries font lines, attribute labels, overrides and pin overrides
1876 if( aPart.num_attrs > 0 || aPart.num_displayed_values > 0 )
1877 {
1878 if( i < aLines.size() )
1879 {
1880 const std::string& fl = aLines[i];
1881
1882 if( fl.size() >= 2 && fl[0] == '"' )
1883 {
1884 size_t qEnd = fl.find( '"', 1 );
1885
1886 if( qEnd != std::string::npos )
1887 aPart.font1 = fl.substr( 1, qEnd - 1 );
1888
1889 i++;
1890 }
1891 }
1892
1893 if( i < aLines.size() )
1894 {
1895 const std::string& fl = aLines[i];
1896
1897 if( fl.size() >= 2 && fl[0] == '"' )
1898 {
1899 size_t qEnd = fl.find( '"', 1 );
1900
1901 if( qEnd != std::string::npos )
1902 aPart.font2 = fl.substr( 1, qEnd - 1 );
1903
1904 i++;
1905 }
1906 }
1907
1908 for( int a = 0; a < aPart.num_attrs && i + 1 < aLines.size(); a++ )
1909 {
1910 PART_ATTRIBUTE attr;
1911 std::istringstream aiss( aLines[i] );
1912 int ax = 0, ay = 0, angle = 0, disp = 0, h = 0, w = 0, vis = 0;
1913
1914 aiss >> ax >> ay >> angle >> disp >> h >> w >> vis;
1915
1916 attr.position.x = ax;
1917 attr.position.y = ay;
1918 attr.rotation = angle;
1919 attr.justification = disp;
1920 attr.height = h;
1921 attr.width = w;
1922 attr.size = h;
1923 attr.visibility = vis;
1924
1925 // PADS uses bit 3 (value 8) of the attribute display flag to mark a label
1926 // hidden. The lower bits select what is displayed (name and/or value), so a
1927 // non-zero flag such as 1 or 3 still denotes a visible label.
1928 attr.visible = ( ( vis & 0x8 ) == 0 );
1929
1930 std::string rest;
1931 std::getline( aiss, rest );
1932 size_t qStart = rest.find( '"' );
1933
1934 if( qStart != std::string::npos )
1935 {
1936 size_t qEnd = rest.find( '"', qStart + 1 );
1937
1938 if( qEnd != std::string::npos )
1939 attr.font_name = rest.substr( qStart + 1, qEnd - qStart - 1 );
1940 }
1941
1942 i++;
1943
1944 if( i < aLines.size() )
1945 attr.name = aLines[i];
1946
1947 aPart.attributes.push_back( attr );
1948 i++;
1949 }
1950
1951 // Each displayed-value override is a position line then a "name" value line
1952 for( int d = 0; d < aPart.num_displayed_values && i < aLines.size(); d++ )
1953 {
1954 if( !aLines[i].empty() && std::isdigit( static_cast<unsigned char>( aLines[i][0] ) ) )
1955 {
1956 i++;
1957 }
1958
1959 if( i >= aLines.size() )
1960 break;
1961
1962 const std::string& valLine = aLines[i];
1963
1964 if( valLine.size() > 2 && valLine[0] == '"' )
1965 {
1966 size_t closeQ = valLine.find( '"', 1 );
1967
1968 if( closeQ != std::string::npos )
1969 {
1970 std::string attrName = valLine.substr( 1, closeQ - 1 );
1971 std::string attrValue;
1972
1973 if( closeQ + 1 < valLine.size() )
1974 {
1975 attrValue = valLine.substr( closeQ + 1 );
1976 size_t start = attrValue.find_first_not_of( " \t" );
1977
1978 if( start != std::string::npos )
1979 attrValue = attrValue.substr( start );
1980 else
1981 attrValue.clear();
1982 }
1983
1984 aPart.attr_overrides[attrName] = attrValue;
1985 }
1986 }
1987
1988 i++;
1989 }
1990
1991 // Populate attr.value from the overrides
1992 for( auto& attr : aPart.attributes )
1993 {
1994 auto it = aPart.attr_overrides.find( attr.name );
1995
1996 if( it != aPart.attr_overrides.end() )
1997 attr.value = it->second;
1998 }
1999
2000 // Pin override lines
2001 while( i < aLines.size() )
2002 {
2003 const std::string& pline = aLines[i];
2004
2005 if( pline.empty() )
2006 break;
2007
2008 if( isSectionMarker( pline ) )
2009 return i - 1;
2010
2011 // An alpha start is the next part
2012 if( std::isalpha( static_cast<unsigned char>( pline[0] ) ) )
2013 return i - 1;
2014
2015 // Pin override: index height width angle justification
2016 if( std::isdigit( static_cast<unsigned char>( pline[0] ) ) )
2017 {
2019 std::istringstream poiss( pline );
2020 int pinIdx = 0;
2021 poiss >> pinIdx >> po.height >> po.width >> po.angle >> po.justification;
2022 aPart.pin_overrides.push_back( po );
2023 }
2024
2025 i++;
2026 }
2027 }
2028 else
2029 {
2030 // Simplified format: @-prefixed attribute lines
2031 while( i < aLines.size() )
2032 {
2033 const std::string& attrLine = aLines[i];
2034
2035 if( attrLine.empty() )
2036 break;
2037
2038 if( isSectionMarker( attrLine ) )
2039 return i - 1;
2040
2041 if( attrLine[0] == '@' )
2042 {
2043 PART_ATTRIBUTE attr;
2044 std::istringstream aiss( attrLine.substr( 1 ) );
2045 aiss >> attr.name;
2046
2047 std::string rest;
2048 std::getline( aiss, rest );
2049 size_t start = rest.find_first_not_of( " \t" );
2050
2051 if( start != std::string::npos )
2052 {
2053 rest = rest.substr( start );
2054
2055 if( !rest.empty() && rest[0] == '"' )
2056 {
2057 size_t endQuote = rest.find( '"', 1 );
2058
2059 if( endQuote != std::string::npos )
2060 {
2061 attr.value = rest.substr( 1, endQuote - 1 );
2062 rest = rest.substr( endQuote + 1 );
2063 }
2064 }
2065 else
2066 {
2067 std::istringstream viss( rest );
2068 viss >> attr.value;
2069 std::getline( viss, rest );
2070 }
2071
2072 std::istringstream piss( rest );
2073 piss >> attr.position.x >> attr.position.y >> attr.rotation >> attr.size;
2074
2075 std::string visStr;
2076
2077 if( piss >> visStr )
2078 attr.visible = ( visStr != "N" && visStr != "0" && visStr != "H" );
2079 }
2080
2081 aPart.attributes.push_back( attr );
2082 i++;
2083 }
2084 else if( std::isalpha( static_cast<unsigned char>( attrLine[0] ) ) )
2085 {
2086 return i - 1;
2087 }
2088 else
2089 {
2090 i++;
2091 }
2092 }
2093 }
2094
2095 return i - 1;
2096}
2097
2098
2099size_t PADS_SCH_PARSER::parseSectionBUSSES( const std::vector<std::string>& aLines, size_t aStartLine )
2100{
2101 constexpr long long MAX_BUS_POINTS = 1'000'000;
2102 size_t i = aStartLine + 1;
2103
2104 // This grammar is inferred rather than observed, and nothing outside the tests reads
2105 // GetBuses(), so a record we cannot read drops that bus instead of the whole import
2106 auto unsupported =
2107 [&]( size_t aLine, const wxString& aDetail )
2108 {
2109 if( m_reporter )
2110 {
2111 m_reporter->Report( wxString::Format( _( "BUSSES line %llu: unsupported record, %s. "
2112 "The bus was skipped." ),
2113 static_cast<unsigned long long>( aLine + 1 ),
2114 aDetail ),
2116 }
2117 };
2118
2119 while( i < aLines.size() )
2120 {
2121 const std::string& line = aLines[i];
2122
2123 if( isSectionMarker( line ) )
2124 return i - 1;
2125
2126 if( line.empty() )
2127 {
2128 ++i;
2129 continue;
2130 }
2131
2132 BUS_DEF bus;
2133 long long parsedPointCount = -1;
2134 std::istringstream header( line );
2135 std::string extra;
2136
2137 if( !( header >> bus.handle >> bus.name >> parsedPointCount ) || header >> extra
2138 || !bus.handle.starts_with( "@@@B" ) || bus.handle.size() == 4
2139 || parsedPointCount < 0 || parsedPointCount > MAX_BUS_POINTS )
2140 {
2141 unsupported( i, wxS( "invalid bus header or point count" ) );
2142 ++i;
2143 continue;
2144 }
2145
2146 const size_t pointCount = static_cast<size_t>( parsedPointCount );
2147 bool busMalformed = false;
2149
2150 if( !bus.name.empty() )
2151 bus.aliases.push_back( bus.name );
2152
2153 ++i;
2154
2155 while( i < aLines.size() && bus.path.size() < pointCount )
2156 {
2157 // Leave the marker or the next handle in place so the outer loop resynchronizes on it
2158 if( isSectionMarker( aLines[i] ) || aLines[i].starts_with( "@@@B" ) )
2159 break;
2160
2161 if( aLines[i].empty() )
2162 {
2163 ++i;
2164 continue;
2165 }
2166
2167 POINT point;
2168 std::istringstream coordinates( aLines[i] );
2169
2170 // A later line must not complete a bus whose earlier point was unreadable
2171 if( !( coordinates >> point.x >> point.y ) || coordinates >> extra )
2172 {
2173 busMalformed = true;
2174 ++i;
2175 continue;
2176 }
2177
2178 bus.path.push_back( point );
2179
2180 ++i;
2181 }
2182
2183 if( busMalformed || bus.path.size() != pointCount )
2184 {
2185 unsupported( i == aLines.size() ? i - 1 : i,
2186 busMalformed ? wxS( "invalid bus point" )
2187 : wxS( "bus ended before its declared point count" ) );
2188 continue;
2189 }
2190
2191 m_buses.push_back( std::move( bus ) );
2192 }
2193
2194 return aLines.size() - 1;
2195}
2196
2197
2198size_t PADS_SCH_PARSER::parseSectionOFFPAGEREFS( const std::vector<std::string>& aLines, size_t aStartLine )
2199{
2200 size_t i = aStartLine + 1;
2201
2202 while( i < aLines.size() )
2203 {
2204 const std::string& line = aLines[i];
2205
2206 if( line.empty() )
2207 {
2208 i++;
2209 continue;
2210 }
2211
2212 if( isSectionMarker( line ) )
2213 return i - 1;
2214
2215 // Fields: @@@O<id> net_name symbol_lib x y rotation flags1 flags2
2216 if( line.find( "@@@O" ) == 0 )
2217 {
2219 std::istringstream iss( line );
2220 std::string idToken;
2221 iss >> idToken;
2222
2223 if( idToken.size() > 4 )
2224 opc.id = PADS_COMMON::ParseInt( idToken.substr( 4 ), 0, "OPC id" );
2225
2226 int x = 0, y = 0;
2227 iss >> opc.signal_name >> opc.symbol_lib >> x >> y >> opc.rotation >> opc.flags1 >> opc.flags2;
2228
2229 opc.position.x = x;
2230 opc.position.y = y;
2232
2233 m_offPageConnectors.push_back( opc );
2234
2235 auto bus = std::find_if( m_buses.begin(), m_buses.end(),
2236 [&]( const BUS_DEF& aBus )
2237 {
2238 return aBus.handle == opc.symbol_lib && aBus.sheet_number == m_currentSheet;
2239 } );
2240
2241 if( bus != m_buses.end() )
2242 {
2243 bus->entries.push_back( { opc.signal_name, opc.position, opc.rotation } );
2244
2245 if( std::find( bus->member_nets.begin(), bus->member_nets.end(), opc.signal_name )
2246 == bus->member_nets.end() )
2247 {
2248 bus->member_nets.push_back( opc.signal_name );
2249 }
2250 }
2251 }
2252
2253 i++;
2254 }
2255
2256 return aLines.size() - 1;
2257}
2258
2259
2260size_t PADS_SCH_PARSER::parseSectionTIEDOTS( const std::vector<std::string>& aLines, size_t aStartLine )
2261{
2262 size_t i = aStartLine + 1;
2263
2264 while( i < aLines.size() )
2265 {
2266 const std::string& line = aLines[i];
2267
2268 if( line.empty() )
2269 {
2270 i++;
2271 continue;
2272 }
2273
2274 if( isSectionMarker( line ) )
2275 return i - 1;
2276
2277 // Fields: @@@D<id> x y
2278 if( line.find( "@@@D" ) == 0 )
2279 {
2280 TIED_DOT dot;
2281 std::istringstream iss( line );
2282 std::string idToken;
2283 iss >> idToken;
2284
2285 if( idToken.size() > 4 )
2286 dot.id = PADS_COMMON::ParseInt( idToken.substr( 4 ), 0, "TIEDOT id" );
2287
2288 int x = 0, y = 0;
2289 iss >> x >> y;
2290 dot.position.x = x;
2291 dot.position.y = y;
2293
2294 m_tiedDots.push_back( dot );
2295 }
2296
2297 i++;
2298 }
2299
2300 return aLines.size() - 1;
2301}
2302
2303
2304size_t PADS_SCH_PARSER::parseSectionCONNECTION( const std::vector<std::string>& aLines, size_t aStartLine )
2305{
2306 // SIGNAL blocks follow inside the CONNECTION section
2307 size_t i = aStartLine + 1;
2308
2309 while( i < aLines.size() )
2310 {
2311 const std::string& line = aLines[i];
2312
2313 if( line.empty() )
2314 {
2315 i++;
2316 continue;
2317 }
2318
2319 // A non-SIGNAL marker ends the CONNECTION section
2320 if( isSectionMarker( line ) )
2321 {
2322 std::string secName = extractSectionName( line );
2323
2324 if( secName == "SIGNAL" )
2325 {
2326 SCH_SIGNAL signal;
2327 i = parseSignalDef( aLines, i, signal );
2328
2329 if( !signal.name.empty() )
2330 {
2331 for( auto& wire : signal.wires )
2332 wire.sheet_number = m_currentSheet;
2333
2334 // Build pin connections from wire endpoint references
2335 for( const auto& wire : signal.wires )
2336 {
2337 for( const auto& ep : { wire.endpoint_a, wire.endpoint_b } )
2338 {
2339 if( ep.find( '.' ) != std::string::npos && ep.find( "@@@" ) == std::string::npos )
2340 {
2341 size_t dotPos = ep.find( '.' );
2342 PIN_CONNECTION conn;
2343 conn.reference = ep.substr( 0, dotPos );
2344 conn.pin_number = ep.substr( dotPos + 1 );
2346
2347 bool found = false;
2348
2349 for( const auto& existing : signal.connections )
2350 {
2351 if( existing.reference == conn.reference && existing.pin_number == conn.pin_number )
2352 {
2353 found = true;
2354 break;
2355 }
2356 }
2357
2358 if( !found )
2359 signal.connections.push_back( conn );
2360 }
2361 }
2362 }
2363
2364 m_signals.push_back( std::move( signal ) );
2365 }
2366
2367 i++;
2368 continue;
2369 }
2370 else
2371 {
2372 return i - 1;
2373 }
2374 }
2375
2376 i++;
2377 }
2378
2379 return aLines.size() - 1;
2380}
2381
2382
2383size_t PADS_SCH_PARSER::parseSignalDef( const std::vector<std::string>& aLines, size_t aStartLine, SCH_SIGNAL& aSignal )
2384{
2385 if( aStartLine >= aLines.size() )
2386 return aStartLine;
2387
2388 // Header fields after the marker: net_name flags1 flags2
2389 const std::string& headerLine = aLines[aStartLine];
2390 std::string secName = extractSectionName( headerLine );
2391
2392 if( secName != "SIGNAL" )
2393 return aStartLine;
2394
2395 size_t afterMarker = headerLine.find( '*', 1 );
2396
2397 if( afterMarker == std::string::npos )
2398 return aStartLine;
2399
2400 std::string rest = headerLine.substr( afterMarker + 1 );
2401 std::istringstream iss( rest );
2402
2403 iss >> aSignal.name >> aSignal.flags1 >> aSignal.flags2;
2404
2405 size_t i = aStartLine + 1;
2406
2407 if( aSignal.flags2 == 1 && i < aLines.size() )
2408 {
2409 const std::string& funcLine = aLines[i];
2410
2411 if( funcLine.find( "\"FUNCTION\"" ) != std::string::npos || funcLine.find( "FUNCTION" ) == 0 )
2412 {
2413 size_t qStart = funcLine.find( '"' );
2414
2415 if( qStart != std::string::npos )
2416 {
2417 size_t qEnd = funcLine.find( '"', qStart + 1 );
2418
2419 if( qEnd != std::string::npos )
2420 {
2421 size_t afterQ = funcLine.find_first_not_of( " \t", qEnd + 1 );
2422
2423 if( afterQ != std::string::npos )
2424 aSignal.function = funcLine.substr( afterQ );
2425 }
2426 }
2427
2428 i++;
2429 }
2430 }
2431
2432 // Each wire is a header line "endpoint_a endpoint_b vertex_count flags" then one line per
2433 // vertex coordinate.
2434 while( i < aLines.size() )
2435 {
2436 const std::string& line = aLines[i];
2437
2438 if( line.empty() )
2439 {
2440 i++;
2441 continue;
2442 }
2443
2444 if( isSectionMarker( line ) )
2445 return i - 1;
2446
2447 WIRE_SEGMENT wire;
2448 std::istringstream wiss( line );
2449 wiss >> wire.endpoint_a >> wire.endpoint_b >> wire.vertex_count >> wire.flags;
2450
2451 if( wire.endpoint_a.empty() || wire.endpoint_b.empty() )
2452 {
2453 i++;
2454 continue;
2455 }
2456
2457 i++;
2458
2459 for( int v = 0; v < wire.vertex_count && i < aLines.size(); v++ )
2460 {
2461 const std::string& ptLine = aLines[i];
2462
2463 if( ptLine.empty() || isSectionMarker( ptLine ) )
2464 break;
2465
2466 POINT pt;
2467 std::istringstream piss( ptLine );
2468 piss >> pt.x >> pt.y;
2469 wire.vertices.push_back( pt );
2470 i++;
2471 }
2472
2473 if( !wire.vertices.empty() )
2474 {
2475 wire.start = wire.vertices.front();
2476 wire.end = wire.vertices.back();
2477 }
2478
2479 aSignal.wires.push_back( wire );
2480 }
2481
2482 return i > 0 ? i - 1 : aStartLine;
2483}
2484
2485
2486size_t PADS_SCH_PARSER::parseSectionNETNAMES( const std::vector<std::string>& aLines, size_t aStartLine )
2487{
2488 size_t i = aStartLine + 1;
2489
2490 while( i < aLines.size() )
2491 {
2492 const std::string& line = aLines[i];
2493
2494 if( line.empty() )
2495 {
2496 i++;
2497 continue;
2498 }
2499
2500 if( isSectionMarker( line ) )
2501 return i - 1;
2502
2503 // Fields: net_name anchor_ref x_offset y_offset rotation justification f3 f4 f5 f6 f7
2504 // height width_pct "font_name"
2505 NETNAME_LABEL label;
2506 std::istringstream iss( line );
2507
2508 iss >> label.net_name >> label.anchor_ref >> label.x_offset >> label.y_offset >> label.rotation
2509 >> label.justification >> label.f3 >> label.f4 >> label.f5 >> label.f6 >> label.f7 >> label.height
2510 >> label.width_pct;
2511
2512 std::string rest;
2513 std::getline( iss, rest );
2514 size_t qStart = rest.find( '"' );
2515
2516 if( qStart != std::string::npos )
2517 {
2518 size_t qEnd = rest.find( '"', qStart + 1 );
2519
2520 if( qEnd != std::string::npos )
2521 label.font_name = rest.substr( qStart + 1, qEnd - qStart - 1 );
2522 }
2523
2524 m_netNameLabels.push_back( label );
2525 i++;
2526 }
2527
2528 return aLines.size() - 1;
2529}
2530
2531
2532size_t PADS_SCH_PARSER::skipBraceDelimitedSection( const std::vector<std::string>& aLines, size_t aStartLine )
2533{
2534 size_t i = aStartLine + 1;
2535 int braceDepth = 0;
2536 bool foundFirstBrace = false;
2537
2538 while( i < aLines.size() )
2539 {
2540 const std::string& line = aLines[i];
2541
2542 for( char c : line )
2543 {
2544 if( c == '{' )
2545 {
2546 braceDepth++;
2547 foundFirstBrace = true;
2548 }
2549 else if( c == '}' )
2550 {
2551 braceDepth--;
2552 }
2553 }
2554
2555 if( foundFirstBrace && braceDepth <= 0 )
2556 return i;
2557
2558 // A section marker before any brace means this section was empty
2559 if( !foundFirstBrace && isSectionMarker( line ) )
2560 return i - 1;
2561
2562 i++;
2563 }
2564
2565 return aLines.size() - 1;
2566}
2567
2568
2569const SYMBOL_DEF* PADS_SCH_PARSER::GetSymbolDef( const std::string& aName ) const
2570{
2571 for( const auto& sym : m_symbolDefs )
2572 {
2573 if( sym.name == aName )
2574 return &sym;
2575 }
2576
2577 return nullptr;
2578}
2579
2580
2581const PART_PLACEMENT* PADS_SCH_PARSER::GetPartPlacement( const std::string& aReference ) const
2582{
2583 for( const auto& part : m_partPlacements )
2584 {
2585 if( part.reference == aReference )
2586 return &part;
2587 }
2588
2589 return nullptr;
2590}
2591
2592
2593const SCH_SIGNAL* PADS_SCH_PARSER::GetSignal( const std::string& aName ) const
2594{
2595 for( const auto& signal : m_signals )
2596 {
2597 if( signal.name == aName )
2598 return &signal;
2599 }
2600
2601 return nullptr;
2602}
2603
2604
2606{
2607 std::set<int> sheets = GetSheetNumbers();
2608
2609 if( sheets.empty() )
2610 return 1;
2611
2612 return *sheets.rbegin();
2613}
2614
2615
2617{
2618 std::set<int> sheets;
2619
2620 for( const auto& header : m_sheetHeaders )
2621 sheets.insert( header.sheet_num );
2622
2623 for( const auto& part : m_partPlacements )
2624 sheets.insert( part.sheet_number );
2625
2626 for( const auto& signal : m_signals )
2627 {
2628 for( const auto& wire : signal.wires )
2629 sheets.insert( wire.sheet_number );
2630
2631 for( const auto& conn : signal.connections )
2632 sheets.insert( conn.sheet_number );
2633 }
2634
2635 if( sheets.empty() )
2636 sheets.insert( 1 );
2637
2638 return sheets;
2639}
2640
2641
2642std::vector<SCH_SIGNAL> PADS_SCH_PARSER::GetSignalsOnSheet( int aSheetNumber ) const
2643{
2644 std::vector<SCH_SIGNAL> result;
2645
2646 for( const auto& signal : m_signals )
2647 {
2648 SCH_SIGNAL filteredSignal;
2649 filteredSignal.name = signal.name;
2650
2651 for( const auto& wire : signal.wires )
2652 {
2653 if( wire.sheet_number == aSheetNumber )
2654 filteredSignal.wires.push_back( wire );
2655 }
2656
2657 for( const auto& conn : signal.connections )
2658 {
2659 if( conn.sheet_number == aSheetNumber )
2660 filteredSignal.connections.push_back( conn );
2661 }
2662
2663 if( !filteredSignal.wires.empty() || !filteredSignal.connections.empty() )
2664 result.push_back( filteredSignal );
2665 }
2666
2667 return result;
2668}
2669
2670
2671std::vector<PART_PLACEMENT> PADS_SCH_PARSER::GetPartsOnSheet( int aSheetNumber ) const
2672{
2673 std::vector<PART_PLACEMENT> result;
2674
2675 for( const auto& part : m_partPlacements )
2676 {
2677 if( part.sheet_number == aSheetNumber )
2678 result.push_back( part );
2679 }
2680
2681 return result;
2682}
2683
2684
2685PIN_TYPE PADS_SCH_PARSER::parsePinType( const std::string& aTypeStr )
2686{
2687 std::string upper = aTypeStr;
2688 std::transform( upper.begin(), upper.end(), upper.begin(), ::toupper );
2689
2690 if( upper == "I" || upper == "IN" || upper == "INPUT" || upper == "L" )
2691 return PIN_TYPE::INPUT;
2692
2693 if( upper == "O" || upper == "OUT" || upper == "OUTPUT" || upper == "S" )
2694 return PIN_TYPE::OUTPUT;
2695
2696 if( upper == "B" || upper == "BI" || upper == "BIDIR" || upper == "BIDIRECTIONAL" )
2698
2699 if( upper == "T" || upper == "TRI" || upper == "TRISTATE" )
2700 return PIN_TYPE::TRISTATE;
2701
2702 if( upper == "OC" || upper == "OPENCOLLECTOR" )
2704
2705 if( upper == "OE" || upper == "OPENEMITTER" )
2707
2708 if( upper == "P" || upper == "PWR" || upper == "POWER" || upper == "G" )
2709 return PIN_TYPE::POWER;
2710
2711 if( upper == "PAS" || upper == "PASSIVE" )
2712 return PIN_TYPE::PASSIVE;
2713
2714 return PIN_TYPE::UNSPECIFIED;
2715}
2716
2717
2719{
2720 switch( std::toupper( aTypeChar ) )
2721 {
2722 case 'L': return PIN_TYPE::INPUT;
2723 case 'S': return PIN_TYPE::OUTPUT;
2724 case 'B': return PIN_TYPE::BIDIRECTIONAL;
2725 case 'P': return PIN_TYPE::POWER;
2726 case 'G': return PIN_TYPE::POWER;
2727 case 'U': return PIN_TYPE::UNSPECIFIED;
2728 default: return PIN_TYPE::UNSPECIFIED;
2729 }
2730}
2731
2732} // namespace PADS_SCH
const char * name
size_t skipBraceDelimitedSection(const std::vector< std::string > &aLines, size_t aStartLine)
size_t parseSectionBUSSES(const std::vector< std::string > &aLines, size_t aStartLine)
size_t parseSectionPARTTYPE(const std::vector< std::string > &aLines, size_t aStartLine)
bool isSectionMarker(const std::string &aLine) const
bool Parse(const std::string &aFileName)
size_t parseSymbolDef(const std::vector< std::string > &aLines, size_t aStartLine, SYMBOL_DEF &aSymbol)
size_t parseSectionSCH(const std::vector< std::string > &aLines, size_t aStartLine)
size_t parseSectionCAEDECAL(const std::vector< std::string > &aLines, size_t aStartLine)
std::vector< OFF_PAGE_CONNECTOR > m_offPageConnectors
size_t parseSectionTIEDOTS(const std::vector< std::string > &aLines, size_t aStartLine)
size_t parsePartPlacement(const std::vector< std::string > &aLines, size_t aStartLine, PART_PLACEMENT &aPart)
size_t parseSectionSHT(const std::vector< std::string > &aLines, size_t aStartLine)
std::vector< SCH_SIGNAL > m_signals
size_t parseSectionOFFPAGEREFS(const std::vector< std::string > &aLines, size_t aStartLine)
std::string extractSectionName(const std::string &aLine) const
size_t parseSectionCONNECTION(const std::vector< std::string > &aLines, size_t aStartLine)
size_t parseSectionLINES(const std::vector< std::string > &aLines, size_t aStartLine)
size_t parseGraphicPrimitive(const std::vector< std::string > &aLines, size_t aStartLine, SYMBOL_GRAPHIC &aGraphic)
static PIN_TYPE ParsePinTypeChar(char aTypeChar)
bool parseHeader(const std::string &aLine)
std::vector< SHEET_HEADER > m_sheetHeaders
size_t parseSectionNETNAMES(const std::vector< std::string > &aLines, size_t aStartLine)
std::vector< SYMBOL_DEF > m_symbolDefs
std::vector< PART_PLACEMENT > m_partPlacements
PIN_TYPE parsePinType(const std::string &aTypeStr)
std::vector< SCH_SIGNAL > GetSignalsOnSheet(int aSheetNumber) const
std::set< int > GetSheetNumbers() const
std::vector< TEXT_ITEM > m_textItems
static bool CheckFileHeader(const std::string &aFileName)
size_t parseSectionTEXT(const std::vector< std::string > &aLines, size_t aStartLine)
size_t parseSignalDef(const std::vector< std::string > &aLines, size_t aStartLine, SCH_SIGNAL &aSignal)
std::vector< NETNAME_LABEL > m_netNameLabels
std::vector< PART_PLACEMENT > GetPartsOnSheet(int aSheetNumber) const
std::vector< LINES_ITEM > m_linesItems
const SCH_SIGNAL * GetSignal(const std::string &aName) const
std::vector< TIED_DOT > m_tiedDots
std::map< std::string, PARTTYPE_DEF > m_partTypes
std::vector< BUS_DEF > m_buses
const SYMBOL_DEF * GetSymbolDef(const std::string &aName) const
const PART_PLACEMENT * GetPartPlacement(const std::string &aReference) const
size_t parseSectionCAE(const std::vector< std::string > &aLines, size_t aStartLine)
size_t parseSectionFIELDS(const std::vector< std::string > &aLines, size_t aStartLine)
size_t parseSectionPART(const std::vector< std::string > &aLines, size_t aStartLine)
static bool empty(const wxTextEntryBase *aCtrl)
static bool isNumber(const char *cp, const char *limit)
Return true if the next sequence of text is a number: either an integer, fixed point,...
Definition dsnlexer.cpp:487
#define _(s)
int ParseInt(const std::string &aStr, int aDefault, const std::string &aContext)
Parse integer from string with error context.
double ParseDouble(const std::string &aStr, double aDefault, const std::string &aContext)
Parse double from string with error context.
VECTOR2I padsSchArcMidpoint(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCenter)
Midpoint of the arc through aStart and aEnd about aCenter, on the minor-arc side (the perpendicular b...
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
std::vector< FAB_LAYER_COLOR > dummy
std::vector< POINT > path
std::vector< std::string > aliases
std::vector< std::string > decal_names
std::vector< PARTTYPE_PIN > pins
std::optional< ARC_DATA > arc
std::vector< TEXT_ITEM > texts
std::vector< SYMBOL_GRAPHIC > primitives
std::vector< GATE_DEF > gates
std::vector< SPECIAL_VARIANT > special_variants
std::vector< std::string > swap_lines
std::vector< SIGPIN > sigpins
std::map< std::string, std::string > attr_overrides
std::string symbol_name
The part type, which is also the power symbol's net name.
std::string decal_name
CAEDECAL the placed gate draws, empty when unresolved.
std::vector< PART_ATTRIBUTE > attributes
std::vector< PIN_OVERRIDE > pin_overrides
std::vector< PIN_CONNECTION > connections
std::vector< WIRE_SEGMENT > wires
std::vector< SYMBOL_GRAPHIC > graphics
std::vector< SYMBOL_PIN > pins
std::vector< CAEDECAL_ATTR > attrs
std::vector< SYMBOL_TEXT > texts
std::vector< GRAPHIC_POINT > points
Wire segment connecting two endpoints through coordinate vertices.
std::vector< POINT > vertices
KIBIS_PIN * pin
int radius
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683