Pcbnew: add footprint keepouts to Specctra DSN exporter.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/7684
This commit is contained in:
Wayne Stambaugh 2021-03-10 07:17:25 -05:00
parent df2154a87a
commit d4231d8e9f
1 changed files with 152 additions and 113 deletions

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Copyright (C) 2007-2015 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com> * Copyright (C) 2007-2015 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 2015-2020 KiCad Developers, see AUTHORS.txt for contributors. * Copyright (C) 2015-2021 KiCad Developers, see AUTHORS.txt for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -64,7 +64,8 @@ using namespace DSN;
// that can create issues. // that can create issues.
// Especially Freerouter does not handle them very well: // Especially Freerouter does not handle them very well:
// - too complex shapes are not accepted, especially shapes with holes (dsn files are not loaded). // - too complex shapes are not accepted, especially shapes with holes (dsn files are not loaded).
// - and Freerouter actually uses something like a convex hull of the shape (that works not very well). // - and Freerouter actually uses something like a convex hull of the shape (that works not very
// well).
// I am guessing non convex polygons with holes linked could create issues with any Router. // I am guessing non convex polygons with holes linked could create issues with any Router.
#define EXPORT_CUSTOM_PADS_CONVEX_HULL #define EXPORT_CUSTOM_PADS_CONVEX_HULL
@ -95,7 +96,7 @@ bool PCB_EDIT_FRAME::ExportSpecctraFile( const wxString& aFullFilename )
// DSN Images (=KiCad FOOTPRINTs and PADs) must be presented from the top view. So we // DSN Images (=KiCad FOOTPRINTs and PADs) must be presented from the top view. So we
// temporarily flip any footprints which are on the back side of the board to the front, // temporarily flip any footprints which are on the back side of the board to the front,
// and record this in the FOOTPRINT's flag field. // and record this in the FOOTPRINT's flag field.
db.FlipFOOTPRINTs( GetBoard()); db.FlipFOOTPRINTs( GetBoard() );
try try
{ {
@ -115,7 +116,7 @@ bool PCB_EDIT_FRAME::ExportSpecctraFile( const wxString& aFullFilename )
} }
// done assuredly, even if an exception was thrown and caught. // done assuredly, even if an exception was thrown and caught.
db.RevertFOOTPRINTs( GetBoard()); db.RevertFOOTPRINTs( GetBoard() );
// The two calls below to FOOTPRINT::Flip(), both set the // The two calls below to FOOTPRINT::Flip(), both set the
// modified flag, yet their actions cancel each other out, so it should // modified flag, yet their actions cancel each other out, so it should
@ -147,8 +148,7 @@ const KICAD_T SPECCTRA_DB::scanPADs[] = { PCB_PAD_T, EOT };
/** /**
* Function scale * Convert a distance from Pcbnew internal units to the reported Specctra DSN units
* converts a distance from PCBNEW internal units to the reported specctra dsn units
* in floating point format. * in floating point format.
*/ */
static inline double scale( int kicadDist ) static inline double scale( int kicadDist )
@ -158,7 +158,7 @@ static inline double scale( int kicadDist )
} }
// / Convert integer internal units to float um ///< Convert integer internal units to float um
static inline double IU2um( int kicadDist ) static inline double IU2um( int kicadDist )
{ {
return kicadDist * (1000.0 / IU_PER_MM); return kicadDist * (1000.0 / IU_PER_MM);
@ -178,10 +178,10 @@ static inline double mapY( int y )
/** /**
* Function mapPt * Convert a KiCad point into a DSN file point.
* converts a KiCad point into a DSN file point. Kicad's BOARD coordinates *
* are in nanometers (called Internal Units or IU)and we are exporting in units * Kicad's #BOARD coordinates are in nanometers (called Internal Units or IU) and we are
* of mils, so we have to scale them. * exporting in units of mils, so we have to scale them.
*/ */
static POINT mapPt( const wxPoint& pt ) static POINT mapPt( const wxPoint& pt )
{ {
@ -195,13 +195,11 @@ static POINT mapPt( const wxPoint& pt )
/** /**
* Function isRoundKeepout * Decide if the pad is a copper-less through hole which needs to be made into a round keepout.
* decides if the pad is a copper-less through hole which needs to be made into
* a round keepout.
*/ */
static bool isRoundKeepout( PAD* aPad ) static bool isRoundKeepout( PAD* aPad )
{ {
if( aPad->GetShape()==PAD_SHAPE_CIRCLE ) if( aPad->GetShape() == PAD_SHAPE_CIRCLE )
{ {
if( aPad->GetDrillSize().x >= aPad->GetSize().x ) if( aPad->GetDrillSize().x >= aPad->GetSize().x )
return true; return true;
@ -215,8 +213,7 @@ static bool isRoundKeepout( PAD* aPad )
/** /**
* Function makePath * Create a PATH element with a single straight line, a pair of vertices.
* creates a PATH element with a single straight line, a pair of vertices.
*/ */
static PATH* makePath( const POINT& aStart, const POINT& aEnd, const std::string& aLayerName ) static PATH* makePath( const POINT& aStart, const POINT& aEnd, const std::string& aLayerName )
{ {
@ -568,7 +565,8 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad )
for( unsigned idx = 0; idx < polygonal_shape.size(); idx++ ) for( unsigned idx = 0; idx < polygonal_shape.size(); idx++ )
{ {
POINT corner( scale( polygonal_shape[idx].x ), scale( -polygonal_shape[idx].y ) ); POINT corner( scale( polygonal_shape[idx].x ),
scale( -polygonal_shape[idx].y ) );
corner += dsnOffset; corner += dsnOffset;
polygon->AppendPoint( corner ); polygon->AppendPoint( corner );
} }
@ -578,9 +576,9 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad )
MD5_HASH hash = pad_shape.GetHash(); MD5_HASH hash = pad_shape.GetHash();
EDA_RECT rect = aPad->GetBoundingBox(); EDA_RECT rect = aPad->GetBoundingBox();
snprintf( name, sizeof(name), "Cust%sPad_%.6gx%.6g_%.6gx_%.6g_%d_um_%s", snprintf( name, sizeof(name), "Cust%sPad_%.6gx%.6g_%.6gx_%.6g_%d_um_%s",
uniqifier.c_str(), IU2um( aPad->GetSize().x ), IU2um( aPad->GetSize().y ), uniqifier.c_str(), IU2um( aPad->GetSize().x ), IU2um( aPad->GetSize().y ),
IU2um( rect.GetWidth() ), IU2um( rect.GetHeight() ), IU2um( rect.GetWidth() ), IU2um( rect.GetHeight() ),
(int)polygonal_shape.size(), hash.Format( true ).c_str() ); (int)polygonal_shape.size(), hash.Format( true ).c_str() );
name[ sizeof(name)-1 ] = 0; name[ sizeof(name)-1 ] = 0;
padstack->SetPadstackId( name ); padstack->SetPadstackId( name );
@ -606,7 +604,7 @@ IMAGE* SPECCTRA_DB::makeIMAGE( BOARD* aBoard, FOOTPRINT* aFootprint )
// get all the FOOTPRINT's pads. // get all the FOOTPRINT's pads.
fpItems.Collect( aFootprint, scanPADs ); fpItems.Collect( aFootprint, scanPADs );
IMAGE* image = new IMAGE(0); IMAGE* image = new IMAGE( 0 );
image->image_id = aFootprint->GetFPID().Format().c_str(); image->image_id = aFootprint->GetFPID().Format().c_str();
@ -668,7 +666,7 @@ IMAGE* SPECCTRA_DB::makeIMAGE( BOARD* aBoard, FOOTPRINT* aFootprint )
padName = pad->GetName(); padName = pad->GetName();
pin->pin_id = TO_UTF8( padName ); pin->pin_id = TO_UTF8( padName );
if( padName!=wxEmptyString && pinmap.find( padName )==pinmap.end() ) if( padName != wxEmptyString && pinmap.find( padName ) == pinmap.end() )
{ {
pinmap[ padName ] = 0; pinmap[ padName ] = 0;
} }
@ -699,7 +697,6 @@ IMAGE* SPECCTRA_DB::makeIMAGE( BOARD* aBoard, FOOTPRINT* aFootprint )
} }
} }
#if 1 // enable image (outline) scopes.
static const KICAD_T scanEDGEs[] = { PCB_FP_SHAPE_T, EOT }; static const KICAD_T scanEDGEs[] = { PCB_FP_SHAPE_T, EOT };
// get all the FOOTPRINT's FP_SHAPEs and convert those to DSN outlines. // get all the FOOTPRINT's FP_SHAPEs and convert those to DSN outlines.
@ -775,14 +772,130 @@ IMAGE* SPECCTRA_DB::makeIMAGE( BOARD* aBoard, FOOTPRINT* aFootprint )
} }
} }
#endif for( FP_ZONE* zone : aFootprint->Zones() )
{
if( !zone->GetIsRuleArea() )
continue;
// IMAGE object coordinates are relative to the IMAGE not absolute board coordinates.
ZONE untransformedZone( *zone );
double angle = -aFootprint->GetOrientation();
NORMALIZE_ANGLE_POS( angle );
untransformedZone.Rotate( aFootprint->GetPosition(), angle );
// keepout areas have a type. types are
// T_place_keepout, T_via_keepout, T_wire_keepout,
// T_bend_keepout, T_elongate_keepout, T_keepout.
// Pcbnew knows only T_keepout, T_via_keepout and T_wire_keepout
DSN_T keepout_type;
if( zone->GetDoNotAllowVias() && zone->GetDoNotAllowTracks() )
keepout_type = T_keepout;
else if( zone->GetDoNotAllowVias() )
keepout_type = T_via_keepout;
else if( zone->GetDoNotAllowTracks() )
keepout_type = T_wire_keepout;
else
keepout_type = T_keepout;
// Now, build keepout polygon on each copper layer where the zone
// keepout is living (keepout zones can live on many copper layers)
const int copperCount = aBoard->GetCopperLayerCount();
for( int layer = 0; layer < copperCount; layer++ )
{
if( layer == copperCount-1 )
layer = B_Cu;
if( !zone->IsOnLayer( PCB_LAYER_ID( layer ) ) )
continue;
KEEPOUT* keepout = new KEEPOUT( m_pcb->structure, keepout_type );
image->keepouts.push_back( keepout );
PATH* mainPolygon = new PATH( keepout, T_polygon );
keepout->SetShape( mainPolygon );
mainPolygon->layer_id = m_layerIds[ m_kicadLayer2pcb[ layer ] ];
// Handle the main outlines
SHAPE_POLY_SET::ITERATOR iterator;
bool is_first_point = true;
wxPoint startpoint;
for( iterator = untransformedZone.IterateWithHoles(); iterator; iterator++ )
{
wxPoint point( iterator->x, iterator->y );
point -= aFootprint->GetPosition();
if( is_first_point )
{
startpoint = point;
is_first_point = false;
}
mainPolygon->AppendPoint( mapPt( point ) );
// this was the end of the main polygon
if( iterator.IsEndContour() )
{
mainPolygon->AppendPoint( mapPt( startpoint ) );
break;
}
}
WINDOW* window = nullptr;
PATH* cutout = nullptr;
bool isStartContour = true;
// handle the cutouts
for( iterator++; iterator; iterator++ )
{
if( isStartContour )
{
is_first_point = true;
window = new WINDOW( keepout );
keepout->AddWindow( window );
cutout = new PATH( window, T_polygon );
window->SetShape( cutout );
cutout->layer_id = m_layerIds[ m_kicadLayer2pcb[ zone->GetLayer() ] ];
}
isStartContour = iterator.IsEndContour();
wxASSERT( window );
wxASSERT( cutout );
wxPoint point( iterator->x, iterator->y );
point -= aFootprint->GetPosition();
if( is_first_point )
{
startpoint = point;
is_first_point = false;
}
cutout->AppendPoint( mapPt( point ) );
// Close the polygon
if( iterator.IsEndContour() )
cutout->AppendPoint( mapPt( startpoint ) );
}
}
}
return image; return image;
} }
PADSTACK* SPECCTRA_DB::makeVia( int aCopperDiameter, int aDrillDiameter, PADSTACK* SPECCTRA_DB::makeVia( int aCopperDiameter, int aDrillDiameter,
int aTopLayer, int aBotLayer ) int aTopLayer, int aBotLayer )
{ {
char name[48]; char name[48];
PADSTACK* padstack = new PADSTACK(); PADSTACK* padstack = new PADSTACK();
@ -802,11 +915,10 @@ PADSTACK* SPECCTRA_DB::makeVia( int aCopperDiameter, int aDrillDiameter,
circle->SetLayerId( m_layerIds[layer].c_str() ); circle->SetLayerId( m_layerIds[layer].c_str() );
} }
snprintf( name, sizeof(name), "Via[%d-%d]_%.6g:%.6g_um", snprintf( name, sizeof( name ), "Via[%d-%d]_%.6g:%.6g_um",
aTopLayer, aBotLayer, dsnDiameter, aTopLayer, aBotLayer, dsnDiameter,
// encode the drill value into the name for later import // encode the drill value into the name for later import
IU2um( aDrillDiameter ) IU2um( aDrillDiameter ) );
);
name[ sizeof(name) - 1 ] = 0; name[ sizeof(name) - 1 ] = 0;
padstack->SetPadstackId( name ); padstack->SetPadstackId( name );
@ -956,12 +1068,9 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
case LT_SIGNAL: layerType = T_signal; break; case LT_SIGNAL: layerType = T_signal; break;
case LT_POWER: layerType = T_power; break; case LT_POWER: layerType = T_power; break;
#if 1 // Freerouter does not support type "mixed", only signal and power. // Freerouter does not support type "mixed", only signal and power.
// Remap "mixed" to "signal". // Remap "mixed" to "signal".
case LT_MIXED: layerType = T_signal; break; case LT_MIXED: layerType = T_signal; break;
#else
case LT_MIXED: layerType = T_mixed; break;
#endif
case LT_JUMPER: layerType = T_jumper; break; case LT_JUMPER: layerType = T_jumper; break;
} }
@ -988,7 +1097,6 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
m_pcb->unit->units = T_um; m_pcb->unit->units = T_um;
m_pcb->resolution->units = T_um; m_pcb->resolution->units = T_um;
m_pcb->resolution->value = 10; // tenths of a um m_pcb->resolution->value = 10; // tenths of a um
// pcb->resolution->value = 1000; // "thousandths of a um" (i.e. "nm")
} }
//-----<boundary_descriptor>------------------------------------------ //-----<boundary_descriptor>------------------------------------------
@ -1002,7 +1110,6 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
fillBOUNDARY( aBoard, boundary ); fillBOUNDARY( aBoard, boundary );
} }
//-----<rules>-------------------------------------------------------- //-----<rules>--------------------------------------------------------
{ {
char rule[80]; char rule[80];
@ -1038,29 +1145,6 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
rules.push_back( rule ); rules.push_back( rule );
/* see: http://www.freerouting.net/usren/viewtopic.php?f=5&t=339#p474
sprintf( rule, "(clearance %.6g (type pad_to_turn_gap))", clearance + safetyMargin );
rules.push_back( rule );
sprintf( rule, "(clearance %.6g (type smd_to_turn_gap))", clearance + safetyMargin );
rules.push_back( rule );
sprintf( rule, "(clearance %.6g (type via_via))", clearance + safetyMargin );
rules.push_back( rule );
sprintf( rule, "(clearance %.6g (type via_smd))", clearance + safetyMargin );
rules.push_back( rule );
sprintf( rule, "(clearance %.6g (type via_pin))", clearance + safetyMargin );
rules.push_back( rule );
sprintf( rule, "(clearance %.6g (type pin_pin))", clearance + safetyMargin );
rules.push_back( rule );
sprintf( rule, "(clearance %.6g (type smd_pin))", clearance + safetyMargin );
rules.push_back( rule );
*/
// Pad to pad spacing on a single SMT part can be closer than our // Pad to pad spacing on a single SMT part can be closer than our
// clearance, we don't want freerouter complaining about that, so // clearance, we don't want freerouter complaining about that, so
// output a significantly smaller pad to pad clearance to freerouter. // output a significantly smaller pad to pad clearance to freerouter.
@ -1070,9 +1154,8 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
rules.push_back( rule ); rules.push_back( rule );
} }
//-----<zones (not keepout areas) become planes>-------------------------------- //-----<zones (not keepout areas) become planes>--------------------------------
// Note: only zones are output here, keepout areas be be created later // Note: only zones are output here, keepout areas are created later.
{ {
int netlessZones = 0; int netlessZones = 0;
@ -1183,7 +1266,7 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
is_first_point = false; is_first_point = false;
} }
cutout->AppendPoint( mapPt(point) ); cutout->AppendPoint( mapPt( point ) );
// Close the polygon // Close the polygon
if( iterator.IsEndContour() ) if( iterator.IsEndContour() )
@ -1225,7 +1308,7 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
for( int layer = 0; layer < copperCount; layer++ ) for( int layer = 0; layer < copperCount; layer++ )
{ {
if( layer == copperCount-1) if( layer == copperCount - 1 )
layer = B_Cu; layer = B_Cu;
if( !item->IsOnLayer( PCB_LAYER_ID( layer ) ) ) if( !item->IsOnLayer( PCB_LAYER_ID( layer ) ) )
@ -1254,7 +1337,7 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
is_first_point = false; is_first_point = false;
} }
mainPolygon->AppendPoint( mapPt(point) ); mainPolygon->AppendPoint( mapPt( point ) );
// this was the end of the main polygon // this was the end of the main polygon
if( iterator.IsEndContour() ) if( iterator.IsEndContour() )
@ -1374,7 +1457,6 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
} }
} }
IMAGE* registered = m_pcb->library->LookupIMAGE( image ); IMAGE* registered = m_pcb->library->LookupIMAGE( image );
if( registered != image ) if( registered != image )
@ -1432,7 +1514,6 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
} }
} }
//-----< output vias used in netclasses >----------------------------------- //-----< output vias used in netclasses >-----------------------------------
{ {
NETCLASSES& nclasses = aBoard->GetDesignSettings().GetNetClasses(); NETCLASSES& nclasses = aBoard->GetDesignSettings().GetNetClasses();
@ -1442,19 +1523,8 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
// the netclass dialog, or such control in the specctra export dialog. // the netclass dialog, or such control in the specctra export dialog.
// if( aBoard->GetDesignSettings().m_CurrentViaType == VIA_THROUGH ) m_top_via_layer = 0; // first specctra cu layer is number zero.
{ m_bot_via_layer = aBoard->GetCopperLayerCount()-1;
m_top_via_layer = 0; // first specctra cu layer is number zero.
m_bot_via_layer = aBoard->GetCopperLayerCount()-1;
}
/*
else
{
// again, should be in the BOARD:
topLayer = kicadLayer2pcb[ GetScreen()->m_Route_Layer_TOP ];
botLayer = kicadLayer2pcb[ GetScreen()->m_Route_Layer_BOTTOM ];
}
*/
// Add the via from the Default netclass first. The via container // Add the via from the Default netclass first. The via container
// in pcb->library preserves the sequence of addition. // in pcb->library preserves the sequence of addition.
@ -1470,29 +1540,6 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
wxASSERT( m_pcb->library->vias.size() == 0 ); wxASSERT( m_pcb->library->vias.size() == 0 );
m_pcb->library->AppendVia( via ); m_pcb->library->AppendVia( via );
#if 0
// I've seen no way to make stock vias useable by freerouter. Also the
// zero based diameter was leading to duplicates in the LookupVia() function.
// User should use netclass based vias when going to freerouter.
// Output the stock vias, but preserve uniqueness in the via container by
// using LookupVia().
for( unsigned i = 0; i < aBoard->m_ViasDimensionsList.size(); ++i )
{
int viaSize = aBoard->m_ViasDimensionsList[i].m_Diameter;
int viaDrill = aBoard->m_ViasDimensionsList[i].m_Drill;
via = makeVia( viaSize, viaDrill,
m_top_via_layer, m_bot_via_layer );
// maybe add 'via' to the library, but only if unique.
PADSTACK* registered = pcb->library->LookupVia( via );
if( registered != via )
delete via;
}
#endif
// set the "spare via" index at the start of the // set the "spare via" index at the start of the
// pcb->library->spareViaIndex = pcb->library->vias.size(); // pcb->library->spareViaIndex = pcb->library->vias.size();
@ -1512,9 +1559,6 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
} }
} }
#if 1 // do existing wires and vias
//-----<create the wires from tracks>----------------------------------- //-----<create the wires from tracks>-----------------------------------
{ {
// export all of them for now, later we'll decide what controls we need // export all of them for now, later we'll decide what controls we need
@ -1540,11 +1584,9 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
if( netcode == 0 ) if( netcode == 0 )
continue; continue;
if( old_netcode != netcode || if( old_netcode != netcode || old_width != track->GetWidth() ||
old_width != track->GetWidth() || old_layer != track->GetLayer() ||
old_layer != track->GetLayer() || ( path && path->points.back() != mapPt(track->GetStart() ) ) )
(path && path->points.back() != mapPt(track->GetStart()) )
)
{ {
old_width = track->GetWidth(); old_width = track->GetWidth();
old_layer = track->GetLayer(); old_layer = track->GetLayer();
@ -1582,7 +1624,6 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
} }
} }
//-----<export the existing real BOARD instantiated vias>----------------- //-----<export the existing real BOARD instantiated vias>-----------------
{ {
// Export all vias, once per unique size and drill diameter combo. // Export all vias, once per unique size and drill diameter combo.
@ -1626,8 +1667,6 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
} }
} }
#endif // do existing wires and vias
//-----<via_descriptor>------------------------------------------------- //-----<via_descriptor>-------------------------------------------------
{ {
// The pcb->library will output <padstack_descriptors> which is a combined // The pcb->library will output <padstack_descriptors> which is a combined
@ -1644,7 +1683,6 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
} }
} }
//-----<output NETCLASSs>---------------------------------------------------- //-----<output NETCLASSs>----------------------------------------------------
NETCLASSES& nclasses = aBoard->GetDesignSettings().GetNetClasses(); NETCLASSES& nclasses = aBoard->GetDesignSettings().GetNetClasses();
@ -1743,6 +1781,7 @@ void SPECCTRA_DB::FlipFOOTPRINTs( BOARD* aBoard )
for( FOOTPRINT* footprint : aBoard->Footprints() ) for( FOOTPRINT* footprint : aBoard->Footprints() )
{ {
footprint->SetFlag( 0 ); footprint->SetFlag( 0 );
if( footprint->GetLayer() == B_Cu ) if( footprint->GetLayer() == B_Cu )
{ {
footprint->Flip( footprint->GetPosition(), false ); footprint->Flip( footprint->GetPosition(), false );