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