KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_io_pads.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2025 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "pcb_io_pads.h"
25#include "pads_layer_mapper.h"
26
27#include <algorithm>
28#include <climits>
29#include <cmath>
30#include <fstream>
31#include <functional>
32
33#include <board.h>
34#include <pcb_track.h>
35#include <pcb_text.h>
36#include <footprint.h>
37#include <zone.h>
38
39#include "pads_parser.h"
41#include <io/pads/pads_common.h>
42
43#include <netinfo.h>
44#include <wx/log.h>
45#include <wx/file.h>
46#include <wx/filename.h>
47#include <core/mirror.h>
48#include <pad.h>
49#include <pcb_shape.h>
50#include <pcb_dimension.h>
54#include <netclass.h>
55#include <geometry/eda_angle.h>
56#include <geometry/shape_arc.h>
57#include <pcb_group.h>
58#include <string_utils.h>
59#include <progress_reporter.h>
60#include <reporter.h>
61#include <advanced_config.h>
62#include <locale_io.h>
63
65{
67 std::bind( &PCB_IO_PADS::DefaultLayerMappingCallback, this, std::placeholders::_1 ) );
68}
69
70
74
75
77{
78 IO_FILE_DESC desc;
79 desc.m_FileExtensions.push_back( "asc" );
80 desc.m_Description = "PADS ASCII";
81 return desc;
82}
83
84
86{
87 // PADS ASCII doesn't really support libraries in the KiCad sense,
88 // but we must implement this.
89 return IO_FILE_DESC( "PADS ASCII Library", { "asc" } );
90}
91
92
93long long PCB_IO_PADS::GetLibraryTimestamp( const wxString& aLibraryPath ) const
94{
95 return 0;
96}
97
98
99bool PCB_IO_PADS::CanReadBoard( const wxString& aFileName ) const
100{
101 if( !PCB_IO::CanReadBoard( aFileName ) )
102 return false;
103
104 std::ifstream file( aFileName.fn_str() );
105
106 if( !file.is_open() )
107 return false;
108
109 std::string line;
110
111 if( std::getline( file, line ) )
112 {
113 if( line.find( "!PADS-" ) != std::string::npos )
114 return true;
115 }
116
117 return false;
118}
119
120
121BOARD* PCB_IO_PADS::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe,
122 const std::map<std::string, UTF8>* aProperties, PROJECT* aProject )
123{
124 LOCALE_IO setlocale;
125
126 std::unique_ptr<BOARD> board( aAppendToMe ? aAppendToMe : new BOARD() );
127
128 if( m_reporter )
129 m_reporter->Report( _( "Starting PADS PCB import" ), RPT_SEVERITY_INFO );
130
132 m_progressReporter->SetNumPhases( 4 );
133
134 PADS_IO::PARSER parser;
135
136 try
137 {
138 parser.Parse( aFileName );
139 }
140 catch( const std::exception& e )
141 {
142 THROW_IO_ERROR( wxString::Format( "Error parsing PADS file: %s", e.what() ) );
143 }
144
145 m_loadBoard = board.get();
146 m_parser = &parser;
149
150 try
151 {
153 m_progressReporter->BeginPhase( 1 );
154
156 loadNets();
157
159 m_progressReporter->BeginPhase( 2 );
160
164 loadTexts();
165
167 m_progressReporter->BeginPhase( 3 );
168
172 loadZones();
175 loadKeepouts();
177 generateDrcRules( aFileName );
179 }
180 catch( ... )
181 {
183 throw;
184 }
185
187 return board.release();
188}
189
190
192{
193 const auto& nets = m_parser->GetNets();
194
195 for( const auto& pads_net : nets )
196 ensureNet( pads_net.name );
197
198 for( const auto& pads_net : nets )
199 {
200 for( const auto& pin : pads_net.pins )
201 {
202 std::string key = pin.ref_des + "." + pin.pin_name;
203 m_pinToNetMap[key] = pads_net.name;
204 }
205 }
206
207 const auto& route_nets = m_parser->GetRoutes();
208
209 for( const auto& route : route_nets )
210 {
211 for( const auto& pin : route.pins )
212 {
213 std::string key = pin.ref_des + "." + pin.pin_name;
214
215 if( m_pinToNetMap.find( key ) == m_pinToNetMap.end() )
216 m_pinToNetMap[key] = route.net_name;
217 }
218 }
219
220 for( const auto& route : route_nets )
221 ensureNet( route.net_name );
222
223 for( const auto& pour_def : m_parser->GetPours() )
224 ensureNet( pour_def.net_name );
225
226 for( const auto& copper : m_parser->GetCopperShapes() )
227 {
228 if( !copper.net_name.empty() && IsCopperLayer( getMappedLayer( copper.layer ) ) )
229 ensureNet( copper.net_name );
230 }
231
232 const auto& reuse_blocks = m_parser->GetReuseBlocks();
233
234 for( const auto& [blockName, block] : reuse_blocks )
235 {
236 for( const std::string& partName : block.part_names )
237 {
238 m_partToBlockMap[partName] = blockName;
239 }
240 }
241}
242
243
245{
246 const auto& decals = m_parser->GetPartDecals();
247 const auto& part_types = m_parser->GetPartTypes();
248 const auto& partInstanceAttrs = m_parser->GetPartInstanceAttrs();
249 const auto& parts = m_parser->GetParts();
250
251 for( const auto& pads_part : parts )
252 {
253 FOOTPRINT* footprint = new FOOTPRINT( m_loadBoard );
254 footprint->SetReference( pads_part.name );
255
256 // Generate deterministic UUID for cross-probe linking between schematic and PCB.
257 // The schematic importer uses the same algorithm, enabling selection sync.
258 KIID symbolUuid = PADS_COMMON::GenerateDeterministicUuid( pads_part.name );
260 path.push_back( symbolUuid );
261 footprint->SetPath( path );
262
263 // Resolve Decal Name
264 std::string decal_name = pads_part.decal;
265
266 // Always resolve through part types to get the full alternate decal
267 // list. A name like "MTHOLE" can be both a decal and a part type, and
268 // the part type entry carries the colon-separated alternate list that
269 // alt_decal_index indexes into.
270 if( !pads_part.explicit_decal )
271 {
272 auto part_type_it = part_types.find( decal_name );
273
274 if( part_type_it != part_types.end() )
275 decal_name = part_type_it->second.decal_name;
276 }
277
278 // Handle Alternate Decals (separated by :)
279 // The part's alt_decal_index specifies which alternate to use (0-based).
280 std::stringstream ss( decal_name );
281 std::string segment;
282 std::vector<std::string> decal_list;
283
284 while( std::getline( ss, segment, ':' ) )
285 {
286 decal_list.push_back( segment );
287 }
288
289 std::string actual_decal_name;
290 bool found_valid_decal = false;
291
292 if( pads_part.alt_decal_index >= 0
293 && static_cast<size_t>( pads_part.alt_decal_index ) < decal_list.size() )
294 {
295 const std::string& alt_decal = decal_list[pads_part.alt_decal_index];
296
297 if( decals.find( alt_decal ) != decals.end() )
298 {
299 actual_decal_name = alt_decal;
300 found_valid_decal = true;
301 }
302 }
303
304 if( !found_valid_decal )
305 {
306 for( const std::string& decal : decal_list )
307 {
308 if( decals.find( decal ) != decals.end() )
309 {
310 actual_decal_name = decal;
311 found_valid_decal = true;
312 break;
313 }
314 }
315 }
316
317 if( found_valid_decal )
318 {
319 decal_name = actual_decal_name;
320 }
321
322 LIB_ID fpid;
323 fpid.SetLibItemName( wxString::FromUTF8( decal_name ) );
324 footprint->SetFPID( fpid );
325
326 footprint->SetValue( pads_part.decal );
327
328 if( !pads_part.alternate_decals.empty() )
329 {
330 wxString alternates;
331
332 for( size_t i = 0; i < pads_part.alternate_decals.size(); ++i )
333 {
334 if( i > 0 )
335 alternates += wxT( ", " );
336
337 alternates += wxString::FromUTF8( pads_part.alternate_decals[i] );
338 }
339
340 PCB_FIELD* field = new PCB_FIELD( footprint, FIELD_T::USER, wxT( "PADS_Alternate_Decals" ) );
341 field->SetLayer( Cmts_User );
342 field->SetVisible( false );
343 field->SetText( alternates );
344 footprint->Add( field );
345 }
346
347 auto partCoordScaler = [&]( double val, bool is_x ) {
348 double origin = is_x ? m_originX : m_originY;
349
350 double part_factor = m_scaleFactor;
351
352 if( !m_parser->IsBasicUnits() )
353 {
354 if( pads_part.units == "M" ) part_factor = PADS_UNIT_CONVERTER::MILS_TO_NM;
355 else if( pads_part.units == "MM" ) part_factor = PADS_UNIT_CONVERTER::MM_TO_NM;
356 else if( pads_part.units == "I" ) part_factor = PADS_UNIT_CONVERTER::INCHES_TO_NM;
357 else if( pads_part.units == "D" ) part_factor = PADS_UNIT_CONVERTER::MILS_TO_NM;
358 }
359
360 long long origin_nm = static_cast<long long>( std::round( origin * m_scaleFactor ) );
361 long long val_nm = static_cast<long long>( std::round( val * part_factor ) );
362
363 long long res_nm = val_nm - origin_nm;
364
365 if( !is_x )
366 res_nm = -res_nm;
367
368 return static_cast<int>( std::clamp<long long>( res_nm, INT_MIN, INT_MAX ) );
369 };
370
371 footprint->SetPosition( VECTOR2I( partCoordScaler( pads_part.location.x, true ),
372 partCoordScaler( pads_part.location.y, false ) ) );
373
374 // Both PADS and KiCad use counter-clockwise positive rotation convention.
375 // The Y-axis flip (PADS Y-up vs KiCad Y-down) does not affect rotation direction,
376 // so we use the PADS rotation value directly for both top and bottom layer parts.
377 // For bottom-layer parts, the subsequent Flip() call handles the layer change and
378 // adjusts the orientation appropriately.
379 footprint->SetOrientation( EDA_ANGLE( pads_part.rotation, DEGREES_T ) );
380
381 footprint->SetLayer( F_Cu );
382
383 // Look up custom attribute values from part type and per-instance overrides.
384 // Per-instance attributes (from PART <refdes> {...} in *PARTTYPE*) take priority.
385 const PADS_IO::PART_TYPE* partType = nullptr;
386 auto ptIt = part_types.find( pads_part.decal );
387
388 if( ptIt != part_types.end() )
389 partType = &ptIt->second;
390
391 const std::map<std::string, std::string>* instanceAttrs = nullptr;
392 auto iaIt = partInstanceAttrs.find( pads_part.name );
393
394 if( iaIt != partInstanceAttrs.end() )
395 instanceAttrs = &iaIt->second;
396
397 auto applyAttributes = [&]( const std::vector<PADS_IO::ATTRIBUTE>& attrs,
398 std::function<int(double)> scaler )
399 {
400 for( const auto& attr : attrs )
401 {
402 PCB_FIELD* field = nullptr;
403 bool ownsField = false;
404
405 if( attr.name == "Ref.Des." )
406 {
407 field = &footprint->Reference();
408 }
409 else if( attr.name == "Part Type" || attr.name == "VALUE" )
410 {
411 field = &footprint->Value();
412 }
413 else
414 {
415 std::string attrValue;
416
417 if( instanceAttrs )
418 {
419 auto valIt = instanceAttrs->find( attr.name );
420
421 if( valIt != instanceAttrs->end() )
422 attrValue = valIt->second;
423 }
424
425 if( attrValue.empty() && partType )
426 {
427 auto valIt = partType->attributes.find( attr.name );
428
429 if( valIt != partType->attributes.end() )
430 attrValue = valIt->second;
431 }
432
433 if( !attrValue.empty() )
434 {
435 field = new PCB_FIELD( footprint, FIELD_T::USER,
436 wxString::FromUTF8( attr.name ) );
437 field->SetText( wxString::FromUTF8( attrValue ) );
438
439 // Footprint text fields on copper layers are almost always documentation
440 // labels. Redirect to the corresponding silkscreen layer.
441 PCB_LAYER_ID fieldLayer = getMappedLayer( attr.level );
442
443 if( fieldLayer == UNDEFINED_LAYER )
444 fieldLayer = Cmts_User;
445 else if( IsCopperLayer( fieldLayer ) )
446 fieldLayer = IsBackLayer( fieldLayer ) ? B_SilkS : F_SilkS;
447
448 field->SetLayer( fieldLayer );
449 ownsField = true;
450 }
451 }
452
453 if( !field )
454 continue;
455
456 int scaledSize = scaler( attr.height );
457 int charHeight =
458 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextHeightScale );
459 int charWidth =
460 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextWidthScale );
461 field->SetTextSize( VECTOR2I( charWidth, charHeight ) );
462
463 if( attr.width > 0 )
464 field->SetTextThickness( scaler( attr.width ) );
465
466 // Position is relative to part origin, rotated by part orientation.
467 // Y is negated for coordinate system conversion.
468 VECTOR2I offset( scaler( attr.x ), -scaler( attr.y ) );
469 EDA_ANGLE part_orient( pads_part.rotation, DEGREES_T );
470 RotatePoint( offset, part_orient );
471
472 // PADS text anchor differs from KiCad by a small offset along the
473 // reading direction. Shift left (toward text start) to compensate.
474 EDA_ANGLE textAngle = EDA_ANGLE( attr.orientation, DEGREES_T ) + part_orient;
475 VECTOR2I textShift( -ADVANCED_CFG::GetCfg().m_PadsTextAnchorOffsetNm, 0 );
476 RotatePoint( textShift, textAngle );
477 offset += textShift;
478
479 field->SetPosition( footprint->GetPosition() + offset );
480 field->SetTextAngle( textAngle );
481 field->SetKeepUpright( false );
482 field->SetVisible( attr.visible );
483
484 if( attr.hjust == "LEFT" )
486 else if( attr.hjust == "RIGHT" )
488 else
490
491 if( attr.vjust == "UP" )
493 else if( attr.vjust == "DOWN" )
495 else
497
498 if( ownsField )
499 footprint->Add( field );
500 }
501 };
502
503 auto decal_it = decals.find( decal_name );
504
505 if( decal_it != decals.end() )
506 {
507 auto decalScaler = [&]( double val ) {
508 if( m_parser->IsBasicUnits() )
509 return scaleSize( val );
510
511 const std::string& units = decal_it->second.units;
512
513 if( units == "M" || units == "D" || units == "MILS" || units == "MIL" )
515 else if( units == "MM" || units == "METRIC" )
517 else if( units == "I" || units == "INCHES" || units == "INCH" )
519 else
520 return scaleSize( val );
521 };
522
523 applyAttributes( decal_it->second.attributes, decalScaler );
524 }
525 else
526 {
527 if( m_reporter )
528 {
529 m_reporter->Report(
530 wxString::Format( _( "Footprint '%s' not found in decal list, part skipped" ),
531 decal_name ),
533 }
534 }
535
536 auto partScaler = [&]( double val ) {
537 if( !m_parser->IsBasicUnits() )
538 {
539 if( pads_part.units == "M" ) return KiROUND( val * PADS_UNIT_CONVERTER::MILS_TO_NM );
540 }
541
542 if( pads_part.units == "M" ) return KiROUND( val );
543
544 return scaleSize( val );
545 };
546
547 applyAttributes( pads_part.attributes, partScaler );
548
549 // PADS "Part Type" maps to KiCad Value field. Hide it since it typically
550 // shows the part type name which is not useful on fabrication layers.
551 footprint->Value().SetVisible( false );
552
553 m_loadBoard->Add( footprint );
554
555 auto blockIt = m_partToBlockMap.find( pads_part.name );
556
557 if( blockIt != m_partToBlockMap.end() )
558 {
559 PCB_FIELD* blockField =
560 new PCB_FIELD( footprint, FIELD_T::USER, wxT( "PADS_Reuse_Block" ) );
561 blockField->SetLayer( Cmts_User );
562 blockField->SetVisible( false );
563 blockField->SetText( wxString::FromUTF8( blockIt->second ) );
564 footprint->Add( blockField );
565 }
566
567 if( decal_it == decals.end() )
568 {
569 continue;
570 }
571
572 // Add Pads and Graphics from Decal
573 {
574 const PADS_IO::PART_DECAL& decal = decal_it->second;
575
576 auto decalScaler = [&]( double val ) {
577 if( m_parser->IsBasicUnits() )
578 return scaleSize( val );
579
580 if( decal.units == "M" || decal.units == "D" || decal.units == "MILS"
581 || decal.units == "MIL" )
583 else if( decal.units == "MM" || decal.units == "METRIC" )
585 else if( decal.units == "I" || decal.units == "INCHES" || decal.units == "INCH" )
587 else
588 return scaleSize( val );
589 };
590
591 auto convertPadShape = [&]( const PADS_IO::PAD_STACK_LAYER& layer_def,
592 PAD* pad, PCB_LAYER_ID kicad_layer,
593 const EDA_ANGLE& part_orient ) {
594 const std::string& shape = layer_def.shape;
595 // In PADS, sizeA is height (Y) and sizeB is width (X), opposite of KiCad convention
596 VECTOR2I size( std::max( decalScaler( layer_def.sizeB ), m_minObjectSize ),
597 std::max( decalScaler( layer_def.sizeA ), m_minObjectSize ) );
598
599 if( shape == "R" || shape == "C" || shape == "A" || shape == "RT" )
600 {
601 pad->SetShape( kicad_layer, PAD_SHAPE::CIRCLE );
602 pad->SetSize( kicad_layer, VECTOR2I( size.x, size.x ) );
603 }
604 else if( shape == "S" || shape == "ST" )
605 {
606 pad->SetShape( kicad_layer, PAD_SHAPE::RECTANGLE );
607 pad->SetSize( kicad_layer, VECTOR2I( size.x, size.x ) );
608 }
609 else if( shape == "O" || shape == "OT" )
610 {
611 pad->SetShape( kicad_layer, PAD_SHAPE::OVAL );
612 pad->SetSize( kicad_layer, size );
613 }
614 else if( shape == "RF" )
615 {
616 pad->SetShape( kicad_layer, PAD_SHAPE::RECTANGLE );
617 pad->SetSize( kicad_layer, size );
618
619 if( layer_def.finger_offset != 0 )
620 {
621 int offset = decalScaler( layer_def.finger_offset );
622 VECTOR2I pad_offset( offset, 0 );
623 RotatePoint( pad_offset, EDA_ANGLE( layer_def.rotation, DEGREES_T ) );
624 pad->SetOffset( kicad_layer, pad_offset );
625 }
626 }
627 else if( shape == "OF" )
628 {
629 pad->SetShape( kicad_layer, PAD_SHAPE::OVAL );
630 pad->SetSize( kicad_layer, size );
631
632 if( layer_def.finger_offset != 0 )
633 {
634 int offset = decalScaler( layer_def.finger_offset );
635 VECTOR2I pad_offset( offset, 0 );
636 RotatePoint( pad_offset, EDA_ANGLE( layer_def.rotation, DEGREES_T ) );
637 pad->SetOffset( kicad_layer, pad_offset );
638 }
639 }
640 else if( shape == "RC" || shape == "OC" )
641 {
642 pad->SetShape( kicad_layer, PAD_SHAPE::ROUNDRECT );
643 pad->SetSize( kicad_layer, size );
644
645 if( layer_def.corner_radius > 0 && size.x > 0 )
646 {
647 double min_dim = std::min( size.x, size.y );
648 double radius = decalScaler( layer_def.corner_radius );
649 double ratio = ( min_dim > 0 ) ? ( radius / min_dim ) : 0.25;
650 ratio = std::min( ratio, 0.5 );
651 pad->SetRoundRectRadiusRatio( kicad_layer, ratio );
652 }
653 else
654 {
655 pad->SetRoundRectRadiusRatio( kicad_layer, 0.25 );
656 }
657
658 if( layer_def.finger_offset != 0 )
659 {
660 int offset = decalScaler( layer_def.finger_offset );
661 VECTOR2I pad_offset( offset, 0 );
662 RotatePoint( pad_offset, EDA_ANGLE( layer_def.rotation, DEGREES_T ) );
663 pad->SetOffset( kicad_layer, pad_offset );
664 }
665 }
666 else
667 {
668 pad->SetShape( kicad_layer, PAD_SHAPE::CIRCLE );
669 pad->SetSize( kicad_layer, VECTOR2I( size.x, size.x ) );
670 }
671
672 pad->SetOrientation( part_orient + EDA_ANGLE( layer_def.rotation, DEGREES_T ) );
673 };
674
675 EDA_ANGLE part_orient( pads_part.rotation, DEGREES_T );
676
677 for( size_t term_idx = 0; term_idx < decal.terminals.size(); ++term_idx )
678 {
679 const auto& term = decal.terminals[term_idx];
680 PAD* pad = new PAD( footprint );
681 footprint->Add( pad );
682
683 pad->SetNumber( term.name );
684
685 VECTOR2I pad_pos( decalScaler( term.x ), -decalScaler( term.y ) );
686 RotatePoint( pad_pos, part_orient );
687 pad->SetPosition( footprint->GetPosition() + pad_pos );
688
689 // Look up pad stack by terminal index (1-based). PAD 0 is the default for
690 // terminals without explicit definitions. PAD N is for terminal index N.
691 int pin_num = static_cast<int>( term_idx + 1 );
692
693 auto stack_it = decal.pad_stacks.find( pin_num );
694
695 if( stack_it == decal.pad_stacks.end() )
696 stack_it = decal.pad_stacks.find( 0 );
697
698 if( stack_it != decal.pad_stacks.end() && !stack_it->second.empty() )
699 {
700 const std::vector<PADS_IO::PAD_STACK_LAYER>& stack = stack_it->second;
701
702 double drill = 0.0;
703 bool plated = true;
704 double slot_length = 0.0;
705 double slot_orientation = 0.0;
706 double pad_rotation = 0.0;
707
708 for( const auto& layer_def : stack )
709 {
710 if( layer_def.drill > 0 )
711 {
712 drill = layer_def.drill;
713 plated = layer_def.plated;
714 slot_length = layer_def.slot_length;
715 slot_orientation = layer_def.slot_orientation;
716 pad_rotation = layer_def.rotation;
717 break;
718 }
719 }
720
721 LSET layer_set;
722
723 auto mapPadsLayer = [&]( int pads_layer ) -> PCB_LAYER_ID {
724 if( pads_layer == -2 || pads_layer == 1 )
725 return F_Cu;
726 else if( pads_layer == -1
727 || pads_layer == m_parser->GetParameters().layer_count )
728 return B_Cu;
729 else if( pads_layer > 1
730 && pads_layer < m_parser->GetParameters().layer_count )
731 {
732 int inner_idx = pads_layer - 2;
733
734 if( inner_idx >= 0 && inner_idx < 30 )
735 return static_cast<PCB_LAYER_ID>( In1_Cu + inner_idx * 2 );
736 }
737
738 return UNDEFINED_LAYER;
739 };
740
741 bool has_explicit_layers = false;
742
743 for( const auto& layer_def : stack )
744 {
745 if( layer_def.layer == -2 || layer_def.layer == -1
746 || layer_def.layer == 1
747 || layer_def.layer == m_parser->GetParameters().layer_count )
748 {
749 has_explicit_layers = true;
750 break;
751 }
752 }
753
754 // Track mask/paste layers explicitly present in the stack regardless
755 // of size. A zero-size entry means "intentionally no pad on this layer"
756 // and must suppress the SMD fallback for that layer.
757 LSET explicitly_seen_tech;
758
759 for( const auto& layer_def : stack )
760 {
761 if( layer_def.layer > 0 )
762 {
763 PCB_LAYER_ID check = getMappedLayer( layer_def.layer );
764
765 if( check == F_Mask || check == B_Mask
766 || check == F_Paste || check == B_Paste )
767 {
768 explicitly_seen_tech.set( check );
769 }
770 }
771 }
772
773 // Pre-scan copper layers to detect whether the pad needs
774 // per-layer shapes. In PADS, layer -2 is top copper and
775 // layer -1 is bottom copper, and they can have different
776 // shapes (e.g. square on top, round on bottom). KiCad's
777 // PADSTACK in NORMAL mode stores a single shape for all
778 // layers, so we must switch to FRONT_INNER_BACK when the
779 // front and back shapes differ.
780 if( has_explicit_layers )
781 {
782 std::string front_shape;
783 std::string back_shape;
784 double front_sizeA = 0;
785 double back_sizeA = 0;
786 double front_sizeB = 0;
787 double back_sizeB = 0;
788
789 for( const auto& layer_def : stack )
790 {
791 if( layer_def.sizeA <= 0 )
792 continue;
793
794 if( layer_def.shape == "RT" || layer_def.shape == "ST"
795 || layer_def.shape == "RA" || layer_def.shape == "SA" )
796 {
797 continue;
798 }
799
800 PCB_LAYER_ID mapped = mapPadsLayer( layer_def.layer );
801
802 if( mapped == F_Cu && front_shape.empty() )
803 {
804 front_shape = layer_def.shape;
805 front_sizeA = layer_def.sizeA;
806 front_sizeB = layer_def.sizeB;
807 }
808 else if( mapped == B_Cu && back_shape.empty() )
809 {
810 back_shape = layer_def.shape;
811 back_sizeA = layer_def.sizeA;
812 back_sizeB = layer_def.sizeB;
813 }
814 }
815
816 if( !front_shape.empty() && !back_shape.empty()
817 && ( front_shape != back_shape
818 || front_sizeA != back_sizeA
819 || front_sizeB != back_sizeB ) )
820 {
821 pad->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
822 }
823 }
824
825 for( const auto& layer_def : stack )
826 {
827 if( layer_def.layer == 0 )
828 {
829 if( !has_explicit_layers )
830 {
831 layer_set = ( drill > 0 ) ? LSET::AllCuMask()
832 : LSET( { F_Cu, B_Cu } );
833 convertPadShape( layer_def, pad, F_Cu, part_orient );
834
835 if( drill == 0 )
836 {
837 pad->SetShape( B_Cu, pad->GetShape( F_Cu ) );
838 pad->SetSize( B_Cu, pad->GetSize( F_Cu ) );
839 }
840 }
841
842 continue;
843 }
844
845 // Skip layers with size 0 - "no pad on this layer" in PADS.
846 // We must not call SetSize with 0 since in PADSTACK NORMAL mode all
847 // layers write to the same ALL_LAYERS slot, overwriting valid sizes.
848 if( layer_def.sizeA <= 0 )
849 continue;
850
851 // RT/ST are thermal relief spoke patterns for plane layers.
852 // RA/SA are anti-pad (clearance) shapes for plane layers.
853 // KiCad computes thermal reliefs from zone settings, so skip
854 // these to avoid overwriting the actual pad shape. However,
855 // the presence of RT/ST indicates this pad should have thermal
856 // relief rather than a solid connection to copper pours.
857 if( layer_def.shape == "RT" || layer_def.shape == "ST" )
858 {
859 pad->SetLocalZoneConnection( ZONE_CONNECTION::THERMAL );
860
861 if( layer_def.thermal_spoke_width > 0 )
862 {
863 pad->SetLocalThermalSpokeWidthOverride(
864 decalScaler( layer_def.thermal_spoke_width ) );
865 }
866
867 if( layer_def.thermal_spoke_orientation != 0.0 )
868 {
869 pad->SetThermalSpokeAngleDegrees(
870 layer_def.thermal_spoke_orientation );
871 }
872
873 continue;
874 }
875
876 if( layer_def.shape == "RA" || layer_def.shape == "SA" )
877 {
878 continue;
879 }
880
881 PCB_LAYER_ID kicad_layer = mapPadsLayer( layer_def.layer );
882
883 if( kicad_layer == UNDEFINED_LAYER && layer_def.layer > 0 )
884 {
885 // For non-copper layers, check if they're mask/paste layers.
886 // PADS pad stacks can include explicit solder mask and paste
887 // mask entries that must be preserved in KiCad.
888 // layer_def.layer > 0 skips the copper sentinels -2 (top)
889 // and -1 (bottom), which mapPadsLayer already resolved above.
890 PCB_LAYER_ID tech_layer = getMappedLayer( layer_def.layer );
891
892 if( tech_layer == F_Mask || tech_layer == B_Mask
893 || tech_layer == F_Paste || tech_layer == B_Paste )
894 {
895 layer_set.set( tech_layer );
896 }
897 }
898 else if( kicad_layer != UNDEFINED_LAYER )
899 {
900 layer_set.set( kicad_layer );
901 convertPadShape( layer_def, pad, kicad_layer, part_orient );
902 }
903 }
904
905 if( layer_set.none() )
906 {
907 layer_set.set( F_Cu );
908 convertPadShape( stack[0], pad, F_Cu, part_orient );
909 }
910
911 // For SMD pads, enable mask/paste layers that the stack did not
912 // explicitly mention. A zero-size stack entry for a mask/paste layer
913 // means "intentionally disabled" and is tracked in explicitly_seen_tech,
914 // so only layers absent from the stack entirely get the fallback.
915 if( drill == 0 )
916 {
917 if( layer_set.test( F_Cu ) && !layer_set.test( F_Mask )
918 && !explicitly_seen_tech.test( F_Mask ) )
919 {
920 layer_set.set( F_Mask );
921 }
922
923 if( layer_set.test( F_Cu ) && !layer_set.test( F_Paste )
924 && !explicitly_seen_tech.test( F_Paste ) )
925 {
926 layer_set.set( F_Paste );
927 }
928
929 if( layer_set.test( B_Cu ) && !layer_set.test( B_Mask )
930 && !explicitly_seen_tech.test( B_Mask ) )
931 {
932 layer_set.set( B_Mask );
933 }
934
935 if( layer_set.test( B_Cu ) && !layer_set.test( B_Paste )
936 && !explicitly_seen_tech.test( B_Paste ) )
937 {
938 layer_set.set( B_Paste );
939 }
940 }
941
942 if( slot_length > 0 && slot_length != drill )
943 {
944 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
945
946 int drillMinor = decalScaler( drill );
947 int drillMajor = decalScaler( slot_length );
948
949 // Slot orientation is in the decal's local frame.
950 // Subtract the pad shape rotation to get the slot
951 // angle in the pad's own local frame.
952 double relAngle = slot_orientation - pad_rotation;
953
954 relAngle = fmod( relAngle, 360.0 );
955
956 if( relAngle < 0 )
957 relAngle += 360.0;
958
959 bool vertical = ( relAngle > 45.0 && relAngle < 135.0 )
960 || ( relAngle > 225.0 && relAngle < 315.0 );
961
962 if( vertical )
963 pad->SetDrillSize( VECTOR2I( drillMinor, drillMajor ) );
964 else
965 pad->SetDrillSize( VECTOR2I( drillMajor, drillMinor ) );
966 }
967 else
968 {
969 pad->SetDrillSize( VECTOR2I( decalScaler( drill ),
970 decalScaler( drill ) ) );
971 }
972
973 if( drill == 0 )
974 {
975 pad->SetAttribute( PAD_ATTRIB::SMD );
976 }
977 else
978 {
979 if( plated )
980 pad->SetAttribute( PAD_ATTRIB::PTH );
981 else
982 pad->SetAttribute( PAD_ATTRIB::NPTH );
983
984 // Preserve any explicit mask/paste layer bits accumulated
985 // during stack iteration before expanding to all copper layers.
986 LSET mask_paste_bits =
987 layer_set & LSET( { F_Mask, B_Mask, F_Paste, B_Paste } );
988 layer_set = LSET::AllCuMask() | mask_paste_bits;
989 }
990
991 pad->SetLayerSet( layer_set );
992 }
993 else
994 {
995 int fallbackSize = std::max( decalScaler( 1.5 ), m_minObjectSize );
996 pad->SetSize( F_Cu, VECTOR2I( fallbackSize, fallbackSize ) );
997 pad->SetShape( F_Cu, PAD_SHAPE::CIRCLE );
998 pad->SetAttribute( PAD_ATTRIB::PTH );
999 pad->SetLayerSet( LSET::AllCuMask() );
1000 }
1001
1002 std::string pinKey = pads_part.name + "." + term.name;
1003 auto netIt = m_pinToNetMap.find( pinKey );
1004
1005 if( netIt != m_pinToNetMap.end() )
1006 {
1007 NETINFO_ITEM* net =
1008 m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( netIt->second ) );
1009
1010 if( net )
1011 pad->SetNet( net );
1012 }
1013 }
1014
1015 for( const auto& item : decal.items )
1016 {
1017 if( item.points.empty() )
1018 continue;
1019
1020 // Decal graphics layers work differently from routing layers in PADS.
1021 // Layer 0 and layer 1 are typically footprint outlines, not copper.
1022 PCB_LAYER_ID shape_layer = F_SilkS;
1023
1024 if( item.layer == 0 )
1025 {
1026 shape_layer = F_SilkS;
1027 }
1028 else
1029 {
1030 PCB_LAYER_ID mapped_layer = getMappedLayer( item.layer );
1031
1032 if( IsCopperLayer( mapped_layer ) )
1033 {
1034 if( mapped_layer == B_Cu )
1035 shape_layer = B_SilkS;
1036 else
1037 shape_layer = F_SilkS;
1038 }
1039 else
1040 {
1041 shape_layer = mapped_layer;
1042 }
1043 }
1044
1045 if( shape_layer == UNDEFINED_LAYER )
1046 {
1047 if( m_reporter )
1048 {
1049 m_reporter->Report( wxString::Format(
1050 _( "Skipping decal item on unmapped layer %d" ), item.layer ),
1052 }
1053 continue;
1054 }
1055
1056 bool is_circle = ( item.type == "CIRCLE" );
1057 bool is_closed = ( item.type == "CLOSED" || is_circle );
1058
1059 // Per PADS spec: CIRCLE pieces have 2 corners representing ends of
1060 // horizontal diameter.
1061 if( is_circle && item.points.size() >= 2 )
1062 {
1063 PCB_SHAPE* shape = new PCB_SHAPE( footprint, SHAPE_T::CIRCLE );
1064 shape->SetLayer( shape_layer );
1065
1066 double x1 = item.points[0].x;
1067 double y1 = item.points[0].y;
1068 double x2 = item.points[1].x;
1069 double y2 = item.points[1].y;
1070
1071 double cx = ( x1 + x2 ) / 2.0;
1072 double cy = ( y1 + y2 ) / 2.0;
1073
1074 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 )
1075 + ( y2 - y1 ) * ( y2 - y1 ) )
1076 / 2.0;
1077
1078 int scaledRadius = std::max( decalScaler( radius ), m_minObjectSize );
1079 VECTOR2I center( decalScaler( cx ), -decalScaler( cy ) );
1080 VECTOR2I pt_on_circle( center.x + scaledRadius, center.y );
1081
1082 RotatePoint( center, part_orient );
1083 RotatePoint( pt_on_circle, part_orient );
1084
1085 VECTOR2I fp_pos = footprint->GetPosition();
1086 shape->SetCenter( fp_pos + center );
1087 shape->SetEnd( fp_pos + pt_on_circle );
1088 shape->SetStroke(
1089 STROKE_PARAMS( decalScaler( item.width ), LINE_STYLE::SOLID ) );
1090
1091 footprint->Add( shape );
1092
1093 continue;
1094 }
1095
1096 if( item.points.size() < 2 )
1097 continue;
1098
1099 for( size_t i = 0; i < item.points.size() - 1; ++i )
1100 {
1101 const PADS_IO::ARC_POINT& p1 = item.points[i];
1102 const PADS_IO::ARC_POINT& p2 = item.points[i + 1];
1103
1104 PCB_SHAPE* shape = new PCB_SHAPE( footprint );
1105 shape->SetLayer( shape_layer );
1106 shape->SetStroke(
1107 STROKE_PARAMS( decalScaler( item.width ), LINE_STYLE::SOLID ) );
1108
1109 if( p2.is_arc )
1110 {
1111 shape->SetShape( SHAPE_T::ARC );
1112 VECTOR2I center( decalScaler( p2.arc.cx ), -decalScaler( p2.arc.cy ) );
1113 VECTOR2I start( decalScaler( p1.x ), -decalScaler( p1.y ) );
1114 VECTOR2I end( decalScaler( p2.x ), -decalScaler( p2.y ) );
1115
1116 // Y-axis flip reverses arc winding; swap endpoints for CCW arcs
1117 if( p2.arc.delta_angle > 0 )
1118 std::swap( start, end );
1119
1120 RotatePoint( center, part_orient );
1121 RotatePoint( start, part_orient );
1122 RotatePoint( end, part_orient );
1123
1124 VECTOR2I fp_pos = footprint->GetPosition();
1125 shape->SetCenter( fp_pos + center );
1126 shape->SetStart( fp_pos + start );
1127 shape->SetEnd( fp_pos + end );
1128 }
1129 else
1130 {
1131 shape->SetShape( SHAPE_T::SEGMENT );
1132 VECTOR2I start( decalScaler( p1.x ), -decalScaler( p1.y ) );
1133 VECTOR2I end( decalScaler( p2.x ), -decalScaler( p2.y ) );
1134
1135 RotatePoint( start, part_orient );
1136 RotatePoint( end, part_orient );
1137
1138 VECTOR2I fp_pos = footprint->GetPosition();
1139 shape->SetStart( fp_pos + start );
1140 shape->SetEnd( fp_pos + end );
1141 }
1142
1143 footprint->Add( shape );
1144 }
1145
1146 if( is_closed && item.points.size() > 2 )
1147 {
1148 const PADS_IO::ARC_POINT& pLast = item.points.back();
1149 const PADS_IO::ARC_POINT& pFirst = item.points.front();
1150
1151 PCB_SHAPE* shape = new PCB_SHAPE( footprint );
1152 shape->SetLayer( shape_layer );
1153 shape->SetStroke(
1154 STROKE_PARAMS( decalScaler( item.width ), LINE_STYLE::SOLID ) );
1155
1156 if( pFirst.is_arc )
1157 {
1158 shape->SetShape( SHAPE_T::ARC );
1159 VECTOR2I center( decalScaler( pFirst.arc.cx ),
1160 -decalScaler( pFirst.arc.cy ) );
1161 VECTOR2I start( decalScaler( pLast.x ), -decalScaler( pLast.y ) );
1162 VECTOR2I end( decalScaler( pFirst.x ), -decalScaler( pFirst.y ) );
1163
1164 if( pFirst.arc.delta_angle > 0 )
1165 std::swap( start, end );
1166
1167 RotatePoint( center, part_orient );
1168 RotatePoint( start, part_orient );
1169 RotatePoint( end, part_orient );
1170
1171 VECTOR2I fp_pos = footprint->GetPosition();
1172 shape->SetCenter( fp_pos + center );
1173 shape->SetStart( fp_pos + start );
1174 shape->SetEnd( fp_pos + end );
1175 }
1176 else
1177 {
1178 shape->SetShape( SHAPE_T::SEGMENT );
1179 VECTOR2I start( decalScaler( pLast.x ), -decalScaler( pLast.y ) );
1180 VECTOR2I end( decalScaler( pFirst.x ), -decalScaler( pFirst.y ) );
1181
1182 RotatePoint( start, part_orient );
1183 RotatePoint( end, part_orient );
1184
1185 VECTOR2I fp_pos = footprint->GetPosition();
1186 shape->SetStart( fp_pos + start );
1187 shape->SetEnd( fp_pos + end );
1188 }
1189
1190 footprint->Add( shape );
1191 }
1192 }
1193 }
1194
1195 if( pads_part.bottom_layer )
1196 {
1197 footprint->Flip( footprint->GetPosition(), FLIP_DIRECTION::LEFT_RIGHT );
1198 }
1199 }
1200}
1201
1202
1204{
1205 const auto& reuse_blocks = m_parser->GetReuseBlocks();
1206
1207 if( reuse_blocks.empty() )
1208 return;
1209
1210 std::map<std::string, PCB_GROUP*> blockGroups;
1211
1212 for( const auto& [blockName, block] : reuse_blocks )
1213 {
1214 if( !block.instances.empty() || !block.part_names.empty() )
1215 {
1217 group->SetName( wxString::FromUTF8( blockName ) );
1218 m_loadBoard->Add( group );
1219 blockGroups[blockName] = group;
1220 }
1221 }
1222
1223 for( FOOTPRINT* fp : m_loadBoard->Footprints() )
1224 {
1225 for( PCB_FIELD* field : fp->GetFields() )
1226 {
1227 if( field->GetName() == wxT( "PADS_Reuse_Block" ) )
1228 {
1229 std::string blockName = field->GetText().ToStdString();
1230 auto groupIt = blockGroups.find( blockName );
1231
1232 if( groupIt != blockGroups.end() )
1233 {
1234 groupIt->second->AddItem( fp );
1235 }
1236
1237 break;
1238 }
1239 }
1240 }
1241}
1242
1243
1245{
1246 const auto& test_points = m_parser->GetTestPoints();
1247
1248 for( const auto& tp : test_points )
1249 {
1250 FOOTPRINT* footprint = new FOOTPRINT( m_loadBoard );
1251
1252 wxString refDes = wxString::Format( wxT( "TP%d" ), m_testPointIndex++ );
1253 footprint->SetReference( refDes );
1254 footprint->SetValue( wxString::FromUTF8( tp.symbol_name ) );
1255
1256 VECTOR2I pos( scaleCoord( tp.x, true ), scaleCoord( tp.y, false ) );
1257 footprint->SetPosition( pos );
1258
1259 PCB_LAYER_ID layer = F_Cu;
1260
1261 if( tp.side == 2 )
1262 layer = B_Cu;
1263
1264 footprint->SetLayer( ( layer == B_Cu ) ? B_Cu : F_Cu );
1265
1266 PAD* pad = new PAD( footprint );
1267 pad->SetNumber( wxT( "1" ) );
1268 pad->SetPosition( pos );
1270 int tpSize = std::max( scaleSize( 50.0 ), m_minObjectSize );
1271 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( tpSize, tpSize ) );
1272 pad->SetAttribute( PAD_ATTRIB::SMD );
1273 pad->SetLayerSet( layer == B_Cu ? LSET( { B_Cu } ) : LSET( { F_Cu } ) );
1274
1275 if( !tp.net_name.empty() )
1276 {
1277 NETINFO_ITEM* net = m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( tp.net_name ) );
1278
1279 if( net )
1280 pad->SetNet( net );
1281 }
1282
1283 footprint->Add( pad );
1284
1285 footprint->SetBoardOnly( true );
1286
1287 PCB_FIELD* tpField = new PCB_FIELD( footprint, FIELD_T::USER, wxT( "Test_Point" ) );
1288 tpField->SetLayer( Cmts_User );
1289 tpField->SetVisible( false );
1290 tpField->SetText( wxString::FromUTF8( tp.type ) );
1291 footprint->Add( tpField );
1292
1293 m_loadBoard->Add( footprint );
1294 }
1295}
1296
1297
1299{
1300 const auto& texts = m_parser->GetTexts();
1301
1302 for( const auto& pads_text : texts )
1303 {
1304 PCB_LAYER_ID textLayer = getMappedLayer( pads_text.layer );
1305
1306 if( textLayer == UNDEFINED_LAYER )
1307 {
1308 if( m_reporter )
1309 {
1310 m_reporter->Report( wxString::Format(
1311 _( "Text on unmapped layer %d assigned to Comments layer" ),
1312 pads_text.layer ), RPT_SEVERITY_WARNING );
1313 }
1314 textLayer = Cmts_User;
1315 }
1316
1318 text->SetText( pads_text.content );
1319
1320 // PADS text cell height includes internal leading and descender space.
1321 // Scale factors calibrated to match PADS rendered character dimensions.
1322 int scaledSize = scaleSize( pads_text.height );
1323 int charHeight =
1324 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextHeightScale );
1325 int charWidth =
1326 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextWidthScale );
1327 text->SetTextSize( VECTOR2I( charWidth, charHeight ) );
1328
1329 if( pads_text.width > 0 )
1330 text->SetTextThickness( scaleSize( pads_text.width ) );
1331
1332 EDA_ANGLE textAngle( pads_text.rotation, DEGREES_T );
1333 text->SetTextAngle( textAngle );
1334
1335 // PADS text anchor differs from KiCad by a small offset along the
1336 // reading direction. Shift left (toward text start) to compensate.
1337 VECTOR2I pos( scaleCoord( pads_text.location.x, true ),
1338 scaleCoord( pads_text.location.y, false ) );
1339 VECTOR2I textShift( -ADVANCED_CFG::GetCfg().m_PadsTextAnchorOffsetNm, 0 );
1340 RotatePoint( textShift, textAngle );
1341 text->SetPosition( pos + textShift );
1342
1343 if( pads_text.hjust == "LEFT" )
1344 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1345 else if( pads_text.hjust == "RIGHT" )
1346 text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
1347 else
1348 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
1349
1350 if( pads_text.vjust == "UP" )
1351 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
1352 else if( pads_text.vjust == "DOWN" )
1353 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
1354 else
1355 text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
1356
1357 text->SetKeepUpright( false );
1358 text->SetLayer( textLayer );
1359 m_loadBoard->Add( text );
1360 }
1361}
1362
1363
1365{
1366 const auto& routes = m_parser->GetRoutes();
1367 std::set<std::pair<int, int>> placedThroughVias;
1368
1369 for( const auto& route : routes )
1370 {
1371 NETINFO_ITEM* net = m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( route.net_name ) );
1372
1373 if( !net )
1374 continue;
1375
1376 for( const auto& track_def : route.tracks )
1377 {
1378 if( track_def.points.size() < 2 )
1379 continue;
1380
1381 PCB_LAYER_ID track_layer = getMappedLayer( track_def.layer );
1382
1383 if( !IsCopperLayer( track_layer ) )
1384 {
1385 if( m_reporter )
1386 {
1387 m_reporter->Report( wxString::Format(
1388 _( "Skipping track on non-copper layer %d" ), track_def.layer ),
1390 }
1391 continue;
1392 }
1393
1394 int track_width = std::max( scaleSize( track_def.width ), m_minObjectSize );
1395
1396 for( size_t i = 0; i < track_def.points.size() - 1; ++i )
1397 {
1398 const PADS_IO::ARC_POINT& p1 = track_def.points[i];
1399 const PADS_IO::ARC_POINT& p2 = track_def.points[i + 1];
1400
1401 VECTOR2I start( scaleCoord( p1.x, true ), scaleCoord( p1.y, false ) );
1402 VECTOR2I end( scaleCoord( p2.x, true ), scaleCoord( p2.y, false ) );
1403
1404 // Skip near-zero-length segments (can occur at via points with width changes).
1405 // Tolerance of 1000nm accounts for floating point precision in coordinate
1406 // transformation.
1407 if( ( start - end ).EuclideanNorm() < 1000 )
1408 continue;
1409
1410 if( p2.is_arc )
1411 {
1412 SHAPE_ARC shapeArc = makeMidpointArc( p1, p2, track_width );
1413
1414 PCB_ARC* arc = new PCB_ARC( m_loadBoard, &shapeArc );
1415 arc->SetNet( net );
1416 arc->SetWidth( track_width );
1417 arc->SetLayer( track_layer );
1418 m_loadBoard->Add( arc );
1419 }
1420 else
1421 {
1422 PCB_TRACK* track = new PCB_TRACK( m_loadBoard );
1423 track->SetNet( net );
1424 track->SetWidth( track_width );
1425 track->SetLayer( track_layer );
1426 track->SetStart( start );
1427 track->SetEnd( end );
1428 m_loadBoard->Add( track );
1429 }
1430 }
1431 }
1432
1433 for( const auto& via_def : route.vias )
1434 {
1435 VECTOR2I pos( scaleCoord( via_def.location.x, true ),
1436 scaleCoord( via_def.location.y, false ) );
1437
1438 VIATYPE viaType = VIATYPE::THROUGH;
1439 auto it = m_parser->GetViaDefs().find( via_def.name );
1440
1441 if( it != m_parser->GetViaDefs().end() )
1442 {
1443 switch( it->second.via_type )
1444 {
1445 case PADS_IO::VIA_TYPE::THROUGH: viaType = VIATYPE::THROUGH; break;
1446 case PADS_IO::VIA_TYPE::BLIND: viaType = VIATYPE::BLIND; break;
1447 case PADS_IO::VIA_TYPE::BURIED: viaType = VIATYPE::BURIED; break;
1448 case PADS_IO::VIA_TYPE::MICROVIA: viaType = VIATYPE::MICROVIA; break;
1449 }
1450 }
1451
1452 // Through-hole vias shared across multiple SIGNAL blocks for the same net
1453 // produce duplicates. Skip if we already placed one at this position.
1454 if( viaType == VIATYPE::THROUGH )
1455 {
1456 auto key = std::make_pair( pos.x, pos.y );
1457
1458 if( placedThroughVias.count( key ) )
1459 continue;
1460
1461 placedThroughVias.insert( key );
1462 }
1463
1464 PCB_VIA* via = new PCB_VIA( m_loadBoard );
1465 via->SetNet( net );
1466 via->SetPosition( pos );
1467
1468 if( it != m_parser->GetViaDefs().end() )
1469 {
1470 const PADS_IO::VIA_DEF& def = it->second;
1471
1472 via->SetWidth( std::max( scaleSize( def.size ), m_minObjectSize ) );
1473 via->SetDrill( std::max( scaleSize( def.drill ), m_minObjectSize ) );
1474
1475 PCB_LAYER_ID startLayer = ( def.start_layer > 0 )
1478 PCB_LAYER_ID endLayer = ( def.end_layer > 0 )
1479 ? getMappedLayer( def.end_layer )
1481
1482 if( startLayer != UNDEFINED_LAYER && endLayer != UNDEFINED_LAYER )
1483 {
1484 via->SetLayerPair( startLayer, endLayer );
1485 via->SetViaType( viaType );
1486 }
1487 else
1488 {
1489 via->SetLayerPair( F_Cu, B_Cu );
1490 via->SetViaType( VIATYPE::THROUGH );
1491 }
1492
1493 if( !def.has_mask_front )
1494 via->SetFrontTentingMode( TENTING_MODE::TENTED );
1495
1496 if( !def.has_mask_back )
1497 via->SetBackTentingMode( TENTING_MODE::TENTED );
1498
1499 }
1500 else
1501 {
1502 via->SetWidth( std::max( scaleSize( 20.0 ), m_minObjectSize ) );
1503 via->SetDrill( std::max( scaleSize( 10.0 ), m_minObjectSize ) );
1504 via->SetLayerPair( F_Cu, B_Cu );
1505 via->SetViaType( VIATYPE::THROUGH );
1506 }
1507
1508 m_loadBoard->Add( via );
1509 }
1510 }
1511}
1512
1513
1515{
1516 const auto& copperShapes = m_parser->GetCopperShapes();
1517
1518 // Check if a COPPER_SHAPE is a non-copper straight-line segment suitable for
1519 // rectangle grouping (2 outline points, no arcs, not filled, not cutout).
1520 auto isRectCandidate = []( const PADS_IO::COPPER_SHAPE& cs )
1521 {
1522 return cs.outline.size() == 2 && !cs.outline[1].is_arc
1523 && !cs.filled && !cs.is_cutout;
1524 };
1525
1526 // Check if 4 consecutive entries at idx form a closed axis-aligned rectangle.
1527 // Each entry must have the same net_name and layer, and consecutive segment
1528 // endpoints must connect to form a closed cycle with only horizontal/vertical edges.
1529 auto tryFormRectangle = [&]( size_t idx, VECTOR2I& minCorner, VECTOR2I& maxCorner ) -> bool
1530 {
1531 if( idx + 3 >= copperShapes.size() )
1532 return false;
1533
1534 const auto& c0 = copperShapes[idx];
1535 const auto& c1 = copperShapes[idx + 1];
1536 const auto& c2 = copperShapes[idx + 2];
1537 const auto& c3 = copperShapes[idx + 3];
1538
1539 if( !isRectCandidate( c0 ) || !isRectCandidate( c1 )
1540 || !isRectCandidate( c2 ) || !isRectCandidate( c3 ) )
1541 {
1542 return false;
1543 }
1544
1545 if( c1.net_name != c0.net_name || c2.net_name != c0.net_name
1546 || c3.net_name != c0.net_name )
1547 {
1548 return false;
1549 }
1550
1551 if( c1.layer != c0.layer || c2.layer != c0.layer || c3.layer != c0.layer )
1552 return false;
1553
1554 // Get the 4 segment start/end pairs in scaled coordinates
1555 VECTOR2I pts[8];
1556 const PADS_IO::COPPER_SHAPE* segs[4] = { &c0, &c1, &c2, &c3 };
1557
1558 for( int i = 0; i < 4; ++i )
1559 {
1560 pts[i * 2] = VECTOR2I( scaleCoord( segs[i]->outline[0].x, true ),
1561 scaleCoord( segs[i]->outline[0].y, false ) );
1562 pts[i * 2 + 1] = VECTOR2I( scaleCoord( segs[i]->outline[1].x, true ),
1563 scaleCoord( segs[i]->outline[1].y, false ) );
1564 }
1565
1566 // Each segment must be axis-aligned
1567 for( int i = 0; i < 4; ++i )
1568 {
1569 VECTOR2I s = pts[i * 2];
1570 VECTOR2I e = pts[i * 2 + 1];
1571
1572 if( s.x != e.x && s.y != e.y )
1573 return false;
1574 }
1575
1576 // Consecutive segments must connect (end of N == start of N+1)
1577 for( int i = 0; i < 3; ++i )
1578 {
1579 if( pts[i * 2 + 1] != pts[( i + 1 ) * 2] )
1580 return false;
1581 }
1582
1583 // Cycle must close (end of last == start of first)
1584 if( pts[7] != pts[0] )
1585 return false;
1586
1587 // Compute bounding box from the 4 corner points
1588 int minX = pts[0].x, maxX = pts[0].x;
1589 int minY = pts[0].y, maxY = pts[0].y;
1590
1591 for( int i = 0; i < 8; ++i )
1592 {
1593 minX = std::min( minX, pts[i].x );
1594 maxX = std::max( maxX, pts[i].x );
1595 minY = std::min( minY, pts[i].y );
1596 maxY = std::max( maxY, pts[i].y );
1597 }
1598
1599 minCorner = VECTOR2I( minX, minY );
1600 maxCorner = VECTOR2I( maxX, maxY );
1601 return true;
1602 };
1603
1604 for( size_t idx = 0; idx < copperShapes.size(); ++idx )
1605 {
1606 const auto& copper = copperShapes[idx];
1607
1608 if( copper.outline.size() < 2 )
1609 continue;
1610
1611 if( copper.is_cutout )
1612 continue;
1613
1614 PCB_LAYER_ID layer = getMappedLayer( copper.layer );
1615
1616 if( layer == UNDEFINED_LAYER )
1617 {
1618 if( m_reporter )
1619 {
1620 m_reporter->Report( wxString::Format(
1621 _( "COPPER item on unmapped layer %d defaulting to F.Cu" ),
1622 copper.layer ),
1624 }
1625
1626 layer = F_Cu;
1627 }
1628
1629 int width = std::max( scaleSize( copper.width ), m_minObjectSize );
1630
1631 if( !IsCopperLayer( layer ) )
1632 {
1633 // Check for 4 consecutive entries forming an axis-aligned rectangle
1634 VECTOR2I minCorner, maxCorner;
1635
1636 if( tryFormRectangle( idx, minCorner, maxCorner ) )
1637 {
1638 PCB_SHAPE* rect = new PCB_SHAPE( m_loadBoard );
1640 rect->SetStart( minCorner );
1641 rect->SetEnd( maxCorner );
1642 rect->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
1643 rect->SetLayer( layer );
1644 m_loadBoard->Add( rect );
1645
1646 idx += 3;
1647 continue;
1648 }
1649
1650 // EasyEDA PADS exports place footprint silkscreen outlines in the *LINES*
1651 // section as COPPER type on the silkscreen layer. Import these as board
1652 // graphics on their actual layer rather than forcing them onto copper.
1653 for( size_t i = 0; i < copper.outline.size() - 1; ++i )
1654 {
1655 const auto& p1 = copper.outline[i];
1656 const auto& p2 = copper.outline[i + 1];
1657
1658 VECTOR2I start( scaleCoord( p1.x, true ), scaleCoord( p1.y, false ) );
1659 VECTOR2I end( scaleCoord( p2.x, true ), scaleCoord( p2.y, false ) );
1660
1661 if( ( start - end ).EuclideanNorm() < 1000 )
1662 continue;
1663
1664 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
1665
1666 if( p2.is_arc )
1667 {
1668 setPcbShapeArc( shape, p1, p2 );
1669 }
1670 else
1671 {
1672 shape->SetShape( SHAPE_T::SEGMENT );
1673 shape->SetStart( start );
1674 shape->SetEnd( end );
1675 }
1676
1677 shape->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
1678 shape->SetLayer( layer );
1679 m_loadBoard->Add( shape );
1680 }
1681
1682 continue;
1683 }
1684
1685 NETINFO_ITEM* net = nullptr;
1686
1687 if( !copper.net_name.empty() )
1688 net = m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( copper.net_name ) );
1689
1690 if( copper.filled )
1691 {
1692 if( copper.outline.size() < 3 )
1693 continue;
1694
1695 ZONE* zone = new ZONE( m_loadBoard );
1696 zone->SetLayer( layer );
1697 zone->SetIsRuleArea( false );
1698
1699 if( net )
1700 zone->SetNet( net );
1701
1702 SHAPE_LINE_CHAIN outline;
1703 appendArcPoints( outline, copper.outline );
1704 outline.SetClosed( true );
1705 zone->Outline()->AddOutline( outline );
1706 m_loadBoard->Add( zone );
1707 }
1708 else
1709 {
1710 for( size_t i = 0; i < copper.outline.size() - 1; ++i )
1711 {
1712 const auto& p1 = copper.outline[i];
1713 const auto& p2 = copper.outline[i + 1];
1714
1715 VECTOR2I start( scaleCoord( p1.x, true ), scaleCoord( p1.y, false ) );
1716 VECTOR2I end( scaleCoord( p2.x, true ), scaleCoord( p2.y, false ) );
1717
1718 if( ( start - end ).EuclideanNorm() < 1000 )
1719 continue;
1720
1721 if( p2.is_arc )
1722 {
1723 SHAPE_ARC shapeArc = makeMidpointArc( p1, p2, width );
1724
1725 PCB_ARC* arc = new PCB_ARC( m_loadBoard, &shapeArc );
1726
1727 if( net )
1728 arc->SetNet( net );
1729
1730 arc->SetWidth( width );
1731 arc->SetLayer( layer );
1732 m_loadBoard->Add( arc );
1733 }
1734 else
1735 {
1736 PCB_TRACK* track = new PCB_TRACK( m_loadBoard );
1737
1738 if( net )
1739 track->SetNet( net );
1740
1741 track->SetWidth( width );
1742 track->SetLayer( layer );
1743 track->SetStart( start );
1744 track->SetEnd( end );
1745 m_loadBoard->Add( track );
1746 }
1747 }
1748 }
1749 }
1750}
1751
1752
1754{
1755 const auto& clusters = m_parser->GetClusters();
1756
1757 if( clusters.empty() )
1758 return;
1759
1760 std::map<std::string, const PADS_IO::CLUSTER*> netToClusterMap;
1761
1762 for( const auto& cluster : clusters )
1763 {
1764 for( const std::string& netName : cluster.net_names )
1765 {
1766 std::string converted = PADS_COMMON::ConvertInvertedNetName( netName ).ToStdString();
1767 netToClusterMap[converted] = &cluster;
1768 }
1769 }
1770
1771 std::map<int, PCB_GROUP*> clusterGroups;
1772
1773 for( const auto& cluster : clusters )
1774 {
1776 group->SetName( wxString::FromUTF8( cluster.name ) );
1777 m_loadBoard->Add( group );
1778 clusterGroups[cluster.id] = group;
1779 }
1780
1781 for( PCB_TRACK* track : m_loadBoard->Tracks() )
1782 {
1783 NETINFO_ITEM* net = track->GetNet();
1784
1785 if( net )
1786 {
1787 std::string netName = net->GetNetname().ToStdString();
1788 auto clusterIt = netToClusterMap.find( netName );
1789
1790 if( clusterIt != netToClusterMap.end() )
1791 {
1792 int clusterId = clusterIt->second->id;
1793 auto groupIt = clusterGroups.find( clusterId );
1794
1795 if( groupIt != clusterGroups.end() )
1796 {
1797 groupIt->second->AddItem( track );
1798 }
1799 }
1800 }
1801 }
1802}
1803
1804
1806{
1807 const auto& pours = m_parser->GetPours();
1808 const auto& params = m_parser->GetParameters();
1809
1810 // Returns true if the points can produce a valid polygon (at least 3 vertices
1811 // for a regular polygon, or a single full-circle point).
1812 auto isValidPoly = []( const std::vector<PADS_IO::ARC_POINT>& pts )
1813 {
1814 if( pts.size() >= 3 )
1815 return true;
1816
1817 if( pts.size() == 1 && pts[0].is_arc
1818 && std::abs( pts[0].arc.delta_angle ) >= 359.0 )
1819 {
1820 return true;
1821 }
1822
1823 return false;
1824 };
1825
1826 // PADS uses lower numbers = higher priority (priority 1 fills on top),
1827 // while KiCad uses higher numbers = higher priority.
1828 int maxPriority = 0;
1829
1830 for( const auto& pour_def : pours )
1831 {
1832 if( pour_def.priority > maxPriority )
1833 maxPriority = pour_def.priority;
1834 }
1835
1836 // Map from pour name to created zone for linking HATOUT/VOIDOUT later
1837 std::map<std::string, ZONE*> pourZoneMap;
1838
1839 // Map from HATOUT name to parent POUROUT name for VOIDOUT chain resolution
1840 std::map<std::string, std::string> hatoutToParent;
1841
1842 // First pass: create zones from POUROUT records and build lookup maps
1843 for( const auto& pour_def : pours )
1844 {
1845 if( pour_def.style == PADS_IO::POUR_STYLE::HATCHED )
1846 {
1847 hatoutToParent[pour_def.name] = pour_def.owner_pour;
1848 continue;
1849 }
1850
1851 if( pour_def.style == PADS_IO::POUR_STYLE::VOIDOUT
1852 || pour_def.thermal_type != PADS_IO::THERMAL_TYPE::NONE )
1853 {
1854 continue;
1855 }
1856
1857 if( pour_def.points.size() < 3 )
1858 continue;
1859
1860 PCB_LAYER_ID pourLayer = getMappedLayer( pour_def.layer );
1861
1862 if( pourLayer == UNDEFINED_LAYER )
1863 {
1864 if( m_reporter )
1865 {
1866 m_reporter->Report( wxString::Format(
1867 _( "Skipping pour on unmapped layer %d" ), pour_def.layer ),
1869 }
1870
1871 continue;
1872 }
1873
1874 ZONE* zone = new ZONE( m_loadBoard );
1875 zone->SetLayer( pourLayer );
1876
1877 zone->Outline()->NewOutline();
1878 appendArcPoints( zone->Outline()->Outline( 0 ), pour_def.points );
1879
1880 if( pour_def.is_cutout )
1881 {
1882 zone->SetIsRuleArea( true );
1883 zone->SetDoNotAllowZoneFills( true );
1884 zone->SetDoNotAllowTracks( false );
1885 zone->SetDoNotAllowVias( false );
1886 zone->SetDoNotAllowPads( false );
1887 zone->SetDoNotAllowFootprints( false );
1888 zone->SetZoneName( wxString::Format( wxT( "Cutout_%s" ), pour_def.owner_pour ) );
1889 }
1890 else
1891 {
1892 NETINFO_ITEM* net = m_loadBoard->FindNet(
1893 PADS_COMMON::ConvertInvertedNetName( pour_def.net_name ) );
1894
1895 if( net )
1896 zone->SetNet( net );
1897
1898 int kicadPriority = maxPriority - pour_def.priority + 1;
1899 zone->SetAssignedPriority( kicadPriority );
1900 zone->SetMinThickness( scaleSize( pour_def.width ) );
1901
1902 zone->SetThermalReliefGap( scaleSize( params.thermal_min_clearance ) );
1903 zone->SetThermalReliefSpokeWidth( scaleSize( params.thermal_line_width ) );
1904
1906 }
1907
1908 pourZoneMap[pour_def.name] = zone;
1909 m_loadBoard->Add( zone );
1910 }
1911
1912 // Second pass: build fill polygons from HATOUT records with VOIDOUT holes
1913 for( const auto& pour_def : pours )
1914 {
1915 if( pour_def.style != PADS_IO::POUR_STYLE::HATCHED )
1916 continue;
1917
1918 if( !isValidPoly( pour_def.points ) )
1919 continue;
1920
1921 auto zoneIt = pourZoneMap.find( pour_def.owner_pour );
1922
1923 if( zoneIt == pourZoneMap.end() )
1924 continue;
1925
1926 ZONE* zone = zoneIt->second;
1927 PCB_LAYER_ID pourLayer = zone->GetLayer();
1928
1929 SHAPE_POLY_SET fillPoly;
1930 fillPoly.NewOutline();
1931 appendArcPoints( fillPoly.Outline( 0 ), pour_def.points );
1932
1933 // PADS HATOUT fill data can contain self-intersecting vertices where
1934 // narrow corridors route between pads. Run Clipper2 union on the
1935 // outline before subtracting holes, since Simplify can introduce
1936 // micro-artifacts in clean complex polygons.
1937 if( fillPoly.Outline( 0 ).PointCount() >= 3
1938 && fillPoly.IsPolygonSelfIntersecting( 0 ) )
1939 {
1940 fillPoly.Simplify();
1941 }
1942
1943 // Collect all matching VOIDOUT regions into a single poly set and
1944 // subtract in one operation. PADS VOIDOUT shapes can extend beyond
1945 // the HATOUT outline boundary (PADS clips at render time), so
1946 // boolean subtraction is needed rather than treating them as
1947 // contained holes. Batching avoids Clipper2 precision accumulation
1948 // from repeated sequential operations.
1949 SHAPE_POLY_SET allVoids;
1950
1951 for( const auto& void_def : pours )
1952 {
1953 if( void_def.style != PADS_IO::POUR_STYLE::VOIDOUT )
1954 continue;
1955
1956 if( !isValidPoly( void_def.points ) )
1957 continue;
1958
1959 // VOIDOUT's owner_pour points to a HATOUT name. Check if that
1960 // HATOUT is owned by our POUROUT.
1961 auto parentIt = hatoutToParent.find( void_def.owner_pour );
1962
1963 if( parentIt == hatoutToParent.end() )
1964 continue;
1965
1966 if( parentIt->second != pour_def.owner_pour )
1967 continue;
1968
1969 allVoids.NewOutline();
1970 appendArcPoints( allVoids.Outline( allVoids.OutlineCount() - 1 ), void_def.points );
1971 }
1972
1973 if( allVoids.OutlineCount() > 0 )
1974 fillPoly.BooleanSubtract( allVoids );
1975
1976 zone->SetFilledPolysList( pourLayer, fillPoly );
1977 zone->SetIsFilled( true );
1978 }
1979}
1980
1981
1983{
1984 for( const PADS_IO::POLYLINE& polyline : m_parser->GetBoardOutlines() )
1985 {
1986 const auto& pts = polyline.points;
1987
1988 if( pts.size() < 2 )
1989 continue;
1990
1991 for( size_t i = 0; i < pts.size() - 1; ++i )
1992 {
1993 const PADS_IO::ARC_POINT& p1 = pts[i];
1994 const PADS_IO::ARC_POINT& p2 = pts[i + 1];
1995
1996 if( std::abs( p1.x - p2.x ) < 0.001 && std::abs( p1.y - p2.y ) < 0.001 )
1997 continue;
1998
1999 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2000
2001 if( p2.is_arc )
2002 {
2003 setPcbShapeArc( shape, p1, p2 );
2004 }
2005 else
2006 {
2007 shape->SetShape( SHAPE_T::SEGMENT );
2008 shape->SetStart( VECTOR2I( scaleCoord( p1.x, true ),
2009 scaleCoord( p1.y, false ) ) );
2010 shape->SetEnd( VECTOR2I( scaleCoord( p2.x, true ),
2011 scaleCoord( p2.y, false ) ) );
2012 }
2013
2014 shape->SetWidth( scaleSize( polyline.width ) );
2015 shape->SetLayer( Edge_Cuts );
2016 m_loadBoard->Add( shape );
2017 }
2018
2019 // PADS format repeats the first point at the end for closed polygons, so check
2020 // if pLast already equals pFirst to avoid creating a zero-length closing segment
2021 if( polyline.closed && pts.size() > 2 )
2022 {
2023 const PADS_IO::ARC_POINT& pLast = pts.back();
2024 const PADS_IO::ARC_POINT& pFirst = pts.front();
2025
2026 bool needsClosing = ( std::abs( pLast.x - pFirst.x ) > 0.001
2027 || std::abs( pLast.y - pFirst.y ) > 0.001 );
2028
2029 if( needsClosing )
2030 {
2031 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2032
2033 if( pFirst.is_arc )
2034 {
2035 setPcbShapeArc( shape, pLast, pFirst );
2036 }
2037 else
2038 {
2039 shape->SetShape( SHAPE_T::SEGMENT );
2040 shape->SetStart( VECTOR2I( scaleCoord( pLast.x, true ),
2041 scaleCoord( pLast.y, false ) ) );
2042 shape->SetEnd( VECTOR2I( scaleCoord( pFirst.x, true ),
2043 scaleCoord( pFirst.y, false ) ) );
2044 }
2045
2046 shape->SetWidth( scaleSize( polyline.width ) );
2047 shape->SetLayer( Edge_Cuts );
2048 m_loadBoard->Add( shape );
2049 }
2050 }
2051 }
2052}
2053
2054
2056{
2057 const auto& dimensions = m_parser->GetDimensions();
2058
2059 for( const auto& dim : dimensions )
2060 {
2061 if( dim.points.size() < 2 )
2062 continue;
2063
2065
2066 VECTOR2I start( scaleCoord( dim.points[0].x, true ),
2067 scaleCoord( dim.points[0].y, false ) );
2068 VECTOR2I end( scaleCoord( dim.points[1].x, true ),
2069 scaleCoord( dim.points[1].y, false ) );
2070
2071 // PADS horizontal/vertical dimensions measure only the X or Y projection.
2072 // PCB_DIM_ALIGNED measures along the start→end direction, so if the base
2073 // points differ on the non-measured axis the line becomes skewed.
2074 // Project the end point onto the measurement axis.
2075 if( dim.is_horizontal )
2076 end.y = start.y;
2077 else
2078 end.x = start.x;
2079
2080 dimension->SetStart( start );
2081 dimension->SetEnd( end );
2082
2083 // The crossbar_pos is the absolute coordinate of the crossbar. We compute
2084 // height as the offset from the start point to the crossbar.
2085 if( dim.is_horizontal )
2086 {
2087 double heightOffset = dim.crossbar_pos - dim.points[0].y;
2088 int height = -scaleSize( heightOffset );
2089 dimension->SetHeight( height );
2090 }
2091 else
2092 {
2093 double heightOffset = dim.crossbar_pos - dim.points[0].x;
2094 int height = scaleSize( heightOffset );
2095 dimension->SetHeight( height );
2096 }
2097
2098 PCB_LAYER_ID dimLayer = getMappedLayer( dim.layer );
2099
2100 if( dimLayer == UNDEFINED_LAYER || IsCopperLayer( dimLayer ) )
2101 dimLayer = Cmts_User;
2102
2103 dimension->SetLayer( dimLayer );
2104
2105 // PADS text_width is stroke thickness, not character width.
2106 // Calculate character dimensions from height.
2107 if( dim.text_height > 0 )
2108 {
2109 int scaledSize = scaleSize( dim.text_height );
2110 int charHeight =
2111 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextHeightScale );
2112 int charWidth =
2113 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextWidthScale );
2114 dimension->SetTextSize( VECTOR2I( charWidth, charHeight ) );
2115
2116 if( dim.text_width > 0 )
2117 dimension->SetTextThickness( scaleSize( dim.text_width ) );
2118 }
2119
2120 if( !dim.text.empty() )
2121 {
2122 dimension->SetOverrideTextEnabled( true );
2123 dimension->SetOverrideText( wxString::FromUTF8( dim.text ) );
2124 }
2125
2126 dimension->SetLineThickness( scaleSize( 5.0 ) );
2127
2128 if( dim.rotation != 0.0 )
2129 dimension->SetTextAngle( EDA_ANGLE( dim.rotation, DEGREES_T ) );
2130
2131 dimension->Update();
2132 m_loadBoard->Add( dimension );
2133 }
2134}
2135
2136
2138{
2139 const auto& keepouts = m_parser->GetKeepouts();
2140 int keepoutIndex = 0;
2141
2142 for( const auto& ko : keepouts )
2143 {
2144 if( ko.outline.size() < 3 )
2145 continue;
2146
2147 ZONE* zone = new ZONE( m_loadBoard );
2148 zone->SetIsRuleArea( true );
2149
2150 if( ko.layers.empty() )
2151 {
2152 zone->SetLayerSet( LSET::AllCuMask() );
2153 }
2154 else if( ko.layers.size() == 1 )
2155 {
2156 PCB_LAYER_ID koLayer = getMappedLayer( ko.layers[0] );
2157
2158 if( koLayer == UNDEFINED_LAYER )
2159 {
2160 if( m_reporter )
2161 {
2162 m_reporter->Report( wxString::Format(
2163 _( "Skipping keepout on unmapped layer %d" ), ko.layers[0] ),
2165 }
2166 delete zone;
2167 continue;
2168 }
2169
2170 zone->SetLayer( koLayer );
2171 }
2172 else
2173 {
2174 LSET layerSet;
2175
2176 for( int layer : ko.layers )
2177 {
2178 PCB_LAYER_ID mappedLayer = getMappedLayer( layer );
2179
2180 if( mappedLayer != UNDEFINED_LAYER )
2181 layerSet.set( mappedLayer );
2182 }
2183
2184 if( layerSet.none() )
2185 {
2186 if( m_reporter )
2187 m_reporter->Report( _( "Skipping keepout with no valid layers" ), RPT_SEVERITY_WARNING );
2188 delete zone;
2189 continue;
2190 }
2191
2192 zone->SetLayerSet( layerSet );
2193 }
2194
2195 zone->SetDoNotAllowTracks( ko.no_traces );
2196 zone->SetDoNotAllowVias( ko.no_vias );
2197 zone->SetDoNotAllowZoneFills( ko.no_copper );
2198 zone->SetDoNotAllowFootprints( ko.no_components );
2199 zone->SetDoNotAllowPads( false );
2200
2201 wxString typeName;
2202
2203 switch( ko.type )
2204 {
2205 case PADS_IO::KEEPOUT_TYPE::ALL: typeName = wxT( "Keepout" ); break;
2206 case PADS_IO::KEEPOUT_TYPE::ROUTE: typeName = wxT( "RouteKeepout" ); break;
2207 case PADS_IO::KEEPOUT_TYPE::VIA: typeName = wxT( "ViaKeepout" ); break;
2208 case PADS_IO::KEEPOUT_TYPE::COPPER: typeName = wxT( "CopperKeepout" ); break;
2209 case PADS_IO::KEEPOUT_TYPE::PLACEMENT: typeName = wxT( "PlacementKeepout" ); break;
2210 }
2211
2212 zone->SetZoneName( wxString::Format( wxT( "%s_%d" ), typeName, ++keepoutIndex ) );
2213
2214 SHAPE_LINE_CHAIN koChain;
2215 appendArcPoints( koChain, ko.outline );
2216
2217 // Close the outline if first and last points don't match
2218 if( ko.outline.size() > 2 )
2219 {
2220 const auto& first = ko.outline.front();
2221 const auto& last = ko.outline.back();
2222
2223 if( std::abs( first.x - last.x ) > 0.001 || std::abs( first.y - last.y ) > 0.001 )
2224 koChain.Append( scaleCoord( first.x, true ), scaleCoord( first.y, false ) );
2225 }
2226
2227 koChain.SetClosed( true );
2228 zone->Outline()->AddOutline( koChain );
2229
2230 m_loadBoard->Add( zone );
2231 }
2232}
2233
2234
2236{
2237 for( const PADS_IO::GRAPHIC_LINE& graphic : m_parser->GetGraphicLines() )
2238 {
2239 const auto& pts = graphic.points;
2240
2241 PCB_LAYER_ID graphicLayer = getMappedLayer( graphic.layer );
2242
2243 if( graphicLayer == UNDEFINED_LAYER )
2244 continue;
2245
2246 if( pts.size() == 1 && pts[0].is_arc
2247 && std::abs( pts[0].arc.delta_angle - 360.0 ) < 0.1 )
2248 {
2249 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2250 shape->SetShape( SHAPE_T::CIRCLE );
2251 VECTOR2I center( scaleCoord( pts[0].arc.cx, true ),
2252 scaleCoord( pts[0].arc.cy, false ) );
2253 int radius = std::max( scaleSize( pts[0].arc.radius ), m_minObjectSize );
2254 shape->SetCenter( center );
2255 shape->SetEnd( VECTOR2I( center.x + radius, center.y ) );
2256 shape->SetWidth( scaleSize( graphic.width ) );
2257 shape->SetLayer( graphicLayer );
2258 m_loadBoard->Add( shape );
2259 continue;
2260 }
2261
2262 if( pts.size() < 2 )
2263 continue;
2264
2265 for( size_t i = 0; i < pts.size() - 1; ++i )
2266 {
2267 const PADS_IO::ARC_POINT& p1 = pts[i];
2268 const PADS_IO::ARC_POINT& p2 = pts[i + 1];
2269
2270 if( std::abs( p1.x - p2.x ) < 0.001 && std::abs( p1.y - p2.y ) < 0.001 )
2271 continue;
2272
2273 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2274
2275 if( p2.is_arc )
2276 {
2277 setPcbShapeArc( shape, p1, p2 );
2278 }
2279 else
2280 {
2281 shape->SetShape( SHAPE_T::SEGMENT );
2282 shape->SetStart( VECTOR2I( scaleCoord( p1.x, true ),
2283 scaleCoord( p1.y, false ) ) );
2284 shape->SetEnd( VECTOR2I( scaleCoord( p2.x, true ),
2285 scaleCoord( p2.y, false ) ) );
2286 }
2287
2288 shape->SetWidth( scaleSize( graphic.width ) );
2289 shape->SetLayer( graphicLayer );
2290 m_loadBoard->Add( shape );
2291 }
2292
2293 if( graphic.closed && pts.size() > 2 )
2294 {
2295 const PADS_IO::ARC_POINT& pLast = pts.back();
2296 const PADS_IO::ARC_POINT& pFirst = pts.front();
2297
2298 bool needsClosing = ( std::abs( pLast.x - pFirst.x ) > 0.001
2299 || std::abs( pLast.y - pFirst.y ) > 0.001 );
2300
2301 if( needsClosing )
2302 {
2303 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2304
2305 if( pFirst.is_arc )
2306 {
2307 setPcbShapeArc( shape, pLast, pFirst );
2308 }
2309 else
2310 {
2311 shape->SetShape( SHAPE_T::SEGMENT );
2312 shape->SetStart( VECTOR2I( scaleCoord( pLast.x, true ),
2313 scaleCoord( pLast.y, false ) ) );
2314 shape->SetEnd( VECTOR2I( scaleCoord( pFirst.x, true ),
2315 scaleCoord( pFirst.y, false ) ) );
2316 }
2317
2318 shape->SetWidth( scaleSize( graphic.width ) );
2319 shape->SetLayer( graphicLayer );
2320 m_loadBoard->Add( shape );
2321 }
2322 }
2323 }
2324}
2325
2326
2327void PCB_IO_PADS::generateDrcRules( const wxString& aFileName )
2328{
2329 wxFileName fn( aFileName );
2330 fn.SetExt( wxT( "kicad_dru" ) );
2331
2332 wxString customRules = wxT( "(version 1)\n" );
2333
2334 const auto& diffPairs = m_parser->GetDiffPairs();
2335
2336 for( const auto& dp : diffPairs )
2337 {
2338 if( dp.name.empty() || ( dp.gap <= 0 && dp.width <= 0 ) )
2339 continue;
2340
2341 wxString ruleName = wxString::Format( wxT( "DiffPair_%s" ), wxString::FromUTF8( dp.name ) );
2342
2343 if( dp.gap > 0 && !dp.positive_net.empty() && !dp.negative_net.empty() )
2344 {
2345 wxString posNet = PADS_COMMON::ConvertInvertedNetName( dp.positive_net );
2346 wxString negNet = PADS_COMMON::ConvertInvertedNetName( dp.negative_net );
2347 double gapMm = dp.gap * m_scaleFactor / PADS_UNIT_CONVERTER::MM_TO_NM;
2348 wxString gapStr = wxString::FromUTF8( FormatDouble2Str( gapMm ) ) + wxT( "mm" );
2349
2350 customRules += wxString::Format(
2351 wxT( "\n(rule \"%s_gap\"\n" )
2352 wxT( " (condition \"A.NetName == '%s' && B.NetName == '%s'\")\n" )
2353 wxT( " (constraint clearance (min %s)))\n" ),
2354 ruleName, posNet, negNet, gapStr );
2355 }
2356 }
2357
2358 if( customRules.length() > 15 )
2359 {
2360 wxFile rulesFile( fn.GetFullPath(), wxFile::write );
2361
2362 if( rulesFile.IsOpened() )
2363 rulesFile.Write( customRules );
2364 }
2365}
2366
2367
2369{
2370 if( !m_reporter )
2371 return;
2372
2373 size_t trackCount = 0;
2374 size_t viaCount = 0;
2375
2376 for( PCB_TRACK* track : m_loadBoard->Tracks() )
2377 {
2378 if( track->Type() == PCB_VIA_T )
2379 viaCount++;
2380 else
2381 trackCount++;
2382 }
2383
2384 m_reporter->Report( wxString::Format( _( "Imported %zu footprints, %d nets, %zu tracks,"
2385 " %zu vias, %zu zones" ),
2386 m_loadBoard->Footprints().size(),
2387 m_loadBoard->GetNetCount(),
2388 trackCount, viaCount,
2389 m_loadBoard->Zones().size() ),
2391}
2392
2393
2394std::map<wxString, PCB_LAYER_ID> PCB_IO_PADS::DefaultLayerMappingCallback(
2395 const std::vector<INPUT_LAYER_DESC>& aInputLayerDescriptionVector )
2396{
2397 std::map<wxString, PCB_LAYER_ID> layer_map;
2398
2399 for( const INPUT_LAYER_DESC& layer : aInputLayerDescriptionVector )
2400 {
2401 layer_map[layer.Name] = layer.AutoMapLayer;
2402 }
2403
2404 return layer_map;
2405}
2406
2407
2408int PCB_IO_PADS::scaleSize( double aVal ) const
2409{
2410 int64_t nm = m_unitConverter.ToNanometersSize( aVal );
2411 return static_cast<int>( std::clamp<int64_t>( nm, INT_MIN, INT_MAX ) );
2412}
2413
2414
2415int PCB_IO_PADS::scaleCoord( double aVal, bool aIsX ) const
2416{
2417 double origin = aIsX ? m_originX : m_originY;
2418
2419 long long origin_nm = static_cast<long long>( std::round( origin * m_scaleFactor ) );
2420 long long val_nm = static_cast<long long>( std::round( aVal * m_scaleFactor ) );
2421
2422 long long result = aIsX ? ( val_nm - origin_nm ) : ( origin_nm - val_nm );
2423 return static_cast<int>( std::clamp<long long>( result, INT_MIN, INT_MAX ) );
2424}
2425
2426
2428{
2429 for( const auto& info : m_layerInfos )
2430 {
2431 if( info.padsLayerNum == aPadsLayer )
2432 {
2433 auto it = m_layer_map.find( wxString::FromUTF8( info.name ) );
2434
2435 if( it != m_layer_map.end() && it->second != UNDEFINED_LAYER )
2436 return it->second;
2437
2438 return m_layerMapper.GetAutoMapLayer( aPadsLayer, info.type );
2439 }
2440 }
2441
2442 return m_layerMapper.GetAutoMapLayer( aPadsLayer );
2443}
2444
2445
2446void PCB_IO_PADS::ensureNet( const std::string& aNetName )
2447{
2448 if( aNetName.empty() )
2449 return;
2450
2451 wxString wxName = PADS_COMMON::ConvertInvertedNetName( aNetName );
2452
2453 if( m_loadBoard->FindNet( wxName ) == nullptr )
2454 {
2455 NETINFO_ITEM* net = new NETINFO_ITEM( m_loadBoard, wxName,
2456 m_loadBoard->GetNetCount() + 1 );
2457 m_loadBoard->Add( net );
2458 }
2459}
2460
2461
2463{
2464 m_loadBoard = nullptr;
2465 m_parser = nullptr;
2468 m_layerInfos.clear();
2469 m_scaleFactor = 0.0;
2470 m_originX = 0.0;
2471 m_originY = 0.0;
2472 m_pinToNetMap.clear();
2473 m_partToBlockMap.clear();
2474 m_testPointIndex = 1;
2475}
2476
2477
2479 const std::vector<PADS_IO::ARC_POINT>& aPts )
2480{
2481 if( aPts.empty() )
2482 return;
2483
2484 // Single full-circle entry becomes a 36-segment polygon
2485 if( aPts.size() == 1 && aPts[0].is_arc
2486 && std::abs( aPts[0].arc.delta_angle ) >= 359.0 )
2487 {
2488 VECTOR2I center( scaleCoord( aPts[0].arc.cx, true ),
2489 scaleCoord( aPts[0].arc.cy, false ) );
2490 int radius = scaleSize( aPts[0].arc.radius );
2491
2492 constexpr int NUM_SEGS = 36;
2493
2494 for( int i = 0; i < NUM_SEGS; i++ )
2495 {
2496 double angle = 2.0 * M_PI * i / NUM_SEGS;
2497 aChain.Append( center.x + KiROUND( radius * cos( angle ) ),
2498 center.y + KiROUND( radius * sin( angle ) ) );
2499 }
2500
2501 return;
2502 }
2503
2504 aChain.Append( scaleCoord( aPts[0].x, true ), scaleCoord( aPts[0].y, false ) );
2505
2506 for( size_t i = 1; i < aPts.size(); i++ )
2507 {
2508 const auto& pt = aPts[i];
2509
2510 if( pt.is_arc )
2511 {
2512 SHAPE_ARC arc = makeMidpointArc( aPts[i - 1], pt, 0 );
2513 const SHAPE_LINE_CHAIN arcPoly = arc.ConvertToPolyline();
2514
2515 for( int j = 1; j < arcPoly.PointCount(); j++ )
2516 aChain.Append( arcPoly.CPoint( j ).x, arcPoly.CPoint( j ).y );
2517 }
2518 else
2519 {
2520 aChain.Append( scaleCoord( pt.x, true ), scaleCoord( pt.y, false ) );
2521 }
2522 }
2523}
2524
2525
2527 const PADS_IO::ARC_POINT& aCurr )
2528{
2529 aShape->SetShape( SHAPE_T::ARC );
2530
2531 VECTOR2I center( scaleCoord( aCurr.arc.cx, true ), scaleCoord( aCurr.arc.cy, false ) );
2532 VECTOR2I start( scaleCoord( aPrev.x, true ), scaleCoord( aPrev.y, false ) );
2533 VECTOR2I end( scaleCoord( aCurr.x, true ), scaleCoord( aCurr.y, false ) );
2534
2535 // Y-axis flip reverses arc winding; swap endpoints for CCW arcs
2536 if( aCurr.arc.delta_angle > 0 )
2537 std::swap( start, end );
2538
2539 aShape->SetCenter( center );
2540 aShape->SetStart( start );
2541 aShape->SetEnd( end );
2542}
2543
2544
2546 const PADS_IO::ARC_POINT& aCurr, int aWidth )
2547{
2548 VECTOR2I start( scaleCoord( aPrev.x, true ), scaleCoord( aPrev.y, false ) );
2549 VECTOR2I end( scaleCoord( aCurr.x, true ), scaleCoord( aCurr.y, false ) );
2550
2551 // Compute the arc midpoint in PADS coordinate space (before the Y-axis
2552 // flip in scaleCoord) so the 3-point constructor gets the correct winding.
2553 double startAngleRad = atan2( aPrev.y - aCurr.arc.cy, aPrev.x - aCurr.arc.cx );
2554 double midAngleRad = startAngleRad + ( aCurr.arc.delta_angle * M_PI / 180.0 ) / 2.0;
2555
2556 double midX = aCurr.arc.cx + aCurr.arc.radius * cos( midAngleRad );
2557 double midY = aCurr.arc.cy + aCurr.arc.radius * sin( midAngleRad );
2558
2559 VECTOR2I mid( scaleCoord( midX, true ), scaleCoord( midY, false ) );
2560
2561 return SHAPE_ARC( start, mid, end, aWidth );
2562}
2563
2564
2566{
2567 m_layerMapper.SetCopperLayerCount( m_parser->GetParameters().layer_count );
2568
2569 std::vector<PADS_IO::LAYER_INFO> padsLayerInfos = m_parser->GetLayerInfos();
2570
2571 auto convertLayerType = []( PADS_IO::PADS_LAYER_FUNCTION func ) -> PADS_LAYER_TYPE {
2572 switch( func )
2573 {
2590 default:
2592 }
2593 };
2594
2595 for( const auto& padsInfo : padsLayerInfos )
2596 {
2598 info.padsLayerNum = padsInfo.number;
2599 info.name = padsInfo.name;
2600
2601 if( padsInfo.layer_type != PADS_IO::PADS_LAYER_FUNCTION::UNKNOWN
2602 && padsInfo.layer_type != PADS_IO::PADS_LAYER_FUNCTION::UNASSIGNED )
2603 {
2604 info.type = convertLayerType( padsInfo.layer_type );
2605
2606 std::string lowerName = padsInfo.name;
2607 std::transform( lowerName.begin(), lowerName.end(), lowerName.begin(),
2608 []( unsigned char c ){ return std::tolower( c ); } );
2609
2610 bool isBottom = lowerName.find( "bottom" ) != std::string::npos
2611 || lowerName.find( "bot" ) != std::string::npos;
2612
2613 if( info.type == PADS_LAYER_TYPE::SOLDERMASK_TOP && isBottom )
2615 else if( info.type == PADS_LAYER_TYPE::PASTE_TOP && isBottom )
2617 else if( info.type == PADS_LAYER_TYPE::SILKSCREEN_TOP && isBottom )
2619 else if( info.type == PADS_LAYER_TYPE::ASSEMBLY_TOP && isBottom )
2621 else if( info.type == PADS_LAYER_TYPE::COPPER_INNER )
2622 {
2623 if( padsInfo.number == 1 )
2625 else if( padsInfo.number == m_parser->GetParameters().layer_count )
2627 }
2628 }
2629 else
2630 {
2631 info.type = m_layerMapper.GetLayerType( padsInfo.number );
2632 }
2633
2634 info.required = padsInfo.required;
2635 m_layerInfos.push_back( info );
2636 }
2637
2638 std::vector<INPUT_LAYER_DESC> inputDescs =
2639 m_layerMapper.BuildInputLayerDescriptions( m_layerInfos );
2640
2642 m_layer_map = m_layer_mapping_handler( inputDescs );
2643
2644 int copperLayerCount = m_parser->GetParameters().layer_count;
2645
2646 if( copperLayerCount < 1 )
2647 copperLayerCount = 2;
2648
2649 m_loadBoard->SetCopperLayerCount( copperLayerCount );
2650
2651 if( m_parser->IsBasicUnits() )
2652 {
2653 m_unitConverter.SetBasicUnitsMode( true );
2654 }
2655 else
2656 {
2657 switch( m_parser->GetParameters().units )
2658 {
2660 m_unitConverter.SetBaseUnits( PADS_UNIT_TYPE::MILS );
2661 break;
2664 break;
2667 break;
2668 }
2669 }
2670
2671 m_scaleFactor = m_parser->IsBasicUnits()
2673 : ( m_parser->GetParameters().units == PADS_IO::UNIT_TYPE::MILS
2675 : m_parser->GetParameters().units == PADS_IO::UNIT_TYPE::METRIC
2678
2679 const auto& designRules = m_parser->GetDesignRules();
2680 BOARD_DESIGN_SETTINGS& bds = m_loadBoard->GetDesignSettings();
2681
2682 bds.m_MinClearance = scaleSize( designRules.min_clearance );
2683 bds.m_TrackMinWidth = scaleSize( designRules.min_track_width );
2684 bds.m_ViasMinSize = scaleSize( designRules.min_via_size );
2685 bds.m_MinThroughDrill = scaleSize( designRules.min_via_drill );
2686 bds.m_HoleToHoleMin = scaleSize( designRules.hole_to_hole );
2687 bds.m_SilkClearance = scaleSize( designRules.silk_clearance );
2688 bds.m_SolderMaskExpansion = scaleSize( designRules.mask_clearance );
2689 bds.m_CopperEdgeClearance = scaleSize( designRules.copper_edge_clearance );
2690
2691 // Do not set the default zone clearance from the PADS design rules. In PADS,
2692 // zone (copper pour) clearance is resolved through the net/netclass clearance
2693 // rules rather than a board-level zone clearance setting. The default netclass
2694 // clearance set below is the correct mapping for PADS' DEFAULTCLEAR value.
2695
2696 bds.SetCustomTrackWidth( scaleSize( designRules.default_track_width ) );
2697 bds.SetCustomViaSize( scaleSize( designRules.default_via_size ) );
2698 bds.SetCustomViaDrill( scaleSize( designRules.default_via_drill ) );
2699
2700 std::shared_ptr<NETCLASS> defaultNetclass = bds.m_NetSettings->GetDefaultNetclass();
2701
2702 if( defaultNetclass )
2703 {
2704 defaultNetclass->SetClearance( scaleSize( designRules.default_clearance ) );
2705 defaultNetclass->SetTrackWidth( scaleSize( designRules.default_track_width ) );
2706 defaultNetclass->SetViaDiameter( scaleSize( designRules.default_via_size ) );
2707 defaultNetclass->SetViaDrill( scaleSize( designRules.default_via_drill ) );
2708 }
2709
2710 const auto& viaDefs = m_parser->GetViaDefs();
2711
2712 if( !viaDefs.empty() )
2713 {
2714 // Use the file's designated default signal via, falling back to the first
2715 // definition if no explicit default was specified
2716 const std::string& defaultViaName = m_parser->GetParameters().default_signal_via;
2717 auto defaultIt = viaDefs.find( defaultViaName );
2718
2719 if( defaultIt == viaDefs.end() )
2720 defaultIt = viaDefs.begin();
2721
2722 int viaDia = scaleSize( defaultIt->second.size );
2723 int viaDrill = scaleSize( defaultIt->second.drill );
2724
2725 bds.SetCustomViaSize( viaDia );
2726 bds.SetCustomViaDrill( viaDrill );
2727
2728 if( defaultNetclass )
2729 {
2730 defaultNetclass->SetViaDiameter( viaDia );
2731 defaultNetclass->SetViaDrill( viaDrill );
2732 }
2733
2734 for( const auto& [name, def] : viaDefs )
2735 bds.m_ViasDimensionsList.emplace_back( scaleSize( def.size ), scaleSize( def.drill ) );
2736 }
2737
2738 const auto& netClasses = m_parser->GetNetClasses();
2739
2740 for( const auto& nc : netClasses )
2741 {
2742 if( nc.name.empty() )
2743 continue;
2744
2745 wxString ncName = wxString::FromUTF8( nc.name );
2746 std::shared_ptr<NETCLASS> netclass = std::make_shared<NETCLASS>( ncName );
2747
2748 if( nc.clearance > 0 )
2749 netclass->SetClearance( scaleSize( nc.clearance ) );
2750
2751 if( nc.track_width > 0 )
2752 netclass->SetTrackWidth( scaleSize( nc.track_width ) );
2753
2754 if( nc.via_size > 0 )
2755 netclass->SetViaDiameter( scaleSize( nc.via_size ) );
2756
2757 if( nc.via_drill > 0 )
2758 netclass->SetViaDrill( scaleSize( nc.via_drill ) );
2759
2760 if( nc.diff_pair_width > 0 )
2761 netclass->SetDiffPairWidth( scaleSize( nc.diff_pair_width ) );
2762
2763 if( nc.diff_pair_gap > 0 )
2764 netclass->SetDiffPairGap( scaleSize( nc.diff_pair_gap ) );
2765
2766 bds.m_NetSettings->SetNetclass( ncName, netclass );
2767
2768 for( const std::string& netName : nc.net_names )
2769 {
2770 wxString wxNetName = PADS_COMMON::ConvertInvertedNetName( netName );
2771 bds.m_NetSettings->SetNetclassPatternAssignment( wxNetName, ncName );
2772 }
2773 }
2774
2775 const auto& diffPairs = m_parser->GetDiffPairs();
2776
2777 for( const auto& dp : diffPairs )
2778 {
2779 if( dp.name.empty() )
2780 continue;
2781
2782 wxString dpClassName =
2783 wxString::Format( wxT( "DiffPair_%s" ), wxString::FromUTF8( dp.name ) );
2784 std::shared_ptr<NETCLASS> dpNetclass = std::make_shared<NETCLASS>( dpClassName );
2785
2786 if( dp.gap > 0 )
2787 dpNetclass->SetDiffPairGap( scaleSize( dp.gap ) );
2788
2789 if( dp.width > 0 )
2790 {
2791 dpNetclass->SetDiffPairWidth( scaleSize( dp.width ) );
2792 dpNetclass->SetTrackWidth( scaleSize( dp.width ) );
2793 }
2794
2795 bds.m_NetSettings->SetNetclass( dpClassName, dpNetclass );
2796
2797 if( !dp.positive_net.empty() )
2798 {
2799 wxString wxPosNet = PADS_COMMON::ConvertInvertedNetName( dp.positive_net );
2800 bds.m_NetSettings->SetNetclassPatternAssignment( wxPosNet, dpClassName );
2801 }
2802
2803 if( !dp.negative_net.empty() )
2804 {
2805 wxString wxNegNet = PADS_COMMON::ConvertInvertedNetName( dp.negative_net );
2806 bds.m_NetSettings->SetNetclassPatternAssignment( wxNegNet, dpClassName );
2807 }
2808 }
2809
2810 m_originX = m_parser->GetParameters().origin.x;
2811 m_originY = m_parser->GetParameters().origin.y;
2812
2813 const auto& boardOutlines = m_parser->GetBoardOutlines();
2814
2815 if( !boardOutlines.empty() )
2816 {
2817 double min_x = std::numeric_limits<double>::max();
2818 double max_x = std::numeric_limits<double>::lowest();
2819 double min_y = std::numeric_limits<double>::max();
2820 double max_y = std::numeric_limits<double>::lowest();
2821
2822 for( const auto& outline : boardOutlines )
2823 {
2824 for( const auto& pt : outline.points )
2825 {
2826 min_x = std::min( min_x, pt.x );
2827 max_x = std::max( max_x, pt.x );
2828 min_y = std::min( min_y, pt.y );
2829 max_y = std::max( max_y, pt.y );
2830 }
2831 }
2832
2833 if( min_x < max_x && min_y < max_y )
2834 {
2835 m_originX = ( min_x + max_x ) / 2.0;
2836 m_originY = ( min_y + max_y ) / 2.0;
2837 }
2838 }
2839
2840 // Build board stackup from LAYER DATA if meaningful data exists.
2841 // Collect copper layer infos ordered by PADS layer number.
2842 std::vector<const PADS_IO::LAYER_INFO*> copperLayerInfos;
2843
2844 for( const auto& li : padsLayerInfos )
2845 {
2846 if( li.is_copper )
2847 copperLayerInfos.push_back( &li );
2848 }
2849
2850 bool hasStackupData = false;
2851
2852 for( const auto* li : copperLayerInfos )
2853 {
2854 if( li->layer_thickness > 0.0 || li->dielectric_constant > 0.0 )
2855 {
2856 hasStackupData = true;
2857 break;
2858 }
2859 }
2860
2861 if( hasStackupData )
2862 {
2863 BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
2864 stackup.RemoveAll();
2865 stackup.BuildDefaultStackupList( &bds, copperLayerCount );
2866
2867 // Build a map from KiCad PCB_LAYER_ID to PADS LAYER_INFO for copper layers
2868 std::map<PCB_LAYER_ID, const PADS_IO::LAYER_INFO*> copperInfoMap;
2869
2870 for( const auto* li : copperLayerInfos )
2871 {
2872 PCB_LAYER_ID kicadLayer = getMappedLayer( li->number );
2873
2874 if( kicadLayer != UNDEFINED_LAYER )
2875 copperInfoMap[kicadLayer] = li;
2876 }
2877
2878 // Track the previous copper layer's info for dielectric assignment
2879 const PADS_IO::LAYER_INFO* prevCopperInfo = nullptr;
2880
2881 for( BOARD_STACKUP_ITEM* item : stackup.GetList() )
2882 {
2883 if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_COPPER )
2884 {
2885 auto it = copperInfoMap.find( item->GetBrdLayerId() );
2886
2887 if( it != copperInfoMap.end() )
2888 {
2889 prevCopperInfo = it->second;
2890
2891 if( it->second->copper_thickness > 0.0 )
2892 item->SetThickness( scaleSize( it->second->copper_thickness ) );
2893 }
2894 }
2895 else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_DIELECTRIC )
2896 {
2897 if( prevCopperInfo )
2898 {
2899 if( prevCopperInfo->layer_thickness > 0.0 )
2900 item->SetThickness( scaleSize( prevCopperInfo->layer_thickness ) );
2901
2902 if( prevCopperInfo->dielectric_constant > 0.0 )
2903 item->SetEpsilonR( prevCopperInfo->dielectric_constant );
2904 }
2905 }
2906 else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SILKSCREEN )
2907 {
2908 item->SetColor( wxT( "White" ) );
2909 }
2910 else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SOLDERMASK )
2911 {
2912 item->SetColor( wxT( "Green" ) );
2913 }
2914 }
2915
2916 int thickness = stackup.BuildBoardThicknessFromStackup();
2917 bds.SetBoardThickness( thickness );
2918 bds.m_HasStackup = true;
2919 }
2920}
const char * name
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_SILKSCREEN
@ BS_ITEM_TYPE_DIELECTRIC
@ BS_ITEM_TYPE_SOLDERMASK
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
BASE_SET & set(size_t pos)
Definition base_set.h:116
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
void SetCustomTrackWidth(int aWidth)
Sets custom width for track (i.e.
void SetCustomViaSize(int aSize)
Set custom size for via diameter (i.e.
BOARD_STACKUP & GetStackupDescriptor()
void SetCustomViaDrill(int aDrill)
Sets custom size for via drill (i.e.
void SetBoardThickness(int aThickness)
std::vector< VIA_DIMENSION > m_ViasDimensionsList
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:285
Manage one layer needed to make a physical board.
Manage layers needed to make a physical board.
void RemoveAll()
Delete all items in list and clear the list.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
int BuildBoardThicknessFromStackup() const
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
void SetCenter(const VECTOR2I &aCenter)
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:178
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:168
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:220
void SetWidth(int aWidth)
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:546
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:431
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:400
void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition eda_text.cpp:298
void SetKeepUpright(bool aKeepUpright)
Definition eda_text.cpp:439
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:284
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition eda_text.cpp:313
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:423
void SetPosition(const VECTOR2I &aPos) override
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:352
void SetOrientation(const EDA_ANGLE &aNewAngle)
void SetPath(const KIID_PATH &aPath)
Definition footprint.h:375
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:787
void SetReference(const wxString &aReference)
Definition footprint.h:757
void SetValue(const wxString &aValue)
Definition footprint.h:778
PCB_FIELD & Reference()
Definition footprint.h:788
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection) override
Flip this object, i.e.
void SetBoardOnly(bool aIsBoardOnly=true)
Definition footprint.h:853
VECTOR2I GetPosition() const override
Definition footprint.h:327
REPORTER * m_reporter
Reporter to log errors/warnings to, may be nullptr.
Definition io_base.h:237
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:240
Definition kiid.h:49
virtual void RegisterCallback(LAYER_MAPPING_HANDLER aLayerMappingHandler)
Register a different handler to be called when mapping of input layers to KiCad layers occurs.
LAYER_MAPPING_HANDLER m_layer_mapping_handler
Callback to get layer mapping.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:49
int SetLibItemName(const UTF8 &aLibItemName)
Override the library item name portion of the LIB_ID to aLibItemName.
Definition lib_id.cpp:111
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:41
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:608
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:599
Handle the data for a net.
Definition netinfo.h:54
const wxString & GetNetname() const
Definition netinfo.h:112
void SetNetclassPatternAssignment(const wxString &pattern, const wxString &netclass)
Sets a netclass pattern assignment Calling this method will reset the effective netclass calculation ...
std::shared_ptr< NETCLASS > GetDefaultNetclass()
Gets the default netclass for the project.
void SetNetclass(const wxString &netclassName, std::shared_ptr< NETCLASS > &netclass)
Sets the given netclass Calling user is responsible for resetting the effective netclass calculation ...
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:172
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
void Parse(const wxString &aFileName)
Maps PADS layer numbers and names to KiCad layer IDs.
Converts PADS file format units to KiCad internal units (nanometers).
static constexpr double MILS_TO_NM
static constexpr double INCHES_TO_NM
static constexpr double BASIC_TO_NM
static constexpr double MM_TO_NM
Definition pad.h:55
void Update()
Update the dimension's cached text and geometry.
void SetOverrideTextEnabled(bool aOverride)
void SetLineThickness(int aWidth)
virtual void SetEnd(const VECTOR2I &aPoint)
virtual void SetStart(const VECTOR2I &aPoint)
void SetOverrideText(const wxString &aValue)
For better understanding of the points that make a dimension:
void SetHeight(int aHeight)
Set the distance from the feature points to the crossbar line.
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:53
double m_scaleFactor
const IO_FILE_DESC GetBoardFileDesc() const override
Returns board file description for the PCB_IO.
int scaleSize(double aVal) const
void loadBoardSetup()
std::vector< PADS_LAYER_INFO > m_layerInfos
void loadTestPoints()
void reportStatistics()
~PCB_IO_PADS() override
SHAPE_ARC makeMidpointArc(const PADS_IO::ARC_POINT &aPrev, const PADS_IO::ARC_POINT &aCurr, int aWidth)
Build a SHAPE_ARC from two consecutive PADS points using the midpoint approach.
double m_originY
void loadClusterGroups()
void loadTracksAndVias()
std::map< std::string, std::string > m_partToBlockMap
int scaleCoord(double aVal, bool aIsX) const
void setPcbShapeArc(PCB_SHAPE *aShape, const PADS_IO::ARC_POINT &aPrev, const PADS_IO::ARC_POINT &aCurr)
Configure a PCB_SHAPE as an arc from two consecutive PADS points using board-level scaleCoord.
std::map< std::string, std::string > m_pinToNetMap
PCB_LAYER_ID getMappedLayer(int aPadsLayer) const
int m_testPointIndex
void appendArcPoints(SHAPE_LINE_CHAIN &aChain, const std::vector< PADS_IO::ARC_POINT > &aPts)
Interpolate arc segments from an ARC_POINT vector into polyline vertices on a SHAPE_LINE_CHAIN.
std::map< wxString, PCB_LAYER_ID > m_layer_map
PADS layer names to KiCad layers.
int m_minObjectSize
void generateDrcRules(const wxString &aFileName)
void loadFootprints()
double m_originX
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const std::map< std::string, UTF8 > *aProperties, PROJECT *aProject) override
Load information from some input file format that this PCB_IO implementation knows about into either ...
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PCB_IO can read the specified board file.
long long GetLibraryTimestamp(const wxString &aLibraryPath) const override
Generate a timestamp representing all the files in the library (including the library directory).
void loadKeepouts()
void loadCopperShapes()
BOARD * m_loadBoard
const IO_FILE_DESC GetLibraryDesc() const override
Get the descriptor for the library container that this IO plugin operates on.
void ensureNet(const std::string &aNetName)
PADS_LAYER_MAPPER m_layerMapper
void clearLoadingState()
void loadGraphicLines()
PADS_UNIT_CONVERTER m_unitConverter
void loadDimensions()
std::map< wxString, PCB_LAYER_ID > DefaultLayerMappingCallback(const std::vector< INPUT_LAYER_DESC > &aInputLayerDescriptionVector)
Return the automapped layers.
void loadBoardOutline()
const PADS_IO::PARSER * m_parser
void loadReuseBlockGroups()
virtual bool CanReadBoard(const wxString &aFileName) const
Checks if this PCB_IO can read the specified board file.
Definition pcb_io.cpp:42
PCB_IO(const wxString &aName)
Definition pcb_io.h:337
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition pcb_shape.h:92
virtual void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:84
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:93
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:96
virtual void SetWidth(int aWidth)
Definition pcb_track.h:90
Container for project specific data.
Definition project.h:65
const SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError=DefaultAccuracyForPCB(), int *aActualError=nullptr) const
Construct a SHAPE_LINE_CHAIN of segments from a given arc.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
Represent a set of closed polygons.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
bool IsPolygonSelfIntersecting(int aPolygonIndex) const
Check whether the aPolygonIndex-th polygon in the set is self intersecting.
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
Simple container to manage line stroke parameters.
Handle a list of polygons defining a copper zone.
Definition zone.h:73
void SetDoNotAllowPads(bool aEnable)
Definition zone.h:738
void SetMinThickness(int aMinThickness)
Definition zone.h:307
void SetThermalReliefSpokeWidth(int aThermalReliefSpokeWidth)
Definition zone.h:237
virtual PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition zone.cpp:501
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:550
SHAPE_POLY_SET * Outline()
Definition zone.h:340
void SetIsRuleArea(bool aEnable)
Definition zone.h:720
void SetDoNotAllowTracks(bool aEnable)
Definition zone.h:737
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition zone.h:634
void SetIsFilled(bool isFilled)
Definition zone.h:298
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:556
void SetDoNotAllowVias(bool aEnable)
Definition zone.h:736
void SetThermalReliefGap(int aThermalReliefGap)
Definition zone.h:226
void SetDoNotAllowFootprints(bool aEnable)
Definition zone.h:739
void SetDoNotAllowZoneFills(bool aEnable)
Definition zone.h:735
void SetAssignedPriority(unsigned aPriority)
Definition zone.h:120
void SetPadConnection(ZONE_CONNECTION aPadConnection)
Definition zone.h:304
void SetZoneName(const wxString &aName)
Definition zone.h:164
#define _(s)
@ DEGREES_T
Definition eda_angle.h:31
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
double m_PadsPcbTextWidthScale
PADS text width scale factor for PCB imports.
double m_PadsPcbTextHeightScale
PADS text height scale factor for PCB imports.
int m_PcbImportMinObjectSizeNm
Minimum object size in nanometers for PCB imports.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:803
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:677
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ Edge_Cuts
Definition layer_ids.h:112
@ F_Paste
Definition layer_ids.h:104
@ Cmts_User
Definition layer_ids.h:108
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ F_Mask
Definition layer_ids.h:97
@ B_Paste
Definition layer_ids.h:105
@ F_SilkS
Definition layer_ids.h:100
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ In1_Cu
Definition layer_ids.h:66
@ B_SilkS
Definition layer_ids.h:101
@ F_Cu
Definition layer_ids.h:64
@ LEFT_RIGHT
Flip left to right (around the Y axis)
Definition mirror.h:28
wxString ConvertInvertedNetName(const std::string &aNetName)
Convert a PADS net name to KiCad format, handling inverted signal notation.
KIID GenerateDeterministicUuid(const std::string &aIdentifier)
Generate a deterministic KIID from a PADS component identifier.
@ NONE
No thermal relief defined.
@ 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)
@ ROUTE
Routing keepout (traces)
@ PLACEMENT
Component placement keepout.
@ COPPER
Copper pour 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.
PADS_LAYER_TYPE
PADS layer types.
@ MILS
Thousandths of an inch (1 mil = 0.001 inch)
@ METRIC
Millimeters.
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:99
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ ROUNDRECT
Definition padstack.h:57
@ RECTANGLE
Definition padstack.h:54
Class to handle a set of BOARD_ITEMs.
VIATYPE
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_INFO
std::string FormatDouble2Str(double aValue)
Print a float number without using scientific notation and no trailing 0 This function is intended in...
Describes an imported layer and how it could be mapped to KiCad Layers.
Container that describes file type info.
Definition io_base.h:43
wxString m_Description
Description shown in the file picker dialog.
Definition io_base.h:44
std::vector< std::string > m_FileExtensions
Filter used for file pickers if m_IsFile is true.
Definition io_base.h:47
A point that may be either a line endpoint or an arc segment.
Definition pads_parser.h:72
ARC arc
Arc parameters (only valid when is_arc is true)
Definition pads_parser.h:76
bool is_arc
True if this segment is an arc, false for line.
Definition pads_parser.h:75
double y
Endpoint Y coordinate.
Definition pads_parser.h:74
double x
Endpoint X coordinate.
Definition pads_parser.h:73
double radius
Arc radius.
Definition pads_parser.h:60
double cx
Center X coordinate.
Definition pads_parser.h:58
double delta_angle
Arc sweep angle in degrees (positive = CCW)
Definition pads_parser.h:62
double cy
Center Y coordinate.
Definition pads_parser.h:59
A copper shape from the LINES section (type=COPPER).
A 2D graphic line/shape from the LINES section (type=LINES).
double layer_thickness
Dielectric thickness (BASIC units)
double dielectric_constant
Relative permittivity (Er)
double drill
Drill hole diameter (0 for SMD)
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 slot_orientation
Slot orientation in degrees (0-179.999)
double thermal_spoke_orientation
First spoke orientation in degrees.
double slot_length
Slot length.
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 corner_radius
Corner radius magnitude (always positive)
double sizeA
Primary size (diameter or width)
std::vector< DECAL_ITEM > items
std::vector< TERMINAL > terminals
std::map< int, std::vector< PAD_STACK_LAYER > > pad_stacks
std::map< std::string, std::string > attributes
Attribute name-value pairs from {...} block.
A polyline that may contain arc segments.
bool has_mask_front
Stack includes top soldermask opening (layer 25)
int start_layer
First PADS layer number in via span.
int end_layer
Last PADS layer number in via span.
bool has_mask_back
Stack includes bottom soldermask opening (layer 28)
Information about a single PADS layer.
@ USER
The field ID hasn't been set yet; field is invalid.
std::string path
KIBIS_PIN * pin
VECTOR2I center
int radius
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
#define M_PI
static thread_pool * tp
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:102
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
@ THERMAL
Use thermal relief for pads.
Definition zones.h:50
@ FULL
pads are covered by copper
Definition zones.h:51