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