554 lines
18 KiB
C++
554 lines
18 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2023 Alex Shvartzkop <dudesuchamazing@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 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 <io/easyeda/easyeda_parser_structs.h>
|
|
#include <pcb_io/easyeda/pcb_io_easyeda_plugin.h>
|
|
#include <pcb_io/easyeda/pcb_io_easyeda_parser.h>
|
|
#include <pcb_io/pcb_io.h>
|
|
|
|
#include <font/fontconfig.h>
|
|
#include <progress_reporter.h>
|
|
#include <common.h>
|
|
#include <macros.h>
|
|
#include <board.h>
|
|
#include <footprint.h>
|
|
#include <board_design_settings.h>
|
|
#include <reporter.h>
|
|
|
|
#include <wx/log.h>
|
|
#include <wx/wfstream.h>
|
|
#include <wx/zipstrm.h>
|
|
#include <wx/stdstream.h>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
#include <core/map_helpers.h>
|
|
|
|
|
|
PCB_IO_EASYEDA::PCB_IO_EASYEDA() : PCB_IO( wxS( "EasyEDA (JLCEDA) Standard" ) )
|
|
{
|
|
}
|
|
|
|
|
|
PCB_IO_EASYEDA::~PCB_IO_EASYEDA()
|
|
{
|
|
}
|
|
|
|
|
|
static bool FindBoardInStream( const wxString& aName, wxInputStream& aStream, nlohmann::json& aOut,
|
|
EASYEDA::DOCUMENT& aDoc )
|
|
{
|
|
if( aName.Lower().EndsWith( wxS( ".json" ) ) )
|
|
{
|
|
wxStdInputStream sin( aStream );
|
|
nlohmann::json js = nlohmann::json::parse( sin, nullptr, false );
|
|
|
|
if( js.is_discarded() )
|
|
return false;
|
|
|
|
EASYEDA::DOCUMENT doc = js.get<EASYEDA::DOCUMENT>();
|
|
|
|
if( doc.head.docType == EASYEDA::DOC_TYPE::PCB
|
|
|| doc.head.docType == EASYEDA::DOC_TYPE::PCB_MODULE
|
|
|| doc.head.docType == EASYEDA::DOC_TYPE::PCB_COMPONENT )
|
|
{
|
|
aOut = js;
|
|
aDoc = doc;
|
|
return true;
|
|
}
|
|
}
|
|
else if( aName.Lower().EndsWith( wxS( ".zip" ) ) )
|
|
{
|
|
std::shared_ptr<wxZipEntry> entry;
|
|
wxZipInputStream zip( aStream );
|
|
|
|
if( !zip.IsOk() )
|
|
return false;
|
|
|
|
while( entry.reset( zip.GetNextEntry() ), entry.get() != NULL )
|
|
{
|
|
wxString name = entry->GetName();
|
|
|
|
if( FindBoardInStream( name, zip, aOut, aDoc ) )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool PCB_IO_EASYEDA::CanReadBoard( const wxString& aFileName ) const
|
|
{
|
|
if( !PCB_IO::CanReadBoard( aFileName ) )
|
|
return false;
|
|
|
|
try
|
|
{
|
|
wxFFileInputStream in( aFileName );
|
|
nlohmann::json js;
|
|
EASYEDA::DOCUMENT doc;
|
|
|
|
return FindBoardInStream( aFileName, in, js, doc );
|
|
}
|
|
catch( nlohmann::json::exception& )
|
|
{
|
|
}
|
|
catch( std::exception& )
|
|
{
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool PCB_IO_EASYEDA::CanReadFootprint( const wxString& aFileName ) const
|
|
{
|
|
return CanReadBoard( aFileName );
|
|
}
|
|
|
|
|
|
bool PCB_IO_EASYEDA::CanReadLibrary( const wxString& aFileName ) const
|
|
{
|
|
return CanReadBoard( aFileName );
|
|
}
|
|
|
|
|
|
BOARD* PCB_IO_EASYEDA::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe,
|
|
const STRING_UTF8_MAP* aProperties, PROJECT* aProject )
|
|
{
|
|
m_loadedFootprints.clear();
|
|
|
|
m_props = aProperties;
|
|
m_board = aAppendToMe ? aAppendToMe : new BOARD();
|
|
|
|
fontconfig::FONTCONFIG::SetReporter( &WXLOG_REPORTER::GetInstance() );
|
|
|
|
// Give the filename to the board if it's new
|
|
if( !aAppendToMe )
|
|
m_board->SetFileName( aFileName );
|
|
|
|
if( m_progressReporter )
|
|
{
|
|
m_progressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) );
|
|
|
|
if( !m_progressReporter->KeepRefreshing() )
|
|
THROW_IO_ERROR( _( "Open cancelled by user." ) );
|
|
}
|
|
|
|
PCB_IO_EASYEDA_PARSER parser( nullptr );
|
|
|
|
try
|
|
{
|
|
wxFFileInputStream in( aFileName );
|
|
nlohmann::json js;
|
|
EASYEDA::DOCUMENT doc;
|
|
|
|
if( !FindBoardInStream( aFileName, in, js, doc ) )
|
|
{
|
|
THROW_IO_ERROR(
|
|
wxString::Format( _( "Unable to find a valid board in '%s'" ), aFileName ) );
|
|
}
|
|
|
|
EASYEDA::DOCUMENT_PCB pcbDoc = js.get<EASYEDA::DOCUMENT_PCB>();
|
|
|
|
const int innerStart = 21;
|
|
const int innerEnd = 52;
|
|
|
|
int maxLayer = innerStart;
|
|
std::map<PCB_LAYER_ID, wxString> layerNames;
|
|
|
|
for( const wxString& layerLine : pcbDoc.layers )
|
|
{
|
|
wxArrayString parts = wxSplit( layerLine, '~', '\0' );
|
|
int layerId = wxAtoi( parts[0] );
|
|
wxString layerName = parts[1];
|
|
wxString layerColor = parts[2];
|
|
wxString visible = parts[3];
|
|
wxString active = parts[4];
|
|
bool enabled = parts[5] != wxS( "false" );
|
|
|
|
if( layerId >= innerStart && layerId <= innerEnd && enabled )
|
|
maxLayer = layerId + 1;
|
|
|
|
layerNames[parser.LayerToKi( parts[0] )] = layerName;
|
|
}
|
|
|
|
m_board->SetCopperLayerCount( 2 + maxLayer - innerStart );
|
|
|
|
for( auto& [klayer, name] : layerNames )
|
|
m_board->SetLayerName( klayer, name );
|
|
|
|
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
|
|
std::shared_ptr<NETCLASS> defNetclass = bds.m_NetSettings->m_DefaultNetClass;
|
|
|
|
if( pcbDoc.DRCRULE )
|
|
{
|
|
std::map<wxString, nlohmann::json>& rules = *pcbDoc.DRCRULE;
|
|
|
|
if( auto defRules = get_opt( rules, "Default" ) )
|
|
{
|
|
wxString key;
|
|
|
|
key = wxS( "trackWidth" );
|
|
if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() )
|
|
{
|
|
double val = parser.ScaleSize( defRules->at( key ) );
|
|
defNetclass->SetTrackWidth( val );
|
|
}
|
|
|
|
key = wxS( "clearance" );
|
|
if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() )
|
|
{
|
|
double val = parser.ScaleSize( defRules->at( key ) );
|
|
defNetclass->SetClearance( val );
|
|
}
|
|
|
|
key = wxS( "viaHoleD" );
|
|
if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() )
|
|
{
|
|
double val = parser.ScaleSize( defRules->at( key ) );
|
|
|
|
defNetclass->SetViaDrill( val );
|
|
}
|
|
|
|
key = wxS( "viaHoleDiameter" ); // Yes, this is via diameter, not drill diameter
|
|
if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() )
|
|
{
|
|
double val = parser.ScaleSize( defRules->at( key ) );
|
|
defNetclass->SetViaDiameter( val );
|
|
}
|
|
}
|
|
}
|
|
|
|
VECTOR2D origin( doc.head.x, doc.head.y );
|
|
parser.ParseBoard( m_board, origin, m_loadedFootprints, doc.shape );
|
|
|
|
// Center the board
|
|
BOX2I outlineBbox = m_board->ComputeBoundingBox( true, false );
|
|
PAGE_INFO pageInfo = m_board->GetPageSettings();
|
|
|
|
VECTOR2D pageCenter( pcbIUScale.MilsToIU( pageInfo.GetWidthMils() / 2 ),
|
|
pcbIUScale.MilsToIU( pageInfo.GetHeightMils() / 2 ) );
|
|
|
|
VECTOR2D offset = pageCenter - outlineBbox.GetCenter();
|
|
|
|
int alignGrid = pcbIUScale.mmToIU( 10 );
|
|
offset.x = KiROUND( offset.x / alignGrid ) * alignGrid;
|
|
offset.y = KiROUND( offset.y / alignGrid ) * alignGrid;
|
|
|
|
m_board->Move( offset );
|
|
bds.SetAuxOrigin( offset );
|
|
|
|
return m_board;
|
|
}
|
|
catch( nlohmann::json::exception& e )
|
|
{
|
|
THROW_IO_ERROR(
|
|
wxString::Format( _( "Error loading board '%s': %s" ), aFileName, e.what() ) );
|
|
}
|
|
catch( std::exception& e )
|
|
{
|
|
THROW_IO_ERROR(
|
|
wxString::Format( _( "Error loading board '%s': %s" ), aFileName, e.what() ) );
|
|
}
|
|
|
|
return m_board;
|
|
}
|
|
|
|
|
|
long long PCB_IO_EASYEDA::GetLibraryTimestamp( const wxString& aLibraryPath ) const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PCB_IO_EASYEDA::FootprintEnumerate( wxArrayString& aFootprintNames,
|
|
const wxString& aLibraryPath, bool aBestEfforts,
|
|
const STRING_UTF8_MAP* aProperties )
|
|
{
|
|
try
|
|
{
|
|
wxFFileInputStream in( aLibraryPath );
|
|
nlohmann::json js;
|
|
EASYEDA::DOCUMENT doc;
|
|
|
|
if( !FindBoardInStream( aLibraryPath, in, js, doc ) )
|
|
{
|
|
THROW_IO_ERROR( wxString::Format( _( "Unable to find valid footprints in '%s'" ),
|
|
aLibraryPath ) );
|
|
}
|
|
|
|
if( doc.head.docType == EASYEDA::DOC_TYPE::PCB
|
|
|| doc.head.docType == EASYEDA::DOC_TYPE::PCB_MODULE )
|
|
{
|
|
for( wxString shap : doc.shape )
|
|
{
|
|
shap.Replace( wxS( "#@$" ), "\n" );
|
|
wxArrayString parts = wxSplit( shap, '\n', '\0' );
|
|
|
|
if( parts.size() < 1 )
|
|
continue;
|
|
|
|
wxArrayString paramsRoot = wxSplit( parts[0], '~', '\0' );
|
|
|
|
if( paramsRoot.size() < 1 )
|
|
continue;
|
|
|
|
wxString rootType = paramsRoot[0];
|
|
|
|
if( rootType == wxS( "LIB" ) )
|
|
{
|
|
if( paramsRoot.size() < 4 )
|
|
continue;
|
|
|
|
wxString packageName = wxString::Format( wxS( "Unknown_%s_%s" ), paramsRoot[1],
|
|
paramsRoot[2] );
|
|
|
|
wxArrayString paramParts = wxSplit( paramsRoot[3], '`', '\0' );
|
|
|
|
std::map<wxString, wxString> paramMap;
|
|
|
|
for( int i = 1; i < paramParts.size(); i += 2 )
|
|
{
|
|
wxString key = paramParts[i - 1];
|
|
wxString value = paramParts[i];
|
|
|
|
if( key == wxS( "package" ) )
|
|
packageName = value;
|
|
|
|
paramMap[key] = value;
|
|
}
|
|
|
|
aFootprintNames.Add( packageName );
|
|
}
|
|
}
|
|
}
|
|
else if( doc.head.docType == EASYEDA::DOC_TYPE::PCB_COMPONENT )
|
|
{
|
|
EASYEDA::DOCUMENT_PCB pcbDoc = js.get<EASYEDA::DOCUMENT_PCB>();
|
|
|
|
wxString packageName = wxString::Format( wxS( "Unknown_%s" ),
|
|
pcbDoc.uuid.value_or( wxS( "Unknown" ) ) );
|
|
|
|
std::optional<std::map<wxString, wxString>> c_para;
|
|
|
|
if( pcbDoc.c_para )
|
|
c_para = pcbDoc.c_para;
|
|
else if( doc.head.c_para )
|
|
c_para = doc.head.c_para;
|
|
|
|
if( c_para )
|
|
{
|
|
packageName = get_def( *c_para, wxS( "package" ), packageName );
|
|
}
|
|
|
|
aFootprintNames.Add( packageName );
|
|
}
|
|
}
|
|
catch( nlohmann::json::exception& e )
|
|
{
|
|
THROW_IO_ERROR( wxString::Format( _( "Error enumerating footprints in library '%s': %s" ),
|
|
aLibraryPath, e.what() ) );
|
|
}
|
|
catch( std::exception& e )
|
|
{
|
|
THROW_IO_ERROR( wxString::Format( _( "Error enumerating footprints in library '%s': %s" ),
|
|
aLibraryPath, e.what() ) );
|
|
}
|
|
}
|
|
|
|
|
|
FOOTPRINT* PCB_IO_EASYEDA::FootprintLoad( const wxString& aLibraryPath,
|
|
const wxString& aFootprintName, bool aKeepUUID,
|
|
const STRING_UTF8_MAP* aProperties )
|
|
{
|
|
fontconfig::FONTCONFIG::SetReporter( nullptr );
|
|
|
|
PCB_IO_EASYEDA_PARSER parser( nullptr );
|
|
|
|
m_loadedFootprints.clear();
|
|
|
|
try
|
|
{
|
|
wxFFileInputStream in( aLibraryPath );
|
|
nlohmann::json js;
|
|
EASYEDA::DOCUMENT doc;
|
|
|
|
if( !FindBoardInStream( aLibraryPath, in, js, doc ) )
|
|
{
|
|
THROW_IO_ERROR( wxString::Format( _( "Unable to find valid footprints in '%s'" ),
|
|
aLibraryPath ) );
|
|
}
|
|
|
|
if( doc.head.docType == EASYEDA::DOC_TYPE::PCB
|
|
|| doc.head.docType == EASYEDA::DOC_TYPE::PCB_MODULE )
|
|
{
|
|
for( wxString shap : doc.shape )
|
|
{
|
|
if( !shap.Contains( wxS( "LIB" ) ) )
|
|
continue;
|
|
|
|
shap.Replace( wxS( "#@$" ), "\n" );
|
|
wxArrayString parts = wxSplit( shap, '\n', '\0' );
|
|
|
|
if( parts.size() < 1 )
|
|
continue;
|
|
|
|
wxArrayString paramsRoot = wxSplit( parts[0], '~', '\0' );
|
|
|
|
if( paramsRoot.size() < 1 )
|
|
continue;
|
|
|
|
wxString rootType = paramsRoot[0];
|
|
|
|
if( rootType == wxS( "LIB" ) )
|
|
{
|
|
if( paramsRoot.size() < 4 )
|
|
continue;
|
|
|
|
VECTOR2D origin( parser.Convert( paramsRoot[1] ),
|
|
parser.Convert( paramsRoot[2] ) );
|
|
|
|
wxString packageName = wxString::Format( wxS( "Unknown_%s_%s" ), paramsRoot[1],
|
|
paramsRoot[2] );
|
|
|
|
wxArrayString paramParts = wxSplit( paramsRoot[3], '`', '\0' );
|
|
|
|
std::map<wxString, wxString> paramMap;
|
|
|
|
for( int i = 1; i < paramParts.size(); i += 2 )
|
|
{
|
|
wxString key = paramParts[i - 1];
|
|
wxString value = paramParts[i];
|
|
|
|
if( key == wxS( "package" ) )
|
|
packageName = value;
|
|
|
|
paramMap[key] = value;
|
|
}
|
|
|
|
EDA_ANGLE orientation;
|
|
if( !paramsRoot[4].IsEmpty() )
|
|
orientation = EDA_ANGLE( parser.Convert( paramsRoot[4] ), DEGREES_T );
|
|
|
|
int layer = 1;
|
|
|
|
if( !paramsRoot[7].IsEmpty() )
|
|
layer = parser.Convert( paramsRoot[7] );
|
|
|
|
if( packageName == aFootprintName )
|
|
{
|
|
parts.RemoveAt( 0 );
|
|
|
|
FOOTPRINT* footprint =
|
|
parser.ParseFootprint( origin, orientation, layer, nullptr,
|
|
paramMap, m_loadedFootprints, parts );
|
|
|
|
if( !footprint )
|
|
return nullptr;
|
|
|
|
footprint->Reference().SetPosition( VECTOR2I() );
|
|
footprint->Reference().SetTextAngle( ANGLE_0 );
|
|
footprint->Reference().SetVisible( true );
|
|
|
|
footprint->Value().SetPosition( VECTOR2I() );
|
|
footprint->Value().SetTextAngle( ANGLE_0 );
|
|
footprint->Value().SetVisible( true );
|
|
|
|
footprint->AutoPositionFields();
|
|
|
|
return footprint;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if( doc.head.docType == EASYEDA::DOC_TYPE::PCB_COMPONENT )
|
|
{
|
|
EASYEDA::DOCUMENT_PCB pcbDoc = js.get<EASYEDA::DOCUMENT_PCB>();
|
|
|
|
wxString packageName = wxString::Format( wxS( "Unknown_%s" ),
|
|
pcbDoc.uuid.value_or( wxS( "Unknown" ) ) );
|
|
|
|
std::optional<std::map<wxString, wxString>> c_para;
|
|
|
|
if( pcbDoc.c_para )
|
|
c_para = pcbDoc.c_para;
|
|
else if( doc.head.c_para )
|
|
c_para = doc.head.c_para;
|
|
|
|
if( c_para )
|
|
{
|
|
packageName = get_def( *c_para, wxS( "package" ), packageName );
|
|
|
|
if( packageName != aFootprintName )
|
|
return nullptr;
|
|
|
|
VECTOR2D origin( doc.head.x, doc.head.y );
|
|
|
|
FOOTPRINT* footprint = parser.ParseFootprint(
|
|
origin, ANGLE_0, F_Cu, nullptr, *c_para, m_loadedFootprints, doc.shape );
|
|
|
|
if( !footprint )
|
|
return nullptr;
|
|
|
|
footprint->Reference().SetPosition( VECTOR2I() );
|
|
footprint->Reference().SetTextAngle( ANGLE_0 );
|
|
footprint->Reference().SetVisible( true );
|
|
|
|
footprint->Value().SetPosition( VECTOR2I() );
|
|
footprint->Value().SetTextAngle( ANGLE_0 );
|
|
footprint->Value().SetVisible( true );
|
|
|
|
footprint->AutoPositionFields();
|
|
|
|
return footprint;
|
|
}
|
|
}
|
|
}
|
|
catch( nlohmann::json::exception& e )
|
|
{
|
|
THROW_IO_ERROR( wxString::Format( _( "Error reading footprint '%s' from library '%s': %s" ),
|
|
aFootprintName, aLibraryPath, e.what() ) );
|
|
}
|
|
catch( std::exception& e )
|
|
{
|
|
THROW_IO_ERROR( wxString::Format( _( "Error reading footprint '%s' from library '%s': %s" ),
|
|
aFootprintName, aLibraryPath, e.what() ) );
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
std::vector<FOOTPRINT*> PCB_IO_EASYEDA::GetImportedCachedLibraryFootprints()
|
|
{
|
|
std::vector<FOOTPRINT*> result;
|
|
|
|
for( auto& [fpUuid, footprint] : m_loadedFootprints )
|
|
{
|
|
result.push_back( static_cast<FOOTPRINT*>( footprint->Clone() ) );
|
|
}
|
|
|
|
return result;
|
|
} |