KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pads_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 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "pads_parser.h"
25#include <io/pads/pads_common.h>
26#include <fstream>
27#include <sstream>
28#include <iostream>
29#include <algorithm>
30#include <climits>
31#include <cstdlib>
32#include <limits>
33#include <wx/log.h>
34
35namespace PADS_IO
36{
37
47static std::vector<std::string> expandShortcutPattern( const std::string& aPattern )
48{
49 std::vector<std::string> result;
50
51 size_t braceStart = aPattern.find( '{' );
52 size_t braceEnd = aPattern.find( '}' );
53
54 if( braceStart == std::string::npos || braceEnd == std::string::npos || braceEnd <= braceStart )
55 {
56 result.push_back( aPattern );
57 return result;
58 }
59
60 std::string prefix = aPattern.substr( 0, braceStart );
61 std::string suffix = ( braceEnd + 1 < aPattern.length() ) ? aPattern.substr( braceEnd + 1 ) : "";
62 std::string range = aPattern.substr( braceStart + 1, braceEnd - braceStart - 1 );
63
64 size_t dashPos = range.find( '-' );
65
66 if( dashPos == std::string::npos )
67 {
68 result.push_back( aPattern );
69 return result;
70 }
71
72 int start = PADS_COMMON::ParseInt( range.substr( 0, dashPos ), INT_MIN, "shortcut range" );
73 int end = PADS_COMMON::ParseInt( range.substr( dashPos + 1 ), INT_MIN, "shortcut range" );
74
75 if( start == INT_MIN || end == INT_MIN )
76 {
77 result.push_back( aPattern );
78 return result;
79 }
80
81 static constexpr int MAX_EXPANSION = 10000;
82
83 if( std::abs( end - start ) > MAX_EXPANSION )
84 {
85 wxLogWarning( wxT( "PADS Import: shortcut range {%d-%d} exceeds limit, skipped" ),
86 start, end );
87 result.push_back( aPattern );
88 return result;
89 }
90
91 for( int i = start; i <= end; ++i )
92 {
93 result.push_back( prefix + std::to_string( i ) + suffix );
94 }
95
96 return result;
97}
98
99
101{
102}
103
105{
106}
107
108void PARSER::Parse( const wxString& aFileName )
109{
110 std::ifstream file( aFileName.ToStdString() );
111 if( !file.is_open() )
112 {
113 throw std::runtime_error( "Could not open file " + aFileName.ToStdString() );
114 }
115
116 std::string line;
117
118 // Read header
119 if( !readLine( file, line ) )
120 {
121 throw std::runtime_error( "Empty file" );
122 }
123
124 // Parse header line format:
125 // PCB files: !PADS-product-version-units[-mode][-encoding]!
126 // Example: !PADS-POWERPCB-V9.4-MILS!
127 // Library files: *PADS-LIBRARY-type-Vversion*
128 // Example: *PADS-LIBRARY-PCB-DECALS-V9*
129 m_is_basic_units = false;
131
132 // Check for library file format (uses * delimiters)
133 if( line.size() > 2 && line[0] == '*' && line.back() == '*' )
134 {
135 std::string header = line.substr( 1, line.size() - 2 );
136 m_file_header.product = header;
137
138 // Detect library type from header
139 if( header.find( "LIBRARY-LINE-ITEMS" ) != std::string::npos ||
140 header.find( "LIBRARY-LINE" ) != std::string::npos )
141 {
143 }
144 else if( header.find( "LIBRARY-SCH-DECALS" ) != std::string::npos )
145 {
147 }
148 else if( header.find( "LIBRARY-PCB-DECALS" ) != std::string::npos ||
149 header.find( "LIBRARY-DECALS" ) != std::string::npos )
150 {
152 }
153 else if( header.find( "LIBRARY-PART-TYPES" ) != std::string::npos )
154 {
156 }
157
158 // Extract version from library header (e.g., V9 from *PADS-LIBRARY-PCB-DECALS-V9*)
159 size_t v_pos = header.rfind( "-V" );
160
161 if( v_pos != std::string::npos )
162 {
163 m_file_header.version = header.substr( v_pos + 1 );
164 }
165
166 // Library files default to mils
168 }
169 else if( line.size() > 2 && line[0] == '!' )
170 {
171 // PCB file format: !PADS-product-version-units[-mode][-encoding]! [description...]
172 // Find the closing '!' to extract just the header marker, ignoring any trailing text
173 size_t close_pos = line.find( '!', 1 );
174
175 if( close_pos == std::string::npos )
176 close_pos = line.size();
177
178 std::string header = line.substr( 1, close_pos - 1 );
179
180 // Split by '-'
181 std::vector<std::string> parts;
182 size_t start = 0;
183 size_t pos = 0;
184
185 while( ( pos = header.find( '-', start ) ) != std::string::npos )
186 {
187 parts.push_back( header.substr( start, pos - start ) );
188 start = pos + 1;
189 }
190
191 parts.push_back( header.substr( start ) );
192
193 // Parse parts: PADS, product, version, units, [mode], [encoding]
194 if( parts.size() >= 4 )
195 {
196 // First part should be "PADS"
197 m_file_header.product = parts[1];
198 m_file_header.version = parts[2];
199 m_file_header.units = parts[3];
200
201 if( parts.size() >= 5 )
202 m_file_header.mode = parts[4];
203
204 if( parts.size() >= 6 )
205 m_file_header.encoding = parts[5];
206 }
207 else if( parts.size() >= 2 )
208 {
209 // Simpler format
210 m_file_header.product = parts[0];
211
212 if( parts.size() >= 2 )
213 m_file_header.version = parts[1];
214
215 if( parts.size() >= 3 )
216 m_file_header.units = parts[2];
217 }
218
219 // Set units based on parsed header
220 if( m_file_header.units == "BASIC" )
221 {
222 m_is_basic_units = true;
223 }
224 else if( m_file_header.units == "MILS" || m_file_header.units == "MIL" )
225 {
227 }
228 else if( m_file_header.units == "MM" || m_file_header.units == "METRIC" )
229 {
231 }
232 else if( m_file_header.units == "INCH" || m_file_header.units == "INCHES" )
233 {
235 }
236 }
237 else if( line.find( "BASIC" ) != std::string::npos )
238 {
239 m_is_basic_units = true;
240 }
241
242 while( readLine( file, line ) )
243 {
244 if( line.empty() ) continue;
245
246 if( line.rfind( "*PCB*", 0 ) == 0 )
247 {
248 parseSectionPCB( file );
249 }
250 else if( line.rfind( "*PART*", 0 ) == 0 )
251 {
252 parseSectionPARTS( file );
253 }
254 else if( line.rfind( "*NET*", 0 ) == 0 )
255 {
256 parseSectionNETS( file );
257 }
258 else if( line.rfind( "*ROUTE*", 0 ) == 0 )
259 {
260 parseSectionROUTES( file );
261 }
262 else if( line.rfind( "*TEXT*", 0 ) == 0 )
263 {
264 parseSectionTEXT( file );
265 }
266 else if( line.rfind( "*BOARD*", 0 ) == 0 )
267 {
268 parseSectionBOARD( file );
269 }
270 else if( line.rfind( "*LINES*", 0 ) == 0 )
271 {
272 parseSectionLINES( file );
273 }
274 else if( line.rfind( "*VIA*", 0 ) == 0 )
275 {
276 parseSectionVIA( file );
277 }
278 else if( line.rfind( "*POUR*", 0 ) == 0 )
279 {
280 parseSectionPOUR( file );
281 }
282 else if( line.rfind( "*PARTDECAL*", 0 ) == 0 )
283 {
284 parseSectionPARTDECAL( file );
285 }
286 else if( line.rfind( "*PARTTYPE*", 0 ) == 0 )
287 {
288 parseSectionPARTTYPE( file );
289 }
290 else if( line.rfind( "*REUSE*", 0 ) == 0 )
291 {
292 parseSectionREUSE( file );
293 }
294 else if( line.rfind( "*CLUSTER*", 0 ) == 0 )
295 {
296 parseSectionCLUSTER( file );
297 }
298 else if( line.rfind( "*JUMPER*", 0 ) == 0 )
299 {
300 parseSectionJUMPER( file );
301 }
302 else if( line.rfind( "*TESTPOINT*", 0 ) == 0 )
303 {
304 parseSectionTESTPOINT( file );
305 }
306 else if( line.rfind( "*NETCLASS*", 0 ) == 0 || line.rfind( "*NETDEF*", 0 ) == 0 )
307 {
308 parseSectionNETCLASS( file );
309 }
310 else if( line.rfind( "*DIFFPAIR*", 0 ) == 0 || line.rfind( "*DIFFPAIRS*", 0 ) == 0 )
311 {
312 parseSectionDIFFPAIR( file );
313 }
314 else if( line.rfind( "LAYER MILS", 0 ) == 0 || line.rfind( "LAYER METRIC", 0 ) == 0 )
315 {
316 parseSectionLAYERDEFS( file );
317 }
318 else if( line.rfind( "*MISC*", 0 ) == 0 )
319 {
320 parseSectionMISC( file );
321 }
322 }
323}
324
325bool PARSER::readLine( std::ifstream& aStream, std::string& aLine )
326{
327 if( m_pushed_line )
328 {
329 aLine = *m_pushed_line;
330 m_pushed_line.reset();
331 return true;
332 }
333
334 while( std::getline( aStream, aLine ) )
335 {
336 // Trim whitespace
337 aLine.erase( 0, aLine.find_first_not_of( " \t\r\n" ) );
338 aLine.erase( aLine.find_last_not_of( " \t\r\n" ) + 1 );
339
340 if( aLine.empty() ) continue;
341 if( aLine.rfind( "*REMARK*", 0 ) == 0 ) continue;
342 return true;
343 }
344 return false;
345}
346
347void PARSER::pushBackLine( const std::string& aLine )
348{
349 m_pushed_line = aLine;
350}
351
352void PARSER::parseSectionPCB( std::ifstream& aStream )
353{
354 std::string line;
355 while( readLine( aStream, line ) )
356 {
357 if( line[0] == '*' )
358 {
359 pushBackLine( line );
360 break;
361 }
362
363 std::istringstream iss( line );
364 std::string token;
365 iss >> token;
366
367 if( token == "UNITS" )
368 {
369 std::string val;
370 iss >> val;
371
372 if( val == "0" ) m_parameters.units = UNIT_TYPE::MILS;
373 else if( val == "1" ) m_parameters.units = UNIT_TYPE::METRIC;
374 else if( val == "2" ) m_parameters.units = UNIT_TYPE::INCHES;
375 }
376 else if( token == "USERGRID" )
377 {
378 iss >> m_parameters.user_grid;
379 }
380 else if( token == "MAXIMUMLAYER" )
381 {
382 iss >> m_parameters.layer_count;
383 }
384 else if( token == "ORIGIN" )
385 {
386 iss >> m_parameters.origin.x >> m_parameters.origin.y;
387 }
388 else if( token == "THERLINEWID" )
389 {
390 iss >> m_parameters.thermal_line_width;
391 }
392 else if( token == "THERSMDWID" )
393 {
394 iss >> m_parameters.thermal_smd_width;
395 }
396 else if( token == "THERFLAGS" )
397 {
398 std::string flags_str;
399 iss >> flags_str;
400
401 try
402 {
403 m_parameters.thermal_flags = std::stoi( flags_str, nullptr, 0 );
404 }
405 catch( const std::exception& )
406 {
407 m_parameters.thermal_flags = 0;
408 }
409 }
410 else if( token == "DRLOVERSIZE" )
411 {
412 iss >> m_parameters.drill_oversize;
413 }
414 else if( token == "VIAPSHVIA" )
415 {
416 iss >> m_parameters.default_signal_via;
417 }
418 else if( token == "STMINCLEAR" )
419 {
420 iss >> m_parameters.thermal_min_clearance;
421 }
422 else if( token == "STMINSPOKES" )
423 {
424 iss >> m_parameters.thermal_min_spokes;
425 }
426 else if( token == "MINCLEAR" )
427 {
428 iss >> m_design_rules.min_clearance;
429 }
430 else if( token == "DEFAULTCLEAR" )
431 {
432 iss >> m_design_rules.default_clearance;
433 }
434 else if( token == "MINTRACKWID" )
435 {
436 iss >> m_design_rules.min_track_width;
437 }
438 else if( token == "DEFAULTTRACKWID" )
439 {
440 iss >> m_design_rules.default_track_width;
441 }
442 else if( token == "MINVIASIZE" )
443 {
444 iss >> m_design_rules.min_via_size;
445 }
446 else if( token == "DEFAULTVIASIZE" )
447 {
448 iss >> m_design_rules.default_via_size;
449 }
450 else if( token == "MINVIADRILL" )
451 {
452 iss >> m_design_rules.min_via_drill;
453 }
454 else if( token == "DEFAULTVIADRILL" )
455 {
456 iss >> m_design_rules.default_via_drill;
457 }
458 else if( token == "HOLEHOLE" )
459 {
460 iss >> m_design_rules.hole_to_hole;
461 }
462 else if( token == "SILKCLEAR" )
463 {
464 iss >> m_design_rules.silk_clearance;
465 }
466 else if( token == "MASKCLEAR" )
467 {
468 iss >> m_design_rules.mask_clearance;
469 }
470 }
471}
472
473void PARSER::parseSectionPARTS( std::ifstream& aStream )
474{
475 std::string line;
476 while( readLine( aStream, line ) )
477 {
478 if( line.find( "*REMARK*" ) == 0 )
479 continue;
480
481 if( line[0] == '*' )
482 {
483 pushBackLine( line );
484 break;
485 }
486
487 // Skip attribute lines and other non-part lines
488 if( line.rfind( "}", 0 ) == 0 ||
489 line.rfind( "{", 0 ) == 0 )
490 {
491 continue;
492 }
493
494 std::istringstream iss( line );
495 PART part;
496 part.location.x = 0.0;
497 part.location.y = 0.0;
498
499 std::string name_token, parttype_string;
500 iss >> name_token >> parttype_string >> part.location.x >> part.location.y >> part.rotation;
501
502 if( iss.fail() )
503 {
504 continue;
505 }
506
507 // Check for shortcut format: PRE{n1-n2}
508 // Example: C{2-20} with same attributes creates C2 through C20
509 std::vector<std::string> expanded_names = expandShortcutPattern( name_token );
510 bool is_shortcut = ( expanded_names.size() > 1 );
511 part.name = expanded_names[0];
512
513 // Check for explicit decal override using @ syntax
514 // Format: PARTTYPE@DECAL_NAME means use DECAL_NAME instead of looking up from PARTTYPE
515 size_t at_pos = parttype_string.find( '@' );
516
517 if( at_pos != std::string::npos )
518 {
519 // Explicit decal specified after @
520 part.part_type = parttype_string.substr( 0, at_pos );
521 part.decal = parttype_string.substr( at_pos + 1 );
522 part.explicit_decal = true;
523 }
524 else
525 {
526 // No @ - could be a direct decal name or a part type name
527 // Store as decal for now, resolution happens in pcb_io_pads.cpp
528 // Split on ':' to get primary and alternates (for direct decal lists)
529 size_t pos = 0;
530 size_t colon_pos = 0;
531 bool first = true;
532
533 while( ( colon_pos = parttype_string.find( ':', pos ) ) != std::string::npos )
534 {
535 std::string decal_name = parttype_string.substr( pos, colon_pos - pos );
536
537 if( first )
538 {
539 part.decal = decal_name;
540 first = false;
541 }
542 else
543 {
544 part.alternate_decals.push_back( decal_name );
545 }
546
547 pos = colon_pos + 1;
548 }
549
550 // Handle the last (or only) decal name
551 std::string last_decal = parttype_string.substr( pos );
552
553 if( first )
554 {
555 part.decal = last_decal;
556 }
557 else
558 {
559 part.alternate_decals.push_back( last_decal );
560 }
561 }
562
563 // Read all remaining tokens
564 std::vector<std::string> tokens;
565 std::string token;
566 while( iss >> token )
567 {
568 tokens.push_back( token );
569 }
570
571 int labels = 0;
572
573 // Process tokens for flags and label count
574 // Format per REMARK: GLUE MIRROR ALT CLSTID CLSTATTR BROTHERID LABELS
575 // GLUE: U (unglued) or G (glued)
576 // MIRROR: N (normal/top) or M (mirrored/bottom)
577 // ALT: Alternate decal index (0-based, -1 or missing = use primary)
578 for( size_t i = 0; i < tokens.size(); ++i )
579 {
580 const std::string& t = tokens[i];
581
582 if( t == "G" )
583 part.glued = true;
584 else if( t == "M" )
585 part.bottom_layer = true;
586
587 // U = unglued (default), N = normal/not-mirrored (default)
588 // These are defaults so we don't need to explicitly handle them
589
590 // Parse ALT field (token index 2 after GLUE and MIRROR)
591 // ALT field is 0-indexed in PADS format
592 if( i == 2 )
593 {
594 int alt = PADS_COMMON::ParseInt( t, -1, "PART ALT" );
595
596 if( alt >= 0 )
597 part.alt_decal_index = alt;
598 }
599
600 // The last token is the label count
601 if( i == tokens.size() - 1 )
602 {
603 try
604 {
605 size_t pos = 0;
606 labels = std::stoi( t, &pos );
607
608 if( pos != t.length() )
609 labels = 0;
610 }
611 catch( const std::exception& )
612 {
613 labels = 0;
614 }
615 }
616 }
617
618 // Check for optional .REUSE. line following part header
619 // Format: .REUSE. instance part
620 if( readLine( aStream, line ) )
621 {
622 if( line.find( ".REUSE." ) == 0 )
623 {
624 std::istringstream riss( line );
625 std::string reuse_keyword;
626 riss >> reuse_keyword >> part.reuse_instance >> part.reuse_part;
627 }
628 else
629 {
630 pushBackLine( line );
631 }
632 }
633
634 for( int i = 0; i < labels; ++i )
635 {
636 ATTRIBUTE attr;
637 if( !readLine( aStream, line ) ) break;
638
639 std::stringstream iss_attr( line );
640 std::string visible_str;
641 std::string mirrored_str;
642 std::string right_reading_str;
643
644 // VISIBLE XLOC YLOC ORI LEVEL HEIGHT WIDTH MIRRORED HJUST VJUST [RIGHTREADING]
645 if( iss_attr >> visible_str >> attr.x >> attr.y >> attr.orientation >> attr.level
646 >> attr.height >> attr.width >> mirrored_str >> attr.hjust >> attr.vjust )
647 {
648 attr.visible = ( visible_str == "VALUE" || visible_str == "FULL_NAME"
649 || visible_str == "NAME" || visible_str == "FULL_BOTH"
650 || visible_str == "BOTH" );
651 attr.mirrored = ( mirrored_str == "M" );
652 iss_attr >> right_reading_str;
653 attr.right_reading = ( right_reading_str == "Y" || right_reading_str == "ORTHO" );
654 }
655
656 // Line 2: Font
657 if( !readLine( aStream, line ) ) break;
658 attr.font_info = line;
659
660 // Line 3: Name
661 if( !readLine( aStream, line ) ) break;
662 attr.name = line;
663
664 part.attributes.push_back( attr );
665 }
666
667 // Add the first part (or only part if not a shortcut)
668 m_parts.push_back( part );
669
670 // If this was a shortcut pattern, create additional parts with same attributes
671 // but different reference designators
672 if( is_shortcut )
673 {
674 for( size_t i = 1; i < expanded_names.size(); ++i )
675 {
676 PART additional_part = part;
677 additional_part.name = expanded_names[i];
678 m_parts.push_back( additional_part );
679 }
680 }
681 }
682}
683
684void PARSER::parseSectionNETS( std::ifstream& aStream )
685{
686 // Implementation for NETS
687 // Format: *NET* NETNAME
688 // REF.PIN REF.PIN ... [.REUSE. instance rsignal]
689 // Supports shortcut format: PRE{n1-n2}.{pin1-pin2} expands to multiple pins
690 std::string line;
691 NET* current_net = nullptr;
692
693 // Helper lambda to parse a pin token that may have .REUSE. suffix
694 auto parsePinToken = []( const std::string& token, NET_PIN& pin ) -> bool
695 {
696 size_t dot_pos = token.find( '.' );
697
698 if( dot_pos == std::string::npos )
699 return false;
700
701 pin.ref_des = token.substr( 0, dot_pos );
702 pin.pin_name = token.substr( dot_pos + 1 );
703 return true;
704 };
705
706 // Helper lambda to expand shortcut format tokens like U{4-8}.{7-8}
707 // Returns a vector of expanded pins
708 auto expandShortcutPin = []( const std::string& token ) -> std::vector<std::string>
709 {
710 std::vector<std::string> results;
711
712 // Check if this contains any {n-m} range patterns
713 if( token.find( '{' ) == std::string::npos )
714 {
715 results.push_back( token );
716 return results;
717 }
718
719 // Parse the token to find all range patterns
720 // Format: PREFIX{start-end}MIDDLE{start-end}SUFFIX...
721 struct RangePart
722 {
723 std::string prefix;
724 int start = 0;
725 int end = 0;
726 bool is_range = false;
727 };
728
729 std::vector<RangePart> parts;
730 size_t pos = 0;
731 std::string current_prefix;
732
733 while( pos < token.size() )
734 {
735 if( token[pos] == '{' )
736 {
737 size_t close_pos = token.find( '}', pos );
738
739 if( close_pos == std::string::npos )
740 {
741 // Malformed, return as-is
742 results.push_back( token );
743 return results;
744 }
745
746 std::string range_str = token.substr( pos + 1, close_pos - pos - 1 );
747 size_t dash_pos = range_str.find( '-' );
748
749 if( dash_pos != std::string::npos )
750 {
751 RangePart part;
752 part.prefix = current_prefix;
753 part.is_range = true;
754
755 part.start = PADS_COMMON::ParseInt( range_str.substr( 0, dash_pos ),
756 INT_MIN, "net range" );
757 part.end = PADS_COMMON::ParseInt( range_str.substr( dash_pos + 1 ),
758 INT_MIN, "net range" );
759
760 if( part.start == INT_MIN || part.end == INT_MIN )
761 {
762 results.push_back( token );
763 return results;
764 }
765
766 parts.push_back( part );
767 current_prefix.clear();
768 }
769 else
770 {
771 // Single value in braces, treat as literal
772 current_prefix += range_str;
773 }
774
775 pos = close_pos + 1;
776 }
777 else
778 {
779 current_prefix += token[pos];
780 pos++;
781 }
782 }
783
784 // Add any trailing text as a final non-range part
785 if( !current_prefix.empty() || parts.empty() )
786 {
787 RangePart final_part;
788 final_part.prefix = current_prefix;
789 final_part.is_range = false;
790 final_part.start = 0;
791 final_part.end = 0;
792 parts.push_back( final_part );
793 }
794
795 // Generate all combinations
796 // Start with empty string
797 results.push_back( "" );
798
799 for( const auto& part : parts )
800 {
801 std::vector<std::string> new_results;
802
803 if( part.is_range )
804 {
805 for( const auto& base : results )
806 {
807 int step = ( part.start <= part.end ) ? 1 : -1;
808
809 for( int i = part.start; step > 0 ? i <= part.end : i >= part.end; i += step )
810 {
811 new_results.push_back( base + part.prefix + std::to_string( i ) );
812 }
813 }
814 }
815 else
816 {
817 for( const auto& base : results )
818 {
819 new_results.push_back( base + part.prefix );
820 }
821 }
822
823 results = std::move( new_results );
824 }
825
826 return results;
827 };
828
829 while( readLine( aStream, line ) )
830 {
831 if( line[0] == '*' )
832 {
833 pushBackLine( line );
834 break;
835 }
836
837 std::istringstream iss( line );
838 std::string token;
839 iss >> token;
840
841 if( token == "SIGNAL" )
842 {
843 NET net;
844 iss >> net.name;
845 m_nets.push_back( net );
846 current_net = &m_nets.back();
847
848 // Parse remaining tokens on this line
849 std::string pin_token;
850
851 while( iss >> pin_token )
852 {
853 // Check for .REUSE. suffix
854 if( pin_token == ".REUSE." )
855 {
856 // Read instance and signal for the previous pin
857 std::string instance, rsignal;
858
859 if( ( iss >> instance >> rsignal ) && !current_net->pins.empty() )
860 {
861 current_net->pins.back().reuse_instance = instance;
862 current_net->pins.back().reuse_signal = rsignal;
863 }
864
865 continue;
866 }
867
868 // Expand shortcut format and add all resulting pins
869 for( const auto& expanded : expandShortcutPin( pin_token ) )
870 {
871 NET_PIN pin;
872
873 if( parsePinToken( expanded, pin ) )
874 current_net->pins.push_back( pin );
875 }
876 }
877 }
878 else
879 {
880 // Continuation of pins for current net
881 if( current_net )
882 {
883 do
884 {
885 // Check for .REUSE. suffix
886 if( token == ".REUSE." )
887 {
888 std::string instance, rsignal;
889
890 if( ( iss >> instance >> rsignal ) && !current_net->pins.empty() )
891 {
892 current_net->pins.back().reuse_instance = instance;
893 current_net->pins.back().reuse_signal = rsignal;
894 }
895
896 continue;
897 }
898
899 // Expand shortcut format and add all resulting pins
900 for( const auto& expanded : expandShortcutPin( token ) )
901 {
902 NET_PIN pin;
903
904 if( parsePinToken( expanded, pin ) )
905 current_net->pins.push_back( pin );
906 }
907
908 } while( iss >> token );
909 }
910 }
911 }
912}
913
914void PARSER::parseSectionVIA( std::ifstream& aStream )
915{
916 std::string line;
917
918 while( readLine( aStream, line ) )
919 {
920 if( line[0] == '*' )
921 {
922 pushBackLine( line );
923 return;
924 }
925
926 std::stringstream iss( line );
927 std::string name;
928 double drill = 0.0;
929 int stacklines = 0;
930
931 if( !( iss >> name >> drill >> stacklines ) )
932 continue;
933
934 VIA_DEF def;
935 def.name = name;
936 def.drill = drill;
937
938 // Parse optional drill_start and drill_end for blind/buried vias
939 int drill_start_val = 0;
940 int drill_end_val = 0;
941
942 if( iss >> drill_start_val >> drill_end_val )
943 {
944 def.drill_start = drill_start_val;
945 def.drill_end = drill_end_val;
946 }
947
948 int min_layer = INT_MAX;
949 int max_layer = INT_MIN;
950
951 for( int i = 0; i < stacklines; ++i )
952 {
953 if( !readLine( aStream, line ) )
954 break;
955
956 std::stringstream iss2( line );
957 int level = 0;
958 double size = 0.0;
959 std::string shape;
960
961 if( !( iss2 >> level >> size >> shape ) )
962 continue;
963
964 PAD_STACK_LAYER layer_data;
965 layer_data.layer = level;
966 layer_data.shape = shape;
967 layer_data.sizeA = size;
968 layer_data.plated = true;
969
970 // Parse shape-specific parameters per PADS spec
971 if( shape == "R" || shape == "S" )
972 {
973 // Round or Square pad: level size shape [corner]
974 // Negative corner = chamfered, positive = rounded, zero = square
975 double corner = 0;
976
977 if( shape == "S" && ( iss2 >> corner ) )
978 {
979 if( corner < 0 )
980 {
981 layer_data.corner_radius = -corner;
982 layer_data.chamfered = true;
983 }
984 else
985 {
986 layer_data.corner_radius = corner;
987 }
988 }
989 }
990 else if( shape == "RA" || shape == "SA" )
991 {
992 // Anti-pad shapes: level size shape (no additional params)
993 // These define clearance shapes in planes
994 }
995 else if( shape == "A" )
996 {
997 // Annular pad: level size shape inner_diameter
998 double intd = 0;
999
1000 if( iss2 >> intd )
1001 layer_data.inner_diameter = intd;
1002 }
1003 else if( shape == "OF" )
1004 {
1005 // Oval finger: level size shape orientation length offset
1006 double ori = 0, length = 0, offset = 0;
1007
1008 if( iss2 >> ori >> length >> offset )
1009 {
1010 layer_data.rotation = ori;
1011 layer_data.sizeB = length;
1012 layer_data.finger_offset = offset;
1013 }
1014 }
1015 else if( shape == "RF" )
1016 {
1017 // Rectangular finger: level size shape orientation length offset
1018 // Per reference parser: rotation is first, then length (becomes sizeB), then offset
1019 double ori = 0, length = 0, offset = 0;
1020
1021 if( iss2 >> ori >> length >> offset )
1022 {
1023 layer_data.rotation = ori;
1024 layer_data.sizeB = length;
1025 layer_data.finger_offset = offset;
1026 }
1027 }
1028 else if( shape == "RT" || shape == "ST" )
1029 {
1030 // Thermal pads: level size shape orientation inner_diam spoke_width spoke_count
1031 double ori = 0, intd = 0, spkwid = 0;
1032 int spknum = 4;
1033
1034 if( iss2 >> ori >> intd >> spkwid >> spknum )
1035 {
1036 layer_data.thermal_spoke_orientation = ori;
1037 layer_data.thermal_outer_diameter = intd;
1038 layer_data.thermal_spoke_width = spkwid;
1039 layer_data.thermal_spoke_count = spknum;
1040 }
1041 }
1042 else if( shape == "O" || shape == "OC" )
1043 {
1044 // Odd shape (O) or Odd Circle (OC): level size shape
1045 // These use custom pad shapes defined elsewhere
1046 // No additional parameters, just store the shape type
1047 }
1048 else if( shape == "RC" )
1049 {
1050 // Rectangular with Corner: level size RC orientation length offset [corner]
1051 // Similar to RF but with optional corner radius
1052 double ori = 0, length = 0, offset = 0, corner = 0;
1053
1054 if( iss2 >> ori >> length >> offset )
1055 {
1056 layer_data.rotation = ori;
1057 layer_data.sizeB = length;
1058 layer_data.finger_offset = offset;
1059
1060 if( iss2 >> corner )
1061 {
1062 if( corner < 0 )
1063 {
1064 layer_data.corner_radius = -corner;
1065 layer_data.chamfered = true;
1066 }
1067 else
1068 {
1069 layer_data.corner_radius = corner;
1070 }
1071 }
1072 }
1073 }
1074
1075 def.stack.push_back( layer_data );
1076
1077 // Map special layer numbers to copper layer indices.
1078 // Non-copper layers (soldermask, silkscreen, etc.) must not
1079 // affect via type classification or pad size.
1080 int effective_layer = level;
1081
1082 if( level == -2 )
1083 effective_layer = 1;
1084 else if( level == -1 )
1085 effective_layer = m_parameters.layer_count;
1086
1087 bool is_copper = ( effective_layer >= 1
1088 && effective_layer <= m_parameters.layer_count );
1089
1090 if( is_copper )
1091 {
1092 if( size > def.size )
1093 def.size = size;
1094
1095 if( effective_layer < min_layer )
1096 min_layer = effective_layer;
1097
1098 if( effective_layer > max_layer )
1099 max_layer = effective_layer;
1100 }
1101
1102 // PADS layer 25 = top soldermask, 28 = bottom soldermask
1103 if( level == 25 )
1104 def.has_mask_front = true;
1105 else if( level == 28 )
1106 def.has_mask_back = true;
1107 }
1108
1109 // Determine layer span and via type
1110 if( min_layer <= max_layer )
1111 {
1112 def.start_layer = min_layer;
1113 def.end_layer = max_layer;
1114
1115 int layer_count = m_parameters.layer_count;
1116 bool starts_at_surface = ( min_layer == 1 || max_layer == layer_count );
1117 bool ends_at_surface = ( max_layer == layer_count || min_layer == 1 );
1118 bool is_full_span = ( min_layer == 1 && max_layer == layer_count );
1119 int span = max_layer - min_layer;
1120
1121 if( is_full_span )
1122 {
1124 }
1125 else if( span == 1 && ( min_layer == 1 || max_layer == layer_count ) )
1126 {
1128 }
1129 else if( starts_at_surface || ends_at_surface )
1130 {
1132 }
1133 else
1134 {
1136 }
1137 }
1138
1139 m_via_defs[name] = def;
1140 }
1141
1142 // If no signal via was specified in the header, fall back to the first definition
1143 if( m_parameters.default_signal_via.empty() && !m_via_defs.empty() )
1144 m_parameters.default_signal_via = m_via_defs.begin()->first;
1145}
1146
1147void PARSER::parseSectionPOUR( std::ifstream& aStream )
1148{
1149 std::string line;
1150
1151 while( readLine( aStream, line ) )
1152 {
1153 if( line[0] == '*' )
1154 {
1155 pushBackLine( line );
1156 return;
1157 }
1158
1159 // Parse Header
1160 // NAME TYPE XLOC YLOC PIECES FLAGS [OWNERNAME SIGNAME [HATCHGRID HATCHRAD [PRIORITY]]]
1161 std::stringstream iss( line );
1162 std::string name, type;
1163 double x = 0.0, y = 0.0;
1164 int pieces = 0, flags = 0;
1165
1166 if( !( iss >> name >> type >> x >> y >> pieces >> flags ) )
1167 continue;
1168
1169 std::string owner, signame;
1170 double hatchgrid = 0.0, hatchrad = 0.0;
1171 int priority = 0;
1172
1173 if( iss >> owner >> signame )
1174 {
1175 iss >> hatchgrid >> hatchrad >> priority;
1176 }
1177
1178 for( int i = 0; i < pieces; ++i )
1179 {
1180 if( !readLine( aStream, line ) )
1181 break;
1182
1183 // PIECETYPE CORNERS ARCS WIDTH LEVEL [THERMALS]
1184 // PIECETYPE: POLY, SEG, CIRCLE, CUTOUT, CIRCUT, POCUT
1185 std::stringstream iss2( line );
1186 std::string poly_type;
1187 int corners = 0, arcs = 0;
1188 double width = 0.0;
1189 int level = 0;
1190
1191 if( !( iss2 >> poly_type >> corners >> arcs >> width >> level ) )
1192 continue;
1193
1194 POUR pour;
1195 pour.name = name;
1196 pour.net_name = signame;
1197 pour.layer = level;
1198 pour.priority = priority;
1199 pour.width = width;
1200 pour.is_cutout = ( poly_type == "POCUT" || poly_type == "CUTOUT"
1201 || poly_type == "CIRCUT" );
1202 pour.owner_pour = owner;
1203 pour.hatch_grid = hatchgrid;
1204 pour.hatch_width = hatchrad;
1205
1206 // The header TYPE field (POUROUT, HATOUT, VOIDOUT, PADTHERM, VIATHERM)
1207 // determines the record's role. The piece-level poly_type (POLY, SEG, etc.)
1208 // only describes the geometry shape.
1209 if( type == "HATOUT" )
1210 {
1212 }
1213 else if( type == "VOIDOUT" )
1214 {
1216 pour.is_cutout = true;
1217 }
1218 else if( type == "PADTHERM" )
1219 {
1221 }
1222 else if( type == "VIATHERM" )
1223 {
1225 }
1226
1227 // Handle different piece types
1228 if( poly_type == "CIRCLE" || poly_type == "CIRCUT" )
1229 {
1230 // Circle piece: one line with center and radius info
1231 // Format: xloc yloc radius
1232 if( !readLine( aStream, line ) )
1233 break;
1234
1235 std::stringstream iss3( line );
1236 double cx = 0.0, cy = 0.0, radius = 0.0;
1237
1238 if( iss3 >> cx >> cy >> radius )
1239 {
1240 // Create arc representing full circle
1241 ARC arc{};
1242 arc.cx = x + cx;
1243 arc.cy = y + cy;
1244 arc.radius = radius;
1245 arc.start_angle = 0.0;
1246 arc.delta_angle = 360.0;
1247 pour.points.emplace_back( x + cx + radius, y + cy, arc );
1248 }
1249 }
1250 else if( poly_type == "SEG" )
1251 {
1252 // Segment piece: pairs of points defining line segments
1253 for( int j = 0; j < corners; ++j )
1254 {
1255 if( !readLine( aStream, line ) )
1256 break;
1257
1258 std::stringstream iss3( line );
1259 double px = 0.0, py = 0.0;
1260
1261 if( iss3 >> px >> py )
1262 {
1263 pour.points.emplace_back( x + px, y + py );
1264 }
1265 }
1266 }
1267 else
1268 {
1269 // Polygon piece types: POLY, POCUT, HATOUT, POUROUT, VOIDOUT,
1270 // PADTHERM, VIATHERM.
1271 //
1272 // Total data lines = corners + arcs. Lines with 4 values
1273 // (cx cy beginAngle sweepAngle) define an arc center and
1274 // angles. The following line gives the arc endpoint.
1275 int totalLines = corners + arcs;
1276 bool nextIsArcEndpoint = false;
1277 ARC pendingArc{};
1278
1279 for( int j = 0; j < totalLines; ++j )
1280 {
1281 if( !readLine( aStream, line ) )
1282 break;
1283
1284 std::stringstream iss3( line );
1285 double px = 0.0, py = 0.0;
1286
1287 if( !( iss3 >> px >> py ) )
1288 continue;
1289
1290 int angle1 = 0, angle2 = 0;
1291
1292 if( iss3 >> angle1 >> angle2 )
1293 {
1294 // Arc center line. The two angles are begin angle
1295 // (direction from center to the previous vertex) and
1296 // sweep angle, both in tenths of degrees.
1297 pendingArc = ARC{};
1298 pendingArc.cx = x + px;
1299 pendingArc.cy = y + py;
1300 pendingArc.start_angle = angle1 / 10.0;
1301 pendingArc.delta_angle = angle2 / 10.0;
1302
1303 if( !pour.points.empty() )
1304 {
1305 double dx = pour.points.back().x - pendingArc.cx;
1306 double dy = pour.points.back().y - pendingArc.cy;
1307 pendingArc.radius = std::sqrt( dx * dx + dy * dy );
1308 }
1309
1310 nextIsArcEndpoint = true;
1311 }
1312 else if( nextIsArcEndpoint )
1313 {
1314 if( pendingArc.radius == 0.0 )
1315 {
1316 double dx = ( x + px ) - pendingArc.cx;
1317 double dy = ( y + py ) - pendingArc.cy;
1318 pendingArc.radius = std::sqrt( dx * dx + dy * dy );
1319 }
1320
1321 pour.points.emplace_back( x + px, y + py, pendingArc );
1322 nextIsArcEndpoint = false;
1323 }
1324 else
1325 {
1326 pour.points.emplace_back( x + px, y + py );
1327 }
1328 }
1329 }
1330
1331 m_pours.push_back( pour );
1332 }
1333 }
1334}
1335
1336void PARSER::parseSectionPARTDECAL( std::ifstream& aStream )
1337{
1338 std::string line;
1339 while( readLine( aStream, line ) )
1340 {
1341 if( line[0] == '*' )
1342 {
1343 pushBackLine( line );
1344 return;
1345 }
1346
1347 // Header: NAME UNITS ORIX ORIY PIECES TERMINALS STACKS TEXT LABELS
1348 std::stringstream iss( line );
1349 std::string name, units;
1350 double orix = 0.0, oriy = 0.0;
1351 int pieces = 0, terminals = 0, stacks = 0, text_cnt = 0, labels = 0;
1352
1353 if( !( iss >> name >> units >> orix >> oriy >> pieces >> terminals >> stacks >> text_cnt >> labels ) )
1354 continue;
1355
1356 PART_DECAL decal;
1357 decal.name = name;
1358 decal.units = units;
1359
1360 // Parse Pieces (Graphics)
1361 for( int i = 0; i < pieces; ++i )
1362 {
1363 if( !readLine( aStream, line ) ) break;
1364
1365 // PIECETYPE CORNERS WIDTHHGHT LINESTYLE LEVEL [RESTRICTIONS]
1366 std::stringstream iss2( line );
1367 std::string type;
1368 int corners = 0;
1369 double width = 0;
1370 int linestyle = 0, level = 0;
1371
1372 if( !( iss2 >> type >> corners >> width ) )
1373 {
1374 // Should not happen if line is valid
1375 continue;
1376 }
1377
1378 // Try to read optional fields
1379 // Some formats have LINESTYLE LEVEL, others just LEVEL
1380 int val1 = 0;
1381 if( iss2 >> val1 )
1382 {
1383 int val2 = 0;
1384 if( iss2 >> val2 )
1385 {
1386 linestyle = val1;
1387 level = val2;
1388 }
1389 else
1390 {
1391 level = val1;
1392 }
1393 }
1394
1395 DECAL_ITEM item;
1396 item.type = type;
1397 item.width = width;
1398 item.layer = level;
1399
1400 // Handle TAG piece type (no coordinates, used for grouping copper/cutouts)
1401 if( type == "TAG" )
1402 {
1403 // Level is used as open/close flag: 1=open group, 0=close group
1404 item.is_tag_open = ( level == 1 );
1405 item.is_tag_close = ( level == 0 );
1406 decal.items.push_back( item );
1407 continue;
1408 }
1409
1410 // Parse pinnum for copper pieces (COPCLS, COPOPN, COPCIR, COPCUT, COPCCO)
1411 // Format includes [pinnum] at the end for copper associated with a pin
1412 if( type.find( "COP" ) == 0 )
1413 {
1414 std::string remaining;
1415 std::getline( iss2, remaining );
1416
1417 // Check for pinnum in remaining tokens
1418 std::istringstream rem_ss( remaining );
1419 int pinnum_val = -1;
1420
1421 if( rem_ss >> pinnum_val )
1422 item.pinnum = pinnum_val;
1423 }
1424
1425 // Parse restrictions for keepout pieces (KPTCLS, KPTCIR)
1426 if( type.find( "KPT" ) == 0 )
1427 {
1428 std::string restrictions;
1429
1430 if( iss2 >> restrictions )
1431 item.restrictions = restrictions;
1432 }
1433
1434 for( int j = 0; j < corners; ++j )
1435 {
1436 if( !readLine( aStream, line ) )
1437 break;
1438
1439 std::stringstream iss3( line );
1440 double px = 0.0, py = 0.0;
1441
1442 if( !( iss3 >> px >> py ) )
1443 continue;
1444
1445 // Per PADS spec, arc format is: x1 y1 ab aa ax1 ay1 ax2 ay2
1446 // where x1,y1 = arc start point, ab = begin angle (tenths of deg),
1447 // aa = sweep angle (tenths of deg), ax1,ay1/ax2,ay2 = bounding box
1448 int startAngleTenths = 0, deltaAngleTenths = 0;
1449 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
1450
1451 if( iss3 >> startAngleTenths >> deltaAngleTenths
1452 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
1453 {
1454 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
1455 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
1456 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
1457 double startAngle = startAngleTenths / 10.0;
1458 double deltaAngle = deltaAngleTenths / 10.0;
1459
1460 // Calculate arc start point (center + radius at start angle)
1461 double startAngleRad = startAngle * M_PI / 180.0;
1462 double startX = cx + radius * std::cos( startAngleRad );
1463 double startY = cy + radius * std::sin( startAngleRad );
1464
1465 // Calculate arc endpoint (center + radius at end angle)
1466 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
1467 double endX = cx + radius * std::cos( endAngleRad );
1468 double endY = cy + radius * std::sin( endAngleRad );
1469
1470 // Add arc start as a regular point (connects from previous point)
1471 item.points.emplace_back( startX, startY );
1472
1473 ARC arc{};
1474 arc.cx = cx;
1475 arc.cy = cy;
1476 arc.radius = radius;
1477 arc.start_angle = startAngle;
1478 arc.delta_angle = deltaAngle;
1479
1480 // Add arc end with arc data (draws the arc from start to end)
1481 item.points.emplace_back( endX, endY, arc );
1482 }
1483 else
1484 {
1485 item.points.emplace_back( px, py );
1486 }
1487 }
1488
1489 decal.items.push_back( item );
1490 }
1491
1492 // Parse Text/Labels
1493 // The header says how many text/labels.
1494 // In the example:
1495 // VALUE ...
1496 // Regular ...
1497 // Part Type
1498 // VALUE ...
1499 // Regular ...
1500 // Ref.Des.
1501
1502 // Each text/label seems to take 3 lines?
1503 // VALUE line, Font line, Content line.
1504
1505 for( int i = 0; i < text_cnt + labels; ++i )
1506 {
1507 std::string line1, line2, line3;
1508 if( !readLine( aStream, line1 ) ) break;
1509 if( !readLine( aStream, line2 ) ) break;
1510 if( !readLine( aStream, line3 ) ) break;
1511
1512 ATTRIBUTE attr;
1513 // Line 1: VALUE X Y ...
1514 std::stringstream ss( line1 );
1515 std::string type_token;
1516 ss >> type_token;
1517
1518 // First token is visibility type: VALUE, FULL_NAME, NAME, FULL_BOTH, BOTH, NONE
1519 std::string mirrored_str, right_reading_str;
1520
1521 if( ss >> attr.x >> attr.y >> attr.orientation >> attr.level
1522 >> attr.height >> attr.width >> mirrored_str >> attr.hjust >> attr.vjust )
1523 {
1524 attr.visible = ( type_token == "VALUE" || type_token == "FULL_NAME"
1525 || type_token == "NAME" || type_token == "FULL_BOTH"
1526 || type_token == "BOTH" );
1527 attr.mirrored = ( mirrored_str == "M" );
1528 ss >> right_reading_str;
1529 attr.right_reading = ( right_reading_str == "Y" || right_reading_str == "ORTHO" );
1530 }
1531
1532 attr.font_info = line2;
1533 attr.name = line3;
1534
1535 decal.attributes.push_back( attr );
1536 } // Parse Terminals (T lines)
1537 // T-150 -110 -150 -110 1
1538 // Format: T X Y NMX NMY PINNUM
1539 // Wait, the example has: T-150 -110 -150 -110 1
1540 // It seems to be T<X> <Y> <NMX> <NMY> <PINNUM>
1541 // Note: T is attached to X coordinate sometimes? "T-150"
1542
1543 for( int i = 0; i < terminals; ++i )
1544 {
1545 if( !readLine( aStream, line ) ) break;
1546
1547 // Handle T prefix
1548 size_t t_pos = line.find( 'T' );
1549 if( t_pos != std::string::npos )
1550 {
1551 line[t_pos] = ' '; // Replace T with space
1552 }
1553
1554 std::stringstream iss_t( line );
1555 TERMINAL term;
1556 double nmx = 0.0, nmy = 0.0;
1557
1558 if( iss_t >> term.x >> term.y >> nmx >> nmy >> term.name )
1559 {
1560 decal.terminals.push_back( term );
1561 }
1562 }
1563
1564 // Parse Stacks (PAD definitions)
1565 // PAD <PIN_INDEX> <STACK_LINES>
1566 // Then <STACK_LINES> lines of data.
1567
1568 for( int i = 0; i < stacks; ++i )
1569 {
1570 if( !readLine( aStream, line ) )
1571 break;
1572
1573 std::stringstream iss_pad( line );
1574 std::string token;
1575 int pin_idx = 0;
1576 int stack_lines = 0;
1577 iss_pad >> token >> pin_idx >> stack_lines;
1578
1579 if( token != "PAD" )
1580 continue;
1581
1582 // Parse optional P (plated) or N (non-plated) after stack_lines
1583 std::string plated_token;
1584 bool default_plated = true;
1585 double header_drill = 0.0;
1586
1587 if( iss_pad >> plated_token )
1588 {
1589 if( plated_token == "P" )
1590 default_plated = true;
1591 else if( plated_token == "N" )
1592 default_plated = false;
1593 else
1594 {
1595 header_drill = PADS_COMMON::ParseDouble( plated_token, 0.0, "pad drill" );
1596 }
1597 }
1598
1599 // Parse optional slotted drill parameters from header
1600 double header_slot_ori = 0.0;
1601 double header_slot_len = 0.0;
1602 double header_slot_off = 0.0;
1603
1604 if( iss_pad >> header_slot_ori >> header_slot_len >> header_slot_off )
1605 {
1606 // Got slotted drill from header
1607 }
1608
1609 std::vector<PAD_STACK_LAYER> stack;
1610
1611 for( int j = 0; j < stack_lines; ++j )
1612 {
1613 if( !readLine( aStream, line ) )
1614 break;
1615
1616 std::stringstream line_ss( line );
1617
1618 int layer = 0;
1619 double size = 0.0;
1620 std::string shape;
1621
1622 if( !( line_ss >> layer >> size >> shape ) )
1623 continue;
1624
1625 PAD_STACK_LAYER layer_data;
1626 layer_data.layer = layer;
1627 layer_data.sizeA = size;
1628 layer_data.sizeB = size;
1629 layer_data.shape = shape;
1630 layer_data.plated = default_plated;
1631 layer_data.drill = header_drill;
1632 layer_data.slot_orientation = header_slot_ori;
1633 layer_data.slot_length = header_slot_len;
1634 layer_data.slot_offset = header_slot_off;
1635
1636 // Parse shape-specific parameters per PADS specification
1637 if( shape == "R" )
1638 {
1639 // Round pad: level size R
1640 // No additional shape params, may have drill after
1641 }
1642 else if( shape == "S" )
1643 {
1644 // Square pad: level size S [corner]
1645 // Negative corner = chamfered, positive = rounded, zero = square
1646 double corner = 0.0;
1647
1648 if( line_ss >> corner )
1649 {
1650 if( corner < 0 )
1651 {
1652 layer_data.corner_radius = -corner;
1653 layer_data.chamfered = true;
1654 }
1655 else
1656 {
1657 layer_data.corner_radius = corner;
1658 }
1659 }
1660 }
1661 else if( shape == "RA" || shape == "SA" )
1662 {
1663 // Anti-pad shapes: level size RA/SA (no additional params)
1664 // These define clearance shapes in plane layers
1665 }
1666 else if( shape == "A" )
1667 {
1668 // Annular pad: level size A inner_diameter
1669 double intd = 0.0;
1670
1671 if( line_ss >> intd )
1672 layer_data.inner_diameter = intd;
1673 }
1674 else if( shape == "OF" )
1675 {
1676 // Oval finger: level size OF orientation length offset
1677 double ori = 0.0, length = 0.0, offset = 0.0;
1678
1679 if( line_ss >> ori >> length >> offset )
1680 {
1681 layer_data.rotation = ori;
1682 layer_data.sizeB = length;
1683 layer_data.finger_offset = offset;
1684 }
1685 }
1686 else if( shape == "RF" )
1687 {
1688 // Rectangular finger: level size RF orientation length offset [corner]
1689 // Per PADS spec, corner radius exists for square and rectangular finger shapes.
1690 double ori = 0.0, length = 0.0, offset = 0.0;
1691
1692 if( line_ss >> ori >> length >> offset )
1693 {
1694 layer_data.rotation = ori;
1695 layer_data.sizeB = length;
1696 layer_data.finger_offset = offset;
1697
1698 double corner = 0.0;
1699
1700 if( line_ss >> corner )
1701 {
1702 if( corner < 0 )
1703 {
1704 layer_data.corner_radius = -corner;
1705 layer_data.chamfered = true;
1706 }
1707 else
1708 {
1709 layer_data.corner_radius = corner;
1710 }
1711 }
1712 }
1713 }
1714 else if( shape == "RT" || shape == "ST" )
1715 {
1716 // Thermal pads: level size RT/ST orientation inner_diam spoke_width spoke_count
1717 double ori = 0.0, outsize = 0.0, spkwid = 0.0;
1718 int spknum = 4;
1719
1720 if( line_ss >> ori >> outsize >> spkwid >> spknum )
1721 {
1722 layer_data.thermal_spoke_orientation = ori;
1723 layer_data.thermal_outer_diameter = outsize;
1724 layer_data.thermal_spoke_width = spkwid;
1725 layer_data.thermal_spoke_count = spknum;
1726 }
1727 }
1728 else if( shape == "O" || shape == "OC" )
1729 {
1730 // Odd shape (O) or Odd Circle (OC): level size shape
1731 // These use custom pad shapes defined elsewhere
1732 // No additional parameters, just store the shape type
1733 }
1734 else if( shape == "RC" )
1735 {
1736 // Rectangular with Corner: level size RC orientation length offset [corner]
1737 // Similar to RF but with optional corner radius
1738 double ori = 0.0, length = 0.0, offset = 0.0, corner = 0.0;
1739
1740 if( line_ss >> ori >> length >> offset )
1741 {
1742 layer_data.rotation = ori;
1743 layer_data.sizeB = length;
1744 layer_data.finger_offset = offset;
1745
1746 if( line_ss >> corner )
1747 {
1748 if( corner < 0 )
1749 {
1750 layer_data.corner_radius = -corner;
1751 layer_data.chamfered = true;
1752 }
1753 else
1754 {
1755 layer_data.corner_radius = corner;
1756 }
1757 }
1758 }
1759 }
1760
1761 // For some shapes, additional tokens may be drill and plated
1762 // Read remaining tokens
1763 std::vector<std::string> remaining;
1764 std::string token_rem;
1765
1766 while( line_ss >> token_rem )
1767 remaining.push_back( token_rem );
1768
1769 // Parse remaining tokens for drill, plated, and slotted drill
1770 if( !remaining.empty() )
1771 {
1772 size_t idx = 0;
1773
1774 // Check for drill value (numeric)
1775 double drill_val = PADS_COMMON::ParseDouble( remaining[idx],
1776 -1.0, "pad layer drill" );
1777
1778 if( drill_val >= 0.0 )
1779 {
1780 layer_data.drill = drill_val;
1781 idx++;
1782 }
1783
1784 // Check for plated flag
1785 if( idx < remaining.size() )
1786 {
1787 if( remaining[idx] == "P" || remaining[idx] == "Y" )
1788 {
1789 layer_data.plated = true;
1790 idx++;
1791 }
1792 else if( remaining[idx] == "N" )
1793 {
1794 layer_data.plated = false;
1795 idx++;
1796 }
1797 }
1798
1799 // Check for slotted drill parameters
1800 if( idx + 2 < remaining.size() )
1801 {
1802 layer_data.slot_orientation =
1803 PADS_COMMON::ParseDouble( remaining[idx], 0.0, "slot params" );
1804 layer_data.slot_length =
1805 PADS_COMMON::ParseDouble( remaining[idx + 1], 0.0, "slot params" );
1806 layer_data.slot_offset =
1807 PADS_COMMON::ParseDouble( remaining[idx + 2], 0.0, "slot params" );
1808 }
1809 }
1810
1811 stack.push_back( layer_data );
1812 }
1813
1814 decal.pad_stacks[pin_idx] = stack;
1815 }
1816
1817 m_decals[name] = decal;
1818 }
1819}
1820
1821void PARSER::parseSectionROUTES( std::ifstream& aStream )
1822{
1823 std::string line;
1824 ROUTE* current_route = nullptr;
1825 TRACK current_track;
1826 bool in_track = false;
1827 bool prev_is_plane_connection = false;
1828 ARC_POINT last_plane_connection_pt;
1829 int last_plane_connection_layer = 0;
1830 double last_plane_connection_width = 0;
1831 bool last_plane_on_copper = false;
1832 std::string default_via_name;
1833
1834 while( readLine( aStream, line ) )
1835 {
1836 if( line[0] == '*' )
1837 {
1838 if( line.rfind( "*SIGNAL*", 0 ) == 0 )
1839 {
1840 if( in_track && current_route )
1841 {
1842 current_route->tracks.push_back( current_track );
1843 current_track.points.clear();
1844 in_track = false;
1845 }
1846
1847 prev_is_plane_connection = false;
1848
1849 std::istringstream iss( line );
1850 std::string token;
1851 iss >> token; // *SIGNAL*
1852
1853 std::string net_name;
1854 iss >> net_name;
1855
1856 // Parse optional flags and default via
1857 default_via_name.clear();
1858
1859 while( iss >> token )
1860 {
1861 if( !token.empty() && token.back() == ';' )
1862 token.pop_back();
1863
1864 if( m_via_defs.count( token ) )
1865 default_via_name = token;
1866 }
1867
1868 m_routes.push_back( ROUTE() );
1869 current_route = &m_routes.back();
1870 current_route->net_name = net_name;
1871 continue;
1872 }
1873
1874 pushBackLine( line );
1875 break;
1876 }
1877
1878 // Parse pin pair lines (start with non-digit/non-sign)
1879 // Format: "REF.PIN REF.PIN"
1880 // These indicate which pins are connected by the following route segments
1881 if( !isdigit( line[0] ) && line[0] != '-' && line[0] != '+' )
1882 {
1883 if( in_track && current_route )
1884 {
1885 current_route->tracks.push_back( current_track );
1886 current_track.points.clear();
1887 in_track = false;
1888 }
1889
1890 prev_is_plane_connection = false;
1891
1892 // Parse pin pairs from this line and add to current route
1893 if( current_route )
1894 {
1895 std::istringstream pin_iss( line );
1896 std::string pin_token;
1897
1898 while( pin_iss >> pin_token )
1899 {
1900 size_t dot_pos = pin_token.find( '.' );
1901
1902 if( dot_pos != std::string::npos )
1903 {
1904 NET_PIN pin;
1905 pin.ref_des = pin_token.substr( 0, dot_pos );
1906 pin.pin_name = pin_token.substr( dot_pos + 1 );
1907
1908 // Check if pin already exists (avoid duplicates)
1909 bool found = false;
1910
1911 for( const auto& existing : current_route->pins )
1912 {
1913 if( existing.ref_des == pin.ref_des &&
1914 existing.pin_name == pin.pin_name )
1915 {
1916 found = true;
1917 break;
1918 }
1919 }
1920
1921 if( !found )
1922 current_route->pins.push_back( pin );
1923 }
1924 }
1925 }
1926
1927 continue;
1928 }
1929
1930 std::istringstream iss( line );
1931 ARC_POINT pt;
1932 int layer = 0;
1933 double width = 0.0;
1934 int flags = 0;
1935 iss >> pt.x >> pt.y >> layer >> width >> flags;
1936
1937 if( iss.fail() )
1938 continue;
1939
1940 // SEGMENTWIDTH is already in mils (not 1/256 mil units as previously thought)
1941
1942 // Parse FLAGS and optional arc direction / via name
1943 // Format: FLAGS [ARCDIR/VIANAME] [POWER] [TEARDROP ...] [JUMPER ...]
1944 // ARCDIR can be CW (clockwise) or CCW (counter-clockwise)
1945 // POWER indicates a connection through a power/ground plane (not a discrete track)
1946 std::string token;
1947 std::string via_name;
1948 std::string arc_dir;
1949
1950 // Per PADS spec, layer 0 means "unrouted portion" (virtual connection through
1951 // a plane or ratline). Only layer 0 makes a segment non-physical. Flag 0x100
1952 // ("plane thermal") and the THERMAL keyword describe pad/via thermal relief
1953 // style and do not affect whether the track segment exists.
1954 bool is_unrouted = ( layer == 0 );
1955 bool is_plane_connection = is_unrouted;
1956 TEARDROP teardrop;
1957 JUMPER_MARKER jumper;
1958 bool has_teardrop = false;
1959 bool has_jumper = false;
1960 bool has_power = false;
1961
1962 while( iss >> token )
1963 {
1964 // Check for arc direction
1965 if( token == "CW" || token == "CCW" )
1966 {
1967 arc_dir = token;
1968 continue;
1969 }
1970
1971 // POWER indicates a connection through a power/ground plane.
1972 // In PADS files, "POWER" often doubles as a via definition name.
1973 // THERMAL describes pad/via thermal relief style.
1974 if( token == "POWER" )
1975 {
1976 has_power = true;
1977
1978 if( m_via_defs.count( token ) )
1979 via_name = token;
1980
1981 continue;
1982 }
1983
1984 if( token == "THERMAL" )
1985 continue;
1986
1987 // Check for via name
1988 if( m_via_defs.count( token ) )
1989 {
1990 via_name = token;
1991 continue;
1992 }
1993
1994 // Parse TEARDROP: TEARDROP [P width length [flags]] [N width length [flags]]
1995 if( token == "TEARDROP" )
1996 {
1997 has_teardrop = true;
1998 std::string td_token;
1999
2000 while( iss >> td_token )
2001 {
2002 if( td_token == "P" )
2003 {
2004 teardrop.has_pad_teardrop = true;
2005 iss >> teardrop.pad_width >> teardrop.pad_length;
2006
2007 // Check for optional flags (numeric)
2008 std::streampos pos = iss.tellg();
2009 int flags = 0;
2010
2011 if( iss >> flags )
2012 {
2013 teardrop.pad_flags = flags;
2014 }
2015 else
2016 {
2017 iss.clear();
2018 iss.seekg( pos );
2019 }
2020 }
2021 else if( td_token == "N" )
2022 {
2023 teardrop.has_net_teardrop = true;
2024 iss >> teardrop.net_width >> teardrop.net_length;
2025
2026 std::streampos pos = iss.tellg();
2027 int flags = 0;
2028
2029 if( iss >> flags )
2030 {
2031 teardrop.net_flags = flags;
2032 }
2033 else
2034 {
2035 iss.clear();
2036 iss.seekg( pos );
2037 }
2038 }
2039 else
2040 {
2041 // Not a teardrop param, push back for further parsing
2042 // Since we can't push back easily, break and handle below
2043 if( td_token == "CW" || td_token == "CCW" )
2044 arc_dir = td_token;
2045 else if( td_token == "POWER" )
2046 {
2047 has_power = true;
2048
2049 if( m_via_defs.count( td_token ) )
2050 via_name = td_token;
2051 }
2052 else if( td_token == "THERMAL" )
2053 ;
2054 else if( m_via_defs.count( td_token ) )
2055 via_name = td_token;
2056
2057 break;
2058 }
2059 }
2060
2061 continue;
2062 }
2063
2064 // Parse JUMPER: jumper_name S|E
2065 // Jumper names are typically followed by S (start) or E (end)
2066 std::streampos pos = iss.tellg();
2067 std::string jumper_flag;
2068
2069 if( iss >> jumper_flag )
2070 {
2071 if( jumper_flag == "S" || jumper_flag == "E" )
2072 {
2073 has_jumper = true;
2074 jumper.name = token;
2075 jumper.is_start = ( jumper_flag == "S" );
2076 jumper.x = pt.x;
2077 jumper.y = pt.y;
2078 continue;
2079 }
2080 else
2081 {
2082 // Not a jumper, restore stream position
2083 iss.clear();
2084 iss.seekg( pos );
2085 }
2086 }
2087 else
2088 {
2089 iss.clear();
2090 iss.seekg( pos );
2091 }
2092
2093 // Skip REUSE tokens
2094 if( token == "REUSE" || token == ".REUSE." )
2095 {
2096 // Skip the instance name that follows
2097 std::string instance;
2098 iss >> instance;
2099 continue;
2100 }
2101 }
2102
2103 // If an arc direction was specified, mark this as an arc point
2104 // The arc center will be calculated from the previous and next points
2105 if( !arc_dir.empty() )
2106 {
2107 // For route arcs, we need previous point to calculate arc parameters
2108 // The arc goes from previous point to this point with given direction
2109 // PADS uses CW/CCW to indicate arc direction
2110 // We'll store a placeholder arc and the loader will need to compute it
2111 pt.is_arc = true;
2112
2113 // Delta angle sign indicates direction: positive = CCW, negative = CW
2114 pt.arc.delta_angle = ( arc_dir == "CCW" ) ? 90.0 : -90.0;
2115 }
2116
2117 // Per PADS spec: Layer 0 means "unrouted portion" - these are NOT physical tracks.
2118 // Layer 65 indicates the end of route/connection at a component pin.
2119 // Vias are only created when an explicit via token (STANDARDVIA, etc.) is present.
2120
2121 int effective_layer = layer;
2122 bool is_pad_connection = ( layer == 65 );
2123
2124 // Layer 0 means "unrouted" - this segment goes through a plane or is a ratline.
2125 // We still need an effective layer for via purposes, so use current track layer if available.
2126 if( is_unrouted && in_track )
2127 effective_layer = current_track.layer;
2128
2129 // Create via at this point if a via token was present.
2130 // This must happen before plane connection handling since plane connection points
2131 // with vias (STANDARDVIA + THERMAL) would otherwise skip via creation.
2132 if( !via_name.empty() && current_route )
2133 {
2134 VIA via;
2135 via.name = via_name;
2136 via.location = { pt.x, pt.y };
2137 current_route->vias.push_back( via );
2138 }
2139
2140 // POWER on a real copper layer means a via to the inner power/ground plane.
2141 // Routes often stub out to a POWER point and backtrack, with the via providing
2142 // the connection to the plane. Normally the POWER token itself names a via
2143 // definition (handled above), but if not, create an implicit via with the
2144 // route's default via type.
2145 if( has_power && via_name.empty() && !is_unrouted && !is_pad_connection && current_route )
2146 {
2147 VIA implicit_via;
2148
2149 if( !default_via_name.empty() )
2150 implicit_via.name = default_via_name;
2151 else if( !m_parameters.default_signal_via.empty() )
2152 implicit_via.name = m_parameters.default_signal_via;
2153
2154 implicit_via.location = { pt.x, pt.y };
2155 current_route->vias.push_back( implicit_via );
2156 }
2157
2158 // Store teardrop if present
2159 if( has_teardrop && current_route )
2160 {
2161 current_route->teardrops.push_back( teardrop );
2162 }
2163
2164 // Store jumper marker if present
2165 if( has_jumper && current_route )
2166 {
2167 current_route->jumpers.push_back( jumper );
2168 }
2169
2170 // Handle plane connections (POWER or THERMAL markers)
2171 // Segments between consecutive plane connection points are virtual connections through
2172 // copper pours and should not be created as discrete tracks.
2173 if( is_plane_connection && prev_is_plane_connection )
2174 {
2175 // Both current and previous points are plane connections - skip this segment.
2176 // The connection is made through the copper pour, not a discrete track.
2177 // Save this point as a potential track start/end if it's on a real copper layer
2178 // (not layer 0 / unrouted). Copper-layer plane points are where signals transition
2179 // between physical tracks and the pour.
2180 if( !is_unrouted )
2181 {
2182 last_plane_connection_pt = pt;
2183 last_plane_connection_layer = effective_layer;
2184 last_plane_connection_width = width;
2185 last_plane_on_copper = true;
2186 }
2187
2188 prev_is_plane_connection = true;
2189 continue;
2190 }
2191
2192 if( is_plane_connection && !prev_is_plane_connection )
2193 {
2194 // Transitioning from track to plane - add this point to complete the track
2195 // then end the track (no further segments until we exit the plane)
2196 if( in_track )
2197 {
2198 current_track.points.push_back( pt );
2199
2200 if( current_route && current_track.points.size() > 1 )
2201 current_route->tracks.push_back( current_track );
2202
2203 current_track.points.clear();
2204 in_track = false;
2205 }
2206
2207 // Save this plane connection point as a potential track start for the next
2208 // non-plane segment, if it's on a real copper layer (not layer 0 / unrouted).
2209 // Copper-layer plane points mark where signals transition between tracks and pours.
2210 last_plane_on_copper = !is_unrouted;
2211
2212 if( last_plane_on_copper )
2213 {
2214 last_plane_connection_pt = pt;
2215 last_plane_connection_layer = effective_layer;
2216 last_plane_connection_width = width;
2217 }
2218
2219 prev_is_plane_connection = true;
2220 continue;
2221 }
2222
2223 if( !is_plane_connection && prev_is_plane_connection )
2224 {
2225 // Transitioning from plane to track. Start a new track from the last copper-layer
2226 // plane point if it was on the same layer as the current point.
2227 if( in_track && current_route && current_track.points.size() > 1 )
2228 current_route->tracks.push_back( current_track );
2229
2230 prev_is_plane_connection = false;
2231
2232 if( is_pad_connection )
2233 {
2234 in_track = false;
2235 continue;
2236 }
2237
2238 current_track.points.clear();
2239
2240 if( last_plane_on_copper && last_plane_connection_layer == effective_layer )
2241 {
2242 current_track.layer = effective_layer;
2243 current_track.width = std::max( width, last_plane_connection_width );
2244 current_track.points.push_back( last_plane_connection_pt );
2245 current_track.points.push_back( pt );
2246 }
2247 else
2248 {
2249 // Layers differ. Create an implicit via if same location.
2250 // POWER vias are already handled by the central POWER handler above.
2251 if( !has_power && via_name.empty() && current_route && last_plane_on_copper &&
2252 std::abs( pt.x - last_plane_connection_pt.x ) < 0.001 &&
2253 std::abs( pt.y - last_plane_connection_pt.y ) < 0.001 )
2254 {
2255 VIA implicit_via;
2256
2257 if( !default_via_name.empty() )
2258 implicit_via.name = default_via_name;
2259 else if( !m_parameters.default_signal_via.empty() )
2260 implicit_via.name = m_parameters.default_signal_via;
2261
2262 implicit_via.location = { pt.x, pt.y };
2263 current_route->vias.push_back( implicit_via );
2264 }
2265
2266 current_track.layer = effective_layer;
2267 current_track.width = width;
2268 current_track.points.push_back( pt );
2269 }
2270
2271 last_plane_on_copper = false;
2272 in_track = true;
2273 continue;
2274 }
2275
2276 // Layer 65 is a special pad connection marker. Add the final point to terminate the
2277 // track at the pad, then stop building this track segment.
2278 if( is_pad_connection )
2279 {
2280 if( in_track && !current_track.points.empty() )
2281 {
2282 current_track.points.push_back( pt );
2283
2284 if( current_route && current_track.points.size() > 1 )
2285 current_route->tracks.push_back( current_track );
2286
2287 current_track.points.clear();
2288 in_track = false;
2289 }
2290
2291 continue;
2292 }
2293
2294 // Normal track building (neither current nor previous is plane connection)
2295 prev_is_plane_connection = false;
2296
2297 if( !in_track )
2298 {
2299 current_track.layer = effective_layer;
2300 current_track.width = width;
2301 current_track.points.clear();
2302 current_track.points.push_back( pt );
2303 in_track = true;
2304 }
2305 else
2306 {
2307 bool layer_changed = ( effective_layer != current_track.layer );
2308 bool width_changed = ( std::abs( width - current_track.width ) > 0.001 );
2309
2310 if( layer_changed || width_changed )
2311 {
2312 // Check if we should connect to this point
2313 bool connect = true;
2314
2315 if( layer_changed && via_name.empty() )
2316 {
2317 bool same_location =
2318 ( pt.x == current_track.points.back().x &&
2319 pt.y == current_track.points.back().y );
2320
2321 if( same_location )
2322 {
2323 // Same location layer change implies an implicit via.
2324 // POWER vias are already created in the central POWER handler.
2325 if( !has_power && current_route )
2326 {
2327 VIA implicit_via;
2328 implicit_via.name =
2329 default_via_name.empty() ? "STANDARDVIA" : default_via_name;
2330 implicit_via.location = { pt.x, pt.y };
2331 current_route->vias.push_back( implicit_via );
2332 }
2333 }
2334 else if( !has_power )
2335 {
2336 // Different location without POWER, treat as jump/ratline
2337 connect = false;
2338 }
2339 // POWER at different location: via already created, keep connected
2340 }
2341
2342 if( connect )
2343 {
2344 current_track.points.push_back( pt );
2345 }
2346
2347 if( current_route )
2348 current_route->tracks.push_back( current_track );
2349
2350 // Start new track from current point
2351 ARC_POINT prev_pt = pt;
2352
2353 current_track.layer = effective_layer;
2354 current_track.width = width;
2355 current_track.points.clear();
2356 current_track.points.push_back( prev_pt );
2357 }
2358 else
2359 {
2360 current_track.points.push_back( pt );
2361 }
2362 }
2363 }
2364
2365 if( in_track && current_route )
2366 {
2367 current_route->tracks.push_back( current_track );
2368 }
2369}
2370
2371void PARSER::parseSectionTEXT( std::ifstream& aStream )
2372{
2373 std::string line;
2374
2375 while( readLine( aStream, line ) )
2376 {
2377 if( line[0] == '*' )
2378 {
2379 pushBackLine( line );
2380 break;
2381 }
2382
2383 // Format: X Y ORI LEVEL HEIGHT WIDTH M HJUST VJUST [NDIM] [.REUSE. instance]
2384 // HJUST: LEFT, CENTER, RIGHT
2385 // VJUST: UP, CENTER, DOWN
2386 std::istringstream iss( line );
2387 TEXT text;
2388
2389 iss >> text.location.x >> text.location.y >> text.rotation >> text.layer
2390 >> text.height >> text.width;
2391
2392 if( iss.fail() )
2393 continue;
2394
2395 std::string mirrored;
2396 iss >> mirrored;
2397 text.mirrored = ( mirrored == "M" );
2398
2399 // Parse optional hjust and vjust
2400 iss >> text.hjust >> text.vjust;
2401
2402 // Parse optional ndim and .REUSE. instance
2403 std::string token;
2404
2405 if( iss >> token )
2406 {
2407 if( token == ".REUSE." )
2408 {
2409 iss >> text.reuse_instance;
2410 }
2411 else
2412 {
2413 text.ndim = PADS_COMMON::ParseInt( token, 0, "text ndim" );
2414
2415 if( iss >> token && token == ".REUSE." )
2416 {
2417 iss >> text.reuse_instance;
2418 }
2419 }
2420 }
2421
2422 // Read Font line
2423 // Format: fontstyle[:fontheight:fontdescent] fontface
2424 if( readLine( aStream, line ) )
2425 {
2426 std::istringstream fiss( line );
2427 std::string font_style_part;
2428
2429 fiss >> font_style_part;
2430
2431 size_t colon_pos = font_style_part.find( ':' );
2432
2433 if( colon_pos != std::string::npos )
2434 {
2435 text.font_style = font_style_part.substr( 0, colon_pos );
2436 std::string remaining = font_style_part.substr( colon_pos + 1 );
2437
2438 size_t second_colon = remaining.find( ':' );
2439
2440 if( second_colon != std::string::npos )
2441 {
2442 text.font_height = PADS_COMMON::ParseDouble(
2443 remaining.substr( 0, second_colon ), 0.0, "font height" );
2444 text.font_descent = PADS_COMMON::ParseDouble(
2445 remaining.substr( second_colon + 1 ), 0.0, "font descent" );
2446 }
2447 else
2448 {
2449 text.font_height = PADS_COMMON::ParseDouble( remaining, 0.0, "font height" );
2450 }
2451 }
2452 else
2453 {
2454 text.font_style = font_style_part;
2455 }
2456
2457 // Extract font face (after angle brackets or rest of line)
2458 size_t bracket_start = line.find( '<' );
2459 size_t bracket_end = line.find( '>' );
2460
2461 if( bracket_start != std::string::npos && bracket_end != std::string::npos )
2462 {
2463 text.font_face = line.substr( bracket_start + 1, bracket_end - bracket_start - 1 );
2464 }
2465 else
2466 {
2467 std::string rest;
2468 std::getline( fiss, rest );
2469
2470 if( !rest.empty() && rest[0] == ' ' )
2471 rest = rest.substr( 1 );
2472
2473 text.font_face = rest;
2474 }
2475 }
2476
2477 // Read Content line
2478 if( readLine( aStream, line ) )
2479 {
2480 // Standard PADS format uses literal backslash-n for line breaks
2481 size_t pos = 0;
2482
2483 while( ( pos = line.find( "\\n", pos ) ) != std::string::npos )
2484 {
2485 line.replace( pos, 2, "\n" );
2486 pos += 1;
2487 }
2488
2489 // EasyEDA exports (mode "250L") encode newlines as underscores
2490 if( m_file_header.mode == "250L" )
2491 std::replace( line.begin(), line.end(), '_', '\n' );
2492
2493 text.content = line;
2494 m_texts.push_back( text );
2495 }
2496 }
2497}
2498
2499void PARSER::parseSectionBOARD( std::ifstream& aStream )
2500{
2501 // The *BOARD* section uses the same format as LINES section with linetype=BOARD
2502 // Format: name BOARD xloc yloc pieces flags [text]
2503 std::string line;
2504
2505 while( readLine( aStream, line ) )
2506 {
2507 if( line[0] == '*' )
2508 {
2509 pushBackLine( line );
2510 break;
2511 }
2512
2513 std::istringstream iss( line );
2514 std::string name, type;
2515 double xloc = 0.0, yloc = 0.0;
2516 int pieces = 0;
2517 iss >> name >> type >> xloc >> yloc >> pieces;
2518
2519 // Parse all pieces for this board outline entry
2520 for( int i = 0; i < pieces; ++i )
2521 {
2522 if( !readLine( aStream, line ) )
2523 break;
2524
2525 if( line[0] == '*' )
2526 {
2527 pushBackLine( line );
2528 return;
2529 }
2530
2531 std::istringstream piss( line );
2532 std::string shape_type;
2533 int corners = 0;
2534 double width = 0.0;
2535 int linestyle = 0, level = 0;
2536 piss >> shape_type >> corners >> width >> linestyle >> level;
2537
2538 // Handle CLOSED, OPEN, CIRCLE, BRDCLS (board cutout), BRDCIR (circular cutout)
2539 if( shape_type == "CLOSED" || shape_type == "OPEN" || shape_type == "BRDCLS" )
2540 {
2541 POLYLINE polyline;
2542 polyline.layer = 0;
2543 polyline.width = width;
2544 polyline.closed = ( shape_type == "CLOSED" || shape_type == "BRDCLS" );
2545
2546 for( int j = 0; j < corners; ++j )
2547 {
2548 if( !readLine( aStream, line ) )
2549 break;
2550
2551 if( line[0] == '*' )
2552 {
2553 pushBackLine( line );
2554 return;
2555 }
2556
2557 std::istringstream ciss( line );
2558 double dx = 0.0, dy = 0.0;
2559 ciss >> dx >> dy;
2560
2561 // Per PADS spec, arc format is: x1 y1 ab aa ax1 ay1 ax2 ay2
2562 // where x1,y1 = arc start point; center = bounding box midpoint
2563 int startAngleTenths = 0, deltaAngleTenths = 0;
2564 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
2565
2566 if( ciss >> startAngleTenths >> deltaAngleTenths
2567 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
2568 {
2569 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
2570 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
2571 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
2572 double startAngle = startAngleTenths / 10.0;
2573 double deltaAngle = deltaAngleTenths / 10.0;
2574
2575 double startAngleRad = startAngle * M_PI / 180.0;
2576 double startX = cx + radius * std::cos( startAngleRad );
2577 double startY = cy + radius * std::sin( startAngleRad );
2578
2579 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
2580 double endX = cx + radius * std::cos( endAngleRad );
2581 double endY = cy + radius * std::sin( endAngleRad );
2582
2583 polyline.points.emplace_back( xloc + startX, yloc + startY );
2584
2585 ARC arc{};
2586 arc.cx = xloc + cx;
2587 arc.cy = yloc + cy;
2588 arc.radius = radius;
2589 arc.start_angle = startAngle;
2590 arc.delta_angle = deltaAngle;
2591
2592 polyline.points.emplace_back( xloc + endX, yloc + endY, arc );
2593 }
2594 else
2595 {
2596 polyline.points.emplace_back( xloc + dx, yloc + dy );
2597 }
2598 }
2599
2600 if( !polyline.points.empty() )
2601 m_board_outlines.push_back( polyline );
2602 }
2603 else if( shape_type == "CIRCLE" || shape_type == "BRDCIR" )
2604 {
2605 // Circle format: 2 coordinates define opposite ends of diameter
2606 POLYLINE polyline;
2607 polyline.layer = 0;
2608 polyline.width = width;
2609 polyline.closed = true;
2610
2611 double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
2612
2613 if( readLine( aStream, line ) )
2614 {
2615 std::istringstream c1( line );
2616 c1 >> x1 >> y1;
2617 }
2618
2619 if( corners >= 2 && readLine( aStream, line ) )
2620 {
2621 std::istringstream c2( line );
2622 c2 >> x2 >> y2;
2623 }
2624
2625 // Calculate center and radius from diameter endpoints
2626 double cx = xloc + ( x1 + x2 ) / 2.0;
2627 double cy = yloc + ( y1 + y2 ) / 2.0;
2628 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 ) ) / 2.0;
2629
2630 // Create full circle arc
2631 ARC arc{};
2632 arc.cx = cx;
2633 arc.cy = cy;
2634 arc.radius = radius;
2635 arc.start_angle = 0.0;
2636 arc.delta_angle = 360.0;
2637
2638 polyline.points.emplace_back( cx + radius, cy, arc );
2639
2640 if( !polyline.points.empty() )
2641 m_board_outlines.push_back( polyline );
2642 }
2643 else
2644 {
2645 // Unknown shape type, skip corners
2646 for( int j = 0; j < corners; ++j )
2647 {
2648 if( !readLine( aStream, line ) )
2649 break;
2650
2651 if( line[0] == '*' )
2652 {
2653 pushBackLine( line );
2654 return;
2655 }
2656 }
2657 }
2658 }
2659 }
2660}
2661
2662void PARSER::parseSectionLINES( std::ifstream& aStream )
2663{
2664 std::string line;
2665
2666 while( readLine( aStream, line ) )
2667 {
2668 if( line[0] == '*' )
2669 {
2670 pushBackLine( line );
2671 break;
2672 }
2673
2674 // Header format: name type xloc yloc pieces flags [text [signame]]
2675 std::istringstream iss( line );
2676 std::string name, type;
2677 double xloc = 0.0, yloc = 0.0;
2678 int pieces = 0, flags = 0, textCount = 0;
2679 std::string signame;
2680
2681 iss >> name >> type >> xloc >> yloc >> pieces >> flags;
2682
2683 // Try to read optional text count and signal name (for COPPER type).
2684 // Standard format: pieces flags textcount signame
2685 // EasyEDA format: pieces flags signame (no text count)
2686 if( iss >> textCount )
2687 {
2688 iss >> signame;
2689 }
2690 else
2691 {
2692 iss.clear();
2693 iss >> signame;
2694 }
2695
2696 // Check for optional .REUSE. line after header
2697 std::string reuse_instance, reuse_signal;
2698
2699 if( readLine( aStream, line ) )
2700 {
2701 if( line.find( ".REUSE." ) != std::string::npos )
2702 {
2703 std::istringstream riss( line );
2704 std::string reuse_keyword;
2705 riss >> reuse_keyword >> reuse_instance >> reuse_signal;
2706 }
2707 else
2708 {
2709 pushBackLine( line );
2710 }
2711 }
2712
2713 if( type == "BOARD" )
2714 {
2715 for( int i=0; i<pieces; ++i )
2716 {
2717 if( !readLine( aStream, line ) ) break;
2718 if( line[0] == '*' ) { pushBackLine( line ); return; }
2719
2720 std::istringstream piss( line );
2721 std::string shape_type;
2722 int corners = 0;
2723 double width = 0.0;
2724 int flags = 0;
2725 int level = 0;
2726 piss >> shape_type >> corners >> width >> flags >> level;
2727
2728 if( shape_type == "CLOSED" || shape_type == "OPEN" || shape_type == "BRDCLS" )
2729 {
2730 POLYLINE polyline;
2731 polyline.layer = 0; // Board outline is layer-agnostic
2732 polyline.width = width;
2733 polyline.closed = ( shape_type == "CLOSED" || shape_type == "BRDCLS" );
2734
2735 for( int j = 0; j < corners; ++j )
2736 {
2737 if( !readLine( aStream, line ) )
2738 break;
2739
2740 if( line[0] == '*' )
2741 {
2742 pushBackLine( line );
2743 return;
2744 }
2745
2746 std::istringstream ciss( line );
2747 double dx = 0.0, dy = 0.0;
2748 ciss >> dx >> dy;
2749
2750 // Per PADS spec, arc format is: x1 y1 ab aa ax1 ay1 ax2 ay2
2751 // where x1,y1 = arc start point; center = bounding box midpoint
2752 int startAngleTenths = 0, deltaAngleTenths = 0;
2753 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
2754
2755 if( ciss >> startAngleTenths >> deltaAngleTenths
2756 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
2757 {
2758 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
2759 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
2760 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
2761 double startAngle = startAngleTenths / 10.0;
2762 double deltaAngle = deltaAngleTenths / 10.0;
2763
2764 double startAngleRad = startAngle * M_PI / 180.0;
2765 double startX = cx + radius * std::cos( startAngleRad );
2766 double startY = cy + radius * std::sin( startAngleRad );
2767
2768 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
2769 double endX = cx + radius * std::cos( endAngleRad );
2770 double endY = cy + radius * std::sin( endAngleRad );
2771
2772 polyline.points.emplace_back( xloc + startX, yloc + startY );
2773
2774 ARC arc{};
2775 arc.cx = xloc + cx;
2776 arc.cy = yloc + cy;
2777 arc.radius = radius;
2778 arc.start_angle = startAngle;
2779 arc.delta_angle = deltaAngle;
2780
2781 polyline.points.emplace_back( xloc + endX, yloc + endY, arc );
2782 }
2783 else
2784 {
2785 polyline.points.emplace_back( xloc + dx, yloc + dy );
2786 }
2787 }
2788
2789 if( !polyline.points.empty() )
2790 m_board_outlines.push_back( polyline );
2791 }
2792 else if( shape_type == "CIRCLE" || shape_type == "BRDCIR" )
2793 {
2794 // Circle: 2 coordinates define opposite ends of diameter
2795 double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
2796
2797 if( readLine( aStream, line ) )
2798 {
2799 std::istringstream c1( line );
2800 c1 >> x1 >> y1;
2801 }
2802
2803 if( corners >= 2 && readLine( aStream, line ) )
2804 {
2805 std::istringstream c2( line );
2806 c2 >> x2 >> y2;
2807 }
2808
2809 double cx = xloc + ( x1 + x2 ) / 2.0;
2810 double cy = yloc + ( y1 + y2 ) / 2.0;
2811 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 ) +
2812 ( y2 - y1 ) * ( y2 - y1 ) ) / 2.0;
2813
2814 POLYLINE polyline;
2815 polyline.layer = 0;
2816 polyline.width = width;
2817 polyline.closed = true;
2818
2819 ARC arc{};
2820 arc.cx = cx;
2821 arc.cy = cy;
2822 arc.radius = radius;
2823 arc.start_angle = 0.0;
2824 arc.delta_angle = 360.0;
2825
2826 polyline.points.emplace_back( cx + radius, cy, arc );
2827
2828 if( !polyline.points.empty() )
2829 m_board_outlines.push_back( polyline );
2830 }
2831 else
2832 {
2833 for( int j=0; j<corners; ++j )
2834 {
2835 if( !readLine( aStream, line ) ) break;
2836 if( line[0] == '*' ) { pushBackLine( line ); return; }
2837 }
2838 }
2839 }
2840 }
2841 else if( name.rfind( "DIM", 0 ) == 0 && type == "LINES" )
2842 {
2843 // Dimension annotation with BASPNT (base points), ARWLN/ARWHD (arrows),
2844 // and EXTLN (extension lines).
2845 //
2846 // BASPNT pairs define the measurement endpoints. There are typically two
2847 // BASPNT shapes: the first defines the start point (usually at origin),
2848 // and the second defines the end point as an offset from the dimension origin.
2849 //
2850 // ARWLN defines the crossbar position (Y for horizontal, X for vertical).
2851 //
2852 // For a proper linear dimension, we extract:
2853 // - Start point from first BASPNT (first coordinate of the pair)
2854 // - End point from second BASPNT (first coordinate of the pair)
2855 // - Crossbar position from ARWLN (used to compute height)
2856 DIMENSION dim;
2857 dim.name = name;
2858 dim.x = xloc;
2859 dim.y = yloc;
2860
2861 double baspnt1_x = 0, baspnt1_y = 0;
2862 double baspnt2_x = 0, baspnt2_y = 0;
2863 double arwln_x = 0, arwln_y = 0;
2864 int baspnt_count = 0;
2865 bool hasArwln = false;
2866
2867 for( int i = 0; i < pieces; ++i )
2868 {
2869 if( !readLine( aStream, line ) )
2870 break;
2871
2872 if( line[0] == '*' )
2873 {
2874 pushBackLine( line );
2875 break;
2876 }
2877
2878 std::istringstream piss( line );
2879 std::string shape_type;
2880 int corners = 0;
2881 double width = 0;
2882 int flags = 0;
2883 int level = 0;
2884 piss >> shape_type >> corners >> width >> flags >> level;
2885
2886 dim.layer = level;
2887
2888 for( int j = 0; j < corners; ++j )
2889 {
2890 if( !readLine( aStream, line ) )
2891 break;
2892
2893 if( line[0] == '*' )
2894 {
2895 pushBackLine( line );
2896 break;
2897 }
2898
2899 std::istringstream ciss( line );
2900 double dx = 0.0, dy = 0.0;
2901 ciss >> dx >> dy;
2902
2903 // BASPNT defines measurement endpoints. First BASPNT is start,
2904 // second BASPNT is end. Only capture the first point of each pair.
2905 if( shape_type == "BASPNT" && j == 0 )
2906 {
2907 if( baspnt_count == 0 )
2908 {
2909 baspnt1_x = xloc + dx;
2910 baspnt1_y = yloc + dy;
2911 }
2912 else if( baspnt_count == 1 )
2913 {
2914 baspnt2_x = xloc + dx;
2915 baspnt2_y = yloc + dy;
2916 }
2917
2918 baspnt_count++;
2919 }
2920
2921 // ARWLN1 first point: crossbar position
2922 if( shape_type == "ARWLN1" && j == 0 )
2923 {
2924 arwln_x = xloc + dx;
2925 arwln_y = yloc + dy;
2926 hasArwln = true;
2927 }
2928 }
2929 }
2930
2931 // Build measurement points from BASPNT positions.
2932 if( baspnt_count >= 2 )
2933 {
2934 // Determine if this is a horizontal or vertical dimension based on
2935 // which axis has the larger offset between the two BASPNT points.
2936 double dx = std::abs( baspnt2_x - baspnt1_x );
2937 double dy = std::abs( baspnt2_y - baspnt1_y );
2938 bool isHorizontal = dx > dy;
2939
2940 dim.is_horizontal = isHorizontal;
2941
2942 POINT pt1{}, pt2{};
2943 pt1.x = baspnt1_x;
2944 pt1.y = baspnt1_y;
2945 pt2.x = baspnt2_x;
2946 pt2.y = baspnt2_y;
2947
2948 // Store crossbar position for height calculation
2949 if( hasArwln )
2950 {
2951 if( isHorizontal )
2952 dim.crossbar_pos = arwln_y;
2953 else
2954 dim.crossbar_pos = arwln_x;
2955 }
2956
2957 dim.points.push_back( pt1 );
2958 dim.points.push_back( pt2 );
2959 }
2960
2961 // Parse text items for this dimension (same 3-line format as board text).
2962 // The first text is used as the dimension value label.
2963 for( int t = 0; t < textCount; ++t )
2964 {
2965 if( !readLine( aStream, line ) )
2966 break;
2967
2968 if( line[0] == '*' )
2969 {
2970 pushBackLine( line );
2971 break;
2972 }
2973
2974 std::istringstream tiss( line );
2975 double tx = 0.0, ty = 0.0;
2976 tiss >> tx >> ty;
2977
2978 if( tiss.fail() )
2979 {
2980 readLine( aStream, line );
2981 readLine( aStream, line );
2982 continue;
2983 }
2984
2985 double trot = 0.0;
2986 int tlayer = 0;
2987 double theight = 0.0, twidth = 0.0;
2988 tiss >> trot >> tlayer >> theight >> twidth;
2989
2990 // Font line
2991 if( !readLine( aStream, line ) )
2992 break;
2993
2994 // Content line
2995 if( !readLine( aStream, line ) )
2996 break;
2997
2998 if( t == 0 )
2999 {
3000 dim.text = line;
3001 dim.text_height = theight;
3002 dim.text_width = twidth;
3003 dim.rotation = trot;
3004 }
3005 }
3006
3007 textCount = 0;
3008
3009 if( !dim.points.empty() )
3010 m_dimensions.push_back( dim );
3011 }
3012 else if( type == "KEEPOUT" || type == "RESTRICTVIA" || type == "RESTRICTROUTE"
3013 || type == "RESTRICTAREA" || type == "PLACEMENT_KEEPOUT" )
3014 {
3015 // Parse keepout area definition
3016 KEEPOUT keepout;
3017
3018 // Set defaults based on type name (fallback if no restriction codes in piece)
3019 if( type == "KEEPOUT" || type == "RESTRICTAREA" )
3020 {
3021 keepout.type = KEEPOUT_TYPE::ALL;
3022 keepout.no_traces = true;
3023 keepout.no_vias = true;
3024 keepout.no_copper = true;
3025 }
3026 else if( type == "RESTRICTVIA" )
3027 {
3028 keepout.type = KEEPOUT_TYPE::VIA;
3029 keepout.no_traces = false;
3030 keepout.no_vias = true;
3031 keepout.no_copper = false;
3032 }
3033 else if( type == "RESTRICTROUTE" )
3034 {
3035 keepout.type = KEEPOUT_TYPE::ROUTE;
3036 keepout.no_traces = true;
3037 keepout.no_vias = false;
3038 keepout.no_copper = false;
3039 }
3040 else if( type == "PLACEMENT_KEEPOUT" )
3041 {
3042 keepout.type = KEEPOUT_TYPE::PLACEMENT;
3043 keepout.no_traces = false;
3044 keepout.no_vias = false;
3045 keepout.no_copper = false;
3046 keepout.no_components = true;
3047 }
3048
3049 for( int i = 0; i < pieces; ++i )
3050 {
3051 if( !readLine( aStream, line ) )
3052 break;
3053
3054 if( line[0] == '*' )
3055 {
3056 pushBackLine( line );
3057 break;
3058 }
3059
3060 // Piece format: PIECETYPE CORNERS WIDTH FLAGS LEVEL [RESTRICTIONS]
3061 // RESTRICTIONS is a string containing: P H R C V T A
3062 std::istringstream piss( line );
3063 std::string shape_type;
3064 int corners = 0;
3065 double width = 0;
3066 int flags = 0;
3067 int level = 0;
3068 std::string restrictions;
3069 piss >> shape_type >> corners >> width >> flags >> level >> restrictions;
3070
3071 if( level > 0 )
3072 keepout.layers.push_back( level );
3073
3074 // Parse restriction codes if present
3075 // Per PADS spec: P=Placement, H=Height, R=Trace/copper, C=Copper pour,
3076 // V=Via/jumper, T=Test point, A=Accordion
3077 // Only override defaults from type name if explicit restrictions are specified
3078 if( !restrictions.empty() )
3079 {
3080 // Check if this looks like a restriction code string (contains letters)
3081 bool has_restriction_codes = false;
3082
3083 for( char c : restrictions )
3084 {
3085 if( std::isalpha( c ) )
3086 {
3087 has_restriction_codes = true;
3088 break;
3089 }
3090 }
3091
3092 if( has_restriction_codes )
3093 {
3094 // Clear all defaults and set based on explicit restriction codes
3095 keepout.no_traces = false;
3096 keepout.no_vias = false;
3097 keepout.no_copper = false;
3098 keepout.no_components = false;
3099 keepout.height_restriction = false;
3100 keepout.no_test_points = false;
3101 keepout.no_accordion = false;
3102
3103 for( char c : restrictions )
3104 {
3105 switch( c )
3106 {
3107 case 'P':
3108 keepout.no_components = true;
3109 break;
3110
3111 case 'H':
3112 keepout.height_restriction = true;
3113 keepout.max_height = width;
3114 break;
3115
3116 case 'R':
3117 keepout.no_traces = true;
3118 break;
3119
3120 case 'C':
3121 keepout.no_copper = true;
3122 break;
3123
3124 case 'V':
3125 keepout.no_vias = true;
3126 break;
3127
3128 case 'T':
3129 keepout.no_test_points = true;
3130 break;
3131
3132 case 'A':
3133 keepout.no_accordion = true;
3134 break;
3135
3136 default:
3137 break;
3138 }
3139 }
3140 }
3141 }
3142
3143 // Handle KPTCIR (circle keepout) differently from KPTCLS (polygon keepout)
3144 if( shape_type == "KPTCIR" )
3145 {
3146 // Circle format: 2 coordinates define opposite ends of diameter
3147 double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
3148
3149 if( readLine( aStream, line ) )
3150 {
3151 std::istringstream c1( line );
3152 c1 >> x1 >> y1;
3153 }
3154
3155 if( corners >= 2 && readLine( aStream, line ) )
3156 {
3157 std::istringstream c2( line );
3158 c2 >> x2 >> y2;
3159 }
3160
3161 // Calculate center and radius from diameter endpoints
3162 double cx = xloc + ( x1 + x2 ) / 2.0;
3163 double cy = yloc + ( y1 + y2 ) / 2.0;
3164 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 ) +
3165 ( y2 - y1 ) * ( y2 - y1 ) ) / 2.0;
3166
3167 // Create full circle arc for keepout outline
3168 ARC arc{};
3169 arc.cx = cx;
3170 arc.cy = cy;
3171 arc.radius = radius;
3172 arc.start_angle = 0.0;
3173 arc.delta_angle = 360.0;
3174
3175 keepout.outline.emplace_back( cx + radius, cy, arc );
3176 }
3177 else
3178 {
3179 // KPTCLS or other polygon piece types
3180 for( int j = 0; j < corners; ++j )
3181 {
3182 if( !readLine( aStream, line ) )
3183 break;
3184
3185 if( line[0] == '*' )
3186 {
3187 pushBackLine( line );
3188 break;
3189 }
3190
3191 std::istringstream ciss( line );
3192 double dx = 0.0, dy = 0.0;
3193 ciss >> dx >> dy;
3194
3195 // Per PADS spec, arc format is: x1 y1 ab aa ax1 ay1 ax2 ay2
3196 // where x1,y1 = arc start point; center = bounding box midpoint
3197 int startAngleTenths = 0, deltaAngleTenths = 0;
3198 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
3199
3200 if( ciss >> startAngleTenths >> deltaAngleTenths
3201 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
3202 {
3203 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
3204 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
3205 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
3206 double startAngle = startAngleTenths / 10.0;
3207 double deltaAngle = deltaAngleTenths / 10.0;
3208
3209 double startAngleRad = startAngle * M_PI / 180.0;
3210 double startX = cx + radius * std::cos( startAngleRad );
3211 double startY = cy + radius * std::sin( startAngleRad );
3212
3213 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
3214 double endX = cx + radius * std::cos( endAngleRad );
3215 double endY = cy + radius * std::sin( endAngleRad );
3216
3217 keepout.outline.emplace_back( xloc + startX, yloc + startY );
3218
3219 ARC arc{};
3220 arc.cx = xloc + cx;
3221 arc.cy = yloc + cy;
3222 arc.radius = radius;
3223 arc.start_angle = startAngle;
3224 arc.delta_angle = deltaAngle;
3225
3226 keepout.outline.emplace_back( xloc + endX, yloc + endY, arc );
3227 }
3228 else
3229 {
3230 keepout.outline.emplace_back( xloc + dx, yloc + dy );
3231 }
3232 }
3233 }
3234 }
3235
3236 if( !keepout.outline.empty() )
3237 m_keepouts.push_back( keepout );
3238 }
3239 else if( type == "COPPER" || type == "COPCUT" )
3240 {
3241 // Parse copper shape definition
3242 // Header already parsed: name type xloc yloc pieces flags [text [signame]]
3243 // signame was parsed earlier if present
3244
3245 for( int i = 0; i < pieces; ++i )
3246 {
3247 if( !readLine( aStream, line ) )
3248 break;
3249
3250 if( line[0] == '*' )
3251 {
3252 pushBackLine( line );
3253 return;
3254 }
3255
3256 // Piece format: PIECETYPE CORNERS WIDTH FLAGS LEVEL
3257 // PIECETYPE: COPOPN (polyline), COPCLS (filled polygon), COPCIR (filled circle),
3258 // COPCUT (polygon void), COPCCO (circle void), CIRCUR (circle void for COPCUT)
3259 std::istringstream piss( line );
3260 std::string shape_type;
3261 int corners = 0;
3262 double width = 0;
3263 int flags = 0;
3264 int level = 0;
3265 piss >> shape_type >> corners >> width >> flags >> level;
3266
3267 COPPER_SHAPE copper;
3268 copper.name = name;
3269 copper.layer = level;
3270 copper.width = width;
3271 copper.net_name = signame;
3272
3273 copper.filled = ( shape_type == "COPCLS" || shape_type == "COPCIR" );
3274 copper.is_cutout = ( shape_type == "COPCUT" || shape_type == "COPCCO" ||
3275 shape_type == "CIRCUR" || type == "COPCUT" );
3276
3277 // Handle circle shapes specially
3278 if( shape_type == "COPCIR" || shape_type == "COPCCO" || shape_type == "CIRCUR" )
3279 {
3280 // Circle: 2 coordinates define opposite ends of diameter
3281 double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
3282
3283 if( readLine( aStream, line ) )
3284 {
3285 std::istringstream c1( line );
3286 c1 >> x1 >> y1;
3287 }
3288
3289 if( corners >= 2 && readLine( aStream, line ) )
3290 {
3291 std::istringstream c2( line );
3292 c2 >> x2 >> y2;
3293 }
3294
3295 double cx = xloc + ( x1 + x2 ) / 2.0;
3296 double cy = yloc + ( y1 + y2 ) / 2.0;
3297 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 ) +
3298 ( y2 - y1 ) * ( y2 - y1 ) ) / 2.0;
3299
3300 ARC arc{};
3301 arc.cx = cx;
3302 arc.cy = cy;
3303 arc.radius = radius;
3304 arc.start_angle = 0.0;
3305 arc.delta_angle = 360.0;
3306
3307 copper.outline.emplace_back( cx + radius, cy, arc );
3308 }
3309 else
3310 {
3311 // COPOPN, COPCLS, COPCUT - polygon shapes
3312 for( int j = 0; j < corners; ++j )
3313 {
3314 if( !readLine( aStream, line ) )
3315 break;
3316
3317 if( line[0] == '*' )
3318 {
3319 pushBackLine( line );
3320 break;
3321 }
3322
3323 std::istringstream ciss( line );
3324 double dx = 0.0, dy = 0.0;
3325 ciss >> dx >> dy;
3326
3327 // Per PADS spec, arc format is: x1 y1 ab aa ax1 ay1 ax2 ay2
3328 // where x1,y1 = arc start point; center = bounding box midpoint
3329 int startAngleTenths = 0, deltaAngleTenths = 0;
3330 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
3331
3332 if( ciss >> startAngleTenths >> deltaAngleTenths
3333 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
3334 {
3335 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
3336 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
3337 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
3338 double startAngle = startAngleTenths / 10.0;
3339 double deltaAngle = deltaAngleTenths / 10.0;
3340
3341 double startAngleRad = startAngle * M_PI / 180.0;
3342 double startX = cx + radius * std::cos( startAngleRad );
3343 double startY = cy + radius * std::sin( startAngleRad );
3344
3345 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
3346 double endX = cx + radius * std::cos( endAngleRad );
3347 double endY = cy + radius * std::sin( endAngleRad );
3348
3349 copper.outline.emplace_back( xloc + startX, yloc + startY );
3350
3351 ARC arc{};
3352 arc.cx = xloc + cx;
3353 arc.cy = yloc + cy;
3354 arc.radius = radius;
3355 arc.start_angle = startAngle;
3356 arc.delta_angle = deltaAngle;
3357
3358 copper.outline.emplace_back( xloc + endX, yloc + endY, arc );
3359 }
3360 else
3361 {
3362 copper.outline.emplace_back( xloc + dx, yloc + dy );
3363 }
3364 }
3365 }
3366
3367 if( !copper.outline.empty() )
3368 m_copper_shapes.push_back( copper );
3369 }
3370 }
3371 else if( type == "LINES" )
3372 {
3373 // Generic 2D graphic lines (non-dimension LINES items)
3374 for( int i = 0; i < pieces; ++i )
3375 {
3376 if( !readLine( aStream, line ) )
3377 break;
3378
3379 if( line[0] == '*' )
3380 {
3381 pushBackLine( line );
3382 return;
3383 }
3384
3385 // Piece format: PIECETYPE CORNERS WIDTH FLAGS LEVEL
3386 std::istringstream piss( line );
3387 std::string shape_type;
3388 int corners = 0;
3389 double width = 0;
3390 int flags = 0;
3391 int level = 0;
3392 piss >> shape_type >> corners >> width >> flags >> level;
3393
3394 GRAPHIC_LINE graphic;
3395 graphic.name = name;
3396 graphic.layer = level;
3397 graphic.width = width;
3398 graphic.reuse_instance = reuse_instance;
3399
3400 // Determine if closed based on shape type
3401 graphic.closed = ( shape_type == "CLOSED" || shape_type == "CIRCLE" );
3402
3403 if( shape_type == "CIRCLE" )
3404 {
3405 // Circle: 2 coordinates define opposite ends of diameter
3406 double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
3407
3408 if( readLine( aStream, line ) )
3409 {
3410 std::istringstream c1( line );
3411 c1 >> x1 >> y1;
3412 }
3413
3414 if( corners >= 2 && readLine( aStream, line ) )
3415 {
3416 std::istringstream c2( line );
3417 c2 >> x2 >> y2;
3418 }
3419
3420 double cx = xloc + ( x1 + x2 ) / 2.0;
3421 double cy = yloc + ( y1 + y2 ) / 2.0;
3422 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 ) +
3423 ( y2 - y1 ) * ( y2 - y1 ) ) / 2.0;
3424
3425 ARC arc{};
3426 arc.cx = cx;
3427 arc.cy = cy;
3428 arc.radius = radius;
3429 arc.start_angle = 0.0;
3430 arc.delta_angle = 360.0;
3431
3432 graphic.points.emplace_back( cx + radius, cy, arc );
3433 }
3434 else
3435 {
3436 // OPEN or CLOSED polyline
3437 for( int j = 0; j < corners; ++j )
3438 {
3439 if( !readLine( aStream, line ) )
3440 break;
3441
3442 if( line[0] == '*' )
3443 {
3444 pushBackLine( line );
3445 break;
3446 }
3447
3448 std::istringstream ciss( line );
3449 double dx = 0.0, dy = 0.0;
3450 ciss >> dx >> dy;
3451
3452 // Check for arc parameters
3453 int startAngleTenths = 0, deltaAngleTenths = 0;
3454 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
3455
3456 if( ciss >> startAngleTenths >> deltaAngleTenths
3457 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
3458 {
3459 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
3460 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
3461 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
3462 double startAngle = startAngleTenths / 10.0;
3463 double deltaAngle = deltaAngleTenths / 10.0;
3464
3465 double startAngleRad = startAngle * M_PI / 180.0;
3466 double startX = cx + radius * std::cos( startAngleRad );
3467 double startY = cy + radius * std::sin( startAngleRad );
3468
3469 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
3470 double endX = cx + radius * std::cos( endAngleRad );
3471 double endY = cy + radius * std::sin( endAngleRad );
3472
3473 graphic.points.emplace_back( xloc + startX, yloc + startY );
3474
3475 ARC arc{};
3476 arc.cx = xloc + cx;
3477 arc.cy = yloc + cy;
3478 arc.radius = radius;
3479 arc.start_angle = startAngle;
3480 arc.delta_angle = deltaAngle;
3481
3482 graphic.points.emplace_back( xloc + endX, yloc + endY, arc );
3483 }
3484 else
3485 {
3486 graphic.points.emplace_back( xloc + dx, yloc + dy );
3487 }
3488 }
3489 }
3490
3491 if( !graphic.points.empty() )
3492 m_graphic_lines.push_back( graphic );
3493 }
3494 }
3495 else
3496 {
3497 // Skip unknown types
3498 for( int i = 0; i < pieces; ++i )
3499 {
3500 if( !readLine( aStream, line ) )
3501 break;
3502
3503 if( line[0] == '*' )
3504 {
3505 pushBackLine( line );
3506 return;
3507 }
3508
3509 std::istringstream piss( line );
3510 std::string shape_type;
3511 int corners = 0;
3512 piss >> shape_type >> corners;
3513
3514 for( int j = 0; j < corners; ++j )
3515 {
3516 if( !readLine( aStream, line ) )
3517 break;
3518
3519 if( line[0] == '*' )
3520 {
3521 pushBackLine( line );
3522 return;
3523 }
3524 }
3525 }
3526 }
3527
3528 // Parse text items that follow the pieces (3 lines each: properties, font, content).
3529 // Text coordinates are relative to the drawing item origin.
3530 for( int t = 0; t < textCount; ++t )
3531 {
3532 if( !readLine( aStream, line ) )
3533 break;
3534
3535 if( line[0] == '*' )
3536 {
3537 pushBackLine( line );
3538 return;
3539 }
3540
3541 std::istringstream tiss( line );
3542 TEXT text;
3543
3544 tiss >> text.location.x >> text.location.y >> text.rotation >> text.layer
3545 >> text.height >> text.width;
3546
3547 if( tiss.fail() )
3548 {
3549 // Consume remaining 2 lines and continue
3550 readLine( aStream, line );
3551 readLine( aStream, line );
3552 continue;
3553 }
3554
3555 text.location.x += xloc;
3556 text.location.y += yloc;
3557
3558 std::string mirrored;
3559 tiss >> mirrored;
3560 text.mirrored = ( mirrored == "M" );
3561 tiss >> text.hjust >> text.vjust;
3562
3563 // Font line
3564 if( !readLine( aStream, line ) )
3565 break;
3566
3567 if( line[0] == '*' )
3568 {
3569 pushBackLine( line );
3570 return;
3571 }
3572
3573 size_t bracket_start = line.find( '<' );
3574 size_t bracket_end = line.find( '>' );
3575
3576 if( bracket_start != std::string::npos && bracket_end != std::string::npos )
3577 text.font_face = line.substr( bracket_start + 1, bracket_end - bracket_start - 1 );
3578
3579 std::istringstream fiss( line );
3580 std::string font_style_part;
3581 fiss >> font_style_part;
3582
3583 size_t colon_pos = font_style_part.find( ':' );
3584
3585 if( colon_pos != std::string::npos )
3586 text.font_style = font_style_part.substr( 0, colon_pos );
3587 else
3588 text.font_style = font_style_part;
3589
3590 // Content line
3591 if( !readLine( aStream, line ) )
3592 break;
3593
3594 if( line[0] == '*' )
3595 {
3596 pushBackLine( line );
3597 return;
3598 }
3599
3600 text.content = line;
3601 m_texts.push_back( text );
3602 }
3603 }
3604}
3605
3606void PARSER::parseSectionPARTTYPE( std::ifstream& aStream )
3607{
3608 std::string line;
3609 PART_TYPE* currentPartType = nullptr;
3610 GATE_DEF* currentGate = nullptr;
3611
3612 // Helper to parse pin electrical type character
3613 auto parsePinElecType = []( char c ) -> PIN_ELEC_TYPE {
3614 switch( c )
3615 {
3616 case 'S': return PIN_ELEC_TYPE::SOURCE;
3617 case 'B': return PIN_ELEC_TYPE::BIDIRECTIONAL;
3618 case 'C': return PIN_ELEC_TYPE::OPEN_COLLECTOR;
3619 case 'T': return PIN_ELEC_TYPE::TRISTATE;
3620 case 'L': return PIN_ELEC_TYPE::LOAD;
3621 case 'Z': return PIN_ELEC_TYPE::TERMINATOR;
3622 case 'P': return PIN_ELEC_TYPE::POWER;
3623 case 'G': return PIN_ELEC_TYPE::GROUND;
3624 default: return PIN_ELEC_TYPE::UNDEFINED;
3625 }
3626 };
3627
3628 while( readLine( aStream, line ) )
3629 {
3630 if( line[0] == '*' )
3631 {
3632 pushBackLine( line );
3633 break;
3634 }
3635
3636 if( line.empty() )
3637 continue;
3638
3639 // Gate line: G gateswap pins
3640 if( line.rfind( "G ", 0 ) == 0 && currentPartType )
3641 {
3642 std::istringstream gss( line );
3643 std::string g_keyword;
3644 int gateSwap = 0, pinCount = 0;
3645 gss >> g_keyword >> gateSwap >> pinCount;
3646
3647 GATE_DEF gate;
3648 gate.gate_swap_type = gateSwap;
3649 currentPartType->gates.push_back( gate );
3650 currentGate = &currentPartType->gates.back();
3651 continue;
3652 }
3653
3654 // SIGPIN pinno width signm
3655 if( line.rfind( "SIGPIN", 0 ) == 0 && currentPartType )
3656 {
3657 std::istringstream sss( line );
3658 std::string keyword;
3659 SIGPIN sigpin;
3660
3661 sss >> keyword >> sigpin.pin_number >> sigpin.width >> sigpin.signal_name;
3662
3663 if( !sigpin.pin_number.empty() )
3664 currentPartType->signal_pins.push_back( sigpin );
3665
3666 continue;
3667 }
3668
3669 // Check if this line contains pin definitions (format: pinnumber.swptyp.pintyp[.funcname])
3670 // These follow a gate definition. Pin definition tokens have at least 3 dot-separated parts.
3671 // Part type header lines may also contain dots in the name (e.g., "CAPSMT0.1UF0402X7R50V")
3672 // but their first token won't have 3+ parts, so we check for that.
3673 if( line.find( '.' ) != std::string::npos && currentPartType )
3674 {
3675 // First check if this could be a part type header line with a dot in the name.
3676 // Part type headers have format: NAME DECAL CLASS ATTRS ... where NAME may contain dots
3677 // but the first dot-separated segment will have <3 parts.
3678 std::stringstream check_ss( line );
3679 std::string first_token;
3680 check_ss >> first_token;
3681
3682 int dot_count = 0;
3683
3684 for( char c : first_token )
3685 {
3686 if( c == '.' )
3687 dot_count++;
3688 }
3689
3690 // If first token has <2 dots, this could be a part type header, not a pin definition
3691 if( dot_count < 2 )
3692 {
3693 // Fall through to part type header parsing below
3694 }
3695 else
3696 {
3697 std::stringstream ss( line );
3698 std::string token;
3699
3700 while( ss >> token )
3701 {
3702 // Parse pin format: PINNAME.SWAPTYPE.PINTYPE[.FUNCNAME] or PINNAME.PADINDEX.TYPE.NET
3703 std::vector<std::string> parts;
3704 size_t start = 0;
3705 size_t pos = 0;
3706
3707 while( ( pos = token.find( '.', start ) ) != std::string::npos )
3708 {
3709 parts.push_back( token.substr( start, pos - start ) );
3710 start = pos + 1;
3711 }
3712
3713 parts.push_back( token.substr( start ) );
3714
3715 if( parts.size() >= 3 )
3716 {
3717 // Check if this is a gate pin definition or pad stack mapping
3718 // Gate pin: pinnumber.swaptype.pintype[.funcname]
3719 // Pad map: pinname.padindex.type.netname
3720
3721 bool isNumericSecond = !parts[1].empty() &&
3722 std::all_of( parts[1].begin(), parts[1].end(), ::isdigit );
3723
3724 if( currentGate && parts[2].size() == 1 && !isNumericSecond )
3725 {
3726 // This looks like a gate pin definition
3727 GATE_PIN gpin;
3728 gpin.pin_number = parts[0];
3729 gpin.swap_type = PADS_COMMON::ParseInt( parts[1], 0, "gate pin swap" );
3730
3731 if( !parts[2].empty() )
3732 gpin.elec_type = parsePinElecType( parts[2][0] );
3733
3734 if( parts.size() >= 4 )
3735 gpin.func_name = parts[3];
3736
3737 currentGate->pins.push_back( gpin );
3738 }
3739 else if( isNumericSecond )
3740 {
3741 int padIdx = PADS_COMMON::ParseInt( parts[1], -1, "pad index" );
3742
3743 if( padIdx >= 0 )
3744 currentPartType->pin_pad_map[parts[0]] = padIdx;
3745 }
3746 }
3747 }
3748
3749 continue;
3750 }
3751 }
3752
3753 // Attribute block enclosed in braces
3754 if( line[0] == '{' && currentPartType )
3755 {
3756 while( readLine( aStream, line ) )
3757 {
3758 if( line.empty() || line[0] == '}' )
3759 break;
3760
3761 if( line[0] == '*' )
3762 {
3763 pushBackLine( line );
3764 return;
3765 }
3766
3767 std::string attrName, attrValue;
3768
3769 if( line[0] == '"' )
3770 {
3771 size_t endQuote = line.find( '"', 1 );
3772
3773 if( endQuote != std::string::npos )
3774 {
3775 attrName = line.substr( 1, endQuote - 1 );
3776 attrValue = line.substr( endQuote + 1 );
3777 }
3778 }
3779 else
3780 {
3781 std::istringstream attrSS( line );
3782 attrSS >> attrName;
3783 std::getline( attrSS >> std::ws, attrValue );
3784 }
3785
3786 if( !attrValue.empty() && attrValue[0] == ' ' )
3787 attrValue = attrValue.substr( 1 );
3788
3789 if( !attrName.empty() && !attrValue.empty() )
3790 currentPartType->attributes[attrName] = attrValue;
3791 }
3792
3793 continue;
3794 }
3795
3796 if( line[0] == '{' || line[0] == '}' )
3797 continue;
3798
3799 // Part type definition line: NAME DECAL CLASS ATTRS GATES SIGS PINSEQ STATE
3800 std::stringstream ss( line );
3801 std::string name, decal;
3802 ss >> name >> decal;
3803
3804 if( !name.empty() && name[0] != 'G' )
3805 {
3806 PART_TYPE pt;
3807 pt.name = name;
3808 pt.decal_name = decal;
3809 m_part_types[name] = pt;
3810 currentPartType = &m_part_types[name];
3811 currentGate = nullptr;
3812 }
3813 }
3814}
3815
3816
3817void PARSER::parseSectionREUSE( std::ifstream& aStream )
3818{
3819 std::string line;
3820 REUSE_BLOCK* currentBlock = nullptr;
3821
3822 while( readLine( aStream, line ) )
3823 {
3824 if( line[0] == '*' )
3825 {
3826 pushBackLine( line );
3827 break;
3828 }
3829
3830 std::stringstream ss( line );
3831 std::string keyword;
3832 ss >> keyword;
3833
3834 if( keyword == "TYPE" )
3835 {
3836 std::string typename_val;
3837 std::getline( ss, typename_val );
3838
3839 if( !typename_val.empty() && typename_val[0] == ' ' )
3840 typename_val = typename_val.substr( 1 );
3841
3842 REUSE_BLOCK block;
3843 block.name = typename_val;
3844 m_reuse_blocks[typename_val] = block;
3845 currentBlock = &m_reuse_blocks[typename_val];
3846 }
3847 else if( keyword == "TIMESTAMP" && currentBlock )
3848 {
3849 long timestamp = 0;
3850 ss >> timestamp;
3851 currentBlock->timestamp = timestamp;
3852 }
3853 else if( keyword == "PART_NAMING" && currentBlock )
3854 {
3855 std::string naming;
3856 std::getline( ss, naming );
3857
3858 if( !naming.empty() && naming[0] == ' ' )
3859 naming = naming.substr( 1 );
3860
3861 currentBlock->part_naming = naming;
3862 }
3863 else if( keyword == "PART" && currentBlock )
3864 {
3865 std::string partname;
3866 std::getline( ss, partname );
3867
3868 if( !partname.empty() && partname[0] == ' ' )
3869 partname = partname.substr( 1 );
3870
3871 currentBlock->part_names.push_back( partname );
3872 }
3873 else if( keyword == "NET_NAMING" && currentBlock )
3874 {
3875 std::string naming;
3876 std::getline( ss, naming );
3877
3878 if( !naming.empty() && naming[0] == ' ' )
3879 naming = naming.substr( 1 );
3880
3881 currentBlock->net_naming = naming;
3882 }
3883 else if( keyword == "NET" && currentBlock )
3884 {
3885 int merge_flag = 0;
3886 std::string netname;
3887
3888 ss >> merge_flag;
3889 std::getline( ss, netname );
3890
3891 if( !netname.empty() && netname[0] == ' ' )
3892 netname = netname.substr( 1 );
3893
3894 REUSE_NET net;
3895 net.merge = ( merge_flag == 1 );
3896 net.name = netname;
3897 currentBlock->nets.push_back( net );
3898 }
3899 else if( keyword == "REUSE" && currentBlock )
3900 {
3901 REUSE_INSTANCE instance;
3902 ss >> instance.instance_name;
3903
3904 std::string next_token;
3905 ss >> next_token;
3906
3907 if( next_token == "PREFIX" || next_token == "SUFFIX" )
3908 {
3909 std::string param;
3910 ss >> param;
3911 instance.part_naming = next_token + " " + param;
3912 ss >> next_token;
3913 }
3914 else if( next_token == "START" || next_token == "INCREMENT" )
3915 {
3916 std::string num;
3917 ss >> num;
3918 instance.part_naming = next_token + " " + num;
3919 ss >> next_token;
3920 }
3921 else if( next_token == "NEXT" )
3922 {
3923 instance.part_naming = next_token;
3924 ss >> next_token;
3925 }
3926
3927 if( next_token == "PREFIX" || next_token == "SUFFIX" )
3928 {
3929 std::string param;
3930 ss >> param;
3931 instance.net_naming = next_token + " " + param;
3932 }
3933 else if( next_token == "START" || next_token == "INCREMENT" )
3934 {
3935 std::string num;
3936 ss >> num;
3937 instance.net_naming = next_token + " " + num;
3938 }
3939 else if( next_token == "NEXT" )
3940 {
3941 instance.net_naming = next_token;
3942 }
3943
3944 std::string glued_str;
3945 ss >> instance.location.x >> instance.location.y >> instance.rotation >> glued_str;
3946
3947 instance.glued = ( glued_str == "Y" || glued_str == "YES" || glued_str == "1" );
3948 currentBlock->instances.push_back( instance );
3949 }
3950 }
3951}
3952
3953
3954void PARSER::parseSectionCLUSTER( std::ifstream& aStream )
3955{
3956 std::string line;
3957 CLUSTER* currentCluster = nullptr;
3958
3959 while( readLine( aStream, line ) )
3960 {
3961 if( line[0] == '*' )
3962 {
3963 pushBackLine( line );
3964 break;
3965 }
3966
3967 std::stringstream ss( line );
3968 std::string firstToken;
3969 ss >> firstToken;
3970
3971 // Check if this is a new cluster definition or a member line
3972 // Cluster definition format varies but typically starts with name/id
3973 if( firstToken.empty() )
3974 continue;
3975
3976 // Try parsing as cluster ID
3977 bool isNumeric = !firstToken.empty() &&
3978 std::all_of( firstToken.begin(), firstToken.end(), ::isdigit );
3979
3980 if( isNumeric )
3981 {
3982 // This could be a cluster ID starting a new cluster
3983 CLUSTER cluster;
3984 cluster.id = PADS_COMMON::ParseInt( firstToken, 0, "CLUSTER" );
3985
3986 // Read optional cluster name
3987 std::string name;
3988
3989 if( ss >> name )
3990 cluster.name = name;
3991 else
3992 cluster.name = "Cluster_" + firstToken;
3993
3994 m_clusters.push_back( cluster );
3995 currentCluster = &m_clusters.back();
3996 }
3997 else if( currentCluster )
3998 {
3999 // Could be a net name or segment reference belonging to current cluster
4000 // PADS format varies - add to net_names or segment_refs based on content
4001 if( firstToken.find( '.' ) != std::string::npos )
4002 {
4003 // Looks like a segment reference (e.g., "NET.1")
4004 currentCluster->segment_refs.push_back( firstToken );
4005 }
4006 else
4007 {
4008 // Treat as net name
4009 currentCluster->net_names.push_back( firstToken );
4010 }
4011
4012 // Continue reading additional items on the same line
4013 std::string item;
4014
4015 while( ss >> item )
4016 {
4017 if( item.find( '.' ) != std::string::npos )
4018 currentCluster->segment_refs.push_back( item );
4019 else
4020 currentCluster->net_names.push_back( item );
4021 }
4022 }
4023 }
4024}
4025
4026
4027void PARSER::parseSectionJUMPER( std::ifstream& aStream )
4028{
4029 std::string line;
4030
4031 while( readLine( aStream, line ) )
4032 {
4033 if( line[0] == '*' )
4034 {
4035 pushBackLine( line );
4036 break;
4037 }
4038
4039 // Jumper header format: name flags minlen maxlen lenincr lcount padstack [end_padstack]
4040 // flags: V=via enabled, N=no via, W=wirebond, D=display silk, G=glued
4041 std::stringstream ss( line );
4042 std::string name, flags;
4043 double minlen = 0.0, maxlen = 0.0, lenincr = 0.0;
4044 int lcount = 0;
4045 std::string padstack, end_padstack;
4046
4047 if( !( ss >> name >> flags >> minlen >> maxlen >> lenincr >> lcount >> padstack ) )
4048 continue;
4049
4050 ss >> end_padstack;
4051
4052 JUMPER_DEF jumper;
4053 jumper.name = name;
4054 jumper.min_length = minlen;
4055 jumper.max_length = maxlen;
4056 jumper.length_increment = lenincr;
4057 jumper.padstack = padstack;
4058 jumper.end_padstack = end_padstack;
4059
4060 // Parse flags
4061 for( char c : flags )
4062 {
4063 switch( c )
4064 {
4065 case 'V': jumper.via_enabled = true; break;
4066 case 'N': jumper.via_enabled = false; break;
4067 case 'W': jumper.wirebond = true; break;
4068 case 'D': jumper.display_silk = true; break;
4069 case 'G': jumper.glued = true; break;
4070 default: break;
4071 }
4072 }
4073
4074 // Parse label entries (each label is 2 lines)
4075 for( int i = 0; i < lcount; ++i )
4076 {
4077 ATTRIBUTE attr;
4078
4079 // Line 1: VISIBLE X Y ORI LEVEL HEIGHT WIDTH MIRRORED HJUST VJUST [RIGHTREADING]
4080 if( !readLine( aStream, line ) )
4081 break;
4082
4083 std::stringstream ss_attr( line );
4084 std::string visible_str, mirrored_str, right_reading_str;
4085
4086 if( ss_attr >> visible_str >> attr.x >> attr.y >> attr.orientation >> attr.level
4087 >> attr.height >> attr.width >> mirrored_str >> attr.hjust >> attr.vjust )
4088 {
4089 attr.visible = ( visible_str == "VALUE" || visible_str == "FULL_NAME" ||
4090 visible_str == "NAME" || visible_str == "FULL_BOTH" ||
4091 visible_str == "BOTH" );
4092 attr.mirrored = ( mirrored_str == "M" || mirrored_str == "1" );
4093 ss_attr >> right_reading_str;
4094 attr.right_reading = ( right_reading_str == "Y" || right_reading_str == "ORTHO" );
4095 }
4096
4097 // Line 2: Font info
4098 if( !readLine( aStream, line ) )
4099 break;
4100
4101 attr.font_info = line;
4102
4103 jumper.labels.push_back( attr );
4104 }
4105
4106 m_jumper_defs.push_back( jumper );
4107 }
4108}
4109
4110
4111void PARSER::parseSectionTESTPOINT( std::ifstream& aStream )
4112{
4113 std::string line;
4114
4115 while( readLine( aStream, line ) )
4116 {
4117 if( line[0] == '*' )
4118 {
4119 pushBackLine( line );
4120 break;
4121 }
4122
4123 std::stringstream ss( line );
4124 std::string type;
4125 ss >> type;
4126
4127 if( type.empty() )
4128 continue;
4129
4130 // Format: TYPE X Y SIDE NETNAME SYMBOLNAME
4131 // TYPE is VIA or PIN
4132 TEST_POINT tp;
4133 tp.type = type;
4134
4135 ss >> tp.x >> tp.y >> tp.side >> tp.net_name >> tp.symbol_name;
4136
4137 if( !tp.net_name.empty() )
4138 {
4139 m_test_points.push_back( tp );
4140 }
4141 }
4142}
4143
4144
4145void PARSER::parseSectionNETCLASS( std::ifstream& aStream )
4146{
4147 std::string line;
4148 NET_CLASS_DEF currentClass;
4149 bool inClass = false;
4150
4151 while( readLine( aStream, line ) )
4152 {
4153 if( line[0] == '*' )
4154 {
4155 // Save the last class if we were building one
4156 if( inClass && !currentClass.name.empty() )
4157 m_net_classes.push_back( currentClass );
4158
4159 pushBackLine( line );
4160 break;
4161 }
4162
4163 std::stringstream ss( line );
4164 std::string token;
4165 ss >> token;
4166
4167 if( token.empty() )
4168 continue;
4169
4170 // Check for class name definition (typically first token without a keyword)
4171 if( token == "CLASS" || token == "NETCLASS" )
4172 {
4173 // Save previous class if any
4174 if( inClass && !currentClass.name.empty() )
4175 m_net_classes.push_back( currentClass );
4176
4177 // Start new class
4178 currentClass = NET_CLASS_DEF();
4179 ss >> currentClass.name;
4180 inClass = true;
4181 }
4182 else if( token == "CLEARANCE" && inClass )
4183 {
4184 ss >> currentClass.clearance;
4185 }
4186 else if( token == "TRACKWIDTH" && inClass )
4187 {
4188 ss >> currentClass.track_width;
4189 }
4190 else if( token == "VIASIZE" && inClass )
4191 {
4192 ss >> currentClass.via_size;
4193 }
4194 else if( token == "VIADRILL" && inClass )
4195 {
4196 ss >> currentClass.via_drill;
4197 }
4198 else if( token == "DIFFPAIRGAP" && inClass )
4199 {
4200 ss >> currentClass.diff_pair_gap;
4201 }
4202 else if( token == "DIFFPAIRWIDTH" && inClass )
4203 {
4204 ss >> currentClass.diff_pair_width;
4205 }
4206 else if( token == "NET" && inClass )
4207 {
4208 // Net assignment: NET netname
4209 std::string netName;
4210 ss >> netName;
4211
4212 if( !netName.empty() )
4213 currentClass.net_names.push_back( netName );
4214 }
4215 else if( !token.empty() && token[0] != '#' )
4216 {
4217 // Check if this looks like a class name (no keyword prefix) in some formats
4218 if( !inClass || ( inClass && currentClass.name.empty() ) )
4219 {
4220 // Save previous if any
4221 if( inClass && !currentClass.name.empty() )
4222 m_net_classes.push_back( currentClass );
4223
4224 currentClass = NET_CLASS_DEF();
4225 currentClass.name = token;
4226 inClass = true;
4227 }
4228 }
4229 }
4230
4231 // Save final class
4232 if( inClass && !currentClass.name.empty() )
4233 m_net_classes.push_back( currentClass );
4234}
4235
4236
4237void PARSER::parseSectionDIFFPAIR( std::ifstream& aStream )
4238{
4239 std::string line;
4240
4241 while( readLine( aStream, line ) )
4242 {
4243 if( line[0] == '*' )
4244 {
4245 pushBackLine( line );
4246 break;
4247 }
4248
4249 std::stringstream ss( line );
4250 std::string token;
4251 ss >> token;
4252
4253 if( token.empty() )
4254 continue;
4255
4256 // Differential pair format can vary. Common patterns:
4257 // DIFFPAIR name positive_net negative_net gap width
4258 // or keyword-based like:
4259 // PAIR name
4260 // POS positive_net
4261 // NEG negative_net
4262 // GAP value
4263 // WIDTH value
4264
4265 if( token == "DIFFPAIR" || token == "PAIR" )
4266 {
4267 DIFF_PAIR_DEF dp;
4268 ss >> dp.name;
4269
4270 // Try to read the nets inline
4271 std::string posNet, negNet;
4272 ss >> posNet >> negNet;
4273
4274 if( !posNet.empty() )
4275 dp.positive_net = posNet;
4276
4277 if( !negNet.empty() )
4278 dp.negative_net = negNet;
4279
4280 // Try to read gap and width inline
4281 double gap = 0.0, width = 0.0;
4282
4283 if( ss >> gap )
4284 dp.gap = gap;
4285
4286 if( ss >> width )
4287 dp.width = width;
4288
4289 if( !dp.name.empty() )
4290 m_diff_pairs.push_back( dp );
4291 }
4292 else if( token == "POS" && !m_diff_pairs.empty() )
4293 {
4294 ss >> m_diff_pairs.back().positive_net;
4295 }
4296 else if( token == "NEG" && !m_diff_pairs.empty() )
4297 {
4298 ss >> m_diff_pairs.back().negative_net;
4299 }
4300 else if( token == "GAP" && !m_diff_pairs.empty() )
4301 {
4302 ss >> m_diff_pairs.back().gap;
4303 }
4304 else if( ( token == "WIDTH" || token == "TRACKWIDTH" ) && !m_diff_pairs.empty() )
4305 {
4306 ss >> m_diff_pairs.back().width;
4307 }
4308 }
4309}
4310
4311
4312void PARSER::parseSectionLAYERDEFS( std::ifstream& aStream )
4313{
4314 std::string line;
4315 int braceDepth = 0;
4316 int currentLayerNum = -1;
4317 LAYER_INFO currentLayer;
4318 bool inLayerBlock = false;
4319
4320 // Helper to parse LAYER_TYPE string to enum
4321 auto parseLayerType = []( const std::string& typeStr ) -> PADS_LAYER_FUNCTION {
4322 if( typeStr == "ROUTING" )
4324 else if( typeStr == "PLANE" )
4326 else if( typeStr == "MIXED" )
4328 else if( typeStr == "UNASSIGNED" )
4330 else if( typeStr == "SOLDER_MASK" )
4332 else if( typeStr == "PASTE_MASK" )
4334 else if( typeStr == "SILK_SCREEN" )
4336 else if( typeStr == "ASSEMBLY" )
4338 else if( typeStr == "DOCUMENTATION" )
4340 else if( typeStr == "DRILL" )
4343 };
4344
4345 while( readLine( aStream, line ) )
4346 {
4347 if( line.empty() )
4348 continue;
4349
4350 // Stop if we hit a new section marker
4351 if( line[0] == '*' )
4352 {
4353 pushBackLine( line );
4354 break;
4355 }
4356
4357 std::istringstream iss( line );
4358 std::string token;
4359 iss >> token;
4360
4361 if( token == "{" )
4362 {
4363 braceDepth++;
4364 continue;
4365 }
4366
4367 if( token == "}" )
4368 {
4369 braceDepth--;
4370
4371 // Closing a layer block, save if we have valid data
4372 if( inLayerBlock && braceDepth == 1 )
4373 {
4374 if( currentLayerNum >= 0 )
4375 {
4376 currentLayer.number = currentLayerNum;
4377
4378 // Determine if copper based on layer number and type
4379 currentLayer.is_copper = ( currentLayer.layer_type == PADS_LAYER_FUNCTION::ROUTING ||
4380 currentLayer.layer_type == PADS_LAYER_FUNCTION::PLANE ||
4381 currentLayer.layer_type == PADS_LAYER_FUNCTION::MIXED );
4382 currentLayer.required = currentLayer.is_copper;
4383 m_layer_defs[currentLayerNum] = currentLayer;
4384 }
4385
4386 inLayerBlock = false;
4387 currentLayerNum = -1;
4388 }
4389
4390 // Exiting the outer LAYER block
4391 if( braceDepth <= 0 )
4392 break;
4393
4394 continue;
4395 }
4396
4397 if( token == "LAYER" )
4398 {
4399 int layerNum = -1;
4400 iss >> layerNum;
4401
4402 if( !iss.fail() && layerNum >= 0 )
4403 {
4404 // Starting a new layer definition
4405 currentLayerNum = layerNum;
4406 currentLayer = LAYER_INFO();
4407 currentLayer.number = layerNum;
4409 inLayerBlock = true;
4410 }
4411 }
4412 else if( token == "LAYER_NAME" && inLayerBlock )
4413 {
4414 // Read the rest of the line as the layer name
4415 std::string name;
4416 std::getline( iss >> std::ws, name );
4417 currentLayer.name = name;
4418 }
4419 else if( token == "LAYER_TYPE" && inLayerBlock )
4420 {
4421 std::string typeStr;
4422 iss >> typeStr;
4423 currentLayer.layer_type = parseLayerType( typeStr );
4424 }
4425 else if( token == "LAYER_THICKNESS" && inLayerBlock )
4426 {
4427 iss >> currentLayer.layer_thickness;
4428 }
4429 else if( token == "COPPER_THICKNESS" && inLayerBlock )
4430 {
4431 iss >> currentLayer.copper_thickness;
4432 }
4433 else if( token == "DIELECTRIC" && inLayerBlock )
4434 {
4435 iss >> currentLayer.dielectric_constant;
4436 }
4437 }
4438}
4439
4440
4441void PARSER::parseSectionMISC( std::ifstream& aStream )
4442{
4443 // The MISC section contains various optional data:
4444 // - NET_CLASS DATA (net class definitions with member nets)
4445 // - GROUP DATA (pin pair groups)
4446 // - ASSOCIATED NET DATA (associated net pairs)
4447 // - DIF_PAIR definitions with extended parameters
4448 // - DESIGN_RULES / RULE_SET (hierarchical design rules)
4449 // - ATTRIBUTES DICTIONARY (attribute type definitions)
4450 //
4451 // We parse DIF_PAIR and NET_CLASS definitions as they're most relevant for KiCad.
4452
4453 std::string line;
4454 int braceDepth = 0;
4455 bool inDifPair = false;
4456 bool inNetClassData = false;
4457 bool inNetClass = false;
4458 bool inDefaultRuleSet = false;
4459 bool inClearanceRule = false;
4460 int defaultRuleSetDepth = -1;
4461 int clearanceRuleDepth = -1;
4462 bool foundDefaultRules = false;
4463 DIFF_PAIR_DEF currentDiffPair;
4464 NET_CLASS_DEF currentNetClass;
4465
4466 while( readLine( aStream, line ) )
4467 {
4468 if( line.empty() )
4469 continue;
4470
4471 // Stop at next section marker
4472 if( line[0] == '*' && braceDepth == 0 )
4473 {
4474 pushBackLine( line );
4475 break;
4476 }
4477
4478 // Track brace depth for nested structures
4479 for( char c : line )
4480 {
4481 if( c == '{' )
4482 braceDepth++;
4483 else if( c == '}' )
4484 {
4485 braceDepth--;
4486
4487 if( braceDepth == 0 && inDifPair )
4488 {
4489 // End of DIF_PAIR block
4490 if( !currentDiffPair.name.empty() )
4491 m_diff_pairs.push_back( currentDiffPair );
4492
4493 inDifPair = false;
4494 currentDiffPair = DIFF_PAIR_DEF();
4495 }
4496
4497 if( braceDepth == 1 && inNetClass )
4498 {
4499 // End of NET_CLASS block (inside NET_CLASS DATA)
4500 if( !currentNetClass.name.empty() )
4501 m_net_classes.push_back( currentNetClass );
4502
4503 inNetClass = false;
4504 currentNetClass = NET_CLASS_DEF();
4505 }
4506
4507 if( braceDepth == 0 && inNetClassData )
4508 {
4509 // End of NET_CLASS DATA block
4510 inNetClassData = false;
4511 }
4512
4513 if( inClearanceRule && braceDepth < clearanceRuleDepth )
4514 {
4515 inClearanceRule = false;
4516
4517 // Fall back to struct defaults if no values were parsed
4518 if( m_design_rules.default_clearance
4519 == std::numeric_limits<double>::max() )
4520 {
4521 m_design_rules.default_clearance = DESIGN_RULES().default_clearance;
4522 }
4523
4524 m_design_rules.min_clearance = m_design_rules.default_clearance;
4525
4526 if( m_design_rules.copper_edge_clearance
4527 == std::numeric_limits<double>::max() )
4528 {
4529 m_design_rules.copper_edge_clearance =
4530 m_design_rules.default_clearance;
4531 }
4532
4533 foundDefaultRules = true;
4534 }
4535
4536 if( inDefaultRuleSet && braceDepth < defaultRuleSetDepth )
4537 inDefaultRuleSet = false;
4538 }
4539 }
4540
4541 std::istringstream iss( line );
4542 std::string token;
4543 iss >> token;
4544
4545 // LAYER DATA block contains per-layer definitions (name, type, etc.)
4546 // which may appear inside *MISC* instead of as a standalone section.
4547 if( token == "LAYER" )
4548 {
4549 std::string secondToken;
4550 iss >> secondToken;
4551
4552 if( secondToken == "DATA" )
4553 {
4554 parseSectionLAYERDEFS( aStream );
4555 continue;
4556 }
4557 }
4558
4559 // Check for NET_CLASS DATA section header
4560 if( token == "NET_CLASS" )
4561 {
4562 std::string secondToken;
4563 iss >> secondToken;
4564
4565 if( secondToken == "DATA" )
4566 {
4567 // Entering NET_CLASS DATA section
4568 inNetClassData = true;
4569 }
4570 else if( inNetClassData && !secondToken.empty() )
4571 {
4572 // Starting a new NET_CLASS definition inside NET_CLASS DATA
4573 // Format: NET_CLASS class_name
4574 if( inNetClass && !currentNetClass.name.empty() )
4575 m_net_classes.push_back( currentNetClass );
4576
4577 currentNetClass = NET_CLASS_DEF();
4578 currentNetClass.name = secondToken;
4579 inNetClass = true;
4580 }
4581 }
4582 else if( inNetClass && token == "NET" )
4583 {
4584 // Add net to current net class
4585 std::string netName;
4586 iss >> netName;
4587
4588 if( !netName.empty() )
4589 currentNetClass.net_names.push_back( netName );
4590 }
4591 else if( token == "RULE_SET" && !foundDefaultRules )
4592 {
4593 // RULE_SET (1) is the default clearance rule set.
4594 // Parse it to extract board-level design rule defaults.
4595 std::string ruleNum;
4596 iss >> ruleNum;
4597
4598 if( ruleNum == "(1)" )
4599 {
4600 inDefaultRuleSet = true;
4601 defaultRuleSetDepth = braceDepth;
4602 }
4603 }
4604 else if( inDefaultRuleSet && token == "CLEARANCE_RULE" )
4605 {
4606 inClearanceRule = true;
4607 clearanceRuleDepth = braceDepth;
4608 m_design_rules.default_clearance = std::numeric_limits<double>::max();
4609 m_design_rules.copper_edge_clearance = std::numeric_limits<double>::max();
4610 }
4611 else if( inClearanceRule )
4612 {
4613 double val = 0.0;
4614 iss >> val;
4615
4616 if( !iss.fail() && val > 0.0 )
4617 {
4618 if( token == "MIN_TRACK_WIDTH" )
4619 {
4620 m_design_rules.min_track_width = val;
4621 }
4622 else if( token == "REC_TRACK_WIDTH" )
4623 {
4624 m_design_rules.default_track_width = val;
4625 }
4626 else if( token == "DRILL_TO_DRILL" )
4627 {
4628 m_design_rules.hole_to_hole = val;
4629 }
4630 else if( token == "OUTLINE_TO_TRACK" || token == "OUTLINE_TO_VIA"
4631 || token == "OUTLINE_TO_PAD" || token == "OUTLINE_TO_COPPER" )
4632 {
4633 m_design_rules.copper_edge_clearance =
4634 std::min( m_design_rules.copper_edge_clearance, val );
4635 }
4636 else
4637 {
4638 // All clearance values (TRACK_TO_TRACK, VIA_TO_*, PAD_TO_*,
4639 // SMD_TO_*, COPPER_TO_*, TEXT_TO_*, DRILL_TO_*) contribute to
4640 // the global minimum copper clearance
4641 m_design_rules.default_clearance =
4642 std::min( m_design_rules.default_clearance, val );
4643 }
4644 }
4645 }
4646 else if( token == "DIF_PAIR" )
4647 {
4648 // Save previous diff pair if any
4649 if( inDifPair && !currentDiffPair.name.empty() )
4650 m_diff_pairs.push_back( currentDiffPair );
4651
4652 currentDiffPair = DIFF_PAIR_DEF();
4653 iss >> currentDiffPair.name;
4654 inDifPair = true;
4655 }
4656 else if( inDifPair )
4657 {
4658 if( token == "NET" )
4659 {
4660 std::string netName;
4661 iss >> netName;
4662
4663 // Assign to positive net first, then negative
4664 if( currentDiffPair.positive_net.empty() )
4665 currentDiffPair.positive_net = netName;
4666 else if( currentDiffPair.negative_net.empty() )
4667 currentDiffPair.negative_net = netName;
4668 }
4669 else if( token == "GAP" )
4670 {
4671 iss >> currentDiffPair.gap;
4672 }
4673 else if( token == "WIDTH" )
4674 {
4675 iss >> currentDiffPair.width;
4676 }
4677 else if( token == "CONNECTION" )
4678 {
4679 // CONNECTION format: ref.pin,ref.pin
4680 // This defines a pin pair for the diff pair
4681 // For now just skip - main net assignment is more important
4682 }
4683 else if( token == "ASSOCIATED" )
4684 {
4685 // ASSOCIATED NET netname - for associated net pairs
4686 std::string keyword, netName;
4687 iss >> keyword >> netName;
4688
4689 if( keyword == "NET" )
4690 {
4691 if( currentDiffPair.positive_net.empty() )
4692 currentDiffPair.positive_net = netName;
4693 else if( currentDiffPair.negative_net.empty() )
4694 currentDiffPair.negative_net = netName;
4695 }
4696 }
4697 }
4698
4699 // Parse per-instance attribute blocks: PART <refdes> { key value ... }
4700 // These appear inside ATTRIBUTE VALUES {...} at variable brace depth.
4701 if( token == "PART" && !inDifPair && !inNetClass )
4702 {
4703 std::string partName;
4704 iss >> partName;
4705
4706 if( !partName.empty() )
4707 {
4708 // Save brace depth before consuming the block's own { ... }
4709 int savedDepth = braceDepth;
4710 auto& attrs = m_part_instance_attrs[partName];
4711
4712 while( readLine( aStream, line ) )
4713 {
4714 if( line.empty() )
4715 continue;
4716
4717 if( line[0] == '}' )
4718 break;
4719
4720 if( line[0] == '{' )
4721 continue;
4722
4723 if( line[0] == '*' )
4724 {
4725 pushBackLine( line );
4727 return;
4728 }
4729
4730 std::string attrName, attrValue;
4731
4732 if( line[0] == '"' )
4733 {
4734 size_t endQuote = line.find( '"', 1 );
4735
4736 if( endQuote != std::string::npos )
4737 {
4738 attrName = line.substr( 1, endQuote - 1 );
4739 attrValue = line.substr( endQuote + 1 );
4740 }
4741 }
4742 else
4743 {
4744 std::istringstream attrSS( line );
4745 attrSS >> attrName;
4746 std::getline( attrSS >> std::ws, attrValue );
4747 }
4748
4749 if( !attrValue.empty() && attrValue[0] == ' ' )
4750 attrValue = attrValue.substr( 1 );
4751
4752 if( !attrName.empty() && !attrValue.empty() )
4753 attrs[attrName] = attrValue;
4754 }
4755
4756 // Restore to the depth before the PART block's braces
4757 braceDepth = savedDepth;
4758 }
4759
4760 continue;
4761 }
4762
4763 // Skip other MISC subsections (ATTRIBUTES DICTIONARY, DESIGN_RULES, etc.)
4764 }
4765
4767}
4768
4769
4771{
4772 DESIGN_RULES defaults;
4773
4774 if( m_design_rules.default_clearance == std::numeric_limits<double>::max() )
4775 m_design_rules.default_clearance = defaults.default_clearance;
4776
4777 if( m_design_rules.copper_edge_clearance == std::numeric_limits<double>::max() )
4778 m_design_rules.copper_edge_clearance = defaults.copper_edge_clearance;
4779}
4780
4781
4782std::vector<LAYER_INFO> PARSER::GetLayerInfos() const
4783{
4784 std::vector<LAYER_INFO> layers;
4785
4786 int layerCount = m_parameters.layer_count;
4787
4788 if( layerCount < 1 )
4789 layerCount = 2;
4790
4791 // Helper to check if a layer number is a copper layer
4792 auto isCopperLayer = [&]( int num ) {
4793 return num >= 1 && num <= layerCount;
4794 };
4795
4796 // Helper to get layer type from parsed defs or default
4797 auto getLayerDef = [&]( int num ) -> const LAYER_INFO* {
4798 auto it = m_layer_defs.find( num );
4799 return it != m_layer_defs.end() ? &it->second : nullptr;
4800 };
4801
4802 // Add copper layers with parsed info if available
4803 for( int i = 1; i <= layerCount; ++i )
4804 {
4805 const LAYER_INFO* parsed = getLayerDef( i );
4806
4807 if( parsed )
4808 {
4809 layers.push_back( *parsed );
4810 }
4811 else
4812 {
4813 // Generate default copper layer info
4815 info.number = i;
4817 info.is_copper = true;
4818 info.required = true;
4819
4820 if( i == 1 )
4821 info.name = "Top";
4822 else if( i == layerCount )
4823 info.name = "Bottom";
4824 else
4825 info.name = "Inner " + std::to_string( i - 1 );
4826
4827 layers.push_back( info );
4828 }
4829 }
4830
4831 // Add non-copper layers from parsed definitions
4832 for( const auto& [num, layerDef] : m_layer_defs )
4833 {
4834 if( !isCopperLayer( num ) )
4835 {
4836 layers.push_back( layerDef );
4837 }
4838 }
4839
4840 // If no non-copper layers were parsed, add default fallbacks
4841 if( m_layer_defs.empty() )
4842 {
4843 // Standard non-copper layers (common PADS layer numbers)
4844 layers.push_back( { 21, "Assembly Top", PADS_LAYER_FUNCTION::ASSEMBLY, false, false } );
4845 layers.push_back( { 22, "Assembly Bottom", PADS_LAYER_FUNCTION::ASSEMBLY, false, false } );
4846 layers.push_back( { 25, "Solder Mask Top", PADS_LAYER_FUNCTION::SOLDER_MASK, false, false } );
4847 layers.push_back( { 26, "Silkscreen Top", PADS_LAYER_FUNCTION::SILK_SCREEN, false, false } );
4848 layers.push_back( { 27, "Silkscreen Bottom", PADS_LAYER_FUNCTION::SILK_SCREEN, false, false } );
4849 layers.push_back( { 28, "Solder Mask Bottom", PADS_LAYER_FUNCTION::SOLDER_MASK, false, false } );
4850 layers.push_back( { 29, "Paste Top", PADS_LAYER_FUNCTION::PASTE_MASK, false, false } );
4851 layers.push_back( { 30, "Paste Bottom", PADS_LAYER_FUNCTION::PASTE_MASK, false, false } );
4852 }
4853
4854 return layers;
4855}
4856
4857} // namespace PADS_IO
const char * name
bool readLine(std::ifstream &aStream, std::string &aLine)
std::map< std::string, PART_DECAL > m_decals
std::vector< CLUSTER > m_clusters
std::map< std::string, REUSE_BLOCK > m_reuse_blocks
std::vector< KEEPOUT > m_keepouts
std::vector< POUR > m_pours
std::vector< JUMPER_DEF > m_jumper_defs
Jumper definitions from JUMPER section.
std::vector< TEXT > m_texts
void parseSectionPARTTYPE(std::ifstream &aStream)
void parseSectionJUMPER(std::ifstream &aStream)
void parseSectionTESTPOINT(std::ifstream &aStream)
void parseSectionLINES(std::ifstream &aStream)
PARAMETERS m_parameters
std::map< std::string, VIA_DEF > m_via_defs
void parseSectionBOARD(std::ifstream &aStream)
void parseSectionVIA(std::ifstream &aStream)
std::vector< COPPER_SHAPE > m_copper_shapes
Copper shapes from LINES section.
std::map< std::string, PART_TYPE > m_part_types
Per-instance attribute overrides from PART <name> {...} blocks in PARTTYPE section.
std::vector< NET > m_nets
void parseSectionPARTS(std::ifstream &aStream)
std::vector< ROUTE > m_routes
std::map< std::string, std::map< std::string, std::string > > m_part_instance_attrs
std::vector< NET_CLASS_DEF > m_net_classes
void parseSectionNETCLASS(std::ifstream &aStream)
void parseSectionMISC(std::ifstream &aStream)
void parseSectionREUSE(std::ifstream &aStream)
void parseSectionCLUSTER(std::ifstream &aStream)
void parseSectionLAYERDEFS(std::ifstream &aStream)
std::vector< LAYER_INFO > GetLayerInfos() const
Get layer information for layer mapping dialog.
DESIGN_RULES m_design_rules
void parseSectionNETS(std::ifstream &aStream)
void Parse(const wxString &aFileName)
FILE_HEADER m_file_header
Parsed file header info.
std::vector< GRAPHIC_LINE > m_graphic_lines
2D graphic lines from LINES section
std::vector< DIMENSION > m_dimensions
std::vector< DIFF_PAIR_DEF > m_diff_pairs
void parseSectionPCB(std::ifstream &aStream)
void clampDesignRuleSentinels()
void parseSectionTEXT(std::ifstream &aStream)
std::vector< POLYLINE > m_board_outlines
std::vector< PART > m_parts
std::map< int, LAYER_INFO > m_layer_defs
Parsed layer definitions by layer number.
void parseSectionROUTES(std::ifstream &aStream)
std::vector< TEST_POINT > m_test_points
std::optional< std::string > m_pushed_line
void pushBackLine(const std::string &aLine)
void parseSectionDIFFPAIR(std::ifstream &aStream)
void parseSectionPOUR(std::ifstream &aStream)
void parseSectionPARTDECAL(std::ifstream &aStream)
static bool empty(const wxTextEntryBase *aCtrl)
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.
@ VIA
Via thermal relief (VIATHERM)
@ PAD
Pad thermal relief (PADTHERM)
static std::vector< std::string > expandShortcutPattern(const std::string &aPattern)
Expand a shortcut format string like "PRE{n1-n2}" into individual names.
@ BURIED
Via spans only inner layers.
@ THROUGH
Via spans all copper layers.
@ BLIND
Via starts at top or bottom and ends at inner layer.
@ MICROVIA
Single-layer blind via (typically HDI)
PIN_ELEC_TYPE
Pin type classification for gate definitions.
@ BIDIRECTIONAL
B - Bidirectional pin.
@ UNDEFINED
U - Undefined.
@ OPEN_COLLECTOR
C - Open collector or or-tieable source.
@ TERMINATOR
Z - Terminator pin.
@ LOAD
L - Load pin.
@ TRISTATE
T - Tri-state pin.
@ POWER
P - Power pin.
@ GROUND
G - Ground pin.
@ SOURCE
S - Source pin.
@ LIB_PCB_DECAL
Library PCB decals (footprints)
Definition pads_parser.h:94
@ LIB_PART_TYPE
Library part types.
Definition pads_parser.h:95
@ LIB_SCH_DECAL
Library schematic decals.
Definition pads_parser.h:93
@ LIB_LINE
Library line items (drafting)
Definition pads_parser.h:92
@ PCB
PCB design file (POWERPCB, PADS-LAYOUT, etc.)
Definition pads_parser.h:91
@ ROUTE
Routing keepout (traces)
@ PLACEMENT
Component placement keepout.
@ VOIDOUT
Void/empty region (VOIDOUT)
@ HATCHED
Hatched pour (HATOUT)
PADS_LAYER_FUNCTION
Layer types from PADS LAYER_TYPE field.
@ ASSEMBLY
Assembly drawing.
@ ROUTING
Copper routing layer.
@ PASTE_MASK
Solder paste mask.
@ MIXED
Mixed signal/plane.
@ UNASSIGNED
Unassigned layer.
@ DOCUMENTATION
Documentation layer.
@ SILK_SCREEN
Silkscreen/legend.
@ PLANE
Power/ground plane.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
Common utilities and types for parsing PADS file formats.
A point that may be either a line endpoint or an arc segment.
Definition pads_parser.h:72
ARC arc
Arc parameters (only valid when is_arc is true)
Definition pads_parser.h:76
bool is_arc
True if this segment is an arc, false for line.
Definition pads_parser.h:75
double y
Endpoint Y coordinate.
Definition pads_parser.h:74
double x
Endpoint X coordinate.
Definition pads_parser.h:73
Arc definition using center point, radius, and angles.
Definition pads_parser.h:57
double cx
Center X coordinate.
Definition pads_parser.h:58
double delta_angle
Arc sweep angle in degrees (positive = CCW)
Definition pads_parser.h:62
std::string font_info
std::string hjust
std::string vjust
A cluster of related route segments that should be grouped together.
std::vector< std::string > segment_refs
References to route segments in cluster.
std::string name
Cluster name/identifier.
int id
Cluster ID number.
std::vector< std::string > net_names
Nets belonging to this cluster.
A copper shape from the LINES section (type=COPPER).
std::vector< ARC_POINT > outline
Shape outline vertices.
bool is_cutout
True for cutouts (COPCUT, COPCCO)
int layer
Layer number.
std::string net_name
Associated net (empty if unconnected)
bool filled
True for filled shapes (COPCLS, COPCIR)
double width
Line width (for open polylines)
std::string name
Shape name.
std::string restrictions
Keepout restrictions (R,C,V,T,A) for KPTCLS/KPTCIR.
bool is_tag_close
True if this is a closing TAG (level=0)
std::vector< ARC_POINT > points
Shape points, may include arc segments.
int pinnum
Pin association for copper pieces (-1 = none, 0+ = pin index)
std::string type
CLOSED, OPEN, CIRCLE, COPCLS, TAG, etc.
bool is_tag_open
True if this is an opening TAG (level=1)
Design rule definitions from PCB section.
double copper_edge_clearance
Board outline clearance (OUTLINE_TO_*)
double default_clearance
Default copper clearance (DEFAULTCLEAR)
Differential pair definition.
double width
Trace width.
std::string positive_net
Positive net name.
std::string negative_net
Negative net name.
double gap
Spacing between traces.
std::string name
Pair name.
A dimension annotation for measurement display.
std::string name
Dimension identifier.
double text_width
Text width.
double y
Origin Y coordinate.
double rotation
Text rotation angle.
bool is_horizontal
True for horizontal dimension.
double x
Origin X coordinate.
std::string text
Dimension text/value.
int layer
Layer for dimension graphics.
double crossbar_pos
Crossbar position (Y for horizontal, X for vertical)
double text_height
Text height.
std::vector< POINT > points
Dimension geometry points (measurement endpoints)
Gate definition for gate-swappable parts.
int gate_swap_type
Gate swap type (0 = not swappable)
std::vector< GATE_PIN > pins
Pins in this gate.
Pin definition within a gate.
std::string func_name
Optional functional name.
int swap_type
Swap type (0 = not swappable)
std::string pin_number
Electrical pin number.
PIN_ELEC_TYPE elec_type
A 2D graphic line/shape from the LINES section (type=LINES).
std::vector< ARC_POINT > points
Shape vertices, may include arcs.
bool closed
True if shape is closed (polygon/circle)
std::string name
Item name.
double width
Line width.
std::string reuse_instance
Reuse block instance name (if member of reuse)
int layer
Layer number.
Jumper definition from JUMPER section.
std::string padstack
Pad stack for start pin (or both if end_padstack empty)
bool wirebond
W flag: wirebond jumper.
double min_length
Minimum possible length.
std::string end_padstack
Pad stack for end pin (optional)
bool glued
G flag: glued.
std::string name
Jumper name/reference designator.
bool display_silk
D flag: display special silk.
std::vector< ATTRIBUTE > labels
Reference designator labels.
double length_increment
Length increment.
double max_length
Maximum possible length.
bool via_enabled
V flag: via enabled.
Jumper endpoint marker in a route.
bool is_start
True if start (S), false if end (E)
std::string name
Jumper part name.
A keepout area definition.
std::vector< ARC_POINT > outline
Keepout boundary.
bool no_vias
Prohibit vias (V restriction)
bool no_components
Prohibit component placement (P restriction)
double max_height
Maximum component height when height_restriction is true.
bool no_copper
Prohibit copper pours (C restriction)
bool no_accordion
Prohibit accordion flex (A restriction for accordion, not all)
bool height_restriction
Component height restriction (H restriction)
KEEPOUT_TYPE type
Type of keepout.
bool no_traces
Prohibit traces (R restriction)
std::vector< int > layers
Affected layers (empty = all)
bool no_test_points
Prohibit test points (T restriction)
PADS_LAYER_FUNCTION layer_type
Parsed layer type from file.
bool required
True if layer must be mapped.
bool is_copper
True if copper layer.
int number
PADS layer number.
double layer_thickness
Dielectric thickness (BASIC units)
std::string name
Layer name.
double dielectric_constant
Relative permittivity (Er)
double copper_thickness
Copper foil thickness (BASIC units)
Net class definition with routing constraints.
double via_drill
Via drill diameter (VIADRILL)
double clearance
Copper clearance (CLEARANCE)
std::vector< std::string > net_names
Nets assigned to this class.
double track_width
Track width (TRACKWIDTH)
std::string name
Net class name.
double diff_pair_width
Differential pair width (DIFFPAIRWIDTH)
double diff_pair_gap
Differential pair gap (DIFFPAIRGAP)
double via_size
Via diameter (VIASIZE)
std::string name
std::vector< NET_PIN > pins
bool chamfered
True if corners are chamfered (negative corner in PADS)
double drill
Drill hole diameter (0 for SMD)
int thermal_spoke_count
Number of thermal spokes (typically 4)
std::string shape
Shape code: R, S, A, O, OF, RF, RT, ST, RA, SA, RC, OC.
bool plated
True if drill is plated (PTH vs NPTH)
double rotation
Pad rotation angle in degrees.
double thermal_outer_diameter
Outer diameter of thermal or void in plane.
double slot_orientation
Slot orientation in degrees (0-179.999)
double thermal_spoke_orientation
First spoke orientation in degrees.
double slot_length
Slot length.
double inner_diameter
Inner diameter for annular ring (0 = solid)
double thermal_spoke_width
Width of thermal spokes.
double finger_offset
Finger pad offset along orientation axis.
double sizeB
Secondary size (height for rectangles/ovals)
double slot_offset
Slot offset from electrical center.
double corner_radius
Corner radius magnitude (always positive)
double sizeA
Primary size (diameter or width)
std::vector< DECAL_ITEM > items
std::vector< TERMINAL > terminals
std::vector< ATTRIBUTE > attributes
std::map< int, std::vector< PAD_STACK_LAYER > > pad_stacks
std::map< std::string, std::string > attributes
Attribute name-value pairs from {...} block.
std::map< std::string, int > pin_pad_map
Maps pin name to pad stack index.
std::vector< GATE_DEF > gates
Gate definitions for swap support.
std::vector< SIGPIN > signal_pins
Standard signal pin definitions.
std::string decal_name
std::string part_type
Part type name when using PARTTYPE@DECAL syntax.
bool explicit_decal
True if decal was explicitly specified with @ syntax.
std::string reuse_instance
Reuse block instance name (if member of reuse)
std::string decal
Primary decal (first in colon-separated list)
std::string name
int alt_decal_index
ALT field from placement (-1 = use primary decal)
std::vector< ATTRIBUTE > attributes
std::string reuse_part
Original part ref des inside the reuse block.
std::vector< std::string > alternate_decals
Alternate decals (remaining after ':' splits)
A polyline that may contain arc segments.
bool closed
True if polyline forms a closed shape.
std::vector< ARC_POINT > points
Polyline vertices, may include arcs.
std::string name
This pour record's name.
bool is_cutout
True if this is a cutout (POCUT) piece.
POUR_STYLE style
Pour fill style.
std::string net_name
std::string owner_pour
Name of parent pour (7th field in header)
double hatch_grid
Hatch grid spacing for hatched pours.
std::vector< ARC_POINT > points
Pour outline, may include arc segments.
THERMAL_TYPE thermal_type
double hatch_width
Hatch line width.
A reuse block definition containing parts and routes that can be instantiated.
std::vector< REUSE_NET > nets
Nets contained in this block with merge flags.
std::string net_naming
Default net naming scheme.
long timestamp
Creation/modification timestamp.
std::string part_naming
Default part naming scheme.
std::vector< std::string > part_names
Parts contained in this block.
std::vector< REUSE_INSTANCE > instances
Placements of this block.
std::string name
Block type name.
std::string instance_name
Instance name.
std::string part_naming
Part naming scheme (may be multi-word like "PREFIX pref")
std::string net_naming
Net naming scheme (may be multi-word like "SUFFIX suf")
bool glued
True if glued in place.
POINT location
Placement location.
double rotation
Rotation angle in degrees.
A reuse block instance placement.
std::string name
Original net name from reuse definition.
bool merge
True to merge nets, false to rename.
std::vector< VIA > vias
std::vector< TEARDROP > teardrops
Teardrop locations in this route.
std::vector< TRACK > tracks
std::vector< NET_PIN > pins
Pins connected to this net (from pin pair lines)
std::vector< JUMPER_MARKER > jumpers
Jumper start/end points in this route.
std::string net_name
Standard signal pin definition (power, ground, etc.)
std::string pin_number
Pin number.
double width
Track width for connections.
std::string signal_name
Standard signal name (e.g., VCC, GND)
Teardrop parameters for a route point.
int net_flags
Net-side teardrop flags.
double pad_width
Teardrop width at pad side.
int pad_flags
Pad-side teardrop flags.
double net_width
Teardrop width at net side.
double pad_length
Teardrop length toward pad.
double net_length
Teardrop length toward net.
std::string name
A test point definition for manufacturing/testing access.
std::vector< ARC_POINT > points
Track points, may include arc segments.
bool has_mask_front
Stack includes top soldermask opening (layer 25)
int drill_start
Drill start layer from file (for blind/buried vias)
int start_layer
First PADS layer number in via span.
int end_layer
Last PADS layer number in via span.
std::vector< PAD_STACK_LAYER > stack
int drill_end
Drill end layer from file (for blind/buried vias)
VIA_TYPE via_type
Classified via type.
std::string name
bool has_mask_back
Stack includes bottom soldermask opening (layer 28)
std::string name
KIBIS_PIN * pin
int radius
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
#define M_PI
static thread_pool * tp