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