kicad/eeschema/sch_io/easyedapro/sch_easyedapro_parser.cpp

1403 lines
48 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 "sch_easyedapro_parser.h"
#include <io/easyedapro/easyedapro_import_utils.h>
#include <core/map_helpers.h>
#include <sch_io/sch_io_mgr.h>
#include <schematic.h>
#include <sch_sheet.h>
#include <sch_line.h>
#include <sch_bitmap.h>
#include <sch_no_connect.h>
#include <sch_label.h>
#include <sch_junction.h>
#include <sch_edit_frame.h>
#include <sch_shape.h>
#include <string_utils.h>
#include <bezier_curves.h>
#include <wx/base64.h>
#include <wx/url.h>
#include <wx/mstream.h>
#include <gfx_import_utils.h>
#include <import_gfx/svg_import_plugin.h>
#include <import_gfx/graphics_importer_lib_symbol.h>
#include <import_gfx/graphics_importer_sch.h>
SCH_EASYEDAPRO_PARSER::SCH_EASYEDAPRO_PARSER( SCHEMATIC* aSchematic,
PROGRESS_REPORTER* aProgressReporter )
{
m_schematic = aSchematic;
}
SCH_EASYEDAPRO_PARSER::~SCH_EASYEDAPRO_PARSER()
{
}
double SCH_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;
}
double SCH_EASYEDAPRO_PARSER::SizeToKi( wxString aValue )
{
return ScaleSize( Convert( aValue ) );
}
static LINE_STYLE ConvertStrokeStyle( int aStyle )
{
if( aStyle == 0 )
return LINE_STYLE::SOLID;
else if( aStyle == 1 )
return LINE_STYLE::DASH;
else if( aStyle == 2 )
return LINE_STYLE::DOT;
else if( aStyle == 3 )
return LINE_STYLE::DASHDOT;
return LINE_STYLE::DEFAULT;
}
template <typename T>
void SCH_EASYEDAPRO_PARSER::ApplyFontStyle( const std::map<wxString, nlohmann::json>& fontStyles,
T& text, const wxString& styleStr )
{
auto it = fontStyles.find( styleStr );
if( it == fontStyles.end() )
return;
nlohmann::json style = it->second;
if( !style.is_array() )
return;
if( style.size() < 12 )
return;
if( style.at( 3 ).is_string() )
{
COLOR4D color( style.at( 3 ).get<wxString>() );
text->SetTextColor( color );
}
if( style.at( 4 ).is_string() )
{
wxString fontname = ( style.at( 4 ) );
if( !fontname.IsSameAs( wxS( "default" ), false ) )
text->SetFont( KIFONT::FONT::GetFont( fontname ) );
}
if( style.at( 5 ).is_number() )
{
double size = style.at( 5 ).get<double>() * 0.5;
text->SetTextSize( VECTOR2I( ScaleSize( size ), ScaleSize( size ) ) );
}
if( style.at( 10 ).is_number() )
{
int valign = style.at( 10 );
if( !text->GetText().Contains( wxS( "\n" ) ) )
{
if( valign == 0 )
text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
else if( valign == 1 )
text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
else if( valign == 2 )
text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
}
else
{
text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
// TODO: align by first line
}
}
else
{
text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
}
if( style.at( 11 ).is_number() )
{
int halign = style.at( 11 );
if( halign == 0 )
text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
else if( halign == 1 )
text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
else if( halign == 2 )
text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
}
else
{
text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
}
}
template <typename T>
void SCH_EASYEDAPRO_PARSER::ApplyLineStyle( const std::map<wxString, nlohmann::json>& lineStyles,
T& shape, const wxString& styleStr )
{
auto it = lineStyles.find( styleStr );
if( it == lineStyles.end() )
return;
nlohmann::json style = it->second;
if( !style.is_array() )
return;
if( style.size() < 6 )
return;
STROKE_PARAMS stroke = shape->GetStroke();
if( style.at( 2 ).is_string() )
{
wxString colorStr = style.at( 2 ).get<wxString>();
if( !colorStr.empty() && colorStr.starts_with( wxS( "#" ) ) )
{
COLOR4D color( colorStr );
stroke.SetColor( color );
}
}
if( style.at( 3 ).is_number() )
{
int dashStyle = style.at( 3 );
stroke.SetLineStyle( ConvertStrokeStyle( dashStyle ) );
}
if( style.at( 5 ).is_number() )
{
double thickness = style.at( 5 );
stroke.SetWidth( ScaleSize( thickness ) );
}
shape->SetStroke( stroke );
}
wxString SCH_EASYEDAPRO_PARSER::ResolveFieldVariables(
const wxString aInput, const std::map<wxString, wxString>& aDeviceAttributes )
{
wxString inputText = aInput;
wxString resolvedText;
int variableCount = 0;
// Resolve variables
// ={Variable1}text{Variable2}
do
{
if( !inputText.StartsWith( wxS( "={" ) ) )
return inputText;
resolvedText.Clear();
variableCount = 0;
for( size_t i = 1; i < inputText.size(); )
{
wxUniChar c = inputText[i++];
if( c == '{' )
{
wxString varName;
bool endFound = false;
while( i < inputText.size() )
{
c = inputText[i++];
if( c == '}' )
{
endFound = true;
break;
}
varName << c;
}
if( !endFound )
return inputText;
wxString varValue =
get_def( aDeviceAttributes, varName, wxString::Format( "{%s!}", varName ) );
resolvedText << varValue;
variableCount++;
}
else
{
resolvedText << c;
}
}
inputText = resolvedText;
} while( variableCount > 0 );
return resolvedText;
}
template <typename T>
void SCH_EASYEDAPRO_PARSER::ApplyAttrToField( const std::map<wxString, nlohmann::json>& fontStyles,
T* field, const EASYEDAPRO::SCH_ATTR& aAttr,
bool aIsSym, bool aToSym,
const std::map<wxString, wxString>& aDeviceAttributes,
SCH_SYMBOL* aParent )
{
EDA_TEXT* text = static_cast<EDA_TEXT*>( field );
text->SetText( ResolveFieldVariables( aAttr.value, aDeviceAttributes ) );
text->SetVisible( aAttr.keyVisible || aAttr.valVisible );
field->SetNameShown( aAttr.keyVisible );
if( aAttr.position )
{
field->SetPosition( !aIsSym ? ScalePos( *aAttr.position )
: ScalePosSym( *aAttr.position ) );
}
ApplyFontStyle( fontStyles, text, aAttr.fontStyle );
auto parent = aParent;
if( parent && parent->Type() == SCH_SYMBOL_T )
{
int orient = static_cast<SCH_SYMBOL*>( parent )->GetOrientation();
if( orient == SYM_ORIENT_180 )
{
text->SetVertJustify( static_cast<GR_TEXT_V_ALIGN_T>( -text->GetVertJustify() ) );
text->SetHorizJustify( static_cast<GR_TEXT_H_ALIGN_T>( -text->GetHorizJustify() ) );
}
else if( orient == SYM_MIRROR_X + SYM_ORIENT_0 )
{
text->SetVertJustify( static_cast<GR_TEXT_V_ALIGN_T>( -text->GetVertJustify() ) );
}
else if( orient == SYM_MIRROR_Y + SYM_ORIENT_0 )
{
text->SetHorizJustify( static_cast<GR_TEXT_H_ALIGN_T>( -text->GetHorizJustify() ) );
}
else if( orient == SYM_MIRROR_Y + SYM_ORIENT_180 )
{
text->SetHorizJustify( static_cast<GR_TEXT_H_ALIGN_T>( text->GetHorizJustify() ) );
}
else if( orient == SYM_ORIENT_90 )
{
text->SetTextAngle( ANGLE_VERTICAL );
text->SetVertJustify( static_cast<GR_TEXT_V_ALIGN_T>( -text->GetVertJustify() ) );
text->SetHorizJustify( static_cast<GR_TEXT_H_ALIGN_T>( -text->GetHorizJustify() ) );
}
if( orient == SYM_ORIENT_270 )
{
text->SetTextAngle( ANGLE_VERTICAL );
}
else if( orient == SYM_MIRROR_X + SYM_ORIENT_90 )
{
text->SetTextAngle( ANGLE_VERTICAL );
text->SetVertJustify( static_cast<GR_TEXT_V_ALIGN_T>( -text->GetVertJustify() ) );
text->SetHorizJustify( static_cast<GR_TEXT_H_ALIGN_T>( -text->GetHorizJustify() ) );
}
else if( orient == SYM_MIRROR_X + SYM_ORIENT_270 )
{
text->SetTextAngle( ANGLE_VERTICAL );
text->SetVertJustify( static_cast<GR_TEXT_V_ALIGN_T>( -text->GetVertJustify() ) );
}
else if( orient == SYM_MIRROR_Y + SYM_ORIENT_90 )
{
text->SetTextAngle( ANGLE_VERTICAL );
text->SetHorizJustify( static_cast<GR_TEXT_H_ALIGN_T>( -text->GetHorizJustify() ) );
}
else if( orient == SYM_MIRROR_Y + SYM_ORIENT_270 )
{
text->SetHorizJustify( static_cast<GR_TEXT_H_ALIGN_T>( text->GetHorizJustify() ) );
}
if( aAttr.rotation == 90 )
{
if( text->GetTextAngle() == ANGLE_HORIZONTAL )
text->SetTextAngle( ANGLE_VERTICAL );
else
text->SetTextAngle( ANGLE_HORIZONTAL );
if( orient == SYM_ORIENT_90 )
{
text->SetVertJustify( static_cast<GR_TEXT_V_ALIGN_T>( -text->GetVertJustify() ) );
text->SetHorizJustify( static_cast<GR_TEXT_H_ALIGN_T>( -text->GetHorizJustify() ) );
}
if( orient == SYM_ORIENT_270 )
{
text->SetVertJustify( static_cast<GR_TEXT_V_ALIGN_T>( -text->GetVertJustify() ) );
text->SetHorizJustify( static_cast<GR_TEXT_H_ALIGN_T>( -text->GetHorizJustify() ) );
}
else if( orient == SYM_MIRROR_X + SYM_ORIENT_90 )
{
text->SetVertJustify( static_cast<GR_TEXT_V_ALIGN_T>( -text->GetVertJustify() ) );
}
}
}
}
EASYEDAPRO::SYM_INFO SCH_EASYEDAPRO_PARSER::ParseSymbol( const std::vector<nlohmann::json>& aLines )
{
EASYEDAPRO::SYM_INFO symInfo;
std::unique_ptr<LIB_SYMBOL> ksymbolPtr = std::make_unique<LIB_SYMBOL>( wxEmptyString );
LIB_SYMBOL* ksymbol = ksymbolPtr.get();
std::map<wxString, nlohmann::json> lineStyles;
std::map<wxString, nlohmann::json> fontStyles;
std::map<wxString, int> partUnits;
std::map<int, std::map<wxString, EASYEDAPRO::SCH_ATTR>> unitAttributes;
std::map<int, std::map<wxString, std::vector<nlohmann::json>>> unitParentedLines;
int totalUnits = 0;
for( const nlohmann::json& line : aLines )
{
wxString type = line.at( 0 );
if( type == wxS( "LINESTYLE" ) )
lineStyles[line.at( 1 )] = line;
else if( type == wxS( "FONTSTYLE" ) )
fontStyles[line.at( 1 )] = line;
else if( type == wxS( "PART" ) )
partUnits[line.at( 1 )] = ++totalUnits;
}
symInfo.partUnits = partUnits;
ksymbol->SetUnitCount( totalUnits, false );
int currentUnit = 1;
for( const nlohmann::json& line : aLines )
{
wxString type = line.at( 0 );
if( type == wxS( "PART" ) )
{
currentUnit = partUnits.at( line.at( 1 ) );
}
else if( type == wxS( "RECT" ) )
{
VECTOR2D start( line.at( 2 ), line.at( 3 ) );
VECTOR2D end( line.at( 4 ), line.at( 5 ) );
wxString styleStr = line.at( 9 );
auto rect = std::make_unique<SCH_SHAPE>( SHAPE_T::RECTANGLE, LAYER_DEVICE );
rect->SetStart( ScalePosSym( start ) );
rect->SetEnd( ScalePosSym( end ) );
rect->SetUnit( currentUnit );
ApplyLineStyle( lineStyles, rect, styleStr );
ksymbol->AddDrawItem( rect.release() );
}
else if( type == wxS( "CIRCLE" ) )
{
VECTOR2D center( line.at( 2 ), line.at( 3 ) );
double radius = line.at( 4 );
wxString styleStr = line.at( 5 );
auto circle = std::make_unique<SCH_SHAPE>( SHAPE_T::CIRCLE, LAYER_DEVICE );
circle->SetCenter( ScalePosSym( center ) );
circle->SetEnd( circle->GetCenter() + VECTOR2I( ScaleSize( radius ), 0 ) );
circle->SetUnit( currentUnit );
ApplyLineStyle( lineStyles, circle, styleStr );
ksymbol->AddDrawItem( circle.release() );
}
else if( type == wxS( "ARC" ) )
{
VECTOR2D start( line.at( 2 ), line.at( 3 ) );
VECTOR2D mid( line.at( 4 ), line.at( 5 ) );
VECTOR2D end( line.at( 6 ), line.at( 7 ) );
wxString styleStr = line.at( 8 );
VECTOR2D kstart = ScalePosSym( start );
VECTOR2D kmid = ScalePosSym( mid );
VECTOR2D kend = ScalePosSym( end );
VECTOR2D kcenter = CalcArcCenter( kstart, kmid, kend );
auto shape = std::make_unique<SCH_SHAPE>( SHAPE_T::ARC, LAYER_DEVICE );
shape->SetStart( kstart );
shape->SetEnd( kend );
shape->SetCenter( kcenter );
if( SEG( start, end ).Side( mid ) != SEG( kstart, kend ).Side( shape->GetArcMid() ) )
{
shape->SetStart( kend );
shape->SetEnd( kstart );
}
shape->SetUnit( currentUnit );
ApplyLineStyle( lineStyles, shape, styleStr );
ksymbol->AddDrawItem( shape.release() );
}
else if( type == wxS( "POLY" ) )
{
std::vector<double> points = line.at( 2 );
wxString styleStr = line.at( 4 );
auto shape = std::make_unique<SCH_SHAPE>( SHAPE_T::POLY, LAYER_DEVICE );
for( size_t i = 1; i < points.size(); i += 2 )
shape->AddPoint( ScalePosSym( VECTOR2D( points[i - 1], points[i] ) ) );
shape->SetUnit( currentUnit );
ApplyLineStyle( lineStyles, shape, styleStr );
ksymbol->AddDrawItem( shape.release() );
}
else if( type == wxS( "TEXT" ) )
{
VECTOR2D pos( line.at( 2 ), line.at( 3 ) );
double angle = line.at( 4 );
wxString textStr = line.at( 5 );
wxString fontStyleStr = line.at( 6 );
auto text = std::make_unique<SCH_TEXT>( ScalePosSym( pos ), UnescapeHTML( textStr ),
LAYER_DEVICE );
text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
text->SetTextAngleDegrees( angle );
text->SetUnit( currentUnit );
ApplyFontStyle( fontStyles, text, fontStyleStr );
ksymbol->AddDrawItem( text.release() );
}
else if( type == wxS( "OBJ" ) )
{
VECTOR2D start, size;
wxString mimeType, data;
//double angle = 0;
int upsideDown = 0;
if( line.at( 3 ).is_number() )
{
start = VECTOR2D( line.at( 3 ), line.at( 4 ) );
size = VECTOR2D( line.at( 5 ), line.at( 6 ) );
//angle = line.at( 7 );
upsideDown = line.at( 8 );
wxString imageUrl = line.at( 9 );
if( imageUrl.BeforeFirst( ':' ) == wxS( "data" ) )
{
wxArrayString paramsArr =
wxSplit( imageUrl.AfterFirst( ':' ).BeforeFirst( ',' ), ';', '\0' );
data = imageUrl.AfterFirst( ',' );
if( paramsArr.size() > 0 )
{
mimeType = paramsArr[0];
}
}
}
else if( line.at( 3 ).is_string() )
{
mimeType = line.at( 3 ).get<wxString>().BeforeFirst( ';' );
start = VECTOR2D( line.at( 4 ), line.at( 5 ) );
size = VECTOR2D( line.at( 6 ), line.at( 7 ) );
//angle = line.at( 8 );
data = line.at( 9 ).get<wxString>();
}
if( mimeType.empty() || data.empty() )
continue;
wxMemoryBuffer buf = wxBase64Decode( data );
if( mimeType == wxS( "image/svg+xml" ) )
{
VECTOR2D offset = ScalePosSym( start );
SVG_IMPORT_PLUGIN svgImportPlugin;
GRAPHICS_IMPORTER_LIB_SYMBOL libsymImporter( ksymbol, 0 );
svgImportPlugin.SetImporter( &libsymImporter );
svgImportPlugin.LoadFromMemory( buf );
VECTOR2D imSize( svgImportPlugin.GetImageWidth(),
svgImportPlugin.GetImageHeight() );
VECTOR2D pixelScale( schIUScale.IUTomm( ScaleSize( size.x ) ) / imSize.x,
schIUScale.IUTomm( -ScaleSize( size.y ) ) / imSize.y );
if( upsideDown )
pixelScale.y *= -1;
libsymImporter.SetScale( pixelScale );
VECTOR2D offsetMM( schIUScale.IUTomm( offset.x ), schIUScale.IUTomm( offset.y ) );
libsymImporter.SetImportOffsetMM( offsetMM );
svgImportPlugin.Import();
// TODO: rotation
for( std::unique_ptr<EDA_ITEM>& item : libsymImporter.GetItems() )
ksymbol->AddDrawItem( static_cast<SCH_ITEM*>( item.release() ) );
}
else
{
wxMemoryInputStream memis( buf.GetData(), buf.GetDataLen() );
wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags()
& ~wxImage::Load_Verbose );
wxImage img;
if( img.LoadFile( memis, mimeType ) )
{
int dimMul = img.GetWidth() * img.GetHeight();
double maxPixels = 30000;
if( dimMul > maxPixels )
{
double scale = sqrt( maxPixels / dimMul );
img.Rescale( img.GetWidth() * scale, img.GetHeight() * scale );
}
VECTOR2D pixelScale( ScaleSize( size.x ) / img.GetWidth(),
-ScaleSize( size.y ) / img.GetHeight() );
// TODO: rotation
ConvertImageToLibShapes( ksymbol, 0, img, pixelScale, ScalePosSym( start ) );
}
}
}
else if( type == wxS( "HEAD" ) )
{
symInfo.head = line;
}
else if( type == wxS( "PIN" ) )
{
wxString pinId = line.at( 1 );
unitParentedLines[currentUnit][pinId].push_back( line );
}
else if( type == wxS( "ATTR" ) )
{
wxString parentId = line.at( 2 );
if( parentId.empty() )
{
EASYEDAPRO::SCH_ATTR attr = line;
unitAttributes[currentUnit].emplace( attr.key, attr );
}
else
{
unitParentedLines[currentUnit][parentId].push_back( line );
}
}
}
if( symInfo.head.symbolType == EASYEDAPRO::SYMBOL_TYPE::POWER_PORT
|| symInfo.head.symbolType == EASYEDAPRO::SYMBOL_TYPE::NETPORT )
{
ksymbol->SetPower();
ksymbol->GetReferenceField().SetText( wxS( "#PWR" ) );
ksymbol->GetReferenceField().SetVisible( false );
ksymbol->SetKeyWords( wxS( "power-flag" ) );
ksymbol->SetShowPinNames( false );
ksymbol->SetShowPinNumbers( false );
if( auto globalNetAttr = get_opt( unitAttributes[1], wxS( "Global Net Name" ) ) )
{
ApplyAttrToField( fontStyles, &ksymbol->GetValueField(), *globalNetAttr, true, true );
wxString globalNetname = globalNetAttr->value;
if( !globalNetname.empty() )
{
ksymbol->SetDescription( wxString::Format(
_( "Power symbol creates a global label with name '%s'" ),
globalNetname ) );
}
}
}
else
{
auto designatorAttr = get_opt( unitAttributes[1], wxS( "Designator" ) );
if( designatorAttr && !designatorAttr->value.empty() )
{
wxString symbolPrefix = designatorAttr->value;
if( !symbolPrefix.EndsWith( wxS( "?" ) ) )
symbolPrefix += wxS( "?" );
ksymbol->GetReferenceField().SetText( symbolPrefix );
}
}
for( auto& [unitId, parentedLines] : unitParentedLines )
{
for( auto& [pinId, lines] : parentedLines )
{
std::optional<EASYEDAPRO::SYM_PIN> epin;
std::map<wxString, EASYEDAPRO::SCH_ATTR> pinAttributes;
for( const nlohmann::json& line : lines )
{
wxString type = line.at( 0 );
if( type == wxS( "ATTR" ) )
{
EASYEDAPRO::SCH_ATTR attr = line;
pinAttributes.emplace( attr.key, attr );
}
else if( type == wxS( "PIN" ) )
{
epin = line;
}
}
if( !epin )
continue;
EASYEDAPRO::PIN_INFO pinInfo;
pinInfo.pin = *epin;
std::unique_ptr<SCH_PIN> pin = std::make_unique<SCH_PIN>( ksymbol );
pin->SetUnit( unitId );
pin->SetLength( ScaleSize( epin->length ) );
pin->SetPosition( ScalePosSym( epin->position ) );
PIN_ORIENTATION orient = PIN_ORIENTATION::PIN_RIGHT;
if( epin->rotation == 0 )
orient = PIN_ORIENTATION::PIN_RIGHT;
if( epin->rotation == 90 )
orient = PIN_ORIENTATION::PIN_UP;
if( epin->rotation == 180 )
orient = PIN_ORIENTATION::PIN_LEFT;
if( epin->rotation == 270 )
orient = PIN_ORIENTATION::PIN_DOWN;
pin->SetOrientation( orient );
if( symInfo.head.symbolType == EASYEDAPRO::SYMBOL_TYPE::POWER_PORT )
{
pin->SetName( ksymbol->GetName() );
//pin->SetVisible( false );
}
else if( auto pinNameAttr = get_opt( pinAttributes, "NAME" ) )
{
pin->SetName( pinNameAttr->value );
pinInfo.name = pinNameAttr->value;
if( !pinNameAttr->valVisible )
pin->SetNameTextSize( schIUScale.MilsToIU( 1 ) );
}
if( auto pinNumAttr = get_opt( pinAttributes, "NUMBER" ) )
{
pin->SetNumber( pinNumAttr->value );
pinInfo.number = pinNumAttr->value;
if( !pinNumAttr->valVisible )
pin->SetNumberTextSize( schIUScale.MilsToIU( 1 ) );
}
if( symInfo.head.symbolType == EASYEDAPRO::SYMBOL_TYPE::POWER_PORT )
{
pin->SetType( ELECTRICAL_PINTYPE::PT_POWER_IN );
}
else if( auto pinTypeAttr = get_opt( pinAttributes, "Pin Type" ) )
{
if( pinTypeAttr->value == wxS( "IN" ) )
pin->SetType( ELECTRICAL_PINTYPE::PT_INPUT );
if( pinTypeAttr->value == wxS( "OUT" ) )
pin->SetType( ELECTRICAL_PINTYPE::PT_OUTPUT );
if( pinTypeAttr->value == wxS( "BI" ) )
pin->SetType( ELECTRICAL_PINTYPE::PT_BIDI );
}
if( get_opt( pinAttributes, "NO_CONNECT" ) )
pin->SetType( ELECTRICAL_PINTYPE::PT_NC );
if( pin->GetNumberTextSize() * int( pin->GetNumber().size() ) > pin->GetLength() )
pin->SetNumberTextSize( pin->GetLength() / pin->GetNumber().size() );
symInfo.pins.push_back( pinInfo );
ksymbol->AddDrawItem( pin.release() );
}
}
symInfo.symbolAttr = get_opt( unitAttributes[1], "Symbol" ); // TODO: per-unit
/*BOX2I bbox = ksymbol->GetBodyBoundingBox( 0, 0, true, true );
bbox.Inflate( schIUScale.MilsToIU( 10 ) );*/
/*ksymbol->GetReferenceField().SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
ksymbol->GetReferenceField().SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
ksymbol->GetReferenceField().SetPosition( VECTOR2I( bbox.GetCenter().x, -bbox.GetTop() ) );
ksymbol->GetValueField().SetVertJustify( GR_TEXT_V_ALIGN_TOP );
ksymbol->GetValueField().SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
ksymbol->GetValueField().SetPosition( VECTOR2I( bbox.GetCenter().x, -bbox.GetBottom() ) );*/
symInfo.libSymbol = std::move( ksymbolPtr );
return symInfo;
}
void SCH_EASYEDAPRO_PARSER::ParseSchematic( SCHEMATIC* aSchematic, SCH_SHEET* aRootSheet,
const nlohmann::json& aProject,
std::map<wxString, EASYEDAPRO::SYM_INFO>& aSymbolMap,
const std::map<wxString, EASYEDAPRO::BLOB>& aBlobMap,
const std::vector<nlohmann::json>& aLines,
const wxString& aLibName )
{
std::vector<std::unique_ptr<SCH_ITEM>> createdItems;
std::map<wxString, std::vector<nlohmann::json>> parentedLines;
std::map<wxString, std::vector<nlohmann::json>> ruleLines;
std::map<wxString, nlohmann::json> lineStyles;
std::map<wxString, nlohmann::json> fontStyles;
for( const nlohmann::json& line : aLines )
{
wxString type = line.at( 0 );
if( type == wxS( "LINESTYLE" ) )
lineStyles[line.at( 1 )] = line;
else if( type == wxS( "FONTSTYLE" ) )
fontStyles[line.at( 1 )] = line;
}
for( const nlohmann::json& line : aLines )
{
wxString type = line.at( 0 );
if( type == wxS( "RECT" ) )
{
VECTOR2D start( line.at( 2 ), line.at( 3 ) );
VECTOR2D end( line.at( 4 ), line.at( 5 ) );
wxString styleStr = line.at( 9 );
std::unique_ptr<SCH_SHAPE> rect = std::make_unique<SCH_SHAPE>( SHAPE_T::RECTANGLE );
rect->SetStart( ScalePos( start ) );
rect->SetEnd( ScalePos( end ) );
ApplyLineStyle( lineStyles, rect, styleStr );
createdItems.push_back( std::move( rect ) );
}
else if( type == wxS( "CIRCLE" ) )
{
VECTOR2D center( line.at( 2 ), line.at( 3 ) );
double radius = line.at( 4 );
wxString styleStr = line.at( 5 );
std::unique_ptr<SCH_SHAPE> circle = std::make_unique<SCH_SHAPE>( SHAPE_T::CIRCLE );
circle->SetCenter( ScalePos( center ) );
circle->SetEnd( circle->GetCenter() + VECTOR2I( ScaleSize( radius ), 0 ) );
ApplyLineStyle( lineStyles, circle, styleStr );
createdItems.push_back( std::move( circle ) );
}
else if( type == wxS( "POLY" ) )
{
std::vector<double> points = line.at( 2 );
wxString styleStr = line.at( 4 );
SHAPE_LINE_CHAIN chain;
for( size_t i = 1; i < points.size(); i += 2 )
chain.Append( ScalePos( VECTOR2D( points[i - 1], points[i] ) ) );
for( int segId = 0; segId < chain.SegmentCount(); segId++ )
{
const SEG& seg = chain.CSegment( segId );
std::unique_ptr<SCH_LINE> schLine =
std::make_unique<SCH_LINE>( seg.A, LAYER_NOTES );
schLine->SetEndPoint( seg.B );
ApplyLineStyle( lineStyles, schLine, styleStr );
createdItems.push_back( std::move( schLine ) );
}
}
else if( type == wxS( "TEXT" ) )
{
VECTOR2D pos( line.at( 2 ), line.at( 3 ) );
double angle = line.at( 4 );
wxString textStr = line.at( 5 );
wxString fontStyleStr = line.at( 6 );
std::unique_ptr<SCH_TEXT> text =
std::make_unique<SCH_TEXT>( ScalePos( pos ), UnescapeHTML( textStr ) );
text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
text->SetTextAngleDegrees( angle );
ApplyFontStyle( fontStyles, text, fontStyleStr );
createdItems.push_back( std::move( text ) );
}
else if( type == wxS( "OBJ" ) )
{
VECTOR2D start, size;
wxString mimeType, base64Data;
double angle = 0;
int flipped = 0;
if( line.at( 3 ).is_number() )
{
start = VECTOR2D( line.at( 3 ), line.at( 4 ) );
size = VECTOR2D( line.at( 5 ), line.at( 6 ) );
angle = line.at( 7 );
flipped = line.at( 8 );
wxString imageUrl = line.at( 9 );
if( imageUrl.BeforeFirst( ':' ) == wxS( "data" ) )
{
wxArrayString paramsArr =
wxSplit( imageUrl.AfterFirst( ':' ).BeforeFirst( ',' ), ';', '\0' );
base64Data = imageUrl.AfterFirst( ',' );
if( paramsArr.size() > 0 )
mimeType = paramsArr[0];
}
else 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];
}
}
}
}
else if( line.at( 3 ).is_string() )
{
mimeType = line.at( 3 ).get<wxString>().BeforeFirst( ';' );
start = VECTOR2D( line.at( 4 ), line.at( 5 ) );
size = VECTOR2D( line.at( 6 ), line.at( 7 ) );
angle = line.at( 8 );
base64Data = line.at( 9 ).get<wxString>();
}
VECTOR2D kstart = ScalePos( start );
VECTOR2D ksize = ScaleSize( size );
if( mimeType.empty() || base64Data.empty() )
continue;
wxMemoryBuffer buf = wxBase64Decode( base64Data );
if( mimeType == wxS( "image/svg+xml" ) )
{
SVG_IMPORT_PLUGIN svgImportPlugin;
GRAPHICS_IMPORTER_SCH schImporter;
svgImportPlugin.SetImporter( &schImporter );
svgImportPlugin.LoadFromMemory( buf );
VECTOR2D imSize( svgImportPlugin.GetImageWidth(),
svgImportPlugin.GetImageHeight() );
VECTOR2D pixelScale( schIUScale.IUTomm( ScaleSize( size.x ) ) / imSize.x,
schIUScale.IUTomm( ScaleSize( size.y ) ) / imSize.y );
schImporter.SetScale( pixelScale );
VECTOR2D offsetMM( schIUScale.IUTomm( kstart.x ), schIUScale.IUTomm( kstart.y ) );
schImporter.SetImportOffsetMM( offsetMM );
svgImportPlugin.Import();
for( std::unique_ptr<EDA_ITEM>& item : schImporter.GetItems() )
{
SCH_ITEM* schItem = static_cast<SCH_ITEM*>( item.release() );
for( double i = angle; i > 0; i -= 90 )
{
if( schItem->Type() == SCH_LINE_T )
{
// Lines need special handling for some reason
schItem->SetFlags( STARTPOINT );
schItem->Rotate( kstart, false );
schItem->ClearFlags( STARTPOINT );
schItem->SetFlags( ENDPOINT );
schItem->Rotate( kstart, false );
schItem->ClearFlags( ENDPOINT );
}
else
{
schItem->Rotate( kstart, false );
}
}
if( flipped )
{
// Lines need special handling for some reason
if( schItem->Type() == SCH_LINE_T )
schItem->SetFlags( STARTPOINT | ENDPOINT );
schItem->MirrorHorizontally( kstart.x );
if( schItem->Type() == SCH_LINE_T )
schItem->ClearFlags( STARTPOINT | ENDPOINT );
}
createdItems.emplace_back( schItem );
}
}
else
{
std::unique_ptr<SCH_BITMAP> bitmap = std::make_unique<SCH_BITMAP>();
wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags()
& ~wxImage::Load_Verbose );
if( bitmap->ReadImageFile( buf ) )
{
VECTOR2D kcenter = kstart + ksize / 2;
double scaleFactor = ScaleSize( size.x ) / bitmap->GetSize().x;
bitmap->SetImageScale( scaleFactor );
bitmap->SetPosition( kcenter );
for( double i = angle; i > 0; i -= 90 )
bitmap->Rotate( kstart, false );
if( flipped )
bitmap->MirrorHorizontally( kstart.x );
createdItems.push_back( std::move( bitmap ) );
}
}
}
if( type == wxS( "WIRE" ) )
{
wxString wireId = line.at( 1 );
parentedLines[wireId].push_back( line );
}
else if( type == wxS( "COMPONENT" ) )
{
wxString compId = line.at( 1 );
parentedLines[compId].push_back( line );
}
else if( type == wxS( "ATTR" ) )
{
wxString compId = line.at( 2 );
parentedLines[compId].push_back( line );
}
}
for( auto& [parentId, lines] : parentedLines )
{
std::optional<EASYEDAPRO::SCH_COMPONENT> component;
std::optional<EASYEDAPRO::SCH_WIRE> wire;
std::map<wxString, EASYEDAPRO::SCH_ATTR> attributes;
for( const nlohmann::json& line : lines )
{
if( line.at( 0 ) == "COMPONENT" )
{
component = line;
}
else if( line.at( 0 ) == "WIRE" )
{
wire = line;
}
else if( line.at( 0 ) == "ATTR" )
{
EASYEDAPRO::SCH_ATTR attr = line;
attributes.emplace( attr.key, attr );
}
}
if( component )
{
auto deviceAttr = get_opt( attributes, "Device" );
auto symbolAttr = get_opt( attributes, "Symbol" );
if( !deviceAttr )
continue;
nlohmann::json compAttrs =
aProject.at( "devices" ).at( deviceAttr->value ).at( "attributes" );
wxString symbolId;
if( symbolAttr && !symbolAttr->value.IsEmpty() )
symbolId = symbolAttr->value;
else
symbolId = compAttrs.at( "Symbol" ).get<wxString>();
auto it = aSymbolMap.find( symbolId );
if( it == aSymbolMap.end() )
{
wxLogError( "Symbol of '%s' with uuid '%s' not found.", component->name, symbolId );
continue;
}
EASYEDAPRO::SYM_INFO& esymInfo = it->second;
LIB_SYMBOL newLibSymbol = *esymInfo.libSymbol.get();
wxString unitName = component->name;
LIB_ID libId = EASYEDAPRO::ToKiCadLibID( aLibName,
newLibSymbol.GetLibId().GetLibItemName() );
auto schSym = std::make_unique<SCH_SYMBOL>( newLibSymbol, libId,
&aSchematic->CurrentSheet(),
esymInfo.partUnits[unitName] );
schSym->SetFootprintFieldText( newLibSymbol.GetFootprintField().GetText() );
for( double i = component->rotation; i > 0; i -= 90 )
schSym->Rotate( VECTOR2I(), false );
if( component->mirror )
schSym->MirrorHorizontally( 0 );
schSym->SetPosition( ScalePos( component->position ) );
if( esymInfo.head.symbolType == EASYEDAPRO::SYMBOL_TYPE::POWER_PORT )
{
if( auto globalNetAttr = get_opt( attributes, "Global Net Name" ) )
{
ApplyAttrToField( fontStyles, schSym->GetField( VALUE_FIELD ), *globalNetAttr,
false, true, compAttrs, schSym.get() );
for( SCH_PIN* pin : schSym->GetAllLibPins() )
pin->SetName( globalNetAttr->value );
}
else
{
schSym->GetField( VALUE_FIELD )
->SetText( newLibSymbol.GetValueField().GetText() );
}
schSym->SetRef( &aSchematic->CurrentSheet(), wxS( "#PWR?" ) );
schSym->GetField( REFERENCE_FIELD )->SetVisible( false );
}
else if( esymInfo.head.symbolType == EASYEDAPRO::SYMBOL_TYPE::NETPORT )
{
auto nameAttr = get_opt( attributes, "Name" );
if( nameAttr )
{
std::unique_ptr<SCH_GLOBALLABEL> label = std::make_unique<SCH_GLOBALLABEL>(
ScalePos( component->position ), nameAttr->value );
SPIN_STYLE spin = SPIN_STYLE::LEFT;
if( esymInfo.symbolAttr )
{
wxString symStr = esymInfo.symbolAttr->value;
if( symStr == wxS( "Netport-IN" ) )
{
spin = SPIN_STYLE::LEFT;
label->SetShape( LABEL_FLAG_SHAPE::L_INPUT );
}
if( symStr == wxS( "Netport-OUT" ) )
{
spin = SPIN_STYLE::RIGHT;
label->SetShape( LABEL_FLAG_SHAPE::L_OUTPUT );
}
if( symStr == wxS( "Netport-BI" ) )
{
spin = SPIN_STYLE::RIGHT;
label->SetShape( LABEL_FLAG_SHAPE::L_BIDI );
}
}
for( double i = component->rotation; i > 0; i -= 90 )
spin = spin.RotateCCW();
label->SetSpinStyle( spin );
nlohmann::json style = fontStyles[nameAttr->fontStyle];
if( !style.is_null() && style.at( 5 ).is_number() )
{
double size = style.at( 5 ).get<double>() * 0.5;
label->SetTextSize( VECTOR2I( ScaleSize( size ), ScaleSize( size ) ) );
}
createdItems.push_back( std::move( label ) );
}
continue;
}
else
{
auto nameAttr = get_opt( attributes, "Name" );
auto valueAttr = get_opt( attributes, "Value" );
std::optional<EASYEDAPRO::SCH_ATTR> targetValueAttr;
if( valueAttr && !valueAttr->value.empty() && valueAttr->valVisible )
targetValueAttr = valueAttr;
else if( nameAttr && !nameAttr->value.empty() && nameAttr->valVisible )
targetValueAttr = nameAttr;
else if( valueAttr && !valueAttr->value.empty() )
targetValueAttr = valueAttr;
else if( nameAttr && !nameAttr->value.empty() )
targetValueAttr = nameAttr;
if( targetValueAttr )
{
ApplyAttrToField( fontStyles, schSym->GetField( VALUE_FIELD ), *targetValueAttr,
false, true, compAttrs, schSym.get() );
}
if( auto descrAttr = get_opt( attributes, "Description" ) )
{
ApplyAttrToField( fontStyles, schSym->GetField( DESCRIPTION_FIELD ), *descrAttr,
false, true, compAttrs, schSym.get() );
}
if( auto designatorAttr = get_opt( attributes, "Designator" ) )
{
ApplyAttrToField( fontStyles, schSym->GetField( REFERENCE_FIELD ),
*designatorAttr, false, true, compAttrs, schSym.get() );
schSym->SetRef( &aSchematic->CurrentSheet(), designatorAttr->value );
}
for( auto& [attrKey, attr] : attributes )
{
if( attrKey == wxS( "Name" ) || attrKey == wxS( "Value" )
|| attrKey == wxS( "Global Net Name" ) || attrKey == wxS( "Designator" )
|| attrKey == wxS( "Description" ) || attrKey == wxS( "Device" )
|| attrKey == wxS( "Footprint" ) || attrKey == wxS( "Symbol" )
|| attrKey == wxS( "Unique ID" ) )
{
continue;
}
if( attr.value.IsEmpty() )
continue;
SCH_FIELD* text =
new SCH_FIELD( schSym.get(), schSym->GetFieldCount(), attrKey );
text->SetPosition( schSym->GetPosition() );
ApplyAttrToField( fontStyles, text, attr, false, true, compAttrs,
schSym.get() );
schSym->AddField( *text );
}
}
for( const EASYEDAPRO::PIN_INFO& pinInfo : esymInfo.pins )
{
wxString pinKey = parentId + pinInfo.pin.id;
auto pinLines = get_opt( parentedLines, pinKey );
if( !pinLines )
continue;
for( const nlohmann::json& pinLine : *pinLines )
{
if( pinLine.at( 0 ) != "ATTR" )
continue;
EASYEDAPRO::SCH_ATTR attr = pinLine;
if( attr.key != wxS( "NO_CONNECT" ) )
continue;
if( SCH_PIN* schPin = schSym->GetPin( pinInfo.number ) )
{
VECTOR2I pos = schSym->GetPinPhysicalPosition( schPin->GetLibPin() );
std::unique_ptr<SCH_NO_CONNECT> noConn =
std::make_unique<SCH_NO_CONNECT>( pos );
createdItems.push_back( std::move( noConn ) );
}
}
}
createdItems.push_back( std::move( schSym ) );
}
else // Not component
{
std::vector<SHAPE_LINE_CHAIN> wireLines;
if( wire )
{
for( const std::vector<double>& ptArr : wire->geometry )
{
SHAPE_LINE_CHAIN chain;
for( size_t i = 1; i < ptArr.size(); i += 2 )
chain.Append( ScalePos( VECTOR2D( ptArr[i - 1], ptArr[i] ) ) );
if( chain.PointCount() < 2 )
continue;
wireLines.push_back( chain );
for( int segId = 0; segId < chain.SegmentCount(); segId++ )
{
const SEG& seg = chain.CSegment( segId );
std::unique_ptr<SCH_LINE> schLine =
std::make_unique<SCH_LINE>( seg.A, LAYER_WIRE );
schLine->SetEndPoint( seg.B );
createdItems.push_back( std::move( schLine ) );
}
}
}
auto netAttr = get_opt( attributes, "NET" );
if( netAttr )
{
if( !netAttr->valVisible )
continue;
VECTOR2I kpos = ScalePos( *netAttr->position );
VECTOR2I nearestPos = kpos;
SEG::ecoord min_dist_sq = VECTOR2I::ECOORD_MAX;
for( const SHAPE_LINE_CHAIN& chain : wireLines )
{
VECTOR2I nearestPt = chain.NearestPoint( kpos, false );
SEG::ecoord dist_sq = ( nearestPt - kpos ).SquaredEuclideanNorm();
if( dist_sq < min_dist_sq )
{
min_dist_sq = dist_sq;
nearestPos = nearestPt;
}
}
std::unique_ptr<SCH_LABEL> label = std::make_unique<SCH_LABEL>();
label->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
label->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
for( double i = netAttr->rotation; i > 0; i -= 90 )
label->Rotate90( true );
label->SetPosition( nearestPos );
label->SetText( netAttr->value );
ApplyFontStyle( fontStyles, label, netAttr->fontStyle );
createdItems.push_back( std::move( label ) );
}
}
}
// Adjust page to content
BOX2I sheetBBox;
for( std::unique_ptr<SCH_ITEM>& ptr : createdItems )
{
if( ptr->Type() == SCH_SYMBOL_T )
sheetBBox.Merge( static_cast<SCH_SYMBOL*>( ptr.get() )->GetBodyAndPinsBoundingBox() );
else
sheetBBox.Merge( ptr->GetBoundingBox() );
}
SCH_SCREEN* screen = aRootSheet->GetScreen();
PAGE_INFO pageInfo = screen->GetPageSettings();
int alignGrid = schIUScale.MilsToIU( 50 );
VECTOR2D offset( -sheetBBox.GetLeft(), -sheetBBox.GetTop() );
offset.x = KiROUND( offset.x / alignGrid ) * alignGrid;
offset.y = KiROUND( offset.y / alignGrid ) * alignGrid;
pageInfo.SetWidthMils( schIUScale.IUToMils( sheetBBox.GetWidth() ) );
pageInfo.SetHeightMils( schIUScale.IUToMils( sheetBBox.GetHeight() ) );
screen->SetPageSettings( pageInfo );
for( std::unique_ptr<SCH_ITEM>& ptr : createdItems )
{
ptr->Move( offset );
screen->Append( ptr.release() );
}
}