From 21ee65aa9c73e39dd81ea68e930209a469af808f Mon Sep 17 00:00:00 2001 From: Alex Shvartzkop Date: Wed, 6 Sep 2023 14:58:39 +0300 Subject: [PATCH] ADDED: Importers for EasyEDA (JLCEDA) Standard / Professional. --- common/CMakeLists.txt | 12 + .../plugins/easyeda/easyeda_parser_base.cpp | 242 +++ common/plugins/easyeda/easyeda_parser_base.h | 65 + .../easyeda/easyeda_parser_structs.cpp | 120 ++ .../plugins/easyeda/easyeda_parser_structs.h | 136 ++ .../easyedapro/easyedapro_import_utils.cpp | 151 ++ .../easyedapro/easyedapro_import_utils.h | 53 + .../plugins/easyedapro/easyedapro_parser.cpp | 274 +++ common/plugins/easyedapro/easyedapro_parser.h | 218 +++ common/string_utils.cpp | 14 + common/wildcards_and_files_ext.cpp | 12 + eeschema/CMakeLists.txt | 13 + eeschema/files-io.cpp | 2 + eeschema/gfx_import_utils.cpp | 155 ++ eeschema/gfx_import_utils.h | 39 + eeschema/sch_io_mgr.cpp | 10 + eeschema/sch_io_mgr.h | 2 + .../easyeda/sch_easyeda_parser.cpp | 1358 +++++++++++++ .../sch_plugins/easyeda/sch_easyeda_parser.h | 80 + .../easyeda/sch_easyeda_plugin.cpp | 583 ++++++ .../sch_plugins/easyeda/sch_easyeda_plugin.h | 90 + .../easyedapro/sch_easyedapro_parser.cpp | 1300 +++++++++++++ .../easyedapro/sch_easyedapro_parser.h | 130 ++ .../easyedapro/sch_easyedapro_plugin.cpp | 566 ++++++ .../easyedapro/sch_easyedapro_plugin.h | 88 + include/string_utils.h | 5 + include/wildcards_and_files_ext.h | 2 + kicad/import_project.cpp | 14 + kicad/kicad_id.h | 4 +- kicad/kicad_manager_frame.cpp | 2 + kicad/kicad_manager_frame.h | 10 + kicad/menubar.cpp | 11 +- libs/kimath/include/geometry/shape_poly_set.h | 6 + libs/kimath/src/geometry/shape_poly_set.cpp | 105 + pcbnew/CMakeLists.txt | 4 +- pcbnew/files.cpp | 2 + pcbnew/io_mgr.cpp | 12 + pcbnew/io_mgr.h | 2 + pcbnew/plugins/easyeda/CMakeLists.txt | 14 + pcbnew/plugins/easyeda/pcb_easyeda_parser.cpp | 1093 +++++++++++ pcbnew/plugins/easyeda/pcb_easyeda_parser.h | 75 + pcbnew/plugins/easyeda/pcb_easyeda_plugin.cpp | 537 ++++++ pcbnew/plugins/easyeda/pcb_easyeda_plugin.h | 84 + pcbnew/plugins/easyedapro/CMakeLists.txt | 14 + .../easyedapro/pcb_easyedapro_parser.cpp | 1713 +++++++++++++++++ .../easyedapro/pcb_easyedapro_parser.h | 102 + .../easyedapro/pcb_easyedapro_plugin.cpp | 409 ++++ .../easyedapro/pcb_easyedapro_plugin.h | 89 + 48 files changed, 10019 insertions(+), 3 deletions(-) create mode 100644 common/plugins/easyeda/easyeda_parser_base.cpp create mode 100644 common/plugins/easyeda/easyeda_parser_base.h create mode 100644 common/plugins/easyeda/easyeda_parser_structs.cpp create mode 100644 common/plugins/easyeda/easyeda_parser_structs.h create mode 100644 common/plugins/easyedapro/easyedapro_import_utils.cpp create mode 100644 common/plugins/easyedapro/easyedapro_import_utils.h create mode 100644 common/plugins/easyedapro/easyedapro_parser.cpp create mode 100644 common/plugins/easyedapro/easyedapro_parser.h create mode 100644 eeschema/gfx_import_utils.cpp create mode 100644 eeschema/gfx_import_utils.h create mode 100644 eeschema/sch_plugins/easyeda/sch_easyeda_parser.cpp create mode 100644 eeschema/sch_plugins/easyeda/sch_easyeda_parser.h create mode 100644 eeschema/sch_plugins/easyeda/sch_easyeda_plugin.cpp create mode 100644 eeschema/sch_plugins/easyeda/sch_easyeda_plugin.h create mode 100644 eeschema/sch_plugins/easyedapro/sch_easyedapro_parser.cpp create mode 100644 eeschema/sch_plugins/easyedapro/sch_easyedapro_parser.h create mode 100644 eeschema/sch_plugins/easyedapro/sch_easyedapro_plugin.cpp create mode 100644 eeschema/sch_plugins/easyedapro/sch_easyedapro_plugin.h create mode 100644 pcbnew/plugins/easyeda/CMakeLists.txt create mode 100644 pcbnew/plugins/easyeda/pcb_easyeda_parser.cpp create mode 100644 pcbnew/plugins/easyeda/pcb_easyeda_parser.h create mode 100644 pcbnew/plugins/easyeda/pcb_easyeda_plugin.cpp create mode 100644 pcbnew/plugins/easyeda/pcb_easyeda_plugin.h create mode 100644 pcbnew/plugins/easyedapro/CMakeLists.txt create mode 100644 pcbnew/plugins/easyedapro/pcb_easyedapro_parser.cpp create mode 100644 pcbnew/plugins/easyedapro/pcb_easyedapro_parser.h create mode 100644 pcbnew/plugins/easyedapro/pcb_easyedapro_plugin.cpp create mode 100644 pcbnew/plugins/easyedapro/pcb_easyedapro_plugin.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 5d6490d62f..f1bc79cf37 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -266,6 +266,16 @@ set( PLUGINS_EAGLE_SRCS plugins/eagle/eagle_parser.cpp ) +set( PLUGINS_EASYEDA_SRCS + plugins/easyeda/easyeda_parser_base.cpp + plugins/easyeda/easyeda_parser_structs.cpp + ) + +set( PLUGINS_EASYEDAPRO_SRCS + plugins/easyedapro/easyedapro_parser.cpp + plugins/easyedapro/easyedapro_import_utils.cpp + ) + set( FONT_SRCS font/font.cpp font/glyph.cpp @@ -295,6 +305,8 @@ set( COMMON_SRCS ${PLUGINS_ALTIUM_SRCS} ${PLUGINS_CADSTAR_SRCS} ${PLUGINS_EAGLE_SRCS} + ${PLUGINS_EASYEDA_SRCS} + ${PLUGINS_EASYEDAPRO_SRCS} ${FONT_SRCS} ${COMMON_IMPORT_GFX_SRCS} jobs/job_dispatcher.cpp diff --git a/common/plugins/easyeda/easyeda_parser_base.cpp b/common/plugins/easyeda/easyeda_parser_base.cpp new file mode 100644 index 0000000000..f7b34f0b33 --- /dev/null +++ b/common/plugins/easyeda/easyeda_parser_base.cpp @@ -0,0 +1,242 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 "easyeda_parser_base.h" + +#include +#include +#include + + +double EASYEDA_PARSER_BASE::Convert( const wxString& aValue ) +{ + double value = 0; + + if( !aValue.ToCDouble( &value ) ) + THROW_IO_ERROR( wxString::Format( _( "Failed to parse number from '%s'" ), aValue ) ); + + return value; +} + + +double EASYEDA_PARSER_BASE::RelPosX( double aValue ) +{ + double value = aValue - m_relOrigin.x; + return ScaleSize( value ); +} + + +double EASYEDA_PARSER_BASE::RelPosY( double aValue ) +{ + double value = aValue - m_relOrigin.y; + return ScaleSize( value ); +} + + +double EASYEDA_PARSER_BASE::RelPosX( const wxString& aValue ) +{ + return RelPosX( Convert( aValue ) ); +} + + +double EASYEDA_PARSER_BASE::RelPosY( const wxString& aValue ) +{ + return RelPosY( Convert( aValue ) ); +} + + +template +VECTOR2 EASYEDA_PARSER_BASE::RelPos( const VECTOR2& aVec ) +{ + return ScalePos( aVec - m_relOrigin ); +} + + +SHAPE_POLY_SET EASYEDA_PARSER_BASE::ParsePolygons( const wxString& data, int aArcMinSegLen ) +{ + SHAPE_POLY_SET result; + + VECTOR2D prevPt; + SHAPE_LINE_CHAIN chain; + + size_t pos = 0; + auto readNumber = [&]( wxString& aOut ) + { + wxUniChar ch = data[pos]; + + while( ch == ' ' || ch == ',' ) + ch = data[++pos]; + + while( isdigit( ch ) || ch == '.' || ch == '-' ) + { + aOut += ch; + pos++; + + if( pos == data.size() ) + break; + + ch = data[pos]; + } + }; + + do + { + wxUniChar sym = data[pos++]; + + if( sym == ' ' ) + continue; + + if( sym == 'M' ) + { + wxString xStr, yStr; + + readNumber( xStr ); + readNumber( yStr ); + + if( chain.PointCount() > 2 ) + { + chain.SetClosed( true ); + result.Append( chain ); + } + + chain.Clear(); + + VECTOR2D pt( Convert( xStr ), Convert( yStr ) ); + chain.Append( RelPos( pt ) ); + + prevPt = pt; + } + else if( sym == 'Z' ) + { + if( chain.PointCount() > 2 ) + { + chain.SetClosed( true ); + result.Append( chain ); + } + chain.Clear(); + } + else if( sym == 'L' ) + { + while( true ) + { + if( pos >= data.size() ) + break; + + wxUniChar ch = data[pos]; + + while( ch == ' ' || ch == ',' ) + { + if( ++pos >= data.size() ) + break; + + ch = data[pos]; + } + + if( !isdigit( ch ) ) + break; + + wxString xStr, yStr; + + readNumber( xStr ); + readNumber( yStr ); + + VECTOR2D pt( Convert( xStr ), Convert( yStr ) ); + chain.Append( RelPos( pt ) ); + + prevPt = pt; + }; + } + else if( sym == 'A' ) + { + wxString radX, radY, unknown, farFlag, cwFlag, endX, endY; + + readNumber( radX ); + readNumber( radY ); + readNumber( unknown ); + readNumber( farFlag ); + readNumber( cwFlag ); + readNumber( endX ); + readNumber( endY ); + + bool isFar = farFlag == wxS( "1" ); + bool cw = cwFlag == wxS( "1" ); + VECTOR2D rad( Convert( radX ), Convert( radY ) ); + VECTOR2D end( Convert( endX ), Convert( endY ) ); + + VECTOR2D start = prevPt; + VECTOR2D delta = end - start; + + double d = delta.EuclideanNorm(); + double h = sqrt( std::max( 0.0, rad.x * rad.x - d * d / 4 ) ); + + //( !far && cw ) => h + //( far && cw ) => -h + //( !far && !cw ) => -h + //( far && !cw ) => h + VECTOR2D arcCenter = + start + delta / 2 + delta.Perpendicular().Resize( ( isFar ^ cw ) ? h : -h ); + + SHAPE_ARC arc; + arc.ConstructFromStartEndCenter( RelPos( start ), RelPos( end ), RelPos( arcCenter ), + !cw ); + + chain.Append( arc ); + + prevPt = end; + } + else if( sym == 'C' ) + { + wxString p1_xStr, p1_yStr, p2_xStr, p2_yStr, p3_xStr, p3_yStr; + readNumber( p1_xStr ); + readNumber( p1_yStr ); + readNumber( p2_xStr ); + readNumber( p2_yStr ); + readNumber( p3_xStr ); + readNumber( p3_yStr ); + + VECTOR2D pt1( Convert( p1_xStr ), Convert( p1_yStr ) ); + VECTOR2D pt2( Convert( p2_xStr ), Convert( p2_yStr ) ); + VECTOR2D pt3( Convert( p3_xStr ), Convert( p3_yStr ) ); + + std::vector ctrlPoints = { RelPos( prevPt ), RelPos( pt1 ), RelPos( pt2 ), + RelPos( pt3 ) }; + BEZIER_POLY converter( ctrlPoints ); + + std::vector bezierPoints; + converter.GetPoly( bezierPoints, aArcMinSegLen, 16 ); + + chain.Append( bezierPoints ); + + prevPt = pt3; + } + } while( pos < data.size() ); + + if( chain.PointCount() > 2 ) + { + chain.SetClosed( true ); + result.Append( chain ); + } + + return result; +} \ No newline at end of file diff --git a/common/plugins/easyeda/easyeda_parser_base.h b/common/plugins/easyeda/easyeda_parser_base.h new file mode 100644 index 0000000000..3f47b3040c --- /dev/null +++ b/common/plugins/easyeda/easyeda_parser_base.h @@ -0,0 +1,65 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef EASYEDA_PARSER_BASE_H_ +#define EASYEDA_PARSER_BASE_H_ + +#include +#include +#include + +class EASYEDA_PARSER_BASE +{ +public: + static double Convert( const wxString& aValue ); + + double ConvertSize( const wxString& aValue ) { return ScaleSize( Convert( aValue ) ); } + + virtual double ScaleSize( double aValue ) = 0; + + double ScaleSize( const wxString& aValue ) { return ScaleSize( Convert( aValue ) ); } + + template + VECTOR2 ScalePos( const VECTOR2& aValue ) + { + return VECTOR2( ScaleSize( aValue.x ), ScaleSize( aValue.y ) ); + } + + double RelPosX( double aValue ); + double RelPosY( double aValue ); + + double RelPosX( const wxString& aValue ); + double RelPosY( const wxString& aValue ); + + template + VECTOR2 RelPos( const VECTOR2& aVec ); + + SHAPE_POLY_SET ParsePolygons( const wxString& aData, int aArcMinSegLen ); + +protected: + VECTOR2D m_relOrigin; +}; + + +#endif // EASYEDA_PARSER_BASE_H_ diff --git a/common/plugins/easyeda/easyeda_parser_structs.cpp b/common/plugins/easyeda/easyeda_parser_structs.cpp new file mode 100644 index 0000000000..736785c8ee --- /dev/null +++ b/common/plugins/easyeda/easyeda_parser_structs.cpp @@ -0,0 +1,120 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 "easyeda_parser_structs.h" + +#include +#include + + +#define PARSE_VALUE( name ) \ + if( j.find( #name ) != j.end() ) \ + j.at( #name ).get_to( d.name ) + +#define PARSE_TO_DOUBLE( name, def ) \ + if( j.find( #name ) == j.end() ) \ + { \ + d.name = def; \ + } \ + else if( j.at( #name ).is_string() ) \ + { \ + wxString str = j.at( #name ).get(); \ + \ + double out = 0; \ + str.ToCDouble( &out ); \ + d.name = out; \ + } \ + else if( j.at( #name ).is_number() ) \ + { \ + d.name = j.at( #name ).get(); \ + } + +void EASYEDA::from_json( const nlohmann::json& j, EASYEDA::DOC_TYPE& d ) +{ + if( j.is_string() ) + { + wxString str = j.get(); + + int out = 0; + str.ToInt( &out ); + d = static_cast( out ); + } + else if( j.is_number() ) + { + d = static_cast( j.get() ); + } +} + +void EASYEDA::from_json( const nlohmann::json& j, EASYEDA::HEAD& d ) +{ + PARSE_VALUE( docType ); + + PARSE_VALUE( editorVersion ); + PARSE_VALUE( title ); + PARSE_VALUE( description ); + + if( j.find( "c_para" ) != j.end() && j.at( "c_para" ).is_object() ) + d.c_para = j.at( "c_para" ); + + PARSE_TO_DOUBLE( x, 0 ); + PARSE_TO_DOUBLE( y, 0 ); +} + +void EASYEDA::from_json( const nlohmann::json& j, EASYEDA::DOCUMENT& d ) +{ + PARSE_VALUE( docType ); + PARSE_VALUE( head ); + + PARSE_VALUE( canvas ); + PARSE_VALUE( shape ); + PARSE_VALUE( dataStr ); +} + +void EASYEDA::from_json( const nlohmann::json& j, EASYEDA::DOCUMENT_PCB& d ) +{ + PARSE_VALUE( c_para ); + d.layers = j.at( "layers" ); + + if( j.find( "DRCRULE" ) != j.end() && j.at( "DRCRULE" ).is_object() ) + d.DRCRULE = j.at( "DRCRULE" ); +} + +void EASYEDA::from_json( const nlohmann::json& j, EASYEDA::DOCUMENT_SYM& d ) +{ + PARSE_VALUE( c_para ); +} + +void EASYEDA::from_json( const nlohmann::json& j, EASYEDA::DOCUMENT_SCHEMATICS& d ) +{ + PARSE_VALUE( schematics ); +} + +void EASYEDA::from_json( const nlohmann::json& j, EASYEDA::C_PARA& d ) +{ + PARSE_VALUE( package ); + PARSE_VALUE( pre ); + PARSE_VALUE( Contributor ); + PARSE_VALUE( link ); + PARSE_VALUE( Model_3D ); +} diff --git a/common/plugins/easyeda/easyeda_parser_structs.h b/common/plugins/easyeda/easyeda_parser_structs.h new file mode 100644 index 0000000000..57332f6383 --- /dev/null +++ b/common/plugins/easyeda/easyeda_parser_structs.h @@ -0,0 +1,136 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef EASYEDA_PARSER_STRUCTS_H_ +#define EASYEDA_PARSER_STRUCTS_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace EASYEDA +{ + +enum class DOC_TYPE +{ + UNKNOWN = 0, + + SCHEMATIC_SHEET = 1, + SYMBOL = 2, + PCB = 3, + PCB_COMPONENT = 4, + SCHEMATIC_LIST = 5, + PCB_MODULE = 14, +}; + +struct HEAD +{ + DOC_TYPE docType = DOC_TYPE::UNKNOWN; + + wxString editorVersion; + wxString title; + wxString description; + + double x = 0; + double y = 0; + + std::optional> c_para; +}; + +struct DOCUMENT +{ + std::optional docType; // May be here or in head + HEAD head; + + // BBox + // colors + wxString canvas; + wxArrayString shape; + std::optional dataStr; +}; + +struct C_PARA +{ + wxString package; + wxString pre; + wxString Contributor; + wxString link; + wxString Model_3D; +}; + +struct DOCUMENT_PCB +{ + std::optional> c_para; + std::vector layers; + std::optional uuid; + std::optional> DRCRULE; +}; + +struct DOCUMENT_SYM +{ + std::optional> c_para; +}; + +struct DOCUMENT_SCHEMATICS +{ + std::optional> schematics; +}; + +void from_json( const nlohmann::json& j, EASYEDA::DOC_TYPE& d ); +void from_json( const nlohmann::json& j, EASYEDA::HEAD& d ); +void from_json( const nlohmann::json& j, EASYEDA::DOCUMENT& d ); +void from_json( const nlohmann::json& j, EASYEDA::C_PARA& d ); +void from_json( const nlohmann::json& j, EASYEDA::DOCUMENT_PCB& d ); +void from_json( const nlohmann::json& j, EASYEDA::DOCUMENT_SYM& d ); +void from_json( const nlohmann::json& j, EASYEDA::DOCUMENT_SCHEMATICS& d ); + +enum class POWER_FLAG_STYLE +{ + UNKNOWN = -1, + + CIRCLE = 0, + ARROW = 1, + BAR = 2, + WAVE = 3, + POWER_GROUND = 4, + SIGNAL_GROUND = 5, + EARTH = 6, + GOST_ARROW = 7, + GOST_POWER_GROUND = 8, + GOST_EARTH = 9, + GOST_BAR = 10 +}; + +} // namespace EASYEDA + + +#endif // EASYEDA_PARSER_STRUCTS_H_ diff --git a/common/plugins/easyedapro/easyedapro_import_utils.cpp b/common/plugins/easyedapro/easyedapro_import_utils.cpp new file mode 100644 index 0000000000..be3a62fc7d --- /dev/null +++ b/common/plugins/easyedapro/easyedapro_import_utils.cpp @@ -0,0 +1,151 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 "easyedapro_import_utils.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + + +wxString EASYEDAPRO::ShortenLibName( wxString aProjectName ) +{ + wxString shortenedName = aProjectName; + shortenedName.Replace( wxS( "ProProject_" ), wxS( "" ) ); + shortenedName.Replace( wxS( "ProDocument_" ), wxS( "" ) ); + shortenedName = shortenedName.substr( 0, 10 ); + + return LIB_ID::FixIllegalChars( shortenedName + wxS( "-easyedapro" ), true ); +} + + +LIB_ID EASYEDAPRO::ToKiCadLibID( const wxString& aLibName, const wxString& aLibReference ) +{ + wxString libName = LIB_ID::FixIllegalChars( aLibName, true ); + wxString libReference = EscapeString( aLibReference, CTX_LIBID ); + + wxString key = !aLibName.empty() ? ( aLibName + ':' + libReference ) : libReference; + + LIB_ID libId; + libId.Parse( key, true ); + + return libId; +} + + +nlohmann::json EASYEDAPRO::ReadProjectFile( const wxString& aZipFileName ) +{ + std::shared_ptr entry; + wxFFileInputStream in( aZipFileName ); + wxZipInputStream zip( in ); + + while( entry.reset( zip.GetNextEntry() ), entry.get() != NULL ) + { + wxString name = entry->GetName(); + + if( name == wxS( "project.json" ) ) + { + wxMemoryOutputStream memos; + memos << zip; + wxStreamBuffer* buf = memos.GetOutputStreamBuffer(); + wxString str( (char*) buf->GetBufferStart(), buf->GetBufferSize() ); + + return nlohmann::json::parse( str ); + } + } + + THROW_IO_ERROR( wxString::Format( _( "'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro " + "project file. Cannot find project.json." ), + aZipFileName ) ); +} + + +void EASYEDAPRO::IterateZipFiles( + const wxString& aFileName, + std::function aCallback ) +{ + std::shared_ptr entry; + wxFFileInputStream in( aFileName ); + wxZipInputStream zip( in ); + + if( !zip.IsOk() ) + { + THROW_IO_ERROR( wxString::Format( _( "Cannot read ZIP archive '%s'" ), aFileName ) ); + } + + while( entry.reset( zip.GetNextEntry() ), entry.get() != NULL ) + { + wxString name = entry->GetName(); + wxString baseName = name.AfterLast( '\\' ).AfterLast( '/' ).BeforeFirst( '.' ); + + try + { + if( aCallback( name, baseName, zip ) ) + break; + } + catch( nlohmann::json::exception& e ) + { + THROW_IO_ERROR( + wxString::Format( _( "JSON error reading '%s': %s" ), name, e.what() ) ); + } + catch( std::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error reading '%s': %s" ), name, e.what() ) ); + } + } +} + + +std::vector EASYEDAPRO::ParseJsonLines( wxInputStream& aInput, + const wxString& aSource ) +{ + wxTextInputStream txt( aInput, wxS( " " ), wxConvUTF8 ); + + int currentLine = 1; + + std::vector lines; + while( aInput.CanRead() ) + { + try + { + nlohmann::json js = nlohmann::json::parse( txt.ReadLine() ); + lines.emplace_back( js ); + } + catch( nlohmann::json::exception& e ) + { + wxLogWarning( wxString::Format( _( "Cannot parse JSON line %d in '%s': %s" ), + currentLine, aSource, e.what() ) ); + } + + currentLine++; + } + + return lines; +} diff --git a/common/plugins/easyedapro/easyedapro_import_utils.h b/common/plugins/easyedapro/easyedapro_import_utils.h new file mode 100644 index 0000000000..0fb37338ce --- /dev/null +++ b/common/plugins/easyedapro/easyedapro_import_utils.h @@ -0,0 +1,53 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef EASYEDAPRO_IMPORT_UTILS_H_ +#define EASYEDAPRO_IMPORT_UTILS_H_ + +#include +#include +#include + +#define EASY_IT_CONTINUE return false +#define EASY_IT_BREAK return true + +namespace EASYEDAPRO +{ + +wxString ShortenLibName( wxString aProjectName ); + +LIB_ID ToKiCadLibID( const wxString& aLibName, const wxString& aLibReference ); + +nlohmann::json ReadProjectFile( const wxString& aZipFileName ); + +void IterateZipFiles( + const wxString& aFileName, + std::function aCallback ); + +std::vector ParseJsonLines( wxInputStream& aInput, const wxString& aSource ); + +} // namespace EASYEDAPRO + + +#endif // EASYEDAPRO_IMPORT_UTILS_H_ diff --git a/common/plugins/easyedapro/easyedapro_parser.cpp b/common/plugins/easyedapro/easyedapro_parser.cpp new file mode 100644 index 0000000000..240810591b --- /dev/null +++ b/common/plugins/easyedapro/easyedapro_parser.cpp @@ -0,0 +1,274 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 "easyedapro_parser.h" + +#include + +#include +#include +#include + + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::SCH_ATTR& d ) +{ + d.id = j.at( 1 ).get(); + d.parentId = j.at( 2 ).get(); + d.key = j.at( 3 ).get(); + d.value = j.at( 4 ).get(); + + if( j.at( 5 ).is_number() ) + d.keyVisible = j.at( 5 ).get(); + else if( j.at( 5 ).is_boolean() ) + d.keyVisible = j.at( 5 ).get(); + + if( j.at( 6 ).is_number() ) + d.valVisible = j.at( 6 ).get(); + else if( j.at( 6 ).is_boolean() ) + d.valVisible = j.at( 6 ).get(); + + if( j.at( 7 ).is_number() && j.at( 8 ).is_number() ) + d.position = VECTOR2D( j.at( 7 ), j.at( 8 ) ); + + if( j.at( 9 ).is_number() ) + d.rotation = j.at( 9 ); + + if( j.at( 10 ).is_string() ) + d.fontStyle = j.at( 10 ).get(); +} + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::PCB_ATTR& d ) +{ + d.id = j.at( 1 ).get(); + d.parentId = j.at( 3 ).get(); + d.layer = j.at( 4 ); + + if( j.at( 5 ).is_number() && j.at( 6 ).is_number() ) + d.position = VECTOR2D( j.at( 5 ), j.at( 6 ) ); + + d.key = j.at( 7 ).get(); + d.value = j.at( 8 ).get(); + + if( j.at( 9 ).is_number() ) + d.keyVisible = j.at( 9 ).get(); + else if( j.at( 9 ).is_boolean() ) + d.keyVisible = j.at( 9 ).get(); + + if( j.at( 10 ).is_number() ) + d.valVisible = j.at( 10 ).get(); + else if( j.at( 10 ).is_boolean() ) + d.valVisible = j.at( 10 ).get(); + + if( j.at( 11 ).is_string() ) + d.fontName = j.at( 11 ).get(); + + if( j.at( 12 ).is_number() ) + d.height = j.at( 12 ); + + if( j.at( 13 ).is_number() ) + d.strokeWidth = j.at( 13 ); + + if( j.at( 16 ).is_number() ) + d.textOrigin = j.at( 16 ); + + if( j.at( 17 ).is_number() ) + d.rotation = j.at( 17 ); + + if( j.at( 18 ).is_number() ) + d.inverted = j.at( 18 ); +} + + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::SCH_COMPONENT& d ) +{ + d.id = j.at( 1 ).get(); + d.name = j.at( 2 ).get(); + + if( j.at( 3 ).is_number() && j.at( 4 ).is_number() ) + d.position = VECTOR2D( j.at( 3 ), j.at( 4 ) ); + + if( j.at( 5 ).is_number() ) + d.rotation = j.at( 5 ); + + if( j.at( 6 ).is_number() ) + d.mirror = j.at( 6 ).get(); + + if( j.at( 6 ).is_number() ) + d.unk1 = j.at( 6 ); + + if( j.at( 7 ).is_object() ) + d.customProps = j.at( 7 ); + + if( j.at( 8 ).is_number() ) + d.unk2 = j.at( 8 ); +} + + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::SCH_WIRE& d ) +{ + d.id = j.at( 1 ).get(); + d.geometry = j.at( 2 ); + + if( j.at( 3 ).is_string() ) + d.lineStyle = j.at( 3 ).get(); +} + + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::SYM_PIN& d ) +{ + d.id = j.at( 1 ).get(); + + if( j.at( 4 ).is_number() && j.at( 5 ).is_number() ) + d.position = VECTOR2D( j.at( 4 ), j.at( 5 ) ); + + if( j.at( 6 ).is_number() ) + d.length = j.at( 6 ); + + if( j.at( 7 ).is_number() ) + d.rotation = j.at( 7 ); + + if( j.at( 9 ).is_number() ) + d.inverted = j.at( 9 ).get() == 2; +} + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::SYM_HEAD& d ) +{ + if( !j.at( 1 ).is_object() ) + return; + + nlohmann::json config = j.at( 1 ); + + d.origin.x = config.value( "originX", 0 ); + d.origin.y = config.value( "originY", 0 ); + d.maxId = config.value( "maxId", 0 ); + d.version = config.value( "version", "" ); + d.symbolType = config.value( "symbolType", SYMBOL_TYPE::NORMAL ); + +} + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_SHEET& d ) +{ + d.name = j.value( "name", "" ); + d.uuid = j.value( "uuid", "" ); + d.id = j.value( "id", 0 ); +} + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_SCHEMATIC& d ) +{ + d.name = j.value( "name", "" ); + d.sheets = j.value( "sheets", std::vector{} ); +} + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_BOARD& d ) +{ + d.schematic = j.value( "schematic", "" ); + d.pcb = j.value( "pcb", "" ); +} + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_SYMBOL& d ) +{ + if( j.at( "source" ).is_string() ) + d.source = j.at( "source" ).get(); + + if( j.at( "desc" ).is_string() ) + d.desc = j.at( "desc" ).get(); + + if( j.at( "title" ).is_string() ) + d.title = j.at( "title" ).get(); + + if( j.at( "version" ).is_string() ) + d.version = j.at( "version" ).get(); + + if( j.at( "type" ).is_number() ) + d.type = j.at( "type" ); + + if( j.at( "tags" ).is_object() ) + d.tags = j.at( "tags" ); + + if( j.find( "custom_tags" ) != j.end() && j.at( "custom_tags" ).is_object() ) + d.custom_tags = j.at( "custom_tags" ); +} + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_FOOTPRINT& d ) +{ + if( j.at( "source" ).is_string() ) + d.source = j.at( "source" ).get(); + + if( j.at( "desc" ).is_string() ) + d.desc = j.at( "desc" ).get(); + + if( j.at( "title" ).is_string() ) + d.title = j.at( "title" ).get(); + + if( j.at( "version" ).is_string() ) + d.version = j.at( "version" ).get(); + + if( j.at( "type" ).is_number() ) + d.type = j.at( "type" ); + + if( j.at( "tags" ).is_object() ) + d.tags = j.at( "tags" ); + + if( j.find( "custom_tags" ) != j.end() && j.at( "custom_tags" ).is_object() ) + d.custom_tags = j.at( "custom_tags" ); +} + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_DEVICE& d ) +{ + if( j.at( "source" ).is_string() ) + d.source = j.at( "source" ).get(); + + if( j.at( "description" ).is_string() ) + d.description = j.at( "description" ).get(); + + if( j.at( "title" ).is_string() ) + d.title = j.at( "title" ).get(); + + if( j.at( "version" ).is_string() ) + d.version = j.at( "version" ).get(); + + if( j.at( "tags" ).is_object() ) + d.tags = j.at( "tags" ); + + if( j.find( "custom_tags" ) != j.end() && j.at( "custom_tags" ).is_object() ) + d.custom_tags = j.at( "custom_tags" ); + + if( j.at( "attributes" ).is_object() ) + d.attributes = j.at( "attributes" ); +} + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::BLOB& d ) +{ + d.objectId = j.at( 1 ).get(); + d.url = j.at( 3 ).get(); +} + +void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::POURED& d ) +{ + d.pouredId = j.at( 1 ).get(); + d.parentId = j.at( 2 ).get(); + d.unki = j.at( 3 ).get(); + d.isPoly = j.at( 4 ).get(); + d.polyData = j.at( 5 ); +} \ No newline at end of file diff --git a/common/plugins/easyedapro/easyedapro_parser.h b/common/plugins/easyedapro/easyedapro_parser.h new file mode 100644 index 0000000000..aaf8062770 --- /dev/null +++ b/common/plugins/easyedapro/easyedapro_parser.h @@ -0,0 +1,218 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef EASYEDAPRO_PARSER_H_ +#define EASYEDAPRO_PARSER_H_ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + + +namespace EASYEDAPRO +{ + +static const bool IMPORT_POURED = false; + +enum class SYMBOL_TYPE +{ + NORMAL = 2, + POWER_PORT = 18, + NETPORT = 19, + SHEET_SYMBOL = 20, + SHORT = 22, +}; + +enum class FOOTPRINT_TYPE +{ + NORMAL = 4, +}; + +struct SCH_ATTR +{ + wxString id; + wxString parentId; + wxString key; + wxString value; + bool keyVisible = false; + bool valVisible = false; + std::optional position; + double rotation = 0; + wxString fontStyle; +}; + +struct PCB_ATTR +{ + wxString id; + int layer = 0; + wxString parentId; + int textOrigin = 0; + VECTOR2D position; + wxString key; + wxString value; + bool keyVisible = false; + bool valVisible = false; + wxString fontName; + double height = 0; + double strokeWidth = 0; + double rotation = 0; + int inverted = 0; +}; + +struct SCH_COMPONENT +{ + wxString id; + wxString name; + + VECTOR2D position; + double rotation = 0; + bool mirror = false; + + nlohmann::json customProps; + + int unk1 = 0; + int unk2 = 0; +}; + +struct SCH_WIRE +{ + wxString id; + std::vector> geometry; + wxString lineStyle; + + int unk1 = 0; +}; + +struct SYM_PIN +{ + wxString id; + + VECTOR2D position; + double length = 0; + double rotation = 0; + bool inverted = false; +}; + +struct SYM_HEAD +{ + VECTOR2D origin; + wxString version; + int maxId = 0; + SYMBOL_TYPE symbolType = SYMBOL_TYPE::NORMAL; +}; + +struct PRJ_SHEET +{ + int id = 0; + wxString name; + wxString uuid; +}; + +struct PRJ_SCHEMATIC +{ + wxString name; + std::vector sheets; +}; + +struct PRJ_BOARD +{ + wxString schematic; + wxString pcb; +}; + +struct PRJ_SYMBOL +{ + wxString source; + wxString desc; + nlohmann::json tags; + nlohmann::json custom_tags; + wxString title; + wxString version; + SYMBOL_TYPE type = SYMBOL_TYPE::NORMAL; +}; + +struct PRJ_FOOTPRINT +{ + wxString source; + wxString desc; + nlohmann::json tags; + nlohmann::json custom_tags; + wxString title; + wxString version; + FOOTPRINT_TYPE type = FOOTPRINT_TYPE::NORMAL; +}; + +struct PRJ_DEVICE +{ + wxString source; + wxString description; + nlohmann::json tags; + nlohmann::json custom_tags; + wxString title; + wxString version; + std::map attributes; +}; + +struct BLOB +{ + wxString objectId; + wxString url; +}; + +struct POURED +{ + wxString pouredId; + wxString parentId; + int unki = 0; + bool isPoly = false; + nlohmann::json polyData; +}; + +void from_json( const nlohmann::json& j, EASYEDAPRO::SCH_ATTR& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::PCB_ATTR& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::SCH_COMPONENT& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::SCH_WIRE& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::SYM_PIN& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::SYM_HEAD& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_SHEET& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_SCHEMATIC& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_BOARD& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_SYMBOL& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_FOOTPRINT& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_DEVICE& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::BLOB& d ); +void from_json( const nlohmann::json& j, EASYEDAPRO::POURED& d ); + +} // namespace EASYEDAPRO + + +#endif // EASYEDAPRO_PARSER_H_ diff --git a/common/string_utils.cpp b/common/string_utils.cpp index 6f9abce855..971c36546b 100644 --- a/common/string_utils.cpp +++ b/common/string_utils.cpp @@ -534,6 +534,20 @@ wxString EscapeHTML( const wxString& aString ) } +wxString UnescapeHTML( const wxString& aString ) +{ + wxString converted = aString; + + converted.Replace( wxS( """ ), wxS( "\"" ) ); + converted.Replace( wxS( "'" ), wxS( "'" ) ); + converted.Replace( wxS( "&" ), wxS( "&" ) ); + converted.Replace( wxS( "<" ), wxS( "<" ) ); + converted.Replace( wxS( ">" ), wxS( ">" ) ); + + return converted; +} + + bool NoPrintableChars( const wxString& aString ) { wxString tmp = aString; diff --git a/common/wildcards_and_files_ext.cpp b/common/wildcards_and_files_ext.cpp index 1c56302b4f..2ce481b3e9 100644 --- a/common/wildcards_and_files_ext.cpp +++ b/common/wildcards_and_files_ext.cpp @@ -291,6 +291,18 @@ wxString AllegroNetlistFileWildcard() } +wxString EasyEdaArchiveWildcard() +{ + return _( "EasyEDA (JLCEDA) Std backup archive" ) + AddFileExtListToFilter( { "zip" } ); +} + + +wxString EasyEdaProFileWildcard() +{ + return _( "EasyEDA (JLCEDA) Pro files" ) + AddFileExtListToFilter( { "epro", "zip" } ); +} + + wxString PcbFileWildcard() { return _( "KiCad printed circuit board files" ) diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index 7a524681fc..549bde99e2 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -60,6 +60,16 @@ set( EESCHEMA_SCH_PLUGINS_LTSPICE sch_plugins/ltspice/ltspice_sch_plugin.cpp ) +set( EESCHEMA_SCH_PLUGINS_EASYEDA + sch_plugins/easyeda/sch_easyeda_parser.cpp + sch_plugins/easyeda/sch_easyeda_plugin.cpp + ) + +set( EESCHEMA_SCH_PLUGINS_EASYEDAPRO + sch_plugins/easyedapro/sch_easyedapro_parser.cpp + sch_plugins/easyedapro/sch_easyedapro_plugin.cpp + ) + set( EESCHEMA_DLGS dialogs/dialog_annotate.cpp dialogs/dialog_annotate_base.cpp @@ -278,6 +288,8 @@ set( EESCHEMA_SRCS ${EESCHEMA_SCH_PLUGINS_ALTIUM} ${EESCHEMA_SCH_PLUGINS_CADSTAR} ${EESCHEMA_SCH_PLUGINS_LTSPICE} + ${EESCHEMA_SCH_PLUGINS_EASYEDA} + ${EESCHEMA_SCH_PLUGINS_EASYEDAPRO} ${EESCHEMA_SIM_SRCS} ${EESCHEMA_WIDGETS} ${EESCHEMA_IMPORT_GFX} @@ -301,6 +313,7 @@ set( EESCHEMA_SRCS fields_grid_table.cpp files-io.cpp generate_alias_info.cpp + gfx_import_utils.cpp picksymbol.cpp lib_field.cpp lib_item.cpp diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp index 2cd6f294e8..e14c710b62 100644 --- a/eeschema/files-io.cpp +++ b/eeschema/files-io.cpp @@ -1249,6 +1249,8 @@ void SCH_EDIT_FRAME::importFile( const wxString& aFileName, int aFileType ) case SCH_IO_MGR::SCH_CADSTAR_ARCHIVE: case SCH_IO_MGR::SCH_EAGLE: case SCH_IO_MGR::SCH_LTSPICE: + case SCH_IO_MGR::SCH_EASYEDA: + case SCH_IO_MGR::SCH_EASYEDAPRO: { // We insist on caller sending us an absolute path, if it does not, we say it's a bug. wxCHECK_MSG( filename.IsAbsolute(), /*void*/, diff --git a/eeschema/gfx_import_utils.cpp b/eeschema/gfx_import_utils.cpp new file mode 100644 index 0000000000..9804e79603 --- /dev/null +++ b/eeschema/gfx_import_utils.cpp @@ -0,0 +1,155 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 "gfx_import_utils.h" + +#include +#include +#include +#include +#include + + +std::unordered_map ConvertImageToPolygons( wxImage img, + VECTOR2D pixelScale ) +{ + // Quantize the image + for( int y = 0; y < img.GetHeight(); y++ ) + { + for( int x = 0; x < img.GetWidth(); x++ ) + { + int r = img.GetRed( x, y ); + int g = img.GetGreen( x, y ); + int b = img.GetBlue( x, y ); + int a = img.GetAlpha( x, y ); + + int roundBits = 5; // 32 + r = std::min( r >> roundBits << roundBits, 0xFF ); + g = std::min( g >> roundBits << roundBits, 0xFF ); + b = std::min( b >> roundBits << roundBits, 0xFF ); + a = std::min( a >> roundBits << roundBits, 0xFF ); + + img.SetRGB( x, y, r, g, b ); + img.SetAlpha( x, y, a ); + } + } + + std::unordered_map colorPolys; + + // Create polygon sets + for( int y = 0; y < img.GetHeight(); y++ ) + { + for( int x = 0; x < img.GetWidth(); x++ ) + { + uint32_t r = img.GetRed( x, y ); + uint32_t g = img.GetGreen( x, y ); + uint32_t b = img.GetBlue( x, y ); + uint32_t a = img.GetAlpha( x, y ); + + if( a > 0 ) + { + uint32_t color = r | ( g << 8 ) | ( b << 16 ) | ( a << 24 ); + + SHAPE_POLY_SET& colorPoly = colorPolys[color]; + + SHAPE_LINE_CHAIN chain; + chain.Append( x * pixelScale.x, y * pixelScale.y, true ); + chain.Append( ( x + 1 ) * pixelScale.x, y * pixelScale.y, true ); + chain.Append( ( x + 1 ) * pixelScale.x, ( y + 1 ) * pixelScale.y, true ); + chain.Append( x * pixelScale.x, ( y + 1 ) * pixelScale.y, true ); + chain.SetClosed( true ); + + colorPoly.AddOutline( chain ); + } + } + } + + for( auto& [color, polySet] : colorPolys ) + { + polySet.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + + for( int i = 0; i < polySet.OutlineCount(); i++ ) + { + SHAPE_POLY_SET::POLYGON& poly = polySet.Polygon( i ); + + for( SHAPE_LINE_CHAIN& chain : poly ) + chain.Simplify(); + } + } + + return colorPolys; +} + + +void ConvertImageToLibShapes( LIB_SYMBOL* aSymbol, int unit, wxImage img, VECTOR2D pixelScale, + VECTOR2D offset ) +{ + std::unordered_map colorPolys = + ConvertImageToPolygons( img, pixelScale ); + + for( auto& [color, polySet] : colorPolys ) + { + polySet.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + + for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() ) + { + std::unique_ptr shape = + std::make_unique( aSymbol, SHAPE_T::POLY ); + + shape->SetPolyShape( poly ); + + int r = color & 0xFF; + int g = ( color >> 8 ) & 0xFF; + int b = ( color >> 16 ) & 0xFF; + int a = ( color >> 24 ) & 0xFF; + + shape->SetWidth( -1 ); + shape->SetFillMode( FILL_T::FILLED_WITH_COLOR ); + shape->SetFillColor( COLOR4D( r / 255.0, g / 255.0, b / 255.0, a / 255.0 ) ); + + shape->SetUnit( unit ); + + shape->Offset( offset ); + + aSymbol->AddDrawItem( shape.release() ); + } + } +} + + +void ConvertSVGToLibShapes( LIB_SYMBOL* aSymbol, int unit, const wxMemoryBuffer& aImageData, + VECTOR2D pixelScale, VECTOR2D offset ) +{ + SVG_IMPORT_PLUGIN svgImportPlugin; + GRAPHICS_IMPORTER_LIB_SYMBOL libeditImporter( aSymbol, unit ); + + libeditImporter.SetScale( pixelScale ); + libeditImporter.SetImportOffsetMM( + VECTOR2D( schIUScale.IUTomm( offset.x ), schIUScale.IUTomm( offset.y ) ) ); + + svgImportPlugin.SetImporter( &libeditImporter ); + svgImportPlugin.LoadFromMemory( aImageData ); + + svgImportPlugin.Import(); +} \ No newline at end of file diff --git a/eeschema/gfx_import_utils.h b/eeschema/gfx_import_utils.h new file mode 100644 index 0000000000..4a149572af --- /dev/null +++ b/eeschema/gfx_import_utils.h @@ -0,0 +1,39 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef GFX_IMPORT_UTILS_H_ +#define GFX_IMPORT_UTILS_H_ + +#include +#include +#include +#include + +std::unordered_map ConvertImageToPolygons( wxImage img, + VECTOR2D pixelScale ); + +void ConvertImageToLibShapes( LIB_SYMBOL* aSymbol, int unit, wxImage img, VECTOR2D pixelScale, + VECTOR2D offset ); + +#endif // GFX_IMPORT_UTILS_H_ diff --git a/eeschema/sch_io_mgr.cpp b/eeschema/sch_io_mgr.cpp index ac5d1a7220..26cf43ae26 100644 --- a/eeschema/sch_io_mgr.cpp +++ b/eeschema/sch_io_mgr.cpp @@ -30,6 +30,8 @@ #include #include +#include +#include #include #include #include // for ExpandEnvVarSubstitutions @@ -67,6 +69,8 @@ SCH_PLUGIN* SCH_IO_MGR::FindPlugin( SCH_FILE_T aFileType ) case SCH_CADSTAR_ARCHIVE: return new CADSTAR_SCH_ARCHIVE_PLUGIN(); case SCH_DATABASE: return new SCH_DATABASE_PLUGIN(); case SCH_EAGLE: return new SCH_EAGLE_PLUGIN(); + case SCH_EASYEDA: return new SCH_EASYEDA_PLUGIN(); + case SCH_EASYEDAPRO: return new SCH_EASYEDAPRO_PLUGIN(); case SCH_LTSPICE: return new SCH_LTSPICE_PLUGIN(); default: return nullptr; } @@ -97,6 +101,8 @@ const wxString SCH_IO_MGR::ShowType( SCH_FILE_T aType ) case SCH_CADSTAR_ARCHIVE: return wxString( wxT( "CADSTAR Schematic Archive" ) ); case SCH_DATABASE: return wxString( wxT( "Database" ) ); case SCH_EAGLE: return wxString( wxT( "EAGLE" ) ); + case SCH_EASYEDA: return wxString( wxT( "EasyEDA (JLCEDA) Std" ) ); + case SCH_EASYEDAPRO: return wxString( wxT( "EasyEDA (JLCEDA) Pro" ) ); case SCH_LTSPICE: return wxString( wxT( "LTspice" ) ); default: return wxString::Format( _( "Unknown SCH_FILE_T value: %d" ), aType ); @@ -122,6 +128,10 @@ SCH_IO_MGR::SCH_FILE_T SCH_IO_MGR::EnumFromStr( const wxString& aType ) return SCH_DATABASE; else if( aType == wxT( "EAGLE" ) ) return SCH_EAGLE; + else if( aType == wxT( "EasyEDA (JLCEDA) Std" ) ) + return SCH_EASYEDA; + else if( aType == wxT( "EasyEDA (JLCEDA) Pro" ) ) + return SCH_EASYEDAPRO; else if( aType == wxT( "LTspice" ) ) return SCH_LTSPICE; diff --git a/eeschema/sch_io_mgr.h b/eeschema/sch_io_mgr.h index 790c7ef858..849c3475b2 100644 --- a/eeschema/sch_io_mgr.h +++ b/eeschema/sch_io_mgr.h @@ -64,6 +64,8 @@ public: SCH_CADSTAR_ARCHIVE, ///< CADSTAR Schematic Archive SCH_DATABASE, ///< KiCad database library SCH_EAGLE, ///< Autodesk Eagle file format + SCH_EASYEDA, ///< EasyEDA Std schematic file + SCH_EASYEDAPRO, ///< EasyEDA Pro archive SCH_LTSPICE, ///< LtSpice Schematic format // Add your schematic type here. diff --git a/eeschema/sch_plugins/easyeda/sch_easyeda_parser.cpp b/eeschema/sch_plugins/easyeda/sch_easyeda_parser.cpp new file mode 100644 index 0000000000..9ea5333778 --- /dev/null +++ b/eeschema/sch_plugins/easyeda/sch_easyeda_parser.cpp @@ -0,0 +1,1358 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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_easyeda_parser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +SCH_EASYEDA_PARSER::SCH_EASYEDA_PARSER( SCHEMATIC* aSchematic, + PROGRESS_REPORTER* aProgressReporter ) +{ + m_schematic = aSchematic; +} + + +SCH_EASYEDA_PARSER::~SCH_EASYEDA_PARSER() +{ +} + + +static LIB_ID EasyEdaToKiCadLibID( const wxString& aLibName, const wxString& aLibReference ) +{ + wxString libReference = EscapeString( aLibReference, CTX_LIBID ); + + wxString key = !aLibName.empty() ? ( aLibName + ':' + libReference ) : libReference; + + LIB_ID libId; + libId.Parse( key, true ); + + return libId; +} + + +static PLOT_DASH_TYPE ConvertStrokeStyle( const wxString& aStyle ) +{ + if( aStyle == wxS( "0" ) ) + return PLOT_DASH_TYPE::SOLID; + else if( aStyle == wxS( "1" ) ) + return PLOT_DASH_TYPE::DASH; + else if( aStyle == wxS( "2" ) ) + return PLOT_DASH_TYPE::DOT; + + return PLOT_DASH_TYPE::DEFAULT; +} + + +static ELECTRICAL_PINTYPE ConvertElecType( const wxString& aType ) +{ + if( aType == wxS( "0" ) ) + return ELECTRICAL_PINTYPE::PT_UNSPECIFIED; + else if( aType == wxS( "1" ) ) + return ELECTRICAL_PINTYPE::PT_INPUT; + else if( aType == wxS( "2" ) ) + return ELECTRICAL_PINTYPE::PT_OUTPUT; + else if( aType == wxS( "3" ) ) + return ELECTRICAL_PINTYPE::PT_BIDI; + else if( aType == wxS( "4" ) ) + return ELECTRICAL_PINTYPE::PT_PASSIVE; + + return ELECTRICAL_PINTYPE::PT_UNSPECIFIED; +} + + +static void TransformToBaseline( EDA_TEXT* textItem, const wxString& baselineAlign, bool invertY ) +{ + int upOffset = 0; + + if( baselineAlign == wxS( "" ) || baselineAlign == wxS( "auto" ) + || baselineAlign == wxS( "use-script" ) || baselineAlign == wxS( "no-change" ) + || baselineAlign == wxS( "reset-size" ) || baselineAlign == wxS( "alphabetic" ) + || baselineAlign == wxS( "inherit" ) ) + { + upOffset = textItem->GetTextSize().y; + } + else if( baselineAlign == wxS( "ideographic" ) || baselineAlign == wxS( "text-after-edge" ) ) + { + upOffset = textItem->GetTextSize().y * 1.2; + } + else if( baselineAlign == wxS( "central" ) ) + { + upOffset = textItem->GetTextSize().y * 0.5; + } + else if( baselineAlign == wxS( "middle" ) ) + { + upOffset = textItem->GetTextSize().y * 0.6; + } + else if( baselineAlign == wxS( "mathematical" ) ) + { + upOffset = textItem->GetTextSize().y * 0.1; + } + else if( baselineAlign == wxS( "hanging" ) || baselineAlign == wxS( "text-before-edge" ) ) + { + upOffset = 0; + } + + VECTOR2I offset( 0, -upOffset ); + RotatePoint( offset, textItem->GetTextAngle() ); + + if( invertY ) + offset.y = -offset.y; + + textItem->SetTextPos( textItem->GetTextPos() + offset ); +} + + +VECTOR2I HelperGeneratePowerPortGraphics( LIB_SYMBOL* aKsymbol, EASYEDA::POWER_FLAG_STYLE aStyle, + REPORTER* aReporter ) +{ + if( aStyle == EASYEDA::POWER_FLAG_STYLE::CIRCLE || aStyle == EASYEDA::POWER_FLAG_STYLE::ARROW ) + { + LIB_SHAPE* line1 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line1 ); + line1->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line1->AddPoint( { 0, 0 } ); + line1->AddPoint( { 0, schIUScale.MilsToIU( -50 ) } ); + + if( aStyle == EASYEDA::POWER_FLAG_STYLE::CIRCLE ) + { + LIB_SHAPE* circle = new LIB_SHAPE( aKsymbol, SHAPE_T::CIRCLE ); + aKsymbol->AddDrawItem( circle ); + circle->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 5 ), PLOT_DASH_TYPE::SOLID ) ); + circle->SetPosition( { schIUScale.MilsToIU( 0 ), schIUScale.MilsToIU( -75 ) } ); + circle->SetEnd( circle->GetPosition() + VECTOR2I( schIUScale.MilsToIU( 25 ), 0 ) ); + } + else + { + LIB_SHAPE* line2 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line2 ); + line2->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line2->AddPoint( { schIUScale.MilsToIU( -25 ), schIUScale.MilsToIU( -50 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 25 ), schIUScale.MilsToIU( -50 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 0 ), schIUScale.MilsToIU( -100 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( -25 ), schIUScale.MilsToIU( -50 ) } ); + } + + return { 0, schIUScale.MilsToIU( 150 ) }; + } + else if( aStyle == EASYEDA::POWER_FLAG_STYLE::WAVE ) + { + LIB_SHAPE* line = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line ); + line->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line->AddPoint( { 0, 0 } ); + line->AddPoint( { 0, schIUScale.MilsToIU( -72 ) } ); + + LIB_SHAPE* bezier = new LIB_SHAPE( aKsymbol, SHAPE_T::BEZIER ); + aKsymbol->AddDrawItem( bezier ); + bezier->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 5 ), PLOT_DASH_TYPE::SOLID ) ); + bezier->AddPoint( { schIUScale.MilsToIU( 30 ), schIUScale.MilsToIU( -50 ) } ); + bezier->AddPoint( { schIUScale.MilsToIU( 30 ), schIUScale.MilsToIU( -87 ) } ); + bezier->AddPoint( { schIUScale.MilsToIU( -30 ), schIUScale.MilsToIU( -63 ) } ); + bezier->AddPoint( { schIUScale.MilsToIU( -30 ), schIUScale.MilsToIU( -100 ) } ); + + return { 0, schIUScale.MilsToIU( 150 ) }; + } + else if( aStyle == EASYEDA::POWER_FLAG_STYLE::POWER_GROUND + || aStyle == EASYEDA::POWER_FLAG_STYLE::SIGNAL_GROUND + || aStyle == EASYEDA::POWER_FLAG_STYLE::EARTH + || aStyle == EASYEDA::POWER_FLAG_STYLE::GOST_ARROW ) + { + LIB_SHAPE* line1 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line1 ); + line1->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line1->AddPoint( { 0, 0 } ); + line1->AddPoint( { 0, schIUScale.MilsToIU( -100 ) } ); + + if( aStyle == EASYEDA::POWER_FLAG_STYLE::POWER_GROUND ) + { + LIB_SHAPE* line2 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line2 ); + line2->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line2->AddPoint( { schIUScale.MilsToIU( -100 ), schIUScale.MilsToIU( -100 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 100 ), schIUScale.MilsToIU( -100 ) } ); + + LIB_SHAPE* line3 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line3 ); + line3->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line3->AddPoint( { schIUScale.MilsToIU( -70 ), schIUScale.MilsToIU( -120 ) } ); + line3->AddPoint( { schIUScale.MilsToIU( 70 ), schIUScale.MilsToIU( -120 ) } ); + + LIB_SHAPE* line4 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line4 ); + line4->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line4->AddPoint( { schIUScale.MilsToIU( -40 ), schIUScale.MilsToIU( -140 ) } ); + line4->AddPoint( { schIUScale.MilsToIU( 40 ), schIUScale.MilsToIU( -140 ) } ); + + LIB_SHAPE* line5 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line5 ); + line5->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line5->AddPoint( { schIUScale.MilsToIU( -10 ), schIUScale.MilsToIU( -160 ) } ); + line5->AddPoint( { schIUScale.MilsToIU( 10 ), schIUScale.MilsToIU( -160 ) } ); + } + else if( aStyle == EASYEDA::POWER_FLAG_STYLE::SIGNAL_GROUND ) + { + LIB_SHAPE* line2 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line2 ); + line2->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line2->AddPoint( { schIUScale.MilsToIU( -100 ), schIUScale.MilsToIU( -100 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 100 ), schIUScale.MilsToIU( -100 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 0 ), schIUScale.MilsToIU( -160 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( -100 ), schIUScale.MilsToIU( -100 ) } ); + } + else if( aStyle == EASYEDA::POWER_FLAG_STYLE::EARTH ) + { + LIB_SHAPE* line2 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line2 ); + line2->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line2->AddPoint( { schIUScale.MilsToIU( -150 ), schIUScale.MilsToIU( -200 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( -100 ), schIUScale.MilsToIU( -100 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 100 ), schIUScale.MilsToIU( -100 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 50 ), schIUScale.MilsToIU( -200 ) } ); + + LIB_SHAPE* line3 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line3 ); + line3->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line3->AddPoint( { schIUScale.MilsToIU( 0 ), schIUScale.MilsToIU( -100 ) } ); + line3->AddPoint( { schIUScale.MilsToIU( -50 ), schIUScale.MilsToIU( -200 ) } ); + } + else // EASYEDA::POWER_FLAG_STYLE::GOST_ARROW + { + LIB_SHAPE* line2 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line2 ); + line2->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line2->AddPoint( { schIUScale.MilsToIU( -25 ), schIUScale.MilsToIU( -50 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 0 ), schIUScale.MilsToIU( -100 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 25 ), schIUScale.MilsToIU( -50 ) } ); + + return { 0, schIUScale.MilsToIU( 150 ) }; // special case + } + + return { 0, schIUScale.MilsToIU( 250 ) }; + } + else if( aStyle == EASYEDA::POWER_FLAG_STYLE::GOST_POWER_GROUND + || aStyle == EASYEDA::POWER_FLAG_STYLE::GOST_EARTH ) + { + LIB_SHAPE* line1 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line1 ); + line1->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line1->AddPoint( { 0, 0 } ); + line1->AddPoint( { 0, schIUScale.MilsToIU( -160 ) } ); + + LIB_SHAPE* line2 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line2 ); + line2->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line2->AddPoint( { schIUScale.MilsToIU( -100 ), schIUScale.MilsToIU( -160 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 100 ), schIUScale.MilsToIU( -160 ) } ); + + LIB_SHAPE* line3 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line3 ); + line3->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line3->AddPoint( { schIUScale.MilsToIU( -60 ), schIUScale.MilsToIU( -200 ) } ); + line3->AddPoint( { schIUScale.MilsToIU( 60 ), schIUScale.MilsToIU( -200 ) } ); + + LIB_SHAPE* line4 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line4 ); + line4->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line4->AddPoint( { schIUScale.MilsToIU( -20 ), schIUScale.MilsToIU( -240 ) } ); + line4->AddPoint( { schIUScale.MilsToIU( 20 ), schIUScale.MilsToIU( -240 ) } ); + + if( aStyle == EASYEDA::POWER_FLAG_STYLE::GOST_POWER_GROUND ) + return { 0, schIUScale.MilsToIU( 300 ) }; + + LIB_SHAPE* circle = new LIB_SHAPE( aKsymbol, SHAPE_T::CIRCLE ); + aKsymbol->AddDrawItem( circle ); + circle->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + circle->SetPosition( { schIUScale.MilsToIU( 0 ), schIUScale.MilsToIU( -160 ) } ); + circle->SetEnd( circle->GetPosition() + VECTOR2I( schIUScale.MilsToIU( 120 ), 0 ) ); + + return { 0, schIUScale.MilsToIU( 350 ) }; + } + else if( aStyle == EASYEDA::POWER_FLAG_STYLE::GOST_BAR ) + { + LIB_SHAPE* line1 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line1 ); + line1->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line1->AddPoint( { 0, 0 } ); + line1->AddPoint( { 0, schIUScale.MilsToIU( -200 ) } ); + + LIB_SHAPE* line2 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line2 ); + line2->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line2->AddPoint( { schIUScale.MilsToIU( -100 ), schIUScale.MilsToIU( -200 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 100 ), schIUScale.MilsToIU( -200 ) } ); + + return { 0, schIUScale.MilsToIU( 250 ) }; + } + else + { + if( aStyle != EASYEDA::POWER_FLAG_STYLE::BAR ) + { + aReporter->Report( _( "Power Port with unknown style imported as 'Bar' type." ), + RPT_SEVERITY_WARNING ); + } + + LIB_SHAPE* line1 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line1 ); + line1->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line1->AddPoint( { 0, 0 } ); + line1->AddPoint( { 0, schIUScale.MilsToIU( -100 ) } ); + + LIB_SHAPE* line2 = new LIB_SHAPE( aKsymbol, SHAPE_T::POLY ); + aKsymbol->AddDrawItem( line2 ); + line2->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), PLOT_DASH_TYPE::SOLID ) ); + line2->AddPoint( { schIUScale.MilsToIU( -50 ), schIUScale.MilsToIU( -100 ) } ); + line2->AddPoint( { schIUScale.MilsToIU( 50 ), schIUScale.MilsToIU( -100 ) } ); + + return { 0, schIUScale.MilsToIU( 150 ) }; + } +} + + +void SCH_EASYEDA_PARSER::ParseSymbolShapes( LIB_SYMBOL* aSymbol, + std::map paramMap, + wxArrayString aShapes ) +{ + for( wxString shapeStr : aShapes ) + { + wxArrayString arr = wxSplit( shapeStr, '~', '\0' ); + + wxString elType = arr[0]; + if( elType == wxS( "PL" ) || elType == wxS( "PG" ) ) + { + wxArrayString ptArr = wxSplit( arr[1], ' ', '\0' ); + wxString strokeColor = arr[2]; + double lineWidth = Convert( arr[3] ); + PLOT_DASH_TYPE strokeStyle = ConvertStrokeStyle( arr[4] ); + wxString fillColor = arr[5].Lower(); + //bool locked = arr[7] != wxS( "0" ); + + SHAPE_LINE_CHAIN chain; + + for( size_t i = 1; i < ptArr.size(); i += 2 ) + chain.Append( + RelPosSym( VECTOR2I( Convert( ptArr[i - 1] ), Convert( ptArr[i] ) ) ) ); + + std::unique_ptr line = std::make_unique( aSymbol, SHAPE_T::POLY ); + + if( elType == wxS( "PG" ) ) + chain.SetClosed( true ); + + if( chain.PointCount() < 2 ) + continue; + + for( int i = 0; i < chain.PointCount(); i++ ) + line->AddPoint( chain.CPoint( i ) ); + + if( chain.IsClosed() ) + line->AddPoint( chain.CPoint( 0 ) ); + + line->SetUnit( 0 ); + line->SetStroke( STROKE_PARAMS( ScaleSize( lineWidth ), strokeStyle ) ); + + if( fillColor != wxS( "none" ) ) + { + line->SetFilled( true ); + + if( fillColor == strokeColor ) + line->SetFillMode( FILL_T::FILLED_SHAPE ); + else + line->SetFillMode( FILL_T::FILLED_WITH_BG_BODYCOLOR ); + } + + aSymbol->AddDrawItem( line.release() ); + } + else if( elType == wxS( "PT" ) ) + { + wxString pointsData = arr[1]; + wxString strokeColor = arr[2]; + double lineWidth = Convert( arr[3] ); + PLOT_DASH_TYPE strokeStyle = ConvertStrokeStyle( arr[4] ); + wxString fillColor = arr[5].Lower(); + //bool locked = arr[7] != wxS( "0" ); + + SHAPE_POLY_SET polygons = ParsePolygons( pointsData, schIUScale.MilsToIU( 10 ) ); + + for( const SHAPE_POLY_SET::POLYGON& poly : polygons.CPolygons() ) + { + std::unique_ptr shape = + std::make_unique( aSymbol, SHAPE_T::POLY ); + + SHAPE_LINE_CHAIN outline = poly[0]; + + outline.Mirror( false, true ); + + if( outline.IsClosed() ) + outline.Append( outline.CPoint( 0 ), true ); + + shape->SetPolyShape( outline ); + + shape->SetUnit( 0 ); + shape->SetStroke( STROKE_PARAMS( ScaleSize( lineWidth ), strokeStyle ) ); + + if( fillColor != wxS( "none" ) ) + { + shape->SetFilled( true ); + + if( fillColor == strokeColor ) + shape->SetFillMode( FILL_T::FILLED_SHAPE ); + else + shape->SetFillMode( FILL_T::FILLED_WITH_BG_BODYCOLOR ); + } + + aSymbol->AddDrawItem( shape.release() ); + } + } + else if( elType == wxS( "Pimage" ) ) + { + //double angle = Convert( arr[4] ); // TODO + VECTOR2D start( Convert( arr[6] ), Convert( arr[7] ) ); + VECTOR2D size( Convert( arr[8] ), Convert( arr[9] ) ); + wxString imageUrl = arr[10]; + + if( imageUrl.BeforeFirst( ':' ) == wxS( "data" ) ) + { + wxArrayString paramsArr = + wxSplit( imageUrl.AfterFirst( ':' ).BeforeFirst( ',' ), ';', '\0' ); + + wxString data = imageUrl.AfterFirst( ',' ); + + if( paramsArr.size() > 0 ) + { + wxString mimeType = paramsArr[0]; + wxMemoryBuffer buf = wxBase64Decode( data ); + + if( mimeType == wxS( "image/svg+xml" ) ) + { + VECTOR2D offset = RelPosSym( start ); + + SVG_IMPORT_PLUGIN svgImportPlugin; + GRAPHICS_IMPORTER_LIB_SYMBOL libsymImporter( aSymbol, 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 ); + + libsymImporter.SetScale( pixelScale ); + + VECTOR2D offsetMM( schIUScale.IUTomm( offset.x ), + schIUScale.IUTomm( offset.y ) ); + + libsymImporter.SetImportOffsetMM( offsetMM ); + + svgImportPlugin.Import(); + + for( std::unique_ptr& item : libsymImporter.GetItems() ) + aSymbol->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() ); + + ConvertImageToLibShapes( aSymbol, 0, img, pixelScale, + RelPosSym( start ) ); + } + } + } + } + } + else if( elType == wxS( "A" ) ) + { + wxString data = arr[1]; + wxString strokeColor = arr[3]; + double lineWidth = Convert( arr[4] ); + PLOT_DASH_TYPE strokeStyle = ConvertStrokeStyle( arr[5] ); + wxString fillColor = arr[6].Lower(); + //bool locked = arr[8] != wxS( "0" ); + + VECTOR2D start, end; + VECTOR2D rad( 10, 10 ); + bool isFar = false; + bool cw = false; + + size_t pos = 0; + auto readNumber = [&]( wxString& aOut ) + { + wxUniChar ch = data[pos]; + + while( ch == ' ' || ch == ',' ) + ch = data[++pos]; + + while( isdigit( ch ) || ch == '.' || ch == '-' ) + { + aOut += ch; + pos++; + + if( pos == data.size() ) + break; + + ch = data[pos]; + } + }; + + do + { + wxUniChar sym = data[pos++]; + + if( sym == 'M' ) + { + wxString xStr, yStr; + readNumber( xStr ); + readNumber( yStr ); + + start = VECTOR2D( Convert( xStr ), Convert( yStr ) ); + } + else if( sym == 'A' ) + { + wxString radX, radY, unknown, farFlag, cwFlag, endX, endY; + readNumber( radX ); + readNumber( radY ); + readNumber( unknown ); + readNumber( farFlag ); + readNumber( cwFlag ); + readNumber( endX ); + readNumber( endY ); + + isFar = farFlag == wxS( "1" ); + cw = cwFlag == wxS( "1" ); + rad = VECTOR2D( Convert( radX ), Convert( radY ) ); + end = VECTOR2D( Convert( endX ), Convert( endY ) ); + } + } while( pos < data.size() ); + + VECTOR2D delta = end - start; + + double d = delta.EuclideanNorm(); + double h = sqrt( rad.x * rad.x - d * d / 4 ); + + //double aa = sqrt(1/(d*d)* ( 4*d*d*rad - d*d*d*d)); + + //double h = sqrt( radX - d * d / 4 ); + + //( !far && cw ) => h + //( far && cw ) => -h + //( !far && !cw ) => -h + //( far && !cw ) => h + VECTOR2D arcCenter = + start + delta / 2 + delta.Perpendicular().Resize( ( isFar ^ cw ) ? h : -h ); + + if( cw ) + std::swap( start, end ); + + std::unique_ptr shape = std::make_unique( aSymbol, SHAPE_T::ARC ); + + shape->SetStart( RelPosSym( start ) ); + shape->SetEnd( RelPosSym( end ) ); + shape->SetCenter( RelPosSym( arcCenter ) ); + + shape->SetUnit( 0 ); + shape->SetStroke( STROKE_PARAMS( ScaleSize( lineWidth ), strokeStyle ) ); + + if( fillColor != wxS( "none" ) ) + { + shape->SetFilled( true ); + + if( fillColor == strokeColor ) + shape->SetFillMode( FILL_T::FILLED_SHAPE ); + else + shape->SetFillMode( FILL_T::FILLED_WITH_BG_BODYCOLOR ); + } + + aSymbol->AddDrawItem( shape.release() ); + } + else if( elType == wxS( "R" ) ) + { + VECTOR2D start( Convert( arr[1] ), Convert( arr[2] ) ); + + VECTOR2D cr; + cr.x = !arr[3].empty() ? Convert( arr[3] ) : 0, + cr.y = !arr[4].empty() ? Convert( arr[4] ) : 0; // TODO: corner radius + + VECTOR2D size( Convert( arr[5] ), Convert( arr[6] ) ); + wxString strokeColor = arr[7]; + double lineWidth = Convert( arr[8] ); + PLOT_DASH_TYPE strokeStyle = ConvertStrokeStyle( arr[9] ); + wxString fillColor = arr[10].Lower(); + //bool locked = arr[12] != wxS( "0" ); + + //if( cr.x == 0 ) + { + std::unique_ptr rect = + std::make_unique( aSymbol, SHAPE_T::RECTANGLE ); + + rect->SetStart( RelPosSym( start ) ); + rect->SetEnd( RelPosSym( start + size ) ); + + rect->SetUnit( 0 ); + rect->SetStroke( STROKE_PARAMS( ScaleSize( lineWidth ), strokeStyle ) ); + + if( fillColor != wxS( "none" ) ) + { + rect->SetFilled( true ); + + if( fillColor == strokeColor ) + rect->SetFillMode( FILL_T::FILLED_SHAPE ); + else + rect->SetFillMode( FILL_T::FILLED_WITH_BG_BODYCOLOR ); + } + + aSymbol->AddDrawItem( rect.release() ); + } + // TODO: rounded rectangles + } + else if( elType == wxS( "E" ) ) + { + std::unique_ptr circle = + std::make_unique( aSymbol, SHAPE_T::CIRCLE ); + + VECTOR2D center( Convert( arr[1] ), Convert( arr[2] ) ); + VECTOR2D radius( Convert( arr[3] ), Convert( arr[4] ) ); // TODO: corner radius + wxString strokeColor = arr[5]; + double lineWidth = Convert( arr[6] ); + PLOT_DASH_TYPE strokeStyle = ConvertStrokeStyle( arr[7] ); + wxString fillColor = arr[8].Lower(); + //bool locked = arr[10] != wxS( "0" ); + + circle->SetCenter( RelPosSym( center ) ); + circle->SetEnd( RelPosSym( center + VECTOR2I( radius.x, 0 ) ) ); + + circle->SetUnit( 0 ); + circle->SetStroke( STROKE_PARAMS( ScaleSize( lineWidth ), strokeStyle ) ); + + if( fillColor != wxS( "none" ) ) + { + circle->SetFilled( true ); + + if( fillColor == strokeColor ) + circle->SetFillMode( FILL_T::FILLED_SHAPE ); + else + circle->SetFillMode( FILL_T::FILLED_WITH_BG_BODYCOLOR ); + } + + aSymbol->AddDrawItem( circle.release() ); + } + else if( elType == wxS( "P" ) ) + { + wxString sepShapeStr = shapeStr; + sepShapeStr.Replace( wxS( "^^" ), wxS( "\n" ) ); + + wxArrayString segments = wxSplit( sepShapeStr, '\n', '\0' ); + wxArrayString mainParts = wxSplit( segments[0], '~', '\0' ); + wxArrayString pinDotParts = wxSplit( segments[1], '~', '\0' ); + wxArrayString pinPathColorParts = wxSplit( segments[2], '~', '\0' ); + wxArrayString pinNameParts = wxSplit( segments[3], '~', '\0' ); + wxArrayString pinNumParts = wxSplit( segments[4], '~', '\0' ); + + //bool show = mainParts[1].Lower() != wxS( "none" ); + ELECTRICAL_PINTYPE elecType = ConvertElecType( mainParts[2] ); + wxString pinNumber = mainParts[3]; + VECTOR2D pinPos( Convert( mainParts[4] ), Convert( mainParts[5] ) ); + //int pinRot = Convert( mainParts[6] ); + + VECTOR2D pinDotPos( Convert( pinDotParts[0] ), Convert( pinDotParts[1] ) ); + + bool nameVisible = pinNameParts[0] != wxS( "0" ); + wxString pinName = pinNameParts[4]; + + bool numVisible = pinNumParts[0] != wxS( "0" ); + wxString ptSize = pinNumParts[4]; + + VECTOR2D startPoint; + bool vertical = false; + double pinLen = 0; + + // M360,290h10 or M 420 300 h -5 + wxString lineData = pinPathColorParts[0]; + wxRegEx regex( + wxS( "^M\\s*([-\\d.]+)[,\\s]([-\\d.]+)\\s*([h|v])\\s*([-\\d.]+)\\s*$" ) ); + if( regex.Matches( lineData ) ) + { + startPoint.x = Convert( regex.GetMatch( lineData, 1 ) ); + startPoint.y = Convert( regex.GetMatch( lineData, 2 ) ); + + vertical = regex.GetMatch( lineData, 3 ).Contains( wxS( "v" ) ); + pinLen = Convert( regex.GetMatch( lineData, 4 ) ); + } + + int pinRotation = 0; + + if( !vertical ) + { + if( startPoint.x == pinPos.x && pinLen < 0 ) + pinRotation = 180; + else if( startPoint.x == pinPos.x && pinLen > 0 ) + pinRotation = 0; + else if( startPoint.x != pinPos.x && pinLen < 0 ) + pinRotation = 0; + else if( startPoint.x != pinPos.x && pinLen > 0 ) + pinRotation = 180; + } + else + { + if( startPoint.y == pinPos.y && pinLen < 0 ) + pinRotation = 90; + else if( startPoint.y == pinPos.y && pinLen > 0 ) + pinRotation = 270; + else if( startPoint.y != pinPos.y && pinLen < 0 ) + pinRotation = 270; + else if( startPoint.y != pinPos.y && pinLen > 0 ) + pinRotation = 90; + } + + PIN_ORIENTATION orient = PIN_ORIENTATION::PIN_RIGHT; + + if( pinRotation == 0 ) + orient = PIN_ORIENTATION::PIN_RIGHT; + else if( pinRotation == 90 ) + orient = PIN_ORIENTATION::PIN_UP; + else if( pinRotation == 180 ) + orient = PIN_ORIENTATION::PIN_LEFT; + else if( pinRotation == 270 ) + orient = PIN_ORIENTATION::PIN_DOWN; + + int pinUnit = 0; + int kPinLen = ScaleSize( std::abs( pinLen ) ); + + std::unique_ptr pin = std::make_unique( aSymbol ); + + pin->SetName( pinName ); + pin->SetNumber( pinNumber ); + pin->SetOrientation( orient ); + pin->SetType( elecType ); + pin->SetLength( kPinLen ); + pin->SetPosition( RelPosSym( pinPos ) ); + pin->SetUnit( pinUnit ); + + if( pin->GetNumberTextSize() * int( pinNumber.size() ) > kPinLen ) + pin->SetNumberTextSize( kPinLen / pinNumber.size() ); + + if( !nameVisible ) + pin->SetNameTextSize( schIUScale.MilsToIU( 1 ) ); + + if( !numVisible ) + pin->SetNumberTextSize( schIUScale.MilsToIU( 1 ) ); + + aSymbol->AddDrawItem( pin.release() ); + } + else if( elType == wxS( "T" ) ) + { + wxString textType = arr[1]; + VECTOR2D pos( Convert( arr[2] ), Convert( arr[3] ) ); + int angle = Convert( arr[4] ); + wxString color = arr[5]; + wxString fontname = arr[6]; + wxString fontSize = arr[7]; + wxString baselineAlign = arr[10]; + wxString textStr = arr[12]; + textStr.Replace( wxS( "\\n" ), wxS( "\n" ) ); + wxString halignStr = arr[14]; // Empty, start, middle, end, inherit + + bool added = false; + EDA_TEXT* textItem = nullptr; + + if( textType == wxS( "P" ) ) + { + textItem = &aSymbol->GetReferenceField(); + } + else if( textType == wxS( "N" ) ) + { + textItem = &aSymbol->GetValueField(); + } + else + { + textItem = new LIB_TEXT( aSymbol ); + added = true; + } + + textItem->SetTextPos( RelPosSym( pos ) ); + textItem->SetText( textStr ); + + textItem->SetTextAngleDegrees( ( 360 - angle ) % 360 ); + textItem->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); + + if( halignStr == wxS( "middle" ) ) + textItem->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); + else if( halignStr == wxS( "end" ) ) + textItem->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); + else + textItem->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); + + textItem->SetFont( KIFONT::FONT::GetFont( fontname ) ); + + double ptSize = 7; + + if( !fontSize.IsEmpty() ) + { + if( fontSize.EndsWith( wxS( "pt" ) ) ) + ptSize = Convert( fontSize.BeforeFirst( 'p' ) ); + else if( fontSize.IsNumber() ) + ptSize = Convert( fontSize ); + } + + double ktextSize = ScaleSize( ptSize ); + + if( textStr.Contains( wxS( "\n" ) ) ) + ktextSize *= 0.8; + else + ktextSize *= 0.95; + + textItem->SetTextSize( VECTOR2I( ktextSize, ktextSize ) ); + + TransformToBaseline( textItem, baselineAlign, true ); + + if( added ) + aSymbol->AddDrawItem( dynamic_cast( textItem ) ); + } + } +} + + +LIB_SYMBOL* SCH_EASYEDA_PARSER::ParseSymbol( const VECTOR2D& aOrigin, + std::map aParams, + wxArrayString aShapes ) +{ + std::unique_ptr ksymbol = std::make_unique( wxEmptyString ); + + m_relOrigin = aOrigin; + + wxString symbolName = wxS( "Unknown" ); + + if( aParams.find( wxS( "name" ) ) != aParams.end() ) + symbolName = aParams.at( wxS( "name" ) ); + else if( aParams.find( wxS( "spiceSymbolName" ) ) != aParams.end() ) + symbolName = aParams.at( wxS( "spiceSymbolName" ) ); + + wxString symbolPrefix; + + if( aParams.find( wxS( "pre" ) ) != aParams.end() ) + symbolPrefix = aParams.at( wxS( "pre" ) ); + else if( aParams.find( wxS( "spicePre" ) ) != aParams.end() ) + symbolPrefix = aParams.at( wxS( "spicePre" ) ); + + if( !symbolPrefix.EndsWith( wxS( "?" ) ) ) + symbolPrefix += wxS( "?" ); + + LIB_ID libId = EasyEdaToKiCadLibID( wxEmptyString, symbolName ); + + ksymbol->SetLibId( libId ); + ksymbol->SetName( symbolName ); + + ksymbol->GetReferenceField().SetText( symbolPrefix ); + ksymbol->GetValueField().SetText( symbolName ); + + ParseSymbolShapes( ksymbol.get(), aParams, aShapes ); + + return ksymbol.release(); +} + +std::pair SCH_EASYEDA_PARSER::MakePowerSymbol( const wxString& aFlagTypename, + const wxString& aNetname ) +{ + std::unique_ptr ksymbol = std::make_unique( wxEmptyString ); + + m_relOrigin = VECTOR2D(); + + LIB_ID libId = EasyEdaToKiCadLibID( wxEmptyString, aNetname ); + + ksymbol->SetPower(); + ksymbol->SetLibId( libId ); + ksymbol->SetName( aNetname ); + ksymbol->GetReferenceField().SetText( wxS( "#PWR" ) ); + ksymbol->GetReferenceField().SetVisible( false ); + ksymbol->GetValueField().SetText( aNetname ); + ksymbol->GetValueField().SetVisible( true ); + ksymbol->SetDescription( wxString::Format( _( "Power symbol creates a global " + "label with name '%s'" ), + aNetname ) ); + ksymbol->SetKeyWords( wxS( "power-flag" ) ); + ksymbol->SetShowPinNames( false ); + ksymbol->SetShowPinNumbers( false ); + + std::unique_ptr pin = std::make_unique( ksymbol.get() ); + + pin->SetName( aNetname ); + pin->SetNumber( wxS( "1" ) ); + pin->SetOrientation( PIN_ORIENTATION::PIN_DOWN ); + pin->SetType( ELECTRICAL_PINTYPE::PT_POWER_IN ); + pin->SetLength( 0 ); + + ksymbol->AddDrawItem( pin.release() ); + + EASYEDA::POWER_FLAG_STYLE flagStyle = EASYEDA::POWER_FLAG_STYLE::POWER_GROUND; + + bool flip = false; + + if( aFlagTypename == wxS( "part_netLabel_gnD" ) ) + { + flagStyle = EASYEDA::POWER_FLAG_STYLE::POWER_GROUND; + } + else if( aFlagTypename == wxS( "part_netLabel_GNd" ) ) + { + flagStyle = EASYEDA::POWER_FLAG_STYLE::SIGNAL_GROUND; + } + else if( aFlagTypename == wxS( "part_netLabel_gNd" ) ) + { + flagStyle = EASYEDA::POWER_FLAG_STYLE::BAR; + } + else if( aFlagTypename == wxS( "part_netLabel_GnD" ) ) + { + flagStyle = EASYEDA::POWER_FLAG_STYLE::EARTH; + } + else if( aFlagTypename == wxS( "part_netLabel_VCC" ) ) + { + flagStyle = EASYEDA::POWER_FLAG_STYLE::BAR; + flip = true; + } + else if( aFlagTypename == wxS( "part_netLabel_+5V" ) ) + { + flagStyle = EASYEDA::POWER_FLAG_STYLE::BAR; + flip = true; + } + else if( aFlagTypename == wxS( "part_netLabel_VEE" ) ) + { + flagStyle = EASYEDA::POWER_FLAG_STYLE::BAR; + } + else if( aFlagTypename == wxS( "part_netLabel_-5V" ) ) + { + flagStyle = EASYEDA::POWER_FLAG_STYLE::BAR; + } + else if( aFlagTypename == wxS( "part_netLabel_Bar" ) ) + { + flagStyle = EASYEDA::POWER_FLAG_STYLE::CIRCLE; + flip = true; + } + + VECTOR2I valueFieldPos = HelperGeneratePowerPortGraphics( ksymbol.get(), flagStyle, nullptr ); + ksymbol->GetValueField().SetPosition( valueFieldPos ); + + return std::make_pair( ksymbol.release(), flip ); +} + +void SCH_EASYEDA_PARSER::ParseSchematic( SCHEMATIC* aSchematic, SCH_SHEET* aRootSheet, + const wxString& aFileName, wxArrayString aShapes ) +{ + std::map> loadedSymbols; + std::map namesCounter; + std::vector> createdItems; + + for( wxString shap : aShapes ) + { + shap.Replace( wxS( "#@$" ), wxS( "\n" ) ); + wxArrayString parts = wxSplit( shap, '\n', '\0' ); + + if( parts.size() < 1 ) + continue; + + wxArrayString arr = wxSplit( parts[0], '~', '\0' ); + + if( arr.size() < 1 ) + continue; + + wxString rootType = arr[0]; + + if( rootType == wxS( "LIB" ) ) + { + if( arr.size() < 4 ) + continue; + + VECTOR2D origin( Convert( arr[1] ), Convert( arr[2] ) ); + + wxString symbolName = wxString::Format( wxS( "Unknown_%s_%s" ), arr[1], arr[2] ); + + wxArrayString paramParts = wxSplit( arr[3], '`', '\0' ); + + std::map paramMap; + + for( size_t i = 1; i < paramParts.size(); i += 2 ) + { + wxString key = paramParts[i - 1]; + wxString value = paramParts[i]; + + if( key == wxS( "spiceSymbolName" ) && !value.IsEmpty() ) + symbolName = value; + + paramMap[key] = value; + } + + int& serial = namesCounter[symbolName]; + + if( serial > 0 ) + symbolName << wxS( "_" ) << serial; + + serial++; + + paramMap[wxS( "spiceSymbolName" )] = symbolName; + + parts.RemoveAt( 0 ); + + VECTOR2D pcbOrigin = m_relOrigin; + + LIB_SYMBOL* sym = ParseSymbol( origin, paramMap, parts ); + loadedSymbols.emplace( symbolName, sym ); + + wxString referenceStr = sym->GetReferenceField().GetText(); + + m_relOrigin = pcbOrigin; + LIB_ID libId = EasyEdaToKiCadLibID( wxEmptyString, symbolName ); + + std::unique_ptr schSym = + std::make_unique( *sym, libId, &aSchematic->CurrentSheet(), 0 ); + + schSym->SetPosition( RelPos( origin ) ); + schSym->SetRef( &aSchematic->CurrentSheet(), referenceStr ); + + createdItems.push_back( std::move( schSym ) ); + } + else if( rootType == wxS( "F" ) ) + { + wxString sepShapeStr = parts[0]; + sepShapeStr.Replace( wxS( "^^" ), wxS( "\n" ) ); + + wxArrayString segments = wxSplit( sepShapeStr, '\n', '\0' ); + wxArrayString mainParts = wxSplit( segments[0], '~', '\0' ); + wxArrayString uselessParts = wxSplit( segments[1], '~', '\0' ); + wxArrayString valueParts = wxSplit( segments[2], '~', '\0' ); + + wxString flagTypename = arr[1]; + VECTOR2D pos( Convert( arr[2] ), Convert( arr[3] ) ); + double angle = Convert( arr[4] ); + + wxString netnameValue = valueParts[0]; + VECTOR2D valuePos( Convert( valueParts[2] ), Convert( valueParts[3] ) ); + double textAngle = Convert( valueParts[4] ); + wxString halignStr = valueParts[5]; + wxString valueFontname = valueParts[7]; + wxString valueFontsize = valueParts[8]; + + auto pair = MakePowerSymbol( flagTypename, netnameValue ); + LIB_SYMBOL* pwrLibSym = pair.first; + bool flip = pair.second; + + LIB_ID libId = EasyEdaToKiCadLibID( wxEmptyString, netnameValue ); + + std::unique_ptr schSym = std::make_unique( + *pwrLibSym, libId, &aSchematic->CurrentSheet(), 0 ); + + if( flip ) + schSym->SetOrientation( SYM_MIRROR_X ); + + if( angle == 0 ) + { + } + else if( angle == 90 ) + { + schSym->SetOrientation( SYM_ROTATE_COUNTERCLOCKWISE ); + } + if( angle == 180 ) + { + schSym->SetOrientation( SYM_ROTATE_COUNTERCLOCKWISE ); + schSym->SetOrientation( SYM_ROTATE_COUNTERCLOCKWISE ); + } + if( angle == 270 ) + { + schSym->SetOrientation( SYM_ROTATE_CLOCKWISE ); + } + + schSym->SetPosition( RelPos( pos ) ); + + SCH_FIELD* valField = schSym->GetField( VALUE_FIELD ); + + valField->SetPosition( RelPos( valuePos ) ); + valField->SetTextAngleDegrees( textAngle - angle ); + + if( !flip ) + valField->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); + else + valField->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); + + if( halignStr == wxS( "middle" ) ) + valField->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); + else if( halignStr == wxS( "end" ) ) + valField->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); + else + valField->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); + + if( flip && ( angle == 90 || angle == 270 ) ) + { + if( valField->GetVertJustify() == GR_TEXT_V_ALIGN_BOTTOM ) + valField->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); + else if( valField->GetVertJustify() == GR_TEXT_V_ALIGN_TOP ) + valField->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); + + if( valField->GetHorizJustify() == GR_TEXT_H_ALIGN_RIGHT ) + valField->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); + else if( valField->GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT ) + valField->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); + + if( flagTypename == wxS( "part_netLabel_Bar" ) ) // "Circle" + valField->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); + } + + if( angle == 0 && flagTypename == wxS( "part_netLabel_Bar" ) ) // "Circle" + valField->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); + + valField->SetFont( KIFONT::FONT::GetFont( valueFontname ) ); + + double ptSize = 7; + + if( valueFontsize.EndsWith( wxS( "pt" ) ) ) + ptSize = Convert( valueFontsize.BeforeFirst( 'p' ) ); + + double ktextSize = ScaleSize( ptSize ); + + if( netnameValue.Contains( wxS( "\n" ) ) ) + ktextSize *= 0.8; + else + ktextSize *= 0.95; + + valField->SetTextSize( VECTOR2I( ktextSize, ktextSize ) ); + + //TransformToBaseline( valField, wxS( "" ), true ); + + createdItems.push_back( std::move( schSym ) ); + } + else if( rootType == wxS( "W" ) ) + { + wxArrayString ptArr = wxSplit( arr[1], ' ', '\0' ); + wxString strokeColor = arr[2]; + //double lineWidth = Convert( arr[3] ); + //PLOT_DASH_TYPE strokeStyle = ConvertStrokeStyle( arr[4] ); + wxString fillColor = arr[5].Lower(); + //bool locked = arr[7] != wxS( "0" ); + + SHAPE_LINE_CHAIN chain; + + for( size_t i = 1; i < ptArr.size(); i += 2 ) + chain.Append( VECTOR2I( RelPosX( ptArr[i - 1] ), RelPosY( ptArr[i] ) ) ); + + for( int segId = 0; segId < chain.SegmentCount(); segId++ ) + { + const SEG& seg = chain.CSegment( segId ); + + std::unique_ptr line = std::make_unique( seg.A, LAYER_WIRE ); + line->SetEndPoint( seg.B ); + + createdItems.push_back( std::move( line ) ); + } + } + else if( rootType == wxS( "N" ) ) + { + VECTOR2D pos( Convert( arr[1] ), Convert( arr[2] ) ); + double angle = Convert( arr[3] ); + wxString netname = arr[5]; + wxString halignStr = arr[7]; + VECTOR2D text_pos( Convert( arr[8] ), + Convert( arr[9] ) ); // TODO: detect top/bottom align + + wxString fontname = arr[10]; + wxString fontSize = arr[11]; + + std::unique_ptr label = + std::make_unique( RelPos( pos ), netname ); + + if( halignStr == wxS( "middle" ) ) + label->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); + else if( halignStr == wxS( "end" ) ) + label->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); + else + label->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); + + for( int left = angle; left > 0; left -= 90 ) + label->Rotate90( false ); + + createdItems.push_back( std::move( label ) ); + } + else if( rootType == wxS( "O" ) ) + { + VECTOR2D pos( Convert( arr[1] ), Convert( arr[2] ) ); + + std::unique_ptr noConn = + std::make_unique( RelPos( pos ) ); + + createdItems.push_back( std::move( noConn ) ); + } + else if( rootType == wxS( "J" ) ) + { + VECTOR2D pos( Convert( arr[1] ), Convert( arr[2] ) ); + //double dia = Convert( arr[3] ); + + std::unique_ptr junction = + std::make_unique( RelPos( pos ) /*, ScaleSizeUnit( dia )*/ ); + + createdItems.push_back( std::move( junction ) ); + } + else if( rootType == wxS( "T" ) ) + { + VECTOR2D pos( Convert( arr[2] ), Convert( arr[3] ) ); + int angle = Convert( arr[4] ); + wxString color = arr[5]; + wxString fontname = arr[6]; + wxString fontSize = arr[7]; + wxString baselineAlign = arr[10]; + wxString textStr = arr[12]; + textStr.Replace( wxS( "\\n" ), wxS( "\n" ) ); + wxString halignStr = arr[14]; // Empty, start, middle, end, inherit + + std::unique_ptr textItem = + std::make_unique( RelPos( pos ), textStr ); + + textItem->SetTextAngleDegrees( ( 360 - angle ) % 360 ); + textItem->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); + + if( halignStr == wxS( "middle" ) ) + textItem->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); + else if( halignStr == wxS( "end" ) ) + textItem->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); + else + textItem->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); + + textItem->SetFont( KIFONT::FONT::GetFont( fontname ) ); + + double ptSize = 7; + + if( fontSize.EndsWith( wxS( "pt" ) ) ) + ptSize = Convert( fontSize.BeforeFirst( 'p' ) ); + + double ktextSize = ScaleSize( ptSize ); + + if( textStr.Contains( wxS( "\n" ) ) ) + ktextSize *= 0.8; + else + ktextSize *= 0.95; + + textItem->SetTextSize( VECTOR2I( ktextSize, ktextSize ) ); + + TransformToBaseline( textItem.get(), baselineAlign, false ); + + createdItems.push_back( std::move( textItem ) ); + } + else if( rootType == wxS( "R" ) ) + { + VECTOR2D start( Convert( arr[1] ), Convert( arr[2] ) ); + + VECTOR2D cr; + cr.x = !arr[3].empty() ? Convert( arr[3] ) : 0, + cr.y = !arr[4].empty() ? Convert( arr[4] ) : 0; // TODO: corner radius + + VECTOR2D size( Convert( arr[5] ), Convert( arr[6] ) ); + wxString strokeColor = arr[7]; + double lineWidth = Convert( arr[8] ); + PLOT_DASH_TYPE strokeStyle = ConvertStrokeStyle( arr[9] ); + wxString fillColor = arr[10].Lower(); + //bool locked = arr[12] != wxS( "0" ); + + //if( cr.x == 0 ) + { + std::unique_ptr rect = std::make_unique( SHAPE_T::RECTANGLE ); + + rect->SetStart( RelPos( start ) ); + rect->SetEnd( RelPos( start + size ) ); + + rect->SetStroke( STROKE_PARAMS( ScaleSize( lineWidth ), strokeStyle ) ); + + if( fillColor != wxS( "none" ) ) + { + rect->SetFilled( true ); + + if( fillColor == strokeColor ) + rect->SetFillMode( FILL_T::FILLED_SHAPE ); + else + rect->SetFillMode( FILL_T::FILLED_WITH_BG_BODYCOLOR ); + } + + createdItems.push_back( std::move( rect ) ); + } + /*else + { + }*/ + } + } + + BOX2I sheetBBox; + + for( std::unique_ptr& ptr : createdItems ) + if( ptr->Type() == SCH_SYMBOL_T ) + sheetBBox.Merge( static_cast( ptr.get() )->GetBodyBoundingBox() ); + + 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() ); + } +} diff --git a/eeschema/sch_plugins/easyeda/sch_easyeda_parser.h b/eeschema/sch_plugins/easyeda/sch_easyeda_parser.h new file mode 100644 index 0000000000..9b0c3e04d5 --- /dev/null +++ b/eeschema/sch_plugins/easyeda/sch_easyeda_parser.h @@ -0,0 +1,80 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef SCH_EASYEDA_PARSER_H_ +#define SCH_EASYEDA_PARSER_H_ + +#include +#include + +#include +#include +#include +#include +#include + + +class EDA_TEXT; +class LIB_SHAPE; +class LIB_PIN; +class SCH_LABEL_BASE; +class SCH_SYMBOL; +class SCH_TEXT; +class SCH_SHAPE; + +class SCH_EASYEDA_PARSER: public EASYEDA_PARSER_BASE +{ +public: + explicit SCH_EASYEDA_PARSER( SCHEMATIC* aSchematic, PROGRESS_REPORTER* aProgressReporter ); + ~SCH_EASYEDA_PARSER(); + + double ScaleSize( double aValue ) override + { + return KiROUND( schIUScale.MilsToIU( aValue * 10 ) ); + } + + template + VECTOR2 RelPosSym( const VECTOR2& aVec ) + { + return VECTOR2( RelPosX( aVec.x ), -RelPosY( aVec.y ) ); + } + + std::pair MakePowerSymbol( const wxString& aFlagTypename, + const wxString& aNetname ); + + void ParseSymbolShapes( LIB_SYMBOL* aContainer, std::map paramMap, + wxArrayString aShapes ); + + LIB_SYMBOL* ParseSymbol( const VECTOR2D& aOrigin, std::map aParams, + wxArrayString aShapes ); + + void ParseSchematic( SCHEMATIC* aSchematic, SCH_SHEET* aRootSheet, const wxString& aFileName, + wxArrayString aShapes ); + +private: + SCHEMATIC* m_schematic; +}; + + +#endif // SCH_EASYEDA_PARSER_H_ diff --git a/eeschema/sch_plugins/easyeda/sch_easyeda_plugin.cpp b/eeschema/sch_plugins/easyeda/sch_easyeda_plugin.cpp new file mode 100644 index 0000000000..b378d78070 --- /dev/null +++ b/eeschema/sch_plugins/easyeda/sch_easyeda_plugin.cpp @@ -0,0 +1,583 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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_easyeda_plugin.h" +#include "sch_easyeda_parser.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + + + +const wxString SCH_EASYEDA_PLUGIN::GetName() const +{ + return wxT( "EasyEDA (JLCEDA) Schematic Importer" ); +} + + +static bool FindSchFileInStream( const wxString& aName, wxInputStream& aStream, + nlohmann::json& aOut, EASYEDA::DOCUMENT& aDoc, + EASYEDA::DOC_TYPE& aDocType ) +{ + if( aName.Lower().EndsWith( wxS( ".json" ) ) ) + { + wxStdInputStream sin( aStream ); + nlohmann::json js = nlohmann::json::parse( sin, nullptr, false ); + + if( js.is_discarded() ) + return false; + + EASYEDA::DOCUMENT doc = js.get(); + EASYEDA::DOC_TYPE type; + + if( doc.docType ) + type = *doc.docType; + else + type = doc.head.docType; + + if( type == EASYEDA::DOC_TYPE::SCHEMATIC_SHEET || type == EASYEDA::DOC_TYPE::SCHEMATIC_LIST + || type == EASYEDA::DOC_TYPE::SYMBOL ) + { + aOut = js; + aDoc = doc; + aDocType = type; + return true; + } + } + else if( aName.Lower().EndsWith( wxS( ".zip" ) ) ) + { + std::shared_ptr entry; + wxZipInputStream zip( aStream ); + + if( !zip.IsOk() ) + return false; + + while( entry.reset( zip.GetNextEntry() ), entry.get() != NULL ) + { + wxString name = entry->GetName(); + + if( FindSchFileInStream( name, zip, aOut, aDoc, aDocType ) ) + return true; + } + } + + return false; +} + + +bool SCH_EASYEDA_PLUGIN::CanReadSchematicFile( const wxString& aFileName ) const +{ + if( !SCH_PLUGIN::CanReadSchematicFile( aFileName ) ) + return false; + + try + { + wxFFileInputStream in( aFileName ); + nlohmann::json js; + EASYEDA::DOCUMENT doc; + EASYEDA::DOC_TYPE docType; + + return FindSchFileInStream( aFileName, in, js, doc, docType ); + } + catch( nlohmann::json::exception& ) + { + } + catch( std::exception& ) + { + } + + return false; +} + + +bool SCH_EASYEDA_PLUGIN::CanReadLibrary( const wxString& aFileName ) const +{ + return CanReadSchematicFile( aFileName ); +} + + +int SCH_EASYEDA_PLUGIN::GetModifyHash() const +{ + return 0; +} + + +LIB_SYMBOL* loadSymbol( const wxString& aLibraryPath, nlohmann::json aFileData, + const wxString& aAliasName, const STRING_UTF8_MAP* aProperties ) +{ + SCH_EASYEDA_PARSER parser( nullptr, nullptr ); + std::map namesCounter; + + try + { + wxFFileInputStream in( aLibraryPath ); + nlohmann::json js; + EASYEDA::DOCUMENT topDoc; + EASYEDA::DOC_TYPE topDocType; + + if( !FindSchFileInStream( aLibraryPath, in, js, topDoc, topDocType ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Unable to find a valid schematic file in '%s'" ), + aLibraryPath ) ); + } + + if( topDocType == EASYEDA::DOC_TYPE::SCHEMATIC_SHEET + || topDocType == EASYEDA::DOC_TYPE::SCHEMATIC_LIST ) + { + EASYEDA::DOCUMENT_SCHEMATICS schDoc = js.get(); + + for( const EASYEDA::DOCUMENT& subDoc : *schDoc.schematics ) + { + if( subDoc.docType ) + { + if( *subDoc.docType != EASYEDA::DOC_TYPE::SCHEMATIC_SHEET ) + continue; + } + else + { + if( subDoc.head.docType != EASYEDA::DOC_TYPE::SCHEMATIC_SHEET ) + continue; + } + + EASYEDA::DOCUMENT dataStrDoc = subDoc.dataStr->get(); + + for( wxString shap : dataStrDoc.shape ) + { + if( !shap.Contains( wxS( "LIB" ) ) ) + continue; + + shap.Replace( wxS( "#@$" ), wxS( "\n" ) ); + wxArrayString parts = wxSplit( shap, '\n', '\0' ); + + if( parts.size() < 1 ) + continue; + + wxArrayString paramsRoot = wxSplit( parts[0], '~', '\0' ); + + if( paramsRoot.size() < 1 ) + continue; + + wxString rootType = paramsRoot[0]; + + if( rootType == wxS( "LIB" ) ) + { + if( paramsRoot.size() < 4 ) + continue; + + VECTOR2D origin( parser.Convert( paramsRoot[1] ), + parser.Convert( paramsRoot[2] ) ); + + wxString symbolName = wxString::Format( wxS( "Unknown_%s_%s" ), + paramsRoot[1], paramsRoot[2] ); + + wxArrayString paramParts = wxSplit( paramsRoot[3], '`', '\0' ); + + std::map paramMap; + + for( size_t i = 1; i < paramParts.size(); i += 2 ) + { + wxString key = paramParts[i - 1]; + wxString value = paramParts[i]; + + if( key == wxS( "spiceSymbolName" ) && !value.IsEmpty() ) + symbolName = value; + + paramMap[key] = value; + } + + int& serial = namesCounter[symbolName]; + + if( serial > 0 ) + symbolName << wxS( "_" ) << serial; + + serial++; + + paramMap[wxS( "spiceSymbolName" )] = symbolName; + + if( symbolName == aAliasName ) + { + parts.RemoveAt( 0 ); + + return parser.ParseSymbol( origin, paramMap, parts ); + } + } + } + } + } + else if( topDocType == EASYEDA::DOC_TYPE::SYMBOL ) + { + EASYEDA::DOCUMENT_SYM symDoc = js.get(); + + wxString symbolName = wxS( "Unknown" ); + + std::optional> c_para; + + if( symDoc.c_para ) + c_para = symDoc.c_para; + else if( topDoc.head.c_para ) + c_para = topDoc.head.c_para; + + if( !c_para ) + return nullptr; + + symbolName = get_def( *c_para, wxS( "name" ), symbolName ); + + int& serial = namesCounter[symbolName]; + + if( serial > 0 ) + symbolName << wxS( "_" ) << serial; + + serial++; + + if( symbolName != aAliasName ) + return nullptr; + + VECTOR2D origin( topDoc.head.x, topDoc.head.y ); + + return parser.ParseSymbol( origin, *c_para, topDoc.shape ); + } + } + catch( nlohmann::json::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error loading symbol '%s' from library '%s': %s" ), + aAliasName, aLibraryPath, e.what() ) ); + } + catch( std::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error loading symbol '%s' from library '%s': %s" ), + aAliasName, aLibraryPath, e.what() ) ); + } + + return nullptr; +} + + +void SCH_EASYEDA_PLUGIN::EnumerateSymbolLib( wxArrayString& aSymbolNameList, + const wxString& aLibraryPath, + const STRING_UTF8_MAP* aProperties ) +{ + std::map namesCounter; + + try + { + wxFFileInputStream in( aLibraryPath ); + nlohmann::json js; + EASYEDA::DOCUMENT topDoc; + EASYEDA::DOC_TYPE topDocType; + + if( !FindSchFileInStream( aLibraryPath, in, js, topDoc, topDocType ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Unable to find a valid schematic file in '%s'" ), + aLibraryPath ) ); + } + + if( topDocType == EASYEDA::DOC_TYPE::SCHEMATIC_SHEET + || topDocType == EASYEDA::DOC_TYPE::SCHEMATIC_LIST ) + { + EASYEDA::DOCUMENT_SCHEMATICS schDoc = js.get(); + + for( const EASYEDA::DOCUMENT& subDoc : *schDoc.schematics ) + { + if( subDoc.docType ) + { + if( *subDoc.docType != EASYEDA::DOC_TYPE::SCHEMATIC_SHEET ) + continue; + } + else + { + if( subDoc.head.docType != EASYEDA::DOC_TYPE::SCHEMATIC_SHEET ) + continue; + } + + EASYEDA::DOCUMENT dataStrDoc = subDoc.dataStr->get(); + + for( wxString shap : dataStrDoc.shape ) + { + if( !shap.Contains( wxS( "LIB" ) ) ) + continue; + + shap.Replace( wxS( "#@$" ), wxS( "\n" ) ); + wxArrayString parts = wxSplit( shap, '\n', '\0' ); + + if( parts.size() < 1 ) + continue; + + wxArrayString paramsRoot = wxSplit( parts[0], '~', '\0' ); + + if( paramsRoot.size() < 1 ) + continue; + + wxString rootType = paramsRoot[0]; + + if( rootType == wxS( "LIB" ) ) + { + if( paramsRoot.size() < 4 ) + continue; + + wxString symbolName = wxString::Format( wxS( "Unknown_%s_%s" ), + paramsRoot[1], paramsRoot[2] ); + + wxArrayString paramParts = wxSplit( paramsRoot[3], '`', '\0' ); + + std::map paramMap; + + for( size_t i = 1; i < paramParts.size(); i += 2 ) + { + wxString key = paramParts[i - 1]; + wxString value = paramParts[i]; + + if( key == wxS( "spiceSymbolName" ) && !value.IsEmpty() ) + symbolName = value; + + paramMap[key] = value; + } + + int& serial = namesCounter[symbolName]; + + if( serial > 0 ) + symbolName << wxS( "_" ) << serial; + + serial++; + + aSymbolNameList.Add( symbolName ); + } + } + } + } + else if( topDocType == EASYEDA::DOC_TYPE::SYMBOL ) + { + EASYEDA::DOCUMENT_SYM symDoc = js.get(); + + wxString packageName = wxS( "Unknown" ); + + if( symDoc.c_para ) + { + packageName = get_def( *symDoc.c_para, wxS( "name" ), packageName ); + } + else if( topDoc.head.c_para ) + { + packageName = get_def( *topDoc.head.c_para, wxS( "name" ), packageName ); + } + + aSymbolNameList.Add( packageName ); + } + } + catch( nlohmann::json::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error enumerating symbol library '%s': %s" ), + aLibraryPath, e.what() ) ); + } + catch( std::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error enumerating symbol library '%s': %s" ), + aLibraryPath, e.what() ) ); + } +} + + +void SCH_EASYEDA_PLUGIN::EnumerateSymbolLib( std::vector& aSymbolList, + const wxString& aLibraryPath, + const STRING_UTF8_MAP* aProperties ) +{ + wxFFileInputStream in( aLibraryPath ); + nlohmann::json js; + EASYEDA::DOCUMENT topDoc; + EASYEDA::DOC_TYPE topDocType; + + if( !FindSchFileInStream( aLibraryPath, in, js, topDoc, topDocType ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Unable to find a valid schematic file in '%s'" ), + aLibraryPath ) ); + } + + try + { + wxArrayString symbolNameList; + + EnumerateSymbolLib( symbolNameList, aLibraryPath, aProperties ); + + for( const wxString& symbolName : symbolNameList ) + { + LIB_SYMBOL* sym = loadSymbol( aLibraryPath, js, symbolName, aProperties ); + + if( sym ) + aSymbolList.push_back( sym ); + } + } + catch( nlohmann::json::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error enumerating symbol library '%s': %s" ), + aLibraryPath, e.what() ) ); + } + catch( std::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error enumerating symbol library '%s': %s" ), + aLibraryPath, e.what() ) ); + } +} + + +LIB_SYMBOL* SCH_EASYEDA_PLUGIN::LoadSymbol( const wxString& aLibraryPath, + const wxString& aAliasName, + const STRING_UTF8_MAP* aProperties ) +{ + try + { + wxFFileInputStream in( aLibraryPath ); + nlohmann::json js; + EASYEDA::DOCUMENT topDoc; + EASYEDA::DOC_TYPE topDocType; + + if( !FindSchFileInStream( aLibraryPath, in, js, topDoc, topDocType ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Unable to find a valid schematic file in '%s'" ), + aLibraryPath ) ); + } + + return loadSymbol( aLibraryPath, js, aAliasName, aProperties ); + } + catch( nlohmann::json::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error loading symbol '%s' from library '%s': %s" ), + aAliasName, aLibraryPath, e.what() ) ); + } + catch( std::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error loading symbol '%s' from library '%s': %s" ), + aAliasName, aLibraryPath, e.what() ) ); + } + + return nullptr; +} + + +static void LoadSchematic( SCHEMATIC* aSchematic, SCH_SHEET* aRootSheet, const wxString& aFileName ) +{ + SCH_EASYEDA_PARSER parser( nullptr, nullptr ); + + try + { + wxFFileInputStream in( aFileName ); + nlohmann::json js; + EASYEDA::DOCUMENT topDoc; + EASYEDA::DOC_TYPE topDocType; + + if( !FindSchFileInStream( aFileName, in, js, topDoc, topDocType ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Unable to find a valid schematic file in '%s'" ), + aFileName ) ); + } + + if( topDocType == EASYEDA::DOC_TYPE::SCHEMATIC_SHEET + || topDocType == EASYEDA::DOC_TYPE::SCHEMATIC_LIST ) + { + EASYEDA::DOCUMENT_SCHEMATICS schDoc = js.get(); + + bool first = true; + + for( const EASYEDA::DOCUMENT& subDoc : *schDoc.schematics ) + { + if( first ) + first = false; // TODO + else + break; + + if( subDoc.docType ) + { + if( *subDoc.docType != EASYEDA::DOC_TYPE::SCHEMATIC_SHEET ) + continue; + } + else + { + if( subDoc.head.docType != EASYEDA::DOC_TYPE::SCHEMATIC_SHEET ) + continue; + } + + EASYEDA::DOCUMENT dataStrDoc = subDoc.dataStr->get(); + + parser.ParseSchematic( aSchematic, aRootSheet, aFileName, dataStrDoc.shape ); + } + } + } + catch( nlohmann::json::exception& e ) + { + THROW_IO_ERROR( + wxString::Format( _( "Error loading schematic '%s': %s" ), aFileName, e.what() ) ); + } + catch( std::exception& e ) + { + THROW_IO_ERROR( + wxString::Format( _( "Error loading schematic '%s': %s" ), aFileName, e.what() ) ); + } +} + + +SCH_SHEET* SCH_EASYEDA_PLUGIN::LoadSchematicFile( const wxString& aFileName, SCHEMATIC* aSchematic, + SCH_SHEET* aAppendToMe, + const STRING_UTF8_MAP* aProperties ) +{ + wxCHECK( !aFileName.IsEmpty() && aSchematic, nullptr ); + + SCH_SHEET* rootSheet = nullptr; + + if( aAppendToMe ) + { + wxCHECK_MSG( aSchematic->IsValid(), nullptr, + wxS( "Can't append to a schematic with no root!" ) ); + + rootSheet = &aSchematic->Root(); + } + else + { + rootSheet = new SCH_SHEET( aSchematic ); + rootSheet->SetFileName( aFileName ); + aSchematic->SetRoot( rootSheet ); + } + + if( !rootSheet->GetScreen() ) + { + SCH_SCREEN* screen = new SCH_SCREEN( aSchematic ); + + screen->SetFileName( aFileName ); + rootSheet->SetScreen( screen ); + } + + SYMBOL_LIB_TABLE* libTable = aSchematic->Prj().SchSymbolLibTable(); + + wxCHECK_MSG( libTable, nullptr, wxS( "Could not load symbol lib table." ) ); + LoadSchematic( aSchematic, rootSheet, aFileName ); + aSchematic->CurrentSheet().UpdateAllScreenReferences(); + + return rootSheet; +} diff --git a/eeschema/sch_plugins/easyeda/sch_easyeda_plugin.h b/eeschema/sch_plugins/easyeda/sch_easyeda_plugin.h new file mode 100644 index 0000000000..6118fe9f40 --- /dev/null +++ b/eeschema/sch_plugins/easyeda/sch_easyeda_plugin.h @@ -0,0 +1,90 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef SCH_EASYEDA_PLUGIN_H_ +#define SCH_EASYEDA_PLUGIN_H_ + +#include +#include + + +class SCH_SHEET; +class SCH_SCREEN; + + +class SCH_EASYEDA_PLUGIN : public SCH_PLUGIN +{ +public: + SCH_EASYEDA_PLUGIN() + { + m_reporter = &WXLOG_REPORTER::GetInstance(); + m_progressReporter = nullptr; + } + + ~SCH_EASYEDA_PLUGIN() {} + + const wxString GetName() const override; + + void SetReporter( REPORTER* aReporter ) override { m_reporter = aReporter; } + + void SetProgressReporter( PROGRESS_REPORTER* aReporter ) override + { + m_progressReporter = aReporter; + } + + const PLUGIN_FILE_DESC GetSchematicFileDesc() const override + { + return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Std files" ), { "json" } ); + } + + const PLUGIN_FILE_DESC GetLibraryFileDesc() const override { return GetSchematicFileDesc(); } + + bool CanReadSchematicFile( const wxString& aFileName ) const override; + + bool CanReadLibrary( const wxString& aFileName ) const override; + + int GetModifyHash() const override; + + SCH_SHEET* LoadSchematicFile( const wxString& aFileName, SCHEMATIC* aSchematic, + SCH_SHEET* aAppendToMe = nullptr, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + void EnumerateSymbolLib( wxArrayString& aSymbolNameList, const wxString& aLibraryPath, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + void EnumerateSymbolLib( std::vector& aSymbolList, const wxString& aLibraryPath, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + LIB_SYMBOL* LoadSymbol( const wxString& aLibraryPath, const wxString& aAliasName, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + bool IsSymbolLibWritable( const wxString& aLibraryPath ) override { return false; } + +private: + REPORTER* m_reporter; // current reporter for warnings/errors + PROGRESS_REPORTER* m_progressReporter; // optional; may be nullptr +}; + + +#endif // SCH_EASYEDA_PLUGIN_H_ diff --git a/eeschema/sch_plugins/easyedapro/sch_easyedapro_parser.cpp b/eeschema/sch_plugins/easyedapro/sch_easyedapro_parser.cpp new file mode 100644 index 0000000000..ff95ef47ca --- /dev/null +++ b/eeschema/sch_plugins/easyedapro/sch_easyedapro_parser.cpp @@ -0,0 +1,1300 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 PLOT_DASH_TYPE ConvertStrokeStyle( int aStyle ) +{ + if( aStyle == 0 ) + return PLOT_DASH_TYPE::SOLID; + else if( aStyle == 1 ) + return PLOT_DASH_TYPE::DASH; + else if( aStyle == 2 ) + return PLOT_DASH_TYPE::DOT; + else if( aStyle == 3 ) + return PLOT_DASH_TYPE::DASHDOT; + + return PLOT_DASH_TYPE::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.SetPlotStyle( ConvertStrokeStyle( dashStyle ) ); + } + + if( style.at( 5 ).is_number() ) + { + double thickness = style.at( 5 ); + stroke.SetWidth( ScaleSize( thickness ) ); + } + + shape->SetStroke( stroke ); +} + + +template +void SCH_EASYEDAPRO_PARSER::ApplyAttrToField( const std::map& fontStyles, + T* field, const EASYEDAPRO::SCH_ATTR& aAttr, + bool aIsSym, bool aToSym, SCH_SYMBOL* aParent ) +{ + EDA_TEXT* text = static_cast( field ); + + text->SetVisible( aAttr.keyVisible || aAttr.valVisible ); + text->SetText( aAttr.value ); + + 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 ) +{ + 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 ); + + std::unique_ptr rect = + std::make_unique( ksymbol, SHAPE_T::RECTANGLE ); + + 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 ); + + std::unique_ptr circle = + std::make_unique( ksymbol, SHAPE_T::CIRCLE ); + + 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 ); + + std::unique_ptr shape = std::make_unique( ksymbol, SHAPE_T::ARC ); + + 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 points = line.at( 2 ); + wxString styleStr = line.at( 4 ); + + std::unique_ptr shape = + std::make_unique( ksymbol, SHAPE_T::POLY ); + + 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 ); + + std::unique_ptr text = std::make_unique( ksymbol ); + + text->SetPosition( ScalePosSym( pos ) ); + text->SetText( UnescapeHTML( textStr ) ); + + 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 " wxS( "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 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 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' ); + + 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(); + } + + if( mimeType.empty() || base64Data.empty() ) + continue; + + wxMemoryBuffer buf = wxBase64Decode( base64Data ); + + 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( ScaleSizeUnit( size.x ) ) / imSize.x, + // schIUScale.IUTomm( -ScaleSizeUnit( 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 + { + std::unique_ptr bitmap = std::make_unique(); + + wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags() + & ~wxImage::Load_Verbose ); + + if( bitmap->ReadImageFile( buf ) ) + { + VECTOR2D kstart = ScalePos( start ); + VECTOR2D ksize = ScaleSize( size ); + VECTOR2D kcenter = kstart + ksize / 2; + + RotatePoint( kcenter, kstart, -EDA_ANGLE( angle, DEGREES_T ) ); + // TODO: rotation + + double scaleFactor = ScaleSize( size.x ) / bitmap->GetSize().x; + bitmap->SetImageScale( scaleFactor ); + bitmap->SetPosition( kcenter ); + + 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; + + 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(); + + 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() ); + + std::unique_ptr 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() ); + + 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, schSym.get() ); + + for( LIB_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 ); + + TEXT_SPIN_STYLE spin = TEXT_SPIN_STYLE::LEFT; + + for( double i = component->rotation; i > 0; i -= 90 ) + spin = spin.RotateCCW(); + + label->SetTextSpinStyle( spin ); + + if( esymInfo.symbolAttr ) + { + wxString symStr = esymInfo.symbolAttr->value; + + if( symStr == wxS( "Netport-IN" ) ) + label->SetShape( LABEL_FLAG_SHAPE::L_INPUT ); + if( symStr == wxS( "Netport-OUT" ) ) + label->SetShape( LABEL_FLAG_SHAPE::L_OUTPUT ); + if( symStr == wxS( "Netport-BI" ) ) + label->SetShape( LABEL_FLAG_SHAPE::L_BIDI ); + } + + 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 + { + 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, schSym.get() ); + + if( auto descrAttr = get_opt( attributes, "Description" ) ) + ApplyAttrToField( fontStyles, schSym->GetField( DESCRIPTION_FIELD ), *descrAttr, + false, true, schSym.get() ); + + if( auto designatorAttr = get_opt( attributes, "Designator" ) ) + { + ApplyAttrToField( fontStyles, schSym->GetField( REFERENCE_FIELD ), + *designatorAttr, false, true, 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, 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 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() ); + } +} diff --git a/eeschema/sch_plugins/easyedapro/sch_easyedapro_parser.h b/eeschema/sch_plugins/easyedapro/sch_easyedapro_parser.h new file mode 100644 index 0000000000..ab18637025 --- /dev/null +++ b/eeschema/sch_plugins/easyedapro/sch_easyedapro_parser.h @@ -0,0 +1,130 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef SCH_EASYEDAPRO_PARSER_H_ +#define SCH_EASYEDAPRO_PARSER_H_ + +#include + +#include +#include +#include +#include +#include + + +class EDA_TEXT; +class LIB_SHAPE; +class LIB_FIELD; +class LIB_PIN; +class SCH_LABEL_BASE; +class SCH_SYMBOL; +class SCH_TEXT; +class SCH_SHAPE; + +namespace EASYEDAPRO +{ +struct PIN_INFO +{ + EASYEDAPRO::SYM_PIN pin; + wxString number; + wxString name; +}; + +struct SYM_INFO +{ + EASYEDAPRO::SYM_HEAD head; + std::vector pins; + std::unique_ptr libSymbol; + std::optional symbolAttr; + std::map partUnits; +}; +} // namespace EASYEDAPRO + + +class SCH_EASYEDAPRO_PARSER +{ + +public: + explicit SCH_EASYEDAPRO_PARSER( SCHEMATIC* aSchematic, PROGRESS_REPORTER* aProgressReporter ); + ~SCH_EASYEDAPRO_PARSER(); + + /*void Parse( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile, + const std::map& aFileMapping );*/ + + static double Convert( wxString aValue ); + + template + static T ScaleSize( T aValue ) + { + return KiROUND( schIUScale.MilsToIU( aValue * 10 ) ); + } + + template + static VECTOR2 ScaleSize( VECTOR2 aValue ) + { + return VECTOR2( ScaleSize( aValue.x ), ScaleSize( aValue.y ) ); + } + + template + static VECTOR2 ScalePos( VECTOR2 aValue ) + { + return VECTOR2( ScaleSize( aValue.x ), -ScaleSize( aValue.y ) ); + } + + template + static VECTOR2 ScalePosSym( VECTOR2 aValue ) + { + return VECTOR2( ScaleSize( aValue.x ), ScaleSize( aValue.y ) ); + } + + double SizeToKi( wxString units ); + + EASYEDAPRO::SYM_INFO ParseSymbol( const std::vector& aLines ); + + void ParseSchematic( SCHEMATIC* aSchematic, SCH_SHEET* aRootSheet, + const nlohmann::json& aProject, + std::map& aSymbolMap, + const std::map& aBlobMap, + const std::vector& aLines, const wxString& aLibName ); + +protected: + SCHEMATIC* m_schematic; + + template + void ApplyFontStyle( const std::map& fontStyles, T& text, + const wxString& styleStr ); + + template + void ApplyLineStyle( const std::map& lineStyles, T& shape, + const wxString& styleStr ); + + template + void ApplyAttrToField( const std::map& fontStyles, T* text, + const EASYEDAPRO::SCH_ATTR& aAttr, bool aIsSym, bool aToSym, + SCH_SYMBOL* aParent = nullptr ); +}; + + +#endif // SCH_EASYEDAPRO_PARSER_H_ diff --git a/eeschema/sch_plugins/easyedapro/sch_easyedapro_plugin.cpp b/eeschema/sch_plugins/easyedapro/sch_easyedapro_plugin.cpp new file mode 100644 index 0000000000..a3a2679ece --- /dev/null +++ b/eeschema/sch_plugins/easyedapro/sch_easyedapro_plugin.cpp @@ -0,0 +1,566 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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_plugin.h" +#include "sch_easyedapro_parser.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +struct SCH_EASYEDAPRO_PLUGIN::PRJ_DATA +{ + std::map m_Symbols; + std::map m_Blobs; +}; + + +SCH_EASYEDAPRO_PLUGIN::SCH_EASYEDAPRO_PLUGIN() +{ + m_reporter = &WXLOG_REPORTER::GetInstance(); + m_progressReporter = nullptr; +} + + +SCH_EASYEDAPRO_PLUGIN::~SCH_EASYEDAPRO_PLUGIN() +{ + if( m_projectData ) + delete m_projectData; +} + + +const wxString SCH_EASYEDAPRO_PLUGIN::GetName() const +{ + return wxT( "EasyEDA (JLCEDA) Schematic Importer" ); +} + + +bool SCH_EASYEDAPRO_PLUGIN::CanReadSchematicFile( const wxString& aFileName ) const +{ + if( aFileName.Lower().EndsWith( wxS( ".epro" ) ) ) + { + return true; + } + else if( aFileName.Lower().EndsWith( wxS( ".zip" ) ) ) + { + std::shared_ptr entry; + wxFFileInputStream in( aFileName ); + wxZipInputStream zip( in ); + + if( !zip.IsOk() ) + return false; + + while( entry.reset( zip.GetNextEntry() ), entry.get() != NULL ) + { + wxString name = entry->GetName(); + + if( name == wxS( "project.json" ) ) + return true; + } + + return false; + } + + return false; +} + + +int SCH_EASYEDAPRO_PLUGIN::GetModifyHash() const +{ + return 0; +} + + +static LIB_SYMBOL* loadSymbol( nlohmann::json project, const wxString& aLibraryPath, + const wxString& aAliasName, const STRING_UTF8_MAP* aProperties ) +{ + SCH_EASYEDAPRO_PARSER parser( nullptr, nullptr ); + LIB_SYMBOL* symbol = nullptr; + wxFileName libFname( aLibraryPath ); + wxString symLibName = EASYEDAPRO::ShortenLibName( libFname.GetName() ); + + /*if( libFname.GetExt() == wxS( "esym" ) ) + { + wxFFileInputStream ffis( aLibraryPath ); + wxTextInputStream txt( ffis, wxS( " " ), wxConvUTF8 ); + + bool loadThis = false; + std::vector lines; + while( ffis.CanRead() ) + { + nlohmann::json js = nlohmann::json::parse( txt.ReadLine() ); + lines.emplace_back( js ); + + if( js.at( 0 ) == "ATTR" && js.at( 3 ) == "Symbol" ) + { + if( js.at( 4 ).get() == aAliasName ) + { + loadThis = true; + } + } + } + + if( loadThis ) + { + EASYEDAPRO::SYM_INFO symInfo = parser.ParseSymbol( lines ); + return symInfo.libSymbol.release(); + } + } + else */ + if( libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) ) + { + std::map prjSymbols = project.at( "symbols" ); + std::map prjFootprints = project.at( "footprints" ); + std::map prjDevices = project.at( "devices" ); + + auto prjSymIt = std::find_if( prjSymbols.begin(), prjSymbols.end(), + [&]( const auto& pair ) + { + return pair.second.title == aAliasName; + } ); + + if( prjSymIt == prjSymbols.end() ) + return nullptr; + + wxString prjSymUuid = prjSymIt->first; + wxString fpTitle; + + for( auto& [key, device] : prjDevices ) + { + auto val = get_opt( device.attributes, "Symbol" ); + + if( val && *val == prjSymUuid ) + { + if( auto fpUuid = get_opt( device.attributes, "Footprint" ) ) + { + if( auto prjFp = get_opt( prjFootprints, *fpUuid ) ) + { + fpTitle = prjFp->title; + break; + } + } + } + } + + auto cb = [&]( const wxString& name, const wxString& symUuid, wxInputStream& zip ) -> bool + { + if( !name.EndsWith( wxS( ".esym" ) ) ) + EASY_IT_CONTINUE; + + if( symUuid != prjSymUuid ) + EASY_IT_CONTINUE; + + wxTextInputStream txt( zip, wxS( " " ), wxConvUTF8 ); + + std::vector lines; + while( zip.CanRead() ) + { + nlohmann::json js = nlohmann::json::parse( txt.ReadLine() ); + lines.emplace_back( js ); + } + + EASYEDAPRO::SYM_INFO symInfo = parser.ParseSymbol( lines ); + + if( !symInfo.libSymbol ) + EASY_IT_CONTINUE; + + LIB_ID libID = EASYEDAPRO::ToKiCadLibID( symLibName, aAliasName ); + symInfo.libSymbol->SetLibId( libID ); + symInfo.libSymbol->SetName( aAliasName ); + symInfo.libSymbol->GetFootprintField().SetText( symLibName + wxS( "/" ) + fpTitle ); + + symbol = symInfo.libSymbol.release(); + + EASY_IT_BREAK; + }; + EASYEDAPRO::IterateZipFiles( aLibraryPath, cb ); + } + + return symbol; +} + + +void SCH_EASYEDAPRO_PLUGIN::EnumerateSymbolLib( wxArrayString& aSymbolNameList, + const wxString& aLibraryPath, + const STRING_UTF8_MAP* aProperties ) +{ + wxFileName fname( aLibraryPath ); + + if( fname.GetExt() == wxS( "esym" ) ) + { + wxFFileInputStream ffis( aLibraryPath ); + wxTextInputStream txt( ffis, wxS( " " ), wxConvUTF8 ); + + while( ffis.CanRead() ) + { + wxString line = txt.ReadLine(); + + if( !line.Contains( wxS( "ATTR" ) ) ) + continue; // Don't bother parsing + + nlohmann::json js = nlohmann::json::parse( line ); + if( js.at( 0 ) == "ATTR" && js.at( 3 ) == "Symbol" ) + { + aSymbolNameList.Add( js.at( 4 ).get() ); + } + } + } + else if( fname.GetExt() == wxS( "epro" ) || fname.GetExt() == wxS( "zip" ) ) + { + nlohmann::json project = EASYEDAPRO::ReadProjectFile( aLibraryPath ); + std::map symbolMap = project.at( "symbols" ); + + for( auto& [key, value] : symbolMap ) + aSymbolNameList.Add( value.at( "title" ) ); + } +} + + +void SCH_EASYEDAPRO_PLUGIN::EnumerateSymbolLib( std::vector& aSymbolList, + const wxString& aLibraryPath, + const STRING_UTF8_MAP* aProperties ) +{ + wxFileName libFname( aLibraryPath ); + wxArrayString symbolNameList; + nlohmann::json project; + + EnumerateSymbolLib( symbolNameList, aLibraryPath, aProperties ); + + if( libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) ) + project = EASYEDAPRO::ReadProjectFile( aLibraryPath ); + + for( const wxString& symbolName : symbolNameList ) + { + LIB_SYMBOL* sym = loadSymbol( project, aLibraryPath, symbolName, aProperties ); + + if( sym ) + aSymbolList.push_back( sym ); + } +} + + +void SCH_EASYEDAPRO_PLUGIN::LoadAllDataFromProject( const wxString& aProjectPath ) +{ + if( m_projectData ) + delete m_projectData; + + m_projectData = new PRJ_DATA; + + SCH_EASYEDAPRO_PARSER parser( nullptr, nullptr ); + wxFileName fname( aProjectPath ); + wxString symLibName = EASYEDAPRO::ShortenLibName( fname.GetName() ); + + if( fname.GetExt() != wxS( "epro" ) && fname.GetExt() != wxS( "zip" ) ) + return; + + nlohmann::json project = EASYEDAPRO::ReadProjectFile( aProjectPath ); + + std::map prjSymbols = project.at( "symbols" ); + std::map prjFootprints = project.at( "footprints" ); + std::map prjDevices = project.at( "devices" ); + + auto cb = [&]( const wxString& name, const wxString& baseName, wxInputStream& zip ) -> bool + { + if( !name.EndsWith( wxS( ".esym" ) ) && !name.EndsWith( wxS( ".eblob" ) ) ) + EASY_IT_CONTINUE; + + std::vector lines = EASYEDAPRO::ParseJsonLines( zip, name ); + + if( name.EndsWith( wxS( ".esym" ) ) ) + { + EASYEDAPRO::PRJ_SYMBOL symData = prjSymbols.at( baseName ); + EASYEDAPRO::SYM_INFO symInfo = parser.ParseSymbol( lines ); + + if( !symInfo.libSymbol ) + EASY_IT_CONTINUE; + + wxString fpTitle; + + for( auto& [key, device] : prjDevices ) + { + auto val = get_opt( device.attributes, "Symbol" ); + + if( val && *val == baseName ) + { + if( auto fpUuid = get_opt( device.attributes, "Footprint" ) ) + { + if( auto prjFp = get_opt( prjFootprints, *fpUuid ) ) + { + fpTitle = prjFp->title; + break; + } + } + } + } + + LIB_ID libID = EASYEDAPRO::ToKiCadLibID( symLibName, symData.title ); + symInfo.libSymbol->SetLibId( libID ); + symInfo.libSymbol->SetName( symData.title ); + symInfo.libSymbol->GetFootprintField().SetText( symLibName + wxS( "/" ) + fpTitle ); + + m_projectData->m_Symbols.emplace( baseName, std::move( symInfo ) ); + } + else if( name.EndsWith( wxS( ".eblob" ) ) ) + { + for( const nlohmann::json& line : lines ) + { + if( line.at( 0 ) == "BLOB" ) + { + EASYEDAPRO::BLOB blob = line; + m_projectData->m_Blobs[blob.objectId] = blob; + } + } + } + + EASY_IT_CONTINUE; + }; + EASYEDAPRO::IterateZipFiles( aProjectPath, cb ); +} + + +LIB_SYMBOL* SCH_EASYEDAPRO_PLUGIN::LoadSymbol( const wxString& aLibraryPath, + const wxString& aAliasName, + const STRING_UTF8_MAP* aProperties ) +{ + wxFileName libFname( aLibraryPath ); + nlohmann::json project; + + if( libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) ) + project = EASYEDAPRO::ReadProjectFile( aLibraryPath ); + + return loadSymbol( project, aLibraryPath, aAliasName, aProperties ); +} + + +SCH_SHEET* SCH_EASYEDAPRO_PLUGIN::LoadSchematicFile( const wxString& aFileName, + SCHEMATIC* aSchematic, SCH_SHEET* aAppendToMe, + const STRING_UTF8_MAP* aProperties ) +{ + wxCHECK( !aFileName.IsEmpty() && aSchematic, nullptr ); + + SCH_SHEET* rootSheet = nullptr; + + if( aAppendToMe ) + { + wxCHECK_MSG( aSchematic->IsValid(), nullptr, + wxS( "Can't append to a schematic with no root!" ) ); + + rootSheet = &aSchematic->Root(); + } + else + { + rootSheet = new SCH_SHEET( aSchematic ); + rootSheet->SetFileName( aFileName ); + aSchematic->SetRoot( rootSheet ); + } + + if( !rootSheet->GetScreen() ) + { + SCH_SCREEN* screen = new SCH_SCREEN( aSchematic ); + + screen->SetFileName( aFileName ); + rootSheet->SetScreen( screen ); + } + + SYMBOL_LIB_TABLE* libTable = aSchematic->Prj().SchSymbolLibTable(); + wxCHECK_MSG( libTable, nullptr, wxS( "Could not load symbol lib table." ) ); + + SCH_EASYEDAPRO_PARSER parser( nullptr, nullptr ); + wxFileName fname( aFileName ); + wxString libName = EASYEDAPRO::ShortenLibName( fname.GetName() ); + + wxFileName libFileName( fname.GetPath(), libName, KiCadSymbolLibFileExtension ); + + if( fname.GetExt() != wxS( "epro" ) && fname.GetExt() != wxS( "zip" ) ) + return rootSheet; + + nlohmann::json project = EASYEDAPRO::ReadProjectFile( aFileName ); + + std::map prjSchematics = project.at( "schematics" ); + std::map prjBoards = project.at( "boards" ); + std::map prjPcbNames = project.at( "pcbs" ); + + wxString schematicToLoad; + + if( prjBoards.size() > 0 ) + { + EASYEDAPRO::PRJ_BOARD boardToLoad = prjBoards.begin()->second; + schematicToLoad = boardToLoad.schematic; + } + else if( prjSchematics.size() > 0 ) + { + schematicToLoad = prjSchematics.begin()->first; + } + + if( schematicToLoad.empty() ) + return nullptr; + + wxString rootBaseName = EscapeString( prjSchematics[schematicToLoad].name, CTX_FILENAME ); + + wxFileName rootFname( aFileName ); + rootFname.SetFullName( rootBaseName + wxS( "." ) + + wxString::FromUTF8( KiCadSchematicFileExtension ) ); + + rootSheet->SetName( prjSchematics[schematicToLoad].name ); + rootSheet->SetFileName( rootFname.GetFullPath() ); + rootSheet->GetScreen()->SetFileName( rootFname.GetFullPath() ); + + const std::vector& prjSchematicSheets = + prjSchematics[schematicToLoad].sheets; + + LoadAllDataFromProject( aFileName ); + + if( !m_projectData ) + return nullptr; + + auto cbs = [&]( const wxString& name, const wxString& symUuid, wxInputStream& zip ) -> bool + { + if( !name.EndsWith( wxS( ".esch" ) ) ) + EASY_IT_CONTINUE; + + wxArrayString nameParts = wxSplit( name, '\\', '\0' ); + + if( nameParts.size() == 1 ) + nameParts = wxSplit( name, '/', '\0' ); + + if( nameParts.size() < 3 ) + EASY_IT_CONTINUE; + + wxString schematicUuid = nameParts[1]; + wxString sheetFileName = nameParts[2]; + wxString sheetId = sheetFileName.BeforeLast( '.' ); + int sheetId_i; + sheetId.ToInt( &sheetId_i ); + + if( schematicUuid != schematicToLoad ) + EASY_IT_CONTINUE; + + auto prjSheetIt = std::find_if( prjSchematicSheets.begin(), prjSchematicSheets.end(), + [&]( const EASYEDAPRO::PRJ_SHEET& s ) + { + return s.id == sheetId_i; + } ); + + if( prjSheetIt == prjSchematicSheets.end() ) + EASY_IT_CONTINUE; + + wxString sheetBaseName = + sheetId + wxS( "_" ) + EscapeString( prjSheetIt->name, CTX_FILENAME ); + + wxFileName sheetFname( aFileName ); + sheetFname.SetFullName( sheetBaseName + wxS( "." ) + + wxString::FromUTF8( KiCadSchematicFileExtension ) ); + + std::unique_ptr subSheet = std::make_unique( aSchematic ); + subSheet->SetFileName( sheetFname.GetFullPath() ); + subSheet->SetName( prjSheetIt->name ); + + SCH_SCREEN* screen = new SCH_SCREEN( aSchematic ); + screen->SetFileName( sheetFname.GetFullPath() ); + screen->SetPageNumber( sheetId ); + subSheet->SetScreen( screen ); + + VECTOR2I pos; + pos.x = schIUScale.MilsToIU( 200 ); + pos.y = schIUScale.MilsToIU( 200 ) + + ( subSheet->GetSize().y + schIUScale.MilsToIU( 200 ) ) * ( sheetId_i - 1 ); + + subSheet->SetPosition( pos ); + + std::vector lines = EASYEDAPRO::ParseJsonLines( zip, name ); + + SCH_SHEET_PATH sheetPath; + sheetPath.push_back( rootSheet ); + sheetPath.push_back( subSheet.get() ); + sheetPath.SetPageNumber( sheetId ); + aSchematic->SetCurrentSheet( sheetPath ); + + parser.ParseSchematic( aSchematic, subSheet.get(), project, m_projectData->m_Symbols, + m_projectData->m_Blobs, lines, libName ); + + rootSheet->GetScreen()->Append( subSheet.release() ); + + EASY_IT_CONTINUE; + }; + EASYEDAPRO::IterateZipFiles( aFileName, cbs ); + + SCH_PLUGIN::SCH_PLUGIN_RELEASER sch_plugin; + sch_plugin.set( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) ); + + if( !libTable->HasLibrary( libName ) ) + { + // Create a new empty symbol library. + sch_plugin->CreateSymbolLib( libFileName.GetFullPath() ); + wxString libTableUri = wxS( "${KIPRJMOD}/" ) + libFileName.GetFullName(); + + // Add the new library to the project symbol library table. + libTable->InsertRow( new SYMBOL_LIB_TABLE_ROW( libName, libTableUri, wxS( "KiCad" ) ) ); + + // Save project symbol library table. + wxFileName fn( aSchematic->Prj().GetProjectPath(), + SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() ); + + // So output formatter goes out of scope and closes the file before reloading. + { + FILE_OUTPUTFORMATTER formatter( fn.GetFullPath() ); + libTable->Format( &formatter, 0 ); + } + + // Relaod the symbol library table. + aSchematic->Prj().SetElem( PROJECT::ELEM_SYMBOL_LIB_TABLE, NULL ); + aSchematic->Prj().SchSymbolLibTable(); + } + + // set properties to prevent save file on every symbol save + STRING_UTF8_MAP properties; + properties.emplace( SCH_SEXPR_PLUGIN::PropBuffering, wxEmptyString ); + + for( auto& [symbolUuid, symInfo] : m_projectData->m_Symbols ) + sch_plugin->SaveSymbol( libFileName.GetFullPath(), symInfo.libSymbol.release(), + &properties ); + + sch_plugin->SaveLibrary( libFileName.GetFullPath() ); + + aSchematic->CurrentSheet().UpdateAllScreenReferences(); + aSchematic->FixupJunctions(); + + return rootSheet; +} diff --git a/eeschema/sch_plugins/easyedapro/sch_easyedapro_plugin.h b/eeschema/sch_plugins/easyedapro/sch_easyedapro_plugin.h new file mode 100644 index 0000000000..fe2db0d70b --- /dev/null +++ b/eeschema/sch_plugins/easyedapro/sch_easyedapro_plugin.h @@ -0,0 +1,88 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef SCH_EASYEDAPRO_PLUGIN_H_ +#define SCH_EASYEDAPRO_PLUGIN_H_ + +#include +#include + + +class SCH_SHEET; +class SCH_SCREEN; + + +class SCH_EASYEDAPRO_PLUGIN : public SCH_PLUGIN +{ +public: + SCH_EASYEDAPRO_PLUGIN(); + ~SCH_EASYEDAPRO_PLUGIN(); + + const wxString GetName() const override; + + void SetReporter( REPORTER* aReporter ) override { m_reporter = aReporter; } + + void SetProgressReporter( PROGRESS_REPORTER* aReporter ) override + { + m_progressReporter = aReporter; + } + + const PLUGIN_FILE_DESC GetSchematicFileDesc() const override + { + return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Pro files" ), { "epro", "zip" } ); + } + + const PLUGIN_FILE_DESC GetLibraryFileDesc() const override { return GetSchematicFileDesc(); } + + bool CanReadSchematicFile( const wxString& aFileName ) const override; + + int GetModifyHash() const override; + + SCH_SHEET* LoadSchematicFile( const wxString& aFileName, SCHEMATIC* aSchematic, + SCH_SHEET* aAppendToMe = nullptr, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + void EnumerateSymbolLib( wxArrayString& aSymbolNameList, const wxString& aLibraryPath, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + void EnumerateSymbolLib( std::vector& aSymbolList, const wxString& aLibraryPath, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + void LoadAllDataFromProject( const wxString& aLibraryPath ); + + LIB_SYMBOL* LoadSymbol( const wxString& aLibraryPath, const wxString& aAliasName, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + bool IsSymbolLibWritable( const wxString& aLibraryPath ) override { return false; } + +private: + struct PRJ_DATA; // Opaque data structure + PRJ_DATA* m_projectData = nullptr; + + REPORTER* m_reporter; // current reporter for warnings/errors + PROGRESS_REPORTER* m_progressReporter; // optional; may be nullptr +}; + + +#endif // SCH_EASYEDAPRO_PLUGIN_H_ diff --git a/include/string_utils.h b/include/string_utils.h index f48c8303ba..87cd70e0ae 100644 --- a/include/string_utils.h +++ b/include/string_utils.h @@ -123,6 +123,11 @@ std::string EscapedUTF8( const wxString& aString ); */ wxString EscapeHTML( const wxString& aString ); +/** + * Return a new wxString unescaped from HTML format. + */ +wxString UnescapeHTML( const wxString& aString ); + /** * Read one line line from \a aFile. * diff --git a/include/wildcards_and_files_ext.h b/include/wildcards_and_files_ext.h index 9a50d11f1d..f99a297b90 100644 --- a/include/wildcards_and_files_ext.h +++ b/include/wildcards_and_files_ext.h @@ -218,6 +218,8 @@ extern wxString CsvFileWildcard(); extern wxString PcbFileWildcard(); extern wxString CadstarArchiveFilesWildcard(); extern wxString EagleFilesWildcard(); +extern wxString EasyEdaArchiveWildcard(); +extern wxString EasyEdaProFileWildcard(); extern wxString PdfFileWildcard(); extern wxString PSFileWildcard(); extern wxString MacrosFileWildcard(); diff --git a/kicad/import_project.cpp b/kicad/import_project.cpp index eff4355ada..0d194d32a5 100644 --- a/kicad/import_project.cpp +++ b/kicad/import_project.cpp @@ -134,4 +134,18 @@ void KICAD_MANAGER_FRAME::OnImportEagleFiles( wxCommandEvent& event ) { ImportNonKiCadProject( _( "Import Eagle Project Files" ), EagleFilesWildcard(), { "sch" }, { "brd" }, SCH_IO_MGR::SCH_EAGLE, IO_MGR::EAGLE ); +} + + +void KICAD_MANAGER_FRAME::OnImportEasyEdaFiles( wxCommandEvent& event ) +{ + ImportNonKiCadProject( _( "Import EasyEDA Std Backup" ), EasyEdaArchiveWildcard(), { "INPUT" }, + { "INPUT" }, SCH_IO_MGR::SCH_EASYEDA, IO_MGR::EASYEDA ); +} + + +void KICAD_MANAGER_FRAME::OnImportEasyEdaProFiles( wxCommandEvent& event ) +{ + ImportNonKiCadProject( _( "Import EasyEDA Pro Project" ), EasyEdaProFileWildcard(), { "INPUT" }, + { "INPUT" }, SCH_IO_MGR::SCH_EASYEDAPRO, IO_MGR::EASYEDAPRO ); } \ No newline at end of file diff --git a/kicad/kicad_id.h b/kicad/kicad_id.h index 147afb09c0..326a723aa2 100644 --- a/kicad/kicad_id.h +++ b/kicad/kicad_id.h @@ -68,8 +68,10 @@ enum id_kicad_frm { ID_SAVE_AND_ZIP_FILES, ID_READ_ZIP_ARCHIVE, ID_INIT_WATCHED_PATHS, - ID_IMPORT_EAGLE_PROJECT, ID_IMPORT_CADSTAR_ARCHIVE_PROJECT, + ID_IMPORT_EAGLE_PROJECT, + ID_IMPORT_EASYEDA_PROJECT, + ID_IMPORT_EASYEDAPRO_PROJECT, // Please, verify: the number of items in this list should be // less than ROOM_FOR_KICADMANAGER (see id.h) diff --git a/kicad/kicad_manager_frame.cpp b/kicad/kicad_manager_frame.cpp index 3135f9f185..f228324af3 100644 --- a/kicad/kicad_manager_frame.cpp +++ b/kicad/kicad_manager_frame.cpp @@ -97,6 +97,8 @@ BEGIN_EVENT_TABLE( KICAD_MANAGER_FRAME, EDA_BASE_FRAME ) EVT_MENU( ID_READ_ZIP_ARCHIVE, KICAD_MANAGER_FRAME::OnUnarchiveFiles ) EVT_MENU( ID_IMPORT_CADSTAR_ARCHIVE_PROJECT, KICAD_MANAGER_FRAME::OnImportCadstarArchiveFiles ) EVT_MENU( ID_IMPORT_EAGLE_PROJECT, KICAD_MANAGER_FRAME::OnImportEagleFiles ) + EVT_MENU( ID_IMPORT_EASYEDA_PROJECT, KICAD_MANAGER_FRAME::OnImportEasyEdaFiles ) + EVT_MENU( ID_IMPORT_EASYEDAPRO_PROJECT, KICAD_MANAGER_FRAME::OnImportEasyEdaProFiles ) // Range menu events EVT_MENU_RANGE( ID_LANGUAGE_CHOICE, ID_LANGUAGE_CHOICE_END, diff --git a/kicad/kicad_manager_frame.h b/kicad/kicad_manager_frame.h index e4fa13b085..3a64145cba 100644 --- a/kicad/kicad_manager_frame.h +++ b/kicad/kicad_manager_frame.h @@ -97,6 +97,16 @@ public: */ void OnImportEagleFiles( wxCommandEvent& event ); + /** + * Open dialog to import EasyEDA Std schematic and board files. + */ + void OnImportEasyEdaFiles( wxCommandEvent& event ); + + /** + * Open dialog to import EasyEDA Pro schematic and board files. + */ + void OnImportEasyEdaProFiles( wxCommandEvent& event ); + /** * Prints the current working directory name and the project name on the text panel. */ diff --git a/kicad/menubar.cpp b/kicad/menubar.cpp index fdb3a38822..f9666cf4c8 100644 --- a/kicad/menubar.cpp +++ b/kicad/menubar.cpp @@ -3,7 +3,7 @@ * * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2009 Wayne Stambaugh - * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors. * Copyright (C) 2019 CERN * * This program is free software; you can redistribute it and/or @@ -113,6 +113,15 @@ void KICAD_MANAGER_FRAME::doReCreateMenuBar() ID_IMPORT_EAGLE_PROJECT, BITMAPS::import_project ); + importMenu->Add( _( "EasyEDA (JLCEDA) Std Backup..." ), + _( "Import EasyEDA (JLCEDA) Standard schematic and board" ), + ID_IMPORT_EASYEDA_PROJECT, + BITMAPS::import_project ); + + importMenu->Add( _( "EasyEDA (JLCEDA) Pro Project..." ), + _( "Import EasyEDA (JLCEDA) Professional schematic and board" ), + ID_IMPORT_EASYEDAPRO_PROJECT, BITMAPS::import_project ); + fileMenu->Add( importMenu ); fileMenu->AppendSeparator(); diff --git a/libs/kimath/include/geometry/shape_poly_set.h b/libs/kimath/include/geometry/shape_poly_set.h index 8cd3dc6bbc..dbea0edce5 100644 --- a/libs/kimath/include/geometry/shape_poly_set.h +++ b/libs/kimath/include/geometry/shape_poly_set.h @@ -1002,6 +1002,12 @@ public: void BooleanXor( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ); + /** + * Extract all contours from this polygon set, then recreate polygons with holes. + * Essentially XOR'ing, but faster. Self-intersecting polygons are not supported. + */ + void RebuildHolesFromContours(); + /// define how inflate transform build inflated polygon enum CORNER_STRATEGY { diff --git a/libs/kimath/src/geometry/shape_poly_set.cpp b/libs/kimath/src/geometry/shape_poly_set.cpp index 9077fef1fe..23b1bd159c 100644 --- a/libs/kimath/src/geometry/shape_poly_set.cpp +++ b/libs/kimath/src/geometry/shape_poly_set.cpp @@ -624,6 +624,111 @@ void SHAPE_POLY_SET::ClearArcs() } +void SHAPE_POLY_SET::RebuildHolesFromContours() +{ + std::vector contours; + + for( const POLYGON& poly : m_polys ) + contours.insert( contours.end(), poly.begin(), poly.end() ); + + std::map> parentToChildren; + std::map> childToParents; + + for( SHAPE_LINE_CHAIN& contour : contours ) + contour.GenerateBBoxCache(); + + for( int i = 0; i < contours.size(); i++ ) + { + const SHAPE_LINE_CHAIN& outline = contours[i]; + + for( int j = 0; j < contours.size(); j++ ) + { + if( i == j ) + continue; + + const SHAPE_LINE_CHAIN& candidate = contours[j]; + const VECTOR2I& pt0 = candidate.CPoint( 0 ); + + if( outline.PointInside( pt0, 0, true ) ) + { + parentToChildren[i].emplace( j ); + childToParents[j].emplace( i ); + } + } + } + + std::set topLevelParents; + + for( int i = 0; i < contours.size(); i++ ) + { + if( childToParents[i].size() == 0 ) + { + topLevelParents.emplace( i ); + } + } + + SHAPE_POLY_SET result; + + std::function )> process; + + process = [&]( int myId, int parentOutlineId, std::vector path ) + { + std::set relParents = childToParents[myId]; + + for( int pathId : path ) + { + int erased = relParents.erase( pathId ); + wxASSERT( erased > 0 ); + } + + wxASSERT( relParents.size() == 0 ); + + int myOutline = -1; + + bool isOutline = path.size() % 2 == 0; + + if( isOutline ) + { + int outlineId = result.AddOutline( contours[myId] ); + myOutline = outlineId; + } + else + { + wxASSERT( parentOutlineId != -1 ); + int holeId = result.AddHole( contours[myId], parentOutlineId ); + } + + auto it = parentToChildren.find( myId ); + if( it != parentToChildren.end() ) + { + std::vector thisPath = path; + thisPath.emplace_back( myId ); + + std::set thisPathSet; + thisPathSet.insert( thisPath.begin(), thisPath.end() ); + + for( int childId : it->second ) + { + const std::set& childPathSet = childToParents[childId]; + + if( thisPathSet != childPathSet ) + continue; // Only interested in immediate children + + process( childId, myOutline, thisPath ); + } + } + }; + + for( int topParentId : topLevelParents ) + { + std::vector path; + process( topParentId, -1, path ); + } + + *this = result; +} + + void SHAPE_POLY_SET::booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode ) { diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 4eeae4d406..f66c924877 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -573,9 +573,11 @@ endif() add_subdirectory( plugins/pcad ) add_subdirectory( plugins/altium ) add_subdirectory( plugins/cadstar ) +add_subdirectory( plugins/easyeda ) +add_subdirectory( plugins/easyedapro ) add_subdirectory( plugins/fabmaster ) -set( PCBNEW_IO_LIBRARIES pcad2kicadpcb altium2pcbnew cadstar2pcbnew fabmaster CACHE INTERNAL "") +set( PCBNEW_IO_LIBRARIES pcad2kicadpcb altium2pcbnew cadstar2pcbnew easyeda easyedapro fabmaster CACHE INTERNAL "") # a very small program launcher for pcbnew_kiface add_executable( pcbnew WIN32 MACOSX_BUNDLE diff --git a/pcbnew/files.cpp b/pcbnew/files.cpp index 118085fdd2..1fd3121a54 100644 --- a/pcbnew/files.cpp +++ b/pcbnew/files.cpp @@ -1176,6 +1176,8 @@ bool PCB_EDIT_FRAME::importFile( const wxString& aFileName, int aFileType ) { case IO_MGR::CADSTAR_PCB_ARCHIVE: case IO_MGR::EAGLE: + case IO_MGR::EASYEDA: + case IO_MGR::EASYEDAPRO: return OpenProjectFiles( std::vector( 1, aFileName ), KICTL_NONKICAD_ONLY | KICTL_IMPORT_LIB ); diff --git a/pcbnew/io_mgr.cpp b/pcbnew/io_mgr.cpp index 0c264ffb5a..237dc047f8 100644 --- a/pcbnew/io_mgr.cpp +++ b/pcbnew/io_mgr.cpp @@ -41,6 +41,8 @@ #include #include #include +#include +#include #define FMT_UNIMPLEMENTED _( "Plugin \"%s\" does not implement the \"%s\" function." ) #define FMT_NOTFOUND _( "Plugin type \"%s\" is not found." ) @@ -231,6 +233,16 @@ static IO_MGR::REGISTER_PLUGIN registerEaglePlugin( wxT( "Eagle" ), []() -> PLUGIN* { return new EAGLE_PLUGIN; } ); +static IO_MGR::REGISTER_PLUGIN registerEasyEDAPlugin( + IO_MGR::EASYEDA, + wxT( "EasyEDA / JLCEDA Std" ), + []() -> PLUGIN* { return new EASYEDA_PLUGIN; }); + +static IO_MGR::REGISTER_PLUGIN registerEasyEDAProPlugin( + IO_MGR::EASYEDAPRO, + wxT( "EasyEDA / JLCEDA Pro" ), + []() -> PLUGIN* { return new EASYEDAPRO_PLUGIN; }); + static IO_MGR::REGISTER_PLUGIN registerFabmasterPlugin( IO_MGR::FABMASTER, wxT( "Fabmaster" ), diff --git a/pcbnew/io_mgr.h b/pcbnew/io_mgr.h index 011bb6e7e2..5a90c734cb 100644 --- a/pcbnew/io_mgr.h +++ b/pcbnew/io_mgr.h @@ -59,6 +59,8 @@ public: ALTIUM_DESIGNER, CADSTAR_PCB_ARCHIVE, EAGLE, + EASYEDA, + EASYEDAPRO, FABMASTER, GEDA_PCB, ///< Geda PCB file formats. PCAD, diff --git a/pcbnew/plugins/easyeda/CMakeLists.txt b/pcbnew/plugins/easyeda/CMakeLists.txt new file mode 100644 index 0000000000..18472adc66 --- /dev/null +++ b/pcbnew/plugins/easyeda/CMakeLists.txt @@ -0,0 +1,14 @@ + +# Sources for the pcbnew PLUGIN called EASYEDA_PLUGIN + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) + + +set( EASYEDA_SRCS + pcb_easyeda_plugin.cpp + pcb_easyeda_parser.cpp + ) + +add_library( easyeda STATIC ${EASYEDA_SRCS} ) + +target_link_libraries( easyeda pcbcommon ) diff --git a/pcbnew/plugins/easyeda/pcb_easyeda_parser.cpp b/pcbnew/plugins/easyeda/pcb_easyeda_parser.cpp new file mode 100644 index 0000000000..a512d57f71 --- /dev/null +++ b/pcbnew/plugins/easyeda/pcb_easyeda_parser.cpp @@ -0,0 +1,1093 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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_easyeda_parser.h" + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static const wxString DIRECT_MODEL_UUID_KEY = wxS( "JLC_3DModel" ); +static const wxString MODEL_SIZE_KEY = wxS( "JLC_3D_Size" ); + +static const int SHAPE_JOIN_DISTANCE = pcbIUScale.mmToIU( 1.5 ); +static const VECTOR2I HIDDEN_TEXT_SIZE( pcbIUScale.mmToIU( 0.5 ), pcbIUScale.mmToIU( 0.5 ) ); + + +PCB_EASYEDA_PARSER::PCB_EASYEDA_PARSER( PROGRESS_REPORTER* aProgressReporter ) +{ +} + +PCB_EASYEDA_PARSER::~PCB_EASYEDA_PARSER() +{ +} + +PCB_LAYER_ID PCB_EASYEDA_PARSER::LayerToKi( const wxString& aLayer ) +{ + int elayer = wxAtoi( aLayer ); + + switch( elayer ) + { + case 1: return F_Cu; + case 2: return B_Cu; + case 3: return F_SilkS; + case 4: return B_SilkS; + case 5: return F_Paste; + case 6: return B_Paste; + case 7: return F_Mask; + case 8: return B_Mask; + /*case 9: return UNDEFINED_LAYER;*/ // Ratsnest + case 10: return Edge_Cuts; + case 11: return Eco1_User; + case 12: return Dwgs_User; + case 13: return F_Fab; + case 14: return B_Fab; + case 15: return Eco2_User; + + case 19: return User_2; // 3D model + + case 21: return In1_Cu; + case 22: return In2_Cu; + case 23: return In3_Cu; + case 24: return In4_Cu; + case 25: return In5_Cu; + case 26: return In6_Cu; + case 27: return In7_Cu; + case 28: return In8_Cu; + case 29: return In9_Cu; + case 30: return In10_Cu; + case 31: return In11_Cu; + case 32: return In12_Cu; + case 33: return In13_Cu; + case 34: return In14_Cu; + case 35: return In15_Cu; + case 36: return In16_Cu; + case 37: return In17_Cu; + case 38: return In18_Cu; + case 39: return In19_Cu; + case 40: return In20_Cu; + case 41: return In21_Cu; + case 42: return In22_Cu; + case 43: return In23_Cu; + case 44: return In24_Cu; + case 45: return In25_Cu; + case 46: return In26_Cu; + case 47: return In27_Cu; + case 48: return In28_Cu; + case 49: return In29_Cu; + case 50: return In30_Cu; + + case 99: return User_3; + case 100: return User_4; + case 101: return User_5; + + default: break; + } + + return User_1; +} + + +static LIB_ID EasyEdaToKiCadLibID( const wxString& aLibName, const wxString& aLibReference ) +{ + wxString libReference = EscapeString( aLibReference, CTX_LIBID ); + + wxString key = !aLibName.empty() ? ( aLibName + ':' + libReference ) : libReference; + + LIB_ID libId; + libId.Parse( key, true ); + + return libId; +} + + +void PCB_EASYEDA_PARSER::ParseToBoardItemContainer( + BOARD_ITEM_CONTAINER* aContainer, BOARD* aParent, std::map paramMap, + std::map>& aFootprintMap, wxArrayString aShapes ) +{ + // TODO: make this path configurable? + const wxString easyedaModelDir = wxS( "EASYEDA_MODELS" ); + wxString kicadModelPrefix = wxS( "${KIPRJMOD}/" ) + easyedaModelDir + wxS( "/" ); + + BOARD* board = aParent ? aParent : dynamic_cast( aContainer ); + FOOTPRINT* footprint = dynamic_cast( aContainer ); + + auto getOrAddNetItem = [&]( const wxString& aNetName ) -> NETINFO_ITEM* + { + if( !board ) + return nullptr; + + if( aNetName.empty() ) + return nullptr; + + if( NETINFO_ITEM* item = board->FindNet( aNetName ) ) + { + return item; + } + else + { + item = new NETINFO_ITEM( board, aNetName, board->GetNetCount() + 1 ); + board->Add( item, ADD_MODE::APPEND ); + return item; + } + }; + + if( footprint ) + { + // TODO: library name + LIB_ID fpID = EasyEdaToKiCadLibID( wxEmptyString, paramMap[wxS( "package" )] ); + footprint->SetFPID( fpID ); + } + + for( wxString shape : aShapes ) + { + wxArrayString arr = wxSplit( shape, '~', '\0' ); + + wxString elType = arr[0]; + if( elType == wxS( "LIB" ) ) + { + shape.Replace( wxS( "#@$" ), "\n" ); + wxArrayString parts = wxSplit( shape, '\n', '\0' ); + + if( parts.size() < 1 ) + continue; + + wxArrayString paramsRoot = wxSplit( parts[0], '~', '\0' ); + + if( paramsRoot.size() < 4 ) + continue; + + VECTOR2D fpOrigin( Convert( paramsRoot[1] ), Convert( paramsRoot[2] ) ); + + wxString packageName = + wxString::Format( wxS( "Unknown_%s_%s" ), paramsRoot[1], paramsRoot[2] ); + + wxArrayString paramParts = wxSplit( paramsRoot[3], '`', '\0' ); + + EDA_ANGLE orientation; + if( !paramsRoot[4].IsEmpty() ) + orientation = EDA_ANGLE( Convert( paramsRoot[4] ), DEGREES_T ); // Already applied + + int layer = 1; + + if( !paramsRoot[7].IsEmpty() ) + layer = Convert( paramsRoot[7] ); + + std::map paramMap; + + for( int i = 1; i < paramParts.size(); i += 2 ) + { + wxString key = paramParts[i - 1]; + wxString value = paramParts[i]; + + if( key == wxS( "package" ) ) + packageName = value; + + paramMap[key] = value; + } + + parts.RemoveAt( 0 ); + + VECTOR2D pcbOrigin = m_relOrigin; + FOOTPRINT* fp = ParseFootprint( fpOrigin, orientation, layer, board, paramMap, + aFootprintMap, parts ); + + if( !fp ) + continue; + + m_relOrigin = pcbOrigin; + + fp->Move( RelPos( fpOrigin ) ); + + aContainer->Add( fp, ADD_MODE::APPEND ); + } + else if( elType == wxS( "TRACK" ) ) + { + double width = ConvertSize( arr[1] ); + PCB_LAYER_ID layer = LayerToKi( arr[2] ); + wxString netname = arr[3]; + wxArrayString data = wxSplit( arr[4], ' ', '\0' ); + + for( int i = 3; i < data.size(); i += 2 ) + { + VECTOR2D start, end; + start.x = RelPosX( data[i - 3] ); + start.y = RelPosY( data[i - 2] ); + end.x = RelPosX( data[i - 1] ); + end.y = RelPosY( data[i] ); + + if( !footprint && IsCopperLayer( layer ) ) + { + std::unique_ptr track = std::make_unique( aContainer ); + + track->SetLayer( layer ); + track->SetWidth( width ); + track->SetStart( start ); + track->SetEnd( end ); + track->SetNet( getOrAddNetItem( netname ) ); + + aContainer->Add( track.release(), ADD_MODE::APPEND ); + } + else + { + std::unique_ptr seg = + std::make_unique( aContainer, SHAPE_T::SEGMENT ); + + seg->SetLayer( layer ); + seg->SetWidth( width ); + seg->SetStart( start ); + seg->SetEnd( end ); + + aContainer->Add( seg.release(), ADD_MODE::APPEND ); + } + } + } + else if( elType == wxS( "CIRCLE" ) ) + { + std::unique_ptr shape = + std::make_unique( aContainer, SHAPE_T::CIRCLE ); + + double width = ConvertSize( arr[4] ); + shape->SetWidth( width ); + + PCB_LAYER_ID layer = LayerToKi( arr[5] ); + shape->SetLayer( layer ); + + VECTOR2D center; + center.x = RelPosX( arr[1] ); + center.y = RelPosY( arr[2] ); + + double radius = ConvertSize( arr[3] ); + + shape->SetCenter( center ); + shape->SetEnd( center + VECTOR2I( radius, 0 ) ); + + aContainer->Add( shape.release(), ADD_MODE::APPEND ); + } + else if( elType == wxS( "RECT" ) ) + { + std::unique_ptr shape = + std::make_unique( aContainer, SHAPE_T::RECTANGLE ); + + double width = ConvertSize( arr[8] ); + shape->SetWidth( width ); + + PCB_LAYER_ID layer = LayerToKi( arr[5] ); + shape->SetLayer( layer ); + + bool filled = arr[9] != wxS( "none" ); + shape->SetFilled( filled ); + + VECTOR2D start; + start.x = RelPosX( arr[1] ); + start.y = RelPosY( arr[2] ); + + VECTOR2D size; + size.x = ConvertSize( arr[3] ); + size.y = ConvertSize( arr[4] ); + + shape->SetStart( start ); + shape->SetEnd( start + size ); + + aContainer->Add( shape.release(), ADD_MODE::APPEND ); + } + else if( elType == wxS( "ARC" ) ) + { + std::unique_ptr shape = + std::make_unique( aContainer, SHAPE_T::ARC ); + + double width = ConvertSize( arr[1] ); + shape->SetWidth( width ); + + PCB_LAYER_ID layer = LayerToKi( arr[2] ); + shape->SetLayer( layer ); + + if( IsCopperLayer( layer ) ) + shape->SetNet( getOrAddNetItem( arr[3] ) ); + + VECTOR2D start, end; + VECTOR2D rad( 10, 10 ); + bool isFar = false; + bool cw = false; + + int pos = 0; + wxString data = arr[4]; + auto readNumber = [&]( wxString& aOut ) + { + wxUniChar ch = data[pos]; + + while( ch == ' ' || ch == ',' ) + ch = data[++pos]; + + while( isdigit( ch ) || ch == '.' || ch == '-' ) + { + aOut += ch; + pos++; + + if( pos == data.size() ) + break; + + ch = data[pos]; + } + }; + + do + { + wxUniChar sym = data[pos++]; + + if( sym == 'M' ) + { + wxString xStr, yStr; + readNumber( xStr ); + readNumber( yStr ); + + start = VECTOR2D( Convert( xStr ), Convert( yStr ) ); + } + else if( sym == 'A' ) + { + wxString radX, radY, unknown, farFlag, cwFlag, endX, endY; + readNumber( radX ); + readNumber( radY ); + readNumber( unknown ); + readNumber( farFlag ); + readNumber( cwFlag ); + readNumber( endX ); + readNumber( endY ); + + isFar = farFlag == wxS( "1" ); + cw = cwFlag == wxS( "1" ); + rad = VECTOR2D( Convert( radX ), Convert( radY ) ); + end = VECTOR2D( Convert( endX ), Convert( endY ) ); + } + } while( pos < data.size() ); + + VECTOR2D delta = end - start; + + double d = delta.EuclideanNorm(); + double h = sqrt( std::max( 0.0, rad.x * rad.x - d * d / 4 ) ); + + //( !far && cw ) => h + //( far && cw ) => -h + //( !far && !cw ) => -h + //( far && !cw ) => h + VECTOR2D arcCenter = + start + delta / 2 + delta.Perpendicular().Resize( ( isFar ^ cw ) ? h : -h ); + + if( !cw ) + std::swap( start, end ); + + shape->SetStart( RelPos( start ) ); + shape->SetEnd( RelPos( end ) ); + shape->SetCenter( RelPos( arcCenter ) ); + + aContainer->Add( shape.release(), ADD_MODE::APPEND ); + } + else if( elType == wxS( "SOLIDREGION" ) ) + { + wxString layer = arr[1]; + + SHAPE_POLY_SET polySet = ParsePolygons( arr[3].Trim(), pcbIUScale.mmToIU( 0.01 ) ); + + if( layer == wxS( "11" ) ) // Multi-layer (board cutout) + { + for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() ) + { + std::unique_ptr shape = + std::make_unique( aContainer, SHAPE_T::POLY ); + + shape->SetLayer( Edge_Cuts ); + shape->SetFilled( false ); + shape->SetWidth( pcbIUScale.mmToIU( 0.1 ) ); + shape->SetPolyShape( poly ); + + aContainer->Add( shape.release(), ADD_MODE::APPEND ); + } + } + else + { + std::unique_ptr zone = std::make_unique( aContainer ); + + PCB_LAYER_ID klayer = LayerToKi( layer ); + zone->SetLayer( klayer ); + + if( IsCopperLayer( klayer ) ) + zone->SetNet( getOrAddNetItem( arr[2] ) ); + + for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() ) + zone->Outline()->AddPolygon( poly ); + + if( arr[4].Lower() == wxS( "cutout" ) ) + { + zone->SetIsRuleArea( true ); + zone->SetDoNotAllowCopperPour( true ); + zone->SetDoNotAllowTracks( false ); + zone->SetDoNotAllowVias( false ); + zone->SetDoNotAllowPads( false ); + zone->SetDoNotAllowFootprints( false ); + } + else + { // solid + zone->SetFilledPolysList( klayer, polySet ); + zone->SetPadConnection( ZONE_CONNECTION::FULL ); + zone->SetIsFilled( true ); + zone->SetNeedRefill( false ); + } + + zone->SetMinThickness( 0 ); + zone->SetLocalClearance( 0 ); + zone->SetAssignedPriority( 100 ); + + aContainer->Add( zone.release(), ADD_MODE::APPEND ); + } + } + else if( elType == wxS( "COPPERAREA" ) ) + { + std::unique_ptr zone = std::make_unique( aContainer ); + + PCB_LAYER_ID layer = LayerToKi( arr[2] ); + zone->SetLayer( layer ); + + wxString netname = arr[3]; + if( IsCopperLayer( layer ) ) + zone->SetNet( getOrAddNetItem( netname ) ); + + zone->SetLocalClearance( ConvertSize( arr[5] ) ); + zone->SetThermalReliefGap( zone->GetLocalClearance() ); + + wxString fillStyle = arr[5]; + if( fillStyle == wxS( "none" ) ) + { + // Do not fill? + } + + SHAPE_POLY_SET polySet = ParsePolygons( arr[4].Trim(), pcbIUScale.mmToIU( 0.01 ) ); + + for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() ) + zone->Outline()->AddPolygon( poly ); + + wxString thermal = arr[8]; + if( thermal == wxS( "direct" ) ) + zone->SetPadConnection( ZONE_CONNECTION::FULL ); + + wxString keepIsland = arr[9]; + if( keepIsland == wxS( "yes" ) ) + zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::NEVER ); + + wxString fillData = arr[10]; + SHAPE_POLY_SET fillPolySet; + try + { + for( const nlohmann::json& polyData : nlohmann::json::parse( fillData ) ) + { + for( const nlohmann::json& contourData : polyData ) + { + SHAPE_POLY_SET contourPolySet = ParsePolygons( contourData.get(), + pcbIUScale.mmToIU( 0.01 ) ); + + SHAPE_POLY_SET currentOutline( contourPolySet.COutline( 0 ) ); + + for( int i = 1; i < contourPolySet.OutlineCount(); i++ ) + currentOutline.AddHole( contourPolySet.COutline( i ) ); + + fillPolySet.Append( currentOutline ); + } + } + + fillPolySet.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + + zone->SetFilledPolysList( layer, fillPolySet ); + zone->SetIsFilled( true ); + zone->SetNeedRefill( false ); + } + catch( nlohmann::json::exception& e ) + { + } + + int fillOrder = wxAtoi( arr[13] ); + zone->SetAssignedPriority( 100 - fillOrder ); + + wxString improveFabrication = arr[17]; + if( improveFabrication == wxS( "none" ) ) + { + zone->SetMinThickness( 0 ); + } + else + { + // arr[1] is "stroke Width" per docs + int minThickness = + std::max( pcbIUScale.mmToIU( 0.03 ), int( ConvertSize( arr[1] ) ) ); + zone->SetMinThickness( minThickness ); + } + + zone->SetThermalReliefSpokeWidth( + std::max( int( ConvertSize( arr[18] ) ), zone->GetMinThickness() ) ); + + aContainer->Add( zone.release(), ADD_MODE::APPEND ); + } + else if( elType == wxS( "SVGNODE" ) ) + { + nlohmann::json nodeData = nlohmann::json::parse( arr[1] ); + + int nodeType = nodeData.at( "nodeType" ); + wxString layer = nodeData.at( "layerid" ); + PCB_LAYER_ID klayer = LayerToKi( layer ); + + if( nodeType == 1 ) + { + std::map attributes = nodeData.at( "attrs" ); + + if( layer == wxS( "19" ) ) // 3DModel + { + if( !footprint ) + continue; + + auto ec_eType = get_opt( attributes, "c_etype" ); + auto ec_rotation = get_opt( attributes, "c_rotation" ); + auto ec_origin = get_opt( attributes, "c_origin" ); + auto ec_width = get_opt( attributes, "c_width" ); + auto ec_height = get_opt( attributes, "c_height" ); + auto ez = get_opt( attributes, "z" ); + auto etitle = get_opt( attributes, "title" ); + auto euuid = get_opt( attributes, "uuid" ); + auto etransform = get_opt( attributes, "transform" ); + + if( !ec_eType || *ec_eType != wxS( "outline3D" ) || !etitle ) + continue; + + wxString modelTitle = *etitle; + VECTOR3D kmodelOffset; + VECTOR3D kmodelRotation; + + if( euuid ) + { + PCB_FIELD field( footprint, footprint->GetFieldCount(), + DIRECT_MODEL_UUID_KEY ); + field.SetLayer( Cmts_User ); + field.SetVisible( false ); + field.SetText( *euuid ); + footprint->AddField( field ); + } + + /*if( etransform ) + { + PCB_FIELD field( footprint, footprint->GetFieldCount(), "3D Transform" ); + field.SetLayer( Cmts_User ); + field.SetVisible( false ); + field.SetText( *etransform ); + footprint->AddField( field ); + }*/ + + if( ec_width && ec_height ) + { + double fitXmm = pcbIUScale.IUTomm( ScaleSize( Convert( *ec_width ) ) ); + double fitYmm = pcbIUScale.IUTomm( ScaleSize( Convert( *ec_height ) ) ); + + double rounding = 0.001; + fitXmm = KiROUND( fitXmm / rounding ) * rounding; + fitYmm = KiROUND( fitYmm / rounding ) * rounding; + + PCB_FIELD field( footprint, footprint->GetFieldCount(), MODEL_SIZE_KEY ); + field.SetLayer( Cmts_User ); + field.SetVisible( false ); + field.SetText( wxString::FromCDouble( fitXmm ) + wxS( " " ) + + wxString::FromCDouble( fitYmm ) ); + footprint->AddField( field ); + } + + if( ec_origin ) + { + wxArrayString orParts = wxSplit( *ec_origin, ',', '\0' ); + + if( orParts.size() == 2 ) + { + VECTOR2D pos; + pos.x = Convert( orParts[0].Trim() ); + pos.y = Convert( orParts[1].Trim() ); + + VECTOR2D rel = RelPos( pos ); + kmodelOffset.x = -pcbIUScale.IUTomm( rel.x ); + kmodelOffset.y = -pcbIUScale.IUTomm( rel.y ); + + RotatePoint( &kmodelOffset.x, &kmodelOffset.y, + -footprint->GetOrientation() ); + } + } + + if( ez ) + { + kmodelOffset.z = pcbIUScale.IUTomm( ScaleSize( Convert( ez->Trim() ) ) ); + } + + if( ec_rotation ) + { + wxArrayString rotParts = wxSplit( *ec_rotation, ',', '\0' ); + + if( rotParts.size() == 3 ) + { + kmodelRotation.x = -Convert( rotParts[0].Trim() ); + kmodelRotation.y = -Convert( rotParts[1].Trim() ); + kmodelRotation.z = -Convert( rotParts[2].Trim() ) + + footprint->GetOrientationDegrees(); + } + } + + if( footprint->GetLayer() == B_Cu ) + { + kmodelRotation.z = 180 - kmodelRotation.z; + RotatePoint( &kmodelOffset.x, &kmodelOffset.y, ANGLE_180 ); + } + + FP_3DMODEL model; + model.m_Filename = kicadModelPrefix + + EscapeString( modelTitle, ESCAPE_CONTEXT::CTX_FILENAME ) + + wxS( ".step" ); + model.m_Offset = kmodelOffset; + model.m_Rotation = kmodelRotation; + footprint->Models().push_back( model ); + } + else + { + if( auto dataStr = get_opt( attributes, "d" ) ) + { + SHAPE_POLY_SET polySet = + ParsePolygons( dataStr->Trim(), + dataStr->size() < 8000 ? pcbIUScale.mmToIU( 0.005 ) + : pcbIUScale.mmToIU( 0.05 ) ); + + polySet.RebuildHolesFromContours(); + + std::unique_ptr group; + + if( polySet.OutlineCount() > 1 ) + group = std::make_unique( aContainer ); + + for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() ) + { + std::unique_ptr shape = + std::make_unique( aContainer, SHAPE_T::POLY ); + + shape->SetFilled( true ); + shape->SetPolyShape( poly ); + shape->SetLayer( klayer ); + shape->SetWidth( 0 ); + + if( group ) + group->AddItem( shape.get() ); + + aContainer->Add( shape.release(), ADD_MODE::APPEND ); + } + + if( group ) + aContainer->Add( group.release(), ADD_MODE::APPEND ); + } + } + } + else + { + THROW_IO_ERROR( wxString::Format( _( "Unknown SVGNODE nodeType %d" ), nodeType ) ); + } + } + else if( elType == wxS( "TEXT" ) ) + { + PCB_TEXT* text; + wxString textType = arr[1]; + bool add = false; + + if( textType == wxS( "P" ) ) + { + text = footprint->GetField( REFERENCE_FIELD ); + } + else if( textType == wxS( "N" ) ) + { + text = footprint->GetField( VALUE_FIELD ); + } + else + { + text = new PCB_TEXT( aContainer ); + add = true; + } + + VECTOR2D start; + start.x = RelPosX( arr[2] ); + start.y = RelPosY( arr[3] ); + text->SetPosition( start ); + + double thickness = ConvertSize( arr[4] ); + text->SetTextThickness( thickness ); + + double rot = Convert( arr[5] ); + text->SetTextAngleDegrees( rot ); + + text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); + text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); + + PCB_LAYER_ID layer = LayerToKi( arr[7] ); + text->SetLayer( layer ); + + if( IsBackLayer( layer ) ) + text->SetMirrored( true ); + + double height = ConvertSize( arr[9] ) * 0.8; + text->SetTextSize( VECTOR2I( height, height ) ); + + text->SetText( arr[10] ); + + //arr[11] // Geometry data + + text->SetVisible( arr[12] != wxS( "none" ) ); + + wxString font = arr[14]; + if( !font.IsEmpty() ) + text->SetFont( KIFONT::FONT::GetFont( font ) ); + + if( add ) + aContainer->Add( text, ADD_MODE::APPEND ); + } + else if( elType == wxS( "VIA" ) ) + { + VECTOR2D center( RelPosX( arr[1] ), RelPosY( arr[2] ) ); + int kdia = ConvertSize( arr[3] ); + int kdrill = ConvertSize( arr[5] ) * 2; + + if( footprint ) + { + std::unique_ptr pad = std::make_unique( footprint ); + + pad->SetPosition( center ); + pad->SetLayerSet( PAD::PTHMask() ); + pad->SetAttribute( PAD_ATTRIB::PTH ); + pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetSize( VECTOR2I( kdia, kdia ) ); + pad->SetDrillShape( PAD_DRILL_SHAPE_CIRCLE ); + pad->SetDrillSize( VECTOR2I( kdrill, kdrill ) ); + + footprint->Add( pad.release(), ADD_MODE::APPEND ); + } + else + { + std::unique_ptr via = std::make_unique( aContainer ); + + via->SetPosition( center ); + + via->SetWidth( kdia ); + via->SetNet( getOrAddNetItem( arr[4] ) ); + via->SetDrill( kdrill ); + + aContainer->Add( via.release(), ADD_MODE::APPEND ); + } + } + else if( elType == wxS( "HOLE" ) ) + { + FOOTPRINT* padContainer; + + VECTOR2D center( RelPosX( arr[1] ), RelPosY( arr[2] ) ); + int kdia = ConvertSize( arr[3] ) * 2; + wxString holeUuid = arr[4]; + + if( footprint ) + { + padContainer = footprint; + } + else + { + std::unique_ptr newFootprint = + std::make_unique( dynamic_cast( aContainer ) ); + + wxString name = wxS( "Hole_" ) + holeUuid; + + newFootprint->SetFPID( LIB_ID( wxEmptyString, name ) ); + newFootprint->SetPosition( center ); + newFootprint->SetLocked( true ); + + newFootprint->Reference().SetText( name ); + newFootprint->Reference().SetVisible( false ); + newFootprint->Reference().SetTextSize( HIDDEN_TEXT_SIZE ); + newFootprint->Value().SetText( name ); + newFootprint->Value().SetVisible( false ); + newFootprint->Value().SetTextSize( HIDDEN_TEXT_SIZE ); + + padContainer = newFootprint.get(); + aContainer->Add( newFootprint.release(), ADD_MODE::APPEND ); + } + + std::unique_ptr pad = std::make_unique( padContainer ); + + pad->SetPosition( center ); + pad->SetLayerSet( PAD::UnplatedHoleMask() ); + pad->SetAttribute( PAD_ATTRIB::NPTH ); + pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetSize( VECTOR2I( kdia, kdia ) ); + pad->SetDrillShape( PAD_DRILL_SHAPE_CIRCLE ); + pad->SetDrillSize( VECTOR2I( kdia, kdia ) ); + + padContainer->Add( pad.release(), ADD_MODE::APPEND ); + } + else if( elType == wxS( "PAD" ) ) + { + FOOTPRINT* padContainer; + + VECTOR2D center( RelPosX( arr[2] ), RelPosY( arr[3] ) ); + VECTOR2D size( ConvertSize( arr[4] ), ConvertSize( arr[5] ) ); + wxString padUuid = arr[12]; + + if( footprint ) + { + padContainer = footprint; + } + else + { + std::unique_ptr newFootprint = + std::make_unique( dynamic_cast( aContainer ) ); + + wxString name = wxS( "Pad_" ) + padUuid; + + newFootprint->SetFPID( LIB_ID( wxEmptyString, name ) ); + newFootprint->SetPosition( center ); + newFootprint->SetLocked( true ); + + newFootprint->Reference().SetText( name ); + newFootprint->Reference().SetVisible( false ); + newFootprint->Reference().SetTextSize( HIDDEN_TEXT_SIZE ); + newFootprint->Value().SetText( name ); + newFootprint->Value().SetVisible( false ); + newFootprint->Value().SetTextSize( HIDDEN_TEXT_SIZE ); + + padContainer = newFootprint.get(); + aContainer->Add( newFootprint.release(), ADD_MODE::APPEND ); + } + + std::unique_ptr pad = std::make_unique( padContainer ); + + pad->SetNet( getOrAddNetItem( arr[7] ) ); + pad->SetNumber( arr[8] ); + pad->SetPosition( center ); + pad->SetSize( size ); + pad->SetOrientationDegrees( Convert( arr[11] ) ); + pad->SetThermalSpokeAngle( ANGLE_0 ); + + wxString elayer = arr[6]; + PCB_LAYER_ID klayer = LayerToKi( elayer ); + + bool plated = arr[15] == wxS( "Y" ); + + if( klayer == F_Cu ) + { + pad->SetLayer( F_Cu ); + pad->SetLayerSet( PAD::SMDMask() ); + pad->SetAttribute( PAD_ATTRIB::SMD ); + } + else if( klayer == B_Cu ) + { + pad->SetLayer( B_Cu ); + pad->SetLayerSet( FlipLayerMask( PAD::SMDMask() ) ); + pad->SetAttribute( PAD_ATTRIB::SMD ); + } + else if( elayer == wxS( "11" ) ) + { + pad->SetLayerSet( plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() ); + pad->SetAttribute( plated ? PAD_ATTRIB::PTH : PAD_ATTRIB::NPTH ); + } + else + { + pad->SetLayer( klayer ); + pad->SetLayerSet( LSET( 1, klayer ) ); + pad->SetAttribute( PAD_ATTRIB::SMD ); + } + + wxString padType = arr[1]; + if( padType == wxS( "ELLIPSE" ) ) + { + pad->SetShape( PAD_SHAPE::OVAL ); + } + else if( padType == wxS( "RECT" ) ) + { + pad->SetShape( PAD_SHAPE::RECTANGLE ); + } + else if( padType == wxS( "OVAL" ) ) + { + if( pad->GetSizeX() == pad->GetSizeY() ) + pad->SetShape( PAD_SHAPE::CIRCLE ); + else + pad->SetShape( PAD_SHAPE::OVAL ); + } + else if( padType == wxS( "POLYGON" ) ) + { + pad->SetShape( PAD_SHAPE::CUSTOM ); + pad->SetAnchorPadShape( PAD_SHAPE::CIRCLE ); + pad->SetSize( { 1, 1 } ); + + wxArrayString data = wxSplit( arr[10], ' ', '\0' ); + + SHAPE_LINE_CHAIN chain; + for( int i = 1; i < data.size(); i += 2 ) + { + VECTOR2D pt; + pt.x = RelPosX( data[i - 1] ); + pt.y = RelPosY( data[i] ); + chain.Append( pt ); + } + chain.SetClosed( true ); + + chain.Move( -center ); + pad->AddPrimitivePoly( chain, 0, true ); + } + + wxString holeDia = arr[9]; + if( !holeDia.IsEmpty() ) + { + double holeD = ConvertSize( holeDia ) * 2; + double holeL = 0; + + wxString holeLength = arr[13]; + if( !holeLength.IsEmpty() ) + holeL = ConvertSize( holeLength ); + + if( holeL > 0 ) + { + pad->SetDrillShape( PAD_DRILL_SHAPE_OBLONG ); + + if( size.x < size.y ) + pad->SetDrillSize( VECTOR2I( holeD, holeL ) ); + else + pad->SetDrillSize( VECTOR2I( holeL, holeD ) ); + } + else + { + pad->SetDrillShape( PAD_DRILL_SHAPE_CIRCLE ); + pad->SetDrillSize( VECTOR2I( holeD, holeD ) ); + } + } + + wxString pasteExp = arr[17]; + if( !pasteExp.IsEmpty() ) + { + double pasteExpansion = ConvertSize( pasteExp ); + pad->SetLocalSolderPasteMargin( pasteExpansion ); + } + + wxString maskExp = arr[18]; + if( !maskExp.IsEmpty() ) + { + double maskExpansion = ConvertSize( maskExp ); + pad->SetLocalSolderMaskMargin( maskExpansion ); + } + + padContainer->Add( pad.release(), ADD_MODE::APPEND ); + } + } +} + + +FOOTPRINT* PCB_EASYEDA_PARSER::ParseFootprint( + const VECTOR2D& aOrigin, const EDA_ANGLE& aOrientation, int aLayer, BOARD* aParent, + std::map aParams, + std::map>& aFootprintMap, wxArrayString aShapes ) +{ + std::unique_ptr footprint = std::make_unique( aParent ); + + if( aLayer == 2 ) // Bottom layer + { + footprint->SetLayer( B_Cu ); + footprint->SetOrientation( aOrientation - ANGLE_180 ); + } + else + { + footprint->SetLayer( F_Cu ); + footprint->SetOrientation( aOrientation ); + } + + footprint->Value().SetText( aParams[wxS( "package" )] ); + + m_relOrigin = aOrigin; + + ParseToBoardItemContainer( footprint.get(), aParent, aParams, aFootprintMap, aShapes ); + + // Heal board outlines + std::vector shapes; + std::vector> newShapes; + + for( BOARD_ITEM* item : footprint->GraphicalItems() ) + { + if( !item->IsOnLayer( Edge_Cuts ) ) + continue; + + if( item->Type() == PCB_SHAPE_T ) + shapes.push_back( static_cast( item ) ); + } + + ConnectBoardShapes( shapes, newShapes, SHAPE_JOIN_DISTANCE ); + + for( std::unique_ptr& ptr : newShapes ) + footprint->Add( ptr.release(), ADD_MODE::APPEND ); + + return footprint.release(); +} + + +void PCB_EASYEDA_PARSER::ParseBoard( BOARD* aBoard, const VECTOR2D& aOrigin, + std::map>& aFootprintMap, + wxArrayString aShapes ) +{ + m_relOrigin = aOrigin; + + ParseToBoardItemContainer( aBoard, nullptr, {}, aFootprintMap, aShapes ); + + // Heal board outlines + std::vector shapes; + std::vector> newShapes; + + for( BOARD_ITEM* item : aBoard->Drawings() ) + { + if( !item->IsOnLayer( Edge_Cuts ) ) + continue; + + if( item->Type() == PCB_SHAPE_T ) + shapes.push_back( static_cast( item ) ); + } + + ConnectBoardShapes( shapes, newShapes, SHAPE_JOIN_DISTANCE ); + + for( std::unique_ptr& ptr : newShapes ) + aBoard->Add( ptr.release(), ADD_MODE::APPEND ); +} diff --git a/pcbnew/plugins/easyeda/pcb_easyeda_parser.h b/pcbnew/plugins/easyeda/pcb_easyeda_parser.h new file mode 100644 index 0000000000..467e893363 --- /dev/null +++ b/pcbnew/plugins/easyeda/pcb_easyeda_parser.h @@ -0,0 +1,75 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef PCB_EASYEDA_PARSER_H_ +#define PCB_EASYEDA_PARSER_H_ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +class BOARD; +class PROGRESS_REPORTER; + +class PCB_EASYEDA_PARSER : public EASYEDA_PARSER_BASE +{ +public: + explicit PCB_EASYEDA_PARSER( PROGRESS_REPORTER* aProgressReporter ); + ~PCB_EASYEDA_PARSER(); + + PCB_LAYER_ID LayerToKi( const wxString& aLayer ); + + double ScaleSize( double aValue ) override + { // + return KiROUND( ( aValue * 254000 ) / 100 ) * 100; + } + + void ParseBoard( BOARD* aBoard, const VECTOR2D& aOrigin, + std::map>& aFootprintMap, + wxArrayString aShapes ); + + FOOTPRINT* ParseFootprint( const VECTOR2D& aOrigin, const EDA_ANGLE& aOrientation, int aLayer, + BOARD* aParent, std::map aParams, + std::map>& aFootprintMap, + wxArrayString aShapes ); + + void ParseToBoardItemContainer( BOARD_ITEM_CONTAINER* aContainer, BOARD* aParent, + std::map paramMap, + std::map>& aFootprintMap, + wxArrayString shapes ); +}; + + +#endif // PCB_EASYEDA_PARSER_H_ diff --git a/pcbnew/plugins/easyeda/pcb_easyeda_plugin.cpp b/pcbnew/plugins/easyeda/pcb_easyeda_plugin.cpp new file mode 100644 index 0000000000..4bf4f01af0 --- /dev/null +++ b/pcbnew/plugins/easyeda/pcb_easyeda_plugin.cpp @@ -0,0 +1,537 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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_easyeda_plugin.h" +#include "pcb_easyeda_parser.h" +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + + +EASYEDA_PLUGIN::EASYEDA_PLUGIN() +{ + m_board = nullptr; + m_props = nullptr; +} + + +EASYEDA_PLUGIN::~EASYEDA_PLUGIN() +{ +} + + +static bool FindBoardInStream( const wxString& aName, wxInputStream& aStream, nlohmann::json& aOut, + EASYEDA::DOCUMENT& aDoc ) +{ + if( aName.Lower().EndsWith( wxS( ".json" ) ) ) + { + wxStdInputStream sin( aStream ); + nlohmann::json js = nlohmann::json::parse( sin, nullptr, false ); + + if( js.is_discarded() ) + return false; + + EASYEDA::DOCUMENT doc = js.get(); + + if( doc.head.docType == EASYEDA::DOC_TYPE::PCB + || doc.head.docType == EASYEDA::DOC_TYPE::PCB_MODULE + || doc.head.docType == EASYEDA::DOC_TYPE::PCB_COMPONENT ) + { + aOut = js; + aDoc = doc; + return true; + } + } + else if( aName.Lower().EndsWith( wxS( ".zip" ) ) ) + { + std::shared_ptr entry; + wxZipInputStream zip( aStream ); + + if( !zip.IsOk() ) + return false; + + while( entry.reset( zip.GetNextEntry() ), entry.get() != NULL ) + { + wxString name = entry->GetName(); + + if( FindBoardInStream( name, zip, aOut, aDoc ) ) + return true; + } + } + + return false; +} + + +bool EASYEDA_PLUGIN::CanReadBoard( const wxString& aFileName ) const +{ + if( !PLUGIN::CanReadBoard( aFileName ) ) + return false; + + try + { + wxFFileInputStream in( aFileName ); + nlohmann::json js; + EASYEDA::DOCUMENT doc; + + return FindBoardInStream( aFileName, in, js, doc ); + } + catch( nlohmann::json::exception& ) + { + } + catch( std::exception& ) + { + } + + return false; +} + + +bool EASYEDA_PLUGIN::CanReadFootprint( const wxString& aFileName ) const +{ + return CanReadBoard( aFileName ); +} + + +bool EASYEDA_PLUGIN::CanReadFootprintLib( const wxString& aFileName ) const +{ + return CanReadBoard( aFileName ); +} + + +BOARD* EASYEDA_PLUGIN::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe, + const STRING_UTF8_MAP* aProperties, PROJECT* aProject, + PROGRESS_REPORTER* aProgressReporter ) +{ + m_loadedFootprints.clear(); + + m_props = aProperties; + m_board = aAppendToMe ? aAppendToMe : new BOARD(); + + // Give the filename to the board if it's new + if( !aAppendToMe ) + m_board->SetFileName( aFileName ); + + if( aProgressReporter ) + { + aProgressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) ); + + if( !aProgressReporter->KeepRefreshing() ) + THROW_IO_ERROR( _( "Open cancelled by user." ) ); + } + + PCB_EASYEDA_PARSER parser( nullptr ); + + try + { + wxFFileInputStream in( aFileName ); + nlohmann::json js; + EASYEDA::DOCUMENT doc; + + if( !FindBoardInStream( aFileName, in, js, doc ) ) + { + THROW_IO_ERROR( + wxString::Format( _( "Unable to find a valid board in '%s'" ), aFileName ) ); + } + + EASYEDA::DOCUMENT_PCB pcbDoc = js.get(); + + const int innerStart = 21; + const int innerEnd = 52; + + int maxLayer = innerStart; + std::map layerNames; + + for( const wxString& layerLine : pcbDoc.layers ) + { + wxArrayString parts = wxSplit( layerLine, '~', '\0' ); + int layerId = wxAtoi( parts[0] ); + wxString layerName = parts[1]; + wxString layerColor = parts[2]; + wxString visible = parts[3]; + wxString active = parts[4]; + bool enabled = parts[5] != wxS( "false" ); + + if( layerId >= innerStart && layerId <= innerEnd && enabled ) + maxLayer = layerId + 1; + + layerNames[parser.LayerToKi( parts[0] )] = layerName; + } + + m_board->SetCopperLayerCount( 2 + maxLayer - innerStart ); + + for( auto& [klayer, name] : layerNames ) + m_board->SetLayerName( klayer, name ); + + BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); + std::shared_ptr defNetclass = bds.m_NetSettings->m_DefaultNetClass; + + if( pcbDoc.DRCRULE ) + { + std::map& rules = *pcbDoc.DRCRULE; + + if( auto defRules = get_opt( rules, "Default" ) ) + { + wxString key; + + key = wxS( "trackWidth" ); + if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() ) + { + double val = parser.ScaleSize( defRules->at( key ) ); + defNetclass->SetTrackWidth( val ); + } + + key = wxS( "clearance" ); + if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() ) + { + double val = parser.ScaleSize( defRules->at( key ) ); + defNetclass->SetClearance( val ); + } + + key = wxS( "viaHoleD" ); + if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() ) + { + double val = parser.ScaleSize( defRules->at( key ) ); + + defNetclass->SetViaDrill( val ); + } + + key = wxS( "viaHoleDiameter" ); // Yes, this is via diameter, not drill diameter + if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() ) + { + double val = parser.ScaleSize( defRules->at( key ) ); + defNetclass->SetViaDiameter( val ); + } + } + } + + VECTOR2D origin( doc.head.x, doc.head.y ); + parser.ParseBoard( m_board, origin, m_loadedFootprints, doc.shape ); + + // Center the board + BOX2I outlineBbox = m_board->ComputeBoundingBox( true ); + PAGE_INFO pageInfo = m_board->GetPageSettings(); + + VECTOR2D pageCenter( pcbIUScale.MilsToIU( pageInfo.GetWidthMils() / 2 ), + pcbIUScale.MilsToIU( pageInfo.GetHeightMils() / 2 ) ); + + VECTOR2D offset = pageCenter - outlineBbox.GetCenter(); + + int alignGrid = pcbIUScale.mmToIU( 10 ); + offset.x = KiROUND( offset.x / alignGrid ) * alignGrid; + offset.y = KiROUND( offset.y / alignGrid ) * alignGrid; + + m_board->Move( offset ); + bds.SetAuxOrigin( offset ); + + return m_board; + } + catch( nlohmann::json::exception& e ) + { + THROW_IO_ERROR( + wxString::Format( _( "Error loading board '%s': %s" ), aFileName, e.what() ) ); + } + catch( std::exception& e ) + { + THROW_IO_ERROR( + wxString::Format( _( "Error loading board '%s': %s" ), aFileName, e.what() ) ); + } + + return m_board; +} + + +long long EASYEDA_PLUGIN::GetLibraryTimestamp( const wxString& aLibraryPath ) const +{ + return 0; +} + + +void EASYEDA_PLUGIN::FootprintEnumerate( wxArrayString& aFootprintNames, + const wxString& aLibraryPath, bool aBestEfforts, + const STRING_UTF8_MAP* aProperties ) +{ + try + { + wxFFileInputStream in( aLibraryPath ); + nlohmann::json js; + EASYEDA::DOCUMENT doc; + + if( !FindBoardInStream( aLibraryPath, in, js, doc ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Unable to find valid footprints in '%s'" ), + aLibraryPath ) ); + } + + if( doc.head.docType == EASYEDA::DOC_TYPE::PCB + || doc.head.docType == EASYEDA::DOC_TYPE::PCB_MODULE ) + { + for( wxString shap : doc.shape ) + { + shap.Replace( wxS( "#@$" ), "\n" ); + wxArrayString parts = wxSplit( shap, '\n', '\0' ); + + if( parts.size() < 1 ) + continue; + + wxArrayString paramsRoot = wxSplit( parts[0], '~', '\0' ); + + if( paramsRoot.size() < 1 ) + continue; + + wxString rootType = paramsRoot[0]; + + if( rootType == wxS( "LIB" ) ) + { + if( paramsRoot.size() < 4 ) + continue; + + wxString packageName = wxString::Format( wxS( "Unknown_%s_%s" ), paramsRoot[1], + paramsRoot[2] ); + + wxArrayString paramParts = wxSplit( paramsRoot[3], '`', '\0' ); + + std::map paramMap; + + for( int i = 1; i < paramParts.size(); i += 2 ) + { + wxString key = paramParts[i - 1]; + wxString value = paramParts[i]; + + if( key == wxS( "package" ) ) + packageName = value; + + paramMap[key] = value; + } + + aFootprintNames.Add( packageName ); + } + } + } + else if( doc.head.docType == EASYEDA::DOC_TYPE::PCB_COMPONENT ) + { + EASYEDA::DOCUMENT_PCB pcbDoc = js.get(); + + wxString packageName = wxString::Format( wxS( "Unknown_%s" ), + pcbDoc.uuid.value_or( wxS( "Unknown" ) ) ); + + if( pcbDoc.c_para ) + { + packageName = get_def( *pcbDoc.c_para, wxS( "package" ), packageName ); + } + + aFootprintNames.Add( packageName ); + } + } + catch( nlohmann::json::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error enumerating footprints in library '%s': %s" ), + aLibraryPath, e.what() ) ); + } + catch( std::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error enumerating footprints in library '%s': %s" ), + aLibraryPath, e.what() ) ); + } +} + + +FOOTPRINT* EASYEDA_PLUGIN::FootprintLoad( const wxString& aLibraryPath, + const wxString& aFootprintName, bool aKeepUUID, + const STRING_UTF8_MAP* aProperties ) +{ + PCB_EASYEDA_PARSER parser( nullptr ); + + m_loadedFootprints.clear(); + + try + { + wxFFileInputStream in( aLibraryPath ); + nlohmann::json js; + EASYEDA::DOCUMENT doc; + + if( !FindBoardInStream( aLibraryPath, in, js, doc ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Unable to find valid footprints in '%s'" ), + aLibraryPath ) ); + } + + if( doc.head.docType == EASYEDA::DOC_TYPE::PCB + || doc.head.docType == EASYEDA::DOC_TYPE::PCB_MODULE ) + { + for( wxString shap : doc.shape ) + { + if( !shap.Contains( wxS( "LIB" ) ) ) + continue; + + shap.Replace( wxS( "#@$" ), "\n" ); + wxArrayString parts = wxSplit( shap, '\n', '\0' ); + + if( parts.size() < 1 ) + continue; + + wxArrayString paramsRoot = wxSplit( parts[0], '~', '\0' ); + + if( paramsRoot.size() < 1 ) + continue; + + wxString rootType = paramsRoot[0]; + + if( rootType == wxS( "LIB" ) ) + { + if( paramsRoot.size() < 4 ) + continue; + + VECTOR2D origin( parser.Convert( paramsRoot[1] ), + parser.Convert( paramsRoot[2] ) ); + + wxString packageName = wxString::Format( wxS( "Unknown_%s_%s" ), paramsRoot[1], + paramsRoot[2] ); + + wxArrayString paramParts = wxSplit( paramsRoot[3], '`', '\0' ); + + std::map paramMap; + + for( int i = 1; i < paramParts.size(); i += 2 ) + { + wxString key = paramParts[i - 1]; + wxString value = paramParts[i]; + + if( key == wxS( "package" ) ) + packageName = value; + + paramMap[key] = value; + } + + EDA_ANGLE orientation; + if( !paramsRoot[4].IsEmpty() ) + orientation = EDA_ANGLE( parser.Convert( paramsRoot[4] ), DEGREES_T ); + + int layer = 1; + + if( !paramsRoot[7].IsEmpty() ) + layer = parser.Convert( paramsRoot[7] ); + + if( packageName == aFootprintName ) + { + parts.RemoveAt( 0 ); + + FOOTPRINT* footprint = + parser.ParseFootprint( origin, orientation, layer, nullptr, + paramMap, m_loadedFootprints, parts ); + + if( !footprint ) + return nullptr; + + footprint->Reference().SetPosition( VECTOR2I() ); + footprint->Reference().SetTextAngle( ANGLE_0 ); + footprint->Reference().SetVisible( true ); + + footprint->Value().SetPosition( VECTOR2I() ); + footprint->Value().SetTextAngle( ANGLE_0 ); + footprint->Value().SetVisible( true ); + + footprint->AutoPositionFields(); + + return footprint; + } + } + } + } + else if( doc.head.docType == EASYEDA::DOC_TYPE::PCB_COMPONENT ) + { + EASYEDA::DOCUMENT_PCB pcbDoc = js.get(); + + wxString packageName = wxString::Format( wxS( "Unknown_%s" ), + pcbDoc.uuid.value_or( wxS( "Unknown" ) ) ); + + if( pcbDoc.c_para ) + { + packageName = get_def( *pcbDoc.c_para, wxS( "package" ), packageName ); + + if( packageName != aFootprintName ) + return nullptr; + + VECTOR2D origin( doc.head.x, doc.head.y ); + + FOOTPRINT* footprint = + parser.ParseFootprint( origin, ANGLE_0, F_Cu, nullptr, *pcbDoc.c_para, + m_loadedFootprints, doc.shape ); + + if( !footprint ) + return nullptr; + + footprint->Reference().SetPosition( VECTOR2I() ); + footprint->Reference().SetTextAngle( ANGLE_0 ); + footprint->Reference().SetVisible( true ); + + footprint->Value().SetPosition( VECTOR2I() ); + footprint->Value().SetTextAngle( ANGLE_0 ); + footprint->Value().SetVisible( true ); + + footprint->AutoPositionFields(); + + return footprint; + } + } + } + catch( nlohmann::json::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error reading footprint '%s' from library '%s': %s" ), + aFootprintName, aLibraryPath, e.what() ) ); + } + catch( std::exception& e ) + { + THROW_IO_ERROR( wxString::Format( _( "Error reading footprint '%s' from library '%s': %s" ), + aFootprintName, aLibraryPath, e.what() ) ); + } + + return nullptr; +} + + +std::vector EASYEDA_PLUGIN::GetImportedCachedLibraryFootprints() +{ + std::vector result; + + for( auto& [fpUuid, footprint] : m_loadedFootprints ) + { + result.push_back( static_cast( footprint->Clone() ) ); + } + + return result; +} \ No newline at end of file diff --git a/pcbnew/plugins/easyeda/pcb_easyeda_plugin.h b/pcbnew/plugins/easyeda/pcb_easyeda_plugin.h new file mode 100644 index 0000000000..ae1da96a33 --- /dev/null +++ b/pcbnew/plugins/easyeda/pcb_easyeda_plugin.h @@ -0,0 +1,84 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef PCB_EASYEDA_PLUGIN_H_ +#define PCB_EASYEDA_PLUGIN_H_ + +#include +#include + + +class EASYEDA_PLUGIN : public PLUGIN +{ +public: + const wxString PluginName() const override + { + return wxS( "EasyEDA (JLCEDA) Standard" ); + } + + PLUGIN_FILE_DESC GetBoardFileDesc() const override + { + return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Std files" ), { "json", "zip" } ); + } + + PLUGIN_FILE_DESC GetFootprintFileDesc() const override { return GetBoardFileDesc(); } + + PLUGIN_FILE_DESC GetFootprintLibDesc() const override { return GetBoardFileDesc(); } + + bool CanReadBoard( const wxString& aFileName ) const override; + + bool CanReadFootprint( const wxString& aFileName ) const override; + + bool CanReadFootprintLib( const wxString& aFileName ) const override; + + BOARD* LoadBoard( const wxString& aFileName, BOARD* aAppendToMe, + const STRING_UTF8_MAP* aProperties = nullptr, PROJECT* aProject = nullptr, + PROGRESS_REPORTER* aProgressReporter = nullptr ) override; + + long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override; + + void FootprintEnumerate( wxArrayString& aFootprintNames, const wxString& aLibraryPath, + bool aBestEfforts, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + std::vector GetImportedCachedLibraryFootprints(); + + FOOTPRINT* FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, + bool aKeepUUID = false, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + bool IsFootprintLibWritable( const wxString& aLibraryPath ) override { return false; } + + EASYEDA_PLUGIN(); + ~EASYEDA_PLUGIN(); + +private: + const STRING_UTF8_MAP* m_props; + BOARD* m_board; + + std::map> m_loadedFootprints; +}; + + +#endif // PCB_EASYEDA_PLUGIN_H_ diff --git a/pcbnew/plugins/easyedapro/CMakeLists.txt b/pcbnew/plugins/easyedapro/CMakeLists.txt new file mode 100644 index 0000000000..de027fe8d3 --- /dev/null +++ b/pcbnew/plugins/easyedapro/CMakeLists.txt @@ -0,0 +1,14 @@ + +# Sources for the pcbnew PLUGIN called EASYEDAPRO_PLUGIN + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) + + +set( EASYEDAPRO_SRCS + pcb_easyedapro_plugin.cpp + pcb_easyedapro_parser.cpp + ) + +add_library( easyedapro STATIC ${EASYEDAPRO_SRCS} ) + +target_link_libraries( easyedapro pcbcommon ) diff --git a/pcbnew/plugins/easyedapro/pcb_easyedapro_parser.cpp b/pcbnew/plugins/easyedapro/pcb_easyedapro_parser.cpp new file mode 100644 index 0000000000..256860bde2 --- /dev/null +++ b/pcbnew/plugins/easyedapro/pcb_easyedapro_parser.cpp @@ -0,0 +1,1713 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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_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 +#include +#include +#include +#include +#include +#include +#include + + +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_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_EASYEDAPRO_PARSER::PCB_EASYEDAPRO_PARSER( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter ) +{ + m_board = aBoard; +} + + +PCB_EASYEDAPRO_PARSER::~PCB_EASYEDAPRO_PARSER() +{ +} + + +PCB_LAYER_ID PCB_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_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() ) + { + 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() ) + { + 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() ) + { + 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> +PCB_EASYEDAPRO_PARSER::ParsePoly( BOARD_ITEM_CONTAINER* aContainer, nlohmann::json polyData, + bool aClosed, bool aInFill ) const +{ + std::vector> 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 shape = + std::make_unique( 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 angle = polyData.at( ++i ); + double cr = ( i + 1 ) < polyData.size() ? polyData.at( ++i ).get() : 0; + + if( cr == 0 ) + { + std::unique_ptr shape = + std::make_unique( 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 shape = + std::make_unique( 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 shape = + std::make_unique( 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() / ( aInFill ? 10 : 1 ); + end.x = ( polyData.at( ++i ) ); + end.y = ( polyData.at( ++i ) ); + + std::unique_ptr shape = + std::make_unique( 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 shape = + std::make_unique( 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 shape = + std::make_unique( 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_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 angle = polyData.at( ++i ); + double cr = ( i + 1 ) < polyData.size() ? polyData.at( ++i ).get() : 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(); + + 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 ctrlPoints = { ScalePos( prevPt ), ScalePos( pt1 ), + ScalePos( pt2 ), ScalePos( pt3 ) }; + BEZIER_POLY converter( ctrlPoints ); + + std::vector 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 PCB_EASYEDAPRO_PARSER::createPAD( FOOTPRINT* aFootprint, + const nlohmann::json& line ) +{ + wxString uuid = line.at( 1 ); + int unk = line.at( 2 ).get(); + wxString netname = line.at( 3 ); + int layer = line.at( 4 ).get(); + 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 = std::make_unique( 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() : 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> 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_EASYEDAPRO_PARSER::ParseFootprint( const nlohmann::json& aProject, + const wxString& aFpUuid, + const std::vector& aLines ) +{ + std::unique_ptr footprintPtr = std::make_unique( 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 ); + int unk = line.at( 2 ).get(); + wxString netname = line.at( 3 ); + int layer = line.at( 4 ).get(); + PCB_LAYER_ID klayer = LayerToKi( layer ); + + if( type == wxS( "POLY" ) ) + { + double thickness = ( line.at( 5 ) ); + nlohmann::json polyData = line.at( 6 ); + + std::vector> 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 = createPAD( footprint, line ); + + footprint->Add( pad.release(), ADD_MODE::APPEND ); + } + else if( type == wxS( "FILL" ) ) + { + int layer = line.at( 4 ).get(); + 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 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 group; + + if( polySet.OutlineCount() > 1 ) + group = std::make_unique( footprint ); + + BOX2I polyBBox = polySet.BBox(); + + for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() ) + { + std::unique_ptr shape = + std::make_unique( 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 ); + int unk = line.at( 2 ).get(); + + int layer = line.at( 3 ).get(); + PCB_LAYER_ID klayer = LayerToKi( layer ); + + double width = line.at( 4 ); + std::set flags = line.at( 5 ); + nlohmann::json polyDataList = line.at( 6 ); + + for( nlohmann::json& polyData : polyDataList ) + { + SHAPE_POLY_SET polySet; + + std::vector> 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 = std::make_unique( 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 devicesMap = aProject.at( "devices" ); + std::map 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 shapes; + std::vector> newShapes; + + for( BOARD_ITEM* item : footprint->GraphicalItems() ) + { + if( !item->IsOnLayer( Edge_Cuts ) ) + continue; + + if( item->Type() == PCB_SHAPE_T ) + shapes.push_back( static_cast( item ) ); + } + + ConnectBoardShapes( shapes, newShapes, SHAPE_JOIN_DISTANCE ); + + for( std::unique_ptr& ptr : newShapes ) + footprint->Add( ptr.release(), ADD_MODE::APPEND ); + + return footprintPtr.release(); +} + + +void PCB_EASYEDAPRO_PARSER::ParseBoard( + BOARD* aBoard, const nlohmann::json& aProject, + std::map>& aFootprintMap, + const std::map& aBlobMap, + const std::multimap& aPouredMap, + const std::vector& aLines, const wxString& aFpLibName ) +{ + std::map> componentLines; + std::map> 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& 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 ); + int unk = line.at( 2 ).get(); + 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 via = std::make_unique( 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(); + 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 track = std::make_unique( 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(); + 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 arc = std::make_unique( 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(); + 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 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 = std::make_unique( 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> 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(); + 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(); + nlohmann::json polyDataList = line.at( 8 ); + + std::unique_ptr zone = std::make_unique( 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, SHAPE_POLY_SET::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(); + 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 = std::make_unique( 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 ); + int unk = line.at( 2 ).get(); + + int layer = line.at( 3 ).get(); + PCB_LAYER_ID klayer = LayerToKi( layer ); + + double width = line.at( 4 ); + std::set 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 = std::make_unique( 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 = std::make_unique( aBoard ); + std::unique_ptr 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(); + 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 ); + int unk = line.at( 2 ).get(); + + int layer = line.at( 3 ).get(); + 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 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 group; + + if( polySet.OutlineCount() > 1 ) + group = std::make_unique( aBoard ); + + BOX2I polyBBox = polySet.BBox(); + + for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() ) + { + std::unique_ptr shape = + std::make_unique( 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( "STRING" ) ) + { + wxString uuid = line.at( 1 ); + int unk = line.at( 2 ); + + int layer = line.at( 3 ).get(); + 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 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; + } + } + + nlohmann::json compAttrs = aProject.at( "devices" ).at( deviceId ).at( "attributes" ); + + wxString fpId; + + if( !fpIdOverride.IsEmpty() ) + fpId = fpIdOverride; + else + fpId = compAttrs.at( "Footprint" ).get(); + + auto it = aFootprintMap.find( fpId ); + if( it == aFootprintMap.end() ) + { + wxLogError( "Footprint of '%s' with uuid '%s' not found.", fpDesignator, fpId ); + continue; + } + + std::unique_ptr& footprintOrig = it->second; + std::unique_ptr footprint( static_cast( footprintOrig->Clone() ) ); + + wxString modelUuid, modelTitle, modelTransform; + + if( auto val = get_opt( localCompAttribs, "3D Model" ) ) + modelUuid = *val; + else + modelUuid = compAttrs.value( "3D Model", "" ); + + if( auto val = get_opt( localCompAttribs, "3D Model Title" ) ) + modelTitle = val->Trim(); + else + modelTitle = compAttrs.value( "3D Model Title", modelUuid ).Trim(); + + if( auto val = get_opt( localCompAttribs, "3D Model Transform" ) ) + modelTransform = *val; + else + modelTransform = compAttrs.value( "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 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 shapes; + std::vector> newShapes; + + for( BOARD_ITEM* item : aBoard->Drawings() ) + { + if( !item->IsOnLayer( Edge_Cuts ) ) + continue; + + if( item->Type() == PCB_SHAPE_T ) + shapes.push_back( static_cast( item ) ); + } + + ConnectBoardShapes( shapes, newShapes, SHAPE_JOIN_DISTANCE ); + + for( std::unique_ptr& 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 ); +} diff --git a/pcbnew/plugins/easyedapro/pcb_easyedapro_parser.h b/pcbnew/plugins/easyedapro/pcb_easyedapro_parser.h new file mode 100644 index 0000000000..2be294c665 --- /dev/null +++ b/pcbnew/plugins/easyedapro/pcb_easyedapro_parser.h @@ -0,0 +1,102 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef PCB_EASYEDAPRO_PARSER_H_ +#define PCB_EASYEDAPRO_PARSER_H_ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +class BOARD; +class PROGRESS_REPORTER; + +class PCB_EASYEDAPRO_PARSER +{ +public: + explicit PCB_EASYEDAPRO_PARSER( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter ); + ~PCB_EASYEDAPRO_PARSER(); + + PCB_LAYER_ID LayerToKi( int aLayer ); + + template + static T ScaleSize( T aValue ) + { + return KiROUND( aValue * 25400 / 500 ) * 500; + } + + template + static VECTOR2 ScaleSize( VECTOR2 aValue ) + { + return VECTOR2( ScaleSize( aValue.x ), ScaleSize( aValue.y ) ); + } + + template + static VECTOR2 ScalePos( VECTOR2 aValue ) + { + return VECTOR2( ScaleSize( aValue.x ), -ScaleSize( aValue.y ) ); + } + + static double Convert( wxString aValue ); + + FOOTPRINT* ParseFootprint( const nlohmann::json& aProject, const wxString& aFpUuid, + const std::vector& aLines ); + + void ParseBoard( BOARD* aBoard, const nlohmann::json& aProject, + std::map>& aFootprintMap, + const std::map& aBlobMap, + const std::multimap& aPouredMap, + const std::vector& aLines, const wxString& aFpLibName ); + + std::vector> ParsePoly( BOARD_ITEM_CONTAINER* aContainer, + nlohmann::json polyData, bool aClosed, + bool aInFill ) const; + + SHAPE_LINE_CHAIN ParseContour( nlohmann::json polyData, bool aInFill, + double aArcAccuracy = SHAPE_ARC::DefaultAccuracyForPCB() ) const; + +private: + BOARD* m_board; + VECTOR2D m_relOrigin; + + std::unique_ptr createPAD( FOOTPRINT* aFootprint, const nlohmann::json& line ); + + void fillFootprintModelInfo( FOOTPRINT* footprint, const wxString& modelUuid, + const wxString& modelTitle, const wxString& modelTransform ) const; +}; + + +#endif // PCB_EASYEDAPRO_PARSER_H_ diff --git a/pcbnew/plugins/easyedapro/pcb_easyedapro_plugin.cpp b/pcbnew/plugins/easyedapro/pcb_easyedapro_plugin.cpp new file mode 100644 index 0000000000..444492c0b8 --- /dev/null +++ b/pcbnew/plugins/easyedapro/pcb_easyedapro_plugin.cpp @@ -0,0 +1,409 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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_easyedapro_plugin.h" +#include "pcb_easyedapro_parser.h" +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +struct EASYEDAPRO_PLUGIN::PRJ_DATA +{ + std::map> m_Footprints; + std::map m_Blobs; + std::map> m_Poured; +}; + + +EASYEDAPRO_PLUGIN::EASYEDAPRO_PLUGIN() +{ + m_board = nullptr; + m_props = nullptr; +} + + +EASYEDAPRO_PLUGIN::~EASYEDAPRO_PLUGIN() +{ + if( m_projectData ) + delete m_projectData; +} + + +bool EASYEDAPRO_PLUGIN::CanReadBoard( const wxString& aFileName ) const +{ + if( aFileName.Lower().EndsWith( wxS( ".epro" ) ) ) + { + return true; + } + else if( aFileName.Lower().EndsWith( wxS( ".zip" ) ) ) + { + std::shared_ptr entry; + wxFFileInputStream in( aFileName ); + wxZipInputStream zip( in ); + + if( !zip.IsOk() ) + return false; + + while( entry.reset( zip.GetNextEntry() ), entry.get() != NULL ) + { + wxString name = entry->GetName(); + + if( name == wxS( "project.json" ) ) + return true; + } + + return false; + } + + return false; +} + + +BOARD* EASYEDAPRO_PLUGIN::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe, + const STRING_UTF8_MAP* aProperties, PROJECT* aProject, + PROGRESS_REPORTER* aProgressReporter ) +{ + m_props = aProperties; + + m_board = aAppendToMe ? aAppendToMe : new BOARD(); + + // Give the filename to the board if it's new + if( !aAppendToMe ) + m_board->SetFileName( aFileName ); + + if( aProgressReporter ) + { + aProgressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) ); + + if( !aProgressReporter->KeepRefreshing() ) + THROW_IO_ERROR( _( "Open cancelled by user." ) ); + } + + PCB_EASYEDAPRO_PARSER parser( nullptr, nullptr ); + + wxFileName fname( aFileName ); + + if( fname.GetExt() == wxS( "epro" ) || fname.GetExt() == wxS( "zip" ) ) + { + nlohmann::json project = EASYEDAPRO::ReadProjectFile( aFileName ); + + std::map prjSchematics = project.at( "schematics" ); + std::map prjBoards = project.at( "boards" ); + std::map prjPcbNames = project.at( "pcbs" ); + + wxString pcbToLoad; + + if( prjBoards.size() > 0 ) + { + EASYEDAPRO::PRJ_BOARD boardToLoad = prjBoards.begin()->second; + pcbToLoad = boardToLoad.pcb; + } + else if( prjPcbNames.size() > 0 ) + { + pcbToLoad = prjPcbNames.begin()->first; + } + + if( prjPcbNames.empty() ) + return nullptr; + + LoadAllDataFromProject( aFileName, project ); + + if( !m_projectData ) + return nullptr; + + auto cb = [&]( const wxString& name, const wxString& pcbUuid, wxInputStream& zip ) -> bool + { + if( !name.EndsWith( wxS( ".epcb" ) ) ) + EASY_IT_CONTINUE; + + if( pcbUuid != pcbToLoad ) + EASY_IT_CONTINUE; + + std::vector lines = EASYEDAPRO::ParseJsonLines( zip, name ); + + wxString boardKey = pcbUuid + wxS( "_0" ); + wxScopedCharBuffer cb = boardKey.ToUTF8(); + wxString boardPouredKey = wxBase64Encode( cb.data(), cb.length() ); + + const std::multimap& boardPoured = + get_def( m_projectData->m_Poured, boardPouredKey ); + + parser.ParseBoard( m_board, project, m_projectData->m_Footprints, + m_projectData->m_Blobs, boardPoured, lines, + EASYEDAPRO::ShortenLibName( fname.GetName() ) ); + + EASY_IT_BREAK; + }; + EASYEDAPRO::IterateZipFiles( aFileName, cb ); + } + + return m_board; +} + + +long long EASYEDAPRO_PLUGIN::GetLibraryTimestamp( const wxString& aLibraryPath ) const +{ + return 0; +} + + +void EASYEDAPRO_PLUGIN::FootprintEnumerate( wxArrayString& aFootprintNames, + const wxString& aLibraryPath, bool aBestEfforts, + const STRING_UTF8_MAP* aProperties ) +{ + wxFileName fname( aLibraryPath ); + + if( fname.GetExt() == wxS( "efoo" ) ) + { + wxFFileInputStream ffis( aLibraryPath ); + wxTextInputStream txt( ffis, wxS( " " ), wxConvUTF8 ); + + while( ffis.CanRead() ) + { + wxString line = txt.ReadLine(); + + if( !line.Contains( wxS( "ATTR" ) ) ) + continue; // Don't bother parsing + + nlohmann::json js = nlohmann::json::parse( line ); + if( js.at( 0 ) == "ATTR" && js.at( 7 ) == "Footprint" ) + { + aFootprintNames.Add( js.at( 8 ).get() ); + } + } + } + else if( fname.GetExt() == wxS( "epro" ) || fname.GetExt() == wxS( "zip" ) ) + { + nlohmann::json project = EASYEDAPRO::ReadProjectFile( aLibraryPath ); + std::map footprintMap = project.at( "footprints" ); + + for( auto& [key, value] : footprintMap ) + aFootprintNames.Add( value.at( "title" ) ); + } +} + + +void EASYEDAPRO_PLUGIN::LoadAllDataFromProject( const wxString& aProjectPath, + const nlohmann::json& aProject ) +{ + if( m_projectData ) + delete m_projectData; + + m_projectData = new PRJ_DATA(); + + PCB_EASYEDAPRO_PARSER parser( nullptr, nullptr ); + wxFileName fname( aProjectPath ); + wxString fpLibName = EASYEDAPRO::ShortenLibName( fname.GetName() ); + + std::map> result; + + auto cb = [&]( const wxString& name, const wxString& baseName, wxInputStream& zip ) -> bool + { + if( !name.EndsWith( wxS( ".efoo" ) ) && !name.EndsWith( wxS( ".eblob" ) ) + && !name.EndsWith( wxS( ".ecop" ) ) ) + { + EASY_IT_CONTINUE; + } + + std::vector lines = EASYEDAPRO::ParseJsonLines( zip, name ); + + if( name.EndsWith( wxS( ".efoo" ) ) ) + { + nlohmann::json fpData = aProject.at( "footprints" ).at( baseName ); + wxString fpTitle = fpData.at( "title" ); + + FOOTPRINT* footprint = parser.ParseFootprint( aProject, baseName, lines ); + + if( !footprint ) + EASY_IT_CONTINUE; + + LIB_ID fpID = EASYEDAPRO::ToKiCadLibID( fpLibName, fpTitle ); + footprint->SetFPID( fpID ); + + m_projectData->m_Footprints.emplace( baseName, footprint ); + } + else if( name.EndsWith( wxS( ".eblob" ) ) ) + { + for( const nlohmann::json& line : lines ) + { + if( line.at( 0 ) == "BLOB" ) + { + EASYEDAPRO::BLOB blob = line; + m_projectData->m_Blobs[blob.objectId] = blob; + } + } + } + else if( name.EndsWith( wxS( ".ecop" ) ) && EASYEDAPRO::IMPORT_POURED ) + { + for( const nlohmann::json& line : lines ) + { + if( line.at( 0 ) == "POURED" ) + { + if( !line.at( 2 ).is_string() ) + continue; // Unknown type of POURED + + EASYEDAPRO::POURED poured = line; + m_projectData->m_Poured[baseName].emplace( poured.parentId, poured ); + } + } + } + EASY_IT_CONTINUE; + }; + EASYEDAPRO::IterateZipFiles( aProjectPath, cb ); +} + + +FOOTPRINT* EASYEDAPRO_PLUGIN::FootprintLoad( const wxString& aLibraryPath, + const wxString& aFootprintName, bool aKeepUUID, + const STRING_UTF8_MAP* aProperties ) +{ + PCB_EASYEDAPRO_PARSER parser( nullptr, nullptr ); + FOOTPRINT* footprint = nullptr; + + wxFileName libFname( aLibraryPath ); + + if( libFname.GetExt() == wxS( "efoo" ) ) + { + wxFFileInputStream ffis( aLibraryPath ); + wxTextInputStream txt( ffis, wxS( " " ), wxConvUTF8 ); + + std::vector lines = EASYEDAPRO::ParseJsonLines( ffis, aLibraryPath ); + + for( const nlohmann::json& js : lines ) + { + if( js.at( 0 ) == "ATTR" ) + { + EASYEDAPRO::PCB_ATTR attr = js; + + if( attr.key == wxS( "Footprint" ) && attr.value != aFootprintName ) + return nullptr; + } + } + + footprint = parser.ParseFootprint( nlohmann::json(), wxEmptyString, lines ); + + if( !footprint ) + { + THROW_IO_ERROR( wxString::Format( _( "Cannot load footprint '%s' from '%s'" ), + aFootprintName, aLibraryPath ) ); + } + + LIB_ID fpID = EASYEDAPRO::ToKiCadLibID( wxEmptyString, aFootprintName ); + footprint->SetFPID( fpID ); + + footprint->Reference().SetVisible( true ); + footprint->Value().SetText( aFootprintName ); + footprint->Value().SetVisible( true ); + footprint->AutoPositionFields(); + } + else if( libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) ) + { + nlohmann::json project = EASYEDAPRO::ReadProjectFile( aLibraryPath ); + + wxString fpUuid; + + std::map footprintMap = project.at( "footprints" ); + for( auto& [uuid, data] : footprintMap ) + { + wxString title = data.at( "title" ); + + if( title == aFootprintName ) + { + fpUuid = uuid; + break; + } + } + + if( !fpUuid ) + { + THROW_IO_ERROR( wxString::Format( _( "Footprint '%s' not found in project '%s'" ), + aFootprintName, aLibraryPath ) ); + } + + auto cb = [&]( const wxString& name, const wxString& baseName, wxInputStream& zip ) -> bool + { + if( !name.EndsWith( wxS( ".efoo" ) ) ) + EASY_IT_CONTINUE; + + if( baseName != fpUuid ) + EASY_IT_CONTINUE; + + std::vector lines = EASYEDAPRO::ParseJsonLines( zip, name ); + + footprint = parser.ParseFootprint( project, fpUuid, lines ); + + if( !footprint ) + { + THROW_IO_ERROR( wxString::Format( _( "Cannot load footprint '%s' from '%s'" ), + aFootprintName, aLibraryPath ) ); + } + + LIB_ID fpID = EASYEDAPRO::ToKiCadLibID( wxEmptyString, aFootprintName ); + footprint->SetFPID( fpID ); + + footprint->Reference().SetVisible( true ); + footprint->Value().SetText( aFootprintName ); + footprint->Value().SetVisible( true ); + footprint->AutoPositionFields(); + + EASY_IT_BREAK; + }; + EASYEDAPRO::IterateZipFiles( aLibraryPath, cb ); + } + + return footprint; +} + + +std::vector EASYEDAPRO_PLUGIN::GetImportedCachedLibraryFootprints() +{ + std::vector result; + + if( !m_projectData ) + return result; + + for( auto& [fpUuid, footprint] : m_projectData->m_Footprints ) + { + result.push_back( static_cast( footprint->Clone() ) ); + } + + return result; +} diff --git a/pcbnew/plugins/easyedapro/pcb_easyedapro_plugin.h b/pcbnew/plugins/easyedapro/pcb_easyedapro_plugin.h new file mode 100644 index 0000000000..ab69a562b2 --- /dev/null +++ b/pcbnew/plugins/easyedapro/pcb_easyedapro_plugin.h @@ -0,0 +1,89 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 + */ + +#ifndef PCB_EASYEDAPRO_PLUGIN_H_ +#define PCB_EASYEDAPRO_PLUGIN_H_ + +#include +#include + + +class EASYEDAPRO_PLUGIN : public PLUGIN +{ +public: + const wxString PluginName() const override + { + return wxS( "EasyEDA (JLCEDA) Professional" ); + } + + PLUGIN_FILE_DESC GetBoardFileDesc() const override + { + return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Pro project" ), { "epro", "zip" } ); + } + + PLUGIN_FILE_DESC GetFootprintLibDesc() const override + { + return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Pro project" ), { "epro", "zip" } ); + } + + PLUGIN_FILE_DESC GetFootprintFileDesc() const override + { + return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Pro files" ), { "efoo", "epro", "zip" } ); + } + + bool CanReadBoard( const wxString& aFileName ) const override; + + BOARD* LoadBoard( const wxString& aFileName, BOARD* aAppendToMe, + const STRING_UTF8_MAP* aProperties = nullptr, PROJECT* aProject = nullptr, + PROGRESS_REPORTER* aProgressReporter = nullptr ) override; + + long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override; + + void FootprintEnumerate( wxArrayString& aFootprintNames, const wxString& aLibraryPath, + bool aBestEfforts, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + std::vector GetImportedCachedLibraryFootprints(); + + FOOTPRINT* FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, + bool aKeepUUID = false, + const STRING_UTF8_MAP* aProperties = nullptr ) override; + + bool IsFootprintLibWritable( const wxString& aLibraryPath ) override { return false; } + + EASYEDAPRO_PLUGIN(); + ~EASYEDAPRO_PLUGIN(); + +private: + struct PRJ_DATA; // Opaque data structure + PRJ_DATA* m_projectData = nullptr; + + void LoadAllDataFromProject( const wxString& aLibraryPath, const nlohmann::json& aProject ); + + const STRING_UTF8_MAP* m_props; + BOARD* m_board; +}; + + +#endif // PCB_EASYEDAPRO_PLUGIN_H_