diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 04489b32f7..d8f00c6739 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,2 +1,5 @@ add_subdirectory( idftools ) +if( USE_OCE ) + add_subdirectory( kicad2step ) +endif( USE_OCE ) diff --git a/utils/kicad2step/CMakeLists.txt b/utils/kicad2step/CMakeLists.txt new file mode 100644 index 0000000000..69ddac4c47 --- /dev/null +++ b/utils/kicad2step/CMakeLists.txt @@ -0,0 +1,25 @@ +include_directories( + pcb + ${CMAKE_CURRENT_SOURCE_DIR} + ${OCE_INCLUDE_DIRS} +) + +add_executable( kicad2step + kicad2step.cpp + pcb/3d_resolver.cpp + pcb/base.cpp + pcb/kicadmodel.cpp + pcb/kicadmodule.cpp + pcb/kicadpad.cpp + pcb/kicadpcb.cpp + pcb/kicadcurve.cpp + pcb/oce_utils.cpp + sexpr/sexpr.cpp + sexpr/sexpr_parser.cpp +) + +target_link_libraries( kicad2step ${wxWidgets_LIBRARIES} ${LIBS_OCE} ) + +install( TARGETS kicad2step + DESTINATION bin + COMPONENT binary ) diff --git a/utils/kicad2step/kicad2step.cpp b/utils/kicad2step/kicad2step.cpp new file mode 100644 index 0000000000..5ab5160d7f --- /dev/null +++ b/utils/kicad2step/kicad2step.cpp @@ -0,0 +1,178 @@ +/* + * This program source code file is part of kicad2mcad + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kicadpcb.h" + +class KICAD2MCAD : public wxAppConsole +{ +public: + virtual bool OnInit(); + virtual int OnRun(); + virtual void OnInitCmdLine(wxCmdLineParser& parser); + virtual bool OnCmdLineParsed(wxCmdLineParser& parser); + +private: +#ifdef SUPPORTS_IGES + bool m_fmtIGES; +#endif + bool m_overwrite; + wxString m_filename; + double m_xOrigin; + double m_yOrigin; +}; + +static const wxCmdLineEntryDesc cmdLineDesc[] = + { + { wxCMD_LINE_OPTION, "f", NULL, "input file name", + wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY }, +#ifdef SUPPORTS_IGES + { wxCMD_LINE_SWITCH, "i", NULL, "IGES output (default STEP)", + wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL }, +#endif + { wxCMD_LINE_SWITCH, "w", NULL, "overwrite output file", + wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_OPTION, "x", NULL, "X origin of board", + wxCMD_LINE_VAL_DOUBLE, wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_OPTION, "y", NULL, "Y origin of board (pcbnew coordinate system)", + wxCMD_LINE_VAL_DOUBLE, wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_SWITCH, "h", NULL, "display this message", + wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP }, + { wxCMD_LINE_NONE } + }; + + +wxIMPLEMENT_APP_CONSOLE( KICAD2MCAD ); + + +bool KICAD2MCAD::OnInit() +{ +#ifdef SUPPORTS_IGES + m_fmtIGES = false; +#endif + m_overwrite = false; + m_xOrigin = 0.0; + m_yOrigin = 0.0; + + if( !wxAppConsole::OnInit() ) + return false; + + return true; +} + + +void KICAD2MCAD::OnInitCmdLine( wxCmdLineParser& parser ) +{ + parser.SetDesc( cmdLineDesc ); + parser.SetSwitchChars( "-" ); + return; +} + + +bool KICAD2MCAD::OnCmdLineParsed( wxCmdLineParser& parser ) +{ + #ifdef SUPPORTS_IGES + if( parser.Found( "i" ) ) + m_fmtIGES = true; + #endif + + if( parser.Found( "w" ) ) + m_overwrite = true; + + parser.Found( "x", &m_xOrigin ); + parser.Found( "y", &m_yOrigin ); + + wxString fname; + parser.Found( "f", &fname ); + m_filename = fname; + + return true; +} + + +int KICAD2MCAD::OnRun() +{ + wxFileName fname( m_filename ); + + if( !fname.FileExists() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * no such file: '" << m_filename.ToUTF8() << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return -1; + } + +#ifdef SUPPORTS_IGES + if( m_fmtIGES ) + fname.SetExt( "igs" ); + else +#endif + fname.SetExt( "stp" ); + + wxString outfile = fname.GetFullPath(); + + KICADPCB pcb; + pcb.SetOrigin( m_xOrigin, m_yOrigin ); + + if( pcb.ReadFile( m_filename ) ) + { + bool res; + + try + { + pcb.ComposePCB(); + + #ifdef SUPPORTS_IGES + if( m_fmtIGES ) + res = pcb.WriteIGES( outfile, m_overwrite ); + else + #endif + res = pcb.WriteSTEP( outfile, m_overwrite ); + + if( !res ) + return -1; + } + catch( Standard_Failure e ) + { + e.Print( std::cerr ); + return -1; + } + catch( ... ) + { + std::cerr << "** (no exception information)\n"; + return -1; + } + } + + return 0; +} diff --git a/utils/kicad2step/pcb/3d_resolver.cpp b/utils/kicad2step/pcb/3d_resolver.cpp new file mode 100644 index 0000000000..c20bf52754 --- /dev/null +++ b/utils/kicad2step/pcb/3d_resolver.cpp @@ -0,0 +1,1167 @@ +/* + * This program source code file is part kicad2mcad + * + * Copyright (C) 2015-2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "kicadpcb.h" + +#include "3d_resolver.h" + +// configuration file version +#define CFGFILE_VERSION 1 +#define S3D_RESOLVER_CONFIG wxT( "3Dresolver.cfg" ) + +// flag bits used to track different one-off messages to users +#define ERRFLG_ALIAS (1) +#define ERRFLG_RELPATH (2) +#define ERRFLG_ENVPATH (4) + +#define MASK_3D_RESOLVER "3D_RESOLVER" + +static wxCriticalSection lock3D_resolver; + +static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult ); + +S3D_RESOLVER::S3D_RESOLVER() +{ + m_errflags = 0; +} + + +bool S3D_RESOLVER::Set3DConfigDir( const wxString& aConfigDir ) +{ + if( aConfigDir.empty() ) + return false; + + wxFileName cfgdir( aConfigDir, "" ); + cfgdir.Normalize(); + + if( false == cfgdir.DirExists() ) + return false; + + wxFileName kcom( cfgdir.GetPath(), "" ); + kcom.RemoveLastDir(); + kcom.SetFullName( "kicad_common" ); + wxUniChar psep = wxFileName::GetPathSeparator(); + + if( kcom.FileExists() ) + { + wxConfigBase* cfg = new wxFileConfig( "", "", kcom.GetFullPath() ); + wxString subgroup( "EnvironmentVariables" ); + wxString entry; + wxString val; + long idx = 0; + + if( cfg->HasGroup( subgroup ) ) + { + cfg->SetPath( subgroup ); + + while( cfg->GetNextEntry( entry, idx ) ) + { + val = cfg->Read( entry, "" ); + + if( val.Last() == psep ) + val = val.substr( 0, val.length() -1 ); + + // only add the EnvVar if it is not currently defined by the shell + wxString subst = "${"; + subst.append( entry ); + subst.append( "}" ); + wxString exvar = wxExpandEnvVars( subst ); + + if( exvar == subst ) + m_EnvVars.insert(std::pair< wxString, wxString >(entry, val)); + + entry.clear(); + val.clear(); + } + } + + delete cfg; + } + + m_ConfigDir = cfgdir.GetPath(); + createPathList(); + + return true; +} + + +bool S3D_RESOLVER::SetProjectDir( const wxString& aProjDir, bool* flgChanged ) +{ + if( aProjDir.empty() ) + return false; + + wxFileName projdir( aProjDir, wxT( "" ) ); + projdir.Normalize(); + + if( false == projdir.DirExists() ) + return false; + + m_curProjDir = projdir.GetPath(); + wxSetEnv( "KIPRJMOD", m_curProjDir ); + + if( flgChanged ) + *flgChanged = false; + + if( m_Paths.empty() ) + { + S3D_ALIAS al; + al.m_alias = "${KIPRJMOD}"; + al.m_pathvar = "${KIPRJMOD}"; + al.m_pathexp = m_curProjDir; + m_Paths.push_back( al ); + m_NameMap.clear(); + + if( flgChanged ) + *flgChanged = true; + + } + else + { + if( m_Paths.front().m_pathexp.Cmp( m_curProjDir ) ) + { + m_Paths.front().m_pathexp = m_curProjDir; + m_NameMap.clear(); + + if( flgChanged ) + *flgChanged = true; + + } + else + { + return true; + } + } + +#ifdef DEBUG + do { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * [INFO] changed project dir to "; + ostr << m_Paths.front().m_pathexp.ToUTF8(); + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + } while( 0 ); +#endif + + return true; +} + + +wxString S3D_RESOLVER::GetProjectDir( void ) +{ + return m_curProjDir; +} + + +bool S3D_RESOLVER::createPathList( void ) +{ + if( !m_Paths.empty() ) + return true; + + wxString kmod; + + // add an entry for the default search path; at this point + // we cannot set a sensible default so we use an empty string. + // the user may change this later with a call to SetProjectDir() + + S3D_ALIAS lpath; + lpath.m_alias = "${KIPRJMOD}"; + lpath.m_pathvar = "${KIPRJMOD}"; + lpath.m_pathexp = m_curProjDir; + m_Paths.push_back( lpath ); + wxFileName fndummy; + wxUniChar psep = fndummy.GetPathSeparator(); + bool hasKISYS3DMOD = false; + + // iterate over the list of internally defined ENV VARs + // and add existing paths to the resolver + std::map< wxString, wxString >::const_iterator mS = m_EnvVars.begin(); + std::map< wxString, wxString >::const_iterator mE = m_EnvVars.end(); + + while( mS != mE ) + { + // filter out URLs, template directories, and known system paths + if( mS->first == wxString( "KICAD_PTEMPLATES" ) + || mS->first == wxString( "KIGITHUB" ) + || mS->first == wxString( "KISYSMOD" ) ) + { + ++mS; + continue; + } + + if( wxString::npos != mS->second.find( wxString( "://" ) ) ) + { + ++mS; + continue; + } + + fndummy.Assign( mS->second, "" ); + wxString pathVal; + + // ensure system ENV VARs supercede internally defined vars + if( wxGetEnv( mS->first, &pathVal ) && wxDirExists( pathVal ) ) + fndummy.Assign( pathVal, "" ); + else + fndummy.Assign( mS->second, "" ); + + fndummy.Normalize(); + + if( !fndummy.DirExists() ) + { + ++mS; + continue; + } + + wxString tmp( "${" ); + tmp.Append( mS->first ); + tmp.Append( "}" ); + + if( tmp == "${KISYS3DMOD}" ) + hasKISYS3DMOD = true; + + lpath.m_alias = tmp; + lpath.m_pathvar = tmp; + lpath.m_pathexp = fndummy.GetFullPath(); + + if( !lpath.m_pathexp.empty() && psep == *lpath.m_pathexp.rbegin() ) + lpath.m_pathexp.erase( --lpath.m_pathexp.end() ); + + m_Paths.push_back( lpath ); + + ++mS; + } + + // special case: if KISYSMOD is not internally defined but is defined by + // the system, then create an entry here + wxString envar; + + if( !hasKISYS3DMOD && wxGetEnv( "KISYS3DMOD", &envar ) ) + { + lpath.m_alias = "${KISYS3DMOD}"; + lpath.m_pathvar = "${KISYS3DMOD}"; + fndummy.Assign( envar, "" ); + fndummy.Normalize(); + + if( fndummy.DirExists() ) + { + lpath.m_pathexp = fndummy.GetFullPath(); + + if( !lpath.m_pathexp.empty() && psep == *lpath.m_pathexp.rbegin() ) + lpath.m_pathexp.erase( --lpath.m_pathexp.end() ); + + if( !lpath.m_pathexp.empty() ) + m_Paths.push_back( lpath ); + } + + } + + if( !m_ConfigDir.empty() ) + readPathList(); + + if( m_Paths.empty() ) + return false; + +#ifdef DEBUG + wxLogTrace( MASK_3D_RESOLVER, " * [3D model] search paths:\n" ); + std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin(); + std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end(); + + while( sPL != ePL ) + { + wxLogTrace( MASK_3D_RESOLVER, " + '%s'\n", (*sPL).m_pathexp.ToUTF8() ); + ++sPL; + } +#endif + + return true; +} + + +bool S3D_RESOLVER::UpdatePathList( std::vector< S3D_ALIAS >& aPathList ) +{ + wxUniChar envMarker( '$' ); + + while( !m_Paths.empty() && envMarker != *m_Paths.back().m_alias.rbegin() ) + m_Paths.pop_back(); + + size_t nI = aPathList.size(); + + for( size_t i = 0; i < nI; ++i ) + addPath( aPathList[i] ); + + return writePathList(); +} + + +wxString S3D_RESOLVER::ResolvePath( const wxString& aFileName ) +{ + wxCriticalSectionLocker lock( lock3D_resolver ); + + if( aFileName.empty() ) + return wxEmptyString; + + if( m_Paths.empty() ) + createPathList(); + + // look up the filename in the internal filename map + std::map< wxString, wxString, S3D::rsort_wxString >::iterator mi; + mi = m_NameMap.find( aFileName ); + + if( mi != m_NameMap.end() ) + return mi->second; + + // first attempt to use the name as specified: + wxString tname = aFileName; + + #ifdef _WIN32 + // translate from KiCad's internal UNIX-like path to MSWin paths + tname.Replace( wxT( "/" ), wxT( "\\" ) ); + #endif + + // Note: variable expansion must preferably be performed via a + // threadsafe wrapper for the getenv() system call. If we allow the + // wxFileName::Normalize() routine to perform expansion then + // we will have a race condition since wxWidgets does not assure + // a threadsafe wrapper for getenv(). + if( tname.StartsWith( wxT( "${" ) ) || tname.StartsWith( wxT( "$(" ) ) ) + tname = expandVars( tname ); + + wxFileName tmpFN( tname ); + + // in the case of absolute filenames we don't store a map item + if( !aFileName.StartsWith( "${" ) && !aFileName.StartsWith( "$(" ) + && !aFileName.StartsWith( ":" ) && tmpFN.IsAbsolute() ) + { + tmpFN.Normalize(); + + if( tmpFN.FileExists() ) + return tmpFN.GetFullPath(); + + return wxEmptyString; + } + + // this case covers full paths, leading expanded vars, and paths + // relative to the current working directory (which is not necessarily + // the current project directory) + if( tmpFN.FileExists() ) + { + tmpFN.Normalize(); + tname = tmpFN.GetFullPath(); + m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) ); + + // special case: if a path begins with ${ENV_VAR} but is not in the + // resolver's path list then add it + if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) ) + checkEnvVarPath( aFileName ); + + return tname; + } + + // if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the + // file either does not exist or the ENV_VAR is not defined + if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) ) + { + if( !( m_errflags & ERRFLG_ENVPATH ) ) + { + m_errflags |= ERRFLG_ENVPATH; + wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined"; + errmsg.append( "\n" ); + errmsg.append( tname ); + wxLogMessage( "%s\n", errmsg.ToUTF8() ); + } + + return wxEmptyString; + } + + // at this point aFileName is: + // a. an aliased shortened name or + // b. cannot be determined + + std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin(); + std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end(); + + // check the path relative to the current project directory; + // note: this is not necessarily the same as the current working + // directory, which has already been checked. This case accounts + // for partial paths which do not contain ${KIPRJMOD}. + // This check is performed before checking the path relative to + // ${KISYS3DMOD} so that users can potentially override a model + // within ${KISYS3DMOD} + if( !sPL->m_pathexp.empty() && !tname.StartsWith( ":" ) ) + { + tmpFN.Assign( sPL->m_pathexp, "" ); + wxString fullPath = tmpFN.GetPathWithSep() + tname; + + if( fullPath.StartsWith( "${" ) || fullPath.StartsWith( "$(" ) ) + fullPath = expandVars( fullPath ); + + if( wxFileName::FileExists( fullPath ) ) + { + tmpFN.Assign( fullPath ); + tmpFN.Normalize(); + tname = tmpFN.GetFullPath(); + m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) ); + + return tname; + } + + } + + // check the partial path relative to ${KISYS3DMOD} (legacy behavior) + if( !tname.StartsWith( ":" ) ) + { + wxFileName fpath; + wxString fullPath( "${KISYS3DMOD}" ); + fullPath.Append( fpath.GetPathSeparator() ); + fullPath.Append( tname ); + fullPath = expandVars( fullPath ); + fpath.Assign( fullPath ); + + if( fpath.Normalize() && fpath.FileExists() ) + { + tname = fpath.GetFullPath(); + m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) ); + return tname; + } + + } + + // ${ENV_VAR} paths have already been checked; skip them + while( sPL != ePL && ( sPL->m_alias.StartsWith( "${" ) + || sPL->m_alias.StartsWith( "$(" ) ) ) + ++sPL; + + // at this point the filename must contain an alias or else it is invalid + wxString alias; // the alias portion of the short filename + wxString relpath; // the path relative to the alias + + if( !SplitAlias( tname, alias, relpath ) ) + { + if( !( m_errflags & ERRFLG_RELPATH ) ) + { + // this can happen if the file was intended to be relative to + // ${KISYS3DMOD} but ${KISYS3DMOD} not set or incorrect. + m_errflags |= ERRFLG_RELPATH; + wxString errmsg = "[3D File Resolver] No such path"; + errmsg.append( "\n" ); + errmsg.append( tname ); + wxLogTrace( MASK_3D_RESOLVER, "%s\n", errmsg.ToUTF8() ); + } + + return wxEmptyString; + } + + while( sPL != ePL ) + { + if( !sPL->m_alias.Cmp( alias ) && !sPL->m_pathexp.empty() ) + { + wxFileName fpath( wxFileName::DirName( sPL->m_pathexp ) ); + wxString fullPath = fpath.GetPathWithSep() + relpath; + + if( fullPath.StartsWith( "${") || fullPath.StartsWith( "$(" ) ) + fullPath = expandVars( fullPath ); + + if( wxFileName::FileExists( fullPath ) ) + { + wxFileName tmp( fullPath ); + + if( tmp.Normalize() ) + tname = tmp.GetFullPath(); + + m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) ); + return tname; + } + } + + ++sPL; + } + + if( !( m_errflags & ERRFLG_ALIAS ) ) + { + m_errflags |= ERRFLG_ALIAS; + wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined"; + errmsg.append( "\n" ); + errmsg.append( tname.substr( 1 ) ); + wxLogTrace( MASK_3D_RESOLVER, "%s\n", errmsg.ToUTF8() ); + } + + return wxEmptyString; +} + + +bool S3D_RESOLVER::addPath( const S3D_ALIAS& aPath ) +{ + if( aPath.m_alias.empty() || aPath.m_pathvar.empty() ) + return false; + + wxCriticalSectionLocker lock( lock3D_resolver ); + + S3D_ALIAS tpath = aPath; + + #ifdef _WIN32 + while( tpath.m_pathvar.EndsWith( wxT( "\\" ) ) ) + tpath.m_pathvar.erase( tpath.m_pathvar.length() - 1 ); + #else + while( tpath.m_pathvar.EndsWith( wxT( "/" ) ) && tpath.m_pathvar.length() > 1 ) + tpath.m_pathvar.erase( tpath.m_pathvar.length() - 1 ); + #endif + + wxFileName path( tpath.m_pathvar, wxT( "" ) ); + path.Normalize(); + + if( !path.DirExists() ) + { + // suppress the message if the missing pathvar is the + // legacy KISYS3DMOD variable + if( aPath.m_pathvar.compare( wxT( "${KISYS3DMOD}" ) ) ) + { + wxString msg = _( "The given path does not exist" ); + msg.append( wxT( "\n" ) ); + msg.append( tpath.m_pathvar ); + wxLogMessage( "%s\n", msg.ToUTF8() ); + } + + tpath.m_pathexp.clear(); + } + else + { + tpath.m_pathexp = path.GetFullPath(); + + #ifdef _WIN32 + while( tpath.m_pathexp.EndsWith( wxT( "\\" ) ) ) + tpath.m_pathexp.erase( tpath.m_pathexp.length() - 1 ); + #else + while( tpath.m_pathexp.EndsWith( wxT( "/" ) ) && tpath.m_pathexp.length() > 1 ) + tpath.m_pathexp.erase( tpath.m_pathexp.length() - 1 ); + #endif + } + + wxString pname = path.GetPath(); + std::list< S3D_ALIAS >::iterator sPL = m_Paths.begin(); + std::list< S3D_ALIAS >::iterator ePL = m_Paths.end(); + + while( sPL != ePL ) + { + if( !tpath.m_alias.Cmp( sPL->m_alias ) ) + { + wxString msg = _( "Alias: " ); + msg.append( tpath.m_alias ); + msg.append( wxT( "\n" ) ); + msg.append( _( "This path: " ) ); + msg.append( tpath.m_pathvar ); + msg.append( wxT( "\n" ) ); + msg.append( _( "Existing path: " ) ); + msg.append( sPL->m_pathvar ); + wxMessageBox( msg, _( "Bad alias (duplicate name)" ) ); + + return false; + } + + ++sPL; + } + + m_Paths.push_back( tpath ); + return true; +} + + +bool S3D_RESOLVER::readPathList( void ) +{ + if( m_ConfigDir.empty() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + wxString errmsg = "3D configuration directory is unknown"; + ostr << " * " << errmsg.ToUTF8(); + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + return false; + } + + wxFileName cfgpath( m_ConfigDir, S3D_RESOLVER_CONFIG ); + cfgpath.Normalize(); + wxString cfgname = cfgpath.GetFullPath(); + + size_t nitems = m_Paths.size(); + + std::ifstream cfgFile; + std::string cfgLine; + + if( !wxFileName::Exists( cfgname ) ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + wxString errmsg = "no 3D configuration file"; + ostr << " * " << errmsg.ToUTF8() << " '"; + ostr << cfgname.ToUTF8() << "'"; + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + return false; + } + + cfgFile.open( cfgname.ToUTF8() ); + + if( !cfgFile.is_open() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + wxString errmsg = "Could not open configuration file"; + ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'"; + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + return false; + } + + int lineno = 0; + S3D_ALIAS al; + size_t idx; + int vnum = 0; // version number + + while( cfgFile.good() ) + { + cfgLine.clear(); + std::getline( cfgFile, cfgLine ); + ++lineno; + + if( cfgLine.empty() ) + { + if( cfgFile.eof() ) + break; + + continue; + } + + if( 1 == lineno && cfgLine.compare( 0, 2, "#V" ) == 0 ) + { + // extract the version number and parse accordingly + if( cfgLine.size() > 2 ) + { + std::istringstream istr; + istr.str( cfgLine.substr( 2 ) ); + istr >> vnum; + } + + continue; + } + + idx = 0; + + if( !getHollerith( cfgLine, idx, al.m_alias ) ) + continue; + + // never add on KISYS3DMOD from a config file + if( !al.m_alias.Cmp( wxT( "KISYS3DMOD" ) ) ) + continue; + + if( !getHollerith( cfgLine, idx, al.m_pathvar ) ) + continue; + + if( !getHollerith( cfgLine, idx, al.m_description ) ) + continue; + + addPath( al ); + } + + cfgFile.close(); + + if( vnum < CFGFILE_VERSION ) + writePathList(); + + if( m_Paths.size() != nitems ) + return true; + + return false; +} + + +bool S3D_RESOLVER::writePathList( void ) +{ + if( m_ConfigDir.empty() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + wxString errmsg = _( "3D configuration directory is unknown" ); + ostr << " * " << errmsg.ToUTF8(); + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + wxMessageBox( errmsg, _( "Write 3D search path list" ) ); + + return false; + } + + // skip all ${ENV_VAR} alias names + std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin(); + std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end(); + + while( sPL != ePL && ( sPL->m_alias.StartsWith( "${" ) + || sPL->m_alias.StartsWith( "$(" ) ) ) + ++sPL; + + wxFileName cfgpath( m_ConfigDir, S3D_RESOLVER_CONFIG ); + wxString cfgname = cfgpath.GetFullPath(); + std::ofstream cfgFile; + + if( sPL == ePL ) + { + wxMessageDialog md( NULL, + _( "3D search path list is empty;\ncontinue to write empty file?" ), + _( "Write 3D search path list" ), wxYES_NO ); + + if( md.ShowModal() == wxID_YES ) + { + cfgFile.open( cfgname.ToUTF8(), std::ios_base::trunc ); + + if( !cfgFile.is_open() ) + { + wxMessageBox( _( "Could not open configuration file" ), + _( "Write 3D search path list" ) ); + + return false; + } + + cfgFile << "#V" << CFGFILE_VERSION << "\n"; + cfgFile.close(); + return true; + } + + return false; + } + + cfgFile.open( cfgname.ToUTF8(), std::ios_base::trunc ); + + if( !cfgFile.is_open() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + wxString errmsg = _( "Could not open configuration file" ); + ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'"; + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + wxMessageBox( errmsg, _( "Write 3D search path list" ) ); + + return false; + } + + cfgFile << "#V" << CFGFILE_VERSION << "\n"; + std::string tstr; + + while( sPL != ePL ) + { + tstr = sPL->m_alias.ToUTF8(); + cfgFile << "\"" << tstr.size() << ":" << tstr << "\","; + tstr = sPL->m_pathvar.ToUTF8(); + cfgFile << "\"" << tstr.size() << ":" << tstr << "\","; + tstr = sPL->m_description.ToUTF8(); + cfgFile << "\"" << tstr.size() << ":" << tstr << "\"\n"; + ++sPL; + } + + bool bad = cfgFile.bad(); + cfgFile.close(); + + if( bad ) + { + wxMessageBox( _( "Problems writing configuration file" ), + _( "Write 3D search path list" ) ); + + return false; + } + + return true; +} + + +void S3D_RESOLVER::checkEnvVarPath( const wxString& aPath ) +{ + bool useParen = false; + + if( aPath.StartsWith( "$(" ) ) + useParen = true; + else if( !aPath.StartsWith( "${" ) ) + return; + + size_t pEnd; + + if( useParen ) + pEnd = aPath.find( ")" ); + else + pEnd = aPath.find( "}" ); + + if( pEnd == wxString::npos ) + return; + + wxString envar = aPath.substr( 0, pEnd + 1 ); + + // check if the alias exists; if not then add it to the end of the + // env var section of the path list + std::list< S3D_ALIAS >::iterator sPL = m_Paths.begin(); + std::list< S3D_ALIAS >::iterator ePL = m_Paths.end(); + + while( sPL != ePL ) + { + if( sPL->m_alias == envar ) + return; + + if( !sPL->m_alias.StartsWith( "${" ) ) + break; + + ++sPL; + } + + S3D_ALIAS lpath; + lpath.m_alias = envar; + lpath.m_pathvar = lpath.m_alias; + wxFileName tmpFN( lpath.m_alias, "" ); + wxUniChar psep = tmpFN.GetPathSeparator(); + tmpFN.Normalize(); + + if( !tmpFN.DirExists() ) + return; + + lpath.m_pathexp = tmpFN.GetFullPath(); + + if( !lpath.m_pathexp.empty() && psep == *lpath.m_pathexp.rbegin() ) + lpath.m_pathexp.erase( --lpath.m_pathexp.end() ); + + if( lpath.m_pathexp.empty() ) + return; + + m_Paths.insert( sPL, lpath ); + return; +} + + +wxString S3D_RESOLVER::expandVars( const wxString& aPath ) +{ + if( aPath.empty() ) + return wxEmptyString; + + wxString result; + + for( auto i: m_EnvVars ) + { + if( !aPath.compare( 2, i.first.length(), i.first ) ) + { + result = i.second; + result.append( aPath.substr( 3 + i.first.length() ) ); + + if( result.StartsWith( "${" ) || result.StartsWith( "$(" ) ) + result = expandVars( result ); + + return result; + } + } + + result = wxExpandEnvVars( aPath ); + + if( result == aPath ) + return wxEmptyString; + + if( result.StartsWith( "${" ) || result.StartsWith( "$(" ) ) + result = expandVars( result ); + + return result; +} + + +wxString S3D_RESOLVER::ShortenPath( const wxString& aFullPathName ) +{ + wxString fname = aFullPathName; + + if( m_Paths.empty() ) + createPathList(); + + wxCriticalSectionLocker lock( lock3D_resolver ); + std::list< S3D_ALIAS >::const_iterator sL = m_Paths.begin(); + std::list< S3D_ALIAS >::const_iterator eL = m_Paths.end(); + size_t idx; + + while( sL != eL ) + { + // undefined paths do not participate in the + // file name shortening procedure + if( sL->m_pathexp.empty() ) + { + ++sL; + continue; + } + + wxFileName fpath( sL->m_pathexp, wxT( "" ) ); + wxString fps = fpath.GetPathWithSep(); + wxString tname; + + idx = fname.find( fps ); + + if( std::string::npos != idx && 0 == idx ) + { + fname = fname.substr( fps.size() ); + + #ifdef _WIN32 + // ensure only the '/' separator is used in the internal name + fname.Replace( wxT( "\\" ), wxT( "/" ) ); + #endif + + if( sL->m_alias.StartsWith( "${" ) || sL->m_alias.StartsWith( "$(" ) ) + { + // old style ENV_VAR + tname = sL->m_alias; + tname.Append( "/" ); + tname.append( fname ); + } + else + { + // new style alias + tname = ":"; + tname.append( sL->m_alias ); + tname.append( ":" ); + tname.append( fname ); + } + + return tname; + } + + ++sL; + } + +#ifdef _WIN32 + // it is strange to convert an MSWin full path to use the + // UNIX separator but this is done for consistency and can + // be helpful even when transferring project files from + // MSWin to *NIX. + fname.Replace( wxT( "\\" ), wxT( "/" ) ); +#endif + + return fname; +} + + + +const std::list< S3D_ALIAS >* S3D_RESOLVER::GetPaths( void ) +{ + return &m_Paths; +} + + +bool S3D_RESOLVER::SplitAlias( const wxString& aFileName, + wxString& anAlias, wxString& aRelPath ) +{ + anAlias.clear(); + aRelPath.clear(); + + if( !aFileName.StartsWith( wxT( ":" ) ) ) + return false; + + size_t tagpos = aFileName.find( wxT( ":" ), 1 ); + + if( wxString::npos == tagpos || 1 == tagpos ) + return false; + + if( tagpos + 1 >= aFileName.length() ) + return false; + + anAlias = aFileName.substr( 1, tagpos - 1 ); + aRelPath = aFileName.substr( tagpos + 1 ); + + return true; +} + + +static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult ) +{ + aResult.clear(); + + if( aIndex >= aString.size() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + wxString errmsg = "bad Hollerith string on line"; + ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'"; + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + + return false; + } + + size_t i2 = aString.find( '"', aIndex ); + + if( std::string::npos == i2 ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + wxString errmsg = "missing opening quote mark in config file"; + ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'"; + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + + return false; + } + + ++i2; + + if( i2 >= aString.size() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + wxString errmsg = "invalid entry (unexpected end of line)"; + ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'"; + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + + return false; + } + + std::string tnum; + + while( aString[i2] >= '0' && aString[i2] <= '9' ) + tnum.append( 1, aString[i2++] ); + + if( tnum.empty() || aString[i2++] != ':' ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + wxString errmsg = "bad Hollerith string on line"; + ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'"; + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + + return false; + } + + std::istringstream istr; + istr.str( tnum ); + size_t nchars; + istr >> nchars; + + if( (i2 + nchars) >= aString.size() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + wxString errmsg = "invalid entry (unexpected end of line)"; + ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'"; + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + + return false; + } + + if( nchars > 0 ) + { + aResult = aString.substr( i2, nchars ); + i2 += nchars; + } + + if( aString[i2] != '"' ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + wxString errmsg = "missing closing quote mark in config file"; + ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'"; + wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() ); + + return false; + } + + aIndex = i2 + 1; + return true; +} + + +bool S3D_RESOLVER::ValidateFileName( const wxString& aFileName, bool& hasAlias ) +{ + // Rules: + // 1. The generic form of an aliased 3D relative path is: + // ALIAS:relative/path + // 2. ALIAS is a UTF string excluding wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) + // 3. The relative path must be a valid relative path for the platform + hasAlias = false; + + if( aFileName.empty() ) + return false; + + wxString filename = aFileName; + wxString lpath; + size_t pos0 = aFileName.find( ':' ); + + // ensure that the file separators suit the current platform + #ifdef __WINDOWS__ + filename.Replace( wxT( "/" ), wxT( "\\" ) ); + + // if we see the :\ pattern then it must be a drive designator + if( pos0 != wxString::npos ) + { + size_t pos1 = aFileName.find( wxT( ":\\" ) ); + + if( pos1 != wxString::npos && ( pos1 != pos0 || pos1 != 1 ) ) + return false; + + // if we have a drive designator then we have no alias + if( pos1 != wxString::npos ) + pos0 = wxString::npos; + } + #else + filename.Replace( wxT( "\\" ), wxT( "/" ) ); + #endif + + // names may not end with ':' + if( pos0 == aFileName.length() -1 ) + return false; + + if( pos0 != wxString::npos ) + { + // ensure the alias component is not empty + if( pos0 == 0 ) + return false; + + lpath = filename.substr( 0, pos0 ); + + // check the alias for restricted characters + if( wxString::npos != lpath.find_first_of( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) ) ) + return false; + + hasAlias = true; + lpath = aFileName.substr( pos0 + 1 ); + } + else + { + lpath = aFileName; + + // in the case of ${ENV_VAR}|$(ENV_VAR)/path, strip the + // environment string before testing + pos0 = wxString::npos; + + if( aFileName.StartsWith( "${" ) ) + pos0 = aFileName.find( '}' ); + else if( aFileName.StartsWith( "$(" ) ) + pos0 = aFileName.find( ')' ); + + if( pos0 != wxString::npos ) + lpath = aFileName.substr( pos0 + 1 ); + + } + + if( wxString::npos != lpath.find_first_of( wxFileName::GetForbiddenChars() ) ) + return false; + + return true; +} diff --git a/utils/kicad2step/pcb/3d_resolver.h b/utils/kicad2step/pcb/3d_resolver.h new file mode 100644 index 0000000000..8959396e8e --- /dev/null +++ b/utils/kicad2step/pcb/3d_resolver.h @@ -0,0 +1,242 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2015-2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file 3d_resolver.h + * provides an extensible class to resolve 3D model paths. + * Derived from 3d_filename_resolver.h,cpp and modified for + * use in stand-alone utilities. + */ + +#ifndef RESOLVER_3D_H +#define RESOLVER_3D_H + +#include +#include +#include +#include + +namespace S3D +{ + + struct rsort_wxString + { + bool operator() (const wxString& strA, const wxString& strB ) const + { + // sort a wxString using the reverse character order; for 3d model + // filenames this will typically be a much faster operation than + // a normal alphabetic sort + wxString::const_reverse_iterator sA = strA.rbegin(); + wxString::const_reverse_iterator eA = strA.rend(); + + wxString::const_reverse_iterator sB = strB.rbegin(); + wxString::const_reverse_iterator eB = strB.rend(); + + if( strA.empty() ) + { + if( strB.empty() ) + return false; + + // note: this rule implies that a null string is first in the sort order + return true; + } + + if( strB.empty() ) + return false; + + while( sA != eA && sB != eB ) + { + if( (*sA) == (*sB) ) + { + ++sA; + ++sB; + continue; + } + + if( (*sA) < (*sB) ) + return true; + else + return false; + } + + if( sB == eB ) + return false; + + return true; + } + }; + +}; // end NAMESPACE + + +class KICADPCB; + +struct S3D_ALIAS +{ + wxString m_alias; // alias to the base path + wxString m_pathvar; // base path as stored in the config file + wxString m_pathexp; // expanded base path + wxString m_description; // description of the aliased path +}; + +class S3D_RESOLVER +{ +private: + wxString m_ConfigDir; // 3D configuration directory + std::list< S3D_ALIAS > m_Paths; // list of base paths to search from + // mapping of (short) file names to resolved names + std::map< wxString, wxString, S3D::rsort_wxString > m_NameMap; + int m_errflags; + wxString m_curProjDir; + // environment variables + std::map< wxString, wxString > m_EnvVars; + + /** + * Function createPathList + * builds the path list using available information such as + * KISYS3DMOD and the 3d_path_list configuration file. Invalid + * paths are silently discarded and removed from the configuration + * file. + * + * @return true if at least one valid path was found + */ + bool createPathList( void ); + + /** + * Function addPath + * checks that a path is valid and adds it to the search list + * + * @param aPath is the alias set to be checked and added + * @return true if aPath is valid + */ + bool addPath( const S3D_ALIAS& aPath ); + + /** + * Function readPathList + * reads a list of path names from a configuration file + * + * @return true if a file was found and contained at least + * one valid path + */ + bool readPathList( void ); + + /** + * Function writePathList + * writes the current path list to a configuration file + * + * @return true if the path list was not empty and was + * successfully written to the configuration file + */ + bool writePathList( void ); + + /** + * Function checkEnvVarPath + * checks the ${ENV_VAR} component of a path and adds + * it to the resolver's path list if it is not yet in + * the list + */ + void checkEnvVarPath( const wxString& aPath ); + + wxString expandVars( const wxString& aPath ); + +public: + S3D_RESOLVER(); + + /** + * Function Set3DConfigDir + * sets the user's configuration directory + * for 3D models. + * + * @param aConfigDir + * @return true if the call succeeds (directory exists) + */ + bool Set3DConfigDir( const wxString& aConfigDir ); + + /** + * Function SetProjectDir + * sets the current KiCad project directory as the first + * entry in the model path list + * + * @param aProjDir is the current project directory + * @param flgChanged, if specified, is set to true if the directory actually changed + * @return true if the call succeeds + */ + bool SetProjectDir( const wxString& aProjDir, bool* flgChanged = NULL ); + wxString GetProjectDir( void ); + + /** + * Function UpdatePathList + * clears the current path list and substitutes the given path + * list, updating the path configuration file on success. + */ + bool UpdatePathList( std::vector< S3D_ALIAS >& aPathList ); + + /** + * Function ResolvePath + * determines the full path of the given file name. In the future + * remote files may be supported, in which case it is best to + * require a full URI in which case ResolvePath should check that + * the URI conforms to RFC-2396 and related documents and copies + * aFileName into aResolvedName if the URI is valid. + */ + wxString ResolvePath( const wxString& aFileName ); + + /** + * Function ShortenPath + * produces a relative path based on the existing + * search directories or returns the same path if + * the path is not a superset of an existing search path. + * + * @param aFullPathName is an absolute path to shorten + * @return the shortened path or aFullPathName + */ + wxString ShortenPath( const wxString& aFullPathName ); + + /** + * Function GetPaths + * returns a pointer to the internal path list; the items in:load + * + * the list can be used to set up the list of search paths + * available to a 3D file browser. + * + * @return pointer to the internal path list + */ + const std::list< S3D_ALIAS >* GetPaths( void ); + + /** + * Function SplitAlias + * returns true if the given name contains an alias and + * populates the string anAlias with the alias and aRelPath + * with the relative path. + */ + bool SplitAlias( const wxString& aFileName, wxString& anAlias, wxString& aRelPath ); + + /** + * Function ValidateName + * returns true if the given path is a valid aliased relative path. + * If the path contains an alias then hasAlias is set true. + */ + bool ValidateFileName( const wxString& aFileName, bool& hasAlias ); +}; + +#endif // RESOLVER_3D_H diff --git a/utils/kicad2step/pcb/base.cpp b/utils/kicad2step/pcb/base.cpp new file mode 100644 index 0000000000..9406e7839a --- /dev/null +++ b/utils/kicad2step/pcb/base.cpp @@ -0,0 +1,244 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include "sexpr/sexpr.h" +#include "base.h" + +static const char bad_position[] = "* corrupt module in PCB file; invalid position"; + + +bool Get2DPositionAndRotation( SEXPR::SEXPR* data, DOUBLET& aPosition, double& aRotation ) +{ + // form: (at X Y {rot}) + int nchild = data->GetNumberOfChildren(); + + if( nchild < 3 ) + { + std::ostringstream ostr; + ostr << bad_position; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + if( data->GetChild( 0 )->GetSymbol() != "at" ) + { + std::ostringstream ostr; + ostr << "* SEXPR item is not a position string"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + SEXPR::SEXPR* child = data->GetChild( 1 ); + double x; + + if( child->IsDouble() ) + x = child->GetDouble(); + else if( child->IsInteger() ) + x = (double) child->GetInteger(); + else + { + std::ostringstream ostr; + ostr << bad_position; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + child = data->GetChild( 2 ); + double y; + + if( child->IsDouble() ) + y = child->GetDouble(); + else if( child->IsInteger() ) + y = (double) child->GetInteger(); + else + { + std::ostringstream ostr; + ostr << bad_position; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + aPosition.x = x; + aPosition.y = y; + + if( nchild == 3 ) + return true; + + child = data->GetChild( 3 ); + double angle = 0.0; + + if( child->IsDouble() ) + angle = child->GetDouble(); + else if( child->IsInteger() ) + angle = (double) child->GetInteger(); + else + { + std::ostringstream ostr; + ostr << bad_position; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + while( angle >= 360.0 ) + angle -= 360.0; + + while( angle <= -360.0 ) + angle += 360.0; + + aRotation = (angle / 180.0) * M_PI; + + return true; +} + + +bool Get2DCoordinate( SEXPR::SEXPR* data, DOUBLET& aCoordinate ) +{ + // form: (at X Y {rot}) + int nchild = data->GetNumberOfChildren(); + + if( nchild < 3 ) + { + std::ostringstream ostr; + ostr << bad_position; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + SEXPR::SEXPR* child = data->GetChild( 1 ); + double x; + + if( child->IsDouble() ) + x = child->GetDouble(); + else if( child->IsInteger() ) + x = (double) child->GetInteger(); + else + { + std::ostringstream ostr; + ostr << bad_position; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + child = data->GetChild( 2 ); + double y; + + if( child->IsDouble() ) + y = child->GetDouble(); + else if( child->IsInteger() ) + y = (double) child->GetInteger(); + else + { + std::ostringstream ostr; + ostr << bad_position; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + aCoordinate.x = x; + aCoordinate.y = y; + + return true; +} + + +bool Get3DCoordinate( SEXPR::SEXPR* data, TRIPLET& aCoordinate ) +{ + // form: (at X Y Z) + int nchild = data->GetNumberOfChildren(); + + if( nchild < 4 ) + { + std::ostringstream ostr; + ostr << bad_position; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + SEXPR::SEXPR* child; + double val[3]; + + for( int i = 1; i < 4; ++i ) + { + child = data->GetChild( i ); + + if( child->IsDouble() ) + val[i -1] = child->GetDouble(); + else if( child->IsInteger() ) + val[i -1] = (double) child->GetInteger(); + else + { + std::ostringstream ostr; + ostr << bad_position; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + } + + aCoordinate.x = val[0]; + aCoordinate.y = val[1]; + aCoordinate.z = val[2]; + + return true; +} + + +bool GetXYZRotation( SEXPR::SEXPR* data, TRIPLET& aRotation ) +{ + const char bad_rotation[] = "* invalid 3D rotation"; + + if( !Get3DCoordinate( data, aRotation ) ) + { + std::ostringstream ostr; + ostr << bad_rotation; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + if( aRotation.x > 360.0 || aRotation.x < -360.0 ) + { + int nr = (int) aRotation.x / 360; + aRotation.x -= ( 360.0 * nr ); + } + + if( aRotation.y > 360.0 || aRotation.y < -360.0 ) + { + int nr = (int) aRotation.y / 360; + aRotation.y -= ( 360.0 * nr ); + } + + if( aRotation.z > 360.0 || aRotation.z < -360.0 ) + { + int nr = (int) aRotation.z / 360; + aRotation.z -= ( 360.0 * nr ); + } + + aRotation.x *= M_PI / 180.0; + aRotation.y *= M_PI / 180.0; + aRotation.z *= M_PI / 180.0; + + return true; +} diff --git a/utils/kicad2step/pcb/base.h b/utils/kicad2step/pcb/base.h new file mode 100644 index 0000000000..39d9aacdad --- /dev/null +++ b/utils/kicad2step/pcb/base.h @@ -0,0 +1,89 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file base.h + * provides declarations of items which are basic to all + * kicad2mcad code. + */ + +#ifndef KICADBASE_H +#define KICADBASE_H + +namespace SEXPR +{ + class SEXPR; +} + +enum CURVE_TYPE +{ + CURVE_NONE = 0, // invalid curve + CURVE_LINE, + CURVE_ARC, + CURVE_CIRCLE +}; + +/* + * Layers of importance to MCAD export: + * LAYER_TOP: specifies that a module is on the top of the PCB + * LAYER_BOTTOM: specifies that a module is on the bottom of the PCB + * LAYER_EDGE: specifies that a Curve is associated with the PCB edge + */ +enum LAYERS +{ + LAYER_NONE = 0, // no layer specified (bad object) + LAYER_TOP, // top side + LAYER_BOTTOM, // bottom side + LAYER_EDGE // edge data +}; + +struct DOUBLET +{ + double x; + double y; + + DOUBLET() : x( 0.0 ), y( 0.0 ) { return; } + DOUBLET( double aX, double aY ) : x( aX ), y( aY ) { return; } +}; + +struct TRIPLET +{ + double x; + double y; + + union + { + double z; + double angle; + }; + + TRIPLET() : x( 0.0 ), y( 0.0 ), z( 0.0 ) { return; } + TRIPLET( double aX, double aY, double aZ ) : x( aX ), y( aY ), z( aZ ) { return; } +}; + +bool Get2DPositionAndRotation( SEXPR::SEXPR* data, DOUBLET& aPosition, double& aRotation ); +bool Get2DCoordinate( SEXPR::SEXPR* data, DOUBLET& aCoordinate ); +bool Get3DCoordinate( SEXPR::SEXPR* data, TRIPLET& aCoordinate ); +bool GetXYZRotation( SEXPR::SEXPR* data, TRIPLET& aRotation ); + +#endif // KICADBASE_H diff --git a/utils/kicad2step/pcb/kicadcurve.cpp b/utils/kicad2step/pcb/kicadcurve.cpp new file mode 100644 index 0000000000..8e4ac0cd1c --- /dev/null +++ b/utils/kicad2step/pcb/kicadcurve.cpp @@ -0,0 +1,136 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include "sexpr/sexpr.h" +#include "kicadcurve.h" + + +KICADCURVE::KICADCURVE() +{ + m_form = CURVE_NONE; + m_angle = 0.0; + m_radius = 0.0; + m_layer = LAYER_NONE; + m_startangle = 0.0; + m_endangle = 0.0; + + return; +} + + +KICADCURVE::~KICADCURVE() +{ + return; +} + + +bool KICADCURVE::Read( SEXPR::SEXPR* aEntry, CURVE_TYPE aCurveType ) +{ + if( CURVE_LINE != aCurveType && CURVE_ARC != aCurveType && CURVE_CIRCLE != aCurveType ) + { + std::ostringstream ostr; + ostr << "* Unsupported curve type: " << aCurveType; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + m_form = aCurveType; + + int nchild = aEntry->GetNumberOfChildren(); + + if( ( CURVE_CIRCLE == aCurveType && nchild < 5 ) + || ( CURVE_ARC == aCurveType && nchild < 6 ) + || ( CURVE_LINE == aCurveType && nchild < 5 ) ) + { + std::ostringstream ostr; + ostr << "* bad curve data; not enough parameters"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + SEXPR::SEXPR* child; + std::string text; + + for( int i = 1; i < nchild; ++i ) + { + child = aEntry->GetChild( i ); + + if( !child->IsList() ) + continue; + + text = child->GetChild( 0 )->GetSymbol(); + + if( text == "start" || text == "center" ) + { + if( !Get2DCoordinate( child, m_start ) ) + return false; + } + else if( text == "end" ) + { + if( !Get2DCoordinate( child, m_end ) ) + return false; + } + else if( text == "angle" ) + { + if( child->GetNumberOfChildren() < 2 + || ( !child->GetChild( 1 )->IsDouble() + && !child->GetChild( 1 )->IsInteger() ) ) + { + std::ostringstream ostr; + ostr << "* bad angle data"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + if( child->GetChild( 1 )->IsDouble() ) + m_angle = child->GetChild( 1 )->GetDouble(); + else + m_angle = child->GetChild( 1 )->GetInteger(); + + m_angle = m_angle / 180.0 * M_PI; + } + else if( text == "layer" ) + { + if( child->GetNumberOfChildren() < 2 + || !child->GetChild( 1 )->IsSymbol() ) + { + std::ostringstream ostr; + ostr << "* bad layer data"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + text = child->GetChild( 1 )->GetSymbol(); + + // NOTE: for the moment we only process Edge.Cuts + if( text == "Edge.Cuts" ) + m_layer = LAYER_EDGE; + } + } + + return true; +} diff --git a/utils/kicad2step/pcb/kicadcurve.h b/utils/kicad2step/pcb/kicadcurve.h new file mode 100644 index 0000000000..fea34a2400 --- /dev/null +++ b/utils/kicad2step/pcb/kicadcurve.h @@ -0,0 +1,61 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file kicadcurve.h + * declares the Curve (glyph) object. + */ + +#ifndef KICADCURVE_H +#define KICADCURVE_H + +#include +#include +#include "base.h" + + +class KICADCURVE +{ +public: + KICADCURVE(); + virtual ~KICADCURVE(); + + bool Read( SEXPR::SEXPR* aEntry, CURVE_TYPE aCurveType ); + + LAYERS GetLayer() + { + return m_layer; + } + + CURVE_TYPE m_form; // form of curve: line, arc, circle + LAYERS m_layer; // layer of the glyph + DOUBLET m_start; // start point of line or center for arc and circle + DOUBLET m_end; // end point of line, first point on arc or circle + DOUBLET m_ep; // actual endpoint, to be computed in the case of arcs + double m_radius;// radius; to be computed in the case of arcs and circles + double m_angle; // subtended angle of arc + double m_startangle; + double m_endangle; +}; + +#endif // KICADCURVE_H diff --git a/utils/kicad2step/pcb/kicadmodel.cpp b/utils/kicad2step/pcb/kicadmodel.cpp new file mode 100644 index 0000000000..6c4c0331d2 --- /dev/null +++ b/utils/kicad2step/pcb/kicadmodel.cpp @@ -0,0 +1,92 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include "sexpr/sexpr.h" +#include "kicadmodel.h" + + +KICADMODEL::KICADMODEL() : m_scale( 1.0, 1.0, 1.0 ) +{ + return; +} + + +KICADMODEL::~KICADMODEL() +{ + return; +} + + +bool KICADMODEL::Read( SEXPR::SEXPR* aEntry ) +{ + // form: ( pad N thru_hole shape (at x y {r}) (size x y) (drill {oval} x {y}) (layers X X X) ) + int nchild = aEntry->GetNumberOfChildren(); + + if( nchild < 2 ) + { + std::ostringstream ostr; + ostr << "* invalid model entry"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + SEXPR::SEXPR* child = aEntry->GetChild( 1 ); + + if( child->IsSymbol() ) + m_modelname = child->GetSymbol(); + else if( child->IsString() ) + m_modelname = child->GetString(); + else + { + std::ostringstream ostr; + ostr << "* invalid model entry; invalid path"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + for( int i = 2; i < nchild; ++i ) + { + child = aEntry->GetChild( i ); + + if( !child->IsList() ) + continue; + + std::string name = child->GetChild( 0 )->GetSymbol(); + bool ret = true; + + if( name == "at" ) + ret = Get3DCoordinate( child->GetChild( 1 ), m_offset ); + else if( name == "scale" ) + ret = Get3DCoordinate( child->GetChild( 1 ), m_scale ); + else if( name == "rotate" ) + ret = GetXYZRotation( child->GetChild( 1 ), m_rotation ); + + if( !ret ) + return false; + } + + return true; +} diff --git a/utils/kicad2step/pcb/kicadmodel.h b/utils/kicad2step/pcb/kicadmodel.h new file mode 100644 index 0000000000..a087c3be87 --- /dev/null +++ b/utils/kicad2step/pcb/kicadmodel.h @@ -0,0 +1,47 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file kicadmodel.h + * declares the 3D model object. + */ + +#ifndef KICADMODEL_H +#define KICADMODEL_H + +#include "base.h" + +struct KICADMODEL +{ + KICADMODEL(); + virtual ~KICADMODEL(); + + bool Read( SEXPR::SEXPR* aEntry ); + + std::string m_modelname; + TRIPLET m_scale; + TRIPLET m_offset; + TRIPLET m_rotation; +}; + +#endif // KICADMODEL_H diff --git a/utils/kicad2step/pcb/kicadmodule.cpp b/utils/kicad2step/pcb/kicadmodule.cpp new file mode 100644 index 0000000000..bfa88a476e --- /dev/null +++ b/utils/kicad2step/pcb/kicadmodule.cpp @@ -0,0 +1,338 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include + +#include "3d_resolver.h" +#include "sexpr/sexpr.h" +#include "kicadmodel.h" +#include "kicadmodule.h" +#include "kicadpad.h" +#include "kicadcurve.h" +#include "oce_utils.h" + + +KICADMODULE::KICADMODULE() +{ + m_side = LAYER_NONE; + m_rotation = 0.0; + + return; +} + + +KICADMODULE::~KICADMODULE() +{ + for( auto i : m_pads ) + delete i; + + for( auto i : m_curves ) + delete i; + + for( auto i : m_models ) + delete i; + + return; +} + + +bool KICADMODULE::Read( SEXPR::SEXPR* aEntry ) +{ + if( NULL == aEntry ) + return false; + + if( aEntry->IsList() ) + { + size_t nc = aEntry->GetNumberOfChildren(); + SEXPR::SEXPR* child = aEntry->GetChild( 0 ); + std::string name = child->GetSymbol(); + + if( name != "module" ) + { + std::ostringstream ostr; + ostr << "* BUG: module parser invoked for type '" << name << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + bool result = true; + + for( size_t i = 1; i < nc && result; ++i ) + { + child = aEntry->GetChild( i ); + + // skip the module name and the optional 'locked' attribute; + // due to the vagaries of the kicad version of sexpr, the + // name may be a Symbol or a String + if( i <= 2 && ( child->IsSymbol() || child->IsString() ) ) + continue; + + if( !child->IsList() ) + { + std::ostringstream ostr; + ostr << "* corrupt module in PCB file\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + std::string symname( child->GetChild( 0 )->GetSymbol() ); + + if( symname == "layer" ) + result = result && parseLayer( child ); + else if( symname == "at" ) + result = result && parsePosition( child ); + else if( symname == "fp_text" ) + result = result && parseText( child ); + else if( symname == "fp_arc" ) + result = result && parseCurve( child, CURVE_ARC ); + else if( symname == "fp_line" ) + result = result && parseCurve( child, CURVE_LINE ); + else if( symname == "fp_circle" ) + result = result && parseCurve( child, CURVE_CIRCLE ); + else if( symname == "pad" ) + result = result && parsePad( child ); + else if( symname == "model" ) + result = result && parseModel( child ); + } + + return result; + } + + std::ostringstream ostr; + ostr << "* data is not a valid PCB module\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; +} + + +bool KICADMODULE::parseModel( SEXPR::SEXPR* data ) +{ + KICADMODEL* mp = new KICADMODEL(); + + if( !mp->Read( data ) ) + { + delete mp; + return false; + } + + m_models.push_back( mp ); + return true; +} + + +bool KICADMODULE::parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType ) +{ + KICADCURVE* mp = new KICADCURVE(); + + if( !mp->Read( data, aCurveType ) ) + { + delete mp; + return false; + } + + // NOTE: for now we are only interested in glyphs on the outline layer + if( LAYER_EDGE != mp->GetLayer() ) + { + delete mp; + return true; + } + + m_curves.push_back( mp ); + return true; +} + + +bool KICADMODULE::parseLayer( SEXPR::SEXPR* data ) +{ + SEXPR::SEXPR* val = data->GetChild( 1 ); + std::string layer; + + if( val->IsSymbol() ) + layer = val->GetSymbol(); + else if( val->IsString() ) + layer = val->GetString(); + else + { + std::ostringstream ostr; + ostr << "* corrupt module in PCB file; layer cannot be parsed\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + if( layer == "F.Cu" ) + m_side = LAYER_TOP; + else if( layer == "B.Cu" ) + m_side = LAYER_BOTTOM; + + return true; +} + + +bool KICADMODULE::parsePosition( SEXPR::SEXPR* data ) +{ + return Get2DPositionAndRotation( data, m_position, m_rotation ); +} + + +bool KICADMODULE::parseText( SEXPR::SEXPR* data ) +{ + // we're only interested in the Reference Designator + if( data->GetNumberOfChildren() < 3 ) + return true; + + SEXPR::SEXPR* child = data->GetChild( 1 ); + std::string text; + + if( child->IsSymbol() ) + text = child->GetSymbol(); + else if( child->IsString() ) + text = child->GetString(); + + if( text != "reference" ) + return true; + + child = data->GetChild( 2 ); + + if( child->IsSymbol() ) + text = child->GetSymbol(); + else if( child->IsString() ) + text = child->GetString(); + + m_refdes = text; + return true; +} + + +bool KICADMODULE::parsePad( SEXPR::SEXPR* data ) +{ + KICADPAD* mp = new KICADPAD(); + + if( !mp->Read( data ) ) + { + delete mp; + return false; + } + + // NOTE: for now we only accept thru-hole pads + // for the MCAD description + if( !mp->IsThruHole() ) + { + delete mp; + return true; + } + + m_pads.push_back( mp ); + return true; +} + + +bool KICADMODULE::ComposePCB( class PCBMODEL* aPCB, S3D_RESOLVER* resolver, DOUBLET aOrigin ) +{ + // translate pads and curves to final position and append to PCB. + double dlim = (double)std::numeric_limits< float >::epsilon(); + + double vsin = sin( m_rotation ); + double vcos = cos( m_rotation ); + bool hasdata = false; + + double posX = m_position.x - aOrigin.x; + double posY = m_position.y - aOrigin.y; + + for( auto i : m_curves ) + { + if( i->m_layer != LAYER_EDGE || CURVE_NONE == i->m_form ) + continue; + + KICADCURVE lcurve = *i; + + if( LAYER_TOP == m_side ) + { + lcurve.m_start.y = -lcurve.m_start.y; + lcurve.m_end.y = -lcurve.m_end.y; + lcurve.m_angle = -lcurve.m_angle; + } + + if( m_rotation < -dlim || m_rotation > dlim ) + { + double x = lcurve.m_start.x * vcos - lcurve.m_start.y * vsin; + double y = lcurve.m_start.x * vsin + lcurve.m_start.y * vcos; + lcurve.m_start.x = x; + lcurve.m_start.y = y; + x = lcurve.m_end.x * vcos - lcurve.m_end.y * vsin; + y = lcurve.m_end.x * vsin + lcurve.m_end.y * vcos; + lcurve.m_end.x = x; + lcurve.m_end.y = y; + } + + lcurve.m_start.x += posX; + lcurve.m_start.y -= posY; + lcurve.m_end.x += posX; + lcurve.m_end.y -= posY; + + if( aPCB->AddOutlineSegment( &lcurve ) ) + hasdata = true; + + } + + for( auto i : m_pads ) + { + if( !i->IsThruHole() ) + continue; + + KICADPAD lpad = *i; + lpad.m_position.y = -lpad.m_position.y; + + if( m_rotation < -dlim || m_rotation > dlim ) + { + double x = lpad.m_position.x * vcos - lpad.m_position.y * vsin; + double y = lpad.m_position.x * vsin + lpad.m_position.y * vcos; + lpad.m_position.x = x; + lpad.m_position.y = y; + } + + lpad.m_position.x += posX; + lpad.m_position.y -= posY; + + if( aPCB->AddPadHole( &lpad ) ) + hasdata = true; + + } + + DOUBLET newpos( posX, posY ); + + for( auto i : m_models ) + { + std::string fname( resolver->ResolvePath( i->m_modelname.c_str() ).ToUTF8() ); + + if( aPCB->AddComponent( fname, m_refdes, LAYER_BOTTOM == m_side ? true : false, + newpos, m_rotation, i->m_offset, i->m_rotation ) ) + hasdata = true; + + } + + return hasdata; +} diff --git a/utils/kicad2step/pcb/kicadmodule.h b/utils/kicad2step/pcb/kicadmodule.h new file mode 100644 index 0000000000..b6b681dfb3 --- /dev/null +++ b/utils/kicad2step/pcb/kicadmodule.h @@ -0,0 +1,75 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file kicadmodule.h + * declares the PCB Component object. + */ + +#ifndef KICADMODULE_H +#define KICADMODULE_H + +#include +#include +#include "base.h" + +namespace SEXPR +{ + class SEXPR; +} + +class KICADPAD; +class KICADCURVE; +class KICADMODEL; +class PCBMODEL; +class S3D_RESOLVER; + +class KICADMODULE +{ +private: + bool parseModel( SEXPR::SEXPR* data ); + bool parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType ); + bool parseLayer( SEXPR::SEXPR* data ); + bool parsePosition( SEXPR::SEXPR* data ); + bool parseText( SEXPR::SEXPR* data ); + bool parsePad( SEXPR::SEXPR* data ); + + LAYERS m_side; + std::string m_refdes; + DOUBLET m_position; + double m_rotation; // rotation (radians) + + std::vector< KICADPAD* > m_pads; + std::vector< KICADCURVE* > m_curves; + std::vector< KICADMODEL* > m_models; + +public: + KICADMODULE(); + virtual ~KICADMODULE(); + + bool Read( SEXPR::SEXPR* aEntry ); + + bool ComposePCB( class PCBMODEL* aPCB, S3D_RESOLVER* resolver, DOUBLET aOrigin ); +}; + +#endif // KICADMODULE_H diff --git a/utils/kicad2step/pcb/kicadpad.cpp b/utils/kicad2step/pcb/kicadpad.cpp new file mode 100644 index 0000000000..9a6d9dedac --- /dev/null +++ b/utils/kicad2step/pcb/kicadpad.cpp @@ -0,0 +1,177 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include "sexpr/sexpr.h" +#include "kicadpad.h" + + +static const char bad_pad[] = "* corrupt module in PCB file; bad pad"; + + +KICADPAD::KICADPAD() +{ + m_rotation = 0.0; + m_thruhole = false; + m_drill.oval = false; + return; +} + + +KICADPAD::~KICADPAD() +{ + return; +} + + +bool KICADPAD::Read( SEXPR::SEXPR* aEntry ) +{ + // form: ( pad N thru_hole shape (at x y {r}) (size x y) (drill {oval} x {y}) (layers X X X) ) + int nchild = aEntry->GetNumberOfChildren(); + + if( nchild < 2 ) + { + std::ostringstream ostr; + ostr << bad_pad; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + SEXPR::SEXPR* child; + + for( int i = 1; i < nchild; ++i ) + { + child = aEntry->GetChild( i ); + + if( child->IsSymbol() && + ( child->GetSymbol() == "thru_hole" || child->GetSymbol() == "np_thru_hole" ) ) + { + m_thruhole = true; + continue; + } + + if( child->IsList() ) + { + std::string name = child->GetChild( 0 )->GetSymbol(); + bool ret = true; + + if( name == "drill" ) + ret = parseDrill( child ); + else if( name == "at" ) + ret = Get2DPositionAndRotation( child, m_position, m_rotation ); + + if( !ret ) + return false; + } + } + + return true; +} + + +bool KICADPAD::parseDrill( SEXPR::SEXPR* aDrill ) +{ + // form: (drill {oval} X {Y}) + const char bad_drill[] = "* corrupt module in PCB file; bad drill"; + int nchild = aDrill->GetNumberOfChildren(); + + if( nchild < 2 ) + { + std::ostringstream ostr; + ostr << bad_drill; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + SEXPR::SEXPR* child = aDrill->GetChild( 1 ); + int idx = 1; + m_drill.oval = false; + + if( child->IsSymbol() ) + { + if( child->GetSymbol() == "oval" && nchild >= 4 ) + { + m_drill.oval = true; + child = aDrill->GetChild( ++idx ); + } + else + { + std::ostringstream ostr; + ostr << bad_drill << " (unexpected symbol: "; + ostr << child->GetSymbol() << "), nchild = " << nchild; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + } + + double x; + + if( child->IsDouble() ) + x = child->GetDouble(); + else if( child->IsInteger() ) + x = (double) child->GetInteger(); + else + { + std::ostringstream ostr; + ostr << bad_drill << " (did not find X size)"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + m_drill.size.x = x; + m_drill.size.y = x; + + if( ++idx == nchild || !m_drill.oval ) + return true; + + for( int i = idx; i < nchild; ++i ) + { + child = aDrill->GetChild( i ); + + // NOTE: the Offset of the copper pad is stored + // in the drill string but since the copper is not + // needed in the MCAD model the Offset is simply ignored. + if( !child->IsList() ) + { + double y; + + if( child->IsDouble() ) + y = child->GetDouble(); + else if( child->IsInteger() ) + y = (double) child->GetInteger(); + else + { + std::ostringstream ostr; + ostr << bad_drill << " (did not find Y size)"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + m_drill.size.y = y; + } + } + + return true; +} diff --git a/utils/kicad2step/pcb/kicadpad.h b/utils/kicad2step/pcb/kicadpad.h new file mode 100644 index 0000000000..db44114702 --- /dev/null +++ b/utils/kicad2step/pcb/kicadpad.h @@ -0,0 +1,66 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file kicadpad.h + * declares the PAD description object. + */ + +#ifndef KICADPAD_H +#define KICADPAD_H + +#include +#include +#include "base.h" + + +struct KICADDRILL +{ + DOUBLET size; + bool oval; +}; + + +class KICADPAD +{ +private: + bool m_thruhole; + bool parseDrill( SEXPR::SEXPR* aDrill ); + +public: + KICADPAD(); + virtual ~KICADPAD(); + + bool Read( SEXPR::SEXPR* aEntry ); + + bool IsThruHole() + { + return m_thruhole; + } + + DOUBLET m_position; + double m_rotation; // rotation (radians) + KICADDRILL m_drill; +}; + +#endif // KICADPAD_H diff --git a/utils/kicad2step/pcb/kicadpcb.cpp b/utils/kicad2step/pcb/kicadpcb.cpp new file mode 100644 index 0000000000..b1ba64509f --- /dev/null +++ b/utils/kicad2step/pcb/kicadpcb.cpp @@ -0,0 +1,385 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "kicadpcb.h" +#include "sexpr/sexpr.h" +#include "sexpr/sexpr_parser.h" +#include "kicadmodule.h" +#include "kicadcurve.h" +#include "oce_utils.h" + + +/* + * GetKicadConfigPath() is taken from KiCad's common.cpp source: + * Copyright (C) 2014-2015 Jean-Pierre Charras, jp.charras at wanadoo.fr + * Copyright (C) 2008-2015 Wayne Stambaugh + * Copyright (C) 1992-2015 KiCad Developers + */ +static wxString GetKicadConfigPath() +{ + wxFileName cfgpath; + + // From the wxWidgets wxStandardPaths::GetUserConfigDir() help: + // Unix: ~ (the home directory) + // Windows: "C:\Documents and Settings\username\Application Data" + // Mac: ~/Library/Preferences + cfgpath.AssignDir( wxStandardPaths::Get().GetUserConfigDir() ); + +#if !defined( __WINDOWS__ ) && !defined( __WXMAC__ ) + wxString envstr; + + if( !wxGetEnv( wxT( "XDG_CONFIG_HOME" ), &envstr ) || envstr.IsEmpty() ) + { + // XDG_CONFIG_HOME is not set, so use the fallback + cfgpath.AppendDir( wxT( ".config" ) ); + } + else + { + // Override the assignment above with XDG_CONFIG_HOME + cfgpath.AssignDir( envstr ); + } +#endif + + cfgpath.AppendDir( wxT( "kicad" ) ); + + if( !cfgpath.DirExists() ) + { + cfgpath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ); + } + + return cfgpath.GetPath(); +} + + +KICADPCB::KICADPCB() +{ + wxFileName cfgdir( GetKicadConfigPath(), "" ); + cfgdir.AppendDir( "3d" ); + m_resolver.Set3DConfigDir( cfgdir.GetPath() ); + m_thickness = 1.6; + m_pcb = NULL; + + return; +} + + +KICADPCB::~KICADPCB() +{ + for( auto i : m_modules ) + delete i; + + for( auto i : m_curves ) + delete i; + + if( m_pcb ) + delete m_pcb; + + return; +} + + +bool KICADPCB::ReadFile( const wxString& aFileName ) +{ + wxFileName fname( aFileName ); + + if( fname.GetExt() != "kicad_pcb" ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * expecting extension 'kicad_pcb', got '"; + ostr << fname.GetExt().ToUTF8() << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; + } + + if( !fname.FileExists() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * no such file: '" << aFileName.ToUTF8() << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; + } + + fname.Normalize(); + m_filename = fname.GetFullPath().ToUTF8(); + m_resolver.SetProjectDir( fname.GetPath() ); + + try + { + SEXPR::PARSER parser; + std::string infile( fname.GetFullPath().ToUTF8() ); + SEXPR::SEXPR* data = parser.ParseFromFile( infile ); + + if( NULL == data ) + { + std::ostringstream ostr; + ostr << "* no data in file: '" << aFileName.ToUTF8() << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; + } + + if( !parsePCB( data ) ) + return false; + + } + catch( std::exception& e ) + { + std::ostringstream ostr; + ostr << "* error reading file: '" << aFileName.ToUTF8() << "'\n"; + ostr << " * " << e.what() << "\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; + } + catch( ... ) + { + std::ostringstream ostr; + ostr << "* unexpected exception while reading file: '" << aFileName.ToUTF8() << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; + } + + return true; +} + + +bool KICADPCB::WriteSTEP( const wxString& aFileName, bool aOverwrite ) +{ + if( m_pcb ) + { + std::string filename( aFileName.ToUTF8() ); + return m_pcb->WriteSTEP( filename, aOverwrite ); + } + + return false; +} + + +#ifdef SUPPORTS_IGES +bool KICADPCB::WriteIGES( const wxString& aFileName, bool aOverwrite ) +{ + if( m_pcb ) + { + std::string filename( aFileName.ToUTF8() ); + return m_pcb->WriteIGES( filename, aOverwrite ); + } + + return false; +} +#endif + + +bool KICADPCB::parsePCB( SEXPR::SEXPR* data ) +{ + if( NULL == data ) + return false; + + if( data->IsList() ) + { + size_t nc = data->GetNumberOfChildren(); + SEXPR::SEXPR* child = data->GetChild( 0 ); + std::string name = child->GetSymbol(); + + if( name != "kicad_pcb" ) + { + std::ostringstream ostr; + ostr << "* data is not a valid PCB file: '" << m_filename << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + bool result = true; + + for( size_t i = 1; i < nc && result; ++i ) + { + child = data->GetChild( i ); + + if( !child->IsList() ) + { + std::ostringstream ostr; + ostr << "* corrupt PCB file: '" << m_filename << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + std::string symname( child->GetChild( 0 )->GetSymbol() ); + + if( symname == "general" ) + result = result && parseGeneral( child ); + else if( symname == "module" ) + result = result && parseModule( child ); + else if( symname == "gr_arc" ) + result = result && parseCurve( child, CURVE_ARC ); + else if( symname == "gr_line" ) + result = result && parseCurve( child, CURVE_LINE ); + else if( symname == "gr_circle" ) + result = result && parseCurve( child, CURVE_CIRCLE ); + } + + return result; + } + + std::ostringstream ostr; + ostr << "* data is not a valid PCB file: '" << m_filename << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; +} + + +bool KICADPCB::parseGeneral( SEXPR::SEXPR* data ) +{ + size_t nc = data->GetNumberOfChildren(); + SEXPR::SEXPR* child = NULL; + + for( size_t i = 1; i < nc; ++i ) + { + child = data->GetChild( i ); + + if( !child->IsList() ) + { + std::ostringstream ostr; + ostr << "* corrupt PCB file: '" << m_filename << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + // at the moment only the thickness is of interest in + // the general section + if( child->GetChild( 0 )->GetSymbol() != "thickness" ) + continue; + + m_thickness = child->GetChild( 1 )->GetDouble(); + return true; + } + + std::ostringstream ostr; + ostr << "* corrupt PCB file: '" << m_filename << "'\n"; + ostr << "* no PCB thickness specified in general section\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; +} + + +bool KICADPCB::parseModule( SEXPR::SEXPR* data ) +{ + KICADMODULE* mp = new KICADMODULE(); + + if( !mp->Read( data ) ) + { + delete mp; + return false; + } + + m_modules.push_back( mp ); + return true; +} + + +bool KICADPCB::parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType ) +{ + KICADCURVE* mp = new KICADCURVE(); + + if( !mp->Read( data, aCurveType ) ) + { + delete mp; + return false; + } + + // reject any curves not on the Edge.Cuts layer + if( mp->GetLayer() != LAYER_EDGE ) + { + delete mp; + return true; + } + + m_curves.push_back( mp ); + return true; +} + + +bool KICADPCB::ComposePCB() +{ + if( m_pcb ) + return true; + + if( m_modules.empty() && m_curves.empty() ) + { + std::ostringstream ostr; + ostr << "** " << __FILE__ << ":" << __FUNCTION__ << ":" << __LINE__ << "\n"; + ostr << "* no PCB data to render\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + m_pcb = new PCBMODEL(); + m_pcb->SetPCBThickness( m_thickness ); + + for( auto i : m_curves ) + { + if( CURVE_NONE == i->m_form || LAYER_EDGE != i->m_layer ) + continue; + + // adjust the coordinate system + KICADCURVE lcurve = *i; + lcurve.m_start.y = -( lcurve.m_start.y - m_origin.y ); + lcurve.m_end.y = -( lcurve.m_end.y - m_origin.y ); + lcurve.m_start.x -= m_origin.x; + lcurve.m_end.x -= m_origin.x; + + if( CURVE_ARC == lcurve.m_form ) + lcurve.m_angle = -lcurve.m_angle; + + m_pcb->AddOutlineSegment( &lcurve ); + } + + for( auto i : m_modules ) + i->ComposePCB( m_pcb, &m_resolver, m_origin ); + + if( !m_pcb->CreatePCB() ) + { + std::ostringstream ostr; + ostr << "** " << __FILE__ << ":" << __FUNCTION__ << ":" << __LINE__ << "\n"; + ostr << "* could not create PCB solid model\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + delete m_pcb; + m_pcb = NULL; + return false; + } + + return true; +} diff --git a/utils/kicad2step/pcb/kicadpcb.h b/utils/kicad2step/pcb/kicadpcb.h new file mode 100644 index 0000000000..2bfc93d7d0 --- /dev/null +++ b/utils/kicad2step/pcb/kicadpcb.h @@ -0,0 +1,88 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file kicadpcb.h + * declares the main PCB object + */ + +#ifndef KICADPCB_H +#define KICADPCB_H + +#include +#include +#include +#include "3d_resolver.h" +#include "base.h" + +#ifdef SUPPORTS_IGES +#undef SUPPORTS_IGES +#endif + +namespace SEXPR +{ + class SEXPR; +} + +class KICADMODULE; +class KICADCURVE; +class PCBMODEL; + +class KICADPCB +{ +private: + S3D_RESOLVER m_resolver; + std::string m_filename; + PCBMODEL* m_pcb; + DOUBLET m_origin; + + // PCB parameters/entities + double m_thickness; + std::vector< KICADMODULE* > m_modules; + std::vector< KICADCURVE* > m_curves; + + bool parsePCB( SEXPR::SEXPR* data ); + bool parseGeneral( SEXPR::SEXPR* data ); + bool parseModule( SEXPR::SEXPR* data ); + bool parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType ); + +public: + KICADPCB(); + virtual ~KICADPCB(); + + void SetOrigin( double aXOrigin, double aYOrigin ) + { + m_origin.x = aXOrigin; + m_origin.y = aYOrigin; + } + + bool ReadFile( const wxString& aFileName ); + bool ComposePCB(); + bool WriteSTEP( const wxString& aFileName, bool aOverwrite ); + #ifdef SUPPORTS_IGES + bool WriteIGES( const wxString& aFileName, bool aOverwrite ); + #endif +}; + + +#endif // KICADPCB_H diff --git a/utils/kicad2step/pcb/oce_utils.cpp b/utils/kicad2step/pcb/oce_utils.cpp new file mode 100644 index 0000000000..12681229ca --- /dev/null +++ b/utils/kicad2step/pcb/oce_utils.cpp @@ -0,0 +1,1391 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "oce_utils.h" +#include "kicadpad.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define USER_PREC (1e-4) +#define USER_ANGLE_PREC (1e-6) +// minimum PCB thickness in mm (2 microns assumes a very thin polyimide film) +#define THICKNESS_MIN (0.002) +// default PCB thickness in mm +#define THICKNESS_DEFAULT (1.6) +// nominal offset from the board +#define BOARD_OFFSET (0.05 ) +// min. length**2 below which 2 points are considered coincident +#define MIN_LENGTH2 (0.0001) + +static void getEndPoints( const KICADCURVE& aCurve, double& spx0, double& spy0, + double& epx0, double& epy0 ) +{ + if( CURVE_ARC == aCurve.m_form ) + { + spx0 = aCurve.m_end.x; + spy0 = aCurve.m_end.y; + epx0 = aCurve.m_ep.x; + epy0 = aCurve.m_ep.y; + return; + } + + // assume a line + spx0 = aCurve.m_start.x; + spy0 = aCurve.m_start.y; + epx0 = aCurve.m_end.x; + epy0 = aCurve.m_end.y; + return; +} + +static void getCurveEndPoint( const KICADCURVE& aCurve, DOUBLET& aEndPoint ) +{ + if( CURVE_CIRCLE == aCurve.m_form ) + return; // circles are closed loops and have no end point + + if( CURVE_ARC == aCurve.m_form ) + { + aEndPoint.x = aCurve.m_ep.x; + aEndPoint.y = aCurve.m_ep.y; + return; + } + + // assume a line + aEndPoint.x = aCurve.m_end.x; + aEndPoint.y = aCurve.m_end.y; + return; +} + + +static void reverseCurve( KICADCURVE& aCurve ) +{ + if( CURVE_NONE == aCurve.m_form || CURVE_CIRCLE == aCurve.m_form ) + return; + + if( CURVE_LINE == aCurve.m_form ) + { + std::swap( aCurve.m_start, aCurve.m_end ); + return; + } + + std::swap( aCurve.m_end, aCurve.m_ep ); + std::swap( aCurve.m_endangle, aCurve.m_startangle ); + aCurve.m_angle = -aCurve.m_angle; + + return; +} + + +// supported file types +enum FormatType +{ + FMT_NONE = 0, + FMT_STEP = 1, + FMT_IGES = 2, + FMT_EMN = 3, + FMT_IDF = 4 +}; + + +FormatType fileType( const char* aFileName ) +{ + wxFileName lfile( aFileName ); + + if( !lfile.FileExists() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * no such file: '" << aFileName << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return FMT_NONE; + } + + wxString ext = lfile.GetExt(); + + if( ext == "idf" || ext == "IDF" ) + return FMT_IDF; // component outline + else if( ext == "emn" || ext == "EMN" ) + return FMT_EMN; // PCB assembly + + std::ifstream ifile; + ifile.open( aFileName ); + + if( !ifile.is_open() ) + return FMT_NONE; + + char iline[82]; + memset( iline, 0, 82 ); + ifile.getline( iline, 82 ); + ifile.close(); + iline[81] = 0; // ensure NULL termination when string is too long + + // check for STEP in Part 21 format + // (this can give false positives since Part 21 is not exclusively STEP) + if( !strncmp( iline, "ISO-10303-21;", 13 ) ) + return FMT_STEP; + + std::string fstr = iline; + + // check for STEP in XML format + // (this can give both false positive and false negatives) + if( fstr.find( "urn:oid:1.0.10303." ) != std::string::npos ) + return FMT_STEP; + + // Note: this is a very simple test which can yield false positives; the only + // sure method for determining if a file *not* an IGES model is to attempt + // to load it. + if( iline[72] == 'S' && ( iline[80] == 0 || iline[80] == 13 || iline[80] == 10 ) ) + return FMT_IGES; + + return FMT_NONE; +} + + +PCBMODEL::PCBMODEL() +{ + m_app = XCAFApp_Application::GetApplication(); + m_app->NewDocument( "MDTV-XCAF", m_doc ); + m_assy = XCAFDoc_DocumentTool::ShapeTool ( m_doc->Main() ); + m_assy_label = m_assy->NewShape(); + m_hasPCB = false; + m_components = 0; + m_precision = USER_PREC; + m_angleprec = USER_ANGLE_PREC; + m_thickness = THICKNESS_DEFAULT; + m_minx = 1.0e10; // absurdly large number; any valid PCB X value will be smaller + m_mincurve = m_curves.end(); + return; +} + + +PCBMODEL::~PCBMODEL() +{ + m_doc->Close(); + return; +} + +// add an outline segment +bool PCBMODEL::AddOutlineSegment( KICADCURVE* aCurve ) +{ + if( NULL == aCurve || LAYER_EDGE != aCurve->m_layer || CURVE_NONE == aCurve->m_form ) + return false; + + if( CURVE_LINE == aCurve->m_form ) + { + // reject zero - length lines + double dx = aCurve->m_end.x - aCurve->m_start.x; + double dy = aCurve->m_end.y - aCurve->m_start.y; + double distance = dx * dx + dy * dy; + + if( distance < MIN_LENGTH2 ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * rejected a zero-length line\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + } + else + { + // ensure that the start (center) and end (start of arc) are not the same point + double dx = aCurve->m_end.x - aCurve->m_start.x; + double dy = aCurve->m_end.y - aCurve->m_start.y; + double rad = dx * dx + dy * dy; + + if( rad < MIN_LENGTH2 ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * rejected a zero-radius arc or circle\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + // calculate the radius and, if applicable, end point + rad = sqrt( rad ); + aCurve->m_radius = rad; + + if( CURVE_ARC == aCurve->m_form ) + { + aCurve->m_startangle = atan2( dy, dx ); + + if( aCurve->m_startangle < 0.0 ) + aCurve->m_startangle += 2.0 * M_PI; + + double eang = aCurve->m_startangle + aCurve->m_angle; + + if( eang < 0.0 ) + eang += 2.0 * M_PI; + + if( aCurve->m_angle < 0.0 && eang > aCurve->m_startangle ) + aCurve->m_startangle += 2.0 * M_PI; + else if( aCurve->m_angle >= 0.0 && eang < aCurve->m_startangle ) + eang += 2.0 * M_PI; + + aCurve->m_endangle = eang; + aCurve->m_ep.x = aCurve->m_start.x + rad * cos( eang ); + aCurve->m_ep.y = aCurve->m_start.y + rad * sin( eang ); + + dx = aCurve->m_ep.x - aCurve->m_end.x; + dy = aCurve->m_ep.y - aCurve->m_end.y; + rad = dx * dx + dy * dy; + + if( rad < MIN_LENGTH2 ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * rejected an arc with equivalent end points\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + } + } + + m_curves.push_back( *aCurve ); + + // check if this curve has the current leftmost feature + switch( aCurve->m_form ) + { + case CURVE_LINE: + if( aCurve->m_start.x < m_minx ) + { + m_minx = aCurve->m_start.x; + m_mincurve = --(m_curves.end()); + } + + if( aCurve->m_end.x < m_minx ) + { + m_minx = aCurve->m_end.x; + m_mincurve = --(m_curves.end()); + } + + break; + + case CURVE_CIRCLE: + do + { + double dx = aCurve->m_start.x - aCurve->m_radius; + + if( dx < m_minx ) + { + m_minx = dx; + m_mincurve = --(m_curves.end()); + } + } while( 0 ); + + break; + + case CURVE_ARC: + do + { + double dx0 = aCurve->m_end.x - aCurve->m_start.x; + double dy0 = aCurve->m_end.y - aCurve->m_start.y; + int q0; // quadrant of start point + + if( dx0 > 0.0 && dy0 >= 0.0 ) + q0 = 1; + else if( dx0 <= 0.0 && dy0 > 0.0 ) + q0 = 2; + else if( dx0 < 0.0 && dy0 <= 0.0 ) + q0 = 3; + else + q0 = 4; + + double dx1 = aCurve->m_ep.x - aCurve->m_start.x; + double dy1 = aCurve->m_ep.y - aCurve->m_start.y; + int q1; // quadrant of end point + + if( dx1 > 0.0 && dy1 >= 0.0 ) + q1 = 1; + else if( dx1 <= 0.0 && dy1 > 0.0 ) + q1 = 2; + else if( dx1 < 0.0 && dy1 <= 0.0 ) + q1 = 3; + else + q1 = 4; + + // calculate x0, y0 for the start point on a CCW arc + double x0 = aCurve->m_end.x; + double x1 = aCurve->m_ep.x; + + if( aCurve->m_angle < 0.0 ) + { + std::swap( q0, q1 ); + std::swap( x0, x1 ); + } + + double minx; + + if( ( q0 <= 2 && q1 >= 3 ) || ( q0 >= 3 && x0 > x1 ) ) + minx = aCurve->m_start.x - aCurve->m_radius; + else + minx = std::min( x0, x1 ); + + if( minx < m_minx ) + { + m_minx = minx; + m_mincurve = --(m_curves.end()); + } + + } while( 0 ); + + break; + + default: + // unexpected curve type + do + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * unsupported curve type: '" << aCurve->m_form << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + } while( 0 ); + + return false; + } + + return true; +} + + +// add a pad hole or slot +bool PCBMODEL::AddPadHole( KICADPAD* aPad ) +{ + if( NULL == aPad || !aPad->IsThruHole() ) + return false; + + if( !aPad->m_drill.oval ) + { + TopoDS_Shape s = BRepPrimAPI_MakeCylinder( aPad->m_drill.size.x * 0.5, + m_thickness * 2.0 ).Shape(); + gp_Trsf shift; + shift.SetTranslation( gp_Vec( aPad->m_position.x, aPad->m_position.y, -m_thickness * 0.5 ) ); + BRepBuilderAPI_Transform hole( s, shift ); + m_cutouts.push_back( hole.Shape() ); + return true; + } + + // slotted hole + double angle_offset = 0.0; + double rad; // radius of the slot + double hlen; // half length of the slot + + if( aPad->m_drill.size.x < aPad->m_drill.size.y ) + { + angle_offset = M_PI_2; + rad = aPad->m_drill.size.x * 0.5; + hlen = aPad->m_drill.size.y * 0.5 - rad; + } + else + { + rad = aPad->m_drill.size.y * 0.5; + hlen = aPad->m_drill.size.x * 0.5 - rad; + } + + DOUBLET c0( -hlen, 0.0 ); + DOUBLET c1( hlen, 0.0 ); + DOUBLET p0( -hlen, rad ); + DOUBLET p1( -hlen, -rad ); + DOUBLET p2( hlen, -rad ); + DOUBLET p3( hlen, rad ); + + angle_offset += aPad->m_rotation; + double dlim = (double)std::numeric_limits< float >::epsilon(); + + if( angle_offset < -dlim || angle_offset > dlim ) + { + double vsin = sin( angle_offset ); + double vcos = cos( angle_offset ); + + double x = c0.x * vcos - c0.y * vsin; + double y = c0.x * vsin + c0.y * vcos; + c0.x = x; + c0.y = y; + + x = c1.x * vcos - c1.y * vsin; + y = c1.x * vsin + c1.y * vcos; + c1.x = x; + c1.y = y; + + x = p0.x * vcos - p0.y * vsin; + y = p0.x * vsin + p0.y * vcos; + p0.x = x; + p0.y = y; + + x = p1.x * vcos - p1.y * vsin; + y = p1.x * vsin + p1.y * vcos; + p1.x = x; + p1.y = y; + + x = p2.x * vcos - p2.y * vsin; + y = p2.x * vsin + p2.y * vcos; + p2.x = x; + p2.y = y; + + x = p3.x * vcos - p3.y * vsin; + y = p3.x * vsin + p3.y * vcos; + p3.x = x; + p3.y = y; + } + + c0.x += aPad->m_position.x; + c0.y += aPad->m_position.y; + c1.x += aPad->m_position.x; + c1.y += aPad->m_position.y; + p0.x += aPad->m_position.x; + p0.y += aPad->m_position.y; + p1.x += aPad->m_position.x; + p1.y += aPad->m_position.y; + p2.x += aPad->m_position.x; + p2.y += aPad->m_position.y; + p3.x += aPad->m_position.x; + p3.y += aPad->m_position.y; + + OUTLINE oln; + KICADCURVE crv0, crv1, crv2, crv3; + + // crv0 = arc + crv0.m_start = c0; + crv0.m_end = p0; + crv0.m_ep = p1; + crv0.m_angle = M_PI; + crv0.m_radius = rad; + crv0.m_form = CURVE_ARC; + + // crv1 = line + crv1.m_start = p1; + crv1.m_end = p2; + crv1.m_form = CURVE_LINE; + + // crv2 = arc + crv2.m_start = c1; + crv2.m_end = p2; + crv2.m_ep = p3; + crv2.m_angle = M_PI; + crv2.m_radius = rad; + crv2.m_form = CURVE_ARC; + + // crv3 = line + crv3.m_start = p3; + crv3.m_end = p0; + crv3.m_form = CURVE_LINE; + + oln.AddSegment( crv0 ); + oln.AddSegment( crv1 ); + oln.AddSegment( crv2 ); + oln.AddSegment( crv3 ); + TopoDS_Shape slot; + + if( oln.MakeShape( slot, m_thickness ) ) + { + if( !slot.IsNull() ) + m_cutouts.push_back( slot ); + + return true; + } + + return false; +} + + +// add a component at the given position and orientation +bool PCBMODEL::AddComponent( const std::string& aFileName, const std::string aRefDes, + bool aBottom, DOUBLET aPosition, double aRotation, + TRIPLET aOffset, TRIPLET aOrientation ) +{ + // first retrieve a label + TDF_Label lmodel; + + if( !getModelLabel( aFileName, lmodel ) ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * no model for filename '" << aFileName << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + // calculate the Location transform + TopLoc_Location toploc; + + if( !getModelLocation( aBottom, aPosition, aRotation, aOffset, aOrientation, toploc ) ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * no location data for filename '" << aFileName << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + // add the located sub-assembly + TDF_Label llabel = m_assy->AddComponent( m_assy_label, lmodel, toploc ); + + if( llabel.IsNull() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * could not add component with filename '" << aFileName << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + // attach the RefDes name + TCollection_ExtendedString refdes( aRefDes.c_str() ); + TDataStd_Name::Set( llabel, refdes ); + + return true; +} + + +void PCBMODEL::SetPCBThickness( double aThickness ) +{ + if( aThickness < 0.0 ) + m_thickness = THICKNESS_DEFAULT; + else if( aThickness < THICKNESS_MIN ) + m_thickness = THICKNESS_MIN; + else + m_thickness = aThickness; + + return; +} + + +// create the PCB (board only) model using the current outlines and drill holes +bool PCBMODEL::CreatePCB() +{ + if( m_hasPCB ) + { + if( m_pcb_label.IsNull() ) + return false; + + return true; + } + + if( m_curves.empty() || m_mincurve == m_curves.end() ) + { + m_hasPCB = true; + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * no valid board outline\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + m_hasPCB = true; // whether or not operations fail we note that CreatePCB has been invoked + TopoDS_Shape board; + OUTLINE oln; // loop to assemble (represents PCB outline and cutouts) + oln.AddSegment( *m_mincurve ); + m_curves.erase( m_mincurve ); + + while( !m_curves.empty() ) + { + if( oln.IsClosed() ) + { + if( board.IsNull() ) + { + if( !oln.MakeShape( board, m_thickness ) ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * could not create board extrusion\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; + } + } + else + { + TopoDS_Shape hole; + + if( oln.MakeShape( hole, m_thickness ) ) + { + m_cutouts.push_back( hole ); + } + else + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * could not create board cutout\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + } + } + + oln.Clear(); + + if( !m_curves.empty() ) + { + oln.AddSegment( m_curves.front() ); + m_curves.pop_front(); + } + + continue; + } + + std::list< KICADCURVE >::iterator sC = m_curves.begin(); + std::list< KICADCURVE >::iterator eC = m_curves.end(); + + while( sC != eC ) + { + if( oln.AddSegment( *sC ) ) + { + m_curves.erase( sC ); + break; + } + + ++sC; + } + + if( sC == eC && !oln.m_curves.empty() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * could not close outline (dropping outline data with " << oln.m_curves.size() << " segments)\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + oln.Clear(); + + if( !m_curves.empty() ) + { + oln.AddSegment( m_curves.front() ); + m_curves.pop_front(); + } + } + } + + if( oln.IsClosed() ) + { + if( board.IsNull() ) + { + if( !oln.MakeShape( board, m_thickness ) ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * could not create board extrusion\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + } + else + { + TopoDS_Shape hole; + + if( oln.MakeShape( hole, m_thickness ) ) + { + m_cutouts.push_back( hole ); + } + else + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * could not create board cutout\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + } + } + } + + // subtract cutouts (if any) + for( auto i : m_cutouts ) + board = BRepAlgoAPI_Cut( board, i ); + + // push the board to the data structure + m_pcb_label = m_assy->AddComponent( m_assy_label, board ); + + if( m_pcb_label.IsNull() ) + return false; + + // color the PCB + Handle(XCAFDoc_ColorTool) color = + XCAFDoc_DocumentTool::ColorTool( m_doc->Main () ); + Quantity_Color pcb_green( 0.06, 0.4, 0.06, Quantity_TOC_RGB ); + color->SetColor( m_pcb_label, pcb_green, XCAFDoc_ColorSurf ); + + TopExp_Explorer topex; + topex.Init( m_assy->GetShape( m_pcb_label ), TopAbs_SOLID ); + + while( topex.More() ) + { + color->SetColor( topex.Current(), pcb_green, XCAFDoc_ColorSurf ); + topex.Next(); + } + + return true; +} + + +#ifdef SUPPORTS_IGES +// write the assembly model in IGES format +bool PCBMODEL::WriteIGES( const std::string& aFileName, bool aOverwrite ) +{ + if( m_pcb_label.IsNull() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * No valid PCB assembly; cannot create output file " << aFileName << "\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + wxFileName fn( aFileName ); + IGESControl_Controller::Init(); + IGESCAFControl_Writer writer; + writer.SetColorMode( Standard_True ); + writer.SetNameMode( Standard_True ); + IGESData_GlobalSection header = writer.Model()->GlobalSection(); + header.SetFileName( new TCollection_HAsciiString( fn.GetFullName().ToUTF8() ) ); + header.SetSendName( new TCollection_HAsciiString( "KiCad electronic assembly" ) ); + header.SetAuthorName( new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.author" ) ) ); + header.SetCompanyName( new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.company" ) ) ); + writer.Model()->SetGlobalSection( header ); + + if( Standard_False == writer.Perform( m_doc, aFileName.c_str() ) ) + return false; + + return true; +} +#endif + + +// write the assembly model in STEP format +bool PCBMODEL::WriteSTEP( const std::string& aFileName, bool aOverwrite ) +{ + if( m_pcb_label.IsNull() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * No valid PCB assembly; cannot create output file " << aFileName << "\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + STEPCAFControl_Writer writer; + writer.SetColorMode( Standard_True ); + writer.SetNameMode( Standard_True ); + + if( Standard_False == writer.Transfer( m_doc, STEPControl_AsIs ) ) + return false; + + APIHeaderSection_MakeHeader hdr( writer.ChangeWriter().Model() ); + wxFileName fn( aFileName ); + hdr.SetName( new TCollection_HAsciiString( fn.GetFullName().ToUTF8() ) ); + // TODO: how to control and ensure consistency with IGES? + hdr.SetAuthorValue( 1, new TCollection_HAsciiString( "An Author" ) ); + hdr.SetOrganizationValue( 1, new TCollection_HAsciiString( "A Company" ) ); + hdr.SetOriginatingSystem( new TCollection_HAsciiString( "KiCad to STEP converter" ) ); + hdr.SetDescriptionValue( 1, new TCollection_HAsciiString( "KiCad electronic assembly" ) ); + + if( Standard_False == writer.Write( aFileName.c_str() ) ) + return false; + + return true; +} + + +bool PCBMODEL::getModelLabel( const std::string aFileName, TDF_Label& aLabel ) +{ + MODEL_MAP::const_iterator mm = m_models.find( aFileName ); + + if( mm != m_models.end() ) + { + aLabel = mm->second; + return true; + } + + aLabel.Nullify(); + + Handle( TDocStd_Document ) doc; + m_app->NewDocument( "MDTV-XCAF", doc ); + + FormatType modelFmt = fileType( aFileName.c_str() ); + + switch( modelFmt ) + { + case FMT_IGES: + if( !readIGES( doc, aFileName.c_str() ) ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * readIGES() failed on filename '" << aFileName << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + break; + + case FMT_STEP: + if( !readSTEP( doc, aFileName.c_str() ) ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * readSTEP() failed on filename '" << aFileName << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + break; + + // TODO: implement IDF and EMN converters + + default: + return false; + } + + aLabel = transferModel( doc, m_doc ); + + if( aLabel.IsNull() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * could not transfer model data from file '" << aFileName << "'\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + + // attach the PART NAME ( base filename: note that in principle + // different models may have the same base filename ) + wxFileName afile( aFileName.c_str() ); + std::string pname( afile.GetName().ToUTF8() ); + TCollection_ExtendedString partname( pname.c_str() ); + TDataStd_Name::Set( aLabel, partname ); + + m_models.insert( MODEL_DATUM( aFileName, aLabel ) ); + ++m_components; + return true; +} + + +bool PCBMODEL::getModelLocation( bool aBottom, DOUBLET aPosition, double aRotation, + TRIPLET aOffset, TRIPLET aOrientation, TopLoc_Location& aLocation ) +{ + // Order of operations: + // a. aOrientation is applied -Z*-Y*-X + // b. aOffset is applied + // Top ? add thickness to the Z offset + // c. Bottom ? Rotate on X axis (in contrast to most ECAD which mirror on Y), + // then rotate on +Z + // Top ? rotate on -Z + // d. aPosition is applied + // + // Note: Y axis is inverted in KiCad + + gp_Trsf lPos; + lPos.SetTranslation( gp_Vec( aPosition.x, -aPosition.y, 0.0 ) ); + + // offset (inches) + aOffset.x *= 25.4; + aOffset.y *= 25.4; + aOffset.z *= 25.4 + BOARD_OFFSET; + gp_Trsf lRot; + + if( aBottom ) + { + lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation ); + lPos.Multiply( lRot ); + lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), M_PI ); + lPos.Multiply( lRot ); + } + else + { + aOffset.z += m_thickness; + lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation ); + lPos.Multiply( lRot ); + } + + gp_Trsf lOff; + lOff.SetTranslation( gp_Vec( aOffset.x, aOffset.y, aOffset.z ) ); + lPos.Multiply( lOff ); + + gp_Trsf lOrient; + lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), + gp_Dir( 0.0, 0.0, 1.0 ) ), -aOrientation.z ); + lPos.Multiply( lOrient ); + lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), + gp_Dir( 0.0, 1.0, 0.0 ) ), -aOrientation.y ); + lPos.Multiply( lOrient ); + lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), + gp_Dir( 1.0, 0.0, 0.0 ) ), -aOrientation.x ); + lPos.Multiply( lOrient ); + + aLocation = TopLoc_Location( lPos ); + return true; +} + + +bool PCBMODEL::readIGES( Handle( TDocStd_Document )& doc, const char* fname ) +{ + IGESControl_Controller::Init(); + IGESCAFControl_Reader reader; + IFSelect_ReturnStatus stat = reader.ReadFile( fname ); + + if( stat != IFSelect_RetDone ) + return false; + + // Enable user-defined shape precision + if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) ) + return false; + + // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles) + if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) ) + return false; + + // set other translation options + reader.SetColorMode(true); // use model colors + reader.SetNameMode(false); // don't use IGES label names + reader.SetLayerMode(false); // ignore LAYER data + + if ( !reader.Transfer( doc ) ) + { + doc->Close(); + return false; + } + + // are there any shapes to translate? + if( reader.NbShapes() < 1 ) + { + doc->Close(); + return false; + } + + return true; +} + + +bool PCBMODEL::readSTEP( Handle(TDocStd_Document)& doc, const char* fname ) +{ + STEPCAFControl_Reader reader; + IFSelect_ReturnStatus stat = reader.ReadFile( fname ); + + if( stat != IFSelect_RetDone ) + return false; + + // Enable user-defined shape precision + if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) ) + return false; + + // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles) + if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) ) + return false; + + // set other translation options + reader.SetColorMode(true); // use model colors + reader.SetNameMode(false); // don't use label names + reader.SetLayerMode(false); // ignore LAYER data + + if ( !reader.Transfer( doc ) ) + { + doc->Close(); + return false; + } + + // are there any shapes to translate? + if( reader.NbRootsForTransfer() < 1 ) + { + doc->Close(); + return false; + } + + return true; +} + + +TDF_Label PCBMODEL::transferModel( Handle( TDocStd_Document )& source, + Handle( TDocStd_Document )& dest ) +{ + // transfer data from Source into a top level component of Dest + + // s_assy = shape tool for the source + Handle(XCAFDoc_ShapeTool) s_assy = XCAFDoc_DocumentTool::ShapeTool ( source->Main() ); + + // retrieve all free shapes within the assembly + TDF_LabelSequence frshapes; + s_assy->GetFreeShapes( frshapes ); + + // d_assy = shape tool for the destination + Handle(XCAFDoc_ShapeTool) d_assy = XCAFDoc_DocumentTool::ShapeTool ( dest->Main() ); + + // create a new shape within the destination and set the assembly tool to point to it + TDF_Label component = d_assy->NewShape(); + + int nshapes = frshapes.Length(); + int id = 1; + Handle( XCAFDoc_ColorTool ) scolor = XCAFDoc_DocumentTool::ColorTool( source->Main() ); + Handle( XCAFDoc_ColorTool ) dcolor = XCAFDoc_DocumentTool::ColorTool( dest->Main() ); + TopExp_Explorer dtop; + TopExp_Explorer stop; + + while( id <= nshapes ) + { + TopoDS_Shape shape = s_assy->GetShape( frshapes.Value(id) ); + + if ( !shape.IsNull() ) + { + TDF_Label niulab = d_assy->AddComponent( component, shape, Standard_False ); + + // check for per-surface colors + stop.Init( shape, TopAbs_FACE ); + dtop.Init( d_assy->GetShape( niulab ), TopAbs_FACE ); + + while( stop.More() && dtop.More() ) + { + Quantity_Color face_color; + + TDF_Label tl; + + // give priority to the base shape's color + if( s_assy->FindShape( stop.Current(), tl ) ) + { + if( scolor->GetColor( tl, XCAFDoc_ColorSurf, face_color ) + || scolor->GetColor( tl, XCAFDoc_ColorGen, face_color ) + || scolor->GetColor( tl, XCAFDoc_ColorCurv, face_color ) ) + { + dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf ); + } + } + else if( scolor->GetColor( stop.Current(), XCAFDoc_ColorSurf, face_color ) + || scolor->GetColor( stop.Current(), XCAFDoc_ColorGen, face_color ) + || scolor->GetColor( stop.Current(), XCAFDoc_ColorCurv, face_color ) ) + { + + dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf ); + } + + stop.Next(); + dtop.Next(); + } + + // check for per-solid colors + stop.Init( shape, TopAbs_SOLID ); + dtop.Init( d_assy->GetShape( niulab ), TopAbs_SOLID, TopAbs_FACE ); + + while( stop.More() && dtop.More() ) + { + Quantity_Color face_color; + + TDF_Label tl; + + // give priority to the base shape's color + if( s_assy->FindShape( stop.Current(), tl ) ) + { + if( scolor->GetColor( tl, XCAFDoc_ColorSurf, face_color ) + || scolor->GetColor( tl, XCAFDoc_ColorGen, face_color ) + || scolor->GetColor( tl, XCAFDoc_ColorCurv, face_color ) ) + { + dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorGen ); + } + } + else if( scolor->GetColor( stop.Current(), XCAFDoc_ColorSurf, face_color ) + || scolor->GetColor( stop.Current(), XCAFDoc_ColorGen, face_color ) + || scolor->GetColor( stop.Current(), XCAFDoc_ColorCurv, face_color ) ) + { + dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf ); + } + + stop.Next(); + dtop.Next(); + } + + + } + + ++id; + }; + + return component; +} + + +OUTLINE::OUTLINE() +{ + m_closed = false; + return; +} + + +OUTLINE::~OUTLINE() +{ + return; +} + + +void OUTLINE::Clear() +{ + m_closed = false; + m_curves.clear(); + return; +} + + +bool OUTLINE::AddSegment( const KICADCURVE& aCurve ) +{ + if( m_closed ) + return false; + + if( m_curves.empty() ) + { + m_curves.push_back( aCurve ); + + if( CURVE_CIRCLE == aCurve.m_form ) + m_closed = true; + + return true; + } + + if( CURVE_CIRCLE == aCurve.m_form ) + return false; + + // get the end points of the first curve + double spx0, spy0; + double epx0, epy0; + getEndPoints( m_curves.front(), spx0, spy0, epx0, epy0 ); + + // get the end points of the free curve + double spx1, spy1; + double epx1, epy1; + getEndPoints( aCurve, spx1, spy1, epx1, epy1 ); + + // check if the curve attaches to the front + double dx, dy; + dx = epx1 - spx0; + dy = epy1 - spy0; + + if( dx * dx + dy * dy < MIN_LENGTH2 ) + { + m_curves.push_front( aCurve ); + m_closed = testClosed( m_curves.front(), m_curves.back() ); + return true; + } + else + { + dx = spx1 - spx0; + dy = spy1 - spy0; + + if( dx * dx + dy * dy < MIN_LENGTH2 ) + { + KICADCURVE curve = aCurve; + reverseCurve( curve ); + m_curves.push_front( curve ); + m_closed = testClosed( m_curves.front(), m_curves.back() ); + return true; + } + } + + // check if the curve attaches to the back + getEndPoints( m_curves.back(), spx0, spy0, epx0, epy0 ); + dx = spx1 - epx0; + dy = spy1 - epy0; + + if( dx * dx + dy * dy < MIN_LENGTH2 ) + { + m_curves.push_back( aCurve ); + m_closed = testClosed( m_curves.front(), m_curves.back() ); + return true; + } + else + { + dx = epx1 - epx0; + dy = epy1 - epy0; + + if( dx * dx + dy * dy < MIN_LENGTH2 ) + { + KICADCURVE curve = aCurve; + reverseCurve( curve ); + m_curves.push_back( curve ); + m_closed = testClosed( m_curves.front(), m_curves.back() ); + return true; + } + } + + // this curve is not an end segment of the current loop + return false; +} + + +bool OUTLINE::MakeShape( TopoDS_Shape& aShape, double aThickness ) +{ + if( !aShape.IsNull() ) + return false; // there is already data in the shape object + + if( m_curves.empty() ) + return true; // suceeded in doing nothing + + if( !m_closed ) + return false; // the loop is not closed + + BRepBuilderAPI_MakeWire wire; + DOUBLET lastPoint; + getCurveEndPoint( m_curves.back(), lastPoint ); + + for( auto i : m_curves ) + { + if( !addEdge( &wire, i, lastPoint ) ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * failed to add an edge\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + return false; + } + } + + TopoDS_Face face = BRepBuilderAPI_MakeFace( wire ); + aShape = BRepPrimAPI_MakePrism( face, gp_Vec( 0, 0, aThickness ) ); + + if( aShape.IsNull() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * failed to create a prismatic shape\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; + } + + return true; +} + + +bool OUTLINE::addEdge( BRepBuilderAPI_MakeWire* aWire, KICADCURVE& aCurve, DOUBLET& aLastPoint ) +{ + TopoDS_Edge edge; + DOUBLET endPoint; + getCurveEndPoint( aCurve, endPoint ); + + switch( aCurve.m_form ) + { + case CURVE_LINE: + edge = BRepBuilderAPI_MakeEdge( gp_Pnt( aLastPoint.x, aLastPoint.y, 0.0 ), + gp_Pnt( endPoint.x, endPoint.y, 0.0 ) ); + break; + + case CURVE_ARC: + do + { + gp_Circ arc( gp_Ax2( gp_Pnt( aCurve.m_start.x, aCurve.m_start.y, 0.0 ), + gp_Dir( 0.0, 0.0, 1.0 ) ), aCurve.m_radius ); + + gp_Pnt sa( aLastPoint.x, aLastPoint.y, 0.0 ); + gp_Pnt ea( endPoint.x, endPoint.y, 0.0 ); + + if( aCurve.m_angle < 0.0 ) + edge = BRepBuilderAPI_MakeEdge( arc, ea, sa ); + else + edge = BRepBuilderAPI_MakeEdge( arc, sa, ea ); + + } while( 0 ); + break; + + case CURVE_CIRCLE: + edge = BRepBuilderAPI_MakeEdge( gp_Circ( gp_Ax2( gp_Pnt( aCurve.m_start.x, aCurve.m_start.y, 0.0 ), + gp_Dir( 0.0, 0.0, 1.0 ) ), aCurve.m_radius ) ); + break; + + default: + do + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * unsupported curve type: " << aCurve.m_form << "\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; + } while( 0 ); + } + + if( edge.IsNull() ) + return false; + + aLastPoint = endPoint; + aWire->Add( edge ); + + if( BRepBuilderAPI_DisconnectedWire == aWire->Error() ) + { + std::ostringstream ostr; + ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; + ostr << " * failed to add curve\n"; + wxLogMessage( "%s\n", ostr.str().c_str() ); + + return false; + } + + return true; +} + + +bool OUTLINE::testClosed( KICADCURVE& aFrontCurve, KICADCURVE& aBackCurve ) +{ + double spx0, spy0, epx0, epy0; + getEndPoints( aFrontCurve, spx0, spy0, epx0, epy0 ); + double spx1, spy1, epx1, epy1; + getEndPoints( aBackCurve, spx1, spy1, epx1, epy1 ); + + double dx = epx1 - spx0; + double dy = epy1 - spy0; + double r = dx * dx + dy * dy; + + if( r < MIN_LENGTH2 ) + return true; + + return false; +} diff --git a/utils/kicad2step/pcb/oce_utils.h b/utils/kicad2step/pcb/oce_utils.h new file mode 100644 index 0000000000..0e560315ce --- /dev/null +++ b/utils/kicad2step/pcb/oce_utils.h @@ -0,0 +1,139 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef OCE_VIS_OCE_UTILS_H +#define OCE_VIS_OCE_UTILS_H + +#include +#include +#include +#include +#include +#include "base.h" +#include "kicadpcb.h" +#include "kicadcurve.h" + +#include +#include +#include +#include +#include +#include + + +typedef std::pair< std::string, TDF_Label > MODEL_DATUM; +typedef std::map< std::string, TDF_Label > MODEL_MAP; + +class KICADPAD; + +class OUTLINE +{ +private: + bool m_closed; // set true if the loop is closed + + bool addEdge( BRepBuilderAPI_MakeWire* aWire, KICADCURVE& aCurve, DOUBLET& aLastPoint ); + bool testClosed( KICADCURVE& aFrontCurve, KICADCURVE& aBackCurve ); + +public: + std::list< KICADCURVE > m_curves; // list of contiguous segments + OUTLINE(); + virtual ~OUTLINE(); + + void Clear(); + + // attempt to add a curve to the outline; on success returns true + bool AddSegment( const KICADCURVE& aCurve ); + + bool IsClosed() + { + return m_closed; + } + + bool MakeShape( TopoDS_Shape& aShape, double aThickness ); +}; + + +class PCBMODEL +{ + Handle( XCAFApp_Application ) m_app; + Handle( TDocStd_Document ) m_doc; + Handle( XCAFDoc_ShapeTool ) m_assy; + TDF_Label m_assy_label; + bool m_hasPCB; // set true if CreatePCB() has been invoked + TDF_Label m_pcb_label; // label for the PCB model + MODEL_MAP m_models; // map of file names to model labels + int m_components; // number of successfully loaded components; + double m_precision; // model (length unit) numeric precision + double m_angleprec; // angle numeric precision + double m_thickness; // PCB thickness, mm + double m_minx; // minimum X value in curves (leftmost curve feature) + std::list< KICADCURVE >::iterator m_mincurve; // iterator to the leftmost curve + + std::list< KICADCURVE > m_curves; + std::vector< TopoDS_Shape > m_cutouts; + + bool getModelLabel( const std::string aFileName, TDF_Label& aLabel ); + + bool getModelLocation( bool aBottom, DOUBLET aPosition, double aRotation, + TRIPLET aOffset, TRIPLET aOrientation, TopLoc_Location& aLocation ); + + bool readIGES( Handle( TDocStd_Document )& m_doc, const char* fname ); + bool readSTEP( Handle( TDocStd_Document )& m_doc, const char* fname ); + + TDF_Label transferModel( Handle( TDocStd_Document )& source, + Handle( TDocStd_Document )& dest ); + +public: + PCBMODEL(); + virtual ~PCBMODEL(); + + // add an outline segment (must be in final position) + bool AddOutlineSegment( KICADCURVE* aCurve ); + + // add a pad hole or slot (must be in final position) + bool AddPadHole( KICADPAD* aPad ); + + // add a component at the given position and orientation + bool AddComponent( const std::string& aFileName, const std::string aRefDes, + bool aBottom, DOUBLET aPosition, double aRotation, + TRIPLET aOffset, TRIPLET aOrientation ); + + // set the thickness of the PCB (mm); the top of the PCB shall be at Z = aThickness + // aThickness < 0.0 == use default thickness + // aThickness <= THICKNESS_MIN == use THICKNESS_MIN + // aThickness > THICKNESS_MIN == use aThickness + void SetPCBThickness( double aThickness ); + + // create the PCB model using the current outlines and drill holes + bool CreatePCB(); + +#ifdef SUPPORTS_IGES + // write the assembly model in IGES format + bool WriteIGES( const std::string& aFileName, bool aOverwrite ); +#endif + + // write the assembly model in STEP format + bool WriteSTEP( const std::string& aFileName, bool aOverwrite ); +}; + +#endif //OCE_VIS_OCE_UTILS_H