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