1150 lines
39 KiB
C++
1150 lines
39 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 "pcb_io_easyeda_parser.h"
|
|
|
|
#include <memory>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
#include <core/map_helpers.h>
|
|
#include <core/json_serializers.h>
|
|
#include <string_utils.h>
|
|
|
|
#include <wx/log.h>
|
|
|
|
#include <font/font.h>
|
|
#include <footprint.h>
|
|
#include <progress_reporter.h>
|
|
#include <board.h>
|
|
#include <board_design_settings.h>
|
|
#include <bezier_curves.h>
|
|
#include <pcb_group.h>
|
|
#include <pcb_track.h>
|
|
#include <pcb_shape.h>
|
|
#include <pcb_text.h>
|
|
#include <zone.h>
|
|
#include <pad.h>
|
|
#include <project.h>
|
|
#include <fix_board_shape.h>
|
|
|
|
|
|
static const wxString DIRECT_MODEL_UUID_KEY = wxS( "JLC_3DModel" );
|
|
static const wxString MODEL_SIZE_KEY = wxS( "JLC_3D_Size" );
|
|
|
|
static const int SHAPE_JOIN_DISTANCE = pcbIUScale.mmToIU( 1.5 );
|
|
static const VECTOR2I HIDDEN_TEXT_SIZE( pcbIUScale.mmToIU( 0.5 ), pcbIUScale.mmToIU( 0.5 ) );
|
|
|
|
|
|
PCB_IO_EASYEDA_PARSER::PCB_IO_EASYEDA_PARSER( PROGRESS_REPORTER* aProgressReporter )
|
|
{
|
|
}
|
|
|
|
PCB_IO_EASYEDA_PARSER::~PCB_IO_EASYEDA_PARSER()
|
|
{
|
|
}
|
|
|
|
PCB_LAYER_ID PCB_IO_EASYEDA_PARSER::LayerToKi( const wxString& aLayer )
|
|
{
|
|
int elayer = wxAtoi( aLayer );
|
|
|
|
switch( elayer )
|
|
{
|
|
case 1: return F_Cu;
|
|
case 2: return B_Cu;
|
|
case 3: return F_SilkS;
|
|
case 4: return B_SilkS;
|
|
case 5: return F_Paste;
|
|
case 6: return B_Paste;
|
|
case 7: return F_Mask;
|
|
case 8: return B_Mask;
|
|
/*case 9: return UNDEFINED_LAYER;*/ // Ratsnest
|
|
case 10: return Edge_Cuts;
|
|
case 11: return Eco1_User;
|
|
case 12: return Dwgs_User;
|
|
case 13: return F_Fab;
|
|
case 14: return B_Fab;
|
|
case 15: return Eco2_User;
|
|
|
|
case 19: return User_2; // 3D model
|
|
|
|
case 21: return In1_Cu;
|
|
case 22: return In2_Cu;
|
|
case 23: return In3_Cu;
|
|
case 24: return In4_Cu;
|
|
case 25: return In5_Cu;
|
|
case 26: return In6_Cu;
|
|
case 27: return In7_Cu;
|
|
case 28: return In8_Cu;
|
|
case 29: return In9_Cu;
|
|
case 30: return In10_Cu;
|
|
case 31: return In11_Cu;
|
|
case 32: return In12_Cu;
|
|
case 33: return In13_Cu;
|
|
case 34: return In14_Cu;
|
|
case 35: return In15_Cu;
|
|
case 36: return In16_Cu;
|
|
case 37: return In17_Cu;
|
|
case 38: return In18_Cu;
|
|
case 39: return In19_Cu;
|
|
case 40: return In20_Cu;
|
|
case 41: return In21_Cu;
|
|
case 42: return In22_Cu;
|
|
case 43: return In23_Cu;
|
|
case 44: return In24_Cu;
|
|
case 45: return In25_Cu;
|
|
case 46: return In26_Cu;
|
|
case 47: return In27_Cu;
|
|
case 48: return In28_Cu;
|
|
case 49: return In29_Cu;
|
|
case 50: return In30_Cu;
|
|
|
|
case 99: return User_3;
|
|
case 100: return User_4;
|
|
case 101: return User_5;
|
|
|
|
default: break;
|
|
}
|
|
|
|
return User_1;
|
|
}
|
|
|
|
|
|
static LIB_ID EasyEdaToKiCadLibID( const wxString& aLibName, const wxString& aLibReference )
|
|
{
|
|
wxString libReference = EscapeString( aLibReference, CTX_LIBID );
|
|
|
|
wxString key = !aLibName.empty() ? ( aLibName + ':' + libReference ) : libReference;
|
|
|
|
LIB_ID libId;
|
|
libId.Parse( key, true );
|
|
|
|
return libId;
|
|
}
|
|
|
|
|
|
void PCB_IO_EASYEDA_PARSER::ParseToBoardItemContainer(
|
|
BOARD_ITEM_CONTAINER* aContainer, BOARD* aParent, std::map<wxString, wxString> paramMap,
|
|
std::map<wxString, std::unique_ptr<FOOTPRINT>>& aFootprintMap, wxArrayString aShapes )
|
|
{
|
|
// TODO: make this path configurable?
|
|
const wxString easyedaModelDir = wxS( "EASYEDA_MODELS" );
|
|
wxString kicadModelPrefix = wxS( "${KIPRJMOD}/" ) + easyedaModelDir + wxS( "/" );
|
|
|
|
BOARD* board = aParent ? aParent : dynamic_cast<BOARD*>( aContainer );
|
|
FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aContainer );
|
|
|
|
auto getOrAddNetItem = [&]( const wxString& aNetName ) -> NETINFO_ITEM*
|
|
{
|
|
if( !board )
|
|
return nullptr;
|
|
|
|
if( aNetName.empty() )
|
|
return nullptr;
|
|
|
|
if( NETINFO_ITEM* item = board->FindNet( aNetName ) )
|
|
{
|
|
return item;
|
|
}
|
|
else
|
|
{
|
|
item = new NETINFO_ITEM( board, aNetName, board->GetNetCount() + 1 );
|
|
board->Add( item, ADD_MODE::APPEND );
|
|
return item;
|
|
}
|
|
};
|
|
|
|
if( footprint )
|
|
{
|
|
// TODO: library name
|
|
LIB_ID fpID = EasyEdaToKiCadLibID( wxEmptyString, paramMap[wxS( "package" )] );
|
|
footprint->SetFPID( fpID );
|
|
}
|
|
|
|
for( wxString shape : aShapes )
|
|
{
|
|
wxArrayString arr = wxSplit( shape, '~', '\0' );
|
|
|
|
wxString elType = arr[0];
|
|
if( elType == wxS( "LIB" ) )
|
|
{
|
|
shape.Replace( wxS( "#@$" ), "\n" );
|
|
wxArrayString parts = wxSplit( shape, '\n', '\0' );
|
|
|
|
if( parts.size() < 1 )
|
|
continue;
|
|
|
|
wxArrayString paramsRoot = wxSplit( parts[0], '~', '\0' );
|
|
|
|
if( paramsRoot.size() < 4 )
|
|
continue;
|
|
|
|
VECTOR2D fpOrigin( Convert( paramsRoot[1] ), Convert( paramsRoot[2] ) );
|
|
|
|
wxString packageName =
|
|
wxString::Format( wxS( "Unknown_%s_%s" ), paramsRoot[1], paramsRoot[2] );
|
|
|
|
wxArrayString paramParts = wxSplit( paramsRoot[3], '`', '\0' );
|
|
|
|
EDA_ANGLE orientation;
|
|
if( !paramsRoot[4].IsEmpty() )
|
|
orientation = EDA_ANGLE( Convert( paramsRoot[4] ), DEGREES_T ); // Already applied
|
|
|
|
int layer = 1;
|
|
|
|
if( !paramsRoot[7].IsEmpty() )
|
|
layer = Convert( paramsRoot[7] );
|
|
|
|
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;
|
|
}
|
|
|
|
parts.RemoveAt( 0 );
|
|
|
|
VECTOR2D pcbOrigin = m_relOrigin;
|
|
FOOTPRINT* fp = ParseFootprint( fpOrigin, orientation, layer, board, paramMap,
|
|
aFootprintMap, parts );
|
|
|
|
if( !fp )
|
|
continue;
|
|
|
|
m_relOrigin = pcbOrigin;
|
|
|
|
fp->Move( RelPos( fpOrigin ) );
|
|
|
|
aContainer->Add( fp, ADD_MODE::APPEND );
|
|
}
|
|
else if( elType == wxS( "TRACK" ) )
|
|
{
|
|
double width = ConvertSize( arr[1] );
|
|
PCB_LAYER_ID layer = LayerToKi( arr[2] );
|
|
wxString netname = arr[3];
|
|
wxArrayString data = wxSplit( arr[4], ' ', '\0' );
|
|
|
|
for( int i = 3; i < data.size(); i += 2 )
|
|
{
|
|
VECTOR2D start, end;
|
|
start.x = RelPosX( data[i - 3] );
|
|
start.y = RelPosY( data[i - 2] );
|
|
end.x = RelPosX( data[i - 1] );
|
|
end.y = RelPosY( data[i] );
|
|
|
|
if( !footprint && IsCopperLayer( layer ) )
|
|
{
|
|
std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( aContainer );
|
|
|
|
track->SetLayer( layer );
|
|
track->SetWidth( width );
|
|
track->SetStart( start );
|
|
track->SetEnd( end );
|
|
track->SetNet( getOrAddNetItem( netname ) );
|
|
|
|
aContainer->Add( track.release(), ADD_MODE::APPEND );
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<PCB_SHAPE> seg =
|
|
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::SEGMENT );
|
|
|
|
seg->SetLayer( layer );
|
|
seg->SetWidth( width );
|
|
seg->SetStart( start );
|
|
seg->SetEnd( end );
|
|
|
|
aContainer->Add( seg.release(), ADD_MODE::APPEND );
|
|
}
|
|
}
|
|
}
|
|
else if( elType == wxS( "CIRCLE" ) )
|
|
{
|
|
std::unique_ptr<PCB_SHAPE> shape =
|
|
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::CIRCLE );
|
|
|
|
double width = ConvertSize( arr[4] );
|
|
shape->SetWidth( width );
|
|
|
|
PCB_LAYER_ID layer = LayerToKi( arr[5] );
|
|
shape->SetLayer( layer );
|
|
|
|
VECTOR2D center;
|
|
center.x = RelPosX( arr[1] );
|
|
center.y = RelPosY( arr[2] );
|
|
|
|
double radius = ConvertSize( arr[3] );
|
|
|
|
shape->SetCenter( center );
|
|
shape->SetEnd( center + VECTOR2I( radius, 0 ) );
|
|
|
|
if( IsCopperLayer( layer ) )
|
|
shape->SetNet( getOrAddNetItem( arr[8] ) );
|
|
|
|
aContainer->Add( shape.release(), ADD_MODE::APPEND );
|
|
}
|
|
else if( elType == wxS( "RECT" ) )
|
|
{
|
|
std::unique_ptr<PCB_SHAPE> shape =
|
|
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::RECTANGLE );
|
|
|
|
double width = ConvertSize( arr[8] );
|
|
shape->SetWidth( width );
|
|
|
|
PCB_LAYER_ID layer = LayerToKi( arr[5] );
|
|
shape->SetLayer( layer );
|
|
|
|
bool filled = arr[9] != wxS( "none" );
|
|
shape->SetFilled( filled );
|
|
|
|
VECTOR2D start;
|
|
start.x = RelPosX( arr[1] );
|
|
start.y = RelPosY( arr[2] );
|
|
|
|
VECTOR2D size;
|
|
size.x = ConvertSize( arr[3] );
|
|
size.y = ConvertSize( arr[4] );
|
|
|
|
shape->SetStart( start );
|
|
shape->SetEnd( start + size );
|
|
|
|
if( IsCopperLayer( layer ) )
|
|
shape->SetNet( getOrAddNetItem( arr[11] ) );
|
|
|
|
aContainer->Add( shape.release(), ADD_MODE::APPEND );
|
|
}
|
|
else if( elType == wxS( "ARC" ) )
|
|
{
|
|
std::unique_ptr<PCB_SHAPE> shape =
|
|
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::ARC );
|
|
|
|
double width = ConvertSize( arr[1] );
|
|
shape->SetWidth( width );
|
|
|
|
PCB_LAYER_ID layer = LayerToKi( arr[2] );
|
|
shape->SetLayer( layer );
|
|
|
|
if( IsCopperLayer( layer ) )
|
|
shape->SetNet( getOrAddNetItem( arr[3] ) );
|
|
|
|
VECTOR2D start, end;
|
|
VECTOR2D rad( 10, 10 );
|
|
bool isFar = false;
|
|
bool cw = false;
|
|
|
|
int pos = 0;
|
|
wxString data = arr[4];
|
|
auto readNumber = [&]( wxString& aOut )
|
|
{
|
|
wxUniChar ch = data[pos];
|
|
|
|
while( ch == ' ' || ch == ',' )
|
|
ch = data[++pos];
|
|
|
|
while( isdigit( ch ) || ch == '.' || ch == '-' )
|
|
{
|
|
aOut += ch;
|
|
pos++;
|
|
|
|
if( pos == data.size() )
|
|
break;
|
|
|
|
ch = data[pos];
|
|
}
|
|
};
|
|
|
|
do
|
|
{
|
|
wxUniChar sym = data[pos++];
|
|
|
|
if( sym == 'M' )
|
|
{
|
|
wxString xStr, yStr;
|
|
readNumber( xStr );
|
|
readNumber( yStr );
|
|
|
|
start = VECTOR2D( Convert( xStr ), Convert( yStr ) );
|
|
}
|
|
else if( sym == 'A' )
|
|
{
|
|
wxString radX, radY, unknown, farFlag, cwFlag, endX, endY;
|
|
readNumber( radX );
|
|
readNumber( radY );
|
|
readNumber( unknown );
|
|
readNumber( farFlag );
|
|
readNumber( cwFlag );
|
|
readNumber( endX );
|
|
readNumber( endY );
|
|
|
|
isFar = farFlag == wxS( "1" );
|
|
cw = cwFlag == wxS( "1" );
|
|
rad = VECTOR2D( Convert( radX ), Convert( radY ) );
|
|
end = VECTOR2D( Convert( endX ), Convert( endY ) );
|
|
}
|
|
} while( pos < data.size() );
|
|
|
|
VECTOR2D delta = end - start;
|
|
|
|
double d = delta.EuclideanNorm();
|
|
double h = sqrt( std::max( 0.0, rad.x * rad.x - d * d / 4 ) );
|
|
|
|
//( !far && cw ) => h
|
|
//( far && cw ) => -h
|
|
//( !far && !cw ) => -h
|
|
//( far && !cw ) => h
|
|
VECTOR2D arcCenter =
|
|
start + delta / 2 + delta.Perpendicular().Resize( ( isFar ^ cw ) ? h : -h );
|
|
|
|
if( !cw )
|
|
std::swap( start, end );
|
|
|
|
shape->SetStart( RelPos( start ) );
|
|
shape->SetEnd( RelPos( end ) );
|
|
shape->SetCenter( RelPos( arcCenter ) );
|
|
|
|
aContainer->Add( shape.release(), ADD_MODE::APPEND );
|
|
}
|
|
else if( elType == wxS( "DIMENSION" ) )
|
|
{
|
|
PCB_LAYER_ID layer = LayerToKi( arr[1] );
|
|
double lineWidth = ConvertSize( arr[7] );
|
|
wxString shapeData = arr[2].Trim();
|
|
//double textHeight = !arr[4].IsEmpty() ? ConvertSize( arr[4] ) : 0;
|
|
|
|
std::vector<SHAPE_LINE_CHAIN> lineChains =
|
|
ParseLineChains( shapeData, pcbIUScale.mmToIU( 0.01 ), false );
|
|
|
|
std::unique_ptr<PCB_GROUP> group = std::make_unique<PCB_GROUP>( aContainer );
|
|
group->SetName( wxS( "Dimension" ) );
|
|
|
|
for( const SHAPE_LINE_CHAIN& chain : lineChains )
|
|
{
|
|
for( int segId = 0; segId < chain.SegmentCount(); segId++ )
|
|
{
|
|
SEG seg = chain.CSegment( segId );
|
|
|
|
std::unique_ptr<PCB_SHAPE> shape =
|
|
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::SEGMENT );
|
|
|
|
shape->SetLayer( layer );
|
|
shape->SetWidth( lineWidth );
|
|
shape->SetStart( seg.A );
|
|
shape->SetEnd( seg.B );
|
|
|
|
group->AddItem( shape.get() );
|
|
|
|
aContainer->Add( shape.release(), ADD_MODE::APPEND );
|
|
}
|
|
}
|
|
|
|
aContainer->Add( group.release(), ADD_MODE::APPEND );
|
|
}
|
|
else if( elType == wxS( "SOLIDREGION" ) )
|
|
{
|
|
wxString layer = arr[1];
|
|
|
|
SHAPE_POLY_SET polySet =
|
|
ParseLineChains( arr[3].Trim(), pcbIUScale.mmToIU( 0.01 ), true );
|
|
|
|
if( layer == wxS( "11" ) ) // Multi-layer (board cutout)
|
|
{
|
|
for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
|
|
{
|
|
std::unique_ptr<PCB_SHAPE> shape =
|
|
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::POLY );
|
|
|
|
shape->SetLayer( Edge_Cuts );
|
|
shape->SetFilled( false );
|
|
shape->SetWidth( pcbIUScale.mmToIU( 0.1 ) );
|
|
shape->SetPolyShape( poly );
|
|
|
|
aContainer->Add( shape.release(), ADD_MODE::APPEND );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aContainer );
|
|
|
|
PCB_LAYER_ID klayer = LayerToKi( layer );
|
|
zone->SetLayer( klayer );
|
|
|
|
if( IsCopperLayer( klayer ) )
|
|
zone->SetNet( getOrAddNetItem( arr[2] ) );
|
|
|
|
for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
|
|
zone->Outline()->AddPolygon( poly );
|
|
|
|
if( arr[4].Lower() == wxS( "cutout" ) )
|
|
{
|
|
zone->SetIsRuleArea( true );
|
|
zone->SetDoNotAllowCopperPour( true );
|
|
zone->SetDoNotAllowTracks( false );
|
|
zone->SetDoNotAllowVias( false );
|
|
zone->SetDoNotAllowPads( false );
|
|
zone->SetDoNotAllowFootprints( false );
|
|
}
|
|
else
|
|
{ // solid
|
|
zone->SetFilledPolysList( klayer, polySet );
|
|
zone->SetPadConnection( ZONE_CONNECTION::FULL );
|
|
zone->SetIsFilled( true );
|
|
zone->SetNeedRefill( false );
|
|
}
|
|
|
|
zone->SetMinThickness( 0 );
|
|
zone->SetLocalClearance( 0 );
|
|
zone->SetAssignedPriority( 100 );
|
|
|
|
aContainer->Add( zone.release(), ADD_MODE::APPEND );
|
|
}
|
|
}
|
|
else if( elType == wxS( "COPPERAREA" ) )
|
|
{
|
|
std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aContainer );
|
|
|
|
PCB_LAYER_ID layer = LayerToKi( arr[2] );
|
|
zone->SetLayer( layer );
|
|
|
|
wxString netname = arr[3];
|
|
if( IsCopperLayer( layer ) )
|
|
zone->SetNet( getOrAddNetItem( netname ) );
|
|
|
|
zone->SetLocalClearance( ConvertSize( arr[5] ) );
|
|
zone->SetThermalReliefGap( zone->GetLocalClearance() );
|
|
|
|
wxString fillStyle = arr[5];
|
|
if( fillStyle == wxS( "none" ) )
|
|
{
|
|
// Do not fill?
|
|
}
|
|
|
|
SHAPE_POLY_SET polySet =
|
|
ParseLineChains( arr[4].Trim(), pcbIUScale.mmToIU( 0.01 ), true );
|
|
|
|
for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
|
|
zone->Outline()->AddPolygon( poly );
|
|
|
|
wxString thermal = arr[8];
|
|
if( thermal == wxS( "direct" ) )
|
|
zone->SetPadConnection( ZONE_CONNECTION::FULL );
|
|
|
|
wxString keepIsland = arr[9];
|
|
if( keepIsland == wxS( "yes" ) )
|
|
zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::NEVER );
|
|
|
|
wxString fillData = arr[10];
|
|
SHAPE_POLY_SET fillPolySet;
|
|
try
|
|
{
|
|
for( const nlohmann::json& polyData : nlohmann::json::parse( fillData ) )
|
|
{
|
|
for( const nlohmann::json& contourData : polyData )
|
|
{
|
|
SHAPE_POLY_SET contourPolySet = ParseLineChains(
|
|
contourData.get<wxString>(), pcbIUScale.mmToIU( 0.01 ), true );
|
|
|
|
SHAPE_POLY_SET currentOutline( contourPolySet.COutline( 0 ) );
|
|
|
|
for( int i = 1; i < contourPolySet.OutlineCount(); i++ )
|
|
currentOutline.AddHole( contourPolySet.COutline( i ) );
|
|
|
|
fillPolySet.Append( currentOutline );
|
|
}
|
|
}
|
|
|
|
fillPolySet.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
zone->SetFilledPolysList( layer, fillPolySet );
|
|
zone->SetIsFilled( true );
|
|
zone->SetNeedRefill( false );
|
|
}
|
|
catch( nlohmann::json::exception& e )
|
|
{
|
|
}
|
|
|
|
int fillOrder = wxAtoi( arr[13] );
|
|
zone->SetAssignedPriority( 100 - fillOrder );
|
|
|
|
wxString improveFabrication = arr[17];
|
|
if( improveFabrication == wxS( "none" ) )
|
|
{
|
|
zone->SetMinThickness( 0 );
|
|
}
|
|
else
|
|
{
|
|
// arr[1] is "stroke Width" per docs
|
|
int minThickness =
|
|
std::max( pcbIUScale.mmToIU( 0.03 ), int( ConvertSize( arr[1] ) ) );
|
|
zone->SetMinThickness( minThickness );
|
|
}
|
|
|
|
if( arr.size() > 18 )
|
|
{
|
|
zone->SetThermalReliefSpokeWidth(
|
|
std::max( int( ConvertSize( arr[18] ) ), zone->GetMinThickness() ) );
|
|
}
|
|
else
|
|
{
|
|
wxFAIL_MSG( wxString::Format( "COPPERAREA unexpected size %d: %s ", arr.size(),
|
|
shape ) );
|
|
|
|
zone->SetThermalReliefSpokeWidth( zone->GetMinThickness() );
|
|
}
|
|
|
|
aContainer->Add( zone.release(), ADD_MODE::APPEND );
|
|
}
|
|
else if( elType == wxS( "SVGNODE" ) )
|
|
{
|
|
nlohmann::json nodeData = nlohmann::json::parse( arr[1] );
|
|
|
|
int nodeType = nodeData.at( "nodeType" );
|
|
wxString layer = nodeData.at( "layerid" );
|
|
PCB_LAYER_ID klayer = LayerToKi( layer );
|
|
|
|
if( nodeType == 1 )
|
|
{
|
|
std::map<wxString, wxString> attributes = nodeData.at( "attrs" );
|
|
|
|
if( layer == wxS( "19" ) ) // 3DModel
|
|
{
|
|
if( !footprint )
|
|
continue;
|
|
|
|
auto ec_eType = get_opt( attributes, "c_etype" );
|
|
auto ec_rotation = get_opt( attributes, "c_rotation" );
|
|
auto ec_origin = get_opt( attributes, "c_origin" );
|
|
auto ec_width = get_opt( attributes, "c_width" );
|
|
auto ec_height = get_opt( attributes, "c_height" );
|
|
auto ez = get_opt( attributes, "z" );
|
|
auto etitle = get_opt( attributes, "title" );
|
|
auto euuid = get_opt( attributes, "uuid" );
|
|
auto etransform = get_opt( attributes, "transform" );
|
|
|
|
if( !ec_eType || *ec_eType != wxS( "outline3D" ) || !etitle )
|
|
continue;
|
|
|
|
wxString modelTitle = *etitle;
|
|
VECTOR3D kmodelOffset;
|
|
VECTOR3D kmodelRotation;
|
|
|
|
if( euuid )
|
|
{
|
|
PCB_FIELD field( footprint, footprint->GetFieldCount(),
|
|
DIRECT_MODEL_UUID_KEY );
|
|
field.SetLayer( Cmts_User );
|
|
field.SetVisible( false );
|
|
field.SetText( *euuid );
|
|
footprint->AddField( field );
|
|
}
|
|
|
|
/*if( etransform )
|
|
{
|
|
PCB_FIELD field( footprint, footprint->GetFieldCount(), "3D Transform" );
|
|
field.SetLayer( Cmts_User );
|
|
field.SetVisible( false );
|
|
field.SetText( *etransform );
|
|
footprint->AddField( field );
|
|
}*/
|
|
|
|
if( ec_width && ec_height )
|
|
{
|
|
double fitXmm = pcbIUScale.IUTomm( ScaleSize( Convert( *ec_width ) ) );
|
|
double fitYmm = pcbIUScale.IUTomm( ScaleSize( Convert( *ec_height ) ) );
|
|
|
|
double rounding = 0.001;
|
|
fitXmm = KiROUND( fitXmm / rounding ) * rounding;
|
|
fitYmm = KiROUND( fitYmm / rounding ) * rounding;
|
|
|
|
PCB_FIELD field( footprint, footprint->GetFieldCount(), MODEL_SIZE_KEY );
|
|
field.SetLayer( Cmts_User );
|
|
field.SetVisible( false );
|
|
field.SetText( wxString::FromCDouble( fitXmm ) + wxS( " " )
|
|
+ wxString::FromCDouble( fitYmm ) );
|
|
footprint->AddField( field );
|
|
}
|
|
|
|
if( ec_origin )
|
|
{
|
|
wxArrayString orParts = wxSplit( *ec_origin, ',', '\0' );
|
|
|
|
if( orParts.size() == 2 )
|
|
{
|
|
VECTOR2D pos;
|
|
pos.x = Convert( orParts[0].Trim() );
|
|
pos.y = Convert( orParts[1].Trim() );
|
|
|
|
VECTOR2D rel = RelPos( pos );
|
|
kmodelOffset.x = -pcbIUScale.IUTomm( rel.x );
|
|
kmodelOffset.y = -pcbIUScale.IUTomm( rel.y );
|
|
|
|
RotatePoint( &kmodelOffset.x, &kmodelOffset.y,
|
|
-footprint->GetOrientation() );
|
|
}
|
|
}
|
|
|
|
if( ez )
|
|
{
|
|
kmodelOffset.z = pcbIUScale.IUTomm( ScaleSize( Convert( ez->Trim() ) ) );
|
|
}
|
|
|
|
if( ec_rotation )
|
|
{
|
|
wxArrayString rotParts = wxSplit( *ec_rotation, ',', '\0' );
|
|
|
|
if( rotParts.size() == 3 )
|
|
{
|
|
kmodelRotation.x = -Convert( rotParts[0].Trim() );
|
|
kmodelRotation.y = -Convert( rotParts[1].Trim() );
|
|
kmodelRotation.z = -Convert( rotParts[2].Trim() )
|
|
+ footprint->GetOrientationDegrees();
|
|
}
|
|
}
|
|
|
|
if( footprint->GetLayer() == B_Cu )
|
|
{
|
|
kmodelRotation.z = 180 - kmodelRotation.z;
|
|
RotatePoint( &kmodelOffset.x, &kmodelOffset.y, ANGLE_180 );
|
|
}
|
|
|
|
FP_3DMODEL model;
|
|
model.m_Filename = kicadModelPrefix
|
|
+ EscapeString( modelTitle, ESCAPE_CONTEXT::CTX_FILENAME )
|
|
+ wxS( ".step" );
|
|
model.m_Offset = kmodelOffset;
|
|
model.m_Rotation = kmodelRotation;
|
|
footprint->Models().push_back( model );
|
|
}
|
|
else
|
|
{
|
|
if( auto dataStr = get_opt( attributes, "d" ) )
|
|
{
|
|
int minSegLen = dataStr->size() < 8000 ? pcbIUScale.mmToIU( 0.005 )
|
|
: pcbIUScale.mmToIU( 0.05 );
|
|
|
|
SHAPE_POLY_SET polySet =
|
|
ParseLineChains( dataStr->Trim(), minSegLen, true );
|
|
|
|
polySet.RebuildHolesFromContours();
|
|
|
|
std::unique_ptr<PCB_GROUP> group;
|
|
|
|
if( polySet.OutlineCount() > 1 )
|
|
group = std::make_unique<PCB_GROUP>( aContainer );
|
|
|
|
for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
|
|
{
|
|
std::unique_ptr<PCB_SHAPE> shape =
|
|
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::POLY );
|
|
|
|
shape->SetFilled( true );
|
|
shape->SetPolyShape( poly );
|
|
shape->SetLayer( klayer );
|
|
shape->SetWidth( 0 );
|
|
|
|
if( group )
|
|
group->AddItem( shape.get() );
|
|
|
|
aContainer->Add( shape.release(), ADD_MODE::APPEND );
|
|
}
|
|
|
|
if( group )
|
|
aContainer->Add( group.release(), ADD_MODE::APPEND );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
THROW_IO_ERROR( wxString::Format( _( "Unknown SVGNODE nodeType %d" ), nodeType ) );
|
|
}
|
|
}
|
|
else if( elType == wxS( "TEXT" ) )
|
|
{
|
|
PCB_TEXT* text;
|
|
wxString textType = arr[1];
|
|
bool add = false;
|
|
|
|
if( textType == wxS( "P" ) )
|
|
{
|
|
text = footprint->GetField( REFERENCE_FIELD );
|
|
}
|
|
else if( textType == wxS( "N" ) )
|
|
{
|
|
text = footprint->GetField( VALUE_FIELD );
|
|
}
|
|
else
|
|
{
|
|
text = new PCB_TEXT( aContainer );
|
|
add = true;
|
|
}
|
|
|
|
VECTOR2D start;
|
|
start.x = RelPosX( arr[2] );
|
|
start.y = RelPosY( arr[3] );
|
|
text->SetPosition( start );
|
|
|
|
double thickness = ConvertSize( arr[4] );
|
|
text->SetTextThickness( thickness );
|
|
|
|
double rot = Convert( arr[5] );
|
|
text->SetTextAngleDegrees( rot );
|
|
|
|
text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
|
|
text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
|
|
|
|
PCB_LAYER_ID layer = LayerToKi( arr[7] );
|
|
text->SetLayer( layer );
|
|
|
|
if( IsBackLayer( layer ) )
|
|
text->SetMirrored( true );
|
|
|
|
double height = ConvertSize( arr[9] ) * 0.8;
|
|
text->SetTextSize( VECTOR2I( height, height ) );
|
|
|
|
text->SetText( UnescapeHTML( arr[10] ) );
|
|
|
|
//arr[11] // Geometry data
|
|
|
|
text->SetVisible( arr[12] != wxS( "none" ) );
|
|
|
|
wxString font = arr[14];
|
|
if( !font.IsEmpty() )
|
|
text->SetFont( KIFONT::FONT::GetFont( font ) );
|
|
|
|
if( add )
|
|
aContainer->Add( text, ADD_MODE::APPEND );
|
|
}
|
|
else if( elType == wxS( "VIA" ) )
|
|
{
|
|
VECTOR2D center( RelPosX( arr[1] ), RelPosY( arr[2] ) );
|
|
int kdia = ConvertSize( arr[3] );
|
|
int kdrill = ConvertSize( arr[5] ) * 2;
|
|
|
|
if( footprint )
|
|
{
|
|
std::unique_ptr<PAD> pad = std::make_unique<PAD>( footprint );
|
|
|
|
pad->SetPosition( center );
|
|
pad->SetLayerSet( PAD::PTHMask() );
|
|
pad->SetAttribute( PAD_ATTRIB::PTH );
|
|
pad->SetShape( PAD_SHAPE::CIRCLE );
|
|
pad->SetSize( VECTOR2I( kdia, kdia ) );
|
|
pad->SetDrillShape( PAD_DRILL_SHAPE_CIRCLE );
|
|
pad->SetDrillSize( VECTOR2I( kdrill, kdrill ) );
|
|
|
|
footprint->Add( pad.release(), ADD_MODE::APPEND );
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( aContainer );
|
|
|
|
via->SetPosition( center );
|
|
|
|
via->SetWidth( kdia );
|
|
via->SetNet( getOrAddNetItem( arr[4] ) );
|
|
via->SetDrill( kdrill );
|
|
|
|
aContainer->Add( via.release(), ADD_MODE::APPEND );
|
|
}
|
|
}
|
|
else if( elType == wxS( "HOLE" ) )
|
|
{
|
|
FOOTPRINT* padContainer;
|
|
|
|
VECTOR2D center( RelPosX( arr[1] ), RelPosY( arr[2] ) );
|
|
int kdia = ConvertSize( arr[3] ) * 2;
|
|
wxString holeUuid = arr[4];
|
|
|
|
if( footprint )
|
|
{
|
|
padContainer = footprint;
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<FOOTPRINT> newFootprint =
|
|
std::make_unique<FOOTPRINT>( dynamic_cast<BOARD*>( aContainer ) );
|
|
|
|
wxString name = wxS( "Hole_" ) + holeUuid;
|
|
|
|
newFootprint->SetFPID( LIB_ID( wxEmptyString, name ) );
|
|
newFootprint->SetPosition( center );
|
|
newFootprint->SetLocked( true );
|
|
|
|
newFootprint->Reference().SetText( name );
|
|
newFootprint->Reference().SetVisible( false );
|
|
newFootprint->Reference().SetTextSize( HIDDEN_TEXT_SIZE );
|
|
newFootprint->Value().SetText( name );
|
|
newFootprint->Value().SetVisible( false );
|
|
newFootprint->Value().SetTextSize( HIDDEN_TEXT_SIZE );
|
|
|
|
padContainer = newFootprint.get();
|
|
aContainer->Add( newFootprint.release(), ADD_MODE::APPEND );
|
|
}
|
|
|
|
std::unique_ptr<PAD> pad = std::make_unique<PAD>( padContainer );
|
|
|
|
pad->SetPosition( center );
|
|
pad->SetLayerSet( PAD::UnplatedHoleMask() );
|
|
pad->SetAttribute( PAD_ATTRIB::NPTH );
|
|
pad->SetShape( PAD_SHAPE::CIRCLE );
|
|
pad->SetSize( VECTOR2I( kdia, kdia ) );
|
|
pad->SetDrillShape( PAD_DRILL_SHAPE_CIRCLE );
|
|
pad->SetDrillSize( VECTOR2I( kdia, kdia ) );
|
|
|
|
padContainer->Add( pad.release(), ADD_MODE::APPEND );
|
|
}
|
|
else if( elType == wxS( "PAD" ) )
|
|
{
|
|
FOOTPRINT* padContainer;
|
|
|
|
VECTOR2D center( RelPosX( arr[2] ), RelPosY( arr[3] ) );
|
|
VECTOR2D size( ConvertSize( arr[4] ), ConvertSize( arr[5] ) );
|
|
wxString padUuid = arr[12];
|
|
|
|
if( footprint )
|
|
{
|
|
padContainer = footprint;
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<FOOTPRINT> newFootprint =
|
|
std::make_unique<FOOTPRINT>( dynamic_cast<BOARD*>( aContainer ) );
|
|
|
|
wxString name = wxS( "Pad_" ) + padUuid;
|
|
|
|
newFootprint->SetFPID( LIB_ID( wxEmptyString, name ) );
|
|
newFootprint->SetPosition( center );
|
|
newFootprint->SetLocked( true );
|
|
|
|
newFootprint->Reference().SetText( name );
|
|
newFootprint->Reference().SetVisible( false );
|
|
newFootprint->Reference().SetTextSize( HIDDEN_TEXT_SIZE );
|
|
newFootprint->Value().SetText( name );
|
|
newFootprint->Value().SetVisible( false );
|
|
newFootprint->Value().SetTextSize( HIDDEN_TEXT_SIZE );
|
|
|
|
padContainer = newFootprint.get();
|
|
aContainer->Add( newFootprint.release(), ADD_MODE::APPEND );
|
|
}
|
|
|
|
std::unique_ptr<PAD> pad = std::make_unique<PAD>( padContainer );
|
|
|
|
pad->SetNet( getOrAddNetItem( arr[7] ) );
|
|
pad->SetNumber( arr[8] );
|
|
pad->SetPosition( center );
|
|
pad->SetSize( size );
|
|
pad->SetOrientationDegrees( Convert( arr[11] ) );
|
|
pad->SetThermalSpokeAngle( ANGLE_0 );
|
|
|
|
wxString elayer = arr[6];
|
|
PCB_LAYER_ID klayer = LayerToKi( elayer );
|
|
|
|
bool plated = arr[15] == wxS( "Y" );
|
|
|
|
if( klayer == F_Cu )
|
|
{
|
|
pad->SetLayer( F_Cu );
|
|
pad->SetLayerSet( PAD::SMDMask() );
|
|
pad->SetAttribute( PAD_ATTRIB::SMD );
|
|
}
|
|
else if( klayer == B_Cu )
|
|
{
|
|
pad->SetLayer( B_Cu );
|
|
pad->SetLayerSet( FlipLayerMask( PAD::SMDMask() ) );
|
|
pad->SetAttribute( PAD_ATTRIB::SMD );
|
|
}
|
|
else if( elayer == wxS( "11" ) )
|
|
{
|
|
pad->SetLayerSet( plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
|
|
pad->SetAttribute( plated ? PAD_ATTRIB::PTH : PAD_ATTRIB::NPTH );
|
|
}
|
|
else
|
|
{
|
|
pad->SetLayer( klayer );
|
|
pad->SetLayerSet( LSET( 1, klayer ) );
|
|
pad->SetAttribute( PAD_ATTRIB::SMD );
|
|
}
|
|
|
|
wxString padType = arr[1];
|
|
if( padType == wxS( "ELLIPSE" ) )
|
|
{
|
|
pad->SetShape( PAD_SHAPE::OVAL );
|
|
}
|
|
else if( padType == wxS( "RECT" ) )
|
|
{
|
|
pad->SetShape( PAD_SHAPE::RECTANGLE );
|
|
}
|
|
else if( padType == wxS( "OVAL" ) )
|
|
{
|
|
if( pad->GetSizeX() == pad->GetSizeY() )
|
|
pad->SetShape( PAD_SHAPE::CIRCLE );
|
|
else
|
|
pad->SetShape( PAD_SHAPE::OVAL );
|
|
}
|
|
else if( padType == wxS( "POLYGON" ) )
|
|
{
|
|
pad->SetShape( PAD_SHAPE::CUSTOM );
|
|
pad->SetAnchorPadShape( PAD_SHAPE::CIRCLE );
|
|
pad->SetSize( { 1, 1 } );
|
|
|
|
wxArrayString data = wxSplit( arr[10], ' ', '\0' );
|
|
|
|
SHAPE_LINE_CHAIN chain;
|
|
for( int i = 1; i < data.size(); i += 2 )
|
|
{
|
|
VECTOR2D pt;
|
|
pt.x = RelPosX( data[i - 1] );
|
|
pt.y = RelPosY( data[i] );
|
|
chain.Append( pt );
|
|
}
|
|
chain.SetClosed( true );
|
|
|
|
chain.Move( -center );
|
|
chain.Rotate( -pad->GetOrientation() );
|
|
pad->AddPrimitivePoly( chain, 0, true );
|
|
}
|
|
|
|
wxString holeDia = arr[9];
|
|
if( !holeDia.IsEmpty() )
|
|
{
|
|
double holeD = ConvertSize( holeDia ) * 2;
|
|
double holeL = 0;
|
|
|
|
wxString holeLength = arr[13];
|
|
if( !holeLength.IsEmpty() )
|
|
holeL = ConvertSize( holeLength );
|
|
|
|
if( holeL > 0 )
|
|
{
|
|
pad->SetDrillShape( PAD_DRILL_SHAPE_OBLONG );
|
|
|
|
if( size.x < size.y )
|
|
pad->SetDrillSize( VECTOR2I( holeD, holeL ) );
|
|
else
|
|
pad->SetDrillSize( VECTOR2I( holeL, holeD ) );
|
|
}
|
|
else
|
|
{
|
|
pad->SetDrillShape( PAD_DRILL_SHAPE_CIRCLE );
|
|
pad->SetDrillSize( VECTOR2I( holeD, holeD ) );
|
|
}
|
|
}
|
|
|
|
wxString pasteExp = arr[17];
|
|
if( !pasteExp.IsEmpty() )
|
|
{
|
|
double pasteExpansion = ConvertSize( pasteExp );
|
|
pad->SetLocalSolderPasteMargin( pasteExpansion );
|
|
}
|
|
|
|
wxString maskExp = arr[18];
|
|
if( !maskExp.IsEmpty() )
|
|
{
|
|
double maskExpansion = ConvertSize( maskExp );
|
|
pad->SetLocalSolderMaskMargin( maskExpansion );
|
|
}
|
|
|
|
padContainer->Add( pad.release(), ADD_MODE::APPEND );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FOOTPRINT* PCB_IO_EASYEDA_PARSER::ParseFootprint(
|
|
const VECTOR2D& aOrigin, const EDA_ANGLE& aOrientation, int aLayer, BOARD* aParent,
|
|
std::map<wxString, wxString> aParams,
|
|
std::map<wxString, std::unique_ptr<FOOTPRINT>>& aFootprintMap, wxArrayString aShapes )
|
|
{
|
|
std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( aParent );
|
|
|
|
if( aLayer == 2 ) // Bottom layer
|
|
{
|
|
footprint->SetLayer( B_Cu );
|
|
footprint->SetOrientation( aOrientation - ANGLE_180 );
|
|
}
|
|
else
|
|
{
|
|
footprint->SetLayer( F_Cu );
|
|
footprint->SetOrientation( aOrientation );
|
|
}
|
|
|
|
footprint->Value().SetText( aParams[wxS( "package" )] );
|
|
|
|
m_relOrigin = aOrigin;
|
|
|
|
ParseToBoardItemContainer( footprint.get(), aParent, aParams, aFootprintMap, aShapes );
|
|
|
|
// Heal board outlines
|
|
std::vector<PCB_SHAPE*> shapes;
|
|
std::vector<std::unique_ptr<PCB_SHAPE>> newShapes;
|
|
|
|
for( BOARD_ITEM* item : footprint->GraphicalItems() )
|
|
{
|
|
if( !item->IsOnLayer( Edge_Cuts ) )
|
|
continue;
|
|
|
|
if( item->Type() == PCB_SHAPE_T )
|
|
shapes.push_back( static_cast<PCB_SHAPE*>( item ) );
|
|
}
|
|
|
|
ConnectBoardShapes( shapes, newShapes, SHAPE_JOIN_DISTANCE );
|
|
|
|
for( std::unique_ptr<PCB_SHAPE>& ptr : newShapes )
|
|
footprint->Add( ptr.release(), ADD_MODE::APPEND );
|
|
|
|
return footprint.release();
|
|
}
|
|
|
|
|
|
void PCB_IO_EASYEDA_PARSER::ParseBoard( BOARD* aBoard, const VECTOR2D& aOrigin,
|
|
std::map<wxString, std::unique_ptr<FOOTPRINT>>& aFootprintMap,
|
|
wxArrayString aShapes )
|
|
{
|
|
m_relOrigin = aOrigin;
|
|
|
|
ParseToBoardItemContainer( aBoard, nullptr, {}, aFootprintMap, aShapes );
|
|
|
|
// Heal board outlines
|
|
std::vector<PCB_SHAPE*> shapes;
|
|
std::vector<std::unique_ptr<PCB_SHAPE>> newShapes;
|
|
|
|
for( BOARD_ITEM* item : aBoard->Drawings() )
|
|
{
|
|
if( !item->IsOnLayer( Edge_Cuts ) )
|
|
continue;
|
|
|
|
if( item->Type() == PCB_SHAPE_T )
|
|
shapes.push_back( static_cast<PCB_SHAPE*>( item ) );
|
|
}
|
|
|
|
ConnectBoardShapes( shapes, newShapes, SHAPE_JOIN_DISTANCE );
|
|
|
|
for( std::unique_ptr<PCB_SHAPE>& ptr : newShapes )
|
|
aBoard->Add( ptr.release(), ADD_MODE::APPEND );
|
|
}
|