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