Add IO_MGR::FindPluginTypeFromBoardPath (+QA tests)

This commit is contained in:
Roberto Fernandez Bautista 2023-08-12 16:37:02 +02:00 committed by Alex Shvartzkop
parent 8d5ebf5e30
commit dbfb7880b5
13 changed files with 271 additions and 62 deletions

View File

@ -66,7 +66,6 @@
#include <wx/stdpaths.h>
#include <wx/filedlg.h>
#include <wx/wfstream.h>
#include <wx/txtstrm.h>
#if wxCHECK_VERSION( 3, 1, 7 )
@ -444,32 +443,6 @@ bool PCB_EDIT_FRAME::Files_io_from_id( int id )
}
// The KIWAY_PLAYER::OpenProjectFiles() API knows nothing about plugins, so
// determine how to load the BOARD here
IO_MGR::PCB_FILE_T FindBoardPlugin( const wxString& aFileName, int aCtl = 0 )
{
const auto& plugins = IO_MGR::PLUGIN_REGISTRY::Instance()->AllPlugins();
for( const auto& plugin : plugins )
{
bool isKiCad = plugin.m_type == IO_MGR::KICAD_SEXP || plugin.m_type == IO_MGR::LEGACY;
if( ( aCtl & KICTL_KICAD_ONLY ) && !isKiCad )
continue;
if( ( aCtl & KICTL_NONKICAD_ONLY ) && isKiCad )
continue;
PLUGIN::RELEASER pi( plugin.m_createFunc() );
if( pi->CanReadBoard( aFileName ) )
return plugin.m_type;
}
return IO_MGR::FILE_TYPE_NONE;
}
int PCB_EDIT_FRAME::inferLegacyEdgeClearance( BOARD* aBoard )
{
PCB_LAYER_COLLECTOR collector;
@ -592,7 +565,7 @@ bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
// No save prompt (we already prompted above), and only reset to a new blank board if new
Clear_Pcb( false, !is_new );
IO_MGR::PCB_FILE_T pluginType = FindBoardPlugin( fullFileName, aCtl );
IO_MGR::PCB_FILE_T pluginType = IO_MGR::FindPluginTypeFromBoardPath( fullFileName, aCtl );
bool converted = pluginType != IO_MGR::LEGACY && pluginType != IO_MGR::KICAD_SEXP;

View File

@ -26,9 +26,12 @@
#include <wx/uri.h>
#include <config.h>
#include <kiway_player.h>
#include <io_mgr.h>
#include <wildcards_and_files_ext.h>
#include <plugins/eagle/eagle_plugin.h>
#include <plugins/geda/gpcb_plugin.h>
#include <io_mgr.h>
#include <plugins/kicad/pcb_plugin.h>
#include <plugins/legacy/legacy_plugin.h>
#include <plugins/pcad/pcad_plugin.h>
@ -38,7 +41,6 @@
#include <plugins/altium/solidworks_pcb_plugin.h>
#include <plugins/cadstar/cadstar_pcb_archive_plugin.h>
#include <plugins/fabmaster/fabmaster_plugin.h>
#include <wildcards_and_files_ext.h>
#define FMT_UNIMPLEMENTED _( "Plugin \"%s\" does not implement the \"%s\" function." )
#define FMT_NOTFOUND _( "Plugin type \"%s\" is not found." )
@ -113,6 +115,32 @@ IO_MGR::PCB_FILE_T IO_MGR::EnumFromStr( const wxString& aType )
}
// The KIWAY_PLAYER::OpenProjectFiles() API knows nothing about plugins, so
// determine how to load the BOARD here
IO_MGR::PCB_FILE_T IO_MGR::FindPluginTypeFromBoardPath( const wxString& aFileName, int aCtl )
{
const auto& plugins = IO_MGR::PLUGIN_REGISTRY::Instance()->AllPlugins();
for( const auto& plugin : plugins )
{
bool isKiCad = plugin.m_type == IO_MGR::KICAD_SEXP || plugin.m_type == IO_MGR::LEGACY;
if( ( aCtl & KICTL_KICAD_ONLY ) && !isKiCad )
continue;
if( ( aCtl & KICTL_NONKICAD_ONLY ) && isKiCad )
continue;
PLUGIN::RELEASER pi( plugin.m_createFunc() );
if( pi->CanReadBoard( aFileName ) )
return plugin.m_type;
}
return IO_MGR::FILE_TYPE_NONE;
}
IO_MGR::PCB_FILE_T IO_MGR::GuessPluginTypeFromLibPath( const wxString& aLibPath )
{
PCB_FILE_T ret = KICAD_SEXP; // default guess, unless detected otherwise.

View File

@ -25,6 +25,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <config.h>
#include <vector>
#include <wx/arrstr.h>
#include <i18n_utility.h>
@ -175,6 +176,11 @@ public:
*/
static PCB_FILE_T EnumFromStr( const wxString& aFileType );
/**
* Return a plugin type given a path for a board file. FILE_TYPE_NONE if the file is not known.
*/
static PCB_FILE_T FindPluginTypeFromBoardPath( const wxString& aFileName, int aCtl = 0 );
/**
* Return a plugin type given a footprint library's libPath.
*/
@ -669,6 +675,10 @@ public:
}
};
#endif
protected:
static bool fileStartsWithPrefix( const wxString& aFilePath, const wxString& aPrefix,
bool aIgnoreWhitespace );
};
#endif // IO_MGR_H_

