kicad/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp

1835 lines
63 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_easyedapro_parser.h"
#include <io/easyedapro/easyedapro_import_utils.h>
#include <memory>
#include <nlohmann/json.hpp>
#include <core/json_serializers.h>
#include <core/map_helpers.h>
#include <string_utils.h>
#include <wx/wfstream.h>
#include <wx/stdstream.h>
#include <wx/log.h>
#include <glm/glm.hpp>
#include <progress_reporter.h>
#include <footprint.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 <font/font.h>
#include <geometry/shape_arc.h>
#include <geometry/shape_circle.h>
#include <geometry/shape_simple.h>
#include <geometry/shape_rect.h>
#include <geometry/shape_compound.h>
#include <zone.h>
#include <pad.h>
#include <convert_basic_shapes_to_polygon.h>
#include <project.h>
#include <fix_board_shape.h>
#include <pcb_reference_image.h>
#include <core/mirror.h>
static const wxString QUERY_MODEL_UUID_KEY = wxS( "JLC_3DModel_Q" );
static const wxString MODEL_SIZE_KEY = wxS( "JLC_3D_Size" );
static const int SHAPE_JOIN_DISTANCE = pcbIUScale.mmToIU( 1.5 );
double PCB_IO_EASYEDAPRO_PARSER::Convert( wxString aValue )
{
double value = 0;
if( !aValue.ToCDouble( &value ) )
THROW_IO_ERROR( wxString::Format( _( "Failed to parse value: '%s'" ), aValue ) );
return value;
}
PCB_IO_EASYEDAPRO_PARSER::PCB_IO_EASYEDAPRO_PARSER( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter )
{
m_board = aBoard;
}
PCB_IO_EASYEDAPRO_PARSER::~PCB_IO_EASYEDAPRO_PARSER()
{
}
PCB_LAYER_ID PCB_IO_EASYEDAPRO_PARSER::LayerToKi( int aLayer )
{
switch( aLayer )
{
case 1: return F_Cu;
case 2: return B_Cu;
case 3: return F_SilkS;
case 4: return B_SilkS;
case 5: return F_Mask;
case 6: return B_Mask;
case 7: return F_Paste;
case 8: return B_Paste;
case 9: return F_Fab;
case 10: return B_Fab;
case 11: return Edge_Cuts;
case 12: return Edge_Cuts; // Multi
case 13: return Dwgs_User;
case 14: return Eco2_User;
case 15: return In1_Cu;
case 16: return In2_Cu;
case 17: return In3_Cu;
case 18: return In4_Cu;
case 19: return In5_Cu;
case 20: return In6_Cu;
case 21: return In7_Cu;
case 22: return In8_Cu;
case 23: return In9_Cu;
case 24: return In10_Cu;
case 25: return In11_Cu;
case 26: return In12_Cu;
case 27: return In13_Cu;
case 28: return In14_Cu;
case 29: return In15_Cu;
case 30: return In16_Cu;
case 31: return In17_Cu;
case 32: return In18_Cu;
case 33: return In19_Cu;
case 34: return In20_Cu;
case 35: return In21_Cu;
case 36: return In22_Cu;
case 37: return In23_Cu;
case 38: return In24_Cu;
case 39: return In25_Cu;
case 40: return In26_Cu;
case 41: return In27_Cu;
case 42: return In28_Cu;
case 43: return In29_Cu;
case 44: return In30_Cu;
case 48: return User_2; // Component shape layer
case 49: return User_3; // Component marking
case 53: return User_4; // 3D shell outline
case 54: return User_5; // 3D shell top
case 55: return User_6; // 3D shell bot
case 56: return User_7; // Drill drawing
default: break;
}
return User_1;
}
static void AlignText( EDA_TEXT* text, int align )
{
switch( align )
{
case 1:
text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
break;
case 2:
text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
break;
case 3:
text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
break;
case 4:
text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
break;
case 5:
text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
break;
case 6:
text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
break;
case 7:
text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
break;
case 8:
text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
break;
case 9:
text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
break;
}
}
void PCB_IO_EASYEDAPRO_PARSER::fillFootprintModelInfo( FOOTPRINT* footprint, const wxString& modelUuid,
const wxString& modelTitle,
const wxString& modelTransform ) const
{
// TODO: make this path configurable?
const wxString easyedaModelDir = wxS( "EASYEDA_MODELS" );
const wxString kicadModelPrefix = wxS( "${KIPRJMOD}/" ) + easyedaModelDir + wxS( "/" );
VECTOR3D kmodelOffset;
VECTOR3D kmodelRotation;
if( !modelUuid.IsEmpty() && !footprint->GetFieldByName( QUERY_MODEL_UUID_KEY ) )
{
PCB_FIELD field( footprint, footprint->GetFieldCount(), QUERY_MODEL_UUID_KEY );
field.SetLayer( Cmts_User );
field.SetVisible( false );
field.SetText( modelUuid );
footprint->AddField( field );
}
if( !modelTransform.IsEmpty() && !footprint->GetFieldByName( MODEL_SIZE_KEY ) )
{
wxArrayString arr = wxSplit( modelTransform, ',', '\0' );
double fitXmm = pcbIUScale.IUTomm( ScaleSize( Convert( arr[0] ) ) );
double fitYmm = pcbIUScale.IUTomm( ScaleSize( Convert( arr[1] ) ) );
if( fitXmm > 0.0 && fitYmm > 0.0 )
{
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 );
}
// TODO: other axes
kmodelRotation.z = -Convert( arr[3] );
kmodelOffset.x = pcbIUScale.IUTomm( ScaleSize( Convert( arr[6] ) ) );
kmodelOffset.y = pcbIUScale.IUTomm( ScaleSize( Convert( arr[7] ) ) );
kmodelOffset.z = pcbIUScale.IUTomm( ScaleSize( Convert( arr[8] ) ) );
}
if( !modelTitle.IsEmpty() && footprint->Models().empty() )
{
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 );
}
}
std::vector<std::unique_ptr<PCB_SHAPE>>
PCB_IO_EASYEDAPRO_PARSER::ParsePoly( BOARD_ITEM_CONTAINER* aContainer, nlohmann::json polyData,
bool aClosed, bool aInFill ) const
{
std::vector<std::unique_ptr<PCB_SHAPE>> results;
VECTOR2D prevPt;
for( int i = 0; i < polyData.size(); i++ )
{
nlohmann::json val = polyData.at( i );
if( val.is_string() )
{
wxString str = val;
if( str == wxS( "CIRCLE" ) )
{
VECTOR2D center;
center.x = ( polyData.at( ++i ) );
center.y = ( polyData.at( ++i ) );
double r = ( polyData.at( ++i ) );
std::unique_ptr<PCB_SHAPE> shape =
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::CIRCLE );
shape->SetCenter( ScalePos( center ) );
shape->SetEnd( ScalePos( center + VECTOR2D( r, 0 ) ) );
shape->SetFilled( aClosed );
results.emplace_back( std::move( shape ) );
}
else if( str == wxS( "R" ) )
{
VECTOR2D start, size;
start.x = ( polyData.at( ++i ) );
start.y = ( polyData.at( ++i ) );
size.x = ( polyData.at( ++i ) );
size.y = -( polyData.at( ++i ).get<double>() );
double angle = polyData.at( ++i );
double cr = ( i + 1 ) < polyData.size() ? polyData.at( ++i ).get<double>() : 0;
if( cr == 0 )
{
std::unique_ptr<PCB_SHAPE> shape =
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::RECTANGLE );
shape->SetStart( ScalePos( start ) );
shape->SetEnd( ScalePos( start + size ) );
shape->SetFilled( aClosed );
shape->Rotate( ScalePos( start ), EDA_ANGLE( angle, DEGREES_T ) );
results.emplace_back( std::move( shape ) );
}
else
{
VECTOR2D end = start + size;
auto addSegment = [&]( VECTOR2D aStart, VECTOR2D aEnd )
{
std::unique_ptr<PCB_SHAPE> shape =
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::SEGMENT );
shape->SetStart( ScalePos( aStart ) );
shape->SetEnd( ScalePos( aEnd ) );
shape->SetFilled( aClosed );
shape->Rotate( ScalePos( start ), EDA_ANGLE( angle, DEGREES_T ) );
results.emplace_back( std::move( shape ) );
};
auto addArc = [&]( VECTOR2D aStart, VECTOR2D aEnd, VECTOR2D center )
{
std::unique_ptr<PCB_SHAPE> shape =
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::ARC );
shape->SetStart( ScalePos( aStart ) );
shape->SetEnd( ScalePos( aEnd ) );
shape->SetCenter( ScalePos( center ) );
shape->SetFilled( aClosed );
shape->Rotate( ScalePos( start ), EDA_ANGLE( angle, DEGREES_T ) );
results.emplace_back( std::move( shape ) );
};
addSegment( { start.x + cr, start.y }, { end.x - cr, start.y } );
addSegment( { end.x, start.y - cr }, { end.x, end.y + cr } );
addSegment( { start.x + cr, end.y }, { end.x - cr, end.y } );
addSegment( { start.x, start.y - cr }, { start.x, end.y + cr } );
addArc( { end.x - cr, start.y }, { end.x, start.y - cr },
{ end.x - cr, start.y - cr } );
addArc( { end.x, end.y + cr }, { end.x - cr, end.y },
{ end.x - cr, end.y + cr } );
addArc( { start.x + cr, end.y }, { start.x, end.y + cr },
{ start.x + cr, end.y + cr } );
addArc( { start.x, start.y - cr }, { start.x + cr, start.y },
{ start.x + cr, start.y - cr } );
}
}
else if( str == wxS( "ARC" ) || str == wxS( "CARC" ) )
{
VECTOR2D end;
double angle = polyData.at( ++i ).get<double>() / ( aInFill ? 10 : 1 );
end.x = ( polyData.at( ++i ) );
end.y = ( polyData.at( ++i ) );
std::unique_ptr<PCB_SHAPE> shape =
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::ARC );
if( angle < 0 )
{
shape->SetStart( ScalePos( prevPt ) );
shape->SetEnd( ScalePos( end ) );
}
else
{
shape->SetStart( ScalePos( end ) );
shape->SetEnd( ScalePos( prevPt ) );
}
VECTOR2D delta = end - prevPt;
VECTOR2D mid = ( prevPt + delta / 2 );
double ha = angle / 2;
double hd = delta.EuclideanNorm() / 2;
double cdist = hd / tan( DEG2RAD( ha ) );
VECTOR2D center = mid + delta.Perpendicular().Resize( cdist );
shape->SetCenter( ScalePos( center ) );
shape->SetFilled( aClosed );
results.emplace_back( std::move( shape ) );
prevPt = end;
}
else if( str == wxS( "L" ) )
{
SHAPE_LINE_CHAIN chain;
chain.Append( ScalePos( prevPt ) );
while( i < polyData.size() - 2 && polyData.at( i + 1 ).is_number() )
{
VECTOR2D pt;
pt.x = ( polyData.at( ++i ) );
pt.y = ( polyData.at( ++i ) );
chain.Append( ScalePos( pt ) );
prevPt = pt;
}
if( aClosed )
{
std::unique_ptr<PCB_SHAPE> shape =
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::POLY );
wxASSERT( chain.PointCount() > 2 );
if( chain.PointCount() > 2 )
{
chain.SetClosed( true );
shape->SetFilled( true );
shape->SetPolyShape( chain );
results.emplace_back( std::move( shape ) );
}
}
else
{
for( int s = 0; s < chain.SegmentCount(); s++ )
{
SEG seg = chain.Segment( s );
std::unique_ptr<PCB_SHAPE> shape =
std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::SEGMENT );
shape->SetStart( seg.A );
shape->SetEnd( seg.B );
results.emplace_back( std::move( shape ) );
}
}
}
}
else if( val.is_number() )
{
prevPt.x = ( polyData.at( i ) );
prevPt.y = ( polyData.at( ++i ) );
}
}
return results;
}
SHAPE_LINE_CHAIN
PCB_IO_EASYEDAPRO_PARSER::ParseContour( nlohmann::json polyData, bool aInFill,
double aArcAccuracy ) const
{
SHAPE_LINE_CHAIN result;
VECTOR2D prevPt;
double bezierMinSegLen = polyData.size() < 300 ? aArcAccuracy : aArcAccuracy * 10;
for( int i = 0; i < polyData.size(); i++ )
{
nlohmann::json val = polyData.at( i );
if( val.is_string() )
{
wxString str = val;
if( str == wxS( "CIRCLE" ) )
{
VECTOR2D center;
center.x = ( polyData.at( ++i ) );
center.y = ( polyData.at( ++i ) );
double r = ( polyData.at( ++i ) );
TransformCircleToPolygon( result, ScalePos( center ), ScaleSize( r ), ARC_HIGH_DEF,
ERROR_INSIDE );
}
else if( str == wxS( "R" ) )
{
VECTOR2D start, size;
start.x = ( polyData.at( ++i ) );
start.y = ( polyData.at( ++i ) );
size.x = ( polyData.at( ++i ) );
size.y = ( polyData.at( ++i ).get<double>() );
double angle = polyData.at( ++i );
double cr = ( i + 1 ) < polyData.size() ? polyData.at( ++i ).get<double>() : 0;
SHAPE_POLY_SET poly;
VECTOR2D kstart = ScalePos( start );
VECTOR2D ksize = ScaleSize( size );
VECTOR2D kcenter = kstart + ksize / 2;
RotatePoint( kcenter, kstart, EDA_ANGLE( angle, DEGREES_T ) );
TransformRoundChamferedRectToPolygon(
poly, kcenter, ksize, EDA_ANGLE( angle, DEGREES_T ), ScaleSize( cr ), 0, 0,
0, ARC_HIGH_DEF, ERROR_INSIDE );
result.Append( poly.Outline( 0 ) );
}
else if( str == wxS( "ARC" ) || str == wxS( "CARC" ) )
{
VECTOR2D end;
double angle = polyData.at( ++i ).get<double>();
if( aInFill ) // In .epcb fills, the angle is 10x for some reason
angle /= 10;
end.x = ( polyData.at( ++i ) );
end.y = ( polyData.at( ++i ) );
VECTOR2D arcStart, arcEnd;
arcStart = prevPt;
arcEnd = end;
VECTOR2D delta = end - prevPt;
VECTOR2D mid = ( prevPt + delta / 2 );
double ha = angle / 2;
double hd = delta.EuclideanNorm() / 2;
double cdist = hd / tan( DEG2RAD( ha ) );
VECTOR2D center = mid + delta.Perpendicular().Resize( cdist );
SHAPE_ARC sarc;
sarc.ConstructFromStartEndCenter( ScalePos( arcStart ), ScalePos( arcEnd ),
ScalePos( center ), angle >= 0, 0 );
result.Append( sarc, aArcAccuracy );
prevPt = end;
}
else if( str == wxS( "C" ) )
{
VECTOR2D pt1;
pt1.x = ( polyData.at( ++i ) );
pt1.y = ( polyData.at( ++i ) );
VECTOR2D pt2;
pt2.x = ( polyData.at( ++i ) );
pt2.y = ( polyData.at( ++i ) );
VECTOR2D pt3;
pt3.x = ( polyData.at( ++i ) );
pt3.y = ( polyData.at( ++i ) );
std::vector<VECTOR2I> ctrlPoints = { ScalePos( prevPt ), ScalePos( pt1 ),
ScalePos( pt2 ), ScalePos( pt3 ) };
BEZIER_POLY converter( ctrlPoints );
std::vector<VECTOR2I> bezierPoints;
converter.GetPoly( bezierPoints, bezierMinSegLen, 16 );
result.Append( bezierPoints );
prevPt = pt3;
}
else if( str == wxS( "L" ) )
{
result.Append( ScalePos( prevPt ) );
while( i < polyData.size() - 2 && polyData.at( i + 1 ).is_number() )
{
VECTOR2D pt;
pt.x = ( polyData.at( ++i ) );
pt.y = ( polyData.at( ++i ) );
result.Append( ScalePos( pt ) );
prevPt = pt;
}
}
}
else if( val.is_number() )
{
prevPt.x = ( polyData.at( i ) );
prevPt.y = ( polyData.at( ++i ) );
}
}
return result;
}
std::unique_ptr<PAD> PCB_IO_EASYEDAPRO_PARSER::createPAD( FOOTPRINT* aFootprint,
const nlohmann::json& line )
{
wxString uuid = line.at( 1 );
// if( line.at( 2 ).is_number() )
// int unk = line.at( 2 ).get<int>();
// else if( line.at( 2 ).is_string() )
// int unk = wxAtoi( line.at( 2 ).get<wxString>() );
wxString netname = line.at( 3 );
int layer = line.at( 4 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
wxString padNumber = line.at( 5 );
VECTOR2D center;
center.x = line.at( 6 );
center.y = line.at( 7 );
double orientation = line.at( 8 );
nlohmann::json padHole = line.at( 9 );
nlohmann::json padShape = line.at( 10 );
std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
pad->SetNumber( padNumber );
pad->SetPosition( ScalePos( center ) );
pad->SetOrientationDegrees( orientation );
if( !padHole.is_null() )
{
double drill_dir = 0;
if( line.at( 14 ).is_number() )
drill_dir = line.at( 14 );
if( padHole.at( 0 ) == wxS( "ROUND" ) || padHole.at( 0 ) == wxS( "SLOT" ) )
{
VECTOR2D drill;
drill.x = padHole.at( 1 );
drill.y = padHole.at( 2 );
double deg = EDA_ANGLE( drill_dir, DEGREES_T ).Normalize90().AsDegrees();
if( std::abs( deg ) >= 45 )
std::swap( drill.x, drill.y ); // KiCad doesn't support arbitrary hole direction
if( padHole.at( 0 ) == wxS( "SLOT" ) )
{
pad->SetDrillShape( PAD_DRILL_SHAPE_OBLONG );
}
pad->SetDrillSize( ScaleSize( drill ) );
pad->SetLayerSet( PAD::PTHMask() );
pad->SetAttribute( PAD_ATTRIB::PTH );
}
}
else
{
if( klayer == F_Cu )
{
pad->SetLayerSet( PAD::SMDMask() );
}
else if( klayer == B_Cu )
{
pad->SetLayerSet( FlipLayerMask( PAD::SMDMask() ) );
}
pad->SetAttribute( PAD_ATTRIB::SMD );
}
wxString padSh = padShape.at( 0 );
if( padSh == wxS( "RECT" ) )
{
VECTOR2D size;
size.x = padShape.at( 1 );
size.y = padShape.at( 2 );
double cr_p = padShape.size() > 3 ? padShape.at( 3 ).get<double>() : 0;
pad->SetSize( ScaleSize( size ) );
if( cr_p == 0 )
{
pad->SetShape( PAD_SHAPE::RECTANGLE );
}
else
{
pad->SetShape( PAD_SHAPE::ROUNDRECT );
pad->SetRoundRectRadiusRatio( cr_p / 100 );
}
}
else if( padSh == wxS( "ELLIPSE" ) )
{
VECTOR2D size;
size.x = padShape.at( 1 );
size.y = padShape.at( 2 );
pad->SetSize( ScaleSize( size ) );
pad->SetShape( PAD_SHAPE::CIRCLE );
}
else if( padSh == wxS( "OVAL" ) )
{
VECTOR2D size;
size.x = padShape.at( 1 );
size.y = padShape.at( 2 );
pad->SetSize( ScaleSize( size ) );
pad->SetShape( PAD_SHAPE::OVAL );
}
else if( padSh == wxS( "POLY" ) )
{
pad->SetShape( PAD_SHAPE::CUSTOM );
pad->SetAnchorPadShape( PAD_SHAPE::CIRCLE );
pad->SetSize( { 1, 1 } );
nlohmann::json polyData = padShape.at( 1 );
std::vector<std::unique_ptr<PCB_SHAPE>> results =
ParsePoly( aFootprint, polyData, true, false );
for( auto& shape : results )
{
shape->SetLayer( klayer );
shape->SetWidth( 0 );
shape->Move( -pad->GetPosition() );
pad->AddPrimitive( shape.release() );
}
}
pad->SetThermalSpokeAngle( ANGLE_90 );
return std::move( pad );
}
FOOTPRINT* PCB_IO_EASYEDAPRO_PARSER::ParseFootprint( const nlohmann::json& aProject,
const wxString& aFpUuid,
const std::vector<nlohmann::json>& aLines )
{
std::unique_ptr<FOOTPRINT> footprintPtr = std::make_unique<FOOTPRINT>( m_board );
FOOTPRINT* footprint = footprintPtr.get();
const VECTOR2I defaultTextSize( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) );
const int defaultTextThickness( pcbIUScale.mmToIU( 0.15 ) );
for( PCB_FIELD* field : footprint->Fields() )
{
field->SetTextSize( defaultTextSize );
field->SetTextThickness( defaultTextThickness );
}
for( const nlohmann::json& line : aLines )
{
if( line.size() == 0 )
continue;
wxString type = line.at( 0 );
if( type == wxS( "POLY" ) || type == wxS( "PAD" ) || type == wxS( "FILL" )
|| type == wxS( "ATTR" ) )
{
wxString uuid = line.at( 1 );
// if( line.at( 2 ).is_number() )
// int unk = line.at( 2 ).get<int>();
// else if( line.at( 2 ).is_string() )
// int unk = wxAtoi( line.at( 2 ).get<wxString>() );
wxString netname = line.at( 3 );
int layer = line.at( 4 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
if( type == wxS( "POLY" ) )
{
double thickness = ( line.at( 5 ) );
nlohmann::json polyData = line.at( 6 );
std::vector<std::unique_ptr<PCB_SHAPE>> results =
ParsePoly( footprint, polyData, false, false );
for( auto& shape : results )
{
shape->SetLayer( klayer );
shape->SetWidth( ScaleSize( thickness ) );
footprint->Add( shape.release(), ADD_MODE::APPEND );
}
}
else if( type == wxS( "PAD" ) )
{
std::unique_ptr<PAD> pad = createPAD( footprint, line );
footprint->Add( pad.release(), ADD_MODE::APPEND );
}
else if( type == wxS( "FILL" ) )
{
int layer = line.at( 4 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
double width = line.at( 5 );
nlohmann::json polyDataList = line.at( 7 );
if( !polyDataList.at( 0 ).is_array() )
polyDataList = nlohmann::json::array( { polyDataList } );
std::vector<SHAPE_LINE_CHAIN> contours;
for( nlohmann::json& polyData : polyDataList )
{
SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
contour.SetClosed( true );
contours.push_back( contour );
}
SHAPE_POLY_SET polySet;
for( SHAPE_LINE_CHAIN& contour : contours )
polySet.AddOutline( contour );
polySet.RebuildHolesFromContours();
std::unique_ptr<PCB_GROUP> group;
if( polySet.OutlineCount() > 1 )
group = std::make_unique<PCB_GROUP>( footprint );
BOX2I polyBBox = polySet.BBox();
for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
{
std::unique_ptr<PCB_SHAPE> shape =
std::make_unique<PCB_SHAPE>( footprint, SHAPE_T::POLY );
shape->SetFilled( true );
shape->SetPolyShape( poly );
shape->SetLayer( klayer );
shape->SetWidth( 0 );
if( group )
group->AddItem( shape.get() );
footprint->Add( shape.release(), ADD_MODE::APPEND );
}
if( group )
footprint->Add( group.release(), ADD_MODE::APPEND );
}
else if( type == wxS( "ATTR" ) )
{
EASYEDAPRO::PCB_ATTR attr = line;
if( attr.key == wxS( "Designator" ) )
footprint->GetField( REFERENCE_FIELD )->SetText( attr.value );
}
}
else if( type == wxS( "REGION" ) )
{
wxString uuid = line.at( 1 );
// if( line.at( 2 ).is_number() )
// int unk = line.at( 2 ).get<int>();
// else if( line.at( 2 ).is_string() )
// int unk = wxAtoi( line.at( 2 ).get<wxString>() );
int layer = line.at( 3 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
double width = line.at( 4 );
std::set<int> flags = line.at( 5 );
nlohmann::json polyDataList = line.at( 6 );
for( nlohmann::json& polyData : polyDataList )
{
SHAPE_POLY_SET polySet;
std::vector<std::unique_ptr<PCB_SHAPE>> results =
ParsePoly( nullptr, polyData, true, false );
for( auto& shape : results )
{
shape->SetFilled( true );
shape->TransformShapeToPolygon( polySet, klayer, 0, ARC_HIGH_DEF, ERROR_INSIDE,
true );
}
polySet.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( footprint );
zone->SetIsRuleArea( true );
zone->SetDoNotAllowFootprints( !!flags.count( 2 ) );
zone->SetDoNotAllowCopperPour( !!flags.count( 7 ) || !!flags.count( 6 )
|| !!flags.count( 8 ) );
zone->SetDoNotAllowPads( !!flags.count( 7 ) );
zone->SetDoNotAllowTracks( !!flags.count( 7 ) || !!flags.count( 5 ) );
zone->SetDoNotAllowVias( !!flags.count( 7 ) );
zone->SetLayer( klayer );
zone->Outline()->Append( polySet );
footprint->Add( zone.release(), ADD_MODE::APPEND );
}
}
}
if( aProject.is_object() )
{
std::map<wxString, EASYEDAPRO::PRJ_DEVICE> devicesMap = aProject.at( "devices" );
std::map<wxString, wxString> compAttrs;
for( auto& [devUuid, devData] : devicesMap )
{
if( auto fp = get_opt( devData.attributes, "Footprint" ) )
{
if( *fp == aFpUuid )
{
compAttrs = devData.attributes;
break;
}
}
}
wxString modelUuid, modelTitle, modelTransform;
modelUuid = get_def( compAttrs, "3D Model", "" );
modelTitle = get_def( compAttrs, "3D Model Title", modelUuid );
modelTransform = get_def( compAttrs, "3D Model Transform", "" );
fillFootprintModelInfo( footprint, modelUuid, modelTitle, modelTransform );
}
// 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 footprintPtr.release();
}
void PCB_IO_EASYEDAPRO_PARSER::ParseBoard(
BOARD* aBoard, const nlohmann::json& aProject,
std::map<wxString, std::unique_ptr<FOOTPRINT>>& aFootprintMap,
const std::map<wxString, EASYEDAPRO::BLOB>& aBlobMap,
const std::multimap<wxString, EASYEDAPRO::POURED>& aPouredMap,
const std::vector<nlohmann::json>& aLines, const wxString& aFpLibName )
{
std::map<wxString, std::vector<nlohmann::json>> componentLines;
std::map<wxString, std::vector<nlohmann::json>> ruleLines;
BOARD_DESIGN_SETTINGS& bds = aBoard->GetDesignSettings();
for( const nlohmann::json& line : aLines )
{
if( line.size() == 0 )
continue;
wxString type = line.at( 0 );
if( type == wxS( "LAYER" ) )
{
int layer = line.at( 1 );
PCB_LAYER_ID klayer = LayerToKi( layer );
wxString layerType = line.at( 2 );
wxString layerName = line.at( 3 );
int layerFlag = line.at( 4 );
if( layerFlag != 0 )
{
LSET blayers = aBoard->GetEnabledLayers();
blayers.set( klayer );
aBoard->SetEnabledLayers( blayers );
aBoard->SetLayerName( klayer, layerName );
}
}
else if( type == wxS( "NET" ) )
{
wxString netname = line.at( 1 );
aBoard->Add( new NETINFO_ITEM( aBoard, netname, aBoard->GetNetCount() + 1 ),
ADD_MODE::APPEND );
}
else if( type == wxS( "RULE" ) )
{
wxString ruleType = line.at( 1 );
wxString ruleName = line.at( 2 );
int isDefault = line.at( 3 );
nlohmann::json ruleData = line.at( 4 );
if( ruleType == wxS( "3" ) && isDefault ) // Track width
{
wxString units = ruleData.at( 0 );
double minVal = ruleData.at( 1 );
double optVal = ruleData.at( 2 );
double maxVal = ruleData.at( 3 );
bds.m_TrackMinWidth = ScaleSize( minVal );
}
else if( ruleType == wxS( "1" ) && isDefault )
{
wxString units = ruleData.at( 0 );
nlohmann::json table = ruleData.at( 1 );
int minVal = INT_MAX;
for( const std::vector<int>& arr : table )
{
for( int val : arr )
{
if( val < minVal )
minVal = val;
}
}
bds.m_MinClearance = ScaleSize( minVal );
}
ruleLines[ruleType].push_back( line );
}
else if( type == wxS( "VIA" ) || type == wxS( "LINE" ) || type == wxS( "ARC" )
|| type == wxS( "POLY" ) || type == wxS( "FILL" ) || type == wxS( "POUR" ) )
{
wxString uuid = line.at( 1 );
// if( line.at( 2 ).is_number() )
// int unk = line.at( 2 ).get<int>();
// else if( line.at( 2 ).is_string() )
// int unk = wxAtoi( line.at( 2 ).get<wxString>() );
wxString netname = line.at( 3 );
if( type == wxS( "VIA" ) )
{
VECTOR2D center;
center.x = line.at( 5 );
center.y = line.at( 6 );
double drill = line.at( 7 );
double dia = line.at( 8 );
std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( aBoard );
via->SetPosition( ScalePos( center ) );
via->SetDrill( ScaleSize( drill ) );
via->SetWidth( ScaleSize( dia ) );
via->SetNet( aBoard->FindNet( netname ) );
aBoard->Add( via.release(), ADD_MODE::APPEND );
}
else if( type == wxS( "LINE" ) )
{
int layer = line.at( 4 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
VECTOR2D start;
start.x = line.at( 5 );
start.y = line.at( 6 );
VECTOR2D end;
end.x = line.at( 7 );
end.y = line.at( 8 );
double width = line.at( 9 );
std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( aBoard );
track->SetLayer( klayer );
track->SetStart( ScalePos( start ) );
track->SetEnd( ScalePos( end ) );
track->SetWidth( ScaleSize( width ) );
track->SetNet( aBoard->FindNet( netname ) );
aBoard->Add( track.release(), ADD_MODE::APPEND );
}
else if( type == wxS( "ARC" ) )
{
int layer = line.at( 4 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
VECTOR2D start;
start.x = line.at( 5 );
start.y = line.at( 6 );
VECTOR2D end;
end.x = line.at( 7 );
end.y = line.at( 8 );
double angle = line.at( 9 );
double width = line.at( 10 );
VECTOR2D delta = end - start;
VECTOR2D mid = ( start + delta / 2 );
double ha = angle / 2;
double hd = delta.EuclideanNorm() / 2;
double cdist = hd / tan( DEG2RAD( ha ) );
VECTOR2D center = mid + delta.Perpendicular().Resize( cdist );
SHAPE_ARC sarc;
sarc.ConstructFromStartEndCenter( ScalePos( start ), ScalePos( end ),
ScalePos( center ), angle >= 0, width );
std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( aBoard, &sarc );
arc->SetWidth( ScaleSize( width ) );
arc->SetLayer( klayer );
arc->SetNet( aBoard->FindNet( netname ) );
aBoard->Add( arc.release(), ADD_MODE::APPEND );
}
else if( type == wxS( "FILL" ) )
{
int layer = line.at( 4 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
double width = line.at( 5 );
nlohmann::json polyDataList = line.at( 7 );
if( !polyDataList.at( 0 ).is_array() )
polyDataList = nlohmann::json::array( { polyDataList } );
std::vector<SHAPE_LINE_CHAIN> contours;
for( nlohmann::json& polyData : polyDataList )
{
SHAPE_LINE_CHAIN contour = ParseContour( polyData, true );
contour.SetClosed( true );
contours.push_back( contour );
}
SHAPE_POLY_SET zoneFillPoly;
for( SHAPE_LINE_CHAIN& contour : contours )
zoneFillPoly.AddOutline( contour );
zoneFillPoly.RebuildHolesFromContours();
zoneFillPoly.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
zone->SetNet( aBoard->FindNet( netname ) );
zone->SetLayer( klayer );
zone->Outline()->Append( SHAPE_RECT( zoneFillPoly.BBox() ).Outline() );
zone->SetFilledPolysList( klayer, zoneFillPoly );
zone->SetAssignedPriority( 500 );
zone->SetIsFilled( true );
zone->SetNeedRefill( false );
zone->SetLocalClearance( bds.m_MinClearance );
zone->SetMinThickness( bds.m_TrackMinWidth );
aBoard->Add( zone.release(), ADD_MODE::APPEND );
}
else if( type == wxS( "POLY" ) )
{
int layer = line.at( 4 );
PCB_LAYER_ID klayer = LayerToKi( layer );
double thickness = line.at( 5 );
nlohmann::json polyData = line.at( 6 );
std::vector<std::unique_ptr<PCB_SHAPE>> results =
ParsePoly( aBoard, polyData, false, false );
for( auto& shape : results )
{
shape->SetLayer( klayer );
shape->SetWidth( ScaleSize( thickness ) );
aBoard->Add( shape.release(), ADD_MODE::APPEND );
}
}
else if( type == wxS( "POUR" ) )
{
int layer = line.at( 4 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
double lineWidth = line.at( 5 ); // Doesn't matter
wxString pourname = line.at( 6 );
int fillOrder = line.at( 7 ).get<int>();
nlohmann::json polyDataList = line.at( 8 );
std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
zone->SetNet( aBoard->FindNet( netname ) );
zone->SetLayer( klayer );
zone->SetAssignedPriority( 500 - fillOrder );
zone->SetLocalClearance( bds.m_MinClearance );
zone->SetMinThickness( bds.m_TrackMinWidth );
for( nlohmann::json& polyData : polyDataList )
{
SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
contour.SetClosed( true );
zone->Outline()->Append( contour );
}
wxASSERT( zone->Outline()->OutlineCount() == 1 );
SHAPE_POLY_SET fillPolySet;
SHAPE_POLY_SET thermalSpokes;
int entryId = 0;
if( EASYEDAPRO::IMPORT_POURED )
{
auto range = aPouredMap.equal_range( uuid );
for( auto& it = range.first; it != range.second; ++it )
{
const EASYEDAPRO::POURED& poured = it->second;
int unki = poured.unki;
SHAPE_POLY_SET thisPoly;
for( int dataId = 0; dataId < poured.polyData.size(); dataId++ )
{
const nlohmann::json& fillData = poured.polyData[dataId];
const double ptScale = 10;
SHAPE_LINE_CHAIN contour =
ParseContour( fillData, false, ARC_HIGH_DEF / ptScale );
// Scale the fill
for( int i = 0; i < contour.PointCount(); i++ )
contour.SetPoint( i, contour.GetPoint( i ) * ptScale );
if( poured.isPoly )
{
contour.SetClosed( true );
// The contour can be self-intersecting
SHAPE_POLY_SET simple( contour );
simple.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
if( dataId == 0 )
{
thisPoly.Append( simple );
}
else
{
thisPoly.BooleanSubtract( simple,
SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
}
}
else
{
const int thermalWidth = pcbIUScale.mmToIU( 0.2 ); // Generic
for( int segId = 0; segId < contour.SegmentCount(); segId++ )
{
const SEG& seg = contour.CSegment( segId );
TransformOvalToPolygon( thermalSpokes, seg.A, seg.B,
thermalWidth, ARC_LOW_DEF,
ERROR_INSIDE );
}
}
}
fillPolySet.Append( thisPoly );
entryId++;
}
if( !fillPolySet.IsEmpty() )
{
fillPolySet.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
const int strokeWidth = pcbIUScale.MilsToIU( 8 ); // Seems to be 8 mils
fillPolySet.Inflate( strokeWidth / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS,
ARC_HIGH_DEF, false );
fillPolySet.BooleanAdd( thermalSpokes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
fillPolySet.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
zone->SetFilledPolysList( klayer, fillPolySet );
zone->SetNeedRefill( false );
zone->SetIsFilled( true );
}
}
aBoard->Add( zone.release(), ADD_MODE::APPEND );
}
}
else if( type == wxS( "TEARDROP" ) )
{
wxString uuid = line.at( 1 );
wxString netname = line.at( 2 );
int layer = line.at( 3 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
nlohmann::json polyData = line.at( 4 );
SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
contour.SetClosed( true );
std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
zone->SetNet( aBoard->FindNet( netname ) );
zone->SetLayer( klayer );
zone->Outline()->Append( contour );
zone->SetFilledPolysList( klayer, contour );
zone->SetNeedRefill( false );
zone->SetIsFilled( true );
zone->SetAssignedPriority( 600 );
zone->SetLocalClearance( 0 );
zone->SetMinThickness( 0 );
zone->SetTeardropAreaType( TEARDROP_TYPE::TD_UNSPECIFIED );
aBoard->Add( zone.release(), ADD_MODE::APPEND );
}
else if( type == wxS( "REGION" ) )
{
wxString uuid = line.at( 1 );
// if( line.at( 2 ).is_number() )
// int unk = line.at( 2 ).get<int>();
// else if( line.at( 2 ).is_string() )
// int unk = wxAtoi( line.at( 2 ).get<wxString>() );
int layer = line.at( 3 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
double width = line.at( 4 );
std::set<int> flags = line.at( 5 );
nlohmann::json polyDataList = line.at( 6 );
for( nlohmann::json& polyData : polyDataList )
{
SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
contour.SetClosed( true );
std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
zone->SetIsRuleArea( true );
zone->SetDoNotAllowFootprints( !!flags.count( 2 ) );
zone->SetDoNotAllowCopperPour( !!flags.count( 7 ) || !!flags.count( 6 )
|| !!flags.count( 8 ) );
zone->SetDoNotAllowPads( !!flags.count( 7 ) );
zone->SetDoNotAllowTracks( !!flags.count( 7 ) || !!flags.count( 5 ) );
zone->SetDoNotAllowVias( !!flags.count( 7 ) );
zone->SetLayer( klayer );
zone->Outline()->Append( contour );
aBoard->Add( zone.release(), ADD_MODE::APPEND );
}
}
else if( type == wxS( "PAD" ) )
{
wxString netname = line.at( 3 );
std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( aBoard );
std::unique_ptr<PAD> pad = createPAD( footprint.get(), line );
pad->SetNet( aBoard->FindNet( netname ) );
VECTOR2I pos = pad->GetPosition();
EDA_ANGLE orient = pad->GetOrientation();
pad->SetPosition( VECTOR2I() );
pad->SetOrientation( ANGLE_0 );
footprint->Add( pad.release(), ADD_MODE::APPEND );
footprint->SetPosition( pos );
footprint->SetOrientation( orient );
wxString fpName = wxS( "Pad_" ) + line.at( 1 ).get<wxString>();
LIB_ID fpID = EASYEDAPRO::ToKiCadLibID( wxEmptyString, fpName );
footprint->SetFPID( fpID );
footprint->Reference().SetVisible( true );
footprint->Value().SetVisible( true );
footprint->AutoPositionFields();
aBoard->Add( footprint.release(), ADD_MODE::APPEND );
}
else if( type == wxS( "IMAGE" ) )
{
wxString uuid = line.at( 1 );
// if( line.at( 2 ).is_number() )
// int unk = line.at( 2 ).get<int>();
// else if( line.at( 2 ).is_string() )
// int unk = wxAtoi( line.at( 2 ).get<wxString>() );
int layer = line.at( 3 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
VECTOR2D start( line.at( 4 ), line.at( 5 ) );
VECTOR2D size( line.at( 6 ), line.at( 7 ) );
double angle = line.at( 8 ); // from top left corner
int mirror = line.at( 9 );
nlohmann::json polyDataList = line.at( 10 );
BOX2I bbox;
std::vector<SHAPE_LINE_CHAIN> contours;
for( nlohmann::json& polyData : polyDataList )
{
SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
contour.SetClosed( true );
contours.push_back( contour );
bbox.Merge( contour.BBox() );
}
VECTOR2D scale( ScaleSize( size.x ) / bbox.GetSize().x,
ScaleSize( size.y ) / bbox.GetSize().y );
SHAPE_POLY_SET polySet;
for( SHAPE_LINE_CHAIN& contour : contours )
{
for( int i = 0; i < contour.PointCount(); i++ )
{
VECTOR2I pt = contour.CPoint( i );
contour.SetPoint( i, VECTOR2I( pt.x * scale.x, pt.y * scale.y ) );
}
polySet.AddOutline( contour );
}
polySet.RebuildHolesFromContours();
std::unique_ptr<PCB_GROUP> group;
if( polySet.OutlineCount() > 1 )
group = std::make_unique<PCB_GROUP>( aBoard );
BOX2I polyBBox = polySet.BBox();
for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
{
std::unique_ptr<PCB_SHAPE> shape =
std::make_unique<PCB_SHAPE>( aBoard, SHAPE_T::POLY );
shape->SetFilled( true );
shape->SetPolyShape( poly );
shape->SetLayer( klayer );
shape->SetWidth( 0 );
shape->Move( ScalePos( start ) - polyBBox.GetOrigin() );
shape->Rotate( ScalePos( start ), EDA_ANGLE( angle, DEGREES_T ) );
if( IsBackLayer( klayer ) ^ !!mirror )
shape->Mirror( ScalePos( start ), !IsBackLayer( klayer ) );
if( group )
group->AddItem( shape.get() );
aBoard->Add( shape.release(), ADD_MODE::APPEND );
}
if( group )
aBoard->Add( group.release(), ADD_MODE::APPEND );
}
else if( type == wxS( "OBJ" ) )
{
VECTOR2D start, size;
wxString mimeType, base64Data;
double angle = 0;
int flipped = 0;
if( !line.at( 3 ).is_number() )
continue;
int layer = line.at( 3 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
start = VECTOR2D( line.at( 5 ), line.at( 6 ) );
size = VECTOR2D( line.at( 7 ), line.at( 8 ) );
angle = line.at( 9 );
flipped = line.at( 10 );
wxString imageUrl = line.at( 11 );
if( imageUrl.BeforeFirst( ':' ) == wxS( "blob" ) )
{
wxString objectId = imageUrl.AfterLast( ':' );
if( auto blob = get_opt( aBlobMap, objectId ) )
{
wxString blobUrl = blob->url;
if( blobUrl.BeforeFirst( ':' ) == wxS( "data" ) )
{
wxArrayString paramsArr =
wxSplit( blobUrl.AfterFirst( ':' ).BeforeFirst( ',' ), ';', '\0' );
base64Data = blobUrl.AfterFirst( ',' );
if( paramsArr.size() > 0 )
mimeType = paramsArr[0];
}
}
}
VECTOR2D kstart = ScalePos( start );
VECTOR2D ksize = ScaleSize( size );
if( mimeType.empty() || base64Data.empty() )
continue;
wxMemoryBuffer buf = wxBase64Decode( base64Data );
if( mimeType == wxS( "image/svg+xml" ) )
{
// Not yet supported by EasyEDA
}
else
{
VECTOR2D kcenter = kstart + ksize / 2;
std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap =
std::make_unique<PCB_REFERENCE_IMAGE>( aBoard, kcenter, klayer );
wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags()
& ~wxImage::Load_Verbose );
if( bitmap->ReadImageFile( buf ) )
{
double scaleFactor = ScaleSize( size.x ) / bitmap->GetSize().x;
bitmap->SetImageScale( scaleFactor );
// TODO: support non-90-deg angles
bitmap->Rotate( kstart, EDA_ANGLE( angle, DEGREES_T ) );
if( flipped )
{
int x = bitmap->GetPosition().x;
MIRROR( x, KiROUND( kstart.x ) );
bitmap->SetX( x );
bitmap->MutableImage()->Mirror( false );
}
aBoard->Add( bitmap.release(), ADD_MODE::APPEND );
}
}
}
else if( type == wxS( "STRING" ) )
{
wxString uuid = line.at( 1 );
// if( line.at( 2 ).is_number() )
// int unk = line.at( 2 ).get<int>();
// else if( line.at( 2 ).is_string() )
// int unk = wxAtoi( line.at( 2 ).get<wxString>() );
int layer = line.at( 3 ).get<int>();
PCB_LAYER_ID klayer = LayerToKi( layer );
VECTOR2D location( line.at( 4 ), line.at( 5 ) );
wxString string = line.at( 6 );
wxString font = line.at( 7 );
double height = line.at( 8 );
double strokew = line.at( 9 );
int align = line.at( 12 );
double angle = line.at( 13 );
int inverted = line.at( 14 );
int mirror = line.at( 16 );
PCB_TEXT* text = new PCB_TEXT( aBoard );
text->SetText( string );
text->SetLayer( klayer );
text->SetPosition( ScalePos( location ) );
text->SetIsKnockout( inverted );
text->SetTextThickness( ScaleSize( strokew ) );
text->SetTextSize( VECTOR2D( ScaleSize( height * 0.6 ), ScaleSize( height * 0.7 ) ) );
if( font != wxS( "default" ) )
{
text->SetFont( KIFONT::FONT::GetFont( font ) );
//text->SetupRenderCache( text->GetShownText(), EDA_ANGLE( angle, DEGREES_T ) );
//text->AddRenderCacheGlyph();
// TODO: import geometry cache
}
AlignText( text, align );
if( IsBackLayer( klayer ) ^ !!mirror )
{
text->SetMirrored( true );
text->SetTextAngleDegrees( -angle );
}
else
{
text->SetTextAngleDegrees( angle );
}
aBoard->Add( text, ADD_MODE::APPEND );
}
else if( type == wxS( "COMPONENT" ) )
{
wxString compId = line.at( 1 );
componentLines[compId].push_back( line );
}
else if( type == wxS( "ATTR" ) )
{
wxString compId = line.at( 3 );
componentLines[compId].push_back( line );
}
else if( type == wxS( "PAD_NET" ) )
{
wxString compId = line.at( 1 );
componentLines[compId].push_back( line );
}
}
for( auto const& [compId, lines] : componentLines )
{
wxString deviceId;
wxString fpIdOverride;
wxString fpDesignator;
std::map<wxString, wxString> localCompAttribs;
for( auto& line : lines )
{
if( line.size() == 0 )
continue;
wxString type = line.at( 0 );
if( type == wxS( "COMPONENT" ) )
{
localCompAttribs = line.at( 7 );
}
else if( type == wxS( "ATTR" ) )
{
EASYEDAPRO::PCB_ATTR attr = line;
if( attr.key == wxS( "Device" ) )
deviceId = attr.value;
else if( attr.key == wxS( "Footprint" ) )
fpIdOverride = attr.value;
else if( attr.key == wxS( "Designator" ) )
fpDesignator = attr.value;
}
}
if( deviceId.empty() )
continue;
nlohmann::json compAttrs = aProject.at( "devices" ).at( deviceId ).at( "attributes" );
wxString fpId;
if( !fpIdOverride.IsEmpty() )
fpId = fpIdOverride;
else
fpId = compAttrs.at( "Footprint" ).get<wxString>();
auto it = aFootprintMap.find( fpId );
if( it == aFootprintMap.end() )
{
wxLogError( "Footprint of '%s' with uuid '%s' not found.", fpDesignator, fpId );
continue;
}
std::unique_ptr<FOOTPRINT>& footprintOrig = it->second;
std::unique_ptr<FOOTPRINT> footprint( static_cast<FOOTPRINT*>( footprintOrig->Clone() ) );
wxString modelUuid, modelTitle, modelTransform;
if( auto val = get_opt( localCompAttribs, "3D Model" ) )
modelUuid = *val;
else
modelUuid = compAttrs.value<wxString>( "3D Model", "" );
if( auto val = get_opt( localCompAttribs, "3D Model Title" ) )
modelTitle = val->Trim();
else
modelTitle = compAttrs.value<wxString>( "3D Model Title", modelUuid ).Trim();
if( auto val = get_opt( localCompAttribs, "3D Model Transform" ) )
modelTransform = *val;
else
modelTransform = compAttrs.value<wxString>( "3D Model Transform", "" );
fillFootprintModelInfo( footprint.get(), modelUuid, modelTitle, modelTransform );
footprint->SetParent( aBoard );
for( auto& line : lines )
{
if( line.size() == 0 )
continue;
wxString type = line.at( 0 );
if( type == wxS( "COMPONENT" ) )
{
int layer = line.at( 3 );
PCB_LAYER_ID klayer = LayerToKi( layer );
VECTOR2D center( line.at( 4 ), line.at( 5 ) );
double orient = line.at( 6 );
//std::map<wxString, wxString> props = line.at( 7 );
if( klayer == B_Cu )
footprint->Flip( footprint->GetPosition(), false );
footprint->SetOrientationDegrees( orient );
footprint->SetPosition( ScalePos( center ) );
}
else if( type == wxS( "ATTR" ) )
{
EASYEDAPRO::PCB_ATTR attr = line;
PCB_LAYER_ID klayer = LayerToKi( attr.layer );
PCB_TEXT* text = nullptr;
bool add = false;
if( attr.key == wxS( "Designator" ) )
{
if( attr.key == wxS( "Designator" ) )
{
text = footprint->GetField( REFERENCE_FIELD );
}
else
{
text = new PCB_TEXT( footprint.get() );
add = true;
}
if( attr.fontName != wxS( "default" ) )
text->SetFont( KIFONT::FONT::GetFont( attr.fontName ) );
if( attr.valVisible && attr.keyVisible )
{
text->SetText( attr.key + ':' + attr.value );
}
else if( attr.keyVisible )
{
text->SetText( attr.key );
}
else
{
text->SetText( attr.value );
}
text->SetVisible( attr.keyVisible || attr.valVisible );
text->SetLayer( klayer );
text->SetPosition( ScalePos( attr.position ) );
text->SetTextAngleDegrees( footprint->IsFlipped() ? -attr.rotation
: attr.rotation );
text->SetIsKnockout( attr.inverted );
text->SetTextThickness( ScaleSize( attr.strokeWidth ) );
text->SetTextSize( VECTOR2D( ScaleSize( attr.height * 0.55 ),
ScaleSize( attr.height * 0.6 ) ) );
AlignText( text, attr.textOrigin );
if( add )
footprint->Add( text, ADD_MODE::APPEND );
}
}
else if( type == wxS( "PAD_NET" ) )
{
wxString padNumber = line.at( 2 );
wxString padNet = line.at( 3 );
PAD* pad = footprint->FindPadByNumber( padNumber );
if( pad )
{
pad->SetNet( aBoard->FindNet( padNet ) );
}
else
{
// Not a pad
}
}
}
aBoard->Add( footprint.release(), ADD_MODE::APPEND );
}
// 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 );
// Center the board
BOX2I outlineBbox = aBoard->ComputeBoundingBox( true );
PAGE_INFO pageInfo = aBoard->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;
aBoard->Move( offset );
bds.SetAuxOrigin( offset );
}