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