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