kicad/utils/idftools/idf_common.cpp

1366 lines
35 KiB
C++

/*
* file: idf_common.cpp
*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2017 Cirilo Bernardo
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 <algorithm>
#include <list>
#include <string>
#include <iostream>
#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 ) 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();
}
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() :
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;
}
bool IDF_DRILL_DATA::Matches( double aDrillDia, double aPosX, double aPosY ) const
{
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";
}
}
double IDF_DRILL_DATA::GetDrillDia() const
{
return dia;
}
double IDF_DRILL_DATA::GetDrillXPos() const
{
return x;
}
double IDF_DRILL_DATA::GetDrillYPos() const
{
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 ) const
{
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 ) :
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;
}
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;
}