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