View File

@ -32,6 +32,8 @@
#include <wx/translation.h>
#include <wx/filename.h>
#include <wx/dir.h>
#include <wx/wfstream.h>
#include <wx/txtstrm.h>
#define FMT_UNIMPLEMENTED wxT( "Plugin \"%s\" does not implement the \"%s\" function." )
@ -291,3 +293,28 @@ void PLUGIN::FootprintLibOptions( STRING_UTF8_MAP* aListToAppendTo ) const
#endif
}
bool PLUGIN::fileStartsWithPrefix( const wxString& aFilePath, const wxString& aPrefix,
bool aIgnoreWhitespace )
{
wxFileInputStream input( aFilePath );
if( input.IsOk() && !input.Eof() )
{
// Find first non-empty line
wxTextInputStream text( input );
wxString line = text.ReadLine();
if( aIgnoreWhitespace )
{
while( line.IsEmpty() )
line = text.ReadLine().Trim( false /*trim from left*/ );
}
if( line.StartsWith( aPrefix ) )
return true;
}
return false;
}

View File

@ -740,6 +740,26 @@ FP_3DMODEL* PCB_PARSER::parse3DModel()
}
bool PCB_PARSER::IsValidBoardHeader()
{
LOCALE_IO toggle;
m_groupInfos.clear();
// See Parse() - FOOTPRINTS can be prefixed with an initial block of single line comments,
// eventually BOARD might be the same
ReadCommentLines();
if( CurTok() != T_LEFT )
return false;
if( NextTok() != T_kicad_pcb)
return false;
return true;
}
BOARD_ITEM* PCB_PARSER::Parse()
{
T token;

View File

@ -113,6 +113,12 @@ public:
*/
wxString GetRequiredVersion();
/**
* Partially parse the input and check if it matches expected header
* @return true if expected header matches
*/
bool IsValidBoardHeader();
private:
///< Convert net code using the mapping table if available,
///< otherwise returns unchanged net code if < 0 or if it's out of range

View File

@ -415,15 +415,18 @@ bool LEGACY_PLUGIN::CanReadBoard( const wxString& aFileName ) const
if( !PLUGIN::CanReadBoard( aFileName ) )
return false;
wxFileInputStream input( aFileName );
FILE_LINE_READER tempReader( aFileName );
if( !input.IsOk() || input.Eof() )
try
{
getVersion( &tempReader );
}
catch( const IO_ERROR& e )
{
return false;
}
wxTextInputStream text( input );
wxString line = text.ReadLine();
return line.StartsWith( wxS( "PCBNEW" ) );
return true;
}
@ -486,7 +489,8 @@ BOARD* LEGACY_PLUGIN::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe,
m_reader = &reader;
m_progressReporter = aProgressReporter;
checkVersion();
m_loading_format_version = getVersion( m_reader );
m_board->SetFileFormatVersionAtLoad( m_loading_format_version );
if( m_progressReporter )
{
@ -618,35 +622,32 @@ void LEGACY_PLUGIN::loadAllSections( bool doAppend )
}
void LEGACY_PLUGIN::checkVersion()
int LEGACY_PLUGIN::getVersion( LINE_READER* aReader )
{
// Read first line and TEST if it is a PCB file format header like this:
// "PCBNEW-BOARD Version 1 ...."
m_reader->ReadLine();
aReader->ReadLine();
char* line = m_reader->Line();
char* line = aReader->Line();
if( !TESTLINE( "PCBNEW-BOARD" ) )
{
THROW_IO_ERROR( wxT( "Unknown file type" ) );
}
int ver = 1; // if sccanf fails
int ver = 1; // if sccanf fails
sscanf( line, "PCBNEW-BOARD Version %d", &ver );
#if !defined(DEBUG)
#if !defined( DEBUG )
if( ver > LEGACY_BOARD_FILE_VERSION )
{
m_error.Printf( _( "File '%s' has an unrecognized version: %d." ),
m_reader->GetSource().GetData(),
ver );
THROW_IO_ERROR( m_error );
THROW_IO_ERROR( wxString::Format( _( "File '%s' has an unrecognized version: %d." ),
aReader->GetSource().GetData(), ver ) );
}
#endif
m_loading_format_version = ver;
m_board->SetFileFormatVersionAtLoad( m_loading_format_version );
return ver;
}

