/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2023 Alex Shvartzkop * Copyright (C) 2023-2024 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // clang-format off static const std::vector c_attributesWhitelist = { "Value", "Datasheet", "Manufacturer Part", "Manufacturer", "BOM_Manufacturer Part", "BOM_Manufacturer", "Supplier Part", "Supplier", "BOM_Supplier Part", "BOM_Supplier", "LCSC Part Name" }; // clang-format on 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 void SCH_EASYEDAPRO_PARSER::ApplyFontStyle( const std::map& 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() ); 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() * 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 void SCH_EASYEDAPRO_PARSER::ApplyLineStyle( const std::map& 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(); 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& 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 void SCH_EASYEDAPRO_PARSER::ApplyAttrToField( const std::map& fontStyles, T* field, const EASYEDAPRO::SCH_ATTR& aAttr, bool aIsSym, bool aToSym, const std::map& aDeviceAttributes, SCH_SYMBOL* aParent ) { EDA_TEXT* text = static_cast( 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( parent )->GetOrientation(); if( orient == SYM_ORIENT_180 ) { text->SetVertJustify( static_cast( -text->GetVertJustify() ) ); text->SetHorizJustify( static_cast( -text->GetHorizJustify() ) ); } else if( orient == SYM_MIRROR_X + SYM_ORIENT_0 ) { text->SetVertJustify( static_cast( -text->GetVertJustify() ) ); } else if( orient == SYM_MIRROR_Y + SYM_ORIENT_0 ) { text->SetHorizJustify( static_cast( -text->GetHorizJustify() ) ); } else if( orient == SYM_MIRROR_Y + SYM_ORIENT_180 ) { text->SetHorizJustify( static_cast( text->GetHorizJustify() ) ); } else if( orient == SYM_ORIENT_90 ) { text->SetTextAngle( ANGLE_VERTICAL ); text->SetVertJustify( static_cast( -text->GetVertJustify() ) ); text->SetHorizJustify( static_cast( -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( -text->GetVertJustify() ) ); text->SetHorizJustify( static_cast( -text->GetHorizJustify() ) ); } else if( orient == SYM_MIRROR_X + SYM_ORIENT_270 ) { text->SetTextAngle( ANGLE_VERTICAL ); text->SetVertJustify( static_cast( -text->GetVertJustify() ) ); } else if( orient == SYM_MIRROR_Y + SYM_ORIENT_90 ) { text->SetTextAngle( ANGLE_VERTICAL ); text->SetHorizJustify( static_cast( -text->GetHorizJustify() ) ); } else if( orient == SYM_MIRROR_Y + SYM_ORIENT_270 ) { text->SetHorizJustify( static_cast( 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( -text->GetVertJustify() ) ); text->SetHorizJustify( static_cast( -text->GetHorizJustify() ) ); } if( orient == SYM_ORIENT_270 ) { text->SetVertJustify( static_cast( -text->GetVertJustify() ) ); text->SetHorizJustify( static_cast( -text->GetHorizJustify() ) ); } else if( orient == SYM_MIRROR_X + SYM_ORIENT_90 ) { text->SetVertJustify( static_cast( -text->GetVertJustify() ) ); } } } } EASYEDAPRO::SYM_INFO SCH_EASYEDAPRO_PARSER::ParseSymbol( const std::vector& aLines, const std::map& aDeviceAttributes ) { EASYEDAPRO::SYM_INFO symInfo; std::unique_ptr ksymbolPtr = std::make_unique( wxEmptyString ); LIB_SYMBOL* ksymbol = ksymbolPtr.get(); std::map lineStyles; std::map fontStyles; std::map partUnits; std::map> unitAttributes; std::map>> 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( 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( 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 ); auto shape = std::make_unique( SHAPE_T::ARC, LAYER_DEVICE ); shape->SetArcGeometry( kstart, kmid, kend ); shape->SetUnit( currentUnit ); ApplyLineStyle( lineStyles, shape, styleStr ); ksymbol->AddDrawItem( shape.release() ); } else if( type == wxS( "POLY" ) ) { std::vector points = line.at( 2 ); wxString styleStr = line.at( 4 ); auto shape = std::make_unique( 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( 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().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(); } 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& item : libsymImporter.GetItems() ) ksymbol->AddDrawItem( static_cast( 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.RemoveLast(); ksymbol->GetReferenceField().SetText( symbolPrefix ); } for( const wxString& attrName : c_attributesWhitelist ) { if( auto valOpt = get_opt( aDeviceAttributes, attrName ) ) { if( valOpt->empty() ) continue; SCH_FIELD* fd = ksymbol->FindField( attrName, true ); if( !fd ) { fd = new SCH_FIELD( ksymbol, ksymbol->GetNextAvailableFieldId(), attrName ); ksymbol->AddField( fd ); } wxString value = *valOpt; value.Replace( wxS( "\u2103" ), wxS( "\u00B0C" ), true ); // ℃ -> °C fd->SetText( value ); fd->SetVisible( false ); } } } for( auto& [unitId, parentedLines] : unitParentedLines ) { for( auto& [pinId, lines] : parentedLines ) { std::optional epin; std::map 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 pin = std::make_unique( 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& aSymbolMap, const std::map& aBlobMap, const std::vector& aLines, const wxString& aLibName ) { std::vector> createdItems; std::map> parentedLines; std::map> ruleLines; std::map lineStyles; std::map 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 rect = std::make_unique( 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 circle = std::make_unique( 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 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 schLine = std::make_unique( 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 text = std::make_unique( 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().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(); } 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& item : schImporter.GetItems() ) { SCH_ITEM* schItem = static_cast( 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 bitmap = std::make_unique(); 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 component; std::optional wire; std::map 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; std::map compAttrs = aProject.at( "devices" ).at( deviceAttr->value ).at( "attributes" ); wxString symbolId; if( symbolAttr && !symbolAttr->value.IsEmpty() ) symbolId = symbolAttr->value; else symbolId = compAttrs.at( "Symbol" ); 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( 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 label = std::make_unique( 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() * 0.5; label->SetTextSize( VECTOR2I( ScaleSize( size ), ScaleSize( size ) ) ); } createdItems.push_back( std::move( label ) ); } continue; } else { for( const wxString& attrKey : c_attributesWhitelist ) { if( auto valOpt = get_opt( compAttrs, attrKey ) ) { if( valOpt->empty() ) continue; SCH_FIELD* text = schSym->FindField( attrKey, true ); if( !text ) { text = schSym->AddField( SCH_FIELD( schSym.get(), schSym->GetFieldCount(), attrKey ) ); } wxString value = *valOpt; value.Replace( wxS( "\u2103" ), wxS( "\u00B0C" ), true ); // ℃ -> °C text->SetText( value ); text->SetVisible( false ); } } auto nameAttr = get_opt( attributes, "Name" ); auto valueAttr = get_opt( attributes, "Value" ); std::optional 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 = schSym->FindField( attrKey, true ); if( !text ) { text = schSym->AddField( SCH_FIELD( schSym.get(), schSym->GetFieldCount(), attrKey ) ); } text->SetPosition( schSym->GetPosition() ); ApplyAttrToField( fontStyles, text, attr, false, true, compAttrs, schSym.get() ); } } 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 noConn = std::make_unique( pos ); createdItems.push_back( std::move( noConn ) ); } } } createdItems.push_back( std::move( schSym ) ); } else // Not component { std::vector wireLines; if( wire ) { for( const std::vector& 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 schLine = std::make_unique( 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 label = std::make_unique(); 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& ptr : createdItems ) { if( ptr->Type() == SCH_SYMBOL_T ) sheetBBox.Merge( static_cast( 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& ptr : createdItems ) { ptr->Move( offset ); screen->Append( ptr.release() ); } }