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 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 level = val2;
1387 }
1388 else
1389 {
1390 level = val1;
1391 }
1392 }
1393
1394 DECAL_ITEM item;
1395 item.type = type;
1396 item.width = width;
1397 item.layer = level;
1398
1399 // Handle TAG piece type (no coordinates, used for grouping copper/cutouts)
1400 if( type == "TAG" )
1401 {
1402 // Level is used as open/close flag: 1=open group, 0=close group
1403 item.is_tag_open = ( level == 1 );
1404 item.is_tag_close = ( level == 0 );
1405 decal.items.push_back( item );
1406 continue;
1407 }
1408
1409 // Parse pinnum for copper pieces (COPCLS, COPOPN, COPCIR, COPCUT, COPCCO)
1410 // Format includes [pinnum] at the end for copper associated with a pin
1411 if( type.find( "COP" ) == 0 )
1412 {
1413 std::string remaining;
1414 std::getline( iss2, remaining );
1415
1416 // Check for pinnum in remaining tokens
1417 std::istringstream rem_ss( remaining );
1418 int pinnum_val = -1;
1419
1420 if( rem_ss >> pinnum_val )
1421 item.pinnum = pinnum_val;
1422 }
1423
1424 // Parse restrictions for keepout pieces (KPTCLS, KPTCIR)
1425 if( type.find( "KPT" ) == 0 )
1426 {
1427 std::string restrictions;
1428
1429 if( iss2 >> restrictions )
1430 item.restrictions = restrictions;
1431 }
1432
1433 for( int j = 0; j < corners; ++j )
1434 {
1435 if( !readLine( aStream, line ) )
1436 break;
1437
1438 std::stringstream iss3( line );
1439 double px = 0.0, py = 0.0;
1440
1441 if( !( iss3 >> px >> py ) )
1442 continue;
1443
1444 // Per PADS spec, arc format is: x1 y1 ab aa ax1 ay1 ax2 ay2
1445 // where x1,y1 = arc start point, ab = begin angle (tenths of deg),
1446 // aa = sweep angle (tenths of deg), ax1,ay1/ax2,ay2 = bounding box
1447 int startAngleTenths = 0, deltaAngleTenths = 0;
1448 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
1449
1450 if( iss3 >> startAngleTenths >> deltaAngleTenths
1451 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
1452 {
1453 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
1454 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
1455 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
1456 double startAngle = startAngleTenths / 10.0;
1457 double deltaAngle = deltaAngleTenths / 10.0;
1458
1459 // Calculate arc start point (center + radius at start angle)
1460 double startAngleRad = startAngle * M_PI / 180.0;
1461 double startX = cx + radius * std::cos( startAngleRad );
1462 double startY = cy + radius * std::sin( startAngleRad );
1463
1464 // Calculate arc endpoint (center + radius at end angle)
1465 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
1466 double endX = cx + radius * std::cos( endAngleRad );
1467 double endY = cy + radius * std::sin( endAngleRad );
1468
1469 // Add arc start as a regular point (connects from previous point)
1470 item.points.emplace_back( startX, startY );
1471
1472 ARC arc{};
1473 arc.cx = cx;
1474 arc.cy = cy;
1475 arc.radius = radius;
1476 arc.start_angle = startAngle;
1477 arc.delta_angle = deltaAngle;
1478
1479 // Add arc end with arc data (draws the arc from start to end)
1480 item.points.emplace_back( endX, endY, arc );
1481 }
1482 else
1483 {
1484 item.points.emplace_back( px, py );
1485 }
1486 }
1487
1488 decal.items.push_back( item );
1489 }
1490
1491 // Parse Text/Labels
1492 // The header says how many text/labels.
1493 // In the example:
1494 // VALUE ...
1495 // Regular ...
1496 // Part Type
1497 // VALUE ...
1498 // Regular ...
1499 // Ref.Des.
1500
1501 // Each text/label seems to take 3 lines?
1502 // VALUE line, Font line, Content line.
1503
1504 for( int i = 0; i < text_cnt + labels; ++i )
1505 {
1506 std::string line1, line2, line3;
1507 if( !readLine( aStream, line1 ) ) break;
1508 if( !readLine( aStream, line2 ) ) break;
1509 if( !readLine( aStream, line3 ) ) break;
1510
1511 ATTRIBUTE attr;
1512 // Line 1: VALUE X Y ...
1513 std::stringstream ss( line1 );
1514 std::string type_token;
1515 ss >> type_token;
1516
1517 // First token is visibility type: VALUE, FULL_NAME, NAME, FULL_BOTH, BOTH, NONE
1518 std::string mirrored_str, right_reading_str;
1519
1520 if( ss >> attr.x >> attr.y >> attr.orientation >> attr.level
1521 >> attr.height >> attr.width >> mirrored_str >> attr.hjust >> attr.vjust )
1522 {
1523 attr.visible = ( type_token == "VALUE" || type_token == "FULL_NAME"
1524 || type_token == "NAME" || type_token == "FULL_BOTH"
1525 || type_token == "BOTH" );
1526 attr.mirrored = ( mirrored_str == "M" );
1527 ss >> right_reading_str;
1528 attr.right_reading = ( right_reading_str == "Y" || right_reading_str == "ORTHO" );
1529 }
1530
1531 attr.font_info = line2;
1532 attr.name = line3;
1533
1534 decal.attributes.push_back( attr );
1535 } // Parse Terminals (T lines)
1536 // T-150 -110 -150 -110 1
1537 // Format: T X Y NMX NMY PINNUM
1538 // Wait, the example has: T-150 -110 -150 -110 1
1539 // It seems to be T<X> <Y> <NMX> <NMY> <PINNUM>
1540 // Note: T is attached to X coordinate sometimes? "T-150"
1541
1542 for( int i = 0; i < terminals; ++i )
1543 {
1544 if( !readLine( aStream, line ) ) break;
1545
1546 // Handle T prefix
1547 size_t t_pos = line.find( 'T' );
1548 if( t_pos != std::string::npos )
1549 {
1550 line[t_pos] = ' '; // Replace T with space
1551 }
1552
1553 std::stringstream iss_t( line );
1554 TERMINAL term;
1555 double nmx = 0.0, nmy = 0.0;
1556
1557 if( iss_t >> term.x >> term.y >> nmx >> nmy >> term.name )
1558 {
1559 decal.terminals.push_back( term );
1560 }
1561 }
1562
1563 // Parse Stacks (PAD definitions)
1564 // PAD <PIN_INDEX> <STACK_LINES>
1565 // Then <STACK_LINES> lines of data.
1566
1567 for( int i = 0; i < stacks; ++i )
1568 {
1569 if( !readLine( aStream, line ) )
1570 break;
1571
1572 std::stringstream iss_pad( line );
1573 std::string token;
1574 int pin_idx = 0;
1575 int stack_lines = 0;
1576 iss_pad >> token >> pin_idx >> stack_lines;
1577
1578 if( token != "PAD" )
1579 continue;
1580
1581 // Parse optional P (plated) or N (non-plated) after stack_lines
1582 std::string plated_token;
1583 bool default_plated = true;
1584 double header_drill = 0.0;
1585
1586 if( iss_pad >> plated_token )
1587 {
1588 if( plated_token == "P" )
1589 default_plated = true;
1590 else if( plated_token == "N" )
1591 default_plated = false;
1592 else
1593 {
1594 header_drill = PADS_COMMON::ParseDouble( plated_token, 0.0, "pad drill" );
1595 }
1596 }
1597
1598 // Parse optional slotted drill parameters from header
1599 double header_slot_ori = 0.0;
1600 double header_slot_len = 0.0;
1601 double header_slot_off = 0.0;
1602
1603 if( iss_pad >> header_slot_ori >> header_slot_len >> header_slot_off )
1604 {
1605 // Got slotted drill from header
1606 }
1607
1608 std::vector<PAD_STACK_LAYER> stack;
1609
1610 for( int j = 0; j < stack_lines; ++j )
1611 {
1612 if( !readLine( aStream, line ) )
1613 break;
1614
1615 std::stringstream line_ss( line );
1616
1617 int layer = 0;
1618 double size = 0.0;
1619 std::string shape;
1620
1621 if( !( line_ss >> layer >> size >> shape ) )
1622 continue;
1623
1624 PAD_STACK_LAYER layer_data;
1625 layer_data.layer = layer;
1626 layer_data.sizeA = size;
1627 layer_data.sizeB = size;
1628 layer_data.shape = shape;
1629 layer_data.plated = default_plated;
1630 layer_data.drill = header_drill;
1631 layer_data.slot_orientation = header_slot_ori;
1632 layer_data.slot_length = header_slot_len;
1633 layer_data.slot_offset = header_slot_off;
1634
1635 // Parse shape-specific parameters per PADS specification
1636 if( shape == "R" )
1637 {
1638 // Round pad: level size R
1639 // No additional shape params, may have drill after
1640 }
1641 else if( shape == "S" )
1642 {
1643 // Square pad: level size S [corner]
1644 // Negative corner = chamfered, positive = rounded, zero = square
1645 double corner = 0.0;
1646
1647 if( line_ss >> corner )
1648 {
1649 if( corner < 0 )
1650 {
1651 layer_data.corner_radius = -corner;
1652 layer_data.chamfered = true;
1653 }
1654 else
1655 {
1656 layer_data.corner_radius = corner;
1657 }
1658 }
1659 }
1660 else if( shape == "RA" || shape == "SA" )
1661 {
1662 // Anti-pad shapes: level size RA/SA (no additional params)
1663 // These define clearance shapes in plane layers
1664 }
1665 else if( shape == "A" )
1666 {
1667 // Annular pad: level size A inner_diameter
1668 double intd = 0.0;
1669
1670 if( line_ss >> intd )
1671 layer_data.inner_diameter = intd;
1672 }
1673 else if( shape == "OF" )
1674 {
1675 // Oval finger: level size OF orientation length offset
1676 double ori = 0.0, length = 0.0, offset = 0.0;
1677
1678 if( line_ss >> ori >> length >> offset )
1679 {
1680 layer_data.rotation = ori;
1681 layer_data.sizeB = length;
1682 layer_data.finger_offset = offset;
1683 }
1684 }
1685 else if( shape == "RF" )
1686 {
1687 // Rectangular finger: level size RF orientation length offset [corner]
1688 // Per PADS spec, corner radius exists for square and rectangular finger shapes.
1689 double ori = 0.0, length = 0.0, offset = 0.0;
1690
1691 if( line_ss >> ori >> length >> offset )
1692 {
1693 layer_data.rotation = ori;
1694 layer_data.sizeB = length;
1695 layer_data.finger_offset = offset;
1696
1697 double corner = 0.0;
1698
1699 if( line_ss >> corner )
1700 {
1701 if( corner < 0 )
1702 {
1703 layer_data.corner_radius = -corner;
1704 layer_data.chamfered = true;
1705 }
1706 else
1707 {
1708 layer_data.corner_radius = corner;
1709 }
1710 }
1711 }
1712 }
1713 else if( shape == "RT" || shape == "ST" )
1714 {
1715 // Thermal pads: level size RT/ST orientation inner_diam spoke_width spoke_count
1716 double ori = 0.0, outsize = 0.0, spkwid = 0.0;
1717 int spknum = 4;
1718
1719 if( line_ss >> ori >> outsize >> spkwid >> spknum )
1720 {
1721 layer_data.thermal_spoke_orientation = ori;
1722 layer_data.thermal_outer_diameter = outsize;
1723 layer_data.thermal_spoke_width = spkwid;
1724 layer_data.thermal_spoke_count = spknum;
1725 }
1726 }
1727 else if( shape == "O" || shape == "OC" )
1728 {
1729 // Odd shape (O) or Odd Circle (OC): level size shape
1730 // These use custom pad shapes defined elsewhere
1731 // No additional parameters, just store the shape type
1732 }
1733 else if( shape == "RC" )
1734 {
1735 // Rectangular with Corner: level size RC orientation length offset [corner]
1736 // Similar to RF but with optional corner radius
1737 double ori = 0.0, length = 0.0, offset = 0.0, corner = 0.0;
1738
1739 if( line_ss >> ori >> length >> offset )
1740 {
1741 layer_data.rotation = ori;
1742 layer_data.sizeB = length;
1743 layer_data.finger_offset = offset;
1744
1745 if( line_ss >> corner )
1746 {
1747 if( corner < 0 )
1748 {
1749 layer_data.corner_radius = -corner;
1750 layer_data.chamfered = true;
1751 }
1752 else
1753 {
1754 layer_data.corner_radius = corner;
1755 }
1756 }
1757 }
1758 }
1759
1760 // For some shapes, additional tokens may be drill and plated
1761 // Read remaining tokens
1762 std::vector<std::string> remaining;
1763 std::string token_rem;
1764
1765 while( line_ss >> token_rem )
1766 remaining.push_back( token_rem );
1767
1768 // Parse remaining tokens for drill, plated, and slotted drill
1769 if( !remaining.empty() )
1770 {
1771 size_t idx = 0;
1772
1773 // Check for drill value (numeric)
1774 double drill_val = PADS_COMMON::ParseDouble( remaining[idx],
1775 -1.0, "pad layer drill" );
1776
1777 if( drill_val >= 0.0 )
1778 {
1779 layer_data.drill = drill_val;
1780 idx++;
1781 }
1782
1783 // Check for plated flag
1784 if( idx < remaining.size() )
1785 {
1786 if( remaining[idx] == "P" || remaining[idx] == "Y" )
1787 {
1788 layer_data.plated = true;
1789 idx++;
1790 }
1791 else if( remaining[idx] == "N" )
1792 {
1793 layer_data.plated = false;
1794 idx++;
1795 }
1796 }
1797
1798 // Check for slotted drill parameters
1799 if( idx + 2 < remaining.size() )
1800 {
1801 layer_data.slot_orientation =
1802 PADS_COMMON::ParseDouble( remaining[idx], 0.0, "slot params" );
1803 layer_data.slot_length =
1804 PADS_COMMON::ParseDouble( remaining[idx + 1], 0.0, "slot params" );
1805 layer_data.slot_offset =
1806 PADS_COMMON::ParseDouble( remaining[idx + 2], 0.0, "slot params" );
1807 }
1808 }
1809
1810 stack.push_back( layer_data );
1811 }
1812
1813 decal.pad_stacks[pin_idx] = stack;
1814 }
1815
1816 m_decals[name] = decal;
1817 }
1818}
1819
1820void PARSER::parseSectionROUTES( std::ifstream& aStream )
1821{
1822 std::string line;
1823 ROUTE* current_route = nullptr;
1824 TRACK current_track;
1825 bool in_track = false;
1826 bool prev_is_plane_connection = false;
1827 ARC_POINT last_plane_connection_pt;
1828 int last_plane_connection_layer = 0;
1829 double last_plane_connection_width = 0;
1830 bool last_plane_on_copper = false;
1831 std::string default_via_name;
1832
1833 while( readLine( aStream, line ) )
1834 {
1835 if( line[0] == '*' )
1836 {
1837 if( line.rfind( "*SIGNAL*", 0 ) == 0 )
1838 {
1839 if( in_track && current_route )
1840 {
1841 current_route->tracks.push_back( current_track );
1842 current_track.points.clear();
1843 in_track = false;
1844 }
1845
1846 prev_is_plane_connection = false;
1847
1848 std::istringstream iss( line );
1849 std::string token;
1850 iss >> token; // *SIGNAL*
1851
1852 std::string net_name;
1853 iss >> net_name;
1854
1855 // Parse optional flags and default via
1856 default_via_name.clear();
1857
1858 while( iss >> token )
1859 {
1860 if( !token.empty() && token.back() == ';' )
1861 token.pop_back();
1862
1863 if( m_via_defs.count( token ) )
1864 default_via_name = token;
1865 }
1866
1867 m_routes.push_back( ROUTE() );
1868 current_route = &m_routes.back();
1869 current_route->net_name = net_name;
1870 continue;
1871 }
1872
1873 pushBackLine( line );
1874 break;
1875 }
1876
1877 // Parse pin pair lines (start with non-digit/non-sign)
1878 // Format: "REF.PIN REF.PIN"
1879 // These indicate which pins are connected by the following route segments
1880 if( !isdigit( line[0] ) && line[0] != '-' && line[0] != '+' )
1881 {
1882 if( in_track && current_route )
1883 {
1884 current_route->tracks.push_back( current_track );
1885 current_track.points.clear();
1886 in_track = false;
1887 }
1888
1889 prev_is_plane_connection = false;
1890
1891 // Parse pin pairs from this line and add to current route
1892 if( current_route )
1893 {
1894 std::istringstream pin_iss( line );
1895 std::string pin_token;
1896
1897 while( pin_iss >> pin_token )
1898 {
1899 size_t dot_pos = pin_token.find( '.' );
1900
1901 if( dot_pos != std::string::npos )
1902 {
1903 NET_PIN pin;
1904 pin.ref_des = pin_token.substr( 0, dot_pos );
1905 pin.pin_name = pin_token.substr( dot_pos + 1 );
1906
1907 // Check if pin already exists (avoid duplicates)
1908 bool found = false;
1909
1910 for( const auto& existing : current_route->pins )
1911 {
1912 if( existing.ref_des == pin.ref_des &&
1913 existing.pin_name == pin.pin_name )
1914 {
1915 found = true;
1916 break;
1917 }
1918 }
1919
1920 if( !found )
1921 current_route->pins.push_back( pin );
1922 }
1923 }
1924 }
1925
1926 continue;
1927 }
1928
1929 std::istringstream iss( line );
1930 ARC_POINT pt;
1931 int layer = 0;
1932 double width = 0.0;
1933 int flags = 0;
1934 iss >> pt.x >> pt.y >> layer >> width >> flags;
1935
1936 if( iss.fail() )
1937 continue;
1938
1939 // SEGMENTWIDTH is already in mils (not 1/256 mil units as previously thought)
1940
1941 // Parse FLAGS and optional arc direction / via name
1942 // Format: FLAGS [ARCDIR/VIANAME] [POWER] [TEARDROP ...] [JUMPER ...]
1943 // ARCDIR can be CW (clockwise) or CCW (counter-clockwise)
1944 // POWER indicates a connection through a power/ground plane (not a discrete track)
1945 std::string token;
1946 std::string via_name;
1947 std::string arc_dir;
1948
1949 // Per PADS spec, layer 0 means "unrouted portion" (virtual connection through
1950 // a plane or ratline). Only layer 0 makes a segment non-physical. Flag 0x100
1951 // ("plane thermal") and the THERMAL keyword describe pad/via thermal relief
1952 // style and do not affect whether the track segment exists.
1953 bool is_unrouted = ( layer == 0 );
1954 bool is_plane_connection = is_unrouted;
1955 TEARDROP teardrop;
1956 JUMPER_MARKER jumper;
1957 bool has_teardrop = false;
1958 bool has_jumper = false;
1959 bool has_power = false;
1960
1961 while( iss >> token )
1962 {
1963 // Check for arc direction
1964 if( token == "CW" || token == "CCW" )
1965 {
1966 arc_dir = token;
1967 continue;
1968 }
1969
1970 // POWER indicates a connection through a power/ground plane.
1971 // In PADS files, "POWER" often doubles as a via definition name.
1972 // THERMAL describes pad/via thermal relief style.
1973 if( token == "POWER" )
1974 {
1975 has_power = true;
1976
1977 if( m_via_defs.count( token ) )
1978 via_name = token;
1979
1980 continue;
1981 }
1982
1983 if( token == "THERMAL" )
1984 continue;
1985
1986 // Check for via name
1987 if( m_via_defs.count( token ) )
1988 {
1989 via_name = token;
1990 continue;
1991 }
1992
1993 // Parse TEARDROP: TEARDROP [P width length [flags]] [N width length [flags]]
1994 if( token == "TEARDROP" )
1995 {
1996 has_teardrop = true;
1997 std::string td_token;
1998
1999 while( iss >> td_token )
2000 {
2001 if( td_token == "P" )
2002 {
2003 teardrop.has_pad_teardrop = true;
2004 iss >> teardrop.pad_width >> teardrop.pad_length;
2005
2006 // Check for optional flags (numeric)
2007 std::streampos pos = iss.tellg();
2008 int td_flags = 0;
2009
2010 if( iss >> td_flags )
2011 {
2012 teardrop.pad_flags = td_flags;
2013 }
2014 else
2015 {
2016 iss.clear();
2017 iss.seekg( pos );
2018 }
2019 }
2020 else if( td_token == "N" )
2021 {
2022 teardrop.has_net_teardrop = true;
2023 iss >> teardrop.net_width >> teardrop.net_length;
2024
2025 std::streampos pos = iss.tellg();
2026 int td_flags = 0;
2027
2028 if( iss >> td_flags )
2029 {
2030 teardrop.net_flags = td_flags;
2031 }
2032 else
2033 {
2034 iss.clear();
2035 iss.seekg( pos );
2036 }
2037 }
2038 else
2039 {
2040 // Not a teardrop param, push back for further parsing
2041 // Since we can't push back easily, break and handle below
2042 if( td_token == "CW" || td_token == "CCW" )
2043 arc_dir = td_token;
2044 else if( td_token == "POWER" )
2045 {
2046 has_power = true;
2047
2048 if( m_via_defs.count( td_token ) )
2049 via_name = td_token;
2050 }
2051 else if( td_token == "THERMAL" )
2052 ;
2053 else if( m_via_defs.count( td_token ) )
2054 via_name = td_token;
2055
2056 break;
2057 }
2058 }
2059
2060 continue;
2061 }
2062
2063 // Parse JUMPER: jumper_name S|E
2064 // Jumper names are typically followed by S (start) or E (end)
2065 std::streampos pos = iss.tellg();
2066 std::string jumper_flag;
2067
2068 if( iss >> jumper_flag )
2069 {
2070 if( jumper_flag == "S" || jumper_flag == "E" )
2071 {
2072 has_jumper = true;
2073 jumper.name = token;
2074 jumper.is_start = ( jumper_flag == "S" );
2075 jumper.x = pt.x;
2076 jumper.y = pt.y;
2077 continue;
2078 }
2079 else
2080 {
2081 // Not a jumper, restore stream position
2082 iss.clear();
2083 iss.seekg( pos );
2084 }
2085 }
2086 else
2087 {
2088 iss.clear();
2089 iss.seekg( pos );
2090 }
2091
2092 // Skip REUSE tokens
2093 if( token == "REUSE" || token == ".REUSE." )
2094 {
2095 // Skip the instance name that follows
2096 std::string instance;
2097 iss >> instance;
2098 continue;
2099 }
2100 }
2101
2102 // If an arc direction was specified, mark this as an arc point
2103 // The arc center will be calculated from the previous and next points
2104 if( !arc_dir.empty() )
2105 {
2106 // For route arcs, we need previous point to calculate arc parameters
2107 // The arc goes from previous point to this point with given direction
2108 // PADS uses CW/CCW to indicate arc direction
2109 // We'll store a placeholder arc and the loader will need to compute it
2110 pt.is_arc = true;
2111
2112 // Delta angle sign indicates direction: positive = CCW, negative = CW
2113 pt.arc.delta_angle = ( arc_dir == "CCW" ) ? 90.0 : -90.0;
2114 }
2115
2116 // Per PADS spec: Layer 0 means "unrouted portion" - these are NOT physical tracks.
2117 // Layer 65 indicates the end of route/connection at a component pin.
2118 // Vias are only created when an explicit via token (STANDARDVIA, etc.) is present.
2119
2120 int effective_layer = layer;
2121 bool is_pad_connection = ( layer == 65 );
2122
2123 // Layer 0 means "unrouted" - this segment goes through a plane or is a ratline.
2124 // We still need an effective layer for via purposes, so use current track layer if available.
2125 if( is_unrouted && in_track )
2126 effective_layer = current_track.layer;
2127
2128 // Create via at this point if a via token was present.
2129 // This must happen before plane connection handling since plane connection points
2130 // with vias (STANDARDVIA + THERMAL) would otherwise skip via creation.
2131 if( !via_name.empty() && current_route )
2132 {
2133 VIA via;
2134 via.name = via_name;
2135 via.location = { pt.x, pt.y };
2136 current_route->vias.push_back( via );
2137 }
2138
2139 // POWER on a real copper layer means a via to the inner power/ground plane.
2140 // Routes often stub out to a POWER point and backtrack, with the via providing
2141 // the connection to the plane. Normally the POWER token itself names a via
2142 // definition (handled above), but if not, create an implicit via with the
2143 // route's default via type.
2144 if( has_power && via_name.empty() && !is_unrouted && !is_pad_connection && current_route )
2145 {
2146 VIA implicit_via;
2147
2148 if( !default_via_name.empty() )
2149 implicit_via.name = default_via_name;
2150 else if( !m_parameters.default_signal_via.empty() )
2151 implicit_via.name = m_parameters.default_signal_via;
2152
2153 implicit_via.location = { pt.x, pt.y };
2154 current_route->vias.push_back( implicit_via );
2155 }
2156
2157 // Store teardrop if present
2158 if( has_teardrop && current_route )
2159 {
2160 current_route->teardrops.push_back( teardrop );
2161 }
2162
2163 // Store jumper marker if present
2164 if( has_jumper && current_route )
2165 {
2166 current_route->jumpers.push_back( jumper );
2167 }
2168
2169 // Handle plane connections (POWER or THERMAL markers)
2170 // Segments between consecutive plane connection points are virtual connections through
2171 // copper pours and should not be created as discrete tracks.
2172 if( is_plane_connection && prev_is_plane_connection )
2173 {
2174 // Both current and previous points are plane connections - skip this segment.
2175 // The connection is made through the copper pour, not a discrete track.
2176 // Save this point as a potential track start/end if it's on a real copper layer
2177 // (not layer 0 / unrouted). Copper-layer plane points are where signals transition
2178 // between physical tracks and the pour.
2179 if( !is_unrouted )
2180 {
2181 last_plane_connection_pt = pt;
2182 last_plane_connection_layer = effective_layer;
2183 last_plane_connection_width = width;
2184 last_plane_on_copper = true;
2185 }
2186
2187 prev_is_plane_connection = true;
2188 continue;
2189 }
2190
2191 if( is_plane_connection && !prev_is_plane_connection )
2192 {
2193 // Transitioning from track to plane - add this point to complete the track
2194 // then end the track (no further segments until we exit the plane)
2195 if( in_track )
2196 {
2197 current_track.points.push_back( pt );
2198
2199 if( current_route && current_track.points.size() > 1 )
2200 current_route->tracks.push_back( current_track );
2201
2202 current_track.points.clear();
2203 in_track = false;
2204 }
2205
2206 // Save this plane connection point as a potential track start for the next
2207 // non-plane segment, if it's on a real copper layer (not layer 0 / unrouted).
2208 // Copper-layer plane points mark where signals transition between tracks and pours.
2209 last_plane_on_copper = !is_unrouted;
2210
2211 if( last_plane_on_copper )
2212 {
2213 last_plane_connection_pt = pt;
2214 last_plane_connection_layer = effective_layer;
2215 last_plane_connection_width = width;
2216 }
2217
2218 prev_is_plane_connection = true;
2219 continue;
2220 }
2221
2222 if( !is_plane_connection && prev_is_plane_connection )
2223 {
2224 // Transitioning from plane to track. Start a new track from the last copper-layer
2225 // plane point if it was on the same layer as the current point.
2226 if( in_track && current_route && current_track.points.size() > 1 )
2227 current_route->tracks.push_back( current_track );
2228
2229 prev_is_plane_connection = false;
2230
2231 if( is_pad_connection )
2232 {
2233 in_track = false;
2234 continue;
2235 }
2236
2237 current_track.points.clear();
2238
2239 if( last_plane_on_copper && last_plane_connection_layer == effective_layer )
2240 {
2241 current_track.layer = effective_layer;
2242 current_track.width = std::max( width, last_plane_connection_width );
2243 current_track.points.push_back( last_plane_connection_pt );
2244 current_track.points.push_back( pt );
2245 }
2246 else
2247 {
2248 // Layers differ. Create an implicit via if same location.
2249 // POWER vias are already handled by the central POWER handler above.
2250 if( !has_power && via_name.empty() && current_route && last_plane_on_copper &&
2251 std::abs( pt.x - last_plane_connection_pt.x ) < 0.001 &&
2252 std::abs( pt.y - last_plane_connection_pt.y ) < 0.001 )
2253 {
2254 VIA implicit_via;
2255
2256 if( !default_via_name.empty() )
2257 implicit_via.name = default_via_name;
2258 else if( !m_parameters.default_signal_via.empty() )
2259 implicit_via.name = m_parameters.default_signal_via;
2260
2261 implicit_via.location = { pt.x, pt.y };
2262 current_route->vias.push_back( implicit_via );
2263 }
2264
2265 current_track.layer = effective_layer;
2266 current_track.width = width;
2267 current_track.points.push_back( pt );
2268 }
2269
2270 last_plane_on_copper = false;
2271 in_track = true;
2272 continue;
2273 }
2274
2275 // Layer 65 is a special pad connection marker. Add the final point to terminate the
2276 // track at the pad, then stop building this track segment.
2277 if( is_pad_connection )
2278 {
2279 if( in_track && !current_track.points.empty() )
2280 {
2281 current_track.points.push_back( pt );
2282
2283 if( current_route && current_track.points.size() > 1 )
2284 current_route->tracks.push_back( current_track );
2285
2286 current_track.points.clear();
2287 in_track = false;
2288 }
2289
2290 continue;
2291 }
2292
2293 // Normal track building (neither current nor previous is plane connection)
2294 prev_is_plane_connection = false;
2295
2296 if( !in_track )
2297 {
2298 current_track.layer = effective_layer;
2299 current_track.width = width;
2300 current_track.points.clear();
2301 current_track.points.push_back( pt );
2302 in_track = true;
2303 }
2304 else
2305 {
2306 bool layer_changed = ( effective_layer != current_track.layer );
2307 bool width_changed = ( std::abs( width - current_track.width ) > 0.001 );
2308
2309 if( layer_changed || width_changed )
2310 {
2311 // Check if we should connect to this point
2312 bool connect = true;
2313
2314 if( layer_changed && via_name.empty() )
2315 {
2316 bool same_location =
2317 ( pt.x == current_track.points.back().x &&
2318 pt.y == current_track.points.back().y );
2319
2320 if( same_location )
2321 {
2322 // Same location layer change implies an implicit via.
2323 // POWER vias are already created in the central POWER handler.
2324 if( !has_power && current_route )
2325 {
2326 VIA implicit_via;
2327 implicit_via.name =
2328 default_via_name.empty() ? "STANDARDVIA" : default_via_name;
2329 implicit_via.location = { pt.x, pt.y };
2330 current_route->vias.push_back( implicit_via );
2331 }
2332 }
2333 else if( !has_power )
2334 {
2335 // Different location without POWER, treat as jump/ratline
2336 connect = false;
2337 }
2338 // POWER at different location: via already created, keep connected
2339 }
2340
2341 if( connect )
2342 {
2343 current_track.points.push_back( pt );
2344 }
2345
2346 if( current_route )
2347 current_route->tracks.push_back( current_track );
2348
2349 // Start new track from current point
2350 ARC_POINT prev_pt = pt;
2351
2352 current_track.layer = effective_layer;
2353 current_track.width = width;
2354 current_track.points.clear();
2355 current_track.points.push_back( prev_pt );
2356 }
2357 else
2358 {
2359 current_track.points.push_back( pt );
2360 }
2361 }
2362 }
2363
2364 if( in_track && current_route )
2365 {
2366 current_route->tracks.push_back( current_track );
2367 }
2368}
2369
2370void PARSER::parseSectionTEXT( std::ifstream& aStream )
2371{
2372 std::string line;
2373
2374 while( readLine( aStream, line ) )
2375 {
2376 if( line[0] == '*' )
2377 {
2378 pushBackLine( line );
2379 break;
2380 }
2381
2382 // Format: X Y ORI LEVEL HEIGHT WIDTH M HJUST VJUST [NDIM] [.REUSE. instance]
2383 // HJUST: LEFT, CENTER, RIGHT
2384 // VJUST: UP, CENTER, DOWN
2385 std::istringstream iss( line );
2386 TEXT text;
2387
2388 iss >> text.location.x >> text.location.y >> text.rotation >> text.layer
2389 >> text.height >> text.width;
2390
2391 if( iss.fail() )
2392 continue;
2393
2394 std::string mirrored;
2395 iss >> mirrored;
2396 text.mirrored = ( mirrored == "M" );
2397
2398 // Parse optional hjust and vjust
2399 iss >> text.hjust >> text.vjust;
2400
2401 // Parse optional ndim and .REUSE. instance
2402 std::string token;
2403
2404 if( iss >> token )
2405 {
2406 if( token == ".REUSE." )
2407 {
2408 iss >> text.reuse_instance;
2409 }
2410 else
2411 {
2412 text.ndim = PADS_COMMON::ParseInt( token, 0, "text ndim" );
2413
2414 if( iss >> token && token == ".REUSE." )
2415 {
2416 iss >> text.reuse_instance;
2417 }
2418 }
2419 }
2420
2421 // Read Font line
2422 // Format: fontstyle[:fontheight:fontdescent] fontface
2423 if( readLine( aStream, line ) )
2424 {
2425 std::istringstream fiss( line );
2426 std::string font_style_part;
2427
2428 fiss >> font_style_part;
2429
2430 size_t colon_pos = font_style_part.find( ':' );
2431
2432 if( colon_pos != std::string::npos )
2433 {
2434 text.font_style = font_style_part.substr( 0, colon_pos );
2435 std::string remaining = font_style_part.substr( colon_pos + 1 );
2436
2437 size_t second_colon = remaining.find( ':' );
2438
2439 if( second_colon != std::string::npos )
2440 {
2441 text.font_height = PADS_COMMON::ParseDouble(
2442 remaining.substr( 0, second_colon ), 0.0, "font height" );
2443 text.font_descent = PADS_COMMON::ParseDouble(
2444 remaining.substr( second_colon + 1 ), 0.0, "font descent" );
2445 }
2446 else
2447 {
2448 text.font_height = PADS_COMMON::ParseDouble( remaining, 0.0, "font height" );
2449 }
2450 }
2451 else
2452 {
2453 text.font_style = font_style_part;
2454 }
2455
2456 // Extract font face (after angle brackets or rest of line)
2457 size_t bracket_start = line.find( '<' );
2458 size_t bracket_end = line.find( '>' );
2459
2460 if( bracket_start != std::string::npos && bracket_end != std::string::npos )
2461 {
2462 text.font_face = line.substr( bracket_start + 1, bracket_end - bracket_start - 1 );
2463 }
2464 else
2465 {
2466 std::string rest;
2467 std::getline( fiss, rest );
2468
2469 if( !rest.empty() && rest[0] == ' ' )
2470 rest = rest.substr( 1 );
2471
2472 text.font_face = rest;
2473 }
2474 }
2475
2476 // Read Content line
2477 if( readLine( aStream, line ) )
2478 {
2479 // Standard PADS format uses literal backslash-n for line breaks
2480 size_t pos = 0;
2481
2482 while( ( pos = line.find( "\\n", pos ) ) != std::string::npos )
2483 {
2484 line.replace( pos, 2, "\n" );
2485 pos += 1;
2486 }
2487
2488 // EasyEDA exports (mode "250L") encode newlines as underscores
2489 if( m_file_header.mode == "250L" )
2490 std::replace( line.begin(), line.end(), '_', '\n' );
2491
2492 text.content = line;
2493 m_texts.push_back( text );
2494 }
2495 }
2496}
2497
2498void PARSER::parseSectionBOARD( std::ifstream& aStream )
2499{
2500 // The *BOARD* section uses the same format as LINES section with linetype=BOARD
2501 // Format: name BOARD xloc yloc pieces flags [text]
2502 std::string line;
2503
2504 while( readLine( aStream, line ) )
2505 {
2506 if( line[0] == '*' )
2507 {
2508 pushBackLine( line );
2509 break;
2510 }
2511
2512 std::istringstream iss( line );
2513 std::string name, type;
2514 double xloc = 0.0, yloc = 0.0;
2515 int pieces = 0;
2516 iss >> name >> type >> xloc >> yloc >> pieces;
2517
2518 // Parse all pieces for this board outline entry
2519 for( int i = 0; i < pieces; ++i )
2520 {
2521 if( !readLine( aStream, line ) )
2522 break;
2523
2524 if( line[0] == '*' )
2525 {
2526 pushBackLine( line );
2527 return;
2528 }
2529
2530 std::istringstream piss( line );
2531 std::string shape_type;
2532 int corners = 0;
2533 double width = 0.0;
2534 int linestyle = 0, level = 0;
2535 piss >> shape_type >> corners >> width >> linestyle >> level;
2536
2537 // Handle CLOSED, OPEN, CIRCLE, BRDCLS (board cutout), BRDCIR (circular cutout)
2538 if( shape_type == "CLOSED" || shape_type == "OPEN" || shape_type == "BRDCLS" )
2539 {
2540 POLYLINE polyline;
2541 polyline.layer = 0;
2542 polyline.width = width;
2543 polyline.closed = ( shape_type == "CLOSED" || shape_type == "BRDCLS" );
2544
2545 for( int j = 0; j < corners; ++j )
2546 {
2547 if( !readLine( aStream, line ) )
2548 break;
2549
2550 if( line[0] == '*' )
2551 {
2552 pushBackLine( line );
2553 return;
2554 }
2555
2556 std::istringstream ciss( line );
2557 double dx = 0.0, dy = 0.0;
2558 ciss >> dx >> dy;
2559
2560 // Per PADS spec, arc format is: x1 y1 ab aa ax1 ay1 ax2 ay2
2561 // where x1,y1 = arc start point; center = bounding box midpoint
2562 int startAngleTenths = 0, deltaAngleTenths = 0;
2563 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
2564
2565 if( ciss >> startAngleTenths >> deltaAngleTenths
2566 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
2567 {
2568 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
2569 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
2570 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
2571 double startAngle = startAngleTenths / 10.0;
2572 double deltaAngle = deltaAngleTenths / 10.0;
2573
2574 double startAngleRad = startAngle * M_PI / 180.0;
2575 double startX = cx + radius * std::cos( startAngleRad );
2576 double startY = cy + radius * std::sin( startAngleRad );
2577
2578 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
2579 double endX = cx + radius * std::cos( endAngleRad );
2580 double endY = cy + radius * std::sin( endAngleRad );
2581
2582 polyline.points.emplace_back( xloc + startX, yloc + startY );
2583
2584 ARC arc{};
2585 arc.cx = xloc + cx;
2586 arc.cy = yloc + cy;
2587 arc.radius = radius;
2588 arc.start_angle = startAngle;
2589 arc.delta_angle = deltaAngle;
2590
2591 polyline.points.emplace_back( xloc + endX, yloc + endY, arc );
2592 }
2593 else
2594 {
2595 polyline.points.emplace_back( xloc + dx, yloc + dy );
2596 }
2597 }
2598
2599 if( !polyline.points.empty() )
2600 m_board_outlines.push_back( polyline );
2601 }
2602 else if( shape_type == "CIRCLE" || shape_type == "BRDCIR" )
2603 {
2604 // Circle format: 2 coordinates define opposite ends of diameter
2605 POLYLINE polyline;
2606 polyline.layer = 0;
2607 polyline.width = width;
2608 polyline.closed = true;
2609
2610 double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
2611
2612 if( readLine( aStream, line ) )
2613 {
2614 std::istringstream c1( line );
2615 c1 >> x1 >> y1;
2616 }
2617
2618 if( corners >= 2 && readLine( aStream, line ) )
2619 {
2620 std::istringstream c2( line );
2621 c2 >> x2 >> y2;
2622 }
2623
2624 // Calculate center and radius from diameter endpoints
2625 double cx = xloc + ( x1 + x2 ) / 2.0;
2626 double cy = yloc + ( y1 + y2 ) / 2.0;
2627 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 ) ) / 2.0;
2628
2629 // Create full circle arc
2630 ARC arc{};
2631 arc.cx = cx;
2632 arc.cy = cy;
2633 arc.radius = radius;
2634 arc.start_angle = 0.0;
2635 arc.delta_angle = 360.0;
2636
2637 polyline.points.emplace_back( cx + radius, cy, arc );
2638
2639 if( !polyline.points.empty() )
2640 m_board_outlines.push_back( polyline );
2641 }
2642 else
2643 {
2644 // Unknown shape type, skip corners
2645 for( int j = 0; j < corners; ++j )
2646 {
2647 if( !readLine( aStream, line ) )
2648 break;
2649
2650 if( line[0] == '*' )
2651 {
2652 pushBackLine( line );
2653 return;
2654 }
2655 }
2656 }
2657 }
2658 }
2659}
2660
2661void PARSER::parseSectionLINES( std::ifstream& aStream )
2662{
2663 std::string line;
2664
2665 while( readLine( aStream, line ) )
2666 {
2667 if( line[0] == '*' )
2668 {
2669 pushBackLine( line );
2670 break;
2671 }
2672
2673 // Header format: name type xloc yloc pieces flags [text [signame]]
2674 std::istringstream iss( line );
2675 std::string name, type;
2676 double xloc = 0.0, yloc = 0.0;
2677 int pieces = 0, flags = 0, textCount = 0;
2678 std::string signame;
2679
2680 iss >> name >> type >> xloc >> yloc >> pieces >> flags;
2681
2682 // Try to read optional text count and signal name (for COPPER type).
2683 // Standard format: pieces flags textcount signame
2684 // EasyEDA format: pieces flags signame (no text count)
2685 if( iss >> textCount )
2686 {
2687 iss >> signame;
2688 }
2689 else
2690 {
2691 iss.clear();
2692 iss >> signame;
2693 }
2694
2695 // Check for optional .REUSE. line after header
2696 std::string reuse_instance, reuse_signal;
2697
2698 if( readLine( aStream, line ) )
2699 {
2700 if( line.find( ".REUSE." ) != std::string::npos )
2701 {
2702 std::istringstream riss( line );
2703 std::string reuse_keyword;
2704 riss >> reuse_keyword >> reuse_instance >> reuse_signal;
2705 }
2706 else
2707 {
2708 pushBackLine( line );
2709 }
2710 }
2711
2712 if( type == "BOARD" )
2713 {
2714 for( int i=0; i<pieces; ++i )
2715 {
2716 if( !readLine( aStream, line ) ) break;
2717 if( line[0] == '*' ) { pushBackLine( line ); return; }
2718
2719 std::istringstream piss( line );
2720 std::string shape_type;
2721 int corners = 0;
2722 double width = 0.0;
2723 int piece_flags = 0;
2724 int level = 0;
2725 piss >> shape_type >> corners >> width >> piece_flags >> level;
2726
2727 if( shape_type == "CLOSED" || shape_type == "OPEN" || shape_type == "BRDCLS" )
2728 {
2729 POLYLINE polyline;
2730 polyline.layer = 0; // Board outline is layer-agnostic
2731 polyline.width = width;
2732 polyline.closed = ( shape_type == "CLOSED" || shape_type == "BRDCLS" );
2733
2734 for( int j = 0; j < corners; ++j )
2735 {
2736 if( !readLine( aStream, line ) )
2737 break;
2738
2739 if( line[0] == '*' )
2740 {
2741 pushBackLine( line );
2742 return;
2743 }
2744
2745 std::istringstream ciss( line );
2746 double dx = 0.0, dy = 0.0;
2747 ciss >> dx >> dy;
2748
2749 // Per PADS spec, arc format is: x1 y1 ab aa ax1 ay1 ax2 ay2
2750 // where x1,y1 = arc start point; center = bounding box midpoint
2751 int startAngleTenths = 0, deltaAngleTenths = 0;
2752 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
2753
2754 if( ciss >> startAngleTenths >> deltaAngleTenths
2755 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
2756 {
2757 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
2758 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
2759 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
2760 double startAngle = startAngleTenths / 10.0;
2761 double deltaAngle = deltaAngleTenths / 10.0;
2762
2763 double startAngleRad = startAngle * M_PI / 180.0;
2764 double startX = cx + radius * std::cos( startAngleRad );
2765 double startY = cy + radius * std::sin( startAngleRad );
2766
2767 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
2768 double endX = cx + radius * std::cos( endAngleRad );
2769 double endY = cy + radius * std::sin( endAngleRad );
2770
2771 polyline.points.emplace_back( xloc + startX, yloc + startY );
2772
2773 ARC arc{};
2774 arc.cx = xloc + cx;
2775 arc.cy = yloc + cy;
2776 arc.radius = radius;
2777 arc.start_angle = startAngle;
2778 arc.delta_angle = deltaAngle;
2779
2780 polyline.points.emplace_back( xloc + endX, yloc + endY, arc );
2781 }
2782 else
2783 {
2784 polyline.points.emplace_back( xloc + dx, yloc + dy );
2785 }
2786 }
2787
2788 if( !polyline.points.empty() )
2789 m_board_outlines.push_back( polyline );
2790 }
2791 else if( shape_type == "CIRCLE" || shape_type == "BRDCIR" )
2792 {
2793 // Circle: 2 coordinates define opposite ends of diameter
2794 double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
2795
2796 if( readLine( aStream, line ) )
2797 {
2798 std::istringstream c1( line );
2799 c1 >> x1 >> y1;
2800 }
2801
2802 if( corners >= 2 && readLine( aStream, line ) )
2803 {
2804 std::istringstream c2( line );
2805 c2 >> x2 >> y2;
2806 }
2807
2808 double cx = xloc + ( x1 + x2 ) / 2.0;
2809 double cy = yloc + ( y1 + y2 ) / 2.0;
2810 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 ) +
2811 ( y2 - y1 ) * ( y2 - y1 ) ) / 2.0;
2812
2813 POLYLINE polyline;
2814 polyline.layer = 0;
2815 polyline.width = width;
2816 polyline.closed = true;
2817
2818 ARC arc{};
2819 arc.cx = cx;
2820 arc.cy = cy;
2821 arc.radius = radius;
2822 arc.start_angle = 0.0;
2823 arc.delta_angle = 360.0;
2824
2825 polyline.points.emplace_back( cx + radius, cy, arc );
2826
2827 if( !polyline.points.empty() )
2828 m_board_outlines.push_back( polyline );
2829 }
2830 else
2831 {
2832 for( int j=0; j<corners; ++j )
2833 {
2834 if( !readLine( aStream, line ) ) break;
2835 if( line[0] == '*' ) { pushBackLine( line ); return; }
2836 }
2837 }
2838 }
2839 }
2840 else if( name.rfind( "DIM", 0 ) == 0 && type == "LINES" )
2841 {
2842 // Dimension annotation with BASPNT (base points), ARWLN/ARWHD (arrows),
2843 // and EXTLN (extension lines).
2844 //
2845 // BASPNT pairs define the measurement endpoints. There are typically two
2846 // BASPNT shapes: the first defines the start point (usually at origin),
2847 // and the second defines the end point as an offset from the dimension origin.
2848 //
2849 // ARWLN defines the crossbar position (Y for horizontal, X for vertical).
2850 //
2851 // For a proper linear dimension, we extract:
2852 // - Start point from first BASPNT (first coordinate of the pair)
2853 // - End point from second BASPNT (first coordinate of the pair)
2854 // - Crossbar position from ARWLN (used to compute height)
2855 DIMENSION dim;
2856 dim.name = name;
2857 dim.x = xloc;
2858 dim.y = yloc;
2859
2860 double baspnt1_x = 0, baspnt1_y = 0;
2861 double baspnt2_x = 0, baspnt2_y = 0;
2862 double arwln_x = 0, arwln_y = 0;
2863 int baspnt_count = 0;
2864 bool hasArwln = false;
2865
2866 for( int i = 0; i < pieces; ++i )
2867 {
2868 if( !readLine( aStream, line ) )
2869 break;
2870
2871 if( line[0] == '*' )
2872 {
2873 pushBackLine( line );
2874 break;
2875 }
2876
2877 std::istringstream piss( line );
2878 std::string shape_type;
2879 int corners = 0;
2880 double width = 0;
2881 int piece_flags = 0;
2882 int level = 0;
2883 piss >> shape_type >> corners >> width >> piece_flags >> level;
2884
2885 dim.layer = level;
2886
2887 for( int j = 0; j < corners; ++j )
2888 {
2889 if( !readLine( aStream, line ) )
2890 break;
2891
2892 if( line[0] == '*' )
2893 {
2894 pushBackLine( line );
2895 break;
2896 }
2897
2898 std::istringstream ciss( line );
2899 double dx = 0.0, dy = 0.0;
2900 ciss >> dx >> dy;
2901
2902 // BASPNT defines measurement endpoints. First BASPNT is start,
2903 // second BASPNT is end. Only capture the first point of each pair.
2904 if( shape_type == "BASPNT" && j == 0 )
2905 {
2906 if( baspnt_count == 0 )
2907 {
2908 baspnt1_x = xloc + dx;
2909 baspnt1_y = yloc + dy;
2910 }
2911 else if( baspnt_count == 1 )
2912 {
2913 baspnt2_x = xloc + dx;
2914 baspnt2_y = yloc + dy;
2915 }
2916
2917 baspnt_count++;
2918 }
2919
2920 // ARWLN1 first point: crossbar position
2921 if( shape_type == "ARWLN1" && j == 0 )
2922 {
2923 arwln_x = xloc + dx;
2924 arwln_y = yloc + dy;
2925 hasArwln = true;
2926 }
2927 }
2928 }
2929
2930 // Build measurement points from BASPNT positions.
2931 if( baspnt_count >= 2 )
2932 {
2933 // Determine if this is a horizontal or vertical dimension based on
2934 // which axis has the larger offset between the two BASPNT points.
2935 double dx = std::abs( baspnt2_x - baspnt1_x );
2936 double dy = std::abs( baspnt2_y - baspnt1_y );
2937 bool isHorizontal = dx > dy;
2938
2939 dim.is_horizontal = isHorizontal;
2940
2941 POINT pt1{}, pt2{};
2942 pt1.x = baspnt1_x;
2943 pt1.y = baspnt1_y;
2944 pt2.x = baspnt2_x;
2945 pt2.y = baspnt2_y;
2946
2947 // Store crossbar position for height calculation
2948 if( hasArwln )
2949 {
2950 if( isHorizontal )
2951 dim.crossbar_pos = arwln_y;
2952 else
2953 dim.crossbar_pos = arwln_x;
2954 }
2955
2956 dim.points.push_back( pt1 );
2957 dim.points.push_back( pt2 );
2958 }
2959
2960 // Parse text items for this dimension (same 3-line format as board text).
2961 // The first text is used as the dimension value label.
2962 for( int t = 0; t < textCount; ++t )
2963 {
2964 if( !readLine( aStream, line ) )
2965 break;
2966
2967 if( line[0] == '*' )
2968 {
2969 pushBackLine( line );
2970 break;
2971 }
2972
2973 std::istringstream tiss( line );
2974 double tx = 0.0, ty = 0.0;
2975 tiss >> tx >> ty;
2976
2977 if( tiss.fail() )
2978 {
2979 readLine( aStream, line );
2980 readLine( aStream, line );
2981 continue;
2982 }
2983
2984 double trot = 0.0;
2985 int tlayer = 0;
2986 double theight = 0.0, twidth = 0.0;
2987 tiss >> trot >> tlayer >> theight >> twidth;
2988
2989 // Font line
2990 if( !readLine( aStream, line ) )
2991 break;
2992
2993 // Content line
2994 if( !readLine( aStream, line ) )
2995 break;
2996
2997 if( t == 0 )
2998 {
2999 dim.text = line;
3000 dim.text_height = theight;
3001 dim.text_width = twidth;
3002 dim.rotation = trot;
3003 }
3004 }
3005
3006 textCount = 0;
3007
3008 if( !dim.points.empty() )
3009 m_dimensions.push_back( dim );
3010 }
3011 else if( type == "KEEPOUT" || type == "RESTRICTVIA" || type == "RESTRICTROUTE"
3012 || type == "RESTRICTAREA" || type == "PLACEMENT_KEEPOUT" )
3013 {
3014 // Parse keepout area definition
3015 KEEPOUT keepout;
3016
3017 // Set defaults based on type name (fallback if no restriction codes in piece)
3018 if( type == "KEEPOUT" || type == "RESTRICTAREA" )
3019 {
3020 keepout.type = KEEPOUT_TYPE::ALL;
3021 keepout.no_traces = true;
3022 keepout.no_vias = true;
3023 keepout.no_copper = true;
3024 }
3025 else if( type == "RESTRICTVIA" )
3026 {
3027 keepout.type = KEEPOUT_TYPE::VIA;
3028 keepout.no_traces = false;
3029 keepout.no_vias = true;
3030 keepout.no_copper = false;
3031 }
3032 else if( type == "RESTRICTROUTE" )
3033 {
3034 keepout.type = KEEPOUT_TYPE::ROUTE;
3035 keepout.no_traces = true;
3036 keepout.no_vias = false;
3037 keepout.no_copper = false;
3038 }
3039 else if( type == "PLACEMENT_KEEPOUT" )
3040 {
3041 keepout.type = KEEPOUT_TYPE::PLACEMENT;
3042 keepout.no_traces = false;
3043 keepout.no_vias = false;
3044 keepout.no_copper = false;
3045 keepout.no_components = true;
3046 }
3047
3048 for( int i = 0; i < pieces; ++i )
3049 {
3050 if( !readLine( aStream, line ) )
3051 break;
3052
3053 if( line[0] == '*' )
3054 {
3055 pushBackLine( line );
3056 break;
3057 }
3058
3059 // Piece format: PIECETYPE CORNERS WIDTH FLAGS LEVEL [RESTRICTIONS]
3060 // RESTRICTIONS is a string containing: P H R C V T A
3061 std::istringstream piss( line );
3062 std::string shape_type;
3063 int corners = 0;
3064 double width = 0;
3065 int piece_flags = 0;
3066 int level = 0;
3067 std::string restrictions;
3068 piss >> shape_type >> corners >> width >> piece_flags >> level >> restrictions;
3069
3070 if( level > 0 )
3071 keepout.layers.push_back( level );
3072
3073 // Parse restriction codes if present
3074 // Per PADS spec: P=Placement, H=Height, R=Trace/copper, C=Copper pour,
3075 // V=Via/jumper, T=Test point, A=Accordion
3076 // Only override defaults from type name if explicit restrictions are specified
3077 if( !restrictions.empty() )
3078 {
3079 // Check if this looks like a restriction code string (contains letters)
3080 bool has_restriction_codes = false;
3081
3082 for( char c : restrictions )
3083 {
3084 if( std::isalpha( c ) )
3085 {
3086 has_restriction_codes = true;
3087 break;
3088 }
3089 }
3090
3091 if( has_restriction_codes )
3092 {
3093 // Clear all defaults and set based on explicit restriction codes
3094 keepout.no_traces = false;
3095 keepout.no_vias = false;
3096 keepout.no_copper = false;
3097 keepout.no_components = false;
3098 keepout.height_restriction = false;
3099 keepout.no_test_points = false;
3100 keepout.no_accordion = false;
3101
3102 for( char c : restrictions )
3103 {
3104 switch( c )
3105 {
3106 case 'P':
3107 keepout.no_components = true;
3108 break;
3109
3110 case 'H':
3111 keepout.height_restriction = true;
3112 keepout.max_height = width;
3113 break;
3114
3115 case 'R':
3116 keepout.no_traces = true;
3117 break;
3118
3119 case 'C':
3120 keepout.no_copper = true;
3121 break;
3122
3123 case 'V':
3124 keepout.no_vias = true;
3125 break;
3126
3127 case 'T':
3128 keepout.no_test_points = true;
3129 break;
3130
3131 case 'A':
3132 keepout.no_accordion = true;
3133 break;
3134
3135 default:
3136 break;
3137 }
3138 }
3139 }
3140 }
3141
3142 // Handle KPTCIR (circle keepout) differently from KPTCLS (polygon keepout)
3143 if( shape_type == "KPTCIR" )
3144 {
3145 // Circle format: 2 coordinates define opposite ends of diameter
3146 double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
3147
3148 if( readLine( aStream, line ) )
3149 {
3150 std::istringstream c1( line );
3151 c1 >> x1 >> y1;
3152 }
3153
3154 if( corners >= 2 && readLine( aStream, line ) )
3155 {
3156 std::istringstream c2( line );
3157 c2 >> x2 >> y2;
3158 }
3159
3160 // Calculate center and radius from diameter endpoints
3161 double cx = xloc + ( x1 + x2 ) / 2.0;
3162 double cy = yloc + ( y1 + y2 ) / 2.0;
3163 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 ) +
3164 ( y2 - y1 ) * ( y2 - y1 ) ) / 2.0;
3165
3166 // Create full circle arc for keepout outline
3167 ARC arc{};
3168 arc.cx = cx;
3169 arc.cy = cy;
3170 arc.radius = radius;
3171 arc.start_angle = 0.0;
3172 arc.delta_angle = 360.0;
3173
3174 keepout.outline.emplace_back( cx + radius, cy, arc );
3175 }
3176 else
3177 {
3178 // KPTCLS or other polygon piece types
3179 for( int j = 0; j < corners; ++j )
3180 {
3181 if( !readLine( aStream, line ) )
3182 break;
3183
3184 if( line[0] == '*' )
3185 {
3186 pushBackLine( line );
3187 break;
3188 }
3189
3190 std::istringstream ciss( line );
3191 double dx = 0.0, dy = 0.0;
3192 ciss >> dx >> dy;
3193
3194 // Per PADS spec, arc format is: x1 y1 ab aa ax1 ay1 ax2 ay2
3195 // where x1,y1 = arc start point; center = bounding box midpoint
3196 int startAngleTenths = 0, deltaAngleTenths = 0;
3197 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
3198
3199 if( ciss >> startAngleTenths >> deltaAngleTenths
3200 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
3201 {
3202 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
3203 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
3204 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
3205 double startAngle = startAngleTenths / 10.0;
3206 double deltaAngle = deltaAngleTenths / 10.0;
3207
3208 double startAngleRad = startAngle * M_PI / 180.0;
3209 double startX = cx + radius * std::cos( startAngleRad );
3210 double startY = cy + radius * std::sin( startAngleRad );
3211
3212 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
3213 double endX = cx + radius * std::cos( endAngleRad );
3214 double endY = cy + radius * std::sin( endAngleRad );
3215
3216 keepout.outline.emplace_back( xloc + startX, yloc + startY );
3217
3218 ARC arc{};
3219 arc.cx = xloc + cx;
3220 arc.cy = yloc + cy;
3221 arc.radius = radius;
3222 arc.start_angle = startAngle;
3223 arc.delta_angle = deltaAngle;
3224
3225 keepout.outline.emplace_back( xloc + endX, yloc + endY, arc );
3226 }
3227 else
3228 {
3229 keepout.outline.emplace_back( xloc + dx, yloc + dy );
3230 }
3231 }
3232 }
3233 }
3234
3235 if( !keepout.outline.empty() )
3236 m_keepouts.push_back( keepout );
3237 }
3238 else if( type == "COPPER" || type == "COPCUT" )
3239 {
3240 // Parse copper shape definition
3241 // Header already parsed: name type xloc yloc pieces flags [text [signame]]
3242 // signame was parsed earlier if present
3243
3244 for( int i = 0; i < pieces; ++i )
3245 {
3246 if( !readLine( aStream, line ) )
3247 break;
3248
3249 if( line[0] == '*' )
3250 {
3251 pushBackLine( line );
3252 return;
3253 }
3254
3255 // Piece format: PIECETYPE CORNERS WIDTH FLAGS LEVEL
3256 // PIECETYPE: COPOPN (polyline), COPCLS (filled polygon), COPCIR (filled circle),
3257 // COPCUT (polygon void), COPCCO (circle void), CIRCUR (circle void for COPCUT)
3258 std::istringstream piss( line );
3259 std::string shape_type;
3260 int corners = 0;
3261 double width = 0;
3262 int piece_flags = 0;
3263 int level = 0;
3264 piss >> shape_type >> corners >> width >> piece_flags >> level;
3265
3266 COPPER_SHAPE copper;
3267 copper.name = name;
3268 copper.layer = level;
3269 copper.width = width;
3270 copper.net_name = signame;
3271
3272 copper.filled = ( shape_type == "COPCLS" || shape_type == "COPCIR" );
3273 copper.is_cutout = ( shape_type == "COPCUT" || shape_type == "COPCCO" ||
3274 shape_type == "CIRCUR" || type == "COPCUT" );
3275
3276 // Handle circle shapes specially
3277 if( shape_type == "COPCIR" || shape_type == "COPCCO" || shape_type == "CIRCUR" )
3278 {
3279 // Circle: 2 coordinates define opposite ends of diameter
3280 double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
3281
3282 if( readLine( aStream, line ) )
3283 {
3284 std::istringstream c1( line );
3285 c1 >> x1 >> y1;
3286 }
3287
3288 if( corners >= 2 && readLine( aStream, line ) )
3289 {
3290 std::istringstream c2( line );
3291 c2 >> x2 >> y2;
3292 }
3293
3294 double cx = xloc + ( x1 + x2 ) / 2.0;
3295 double cy = yloc + ( y1 + y2 ) / 2.0;
3296 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 ) +
3297 ( y2 - y1 ) * ( y2 - y1 ) ) / 2.0;
3298
3299 ARC arc{};
3300 arc.cx = cx;
3301 arc.cy = cy;
3302 arc.radius = radius;
3303 arc.start_angle = 0.0;
3304 arc.delta_angle = 360.0;
3305
3306 copper.outline.emplace_back( cx + radius, cy, arc );
3307 }
3308 else
3309 {
3310 // COPOPN, COPCLS, COPCUT - polygon shapes
3311 for( int j = 0; j < corners; ++j )
3312 {
3313 if( !readLine( aStream, line ) )
3314 break;
3315
3316 if( line[0] == '*' )
3317 {
3318 pushBackLine( line );
3319 break;
3320 }
3321
3322 std::istringstream ciss( line );
3323 double dx = 0.0, dy = 0.0;
3324 ciss >> dx >> dy;
3325
3326 // Per PADS spec, arc format is: x1 y1 ab aa ax1 ay1 ax2 ay2
3327 // where x1,y1 = arc start point; center = bounding box midpoint
3328 int startAngleTenths = 0, deltaAngleTenths = 0;
3329 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
3330
3331 if( ciss >> startAngleTenths >> deltaAngleTenths
3332 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
3333 {
3334 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
3335 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
3336 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
3337 double startAngle = startAngleTenths / 10.0;
3338 double deltaAngle = deltaAngleTenths / 10.0;
3339
3340 double startAngleRad = startAngle * M_PI / 180.0;
3341 double startX = cx + radius * std::cos( startAngleRad );
3342 double startY = cy + radius * std::sin( startAngleRad );
3343
3344 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
3345 double endX = cx + radius * std::cos( endAngleRad );
3346 double endY = cy + radius * std::sin( endAngleRad );
3347
3348 copper.outline.emplace_back( xloc + startX, yloc + startY );
3349
3350 ARC arc{};
3351 arc.cx = xloc + cx;
3352 arc.cy = yloc + cy;
3353 arc.radius = radius;
3354 arc.start_angle = startAngle;
3355 arc.delta_angle = deltaAngle;
3356
3357 copper.outline.emplace_back( xloc + endX, yloc + endY, arc );
3358 }
3359 else
3360 {
3361 copper.outline.emplace_back( xloc + dx, yloc + dy );
3362 }
3363 }
3364 }
3365
3366 if( !copper.outline.empty() )
3367 m_copper_shapes.push_back( copper );
3368 }
3369 }
3370 else if( type == "LINES" )
3371 {
3372 // Generic 2D graphic lines (non-dimension LINES items)
3373 for( int i = 0; i < pieces; ++i )
3374 {
3375 if( !readLine( aStream, line ) )
3376 break;
3377
3378 if( line[0] == '*' )
3379 {
3380 pushBackLine( line );
3381 return;
3382 }
3383
3384 // Piece format: PIECETYPE CORNERS WIDTH FLAGS LEVEL
3385 std::istringstream piss( line );
3386 std::string shape_type;
3387 int corners = 0;
3388 double width = 0;
3389 int piece_flags = 0;
3390 int level = 0;
3391 piss >> shape_type >> corners >> width >> piece_flags >> level;
3392
3393 GRAPHIC_LINE graphic;
3394 graphic.name = name;
3395 graphic.layer = level;
3396 graphic.width = width;
3397 graphic.reuse_instance = reuse_instance;
3398
3399 // Determine if closed based on shape type
3400 graphic.closed = ( shape_type == "CLOSED" || shape_type == "CIRCLE" );
3401
3402 if( shape_type == "CIRCLE" )
3403 {
3404 // Circle: 2 coordinates define opposite ends of diameter
3405 double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
3406
3407 if( readLine( aStream, line ) )
3408 {
3409 std::istringstream c1( line );
3410 c1 >> x1 >> y1;
3411 }
3412
3413 if( corners >= 2 && readLine( aStream, line ) )
3414 {
3415 std::istringstream c2( line );
3416 c2 >> x2 >> y2;
3417 }
3418
3419 double cx = xloc + ( x1 + x2 ) / 2.0;
3420 double cy = yloc + ( y1 + y2 ) / 2.0;
3421 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 ) +
3422 ( y2 - y1 ) * ( y2 - y1 ) ) / 2.0;
3423
3424 ARC arc{};
3425 arc.cx = cx;
3426 arc.cy = cy;
3427 arc.radius = radius;
3428 arc.start_angle = 0.0;
3429 arc.delta_angle = 360.0;
3430
3431 graphic.points.emplace_back( cx + radius, cy, arc );
3432 }
3433 else
3434 {
3435 // OPEN or CLOSED polyline
3436 for( int j = 0; j < corners; ++j )
3437 {
3438 if( !readLine( aStream, line ) )
3439 break;
3440
3441 if( line[0] == '*' )
3442 {
3443 pushBackLine( line );
3444 break;
3445 }
3446
3447 std::istringstream ciss( line );
3448 double dx = 0.0, dy = 0.0;
3449 ciss >> dx >> dy;
3450
3451 // Check for arc parameters
3452 int startAngleTenths = 0, deltaAngleTenths = 0;
3453 double bboxMinX = 0.0, bboxMinY = 0.0, bboxMaxX = 0.0, bboxMaxY = 0.0;
3454
3455 if( ciss >> startAngleTenths >> deltaAngleTenths
3456 >> bboxMinX >> bboxMinY >> bboxMaxX >> bboxMaxY )
3457 {
3458 double cx = ( bboxMinX + bboxMaxX ) / 2.0;
3459 double cy = ( bboxMinY + bboxMaxY ) / 2.0;
3460 double radius = ( bboxMaxX - bboxMinX ) / 2.0;
3461 double startAngle = startAngleTenths / 10.0;
3462 double deltaAngle = deltaAngleTenths / 10.0;
3463
3464 double startAngleRad = startAngle * M_PI / 180.0;
3465 double startX = cx + radius * std::cos( startAngleRad );
3466 double startY = cy + radius * std::sin( startAngleRad );
3467
3468 double endAngleRad = ( startAngle + deltaAngle ) * M_PI / 180.0;
3469 double endX = cx + radius * std::cos( endAngleRad );
3470 double endY = cy + radius * std::sin( endAngleRad );
3471
3472 graphic.points.emplace_back( xloc + startX, yloc + startY );
3473
3474 ARC arc{};
3475 arc.cx = xloc + cx;
3476 arc.cy = yloc + cy;
3477 arc.radius = radius;
3478 arc.start_angle = startAngle;
3479 arc.delta_angle = deltaAngle;
3480
3481 graphic.points.emplace_back( xloc + endX, yloc + endY, arc );
3482 }
3483 else
3484 {
3485 graphic.points.emplace_back( xloc + dx, yloc + dy );
3486 }
3487 }
3488 }
3489
3490 if( !graphic.points.empty() )
3491 m_graphic_lines.push_back( graphic );
3492 }
3493 }
3494 else
3495 {
3496 // Skip unknown types
3497 for( int i = 0; i < pieces; ++i )
3498 {
3499 if( !readLine( aStream, line ) )
3500 break;
3501
3502 if( line[0] == '*' )
3503 {
3504 pushBackLine( line );
3505 return;
3506 }
3507
3508 std::istringstream piss( line );
3509 std::string shape_type;
3510 int corners = 0;
3511 piss >> shape_type >> corners;
3512
3513 for( int j = 0; j < corners; ++j )
3514 {
3515 if( !readLine( aStream, line ) )
3516 break;
3517
3518 if( line[0] == '*' )
3519 {
3520 pushBackLine( line );
3521 return;
3522 }
3523 }
3524 }
3525 }
3526
3527 // Parse text items that follow the pieces (3 lines each: properties, font, content).
3528 // Text coordinates are relative to the drawing item origin.
3529 for( int t = 0; t < textCount; ++t )
3530 {
3531 if( !readLine( aStream, line ) )
3532 break;
3533
3534 if( line[0] == '*' )
3535 {
3536 pushBackLine( line );
3537 return;
3538 }
3539
3540 std::istringstream tiss( line );
3541 TEXT text;
3542
3543 tiss >> text.location.x >> text.location.y >> text.rotation >> text.layer
3544 >> text.height >> text.width;
3545
3546 if( tiss.fail() )
3547 {
3548 // Consume remaining 2 lines and continue
3549 readLine( aStream, line );
3550 readLine( aStream, line );
3551 continue;
3552 }
3553
3554 text.location.x += xloc;
3555 text.location.y += yloc;
3556
3557 std::string mirrored;
3558 tiss >> mirrored;
3559 text.mirrored = ( mirrored == "M" );
3560 tiss >> text.hjust >> text.vjust;
3561
3562 // Font line
3563 if( !readLine( aStream, line ) )
3564 break;
3565
3566 if( line[0] == '*' )
3567 {
3568 pushBackLine( line );
3569 return;
3570 }
3571
3572 size_t bracket_start = line.find( '<' );
3573 size_t bracket_end = line.find( '>' );
3574
3575 if( bracket_start != std::string::npos && bracket_end != std::string::npos )
3576 text.font_face = line.substr( bracket_start + 1, bracket_end - bracket_start - 1 );
3577
3578 std::istringstream fiss( line );
3579 std::string font_style_part;
3580 fiss >> font_style_part;
3581
3582 size_t colon_pos = font_style_part.find( ':' );
3583
3584 if( colon_pos != std::string::npos )
3585 text.font_style = font_style_part.substr( 0, colon_pos );
3586 else
3587 text.font_style = font_style_part;
3588
3589 // Content line
3590 if( !readLine( aStream, line ) )
3591 break;
3592
3593 if( line[0] == '*' )
3594 {
3595 pushBackLine( line );
3596 return;
3597 }
3598
3599 text.content = line;
3600 m_texts.push_back( text );
3601 }
3602 }
3603}
3604
3605void PARSER::parseSectionPARTTYPE( std::ifstream& aStream )
3606{
3607 std::string line;
3608 PART_TYPE* currentPartType = nullptr;
3609 GATE_DEF* currentGate = nullptr;
3610
3611 // Helper to parse pin electrical type character
3612 auto parsePinElecType = []( char c ) -> PIN_ELEC_TYPE {
3613 switch( c )
3614 {
3615 case 'S': return PIN_ELEC_TYPE::SOURCE;
3616 case 'B': return PIN_ELEC_TYPE::BIDIRECTIONAL;
3617 case 'C': return PIN_ELEC_TYPE::OPEN_COLLECTOR;
3618 case 'T': return PIN_ELEC_TYPE::TRISTATE;
3619 case 'L': return PIN_ELEC_TYPE::LOAD;
3620 case 'Z': return PIN_ELEC_TYPE::TERMINATOR;
3621 case 'P': return PIN_ELEC_TYPE::POWER;
3622 case 'G': return PIN_ELEC_TYPE::GROUND;
3623 default: return PIN_ELEC_TYPE::UNDEFINED;
3624 }
3625 };
3626
3627 while( readLine( aStream, line ) )
3628 {
3629 if( line[0] == '*' )
3630 {
3631 pushBackLine( line );
3632 break;
3633 }
3634
3635 if( line.empty() )
3636 continue;
3637
3638 // Gate line: G gateswap pins
3639 if( line.rfind( "G ", 0 ) == 0 && currentPartType )
3640 {
3641 std::istringstream gss( line );
3642 std::string g_keyword;
3643 int gateSwap = 0, pinCount = 0;
3644 gss >> g_keyword >> gateSwap >> pinCount;
3645
3646 GATE_DEF gate;
3647 gate.gate_swap_type = gateSwap;
3648 currentPartType->gates.push_back( gate );
3649 currentGate = &currentPartType->gates.back();
3650 continue;
3651 }
3652
3653 // SIGPIN pinno width signm
3654 if( line.rfind( "SIGPIN", 0 ) == 0 && currentPartType )
3655 {
3656 std::istringstream sss( line );
3657 std::string keyword;
3658 SIGPIN sigpin;
3659
3660 sss >> keyword >> sigpin.pin_number >> sigpin.width >> sigpin.signal_name;
3661
3662 if( !sigpin.pin_number.empty() )
3663 currentPartType->signal_pins.push_back( sigpin );
3664
3665 continue;
3666 }
3667
3668 // Check if this line contains pin definitions (format: pinnumber.swptyp.pintyp[.funcname])
3669 // These follow a gate definition. Pin definition tokens have at least 3 dot-separated parts.
3670 // Part type header lines may also contain dots in the name (e.g., "CAPSMT0.1UF0402X7R50V")
3671 // but their first token won't have 3+ parts, so we check for that.
3672 if( line.find( '.' ) != std::string::npos && currentPartType )
3673 {
3674 // First check if this could be a part type header line with a dot in the name.
3675 // Part type headers have format: NAME DECAL CLASS ATTRS ... where NAME may contain dots
3676 // but the first dot-separated segment will have <3 parts.
3677 std::stringstream check_ss( line );
3678 std::string first_token;
3679 check_ss >> first_token;
3680
3681 int dot_count = 0;
3682
3683 for( char c : first_token )
3684 {
3685 if( c == '.' )
3686 dot_count++;
3687 }
3688
3689 // If first token has <2 dots, this could be a part type header, not a pin definition
3690 if( dot_count < 2 )
3691 {
3692 // Fall through to part type header parsing below
3693 }
3694 else
3695 {
3696 std::stringstream ss( line );
3697 std::string token;
3698
3699 while( ss >> token )
3700 {
3701 // Parse pin format: PINNAME.SWAPTYPE.PINTYPE[.FUNCNAME] or PINNAME.PADINDEX.TYPE.NET
3702 std::vector<std::string> parts;
3703 size_t start = 0;
3704 size_t pos = 0;
3705
3706 while( ( pos = token.find( '.', start ) ) != std::string::npos )
3707 {
3708 parts.push_back( token.substr( start, pos - start ) );
3709 start = pos + 1;
3710 }
3711
3712 parts.push_back( token.substr( start ) );
3713
3714 if( parts.size() >= 3 )
3715 {
3716 // Check if this is a gate pin definition or pad stack mapping
3717 // Gate pin: pinnumber.swaptype.pintype[.funcname]
3718 // Pad map: pinname.padindex.type.netname
3719
3720 bool isNumericSecond = !parts[1].empty() &&
3721 std::all_of( parts[1].begin(), parts[1].end(), ::isdigit );
3722
3723 if( currentGate && parts[2].size() == 1 && !isNumericSecond )
3724 {
3725 // This looks like a gate pin definition
3726 GATE_PIN gpin;
3727 gpin.pin_number = parts[0];
3728 gpin.swap_type = PADS_COMMON::ParseInt( parts[1], 0, "gate pin swap" );
3729
3730 if( !parts[2].empty() )
3731 gpin.elec_type = parsePinElecType( parts[2][0] );
3732
3733 if( parts.size() >= 4 )
3734 gpin.func_name = parts[3];
3735
3736 currentGate->pins.push_back( gpin );
3737 }
3738 else if( isNumericSecond )
3739 {
3740 int padIdx = PADS_COMMON::ParseInt( parts[1], -1, "pad index" );
3741
3742 if( padIdx >= 0 )
3743 currentPartType->pin_pad_map[parts[0]] = padIdx;
3744 }
3745 }
3746 }
3747
3748 continue;
3749 }
3750 }
3751
3752 // Attribute block enclosed in braces
3753 if( line[0] == '{' && currentPartType )
3754 {
3755 while( readLine( aStream, line ) )
3756 {
3757 if( line.empty() || line[0] == '}' )
3758 break;
3759
3760 if( line[0] == '*' )
3761 {
3762 pushBackLine( line );
3763 return;
3764 }
3765
3766 std::string attrName, attrValue;
3767
3768 if( line[0] == '"' )
3769 {
3770 size_t endQuote = line.find( '"', 1 );
3771
3772 if( endQuote != std::string::npos )
3773 {
3774 attrName = line.substr( 1, endQuote - 1 );
3775 attrValue = line.substr( endQuote + 1 );
3776 }
3777 }
3778 else
3779 {
3780 std::istringstream attrSS( line );
3781 attrSS >> attrName;
3782 std::getline( attrSS >> std::ws, attrValue );
3783 }
3784
3785 if( !attrValue.empty() && attrValue[0] == ' ' )
3786 attrValue = attrValue.substr( 1 );
3787
3788 if( !attrName.empty() && !attrValue.empty() )
3789 currentPartType->attributes[attrName] = attrValue;
3790 }
3791
3792 continue;
3793 }
3794
3795 if( line[0] == '{' || line[0] == '}' )
3796 continue;
3797
3798 // Part type definition line: NAME DECAL CLASS ATTRS GATES SIGS PINSEQ STATE
3799 std::stringstream ss( line );
3800 std::string name, decal;
3801 ss >> name >> decal;
3802
3803 if( !name.empty() && name[0] != 'G' )
3804 {
3805 PART_TYPE pt;
3806 pt.name = name;
3807 pt.decal_name = decal;
3808 m_part_types[name] = pt;
3809 currentPartType = &m_part_types[name];
3810 currentGate = nullptr;
3811 }
3812 }
3813}
3814
3815
3816void PARSER::parseSectionREUSE( std::ifstream& aStream )
3817{
3818 std::string line;
3819 REUSE_BLOCK* currentBlock = nullptr;
3820
3821 while( readLine( aStream, line ) )
3822 {
3823 if( line[0] == '*' )
3824 {
3825 pushBackLine( line );
3826 break;
3827 }
3828
3829 std::stringstream ss( line );
3830 std::string keyword;
3831 ss >> keyword;
3832
3833 if( keyword == "TYPE" )
3834 {
3835 std::string typename_val;
3836 std::getline( ss, typename_val );
3837
3838 if( !typename_val.empty() && typename_val[0] == ' ' )
3839 typename_val = typename_val.substr( 1 );
3840
3841 REUSE_BLOCK block;
3842 block.name = typename_val;
3843 m_reuse_blocks[typename_val] = block;
3844 currentBlock = &m_reuse_blocks[typename_val];
3845 }
3846 else if( keyword == "TIMESTAMP" && currentBlock )
3847 {
3848 long timestamp = 0;
3849 ss >> timestamp;
3850 currentBlock->timestamp = timestamp;
3851 }
3852 else if( keyword == "PART_NAMING" && currentBlock )
3853 {
3854 std::string naming;
3855 std::getline( ss, naming );
3856
3857 if( !naming.empty() && naming[0] == ' ' )
3858 naming = naming.substr( 1 );
3859
3860 currentBlock->part_naming = naming;
3861 }
3862 else if( keyword == "PART" && currentBlock )
3863 {
3864 std::string partname;
3865 std::getline( ss, partname );
3866
3867 if( !partname.empty() && partname[0] == ' ' )
3868 partname = partname.substr( 1 );
3869
3870 currentBlock->part_names.push_back( partname );
3871 }
3872 else if( keyword == "NET_NAMING" && currentBlock )
3873 {
3874 std::string naming;
3875 std::getline( ss, naming );
3876
3877 if( !naming.empty() && naming[0] == ' ' )
3878 naming = naming.substr( 1 );
3879
3880 currentBlock->net_naming = naming;
3881 }
3882 else if( keyword == "NET" && currentBlock )
3883 {
3884 int merge_flag = 0;
3885 std::string netname;
3886
3887 ss >> merge_flag;
3888 std::getline( ss, netname );
3889
3890 if( !netname.empty() && netname[0] == ' ' )
3891 netname = netname.substr( 1 );
3892
3893 REUSE_NET net;
3894 net.merge = ( merge_flag == 1 );
3895 net.name = netname;
3896 currentBlock->nets.push_back( net );
3897 }
3898 else if( keyword == "REUSE" && currentBlock )
3899 {
3900 REUSE_INSTANCE instance;
3901 ss >> instance.instance_name;
3902
3903 std::string next_token;
3904 ss >> next_token;
3905
3906 if( next_token == "PREFIX" || next_token == "SUFFIX" )
3907 {
3908 std::string param;
3909 ss >> param;
3910 instance.part_naming = next_token + " " + param;
3911 ss >> next_token;
3912 }
3913 else if( next_token == "START" || next_token == "INCREMENT" )
3914 {
3915 std::string num;
3916 ss >> num;
3917 instance.part_naming = next_token + " " + num;
3918 ss >> next_token;
3919 }
3920 else if( next_token == "NEXT" )
3921 {
3922 instance.part_naming = next_token;
3923 ss >> next_token;
3924 }
3925
3926 if( next_token == "PREFIX" || next_token == "SUFFIX" )
3927 {
3928 std::string param;
3929 ss >> param;
3930 instance.net_naming = next_token + " " + param;
3931 }
3932 else if( next_token == "START" || next_token == "INCREMENT" )
3933 {
3934 std::string num;
3935 ss >> num;
3936 instance.net_naming = next_token + " " + num;
3937 }
3938 else if( next_token == "NEXT" )
3939 {
3940 instance.net_naming = next_token;
3941 }
3942
3943 std::string glued_str;
3944 ss >> instance.location.x >> instance.location.y >> instance.rotation >> glued_str;
3945
3946 instance.glued = ( glued_str == "Y" || glued_str == "YES" || glued_str == "1" );
3947 currentBlock->instances.push_back( instance );
3948 }
3949 }
3950}
3951
3952
3953void PARSER::parseSectionCLUSTER( std::ifstream& aStream )
3954{
3955 std::string line;
3956 CLUSTER* currentCluster = nullptr;
3957
3958 while( readLine( aStream, line ) )
3959 {
3960 if( line[0] == '*' )
3961 {
3962 pushBackLine( line );
3963 break;
3964 }
3965
3966 std::stringstream ss( line );
3967 std::string firstToken;
3968 ss >> firstToken;
3969
3970 // Check if this is a new cluster definition or a member line
3971 // Cluster definition format varies but typically starts with name/id
3972 if( firstToken.empty() )
3973 continue;
3974
3975 // Try parsing as cluster ID
3976 bool isNumeric = !firstToken.empty() &&
3977 std::all_of( firstToken.begin(), firstToken.end(), ::isdigit );
3978
3979 if( isNumeric )
3980 {
3981 // This could be a cluster ID starting a new cluster
3982 CLUSTER cluster;
3983 cluster.id = PADS_COMMON::ParseInt( firstToken, 0, "CLUSTER" );
3984
3985 // Read optional cluster name
3986 std::string name;
3987
3988 if( ss >> name )
3989 cluster.name = name;
3990 else
3991 cluster.name = "Cluster_" + firstToken;
3992
3993 m_clusters.push_back( cluster );
3994 currentCluster = &m_clusters.back();
3995 }
3996 else if( currentCluster )
3997 {
3998 // Could be a net name or segment reference belonging to current cluster
3999 // PADS format varies - add to net_names or segment_refs based on content
4000 if( firstToken.find( '.' ) != std::string::npos )
4001 {
4002 // Looks like a segment reference (e.g., "NET.1")
4003 currentCluster->segment_refs.push_back( firstToken );
4004 }
4005 else
4006 {
4007 // Treat as net name
4008 currentCluster->net_names.push_back( firstToken );
4009 }
4010
4011 // Continue reading additional items on the same line
4012 std::string item;
4013
4014 while( ss >> item )
4015 {
4016 if( item.find( '.' ) != std::string::npos )
4017 currentCluster->segment_refs.push_back( item );
4018 else
4019 currentCluster->net_names.push_back( item );
4020 }
4021 }
4022 }
4023}
4024
4025
4026void PARSER::parseSectionJUMPER( std::ifstream& aStream )
4027{
4028 std::string line;
4029
4030 while( readLine( aStream, line ) )
4031 {
4032 if( line[0] == '*' )
4033 {
4034 pushBackLine( line );
4035 break;
4036 }
4037
4038 // Jumper header format: name flags minlen maxlen lenincr lcount padstack [end_padstack]
4039 // flags: V=via enabled, N=no via, W=wirebond, D=display silk, G=glued
4040 std::stringstream ss( line );
4041 std::string name, flags;
4042 double minlen = 0.0, maxlen = 0.0, lenincr = 0.0;
4043 int lcount = 0;
4044 std::string padstack, end_padstack;
4045
4046 if( !( ss >> name >> flags >> minlen >> maxlen >> lenincr >> lcount >> padstack ) )
4047 continue;
4048
4049 ss >> end_padstack;
4050
4051 JUMPER_DEF jumper;
4052 jumper.name = name;
4053 jumper.min_length = minlen;
4054 jumper.max_length = maxlen;
4055 jumper.length_increment = lenincr;
4056 jumper.padstack = padstack;
4057 jumper.end_padstack = end_padstack;
4058
4059 // Parse flags
4060 for( char c : flags )
4061 {
4062 switch( c )
4063 {
4064 case 'V': jumper.via_enabled = true; break;
4065 case 'N': jumper.via_enabled = false; break;
4066 case 'W': jumper.wirebond = true; break;
4067 case 'D': jumper.display_silk = true; break;
4068 case 'G': jumper.glued = true; break;
4069 default: break;
4070 }
4071 }
4072
4073 // Parse label entries (each label is 2 lines)
4074 for( int i = 0; i < lcount; ++i )
4075 {
4076 ATTRIBUTE attr;
4077
4078 // Line 1: VISIBLE X Y ORI LEVEL HEIGHT WIDTH MIRRORED HJUST VJUST [RIGHTREADING]
4079 if( !readLine( aStream, line ) )
4080 break;
4081
4082 std::stringstream ss_attr( line );
4083 std::string visible_str, mirrored_str, right_reading_str;
4084
4085 if( ss_attr >> visible_str >> attr.x >> attr.y >> attr.orientation >> attr.level
4086 >> attr.height >> attr.width >> mirrored_str >> attr.hjust >> attr.vjust )
4087 {
4088 attr.visible = ( visible_str == "VALUE" || visible_str == "FULL_NAME" ||
4089 visible_str == "NAME" || visible_str == "FULL_BOTH" ||
4090 visible_str == "BOTH" );
4091 attr.mirrored = ( mirrored_str == "M" || mirrored_str == "1" );
4092 ss_attr >> right_reading_str;
4093 attr.right_reading = ( right_reading_str == "Y" || right_reading_str == "ORTHO" );
4094 }
4095
4096 // Line 2: Font info
4097 if( !readLine( aStream, line ) )
4098 break;
4099
4100 attr.font_info = line;
4101
4102 jumper.labels.push_back( attr );
4103 }
4104
4105 m_jumper_defs.push_back( jumper );
4106 }
4107}
4108
4109
4110void PARSER::parseSectionTESTPOINT( std::ifstream& aStream )
4111{
4112 std::string line;
4113
4114 while( readLine( aStream, line ) )
4115 {
4116 if( line[0] == '*' )
4117 {
4118 pushBackLine( line );
4119 break;
4120 }
4121
4122 std::stringstream ss( line );
4123 std::string type;
4124 ss >> type;
4125
4126 if( type.empty() )
4127 continue;
4128
4129 // Format: TYPE X Y SIDE NETNAME SYMBOLNAME
4130 // TYPE is VIA or PIN
4131 TEST_POINT tp;
4132 tp.type = type;
4133
4134 ss >> tp.x >> tp.y >> tp.side >> tp.net_name >> tp.symbol_name;
4135
4136 if( !tp.net_name.empty() )
4137 {
4138 m_test_points.push_back( tp );
4139 }
4140 }
4141}
4142
4143
4144void PARSER::parseSectionNETCLASS( std::ifstream& aStream )
4145{
4146 std::string line;
4147 NET_CLASS_DEF currentClass;
4148 bool inClass = false;
4149
4150 while( readLine( aStream, line ) )
4151 {
4152 if( line[0] == '*' )
4153 {
4154 // Save the last class if we were building one
4155 if( inClass && !currentClass.name.empty() )
4156 m_net_classes.push_back( currentClass );
4157
4158 pushBackLine( line );
4159 break;
4160 }
4161
4162 std::stringstream ss( line );
4163 std::string token;
4164 ss >> token;
4165
4166 if( token.empty() )
4167 continue;
4168
4169 // Check for class name definition (typically first token without a keyword)
4170 if( token == "CLASS" || token == "NETCLASS" )
4171 {
4172 // Save previous class if any
4173 if( inClass && !currentClass.name.empty() )
4174 m_net_classes.push_back( currentClass );
4175
4176 // Start new class
4177 currentClass = NET_CLASS_DEF();
4178 ss >> currentClass.name;
4179 inClass = true;
4180 }
4181 else if( token == "CLEARANCE" && inClass )
4182 {
4183 ss >> currentClass.clearance;
4184 }
4185 else if( token == "TRACKWIDTH" && inClass )
4186 {
4187 ss >> currentClass.track_width;
4188 }
4189 else if( token == "VIASIZE" && inClass )
4190 {
4191 ss >> currentClass.via_size;
4192 }
4193 else if( token == "VIADRILL" && inClass )
4194 {
4195 ss >> currentClass.via_drill;
4196 }
4197 else if( token == "DIFFPAIRGAP" && inClass )
4198 {
4199 ss >> currentClass.diff_pair_gap;
4200 }
4201 else if( token == "DIFFPAIRWIDTH" && inClass )
4202 {
4203 ss >> currentClass.diff_pair_width;
4204 }
4205 else if( token == "NET" && inClass )
4206 {
4207 // Net assignment: NET netname
4208 std::string netName;
4209 ss >> netName;
4210
4211 if( !netName.empty() )
4212 currentClass.net_names.push_back( netName );
4213 }
4214 else if( !token.empty() && token[0] != '#' )
4215 {
4216 // Check if this looks like a class name (no keyword prefix) in some formats
4217 if( !inClass || ( inClass && currentClass.name.empty() ) )
4218 {
4219 // Save previous if any
4220 if( inClass && !currentClass.name.empty() )
4221 m_net_classes.push_back( currentClass );
4222
4223 currentClass = NET_CLASS_DEF();
4224 currentClass.name = token;
4225 inClass = true;
4226 }
4227 }
4228 }
4229
4230 // Save final class
4231 if( inClass && !currentClass.name.empty() )
4232 m_net_classes.push_back( currentClass );
4233}
4234
4235
4236void PARSER::parseSectionDIFFPAIR( std::ifstream& aStream )
4237{
4238 std::string line;
4239
4240 while( readLine( aStream, line ) )
4241 {
4242 if( line[0] == '*' )
4243 {
4244 pushBackLine( line );
4245 break;
4246 }
4247
4248 std::stringstream ss( line );
4249 std::string token;
4250 ss >> token;
4251
4252 if( token.empty() )
4253 continue;
4254
4255 // Differential pair format can vary. Common patterns:
4256 // DIFFPAIR name positive_net negative_net gap width
4257 // or keyword-based like:
4258 // PAIR name
4259 // POS positive_net
4260 // NEG negative_net
4261 // GAP value
4262 // WIDTH value
4263
4264 if( token == "DIFFPAIR" || token == "PAIR" )
4265 {
4266 DIFF_PAIR_DEF dp;
4267 ss >> dp.name;
4268
4269 // Try to read the nets inline
4270 std::string posNet, negNet;
4271 ss >> posNet >> negNet;
4272
4273 if( !posNet.empty() )
4274 dp.positive_net = posNet;
4275
4276 if( !negNet.empty() )
4277 dp.negative_net = negNet;
4278
4279 // Try to read gap and width inline
4280 double gap = 0.0, width = 0.0;
4281
4282 if( ss >> gap )
4283 dp.gap = gap;
4284
4285 if( ss >> width )
4286 dp.width = width;
4287
4288 if( !dp.name.empty() )
4289 m_diff_pairs.push_back( dp );
4290 }
4291 else if( token == "POS" && !m_diff_pairs.empty() )
4292 {
4293 ss >> m_diff_pairs.back().positive_net;
4294 }
4295 else if( token == "NEG" && !m_diff_pairs.empty() )
4296 {
4297 ss >> m_diff_pairs.back().negative_net;
4298 }
4299 else if( token == "GAP" && !m_diff_pairs.empty() )
4300 {
4301 ss >> m_diff_pairs.back().gap;
4302 }
4303 else if( ( token == "WIDTH" || token == "TRACKWIDTH" ) && !m_diff_pairs.empty() )
4304 {
4305 ss >> m_diff_pairs.back().width;
4306 }
4307 }
4308}
4309
4310
4311void PARSER::parseSectionLAYERDEFS( std::ifstream& aStream )
4312{
4313 std::string line;
4314 int braceDepth = 0;
4315 int currentLayerNum = -1;
4316 LAYER_INFO currentLayer;
4317 bool inLayerBlock = false;
4318
4319 // Helper to parse LAYER_TYPE string to enum
4320 auto parseLayerType = []( const std::string& typeStr ) -> PADS_LAYER_FUNCTION {
4321 if( typeStr == "ROUTING" )
4323 else if( typeStr == "PLANE" )
4325 else if( typeStr == "MIXED" )
4327 else if( typeStr == "UNASSIGNED" )
4329 else if( typeStr == "SOLDER_MASK" )
4331 else if( typeStr == "PASTE_MASK" )
4333 else if( typeStr == "SILK_SCREEN" )
4335 else if( typeStr == "ASSEMBLY" )
4337 else if( typeStr == "DOCUMENTATION" )
4339 else if( typeStr == "DRILL" )
4342 };
4343
4344 while( readLine( aStream, line ) )
4345 {
4346 if( line.empty() )
4347 continue;
4348
4349 // Stop if we hit a new section marker
4350 if( line[0] == '*' )
4351 {
4352 pushBackLine( line );
4353 break;
4354 }
4355
4356 std::istringstream iss( line );
4357 std::string token;
4358 iss >> token;
4359
4360 if( token == "{" )
4361 {
4362 braceDepth++;
4363 continue;
4364 }
4365
4366 if( token == "}" )
4367 {
4368 braceDepth--;
4369
4370 // Closing a layer block, save if we have valid data
4371 if( inLayerBlock && braceDepth == 1 )
4372 {
4373 if( currentLayerNum >= 0 )
4374 {
4375 currentLayer.number = currentLayerNum;
4376
4377 // Determine if copper based on layer number and type
4378 currentLayer.is_copper = ( currentLayer.layer_type == PADS_LAYER_FUNCTION::ROUTING ||
4379 currentLayer.layer_type == PADS_LAYER_FUNCTION::PLANE ||
4380 currentLayer.layer_type == PADS_LAYER_FUNCTION::MIXED );
4381 currentLayer.required = currentLayer.is_copper;
4382 m_layer_defs[currentLayerNum] = currentLayer;
4383 }
4384
4385 inLayerBlock = false;
4386 currentLayerNum = -1;
4387 }
4388
4389 // Exiting the outer LAYER block
4390 if( braceDepth <= 0 )
4391 break;
4392
4393 continue;
4394 }
4395
4396 if( token == "LAYER" )
4397 {
4398 int layerNum = -1;
4399 iss >> layerNum;
4400
4401 if( !iss.fail() && layerNum >= 0 )
4402 {
4403 // Starting a new layer definition
4404 currentLayerNum = layerNum;
4405 currentLayer = LAYER_INFO();
4406 currentLayer.number = layerNum;
4408 inLayerBlock = true;
4409 }
4410 }
4411 else if( token == "LAYER_NAME" && inLayerBlock )
4412 {
4413 // Read the rest of the line as the layer name
4414 std::string name;
4415 std::getline( iss >> std::ws, name );
4416 currentLayer.name = name;
4417 }
4418 else if( token == "LAYER_TYPE" && inLayerBlock )
4419 {
4420 std::string typeStr;
4421 iss >> typeStr;
4422 currentLayer.layer_type = parseLayerType( typeStr );
4423 }
4424 else if( token == "LAYER_THICKNESS" && inLayerBlock )
4425 {
4426 iss >> currentLayer.layer_thickness;
4427 }
4428 else if( token == "COPPER_THICKNESS" && inLayerBlock )
4429 {
4430 iss >> currentLayer.copper_thickness;
4431 }
4432 else if( token == "DIELECTRIC" && inLayerBlock )
4433 {
4434 iss >> currentLayer.dielectric_constant;
4435 }
4436 }
4437}
4438
4439
4440void PARSER::parseSectionMISC( std::ifstream& aStream )
4441{
4442 // The MISC section contains various optional data:
4443 // - NET_CLASS DATA (net class definitions with member nets)
4444 // - GROUP DATA (pin pair groups)
4445 // - ASSOCIATED NET DATA (associated net pairs)
4446 // - DIF_PAIR definitions with extended parameters
4447 // - DESIGN_RULES / RULE_SET (hierarchical design rules)
4448 // - ATTRIBUTES DICTIONARY (attribute type definitions)
4449 //
4450 // We parse DIF_PAIR and NET_CLASS definitions as they're most relevant for KiCad.
4451
4452 std::string line;
4453 int braceDepth = 0;
4454 bool inDifPair = false;
4455 bool inNetClassData = false;
4456 bool inNetClass = false;
4457 bool inRuleSet = false;
4458 bool inRuleSetFor = false;
4459 bool inClearanceRule = false;
4460 int ruleSetDepth = -1;
4461 int clearanceRuleDepth = -1;
4462 bool foundDefaultRules = false;
4463 bool isDefaultRuleSet = false;
4464 std::string ruleSetNetClass;
4465 DIFF_PAIR_DEF currentDiffPair;
4466 NET_CLASS_DEF currentNetClass;
4467
4468 while( readLine( aStream, line ) )
4469 {
4470 if( line.empty() )
4471 continue;
4472
4473 // Stop at next section marker
4474 if( line[0] == '*' && braceDepth == 0 )
4475 {
4476 pushBackLine( line );
4477 break;
4478 }
4479
4480 // Track brace depth for nested structures
4481 for( char c : line )
4482 {
4483 if( c == '{' )
4484 braceDepth++;
4485 else if( c == '}' )
4486 {
4487 braceDepth--;
4488
4489 if( braceDepth == 0 && inDifPair )
4490 {
4491 // End of DIF_PAIR block
4492 if( !currentDiffPair.name.empty() )
4493 m_diff_pairs.push_back( currentDiffPair );
4494
4495 inDifPair = false;
4496 currentDiffPair = DIFF_PAIR_DEF();
4497 }
4498
4499 if( braceDepth == 1 && inNetClass )
4500 {
4501 // End of NET_CLASS block (inside NET_CLASS DATA)
4502 if( !currentNetClass.name.empty() )
4503 m_net_classes.push_back( currentNetClass );
4504
4505 inNetClass = false;
4506 currentNetClass = NET_CLASS_DEF();
4507 }
4508
4509 if( braceDepth == 0 && inNetClassData )
4510 {
4511 // End of NET_CLASS DATA block
4512 inNetClassData = false;
4513 }
4514
4515 if( inClearanceRule && braceDepth < clearanceRuleDepth )
4516 {
4517 inClearanceRule = false;
4518
4519 if( isDefaultRuleSet )
4520 {
4521 if( m_design_rules.default_clearance
4522 == std::numeric_limits<double>::max() )
4523 {
4524 m_design_rules.default_clearance =
4525 DESIGN_RULES().default_clearance;
4526 }
4527
4528 m_design_rules.min_clearance = m_design_rules.default_clearance;
4529
4530 if( m_design_rules.copper_edge_clearance
4531 == std::numeric_limits<double>::max() )
4532 {
4533 m_design_rules.copper_edge_clearance =
4534 m_design_rules.default_clearance;
4535 }
4536
4537 foundDefaultRules = true;
4538 }
4539 }
4540
4541 if( inRuleSetFor && braceDepth < ruleSetDepth + 1 )
4542 inRuleSetFor = false;
4543
4544 if( inRuleSet && braceDepth < ruleSetDepth )
4545 {
4546 inRuleSet = false;
4547 isDefaultRuleSet = false;
4548 ruleSetNetClass.clear();
4549 }
4550 }
4551 }
4552
4553 std::istringstream iss( line );
4554 std::string token;
4555 iss >> token;
4556
4557 // LAYER DATA block contains per-layer definitions (name, type, etc.)
4558 // which may appear inside *MISC* instead of as a standalone section.
4559 if( token == "LAYER" )
4560 {
4561 std::string secondToken;
4562 iss >> secondToken;
4563
4564 if( secondToken == "DATA" )
4565 {
4566 parseSectionLAYERDEFS( aStream );
4567 continue;
4568 }
4569 }
4570
4571 // Check for NET_CLASS DATA section header
4572 if( token == "NET_CLASS" )
4573 {
4574 std::string secondToken;
4575 iss >> secondToken;
4576
4577 if( secondToken == "DATA" )
4578 {
4579 // Entering NET_CLASS DATA section
4580 inNetClassData = true;
4581 }
4582 else if( inNetClassData && !secondToken.empty() )
4583 {
4584 // Starting a new NET_CLASS definition inside NET_CLASS DATA
4585 // Format: NET_CLASS class_name
4586 if( inNetClass && !currentNetClass.name.empty() )
4587 m_net_classes.push_back( currentNetClass );
4588
4589 currentNetClass = NET_CLASS_DEF();
4590 currentNetClass.name = secondToken;
4591 inNetClass = true;
4592 }
4593 }
4594 else if( inNetClass && token == "NET" )
4595 {
4596 // Add net to current net class
4597 std::string netName;
4598 iss >> netName;
4599
4600 if( !netName.empty() )
4601 currentNetClass.net_names.push_back( netName );
4602 }
4603 else if( token == "RULE_SET" )
4604 {
4605 std::string ruleNum;
4606 iss >> ruleNum;
4607
4608 inRuleSet = true;
4609 ruleSetDepth = braceDepth;
4610 ruleSetNetClass.clear();
4611 isDefaultRuleSet = ( ruleNum == "(1)" && !foundDefaultRules );
4612 }
4613 else if( inRuleSet && !inClearanceRule && token == "FOR" )
4614 {
4615 inRuleSetFor = true;
4616 }
4617 else if( inRuleSetFor && token == "NET_CLASS" )
4618 {
4619 iss >> ruleSetNetClass;
4620 }
4621 else if( inRuleSet && token == "CLEARANCE_RULE" )
4622 {
4623 inClearanceRule = true;
4624 clearanceRuleDepth = braceDepth;
4625
4626 if( isDefaultRuleSet )
4627 {
4628 m_design_rules.default_clearance = std::numeric_limits<double>::max();
4629 m_design_rules.copper_edge_clearance = std::numeric_limits<double>::max();
4630 }
4631 }
4632 else if( inClearanceRule )
4633 {
4634 double val = 0.0;
4635 iss >> val;
4636
4637 if( !iss.fail() && val > 0.0 )
4638 {
4639 if( isDefaultRuleSet )
4640 {
4641 if( token == "MIN_TRACK_WIDTH" )
4642 {
4643 m_design_rules.min_track_width = val;
4644 }
4645 else if( token == "REC_TRACK_WIDTH" )
4646 {
4647 m_design_rules.default_track_width = val;
4648 }
4649 else if( token == "DRILL_TO_DRILL" )
4650 {
4651 m_design_rules.hole_to_hole = val;
4652 }
4653 else if( token == "OUTLINE_TO_TRACK" || token == "OUTLINE_TO_VIA"
4654 || token == "OUTLINE_TO_PAD" || token == "OUTLINE_TO_COPPER"
4655 || token == "OUTLINE_TO_SMD" )
4656 {
4657 m_design_rules.copper_edge_clearance =
4658 std::min( m_design_rules.copper_edge_clearance, val );
4659 }
4660 else if( token.rfind( "SAME_NET_", 0 ) == 0 || token == "BODY_TO_BODY"
4661 || token == "MAX_TRACK_WIDTH"
4662 || token.rfind( "TEXT_TO_", 0 ) == 0
4663 || token.rfind( "COPPER_TO_", 0 ) == 0 )
4664 {
4665 // Exclude same-net spacings, physical body clearances, text
4666 // clearances, and copper-pour clearances from the inter-net
4667 // copper clearance.
4668 }
4669 else if( token == "TRACK_TO_TRACK" || token.rfind( "VIA_TO_", 0 ) == 0
4670 || token.rfind( "PAD_TO_", 0 ) == 0
4671 || token.rfind( "SMD_TO_", 0 ) == 0
4672 || token.rfind( "DRILL_TO_", 0 ) == 0 )
4673 {
4674 m_design_rules.default_clearance =
4675 std::min( m_design_rules.default_clearance, val );
4676 }
4677 }
4678 else if( !ruleSetNetClass.empty() )
4679 {
4680 for( auto& nc : m_net_classes )
4681 {
4682 if( nc.name == ruleSetNetClass )
4683 {
4684 if( token == "REC_TRACK_WIDTH" )
4685 nc.track_width = val;
4686 else if( token == "TRACK_TO_TRACK" )
4687 nc.clearance = val;
4688
4689 break;
4690 }
4691 }
4692 }
4693 }
4694 }
4695 else if( token == "DIF_PAIR" )
4696 {
4697 // Save previous diff pair if any
4698 if( inDifPair && !currentDiffPair.name.empty() )
4699 m_diff_pairs.push_back( currentDiffPair );
4700
4701 currentDiffPair = DIFF_PAIR_DEF();
4702 iss >> currentDiffPair.name;
4703 inDifPair = true;
4704 }
4705 else if( inDifPair )
4706 {
4707 if( token == "NET" )
4708 {
4709 std::string netName;
4710 iss >> netName;
4711
4712 // Assign to positive net first, then negative
4713 if( currentDiffPair.positive_net.empty() )
4714 currentDiffPair.positive_net = netName;
4715 else if( currentDiffPair.negative_net.empty() )
4716 currentDiffPair.negative_net = netName;
4717 }
4718 else if( token == "GAP" )
4719 {
4720 iss >> currentDiffPair.gap;
4721 }
4722 else if( token == "WIDTH" )
4723 {
4724 iss >> currentDiffPair.width;
4725 }
4726 else if( token == "CONNECTION" )
4727 {
4728 // CONNECTION format: ref.pin,ref.pin
4729 // This defines a pin pair for the diff pair
4730 // For now just skip - main net assignment is more important
4731 }
4732 else if( token == "ASSOCIATED" )
4733 {
4734 // ASSOCIATED NET netname - for associated net pairs
4735 std::string keyword, netName;
4736 iss >> keyword >> netName;
4737
4738 if( keyword == "NET" )
4739 {
4740 if( currentDiffPair.positive_net.empty() )
4741 currentDiffPair.positive_net = netName;
4742 else if( currentDiffPair.negative_net.empty() )
4743 currentDiffPair.negative_net = netName;
4744 }
4745 }
4746 }
4747
4748 // Parse per-instance attribute blocks: PART <refdes> { key value ... }
4749 // These appear inside ATTRIBUTE VALUES {...} at variable brace depth.
4750 if( token == "PART" && !inDifPair && !inNetClass )
4751 {
4752 std::string partName;
4753 iss >> partName;
4754
4755 if( !partName.empty() )
4756 {
4757 // Save brace depth before consuming the block's own { ... }
4758 int savedDepth = braceDepth;
4759 auto& attrs = m_part_instance_attrs[partName];
4760
4761 while( readLine( aStream, line ) )
4762 {
4763 if( line.empty() )
4764 continue;
4765
4766 if( line[0] == '}' )
4767 break;
4768
4769 if( line[0] == '{' )
4770 continue;
4771
4772 if( line[0] == '*' )
4773 {
4774 pushBackLine( line );
4776 return;
4777 }
4778
4779 std::string attrName, attrValue;
4780
4781 if( line[0] == '"' )
4782 {
4783 size_t endQuote = line.find( '"', 1 );
4784
4785 if( endQuote != std::string::npos )
4786 {
4787 attrName = line.substr( 1, endQuote - 1 );
4788 attrValue = line.substr( endQuote + 1 );
4789 }
4790 }
4791 else
4792 {
4793 std::istringstream attrSS( line );
4794 attrSS >> attrName;
4795 std::getline( attrSS >> std::ws, attrValue );
4796 }
4797
4798 if( !attrValue.empty() && attrValue[0] == ' ' )
4799 attrValue = attrValue.substr( 1 );
4800
4801 if( !attrName.empty() && !attrValue.empty() )
4802 attrs[attrName] = attrValue;
4803 }
4804
4805 // Restore to the depth before the PART block's braces
4806 braceDepth = savedDepth;
4807 }
4808
4809 continue;
4810 }
4811
4812 // Skip other MISC subsections (ATTRIBUTES DICTIONARY, DESIGN_RULES, etc.)
4813 }
4814
4816}
4817
4818
4820{
4821 DESIGN_RULES defaults;
4822
4823 if( m_design_rules.default_clearance == std::numeric_limits<double>::max() )
4824 m_design_rules.default_clearance = defaults.default_clearance;
4825
4826 if( m_design_rules.copper_edge_clearance == std::numeric_limits<double>::max() )
4827 m_design_rules.copper_edge_clearance = defaults.copper_edge_clearance;
4828}
4829
4830
4831std::vector<LAYER_INFO> PARSER::GetLayerInfos() const
4832{
4833 std::vector<LAYER_INFO> layers;
4834
4835 int layerCount = m_parameters.layer_count;
4836
4837 if( layerCount < 1 )
4838 layerCount = 2;
4839
4840 // Helper to check if a layer number is a copper layer
4841 auto isCopperLayer = [&]( int num ) {
4842 return num >= 1 && num <= layerCount;
4843 };
4844
4845 // Helper to get layer type from parsed defs or default
4846 auto getLayerDef = [&]( int num ) -> const LAYER_INFO* {
4847 auto it = m_layer_defs.find( num );
4848 return it != m_layer_defs.end() ? &it->second : nullptr;
4849 };
4850
4851 // Add copper layers with parsed info if available
4852 for( int i = 1; i <= layerCount; ++i )
4853 {
4854 const LAYER_INFO* parsed = getLayerDef( i );
4855
4856 if( parsed )
4857 {
4858 layers.push_back( *parsed );
4859 }
4860 else
4861 {
4862 // Generate default copper layer info
4864 info.number = i;
4866 info.is_copper = true;
4867 info.required = true;
4868
4869 if( i == 1 )
4870 info.name = "Top";
4871 else if( i == layerCount )
4872 info.name = "Bottom";
4873 else
4874 info.name = "Inner " + std::to_string( i - 1 );
4875
4876 layers.push_back( info );
4877 }
4878 }
4879
4880 // Add non-copper layers from parsed definitions
4881 for( const auto& [num, layerDef] : m_layer_defs )
4882 {
4883 if( !isCopperLayer( num ) )
4884 {
4885 layers.push_back( layerDef );
4886 }
4887 }
4888
4889 // If no non-copper layers were parsed, add default fallbacks
4890 if( m_layer_defs.empty() )
4891 {
4892 // Standard non-copper layers (common PADS layer numbers)
4893 layers.push_back( { 21, "Assembly Top", PADS_LAYER_FUNCTION::ASSEMBLY, false, false } );
4894 layers.push_back( { 22, "Assembly Bottom", PADS_LAYER_FUNCTION::ASSEMBLY, false, false } );
4895 layers.push_back( { 25, "Solder Mask Top", PADS_LAYER_FUNCTION::SOLDER_MASK, false, false } );
4896 layers.push_back( { 26, "Silkscreen Top", PADS_LAYER_FUNCTION::SILK_SCREEN, false, false } );
4897 layers.push_back( { 27, "Silkscreen Bottom", PADS_LAYER_FUNCTION::SILK_SCREEN, false, false } );
4898 layers.push_back( { 28, "Solder Mask Bottom", PADS_LAYER_FUNCTION::SOLDER_MASK, false, false } );
4899 layers.push_back( { 29, "Paste Top", PADS_LAYER_FUNCTION::PASTE_MASK, false, false } );
4900 layers.push_back( { 30, "Paste Bottom", PADS_LAYER_FUNCTION::PASTE_MASK, false, false } );
4901 }
4902
4903 return layers;
4904}
4905
4906} // 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