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;
641 std::istringstream iss( line );
642
643 int x = 0, y = 0;
644 iss >> x >> y >> item.rotation >> item.justification >> item.height >> item.width_factor
645 >> item.attr_flag;
646
647 item.position.x = x;
648 item.position.y = y;
649
650 // Parse quoted font name from remainder
651 std::string rest;
652 std::getline( iss, rest );
653 size_t qStart = rest.find( '"' );
654
655 if( qStart != std::string::npos )
656 {
657 size_t qEnd = rest.find( '"', qStart + 1 );
658
659 if( qEnd != std::string::npos )
660 item.font_name = rest.substr( qStart + 1, qEnd - qStart - 1 );
661 }
662
663 // Next line is content
664 i++;
665
666 if( i < aLines.size() )
667 item.content = aLines[i];
668
669 m_textItems.push_back( item );
670 i++;
671 }
672
673 return aLines.size() - 1;
674}
675
676
677size_t PADS_SCH_PARSER::parseSectionLINES( const std::vector<std::string>& aLines, size_t aStartLine )
678{
679 size_t i = aStartLine + 1;
680
681 while( i < aLines.size() )
682 {
683 const std::string& line = aLines[i];
684
685 if( line.empty() )
686 {
687 i++;
688 continue;
689 }
690
691 if( isSectionMarker( line ) )
692 return i - 1;
693
694 // Look for LINES item header: name LINES x y param1 param2
695 if( line.find( "LINES" ) != std::string::npos )
696 {
697 LINES_ITEM item;
698 std::istringstream iss( line );
699 std::string name, keyword;
700 int x = 0, y = 0;
701
702 iss >> name >> keyword >> x >> y >> item.param1 >> item.param2;
703
704 if( keyword == "LINES" )
705 {
706 item.name = name;
707 item.origin.x = x;
708 item.origin.y = y;
709
710 i++;
711
712 // Parse graphic primitives and embedded text until next LINES item or section
713 while( i < aLines.size() )
714 {
715 const std::string& pline = aLines[i];
716
717 if( pline.empty() )
718 {
719 i++;
720 continue;
721 }
722
723 if( isSectionMarker( pline ) )
724 break;
725
726 // Check if this is a new LINES item header
727 if( pline.find( "LINES" ) != std::string::npos )
728 {
729 std::istringstream tiss( pline );
730 std::string tname, tkw;
731 tiss >> tname >> tkw;
732
733 if( tkw == "LINES" )
734 break;
735 }
736
737 std::istringstream piss( pline );
738 std::string firstToken;
739 piss >> firstToken;
740
741 if( firstToken == "OPEN" || firstToken == "CLOSED" ||
742 firstToken == "CIRCLE" || firstToken == "COPCLS" )
743 {
744 SYMBOL_GRAPHIC graphic;
745 i = parseGraphicPrimitive( aLines, i, graphic );
746 item.primitives.push_back( graphic );
747 i++;
748 continue;
749 }
750
751 // Try to parse as text (attribute line + content) if it starts with
752 // a number that could be a coordinate
753 bool isNumber = !firstToken.empty()
754 && ( std::isdigit( firstToken[0] )
755 || firstToken[0] == '-'
756 || firstToken[0] == '+' );
757
758 if( isNumber && pline.find( '"' ) != std::string::npos )
759 {
760 // Text attribute line
762 std::istringstream tiss( pline );
763 int tx = 0, ty = 0;
764
765 tiss >> tx >> ty >> text.rotation >> text.justification >> text.height
766 >> text.width_factor;
767
768 text.position.x = tx;
769 text.position.y = ty;
770
771 std::string trest;
772 std::getline( tiss, trest );
773 size_t qStart = trest.find( '"' );
774
775 if( qStart != std::string::npos )
776 {
777 size_t qEnd = trest.find( '"', qStart + 1 );
778
779 if( qEnd != std::string::npos )
780 text.font_name = trest.substr( qStart + 1, qEnd - qStart - 1 );
781 }
782
783 i++;
784
785 if( i < aLines.size() )
786 text.content = aLines[i];
787
788 item.texts.push_back( text );
789 }
790
791 i++;
792 }
793
794 m_linesItems.push_back( item );
795 continue;
796 }
797 }
798
799 i++;
800 }
801
802 return aLines.size() - 1;
803}
804
805
806size_t PADS_SCH_PARSER::parseSectionCAEDECAL( const std::vector<std::string>& aLines,
807 size_t aStartLine )
808{
809 size_t i = aStartLine + 1;
810
811 while( i < aLines.size() )
812 {
813 const std::string& line = aLines[i];
814
815 if( line.empty() )
816 {
817 i++;
818 continue;
819 }
820
821 if( isSectionMarker( line ) )
822 break;
823
824 // Each entry starts with a header line of 13 fields
825 SYMBOL_DEF symbol;
826 i = parseSymbolDef( aLines, i, symbol );
827
828 if( !symbol.name.empty() )
829 m_symbolDefs.push_back( std::move( symbol ) );
830
831 i++;
832 }
833
834 // Resolve pin lengths from pin decal OPEN line geometry. The name-based
835 // heuristic (SHORT/LONG) only covers standard pin decals; custom names
836 // like PIN_ADI_300 need their length computed from the actual graphics.
837 std::map<std::string, double> pinDecalLengths;
838
839 for( const auto& sym : m_symbolDefs )
840 {
841 if( !sym.is_pin_decal )
842 continue;
843
844 for( const auto& graphic : sym.graphics )
845 {
846 if( graphic.type != GRAPHIC_TYPE::LINE && graphic.type != GRAPHIC_TYPE::POLYLINE )
847 continue;
848
849 if( graphic.points.size() < 2 )
850 continue;
851
852 const auto& first = graphic.points.front().coord;
853 const auto& last = graphic.points.back().coord;
854 double dx = last.x - first.x;
855 double dy = last.y - first.y;
856 pinDecalLengths[sym.name] = std::sqrt( dx * dx + dy * dy );
857 break;
858 }
859 }
860
861 for( auto& sym : m_symbolDefs )
862 {
863 if( sym.is_pin_decal )
864 continue;
865
866 for( auto& pin : sym.pins )
867 {
868 if( pin.pin_decal_name.empty() )
869 continue;
870
871 auto it = pinDecalLengths.find( pin.pin_decal_name );
872
873 if( it != pinDecalLengths.end() )
874 pin.length = it->second;
875 }
876 }
877
878 return i > 0 ? i - 1 : aLines.size() - 1;
879}
880
881
882size_t PADS_SCH_PARSER::parseSymbolDef( const std::vector<std::string>& aLines, size_t aStartLine,
883 SYMBOL_DEF& aSymbol )
884{
885 if( aStartLine >= aLines.size() )
886 return aStartLine;
887
888 const std::string& headerLine = aLines[aStartLine];
889
890 // Parse header: name f1 f2 height width h2 w2 num_attrs num_pieces has_polarity num_pins
891 // pin_origin_code is_pin_decal
892 std::istringstream iss( headerLine );
893 std::string name;
894 iss >> name;
895
896 if( name.empty() )
897 return aStartLine;
898
899 aSymbol.name = name;
900
901 // Try parsing as full 13-field CAEDECAL format
902 std::vector<std::string> tokens;
903 std::string token;
904
905 while( iss >> token )
906 tokens.push_back( token );
907
908 if( tokens.size() >= 12 )
909 {
910 // Full CAEDECAL format
911 aSymbol.f1 = PADS_COMMON::ParseInt( tokens[0], 0, "CAEDECAL header" );
912 aSymbol.f2 = PADS_COMMON::ParseInt( tokens[1], 0, "CAEDECAL header" );
913 aSymbol.height = PADS_COMMON::ParseInt( tokens[2], 0, "CAEDECAL header" );
914 aSymbol.width = PADS_COMMON::ParseInt( tokens[3], 0, "CAEDECAL header" );
915 aSymbol.h2 = PADS_COMMON::ParseInt( tokens[4], 0, "CAEDECAL header" );
916 aSymbol.w2 = PADS_COMMON::ParseInt( tokens[5], 0, "CAEDECAL header" );
917 aSymbol.num_attrs = PADS_COMMON::ParseInt( tokens[6], 0, "CAEDECAL header" );
918 aSymbol.num_pieces = PADS_COMMON::ParseInt( tokens[7], 0, "CAEDECAL header" );
919 aSymbol.has_polarity = PADS_COMMON::ParseInt( tokens[8], 0, "CAEDECAL header" );
920 aSymbol.num_pins = PADS_COMMON::ParseInt( tokens[9], 0, "CAEDECAL header" );
921 aSymbol.pin_origin_code = PADS_COMMON::ParseInt( tokens[10], 0, "CAEDECAL header" );
922 aSymbol.is_pin_decal = PADS_COMMON::ParseInt( tokens[11], 0, "CAEDECAL header" );
923 }
924 else
925 {
926 // Simplified test format: name num_pieces num_pins gate_count
927 if( tokens.size() >= 3 )
928 {
929 aSymbol.num_pieces = PADS_COMMON::ParseInt( tokens[0], 0, "CAEDECAL simplified" );
930 aSymbol.num_pins = PADS_COMMON::ParseInt( tokens[1], 0, "CAEDECAL simplified" );
931 aSymbol.gate_count = PADS_COMMON::ParseInt( tokens[2], 0, "CAEDECAL simplified" );
932 }
933 else
934 {
935 return aStartLine;
936 }
937
938 // Parse simplified format graphics and pins inline
939 size_t idx = aStartLine + 1;
940
941 for( int p = 0; p < aSymbol.num_pieces && idx < aLines.size(); p++ )
942 {
943 const std::string& gline = aLines[idx];
944
945 if( gline.empty() || isSectionMarker( gline ) )
946 break;
947
948 SYMBOL_GRAPHIC graphic;
949 std::istringstream giss( gline );
950 std::string typeStr;
951 giss >> typeStr;
952
953 if( typeStr == "OPEN" || typeStr == "LINE" )
954 {
955 graphic.type = GRAPHIC_TYPE::LINE;
956 POINT p1, p2;
957 giss >> p1.x >> p1.y >> p2.x >> p2.y;
958 graphic.points.push_back( { p1, std::nullopt } );
959 graphic.points.push_back( { p2, std::nullopt } );
960 giss >> graphic.line_width;
961 }
962 else if( typeStr == "CLOSED" || typeStr == "RECT" )
963 {
965 POINT p1, p2;
966 giss >> p1.x >> p1.y >> p2.x >> p2.y;
967 graphic.points.push_back( { p1, std::nullopt } );
968 graphic.points.push_back( { p2, std::nullopt } );
969 giss >> graphic.line_width;
970 }
971 else if( typeStr == "CIRCLE" )
972 {
973 graphic.type = GRAPHIC_TYPE::CIRCLE;
974 giss >> graphic.center.x >> graphic.center.y >> graphic.radius;
975 giss >> graphic.line_width;
976 }
977
978 aSymbol.graphics.push_back( graphic );
979 idx++;
980 }
981
982 for( int p = 0; p < aSymbol.num_pins && idx < aLines.size(); p++ )
983 {
984 const std::string& pline = aLines[idx];
985
986 if( pline.empty() || isSectionMarker( pline ) )
987 break;
988
990 std::istringstream piss( pline );
991 double orientation = 0;
992
993 piss >> pin.position.x >> pin.position.y >> orientation >> pin.length;
994 piss >> pin.number >> pin.name;
995
996 pin.rotation = orientation;
997
998 std::string typeStr;
999
1000 if( piss >> typeStr )
1001 pin.type = parsePinType( typeStr );
1002
1003 aSymbol.pins.push_back( pin );
1004 idx++;
1005 }
1006
1007 return idx - 1;
1008 }
1009
1010 // Full CAEDECAL format parsing
1011 size_t idx = aStartLine + 1;
1012
1013 // TIMESTAMP line
1014 if( idx < aLines.size() && aLines[idx].find( "TIMESTAMP" ) == 0 )
1015 {
1016 std::istringstream tiss( aLines[idx] );
1017 std::string kw;
1018 tiss >> kw >> aSymbol.timestamp;
1019 idx++;
1020 }
1021
1022 // Two font name lines (optional, not present in older formats like V5.2)
1023 if( idx < aLines.size() && aLines[idx].size() >= 2 && aLines[idx][0] == '"' )
1024 {
1025 size_t qEnd = aLines[idx].find( '"', 1 );
1026
1027 if( qEnd != std::string::npos )
1028 aSymbol.font1 = aLines[idx].substr( 1, qEnd - 1 );
1029
1030 idx++;
1031 }
1032
1033 if( idx < aLines.size() && aLines[idx].size() >= 2 && aLines[idx][0] == '"' )
1034 {
1035 size_t qEnd = aLines[idx].find( '"', 1 );
1036
1037 if( qEnd != std::string::npos )
1038 aSymbol.font2 = aLines[idx].substr( 1, qEnd - 1 );
1039
1040 idx++;
1041 }
1042
1043 // Attribute label pairs (num_attrs pairs of position+name lines)
1044 for( int a = 0; a < aSymbol.num_attrs && idx + 1 < aLines.size(); a++ )
1045 {
1046 CAEDECAL_ATTR attr;
1047 std::istringstream aiss( aLines[idx] );
1048 int x = 0, y = 0;
1049
1050 aiss >> x >> y >> attr.angle >> attr.justification >> attr.height >> attr.width;
1051 attr.position.x = x;
1052 attr.position.y = y;
1053
1054 // Parse quoted font name
1055 std::string rest;
1056 std::getline( aiss, rest );
1057 size_t qStart = rest.find( '"' );
1058
1059 if( qStart != std::string::npos )
1060 {
1061 size_t qEnd = rest.find( '"', qStart + 1 );
1062
1063 if( qEnd != std::string::npos )
1064 attr.font_name = rest.substr( qStart + 1, qEnd - qStart - 1 );
1065 }
1066
1067 idx++;
1068
1069 if( idx < aLines.size() )
1070 attr.attr_name = aLines[idx];
1071
1072 aSymbol.attrs.push_back( attr );
1073 idx++;
1074 }
1075
1076 // Graphic primitives (num_pieces)
1077 for( int p = 0; p < aSymbol.num_pieces && idx < aLines.size(); p++ )
1078 {
1079 if( aLines[idx].empty() )
1080 {
1081 idx++;
1082 p--;
1083 continue;
1084 }
1085
1086 SYMBOL_GRAPHIC graphic;
1087 idx = parseGraphicPrimitive( aLines, idx, graphic );
1088 aSymbol.graphics.push_back( graphic );
1089 idx++;
1090 }
1091
1092 // Embedded text labels (between graphics and pins)
1093 // Scan forward looking for T-prefixed pin lines. A blank line marks the end of
1094 // the current CAEDECAL entry (important for entries with num_pins=0 like pin decals).
1095 while( idx < aLines.size() )
1096 {
1097 const std::string& tline = aLines[idx];
1098
1099 if( tline.empty() )
1100 break;
1101
1102 if( isSectionMarker( tline ) )
1103 break;
1104
1105 // Pin T-lines start with T immediately followed by a digit or minus sign
1106 if( tline.size() > 1 && tline[0] == 'T' &&
1107 ( std::isdigit( static_cast<unsigned char>( tline[1] ) ) || tline[1] == '-' ) )
1108 {
1109 break;
1110 }
1111
1112 // This is an embedded text label (two-line format)
1114 std::istringstream tiss( tline );
1115 int tx = 0, ty = 0;
1116
1117 tiss >> tx >> ty >> text.rotation >> text.justification;
1118
1119 int height = 0, width = 0;
1120 tiss >> height >> width;
1121 text.size = height;
1122 text.width_factor = width;
1123
1124 text.position.x = tx;
1125 text.position.y = ty;
1126
1127 idx++;
1128
1129 if( idx < aLines.size() )
1130 {
1131 text.content = aLines[idx];
1132 idx++;
1133 }
1134
1135 aSymbol.texts.push_back( text );
1136 }
1137
1138 // Pin T/P line pairs (num_pins)
1139 for( int p = 0; p < aSymbol.num_pins && idx < aLines.size(); p++ )
1140 {
1141 const std::string& tLine = aLines[idx];
1142
1143 if( tLine.empty() )
1144 {
1145 idx++;
1146 p--;
1147 continue;
1148 }
1149
1150 if( isSectionMarker( tLine ) )
1151 break;
1152
1154
1155 // T line: T<x> y angle side pn_h pn_w pn_angle pn_just pl_h pl_w pl_angle pl_just
1156 // pin_decal_name
1157 if( tLine.size() > 1 && tLine[0] == 'T' )
1158 {
1159 // T immediately followed by x coordinate
1160 std::string tContent = tLine.substr( 1 );
1161 std::istringstream tiss( tContent );
1162 int tx = 0, ty = 0, angle = 0;
1163
1164 tiss >> tx >> ty >> angle >> pin.side >> pin.pn_h >> pin.pn_w >> pin.pn_angle
1165 >> pin.pn_just >> pin.pl_h >> pin.pl_w >> pin.pl_angle >> pin.pl_just
1166 >> pin.pin_decal_name;
1167
1168 pin.position.x = tx;
1169 pin.position.y = ty;
1170 pin.rotation = angle;
1171
1172 // Determine inverted/clock from pin decal name
1173 if( pin.pin_decal_name == "PINB" || pin.pin_decal_name == "PINORB" ||
1174 pin.pin_decal_name == "PCLKB" || pin.pin_decal_name == "PINIEB" ||
1175 pin.pin_decal_name == "PINCLKB" )
1176 {
1177 pin.inverted = true;
1178 }
1179
1180 if( pin.pin_decal_name == "PCLK" || pin.pin_decal_name == "PCLKB" ||
1181 pin.pin_decal_name == "PINCLK" || pin.pin_decal_name == "PINCLKB" )
1182 {
1183 pin.clock = true;
1184 }
1185
1186 // Pin stub length from pin decal name. Self-contained pin decals
1187 // (no pin_decal_name) draw the pin graphics themselves so the
1188 // KiCad stub length should be zero.
1189 if( pin.pin_decal_name.empty() )
1190 pin.length = 0.0;
1191 else if( pin.pin_decal_name.find( "SHORT" ) != std::string::npos )
1192 pin.length = 100.0;
1193 else if( pin.pin_decal_name.find( "LONG" ) != std::string::npos )
1194 pin.length = 300.0;
1195 }
1196
1197 idx++;
1198
1199 // P line: P<x1> y1 angle1 just1 x2 y2 angle2 just2 flags
1200 if( idx < aLines.size() )
1201 {
1202 const std::string& pLine = aLines[idx];
1203
1204 if( pLine.size() > 1 && pLine[0] == 'P' )
1205 {
1206 std::string pContent = pLine.substr( 1 );
1207 std::istringstream piss( pContent );
1208 int px1 = 0, py1 = 0, px2 = 0, py2 = 0;
1209
1210 piss >> px1 >> py1 >> pin.pn_off_angle >> pin.pn_off_just
1211 >> px2 >> py2 >> pin.pl_off_angle >> pin.pl_off_just >> pin.p_flags;
1212
1213 pin.pn_offset.x = px1;
1214 pin.pn_offset.y = py1;
1215 pin.pl_offset.x = px2;
1216 pin.pl_offset.y = py2;
1217
1218 // Pin name hidden if flags bit 128 set
1219 if( pin.p_flags & 128 )
1220 pin.name = "";
1221 }
1222
1223 idx++;
1224 }
1225
1226 // Pin number is derived from index+1 by default. The actual assignment
1227 // comes from the PARTTYPE section, so we use a placeholder.
1228 pin.number = std::to_string( p + 1 );
1229
1230 aSymbol.pins.push_back( pin );
1231 }
1232
1233 return idx > 0 ? idx - 1 : 0;
1234}
1235
1236
1237size_t PADS_SCH_PARSER::parseGraphicPrimitive( const std::vector<std::string>& aLines,
1238 size_t aStartLine, SYMBOL_GRAPHIC& aGraphic )
1239{
1240 if( aStartLine >= aLines.size() )
1241 return aStartLine;
1242
1243 const std::string& headerLine = aLines[aStartLine];
1244 std::istringstream iss( headerLine );
1245 std::string typeStr;
1246 int pointCount = 0, lineWidth = 0, lineStyle = 255;
1247
1248 iss >> typeStr >> pointCount >> lineWidth >> lineStyle;
1249
1250 aGraphic.line_width = lineWidth;
1251 aGraphic.line_style = lineStyle;
1252
1253 if( typeStr == "OPEN" )
1254 {
1255 aGraphic.type = GRAPHIC_TYPE::POLYLINE;
1256 aGraphic.filled = false;
1257 }
1258 else if( typeStr == "CLOSED" )
1259 {
1260 aGraphic.type = GRAPHIC_TYPE::RECTANGLE;
1261 aGraphic.filled = false;
1262 }
1263 else if( typeStr == "CIRCLE" )
1264 {
1265 aGraphic.type = GRAPHIC_TYPE::CIRCLE;
1266 aGraphic.filled = false;
1267 }
1268 else if( typeStr == "COPCLS" )
1269 {
1270 aGraphic.type = GRAPHIC_TYPE::RECTANGLE;
1271 aGraphic.filled = true;
1272 }
1273
1274 // Parse point data
1275 size_t idx = aStartLine + 1;
1276
1277 for( int p = 0; p < pointCount && idx < aLines.size(); p++ )
1278 {
1279 const std::string& ptLine = aLines[idx];
1280
1281 if( ptLine.empty() || isSectionMarker( ptLine ) )
1282 break;
1283
1284 std::istringstream piss( ptLine );
1285 GRAPHIC_POINT gpt;
1286 piss >> gpt.coord.x >> gpt.coord.y;
1287
1288 std::vector<std::string> extraTokens;
1289 std::string tok;
1290
1291 while( piss >> tok )
1292 extraTokens.push_back( tok );
1293
1294 if( extraTokens.size() >= 6 )
1295 {
1296 ARC_DATA arcData;
1297 arcData.bulge = PADS_COMMON::ParseDouble( extraTokens[0], 0.0, "arc data" );
1298 arcData.angle = PADS_COMMON::ParseDouble( extraTokens[1], 0.0, "arc data" );
1299 arcData.bbox_x1 = PADS_COMMON::ParseDouble( extraTokens[2], 0.0, "arc data" );
1300 arcData.bbox_y1 = PADS_COMMON::ParseDouble( extraTokens[3], 0.0, "arc data" );
1301 arcData.bbox_x2 = PADS_COMMON::ParseDouble( extraTokens[4], 0.0, "arc data" );
1302 arcData.bbox_y2 = PADS_COMMON::ParseDouble( extraTokens[5], 0.0, "arc data" );
1303 gpt.arc = arcData;
1304 }
1305
1306 aGraphic.points.push_back( gpt );
1307
1308 idx++;
1309 }
1310
1311 // For CLOSED/COPCLS polygons, determine whether the points form an axis-aligned rectangle
1312 // or a general polygon. Rectangles are reduced to 2 corner points (min, max) for the builder.
1313 // Non-rectangular shapes (triangles, arbitrary polygons) become POLYLINE primitives.
1314 if( aGraphic.type == GRAPHIC_TYPE::RECTANGLE && aGraphic.points.size() >= 4 )
1315 {
1316 std::set<double> uniqueX, uniqueY;
1317
1318 for( const auto& pt : aGraphic.points )
1319 {
1320 uniqueX.insert( pt.coord.x );
1321 uniqueY.insert( pt.coord.y );
1322 }
1323
1324 bool isRect = ( uniqueX.size() == 2 && uniqueY.size() == 2 );
1325
1326 if( isRect )
1327 {
1328 double minX = *uniqueX.begin();
1329 double maxX = *uniqueX.rbegin();
1330 double minY = *uniqueY.begin();
1331 double maxY = *uniqueY.rbegin();
1332
1333 aGraphic.points.clear();
1334 aGraphic.points.push_back( { { minX, minY }, std::nullopt } );
1335 aGraphic.points.push_back( { { maxX, maxY }, std::nullopt } );
1336 }
1337 else
1338 {
1339 aGraphic.type = GRAPHIC_TYPE::POLYLINE;
1340 }
1341 }
1342
1343 // For CIRCLE with 2 points, compute center and radius
1344 if( aGraphic.type == GRAPHIC_TYPE::CIRCLE && aGraphic.points.size() == 2 )
1345 {
1346 aGraphic.center.x = ( aGraphic.points[0].coord.x + aGraphic.points[1].coord.x ) / 2.0;
1347 aGraphic.center.y = ( aGraphic.points[0].coord.y + aGraphic.points[1].coord.y ) / 2.0;
1348 double dx = aGraphic.points[1].coord.x - aGraphic.points[0].coord.x;
1349 double dy = aGraphic.points[1].coord.y - aGraphic.points[0].coord.y;
1350 aGraphic.radius = std::sqrt( dx * dx + dy * dy ) / 2.0;
1351 }
1352
1353 return idx > 0 ? idx - 1 : aStartLine;
1354}
1355
1356
1357size_t PADS_SCH_PARSER::parseSectionPARTTYPE( const std::vector<std::string>& aLines,
1358 size_t aStartLine )
1359{
1360 size_t i = aStartLine + 1;
1361
1362 while( i < aLines.size() )
1363 {
1364 const std::string& line = aLines[i];
1365
1366 if( line.empty() )
1367 {
1368 i++;
1369 continue;
1370 }
1371
1372 if( isSectionMarker( line ) )
1373 return i - 1;
1374
1375 // Part type header: name category num_physical num_sigpins unused num_swap_groups
1376 PARTTYPE_DEF pt;
1377 std::istringstream iss( line );
1378 iss >> pt.name >> pt.category >> pt.num_physical >> pt.num_sigpins
1379 >> pt.unused >> pt.num_swap_groups;
1380
1381 if( pt.name.empty() )
1382 {
1383 i++;
1384 continue;
1385 }
1386
1387 i++;
1388
1389 // TIMESTAMP line
1390 if( i < aLines.size() && aLines[i].find( "TIMESTAMP" ) == 0 )
1391 {
1392 std::istringstream tiss( aLines[i] );
1393 std::string kw;
1394 tiss >> kw >> pt.timestamp;
1395 i++;
1396 }
1397
1398 // Check for special symbols ($GND_SYMS, $PWR_SYMS, $OSR_SYMS)
1399 bool isSpecial = ( pt.name == "$GND_SYMS" || pt.name == "$PWR_SYMS" ||
1400 pt.name == "$OSR_SYMS" );
1401
1402 // V5.2 uses "G:decalname swap num_pins" for gate definitions while V9.0+ uses
1403 // "GATE num_variants num_pins swap" followed by decal name lines. Detect by
1404 // checking whether the first content line starts with "G:".
1405 bool isV52Gates = ( i < aLines.size() && aLines[i].size() >= 3
1406 && aLines[i][0] == 'G' && aLines[i][1] == ':' );
1407
1408 if( isSpecial && !isV52Gates )
1409 {
1410 // V9.0+ special symbol format: keyword num_variants, then variant lines
1411 if( i < aLines.size() )
1412 {
1413 std::istringstream siss( aLines[i] );
1414 int numVariants = 0;
1415 siss >> pt.special_keyword >> numVariants;
1416 i++;
1417
1418 for( int v = 0; v < numVariants && i < aLines.size(); v++ )
1419 {
1421 std::istringstream viss( aLines[i] );
1422 viss >> sv.decal_name >> sv.pin_type;
1423
1424 std::string rest;
1425
1426 if( viss >> rest )
1427 sv.net_suffix = rest;
1428
1429 pt.special_variants.push_back( sv );
1430 i++;
1431 }
1432 }
1433 }
1434 else if( i < aLines.size() )
1435 {
1436 if( isSpecial )
1437 {
1438 if( pt.name == "$GND_SYMS" )
1439 pt.special_keyword = "GND";
1440 else if( pt.name == "$PWR_SYMS" )
1441 pt.special_keyword = "PWR";
1442 else
1443 pt.special_keyword = "OSR";
1444 }
1445
1446 // Standard parts have GATE, CONN, or V5.2 G: blocks
1447 while( i < aLines.size() )
1448 {
1449 const std::string& gline = aLines[i];
1450
1451 if( gline.empty() )
1452 {
1453 // Blank line ends this parttype entry
1454 break;
1455 }
1456
1457 if( isSectionMarker( gline ) )
1458 break;
1459
1460 std::istringstream giss( gline );
1461 std::string keyword;
1462 giss >> keyword;
1463
1464 if( keyword == "GATE" )
1465 {
1466 GATE_DEF gate;
1467 giss >> gate.num_decal_variants >> gate.num_pins >> gate.swap_flag;
1468 i++;
1469
1470 // Decal name lines
1471 for( int d = 0; d < gate.num_decal_variants && i < aLines.size(); d++ )
1472 {
1473 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1474 break;
1475
1476 gate.decal_names.push_back( aLines[i] );
1477 i++;
1478 }
1479
1480 // Pin definition lines
1481 for( int p = 0; p < gate.num_pins && i < aLines.size(); p++ )
1482 {
1483 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1484 break;
1485
1487 std::istringstream piss( aLines[i] );
1488 std::string pinType;
1489
1490 piss >> pin.pin_id >> pin.swap_group >> pinType;
1491
1492 if( !pinType.empty() )
1493 pin.pin_type = pinType[0];
1494
1495 std::string pinName;
1496
1497 if( piss >> pinName )
1498 pin.pin_name = pinName;
1499
1500 gate.pins.push_back( pin );
1501 i++;
1502 }
1503
1504 pt.gates.push_back( gate );
1505 continue;
1506 }
1507 else if( keyword.size() >= 3 && keyword[0] == 'G' && keyword[1] == ':' )
1508 {
1509 // V5.2 gate format: G:decal1[:decal2:...] swap_flag num_pins
1510 // Pin lines use dot-separated fields with multiple pins per line.
1511 if( pt.category == "CON" )
1512 pt.is_connector = true;
1513
1514 GATE_DEF gate;
1515
1516 std::string decalStr = keyword.substr( 2 );
1517 std::istringstream diss( decalStr );
1518 std::string decalName;
1519
1520 while( std::getline( diss, decalName, ':' ) )
1521 {
1522 if( !decalName.empty() )
1523 gate.decal_names.push_back( decalName );
1524 }
1525
1526 gate.num_decal_variants = static_cast<int>( gate.decal_names.size() );
1527 giss >> gate.swap_flag >> gate.num_pins;
1528 i++;
1529
1530 int pinsRead = 0;
1531
1532 while( pinsRead < gate.num_pins && i < aLines.size() )
1533 {
1534 const std::string& pline = aLines[i];
1535
1536 if( pline.empty() || isSectionMarker( pline ) )
1537 break;
1538
1539 if( ( pline[0] == 'G' && pline.size() >= 2 && pline[1] == ':' )
1540 || pline.find( "SIGPIN" ) == 0 )
1541 {
1542 break;
1543 }
1544
1545 std::istringstream piss( pline );
1546 std::string pinToken;
1547
1548 while( piss >> pinToken && pinsRead < gate.num_pins )
1549 {
1551 std::vector<std::string> fields;
1552 std::istringstream fiss( pinToken );
1553 std::string field;
1554
1555 while( std::getline( fiss, field, '.' ) )
1556 fields.push_back( field );
1557
1558 if( fields.size() >= 1 )
1559 pin.pin_id = fields[0];
1560
1561 if( fields.size() >= 2 )
1562 {
1563 pin.swap_group =
1564 PADS_COMMON::ParseInt( fields[1], 0, "V5.2 pin" );
1565 }
1566
1567 if( fields.size() >= 3 && !fields[2].empty() )
1568 pin.pin_type = fields[2][0];
1569
1570 if( fields.size() >= 4 )
1571 pin.pin_name = fields[3];
1572
1573 gate.pins.push_back( pin );
1574 pinsRead++;
1575 }
1576
1577 i++;
1578 }
1579
1580 if( isSpecial )
1581 {
1583 sv.decal_name =
1584 gate.decal_names.empty() ? "" : gate.decal_names[0];
1585
1586 if( !gate.pins.empty() )
1587 sv.pin_type = std::string( 1, gate.pins[0].pin_type );
1588
1589 pt.special_variants.push_back( sv );
1590 }
1591
1592 pt.gates.push_back( gate );
1593 continue;
1594 }
1595 else if( keyword == "CONN" )
1596 {
1597 pt.is_connector = true;
1598 GATE_DEF gate;
1599 int numPins = 0;
1600 giss >> gate.num_decal_variants >> numPins;
1601 gate.num_pins = numPins;
1602 i++;
1603
1604 // Decal name + pin_type lines
1605 for( int d = 0; d < gate.num_decal_variants && i < aLines.size(); d++ )
1606 {
1607 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1608 break;
1609
1610 std::istringstream diss( aLines[i] );
1611 std::string decalName, pinType;
1612 diss >> decalName >> pinType;
1613 gate.decal_names.push_back( decalName );
1614 i++;
1615 }
1616
1617 // Pin definition lines
1618 for( int p = 0; p < numPins && i < aLines.size(); p++ )
1619 {
1620 if( aLines[i].empty() || isSectionMarker( aLines[i] ) )
1621 break;
1622
1624 std::istringstream piss( aLines[i] );
1625 std::string pinType;
1626
1627 piss >> pin.pin_id >> pin.swap_group >> pinType;
1628
1629 if( !pinType.empty() )
1630 pin.pin_type = pinType[0];
1631
1632 gate.pins.push_back( pin );
1633 i++;
1634 }
1635
1636 pt.gates.push_back( gate );
1637 continue;
1638 }
1639 else if( keyword == "SIGPIN" )
1640 {
1642 std::string token;
1643 giss >> token;
1644
1645 // V5.2 uses dot-separated fields (e.g. "1.50.DGND") while
1646 // V9.0+ uses space-separated "pin_number net_name".
1647 if( token.find( '.' ) != std::string::npos )
1648 {
1649 std::vector<std::string> fields;
1650 std::istringstream fiss( token );
1651 std::string field;
1652
1653 while( std::getline( fiss, field, '.' ) )
1654 fields.push_back( field );
1655
1656 if( fields.size() >= 1 )
1657 sp.pin_number = fields[0];
1658
1659 if( fields.size() >= 3 )
1660 sp.net_name = fields.back();
1661 }
1662 else
1663 {
1664 sp.pin_number = token;
1665 giss >> sp.net_name;
1666 }
1667
1668 pt.sigpins.push_back( sp );
1669 i++;
1670 continue;
1671 }
1672 else
1673 {
1674 // Swap group line or unknown, store and continue
1675 pt.swap_lines.push_back( gline );
1676 i++;
1677 continue;
1678 }
1679 }
1680 }
1681
1682 m_partTypes[pt.name] = pt;
1683 }
1684
1685 return aLines.size() - 1;
1686}
1687
1688
1689size_t PADS_SCH_PARSER::parseSectionPART( const std::vector<std::string>& aLines,
1690 size_t aStartLine )
1691{
1692 size_t i = aStartLine + 1;
1693
1694 // Skip blank lines after header
1695 while( i < aLines.size() && aLines[i].empty() )
1696 i++;
1697
1698 while( i < aLines.size() )
1699 {
1700 const std::string& line = aLines[i];
1701
1702 if( line.empty() )
1703 {
1704 i++;
1705 continue;
1706 }
1707
1708 if( isSectionMarker( line ) )
1709 return i - 1;
1710
1711 // Part header starts with reference designator (alpha character)
1712 if( std::isalpha( static_cast<unsigned char>( line[0] ) ) )
1713 {
1714 PART_PLACEMENT part;
1715 i = parsePartPlacement( aLines, i, part );
1716
1717 if( !part.reference.empty() )
1718 {
1720 m_partPlacements.push_back( std::move( part ) );
1721 }
1722
1723 i++;
1724 continue;
1725 }
1726
1727 i++;
1728 }
1729
1730 return aLines.size() - 1;
1731}
1732
1733
1734size_t PADS_SCH_PARSER::parsePartPlacement( const std::vector<std::string>& aLines,
1735 size_t aStartLine, PART_PLACEMENT& aPart )
1736{
1737 if( aStartLine >= aLines.size() )
1738 return aStartLine;
1739
1740 const std::string& headerLine = aLines[aStartLine];
1741 std::istringstream iss( headerLine );
1742
1743 // Two PART header formats:
1744 // Normal: ref part_type x y angle mirror h1 w1 h2 w2 attrs disp pins u1 gate u2
1745 // Power: ref net_name $part_type x y angle mirror variant_index
1746 // Detect power format by checking whether the third token is numeric.
1747 std::string refdes, partType;
1748 int x = 0, y = 0, angleCode = 0, mirrorFlag = 0;
1749
1750 iss >> refdes >> partType;
1751
1752 if( !( iss >> x ) )
1753 {
1754 // Third field is not a number (e.g. "$PWR_SYMS"), so this is a power symbol entry.
1755 iss.clear();
1756 std::string actualPartType;
1757 iss >> actualPartType >> x >> y >> angleCode >> mirrorFlag;
1758
1759 aPart.power_net_name = partType;
1760 partType = actualPartType;
1761 }
1762 else
1763 {
1764 iss >> y >> angleCode >> mirrorFlag;
1765 }
1766
1767 aPart.reference = refdes;
1768 aPart.part_type = partType;
1769 aPart.symbol_name = partType;
1770 aPart.position.x = x;
1771 aPart.position.y = y;
1772
1773 switch( angleCode )
1774 {
1775 case 0: aPart.rotation = 0.0; break;
1776 case 1: aPart.rotation = 90.0; break;
1777 case 2: aPart.rotation = 180.0; break;
1778 case 3: aPart.rotation = 270.0; break;
1779 default: aPart.rotation = angleCode; break;
1780 }
1781
1782 aPart.mirror_flags = mirrorFlag;
1783
1784 if( !aPart.power_net_name.empty() )
1785 {
1786 // Power symbol: remaining field is the variant index
1787 int variantIdx = 0;
1788
1789 if( iss >> variantIdx )
1790 aPart.gate_index = variantIdx;
1791 }
1792 else
1793 {
1794 // Try to read remaining header fields for normal parts
1795 int numAttrs = 0, numDisplayedValues = 0, numPins = 0, unused1 = 0, gateIdx = 0;
1796 int unused2 = 0;
1797
1798 if( iss >> aPart.h1 >> aPart.w1 >> aPart.h2 >> aPart.w2 >> numAttrs >> numDisplayedValues
1799 >> numPins >> unused1 >> gateIdx >> unused2 )
1800 {
1801 aPart.num_attrs = numAttrs;
1802 aPart.num_displayed_values = numDisplayedValues;
1803 aPart.num_pins = numPins;
1804 aPart.gate_index = gateIdx;
1805 aPart.gate_number = gateIdx + 1;
1806 }
1807 else
1808 {
1809 // Simplified test format: ref part_type x y angle mirror sheet gate
1810 std::istringstream iss2( headerLine );
1811 std::string dummy;
1812 iss2 >> dummy >> dummy >> x >> y;
1813
1814 double rotDeg = 0;
1815 iss2 >> rotDeg;
1816 aPart.rotation = rotDeg;
1817
1818 std::string mirrorStr;
1819
1820 if( iss2 >> mirrorStr )
1821 {
1822 if( mirrorStr == "M" || mirrorStr == "Y" || mirrorStr == "1" )
1823 aPart.mirror_flags = 1;
1824 }
1825
1826 iss2 >> aPart.sheet_number >> aPart.gate_number;
1827 }
1828 }
1829
1830 // Extract gate index from reference designator suffix. PADS multi-gate components
1831 // use "REFDES-LETTER" or "REFDES.LETTER" (e.g., U17-A, U1.B).
1832 size_t sepPos = refdes.rfind( '-' );
1833
1834 if( sepPos == std::string::npos )
1835 sepPos = refdes.rfind( '.' );
1836
1837 if( sepPos != std::string::npos && sepPos + 1 < refdes.size() )
1838 {
1839 char gateLetter = refdes[sepPos + 1];
1840
1841 if( std::isalpha( static_cast<unsigned char>( gateLetter ) ) )
1842 {
1843 // Only derive gate index from the letter when the header didn't provide one
1844 if( aPart.gate_index == 0 )
1845 {
1846 aPart.gate_index = std::toupper( static_cast<unsigned char>( gateLetter ) ) - 'A';
1847 aPart.gate_number = aPart.gate_index + 1;
1848 }
1849 }
1850 }
1851
1852 size_t i = aStartLine + 1;
1853
1854 // If full format, parse font lines, attribute labels, overrides, and pin overrides
1855 if( aPart.num_attrs > 0 || aPart.num_displayed_values > 0 )
1856 {
1857 // Two font lines
1858 if( i < aLines.size() )
1859 {
1860 const std::string& fl = aLines[i];
1861
1862 if( fl.size() >= 2 && fl[0] == '"' )
1863 {
1864 size_t qEnd = fl.find( '"', 1 );
1865
1866 if( qEnd != std::string::npos )
1867 aPart.font1 = fl.substr( 1, qEnd - 1 );
1868
1869 i++;
1870 }
1871 }
1872
1873 if( i < aLines.size() )
1874 {
1875 const std::string& fl = aLines[i];
1876
1877 if( fl.size() >= 2 && fl[0] == '"' )
1878 {
1879 size_t qEnd = fl.find( '"', 1 );
1880
1881 if( qEnd != std::string::npos )
1882 aPart.font2 = fl.substr( 1, qEnd - 1 );
1883
1884 i++;
1885 }
1886 }
1887
1888 // Attribute label pairs (num_attrs pairs)
1889 for( int a = 0; a < aPart.num_attrs && i + 1 < aLines.size(); a++ )
1890 {
1891 PART_ATTRIBUTE attr;
1892 std::istringstream aiss( aLines[i] );
1893 int ax = 0, ay = 0, angle = 0, disp = 0, h = 0, w = 0, vis = 0;
1894
1895 aiss >> ax >> ay >> angle >> disp >> h >> w >> vis;
1896
1897 attr.position.x = ax;
1898 attr.position.y = ay;
1899 attr.rotation = angle;
1900 attr.justification = disp;
1901 attr.height = h;
1902 attr.width = w;
1903 attr.size = h;
1904 attr.visibility = vis;
1905 attr.visible = ( vis == 0 );
1906
1907 // Parse quoted font name
1908 std::string rest;
1909 std::getline( aiss, rest );
1910 size_t qStart = rest.find( '"' );
1911
1912 if( qStart != std::string::npos )
1913 {
1914 size_t qEnd = rest.find( '"', qStart + 1 );
1915
1916 if( qEnd != std::string::npos )
1917 attr.font_name = rest.substr( qStart + 1, qEnd - qStart - 1 );
1918 }
1919
1920 i++;
1921
1922 if( i < aLines.size() )
1923 attr.name = aLines[i];
1924
1925 aPart.attributes.push_back( attr );
1926 i++;
1927 }
1928
1929 // Displayed value overrides: each has a position line then a "name" value line
1930 for( int d = 0; d < aPart.num_displayed_values && i < aLines.size(); d++ )
1931 {
1932 // Skip the position/formatting line (starts with digit)
1933 if( !aLines[i].empty()
1934 && std::isdigit( static_cast<unsigned char>( aLines[i][0] ) ) )
1935 {
1936 i++;
1937 }
1938
1939 if( i >= aLines.size() )
1940 break;
1941
1942 const std::string& valLine = aLines[i];
1943
1944 if( valLine.size() > 2 && valLine[0] == '"' )
1945 {
1946 size_t closeQ = valLine.find( '"', 1 );
1947
1948 if( closeQ != std::string::npos )
1949 {
1950 std::string attrName = valLine.substr( 1, closeQ - 1 );
1951 std::string attrValue;
1952
1953 if( closeQ + 1 < valLine.size() )
1954 {
1955 attrValue = valLine.substr( closeQ + 1 );
1956 size_t start = attrValue.find_first_not_of( " \t" );
1957
1958 if( start != std::string::npos )
1959 attrValue = attrValue.substr( start );
1960 else
1961 attrValue.clear();
1962 }
1963
1964 aPart.attr_overrides[attrName] = attrValue;
1965 }
1966 }
1967
1968 i++;
1969 }
1970
1971 // Apply overrides back to the attribute vector so attr.value is populated
1972 for( auto& attr : aPart.attributes )
1973 {
1974 auto it = aPart.attr_overrides.find( attr.name );
1975
1976 if( it != aPart.attr_overrides.end() )
1977 attr.value = it->second;
1978 }
1979
1980 // Pin override lines
1981 while( i < aLines.size() )
1982 {
1983 const std::string& pline = aLines[i];
1984
1985 if( pline.empty() )
1986 break;
1987
1988 if( isSectionMarker( pline ) )
1989 return i - 1;
1990
1991 // New part (alpha at start)
1992 if( std::isalpha( static_cast<unsigned char>( pline[0] ) ) )
1993 return i - 1;
1994
1995 // Pin override: index height width angle justification
1996 if( std::isdigit( static_cast<unsigned char>( pline[0] ) ) )
1997 {
1999 std::istringstream poiss( pline );
2000 int pinIdx = 0;
2001 poiss >> pinIdx >> po.height >> po.width >> po.angle >> po.justification;
2002 aPart.pin_overrides.push_back( po );
2003 }
2004
2005 i++;
2006 }
2007 }
2008 else
2009 {
2010 // Simplified test format: @-prefixed attribute lines
2011 while( i < aLines.size() )
2012 {
2013 const std::string& attrLine = aLines[i];
2014
2015 if( attrLine.empty() )
2016 break;
2017
2018 if( isSectionMarker( attrLine ) )
2019 return i - 1;
2020
2021 if( attrLine[0] == '@' )
2022 {
2023 PART_ATTRIBUTE attr;
2024 std::istringstream aiss( attrLine.substr( 1 ) );
2025 aiss >> attr.name;
2026
2027 std::string rest;
2028 std::getline( aiss, rest );
2029 size_t start = rest.find_first_not_of( " \t" );
2030
2031 if( start != std::string::npos )
2032 {
2033 rest = rest.substr( start );
2034
2035 if( !rest.empty() && rest[0] == '"' )
2036 {
2037 size_t endQuote = rest.find( '"', 1 );
2038
2039 if( endQuote != std::string::npos )
2040 {
2041 attr.value = rest.substr( 1, endQuote - 1 );
2042 rest = rest.substr( endQuote + 1 );
2043 }
2044 }
2045 else
2046 {
2047 std::istringstream viss( rest );
2048 viss >> attr.value;
2049 std::getline( viss, rest );
2050 }
2051
2052 std::istringstream piss( rest );
2053 piss >> attr.position.x >> attr.position.y >> attr.rotation >> attr.size;
2054
2055 std::string visStr;
2056
2057 if( piss >> visStr )
2058 attr.visible = ( visStr != "N" && visStr != "0" && visStr != "H" );
2059 }
2060
2061 aPart.attributes.push_back( attr );
2062 i++;
2063 }
2064 else if( std::isalpha( static_cast<unsigned char>( attrLine[0] ) ) )
2065 {
2066 return i - 1;
2067 }
2068 else
2069 {
2070 i++;
2071 }
2072 }
2073 }
2074
2075 return i - 1;
2076}
2077
2078
2079size_t PADS_SCH_PARSER::parseSectionOFFPAGEREFS( const std::vector<std::string>& aLines,
2080 size_t aStartLine )
2081{
2082 size_t i = aStartLine + 1;
2083
2084 while( i < aLines.size() )
2085 {
2086 const std::string& line = aLines[i];
2087
2088 if( line.empty() )
2089 {
2090 i++;
2091 continue;
2092 }
2093
2094 if( isSectionMarker( line ) )
2095 return i - 1;
2096
2097 // Format: @@@O<id> net_name symbol_lib x y rotation flags1 flags2
2098 if( line.find( "@@@O" ) == 0 )
2099 {
2101 std::istringstream iss( line );
2102 std::string idToken;
2103 iss >> idToken;
2104
2105 // Extract numeric ID from @@@O<id>
2106 if( idToken.size() > 4 )
2107 opc.id = PADS_COMMON::ParseInt( idToken.substr( 4 ), 0, "OPC id" );
2108
2109 int x = 0, y = 0;
2110 iss >> opc.signal_name >> opc.symbol_lib >> x >> y >> opc.rotation
2111 >> opc.flags1 >> opc.flags2;
2112
2113 opc.position.x = x;
2114 opc.position.y = y;
2116
2117 m_offPageConnectors.push_back( opc );
2118 }
2119
2120 i++;
2121 }
2122
2123 return aLines.size() - 1;
2124}
2125
2126
2127size_t PADS_SCH_PARSER::parseSectionTIEDOTS( const std::vector<std::string>& aLines,
2128 size_t aStartLine )
2129{
2130 size_t i = aStartLine + 1;
2131
2132 while( i < aLines.size() )
2133 {
2134 const std::string& line = aLines[i];
2135
2136 if( line.empty() )
2137 {
2138 i++;
2139 continue;
2140 }
2141
2142 if( isSectionMarker( line ) )
2143 return i - 1;
2144
2145 // Format: @@@D<id> x y
2146 if( line.find( "@@@D" ) == 0 )
2147 {
2148 TIED_DOT dot;
2149 std::istringstream iss( line );
2150 std::string idToken;
2151 iss >> idToken;
2152
2153 if( idToken.size() > 4 )
2154 dot.id = PADS_COMMON::ParseInt( idToken.substr( 4 ), 0, "TIEDOT id" );
2155
2156 int x = 0, y = 0;
2157 iss >> x >> y;
2158 dot.position.x = x;
2159 dot.position.y = y;
2161
2162 m_tiedDots.push_back( dot );
2163 }
2164
2165 i++;
2166 }
2167
2168 return aLines.size() - 1;
2169}
2170
2171
2172size_t PADS_SCH_PARSER::parseSectionCONNECTION( const std::vector<std::string>& aLines,
2173 size_t aStartLine )
2174{
2175 // *CONNECTION* is the section marker. *SIGNAL* blocks follow within.
2176 size_t i = aStartLine + 1;
2177
2178 while( i < aLines.size() )
2179 {
2180 const std::string& line = aLines[i];
2181
2182 if( line.empty() )
2183 {
2184 i++;
2185 continue;
2186 }
2187
2188 // A non-SIGNAL section marker ends the CONNECTION section
2189 if( isSectionMarker( line ) )
2190 {
2191 std::string secName = extractSectionName( line );
2192
2193 if( secName == "SIGNAL" )
2194 {
2195 SCH_SIGNAL signal;
2196 i = parseSignalDef( aLines, i, signal );
2197
2198 if( !signal.name.empty() )
2199 {
2200 // Set sheet number on all wire segments
2201 for( auto& wire : signal.wires )
2202 wire.sheet_number = m_currentSheet;
2203
2204 // Build connections from endpoint references
2205 for( const auto& wire : signal.wires )
2206 {
2207 for( const auto& ep : { wire.endpoint_a, wire.endpoint_b } )
2208 {
2209 if( ep.find( '.' ) != std::string::npos &&
2210 ep.find( "@@@" ) == std::string::npos )
2211 {
2212 size_t dotPos = ep.find( '.' );
2213 PIN_CONNECTION conn;
2214 conn.reference = ep.substr( 0, dotPos );
2215 conn.pin_number = ep.substr( dotPos + 1 );
2217
2218 // Avoid duplicates
2219 bool found = false;
2220
2221 for( const auto& existing : signal.connections )
2222 {
2223 if( existing.reference == conn.reference &&
2224 existing.pin_number == conn.pin_number )
2225 {
2226 found = true;
2227 break;
2228 }
2229 }
2230
2231 if( !found )
2232 signal.connections.push_back( conn );
2233 }
2234 }
2235 }
2236
2237 m_signals.push_back( std::move( signal ) );
2238 }
2239
2240 i++;
2241 continue;
2242 }
2243 else
2244 {
2245 return i - 1;
2246 }
2247 }
2248
2249 i++;
2250 }
2251
2252 return aLines.size() - 1;
2253}
2254
2255
2256size_t PADS_SCH_PARSER::parseSignalDef( const std::vector<std::string>& aLines, size_t aStartLine,
2257 SCH_SIGNAL& aSignal )
2258{
2259 if( aStartLine >= aLines.size() )
2260 return aStartLine;
2261
2262 // Header: *SIGNAL* net_name flags1 flags2
2263 const std::string& headerLine = aLines[aStartLine];
2264 std::string secName = extractSectionName( headerLine );
2265
2266 if( secName != "SIGNAL" )
2267 return aStartLine;
2268
2269 // Extract everything after *SIGNAL*
2270 size_t afterMarker = headerLine.find( '*', 1 );
2271
2272 if( afterMarker == std::string::npos )
2273 return aStartLine;
2274
2275 std::string rest = headerLine.substr( afterMarker + 1 );
2276 std::istringstream iss( rest );
2277
2278 iss >> aSignal.name >> aSignal.flags1 >> aSignal.flags2;
2279
2280 size_t i = aStartLine + 1;
2281
2282 // Optional FUNCTION line
2283 if( aSignal.flags2 == 1 && i < aLines.size() )
2284 {
2285 const std::string& funcLine = aLines[i];
2286
2287 if( funcLine.find( "\"FUNCTION\"" ) != std::string::npos ||
2288 funcLine.find( "FUNCTION" ) == 0 )
2289 {
2290 size_t qStart = funcLine.find( '"' );
2291
2292 if( qStart != std::string::npos )
2293 {
2294 size_t qEnd = funcLine.find( '"', qStart + 1 );
2295
2296 if( qEnd != std::string::npos )
2297 {
2298 size_t afterQ = funcLine.find_first_not_of( " \t", qEnd + 1 );
2299
2300 if( afterQ != std::string::npos )
2301 aSignal.function = funcLine.substr( afterQ );
2302 }
2303 }
2304
2305 i++;
2306 }
2307 }
2308
2309 // Wire segments: endpoint_a endpoint_b vertex_count flags
2310 // x1 y1
2311 // x2 y2 ...
2312 while( i < aLines.size() )
2313 {
2314 const std::string& line = aLines[i];
2315
2316 if( line.empty() )
2317 {
2318 i++;
2319 continue;
2320 }
2321
2322 if( isSectionMarker( line ) )
2323 return i - 1;
2324
2325 // Wire segment header line: endpoint_a endpoint_b vertex_count flags
2326 WIRE_SEGMENT wire;
2327 std::istringstream wiss( line );
2328 wiss >> wire.endpoint_a >> wire.endpoint_b >> wire.vertex_count >> wire.flags;
2329
2330 if( wire.endpoint_a.empty() || wire.endpoint_b.empty() )
2331 {
2332 i++;
2333 continue;
2334 }
2335
2336 i++;
2337
2338 // Read vertex coordinates
2339 for( int v = 0; v < wire.vertex_count && i < aLines.size(); v++ )
2340 {
2341 const std::string& ptLine = aLines[i];
2342
2343 if( ptLine.empty() || isSectionMarker( ptLine ) )
2344 break;
2345
2346 POINT pt;
2347 std::istringstream piss( ptLine );
2348 piss >> pt.x >> pt.y;
2349 wire.vertices.push_back( pt );
2350 i++;
2351 }
2352
2353 // Set start/end from first/last vertex for backward compat
2354 if( !wire.vertices.empty() )
2355 {
2356 wire.start = wire.vertices.front();
2357 wire.end = wire.vertices.back();
2358 }
2359
2360 aSignal.wires.push_back( wire );
2361 }
2362
2363 return i > 0 ? i - 1 : aStartLine;
2364}
2365
2366
2367size_t PADS_SCH_PARSER::parseSectionNETNAMES( const std::vector<std::string>& aLines,
2368 size_t aStartLine )
2369{
2370 size_t i = aStartLine + 1;
2371
2372 while( i < aLines.size() )
2373 {
2374 const std::string& line = aLines[i];
2375
2376 if( line.empty() )
2377 {
2378 i++;
2379 continue;
2380 }
2381
2382 if( isSectionMarker( line ) )
2383 return i - 1;
2384
2385 // Format: net_name anchor_ref x_offset y_offset rotation justification f3 f4 f5 f6 f7
2386 // height width_pct "font_name"
2387 NETNAME_LABEL label;
2388 std::istringstream iss( line );
2389
2390 iss >> label.net_name >> label.anchor_ref >> label.x_offset >> label.y_offset
2391 >> label.rotation >> label.justification >> label.f3 >> label.f4 >> label.f5
2392 >> label.f6 >> label.f7 >> label.height >> label.width_pct;
2393
2394 // Parse quoted font name
2395 std::string rest;
2396 std::getline( iss, rest );
2397 size_t qStart = rest.find( '"' );
2398
2399 if( qStart != std::string::npos )
2400 {
2401 size_t qEnd = rest.find( '"', qStart + 1 );
2402
2403 if( qEnd != std::string::npos )
2404 label.font_name = rest.substr( qStart + 1, qEnd - qStart - 1 );
2405 }
2406
2407 m_netNameLabels.push_back( label );
2408 i++;
2409 }
2410
2411 return aLines.size() - 1;
2412}
2413
2414
2415size_t PADS_SCH_PARSER::skipBraceDelimitedSection( const std::vector<std::string>& aLines,
2416 size_t aStartLine )
2417{
2418 size_t i = aStartLine + 1;
2419 int braceDepth = 0;
2420 bool foundFirstBrace = false;
2421
2422 while( i < aLines.size() )
2423 {
2424 const std::string& line = aLines[i];
2425
2426 for( char c : line )
2427 {
2428 if( c == '{' )
2429 {
2430 braceDepth++;
2431 foundFirstBrace = true;
2432 }
2433 else if( c == '}' )
2434 {
2435 braceDepth--;
2436 }
2437 }
2438
2439 if( foundFirstBrace && braceDepth <= 0 )
2440 return i;
2441
2442 // If we hit another section marker without finding any braces, this section was empty
2443 if( !foundFirstBrace && isSectionMarker( line ) )
2444 return i - 1;
2445
2446 i++;
2447 }
2448
2449 return aLines.size() - 1;
2450}
2451
2452
2453const SYMBOL_DEF* PADS_SCH_PARSER::GetSymbolDef( const std::string& aName ) const
2454{
2455 for( const auto& sym : m_symbolDefs )
2456 {
2457 if( sym.name == aName )
2458 return &sym;
2459 }
2460
2461 return nullptr;
2462}
2463
2464
2465const PART_PLACEMENT* PADS_SCH_PARSER::GetPartPlacement( const std::string& aReference ) const
2466{
2467 for( const auto& part : m_partPlacements )
2468 {
2469 if( part.reference == aReference )
2470 return &part;
2471 }
2472
2473 return nullptr;
2474}
2475
2476
2477const SCH_SIGNAL* PADS_SCH_PARSER::GetSignal( const std::string& aName ) const
2478{
2479 for( const auto& signal : m_signals )
2480 {
2481 if( signal.name == aName )
2482 return &signal;
2483 }
2484
2485 return nullptr;
2486}
2487
2488
2490{
2491 std::set<int> sheets = GetSheetNumbers();
2492
2493 if( sheets.empty() )
2494 return 1;
2495
2496 return *sheets.rbegin();
2497}
2498
2499
2501{
2502 std::set<int> sheets;
2503
2504 for( const auto& header : m_sheetHeaders )
2505 sheets.insert( header.sheet_num );
2506
2507 for( const auto& part : m_partPlacements )
2508 sheets.insert( part.sheet_number );
2509
2510 for( const auto& signal : m_signals )
2511 {
2512 for( const auto& wire : signal.wires )
2513 sheets.insert( wire.sheet_number );
2514
2515 for( const auto& conn : signal.connections )
2516 sheets.insert( conn.sheet_number );
2517 }
2518
2519 if( sheets.empty() )
2520 sheets.insert( 1 );
2521
2522 return sheets;
2523}
2524
2525
2526std::vector<SCH_SIGNAL> PADS_SCH_PARSER::GetSignalsOnSheet( int aSheetNumber ) const
2527{
2528 std::vector<SCH_SIGNAL> result;
2529
2530 for( const auto& signal : m_signals )
2531 {
2532 SCH_SIGNAL filteredSignal;
2533 filteredSignal.name = signal.name;
2534
2535 for( const auto& wire : signal.wires )
2536 {
2537 if( wire.sheet_number == aSheetNumber )
2538 filteredSignal.wires.push_back( wire );
2539 }
2540
2541 for( const auto& conn : signal.connections )
2542 {
2543 if( conn.sheet_number == aSheetNumber )
2544 filteredSignal.connections.push_back( conn );
2545 }
2546
2547 if( !filteredSignal.wires.empty() || !filteredSignal.connections.empty() )
2548 result.push_back( filteredSignal );
2549 }
2550
2551 return result;
2552}
2553
2554
2555std::vector<PART_PLACEMENT> PADS_SCH_PARSER::GetPartsOnSheet( int aSheetNumber ) const
2556{
2557 std::vector<PART_PLACEMENT> result;
2558
2559 for( const auto& part : m_partPlacements )
2560 {
2561 if( part.sheet_number == aSheetNumber )
2562 result.push_back( part );
2563 }
2564
2565 return result;
2566}
2567
2568
2569PIN_TYPE PADS_SCH_PARSER::parsePinType( const std::string& aTypeStr )
2570{
2571 std::string upper = aTypeStr;
2572 std::transform( upper.begin(), upper.end(), upper.begin(), ::toupper );
2573
2574 if( upper == "I" || upper == "IN" || upper == "INPUT" || upper == "L" )
2575 return PIN_TYPE::INPUT;
2576
2577 if( upper == "O" || upper == "OUT" || upper == "OUTPUT" || upper == "S" )
2578 return PIN_TYPE::OUTPUT;
2579
2580 if( upper == "B" || upper == "BI" || upper == "BIDIR" || upper == "BIDIRECTIONAL" )
2582
2583 if( upper == "T" || upper == "TRI" || upper == "TRISTATE" )
2584 return PIN_TYPE::TRISTATE;
2585
2586 if( upper == "OC" || upper == "OPENCOLLECTOR" )
2588
2589 if( upper == "OE" || upper == "OPENEMITTER" )
2591
2592 if( upper == "P" || upper == "PWR" || upper == "POWER" || upper == "G" )
2593 return PIN_TYPE::POWER;
2594
2595 if( upper == "PAS" || upper == "PASSIVE" )
2596 return PIN_TYPE::PASSIVE;
2597
2598 return PIN_TYPE::UNSPECIFIED;
2599}
2600
2601
2603{
2604 switch( std::toupper( aTypeChar ) )
2605 {
2606 case 'L': return PIN_TYPE::INPUT;
2607 case 'S': return PIN_TYPE::OUTPUT;
2608 case 'B': return PIN_TYPE::BIDIRECTIONAL;
2609 case 'P': return PIN_TYPE::POWER;
2610 case 'G': return PIN_TYPE::POWER;
2611 case 'U': return PIN_TYPE::UNSPECIFIED;
2612 default: return PIN_TYPE::UNSPECIFIED;
2613 }
2614}
2615
2616} // 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.