kicad/pcbnew/specctra_import_export/specctra_export.cpp

1848 lines
63 KiB
C++
Raw Normal View History

2008-01-21 21:24:39 +00:00
/*
* This program source code file is part of KiCad, a free EDA CAD application.
2008-01-21 21:24:39 +00:00
*
* Copyright (C) 2007-2015 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 2015-2023 KiCad Developers, see AUTHORS.txt for contributors.
2008-02-07 20:23:58 +00:00
*
2008-01-21 21:24:39 +00:00
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
2008-02-07 20:23:58 +00:00
*
2008-01-21 21:24:39 +00:00
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
2008-02-07 20:23:58 +00:00
*
2008-01-21 21:24:39 +00:00
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
2008-02-07 20:23:58 +00:00
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
2008-01-21 21:24:39 +00:00
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
2008-02-07 20:23:58 +00:00
2008-01-21 21:24:39 +00:00
/* This source is a complement to specctra.cpp and implements the export to
specctra dsn file format. The specification for the grammar of the specctra
dsn file used to develop this code is given here:
2008-09-17 13:32:43 +00:00
http://tech.groups.yahoo.com/group/kicad-users/files/ then file "specctra.pdf"
2008-02-07 20:23:58 +00:00
2008-01-21 21:24:39 +00:00
Also see the comments at the top of the specctra.cpp file itself.
*/
2018-01-29 20:58:58 +00:00
#include <pcb_edit_frame.h>
#include <confirm.h> // DisplayError()
#include <gestfich.h> // EDA_FileSelector()
2020-10-24 01:38:50 +00:00
#include <locale_io.h>
#include <macros.h>
#include <math/util.h> // for KiROUND
2008-02-09 08:34:45 +00:00
#include <set> // std::set
2008-02-14 01:07:52 +00:00
#include <map> // std::map
2008-02-01 01:09:39 +00:00
#include <board.h>
#include <board_design_settings.h>
#include <footprint.h>
#include <pcb_shape.h>
2021-06-11 21:07:02 +00:00
#include <pcb_track.h>
#include <pad.h>
#include <zone.h>
#include <base_units.h>
#include <collectors.h>
#include <geometry/shape_poly_set.h>
#include <geometry/convex_hull.h>
#include <convert_basic_shapes_to_polygon.h>
#include <geometry/geometry_utils.h>
#include <pcbnew_settings.h>
2021-06-03 12:11:15 +00:00
#include <wx/log.h>
#include "specctra.h"
2008-01-21 22:16:45 +00:00
using namespace DSN;
// comment the line #define EXPORT_CUSTOM_PADS_CONVEX_HULL to export CUSTOM pads exact shapes.
// Shapes can be non convex polygons with holes (linked to outline) that can create issues.
// Especially Freerouter does not handle them very well:
// - 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 poorly).
// I am guessing non convex polygons with holes linked could create issues with any Router.
#define EXPORT_CUSTOM_PADS_CONVEX_HULL
2008-01-21 21:24:39 +00:00
// Add .1 mil to the requested clearances as a safety margin. There has been disagreement about
// interpretation of clearance in the past between KiCad and Freerouter, so keep this safetyMargin
// until the disagreement is resolved and stable. Freerouter seems to be moving (protected)
// traces upon loading the DSN file, and even though it seems to sometimes add its own 0.1 to the
// clearances, I believe this is happening after the load process (and moving traces) so I am of
// the opinion this is still needed.
static const double safetyMargin = 0.1;
bool PCB_EDIT_FRAME::ExportSpecctraFile( const wxString& aFullFilename )
{
BASE_SCREEN* screen = GetScreen();
bool wasModified = screen->IsContentModified();
wxString errorText;
bool ok = true;
2008-02-29 06:49:34 +00:00
2008-02-07 20:23:58 +00:00
try
{
ExportBoardToSpecctraFile( GetBoard(), aFullFilename );
2008-02-07 20:23:58 +00:00
}
catch( const IO_ERROR& ioe )
2008-01-21 22:16:45 +00:00
{
2008-01-24 21:47:54 +00:00
ok = false;
2008-02-07 20:23:58 +00:00
// copy the error string to safe place, ioe is in this scope only.
errorText = ioe.What();
2008-01-24 21:47:54 +00:00
}
// The two calls to FOOTPRINT::Flip() in ExportBoardToSpecctraFile both set the modified flag,
// yet their actions cancel each other out, so it should be ok to clear the flag.
2008-02-01 20:32:18 +00:00
if( !wasModified )
screen->SetContentModified( false );
2008-02-07 20:23:58 +00:00
2008-01-24 21:47:54 +00:00
if( ok )
SetStatusText( wxString( _( "BOARD exported OK." ) ) );
2008-01-24 21:47:54 +00:00
else
DisplayErrorMessage( this, _( "Unable to export, please fix and try again" ), errorText );
return ok;
2008-01-21 21:24:39 +00:00
}
void ExportBoardToSpecctraFile( BOARD* aBoard, const wxString& aFullFilename )
{
SPECCTRA_DB db;
db.SetPCB( SPECCTRA_DB::MakePCB() );
LOCALE_IO toggle; // Switch the locale to standard C
// Build the board outlines *before* flipping footprints
if( !db.BuiltBoardOutlines( aBoard ) )
wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) );
// 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,
// and record this in the FOOTPRINT's flag field.
db.FlipFOOTPRINTs( aBoard );
try
{
aBoard->SynchronizeNetsAndNetClasses( false );
db.FromBOARD( aBoard );
db.ExportPCB( aFullFilename, true );
db.RevertFOOTPRINTs( aBoard );
// if an exception is thrown by FromBOARD() or ExportPCB(), then ~SPECCTRA_DB() will
// close the file.
}
catch( ... )
{
db.RevertFOOTPRINTs( aBoard );
throw;
}
}
2008-01-21 21:24:39 +00:00
namespace DSN {
// "specctra reported units" are what we tell the external router that our exported lengths are in
2008-02-03 15:23:00 +00:00
/**
* Convert a distance from Pcbnew internal units to the reported Specctra DSN units in floating
* point format.
*/
2008-01-24 21:47:54 +00:00
static inline double scale( int kicadDist )
{
2012-04-11 14:49:11 +00:00
// nanometers to um
2022-09-17 00:45:14 +00:00
return kicadDist / ( pcbIUScale.IU_PER_MM / 1000.0 );
}
///< Convert integer internal units to float um
static inline double IU2um( int kicadDist )
{
2022-09-17 00:45:14 +00:00
return kicadDist * ( 1000.0 / pcbIUScale.IU_PER_MM );
2008-01-24 21:47:54 +00:00
}
2008-01-24 21:47:54 +00:00
static inline double mapX( int x )
{
return scale( x );
2008-01-24 21:47:54 +00:00
}
2008-01-24 21:47:54 +00:00
static inline double mapY( int y )
{
return -scale( y ); // make y negative, since it is increasing going down.
2008-01-24 21:47:54 +00:00
}
2008-01-23 01:52:49 +00:00
/**
* Convert 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 of mils, so we have to scale them.
2008-01-23 01:52:49 +00:00
*/
static POINT mapPt( const VECTOR2I& pt )
2008-01-21 22:16:45 +00:00
{
2008-01-22 20:48:02 +00:00
POINT ret;
ret.x = mapX( pt.x );
ret.y = mapY( pt.y );
ret.FixNegativeZero();
2008-01-22 20:48:02 +00:00
return ret;
}
2008-01-21 22:16:45 +00:00
2008-01-22 20:48:02 +00:00
static POINT mapPt( const VECTOR2I& pt, FOOTPRINT* aFootprint )
{
VECTOR2I fpRelative = pt - aFootprint->GetPosition();
RotatePoint( fpRelative, -aFootprint->GetOrientation() );
return mapPt( fpRelative );
}
2008-02-09 08:34:45 +00:00
/**
* Decide if the pad is a copper-less through hole which needs to be made into a round keepout.
2008-02-09 08:34:45 +00:00
*/
2020-11-12 22:30:02 +00:00
static bool isRoundKeepout( PAD* aPad )
2008-02-09 08:34:45 +00:00
{
2021-05-01 12:22:35 +00:00
if( aPad->GetShape() == PAD_SHAPE::CIRCLE )
{
2012-02-19 04:02:19 +00:00
if( aPad->GetDrillSize().x >= aPad->GetSize().x )
return true;
if( !( aPad->GetLayerSet() & LSET::AllCuMask() ).any() )
return true;
}
return false;
2008-02-09 08:34:45 +00:00
}
2008-01-25 22:03:36 +00:00
/**
* Create a PATH element with a single straight line, a pair of vertices.
2008-01-25 22:03:36 +00:00
*/
static PATH* makePath( const POINT& aStart, const POINT& aEnd, const std::string& aLayerName )
{
PATH* path = new PATH( nullptr, T_path );
2008-01-25 22:03:36 +00:00
path->AppendPoint( aStart );
path->AppendPoint( aEnd );
path->SetLayerId( aLayerName.c_str() );
return path;
}
bool SPECCTRA_DB::BuiltBoardOutlines( BOARD* aBoard )
{
return aBoard->GetBoardPolygonOutlines( m_brd_outlines );
}
2020-11-12 22:30:02 +00:00
PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad )
2008-02-14 01:07:52 +00:00
{
char name[256]; // padstack name builder
2008-02-14 01:07:52 +00:00
std::string uniqifier;
2008-01-29 16:45:14 +00:00
// caller must do these checks before calling here.
wxASSERT( !isRoundKeepout( aPad ) );
2008-01-29 16:45:14 +00:00
2008-02-14 01:07:52 +00:00
PADSTACK* padstack = new PADSTACK();
int reportedLayers = 0; // how many in reported padstack
const char* layerName[MAX_CU_LAYERS];
2008-02-07 20:23:58 +00:00
2008-11-10 20:54:26 +00:00
uniqifier = '[';
2008-02-07 20:23:58 +00:00
static const LSET all_cu = LSET::AllCuMask();
bool onAllCopperLayers = ( (aPad->GetLayerSet() & all_cu) == all_cu );
2008-02-07 20:23:58 +00:00
2008-11-10 20:54:26 +00:00
if( onAllCopperLayers )
uniqifier += 'A'; // A for all layers
2008-02-12 02:09:39 +00:00
2008-11-10 20:54:26 +00:00
const int copperCount = aBoard->GetCopperLayerCount();
for( int layer=0; layer<copperCount; ++layer )
2008-02-14 01:07:52 +00:00
{
PCB_LAYER_ID kilayer = m_pcbLayer2kicad[layer];
2008-11-10 20:54:26 +00:00
if( onAllCopperLayers || aPad->IsOnLayer( kilayer ) )
2008-02-14 01:07:52 +00:00
{
layerName[reportedLayers++] = m_layerIds[layer].c_str();
2008-11-10 20:54:26 +00:00
if( !onAllCopperLayers )
{
2008-11-10 20:54:26 +00:00
if( layer == 0 )
uniqifier += 'T';
else if( layer == copperCount - 1 )
2008-11-10 20:54:26 +00:00
uniqifier += 'B';
else
uniqifier += char('0' + layer); // layer index char
}
}
2008-01-29 16:45:14 +00:00
}
2008-02-07 20:23:58 +00:00
2008-11-10 20:54:26 +00:00
uniqifier += ']';
POINT dsnOffset;
2012-02-19 04:02:19 +00:00
if( aPad->GetOffset().x || aPad->GetOffset().y )
{
char offsetTxt[64];
2022-01-01 18:08:03 +00:00
VECTOR2I offset( aPad->GetOffset().x, aPad->GetOffset().y );
dsnOffset = mapPt( offset );
// using () would cause padstack name to be quoted, and {} locks freerouter, so use [].
std::snprintf( offsetTxt, sizeof( offsetTxt ), "[%.6g,%.6g]", dsnOffset.x, dsnOffset.y );
uniqifier += offsetTxt;
}
2012-02-19 04:02:19 +00:00
switch( aPad->GetShape() )
2008-02-05 02:13:16 +00:00
{
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::CIRCLE:
2021-07-19 23:56:05 +00:00
{
double diameter = scale( aPad->GetSize().x );
2008-02-01 01:09:39 +00:00
2021-07-19 23:56:05 +00:00
for( int ndx = 0; ndx < reportedLayers; ++ndx )
{
SHAPE* shape = new SHAPE( padstack );
2021-07-19 23:56:05 +00:00
padstack->Append( shape );
2008-02-07 20:23:58 +00:00
2021-07-19 23:56:05 +00:00
CIRCLE* circle = new CIRCLE( shape );
2021-07-19 23:56:05 +00:00
shape->SetShape( circle );
2008-02-07 20:23:58 +00:00
2021-07-19 23:56:05 +00:00
circle->SetLayerId( layerName[ndx] );
circle->SetDiameter( diameter );
circle->SetVertex( dsnOffset );
}
2008-02-07 20:23:58 +00:00
2021-07-19 23:56:05 +00:00
snprintf( name, sizeof(name), "Round%sPad_%.6g_um",
uniqifier.c_str(), IU2um( aPad->GetSize().x ) );
2021-07-19 23:56:05 +00:00
name[ sizeof(name) - 1 ] = 0;
2021-07-19 23:56:05 +00:00
padstack->SetPadstackId( name );
2008-02-05 02:13:16 +00:00
break;
2021-07-19 23:56:05 +00:00
}
2008-02-07 20:23:58 +00:00
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::RECT:
2021-07-19 23:56:05 +00:00
{
double dx = scale( aPad->GetSize().x ) / 2.0;
double dy = scale( aPad->GetSize().y ) / 2.0;
2008-02-05 02:13:16 +00:00
2021-07-19 23:56:05 +00:00
POINT lowerLeft( -dx, -dy );
POINT upperRight( dx, dy );
2008-02-07 20:23:58 +00:00
2021-07-19 23:56:05 +00:00
lowerLeft += dsnOffset;
upperRight += dsnOffset;
2021-07-19 23:56:05 +00:00
for( int ndx = 0; ndx < reportedLayers; ++ndx )
{
SHAPE* shape = new SHAPE( padstack );
2021-07-19 23:56:05 +00:00
padstack->Append( shape );
2008-02-07 20:23:58 +00:00
2021-07-19 23:56:05 +00:00
RECTANGLE* rect = new RECTANGLE( shape );
2021-07-19 23:56:05 +00:00
shape->SetShape( rect );
2008-02-07 20:23:58 +00:00
2021-07-19 23:56:05 +00:00
rect->SetLayerId( layerName[ndx] );
rect->SetCorners( lowerLeft, upperRight );
}
2008-02-14 01:07:52 +00:00
2021-07-19 23:56:05 +00:00
snprintf( name, sizeof( name ), "Rect%sPad_%.6gx%.6g_um", uniqifier.c_str(),
IU2um( aPad->GetSize().x ), IU2um( aPad->GetSize().y ) );
2021-07-19 23:56:05 +00:00
name[sizeof( name ) - 1] = 0;
2008-02-07 20:23:58 +00:00
2021-07-19 23:56:05 +00:00
padstack->SetPadstackId( name );
2008-02-05 02:13:16 +00:00
break;
2021-07-19 23:56:05 +00:00
}
2008-02-07 20:23:58 +00:00
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::OVAL:
2021-07-19 23:56:05 +00:00
{
double dx = scale( aPad->GetSize().x ) / 2.0;
double dy = scale( aPad->GetSize().y ) / 2.0;
double dr = dx - dy;
double radius;
POINT pstart;
POINT pstop;
if( dr >= 0 ) // oval is horizontal
2008-02-14 01:07:52 +00:00
{
2021-07-19 23:56:05 +00:00
radius = dy;
2021-07-19 23:56:05 +00:00
pstart = POINT( -dr, 0.0 );
pstop = POINT( dr, 0.0 );
}
else // oval is vertical
{
radius = dx;
dr = -dr;
2008-02-01 01:09:39 +00:00
2021-07-19 23:56:05 +00:00
pstart = POINT( 0.0, -dr );
pstop = POINT( 0.0, dr );
}
2021-07-19 23:56:05 +00:00
pstart += dsnOffset;
pstop += dsnOffset;
2021-07-19 23:56:05 +00:00
for( int ndx = 0; ndx < reportedLayers; ++ndx )
{
SHAPE* shape;
PATH* path;
2008-02-01 01:09:39 +00:00
2021-07-19 23:56:05 +00:00
// see http://www.freerouting.net/usren/viewtopic.php?f=3&t=317#p408
shape = new SHAPE( padstack );
2008-02-01 01:09:39 +00:00
2021-07-19 23:56:05 +00:00
padstack->Append( shape );
path = makePath( pstart, pstop, layerName[ndx] );
shape->SetShape( path );
path->aperture_width = 2.0 * radius;
2008-02-14 01:07:52 +00:00
}
2021-07-19 23:56:05 +00:00
snprintf( name, sizeof( name ), "Oval%sPad_%.6gx%.6g_um", uniqifier.c_str(),
IU2um( aPad->GetSize().x ), IU2um( aPad->GetSize().y ) );
name[sizeof( name ) - 1] = 0;
padstack->SetPadstackId( name );
2008-02-14 01:07:52 +00:00
break;
2021-07-19 23:56:05 +00:00
}
2008-02-07 20:23:58 +00:00
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::TRAPEZOID:
2021-07-19 23:56:05 +00:00
{
double dx = scale( aPad->GetSize().x ) / 2.0;
double dy = scale( aPad->GetSize().y ) / 2.0;
2021-07-19 23:56:05 +00:00
double ddx = scale( aPad->GetDelta().x ) / 2.0;
double ddy = scale( aPad->GetDelta().y ) / 2.0;
2021-07-19 23:56:05 +00:00
// see class_pad_draw_functions.cpp which draws the trapezoid pad
POINT lowerLeft( -dx - ddy, -dy - ddx );
POINT upperLeft( -dx + ddy, +dy + ddx );
POINT upperRight( +dx - ddy, +dy - ddx );
POINT lowerRight( +dx + ddy, -dy + ddx );
2021-07-19 23:56:05 +00:00
lowerLeft += dsnOffset;
upperLeft += dsnOffset;
upperRight += dsnOffset;
lowerRight += dsnOffset;
2021-07-19 23:56:05 +00:00
for( int ndx = 0; ndx < reportedLayers; ++ndx )
{
SHAPE* shape = new SHAPE( padstack );
2021-07-19 23:56:05 +00:00
padstack->Append( shape );
2021-07-19 23:56:05 +00:00
// a T_polygon exists as a PATH
PATH* polygon = new PATH( shape, T_polygon );
2021-07-19 23:56:05 +00:00
shape->SetShape( polygon );
2021-07-19 23:56:05 +00:00
polygon->SetLayerId( layerName[ndx] );
2021-07-19 23:56:05 +00:00
polygon->AppendPoint( lowerLeft );
polygon->AppendPoint( upperLeft );
polygon->AppendPoint( upperRight );
polygon->AppendPoint( lowerRight );
}
2021-07-19 23:56:05 +00:00
// this string _must_ be unique for a given physical shape
snprintf( name, sizeof( name ), "Trapz%sPad_%.6gx%.6g_%c%.6gx%c%.6g_um", uniqifier.c_str(),
IU2um( aPad->GetSize().x ), IU2um( aPad->GetSize().y ),
aPad->GetDelta().x < 0 ? 'n' : 'p', std::abs( IU2um( aPad->GetDelta().x ) ),
aPad->GetDelta().y < 0 ? 'n' : 'p', std::abs( IU2um( aPad->GetDelta().y ) ) );
name[sizeof( name ) - 1] = 0;
padstack->SetPadstackId( name );
2008-02-14 01:07:52 +00:00
break;
2021-07-19 23:56:05 +00:00
}
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::CHAMFERED_RECT:
case PAD_SHAPE::ROUNDRECT:
2021-07-19 23:56:05 +00:00
{
// Export the shape as as polygon, round rect does not exist as primitive
const int circleToSegmentsCount = 36;
int rradius = aPad->GetRoundRectCornerRadius();
SHAPE_POLY_SET cornerBuffer;
// Use a slightly bigger shape because the round corners are approximated by
// segments, giving to the polygon a slightly smaller shape than the actual shape
/* calculates the coeff to compensate radius reduction of holes clearance
* due to the segment approx.
* For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2)
* correctionFactor is cos( PI/s_CircleToSegmentsCount )
*/
double correctionFactor = cos( M_PI / (double) circleToSegmentsCount );
int extra_clearance = KiROUND( rradius * ( 1.0 - correctionFactor ) );
2022-01-05 01:42:27 +00:00
VECTOR2I psize = aPad->GetSize();
2021-07-19 23:56:05 +00:00
psize.x += extra_clearance * 2;
psize.y += extra_clearance * 2;
rradius += extra_clearance;
bool doChamfer = aPad->GetShape() == PAD_SHAPE::CHAMFERED_RECT;
TransformRoundChamferedRectToPolygon( cornerBuffer, VECTOR2I( 0, 0 ), psize, ANGLE_0,
rradius, aPad->GetChamferRectRatio(),
doChamfer ? aPad->GetChamferPositions() : 0,
0, aBoard->GetDesignSettings().m_MaxError,
ERROR_INSIDE );
2021-07-19 23:56:05 +00:00
SHAPE_LINE_CHAIN& polygonal_shape = cornerBuffer.Outline( 0 );
for( int ndx = 0; ndx < reportedLayers; ++ndx )
{
2021-07-19 23:56:05 +00:00
SHAPE* shape = new SHAPE( padstack );
2021-07-19 23:56:05 +00:00
padstack->Append( shape );
2021-07-19 23:56:05 +00:00
// a T_polygon exists as a PATH
PATH* polygon = new PATH( shape, T_polygon );
2021-07-19 23:56:05 +00:00
shape->SetShape( polygon );
2021-07-19 23:56:05 +00:00
polygon->SetLayerId( layerName[ndx] );
2021-07-19 23:56:05 +00:00
// append a closed polygon
POINT first_corner;
2021-07-19 23:56:05 +00:00
for( int idx = 0; idx < polygonal_shape.PointCount(); idx++ )
{
POINT corner( scale( polygonal_shape.CPoint( idx ).x ),
scale( -polygonal_shape.CPoint( idx ).y ) );
corner += dsnOffset;
polygon->AppendPoint( corner );
if( idx == 0 )
first_corner = corner;
}
2021-07-19 23:56:05 +00:00
polygon->AppendPoint( first_corner ); // Close polygon
}
2021-07-19 23:56:05 +00:00
// this string _must_ be unique for a given physical shape
snprintf( name, sizeof( name ), "RoundRect%sPad_%.6gx%.6g_%.6g_um_%f_%X", uniqifier.c_str(),
IU2um( aPad->GetSize().x ), IU2um( aPad->GetSize().y ), IU2um( rradius ),
doChamfer ? aPad->GetChamferRectRatio() : 0.0,
doChamfer ? aPad->GetChamferPositions() : 0 );
2021-07-19 23:56:05 +00:00
name[sizeof( name ) - 1] = 0;
padstack->SetPadstackId( name );
break;
2021-07-19 23:56:05 +00:00
}
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::CUSTOM:
2021-07-19 23:56:05 +00:00
{
2022-01-01 18:08:03 +00:00
std::vector<VECTOR2I> polygonal_shape;
2021-07-19 23:56:05 +00:00
SHAPE_POLY_SET pad_shape;
aPad->MergePrimitivesAsPolygon( &pad_shape );
#ifdef EXPORT_CUSTOM_PADS_CONVEX_HULL
2021-07-19 23:56:05 +00:00
BuildConvexHull( polygonal_shape, pad_shape );
#else
2021-07-19 23:56:05 +00:00
const SHAPE_LINE_CHAIN& p_outline = pad_shape.COutline( 0 );
2021-07-19 23:56:05 +00:00
for( int ii = 0; ii < p_outline.PointCount(); ++ii )
polygonal_shape.push_back( wxPoint( p_outline.CPoint( ii ) ) );
#endif
2021-07-19 23:56:05 +00:00
// The polygon must be closed
if( polygonal_shape.front() != polygonal_shape.back() )
polygonal_shape.push_back( polygonal_shape.front() );
2021-07-19 23:56:05 +00:00
for( int ndx = 0; ndx < reportedLayers; ++ndx )
{
SHAPE* shape = new SHAPE( padstack );
2021-07-19 23:56:05 +00:00
padstack->Append( shape );
2021-07-19 23:56:05 +00:00
// a T_polygon exists as a PATH
PATH* polygon = new PATH( shape, T_polygon );
2021-07-19 23:56:05 +00:00
shape->SetShape( polygon );
2021-07-19 23:56:05 +00:00
polygon->SetLayerId( layerName[ndx] );
2021-07-19 23:56:05 +00:00
for( unsigned idx = 0; idx < polygonal_shape.size(); idx++ )
{
POINT corner( scale( polygonal_shape[idx].x ), scale( -polygonal_shape[idx].y ) );
corner += dsnOffset;
polygon->AppendPoint( corner );
}
2021-07-19 23:56:05 +00:00
}
2021-07-19 23:56:05 +00:00
// this string _must_ be unique for a given physical shape, so try to make it unique
MD5_HASH hash = pad_shape.GetHash();
2022-08-31 16:17:14 +00:00
BOX2I rect = aPad->GetBoundingBox();
2021-07-19 23:56:05 +00:00
snprintf( name, sizeof( name ), "Cust%sPad_%.6gx%.6g_%.6gx_%.6g_%d_um_%s",
uniqifier.c_str(), IU2um( aPad->GetSize().x ), IU2um( aPad->GetSize().y ),
IU2um( rect.GetWidth() ), IU2um( rect.GetHeight() ), (int) polygonal_shape.size(),
hash.Format( true ).c_str() );
name[sizeof( name ) - 1] = 0;
2021-07-19 23:56:05 +00:00
padstack->SetPadstackId( name );
break;
2008-02-14 01:07:52 +00:00
}
2021-07-19 23:56:05 +00:00
}
2008-02-07 20:23:58 +00:00
2008-02-01 01:09:39 +00:00
return padstack;
2008-02-07 20:23:58 +00:00
}
2008-02-01 01:09:39 +00:00
2008-02-29 06:49:34 +00:00
/// data type used to ensure unique-ness of pin names, holding (wxString and int)
2009-09-10 15:22:26 +00:00
typedef std::map<wxString, int> PINMAP;
2008-02-09 08:34:45 +00:00
2020-11-13 15:15:52 +00:00
IMAGE* SPECCTRA_DB::makeIMAGE( BOARD* aBoard, FOOTPRINT* aFootprint )
2008-01-24 21:47:54 +00:00
{
PINMAP pinmap;
wxString padNumber;
2020-11-13 01:33:30 +00:00
PCB_TYPE_COLLECTOR fpItems;
2008-01-24 21:47:54 +00:00
2020-11-13 15:15:52 +00:00
// get all the FOOTPRINT's pads.
fpItems.Collect( aFootprint, { PCB_PAD_T } );
2008-01-24 21:47:54 +00:00
IMAGE* image = new IMAGE( nullptr );
2008-02-07 20:23:58 +00:00
image->m_image_id = aFootprint->GetFPID().Format().c_str();
2008-02-07 20:23:58 +00:00
2008-02-14 01:07:52 +00:00
// from the pads, and make an IMAGE using collated padstacks.
2021-07-19 23:56:05 +00:00
for( int p = 0; p < fpItems.GetCount(); ++p )
2008-02-14 01:07:52 +00:00
{
2020-11-13 01:33:30 +00:00
PAD* pad = (PAD*) fpItems[p];
2008-01-24 21:47:54 +00:00
2008-02-14 01:07:52 +00:00
// see if this pad is a through hole with no copper on its perimeter
if( isRoundKeepout( pad ) )
2008-01-24 21:47:54 +00:00
{
2012-02-19 04:02:19 +00:00
double diameter = scale( pad->GetDrillSize().x );
POINT vertex = mapPt( pad->GetFPRelativePosition() );
2008-01-24 21:47:54 +00:00
diameter += scale( aBoard->GetDesignSettings().m_HoleClearance * 2 );
2008-02-14 01:07:52 +00:00
int layerCount = aBoard->GetCopperLayerCount();
for( int layer=0; layer<layerCount; ++layer )
2008-02-14 01:07:52 +00:00
{
KEEPOUT* keepout = new KEEPOUT( image, T_keepout );
image->m_keepouts.push_back( keepout );
2008-02-08 15:00:50 +00:00
CIRCLE* circle = new CIRCLE( keepout );
2008-02-14 01:07:52 +00:00
keepout->SetShape( circle );
2008-02-07 20:23:58 +00:00
2008-02-14 01:07:52 +00:00
circle->SetDiameter( diameter );
circle->SetVertex( vertex );
circle->SetLayerId( m_layerIds[layer].c_str() );
2008-02-14 01:07:52 +00:00
}
}
2021-07-19 23:56:05 +00:00
else // else if() could there be a square keepout here?
2008-02-08 15:00:50 +00:00
{
// Pads not on copper layers (i.e. only on tech layers) are ignored
// because they create invalid pads in .dsn file for freeroute
LSET mask_copper_layers = pad->GetLayerSet() & LSET::AllCuMask();
if( !mask_copper_layers.any() )
continue;
PADSTACK* padstack = makePADSTACK( aBoard, pad );
PADSTACKSET::iterator iter = m_padstackset.find( *padstack );
2008-02-07 20:23:58 +00:00
if( iter != m_padstackset.end() )
2008-02-08 15:00:50 +00:00
{
2008-02-14 01:07:52 +00:00
// padstack is a duplicate, delete it and use the original
delete padstack;
padstack = (PADSTACK*) *iter.base(); // folklore, be careful here
2008-02-08 15:00:50 +00:00
}
2008-02-14 01:07:52 +00:00
else
2008-02-08 15:00:50 +00:00
{
m_padstackset.insert( padstack );
2008-02-08 15:00:50 +00:00
}
2008-01-31 01:30:52 +00:00
PIN* pin = new PIN( image );
2008-02-07 20:23:58 +00:00
padNumber = pad->GetNumber();
pin->m_pin_id = TO_UTF8( padNumber );
2008-02-14 01:07:52 +00:00
if( padNumber != wxEmptyString && pinmap.find( padNumber ) == pinmap.end() )
2008-01-24 21:47:54 +00:00
{
pinmap[ padNumber ] = 0;
2008-02-14 01:07:52 +00:00
}
else // pad name is a duplicate within this footprint
2008-02-14 01:07:52 +00:00
{
int duplicates = ++pinmap[ padNumber ];
2008-02-07 20:23:58 +00:00
pin->m_pin_id +=
"@" + std::to_string( duplicates ); // append "@1" or "@2", etc. to pin name
2008-02-14 01:07:52 +00:00
}
2008-02-07 20:23:58 +00:00
pin->m_kiNetCode = pad->GetNetCode();
2008-01-24 21:47:54 +00:00
image->m_pins.push_back( pin );
2008-02-07 20:23:58 +00:00
pin->m_padstack_id = padstack->m_padstack_id;
2008-01-24 21:47:54 +00:00
EDA_ANGLE angle = pad->GetOrientation() - aFootprint->GetOrientation();
pin->SetRotation( angle.Normalize().AsDegrees() );
2008-02-07 20:23:58 +00:00
VECTOR2I pos( pad->GetFPRelativePosition() );
2008-01-24 21:47:54 +00:00
pin->SetVertex( mapPt( pos ) );
2008-02-14 01:07:52 +00:00
}
}
2008-02-07 20:23:58 +00:00
// get all the FOOTPRINT's SHAPEs and convert those to DSN outlines.
fpItems.Collect( aFootprint, { PCB_SHAPE_T } );
2020-11-13 01:33:30 +00:00
for( int i = 0; i < fpItems.GetCount(); ++i )
{
PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( fpItems[i] );
SHAPE* outline;
PATH* path;
2011-12-14 04:29:25 +00:00
switch( graphic->GetShape() )
{
case SHAPE_T::SEGMENT:
outline = new SHAPE( image, T_outline );
image->Append( outline );
path = new PATH( outline );
outline->SetShape( path );
2011-12-14 04:29:25 +00:00
path->SetAperture( scale( graphic->GetWidth() ) );
path->SetLayerId( "signal" );
path->AppendPoint( mapPt( graphic->GetStart(), aFootprint ) );
path->AppendPoint( mapPt( graphic->GetEnd(), aFootprint ) );
break;
case SHAPE_T::CIRCLE:
2021-07-19 23:56:05 +00:00
{
// this is best done by 4 QARC's but freerouter does not yet support QARCs.
// for now, support by using line segments.
outline = new SHAPE( image, T_outline );
image->Append( outline );
2021-07-19 23:56:05 +00:00
path = new PATH( outline );
2021-07-19 23:56:05 +00:00
outline->SetShape( path );
path->SetAperture( scale( graphic->GetWidth() ) );
path->SetLayerId( "signal" );
2021-07-19 23:56:05 +00:00
double radius = graphic->GetRadius();
VECTOR2I circle_centre = graphic->GetStart();
2021-07-19 23:56:05 +00:00
SHAPE_LINE_CHAIN polyline;
2022-01-14 15:34:41 +00:00
ConvertArcToPolyline( polyline, VECTOR2I( circle_centre ), radius, ANGLE_0, ANGLE_360,
2021-07-19 23:56:05 +00:00
ARC_HIGH_DEF, ERROR_INSIDE );
2021-07-19 23:56:05 +00:00
for( int ii = 0; ii < polyline.PointCount(); ++ii )
{
VECTOR2I corner( polyline.CPoint( ii ).x, polyline.CPoint( ii ).y );
path->AppendPoint( mapPt( corner, aFootprint ) );
}
2021-07-19 23:56:05 +00:00
break;
2021-07-19 23:56:05 +00:00
}
case SHAPE_T::RECT:
2021-07-19 23:56:05 +00:00
{
outline = new SHAPE( image, T_outline );
image->Append( outline );
path = new PATH( outline );
outline->SetShape( path );
path->SetAperture( scale( graphic->GetWidth() ) );
path->SetLayerId( "signal" );
VECTOR2I corner = graphic->GetStart();
path->AppendPoint( mapPt( corner, aFootprint ) );
corner.x = graphic->GetEnd().x;
path->AppendPoint( mapPt( corner, aFootprint ) );
corner.y = graphic->GetEnd().y;
path->AppendPoint( mapPt( corner, aFootprint ) );
corner.x = graphic->GetStart().x;
path->AppendPoint( mapPt( corner, aFootprint ) );
break;
2021-07-19 23:56:05 +00:00
}
case SHAPE_T::ARC:
2021-07-19 23:56:05 +00:00
{
// this is best done by QARC's but freerouter does not yet support QARCs.
// for now, support by using line segments.
// So we use a polygon (PATH) to create a approximate arc shape
outline = new SHAPE( image, T_outline );
image->Append( outline );
path = new PATH( outline );
outline->SetShape( path );
path->SetAperture( 0 );//scale( graphic->GetWidth() ) );
path->SetLayerId( "signal" );
VECTOR2I arc_centre = graphic->GetCenter();
2022-01-14 15:34:41 +00:00
double radius = graphic->GetRadius() + graphic->GetWidth()/2;
EDA_ANGLE arcAngle = graphic->GetArcAngle();
2021-07-19 23:56:05 +00:00
2022-01-14 15:34:41 +00:00
VECTOR2I startRadial = graphic->GetStart() - graphic->GetCenter();
EDA_ANGLE arcStart( startRadial );
arcStart.Normalize();
2021-07-19 23:56:05 +00:00
// For some obscure reason, FreeRouter does not show the same polygonal
// shape for polygons CW and CCW. So used only the order of corners
// giving the best look.
2022-01-14 15:34:41 +00:00
if( arcAngle < ANGLE_0 )
{
VECTOR2I endRadial = graphic->GetEnd() - graphic->GetCenter();
2022-01-14 15:34:41 +00:00
arcStart = EDA_ANGLE( endRadial );
arcStart.Normalize();
2022-01-14 15:34:41 +00:00
arcAngle = -arcAngle;
2021-07-19 23:56:05 +00:00
}
2021-07-19 23:56:05 +00:00
SHAPE_LINE_CHAIN polyline;
2022-01-14 15:34:41 +00:00
ConvertArcToPolyline( polyline, VECTOR2I( arc_centre ), radius, arcStart, arcAngle,
ARC_HIGH_DEF, ERROR_INSIDE );
2021-07-19 23:56:05 +00:00
SHAPE_POLY_SET polyBuffer;
polyBuffer.AddOutline( polyline );
2021-07-19 23:56:05 +00:00
radius -= graphic->GetWidth();
2021-07-19 23:56:05 +00:00
if( radius > 0 )
{
polyline.Clear();
2022-01-14 15:34:41 +00:00
ConvertArcToPolyline( polyline, VECTOR2I( arc_centre ), radius, arcStart, arcAngle,
ARC_HIGH_DEF, ERROR_INSIDE );
2021-07-19 23:56:05 +00:00
// Add points in reverse order, to create a closed polygon
for( int ii = polyline.PointCount() - 1; ii >= 0; --ii )
polyBuffer.Append( polyline.CPoint( ii ) );
}
2021-07-19 23:56:05 +00:00
// ensure the polygon is closed
polyBuffer.Append( polyBuffer.Outline( 0 ).CPoint( 0 ) );
2008-02-29 06:49:34 +00:00
VECTOR2I move = graphic->GetCenter() - arc_centre;
2008-02-29 06:49:34 +00:00
TransformCircleToPolygon( polyBuffer, graphic->GetStart() - move,
2021-07-19 23:56:05 +00:00
graphic->GetWidth() / 2, ARC_HIGH_DEF, ERROR_INSIDE );
TransformCircleToPolygon( polyBuffer, graphic->GetEnd() - move,
2021-07-19 23:56:05 +00:00
graphic->GetWidth() / 2, ARC_HIGH_DEF, ERROR_INSIDE );
2021-07-19 23:56:05 +00:00
polyBuffer.Simplify( SHAPE_POLY_SET::PM_FAST );
SHAPE_LINE_CHAIN& poly = polyBuffer.Outline( 0 );
2021-07-19 23:56:05 +00:00
for( int ii = 0; ii < poly.PointCount(); ++ii )
{
VECTOR2I corner( poly.CPoint( ii ).x, poly.CPoint( ii ).y );
path->AppendPoint( mapPt( corner, aFootprint ) );
}
2021-07-19 23:56:05 +00:00
break;
2021-07-19 23:56:05 +00:00
}
default:
continue;
}
}
for( ZONE* zone : aFootprint->Zones() )
{
if( !zone->GetIsRuleArea() )
continue;
// IMAGE object coordinates are relative to the IMAGE not absolute board coordinates.
ZONE untransformedZone( *zone );
2022-01-13 17:27:36 +00:00
EDA_ANGLE angle = -aFootprint->GetOrientation();
angle.Normalize();
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->m_structure, keepout_type );
image->m_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;
VECTOR2I startpoint;
for( iterator = untransformedZone.IterateWithHoles(); iterator; iterator++ )
{
VECTOR2I 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 );
VECTOR2I 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 ) );
}
}
}
2008-02-14 01:07:52 +00:00
return image;
}
2008-02-07 20:23:58 +00:00
2008-01-25 22:03:36 +00:00
2008-02-14 01:07:52 +00:00
PADSTACK* SPECCTRA_DB::makeVia( int aCopperDiameter, int aDrillDiameter,
int aTopLayer, int aBotLayer )
2008-02-14 01:07:52 +00:00
{
char name[48];
PADSTACK* padstack = new PADSTACK();
double dsnDiameter = scale( aCopperDiameter );
2008-02-07 20:23:58 +00:00
for( int layer=aTopLayer; layer<=aBotLayer; ++layer )
2008-02-14 01:07:52 +00:00
{
SHAPE* shape = new SHAPE( padstack );
2008-02-14 01:07:52 +00:00
padstack->Append( shape );
2008-02-07 20:23:58 +00:00
2008-02-14 01:07:52 +00:00
CIRCLE* circle = new CIRCLE( shape );
2008-02-14 01:07:52 +00:00
shape->SetShape( circle );
2008-01-25 22:03:36 +00:00
2008-02-14 01:07:52 +00:00
circle->SetDiameter( dsnDiameter );
circle->SetLayerId( m_layerIds[layer].c_str() );
2008-01-24 21:47:54 +00:00
}
2008-01-26 02:02:27 +00:00
snprintf( name, sizeof( name ), "Via[%d-%d]_%.6g:%.6g_um",
aTopLayer, aBotLayer, dsnDiameter,
// encode the drill value into the name for later import
IU2um( aDrillDiameter ) );
name[ sizeof(name) - 1 ] = 0;
2008-02-14 01:07:52 +00:00
padstack->SetPadstackId( name );
2008-01-26 02:02:27 +00:00
2008-02-14 01:07:52 +00:00
return padstack;
}
2008-01-29 16:45:14 +00:00
2008-02-07 20:23:58 +00:00
2021-06-11 21:07:02 +00:00
PADSTACK* SPECCTRA_DB::makeVia( const PCB_VIA* aVia )
2008-02-14 01:07:52 +00:00
{
PCB_LAYER_ID topLayerNum;
PCB_LAYER_ID botLayerNum;
2008-01-26 02:02:27 +00:00
* KIWAY Milestone A): Make major modules into DLL/DSOs. ! The initial testing of this commit should be done using a Debug build so that all the wxASSERT()s are enabled. Also, be sure and keep enabled the USE_KIWAY_DLLs option. The tree won't likely build without it. Turning it off is senseless anyways. If you want stable code, go back to a prior version, the one tagged with "stable". * Relocate all functionality out of the wxApp derivative into more finely targeted purposes: a) DLL/DSO specific b) PROJECT specific c) EXE or process specific d) configuration file specific data e) configuration file manipulations functions. All of this functionality was blended into an extremely large wxApp derivative and that was incompatible with the desire to support multiple concurrently loaded DLL/DSO's ("KIFACE")s and multiple concurrently open projects. An amazing amount of organization come from simply sorting each bit of functionality into the proper box. * Switch to wxConfigBase from wxConfig everywhere except instantiation. * Add classes KIWAY, KIFACE, KIFACE_I, SEARCH_STACK, PGM_BASE, PGM_KICAD, PGM_SINGLE_TOP, * Remove "Return" prefix on many function names. * Remove obvious comments from CMakeLists.txt files, and from else() and endif()s. * Fix building boost for use in a DSO on linux. * Remove some of the assumptions in the CMakeLists.txt files that windows had to be the host platform when building windows binaries. * Reduce the number of wxStrings being constructed at program load time via static construction. * Pass wxConfigBase* to all SaveSettings() and LoadSettings() functions so that these functions are useful even when the wxConfigBase comes from another source, as is the case in the KICAD_MANAGER_FRAME. * Move the setting of the KIPRJMOD environment variable into class PROJECT, so that it can be moved into a project variable soon, and out of FP_LIB_TABLE. * Add the KIWAY_PLAYER which is associated with a particular PROJECT, and all its child wxFrames and wxDialogs now have a Kiway() member function which returns a KIWAY& that that window tree branch is in support of. This is like wxWindows DNA in that child windows get this member with proper value at time of construction. * Anticipate some of the needs for milestones B) and C) and make code adjustments now in an effort to reduce work in those milestones. * No testing has been done for python scripting, since milestone C) has that being largely reworked and re-thought-out.
2014-03-20 00:42:08 +00:00
aVia->LayerPair( &topLayerNum, &botLayerNum );
2008-02-07 20:23:58 +00:00
int topLayer = m_kicadLayer2pcb[topLayerNum];
int botLayer = m_kicadLayer2pcb[botLayerNum];
2008-02-07 20:23:58 +00:00
2008-02-14 01:07:52 +00:00
if( topLayer > botLayer )
std::swap( topLayer, botLayer );
2008-02-14 01:07:52 +00:00
return makeVia( aVia->GetWidth(), aVia->GetDrillValue(), topLayer, botLayer );
2008-01-21 22:16:45 +00:00
}
2008-01-22 20:48:02 +00:00
void SPECCTRA_DB::fillBOUNDARY( BOARD* aBoard, BOUNDARY* boundary )
{
for( int cnt = 0; cnt < m_brd_outlines.OutlineCount(); cnt++ ) // Should be one outline
{
PATH* path = new PATH( boundary );
boundary->paths.push_back( path );
path->layer_id = "pcb";
SHAPE_LINE_CHAIN& outline = m_brd_outlines.Outline( cnt );
2017-03-20 13:55:32 +00:00
for( int ii = 0; ii < outline.PointCount(); ii++ )
{
VECTOR2I pos( outline.CPoint( ii ).x, outline.CPoint( ii ).y );
2017-03-20 13:55:32 +00:00
path->AppendPoint( mapPt( pos ) );
}
2017-03-20 13:55:32 +00:00
// Close polygon:
VECTOR2I pos0( outline.CPoint( 0 ).x, outline.CPoint( 0 ).y );
path->AppendPoint( mapPt( pos0 ) );
2008-05-05 20:13:54 +00:00
// Generate holes as keepout:
for( int ii = 0; ii < m_brd_outlines.HoleCount( cnt ); ii++ )
{
// emit a signal layers keepout for every interior polygon left...
2021-07-19 23:56:05 +00:00
KEEPOUT* keepout = new KEEPOUT( nullptr, T_keepout );
PATH* poly_ko = new PATH( nullptr, T_polygon );
keepout->SetShape( poly_ko );
poly_ko->SetLayerId( "signal" );
m_pcb->m_structure->m_keepouts.push_back( keepout );
SHAPE_LINE_CHAIN& hole = m_brd_outlines.Hole( cnt, ii );
for( int jj = 0; jj < hole.PointCount(); jj++ )
{
VECTOR2I pos( hole.CPoint( jj ).x, hole.CPoint( jj ).y );
poly_ko->AppendPoint( mapPt( pos ) );
}
// Close polygon:
VECTOR2I pos( hole.CPoint( 0 ).x, hole.CPoint( 0 ).y );
2017-03-20 13:55:32 +00:00
poly_ko->AppendPoint( mapPt( pos ) );
}
}
}
typedef std::set<std::string> STRINGSET;
typedef std::pair<STRINGSET::iterator, bool> STRINGSET_PAIR;
2008-02-14 01:07:52 +00:00
void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
2008-01-22 20:48:02 +00:00
{
std::shared_ptr<NET_SETTINGS>& netSettings = aBoard->GetDesignSettings().m_NetSettings;
2008-02-07 20:23:58 +00:00
// Not all boards are exportable. Check that all reference Ids are unique, or we won't be
// able to import the session file which comes back to us later from the router.
2008-02-09 08:34:45 +00:00
{
STRINGSET refs; // holds footprint reference designators
2008-02-09 08:34:45 +00:00
for( FOOTPRINT* footprint : aBoard->Footprints() )
2008-02-09 08:34:45 +00:00
{
2020-11-13 15:15:52 +00:00
if( footprint->GetReference() == wxEmptyString )
2008-02-09 08:34:45 +00:00
{
THROW_IO_ERROR( wxString::Format( _( "Footprint with value of '%s' has an empty "
"reference designator." ),
footprint->GetValue() ) );
2008-02-09 08:34:45 +00:00
}
// if we cannot insert OK, that means the reference has been seen before.
2020-11-13 15:15:52 +00:00
STRINGSET_PAIR refpair = refs.insert( TO_UTF8( footprint->GetReference() ) );
2008-02-14 01:07:52 +00:00
if( !refpair.second ) // insert failed
2008-02-09 08:34:45 +00:00
{
THROW_IO_ERROR( wxString::Format( _( "Multiple footprints have the reference "
"designator '%s'." ),
footprint->GetReference() ) );
2008-02-09 08:34:45 +00:00
}
}
}
2008-02-07 20:23:58 +00:00
if( !m_pcb )
m_pcb = SPECCTRA_DB::MakePCB();
2008-01-22 20:48:02 +00:00
2008-01-31 01:30:52 +00:00
//-----<layer_descriptor>-----------------------------------------------
{
// Specctra wants top physical layer first, then going down to the bottom most physical
// layer in physical sequence.
2008-01-31 06:46:31 +00:00
buildLayerMaps( aBoard );
2008-02-07 20:23:58 +00:00
int layerCount = aBoard->GetCopperLayerCount();
2008-01-31 01:30:52 +00:00
for( int pcbNdx=0; pcbNdx<layerCount; ++pcbNdx )
{
LAYER* layer = new LAYER( m_pcb->m_structure );
m_pcb->m_structure->m_layers.push_back( layer );
2008-02-07 20:23:58 +00:00
layer->name = m_layerIds[pcbNdx];
2008-02-07 20:23:58 +00:00
2008-03-04 04:22:27 +00:00
DSN_T layerType;
switch( aBoard->GetLayerType( m_pcbLayer2kicad[pcbNdx] ) )
2008-03-04 04:22:27 +00:00
{
default:
case LT_SIGNAL: layerType = T_signal; break;
case LT_POWER: layerType = T_power; break;
// Freerouter does not support type "mixed", only signal and power.
// Remap "mixed" to "signal".
case LT_MIXED: layerType = T_signal; break;
case LT_JUMPER: layerType = T_jumper; break;
2008-03-04 04:22:27 +00:00
}
layer->layer_type = layerType;
2008-02-05 02:13:16 +00:00
layer->properties.push_back( PROPERTY() );
PROPERTY* property = &layer->properties.back();
2008-02-05 02:13:16 +00:00
property->name = "index";
property->value = std::to_string( pcbNdx );
2008-01-31 01:30:52 +00:00
}
}
// a space in a quoted token is NOT a terminator, true establishes this.
m_pcb->m_parser->space_in_quoted_tokens = true;
2008-02-07 20:23:58 +00:00
2008-01-23 01:52:49 +00:00
//-----<unit_descriptor> & <resolution_descriptor>--------------------
2008-02-07 20:23:58 +00:00
{
// Tell freerouter to use "tenths of micrometers", which is 100 nm resolution. Possibly
// more resolution is possible in freerouter, but it would need testing.
m_pcb->m_unit->units = T_um;
m_pcb->m_resolution->units = T_um;
m_pcb->m_resolution->value = 10; // tenths of a um
2008-01-23 01:52:49 +00:00
}
2008-01-22 20:48:02 +00:00
2008-01-23 01:52:49 +00:00
//-----<boundary_descriptor>------------------------------------------
2008-01-22 20:48:02 +00:00
{
// Because fillBOUNDARY() can throw an exception, we link in an empty boundary so the
// BOUNDARY does not get lost in the event of of an exception.
BOUNDARY* boundary = new BOUNDARY( nullptr );
m_pcb->m_structure->SetBOUNDARY( boundary );
fillBOUNDARY( aBoard, boundary );
2008-01-22 20:48:02 +00:00
}
2008-01-31 06:46:31 +00:00
//-----<rules>--------------------------------------------------------
{
2020-05-18 00:20:16 +00:00
char rule[80];
int defaultTrackWidth = netSettings->m_DefaultNetClass->GetTrackWidth();
int defaultClearance = netSettings->m_DefaultNetClass->GetClearance();
double clearance = scale( defaultClearance );
2008-02-09 16:33:03 +00:00
STRINGS& rules = m_pcb->m_structure->m_rules->m_rules;
2008-02-07 20:23:58 +00:00
std::snprintf( rule, sizeof( rule ), "(width %.6g)", scale( defaultTrackWidth ) );
2008-01-31 06:46:31 +00:00
rules.push_back( rule );
2008-02-07 20:23:58 +00:00
std::snprintf( rule, sizeof( rule ), "(clearance %.6g)", clearance + safetyMargin );
2008-10-23 21:06:43 +00:00
rules.push_back( rule );
// On a high density board (4 mil tracks, 4 mil spacing) a typical solder mask clearance
// will be 2-3 mils. This exposes 2 to 3 mils of bare board around each pad, and would
// leave only 1 to 2 mils of solder mask between the solder mask's boundary and the edge of
// any trace within "clearance" of the pad. So we need at least 2 mils *extra* clearance
// for traces which would come near a pad on a different net. So if the baseline trace to
// trace clearance was 4 mils, then the SMD to trace clearance should be at least 6 mils.
double default_smd = clearance + safetyMargin;
if( default_smd <= 6.0 )
default_smd = 6.0;
std::snprintf( rule, sizeof( rule ), "(clearance %.6g (type default_smd))", default_smd );
2008-01-31 06:46:31 +00:00
rules.push_back( rule );
2008-02-07 20:23:58 +00:00
// Pad to pad spacing on a single SMT part can be closer than our clearance. We don't want
// freerouter complaining about that, so output a significantly smaller pad to pad
// clearance to freerouter.
clearance = scale( defaultClearance ) / 4;
2008-02-09 16:33:03 +00:00
std::snprintf( rule, sizeof( rule ), "(clearance %.6g (type smd_smd))", clearance );
2008-01-31 06:46:31 +00:00
rules.push_back( rule );
}
2008-02-07 20:23:58 +00:00
2020-11-19 22:35:32 +00:00
//-----<zones (not keepout areas) become planes>--------------------------------
// Note: only zones are output here, keepout areas are created later.
2008-01-23 22:36:37 +00:00
{
int netlessZones = 0;
for( ZONE* zone : aBoard->Zones() )
2008-01-23 22:36:37 +00:00
{
if( zone->GetIsRuleArea() )
continue;
// Currently, we export only copper layers
2023-03-07 14:08:23 +00:00
if( ! zone->IsOnCopperLayer() )
continue;
// Now, build zone polygon on each copper layer where the zone
// is living (zones can live on many copper layers)
const int copperCount = aBoard->GetCopperLayerCount();
2008-05-22 15:55:24 +00:00
for( int layer = 0; layer < copperCount; layer++ )
{
if( layer == copperCount-1 )
layer = B_Cu;
if( !zone->IsOnLayer( PCB_LAYER_ID( layer ) ) )
continue;
2008-01-31 01:30:52 +00:00
COPPER_PLANE* plane = new COPPER_PLANE( m_pcb->m_structure );
m_pcb->m_structure->m_planes.push_back( plane );
PATH* mainPolygon = new PATH( plane, T_polygon );
plane->SetShape( mainPolygon );
plane->m_name = TO_UTF8( zone->GetNetname() );
if( plane->m_name.size() == 0 )
{
// This is one of those no connection zones, netcode=0, and it has no name.
// Create a unique, bogus netname.
NET* no_net = new NET( m_pcb->m_network );
2008-02-07 20:23:58 +00:00
no_net->m_net_id = "@:no_net_" + std::to_string( netlessZones++ );
// add the bogus net name to network->nets.
m_pcb->m_network->m_nets.push_back( no_net );
// use the bogus net name in the netless zone.
plane->m_name = no_net->m_net_id;
}
mainPolygon->layer_id = m_layerIds[ m_kicadLayer2pcb[ layer ] ];
2009-02-06 14:23:56 +00:00
// Handle the main outlines
SHAPE_POLY_SET::ITERATOR iterator;
VECTOR2I startpoint;
bool is_first_point = true;
for( iterator = zone->IterateWithHoles(); iterator; iterator++ )
{
VECTOR2I point( iterator->x, iterator->y );
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() )
{
// Close polygon
mainPolygon->AppendPoint( mapPt( startpoint ) );
break;
}
}
2009-02-06 14:23:56 +00:00
WINDOW* window = nullptr;
PATH* cutout = nullptr;
2009-02-06 14:23:56 +00:00
bool isStartContour = true;
// handle the cutouts
for( iterator++; iterator; iterator++ )
2009-02-06 14:23:56 +00:00
{
if( isStartContour )
{
is_first_point = true;
window = new WINDOW( plane );
plane->AddWindow( window );
2009-02-06 14:23:56 +00:00
cutout = new PATH( window, T_polygon );
window->SetShape( cutout );
2023-03-08 10:37:56 +00:00
cutout->layer_id = m_layerIds[ m_kicadLayer2pcb[ layer ] ];
}
2009-02-06 14:23:56 +00:00
// If the point in this iteration is the last of the contour, the next iteration
// will start with a new contour.
isStartContour = iterator.IsEndContour();
wxASSERT( window );
wxASSERT( cutout );
2009-02-06 14:23:56 +00:00
VECTOR2I point( iterator->x, iterator->y );
if( is_first_point )
{
startpoint = point;
is_first_point = false;
}
cutout->AppendPoint( mapPt( point ) );
// Close the polygon
if( iterator.IsEndContour() )
cutout->AppendPoint( mapPt( startpoint ) );
}
} // end build zones by layer
2008-01-23 22:36:37 +00:00
}
}
2008-01-24 21:47:54 +00:00
2020-11-19 22:35:32 +00:00
//-----<zones flagged keepout areas become keepout>--------------------------------
{
for( ZONE* zone : aBoard->Zones() )
{
if( !zone->GetIsRuleArea() )
continue;
// Keepout areas have a type: 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->m_structure, keepout_type );
m_pcb->m_structure->m_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;
VECTOR2I startpoint;
for( iterator = zone->IterateWithHoles(); iterator; iterator++ )
{
VECTOR2I point( iterator->x, iterator->y );
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 );
2023-03-07 13:08:03 +00:00
cutout->layer_id = m_layerIds[ m_kicadLayer2pcb[ layer ] ];
}
isStartContour = iterator.IsEndContour();
wxASSERT( window );
wxASSERT( cutout );
VECTOR2I point( iterator->x, iterator->y );
if( is_first_point )
{
startpoint = point;
is_first_point = false;
}
cutout->AppendPoint( mapPt(point) );
// Close the polygon
if( iterator.IsEndContour() )
cutout->AppendPoint( mapPt( startpoint ) );
}
}
}
}
2008-02-07 20:23:58 +00:00
2008-02-14 01:07:52 +00:00
//-----<build the images, components, and netlist>-----------------------
2008-01-23 22:36:37 +00:00
{
PIN_REF empty( m_pcb->m_network );
std::string componentId;
int highestNetCode = 0;
NETINFO_LIST& netInfo = aBoard->GetNetInfo();
// find the highest numbered netCode within the board.
for( NETINFO_LIST::iterator i = netInfo.begin(); i != netInfo.end(); ++i )
highestNetCode = std::max( highestNetCode, i->GetNetCode() );
2008-02-14 01:07:52 +00:00
deleteNETs();
2008-01-24 21:47:54 +00:00
2008-02-14 01:07:52 +00:00
// expand the net vector to highestNetCode+1, setting empty to NULL
2021-07-19 23:56:05 +00:00
m_nets.resize( highestNetCode + 1, nullptr );
2008-02-07 20:23:58 +00:00
for( unsigned i = 1 /* skip "No Net" at [0] */; i < m_nets.size(); ++i )
m_nets[i] = new NET( m_pcb->m_network );
2008-01-29 16:45:14 +00:00
for( NETINFO_LIST::iterator i = netInfo.begin(); i != netInfo.end(); ++i )
2008-02-14 01:07:52 +00:00
{
if( i->GetNetCode() > 0 )
m_nets[i->GetNetCode()]->m_net_id = TO_UTF8( i->GetNetname() );
2008-02-14 01:07:52 +00:00
}
2008-02-07 20:23:58 +00:00
m_padstackset.clear();
2008-02-14 01:07:52 +00:00
for( FOOTPRINT* footprint : aBoard->Footprints() )
2008-01-23 22:36:37 +00:00
{
IMAGE* image = makeIMAGE( aBoard, footprint );
2008-02-14 01:07:52 +00:00
2020-11-13 15:15:52 +00:00
componentId = TO_UTF8( footprint->GetReference() );
2008-02-29 06:49:34 +00:00
// Create a net list entry for all the actual pins in the current footprint.
// Location of this code is critical because we fabricated some pin names to ensure
// unique-ness within a footprint, and the life of this 'IMAGE* image' is not
// necessarily long. The exported netlist will have some fabricated pin names in it.
// If you don't like fabricated pin names, then make sure all pads within your
// FOOTPRINTs are uniquely named!
for( unsigned p = 0; p < image->m_pins.size(); ++p )
2008-02-14 01:07:52 +00:00
{
PIN* pin = &image->m_pins[p];
int netcode = pin->m_kiNetCode;
2008-02-14 01:07:52 +00:00
if( netcode > 0 )
{
NET* net = m_nets[netcode];
2008-02-14 01:07:52 +00:00
net->m_pins.push_back( empty );
2008-02-14 01:07:52 +00:00
PIN_REF& pin_ref = net->m_pins.back();
2008-02-14 01:07:52 +00:00
2008-02-29 06:49:34 +00:00
pin_ref.component_id = componentId;
pin_ref.pin_id = pin->m_pin_id;
2008-02-14 01:07:52 +00:00
}
}
IMAGE* registered = m_pcb->m_library->LookupIMAGE( image );
if( registered != image )
2008-01-24 21:47:54 +00:00
{
// If our new 'image' is not a unique IMAGE, delete it.
2008-02-14 01:07:52 +00:00
// and use the registered one, known as 'image' after this.
2008-01-29 16:45:14 +00:00
delete image;
2008-02-14 01:07:52 +00:00
image = registered;
2008-01-29 16:45:14 +00:00
}
COMPONENT* comp = m_pcb->m_placement->LookupCOMPONENT( image->GetImageId() );
PLACE* place = new PLACE( comp );
2008-02-07 20:23:58 +00:00
comp->m_places.push_back( place );
2008-02-07 20:23:58 +00:00
2020-11-13 15:15:52 +00:00
place->SetRotation( footprint->GetOrientationDegrees() );
place->SetVertex( mapPt( footprint->GetPosition() ) );
place->m_component_id = componentId;
place->m_part_number = TO_UTF8( footprint->GetValue() );
2008-02-07 20:23:58 +00:00
// footprint is flipped from bottom side, set side to T_back
2020-11-13 15:15:52 +00:00
if( footprint->GetFlag() )
2008-02-05 02:13:16 +00:00
{
EDA_ANGLE angle = ANGLE_180 - footprint->GetOrientation();
place->SetRotation( angle.Normalize().AsDegrees() );
2008-02-07 20:23:58 +00:00
place->m_side = T_back;
2008-02-05 02:13:16 +00:00
}
2008-01-29 16:45:14 +00:00
}
2008-02-07 20:23:58 +00:00
2008-02-14 01:07:52 +00:00
// copy the SPECCTRA_DB::padstackset to the LIBRARY. Since we are
// removing, do not increment the iterator
for( PADSTACKSET::iterator i = m_padstackset.begin(); i != m_padstackset.end();
i = m_padstackset.begin() )
2008-02-14 01:07:52 +00:00
{
PADSTACKSET::auto_type ps = m_padstackset.release( i );
2008-02-14 01:07:52 +00:00
PADSTACK* padstack = ps.release();
2008-02-07 20:23:58 +00:00
m_pcb->m_library->AddPadstack( padstack );
2008-02-14 01:07:52 +00:00
}
2008-02-07 20:23:58 +00:00
2008-02-14 01:07:52 +00:00
// copy our SPECCTRA_DB::nets to the pcb->network
for( unsigned n = 1; n < m_nets.size(); ++n )
2008-02-14 01:07:52 +00:00
{
NET* net = m_nets[n];
if( net->m_pins.size() )
2008-02-14 01:07:52 +00:00
{
// give ownership to pcb->network
m_pcb->m_network->m_nets.push_back( net );
m_nets[n] = nullptr;
2008-02-14 01:07:52 +00:00
}
}
}
//-----< output vias used in netclasses >-----------------------------------
2008-02-14 01:07:52 +00:00
{
// Assume the netclass vias are all the same kind of thru, blind, or buried vias.
// This is in lieu of either having each netclass via have its own layer pair in
// the netclass dialog, or such control in the specctra export dialog.
m_top_via_layer = 0; // first specctra cu layer is number zero.
m_bot_via_layer = aBoard->GetCopperLayerCount()-1;
// Add the via from the Default netclass first. The via container
// in pcb->library preserves the sequence of addition.
2008-02-14 01:07:52 +00:00
PADSTACK* via = makeVia( netSettings->m_DefaultNetClass->GetViaDiameter(),
netSettings->m_DefaultNetClass->GetViaDrill(),
m_top_via_layer, m_bot_via_layer );
2008-02-07 20:23:58 +00:00
// we AppendVia() this first one, there is no way it can be a duplicate,
// the pcb->library via container is empty at this point. After this,
// we'll have to use LookupVia().
wxASSERT( m_pcb->m_library->m_vias.size() == 0 );
m_pcb->m_library->AppendVia( via );
// set the "spare via" index at the start of the
// pcb->library->spareViaIndex = pcb->library->vias.size();
// output the non-Default netclass vias
for( const auto& [ name, netclass ] : netSettings->m_NetClasses )
{
via = makeVia( netclass->GetViaDiameter(), netclass->GetViaDrill(),
m_top_via_layer, m_bot_via_layer );
// maybe add 'via' to the library, but only if unique.
PADSTACK* registered = m_pcb->m_library->LookupVia( via );
if( registered != via )
delete via;
}
}
2008-01-30 19:16:46 +00:00
//-----<create the wires from tracks>-----------------------------------
{
// export all of them for now, later we'll decide what controls we need on this.
2008-02-07 20:23:58 +00:00
std::string netname;
WIRING* wiring = m_pcb->m_wiring;
PATH* path = nullptr;
2008-01-31 01:30:52 +00:00
2008-02-07 20:23:58 +00:00
int old_netcode = -1;
int old_width = -1;
2021-07-21 23:14:56 +00:00
int old_layer = UNDEFINED_LAYER;
2008-01-31 01:30:52 +00:00
for( PCB_TRACK* track : aBoard->Tracks() )
2008-01-30 19:16:46 +00:00
{
if( !track->IsType( { PCB_TRACE_T, PCB_ARC_T } ) )
continue;
int netcode = track->GetNetCode();
2008-02-29 06:49:34 +00:00
if( netcode == 0 )
2008-01-31 01:30:52 +00:00
continue;
if( old_netcode != netcode
|| old_width != track->GetWidth()
|| old_layer != track->GetLayer()
|| ( path && path->points.back() != mapPt( track->GetStart() ) ) )
2008-01-31 01:30:52 +00:00
{
old_width = track->GetWidth();
2008-01-31 06:46:31 +00:00
old_layer = track->GetLayer();
2008-01-31 01:30:52 +00:00
2008-02-29 06:49:34 +00:00
if( old_netcode != netcode )
2008-01-31 01:30:52 +00:00
{
2008-02-29 06:49:34 +00:00
old_netcode = netcode;
NETINFO_ITEM* net = aBoard->FindNet( netcode );
wxASSERT( net );
netname = TO_UTF8( net->GetNetname() );
2008-01-31 01:30:52 +00:00
}
2008-01-31 06:46:31 +00:00
WIRE* wire = new WIRE( wiring );
2008-01-31 06:46:31 +00:00
wiring->wires.push_back( wire );
wire->m_net_id = netname;
2008-02-07 20:23:58 +00:00
if( track->IsLocked() )
wire->m_wire_type = T_fix; // tracks with fix property are not returned in .ses files
else
wire->m_wire_type = T_route; // could be T_protect
2008-02-07 20:23:58 +00:00
2021-07-21 23:14:56 +00:00
int kiLayer = track->GetLayer();
int pcbLayer = m_kicadLayer2pcb[kiLayer];
2008-02-07 20:23:58 +00:00
2008-01-31 06:46:31 +00:00
path = new PATH( wire );
wire->SetShape( path );
path->layer_id = m_layerIds[pcbLayer];
2008-01-31 06:46:31 +00:00
path->aperture_width = scale( old_width );
path->AppendPoint( mapPt( track->GetStart() ) );
2008-01-31 01:30:52 +00:00
}
2008-02-07 20:23:58 +00:00
2015-11-11 18:47:51 +00:00
if( path ) // Should not occur
path->AppendPoint( mapPt( track->GetEnd() ) );
2008-01-30 19:16:46 +00:00
}
2008-02-01 01:09:39 +00:00
}
2008-02-07 20:23:58 +00:00
2008-02-09 08:34:45 +00:00
//-----<export the existing real BOARD instantiated vias>-----------------
2008-02-01 01:09:39 +00:00
{
// Export all vias, once per unique size and drill diameter combo.
for( PCB_TRACK* track : aBoard->Tracks() )
2008-02-01 01:09:39 +00:00
{
if( track->Type() != PCB_VIA_T )
continue;
2008-02-07 20:23:58 +00:00
PCB_VIA* via = static_cast<PCB_VIA*>( track );
2021-06-11 21:07:02 +00:00
int netcode = via->GetNetCode();
2008-02-21 06:04:32 +00:00
if( netcode == 0 )
continue;
PADSTACK* padstack = makeVia( via );
PADSTACK* registered = m_pcb->m_library->LookupVia( padstack );
// if the one looked up is not our padstack, then delete our padstack
// since it was a duplicate of one already registered.
2008-02-01 01:09:39 +00:00
if( padstack != registered )
delete padstack;
WIRE_VIA* dsnVia = new WIRE_VIA( m_pcb->m_wiring );
m_pcb->m_wiring->wire_vias.push_back( dsnVia );
2008-02-07 20:23:58 +00:00
dsnVia->m_padstack_id = registered->m_padstack_id;
dsnVia->m_vertexes.push_back( mapPt( via->GetPosition() ) );
2008-02-07 20:23:58 +00:00
NETINFO_ITEM* net = aBoard->FindNet( netcode );
wxASSERT( net );
2008-02-07 20:23:58 +00:00
dsnVia->m_net_id = TO_UTF8( net->GetNetname() );
2008-02-07 20:23:58 +00:00
if( via->IsLocked() )
dsnVia->m_via_type = T_fix; // vias with fix property are not returned in .ses files
else
dsnVia->m_via_type = T_route; // could be T_protect
2008-02-01 01:09:39 +00:00
}
}
2008-02-07 20:23:58 +00:00
2008-02-01 01:09:39 +00:00
//-----<via_descriptor>-------------------------------------------------
{
// The pcb->library will output <padstack_descriptors> which is a combined list of part
// padstacks and via padstacks. specctra dsn uses the <via_descriptors> to say which of
// those padstacks are vias.
// Output the vias in the padstack list here, by name only. This must be done after
// exporting existing vias as WIRE_VIAs.
VIA* vias = m_pcb->m_structure->m_via;
2008-02-01 01:09:39 +00:00
for( unsigned viaNdx = 0; viaNdx < m_pcb->m_library->m_vias.size(); ++viaNdx )
vias->AppendVia( m_pcb->m_library->m_vias[viaNdx].m_padstack_id.c_str() );
2008-01-30 19:16:46 +00:00
}
2008-02-01 01:09:39 +00:00
2009-09-10 15:22:26 +00:00
//-----<output NETCLASSs>----------------------------------------------------
exportNETCLASS( netSettings->m_DefaultNetClass, aBoard );
2009-09-10 15:22:26 +00:00
for( const auto& [ name, netclass ] : netSettings->m_NetClasses )
exportNETCLASS( netclass, aBoard );
2009-09-10 15:22:26 +00:00
}
2008-02-29 06:49:34 +00:00
void SPECCTRA_DB::exportNETCLASS( const std::shared_ptr<NETCLASS>& aNetClass, BOARD* aBoard )
2009-09-10 15:22:26 +00:00
{
/* From page 11 of specctra spec:
*
* Routing and Placement Rule Hierarchies
*
* Routing and placement rules can be defined at multiple levels of design
* specification. When a routing or placement rule is defined for an object at
* multiple levels, a predefined routing or placement precedence order
* automatically determines which rule to apply to the object. The routing rule
* precedence order is
*
* pcb < layer < class < class layer < group_set < group_set layer < net <
* net layer < group < group layer < fromto < fromto layer < class_class <
* class_class layer < padstack < region < class region < net region <
* class_class region
*
* A pcb rule (global rule for the PCB design) has the lowest precedence in the
* hierarchy. A class-to-class region rule has the highest precedence. Rules
* set at one level of the hierarchy override conflicting rules set at lower
* levels. The placement rule precedence order is
*
* pcb < image_set < image < component < super cluster < room <
* room_image_set < family_family < image_image
*
* A pcb rule (global rule for the PCB design) has the lowest precedence in the
* hierarchy. An image-to-image rule has the highest precedence. Rules set at
* one level of the hierarchy override conflicting rules set at lower levels.
*/
char text[256];
2008-02-29 06:49:34 +00:00
CLASS* clazz = new CLASS( m_pcb->m_network );
m_pcb->m_network->m_classes.push_back( clazz );
2008-02-29 06:49:34 +00:00
// Freerouter creates a class named 'default' anyway, and if we try to use that we end up
// with two 'default' via rules so use something else as the name of our default class.
clazz->m_class_id = TO_UTF8( aNetClass->GetName() );
2008-02-29 06:49:34 +00:00
for( NETINFO_ITEM* net : aBoard->GetNetInfo() )
{
if( net->GetNetClass()->GetName() == clazz->m_class_id )
clazz->m_net_ids.push_back( TO_UTF8( net->GetNetname() ) );
}
2008-02-14 15:34:40 +00:00
clazz->m_rules = new RULE( clazz, T_rule );
2009-09-10 15:22:26 +00:00
// output the track width.
int trackWidth = aNetClass->GetTrackWidth();
std::snprintf( text, sizeof( text ), "(width %.6g)", scale( trackWidth ) );
clazz->m_rules->m_rules.push_back( text );
2009-09-10 15:22:26 +00:00
// output the clearance.
int clearance = aNetClass->GetClearance();
std::snprintf( text, sizeof( text ), "(clearance %.6g)", scale( clearance ) + safetyMargin );
clazz->m_rules->m_rules.push_back( text );
2009-09-10 15:22:26 +00:00
if( aNetClass->GetName() == NETCLASS::Default )
clazz->m_class_id = "kicad_default";
// The easiest way to get the via name is to create a temporary via (which generates the
// name internal to the PADSTACK), and then grab the name and delete the via. There are not
// that many netclasses so this should never become a performance issue.
PADSTACK* via = makeVia( aNetClass->GetViaDiameter(), aNetClass->GetViaDrill(),
m_top_via_layer, m_bot_via_layer );
snprintf( text, sizeof(text), "(use_via %s)", via->GetPadstackId().c_str() );
clazz->m_circuit.push_back( text );
delete via;
2008-02-14 15:34:40 +00:00
}
void SPECCTRA_DB::FlipFOOTPRINTs( BOARD* aBoard )
2008-02-14 15:34:40 +00:00
{
// DSN Images (=KiCad FOOTPRINTs and PADs) must be presented from the top view.
// Note: to export footprints, the footprints must be flipped around the X axis, otherwise
// the rotation angle is not good.
for( FOOTPRINT* footprint : aBoard->Footprints() )
2008-02-14 15:34:40 +00:00
{
footprint->SetFlag( 0 );
if( footprint->GetLayer() == B_Cu )
2008-02-14 15:34:40 +00:00
{
footprint->Flip( footprint->GetPosition(), false );
footprint->SetFlag( 1 );
2008-02-14 15:34:40 +00:00
}
}
m_footprintsAreFlipped = true;
2008-02-14 15:34:40 +00:00
}
void SPECCTRA_DB::RevertFOOTPRINTs( BOARD* aBoard )
2008-02-14 15:34:40 +00:00
{
if( !m_footprintsAreFlipped )
2008-02-14 15:34:40 +00:00
return;
2008-02-07 20:23:58 +00:00
2020-11-13 15:15:52 +00:00
// DSN Images (=KiCad FOOTPRINTs and PADs) must be presented from the
// top view. Restore those that were flipped.
// Note: to export footprints, the footprints were flipped around the X axis,
for( FOOTPRINT* footprint : aBoard->Footprints() )
2008-01-25 22:03:36 +00:00
{
if( footprint->GetFlag() )
2008-01-25 22:03:36 +00:00
{
footprint->Flip( footprint->GetPosition(), false );
footprint->SetFlag( 0 );
2008-01-25 22:03:36 +00:00
}
}
2008-02-14 15:34:40 +00:00
m_footprintsAreFlipped = false;
2008-01-22 20:48:02 +00:00
}
2008-01-21 21:24:39 +00:00
} // namespace DSN