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