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