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 (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;
|
|
}
|