From 6652bcdc5fb28c9ec5c91643b6b77d793ae5980f Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 28 May 2014 08:26:46 +0200 Subject: [PATCH] Add patch about idf export (from cirilo_bernardo) --- utils/idftools/CMakeLists.txt | 35 +- utils/idftools/dxf2idfmain.cpp | 3 +- utils/idftools/idf2vrml.cpp | 842 ++++ utils/idftools/idf_common.cpp | 1376 ++++++ utils/idftools/idf_common.h | 705 +++ utils/idftools/idf_cylinder.cpp | 28 +- .../idf_examples/Arduino_MEGA_2560-Rev3.emn | 149 + .../idf_examples/Arduino_MEGA_2560-Rev3.emp | 4 + utils/idftools/idf_examples/idf_example.emn | 269 ++ utils/idftools/idf_examples/idf_example.emp | 69 + utils/idftools/idf_examples/test_idf2.emn | 71 + utils/idftools/idf_examples/test_idf2.emp | 290 ++ utils/idftools/idf_helpers.cpp | 295 ++ utils/idftools/idf_helpers.h | 171 + utils/idftools/idf_outlines.cpp | 2614 +++++++++++ utils/idftools/idf_outlines.h | 754 ++++ utils/idftools/idf_parser.cpp | 3819 +++++++++++++++++ utils/idftools/idf_parser.h | 657 +++ utils/idftools/idf_rect.cpp | 24 +- utils/idftools/vrml_layer.cpp | 1578 +++++++ utils/idftools/vrml_layer.h | 429 ++ 21 files changed, 14152 insertions(+), 30 deletions(-) create mode 100644 utils/idftools/idf2vrml.cpp create mode 100644 utils/idftools/idf_common.cpp create mode 100644 utils/idftools/idf_common.h create mode 100644 utils/idftools/idf_examples/Arduino_MEGA_2560-Rev3.emn create mode 100644 utils/idftools/idf_examples/Arduino_MEGA_2560-Rev3.emp create mode 100644 utils/idftools/idf_examples/idf_example.emn create mode 100644 utils/idftools/idf_examples/idf_example.emp create mode 100644 utils/idftools/idf_examples/test_idf2.emn create mode 100644 utils/idftools/idf_examples/test_idf2.emp create mode 100644 utils/idftools/idf_helpers.cpp create mode 100644 utils/idftools/idf_helpers.h create mode 100644 utils/idftools/idf_outlines.cpp create mode 100644 utils/idftools/idf_outlines.h create mode 100644 utils/idftools/idf_parser.cpp create mode 100644 utils/idftools/idf_parser.h create mode 100644 utils/idftools/vrml_layer.cpp create mode 100644 utils/idftools/vrml_layer.h diff --git a/utils/idftools/CMakeLists.txt b/utils/idftools/CMakeLists.txt index a36fa45878..d934baf6e8 100644 --- a/utils/idftools/CMakeLists.txt +++ b/utils/idftools/CMakeLists.txt @@ -1,29 +1,32 @@ include_directories( - "${CMAKE_SOURCE_DIR}/include" "${CMAKE_SOURCE_DIR}/lib_dxf" - "${CMAKE_SOURCE_DIR}/pcbnew/exporters" "${CMAKE_SOURCE_DIR}/utils/idftools" ) link_directories( "${CMAKE_BINARY_DIR}/lib_dxf" -) - -add_executable( idfcyl idf_cylinder.cpp ) - -add_executable( idfrect idf_rect.cpp ) - -add_executable( dxf2idf dxf2idfmain.cpp dxf2idf.cpp - "${CMAKE_SOURCE_DIR}/pcbnew/exporters/idf_common.cpp" - "${CMAKE_SOURCE_DIR}/common/richio.cpp" ) -add_dependencies( idfcyl lib-dependencies ) -add_dependencies( idfrect lib-dependencies ) -add_dependencies( dxf2idf lib-dependencies ) +add_library( idf3 STATIC + idf_helpers.cpp idf_common.cpp idf_outlines.cpp + idf_parser.cpp vrml_layer.cpp ) -target_link_libraries( dxf2idf lib_dxf ${wxWidgets_LIBRARIES} ) +add_executable( idfcyl idf_cylinder.cpp ) +add_executable( idfrect idf_rect.cpp ) +add_executable( dxf2idf dxf2idfmain.cpp dxf2idf.cpp ) +add_executable( idf2vrml idf2vrml.cpp ) -install( TARGETS idfcyl idfrect dxf2idf +target_link_libraries( dxf2idf lib_dxf idf3 ${wxWidgets_LIBRARIES} ) + +if( WIN32 ) + set ( LIB_GLU glu32 ) +else() + set ( LIB_GLU GLU ) +endif() + +target_link_libraries( idf2vrml idf3 ${LIB_GLU} ${wxWidgets_LIBRARIES} ) + + +install( TARGETS idfcyl idfrect dxf2idf idf2vrml DESTINATION ${KICAD_BIN} COMPONENT binary ) diff --git a/utils/idftools/dxf2idfmain.cpp b/utils/idftools/dxf2idfmain.cpp index 6d4f4a6143..df804c15be 100644 --- a/utils/idftools/dxf2idfmain.cpp +++ b/utils/idftools/dxf2idfmain.cpp @@ -123,7 +123,8 @@ int main( int argc, char **argv ) tstr.clear(); tstr.str( line ); - if( (tstr >> height ) && height > 0.001 ) + tstr >> height; + if( !tstr.fail() && height > 0.001 ) ok = true; } diff --git a/utils/idftools/idf2vrml.cpp b/utils/idftools/idf2vrml.cpp new file mode 100644 index 0000000000..0032e39b61 --- /dev/null +++ b/utils/idftools/idf2vrml.cpp @@ -0,0 +1,842 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 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 + */ + +/* + * This program takes an IDF base name, loads the board outline + * and component outine files, and creates a single VRML file. + * The VRML file can be used to visually verify the IDF files + * before sending them to a mechanical designer. The output scale + * is 10:1; this scale was chosen because VRML was originally + * intended to describe large virtual worlds and rounding errors + * would be more likely if we used a 1:1 scale. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifndef MIN_ANG +#define MIN_ANG 0.01 +#endif + +extern char* optarg; +extern int optopt; + +using namespace std; + +#define CLEANUP do { \ +setlocale( LC_ALL, "C" ); \ +} while( 0 ); + +// define colors +struct VRML_COLOR +{ + double diff[3]; + double emis[3]; + double spec[3]; + double ambi; + double tran; + double shin; +}; + +struct VRML_IDS +{ + int colorIndex; + std::string objectName; + bool used; + bool bottom; + double dX, dY, dZ, dA; + + VRML_IDS() + { + colorIndex = 0; + used = false; + bottom = false; + dX = 0.0; + dY = 0.0; + dZ = 0.0; + dA = 0.0; + } +}; + +#define NCOLORS 7 +VRML_COLOR colors[NCOLORS] = { \ + { 0, 0.82, 0.247, 0, 0, 0, 0, 0.82, 0.247, 0.9, 0, 0.1}, \ + { 1, 0, 0, 1, 0, 0, 1, 0, 0, 0.9, 0, 0.1}, \ + { 0.659, 0, 0.463, 0, 0, 0, 0.659, 0, 0.463, 0.9, 0, 0.1}, \ + { 0.659, 0.294, 0, 0, 0, 0, 0.659, 0.294, 0, 0.9, 0, 0.1}, \ + { 0, 0.918, 0.659, 0, 0, 0, 0, 0.918, 0.659, 0.9, 0, 0.1}, \ + { 0.808, 0.733 , 0.071, 0, 0, 0, 0.808, 0.733 , 0.071, 0.9, 0, 0.1}, \ + { 0.102, 1, 0.984, 0, 0, 0, 0.102, 1, 0.984, 0.9, 0, 0.1} +}; + +bool WriteHeader( IDF3_BOARD& board, std::ofstream& file ); +bool MakeBoard( IDF3_BOARD& board, std::ofstream& file ); +bool MakeComponents( IDF3_BOARD& board, std::ofstream& file, bool compact ); +bool PopulateVRML( VRML_LAYER& model, const std::list< IDF_OUTLINE* >* items, bool bottom, + double scale, double dX = 0.0, double dY = 0.0, double angle = 0.0 ); +bool AddSegment( VRML_LAYER& model, IDF_SEGMENT* seg, int icont, int iseg ); +bool WriteTriangles( std::ofstream& file, VRML_IDS* vID, VRML_LAYER* layer, bool plane, + bool top, double top_z, double bottom_z, int precision, bool compact ); +inline void TransformPoint( IDF_SEGMENT& seg, double frac, bool bottom, + double dX, double dY, double angle ); +VRML_IDS* GetColor( std::map& cmap, + int& index, const std::string& uid ); + + +void PrintUsage( void ) +{ + cout << "-\nUsage: idf2vrml -f input_file.emn -s scale_factor -k\n"; + cout << "flags: -k: produce KiCad-firendly VRML output; default is compact VRML\n-\n"; + cout << "example to produce a model for use by KiCad: idf2vrml -f input.emn -s 0.3937008 -k\n\n"; + return; +} + +int main( int argc, char **argv ) +{ + // IDF implicitly requires the C locale + setlocale( LC_ALL, "C" ); + + // Essential inputs: + // 1. IDF file + // 2. Output scale: internal IDF units are mm, so 1 = 1mm per VRML unit, + // 0.1 = 1cm per VRML unit, 0.01 = 1m per VRML unit, + // 1/25.4 = 1in per VRML unit, 1/2.54 = 0.1in per VRML unit (KiCad model) + // 3. KiCad-friendly output (do not reuse features via DEF+USE) + // Render each component to VRML; if the user wants + // a KiCad friendly output then we must avoid DEF+USE; + // otherwise we employ DEF+USE to minimize file size + + std::string inputFilename; + double scaleFactor = 1.0; + bool compact = true; + int ichar; + + while( ( ichar = getopt( argc, argv, ":f:s:k" ) ) != -1 ) + { + switch( ichar ) + { + case 'f': + inputFilename = optarg; + break; + + case 's': + do + { + errno = 0; + char* cp = NULL; + scaleFactor = strtod( optarg, &cp ); + + if( errno || cp == optarg ) + { + cerr << "* invalid scale factor: '" << optarg << "'\n"; + return -1; + } + + if( scaleFactor < 0.001 || scaleFactor > 10 ) + { + cerr << "* scale factor out of range (" << scaleFactor << "); range is 0.001 to 10.0\n"; + return -1; + } + + } while( 0 ); + break; + + case 'k': + compact = false; + break; + + case ':': + cerr << "* Missing parameter to option '-" << ((char) optopt) << "'\n"; + PrintUsage(); + return -1; + break; + + default: + cerr << "* Unexpected option: '-"; + + if( ichar == '?' ) + cerr << ((char) optopt) << "'\n"; + else + cerr << ((char) ichar) << "'\n"; + + PrintUsage(); + return -1; + break; + } + } + + if( inputFilename.empty() ) + { + cerr << "* no IDF filename supplied\n"; + PrintUsage(); + return -1; + } + + IDF3_BOARD pcb( IDF3::CAD_ELEC ); + + cout << "** Reading file: " << inputFilename << "\n"; + + if( !pcb.ReadFile( FROM_UTF8( inputFilename.c_str() ) ) ) + { + CLEANUP; + cerr << "* Could not read file: " << inputFilename << "\n"; + return -1; + } + + // set the scale and output precision ( scale 1 == precision 5) + pcb.SetUserScale( scaleFactor ); + + if( scaleFactor < 0.01 ) + pcb.SetUserPrecision( 8 ); + else if( scaleFactor < 0.1 ) + pcb.SetUserPrecision( 7 ); + else if( scaleFactor < 1.0 ) + pcb.SetUserPrecision( 6 ); + else if( scaleFactor < 10.0 ) + pcb.SetUserPrecision( 5 ); + else + pcb.SetUserPrecision( 4 ); + + // Create the VRML file and write the header + char* bnp = (char*) malloc( inputFilename.size() + 1 ); + strcpy( bnp, inputFilename.c_str() ); + + std::string fname = basename( bnp ); + free( bnp ); + std::string::iterator itf = fname.end(); + *(--itf) = 'l'; + *(--itf) = 'r'; + *(--itf) = 'w'; + + cout << "Writing file: '" << fname << "'\n"; + + std::ofstream ofile; + ofile.open( fname.c_str(), std::ios_base::out ); + + ofile << fixed; // do not use exponents in VRML output + WriteHeader( pcb, ofile ); + + // STEP 1: Render the PCB alone + MakeBoard( pcb, ofile ); + + // STEP 2: Render the components + MakeComponents( pcb, ofile, compact ); + + ofile << "]\n}\n"; + ofile.close(); + + // restore the locale + setlocale( LC_ALL, "" ); + return 0; +} + + +bool WriteHeader( IDF3_BOARD& board, std::ofstream& file ) +{ + std::string bname = board.GetBoardName(); + + if( bname.empty() ) + { + bname = "BoardWithNoName"; + } + else + { + std::string::iterator ss = bname.begin(); + std::string::iterator se = bname.end(); + + while( ss != se ) + { + if( *ss == '/' || *ss == ' ' || *ss == ':' ) + *ss = '_'; + + ++ss; + } + } + + file << "#VRML V2.0 utf8\n\n"; + file << "WorldInfo {\n"; + file << " title \"" << bname << "\"\n}\n\n"; + file << "Transform {\n"; + file << "children [\n"; + + return !file.fail(); +} + + +bool MakeBoard( IDF3_BOARD& board, std::ofstream& file ) +{ + VRML_LAYER vpcb; + + if( board.GetBoardOutlinesSize() < 1 ) + { + ERROR_IDF << "\n"; + cerr << "* Cannot proceed; no board outline in IDF object\n"; + return false; + } + + double scale = board.GetUserScale(); + + // set the arc parameters according to output scale + int tI; + double tMin, tMax; + vpcb.GetArcParams( tI, tMin, tMax ); + vpcb.SetArcParams( tI, tMin * scale, tMax * scale ); + + if( !PopulateVRML( vpcb, board.GetBoardOutline()->GetOutlines(), false, board.GetUserScale() ) ) + { + return false; + } + + vpcb.EnsureWinding( 0, false ); + + int nvcont = vpcb.GetNContours(); + + while( nvcont > 0 ) + vpcb.EnsureWinding( nvcont--, true ); + + // Add the drill holes + const std::list* drills = &board.GetBoardDrills(); + + std::list::const_iterator sd = drills->begin(); + std::list::const_iterator ed = drills->end(); + + while( sd != ed ) + { + vpcb.AddCircle( (*sd)->GetDrillXPos() * scale, (*sd)->GetDrillYPos() * scale, + (*sd)->GetDrillDia() * scale / 2.0, true ); + ++sd; + } + + std::map< std::string, IDF3_COMPONENT* >*const comp = board.GetComponents(); + std::map< std::string, IDF3_COMPONENT* >::const_iterator sc = comp->begin(); + std::map< std::string, IDF3_COMPONENT* >::const_iterator ec = comp->end(); + + while( sc != ec ) + { + drills = sc->second->GetDrills(); + sd = drills->begin(); + ed = drills->end(); + + while( sd != ed ) + { + vpcb.AddCircle( (*sd)->GetDrillXPos() * scale, (*sd)->GetDrillYPos() * scale, + (*sd)->GetDrillDia() * scale / 2.0, true ); + ++sd; + } + + ++sc; + } + + // tesselate and write out + vpcb.Tesselate( NULL ); + + double thick = board.GetBoardThickness() / 2.0 * scale; + + VRML_IDS tvid; + tvid.colorIndex = 0; + + WriteTriangles( file, &tvid, &vpcb, false, false, + thick, -thick, board.GetUserPrecision(), false ); + + return true; +} + +bool PopulateVRML( VRML_LAYER& model, const std::list< IDF_OUTLINE* >* items, bool bottom, double scale, + double dX, double dY, double angle ) +{ + // empty outlines are not unusual so we fail quietly + if( items->size() < 1 ) + return false; + + int nvcont = 0; + int iseg = 0; + + std::list< IDF_OUTLINE* >::const_iterator scont = items->begin(); + std::list< IDF_OUTLINE* >::const_iterator econt = items->end(); + std::list::iterator sseg; + std::list::iterator eseg; + + IDF_SEGMENT lseg; + + while( scont != econt ) + { + nvcont = model.NewContour(); + + if( nvcont < 0 ) + { + ERROR_IDF << "\n"; + cerr << "* cannot create an outline\n"; + return false; + } + + if( (*scont)->size() < 1 ) + { + ERROR_IDF << "invalid contour: no vertices\n"; + return false; + } + + sseg = (*scont)->begin(); + eseg = (*scont)->end(); + + iseg = 0; + while( sseg != eseg ) + { + lseg = **sseg; + TransformPoint( lseg, scale, bottom, dX, dY, angle ); + + if( !AddSegment( model, &lseg, nvcont, iseg ) ) + return false; + + ++iseg; + ++sseg; + } + + ++scont; + } + + return true; +} + + +bool AddSegment( VRML_LAYER& model, IDF_SEGMENT* seg, int icont, int iseg ) +{ + // note: in all cases we must add all but the last point in the segment + // to avoid redundant points + + if( seg->angle != 0.0 ) + { + if( seg->IsCircle() ) + { + if( iseg != 0 ) + { + ERROR_IDF << "adding a circle to an existing vertex list\n"; + return false; + } + + return model.AppendCircle( seg->center.x, seg->center.y, seg->radius, icont ); + } + else + { + return model.AppendArc( seg->center.x, seg->center.y, seg->radius, + seg->offsetAngle, seg->angle, icont ); + } + } + + if( !model.AddVertex( icont, seg->startPoint.x, seg->startPoint.y ) ) + return false; + + return true; +} + + +bool WriteTriangles( std::ofstream& file, VRML_IDS* vID, VRML_LAYER* layer, bool plane, + bool top, double top_z, double bottom_z, int precision, bool compact ) +{ + if( vID == NULL || layer == NULL ) + return false; + + file << "Transform {\n"; + + if( compact && !vID->objectName.empty() ) + { + file << "translation " << setprecision( precision ) << vID->dX; + file << " " << vID->dY << " "; + + if( vID->bottom ) + { + file << -vID->dZ << "\n"; + + double tx, ty; + + // calculate the rotation axis and angle + tx = cos( M_PI2 - vID->dA / 2.0 ); + ty = sin( M_PI2 - vID->dA / 2.0 ); + + file << "rotation " << setprecision( precision ); + file << tx << " " << ty << " 0 "; + file << setprecision(5) << M_PI << "\n"; + } + else + { + file << vID->dZ << "\n"; + file << "rotation 0 0 1 " << setprecision(5) << vID->dA << "\n"; + } + + file << "children [\n"; + + if( vID->used ) + { + file << "USE " << vID->objectName << "\n"; + file << "]\n"; + file << "}\n"; + return true; + } + + file << "DEF " << vID->objectName << " Transform {\n"; + + if( !plane && top_z <= bottom_z ) + { + // the height specification is faulty; make the component + // a bright red to highlight it + vID->colorIndex = 1; + // we don't know the scale, but 5 units is huge in most situations + top_z = bottom_z + 5.0; + } + + } + + VRML_COLOR* color = &colors[vID->colorIndex]; + + vID->used = true; + + file << "children [\n"; + file << "Group {\n"; + file << "children [\n"; + file << "Shape {\n"; + file << "appearance Appearance {\n"; + file << "material Material {\n"; + + // material definition + file << "diffuseColor " << setprecision(3) << color->diff[0] << " "; + file << color->diff[1] << " " << color->diff[2] << "\n"; + file << "specularColor " << color->spec[0] << " " << color->spec[1]; + file << " " << color->spec[2] << "\n"; + file << "emissiveColor " << color->emis[0] << " " << color->emis[1]; + file << " " << color->emis[2] << "\n"; + file << "ambientIntensity " << color->ambi << "\n"; + file << "transparency " << color->tran << "\n"; + file << "shininess " << color->shin << "\n"; + + file << "}\n"; + file << "}\n"; + file << "geometry IndexedFaceSet {\n"; + file << "solid TRUE\n"; + file << "coord Coordinate {\n"; + file << "point [\n"; + + // XXX: TODO: check return values of Write() routines; they may fail + // even though the stream is good (bad internal data) + + // Coordinates (vertices) + if( plane ) + { + if( ! layer->WriteVertices( top_z, file, precision ) ) + { + cerr << "* errors writing planar vertices to " << vID->objectName << "\n"; + cerr << "** " << layer->GetError() << "\n"; + } + } + else + { + if( ! layer->Write3DVertices( top_z, bottom_z, file, precision ) ) + { + cerr << "* errors writing 3D vertices to " << vID->objectName << "\n"; + cerr << "** " << layer->GetError() << "\n"; + } + } + + file << "\n"; + + file << "]\n"; + file << "}\n"; + file << "coordIndex [\n"; + + // Indices + if( plane ) + layer->WriteIndices( top, file ); + else + layer->Write3DIndices( file ); + + file << "\n"; + file << "]\n"; + file << "}\n"; + file << "}\n"; + file << "]\n"; + file << "}\n"; + file << "]\n"; + file << "}\n"; + + if( compact && !vID->objectName.empty() ) + { + file << "]\n"; + file << "}\n"; + } + + return !file.fail(); +} + +inline void TransformPoint( IDF_SEGMENT& seg, double frac, bool bottom, + double dX, double dY, double angle ) +{ + dX *= frac; + dY *= frac; + + if( bottom ) + { + // mirror points on the Y axis + seg.startPoint.x = -seg.startPoint.x; + seg.endPoint.x = -seg.endPoint.x; + seg.center.x = -seg.center.x; + angle = -angle; + } + + seg.startPoint.x *= frac; + seg.startPoint.y *= frac; + seg.endPoint.x *= frac; + seg.endPoint.y *= frac; + seg.center.x *= frac; + seg.center.y *= frac; + + double tsin = 0.0; + double tcos = 0.0; + + if( angle > MIN_ANG || angle < -MIN_ANG ) + { + double ta = angle * M_PI / 180.0; + double tx, ty; + + tsin = sin( ta ); + tcos = cos( ta ); + + tx = seg.startPoint.x * tcos - seg.startPoint.y * tsin; + ty = seg.startPoint.x * tsin + seg.startPoint.y * tcos; + seg.startPoint.x = tx; + seg.startPoint.y = ty; + + tx = seg.endPoint.x * tcos - seg.endPoint.y * tsin; + ty = seg.endPoint.x * tsin + seg.endPoint.y * tcos; + seg.endPoint.x = tx; + seg.endPoint.y = ty; + + if( seg.angle != 0 ) + { + tx = seg.center.x * tcos - seg.center.y * tsin; + ty = seg.center.x * tsin + seg.center.y * tcos; + seg.center.x = tx; + seg.center.y = ty; + } + } + + seg.startPoint.x += dX; + seg.startPoint.y += dY; + seg.endPoint.x += dX; + seg.endPoint.y += dY; + seg.center.x += dX; + seg.center.y += dY; + + if( seg.angle != 0 ) + { + seg.radius *= frac; + + if( bottom ) + { + if( !seg.IsCircle() ) + { + seg.angle = -seg.angle; + if( seg.offsetAngle > 0.0 ) + seg.offsetAngle = 180 - seg.offsetAngle; + else + seg.offsetAngle = -seg.offsetAngle - 180; + } + } + + if( angle > MIN_ANG || angle < -MIN_ANG ) + seg.offsetAngle += angle; + } + + return; +} + +bool MakeComponents( IDF3_BOARD& board, std::ofstream& file, bool compact ) +{ + int cidx = 2; // color index; start at 2 since 0,1 are special (board, NOGEOM_NOPART) + + VRML_LAYER vpcb; + + double scale = board.GetUserScale(); + double thick = board.GetBoardThickness() / 2.0; + + // set the arc parameters according to output scale + int tI; + double tMin, tMax; + vpcb.GetArcParams( tI, tMin, tMax ); + vpcb.SetArcParams( tI, tMin * scale, tMax * scale ); + + // Add the component outlines + const std::map< std::string, IDF3_COMPONENT* >*const comp = board.GetComponents(); + std::map< std::string, IDF3_COMPONENT* >::const_iterator sc = comp->begin(); + std::map< std::string, IDF3_COMPONENT* >::const_iterator ec = comp->end(); + + std::list< IDF3_COMP_OUTLINE_DATA* >::const_iterator so; + std::list< IDF3_COMP_OUTLINE_DATA* >::const_iterator eo; + + double vX, vY, vA; + double tX, tY, tZ, tA; + double top, bot; + bool bottom; + IDF3::IDF_LAYER lyr; + + std::map< std::string, VRML_IDS*> cmap; // map colors by outline UID + VRML_IDS* vcp; + + while( sc != ec ) + { + sc->second->GetPosition( vX, vY, vA, lyr ); + + if( lyr == IDF3::LYR_BOTTOM ) + bottom = true; + else + bottom = false; + + so = sc->second->GetOutlinesData()->begin(); + eo = sc->second->GetOutlinesData()->end(); + + while( so != eo ) + { + (*so)->GetOffsets( tX, tY, tZ, tA ); + tX += vX; + tY += vY; + tA += vA; + + vcp = GetColor( cmap, cidx, ((IDF3_COMP_OUTLINE*)((*so)->GetOutline()))->GetUID() ); + + if( !compact ) + { + if( !PopulateVRML( vpcb, (*so)->GetOutline()->GetOutlines(), bottom, + board.GetUserScale(), tX, tY, tA ) ) + { + return false; + } + } + else + { + if( !vcp->used && !PopulateVRML( vpcb, (*so)->GetOutline()->GetOutlines(), false, + board.GetUserScale() ) ) + { + return false; + } + + vcp->dX = tX * scale; + vcp->dY = tY * scale; + vcp->dZ = tZ * scale; + vcp->dA = tA * M_PI / 180.0; + } + + if( !compact || !vcp->used ) + { + vpcb.EnsureWinding( 0, false ); + vpcb.Tesselate( NULL ); + } + + if( !compact ) + { + if( bottom ) + { + top = -thick - tZ; + bot = (top - (*so)->GetOutline()->GetThickness() ) * scale; + top *= scale; + } + else + { + bot = thick + tZ; + top = (bot + (*so)->GetOutline()->GetThickness() ) * scale; + bot *= scale; + } + } + else + { + bot = thick; + top = (bot + (*so)->GetOutline()->GetThickness() ) * scale; + bot *= scale; + } + + vcp = GetColor( cmap, cidx, ((IDF3_COMP_OUTLINE*)((*so)->GetOutline()))->GetUID() ); + vcp->bottom = bottom; + + WriteTriangles( file, vcp, &vpcb, false, + false, top, bot, board.GetUserPrecision(), compact ); + + vpcb.Clear(); + ++so; + } + + ++sc; + } + + return true; +} + + +VRML_IDS* GetColor( std::map& cmap, int& index, const std::string& uid ) +{ + static int refnum = 0; + + if( index < 2 ) + index = 2; // 0 and 1 are special (BOARD, UID=NOGEOM_NOPART) + + std::map::iterator cit = cmap.find( uid ); + + if( cit == cmap.end() ) + { + VRML_IDS* id = new VRML_IDS; + + if( !uid.compare( "NOGEOM_NOPART" ) ) + id->colorIndex = 1; + else + id->colorIndex = index++; + + std::ostringstream ostr; + ostr << "OBJECTn" << refnum++; + id->objectName = ostr.str(); + + cout << "* " << ostr.str() << " = '" << uid << "'\n"; + + cmap.insert( std::pair(uid, id) ); + + if( index >= NCOLORS ) + index = 2; + + return id; + } + + return cit->second; +} diff --git a/utils/idftools/idf_common.cpp b/utils/idftools/idf_common.cpp new file mode 100644 index 0000000000..492ca928ce --- /dev/null +++ b/utils/idftools/idf_common.cpp @@ -0,0 +1,1376 @@ +/** + * file: idf_common.cpp + * + * This program source code file is part of KiCad, a free EDA CAD application. + * + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace IDF3; +using namespace std; + + +std::string source; +std::string message; + +IDF_ERROR::IDF_ERROR( const char* aSourceFile, + const char* aSourceMethod, + int aSourceLine, + const std::string& aMessage ) throw() +{ + ostringstream ostr; + + if( aSourceFile ) + ostr << "* " << aSourceFile << ":"; + else + ostr << "* [BUG: No Source File]:"; + + ostr << aSourceLine << ":"; + + if( aSourceMethod ) + ostr << aSourceMethod << "(): "; + else + ostr << "[BUG: No Source Method]:\n* "; + + ostr << aMessage; + message = ostr.str(); + + return; +} + + +IDF_ERROR::~IDF_ERROR() throw() +{ + return; +} + + +const char* IDF_ERROR::what() const throw() +{ + return message.c_str(); +} + + +IDF_NOTE::IDF_NOTE() +{ + xpos = 0.0; + ypos = 0.0; + height = 0.0; + length = 0.0; +} + + +bool IDF_NOTE::ReadNote( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState, + IDF3::IDF_UNIT aBoardUnit ) +{ + std::string iline; // the input line + bool isComment; // true if a line just read in is a comment line + std::streampos pos; + int idx = 0; + bool quoted = false; + std::string token; + + // RECORD 2: X, Y, text Height, text Length, "TEXT" + while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() ); + + if( ( !aBoardFile.good() && !aBoardFile.eof() ) || iline.empty() ) + { + ERROR_IDF; + cerr << "problems reading board notes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: comment within a section (NOTES)\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: X position in NOTES section must not be in quotes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( ".END_NOTES", token ) ) + { + // the end of the note section is a special case; although we return 'false' + // we do not change the board's state variable and we ensure that errno is 0; + // all other false returns set the state to FILE_INVALID + errno = 0; + return false; + } + + istringstream istr; + istr.str( token ); + + istr >> xpos; + if( istr.fail() ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: X position in NOTES section is not numeric\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: Y position in NOTES section is missing\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: Y position in NOTES section must not be in quotes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + istr.clear(); + istr.str( token ); + + istr >> ypos; + if( istr.fail() ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: Y position in NOTES section is not numeric\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: text height in NOTES section is missing\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: text height in NOTES section must not be in quotes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + istr.clear(); + istr.str( token ); + + istr >> height; + if( istr.fail() ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: text height in NOTES section is not numeric\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: text length in NOTES section is missing\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: text length in NOTES section must not be in quotes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + istr.clear(); + istr.str( token ); + + istr >> length; + if( istr.fail() ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: text length in NOTES section is not numeric\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: text value in NOTES section is missing\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + text = token; + + if( aBoardUnit == UNIT_THOU ) + { + xpos *= IDF_MM_TO_THOU; + ypos *= IDF_MM_TO_THOU; + height *= IDF_MM_TO_THOU; + length *= IDF_MM_TO_THOU; + } + + return true; +} + + +bool IDF_NOTE::WriteNote( std::ofstream& aBoardFile, IDF3::IDF_UNIT aBoardUnit ) +{ + if( aBoardUnit == UNIT_THOU ) + { + aBoardFile << setiosflags(ios::fixed) << setprecision(1) + << (xpos / IDF_MM_TO_THOU) << " " + << (ypos / IDF_MM_TO_THOU) << " " + << (height / IDF_MM_TO_THOU) << " " + << (length / IDF_MM_TO_THOU) << " "; + } + else + { + aBoardFile << setiosflags(ios::fixed) << setprecision(5) + << xpos << " " << ypos << " " << height << " " << length << " "; + } + + aBoardFile << "\"" << text << "\"\n"; + + return !aBoardFile.bad(); +} + + +void IDF_NOTE::SetText( const std::string& aText ) +{ + text = aText; + return; +} + +void IDF_NOTE::SetPosition( double aXpos, double aYpos ) +{ + xpos = aXpos; + ypos = aYpos; + return; +} + +void IDF_NOTE::SetSize( double aHeight, double aLength ) +{ + height = aHeight; + length = aLength; + return; +} + +const std::string& IDF_NOTE::GetText( void ) +{ + return text; +} + +void IDF_NOTE::GetPosition( double& aXpos, double& aYpos ) +{ + aXpos = xpos; + aYpos = ypos; + return; +} + +void IDF_NOTE::GetSize( double& aHeight, double& aLength ) +{ + aHeight = height; + aLength = length; + return; +} + + +/* + * CLASS: IDF_DRILL_DATA + */ +IDF_DRILL_DATA::IDF_DRILL_DATA() +{ + dia = 0.0; + x = 0.0; + y = 0.0; + plating = NPTH; + kref = NOREFDES; + khole = MTG; + owner = UNOWNED; + + return; +} + + +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 = BOARD; + } + else if( aRefDes.empty() || !aRefDes.compare( "NOREFDES" ) ) + { + kref = NOREFDES; + } + else if( !aRefDes.compare( "PANEL" ) ) + { + kref = PANEL; + } + else + { + kref = REFDES; + refdes = aRefDes; + } + + if( !aHoleType.compare( "PIN" ) ) + { + khole = PIN; + } + else if( !aHoleType.compare( "VIA" ) ) + { + khole = VIA; + } + else if( aHoleType.empty() || !aHoleType.compare( "MTG" ) ) + { + khole = MTG; + } + else if( !aHoleType.compare( "TOOL" ) ) + { + khole = TOOL; + } + else + { + khole = OTHER; + holetype = aHoleType; + } + + owner = aOwner; +} // IDF_DRILL_DATA::IDF_DRILL_DATA( ... ) + +bool IDF_DRILL_DATA::Matches( double aDrillDia, double aPosX, double aPosY ) +{ + double ddia = aDrillDia - dia; + IDF_POINT p1, p2; + + p1.x = x; + p1.y = y; + p2.x = aPosX; + p2.y = aPosY; + + if( ddia > -0.00001 && ddia < 0.00001 && p1.Matches( p2, 0.00001 ) ) + return true; + + return false; +} + +bool IDF_DRILL_DATA::Read( std::ifstream& aBoardFile, IDF3::IDF_UNIT aBoardUnit, + IDF3::FILE_STATE aBoardState ) +{ + std::string iline; // the input line + bool isComment; // true if a line just read in is a comment line + std::streampos pos; + int idx = 0; + bool quoted = false; + std::string token; + + // RECORD 2: DIA, X, Y, Plating Style, REFDES, HOLE TYPE, HOLE OWNER + while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() ); + + if( ( !aBoardFile.good() && !aBoardFile.eof() ) || iline.empty() ) + { + ERROR_IDF; + cerr << "problems reading board drilled holes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: comment within a section (DRILLED HOLES)\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: drill diameter must not be in quotes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( ".END_DRILLED_HOLES", token ) ) + { + // the end of the section is a special case; although we return 'false' + // we do not change the board's state variable and we ensure that errno is 0; + // all other false returns set the state to FILE_INVALID + errno = 0; + return false; + } + + istringstream istr; + istr.str( token ); + + istr >> dia; + if( istr.fail() ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: drill diameter is not numeric\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( ( aBoardUnit == UNIT_MM && dia < IDF_MIN_DIA_MM ) + || ( aBoardUnit != UNIT_MM && dia < IDF_MIN_DIA_THOU ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Invalid drill diameter (too small): " << token << "\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: missing X position for drilled hole\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: X position in DRILLED HOLES section must not be in quotes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + istr.clear(); + istr.str( token ); + + istr >> x; + if( istr.fail() ) + { + ERROR_IDF; + cerr << "* Violation of specification: X position in DRILLED HOLES section is not numeric\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: missing Y position for drilled hole\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: Y position in DRILLED HOLES section must not be in quotes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + istr.clear(); + istr.str( token ); + + istr >> y; + if( istr.fail() ) + { + ERROR_IDF; + cerr << "* Violation of specification: Y position in DRILLED HOLES section is not numeric\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: missing PLATING for drilled hole\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( "PTH", token ) ) + { + plating = IDF3::PTH; + } + else if( CompareToken( "NPTH", token ) ) + { + plating = IDF3::NPTH; + } + else + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: invalid PLATING type ('" << token << "')\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: missing REFDES for drilled hole\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( "BOARD", token ) ) + { + kref = IDF3::BOARD; + } + else if( CompareToken( "NOREFDES", token ) ) + { + kref = IDF3::NOREFDES; + } + else if( CompareToken( "PANEL", token ) ) + { + kref = IDF3::PANEL; + } + else + { + kref = IDF3::REFDES; + refdes = token; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: missing HOLE TYPE for drilled hole\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( "PIN", token ) ) + { + khole = IDF3::PIN; + } + else if( CompareToken( "VIA", token ) ) + { + khole = IDF3::VIA; + } + else if( CompareToken( "MTG", token ) ) + { + khole = IDF3::MTG; + } + else if( CompareToken( "TOOL", token ) ) + { + khole = IDF3::TOOL; + } + else + { + khole = IDF3::OTHER; + holetype = token; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: missing OWNER for drilled hole\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !ParseOwner( token, owner ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: invalid OWNER for drilled hole ('" << token << "')\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( aBoardUnit == UNIT_THOU ) + { + dia *= IDF_MM_TO_THOU; + x *= IDF_MM_TO_THOU; + y *= IDF_MM_TO_THOU; + } + + return true; +} + +bool IDF_DRILL_DATA::Write( std::ofstream& aBoardFile, IDF3::IDF_UNIT aBoardUnit ) +{ + std::string holestr; + std::string refstr; + std::string ownstr; + std::string pltstr; + + switch( khole ) + { + case PIN: + holestr = "PIN"; + break; + + case VIA: + holestr = "VIA"; + break; + + case TOOL: + holestr = "TOOL"; + break; + + case OTHER: + holestr = "\"" + holetype + "\""; + break; + + default: + holestr = "MTG"; + break; + } + + switch( kref ) + { + case BOARD: + refstr = "BOARD"; + break; + + case PANEL: + refstr = "PANEL"; + break; + + case REFDES: + refstr = "\"" + refdes + "\""; + break; + + default: + refstr = "NOREFDES"; + break; + } + + if( plating == PTH ) + pltstr = "PTH"; + else + pltstr = "NPTH"; + + switch( owner ) + { + case MCAD: + ownstr = "MCAD"; + break; + + case ECAD: + ownstr = "ECAD"; + break; + + default: + ownstr = "UNOWNED"; + break; + } + + if( aBoardUnit == UNIT_MM ) + { + aBoardFile << std::setiosflags( std::ios::fixed ) << std::setprecision( 3 ) << dia << " " + << std::setprecision( 5 ) << x << " " << y << " " + << pltstr.c_str() << " " << refstr.c_str() << " " + << holestr.c_str() << " " << ownstr.c_str() << "\n"; + } + else + { + aBoardFile << std::setiosflags( std::ios::fixed ) << std::setprecision( 1 ) << (dia / IDF_MM_TO_THOU) << " " + << std::setprecision( 1 ) << (x / IDF_MM_TO_THOU) << " " << (y / IDF_MM_TO_THOU) << " " + << pltstr.c_str() << " " << refstr.c_str() << " " + << holestr.c_str() << " " << ownstr.c_str() << "\n"; + } + + return ! aBoardFile.fail(); +} // IDF_DRILL_DATA::Write( aBoardFile, unitMM ) + + +double IDF_DRILL_DATA::GetDrillDia() +{ + return dia; +} + +double IDF_DRILL_DATA::GetDrillXPos() +{ + return x; +} + +double IDF_DRILL_DATA::GetDrillYPos() +{ + return y; +} + +IDF3::KEY_PLATING IDF_DRILL_DATA::GetDrillPlating() +{ + return plating; +} + +const std::string& IDF_DRILL_DATA::GetDrillRefDes() +{ + switch( kref ) + { + case BOARD: + refdes = "BOARD"; + break; + + case PANEL: + refdes = "PANEL"; + break; + + case REFDES: + break; + + default: + refdes = "NOREFDES"; + break; + } + + return refdes; +} + +const std::string& IDF_DRILL_DATA::GetDrillHoleType() +{ + switch( khole ) + { + case PIN: + holetype = "PIN"; + break; + + case VIA: + holetype = "VIA"; + break; + + case TOOL: + holetype = "TOOL"; + break; + + case OTHER: + break; + + default: + holetype = "MTG"; + break; + } + + return holetype; +} + + +#ifdef DEBUG_IDF +void IDF3::PrintSeg( IDF_SEGMENT* aSegment ) +{ + if( aSegment->IsCircle() ) + { + fprintf(stdout, "printSeg(): CIRCLE: C(%.3f, %.3f) P(%.3f, %.3f) rad. %.3f\n", + aSegment->startPoint.x, aSegment->startPoint.y, + aSegment->endPoint.x, aSegment->endPoint.y, + aSegment->radius ); + return; + } + + if( aSegment->angle < -MIN_ANG || aSegment->angle > MIN_ANG ) + { + fprintf(stdout, "printSeg(): ARC: p1(%.3f, %.3f) p2(%.3f, %.3f) ang. %.3f\n", + aSegment->startPoint.x, aSegment->startPoint.y, + aSegment->endPoint.x, aSegment->endPoint.y, + aSegment->angle ); + return; + } + + fprintf(stdout, "printSeg(): LINE: p1(%.3f, %.3f) p2(%.3f, %.3f)\n", + aSegment->startPoint.x, aSegment->startPoint.y, + aSegment->endPoint.x, aSegment->endPoint.y ); + + return; +} +#endif + + +bool IDF_POINT::Matches( const IDF_POINT& aPoint, double aRadius ) +{ + double dx = x - aPoint.x; + double dy = y - aPoint.y; + + double d2 = dx * dx + dy * dy; + + if( d2 <= aRadius * aRadius ) + return true; + + return false; +} + + +double IDF_POINT::CalcDistance( const IDF_POINT& aPoint ) const +{ + double dx = aPoint.x - x; + double dy = aPoint.y - y; + double dist = sqrt( dx * dx + dy * dy ); + + return dist; +} + + +double IDF3::CalcAngleRad( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ) +{ + return atan2( aEndPoint.y - aStartPoint.y, aEndPoint.x - aStartPoint.x ); +} + + +double IDF3::CalcAngleDeg( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ) +{ + double ang = CalcAngleRad( aStartPoint, aEndPoint ); + + // round to thousandths of a degree + int iang = int (ang / M_PI * 1800000.0); + + ang = iang / 10000.0; + + return ang; +} + + +void IDF3::GetOutline( std::list& aLines, + IDF_OUTLINE& aOutline ) +{ + aOutline.Clear(); + + // NOTE: To tell if the point order is CCW or CW, + // sum all: (endPoint.X[n] - startPoint.X[n])*(endPoint[n] + startPoint.Y[n]) + // If the result is >0, the direction is CW, otherwise + // it is CCW. Note that the result cannot be 0 unless + // we have a bounded area of 0. + + // First we find the segment with the leftmost point + std::list::iterator bl = aLines.begin(); + std::list::iterator el = aLines.end(); + std::list::iterator idx = bl++; // iterator for the object with minX + + double minx = (*idx)->GetMinX(); + double curx; + + while( bl != el ) + { + curx = (*bl)->GetMinX(); + + if( curx < minx ) + { + minx = curx; + idx = bl; + } + + ++bl; + } + + aOutline.push( *idx ); +#ifdef DEBUG_IDF + PrintSeg( *idx ); +#endif + aLines.erase( idx ); + + // If the item is a circle then we're done + if( aOutline.front()->IsCircle() ) + return; + + // Assemble the loop + bool complete = false; // set if loop is complete + bool matched; // set if a segment's end point was matched + + while( !complete ) + { + matched = false; + bl = aLines.begin(); + el = aLines.end(); + + while( bl != el && !matched ) + { + if( (*bl)->MatchesStart( aOutline.back()->endPoint ) ) + { + if( (*bl)->IsCircle() ) + { + // a circle on the perimeter is pathological but we just ignore it + ++bl; + } + else + { + matched = true; +#ifdef DEBUG_IDF + PrintSeg( *bl ); +#endif + aOutline.push( *bl ); + aLines.erase( bl ); + } + + continue; + } + + ++bl; + } + + if( !matched ) + { + // attempt to match the end points + bl = aLines.begin(); + el = aLines.end(); + + while( bl != el && !matched ) + { + if( (*bl)->MatchesEnd( aOutline.back()->endPoint ) ) + { + if( (*bl)->IsCircle() ) + { + // a circle on the perimeter is pathological but we just ignore it + ++bl; + } + else + { + matched = true; + (*bl)->SwapEnds(); +#ifdef DEBUG_IDF + printSeg( *bl ); +#endif + aOutline.push( *bl ); + aLines.erase( bl ); + } + + continue; + } + + ++bl; + } + } + + if( !matched ) + { + // still no match - attempt to close the loop + if( (aOutline.size() > 1) || ( aOutline.front()->angle < -MIN_ANG ) + || ( aOutline.front()->angle > MIN_ANG ) ) + { + // close the loop + IDF_SEGMENT* seg = new IDF_SEGMENT( aOutline.back()->endPoint, + aOutline.front()->startPoint ); + + if( seg ) + { + complete = true; +#ifdef DEBUG_IDF + printSeg( seg ); +#endif + aOutline.push( seg ); + break; + } + } + + // the outline is bad; drop the segments + aOutline.Clear(); + + return; + } + + // check if the loop is complete + if( aOutline.front()->MatchesStart( aOutline.back()->endPoint ) ) + { + complete = true; + break; + } + } +} + + +IDF_SEGMENT::IDF_SEGMENT() +{ + angle = 0.0; + offsetAngle = 0.0; + radius = 0.0; +} + + +IDF_SEGMENT::IDF_SEGMENT( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ) +{ + angle = 0.0; + offsetAngle = 0.0; + radius = 0.0; + startPoint = aStartPoint; + endPoint = aEndPoint; +} + + +IDF_SEGMENT::IDF_SEGMENT( const IDF_POINT& aStartPoint, + const IDF_POINT& aEndPoint, + double aAngle, + bool aFromKicad ) +{ + double diff = abs( aAngle ) - 360.0; + + if( ( diff < MIN_ANG + && diff > -MIN_ANG ) || ( aAngle < MIN_ANG && aAngle > -MIN_ANG ) || (!aFromKicad) ) + { + angle = 0.0; + startPoint = aStartPoint; + endPoint = aEndPoint; + + if( diff < MIN_ANG && diff > -MIN_ANG ) + { + angle = 360.0; + center = aStartPoint; + offsetAngle = 0.0; + radius = aStartPoint.CalcDistance( aEndPoint ); + } + else if( aAngle > MIN_ANG || aAngle < -MIN_ANG ) + { + angle = aAngle; + CalcCenterAndRadius(); + } + + return; + } + + // we need to convert from the KiCad arc convention + angle = aAngle; + + center = aStartPoint; + + offsetAngle = IDF3::CalcAngleDeg( aStartPoint, aEndPoint ); + + radius = aStartPoint.CalcDistance( aEndPoint ); + + startPoint = aEndPoint; + + double ang = offsetAngle + aAngle; + ang = (ang / 180.0) * M_PI; + + endPoint.x = ( radius * cos( ang ) ) + center.x; + endPoint.y = ( radius * sin( ang ) ) + center.y; +} + + +bool IDF_SEGMENT::MatchesStart( const IDF_POINT& aPoint, double aRadius ) +{ + return startPoint.Matches( aPoint, aRadius ); +} + + +bool IDF_SEGMENT::MatchesEnd( const IDF_POINT& aPoint, double aRadius ) +{ + return endPoint.Matches( aPoint, aRadius ); +} + + +void IDF_SEGMENT::CalcCenterAndRadius( void ) +{ + // NOTE: this routine does not check if the points are the same + // or too close to be sensible in a production setting. + + double offAng = IDF3::CalcAngleRad( startPoint, endPoint ); + double d = startPoint.CalcDistance( endPoint ) / 2.0; + double xm = ( startPoint.x + endPoint.x ) * 0.5; + double ym = ( startPoint.y + endPoint.y ) * 0.5; + + radius = d / sin( angle * M_PI / 360.0 ); + + if( radius < 0.0 ) + { + radius = -radius; + } + + // calculate the height of the triangle with base d and hypotenuse r + double dh2 = radius * radius - d * d; + + if( dh2 < 0 ) + { + // this should only ever happen due to rounding errors when r == d + dh2 = 0; + } + + double h = sqrt( dh2 ); + + if( angle > 0.0 ) + offAng += M_PI2; + else + offAng -= M_PI2; + + if( ( angle > 180.0 ) || ( angle < -180.0 ) ) + offAng += M_PI; + + center.x = h * cos( offAng ) + xm; + center.y = h * sin( offAng ) + ym; + + offsetAngle = IDF3::CalcAngleDeg( center, startPoint ); +} + + +bool IDF_SEGMENT::IsCircle( void ) +{ + double diff = abs( angle ) - 360.0; + + if( ( diff < MIN_ANG ) && ( diff > -MIN_ANG ) ) + return true; + + return false; +} + + +double IDF_SEGMENT::GetMinX( void ) +{ + if( angle == 0.0 ) + return std::min( startPoint.x, endPoint.x ); + + // Calculate the leftmost point of the circle or arc + + if( IsCircle() ) + { + // if only everything were this easy + return center.x - radius; + } + + // cases: + // 1. CCW arc: if offset + included angle >= 180 deg then + // MinX = center.x - radius, otherwise MinX is the + // same as for the case of a line. + // 2. CW arc: if offset + included angle <= -180 deg then + // MinX = center.x - radius, otherwise MinX is the + // same as for the case of a line. + + if( angle > 0 ) + { + // CCW case + if( ( offsetAngle + angle ) >= 180.0 ) + { + return center.x - radius; + } + else + { + return std::min( startPoint.x, endPoint.x ); + } + } + + // CW case + if( ( offsetAngle + angle ) <= -180.0 ) + { + return center.x - radius; + } + + return std::min( startPoint.x, endPoint.x ); +} + + +void IDF_SEGMENT::SwapEnds( void ) +{ + if( IsCircle() ) + { + // reverse the direction + angle = -angle; + return; + } + + IDF_POINT tmp = startPoint; + startPoint = endPoint; + endPoint = tmp; + + if( ( angle < MIN_ANG ) && ( angle > -MIN_ANG ) ) + return; // nothing more to do + + // change the direction of the arc + angle = -angle; + // calculate the new offset angle + offsetAngle = IDF3::CalcAngleDeg( center, startPoint ); +} + + +bool IDF_OUTLINE::IsCCW( void ) +{ + // note: when outlines are not valid, 'false' is returned + switch( outline.size() ) + { + case 0: + // no outline + return false; + break; + + case 1: + // circles are always reported as CCW + if( outline.front()->IsCircle() ) + return true; + else + return false; + break; + + case 2: + // we may have a closed outline consisting of: + // 1. arc and line, winding depends on the arc + // 2. 2 arcs, winding depends on larger arc + { + double a1 = outline.front()->angle; + double a2 = outline.back()->angle; + + if( ( a1 < -MIN_ANG || a1 > MIN_ANG ) + && ( a2 < -MIN_ANG || a2 > MIN_ANG ) ) + { + // we have 2 arcs + if( abs( a1 ) >= abs( a2 ) ) + { + // winding depends on a1 + if( a1 < 0.0 ) + return false; + else + return true; + } + else + { + if( a2 < 0.0 ) + return false; + else + return true; + } + } + + // we may have a line + arc (or 2 lines) + if( a1 < -MIN_ANG ) + return false; + + if( a1 > MIN_ANG ) + return true; + + if( a2 < -MIN_ANG ) + return false; + + if( a2 > MIN_ANG ) + return true; + + // we have 2 lines (invalid outline) + return false; + } + break; + + default: + break; + } + + double winding = dir + ( outline.front()->startPoint.x - outline.back()->endPoint.x ) + * ( outline.front()->startPoint.y + outline.back()->endPoint.y ); + + if( winding > 0.0 ) + return false; + + return true; +} + + +// returns true if the outline is a circle +bool IDF_OUTLINE::IsCircle( void ) +{ + if( outline.front()->IsCircle() ) + return true; + + return false; +} + + +bool IDF_OUTLINE::push( IDF_SEGMENT* item ) +{ + if( !outline.empty() ) + { + if( item->IsCircle() ) + { + // not allowed + ERROR_IDF << "INVALID GEOMETRY\n"; + cerr << "* a circle is being added to a non-empty outline\n"; + return false; + } + else + { + if( outline.back()->IsCircle() ) + { + // we can't add lines to a circle + ERROR_IDF << "INVALID GEOMETRY\n"; + cerr << "* a line is being added to a circular outline\n"; + return false; + } + else if( !item->MatchesStart( outline.back()->endPoint ) ) + { + // startPoint[N] != endPoint[N -1] + ERROR_IDF << "INVALID GEOMETRY\n"; + cerr << "* disjoint segments (current start point != last end point)\n"; + cerr << "* start point: " << item->startPoint.x << ", " << item->startPoint.y << "\n"; + cerr << "* end point: " << outline.back()->endPoint.x << ", " << outline.back()->endPoint.y << "\n"; + return false; + } + } + } + + outline.push_back( item ); + dir += ( outline.back()->endPoint.x - outline.back()->startPoint.x ) + * ( outline.back()->endPoint.y + outline.back()->startPoint.y ); + + return true; +} diff --git a/utils/idftools/idf_common.h b/utils/idftools/idf_common.h new file mode 100644 index 0000000000..ffa9faf7f2 --- /dev/null +++ b/utils/idftools/idf_common.h @@ -0,0 +1,705 @@ +/** + * @file idf_common.h + */ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * 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 + */ + +#ifndef IDF_COMMON_H +#define IDF_COMMON_H + +#include +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795028841 +#endif + +#ifndef M_PI2 +#define M_PI2 ( M_PI / 2.0 ) +#endif + +#ifndef M_PI4 +#define M_PI4 ( M_PI / 4.0 ) +#endif + +// differences in angle smaller than MIN_ANG are considered equal +#define MIN_ANG (0.01) + +class IDF_POINT; +class IDF_SEGMENT; +class IDF_DRILL_DATA; +class IDF_OUTLINE; +class IDF_LIB; + + +struct IDF_ERROR : std::exception +{ + std::string message; + + IDF_ERROR( const char* aSourceFile, + const char* aSourceMethod, + int aSourceLine, + const std::string& aMessage ) throw(); + + virtual ~IDF_ERROR() throw(); + + virtual const char* what() const throw(); +}; + + +namespace IDF3 { + + /** + * ENUM FILE_STATE + * represents state values for the IDF parser's input + */ + enum FILE_STATE + { + FILE_START = 0, // no data has been read; expecting .HEADER + FILE_HEADER, // header has been read; expecting .BOARD_OUTLINE + FILE_OUTLINE, // board outline has been read; most sections can be accepted + FILE_PLACEMENT, // placement has been read; no further sections can be accepted + FILE_INVALID, // file is invalid + FILE_ERROR // other errors while processing the file + }; + + /** + * ENUM KEY_OWNER + * represents the type of CAD which has ownership an object + */ + enum KEY_OWNER + { + UNOWNED = 0, //< either MCAD or ECAD may modify a feature + MCAD, //< only MCAD may modify a feature + ECAD //< only ECAD may modify a feature + }; + + /** + * ENUM KEY_HOLETYPE + * represents the purpose of an IDF hole + */ + enum KEY_HOLETYPE + { + PIN = 0, //< drill hole is for a pin + VIA, //< drill hole is for a via + MTG, //< drill hole is for mounting + TOOL, //< drill hole is for tooling + OTHER //< user has specified a custom type + }; + + /** + * ENUM KEY_PLATING + * represents the plating condition of a hole + */ + enum KEY_PLATING + { + PTH = 0, //< Plate-Through Hole + NPTH //< Non-Plate-Through Hole + }; + + /** + * ENUM KEY_REFDES + * represents a component's Reference Designator + */ + enum KEY_REFDES + { + BOARD = 0, //< feature is associated with the board + NOREFDES, //< feature is associated with a component with no RefDes + PANEL, //< feature is associated with an IDF panel + REFDES //< reference designator as assigned by the CAD software + }; + + /** + * ENUM CAD_TYPE + * represents the class of CAD program which is opening or modifying a file + */ + enum CAD_TYPE + { + CAD_ELEC = 0, //< An Electrical CAD is opening/modifying the file + CAD_MECH, //< A Mechanical CAD is opening/modifying the file + CAD_INVALID + }; + + /** + * ENUM IDF_LAYER + * represents the various IDF layer classes and groupings + */ + enum IDF_LAYER + { + LYR_TOP = 0, + LYR_BOTTOM, + LYR_BOTH, + LYR_INNER, + LYR_ALL, + LYR_INVALID + }; + + /** + * ENUM OUTLINE_TYPE + * identifies the class of outline + */ + enum OUTLINE_TYPE + { + OTLN_BOARD = 0, + OTLN_OTHER, + OTLN_PLACE, + OTLN_ROUTE, + OTLN_PLACE_KEEPOUT, + OTLN_ROUTE_KEEPOUT, + OTLN_VIA_KEEPOUT, + OTLN_GROUP_PLACE, + OTLN_COMPONENT, + OTLN_INVALID + }; + + /** + * ENUM COMP_TYPE + * identifies whether a component is a mechanical or electrical part + */ + enum COMP_TYPE + { + COMP_ELEC = 0, //< Component library object is an electrical part + COMP_MECH, //< Component library object is a mechanical part + COMP_INVALID + }; + + /** + * ENUM IDF_UNIT + * represents the native unit of the board and of component outlines + */ + enum IDF_UNIT + { + UNIT_MM = 0, //< Units in the file are in millimeters + UNIT_THOU, //< Units in the file are in mils (aka thou) + UNIT_INVALID + }; + + /** + * ENUM IDF_PLACEMENT + * represents the placement status of a component + */ + enum IDF_PLACEMENT + { + PS_UNPLACED = 0, //< component location on the board has not been specified + PS_PLACED, //< component location has been specified and may be modified by ECAD or MCAD + PS_MCAD, //< component location has been specified and may only be modified by MCAD + PS_ECAD, //< component location has been specified and may only be modified by ECAD + PS_INVALID + }; + + /** + * Function CalcAngleRad + * calculates the angle (radians) between the horizon and the segment aStartPoint to aEndPoint + * + * @param aStartPoint is the start point of a line segment + * @param aEndPoint is the end point of a line segment + * + * @return double: the angle in radians + */ + double CalcAngleRad( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ); + + + /** + * Function CalcAngleDeg + * calculates the angle (degrees) between the horizon and the segment aStartPoint to aEndPoint + * + * @param aStartPoint is the start point of a line segment + * @param aEndPoint is the end point of a line segment + * + * @return double: the angle in degrees + */ + double CalcAngleDeg( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ); + + /** + * Function GetOutline + * takes contiguous elements from 'aLines' and stuffs them into 'aOutline'; elements put + * into the outline are deleted from aLines. This function is useful for sorting the jumbled + * mess of line segments and arcs which represent a board outline and cutouts in KiCad. + * The function will determine which segment element within aLines contains the leftmost + * point and retrieve the outline of which that segment is part. + * + * @param aLines (input/output) is a list of IDF segments which comprise an outline and + * cutouts. + * @param aOutline (output) is the ordered set of segments + */ + void GetOutline( std::list& aLines, + IDF_OUTLINE& aOutline ); + +#ifdef DEBUG_IDF + // prints out segment information for debug purposes + void PrintSeg( IDF_SEGMENT* aSegment ); +#endif +} + + +/** + * Class IDF_NOTE + * represents an entry in the NOTE section of an IDF file + */ +class IDF_NOTE +{ +private: + std::string text; // note text as per IDFv3 + double xpos; // text X position as per IDFv3 + double ypos; // text Y position as per IDFv3 + double height; // text height as per IDFv3 + double length; // text length as per IDFv3 + +public: + IDF_NOTE(); + + /** + * Function ReadNote + * reads a note entry from an IDFv3 file + * + * @param aBoardFile is an open BOARD file; the file position must be set to the start of a NOTE entry + * @param aBoardState is the parser's current state value + * @param aBoardUnit is the BOARD file's native units (MM or THOU) + * + * @return bool: true if a note item was read, false otherwise. In case of unrecoverable errors + * an exception is thrown + */ + bool ReadNote( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState, IDF3::IDF_UNIT aBoardUnit ); + + /** + * Function WriteNote + * writes a note entry to an IDFv3 file + * + * @param aBoardFile is an open BOARD file; the file position must be within a NOTE section + * @param aBoardUnit is the BOARD file's native units (MM or THOU) + * + * @return bool: true if the item was successfully written, false otherwise. In case of + * unrecoverable errors an exception is thrown + */ + bool WriteNote( std::ofstream& aBoardFile, IDF3::IDF_UNIT aBoardUnit ); + + /** + * Function SetText + * sets the text to be stored as a NOTE entry + */ + void SetText( const std::string& aText ); + + /** + * Function SetPosition + * sets the position (mm) of the NOTE entry + */ + void SetPosition( double aXpos, double aYpos ); + + /** + * Function SetSize + * sets the height and length (mm) of the NOTE entry + */ + void SetSize( double aHeight, double aLength ); + + /** + * Function GetText + * returns the string stored in the note entry + */ + const std::string& GetText( void ); + + /** + * Function GetText + * returns the position (mm) of the note entry + */ + void GetPosition( double& aXpos, double& aYpos ); + + /** + * Function GetText + * returns the height and length (mm) of the note entry + */ + void GetSize( double& aHeight, double& aLength ); +}; + + +/** + * @Class IDF_DRILL_DATA + * contains information describing a drilled hole and is responsible for + * writing this information to a file in compliance with the IDFv3 specification. + */ +class IDF_DRILL_DATA +{ +private: + double dia; + double x; + double y; + IDF3::KEY_PLATING plating; + IDF3::KEY_REFDES kref; + IDF3::KEY_HOLETYPE khole; + std::string refdes; + std::string holetype; + IDF3::KEY_OWNER owner; + +public: + /** + * Constructor IDF_DRILL_DATA + * creates an empty drill entry which can be populated by the + * Read() function + */ + IDF_DRILL_DATA(); + + /** + * Constructor IDF_DRILL_DATA + * creates a drill entry with information compliant with the + * IDFv3 specifications. + * @param aDrillDia : drill diameter + * @param aPosX : X coordinate of the drill center + * @param aPosY : Y coordinate of the drill center + * @param aPlating : flag, PTH or NPTH + * @param aRefDes : component Reference Designator + * @param aHoleType : purpose of hole + * @param aOwner : one of MCAD, ECAD, UNOWNED + */ + 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 ); + + /** + * Function Matches + * returns true if the given drill diameter and location + * matches the diameter and location of this IDF_DRILL_DATA object + * + * @param aDrillDia is the drill diameter (mm) + * @param aPosX is the X position (mm) of the drilled hole + * @param aPosY is the Y position (mm) of the drilled hole + * + * @return bool: true if the diameter and position match this object + */ + bool Matches( double aDrillDia, double aPosX, double aPosY ); + + /** + * Function Read + * read a drill entry from an IDFv3 file + * + * @param aBoardFile is an open IDFv3 file; the file position must be within the DRILLED_HOLES section + * @param aBoardUnit is the board file's native unit (MM or THOU) + * @param aBoardState is the state value of the parser + * + * @return bool: true if data was successfully read, otherwise false. In case of an + * unrecoverable error an exception is thrown + */ + bool Read( std::ifstream& aBoardFile, IDF3::IDF_UNIT aBoardUnit, IDF3::FILE_STATE aBoardState ); + + /** + * Function Write + * writes a single line representing a hole within a .DRILLED_HOLES section + * + * @param aBoardFile is an open BOARD file + * @param aBoardUnit is the native unit of the output file + * + * @return bool: true if the data was successfully written, otherwise false. In case of + * an unrecoverable error an exception is thrown + */ + bool Write( std::ofstream& aBoardFile, IDF3::IDF_UNIT aBoardUnit ); + + /** + * Function GettDrillDia + * returns the drill diameter in mm + */ + double GetDrillDia(); + + + /** + * Function GettDrillXPos + * returns the drill's X position in mm + */ + double GetDrillXPos(); + + /** + * Function GettDrillYPos + * returns the drill's Y position in mm + */ + double GetDrillYPos(); + + /** + * Function GetDrillPlating + * returns the plating value (PTH, NPTH) + */ + IDF3::KEY_PLATING GetDrillPlating(); + + /** + * Function GetDrillRefDes + * returns the reference designator of the hole; this + * may be a component reference designator, BOARD, or + * NOREFDES as per IDFv3. + */ + const std::string& GetDrillRefDes(); + + /** + * Function GetDrillHoleType + * returns the classification of the hole; this may be one of + * PIN, VIA, MTG, TOOL, or a user-specified string + */ + const std::string& GetDrillHoleType(); +}; + + +/** + * @Class IDF_POINT + * represents a point as used by the various IDF related classes + */ +class IDF_POINT +{ +public: + double x; // < X coordinate + double y; // < Y coordinate + + IDF_POINT() + { + x = 0.0; + y = 0.0; + } + + /** + * Function Matches() + * returns true if the given coordinate point is within the given radius + * of the point. + * + * @param aPoint : coordinates of the point being compared + * @param aRadius : radius (mm) within which the points are considered the same + * + * @return bool: true if this point matches the given point + */ + bool Matches( const IDF_POINT& aPoint, double aRadius = 1e-5 ); + + /** + * Function CalcDistance() + * returns the Euclidean distance between this point and the given point + * + * @param aPoint : coordinates of the point whose distance is to be determined + * + * @return double: distance between this point and aPoint + */ + double CalcDistance( const IDF_POINT& aPoint ) const; +}; + + +/** + * @Class IDF_SEGMENT + * represents a geometry segment as used in IDFv3 outlines; it may be any of + * an arc, line segment, or circle + */ +class IDF_SEGMENT +{ +private: + /** + * Function CalcCenterAndRadius() + * Calculates the center, radius, and angle between center and start point given the + * IDF compliant points and included angle. + * + * @var startPoint, @var endPoint, and @var angle must be set prior as per IDFv3 + */ + void CalcCenterAndRadius( void ); + +public: + IDF_POINT startPoint; ///< starting point coordinates in mm + IDF_POINT endPoint; ///< end point coordinates in mm + IDF_POINT center; ///< center of an arc or circle; internally calculated and not to be set by the user + double angle; ///< included angle (degrees) according to IDFv3 specification + double offsetAngle; ///< angle between center and start of arc; internally calculated + double radius; ///< radius of the arc or circle; internally calculated + + /** + * Constructor IDF_SEGMENT + * initializes the internal variables + */ + IDF_SEGMENT(); + + /** + * Function IDF_SEGMENT + * creates a straight segment + */ + IDF_SEGMENT( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ); + + /** + * Constructor IDF_SEGMENT + * creates a straight segment, arc, or circle depending on the angle + * + * @param aStartPoint : start point (center if using KiCad convention, otherwise IDF convention) + * @param aEndPoint : end point (start of arc if using KiCad convention, otherwise IDF convention) + * @param aAngle : included angle; the KiCad convention is equivalent to the IDF convention + * @param fromKicad : set true if we need to convert from KiCad to IDF convention + */ + IDF_SEGMENT( const IDF_POINT& aStartPoint, + const IDF_POINT& aEndPoint, + double aAngle, + bool aFromKicad ); + + /** + * Function MatchesStart + * returns true if the given coordinate is within a radius 'rad' + * of the start point. + * + * @param aPoint : coordinates of the point (mm) being compared + * @param aRadius : radius (mm) within which the points are considered the same + * + * @return bool: true if the given point matches the start point of this segment + */ + bool MatchesStart( const IDF_POINT& aPoint, double aRadius = 1e-3 ); + + /** + * Function MatchesEnd + * returns true if the given coordinate is within a radius 'rad' + * of the end point. + * + * @param aPoint : coordinates (mm) of the point being compared + * @param aRadius : radius (mm) within which the points are considered the same + * + * @return bool: true if the given point matches the end point of this segment + */ + bool MatchesEnd( const IDF_POINT& aPoint, double aRadius = 1e-3 ); + + /** + * Function IsCircle + * returns true if this segment is a circle + */ + bool IsCircle( void ); + + /** + * Function GetMinX() + * returns the minimum X coordinate of this segment + */ + double GetMinX( void ); + + /** + * Function SwapEnds() + * Swaps the start and end points and alters internal + * variables as necessary for arcs + */ + void SwapEnds( void ); +}; + + +/** + * @Class IDF_OUTLINE + * contains segment and winding information for an IDF outline + */ +class IDF_OUTLINE +{ +private: + double dir; // accumulator to help determine winding direction + std::list outline; // sequential segments comprising an outline + +public: + IDF_OUTLINE() { dir = 0.0; } + ~IDF_OUTLINE() { Clear(); } + + /** + * Function IsCCW + * returns true if the current list of points represents a counterclockwise winding + */ + bool IsCCW( void ); + + /** + * Function IsCircle + * returns true if this outline is a circle + */ + bool IsCircle( void ); + + /** + * Function Clear + * clears the internal list of outline segments + */ + void Clear( void ) + { + dir = 0.0; + + while( !outline.empty() ) + { + delete outline.front(); + outline.pop_front(); + } + } + + /** + * Function size + * returns the size of the internal segment list + */ + size_t size( void ) + { + return outline.size(); + } + + /** + * Function empty + * returns true if the internal segment list is empty + */ + bool empty( void ) + { + return outline.empty(); + } + + /** + * Function front + * returns the front() iterator of the internal segment list + */ + IDF_SEGMENT*& front( void ) + { + return outline.front(); + } + + /** + * Function back + * returns the back() iterator of the internal segment list + */ + IDF_SEGMENT*& back( void ) + { + return outline.back(); + } + + /** + * Function begin + * returns the begin() iterator of the internal segment list + */ + std::list::iterator begin( void ) + { + return outline.begin(); + } + + /** + * Function end + * returns the end() iterator of the internal segment list + */ + std::list::iterator end( void ) + { + return outline.end(); + } + + /** + * Function push + * adds a segment to the internal segment list; segments must be added + * in order so that startPoint[N] == endPoint[N - 1] + * + * @param item is a pointer to the segment to add to the outline + * + * @return bool: true if the segment was added, otherwise false + * (outline restrictions have been violated) + */ + bool push( IDF_SEGMENT* item ); +}; + +#endif // IDF_COMMON_H diff --git a/utils/idftools/idf_cylinder.cpp b/utils/idftools/idf_cylinder.cpp index f64a9b3d5c..3c164f7ead 100644 --- a/utils/idftools/idf_cylinder.cpp +++ b/utils/idftools/idf_cylinder.cpp @@ -149,7 +149,9 @@ int main( int argc, char **argv ) tstr.clear(); tstr.str( line ); - if( (tstr >> dia) && dia > 0.0 ) + + tstr >> dia; + if( !tstr.fail() && dia > 0.0 ) ok = true; } @@ -163,7 +165,9 @@ int main( int argc, char **argv ) tstr.clear(); tstr.str( line ); - if( (tstr >> length) && length > 0.0 ) + + tstr >> length; + if( !tstr.fail() && length > 0.0 ) ok = true; } @@ -177,7 +181,9 @@ int main( int argc, char **argv ) tstr.clear(); tstr.str( line ); - if( (tstr >> extraZ) && extraZ >= 0.0 ) + + tstr >> extraZ; + if( !tstr.fail() && extraZ >= 0.0 ) ok = true; } @@ -191,7 +197,9 @@ int main( int argc, char **argv ) tstr.clear(); tstr.str( line ); - if( (tstr >> wireDia) && wireDia > 0.0 ) + + tstr >> wireDia; + if( !tstr.fail() && wireDia > 0.0 ) { if( wireDia < dia ) ok = true; @@ -236,7 +244,9 @@ void make_vcyl( bool inch, bool axial, double dia, double length, tstr.clear(); tstr.str( line ); - if( (tstr >> pitch) && pitch > 0.0 ) + + tstr >> pitch; + if( !tstr.fail() && pitch > 0.0 ) { if( (pitch - wireDia) <= (dia / 2.0) ) { @@ -436,7 +446,9 @@ void make_hcyl( bool inch, bool axial, double dia, double length, tstr.clear(); tstr.str( line ); - if( (tstr >> pitch) && pitch > 0.0 ) + + tstr >> pitch; + if( !tstr.fail() && pitch > 0.0 ) { if( axial ) { @@ -477,7 +489,9 @@ void make_hcyl( bool inch, bool axial, double dia, double length, tstr.clear(); tstr.str( line ); - if( (tstr >> lead) && lead > 0.0 ) + + tstr >> lead; + if( !tstr.fail() && lead > 0.0 ) { if( lead < wireDia ) cout << "* WARNING: lead length must be >= wireDia\n"; diff --git a/utils/idftools/idf_examples/Arduino_MEGA_2560-Rev3.emn b/utils/idftools/idf_examples/Arduino_MEGA_2560-Rev3.emn new file mode 100644 index 0000000000..4c21583dca --- /dev/null +++ b/utils/idftools/idf_examples/Arduino_MEGA_2560-Rev3.emn @@ -0,0 +1,149 @@ +.HEADER +BOARD_FILE 3.0 "Created by KiCad (2014-01-21 BZR 4629)-product" 2014/01/23.09:19:46 1 +"Arduino_MEGA_2560-Rev3.kicad_pcb" MM +.END_HEADER + +.BOARD_OUTLINE ECAD +1.60000 +0 246.20220 -25.67000 0 +0 247.20220 -26.67000 0 +0 344.26220 -26.67000 0 +0 345.26220 -25.67000 0 +0 345.26220 -25.40000 0 +0 347.80220 -22.86000 0 +0 347.80220 11.43000 0 +0 345.26220 13.97000 0 +0 345.26220 24.13000 0 +0 342.72220 26.67000 0 +0 247.20220 26.67000 0 +0 246.20220 25.67000 0 +0 246.20220 -25.67000 0 +.END_BOARD_OUTLINE + +.DRILLED_HOLES +3.200 342.72220 -24.13000 NPTH "@HOLE0" MTG ECAD +3.200 261.44220 24.13000 NPTH "@HOLE1" MTG ECAD +3.200 336.37220 24.13000 NPTH "@HOLE2" MTG ECAD +3.200 260.17220 -24.13000 NPTH "@HOLE3" MTG ECAD +3.200 312.24220 8.89000 NPTH "@HOLE4" MTG ECAD +3.200 312.24220 -19.05000 NPTH "@HOLE5" MTG ECAD +0.950 309.82920 3.81000 PTH "ICSP" PIN ECAD +0.950 312.36920 3.81000 PTH "ICSP" PIN ECAD +0.950 309.82920 1.27000 PTH "ICSP" PIN ECAD +0.950 312.36920 1.27000 PTH "ICSP" PIN ECAD +0.950 309.82920 -1.27000 PTH "ICSP" PIN ECAD +0.950 312.36920 -1.27000 PTH "ICSP" PIN ECAD +0.850 309.70220 24.13000 PTH "PWML" PIN ECAD +0.850 307.16220 24.13000 PTH "PWML" PIN ECAD +0.850 304.62220 24.13000 PTH "PWML" PIN ECAD +0.850 302.08220 24.13000 PTH "PWML" PIN ECAD +0.850 299.54220 24.13000 PTH "PWML" PIN ECAD +0.850 297.00220 24.13000 PTH "PWML" PIN ECAD +0.850 294.46220 24.13000 PTH "PWML" PIN ECAD +0.850 291.92220 24.13000 PTH "PWML" PIN ECAD +1.400 254.88900 -23.36800 PTH "X1" PIN ECAD +1.400 257.88620 -18.36420 PTH "X1" PIN ECAD +1.400 251.89180 -18.36420 PTH "X1" PIN ECAD +1.400 255.65100 -23.36800 PTH "X1" PIN ECAD +1.400 254.12700 -23.36800 PTH "X1" PIN ECAD +1.400 251.89180 -19.12620 PTH "X1" PIN ECAD +1.400 251.89180 -17.60220 PTH "X1" PIN ECAD +1.400 257.88620 -19.12620 PTH "X1" PIN ECAD +1.400 257.88620 -17.60220 PTH "X1" PIN ECAD +0.850 297.00220 -24.13000 PTH "ADCL" PIN ECAD +0.850 299.54220 -24.13000 PTH "ADCL" PIN ECAD +0.850 302.08220 -24.13000 PTH "ADCL" PIN ECAD +0.850 304.62220 -24.13000 PTH "ADCL" PIN ECAD +0.850 307.16220 -24.13000 PTH "ADCL" PIN ECAD +0.850 309.70220 -24.13000 PTH "ADCL" PIN ECAD +0.850 312.24220 -24.13000 PTH "ADCL" PIN ECAD +0.850 314.78220 -24.13000 PTH "ADCL" PIN ECAD +0.850 332.56220 24.13000 PTH "COMMUNICATION" PIN ECAD +0.850 330.02220 24.13000 PTH "COMMUNICATION" PIN ECAD +0.850 327.48220 24.13000 PTH "COMMUNICATION" PIN ECAD +0.850 324.94220 24.13000 PTH "COMMUNICATION" PIN ECAD +0.850 322.40220 24.13000 PTH "COMMUNICATION" PIN ECAD +0.850 319.86220 24.13000 PTH "COMMUNICATION" PIN ECAD +0.850 317.32220 24.13000 PTH "COMMUNICATION" PIN ECAD +0.850 314.78220 24.13000 PTH "COMMUNICATION" PIN ECAD +0.850 319.86220 -24.13000 PTH "ADCH" PIN ECAD +0.850 322.40220 -24.13000 PTH "ADCH" PIN ECAD +0.850 324.94220 -24.13000 PTH "ADCH" PIN ECAD +0.850 327.48220 -24.13000 PTH "ADCH" PIN ECAD +0.850 330.02220 -24.13000 PTH "ADCH" PIN ECAD +0.850 332.56220 -24.13000 PTH "ADCH" PIN ECAD +0.850 335.10220 -24.13000 PTH "ADCH" PIN ECAD +0.850 337.64220 -24.13000 PTH "ADCH" PIN ECAD +0.950 254.72220 10.18000 PTH "X2" PIN ECAD +0.950 254.72220 12.68000 PTH "X2" PIN ECAD +0.950 252.72220 12.68000 PTH "X2" PIN ECAD +0.950 252.72220 10.18000 PTH "X2" PIN ECAD +2.200 250.01220 17.43000 PTH "X2" PIN ECAD +2.200 250.01220 5.43000 PTH "X2" PIN ECAD +0.950 267.03020 20.82800 PTH "ICSP1" PIN ECAD +0.950 267.03020 18.28800 PTH "ICSP1" PIN ECAD +0.950 264.49020 20.82800 PTH "ICSP1" PIN ECAD +0.950 264.49020 18.28800 PTH "ICSP1" PIN ECAD +0.950 261.95020 20.82800 PTH "ICSP1" PIN ECAD +0.950 261.95020 18.28800 PTH "ICSP1" PIN ECAD +0.850 267.15720 -0.63500 PTH "Y2" PIN ECAD +0.850 262.07720 -0.63500 PTH "Y2" PIN ECAD +0.950 267.03020 13.20800 PTH "JP5" PIN ECAD +0.950 264.49020 13.20800 PTH "JP5" PIN ECAD +0.950 267.03020 15.74800 PTH "JP5" PIN ECAD +0.950 264.49020 15.74800 PTH "JP5" PIN ECAD +0.950 340.18220 24.13000 PTH "XIO" PIN ECAD +0.950 342.72220 24.13000 PTH "XIO" PIN ECAD +0.950 340.18220 21.59000 PTH "XIO" PIN ECAD +0.950 342.72220 21.59000 PTH "XIO" PIN ECAD +0.950 340.18220 19.05000 PTH "XIO" PIN ECAD +0.950 342.72220 19.05000 PTH "XIO" PIN ECAD +0.950 340.18220 16.51000 PTH "XIO" PIN ECAD +0.950 342.72220 16.51000 PTH "XIO" PIN ECAD +0.950 340.18220 13.97000 PTH "XIO" PIN ECAD +0.950 342.72220 13.97000 PTH "XIO" PIN ECAD +0.950 340.18220 11.43000 PTH "XIO" PIN ECAD +0.950 342.72220 11.43000 PTH "XIO" PIN ECAD +0.950 340.18220 8.89000 PTH "XIO" PIN ECAD +0.950 342.72220 8.89000 PTH "XIO" PIN ECAD +0.950 340.18220 6.35000 PTH "XIO" PIN ECAD +0.950 342.72220 6.35000 PTH "XIO" PIN ECAD +0.950 340.18220 3.81000 PTH "XIO" PIN ECAD +0.950 342.72220 3.81000 PTH "XIO" PIN ECAD +0.950 340.18220 1.27000 PTH "XIO" PIN ECAD +0.950 342.72220 1.27000 PTH "XIO" PIN ECAD +0.950 340.18220 -1.27000 PTH "XIO" PIN ECAD +0.950 342.72220 -1.27000 PTH "XIO" PIN ECAD +0.950 340.18220 -3.81000 PTH "XIO" PIN ECAD +0.950 342.72220 -3.81000 PTH "XIO" PIN ECAD +0.950 340.18220 -6.35000 PTH "XIO" PIN ECAD +0.950 342.72220 -6.35000 PTH "XIO" PIN ECAD +0.950 340.18220 -8.89000 PTH "XIO" PIN ECAD +0.950 342.72220 -8.89000 PTH "XIO" PIN ECAD +0.950 340.18220 -11.43000 PTH "XIO" PIN ECAD +0.950 342.72220 -11.43000 PTH "XIO" PIN ECAD +0.950 340.18220 -13.97000 PTH "XIO" PIN ECAD +0.950 342.72220 -13.97000 PTH "XIO" PIN ECAD +0.950 340.18220 -16.51000 PTH "XIO" PIN ECAD +0.950 342.72220 -16.51000 PTH "XIO" PIN ECAD +0.950 340.18220 -19.05000 PTH "XIO" PIN ECAD +0.950 342.72220 -19.05000 PTH "XIO" PIN ECAD +0.850 264.99820 24.13000 PTH "JP6" PIN ECAD +0.850 267.53820 24.13000 PTH "JP6" PIN ECAD +0.850 270.07820 24.13000 PTH "JP6" PIN ECAD +0.850 272.61820 24.13000 PTH "JP6" PIN ECAD +0.850 275.15820 24.13000 PTH "JP6" PIN ECAD +0.850 277.69820 24.13000 PTH "JP6" PIN ECAD +0.850 280.23820 24.13000 PTH "JP6" PIN ECAD +0.850 282.77820 24.13000 PTH "JP6" PIN ECAD +0.850 285.31820 24.13000 PTH "JP6" PIN ECAD +0.850 287.85820 24.13000 PTH "JP6" PIN ECAD +0.850 274.14220 -24.13000 PTH "POWER" PIN ECAD +0.850 276.68220 -24.13000 PTH "POWER" PIN ECAD +0.850 279.22220 -24.13000 PTH "POWER" PIN ECAD +0.850 281.76220 -24.13000 PTH "POWER" PIN ECAD +0.850 284.30220 -24.13000 PTH "POWER" PIN ECAD +0.850 286.84220 -24.13000 PTH "POWER" PIN ECAD +0.850 289.38220 -24.13000 PTH "POWER" PIN ECAD +0.850 291.92220 -24.13000 PTH "POWER" PIN ECAD +.END_DRILLED_HOLES diff --git a/utils/idftools/idf_examples/Arduino_MEGA_2560-Rev3.emp b/utils/idftools/idf_examples/Arduino_MEGA_2560-Rev3.emp new file mode 100644 index 0000000000..693ae28c6f --- /dev/null +++ b/utils/idftools/idf_examples/Arduino_MEGA_2560-Rev3.emp @@ -0,0 +1,4 @@ +.HEADER +LIBRARY_FILE 3.0 "Created by KiCad (2014-01-21 BZR 4629)-product" 2014/01/23.09:19:46 1 +.END_HEADER + diff --git a/utils/idftools/idf_examples/idf_example.emn b/utils/idftools/idf_examples/idf_example.emn new file mode 100644 index 0000000000..417e5dd5af --- /dev/null +++ b/utils/idftools/idf_examples/idf_example.emn @@ -0,0 +1,269 @@ +.HEADER +BOARD_FILE 3.0 "Sample File Generator" 10/22/96.16:02:44 1 +sample_board THOU +.END_HEADER + +# This is the first BOARD section +# SEC1-0 +# SEC1-1 +.BOARD_OUTLINE MCAD +62.0 +0 5030.5 -120.0 0.0 +0 5187.5 -120.0 0.0 +0 5187.5 2130.0 0.0 +0 5155.0 2130.0 0.0 +0 5155.0 2550.0 -180.0 +0 5187.5 2550.0 0.0 +0 5187.5 4935.0 0.0 +0 4945.0 5145.0 0.0 +0 4945.0 5420.0 0.0 +0 4865.0 5500.0 0.0 +0 210.0 5500.0 0.0 +0 130.0 5420.0 0.0 +0 130.0 5145.0 0.0 +0 -112.5 4935.0 0.0 +0 -112.5 2550.0 0.0 +0 -80.0 2550.0 0.0 +0 -80.0 2130.0 -180.0 +0 -112.5 2130.0 0.0 +0 -112.5 -140.0 0.0 +0 45.5 -140.0 0.0 +0 45.5 -400.0 0.0 +0 2442.5 -400.0 0.0 +0 2442.5 -140.0 0.0 +0 2631.5 -140.0 0.0 +0 2631.5 -400.0 0.0 +0 5030.5 -400.0 0.0 +0 5030.5 -120.0 0.0 +1 2650.0 2350.0 0.0 +1 3000.0 2350.0 360.0 +.END_BOARD_OUTLINE + + +# This is the second BOARD section +# SEC2-0 +# SEC2-1 +# NOT SEC1-1 +.ROUTE_OUTLINE ECAD +ALL +0 5112.5 150.0 0.0 +0 5112.5 2058.2 0.0 +0 5112.5 2621.8 -162.9 +0 5112.5 4863.2 0.0 +0 4878.8 5075.0 0.0 +0 226.4 5075.0 0.0 +0 138.0 4910.3 0.0 +0 138.0 4800.0 0.0 +0 -37.5 4662.5 0.0 +0 -37.5 2621.8 0.0 +0 -37.5 2058.2 -162.9 +0 -37.5 150.0 0.0 +0 162.5 0.0 0.0 +0 4912.5 0.0 0.0 +0 5112.5 150.0 0.0 +.END_ROUTE_OUTLINE + + +# This is the third BOARD section +# SEC3-0 +# SEC3-1 +.PLACE_OUTLINE MCAD +TOP 1000.0 +0 5080.0 2034.9 0.0 +0 5080.0 2645.1 -152.9 +0 5080.0 4837.3 0.0 +0 4855.3 5042.5 0.0 +0 252.9 5042.5 0.0 +0 170.5 4896.9 0.0 +0 170.5 4798.4 0.0 +0 -5.0 4659.0 0.0 +0 -5.0 2645.1 0.0 +0 -5.0 2034.9 -152.9 +0 -5.0 182.5 0.0 +0 192.0 32.5 0.0 +0 4883.1 32.5 0.0 +0 5080.0 182.5 0.0 +0 5080.0 2034.9 0.0 +.END_PLACE_OUTLINE + +# This is the fourth BOARD section +# SEC4-0 +# SEC4-1 +.PLACE_OUTLINE UNOWNED +BOTTOM 200.0 +0 300.0 200.0 0.0 +0 4800.0 200.0 0.0 +0 4800.0 4800.0 0.0 +0 300.0 4800.0 0.0 +0 300.0 200.0 0.0 +.END_PLACE_OUTLINE + + +# This is the fifth BOARD section +# SEC5-0 +# SEC5-1 +.ROUTE_KEEPOUT ECAD +ALL +0 2650.0 2350.0 0.0 +0 3100.0 2350.0 360.0 +.END_ROUTE_KEEPOUT + +# This is the sixth BOARD section +# SEC6-0 +# SEC6-1 +.PLACE_KEEPOUT MCAD +BOTH 0.0 +0 2650.0 2350.0 0.0 +0 3100.0 2350.0 360.0 +.END_PLACE_KEEPOUT + +# This is the seventh BOARD section +# SEC7-0 +# SEC7-1 +.PLACE_KEEPOUT MCAD +TOP 300.0 +0 3700.0 5000.0 0.0 +0 3700.0 4300.0 0.0 +0 4000.0 4300.0 0.0 +0 4000.0 3700.0 0.0 +0 5000.0 3700.0 0.0 +0 5000.0 4800.0 0.0 +0 4800.0 5000.0 0.0 +0 3700.0 5000.0 0.0 +.END_PLACE_KEEPOUT + + +# This is the eighth BOARD section +# SEC8-0 +# SEC8-1 +.DRILLED_HOLES +30.0 1800.0 100.0 PTH J1 PIN ECAD +30.0 1700.0 100.0 PTH J1 PIN ECAD +30.0 1600.0 100.0 PTH J1 PIN ECAD +30.0 1500.0 100.0 PTH J1 PIN ECAD +30.0 1400.0 100.0 PTH J1 PIN ECAD +30.0 1300.0 100.0 PTH J1 PIN ECAD +30.0 1200.0 100.0 PTH J1 PIN ECAD +30.0 1100.0 100.0 PTH J1 PIN ECAD +30.0 1000.0 100.0 PTH J1 PIN ECAD +30.0 0900.0 100.0 PTH J1 PIN ECAD +30.0 0800.0 100.0 PTH J1 PIN ECAD +30.0 0700.0 100.0 PTH J1 PIN ECAD +30.0 0700.0 200.0 PTH J1 PIN ECAD +30.0 0800.0 200.0 PTH J1 PIN ECAD +30.0 0900.0 200.0 PTH J1 PIN ECAD +30.0 1000.0 200.0 PTH J1 PIN ECAD +30.0 1100.0 200.0 PTH J1 PIN ECAD +30.0 1200.0 200.0 PTH J1 PIN ECAD +30.0 1300.0 200.0 PTH J1 PIN ECAD +30.0 1400.0 200.0 PTH J1 PIN ECAD +30.0 1500.0 200.0 PTH J1 PIN ECAD +30 1600 200 PTH J1 PIN ECAD +30 1700 200 PTH J1 PIN ECAD +30 1800 200 PTH J1 PIN ECAD +30 4400 100 PTH J2 PIN ECAD +30 4300 100 PTH J2 PIN ECAD +30 4200 100 PTH J2 PIN ECAD +30 4100 100 PTH J2 PIN ECAD +30 4000 100 PTH J2 PIN ECAD +30 3900 100 PTH J2 PIN ECAD +30 3800 100 PTH J2 PIN ECAD +30 3700 100 PTH J2 PIN ECAD +30 3600 100 PTH J2 PIN ECAD +30 3500 100 PTH J2 PIN ECAD +30 3400 100 PTH J2 PIN ECAD +30 3300 100 PTH J2 PIN ECAD +30 3300 200 PTH J2 PIN ECAD +30 3400 200 PTH J2 PIN ECAD +30 3500 200 PTH J2 PIN ECAD +30 3600 200 PTH J2 PIN ECAD +30 3700 200 PTH J2 PIN ECAD +30 3800 200 PTH J2 PIN ECAD +30 3900 200 PTH J2 PIN ECAD +30 4000 200 PTH J2 PIN ECAD +30 4100 200 PTH J2 PIN ECAD +30 4200 200 PTH J2 PIN ECAD +30 4300 200 PTH J2 PIN ECAD +30 4400 200 PTH J2 PIN ECAD +30 3000 3300 PTH U3 PIN ECAD +30 3024.2 3203 PTH U3 PIN ECAD +30 3048.4 3105.9 PTH U3 PIN ECAD +30 3072.6 3008.9 PTH U3 PIN ECAD +30 3096.8 2911.9 PTH U3 PIN ECAD +30 3121 2814.9 PTH U3 PIN ECAD +30 3145.2 2717.8 PTH U3 PIN ECAD +30 3436.2 2790.4 PTH U3 PIN ECAD +30 3412.1 2887.4 PTH U3 PIN ECAD +30 3387.9 2984.5 PTH U3 PIN ECAD +30 3363.7 3081.5 PTH U3 PIN ECAD +30 3339.5 3178.5 PTH U3 PIN ECAD +30 3315.3 3275.6 PTH U3 PIN ECAD +30 3291.1 3372.6 PTH U3 PIN ECAD +30 2200 2500 PTH U4 PIN ECAD +30 2100 2500 PTH U4 PIN ECAD +30 2000 2500 PTH U4 PIN ECAD +30 1900 2500 PTH U4 PIN ECAD +30 1800 2500 PTH U4 PIN ECAD +30 1700 2500 PTH U4 PIN ECAD +30 1600 2500 PTH U4 PIN ECAD +30 1600 2200 PTH U4 PIN ECAD +30 1700 2200 PTH U4 PIN ECAD +30 1800 2200 PTH U4 PIN ECAD +30 1900 2200 PTH U4 PIN ECAD +30 2000 2200 PTH U4 PIN ECAD +30 2100 2200 PTH U4 PIN ECAD +30 2200 2200 PTH U4 PIN ECAD +20 2500 3100 PTH BOARD VIA ECAD +20 2500 3200 PTH BOARD VIA ECAD +20 2500 3300 PTH BOARD VIA ECAD +20 2000 1600 PTH BOARD VIA ECAD +20 1100 900 PTH BOARD VIA ECAD +20 1200 1600 PTH BOARD VIA ECAD +20 3900 3800 PTH BOARD VIA ECAD +20 3900 2300 PTH BOARD VIA ECAD +100.0 3100.0 -50.0 NPTH J2 MTG ECAD +100.0 4600.0 -50.0 NPTH J2 MTG ECAD +100.0 500.0 -50.0 NPTH J1 MTG ECAD +100.0 2000.0 -50.0 NPTH J1 MTG ECAD +93.0 5075.0 0.0 PTH BOARD MTG UNOWNED +93.0 0.0 4800.0 NPTH BOARD TOOL MCAD +93.0 0.0 0.0 PTH BOARD MTG UNOWNED +.END_DRILLED_HOLES + + +# This is the ninth BOARD section +# SEC9-0 +# SEC9-1 +.NOTES +3500.0 3300.0 75.0 2500.0 "This component rotated 14 degrees" +400.0 4400.0 75.0 3200.0 "Component height limited by enclosure latch" +1800.0 300.0 75.0 1700.0 "Do not move connectors!" +.END_NOTES + +# This is the tenth and ALWAYS FINAL BOARD section +# SEC10-0 +# SEC10-1 +.PLACEMENT +cs13_a pn-cap C1 +4000.0 1000.0 100.0 0.0 TOP PLACED +cc1210 pn-cc1210 C2 +3000.0 3500.0 0.0 0.0 TOP PLACED +cc1210 pn-cc1210 C3 +3200.0 1800.0 0.0 0.0 BOTTOM PLACED +cc1210 pn-cc1210 C4 +1400.0 2300.0 0.0 270.0 TOP PLACED +cc1210 pn-cc1210 C5 +1799.5 3518.1 0.0 0.0 BOTTOM PLACED +conn_din24 connector J1 +1800.0 100.0 0.0 0.0 TOP MCAD +conn_din24 connector J2 +4400.0 100.0 0.0 0.0 TOP MCAD +plcc_20 pn-pal16l8-plcc U1 +1800.0 3200.0 0.0 0.0 BOTTOM ECAD +plcc_20 pn-pal16l8-plcc U2 +3200.0 1800.0 0.0 0.0 TOP PLACED +dip_14w pn-hs346-dip U3 +3000.0 3300.0 0.0 14.0 TOP PLACED +dip_14w pn-hs346-dip U4 +2200.0 2500.0 0.0 270.0 TOP PLACED +.END_PLACEMENT diff --git a/utils/idftools/idf_examples/idf_example.emp b/utils/idftools/idf_examples/idf_example.emp new file mode 100644 index 0000000000..b25b00056e --- /dev/null +++ b/utils/idftools/idf_examples/idf_example.emp @@ -0,0 +1,69 @@ +.HEADER +LIBRARY_file 3.0 "Sample File Generator" 10/22/96.16:41:37 1 +.END_HEADER + +# Component #1/5 +.ELECTRICAL +cs13_a pn-cap THOU 150.0 +0 -55.0 55.0 0.0 +0 -55.0 -55.0 0.0 +0 135.0 -55.0 0.0 +0 135.0 -80.0 0.0 +0 565.0 -80.0 0.0 +0 565.0 -55.0 0.0 +0 755.0 -55.0 0.0 +0 755.0 55.0 0.0 +0 565.0 55.0 0.0 +0 565.0 80.0 0.0 +0 135.0 80.0 0.0 +0 135.0 55.0 0.0 +0 -55.0 55.0 0.0 +PROP CAPACITANCE 100.0 +PROP TOLERANCE 5.0 +.END_ELECTRICAL + + +# Component #2/5 +.ELECTRICAL +cc1210 pn-cc1210 THOU 67.0 +0 -40.0 56.0 0.0 +0 -40.0 -56.0 0.0 +0 182.0 -56.0 0.0 +0 182.0 56.0 0.0 +0 -40.0 56.0 0.0 +PROP CAPACITANCE 0.1 +PROP TOLERANCE 5.0 +.END_ELECTRICAL + +# Component #3/5 +.ELECTRICAL +conn_din24 connector THOU 435.0 +0 -1400.0 -500.0 0.0 +0 300.0 -500.0 0.0 +0 300.0 150.0 0.0 +0 -1400.0 150.0 0.0 +0 -1400.0 -500.0 0.0 +.END_ELECTRICAL + + +# Component #4/5 +.ELECTRICAL +dip_14w pn-hs346-dip THOU 200.0 +0 350.0 50.0 0.0 +0 -50.0 50.0 0.0 +0 -50.0 -650.0 0.0 +0 350.0 -650.0 0.0 +0 350.0 50.0 0.0 +.END_ELECTRICAL + + +# Component #5/5 +.ELECTRICAL +plcc_20 pn-pal16l8-plcc THOU 14.0 +0 -200.0 240.0 0.0 +0 -240.0 200.0 0.0 +0 -240.0 -240.0 0.0 +0 240.0 -240.0 0.0 +0 240.0 240.0 0.0 +0 -200.0 240.0 0.0 +.END_ELECTRICAL diff --git a/utils/idftools/idf_examples/test_idf2.emn b/utils/idftools/idf_examples/test_idf2.emn new file mode 100644 index 0000000000..b317a007fe --- /dev/null +++ b/utils/idftools/idf_examples/test_idf2.emn @@ -0,0 +1,71 @@ +.HEADER +BOARD_FILE 3.0 "Created by KiCad (2014-01-25 BZR 4633)-product" 2014/02/01.15:09:15 1 +"test_idf2.kicad_pcb" MM +.END_HEADER + +.BOARD_OUTLINE ECAD +1.60000 +0 -86.00000 42.00000 0 +0 -86.00000 -42.00000 0 +0 86.00000 -42.00000 0 +0 86.00000 42.00000 0 +0 -86.00000 42.00000 0 +.END_BOARD_OUTLINE + +.DRILLED_HOLES +0.800 -74.00000 16.00000 PTH BOARD PIN ECAD +0.800 -74.00000 -28.00000 PTH BOARD PIN ECAD +0.850 -55.75000 16.00000 PTH BOARD PIN ECAD +0.850 -52.25000 16.00000 PTH BOARD PIN ECAD +0.850 -35.75000 16.00000 PTH BOARD PIN ECAD +0.850 -32.25000 16.00000 PTH BOARD PIN ECAD +1.575 -57.17500 -28.00000 PTH BOARD PIN ECAD +1.575 -50.82500 -28.00000 PTH BOARD PIN ECAD +1.575 -37.17500 -28.00000 PTH BOARD PIN ECAD +1.575 -30.82500 -28.00000 PTH BOARD PIN ECAD +0.800 -14.00000 16.00000 PTH BOARD PIN ECAD +0.800 -14.00000 -28.00000 PTH BOARD PIN ECAD +0.800 6.00000 16.00000 PTH BOARD PIN ECAD +0.800 6.00000 -28.00000 PTH BOARD PIN ECAD +0.800 26.00000 16.00000 PTH BOARD PIN ECAD +0.800 26.00000 -28.00000 PTH BOARD PIN ECAD +0.800 46.00000 16.00000 PTH BOARD PIN ECAD +0.800 46.00000 -28.00000 PTH BOARD PIN ECAD +0.800 66.00000 16.00000 PTH BOARD PIN ECAD +0.800 66.00000 -28.00000 PTH BOARD PIN ECAD +.END_DRILLED_HOLES + +.PLACEMENT +"CYLV_MM" "D5.000_H8.000_Z3.000" "NOREFDES_0" +-74.000000 16.000000 0.000000 0.000 TOP ECAD +"CYLV_IN" "D0.250_H0.250_Z0.127" "NOREFDES_1" +-74.000000 -28.000000 0.000000 0.000 TOP ECAD +"CYLV_MM_L" "D5.000_H8.000_Z3.000_WD0.800_P3.500" "NOREFDES_2" +-54.000000 16.000000 0.000000 0.000 TOP ECAD +"CYLV_MM_R" "D5.000_H8.000_Z3.000_WD0.800_P3.500" "NOREFDES_3" +-34.000000 16.000000 0.000000 0.000 TOP ECAD +"CYLV_IN_L" "D0.250_H0.250_Z0.127_WD0.062_P0.250" "NOREFDES_4" +-54.000000 -28.000000 0.000000 0.000 TOP ECAD +"CYLV_IN_R" "D0.250_H0.250_Z0.127_WD0.062_P0.250" "NOREFDES_5" +-34.000000 -28.000000 0.000000 0.000 TOP ECAD +"CYLH_MM_AXI" "D2.500_H4.000_Z0.500_WD0.600_P8.000" "NOREFDES_6" +-14.000000 16.000000 0.000000 0.000 TOP ECAD +"CYLH_IN_AXI" "D0.098_H0.157_Z0.020_WD0.024_P0.315" "NOREFDES_7" +-14.000000 -28.000000 0.000000 0.000 TOP ECAD +"CYLH_MM_RAD" "D5.000_H6.000_Z0.200_WD0.600_P2.500_L3.000" "NOREFDES_8" +6.000000 16.000000 0.000000 0.000 TOP ECAD +"CYLH_IN_RAD" "D0.197_H0.236_Z0.008_WD0.024_P0.098_L0.118" "NOREFDES_9" +6.000000 -28.000000 0.000000 0.000 TOP ECAD +"RECTMM" "W10.000_L10.000_H6.000_C0.000" "NOREFDES_10" +26.000000 16.000000 0.000000 0.000 TOP ECAD +"RECTIN" "W393_L393_H236_C0" "NOREFDES_11" +26.000000 -28.000000 0.000000 0.000 TOP ECAD +"RECTMM" "W10.000_L10.000_H2.000_C0.500" "NOREFDES_12" +46.000000 16.000000 0.000000 0.000 TOP ECAD +"RECTIN" "W393_L393_H78_C19" "NOREFDES_13" +46.000000 -28.000000 0.000000 0.000 TOP ECAD +"RECTLMM" "W10.000_L10.000_H12.000_D0.800_P6.000" "NOREFDES_14" +66.000000 16.000000 0.000000 0.000 TOP ECAD +"RECTLIN" "W393_L393_H472_D31_P236" "NOREFDES_15" +66.000000 -28.000000 0.000000 0.000 TOP ECAD +.END_PLACEMENT diff --git a/utils/idftools/idf_examples/test_idf2.emp b/utils/idftools/idf_examples/test_idf2.emp new file mode 100644 index 0000000000..9777025663 --- /dev/null +++ b/utils/idftools/idf_examples/test_idf2.emp @@ -0,0 +1,290 @@ +.HEADER +LIBRARY_FILE 3.0 "Created by KiCad (2014-01-25 BZR 4633)-product" 2014/02/01.15:09:15 1 +.END_HEADER + +# cylindrical outline, vertical, no pins +# file: "cylvmm_0_D5_L8_Z3.idf" +# dia: 5.000 mm +# length: 8.000 mm +# extra height: 3.000 mm +.ELECTRICAL +"CYLV_MM" "D5.000_H8.000_Z3.000" MM 11.000 +0 0 0 0 +0 5.000 0 360 +.END_ELECTRICAL + +# cylindrical outline, vertical, no pins +# file: "cylvin_0_D0.25_L0.25_Z0.127.idf" +# dia: 250 THOU +# length: 250 THOU +# extra height: 127 THOU +.ELECTRICAL +"CYLV_IN" "D0.250_H0.250_Z0.127" THOU 377 +0 0 0 0 +0 250 0 360 +.END_ELECTRICAL + +# cylindrical outline, vertical, 1 pin on left +# file: "cylvmm_1L_D5_L8_Z3_WD0.8_P3.5.idf" +# dia: 5.000 mm +# length: 8.000 mm +# extra height: 3.000 mm +# wire dia: 0.800 mm +# pitch: 3.500 mm +.ELECTRICAL +"CYLV_MM_L" "D5.000_H8.000_Z3.000_WD0.800_P3.500" MM 11.000 +1 -0.718 0.400 0 +1 -0.718 -0.400 -341.586 +1 -1.750 -0.400 0 +1 -1.750 0.400 -180 +1 -0.718 0.400 0 +.END_ELECTRICAL + +# cylindrical outline, vertical, 1 pin on right +# file: "cylvmm_1R_D5_L8_Z3_WD0.8_P3.5.idf" +# dia: 5.000 mm +# length: 8.000 mm +# extra height: 3.000 mm +# wire dia: 0.800 mm +# pitch: 3.500 mm +.ELECTRICAL +"CYLV_MM_R" "D5.000_H8.000_Z3.000_WD0.800_P3.500" MM 11.000 +0 0.718 0.400 0 +0 0.718 -0.400 341.586 +0 1.750 -0.400 0 +0 1.750 0.400 180 +0 0.718 0.400 0 +.END_ELECTRICAL + +# cylindrical outline, vertical, 1 pin on left +# file: "cylvin_1L_D0.25_L0.25_Z0.127_WD0.062_P0.25.idf" +# dia: 250 THOU +# length: 250 THOU +# extra height: 127 THOU +# wire dia: 62 THOU +# pitch: 250 THOU +.ELECTRICAL +"CYLV_IN_L" "D0.250_H0.250_Z0.127_WD0.062_P0.250" THOU 377 +1 3 31 0 +1 3 -31 -331.282 +1 -125 -31 0 +1 -125 31 -180 +1 3 31 0 +.END_ELECTRICAL + +# cylindrical outline, vertical, 1 pin on right +# file: "cylvin_1R_D0.25_L0.25_Z0.127_WD0.062_P0.25.idf" +# dia: 250 THOU +# length: 250 THOU +# extra height: 127 THOU +# wire dia: 62 THOU +# pitch: 250 THOU +.ELECTRICAL +"CYLV_IN_R" "D0.250_H0.250_Z0.127_WD0.062_P0.250" THOU 377 +0 -3 31 0 +0 -3 -31 331.282 +0 125 -31 0 +0 125 31 180 +0 -3 31 0 +.END_ELECTRICAL + +# cylindrical outline, horiz., axial pins +# file: "resistor.idf" +# dia: 2.500 mm +# length: 4.000 mm +# extra height: 0.500 mm +# wire dia: 0.600 mm +# pitch: 8.000 mm +.ELECTRICAL +"CYLH_MM_AXI" "D2.500_H4.000_Z0.500_WD0.600_P8.000" MM 3.000 +0 -2.000 1.250 0 +0 -2.000 0.300 0 +0 -4.000 0.300 0 +0 -4.000 -0.300 180 +0 -2.000 -0.300 0 +0 -2.000 -1.250 0 +0 2.000 -1.250 0 +0 2.000 -0.300 0 +0 4.000 -0.300 0 +0 4.000 0.300 180 +0 2.000 0.300 0 +0 2.000 1.250 0 +0 -2.000 1.250 0 +.END_ELECTRICAL + +# cylindrical outline, horiz., axial pins +# file: "resistor_in.idf" +# dia: 98 THOU +# length: 157 THOU +# extra height: 20 THOU +# wire dia: 24 THOU +# pitch: 315 THOU +.ELECTRICAL +"CYLH_IN_AXI" "D0.098_H0.157_Z0.020_WD0.024_P0.315" THOU 118 +0 -78 49 0 +0 -78 12 0 +0 -157 12 0 +0 -157 -12 180 +0 -78 -12 0 +0 -78 -49 0 +0 78 -49 0 +0 78 -12 0 +0 157 -12 0 +0 157 12 180 +0 78 12 0 +0 78 49 0 +0 -78 49 0 +.END_ELECTRICAL + +# cylindrical outline, horiz., radial pins +# file: "capacitor.idf" +# dia: 5.000 mm +# length: 6.000 mm +# extra height: 0.200 mm +# wire dia: 0.600 mm +# pitch: 2.500 mm +# lead: 3.000 mm +.ELECTRICAL +"CYLH_MM_RAD" "D5.000_H6.000_Z0.200_WD0.600_P2.500_L3.000" MM 5.200 +0 -2.500 9.000 0 +0 -2.500 3.000 0 +0 -1.550 3.000 0 +0 -1.550 0 0 +0 -0.950 0 180 +0 -0.950 3.000 0 +0 0.950 3.000 0 +0 0.950 0 0 +0 1.550 0 180 +0 1.550 3.000 0 +0 2.500 3.000 0 +0 2.500 9.000 0 +0 -2.500 9.000 0 +.END_ELECTRICAL + +# cylindrical outline, horiz., radial pins +# file: "capacitor_in.idf" +# dia: 197 THOU +# length: 236 THOU +# extra height: 8 THOU +# wire dia: 24 THOU +# pitch: 98 THOU +# lead: 118 THOU +.ELECTRICAL +"CYLH_IN_RAD" "D0.197_H0.236_Z0.008_WD0.024_P0.098_L0.118" THOU 205 +0 -98 354 0 +0 -98 118 0 +0 -61 118 0 +0 -61 0 0 +0 -37 0 180 +0 -37 118 0 +0 37 118 0 +0 37 0 0 +0 61 0 180 +0 61 118 0 +0 98 118 0 +0 98 354 0 +0 -98 354 0 +.END_ELECTRICAL + +# rectangular outline +# file: "rectMM_10x10x6_C0.idf" +# width: 10.000 mm +# length: 10.000 mm +# height: 6.000 mm +# chamfer: 0.000 mm +.ELECTRICAL +"RECTMM" "W10.000_L10.000_H6.000_C0.000" MM 6.000 +0 5.000 5.000 0 +0 -5.000 5.000 0 +0 -5.000 -5.000 0 +0 5.000 -5.000 0 +0 5.000 5.000 0 +.END_ELECTRICAL + +# rectangular outline +# file: "rectIN_10x10x6mm_C0mm.idf" +# width: 393 THOU +# length: 393 THOU +# height: 236 THOU +# chamfer: 0 THOU +.ELECTRICAL +"RECTIN" "W393_L393_H236_C0" THOU 236 +0 196 196 0 +0 -196 196 0 +0 -196 -196 0 +0 196 -196 0 +0 196 196 0 +.END_ELECTRICAL + +# rectangular outline +# file: "rectMM_10x10x2_C0.5.idf" +# width: 10.000 mm +# length: 10.000 mm +# height: 2.000 mm +# chamfer: 0.500 mm +.ELECTRICAL +"RECTMM" "W10.000_L10.000_H2.000_C0.500" MM 2.000 +0 5.000 5.000 0 +0 -4.500 5.000 0 +0 -5.000 4.500 0 +0 -5.000 -5.000 0 +0 5.000 -5.000 0 +0 5.000 5.000 0 +.END_ELECTRICAL + +# rectangular outline +# file: "rectIN_10x10x2mm_C0.5mm.idf" +# width: 393 THOU +# length: 393 THOU +# height: 78 THOU +# chamfer: 19 THOU +.ELECTRICAL +"RECTIN" "W393_L393_H78_C19" THOU 78 +0 196 196 0 +0 -176 196 0 +0 -196 176 0 +0 -196 -196 0 +0 196 -196 0 +0 196 196 0 +.END_ELECTRICAL + +# rectangular outline, leaded +# file: "rectLMM_10x10x12_D0.8_P6.0.idf" +# width: 10.000 mm +# length: 10.000 mm +# height: 12.000 mm +# wire dia: 0.800 mm +# pitch: 6.000 mm +.ELECTRICAL +"RECTLMM" "W10.000_L10.000_H12.000_D0.800_P6.000" MM 12.000 +0 3.000 0.400 0 +0 2.000 0.400 0 +0 2.000 5.000 0 +0 -8.000 5.000 0 +0 -8.000 -5.000 0 +0 2.000 -5.000 0 +0 2.000 -0.400 0 +0 3.000 -0.400 0 +0 3.000 0.400 180 +.END_ELECTRICAL + +# rectangular outline, leaded +# file: "rectLIN_10x10x12mm_D0.8mm_P6.0mm.idf" +# width: 393 THOU +# length: 393 THOU +# height: 472 THOU +# wire dia: 31 THOU +# pitch: 236 THOU +.ELECTRICAL +"RECTLIN" "W393_L393_H472_D31_P236" THOU 472 +0 118 15 0 +0 78 15 0 +0 78 196 0 +0 -315 196 0 +0 -315 -196 0 +0 78 -196 0 +0 78 -15 0 +0 118 -15 0 +0 118 15 180 +.END_ELECTRICAL + diff --git a/utils/idftools/idf_helpers.cpp b/utils/idftools/idf_helpers.cpp new file mode 100644 index 0000000000..a1c7141acb --- /dev/null +++ b/utils/idftools/idf_helpers.cpp @@ -0,0 +1,295 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 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 + */ + +#include +#include +#include + +#include +#include + +using namespace std; +using namespace IDF3; + +// fetch a line from the given input file and trim the ends +bool IDF3::FetchIDFLine( std::ifstream& aModel, std::string& aLine, bool& isComment, std::streampos& aFilePos ) +{ + aLine = ""; + aFilePos = aModel.tellg(); + + if( aFilePos == -1 ) + return false; + + std::getline( aModel, aLine ); + + isComment = false; + + // A comment begins with a '#' and must be the first character on the line + if( aLine[0] == '#' ) + { + // opening '#' is stripped + isComment = true; + aLine.erase( aLine.begin() ); + } + + // strip leading and trailing spaces + while( !aLine.empty() && isspace( *aLine.begin() ) ) + aLine.erase( aLine.begin() ); + + while( !aLine.empty() && isspace( *aLine.rbegin() ) ) + aLine.erase( --aLine.end() ); + + // a comment line may be empty to improve human readability + if( aLine.empty() && !isComment ) + return false; + + return true; +} + + +// extract an IDF string and move the index to point to the character after the substring +bool IDF3::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 ) + { + ERROR_IDF << "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; +} + + +// perform a comparison between a fixed token string and an input string. +// the token is assumed to be an upper case IDF token and the input string +// is data from an IDF file. Since IDF tokens are case-insensitive, we cannot +// assume anything about the case of the input string. +bool IDF3::CompareToken( const char* aTokenString, const std::string& aInputString ) +{ + std::string::size_type i, j; + std::string bigToken = aInputString; + j = aInputString.length(); + + for( i = 0; i < j; ++i ) + bigToken[i] = std::toupper( bigToken[i] ); + + if( !bigToken.compare( aTokenString ) ) + return true; + + return false; +} + + +// parse a string for an IDF3::KEY_OWNER +bool IDF3::ParseOwner( const std::string& aToken, IDF3::KEY_OWNER& aOwner ) +{ + if( CompareToken( "UNOWNED", aToken ) ) + { + aOwner = UNOWNED; + return true; + } + else if( CompareToken( "ECAD", aToken ) ) + { + aOwner = ECAD; + return true; + } + else if( CompareToken( "MCAD", aToken ) ) + { + aOwner = MCAD; + return true; + } + + ERROR_IDF << "unrecognized IDF OWNER: '" << aToken << "'\n"; + + return false; +} + + +bool IDF3::ParseIDFLayer( const std::string& aToken, IDF3::IDF_LAYER& aLayer ) +{ + if( CompareToken( "TOP", aToken ) ) + { + aLayer = LYR_TOP; + return true; + } + else if( CompareToken( "BOTTOM", aToken ) ) + { + aLayer = LYR_BOTTOM; + return true; + } + else if( CompareToken( "BOTH", aToken ) ) + { + aLayer = LYR_BOTH; + return true; + } + else if( CompareToken( "INNER", aToken ) ) + { + aLayer = LYR_INNER; + return true; + } + else if( CompareToken( "ALL", aToken ) ) + { + aLayer = LYR_ALL; + return true; + } + + ERROR_IDF << "unrecognized IDF LAYER: '" << aToken << "'\n"; + + aLayer = LYR_INVALID; + return false; +} + + +bool IDF3::WriteLayersText( std::ofstream& aBoardFile, IDF3::IDF_LAYER aLayer ) +{ + switch( aLayer ) + { + case LYR_TOP: + aBoardFile << "TOP"; + break; + + case LYR_BOTTOM: + aBoardFile << "BOTTOM"; + break; + + case LYR_BOTH: + aBoardFile << "BOTH"; + break; + + case LYR_INNER: + aBoardFile << "INNER"; + break; + + case LYR_ALL: + aBoardFile << "ALL"; + break; + + default: + ERROR_IDF << "Invalid IDF layer" << aLayer << "\n"; + return false; + break; + } + + return !aBoardFile.fail(); +} + + +std::string IDF3::GetPlacementString( IDF3::IDF_PLACEMENT aPlacement ) +{ + switch( aPlacement ) + { + case PS_UNPLACED: + return "UNPLACED"; + + case PS_PLACED: + return "PLACED"; + + case PS_MCAD: + return "MCAD"; + + case PS_ECAD: + return "ECAD"; + + default: + break; + } + + std::ostringstream ostr; + ostr << "[INVALID PLACEMENT VALUE]:" << aPlacement; + + return ostr.str(); +} + + +std::string IDF3::GetLayerString( IDF3::IDF_LAYER aLayer ) +{ + switch( aLayer ) + { + case LYR_TOP: + return "TOP"; + + case LYR_BOTTOM: + return "BOTTOM"; + + case LYR_BOTH: + return "BOTH"; + + case LYR_INNER: + return "INNER"; + + case LYR_ALL: + return "ALL"; + + default: + break; + } + + std::ostringstream ostr; + ostr << "[INVALID LAYER VALUE]:" << aLayer; + + return ostr.str(); +} diff --git a/utils/idftools/idf_helpers.h b/utils/idftools/idf_helpers.h new file mode 100644 index 0000000000..da392f0774 --- /dev/null +++ b/utils/idftools/idf_helpers.h @@ -0,0 +1,171 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 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 + */ + +#ifndef IDF_HELPERS_H +#define IDF_HELPERS_H + +#include +#include +#include +#include + +/** + * Macro TO_UTF8 + * converts a wxString to a UTF8 encoded C string for all wxWidgets build modes. + * wxstring is a wxString, not a wxT() or _(). The scope of the return value + * is very limited and volatile, but can be used with printf() style functions well. + * NOTE: Taken from KiCad include/macros.h + */ +#define TO_UTF8( wxstring ) ( (const char*) (wxstring).utf8_str() ) + +/** + * function FROM_UTF8 + * converts a UTF8 encoded C string to a wxString for all wxWidgets build modes. + * NOTE: Taken from KiCad include/macros.h + */ +static inline wxString FROM_UTF8( const char* cstring ) +{ + wxString line = wxString::FromUTF8( cstring ); + + if( line.IsEmpty() ) // happens when cstring is not a valid UTF8 sequence + line = wxConvCurrent->cMB2WC( cstring ); // try to use locale conversion + + return line; +} + + +#define ERROR_IDF std::cerr << "* " << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): " + +// minimum drill diameters / slot widths to be represented in the IDF output +#define IDF_MIN_DIA_MM ( 0.001 ) +#define IDF_MIN_DIA_THOU ( 0.00039 ) + +// conversion between mm and thou +#define IDF_MM_TO_THOU 0.0254 + +namespace IDF3 +{ + +/** + * Function FetchIDFLine + * retrieves a single line from an IDF file and performs minimal processing. If a comment symbol + * is encountered then it is removed and a single leading space is removed if present; all trailing + * spaces are removed. If the line is not a comment then all leading and trailing spaces are stripped. + * + * @param aModel is an open IDFv3 file + * @param aLine (output) is the line retrieved from the file + * @param isComment (output) is set to true if the line is a comment + * @param aFilePos (output) is set to the beginning of the line in case the file needs to be rewound + * + * @return bool: true if a line was read and was not empty; otherwise false + */ +bool FetchIDFLine( std::ifstream& aModel, std::string& aLine, bool& isComment, std::streampos& aFilePos ); + + +/** + * Function GetIDFString + * parses a line retrieved via FetchIDFLine() and returns the first IDF string found from the starting + * point aIndex + * + * @param aLine is the line to parse + * @param aIDFString (output) is the IDF string retrieved + * @param hasQuotes (output) is true if the string was in quotation marks + * @param aIndex (input/output) is the index into the input line + * + * @return bool: true if a string was retrieved, otherwise false + */ +bool GetIDFString( const std::string& aLine, std::string& aIDFString, + bool& hasQuotes, int& aIndex ); + +/** + * Function CompareToken + * performs a case-insensitive comparison of a token string and an input string + * + * @param aToken is an IDF token such as ".HEADER" + * @param aInputString is a string typically retrieved via GetIDFString + * + * @return bool: true if the token and input string match + */ +bool CompareToken( const char* aTokenString, const std::string& aInputString ); + + +/** + * Function ParseOwner + * parses the input string for a valid IDF Owner type + * + * @param aToken is the string to be parsed + * @param aOwner (output) is the IDF Owner class + * + * @return bool: true if a valid OWNER was found, otherwise false + */ +bool ParseOwner( const std::string& aToken, IDF3::KEY_OWNER& aOwner ); + + +/** + * Function ParseIDFLayer + * parses an input string for a valid IDF layer specification + * + * @param aToken is the string to be parsed + * @param aLayer (output) is the IDF Layer type or group + * + * @return bool: true if a valid IDF Layer type was found, otherwise false + */ +bool ParseIDFLayer( const std::string& aToken, IDF3::IDF_LAYER& aLayer ); + + +/** + * Function WriteLayersText + * writes out text corresponding to the given IDF Layer type + * + * @param aBoardFile is an IDFv3 file open for output + * @param aLayer is the IDF Layer type + * + * @return bool: true if the data was successfully written, otherwise false + */ +bool WriteLayersText( std::ofstream& aBoardFile, IDF3::IDF_LAYER aLayer ); + + +/** + * Function GetPlacementString + * returns a string representing the given IDF Placement type + * + * @param aPlacement is the IDF placement type to encode as a string + * + * @return string: the string representation of aPlacement + */ +std::string GetPlacementString( IDF3::IDF_PLACEMENT aPlacement ); + + +/** + * Function GetLayerString + * returns a string representing the given IDF Layer type + * + * @param aLayer is the IDF layer type to encode as a string + * + * @return string: the string representation of aLayer + */ +std::string GetLayerString( IDF3::IDF_LAYER aLayer ); + +} + +#endif // IDF_HELPERS_H diff --git a/utils/idftools/idf_outlines.cpp b/utils/idftools/idf_outlines.cpp new file mode 100644 index 0000000000..67779a407b --- /dev/null +++ b/utils/idftools/idf_outlines.cpp @@ -0,0 +1,2614 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 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 + */ + +#include +#include +#include +#include + +#include +#include +#include + +using namespace IDF3; +using namespace std; + + +/* + * CLASS: BOARD OUTLINE + */ +BOARD_OUTLINE::BOARD_OUTLINE() +{ + outlineType = OTLN_BOARD; + single = false; + owner = UNOWNED; + parent = NULL; + thickness = 0.0; + unit = UNIT_MM; + return; +} + +BOARD_OUTLINE::~BOARD_OUTLINE() +{ + Clear(); + return; +} + +IDF3::OUTLINE_TYPE BOARD_OUTLINE::GetOutlineType( void ) +{ + return outlineType; +} + +bool BOARD_OUTLINE::readOutlines( std::ifstream& aBoardFile ) +{ + // reads the outline data from a file + double x, y, ang; + double dLoc = 1e-5; // distances are equal when closer than 0.1 micron + bool comment = false; + bool quoted = false; + bool closed = false; + int idx = 0; + int loopidx = -1; + int tmp = 0; + int npts = 0; + std::string iline; + std::string entry; + std::stringstream tstr; + IDF_OUTLINE* op = NULL; + IDF_SEGMENT* sp = NULL; + IDF_POINT prePt; + IDF_POINT curPt; + std::streampos pos; + + // destroy any existing outline data + ClearOutlines(); + + while( aBoardFile.good() ) + { + if( !FetchIDFLine( aBoardFile, iline, comment, pos ) ) + continue; + + idx = 0; + GetIDFString( iline, entry, quoted, idx ); + + if( quoted ) + { + ERROR_IDF << "invalid outline; FIELD 1 is quoted\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + // check for the end of the section + if( entry.size() >= 5 && CompareToken( ".END_", entry.substr( 0, 5 ) ) ) + { + // rewind to the start of the last line; the routine invoking + // this is responsible for checking that the current '.END_ ...' + // matches the section header. + aBoardFile.seekg( pos ); + + if( outlines.size() > 0 ) + { + if( npts > 0 && !closed ) + { + ERROR_IDF << "invalid outline (not closed)\n"; + return false; + } + + // verify winding + if( !single ) + { + if( !outlines.front()->IsCCW() ) + { + ERROR_IDF << "invalid IDF3 file (BOARD_OUTLINE)\n"; + cerr << "* first outline is not in CCW order\n"; +//#warning TO BE IMPLEMENTED + // outlines.front()->EnsureWinding( false ); + return true; + } + + if( outlines.size() > 1 && outlines.back()->IsCCW() && !outlines.back()->IsCircle() ) + { + ERROR_IDF << "invalid IDF3 file (BOARD_OUTLINE)\n"; + cerr << "* cutout points are not in CW order\n"; +//#warning TO BE IMPLEMENTED + // outlines.front()->EnsureWinding( true ); + return true; + } + } + } + + return true; + } + + tstr.clear(); + tstr << entry; + + tstr >> tmp; + if( tstr.fail() ) + { + if( outlineType == OTLN_COMPONENT && CompareToken( "PROP", entry ) ) + { + aBoardFile.seekg( pos ); + return true; + } + + ERROR_IDF << "invalid outline; FIELD 1 is not numeric\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + if( tmp != loopidx ) + { + // index change + + if( tmp < 0 ) + { + ERROR_IDF << "invalid outline; FIELD 1 is invalid\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + if( loopidx == -1 ) + { + // first outline + if( single ) + { + // outline may have a Loop Index of 0 or 1 + if( tmp == 0 || tmp == 1 ) + { + op = new IDF_OUTLINE; + if( op == NULL ) + { + ERROR_IDF << "memory allocation failed\n"; + return false; + } + outlines.push_back( op ); + loopidx = tmp; + } + else + { + ERROR_IDF << "invalid outline; FIELD 1 is invalid (must be 0 or 1)\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + } + else + { + // outline *MUST* have a Loop Index of 0 + if( tmp != 0 ) + { + ERROR_IDF << "invalid outline; first outline of a BOARD or PANEL must have a Loop Index of 0\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + op = new IDF_OUTLINE; + + if( op == NULL ) + { + ERROR_IDF << "memory allocation failed\n"; + return false; + } + + outlines.push_back( op ); + loopidx = tmp; + } + // end of block for first outline + } + else + { + // outline for cutout + if( single ) + { + ERROR_IDF << "invalid outline; a simple outline type may only have one outline\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + if( tmp - loopidx != 1 ) + { + ERROR_IDF << "invalid outline; cutouts must be numbered in order from 1 onwards\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + // verify winding of previous outline + if( ( loopidx = 0 && !op->IsCCW() ) + || ( loopidx > 0 && op->IsCCW() ) ) + { + ERROR_IDF << "invalid outline (violation of loop point order rules by Loop Index " + << loopidx << ")\n"; + return false; + } + + op = new IDF_OUTLINE; + + if( op == NULL ) + { + ERROR_IDF << "memory allocation failed\n"; + return false; + } + + outlines.push_back( op ); + loopidx = tmp; + } + // end of index change code + npts = 0; + closed = false; + } + + if( op == NULL ) + { + ERROR_IDF << "invalid outline; FIELD 1 is invalid\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + if( !GetIDFString( iline, entry, quoted, idx ) ) + { + ERROR_IDF << "invalid RECORD 3, FIELD 2 does not exist\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + if( quoted ) + { + ERROR_IDF << "invalid RECORD 3, FIELD 2 is quoted\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + tstr.clear(); + tstr << entry; + + tstr >> x; + if( tstr.fail() ) + { + ERROR_IDF << "invalid RECORD 3, invalid X value in FIELD 2\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + if( !GetIDFString( iline, entry, quoted, idx ) ) + { + ERROR_IDF << "invalid RECORD 3, FIELD 3 does not exist\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + if( quoted ) + { + ERROR_IDF << "invalid RECORD 3, FIELD 3 is quoted\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + tstr.clear(); + tstr << entry; + + tstr >> y; + if( tstr.fail() ) + { + ERROR_IDF << "invalid RECORD 3, invalid Y value in FIELD 3\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + if( !GetIDFString( iline, entry, quoted, idx ) ) + { + ERROR_IDF << "invalid RECORD 3, FIELD 4 does not exist\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + if( quoted ) + { + ERROR_IDF << "invalid RECORD 3, FIELD 4 is quoted\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + tstr.clear(); + tstr << entry; + + tstr >> ang; + if( tstr.fail() ) + { + ERROR_IDF << "invalid ANGLE value in FIELD 3\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + // the line was successfully read; convert to mm if necessary + if( unit == UNIT_THOU ) + { + x *= IDF_MM_TO_THOU; + y *= IDF_MM_TO_THOU; + } + + if( npts++ == 0 ) + { + // first point + prePt.x = x; + prePt.y = y; + + // ensure that the first point is not an arc specification + if( ang < -MIN_ANG || ang > MIN_ANG ) + { + ERROR_IDF << "invalid RECORD 3, first point has non-zero angle\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + } + else + { + // Nth point + if( closed ) + { + ERROR_IDF << "invalid RECORD 3; adding a segment to a closed outline\n"; + std::cerr << " LINE: " << iline << "\n"; + return false; + } + + curPt.x = x; + curPt.y = y; + + if( ang > -MIN_ANG && ang < MIN_ANG ) + { + sp = new IDF_SEGMENT( prePt, curPt ); + } + else + { + sp = new IDF_SEGMENT( prePt, curPt, ang, false ); + } + + if( sp == NULL ) + { + ERROR_IDF << "memory allocation failure\n"; + return false; + } + + if( sp->IsCircle() ) + { + // this is a circle; the loop is closed + if( op->size() != 0 ) + { + ERROR_IDF << "invalid RECORD 3; adding a circle to a non-empty outline\n"; + std::cerr << " LINE: " << iline << "\n"; + delete sp; + return false; + } + + closed = true; + } + else if( op->size() != 0 ) + { + if( curPt.Matches( op->front()->startPoint, dLoc ) ) + closed = true; + } + + op->push( sp ); + prePt.x = x; + prePt.y = y; + } + } // while( aBoardFile.good() ) + + // 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 + + return false; +} + +bool BOARD_OUTLINE::writeComments( std::ofstream& aBoardFile ) +{ + if( comments.empty() ) + return true; + + list< string >::const_iterator itS = comments.begin(); + list< string >::const_iterator itE = comments.end(); + + while( itS != itE ) + { + aBoardFile << "# " << *itS << "\n"; + ++itS; + } + + return !aBoardFile.fail(); +} + +bool BOARD_OUTLINE::writeOwner( std::ofstream& aBoardFile ) +{ + switch( owner ) + { + case ECAD: + aBoardFile << "ECAD\n"; + break; + + case MCAD: + aBoardFile << "MCAD\n"; + break; + + default: + aBoardFile << "UNOWNED\n"; + break; + } + + return !aBoardFile.fail(); +} + +bool BOARD_OUTLINE::writeOutline( std::ofstream& aBoardFile, IDF_OUTLINE* aOutline, size_t aIndex ) +{ + // TODO: check the stream integrity + + std::list::iterator bo; + std::list::iterator eo; + + if( aOutline->size() == 1 ) + { + if( !aOutline->front()->IsCircle() ) + { + // this is a bad outline + ERROR_IDF << "bad outline (single segment item, not circle)\n"; + return false; + } + + if( single ) + aIndex = 0; + + // NOTE: a circle always has an angle of 360, never -360, + // otherwise SolidWorks chokes on the file. + if( unit == UNIT_MM ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << aOutline->front()->startPoint.x << " " + << aOutline->front()->startPoint.y << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << aOutline->front()->endPoint.x << " " + << aOutline->front()->endPoint.y << " 360\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << (aOutline->front()->startPoint.x / IDF_MM_TO_THOU) << " " + << (aOutline->front()->startPoint.y / IDF_MM_TO_THOU) << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << (aOutline->front()->endPoint.x / IDF_MM_TO_THOU) << " " + << (aOutline->front()->endPoint.y / IDF_MM_TO_THOU) << " 360\n"; + } + + return !aBoardFile.fail(); + } + + // ensure that the very last point is the same as the very first point + aOutline->back()-> endPoint = aOutline->front()->startPoint; + + if( single ) + { + // only indices 0 (CCW) and 1 (CW) are valid; set the index according to + // the outline's winding + if( aOutline->IsCCW() ) + aIndex = 0; + else + aIndex = 1; + } + + // check if we must reverse things + if( ( aOutline->IsCCW() && ( aIndex > 0 ) ) + || ( ( !aOutline->IsCCW() ) && ( aIndex == 0 ) ) ) + { + eo = aOutline->begin(); + bo = aOutline->end(); + --bo; + + // for the first item we write out both points + if( unit == UNIT_MM ) + { + if( aOutline->front()->angle < MIN_ANG && aOutline->front()->angle > -MIN_ANG ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << aOutline->front()->endPoint.x << " " + << aOutline->front()->endPoint.y << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << aOutline->front()->startPoint.x << " " + << aOutline->front()->startPoint.y << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << aOutline->front()->endPoint.x << " " + << aOutline->front()->endPoint.y << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << aOutline->front()->startPoint.x << " " + << aOutline->front()->startPoint.y << " " + << setprecision(5) << -aOutline->front()->angle << "\n"; + } + } + else + { + if( aOutline->front()->angle < MIN_ANG && aOutline->front()->angle > -MIN_ANG ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << (aOutline->front()->endPoint.x / IDF_MM_TO_THOU) << " " + << (aOutline->front()->endPoint.y / IDF_MM_TO_THOU) << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << (aOutline->front()->startPoint.x / IDF_MM_TO_THOU) << " " + << (aOutline->front()->startPoint.y / IDF_MM_TO_THOU) << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << (aOutline->front()->endPoint.x / IDF_MM_TO_THOU) << " " + << (aOutline->front()->endPoint.y / IDF_MM_TO_THOU) << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << (aOutline->front()->startPoint.x / IDF_MM_TO_THOU) << " " + << (aOutline->front()->startPoint.y / IDF_MM_TO_THOU) << " " + << setprecision(5) << -aOutline->front()->angle << "\n"; + } + } + + // for all other segments we only write out the start point + while( bo != eo ) + { + if( unit == UNIT_MM ) + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << (*bo)->startPoint.x << " " + << (*bo)->startPoint.y << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << (*bo)->startPoint.x << " " + << (*bo)->startPoint.y << " " + << setprecision(5) << -(*bo)->angle << "\n"; + } + } + else + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->startPoint.x / IDF_MM_TO_THOU) << " " + << ((*bo)->startPoint.y / IDF_MM_TO_THOU) << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->startPoint.x / IDF_MM_TO_THOU) << " " + << ((*bo)->startPoint.y / IDF_MM_TO_THOU) << " " + << setprecision(5) << -(*bo)->angle << "\n"; + } + } + + --bo; + } + } + else + { + bo = aOutline->begin(); + eo = aOutline->end(); + + // for the first item we write out both points + if( unit == UNIT_MM ) + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << (*bo)->startPoint.x << " " + << (*bo)->startPoint.y << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << (*bo)->endPoint.x << " " + << (*bo)->endPoint.y << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << (*bo)->startPoint.x << " " + << (*bo)->startPoint.y << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << (*bo)->endPoint.x << " " + << (*bo)->endPoint.y << " " + << setprecision(5) << (*bo)->angle << "\n"; + } + } + else + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->startPoint.x / IDF_MM_TO_THOU) << " " + << ((*bo)->startPoint.y / IDF_MM_TO_THOU) << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->endPoint.x / IDF_MM_TO_THOU) << " " + << ((*bo)->endPoint.y / IDF_MM_TO_THOU) << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->startPoint.x / IDF_MM_TO_THOU) << " " + << ((*bo)->startPoint.y / IDF_MM_TO_THOU) << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->endPoint.x / IDF_MM_TO_THOU) << " " + << ((*bo)->endPoint.y / IDF_MM_TO_THOU) << " " + << setprecision(5) << (*bo)->angle << "\n"; + } + } + + ++bo; + + // for all other segments we only write out the last point + while( bo != eo ) + { + if( unit == UNIT_MM ) + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << (*bo)->endPoint.x << " " + << (*bo)->endPoint.y << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(5) + << (*bo)->endPoint.x << " " + << (*bo)->endPoint.y << " " + << setprecision(5) << (*bo)->angle << "\n"; + } + } + else + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->endPoint.x / IDF_MM_TO_THOU) << " " + << ((*bo)->endPoint.y / IDF_MM_TO_THOU) << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->endPoint.x / IDF_MM_TO_THOU) << " " + << ((*bo)->endPoint.y / IDF_MM_TO_THOU) << " " + << setprecision(5) << (*bo)->angle << "\n"; + } + } + + ++bo; + } + } + + return !aBoardFile.fail(); +} + +bool BOARD_OUTLINE::writeOutlines( std::ofstream& aBoardFile ) +{ + if( outlines.empty() ) + return true; + + int idx = 0; + std::list< IDF_OUTLINE* >::iterator itS = outlines.begin(); + std::list< IDF_OUTLINE* >::iterator itE = outlines.end(); + + while( itS != itE ) + { + if( !writeOutline( aBoardFile, *itS, idx++ ) ) + return false; + + ++itS; + } + + return true; +} + +void BOARD_OUTLINE::SetUnit( IDF3::IDF_UNIT aUnit ) +{ + unit = aUnit; + return; +} + +IDF3::IDF_UNIT BOARD_OUTLINE::GetUnit( void ) +{ + return unit; +} + +bool BOARD_OUTLINE::SetThickness( double aThickness ) +{ + if( aThickness < 0.0 ) + return false; + + thickness = aThickness; + return true; +} + +double BOARD_OUTLINE::GetThickness( void ) +{ + return thickness; +} + +bool BOARD_OUTLINE::ReadData( std::ifstream& aBoardFile, const std::string& aHeader ) +{ + // BOARD_OUTLINE (PANEL_OUTLINE) + // .BOARD_OUTLINE [OWNER] + // [thickness] + // [outlines] + + // check RECORD 1 + std::string token; + bool quoted = false; + int idx = 0; + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ERROR_IDF << "invalid invocation; blank header line\n"; + return false; + } + + if( quoted ) + { + ERROR_IDF << "section names may not be quoted:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + + if( !CompareToken( ".BOARD_OUTLINE", token ) ) + { + ERROR_IDF << "not a board outline:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ERROR_IDF << "no OWNER; setting to UNOWNED\n"; + owner = UNOWNED; + } + else + { + if( !ParseOwner( token, owner ) ) + { + ERROR_IDF << "invalid OWNER (reverting to UNOWNED): " << token << "\n"; + owner = UNOWNED; + } + } + + // check RECORD 2 + std::string iline; + bool comment = false; + std::streampos pos; + + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && !aBoardFile.eof() ) || iline.empty() ) + { + ERROR_IDF << "bad .BOARD_OUTLINE section (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within .BOARD_OUTLINE section\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad .BOARD_OUTLINE section (no thickness)\n"; + return false; + } + + std::stringstream teststr; + teststr << token; + + teststr >> thickness; + if( teststr.fail() ) + { + ERROR_IDF << "bad .BOARD_OUTLINE section (invalid RECORD 2)\n"; + std::cerr << "\tLINE: " << iline << "\n"; + return false; + } + + if( unit == UNIT_THOU ) + thickness *= IDF_MM_TO_THOU; + + // read RECORD 3 values + // XXX - check the return value - we may have empty lines and what-not + readOutlines( aBoardFile ); + + // check RECORD 4 + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && aBoardFile.eof() ) || iline.empty() ) + { + ERROR_IDF << "bad .BOARD_OUTLINE section (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within .BOARD_OUTLINE section\n"; + return false; + } + + if( !CompareToken( ".END_BOARD_OUTLINE", iline ) ) + { + ERROR_IDF << "bad .BOARD_OUTLINE section (no .END_BOARD_OUTLINE)\n"; + return false; + } + + return true; +} + +bool BOARD_OUTLINE::WriteData( std::ofstream& aBoardFile ) +{ + writeComments( aBoardFile ); + + // note: a BOARD_OUTLINE section is required, even if it is empty + aBoardFile << ".BOARD_OUTLINE "; + + writeOwner( aBoardFile ); + + if( unit == UNIT_MM ) + aBoardFile << setiosflags(ios::fixed) << setprecision(5) << thickness << "\n"; + else + aBoardFile << setiosflags(ios::fixed) << setprecision(1) << (thickness / IDF_MM_TO_THOU) << "\n"; + + if( !writeOutlines( aBoardFile ) ) + return false; + + aBoardFile << ".END_BOARD_OUTLINE\n\n"; + + return !aBoardFile.fail(); +} + +void BOARD_OUTLINE::Clear( void ) +{ + comments.clear(); + ClearOutlines(); + + owner = UNOWNED; + return; +} + +void BOARD_OUTLINE::SetParent( IDF3_BOARD* aParent ) +{ + parent = aParent; +} + +IDF3_BOARD* BOARD_OUTLINE::GetParent( void ) +{ + return parent; +} + +bool BOARD_OUTLINE::AddOutline( IDF_OUTLINE* aOutline ) +{ + std::list< IDF_OUTLINE* >::iterator itS = outlines.begin(); + std::list< IDF_OUTLINE* >::iterator itE = outlines.end(); + + while( itS != itE ) + { + if( *itS == aOutline ) + return false; + + ++itS; + } + + outlines.push_back( aOutline ); + return true; +} + +bool BOARD_OUTLINE::DelOutline( IDF_OUTLINE* aOutline ) +{ + std::list< IDF_OUTLINE* >::iterator itS = outlines.begin(); + std::list< IDF_OUTLINE* >::iterator itE = outlines.end(); + + if( outlines.empty() ) + return false; + + // if there are more than 1 outlines it makes no sense to delete + // the first outline (board outline) since that would have the + // undesirable effect of substituting a cutout outline as the board outline + if( aOutline == outlines.front() ) + { + if( outlines.size() > 1 ) + return false; + + outlines.clear(); + return true; + } + + while( itS != itE ) + { + if( *itS == aOutline ) + { + outlines.erase( itS ); + return true; + } + + ++itS; + } + + return false; +} + +bool BOARD_OUTLINE::DelOutline( size_t aIndex ) +{ + std::list< IDF_OUTLINE* >::iterator itS = outlines.begin(); + std::list< IDF_OUTLINE* >::iterator itE = outlines.end(); + + if( outlines.empty() ) + return false; + + if( aIndex >= outlines.size() ) + return false; + + if( aIndex == 0 ) + { + // if there are more than 1 outlines it makes no sense to delete + // the first outline (board outline) since that would have the + // undesirable effect of substituting a cutout outline as the board outline + if( outlines.size() > 1 ) + return false; + + delete *itS; + outlines.clear(); + + return true; + } + + for( ; aIndex > 0; --aIndex ) + ++itS; + + delete *itS; + outlines.erase( itS ); + + return true; +} + +const std::list< IDF_OUTLINE* >*const BOARD_OUTLINE::GetOutlines( void ) +{ + return &outlines; +} + +size_t BOARD_OUTLINE::OutlinesSize( void ) +{ + return outlines.size(); +} + +IDF_OUTLINE* BOARD_OUTLINE::GetOutline( size_t aIndex ) +{ + if( aIndex >= outlines.size() ) + return NULL; + + std::list< IDF_OUTLINE* >::iterator itS = outlines.begin(); + + for( ; aIndex > 0; --aIndex ) + ++itS; + + return *itS; +} + +IDF3::KEY_OWNER BOARD_OUTLINE::GetOwner( void ) +{ + return owner; +} + +bool BOARD_OUTLINE::SetOwner( IDF3::KEY_OWNER aOwner ) +{ + // if this is a COMPONENT OUTLINE there can be no owner + if( outlineType == IDF3::OTLN_COMPONENT ) + return true; + + // if no one owns the outline, any system may + // set the owner + if( owner == UNOWNED ) + { + owner = aOwner; + return true; + } + + // if the outline is owned, only the owning + // CAD system can make alterations + if( parent == NULL ) + return false; + + if( owner == MCAD && parent->GetCadType() == CAD_MECH ) + { + owner = aOwner; + return true; + } + + if( owner == ECAD && parent->GetCadType() == CAD_ELEC ) + { + owner = aOwner; + return true; + } + + return false; +} + +bool BOARD_OUTLINE::IsSingle( void ) +{ + return single; +} + +void BOARD_OUTLINE::ClearOutlines( void ) +{ + std::list< IDF_OUTLINE* >::iterator itS = outlines.begin(); + std::list< IDF_OUTLINE* >::iterator itE = outlines.end(); + + while( itS != itE ) + { + delete *itS; + ++itS; + } + + outlines.clear(); + return; +} + +void BOARD_OUTLINE::AddComment( const std::string& aComment ) +{ + if( aComment.empty() ) + return; + + comments.push_back( aComment ); + return; +} + +size_t BOARD_OUTLINE::CommentsSize( void ) +{ + return comments.size(); +} + +std::list< std::string >* BOARD_OUTLINE::GetComments( void ) +{ + return &comments; +} + +const std::string* BOARD_OUTLINE::GetComment( size_t aIndex ) +{ + if( aIndex >= comments.size() ) + return NULL; + + std::list< std::string >::iterator itS = comments.begin(); + + for( ; aIndex > 0; --aIndex ) + ++itS; + + return &(*itS); +} + +bool BOARD_OUTLINE::DeleteComment( size_t aIndex ) +{ + if( aIndex >= comments.size() ) + return false; + + std::list< std::string >::iterator itS = comments.begin(); + + for( ; aIndex > 0; --aIndex ) + ++itS; + + comments.erase( itS ); + return true; +} + +void BOARD_OUTLINE::ClearComments( void ) +{ + comments.clear(); + return; +} + + +/* + * CLASS: OTHER_OUTLINE + */ +OTHER_OUTLINE::OTHER_OUTLINE() +{ + outlineType = OTLN_OTHER; + side = LYR_INVALID; + single = true; + + return; +} + +void OTHER_OUTLINE::SetOutlineIdentifier( const std::string aUniqueID ) +{ + uniqueID = aUniqueID; + return; +} + +const std::string& OTHER_OUTLINE::GetOutlineIdentifier( void ) +{ + return uniqueID; +} + +bool OTHER_OUTLINE::SetSide( IDF3::IDF_LAYER aSide ) +{ + switch( aSide ) + { + case LYR_TOP: + case LYR_BOTTOM: + side = aSide; + break; + + default: + ERROR_IDF << "invalid side (" << aSide << "); must be one of TOP/BOTTOM\n"; + side = LYR_INVALID; + return false; + break; + } + + return true; +} + +IDF3::IDF_LAYER OTHER_OUTLINE::GetSide( void ) +{ + return side; +} + +bool OTHER_OUTLINE::ReadData( std::ifstream& aBoardFile, const std::string& aHeader ) +{ + // OTHER_OUTLINE/VIA_KEEPOUT + // .OTHER_OUTLINE [OWNER] + // [outline identifier] [thickness] [board side: Top/Bot] {not present in VA\IA KEEPOUT} + // [outline] + + // check RECORD 1 + std::string token; + bool quoted = false; + int idx = 0; + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ERROR_IDF << "invalid invocation; blank header line\n"; + return false; + } + + if( quoted ) + { + ERROR_IDF << "section names may not be quoted:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + + if( outlineType == OTLN_OTHER ) + { + if( !CompareToken( ".OTHER_OUTLINE", token ) ) + { + ERROR_IDF << "not an OTHER outline:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + } + else + { + if( !CompareToken( ".VIA_KEEPOUT", token ) ) + { + ERROR_IDF << "not a VIA_KEEPOUT outline:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + } + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ERROR_IDF << "no OWNER; setting to UNOWNED\n"; + owner = UNOWNED; + } + else + { + if( !ParseOwner( token, owner ) ) + { + ERROR_IDF << "invalid OWNER (reverting to UNOWNED): " << token << "\n"; + owner = UNOWNED; + } + } + + std::string iline; + bool comment = false; + std::streampos pos; + + if( outlineType == OTLN_OTHER ) + { + // check RECORD 2 + // [outline identifier] [thickness] [board side: Top/Bot] + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && aBoardFile.eof() ) || iline.empty() ) + { + ERROR_IDF << "bad .OTHER_OUTLINE section (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within .OTHER_OUTLINE section\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad .OTHER_OUTLINE section (no outline identifier)\n"; + return false; + } + + uniqueID = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad .OTHER_OUTLINE section (no thickness)\n"; + return false; + } + + std::stringstream teststr; + teststr << token; + + teststr >> thickness; + if( teststr.fail() ) + { + ERROR_IDF << "bad .OTHER_OUTLINE section (invalid RECORD 2 reading thickness)\n"; + std::cerr << "\tLINE: " << iline << "\n"; + return false; + } + + if( unit == UNIT_THOU ) + thickness *= IDF_MM_TO_THOU; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad .OTHER_OUTLINE section (no board side)\n"; + return false; + } + + if( !ParseIDFLayer( token, side ) || ( side != LYR_TOP && side != LYR_BOTTOM ) ) + { + ERROR_IDF << "bad .OTHER_OUTLINE section (invalid side, must be TOP/BOTTOM only)\n"; + std::cerr << "\tLINE: " << iline << "\n"; + return false; + } + } + + // read RECORD 3 values + readOutlines( aBoardFile ); + + // check RECORD 4 + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && aBoardFile.eof() ) || iline.empty() ) + { + ERROR_IDF << "bad .OTHER_OUTLINE/.VIA_KEEPOUT section (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within .OTHER_OUTLINE/.VIA_KEEPOUT section\n"; + return false; + } + + if( outlineType == OTLN_OTHER ) + { + if( !CompareToken( ".END_OTHER_OUTLINE", iline ) ) + { + ERROR_IDF << "bad .OTHER_OUTLINE section (no .END_OTHER_OUTLINE)\n"; + return false; + } + } + else + { + if( !CompareToken( ".END_VIA_KEEPOUT", iline ) ) + { + ERROR_IDF << "bad .VIA_KEEPOUT section (no .END_VIA_KEEPOUT)\n"; + return false; + } + } + + return true; +} + +bool OTHER_OUTLINE::WriteData( std::ofstream& aBoardFile ) +{ + // this section is optional; do not write if not required + if( outlines.empty() ) + return true; + + writeComments( aBoardFile ); + + // write RECORD 1 + if( outlineType == OTLN_OTHER ) + aBoardFile << ".OTHER_OUTLINE "; + else + aBoardFile << ".VIA_KEEPOUT "; + + writeOwner( aBoardFile ); + + // write RECORD 2 + if( outlineType == OTLN_OTHER ) + { + aBoardFile << "\"" << uniqueID << "\" "; + + if( unit == UNIT_MM ) + aBoardFile << setiosflags(ios::fixed) << setprecision(5) << thickness << " "; + else + aBoardFile << setiosflags(ios::fixed) << setprecision(1) << (thickness / IDF_MM_TO_THOU) << " "; + + switch( side ) + { + case LYR_TOP: + case LYR_BOTTOM: + WriteLayersText( aBoardFile, side ); + break; + + default: + ERROR_IDF << "Invalid OTHER_OUTLINE side (neither top nor bottom): " << side << "\n"; + return false; + break; + } + } + + // write RECORD 3 + if( !writeOutlines( aBoardFile ) ) + return false; + + // write RECORD 4 + if( outlineType == OTLN_OTHER ) + aBoardFile << ".END_OTHER_OUTLINE\n\n"; + else + aBoardFile << ".END_VIA_KEEPOUT\n\n"; + + return !aBoardFile.fail(); +} + +void OTHER_OUTLINE::Clear( void ) +{ + side = LYR_INVALID; + uniqueID.clear(); + + BOARD_OUTLINE::Clear(); + + return; +} + + +/* + * CLASS: ROUTE_OUTLINE + */ +ROUTE_OUTLINE::ROUTE_OUTLINE() +{ + outlineType = OTLN_ROUTE; + single = true; + layers = LYR_INVALID; +} + +void ROUTE_OUTLINE::SetLayers( IDF3::IDF_LAYER aLayer ) +{ + layers = aLayer; +} + +IDF3::IDF_LAYER ROUTE_OUTLINE::GetLayers( void ) +{ + return layers; +} + +bool ROUTE_OUTLINE::ReadData( std::ifstream& aBoardFile, const std::string& aHeader ) +{ + // ROUTE_OUTLINE (or ROUTE_KEEPOUT) + // .ROUTE_OUTLINE [OWNER] + // [layers] + // [outline] + + // check RECORD 1 + std::string token; + bool quoted = false; + int idx = 0; + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ERROR_IDF << "invalid invocation; blank header line\n"; + return false; + } + + if( quoted ) + { + ERROR_IDF << "section names may not be quoted:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + + if( outlineType == OTLN_ROUTE ) + { + if( !CompareToken( ".ROUTE_OUTLINE", token ) ) + { + ERROR_IDF << "not a ROUTE outline:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + } + else + { + if( !CompareToken( ".ROUTE_KEEPOUT", token ) ) + { + ERROR_IDF << "not a ROUTE KEEPOUT outline:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + } + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ERROR_IDF << "no OWNER; setting to UNOWNED\n"; + owner = UNOWNED; + } + else + { + if( !ParseOwner( token, owner ) ) + { + ERROR_IDF << "invalid OWNER (reverting to UNOWNED): " << token << "\n"; + owner = UNOWNED; + } + } + + // check RECORD 2 + // [layers: TOP, BOTTOM, BOTH, INNER, ALL] + std::string iline; + bool comment = false; + std::streampos pos; + + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( !aBoardFile.good() ) + { + ERROR_IDF << "bad .ROUTE_OUTLINE/KEEPOUT section (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within .ROUTE_OUTLINE/KEEPOUT section\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad .ROUTE_OUTLINE/KEEPOUT section (no layers specification)\n"; + return false; + } + + if( quoted ) + { + ERROR_IDF << "bad .ROUTE_OUTLINE/KEEPOUT section (layers may not be quoted)\n"; + std::cerr << "\tLINE: " << iline << "\n"; + return false; + } + + if( !ParseIDFLayer( token, layers ) ) + { + ERROR_IDF << "bad .ROUTE_OUTLINE/KEEPOUT section (invalid layer)\n"; + std::cerr << "\tLINE: " << iline << "\n"; + return false; + } + + // read RECORD 3 values + readOutlines( aBoardFile ); + + // check RECORD 4 + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && aBoardFile.eof() ) || iline.empty() ) + { + ERROR_IDF << "bad .ROUTE_OUTLINE/KEEPOUT section (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within .ROUTE_OUTLINE/KEEPOUT section\n"; + return false; + } + + if( outlineType == OTLN_ROUTE ) + { + if( !CompareToken( ".END_ROUTE_OUTLINE", iline ) ) + { + ERROR_IDF << "bad .ROUTE_OUTLINE section (no .END_ROUTE_OUTLINE)\n"; + return false; + } + } + else + { + if( !CompareToken( ".END_ROUTE_KEEPOUT", iline ) ) + { + ERROR_IDF << "bad .ROUTE_KEEPOUT section (no .END_ROUTE_KEEPOUT)\n"; + return false; + } + } + + return true; +} + + +bool ROUTE_OUTLINE::WriteData( std::ofstream& aBoardFile ) +{ + // this section is optional; do not write if not required + if( outlines.empty() ) + return true; + + if( layers == LYR_INVALID ) + { + ERROR_IDF << "layer not specified\n"; + return false; + } + + writeComments( aBoardFile ); + + // write RECORD 1 + if( outlineType == OTLN_ROUTE ) + aBoardFile << ".ROUTE_OUTLINE "; + else + aBoardFile << ".ROUTE_KEEPOUT "; + + writeOwner( aBoardFile ); + + // write RECORD 2 + WriteLayersText( aBoardFile, layers ); + aBoardFile << "\n"; + + // write RECORD 3 + if( !writeOutlines( aBoardFile ) ) + return false; + + // write RECORD 4 + if( outlineType == OTLN_ROUTE ) + aBoardFile << ".END_ROUTE_OUTLINE\n\n"; + else + aBoardFile << ".END_ROUTE_KEEPOUT\n\n"; + + return !aBoardFile.fail(); +} + + +void ROUTE_OUTLINE::Clear( void ) +{ + BOARD_OUTLINE::Clear(); + layers = LYR_INVALID; + return; +} + + +/* + * CLASS: PLACE_OUTLINE + */ +PLACE_OUTLINE::PLACE_OUTLINE() +{ + outlineType = OTLN_PLACE; + single = true; + thickness = 0.0; + side = LYR_INVALID; +} + +void PLACE_OUTLINE::SetSide( IDF3::IDF_LAYER aSide ) +{ + switch( aSide ) + { + case LYR_TOP: + case LYR_BOTTOM: + case LYR_BOTH: + side = aSide; + break; + + default: + // XXX - throw + ERROR_IDF << "invalid layer (" << aSide << "): must be one of TOP/BOTTOM/BOTH\n"; + side = LYR_INVALID; + return; + break; + } + + return; +} + +IDF3::IDF_LAYER PLACE_OUTLINE::GetSide( void ) +{ + return side; +} + +void PLACE_OUTLINE::SetMaxHeight( double aHeight ) +{ + if( aHeight < 0.0 ) + { + ERROR_IDF << "invalid height (must be >= 0.0); default to 0\n"; + thickness = 0.0; + return; + } + + thickness = aHeight; + return; +} + +double PLACE_OUTLINE::GetMaxHeight( void ) +{ + return thickness; +} + +bool PLACE_OUTLINE::ReadData( std::ifstream& aBoardFile, const std::string& aHeader ) +{ + // PLACE_OUTLINE/KEEPOUT + // .PLACE_OUTLINE [OWNER] + // [board side: Top/Bot/Both] [height] + // [outline] + + // check RECORD 1 + std::string token; + bool quoted = false; + int idx = 0; + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ERROR_IDF << "invalid invocation; blank header line\n"; + return false; + } + + if( quoted ) + { + ERROR_IDF << "section names may not be quoted:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + + if( outlineType == OTLN_PLACE ) + { + if( !CompareToken( ".PLACE_OUTLINE", token ) ) + { + ERROR_IDF << "not a PLACE outline:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + } + else + { + if( !CompareToken( ".PLACE_KEEPOUT", token ) ) + { + ERROR_IDF << "not a PLACE_KEEPOUT outline:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + } + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ERROR_IDF << "no OWNER; setting to UNOWNED\n"; + owner = UNOWNED; + } + else + { + if( !ParseOwner( token, owner ) ) + { + ERROR_IDF << "invalid OWNER (reverting to UNOWNED): " << token << "\n"; + owner = UNOWNED; + } + } + + // check RECORD 2 + // [board side: Top/Bot/Both] [height] + std::string iline; + bool comment = false; + std::streampos pos; + + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( !aBoardFile.good() ) + { + ERROR_IDF << "bad .PLACE_OUTLINE/KEEPOUT section (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within .PLACE_OUTLINE/KEEPOUT section\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad .PLACE_OUTLINE/KEEPOUT section (no board side information)\n"; + return false; + } + + if( !ParseIDFLayer( token, side ) || + ( side != LYR_TOP && side != LYR_BOTTOM && side != LYR_BOTH ) ) + { + ERROR_IDF << "bad .PLACE_OUTLINE/KEEPOUT section (invalid side, must be one of TOP/BOTTOM/BOTH)\n"; + std::cerr << "\tLINE: " << iline << "\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad .PLACE_OUTLINE/KEEPOUT section (no height)\n"; + return false; + } + + std::stringstream teststr; + teststr << token; + + teststr >> thickness; + if( teststr.fail() ) + { + ERROR_IDF << "bad .PLACE_OUTLINE/KEEPOUT section (invalid RECORD 2 reading height)\n"; + std::cerr << "\tLINE: " << iline << "\n"; + return false; + } + + if( unit == UNIT_THOU ) + thickness *= IDF_MM_TO_THOU; + + // read RECORD 3 values + readOutlines( aBoardFile ); + + // check RECORD 4 + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && aBoardFile.eof() ) || iline.empty() ) + { + ERROR_IDF << "bad .PLACE_OUTLINE/KEEPOUT section (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within .PLACE_OUTLINE/KEEPOUT section\n"; + return false; + } + + if( outlineType == OTLN_PLACE ) + { + if( !GetIDFString( iline, token, quoted, idx ) + || !CompareToken( ".END_PLACE_OUTLINE", token ) ) + { + ERROR_IDF << "bad .PLACE_OUTLINE section (no .END_PLACE_OUTLINE)\n"; + return false; + } + } + else + { + if( !GetIDFString( iline, token, quoted, idx ) + || !CompareToken( ".END_PLACE_KEEPOUT", token ) ) + { + ERROR_IDF << "bad .PLACE_KEEPOUT section (no .END_PLACE_KEEPOUT)\n"; + return false; + } + } + + return true; +} + +bool PLACE_OUTLINE::WriteData( std::ofstream& aBoardFile ) +{ + // this section is optional; do not write if not required + if( outlines.empty() ) + return true; + + writeComments( aBoardFile ); + + // write RECORD 1 + if( outlineType == OTLN_PLACE ) + aBoardFile << ".PLACE_OUTLINE "; + else + aBoardFile << ".PLACE_KEEPOUT "; + + writeOwner( aBoardFile ); + + // write RECORD 2 + switch( side ) + { + case LYR_TOP: + case LYR_BOTTOM: + case LYR_BOTH: + WriteLayersText( aBoardFile, side ); + break; + + default: + ERROR_IDF << "Invalid PLACE_OUTLINE/KEEPOUT side (" << side << "); must be one of TOP/BOTTOM/BOTH\n"; + return false; + break; + } + + aBoardFile << " "; + + if( unit == UNIT_MM ) + aBoardFile << setiosflags(ios::fixed) << setprecision(5) << thickness << "\n"; + else + aBoardFile << setiosflags(ios::fixed) << setprecision(1) << (thickness / IDF_MM_TO_THOU) << "\n"; + + // write RECORD 3 + if( !writeOutlines( aBoardFile ) ) + return false; + + // write RECORD 4 + if( outlineType == OTLN_PLACE ) + aBoardFile << ".END_PLACE_OUTLINE\n\n"; + else + aBoardFile << ".END_PLACE_KEEPOUT\n\n"; + + return !aBoardFile.fail(); +} + +void PLACE_OUTLINE::Clear( void ) +{ + BOARD_OUTLINE::Clear(); + thickness = 0.0; + side = LYR_INVALID; + return; +} + + +/* + * CLASS: ROUTE_KEEPOUT + */ +ROUTE_KO_OUTLINE::ROUTE_KO_OUTLINE() +{ + outlineType = OTLN_ROUTE_KEEPOUT; + return; +} + + +/* + * CLASS: PLACE_KEEPOUT + */ +PLACE_KO_OUTLINE::PLACE_KO_OUTLINE() +{ + outlineType = OTLN_PLACE_KEEPOUT; + return; +} + + +/* + * CLASS: VIA_KEEPOUT + */ +VIA_KO_OUTLINE::VIA_KO_OUTLINE() +{ + outlineType = OTLN_VIA_KEEPOUT; +} + + +/* + * CLASS: PLACEMENT GROUP (PLACE_REGION) + */ +GROUP_OUTLINE::GROUP_OUTLINE() +{ + outlineType = OTLN_GROUP_PLACE; + thickness = 0.0; + side = LYR_INVALID; + single = true; + return; +} + +void GROUP_OUTLINE::SetSide( IDF3::IDF_LAYER aSide ) +{ + switch( aSide ) + { + case LYR_TOP: + case LYR_BOTTOM: + case LYR_BOTH: + side = aSide; + break; + + default: + // XXX throw + ERROR_IDF << "invalid side (" << aSide << "); must be one of TOP/BOTTOM/BOTH\n"; + return; + break; + } + + return; +} + +IDF3::IDF_LAYER GROUP_OUTLINE::GetSide( void ) +{ + return side; +} + +void GROUP_OUTLINE::SetGroupName( std::string aGroupName ) +{ + groupName = aGroupName; + return; +} + +const std::string& GROUP_OUTLINE::GetGroupName( void ) +{ + return groupName; +} + +bool GROUP_OUTLINE::ReadData( std::ifstream& aBoardFile, const std::string& aHeader ) +{ + // Placement Group + // .PLACE_REGION [OWNER] + // [side: Top/Bot/Both ] [component group name] + // [outline] + + // check RECORD 1 + std::string token; + bool quoted = false; + int idx = 0; + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ERROR_IDF << "invalid invocation; blank header line\n"; + return false; + } + + if( quoted ) + { + ERROR_IDF << "section names may not be quoted:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + + if( !CompareToken( ".PLACE_REGION", token ) ) + { + ERROR_IDF << "not a PLACE_REGION outline:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ERROR_IDF << "no OWNER; setting to UNOWNED\n"; + owner = UNOWNED; + } + else + { + if( !ParseOwner( token, owner ) ) + { + ERROR_IDF << "invalid OWNER (reverting to UNOWNED): " << token << "\n"; + owner = UNOWNED; + } + } + + std::string iline; + bool comment = false; + std::streampos pos; + + // check RECORD 2 + // [side: Top/Bot/Both ] [component group name] + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( !aBoardFile.good() ) + { + ERROR_IDF << "bad .PLACE_REGION section (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within .PLACE_REGION section\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad .PLACE_REGION section (no board side)\n"; + return false; + } + + if( !ParseIDFLayer( token, side ) || + ( side != LYR_TOP && side != LYR_BOTTOM && side != LYR_BOTH ) ) + { + ERROR_IDF << "bad .PLACE_REGION section (invalid side, must be TOP/BOTTOM/BOTH)\n"; + std::cerr << "\tLINE: " << iline << "\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad .PLACE_REGION section (no outline identifier)\n"; + return false; + } + + groupName = token; + + // read RECORD 3 values + readOutlines( aBoardFile ); + + // check RECORD 4 + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && aBoardFile.eof() ) || iline.empty() ) + { + ERROR_IDF << "bad .PLACE_REGION section (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within .PLACE_REGION section\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) + || !CompareToken( ".END_PLACE_REGION", token ) ) + { + ERROR_IDF << "bad .PLACE_REGION section (no .END_PLACE_REGION)\n"; + return false; + } + + return true; +} + +bool GROUP_OUTLINE::WriteData( std::ofstream& aBoardFile ) +{ + // this section is optional; do not write if not required + if( outlines.empty() ) + return true; + + writeComments( aBoardFile ); + + // write RECORD 1 + aBoardFile << ".PLACE_REGION "; + + writeOwner( aBoardFile ); + + // write RECORD 2 + switch( side ) + { + case LYR_TOP: + case LYR_BOTTOM: + case LYR_BOTH: + WriteLayersText( aBoardFile, side ); + break; + + default: + ERROR_IDF << "Invalid PLACE_REGION side (must be TOP/BOTTOM/BOTH): " << side << "\n"; + return false; + break; + } + + aBoardFile << " \"" << groupName << "\"\n"; + + // write RECORD 3 + if( !writeOutlines( aBoardFile ) ) + return false; + + // write RECORD 4 + aBoardFile << ".END_PLACE_REGION\n\n"; + + return !aBoardFile.fail(); +} + +void GROUP_OUTLINE::Clear( void ) +{ + BOARD_OUTLINE::Clear(); + thickness = 0.0; + side = LYR_INVALID; + groupName.clear(); + return; +} + +/* + * CLASS: COMPONENT OUTLINE + */ +IDF3_COMP_OUTLINE::IDF3_COMP_OUTLINE() +{ + single = true; + outlineType = OTLN_COMPONENT; + compType = COMP_INVALID; + refNum = 0; + return; +} + +bool IDF3_COMP_OUTLINE::readProperties( std::ifstream& aLibFile ) +{ + bool quoted = false; + bool comment = false; + std::string iline; + std::string token; + std::streampos pos; + std::string pname; // property name + std::string pval; // property value + int idx = 0; + + while( aLibFile.good() ) + { + if( !FetchIDFLine( aLibFile, iline, comment, pos ) ) + continue; + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within component outline section\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad component outline section (no PROP)\n"; + return false; + } + + if( quoted ) + { + ERROR_IDF << "bad component outline section (PROP or .END may not be quoted)\n"; + return false; + } + + if( token.size() >= 5 && CompareToken( ".END_", token.substr( 0, 5 ) ) ) + { + aLibFile.seekg( pos ); + return true; + } + + if( !CompareToken( "PROP", token ) ) + { + ERROR_IDF << "invalid electrical outline; expecting PROP or .END_ELECTRICAL\n"; + std::cerr << "\tLINE: " << iline << "\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad component outline section (no prop name)\n"; + return false; + } + + pname = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad component outline section (no prop value)\n"; + return false; + } + + pval = token; + + if( props.insert( pair< string, string >(pname, pval) ).second == false ) + { + ERROR_IDF << "bad component outline: duplicate property name '" << pname << "'\n"; + return false; + } + } + + return !aLibFile.fail(); +} + +bool IDF3_COMP_OUTLINE::writeProperties( std::ofstream& aLibFile ) +{ + if( props.empty() ) + return true; + std::map< std::string, std::string >::const_iterator itS = props.begin(); + std::map< std::string, std::string >::const_iterator itE = props.end(); + + while( itS != itE ) + { + aLibFile << "PROP " << "\"" << itS->first << "\" \"" + << itS->second << "\"\n"; + ++itS; + } + + return !aLibFile.fail(); +} + +bool IDF3_COMP_OUTLINE::ReadData( std::ifstream& aLibFile, const std::string& aHeader ) +{ + // .ELECTRICAL/.MECHANICAL + // [GEOM] [PART] [UNIT] [HEIGHT] + // [outline] + // [PROP] [prop name] [prop value] + // check RECORD 1 + std::string token; + bool quoted = false; + int idx = 0; + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ERROR_IDF << "invalid invocation; blank header line\n"; + return false; + } + + if( quoted ) + { + ERROR_IDF << "section names may not be quoted:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + + if( CompareToken( ".ELECTRICAL", token ) ) + { + compType = COMP_ELEC; + } + else if( CompareToken( ".MECHANICAL", token ) ) + { + compType = COMP_MECH; + } + else + { + ERROR_IDF << "not a component outline:\n"; + std::cerr << "\tLINE: " << aHeader << "\n"; + return false; + } + + // check RECORD 2 + // [GEOM] [PART] [UNIT] [HEIGHT] + std::string iline; + bool comment = false; + std::streampos pos; + + while( aLibFile.good() && !FetchIDFLine( aLibFile, iline, comment, pos ) ); + + if( !aLibFile.good() ) + { + ERROR_IDF << "bad component outline data (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within a component outline section\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad component outline (no GEOMETRY NAME)\n"; + return false; + } + + geometry = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad component outline (no PART NAME)\n"; + return false; + } + + part = token; + + if( part.empty() && geometry.empty() ) + { + ERROR_IDF << "bad component outline (both GEOMETRY and PART names are empty)\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad component outline (no unit type)\n"; + return false; + } + + if( CompareToken( "MM", token ) ) + { + unit = UNIT_MM; + } + else if( CompareToken( "THOU", token ) ) + { + unit = UNIT_THOU; + } + else + { + ERROR_IDF << "bad component outline (invalid unit type)\n"; + std::cerr << "\tLINE: " << iline << "\n"; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF << "bad component outline (no height)\n"; + return false; + } + + std::istringstream teststr; + teststr.str( token ); + + teststr >> thickness; + if( teststr.fail() ) + { + ERROR_IDF << "bad component outline (invalid height)\n"; + std::cerr << "\tLINE: " << iline << "\n"; + return false; + } + + if( unit == UNIT_THOU ) + thickness *= IDF_MM_TO_THOU; + + // read RECORD 3 values + readOutlines( aLibFile ); + + if( compType == COMP_ELEC ) + { + if( !readProperties( aLibFile ) ) + return false; + } + + // check RECORD 4 + while( aLibFile.good() && !FetchIDFLine( aLibFile, iline, comment, pos ) ); + + if( ( !aLibFile.good() && aLibFile.eof() ) || iline.empty() ) + { + ERROR_IDF << "bad component outline data (premature end)\n"; + return false; + } + + idx = 0; + if( comment ) + { + ERROR_IDF << "comment within component outline section\n"; + return false; + } + + if( compType == COMP_ELEC ) + { + if( !CompareToken( ".END_ELECTRICAL", iline ) ) + { + ERROR_IDF << "bad component outline (no .END_ELECTRICAL)\n"; + return false; + } + } + else + { + if( !CompareToken( ".END_MECHANICAL", iline ) ) + { + ERROR_IDF << "corrupt .MECHANICAL outline\n"; + return false; + } + } + + return true; +} + +bool IDF3_COMP_OUTLINE::WriteData( std::ofstream& aLibFile ) +{ + if( compType != COMP_ELEC && compType != COMP_MECH ) + { + ERROR_IDF << "component type not set or invalid\n"; + return false; + } + + if( refNum == 0 ) + return true; // nothing to do + + writeComments( aLibFile ); + + // note: the outline section is required, even if it is empty + if( compType == COMP_ELEC ) + aLibFile << ".ELECTRICAL\n"; + else + aLibFile << ".MECHANICAL\n"; + + // RECORD 2 + // [GEOM] [PART] [UNIT] [HEIGHT] + aLibFile << "\"" << geometry << "\" \"" << part << "\" "; + + if( unit == UNIT_MM ) + aLibFile << "MM " << setiosflags(ios::fixed) << setprecision(5) << thickness << "\n"; + else + aLibFile << "THOU " << setiosflags(ios::fixed) << setprecision(1) << (thickness / IDF_MM_TO_THOU) << "\n"; + + if( !writeOutlines( aLibFile ) ) + return false; + + if( compType == COMP_ELEC ) + { + writeProperties( aLibFile ); + aLibFile << ".END_ELECTRICAL\n\n"; + } + else + { + aLibFile << ".END_MECHANICAL\n\n"; + } + + return !aLibFile.fail(); +} + +void IDF3_COMP_OUTLINE::Clear( void ) +{ + BOARD_OUTLINE::Clear(); + uid.clear(); + geometry.clear(); + part.clear(); + compType = COMP_INVALID; + refNum = 0; + props.clear(); + return; +} + +void IDF3_COMP_OUTLINE::SetComponentClass( IDF3::COMP_TYPE aCompClass ) +{ + switch( aCompClass ) + { + case COMP_ELEC: + case COMP_MECH: + compType = aCompClass; + break; + + default: + // XXX - throw + ERROR_IDF << "invalid component class (must be ELECTRICAL or MECHANICAL)\n"; + return; + break; + } + + return; +} + +IDF3::COMP_TYPE IDF3_COMP_OUTLINE::GetComponentClass( void ) +{ + return compType; +} + + +void IDF3_COMP_OUTLINE::SetGeomName( const std::string& aGeomName ) +{ + geometry = aGeomName; + uid.clear(); + return; +} + +const std::string& IDF3_COMP_OUTLINE::GetGeomName( void ) +{ + return geometry; +} + +void IDF3_COMP_OUTLINE::SetPartName( const std::string& aPartName ) +{ + part = aPartName; + uid.clear(); + return; +} + +const std::string& IDF3_COMP_OUTLINE::GetPartName( void ) +{ + return part; +} + +const std::string& IDF3_COMP_OUTLINE::GetUID( void ) +{ + if( !uid.empty() ) + return uid; + + if( geometry.empty() && part.empty() ) + return uid; + + uid = geometry + "_" + part; + + return uid; +} + + +int IDF3_COMP_OUTLINE::IncrementRef( void ) +{ + return ++refNum; +} + +int IDF3_COMP_OUTLINE::DecrementRef( void ) +{ + if( refNum == 0 ) + { + ERROR_IDF << "BUG: decrementing refNum beyond 0\n"; + return 0; + } + + --refNum; + return refNum; +} + +bool IDF3_COMP_OUTLINE::CreateDefaultOutline( const std::string &aGeom, const std::string &aPart ) +{ + Clear(); + + if( aGeom.empty() && aPart.empty() ) + { + geometry = "NOGEOM"; + part = "NOPART"; + uid = "NOGEOM_NOPART"; + } + else + { + geometry = aGeom; + part = aPart; + uid = aGeom + "_" + aPart; + } + + compType = COMP_ELEC; + thickness = 5.0; + unit = UNIT_MM; + + // Create a star shape 5mm high with points on 5 and 3 mm circles + double a, da; + da = M_PI / 5.0; + a = da / 2.0; + + IDF_POINT p1, p2; + IDF_OUTLINE* ol = new IDF_OUTLINE; + IDF_SEGMENT* sp; + + p1.x = 1.5 * cos( a ); + p1.y = 1.5 * sin( a ); + + if( ol == NULL ) + return false; + + for( int i = 0; i < 10; ++i ) + { + if( i & 1 ) + { + p2.x = 2.5 * cos( a ); + p2.y = 2.5 * sin( a ); + } + else + { + p2.x = 1.5 * cos( a ); + p2.y = 1.5 * sin( a ); + } + + sp = new IDF_SEGMENT( p1, p2 ); + + if( sp == NULL ) + { + Clear(); + return false; + } + + ol->push( sp ); + a += da; + p1 = p2; + } + + a = da / 2.0; + p2.x = 1.5 * cos( a ); + p2.y = 1.5 * sin( a ); + + sp = new IDF_SEGMENT( p1, p2 ); + + if( sp == NULL ) + { + Clear(); + return false; + } + + ol->push( sp ); + outlines.push_back( ol ); + + return true; +} diff --git a/utils/idftools/idf_outlines.h b/utils/idftools/idf_outlines.h new file mode 100644 index 0000000000..4f360e51e9 --- /dev/null +++ b/utils/idftools/idf_outlines.h @@ -0,0 +1,754 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 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 + */ + + +#ifndef IDF_OUTLINES_H +#define IDF_OUTLINES_H + +#include +#include +#include +#include +#include + +#include + +/* + * NOTES ON OUTLINE TYPES: + * + * BOARD_OUTLINE (PANEL_OUTLINE) + * .BOARD_OUTLINE [OWNER] + * [thickness] + * [outlines] + * + * OTHER_OUTLINE + * .OTHER_OUTLINE [OWNER] + * [outline identifier] [thickness] [board side: Top/Bot] + * [outline] + * + * ROUTE_OUTLINE + * .ROUTE_OUTLINE [OWNER] + * [layers] + * [outline] + * + * PLACE_OUTLINE + * .PLACE_OUTLINE [OWNER] + * [board side: Top/Bot/Both] [height] + * [outline] + * + * ROUTE_KEEPOUT + * .ROUTE_KEEPOUT [OWNER] + * [layers] + * [outline] + * + * VIA_KEEPOUT + * .VIA_KEEPOUT [OWNER] + * [outline] + * + * PLACE_KEEPOUT + * .PLACE_KEEPOUT [OWNER] + * [board side: Top/Bot/Both] [height] + * [outline] + * + * Placement Group + * .PLACE_REGION [OWNER] + * [side: Top/Bot/Both ] [component group name] + * [outline] + * + * Component Outline: + * .ELECTRICAL/.MECHANICAL + * [GEOM] [PART] [UNIT] [HEIGHT] + * [outline] + * [PROP] [prop name] [prop value] + */ + +class IDF3_BOARD; + + +/** + * Class BOARD_OUTLINE + * supports the IDFv3 BOARD OUTLINE data and is the basis of other IDFv3 outline classes + */ +class BOARD_OUTLINE +{ +protected: + std::list< IDF_OUTLINE* > outlines; + IDF3::KEY_OWNER owner; // indicates the owner of this outline (MCAD, ECAD, UNOWNED) + IDF3::OUTLINE_TYPE outlineType;// type of IDF outline + bool single; // true if only a single outline is accepted + std::list< std::string > comments; // associated comment list + IDF3::IDF_UNIT unit; // outline's native unit (MM or THOU) + IDF3_BOARD* parent; // BOARD which contains this outline + double thickness; // Board/Extrude Thickness or Height (IDF spec) + + // Read outline data from a BOARD or LIBRARY file's outline section + bool readOutlines( std::ifstream& aBoardFile ); + // Write comments to a BOARD or LIBRARY file (must not be within a SECTION as per IDFv3 spec) + bool writeComments( std::ofstream& aBoardFile ); + // Write the outline owner to a BOARD file + bool writeOwner( std::ofstream& aBoardFile ); + // Write the data of a single outline object + bool writeOutline( std::ofstream& aBoardFile, IDF_OUTLINE* aOutline, size_t aIndex ); + // Iterate through the outlines and write out all data + bool writeOutlines( std::ofstream& aBoardFile ); // write outline data (no headers) + +public: + BOARD_OUTLINE(); + virtual ~BOARD_OUTLINE(); + + /** + * Function SetUnit + * sets the native unit of the outline; except for component outlines this must + * be the same as the native unit of the parent IDF_BOARD object + * + * @param aUnit is the native unit (UNIT_MM or UNIT_THOU) + */ + virtual void SetUnit( IDF3::IDF_UNIT aUnit ); + + /** + * Function GetUnit + * returns the native unit type of the outline + * + * @return IDF_UNIT is the native unit (UNIT_MM or UNIT_THOU) + */ + virtual IDF3::IDF_UNIT GetUnit( void ); + + /** + * Function SetThickness + * sets the thickness or height of the outline (mm) + * + * @param aThickness is the thickness or height of the outline in mm + */ + virtual bool SetThickness( double aThickness ); + + /** + * Function GetThickness + * returns the thickness or height of an outline (mm) + */ + virtual double GetThickness( void ); + + /** + * Function ReadData + * reads data from a .BOARD_OUTLINE section + * + * @param aBoardFile is an IDFv3 file opened for reading + * @param aHeader is the ".BOARD_OUTLINE" header line as read by FetchIDFLine + * + * @return bool: true if the BOARD_OUTLINE section was successfully read, otherwise + * false. In case of an unrecoverable error an exception is thrown. On a successful + * return the file pointer will be at the line following .END_BOARD_OUTLINE + */ + virtual bool ReadData( std::ifstream& aBoardFile, const std::string& aHeader ); + + /** + * Function WriteData + * writes the comments and .BOARD_OUTLINE section to an IDFv3 file + * + * @param aBoardFile is an IDFv3 file opened for writing + * + * @return bool: true if the data had been successfully written, otherwise false. + */ + virtual bool WriteData( std::ofstream& aBoardFile ); + + /** + * Function Clear + * frees memory and reinitializes all internal data except for the parent pointer + */ + virtual void Clear( void ); + + /** + * Function GetOutlineType + * returns the type of outline according to the IDFv3 classification + */ + IDF3::OUTLINE_TYPE GetOutlineType( void ); + + /** + * Function SetParent + * sets the parent IDF_BOARD object + */ + void SetParent( IDF3_BOARD* aParent ); + + /** + * Function GetParent + * returns the parent IDF_BOARD object + */ + IDF3_BOARD* GetParent( void ); + + + /** + * Function AddOutline + * adds the specified outline to this object. + * + * @param aOutline is a valid IDF outline + * + * @return bool: true if the outline was added; false if the outline + * already existed. If the outline cannot be added due to a violation + * of the IDF specification (multiple outlines for anything other than + * a BOARD_OUTLINE, or the ownership rules are violated) an exception is + * thrown. + */ + bool AddOutline( IDF_OUTLINE* aOutline ); + + /** + * Function DelOutline( IDF_OUTLINE* aOutline ) + * removes the given outline, subject to IDF ownership rules, + * if it is owned by this object. The outline pointer remains + * valid and it is the user's responsibility to delete the object. + * The first outline in the list will never be deleted unless it + * is the sole remaining outline; this is to ensure that a board + * outline is not removed while the cutouts remain. + * + * @param aOutline is a pointer to the outline to remove from the list + * + * @return bool: true if the outline was found and removed; false if + * the outline was not found. If an ownership violation occurs an + * exception is thrown. + */ + bool DelOutline( IDF_OUTLINE* aOutline ); + + /** + * Function DelOutline( IDF_OUTLINE* aOutline ) + * deletes the outline specified by the given index, subject to + * IDF ownership rules. The outline data is destroyed. + * The first outline in the list will never be deleted unless it + * is the sole remaining outline; this is to ensure that a board + * outline is not removed while the cutouts remain. + * + * @param aIndex is an index to the outline to delete + * + * @return bool: true if the outline was found and deleted; false if + * the outline was not found. If an ownership violation or indexation + * error occurs an exception is thrown. + */ + bool DelOutline( size_t aIndex ); + + /** + * Function GetOutlines + * returns a pointer to the internal outlines list. It is up to the + * user to respect the IDFv3 specification and avoid changes to this + * list which are in violation of the specification. + */ + const std::list< IDF_OUTLINE* >*const GetOutlines( void ); + + /** + * Function OutlinesSize + * returns the number of items in the internal outline list + */ + size_t OutlinesSize( void ); + + /** + * Function GetOutline + * returns a pointer to the outline as specified by aIndex. + * If the index is out of bounds an error is thrown. It is the + * responsibility of the user to observe IDF ownership rules. + */ + IDF_OUTLINE* GetOutline( size_t aIndex ); + + /** + * Function GetOwner + * returns the ownership status of the outline ( ECAD, MCAD, UNOWNED) + */ + IDF3::KEY_OWNER GetOwner( void ); + + /** + * Function SetOwner + * sets the ownership status of the outline subject to IDF + * ownership rules. The return value is true if the ownership + * was changed and false if a specification violation occurred. + */ + bool SetOwner( IDF3::KEY_OWNER aOwner ); + + /** + * Function IsSingle + * return true if this type of outline only supports a single + * outline. All outlines except for BOARD_OUTLINE are single. + */ + bool IsSingle( void ); + + /** + * Function ClearOutlines + * clears internal data except for the parent pointer + */ + void ClearOutlines( void ); + + /** + * Function AddComment + * adds a comment to the outline data; this function is not + * subject to IDF ownership rules. + */ + void AddComment( const std::string& aComment ); + + /** + * Function CommentSize + * returns the number of comments in the internal list + */ + size_t CommentsSize( void ); + + /** + * Function GetComments + * returns a pointer to the internal list of comments + */ + std::list< std::string >* GetComments( void ); + + /** + * Function GetComment + * returns the string representing the indexed comment or + * NULL if the index is out of bounds + */ + const std::string* GetComment( size_t aIndex ); + + /** + * Function DeleteComment + * deletes a comment based on the given index. + * + * @return bool: true if a comment was deleted, false if + * the index is out of bounds. + */ + bool DeleteComment( size_t aIndex ); + + /** + * Function ClearComments + * deletes all comments + */ + void ClearComments( void ); +}; + + +/** + * Class OTHER_OUTLINE + * describes miscellaneous extrusions on the board + */ +class OTHER_OUTLINE : public BOARD_OUTLINE +{ +private: + std::string uniqueID; // Outline Identifier (IDF spec) + IDF3::IDF_LAYER side; // Board Side [TOP/BOTTOM ONLY] (IDF spec) + +public: + OTHER_OUTLINE(); + + /** + * Function SetOutlineIdentifier + * sets the Outline Identifier string of this OTHER_OUTLINE object + * as per IDFv3 spec. + */ + virtual void SetOutlineIdentifier( const std::string aUniqueID ); + + /** + * Function GetOutlineIdentifier + * returns the object's Outline Identifier + */ + virtual const std::string& GetOutlineIdentifier( void ); + + /** + * Function SetSide + * sets the side which this outline is applicable to (TOP, BOTTOM). + * + * @return bool: true if the side was set, false if the side is invalid. + * An exception is thrown if there is a violation of IDF ownership rules. + */ + virtual bool SetSide( IDF3::IDF_LAYER aSide ); + + /** + * Function GetSide + * returns the side which this outline is applicable to + */ + virtual IDF3::IDF_LAYER GetSide( void ); + + /** + * Function ReadData + * reads an OTHER_OUTLINE data from an IDFv3 file. + * + * @param aBoardFile is an IDFv3 file open for reading + * @param aHeader is the .OTHER_OUTLINE header as read via FetchIDFLine + * + * @return bool: true if data was read, otherwise false. If an unrecoverable + * error occurs an exception is thrown. + */ + virtual bool ReadData( std::ifstream& aBoardFile, const std::string& aHeader ); + + /** + * Function WriteData + * writes the OTHER_OUTLINE data to an open IDFv3 file + * + * @param aBoardFile is an IDFv3 file open for writing + * + * @return bool: true if the data was successfully written, otherwise false. + */ + virtual bool WriteData( std::ofstream& aBoardFile ); + + /** + * Function Clear + * deletes internal data except for the parent object + */ + virtual void Clear( void ); +}; + + +/** + * Class ROUTE_OUTLINE + * describes routing areas on the board + */ +class ROUTE_OUTLINE : public BOARD_OUTLINE +{ +protected: + IDF3::IDF_LAYER layers; // Routing layers (IDF spec) + +public: + ROUTE_OUTLINE(); + + /** + * Function SetLayers + * sets the layer or group of layers this outline is applicable to. + * This function is subject to IDF ownership rules. An exception is + * thrown if an invalid layer is provided or an IDF ownership violation + * occurs. + */ + virtual void SetLayers( IDF3::IDF_LAYER aLayer ); + + /** + * Function GetLayers + * returns the layer or group of layers which this outline is applicable to + */ + virtual IDF3::IDF_LAYER GetLayers( void ); + + /** + * Function ReadData + * reads ROUTE_OUTLINE data from an IDFv3 file + * + * @param aBoardFile is an open IDFv3 board file + * @param aHeader is the .ROUTE_OUTLINE header as returned by FetchIDFLine + * + * @return bool: true if data was read, otherwise false. If unrecoverable + * errors occur an exception is thrown. + */ + virtual bool ReadData( std::ifstream& aBoardFile, const std::string& aHeader ); + + /** + * Function WriteData + * writes the ROUTE_OUTLINE data to an open IDFv3 file + */ + virtual bool WriteData( std::ofstream& aBoardFile ); + + /** + * Function Clear + * deletes internal data except for the parent object + */ + virtual void Clear( void ); +}; + +/** + * Class PLACE_OUTLINE + * describes areas on the board for placing components + */ +class PLACE_OUTLINE : public BOARD_OUTLINE +{ +protected: + IDF3::IDF_LAYER side; // Board Side [TOP/BOTTOM/BOTH ONLY] (IDF spec) + double height; // Max Height (IDF spec) + +public: + PLACE_OUTLINE(); + + /** + * Function SetSide + * sets the side (TOP, BOTTOM, BOTH) which this outline applies to, + * subject to IDF ownership rules. An exception is thrown if there is + * an ownership violation or an invalid layer is passed. + */ + virtual void SetSide( IDF3::IDF_LAYER aSide ); + + /** + * Function GetSide + * returns the side which this outline is applicable to + */ + virtual IDF3::IDF_LAYER GetSide( void ); + + /** + * Function SetMaxHeight + * sets the maximum height of a component within this outline, + * subject to IDF ownership rules. An exception is thrown if + * there is an ownership violation or aHeight is negative. + */ + virtual void SetMaxHeight( double aHeight ); + + /** + * Function GetMaxHeight + * returns the maximum allowable height for a component in this region + */ + virtual double GetMaxHeight( void ); + + /** + * Function ReadData + * reads PLACE_OUTLINE data from an open IDFv3 file. + * + * @param aBoardFile is an IDFv3 file opened for reading + * @param aHeader is the .PLACE_OUTLINE header as returned by FetchIDFLine + * + * @return bool: true if data was read, otherwise false. If there are + * unrecoverable errors an exception is thrown. + */ + virtual bool ReadData( std::ifstream& aBoardFile, const std::string& aHeader ); + + /** + * Function WriteData + * writes the PLACE_OUTLINE data to an open IDFv3 file + * + * @param aBoardFile is an IDFv3 file opened for writing + * + * @return bool: true if the data was successfully written, otherwise false + */ + virtual bool WriteData( std::ofstream& aBoardFile ); + + /** + * Function Clear + * deletes all internal data + */ + virtual void Clear( void ); +}; + + +/** + * Class ROUTE_KO_OUTLINE + * describes regions and layers where no electrical routing is permitted + */ +class ROUTE_KO_OUTLINE : public ROUTE_OUTLINE +{ +public: + ROUTE_KO_OUTLINE(); +}; + +/** + * Class VIA_KO_OUTLINE + * describes regions in which vias are prohibited. Note: IDFv3 only considers + * thru-hole vias and makes no statement regarding behavior with blind or buried + * vias. + */ +class VIA_KO_OUTLINE : public OTHER_OUTLINE +{ +public: + VIA_KO_OUTLINE(); +}; + + +/** + * Class PLACE_KO_OUTLINE + * represents regions and layers in which no component may + * be placed or on which a maximum component height is in effect. + */ +class PLACE_KO_OUTLINE : public PLACE_OUTLINE +{ +public: + PLACE_KO_OUTLINE(); +}; + +/** + * Class GROUP_OUTLINE + * represents regions and layers in which user-specified features or components + * may be placed. + */ +class GROUP_OUTLINE : public BOARD_OUTLINE +{ +private: + IDF3::IDF_LAYER side; // Board Side [TOP/BOTTOM/BOTH ONLY] (IDF spec) + std::string groupName; // non-unique string + +public: + GROUP_OUTLINE(); + + /** + * Function SetSide + * sets the side which this outline applies to (TOP, BOTTOM, BOTH), + * subject to IDF ownership rules. If an ownership violation occurs + * or an invalid side is specified, an exception is thrown. + */ + virtual void SetSide( IDF3::IDF_LAYER aSide ); + + /** + * Function GetSide + * returns the side which this outline applies to + */ + virtual IDF3::IDF_LAYER GetSide( void ); + + /** + * Function SetGroupName + * sets the name of the group, subject to IDF ownership rules. + * An empty name or an ownership violation results in a thrown + * exception. + */ + virtual void SetGroupName( std::string aGroupName ); + + /** + * Function GetGroupName + * returns a reference to the (non-unique) group name + */ + virtual const std::string& GetGroupName( void ); + + /** + * Function ReadData + * reads GROUP_OUTLINE data from an open IDFv3 file + * + * @param aBoardFile is an open IDFv3 file + * @param aHeader is the .PLACE_REGION header as returned by FetchIDFLine + * + * @return bool: true if data was read, otherwise false. If an unrecoverable + * error occurs an exception is thrown. + */ + virtual bool ReadData( std::ifstream& aBoardFile, const std::string& aHeader ); + + /** + * Function WriteData + * writes the data to a .PLACE_REGION section of an IDFv3 file + * + * @param aBoardFile is an IDFv3 file open for writing + * + * @return bool: true if the data is successfully written, otherwise false + */ + virtual bool WriteData( std::ofstream& aBoardFile ); + + /** + * Function Clear + * deletes internal data, subject to IDF ownership rules + */ + virtual void Clear( void ); +}; + + +/** + * class IDF3_COMP_OUTLINE + * represents a component's outline as stored in an IDF library file + */ +class IDF3_COMP_OUTLINE : public BOARD_OUTLINE +{ +private: + std::string uid; // unique ID + std::string geometry; // geometry name (IDF) + std::string part; // part name (IDF) + IDF3::COMP_TYPE compType; // component type + int refNum; // number of components referring to this outline + + std::map< std::string, std::string > props; // properties list + + bool readProperties( std::ifstream& aLibFile ); + bool writeProperties( std::ofstream& aLibFile ); + +public: + IDF3_COMP_OUTLINE(); + + /** + * Function ReadData + * reads a component outline from an open IDFv3 file + * + * @param aLibFile is an open IDFv3 Library file + * @param aHeader is the .ELECTRICAL or .MECHANICAL header as returned by FetchIDFLine + * + * @return bool: true if data was read, otherwise false. If unrecoverable errors + * occur, an exception is thrown. + */ + virtual bool ReadData( std::ifstream& aLibFile, const std::string& aHeader ); + + /** + * Function WriteData + * writes comments and component outline data to an IDFv3 Library file + * + * @param aLibFile is an IDFv3 library file open for writing + * + * @return bool: true if the data was successfully written, otherwise false + */ + virtual bool WriteData( std::ofstream& aLibFile ); + + /** + * Function Clear + * deletes internal outline data + */ + virtual void Clear( void ); + + /** + * Function SetComponentClass + * sets the type of component outline (.ELECTRICAL or .MECHANICAL) + * If the specified class is invalid an exception is thrown. + */ + void SetComponentClass( IDF3::COMP_TYPE aCompClass ); + + /** + * Function GetComponentClass + * returns the class of component represented by this outline + */ + IDF3::COMP_TYPE GetComponentClass( void ); + + /** + * Function SetGeomName + * sets the Geometry Name (Package Name, IDFv3 spec) of the component outline + */ + void SetGeomName( const std::string& aGeomName ); + + /** + * Function GetGeomName + * returns the Geometry Name (Package Name) of the component outline + */ + const std::string& GetGeomName( void ); + + /** + * Function SetPartName + * sets the Part Name (Part Number, IDFv3 spec) of the component outline + */ + void SetPartName( const std::string& aPartName ); + + /** + * Function GetPartName + * returns the Part Name (Part Number) of the component outline + */ + const std::string& GetPartName( void ); + + /** + * Function GetUID + * returns the unique identifier for this component outline; + * this is equal to GEOM_NAME + "_" + PART_NAME + */ + const std::string& GetUID( void ); + + /** + * Function IncrementRef + * increments the internal reference counter to keep track of the number of + * components referring to this outline. + */ + int IncrementRef( void ); + + /** + * Function DecrementRef + * decrements the internal reference counter to keep track of the number of + * components referring to this outline. + */ + int DecrementRef( void ); + + /** + * Function CreateDefaultOutline + * creates a default outline with the given Geometry and Part names. + * This outline is a star with outer radius 5mm and inner radius 2.5mm. + */ + bool CreateDefaultOutline( const std::string &aGeom, const std::string &aPart ); + + // XXX: property manipulators +}; + +#endif // IDF_OUTLINES_H \ No newline at end of file diff --git a/utils/idftools/idf_parser.cpp b/utils/idftools/idf_parser.cpp new file mode 100644 index 0000000000..8582cc0321 --- /dev/null +++ b/utils/idftools/idf_parser.cpp @@ -0,0 +1,3819 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 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 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace IDF3; + +/* + * CLASS: IDF3_COMP_OUTLINE_DATA + * This represents the outline placement + * information and other data specific to + * each component instance. + */ +IDF3_COMP_OUTLINE_DATA::IDF3_COMP_OUTLINE_DATA() +{ + parent = NULL; + outline = NULL; + xoff = 0.0; + yoff = 0.0; + zoff = 0.0; + aoff = 0.0; + + return; +} + +IDF3_COMP_OUTLINE_DATA::IDF3_COMP_OUTLINE_DATA( IDF3_COMPONENT* aParent, + IDF3_COMP_OUTLINE* aOutline ) +{ + parent = aParent; + outline = aOutline; + xoff = 0.0; + yoff = 0.0; + zoff = 0.0; + aoff = 0.0; + + if( aOutline ) + aOutline->IncrementRef(); + + return; +} + +IDF3_COMP_OUTLINE_DATA::IDF3_COMP_OUTLINE_DATA( IDF3_COMPONENT* aParent, + IDF3_COMP_OUTLINE* aOutline, + double aXoff, double aYoff, + double aZoff, double aAngleOff ) +{ + parent = aParent; + outline = aOutline; + xoff = aXoff; + yoff = aYoff; + zoff = aZoff; + aoff = aAngleOff; + return; +} + +IDF3_COMP_OUTLINE_DATA::~IDF3_COMP_OUTLINE_DATA() +{ + if( outline ) + outline->DecrementRef(); + + return; +} + +void IDF3_COMP_OUTLINE_DATA::SetOffsets( double aXoff, double aYoff, + double aZoff, double aAngleOff ) +{ + xoff = aXoff; + yoff = aYoff; + zoff = aZoff; + aoff = aAngleOff; + return; +} + +void IDF3_COMP_OUTLINE_DATA::GetOffsets( double& aXoff, double& aYoff, + double& aZoff, double& aAngleOff ) +{ + aXoff = xoff; + aYoff = yoff; + aZoff = zoff; + aAngleOff = aoff; + return; +} + + +void IDF3_COMP_OUTLINE_DATA::SetParent( IDF3_COMPONENT* aParent ) +{ + parent = aParent; +} + +void IDF3_COMP_OUTLINE_DATA::SetOutline( IDF3_COMP_OUTLINE* aOutline ) +{ + if( outline ) + outline->DecrementRef(); + + outline = aOutline; + + if( outline ) + outline->IncrementRef(); + + return; +} + +bool IDF3_COMP_OUTLINE_DATA::ReadPlaceData( std::ifstream &aBoardFile, + IDF3::FILE_STATE& aBoardState, IDF3_BOARD *aBoard ) +{ + if( !aBoard ) + { + ERROR_IDF; + cerr << "BUG: invoked with no reference to the parent IDF_BOARD\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + // clear out data possibly left over from previous use of the object + outline = NULL; + parent = NULL; + + std::string iline; // the input line + bool isComment; // true if a line just read in is a comment line + std::streampos pos; + int idx = 0; + bool quoted = false; + std::string token; + std::string uid; + std::string refdes; + IDF3::IDF_PLACEMENT placement = IDF3::PS_UNPLACED; + IDF3::IDF_LAYER side = IDF3::LYR_TOP; + + // RECORD 2: 'package name', 'part number', 'Refdes' (any, NOREFDES, BOARD) + while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() ); + + if( ( !aBoardFile.good() && !aBoardFile.eof() ) || iline.empty() ) + { + ERROR_IDF; + cerr << "problems reading PLACEMENT SECTION\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: comment within a section (PLACEMENT)\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( !quoted && CompareToken( ".END_PLACEMENT", token ) ) + { + errno = 0; + aBoardState = IDF3::FILE_PLACEMENT; + return false; + } + + std::string ngeom = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: no PART NAME in PLACEMENT RECORD2\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + std::string npart = token; + uid = ngeom + "_" + npart; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: no REFDES in PLACEMENT RECORD2\n"; + cerr << "* Line: '" << iline << "'\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( "NOREFDES", token ) ) + { + // according to the IDF3.0 specification, this is a + // mechanical component. The specification is defective + // since it is impossible to associate mechanical + // components with their holes unless the mechanical + // component is given a unique RefDes. This class of defect + // is one reason IDF does not work well in faithfully + // conveying information between ECAD and MCAD. + refdes = token; + } + else if( CompareToken( "BOARD", token ) ) + { + ERROR_IDF; + cerr << "unsupported feature\n"; + cerr << "* RefDes is 'BOARD', indicating this is a PANEL FILE (not supported)\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + else if( CompareToken( "PANEL", token ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: RefDes in PLACEMENT RECORD2 is 'PANEL'\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + else if( token.empty() ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: empty RefDes string in PLACEMENT RECORD2\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + else + { + // note: perversely, spaces can be a valid RefDes + refdes = token; + } + + // RECORD 3: X, Y, Z, ROT, SIDE (top/bot), PLACEMENT (placed, unplaced, mcad, ecad) + while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() ); + + if( !aBoardFile.good() ) + { + ERROR_IDF; + cerr << "problems reading PLACEMENT SECTION, RECORD 3\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: comment within a section (PLACEMENT)\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: X value must not be in quotes (PLACEMENT RECORD 3)\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + istringstream istr; + istr.str( token ); + + istr >> xoff; + if( istr.fail() ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: X value is not numeric (PLACEMENT RECORD 3)\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: no Y value in PLACEMENT RECORD 3\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + istr.clear(); + istr.str( token ); + + istr >> yoff; + if( istr.fail() ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: Y value is not numeric (PLACEMENT RECORD 3)\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: no Z value in PLACEMENT RECORD 3\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + istr.clear(); + istr.str( token ); + + istr >> zoff; + if( istr.fail() ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: Z value is not numeric (PLACEMENT RECORD 3)\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: no rotation value in PLACEMENT RECORD 3\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + istr.clear(); + istr.str( token ); + + istr >> aoff; + if( istr.fail() ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: rotation value is not numeric (PLACEMENT RECORD 3)\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: no SIDE value in PLACEMENT RECORD 3\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( "TOP", token ) ) + { + side = IDF3::LYR_TOP; + } + else if( CompareToken( "BOTTOM", token ) ) + { + side = IDF3::LYR_BOTTOM; + } + else + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: SIDE value in PLACEMENT RECORD 3 is invalid ('"; + cerr << token << "')\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: no PLACEMENT value in PLACEMENT RECORD 3\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( "PLACED", token ) ) + { + placement = IDF3::PS_PLACED; + } + else if( CompareToken( "UNPLACED", token ) ) + { + placement = IDF3::PS_UNPLACED; + } + else if( CompareToken( "MCAD", token ) ) + { + placement = IDF3::PS_MCAD; + } + else if( CompareToken( "ECAD", token ) ) + { + placement = IDF3::PS_ECAD; + } + else + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: PLACEMENT value in PLACEMENT RECORD 3 is invalid ('"; + cerr << token << "')\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + outline = aBoard->GetComponentOutline( uid ); + + if( outline == NULL ) + { + ERROR_IDF << "MISSING OUTLINE\n"; + cerr << "* GeomName( " << ngeom << " ), PartName( " << npart << " )\n"; + cerr << "* Substituting default outline.\n"; + outline = aBoard->GetInvalidOutline( ngeom, npart ); + + if( outline == NULL ) + { + ERROR_IDF << "cannot create outline object\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + } + + if( aBoard->GetUnit() == IDF3::UNIT_THOU ) + { + xoff *= IDF_MM_TO_THOU; + yoff *= IDF_MM_TO_THOU; + zoff *= IDF_MM_TO_THOU; + } + + parent = aBoard->FindComponent( refdes ); + + if( parent == NULL ) + { + IDF3_COMPONENT* cp = new IDF3_COMPONENT( aBoard ); + + if( cp == NULL ) + { + ERROR_IDF << "cannot create component object\n"; + aBoardState = IDF3::FILE_INVALID; + outline = NULL; + return false; + } + + cp->SetRefDes( refdes ); + cp->SetPosition( xoff, yoff, aoff, side ); + cp->SetPlacement( placement ); + + xoff = 0; + yoff = 0; + aoff = 0; + + aBoard->AddComponent( cp ); + + parent = cp; + } + else + { + double tX, tY, tA; + IDF3::IDF_LAYER tL; + + if( parent->GetPosition( tX, tY, tA, tL ) ) + { + if( side != tL ) + { + ERROR_IDF << "inconsistent PLACEMENT data\n"; + cerr << "* SIDE value has changed from " << GetLayerString( tL ); + cerr << " to " << GetLayerString( side ) << "\n"; + aBoardState = IDF3::FILE_INVALID; + outline = NULL; + return false; + } + + xoff -= tX; + yoff -= tY; + aoff -= tA; + } + else + { + parent->SetPosition( xoff, yoff, aoff, side ); + parent->SetPlacement( placement ); + + xoff = 0; + yoff = 0; + aoff = 0; + } + + if( placement != parent->GetPlacement() ) + { + ERROR_IDF << "inconsistent PLACEMENT data\n"; + cerr << "* placement value has changed from " << GetPlacementString( parent->GetPlacement() ); + cerr << " to " << GetPlacementString( placement ) << "\n"; + cerr << "* line: '" << iline << "'\n"; + aBoardState = IDF3::FILE_INVALID; + outline = NULL; + return false; + } + + } + + // copy internal data to a new object and push it into the component's outline list + IDF3_COMP_OUTLINE_DATA* cdp = new IDF3_COMP_OUTLINE_DATA; + *cdp = *this; + outline->IncrementRef(); + outline = NULL; + + if( !parent->AddOutlineData( cdp ) ) + { + ERROR_IDF << "could not add outline data object\n"; + aBoardState = IDF3::FILE_INVALID; + delete cdp; + return false; + } + + return true; +} + +bool IDF3_COMP_OUTLINE_DATA::WritePlaceData( std::ofstream& aBoardFile, + double aXpos, double aYpos, double aAngle, + const std::string aRefDes, + IDF3::IDF_PLACEMENT aPlacement, + IDF3::IDF_LAYER aSide ) +{ + if( outline == NULL ) + return true; + + if( outline->GetUID().empty() ) + { + ERROR_IDF << "invalid GEOM/PART names\n"; + return false; + } + + if( aPlacement == PS_INVALID ) + { + ERROR_IDF << "placement invalid; defaulting to PLACED\n"; + aPlacement = PS_PLACED; + } + + if( aSide != LYR_TOP && aSide != LYR_BOTTOM ) + { + ERROR_IDF << "invalid side (" << aSide << "); must be TOP or BOTTOM\n"; + return false; + } + + // calculate the final position based on layer + double xpos, ypos, ang; + + switch( aSide ) + { + case LYR_TOP: + xpos = aXpos + xoff; + ypos = aYpos + yoff; + ang = aAngle + aoff; + break; + + default: + xpos = aXpos - xoff; + ypos = aYpos + yoff; + ang = aAngle - aoff; + break; + } + + std::string arefdes = aRefDes; + + if( arefdes.empty() || !arefdes.compare( "~" ) + || ( arefdes.size() >= 8 && CompareToken( "NOREFDES", arefdes.substr(0, 8) ) ) ) + arefdes = "NOREFDES"; + + aBoardFile << "\"" << outline->GetGeomName() << "\" \"" << outline->GetPartName() << "\" " + << arefdes << "\n"; + + IDF3::IDF_UNIT unit = UNIT_MM; + + if( parent ) + unit = parent->GetUnit(); + + if( unit == UNIT_MM ) + { + aBoardFile << setiosflags(ios::fixed) << setprecision(5) << xpos << " " + << ypos << " " << setprecision(3) << zoff << " " + << ang << " "; + } + else + { + aBoardFile << setiosflags(ios::fixed) << setprecision(1) << (xpos / IDF_MM_TO_THOU) << " " + << (ypos / IDF_MM_TO_THOU) << " " << (zoff / IDF_MM_TO_THOU) << " " + << setprecision(3) << ang << " "; + } + + WriteLayersText( aBoardFile, aSide ); + + switch( aPlacement ) + { + case PS_PLACED: + aBoardFile << " PLACED\n"; + break; + + case PS_UNPLACED: + aBoardFile << " UNPLACED\n"; + break; + + case PS_MCAD: + aBoardFile << " MCAD\n"; + break; + + default: + aBoardFile << " ECAD\n"; + break; + } + + return !aBoardFile.fail(); +} + + +/* + * CLASS: IDF3_COMPONENT + * + * This represents a component and its associated + * IDF outlines and ancillary data (position, etc) + */ +IDF3_COMPONENT::IDF3_COMPONENT() +{ + xpos = 0.0; + ypos = 0.0; + angle = 0.0; + parent = NULL; + + hasPosition = false; + placement = PS_INVALID; + layer = LYR_INVALID; + return; +} + +IDF3_COMPONENT::IDF3_COMPONENT( IDF3_BOARD* aParent ) +{ + xpos = 0.0; + ypos = 0.0; + angle = 0.0; + + hasPosition = false; + placement = PS_INVALID; + layer = LYR_INVALID; + + parent = aParent; + return; +} + +IDF3_COMPONENT::~IDF3_COMPONENT() +{ + std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itcS = components.begin(); + std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itcE = components.end(); + + while( itcS != itcE ) + { + delete *itcS; + ++itcS; + } + + components.clear(); + + std::list< IDF_DRILL_DATA* >::iterator itdS = drills.begin(); + std::list< IDF_DRILL_DATA* >::iterator itdE = drills.end(); + + while( itdS != itdE ) + { + delete *itdS; + ++itdS; + } + + drills.clear(); + + return; +} + +void IDF3_COMPONENT::SetParent( IDF3_BOARD* aParent ) +{ + parent = aParent; + return; +} + +IDF3::CAD_TYPE IDF3_COMPONENT::GetCadType( void ) +{ + if( parent ) + return parent->GetCadType(); + + return CAD_INVALID; +} + +IDF3::IDF_UNIT IDF3_COMPONENT::GetUnit( void ) +{ + if( parent ) + return parent->GetUnit(); + + return UNIT_INVALID; +} + + +bool IDF3_COMPONENT::SetRefDes( const std::string& aRefDes ) +{ + if( aRefDes.empty() ) + { + ERROR_IDF << "invalid RefDes (empty)\n"; + return false; + } + + if( CompareToken( "PANEL", aRefDes ) ) + { + ERROR_IDF; + cerr << "\n*BUG: PANEL is a reserved designator and may not be used by components\n"; + return false; + } + + refdes = aRefDes; + return true; +} + +const std::string& IDF3_COMPONENT::GetRefDes( void ) +{ + return refdes; +} + +IDF_DRILL_DATA* IDF3_COMPONENT::AddDrill( double aDia, double aXpos, double aYpos, + IDF3::KEY_PLATING aPlating, + const std::string aHoleType, + IDF3::KEY_OWNER aOwner ) +{ + IDF_DRILL_DATA* dp = new IDF_DRILL_DATA( aDia, aXpos, aYpos, aPlating, + refdes, aHoleType, aOwner ); + + if( dp == NULL ) + return NULL; + + drills.push_back( dp ); + + return dp; +} + +IDF_DRILL_DATA* IDF3_COMPONENT::AddDrill( IDF_DRILL_DATA* aDrilledHole ) +{ + if( !aDrilledHole ) + return NULL; + + if( CompareToken( "PANEL", refdes ) ) + { + ERROR_IDF; + cerr << "\n*BUG: PANEL drills not supported\n"; + return NULL; + } + + if( refdes.compare( aDrilledHole->GetDrillRefDes() ) ) + { + ERROR_IDF; + cerr << "\n*BUG: pushing an incorrect REFDES ('" << aDrilledHole->GetDrillRefDes(); + cerr << "') to component ('" << refdes << "')\n"; + return NULL; + } + + drills.push_back( aDrilledHole ); + + return aDrilledHole; +} + + +bool IDF3_COMPONENT::DelDrill( double aDia, double aXpos, double aYpos ) +{ + if( drills.empty() ) + return false; + + // XXX - throw on ownership violation + + bool val = false; + + list< IDF_DRILL_DATA* >::iterator itS = drills.begin(); + list< IDF_DRILL_DATA* >::iterator itE = drills.end(); + + while( !drills.empty() && itS != itE ) + { + if( (*itS)->Matches( aDia, aXpos, aYpos ) ) + { + val = true; + delete *itS; + drills.erase( itS ); + itS = drills.begin(); + itE = drills.end(); + continue; + } + ++itS; + } + + return val; +} + +bool IDF3_COMPONENT::DelDrill( IDF_DRILL_DATA* aDrill ) +{ + if( drills.empty() ) + return false; + + // XXX - throw on ownership violation + + list< IDF_DRILL_DATA* >::iterator itS = drills.begin(); + list< IDF_DRILL_DATA* >::iterator itE = drills.end(); + + while( !drills.empty() && itS != itE ) + { + if( *itS == aDrill ) + { + delete *itS; + drills.erase( itS ); + return true; + } + ++itS; + } + + return false; +} + +const std::list< IDF_DRILL_DATA* >*const IDF3_COMPONENT::GetDrills( void ) +{ + return &drills; +} + +bool IDF3_COMPONENT::AddOutlineData( IDF3_COMP_OUTLINE_DATA* aComponentOutline ) +{ + if( aComponentOutline == NULL ) + return false; + + components.push_back( aComponentOutline ); + + return true; +} + +bool IDF3_COMPONENT::DeleteOutlineData( IDF3_COMP_OUTLINE_DATA* aComponentOutline ) +{ + if( components.empty() || aComponentOutline == NULL ) + return false; + + std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itS = components.begin(); + std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itE = components.end(); + + while( itS != itE ) + { + if( *itS == aComponentOutline ) + { + delete *itS; + components.erase( itS ); + return true; + } + + ++itS; + } + + return false; +} + +bool IDF3_COMPONENT::DeleteOutlineData( size_t aIndex ) +{ + if( aIndex >= components.size() ) + return false; + + std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itS = components.begin(); + std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itE = components.end(); + size_t idx = 0; + + while( itS != itE ) + { + if( idx == aIndex ) + { + delete *itS; + components.erase( itS ); + return true; + } + + ++idx; + ++itS; + } + + return false; +} + +size_t IDF3_COMPONENT::GetOutlinesSize( void ) +{ + return components.size(); +} + +const std::list< IDF3_COMP_OUTLINE_DATA* >*const IDF3_COMPONENT::GetOutlinesData( void ) +{ + return &components; +} + +bool IDF3_COMPONENT::GetPosition( double& aXpos, double& aYpos, double& aAngle, + IDF3::IDF_LAYER& aLayer ) +{ + if( !hasPosition ) + { + aXpos = 0.0; + aYpos = 0.0; + aAngle = 0.0; + aLayer = IDF3::LYR_INVALID; + return false; + } + + aXpos = xpos; + aYpos = ypos; + aAngle = angle; + aLayer = layer; + return true; +} + +bool IDF3_COMPONENT::SetPosition( double aXpos, double aYpos, double aAngle, IDF3::IDF_LAYER aLayer ) +{ + switch( aLayer ) + { + case LYR_TOP: + case LYR_BOTTOM: + break; + + default: + ERROR_IDF << "invalid side (must be TOP or BOTTOM only): " << aLayer << "\n"; + return false; + break; + } + + if( hasPosition ) + return false; + + hasPosition = true; + xpos = aXpos; + ypos = aYpos; + angle = aAngle; + layer = aLayer; + return true; +} + + +IDF3::IDF_PLACEMENT IDF3_COMPONENT::GetPlacement( void ) +{ + return placement; +} + + +void IDF3_COMPONENT::SetPlacement( IDF3::IDF_PLACEMENT aPlacementValue ) +{ + // XXX - throw on ownership violation or invalid placement value + if( aPlacementValue < PS_UNPLACED || aPlacementValue >= PS_INVALID ) + return; + + placement = aPlacementValue; + return; +} + +bool IDF3_COMPONENT::WriteDrillData( std::ofstream& aBoardFile ) +{ + if( drills.empty() ) + return true; + + std::list< IDF_DRILL_DATA* >::iterator itS = drills.begin(); + std::list< IDF_DRILL_DATA* >::iterator itE = drills.end(); + + while( itS != itE ) + { + if( !(*itS)->Write( aBoardFile, GetUnit() ) ) + return false; + + ++itS; + } + + return true; +} + +bool IDF3_COMPONENT::WritePlaceData( std::ofstream& aBoardFile ) +{ + if( components.empty() ) + return true; + + std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itS = components.begin(); + std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itE = components.end(); + + while( itS != itE ) + { + if( !(*itS)->WritePlaceData( aBoardFile, xpos, ypos, angle, refdes, placement, layer ) ) + return false; + + ++itS; + } + + return true; +} + + +IDF3_BOARD::IDF3_BOARD( IDF3::CAD_TYPE aCadType ) +{ + state = FILE_START; + cadType = aCadType; + userPrec = 5; + userScale = 1.0; + userXoff = 0.0; + userYoff = 0.0; + brdFileVersion = 0; + libFileVersion = 0; + + // unlike other outlines which are created as necessary, + // the board outline always exists and its parent must + // be set here + olnBoard.SetParent( this ); + olnBoard.SetThickness( 1.6 ); + + return; +} + +IDF3_BOARD::~IDF3_BOARD() +{ + Clear(); + + return; +} + +IDF3::CAD_TYPE IDF3_BOARD::GetCadType( void ) +{ + return cadType; +} + +void IDF3_BOARD::SetBoardName( std::string aBoardName ) +{ + boardName = aBoardName; + return; +} + +const std::string& IDF3_BOARD::GetBoardName( void ) +{ + return boardName; +} + +bool IDF3_BOARD::setUnit( IDF3::IDF_UNIT aUnit, bool convert ) +{ + switch( aUnit ) + { + case UNIT_MM: + case UNIT_THOU: + unit = aUnit; + break; + + default: + ERROR_IDF << "invalid board unit\n"; + return false; + break; + } + + // iterate through all owned OUTLINE objects (except IDF3_COMP_OUTLINE) + // and set to the same unit + + olnBoard.SetUnit( aUnit ); + + do + { + std::map< std::string, OTHER_OUTLINE*>::iterator its = olnOther.begin(); + std::map< std::string, OTHER_OUTLINE*>::iterator ite = olnOther.end(); + + while( its != ite ) + { + its->second->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + do + { + std::list::iterator its = olnRoute.begin(); + std::list::iterator ite = olnRoute.end(); + + while( its != ite ) + { + (*its)->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + do + { + std::list::iterator its = olnPlace.begin(); + std::list::iterator ite = olnPlace.end(); + + while( its != ite ) + { + (*its)->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + do + { + std::list::iterator its = olnRouteKeepout.begin(); + std::list::iterator ite = olnRouteKeepout.end(); + + while( its != ite ) + { + (*its)->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + do + { + std::list::iterator its = olnViaKeepout.begin(); + std::list::iterator ite = olnViaKeepout.end(); + + while( its != ite ) + { + (*its)->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + do + { + std::list::iterator its = olnPlaceKeepout.begin(); + std::list::iterator ite = olnPlaceKeepout.end(); + + while( its != ite ) + { + (*its)->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + do + { + std::map::iterator its = olnGroup.begin(); + std::map::iterator ite = olnGroup.end(); + + while( its != ite ) + { + its->second->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + //iterate through all owned IDF3_COMP_OUTLINE objects and + // set to the same unit IF convert = true + if( convert ) + { + std::map::iterator its = compOutlines.begin(); + std::map::iterator ite = compOutlines.end(); + + while( its != ite ) + { + its->second->SetUnit( aUnit ); + ++its; + } + + } + + return true; +} + + +IDF3::IDF_UNIT IDF3_BOARD::GetUnit( void ) +{ + return unit; +} + + +bool IDF3_BOARD::SetBoardThickness( double aBoardThickness ) +{ + if( aBoardThickness <= 0.0 ) + { + ERROR_IDF << "board thickness must be > 0\n"; + return false; + } + + return olnBoard.SetThickness( aBoardThickness ); +} + + +double IDF3_BOARD::GetBoardThickness( void ) +{ + return olnBoard.GetThickness(); +} + + +// read the DRILLED HOLES section +bool IDF3_BOARD::readBrdDrills( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ) +{ + IDF_DRILL_DATA drill; + + while( drill.Read( aBoardFile, unit, aBoardState ) ) + { + if( CompareToken( "PANEL", drill.GetDrillRefDes() ) ) + { + ERROR_IDF; + cerr << "\n[INFO]: Dropping unsupported drill refdes: 'PANEL' (not supported)\n"; + continue; + } + + IDF_DRILL_DATA *dp = new IDF_DRILL_DATA; + *dp = drill; + if( AddDrill( dp ) == NULL ) + { + delete dp; + ERROR_IDF; + cerr << "\n* BUG: could not add drill data; cannot continue reading the file\n"; + aBoardState = FILE_INVALID; + return false; + } + } + + if( errno == 0 && aBoardState != IDF3::FILE_INVALID ) + return true; + + return false; + +} + + +// read the NOTES section +bool IDF3_BOARD::readBrdNotes( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ) +{ + IDF_NOTE note; + + while( note.ReadNote( aBoardFile, aBoardState, unit ) ) + { + IDF_NOTE *np = new IDF_NOTE; + *np = note; + notes.push_back( np ); + } + + if( errno == 0 && aBoardState != IDF3::FILE_INVALID ) + return true; + + return false; +} + + +// read the component placement section +bool IDF3_BOARD::readBrdPlacement( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ) +{ + IDF3_COMP_OUTLINE_DATA oldata; + + while( oldata.ReadPlaceData( aBoardFile, aBoardState, this ) ); + + if( errno == 0 && aBoardState != IDF3::FILE_INVALID ) + return true; + + ERROR_IDF << "problems reading board PLACEMENT section\n"; + + return false; + +} + + +// read the board HEADER +bool IDF3_BOARD::readBrdHeader( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ) +{ + std::string iline; // the input line + bool isComment; // true if a line just read in is a comment line + std::streampos pos; + int idx = 0; + bool quoted = false; + std::string token; + + // RECORD 1: ".HEADER" must be the very first line + while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() ); + + if( !aBoardFile.good() ) + { + ERROR_IDF; + cerr << "problems reading board header\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: first line must be .HEADER\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !CompareToken( ".HEADER", iline ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: first line must be .HEADER and have no quotes or trailing text\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + // RECORD 2: + // File Type [str]: BOARD_FILE (PANEL_FILE not supported) + // IDF Version Number [float]: must be 3.0 + // Source System [str]: ignored + // Date [str]: ignored + // Board File Version [int]: ignored + while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() ); + + if( !aBoardFile.good() ) + { + ERROR_IDF; + cerr << "problems reading board header, RECORD 2\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: comment within .HEADER section\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: File Type in HEADER section must not be in quotes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !CompareToken( "BOARD_FILE", token ) ) + { + ERROR_IDF; + + if( CompareToken( "PANEL_FILE", token ) ) + { + cerr << "not a board file\n"; + cerr << "* PANEL_FILE is not supported (expecting BOARD_FILE)\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + else + { + cerr << "invalid IDFv3 file\n"; + cerr << "* Expecting string: BOARD_FILE\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification for HEADER section, RECORD 2: no FIELD 2\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: IDF Version must not be in quotes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( token.compare( "3.0" ) && token.compare( "3." ) && token.compare( "3" ) ) + { + ERROR_IDF; + cerr << "unsupported IDF version\n"; + cerr << "* Expecting version to be one of '3.0', '3.', or '3' (value: '" << token << "')\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification for HEADER section, RECORD 2, FIELD 3: no Source System string\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + brdSource = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification for HEADER section, RECORD 2, FIELD 4: no Date string\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + brdDate = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification for HEADER section, RECORD 2, FIELD 5: no Board File Version number\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + std::istringstream istr; + istr.str( token ); + + istr >> brdFileVersion; + + if( istr.fail() ) + { + ERROR_IDF << "invalid Board File Version in header\n"; + cerr << "* Setting default version of 1\n"; + brdFileVersion = 1; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: HEADER section, RECORD 2, FIELD 5: Board File Version must not be in quotes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + // RECORD 3: + // Board Name [str]: stored + // Units [str]: MM or THOU + while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() ); + + if( !aBoardFile.good() ) + { + ERROR_IDF; + cerr << "problems reading board header, RECORD 2\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: comment within .HEADER section\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + boardName = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification for HEADER section, RECORD 3, FIELD 1: no Board Name\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: HEADER section, RECORD 3, FIELD 2: UNIT may not be in quotes\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( "MM", token ) ) + { + unit = IDF3::UNIT_MM; + } + else if( CompareToken( "THOU", token ) ) + { + unit = IDF3::UNIT_THOU; + } + else + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* HEADER section, RECORD 3, FIELD 2: expecting MM or THOU (got '" << token << "')\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + olnBoard.SetUnit( unit ); + + // RECORD 4: + // .END_HEADER + while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() ); + + if( ( !aBoardFile.good() && !aBoardFile.eof() ) || iline.empty() ) + { + ERROR_IDF; + cerr << "problems reading board header, RECORD 4\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: comment within .HEADER section\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !CompareToken( ".END_HEADER", iline ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: expected .END_HEADER (got '" << iline << "')\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + aBoardState = IDF3::FILE_HEADER; + return true; +} + + +// read individual board sections; pay attention to IDFv3 section specifications +bool IDF3_BOARD::readBrdSection( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ) +{ + std::list< std::string > comments; // comments associated with a section + + // Reads in .SECTION_ID or #COMMENTS + // Expected SECTION IDs: + // .BOARD_OUTLINE + // .PANEL_OUTLINE (NOT SUPPORTED) + // .OTHER_OUTLINE + // .ROUTE_OUTLINE + // .PLACE_OUTLINE + // .ROUTE_KEEPOUT + // .VIA_KEEPOUT + // .PLACE_KEEPOUT + // .PLACE_REGION + // .DRILLED_HOLES + // .NOTES (NOT YET SUPPORTED: NOTES SECTION WILL BE SKIPPED FOR NOW) + // .PLACEMENT + std::string iline; // the input line + bool isComment; // true if a line just read in is a comment line + std::streampos pos; + int idx = 0; + bool quoted = false; + std::string token; + + while( aBoardFile.good() ) + { + while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() ); + + if( !aBoardFile.good() ) + { + if( aBoardFile.eof() && aBoardState >= IDF3::FILE_HEADER && aBoardState < IDF3::FILE_INVALID ) + return true; + + ERROR_IDF; + cerr << "problems reading board section\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + comments.push_back( iline ); + continue; + } + + // This must be a header + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "problems reading board section\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: quoted string where SECTION HEADER expected\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( ".BOARD_OUTLINE", token ) ) + { + if( aBoardState != IDF3::FILE_HEADER ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: no HEADER section\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !olnBoard.ReadData( aBoardFile, iline ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + olnBoard.AddComment( *its ); + ++its; + } + } + + aBoardState = IDF3::FILE_OUTLINE; + return true; + } + + if( CompareToken( ".PANEL_OUTLINE", token ) ) + { + ERROR_IDF; + cerr << "PANEL_OUTLINE not supported\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( ".OTHER_OUTLINE", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: expecting .BOARD_OUTLINE, have .OTHER_OUTLINE\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + OTHER_OUTLINE* op = new OTHER_OUTLINE; + + if( op == NULL ) + { + ERROR_IDF; + cerr << "could not create OTHER_OUTLINE object\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + op->SetUnit( unit ); + + if( !op->ReadData( aBoardFile, iline ) ) + { + ERROR_IDF; + cerr << "problems reading the OTHER_OUTLINE section\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + if( olnOther.insert( pair(op->GetOutlineIdentifier(), op) ).second == false ) + { + ERROR_IDF; + cerr << "* Violation of specification. Non-unique ID in OTHER_OUTLINE '"; + cerr << op->GetOutlineIdentifier() << "'\n"; + delete op; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + return true; + } + + if( CompareToken( ".ROUTE_OUTLINE", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: expecting .BOARD_OUTLINE, have .ROUTE_OUTLINE\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + ROUTE_OUTLINE* op = new ROUTE_OUTLINE; + + if( op == NULL ) + { + ERROR_IDF; + cerr << "could not create ROUTE_OUTLINE object\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + op->SetUnit( unit ); + + if( !op->ReadData( aBoardFile, iline ) ) + { + ERROR_IDF; + cerr << "problems reading the ROUTE_OUTLINE section\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnRoute.push_back( op ); + + return true; + } + + if( CompareToken( ".PLACE_OUTLINE", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: expecting .BOARD_OUTLINE, have .PLACE_OUTLINE\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + PLACE_OUTLINE* op = new PLACE_OUTLINE; + + if( op == NULL ) + { + ERROR_IDF; + cerr << "could not create PLACE_OUTLINE object\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + op->SetUnit( unit ); + + if( !op->ReadData( aBoardFile, iline ) ) + { + ERROR_IDF; + cerr << "problems reading the PLACE_OUTLINE section\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnPlace.push_back( op ); + + return true; + } + + if( CompareToken( ".ROUTE_KEEPOUT", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: expecting .BOARD_OUTLINE, have .ROUTE_KEEPOUT\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + ROUTE_KO_OUTLINE* op = new ROUTE_KO_OUTLINE; + + if( op == NULL ) + { + ERROR_IDF; + cerr << "could not create ROUTE_KEEPOUT object\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + op->SetUnit( unit ); + + if( !op->ReadData( aBoardFile, iline ) ) + { + ERROR_IDF; + cerr << "problems reading the ROUTE_KEEPOUT section\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnRouteKeepout.push_back( op ); + + return true; + } + + if( CompareToken( ".VIA_KEEPOUT", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: expecting .BOARD_OUTLINE, have .VIA_KEEPOUT\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + VIA_KO_OUTLINE* op = new VIA_KO_OUTLINE; + + if( op == NULL ) + { + ERROR_IDF; + cerr << "could not create VIA_KEEPOUT object\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + op->SetUnit( unit ); + + if( !op->ReadData( aBoardFile, iline ) ) + { + ERROR_IDF; + cerr << "problems reading the VIA_KEEPOUT section\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnViaKeepout.push_back( op ); + + return true; + } + + if( CompareToken( ".PLACE_KEEPOUT", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: expecting .BOARD_OUTLINE, have .PLACE_KEEPOUT\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + PLACE_KO_OUTLINE* op = new PLACE_KO_OUTLINE; + + if( op == NULL ) + { + ERROR_IDF; + cerr << "could not create PLACE_KEEPOUT object\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + op->SetUnit( unit ); + + if( !op->ReadData( aBoardFile, iline ) ) + { + ERROR_IDF; + cerr << "problems reading the PLACE_KEEPOUT section\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnPlaceKeepout.push_back( op ); + + return true; + } + + if( CompareToken( ".PLACE_REGION", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: expecting .BOARD_OUTLINE, have .PLACE_REGION\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + GROUP_OUTLINE* op = new GROUP_OUTLINE; + + if( op == NULL ) + { + ERROR_IDF; + cerr << "could not create PLACE_REGION object\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + op->SetUnit( unit ); + + if( !op->ReadData( aBoardFile, iline ) ) + { + ERROR_IDF; + cerr << "problems reading the PLACE_REGION section\n"; + aBoardState = IDF3::FILE_ERROR; + return false; + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnGroup.insert( pair(op->GetGroupName(), op) ); + + return true; + } + + if( CompareToken( ".DRILLED_HOLES", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: expecting .BOARD_OUTLINE, have .DRILLED_HOLES\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !readBrdDrills( aBoardFile, aBoardState ) ) + { + if( !aBoardFile.good() || aBoardState == IDF3::FILE_INVALID ) + { + ERROR_IDF << "could not read board DRILLED HOLES section\n"; + return false; + } + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + drillComments.push_back( *its ); + ++its; + } + } + + return true; + } + + if( CompareToken( ".NOTES", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: expecting .BOARD_OUTLINE, have .NOTES\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !readBrdNotes( aBoardFile, aBoardState ) ) + { + if( !aBoardFile.good() || aBoardState == IDF3::FILE_INVALID ) + { + ERROR_IDF << "could not read board NOTES section\n"; + return false; + } + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + noteComments.push_back( *its ); + ++its; + } + } + + return true; + } + + if( CompareToken( ".PLACEMENT", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + { + ERROR_IDF; + cerr << "invalid IDFv3 file\n"; + cerr << "* Violation of specification: expecting .BOARD_OUTLINE, have .PLACEMENT\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + + if( !readBrdPlacement( aBoardFile, aBoardState ) ) + { + if( !aBoardFile.good() || aBoardState == IDF3::FILE_INVALID ) + { + ERROR_IDF << "could not read board PLACEMENT section\n"; + return false; + } + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + placeComments.push_back( *its ); + ++its; + } + } + + return true; + } + } // while( aBoardFile.good() + + if( !aBoardFile.good() ) + { + if( !aBoardFile.eof() || aBoardState < IDF3::FILE_OUTLINE || aBoardState >= IDF3::FILE_INVALID ) + { + ERROR_IDF; + cerr << "problems reading board section\n"; + aBoardState = IDF3::FILE_INVALID; + return false; + } + } + + return true; +} // readBrdSection() + + +// read the board file data +bool IDF3_BOARD::readBoardFile( const std::string& aFileName ) +{ + std::ifstream brd; + + brd.open( aFileName.c_str(), std::ios_base::in ); + + if( !brd.is_open() ) + { + ERROR_IDF; + cerr << "could not open file: '" << aFileName << "'\n"; + return false; + } + + std::string iline; // the input line + bool isComment; // true if a line just read in is a comment line + std::streampos pos; + IDF3::FILE_STATE state = IDF3::FILE_START; + + // note: as per IDFv3 specification: + // "The Header section must be the first section in the file, the second + // section must be the Outline section, and the last section must be the + // Placement section. All other sections may be in any order." + + // further notes: Except for the HEADER section, sections may be preceeded by + // comment lines which will be copied back out on write(). No comments may + // be associated with the board file itself since the only logical location + // for unambiguous association is at the end of the file, which is inconvenient + // for large files. + + if( !readBrdHeader( brd, state ) ) + { + ERROR_IDF; + cerr << "could not find a valid header\n"; + brd.close(); + return false; + } + + // read the various sections + while( readBrdSection( brd, state ) && state != IDF3::FILE_PLACEMENT && !brd.eof() ); + + if( state == IDF3::FILE_INVALID ) + { + brd.close(); + ERROR_IDF; + cerr << "problems reading file: '" << aFileName << "'\n"; + return false; + } + + if( !brd.good() ) + { + // check if we have valid data + if( brd.eof() && state >= IDF3::FILE_OUTLINE && state < IDF3::FILE_INVALID ) + { + brd.close(); + return true; + } + + brd.close(); + ERROR_IDF; + cerr << "problems reading file: '" << aFileName << "'\n"; + return false; + } + + if( brd.good() && state == IDF3::FILE_PLACEMENT ) + { + // read in any trailing lines and report on ignored comments (minor fault) + // and any non-comment item (non-compliance with IDFv3) + while( brd.good() ) + { + while( !FetchIDFLine( brd, iline, isComment, pos ) && brd.good() ); + + // normally this is a fault but we have all the data in accordance with specs + if( ( !brd.good() && !brd.eof() ) || iline.empty() ) + break; + + if( isComment ) + { + ERROR_IDF << "[warning]: trailing comments after PLACEMENT\n"; + } + else + { + ERROR_IDF << "invalid IDF3 file\n"; + cerr << "* Violation of specification: non-comment lines after PLACEMENT section\n"; + Clear(); + brd.close(); + return false; + } + } + } + + brd.close(); + return true; +} // readBoardFile() + + +// read the library sections (outlines) +bool IDF3_BOARD::readLibSection( std::ifstream& aLibFile, IDF3::FILE_STATE& aLibState, IDF3_BOARD* aBoard ) +{ + if( aBoard == NULL ) + { + ERROR_IDF << "BUG: invoked with NULL reference aBoard\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + std::list< std::string > comments; // comments associated with a section + + // Reads in .ELECTRICAL, .MECHANICAL or #COMMENTS + std::string iline; // the input line + bool isComment; // true if a line just read in is a comment line + std::streampos pos; + int idx = 0; + bool quoted = false; + std::string token; + IDF3_COMP_OUTLINE *pout = new IDF3_COMP_OUTLINE; + + while( aLibFile.good() ) + { + while( !FetchIDFLine( aLibFile, iline, isComment, pos ) && aLibFile.good() ); + + if( !aLibFile.good() && !aLibFile.eof() ) + { + ERROR_IDF; + cerr << "problems reading library section\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + // no data was read; this only happens at eof() + if( iline.empty() ) + return true; + + if( isComment ) + { + comments.push_back( iline ); + continue; + } + + // This must be a header + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "problems reading library section\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification: quoted string where .ELECTRICAL or .MECHANICAL expected\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( CompareToken( ".ELECTRICAL", token ) || CompareToken( ".MECHANICAL", token ) ) + { + if( !pout->ReadData( aLibFile, token ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library [faulty section]\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( !comments.empty() ) + { + std::list::iterator its = comments.begin(); + std::list::iterator ite = comments.end(); + + while( its != ite ) + { + pout->AddComment( *its ); + ++its; + } + } + + IDF3_COMP_OUTLINE* cop = aBoard->GetComponentOutline( pout->GetUID() ); + + if( cop == NULL ) + { + compOutlines.insert( pair( pout->GetUID(), pout ) ); + } + else + { + ERROR_IDF; + cerr << "duplicate Component Outline: '" << pout->GetUID() << "'\n"; + delete pout; + } + + return true; + } + else + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Expecting .ELECTRICAL or .MECHANICAL, got '" << token << "'\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + } + + ERROR_IDF; + cerr << "problems reading library section\n"; + aLibState = IDF3::FILE_INVALID; + return false; +} + + +// read the library HEADER +bool IDF3_BOARD::readLibHeader( std::ifstream& aLibFile, IDF3::FILE_STATE& aLibState ) +{ + std::string iline; // the input line + bool isComment; // true if a line just read in is a comment line + std::streampos pos; + int idx = 0; + bool quoted = false; + std::string token; + + // RECORD 1: ".HEADER" must be the very first line + while( !FetchIDFLine( aLibFile, iline, isComment, pos ) && aLibFile.good() ); + + if( !aLibFile.good() ) + { + ERROR_IDF; + cerr << "problems reading library header\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification: first line must be .HEADER\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( !CompareToken( ".HEADER", iline ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification: first line must be .HEADER and have no quotes or trailing text\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + // RECORD 2: + // File Type [str]: LIBRARY_FILE + // IDF Version Number [float]: must be 3.0 + // Source System [str]: ignored + // Date [str]: ignored + // Library File Version [int]: ignored + while( !FetchIDFLine( aLibFile, iline, isComment, pos ) && aLibFile.good() ); + + if( !aLibFile.good() ) + { + ERROR_IDF; + cerr << "problems reading library header, RECORD 2\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification: comment within .HEADER section\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification: File Type in HEADER section must not be in quotes\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( !CompareToken( "LIBRARY_FILE", token ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Expecting string: LIBRARY_FILE (got '" << token << "')\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification for HEADER section, RECORD 2: no FIELD 2\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification: IDF Version must not be in quotes\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( token.compare( "3.0" ) && token.compare( "3." ) && token.compare( "3" ) ) + { + ERROR_IDF; + cerr << "unsupported IDF library version\n"; + cerr << "* Expecting version to be one of '3.0', '3.', or '3' (value: '" << token << "')\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification for HEADER section, RECORD 2, FIELD 3: no Source System string\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + libSource = token; + + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification for HEADER section, RECORD 2, FIELD 4: no Date string\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + libDate = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification for HEADER section, RECORD 2, FIELD 5: no Board File Version number\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + std::istringstream istr; + istr.str( token ); + + istr >> libFileVersion; + + if( istr.fail() ) + { + ERROR_IDF << "invalid Library File Version in header\n"; + cerr << "* Setting default version of 1\n"; + libFileVersion = 1; + } + + if( quoted ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification: HEADER section, RECORD 2, FIELD 5: Library File Version must not be in quotes\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + // RECORD 3: + // .END_HEADER + while( !FetchIDFLine( aLibFile, iline, isComment, pos ) && aLibFile.good() ); + + if( ( !aLibFile.good() && !aLibFile.eof() ) || iline.empty() ) + { + ERROR_IDF; + cerr << "problems reading library header, RECORD 3\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( isComment ) + { + ERROR_IDF; + cerr << "invalid IDFv3 library\n"; + cerr << "* Violation of specification: comment within .HEADER section\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + if( !CompareToken( ".END_HEADER", iline ) ) + { + ERROR_IDF; + cerr << "invalid IDFv3 header\n"; + cerr << "* Violation of specification: expected .END_HEADER (got '" << iline << "')\n"; + aLibState = IDF3::FILE_INVALID; + return false; + } + + aLibState = IDF3::FILE_HEADER; + return true; +} + + +// read the library file data +bool IDF3_BOARD::readLibFile( const std::string& aFileName ) +{ + std::ifstream lib; + + lib.open( aFileName.c_str(), std::ios_base::in ); + + if( !lib.is_open() ) + { + ERROR_IDF; + cerr << "could not open file: '" << aFileName << "'\n"; + return false; + } + + IDF3::FILE_STATE state = IDF3::FILE_START; + + if( !readLibHeader( lib, state ) ) + { + ERROR_IDF; + cerr << "[IDF library] could not find a valid header\n"; + lib.close(); + return false; + } + + // read the library sections + while( readLibSection( lib, state, this ) && lib.good() ); + + if( state <= IDF3::FILE_START || state >= IDF3::FILE_INVALID ) + { + lib.close(); + ERROR_IDF; + cerr << "problems reading file: '" << aFileName << "'\n"; + return false; + } + + lib.close(); + return true; +} + + +bool IDF3_BOARD::ReadFile( const wxString& aFullFileName ) +{ + // 1. Check that the file extension is 'emn' + // 2. Check if a file with extension 'emp' exists and read it + // 3. Open the specified filename and read it + + std::string fname = TO_UTF8( aFullFileName ); + + wxFileName brdname( aFullFileName ); + wxFileName libname( aFullFileName ); + + brdname.SetExt( wxT( "emn" ) ); + libname.SetExt( wxT( "emp" ) ); + + std::string bfname = TO_UTF8( aFullFileName ); + + if( !brdname.IsOk() ) + { + ERROR_IDF; + cerr << "invalid file name: '" << bfname << "'\n"; + return false; + } + + if( !brdname.IsOk() ) + { + ERROR_IDF; + cerr << "invalid file name: '" << bfname << "'\n"; + return false; + } + + if( !brdname.FileExists() ) + { + ERROR_IDF; + cerr << "no such file: '" << bfname << "'\n"; + return false; + } + + if( !brdname.IsFileReadable() ) + { + ERROR_IDF; + cerr << "cannot read file: '" << bfname << "'\n"; + return false; + } + + bfname = TO_UTF8( brdname.GetFullPath() ); + std::string lfname = TO_UTF8( libname.GetFullPath() ); + + if( !libname.FileExists() ) + { + ERROR_IDF; + cerr << "no associated library file (*.emp)\n"; + } + else if( !libname.IsFileReadable() ) + { + ERROR_IDF; + cerr << "cannot read library file: '" << lfname << "'\n"; + } + else + { + // read the library file before proceeding + if( !readLibFile( lfname ) ) + { + ERROR_IDF; + cerr << "problems reading library file: '" << lfname << "'\n"; + return false; + } + } + + // read the board file + if( !readBoardFile( bfname ) ) + { + Clear(); + ERROR_IDF; + cerr << "problems reading board file: '" << lfname << "'\n"; + return false; + } + + return true; +} + +// write the library file data +bool IDF3_BOARD::writeLibFile( const std::string& aFileName ) +{ + std::ofstream lib; + + lib.open( aFileName.c_str(), std::ios_base::out ); + + if( !lib.is_open() ) + { + ERROR_IDF; + cerr << "could not open library file: '" << aFileName << "'\n"; + return false; + } + + wxDateTime tdate( time( NULL ) ); + + if( idfSource.empty() ) + idfSource = "KiCad-IDF Framework"; + + ostringstream fileDate; + fileDate << setfill( '0' ) << setw(4) << tdate.GetYear(); + fileDate << "/" << setw(2) << tdate.GetMonth() << "/" << tdate.GetDay(); + fileDate << "." << tdate.GetHour() << ":" << tdate.GetMinute() << ":" << tdate.GetSecond(); + libDate = fileDate.str(); + + lib << ".HEADER\n"; + lib << "LIBRARY_FILE 3.0 \"Created by " << idfSource; + lib << "\" " << libDate << " " << (++libFileVersion) << "\n"; + lib << ".END_HEADER\n\n"; + + std::map< std::string, IDF3_COMP_OUTLINE*>::iterator its = compOutlines.begin(); + std::map< std::string, IDF3_COMP_OUTLINE*>::iterator ite = compOutlines.end(); + + while( its != ite ) + { + its->second->WriteData( lib ); + ++its; + } + + bool ok = !lib.fail(); + + lib.close(); + + return ok; +} + +// write the board file data +bool IDF3_BOARD::writeBoardFile( const std::string& aFileName ) +{ + std::ofstream brd; + + brd.open( aFileName.c_str(), std::ios_base::out ); + + if( !brd.is_open() ) + { + ERROR_IDF; + cerr << "could not open board file: '" << aFileName << "'\n"; + return false; + } + + wxDateTime tdate( time( NULL ) ); + + if( idfSource.empty() ) + idfSource = "KiCad-IDF Framework"; + + ostringstream fileDate; + fileDate << setfill( '0' ) << setw(4) << tdate.GetYear(); + fileDate << "/" << setw(2) << tdate.GetMonth() << "/" << tdate.GetDay(); + fileDate << "." << tdate.GetHour() << ":" << tdate.GetMinute() << ":" << tdate.GetSecond(); + brdDate = fileDate.str(); + + brd << ".HEADER\n"; + brd << "BOARD_FILE 3.0 \"Created by " << idfSource; + brd << "\" " << brdDate << " " << (++brdFileVersion) << "\n"; + + if( boardName.empty() ) + brd << "\"BOARD WITH NO NAME\" "; + else + brd << "\"" << boardName << "\" "; + + brd << setw(1) << setfill( ' ' ); + + if( unit == IDF3::UNIT_MM ) + brd << "MM\n"; + else + brd << "THOU\n"; + + brd << ".END_HEADER\n\n"; + + // write the BOARD_OUTLINE + if( !olnBoard.WriteData( brd ) ) + { + ERROR_IDF << "problems writing BOARD OUTLINE\n"; + brd.close(); + return false; + } + + // OTHER outlines + do + { + std::map::iterator its = olnOther.begin(); + std::map::iterator ite = olnOther.end(); + + while( (its != ite) && its->second->WriteData( brd ) ) ++its; + + } while( 0 ); + + // ROUTE outlines + do + { + std::list::iterator its = olnRoute.begin(); + std::list::iterator ite = olnRoute.end(); + + while( (its != ite) && (*its)->WriteData( brd ) ) ++its; + + } while( 0 ); + + // PLACEMENT outlines + do + { + std::list::iterator its = olnPlace.begin(); + std::list::iterator ite = olnPlace.end(); + + while( (its != ite) && (*its)->WriteData( brd ) ) ++its; + + } while( 0 ); + + // ROUTE KEEPOUT outlines + do + { + std::list::iterator its = olnRouteKeepout.begin(); + std::list::iterator ite = olnRouteKeepout.end(); + + while( (its != ite) && (*its)->WriteData( brd ) ) ++its; + + } while( 0 ); + + // VIA KEEPOUT outlines + do + { + std::list::iterator its = olnViaKeepout.begin(); + std::list::iterator ite = olnViaKeepout.end(); + + while( (its != ite) && (*its)->WriteData( brd ) ) ++its; + + } while( 0 ); + + // PLACE KEEPOUT outlines + do + { + std::list::iterator its = olnPlaceKeepout.begin(); + std::list::iterator ite = olnPlaceKeepout.end(); + + while( (its != ite) && (*its)->WriteData( brd ) ) ++its; + + } while( 0 ); + + // PLACEMENT GROUP outlines + do + { + std::map::iterator its = olnGroup.begin(); + std::map::iterator ite = olnGroup.end(); + + while( (its != ite) && its->second->WriteData( brd ) ) ++its; + + } while( 0 ); + + // Drilled holes + do + { + std::list::iterator itds = drillComments.begin(); + std::list::iterator itde = drillComments.end(); + + while( itds != itde ) + { + brd << "# " << *itds << "\n"; + ++itds; + } + + brd << ".DRILLED_HOLES\n"; + + std::list::iterator itbs = board_drills.begin(); + std::list::iterator itbe = board_drills.end(); + + while( itbs != itbe ) + { + (*itbs)->Write( brd, unit ); + ++itbs; + } + + std::map< std::string, IDF3_COMPONENT*>::iterator itcs = components.begin(); + std::map< std::string, IDF3_COMPONENT*>::iterator itce = components.end(); + + while( itcs != itce ) + { + itcs->second->WriteDrillData( brd ); + ++itcs; + } + + brd << ".END_DRILLED_HOLES\n\n"; + } while( 0 ); + + // Notes + if( !notes.empty() ) + { + std::list::iterator itncs = noteComments.begin(); + std::list::iterator itnce = noteComments.end(); + + while( itncs != itnce ) + { + brd << "# " << *itncs << "\n"; + ++itncs; + } + + brd << ".NOTES\n"; + + std::list::iterator itns = notes.begin(); + std::list::iterator itne = notes.end(); + + while( itns != itne ) + { + (*itns)->WriteNote( brd, unit ); + ++itns; + } + + brd << ".END_NOTES\n\n"; + + } + + // Placement + if( !components.empty() ) + { + std::list::iterator itpcs = placeComments.begin(); + std::list::iterator itpce = placeComments.end(); + + while( itpcs != itpce ) + { + brd << "# " << *itpcs << "\n"; + ++itpcs; + } + + std::map< std::string, IDF3_COMPONENT*>::iterator itcs = components.begin(); + std::map< std::string, IDF3_COMPONENT*>::iterator itce = components.end(); + + brd << ".PLACEMENT\n"; + + while( itcs != itce ) + { + itcs->second->WritePlaceData( brd ); + ++itcs; + } + + brd << ".END_PLACEMENT\n"; + } + + bool ok = !brd.fail(); + brd.close(); + + return ok; +} + +bool IDF3_BOARD::WriteFile( const wxString& aFullFileName, bool aUnitMM, bool aForceUnitFlag ) +{ + if( aUnitMM == IDF3::UNIT_MM ) + setUnit( IDF3::UNIT_MM, aForceUnitFlag ); + else + setUnit( IDF3::UNIT_THOU, aForceUnitFlag ); + + // 1. Check that the file extension is 'emn' + // 2. Write the *.emn file according to the IDFv3 spec + // 3. Write the *.emp file according to the IDFv3 spec + + std::string fname = TO_UTF8( aFullFileName ); + + wxFileName brdname( aFullFileName ); + wxFileName libname( aFullFileName ); + + brdname.SetExt( wxT( "emn" ) ); + libname.SetExt( wxT( "emp" ) ); + + std::string bfname = TO_UTF8( aFullFileName ); + + if( !brdname.IsOk() ) + { + ERROR_IDF; + cerr << "invalid file name: '" << bfname << "'\n"; + errno = EINVAL; + return false; + } + + if( brdname.FileExists() && !brdname.IsFileWritable() ) + { + ERROR_IDF; + cerr << "cannot overwrite existing board file\n"; + cerr << "* Filename: '" << bfname << "'\n"; + errno = EACCES; + return false; + } + + bfname = TO_UTF8( brdname.GetFullPath() ); + std::string lfname = TO_UTF8( libname.GetFullPath() ); + + if( libname.FileExists() && !libname.IsFileWritable() ) + { + ERROR_IDF; + cerr << "cannot overwrite existing library file\n"; + cerr << "* Filename: '" << lfname << "'\n"; + errno = EACCES; + return false; + } + + if( !writeLibFile( lfname ) ) + { + ERROR_IDF; + cerr << "problems writing library file: '" << lfname << "'\n"; + return false; + } + + if( !writeBoardFile( bfname ) ) + { + ERROR_IDF; + cerr << "problems writing board file: '" << bfname << "'\n"; + return false; + } + + return true; +} + + +const std::string& IDF3_BOARD::GetIDFSource( void ) +{ + return idfSource; +} + + +void IDF3_BOARD::SetIDFSource( const std::string& aIDFSource ) +{ + idfSource = aIDFSource; + return; +} + +const std::string& IDF3_BOARD::GetBoardSource( void ) +{ + return brdSource; +} + +const std::string& IDF3_BOARD::GetLibrarySource( void ) +{ + return libSource; +} + +const std::string& IDF3_BOARD::GetBoardDate( void ) +{ + return brdDate; +} + +const std::string& IDF3_BOARD::GetLibraryDate( void ) +{ + return libDate; +} + +int IDF3_BOARD::GetBoardVersion( void ) +{ + return brdFileVersion; +} + +bool IDF3_BOARD::SetBoardVersion( int aVersion ) +{ + if( aVersion < 0 ) + return false; + + brdFileVersion = aVersion; + + return true; +} + + +int IDF3_BOARD::GetLibraryVersion( void ) +{ + return libFileVersion; +} + + +bool IDF3_BOARD::SetLibraryVersion( int aVersion ) +{ + if( aVersion < 0 ) + return false; + + libFileVersion = aVersion; + + return true; +} + + +double IDF3_BOARD::GetUserScale( void ) +{ + return userScale; +} + + +bool IDF3_BOARD::SetUserScale( double aScaleFactor ) +{ + if( aScaleFactor <= 0.0 ) + { + ERROR_IDF << "user scale factor must be > 0\n"; + return false; + } + + userScale = aScaleFactor; + return true; +} + +int IDF3_BOARD::GetUserPrecision( void ) +{ + return userPrec; +} + +bool IDF3_BOARD::SetUserPrecision( int aPrecision ) +{ + if( aPrecision < 0 || aPrecision > 8 ) + return false; + + userPrec = aPrecision; + return true; +} + + +void IDF3_BOARD::GetUserOffset( double& aXoff, double& aYoff ) +{ + aXoff = userXoff; + aYoff = userYoff; + return; +} + + +void IDF3_BOARD::SetUserOffset( double aXoff, double aYoff ) +{ + userXoff = aXoff; + userYoff = aYoff; + return; +} + + +bool IDF3_BOARD::AddBoardOutline( IDF_OUTLINE* aOutline ) +{ + return olnBoard.AddOutline( aOutline ); +} + + +bool IDF3_BOARD::DelBoardOutline( IDF_OUTLINE* aOutline ) +{ + return olnBoard.DelOutline( aOutline ); +} + + +bool IDF3_BOARD::DelBoardOutline( size_t aIndex ) +{ + return olnBoard.DelOutline( aIndex ); +} + + +size_t IDF3_BOARD::GetBoardOutlinesSize( void ) +{ + return olnBoard.OutlinesSize(); +} + + +BOARD_OUTLINE* IDF3_BOARD::GetBoardOutline( void ) +{ + return &olnBoard; +} + + +const std::list< IDF_OUTLINE* >*const IDF3_BOARD::GetBoardOutlines( void ) +{ + return olnBoard.GetOutlines(); +} + + +IDF_DRILL_DATA* IDF3_BOARD::AddBoardDrill( double aDia, double aXpos, double aYpos, + IDF3::KEY_PLATING aPlating, + const std::string aHoleType, + IDF3::KEY_OWNER aOwner ) +{ + IDF_DRILL_DATA* drill = new IDF_DRILL_DATA( aDia, aXpos, aYpos, aPlating, + "BOARD", aHoleType, aOwner ); + + if( drill != NULL ) + board_drills.push_back( drill ); + + return drill; +} + +IDF_DRILL_DATA* IDF3_BOARD::AddDrill( IDF_DRILL_DATA* aDrilledHole ) +{ + if( !aDrilledHole ) + return NULL; + + if( CompareToken( "PANEL", aDrilledHole->GetDrillRefDes() ) ) + { + ERROR_IDF; + cerr << "\n*BUG: PANEL drilled holes are not supported\n"; + return NULL; + } + + if( CompareToken( "BOARD", aDrilledHole->GetDrillRefDes() ) ) + { + board_drills.push_back( aDrilledHole ); + return aDrilledHole; + } + + return addCompDrill( aDrilledHole ); +} + + +bool IDF3_BOARD::DelBoardDrill( double aDia, double aXpos, double aYpos ) +{ + std::list::iterator sp = board_drills.begin(); + std::list::iterator ep = board_drills.end(); + bool rval = false; + + while( sp != ep ) + { + if( (*sp)->Matches( aDia, aXpos, aYpos ) ) + { + rval = true; + delete *sp; + sp = board_drills.erase( sp ); + continue; + } + ++sp; + } + + return rval; +} + + +// a slot is a deficient representation of a kicad slotted hole; +// it is usually associated with a component but IDFv3 does not +// provide for such an association. +bool IDF3_BOARD::AddSlot( double aWidth, double aLength, double aOrientation, double aX, double aY ) +{ + if( aWidth < IDF_MIN_DIA_MM ) + return true; + + IDF_POINT c[2]; // centers + IDF_POINT pt[4]; + + // make sure the user isn't giving us dud information + if( aLength < aWidth ) + std::swap( aLength, aWidth ); + + if( aLength == aWidth ) + { + ERROR_IDF; + cerr << "length == width (" << aWidth << ")\n"; + return false; + } + + 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 = new IDF_OUTLINE; + + if( outline == NULL ) + { + ERROR_IDF; + cerr << "could not create an outline object\n"; + return false; + } + + // 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 AddBoardOutline( outline ); +} + + +IDF_DRILL_DATA* IDF3_BOARD::addCompDrill( double aDia, double aXpos, double aYpos, + IDF3::KEY_PLATING aPlating, + const std::string aHoleType, + IDF3::KEY_OWNER aOwner, + const std::string& aRefDes ) +{ + // first find the matching component; if it doesn't exist we must create it somehow - + // question is, do we need a component outline at this stage or can those be added later? + // + // Presumably we can create a component with no outline and add the outlines later. + // If a component is created and an outline specified but the outline is not loaded, + // we're screwed if (a) we have already read the library file (*.emp) or (b) we don't + // know the filename + + std::string refdes = aRefDes; + + // note: for components 'NOREFDES' would be assigned a Unique ID, but for holes + // there is no way of associating the hole with the correct entity (if any) + // so a hole added with "NOREFDES" goes to a generic component "NOREFDES" + if( refdes.empty() ) + refdes = "NOREFDES"; + + // check if the target is BOARD or PANEL + if( CompareToken( "BOARD", refdes ) ) + return AddBoardDrill( aDia, aXpos, aYpos, aPlating, aHoleType, aOwner ); + + if( CompareToken( "PANEL", refdes ) ) + { + ERROR_IDF; + cerr << "PANEL data not supported\n"; + return NULL; + } + + std::map::iterator ref = components.find( refdes ); + + if( ref == components.end() ) + { + // create the item + IDF3_COMPONENT* comp = new IDF3_COMPONENT( this ); + + if( comp == NULL ) + { + ERROR_IDF; + cerr << "could not create new component object\n"; + return NULL; + } + + comp->SetParent( this ); + comp->SetRefDes( refdes ); + ref = components.insert( std::pair< std::string, IDF3_COMPONENT*> ( comp->GetRefDes(), comp ) ).first; + } + + // add the drill + return ref->second->AddDrill( aDia, aXpos, aYpos, aPlating, aHoleType, aOwner ); +} + + +IDF_DRILL_DATA* IDF3_BOARD::addCompDrill( IDF_DRILL_DATA* aDrilledHole ) +{ + if( !aDrilledHole ) + return NULL; + + if( CompareToken( "PANEL", aDrilledHole->GetDrillRefDes() ) ) + { + ERROR_IDF; + cerr << "PANEL data not supported\n"; + return NULL; + } + + std::map::iterator ref = components.find( aDrilledHole->GetDrillRefDes() ); + + if( ref == components.end() ) + { + // create the item + IDF3_COMPONENT* comp = new IDF3_COMPONENT( this ); + + if( comp == NULL ) + { + ERROR_IDF; + cerr << "could not create new component object\n"; + return NULL; + } + + comp->SetParent( this ); + comp->SetRefDes( aDrilledHole->GetDrillRefDes() ); + ref = components.insert( std::pair< std::string, IDF3_COMPONENT*> ( comp->GetRefDes(), comp ) ).first; + } + + // add the drill + return ref->second->AddDrill( aDrilledHole ); +} + + +bool IDF3_BOARD::delCompDrill( double aDia, double aXpos, double aYpos, std::string aRefDes ) +{ + std::map::iterator ref = components.find( aRefDes ); + + if( ref == components.end() ) + return false; + + return ref->second->DelDrill( aDia, aXpos, aYpos ); +} + + +bool IDF3_BOARD::AddComponent( IDF3_COMPONENT* aComponent ) +{ + if( !aComponent ) + { + ERROR_IDF << "Invalid component pointer (NULL)\n"; + return false; + } + + if( components.insert( std::pair + ( aComponent->GetRefDes(), aComponent ) ).second == false ) + { + ERROR_IDF << "Duplicate RefDes ('" << aComponent->GetRefDes() << "')\n"; + return false; + } + + return true; +} + + +bool IDF3_BOARD::DelComponent( IDF3_COMPONENT* aComponent ) +{ + if( !aComponent ) + { + ERROR_IDF << "Invalid component pointer (NULL)\n"; + return false; + } + + std::map::iterator it = + components.find( aComponent->GetRefDes() ); + + if( it == components.end() ) + return false; + + delete it->second; + components.erase( it ); + + return true; +} + + +bool IDF3_BOARD::DelComponent( size_t aIndex ) +{ + if( aIndex >= components.size() ) + { + ERROR_IDF << "index (" << aIndex << ") >= components size (" + << components.size() << ")\n"; + return false; + } + + std::map::iterator it = components.begin(); + + while( aIndex-- > 0 ) ++it; + + delete it->second; + components.erase( it ); + + return false; +} + + +size_t IDF3_BOARD::GetComponentsSize( void ) +{ + return components.size(); +} + + +std::map< std::string, IDF3_COMPONENT* >*const IDF3_BOARD::GetComponents( void ) +{ + return &components; +} + + +IDF3_COMPONENT* IDF3_BOARD::FindComponent( std::string aRefDes ) +{ + std::map::iterator it = components.find( aRefDes ); + + if( it == components.end() ) + return NULL; + + return it->second; +} + + +// returns a pointer to a component outline object or NULL +// if the object doesn't exist +IDF3_COMP_OUTLINE* IDF3_BOARD::GetComponentOutline( const std::string aGeomName, + const std::string aPartName, + wxString aFullFileName ) +{ + std::ostringstream ostr; + ostr << aGeomName << "_" << aPartName; + + IDF3_COMP_OUTLINE* cp = GetComponentOutline( ostr.str() ); + + if( cp != NULL ) + return cp; + + std::string fname = TO_UTF8( aFullFileName ); + + cp = new IDF3_COMP_OUTLINE; + + if( cp == NULL ) + { + ERROR_IDF; + cerr << "failed to create outline with UID '" << aGeomName << "_"; + cerr << aPartName << "'\n"; + cerr << "* filename: '" << fname << "'\n"; + return NULL; + } + + wxFileName idflib( aFullFileName ); + + if( !idflib.IsOk() ) + { + ERROR_IDF; + cerr << "invalid file name: '" << fname << "'\n"; + delete cp; + return NULL; + } + + if( !idflib.FileExists() ) + { + ERROR_IDF; + cerr << "no such file: '" << fname << "'\n"; + delete cp; + return NULL; + } + + if( !idflib.IsFileReadable() ) + { + ERROR_IDF; + cerr << "cannot read file: '" << fname << "'\n"; + delete cp; + return NULL; + } + + std::ifstream model; + + model.open( fname.c_str(), std::ios_base::in ); + + if( !model.is_open() ) + { + ERROR_IDF; + cerr << "could not open file: '" << fname << "'\n"; + delete cp; + return NULL; + } + + std::string iline; // the input line + bool isComment; // true if a line just read in is a comment line + std::streampos pos; + + while( true ) + { + while( !FetchIDFLine( model, iline, isComment, pos ) && model.good() ); + + if( !model.good() ) + { + ERROR_IDF; + cerr << "problems reading file: '" << fname << "'\n"; + delete cp; + model.close(); + return NULL; + } + + // accept comment lines, .ELECTRICAL, or .MECHANICAL only + if( isComment ) + { + cp->AddComment( iline ); + continue; + } + + if( CompareToken( ".ELECTRICAL", iline ) || CompareToken( ".MECHANICAL", iline ) ) + { + if( !cp->ReadData( model, iline ) ) + { + ERROR_IDF; + cerr << "problems reading file: '" << fname << "'\n"; + delete cp; + model.close(); + return NULL; + } + else + { + break; + } + } + else + { + ERROR_IDF << "faulty IDF component definition\n"; + cerr << "* Expecting .ELECTRICAL or .MECHANICAL, got '" << iline << "'\n"; + cerr << "* File: '" << fname << "'\n"; + delete cp; + model.close(); + return NULL; + } + } // while( true ) + + model.close(); + + return cp; +} + + +// returns a pointer to the component outline object with the +// unique ID aComponentID +IDF3_COMP_OUTLINE* IDF3_BOARD::GetComponentOutline( std::string aComponentID ) +{ + std::map< std::string, IDF3_COMP_OUTLINE*>::iterator its = compOutlines.find( aComponentID ); + + if( its != compOutlines.end() ) + return its->second; + + return NULL; +} + + +// returns a pointer to the outline which is substituted +// whenever a true outline cannot be found or is defective +IDF3_COMP_OUTLINE* IDF3_BOARD::GetInvalidOutline( const std::string& aGeomName, const std::string& aPartName ) +{ + std::string uid; + bool empty = false; + + if( aGeomName.empty() && aPartName.empty() ) + { + uid = "NOGEOM_NOPART"; + empty = true; + } + else + { + uid = aGeomName + "_" + aPartName; + } + + IDF3_COMP_OUTLINE* cp = GetComponentOutline( uid ); + + if( cp != NULL ) + return cp; + + cp = new IDF3_COMP_OUTLINE; + + if( cp == NULL ) + { + ERROR_IDF << "could not create new outline\n"; + return NULL; + } + + if( empty ) + cp->CreateDefaultOutline( "", "" ); + else + cp->CreateDefaultOutline( aGeomName, aPartName ); + + compOutlines.insert( pair(cp->GetUID(), cp) ); + + return cp; +} + + +// clears all data +void IDF3_BOARD::Clear( void ) +{ + // preserve the board thickness + double thickness = olnBoard.GetThickness(); + + idfSource.clear(); + brdSource.clear(); + libSource.clear(); + brdDate.clear(); + libDate.clear(); + brdFileVersion = 0; + libFileVersion = 0; + + // delete comment lists + noteComments.clear(); + drillComments.clear(); + placeComments.clear(); + + // delete notes + while( !notes.empty() ) + { + delete notes.front(); + notes.pop_front(); + } + + // delete drill list + do + { + std::list::iterator ds = board_drills.begin(); + std::list::iterator de = board_drills.end(); + + while( ds != de ) + { + delete *ds; + ++ds; + } + + board_drills.clear(); + } while(0); + + + // delete components + do + { + std::map::iterator cs = components.begin(); + std::map::iterator ce = components.end(); + + while( cs != ce ) + { + delete cs->second; + ++cs; + } + + components.clear(); + } while(0); + + + // delete component outlines + do + { + std::map::iterator cs = compOutlines.begin(); + std::map::iterator ce = compOutlines.end(); + + while( cs != ce ) + { + delete cs->second; + ++cs; + } + + compOutlines.clear(); + } while(0); + + + // delete OTHER outlines + do + { + std::map::iterator os = olnOther.begin(); + std::map::iterator oe = olnOther.end(); + + while( os != oe ) + { + delete os->second; + ++os; + } + + olnOther.clear(); + } while(0); + + + // delete ROUTE outlines + do + { + std::list::iterator os = olnRoute.begin(); + std::list::iterator oe = olnRoute.end(); + + while( os != oe ) + { + delete *os; + ++os; + } + + olnRoute.clear(); + } while(0); + + + // delete PLACE outlines + do + { + std::list::iterator os = olnPlace.begin(); + std::list::iterator oe = olnPlace.end(); + + while( os != oe ) + { + delete *os; + ++os; + } + + olnPlace.clear(); + } while(0); + + + // delete ROUTE KEEPOUT outlines + do + { + std::list::iterator os = olnRouteKeepout.begin(); + std::list::iterator oe = olnRouteKeepout.end(); + + while( os != oe ) + { + delete *os; + ++os; + } + + olnRouteKeepout.clear(); + } while(0); + + + // delete VIA KEEPOUT outlines + do + { + std::list::iterator os = olnViaKeepout.begin(); + std::list::iterator oe = olnViaKeepout.end(); + + while( os != oe ) + { + delete *os; + ++os; + } + + olnViaKeepout.clear(); + } while(0); + + + // delete PLACEMENT KEEPOUT outlines + do + { + std::list::iterator os = olnPlaceKeepout.begin(); + std::list::iterator oe = olnPlaceKeepout.end(); + + while( os != oe ) + { + delete *os; + ++os; + } + + olnPlaceKeepout.clear(); + } while(0); + + + // delete PLACEMENT GROUP outlines + do + { + std::map::iterator os = olnGroup.begin(); + std::map::iterator oe = olnGroup.end(); + + while( os != oe ) + { + delete os->second; + ++os; + } + + olnGroup.clear(); + } while(0); + + boardName.clear(); + olnBoard.SetThickness( thickness ); + + state = FILE_START; + unit = UNIT_MM; + userScale = 1.0; + userXoff = 0.0; + userYoff = 0.0; + + return; +} diff --git a/utils/idftools/idf_parser.h b/utils/idftools/idf_parser.h new file mode 100644 index 0000000000..eb812a997c --- /dev/null +++ b/utils/idftools/idf_parser.h @@ -0,0 +1,657 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 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 + */ + +// NOTE: +// 1. Due to the complexity of objects and the risk of accumulated +// position errors, CAD packages should only delete or add complete +// components. If a component being added already exists, it is +// replaced by the new component IF and only if the CAD type is +// permitted to make such changes. +// +// 2. Internally all units shall be in mm and by default we shall +// write files with mm units. The internal flags mm/thou shall only +// be used to translate data being read from or written to files. +// This avoids the painful management of a mixture of mm and thou. +// The API shall require all dimensions in mm; for people using any +// other unit, it is their responsibility to perform the conversion +// to mm. Conversion back to thou may incur small rounding errors. + +// BUGS: +// 1. IDF compliance: On DELETE operations, ensure that the CAD +// has permission to make these deletions. This is no small task; +// however this compliance task can be deferred since it is not +// essential to the immediate needs of KiCad which are IDF +// export and IDF->VRML conversion + +#ifndef IDF_PARSER_H +#define IDF_PARSER_H + +#include + +class IDF3_COMPONENT; + +class IDF3_COMP_OUTLINE_DATA +{ +private: + double xoff; // X offset from KiCad or X placement from IDF file + double yoff; // Y offset from KiCad or Y placement from IDF file + double zoff; // height offset (specified in IDFv3 spec, corresponds to KiCad Z offset) + double aoff; // angular offset from KiCad or Rotation Angle from IDF file + + IDF3_COMP_OUTLINE* outline; // component outline to use + IDF3_COMPONENT* parent; // associated component + +public: + /** + * Constructor + * creates an object with default settings and no parent or associated outline + */ + IDF3_COMP_OUTLINE_DATA(); + + /** + * Constructor + * creates an object with default settings and the specified parent and associated outline + * + * @param aParent is the owning IDF3_COMPONENT object + * @param aOutline is the outline for this placed component + */ + IDF3_COMP_OUTLINE_DATA( IDF3_COMPONENT* aParent, IDF3_COMP_OUTLINE* aOutline ); + + /** + * Constructor + * creates an object the specified parent and associated outline and the specified + * data. + * + * @param aParent is the owning IDF3_COMPONENT object + * @param aOutline is the outline for this placed component + * @param aXoff is the X offset of this outline in relation to its parent + * @param aYoff is the Y offset of this outline in relation to its parent + * @param aZoff is the board offset of this outline as per IDFv3 specification + * @param aAoff is the rotational offset of this outline in relation to its parent + */ + IDF3_COMP_OUTLINE_DATA( IDF3_COMPONENT* aParent, IDF3_COMP_OUTLINE* aOutline, + double aXoff, double aYoff, double aZoff, double aAngleOff ); + + ~IDF3_COMP_OUTLINE_DATA(); + + /** + * Function SetOffsets + * sets the position and orientation of this outline item in relation to its parent + * + * @param aXoff is the X offset of this outline in relation to its parent + * @param aYoff is the Y offset of this outline in relation to its parent + * @param aZoff is the board offset of this outline as per IDFv3 specification + * @param aAoff is the rotational offset of this outline in relation to its parent + */ + void SetOffsets( double aXoff, double aYoff, double aZoff, double aAngleOff ); + + /** + * Function GetOffsets + * retrieves the position and orientation of this outline item in relation to its parent + * + * @param aXoff is the X offset of this outline in relation to its parent + * @param aYoff is the Y offset of this outline in relation to its parent + * @param aZoff is the board offset of this outline as per IDFv3 specification + * @param aAoff is the rotational offset of this outline in relation to its parent + */ + void GetOffsets( double& aXoff, double& aYoff, double& aZoff, double& aAngleOff ); + + /** + * Function SetParent + * sets the parent object + * + * @param aParent is the owning IDF3_COMPONENT object + */ + void SetParent( IDF3_COMPONENT* aParent ); + + /** + * Function SetOutline + * sets the outline whose position is managed by this object + * + * @param aOutline is the outline for this component + */ + void SetOutline( IDF3_COMP_OUTLINE* aOutline ); + + /** + * Function GetOutline + * retrieves the outline whose position is managed by this object + * + * @return IDF3_COMP_OUTLINE*: the outline for this component + */ + IDF3_COMP_OUTLINE* GetOutline( void ) + { + return outline; + } + + /** + * Function ReadPlaceData + * reads placement data from an open IDFv3 file + * + * @param aBoardFile is the open IDFv3 file + * @param aBoardState is the internal status flag of the IDF parser + * @param aBoard is the IDF3_BOARD object which will store the data + * + * @return bool: true if placement data was successfully read. false if + * no placement data was read; this may happen if the end of the placement + * data was encountered or an error occurred. if an error occurred then + * aBoardState is set to IDF3::FILE_INVALID or IDF3::FILE_ERROR. + */ + bool ReadPlaceData( std::ifstream &aBoardFile, IDF3::FILE_STATE& aBoardState, + IDF3_BOARD *aBoard ); + + /** + * Function WritePlaceData + * writes RECORD 2 and RECORD 3 of a PLACEMENT section as per IDFv3 specification + * + * @param aBoardFile is the open IDFv3 file + * @param aXpos is the X location of the parent component + * @param aYpos is the Y location of the parent component + * @param aAngle is the rotation of the parent component + * @param aRefDes is the reference designator of the parent component + * @param aPlacement is the IDF Placement Status of the parent component + * @param aSide is the IDF Layer Designator (TOP or BOTTOM) + * + * @return bool: true if data was successfully written, otherwise false + */ + bool WritePlaceData( std::ofstream& aBoardFile, double aXpos, double aYpos, double aAngle, + const std::string aRefDes, IDF3::IDF_PLACEMENT aPlacement, + IDF3::IDF_LAYER aSide ); +}; + + +class IDF3_COMPONENT +{ +private: + std::list< IDF3_COMP_OUTLINE_DATA* > components; + std::list< IDF_DRILL_DATA* > drills; + + double xpos; + double ypos; + double angle; + IDF3::IDF_PLACEMENT placement; + IDF3::IDF_LAYER layer; // [TOP/BOTTOM ONLY as per IDF spec] + bool hasPosition; ///< True after SetPosition is called once + std::string refdes; ///< Reference Description (MUST BE UNIQUE) + IDF3_BOARD* parent; + +public: + /** + * Constructor + * sets internal parameters to default values + */ + IDF3_COMPONENT(); + + /** + * Constructor + * sets the parent object and initializes other internal parameters to default values + * + * @param aParent is the owning IDF3_BOARD object + */ + IDF3_COMPONENT( IDF3_BOARD* aParent ); + + ~IDF3_COMPONENT(); + + /** + * Function SetParent + * sets the parent object + * + * @param aParent is the owning IDF3_BOARD object + */ + void SetParent( IDF3_BOARD* aParent ); + + /** + * Function GetCadType + * returns the type of CAD (IDF3::CAD_ELEC, IDF3::CAD_MECH) which instantiated this object + * + * @return IDF3::CAD_TYPE + */ + IDF3::CAD_TYPE GetCadType( void ); + + /** + * Function GetCadType + * returns the IDF UNIT type of the parent object or IDF3::UNIT_INVALID if + * the parent was not set + * + * @return IDF3::IDF_UNIT + */ + IDF3::IDF_UNIT GetUnit( void ); + + /** + * Function SetRefDes + * sets the Reference Designator (RefDes) of this component; the RefDes is shared + * by all outlines associated with this component. + * + * @return bool: true if the RefDes was accepted, otherwise false. Prohibited + * values include empty strings and the word PANEL. + */ + bool SetRefDes( const std::string& aRefDes ); + + /** + * Function GetRefDes + * Retrieves the Reference Designator (RefDes) of this component + * + * @return string: the Reference Designator + */ + const std::string& GetRefDes( void ); + + /** + * Function AddDrill + * adds a drill entry to the component and returns its pointer + * + * @param aDia diameter of the drill (mm) + * @param aXpos X position of the drill (mm) + * @param aYpos Y position of the drill (mm) + * @param aPlating plating type (PTH, NPTH) + * @param aHoleType hole class (PIN, VIA, MTG, TOOL, etc) + * @param aOwner owning CAD system (ECAD, MCAD, UNOWNED) + * + * @return pointer: a pointer to the newly created drill entry or NULL + */ + IDF_DRILL_DATA* AddDrill( double aDia, double aXpos, double aYpos, + IDF3::KEY_PLATING aPlating, + const std::string aHoleType, + IDF3::KEY_OWNER aOwner ); + + /** + * Function AddDrill + * adds the given drill entry to the component and returns the pointer + * to indicate success. A return value of NULL indicates that the item + * was not added and it is the user's responsibility to delete the + * object if necessary. + * + * @param aDrilledHole pointer to a drill entry + * + * @return pointer: aDrilledHole if the function succeeds, otherwise NULL + */ + IDF_DRILL_DATA* AddDrill( IDF_DRILL_DATA* aDrilledHole ); + + /** + * Function DelDrill( double aDia, double aXpos, double aYpos ) + * deletes a drill entry based on its size and location. This operation is + * subject to IDF ownership rules. + * + * @param aDia diameter (mm) of the drilled hole to be deleted + * @param aXpos X position (mm) of the hole to be deleted + * @param aYpos X position (mm) of the hole to be deleted + * + * @return bool: true if a drill was found and deleted, otherwise false. + * If an ownership violation occurs an exception is thrown. + */ + bool DelDrill( double aDia, double aXpos, double aYpos ); + + /** + * Function DelDrill( IDF_DRILL_DATA* aDrill ) + * deletes a drill entry based on pointer. This operation is + * subject to IDF ownership rules. + * + * @param aDrill the pointer associated with the drill entry to be deleted + * + * @return bool: true if a drill was found and deleted, otherwise false. + * If an ownership violation occurs an exception is thrown. + */ + bool DelDrill( IDF_DRILL_DATA* aDrill ); + + /** + * Function GetDrills + * returns a pointer to the internal list of drills. To avoid IDF + * violations, the user should not alter these entries. + */ + const std::list< IDF_DRILL_DATA* >*const GetDrills( void ); + + /** + * Function AddOutlineData + * adds the given component outline data to this component + * + * @param aComponentOutline is a pointer to the outline data to be added + * + * @return true if the operation succeedes, otherwise false + */ + bool AddOutlineData( IDF3_COMP_OUTLINE_DATA* aComponentOutline ); + + /** + * Function DeleteOutlineData( IDF3_COMP_OUTLINE_DATA* aComponentOutline ) + * removes outline data based on the pointer provided. + * + * @param aComponentOutline is a pointer to be deleted from the internal list + * + * @return bool: true if the data was found and deleted, otherwise false + */ + bool DeleteOutlineData( IDF3_COMP_OUTLINE_DATA* aComponentOutline ); + + /** + * Function DeleteOutlineData( size_t aIndex ) + * removes outline data based on the provided index. + * + * @param aIndex is an index to the internal outline list + * + * @return bool: true if the data was deleted, false if the + * index was out of bounds. + */ + bool DeleteOutlineData( size_t aIndex ); + + /** + * Function GetOutlineSize + * returns the number of outlines in the internal list + */ + size_t GetOutlinesSize( void ); + + + /** + * Function GetOutlinesData + * returns a pointer to the internal list of outline data + */ + const std::list< IDF3_COMP_OUTLINE_DATA* >*const GetOutlinesData( void ); + + /** + * Function GetPosition + * retrieves the internal position parameters and returns true if the + * position was previously set, otherwise false. + */ + bool GetPosition( double& aXpos, double& aYpos, double& aAngle, IDF3::IDF_LAYER& aLayer ); + + // NOTE: it may be possible to extend this so that internal drills and outlines + // are moved when the component is moved. However there is always a danger of + // position creep due to the relative position updates. + /** + * Function SetPosition + * sets the internal position parameters and returns true if the + * position was set, false if the position was previously set. This object + * does not allow modification of the position once it is set since this may + * adversely affect the relationship with its internal objects. + * + * @param aXpos is the X position (mm) of the component + * @param aYpos is the Y position (mm) of the component + * @param aAngle is the rotation of the component (degrees) + * @param aLayer is the layer on which the component is places (TOP, BOTTOM) + * + * @return bool: true if the position was set, otherwise false + */ + bool SetPosition( double aXpos, double aYpos, double aAngle, IDF3::IDF_LAYER aLayer ); + + /** + * Function GetPlacement + * returns the IDF placement value of this component (UNPLACED, PLACED, ECAD, MCAD) + */ + IDF3::IDF_PLACEMENT GetPlacement( void ); + + /** + * Function SetPlacement + * sets the placement value of the component subject to ownership rules. + * An exception is thrown if aPlacementValue is invalid or an ownership + * violation occurs. + */ + void SetPlacement( IDF3::IDF_PLACEMENT aPlacementValue ); + + /** + * Function WriteDrillData + * writes the internal drill data to an IDFv3 .DRILLED_HOLES section + * + * @param aBoardFile is an IDFv3 file opened for writing + * + * @return bool: true if the operation succeeded, otherwise false + */ + bool WriteDrillData( std::ofstream& aBoardFile ); + + /** + * Function WritePlaceData + * writes the component placement data to an IDFv3 .PLACEMENT section + * + * @param aBoardFile is an IDFv3 file opened for writing + * + * @return bool: true if the operation succeeded, otherwise false + */ + bool WritePlaceData( std::ofstream& aBoardFile ); +}; + +class IDF3_BOARD +{ +private: + std::list< IDF_NOTE* > notes; // IDF notes + std::list< std::string > noteComments; // comment list for NOTES section + std::list< std::string > drillComments; // comment list for DRILL section + std::list< std::string > placeComments; // comment list for PLACEMENT section + std::list board_drills; + std::map< std::string, IDF3_COMPONENT*> components; // drill and placement data for components + std::map< std::string, IDF3_COMP_OUTLINE*> compOutlines; // component outlines (data for library file) + std::string boardName; + IDF3::FILE_STATE state; + IDF3::CAD_TYPE cadType; + IDF3::IDF_UNIT unit; + + std::string idfSource; // SOURCE string to use when writing BOARD and LIBRARY headers + std::string brdSource; // SOURCE string as retrieved from a BOARD file + std::string libSource; // SOURCE string as retrieved from a LIBRARY file + std::string brdDate; // DATE string from BOARD file + std::string libDate; // DATE string from LIBRARY file + int brdFileVersion; // File Version from BOARD file + int libFileVersion; // File Version from LIBRARY file + + int userPrec; // user may store any integer here + double userScale; // user may store a scale for translating to arbitrary units + double userXoff; // user may specify an arbitrary X/Y offset + double userYoff; + + // main board outline and cutouts + BOARD_OUTLINE olnBoard; + // OTHER outlines + std::map olnOther; + // ROUTE outlines + std::list olnRoute; + // PLACEMENT outlines + std::list olnPlace; + // ROUTE KEEPOUT outlines + std::list olnRouteKeepout; + // VIA KEEPOUT outlines + std::list olnViaKeepout; + // PLACE KEEPOUT outlines + std::list olnPlaceKeepout; + // PLACEMENT GROUP outlines + std::multimap olnGroup; + + // Set the unit; this can only be done internally upon + // reading a file or saving + bool setUnit( IDF3::IDF_UNIT aUnit, bool convert = false ); + + IDF_DRILL_DATA* addCompDrill( double aDia, double aXpos, double aYpos, + IDF3::KEY_PLATING aPlating, + const std::string aHoleType, + IDF3::KEY_OWNER aOwner, + const std::string& aRefDes ); + + IDF_DRILL_DATA* addCompDrill( IDF_DRILL_DATA* aDrilledHole ); + + bool delCompDrill( double aDia, double aXpos, double aYpos, std::string aRefDes ); + + // read the DRILLED HOLES section + bool readBrdDrills( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ); + // read the NOTES section + bool readBrdNotes( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ); + // read the component placement section + bool readBrdPlacement( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ); + // read the board HEADER + bool readBrdHeader( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ); + // read individual board sections; pay attention to IDFv3 section specifications + bool readBrdSection( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ); + // read the board file data + bool readBoardFile( const std::string& aFileName ); + + // write the board file data + bool writeBoardFile( const std::string& aFileName ); + + // read the library sections (outlines) + bool readLibSection( std::ifstream& aLibFile, IDF3::FILE_STATE& aLibState, IDF3_BOARD* aBoard ); + // read the library HEADER + bool readLibHeader( std::ifstream& aLibFile, IDF3::FILE_STATE& aLibState ); + // read the library file data + bool readLibFile( const std::string& aFileName ); + + // write the library file data + bool writeLibFile( const std::string& aFileName ); + +public: + IDF3_BOARD( IDF3::CAD_TYPE aCadType ); + virtual ~IDF3_BOARD(); + + IDF3::CAD_TYPE GetCadType( void ); + + // retrieve the nominal unit used in reading/writing + // data. This is primarily for use by owned objects + // and is only of informational use for the end user. + // Internally all data is represented in mm and the + // end user must use only mm in the API. + IDF3::IDF_UNIT GetUnit( void ); + + void SetBoardName( std::string aBoardName ); + const std::string& GetBoardName( void ); + + bool SetBoardThickness( double aBoardThickness ); + double GetBoardThickness( void ); + + bool ReadFile( const wxString& aFullFileName ); + bool WriteFile( const wxString& aFullFileName, bool aUnitMM = true, bool aForceUnitFlag = false ); + + const std::string& GetIDFSource( void ); + void SetIDFSource( const std::string& aIDFSource); + const std::string& GetBoardSource( void ); + const std::string& GetLibrarySource( void ); + const std::string& GetBoardDate( void ); + const std::string& GetLibraryDate( void ); + int GetBoardVersion( void ); + bool SetBoardVersion( int aVersion ); + int GetLibraryVersion( void ); + bool SetLibraryVersion( int aVersion ); + + double GetUserScale( void ); + bool SetUserScale( double aScaleFactor ); + + int GetUserPrecision( void ); + bool SetUserPrecision( int aPrecision ); + + void GetUserOffset( double& aXoff, double& aYoff ); + void SetUserOffset( double aXoff, double aYoff ); + + bool AddBoardOutline( IDF_OUTLINE* aOutline ); + bool DelBoardOutline( IDF_OUTLINE* aOutline ); + bool DelBoardOutline( size_t aIndex ); + size_t GetBoardOutlinesSize( void ); + BOARD_OUTLINE* GetBoardOutline( void ); + const std::list< IDF_OUTLINE* >*const GetBoardOutlines( void ); + + /// XXX - TO BE IMPLEMENTED + // AddDrillComment + // AddPlacementComment + // GetDrillComments() + // GetPlacementComments() + // ClearDrillComments() + // ClearPlacementComments() + // AddNoteComment + // GetNoteComments + // AddNote + // + // const std::map*const GetOtherOutlines() + // size_t GetOtherOutlinesSize() + // OTHER_OUTLINE* AddOtherOutline( OTHER_OUTLINE* aOtherOutline ) + // bool DelOtherOutline( int aIndex ) + // bool DelOtherOutline( OTHER_OUTLINE* aOtherOutline ) + // + // const std::list*const GetRouteOutlines() + // size_t GetRouteOutlinesSize() + // ROUTE_OUTLINE* AddRouteOutline( ROUTE_OUTLINE* aRouteOutline ) + // bool DelRouteOutline( int aIndex ) + // bool DelRouteOutline( ROUTE_OUTLINE* aRouteOutline ) + // + // const std::list*const GetPlacementOutlines() + // size_t GetPlacementOutlinesSize() + // PLACE_OUTLINE* AddPlacementOutline( PLACE_OUTLINE* aPlaceOutline ) + // bool DelPlacementOutline( int aIndex ) + // bool DelPlacementOutline( PLACE_OUTLINE* aPlaceOutline ) + // + // const std::list*const GetRouteKeepOutOutlines() + // size_t GetRouteKeepOutOutlinesSize() + // ROUTE_KO_OUTLINE* AddRouteKeepoutOutline( ROUTE_KO_OUTLINE* aRouteKeepOut ) + // bool DelRouteKeepOutOutline( int aIndex ) + // bool DelRouteKeepOutOutline( ROUTE_KO_OUTLINE* aRouteKeepOut ) + // + // const std::list*const GetViaKeepOutOutlines() + // size_t GetViaKeepOutOutlinesSize() + // VIA_KO_OUTLINE* AddViaKeepoutOutline( VIA_KO_OUTLINE* aViaKeepOut ) + // bool DelViaKeepOutOutline( int aIndex ) + // bool DelViaKeepOutOutline( VIA_KO_OUTLINE* aViaKeepOut ) + // + // const std::list*const GetPlacementKeepOutOutlines() + // size_t GetPlacementKeepOutOutlinesSize() + // PLACE_KO_OUTLINE* AddPlacementKeepoutOutline( PLACE_KO_OUTLINE* aPlaceKeepOut ) + // bool DelPlacementKeepOutOutline( int aIndex ) + // bool DelPlacementKeepOutOutline( PLACE_KO_OUTLINE* aPlaceKeepOut ) + // + // const std::multimap*const GetGroupOutlines() + // size_t GetGroupOutlinesSize() + // GROUP_OUTLINE* AddGroupOutline( GROUP_OUTLINE* aGroupOutline ) + // bool DelGroupOutline( int aIndex ) + // bool DelGroupOutline( GROUP_OUTLINE* aGroupOutline ) + + std::list& GetBoardDrills( void ) + { + return board_drills; + } + + IDF_DRILL_DATA* AddBoardDrill( double aDia, double aXpos, double aYpos, + IDF3::KEY_PLATING aPlating, + const std::string aHoleType, + IDF3::KEY_OWNER aOwner ); + + IDF_DRILL_DATA* AddDrill( IDF_DRILL_DATA* aDrilledHole ); + + bool DelBoardDrill( double aDia, double aXpos, double aYpos ); + + // a slot is a deficient representation of a kicad slotted hole; + // it is usually associated with a component but IDFv3 does not + // provide for such an association. + bool AddSlot( double aWidth, double aLength, double aOrientation, double aX, double aY ); + + bool AddComponent( IDF3_COMPONENT* aComponent ); + bool DelComponent( IDF3_COMPONENT* aComponent ); + bool DelComponent( size_t aIndex ); + size_t GetComponentsSize( void ); + std::map< std::string, IDF3_COMPONENT* >*const GetComponents( void ); + IDF3_COMPONENT* FindComponent( std::string aRefDes ); + + // returns a pointer to a component outline object or NULL + // if the object doesn't exist + IDF3_COMP_OUTLINE* GetComponentOutline( const std::string aGeomName, + const std::string aPartName, + wxString aFullFileName ); + + // returns a pointer to the component outline object with the + // unique ID aComponentID + IDF3_COMP_OUTLINE* GetComponentOutline( std::string aComponentID ); + + // returns a pointer to the outline "NOGEOM NOPART" which is substituted + // whenever a true outline cannot be found or is defective + IDF3_COMP_OUTLINE* GetInvalidOutline( const std::string& aGeomName, const std::string& aPartName ); + + // clears all data + void Clear( void ); +}; + +#endif // IDF_PARSER_H diff --git a/utils/idftools/idf_rect.cpp b/utils/idftools/idf_rect.cpp index 7a9392ab5c..17b145b255 100644 --- a/utils/idftools/idf_rect.cpp +++ b/utils/idftools/idf_rect.cpp @@ -98,7 +98,9 @@ int main( int argc, char **argv ) tstr.clear(); tstr.str( line ); - if( (tstr >> width) && width >= 0.001 ) + + tstr >> width; + if( !tstr.fail() && width >= 0.001 ) ok = true; } @@ -112,7 +114,9 @@ int main( int argc, char **argv ) tstr.clear(); tstr.str( line ); - if( (tstr >> length) && length > 0.0 ) + + tstr >> length; + if( !tstr.fail() && length > 0.0 ) ok = true; } @@ -126,7 +130,9 @@ int main( int argc, char **argv ) tstr.clear(); tstr.str( line ); - if( (tstr >> height) && height >= 0.001 ) + + tstr >> height; + if( !tstr.fail() && height >= 0.001 ) ok = true; } @@ -140,7 +146,9 @@ int main( int argc, char **argv ) tstr.clear(); tstr.str( line ); - if( (tstr >> chamfer) && chamfer >= 0.0 ) + + tstr >> chamfer; + if( !tstr.fail() && chamfer >= 0.0 ) { if( chamfer > width / 3.0 || chamfer > length / 3.0 ) cout << "* WARNING: chamfer must be <= MIN( width, length )/3\n"; @@ -182,7 +190,9 @@ int main( int argc, char **argv ) tstr.clear(); tstr.str( line ); - if( (tstr >> wireDia) && wireDia >= 0.001 ) + + tstr >> wireDia; + if( !tstr.fail() && wireDia >= 0.001 ) { if( wireDia >= length ) cout << "* WARNING: wire diameter must be < length\n"; @@ -201,7 +211,9 @@ int main( int argc, char **argv ) tstr.clear(); tstr.str( line ); - if( (tstr >> pitch) && pitch >= 0.001 ) + + tstr >> pitch; + if( !tstr.fail() && pitch >= 0.001 ) { if( pitch <= ( length + wireDia ) / 2.0 ) cout << "* WARNING: pitch must be > (length + wireDia)/2\n"; diff --git a/utils/idftools/vrml_layer.cpp b/utils/idftools/vrml_layer.cpp new file mode 100644 index 0000000000..3e736a41bc --- /dev/null +++ b/utils/idftools/vrml_layer.cpp @@ -0,0 +1,1578 @@ +/* + * file: vrml_layer.cpp + * + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 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 + */ + +/* + * NOTES ON OUTPUT PRECISION: + * + * If we use %.6f then we have no need for special unit dependent formatting: + * + * inch: .0254 microns + * mm: 0.001 microns + * m: 1 micron + * + */ + +#include +#include +#include +#include +#include + +#include // CALLBACK definition, needed on Windows + +#ifndef CALLBACK +#define CALLBACK +#endif + +#define GLCALLBACK(x) (( void (CALLBACK*)() )&(x)) + +// minimum sides to a circle +#define MIN_NSIDES 6 + +static void FormatDoublet( double x, double y, int precision, std::string& strx, std::string& stry ) +{ + std::ostringstream ostr; + + ostr << std::fixed << std::setprecision( precision ); + + ostr << x; + strx = ostr.str(); + + ostr.str( "" ); + ostr << y; + stry = ostr.str(); + + while( *strx.rbegin() == '0' ) + strx.erase( strx.size() - 1 ); + + while( *stry.rbegin() == '0' ) + stry.erase( stry.size() - 1 ); +} + + +static void FormatSinglet( double x, int precision, std::string& strx ) +{ + std::ostringstream ostr; + + ostr << std::fixed << std::setprecision( precision ); + + ostr << x; + strx = ostr.str(); + + while( *strx.rbegin() == '0' ) + strx.erase( strx.size() - 1 ); +} + + +int VRML_LAYER::calcNSides( double aRadius, double aAngle ) +{ + // check #segments on ends of arc + int maxSeg = maxArcSeg * aAngle / M_PI; + + if( maxSeg < 3 ) + maxSeg = 3; + + int csides = aRadius * M_PI / minSegLength; + + if( csides < 0 ) + csides = -csides; + + if( csides > maxSeg ) + { + if( csides < 2 * maxSeg ) + csides /= 2; + else + csides = (((double) csides) * minSegLength / maxSegLength ); + } + + if( csides < 3 ) + csides = 3; + + if( (csides & 1) == 0 ) + csides += 1; + + return csides; +} + + +static void CALLBACK vrml_tess_begin( GLenum cmd, void* user_data ) +{ + VRML_LAYER* lp = (VRML_LAYER*) user_data; + + lp->glStart( cmd ); +} + + +static void CALLBACK vrml_tess_end( void* user_data ) +{ + VRML_LAYER* lp = (VRML_LAYER*) user_data; + + lp->glEnd(); +} + + +static void CALLBACK vrml_tess_vertex( void* vertex_data, void* user_data ) +{ + VRML_LAYER* lp = (VRML_LAYER*) user_data; + + lp->glPushVertex( (VERTEX_3D*) vertex_data ); +} + + +static void CALLBACK vrml_tess_err( GLenum errorID, void* user_data ) +{ + VRML_LAYER* lp = (VRML_LAYER*) user_data; + + lp->Fault = true; + lp->SetGLError( errorID ); +} + + +static void CALLBACK vrml_tess_combine( GLdouble coords[3], void* vertex_data[4], + GLfloat weight[4], void** outData, void* user_data ) +{ + VRML_LAYER* lp = (VRML_LAYER*) user_data; + + *outData = lp->AddExtraVertex( coords[0], coords[1] ); +} + + +VRML_LAYER::VRML_LAYER() +{ + // arc parameters suitable to mm measurements + maxArcSeg = 48; + minSegLength = 0.1; + maxSegLength = 0.5; + + fix = false; + Fault = false; + idx = 0; + ord = 0; + glcmd = 0; + pholes = NULL; + + tess = gluNewTess(); + + if( !tess ) + return; + + // set up the tesselator callbacks + gluTessCallback( tess, GLU_TESS_BEGIN_DATA, GLCALLBACK( vrml_tess_begin ) ); + + gluTessCallback( tess, GLU_TESS_VERTEX_DATA, GLCALLBACK( vrml_tess_vertex ) ); + + gluTessCallback( tess, GLU_TESS_END_DATA, GLCALLBACK( vrml_tess_end ) ); + + gluTessCallback( tess, GLU_TESS_ERROR_DATA, GLCALLBACK( vrml_tess_err ) ); + + gluTessCallback( tess, GLU_TESS_COMBINE_DATA, GLCALLBACK( vrml_tess_combine ) ); + + gluTessProperty( tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE ); + + gluTessNormal( tess, 0, 0, 1 ); +} + + +VRML_LAYER::~VRML_LAYER() +{ + Clear(); + + if( tess ) + { + gluDeleteTess( tess ); + tess = NULL; + } +} + + +void VRML_LAYER::GetArcParams( int& aMaxSeg, double& aMinLength, double& aMaxLength ) +{ + aMaxSeg = maxArcSeg; + aMinLength = minSegLength; + aMaxLength = maxSegLength; +} + +bool VRML_LAYER::SetArcParams( int aMaxSeg, double aMinLength, double aMaxLength ) +{ + if( aMaxSeg < 8 ) + aMaxSeg = 8; + + if( aMinLength <= 0 || aMaxLength <= aMinLength ) + return false; + + maxArcSeg = aMaxSeg; + minSegLength = aMinLength; + maxSegLength = aMaxLength; + return true; +} + + +// clear all data +void VRML_LAYER::Clear( void ) +{ + int i; + + fix = false; + idx = 0; + + for( i = contours.size(); i > 0; --i ) + { + delete contours.back(); + contours.pop_back(); + } + + while( !areas.empty() ) + areas.pop_back(); + + for( i = vertices.size(); i > 0; --i ) + { + delete vertices.back(); + vertices.pop_back(); + } + + clearTmp(); +} + + +// clear ephemeral data in between invocations of the tesselation routine +void VRML_LAYER::clearTmp( void ) +{ + unsigned int i; + + Fault = false; + hidx = 0; + eidx = 0; + ord = 0; + glcmd = 0; + + while( !triplets.empty() ) + triplets.pop_back(); + + for( i = outline.size(); i > 0; --i ) + { + delete outline.back(); + outline.pop_back(); + } + + for( i = ordmap.size(); i > 0; --i ) + ordmap.pop_back(); + + for( i = extra_verts.size(); i > 0; --i ) + { + delete extra_verts.back(); + extra_verts.pop_back(); + } + + // note: unlike outline and extra_verts, + // vlist is not responsible for memory management + for( i = vlist.size(); i > 0; --i ) + vlist.pop_back(); + + // go through the vertex list and reset ephemeral parameters + for( i = 0; i < vertices.size(); ++i ) + { + vertices[i]->o = -1; + } +} + + +// create a new contour to be populated; returns an index +// into the contour list or -1 if there are problems +int VRML_LAYER::NewContour( void ) +{ + if( fix ) + return -1; + + std::list* contour = new std::list; + + if( !contour ) + return -1; + + contours.push_back( contour ); + areas.push_back( 0.0 ); + + return contours.size() - 1; +} + + +// adds a vertex to the existing list and places its index in +// an existing contour; returns true if OK, +// false otherwise (indexed contour does not exist) +bool VRML_LAYER::AddVertex( int aContourID, double aXpos, double aYpos ) +{ + if( fix ) + { + error = "AddVertex(): no more vertices may be added (Tesselate was previously executed)"; + return false; + } + + if( aContourID < 0 || (unsigned int) aContourID >= contours.size() ) + { + error = "AddVertex(): aContour is not within a valid range"; + return false; + } + + VERTEX_3D* vertex = new VERTEX_3D; + + if( !vertex ) + { + error = "AddVertex(): a new vertex could not be allocated"; + return false; + } + + vertex->x = aXpos; + vertex->y = aYpos; + vertex->i = idx++; + vertex->o = -1; + + VERTEX_3D* v2 = NULL; + + if( contours[aContourID]->size() > 0 ) + v2 = vertices[ contours[aContourID]->back() ]; + + vertices.push_back( vertex ); + contours[aContourID]->push_back( vertex->i ); + + if( v2 ) + areas[aContourID] += ( aXpos - v2->x ) * ( aYpos + v2->y ); + + return true; +} + + +// ensure the winding of a contour with respect to the normal (0, 0, 1); +// set 'hole' to true to ensure a hole (clockwise winding) +bool VRML_LAYER::EnsureWinding( int aContourID, bool aHoleFlag ) +{ + if( aContourID < 0 || (unsigned int) aContourID >= contours.size() ) + { + error = "EnsureWinding(): aContour is outside the valid range"; + return false; + } + + std::list* cp = contours[aContourID]; + + if( cp->size() < 3 ) + { + error = "EnsureWinding(): there are fewer than 3 vertices"; + return false; + } + + double dir = areas[aContourID]; + + VERTEX_3D* vp0 = vertices[ cp->back() ]; + VERTEX_3D* vp1 = vertices[ cp->front() ]; + + dir += ( vp1->x - vp0->x ) * ( vp1->y + vp0->y ); + + // if dir is positive, winding is CW + if( ( aHoleFlag && dir < 0 ) || ( !aHoleFlag && dir > 0 ) ) + { + cp->reverse(); + areas[aContourID] = -areas[aContourID]; + } + + return true; +} + + +bool VRML_LAYER::AppendCircle( double aXpos, double aYpos, + double aRadius, int aContourID, + bool aHoleFlag ) +{ + if( aContourID < 0 || (unsigned int) aContourID >= contours.size() ) + { + error = "AppendCircle(): invalid contour (out of range)"; + return false; + } + + int nsides = M_PI * 2.0 * aRadius / minSegLength; + + if( nsides > maxArcSeg ) + { + if( nsides > 2 * maxArcSeg ) + { + // use segments approx. maxAr + nsides = M_PI * 2.0 * aRadius / maxSegLength; + } + else + { + nsides /= 2; + } + } + + if( nsides < MIN_NSIDES ) + nsides = MIN_NSIDES; + + // even numbers give prettier results for circles + if( nsides & 1 ) + nsides += 1; + + double da = M_PI * 2.0 / nsides; + + bool fail = false; + + if( aHoleFlag ) + { + fail |= !AddVertex( aContourID, aXpos + aRadius, aYpos ); + + for( double angle = da; angle < M_PI * 2; angle += da ) + fail |= !AddVertex( aContourID, aXpos + aRadius * cos( angle ), + aYpos - aRadius * sin( angle ) ); + } + else + { + fail |= !AddVertex( aContourID, aXpos + aRadius, aYpos ); + + for( double angle = da; angle < M_PI * 2; angle += da ) + fail |= !AddVertex( aContourID, aXpos + aRadius * cos( angle ), + aYpos + aRadius * sin( angle ) ); + } + + return !fail; +} + + +// adds a circle the existing list; if 'hole' is true the contour is +// a hole. Returns true if OK. +bool VRML_LAYER::AddCircle( double aXpos, double aYpos, double aRadius, bool aHoleFlag ) +{ + int pad = NewContour(); + + if( pad < 0 ) + { + error = "AddCircle(): failed to add a contour"; + return false; + } + + return AppendCircle( aXpos, aYpos, aRadius, pad, aHoleFlag ); +} + + +// adds a slotted pad with orientation given by angle; if 'hole' is true the +// contour is a hole. Returns true if OK. +bool VRML_LAYER::AddSlot( double aCenterX, double aCenterY, + double aSlotLength, double aSlotWidth, + double aAngle, bool aHoleFlag ) +{ + aAngle *= M_PI / 180.0; + + if( aSlotWidth > aSlotLength ) + { + aAngle += M_PI2; + std::swap( aSlotLength, aSlotWidth ); + } + + aSlotWidth /= 2.0; + aSlotLength = aSlotLength / 2.0 - aSlotWidth; + + int csides = calcNSides( aSlotWidth, M_PI ); + + double capx, capy; + + capx = aCenterX + cos( aAngle ) * aSlotLength; + capy = aCenterY + sin( aAngle ) * aSlotLength; + + double ang, da; + int i; + int pad = NewContour(); + + if( pad < 0 ) + { + error = "AddCircle(): failed to add a contour"; + return false; + } + + da = M_PI / csides; + bool fail = false; + + if( aHoleFlag ) + { + for( ang = aAngle + M_PI2, i = 0; i < csides; ang -= da, ++i ) + fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), + capy + aSlotWidth * sin( ang ) ); + + ang = aAngle - M_PI2; + fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), + capy + aSlotWidth * sin( ang ) ); + + capx = aCenterX - cos( aAngle ) * aSlotLength; + capy = aCenterY - sin( aAngle ) * aSlotLength; + + for( ang = aAngle - M_PI2, i = 0; i < csides; ang -= da, ++i ) + fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), + capy + aSlotWidth * sin( ang ) ); + + ang = aAngle + M_PI2; + fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), + capy + aSlotWidth * sin( ang ) ); + } + else + { + for( ang = aAngle - M_PI2, i = 0; i < csides; ang += da, ++i ) + fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), + capy + aSlotWidth * sin( ang ) ); + + ang = aAngle + M_PI2; + fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), + capy + aSlotWidth * sin( ang ) ); + + capx = aCenterX - cos( aAngle ) * aSlotLength; + capy = aCenterY - sin( aAngle ) * aSlotLength; + + for( ang = aAngle + M_PI2, i = 0; i < csides; ang += da, ++i ) + fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), + capy + aSlotWidth * sin( ang ) ); + + ang = aAngle - M_PI2; + fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), + capy + aSlotWidth * sin( ang ) ); + } + + return !fail; +} + + +// adds an arc to the given center, start point, pen width, and angle (degrees). +bool VRML_LAYER::AppendArc( double aCenterX, double aCenterY, double aRadius, + double aStartAngle, double aAngle, int aContourID ) +{ + if( aContourID < 0 || (unsigned int) aContourID >= contours.size() ) + { + error = "AppendArc(): invalid contour (out of range)"; + return false; + } + + aAngle = aAngle / 180.0 * M_PI; + aStartAngle = aStartAngle / 180.0 * M_PI; + + int nsides = calcNSides( aRadius, aAngle ); + + double da = aAngle / nsides; + + bool fail = false; + + if( aAngle > 0 ) + { + aAngle += aStartAngle; + for( double ang = aStartAngle; ang < aAngle; ang += da ) + fail |= !AddVertex( aContourID, aCenterX + aRadius * cos( ang ), + aCenterY + aRadius * sin( ang ) ); + } + else + { + aAngle += aStartAngle; + for( double ang = aStartAngle; ang > aAngle; ang += da ) + fail |= !AddVertex( aContourID, aCenterX + aRadius * cos( ang ), + aCenterY + aRadius * sin( ang ) ); + } + + return !fail; +} + + +// adds an arc with the given center, start point, pen width, and angle (degrees). +bool VRML_LAYER::AddArc( double aCenterX, double aCenterY, double aStartX, double aStartY, + double aArcWidth, double aAngle, bool aHoleFlag ) +{ + aAngle *= M_PI / 180.0; + + // we don't accept small angles; in fact, 1 degree ( 0.01745 ) is already + // way too small but we must set a limit somewhere + if( aAngle < 0.01745 && aAngle > -0.01745 ) + { + error = "AddArc(): angle is too small: abs( angle ) < 1 degree"; + return false; + } + + double rad = sqrt( (aStartX - aCenterX) * (aStartX - aCenterX) + + (aStartY - aCenterY) * (aStartY - aCenterY) ); + + aArcWidth /= 2.0; // this is the radius of the caps + + // we will not accept an arc with an inner radius close to zero so we + // set a limit here. the end result will vary somewhat depending on + // the output units + if( aArcWidth >= ( rad * 1.01 ) ) + { + error = "AddArc(): width/2 exceeds radius*1.01"; + return false; + } + + // calculate the radii of the outer and inner arcs + double orad = rad + aArcWidth; + double irad = rad - aArcWidth; + + int osides = calcNSides( orad, aAngle ); + int isides = calcNSides( irad, aAngle ); + int csides = calcNSides( aArcWidth, M_PI ); + + double stAngle = atan2( aStartY - aCenterY, aStartX - aCenterX ); + double endAngle = stAngle + aAngle; + + // calculate ends of inner and outer arc + double oendx = aCenterX + orad* cos( endAngle ); + double oendy = aCenterY + orad* sin( endAngle ); + double ostx = aCenterX + orad* cos( stAngle ); + double osty = aCenterY + orad* sin( stAngle ); + + double iendx = aCenterX + irad* cos( endAngle ); + double iendy = aCenterY + irad* sin( endAngle ); + double istx = aCenterX + irad* cos( stAngle ); + double isty = aCenterY + irad* sin( stAngle ); + + if( ( aAngle < 0 && !aHoleFlag ) || ( aAngle > 0 && aHoleFlag ) ) + { + aAngle = -aAngle; + std::swap( stAngle, endAngle ); + std::swap( oendx, ostx ); + std::swap( oendy, osty ); + std::swap( iendx, istx ); + std::swap( iendy, isty ); + } + + int arc = NewContour(); + + if( arc < 0 ) + { + error = "AddArc(): could not create a contour"; + return false; + } + + // trace the outer arc: + int i; + double ang; + double da = aAngle / osides; + + for( ang = stAngle, i = 0; i < osides; ang += da, ++i ) + AddVertex( arc, aCenterX + orad * cos( ang ), aCenterY + orad * sin( ang ) ); + + // trace the first cap + double capx = ( iendx + oendx ) / 2.0; + double capy = ( iendy + oendy ) / 2.0; + + if( aHoleFlag ) + da = -M_PI / csides; + else + da = M_PI / csides; + + for( ang = endAngle, i = 0; i < csides; ang += da, ++i ) + AddVertex( arc, capx + aArcWidth * cos( ang ), capy + aArcWidth * sin( ang ) ); + + // trace the inner arc: + da = -aAngle / isides; + + for( ang = endAngle, i = 0; i < isides; ang += da, ++i ) + AddVertex( arc, aCenterX + irad * cos( ang ), aCenterY + irad * sin( ang ) ); + + // trace the final cap + capx = ( istx + ostx ) / 2.0; + capy = ( isty + osty ) / 2.0; + + if( aHoleFlag ) + da = -M_PI / csides; + else + da = M_PI / csides; + + for( ang = stAngle + M_PI, i = 0; i < csides; ang += da, ++i ) + AddVertex( arc, capx + aArcWidth * cos( ang ), capy + aArcWidth * sin( ang ) ); + + return true; +} + + +// tesselates the contours in preparation for a 3D output; +// returns true if all was fine, false otherwise +bool VRML_LAYER::Tesselate( VRML_LAYER* holes ) +{ + if( !tess ) + { + error = "Tesselate(): GLU tesselator was not initialized"; + return false; + } + + pholes = holes; + Fault = false; + + if( contours.size() < 1 || vertices.size() < 3 ) + { + error = "Tesselate(): not enough vertices"; + return false; + } + + // finish the winding calculation on all vertices prior to setting 'fix' + if( !fix ) + { + for( unsigned int i = 0; i < contours.size(); ++i ) + { + if( contours[i]->size() < 3 ) + continue; + + VERTEX_3D* vp0 = vertices[ contours[i]->back() ]; + VERTEX_3D* vp1 = vertices[ contours[i]->front() ]; + areas[i] += ( vp1->x - vp0->x ) * ( vp1->y + vp0->y ); + } + } + + // prevent the addition of any further contours and contour vertices + fix = true; + + // clear temporary internals which may have been used in a previous run + clearTmp(); + + // request an outline + gluTessProperty( tess, GLU_TESS_BOUNDARY_ONLY, GL_TRUE ); + + // adjust internal indices for extra points and holes + if( holes ) + hidx = holes->GetSize(); + else + hidx = 0; + + eidx = idx + hidx; + + // open the polygon + gluTessBeginPolygon( tess, this ); + + pushVertices( false ); + + // close the polygon + gluTessEndPolygon( tess ); + + if( Fault ) + return false; + + // push the (solid) outline to the tesselator + if( !pushOutline( holes ) ) + return false; + + // add the holes contained by this object + pushVertices( true ); + + // import external holes (if any) + if( hidx && ( holes->Import( idx, tess ) < 0 ) ) + { + std::ostringstream ostr; + ostr << "Tesselate():FAILED: " << holes->GetError(); + error = ostr.str(); + return NULL; + } + + if( Fault ) + return false; + + // erase the previous outline data and vertex order + // but preserve the extra vertices + for( int i = outline.size(); i > 0; --i ) + { + delete outline.back(); + outline.pop_back(); + } + + for( unsigned int i = ordmap.size(); i > 0; --i ) + ordmap.pop_back(); + + // go through the vertex lists and reset ephemeral parameters + for( unsigned int i = 0; i < vertices.size(); ++i ) + { + vertices[i]->o = -1; + } + + for( unsigned int i = 0; i < extra_verts.size(); ++i ) + { + extra_verts[i]->o = -1; + } + + ord = 0; + + // close the polygon; we now have all the data necessary for the tesselation + gluTessEndPolygon( tess ); + + // request a tesselated surface + gluTessProperty( tess, GLU_TESS_BOUNDARY_ONLY, GL_FALSE ); + + if( !pushOutline( holes ) ) + return false; + + gluTessEndPolygon( tess ); + + if( Fault ) + return false; + + return true; +} + + +bool VRML_LAYER::pushOutline( VRML_LAYER* holes ) +{ + // traverse the outline list to push all used vertices + if( outline.size() < 1 ) + { + error = "pushOutline() failed: no vertices to push"; + return false; + } + + gluTessBeginPolygon( tess, this ); + + std::list*>::const_iterator obeg = outline.begin(); + std::list*>::const_iterator oend = outline.end(); + + int pi; + std::list::const_iterator begin; + std::list::const_iterator end; + GLdouble pt[3]; + VERTEX_3D* vp; + + while( obeg != oend ) + { + if( (*obeg)->size() < 3 ) + { + ++obeg; + continue; + } + + gluTessBeginContour( tess ); + + begin = (*obeg)->begin(); + end = (*obeg)->end(); + + while( begin != end ) + { + pi = *begin; + + if( pi < 0 || (unsigned int) pi > ordmap.size() ) + { + error = "pushOutline():BUG: *outline.begin() is not a valid index to ordmap"; + return false; + } + + // retrieve the actual index + pi = ordmap[pi]; + + vp = getVertexByIndex( pi, holes ); + + if( !vp ) + { + error = "pushOutline():: BUG: ordmap[n] is not a valid index to vertices[]"; + return false; + } + + pt[0] = vp->x; + pt[1] = vp->y; + pt[2] = 0.0; + gluTessVertex( tess, pt, vp ); + ++begin; + } + + gluTessEndContour( tess ); + ++obeg; + } + + return true; +} + + +// writes out the vertex list for a planar feature +bool VRML_LAYER::WriteVertices( double aZcoord, std::ofstream& aOutFile, int aPrecision ) +{ + if( ordmap.size() < 3 ) + { + error = "WriteVertices(): not enough vertices"; + return false; + } + + if( aPrecision < 4 ) + aPrecision = 4; + + int i, j; + + VERTEX_3D* vp = getVertexByIndex( ordmap[0], pholes ); + + if( !vp ) + return false; + + std::string strx, stry, strz; + FormatDoublet( vp->x, vp->y, aPrecision, strx, stry ); + FormatSinglet( aZcoord, aPrecision, strz ); + + aOutFile << strx << " " << stry << " " << strz; + + for( i = 1, j = ordmap.size(); i < j; ++i ) + { + vp = getVertexByIndex( ordmap[i], pholes ); + + if( !vp ) + return false; + + FormatDoublet( vp->x, vp->y, aPrecision, strx, stry ); + + if( i & 1 ) + aOutFile << ", " << strx << " " << stry << " " << strz; + else + aOutFile << ",\n" << strx << " " << stry << " " << strz; + } + + return !aOutFile.fail(); +} + + +// writes out the vertex list for a 3D feature; top and bottom are the +// Z values for the top and bottom; top must be > bottom +bool VRML_LAYER::Write3DVertices( double aTopZ, double aBottomZ, + std::ofstream& aOutFile, int aPrecision ) +{ + if( ordmap.size() < 3 ) + { + error = "Write3DVertices(): insufficient vertices"; + return false; + } + + if( aPrecision < 4 ) + aPrecision = 4; + + if( aTopZ <= aBottomZ ) + { + error = "Write3DVertices(): top <= bottom"; + return false; + } + + int i, j; + + VERTEX_3D* vp = getVertexByIndex( ordmap[0], pholes ); + + if( !vp ) + return false; + + std::string strx, stry, strz; + FormatDoublet( vp->x, vp->y, aPrecision, strx, stry ); + FormatSinglet( aTopZ, aPrecision, strz ); + + aOutFile << strx << " " << stry << " " << strz; + + for( i = 1, j = ordmap.size(); i < j; ++i ) + { + vp = getVertexByIndex( ordmap[i], pholes ); + + if( !vp ) + return false; + + FormatDoublet( vp->x, vp->y, aPrecision, strx, stry ); + + if( i & 1 ) + aOutFile << ", " << strx << " " << stry << " " << strz; + else + aOutFile << ",\n" << strx << " " << stry << " " << strz; + } + + // repeat for the bottom layer + vp = getVertexByIndex( ordmap[0], pholes ); + FormatDoublet( vp->x, vp->y, aPrecision, strx, stry ); + FormatSinglet( aBottomZ, aPrecision, strz ); + + bool endl; + + if( i & 1 ) + { + aOutFile << ", " << strx << " " << stry << " " << strz; + endl = false; + } + else + { + aOutFile << ",\n" << strx << " " << stry << " " << strz; + endl = true; + } + + for( i = 1, j = ordmap.size(); i < j; ++i ) + { + vp = getVertexByIndex( ordmap[i], pholes ); + FormatDoublet( vp->x, vp->y, aPrecision, strx, stry ); + + if( endl ) + { + aOutFile << ", " << strx << " " << stry << " " << strz; + endl = false; + } + else + { + aOutFile << ",\n" << strx << " " << stry << " " << strz; + endl = true; + } + } + + return !aOutFile.fail(); +} + + +// writes out the index list; +// 'top' indicates the vertex ordering and should be +// true for a polygon visible from above the PCB +bool VRML_LAYER::WriteIndices( bool aTopFlag, std::ofstream& aOutFile ) +{ + if( triplets.empty() ) + { + error = "WriteIndices(): no triplets (triangular facets) to write"; + return false; + } + + // go through the triplet list and write out the indices based on order + std::list::const_iterator tbeg = triplets.begin(); + std::list::const_iterator tend = triplets.end(); + + int i = 1; + + if( aTopFlag ) + aOutFile << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; + else + aOutFile << tbeg->i2 << ", " << tbeg->i1 << ", " << tbeg->i3 << ", -1"; + + ++tbeg; + + while( tbeg != tend ) + { + if( (i++ & 7) == 4 ) + { + i = 1; + + if( aTopFlag ) + aOutFile << ",\n" << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; + else + aOutFile << ",\n" << tbeg->i2 << ", " << tbeg->i1 << ", " << tbeg->i3 << ", -1"; + } + else + { + if( aTopFlag ) + aOutFile << ", " << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; + else + aOutFile << ", " << tbeg->i2 << ", " << tbeg->i1 << ", " << tbeg->i3 << ", -1"; + } + + ++tbeg; + } + + return !aOutFile.fail(); +} + + +// writes out the index list for a 3D feature +bool VRML_LAYER::Write3DIndices( std::ofstream& aOutFile ) +{ + if( triplets.empty() ) + { + error = "Write3DIndices(): no triplets (triangular facets) to write"; + return false; + } + + if( outline.empty() ) + { + error = "WriteIndices(): no outline available"; + return false; + } + + // go through the triplet list and write out the indices based on order + std::list::const_iterator tbeg = triplets.begin(); + std::list::const_iterator tend = triplets.end(); + + int i = 1; + int idx2 = ordmap.size(); // index to the bottom vertices + + // print out the top vertices + aOutFile << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; + ++tbeg; + + while( tbeg != tend ) + { + if( (i++ & 7) == 4 ) + { + i = 1; + aOutFile << ",\n" << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; + } + else + { + aOutFile << ", " << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; + } + + ++tbeg; + } + + // print out the bottom vertices + tbeg = triplets.begin(); + + while( tbeg != tend ) + { + if( (i++ & 7) == 4 ) + { + i = 1; + aOutFile << ",\n" << (tbeg->i2 + idx2) << ", " << (tbeg->i1 + idx2) << ", " << (tbeg->i3 + idx2) << ", -1"; + } + else + { + aOutFile << ", " << (tbeg->i2 + idx2) << ", " << (tbeg->i1 + idx2) << ", " << (tbeg->i3 + idx2) << ", -1"; + } + + ++tbeg; + } + + // print out indices for the walls joining top to bottom + int firstPoint; + int lastPoint; + int curPoint; + + std::list*>::const_iterator obeg = outline.begin(); + std::list*>::const_iterator oend = outline.end(); + std::list* cp; + std::list::const_iterator cbeg; + std::list::const_iterator cend; + + i = 2; + while( obeg != oend ) + { + cp = *obeg; + + if( cp->size() < 3 ) + { + ++obeg; + continue; + } + + cbeg = cp->begin(); + cend = cp->end(); + + firstPoint = *(cbeg++); + lastPoint = firstPoint; + + while( cbeg != cend ) + { + curPoint = *(cbeg++); + + if( (i++ & 3) == 2 ) + { + i = 1; + aOutFile << ",\n" << curPoint << ", " << lastPoint << ", " << curPoint + idx2; + aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint << ", " << lastPoint + idx2 << ", -1"; + } + else + { + aOutFile << ", " << curPoint << ", " << lastPoint << ", " << curPoint + idx2; + aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint << ", " << lastPoint + idx2 << ", -1"; + } + lastPoint = curPoint; + } + + if( (i++ & 3) == 2 ) + { + aOutFile << ",\n" << firstPoint << ", " << lastPoint << ", " << firstPoint + idx2; + aOutFile << ", -1, " << firstPoint + idx2 << ", " << lastPoint << ", " << lastPoint + idx2 << ", -1"; + } + else + { + aOutFile << ", " << firstPoint << ", " << lastPoint << ", " << firstPoint + idx2; + aOutFile << ", -1, " << firstPoint + idx2 << ", " << lastPoint << ", " << lastPoint + idx2 << ", -1"; + } + + ++obeg; + } + + return !aOutFile.fail(); +} + + +// add a triangular facet (triplet) to the ouptut index list +bool VRML_LAYER::addTriplet( VERTEX_3D* p0, VERTEX_3D* p1, VERTEX_3D* p2 ) +{ + double dx0 = p1->x - p0->x; + double dx1 = p2->x - p0->x; + + double dy0 = p1->y - p0->y; + double dy1 = p2->y - p0->y; + + // this number is chosen because we shall only write 6 decimal places + // on the VRML output + double err = 0.000001; + + // test if the triangles are degenerate (parallel sides) + + if( dx0 < err && dx0 > -err && dx1 < err && dx1 > -err ) + return false; + + if( dy0 < err && dy0 > -err && dy1 < err && dy1 > -err ) + return false; + + double sl0 = dy0 / dx0; + double sl1 = dy1 / dx1; + + double dsl = sl1 - sl0; + + if( dsl < err && dsl > -err ) + return false; + + triplets.push_back( TRIPLET_3D( p0->o, p1->o, p2->o ) ); + + return true; +} + + +// add an extra vertex (to be called only by the COMBINE callback) +VERTEX_3D* VRML_LAYER::AddExtraVertex( double aXpos, double aYpos ) +{ + VERTEX_3D* vertex = new VERTEX_3D; + + if( !vertex ) + { + error = "AddExtraVertex(): could not allocate a new vertex"; + return NULL; + } + + if( eidx == 0 ) + eidx = idx + hidx; + + vertex->x = aXpos; + vertex->y = aYpos; + vertex->i = eidx++; + vertex->o = -1; + + extra_verts.push_back( vertex ); + + return vertex; +} + + +// start a GL command list +void VRML_LAYER::glStart( GLenum cmd ) +{ + glcmd = cmd; + + while( !vlist.empty() ) + vlist.pop_back(); +} + + +// process a vertex +void VRML_LAYER::glPushVertex( VERTEX_3D* vertex ) +{ + if( vertex->o < 0 ) + { + vertex->o = ord++; + ordmap.push_back( vertex->i ); + } + + vlist.push_back( vertex ); +} + + +// end a GL command list +void VRML_LAYER::glEnd( void ) +{ + switch( glcmd ) + { + case GL_LINE_LOOP: + { + // add the loop to the list of outlines + std::list* loop = new std::list; + + if( !loop ) + break; + + for( unsigned int i = 0; i < vlist.size(); ++i ) + { + loop->push_back( vlist[i]->o ); + } + + outline.push_back( loop ); + } + break; + + case GL_TRIANGLE_FAN: + processFan(); + break; + + case GL_TRIANGLE_STRIP: + processStrip(); + break; + + case GL_TRIANGLES: + processTri(); + break; + + default: + break; + } + + while( !vlist.empty() ) + vlist.pop_back(); + + glcmd = 0; +} + + +// set the error message +void VRML_LAYER::SetGLError( GLenum errorID ) +{ + error = ""; + error = (const char*)gluGetString( errorID ); + + if( error.empty() ) + { + std::ostringstream ostr; + ostr << "Unknown OpenGL error: " << errorID; + error = ostr.str(); + } +} + + +// process a GL_TRIANGLE_FAN list +void VRML_LAYER::processFan( void ) +{ + if( vlist.size() < 3 ) + return; + + VERTEX_3D* p0 = vlist[0]; + + int i; + int end = vlist.size(); + + for( i = 2; i < end; ++i ) + { + addTriplet( p0, vlist[i - 1], vlist[i] ); + } +} + + +// process a GL_TRIANGLE_STRIP list +void VRML_LAYER::processStrip( void ) +{ + // note: (source: http://www.opengl.org/wiki/Primitive) + // GL_TRIANGLE_STRIP​: Every group of 3 adjacent vertices forms a triangle. + // The face direction of the strip is determined by the winding of the + // first triangle. Each successive triangle will have its effective face + // order reverse, so the system compensates for that by testing it in the + // opposite way. A vertex stream of n length will generate n-2 triangles. + + if( vlist.size() < 3 ) + return; + + int i; + int end = vlist.size(); + bool flip = false; + + for( i = 2; i < end; ++i ) + { + if( flip ) + { + addTriplet( vlist[i - 1], vlist[i - 2], vlist[i] ); + flip = false; + } + else + { + addTriplet( vlist[i - 2], vlist[i - 1], vlist[i] ); + flip = true; + } + } +} + + +// process a GL_TRIANGLES list +void VRML_LAYER::processTri( void ) +{ + // notes: + // 1. each successive group of 3 vertices is a triangle + // 2. as per OpenGL specification, any incomplete triangles are to be ignored + + if( vlist.size() < 3 ) + return; + + int i; + int end = vlist.size(); + + for( i = 2; i < end; i += 3 ) + addTriplet( vlist[i - 2], vlist[i - 1], vlist[i] ); +} + + +// push the internally held vertices +void VRML_LAYER::pushVertices( bool holes ) +{ + // push the internally held vertices + unsigned int i; + + std::list::const_iterator begin; + std::list::const_iterator end; + GLdouble pt[3]; + VERTEX_3D* vp; + + for( i = 0; i < contours.size(); ++i ) + { + if( contours[i]->size() < 3 ) + continue; + + if( ( holes && areas[i] <= 0.0 ) || ( !holes && areas[i] > 0.0 ) ) + continue; + + gluTessBeginContour( tess ); + + begin = contours[i]->begin(); + end = contours[i]->end(); + + while( begin != end ) + { + vp = vertices[ *begin ]; + pt[0] = vp->x; + pt[1] = vp->y; + pt[2] = 0.0; + gluTessVertex( tess, pt, vp ); + ++begin; + } + + gluTessEndContour( tess ); + } +} + + +VERTEX_3D* VRML_LAYER::getVertexByIndex( int aPointIndex, VRML_LAYER* holes ) +{ + if( aPointIndex < 0 || (unsigned int) aPointIndex >= ( idx + hidx + extra_verts.size() ) ) + { + error = "getVertexByIndex():BUG: invalid index"; + return NULL; + } + + if( aPointIndex < idx ) + { + // vertex is in the vertices[] list + return vertices[ aPointIndex ]; + } + else if( aPointIndex >= idx + hidx ) + { + // vertex is in the extra_verts[] list + return extra_verts[aPointIndex - idx - hidx]; + } + + // vertex is in the holes object + if( !holes ) + { + error = "getVertexByIndex():BUG: invalid index"; + return NULL; + } + + VERTEX_3D* vp = holes->GetVertexByIndex( aPointIndex ); + + if( !vp ) + { + std::ostringstream ostr; + ostr << "getVertexByIndex():FAILED: " << holes->GetError(); + error = ostr.str(); + return NULL; + } + + return vp; +} + + +// retrieve the total number of vertices +int VRML_LAYER::GetSize( void ) +{ + return vertices.size(); +} + + +// Inserts all contours into the given tesselator; this results in the +// renumbering of all vertices from 'start'. Returns the end number. +// Take care when using this call since tesselators cannot work on +// the internal data concurrently +int VRML_LAYER::Import( int start, GLUtesselator* tess ) +{ + if( start < 0 ) + { + error = "Import(): invalid index ( start < 0 )"; + return -1; + } + + if( !tess ) + { + error = "Import(): NULL tesselator pointer"; + return -1; + } + + unsigned int i, j; + + // renumber from 'start' + for( i = 0, j = vertices.size(); i < j; ++i ) + { + vertices[i]->i = start++; + vertices[i]->o = -1; + } + + // push each contour to the tesselator + VERTEX_3D* vp; + GLdouble pt[3]; + + std::list::const_iterator cbeg; + std::list::const_iterator cend; + + for( i = 0; i < contours.size(); ++i ) + { + if( contours[i]->size() < 3 ) + continue; + + cbeg = contours[i]->begin(); + cend = contours[i]->end(); + + gluTessBeginContour( tess ); + + while( cbeg != cend ) + { + vp = vertices[ *cbeg++ ]; + pt[0] = vp->x; + pt[1] = vp->y; + pt[2] = 0.0; + gluTessVertex( tess, pt, vp ); + } + + gluTessEndContour( tess ); + } + + return start; +} + + +// return the vertex identified by index +VERTEX_3D* VRML_LAYER::GetVertexByIndex( int aPointIndex ) +{ + int i0 = vertices[0]->i; + + if( aPointIndex < i0 || aPointIndex >= ( i0 + (int) vertices.size() ) ) + { + error = "GetVertexByIndex(): invalid index"; + return NULL; + } + + return vertices[aPointIndex - i0]; +} + + +// return the error string +const std::string& VRML_LAYER::GetError( void ) +{ + return error; +} diff --git a/utils/idftools/vrml_layer.h b/utils/idftools/vrml_layer.h new file mode 100644 index 0000000000..20a315665a --- /dev/null +++ b/utils/idftools/vrml_layer.h @@ -0,0 +1,429 @@ +/* + * file: vrml_layer.h + * + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 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 + */ + +/** + * @file vrml_layer.h + */ + +/* + * Classes and structures to support the tesselation of a + * PCB for VRML output. + */ + +#ifndef VRML_BOARD_H +#define VRML_BOARD_H + +#ifdef __WXMAC__ +# ifdef __DARWIN__ +# include +# else +# include +# endif +#else +# include +#endif + +#include +#include +#include +#include + +#ifndef M_PI2 +#define M_PI2 ( M_PI / 2.0 ) +#endif + +#ifndef M_PI4 +#define M_PI4 ( M_PI / 4.0 ) +#endif + +struct GLUtesselator; + +struct VERTEX_3D +{ + double x; + double y; + int i; // vertex index + int o; // vertex order +}; + +struct TRIPLET_3D +{ + int i1, i2, i3; + + TRIPLET_3D( int p1, int p2, int p3 ) + { + i1 = p1; + i2 = p2; + i3 = p3; + } +}; + + +class VRML_LAYER +{ +private: + // Arc parameters + int maxArcSeg; // maximum number of arc segments in a small circle + double minSegLength; // min. segment length + double maxSegLength; // max. segment length + + bool fix; // when true, no more vertices may be added by the user + int idx; // vertex index (number of contained vertices) + int ord; // vertex order (number of ordered vertices) + std::vector vertices; // vertices of all contours + std::vector*> contours; // lists of vertices for each contour + std::vector< double > areas; // area of the contours (positive if winding is CCW) + std::list triplets; // output facet triplet list (triplet of ORDER values) + std::list*> outline; // indices for outline outputs (index by ORDER values) + std::vector ordmap; // mapping of ORDER to INDEX + + std::string error; // error message + + int hidx; // number of vertices in the holes + int eidx; // index for extra vertices + std::vector extra_verts; // extra vertices added for outlines and facets + std::vector vlist; // vertex list for the GL command in progress + VRML_LAYER* pholes; // pointer to another layer object used for tesselation; + // this object is normally expected to hold only holes + + GLUtesselator* tess; // local instance of the GLU tesselator + + GLenum glcmd; // current GL command type ( fan, triangle, tri-strip, loop ) + + void clearTmp( void ); // clear ephemeral data used by the tesselation routine + + // add a triangular facet (triplet) to the output index list + bool addTriplet( VERTEX_3D* p0, VERTEX_3D* p1, VERTEX_3D* p2 ); + + // retrieve a vertex given its index; the vertex may be contained in the + // vertices vector, extra_verts vector, or foreign VRML_LAYER object + VERTEX_3D* getVertexByIndex( int aPointIndex, VRML_LAYER* holes ); + + void processFan( void ); // process a GL_TRIANGLE_FAN list + void processStrip( void ); // process a GL_TRIANGLE_STRIP list + void processTri( void ); // process a GL_TRIANGLES list + + void pushVertices( bool holes ); // push the internal vertices + bool pushOutline( VRML_LAYER* holes ); // push the outline vertices + + // calculate number of sides on an arc (angle is in radians) + int calcNSides( double aRadius, double aAngle ); + +public: + /// set to true when a fault is encountered during tesselation + bool Fault; + + VRML_LAYER(); + virtual ~VRML_LAYER(); + + /** + * Function GetArcParams + * retieves the parameters used in calculating the number of vertices in an arc + * + * @param aMaxSeg is the maximum number of segments for an arc with cords of length aMinLength + * @param aMinLength is the minimum length of cords in an arc + * @param aMaxLength is the maximum length of cords in an arc + */ + void GetArcParams( int& aMaxSeg, double& aMinLength, double& aMaxLength ); + + /** + * Function SetArcParams + * sets the parameters used in calculating the number of vertices in an arc. + * The default settings are reasonable for rendering for unit lengths of 1mm + * + * @param aMaxSeg is the maximum number of segments for an arc with cords of length aMinLength + * @param aMinLength is the minimum length of cords in an arc + * @param aMaxLength is the maximum length of cords in an arc + * + * @return bool: true if the parameters were accepted + */ + bool SetArcParams( int aMaxSeg, double aMinLength, double aMaxLength ); + + /** + * Function Clear + * erases all data except for arc parameters. + */ + void Clear( void ); + + /** + * Function GetSize + * returns the total number of vertices indexed + */ + int GetSize( void ); + + /** + * Function GetNConours + * returns the number of stored contours + */ + int GetNContours( void ) + { + return contours.size(); + } + + /** + * Function NewContour + * creates a new list of vertices and returns an index to the list + * + * @return int: index to the list or -1 if the operation failed + */ + int NewContour( void ); + + /** + * Function AddVertex + * adds a point to the requested contour + * + * @param aContour is an index previously returned by a call to NewContour() + * @param aXpos is the X coordinate of the vertex + * @param aYpos is the Y coordinate of the vertex + * + * @return bool: true if the vertex was added + */ + bool AddVertex( int aContourID, double aXpos, double aYpos ); + + /** + * Function EnsureWinding + * checks the winding of a contour and ensures that it is a hole or + * a solid depending on the value of @param hole + * + * @param aContour is an index to a contour as returned by NewContour() + * @param aHoleFlag determines if the contour must be a hole + * + * @return bool: true if the operation suceeded + */ + bool EnsureWinding( int aContourID, bool aHoleFlag ); + + /** + * Function AppendCircle + * adds a circular contour to the specified (empty) contour + * + * @param aXpos is the X coordinate of the hole center + * @param aYpos is the Y coordinate of the hole center + * @param aRadius is the radius of the hole + * @param aContourID is the contour index + * @param aHoleFlag determines if the contour to be created is a cutout + * + * @return bool: true if the new contour was successfully created + */ + bool AppendCircle( double aXpos, double aYpos, double aRadius, int aContourID, bool aHoleFlag = false ); + + /** + * Function AddCircle + * creates a circular contour and adds it to the internal list + * + * @param aXpos is the X coordinate of the hole center + * @param aYpos is the Y coordinate of the hole center + * @param aRadius is the radius of the hole + * @param aHoleFlag determines if the contour to be created is a cutout + * + * @return bool: true if the new contour was successfully created + */ + bool AddCircle( double aXpos, double aYpos, double aRadius, bool aHoleFlag = false ); + + /** + * Function AddSlot + * creates and adds a slot feature to the list of contours + * + * @param aCenterX is the X coordinate of the slot's center + * @param aCenterY is the Y coordinate of the slot's center + * @param aSlotLength is the length of the slot along the major axis + * @param aSlotWidth is the width of the slot along the minor axis + * @param aAngle (degrees) is the orientation of the slot + * @param aHoleFlag determines whether the slot is a hole or a solid + * + * @return bool: true if the slot was successfully created + */ + bool AddSlot( double aCenterX, double aCenterY, double aSlotLength, double aSlotWidth, + double aAngle, bool aHoleFlag = false ); + + /** + * Function AppendArc + * adds an arc to the specified contour + * + * @param aCenterX is the X coordinate of the arc's center + * @param aCenterY is the Y coordinate of the arc's center + * @param aRadius is the radius of the arc + * @param aStartAngle (degrees) is the starting angle of the arc + * @param aAngle (degrees) is the measure of the arc + * @param aContourID is the contour's index + * + * @return bool: true if the slot was successfully created + */ + bool AppendArc( double aCenterX, double aCenterY, double aRadius, + double aStartAngle, double aAngle, int aContourID ); + + /** + * Function AddArc + * creates a slotted arc and adds it to the internal list of contours + * + * @param aCenterX is the X coordinate of the arc's center + * @param aCenterY is the Y coordinate of the arc's center + * @param aStartX is the X coordinate of the starting point + * @param aStartY is the Y coordinate of the starting point + * @param aArcWidth is the width of the arc + * @param aAngle is the included angle (degrees) + * @param aHoleFlag determines whether the arc is to be a hole or a solid + * + * @return bool: true if the feature was successfully created + */ + bool AddArc( double aCenterX, double aCenterY, double aStartX, double aStartY, + double aArcWidth, double aAngle, bool aHoleFlag = false ); + + /** + * Function Tesselate + * creates a list of outline vertices as well as the + * vertex sets required to render the surface. + * + * @param holes is an optional pointer to cutouts to be imposed on the + * surface. + * + * @return bool: true if the operation succeeded + */ + bool Tesselate( VRML_LAYER* holes = NULL ); + + /** + * Function WriteVertices + * writes out the list of vertices required to render a + * planar surface. + * + * @param aZcoord is the Z coordinate of the plane + * @param aOutFile is the file to write to + * @param aPrecision is the precision of the output coordinates + * + * @return bool: true if the operation succeeded + */ + bool WriteVertices( double aZcoord, std::ofstream& aOutFile, int aPrecision ); + + /** + * Function Write3DVertices + * writes out the list of vertices required to render an extruded solid + * + * @param aTopZ is the Z coordinate of the top plane + * @param aBottomZ is the Z coordinate of the bottom plane + * @param aOutFile is the file to write to + * @param aPrecision is the precision of the output coordinates + * + * @return bool: true if the operation succeeded + */ + bool Write3DVertices( double aTopZ, double aBottomZ, std::ofstream& aOutFile, int aPrecision ); + + /** + * Function WriteIndices + * writes out the vertex sets required to render a planar + * surface. + * + * @param aTopFlag is true if the surface is to be visible from above; + * if false the surface will be visible from below. + * @param aOutFile is the file to write to + * + * @return bool: true if the operation succeeded + */ + bool WriteIndices( bool aTopFlag, std::ofstream& aOutFile ); + + /** + * Function Write3DIndices + * writes out the vertex sets required to render an extruded solid + * + * @param aOutFile is the file to write to + * + * @return bool: true if the operation succeeded + */ + bool Write3DIndices( std::ofstream& aOutFile ); + + /** + * Function AddExtraVertex + * adds an extra vertex as required by the GLU tesselator + * + * @return VERTEX_3D*: is the new vertex or NULL if a vertex + * could not be created. + */ + VERTEX_3D* AddExtraVertex( double aXpos, double aYpos ); + + /** + * Function glStart + * is invoked by the GLU tesselator callback to notify this object + * of the type of GL command which is applicable to the upcoming + * vertex list. + * + * @param cmd is the GL command + */ + void glStart( GLenum cmd ); + + /** + * Function glPushVertex + * is invoked by the GLU tesselator callback; the supplied vertex is + * added to the internal list of vertices awaiting processing upon + * execution of glEnd() + * + * @param vertex is a vertex forming part of the GL command as previously + * set by glStart + */ + void glPushVertex( VERTEX_3D* vertex ); + + /** + * Function glEnd + * is invoked by the GLU tesselator callback to notify this object + * that the vertex list is complete and ready for processing + */ + void glEnd( void ); + + /** + * Function SetGLError + * sets the error message according to the specified OpenGL error + */ + void SetGLError( GLenum error_id ); + + /** + * Function Import + * inserts all contours into the given tesselator; this + * results in the renumbering of all vertices from @param start. + * Take care when using this call since tesselators cannot work on + * the internal data concurrently. + * + * @param start is the starting number for vertex indices + * @param tess is a pointer to a GLU Tesselator object + * + * @return int: the number of vertices exported + */ + int Import( int start, GLUtesselator* tess ); + + /** + * Function GetVertexByIndex + * returns a pointer to the requested vertex or + * NULL if no such vertex exists. + * + * @param aPointIndex is a vertex index + * + * @return VERTEX_3D*: the requested vertex or NULL + */ + VERTEX_3D* GetVertexByIndex( int aPointIndex ); + + /* + * Function GetError + * Returns the error message related to the last failed operation + */ + const std::string& GetError( void ); +}; + +#endif // VRML_BOARD_H