954 lines
33 KiB
C++
954 lines
33 KiB
C++
/*
|
|
* This program source code file is part of KICAD, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2007-2008 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
|
|
* Copyright (C) 2007 Kicad Developers, see change_log.txt for contributors.
|
|
*
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* 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.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
|
|
/* 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:
|
|
http://www.autotraxeda.com/docs/SPECCTRA/SPECCTRA.pdf
|
|
|
|
Also see the comments at the top of the specctra.cpp file itself.
|
|
*/
|
|
|
|
|
|
#include "specctra.h"
|
|
#include "collectors.h"
|
|
#include "wxPcbStruct.h" // Change_Side_Module()
|
|
#include "pcbstruct.h" // HISTORY_NUMBER
|
|
|
|
using namespace DSN;
|
|
|
|
|
|
// see wxPcbStruct.h
|
|
void WinEDA_PcbFrame::ExportToSPECCTRA( wxCommandEvent& event )
|
|
{
|
|
wxString fullFileName = GetScreen()->m_FileName;
|
|
wxString std_ext = wxT( ".dsn" );
|
|
wxString mask = wxT( "*" ) + std_ext;
|
|
|
|
ChangeFileNameExt( fullFileName, std_ext );
|
|
|
|
fullFileName = EDA_FileSelector( _( "Specctra DSN file:" ),
|
|
wxEmptyString, /* Chemin par defaut */
|
|
fullFileName, /* nom fichier par defaut */
|
|
std_ext, /* extension par defaut */
|
|
mask, /* Masque d'affichage */
|
|
this,
|
|
wxFD_SAVE,
|
|
FALSE
|
|
);
|
|
if( fullFileName == wxEmptyString )
|
|
return;
|
|
|
|
|
|
SPECCTRA_DB db;
|
|
bool ok = true;
|
|
wxString errorText;
|
|
|
|
db.SetPCB( SPECCTRA_DB::MakePCB() );
|
|
|
|
try
|
|
{
|
|
db.FromBOARD( m_Pcb );
|
|
db.ExportPCB( fullFileName, true );
|
|
|
|
// if an exception is thrown by FromBOARD or ExportPCB(), then
|
|
// ~SPECCTRA_DB() will close the file.
|
|
}
|
|
catch( IOError ioe )
|
|
{
|
|
ok = false;
|
|
|
|
// copy the error string to safe place, ioe is in this scope only.
|
|
errorText = ioe.errorText;
|
|
}
|
|
|
|
if( ok )
|
|
{
|
|
// @todo display a message saying the export is complete.
|
|
}
|
|
else
|
|
DisplayError( this, errorText );
|
|
}
|
|
|
|
|
|
namespace DSN {
|
|
|
|
struct POINT_PAIR
|
|
{
|
|
POINT p1; ///< start
|
|
POINT p2; ///< end
|
|
BOARD_ITEM* item; ///< the item which has these points, TRACK or DRAWSEGMENT
|
|
};
|
|
typedef std::vector<POINT_PAIR> POINT_PAIRS;
|
|
|
|
|
|
static inline void swap( POINT_PAIR& pair )
|
|
{
|
|
POINT temp = pair.p1;
|
|
pair.p1 = pair.p2;
|
|
pair.p2 = temp;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function scale
|
|
* converts a distance from kicad units to our reported specctra dsn units:
|
|
* 1/10000 inches (deci-mils) to mils. So the factor of 10 comes in.
|
|
*/
|
|
static inline double scale( int kicadDist )
|
|
{
|
|
return kicadDist/10.0;
|
|
}
|
|
|
|
static inline double mapX( int x )
|
|
{
|
|
return scale(x);
|
|
}
|
|
|
|
static inline double mapY( int y )
|
|
{
|
|
return -scale(y); // make y negative, since it is increasing going down.
|
|
}
|
|
|
|
|
|
/**
|
|
* Function mapPt
|
|
* converts a Kicad point into a DSN file point. Kicad's BOARD coordinates
|
|
* are in deci-mils (i.e. 1/10,000th of an inch) and we are exporting in units
|
|
* of mils, so we have to divide by 10.
|
|
*/
|
|
static POINT mapPt( const wxPoint& pt )
|
|
{
|
|
POINT ret;
|
|
ret.x = mapX( pt.x );
|
|
ret.y = mapY( pt.y );
|
|
ret.FixNegativeZero();
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function swapEnds
|
|
* will swap ends of any POINT_PAIR in the POINT_PAIRS list in order to
|
|
* make the consecutive POINT_PAIRs be "connected" at their ends.
|
|
*/
|
|
static void swapEnds( POINT_PAIRS& aList )
|
|
{
|
|
POINT temp;
|
|
|
|
if( aList.size() <= 1 )
|
|
return;
|
|
|
|
for( unsigned i=0; i<aList.size(); ++i )
|
|
{
|
|
if( aList[i].p1 == aList[i+1].p1 )
|
|
swap( aList[i] );
|
|
|
|
else if( aList[i].p1 == aList[i+1].p2 )
|
|
{
|
|
swap( aList[i] );
|
|
swap( aList[i+1] );
|
|
++i; // skip next one, we swapped i+1 here
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Function isRectangle
|
|
* tests to see if the POINT_PAIRS list makes up a vertically/horizontally
|
|
* oriented rectangle.
|
|
* @return bool - true if there are 4 point pairs making a rectangle.
|
|
*/
|
|
static bool isRectangle( POINT_PAIRS& aList )
|
|
{
|
|
if( aList.size() == 4 )
|
|
{
|
|
for( unsigned i=0; i<aList.size(); ++i )
|
|
{
|
|
if( i < aList.size()-1 )
|
|
if( aList[i].p2 != aList[i+1].p1 )
|
|
return false;
|
|
|
|
if( aList[i].p1.x != aList[i].p2.x
|
|
&& aList[i].p1.y != aList[i].p2.y )
|
|
return false;
|
|
}
|
|
|
|
return ( aList[0].p1 == aList[3].p2 );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**************************************************************************/
|
|
static int Pad_list_Sort_by_Shapes( const void* refptr, const void* objptr )
|
|
/**************************************************************************/
|
|
{
|
|
const D_PAD* padref = *(D_PAD**)refptr;
|
|
const D_PAD* padcmp = *(D_PAD**)objptr;
|
|
|
|
return D_PAD::Compare( padref, padcmp );
|
|
}
|
|
|
|
|
|
/**
|
|
* Function makePath
|
|
* 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 )
|
|
{
|
|
PATH* path = new PATH( 0, T_path );
|
|
|
|
path->AppendPoint( aStart );
|
|
path->AppendPoint( aEnd );
|
|
path->SetLayerId( aLayerName.c_str() );
|
|
return path;
|
|
}
|
|
|
|
|
|
static QARC* makeArc( const POINT& aStart, const POINT& aEnd,
|
|
const POINT& aCenter, const std::string& aLayerName )
|
|
{
|
|
QARC* qarc = new QARC(0);
|
|
|
|
qarc->SetStart( aStart );
|
|
qarc->SetEnd( aEnd );
|
|
qarc->SetCenter( aCenter );
|
|
qarc->SetLayerId( aLayerName.c_str() );
|
|
return qarc;
|
|
}
|
|
|
|
|
|
IMAGE* SPECCTRA_DB::makeIMAGE( MODULE* aModule )
|
|
{
|
|
PADSTACKS& padstacks = pcb->library->padstacks;
|
|
|
|
TYPE_COLLECTOR pads;
|
|
static const KICAD_T scanPADs[] = { TYPEPAD, EOT };
|
|
|
|
// get all the MODULE's pads.
|
|
pads.Collect( aModule, scanPADs );
|
|
|
|
IMAGE* image = new IMAGE(0);
|
|
|
|
image->image_id = CONV_TO_UTF8( aModule->m_LibRef );
|
|
|
|
// from the pads, and make an IMAGE using collated padstacks.
|
|
for( int p=0; p<pads.GetCount(); ++p )
|
|
{
|
|
D_PAD* pad = (D_PAD*) pads[p];
|
|
|
|
// see if this pad is a through hole with no copper on its perimeter
|
|
if( !pad->IsOnLayer( LAYER_CMP_N ) && !pad->IsOnLayer( COPPER_LAYER_N ) )
|
|
{
|
|
if( pad->m_Drill.x!=0 )
|
|
{
|
|
KEEPOUT* keepout = new KEEPOUT(image, T_keepout);
|
|
image->keepouts.push_back( keepout );
|
|
|
|
WINDOW* window = new WINDOW(keepout);
|
|
keepout->windows.push_back( window );
|
|
|
|
CIRCLE* circle = new CIRCLE(window);
|
|
window->SetShape( circle );
|
|
|
|
circle->SetDiameter( scale(pad->m_Drill.x) );
|
|
circle->SetVertex( POINT( mapPt( pad->m_Pos0 ) ) );
|
|
circle->layer_id = "signal";
|
|
|
|
// ?? the keepout is not affecting the power layers?
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PADSTACK* padstack = &padstacks[pad->m_logical_connexion];
|
|
|
|
PIN* pin = new PIN(image);
|
|
image->pins.push_back( pin );
|
|
|
|
pin->padstack_id = padstack->padstack_id;
|
|
pin->pin_id = CONV_TO_UTF8( pad->ReturnStringPadName() );
|
|
pin->SetVertex( mapPt( pad->m_Pos0 ) );
|
|
}
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
|
|
void SPECCTRA_DB::makePADSTACKs( BOARD* aBoard, TYPE_COLLECTOR& aPads )
|
|
{
|
|
char name[80]; // padstack name builder
|
|
|
|
if( aPads.GetCount() )
|
|
{
|
|
qsort( (void*) aPads.BasePtr(), aPads.GetCount(), sizeof(D_PAD*), Pad_list_Sort_by_Shapes );
|
|
}
|
|
|
|
D_PAD* old_pad = NULL;
|
|
|
|
#define COPPER_LAYERS 2 // top and bottom
|
|
|
|
int reportedLayers = COPPER_LAYERS; // how many layers are reported.
|
|
|
|
|
|
// for now, report on only the top and bottom layers with respect to the copper
|
|
// within a pad's padstack. this is usually correct, but not rigorous. We could do
|
|
// better if there was actually a "layer type" field within Kicad which would
|
|
// hold one of: T_signal, T_power, T_mixed, T_jumper
|
|
// See bottom of page 74 of the SECCTRA Design Language Reference, May 2000.
|
|
|
|
std::string layerId[COPPER_LAYERS] = {
|
|
CONV_TO_UTF8(aBoard->GetLayerName( LAYER_CMP_N )),
|
|
CONV_TO_UTF8(aBoard->GetLayerName( COPPER_LAYER_N )),
|
|
};
|
|
|
|
#if 1
|
|
// Late breaking news: we can use the reserved layer name "signal" and report the
|
|
// padstack as a single layer. See <reserved_layer_name> in the spec.
|
|
// But this probably gives problems for a "power" layer or power pin, we'll see.
|
|
reportedLayers = 1;
|
|
layerId[0] = "signal";
|
|
#endif
|
|
|
|
for( int i=0; i<aPads.GetCount(); ++i )
|
|
{
|
|
D_PAD* pad = (D_PAD*) aPads[i];
|
|
|
|
bool doLayer[COPPER_LAYERS] = {
|
|
pad->IsOnLayer( LAYER_CMP_N ),
|
|
pad->IsOnLayer( COPPER_LAYER_N )
|
|
};
|
|
|
|
if( old_pad && 0==D_PAD::Compare( old_pad, pad ) )
|
|
{
|
|
// padstacks.size()-1 is the index of the matching padstack in LIBRARY::padstacks
|
|
pad->m_logical_connexion = pcb->library->padstacks.size()-1;
|
|
|
|
// this is the same as the last pad, so do not add it to the padstack list.
|
|
continue;
|
|
}
|
|
|
|
// if pad has no copper presence, then it will be made into
|
|
// an "image->keepout" later. No copper pad here, it is probably a hole.
|
|
if( !doLayer[0] && !doLayer[1] )
|
|
{
|
|
// padstacks.size()-1 is the index of the matching padstack in LIBRARY::padstacks
|
|
pad->m_logical_connexion = pcb->library->padstacks.size()-1;
|
|
|
|
continue;
|
|
}
|
|
|
|
#if 1 // late breaking news..... see above
|
|
doLayer[0] = true;
|
|
#endif
|
|
|
|
old_pad = pad;
|
|
|
|
PADSTACK* padstack = new PADSTACK( pcb->library );
|
|
pcb->library->AddPadstack( padstack );
|
|
|
|
// padstacks.size()-1 is the index of the matching padstack in LIBRARY::padstacks
|
|
pad->m_logical_connexion = pcb->library->padstacks.size()-1;
|
|
|
|
// paddOfset is the offset of copper shape relative to hole position,
|
|
// and pad->m_Pos is hole position. All shapes must be shifted by
|
|
// this distance, normally (0,0).
|
|
|
|
// Note that the y correction here is set negative.
|
|
POINT padOffset( scale(pad->m_Offset.x), -scale(pad->m_Offset.y) );
|
|
|
|
int coppers = 0;
|
|
|
|
switch( pad->m_PadShape )
|
|
{
|
|
default:
|
|
case PAD_CIRCLE:
|
|
{
|
|
double diameter = scale(pad->m_Size.x);
|
|
|
|
for( int layer=0; layer<reportedLayers; ++layer )
|
|
{
|
|
if( doLayer[layer] )
|
|
{
|
|
SHAPE* shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
|
|
CIRCLE* circle = new CIRCLE( shape );
|
|
shape->SetShape( circle );
|
|
|
|
circle->SetLayerId( layerId[layer].c_str() );
|
|
circle->SetDiameter( diameter );
|
|
circle->SetVertex( padOffset );
|
|
++coppers;
|
|
}
|
|
}
|
|
|
|
snprintf( name, sizeof(name), "Round%dPad_%.6g_mil", coppers, scale(pad->m_Size.x) );
|
|
name[ sizeof(name)-1 ] = 0;
|
|
|
|
// @todo verify that all pad names are unique, there is a chance that
|
|
// D_PAD::Compare() could say two pads are different, yet the get the same
|
|
// name here. If so, blend in the padNdx into the name.
|
|
|
|
padstack->SetPadstackId( name );
|
|
}
|
|
break;
|
|
|
|
case PAD_RECT:
|
|
{
|
|
double dx = scale( pad->m_Size.x ) / 2.0;
|
|
double dy = scale( pad->m_Size.y ) / 2.0;
|
|
|
|
POINT lowerLeft( -dx, -dy );
|
|
POINT upperRight( dx, dy );
|
|
|
|
lowerLeft += padOffset;
|
|
upperRight += padOffset;
|
|
|
|
for( int layer=0; layer<reportedLayers; ++layer )
|
|
{
|
|
if( doLayer[layer] )
|
|
{
|
|
SHAPE* shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
|
|
RECTANGLE* rect = new RECTANGLE( shape );
|
|
shape->SetShape( rect );
|
|
|
|
rect->SetLayerId( layerId[layer].c_str() );
|
|
rect->SetCorners( lowerLeft, upperRight );
|
|
++coppers;
|
|
}
|
|
}
|
|
|
|
snprintf( name, sizeof(name), "Rect%dPad_%.6gx%.6g_mil",
|
|
coppers, scale(pad->m_Size.x), scale(pad->m_Size.y) );
|
|
name[ sizeof(name)-1 ] = 0;
|
|
|
|
// @todo verify that all pad names are unique, there is a chance that
|
|
// D_PAD::Compare() could say two pads are different, yet they get the same
|
|
// name here. If so, blend in the padNdx into the name.
|
|
|
|
padstack->SetPadstackId( name );
|
|
}
|
|
break;
|
|
|
|
case PAD_OVAL:
|
|
{
|
|
double dx = scale( pad->m_Size.x ) / 2.0;
|
|
double dy = scale( pad->m_Size.y ) / 2.0;
|
|
double dr = dx - dy;
|
|
|
|
if( dr >= 0 ) // oval is horizontal
|
|
{
|
|
double radius = dy;
|
|
|
|
for( int layer=0; layer<reportedLayers; ++layer )
|
|
{
|
|
if( doLayer[layer] )
|
|
{
|
|
// each oval is 2 lines and 4 (quarter circle) qarcs
|
|
|
|
SHAPE* shape;
|
|
PATH* path;
|
|
QARC* qarc;
|
|
|
|
shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
path = makePath(
|
|
POINT( -dr + padOffset.x, padOffset.y - radius ), // aStart
|
|
POINT( dr + padOffset.x, padOffset.y - radius ), // aEnd
|
|
layerId[layer] );
|
|
shape->SetShape( path );
|
|
|
|
shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
// @todo: this 1/2 circle arc needs to be split into two quarter circle arcs
|
|
qarc = makeArc(
|
|
POINT( dr + padOffset.x, padOffset.y - radius), // aStart
|
|
POINT( dr + padOffset.x, padOffset.y + radius), // aEnd
|
|
POINT( dr + padOffset.x, padOffset.y ), // aCenter
|
|
layerId[layer] );
|
|
shape->SetShape( qarc );
|
|
|
|
shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
path = makePath(
|
|
POINT( dr + padOffset.x, padOffset.y + radius ), // aStart
|
|
POINT( -dr + padOffset.x, padOffset.y + radius ), // aEnd
|
|
layerId[layer] );
|
|
shape->SetShape( path );
|
|
|
|
shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
// @todo: this 1/2 circle arc needs to be split into two quarter circle arcs
|
|
qarc = makeArc(
|
|
POINT( -dr + padOffset.x, padOffset.y + radius), // aStart
|
|
POINT( -dr + padOffset.x, padOffset.y - radius), // aEnd
|
|
POINT( -dr + padOffset.x, padOffset.y ), // aCenter
|
|
layerId[layer] );
|
|
shape->SetShape( qarc );
|
|
++coppers;
|
|
}
|
|
}
|
|
}
|
|
else // oval is vertical
|
|
{
|
|
double radius = dx;
|
|
|
|
dr = -dr;
|
|
|
|
for( int layer=0; layer<reportedLayers; ++layer )
|
|
{
|
|
if( doLayer[layer] )
|
|
{
|
|
// each oval is 2 lines and 2 qarcs
|
|
|
|
SHAPE* shape;
|
|
PATH* path;
|
|
QARC* qarc;
|
|
|
|
shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
path = makePath(
|
|
POINT( -radius + padOffset.x, padOffset.y - dr ), // aStart
|
|
POINT( -radius + padOffset.x, padOffset.y + dr ), // aEnd
|
|
layerId[layer] );
|
|
shape->SetShape( path );
|
|
|
|
shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
// @todo: this 1/2 circle arc needs to be split into two quarter circle arcs
|
|
qarc = makeArc(
|
|
POINT( -radius + padOffset.x, padOffset.y + dr ), // aStart
|
|
POINT( radius + padOffset.x, padOffset.y + dr), // aEnd
|
|
POINT( padOffset.x, padOffset.y +dr ), // aCenter
|
|
layerId[layer] );
|
|
shape->SetShape( qarc );
|
|
|
|
shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
path = makePath(
|
|
POINT( radius + padOffset.x, padOffset.y + dr ), // aStart
|
|
POINT( radius + padOffset.x, padOffset.y - dr ), // aEnd
|
|
layerId[layer] );
|
|
shape->SetShape( path );
|
|
|
|
shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
// @todo: this 1/2 circle arc needs to be split into two quarter circle arcs
|
|
qarc = makeArc(
|
|
POINT( radius + padOffset.x, padOffset.y - dr), // aStart
|
|
POINT( -radius + padOffset.x, padOffset.y - dr), // aEnd
|
|
POINT( padOffset.x, padOffset.y - dr ), // aCenter
|
|
layerId[layer] );
|
|
shape->SetShape( qarc );
|
|
++coppers;
|
|
}
|
|
}
|
|
}
|
|
|
|
snprintf( name, sizeof(name), "Oval%dPad_%.6gx%.6g_mil",
|
|
coppers, scale(pad->m_Size.x), scale(pad->m_Size.y) );
|
|
name[ sizeof(name)-1 ] = 0;
|
|
|
|
// @todo verify that all pad names are unique, there is a chance that
|
|
// D_PAD::Compare() could say two pads are different, yet they get the same
|
|
// name here. If so, blend in the padNdx into the name.
|
|
|
|
padstack->SetPadstackId( name );
|
|
}
|
|
break;
|
|
|
|
/*
|
|
case PAD_TRAPEZOID:
|
|
break;
|
|
*/
|
|
}
|
|
}
|
|
|
|
// unique pads are now in the padstack list.
|
|
// next we add the via's which may be used.
|
|
|
|
int defaultViaSize = aBoard->m_BoardSettings->m_CurrentViaSize;
|
|
if( defaultViaSize )
|
|
{
|
|
PADSTACK* padstack = new PADSTACK( pcb->library );
|
|
pcb->library->AddPadstack( padstack );
|
|
|
|
// remember this index, it is the default via and also the start of the
|
|
// vias within the padstack list. Before this index are the pads.
|
|
// At this index and later are the vias.
|
|
pcb->library->SetViaStartIndex( pcb->library->padstacks.size()-1 );
|
|
|
|
SHAPE* shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
|
|
CIRCLE* circle = new CIRCLE( shape );
|
|
shape->SetShape( circle );
|
|
circle->SetLayerId( layerId[0].c_str() );
|
|
circle->SetDiameter( scale(defaultViaSize) );
|
|
|
|
padstack->SetPadstackId( "Via_Default" );
|
|
}
|
|
|
|
for( int i=0; i<HISTORY_NUMBER; ++i )
|
|
{
|
|
int viaSize = aBoard->m_BoardSettings->m_ViaSizeHistory[i];
|
|
if( !viaSize )
|
|
break;
|
|
|
|
if( viaSize == defaultViaSize )
|
|
continue;
|
|
|
|
PADSTACK* padstack = new PADSTACK( pcb->library );
|
|
pcb->library->AddPadstack( padstack );
|
|
|
|
SHAPE* shape = new SHAPE( padstack );
|
|
padstack->Append( shape );
|
|
|
|
CIRCLE* circle = new CIRCLE( shape );
|
|
shape->SetShape( circle );
|
|
circle->SetLayerId( layerId[0].c_str() );
|
|
circle->SetDiameter( scale(viaSize) );
|
|
|
|
snprintf( name, sizeof(name), "Via_%.6g_mil", scale(viaSize) );
|
|
name[ sizeof(name)-1 ] = 0;
|
|
|
|
padstack->SetPadstackId( name );
|
|
}
|
|
}
|
|
|
|
|
|
void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
|
|
{
|
|
TYPE_COLLECTOR items;
|
|
POINT_PAIRS ppairs;
|
|
POINT_PAIR pair;
|
|
|
|
if( !pcb )
|
|
pcb = SPECCTRA_DB::MakePCB();
|
|
|
|
// DSN Images (=Kicad MODULES and pads) must be presented from the
|
|
// top view. So we temporarily flip any modules which are on the back
|
|
// side of the board to the front, and record this in the MODULE's flag field.
|
|
for( MODULE* module = aBoard->m_Modules; module; module = module->Next() )
|
|
{
|
|
module->flag = 0;
|
|
if( module->GetLayer() == COPPER_LAYER_N )
|
|
{
|
|
aBoard->Change_Side_Module( module, NULL );
|
|
module->flag = 1;
|
|
}
|
|
}
|
|
|
|
// a space in a quoted token is NOT a terminator, true establishes this.
|
|
pcb->parser->space_in_quoted_tokens = true;
|
|
|
|
//-----<unit_descriptor> & <resolution_descriptor>--------------------
|
|
{
|
|
pcb->unit->units = T_mil;
|
|
pcb->resolution->units = T_mil;
|
|
pcb->resolution->value = 100;
|
|
}
|
|
|
|
|
|
//-----<boundary_descriptor>------------------------------------------
|
|
{
|
|
// get all the DRAWSEGMENTS into 'items', then look for layer == EDGE_N,
|
|
// and those segments comprise the board's perimeter.
|
|
static const KICAD_T scanDRAWSEGMENTS[] = { TYPEDRAWSEGMENT, EOT };
|
|
items.Collect( aBoard, scanDRAWSEGMENTS );
|
|
|
|
bool haveEdges = false;
|
|
ppairs.clear();
|
|
for( int i=0; i<items.GetCount(); ++i )
|
|
{
|
|
DRAWSEGMENT* item = (DRAWSEGMENT*) items[i];
|
|
|
|
wxASSERT( item->Type() == TYPEDRAWSEGMENT );
|
|
|
|
if( item->GetLayer() == EDGE_N )
|
|
{
|
|
pair.p1 = mapPt( item->m_Start );
|
|
pair.p2 = mapPt( item->m_End );
|
|
pair.item = item;
|
|
ppairs.push_back( pair );
|
|
haveEdges = true;
|
|
}
|
|
}
|
|
|
|
if( haveEdges )
|
|
{
|
|
swapEnds( ppairs );
|
|
|
|
#if 0 && defined(DEBUG)
|
|
for( unsigned i=0; i<ppairs.size(); ++i )
|
|
{
|
|
POINT_PAIR* p = &ppairs[i];
|
|
p->item->Show( 0, std::cout );
|
|
}
|
|
#endif
|
|
|
|
BOUNDARY* boundary = new BOUNDARY(0);
|
|
|
|
if( isRectangle( ppairs ) )
|
|
{
|
|
RECTANGLE* rect = new RECTANGLE( boundary );
|
|
rect->layer_id = "pcb";
|
|
|
|
// opposite corners
|
|
rect->SetCorners( ppairs[0].p1, ppairs[2].p1 );
|
|
|
|
boundary->rectangle = rect;
|
|
}
|
|
else
|
|
{
|
|
PATH* path = new PATH( boundary );
|
|
|
|
path->layer_id = "pcb";
|
|
for( unsigned i=0; i<ppairs.size(); ++i )
|
|
{
|
|
// unless its a closed polygon, this probably won't work,
|
|
// otherwise it will.
|
|
path->points.push_back( ppairs[i].p1 );
|
|
}
|
|
|
|
boundary->paths.push_back( path );
|
|
}
|
|
|
|
pcb->structure->SetBOUNDARY( boundary );
|
|
}
|
|
else
|
|
{
|
|
aBoard->ComputeBoundaryBox();
|
|
|
|
BOUNDARY* boundary = new BOUNDARY(0);
|
|
RECTANGLE* rect = new RECTANGLE( boundary );
|
|
|
|
rect->layer_id = "pcb";
|
|
|
|
// opposite corners
|
|
wxPoint bottomRight;
|
|
bottomRight.x = aBoard->m_BoundaryBox.GetRight();
|
|
bottomRight.y = aBoard->m_BoundaryBox.GetBottom();
|
|
|
|
rect->SetCorners( mapPt( aBoard->m_BoundaryBox.GetOrigin() ),
|
|
mapPt( bottomRight ) );
|
|
|
|
boundary->rectangle = rect;
|
|
|
|
pcb->structure->SetBOUNDARY( boundary );
|
|
}
|
|
}
|
|
|
|
|
|
//-----<layer_descriptor>-----------------------------------------------
|
|
{
|
|
// specctra wants top physical layer first, then going down to the
|
|
// bottom most physical layer in physical sequence.
|
|
// @question : why does Kicad not display layers in that order?
|
|
int layerCount = aBoard->GetCopperLayerCount();
|
|
|
|
for( int ndx=layerCount-1; ndx >= 0; --ndx )
|
|
{
|
|
wxString layerName = aBoard->GetLayerName( ndx>0 && ndx==layerCount-1 ? LAYER_CMP_N : ndx );
|
|
LAYER* layer = new LAYER( pcb->structure );
|
|
|
|
layer->name = CONV_TO_UTF8( layerName );
|
|
|
|
// layer->type = @todo need this, the export would be better.
|
|
|
|
pcb->structure->layers.push_back( layer );
|
|
}
|
|
}
|
|
|
|
|
|
//-----<zone containers become planes>--------------------------------------------
|
|
{
|
|
static const KICAD_T scanZONEs[] = { TYPEZONE_CONTAINER, EOT };
|
|
items.Collect( aBoard, scanZONEs );
|
|
|
|
for( int i=0; i<items.GetCount(); ++i )
|
|
{
|
|
ZONE_CONTAINER* item = (ZONE_CONTAINER*) items[i];
|
|
|
|
wxString layerName = aBoard->GetLayerName( item->GetLayer() );
|
|
COPPER_PLANE* plane = new COPPER_PLANE( pcb->structure );
|
|
PATH* polygon = new PATH( plane, T_polygon );
|
|
|
|
plane->path = polygon;
|
|
plane->name = CONV_TO_UTF8( item->m_Netname );
|
|
|
|
polygon->layer_id = CONV_TO_UTF8( layerName );
|
|
|
|
int count = item->m_Poly->corner.size();
|
|
for( int j=0; j<count; ++j )
|
|
{
|
|
wxPoint point( item->m_Poly->corner[j].x, item->m_Poly->corner[j].y );
|
|
|
|
polygon->points.push_back( mapPt(point) );
|
|
}
|
|
|
|
pcb->structure->planes.push_back( plane );
|
|
}
|
|
}
|
|
|
|
// keepouts could go here, there are none in Kicad at this time.
|
|
// although COPPER_PLANEs probably will need them for the thru holes, etc.
|
|
// but in that case they are WINDOWs within the COPPER_PLANEs.
|
|
|
|
|
|
//-----<build the initial padstack list>--------------------------------
|
|
{
|
|
TYPE_COLLECTOR pads;
|
|
static const KICAD_T scanPADs[] = { TYPEPAD, EOT };
|
|
|
|
// get all the D_PADs into 'pads'.
|
|
pads.Collect( aBoard, scanPADs );
|
|
|
|
makePADSTACKs( aBoard, pads );
|
|
|
|
#if 0 && defined(DEBUG)
|
|
for( int p=0; p<pads.GetCount(); ++p )
|
|
pads[p]->Show( 0, std::cout );
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----<build the images and components>---------------------------------
|
|
{
|
|
static const KICAD_T scanMODULEs[] = { TYPEMODULE, EOT };
|
|
items.Collect( aBoard, scanMODULEs );
|
|
|
|
for( int m=0; m<items.GetCount(); ++m )
|
|
{
|
|
MODULE* module = (MODULE*) items[m];
|
|
|
|
IMAGE* image = makeIMAGE( module );
|
|
|
|
IMAGE* registered = pcb->library->LookupIMAGE( image );
|
|
if( registered != image )
|
|
{
|
|
// If our new 'image' is not a unique IMAGE, delete it.
|
|
// In either case, 'registered' is the one we'll work with henceforth.
|
|
delete image;
|
|
}
|
|
|
|
const std::string& imageId = registered->image_id;
|
|
|
|
COMPONENT* comp = pcb->placement->LookupCOMPONENT( imageId );
|
|
|
|
PLACE* place = new PLACE( comp );
|
|
comp->places.push_back( place );
|
|
|
|
place->SetRotation( module->m_Orient/10.0 );
|
|
place->SetVertex( mapPt( module->m_Pos ) );
|
|
place->component_id = CONV_TO_UTF8( module->GetReference() );
|
|
place->part_number = CONV_TO_UTF8( module->GetValue() );
|
|
|
|
// module is flipped from bottom side, set side to T_back
|
|
if( module->flag )
|
|
place->side = T_back;
|
|
}
|
|
}
|
|
|
|
|
|
//-----<via_descriptor>-------------------------------------------------
|
|
{
|
|
// Output the vias in the padstack list here, by name
|
|
VIA* vias = pcb->structure->via;
|
|
PADSTACKS& padstacks = pcb->library->padstacks;
|
|
int viaNdx = pcb->library->via_start_index;
|
|
|
|
if( viaNdx != -1 )
|
|
{
|
|
for( ; viaNdx < (int)padstacks.size(); ++viaNdx )
|
|
{
|
|
vias->AppendVia( padstacks[viaNdx].padstack_id.c_str() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----<create the nets>------------------------------------------------
|
|
{
|
|
NETWORK* network = pcb->network;
|
|
static const KICAD_T scanNETs[] = { PCB_EQUIPOT_STRUCT_TYPE, EOT };
|
|
|
|
items.Collect( aBoard, scanNETs );
|
|
|
|
PIN_REF emptypin(0);
|
|
|
|
for( int i=0; i<items.GetCount(); ++i )
|
|
{
|
|
EQUIPOT* kinet = (EQUIPOT*)items[i];
|
|
|
|
NET* net = new NET( network );
|
|
network->nets.push_back( net );
|
|
|
|
net->net_id = CONV_TO_UTF8( kinet->m_Netname );
|
|
net->net_number = kinet->GetNet();
|
|
|
|
D_PAD** ppad = kinet->m_PadzoneStart;
|
|
for( ; ppad < kinet->m_PadzoneEnd; ++ppad )
|
|
{
|
|
D_PAD* pad = *ppad;
|
|
|
|
wxASSERT( pad->Type() == TYPEPAD );
|
|
|
|
// push on an empty one, then fill it via 'pin_ref'
|
|
net->pins.push_back( emptypin );
|
|
PIN_REF* pin_ref = &net->pins.back();
|
|
|
|
pin_ref->SetParent( net );
|
|
pin_ref->component_id = CONV_TO_UTF8( ((MODULE*)pad->m_Parent)->GetReference() );;
|
|
pin_ref->pin_id = CONV_TO_UTF8( pad->ReturnStringPadName() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----<restore MODULEs>------------------------------------------------
|
|
|
|
// DSN Images (=Kicad MODULES and pads) must be presented from the
|
|
// top view. Restore those that were flipped.
|
|
for( MODULE* module = aBoard->m_Modules; module; module = module->Next() )
|
|
{
|
|
if( module->flag )
|
|
{
|
|
aBoard->Change_Side_Module( module, NULL );
|
|
module->flag = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
} // namespace DSN
|
|
|