KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pads_pcb_converter.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) 2026 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "pads_pcb_converter.h"
21
22#include <algorithm>
23#include <climits>
24#include <cmath>
25#include <limits>
26
27#include <wx/file.h>
28#include <wx/filefn.h>
29#include <wx/filename.h>
30
31#include <advanced_config.h>
32#include <board.h>
35#include <geometry/eda_angle.h>
36#include <geometry/shape_arc.h>
38#include <io/pads/pads_common.h>
39#include <math/util.h>
40#include <netinfo.h>
41#include <pcb_dimension.h>
42#include <pcb_text.h>
43#include <pcb_track.h>
44#include <reporter.h>
45#include <string_utils.h>
46#include <trigo.h>
47#include <zone.h>
48
49
51 m_board( aBoard ),
52 m_reporter( aReporter )
53{
54}
55
56
57void PADS_PCB_CONVERTER::SetOrigin( double aX, double aY )
58{
59 m_originX = aX;
60 m_originY = aY;
61}
62
63
64bool PADS_PCB_CONVERTER::SetOriginFromOutlines( const std::vector<PADS_IO::POLYLINE>& aOutlines )
65{
66 if( aOutlines.empty() )
67 return false;
68
69 double minX = std::numeric_limits<double>::max();
70 double maxX = std::numeric_limits<double>::lowest();
71 double minY = std::numeric_limits<double>::max();
72 double maxY = std::numeric_limits<double>::lowest();
73
74 for( const PADS_IO::POLYLINE& outline : aOutlines )
75 {
76 for( const PADS_IO::ARC_POINT& pt : outline.points )
77 {
78 minX = std::min( minX, pt.x );
79 maxX = std::max( maxX, pt.x );
80 minY = std::min( minY, pt.y );
81 maxY = std::max( maxY, pt.y );
82 }
83 }
84
85 if( minX >= maxX || minY >= maxY )
86 return false;
87
88 m_originX = ( minX + maxX ) / 2.0;
89 m_originY = ( minY + maxY ) / 2.0;
90
91 return true;
92}
93
94
95int PADS_PCB_CONVERTER::ScaleSize( double aVal ) const
96{
97 int64_t nm = m_unitConverter.ToNanometersSize( aVal );
98
99 return static_cast<int>( std::clamp<int64_t>( nm, INT_MIN, INT_MAX ) );
100}
101
102
103int PADS_PCB_CONVERTER::ScaleCoord( double aVal, bool aIsX ) const
104{
106}
107
108
109VECTOR2I PADS_PCB_CONVERTER::ScalePoint( double aX, double aY ) const
110{
111 return VECTOR2I( ScaleCoord( aX, true ), ScaleCoord( aY, false ) );
112}
113
114
115void PADS_PCB_CONVERTER::SetupLayers( const std::vector<PADS_IO::LAYER_INFO>& aPadsLayers, int aPadsLayerCount,
116 const LAYER_MAPPING_HANDLER& aMappingHandler, bool aResolveUnknownTypeByName )
117{
118 m_layerMapper.SetCopperLayerCount( aPadsLayerCount );
119
120 auto convertLayerType =
122 {
123 switch( func )
124 {
141 default:
143 }
144 };
145
146 for( const PADS_IO::LAYER_INFO& padsInfo : aPadsLayers )
147 {
149 info.padsLayerNum = padsInfo.number;
150 info.name = padsInfo.name;
151
152 if( padsInfo.layer_type != PADS_IO::PADS_LAYER_FUNCTION::UNKNOWN
153 && padsInfo.layer_type != PADS_IO::PADS_LAYER_FUNCTION::UNASSIGNED )
154 {
155 info.type = convertLayerType( padsInfo.layer_type );
156
157 std::string lowerName = padsInfo.name;
158 std::transform( lowerName.begin(), lowerName.end(), lowerName.begin(),
159 []( unsigned char c )
160 {
161 return std::tolower( c );
162 } );
163
164 bool isBottom = lowerName.find( "bottom" ) != std::string::npos
165 || lowerName.find( "bot" ) != std::string::npos;
166
167 if( info.type == PADS_LAYER_TYPE::SOLDERMASK_TOP && isBottom )
169 else if( info.type == PADS_LAYER_TYPE::PASTE_TOP && isBottom )
171 else if( info.type == PADS_LAYER_TYPE::SILKSCREEN_TOP && isBottom )
173 else if( info.type == PADS_LAYER_TYPE::ASSEMBLY_TOP && isBottom )
175 else if( info.type == PADS_LAYER_TYPE::COPPER_INNER )
176 {
177 if( padsInfo.number == 1 )
179 else if( padsInfo.number == aPadsLayerCount )
181 }
182 }
183 else
184 {
185 info.type = m_layerMapper.GetLayerType( padsInfo.number );
186
187 if( aResolveUnknownTypeByName && info.type == PADS_LAYER_TYPE::UNKNOWN )
188 {
189 info.type = m_layerMapper.ParseLayerName( padsInfo.name );
190
191 if( info.type == PADS_LAYER_TYPE::UNKNOWN )
193 }
194 }
195
196 info.required = padsInfo.required;
197 m_layerInfos.push_back( info );
198 }
199
200 std::vector<INPUT_LAYER_DESC> inputDescs = m_layerMapper.BuildInputLayerDescriptions( m_layerInfos );
201
202 if( aMappingHandler )
203 m_layerMap = aMappingHandler( inputDescs );
204
205 // SetCopperLayerCount past MAX_CU_LAYERS enables layer ids beyond PCB_LAYER_ID, and BASE_SET
206 // resizes rather than rejecting. KiCad also requires an even count, and a one-layer PADS board
207 // is real, so round up before capping.
208 int requested = std::max( aPadsLayerCount, 2 );
209
210 if( requested % 2 )
211 requested++;
212
213 m_copperLayerCount = std::min( requested, MAX_CU_LAYERS );
214
215 if( aPadsLayerCount > MAX_CU_LAYERS && m_reporter )
216 {
217 // mapInnerCopperLayer collapses everything past the cap onto the last inner layer rather
218 // than discarding it, so say that rather than claiming the layers were dropped
219 m_reporter->Report( wxString::Format( _( "The PADS file declares %d copper layers; KiCad supports "
220 "%d, so the layers past that share the last inner layer." ),
221 aPadsLayerCount, MAX_CU_LAYERS ),
223 }
224
225 m_board->SetCopperLayerCount( m_copperLayerCount );
226}
227
228
230{
231 for( const PADS_LAYER_INFO& info : m_layerInfos )
232 {
233 if( info.padsLayerNum == aPadsLayer )
234 {
235 auto it = m_layerMap.find( PADS_COMMON::ConvertText( info.name ) );
236
237 if( it != m_layerMap.end() && it->second != UNDEFINED_LAYER )
238 return it->second;
239
240 return m_layerMapper.GetAutoMapLayer( aPadsLayer, info.type );
241 }
242 }
243
244 return m_layerMapper.GetAutoMapLayer( aPadsLayer );
245}
246
247
248void PADS_PCB_CONVERTER::BuildStackup( const std::vector<PADS_IO::LAYER_INFO>& aPadsLayers )
249{
250 std::vector<const PADS_IO::LAYER_INFO*> copperLayerInfos;
251
252 for( const PADS_IO::LAYER_INFO& li : aPadsLayers )
253 {
254 if( li.is_copper )
255 copperLayerInfos.push_back( &li );
256 }
257
258 bool hasStackupData = false;
259
260 for( const PADS_IO::LAYER_INFO* li : copperLayerInfos )
261 {
262 if( li->layer_thickness > 0.0 || li->dielectric_constant > 0.0 )
263 {
264 hasStackupData = true;
265 break;
266 }
267 }
268
269 if( !hasStackupData )
270 return;
271
272 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
273 BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
274
275 stackup.RemoveAll();
277
278 std::map<PCB_LAYER_ID, const PADS_IO::LAYER_INFO*> copperInfoMap;
279
280 for( const PADS_IO::LAYER_INFO* li : copperLayerInfos )
281 {
282 PCB_LAYER_ID kicadLayer = GetMappedLayer( li->number );
283
284 if( kicadLayer != UNDEFINED_LAYER )
285 copperInfoMap[kicadLayer] = li;
286 }
287
288 // A dielectric item carries the thickness and permittivity of the copper layer above it.
289 const PADS_IO::LAYER_INFO* prevCopperInfo = nullptr;
290
291 for( BOARD_STACKUP_ITEM* item : stackup.GetList() )
292 {
293 if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_COPPER )
294 {
295 auto it = copperInfoMap.find( item->GetBrdLayerId() );
296
297 if( it != copperInfoMap.end() )
298 {
299 prevCopperInfo = it->second;
300
301 if( it->second->copper_thickness > 0.0 )
302 item->SetThickness( ScaleSize( it->second->copper_thickness ) );
303 }
304 }
305 else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_DIELECTRIC )
306 {
307 if( prevCopperInfo )
308 {
309 if( prevCopperInfo->layer_thickness > 0.0 )
310 item->SetThickness( ScaleSize( prevCopperInfo->layer_thickness ) );
311
312 if( prevCopperInfo->dielectric_constant > 0.0 )
313 item->SetEpsilonR( prevCopperInfo->dielectric_constant );
314 }
315 }
316 else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SILKSCREEN )
317 {
318 item->SetColor( wxT( "White" ) );
319 }
320 else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SOLDERMASK )
321 {
322 item->SetColor( wxT( "Green" ) );
323 }
324 }
325
327 bds.m_HasStackup = true;
328}
329
330
331void PADS_PCB_CONVERTER::EnsureNet( const std::string& aNetName )
332{
333 if( aNetName.empty() )
334 return;
335
336 wxString wxName = PADS_COMMON::ConvertInvertedNetName( aNetName );
337
338 if( m_board->FindNet( wxName ) == nullptr )
339 {
340 NETINFO_ITEM* net = new NETINFO_ITEM( m_board, wxName, static_cast<int>( m_board->GetNetCount() ) + 1 );
341 m_board->Add( net );
342 }
343}
344
345
346void PADS_PCB_CONVERTER::AppendArcPoints( SHAPE_LINE_CHAIN& aChain, const std::vector<PADS_IO::ARC_POINT>& aPts ) const
347{
348 if( aPts.empty() )
349 return;
350
351 // Single full-circle entry becomes a 36-segment polygon
352 if( aPts.size() == 1 && aPts[0].is_arc && std::abs( aPts[0].arc.delta_angle ) >= 359.0 )
353 {
354 VECTOR2I center = ScalePoint( aPts[0].arc.cx, aPts[0].arc.cy );
355 int radius = ScaleSize( aPts[0].arc.radius );
356
357 constexpr int NUM_SEGS = 36;
358
359 for( int i = 0; i < NUM_SEGS; i++ )
360 {
361 double angle = 2.0 * M_PI * i / NUM_SEGS;
362 aChain.Append( center.x + KiROUND( radius * cos( angle ) ),
363 center.y + KiROUND( radius * sin( angle ) ) );
364 }
365
366 return;
367 }
368
369 aChain.Append( ScalePoint( aPts[0].x, aPts[0].y ) );
370
371 for( size_t i = 1; i < aPts.size(); i++ )
372 {
373 const PADS_IO::ARC_POINT& pt = aPts[i];
374
375 if( pt.is_arc )
376 {
377 SHAPE_ARC arc = MakeMidpointArc( aPts[i - 1], pt, 0 );
378 const SHAPE_LINE_CHAIN arcPoly = arc.ConvertToPolyline();
379
380 for( int j = 1; j < arcPoly.PointCount(); j++ )
381 aChain.Append( arcPoly.CPoint( j ).x, arcPoly.CPoint( j ).y );
382 }
383 else
384 {
385 aChain.Append( ScalePoint( pt.x, pt.y ) );
386 }
387 }
388}
389
390
392 int aWidth ) const
393{
394 VECTOR2I start = ScalePoint( aPrev.x, aPrev.y );
395 VECTOR2I end = ScalePoint( aCurr.x, aCurr.y );
396
397 double midX, midY;
398
399 if( aCurr.arc.radius == 0.0 )
400 {
401 // Route arcs specify only CW/CCW direction without explicit geometry.
402 // They are semicircles between the two endpoints. Compute the midpoint
403 // on the perpendicular bisector of the chord, at distance radius from
404 // the chord center (where radius = half the chord length).
405 double dx = aCurr.x - aPrev.x;
406 double dy = aCurr.y - aPrev.y;
407
408 if( aCurr.arc.delta_angle < 0 )
409 {
410 // CW: arc bulges to the left of the start-to-end direction
411 midX = ( aPrev.x + aCurr.x ) / 2.0 - dy / 2.0;
412 midY = ( aPrev.y + aCurr.y ) / 2.0 + dx / 2.0;
413 }
414 else
415 {
416 // CCW: arc bulges to the right of the start-to-end direction
417 midX = ( aPrev.x + aCurr.x ) / 2.0 + dy / 2.0;
418 midY = ( aPrev.y + aCurr.y ) / 2.0 - dx / 2.0;
419 }
420 }
421 else
422 {
423 // Full arc with explicit center and radius (pours, decals, board outlines).
424 // Compute the arc midpoint in PADS coordinate space (before the Y-axis
425 // flip in ScaleCoord) so the 3-point constructor gets the correct winding.
426 double startAngleRad = atan2( aPrev.y - aCurr.arc.cy, aPrev.x - aCurr.arc.cx );
427 double midAngleRad = startAngleRad + ( aCurr.arc.delta_angle * M_PI / 180.0 ) / 2.0;
428
429 midX = aCurr.arc.cx + aCurr.arc.radius * cos( midAngleRad );
430 midY = aCurr.arc.cy + aCurr.arc.radius * sin( midAngleRad );
431 }
432
433 return SHAPE_ARC( start, ScalePoint( midX, midY ), end, aWidth );
434}
435
436
437void PADS_PCB_CONVERTER::LoadTexts( const std::vector<PADS_IO::TEXT>& aTexts )
438{
439 for( const PADS_IO::TEXT& padsText : aTexts )
440 {
441 PCB_LAYER_ID textLayer = GetMappedLayer( padsText.layer );
442
443 if( textLayer == UNDEFINED_LAYER )
444 {
445 if( m_reporter )
446 {
447 m_reporter->Report( wxString::Format( _( "Text on unmapped layer %d assigned to Comments layer" ),
448 padsText.layer ),
450 }
451
452 textLayer = Cmts_User;
453 }
454
455 PCB_TEXT* text = new PCB_TEXT( m_board );
456 text->SetText( PADS_COMMON::ConvertText( padsText.content ) );
457
458 // PADS text cell height includes internal leading and descender space.
459 // Scale factors calibrated to match PADS rendered character dimensions.
460 int scaledSize = ScaleSize( padsText.height );
461 int charHeight = static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextHeightScale );
462 int charWidth = static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextWidthScale );
463 text->SetTextSize( VECTOR2I( charWidth, charHeight ) );
464
465 if( padsText.width > 0 )
466 text->SetTextThickness( ScaleSize( padsText.width ) );
467
468 EDA_ANGLE textAngle( padsText.rotation, DEGREES_T );
469 text->SetTextAngle( textAngle );
470
471 // PADS text anchor differs from KiCad by a small offset along the
472 // reading direction. Shift left (toward text start) to compensate.
473 VECTOR2I pos = ScalePoint( padsText.location.x, padsText.location.y );
474 VECTOR2I textShift( -ADVANCED_CFG::GetCfg().m_PadsTextAnchorOffsetNm, 0 );
475 RotatePoint( textShift, textAngle );
476 text->SetPosition( pos + textShift );
477
478 if( padsText.hjust == "LEFT" )
479 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
480 else if( padsText.hjust == "RIGHT" )
481 text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
482 else
483 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
484
485 if( padsText.vjust == "UP" )
486 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
487 else if( padsText.vjust == "DOWN" )
488 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
489 else
490 text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
491
492 text->SetKeepUpright( false );
493 text->SetLayer( textLayer );
494
495 // Honor the PADS back-side mirror flag.
496 text->SetMirrored( padsText.mirrored );
497
498 m_board->Add( text );
499 }
500}
501
502
503void PADS_PCB_CONVERTER::LoadKeepouts( const std::vector<PADS_IO::KEEPOUT>& aKeepouts )
504{
505 int keepoutIndex = 0;
506
507 for( const PADS_IO::KEEPOUT& ko : aKeepouts )
508 {
509 if( ko.outline.size() < 3 )
510 continue;
511
512 ZONE* zone = new ZONE( m_board );
513 zone->SetIsRuleArea( true );
514
515 if( ko.layers.empty() )
516 {
517 zone->SetLayerSet( LSET::AllCuMask() );
518 }
519 else if( ko.layers.size() == 1 )
520 {
521 PCB_LAYER_ID koLayer = GetMappedLayer( ko.layers[0] );
522
523 if( koLayer == UNDEFINED_LAYER )
524 {
525 if( m_reporter )
526 {
527 m_reporter->Report( wxString::Format( _( "Skipping keepout on unmapped layer %d" ), ko.layers[0] ),
529 }
530
531 delete zone;
532 continue;
533 }
534
535 zone->SetLayer( koLayer );
536 }
537 else
538 {
539 LSET layerSet;
540
541 for( int layer : ko.layers )
542 {
543 PCB_LAYER_ID mappedLayer = GetMappedLayer( layer );
544
545 if( mappedLayer != UNDEFINED_LAYER )
546 layerSet.set( mappedLayer );
547 }
548
549 if( layerSet.none() )
550 {
551 if( m_reporter )
552 m_reporter->Report( _( "Skipping keepout with no valid layers" ), RPT_SEVERITY_WARNING );
553
554 delete zone;
555 continue;
556 }
557
558 zone->SetLayerSet( layerSet );
559 }
560
561 zone->SetDoNotAllowTracks( ko.no_traces );
562 zone->SetDoNotAllowVias( ko.no_vias );
563 zone->SetDoNotAllowZoneFills( ko.no_copper );
564 zone->SetDoNotAllowFootprints( ko.no_components );
565 zone->SetDoNotAllowPads( false );
566
567 wxString typeName;
568
569 switch( ko.type )
570 {
571 case PADS_IO::KEEPOUT_TYPE::ALL: typeName = wxT( "Keepout" ); break;
572 case PADS_IO::KEEPOUT_TYPE::ROUTE: typeName = wxT( "RouteKeepout" ); break;
573 case PADS_IO::KEEPOUT_TYPE::VIA: typeName = wxT( "ViaKeepout" ); break;
574 case PADS_IO::KEEPOUT_TYPE::COPPER: typeName = wxT( "CopperKeepout" ); break;
575 case PADS_IO::KEEPOUT_TYPE::PLACEMENT: typeName = wxT( "PlacementKeepout" ); break;
576 }
577
578 zone->SetZoneName( wxString::Format( wxT( "%s_%d" ), typeName, ++keepoutIndex ) );
579
580 SHAPE_LINE_CHAIN koChain;
581 AppendArcPoints( koChain, ko.outline );
582
583 // Close the outline if first and last points don't match
584 if( ko.outline.size() > 2 )
585 {
586 const PADS_IO::ARC_POINT& first = ko.outline.front();
587 const PADS_IO::ARC_POINT& last = ko.outline.back();
588
589 if( std::abs( first.x - last.x ) > 0.001 || std::abs( first.y - last.y ) > 0.001 )
590 koChain.Append( ScalePoint( first.x, first.y ) );
591 }
592
593 koChain.SetClosed( true );
594 zone->Outline()->AddOutline( koChain );
596
597 m_board->Add( zone );
598 }
599}
600
601
602void PADS_PCB_CONVERTER::LoadDimensions( const std::vector<PADS_IO::DIMENSION>& aDimensions )
603{
604 for( const PADS_IO::DIMENSION& dim : aDimensions )
605 {
606 if( dim.points.size() < 2 )
607 continue;
608
610
611 VECTOR2I start = ScalePoint( dim.points[0].x, dim.points[0].y );
612 VECTOR2I end = ScalePoint( dim.points[1].x, dim.points[1].y );
613
614 // PADS horizontal/vertical dimensions measure only the X or Y projection.
615 // PCB_DIM_ALIGNED measures along the start-to-end direction, so if the base
616 // points differ on the non-measured axis the line becomes skewed.
617 // Project the end point onto the measurement axis.
618 if( dim.is_horizontal )
619 end.y = start.y;
620 else
621 end.x = start.x;
622
623 dimension->SetStart( start );
624 dimension->SetEnd( end );
625
626 // The crossbar_pos is the absolute coordinate of the crossbar, so the height is its
627 // offset from the start point.
628 if( dim.is_horizontal )
629 dimension->SetHeight( -ScaleSize( dim.crossbar_pos - dim.points[0].y ) );
630 else
631 dimension->SetHeight( ScaleSize( dim.crossbar_pos - dim.points[0].x ) );
632
633 PCB_LAYER_ID dimLayer = GetMappedLayer( dim.layer );
634
635 if( dimLayer == UNDEFINED_LAYER || IsCopperLayer( dimLayer ) )
636 dimLayer = Cmts_User;
637
638 dimension->SetLayer( dimLayer );
639
640 // PADS text_width is stroke thickness, not character width.
641 // Calculate character dimensions from height.
642 if( dim.text_height > 0 )
643 {
644 int scaledSize = ScaleSize( dim.text_height );
645 int charHeight = static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextHeightScale );
646 int charWidth = static_cast<int>( scaledSize * ADVANCED_CFG::GetCfg().m_PadsPcbTextWidthScale );
647 dimension->SetTextSize( VECTOR2I( charWidth, charHeight ) );
648
649 if( dim.text_width > 0 )
650 dimension->SetTextThickness( ScaleSize( dim.text_width ) );
651 }
652
653 if( !dim.text.empty() )
654 {
655 dimension->SetOverrideTextEnabled( true );
656 dimension->SetOverrideText( PADS_COMMON::ConvertText( dim.text ) );
657 }
658
659 // Both dialects share this converter, so a file-unit constant would be a 3 nm hairline in
660 // the binary reader's BASIC mode
661 dimension->SetLineThickness( pcbIUScale.mmToIU( 0.127 ) );
662
663 if( dim.rotation != 0.0 )
664 dimension->SetTextAngle( EDA_ANGLE( dim.rotation, DEGREES_T ) );
665
666 dimension->Update();
667 m_board->Add( dimension );
668 }
669}
670
671
672void PADS_PCB_CONVERTER::ApplyPourSettings( ZONE* aZone, const PADS_IO::POUR& aPour, int aMaxPriority,
673 const PADS_IO::PARAMETERS& aParams )
674{
675 if( aPour.is_cutout )
676 {
677 aZone->SetIsRuleArea( true );
678 aZone->SetDoNotAllowZoneFills( true );
679 aZone->SetDoNotAllowTracks( false );
680 aZone->SetDoNotAllowVias( false );
681 aZone->SetDoNotAllowPads( false );
682 aZone->SetDoNotAllowFootprints( false );
683 aZone->SetZoneName( wxString::Format( wxT( "Cutout_%s" ),
685
686 return;
687 }
688
690
691 if( net )
692 aZone->SetNet( net );
693
694 // PADS fills the lowest priority number on top, KiCad the highest.
695 aZone->SetAssignedPriority( aMaxPriority - aPour.priority + 1 );
696 aZone->SetMinThickness( ScaleSize( aPour.width ) );
697
700
701 // A PADS pour connects solid by default; a pad asks for spoke relief through its own RT/ST
702 // padstack rows, which both importers apply as a per-pad override.
704}
705
706
707void PADS_PCB_CONVERTER::WriteDiffPairRules( const wxString& aFileName,
708 const std::vector<PADS_IO::DIFF_PAIR_DEF>& aDiffPairs )
709{
710 if( aDiffPairs.empty() )
711 return;
712
713 // The expression tokenizer reads \ before the closing quote as an escaped quote and has no
714 // rule that yields a trailing backslash, so only that one shape has no faithful encoding
715 auto isSerializable =
716 []( const wxString& aName )
717 {
718 return !aName.EndsWith( wxS( "\\" ) );
719 };
720
721 // The name is read back through two layers, so escape for the inner one first. The expression
722 // tokenizer takes \' inside a quoted operand and the s-expression lexer takes \\ \" \r \n
723 auto escapeOperand =
724 []( const wxString& aName )
725 {
726 wxString out = aName;
727
728 out.Replace( wxS( "'" ), wxS( "\\'" ) );
729 out.Replace( wxS( "\\" ), wxS( "\\\\" ) );
730 out.Replace( wxS( "\"" ), wxS( "\\\"" ) );
731 out.Replace( wxS( "\r" ), wxS( "\\r" ) );
732 out.Replace( wxS( "\n" ), wxS( "\\n" ) );
733
734 return out;
735 };
736
737 auto escapeSymbol =
738 []( const wxString& aName )
739 {
740 wxString out = aName;
741
742 out.Replace( wxS( "\\" ), wxS( "\\\\" ) );
743 out.Replace( wxS( "\"" ), wxS( "\\\"" ) );
744 out.Replace( wxS( "\r" ), wxS( "\\r" ) );
745 out.Replace( wxS( "\n" ), wxS( "\\n" ) );
746
747 return out;
748 };
749
750 wxString customRules = wxT( "(version 2)\n" );
751 bool hasAnyRule = false;
752
753 for( const PADS_IO::DIFF_PAIR_DEF& dp : aDiffPairs )
754 {
755 if( dp.name.empty() || dp.gap <= 0 || dp.positive_net.empty() || dp.negative_net.empty() )
756 continue;
757
758 wxString ruleName = wxString::Format( wxT( "DiffPair_%s" ), PADS_COMMON::ConvertText( dp.name ) );
759 wxString posNet = PADS_COMMON::ConvertInvertedNetName( dp.positive_net );
760 wxString negNet = PADS_COMMON::ConvertInvertedNetName( dp.negative_net );
761
762 // The rule name is followed by _gap, so only the net names can end the quoted operand
763 if( !isSerializable( posNet ) || !isSerializable( negNet ) )
764 {
765 if( m_reporter )
766 {
767 m_reporter->Report( wxString::Format( _( "Skipped design rule for differential pair "
768 "'%s'; a net name ends with a backslash." ),
769 ruleName ),
771 }
772
773 continue;
774 }
775
776 double gapMm = dp.gap * m_scaleFactor / PADS_UNIT_CONVERTER::MM_TO_NM;
777 wxString gapStr = wxString::FromUTF8( FormatDouble2Str( gapMm ) ) + wxT( "mm" );
778
779 customRules += wxString::Format( wxT( "\n(rule \"%s_gap\"\n" )
780 wxT( " (condition \"A.NetName == '%s' && B.NetName == '%s'\")\n" )
781 wxT( " (constraint clearance (min %s)))\n" ),
782 escapeSymbol( ruleName ), escapeOperand( posNet ),
783 escapeOperand( negNet ), gapStr );
784 hasAnyRule = true;
785 }
786
787 // A file holding only the version header is clutter, so write nothing unless a rule was emitted
788 if( !hasAnyRule )
789 return;
790
791 wxFileName fn( aFileName );
792 fn.SetExt( wxT( "kicad_dru" ) );
793
794 // An import must not destroy rules the user already has. Creating exclusively both refuses an
795 // existing file and closes the race a FileExists test would leave open
796 wxFile rulesFile( fn.GetFullPath(), wxFile::write_excl );
797
798 if( !rulesFile.IsOpened() )
799 {
800 if( m_reporter )
801 {
802 wxString msg = fn.FileExists() ? _( "Design rules for the imported differential pairs were not "
803 "written; '%s' already exists." )
804 : _( "Could not write design rules to '%s'." );
805
806 m_reporter->Report( wxString::Format( msg, fn.GetFullPath() ), RPT_SEVERITY_WARNING );
807 }
808
809 return;
810 }
811
812 bool written = rulesFile.Write( customRules );
813
814 if( !rulesFile.Close() )
815 written = false;
816
817 // A partial sidecar will not parse, and because the file is created exclusively it would also
818 // block the next import from writing a good one
819 if( !written )
820 {
821 wxRemoveFile( fn.GetFullPath() );
822
823 if( m_reporter )
824 {
825 m_reporter->Report( wxString::Format( _( "Could not write design rules to '%s'." ),
826 fn.GetFullPath() ),
828 }
829 }
830}
831
832
834{
835 if( !m_reporter )
836 return;
837
838 size_t trackCount = 0;
839 size_t viaCount = 0;
840
841 for( PCB_TRACK* track : m_board->Tracks() )
842 {
843 if( track->Type() == PCB_VIA_T )
844 viaCount++;
845 else
846 trackCount++;
847 }
848
849 m_reporter->Report( wxString::Format( _( "Imported %zu footprints, %d nets, %zu tracks, %zu vias, %zu zones" ),
850 m_board->Footprints().size(), m_board->GetNetCount(), trackCount, viaCount,
851 m_board->Zones().size() ),
853}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
@ 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:995
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:126
Container for design settings for a BOARD object.
BOARD_STACKUP & GetStackupDescriptor()
void SetBoardThickness(int aThickness)
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:374
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:409
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:595
Handle the data for a net.
Definition netinfo.h:50
PADS_LAYER_MAPPER m_layerMapper
void AppendArcPoints(SHAPE_LINE_CHAIN &aChain, const std::vector< PADS_IO::ARC_POINT > &aPts) const
Append PADS vertices to a chain, converting arc entries to polylines and expanding a lone full-circle...
VECTOR2I ScalePoint(double aX, double aY) const
PADS_UNIT_CONVERTER m_unitConverter
void WriteDiffPairRules(const wxString &aFileName, const std::vector< PADS_IO::DIFF_PAIR_DEF > &aDiffPairs)
Write a .kicad_dru beside aFileName carrying one clearance rule per differential pair with a gap.
bool SetOriginFromOutlines(const std::vector< PADS_IO::POLYLINE > &aOutlines)
Move the origin to the center of the board outline bounding box, which places the imported board near...
PCB_LAYER_ID GetMappedLayer(int aPadsLayer) const
std::map< wxString, PCB_LAYER_ID > m_layerMap
PADS_PCB_CONVERTER(BOARD *aBoard, REPORTER *aReporter)
void SetupLayers(const std::vector< PADS_IO::LAYER_INFO > &aPadsLayers, int aPadsLayerCount, const LAYER_MAPPING_HANDLER &aMappingHandler, bool aResolveUnknownTypeByName)
Resolve the PADS layer table to KiCad layers and apply the copper layer count to the board.
void EnsureNet(const std::string &aNetName)
SHAPE_ARC MakeMidpointArc(const PADS_IO::ARC_POINT &aPrev, const PADS_IO::ARC_POINT &aCurr, int aWidth) const
Build a SHAPE_ARC from two consecutive PADS points.
void LoadTexts(const std::vector< PADS_IO::TEXT > &aTexts)
void LoadDimensions(const std::vector< PADS_IO::DIMENSION > &aDimensions)
void ApplyPourSettings(ZONE *aZone, const PADS_IO::POUR &aPour, int aMaxPriority, const PADS_IO::PARAMETERS &aParams)
Apply the cutout or fill settings of aPour to a zone whose outline the caller has already built.
void LoadKeepouts(const std::vector< PADS_IO::KEEPOUT > &aKeepouts)
void SetOrigin(double aX, double aY)
std::vector< PADS_LAYER_INFO > m_layerInfos
int ScaleSize(double aVal) const
int ScaleCoord(double aVal, bool aIsX) const
void BuildStackup(const std::vector< PADS_IO::LAYER_INFO > &aPadsLayers)
Build the board stackup from the PADS physical layer data, if that data carries real thicknesses or d...
static constexpr double MM_TO_NM
void Update()
Update the dimension's cached text and geometry.
virtual void SetEnd(const VECTOR2I &aPoint)
virtual void SetStart(const VECTOR2I &aPoint)
void SetOverrideTextEnabled(bool aOverride)
void SetLineThickness(int aWidth)
void SetOverrideText(const wxString &aValue)
For better understanding of the points that make a dimension:
void SetHeight(int aHeight)
Set the distance from the feature points to the crossbar line.
void SetTextThickness(int aWidth) override
The TextThickness is that set by the user.
Definition pcb_text.cpp:512
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true) override
Definition pcb_text.cpp:484
void SetTextAngle(const EDA_ANGLE &aAngle) override
Definition pcb_text.cpp:569
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
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.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
Handle a list of polygons defining a copper zone.
Definition zone.h:70
void SetDoNotAllowPads(bool aEnable)
Definition zone.h:826
void SetMinThickness(int aMinThickness)
Definition zone.h:316
void SetThermalReliefSpokeWidth(int aThermalReliefSpokeWidth)
Definition zone.h:251
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:641
SHAPE_POLY_SET * Outline()
Definition zone.h:418
void SetIsRuleArea(bool aEnable)
Definition zone.h:808
void SetDoNotAllowTracks(bool aEnable)
Definition zone.h:825
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:666
void SetDoNotAllowVias(bool aEnable)
Definition zone.h:824
void SetNet(NETINFO_ITEM *aNetInfo) override
Override that drops aNetInfo when this zone is in copper-thieving fill mode.
Definition zone.cpp:632
void SetThermalReliefGap(int aThermalReliefGap)
Definition zone.h:240
void SetDoNotAllowFootprints(bool aEnable)
Definition zone.h:827
void SetDoNotAllowZoneFills(bool aEnable)
Definition zone.h:823
void SetAssignedPriority(unsigned aPriority)
Definition zone.h:117
void SetPadConnection(ZONE_CONNECTION aPadConnection)
Definition zone.h:313
void SetZoneName(const wxString &aName)
Definition zone.h:161
static int GetDefaultHatchPitch()
Definition zone.cpp:1617
void SetBorderDisplayStyle(ZONE_BORDER_DISPLAY_STYLE aBorderHatchStyle, int aBorderHatchPitch, bool aRebuilBorderHatch)
Set all hatch parameters for the zone.
Definition zone.cpp:1540
#define _(s)
@ DEGREES_T
Definition eda_angle.h:31
double m_PadsPcbTextWidthScale
PADS text width scale factor for PCB imports.
double m_PadsPcbTextHeightScale
PADS text height scale factor for PCB imports.
#define MAX_CU_LAYERS
Definition layer_ids.h:172
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Cmts_User
Definition layer_ids.h:104
@ UNDEFINED_LAYER
Definition layer_ids.h:57
int PadsScaleCoord(double aVal, bool aIsX, double aOriginX, double aOriginY, double aScaleFactor)
Map a PADS board coordinate to a KiCad internal coordinate.
wxString ConvertText(const std::string &aText)
Decode text from a PADS file, which uses an 8-bit codepage rather than UTF-8.
wxString ConvertInvertedNetName(const std::string &aNetName)
Convert a PADS net name to KiCad notation.
PADS_LAYER_FUNCTION
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
PADS_LAYER_TYPE
Functional categories of PADS layers, independent of specific layer numbers.
std::function< std::map< wxString, PCB_LAYER_ID >(const std::vector< INPUT_LAYER_DESC > &)> LAYER_MAPPING_HANDLER
Pointer to a function that takes a map of source and KiCad layers and returns a re-mapped version.
@ 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...
A polyline point that may instead be an arc segment.
Definition pads_parser.h:64
ARC arc
Only valid when is_arc is true.
Definition pads_parser.h:68
double radius
Definition pads_parser.h:53
double delta_angle
Definition pads_parser.h:55
double layer_thickness
Dielectric, BASIC units.
double dielectric_constant
Er.
double thermal_line_width
THERLINEWID, THT.
double thermal_min_clearance
STMINCLEAR.
A polyline that may contain arc segments, used for board outlines and graphics.
bool is_cutout
POCUT piece.
std::string net_name
std::string owner_pour
Parent pour, 7th header field.
VECTOR2I center
int radius
VECTOR2I end
@ 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
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:225
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:94
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
@ FULL
pads are covered by copper
Definition zones.h:47