/** * file: idf_common.cpp * * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013-2017 Cirilo Bernardo * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #include using namespace IDF3; using namespace std; std::string source; std::string message; IDF_ERROR::IDF_ERROR( const char* aSourceFile, const char* aSourceMethod, int aSourceLine, const std::string& aMessage ) noexcept { 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() noexcept { } const char* IDF_ERROR::what() const noexcept { 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::istream& 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::ostream& 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; } void IDF_NOTE::SetPosition( double aXpos, double aYpos ) { xpos = aXpos; ypos = aYpos; } void IDF_NOTE::SetSize( double aHeight, double aLength ) { height = aHeight; length = aLength; } const std::string& IDF_NOTE::GetText() { return text; } void IDF_NOTE::GetPosition( double& aXpos, double& aYpos ) { aXpos = xpos; aYpos = ypos; } void IDF_NOTE::GetSize( double& aHeight, double& aLength ) { aHeight = height; aLength = length; } /* * 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 ) { } 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::istream& 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::ostream& 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"; } } // 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 ); } #endif bool IDF_POINT::Matches( const IDF_POINT& aPoint, double aRadius ) { double dx = x - aPoint.x; double dy = y - aPoint.y; double d2 = dx * dx + dy * dy; if( d2 <= aRadius * aRadius ) return true; return false; } double IDF_POINT::CalcDistance( const IDF_POINT& aPoint ) const { double dx = aPoint.x - x; double dy = aPoint.y - y; double dist = sqrt( dx * dx + dy * dy ); return dist; } double IDF3::CalcAngleRad( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ) { return atan2( aEndPoint.y - aStartPoint.y, aEndPoint.x - aStartPoint.x ); } double IDF3::CalcAngleDeg( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ) { double ang = CalcAngleRad( aStartPoint, aEndPoint ); // round to thousandths of a degree int iang = int (ang / M_PI * 1800000.0); ang = iang / 10000.0; return ang; } void IDF3::GetOutline( std::list& aLines, IDF_OUTLINE& aOutline ) { aOutline.Clear(); // NOTE: To tell if the point order is CCW or CW, // sum all: (endPoint.X[n] - startPoint.X[n])*(endPoint[n] + startPoint.Y[n]) // If the result is >0, the direction is CW, otherwise // it is CCW. Note that the result cannot be 0 unless // we have a bounded area of 0. // First we find the segment with the leftmost point std::list::iterator bl = aLines.begin(); std::list::iterator el = aLines.end(); std::list::iterator idx = bl++; // iterator for the object with minX double minx = (*idx)->GetMinX(); double curx; while( bl != el ) { curx = (*bl)->GetMinX(); if( curx < minx ) { minx = curx; idx = bl; } ++bl; } aOutline.push( *idx ); #ifdef DEBUG_IDF PrintSeg( *idx ); #endif aLines.erase( idx ); // If the item is a circle then we're done if( aOutline.front()->IsCircle() ) return; // Assemble the loop bool complete = false; // set if loop is complete bool matched; // set if a segment's end point was matched while( !complete ) { matched = false; bl = aLines.begin(); el = aLines.end(); while( bl != el && !matched ) { if( (*bl)->MatchesStart( aOutline.back()->endPoint ) ) { if( (*bl)->IsCircle() ) { // a circle on the perimeter is pathological but we just ignore it ++bl; } else { matched = true; #ifdef DEBUG_IDF PrintSeg( *bl ); #endif aOutline.push( *bl ); 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 ) : startPoint( aStartPoint ), endPoint( aEndPoint ), angle( 0.0 ), offsetAngle( 0.0), radius( 0.0 ) { } IDF_SEGMENT::IDF_SEGMENT( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint, double aAngle, bool aFromKicad ) : angle( 0.0 ), offsetAngle( 0.0 ), radius( 0.0 ) { 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() { // 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; // this should only ever happen due to rounding errors when r == d if( dh2 < 0 ) 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() { double diff = abs( angle ) - 360.0; if( ( diff < MIN_ANG ) && ( diff > -MIN_ANG ) ) return true; return false; } double IDF_SEGMENT::GetMinX() { if( angle == 0.0 ) return std::min( startPoint.x, endPoint.x ); // Calculate the leftmost point of the circle or arc // if only everything were this easy if( IsCircle() ) 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() { 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() { // 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() { 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; }