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 if( decal_it != decals.end() )
506 {
507 auto decalScaler = [&]( double val ) {
508 if( m_parser->IsBasicUnits() )
509 return scaleSize( val );
510
511 const std::string& units = decal_it->second.units;
512
513 if( units == "M" || units == "D" || units == "MILS" || units == "MIL" )
515 else if( units == "MM" || units == "METRIC" )
517 else if( units == "I" || units == "INCHES" || units == "INCH" )
519 else
520 return scaleSize( val );
521 };
522
523 applyAttributes( decal_it->second.attributes, decalScaler );
524 }
525 else
526 {
527 if( m_reporter )
528 {
529 m_reporter->Report(
530 wxString::Format( _( "Footprint '%s' not found in decal list, part skipped" ),
531 decal_name ),
533 }
534 }
535
536 auto partScaler = [&]( double val ) {
537 if( !m_parser->IsBasicUnits() )
538 {
539 if( pads_part.units == "M" ) return KiROUND( val * PADS_UNIT_CONVERTER::MILS_TO_NM );
540 }
541
542 if( pads_part.units == "M" ) return KiROUND( val );
543
544 return scaleSize( val );
545 };
546
547 applyAttributes( pads_part.attributes, partScaler );
548
549 // PADS "Part Type" maps to KiCad Value field. Hide it since it typically
550 // shows the part type name which is not useful on fabrication layers.
551 footprint->Value().SetVisible( false );
552
553 m_loadBoard->Add( footprint );
554
555 auto blockIt = m_partToBlockMap.find( pads_part.name );
556
557 if( blockIt != m_partToBlockMap.end() )
558 {
559 PCB_FIELD* blockField =
560 new PCB_FIELD( footprint, FIELD_T::USER, wxT( "PADS_Reuse_Block" ) );
561 blockField->SetLayer( Cmts_User );
562 blockField->SetVisible( false );
563 blockField->SetText( wxString::FromUTF8( blockIt->second ) );
564 footprint->Add( blockField );
565 }
566
567 if( decal_it == decals.end() )
568 {
569 continue;
570 }
571
572 // Add Pads and Graphics from Decal
573 {
574 const PADS_IO::PART_DECAL& decal = decal_it->second;
575
576 auto decalScaler = [&]( double val ) {
577 if( m_parser->IsBasicUnits() )
578 return scaleSize( val );
579
580 if( decal.units == "M" || decal.units == "D" || decal.units == "MILS"
581 || decal.units == "MIL" )
583 else if( decal.units == "MM" || decal.units == "METRIC" )
585 else if( decal.units == "I" || decal.units == "INCHES" || decal.units == "INCH" )
587 else
588 return scaleSize( val );
589 };
590
591 auto convertPadShape = [&]( const PADS_IO::PAD_STACK_LAYER& layer_def,
592 PAD* pad, PCB_LAYER_ID kicad_layer,
593 const EDA_ANGLE& part_orient ) {
594 const std::string& shape = layer_def.shape;
595 // In PADS, sizeA is height (Y) and sizeB is width (X), opposite of KiCad convention
596 VECTOR2I size( std::max( decalScaler( layer_def.sizeB ), m_minObjectSize ),
597 std::max( decalScaler( layer_def.sizeA ), m_minObjectSize ) );
598
599 if( shape == "R" || shape == "C" || shape == "A" || shape == "RT" )
600 {
601 pad->SetShape( kicad_layer, PAD_SHAPE::CIRCLE );
602 pad->SetSize( kicad_layer, VECTOR2I( size.x, size.x ) );
603 }
604 else if( shape == "S" || shape == "ST" )
605 {
606 pad->SetShape( kicad_layer, PAD_SHAPE::RECTANGLE );
607 pad->SetSize( kicad_layer, VECTOR2I( size.x, size.x ) );
608 }
609 else if( shape == "O" || shape == "OT" )
610 {
611 pad->SetShape( kicad_layer, PAD_SHAPE::OVAL );
612 pad->SetSize( kicad_layer, size );
613 }
614 else if( shape == "RF" )
615 {
616 pad->SetShape( kicad_layer, PAD_SHAPE::RECTANGLE );
617 pad->SetSize( kicad_layer, size );
618
619 if( layer_def.finger_offset != 0 )
620 {
621 int offset = decalScaler( layer_def.finger_offset );
622 VECTOR2I pad_offset( offset, 0 );
623 RotatePoint( pad_offset, EDA_ANGLE( layer_def.rotation, DEGREES_T ) );
624 pad->SetOffset( kicad_layer, pad_offset );
625 }
626 }
627 else if( shape == "OF" )
628 {
629 pad->SetShape( kicad_layer, PAD_SHAPE::OVAL );
630 pad->SetSize( kicad_layer, size );
631
632 if( layer_def.finger_offset != 0 )
633 {
634 int offset = decalScaler( layer_def.finger_offset );
635 VECTOR2I pad_offset( offset, 0 );
636 RotatePoint( pad_offset, EDA_ANGLE( layer_def.rotation, DEGREES_T ) );
637 pad->SetOffset( kicad_layer, pad_offset );
638 }
639 }
640 else if( shape == "RC" || shape == "OC" )
641 {
642 pad->SetShape( kicad_layer, PAD_SHAPE::ROUNDRECT );
643 pad->SetSize( kicad_layer, size );
644
645 if( layer_def.corner_radius > 0 && size.x > 0 )
646 {
647 double min_dim = std::min( size.x, size.y );
648 double radius = decalScaler( layer_def.corner_radius );
649 double ratio = ( min_dim > 0 ) ? ( radius / min_dim ) : 0.25;
650 ratio = std::min( ratio, 0.5 );
651 pad->SetRoundRectRadiusRatio( kicad_layer, ratio );
652 }
653 else
654 {
655 pad->SetRoundRectRadiusRatio( kicad_layer, 0.25 );
656 }
657
658 if( layer_def.finger_offset != 0 )
659 {
660 int offset = decalScaler( layer_def.finger_offset );
661 VECTOR2I pad_offset( offset, 0 );
662 RotatePoint( pad_offset, EDA_ANGLE( layer_def.rotation, DEGREES_T ) );
663 pad->SetOffset( kicad_layer, pad_offset );
664 }
665 }
666 else
667 {
668 pad->SetShape( kicad_layer, PAD_SHAPE::CIRCLE );
669 pad->SetSize( kicad_layer, VECTOR2I( size.x, size.x ) );
670 }
671
672 pad->SetOrientation( part_orient + EDA_ANGLE( layer_def.rotation, DEGREES_T ) );
673 };
674
675 EDA_ANGLE part_orient( pads_part.rotation, DEGREES_T );
676
677 for( size_t term_idx = 0; term_idx < decal.terminals.size(); ++term_idx )
678 {
679 const auto& term = decal.terminals[term_idx];
680 PAD* pad = new PAD( footprint );
681 footprint->Add( pad );
682
683 pad->SetNumber( term.name );
684
685 VECTOR2I pad_pos( decalScaler( term.x ), -decalScaler( term.y ) );
686 RotatePoint( pad_pos, part_orient );
687 pad->SetPosition( footprint->GetPosition() + pad_pos );
688
689 // Look up pad stack by terminal index (1-based). PAD 0 is the default for
690 // terminals without explicit definitions. PAD N is for terminal index N.
691 int pin_num = static_cast<int>( term_idx + 1 );
692
693 auto stack_it = decal.pad_stacks.find( pin_num );
694
695 if( stack_it == decal.pad_stacks.end() )
696 stack_it = decal.pad_stacks.find( 0 );
697
698 if( stack_it != decal.pad_stacks.end() && !stack_it->second.empty() )
699 {
700 const std::vector<PADS_IO::PAD_STACK_LAYER>& stack = stack_it->second;
701
702 double drill = 0.0;
703 bool plated = true;
704 double slot_length = 0.0;
705 double slot_orientation = 0.0;
706 double pad_rotation = 0.0;
707
708 for( const auto& layer_def : stack )
709 {
710 if( layer_def.drill > 0 )
711 {
712 drill = layer_def.drill;
713 plated = layer_def.plated;
714 slot_length = layer_def.slot_length;
715 slot_orientation = layer_def.slot_orientation;
716 pad_rotation = layer_def.rotation;
717 break;
718 }
719 }
720
721 LSET layer_set;
722
723 auto mapPadsLayer = [&]( int pads_layer ) -> PCB_LAYER_ID {
724 if( pads_layer == -2 || pads_layer == 1 )
725 return F_Cu;
726 else if( pads_layer == -1
727 || pads_layer == m_parser->GetParameters().layer_count )
728 return B_Cu;
729 else if( pads_layer > 1
730 && pads_layer < m_parser->GetParameters().layer_count )
731 {
732 int inner_idx = pads_layer - 2;
733
734 if( inner_idx >= 0 && inner_idx < 30 )
735 return static_cast<PCB_LAYER_ID>( In1_Cu + inner_idx * 2 );
736 }
737
738 return UNDEFINED_LAYER;
739 };
740
741 bool has_explicit_layers = false;
742
743 for( const auto& layer_def : stack )
744 {
745 if( layer_def.layer == -2 || layer_def.layer == -1
746 || layer_def.layer == 1
747 || layer_def.layer == m_parser->GetParameters().layer_count )
748 {
749 has_explicit_layers = true;
750 break;
751 }
752 }
753
754 for( const auto& layer_def : stack )
755 {
756 if( layer_def.layer == 0 )
757 {
758 if( !has_explicit_layers )
759 {
760 layer_set = ( drill > 0 ) ? LSET::AllCuMask()
761 : LSET( { F_Cu, B_Cu } );
762 convertPadShape( layer_def, pad, F_Cu, part_orient );
763
764 if( drill == 0 )
765 {
766 pad->SetShape( B_Cu, pad->GetShape( F_Cu ) );
767 pad->SetSize( B_Cu, pad->GetSize( F_Cu ) );
768 }
769 }
770
771 continue;
772 }
773
774 // Skip layers with size 0 - "no pad on this layer" in PADS.
775 // We must not call SetSize with 0 since in PADSTACK NORMAL mode all
776 // layers write to the same ALL_LAYERS slot, overwriting valid sizes.
777 if( layer_def.sizeA <= 0 )
778 continue;
779
780 // RT/ST are thermal relief spoke patterns for plane layers.
781 // KiCad computes thermal reliefs from zone settings, so skip
782 // these to avoid overwriting the actual pad shape.
783 if( layer_def.shape == "RT" || layer_def.shape == "ST"
784 || layer_def.shape == "RA" || layer_def.shape == "SA" )
785 {
786 continue;
787 }
788
789 PCB_LAYER_ID kicad_layer = mapPadsLayer( layer_def.layer );
790
791 if( kicad_layer != UNDEFINED_LAYER )
792 {
793 layer_set.set( kicad_layer );
794 convertPadShape( layer_def, pad, kicad_layer, part_orient );
795 }
796 }
797
798 if( layer_set.none() )
799 {
800 layer_set.set( F_Cu );
801 convertPadShape( stack[0], pad, F_Cu, part_orient );
802 }
803
804 if( slot_length > 0 && slot_length != drill )
805 {
806 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
807
808 int drillMinor = decalScaler( drill );
809 int drillMajor = decalScaler( slot_length );
810
811 // Slot orientation is in the decal's local frame.
812 // Subtract the pad shape rotation to get the slot
813 // angle in the pad's own local frame.
814 double relAngle = slot_orientation - pad_rotation;
815
816 relAngle = fmod( relAngle, 360.0 );
817
818 if( relAngle < 0 )
819 relAngle += 360.0;
820
821 bool vertical = ( relAngle > 45.0 && relAngle < 135.0 )
822 || ( relAngle > 225.0 && relAngle < 315.0 );
823
824 if( vertical )
825 pad->SetDrillSize( VECTOR2I( drillMinor, drillMajor ) );
826 else
827 pad->SetDrillSize( VECTOR2I( drillMajor, drillMinor ) );
828 }
829 else
830 {
831 pad->SetDrillSize( VECTOR2I( decalScaler( drill ),
832 decalScaler( drill ) ) );
833 }
834
835 if( drill == 0 )
836 {
837 pad->SetAttribute( PAD_ATTRIB::SMD );
838 }
839 else
840 {
841 if( plated )
842 pad->SetAttribute( PAD_ATTRIB::PTH );
843 else
844 pad->SetAttribute( PAD_ATTRIB::NPTH );
845
846 layer_set = LSET::AllCuMask();
847 }
848
849 pad->SetLayerSet( layer_set );
850 }
851 else
852 {
853 int fallbackSize = std::max( decalScaler( 1.5 ), m_minObjectSize );
854 pad->SetSize( F_Cu, VECTOR2I( fallbackSize, fallbackSize ) );
855 pad->SetShape( F_Cu, PAD_SHAPE::CIRCLE );
856 pad->SetAttribute( PAD_ATTRIB::PTH );
857 pad->SetLayerSet( LSET::AllCuMask() );
858 }
859
860 std::string pinKey = pads_part.name + "." + term.name;
861 auto netIt = m_pinToNetMap.find( pinKey );
862
863 if( netIt != m_pinToNetMap.end() )
864 {
865 NETINFO_ITEM* net =
866 m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( netIt->second ) );
867
868 if( net )
869 pad->SetNet( net );
870 }
871 }
872
873 for( const auto& item : decal.items )
874 {
875 if( item.points.empty() )
876 continue;
877
878 // Decal graphics layers work differently from routing layers in PADS.
879 // Layer 0 and layer 1 are typically footprint outlines, not copper.
880 PCB_LAYER_ID shape_layer = F_SilkS;
881
882 if( item.layer == 0 )
883 {
884 shape_layer = F_SilkS;
885 }
886 else
887 {
888 PCB_LAYER_ID mapped_layer = getMappedLayer( item.layer );
889
890 if( IsCopperLayer( mapped_layer ) )
891 {
892 if( mapped_layer == B_Cu )
893 shape_layer = B_SilkS;
894 else
895 shape_layer = F_SilkS;
896 }
897 else
898 {
899 shape_layer = mapped_layer;
900 }
901 }
902
903 if( shape_layer == UNDEFINED_LAYER )
904 {
905 if( m_reporter )
906 {
907 m_reporter->Report( wxString::Format(
908 _( "Skipping decal item on unmapped layer %d" ), item.layer ),
910 }
911 continue;
912 }
913
914 bool is_circle = ( item.type == "CIRCLE" );
915 bool is_closed = ( item.type == "CLOSED" || is_circle );
916
917 // Per PADS spec: CIRCLE pieces have 2 corners representing ends of
918 // horizontal diameter.
919 if( is_circle && item.points.size() >= 2 )
920 {
921 PCB_SHAPE* shape = new PCB_SHAPE( footprint, SHAPE_T::CIRCLE );
922 shape->SetLayer( shape_layer );
923
924 double x1 = item.points[0].x;
925 double y1 = item.points[0].y;
926 double x2 = item.points[1].x;
927 double y2 = item.points[1].y;
928
929 double cx = ( x1 + x2 ) / 2.0;
930 double cy = ( y1 + y2 ) / 2.0;
931
932 double radius = std::sqrt( ( x2 - x1 ) * ( x2 - x1 )
933 + ( y2 - y1 ) * ( y2 - y1 ) )
934 / 2.0;
935
936 int scaledRadius = std::max( decalScaler( radius ), m_minObjectSize );
937 VECTOR2I center( decalScaler( cx ), -decalScaler( cy ) );
938 VECTOR2I pt_on_circle( center.x + scaledRadius, center.y );
939
940 RotatePoint( center, part_orient );
941 RotatePoint( pt_on_circle, part_orient );
942
943 VECTOR2I fp_pos = footprint->GetPosition();
944 shape->SetCenter( fp_pos + center );
945 shape->SetEnd( fp_pos + pt_on_circle );
946 shape->SetStroke(
947 STROKE_PARAMS( decalScaler( item.width ), LINE_STYLE::SOLID ) );
948
949 footprint->Add( shape );
950
951 continue;
952 }
953
954 if( item.points.size() < 2 )
955 continue;
956
957 for( size_t i = 0; i < item.points.size() - 1; ++i )
958 {
959 const PADS_IO::ARC_POINT& p1 = item.points[i];
960 const PADS_IO::ARC_POINT& p2 = item.points[i + 1];
961
962 PCB_SHAPE* shape = new PCB_SHAPE( footprint );
963 shape->SetLayer( shape_layer );
964 shape->SetStroke(
965 STROKE_PARAMS( decalScaler( item.width ), LINE_STYLE::SOLID ) );
966
967 if( p2.is_arc )
968 {
969 shape->SetShape( SHAPE_T::ARC );
970 VECTOR2I center( decalScaler( p2.arc.cx ), -decalScaler( p2.arc.cy ) );
971 VECTOR2I start( decalScaler( p1.x ), -decalScaler( p1.y ) );
972 VECTOR2I end( decalScaler( p2.x ), -decalScaler( p2.y ) );
973
974 // Y-axis flip reverses arc winding; swap endpoints for CCW arcs
975 if( p2.arc.delta_angle > 0 )
976 std::swap( start, end );
977
978 RotatePoint( center, part_orient );
979 RotatePoint( start, part_orient );
980 RotatePoint( end, part_orient );
981
982 VECTOR2I fp_pos = footprint->GetPosition();
983 shape->SetCenter( fp_pos + center );
984 shape->SetStart( fp_pos + start );
985 shape->SetEnd( fp_pos + end );
986 }
987 else
988 {
989 shape->SetShape( SHAPE_T::SEGMENT );
990 VECTOR2I start( decalScaler( p1.x ), -decalScaler( p1.y ) );
991 VECTOR2I end( decalScaler( p2.x ), -decalScaler( p2.y ) );
992
993 RotatePoint( start, part_orient );
994 RotatePoint( end, part_orient );
995
996 VECTOR2I fp_pos = footprint->GetPosition();
997 shape->SetStart( fp_pos + start );
998 shape->SetEnd( fp_pos + end );
999 }
1000
1001 footprint->Add( shape );
1002 }
1003
1004 if( is_closed && item.points.size() > 2 )
1005 {
1006 const PADS_IO::ARC_POINT& pLast = item.points.back();
1007 const PADS_IO::ARC_POINT& pFirst = item.points.front();
1008
1009 PCB_SHAPE* shape = new PCB_SHAPE( footprint );
1010 shape->SetLayer( shape_layer );
1011 shape->SetStroke(
1012 STROKE_PARAMS( decalScaler( item.width ), LINE_STYLE::SOLID ) );
1013
1014 if( pFirst.is_arc )
1015 {
1016 shape->SetShape( SHAPE_T::ARC );
1017 VECTOR2I center( decalScaler( pFirst.arc.cx ),
1018 -decalScaler( pFirst.arc.cy ) );
1019 VECTOR2I start( decalScaler( pLast.x ), -decalScaler( pLast.y ) );
1020 VECTOR2I end( decalScaler( pFirst.x ), -decalScaler( pFirst.y ) );
1021
1022 if( pFirst.arc.delta_angle > 0 )
1023 std::swap( start, end );
1024
1025 RotatePoint( center, part_orient );
1026 RotatePoint( start, part_orient );
1027 RotatePoint( end, part_orient );
1028
1029 VECTOR2I fp_pos = footprint->GetPosition();
1030 shape->SetCenter( fp_pos + center );
1031 shape->SetStart( fp_pos + start );
1032 shape->SetEnd( fp_pos + end );
1033 }
1034 else
1035 {
1036 shape->SetShape( SHAPE_T::SEGMENT );
1037 VECTOR2I start( decalScaler( pLast.x ), -decalScaler( pLast.y ) );
1038 VECTOR2I end( decalScaler( pFirst.x ), -decalScaler( pFirst.y ) );
1039
1040 RotatePoint( start, part_orient );
1041 RotatePoint( end, part_orient );
1042
1043 VECTOR2I fp_pos = footprint->GetPosition();
1044 shape->SetStart( fp_pos + start );
1045 shape->SetEnd( fp_pos + end );
1046 }
1047
1048 footprint->Add( shape );
1049 }
1050 }
1051 }
1052
1053 if( pads_part.bottom_layer )
1054 {
1055 footprint->Flip( footprint->GetPosition(), FLIP_DIRECTION::LEFT_RIGHT );
1056 }
1057 }
1058}
1059
1060
1062{
1063 const auto& reuse_blocks = m_parser->GetReuseBlocks();
1064
1065 if( reuse_blocks.empty() )
1066 return;
1067
1068 std::map<std::string, PCB_GROUP*> blockGroups;
1069
1070 for( const auto& [blockName, block] : reuse_blocks )
1071 {
1072 if( !block.instances.empty() || !block.part_names.empty() )
1073 {
1075 group->SetName( wxString::FromUTF8( blockName ) );
1076 m_loadBoard->Add( group );
1077 blockGroups[blockName] = group;
1078 }
1079 }
1080
1081 for( FOOTPRINT* fp : m_loadBoard->Footprints() )
1082 {
1083 for( PCB_FIELD* field : fp->GetFields() )
1084 {
1085 if( field->GetName() == wxT( "PADS_Reuse_Block" ) )
1086 {
1087 std::string blockName = field->GetText().ToStdString();
1088 auto groupIt = blockGroups.find( blockName );
1089
1090 if( groupIt != blockGroups.end() )
1091 {
1092 groupIt->second->AddItem( fp );
1093 }
1094
1095 break;
1096 }
1097 }
1098 }
1099}
1100
1101
1103{
1104 const auto& test_points = m_parser->GetTestPoints();
1105
1106 for( const auto& tp : test_points )
1107 {
1108 FOOTPRINT* footprint = new FOOTPRINT( m_loadBoard );
1109
1110 wxString refDes = wxString::Format( wxT( "TP%d" ), m_testPointIndex++ );
1111 footprint->SetReference( refDes );
1112 footprint->SetValue( wxString::FromUTF8( tp.symbol_name ) );
1113
1114 VECTOR2I pos( scaleCoord( tp.x, true ), scaleCoord( tp.y, false ) );
1115 footprint->SetPosition( pos );
1116
1117 PCB_LAYER_ID layer = F_Cu;
1118
1119 if( tp.side == 2 )
1120 layer = B_Cu;
1121
1122 footprint->SetLayer( ( layer == B_Cu ) ? B_Cu : F_Cu );
1123
1124 PAD* pad = new PAD( footprint );
1125 pad->SetNumber( wxT( "1" ) );
1126 pad->SetPosition( pos );
1128 int tpSize = std::max( scaleSize( 50.0 ), m_minObjectSize );
1129 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( tpSize, tpSize ) );
1130 pad->SetAttribute( PAD_ATTRIB::SMD );
1131 pad->SetLayerSet( layer == B_Cu ? LSET( { B_Cu } ) : LSET( { F_Cu } ) );
1132
1133 if( !tp.net_name.empty() )
1134 {
1135 NETINFO_ITEM* net = m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( tp.net_name ) );
1136
1137 if( net )
1138 pad->SetNet( net );
1139 }
1140
1141 footprint->Add( pad );
1142
1143 footprint->SetBoardOnly( true );
1144
1145 PCB_FIELD* tpField = new PCB_FIELD( footprint, FIELD_T::USER, wxT( "Test_Point" ) );
1146 tpField->SetLayer( Cmts_User );
1147 tpField->SetVisible( false );
1148 tpField->SetText( wxString::FromUTF8( tp.type ) );
1149 footprint->Add( tpField );
1150
1151 m_loadBoard->Add( footprint );
1152 }
1153}
1154
1155
1157{
1158 const auto& texts = m_parser->GetTexts();
1159
1160 for( const auto& pads_text : texts )
1161 {
1162 PCB_LAYER_ID textLayer = getMappedLayer( pads_text.layer );
1163
1164 if( textLayer == UNDEFINED_LAYER )
1165 {
1166 if( m_reporter )
1167 {
1168 m_reporter->Report( wxString::Format(
1169 _( "Text on unmapped layer %d assigned to Comments layer" ),
1170 pads_text.layer ), RPT_SEVERITY_WARNING );
1171 }
1172 textLayer = Cmts_User;
1173 }
1174
1176 text->SetText( pads_text.content );
1177
1178 // PADS text cell height includes internal leading and descender space.
1179 // Scale factors calibrated to match PADS rendered character dimensions.
1180 int scaledSize = scaleSize( pads_text.height );
1181 int charHeight =
1182 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextHeightScale );
1183 int charWidth =
1184 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextWidthScale );
1185 text->SetTextSize( VECTOR2I( charWidth, charHeight ) );
1186
1187 if( pads_text.width > 0 )
1188 text->SetTextThickness( scaleSize( pads_text.width ) );
1189
1190 EDA_ANGLE textAngle( pads_text.rotation, DEGREES_T );
1191 text->SetTextAngle( textAngle );
1192
1193 // PADS text anchor differs from KiCad by a small offset along the
1194 // reading direction. Shift left (toward text start) to compensate.
1195 VECTOR2I pos( scaleCoord( pads_text.location.x, true ),
1196 scaleCoord( pads_text.location.y, false ) );
1197 VECTOR2I textShift( -ADVANCED_CFG::GetCfg().m_PadsTextAnchorOffsetNm, 0 );
1198 RotatePoint( textShift, textAngle );
1199 text->SetPosition( pos + textShift );
1200
1201 if( pads_text.hjust == "LEFT" )
1202 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1203 else if( pads_text.hjust == "RIGHT" )
1204 text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
1205 else
1206 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
1207
1208 if( pads_text.vjust == "UP" )
1209 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
1210 else if( pads_text.vjust == "DOWN" )
1211 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
1212 else
1213 text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
1214
1215 text->SetKeepUpright( false );
1216 text->SetLayer( textLayer );
1217 m_loadBoard->Add( text );
1218 }
1219}
1220
1221
1223{
1224 const auto& routes = m_parser->GetRoutes();
1225 std::set<std::pair<int, int>> placedThroughVias;
1226
1227 for( const auto& route : routes )
1228 {
1229 NETINFO_ITEM* net = m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( route.net_name ) );
1230
1231 if( !net )
1232 continue;
1233
1234 for( const auto& track_def : route.tracks )
1235 {
1236 if( track_def.points.size() < 2 )
1237 continue;
1238
1239 PCB_LAYER_ID track_layer = getMappedLayer( track_def.layer );
1240
1241 if( !IsCopperLayer( track_layer ) )
1242 {
1243 if( m_reporter )
1244 {
1245 m_reporter->Report( wxString::Format(
1246 _( "Skipping track on non-copper layer %d" ), track_def.layer ),
1248 }
1249 continue;
1250 }
1251
1252 int track_width = std::max( scaleSize( track_def.width ), m_minObjectSize );
1253
1254 for( size_t i = 0; i < track_def.points.size() - 1; ++i )
1255 {
1256 const PADS_IO::ARC_POINT& p1 = track_def.points[i];
1257 const PADS_IO::ARC_POINT& p2 = track_def.points[i + 1];
1258
1259 VECTOR2I start( scaleCoord( p1.x, true ), scaleCoord( p1.y, false ) );
1260 VECTOR2I end( scaleCoord( p2.x, true ), scaleCoord( p2.y, false ) );
1261
1262 // Skip near-zero-length segments (can occur at via points with width changes).
1263 // Tolerance of 1000nm accounts for floating point precision in coordinate
1264 // transformation.
1265 if( ( start - end ).EuclideanNorm() < 1000 )
1266 continue;
1267
1268 if( p2.is_arc )
1269 {
1270 SHAPE_ARC shapeArc = makeMidpointArc( p1, p2, track_width );
1271
1272 PCB_ARC* arc = new PCB_ARC( m_loadBoard, &shapeArc );
1273 arc->SetNet( net );
1274 arc->SetWidth( track_width );
1275 arc->SetLayer( track_layer );
1276 m_loadBoard->Add( arc );
1277 }
1278 else
1279 {
1280 PCB_TRACK* track = new PCB_TRACK( m_loadBoard );
1281 track->SetNet( net );
1282 track->SetWidth( track_width );
1283 track->SetLayer( track_layer );
1284 track->SetStart( start );
1285 track->SetEnd( end );
1286 m_loadBoard->Add( track );
1287 }
1288 }
1289 }
1290
1291 for( const auto& via_def : route.vias )
1292 {
1293 VECTOR2I pos( scaleCoord( via_def.location.x, true ),
1294 scaleCoord( via_def.location.y, false ) );
1295
1296 VIATYPE viaType = VIATYPE::THROUGH;
1297 auto it = m_parser->GetViaDefs().find( via_def.name );
1298
1299 if( it != m_parser->GetViaDefs().end() )
1300 {
1301 switch( it->second.via_type )
1302 {
1303 case PADS_IO::VIA_TYPE::THROUGH: viaType = VIATYPE::THROUGH; break;
1304 case PADS_IO::VIA_TYPE::BLIND: viaType = VIATYPE::BLIND; break;
1305 case PADS_IO::VIA_TYPE::BURIED: viaType = VIATYPE::BURIED; break;
1306 case PADS_IO::VIA_TYPE::MICROVIA: viaType = VIATYPE::MICROVIA; break;
1307 }
1308 }
1309
1310 // Through-hole vias shared across multiple SIGNAL blocks for the same net
1311 // produce duplicates. Skip if we already placed one at this position.
1312 if( viaType == VIATYPE::THROUGH )
1313 {
1314 auto key = std::make_pair( pos.x, pos.y );
1315
1316 if( placedThroughVias.count( key ) )
1317 continue;
1318
1319 placedThroughVias.insert( key );
1320 }
1321
1322 PCB_VIA* via = new PCB_VIA( m_loadBoard );
1323 via->SetNet( net );
1324 via->SetPosition( pos );
1325
1326 if( it != m_parser->GetViaDefs().end() )
1327 {
1328 const PADS_IO::VIA_DEF& def = it->second;
1329
1330 via->SetWidth( std::max( scaleSize( def.size ), m_minObjectSize ) );
1331 via->SetDrill( std::max( scaleSize( def.drill ), m_minObjectSize ) );
1332
1333 PCB_LAYER_ID startLayer = ( def.start_layer > 0 )
1336 PCB_LAYER_ID endLayer = ( def.end_layer > 0 )
1337 ? getMappedLayer( def.end_layer )
1339
1340 if( startLayer != UNDEFINED_LAYER && endLayer != UNDEFINED_LAYER )
1341 {
1342 via->SetLayerPair( startLayer, endLayer );
1343 via->SetViaType( viaType );
1344 }
1345 else
1346 {
1347 via->SetLayerPair( F_Cu, B_Cu );
1348 via->SetViaType( VIATYPE::THROUGH );
1349 }
1350
1351 if( !def.has_mask_front )
1352 via->SetFrontTentingMode( TENTING_MODE::TENTED );
1353
1354 if( !def.has_mask_back )
1355 via->SetBackTentingMode( TENTING_MODE::TENTED );
1356
1357 }
1358 else
1359 {
1360 via->SetWidth( std::max( scaleSize( 20.0 ), m_minObjectSize ) );
1361 via->SetDrill( std::max( scaleSize( 10.0 ), m_minObjectSize ) );
1362 via->SetLayerPair( F_Cu, B_Cu );
1363 via->SetViaType( VIATYPE::THROUGH );
1364 }
1365
1366 m_loadBoard->Add( via );
1367 }
1368 }
1369}
1370
1371
1373{
1374 const auto& copperShapes = m_parser->GetCopperShapes();
1375
1376 // Check if a COPPER_SHAPE is a non-copper straight-line segment suitable for
1377 // rectangle grouping (2 outline points, no arcs, not filled, not cutout).
1378 auto isRectCandidate = []( const PADS_IO::COPPER_SHAPE& cs )
1379 {
1380 return cs.outline.size() == 2 && !cs.outline[1].is_arc
1381 && !cs.filled && !cs.is_cutout;
1382 };
1383
1384 // Check if 4 consecutive entries at idx form a closed axis-aligned rectangle.
1385 // Each entry must have the same net_name and layer, and consecutive segment
1386 // endpoints must connect to form a closed cycle with only horizontal/vertical edges.
1387 auto tryFormRectangle = [&]( size_t idx, VECTOR2I& minCorner, VECTOR2I& maxCorner ) -> bool
1388 {
1389 if( idx + 3 >= copperShapes.size() )
1390 return false;
1391
1392 const auto& c0 = copperShapes[idx];
1393 const auto& c1 = copperShapes[idx + 1];
1394 const auto& c2 = copperShapes[idx + 2];
1395 const auto& c3 = copperShapes[idx + 3];
1396
1397 if( !isRectCandidate( c0 ) || !isRectCandidate( c1 )
1398 || !isRectCandidate( c2 ) || !isRectCandidate( c3 ) )
1399 {
1400 return false;
1401 }
1402
1403 if( c1.net_name != c0.net_name || c2.net_name != c0.net_name
1404 || c3.net_name != c0.net_name )
1405 {
1406 return false;
1407 }
1408
1409 if( c1.layer != c0.layer || c2.layer != c0.layer || c3.layer != c0.layer )
1410 return false;
1411
1412 // Get the 4 segment start/end pairs in scaled coordinates
1413 VECTOR2I pts[8];
1414 const PADS_IO::COPPER_SHAPE* segs[4] = { &c0, &c1, &c2, &c3 };
1415
1416 for( int i = 0; i < 4; ++i )
1417 {
1418 pts[i * 2] = VECTOR2I( scaleCoord( segs[i]->outline[0].x, true ),
1419 scaleCoord( segs[i]->outline[0].y, false ) );
1420 pts[i * 2 + 1] = VECTOR2I( scaleCoord( segs[i]->outline[1].x, true ),
1421 scaleCoord( segs[i]->outline[1].y, false ) );
1422 }
1423
1424 // Each segment must be axis-aligned
1425 for( int i = 0; i < 4; ++i )
1426 {
1427 VECTOR2I s = pts[i * 2];
1428 VECTOR2I e = pts[i * 2 + 1];
1429
1430 if( s.x != e.x && s.y != e.y )
1431 return false;
1432 }
1433
1434 // Consecutive segments must connect (end of N == start of N+1)
1435 for( int i = 0; i < 3; ++i )
1436 {
1437 if( pts[i * 2 + 1] != pts[( i + 1 ) * 2] )
1438 return false;
1439 }
1440
1441 // Cycle must close (end of last == start of first)
1442 if( pts[7] != pts[0] )
1443 return false;
1444
1445 // Compute bounding box from the 4 corner points
1446 int minX = pts[0].x, maxX = pts[0].x;
1447 int minY = pts[0].y, maxY = pts[0].y;
1448
1449 for( int i = 0; i < 8; ++i )
1450 {
1451 minX = std::min( minX, pts[i].x );
1452 maxX = std::max( maxX, pts[i].x );
1453 minY = std::min( minY, pts[i].y );
1454 maxY = std::max( maxY, pts[i].y );
1455 }
1456
1457 minCorner = VECTOR2I( minX, minY );
1458 maxCorner = VECTOR2I( maxX, maxY );
1459 return true;
1460 };
1461
1462 for( size_t idx = 0; idx < copperShapes.size(); ++idx )
1463 {
1464 const auto& copper = copperShapes[idx];
1465
1466 if( copper.outline.size() < 2 )
1467 continue;
1468
1469 if( copper.is_cutout )
1470 continue;
1471
1472 PCB_LAYER_ID layer = getMappedLayer( copper.layer );
1473
1474 if( layer == UNDEFINED_LAYER )
1475 {
1476 if( m_reporter )
1477 {
1478 m_reporter->Report( wxString::Format(
1479 _( "COPPER item on unmapped layer %d defaulting to F.Cu" ),
1480 copper.layer ),
1482 }
1483
1484 layer = F_Cu;
1485 }
1486
1487 int width = std::max( scaleSize( copper.width ), m_minObjectSize );
1488
1489 if( !IsCopperLayer( layer ) )
1490 {
1491 // Check for 4 consecutive entries forming an axis-aligned rectangle
1492 VECTOR2I minCorner, maxCorner;
1493
1494 if( tryFormRectangle( idx, minCorner, maxCorner ) )
1495 {
1496 PCB_SHAPE* rect = new PCB_SHAPE( m_loadBoard );
1498 rect->SetStart( minCorner );
1499 rect->SetEnd( maxCorner );
1500 rect->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
1501 rect->SetLayer( layer );
1502 m_loadBoard->Add( rect );
1503
1504 idx += 3;
1505 continue;
1506 }
1507
1508 // EasyEDA PADS exports place footprint silkscreen outlines in the *LINES*
1509 // section as COPPER type on the silkscreen layer. Import these as board
1510 // graphics on their actual layer rather than forcing them onto copper.
1511 for( size_t i = 0; i < copper.outline.size() - 1; ++i )
1512 {
1513 const auto& p1 = copper.outline[i];
1514 const auto& p2 = copper.outline[i + 1];
1515
1516 VECTOR2I start( scaleCoord( p1.x, true ), scaleCoord( p1.y, false ) );
1517 VECTOR2I end( scaleCoord( p2.x, true ), scaleCoord( p2.y, false ) );
1518
1519 if( ( start - end ).EuclideanNorm() < 1000 )
1520 continue;
1521
1522 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
1523
1524 if( p2.is_arc )
1525 {
1526 setPcbShapeArc( shape, p1, p2 );
1527 }
1528 else
1529 {
1530 shape->SetShape( SHAPE_T::SEGMENT );
1531 shape->SetStart( start );
1532 shape->SetEnd( end );
1533 }
1534
1535 shape->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
1536 shape->SetLayer( layer );
1537 m_loadBoard->Add( shape );
1538 }
1539
1540 continue;
1541 }
1542
1543 NETINFO_ITEM* net = nullptr;
1544
1545 if( !copper.net_name.empty() )
1546 net = m_loadBoard->FindNet( PADS_COMMON::ConvertInvertedNetName( copper.net_name ) );
1547
1548 if( copper.filled )
1549 {
1550 if( copper.outline.size() < 3 )
1551 continue;
1552
1553 ZONE* zone = new ZONE( m_loadBoard );
1554 zone->SetLayer( layer );
1555 zone->SetIsRuleArea( false );
1556
1557 if( net )
1558 zone->SetNet( net );
1559
1560 SHAPE_LINE_CHAIN outline;
1561 appendArcPoints( outline, copper.outline );
1562 outline.SetClosed( true );
1563 zone->Outline()->AddOutline( outline );
1564 m_loadBoard->Add( zone );
1565 }
1566 else
1567 {
1568 for( size_t i = 0; i < copper.outline.size() - 1; ++i )
1569 {
1570 const auto& p1 = copper.outline[i];
1571 const auto& p2 = copper.outline[i + 1];
1572
1573 VECTOR2I start( scaleCoord( p1.x, true ), scaleCoord( p1.y, false ) );
1574 VECTOR2I end( scaleCoord( p2.x, true ), scaleCoord( p2.y, false ) );
1575
1576 if( ( start - end ).EuclideanNorm() < 1000 )
1577 continue;
1578
1579 if( p2.is_arc )
1580 {
1581 SHAPE_ARC shapeArc = makeMidpointArc( p1, p2, width );
1582
1583 PCB_ARC* arc = new PCB_ARC( m_loadBoard, &shapeArc );
1584
1585 if( net )
1586 arc->SetNet( net );
1587
1588 arc->SetWidth( width );
1589 arc->SetLayer( layer );
1590 m_loadBoard->Add( arc );
1591 }
1592 else
1593 {
1594 PCB_TRACK* track = new PCB_TRACK( m_loadBoard );
1595
1596 if( net )
1597 track->SetNet( net );
1598
1599 track->SetWidth( width );
1600 track->SetLayer( layer );
1601 track->SetStart( start );
1602 track->SetEnd( end );
1603 m_loadBoard->Add( track );
1604 }
1605 }
1606 }
1607 }
1608}
1609
1610
1612{
1613 const auto& clusters = m_parser->GetClusters();
1614
1615 if( clusters.empty() )
1616 return;
1617
1618 std::map<std::string, const PADS_IO::CLUSTER*> netToClusterMap;
1619
1620 for( const auto& cluster : clusters )
1621 {
1622 for( const std::string& netName : cluster.net_names )
1623 {
1624 std::string converted = PADS_COMMON::ConvertInvertedNetName( netName ).ToStdString();
1625 netToClusterMap[converted] = &cluster;
1626 }
1627 }
1628
1629 std::map<int, PCB_GROUP*> clusterGroups;
1630
1631 for( const auto& cluster : clusters )
1632 {
1634 group->SetName( wxString::FromUTF8( cluster.name ) );
1635 m_loadBoard->Add( group );
1636 clusterGroups[cluster.id] = group;
1637 }
1638
1639 for( PCB_TRACK* track : m_loadBoard->Tracks() )
1640 {
1641 NETINFO_ITEM* net = track->GetNet();
1642
1643 if( net )
1644 {
1645 std::string netName = net->GetNetname().ToStdString();
1646 auto clusterIt = netToClusterMap.find( netName );
1647
1648 if( clusterIt != netToClusterMap.end() )
1649 {
1650 int clusterId = clusterIt->second->id;
1651 auto groupIt = clusterGroups.find( clusterId );
1652
1653 if( groupIt != clusterGroups.end() )
1654 {
1655 groupIt->second->AddItem( track );
1656 }
1657 }
1658 }
1659 }
1660}
1661
1662
1664{
1665 const auto& pours = m_parser->GetPours();
1666 const auto& params = m_parser->GetParameters();
1667
1668 // Returns true if the points can produce a valid polygon (at least 3 vertices
1669 // for a regular polygon, or a single full-circle point).
1670 auto isValidPoly = []( const std::vector<PADS_IO::ARC_POINT>& pts )
1671 {
1672 if( pts.size() >= 3 )
1673 return true;
1674
1675 if( pts.size() == 1 && pts[0].is_arc
1676 && std::abs( pts[0].arc.delta_angle ) >= 359.0 )
1677 {
1678 return true;
1679 }
1680
1681 return false;
1682 };
1683
1684 // PADS uses lower numbers = higher priority (priority 1 fills on top),
1685 // while KiCad uses higher numbers = higher priority.
1686 int maxPriority = 0;
1687
1688 for( const auto& pour_def : pours )
1689 {
1690 if( pour_def.priority > maxPriority )
1691 maxPriority = pour_def.priority;
1692 }
1693
1694 // Map from pour name to created zone for linking HATOUT/VOIDOUT later
1695 std::map<std::string, ZONE*> pourZoneMap;
1696
1697 // Map from HATOUT name to parent POUROUT name for VOIDOUT chain resolution
1698 std::map<std::string, std::string> hatoutToParent;
1699
1700 // First pass: create zones from POUROUT records and build lookup maps
1701 for( const auto& pour_def : pours )
1702 {
1703 if( pour_def.style == PADS_IO::POUR_STYLE::HATCHED )
1704 {
1705 hatoutToParent[pour_def.name] = pour_def.owner_pour;
1706 continue;
1707 }
1708
1709 if( pour_def.style == PADS_IO::POUR_STYLE::VOIDOUT
1710 || pour_def.thermal_type != PADS_IO::THERMAL_TYPE::NONE )
1711 {
1712 continue;
1713 }
1714
1715 if( pour_def.points.size() < 3 )
1716 continue;
1717
1718 PCB_LAYER_ID pourLayer = getMappedLayer( pour_def.layer );
1719
1720 if( pourLayer == UNDEFINED_LAYER )
1721 {
1722 if( m_reporter )
1723 {
1724 m_reporter->Report( wxString::Format(
1725 _( "Skipping pour on unmapped layer %d" ), pour_def.layer ),
1727 }
1728
1729 continue;
1730 }
1731
1732 ZONE* zone = new ZONE( m_loadBoard );
1733 zone->SetLayer( pourLayer );
1734
1735 zone->Outline()->NewOutline();
1736 appendArcPoints( zone->Outline()->Outline( 0 ), pour_def.points );
1737
1738 if( pour_def.is_cutout )
1739 {
1740 zone->SetIsRuleArea( true );
1741 zone->SetDoNotAllowZoneFills( true );
1742 zone->SetDoNotAllowTracks( false );
1743 zone->SetDoNotAllowVias( false );
1744 zone->SetDoNotAllowPads( false );
1745 zone->SetDoNotAllowFootprints( false );
1746 zone->SetZoneName( wxString::Format( wxT( "Cutout_%s" ), pour_def.owner_pour ) );
1747 }
1748 else
1749 {
1750 NETINFO_ITEM* net = m_loadBoard->FindNet(
1751 PADS_COMMON::ConvertInvertedNetName( pour_def.net_name ) );
1752
1753 if( net )
1754 zone->SetNet( net );
1755
1756 int kicadPriority = maxPriority - pour_def.priority + 1;
1757 zone->SetAssignedPriority( kicadPriority );
1758 zone->SetMinThickness( scaleSize( pour_def.width ) );
1759
1760 zone->SetThermalReliefGap( scaleSize( params.thermal_min_clearance ) );
1761 zone->SetThermalReliefSpokeWidth( scaleSize( params.thermal_line_width ) );
1762
1764 }
1765
1766 pourZoneMap[pour_def.name] = zone;
1767 m_loadBoard->Add( zone );
1768 }
1769
1770 // Second pass: build fill polygons from HATOUT records with VOIDOUT holes
1771 for( const auto& pour_def : pours )
1772 {
1773 if( pour_def.style != PADS_IO::POUR_STYLE::HATCHED )
1774 continue;
1775
1776 if( !isValidPoly( pour_def.points ) )
1777 continue;
1778
1779 auto zoneIt = pourZoneMap.find( pour_def.owner_pour );
1780
1781 if( zoneIt == pourZoneMap.end() )
1782 continue;
1783
1784 ZONE* zone = zoneIt->second;
1785 PCB_LAYER_ID pourLayer = zone->GetLayer();
1786
1787 SHAPE_POLY_SET fillPoly;
1788 fillPoly.NewOutline();
1789 appendArcPoints( fillPoly.Outline( 0 ), pour_def.points );
1790
1791 // PADS HATOUT fill data can contain self-intersecting vertices where
1792 // narrow corridors route between pads. Run Clipper2 union on the
1793 // outline before subtracting holes, since Simplify can introduce
1794 // micro-artifacts in clean complex polygons.
1795 if( fillPoly.Outline( 0 ).PointCount() >= 3
1796 && fillPoly.IsPolygonSelfIntersecting( 0 ) )
1797 {
1798 fillPoly.Simplify();
1799 }
1800
1801 // Collect all matching VOIDOUT regions into a single poly set and
1802 // subtract in one operation. PADS VOIDOUT shapes can extend beyond
1803 // the HATOUT outline boundary (PADS clips at render time), so
1804 // boolean subtraction is needed rather than treating them as
1805 // contained holes. Batching avoids Clipper2 precision accumulation
1806 // from repeated sequential operations.
1807 SHAPE_POLY_SET allVoids;
1808
1809 for( const auto& void_def : pours )
1810 {
1811 if( void_def.style != PADS_IO::POUR_STYLE::VOIDOUT )
1812 continue;
1813
1814 if( !isValidPoly( void_def.points ) )
1815 continue;
1816
1817 // VOIDOUT's owner_pour points to a HATOUT name. Check if that
1818 // HATOUT is owned by our POUROUT.
1819 auto parentIt = hatoutToParent.find( void_def.owner_pour );
1820
1821 if( parentIt == hatoutToParent.end() )
1822 continue;
1823
1824 if( parentIt->second != pour_def.owner_pour )
1825 continue;
1826
1827 allVoids.NewOutline();
1828 appendArcPoints( allVoids.Outline( allVoids.OutlineCount() - 1 ), void_def.points );
1829 }
1830
1831 if( allVoids.OutlineCount() > 0 )
1832 fillPoly.BooleanSubtract( allVoids );
1833
1834 zone->SetFilledPolysList( pourLayer, fillPoly );
1835 zone->SetIsFilled( true );
1836 }
1837}
1838
1839
1841{
1842 for( const PADS_IO::POLYLINE& polyline : m_parser->GetBoardOutlines() )
1843 {
1844 const auto& pts = polyline.points;
1845
1846 if( pts.size() < 2 )
1847 continue;
1848
1849 for( size_t i = 0; i < pts.size() - 1; ++i )
1850 {
1851 const PADS_IO::ARC_POINT& p1 = pts[i];
1852 const PADS_IO::ARC_POINT& p2 = pts[i + 1];
1853
1854 if( std::abs( p1.x - p2.x ) < 0.001 && std::abs( p1.y - p2.y ) < 0.001 )
1855 continue;
1856
1857 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
1858
1859 if( p2.is_arc )
1860 {
1861 setPcbShapeArc( shape, p1, p2 );
1862 }
1863 else
1864 {
1865 shape->SetShape( SHAPE_T::SEGMENT );
1866 shape->SetStart( VECTOR2I( scaleCoord( p1.x, true ),
1867 scaleCoord( p1.y, false ) ) );
1868 shape->SetEnd( VECTOR2I( scaleCoord( p2.x, true ),
1869 scaleCoord( p2.y, false ) ) );
1870 }
1871
1872 shape->SetWidth( scaleSize( polyline.width ) );
1873 shape->SetLayer( Edge_Cuts );
1874 m_loadBoard->Add( shape );
1875 }
1876
1877 // PADS format repeats the first point at the end for closed polygons, so check
1878 // if pLast already equals pFirst to avoid creating a zero-length closing segment
1879 if( polyline.closed && pts.size() > 2 )
1880 {
1881 const PADS_IO::ARC_POINT& pLast = pts.back();
1882 const PADS_IO::ARC_POINT& pFirst = pts.front();
1883
1884 bool needsClosing = ( std::abs( pLast.x - pFirst.x ) > 0.001
1885 || std::abs( pLast.y - pFirst.y ) > 0.001 );
1886
1887 if( needsClosing )
1888 {
1889 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
1890
1891 if( pFirst.is_arc )
1892 {
1893 setPcbShapeArc( shape, pLast, pFirst );
1894 }
1895 else
1896 {
1897 shape->SetShape( SHAPE_T::SEGMENT );
1898 shape->SetStart( VECTOR2I( scaleCoord( pLast.x, true ),
1899 scaleCoord( pLast.y, false ) ) );
1900 shape->SetEnd( VECTOR2I( scaleCoord( pFirst.x, true ),
1901 scaleCoord( pFirst.y, false ) ) );
1902 }
1903
1904 shape->SetWidth( scaleSize( polyline.width ) );
1905 shape->SetLayer( Edge_Cuts );
1906 m_loadBoard->Add( shape );
1907 }
1908 }
1909 }
1910}
1911
1912
1914{
1915 const auto& dimensions = m_parser->GetDimensions();
1916
1917 for( const auto& dim : dimensions )
1918 {
1919 if( dim.points.size() < 2 )
1920 continue;
1921
1923
1924 VECTOR2I start( scaleCoord( dim.points[0].x, true ),
1925 scaleCoord( dim.points[0].y, false ) );
1926 VECTOR2I end( scaleCoord( dim.points[1].x, true ),
1927 scaleCoord( dim.points[1].y, false ) );
1928
1929 // PADS horizontal/vertical dimensions measure only the X or Y projection.
1930 // PCB_DIM_ALIGNED measures along the start→end direction, so if the base
1931 // points differ on the non-measured axis the line becomes skewed.
1932 // Project the end point onto the measurement axis.
1933 if( dim.is_horizontal )
1934 end.y = start.y;
1935 else
1936 end.x = start.x;
1937
1938 dimension->SetStart( start );
1939 dimension->SetEnd( end );
1940
1941 // The crossbar_pos is the absolute coordinate of the crossbar. We compute
1942 // height as the offset from the start point to the crossbar.
1943 if( dim.is_horizontal )
1944 {
1945 double heightOffset = dim.crossbar_pos - dim.points[0].y;
1946 int height = -scaleSize( heightOffset );
1947 dimension->SetHeight( height );
1948 }
1949 else
1950 {
1951 double heightOffset = dim.crossbar_pos - dim.points[0].x;
1952 int height = scaleSize( heightOffset );
1953 dimension->SetHeight( height );
1954 }
1955
1956 PCB_LAYER_ID dimLayer = getMappedLayer( dim.layer );
1957
1958 if( dimLayer == UNDEFINED_LAYER || IsCopperLayer( dimLayer ) )
1959 dimLayer = Cmts_User;
1960
1961 dimension->SetLayer( dimLayer );
1962
1963 // PADS text_width is stroke thickness, not character width.
1964 // Calculate character dimensions from height.
1965 if( dim.text_height > 0 )
1966 {
1967 int scaledSize = scaleSize( dim.text_height );
1968 int charHeight =
1969 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextHeightScale );
1970 int charWidth =
1971 static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextWidthScale );
1972 dimension->SetTextSize( VECTOR2I( charWidth, charHeight ) );
1973
1974 if( dim.text_width > 0 )
1975 dimension->SetTextThickness( scaleSize( dim.text_width ) );
1976 }
1977
1978 if( !dim.text.empty() )
1979 {
1980 dimension->SetOverrideTextEnabled( true );
1981 dimension->SetOverrideText( wxString::FromUTF8( dim.text ) );
1982 }
1983
1984 dimension->SetLineThickness( scaleSize( 5.0 ) );
1985
1986 if( dim.rotation != 0.0 )
1987 dimension->SetTextAngle( EDA_ANGLE( dim.rotation, DEGREES_T ) );
1988
1989 dimension->Update();
1990 m_loadBoard->Add( dimension );
1991 }
1992}
1993
1994
1996{
1997 const auto& keepouts = m_parser->GetKeepouts();
1998 int keepoutIndex = 0;
1999
2000 for( const auto& ko : keepouts )
2001 {
2002 if( ko.outline.size() < 3 )
2003 continue;
2004
2005 ZONE* zone = new ZONE( m_loadBoard );
2006 zone->SetIsRuleArea( true );
2007
2008 if( ko.layers.empty() )
2009 {
2010 zone->SetLayerSet( LSET::AllCuMask() );
2011 }
2012 else if( ko.layers.size() == 1 )
2013 {
2014 PCB_LAYER_ID koLayer = getMappedLayer( ko.layers[0] );
2015
2016 if( koLayer == UNDEFINED_LAYER )
2017 {
2018 if( m_reporter )
2019 {
2020 m_reporter->Report( wxString::Format(
2021 _( "Skipping keepout on unmapped layer %d" ), ko.layers[0] ),
2023 }
2024 delete zone;
2025 continue;
2026 }
2027
2028 zone->SetLayer( koLayer );
2029 }
2030 else
2031 {
2032 LSET layerSet;
2033
2034 for( int layer : ko.layers )
2035 {
2036 PCB_LAYER_ID mappedLayer = getMappedLayer( layer );
2037
2038 if( mappedLayer != UNDEFINED_LAYER )
2039 layerSet.set( mappedLayer );
2040 }
2041
2042 if( layerSet.none() )
2043 {
2044 if( m_reporter )
2045 m_reporter->Report( _( "Skipping keepout with no valid layers" ), RPT_SEVERITY_WARNING );
2046 delete zone;
2047 continue;
2048 }
2049
2050 zone->SetLayerSet( layerSet );
2051 }
2052
2053 zone->SetDoNotAllowTracks( ko.no_traces );
2054 zone->SetDoNotAllowVias( ko.no_vias );
2055 zone->SetDoNotAllowZoneFills( ko.no_copper );
2056 zone->SetDoNotAllowFootprints( ko.no_components );
2057 zone->SetDoNotAllowPads( false );
2058
2059 wxString typeName;
2060
2061 switch( ko.type )
2062 {
2063 case PADS_IO::KEEPOUT_TYPE::ALL: typeName = wxT( "Keepout" ); break;
2064 case PADS_IO::KEEPOUT_TYPE::ROUTE: typeName = wxT( "RouteKeepout" ); break;
2065 case PADS_IO::KEEPOUT_TYPE::VIA: typeName = wxT( "ViaKeepout" ); break;
2066 case PADS_IO::KEEPOUT_TYPE::COPPER: typeName = wxT( "CopperKeepout" ); break;
2067 case PADS_IO::KEEPOUT_TYPE::PLACEMENT: typeName = wxT( "PlacementKeepout" ); break;
2068 }
2069
2070 zone->SetZoneName( wxString::Format( wxT( "%s_%d" ), typeName, ++keepoutIndex ) );
2071
2072 SHAPE_LINE_CHAIN koChain;
2073 appendArcPoints( koChain, ko.outline );
2074
2075 // Close the outline if first and last points don't match
2076 if( ko.outline.size() > 2 )
2077 {
2078 const auto& first = ko.outline.front();
2079 const auto& last = ko.outline.back();
2080
2081 if( std::abs( first.x - last.x ) > 0.001 || std::abs( first.y - last.y ) > 0.001 )
2082 koChain.Append( scaleCoord( first.x, true ), scaleCoord( first.y, false ) );
2083 }
2084
2085 koChain.SetClosed( true );
2086 zone->Outline()->AddOutline( koChain );
2087
2088 m_loadBoard->Add( zone );
2089 }
2090}
2091
2092
2094{
2095 for( const PADS_IO::GRAPHIC_LINE& graphic : m_parser->GetGraphicLines() )
2096 {
2097 const auto& pts = graphic.points;
2098
2099 PCB_LAYER_ID graphicLayer = getMappedLayer( graphic.layer );
2100
2101 if( graphicLayer == UNDEFINED_LAYER )
2102 continue;
2103
2104 if( pts.size() == 1 && pts[0].is_arc
2105 && std::abs( pts[0].arc.delta_angle - 360.0 ) < 0.1 )
2106 {
2107 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2108 shape->SetShape( SHAPE_T::CIRCLE );
2109 VECTOR2I center( scaleCoord( pts[0].arc.cx, true ),
2110 scaleCoord( pts[0].arc.cy, false ) );
2111 int radius = std::max( scaleSize( pts[0].arc.radius ), m_minObjectSize );
2112 shape->SetCenter( center );
2113 shape->SetEnd( VECTOR2I( center.x + radius, center.y ) );
2114 shape->SetWidth( scaleSize( graphic.width ) );
2115 shape->SetLayer( graphicLayer );
2116 m_loadBoard->Add( shape );
2117 continue;
2118 }
2119
2120 if( pts.size() < 2 )
2121 continue;
2122
2123 for( size_t i = 0; i < pts.size() - 1; ++i )
2124 {
2125 const PADS_IO::ARC_POINT& p1 = pts[i];
2126 const PADS_IO::ARC_POINT& p2 = pts[i + 1];
2127
2128 if( std::abs( p1.x - p2.x ) < 0.001 && std::abs( p1.y - p2.y ) < 0.001 )
2129 continue;
2130
2131 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2132
2133 if( p2.is_arc )
2134 {
2135 setPcbShapeArc( shape, p1, p2 );
2136 }
2137 else
2138 {
2139 shape->SetShape( SHAPE_T::SEGMENT );
2140 shape->SetStart( VECTOR2I( scaleCoord( p1.x, true ),
2141 scaleCoord( p1.y, false ) ) );
2142 shape->SetEnd( VECTOR2I( scaleCoord( p2.x, true ),
2143 scaleCoord( p2.y, false ) ) );
2144 }
2145
2146 shape->SetWidth( scaleSize( graphic.width ) );
2147 shape->SetLayer( graphicLayer );
2148 m_loadBoard->Add( shape );
2149 }
2150
2151 if( graphic.closed && pts.size() > 2 )
2152 {
2153 const PADS_IO::ARC_POINT& pLast = pts.back();
2154 const PADS_IO::ARC_POINT& pFirst = pts.front();
2155
2156 bool needsClosing = ( std::abs( pLast.x - pFirst.x ) > 0.001
2157 || std::abs( pLast.y - pFirst.y ) > 0.001 );
2158
2159 if( needsClosing )
2160 {
2161 PCB_SHAPE* shape = new PCB_SHAPE( m_loadBoard );
2162
2163 if( pFirst.is_arc )
2164 {
2165 setPcbShapeArc( shape, pLast, pFirst );
2166 }
2167 else
2168 {
2169 shape->SetShape( SHAPE_T::SEGMENT );
2170 shape->SetStart( VECTOR2I( scaleCoord( pLast.x, true ),
2171 scaleCoord( pLast.y, false ) ) );
2172 shape->SetEnd( VECTOR2I( scaleCoord( pFirst.x, true ),
2173 scaleCoord( pFirst.y, false ) ) );
2174 }
2175
2176 shape->SetWidth( scaleSize( graphic.width ) );
2177 shape->SetLayer( graphicLayer );
2178 m_loadBoard->Add( shape );
2179 }
2180 }
2181 }
2182}
2183
2184
2185void PCB_IO_PADS::generateDrcRules( const wxString& aFileName )
2186{
2187 wxFileName fn( aFileName );
2188 fn.SetExt( wxT( "kicad_dru" ) );
2189
2190 wxString customRules = wxT( "(version 1)\n" );
2191
2192 const auto& diffPairs = m_parser->GetDiffPairs();
2193
2194 for( const auto& dp : diffPairs )
2195 {
2196 if( dp.name.empty() || ( dp.gap <= 0 && dp.width <= 0 ) )
2197 continue;
2198
2199 wxString ruleName = wxString::Format( wxT( "DiffPair_%s" ), wxString::FromUTF8( dp.name ) );
2200
2201 if( dp.gap > 0 && !dp.positive_net.empty() && !dp.negative_net.empty() )
2202 {
2203 wxString posNet = PADS_COMMON::ConvertInvertedNetName( dp.positive_net );
2204 wxString negNet = PADS_COMMON::ConvertInvertedNetName( dp.negative_net );
2205 double gapMm = dp.gap * m_scaleFactor / PADS_UNIT_CONVERTER::MM_TO_NM;
2206 wxString gapStr = wxString::FromUTF8( FormatDouble2Str( gapMm ) ) + wxT( "mm" );
2207
2208 customRules += wxString::Format(
2209 wxT( "\n(rule \"%s_gap\"\n" )
2210 wxT( " (condition \"A.NetName == '%s' && B.NetName == '%s'\")\n" )
2211 wxT( " (constraint clearance (min %s)))\n" ),
2212 ruleName, posNet, negNet, gapStr );
2213 }
2214 }
2215
2216 if( customRules.length() > 15 )
2217 {
2218 wxFile rulesFile( fn.GetFullPath(), wxFile::write );
2219
2220 if( rulesFile.IsOpened() )
2221 rulesFile.Write( customRules );
2222 }
2223}
2224
2225
2227{
2228 if( !m_reporter )
2229 return;
2230
2231 size_t trackCount = 0;
2232 size_t viaCount = 0;
2233
2234 for( PCB_TRACK* track : m_loadBoard->Tracks() )
2235 {
2236 if( track->Type() == PCB_VIA_T )
2237 viaCount++;
2238 else
2239 trackCount++;
2240 }
2241
2242 m_reporter->Report( wxString::Format( _( "Imported %zu footprints, %d nets, %zu tracks,"
2243 " %zu vias, %zu zones" ),
2244 m_loadBoard->Footprints().size(),
2245 m_loadBoard->GetNetCount(),
2246 trackCount, viaCount,
2247 m_loadBoard->Zones().size() ),
2249}
2250
2251
2252std::map<wxString, PCB_LAYER_ID> PCB_IO_PADS::DefaultLayerMappingCallback(
2253 const std::vector<INPUT_LAYER_DESC>& aInputLayerDescriptionVector )
2254{
2255 std::map<wxString, PCB_LAYER_ID> layer_map;
2256
2257 for( const INPUT_LAYER_DESC& layer : aInputLayerDescriptionVector )
2258 {
2259 layer_map[layer.Name] = layer.AutoMapLayer;
2260 }
2261
2262 return layer_map;
2263}
2264
2265
2266int PCB_IO_PADS::scaleSize( double aVal ) const
2267{
2268 int64_t nm = m_unitConverter.ToNanometersSize( aVal );
2269 return static_cast<int>( std::clamp<int64_t>( nm, INT_MIN, INT_MAX ) );
2270}
2271
2272
2273int PCB_IO_PADS::scaleCoord( double aVal, bool aIsX ) const
2274{
2275 double origin = aIsX ? m_originX : m_originY;
2276
2277 long long origin_nm = static_cast<long long>( std::round( origin * m_scaleFactor ) );
2278 long long val_nm = static_cast<long long>( std::round( aVal * m_scaleFactor ) );
2279
2280 long long result = aIsX ? ( val_nm - origin_nm ) : ( origin_nm - val_nm );
2281 return static_cast<int>( std::clamp<long long>( result, INT_MIN, INT_MAX ) );
2282}
2283
2284
2286{
2287 for( const auto& info : m_layerInfos )
2288 {
2289 if( info.padsLayerNum == aPadsLayer )
2290 {
2291 auto it = m_layer_map.find( wxString::FromUTF8( info.name ) );
2292
2293 if( it != m_layer_map.end() && it->second != UNDEFINED_LAYER )
2294 return it->second;
2295
2296 return m_layerMapper.GetAutoMapLayer( aPadsLayer, info.type );
2297 }
2298 }
2299
2300 return m_layerMapper.GetAutoMapLayer( aPadsLayer );
2301}
2302
2303
2304void PCB_IO_PADS::ensureNet( const std::string& aNetName )
2305{
2306 if( aNetName.empty() )
2307 return;
2308
2309 wxString wxName = PADS_COMMON::ConvertInvertedNetName( aNetName );
2310
2311 if( m_loadBoard->FindNet( wxName ) == nullptr )
2312 {
2313 NETINFO_ITEM* net = new NETINFO_ITEM( m_loadBoard, wxName,
2314 m_loadBoard->GetNetCount() + 1 );
2315 m_loadBoard->Add( net );
2316 }
2317}
2318
2319
2321{
2322 m_loadBoard = nullptr;
2323 m_parser = nullptr;
2326 m_layerInfos.clear();
2327 m_scaleFactor = 0.0;
2328 m_originX = 0.0;
2329 m_originY = 0.0;
2330 m_pinToNetMap.clear();
2331 m_partToBlockMap.clear();
2332 m_testPointIndex = 1;
2333}
2334
2335
2337 const std::vector<PADS_IO::ARC_POINT>& aPts )
2338{
2339 if( aPts.empty() )
2340 return;
2341
2342 // Single full-circle entry becomes a 36-segment polygon
2343 if( aPts.size() == 1 && aPts[0].is_arc
2344 && std::abs( aPts[0].arc.delta_angle ) >= 359.0 )
2345 {
2346 VECTOR2I center( scaleCoord( aPts[0].arc.cx, true ),
2347 scaleCoord( aPts[0].arc.cy, false ) );
2348 int radius = scaleSize( aPts[0].arc.radius );
2349
2350 constexpr int NUM_SEGS = 36;
2351
2352 for( int i = 0; i < NUM_SEGS; i++ )
2353 {
2354 double angle = 2.0 * M_PI * i / NUM_SEGS;
2355 aChain.Append( center.x + KiROUND( radius * cos( angle ) ),
2356 center.y + KiROUND( radius * sin( angle ) ) );
2357 }
2358
2359 return;
2360 }
2361
2362 aChain.Append( scaleCoord( aPts[0].x, true ), scaleCoord( aPts[0].y, false ) );
2363
2364 for( size_t i = 1; i < aPts.size(); i++ )
2365 {
2366 const auto& pt = aPts[i];
2367
2368 if( pt.is_arc )
2369 {
2370 SHAPE_ARC arc = makeMidpointArc( aPts[i - 1], pt, 0 );
2371 const SHAPE_LINE_CHAIN arcPoly = arc.ConvertToPolyline();
2372
2373 for( int j = 1; j < arcPoly.PointCount(); j++ )
2374 aChain.Append( arcPoly.CPoint( j ).x, arcPoly.CPoint( j ).y );
2375 }
2376 else
2377 {
2378 aChain.Append( scaleCoord( pt.x, true ), scaleCoord( pt.y, false ) );
2379 }
2380 }
2381}
2382
2383
2385 const PADS_IO::ARC_POINT& aCurr )
2386{
2387 aShape->SetShape( SHAPE_T::ARC );
2388
2389 VECTOR2I center( scaleCoord( aCurr.arc.cx, true ), scaleCoord( aCurr.arc.cy, false ) );
2390 VECTOR2I start( scaleCoord( aPrev.x, true ), scaleCoord( aPrev.y, false ) );
2391 VECTOR2I end( scaleCoord( aCurr.x, true ), scaleCoord( aCurr.y, false ) );
2392
2393 // Y-axis flip reverses arc winding; swap endpoints for CCW arcs
2394 if( aCurr.arc.delta_angle > 0 )
2395 std::swap( start, end );
2396
2397 aShape->SetCenter( center );
2398 aShape->SetStart( start );
2399 aShape->SetEnd( end );
2400}
2401
2402
2404 const PADS_IO::ARC_POINT& aCurr, int aWidth )
2405{
2406 VECTOR2I start( scaleCoord( aPrev.x, true ), scaleCoord( aPrev.y, false ) );
2407 VECTOR2I end( scaleCoord( aCurr.x, true ), scaleCoord( aCurr.y, false ) );
2408
2409 // Compute the arc midpoint in PADS coordinate space (before the Y-axis
2410 // flip in scaleCoord) so the 3-point constructor gets the correct winding.
2411 double startAngleRad = atan2( aPrev.y - aCurr.arc.cy, aPrev.x - aCurr.arc.cx );
2412 double midAngleRad = startAngleRad + ( aCurr.arc.delta_angle * M_PI / 180.0 ) / 2.0;
2413
2414 double midX = aCurr.arc.cx + aCurr.arc.radius * cos( midAngleRad );
2415 double midY = aCurr.arc.cy + aCurr.arc.radius * sin( midAngleRad );
2416
2417 VECTOR2I mid( scaleCoord( midX, true ), scaleCoord( midY, false ) );
2418
2419 return SHAPE_ARC( start, mid, end, aWidth );
2420}
2421
2422
2424{
2425 m_layerMapper.SetCopperLayerCount( m_parser->GetParameters().layer_count );
2426
2427 std::vector<PADS_IO::LAYER_INFO> padsLayerInfos = m_parser->GetLayerInfos();
2428
2429 auto convertLayerType = []( PADS_IO::PADS_LAYER_FUNCTION func ) -> PADS_LAYER_TYPE {
2430 switch( func )
2431 {
2448 default:
2450 }
2451 };
2452
2453 for( const auto& padsInfo : padsLayerInfos )
2454 {
2456 info.padsLayerNum = padsInfo.number;
2457 info.name = padsInfo.name;
2458
2459 if( padsInfo.layer_type != PADS_IO::PADS_LAYER_FUNCTION::UNKNOWN
2460 && padsInfo.layer_type != PADS_IO::PADS_LAYER_FUNCTION::UNASSIGNED )
2461 {
2462 info.type = convertLayerType( padsInfo.layer_type );
2463
2464 std::string lowerName = padsInfo.name;
2465 std::transform( lowerName.begin(), lowerName.end(), lowerName.begin(),
2466 []( unsigned char c ){ return std::tolower( c ); } );
2467
2468 bool isBottom = lowerName.find( "bottom" ) != std::string::npos
2469 || lowerName.find( "bot" ) != std::string::npos;
2470
2471 if( info.type == PADS_LAYER_TYPE::SOLDERMASK_TOP && isBottom )
2473 else if( info.type == PADS_LAYER_TYPE::PASTE_TOP && isBottom )
2475 else if( info.type == PADS_LAYER_TYPE::SILKSCREEN_TOP && isBottom )
2477 else if( info.type == PADS_LAYER_TYPE::ASSEMBLY_TOP && isBottom )
2479 else if( info.type == PADS_LAYER_TYPE::COPPER_INNER )
2480 {
2481 if( padsInfo.number == 1 )
2483 else if( padsInfo.number == m_parser->GetParameters().layer_count )
2485 }
2486 }
2487 else
2488 {
2489 info.type = m_layerMapper.GetLayerType( padsInfo.number );
2490 }
2491
2492 info.required = padsInfo.required;
2493 m_layerInfos.push_back( info );
2494 }
2495
2496 std::vector<INPUT_LAYER_DESC> inputDescs =
2497 m_layerMapper.BuildInputLayerDescriptions( m_layerInfos );
2498
2500 m_layer_map = m_layer_mapping_handler( inputDescs );
2501
2502 int copperLayerCount = m_parser->GetParameters().layer_count;
2503
2504 if( copperLayerCount < 1 )
2505 copperLayerCount = 2;
2506
2507 m_loadBoard->SetCopperLayerCount( copperLayerCount );
2508
2509 if( m_parser->IsBasicUnits() )
2510 {
2511 m_unitConverter.SetBasicUnitsMode( true );
2512 }
2513 else
2514 {
2515 switch( m_parser->GetParameters().units )
2516 {
2518 m_unitConverter.SetBaseUnits( PADS_UNIT_TYPE::MILS );
2519 break;
2522 break;
2525 break;
2526 }
2527 }
2528
2529 m_scaleFactor = m_parser->IsBasicUnits()
2531 : ( m_parser->GetParameters().units == PADS_IO::UNIT_TYPE::MILS
2533 : m_parser->GetParameters().units == PADS_IO::UNIT_TYPE::METRIC
2536
2537 const auto& designRules = m_parser->GetDesignRules();
2538 BOARD_DESIGN_SETTINGS& bds = m_loadBoard->GetDesignSettings();
2539
2540 bds.m_MinClearance = scaleSize( designRules.min_clearance );
2541 bds.m_TrackMinWidth = scaleSize( designRules.min_track_width );
2542 bds.m_ViasMinSize = scaleSize( designRules.min_via_size );
2543 bds.m_MinThroughDrill = scaleSize( designRules.min_via_drill );
2544 bds.m_HoleToHoleMin = scaleSize( designRules.hole_to_hole );
2545 bds.m_SilkClearance = scaleSize( designRules.silk_clearance );
2546 bds.m_SolderMaskExpansion = scaleSize( designRules.mask_clearance );
2547 bds.m_CopperEdgeClearance = scaleSize( designRules.copper_edge_clearance );
2548
2549 bds.GetDefaultZoneSettings().m_ZoneClearance = scaleSize( designRules.default_clearance );
2550
2551 bds.SetCustomTrackWidth( scaleSize( designRules.default_track_width ) );
2552 bds.SetCustomViaSize( scaleSize( designRules.default_via_size ) );
2553 bds.SetCustomViaDrill( scaleSize( designRules.default_via_drill ) );
2554
2555 std::shared_ptr<NETCLASS> defaultNetclass = bds.m_NetSettings->GetDefaultNetclass();
2556
2557 if( defaultNetclass )
2558 {
2559 defaultNetclass->SetClearance( scaleSize( designRules.default_clearance ) );
2560 defaultNetclass->SetTrackWidth( scaleSize( designRules.default_track_width ) );
2561 defaultNetclass->SetViaDiameter( scaleSize( designRules.default_via_size ) );
2562 defaultNetclass->SetViaDrill( scaleSize( designRules.default_via_drill ) );
2563 }
2564
2565 const auto& viaDefs = m_parser->GetViaDefs();
2566
2567 if( !viaDefs.empty() )
2568 {
2569 // Use the file's designated default signal via, falling back to the first
2570 // definition if no explicit default was specified
2571 const std::string& defaultViaName = m_parser->GetParameters().default_signal_via;
2572 auto defaultIt = viaDefs.find( defaultViaName );
2573
2574 if( defaultIt == viaDefs.end() )
2575 defaultIt = viaDefs.begin();
2576
2577 int viaDia = scaleSize( defaultIt->second.size );
2578 int viaDrill = scaleSize( defaultIt->second.drill );
2579
2580 bds.SetCustomViaSize( viaDia );
2581 bds.SetCustomViaDrill( viaDrill );
2582
2583 if( defaultNetclass )
2584 {
2585 defaultNetclass->SetViaDiameter( viaDia );
2586 defaultNetclass->SetViaDrill( viaDrill );
2587 }
2588
2589 for( const auto& [name, def] : viaDefs )
2590 bds.m_ViasDimensionsList.emplace_back( scaleSize( def.size ), scaleSize( def.drill ) );
2591 }
2592
2593 const auto& netClasses = m_parser->GetNetClasses();
2594
2595 for( const auto& nc : netClasses )
2596 {
2597 if( nc.name.empty() )
2598 continue;
2599
2600 wxString ncName = wxString::FromUTF8( nc.name );
2601 std::shared_ptr<NETCLASS> netclass = std::make_shared<NETCLASS>( ncName );
2602
2603 if( nc.clearance > 0 )
2604 netclass->SetClearance( scaleSize( nc.clearance ) );
2605
2606 if( nc.track_width > 0 )
2607 netclass->SetTrackWidth( scaleSize( nc.track_width ) );
2608
2609 if( nc.via_size > 0 )
2610 netclass->SetViaDiameter( scaleSize( nc.via_size ) );
2611
2612 if( nc.via_drill > 0 )
2613 netclass->SetViaDrill( scaleSize( nc.via_drill ) );
2614
2615 if( nc.diff_pair_width > 0 )
2616 netclass->SetDiffPairWidth( scaleSize( nc.diff_pair_width ) );
2617
2618 if( nc.diff_pair_gap > 0 )
2619 netclass->SetDiffPairGap( scaleSize( nc.diff_pair_gap ) );
2620
2621 bds.m_NetSettings->SetNetclass( ncName, netclass );
2622
2623 for( const std::string& netName : nc.net_names )
2624 {
2625 wxString wxNetName = PADS_COMMON::ConvertInvertedNetName( netName );
2626 bds.m_NetSettings->SetNetclassPatternAssignment( wxNetName, ncName );
2627 }
2628 }
2629
2630 const auto& diffPairs = m_parser->GetDiffPairs();
2631
2632 for( const auto& dp : diffPairs )
2633 {
2634 if( dp.name.empty() )
2635 continue;
2636
2637 wxString dpClassName =
2638 wxString::Format( wxT( "DiffPair_%s" ), wxString::FromUTF8( dp.name ) );
2639 std::shared_ptr<NETCLASS> dpNetclass = std::make_shared<NETCLASS>( dpClassName );
2640
2641 if( dp.gap > 0 )
2642 dpNetclass->SetDiffPairGap( scaleSize( dp.gap ) );
2643
2644 if( dp.width > 0 )
2645 {
2646 dpNetclass->SetDiffPairWidth( scaleSize( dp.width ) );
2647 dpNetclass->SetTrackWidth( scaleSize( dp.width ) );
2648 }
2649
2650 bds.m_NetSettings->SetNetclass( dpClassName, dpNetclass );
2651
2652 if( !dp.positive_net.empty() )
2653 {
2654 wxString wxPosNet = PADS_COMMON::ConvertInvertedNetName( dp.positive_net );
2655 bds.m_NetSettings->SetNetclassPatternAssignment( wxPosNet, dpClassName );
2656 }
2657
2658 if( !dp.negative_net.empty() )
2659 {
2660 wxString wxNegNet = PADS_COMMON::ConvertInvertedNetName( dp.negative_net );
2661 bds.m_NetSettings->SetNetclassPatternAssignment( wxNegNet, dpClassName );
2662 }
2663 }
2664
2665 m_originX = m_parser->GetParameters().origin.x;
2666 m_originY = m_parser->GetParameters().origin.y;
2667
2668 const auto& boardOutlines = m_parser->GetBoardOutlines();
2669
2670 if( !boardOutlines.empty() )
2671 {
2672 double min_x = std::numeric_limits<double>::max();
2673 double max_x = std::numeric_limits<double>::lowest();
2674 double min_y = std::numeric_limits<double>::max();
2675 double max_y = std::numeric_limits<double>::lowest();
2676
2677 for( const auto& outline : boardOutlines )
2678 {
2679 for( const auto& pt : outline.points )
2680 {
2681 min_x = std::min( min_x, pt.x );
2682 max_x = std::max( max_x, pt.x );
2683 min_y = std::min( min_y, pt.y );
2684 max_y = std::max( max_y, pt.y );
2685 }
2686 }
2687
2688 if( min_x < max_x && min_y < max_y )
2689 {
2690 m_originX = ( min_x + max_x ) / 2.0;
2691 m_originY = ( min_y + max_y ) / 2.0;
2692 }
2693 }
2694
2695 // Build board stackup from LAYER DATA if meaningful data exists.
2696 // Collect copper layer infos ordered by PADS layer number.
2697 std::vector<const PADS_IO::LAYER_INFO*> copperLayerInfos;
2698
2699 for( const auto& li : padsLayerInfos )
2700 {
2701 if( li.is_copper )
2702 copperLayerInfos.push_back( &li );
2703 }
2704
2705 bool hasStackupData = false;
2706
2707 for( const auto* li : copperLayerInfos )
2708 {
2709 if( li->layer_thickness > 0.0 || li->dielectric_constant > 0.0 )
2710 {
2711 hasStackupData = true;
2712 break;
2713 }
2714 }
2715
2716 if( hasStackupData )
2717 {
2718 BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
2719 stackup.RemoveAll();
2720 stackup.BuildDefaultStackupList( &bds, copperLayerCount );
2721
2722 // Build a map from KiCad PCB_LAYER_ID to PADS LAYER_INFO for copper layers
2723 std::map<PCB_LAYER_ID, const PADS_IO::LAYER_INFO*> copperInfoMap;
2724
2725 for( const auto* li : copperLayerInfos )
2726 {
2727 PCB_LAYER_ID kicadLayer = getMappedLayer( li->number );
2728
2729 if( kicadLayer != UNDEFINED_LAYER )
2730 copperInfoMap[kicadLayer] = li;
2731 }
2732
2733 // Track the previous copper layer's info for dielectric assignment
2734 const PADS_IO::LAYER_INFO* prevCopperInfo = nullptr;
2735
2736 for( BOARD_STACKUP_ITEM* item : stackup.GetList() )
2737 {
2738 if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_COPPER )
2739 {
2740 auto it = copperInfoMap.find( item->GetBrdLayerId() );
2741
2742 if( it != copperInfoMap.end() )
2743 {
2744 prevCopperInfo = it->second;
2745
2746 if( it->second->copper_thickness > 0.0 )
2747 item->SetThickness( scaleSize( it->second->copper_thickness ) );
2748 }
2749 }
2750 else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_DIELECTRIC )
2751 {
2752 if( prevCopperInfo )
2753 {
2754 if( prevCopperInfo->layer_thickness > 0.0 )
2755 item->SetThickness( scaleSize( prevCopperInfo->layer_thickness ) );
2756
2757 if( prevCopperInfo->dielectric_constant > 0.0 )
2758 item->SetEpsilonR( prevCopperInfo->dielectric_constant );
2759 }
2760 }
2761 else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SILKSCREEN )
2762 {
2763 item->SetColor( wxT( "White" ) );
2764 }
2765 else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SOLDERMASK )
2766 {
2767 item->SetColor( wxT( "Green" ) );
2768 }
2769 }
2770
2771 int thickness = stackup.BuildBoardThicknessFromStackup();
2772 bds.SetBoardThickness( thickness );
2773 bds.m_HasStackup = true;
2774 }
2775}
const char * name
@ 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
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
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)
ZONE_SETTINGS & GetDefaultZoneSettings()
std::vector< VIA_DIMENSION > m_ViasDimensionsList
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:285
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:322
void SetCenter(const VECTOR2I &aCenter)
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:178
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:168
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:220
void SetWidth(int aWidth)
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:546
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:431
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:400
void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition eda_text.cpp:298
void SetKeepUpright(bool aKeepUpright)
Definition eda_text.cpp:439
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:284
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition eda_text.cpp:313
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:423
void SetPosition(const VECTOR2I &aPos) override
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:352
void SetOrientation(const EDA_ANGLE &aNewAngle)
void SetPath(const KIID_PATH &aPath)
Definition footprint.h:375
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:787
void SetReference(const wxString &aReference)
Definition footprint.h:757
void SetValue(const wxString &aValue)
Definition footprint.h:778
PCB_FIELD & Reference()
Definition footprint.h:788
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:853
VECTOR2I GetPosition() const override
Definition footprint.h:327
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:49
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 LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:599
static LSET AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:608
Handle the data for a net.
Definition netinfo.h:54
const wxString & GetNetname() const
Definition netinfo.h:112
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 ...
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
void Parse(const wxString &aFileName)
Maps PADS layer numbers and names to KiCad layer IDs.
Converts PADS file format units to KiCad internal units (nanometers).
static constexpr double MILS_TO_NM
static constexpr double INCHES_TO_NM
static constexpr double BASIC_TO_NM
static constexpr double MM_TO_NM
Definition pad.h:55
void Update()
Update the dimension's cached text and geometry.
void SetOverrideTextEnabled(bool aOverride)
void SetLineThickness(int aWidth)
virtual void SetEnd(const VECTOR2I &aPoint)
virtual void SetStart(const VECTOR2I &aPoint)
void SetOverrideText(const wxString &aValue)
For better understanding of the points that make a dimension:
void SetHeight(int aHeight)
Set the distance from the feature points to the crossbar line.
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:53
double m_scaleFactor
const IO_FILE_DESC GetBoardFileDesc() const override
Returns board file description for the PCB_IO.
int scaleSize(double aVal) const
void loadBoardSetup()
std::vector< PADS_LAYER_INFO > m_layerInfos
void loadTestPoints()
void reportStatistics()
~PCB_IO_PADS() override
SHAPE_ARC makeMidpointArc(const PADS_IO::ARC_POINT &aPrev, const PADS_IO::ARC_POINT &aCurr, int aWidth)
Build a SHAPE_ARC from two consecutive PADS points using the midpoint approach.
double m_originY
void loadClusterGroups()
void loadTracksAndVias()
std::map< std::string, std::string > m_partToBlockMap
int scaleCoord(double aVal, bool aIsX) const
void setPcbShapeArc(PCB_SHAPE *aShape, const PADS_IO::ARC_POINT &aPrev, const PADS_IO::ARC_POINT &aCurr)
Configure a PCB_SHAPE as an arc from two consecutive PADS points using board-level scaleCoord.
std::map< std::string, std::string > m_pinToNetMap
PCB_LAYER_ID getMappedLayer(int aPadsLayer) const
int m_testPointIndex
void appendArcPoints(SHAPE_LINE_CHAIN &aChain, const std::vector< PADS_IO::ARC_POINT > &aPts)
Interpolate arc segments from an ARC_POINT vector into polyline vertices on a SHAPE_LINE_CHAIN.
std::map< wxString, PCB_LAYER_ID > m_layer_map
PADS layer names to KiCad layers.
int m_minObjectSize
void generateDrcRules(const wxString &aFileName)
void loadFootprints()
double m_originX
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const std::map< std::string, UTF8 > *aProperties, PROJECT *aProject) override
Load information from some input file format that this PCB_IO implementation knows about into either ...
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PCB_IO can read the specified board file.
long long GetLibraryTimestamp(const wxString &aLibraryPath) const override
Generate a timestamp representing all the files in the library (including the library directory).
void loadKeepouts()
void loadCopperShapes()
BOARD * m_loadBoard
const IO_FILE_DESC GetLibraryDesc() const override
Get the descriptor for the library container that this IO plugin operates on.
void ensureNet(const std::string &aNetName)
PADS_LAYER_MAPPER m_layerMapper
void clearLoadingState()
void loadGraphicLines()
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:337
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:92
virtual void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:84
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:150
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:153
virtual void SetWidth(int aWidth)
Definition pcb_track.h:147
Container for project specific data.
Definition project.h:65
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 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:73
void SetDoNotAllowPads(bool aEnable)
Definition zone.h:738
void SetMinThickness(int aMinThickness)
Definition zone.h:307
void SetThermalReliefSpokeWidth(int aThermalReliefSpokeWidth)
Definition zone.h:237
virtual PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition zone.cpp:501
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:550
SHAPE_POLY_SET * Outline()
Definition zone.h:340
void SetIsRuleArea(bool aEnable)
Definition zone.h:720
void SetDoNotAllowTracks(bool aEnable)
Definition zone.h:737
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition zone.h:634
void SetIsFilled(bool isFilled)
Definition zone.h:298
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:556
void SetDoNotAllowVias(bool aEnable)
Definition zone.h:736
void SetThermalReliefGap(int aThermalReliefGap)
Definition zone.h:226
void SetDoNotAllowFootprints(bool aEnable)
Definition zone.h:739
void SetDoNotAllowZoneFills(bool aEnable)
Definition zone.h:735
void SetAssignedPriority(unsigned aPriority)
Definition zone.h:120
void SetPadConnection(ZONE_CONNECTION aPadConnection)
Definition zone.h:304
void SetZoneName(const wxString &aName)
Definition zone.h:164
#define _(s)
@ DEGREES_T
Definition eda_angle.h:31
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
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:803
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:677
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ Edge_Cuts
Definition layer_ids.h:112
@ Cmts_User
Definition layer_ids.h:108
@ B_Cu
Definition layer_ids.h:65
@ 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
Definition pcb_track.h:67
@ THROUGH
Definition pcb_track.h:68
@ MICROVIA
Definition pcb_track.h:71
@ 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 slot_length
Slot length.
double finger_offset
Finger pad offset along orientation axis.
double sizeB
Secondary size (height for rectangles/ovals)
double corner_radius
Corner radius magnitude (always positive)
double sizeA
Primary size (diameter or width)
std::vector< DECAL_ITEM > items
std::vector< TERMINAL > terminals
std::map< int, std::vector< PAD_STACK_LAYER > > pad_stacks
std::map< std::string, std::string > attributes
Attribute name-value pairs from {...} block.
A polyline that may contain arc segments.
bool has_mask_front
Stack includes top soldermask opening (layer 25)
int start_layer
First PADS layer number in via span.
int end_layer
Last PADS layer number in via span.
bool has_mask_back
Stack includes bottom soldermask opening (layer 28)
Information about a single PADS layer.
@ USER
The field ID hasn't been set yet; field is invalid.
std::string path
KIBIS_PIN * pin
VECTOR2I center
int radius
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
#define M_PI
static thread_pool * tp
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:102
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
@ THERMAL
Use thermal relief for pads.
Definition zones.h:50