kicad/pcbnew/exporters/idf.cpp

1444 lines
39 KiB
C++
Raw Normal View History

/**
* file: idf.cpp
*
* This program source code file is part of KiCad, a free EDA CAD application.
*
2014-01-29 16:42:21 +00:00
* Copyright (C) 2013-2014 Cirilo Bernardo
*
* 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
*/
// TODO: Consider using different precision formats for THOU vs MM output
// Keep in mind that THOU cannot represent MM very well although MM can
// represent 1 THOU with 4 decimal places. For modern manufacturing we
// are interested in a resolution of about 0.1 THOU.
#include <list>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <ctime>
#include <cctype>
#include <strings.h>
* 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
#include <pgm_base.h>
#include <wx/config.h>
#include <wx/file.h>
#include <wx/filename.h>
#include <macros.h>
2014-01-29 16:42:21 +00:00
#include <richio.h>
#include <idf.h>
#include <build_version.h>
// minimum drill diameter (nanometers) - 10000 is a 0.01mm drill
#define IDF_MIN_DIA ( 10000.0 )
// minimum board thickness; this is about 0.012mm (0.5 mils)
// which is about the thickness of a single kapton layer typically
// used in a flexible design.
#define IDF_MIN_BRD_THICKNESS (12000)
// START: a few routines to help IDF_LIB but which may be of general use in the future
// as IDF support develops
// fetch a line from the given input file and trim the ends
static bool FetchIDFLine( std::ifstream& aModel, std::string& aLine, bool& isComment );
// extract an IDF string and move the index to point to the character after the substring
static bool GetIDFString( const std::string& aLine, std::string& aIDFString,
bool& hasQuotes, int& aIndex );
// END: IDF_LIB helper routines
2014-01-29 16:42:21 +00:00
IDF_DRILL_DATA::IDF_DRILL_DATA( double aDrillDia, double aPosX, double aPosY,
IDF3::KEY_PLATING aPlating,
const std::string aRefDes,
const std::string aHoleType,
IDF3::KEY_OWNER aOwner )
{
if( aDrillDia < 0.3 )
dia = 0.3;
else
dia = aDrillDia;
x = aPosX;
y = aPosY;
plating = aPlating;
if( !aRefDes.compare( "BOARD" ) )
{
kref = IDF3::BOARD;
}
else if( aRefDes.empty() || !aRefDes.compare( "NOREFDES" ) )
{
kref = IDF3::NOREFDES;
}
else if( !aRefDes.compare( "PANEL" ) )
{
kref = IDF3::PANEL;
}
else
{
kref = IDF3::REFDES;
refdes = aRefDes;
}
if( !aHoleType.compare( "PIN" ) )
{
khole = IDF3::PIN;
}
else if( !aHoleType.compare( "VIA" ) )
{
khole = IDF3::VIA;
}
else if( aHoleType.empty() || !aHoleType.compare( "MTG" ) )
{
khole = IDF3::MTG;
}
else if( !aHoleType.compare( "TOOL" ) )
{
khole = IDF3::TOOL;
}
else
{
khole = IDF3::OTHER;
holetype = aHoleType;
}
owner = aOwner;
} // IDF_DRILL_DATA::IDF_DRILL_DATA( ... )
bool IDF_DRILL_DATA::Write( FILE* aLayoutFile )
{
// TODO: check stream integrity and return 'false' as appropriate
if( !aLayoutFile )
return false;
std::string holestr;
std::string refstr;
std::string ownstr;
std::string pltstr;
switch( khole )
{
case IDF3::PIN:
holestr = "PIN";
break;
case IDF3::VIA:
holestr = "VIA";
break;
case IDF3::TOOL:
holestr = "TOOL";
break;
case IDF3::OTHER:
holestr = "\"" + holetype + "\"";
break;
default:
holestr = "MTG";
break;
}
switch( kref )
{
case IDF3::BOARD:
refstr = "BOARD";
break;
case IDF3::PANEL:
refstr = "PANEL";
break;
case IDF3::REFDES:
refstr = "\"" + refdes + "\"";
break;
default:
refstr = "NOREFDES";
break;
}
if( plating == IDF3::PTH )
pltstr = "PTH";
else
pltstr = "NPTH";
switch( owner )
{
case IDF3::MCAD:
ownstr = "MCAD";
break;
case IDF3::ECAD:
ownstr = "ECAD";
break;
default:
ownstr = "UNOWNED";
}
fprintf( aLayoutFile, "%.3f %.5f %.5f %s %s %s %s\n",
dia, x, y, pltstr.c_str(), refstr.c_str(), holestr.c_str(), ownstr.c_str() );
return true;
} // IDF_DRILL_DATA::Write( aLayoutFile )
IDF_BOARD::IDF_BOARD()
{
2014-01-29 16:42:21 +00:00
refdesIndex = 0;
outlineIndex = 0;
scale = 1e-6;
boardThickness = 1.6; // default to 1.6mm thick boards
useThou = false; // by default we want mm output
hasBrdOutlineHdr = false;
layoutFile = NULL;
libFile = NULL;
}
IDF_BOARD::~IDF_BOARD()
{
2014-01-29 16:42:21 +00:00
// simply close files if they are open; do not attempt
// anything else since a previous exception may have left
// data in a bad state.
if( layoutFile != NULL )
{
fclose( layoutFile );
layoutFile = NULL;
}
if( libFile != NULL )
{
fclose( libFile );
libFile = NULL;
}
}
bool IDF_BOARD::Setup( wxString aBoardName,
wxString aFullFileName,
bool aUseThou,
int aBoardThickness )
{
if( aBoardThickness < IDF_MIN_BRD_THICKNESS )
return false;
if( aUseThou )
{
useThou = true;
scale = 1e-3 / 25.4;
}
else
{
useThou = false;
scale = 1e-6;
}
boardThickness = aBoardThickness * scale;
wxFileName brdname( aBoardName );
wxFileName idfname( aFullFileName );
// open the layout file
idfname.SetExt( wxT( "emn" ) );
layoutFile = wxFopen( aFullFileName, wxT( "wt" ) );
if( layoutFile == NULL )
return false;
// open the library file
idfname.SetExt( wxT( "emp" ) );
libFile = wxFopen( idfname.GetFullPath(), wxT( "wt" ) );
if( libFile == NULL )
{
fclose( layoutFile );
layoutFile = NULL;
return false;
}
wxDateTime tdate( time( NULL ) );
fprintf( layoutFile, ".HEADER\n"
"BOARD_FILE 3.0 \"Created by KiCad %s\""
" %.4u/%.2u/%.2u.%.2u:%.2u:%.2u 1\n"
"\"%s\" %s\n"
".END_HEADER\n\n",
TO_UTF8( GetBuildVersion() ),
tdate.GetYear(), tdate.GetMonth() + 1, tdate.GetDay(),
tdate.GetHour(), tdate.GetMinute(), tdate.GetSecond(),
TO_UTF8( brdname.GetFullName() ), useThou ? "THOU" : "MM" );
fprintf( libFile, ".HEADER\n"
2014-01-29 16:42:21 +00:00
"LIBRARY_FILE 3.0 \"Created by KiCad %s\" %.4d/%.2d/%.2d.%.2d:%.2d:%.2d 1\n"
".END_HEADER\n\n",
TO_UTF8( GetBuildVersion() ),
tdate.GetYear(), tdate.GetMonth() + 1, tdate.GetDay(),
tdate.GetHour(), tdate.GetMinute(), tdate.GetSecond() );
return true;
}
bool IDF_BOARD::Finish( void )
{
// Steps to finalize the board and library files:
// 1. (emn) close the BOARD_OUTLINE section
// 2. (emn) write out the DRILLED_HOLES section
// 3. (emp) finalize the library file
// 4. (emn) write out the COMPONENT_PLACEMENT section
if( layoutFile == NULL || libFile == NULL )
return false;
// Finalize the board outline section
fprintf( layoutFile, ".END_BOARD_OUTLINE\n\n" );
// Write out the drill section
bool ok = WriteDrills();
// populate the library (*.emp) file and write the
// PLACEMENT section
if( ok )
ok = IDFLib.WriteFiles( layoutFile, libFile );
fclose( libFile );
libFile = NULL;
fclose( layoutFile );
layoutFile = NULL;
return ok;
}
bool IDF_BOARD::AddOutline( IDF_OUTLINE& aOutline )
{
if( !layoutFile )
return false;
// TODO: check the stream integrity
std::list<IDF_SEGMENT*>::iterator bo;
std::list<IDF_SEGMENT*>::iterator eo;
if( !hasBrdOutlineHdr )
{
fprintf( layoutFile, ".BOARD_OUTLINE ECAD\n%.5f\n", boardThickness );
hasBrdOutlineHdr = true;
}
if( aOutline.size() == 1 )
{
if( !aOutline.front()->IsCircle() )
return false; // this is a bad outline
// NOTE: a circle always has an angle of 360, never -360,
// otherwise SolidWorks chokes on the file.
fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex,
aOutline.front()->startPoint.x, aOutline.front()->startPoint.y );
fprintf( layoutFile, "%d %.5f %.5f 360\n", outlineIndex,
aOutline.front()->endPoint.x, aOutline.front()->endPoint.y );
++outlineIndex;
return true;
}
// ensure that the very last point is the same as the very first point
aOutline.back()-> endPoint = aOutline.front()->startPoint;
// check if we must reverse things
if( ( aOutline.IsCCW() && ( outlineIndex > 0 ) )
|| ( ( !aOutline.IsCCW() ) && ( outlineIndex == 0 ) ) )
{
eo = aOutline.begin();
bo = aOutline.end();
--bo;
// for the first item we write out both points
if( aOutline.front()->angle < MIN_ANG && aOutline.front()->angle > -MIN_ANG )
{
fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex,
aOutline.front()->endPoint.x, aOutline.front()->endPoint.y );
fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex,
aOutline.front()->startPoint.x, aOutline.front()->startPoint.y );
}
else
{
fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex,
aOutline.front()->endPoint.x, aOutline.front()->endPoint.y );
fprintf( layoutFile, "%d %.5f %.5f %.5f\n", outlineIndex,
aOutline.front()->startPoint.x, aOutline.front()->startPoint.y,
-aOutline.front()->angle );
}
// for all other segments we only write out the start point
while( bo != eo )
{
if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG )
{
fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex,
(*bo)->startPoint.x, (*bo)->startPoint.y );
}
else
{
fprintf( layoutFile, "%d %.5f %.5f %.5f\n", outlineIndex,
(*bo)->startPoint.x, (*bo)->startPoint.y, -(*bo)->angle );
}
--bo;
}
}
else
{
bo = aOutline.begin();
eo = aOutline.end();
// for the first item we write out both points
if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG )
{
fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex,
(*bo)->startPoint.x, (*bo)->startPoint.y );
fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex,
(*bo)->endPoint.x, (*bo)->endPoint.y );
}
else
{
fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex,
(*bo)->startPoint.x, (*bo)->startPoint.y );
fprintf( layoutFile, "%d %.5f %.5f %.5f\n", outlineIndex,
(*bo)->endPoint.x, (*bo)->endPoint.y, (*bo)->angle );
}
++bo;
// for all other segments we only write out the last point
while( bo != eo )
{
if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG )
{
fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex,
(*bo)->endPoint.x, (*bo)->endPoint.y );
}
else
{
fprintf( layoutFile, "%d %.5f %.5f %.5f\n", outlineIndex,
(*bo)->endPoint.x, (*bo)->endPoint.y, (*bo)->angle );
}
++bo;
}
}
++outlineIndex;
return true;
}
bool IDF_BOARD::AddDrill( double dia, double x, double y,
IDF3::KEY_PLATING plating,
const std::string refdes,
const std::string holeType,
IDF3::KEY_OWNER owner )
{
if( dia < IDF_MIN_DIA * scale )
return false;
IDF_DRILL_DATA* dp = new IDF_DRILL_DATA( dia, x, y, plating, refdes, holeType, owner );
drills.push_back( dp );
return true;
}
bool IDF_BOARD::AddSlot( double aWidth, double aLength, double aOrientation,
double aX, double aY )
{
if( aWidth < IDF_MIN_DIA * scale )
return false;
if( aLength < IDF_MIN_DIA * scale )
return false;
IDF_POINT c[2]; // centers
IDF_POINT pt[4];
double a1 = aOrientation / 180.0 * M_PI;
double a2 = a1 + M_PI2;
double d1 = aLength / 2.0;
double d2 = aWidth / 2.0;
double sa1 = sin( a1 );
double ca1 = cos( a1 );
double dsa2 = d2 * sin( a2 );
double dca2 = d2 * cos( a2 );
c[0].x = aX + d1 * ca1;
c[0].y = aY + d1 * sa1;
c[1].x = aX - d1 * ca1;
c[1].y = aY - d1 * sa1;
pt[0].x = c[0].x - dca2;
pt[0].y = c[0].y - dsa2;
pt[1].x = c[1].x - dca2;
pt[1].y = c[1].y - dsa2;
pt[2].x = c[1].x + dca2;
pt[2].y = c[1].y + dsa2;
pt[3].x = c[0].x + dca2;
pt[3].y = c[0].y + dsa2;
IDF_OUTLINE outline;
// first straight run
IDF_SEGMENT* seg = new IDF_SEGMENT( pt[0], pt[1] );
outline.push( seg );
// first 180 degree cap
seg = new IDF_SEGMENT( c[1], pt[1], -180.0, true );
outline.push( seg );
// final straight run
seg = new IDF_SEGMENT( pt[2], pt[3] );
outline.push( seg );
// final 180 degree cap
seg = new IDF_SEGMENT( c[0], pt[3], -180.0, true );
outline.push( seg );
return AddOutline( outline );
}
bool IDF_BOARD::PlaceComponent( const wxString aComponentFile, const std::string aRefDes,
double aXLoc, double aYLoc, double aZLoc,
double aRotation, bool isOnTop )
{
return IDFLib.PlaceComponent( aComponentFile, aRefDes,
aXLoc, aYLoc, aZLoc,
aRotation, isOnTop );
}
2014-01-29 16:42:21 +00:00
std::string IDF_BOARD::GetRefDes( void )
{
std::ostringstream ostr;
ostr << "NOREFDES_" << refdesIndex++;
return ostr.str();
}
bool IDF_BOARD::WriteDrills( void )
{
if( !layoutFile )
return false;
// TODO: check the stream integrity and return false as appropriate
if( drills.empty() )
return true;
fprintf( layoutFile, ".DRILLED_HOLES\n" );
std::list<class IDF_DRILL_DATA*>::iterator ds = drills.begin();
std::list<class IDF_DRILL_DATA*>::iterator de = drills.end();
while( ds != de )
{
if( !(*ds)->Write( layoutFile ) )
return false;
++ds;
}
fprintf( layoutFile, ".END_DRILLED_HOLES\n" );
return true;
}
double IDF_BOARD::GetScale( void )
{
return scale;
}
void IDF_BOARD::SetOffset( double x, double y )
{
offsetX = x;
offsetY = y;
}
void IDF_BOARD::GetOffset( double& x, double& y )
{
x = offsetX;
y = offsetY;
}
IDF_LIB::~IDF_LIB()
{
while( !components.empty() )
{
delete components.back();
components.pop_back();
}
}
bool IDF_LIB::writeLib( FILE* aLibFile )
{
if( !aLibFile )
return false;
// TODO: check stream integrity and return false as appropriate
// export models
std::list< IDF_COMP* >::const_iterator mbeg = components.begin();
std::list< IDF_COMP* >::const_iterator mend = components.end();
while( mbeg != mend )
{
if( !(*mbeg)->WriteLib( aLibFile ) )
return false;
++mbeg;
}
libWritten = true;
return true;
}
bool IDF_LIB::writeBrd( FILE* aLayoutFile )
{
if( !aLayoutFile || !libWritten )
return false;
if( components.empty() )
return true;
// TODO: check stream integrity and return false as appropriate
// write out the board placement information
std::list< IDF_COMP* >::const_iterator mbeg = components.begin();
std::list< IDF_COMP* >::const_iterator mend = components.end();
fprintf( aLayoutFile, "\n.PLACEMENT\n" );
while( mbeg != mend )
{
if( !(*mbeg)->WritePlacement( aLayoutFile ) )
return false;
++mbeg;
}
fprintf( aLayoutFile, ".END_PLACEMENT\n" );
return true;
}
bool IDF_LIB::WriteFiles( FILE* aLayoutFile, FILE* aLibFile )
{
if( !aLayoutFile || !aLibFile )
return false;
libWritten = false;
regOutlines.clear();
if( !writeLib( aLibFile ) )
return false;
return writeBrd( aLayoutFile );
}
bool IDF_LIB::RegisterOutline( const std::string aGeomPartString )
{
std::set< std::string >::const_iterator it = regOutlines.find( aGeomPartString );
if( it != regOutlines.end() )
return true;
regOutlines.insert( aGeomPartString );
return false;
}
bool IDF_LIB::PlaceComponent( const wxString aComponentFile, const std::string aRefDes,
double aXLoc, double aYLoc, double aZLoc,
double aRotation, bool isOnTop )
{
IDF_COMP* comp = new IDF_COMP( this );
if( comp == NULL )
{
std::cerr << "IDF_LIB: *ERROR* could not allocate memory for a component\n";
return false;
}
components.push_back( comp );
if( !comp->PlaceComponent( aComponentFile, aRefDes,
aXLoc, aYLoc, aZLoc,
aRotation, isOnTop ) )
{
std::cerr << "IDF_LIB: file does not exist (or is symlink):\n";
std::cerr << " FILE: " << TO_UTF8( aComponentFile ) << "\n";
return false;
}
return true;
}
IDF_COMP::IDF_COMP( IDF_LIB* aParent )
{
parent = aParent;
}
bool IDF_COMP::PlaceComponent( const wxString aComponentFile, const std::string aRefDes,
double aXLoc, double aYLoc, double aZLoc,
double aRotation, bool isOnTop )
{
componentFile = aComponentFile;
refdes = aRefDes;
if( refdes.empty() || !refdes.compare( "~" ) || !refdes.compare( "0" ) )
refdes = "NOREFDES";
loc_x = aXLoc;
loc_y = aYLoc;
loc_z = aZLoc;
rotation = aRotation;
top = isOnTop;
wxString fname = wxExpandEnvVars( aComponentFile );
if( !wxFileName::FileExists( fname ) )
return false;
componentFile = fname;
return true;
}
bool IDF_COMP::WritePlacement( FILE* aLayoutFile )
{
if( aLayoutFile == NULL )
{
std::cerr << "IDF_COMP: *ERROR* WritePlacement() invoked with aLayoutFile = NULL\n";
return false;
}
if( parent == NULL )
{
std::cerr << "IDF_COMP: *ERROR* no valid pointer \n";
return false;
}
if( componentFile.empty() )
{
std::cerr << "IDF_COMP: *BUG* empty componentFile name in WritePlacement()\n";
return false;
}
if( geometry.empty() && partno.empty() )
{
std::cerr << "IDF_COMP: *BUG* geometry and partno strings are empty in WritePlacement()\n";
return false;
}
// TODO: monitor stream integrity and respond accordingly
// PLACEMENT, RECORD 2:
fprintf( aLayoutFile, "\"%s\" \"%s\" \"%s\"\n",
geometry.c_str(), partno.c_str(), refdes.c_str() );
// PLACEMENT, RECORD 3:
if( rotation >= -MIN_ANG && rotation <= -MIN_ANG )
{
fprintf( aLayoutFile, "%.6f %.6f %.6f 0 %s ECAD\n",
loc_x, loc_y, loc_z, top ? "TOP" : "BOTTOM" );
}
else
{
fprintf( aLayoutFile, "%.6f %.6f %.6f %.3f %s ECAD\n",
loc_x, loc_y, loc_z, rotation, top ? "TOP" : "BOTTOM" );
}
return true;
}
bool IDF_COMP::WriteLib( FILE* aLibFile )
{
// 1. parse the file for the .ELECTRICAL or .MECHANICAL section
// and extract the Geometry and PartNumber strings
// 2. Register the name; check if it already exists
// 3. parse the rest of the file until .END_ELECTRICAL or
// .END_MECHANICAL; validate that each entry conforms
// to a valid outline
// 4. write lines to library file
//
// NOTE on parsing (the order matters):
// + store each line which begins with '#'
// + strip blanks from both ends of the line
// + drop each blank line
// + the first non-blank non-comment line must be
// .ELECTRICAL or .MECHANICAL (as per spec, case does not matter)
// + the first non-blank line after RECORD 1 must be RECORD 2
// + following RECORD 2, only blank lines, valid outline entries,
// and .END_{MECHANICAL,ELECTRICAL} are allowed
// + only a single outline may be specified; the order may be
// CW or CCW.
// + all valid lines are stored and written to the library file
//
// return: false if we do could not write model data; we may return
// true even if we could not read an IDF file for some reason, provided
// that the default model was written. In such a case, warnings will be
// written to stderr.
if( aLibFile == NULL )
{
std::cerr << "IDF_COMP: *ERROR* WriteLib() invoked with aLibFile = NULL\n";
return false;
}
if( parent == NULL )
{
std::cerr << "IDF_COMP: *ERROR* no valid pointer \n";
return false;
}
if( componentFile.empty() )
{
std::cerr << "IDF_COMP: *BUG* empty componentFile name in WriteLib()\n";
return false;
}
std::list< std::string > records;
std::ifstream model;
std::string fname = TO_UTF8( componentFile );
model.open( fname.c_str(), std::ios_base::in );
if( !model.is_open() )
{
std::cerr << "* IDF EXPORT: could not open file " << fname << "\n";
return substituteComponent( aLibFile );
}
std::string entryType; // will be one of ELECTRICAL or MECHANICAL
std::string endMark; // will be one of .END_ELECTRICAL or .END_MECHANICAL
std::string iline; // the input line
int state = 1;
bool isComment; // true if a line just read in is a comment line
bool isNewItem = false; // true if the outline is a previously unsaved IDF item
// some vars for parsing record 3
int loopIdx = -1; // direction of points in outline (0=CW, 1=CCW, -1=no points yet)
double firstX;
double firstY;
bool lineClosed = false; // true when outline has been closed; only one outline is permitted
while( state )
{
while( !FetchIDFLine( model, iline, isComment ) && model.good() );
if( !model.good() )
{
// this should not happen; we should at least
// have encountered the .END_ statement;
// however, we shall make a concession if the
// last line is an .END_ statement which had
// not been correctly terminated
if( !endMark.empty() && !strncasecmp( iline.c_str(), endMark.c_str(), 15 ) )
{
std::cerr << "IDF EXPORT: *WARNING* IDF file is not properly terminated\n";
std::cerr << "* FILE: " << fname << "\n";
records.push_back( endMark );
break;
}
std::cerr << "IDF EXPORT: *ERROR* faulty IDF file\n";
std::cerr << "* FILE: " << fname << "\n";
return substituteComponent( aLibFile );
}
switch( state )
{
case 1:
// accept comment lines, .ELECTRICAL, or .MECHANICAL;
// all others are simply ignored
if( isComment )
{
records.push_back( iline );
break;
}
if( !strncasecmp( iline.c_str(), ".electrical", 11 ) )
{
entryType = ".ELECTRICAL";
endMark = ".END_ELECTRICAL";
records.push_back( entryType );
state = 2;
break;
}
if( !strncasecmp( iline.c_str(), ".mechanical", 11 ) )
{
entryType = ".MECHANICAL";
endMark = ".END_MECHANICAL";
records.push_back( entryType );
state = 2;
break;
}
break;
case 2:
// accept only a RECORD 2 compliant line;
// anything else constitutes a malformed IDF file
if( isComment )
{
std::cerr << "IDF EXPORT: bad IDF file\n";
std::cerr << "* LINE: " << iline << "\n";
std::cerr << "* FILE: " << fname << "\n";
std::cerr << "* REASON: comment within "
<< entryType << " section\n";
model.close();
return substituteComponent( aLibFile );
}
if( !parseRec2( iline, isNewItem ) )
{
std::cerr << "IDF EXPORT: bad IDF file\n";
std::cerr << "* LINE: " << iline << "\n";
std::cerr << "* FILE: " << fname << "\n";
std::cerr << "* REASON: expecting RECORD 2 of "
<< entryType << " section\n";
model.close();
return substituteComponent( aLibFile );
}
if( isNewItem )
{
records.push_back( iline );
state = 3;
}
else
{
model.close();
return true;
}
break;
case 3:
// accept outline entries or end of section
if( isComment )
{
std::cerr << "IDF EXPORT: bad IDF file\n";
std::cerr << "* LINE: " << iline << "\n";
std::cerr << "* FILE: " << fname << "\n";
std::cerr << "* REASON: comment within "
<< entryType << " section\n";
model.close();
return substituteComponent( aLibFile );
}
if( !strncasecmp( iline.c_str(), endMark.c_str(), 15 ) )
{
records.push_back( endMark );
state = 0;
break;
}
if( lineClosed )
{
// there should be no further points
std::cerr << "IDF EXPORT: faulty IDF file\n";
std::cerr << "* LINE: " << iline << "\n";
std::cerr << "* FILE: " << fname << "\n";
std::cerr << "* REASON: more than 1 outline in "
<< entryType << " section\n";
model.close();
return substituteComponent( aLibFile );
}
if( !parseRec3( iline, loopIdx, firstX, firstY, lineClosed ) )
{
std::cerr << "IDF EXPORT: unexpected line in IDF file\n";
std::cerr << "* LINE: " << iline << "\n";
std::cerr << "* FILE: " << fname << "\n";
model.close();
return substituteComponent( aLibFile );
}
records.push_back( iline );
break;
default:
std::cerr << "IDF EXPORT: BUG in " << __FUNCTION__ << ": unexpected state\n";
model.close();
return substituteComponent( aLibFile );
break;
} // switch( state )
} // while( state )
model.close();
if( !lineClosed )
{
std::cerr << "IDF EXPORT: component outline not closed\n";
std::cerr << "* FILE: " << fname << "\n";
return substituteComponent( aLibFile );
}
std::list< std::string >::iterator lbeg = records.begin();
std::list< std::string >::iterator lend = records.end();
// TODO: check stream integrity
while( lbeg != lend )
{
fprintf( aLibFile, "%s\n", lbeg->c_str() );
++lbeg;
}
fprintf( aLibFile, "\n" );
return true;
}
bool IDF_COMP::substituteComponent( FILE* aLibFile )
{
// the component outline does not exist or could not be
// read; substitute a placeholder
// TODO: check the stream integrity
geometry = "NOGEOM";
partno = "NOPART";
if( parent->RegisterOutline( "NOGEOM_NOPART" ) )
return true;
// Create a star shape 5mm high with points on 5 and 2.5 mm circles
fprintf( aLibFile, ".ELECTRICAL\n" );
fprintf( aLibFile, "\"NOGEOM\" \"NOPART\" MM 5\n" );
double a, da, x, y;
da = M_PI / 5.0;
a = da / 2.0;
for( int i = 0; i < 10; ++i )
{
if( i & 1 )
{
x = 2.5 * cos( a );
y = 2.5 * sin( a );
}
else
{
x = 1.5 * cos( a );
y = 1.5 * sin( a );
}
a += da;
fprintf( aLibFile, "0 %.3f %.3f 0\n", x, y );
}
a = da / 2.0;
x = 1.5 * cos( a );
y = 1.5 * sin( a );
fprintf( aLibFile, "0 %.3f %.3f 0\n", x, y );
fprintf( aLibFile, ".END_ELECTRICAL\n\n" );
return true;
}
bool IDF_COMP::parseRec2( const std::string aLine, bool& isNewItem )
{
// RECORD 2:
// + "Geometry Name"
// + "Part Number"
// + MM or THOU
// + height (float)
isNewItem = false;
int idx = 0;
bool quoted = false;
std::string entry;
if( !GetIDFString( aLine, entry, quoted, idx ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 2 in model file (no Geometry Name entry)\n";
return false;
}
geometry = entry;
if( !GetIDFString( aLine, entry, quoted, idx ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 2 in model file (no Part No. entry)\n";
return false;
}
partno = entry;
if( geometry.empty() && partno.empty() )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 2 in model file\n";
std::cerr << " Geometry Name and Part Number are both empty.\n";
return false;
}
if( !GetIDFString( aLine, entry, quoted, idx ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 2, missing FIELD 3\n";
return false;
}
if( strcasecmp( "MM", entry.c_str() ) && strcasecmp( "THOU", entry.c_str() ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 2, invalid FIELD 3 \""
<< entry << "\"\n";
return false;
}
if( !GetIDFString( aLine, entry, quoted, idx ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 2, missing FIELD 4\n";
return false;
}
if( quoted )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 2, invalid FIELD 4 (quoted)\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
// ensure that we have a valid value
double val;
std::stringstream teststr;
teststr << entry;
if( !( teststr >> val ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 2, invalid FIELD 4 (must be numeric)\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
2014-01-29 16:42:21 +00:00
teststr.clear();
teststr << geometry << "_" << partno;
2014-01-29 16:42:21 +00:00
if( !parent->RegisterOutline( teststr.str() ) )
isNewItem = true;
return true;
}
bool IDF_COMP::parseRec3( const std::string aLine, int& aLoopIndex,
double& aX, double& aY, bool& aClosed )
{
// RECORD 3:
// + 0,1 (loop label)
// + X coord (float)
// + Y coord (float)
// + included angle (0 for line, +ang for CCW, -ang for CW, +360 for circle)
//
// notes:
// 1. first entry may not be a circle or arc
// 2. it would be nice, but not essential, to ensure that the
// winding is indeed as specified by the loop label
//
double x, y, ang;
bool ccw = false;
bool quoted = false;
int idx = 0;
std::string entry;
if( !GetIDFString( aLine, entry, quoted, idx ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, no data\n";
return false;
}
if( quoted )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, FIELD 1 is quoted\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
if( entry.compare( "0" ) && entry.compare( "1" ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, FIELD 1 is invalid (must be 0 or 1)\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
if( !entry.compare( "0" ) )
ccw = true;
if( aLoopIndex == 0 && !ccw )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, LOOP INDEX changed from 0 to 1\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
if( aLoopIndex == 1 && ccw )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, LOOP INDEX changed from 1 to 0\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
if( !GetIDFString( aLine, entry, quoted, idx ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, FIELD 2 does not exist\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
if( quoted )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, FIELD 2 is quoted\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
std::stringstream tstr;
tstr.str( entry );
if( !(tstr >> x ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, invalid X value in FIELD 2\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
if( !GetIDFString( aLine, entry, quoted, idx ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, FIELD 3 does not exist\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
if( quoted )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, FIELD 3 is quoted\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
tstr.clear();
tstr.str( entry );
if( !(tstr >> y ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, invalid Y value in FIELD 3\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
if( !GetIDFString( aLine, entry, quoted, idx ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, FIELD 4 does not exist\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
if( quoted )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, FIELD 4 is quoted\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
tstr.clear();
tstr.str( entry );
if( !(tstr >> ang ) )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, invalid ANGLE value in FIELD 3\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
if( aLoopIndex == -1 )
{
// this is the first point; there are some special checks
aLoopIndex = ccw ? 0 : 1;
aX = x;
aY = y;
aClosed = false;
// ensure that the first point is not an arc specification
if( ang < -MIN_ANG || ang > MIN_ANG )
{
std::cerr << "IDF_COMP: *ERROR* invalid RECORD 3, first point has non-zero angle\n";
std::cerr << " LINE: " << aLine << "\n";
return false;
}
}
else
{
// does this close the outline?
if( ang < 0.0 ) ang = -ang;
ang -= 360.0;
if( ang > -MIN_ANG && ang < MIN_ANG )
{
// this is a circle; the loop is closed
aClosed = true;
}
else
{
x = (aX - x) * (aX - x);
y = (aY - y) * (aY - y) + x;
if( y <= 1e-6 )
{
// the points are close enough; the loop is closed
aClosed = true;
}
}
}
// NOTE:
// 1. ideally we would ensure that there are no arcs with a radius of 0; this entails
// actively calculating the last point as the previous entry could have been an instruction
// to create an arc. This check is sacrificed in the interest of speed.
// 2. a bad outline can be crafted by giving at least one valid segment and then introducing
// a circle; such a condition is not checked for here in the interest of speed.
// 3. a circle specified with an angle of -360 is invalid, but that condition is not
// tested here.
return true;
}
// fetch a line from the given input file and trim the ends
static bool FetchIDFLine( std::ifstream& aModel, std::string& aLine, bool& isComment )
{
aLine = "";
std::getline( aModel, aLine );
isComment = false;
// A comment begins with a '#' and must be the first character on the line
if( aLine[0] == '#' )
isComment = true;
while( !aLine.empty() && isspace( *aLine.begin() ) )
aLine.erase( aLine.begin() );
while( !aLine.empty() && isspace( *aLine.rbegin() ) )
aLine.erase( --aLine.end() );
if( aLine.empty() )
return false;
return true;
}
// extract an IDF string and move the index to point to the character after the substring
static bool GetIDFString( const std::string& aLine, std::string& aIDFString,
bool& hasQuotes, int& aIndex )
{
// 1. drop all leading spaces
// 2. if the first character is '"', read until the next '"',
// otherwise read until the next space or EOL.
std::ostringstream ostr;
int len = aLine.length();
int idx = aIndex;
if( idx < 0 || idx >= len )
return false;
while( isspace( aLine[idx] ) && idx < len ) ++idx;
if( idx == len )
{
aIndex = idx;
return false;
}
if( aLine[idx] == '"' )
{
hasQuotes = true;
++idx;
while( aLine[idx] != '"' && idx < len )
ostr << aLine[idx++];
if( idx == len )
{
std::cerr << "GetIDFString(): *ERROR*: unterminated quote mark in line:\n";
std::cerr << "LINE: " << aLine << "\n";
aIndex = idx;
return false;
}
++idx;
}
else
{
hasQuotes = false;
while( !isspace( aLine[idx] ) && idx < len )
ostr << aLine[idx++];
}
aIDFString = ostr.str();
aIndex = idx;
return true;
}