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