0b2d4d4879
Recommendation is to avoid using the year nomenclature as this information is already encoded in the git repo. Avoids needing to repeatly update. Also updates AUTHORS.txt from current repo with contributor names
1366 lines
35 KiB
C++
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 The 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;
|
|
}
|