View File

@ -151,7 +151,7 @@ protected:
*/
EDA_ANGLE degParse( const char* aValue, const char** nptrptr = nullptr );
void checkVersion();
static int getVersion( LINE_READER* aReader );
void loadAllSections( bool doAppend );

View File

@ -54,15 +54,12 @@ PCAD_PLUGIN::~PCAD_PLUGIN()
}
const wxString PCAD_PLUGIN::PluginName() const
bool PCAD_PLUGIN::CanReadBoard( const wxString& aFileName ) const
{
return wxT( "P-Cad" );
}
if( !PLUGIN::CanReadBoard( aFileName ) )
return false;
PLUGIN_FILE_DESC PCAD_PLUGIN::GetBoardFileDesc() const
{
return PLUGIN_FILE_DESC( _HKI( "P-Cad 200x ASCII PCB files" ), { "pcb" } );
return fileStartsWithPrefix( aFileName, wxT( "ACCEL_ASCII" ), false );
}

View File

@ -38,9 +38,14 @@ public:
PCAD_PLUGIN();
~PCAD_PLUGIN();
const wxString PluginName() const override;
const wxString PluginName() const { return wxT( "P-Cad" ); }
PLUGIN_FILE_DESC GetBoardFileDesc() const override;
PLUGIN_FILE_DESC GetBoardFileDesc() const
{
return PLUGIN_FILE_DESC( _HKI( "P-Cad 200x ASCII PCB files" ), { "pcb" } );
}
bool CanReadBoard( const wxString& aFileName ) const override;
BOARD* LoadBoard( const wxString& aFileName, BOARD* aAppendToMe,
const STRING_UTF8_MAP* aProperties = nullptr, PROJECT* aProject = nullptr,

View File

@ -68,8 +68,6 @@ using namespace std::placeholders;
// files.cpp
extern bool AskLoadBoardFileName( PCB_EDIT_FRAME* aParent, wxString* aFileName, int aCtl = 0 );
extern IO_MGR::PCB_FILE_T FindBoardPlugin( const wxString& aFileName, int aCtl = 0 );
PCB_CONTROL::PCB_CONTROL() :
PCB_TOOL_BASE( "pcbnew.Control" ),
@ -1018,7 +1016,8 @@ int PCB_CONTROL::AppendBoardFromFile( const TOOL_EVENT& aEvent )
if( !AskLoadBoardFileName( editFrame, &fileName, true ) )
return 1;
IO_MGR::PCB_FILE_T pluginType = FindBoardPlugin( fileName, KICTL_KICAD_ONLY );
IO_MGR::PCB_FILE_T pluginType =
IO_MGR::FindPluginTypeFromBoardPath( fileName, KICTL_KICAD_ONLY );
PLUGIN::RELEASER pi( IO_MGR::PluginFind( pluginType ) );
return AppendBoard( *pi, fileName );
@ -1559,7 +1558,7 @@ int PCB_CONTROL::DdAppendBoard( const TOOL_EVENT& aEvent )
return 1;
wxString filePath = fileName.GetFullPath();
IO_MGR::PCB_FILE_T pluginType = FindBoardPlugin( filePath );
IO_MGR::PCB_FILE_T pluginType = IO_MGR::FindPluginTypeFromBoardPath( filePath );
PLUGIN::RELEASER pi( IO_MGR::PluginFind( pluginType ) );
return AppendBoard( *pi, filePath );

View File

@ -34,6 +34,7 @@ set( QA_PCBNEW_SRCS
test_array_pad_name_provider.cpp
test_board_item.cpp
test_graphics_import_mgr.cpp
test_io_mgr.cpp
test_lset.cpp
test_pns_basics.cpp
test_pad_numbering.cpp

View File

@ -0,0 +1,142 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Roberto Fernandez Bautista <roberto.fer.bau@gmail.com>
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <pcbnew_utils/board_test_utils.h>
#include <pcbnew_utils/board_file_utils.h>
#include <qa_utils/wx_utils/unit_test_utils.h>
#include <pcbnew/io_mgr.h>
BOOST_AUTO_TEST_SUITE( IOMGR )
struct BOARD_PLUGIN_CASE
{
std::string m_case_name;
std::string m_file_rel_path;
IO_MGR::PCB_FILE_T m_expected_type;
};
static const std::vector<BOARD_PLUGIN_CASE> BoardPluginCases = {
//
// FAKE Boards (should return FILE_TYPE_NONE):
//
{
"Fake Board file (KiCad *Legacy* / EAGLE file ext)",
"plugins/fakeboard.brd",
IO_MGR::FILE_TYPE_NONE
},
{
"Fake Board file (KiCad file ext)",
"plugins/fakeboard.kicad_pcb",
IO_MGR::FILE_TYPE_NONE
},
{
"Fake Board file (PCAD file ext)",
"plugins/fakeboard.pcb",
IO_MGR::FILE_TYPE_NONE
},
{
"Fake Board file (CADSTAR file ext)",
"plugins/fakeboard.cpa",
IO_MGR::FILE_TYPE_NONE
},
//
// REAL Boards:
//
{
"Basic KiCad *Legacy* board file",
"plugins/legacy_demos/flat_hierarchy/flat_hierarchy.brd",
IO_MGR::LEGACY
},
{
"Basic KiCad board file",
"complex_hierarchy.kicad_pcb",
IO_MGR::KICAD_SEXP
},
{
"Basic Eagle board file",
"plugins/eagle/Adafruit-AHT20-PCB/Adafruit AHT20 Temperature & Humidity.brd",
IO_MGR::EAGLE
},
{
"Basic PCAD board file",
"plugins/pcad/pcad_4layer_glyph_test_ascii.PCB",
IO_MGR::PCAD
},
{
"Basic CADSTAR board file",
"plugins/cadstar/route_offset/minimal_route_offset_curved_track.cpa",
IO_MGR::CADSTAR_PCB_ARCHIVE
}
// Todo: Add Altium (+derivatives) and Fabmaster tests
};
BOOST_AUTO_TEST_CASE( FindBoardPluginType )
{
for( auto& c : BoardPluginCases )
{
BOOST_TEST_CONTEXT( c.m_case_name )
{
std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + c.m_file_rel_path;
BOOST_CHECK_EQUAL( IO_MGR::FindPluginTypeFromBoardPath( dataPath ),
c.m_expected_type );
// Todo add tests to check if it still works with upper/lower case ext.
// ( FindPluginTypeFromBoardPath should be case insensitive)
}
}
}
BOOST_AUTO_TEST_CASE( CheckCanReadBoard )
{
for( auto& c : BoardPluginCases )
{
BOOST_TEST_CONTEXT( c.m_case_name )
{
std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + c.m_file_rel_path;
auto& pluginEntries = IO_MGR::PLUGIN_REGISTRY::Instance()->AllPlugins();
for( auto& entry : pluginEntries )
{
BOOST_TEST_CONTEXT( entry.m_name )
{
auto plugin = PLUGIN::RELEASER( IO_MGR::PluginFind( entry.m_type ) );
bool expectValidHeader = c.m_expected_type == entry.m_type;
BOOST_CHECK_EQUAL( plugin->CanReadBoard( dataPath ), expectValidHeader );
}
}
}
}
}
BOOST_AUTO_TEST_SUITE_END()