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 // 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 i++;
1391
1392 // TIMESTAMP line
1393 if( i < aLines.size() && aLines[i].find( "TIMESTAMP" ) == 0 )
1394 {
1395 std::istringstream tiss( aLines[i] );
1396 std::string kw;
1397 tiss >> kw >> pt.timestamp;
1398 i++;
1399 }
1400
1401 // Check for special symbols ($GND_SYMS, $PWR_SYMS, $OSR_SYMS)
1402 bool isSpecial = ( pt.name == "$GND_SYMS" || pt.name == "$PWR_SYMS" ||
1403 pt.name == "$OSR_SYMS" );
1404
1405 // V5.2 uses "G:decalname swap num_pins" for gate definitions while V9.0+ uses
1406 // "GATE num_variants num_pins swap" followed by decal name lines. Detect by
1407 // checking whether the first content line starts with "G:".
1408 bool isV52Gates = ( i < aLines.size() && aLines[i].size() >= 3
1409 && aLines[i][0] == 'G' && aLines[i][1] == ':' );
1410
1411 if( isSpecial && !isV52Gates )
1412 {
1413 // V9.0+ special symbol format: keyword num_variants, then variant lines
1414 if( i < aLines.size() )
1415 {
1416 std::istringstream siss( aLines[i] );
1417 int numVariants = 0;
1418 siss >> pt.special_keyword >> numVariants;
1419 i++;
1420
1421 for( int v = 0; v < numVariants && i < aLines.size(); v++ )
1422 {
1424 std::istringstream viss( aLines[i] );
1425 viss >> sv.decal_name >> sv.pin_type;
1426
1427 std::string rest;
1428
1429 if( viss >> rest )
1430 sv.net_suffix = rest;
1431
1432 pt.special_variants.push_back( sv );
1433 i++;
1434 }
1435 }
1436 }
1437 else if( i < aLines.size() )
1438 {
1439 if( isSpecial )
1440 {
1441 if( pt.name == "$GND_SYMS" )
1442 pt.special_keyword = "GND";
1443 else if( pt.name == "$PWR_SYMS" )
1444 pt.special_keyword = "PWR";
1445 else
1446 pt.special_keyword = "OSR";
1447 }
1448
1449 // Standard parts have GATE, CONN, or V5.2 G: blocks
1450 while( i < aLines.size() )
1451 {
1452 const std::string& gline = aLines[i];
1453
1454 if( gline.empty() )
1455 {
1456 // Blank line ends this parttype entry
1457 break;
1458 }
1459
1460 if( isSectionMarker( gline ) )
1461 break;
1462
1463 std::istringstream giss( gline );
1464 std::string keyword;
1465 giss >> keyword;
1466
1467 if( keyword == "GATE" )
1468 {
1469 GATE_DEF gate;
1470 giss >> gate.num_decal_variants >> gate.num_pins >> gate.swap_flag;
1471 i++;
1472
1473 // Decal name lines
1474 for( int d = 0; d < gate.num_decal_variants && i < aLines.size(); d++ )
1475 {
1476 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1477 break;
1478
1479 gate.decal_names.push_back( aLines[i] );
1480 i++;
1481 }
1482
1483 // Pin definition lines
1484 for( int p = 0; p < gate.num_pins && i < aLines.size(); p++ )
1485 {
1486 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1487 break;
1488
1490 std::istringstream piss( aLines[i] );
1491 std::string pinType;
1492
1493 piss >> pin.pin_id >> pin.swap_group >> pinType;
1494
1495 if( !pinType.empty() )
1496 pin.pin_type = pinType[0];
1497
1498 std::string pinName;
1499
1500 if( piss >> pinName )
1501 pin.pin_name = pinName;
1502
1503 gate.pins.push_back( pin );
1504 i++;
1505 }
1506
1507 pt.gates.push_back( gate );
1508 continue;
1509 }
1510 else if( keyword.size() >= 3 && keyword[0] == 'G' && keyword[1] == ':' )
1511 {
1512 // V5.2 gate format: G:decal1[:decal2:...] swap_flag num_pins
1513 // Pin lines use dot-separated fields with multiple pins per line.
1514 if( pt.category == "CON" )
1515 pt.is_connector = true;
1516
1517 GATE_DEF gate;
1518
1519 std::string decalStr = keyword.substr( 2 );
1520 std::istringstream diss( decalStr );
1521 std::string decalName;
1522
1523 while( std::getline( diss, decalName, ':' ) )
1524 {
1525 if( !decalName.empty() )
1526 gate.decal_names.push_back( decalName );
1527 }
1528
1529 gate.num_decal_variants = static_cast<int>( gate.decal_names.size() );
1530 giss >> gate.swap_flag >> gate.num_pins;
1531 i++;
1532
1533 int pinsRead = 0;
1534
1535 while( pinsRead < gate.num_pins && i < aLines.size() )
1536 {
1537 const std::string& pline = aLines[i];
1538
1539 if( pline.empty() || isSectionMarker( pline ) )
1540 break;
1541
1542 if( ( pline[0] == 'G' && pline.size() >= 2 && pline[1] == ':' )
1543 || pline.find( "SIGPIN" ) == 0 )
1544 {
1545 break;
1546 }
1547
1548 std::istringstream piss( pline );
1549 std::string pinToken;
1550
1551 while( piss >> pinToken && pinsRead < gate.num_pins )
1552 {
1554 std::vector<std::string> fields;
1555 std::istringstream fiss( pinToken );
1556 std::string field;
1557
1558 while( std::getline( fiss, field, '.' ) )
1559 fields.push_back( field );
1560
1561 if( fields.size() >= 1 )
1562 pin.pin_id = fields[0];
1563
1564 if( fields.size() >= 2 )
1565 {
1566 pin.swap_group =
1567 PADS_COMMON::ParseInt( fields[1], 0, "V5.2 pin" );
1568 }
1569
1570 if( fields.size() >= 3 && !fields[2].empty() )
1571 pin.pin_type = fields[2][0];
1572
1573 if( fields.size() >= 4 )
1574 pin.pin_name = fields[3];
1575
1576 gate.pins.push_back( pin );
1577 pinsRead++;
1578 }
1579
1580 i++;
1581 }
1582
1583 if( isSpecial )
1584 {
1586 sv.decal_name =
1587 gate.decal_names.empty() ? "" : gate.decal_names[0];
1588
1589 if( !gate.pins.empty() )
1590 sv.pin_type = std::string( 1, gate.pins[0].pin_type );
1591
1592 pt.special_variants.push_back( sv );
1593 }
1594
1595 pt.gates.push_back( gate );
1596 continue;
1597 }
1598 else if( keyword == "CONN" )
1599 {
1600 pt.is_connector = true;
1601 GATE_DEF gate;
1602 int numPins = 0;
1603 giss >> gate.num_decal_variants >> numPins;
1604 gate.num_pins = numPins;
1605 i++;
1606
1607 // Decal name + pin_type lines
1608 for( int d = 0; d < gate.num_decal_variants && i < aLines.size(); d++ )
1609 {
1610 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1611 break;
1612
1613 std::istringstream diss( aLines[i] );
1614 std::string decalName, pinType;
1615 diss >> decalName >> pinType;
1616 gate.decal_names.push_back( decalName );
1617 i++;
1618 }
1619
1620 // Pin definition lines
1621 for( int p = 0; p < numPins && i < aLines.size(); p++ )
1622 {
1623 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1624 break;
1625
1627 std::istringstream piss( aLines[i] );
1628 std::string pinType;
1629
1630 piss >> pin.pin_id >> pin.swap_group >> pinType;
1631
1632 if( !pinType.empty() )
1633 pin.pin_type = pinType[0];
1634
1635 gate.pins.push_back( pin );
1636 i++;
1637 }
1638
1639 pt.gates.push_back( gate );
1640 continue;
1641 }
1642 else if( keyword == "SIGPIN" )
1643 {
1645 std::string token;
1646 giss >> token;
1647
1648 // V5.2 uses dot-separated fields (e.g. "1.50.DGND") while
1649 // V9.0+ uses space-separated "pin_number net_name".
1650 if( token.find( '.' ) != std::string::npos )
1651 {
1652 std::vector<std::string> fields;
1653 std::istringstream fiss( token );
1654 std::string field;
1655
1656 while( std::getline( fiss, field, '.' ) )
1657 fields.push_back( field );
1658
1659 if( fields.size() >= 1 )
1660 sp.pin_number = fields[0];
1661
1662 if( fields.size() >= 3 )
1663 sp.net_name = fields.back();
1664 }
1665 else
1666 {
1667 sp.pin_number = token;
1668 giss >> sp.net_name;
1669 }
1670
1671 pt.sigpins.push_back( sp );
1672 i++;
1673 continue;
1674 }
1675 else
1676 {
1677 // Swap group line or unknown, store and continue
1678 pt.swap_lines.push_back( gline );
1679 i++;
1680 continue;
1681 }
1682 }
1683 }
1684
1685 m_partTypes[pt.name] = pt;
1686 }
1687
1688 return aLines.size() - 1;
1689}
1690
1691
1692size_t PADS_SCH_PARSER::parseSectionPART( const std::vector<std::string>& aLines,
1693 size_t aStartLine )
1694{
1695 size_t i = aStartLine + 1;
1696
1697 // Skip blank lines after header
1698 while( i < aLines.size() && aLines[i].empty() )
1699 i++;
1700
1701 while( i < aLines.size() )
1702 {
1703 const std::string& line = aLines[i];
1704
1705 if( line.empty() )
1706 {
1707 i++;
1708 continue;
1709 }
1710
1711 if( isSectionMarker( line ) )
1712 return i - 1;
1713
1714 // Part header starts with reference designator (alpha character)
1715 if( std::isalpha( static_cast<unsigned char>( line[0] ) ) )
1716 {
1717 PART_PLACEMENT part;
1718 i = parsePartPlacement( aLines, i, part );
1719
1720 if( !part.reference.empty() )
1721 {
1723 m_partPlacements.push_back( std::move( part ) );
1724 }
1725
1726 i++;
1727 continue;
1728 }
1729
1730 i++;
1731 }
1732
1733 return aLines.size() - 1;
1734}
1735
1736
1737size_t PADS_SCH_PARSER::parsePartPlacement( const std::vector<std::string>& aLines,
1738 size_t aStartLine, PART_PLACEMENT& aPart )
1739{
1740 if( aStartLine >= aLines.size() )
1741 return aStartLine;
1742
1743 const std::string& headerLine = aLines[aStartLine];
1744 std::istringstream iss( headerLine );
1745
1746 // Two PART header formats:
1747 // Normal: ref part_type x y angle mirror h1 w1 h2 w2 attrs disp pins u1 gate u2
1748 // Power: ref net_name $part_type x y angle mirror variant_index
1749 // Detect power format by checking whether the third token is numeric.
1750 std::string refdes, partType;
1751 int x = 0, y = 0, angleCode = 0, mirrorFlag = 0;
1752
1753 iss >> refdes >> partType;
1754
1755 if( !( iss >> x ) )
1756 {
1757 // Third field is not a number (e.g. "$PWR_SYMS"), so this is a power symbol entry.
1758 iss.clear();
1759 std::string actualPartType;
1760 iss >> actualPartType >> x >> y >> angleCode >> mirrorFlag;
1761
1762 aPart.power_net_name = partType;
1763 partType = actualPartType;
1764 }
1765 else
1766 {
1767 iss >> y >> angleCode >> mirrorFlag;
1768 }
1769
1770 aPart.reference = refdes;
1771 aPart.part_type = partType;
1772 aPart.symbol_name = partType;
1773 aPart.position.x = x;
1774 aPart.position.y = y;
1775
1776 switch( angleCode )
1777 {
1778 case 0: aPart.rotation = 0.0; break;
1779 case 1: aPart.rotation = 90.0; break;
1780 case 2: aPart.rotation = 180.0; break;
1781 case 3: aPart.rotation = 270.0; break;
1782 default: aPart.rotation = angleCode; break;
1783 }
1784
1785 aPart.mirror_flags = mirrorFlag;
1786
1787 if( !aPart.power_net_name.empty() )
1788 {
1789 // Power symbol: remaining field is the variant index
1790 int variantIdx = 0;
1791
1792 if( iss >> variantIdx )
1793 aPart.gate_index = variantIdx;
1794 }
1795 else
1796 {
1797 // Try to read remaining header fields for normal parts
1798 int numAttrs = 0, numDisplayedValues = 0, numPins = 0, unused1 = 0, gateIdx = 0;
1799 int unused2 = 0;
1800
1801 if( iss >> aPart.h1 >> aPart.w1 >> aPart.h2 >> aPart.w2 >> numAttrs >> numDisplayedValues
1802 >> numPins >> unused1 >> gateIdx >> unused2 )
1803 {
1804 aPart.num_attrs = numAttrs;
1805 aPart.num_displayed_values = numDisplayedValues;
1806 aPart.num_pins = numPins;
1807 aPart.gate_index = gateIdx;
1808 aPart.gate_number = gateIdx + 1;
1809 }
1810 else
1811 {
1812 // Simplified test format: ref part_type x y angle mirror sheet gate
1813 std::istringstream iss2( headerLine );
1814 std::string dummy;
1815 iss2 >> dummy >> dummy >> x >> y;
1816
1817 double rotDeg = 0;
1818 iss2 >> rotDeg;
1819 aPart.rotation = rotDeg;
1820
1821 std::string mirrorStr;
1822
1823 if( iss2 >> mirrorStr )
1824 {
1825 if( mirrorStr == "M" || mirrorStr == "Y" || mirrorStr == "1" )
1826 aPart.mirror_flags = 1;
1827 }
1828
1829 iss2 >> aPart.sheet_number >> aPart.gate_number;
1830 }
1831 }
1832
1833 // Extract gate index from reference designator suffix. PADS multi-gate components
1834 // use "REFDES-LETTER" or "REFDES.LETTER" (e.g., U17-A, U1.B).
1835 size_t sepPos = refdes.rfind( '-' );
1836
1837 if( sepPos == std::string::npos )
1838 sepPos = refdes.rfind( '.' );
1839
1840 if( sepPos != std::string::npos && sepPos + 1 < refdes.size() )
1841 {
1842 char gateLetter = refdes[sepPos + 1];
1843
1844 if( std::isalpha( static_cast<unsigned char>( gateLetter ) ) )
1845 {
1846 // Only derive gate index from the letter when the header didn't provide one
1847 if( aPart.gate_index == 0 )
1848 {
1849 aPart.gate_index = std::toupper( static_cast<unsigned char>( gateLetter ) ) - 'A';
1850 aPart.gate_number = aPart.gate_index + 1;
1851 }
1852 }
1853 }
1854
1855 size_t i = aStartLine + 1;
1856
1857 // If full format, parse font lines, attribute labels, overrides, and pin overrides
1858 if( aPart.num_attrs > 0 || aPart.num_displayed_values > 0 )
1859 {
1860 // Two font lines
1861 if( i < aLines.size() )
1862 {
1863 const std::string& fl = aLines[i];
1864
1865 if( fl.size() >= 2 && fl[0] == '"' )
1866 {
1867 size_t qEnd = fl.find( '"', 1 );
1868
1869 if( qEnd != std::string::npos )
1870 aPart.font1 = fl.substr( 1, qEnd - 1 );
1871
1872 i++;
1873 }
1874 }
1875
1876 if( i < aLines.size() )
1877 {
1878 const std::string& fl = aLines[i];
1879
1880 if( fl.size() >= 2 && fl[0] == '"' )
1881 {
1882 size_t qEnd = fl.find( '"', 1 );
1883
1884 if( qEnd != std::string::npos )
1885 aPart.font2 = fl.substr( 1, qEnd - 1 );
1886
1887 i++;
1888 }
1889 }
1890
1891 // Attribute label pairs (num_attrs pairs)
1892 for( int a = 0; a < aPart.num_attrs && i + 1 < aLines.size(); a++ )
1893 {
1894 PART_ATTRIBUTE attr;
1895 std::istringstream aiss( aLines[i] );
1896 int ax = 0, ay = 0, angle = 0, disp = 0, h = 0, w = 0, vis = 0;
1897
1898 aiss >> ax >> ay >> angle >> disp >> h >> w >> vis;
1899
1900 attr.position.x = ax;
1901 attr.position.y = ay;
1902 attr.rotation = angle;
1903 attr.justification = disp;
1904 attr.height = h;
1905 attr.width = w;
1906 attr.size = h;
1907 attr.visibility = vis;
1908 attr.visible = ( vis == 0 );
1909
1910 // Parse quoted font name
1911 std::string rest;
1912 std::getline( aiss, rest );
1913 size_t qStart = rest.find( '"' );
1914
1915 if( qStart != std::string::npos )
1916 {
1917 size_t qEnd = rest.find( '"', qStart + 1 );
1918
1919 if( qEnd != std::string::npos )
1920 attr.font_name = rest.substr( qStart + 1, qEnd - qStart - 1 );
1921 }
1922
1923 i++;
1924
1925 if( i < aLines.size() )
1926 attr.name = aLines[i];
1927
1928 aPart.attributes.push_back( attr );
1929 i++;
1930 }
1931
1932 // Displayed value overrides: each has a position line then a "name" value line
1933 for( int d = 0; d < aPart.num_displayed_values && i < aLines.size(); d++ )
1934 {
1935 // Skip the position/formatting line (starts with digit)
1936 if( !aLines[i].empty()
1937 && std::isdigit( static_cast<unsigned char>( aLines[i][0] ) ) )
1938 {
1939 i++;
1940 }
1941
1942 if( i >= aLines.size() )
1943 break;
1944
1945 const std::string& valLine = aLines[i];
1946
1947 if( valLine.size() > 2 && valLine[0] == '"' )
1948 {
1949 size_t closeQ = valLine.find( '"', 1 );
1950
1951 if( closeQ != std::string::npos )
1952 {
1953 std::string attrName = valLine.substr( 1, closeQ - 1 );
1954 std::string attrValue;
1955
1956 if( closeQ + 1 < valLine.size() )
1957 {
1958 attrValue = valLine.substr( closeQ + 1 );
1959 size_t start = attrValue.find_first_not_of( " \t" );
1960
1961 if( start != std::string::npos )
1962 attrValue = attrValue.substr( start );
1963 else
1964 attrValue.clear();
1965 }
1966
1967 aPart.attr_overrides[attrName] = attrValue;
1968 }
1969 }
1970
1971 i++;
1972 }
1973
1974 // Apply overrides back to the attribute vector so attr.value is populated
1975 for( auto& attr : aPart.attributes )
1976 {
1977 auto it = aPart.attr_overrides.find( attr.name );
1978
1979 if( it != aPart.attr_overrides.end() )
1980 attr.value = it->second;
1981 }
1982
1983 // Pin override lines
1984 while( i < aLines.size() )
1985 {
1986 const std::string& pline = aLines[i];
1987
1988 if( pline.empty() )
1989 break;
1990
1991 if( isSectionMarker( pline ) )
1992 return i - 1;
1993
1994 // New part (alpha at start)
1995 if( std::isalpha( static_cast<unsigned char>( pline[0] ) ) )
1996 return i - 1;
1997
1998 // Pin override: index height width angle justification
1999 if( std::isdigit( static_cast<unsigned char>( pline[0] ) ) )
2000 {
2002 std::istringstream poiss( pline );
2003 int pinIdx = 0;
2004 poiss >> pinIdx >> po.height >> po.width >> po.angle >> po.justification;
2005 aPart.pin_overrides.push_back( po );
2006 }
2007
2008 i++;
2009 }
2010 }
2011 else
2012 {
2013 // Simplified test format: @-prefixed attribute lines
2014 while( i < aLines.size() )
2015 {
2016 const std::string& attrLine = aLines[i];
2017
2018 if( attrLine.empty() )
2019 break;
2020
2021 if( isSectionMarker( attrLine ) )
2022 return i - 1;
2023
2024 if( attrLine[0] == '@' )
2025 {
2026 PART_ATTRIBUTE attr;
2027 std::istringstream aiss( attrLine.substr( 1 ) );
2028 aiss >> attr.name;
2029
2030 std::string rest;
2031 std::getline( aiss, rest );
2032 size_t start = rest.find_first_not_of( " \t" );
2033
2034 if( start != std::string::npos )
2035 {
2036 rest = rest.substr( start );
2037
2038 if( !rest.empty() && rest[0] == '"' )
2039 {
2040 size_t endQuote = rest.find( '"', 1 );
2041
2042 if( endQuote != std::string::npos )
2043 {
2044 attr.value = rest.substr( 1, endQuote - 1 );
2045 rest = rest.substr( endQuote + 1 );
2046 }
2047 }
2048 else
2049 {
2050 std::istringstream viss( rest );
2051 viss >> attr.value;
2052 std::getline( viss, rest );
2053 }
2054
2055 std::istringstream piss( rest );
2056 piss >> attr.position.x >> attr.position.y >> attr.rotation >> attr.size;
2057
2058 std::string visStr;
2059
2060 if( piss >> visStr )
2061 attr.visible = ( visStr != "N" && visStr != "0" && visStr != "H" );
2062 }
2063
2064 aPart.attributes.push_back( attr );
2065 i++;
2066 }
2067 else if( std::isalpha( static_cast<unsigned char>( attrLine[0] ) ) )
2068 {
2069 return i - 1;
2070 }
2071 else
2072 {
2073 i++;
2074 }
2075 }
2076 }
2077
2078 return i - 1;
2079}
2080
2081
2082size_t PADS_SCH_PARSER::parseSectionOFFPAGEREFS( const std::vector<std::string>& aLines,
2083 size_t aStartLine )
2084{
2085 size_t i = aStartLine + 1;
2086
2087 while( i < aLines.size() )
2088 {
2089 const std::string& line = aLines[i];
2090
2091 if( line.empty() )
2092 {
2093 i++;
2094 continue;
2095 }
2096
2097 if( isSectionMarker( line ) )
2098 return i - 1;
2099
2100 // Format: @@@O<id> net_name symbol_lib x y rotation flags1 flags2
2101 if( line.find( "@@@O" ) == 0 )
2102 {
2104 std::istringstream iss( line );
2105 std::string idToken;
2106 iss >> idToken;
2107
2108 // Extract numeric ID from @@@O<id>
2109 if( idToken.size() > 4 )
2110 opc.id = PADS_COMMON::ParseInt( idToken.substr( 4 ), 0, "OPC id" );
2111
2112 int x = 0, y = 0;
2113 iss >> opc.signal_name >> opc.symbol_lib >> x >> y >> opc.rotation
2114 >> opc.flags1 >> opc.flags2;
2115
2116 opc.position.x = x;
2117 opc.position.y = y;
2119
2120 m_offPageConnectors.push_back( opc );
2121 }
2122
2123 i++;
2124 }
2125
2126 return aLines.size() - 1;
2127}
2128
2129
2130size_t PADS_SCH_PARSER::parseSectionTIEDOTS( const std::vector<std::string>& aLines,
2131 size_t aStartLine )
2132{
2133 size_t i = aStartLine + 1;
2134
2135 while( i < aLines.size() )
2136 {
2137 const std::string& line = aLines[i];
2138
2139 if( line.empty() )
2140 {
2141 i++;
2142 continue;
2143 }
2144
2145 if( isSectionMarker( line ) )
2146 return i - 1;
2147
2148 // Format: @@@D<id> x y
2149 if( line.find( "@@@D" ) == 0 )
2150 {
2151 TIED_DOT dot;
2152 std::istringstream iss( line );
2153 std::string idToken;
2154 iss >> idToken;
2155
2156 if( idToken.size() > 4 )
2157 dot.id = PADS_COMMON::ParseInt( idToken.substr( 4 ), 0, "TIEDOT id" );
2158
2159 int x = 0, y = 0;
2160 iss >> x >> y;
2161 dot.position.x = x;
2162 dot.position.y = y;
2164
2165 m_tiedDots.push_back( dot );
2166 }
2167
2168 i++;
2169 }
2170
2171 return aLines.size() - 1;
2172}
2173
2174
2175size_t PADS_SCH_PARSER::parseSectionCONNECTION( const std::vector<std::string>& aLines,
2176 size_t aStartLine )
2177{
2178 // *CONNECTION* is the section marker. *SIGNAL* blocks follow within.
2179 size_t i = aStartLine + 1;
2180
2181 while( i < aLines.size() )
2182 {
2183 const std::string& line = aLines[i];
2184
2185 if( line.empty() )
2186 {
2187 i++;
2188 continue;
2189 }
2190
2191 // A non-SIGNAL section marker ends the CONNECTION section
2192 if( isSectionMarker( line ) )
2193 {
2194 std::string secName = extractSectionName( line );
2195
2196 if( secName == "SIGNAL" )
2197 {
2198 SCH_SIGNAL signal;
2199 i = parseSignalDef( aLines, i, signal );
2200
2201 if( !signal.name.empty() )
2202 {
2203 // Set sheet number on all wire segments
2204 for( auto& wire : signal.wires )
2205 wire.sheet_number = m_currentSheet;
2206
2207 // Build connections from endpoint references
2208 for( const auto& wire : signal.wires )
2209 {
2210 for( const auto& ep : { wire.endpoint_a, wire.endpoint_b } )
2211 {
2212 if( ep.find( '.' ) != std::string::npos &&
2213 ep.find( "@@@" ) == std::string::npos )
2214 {
2215 size_t dotPos = ep.find( '.' );
2216 PIN_CONNECTION conn;
2217 conn.reference = ep.substr( 0, dotPos );
2218 conn.pin_number = ep.substr( dotPos + 1 );
2220
2221 // Avoid duplicates
2222 bool found = false;
2223
2224 for( const auto& existing : signal.connections )
2225 {
2226 if( existing.reference == conn.reference &&
2227 existing.pin_number == conn.pin_number )
2228 {
2229 found = true;
2230 break;
2231 }
2232 }
2233
2234 if( !found )
2235 signal.connections.push_back( conn );
2236 }
2237 }
2238 }
2239
2240 m_signals.push_back( std::move( signal ) );
2241 }
2242
2243 i++;
2244 continue;
2245 }
2246 else
2247 {
2248 return i - 1;
2249 }
2250 }
2251
2252 i++;
2253 }
2254
2255 return aLines.size() - 1;
2256}
2257
2258
2259size_t PADS_SCH_PARSER::parseSignalDef( const std::vector<std::string>& aLines, size_t aStartLine,
2260 SCH_SIGNAL& aSignal )
2261{
2262 if( aStartLine >= aLines.size() )
2263 return aStartLine;
2264
2265 // Header: *SIGNAL* net_name flags1 flags2
2266 const std::string& headerLine = aLines[aStartLine];
2267 std::string secName = extractSectionName( headerLine );
2268
2269 if( secName != "SIGNAL" )
2270 return aStartLine;
2271
2272 // Extract everything after *SIGNAL*
2273 size_t afterMarker = headerLine.find( '*', 1 );
2274
2275 if( afterMarker == std::string::npos )
2276 return aStartLine;
2277
2278 std::string rest = headerLine.substr( afterMarker + 1 );
2279 std::istringstream iss( rest );
2280
2281 iss >> aSignal.name >> aSignal.flags1 >> aSignal.flags2;
2282
2283 size_t i = aStartLine + 1;
2284
2285 // Optional FUNCTION line
2286 if( aSignal.flags2 == 1 && i < aLines.size() )
2287 {
2288 const std::string& funcLine = aLines[i];
2289
2290 if( funcLine.find( "\"FUNCTION\"" ) != std::string::npos ||
2291 funcLine.find( "FUNCTION" ) == 0 )
2292 {
2293 size_t qStart = funcLine.find( '"' );
2294
2295 if( qStart != std::string::npos )
2296 {
2297 size_t qEnd = funcLine.find( '"', qStart + 1 );
2298
2299 if( qEnd != std::string::npos )
2300 {
2301 size_t afterQ = funcLine.find_first_not_of( " \t", qEnd + 1 );
2302
2303 if( afterQ != std::string::npos )
2304 aSignal.function = funcLine.substr( afterQ );
2305 }
2306 }
2307
2308 i++;
2309 }
2310 }
2311
2312 // Wire segments: endpoint_a endpoint_b vertex_count flags
2313 // x1 y1
2314 // x2 y2 ...
2315 while( i < aLines.size() )
2316 {
2317 const std::string& line = aLines[i];
2318
2319 if( line.empty() )
2320 {
2321 i++;
2322 continue;
2323 }
2324
2325 if( isSectionMarker( line ) )
2326 return i - 1;
2327
2328 // Wire segment header line: endpoint_a endpoint_b vertex_count flags
2329 WIRE_SEGMENT wire;
2330 std::istringstream wiss( line );
2331 wiss >> wire.endpoint_a >> wire.endpoint_b >> wire.vertex_count >> wire.flags;
2332
2333 if( wire.endpoint_a.empty() || wire.endpoint_b.empty() )
2334 {
2335 i++;
2336 continue;
2337 }
2338
2339 i++;
2340
2341 // Read vertex coordinates
2342 for( int v = 0; v < wire.vertex_count && i < aLines.size(); v++ )
2343 {
2344 const std::string& ptLine = aLines[i];
2345
2346 if( ptLine.empty() || isSectionMarker( ptLine ) )
2347 break;
2348
2349 POINT pt;
2350 std::istringstream piss( ptLine );
2351 piss >> pt.x >> pt.y;
2352 wire.vertices.push_back( pt );
2353 i++;
2354 }
2355
2356 // Set start/end from first/last vertex for backward compat
2357 if( !wire.vertices.empty() )
2358 {
2359 wire.start = wire.vertices.front();
2360 wire.end = wire.vertices.back();
2361 }
2362
2363 aSignal.wires.push_back( wire );
2364 }
2365
2366 return i > 0 ? i - 1 : aStartLine;
2367}
2368
2369
2370size_t PADS_SCH_PARSER::parseSectionNETNAMES( const std::vector<std::string>& aLines,
2371 size_t aStartLine )
2372{
2373 size_t i = aStartLine + 1;
2374
2375 while( i < aLines.size() )
2376 {
2377 const std::string& line = aLines[i];
2378
2379 if( line.empty() )
2380 {
2381 i++;
2382 continue;
2383 }
2384
2385 if( isSectionMarker( line ) )
2386 return i - 1;
2387
2388 // Format: net_name anchor_ref x_offset y_offset rotation justification f3 f4 f5 f6 f7
2389 // height width_pct "font_name"
2390 NETNAME_LABEL label;
2391 std::istringstream iss( line );
2392
2393 iss >> label.net_name >> label.anchor_ref >> label.x_offset >> label.y_offset
2394 >> label.rotation >> label.justification >> label.f3 >> label.f4 >> label.f5
2395 >> label.f6 >> label.f7 >> label.height >> label.width_pct;
2396
2397 // Parse quoted font name
2398 std::string rest;
2399 std::getline( iss, rest );
2400 size_t qStart = rest.find( '"' );
2401
2402 if( qStart != std::string::npos )
2403 {
2404 size_t qEnd = rest.find( '"', qStart + 1 );
2405
2406 if( qEnd != std::string::npos )
2407 label.font_name = rest.substr( qStart + 1, qEnd - qStart - 1 );
2408 }
2409
2410 m_netNameLabels.push_back( label );
2411 i++;
2412 }
2413
2414 return aLines.size() - 1;
2415}
2416
2417
2418size_t PADS_SCH_PARSER::skipBraceDelimitedSection( const std::vector<std::string>& aLines,
2419 size_t aStartLine )
2420{
2421 size_t i = aStartLine + 1;
2422 int braceDepth = 0;
2423 bool foundFirstBrace = false;
2424
2425 while( i < aLines.size() )
2426 {
2427 const std::string& line = aLines[i];
2428
2429 for( char c : line )
2430 {
2431 if( c == '{' )
2432 {
2433 braceDepth++;
2434 foundFirstBrace = true;
2435 }
2436 else if( c == '}' )
2437 {
2438 braceDepth--;
2439 }
2440 }
2441
2442 if( foundFirstBrace && braceDepth <= 0 )
2443 return i;
2444
2445 // If we hit another section marker without finding any braces, this section was empty
2446 if( !foundFirstBrace && isSectionMarker( line ) )
2447 return i - 1;
2448
2449 i++;
2450 }
2451
2452 return aLines.size() - 1;
2453}
2454
2455
2456const SYMBOL_DEF* PADS_SCH_PARSER::GetSymbolDef( const std::string& aName ) const
2457{
2458 for( const auto& sym : m_symbolDefs )
2459 {
2460 if( sym.name == aName )
2461 return &sym;
2462 }
2463
2464 return nullptr;
2465}
2466
2467
2468const PART_PLACEMENT* PADS_SCH_PARSER::GetPartPlacement( const std::string& aReference ) const
2469{
2470 for( const auto& part : m_partPlacements )
2471 {
2472 if( part.reference == aReference )
2473 return &part;
2474 }
2475
2476 return nullptr;
2477}
2478
2479
2480const SCH_SIGNAL* PADS_SCH_PARSER::GetSignal( const std::string& aName ) const
2481{
2482 for( const auto& signal : m_signals )
2483 {
2484 if( signal.name == aName )
2485 return &signal;
2486 }
2487
2488 return nullptr;
2489}
2490
2491
2493{
2494 std::set<int> sheets = GetSheetNumbers();
2495
2496 if( sheets.empty() )
2497 return 1;
2498
2499 return *sheets.rbegin();
2500}
2501
2502
2504{
2505 std::set<int> sheets;
2506
2507 for( const auto& header : m_sheetHeaders )
2508 sheets.insert( header.sheet_num );
2509
2510 for( const auto& part : m_partPlacements )
2511 sheets.insert( part.sheet_number );
2512
2513 for( const auto& signal : m_signals )
2514 {
2515 for( const auto& wire : signal.wires )
2516 sheets.insert( wire.sheet_number );
2517
2518 for( const auto& conn : signal.connections )
2519 sheets.insert( conn.sheet_number );
2520 }
2521
2522 if( sheets.empty() )
2523 sheets.insert( 1 );
2524
2525 return sheets;
2526}
2527
2528
2529std::vector<SCH_SIGNAL> PADS_SCH_PARSER::GetSignalsOnSheet( int aSheetNumber ) const
2530{
2531 std::vector<SCH_SIGNAL> result;
2532
2533 for( const auto& signal : m_signals )
2534 {
2535 SCH_SIGNAL filteredSignal;
2536 filteredSignal.name = signal.name;
2537
2538 for( const auto& wire : signal.wires )
2539 {
2540 if( wire.sheet_number == aSheetNumber )
2541 filteredSignal.wires.push_back( wire );
2542 }
2543
2544 for( const auto& conn : signal.connections )
2545 {
2546 if( conn.sheet_number == aSheetNumber )
2547 filteredSignal.connections.push_back( conn );
2548 }
2549
2550 if( !filteredSignal.wires.empty() || !filteredSignal.connections.empty() )
2551 result.push_back( filteredSignal );
2552 }
2553
2554 return result;
2555}
2556
2557
2558std::vector<PART_PLACEMENT> PADS_SCH_PARSER::GetPartsOnSheet( int aSheetNumber ) const
2559{
2560 std::vector<PART_PLACEMENT> result;
2561
2562 for( const auto& part : m_partPlacements )
2563 {
2564 if( part.sheet_number == aSheetNumber )
2565 result.push_back( part );
2566 }
2567
2568 return result;
2569}
2570
2571
2572PIN_TYPE PADS_SCH_PARSER::parsePinType( const std::string& aTypeStr )
2573{
2574 std::string upper = aTypeStr;
2575 std::transform( upper.begin(), upper.end(), upper.begin(), ::toupper );
2576
2577 if( upper == "I" || upper == "IN" || upper == "INPUT" || upper == "L" )
2578 return PIN_TYPE::INPUT;
2579
2580 if( upper == "O" || upper == "OUT" || upper == "OUTPUT" || upper == "S" )
2581 return PIN_TYPE::OUTPUT;
2582
2583 if( upper == "B" || upper == "BI" || upper == "BIDIR" || upper == "BIDIRECTIONAL" )
2585
2586 if( upper == "T" || upper == "TRI" || upper == "TRISTATE" )
2587 return PIN_TYPE::TRISTATE;
2588
2589 if( upper == "OC" || upper == "OPENCOLLECTOR" )
2591
2592 if( upper == "OE" || upper == "OPENEMITTER" )
2594
2595 if( upper == "P" || upper == "PWR" || upper == "POWER" || upper == "G" )
2596 return PIN_TYPE::POWER;
2597
2598 if( upper == "PAS" || upper == "PASSIVE" )
2599 return PIN_TYPE::PASSIVE;
2600
2601 return PIN_TYPE::UNSPECIFIED;
2602}
2603
2604
2606{
2607 switch( std::toupper( aTypeChar ) )
2608 {
2609 case 'L': return PIN_TYPE::INPUT;
2610 case 'S': return PIN_TYPE::OUTPUT;
2611 case 'B': return PIN_TYPE::BIDIRECTIONAL;
2612 case 'P': return PIN_TYPE::POWER;
2613 case 'G': return PIN_TYPE::POWER;
2614 case 'U': return PIN_TYPE::UNSPECIFIED;
2615 default: return PIN_TYPE::UNSPECIFIED;
2616 }
2617}
2618
2619} // 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
std::vector< std::string > header
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.