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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "pcb_io_pads.h"
21#include "pads_layer_mapper.h"
22#include "pads_pcb_shapes.h"
23
24#include <algorithm>
25#include <climits>
26#include <cmath>
27#include <fstream>
28#include <functional>
29
30#include <board.h>
31#include <pcb_track.h>
32#include <footprint.h>
33#include <zone.h>
34
35#include "pads_parser.h"
37#include <io/pads/pads_common.h>
38
39#include <netinfo.h>
40#include <wx/log.h>
41#include <core/mirror.h>
42#include <pad.h>
43#include <pcb_shape.h>
46#include <netclass.h>
48#include <geometry/eda_angle.h>
49#include <geometry/shape_arc.h>
50#include <pcb_group.h>
51#include <string_utils.h>
52#include <progress_reporter.h>
53#include <reporter.h>
54#include <advanced_config.h>
55#include <locale_io.h>
56
58{
60 std::bind( &PCB_IO_PADS::DefaultLayerMappingCallback, this, std::placeholders::_1 ) );
61}
62
63
67
68
70{
71 IO_FILE_DESC desc;
72 desc.m_FileExtensions.push_back( "asc" );
73 desc.m_Description = "PADS ASCII";
74 return desc;
75}
76
77
79{
80 // PADS ASCII doesn't really support libraries in the KiCad sense,
81 // but we must implement this.
82 return IO_FILE_DESC( "PADS ASCII Library", { "asc" } );
83}
84
85
86long long PCB_IO_PADS::GetLibraryTimestamp( const wxString& aLibraryPath ) const
87{
88 return 0;
89}
90
91
92bool PCB_IO_PADS::CanReadBoard( const wxString& aFileName ) const
93{
94 if( !PCB_IO::CanReadBoard( aFileName ) )
95 return false;
96
97 std::ifstream file( aFileName.fn_str() );
98
99 if( !file.is_open() )
100 return false;
101
102 std::string line;
103
104 if( std::getline( file, line ) )
105 {
106 if( line.find( "!PADS-" ) != std::string::npos )
107 return true;
108 }
109
110 return false;
111}
112
113
114void PCB_IO_PADS::loadBoard( const wxString& aFileName, BOARD& aBoard, bool aIsNewLoad,
115 const std::map<std::string, UTF8>* aProperties, PROJECT* aProject )
116{
117 LOCALE_IO setlocale;
118
119 if( m_reporter )
120 m_reporter->Report( _( "Starting PADS PCB import" ), RPT_SEVERITY_INFO );
121
123 m_progressReporter->SetNumPhases( 4 );
124
125 PADS_IO::PARSER parser;
126
127 try
128 {
129 parser.Parse( aFileName );
130 }
131 catch( const std::exception& e )
132 {
133 THROW_IO_ERRORF( wxT( "Error parsing PADS file: %s" ), e.what() );
134 }
135
136 m_loadBoard = &aBoard;
137 m_parser = &parser;
138 m_converter = std::make_unique<PADS_PCB_CONVERTER>( m_loadBoard, m_reporter );
141
142 try
143 {
145 m_progressReporter->BeginPhase( 1 );
146
148 loadNets();
149
151 m_progressReporter->BeginPhase( 2 );
152
156 m_converter->LoadTexts( m_parser->GetTexts() );
157
159 m_progressReporter->BeginPhase( 3 );
160
164 loadZones();
166 m_converter->LoadDimensions( m_parser->GetDimensions() );
167 m_converter->LoadKeepouts( m_parser->GetKeepouts() );
169 m_converter->WriteDiffPairRules( aFileName, m_parser->GetDiffPairs() );
170 m_converter->ReportStatistics();
171 }
172 catch( ... )
173 {
175 throw;
176 }
177
179}
180
181
183{
184 const auto& nets = m_parser->GetNets();
185
186 for( const auto& pads_net : nets )
187 m_converter->EnsureNet( pads_net.name );
188
189 for( const auto& pads_net : nets )
190 {
191 for( const auto& pin : pads_net.pins )
192 {
193 std::string key = pin.ref_des + "." + pin.pin_name;
194 m_pinToNetMap[key] = pads_net.name;
195 }
196 }
197
198 const auto& route_nets = m_parser->GetRoutes();
199
200 for( const auto& route : route_nets )
201 {
202 for( const auto& pin : route.pins )
203 {
204 std::string key = pin.ref_des + "." + pin.pin_name;
205
206 if( m_pinToNetMap.find( key ) == m_pinToNetMap.end() )
207 m_pinToNetMap[key] = route.net_name;
208 }
209 }
210
211 for( const auto& route : route_nets )
212 m_converter->EnsureNet( route.net_name );
213
214 for( const auto& pour_def : m_parser->GetPours() )
215 m_converter->EnsureNet( pour_def.net_name );
216
217 for( const auto& copper : m_parser->GetCopperShapes() )
218 {
219 if( !copper.net_name.empty() && IsCopperLayer( getMappedLayer( copper.layer ) ) )
220 m_converter->EnsureNet( copper.net_name );
221 }
222
223 const auto& reuse_blocks = m_parser->GetReuseBlocks();
224
225 for( const auto& [blockName, block] : reuse_blocks )
226 {
227 for( const std::string& partName : block.part_names )
228 {
229 m_partToBlockMap[partName] = blockName;
230 }
231 }
232}
233
234
236{
237 const auto& decals = m_parser->GetPartDecals();
238 const auto& part_types = m_parser->GetPartTypes();
239 const auto& partInstanceAttrs = m_parser->GetPartInstanceAttrs();
240 const auto& parts = m_parser->GetParts();
241
242 for( const auto& pads_part : parts )
243 {
244 FOOTPRINT* footprint = new FOOTPRINT( m_loadBoard );
245 footprint->SetReference( pads_part.name );
246
247 // Generate deterministic UUID for cross-probe linking between schematic and PCB.
248 // The schematic importer uses the same algorithm, enabling selection sync.
249 KIID symbolUuid = PADS_COMMON::GenerateDeterministicUuid( pads_part.name );
251 path.push_back( symbolUuid );
252 footprint->SetPath( path );
253
254 // Resolve Decal Name
255 std::string decal_name = pads_part.decal;
256
257 // Always resolve through part types to get the full alternate decal
258 // list. A name like "MTHOLE" can be both a decal and a part type, and
259 // the part type entry carries the colon-separated alternate list that
260 // alt_decal_index indexes into.
261 if( !pads_part.explicit_decal )
262 {
263 auto part_type_it = part_types.find( decal_name );
264
265 if( part_type_it != part_types.end() )
266 decal_name = part_type_it->second.decal_name;
267 }
268
269 // Handle Alternate Decals (separated by :)
270 // The part's alt_decal_index specifies which alternate to use (0-based).
271 std::stringstream ss( decal_name );
272 std::string segment;
273 std::vector<std::string> decal_list;
274
275 while( std::getline( ss, segment, ':' ) )
276 {
277 decal_list.push_back( segment );
278 }
279
280 std::string actual_decal_name;
281 bool found_valid_decal = false;
282
283 if( pads_part.alt_decal_index >= 0
284 && static_cast<size_t>( pads_part.alt_decal_index ) < decal_list.size() )
285 {
286 const std::string& alt_decal = decal_list[pads_part.alt_decal_index];
287
288 if( decals.find( alt_decal ) != decals.end() )
289 {
290 actual_decal_name = alt_decal;
291 found_valid_decal = true;
292 }
293 }
294
295 if( !found_valid_decal )
296 {
297 for( const std::string& decal : decal_list )
298 {
299 if( decals.find( decal ) != decals.end() )
300 {
301 actual_decal_name = decal;
302 found_valid_decal = true;
303 break;
304 }
305 }
306 }
307
308 if( found_valid_decal )
309 {
310 decal_name = actual_decal_name;
311 }
312
313 LIB_ID fpid;
314 fpid.SetLibItemName( PADS_COMMON::ConvertText( decal_name ) );
315 footprint->SetFPID( fpid );
316
317 footprint->SetValue( pads_part.decal );
318
319 if( !pads_part.alternate_decals.empty() )
320 {
321 wxString alternates;
322
323 for( size_t i = 0; i < pads_part.alternate_decals.size(); ++i )
324 {
325 if( i > 0 )
326 alternates += wxT( ", " );
327
328 alternates += PADS_COMMON::ConvertText( pads_part.alternate_decals[i] );
329 }
330
331 PCB_FIELD* field = new PCB_FIELD( footprint, FIELD_T::USER, wxT( "PADS_Alternate_Decals" ) );
332 field->SetLayer( Cmts_User );
333 field->SetVisible( false );
334 field->SetText( alternates );
335 footprint->Add( field );
336 }
337
338 auto partCoordScaler =
339 [&]( double val, bool is_x )
340 {
341 double origin = is_x ? m_converter->GetOriginX() : m_converter->GetOriginY();
342
343 double part_factor = m_converter->GetScaleFactor();
344
345 if( !m_parser->IsBasicUnits() )
346 {
347 if( pads_part.units == "M" ) part_factor = PADS_UNIT_CONVERTER::MILS_TO_NM;
348 else if( pads_part.units == "MM" ) part_factor = PADS_UNIT_CONVERTER::MM_TO_NM;
349 else if( pads_part.units == "I" ) part_factor = PADS_UNIT_CONVERTER::INCHES_TO_NM;
350 else if( pads_part.units == "D" ) part_factor = PADS_UNIT_CONVERTER::MILS_TO_NM;
351 }
352
353 long long origin_nm =
354 static_cast<long long>( std::round( origin * m_converter->GetScaleFactor() ) );
355 long long val_nm = static_cast<long long>( std::round( val * part_factor ) );
356
357 long long res_nm = val_nm - origin_nm;
358
359 if( !is_x )
360 res_nm = -res_nm;
361
362 return static_cast<int>( std::clamp<long long>( res_nm, INT_MIN, INT_MAX ) );
363 };
364
365 footprint->SetPosition( VECTOR2I( partCoordScaler( pads_part.location.x, true ),
366 partCoordScaler( pads_part.location.y, false ) ) );
367
368 // Both PADS and KiCad use counter-clockwise positive rotation convention.
369 // The Y-axis flip (PADS Y-up vs KiCad Y-down) does not affect rotation direction,
370 // so we use the PADS rotation value directly for both top and bottom layer parts.
371 // For bottom-layer parts, the subsequent Flip() call handles the layer change and
372 // adjusts the orientation appropriately.
373 footprint->SetOrientation( EDA_ANGLE( pads_part.rotation, DEGREES_T ) );
374
375 footprint->SetLayer( F_Cu );
376
377 // Look up custom attribute values from part type and per-instance overrides.
378 // Per-instance attributes (from PART <refdes> {...} in *PARTTYPE*) take priority.
379 const PADS_IO::PART_TYPE* partType = nullptr;
380 auto ptIt = part_types.find( pads_part.decal );
381
382 if( ptIt != part_types.end() )
383 partType = &ptIt->second;
384
385 const std::map<std::string, std::string>* instanceAttrs = nullptr;
386 auto iaIt = partInstanceAttrs.find( pads_part.name );
387
388 if( iaIt != partInstanceAttrs.end() )
389 instanceAttrs = &iaIt->second;
390
391 auto applyAttributes =
392 [&]( const std::vector<PADS_IO::ATTRIBUTE>& attrs, std::function<int(double)> scaler )
393 {
394 for( const auto& attr : attrs )
395 {
396 PCB_FIELD* field = nullptr;
397 bool ownsField = false;
398
399 if( attr.name == "Ref.Des." )
400 {
401 field = &footprint->Reference();
402 }
403 else if( attr.name == "Part Type" || attr.name == "VALUE" )
404 {
405 field = &footprint->Value();
406 }
407 else
408 {
409 std::string attrValue;
410
411 if( instanceAttrs )
412 {
413 auto valIt = instanceAttrs->find( attr.name );
414
415 if( valIt != instanceAttrs->end() )
416 attrValue = valIt->second;
417 }
418
419 if( attrValue.empty() && partType )
420 {
421 auto valIt = partType->attributes.find( attr.name );
422
423 if( valIt != partType->attributes.end() )
424 attrValue = valIt->second;
425 }
426
427 if( !attrValue.empty() )
428 {
429 field = new PCB_FIELD( footprint, FIELD_T::USER,
430 PADS_COMMON::ConvertText( attr.name ) );
431 field->SetText( PADS_COMMON::ConvertText( attrValue ) );
432
433 // Footprint text fields on copper layers are almost always documentation
434 // labels. Redirect to the corresponding silkscreen layer.
435 PCB_LAYER_ID fieldLayer = getMappedLayer( attr.level );
436
437 if( fieldLayer == UNDEFINED_LAYER )
438 fieldLayer = Cmts_User;
439 else if( IsCopperLayer( fieldLayer ) )
440 fieldLayer = IsBackLayer( fieldLayer ) ? B_SilkS : F_SilkS;
441
442 field->SetLayer( fieldLayer );
443 ownsField = true;
444 }
445 }
446
447 if( !field )
448 continue;
449
450 int scaledSize = scaler( attr.height );
451 int charHeight =
452 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextHeightScale );
453 int charWidth =
454 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextWidthScale );
455 field->SetTextSize( VECTOR2I( charWidth, charHeight ) );
456
457 if( attr.width > 0 )
458 field->SetTextThickness( scaler( attr.width ) );
459
460 // Position is relative to part origin, rotated by part orientation.
461 // Y is negated for coordinate system conversion.
462 VECTOR2I offset( scaler( attr.x ), -scaler( attr.y ) );
463 EDA_ANGLE part_orient( pads_part.rotation, DEGREES_T );
464 RotatePoint( offset, part_orient );
465
466 // PADS text anchor differs from KiCad by a small offset along the
467 // reading direction. Shift left (toward text start) to compensate.
468 EDA_ANGLE textAngle = EDA_ANGLE( attr.orientation, DEGREES_T ) + part_orient;
469 VECTOR2I textShift( -ADVANCED_CFG::GetCfg().m_PadsTextAnchorOffsetNm, 0 );
470 RotatePoint( textShift, textAngle );
471 offset += textShift;
472
473 field->SetPosition( footprint->GetPosition() + offset );
474 field->SetTextAngle( textAngle );
475 field->SetKeepUpright( false );
476 field->SetVisible( attr.visible );
477
478 if( attr.hjust == "LEFT" )
480 else if( attr.hjust == "RIGHT" )
482 else
484
485 if( attr.vjust == "UP" )
487 else if( attr.vjust == "DOWN" )
489 else
491
492 if( ownsField )
493 footprint->Add( field );
494 }
495 };
496
497 auto decal_it = decals.find( decal_name );
498
499 double decalScale = ( decal_it != decals.end() ) ? decalUnitScale( decal_it->second.units )
500 : 0.0;
501
502 auto decalScaler =
503 [&, decalScale]( double val )
504 {
505 return decalScale > 0.0 ? KiROUND( val * decalScale ) : scaleSize( val );
506 };
507
508 if( decal_it != decals.end() )
509 {
510 applyAttributes( decal_it->second.attributes, decalScaler );
511 }
512 else
513 {
514 if( m_reporter )
515 {
516 m_reporter->Report( wxString::Format( _( "Footprint '%s' not found in decal list, part skipped" ),
517 decal_name ),
519 }
520 }
521
522 auto partScaler =
523 [&]( double val )
524 {
525 if( !m_parser->IsBasicUnits() )
526 {
527 if( pads_part.units == "M" )
529 }
530
531 if( pads_part.units == "M" )
532 return KiROUND( val );
533
534 return scaleSize( val );
535 };
536
537 applyAttributes( pads_part.attributes, partScaler );
538
539 // PADS "Part Type" maps to KiCad Value field. Hide it since it typically
540 // shows the part type name which is not useful on fabrication layers.
541 footprint->Value().SetVisible( false );
542
543 m_loadBoard->Add( footprint );
544
545 auto blockIt = m_partToBlockMap.find( pads_part.name );
546
547 if( blockIt != m_partToBlockMap.end() )
548 {
549 PCB_FIELD* blockField = new PCB_FIELD( footprint, FIELD_T::USER, wxT( "PADS_Reuse_Block" ) );
550 blockField->SetLayer( Cmts_User );
551 blockField->SetVisible( false );
552 blockField->SetText( PADS_COMMON::ConvertText( blockIt->second ) );
553 footprint->Add( blockField );
554 }
555
556 if( decal_it == decals.end() )
557 continue;
558
559 // Add Pads and Graphics from Decal
560 {
561 const PADS_IO::PART_DECAL& decal = decal_it->second;
562
563 // Turn a rectangular pad into a roundrect or chamfered rect from the PADS corner
564 // radius. aDefaultRound keeps the shape rounded (0.25 ratio) when the decal gives
565 // no radius, as PADS RC/OC pads are rounded by definition; S and RF stay square.
566 auto applyCornerRadius =
567 [&]( const PADS_IO::PAD_STACK_LAYER& layer_def, PAD* pad, PCB_LAYER_ID kicad_layer,
568 const VECTOR2I& aSize, bool aDefaultRound )
569 {
570 if( layer_def.corner_radius > 0 )
571 {
572 int min_dim = std::min( aSize.x, aSize.y );
573 double radius = decalScaler( layer_def.corner_radius );
574 double ratio = ( min_dim > 0 ) ? std::min( radius / min_dim, 0.5 ) : 0.25;
575
576 if( layer_def.chamfered )
577 {
578 pad->SetShape( kicad_layer, PAD_SHAPE::CHAMFERED_RECT );
579 pad->SetRoundRectRadiusRatio( kicad_layer, 0.0 );
580 pad->SetChamferRectRatio( kicad_layer, ratio );
581 pad->SetChamferPositions( kicad_layer, RECT_CHAMFER_ALL );
582 }
583 else
584 {
585 pad->SetShape( kicad_layer, PAD_SHAPE::ROUNDRECT );
586 pad->SetRoundRectRadiusRatio( kicad_layer, ratio );
587 }
588 }
589 else if( aDefaultRound )
590 {
591 pad->SetShape( kicad_layer, PAD_SHAPE::ROUNDRECT );
592 pad->SetRoundRectRadiusRatio( kicad_layer, 0.25 );
593 }
594 else
595 {
596 pad->SetShape( kicad_layer, PAD_SHAPE::RECTANGLE );
597 }
598 };
599
600 auto convertPadShape =
601 [&]( const PADS_IO::PAD_STACK_LAYER& layer_def, PAD* pad, PCB_LAYER_ID kicad_layer )
602 {
603 const std::string& shape = layer_def.shape;
604 // In PADS, sizeA is height (Y) and sizeB is width (X), opposite of KiCad convention
605 VECTOR2I size( std::max( decalScaler( layer_def.sizeB ), m_minObjectSize ),
606 std::max( decalScaler( layer_def.sizeA ), m_minObjectSize ) );
607
608 pad->SetShape( kicad_layer, PADS_PCB::PadsShapeToKiCad( shape ) );
609
610 if( shape == "R" || shape == "C" || shape == "A" || shape == "RT" )
611 {
612 pad->SetSize( kicad_layer, VECTOR2I( size.x, size.x ) );
613 }
614 else if( shape == "S" || shape == "ST" )
615 {
616 // The via pad-stack parser leaves sizeB unset for square pads, so take
617 // the single populated dimension for both sides of the square.
618 int side = ( layer_def.sizeB > 0 ) ? size.x : size.y;
619 VECTOR2I sq_size( side, side );
620 applyCornerRadius( layer_def, pad, kicad_layer, sq_size, false );
621 pad->SetSize( kicad_layer, sq_size );
622 }
623 else if( shape == "O" || shape == "OT" )
624 {
625 pad->SetSize( kicad_layer, size );
626 }
627 else if( shape == "RF" )
628 {
629 applyCornerRadius( layer_def, pad, kicad_layer, size, false );
630 pad->SetSize( kicad_layer, size );
631 }
632 else if( shape == "OF" )
633 {
634 pad->SetSize( kicad_layer, size );
635 }
636 else if( shape == "RC" || shape == "OC" )
637 {
638 applyCornerRadius( layer_def, pad, kicad_layer, size, true );
639 pad->SetSize( kicad_layer, size );
640 }
641 else
642 {
643 pad->SetSize( kicad_layer, VECTOR2I( size.x, size.x ) );
644 }
645
646 if( layer_def.finger_offset != 0 )
647 {
648 // finger_offset runs along the finger's long axis (pad-local X before
649 // rotation). PAD::ShapePos() rotates the offset by GetOrientation(), so
650 // store it unrotated; pre-rotating by layer_def.rotation here would
651 // double-apply the rotation.
652 pad->SetOffset( kicad_layer, VECTOR2I( decalScaler( layer_def.finger_offset ), 0 ) );
653 }
654 };
655
656 EDA_ANGLE part_orient( pads_part.rotation, DEGREES_T );
657
658 for( size_t term_idx = 0; term_idx < decal.terminals.size(); ++term_idx )
659 {
660 const auto& term = decal.terminals[term_idx];
661 PAD* pad = new PAD( footprint );
662 footprint->Add( pad );
663
664 pad->SetNumber( term.name );
665
666 VECTOR2I pad_pos( decalScaler( term.x ), -decalScaler( term.y ) );
667 RotatePoint( pad_pos, part_orient );
668 pad->SetPosition( footprint->GetPosition() + pad_pos );
669
670 // Look up pad stack by terminal index (1-based). PAD 0 is the default for
671 // terminals without explicit definitions. PAD N is for terminal index N.
672 int pin_num = static_cast<int>( term_idx + 1 );
673
674 auto stack_it = decal.pad_stacks.find( pin_num );
675
676 if( stack_it == decal.pad_stacks.end() )
677 stack_it = decal.pad_stacks.find( 0 );
678
679 if( stack_it != decal.pad_stacks.end() && !stack_it->second.empty() )
680 {
681 const std::vector<PADS_IO::PAD_STACK_LAYER>& stack = stack_it->second;
682
683 double drill = 0.0;
684 bool plated = true;
685 double slot_length = 0.0;
686 double slot_orientation = 0.0;
687 double pad_rotation = 0.0;
688
689 for( const PADS_IO::PAD_STACK_LAYER& layer_def : stack )
690 {
691 if( layer_def.drill > 0 )
692 {
693 drill = layer_def.drill;
694 plated = layer_def.plated;
695 slot_length = layer_def.slot_length;
696 slot_orientation = layer_def.slot_orientation;
697 pad_rotation = layer_def.rotation;
698 break;
699 }
700 }
701
702 LSET layer_set;
703
704 auto mapPadsLayer =
705 [&]( int pads_layer ) -> PCB_LAYER_ID
706 {
707 if( pads_layer == -2 || pads_layer == 1 )
708 {
709 return F_Cu;
710 }
711 else if( pads_layer == -1 || pads_layer == m_parser->GetParameters().layer_count )
712 {
713 return B_Cu;
714 }
715 else if( pads_layer > 1 && pads_layer < m_parser->GetParameters().layer_count )
716 {
717 int inner_idx = pads_layer - 2;
718
719 if( inner_idx >= 0 && inner_idx < 30 )
720 return static_cast<PCB_LAYER_ID>( In1_Cu + inner_idx * 2 );
721 }
722
723 return UNDEFINED_LAYER;
724 };
725
726 bool has_explicit_layers = false;
727
728 for( const PADS_IO::PAD_STACK_LAYER& layer_def : stack )
729 {
730 if( layer_def.layer == -2 || layer_def.layer == -1
731 || layer_def.layer == 1
732 || layer_def.layer == m_parser->GetParameters().layer_count )
733 {
734 has_explicit_layers = true;
735 break;
736 }
737 }
738
739 // KiCad keeps one orientation per pad; PADS carries it per pad-stack
740 // layer. Capture from the first converted entry and apply once below,
741 // so a later (e.g. back-side round) layer can't reset it to zero.
742 double shape_rotation = 0.0;
743 bool shape_rotation_set = false;
744
745 auto convertGeometry =
746 [&]( const PADS_IO::PAD_STACK_LAYER& aLayerDef, PCB_LAYER_ID aKicadLayer )
747 {
748 convertPadShape( aLayerDef, pad, aKicadLayer );
749
750 if( !shape_rotation_set )
751 {
752 shape_rotation = aLayerDef.rotation;
753 shape_rotation_set = true;
754 }
755 };
756
757 // Track mask/paste layers explicitly present in the stack regardless
758 // of size. A zero-size entry means "intentionally no pad on this layer"
759 // and must suppress the SMD fallback for that layer.
760 LSET explicitly_seen_tech;
761
762 for( const PADS_IO::PAD_STACK_LAYER& layer_def : stack )
763 {
764 if( layer_def.layer > 0 )
765 {
766 PCB_LAYER_ID check = getMappedLayer( layer_def.layer );
767
768 if( check == F_Mask || check == B_Mask || check == F_Paste || check == B_Paste )
769 explicitly_seen_tech.set( check );
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 // The corner radius and chamfer flag change the resulting KiCad
783 // shape, so fold them into the comparison key; otherwise two
784 // same-code entries differing only in corner would stay NORMAL and
785 // leak the front rounding onto the back copper.
786 auto shapeKey =
787 []( const PADS_IO::PAD_STACK_LAYER& aLayerDef )
788 {
789 return aLayerDef.shape + "|" + std::to_string( aLayerDef.corner_radius )
790 + "|" + std::to_string( aLayerDef.chamfered );
791 };
792
793 std::string front_shape;
794 std::string back_shape;
795
796 for( const PADS_IO::PAD_STACK_LAYER& layer_def : stack )
797 {
798 if( layer_def.sizeA <= 0 )
799 continue;
800
801 if( !PADS_IO::IsCopperPadRow( layer_def ) )
802 continue;
803
804 PCB_LAYER_ID mapped = mapPadsLayer( layer_def.layer );
805
806 if( mapped == F_Cu && front_shape.empty() )
807 front_shape = shapeKey( layer_def );
808 else if( mapped == B_Cu && back_shape.empty() )
809 back_shape = shapeKey( layer_def );
810 }
811
812 // Only switch to FRONT_INNER_BACK when the pad shape itself
813 // differs between front and back copper. Size-only differences
814 // (e.g. different annular ring diameters) are represented in
815 // NORMAL mode using the primary (front/component-side) shape, which
816 // keeps mirrored placements visually consistent with the original.
817 if( !front_shape.empty() && !back_shape.empty() && front_shape != back_shape )
818 pad->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
819 }
820
821 // Tracks whether convertPadShape has already been called for a copper
822 // layer in NORMAL mode. In NORMAL mode all copper layers map to the
823 // same ALL_LAYERS slot, so a second call would overwrite the first.
824 // The primary (layer -2, component-side) entry must win.
825 bool normal_copper_set = false;
826
827 for( const PADS_IO::PAD_STACK_LAYER& layer_def : stack )
828 {
829 if( layer_def.layer == 0 )
830 {
831 if( !has_explicit_layers )
832 {
833 layer_set = ( drill > 0 ) ? LSET::AllCuMask()
834 : LSET( { F_Cu, B_Cu } );
835 convertGeometry( layer_def, F_Cu );
836
837 if( drill == 0 )
838 {
839 pad->SetShape( B_Cu, pad->GetShape( F_Cu ) );
840 pad->SetSize( B_Cu, pad->GetSize( F_Cu ) );
841 }
842 }
843
844 continue;
845 }
846
847 // Skip layers with size 0 - "no pad on this layer" in PADS.
848 // We must not call SetSize with 0 since in PADSTACK NORMAL mode all
849 // layers write to the same ALL_LAYERS slot, overwriting valid sizes.
850 if( layer_def.sizeA <= 0 )
851 continue;
852
853 // RT/ST are thermal relief spoke patterns for plane layers.
854 // RA/SA are anti-pad (clearance) shapes for plane layers.
855 // KiCad computes thermal reliefs from zone settings, so skip
856 // these to avoid overwriting the actual pad shape. However,
857 // the presence of RT/ST indicates this pad should have thermal
858 // relief rather than a solid connection to copper pours.
859 if( PADS_IO::IsThermalReliefPadRow( layer_def ) )
860 {
861 pad->SetLocalZoneConnection( ZONE_CONNECTION::THERMAL );
862
863 if( layer_def.thermal_spoke_width > 0 )
864 pad->SetLocalThermalSpokeWidthOverride( decalScaler( layer_def.thermal_spoke_width ) );
865
866 if( layer_def.thermal_outer_diameter > layer_def.sizeA )
867 {
868 double gap = ( layer_def.thermal_outer_diameter - layer_def.sizeA ) / 2.0;
869 int scaledGap = decalScaler( gap );
870
871 // An override of 0 reads as "inherit the zone gap", so only
872 // apply it when the relief gap survives rounding to nm.
873 if( scaledGap > 0 )
874 pad->SetLocalThermalGapOverride( scaledGap );
875 }
876
877 if( layer_def.thermal_spoke_orientation != 0.0 )
878 pad->SetThermalSpokeAngleDegrees( layer_def.thermal_spoke_orientation );
879
880 continue;
881 }
882
883 if( PADS_IO::IsAntiPadRow( layer_def ) )
884 continue;
885
886 PCB_LAYER_ID kicad_layer = mapPadsLayer( layer_def.layer );
887
888 if( kicad_layer == UNDEFINED_LAYER && layer_def.layer > 0 )
889 {
890 // For non-copper layers, check if they're mask/paste layers.
891 // PADS pad stacks can include explicit solder mask and paste
892 // mask entries that must be preserved in KiCad.
893 // layer_def.layer > 0 skips the copper sentinels -2 (top)
894 // and -1 (bottom), which mapPadsLayer already resolved above.
895 PCB_LAYER_ID tech_layer = getMappedLayer( layer_def.layer );
896
897 if( tech_layer == F_Mask || tech_layer == B_Mask
898 || tech_layer == F_Paste || tech_layer == B_Paste )
899 {
900 layer_set.set( tech_layer );
901 }
902 }
903 else if( kicad_layer != UNDEFINED_LAYER )
904 {
905 layer_set.set( kicad_layer );
906
907 // In NORMAL mode, all copper entries map to the same ALL_LAYERS
908 // slot. Only the first (primary/component-side) entry sets the
909 // shape; later entries for secondary copper are skipped so they
910 // do not overwrite the primary size.
911 bool is_copper = IsCopperLayer( kicad_layer );
912
913 if( is_copper
914 && normal_copper_set
915 && pad->Padstack().Mode() == PADSTACK::MODE::NORMAL )
916 {
917 continue;
918 }
919
920 convertGeometry( layer_def, kicad_layer );
921
922 if( is_copper )
923 normal_copper_set = true;
924 }
925 }
926
927 if( layer_set.none() )
928 {
929 layer_set.set( F_Cu );
930 convertGeometry( stack[0], F_Cu );
931 }
932
933 // Apply part placement plus finger orientation once, now that all
934 // pad-stack layers are converted.
935 pad->SetOrientation( part_orient + EDA_ANGLE( shape_rotation, DEGREES_T ) );
936
937 // For SMD pads, enable mask/paste layers that the stack did not
938 // explicitly mention. A zero-size stack entry for a mask/paste layer
939 // means "intentionally disabled" and is tracked in explicitly_seen_tech,
940 // so only layers absent from the stack entirely get the fallback.
941 if( drill == 0 )
942 {
943 if( layer_set.test( F_Cu ) && !layer_set.test( F_Mask )
944 && !explicitly_seen_tech.test( F_Mask ) )
945 {
946 layer_set.set( F_Mask );
947 }
948
949 if( layer_set.test( F_Cu ) && !layer_set.test( F_Paste )
950 && !explicitly_seen_tech.test( F_Paste ) )
951 {
952 layer_set.set( F_Paste );
953 }
954
955 if( layer_set.test( B_Cu ) && !layer_set.test( B_Mask )
956 && !explicitly_seen_tech.test( B_Mask ) )
957 {
958 layer_set.set( B_Mask );
959 }
960
961 if( layer_set.test( B_Cu ) && !layer_set.test( B_Paste )
962 && !explicitly_seen_tech.test( B_Paste ) )
963 {
964 layer_set.set( B_Paste );
965 }
966 }
967
968 if( slot_length > 0 && slot_length != drill )
969 {
970 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
971
972 int drillMinor = decalScaler( drill );
973 int drillMajor = decalScaler( slot_length );
974
975 // Slot orientation is in the decal's local frame.
976 // Subtract the pad shape rotation to get the slot
977 // angle in the pad's own local frame.
978 double relAngle = slot_orientation - pad_rotation;
979
980 relAngle = fmod( relAngle, 360.0 );
981
982 if( relAngle < 0 )
983 relAngle += 360.0;
984
985 bool vertical = ( relAngle > 45.0 && relAngle < 135.0 )
986 || ( relAngle > 225.0 && relAngle < 315.0 );
987
988 if( vertical )
989 pad->SetDrillSize( VECTOR2I( drillMinor, drillMajor ) );
990 else
991 pad->SetDrillSize( VECTOR2I( drillMajor, drillMinor ) );
992 }
993 else
994 {
995 pad->SetDrillSize( VECTOR2I( decalScaler( drill ), decalScaler( drill ) ) );
996 }
997
998 if( drill == 0 )
999 {
1000 pad->SetAttribute( PAD_ATTRIB::SMD );
1001 }
1002 else
1003 {
1004 // Preserve any explicit mask/paste layer bits accumulated
1005 // during stack iteration before expanding to all copper layers.
1006 LSET mask_paste_bits = layer_set & LSET( { F_Mask, B_Mask, F_Paste, B_Paste } );
1007
1008 // A stack with no mask row still opens the mask; only an unplated hole
1009 // is mechanical, and it carries no inner copper
1010 if( plated )
1011 {
1012 pad->SetAttribute( PAD_ATTRIB::PTH );
1013 layer_set = PAD::PTHMask() | mask_paste_bits;
1014 }
1015 else
1016 {
1017 pad->SetAttribute( PAD_ATTRIB::NPTH );
1018 layer_set = PAD::UnplatedHoleMask() | mask_paste_bits;
1019 }
1020 }
1021
1022 pad->SetLayerSet( layer_set );
1023 }
1024 else
1025 {
1026 int fallbackSize = std::max( decalScaler( 1.5 ), m_minObjectSize );
1027 pad->SetSize( F_Cu, VECTOR2I( fallbackSize, fallbackSize ) );
1028 pad->SetShape( F_Cu, PAD_SHAPE::CIRCLE );
1029 pad->SetAttribute( PAD_ATTRIB::PTH );
1030 pad->SetLayerSet( PAD::PTHMask() );
1031 }
1032
1033 // An unplated hole is mechanical; joining it to a net pulls ratsnest to a
1034 // mounting hole
1035 if( pad->GetAttribute() != PAD_ATTRIB::NPTH )
1036 {
1037 std::string pinKey = pads_part.name + "." + term.name;
1038 auto netIt = m_pinToNetMap.find( pinKey );
1039
1040 if( netIt != m_pinToNetMap.end() )
1041 {
1042 NETINFO_ITEM* net =
1043 m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( netIt->second ) );
1044
1045 if( net )
1046 pad->SetNet( net );
1047 }
1048 }
1049 }
1050
1051 for( const PADS_IO::DECAL_ITEM& item : decal.items )
1052 {
1053 if( item.points.empty() )
1054 continue;
1055
1056 // Decal graphics layers work differently from routing layers in PADS.
1057 // Layer 0 and layer 1 are typically footprint outlines, not copper.
1058 PCB_LAYER_ID shape_layer = F_SilkS;
1059
1060 if( item.layer == 0 )
1061 {
1062 shape_layer = F_SilkS;
1063 }
1064 else
1065 {
1066 PCB_LAYER_ID mapped_layer = getMappedLayer( item.layer );
1067
1068 if( IsCopperLayer( mapped_layer ) )
1069 {
1070 if( mapped_layer == B_Cu )
1071 shape_layer = B_SilkS;
1072 else
1073 shape_layer = F_SilkS;
1074 }
1075 else
1076 {
1077 shape_layer = mapped_layer;
1078 }
1079 }
1080
1081 if( shape_layer == UNDEFINED_LAYER )
1082 {
1083 if( m_reporter )
1084 {
1085 m_reporter->Report( wxString::Format( _( "Skipping decal item on unmapped layer %d" ),
1086 item.layer ),
1088 }
1089 continue;
1090 }
1091
1092 bool is_circle = ( item.type == "CIRCLE" );
1093 bool is_closed = ( item.type == "CLOSED" || is_circle );
1094
1095 // Per PADS spec: CIRCLE pieces have 2 corners representing ends of
1096 // horizontal diameter.
1097 if( is_circle && item.points.size() >= 2 )
1098 {
1099 PCB_SHAPE* shape = new PCB_SHAPE( footprint, SHAPE_T::CIRCLE );
1100 shape->SetLayer( shape_layer );
1101
1102 double x1 = item.points[0].x;
1103 double y1 = item.points[0].y;
1104 double x2 = item.points[1].x;
1105 double y2 = item.points[1].y;
1106
1107 double cx = ( x1 + x2 ) / 2.0;
1108 double cy = ( y1 + y2 ) / 2.0;
1109
1110 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 )
1111 + ( y2 - y1 ) * ( y2 - y1 ) )
1112 / 2.0;
1113
1114 int scaledRadius = std::max( decalScaler( radius ), m_minObjectSize );
1115 VECTOR2I center( decalScaler( cx ), -decalScaler( cy ) );
1116 VECTOR2I pt_on_circle( center.x + scaledRadius, center.y );
1117
1118 RotatePoint( center, part_orient );
1119 RotatePoint( pt_on_circle, part_orient );
1120
1121 VECTOR2I fp_pos = footprint->GetPosition();
1122 shape->SetCenter( fp_pos + center );
1123 shape->SetEnd( fp_pos + pt_on_circle );
1124 shape->SetStroke( STROKE_PARAMS( decalScaler( item.width ), LINE_STYLE::SOLID ) );
1125
1126 footprint->Add( shape );
1127
1128 continue;
1129 }
1130
1131 if( item.points.size() < 2 )
1132 continue;
1133
1134 for( size_t i = 0; i < item.points.size() - 1; ++i )
1135 {
1136 const PADS_IO::ARC_POINT& p1 = item.points[i];
1137 const PADS_IO::ARC_POINT& p2 = item.points[i + 1];
1138
1139 PCB_SHAPE* shape = new PCB_SHAPE( footprint );
1140 shape->SetLayer( shape_layer );
1141 shape->SetStroke( STROKE_PARAMS( decalScaler( item.width ), LINE_STYLE::SOLID ) );
1142
1143 if( p2.is_arc )
1144 {
1145 shape->SetShape( SHAPE_T::ARC );
1146 VECTOR2I center( decalScaler( p2.arc.cx ), -decalScaler( p2.arc.cy ) );
1147 VECTOR2I start( decalScaler( p1.x ), -decalScaler( p1.y ) );
1148 VECTOR2I end( decalScaler( p2.x ), -decalScaler( p2.y ) );
1149
1150 // Y-axis flip reverses arc winding; swap endpoints for CCW arcs
1151 if( p2.arc.delta_angle > 0 )
1152 std::swap( start, end );
1153
1154 RotatePoint( center, part_orient );
1155 RotatePoint( start, part_orient );
1156 RotatePoint( end, part_orient );
1157
1158 VECTOR2I fp_pos = footprint->GetPosition();
1159 shape->SetCenter( fp_pos + center );
1160 shape->SetStart( fp_pos + start );
1161 shape->SetEnd( fp_pos + end );
1162 }
1163 else
1164 {
1165 shape->SetShape( SHAPE_T::SEGMENT );
1166 VECTOR2I start( decalScaler( p1.x ), -decalScaler( p1.y ) );
1167 VECTOR2I end( decalScaler( p2.x ), -decalScaler( p2.y ) );
1168
1169 RotatePoint( start, part_orient );
1170 RotatePoint( end, part_orient );
1171
1172 VECTOR2I fp_pos = footprint->GetPosition();
1173 shape->SetStart( fp_pos + start );
1174 shape->SetEnd( fp_pos + end );
1175 }
1176
1177 footprint->Add( shape );
1178 }
1179
1180 if( is_closed && item.points.size() > 2 )
1181 {
1182 const PADS_IO::ARC_POINT& pLast = item.points.back();
1183 const PADS_IO::ARC_POINT& pFirst = item.points.front();
1184
1185 PCB_SHAPE* shape = new PCB_SHAPE( footprint );
1186 shape->SetLayer( shape_layer );
1187 shape->SetStroke( STROKE_PARAMS( decalScaler( item.width ), LINE_STYLE::SOLID ) );
1188
1189 if( pFirst.is_arc )
1190 {
1191 shape->SetShape( SHAPE_T::ARC );
1192 VECTOR2I center( decalScaler( pFirst.arc.cx ), -decalScaler( pFirst.arc.cy ) );
1193 VECTOR2I start( decalScaler( pLast.x ), -decalScaler( pLast.y ) );
1194 VECTOR2I end( decalScaler( pFirst.x ), -decalScaler( pFirst.y ) );
1195
1196 if( pFirst.arc.delta_angle > 0 )
1197 std::swap( start, end );
1198
1199 RotatePoint( center, part_orient );
1200 RotatePoint( start, part_orient );
1201 RotatePoint( end, part_orient );
1202
1203 VECTOR2I fp_pos = footprint->GetPosition();
1204 shape->SetCenter( fp_pos + center );
1205 shape->SetStart( fp_pos + start );
1206 shape->SetEnd( fp_pos + end );
1207 }
1208 else
1209 {
1210 shape->SetShape( SHAPE_T::SEGMENT );
1211 VECTOR2I start( decalScaler( pLast.x ), -decalScaler( pLast.y ) );
1212 VECTOR2I end( decalScaler( pFirst.x ), -decalScaler( pFirst.y ) );
1213
1214 RotatePoint( start, part_orient );
1215 RotatePoint( end, part_orient );
1216
1217 VECTOR2I fp_pos = footprint->GetPosition();
1218 shape->SetStart( fp_pos + start );
1219 shape->SetEnd( fp_pos + end );
1220 }
1221
1222 footprint->Add( shape );
1223 }
1224 }
1225 }
1226
1227 if( pads_part.bottom_layer )
1228 footprint->Flip( footprint->GetPosition(), FLIP_DIRECTION::LEFT_RIGHT );
1229 }
1230}
1231
1232
1234{
1235 const auto& reuse_blocks = m_parser->GetReuseBlocks();
1236
1237 if( reuse_blocks.empty() )
1238 return;
1239
1240 std::map<std::string, PCB_GROUP*> blockGroups;
1241
1242 for( const auto& [blockName, block] : reuse_blocks )
1243 {
1244 if( !block.instances.empty() || !block.part_names.empty() )
1245 {
1247 group->SetName( PADS_COMMON::ConvertText( blockName ) );
1248 m_loadBoard->Add( group );
1249 blockGroups[blockName] = group;
1250 }
1251 }
1252
1253 for( FOOTPRINT* fp : m_loadBoard->Footprints() )
1254 {
1255 for( PCB_FIELD* field : fp->GetFields() )
1256 {
1257 if( field->GetName() == wxT( "PADS_Reuse_Block" ) )
1258 {
1259 std::string blockName = field->GetText().ToStdString();
1260 auto groupIt = blockGroups.find( blockName );
1261
1262 if( groupIt != blockGroups.end() )
1263 groupIt->second->AddItem( fp );
1264
1265 break;
1266 }
1267 }
1268 }
1269}
1270
1271
1273{
1274 const std::vector<PADS_IO::TEST_POINT>& test_points = m_parser->GetTestPoints();
1275 const std::map<std::string, PADS_IO::VIA_DEF>& via_defs = m_parser->GetViaDefs();
1276
1277 for( const PADS_IO::TEST_POINT& tp : test_points )
1278 {
1279 FOOTPRINT* footprint = new FOOTPRINT( m_loadBoard );
1280
1281 wxString refDes = wxString::Format( wxT( "TP%d" ), m_testPointIndex++ );
1282 footprint->SetReference( refDes );
1283 footprint->SetValue( PADS_COMMON::ConvertText( tp.symbol_name ) );
1284
1285 VECTOR2I pos( scaleCoord( tp.x, true ), scaleCoord( tp.y, false ) );
1286 footprint->SetPosition( pos );
1287
1288 // Default layer and size; refined below from the via definition.
1289 PCB_LAYER_ID layer = ( tp.side == 2 ) ? B_Cu : F_Cu;
1290 int tpSize = std::max( scaleSize( 50.0 ), m_minObjectSize );
1291
1292 auto it = via_defs.find( tp.symbol_name );
1293
1294 if( it != via_defs.end() )
1295 {
1296 const PADS_IO::VIA_DEF& def = it->second;
1297
1298 // Inspect the pad stack once to recover both the pad size and the
1299 // board side. A non-zero pad on copper layer -2 (top) or -1 (bottom)
1300 // takes first priority for the side; when those are both zero (an
1301 // in-circuit test point) the soldermask layers (25=top, 28=bottom)
1302 // indicate the side instead. The pad size falls back to the largest
1303 // stack entry when the via definition carries no explicit size.
1304 double stackSize = def.size;
1305 bool hasTopPad = false;
1306 bool hasBottomPad = false;
1307 bool hasMaskTop = false;
1308 bool hasMaskBot = false;
1309
1310 for( const PADS_IO::PAD_STACK_LAYER& stackLayer : def.stack )
1311 {
1312 if( def.size <= 0.0 && stackLayer.sizeA > stackSize )
1313 stackSize = stackLayer.sizeA;
1314
1316 && stackLayer.sizeA > 0.0 )
1317 {
1318 hasTopPad = true;
1319 }
1321 && stackLayer.sizeA > 0.0 )
1322 {
1323 hasBottomPad = true;
1324 }
1325 else if( stackLayer.layer == PADS_LAYER_MAPPER::LAYER_SOLDERMASK_TOP )
1326 {
1327 hasMaskTop = true;
1328 }
1329 else if( stackLayer.layer == PADS_LAYER_MAPPER::LAYER_SOLDERMASK_BOTTOM )
1330 {
1331 hasMaskBot = true;
1332 }
1333 }
1334
1335 if( stackSize > 0.0 )
1336 tpSize = std::max( scaleSize( stackSize ), m_minObjectSize );
1337
1338 if( hasTopPad && !hasBottomPad )
1339 layer = F_Cu;
1340 else if( hasBottomPad && !hasTopPad )
1341 layer = B_Cu;
1342 else if( hasMaskBot && !hasMaskTop )
1343 layer = B_Cu;
1344 else if( hasMaskTop && !hasMaskBot )
1345 layer = F_Cu;
1346 }
1347
1348 footprint->SetLayer( layer );
1349
1350 PAD* pad = new PAD( footprint );
1351 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
1352 pad->SetNumber( wxT( "1" ) );
1353 pad->SetPosition( pos );
1355 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( tpSize, tpSize ) );
1356 pad->SetAttribute( PAD_ATTRIB::SMD );
1357 pad->SetLayerSet( layer == B_Cu ? LSET( { B_Cu } ) : LSET( { F_Cu } ) );
1358
1359 if( !tp.net_name.empty() )
1360 {
1361 NETINFO_ITEM* net = m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( tp.net_name ) );
1362
1363 if( net )
1364 pad->SetNet( net );
1365 }
1366
1367 footprint->Add( pad );
1368
1369 footprint->SetBoardOnly( true );
1370
1371 PCB_FIELD* tpField = new PCB_FIELD( footprint, FIELD_T::USER, wxT( "Test_Point" ) );
1372 tpField->SetLayer( Cmts_User );
1373 tpField->SetVisible( false );
1374 tpField->SetText( PADS_COMMON::ConvertText( tp.type ) );
1375 footprint->Add( tpField );
1376
1377 m_loadBoard->Add( footprint );
1378 }
1379}
1380
1381
1383{
1384 const std::vector<PADS_IO::ROUTE>& routes = m_parser->GetRoutes();
1385 std::set<std::pair<int, int>> placedThroughVias;
1386
1387 // Build a position set for test-point vias so we don't also place a bare
1388 // PCB_VIA at those locations; loadTestPoints() already creates footprints.
1389 std::set<std::pair<int, int>> testPointPositions;
1390
1391 for( const PADS_IO::TEST_POINT& tp : m_parser->GetTestPoints() )
1392 {
1393 if( tp.type == "VIA" )
1394 testPointPositions.emplace( scaleCoord( tp.x, true ), scaleCoord( tp.y, false ) );
1395 }
1396
1397 for( const PADS_IO::ROUTE& route : routes )
1398 {
1399 NETINFO_ITEM* net = m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( route.net_name ) );
1400
1401 if( !net )
1402 continue;
1403
1404 for( const PADS_IO::TRACK& track_def : route.tracks )
1405 {
1406 if( track_def.points.size() < 2 )
1407 continue;
1408
1409 PCB_LAYER_ID track_layer = getMappedLayer( track_def.layer );
1410
1411 if( !IsCopperLayer( track_layer ) )
1412 {
1413 if( m_reporter )
1414 {
1415 m_reporter->Report( wxString::Format( _( "Skipping track on non-copper layer %d" ),
1416 track_def.layer ),
1418 }
1419
1420 continue;
1421 }
1422
1423 int track_width = std::max( scaleSize( track_def.width ), m_minObjectSize );
1424
1425 for( size_t i = 0; i < track_def.points.size() - 1; ++i )
1426 {
1427 const PADS_IO::ARC_POINT& p1 = track_def.points[i];
1428 const PADS_IO::ARC_POINT& p2 = track_def.points[i + 1];
1429
1430 VECTOR2I start( scaleCoord( p1.x, true ), scaleCoord( p1.y, false ) );
1431 VECTOR2I end( scaleCoord( p2.x, true ), scaleCoord( p2.y, false ) );
1432
1433 // Skip near-zero-length segments (can occur at via points with width changes).
1434 // Tolerance of 1000nm accounts for floating point precision in coordinate
1435 // transformation.
1436 if( ( start - end ).EuclideanNorm() < 1000 )
1437 continue;
1438
1439 if( p2.is_arc )
1440 {
1441 SHAPE_ARC shapeArc = m_converter->MakeMidpointArc( p1, p2, track_width );
1442
1443 PCB_ARC* arc = new PCB_ARC( m_loadBoard, &shapeArc );
1444 arc->SetNet( net );
1445 arc->SetWidth( track_width );
1446 arc->SetLayer( track_layer );
1447 m_loadBoard->Add( arc );
1448 }
1449 else
1450 {
1451 PCB_TRACK* track = new PCB_TRACK( m_loadBoard );
1452 track->SetNet( net );
1453 track->SetWidth( track_width );
1454 track->SetLayer( track_layer );
1455 track->SetStart( start );
1456 track->SetEnd( end );
1457 m_loadBoard->Add( track );
1458 }
1459 }
1460 }
1461
1462 for( const PADS_IO::VIA& via_def : route.vias )
1463 {
1464 VECTOR2I pos( scaleCoord( via_def.location.x, true ), scaleCoord( via_def.location.y, false ) );
1465
1466 // Test-point vias are imported as footprints by loadTestPoints().
1467 if( testPointPositions.count( { pos.x, pos.y } ) )
1468 continue;
1469
1470 VIATYPE viaType = VIATYPE::THROUGH;
1471 auto it = m_parser->GetViaDefs().find( via_def.name );
1472
1473 if( it != m_parser->GetViaDefs().end() )
1474 {
1475 switch( it->second.via_type )
1476 {
1477 case PADS_IO::VIA_TYPE::THROUGH: viaType = VIATYPE::THROUGH; break;
1478 case PADS_IO::VIA_TYPE::BLIND: viaType = VIATYPE::BLIND; break;
1479 case PADS_IO::VIA_TYPE::BURIED: viaType = VIATYPE::BURIED; break;
1480 case PADS_IO::VIA_TYPE::MICROVIA: viaType = VIATYPE::MICROVIA; break;
1481 }
1482 }
1483
1484 // Through-hole vias shared across multiple SIGNAL blocks for the same net
1485 // produce duplicates. Skip if we already placed one at this position.
1486 if( viaType == VIATYPE::THROUGH )
1487 {
1488 auto key = std::make_pair( pos.x, pos.y );
1489
1490 if( placedThroughVias.count( key ) )
1491 continue;
1492
1493 placedThroughVias.insert( key );
1494 }
1495
1496 PCB_VIA* via = new PCB_VIA( m_loadBoard );
1497 via->SetNet( net );
1498 via->SetPosition( pos );
1499 via->SetPadstackMode( PADSTACK::MODE::NORMAL );
1500 via->Padstack().SetShape( PAD_SHAPE::CIRCLE, PADSTACK::ALL_LAYERS );
1501
1502 if( it != m_parser->GetViaDefs().end() )
1503 {
1504 const PADS_IO::VIA_DEF& def = it->second;
1505
1506 via->SetWidth( PADSTACK::ALL_LAYERS, std::max( scaleSize( def.size ), m_minObjectSize ) );
1507 via->SetDrill( std::max( scaleSize( def.drill ), m_minObjectSize ) );
1508
1509 PCB_LAYER_ID startLayer = ( def.start_layer > 0 ) ? getMappedLayer( def.start_layer )
1511 PCB_LAYER_ID endLayer = ( def.end_layer > 0 ) ? getMappedLayer( def.end_layer )
1513
1514 if( startLayer != UNDEFINED_LAYER && endLayer != UNDEFINED_LAYER )
1515 {
1516 via->SetLayerPair( startLayer, endLayer );
1517 via->SetViaType( viaType );
1518 }
1519 else
1520 {
1521 via->SetLayerPair( F_Cu, B_Cu );
1522 via->SetViaType( VIATYPE::THROUGH );
1523 }
1524
1525 if( !def.has_mask_front )
1526 via->SetFrontTentingMode( TENTING_MODE::TENTED );
1527
1528 if( !def.has_mask_back )
1529 via->SetBackTentingMode( TENTING_MODE::TENTED );
1530 }
1531 else
1532 {
1533 via->SetWidth( PADSTACK::ALL_LAYERS, std::max( scaleSize( 20.0 ), m_minObjectSize ) );
1534 via->SetDrill( std::max( scaleSize( 10.0 ), m_minObjectSize ) );
1535 via->SetLayerPair( F_Cu, B_Cu );
1536 via->SetViaType( VIATYPE::THROUGH );
1537 }
1538
1539 m_loadBoard->Add( via );
1540 }
1541 }
1542}
1543
1544
1546{
1547 const std::vector<PADS_IO::COPPER_SHAPE>& copperShapes = m_parser->GetCopperShapes();
1548
1549 // Check if a COPPER_SHAPE is a non-copper straight-line segment suitable for
1550 // rectangle grouping (2 outline points, no arcs, not filled, not cutout).
1551 auto isRectCandidate =
1552 []( const PADS_IO::COPPER_SHAPE& cs )
1553 {
1554 return cs.outline.size() == 2 && !cs.outline[1].is_arc
1555 && !cs.filled && !cs.is_cutout;
1556 };
1557
1558 // Check if 4 consecutive entries at idx form a closed axis-aligned rectangle.
1559 // Each entry must have the same net_name and layer, and consecutive segment
1560 // endpoints must connect to form a closed cycle with only horizontal/vertical edges.
1561 auto tryFormRectangle =
1562 [&]( size_t idx, VECTOR2I& minCorner, VECTOR2I& maxCorner ) -> bool
1563 {
1564 if( idx + 3 >= copperShapes.size() )
1565 return false;
1566
1567 const PADS_IO::COPPER_SHAPE& c0 = copperShapes[idx];
1568 const PADS_IO::COPPER_SHAPE& c1 = copperShapes[idx + 1];
1569 const PADS_IO::COPPER_SHAPE& c2 = copperShapes[idx + 2];
1570 const PADS_IO::COPPER_SHAPE& c3 = copperShapes[idx + 3];
1571
1572 if( !isRectCandidate( c0 ) || !isRectCandidate( c1 )
1573 || !isRectCandidate( c2 ) || !isRectCandidate( c3 ) )
1574 {
1575 return false;
1576 }
1577
1578 if( c1.net_name != c0.net_name || c2.net_name != c0.net_name || c3.net_name != c0.net_name )
1579 return false;
1580
1581 if( c1.layer != c0.layer || c2.layer != c0.layer || c3.layer != c0.layer )
1582 return false;
1583
1584 // Get the 4 segment start/end pairs in scaled coordinates
1585 VECTOR2I pts[8];
1586 const PADS_IO::COPPER_SHAPE* segs[4] = { &c0, &c1, &c2, &c3 };
1587
1588 for( int i = 0; i < 4; ++i )
1589 {
1590 pts[i * 2] = VECTOR2I( scaleCoord( segs[i]->outline[0].x, true ),
1591 scaleCoord( segs[i]->outline[0].y, false ) );
1592 pts[i * 2 + 1] = VECTOR2I( scaleCoord( segs[i]->outline[1].x, true ),
1593 scaleCoord( segs[i]->outline[1].y, false ) );
1594 }
1595
1596 // Each segment must be axis-aligned
1597 for( int i = 0; i < 4; ++i )
1598 {
1599 VECTOR2I s = pts[i * 2];
1600 VECTOR2I e = pts[i * 2 + 1];
1601
1602 if( s.x != e.x && s.y != e.y )
1603 return false;
1604 }
1605
1606 // Consecutive segments must connect (end of N == start of N+1)
1607 for( int i = 0; i < 3; ++i )
1608 {
1609 if( pts[i * 2 + 1] != pts[( i + 1 ) * 2] )
1610 return false;
1611 }
1612
1613 // Cycle must close (end of last == start of first)
1614 if( pts[7] != pts[0] )
1615 return false;
1616
1617 // Compute bounding box from the 4 corner points
1618 int minX = pts[0].x, maxX = pts[0].x;
1619 int minY = pts[0].y, maxY = pts[0].y;
1620
1621 for( int i = 0; i < 8; ++i )
1622 {
1623 minX = std::min( minX, pts[i].x );
1624 maxX = std::max( maxX, pts[i].x );
1625 minY = std::min( minY, pts[i].y );
1626 maxY = std::max( maxY, pts[i].y );
1627 }
1628
1629 minCorner = VECTOR2I( minX, minY );
1630 maxCorner = VECTOR2I( maxX, maxY );
1631 return true;
1632 };
1633
1634 for( size_t idx = 0; idx < copperShapes.size(); ++idx )
1635 {
1636 const PADS_IO::COPPER_SHAPE& copper = copperShapes[idx];
1637
1638 if( copper.outline.size() < 2 )
1639 continue;
1640
1641 if( copper.is_cutout )
1642 continue;
1643
1644 PCB_LAYER_ID layer = getMappedLayer( copper.layer );
1645
1646 if( layer == UNDEFINED_LAYER )
1647 {
1648 if( m_reporter )
1649 {
1650 m_reporter->Report( wxString::Format( _( "COPPER item on unmapped layer %d defaulting to F.Cu" ),
1651 copper.layer ),
1653 }
1654
1655 layer = F_Cu;
1656 }
1657
1658 int width = std::max( scaleSize( copper.width ), m_minObjectSize );
1659
1660 if( !IsCopperLayer( layer ) )
1661 {
1662 // Check for 4 consecutive entries forming an axis-aligned rectangle
1663 VECTOR2I minCorner, maxCorner;
1664
1665 if( tryFormRectangle( idx, minCorner, maxCorner ) )
1666 {
1667 PCB_SHAPE* rect = new PCB_SHAPE( m_loadBoard );
1669 rect->SetStart( minCorner );
1670 rect->SetEnd( maxCorner );
1671 rect->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
1672 rect->SetLayer( layer );
1673 m_loadBoard->Add( rect );
1674
1675 idx += 3;
1676 continue;
1677 }
1678
1679 // EasyEDA PADS exports place footprint silkscreen outlines in the *LINES*
1680 // section as COPPER type on the silkscreen layer. Import these as board
1681 // graphics on their actual layer rather than forcing them onto copper.
1682 for( size_t i = 0; i < copper.outline.size() - 1; ++i )
1683 {
1684 const PADS_IO::ARC_POINT& p1 = copper.outline[i];
1685 const PADS_IO::ARC_POINT& p2 = copper.outline[i + 1];
1686
1687 VECTOR2I start( scaleCoord( p1.x, true ), scaleCoord( p1.y, false ) );
1688 VECTOR2I end( scaleCoord( p2.x, true ), scaleCoord( p2.y, false ) );
1689
1690 if( ( start - end ).EuclideanNorm() < 1000 )
1691 continue;
1692
1693 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
1694
1695 if( p2.is_arc )
1696 {
1697 setPcbShapeArc( shape, p1, p2 );
1698 }
1699 else
1700 {
1701 shape->SetShape( SHAPE_T::SEGMENT );
1702 shape->SetStart( start );
1703 shape->SetEnd( end );
1704 }
1705
1706 shape->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
1707 shape->SetLayer( layer );
1708 m_loadBoard->Add( shape );
1709 }
1710
1711 continue;
1712 }
1713
1714 NETINFO_ITEM* net = nullptr;
1715
1716 if( !copper.net_name.empty() )
1717 net = m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( copper.net_name ) );
1718
1719 if( copper.filled )
1720 {
1721 if( copper.outline.size() < 3 )
1722 continue;
1723
1724 ZONE* zone = new ZONE( m_loadBoard );
1725 zone->SetLayer( layer );
1726 zone->SetIsRuleArea( false );
1727
1728 if( net )
1729 zone->SetNet( net );
1730
1731 SHAPE_LINE_CHAIN outline;
1732 m_converter->AppendArcPoints( outline, copper.outline );
1733 outline.SetClosed( true );
1734 zone->Outline()->AddOutline( outline );
1736
1737 m_loadBoard->Add( zone );
1738 }
1739 else
1740 {
1741 for( size_t i = 0; i < copper.outline.size() - 1; ++i )
1742 {
1743 const PADS_IO::ARC_POINT& p1 = copper.outline[i];
1744 const PADS_IO::ARC_POINT& p2 = copper.outline[i + 1];
1745
1746 VECTOR2I start( scaleCoord( p1.x, true ), scaleCoord( p1.y, false ) );
1747 VECTOR2I end( scaleCoord( p2.x, true ), scaleCoord( p2.y, false ) );
1748
1749 if( ( start - end ).EuclideanNorm() < 1000 )
1750 continue;
1751
1752 if( p2.is_arc )
1753 {
1754 SHAPE_ARC shapeArc = m_converter->MakeMidpointArc( p1, p2, width );
1755
1756 PCB_ARC* arc = new PCB_ARC( m_loadBoard, &shapeArc );
1757
1758 if( net )
1759 arc->SetNet( net );
1760
1761 arc->SetWidth( width );
1762 arc->SetLayer( layer );
1763 m_loadBoard->Add( arc );
1764 }
1765 else
1766 {
1767 PCB_TRACK* track = new PCB_TRACK( m_loadBoard );
1768
1769 if( net )
1770 track->SetNet( net );
1771
1772 track->SetWidth( width );
1773 track->SetLayer( layer );
1774 track->SetStart( start );
1775 track->SetEnd( end );
1776 m_loadBoard->Add( track );
1777 }
1778 }
1779 }
1780 }
1781}
1782
1783
1785{
1786 const std::vector<PADS_IO::CLUSTER>& clusters = m_parser->GetClusters();
1787
1788 if( clusters.empty() )
1789 return;
1790
1791 std::map<std::string, const PADS_IO::CLUSTER*> netToClusterMap;
1792
1793 for( const PADS_IO::CLUSTER& cluster : clusters )
1794 {
1795 for( const std::string& netName : cluster.net_names )
1796 {
1797 std::string converted = PADS_COMMON::ConvertInvertedNetName( netName ).ToStdString();
1798 netToClusterMap[converted] = &cluster;
1799 }
1800 }
1801
1802 std::map<int, PCB_GROUP*> clusterGroups;
1803
1804 for( const PADS_IO::CLUSTER& cluster : clusters )
1805 {
1807 group->SetName( PADS_COMMON::ConvertText( cluster.name ) );
1808 m_loadBoard->Add( group );
1809 clusterGroups[cluster.id] = group;
1810 }
1811
1812 for( PCB_TRACK* track : m_loadBoard->Tracks() )
1813 {
1814 NETINFO_ITEM* net = track->GetNet();
1815
1816 if( net )
1817 {
1818 std::string netName = net->GetNetname().ToStdString();
1819 auto clusterIt = netToClusterMap.find( netName );
1820
1821 if( clusterIt != netToClusterMap.end() )
1822 {
1823 int clusterId = clusterIt->second->id;
1824 auto groupIt = clusterGroups.find( clusterId );
1825
1826 if( groupIt != clusterGroups.end() )
1827 groupIt->second->AddItem( track );
1828 }
1829 }
1830 }
1831}
1832
1833
1835{
1836 const std::vector<PADS_IO::POUR>& pours = m_parser->GetPours();
1837 const PADS_IO::PARAMETERS& params = m_parser->GetParameters();
1838
1839 // Returns true if the points can produce a valid polygon (at least 3 vertices
1840 // for a regular polygon, or a single full-circle point).
1841 auto isValidPoly =
1842 []( const std::vector<PADS_IO::ARC_POINT>& pts )
1843 {
1844 if( pts.size() >= 3 )
1845 return true;
1846
1847 if( pts.size() == 1 && pts[0].is_arc && std::abs( pts[0].arc.delta_angle ) >= 359.0 )
1848 return true;
1849
1850 return false;
1851 };
1852
1853 // PADS uses lower numbers = higher priority (priority 1 fills on top),
1854 // while KiCad uses higher numbers = higher priority.
1855 int maxPriority = 0;
1856
1857 for( const PADS_IO::POUR& pour_def : pours )
1858 {
1859 if( pour_def.priority > maxPriority )
1860 maxPriority = pour_def.priority;
1861 }
1862
1863 // Map from pour name to created zone for linking HATOUT/VOIDOUT later
1864 std::map<std::string, ZONE*> pourZoneMap;
1865
1866 // Map from HATOUT name to parent POUROUT name for VOIDOUT chain resolution
1867 std::map<std::string, std::string> hatoutToParent;
1868
1869 // First pass: create zones from POUROUT records and build lookup maps
1870 for( const PADS_IO::POUR& pour_def : pours )
1871 {
1872 if( pour_def.style == PADS_IO::POUR_STYLE::HATCHED )
1873 {
1874 hatoutToParent[pour_def.name] = pour_def.owner_pour;
1875 continue;
1876 }
1877
1878 if( pour_def.style == PADS_IO::POUR_STYLE::VOIDOUT
1879 || pour_def.thermal_type != PADS_IO::THERMAL_TYPE::NONE )
1880 {
1881 continue;
1882 }
1883
1884 if( !isValidPoly( pour_def.points ) )
1885 continue;
1886
1887 PCB_LAYER_ID pourLayer = getMappedLayer( pour_def.layer );
1888
1889 if( pourLayer == UNDEFINED_LAYER )
1890 {
1891 if( m_reporter )
1892 {
1893 m_reporter->Report( wxString::Format( _( "Skipping pour on unmapped layer %d" ), pour_def.layer ),
1895 }
1896
1897 continue;
1898 }
1899
1900 ZONE* zone = new ZONE( m_loadBoard );
1901 zone->SetLayer( pourLayer );
1902
1903 zone->Outline()->NewOutline();
1904 m_converter->AppendArcPoints( zone->Outline()->Outline( 0 ), pour_def.points );
1906
1907 m_converter->ApplyPourSettings( zone, pour_def, maxPriority, params );
1908
1909 pourZoneMap[pour_def.name] = zone;
1910 m_loadBoard->Add( zone );
1911 }
1912
1913 // Second pass: build fill polygons from HATOUT records with VOIDOUT holes
1914 for( const PADS_IO::POUR& pour_def : pours )
1915 {
1916 if( pour_def.style != PADS_IO::POUR_STYLE::HATCHED )
1917 continue;
1918
1919 if( !isValidPoly( pour_def.points ) )
1920 continue;
1921
1922 auto zoneIt = pourZoneMap.find( pour_def.owner_pour );
1923
1924 if( zoneIt == pourZoneMap.end() )
1925 continue;
1926
1927 ZONE* zone = zoneIt->second;
1928 PCB_LAYER_ID pourLayer = zone->GetLayer();
1929
1930 SHAPE_POLY_SET fillPoly;
1931 fillPoly.NewOutline();
1932 m_converter->AppendArcPoints( fillPoly.Outline( 0 ), pour_def.points );
1933
1934 // PADS HATOUT fill data can contain self-intersecting vertices where
1935 // narrow corridors route between pads. Run Clipper2 union on the
1936 // outline before subtracting holes, since Simplify can introduce
1937 // micro-artifacts in clean complex polygons.
1938 if( fillPoly.Outline( 0 ).PointCount() >= 3 && fillPoly.IsPolygonSelfIntersecting( 0 ) )
1939 fillPoly.Simplify();
1940
1941 fillPoly.Inflate( scaleSize( pour_def.width ) / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF );
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 PADS_IO::POUR& 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 SHAPE_POLY_SET voidPoly;
1970 voidPoly.NewOutline();
1971 m_converter->AppendArcPoints( voidPoly.Outline( 0 ), void_def.points );
1972 voidPoly.Inflate( scaleSize( void_def.width ) / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF );
1973
1974 allVoids.Append( voidPoly );
1975 }
1976
1977 if( allVoids.OutlineCount() > 0 )
1978 fillPoly.BooleanSubtract( allVoids );
1979
1980 zone->SetFilledPolysList( pourLayer, fillPoly );
1981 zone->SetIsFilled( true );
1982 }
1983}
1984
1985
1987{
1988 for( const PADS_IO::POLYLINE& polyline : m_parser->GetBoardOutlines() )
1989 {
1990 const auto& pts = polyline.points;
1991
1992 if( pts.size() < 2 )
1993 continue;
1994
1995 for( size_t i = 0; i < pts.size() - 1; ++i )
1996 {
1997 const PADS_IO::ARC_POINT& p1 = pts[i];
1998 const PADS_IO::ARC_POINT& p2 = pts[i + 1];
1999
2000 if( std::abs( p1.x - p2.x ) < 0.001 && std::abs( p1.y - p2.y ) < 0.001 )
2001 continue;
2002
2003 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2004
2005 if( p2.is_arc )
2006 {
2007 setPcbShapeArc( shape, p1, p2 );
2008 }
2009 else
2010 {
2011 shape->SetShape( SHAPE_T::SEGMENT );
2012 shape->SetStart( VECTOR2I( scaleCoord( p1.x, true ), scaleCoord( p1.y, false ) ) );
2013 shape->SetEnd( VECTOR2I( scaleCoord( p2.x, true ), scaleCoord( p2.y, false ) ) );
2014 }
2015
2016 shape->SetWidth( scaleSize( polyline.width ) );
2017 shape->SetLayer( Edge_Cuts );
2018 m_loadBoard->Add( shape );
2019 }
2020
2021 // PADS format repeats the first point at the end for closed polygons, so check
2022 // if pLast already equals pFirst to avoid creating a zero-length closing segment
2023 if( polyline.closed && pts.size() > 2 )
2024 {
2025 const PADS_IO::ARC_POINT& pLast = pts.back();
2026 const PADS_IO::ARC_POINT& pFirst = pts.front();
2027
2028 bool needsClosing = ( std::abs( pLast.x - pFirst.x ) > 0.001
2029 || std::abs( pLast.y - pFirst.y ) > 0.001 );
2030
2031 if( needsClosing )
2032 {
2033 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2034
2035 if( pFirst.is_arc )
2036 {
2037 setPcbShapeArc( shape, pLast, pFirst );
2038 }
2039 else
2040 {
2041 shape->SetShape( SHAPE_T::SEGMENT );
2042 shape->SetStart( VECTOR2I( scaleCoord( pLast.x, true ), scaleCoord( pLast.y, false ) ) );
2043 shape->SetEnd( VECTOR2I( scaleCoord( pFirst.x, true ), 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 for( const PADS_IO::GRAPHIC_LINE& graphic : m_parser->GetGraphicLines() )
2058 {
2059 const std::vector<PADS_IO::ARC_POINT>& pts = graphic.points;
2060
2061 PCB_LAYER_ID graphicLayer = getMappedLayer( graphic.layer );
2062
2063 if( graphicLayer == UNDEFINED_LAYER )
2064 continue;
2065
2066 if( pts.size() == 1 && pts[0].is_arc && std::abs( pts[0].arc.delta_angle - 360.0 ) < 0.1 )
2067 {
2068 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2069 shape->SetShape( SHAPE_T::CIRCLE );
2070 VECTOR2I center( scaleCoord( pts[0].arc.cx, true ), scaleCoord( pts[0].arc.cy, false ) );
2071 int radius = std::max( scaleSize( pts[0].arc.radius ), m_minObjectSize );
2072 shape->SetCenter( center );
2073 shape->SetEnd( VECTOR2I( center.x + radius, center.y ) );
2074 shape->SetWidth( scaleSize( graphic.width ) );
2075 shape->SetLayer( graphicLayer );
2076 m_loadBoard->Add( shape );
2077 continue;
2078 }
2079
2080 if( pts.size() < 2 )
2081 continue;
2082
2083 for( size_t i = 0; i < pts.size() - 1; ++i )
2084 {
2085 const PADS_IO::ARC_POINT& p1 = pts[i];
2086 const PADS_IO::ARC_POINT& p2 = pts[i + 1];
2087
2088 if( std::abs( p1.x - p2.x ) < 0.001 && std::abs( p1.y - p2.y ) < 0.001 )
2089 continue;
2090
2091 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2092
2093 if( p2.is_arc )
2094 {
2095 setPcbShapeArc( shape, p1, p2 );
2096 }
2097 else
2098 {
2099 shape->SetShape( SHAPE_T::SEGMENT );
2100 shape->SetStart( VECTOR2I( scaleCoord( p1.x, true ), scaleCoord( p1.y, false ) ) );
2101 shape->SetEnd( VECTOR2I( scaleCoord( p2.x, true ), scaleCoord( p2.y, false ) ) );
2102 }
2103
2104 shape->SetWidth( scaleSize( graphic.width ) );
2105 shape->SetLayer( graphicLayer );
2106 m_loadBoard->Add( shape );
2107 }
2108
2109 if( graphic.closed && pts.size() > 2 )
2110 {
2111 const PADS_IO::ARC_POINT& pLast = pts.back();
2112 const PADS_IO::ARC_POINT& pFirst = pts.front();
2113
2114 bool needsClosing = ( std::abs( pLast.x - pFirst.x ) > 0.001
2115 || std::abs( pLast.y - pFirst.y ) > 0.001 );
2116
2117 if( needsClosing )
2118 {
2119 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2120
2121 if( pFirst.is_arc )
2122 {
2123 setPcbShapeArc( shape, pLast, pFirst );
2124 }
2125 else
2126 {
2127 shape->SetShape( SHAPE_T::SEGMENT );
2128 shape->SetStart( VECTOR2I( scaleCoord( pLast.x, true ), scaleCoord( pLast.y, false ) ) );
2129 shape->SetEnd( VECTOR2I( scaleCoord( pFirst.x, true ), scaleCoord( pFirst.y, false ) ) );
2130 }
2131
2132 shape->SetWidth( scaleSize( graphic.width ) );
2133 shape->SetLayer( graphicLayer );
2134 m_loadBoard->Add( shape );
2135 }
2136 }
2137 }
2138}
2139
2140
2141std::map<wxString, PCB_LAYER_ID> PCB_IO_PADS::DefaultLayerMappingCallback(
2142 const std::vector<INPUT_LAYER_DESC>& aInputLayerDescriptionVector )
2143{
2144 std::map<wxString, PCB_LAYER_ID> layer_map;
2145
2146 for( const INPUT_LAYER_DESC& layer : aInputLayerDescriptionVector )
2147 layer_map[layer.Name] = layer.AutoMapLayer;
2148
2149 return layer_map;
2150}
2151
2152
2153double PCB_IO_PADS::decalUnitScale( const std::string& aUnits ) const
2154{
2155 if( m_parser->IsBasicUnits() )
2156 return 0.0;
2157
2158 if( aUnits == "I" || aUnits == "MIL" || aUnits == "MILS" )
2160
2161 if( aUnits == "M" || aUnits == "MM" || aUnits == "METRIC" )
2163
2164 if( aUnits == "INCH" || aUnits == "INCHES" )
2166
2167 return 0.0;
2168}
2169
2170
2172{
2173 m_loadBoard = nullptr;
2174 m_parser = nullptr;
2175 m_converter.reset();
2176 m_pinToNetMap.clear();
2177 m_partToBlockMap.clear();
2178 m_testPointIndex = 1;
2179}
2180
2181
2183 const PADS_IO::ARC_POINT& aCurr )
2184{
2185 aShape->SetShape( SHAPE_T::ARC );
2186
2187 VECTOR2I center( scaleCoord( aCurr.arc.cx, true ), scaleCoord( aCurr.arc.cy, false ) );
2188 VECTOR2I start( scaleCoord( aPrev.x, true ), scaleCoord( aPrev.y, false ) );
2189 VECTOR2I end( scaleCoord( aCurr.x, true ), scaleCoord( aCurr.y, false ) );
2190
2191 // Y-axis flip reverses arc winding; swap endpoints for CCW arcs
2192 if( aCurr.arc.delta_angle > 0 )
2193 std::swap( start, end );
2194
2195 aShape->SetCenter( center );
2196 aShape->SetStart( start );
2197 aShape->SetEnd( end );
2198}
2199
2200
2202{
2203 std::vector<PADS_IO::LAYER_INFO> padsLayerInfos = m_parser->GetLayerInfos();
2204
2205 // The ASCII layer table always declares a function, so an unresolved layer type means
2206 // the file said nothing useful and the layer name is no better a guess.
2207 m_converter->SetupLayers( padsLayerInfos, m_parser->GetParameters().layer_count, m_layer_mapping_handler, false );
2208
2209 if( m_parser->IsBasicUnits() )
2210 {
2211 m_converter->UnitConverter().SetBasicUnitsMode( true );
2212 }
2213 else
2214 {
2215 switch( m_parser->GetParameters().units )
2216 {
2217 case PADS_IO::UNIT_TYPE::MILS: m_converter->UnitConverter().SetBaseUnits( PADS_UNIT_TYPE::MILS ); break;
2218 case PADS_IO::UNIT_TYPE::METRIC: m_converter->UnitConverter().SetBaseUnits( PADS_UNIT_TYPE::METRIC ); break;
2219 case PADS_IO::UNIT_TYPE::INCHES: m_converter->UnitConverter().SetBaseUnits( PADS_UNIT_TYPE::INCHES ); break;
2220 }
2221 }
2222
2223 m_converter->SetScaleFactor( m_parser->IsBasicUnits()
2225 : ( m_parser->GetParameters().units == PADS_IO::UNIT_TYPE::MILS
2227 : m_parser->GetParameters().units == PADS_IO::UNIT_TYPE::METRIC
2230
2231 const PADS_IO::DESIGN_RULES& designRules = m_parser->GetDesignRules();
2232 BOARD_DESIGN_SETTINGS& bds = m_loadBoard->GetDesignSettings();
2233
2234 bds.m_MinClearance = scaleSize( designRules.min_clearance );
2235 bds.m_TrackMinWidth = scaleSize( designRules.min_track_width );
2236 bds.m_ViasMinSize = scaleSize( designRules.min_via_size );
2237 bds.m_MinThroughDrill = scaleSize( designRules.min_via_drill );
2238 bds.m_HoleToHoleMin = scaleSize( designRules.hole_to_hole );
2239 bds.m_SilkClearance = scaleSize( designRules.silk_clearance );
2240 bds.m_SolderMaskExpansion = scaleSize( designRules.mask_clearance );
2242
2243 // Do not set the default zone clearance from the PADS design rules. In PADS,
2244 // zone (copper pour) clearance is resolved through the net/netclass clearance
2245 // rules rather than a board-level zone clearance setting. The default netclass
2246 // clearance set below is the correct mapping for PADS' DEFAULTCLEAR value.
2247
2249 bds.SetCustomViaSize( scaleSize( designRules.default_via_size ) );
2250 bds.SetCustomViaDrill( scaleSize( designRules.default_via_drill ) );
2251
2252 std::shared_ptr<NETCLASS> defaultNetclass = bds.m_NetSettings->GetDefaultNetclass();
2253
2254 if( defaultNetclass )
2255 {
2256 defaultNetclass->SetClearance( scaleSize( designRules.default_clearance ) );
2257 defaultNetclass->SetTrackWidth( scaleSize( designRules.default_track_width ) );
2258 defaultNetclass->SetViaDiameter( scaleSize( designRules.default_via_size ) );
2259 defaultNetclass->SetViaDrill( scaleSize( designRules.default_via_drill ) );
2260 }
2261
2262 const std::map<std::string, PADS_IO::VIA_DEF>& viaDefs = m_parser->GetViaDefs();
2263
2264 if( !viaDefs.empty() )
2265 {
2266 // Use the file's designated default signal via, falling back to the first
2267 // definition if no explicit default was specified
2268 const std::string& defaultViaName = m_parser->GetParameters().default_signal_via;
2269 auto defaultIt = viaDefs.find( defaultViaName );
2270
2271 if( defaultIt == viaDefs.end() )
2272 defaultIt = viaDefs.begin();
2273
2274 int viaDia = scaleSize( defaultIt->second.size );
2275 int viaDrill = scaleSize( defaultIt->second.drill );
2276
2277 bds.SetCustomViaSize( viaDia );
2278 bds.SetCustomViaDrill( viaDrill );
2279
2280 if( defaultNetclass )
2281 {
2282 defaultNetclass->SetViaDiameter( viaDia );
2283 defaultNetclass->SetViaDrill( viaDrill );
2284 }
2285
2286 for( const auto& [name, def] : viaDefs )
2287 bds.m_ViasDimensionsList.emplace_back( scaleSize( def.size ), scaleSize( def.drill ) );
2288 }
2289
2290 const std::vector<PADS_IO::NET_CLASS_DEF>& netClasses = m_parser->GetNetClasses();
2291
2292 for( const PADS_IO::NET_CLASS_DEF& nc : netClasses )
2293 {
2294 if( nc.name.empty() )
2295 continue;
2296
2297 wxString ncName = PADS_COMMON::ConvertText( nc.name );
2298 std::shared_ptr<NETCLASS> netclass = std::make_shared<NETCLASS>( ncName );
2299
2300 if( nc.clearance > 0 )
2301 netclass->SetClearance( scaleSize( nc.clearance ) );
2302
2303 if( nc.track_width > 0 )
2304 netclass->SetTrackWidth( scaleSize( nc.track_width ) );
2305
2306 if( nc.via_size > 0 )
2307 netclass->SetViaDiameter( scaleSize( nc.via_size ) );
2308
2309 if( nc.via_drill > 0 )
2310 netclass->SetViaDrill( scaleSize( nc.via_drill ) );
2311
2312 if( nc.diff_pair_width > 0 )
2313 netclass->SetDiffPairWidth( scaleSize( nc.diff_pair_width ) );
2314
2315 if( nc.diff_pair_gap > 0 )
2316 netclass->SetDiffPairGap( scaleSize( nc.diff_pair_gap ) );
2317
2318 bds.m_NetSettings->SetNetclass( ncName, netclass );
2319
2320 for( const std::string& netName : nc.net_names )
2321 {
2322 wxString wxNetName = PADS_COMMON::ConvertInvertedNetName( netName );
2323 bds.m_NetSettings->SetNetclassPatternAssignment( wxNetName, ncName );
2324 }
2325 }
2326
2327 const std::vector<PADS_IO::DIFF_PAIR_DEF>& diffPairs = m_parser->GetDiffPairs();
2328
2329 for( const PADS_IO::DIFF_PAIR_DEF& dp : diffPairs )
2330 {
2331 if( dp.name.empty() )
2332 continue;
2333
2334 wxString dpClassName = wxString::Format( wxT( "DiffPair_%s" ), PADS_COMMON::ConvertText( dp.name ) );
2335 std::shared_ptr<NETCLASS> dpNetclass = std::make_shared<NETCLASS>( dpClassName );
2336
2337 if( dp.gap > 0 )
2338 dpNetclass->SetDiffPairGap( scaleSize( dp.gap ) );
2339
2340 if( dp.width > 0 )
2341 {
2342 dpNetclass->SetDiffPairWidth( scaleSize( dp.width ) );
2343 dpNetclass->SetTrackWidth( scaleSize( dp.width ) );
2344 }
2345
2346 bds.m_NetSettings->SetNetclass( dpClassName, dpNetclass );
2347
2348 if( !dp.positive_net.empty() )
2349 {
2350 wxString wxPosNet = PADS_COMMON::ConvertInvertedNetName( dp.positive_net );
2351 bds.m_NetSettings->SetNetclassPatternAssignment( wxPosNet, dpClassName );
2352 }
2353
2354 if( !dp.negative_net.empty() )
2355 {
2356 wxString wxNegNet = PADS_COMMON::ConvertInvertedNetName( dp.negative_net );
2357 bds.m_NetSettings->SetNetclassPatternAssignment( wxNegNet, dpClassName );
2358 }
2359 }
2360
2361 m_converter->SetOrigin( m_parser->GetParameters().origin.x, m_parser->GetParameters().origin.y );
2362 m_converter->SetOriginFromOutlines( m_parser->GetBoardOutlines() );
2363
2364 m_converter->BuildStackup( padsLayerInfos );
2365}
const char * name
constexpr int ARC_HIGH_DEF
Definition base_units.h:137
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
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:126
virtual void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
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.
void SetCustomViaDrill(int aDrill)
Sets custom size for via drill (i.e.
std::vector< VIA_DIMENSION > m_ViasDimensionsList
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:374
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
void SetCenter(const VECTOR2I &aCenter)
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:373
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:342
void SetKeepUpright(bool aKeepUpright)
Definition eda_text.cpp:381
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:231
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:365
void SetPosition(const VECTOR2I &aPos) override
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:474
void SetOrientation(const EDA_ANGLE &aNewAngle)
void SetPath(const KIID_PATH &aPath)
Definition footprint.h:497
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:939
void SetReference(const wxString &aReference)
Definition footprint.h:907
void SetValue(const wxString &aValue)
Definition footprint.h:930
PCB_FIELD & Reference()
Definition footprint.h:940
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection) override
Flip this object, i.e.
void SetBoardOnly(bool aIsBoardOnly=true)
Definition footprint.h:1020
VECTOR2I GetPosition() const override
Definition footprint.h:435
REPORTER * m_reporter
Reporter to log errors/warnings to, may be nullptr.
Definition io_base.h:238
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:241
Definition kiid.h:46
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:45
int SetLibItemName(const UTF8 &aLibItemName)
Override the library item name portion of the LIB_ID to aLibItemName.
Definition lib_id.cpp:124
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
Handle the data for a net.
Definition netinfo.h:50
const wxString & GetNetname() const
Definition netinfo.h:110
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() const
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 ...
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:170
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:171
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
void Parse(const wxString &aFileName)
static constexpr int LAYER_SOLDERMASK_TOP
static constexpr int LAYER_PAD_STACK_BOTTOM
static constexpr int LAYER_PAD_STACK_TOP
static constexpr int LAYER_SOLDERMASK_BOTTOM
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:61
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:606
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition pad.cpp:627
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
const IO_FILE_DESC GetBoardFileDesc() const override
Returns board file description for the PCB_IO.
int scaleSize(double aVal) const
Shorthands for the converter's transform, which the loaders below use everywhere.
Definition pcb_io_pads.h:65
void loadBoardSetup()
void loadTestPoints()
~PCB_IO_PADS() override
void loadClusterGroups()
void loadTracksAndVias()
std::map< std::string, std::string > m_partToBlockMap
Definition pcb_io_pads.h:97
int scaleCoord(double aVal, bool aIsX) const
Definition pcb_io_pads.h:66
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, applying the Y-axis winding fix.
std::map< std::string, std::string > m_pinToNetMap
Definition pcb_io_pads.h:96
PCB_LAYER_ID getMappedLayer(int aPadsLayer) const
Definition pcb_io_pads.h:67
int m_testPointIndex
Definition pcb_io_pads.h:98
int m_minObjectSize
Definition pcb_io_pads.h:99
void loadFootprints()
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 loadCopperShapes()
BOARD * m_loadBoard
Definition pcb_io_pads.h:93
const IO_FILE_DESC GetLibraryDesc() const override
Get the descriptor for the library container that this IO plugin operates on.
void clearLoadingState()
void loadGraphicLines()
void loadBoard(const wxString &aFileName, BOARD &aBoard, bool aIsNewLoad, const std::map< std::string, UTF8 > *aProperties, PROJECT *aProject) override
Parse aFileName into aBoard.
double decalUnitScale(const std::string &aUnits) const
Resolve a PADS decal/part UNITS letter to a nm-per-unit scale factor, or 0.0 to use the file's primar...
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
Definition pcb_io_pads.h:94
void loadReuseBlockGroups()
std::unique_ptr< PADS_PCB_CONVERTER > m_converter
Definition pcb_io_pads.h:95
virtual bool CanReadBoard(const wxString &aFileName) const
Checks if this PCB_IO can read the specified board file.
Definition pcb_io.cpp:40
PCB_IO(const wxString &aName)
Definition pcb_io.h:351
void SetWidth(int aWidth) override
void SetShape(SHAPE_T aShape) override
Definition pcb_shape.h:207
void SetEnd(const VECTOR2I &aEnd) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
void SetStroke(const STROKE_PARAMS &aStroke) override
void SetTextThickness(int aWidth) override
The TextThickness is that set by the user.
Definition pcb_text.cpp:512
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true) override
Definition pcb_text.cpp:484
void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:102
void SetTextAngle(const EDA_ANGLE &aAngle) override
Definition pcb_text.cpp:569
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:89
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:92
virtual void SetWidth(int aWidth)
Definition pcb_track.h:86
Container for project specific data.
Definition project.h:63
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.
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 Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
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:70
virtual PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition zone.cpp:574
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:641
SHAPE_POLY_SET * Outline()
Definition zone.h:418
void SetIsRuleArea(bool aEnable)
Definition zone.h:808
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition zone.h:721
void SetIsFilled(bool isFilled)
Definition zone.h:307
void SetNet(NETINFO_ITEM *aNetInfo) override
Override that drops aNetInfo when this zone is in copper-thieving fill mode.
Definition zone.cpp:632
static int GetDefaultHatchPitch()
Definition zone.cpp:1617
void SetBorderDisplayStyle(ZONE_BORDER_DISPLAY_STYLE aBorderHatchStyle, int aBorderHatchPitch, bool aRebuilBorderHatch)
Set all hatch parameters for the zone.
Definition zone.cpp:1540
@ ROUND_ALL_CORNERS
All angles are rounded.
#define _(s)
@ DEGREES_T
Definition eda_angle.h:31
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
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_ERRORF(msg,...)
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:829
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Edge_Cuts
Definition layer_ids.h:108
@ F_Paste
Definition layer_ids.h:100
@ Cmts_User
Definition layer_ids.h:104
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ B_Paste
Definition layer_ids.h:101
@ F_SilkS
Definition layer_ids.h:96
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ In1_Cu
Definition layer_ids.h:62
@ B_SilkS
Definition layer_ids.h:97
@ F_Cu
Definition layer_ids.h:60
@ LEFT_RIGHT
Flip left to right (around the Y axis)
Definition mirror.h:24
wxString ConvertText(const std::string &aText)
Decode text from a PADS file, which uses an 8-bit codepage rather than UTF-8.
wxString ConvertInvertedNetName(const std::string &aNetName)
Convert a PADS net name to KiCad notation.
KIID GenerateDeterministicUuid(const std::string &aIdentifier)
Generate a deterministic KIID from a PADS component identifier.
@ BURIED
Inner layers only.
@ THROUGH
Spans all copper layers.
@ BLIND
Surface to inner layer.
@ MICROVIA
Single-layer blind, typically HDI.
bool IsAntiPadRow(const PAD_STACK_LAYER &aLayer)
RA and SA rows carry a plane's anti-pad clearance rather than the pad's own copper.
bool IsThermalReliefPadRow(const PAD_STACK_LAYER &aLayer)
RT and ST rows carry a plane's thermal-relief spoke pattern rather than the pad's own copper.
bool IsCopperPadRow(const PAD_STACK_LAYER &aLayer)
True when the row describes pad or via copper.
PAD_SHAPE PadsShapeToKiCad(const std::string &aShape)
Map a PADS pad-stack shape code to a KiCad pad shape, shared by both PADS PCB importers.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
@ MILS
Thousandths of an inch.
@ METRIC
Millimeters.
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:98
@ PTH
Plated through hole pad.
Definition padstack.h:97
@ CHAMFERED_RECT
Definition padstack.h:59
@ ROUNDRECT
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:53
Class to handle a set of BOARD_ITEMs.
VIATYPE
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_INFO
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 polyline point that may instead be an arc segment.
Definition pads_parser.h:64
ARC arc
Only valid when is_arc is true.
Definition pads_parser.h:68
double delta_angle
Definition pads_parser.h:55
Standalone copper area from the LINES section (type=COPPER), not part of a pour.
std::vector< ARC_POINT > outline
bool is_cutout
COPCUT, COPCCO.
std::string net_name
Empty if unconnected.
bool filled
COPCLS, COPCIR.
double width
For open polylines.
std::vector< ARC_POINT > points
std::string type
CLOSED, OPEN, CIRCLE, COPCLS, TAG, etc.
double silk_clearance
SILKCLEAR.
double default_track_width
DEFAULTTRACKWID.
double default_via_drill
DEFAULTVIADRILL.
double min_track_width
MINTRACKWID.
double min_via_size
MINVIASIZE.
double min_via_drill
MINVIADRILL.
double min_clearance
MINCLEAR.
double default_via_size
DEFAULTVIASIZE.
double copper_edge_clearance
OUTLINE_TO_*.
double default_clearance
DEFAULTCLEAR.
double hole_to_hole
HOLEHOLE.
double mask_clearance
MASKCLEAR.
Non-electrical drawing item from the LINES section (type=LINES).
bool chamfered
Negative corner value in PADS.
double drill
0 for SMD
std::string shape
R, S, A, O, OF, RF, RT, ST, RA, SA, RC, OC.
bool plated
PTH vs NPTH.
double thermal_outer_diameter
Thermal or void in plane.
double slot_orientation
0-179.999 degrees
double thermal_spoke_orientation
First spoke.
double finger_offset
Along orientation axis.
double sizeB
Height for rectangles/ovals.
double corner_radius
Always positive.
double sizeA
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
From {...} block.
A polyline that may contain arc segments, used for board outlines and graphics.
std::vector< ARC_POINT > points
bool has_mask_front
Stack includes top soldermask, layer 25.
int start_layer
First PADS layer in span.
int end_layer
Last PADS layer in span.
std::vector< PAD_STACK_LAYER > stack
bool has_mask_back
Stack includes bottom soldermask, layer 28.
std::string name
@ USER
The field ID hasn't been set yet; field is invalid.
std::string path
KIBIS_PIN * pin
VECTOR2I center
int radius
VECTOR2I end
@ 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
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:225
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
@ THERMAL
Use thermal relief for pads.
Definition zones.h:46