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