kicad/pcbnew/specctra_import_export/specctra_import.cpp

518 lines
17 KiB
C++
Raw Normal View History

2008-02-06 22:32:15 +00:00
/*
* This program source code file is part of KiCad, a free EDA CAD application.
2008-02-06 22:32:15 +00:00
*
* Copyright (C) 2007-2013 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 2007 KiCad Developers, see change_log.txt for contributors.
2008-02-07 20:23:58 +00:00
*
2008-02-06 22:32:15 +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-02-06 22:32:15 +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-02-06 22:32:15 +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-02-06 22:32:15 +00:00
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
2008-02-07 20:23:58 +00:00
2008-02-06 22:32:15 +00:00
/* This source is a complement to specctra.cpp and implements the import of
2008-02-07 20:23:58 +00:00
a specctra session file (*.ses), and import of a specctra design file
(*.dsn) file. The specification for the grammar of the specctra dsn file
2008-02-06 22:32:15 +00:00
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-06 22:32:15 +00:00
Also see the comments at the top of the specctra.cpp file itself.
*/
#include <confirm.h> // DisplayError()
#include <gestfich.h> // EDA_FileSelector()
2018-01-29 20:58:58 +00:00
#include <pcb_edit_frame.h>
2020-10-24 01:38:50 +00:00
#include <locale_io.h>
#include <macros.h>
#include <board.h>
#include <board_design_settings.h>
#include <footprint.h>
#include <track.h>
#include <connectivity/connectivity_data.h>
#include <view/view.h>
#include "specctra.h"
#include <math/util.h> // for KiROUND
#include <pcbnew_settings.h>
2008-02-06 22:32:15 +00:00
using namespace DSN;
bool PCB_EDIT_FRAME::ImportSpecctraSession( const wxString& fullFileName )
{
// To avoid issues with undo/redo lists (dangling pointers)
// clear the lists
// todo: use undo/redo feature
ClearUndoRedoList();
2008-02-07 06:49:16 +00:00
SPECCTRA_DB db;
2012-01-16 21:43:07 +00:00
LOCALE_IO toggle;
2008-02-07 20:23:58 +00:00
try
{
db.LoadSESSION( fullFileName );
db.FromSESSION( GetBoard() );
2008-02-07 06:49:16 +00:00
}
catch( const IO_ERROR& ioe )
2008-02-07 06:49:16 +00:00
{
2019-06-04 20:59:59 +00:00
wxString msg = _( "Board may be corrupted, do not save it.\n Fix problem and try again" );
wxString extra = ioe.What();
2008-02-09 08:34:45 +00:00
DisplayErrorMessage( this, msg, extra);
return false;
2008-02-07 06:49:16 +00:00
}
2008-02-07 17:10:12 +00:00
OnModify();
GetBoard()->GetConnectivity()->Clear();
GetBoard()->GetConnectivity()->Build( GetBoard() );
if( GetCanvas() ) // Update view:
{
// Update footprint positions
GetCanvas()->GetView()->RecacheAllItems();
// add imported tracks (previous tracks are removed, therfore all are new)
2019-05-31 02:30:28 +00:00
for( auto track : GetBoard()->Tracks() )
GetCanvas()->GetView()->Add( track );
}
2008-02-07 20:23:58 +00:00
SetStatusText( wxString( _( "Session file imported and merged OK." ) ) );
2008-02-07 20:23:58 +00:00
Refresh();
return true;
2008-02-07 06:49:16 +00:00
}
namespace DSN {
2008-02-07 17:10:12 +00:00
/**
* Function scale
* converts a session file distance to KiCad units of deci-mils.
* @param distance The session file length to convert.
* @param aResolution The session UNIT_RES which holds the engineering unit
* specifier
* @return int - The KiCad length in internal unit
*/
static int scale( double distance, UNIT_RES* aResolution )
2008-02-07 17:10:12 +00:00
{
2008-02-07 20:23:58 +00:00
double resValue = aResolution->GetValue();
2012-04-11 14:49:11 +00:00
double factor;
2008-02-07 20:23:58 +00:00
switch( aResolution->GetEngUnits() )
{
default:
case T_inch:
factor = 25.4e6; // nanometers per inch
break;
case T_mil:
factor = 25.4e3; // nanometers per mil
break;
case T_cm:
factor = 1e7; // nanometers per cm
break;
case T_mm:
factor = 1e6; // nanometers per mm
break;
case T_um:
factor = 1e3; // nanometers per um
break;
}
// Dick Hollenbeck's KiROUND R&D // This provides better project control over rounding to int from double // than wxRound() did. This scheme provides better logging in Debug builds // and it provides for compile time calculation of constants. #include <stdio.h> #include <assert.h> #include <limits.h> //-----<KiROUND KIT>------------------------------------------------------------ /** * KiROUND * rounds a floating point number to an int using * "round halfway cases away from zero". * In Debug build an assert fires if will not fit into an int. */ #if defined( DEBUG ) // DEBUG: a macro to capture line and file, then calls this inline static inline int KiRound( double v, int line, const char* filename ) { v = v < 0 ? v - 0.5 : v + 0.5; if( v > INT_MAX + 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' > 0 ' for int\n", __FUNCTION__, filename, line, v ); } else if( v < INT_MIN - 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' < 0 ' for int\n", __FUNCTION__, filename, line, v ); } return int( v ); } #define KiROUND( v ) KiRound( v, __LINE__, __FILE__ ) #else // RELEASE: a macro so compile can pre-compute constants. #define KiROUND( v ) int( (v) < 0 ? (v) - 0.5 : (v) + 0.5 ) #endif //-----</KiROUND KIT>----------------------------------------------------------- // Only a macro is compile time calculated, an inline function causes a static constructor // in a situation like this. // Therefore the Release build is best done with a MACRO not an inline function. int Computed = KiROUND( 14.3 * 8 ); int main( int argc, char** argv ) { for( double d = double(INT_MAX)-1; d < double(INT_MAX)+8; d += 2.0 ) { int i = KiROUND( d ); printf( "t: %d %.16g\n", i, d ); } return 0; }
2012-04-19 06:55:45 +00:00
int ret = KiROUND( factor * distance / resValue );
2008-02-09 16:33:03 +00:00
return ret;
}
2008-02-12 01:02:53 +00:00
/**
* Function mapPt
* translates a point from the Specctra Session format coordinate system
* to the KiCad coordinate system.
2008-02-12 01:02:53 +00:00
* @param aPoint The session point to translate
* @param aResolution - The amount to scale the point.
* @return wxPoint - The KiCad coordinate system point.
2008-02-12 01:02:53 +00:00
*/
static wxPoint mapPt( const POINT& aPoint, UNIT_RES* aResolution )
{
wxPoint ret( scale( aPoint.x, aResolution ),
2008-02-29 06:49:34 +00:00
-scale( aPoint.y, aResolution ) ); // negate y
2008-02-07 20:23:58 +00:00
2008-02-07 17:10:12 +00:00
return ret;
}
2008-02-07 06:49:16 +00:00
2008-02-07 20:23:58 +00:00
TRACK* SPECCTRA_DB::makeTRACK( PATH* aPath, int aPointIndex, int aNetcode )
{
int layerNdx = findLayerName( aPath->layer_id );
if( layerNdx == -1 )
{
wxString layerName = FROM_UTF8( aPath->layer_id.c_str() );
THROW_IO_ERROR(
wxString::Format( _( "Session file uses invalid layer id \"%s\"" ), layerName ) );
}
TRACK* track = new TRACK( m_sessionBoard );
track->SetStart( mapPt( aPath->points[aPointIndex+0], m_routeResolution ) );
track->SetEnd( mapPt( aPath->points[aPointIndex+1], m_routeResolution ) );
track->SetLayer( m_pcbLayer2kicad[layerNdx] );
track->SetWidth( scale( aPath->aperture_width, m_routeResolution ) );
track->SetNetCode( aNetcode );
return track;
}
::VIA* SPECCTRA_DB::makeVIA( PADSTACK* aPadstack, const POINT& aPoint,
int aNetCode, int aViaDrillDefault )
2008-02-09 08:34:45 +00:00
{
::VIA* via = 0;
2008-02-09 08:34:45 +00:00
SHAPE* shape;
int shapeCount = aPadstack->Length();
int drill_diam_iu = -1;
int copperLayerCount = m_sessionBoard->GetCopperLayerCount();
2008-02-09 08:34:45 +00:00
// The drill diameter is encoded in the padstack name if Pcbnew did the DSN export.
// It is after the colon and before the last '_'
2008-02-09 08:34:45 +00:00
int drillStartNdx = aPadstack->padstack_id.find( ':' );
if( drillStartNdx != -1 )
{
2008-02-12 01:02:53 +00:00
++drillStartNdx; // skip over the ':'
2008-02-09 08:34:45 +00:00
int drillEndNdx = aPadstack->padstack_id.rfind( '_' );
if( drillEndNdx != -1 )
{
std::string diam_txt( aPadstack->padstack_id,
drillStartNdx, drillEndNdx-drillStartNdx );
2008-02-09 16:33:03 +00:00
double drill_um = strtod( diam_txt.c_str(), 0 );
drill_diam_iu = int( drill_um * (IU_PER_MM / 1000.0) );
if( drill_diam_iu == aViaDrillDefault )
drill_diam_iu = UNDEFINED_DRILL_DIAMETER;
2008-02-09 08:34:45 +00:00
}
}
if( shapeCount == 0 )
{
THROW_IO_ERROR( _( "Session via padstack has no shapes" ) );
2008-02-09 08:34:45 +00:00
}
else if( shapeCount == 1 )
{
shape = (SHAPE*) (*aPadstack)[0];
2008-02-12 01:02:53 +00:00
DSN_T type = shape->shape->Type();
if( type != T_circle )
THROW_IO_ERROR(
wxString::Format( _( "Unsupported via shape: %s" ), GetTokenString( type ) ) );
2008-02-12 01:02:53 +00:00
CIRCLE* circle = (CIRCLE*) shape->shape;
int viaDiam = scale( circle->diameter, m_routeResolution );
2008-02-12 01:02:53 +00:00
via = new ::VIA( m_sessionBoard );
via->SetPosition( mapPt( aPoint, m_routeResolution ) );
via->SetDrill( drill_diam_iu );
2019-12-28 00:55:11 +00:00
via->SetViaType( VIATYPE::THROUGH );
via->SetWidth( viaDiam );
via->SetLayerPair( F_Cu, B_Cu );
2008-02-09 08:34:45 +00:00
}
2008-02-12 01:02:53 +00:00
else if( shapeCount == copperLayerCount )
2008-02-09 08:34:45 +00:00
{
shape = (SHAPE*) (*aPadstack)[0];
2008-02-12 01:02:53 +00:00
DSN_T type = shape->shape->Type();
if( type != T_circle )
THROW_IO_ERROR(
wxString::Format( _( "Unsupported via shape: %s" ), GetTokenString( type ) ) );
2008-02-12 01:02:53 +00:00
CIRCLE* circle = (CIRCLE*) shape->shape;
int viaDiam = scale( circle->diameter, m_routeResolution );
2008-02-12 01:02:53 +00:00
via = new ::VIA( m_sessionBoard );
via->SetPosition( mapPt( aPoint, m_routeResolution ) );
via->SetDrill( drill_diam_iu );
2019-12-28 00:55:11 +00:00
via->SetViaType( VIATYPE::THROUGH );
via->SetWidth( viaDiam );
via->SetLayerPair( F_Cu, B_Cu );
2008-02-12 01:02:53 +00:00
}
else // VIA_MICROVIA or VIA_BLIND_BURIED
{
int topLayerNdx = -1; // session layer detectors
int botLayerNdx = INT_MAX;
2008-02-12 01:02:53 +00:00
int viaDiam = -1;
for( int i=0; i<shapeCount; ++i )
2008-02-09 08:34:45 +00:00
{
2008-02-12 01:02:53 +00:00
shape = (SHAPE*) (*aPadstack)[i];
DSN_T type = shape->shape->Type();
if( type != T_circle )
THROW_IO_ERROR( wxString::Format(
_( "Unsupported via shape: %s" ), GetTokenString( type ) ) );
2008-02-12 01:02:53 +00:00
2008-02-09 08:34:45 +00:00
CIRCLE* circle = (CIRCLE*) shape->shape;
2008-02-12 01:02:53 +00:00
int layerNdx = findLayerName( circle->layer_id );
if( layerNdx == -1 )
2008-02-12 01:02:53 +00:00
{
wxString layerName = FROM_UTF8( circle->layer_id.c_str() );
THROW_IO_ERROR( wxString::Format(
_( "Session file uses invalid layer id \"%s\"" ), layerName ) );
2008-02-12 01:02:53 +00:00
}
if( layerNdx > topLayerNdx )
topLayerNdx = layerNdx;
if( layerNdx < botLayerNdx )
botLayerNdx = layerNdx;
if( viaDiam == -1 )
viaDiam = scale( circle->diameter, m_routeResolution );
2008-02-09 08:34:45 +00:00
}
2008-02-12 01:02:53 +00:00
via = new ::VIA( m_sessionBoard );
via->SetPosition( mapPt( aPoint, m_routeResolution ) );
via->SetDrill( drill_diam_iu );
2008-02-12 01:02:53 +00:00
if( (topLayerNdx==0 && botLayerNdx==1)
|| (topLayerNdx==copperLayerCount-2 && botLayerNdx==copperLayerCount-1))
2019-12-28 00:55:11 +00:00
via->SetViaType( VIATYPE::MICROVIA );
2008-02-12 01:02:53 +00:00
else
2019-12-28 00:55:11 +00:00
via->SetViaType( VIATYPE::BLIND_BURIED );
2008-02-12 01:02:53 +00:00
via->SetWidth( viaDiam );
2008-02-12 01:02:53 +00:00
PCB_LAYER_ID topLayer = m_pcbLayer2kicad[topLayerNdx];
PCB_LAYER_ID botLayer = m_pcbLayer2kicad[botLayerNdx];
2008-02-12 01:02:53 +00:00
via->SetLayerPair( topLayer, botLayer );
2008-02-09 08:34:45 +00:00
}
wxASSERT( via );
2008-02-09 08:34:45 +00:00
via->SetNetCode( aNetCode );
2008-02-09 08:34:45 +00:00
return via;
}
2008-02-07 20:23:58 +00:00
// no UI code in this function, throw exception to report problems to the
// UI handler: void PCB_EDIT_FRAME::ImportSpecctraSession( wxCommandEvent& event )
2008-02-07 06:49:16 +00:00
void SPECCTRA_DB::FromSESSION( BOARD* aBoard )
2008-02-07 06:49:16 +00:00
{
m_sessionBoard = aBoard; // not owned here
2008-02-07 06:49:16 +00:00
if( !m_session )
THROW_IO_ERROR( _("Session file is missing the \"session\" section") );
2008-02-07 20:23:58 +00:00
if( !m_session->route )
THROW_IO_ERROR( _("Session file is missing the \"routes\" section") );
2008-02-07 06:49:16 +00:00
if( !m_session->route->library )
THROW_IO_ERROR( _("Session file is missing the \"library_out\" section") );
2008-02-07 20:23:58 +00:00
2008-02-07 17:10:12 +00:00
// delete all the old tracks and vias
2019-05-31 02:30:28 +00:00
aBoard->Tracks().clear();
2008-02-07 17:10:12 +00:00
2008-02-07 20:23:58 +00:00
aBoard->DeleteMARKERs();
2008-02-07 17:10:12 +00:00
buildLayerMaps( aBoard );
if( m_session->placement )
2008-02-07 06:49:16 +00:00
{
2012-01-16 21:43:07 +00:00
// Walk the PLACEMENT object's COMPONENTs list, and for each PLACE within
// each COMPONENT, reposition and re-orient each component and put on
// correct side of the board.
COMPONENTS& components = m_session->placement->components;
2012-01-16 21:43:07 +00:00
for( COMPONENTS::iterator comp=components.begin(); comp!=components.end(); ++comp )
2008-02-07 17:10:12 +00:00
{
2012-01-16 21:43:07 +00:00
PLACES& places = comp->places;
for( unsigned i=0; i<places.size(); ++i )
2008-02-07 17:10:12 +00:00
{
2012-01-16 21:43:07 +00:00
PLACE* place = &places[i]; // '&' even though places[] holds a pointer!
2008-02-07 20:23:58 +00:00
2020-11-13 15:15:52 +00:00
wxString reference = FROM_UTF8( place->component_id.c_str() );
FOOTPRINT* footprint = aBoard->FindFootprintByReference( reference );
2020-02-20 12:11:04 +00:00
2020-11-13 15:15:52 +00:00
if( !footprint )
2012-01-16 21:43:07 +00:00
{
2020-02-20 12:11:04 +00:00
THROW_IO_ERROR( wxString::Format( _( "Reference '%s' not found." ),
reference ) );
2012-01-16 21:43:07 +00:00
}
2008-02-07 20:23:58 +00:00
2012-01-16 21:43:07 +00:00
if( !place->hasVertex )
continue;
2008-02-07 20:23:58 +00:00
2012-01-16 21:43:07 +00:00
UNIT_RES* resolution = place->GetUnits();
wxASSERT( resolution );
2008-02-07 20:23:58 +00:00
2012-01-16 21:43:07 +00:00
wxPoint newPos = mapPt( place->vertex, resolution );
2020-11-13 15:15:52 +00:00
footprint->SetPosition( newPos );
2012-01-16 21:43:07 +00:00
if( place->side == T_front )
2008-02-07 20:23:58 +00:00
{
2012-01-16 21:43:07 +00:00
// convert from degrees to tenths of degrees used in KiCad.
2013-05-04 11:57:09 +00:00
int orientation = KiROUND( place->rotation * 10.0 );
2020-11-13 15:15:52 +00:00
if( footprint->GetLayer() != F_Cu )
2012-01-16 21:43:07 +00:00
{
// footprint is on copper layer (back)
2020-11-13 15:15:52 +00:00
footprint->Flip( footprint->GetPosition(), false );
2012-01-16 21:43:07 +00:00
}
2020-11-13 15:15:52 +00:00
footprint->SetOrientation( orientation );
2008-02-07 20:23:58 +00:00
}
2012-01-16 21:43:07 +00:00
else if( place->side == T_back )
2008-02-07 20:23:58 +00:00
{
2013-05-04 11:57:09 +00:00
int orientation = KiROUND( (place->rotation + 180.0) * 10.0 );
2020-11-13 15:15:52 +00:00
if( footprint->GetLayer() != B_Cu )
2012-01-16 21:43:07 +00:00
{
// footprint is on component layer (front)
2020-11-13 15:15:52 +00:00
footprint->Flip( footprint->GetPosition(), false );
2012-01-16 21:43:07 +00:00
}
2020-11-13 15:15:52 +00:00
footprint->SetOrientation( orientation );
2012-01-16 21:43:07 +00:00
}
else
{
// as I write this, the PARSER *is* catching this, so we should never see below:
wxFAIL_MSG( wxT("DSN::PARSER did not catch an illegal side := 'back|front'") );
}
2008-02-07 17:10:12 +00:00
}
}
2008-02-07 06:49:16 +00:00
}
m_routeResolution = m_session->route->GetUnits();
2008-02-07 20:23:58 +00:00
// Walk the NET_OUTs and create tracks and vias anew.
NET_OUTS& net_outs = m_session->route->net_outs;
for( NET_OUTS::iterator net = net_outs.begin(); net!=net_outs.end(); ++net )
2008-02-07 06:49:16 +00:00
{
int netoutCode = 0;
2008-02-07 20:23:58 +00:00
2008-02-09 08:34:45 +00:00
// page 143 of spec says wire's net_id is optional
if( net->net_id.size() )
{
wxString netName = FROM_UTF8( net->net_id.c_str() );
NETINFO_ITEM* netinfo = aBoard->FindNet( netName );
2008-02-09 08:34:45 +00:00
if( netinfo )
2020-12-08 13:02:08 +00:00
{
netoutCode = netinfo->GetNetCode();
}
2008-02-29 06:49:34 +00:00
else // else netCode remains 0
{
// int breakhere = 1;
}
}
WIRES& wires = net->wires;
for( unsigned i = 0; i<wires.size(); ++i )
{
WIRE* wire = &wires[i];
DSN_T shape = wire->shape->Type();
if( shape != T_path )
{
2008-02-29 06:49:34 +00:00
/* shape == T_polygon is expected from freerouter if you have
a zone on a non "power" type layer, i.e. a T_signal layer
and the design does a round trip back in as session here.
We kept our own zones in the BOARD, so ignore this so called
'wire'.
wxString netId = FROM_UTF8( wire->net_id.c_str() );
THROW_IO_ERROR( wxString::Format( _("Unsupported wire shape: \"%s\" for net: \"%s\""),
DLEX::GetTokenString(shape).GetData(),
netId.GetData()
) );
2008-02-29 06:49:34 +00:00
*/
}
2008-02-29 06:49:34 +00:00
else
{
2008-02-29 06:49:34 +00:00
PATH* path = (PATH*) wire->shape;
for( unsigned pt=0; pt<path->points.size()-1; ++pt )
{
TRACK* track = makeTRACK( path, pt, netoutCode );
2008-02-29 06:49:34 +00:00
aBoard->Add( track );
}
}
}
WIRE_VIAS& wire_vias = net->wire_vias;
LIBRARY& library = *m_session->route->library;
for( unsigned i=0; i<wire_vias.size(); ++i )
{
2008-02-09 08:34:45 +00:00
int netCode = 0;
// page 144 of spec says wire_via's net_id is optional
if( net->net_id.size() )
{
wxString netName = FROM_UTF8( net->net_id.c_str() );
NETINFO_ITEM* netvia = aBoard->FindNet( netName );
2008-02-09 08:34:45 +00:00
if( netvia )
2020-12-08 13:02:08 +00:00
netCode = netvia->GetNetCode();
2008-02-09 08:34:45 +00:00
// else netCode remains 0
}
WIRE_VIA* wire_via = &wire_vias[i];
// example: (via Via_15:8_mil 149000 -71000 )
PADSTACK* padstack = library.FindPADSTACK( wire_via->GetPadstackId() );
if( !padstack )
{
// Dick Feb 29, 2008:
// Freerouter has a bug where it will not round trip all vias.
// Vias which have a (use_via) element will be round tripped.
// Vias which do not, don't come back in in the session library,
// even though they may be actually used in the pre-routed,
// protected wire_vias. So until that is fixed, create the
// padstack from its name as a work around.
// Could use a STRING_FORMATTER here and convert the entire
2008-02-09 08:34:45 +00:00
// wire_via to text and put that text into the exception.
wxString psid( FROM_UTF8( wire_via->GetPadstackId().c_str() ) );
2008-02-09 08:34:45 +00:00
THROW_IO_ERROR( wxString::Format(
_( "A wire_via references a missing padstack \"%s\"" ), psid ) );
2008-02-09 08:34:45 +00:00
}
NETCLASSPTR netclass = aBoard->GetDesignSettings().GetNetClasses().GetDefault();
int via_drill_default = netclass->GetViaDrill();
2008-02-09 08:34:45 +00:00
for( unsigned v=0; v<wire_via->vertexes.size(); ++v )
{
::VIA* via = makeVIA( padstack, wire_via->vertexes[v], netCode, via_drill_default );
2008-02-12 01:02:53 +00:00
aBoard->Add( via );
2008-02-09 08:34:45 +00:00
}
}
2008-02-07 06:49:16 +00:00
}
2008-02-06 22:32:15 +00:00
}
2008-02-07 06:49:16 +00:00
} // namespace DSN