diff options
Diffstat (limited to 'utils')
26 files changed, 18035 insertions, 0 deletions
diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt new file mode 100644 index 0000000..04489b3 --- /dev/null +++ b/utils/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory( idftools ) + diff --git a/utils/idftools/CMakeLists.txt b/utils/idftools/CMakeLists.txt new file mode 100644 index 0000000..4334403 --- /dev/null +++ b/utils/idftools/CMakeLists.txt @@ -0,0 +1,36 @@ +include_directories( + "${CMAKE_SOURCE_DIR}/lib_dxf" + "${CMAKE_SOURCE_DIR}/utils/idftools" + ${OPENGL_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} + ) + +link_directories( + "${CMAKE_BINARY_DIR}/lib_dxf" + ) + +add_library( idf3 STATIC + idf_helpers.cpp idf_common.cpp idf_outlines.cpp + idf_parser.cpp vrml_layer.cpp ) + +add_executable( idfcyl idf_cylinder.cpp ) +add_executable( idfrect idf_rect.cpp ) +add_executable( dxf2idf dxf2idfmain.cpp dxf2idf.cpp ) +add_executable( idf2vrml idf2vrml.cpp ) + +add_dependencies( idf2vrml boost ) + +target_link_libraries( dxf2idf lib_dxf idf3 ${wxWidgets_LIBRARIES} ) + +target_link_libraries( idf2vrml idf3 ${OPENGL_LIBRARIES} ${wxWidgets_LIBRARIES} ) + +if( APPLE ) + # puts binaries into the *.app bundle while linking + set_target_properties( idfcyl idfrect dxf2idf idf2vrml PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${OSX_BUNDLE_BUILD_BIN_DIR} + ) +else() + install( TARGETS idfcyl idfrect dxf2idf idf2vrml + DESTINATION ${KICAD_BIN} + COMPONENT binary ) +endif() diff --git a/utils/idftools/dxf2idf.cpp b/utils/idftools/dxf2idf.cpp new file mode 100644 index 0000000..7147cda --- /dev/null +++ b/utils/idftools/dxf2idf.cpp @@ -0,0 +1,460 @@ +/* + * 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 <cstdio> +#include <iostream> +#include <libdxfrw.h> +#include <dxf2idf.h> + +// differences in angle smaller than MIN_ANG are considered equal +#define MIN_ANG (0.01) + +// min and max bulge bracketing min. arc before transition to line segment +// and max. arc limit +// MIN_BULGE = 0.002 ~0.45 degrees +// MAX_BULGE = 2000 ~89.97 degrees +#define MIN_BULGE 0.002 +#define MAX_BULGE 2000.0 + +DXF2IDF::~DXF2IDF() +{ + while( !lines.empty() ) + { +#ifdef DEBUG_IDF + IDF3::printSeg( lines.back() ); +#endif + delete lines.back(); + lines.pop_back(); + } +} + + +bool DXF2IDF::ReadDxf( const std::string aFile ) +{ + dxfRW* reader = new dxfRW( aFile.c_str() ); + + if( !reader ) + return false; + + bool success = reader->read( this, true ); + + delete reader; + return success; +} + + +void DXF2IDF::addLine( const DRW_Line& data ) +{ + IDF_POINT p1, p2; + + p1.x = data.basePoint.x * m_scale; + p1.y = data.basePoint.y * m_scale; + p2.x = data.secPoint.x * m_scale; + p2.y = data.secPoint.y * m_scale; + + insertLine( p1, p2 ); + return; +} + + +void DXF2IDF::addCircle( const DRW_Circle& data ) +{ + IDF_POINT p1, p2; + + p1.x = data.basePoint.x * m_scale; + p1.y = data.basePoint.y * m_scale; + + p2.x = p1.x + data.radious * m_scale; + p2.y = p1.y; + + IDF_SEGMENT* seg = new IDF_SEGMENT( p1, p2, 360, true ); + + if( seg ) + lines.push_back( seg ); + + return; +} + + +void DXF2IDF::addArc( const DRW_Arc& data ) +{ + IDF_POINT p1, p2; + + p1.x = data.basePoint.x * m_scale; + p1.y = data.basePoint.y * m_scale; + + // note: DXF circles always run CCW + double ea = data.endangle; + + while( ea < data.staangle ) + ea += M_PI; + + p2.x = p1.x + cos( data.staangle ) * data.radious * m_scale; + p2.y = p1.y + sin( data.staangle ) * data.radious * m_scale; + + double angle = ( ea - data.staangle ) * 180.0 / M_PI; + + IDF_SEGMENT* seg = new IDF_SEGMENT( p1, p2, angle, true ); + + if( seg ) + lines.push_back( seg ); + + return; +} + + +bool DXF2IDF::WriteOutline( FILE* aFile, bool isInch ) +{ + if( lines.empty() ) + { + std::cerr << "* DXF2IDF: empty outline\n"; + return false; + } + + // 1. find lowest X value + // 2. string an outline together + // 3. emit warnings if more than 1 outline + IDF_OUTLINE outline; + + IDF3::GetOutline( lines, outline ); + + if( outline.empty() ) + { + std::cerr << "* DXF2IDF::WriteOutline(): no valid outline in file\n"; + return false; + } + + if( !lines.empty() ) + { + std::cerr << "* DXF2IDF::WriteOutline(): WARNING: more than 1 outline in file\n"; + std::cerr << "* Only the first outline will be used\n"; + } + + char loopDir = '1'; + + if( outline.IsCCW() ) + loopDir = '0'; + + std::list<IDF_SEGMENT*>::iterator bo; + std::list<IDF_SEGMENT*>::iterator eo; + + if( outline.size() == 1 ) + { + if( !outline.front()->IsCircle() ) + { + std::cerr << "* DXF2IDF::WriteOutline(): bad outline\n"; + return false; + } + + // NOTE: a circle always has an angle of 360, never -360, + // otherwise SolidWorks chokes on the file. + if( isInch ) + { + fprintf( aFile, "%c %d %d 0\n", loopDir, + (int) (1000 * outline.front()->startPoint.x), + (int) (1000 * outline.front()->startPoint.y) ); + fprintf( aFile, "%c %d %d 360\n", loopDir, + (int) (1000 * outline.front()->endPoint.x), + (int) (1000 * outline.front()->endPoint.y) ); + } + else + { + fprintf( aFile, "%c %.3f %.3f 0\n", loopDir, + outline.front()->startPoint.x, outline.front()->startPoint.y ); + fprintf( aFile, "%c %.3f %.3f 360\n", loopDir, + outline.front()->endPoint.x, outline.front()->endPoint.y ); + } + + return true; + } + + // ensure that the very last point is the same as the very first point + outline.back()-> endPoint = outline.front()->startPoint; + + bo = outline.begin(); + eo = outline.end(); + + // for the first item we write out both points + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + if( isInch ) + { + fprintf( aFile, "%c %d %d 0\n", loopDir, + (int) (1000 * (*bo)->startPoint.x), + (int) (1000 * (*bo)->startPoint.y) ); + fprintf( aFile, "%c %d %d 0\n", loopDir, + (int) (1000 * (*bo)->endPoint.x), + (int) (1000 * (*bo)->endPoint.y) ); + } + else + { + fprintf( aFile, "%c %.3f %.3f 0\n", loopDir, + (*bo)->startPoint.x, (*bo)->startPoint.y ); + fprintf( aFile, "%c %.3f %.3f 0\n", loopDir, + (*bo)->endPoint.x, (*bo)->endPoint.y ); + } + } + else + { + if( isInch ) + { + fprintf( aFile, "%c %d %d 0\n", loopDir, + (int) (1000 * (*bo)->startPoint.x), + (int) (1000 * (*bo)->startPoint.y) ); + fprintf( aFile, "%c %d %d %.2f\n", loopDir, + (int) (1000 * (*bo)->endPoint.x), + (int) (1000 * (*bo)->endPoint.y), + (*bo)->angle ); + } + else + { + fprintf( aFile, "%c %.3f %.3f 0\n", loopDir, + (*bo)->startPoint.x, (*bo)->startPoint.y ); + fprintf( aFile, "%c %.3f %.3f %.2f\n", loopDir, + (*bo)->endPoint.x, (*bo)->endPoint.y, (*bo)->angle ); + } + } + + ++bo; + + // for all other segments we only write out the last point + while( bo != eo ) + { + if( isInch ) + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + fprintf( aFile, "%c %d %d 0\n", loopDir, + (int) (1000 * (*bo)->endPoint.x), + (int) (1000 * (*bo)->endPoint.y) ); + } + else + { + fprintf( aFile, "%c %d %d %.2f\n", loopDir, + (int) (1000 * (*bo)->endPoint.x), + (int) (1000 * (*bo)->endPoint.y), + (*bo)->angle ); + } + } + else + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + fprintf( aFile, "%c %.5f %.5f 0\n", loopDir, + (*bo)->endPoint.x, (*bo)->endPoint.y ); + } + else + { + fprintf( aFile, "%c %.5f %.5f %.2f\n", loopDir, + (*bo)->endPoint.x, (*bo)->endPoint.y, (*bo)->angle ); + } + } + + ++bo; + } + + return true; +} + + +void DXF2IDF::addHeader( const DRW_Header* data ) +{ + std::map<std::string, DRW_Variant*>::const_iterator it; + m_scale = 1.0; // assume no scale factor + + for( it = data->vars.begin(); it != data->vars.end(); ++it ) + { + std::string key = ( (*it).first ).c_str(); + + if( key == "$INSUNITS" ) + { + DRW_Variant* var = (*it).second; + + switch( var->content.i ) + { + case 1: // inches + m_scale = 25.4; + break; + + case 2: // feet + m_scale = 304.8; + break; + + case 5: // centimeters + m_scale = 10.0; + break; + + case 6: // meters + m_scale = 1000.0; + break; + + case 8: // microinches + m_scale = 2.54e-5; + break; + + case 9: // mils + m_scale = 0.0254; + break; + + case 10: // yards + m_scale = 914.4; + break; + + case 11: // Angstroms + m_scale = 1.0e-7; + break; + + case 12: // nanometers + m_scale = 1.0e-6; + break; + + case 13: // micrometers + m_scale = 1.0e-3; + break; + + case 14: // decimeters + m_scale = 100.0; + break; + + default: + // use the default of 1.0 for: + // 0: Unspecified Units + // 4: mm + // 3: miles + // 7: kilometers + // 15: decameters + // 16: hectometers + // 17: gigameters + // 18: AU + // 19: lightyears + // 20: parsecs + break; + } + } + } +} + + +void DXF2IDF::addLWPolyline(const DRW_LWPolyline& data ) +{ + IDF_POINT poly_start; + IDF_POINT seg_start; + IDF_POINT seg_end; + double bulge = 0.0; + + if( !data.vertlist.empty() ) + { + DRW_Vertex2D* vertex = data.vertlist[0]; + seg_start.x = vertex->x * m_scale; + seg_start.y = vertex->y * m_scale; + poly_start = seg_start; + bulge = vertex->bulge; + } + + for( size_t i = 1; i < data.vertlist.size(); ++i ) + { + DRW_Vertex2D* vertex = data.vertlist[i]; + seg_end.x = vertex->x * m_scale; + seg_end.y = vertex->y * m_scale; + + if( std::abs( bulge ) < MIN_BULGE ) + insertLine( seg_start, seg_end ); + else + insertArc( seg_start, seg_end, bulge ); + + seg_start = seg_end; + bulge = vertex->bulge; + } + + // Polyline flags bit 0 indicates closed (1) or open (0) polyline + if( data.flags & 1 ) + { + if( std::abs( bulge ) < MIN_BULGE ) + insertLine( seg_start, poly_start ); + else + insertArc( seg_start, poly_start, bulge ); + } + + return; +} + + +void DXF2IDF::addPolyline(const DRW_Polyline& data ) +{ + IDF_POINT poly_start; + IDF_POINT seg_start; + IDF_POINT seg_end; + + if( !data.vertlist.empty() ) + { + DRW_Vertex* vertex = data.vertlist[0]; + seg_start.x = vertex->basePoint.x * m_scale; + seg_start.y = vertex->basePoint.y * m_scale; + poly_start = seg_start; + } + + for( size_t i = 1; i < data.vertlist.size(); ++i ) + { + DRW_Vertex* vertex = data.vertlist[i]; + seg_end.x = vertex->basePoint.x * m_scale; + seg_end.y = vertex->basePoint.y * m_scale; + insertLine( seg_start, seg_end ); + seg_start = seg_end; + } + + // Polyline flags bit 0 indicates closed (1) or open (0) polyline + if( data.flags & 1 ) + insertLine( seg_start, poly_start ); + + return; +} + + +void DXF2IDF::insertLine( const IDF_POINT& aSegStart, const IDF_POINT& aSegEnd ) +{ + IDF_SEGMENT* seg = new IDF_SEGMENT( aSegStart, aSegEnd ); + + if( seg ) + lines.push_back( seg ); + + return; +} + + +void DXF2IDF::insertArc( const IDF_POINT& aSegStart, const IDF_POINT& aSegEnd, + double aBulge ) +{ + if( aBulge < -MAX_BULGE ) + aBulge = -MAX_BULGE; + else if( aBulge > MAX_BULGE ) + aBulge = MAX_BULGE; + + double ang = 720.0 * atan( aBulge ) / M_PI; + + IDF_SEGMENT* seg = new IDF_SEGMENT( aSegStart, aSegEnd, ang, false ); + + if( seg ) + lines.push_back( seg ); + + return; +} diff --git a/utils/idftools/dxf2idf.h b/utils/idftools/dxf2idf.h new file mode 100644 index 0000000..2eb8ead --- /dev/null +++ b/utils/idftools/dxf2idf.h @@ -0,0 +1,102 @@ +/* + * 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 DXF2IDF_H +#define DXF2IDF_H + +#include <string> +#include <drw_interface.h> +#include <idf_common.h> + +class DXF2IDF : public DRW_Interface +{ +private: + std::list< IDF_SEGMENT* > lines; // Unsorted list of graphical segments + double m_scale; // scaling factor to mm + + void insertLine( const IDF_POINT& aSegStart, const IDF_POINT& aSegEnd ); + void insertArc( const IDF_POINT& aSegStart, const IDF_POINT& aSegEnd, double aBulge ); + +public: + ~DXF2IDF(); + + bool ReadDxf( const std::string aFile ); + bool WriteOutline( FILE* aFile, bool isInch ); + +private: + // DRW_Interface implemented callback functions + virtual void addHeader( const DRW_Header* data ); + virtual void addLine(const DRW_Line& data); + virtual void addArc(const DRW_Arc& data ); + virtual void addCircle(const DRW_Circle& data ); + virtual void addLWPolyline(const DRW_LWPolyline& data ); + virtual void addPolyline(const DRW_Polyline& data ); + + // DRW_Interface callbacks unsupported by DXF2IDF + virtual void addLType( const DRW_LType& data ){} + virtual void addLayer( const DRW_Layer& data ){} + virtual void addDimStyle( const DRW_Dimstyle& data ){} + virtual void addVport(const DRW_Vport& data){} + virtual void addTextStyle(const DRW_Textstyle& data){} + virtual void addBlock(const DRW_Block& data ){} + virtual void setBlock(const int handle){} + virtual void endBlock(){} + virtual void addPoint(const DRW_Point& data ){} + virtual void addRay(const DRW_Ray& data ){} + virtual void addXline(const DRW_Xline& data ){} + virtual void addEllipse(const DRW_Ellipse& data ){} + virtual void addSpline(const DRW_Spline* data ){} + virtual void addKnot(const DRW_Entity&){} + virtual void addInsert(const DRW_Insert& data ){} + virtual void addTrace(const DRW_Trace& data ){} + virtual void add3dFace(const DRW_3Dface& data ){} + virtual void addSolid(const DRW_Solid& data ){} + virtual void addMText(const DRW_MText& data){} + virtual void addText(const DRW_Text& data ){} + virtual void addDimAlign(const DRW_DimAligned *data ){} + virtual void addDimLinear(const DRW_DimLinear *data ){} + virtual void addDimRadial(const DRW_DimRadial *data ){} + virtual void addDimDiametric(const DRW_DimDiametric *data ){} + virtual void addDimAngular(const DRW_DimAngular *data ){} + virtual void addDimAngular3P(const DRW_DimAngular3p *data ){} + virtual void addDimOrdinate(const DRW_DimOrdinate *data ){} + virtual void addLeader(const DRW_Leader *data ){} + virtual void addHatch(const DRW_Hatch* data ){} + virtual void addViewport(const DRW_Viewport& data){} + virtual void addImage(const DRW_Image* data ){} + virtual void linkImage(const DRW_ImageDef* data ){} + virtual void addComment(const char*){} + virtual void writeHeader(DRW_Header& data){} + virtual void writeBlocks(){} + virtual void writeBlockRecords(){} + virtual void writeEntities(){} + virtual void writeLTypes(){} + virtual void writeLayers(){} + virtual void writeTextstyles(){} + virtual void writeVports(){} + virtual void writeDimstyles(){} + virtual void addAppId( const DRW_AppId& data ) {} + virtual void writeAppId() {} +}; + +#endif // DXF2IDF_H diff --git a/utils/idftools/dxf2idfmain.cpp b/utils/idftools/dxf2idfmain.cpp new file mode 100644 index 0000000..df804c1 --- /dev/null +++ b/utils/idftools/dxf2idfmain.cpp @@ -0,0 +1,190 @@ +/* + * 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 <cstdio> +#include <iostream> +#include <sstream> +#include <string> +#include <list> +#include <dxf2idf.h> + +using namespace std; + +int main( int argc, char **argv ) +{ + list< string > comments; + string line; + stringstream tstr; + + string dname; // DXF filename + string gname; // Geometry Name + string pname; // Part Name + double height; // extrusion height + bool inch = false; // true = inches, false = mm + bool ok; + + if( argc == 1 ) + { + // no arguments; print out usage information + cout << "dxf2idf: this program takes line, arc, and circle segments\n"; + cout << " from a DXF file and creates an IDF component outline file.\n\n"; + cout << "Input:\n"; + cout << " DXF filename: the input file, must end in '.dxf'\n"; + cout << " Units: mm, in (millimeters or inches)\n"; + cout << " Geometry Name: string, as per IDF version 3.0 specification\n"; + cout << " Part Name: as per IDF version 3.0 specification of Part Number\n"; + cout << " Height: extruded height of the outline\n"; + cout << " Comments: all non-empty lines are comments to be added to\n"; + cout << " the IDF file. An empty line signifies the end of\n"; + cout << " the comment block.\n"; + cout << " File name: output filename, must end in '.idf'\n\n"; + } + + line.clear(); + while( line.empty() || line.find( ".dxf" ) == string::npos ) + { + cout << "* DXF filename: "; + + line.clear(); + std::getline( cin, line ); + } + dname = line; + + line.clear(); + while( line.compare( "mm" ) && line.compare( "in" ) + && line.compare( "MM" ) && line.compare( "IN" ) ) + { + cout << "* Units (mm,in): "; + line.clear(); + std::getline( cin, line ); + } + + if( line.compare( "mm" ) && line.compare( "MM" ) ) + inch = true; + + line.clear(); + while( line.empty() ) + { + cout << "* Geometry name: "; + line.clear(); + std::getline( cin, line ); + + if( line.find( "\"" ) != string::npos ) + { + cerr << "[INFO] geometry name may not contain quotation marks\n"; + line.clear(); + } + } + gname = line; + + line.clear(); + while( line.empty() ) + { + cout << "* Part name: "; + line.clear(); + std::getline( cin, line ); + + if( line.find( "\"" ) != string::npos ) + { + cerr << "[INFO] part name may not contain quotation marks\n"; + line.clear(); + } + } + pname = line; + + ok = false; + while( !ok ) + { + cout << "* Height: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> height; + if( !tstr.fail() && height > 0.001 ) + ok = true; + } + + cout << "* COMMENTS: any non-blank line is a comment;\n"; + cout << " a blank line signifies the end of comments.\n"; + ok = false; + while( !ok ) + { + line.clear(); + std::getline( cin, line ); + + if( line.empty() ) + { + ok = true; + } + else + { + if( line[0] != '#' ) + line.insert( 0, "# " ); + + comments.push_back( line ); + } + } + + line.clear(); + while( line.empty() || line.find( ".idf" ) == string::npos ) + { + cout << "* File name (*.idf): "; + + line.clear(); + std::getline( cin, line ); + } + + DXF2IDF dxf; + + dxf.ReadDxf( dname.c_str() ); + + FILE* fp = fopen( line.c_str(), "w" ); + + list< string >::const_iterator scom = comments.begin(); + list< string >::const_iterator ecom = comments.end(); + + while( scom != ecom ) + { + fprintf( fp, "%s\n", (*scom).c_str() ); + ++scom; + } + + fprintf( fp, ".ELECTRICAL\n" ); + + if( inch ) + fprintf( fp, "\"%s\" \"%s\" THOU %d\n", gname.c_str(), + pname.c_str(), (int) (height * 1000.0) ); + else + fprintf( fp, "\"%s\" \"%s\" MM %.3f\n", gname.c_str(), + pname.c_str(), height ); + + dxf.WriteOutline( fp, inch ); + + fprintf( fp, ".END_ELECTRICAL\n" ); + + return 0; +}
\ No newline at end of file diff --git a/utils/idftools/idf2vrml.cpp b/utils/idftools/idf2vrml.cpp new file mode 100644 index 0000000..df66e4b --- /dev/null +++ b/utils/idftools/idf2vrml.cpp @@ -0,0 +1,990 @@ +/* + * 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 <iostream> +#include <iomanip> +#include <fstream> +#include <string> +#include <sstream> +#include <cmath> +#include <cstdio> +#include <cerrno> +#include <list> +#include <utility> +#include <clocale> +#include <vector> +#include <cstdlib> +#include <cstring> +#include <algorithm> +#include <libgen.h> +#include <unistd.h> +#include <boost/ptr_container/ptr_map.hpp> + +#include <idf_helpers.h> +#include <idf_common.h> +#include <idf_parser.h> +#include <vrml_layer.h> + +#ifndef MIN_ANG +#define MIN_ANG 0.01 +#endif + +extern char* optarg; +extern int optopt; + +using namespace std; +using namespace boost; + +#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 MakeOtherOutlines( IDF3_BOARD& board, std::ofstream& file ); +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( boost::ptr_map<const std::string, VRML_IDS>& cmap, + int& index, const std::string& uid ); + + +void PrintUsage( void ) +{ + cout << "-\nUsage: idf2vrml -f input_file.emn -s scale_factor {-k} {-d} {-z} {-m}\n"; + cout << "flags:\n"; + cout << " -k: produce KiCad-friendly VRML output; default is compact VRML\n"; + cout << " -d: suppress substitution of default outlines\n"; + cout << " -z: suppress rendering of zero-height outlines\n"; + cout << " -m: print object mapping to stdout for debugging purposes\n"; + cout << "example to produce a model for use by KiCad: idf2vrml -f input.emn -s 0.3937008 -k\n\n"; + return; +} + +bool nozeroheights; +bool showObjectMapping; + +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; + bool nooutlinesubs = false; + int ichar; + + nozeroheights = false; + showObjectMapping = false; + + while( ( ichar = getopt( argc, argv, ":f:s:kdzm" ) ) != -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 'd': + nooutlinesubs = true; + break; + + case 'z': + nozeroheights = true; + break; + + case 'm': + showObjectMapping = true; + 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() ), nooutlinesubs ) ) + { + cerr << "** Failed to read IDF data:\n"; + cerr << pcb.GetError() << "\n\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 ); + + // STEP 3: Render the OTHER outlines + MakeOtherOutlines( pcb, ofile ); + + 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() - 1; + + while( nvcont > 0 ) + vpcb.EnsureWinding( nvcont--, true ); + + // Add the drill holes + const std::list<IDF_DRILL_DATA*>* drills = &board.GetBoardDrills(); + + std::list<IDF_DRILL_DATA*>::const_iterator sd = drills->begin(); + std::list<IDF_DRILL_DATA*>::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<IDF_SEGMENT*>::iterator sseg; + std::list<IDF_SEGMENT*>::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"; + + // 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; + + boost::ptr_map< const std::string, VRML_IDS> cmap; // map colors by outline UID + VRML_IDS* vcp; + IDF3_COMP_OUTLINE* pout; + + 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 ) + { + if( (*so)->GetOutline()->GetThickness() < 0.00000001 && nozeroheights ) + { + vpcb.Clear(); + ++so; + continue; + } + + (*so)->GetOffsets( tX, tY, tZ, tA ); + tX += vX; + tY += vY; + tA += vA; + + if( ( pout = (IDF3_COMP_OUTLINE*)((*so)->GetOutline()) ) ) + { + vcp = GetColor( cmap, cidx, pout->GetUID() ); + } + else + { + vpcb.Clear(); + ++so; + continue; + } + + 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 ); + + int nvcont = vpcb.GetNContours() - 1; + + while( nvcont > 0 ) + vpcb.EnsureWinding( nvcont--, true ); + + 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; + + // note: this can happen because IDF allows some negative heights/thicknesses + if( bot > top ) + std::swap( bot, top ); + + WriteTriangles( file, vcp, &vpcb, false, + false, top, bot, board.GetUserPrecision(), compact ); + + vpcb.Clear(); + ++so; + } + + ++sc; + } + + return true; +} + + +VRML_IDS* GetColor( boost::ptr_map<const std::string, VRML_IDS>& 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) + + boost::ptr_map<const std::string, VRML_IDS>::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(); + + if( showObjectMapping ) + cout << "* " << ostr.str() << " = '" << uid << "'\n"; + + cmap.insert( uid, id ); + + if( index >= NCOLORS ) + index = 2; + + return id; + } + + return cit->second; +} + + +bool MakeOtherOutlines( IDF3_BOARD& board, std::ofstream& file ) +{ + 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, OTHER_OUTLINE* >*const comp = board.GetOtherOutlines(); + std::map< std::string, OTHER_OUTLINE* >::const_iterator sc = comp->begin(); + std::map< std::string, OTHER_OUTLINE* >::const_iterator ec = comp->end(); + + double top, bot; + bool bottom; + int nvcont; + + boost::ptr_map< const std::string, VRML_IDS> cmap; // map colors by outline UID + VRML_IDS* vcp; + OTHER_OUTLINE* pout; + + while( sc != ec ) + { + pout = sc->second; + + if( pout->GetThickness() < 0.00000001 && nozeroheights ) + { + vpcb.Clear(); + ++sc; + continue; + } + + vcp = GetColor( cmap, cidx, pout->GetOutlineIdentifier() ); + + if( !PopulateVRML( vpcb, pout->GetOutlines(), false, + board.GetUserScale(), 0, 0, 0 ) ) + { + return false; + } + + vpcb.EnsureWinding( 0, false ); + + nvcont = vpcb.GetNContours() - 1; + + while( nvcont > 0 ) + vpcb.EnsureWinding( nvcont--, true ); + + vpcb.Tesselate( NULL ); + + if( pout->GetSide() == IDF3::LYR_BOTTOM ) + bottom = true; + else + bottom = false; + + if( bottom ) + { + top = -thick; + bot = ( top - pout->GetThickness() ) * scale; + top *= scale; + } + else + { + bot = thick; + top = (bot + pout->GetThickness() ) * scale; + bot *= scale; + } + + // note: this can happen because IDF allows some negative heights/thicknesses + if( bot > top ) + std::swap( bot, top ); + + vcp->bottom = bottom; + WriteTriangles( file, vcp, &vpcb, false, + false, top, bot, board.GetUserPrecision(), false ); + + vpcb.Clear(); + ++sc; + } + + return true; +} diff --git a/utils/idftools/idf_common.cpp b/utils/idftools/idf_common.cpp new file mode 100644 index 0000000..8eedfa6 --- /dev/null +++ b/utils/idftools/idf_common.cpp @@ -0,0 +1,1387 @@ +/** + * 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 <list> +#include <string> +#include <iostream> +#include <fstream> +#include <sstream> +#include <iomanip> +#include <cerrno> +#include <cstdio> +#include <cmath> +#include <idf_common.h> +#include <idf_helpers.h> + +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() ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, "problems reading board notes" ) ); + } + + if( isComment ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: comment within a section (NOTES)" ) ); + } + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: X position in NOTES section must not be in quotes" ) ); + } + + if( CompareToken( ".END_NOTES", token ) ) + return false; + + istringstream istr; + istr.str( token ); + + istr >> xpos; + if( istr.fail() ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: X position in NOTES section is not numeric" ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: Y position in NOTES section is missing" ) ); + } + + if( quoted ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: Y position in NOTES section must not be in quotes" ) ); + } + + istr.clear(); + istr.str( token ); + + istr >> ypos; + if( istr.fail() ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: Y position in NOTES section is not numeric" ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: text height in NOTES section is missing" ) ); + } + + if( quoted ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: text height in NOTES section must not be in quotes" ) ); + } + + istr.clear(); + istr.str( token ); + + istr >> height; + if( istr.fail() ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: text height in NOTES section is not numeric" ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: text length in NOTES section is missing" ) ); + } + + if( quoted ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: text length in NOTES section must not be in quotes" ) ); + } + + istr.clear(); + istr.str( token ); + + istr >> length; + if( istr.fail() ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: text length in NOTES section is not numeric" ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: text value in NOTES section is missing" ) ); + } + + text = token; + + if( aBoardUnit == UNIT_THOU ) + { + xpos *= IDF_THOU_TO_MM; + ypos *= IDF_THOU_TO_MM; + height *= IDF_THOU_TO_MM; + length *= IDF_THOU_TO_MM; + } + + 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_THOU_TO_MM) << " " + << (ypos / IDF_THOU_TO_MM) << " " + << (height / IDF_THOU_TO_MM) << " " + << (length / IDF_THOU_TO_MM) << " "; + } + 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, IDF3::IDF_VERSION aIdfVersion ) +{ + 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() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "problems reading board drilled holes" ) ); + + if( isComment ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: comment within a section (DRILLED HOLES)" ) ); + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: drill diameter must not be in quotes" ) ); + + if( CompareToken( ".END_DRILLED_HOLES", token ) ) + return false; + + istringstream istr; + istr.str( token ); + + istr >> dia; + if( istr.fail() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: drill diameter is not numeric" ) ); + + if( ( aBoardUnit == UNIT_MM && dia < IDF_MIN_DIA_MM ) + || ( aBoardUnit == UNIT_THOU && dia < IDF_MIN_DIA_THOU ) + || ( aBoardUnit == UNIT_TNM && dia < IDF_MIN_DIA_TNM ) ) + { + ostringstream ostr; + ostr << "invalid IDF file\n"; + ostr << "* Invalid drill diameter (too small): '" << token << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: missing X position for drilled hole" ) ); + + if( quoted ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: X position in DRILLED HOLES section must not be in quotes" ) ); + + istr.clear(); + istr.str( token ); + + istr >> x; + if( istr.fail() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: X position in DRILLED HOLES section is not numeric" ) ); + + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: missing Y position for drilled hole" ) ); + + if( quoted ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: Y position in DRILLED HOLES section must not be in quotes" ) ); + + istr.clear(); + istr.str( token ); + + istr >> y; + if( istr.fail() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: Y position in DRILLED HOLES section is not numeric" ) ); + + if( aIdfVersion > IDF_V2 ) + { + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: missing PLATING for drilled hole" ) ); + + if( CompareToken( "PTH", token ) ) + { + plating = IDF3::PTH; + } + else if( CompareToken( "NPTH", token ) ) + { + plating = IDF3::NPTH; + } + else + { + ostringstream ostr; + ostr << "invalid IDFv3 file\n"; + ostr << "* Violation of specification: invalid PLATING type ('" << token << "')"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + else + { + plating = IDF3::PTH; + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + if( aIdfVersion > IDF_V2 ) + { + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: missing REFDES for drilled hole" ) ); + } + else + { + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv2 file\n" + "* Violation of specification: missing HOLE TYPE for drilled hole" ) ); + } + } + + std::string tok1 = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + if( aIdfVersion > IDF_V2 ) + { + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: missing HOLE TYPE for drilled hole" ) ); + } + else + { + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv2 file\n" + "* Violation of specification: missing REFDES for drilled hole" ) ); + } + } + + std::string tok2 = token; + + if( aIdfVersion > IDF_V2 ) + token = tok1; + + 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( aIdfVersion > IDF_V2 ) + token = tok2; + else + token = tok1; + + 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( aIdfVersion > IDF_V2 ) + { + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv3 file\n" + "* Violation of specification: missing OWNER for drilled hole" ) ); + + if( !ParseOwner( token, owner ) ) + { + ostringstream ostr; + ostr << "invalid IDFv3 file\n"; + ostr << "* Violation of specification: invalid OWNER for drilled hole ('" << token << "')"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + else + { + owner = IDF3::UNOWNED; + } + + if( aBoardUnit == UNIT_THOU ) + { + dia *= IDF_THOU_TO_MM; + x *= IDF_THOU_TO_MM; + y *= IDF_THOU_TO_MM; + } + else if( ( aIdfVersion == IDF_V2 ) && ( aBoardUnit == UNIT_TNM ) ) + { + dia *= IDF_TNM_TO_MM; + x *= IDF_TNM_TO_MM; + y *= IDF_TNM_TO_MM; + } + else if( aBoardUnit != UNIT_MM ) + { + ostringstream ostr; + ostr << "\n* BUG: invalid UNIT type: " << aBoardUnit; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + return true; +} + +void 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_THOU_TO_MM) << " " + << std::setprecision( 1 ) << (x / IDF_THOU_TO_MM) << " " << (y / IDF_THOU_TO_MM) << " " + << pltstr.c_str() << " " << refstr.c_str() << " " + << holestr.c_str() << " " << ownstr.c_str() << "\n"; + } + + return; +} // 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<IDF_SEGMENT*>& 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<IDF_SEGMENT*>::iterator bl = aLines.begin(); + std::list<IDF_SEGMENT*>::iterator el = aLines.end(); + std::list<IDF_SEGMENT*>::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 ); + 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 ); + 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_PI_2; + else + offAng -= M_PI_2; + + if( angle < -180.0 ) + offAng += M_PI; + else if( angle > 180 ) + 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; the winding is determined by + // the longer cord. although the angles are in + // degrees, there is no need to convert to radians + // to determine the longer cord. + if( abs( a1 * outline.front()->radius ) >= + abs( a2 * outline.back()->radius ) ) + { + // 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 ); + + double ang = outline.back()->angle; + double oang = outline.back()->offsetAngle; + double radius = outline.back()->radius; + + if( ang < -MIN_ANG || ang > MIN_ANG ) + { + // arcs require special consideration since the winding depends on + // the arc length; the arc length is adequately represented by + // taking 2 cords from the endpoints to the midpoint of the arc. + oang = (oang + ang / 2.0) * M_PI / 180.0; + double midx = outline.back()->center.x + radius * cos( oang ); + double midy = outline.back()->center.y + radius * sin( oang ); + + dir += ( outline.back()->endPoint.x - midx ) + * ( outline.back()->endPoint.y + midy ); + + dir += ( midx - outline.back()->startPoint.x ) + * ( midy + outline.back()->startPoint.y ); + } + else + { + 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 0000000..398f8f8 --- /dev/null +++ b/utils/idftools/idf_common.h @@ -0,0 +1,711 @@ +/** + * @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 <list> +#include <fstream> +#include <exception> +#include <string> +#include <cmath> + +// 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 IDF_VERSION + * represents the supported IDF versions (3.0 and 2.0 ONLY) + */ + enum IDF_VERSION + { + IDF_V2 = 0, // version 2 has read support only; files written as IDFv3 + IDF_V3 // version 3 has full read/write support + }; + + /** + * 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_TNM, //< Deprecated Ten Nanometer Units from IDFv2 + 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<IDF_SEGMENT*>& 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 +{ +friend class IDF3_BOARD; +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 + + /** + * 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 ); + +public: + IDF_NOTE(); + + /** + * 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 +{ +friend class IDF3_BOARD; +friend class IDF3_COMPONENT; +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; + + /** + * 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, + IDF3::IDF_VERSION aIdfVersion ); + + /** + * Function write + * writes a single line representing a hole within a .DRILLED_HOLES section + * In case of an unrecoverable error an exception is thrown. + * + * @param aBoardFile is an open BOARD file + * @param aBoardUnit is the native unit of the output file + */ + void write( std::ofstream& aBoardFile, IDF3::IDF_UNIT aBoardUnit ); + +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 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(); + + IDF3::KEY_OWNER GetDrillOwner( void ) + { + return owner; + } +}; + + +/** + * @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<IDF_SEGMENT*> 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<IDF_SEGMENT*>::iterator begin( void ) + { + return outline.begin(); + } + + /** + * Function end + * returns the end() iterator of the internal segment list + */ + std::list<IDF_SEGMENT*>::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 new file mode 100644 index 0000000..e7870d4 --- /dev/null +++ b/utils/idftools/idf_cylinder.cpp @@ -0,0 +1,681 @@ +/* + * 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 creates an outline for a horizontal or vertically + * oriented axial or radial leaded cylinder with dimensions based + * on the user's input. + */ + +#include <iostream> +#include <fstream> +#include <string> +#include <sstream> +#include <cmath> +#include <cstdio> +#include <list> +#include <utility> +#include <clocale> + +using namespace std; + +void make_vcyl( bool inch, bool axial, double dia, double length, + double z, double wireDia ); + +void make_hcyl( bool inch, bool axial, double dia, double length, + double z, double wireDia ); + +void writeAxialCyl( FILE* fp, bool inch, double dia, double length, double wireDia, double pitch ); + +void writeRadialCyl( FILE* fp, bool inch, double dia, double length, double wireDia, + double pitch, double lead ); + +int main( int argc, char **argv ) +{ + // IDF implicitly requires the C locale + setlocale( LC_ALL, "C" ); + + if( argc == 1 ) + { + cout << "idfcyl: This program generates an outline for a cylindrical component.\n"; + cout << " The cylinder may be horizontal or vertical.\n"; + cout << " A horizontal cylinder may have wires at one or both ends.\n"; + cout << " A vertical cylinder may have at most one wire which may be\n"; + cout << " placed on the left or right side.\n\n"; + cout << "Input:\n"; + cout << " Unit: mm, in (millimeters or inches)\n"; + cout << " Orientation: V (vertical)\n"; + cout << " Lead type: X, R (axial, radial)\n"; + cout << " Diameter of body\n"; + cout << " Length of body\n"; + cout << " Board offset\n"; + cout << " * Wire diameter\n"; + cout << " * Pitch\n"; + cout << " ** Wire side: L, R (left, right)\n"; + cout << " *** Lead length\n"; + cout << " File name (must end in *.idf)\n\n"; + cout << " NOTES:\n"; + cout << " * only required for horizontal orientation or\n"; + cout << " vertical orientation with axial leads\n\n"; + cout << " ** only required for vertical orientation with axial leads\n\n"; + cout << " *** only required for horizontal orientation with radial leads\n\n"; + } + + char orientation = '\0'; + bool inch = false; // default mm + double dia = 0.0; + double length = 0.0; + double extraZ = 0.0; + double wireDia = 0.0; + bool axial = false; + + stringstream tstr; + string line; + + line.clear(); + while( line.compare( "mm" ) && line.compare( "in" ) ) + { + cout << "* Units (mm,in): "; + line.clear(); + std::getline( cin, line ); + } + + if( line.compare( "mm" ) ) + inch = true; + + line.clear(); + while( line.compare( "H" ) && line.compare( "h" ) + && line.compare( "V" ) && line.compare( "v" ) ) + { + cout << "* Orientation (H,V): "; + line.clear(); + std::getline( cin, line ); + } + + if( line.compare( "H" ) && line.compare( "h" ) ) + orientation = 'v'; + else + orientation = 'h'; + + bool ok = false; + + while( !ok ) + { + cout << "* Axial or Radial (X,R): "; + + line.clear(); + std::getline( cin, line ); + + if( !line.compare( "x" ) || !line.compare( "X" ) ) + { + axial = true; + ok = true; + } + else if( !line.compare( "r" ) || !line.compare( "R" ) ) + { + axial = false; + ok = true; + } + } + + // cylinder dimensions + ok = false; + while( !ok ) + { + cout << "* Diameter: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> dia; + if( !tstr.fail() && dia > 0.0 ) + ok = true; + } + + ok = false; + while( !ok ) + { + cout << "* Length: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> length; + if( !tstr.fail() && length > 0.0 ) + ok = true; + } + + ok = false; + while( !ok ) + { + cout << "* Board offset: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> extraZ; + if( !tstr.fail() && extraZ >= 0.0 ) + ok = true; + } + + ok = false; + while( ( axial || orientation == 'h' ) && !ok ) + { + cout << "* Wire diameter: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> wireDia; + if( !tstr.fail() && wireDia > 0.0 ) + { + if( wireDia < dia ) + ok = true; + else + cout << "* WARNING: wire diameter must be < cylinder diameter\n"; + } + } + + switch( orientation ) + { + case 'v': + make_vcyl( inch, axial, dia, length, extraZ, wireDia ); + break; + case 'h': + make_hcyl( inch, axial, dia, length, extraZ, wireDia ); + break; + default: + break; + } + + setlocale( LC_ALL, "" ); + return 0; +} + + +void make_vcyl( bool inch, bool axial, double dia, double length, + double z, double wireDia ) +{ + bool ok = false; + bool left = false; + stringstream tstr; + string line; + + double pitch = 0.0; + + while( axial && !ok ) + { + cout << "* Pitch: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> pitch; + if( !tstr.fail() && pitch > 0.0 ) + { + if( (pitch - wireDia) <= (dia / 2.0) ) + { + cout << "* WARNING: Pitch must be > dia/2 + wireDia\n"; + } + else + { + ok = true; + } + } + } + + ok = false; + while( axial && !ok ) + { + cout << "* Pin side (L,R): "; + + line.clear(); + std::getline( cin, line ); + + if( !line.compare( "l" ) || !line.compare( "L" ) ) + { + left = true; + ok = true; + } + else if( !line.compare( "r" ) || !line.compare( "R" ) ) + ok = true; + } + + line.clear(); + while( line.empty() || line.find( ".idf" ) == string::npos ) + { + cout << "* File name (*.idf): "; + + line.clear(); + std::getline( cin, line ); + } + + FILE* fp = fopen( line.c_str(), "w" ); + + if( !fp ) + { + cerr << "Could not open output file: " << line << "\n"; + return; + } + + fprintf( fp, "# cylindrical outline, vertical, " ); + + if( !axial ) + fprintf( fp, "radial leads\n" ); + else + fprintf( fp, "axial lead on %s\n", left ? "left" : "right" ); + + fprintf( fp, "# file: \"%s\"\n", line.c_str() ); + + if( inch ) + { + fprintf( fp, "# dia: %d THOU\n", (int) (dia * 1000) ); + fprintf( fp, "# length: %d THOU\n", (int) (length * 1000) ); + fprintf( fp, "# board offset: %d THOU\n", (int) (z * 1000) ); + + if( axial ) + { + fprintf( fp, "# wire dia: %d THOU\n", (int) (wireDia * 1000) ); + fprintf( fp, "# pitch: %d THOU\n", (int) (pitch * 1000) ); + } + } + else + { + fprintf( fp, "# dia: %.3f mm\n", dia ); + fprintf( fp, "# length: %.3f mm\n", length ); + fprintf( fp, "# board offset: %.3f mm\n", z ); + + if( axial ) + { + fprintf( fp, "# wire dia: %.3f mm\n", wireDia ); + fprintf( fp, "# pitch: %.3f mm\n", pitch ); + } + } + + fprintf( fp, ".ELECTRICAL\n" ); + + if( !axial ) + { + fprintf( fp, "\"CYLV_%s_RAD\" \"D%.3f_H%.3f_Z%.3f\" ", inch ? "IN" : "MM", + dia, length, z ); + } + else + { + fprintf( fp, "\"CYLV_%s_AX%s\" \"D%.3f_H%.3f_Z%.3f_WD%.3f_P%.3f\" ", inch ? "IN" : "MM", + left ? "L" : "R", dia, length, z, wireDia, pitch ); + } + + if( inch ) + fprintf( fp, "THOU %d\n", (int) ((length + z) * 1000) ); + else + fprintf( fp, "MM %.3f\n", length + z ); + + if( !axial ) + { + fprintf( fp, "0 0 0 0\n" ); + + if( inch ) + fprintf( fp, "0 %d 0 360\n", (int) (dia * 500) ); + else + fprintf( fp, "0 %.3f 0 360\n", dia / 2.0 ); + + fprintf( fp, ".END_ELECTRICAL\n" ); + fclose( fp ); + return; + } + + double px[4], py[4]; + + // points are: + // [0] = upper point on cylinder perimeter + // [1] = lower point on cylinder perimeter + // [2] = point beneath wire center + // [3] = point above wire center + + if( inch ) + { + dia *= 1000.0; + pitch *= 1000.0; + wireDia *= 1000.0; + } + + double ang = asin( wireDia / dia ); + px[0] = dia * cos( ang ) / 2.0 - pitch / 2.0; + px[1] = px[0]; + px[2] = pitch / 2.0; + px[3] = px[2]; + + py[0] = wireDia / 2.0; + py[1] = -py[0]; + py[2] = py[1]; + py[3] = py[0]; + + char li = '0'; + + double fullAng = 360.0; + + if( left ) + { + li = '1'; + fullAng = -360.0; + for( int i = 0; i < 4; ++i ) px[i] = -px[i]; + } + + + if( inch ) + { + fprintf( fp, "%c %d %d 0\n", li, (int) px[0], (int) py[0] ); + fprintf( fp, "%c %d %d %.3f\n", li, (int) px[1], (int) py[1], + fullAng * ( 1 - ang / M_PI ) ); + fprintf( fp, "%c %d %d 0\n", li, (int) px[2], (int) py[2] ); + fprintf( fp, "%c %d %d %s\n", li, (int) px[3], (int) py[3], + left ? "-180" : "180" ); + fprintf( fp, "%c %d %d 0\n", li, (int) px[0], (int) py[0] ); + } + else + { + fprintf( fp, "%c %.3f %.3f 0\n", li, px[0], py[0] ); + fprintf( fp, "%c %.3f %.3f %.3f\n", li, px[1], py[1], fullAng * ( 1 - ang / M_PI ) ); + fprintf( fp, "%c %.3f %.3f 0\n", li, px[2], py[2] ); + fprintf( fp, "%c %.3f %.3f %s\n", li, px[3], py[3], + left ? "-180" : "180" ); + fprintf( fp, "%c %.3f %.3f 0\n", li, px[0], py[0] ); + } + + fprintf( fp, ".END_ELECTRICAL\n" ); + fclose( fp ); + return; +} + + +void make_hcyl( bool inch, bool axial, double dia, double length, + double z, double wireDia ) +{ + stringstream tstr; + string line; + + double pitch = 0.0; + double lead = 0.0; // lead length for radial leads + + bool ok = false; + while( !ok ) + { + if( axial ) + cout << "* Axial pitch: "; + else + cout << "* Radial pitch: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> pitch; + if( !tstr.fail() && pitch > 0.0 ) + { + if( axial ) + { + if( (pitch - wireDia) <= length ) + { + cout << "* WARNING: Axial pitch must be > length + wireDia\n"; + } + else + { + ok = true; + } + } + else + { + if( (pitch + wireDia) >= dia ) + { + cout << "* WARNING: Radial pitch must be < dia - wireDia\n"; + } + else if( pitch <= wireDia ) + { + cout << "* WARNING: Radial pitch must be > wireDia\n"; + } + else + { + ok = true; + } + } + } + } + + ok = false; + while( !axial && !ok ) + { + cout << "* Lead length: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> lead; + if( !tstr.fail() && lead > 0.0 ) + { + if( lead < wireDia ) + cout << "* WARNING: lead length must be >= wireDia\n"; + else + ok = true; + } + } + + line.clear(); + while( line.empty() || line.find( ".idf" ) == string::npos ) + { + cout << "* File name (*.idf): "; + + line.clear(); + std::getline( cin, line ); + } + + FILE* fp = fopen( line.c_str(), "w" ); + + if( !fp ) + { + cerr << "Could not open output file: " << line << "\n"; + return; + } + + fprintf( fp, "# cylindrical outline, horiz., " ); + + fprintf( fp, "%s pins\n", axial ? "axial" : "radial" ); + + fprintf( fp, "# file: \"%s\"\n", line.c_str() ); + + if( inch ) + { + fprintf( fp, "# dia: %d THOU\n", (int) (dia * 1000) ); + fprintf( fp, "# length: %d THOU\n", (int) (length * 1000) ); + fprintf( fp, "# extra height: %d THOU\n", (int) (z * 1000) ); + fprintf( fp, "# wire dia: %d THOU\n", (int) (wireDia * 1000) ); + fprintf( fp, "# pitch: %d THOU\n", (int) (pitch * 1000) ); + if( !axial ) + fprintf( fp, "# lead: %d THOU\n", (int) (lead * 1000) ); + } + else + { + fprintf( fp, "# dia: %.3f mm\n", dia ); + fprintf( fp, "# length: %.3f mm\n", length ); + fprintf( fp, "# extra height: %.3f mm\n", z ); + fprintf( fp, "# wire dia: %.3f mm\n", wireDia ); + fprintf( fp, "# pitch: %.3f mm\n", pitch ); + if( !axial ) + fprintf( fp, "# lead: %.3f mm\n", lead ); + } + + fprintf( fp, ".ELECTRICAL\n" ); + + if( axial ) + { + fprintf( fp, "\"CYLH_%s_AXI\" \"D%.3f_H%.3f_Z%.3f_WD%.3f_P%.3f\" ", + inch ? "IN" : "MM", dia, length, z, wireDia, pitch ); + } + else + { + fprintf( fp, "\"CYLH_%s_RAD\" \"D%.3f_H%.3f_Z%.3f_WD%.3f_P%.3f_L%.3f\" ", + inch ? "IN" : "MM", dia, length, z, wireDia, pitch, lead ); + } + + if( inch ) + { + fprintf( fp, "THOU %d\n", (int) ((dia + z) * 1000) ); + dia *= 1000.0; + length *= 1000.0; + wireDia *= 1000.0; + pitch *= 1000.0; + if( !axial ) + lead *= 1000.0; + } + else + { + fprintf( fp, "MM %.3f\n", dia + z ); + } + + if( axial ) + writeAxialCyl( fp, inch, dia, length, wireDia, pitch ); + else + writeRadialCyl( fp, inch, dia, length, wireDia, pitch, lead ); + + fprintf( fp, ".END_ELECTRICAL\n" ); + fclose( fp ); + return; + return; +} + +void writeAxialCyl( FILE* fp, bool inch, double dia, double length, + double wireDia, double pitch ) +{ + double x1, y1; + double x2, y2; + + x1 = -length / 2.0; + x2 = -pitch / 2.0; + y1 = dia / 2.0; + y2 = wireDia / 2.0; + + if( inch ) + { + fprintf( fp, "0 %d %d 0\n", (int) x1, (int) y1 ); + fprintf( fp, "0 %d %d 0\n", (int) x1, (int) y2 ); + fprintf( fp, "0 %d %d 0\n", (int) x2, (int) y2 ); + fprintf( fp, "0 %d %d 180\n", (int) x2, (int) -y2 ); + fprintf( fp, "0 %d %d 0\n", (int) x1, (int) -y2 ); + fprintf( fp, "0 %d %d 0\n", (int) x1, (int) -y1 ); + fprintf( fp, "0 %d %d 0\n", (int) -x1, (int) -y1 ); + fprintf( fp, "0 %d %d 0\n", (int) -x1, (int) -y2 ); + fprintf( fp, "0 %d %d 0\n", (int) -x2, (int) -y2 ); + fprintf( fp, "0 %d %d 180\n", (int) -x2, (int) y2 ); + fprintf( fp, "0 %d %d 0\n", (int) -x1, (int) y2 ); + fprintf( fp, "0 %d %d 0\n", (int) -x1, (int) y1 ); + fprintf( fp, "0 %d %d 0\n", (int) x1, (int) y1 ); + } + else + { + fprintf( fp, "0 %.3f %.3f 0\n", x1, y1 ); + fprintf( fp, "0 %.3f %.3f 0\n", x1, y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", x2, y2 ); + fprintf( fp, "0 %.3f %.3f 180\n", x2, -y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", x1, -y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", x1, -y1 ); + fprintf( fp, "0 %.3f %.3f 0\n", -x1, -y1 ); + fprintf( fp, "0 %.3f %.3f 0\n", -x1, -y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", -x2, -y2 ); + fprintf( fp, "0 %.3f %.3f 180\n", -x2, y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", -x1, y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", -x1, y1 ); + fprintf( fp, "0 %.3f %.3f 0\n", x1, y1 ); + } + + return; +} + +void writeRadialCyl( FILE* fp, bool inch, double dia, double length, + double wireDia, double pitch, double lead ) +{ + double x1, y1; + double x2, y2; + double x3; + + // center is between the mounting holes + // which are on a horizontal line + y1 = lead + length; + y2 = lead; + x1 = dia / 2.0; + x2 = ( pitch + wireDia ) /2.0; + x3 = x2 - wireDia; + + if( inch ) + { + fprintf( fp, "0 %d %d 0\n", (int) -x1, (int) y1 ); + fprintf( fp, "0 %d %d 0\n", (int) -x1, (int) y2 ); + fprintf( fp, "0 %d %d 0\n", (int) -x2, (int) y2 ); + fprintf( fp, "0 %d 0 0\n", (int) -x2 ); + fprintf( fp, "0 %d 0 180\n", (int) -x3 ); + fprintf( fp, "0 %d %d 0\n", (int) -x3, (int) y2 ); + fprintf( fp, "0 %d %d 0\n", (int) x3, (int) y2 ); + fprintf( fp, "0 %d 0 0\n", (int) x3 ); + fprintf( fp, "0 %d 0 180\n", (int) x2 ); + fprintf( fp, "0 %d %d 0\n", (int) x2, (int) y2 ); + fprintf( fp, "0 %d %d 0\n", (int) x1, (int) y2 ); + fprintf( fp, "0 %d %d 0\n", (int) x1, (int) y1 ); + fprintf( fp, "0 %d %d 0\n", (int) -x1, (int) y1 ); + } + else + { + fprintf( fp, "0 %.3f %.3f 0\n", -x1, y1 ); + fprintf( fp, "0 %.3f %.3f 0\n", -x1, y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", -x2, y2 ); + fprintf( fp, "0 %.3f 0 0\n", -x2 ); + fprintf( fp, "0 %.3f 0 180\n", -x3 ); + fprintf( fp, "0 %.3f %.3f 0\n", -x3, y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", x3, y2 ); + fprintf( fp, "0 %.3f 0 0\n", x3 ); + fprintf( fp, "0 %.3f 0 180\n", x2 ); + fprintf( fp, "0 %.3f %.3f 0\n", x2, y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", x1, y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", x1, y1 ); + fprintf( fp, "0 %.3f %.3f 0\n", -x1, y1 ); + } + + return; +} 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 0000000..4c21583 --- /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 0000000..693ae28 --- /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 0000000..417e5dd --- /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 0000000..b25b000 --- /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_donut.emn b/utils/idftools/idf_examples/test_donut.emn new file mode 100644 index 0000000..fd5b87f --- /dev/null +++ b/utils/idftools/idf_examples/test_donut.emn @@ -0,0 +1,45 @@ +.HEADER +BOARD_FILE 3.0 "Created by some software" 2014/02/01.15:09:15 1 +"test_donut" MM +.END_HEADER + +# The board outline is a simple square with a small hole in it +.BOARD_OUTLINE ECAD +1.60000 +0 -100 100 0 +0 -100 -100 0 +0 100 -100 0 +0 100 100 0 +0 -100 100 0 +1 0 0 0 +1 5 0 360 +.END_BOARD_OUTLINE + +# This OTHER OUTLINE is a square toroid +.OTHER_OUTLINE UNOWNED +MY_DONUT 30 TOP +0 0 0 0 +0 75 0 360 +1 0 0 0 +1 30 0 360 +.END_OTHER_OUTLINE + +# This OTHER OUTLINE is a square with a hole +.OTHER_OUTLINE UNOWNED +MY_NOT_DONUT 2 BOTTOM +0 -50 50 0 +0 -50 -50 0 +0 50 -50 0 +0 50 50 0 +0 -50 50 0 +1 0 0 0 +1 10 0 360 +2 0 50 0 +2 0 75 360 +3 50 0 0 +3 75 0 360 +4 0 -50 0 +4 0 -75 360 +5 -50 0 0 +5 -75 0 360 +.END_OTHER_OUTLINE diff --git a/utils/idftools/idf_examples/test_donut.emp b/utils/idftools/idf_examples/test_donut.emp new file mode 100644 index 0000000..d3c09b7 --- /dev/null +++ b/utils/idftools/idf_examples/test_donut.emp @@ -0,0 +1,5 @@ +.HEADER +LIBRARY_FILE 3.0 "Created by some software" 2014/02/01.15:09:15 1 +.END_HEADER + +# This file contains no component outlines
\ No newline at end of file diff --git a/utils/idftools/idf_examples/test_idf2.emn b/utils/idftools/idf_examples/test_idf2.emn new file mode 100644 index 0000000..b317a00 --- /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 0000000..9777025 --- /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 0000000..cad1852 --- /dev/null +++ b/utils/idftools/idf_helpers.cpp @@ -0,0 +1,323 @@ +/* + * 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 <cctype> +#include <iostream> +#include <sstream> + +#include <idf_common.h> +#include <idf_helpers.h> + +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: + do{ + std::ostringstream ostr; + ostr << "invalid IDF layer: " << aLayer; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } while( 0 ); + + 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(); +} + +std::string IDF3::GetOwnerString( IDF3::KEY_OWNER aOwner ) +{ + switch( aOwner ) + { + case IDF3::UNOWNED: + return "UNOWNED"; + + case IDF3::MCAD: + return "MCAD"; + + case IDF3::ECAD: + return "ECAD"; + + default: + break; + } + + ostringstream ostr; + ostr << "UNKNOWN: " << aOwner; + + return ostr.str(); +} diff --git a/utils/idftools/idf_helpers.h b/utils/idftools/idf_helpers.h new file mode 100644 index 0000000..9b0c33f --- /dev/null +++ b/utils/idftools/idf_helpers.h @@ -0,0 +1,175 @@ +/* + * 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 <wx/wx.h> +#include <fstream> +#include <string> +#include <idf_common.h> + +/** + * 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 ) +#define IDF_MIN_DIA_TNM ( 100 ) + +// conversion from thou to mm +#define IDF_THOU_TO_MM 0.0254 +// conversion from TNM to mm +#define IDF_TNM_TO_MM 0.00001 + +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 ); + +std::string GetOwnerString( IDF3::KEY_OWNER aOwner ); +} + +#endif // IDF_HELPERS_H diff --git a/utils/idftools/idf_outlines.cpp b/utils/idftools/idf_outlines.cpp new file mode 100644 index 0000000..f1541d2 --- /dev/null +++ b/utils/idftools/idf_outlines.cpp @@ -0,0 +1,3614 @@ +/* + * 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 <iostream> +#include <iomanip> +#include <sstream> +#include <cmath> + +#include <idf_helpers.h> +#include <idf_outlines.h> +#include <idf_parser.h> + +using namespace IDF3; +using namespace std; + + +static std::string GetOutlineTypeString( IDF3::OUTLINE_TYPE aOutlineType ) +{ + switch( aOutlineType ) + { + case OTLN_BOARD: + return ".BOARD_OUTLINE"; + + case OTLN_OTHER: + return ".OTHER_OUTLINE"; + + case OTLN_PLACE: + return ".PLACEMENT_OUTLINE"; + + case OTLN_ROUTE: + return ".ROUTE_OUTLINE"; + + case OTLN_PLACE_KEEPOUT: + return ".PLACE_KEEPOUT"; + + case OTLN_ROUTE_KEEPOUT: + return ".ROUTE_KEEPOUT"; + + case OTLN_VIA_KEEPOUT: + return ".VIA_KEEPOUT"; + + case OTLN_GROUP_PLACE: + return ".PLACE_REGION"; + + case OTLN_COMPONENT: + return "COMPONENT OUTLINE"; + + default: + break; + } + + std::ostringstream ostr; + ostr << "[INVALID OUTLINE TYPE VALUE]:" << aOutlineType; + + return ostr.str(); +} + +#ifndef DISABLE_IDF_OWNERSHIP +static bool CheckOwnership( int aSourceLine, const char* aSourceFunc, + IDF3_BOARD* aParent, IDF3::KEY_OWNER aOwnerCAD, + IDF3::OUTLINE_TYPE aOutlineType, std::string& aErrorString ) +{ + if( aParent == NULL ) + { + ostringstream ostr; + ostr << "* " << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc << "():\n"; + ostr << "* BUG: outline's parent not set; cannot enforce ownership rules\n"; + ostr << "* outline type: " << GetOutlineTypeString( aOutlineType ); + aErrorString = ostr.str(); + + return false; + } + + // note: component outlines have no owner so we don't care about + // who modifies them + if( aOwnerCAD == UNOWNED || aOutlineType == IDF3::OTLN_COMPONENT ) + return true; + + IDF3::CAD_TYPE parentCAD = aParent->GetCadType(); + + if( aOwnerCAD == MCAD && parentCAD == CAD_MECH ) + return true; + + if( aOwnerCAD == ECAD && parentCAD == CAD_ELEC ) + return true; + + do + { + ostringstream ostr; + ostr << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc << "():\n"; + ostr << "* ownership violation; CAD type is "; + + if( parentCAD == CAD_MECH ) + ostr << "MCAD "; + else + ostr << "ECAD "; + + ostr << "while outline owner is " << GetOwnerString( aOwnerCAD ) << "\n"; + ostr << "* outline type: " << GetOutlineTypeString( aOutlineType ); + aErrorString = ostr.str(); + + } while( 0 ); + + return false; +} +#endif + + +/* + * 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; +} + +void BOARD_OUTLINE::readOutlines( std::ifstream& aBoardFile, IDF3::IDF_VERSION aIdfVersion ) +{ + // 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 ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 1 of " << GetOutlineTypeString( outlineType ); + ostr << " is quoted\n"; + ostr << "* line: '" << iline << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + // 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. + if(aBoardFile.eof()) + aBoardFile.clear(); + + aBoardFile.seekg( pos ); + + if( outlines.size() > 0 ) + { + if( npts > 0 && !closed ) + { + ostringstream ostr; + ostr << "invalid outline (not closed)\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + // verify winding + if( !single ) + { + if( !outlines.front()->IsCCW() ) + { + ERROR_IDF << "invalid IDF3 file (BOARD_OUTLINE)\n"; + cerr << "* WARNING: first outline is not in CCW order\n"; + return; + } + + if( outlines.size() > 1 && outlines.back()->IsCCW() && !outlines.back()->IsCircle() ) + { + ERROR_IDF << "invalid IDF3 file (BOARD_OUTLINE)\n"; + cerr << "* WARNING: final cutout does not have points in CW order\n"; + cerr << "* file position: " << pos << "\n"; + return; + } + } + } + + return; + } + + tstr.clear(); + tstr << entry; + + tstr >> tmp; + if( tstr.fail() ) + { + if( outlineType == OTLN_COMPONENT && CompareToken( "PROP", entry ) ) + { + aBoardFile.seekg( pos ); + return; + } + + do{ + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 1 of " << GetOutlineTypeString( outlineType ); + ostr << " is not numeric\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + + } while( 0 ); + } + + if( tmp != loopidx ) + { + // index change + if( npts > 0 && !closed ) + { + ostringstream ostr; + ostr << "invalid outline ( outline # " << loopidx << " not closed)\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( tmp < 0 ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 1 of " << GetOutlineTypeString( outlineType ); + ostr << " is invalid\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + 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 ) + { + clearOutlines(); + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "memory allocation failed" ) ); + } + + outlines.push_back( op ); + loopidx = tmp; + } + else + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 1 of " << GetOutlineTypeString( outlineType ); + ostr << " is invalid (must be 0 or 1)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + else + { + // outline *MUST* have a Loop Index of 0 + if( tmp != 0 ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 1 of " << GetOutlineTypeString( outlineType ); + ostr << " is invalid (must be 0)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + op = new IDF_OUTLINE; + + if( op == NULL ) + { + clearOutlines(); + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "memory allocation failed" ) ); + } + + outlines.push_back( op ); + loopidx = tmp; + } + // end of block for first outline + } + else + { + // outline for cutout + if( single ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ); + ostr << " section may only have one outline\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( tmp - loopidx != 1 ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ); + ostr << " section must have cutouts in numeric order from 1 onwards\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + // verify winding of previous outline + if( ( loopidx == 0 && !op->IsCCW() ) + || ( loopidx > 0 && op->IsCCW() && !op->IsCircle() ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation of loop point order rules by Loop Index " << loopidx << "\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + op = new IDF_OUTLINE; + + if( op == NULL ) + { + clearOutlines(); + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "memory allocation failed" ) ); + } + + outlines.push_back( op ); + loopidx = tmp; + } + // end of index change code + npts = 0; + closed = false; + } + + if( op == NULL ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 1 of " << GetOutlineTypeString( outlineType ); + ostr << " is invalid\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, entry, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 2 of "; + ostr << GetOutlineTypeString( outlineType ) << " does not exist\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( quoted ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 2 of "; + ostr << GetOutlineTypeString( outlineType ) << " must not be in quotes\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + tstr.clear(); + tstr << entry; + + tstr >> x; + if( tstr.fail() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 2 of "; + ostr << GetOutlineTypeString( outlineType ) << " is an invalid X value\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, entry, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 3 of "; + ostr << GetOutlineTypeString( outlineType ) << " does not exist\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( quoted ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 3 of "; + ostr << GetOutlineTypeString( outlineType ) << " must not be in quotes\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + tstr.clear(); + tstr << entry; + + tstr >> y; + if( tstr.fail() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 3 of "; + ostr << GetOutlineTypeString( outlineType ) << " is an invalid Y value\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, entry, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 4 of "; + ostr << GetOutlineTypeString( outlineType ) << " does not exist\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( quoted ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 4 of "; + ostr << GetOutlineTypeString( outlineType ) << " must not be in quotes\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + tstr.clear(); + tstr << entry; + + tstr >> ang; + if( tstr.fail() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3, FIELD 4 of "; + ostr << GetOutlineTypeString( outlineType ) << " is not a valid angle\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + // the line was successfully read; convert to mm if necessary + if( unit == UNIT_THOU ) + { + x *= IDF_THOU_TO_MM; + y *= IDF_THOU_TO_MM; + } + else if( ( aIdfVersion == IDF_V2 ) && ( unit == UNIT_TNM ) ) + { + x *= IDF_TNM_TO_MM; + y *= IDF_TNM_TO_MM; + } + else if( unit != UNIT_MM ) + { + ostringstream ostr; + ostr << "\n* BUG: invalid UNIT type: " << unit; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + 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 ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3 of "; + ostr << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: first point of an outline has a non-zero angle\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + else + { + // Nth point + if( closed ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3 of "; + ostr << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: adding a segment to a closed outline\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + 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 ) + { + clearOutlines(); + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "memory allocation failed" ) ); + } + + if( sp->IsCircle() ) + { + // this is a circle; the loop is closed + if( op->size() != 0 ) + { + delete sp; + + ostringstream ostr; + + ostr << "\n* invalid outline: RECORD 3 of "; + ostr << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: adding a circle to a non-empty outline\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + 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 + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "problems reading file (premature end of outline)" ) ); + + return; +} + +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(); +} + +void BOARD_OUTLINE::writeOutline( std::ofstream& aBoardFile, IDF_OUTLINE* aOutline, size_t aIndex ) +{ + std::list<IDF_SEGMENT*>::iterator bo; + std::list<IDF_SEGMENT*>::iterator eo; + + if( !aOutline ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: NULL outline pointer" ) ); + + if( aOutline->size() == 1 ) + { + if( !aOutline->front()->IsCircle() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "bad outline (single segment item, not circle)" ) ); + + if( single ) + aIndex = 0; + + // NOTE: a circle always has an angle of 360, never -360, + // otherwise SolidWorks chokes on the file. + if( unit != UNIT_THOU ) + { + 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_THOU_TO_MM) << " " + << (aOutline->front()->startPoint.y / IDF_THOU_TO_MM) << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << (aOutline->front()->endPoint.x / IDF_THOU_TO_MM) << " " + << (aOutline->front()->endPoint.y / IDF_THOU_TO_MM) << " 360\n"; + } + + return; + } + + 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; + + // ensure that the very last point is the same as the very first point + if( aOutline->size() > 1 ) + { + std::list<IDF_SEGMENT*>::iterator to = eo; + ++to; + (*to)->startPoint = (*eo)->endPoint; + } + + // for the first item we write out both points + if( unit != UNIT_THOU ) + { + 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(3) << -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_THOU_TO_MM) << " " + << (aOutline->front()->endPoint.y / IDF_THOU_TO_MM) << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << (aOutline->front()->startPoint.x / IDF_THOU_TO_MM) << " " + << (aOutline->front()->startPoint.y / IDF_THOU_TO_MM) << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << (aOutline->front()->endPoint.x / IDF_THOU_TO_MM) << " " + << (aOutline->front()->endPoint.y / IDF_THOU_TO_MM) << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << (aOutline->front()->startPoint.x / IDF_THOU_TO_MM) << " " + << (aOutline->front()->startPoint.y / IDF_THOU_TO_MM) << " " + << setprecision(3) << -aOutline->front()->angle << "\n"; + } + } + + // for all other segments we only write out the start point + while( bo != eo ) + { + if( unit != UNIT_THOU ) + { + 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(3) << -(*bo)->angle << "\n"; + } + } + else + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->startPoint.x / IDF_THOU_TO_MM) << " " + << ((*bo)->startPoint.y / IDF_THOU_TO_MM) << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->startPoint.x / IDF_THOU_TO_MM) << " " + << ((*bo)->startPoint.y / IDF_THOU_TO_MM) << " " + << setprecision(3) << -(*bo)->angle << "\n"; + } + } + + --bo; + } + } + else + { + // ensure that the very last point is the same as the very first point + if( aOutline->size() > 1 ) + aOutline->back()-> endPoint = aOutline->front()->startPoint; + + bo = aOutline->begin(); + eo = aOutline->end(); + + // for the first item we write out both points + if( unit != UNIT_THOU ) + { + 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(3) << (*bo)->angle << "\n"; + } + } + else + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->startPoint.x / IDF_THOU_TO_MM) << " " + << ((*bo)->startPoint.y / IDF_THOU_TO_MM) << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->endPoint.x / IDF_THOU_TO_MM) << " " + << ((*bo)->endPoint.y / IDF_THOU_TO_MM) << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->startPoint.x / IDF_THOU_TO_MM) << " " + << ((*bo)->startPoint.y / IDF_THOU_TO_MM) << " 0\n"; + + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->endPoint.x / IDF_THOU_TO_MM) << " " + << ((*bo)->endPoint.y / IDF_THOU_TO_MM) << " " + << setprecision(3) << (*bo)->angle << "\n"; + } + } + + ++bo; + + // for all other segments we only write out the last point + while( bo != eo ) + { + if( unit != UNIT_THOU ) + { + 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(3) << (*bo)->angle << "\n"; + } + } + else + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->endPoint.x / IDF_THOU_TO_MM) << " " + << ((*bo)->endPoint.y / IDF_THOU_TO_MM) << " 0\n"; + } + else + { + aBoardFile << aIndex << " " << setiosflags(ios::fixed) << setprecision(1) + << ((*bo)->endPoint.x / IDF_THOU_TO_MM) << " " + << ((*bo)->endPoint.y / IDF_THOU_TO_MM) << " " + << setprecision(3) << (*bo)->angle << "\n"; + } + } + + ++bo; + } + } + + return; +} + +void BOARD_OUTLINE::writeOutlines( std::ofstream& aBoardFile ) +{ + if( outlines.empty() ) + return; + + int idx = 0; + std::list< IDF_OUTLINE* >::iterator itS = outlines.begin(); + std::list< IDF_OUTLINE* >::iterator itE = outlines.end(); + + while( itS != itE ) + { + writeOutline( aBoardFile, *itS, idx++ ); + ++itS; + } + + return; +} + +bool BOARD_OUTLINE::SetUnit( IDF3::IDF_UNIT aUnit ) +{ + // note: although UNIT_TNM is accepted here without reservation, + // this can only affect data being read from a file. + if( aUnit != UNIT_MM && aUnit != UNIT_THOU && aUnit != UNIT_TNM ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: invalid IDF UNIT (must be one of UNIT_MM or UNIT_THOU): " << aUnit << "\n"; + ostr << "* outline type: " << GetOutlineTypeString( outlineType ); + errormsg = ostr.str(); + + return false; + } + + unit = aUnit; + return true; +} + +IDF3::IDF_UNIT BOARD_OUTLINE::GetUnit( void ) +{ + return unit; +} + +bool BOARD_OUTLINE::setThickness( double aThickness ) +{ + if( aThickness < 0.0 ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: aThickness < 0.0\n"; + ostr << "* outline type: " << GetOutlineTypeString( outlineType ); + errormsg = ostr.str(); + + return false; + } + + thickness = aThickness; + return true; +} + +bool BOARD_OUTLINE::SetThickness( double aThickness ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + return setThickness( aThickness ); +} + +double BOARD_OUTLINE::GetThickness( void ) +{ + return thickness; +} + +void BOARD_OUTLINE::readData( std::ifstream& aBoardFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ) +{ + // BOARD_OUTLINE (PANEL_OUTLINE) + // .BOARD_OUTLINE [OWNER] + // [thickness] + // [outlines] + + // check RECORD 1 + std::string token; + bool quoted = false; + int idx = 0; + std::streampos pos; + + pos = aBoardFile.tellg(); + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, "invalid invocation: blank header line" ) ); + + if( quoted ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: section names may not be in quotes\n"; + ostr << "* line: '" << aHeader << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !CompareToken( ".BOARD_OUTLINE", token ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: not a board outline\n"; + ostr << "* line: '" << aHeader << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + if( aIdfVersion > IDF_V2 ) + 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; + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && !aBoardFile.eof() ) || iline.empty() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within .BOARD_OUTLINE section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no thickness specified\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + std::stringstream teststr; + teststr << token; + + teststr >> thickness; + if( teststr.fail() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: invalid RECORD 2 (thickness)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( unit == UNIT_THOU ) + { + thickness *= IDF_THOU_TO_MM; + } + else if( ( aIdfVersion == IDF_V2 ) && ( unit == UNIT_TNM ) ) + { + thickness *= IDF_TNM_TO_MM; + } + else if( unit != UNIT_MM ) + { + ostringstream ostr; + ostr << "\n* BUG: invalid UNIT type: " << unit; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + // for some unknown reason IDF allows 0 or negative thickness, but this + // is a problem so we fix it here + if( thickness <= 0.0 ) + { + if( thickness == 0.0 ) + { + ERROR_IDF << "\n* WARNING: setting board thickness to default 1.6mm ("; + cerr << thickness << ")\n"; + thickness = 1.6; + } + else + { + thickness = -thickness; + ERROR_IDF << "\n* WARNING: setting board thickness to positive number ("; + cerr << thickness << ")\n"; + } + } + + // read RECORD 3 values + readOutlines( aBoardFile, aIdfVersion ); + + // check RECORD 4 + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && aBoardFile.eof() ) || iline.empty() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !CompareToken( ".END_BOARD_OUTLINE", iline ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no .END_BOARD_OUTLINE found\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + return; +} + + +void 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_THOU ) + aBoardFile << setiosflags(ios::fixed) << setprecision(5) << thickness << "\n"; + else + aBoardFile << setiosflags(ios::fixed) << setprecision(1) << (thickness / IDF_THOU_TO_MM) << "\n"; + + writeOutlines( aBoardFile ); + + aBoardFile << ".END_BOARD_OUTLINE\n\n"; + + return; +} + +void BOARD_OUTLINE::clear( void ) +{ + comments.clear(); + clearOutlines(); + + owner = UNOWNED; + return; +} + +bool BOARD_OUTLINE::Clear( void ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + clear(); + + return true; +} + +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(); + + try + { + while( itS != itE ) + { + if( *itS == aOutline ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "duplicate outline pointer" ) ); + + ++itS; + } + + outlines.push_back( aOutline ); + + } + catch( const std::exception& e ) + { + errormsg = e.what(); + + return false; + } + + return true; +} + +bool BOARD_OUTLINE::AddOutline( IDF_OUTLINE* aOutline ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + return addOutline( aOutline ); +} + +bool BOARD_OUTLINE::DelOutline( IDF_OUTLINE* aOutline ) +{ + std::list< IDF_OUTLINE* >::iterator itS = outlines.begin(); + std::list< IDF_OUTLINE* >::iterator itE = outlines.end(); + + if( !aOutline ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: NULL aOutline pointer\n"; + ostr << "* outline type: " << GetOutlineTypeString( outlineType ); + errormsg = ostr.str(); + + return false; + } + + if( outlines.empty() ) + { + errormsg.clear(); + 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 ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: attempting to delete first outline in list\n"; + ostr << "* outline type: " << GetOutlineTypeString( outlineType ); + errormsg = ostr.str(); + + return false; + } + + outlines.clear(); + return true; + } + + while( itS != itE ) + { + if( *itS == aOutline ) + { + outlines.erase( itS ); + return true; + } + + ++itS; + } + + errormsg.clear(); + return false; +} + + +bool BOARD_OUTLINE::DelOutline( size_t aIndex ) +{ + std::list< IDF_OUTLINE* >::iterator itS = outlines.begin(); + + if( outlines.empty() ) + { + errormsg.clear(); + return false; + } + + if( aIndex >= outlines.size() ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: index out of bounds (" << aIndex << " / " << outlines.size() << ")\n"; + ostr << "* outline type: " << GetOutlineTypeString( outlineType ); + errormsg = ostr.str(); + + 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 ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: attempting to delete first outline in list\n"; + ostr << "* outline type: " << GetOutlineTypeString( outlineType ); + errormsg = ostr.str(); + + 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() ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* aIndex (" << aIndex << ") is out of range (" << outlines.size() << ")"; + errormsg = ostr.str(); + + 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 ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + owner = aOwner; + return true; +} + +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( IDF3_BOARD* aParent ) +{ + setParent( aParent ); + outlineType = OTLN_OTHER; + side = LYR_INVALID; + single = false; + + return; +} + +bool OTHER_OUTLINE::SetOutlineIdentifier( const std::string aUniqueID ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + uniqueID = aUniqueID; + + return true; +} + +const std::string& OTHER_OUTLINE::GetOutlineIdentifier( void ) +{ + return uniqueID; +} + +bool OTHER_OUTLINE::SetSide( IDF3::IDF_LAYER aSide ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + switch( aSide ) + { + case LYR_TOP: + case LYR_BOTTOM: + side = aSide; + break; + + default: + do{ + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: invalid side (" << aSide << "); must be one of TOP/BOTTOM\n"; + ostr << "* outline type: " << GetOutlineTypeString( outlineType ); + errormsg = ostr.str(); + } while( 0 ); + + side = LYR_INVALID; + return false; + + break; + } + + return true; +} + +IDF3::IDF_LAYER OTHER_OUTLINE::GetSide( void ) +{ + return side; +} + +void OTHER_OUTLINE::readData( std::ifstream& aBoardFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ) +{ + // 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; + std::streampos pos = aBoardFile.tellg(); + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + ostringstream ostr; + ostr << "\n* BUG: invalid invocation: blank header line\n"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( quoted ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: section names must not be in quotes\n"; + ostr << "* line: '" << aHeader << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( outlineType == OTLN_OTHER ) + { + if( !CompareToken( ".OTHER_OUTLINE", token ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* BUG: not an .OTHER outline\n"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + else + { + if( !CompareToken( ".VIA_KEEPOUT", token ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* BUG: not a .VIA_KEEPOUT outline\n"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + if( aIdfVersion > IDF_V2 ) + 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; + + 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() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within .OTHER_OUTLINE section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no outline identifier\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + uniqueID = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no thickness\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + std::stringstream teststr; + teststr << token; + + teststr >> thickness; + if( teststr.fail() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: invalid thickness\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( unit == UNIT_THOU ) + { + thickness *= IDF_THOU_TO_MM; + } + else if( ( aIdfVersion == IDF_V2 ) && ( unit == UNIT_TNM ) ) + { + thickness *= IDF_TNM_TO_MM; + } + else if( unit != UNIT_MM ) + { + ostringstream ostr; + ostr << "\n* BUG: invalid UNIT type: " << unit; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( aIdfVersion == IDF_V2 ) + { + side = LYR_TOP; + } + else + { + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no board side\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !ParseIDFLayer( token, side ) || ( side != LYR_TOP && side != LYR_BOTTOM ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: invalid side (must be TOP or BOTTOM only)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + + } + + // read RECORD 3 values + readOutlines( aBoardFile, aIdfVersion ); + + // check RECORD 4 + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && aBoardFile.eof() ) || iline.empty() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( outlineType == OTLN_OTHER ) + { + if( !CompareToken( ".END_OTHER_OUTLINE", iline ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no .END_OTHER_OUTLINE found\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + else + { + if( !CompareToken( ".END_VIA_KEEPOUT", iline ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no .END_VIA_KEEPOUT found\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + + return; +} + +void OTHER_OUTLINE::writeData( std::ofstream& aBoardFile ) +{ + // this section is optional; do not write if not required + if( outlines.empty() ) + return; + + 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_THOU ) + aBoardFile << setiosflags(ios::fixed) << setprecision(5) << thickness << " "; + else + aBoardFile << setiosflags(ios::fixed) << setprecision(1) << (thickness / IDF_THOU_TO_MM) << " "; + + switch( side ) + { + case LYR_TOP: + case LYR_BOTTOM: + WriteLayersText( aBoardFile, side ); + break; + + default: + do{ + ostringstream ostr; + ostr << "\n* invalid OTHER_OUTLINE side (neither top nor bottom): "; + ostr << side; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } while( 0 ); + + break; + } + } + + // write RECORD 3 + writeOutlines( aBoardFile ); + + // write RECORD 4 + if( outlineType == OTLN_OTHER ) + aBoardFile << ".END_OTHER_OUTLINE\n\n"; + else + aBoardFile << ".END_VIA_KEEPOUT\n\n"; + + return; +} + + +bool OTHER_OUTLINE::Clear( void ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + clear(); + side = LYR_INVALID; + uniqueID.clear(); + + return true; +} + + +/* + * CLASS: ROUTE_OUTLINE + */ +ROUTE_OUTLINE::ROUTE_OUTLINE( IDF3_BOARD* aParent ) +{ + setParent( aParent ); + outlineType = OTLN_ROUTE; + single = true; + layers = LYR_INVALID; +} + +bool ROUTE_OUTLINE::SetLayers( IDF3::IDF_LAYER aLayer ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + layers = aLayer; + + return true; +} + +IDF3::IDF_LAYER ROUTE_OUTLINE::GetLayers( void ) +{ + return layers; +} + +void ROUTE_OUTLINE::readData( std::ifstream& aBoardFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ) +{ + // ROUTE_OUTLINE (or ROUTE_KEEPOUT) + // .ROUTE_OUTLINE [OWNER] + // [layers] + // [outline] + + // check RECORD 1 + std::string token; + bool quoted = false; + int idx = 0; + std::streampos pos = aBoardFile.tellg(); + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: invalid invocation; blank header line" ) ); + } + + if( quoted ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: section names must not be in quotes\n"; + ostr << "* line: '" << aHeader << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( outlineType == OTLN_ROUTE ) + { + if( !CompareToken( ".ROUTE_OUTLINE", token ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: not a ROUTE outline" ) ); + } + else + { + if( !CompareToken( ".ROUTE_KEEPOUT", token ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: not a ROUTE KEEPOUT outline" ) ); + } + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + if( aIdfVersion > IDF_V2 ) + 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; + + if( aIdfVersion > IDF_V2 || outlineType == OTLN_ROUTE_KEEPOUT ) + { + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( !aBoardFile.good() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within a section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no layers specification\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( quoted ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: layers specification must not be in quotes\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !ParseIDFLayer( token, layers ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: invalid layers specification\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( aIdfVersion == IDF_V2 ) + { + if( layers == LYR_INNER || layers == LYR_ALL ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: IDFv2 allows only TOP/BOTTOM/BOTH; layer was '"; + ostr << token << "'\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + + } // RECORD 2, conditional > IDFv2 or ROUTE_KO_OUTLINE + else + { + layers = LYR_ALL; + } + + // read RECORD 3 values + readOutlines( aBoardFile, aIdfVersion ); + + // check RECORD 4 + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && aBoardFile.eof() ) || iline.empty() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( outlineType == OTLN_ROUTE ) + { + if( !CompareToken( ".END_ROUTE_OUTLINE", iline ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no .END_ROUTE_OUTLINE found\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + else + { + if( !CompareToken( ".END_ROUTE_KEEPOUT", iline ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no .END_ROUTE_KEEPOUT found\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + + return; +} + + +void ROUTE_OUTLINE::writeData( std::ofstream& aBoardFile ) +{ + // this section is optional; do not write if not required + if( outlines.empty() ) + return; + + if( layers == LYR_INVALID ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "layer not specified" ) ); + + 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 + writeOutlines( aBoardFile ); + + // write RECORD 4 + if( outlineType == OTLN_ROUTE ) + aBoardFile << ".END_ROUTE_OUTLINE\n\n"; + else + aBoardFile << ".END_ROUTE_KEEPOUT\n\n"; + + return; +} + + +bool ROUTE_OUTLINE::Clear( void ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + clear(); + layers = LYR_INVALID; + + return true; +} + + +/* + * CLASS: PLACE_OUTLINE + */ +PLACE_OUTLINE::PLACE_OUTLINE( IDF3_BOARD* aParent ) +{ + setParent( aParent ); + outlineType = OTLN_PLACE; + single = true; + thickness = -1.0; + side = LYR_INVALID; +} + + +bool PLACE_OUTLINE::SetSide( IDF3::IDF_LAYER aSide ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + switch( aSide ) + { + case LYR_TOP: + case LYR_BOTTOM: + case LYR_BOTH: + side = aSide; + break; + + default: + do{ + side = LYR_INVALID; + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: invalid layer (" << aSide << "): must be one of TOP/BOTTOM/BOTH\n"; + ostr << "* outline type: " << GetOutlineTypeString( outlineType ); + errormsg = ostr.str(); + + return false; + } while( 0 ); + + break; + } + + return true; +} + + +IDF3::IDF_LAYER PLACE_OUTLINE::GetSide( void ) +{ + return side; +} + + +bool PLACE_OUTLINE::SetMaxHeight( double aHeight ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + if( aHeight < 0.0 ) + { + thickness = 0.0; + + do{ + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: invalid height (" << aHeight << "): must be >= 0.0"; + ostr << "* outline type: " << GetOutlineTypeString( outlineType ); + errormsg = ostr.str(); + + return false; + } while( 0 ); + } + + thickness = aHeight; + return true; +} + +double PLACE_OUTLINE::GetMaxHeight( void ) +{ + return thickness; +} + +void PLACE_OUTLINE::readData( std::ifstream& aBoardFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ) +{ + // 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; + std::streampos pos = aBoardFile.tellg(); + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: invalid invocation: blank header line\n" ) ); + + if( quoted ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: section name must not be in quotes\n"; + ostr << "* line: '" << aHeader << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( outlineType == OTLN_PLACE ) + { + if( !CompareToken( ".PLACE_OUTLINE", token ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: not a .PLACE_OUTLINE" ) ); + } + else + { + if( !CompareToken( ".PLACE_KEEPOUT", token ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: not a .PLACE_KEEPOUT" ) ); + } + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + if( aIdfVersion > IDF_V2 ) + 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; + + if( aIdfVersion > IDF_V2 || outlineType == OTLN_PLACE_KEEPOUT ) + { + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( !aBoardFile.good() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within the section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no board side information\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !ParseIDFLayer( token, side ) || + ( side != LYR_TOP && side != LYR_BOTTOM && side != LYR_BOTH ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: invalid board side: must be one of TOP/BOTTOM/BOTH\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( GetIDFString( iline, token, quoted, idx ) ) + { + std::stringstream teststr; + teststr << token; + + teststr >> thickness; + + if( teststr.fail() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: invalid height\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( thickness < 0.0 ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: thickness < 0\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( unit == UNIT_THOU ) + { + thickness *= IDF_THOU_TO_MM; + } + else if( ( aIdfVersion == IDF_V2 ) && ( unit == UNIT_TNM ) ) + { + thickness *= IDF_TNM_TO_MM; + } + else if( unit != UNIT_MM ) + { + ostringstream ostr; + ostr << "\n* BUG: invalid UNIT type: " << unit; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( thickness < 0.0 ) + thickness = 0.0; + + } + else + { + // for OTLN_PLACE, thickness may be omitted, but is required for OTLN_PLACE_KEEPOUT + if( outlineType == OTLN_PLACE_KEEPOUT ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: missing thickness\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + thickness = -1.0; + } + } + else + { + side = LYR_TOP; + thickness = 0.0; + } + + // read RECORD 3 values + readOutlines( aBoardFile, aIdfVersion ); + + // check RECORD 4 + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && aBoardFile.eof() ) || iline.empty() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( outlineType == OTLN_PLACE ) + { + if( !GetIDFString( iline, token, quoted, idx ) + || !CompareToken( ".END_PLACE_OUTLINE", token ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid .PLACE_OUTLINE section: no .END_PLACE_OUTLINE found" ) ); + } + else + { + if( !GetIDFString( iline, token, quoted, idx ) + || !CompareToken( ".END_PLACE_KEEPOUT", token ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid .PLACE_KEEPOUT section: no .END_PLACE_KEEPOUT found" ) ); + } + + return; +} + +void PLACE_OUTLINE::writeData( std::ofstream& aBoardFile ) +{ + // this section is optional; do not write if not required + if( outlines.empty() ) + return; + + 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: + do + { + ostringstream ostr; + ostr << "\n* invalid PLACE_OUTLINE/KEEPOUT side ("; + ostr << side << "); must be one of TOP/BOTTOM/BOTH"; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } while( 0 ); + + break; + } + + // thickness is optional for OTLN_PLACE, but mandatory for OTLN_PLACE_KEEPOUT + if( thickness < 0.0 && outlineType == OTLN_PLACE_KEEPOUT) + { + aBoardFile << "\n"; + } + else + { + aBoardFile << " "; + + if( unit != UNIT_THOU ) + aBoardFile << setiosflags(ios::fixed) << setprecision(5) << thickness << "\n"; + else + aBoardFile << setiosflags(ios::fixed) << setprecision(1) << (thickness / IDF_THOU_TO_MM) << "\n"; + } + + // write RECORD 3 + writeOutlines( aBoardFile ); + + // write RECORD 4 + if( outlineType == OTLN_PLACE ) + aBoardFile << ".END_PLACE_OUTLINE\n\n"; + else + aBoardFile << ".END_PLACE_KEEPOUT\n\n"; + + return; +} + + +bool PLACE_OUTLINE::Clear( void ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + clear(); + thickness = 0.0; + side = LYR_INVALID; + + return true; +} + + +/* + * CLASS: ROUTE_KEEPOUT + */ +ROUTE_KO_OUTLINE::ROUTE_KO_OUTLINE( IDF3_BOARD* aParent ) + : ROUTE_OUTLINE( aParent ) +{ + outlineType = OTLN_ROUTE_KEEPOUT; + return; +} + + +/* + * CLASS: PLACE_KEEPOUT + */ +PLACE_KO_OUTLINE::PLACE_KO_OUTLINE( IDF3_BOARD* aParent ) + : PLACE_OUTLINE( aParent ) +{ + outlineType = OTLN_PLACE_KEEPOUT; + return; +} + + +/* + * CLASS: VIA_KEEPOUT + */ +VIA_KO_OUTLINE::VIA_KO_OUTLINE( IDF3_BOARD* aParent ) + : OTHER_OUTLINE( aParent ) +{ + single = true; + outlineType = OTLN_VIA_KEEPOUT; +} + + +/* + * CLASS: PLACEMENT GROUP (PLACE_REGION) + */ +GROUP_OUTLINE::GROUP_OUTLINE( IDF3_BOARD* aParent ) +{ + setParent( aParent ); + outlineType = OTLN_GROUP_PLACE; + thickness = 0.0; + side = LYR_INVALID; + single = true; + return; +} + + +bool GROUP_OUTLINE::SetSide( IDF3::IDF_LAYER aSide ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + switch( aSide ) + { + case LYR_TOP: + case LYR_BOTTOM: + case LYR_BOTH: + side = aSide; + break; + + default: + do{ + ostringstream ostr; + ostr << "invalid side (" << aSide << "); must be one of TOP/BOTTOM/BOTH\n"; + ostr << "* outline type: " << GetOutlineTypeString( outlineType ); + errormsg = ostr.str(); + + return false; + } while( 0 ); + + break; + } + + return true; +} + + +IDF3::IDF_LAYER GROUP_OUTLINE::GetSide( void ) +{ + return side; +} + + +bool GROUP_OUTLINE::SetGroupName( std::string aGroupName ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + groupName = aGroupName; + + return true; +} + + +const std::string& GROUP_OUTLINE::GetGroupName( void ) +{ + return groupName; +} + + +void GROUP_OUTLINE::readData( std::ifstream& aBoardFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ) +{ + // 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; + std::streampos pos = aBoardFile.tellg(); + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: invalid invocation: blank header line" ) ); + + if( quoted ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: section name must not be in quotes\n"; + ostr << "* line: '" << aHeader << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !CompareToken( ".PLACE_REGION", token ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: not a .PLACE_REGION" ) ); + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + { + if( aIdfVersion > IDF_V2 ) + 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; + + // check RECORD 2 + // [side: Top/Bot/Both ] [component group name] + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( !aBoardFile.good() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no board side specified\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !ParseIDFLayer( token, side ) || + ( side != LYR_TOP && side != LYR_BOTTOM && side != LYR_BOTH ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: invalid board side, must be one of TOP/BOTTOM/BOTH\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no outline identifier\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + groupName = token; + + // read RECORD 3 values + readOutlines( aBoardFile, aIdfVersion ); + + // check RECORD 4 + while( aBoardFile.good() && !FetchIDFLine( aBoardFile, iline, comment, pos ) ); + + if( ( !aBoardFile.good() && aBoardFile.eof() ) || iline.empty() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) + || !CompareToken( ".END_PLACE_REGION", token ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* invalid .PLACE_REGION section: no .END_PLACE_REGION found" ) ); + + return; +} + + +void GROUP_OUTLINE::writeData( std::ofstream& aBoardFile ) +{ + // this section is optional; do not write if not required + if( outlines.empty() ) + return; + + 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: + do{ + ostringstream ostr; + ostr << "\n* invalid PLACE_REGION side (must be TOP/BOTTOM/BOTH): "; + ostr << side; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } while( 0 ); + + break; + } + + aBoardFile << " \"" << groupName << "\"\n"; + + // write RECORD 3 + writeOutlines( aBoardFile ); + + // write RECORD 4 + aBoardFile << ".END_PLACE_REGION\n\n"; + + return; +} + +bool GROUP_OUTLINE::Clear( void ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + clear(); + thickness = 0.0; + side = LYR_INVALID; + groupName.clear(); + + return true; +} + +/* + * CLASS: COMPONENT OUTLINE + */ +IDF3_COMP_OUTLINE::IDF3_COMP_OUTLINE( IDF3_BOARD* aParent ) +{ + setParent( aParent ); + single = true; + outlineType = OTLN_COMPONENT; + compType = COMP_INVALID; + refNum = 0; + return; +} + +void 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 ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: bad property section (no PROP)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( quoted ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: PROP or .END must not be quoted\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( token.size() >= 5 && CompareToken( ".END_", token.substr( 0, 5 ) ) ) + { + if(aLibFile.eof()) + aLibFile.clear(); + + aLibFile.seekg( pos ); + return; + } + + if( !CompareToken( "PROP", token ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: expecting PROP or .END_ELECTRICAL\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no PROP name\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + pname = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no PROP value\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + pval = token; + + if( props.insert( pair< string, string >(pname, pval) ).second == false ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: duplicate property name \"" << pname << "\"\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + + return; +} + + +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(); +} + +void IDF3_COMP_OUTLINE::readData( std::ifstream& aLibFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ) +{ + // .ELECTRICAL/.MECHANICAL + // [GEOM] [PART] [UNIT] [HEIGHT] + // [outline] + // [PROP] [prop name] [prop value] + // check RECORD 1 + std::string token; + bool quoted = false; + int idx = 0; + std::streampos pos = aLibFile.tellg(); + + if( !GetIDFString( aHeader, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: invalid invocation: blank header line" ) ); + + if( quoted ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: section name must not be in quotes\n"; + ostr << "* line: '" << aHeader << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( CompareToken( ".ELECTRICAL", token ) ) + { + compType = COMP_ELEC; + } + else if( CompareToken( ".MECHANICAL", token ) ) + { + compType = COMP_MECH; + } + else + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: expecting .ELECTRICAL or .MECHANICAL header\n"; + ostr << "* line: '" << aHeader << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + // check RECORD 2 + // [GEOM] [PART] [UNIT] [HEIGHT] + std::string iline; + bool comment = false; + + while( aLibFile.good() && !FetchIDFLine( aLibFile, iline, comment, pos ) ); + + if( !aLibFile.good() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no GEOMETRY NAME\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + geometry = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no PART NAME\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + part = token; + + if( part.empty() && geometry.empty() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: both GEOMETRY and PART names are empty\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no UNIT type\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( CompareToken( "MM", token ) ) + { + unit = UNIT_MM; + } + else if( CompareToken( "THOU", token ) ) + { + unit = UNIT_THOU; + } + else if( aIdfVersion == IDF_V2 && !CompareToken( "TNM", token ) ) + { + unit = UNIT_TNM; + } + else + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: invalid UNIT '" << token << "': must be one of MM or THOU\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no height specified\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + std::istringstream teststr; + teststr.str( token ); + + teststr >> thickness; + if( teststr.fail() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: invalid height '" << token << "'\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( unit == UNIT_THOU ) + { + thickness *= IDF_THOU_TO_MM; + } + else if( ( aIdfVersion == IDF_V2 ) && ( unit == UNIT_TNM ) ) + { + thickness *= IDF_TNM_TO_MM; + } + else if( unit != UNIT_MM ) + { + ostringstream ostr; + ostr << "\n* BUG: invalid UNIT type: " << unit; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + // read RECORD 3 values + readOutlines( aLibFile, aIdfVersion ); + + if( compType == COMP_ELEC && aIdfVersion > IDF_V2 ) + readProperties( aLibFile ); + + // check RECORD 4 + while( aLibFile.good() && !FetchIDFLine( aLibFile, iline, comment, pos ) ); + + if( ( !aLibFile.good() && aLibFile.eof() ) || iline.empty() ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: premature end\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + if( comment ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: comment within section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( compType == COMP_ELEC ) + { + if( !CompareToken( ".END_ELECTRICAL", iline ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no .END_ELECTRICAL found\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + else + { + if( !CompareToken( ".END_MECHANICAL", iline ) ) + { + ostringstream ostr; + + ostr << "\n* invalid outline: " << GetOutlineTypeString( outlineType ) << "\n"; + ostr << "* violation: no .END_MECHANICAL found\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + + return; +} + + +void IDF3_COMP_OUTLINE::writeData( std::ofstream& aLibFile ) +{ + if( refNum == 0 ) + return; // nothing to do + + if( compType != COMP_ELEC && compType != COMP_MECH ) + { + ostringstream ostr; + ostr << "\n* component type not set or invalid: " << compType; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + 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_THOU ) + aLibFile << "MM " << setiosflags(ios::fixed) << setprecision(5) << thickness << "\n"; + else + aLibFile << "THOU " << setiosflags(ios::fixed) << setprecision(1) << (thickness / IDF_THOU_TO_MM) << "\n"; + + writeOutlines( aLibFile ); + + if( compType == COMP_ELEC ) + { + writeProperties( aLibFile ); + aLibFile << ".END_ELECTRICAL\n\n"; + } + else + { + aLibFile << ".END_MECHANICAL\n\n"; + } + + return; +} + + +bool IDF3_COMP_OUTLINE::Clear( void ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !CheckOwnership( __LINE__, __FUNCTION__, parent, owner, outlineType, errormsg ) ) + return false; +#endif + + clear(); + uid.clear(); + geometry.clear(); + part.clear(); + compType = COMP_INVALID; + refNum = 0; + props.clear(); + + return true; +} + +bool IDF3_COMP_OUTLINE::SetComponentClass( IDF3::COMP_TYPE aCompClass ) +{ + switch( aCompClass ) + { + case COMP_ELEC: + case COMP_MECH: + compType = aCompClass; + break; + + default: + do{ + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: invalid component class (must be ELECTRICAL or MECHANICAL): "; + ostr << aCompClass << "\n"; + errormsg = ostr.str(); + + return false; + } while( 0 ); + + break; + } + + return true; +} + + +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 ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: decrementing refNum beyond 0"; + errormsg = ostr.str(); + + return -1; + } + + --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 0000000..33957e7 --- /dev/null +++ b/utils/idftools/idf_outlines.h @@ -0,0 +1,771 @@ +/* + * 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 <string> +#include <list> +#include <map> +#include <wx/string.h> +#include <wx/filename.h> + +#include <idf_common.h> + +/* + * 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 +{ +friend class IDF3_BOARD; +protected: + std::string errormsg; + 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 + void readOutlines( std::ifstream& aBoardFile, IDF3::IDF_VERSION aIdfVersion ); + // 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 + void writeOutline( std::ofstream& aBoardFile, IDF_OUTLINE* aOutline, size_t aIndex ); + // Iterate through the outlines and write out all data + void writeOutlines( std::ofstream& aBoardFile ); // write outline data (no headers) + // Clear internal list of outlines + void clearOutlines( void ); + /** + * Function SetParent + * sets the parent IDF_BOARD object + */ + void setParent( IDF3_BOARD* aParent ); + + // Shadow routines used by friends to bypass ownership checks + bool addOutline( IDF_OUTLINE* aOutline ); + virtual bool setThickness( double aThickness ); + virtual void clear( void ); + + /** + * Function readData + * reads data from a .BOARD_OUTLINE section + * 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 + * + * @param aBoardFile is an IDFv3 file opened for reading + * @param aHeader is the ".BOARD_OUTLINE" header line as read by FetchIDFLine + */ + virtual void readData( std::ifstream& aBoardFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ); + + /** + * Function writeData + * writes the comments and .BOARD_OUTLINE section to an IDFv3 file. + * Throws exceptions. + * + * @param aBoardFile is an IDFv3 file opened for writing + */ + virtual void writeData( std::ofstream& aBoardFile ); + +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 bool 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 Clear + * frees memory and reinitializes all internal data except for the parent pointer. + * + * @return bool: true if OK, false on ownership violations + */ + virtual bool Clear( void ); + + /** + * Function GetOutlineType + * returns the type of outline according to the IDFv3 classification + */ + IDF3::OUTLINE_TYPE GetOutlineType( void ); + + /** + * 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 or an ownership violation occurs. + */ + 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 or an ownership violation occurs. + */ + 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 or an ownership violation or indexation + * error occurs. + */ + 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 NULL is returned and the + * error message is set. 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 ); + + const std::string& GetError( void ) + { + return errormsg; + } +}; + + +/** + * Class OTHER_OUTLINE + * describes miscellaneous extrusions on the board + */ +class OTHER_OUTLINE : public BOARD_OUTLINE +{ +friend class IDF3_BOARD; +private: + std::string uniqueID; // Outline Identifier (IDF spec) + IDF3::IDF_LAYER side; // Board Side [TOP/BOTTOM ONLY] (IDF spec) + + /** + * Function readData + * reads an OTHER_OUTLINE data from an IDFv3 file. + * If an unrecoverable error occurs an exception is thrown. + * + * @param aBoardFile is an IDFv3 file open for reading + * @param aHeader is the .OTHER_OUTLINE header as read via FetchIDFLine + */ + virtual void readData( std::ifstream& aBoardFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ); + + /** + * 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 void writeData( std::ofstream& aBoardFile ); + +public: + OTHER_OUTLINE( IDF3_BOARD* aParent ); + + /** + * Function SetOutlineIdentifier + * sets the Outline Identifier string of this OTHER_OUTLINE object + * as per IDFv3 spec. + */ + virtual bool 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 + * or 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 Clear + * deletes internal data except for the parent object + */ + virtual bool Clear( void ); +}; + + +/** + * Class ROUTE_OUTLINE + * describes routing areas on the board + */ +class ROUTE_OUTLINE : public BOARD_OUTLINE +{ +friend class IDF3_BOARD; +private: + /** + * Function readData + * reads ROUTE_OUTLINE data from an IDFv3 file + * If an unrecoverable error occurs an exception is thrown. + * + * @param aBoardFile is an open IDFv3 board file + * @param aHeader is the .ROUTE_OUTLINE header as returned by FetchIDFLine + */ + virtual void readData( std::ifstream& aBoardFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ); + + /** + * Function writeData + * writes the ROUTE_OUTLINE data to an open IDFv3 file + */ + virtual void writeData( std::ofstream& aBoardFile ); + +protected: + IDF3::IDF_LAYER layers; // Routing layers (IDF spec) + +public: + ROUTE_OUTLINE( IDF3_BOARD* aParent ); + + /** + * Function SetLayers + * sets the layer or group of layers this outline is applicable to. + * This function is subject to IDF ownership rules; true is returned + * on success, otherwise false is returned and the error message is set. + */ + virtual bool 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 Clear + * deletes internal data except for the parent object + */ + virtual bool Clear( void ); +}; + +/** + * Class PLACE_OUTLINE + * describes areas on the board for placing components + */ +class PLACE_OUTLINE : public BOARD_OUTLINE +{ +friend class IDF3_BOARD; +private: + /** + * Function readData + * reads PLACE_OUTLINE data from an open IDFv3 file. + * If an unrecoverable error occurs an exception is thrown. + * + * @param aBoardFile is an IDFv3 file opened for reading + * @param aHeader is the .PLACE_OUTLINE header as returned by FetchIDFLine + */ + virtual void readData( std::ifstream& aBoardFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ); + + /** + * 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 void writeData( std::ofstream& aBoardFile ); + +protected: + IDF3::IDF_LAYER side; // Board Side [TOP/BOTTOM/BOTH ONLY] (IDF spec) + +public: + PLACE_OUTLINE( IDF3_BOARD* aParent ); + + /** + * Function SetSide + * sets the side (TOP, BOTTOM, BOTH) which this outline applies to. + * This function is subject to IDF ownership rules; true is returned + * on success, otherwise false is returned and the error message is set. + */ + 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 SetMaxHeight + * sets the maximum height of a component within this outline. + * This function is subject to IDF ownership rules; true is returned + * on success, otherwise false is returned and the error message is set. + */ + virtual bool SetMaxHeight( double aHeight ); + + /** + * Function GetMaxHeight + * returns the maximum allowable height for a component in this region + */ + virtual double GetMaxHeight( void ); + + /** + * Function Clear + * deletes all internal data + */ + virtual bool 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( IDF3_BOARD* aParent ); +}; + +/** + * 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( IDF3_BOARD* aParent ); +}; + + +/** + * 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( IDF3_BOARD* aParent ); +}; + +/** + * Class GROUP_OUTLINE + * represents regions and layers in which user-specified features or components + * may be placed. + */ +class GROUP_OUTLINE : public BOARD_OUTLINE +{ +friend class IDF3_BOARD; +private: + IDF3::IDF_LAYER side; // Board Side [TOP/BOTTOM/BOTH ONLY] (IDF spec) + std::string groupName; // non-unique string + + /** + * Function readData + * reads GROUP_OUTLINE data from an open IDFv3 file + * If an unrecoverable error occurs an exception is thrown. + * + * @param aBoardFile is an open IDFv3 file + * @param aHeader is the .PLACE_REGION header as returned by FetchIDFLine + */ + virtual void readData( std::ifstream& aBoardFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ); + + /** + * 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 void writeData( std::ofstream& aBoardFile ); + +public: + GROUP_OUTLINE( IDF3_BOARD* aParent ); + + /** + * Function SetSide + * sets the side which this outline applies to (TOP, BOTTOM, BOTH). + * This function is subject to IDF ownership rules; true is returned + * on success, otherwise false is returned and the error message is set. + */ + virtual bool 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. + * This function is subject to IDF ownership rules; true is returned + * on success, otherwise false is returned and the error message is set. + */ + virtual bool SetGroupName( std::string aGroupName ); + + /** + * Function GetGroupName + * returns a reference to the (non-unique) group name + */ + virtual const std::string& GetGroupName( void ); + + /** + * Function Clear + * deletes internal data, subject to IDF ownership rules + */ + virtual bool 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 +{ +friend class IDF3_BOARD; +friend class IDF3_COMP_OUTLINE_DATA; +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 + + void readProperties( std::ifstream& aLibFile ); + bool writeProperties( std::ofstream& aLibFile ); + + /** + * Function readData + * reads a component outline from an open IDFv3 file + * If an unrecoverable error occurs, an exception is thrown. + * + * @param aLibFile is an open IDFv3 Library file + * @param aHeader is the .ELECTRICAL or .MECHANICAL header as returned by FetchIDFLine + */ + virtual void readData( std::ifstream& aLibFile, const std::string& aHeader, + IDF3::IDF_VERSION aIdfVersion ); + + /** + * 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 void writeData( std::ofstream& aLibFile ); + + /** + * Function incrementRef + * increments the internal reference counter to keep track of the number of + * components referring to this outline. + * + * @return int: the number of current references to this component outline + */ + int incrementRef( void ); + + /** + * Function decrementRef + * decrements the internal reference counter to keep track of the number of + * components referring to this outline. + * + * @return int: the number of remaining references or -1 if there were no + * references when the function was invoked, in which case the error message + * is also set. + */ + int decrementRef( void ); + +public: + IDF3_COMP_OUTLINE( IDF3_BOARD* aParent ); + + /** + * Function Clear + * deletes internal outline data + */ + virtual bool Clear( void ); + + /** + * Function SetComponentClass + * sets the type of component outline (.ELECTRICAL or .MECHANICAL). + * Returns true on success, otherwise false and the error message is set + */ + bool 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 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 diff --git a/utils/idftools/idf_parser.cpp b/utils/idftools/idf_parser.cpp new file mode 100644 index 0000000..c4d3e4c --- /dev/null +++ b/utils/idftools/idf_parser.cpp @@ -0,0 +1,4288 @@ +/* + * 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 <iostream> +#include <iomanip> +#include <fstream> +#include <sstream> +#include <cmath> +#include <cerrno> +#include <algorithm> + +#include <idf_parser.h> +#include <idf_helpers.h> + +using namespace std; +using namespace IDF3; + + +static bool MatchCompOutline( IDF3_COMP_OUTLINE* aOutlineA, IDF3_COMP_OUTLINE* aOutlineB ) +{ + if( aOutlineA->GetComponentClass() != aOutlineB->GetComponentClass() ) + return false; + + if( aOutlineA->OutlinesSize() != aOutlineB->OutlinesSize() ) + return false; + + // are both outlines empty? + if( aOutlineA->OutlinesSize() == 0 ) + return true; + + IDF_OUTLINE* opA = aOutlineA->GetOutline( 0 ); + IDF_OUTLINE* opB = aOutlineB->GetOutline( 0 ); + + if( opA->size() != opB->size() ) + return false; + + if( opA->size() == 0 ) + return true; + + std::list<IDF_SEGMENT*>::iterator olAs = opA->begin(); + std::list<IDF_SEGMENT*>::iterator olAe = opA->end(); + std::list<IDF_SEGMENT*>::iterator olBs = opB->begin(); + + while( olAs != olAe ) + { + if( !(*olAs)->MatchesStart( (*olBs)->startPoint ) ) + return false; + + if( !(*olAs)->MatchesEnd( (*olBs)->endPoint ) ) + return false; + + ++olAs; + ++olBs; + } + + return true; +} + + +/* + * 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; +} + +#ifndef DISABLE_IDF_OWNERSHIP +bool IDF3_COMP_OUTLINE_DATA::checkOwnership( int aSourceLine, const char* aSourceFunc ) +{ + if( !parent ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc << "():\n"; + ostr << "* BUG: IDF3_COMP_OUTLINE_DATA::parent not set; cannot enforce ownership rules\n"; + errormsg = ostr.str(); + + return false; + } + + IDF3::IDF_PLACEMENT placement = parent->GetPlacement(); + IDF3::CAD_TYPE parentCAD = parent->GetCadType(); + + if( placement == PS_PLACED || placement == PS_UNPLACED ) + return true; + + if( placement == PS_MCAD && parentCAD == CAD_MECH ) + return true; + + if( placement == PS_ECAD && parentCAD == CAD_ELEC ) + return true; + + do + { + ostringstream ostr; + ostr << "* " << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc << "():\n"; + ostr << "* ownership violation; CAD type is "; + + if( parentCAD == CAD_MECH ) + ostr << "MCAD "; + else + ostr << "ECAD "; + + ostr << "while outline owner is " << GetPlacementString( placement ) << "\n"; + errormsg = ostr.str(); + + } while( 0 ); + + return false; +} +#endif + +bool IDF3_COMP_OUTLINE_DATA::SetOffsets( double aXoff, double aYoff, + double aZoff, double aAngleOff ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !checkOwnership( __LINE__, __FUNCTION__ ) ) + return false; +#endif + + xoff = aXoff; + yoff = aYoff; + zoff = aZoff; + aoff = aAngleOff; + return true; +} + +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; +} + +bool IDF3_COMP_OUTLINE_DATA::SetOutline( IDF3_COMP_OUTLINE* aOutline ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !checkOwnership( __LINE__, __FUNCTION__ ) ) + return false; +#endif + + if( outline ) + outline->decrementRef(); + + outline = aOutline; + + if( outline ) + outline->incrementRef(); + + return true; +} + + +bool IDF3_COMP_OUTLINE_DATA::readPlaceData( std::ifstream &aBoardFile, + IDF3::FILE_STATE& aBoardState, + IDF3_BOARD *aBoard, + IDF3::IDF_VERSION aIdfVersion, + bool aNoSubstituteOutlines ) +{ + if( !aBoard ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: invoked with no reference to the parent IDF_BOARD" ) ); + + // 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() ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: could not read PLACEMENT section\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( isComment ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: comment within PLACEMENT section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( !quoted && CompareToken( ".END_PLACEMENT", token ) ) + { + aBoardState = IDF3::FILE_PLACEMENT; + return false; + } + + std::string ngeom = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: no PART NAME in PLACEMENT RECORD2\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + std::string npart = token; + uid = ngeom + "_" + npart; + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: no REFDES in PLACEMENT RECORD2\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + 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 = aBoard->GetNewRefDes(); + } + else if( CompareToken( "BOARD", token ) ) + { + ostringstream ostr; + + ostr << "UNSUPPORTED FEATURE\n"; + ostr << "* RefDes is 'BOARD', indicating this is a PANEL FILE (not supported)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + else if( CompareToken( "PANEL", token ) ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: RefDes in PLACEMENT RECORD2 is 'PANEL'\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + else if( token.empty() ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: empty RefDes string in PLACEMENT RECORD2\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + else + { + // note: perversely, spaces can be a valid RefDes + refdes = token; + } + + // V2: RECORD 3: X, Y, ROT, SIDE (top/bot), PLACEMENT (fixed, placed, unplaced) + // V3: 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() ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* problems reading PLACEMENT SECTION, RECORD 3\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( isComment ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: comment within PLACEMENT section\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: X value must not be in quotes (PLACEMENT RECORD 3)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + istringstream istr; + istr.str( token ); + + istr >> xoff; + if( istr.fail() ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: X value is not numeric (PLACEMENT RECORD 3)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: no Y value (PLACEMENT RECORD 3)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + istr.clear(); + istr.str( token ); + + istr >> yoff; + if( istr.fail() ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: Y value is not numeric (PLACEMENT RECORD 3)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( aIdfVersion > IDF_V2 ) + { + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "invalid IDFv3 file\n"; + ostr << "* violation: no Z value (PLACEMENT RECORD 3)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + istr.clear(); + istr.str( token ); + + istr >> zoff; + if( istr.fail() ) + { + ostringstream ostr; + + ostr << "invalid IDFv3 file\n"; + ostr << "* violation: Z value is not numeric (PLACEMENT RECORD 3)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: no rotation value (PLACEMENT RECORD 3)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + istr.clear(); + istr.str( token ); + + istr >> aoff; + if( istr.fail() ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: rotation value is not numeric (PLACEMENT RECORD 3)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: no SIDE value (PLACEMENT RECORD 3)\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( CompareToken( "TOP", token ) ) + { + side = IDF3::LYR_TOP; + } + else if( CompareToken( "BOTTOM", token ) ) + { + side = IDF3::LYR_BOTTOM; + } + else + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: invalid SIDE value in PLACEMENT RECORD 3 ('"; + ostr << token << "'); must be one of TOP/BOTTOM\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: no PLACEMENT value in PLACEMENT RECORD 3\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( CompareToken( "PLACED", token ) ) + { + placement = IDF3::PS_PLACED; + } + else if( CompareToken( "UNPLACED", token ) ) + { + placement = IDF3::PS_UNPLACED; + } + else if( aIdfVersion > IDF_V2 && CompareToken( "MCAD", token ) ) + { + placement = IDF3::PS_MCAD; + } + else if( aIdfVersion > IDF_V2 && CompareToken( "ECAD", token ) ) + { + placement = IDF3::PS_ECAD; + } + else if( aIdfVersion < IDF_V3 && CompareToken( "FIXED", token ) ) + { + if( aBoard->GetCadType() == CAD_ELEC ) + placement = IDF3::PS_MCAD; + else + placement = IDF3::PS_ECAD; + } + else + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: invalid PLACEMENT value ('"; + ostr << token << "') in PLACEMENT RECORD 3\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + outline = aBoard->GetComponentOutline( uid ); + + if( outline == NULL && !aNoSubstituteOutlines ) + { + ERROR_IDF << "MISSING OUTLINE\n"; + cerr << "* GeomName( " << ngeom << " ), PartName( " << npart << " )\n"; + cerr << "* Substituting default outline.\n"; + outline = aBoard->GetInvalidOutline( ngeom, npart ); + + if( outline == NULL ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* missing outline: cannot create default" ) ); + } + + if( aBoard->GetUnit() == IDF3::UNIT_THOU ) + { + xoff *= IDF_THOU_TO_MM; + yoff *= IDF_THOU_TO_MM; + zoff *= IDF_THOU_TO_MM; + } + + parent = aBoard->FindComponent( refdes ); + + if( parent == NULL ) + { + IDF3_COMPONENT* cp = new IDF3_COMPONENT( aBoard ); + + if( cp == NULL ) + { + outline = NULL; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "cannot create component object" ) ); + } + + 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 ) + { + outline = NULL; + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: inconsistent PLACEMENT data; "; + ostr << "* SIDE value has changed from " << GetLayerString( tL ); + ostr << " to " << GetLayerString( side ) << "\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + 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() ) + { + outline = NULL; + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* violation: inconsistent PLACEMENT data; "; + ostr << "* PLACEMENT value has changed from "; + ostr << GetPlacementString( parent->GetPlacement() ); + ostr << " to " << GetPlacementString( placement ) << "\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* file position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + } + + // 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; + if( outline ) outline->incrementRef(); + outline = NULL; + + if( !parent->AddOutlineData( cdp ) ) + { + delete cdp; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "could not add outline data object" ) ); + } + + return true; +} // IDF3_COMP_OUTLINE_DATA::readPlaceData + + +void 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; + + if( outline->GetUID().empty() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "empty GEOM and PART names" ) ); + + if( aPlacement == PS_INVALID ) + { + ERROR_IDF << "placement invalid (" << aRefDes << ":"; + std::cerr << aPlacement << "); defaulting to PLACED\n"; + aPlacement = PS_PLACED; + } + + if( aSide != LYR_TOP && aSide != LYR_BOTTOM ) + { + ostringstream ostr; + ostr << "\n* invalid side (" << GetLayerString( aSide ) << "); "; + ostr << "must be TOP or BOTTOM\n"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + // 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_THOU_TO_MM) << " " + << (ypos / IDF_THOU_TO_MM) << " " << (zoff / IDF_THOU_TO_MM) << " " + << 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; +} + + +/* + * CLASS: IDF3_COMPONENT + * + * This represents a component and its associated + * IDF outlines and ancillary data (position, etc) + */ +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; +} + +#ifndef DISABLE_IDF_OWNERSHIP +bool IDF3_COMPONENT::checkOwnership( int aSourceLine, const char* aSourceFunc ) +{ + if( !parent ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc << "():\n"; + ostr << "\n* BUG: parent not set"; + errormsg = ostr.str(); + + return false; + } + + IDF3::CAD_TYPE pcad = parent->GetCadType(); + + switch( placement ) + { + case PS_UNPLACED: + case PS_PLACED: + case PS_INVALID: + break; + + case PS_MCAD: + + if( pcad != CAD_MECH ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "\n* ownership violation; internal CAD type (MCAD) conflicts with PLACEMENT ("; + ostr << GetPlacementString( placement ) << ")"; + errormsg = ostr.str(); + + return false; + } + break; + + case PS_ECAD: + + if( pcad != CAD_ELEC ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "\n* ownership violation; internal CAD type (MCAD) conflicts with PLACEMENT ("; + ostr << GetPlacementString( placement ) << ")"; + errormsg = ostr.str(); + + return false; + } + break; + + default: + do{ + ostringstream ostr; + ostr << "\n* BUG: unhandled internal placement value (" << placement << ")"; + errormsg = ostr.str(); + + return false; + } while( 0 ); + + break; + } + + return true; +} +#endif + + +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 ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !checkOwnership( __LINE__, __FUNCTION__ ) ) + return false; +#endif + + if( aRefDes.empty() ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): invalid RefDes (empty)"; + errormsg = ostr.str(); + + return false; + } + + if( CompareToken( "PANEL", aRefDes ) ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: PANEL is a reserved designator and may not be used by components"; + errormsg = ostr.str(); + + 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 at component level\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 ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !checkOwnership( __LINE__, __FUNCTION__ ) ) + return false; +#endif + + errormsg.clear(); + + if( drills.empty() ) + return false; + + 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; + itS = drills.erase( itS ); + continue; + } + ++itS; + } + + return val; +} + + +bool IDF3_COMPONENT::DelDrill( IDF_DRILL_DATA* aDrill ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !checkOwnership( __LINE__, __FUNCTION__ ) ) + return false; +#endif + + errormsg.clear(); + + if( drills.empty() ) + return false; + + 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 ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): invalid aComponentOutline (NULL)"; + errormsg = ostr.str(); + + return false; + } + + + components.push_back( aComponentOutline ); + + return true; +} + +bool IDF3_COMPONENT::DeleteOutlineData( IDF3_COMP_OUTLINE_DATA* aComponentOutline ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !checkOwnership( __LINE__, __FUNCTION__ ) ) + return false; +#endif + + if( components.empty() ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): component list is empty"; + errormsg = ostr.str(); + + return false; + } + + if( aComponentOutline == NULL ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): invalid aComponentOutline (NULL)"; + errormsg = ostr.str(); + + return false; + } + + errormsg.clear(); + + 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 ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !checkOwnership( __LINE__, __FUNCTION__ ) ) + return false; +#endif + + if( aIndex >= components.size() ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* aIndex (" << aIndex << ") out of range; list size is " << components.size(); + errormsg = ostr.str(); + + 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 ) +{ + errormsg.clear(); + + 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 ) +{ +#ifndef DISABLE_IDF_OWNERSHIP + if( !checkOwnership( __LINE__, __FUNCTION__ ) ) + return false; +#endif + + errormsg.clear(); + + switch( aLayer ) + { + case LYR_TOP: + case LYR_BOTTOM: + break; + + default: + do{ + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "\n* invalid side (must be TOP or BOTTOM only): " << GetLayerString( aLayer ); + errormsg = ostr.str(); + + return false; + } while( 0 ); + 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; +} + + +bool IDF3_COMPONENT::SetPlacement( IDF3::IDF_PLACEMENT aPlacementValue ) +{ + if( aPlacementValue < PS_UNPLACED || aPlacementValue >= PS_INVALID ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "\n* invalid PLACEMENT value (" << aPlacementValue << ")"; + errormsg = ostr.str(); + + return false; + } + +#ifndef DISABLE_IDF_OWNERSHIP + if( !checkOwnership( __LINE__, __FUNCTION__ ) ) + return false; +#endif + + placement = aPlacementValue; + + return true; +} + +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 ) + { + (*itS)->write( aBoardFile, GetUnit() ); + ++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 ) + { + (*itS)->writePlaceData( aBoardFile, xpos, ypos, angle, refdes, placement, layer ); + ++itS; + } + + return true; +} + + +IDF3_BOARD::IDF3_BOARD( IDF3::CAD_TYPE aCadType ) +{ + idfVer = IDF_V3; + state = FILE_START; + cadType = aCadType; + userPrec = 5; + userScale = 1.0; + userXoff = 0.0; + userYoff = 0.0; + brdFileVersion = 0; + libFileVersion = 0; + iRefDes = 0; + unit = UNIT_MM; + + // 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; +} + + +const std::string& IDF3_BOARD::GetNewRefDes( void ) +{ + ostringstream ostr; + ostr << "NOREFDESn" << iRefDes++; + + sRefDes = ostr.str(); + + return sRefDes; +} + + +#ifndef DISABLE_IDF_OWNERSHIP +bool IDF3_BOARD::checkComponentOwnership( int aSourceLine, const char* aSourceFunc, + IDF3_COMPONENT* aComponent ) +{ + if( !aComponent ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc; + ostr << "(): Invalid component pointer (NULL)"; + errormsg = ostr.str(); + + return false; + } + + IDF3::IDF_PLACEMENT place = aComponent->GetPlacement(); + + if( place == PS_PLACED || place == PS_UNPLACED ) + return true; + + if( place == PS_MCAD && cadType == CAD_MECH ) + return true; + + if( place == PS_ECAD && cadType == CAD_ELEC ) + return true; + + do + { + ostringstream ostr; + ostr << "* " << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc << "():\n"; + ostr << "* ownership violation; CAD type is "; + + if( cadType == CAD_MECH ) + ostr << "MCAD "; + else + ostr << "ECAD "; + + ostr << "while outline owner is " << GetPlacementString( place ) << "\n"; + errormsg = ostr.str(); + + } while( 0 ); + + return false; +} +#endif + +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; + + case UNIT_TNM: + ERROR_IDF << "\n* TNM unit is not supported; defaulting to mm\n"; + unit = UNIT_MM; + break; + + default: + do + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* invalid board unit (" << aUnit << ")"; + errormsg = ostr.str(); + + return false; + } while( 0 ); + + 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<ROUTE_OUTLINE*>::iterator its = olnRoute.begin(); + std::list<ROUTE_OUTLINE*>::iterator ite = olnRoute.end(); + + while( its != ite ) + { + (*its)->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + do + { + std::list<PLACE_OUTLINE*>::iterator its = olnPlace.begin(); + std::list<PLACE_OUTLINE*>::iterator ite = olnPlace.end(); + + while( its != ite ) + { + (*its)->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + do + { + std::list<ROUTE_KO_OUTLINE*>::iterator its = olnRouteKeepout.begin(); + std::list<ROUTE_KO_OUTLINE*>::iterator ite = olnRouteKeepout.end(); + + while( its != ite ) + { + (*its)->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + do + { + std::list<VIA_KO_OUTLINE*>::iterator its = olnViaKeepout.begin(); + std::list<VIA_KO_OUTLINE*>::iterator ite = olnViaKeepout.end(); + + while( its != ite ) + { + (*its)->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + do + { + std::list<PLACE_KO_OUTLINE*>::iterator its = olnPlaceKeepout.begin(); + std::list<PLACE_KO_OUTLINE*>::iterator ite = olnPlaceKeepout.end(); + + while( its != ite ) + { + (*its)->SetUnit( aUnit ); + ++its; + } + + } while( 0 ); + + do + { + std::multimap<std::string, GROUP_OUTLINE*>::iterator its = olnGroup.begin(); + std::multimap<std::string, GROUP_OUTLINE*>::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<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->SetUnit( aUnit ); + ++its; + } + + } + + return true; +} + + +IDF3::IDF_UNIT IDF3_BOARD::GetUnit( void ) +{ + return unit; +} + + +bool IDF3_BOARD::SetBoardThickness( double aBoardThickness ) +{ + if( aBoardThickness <= 0.0 ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "; + ostr << "board thickness (" << aBoardThickness << ") must be > 0"; + errormsg = ostr.str(); + + return false; + } + + if(! olnBoard.SetThickness( aBoardThickness ) ) + { + errormsg = olnBoard.GetError(); + return false; + } + + return true; +} + + +double IDF3_BOARD::GetBoardThickness( void ) +{ + return olnBoard.GetThickness(); +} + + +// read the DRILLED HOLES section +void IDF3_BOARD::readBrdDrills( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ) +{ + IDF_DRILL_DATA drill; + + while( drill.read( aBoardFile, unit, aBoardState, idfVer ) ) + { + IDF_DRILL_DATA *dp = new IDF_DRILL_DATA; + *dp = drill; + + if( AddDrill( dp ) == NULL ) + { + delete dp; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: could not add drill data; cannot continue reading the file" ) ); + } + } + + return; +} + + +// read the NOTES section +void 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 ); + } + + return; +} + + +// read the component placement section +void IDF3_BOARD::readBrdPlacement( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState, bool aNoSubstituteOutlines ) +{ + IDF3_COMP_OUTLINE_DATA oldata; + + while( oldata.readPlaceData( aBoardFile, aBoardState, this, idfVer, aNoSubstituteOutlines ) ); + + return; +} + + +// read the board HEADER +void 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() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "problems reading board header" ) ); + + if( isComment ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: first line must be .HEADER\n" ) ); + + if( !CompareToken( ".HEADER", iline ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification:\n" + "* first line must be .HEADER and have no quotes or trailing text" ) ); + + // 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() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "problems reading board header, RECORD 2" ) ); + + if( isComment ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: comment within .HEADER section" ) ); + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification:\n" + "* File Type in HEADER section must not be in quotes" ) ); + + if( !CompareToken( "BOARD_FILE", token ) ) + { + ERROR_IDF; + + if( CompareToken( "PANEL_FILE", token ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "not a board file\n" + "* PANEL_FILE is not supported (expecting BOARD_FILE)" ) ); + else + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Expecting string: BOARD_FILE" ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: HEADER section, RECORD 2: no FIELD 2" ) ); + + if( quoted ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: IDF Version must not be in quotes" ) ); + + if( !token.compare( "3.0" ) || !token.compare( "3." ) || !token.compare( "3" ) ) + idfVer = IDF_V3; + else if( !token.compare( "2.0" ) || !token.compare( "2." ) || !token.compare( "2" ) ) + idfVer = IDF_V2; + else + { + ostringstream ostr; + + ostr << "unsupported IDF version\n"; + ostr << "* Expecting version to be a variant of '3.0', '2.0' (value: '" << token << "')\n"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification:\n" + "* HEADER section, RECORD 2, FIELD 3: no Source System string" ) ); + + brdSource = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification:\n" + "* HEADER section, RECORD 2, FIELD 4: no Date string" ) ); + + brdDate = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification:\n" + "* HEADER section, RECORD 2, FIELD 5: no Board File Version number" ) ); + + 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 ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification:\n" + "* HEADER section, RECORD 2, FIELD 5: Board File Version must not be in quotes" ) ); + + // RECORD 3: + // Board Name [str]: stored + // Units [str]: MM or THOU + while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() ); + + if( !aBoardFile.good() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* problems reading board header, RECORD 2" ) ); + + if( isComment ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: comment within .HEADER section" ) ); + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + boardName = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification:\n" + "* HEADER section, RECORD 3, FIELD 1: no Board Name" ) ); + + if( quoted ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification:\n" + "* HEADER section, RECORD 3, FIELD 2: UNIT may not be in quotes" ) ); + + if( CompareToken( "MM", token ) ) + { + unit = IDF3::UNIT_MM; + } + else if( CompareToken( "THOU", token ) ) + { + unit = IDF3::UNIT_THOU; + } + else if( ( idfVer == IDF_V2 ) && CompareToken( "TNM", token ) ) + { + unit = IDF3::UNIT_TNM; + } + else + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* HEADER section, RECORD 3, FIELD 2: expecting MM or THOU (got '" << token << "')\n"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + olnBoard.SetUnit( unit ); + + // RECORD 4: + // .END_HEADER + while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() ); + + if( ( !aBoardFile.good() && !aBoardFile.eof() ) || iline.empty() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "problems reading board header, RECORD 4" ) ); + + if( isComment ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: comment within .HEADER section\n" ) ); + + if( !CompareToken( ".END_HEADER", iline ) ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* Violation of specification: expected .END_HEADER\n"; + ostr << "* line: '" << iline << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + aBoardState = IDF3::FILE_HEADER; + return; +} + + +// read individual board sections; pay attention to IDFv3 section specifications +void IDF3_BOARD::readBrdSection( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState, + bool aNoSubstituteOutlines ) +{ + 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 + // .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 ) + { + if( !comments.empty() ) + ERROR_IDF << "[warning]: trailing comments in IDF file (comments will be lost)\n"; + + return; + } + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "problems reading board section" ) ); + } + + if( isComment ) + { + comments.push_back( iline ); + continue; + } + + // This must be a header + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + { + ostringstream ostr; + + ostr << "invalid IDF file\n"; + ostr << "* Violation of specification: quoted string where SECTION HEADER expected\n"; + ostr << "* line: '" << iline << "'"; + ostr << "* position: " << pos; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( CompareToken( ".BOARD_OUTLINE", token ) ) + { + if( aBoardState != IDF3::FILE_HEADER ) + { + aBoardState = IDF3::FILE_INVALID; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: no HEADER section" ) ); + } + + olnBoard.readData( aBoardFile, iline, idfVer ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::iterator ite = comments.end(); + + while( its != ite ) + { + olnBoard.AddComment( *its ); + ++its; + } + } + + aBoardState = IDF3::FILE_OUTLINE; + return; + } + + if( CompareToken( ".PANEL_OUTLINE", token ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "PANEL_OUTLINE not supported" ) ); + + if( CompareToken( ".OTHER_OUTLINE", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: expecting .BOARD_OUTLINE, have .OTHER_OUTLINE" ) ); + + OTHER_OUTLINE* op = new OTHER_OUTLINE( this ); + + if( op == NULL ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "could not create OTHER_OUTLINE object" ) ); + + op->SetUnit( unit ); + op->readData( aBoardFile, iline, idfVer ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + if( olnOther.insert( pair<string, OTHER_OUTLINE*>(op->GetOutlineIdentifier(), op) ).second == false ) + { + ostringstream ostr; + ostr << "invalid IDF file\n"; + ostr << "* Violation of specification. Non-unique ID in OTHER_OUTLINE '"; + ostr << op->GetOutlineIdentifier() << "'\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* pos: " << pos; + delete op; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + return; + } + + if( CompareToken( ".ROUTE_OUTLINE", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: expecting .BOARD_OUTLINE, have .ROUTE_OUTLINE" ) ); + + ROUTE_OUTLINE* op = new ROUTE_OUTLINE( this ); + + if( op == NULL ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "could not create ROUTE_OUTLINE object" ) ); + + op->SetUnit( unit ); + op->readData( aBoardFile, iline, idfVer ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnRoute.push_back( op ); + + return; + } + + if( CompareToken( ".PLACE_OUTLINE", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: expecting .BOARD_OUTLINE, have .PLACE_OUTLINE" ) ); + + PLACE_OUTLINE* op = new PLACE_OUTLINE( this ); + + if( op == NULL ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "could not create PLACE_OUTLINE object" ) ); + + op->SetUnit( unit ); + op->readData( aBoardFile, iline, idfVer ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnPlace.push_back( op ); + + return; + } + + if( CompareToken( ".ROUTE_KEEPOUT", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: expecting .BOARD_OUTLINE, have .ROUTE_KEEPOUT" ) ); + + ROUTE_KO_OUTLINE* op = new ROUTE_KO_OUTLINE( this ); + + if( op == NULL ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "could not create ROUTE_KEEPOUT object" ) ); + + op->SetUnit( unit ); + op->readData( aBoardFile, iline, idfVer ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnRouteKeepout.push_back( op ); + + return; + } + + if( CompareToken( ".VIA_KEEPOUT", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: expecting .BOARD_OUTLINE, have .VIA_KEEPOUT" ) ); + + VIA_KO_OUTLINE* op = new VIA_KO_OUTLINE( this ); + + if( op == NULL ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "could not create VIA_KEEPOUT object" ) ); + + op->SetUnit( unit ); + op->readData( aBoardFile, iline, idfVer ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnViaKeepout.push_back( op ); + + return; + } + + if( CompareToken( ".PLACE_KEEPOUT", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: expecting .BOARD_OUTLINE, have .PLACE_KEEPOUT" ) ); + + PLACE_KO_OUTLINE* op = new PLACE_KO_OUTLINE( this ); + + if( op == NULL ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "could not create PLACE_KEEPOUT object" ) ); + + op->SetUnit( unit ); + op->readData( aBoardFile, iline, idfVer ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnPlaceKeepout.push_back( op ); + + return; + } + + if( CompareToken( ".PLACE_REGION", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: expecting .BOARD_OUTLINE, have .PLACE_REGION" ) ); + + GROUP_OUTLINE* op = new GROUP_OUTLINE( this ); + + if( op == NULL ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "could not create PLACE_REGION object" ) ); + + op->SetUnit( unit ); + op->readData( aBoardFile, iline, idfVer ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::iterator ite = comments.end(); + + while( its != ite ) + { + op->AddComment( *its ); + ++its; + } + } + + olnGroup.insert( pair<string, GROUP_OUTLINE*>(op->GetGroupName(), op) ); + + return; + } + + if( CompareToken( ".DRILLED_HOLES", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: expecting .BOARD_OUTLINE, have .DRILLED_HOLES" ) ); + + readBrdDrills( aBoardFile, aBoardState ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::iterator ite = comments.end(); + + while( its != ite ) + { + drillComments.push_back( *its ); + ++its; + } + } + + return; + } + + if( CompareToken( ".NOTES", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: expecting .BOARD_OUTLINE, have .NOTES" ) ); + + if( idfVer < IDF_V3 ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDFv2 file\n" + "* Violation of specification: NOTES section not in specification" ) ); + + readBrdNotes( aBoardFile, aBoardState ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::iterator ite = comments.end(); + + while( its != ite ) + { + noteComments.push_back( *its ); + ++its; + } + } + + return; + } + + if( CompareToken( ".PLACEMENT", token ) ) + { + if( aBoardState != IDF3::FILE_OUTLINE ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: expecting .BOARD_OUTLINE, have .PLACEMENT" ) ); + + readBrdPlacement( aBoardFile, aBoardState, aNoSubstituteOutlines ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::iterator ite = comments.end(); + + while( its != ite ) + { + placeComments.push_back( *its ); + ++its; + } + } + + return; + } + } // while( aBoardFile.good() + + return; +} // readBrdSection() + + +// read the board file data +void IDF3_BOARD::readBoardFile( const std::string& aFileName, bool aNoSubstituteOutlines ) +{ + std::ifstream brd; + + brd.exceptions ( std::ifstream::badbit ); + + try + { + brd.open( aFileName.c_str(), std::ios_base::in | std::ios_base::binary ); + + if( !brd.is_open() ) + { + ostringstream ostr; + ostr << "\n* could not open file: '" << aFileName << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + 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. + + readBrdHeader( brd, state ); + + // read the various sections + while( state != IDF3::FILE_PLACEMENT && brd.good() ) + readBrdSection( brd, state, aNoSubstituteOutlines ); + + if( !brd.good() ) + { + // check if we have valid data + if( brd.eof() && state >= IDF3::FILE_OUTLINE && state < IDF3::FILE_INVALID ) + { + brd.close(); + return; + } + + brd.close(); + + ostringstream ostr; + ostr << "\n* empty IDF file: '" << aFileName << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + 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 + { + ostringstream ostr; + ostr << "\n* problems reading file: '" << aFileName << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF file\n" + "* Violation of specification: non-comment lines after PLACEMENT section" ) ); + } + } + } + } + catch( const std::exception& e ) + { + brd.exceptions ( std::ios_base::goodbit ); + + if( brd.is_open() ) + brd.close(); + + throw; + } + + brd.close(); + return; +} // readBoardFile() + + +// read the library sections (outlines) +void IDF3_BOARD::readLibSection( std::ifstream& aLibFile, IDF3::FILE_STATE& aLibState, IDF3_BOARD* aBoard ) +{ + if( aBoard == NULL ) + { + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* BUG: invoked with NULL reference aBoard" ) ); + } + + 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( this ); + + if( !pout ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "\n* memory allocation failure" ) ); + + while( aLibFile.good() ) + { + while( !FetchIDFLine( aLibFile, iline, isComment, pos ) && aLibFile.good() ); + + if( !aLibFile.good() && !aLibFile.eof() ) + { + delete pout; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "problems reading library section" ) ); + } + + // no data was read; this only happens at eof() + if( iline.empty() ) + { + delete pout; + return; + } + + if( isComment ) + { + comments.push_back( iline ); + continue; + } + + // This must be a header + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + { + ostringstream ostr; + ostr << "invalid IDF library\n"; + ostr << "* Violation of specification: quoted string where .ELECTRICAL or .MECHANICAL expected\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* pos: " << pos; + delete pout; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( CompareToken( ".ELECTRICAL", token ) || CompareToken( ".MECHANICAL", token ) ) + { + pout->readData( aLibFile, token, idfVer ); + + if( !comments.empty() ) + { + std::list<std::string>::iterator its = comments.begin(); + std::list<std::string>::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<const std::string, IDF3_COMP_OUTLINE*>( pout->GetUID(), pout ) ); + } + else + { + if( MatchCompOutline( pout, cop ) ) + { + delete pout; + // everything is fine; the outlines are genuine duplicates + return; + } + + ostringstream ostr; + ostr << "invalid IDF library\n"; + ostr << "duplicate Component Outline: '" << pout->GetUID() << "'\n"; + ostr << "* Violation of specification: multiple outlines have the same GEOM and PART name\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* pos: " << pos; + delete pout; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + return; + } + else + { + ostringstream ostr; + ostr << "invalid IDF library\n"; + ostr << "* Expecting .ELECTRICAL or .MECHANICAL, got '" << token << "'\n"; + ostr << "* line: '" << iline << "'\n"; + ostr << "* pos: " << pos; + delete pout; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } + + delete pout; + + if( !aLibFile.eof() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "problems reading IDF library file" ) ); + + return; +} + + +// read the library HEADER +void 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() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* premature end of file (no HEADER)" ) ); + + if( isComment ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* Violation of specification: first line must be .HEADER" ) ); + + if( !CompareToken( ".HEADER", iline ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* Violation of specification:\n" + "* first line must be .HEADER and have no quotes or trailing text" ) ); + + // 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() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* premature end of HEADER" ) ); + + if( isComment ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* Violation of specification: comment within .HEADER section" ) ); + + idx = 0; + GetIDFString( iline, token, quoted, idx ); + + if( quoted ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* Violation of specification:\n" + "* file Type in HEADER section must not be in quotes" ) ); + + if( !CompareToken( "LIBRARY_FILE", token ) ) + { + ostringstream ostr; + ostr << "invalid IDF library\n"; + ostr << "* Expecting string: LIBRARY_FILE (got '" << token << "')\n"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* Violation of specification: HEADER section, RECORD 2: no FIELD 2" ) ); + + if( quoted ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* Violation of specification: IDF Version must not be in quotes" ) ); + + if( !token.compare( "3.0" ) || !token.compare( "3." ) || !token.compare( "3" ) ) + idfVer = IDF_V3; + else if( !token.compare( "2.0" ) || !token.compare( "2." ) || !token.compare( "2" ) ) + idfVer = IDF_V2; + else + { + ostringstream ostr; + + ostr << "unsupported IDF version\n"; + ostr << "* Expecting version to be a variant of '3.0', '2.0' (value: '" << token << "')\n"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* Violation of specification:\n" + "* HEADER section, RECORD 2, FIELD 3: no Source System string" ) ); + + libSource = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* Violation of specification:\n" + "* HEADER section, RECORD 2, FIELD 4: no Date string" ) ); + + libDate = token; + + if( !GetIDFString( iline, token, quoted, idx ) ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* Violation of specification:\n" + "* HEADER section, RECORD 2, FIELD 5: no Board File Version number" ) ); + + 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 ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* Violation of specification:\n" + "* HEADER section, RECORD 2, FIELD 5: Library File Version must not be in quotes" ) ); + + // RECORD 3: + // .END_HEADER + while( !FetchIDFLine( aLibFile, iline, isComment, pos ) && aLibFile.good() ); + + if( ( !aLibFile.good() && !aLibFile.eof() ) || iline.empty() ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "problems reading library header, RECORD 3" ) ); + + if( isComment ) + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, + "invalid IDF library file\n" + "* Violation of specification: comment within .HEADER section" ) ); + + if( !CompareToken( ".END_HEADER", iline ) ) + { + ostringstream ostr; + ostr << "invalid IDF header\n"; + ostr << "* Violation of specification: expected .END_HEADER (got '" << iline << "')\n"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + aLibState = IDF3::FILE_HEADER; + return; +} + + +// read the library file data +void IDF3_BOARD::readLibFile( const std::string& aFileName ) +{ + std::ifstream lib; + + lib.exceptions ( std::ifstream::badbit ); + + try + { + lib.open( aFileName.c_str(), std::ios_base::in | std::ios_base::binary ); + + IDF3::FILE_STATE state = IDF3::FILE_START; + + readLibHeader( lib, state ); + + while( lib.good() ) readLibSection( lib, state, this ); + } + catch( const std::exception& e ) + { + lib.exceptions ( std::ios_base::goodbit ); + + if( lib.is_open() ) + lib.close(); + + throw; + } + + lib.close(); + return; +} + + +bool IDF3_BOARD::ReadFile( const wxString& aFullFileName, bool aNoSubstituteOutlines ) +{ + // 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 ); + + try + { + if( !brdname.IsOk() ) + { + ostringstream ostr; + ostr << "\n* invalid file name: '" << bfname << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !brdname.FileExists() ) + { + ostringstream ostr; + ostr << "\n* no such file: '" << bfname << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( !brdname.IsFileReadable() ) + { + ostringstream ostr; + ostr << "\n* cannot read file: '" << bfname << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + bfname = TO_UTF8( brdname.GetFullPath() ); + std::string lfname = TO_UTF8( libname.GetFullPath() ); + + if( !libname.FileExists() ) + { + // NOTE: Since this is a common case we simply proceed + // with the assumption that there is no library file; + // however we print a message to inform the user. + ERROR_IDF; + cerr << "no associated library file (*.emp)\n"; + } + else if( !libname.IsFileReadable() ) + { + ostringstream ostr; + ostr << "\n* cannot read library file: '" << lfname << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + else + { + // read the library file before proceeding + readLibFile( lfname ); + } + + // read the board file + readBoardFile( bfname, aNoSubstituteOutlines ); + } + catch( const std::exception& e ) + { + Clear(); + errormsg = e.what(); + + return false; + } + + return true; +} + + +// write the library file data +bool IDF3_BOARD::writeLibFile( const std::string& aFileName ) +{ + std::ofstream lib; + lib.exceptions( std::ofstream::failbit ); + + try + { + lib.open( aFileName.c_str(), std::ios_base::out ); + + 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; + } + + } + catch( const std::exception& e ) + { + lib.exceptions( std::ios_base::goodbit ); + + if( lib.is_open() ) + lib.close(); + + throw; + } + + lib.close(); + + return true; +} + +// write the board file data +void IDF3_BOARD::writeBoardFile( const std::string& aFileName ) +{ + std::ofstream brd; + brd.exceptions( std::ofstream::failbit ); + + try + { + brd.open( aFileName.c_str(), std::ios_base::out ); + + 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 + olnBoard.writeData( brd ); + + // OTHER outlines + 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->writeData( brd ); + ++its; + } + + } while( 0 ); + + // ROUTE outlines + do + { + std::list<ROUTE_OUTLINE*>::iterator its = olnRoute.begin(); + std::list<ROUTE_OUTLINE*>::iterator ite = olnRoute.end(); + + while( its != ite ) + { + (*its)->writeData( brd ); + ++its; + } + + } while( 0 ); + + // PLACEMENT outlines + do + { + std::list<PLACE_OUTLINE*>::iterator its = olnPlace.begin(); + std::list<PLACE_OUTLINE*>::iterator ite = olnPlace.end(); + + while( its != ite ) + { + (*its)->writeData( brd ); + ++its; + } + + } while( 0 ); + + // ROUTE KEEPOUT outlines + do + { + std::list<ROUTE_KO_OUTLINE*>::iterator its = olnRouteKeepout.begin(); + std::list<ROUTE_KO_OUTLINE*>::iterator ite = olnRouteKeepout.end(); + + while( its != ite ) + { + (*its)->writeData( brd ); + ++its; + } + + } while( 0 ); + + // VIA KEEPOUT outlines + do + { + std::list<VIA_KO_OUTLINE*>::iterator its = olnViaKeepout.begin(); + std::list<VIA_KO_OUTLINE*>::iterator ite = olnViaKeepout.end(); + + while( its != ite ) + { + (*its)->writeData( brd ); + ++its; + } + + } while( 0 ); + + // PLACE KEEPOUT outlines + do + { + std::list<PLACE_KO_OUTLINE*>::iterator its = olnPlaceKeepout.begin(); + std::list<PLACE_KO_OUTLINE*>::iterator ite = olnPlaceKeepout.end(); + + while( its != ite ) + { + (*its)->writeData( brd ); + ++its; + } + + } while( 0 ); + + // PLACEMENT GROUP outlines + do + { + std::multimap<std::string, GROUP_OUTLINE*>::iterator its = olnGroup.begin(); + std::multimap<std::string, GROUP_OUTLINE*>::iterator ite = olnGroup.end(); + + while( its != ite ) + { + its->second->writeData( brd ); + ++its; + } + + } while( 0 ); + + // Drilled holes + do + { + std::list<std::string>::iterator itds = drillComments.begin(); + std::list<std::string>::iterator itde = drillComments.end(); + + while( itds != itde ) + { + brd << "# " << *itds << "\n"; + ++itds; + } + + brd << ".DRILLED_HOLES\n"; + + std::list<IDF_DRILL_DATA*>::iterator itbs = board_drills.begin(); + std::list<IDF_DRILL_DATA*>::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<std::string>::iterator itncs = noteComments.begin(); + std::list<std::string>::iterator itnce = noteComments.end(); + + while( itncs != itnce ) + { + brd << "# " << *itncs << "\n"; + ++itncs; + } + + brd << ".NOTES\n"; + + std::list<IDF_NOTE*>::iterator itns = notes.begin(); + std::list<IDF_NOTE*>::iterator itne = notes.end(); + + while( itns != itne ) + { + (*itns)->writeNote( brd, unit ); + ++itns; + } + + brd << ".END_NOTES\n\n"; + + } + + // Placement + if( !components.empty() ) + { + std::list<std::string>::iterator itpcs = placeComments.begin(); + std::list<std::string>::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(); + + // determine if there are any component outlines at all and avoid + // writing an empty PLACEMENT section if there are no outlines. + // this will cost a little time but prevents software such as + // CircuitWorks from segfaulting on an empty section. + + bool hasOutlines = false; + + while( itcs != itce ) + { + if( itcs->second->GetOutlinesSize() > 0 ) + { + itcs = components.begin(); + hasOutlines = true; + break; + } + + ++itcs; + } + + if( hasOutlines ) + { + brd << ".PLACEMENT\n"; + + while( itcs != itce ) + { + itcs->second->writePlaceData( brd ); + ++itcs; + } + + brd << ".END_PLACEMENT\n"; + } + + } + + } + catch( const std::exception& e ) + { + brd.exceptions( std::ios_base::goodbit ); + + if( brd.is_open() ) + brd.close(); + + throw; + } + + brd.close(); + + return; +} + + +bool IDF3_BOARD::WriteFile( const wxString& aFullFileName, bool aUnitMM, bool aForceUnitFlag ) +{ + if( aUnitMM != IDF3::UNIT_THOU ) + 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 ); + + try + { + if( !brdname.IsOk() ) + { + ostringstream ostr; + ostr << "\n* invalid file name: '" << bfname << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + if( brdname.FileExists() && !brdname.IsFileWritable() ) + { + ostringstream ostr; + ostr << "cannot overwrite existing board file\n"; + ostr << "* filename: '" << bfname << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + bfname = TO_UTF8( brdname.GetFullPath() ); + std::string lfname = TO_UTF8( libname.GetFullPath() ); + + if( libname.FileExists() && !libname.IsFileWritable() ) + { + ostringstream ostr; + ostr << "cannot overwrite existing library file\n"; + ostr << "* filename: '" << lfname << "'"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + writeLibFile( lfname ); + writeBoardFile( bfname ); + + } + catch( const std::exception& e ) + { + errormsg = e.what(); + + 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 ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* board version (" << aVersion << ") must be >= 0"; + errormsg = ostr.str(); + + return false; + } + + brdFileVersion = aVersion; + + return true; +} + + +int IDF3_BOARD::GetLibraryVersion( void ) +{ + return libFileVersion; +} + + +bool IDF3_BOARD::SetLibraryVersion( int aVersion ) +{ + if( aVersion < 0 ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* library version (" << aVersion << ") must be >= 0"; + errormsg = ostr.str(); + + return false; + } + + libFileVersion = aVersion; + + return true; +} + + +double IDF3_BOARD::GetUserScale( void ) +{ + return userScale; +} + + +bool IDF3_BOARD::SetUserScale( double aScaleFactor ) +{ + if( aScaleFactor == 0.0 ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* BUG: user scale factor must not be 0"; + errormsg = ostr.str(); + + return false; + } + + userScale = aScaleFactor; + return true; +} + +int IDF3_BOARD::GetUserPrecision( void ) +{ + return userPrec; +} + +bool IDF3_BOARD::SetUserPrecision( int aPrecision ) +{ + if( aPrecision < 1 || aPrecision > 8 ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* precision value (" << aPrecision << ") must be 1..8"; + errormsg = ostr.str(); + + 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 ) +{ + if( !olnBoard.AddOutline( aOutline ) ) + { + errormsg = olnBoard.GetError(); + + return false; + } + + return true; +} + + +bool IDF3_BOARD::DelBoardOutline( IDF_OUTLINE* aOutline ) +{ + if( !olnBoard.DelOutline( aOutline ) ) + { + errormsg = olnBoard.GetError(); + return false; + } + + return true; +} + + +bool IDF3_BOARD::DelBoardOutline( size_t aIndex ) +{ + if( !olnBoard.DelOutline( aIndex ) ) + { + errormsg = olnBoard.GetError(); + return false; + } + + return true; +} + + +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; + + // note: PANEL drills are essentially BOARD drills which + // the panel requires to be present + if( CompareToken( "BOARD", aDrilledHole->GetDrillRefDes() ) + || CompareToken( "PANEL", aDrilledHole->GetDrillRefDes() ) ) + { + board_drills.push_back( aDrilledHole ); + return aDrilledHole; + } + + return addCompDrill( aDrilledHole ); +} + + +bool IDF3_BOARD::DelBoardDrill( double aDia, double aXpos, double aYpos ) +{ + errormsg.clear(); + + std::list<IDF_DRILL_DATA*>::iterator sp = board_drills.begin(); + std::list<IDF_DRILL_DATA*>::iterator ep = board_drills.end(); + bool rval = false; + + while( sp != ep ) + { + if( (*sp)->Matches( aDia, aXpos, aYpos ) ) + { +#ifndef DISABLE_IDF_OWNERSHIP + IDF3::KEY_OWNER keyo = (*sp)->GetDrillOwner(); + + if( keyo == UNOWNED || ( keyo == MCAD && cadType == CAD_MECH ) + || ( keyo == ECAD && cadType == CAD_ELEC ) ) + { + rval = true; + delete *sp; + sp = board_drills.erase( sp ); + continue; + } + else + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* ownership violation; drill owner ("; + + switch( keyo ) + { + case ECAD: + ostr << "ECAD"; + break; + + case MCAD: + ostr << "MCAD"; + break; + + default: + ostr << "invalid: " << keyo; + break; + } + + ostr << ") may not be modified by "; + + if( cadType == CAD_MECH ) + ostr << "MCAD"; + else + ostr << "ECAD"; + + errormsg = ostr.str(); + + ++sp; + continue; + } +#else + rval = true; + delete *sp; + sp = board_drills.erase( sp ); + continue; +#endif + } + + ++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. Note: this mechanism must bypass +// the BOARD_OUTLINE ownership rules +bool IDF3_BOARD::AddSlot( double aWidth, double aLength, double aOrientation, double aX, double aY ) +{ + if( aWidth < IDF_MIN_DIA_MM ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* slot width (" << aWidth << ") must be >= " << IDF_MIN_DIA_MM; + errormsg = ostr.str(); + + return false; + } + + if( aLength < IDF_MIN_DIA_MM ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* slot length (" << aLength << ") must be >= " << IDF_MIN_DIA_MM; + errormsg = ostr.str(); + + return false; + } + + IDF_POINT c[2]; // centers + IDF_POINT pt[4]; + + double a1 = aOrientation / 180.0 * M_PI; + double a2 = a1 + M_PI_2; + 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 ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* could not create an outline object"; + errormsg = ostr.str(); + + 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 ); + + if( !olnBoard.addOutline( outline ) ) + { + errormsg = olnBoard.GetError(); + return false; + } + + return true; +} + + +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 ) ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* PANEL data not supported"; + errormsg = ostr.str(); + + return NULL; + } + + std::map<std::string, IDF3_COMPONENT*>::iterator ref = components.find( refdes ); + + if( ref == components.end() ) + { + // create the item + IDF3_COMPONENT* comp = new IDF3_COMPONENT( this ); + + if( comp == NULL ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* could not create new component object"; + errormsg = ostr.str(); + + return NULL; + } + + comp->SetParent( this ); + comp->SetRefDes( refdes ); + ref = components.insert( std::pair< std::string, IDF3_COMPONENT*> ( comp->GetRefDes(), comp ) ).first; + } + + // add the drill + IDF_DRILL_DATA* dp = ref->second->AddDrill( aDia, aXpos, aYpos, aPlating, aHoleType, aOwner ); + + if( !dp ) + { + errormsg = ref->second->GetError(); + return NULL; + } + + return dp; +} + + +IDF_DRILL_DATA* IDF3_BOARD::addCompDrill( IDF_DRILL_DATA* aDrilledHole ) +{ + if( !aDrilledHole ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): NULL pointer"; + errormsg = ostr.str(); + + return NULL; + } + + if( CompareToken( "PANEL", aDrilledHole->GetDrillRefDes() ) ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* PANEL data not supported"; + errormsg = ostr.str(); + + return NULL; + } + + std::map<std::string, IDF3_COMPONENT*>::iterator ref = components.find( aDrilledHole->GetDrillRefDes() ); + + if( ref == components.end() ) + { + // create the item + IDF3_COMPONENT* comp = new IDF3_COMPONENT( this ); + + if( comp == NULL ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* could not create new component object"; + errormsg = ostr.str(); + + return NULL; + } + + comp->SetParent( this ); + comp->SetRefDes( aDrilledHole->GetDrillRefDes() ); + ref = components.insert( std::pair< std::string, IDF3_COMPONENT*> ( comp->GetRefDes(), comp ) ).first; + } + + IDF_DRILL_DATA* dp = ref->second->AddDrill( aDrilledHole ); + + if( !dp ) + { + errormsg = ref->second->GetError(); + return NULL; + } + + return dp; +} + + +bool IDF3_BOARD::delCompDrill( double aDia, double aXpos, double aYpos, std::string aRefDes ) +{ + errormsg.clear(); + + std::map<std::string, IDF3_COMPONENT*>::iterator ref = components.find( aRefDes ); + + if( ref == components.end() ) + return false; + + if( !ref->second->DelDrill( aDia, aXpos, aYpos ) ) + { + errormsg = ref->second->GetError(); + return false; + } + + return true; +} + + +bool IDF3_BOARD::AddComponent( IDF3_COMPONENT* aComponent ) +{ + if( !aComponent ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__; + ostr << "(): Invalid component pointer (NULL)"; + errormsg = ostr.str(); + + return false; + } + + if( components.insert( std::pair<std::string, IDF3_COMPONENT*> + ( aComponent->GetRefDes(), aComponent ) ).second == false ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n"; + ostr << "* duplicate RefDes ('" << aComponent->GetRefDes() << "')"; + errormsg = ostr.str(); + + return false; + } + + return true; +} + + +bool IDF3_BOARD::DelComponent( IDF3_COMPONENT* aComponent ) +{ + errormsg.clear(); + +#ifndef DISABLE_IDF_OWNERSHIP + if( !checkComponentOwnership( __LINE__, __FUNCTION__, aComponent ) ) + return false; +#endif + + std::map<std::string, IDF3_COMPONENT*>::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() ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n"; + ostr << "* aIndex (" << aIndex << ") out of range (" << components.size() << ")"; + errormsg = ostr.str(); + + return false; + } + + std::map<std::string, IDF3_COMPONENT*>::iterator it = components.begin(); + + while( aIndex-- > 0 ) ++it; + +#ifndef DISABLE_IDF_OWNERSHIP + if( !checkComponentOwnership( __LINE__, __FUNCTION__, it->second ) ) + return false; +#endif + + delete it->second; + components.erase( it ); + + return true; +} + + +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<std::string, IDF3_COMPONENT*>::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( wxString aFullFileName ) +{ + std::string fname = TO_UTF8( aFullFileName ); + wxFileName idflib( aFullFileName ); + + if( !idflib.IsOk() ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n"; + cerr << "* invalid file name: '" << fname << "'"; + errormsg = ostr.str(); + + return NULL; + } + + if( !idflib.FileExists() ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n"; + cerr << "* no such file: '" << fname << "'"; + errormsg = ostr.str(); + + return NULL; + } + + if( !idflib.IsFileReadable() ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n"; + cerr << "* cannot read file: '" << fname << "'"; + errormsg = ostr.str(); + + return NULL; + } + + std::map< std::string, std::string >::iterator itm = uidFileList.find( fname ); + + if( itm != uidFileList.end() ) + return GetComponentOutline( itm->second ); + + IDF3_COMP_OUTLINE* cp = new IDF3_COMP_OUTLINE( this ); + + if( cp == NULL ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n"; + cerr << "* failed to create outline\n"; + cerr << "* filename: '" << fname << "'"; + errormsg = ostr.str(); + + return NULL; + } + + std::ifstream model; + model.exceptions ( std::ifstream::badbit ); + + try + { + model.open( fname.c_str(), std::ios_base::in | std::ios_base::binary ); + + + 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() ) + { + ostringstream ostr; + ostr << "\n* problems reading file: '" << fname << "'"; + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + + // accept comment lines, .ELECTRICAL, or .MECHANICAL only + if( isComment ) + { + cp->AddComment( iline ); + continue; + } + + if( CompareToken( ".ELECTRICAL", iline ) || CompareToken( ".MECHANICAL", iline ) ) + { + cp->readData( model, iline, idfVer ); + break; + } + else + { + ostringstream ostr; + ostr << "faulty IDF component definition\n"; + ostr << "* Expecting .ELECTRICAL or .MECHANICAL, got '" << iline << "'\n"; + cerr << "* File: '" << fname << "'\n"; + + throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) ); + } + } // while( true ) + } + catch( const std::exception& e ) + { + delete cp; + + model.exceptions ( std::ios_base::goodbit ); + + if( model.is_open() ) + model.close(); + + errormsg = e.what(); + + return NULL; + } + + model.close(); + + // check the unique ID against the list from library components + std::list< std::string >::iterator lsts = uidLibList.begin(); + std::list< std::string >::iterator lste = uidLibList.end(); + std::string uid = cp->GetUID(); + IDF3_COMP_OUTLINE* oldp = NULL; + + while( lsts != lste ) + { + if( ! lsts->compare( uid ) ) + { + oldp = GetComponentOutline( uid ); + + if( MatchCompOutline( cp, oldp ) ) + { + // everything is fine; the outlines are genuine duplicates; delete the copy + delete cp; + // make sure we can find the item via its filename + uidFileList.insert( std::pair< std::string, std::string>( fname, uid ) ); + // return the pointer to the original + return oldp; + } + else + { + delete cp; + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* duplicate UID for different Component Outlines: '" << uid << "'\n"; + ostr << "* original loaded from library, duplicate in current file\n"; + ostr << "* file: '" << fname << "'"; + + errormsg = ostr.str(); + return NULL; + } + } + + ++lsts; + } + + // if we got this far then any duplicates are from files previously read + oldp = GetComponentOutline( uid ); + + if( oldp == NULL ) + { + // everything is fine, there are no existing entries + uidFileList.insert( std::pair< std::string, std::string>( fname, uid ) ); + compOutlines.insert( pair<const std::string, IDF3_COMP_OUTLINE*>( uid, cp ) ); + + return cp; + } + + if( MatchCompOutline( cp, oldp ) ) + { + // everything is fine; the outlines are genuine duplicates; delete the copy + delete cp; + // make sure we can find the item via its other filename + uidFileList.insert( std::pair< std::string, std::string>( fname, uid ) ); + // return the pointer to the original + return oldp; + } + + delete cp; + + // determine the file name of the first instance + std::map< std::string, std::string >::iterator ufls = uidFileList.begin(); + std::map< std::string, std::string >::iterator ufle = uidFileList.end(); + std::string oldfname; + + while( ufls != ufle ) + { + if( ! ufls->second.compare( uid ) ) + { + oldfname = ufls->first; + break; + } + + ++ufls; + } + + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n"; + ostr << "* duplicate UID for different Component Outlines: '" << uid << "'\n"; + ostr << "* original file: '" << oldfname << "'\n"; + ostr << "* this file: '" << fname << "'"; + + errormsg = ostr.str(); + return NULL; +} + + +// 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( this ); + + if( cp == NULL ) + { + ostringstream ostr; + ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "; + cerr << "could not create new outline"; + errormsg = ostr.str(); + + return NULL; + } + + if( empty ) + cp->CreateDefaultOutline( "", "" ); + else + cp->CreateDefaultOutline( aGeomName, aPartName ); + + compOutlines.insert( pair<const std::string, IDF3_COMP_OUTLINE*>(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(); + uidFileList.clear(); + uidLibList.clear(); + brdFileVersion = 0; + libFileVersion = 0; + iRefDes = 0; + sRefDes.clear(); + + // 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<IDF_DRILL_DATA*>::iterator ds = board_drills.begin(); + std::list<IDF_DRILL_DATA*>::iterator de = board_drills.end(); + + while( ds != de ) + { + delete *ds; + ++ds; + } + + board_drills.clear(); + } while(0); + + + // delete components + do + { + std::map<std::string, IDF3_COMPONENT*>::iterator cs = components.begin(); + std::map<std::string, IDF3_COMPONENT*>::iterator ce = components.end(); + + while( cs != ce ) + { + delete cs->second; + ++cs; + } + + components.clear(); + } while(0); + + + // delete component outlines + do + { + std::map<std::string, IDF3_COMP_OUTLINE*>::iterator cs = compOutlines.begin(); + std::map<std::string, IDF3_COMP_OUTLINE*>::iterator ce = compOutlines.end(); + + while( cs != ce ) + { + delete cs->second; + ++cs; + } + + compOutlines.clear(); + } while(0); + + + // delete OTHER outlines + do + { + std::map<std::string, OTHER_OUTLINE*>::iterator os = olnOther.begin(); + std::map<std::string, OTHER_OUTLINE*>::iterator oe = olnOther.end(); + + while( os != oe ) + { + delete os->second; + ++os; + } + + olnOther.clear(); + } while(0); + + + // delete ROUTE outlines + do + { + std::list<ROUTE_OUTLINE*>::iterator os = olnRoute.begin(); + std::list<ROUTE_OUTLINE*>::iterator oe = olnRoute.end(); + + while( os != oe ) + { + delete *os; + ++os; + } + + olnRoute.clear(); + } while(0); + + + // delete PLACE outlines + do + { + std::list<PLACE_OUTLINE*>::iterator os = olnPlace.begin(); + std::list<PLACE_OUTLINE*>::iterator oe = olnPlace.end(); + + while( os != oe ) + { + delete *os; + ++os; + } + + olnPlace.clear(); + } while(0); + + + // delete ROUTE KEEPOUT outlines + do + { + std::list<ROUTE_KO_OUTLINE*>::iterator os = olnRouteKeepout.begin(); + std::list<ROUTE_KO_OUTLINE*>::iterator oe = olnRouteKeepout.end(); + + while( os != oe ) + { + delete *os; + ++os; + } + + olnRouteKeepout.clear(); + } while(0); + + + // delete VIA KEEPOUT outlines + do + { + std::list<VIA_KO_OUTLINE*>::iterator os = olnViaKeepout.begin(); + std::list<VIA_KO_OUTLINE*>::iterator oe = olnViaKeepout.end(); + + while( os != oe ) + { + delete *os; + ++os; + } + + olnViaKeepout.clear(); + } while(0); + + + // delete PLACEMENT KEEPOUT outlines + do + { + std::list<PLACE_KO_OUTLINE*>::iterator os = olnPlaceKeepout.begin(); + std::list<PLACE_KO_OUTLINE*>::iterator oe = olnPlaceKeepout.end(); + + while( os != oe ) + { + delete *os; + ++os; + } + + olnPlaceKeepout.clear(); + } while(0); + + + // delete PLACEMENT GROUP outlines + do + { + std::multimap<std::string, GROUP_OUTLINE*>::iterator os = olnGroup.begin(); + std::multimap<std::string, GROUP_OUTLINE*>::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; +} + + +const std::map<std::string, OTHER_OUTLINE*>*const +IDF3_BOARD::GetOtherOutlines( void ) +{ + return &olnOther; +} diff --git a/utils/idftools/idf_parser.h b/utils/idftools/idf_parser.h new file mode 100644 index 0000000..e666e17 --- /dev/null +++ b/utils/idftools/idf_parser.h @@ -0,0 +1,721 @@ +/* + * 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: + * + * Rules to ensure friendly use within a DLL: + * + * 1. all functions which throw exceptions must not be publicly available; + * they must become FRIEND functions instead. + * + * 2. All objects with PRIVATE functions which throw exceptions when + * invoked by a PUBLIC function must indicate success or failure + * and make the exception information available via a GetError() + * routine. + * + * General notes: + * + * 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. + */ + + +#ifndef IDF_PARSER_H +#define IDF_PARSER_H + +#include <idf_outlines.h> + +class IDF3_COMPONENT; + +class IDF3_COMP_OUTLINE_DATA +{ +friend class IDF3_BOARD; +friend class IDF3_COMPONENT; +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 + std::string errormsg; + + IDF3_COMP_OUTLINE* outline; // component outline to use + IDF3_COMPONENT* parent; // associated component + +#ifndef DISABLE_IDF_OWNERSHIP + bool checkOwnership( int aSourceLine, const char* aSourceFunc ); +#endif + + /** + * 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 aIdfVersion is the version of the file currently being parsed + * @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 + * an exception is thrown. + */ + bool readPlaceData( std::ifstream &aBoardFile, IDF3::FILE_STATE& aBoardState, + IDF3_BOARD *aBoard, IDF3::IDF_VERSION aIdfVersion, + bool aNoSubstituteOutlines ); + + /** + * 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 + */ + void writePlaceData( std::ofstream& aBoardFile, double aXpos, double aYpos, double aAngle, + const std::string aRefDes, IDF3::IDF_PLACEMENT aPlacement, + IDF3::IDF_LAYER aSide ); + +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 + * + * @return bool: true if the operation succeeded, false if an ownership + * violation occurred + */ + bool 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 + * + * @return bool: true if the operation succeeded, false if an ownership + * violation occurred + */ + bool 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; + } + + const std::string& GetError( void ) + { + return errormsg; + } +}; + + +class IDF3_COMPONENT +{ +friend class IDF3_BOARD; +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; + std::string errormsg; + + /** + * 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 ); + +#ifndef DISABLE_IDF_OWNERSHIP + bool checkOwnership( int aSourceLine, const char* aSourceFunc ); +#endif + +public: + /** + * 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. + * + * @return bool: true if the operation succeeded, otherwise false and the + * error message is set. + */ + bool SetPlacement( IDF3::IDF_PLACEMENT aPlacementValue ); + + const std::string& GetError( void ) + { + return errormsg; + } +}; + +class IDF3_BOARD +{ +private: + std::map< std::string, std::string > uidFileList; // map of files opened and UIDs + std::list< std::string > uidLibList; // list of UIDs read from a library file + std::string errormsg; // string for passing error messages to user + 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<IDF_DRILL_DATA*> 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; + IDF3::IDF_VERSION idfVer; // IDF version of Board or Library + int iRefDes; // counter for automatically numbered NOREFDES items + std::string sRefDes; + + 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<std::string, OTHER_OUTLINE*> olnOther; + // ROUTE outlines + std::list<ROUTE_OUTLINE*> olnRoute; + // PLACEMENT outlines + std::list<PLACE_OUTLINE*> olnPlace; + // ROUTE KEEPOUT outlines + std::list<ROUTE_KO_OUTLINE*> olnRouteKeepout; + // VIA KEEPOUT outlines + std::list<VIA_KO_OUTLINE*> olnViaKeepout; + // PLACE KEEPOUT outlines + std::list<PLACE_KO_OUTLINE*> olnPlaceKeepout; + // PLACEMENT GROUP outlines + std::multimap<std::string, GROUP_OUTLINE*> 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 + void readBrdDrills( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ); + // read the NOTES section + void readBrdNotes( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ); + // read the component placement section + void readBrdPlacement( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState, + bool aNoSubstituteOutlines ); + // read the board HEADER + void readBrdHeader( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState ); + // read individual board sections; pay attention to IDFv3 section specifications + // exception thrown on unrecoverable errors. state flag set to FILE_PLACEMENT + // upon reading the PLACEMENT file; according to IDFv3 this is the final section + void readBrdSection( std::ifstream& aBoardFile, IDF3::FILE_STATE& aBoardState, + bool aNoSubstituteOutlines ); + // read the board file data + void readBoardFile( const std::string& aFileName, bool aNoSubstituteOutlines ); + + // write the board file data + void writeBoardFile( const std::string& aFileName ); + + // read the library sections (outlines) + void readLibSection( std::ifstream& aLibFile, IDF3::FILE_STATE& aLibState, IDF3_BOARD* aBoard ); + // read the library HEADER + void readLibHeader( std::ifstream& aLibFile, IDF3::FILE_STATE& aLibState ); + // read the library file data + void readLibFile( const std::string& aFileName ); + + // write the library file data + bool writeLibFile( const std::string& aFileName ); + +#ifndef DISABLE_IDF_OWNERSHIP + bool checkComponentOwnership( int aSourceLine, const char* aSourceFunc, + IDF3_COMPONENT* aComponent ); +#endif + +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 ); + + const std::string& GetNewRefDes( void ); + + void SetBoardName( std::string aBoardName ); + const std::string& GetBoardName( void ); + + bool SetBoardThickness( double aBoardThickness ); + double GetBoardThickness( void ); + + bool ReadFile( const wxString& aFullFileName, bool aNoSubstituteOutlines = false ); + 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 ); + + // Operations for OTHER OUTLINES + const std::map<std::string, OTHER_OUTLINE*>*const GetOtherOutlines( void ); + + /// XXX - TO BE IMPLEMENTED + // + // SetBoardOutlineOwner() + // + // AddDrillComment + // AddPlacementComment + // GetDrillComments() + // GetPlacementComments() + // ClearDrillComments() + // ClearPlacementComments() + // AddNoteComment + // GetNoteComments + // AddNote + // + // [IMPLEMENTED] const std::map<std::string, OTHER_OUTLINE*>*const GetOtherOutlines( void ) + // size_t GetOtherOutlinesSize() + // OTHER_OUTLINE* AddOtherOutline( OTHER_OUTLINE* aOtherOutline ) + // bool DelOtherOutline( int aIndex ) + // bool DelOtherOutline( OTHER_OUTLINE* aOtherOutline ) + // + // const std::list<ROUTE_OUTLINE*>*const GetRouteOutlines() + // size_t GetRouteOutlinesSize() + // ROUTE_OUTLINE* AddRouteOutline( ROUTE_OUTLINE* aRouteOutline ) + // bool DelRouteOutline( int aIndex ) + // bool DelRouteOutline( ROUTE_OUTLINE* aRouteOutline ) + // + // const std::list<PLACE_OUTLINE*>*const GetPlacementOutlines() + // size_t GetPlacementOutlinesSize() + // PLACE_OUTLINE* AddPlacementOutline( PLACE_OUTLINE* aPlaceOutline ) + // bool DelPlacementOutline( int aIndex ) + // bool DelPlacementOutline( PLACE_OUTLINE* aPlaceOutline ) + // + // const std::list<ROUTE_KO_OUTLINE*>*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<VIA_KO_OUTLINE*>*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<PLACE_KO_OUTLINE*>*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<std::string, GROUP_OUTLINE*>*const GetGroupOutlines() + // size_t GetGroupOutlinesSize() + // GROUP_OUTLINE* AddGroupOutline( GROUP_OUTLINE* aGroupOutline ) + // bool DelGroupOutline( int aIndex ) + // bool DelGroupOutline( GROUP_OUTLINE* aGroupOutline ) + + std::list<IDF_DRILL_DATA*>& 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( 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 ); + + // return error string + const std::string& GetError( void ) + { + return errormsg; + } +}; + +#endif // IDF_PARSER_H diff --git a/utils/idftools/idf_rect.cpp b/utils/idftools/idf_rect.cpp new file mode 100644 index 0000000..17b145b --- /dev/null +++ b/utils/idftools/idf_rect.cpp @@ -0,0 +1,433 @@ +/* + * 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 <iostream> +#include <fstream> +#include <string> +#include <sstream> +#include <cmath> +#include <cstdio> +#include <list> +#include <utility> +#include <clocale> + +using namespace std; + +void writeLeaded( FILE* fp, double width, double length, double height, + double wireDia, double pitch, bool inch ); + +void writeLeadless( FILE* fp, double width, double length, + double height, double chamfer, bool inch ); + +int main( int argc, char **argv ) +{ + // IDF implicitly requires the C locale + setlocale( LC_ALL, "C" ); + + if( argc == 1 ) + { + cout << "idfrect: This program generates an outline for a rectangular component.\n"; + cout << " The component may have a single lead (axial) or a chamfer on the\n"; + cout << " upper left corner.\n"; + cout << "Input:\n"; + cout << " Unit: mm, in (millimeters or inches)\n"; + cout << " Width:\n"; + cout << " Length:\n"; + cout << " Height:\n"; + cout << " Chamfer: length of the 45 deg. chamfer\n"; + cout << " * Leaded: Y,N (lead is always to the right)\n"; + cout << " ** Wire diameter\n"; + cout << " ** Pitch\n"; + cout << " File name (must end in *.idf)\n\n"; + cout << " NOTES:\n"; + cout << " * only required if chamfer = 0\n\n"; + cout << " ** only required for leaded components\n\n"; + } + + bool inch = false; // default mm + double width = 0.0; + double length = 0.0; + double height = 0.0; + double wireDia = 0.0; + double pitch = 0.0; + double chamfer = 0.0; + bool leaded = false; + bool ok = false; + + stringstream tstr; + string line; + + line.clear(); + while( line.compare( "mm" ) && line.compare( "in" ) ) + { + cout << "* Units (mm,in): "; + line.clear(); + std::getline( cin, line ); + } + + if( line.compare( "mm" ) ) + inch = true; + + ok = false; + while( !ok ) + { + cout << "* Width: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> width; + if( !tstr.fail() && width >= 0.001 ) + ok = true; + } + + ok = false; + while( !ok ) + { + cout << "* Length: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> length; + if( !tstr.fail() && length > 0.0 ) + ok = true; + } + + ok = false; + while( !ok ) + { + cout << "* Height: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> height; + if( !tstr.fail() && height >= 0.001 ) + ok = true; + } + + ok = false; + while( !ok ) + { + cout << "* Chamfer (0 for none): "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + 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"; + else + ok = true; + } + } + + if( chamfer < 1e-6 ) + { + ok = false; + while( !ok ) + { + cout << "* Leaded: Y, N: "; + + line.clear(); + std::getline( cin, line ); + + if( !line.compare( "Y" ) || !line.compare( "y" ) ) + { + leaded = true; + ok = true; + } + else if( !line.compare( "N" ) || !line.compare( "n" ) ) + { + leaded = false; + ok = true; + } + } + } + + ok = false; + while( leaded && !ok ) + { + cout << "* Wire dia.: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> wireDia; + if( !tstr.fail() && wireDia >= 0.001 ) + { + if( wireDia >= length ) + cout << "* WARNING: wire diameter must be < length\n"; + else + ok = true; + } + } + + ok = false; + while( leaded && !ok ) + { + cout << "* Pitch: "; + + line.clear(); + std::getline( cin, line ); + + tstr.clear(); + tstr.str( line ); + + tstr >> pitch; + if( !tstr.fail() && pitch >= 0.001 ) + { + if( pitch <= ( length + wireDia ) / 2.0 ) + cout << "* WARNING: pitch must be > (length + wireDia)/2\n"; + else + ok = true; + } + } + + line.clear(); + while( line.empty() || line.find( ".idf" ) == string::npos ) + { + cout << "* File name (*.idf): "; + + line.clear(); + std::getline( cin, line ); + } + + FILE* fp = fopen( line.c_str(), "w" ); + + if( !fp ) + { + cerr << "Could not open output file: " << line << "\n"; + } + else + { + fprintf( fp, "# rectangular outline%s\n", leaded ? ", leaded" : "" ); + fprintf( fp, "# file: \"%s\"\n", line.c_str() ); + + if( inch ) + { + width *= 1000.0; + length *= 1000.0; + height *= 1000.0; + wireDia *= 1000.0; + pitch *= 1000.0; + chamfer *= 1000.0; + + fprintf( fp, "# width: %d THOU\n", (int) width ); + fprintf( fp, "# length: %d THOU\n", (int) length ); + fprintf( fp, "# height: %d THOU\n", (int) height ); + + if( leaded ) + { + fprintf( fp, "# wire dia: %d THOU\n", (int) wireDia ); + fprintf( fp, "# pitch: %d THOU\n", (int) pitch ); + } + else + { + fprintf( fp, "# chamfer: %d THOU\n", (int) chamfer ); + } + + fprintf( fp, ".ELECTRICAL\n" ); + fprintf( fp, "\"RECT%sIN\" \"W%d_L%d_H%d", leaded ? "L" : "", + (int) width, (int) length, (int) height ); + + if( leaded ) + fprintf( fp, "_D%d_P%d\" ", (int) wireDia, (int) pitch ); + else + fprintf( fp, "_C%d\" ", (int) chamfer ); + + fprintf( fp, "THOU %d\n", (int) height ); + } + else + { + fprintf( fp, "# width: %.3f mm\n", width ); + fprintf( fp, "# length: %.3f mm\n", length ); + fprintf( fp, "# height: %.3f mm\n", height ); + + if( leaded ) + { + fprintf( fp, "# wire dia: %.3f mm\n", wireDia ); + fprintf( fp, "# pitch: %.3f mm\n", pitch ); + } + else + { + fprintf( fp, "# chamfer: %.3f mm\n", chamfer ); + } + + fprintf( fp, ".ELECTRICAL\n" ); + fprintf( fp, "\"RECT%sMM\" \"W%.3f_L%.3f_H%.3f_", leaded ? "L" : "", + width, length, height ); + + if( leaded ) + fprintf( fp, "D%.3f_P%.3f\" ", wireDia, pitch ); + else + fprintf( fp, "C%.3f\" ", chamfer ); + + fprintf( fp, "MM %.3f\n", height ); + } + + if( leaded ) + writeLeaded( fp, width, length, height, wireDia, pitch, inch ); + else + writeLeadless( fp, width, length, height, chamfer, inch ); + + fprintf( fp, ".END_ELECTRICAL\n" ); + fclose( fp ); + } + + setlocale( LC_ALL, "" ); + return 0; +} + + +void writeLeaded( FILE* fp, double width, double length, + double height, double wireDia, double pitch, bool inch ) +{ + if( inch ) + { + int x1, x2, x3; + int y1, y2; + + x1 = pitch / 2.0; + x2 = width / 2.0 - x1; + x3 = x2 - width; + + y1 = wireDia / 2.0; + y2 = length / 2.0; + + fprintf( fp, "0 %d %d 0\n", x1, y1 ); + fprintf( fp, "0 %d %d 0\n", x2, y1 ); + fprintf( fp, "0 %d %d 0\n", x2, y2 ); + fprintf( fp, "0 %d %d 0\n", x3, y2 ); + fprintf( fp, "0 %d %d 0\n", x3, -y2 ); + fprintf( fp, "0 %d %d 0\n", x2, -y2 ); + fprintf( fp, "0 %d %d 0\n", x2, -y1 ); + fprintf( fp, "0 %d %d 0\n", x1, -y1 ); + fprintf( fp, "0 %d %d 180\n", x1, y1 ); + } + else + { + double x1, x2, x3; + double y1, y2; + + x1 = pitch / 2.0; + x2 = width / 2.0 - x1; + x3 = x2 - width; + + y1 = wireDia / 2.0; + y2 = length / 2.0; + + fprintf( fp, "0 %.3f %.3f 0\n", x1, y1 ); + fprintf( fp, "0 %.3f %.3f 0\n", x2, y1 ); + fprintf( fp, "0 %.3f %.3f 0\n", x2, y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", x3, y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", x3, -y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", x2, -y2 ); + fprintf( fp, "0 %.3f %.3f 0\n", x2, -y1 ); + fprintf( fp, "0 %.3f %.3f 0\n", x1, -y1 ); + fprintf( fp, "0 %.3f %.3f 180\n", x1, y1 ); + } + + return; +} + +void writeLeadless( FILE* fp, double width, double length, + double height, double chamfer, bool inch ) +{ + if( chamfer < 0.001 ) + { + if( inch ) + { + int x = width / 2.0; + int y = length / 2.0; + + fprintf( fp, "0 %d %d 0\n", x, y ); + fprintf( fp, "0 %d %d 0\n", -x, y ); + fprintf( fp, "0 %d %d 0\n", -x, -y ); + fprintf( fp, "0 %d %d 0\n", x, -y ); + fprintf( fp, "0 %d %d 0\n", x, y ); + } + else + { + double x = width / 2.0; + double y = length / 2.0; + + fprintf( fp, "0 %.3f %.3f 0\n", x, y ); + fprintf( fp, "0 %.3f %.3f 0\n", -x, y ); + fprintf( fp, "0 %.3f %.3f 0\n", -x, -y ); + fprintf( fp, "0 %.3f %.3f 0\n", x, -y ); + fprintf( fp, "0 %.3f %.3f 0\n", x, y ); + } + + return; + } + + if( inch ) + { + int x = width / 2.0; + int y = length / 2.0; + int x1 = x - chamfer; + int y1 = y - chamfer; + + fprintf( fp, "0 %d %d 0\n", x, y ); + fprintf( fp, "0 %d %d 0\n", -x1, y ); + fprintf( fp, "0 %d %d 0\n", -x, y1 ); + fprintf( fp, "0 %d %d 0\n", -x, -y ); + fprintf( fp, "0 %d %d 0\n", x, -y ); + fprintf( fp, "0 %d %d 0\n", x, y ); + } + else + { + double x = width / 2.0; + double y = length / 2.0; + double x1 = x - chamfer; + double y1 = y - chamfer; + + fprintf( fp, "0 %.3f %.3f 0\n", x, y ); + fprintf( fp, "0 %.3f %.3f 0\n", -x1, y ); + fprintf( fp, "0 %.3f %.3f 0\n", -x, y1 ); + fprintf( fp, "0 %.3f %.3f 0\n", -x, -y ); + fprintf( fp, "0 %.3f %.3f 0\n", x, -y ); + fprintf( fp, "0 %.3f %.3f 0\n", x, y ); + } + + return; +} diff --git a/utils/idftools/vrml_layer.cpp b/utils/idftools/vrml_layer.cpp new file mode 100644 index 0000000..5974371 --- /dev/null +++ b/utils/idftools/vrml_layer.cpp @@ -0,0 +1,1788 @@ +/* + * 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 + */ + +// Wishlist: +// 1. crop anything outside the board outline on PTH, silk, and copper layers +// 2. on the PTH layer, handle cropped holes differently from others; +// these are assumed to be castellated edges and the profile is not +// a closed loop as assumed for all other outlines. +// 3. a scheme is needed to tell a castellated edge from a plain board edge + + +#include <sstream> +#include <string> +#include <iomanip> +#include <cmath> +#include <vrml_layer.h> + +#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], VERTEX_3D* vertex_data[4], + GLfloat weight[4], void** outData, void* user_data ) +{ + VRML_LAYER* lp = (VRML_LAYER*) user_data; + + // the plating is set to true only if all are plated + bool plated = vertex_data[0]->pth; + + if( !vertex_data[1]->pth ) + plated = false; + + if( vertex_data[2] && !vertex_data[2]->pth ) + plated = false; + + if( vertex_data[3] && !vertex_data[3]->pth ) + plated = false; + + *outData = lp->AddExtraVertex( coords[0], coords[1], plated ); +} + + +VRML_LAYER::VRML_LAYER() +{ + // arc parameters suitable to mm measurements + maxArcSeg = 48; + minSegLength = 0.1; + maxSegLength = 0.5; + offsetX = 0.0; + offsetY = 0.0; + + fix = false; + Fault = false; + idx = 0; + hidx = 0; + eidx = 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(); + } + + pth.clear(); + + areas.clear(); + + 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; + + triplets.clear(); + solid.clear(); + + for( i = outline.size(); i > 0; --i ) + { + delete outline.back(); + outline.pop_back(); + } + + ordmap.clear(); + + 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 + vlist.clear(); + + // 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( bool aPlatedHole ) +{ + if( fix ) + return -1; + + std::list<int>* contour = new std::list<int>; + + if( !contour ) + return -1; + + contours.push_back( contour ); + areas.push_back( 0.0 ); + + pth.push_back( aPlatedHole ); + + 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->pth = pth[ aContourID ]; + + 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<int>* 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, bool aPlatedHole ) +{ + int pad; + + if( aHoleFlag && aPlatedHole ) + pad = NewContour( true ); + else + pad = NewContour( false ); + + 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, bool aPlatedHole ) +{ + 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; + + if( aHoleFlag && aPlatedHole ) + pad = NewContour( true ); + else + pad = NewContour( false ); + + 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, bool aPlatedHole ) +{ + 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; + + if( aHoleFlag && aPlatedHole ) + arc = NewContour( true ); + else + arc = NewContour( false ); + + 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, bool aHolesOnly ) +{ + if( !tess ) + { + error = "Tesselate(): GLU tesselator was not initialized"; + return false; + } + + pholes = holes; + Fault = false; + + if( aHolesOnly ) + gluTessProperty( tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NEGATIVE ); + else + gluTessProperty( tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE ); + + + 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; + + if( aHolesOnly && ( checkNContours( true ) == 0 ) ) + { + error = "tesselate(): no hole contours"; + return false; + } + else if( !aHolesOnly && ( checkNContours( false ) == 0 ) ) + { + error = "tesselate(): no solid contours"; + return false; + } + + // open the polygon + gluTessBeginPolygon( tess, this ); + + if( aHolesOnly ) + { + pholes = NULL; // do not accept foreign holes + hidx = 0; + eidx = idx; + + // add holes + pushVertices( true ); + + gluTessEndPolygon( tess ); + + if( Fault ) + return false; + + return true; + } + + // add solid outlines + pushVertices( false ); + + // close the polygon + gluTessEndPolygon( tess ); + + if( Fault ) + return false; + + // if there are no outlines we cannot proceed + if( outline.empty() ) + { + error = "tesselate(): no points in result"; + return false; + } + + // at this point we have a solid outline; add it to the tesselator + gluTessBeginPolygon( tess, this ); + + if( !pushOutline( NULL ) ) + 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 false; + } + + if( Fault ) + return false; + + // erase the previous outline data and vertex order + // but preserve the extra vertices + while( !outline.empty() ) + { + delete outline.back(); + outline.pop_back(); + } + + ordmap.clear(); + ord = 0; + + // 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; + } + + // close the polygon; this creates the outline points + // and the point ordering list 'ordmap' + solid.clear(); + gluTessEndPolygon( tess ); + + // repeat the last operation but request a tesselated surface + // rather than an outline; this creates the triangles list. + gluTessProperty( tess, GLU_TESS_BOUNDARY_ONLY, GL_FALSE ); + + gluTessBeginPolygon( tess, this ); + + 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; + } + + std::list<std::list<int>*>::const_iterator obeg = outline.begin(); + std::list<std::list<int>*>::const_iterator oend = outline.end(); + + int nc = 0; // number of contours pushed + + int pi; + std::list<int>::const_iterator begin; + std::list<int>::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() ) + { + gluTessEndContour( tess ); + 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 ) + { + gluTessEndContour( tess ); + 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; + ++nc; + } + + if( !nc ) + { + error = "pushOutline():: no valid contours available"; + return false; + } + + 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 + offsetX, vp->y + offsetY, 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 + offsetX, vp->y + offsetY, 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 + offsetX, vp->y + offsetY, 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 + offsetX, vp->y + offsetY, 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 + offsetX, vp->y + offsetY, 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 + offsetX, vp->y + offsetY, 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<TRIPLET_3D>::const_iterator tbeg = triplets.begin(); + std::list<TRIPLET_3D>::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, bool aIncludePlatedHoles ) +{ + if( outline.empty() ) + { + error = "WriteIndices(): no outline available"; + return false; + } + + char mark; + bool holes_only = triplets.empty(); + + int i = 1; + int idx2 = ordmap.size(); // index to the bottom vertices + + if( !holes_only ) + { + mark = ','; + + // go through the triplet list and write out the indices based on order + std::list<TRIPLET_3D>::const_iterator tbeg = triplets.begin(); + std::list<TRIPLET_3D>::const_iterator tend = triplets.end(); + + // 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; + } + } + else + mark = ' '; + + + // print out indices for the walls joining top to bottom + int lastPoint; + int curPoint; + int curContour = 0; + + std::list<std::list<int>*>::const_iterator obeg = outline.begin(); + std::list<std::list<int>*>::const_iterator oend = outline.end(); + std::list<int>* cp; + std::list<int>::const_iterator cbeg; + std::list<int>::const_iterator cend; + + i = 2; + while( obeg != oend ) + { + cp = *obeg; + + if( cp->size() < 3 ) + { + ++obeg; + ++curContour; + continue; + } + + cbeg = cp->begin(); + cend = cp->end(); + lastPoint = *(cbeg++); + + // skip all PTH vertices which are not in a solid outline + if( !aIncludePlatedHoles && !solid[curContour] + && getVertexByIndex( ordmap[lastPoint], pholes )->pth ) + { + ++obeg; + ++curContour; + continue; + } + + while( cbeg != cend ) + { + curPoint = *(cbeg++); + + if( !holes_only ) + { + if( (i++ & 3) == 2 ) + { + i = 1; + aOutFile << mark << "\n" << curPoint << ", " << lastPoint << ", " << curPoint + idx2; + aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint << ", " << lastPoint + idx2 << ", -1"; + } + else + { + aOutFile << mark << " " << curPoint << ", " << lastPoint << ", " << curPoint + idx2; + aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint << ", " << lastPoint + idx2 << ", -1"; + } + } + else + { + if( (i++ & 3) == 2 ) + { + i = 1; + aOutFile << mark << "\n" << curPoint << ", " << curPoint + idx2 << ", " << lastPoint; + aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint + idx2 << ", " << lastPoint << ", -1"; + } + else + { + aOutFile << mark << " " << curPoint << ", " << curPoint + idx2 << ", " << lastPoint; + aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint + idx2 << ", " << lastPoint << ", -1"; + } + } + + mark = ','; + lastPoint = curPoint; + } + + // check if the loop needs to be closed + cbeg = cp->begin(); + cend = --cp->end(); + + curPoint = *(cbeg); + lastPoint = *(cend); + + if( !holes_only ) + { + if( (i++ & 3) == 2 ) + { + 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"; + } + } + else + { + if( (i++ & 3) == 2 ) + { + aOutFile << ",\n" << curPoint << ", " << curPoint + idx2 << ", " << lastPoint; + aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint + idx2 << ", " << lastPoint << ", -1"; + } + else + { + aOutFile << ", " << curPoint << ", " << curPoint + idx2 << ", " << lastPoint; + aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint + idx2 << ", " << lastPoint << ", -1"; + } + } + + ++obeg; + ++curContour; + } + + 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 9 decimal places + // at most on the VRML output + double err = 0.000000001; + + // 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, bool aPlatedHole ) +{ + 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; + vertex->pth = aPlatedHole; + + 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<int>* loop = new std::list<int>; + + if( !loop ) + break; + + double firstX = 0.0; + double firstY = 0.0; + double lastX = 0.0; + double lastY = 0.0; + double curX, curY; + double area = 0.0; + + if( vlist.size() > 0 ) + { + loop->push_back( vlist[0]->o ); + firstX = vlist[0]->x; + firstY = vlist[0]->y; + lastX = firstX; + lastY = firstY; + } + + for( size_t i = 1; i < vlist.size(); ++i ) + { + loop->push_back( vlist[i]->o ); + curX = vlist[i]->x; + curY = vlist[i]->y; + area += ( curX - lastX ) * ( curY + lastY ); + lastX = curX; + lastY = curY; + } + + area += ( firstX - lastX ) * ( firstY + lastY ); + + outline.push_back( loop ); + + if( area <= 0.0 ) + solid.push_back( true ); + else + solid.push_back( false ); + } + 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 ) +{ + const char * msg = (const char*)gluErrorString( errorID ); + + // If errorID is an illegal id, gluErrorString returns NULL + if( msg ) + error = msg; + else + error.clear(); + + 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] ); +} + + +int VRML_LAYER::checkNContours( bool holes ) +{ + int nc = 0; // number of contours + + if( contours.empty() ) + return 0; + + std::list<int>::const_iterator begin; + std::list<int>::const_iterator end; + + for( size_t i = 0; i < contours.size(); ++i ) + { + if( contours[i]->size() < 3 ) + continue; + + if( ( holes && areas[i] <= 0.0 ) || ( !holes && areas[i] > 0.0 ) ) + continue; + + ++nc; + } + + return nc; +} + + +// push the internally held vertices +void VRML_LAYER::pushVertices( bool holes ) +{ + // push the internally held vertices + unsigned int i; + + std::list<int>::const_iterator begin; + std::list<int>::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 ); + } + + return; +} + + +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<int>::const_iterator cbeg; + std::list<int>::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; +} + + +void VRML_LAYER::SetVertexOffsets( double aXoffset, double aYoffset ) +{ + offsetX = aXoffset; + offsetY = aYoffset; + return; +} diff --git a/utils/idftools/vrml_layer.h b/utils/idftools/vrml_layer.h new file mode 100644 index 0000000..92b5891 --- /dev/null +++ b/utils/idftools/vrml_layer.h @@ -0,0 +1,461 @@ +/* + * 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_LAYER_H +#define VRML_LAYER_H + + +#include <wx/glcanvas.h> // CALLBACK definition, needed on Windows + // alse needed on OSX to define __DARWIN__ + +#ifdef __WXMAC__ +# ifdef __DARWIN__ +# include <OpenGL/glu.h> +# else +# include <glu.h> +# endif +#else +# include <GL/glu.h> +#endif + +#include <fstream> +#include <vector> +#include <list> +#include <utility> + +#ifndef M_PI2 +#define M_PI2 ( M_PI / 2.0 ) +#endif + +#ifndef M_PI4 +#define M_PI4 ( M_PI / 4.0 ) +#endif + + +struct VERTEX_3D +{ + double x; + double y; + int i; // vertex index + int o; // vertex order + bool pth; // true for plate-through hole +}; + +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 + + // Vertex offsets to work around a suspected GLU tesselator bug + double offsetX; + double offsetY; + + 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<VERTEX_3D*> vertices; // vertices of all contours + std::vector<std::list<int>*> contours; // lists of vertices for each contour + std::vector<bool>pth; // indicates whether a 'contour' is a PTH or not + std::vector<bool>solid; // indicates whether a 'contour' is a solid or a hole + std::vector< double > areas; // area of the contours (positive if winding is CCW) + std::list<TRIPLET_3D> triplets; // output facet triplet list (triplet of ORDER values) + std::list<std::list<int>*> outline; // indices for outline outputs (index by ORDER values) + std::vector<int> 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<VERTEX_3D*> extra_verts; // extra vertices added for outlines and facets + std::vector<VERTEX_3D*> 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 ); + + // returns the number of solid or hole contours + int checkNContours( bool holes ); + +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 + * + * @param aPlatedHole is true if the new contour will represent a plated hole + * + * @return int: index to the list or -1 if the operation failed + */ + int NewContour( bool aPlatedHole = false ); + + /** + * 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 + * @param aPlatedHole is true if this is a plated hole + * + * @return bool: true if the new contour was successfully created + */ + bool AddCircle( double aXpos, double aYpos, double aRadius, + bool aHoleFlag = false, bool aPlatedHole = 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 + * @param aPlatedHole is true if this is a plated slot + * + * @return bool: true if the slot was successfully created + */ + bool AddSlot( double aCenterX, double aCenterY, double aSlotLength, double aSlotWidth, + double aAngle, bool aHoleFlag = false, bool aPlatedHole = 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 + * @param aPlatedHole is true if this is a plated slotted arc + * + * @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, bool aPlatedHole = 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. + * @param aHolesOnly is true if the outline contains only holes + * + * @return bool: true if the operation succeeded + */ + bool Tesselate( VRML_LAYER* holes = NULL, bool aHolesOnly = false ); + + /** + * 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 + * @param aIncludePlatedHoles is true if holes marked as plated should + * be rendered. Default is false since the user will usually + * render these holes in a different color + * + * @return bool: true if the operation succeeded + */ + bool Write3DIndices( std::ofstream& aOutFile, bool aIncludePlatedHoles = false ); + + /** + * Function AddExtraVertex + * adds an extra vertex as required by the GLU tesselator. + * + * @param aXpos is the X coordinate of the newly created point + * @param aYpos is the Y coordinate of the newly created point + * @param aPlatedHole is true if this point is part of a plated hole + * + * @return VERTEX_3D*: is the new vertex or NULL if a vertex + * could not be created. + */ + VERTEX_3D* AddExtraVertex( double aXpos, double aYpos, bool aPlatedHole ); + + /** + * 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 ); + + void SetVertexOffsets( double aXoffset, double aYoffset ); +}; + +#endif // VRML_LAYER_H |