From b707c84b62e38f9b0384aedcece80107fa865d1b Mon Sep 17 00:00:00 2001 From: Thomas Pointhuber Date: Fri, 3 Apr 2020 23:22:24 +0000 Subject: [PATCH] Use Record handling for parsing Now, we know how big a record is, and should be able to parse all boards without missing bytes? --- common/wildcards_and_files_ext.cpp | 14 + cvpcb/CMakeLists.txt | 1 + eeschema/sch_io_mgr.h | 16 +- include/wildcards_and_files_ext.h | 3 + pcbnew/CMakeLists.txt | 2 + pcbnew/altium2kicadpcb_plugin/CMakeLists.txt | 22 + .../altium_circuit_maker_plugin.cpp | 101 + .../altium_circuit_maker_plugin.h | 63 + .../altium_circuit_studio_plugin.cpp | 101 + .../altium_circuit_studio_plugin.h | 63 + .../altium_designer_plugin.cpp | 101 + .../altium_designer_plugin.h | 63 + .../altium2kicadpcb_plugin/altium_parser.cpp | 234 ++ pcbnew/altium2kicadpcb_plugin/altium_parser.h | 199 ++ .../altium2kicadpcb_plugin/altium_parser.ksy | 658 ++++++ .../altium_parser_pcb.cpp | 941 ++++++++ .../altium_parser_pcb.h | 616 +++++ pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp | 2011 +++++++++++++++++ pcbnew/altium2kicadpcb_plugin/altium_pcb.h | 187 ++ pcbnew/eagle_plugin.h | 1 + pcbnew/files.cpp | 30 +- pcbnew/io_mgr.cpp | 23 +- pcbnew/io_mgr.h | 14 +- qa/gal/gal_pixel_alignment/CMakeLists.txt | 3 +- qa/pcbnew/CMakeLists.txt | 1 + qa/pcbnew_tools/CMakeLists.txt | 1 + thirdparty/CMakeLists.txt | 1 + thirdparty/compoundfilereader/CMakeLists.txt | 3 + thirdparty/compoundfilereader/LICENSE.MIT | 22 + thirdparty/compoundfilereader/README.txt | 4 + .../compoundfilereader/compoundfilereader.h | 480 ++++ thirdparty/compoundfilereader/utf.h | 137 ++ 32 files changed, 6089 insertions(+), 27 deletions(-) create mode 100644 pcbnew/altium2kicadpcb_plugin/CMakeLists.txt create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.cpp create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.h create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.cpp create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.h create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.cpp create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.h create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_parser.cpp create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_parser.h create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_parser.ksy create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.cpp create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.h create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp create mode 100644 pcbnew/altium2kicadpcb_plugin/altium_pcb.h create mode 100644 thirdparty/compoundfilereader/CMakeLists.txt create mode 100644 thirdparty/compoundfilereader/LICENSE.MIT create mode 100644 thirdparty/compoundfilereader/README.txt create mode 100644 thirdparty/compoundfilereader/compoundfilereader.h create mode 100644 thirdparty/compoundfilereader/utf.h diff --git a/common/wildcards_and_files_ext.cpp b/common/wildcards_and_files_ext.cpp index af5918b750..1c994093fb 100644 --- a/common/wildcards_and_files_ext.cpp +++ b/common/wildcards_and_files_ext.cpp @@ -245,6 +245,20 @@ wxString PCadPcbFileWildcard() return _( "P-Cad 200x ASCII PCB files" ) + AddFileExtListToFilter( { "pcb" } ); } +wxString AltiumDesignerPcbFileWildcard() +{ + return _( "Altium Designer PCB files" ) + AddFileExtListToFilter( { "PcbDoc" } ); +} + +wxString AltiumCircuitStudioPcbFileWildcard() +{ + return _( "Altium Circuit Studio PCB files" ) + AddFileExtListToFilter( { "CSPcbDoc" } ); +} + +wxString AltiumCircuitMakerPcbFileWildcard() +{ + return _( "Altium Circuit Maker PCB files" ) + AddFileExtListToFilter( { "CMPcbDoc" } ); +} wxString PcbFileWildcard() { diff --git a/cvpcb/CMakeLists.txt b/cvpcb/CMakeLists.txt index 8b7a9d2a41..71cb8fa651 100644 --- a/cvpcb/CMakeLists.txt +++ b/cvpcb/CMakeLists.txt @@ -88,6 +88,7 @@ set_target_properties( cvpcb_kiface PROPERTIES target_link_libraries( cvpcb_kiface pcbcommon pcad2kicadpcb + altium2kicadpcb 3d-viewer gal common diff --git a/eeschema/sch_io_mgr.h b/eeschema/sch_io_mgr.h index 1f6144544c..369e0f04a2 100644 --- a/eeschema/sch_io_mgr.h +++ b/eeschema/sch_io_mgr.h @@ -51,15 +51,15 @@ public: * has been a plugin written. */ DEFINE_ENUM_VECTOR( SCH_FILE_T, - { - SCH_LEGACY, ///< Legacy Eeschema file formats prior to s-expression. - SCH_KICAD, ///< The s-expression version of the schematic file formats. - SCH_EAGLE, ///< Autodesk Eagle file format - // Add your schematic type here. + { + SCH_LEGACY, ///< Legacy Eeschema file formats prior to s-expression. + SCH_KICAD, ///< The s-expression version of the schematic file formats. + SCH_EAGLE, ///< Autodesk Eagle file format + // Add your schematic type here. - // ALTIUM, - // etc. - } ) + // ALTIUM, + // etc. + } ) /** * Return a #SCH_PLUGIN which the caller can use to import, export, save, or load diff --git a/include/wildcards_and_files_ext.h b/include/wildcards_and_files_ext.h index d120781a33..559ab16d36 100644 --- a/include/wildcards_and_files_ext.h +++ b/include/wildcards_and_files_ext.h @@ -188,6 +188,9 @@ extern wxString EaglePcbFileWildcard(); extern wxString EagleSchematicFileWildcard(); extern wxString EagleFilesWildcard(); extern wxString PCadPcbFileWildcard(); +extern wxString AltiumDesignerPcbFileWildcard(); +extern wxString AltiumCircuitStudioPcbFileWildcard(); +extern wxString AltiumCircuitMakerPcbFileWildcard(); extern wxString PdfFileWildcard(); extern wxString PSFileWildcard(); extern wxString MacrosFileWildcard(); diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 2e1933d004..d99c1fb290 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -569,6 +569,7 @@ endif() add_subdirectory( pcad2kicadpcb_plugin ) +add_subdirectory( altium2kicadpcb_plugin ) if( BUILD_GITHUB_PLUGIN ) add_subdirectory( github ) @@ -666,6 +667,7 @@ set( PCBNEW_KIFACE_LIBRARIES pcbcommon pnsrouter pcad2kicadpcb + altium2kicadpcb common gal dxflib_qcad diff --git a/pcbnew/altium2kicadpcb_plugin/CMakeLists.txt b/pcbnew/altium2kicadpcb_plugin/CMakeLists.txt new file mode 100644 index 0000000000..67ec2bcca0 --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/CMakeLists.txt @@ -0,0 +1,22 @@ + +# Sources for the pcbnew PLUGIN called ALTIUM_DESIGNER_PLUGIN, ALTIUM_CIRCUIT_STUDIO_PLUGIN and ALTIUM_CIRCUIT_MAKER_PLUGIN + +set( ALTIUM2PCBNEW_SRCS + altium_circuit_maker_plugin.cpp + altium_circuit_studio_plugin.cpp + altium_designer_plugin.cpp + altium_pcb.cpp + altium_parser.cpp + altium_parser_pcb.cpp + ) + +add_library( altium2kicadpcb STATIC ${ALTIUM2PCBNEW_SRCS} ) + +add_dependencies( altium2kicadpcb compoundfilereader ) + +target_link_libraries( altium2kicadpcb pcbcommon ) + +target_include_directories( altium2kicadpcb PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + $ + ) \ No newline at end of file diff --git a/pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.cpp b/pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.cpp new file mode 100644 index 0000000000..e9d073dcaf --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.cpp @@ -0,0 +1,101 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 Thomas Pointhuber + * + * 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 + */ + +/** + * @file altium_plugin.cpp + * @brief Pcbnew PLUGIN for Altium *.PcbDoc format. + */ + +#include + +#include + +#include +#include + +#include + +#include +#include + +ALTIUM_CIRCUIT_MAKER_PLUGIN::ALTIUM_CIRCUIT_MAKER_PLUGIN() +{ + m_board = nullptr; + m_props = nullptr; +} + + +ALTIUM_CIRCUIT_MAKER_PLUGIN::~ALTIUM_CIRCUIT_MAKER_PLUGIN() +{ +} + + +const wxString ALTIUM_CIRCUIT_MAKER_PLUGIN::PluginName() const +{ + return wxT( "Altium Circuit Maker" ); +} + + +const wxString ALTIUM_CIRCUIT_MAKER_PLUGIN::GetFileExtension() const +{ + return wxT( "CMPcbDoc" ); +} + + +BOARD* ALTIUM_CIRCUIT_MAKER_PLUGIN::Load( + const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties ) +{ + 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 ); + + // clang-format off + const std::map mapping = { + { ALTIUM_PCB_DIR::FILE_HEADER, "FileHeader" }, + { ALTIUM_PCB_DIR::ARCS6, "1CEEB63FB33847F8AFC4485F64735E\\Data" }, + { ALTIUM_PCB_DIR::BOARD6, "96B09F5C6CEE434FBCE0DEB3E88E70\\Data" }, + { ALTIUM_PCB_DIR::BOARDREGIONS, "E3A544335C30403A991912052C936F\\Data" }, + { ALTIUM_PCB_DIR::CLASSES6, "4F71DD45B09143988210841EA1C28D\\Data" }, + { ALTIUM_PCB_DIR::COMPONENTS6, "F9D060ACC7DD4A85BC73CB785BAC81\\Data" }, + { ALTIUM_PCB_DIR::DIMENSIONS6, "068B9422DBB241258BA2DE9A6BA1A6\\Data" }, + { ALTIUM_PCB_DIR::FILLS6, "6FFE038462A940E9B422EFC8F5D85E\\Data" }, + { ALTIUM_PCB_DIR::NETS6, "35D7CF51BB9B4875B3A138B32D80DC\\Data" }, + { ALTIUM_PCB_DIR::PADS6, "4F501041A9BC4A06BDBDAB67D3820E\\Data" }, + { ALTIUM_PCB_DIR::POLYGONS6, "A1931C8B0B084A61AA45146575FDD3\\Data" }, + { ALTIUM_PCB_DIR::REGIONS6, "F513A5885418472886D3EF18A09E46\\Data" }, + { ALTIUM_PCB_DIR::RULES6, "C27718A40C94421388FAE5BD7785D7\\Data" }, + { ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6,"BDAA2C70289849078C8EBEEC7F0848\\Data" }, + { ALTIUM_PCB_DIR::TEXTS6, "A34BC67C2A5F408D8F377378C5C5E2\\Data" }, + { ALTIUM_PCB_DIR::TRACKS6, "412A754DBB864645BF01CD6A80C358\\Data" }, + { ALTIUM_PCB_DIR::VIAS6, "C87A685A0EFA4A90BEEFD666198B56\\Data" } + }; + // clang-format on + + ParseAltiumPcb( m_board, aFileName, mapping ); + + return m_board; +} diff --git a/pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.h b/pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.h new file mode 100644 index 0000000000..6e10b0a54b --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.h @@ -0,0 +1,63 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 Thomas Pointhuber + * + * 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 + */ + +/** + * @file pcad_plugin.h + * @brief Pcbnew PLUGIN for Altium *.PcbDoc format. + */ + +#ifndef ALTIUM_CIRCUIT_MAKER_PLUGIN_H_ +#define ALTIUM_CIRCUIT_MAKER_PLUGIN_H_ + + +#include + +class ALTIUM_CIRCUIT_MAKER_PLUGIN : public PLUGIN +{ +public: + // ------------------------------------------------------- + + const wxString PluginName() const override; + + BOARD* Load( + const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties ) override; + + const wxString GetFileExtension() const override; + + long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override + { + // TODO? + return 0; + } + + // ------------------------------------------------------ + + ALTIUM_CIRCUIT_MAKER_PLUGIN(); + ~ALTIUM_CIRCUIT_MAKER_PLUGIN(); + +private: + const PROPERTIES* m_props; + BOARD* m_board; +}; + +#endif // ALTIUM_CIRCUIT_MAKER_PLUGIN_H_ \ No newline at end of file diff --git a/pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.cpp b/pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.cpp new file mode 100644 index 0000000000..a7c600f114 --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.cpp @@ -0,0 +1,101 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 Thomas Pointhuber + * + * 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 + */ + +/** + * @file altium_plugin.cpp + * @brief Pcbnew PLUGIN for Altium *.PcbDoc format. + */ + +#include + +#include + +#include +#include + +#include + +#include +#include + +ALTIUM_CIRCUIT_STUDIO_PLUGIN::ALTIUM_CIRCUIT_STUDIO_PLUGIN() +{ + m_board = nullptr; + m_props = nullptr; +} + + +ALTIUM_CIRCUIT_STUDIO_PLUGIN::~ALTIUM_CIRCUIT_STUDIO_PLUGIN() +{ +} + + +const wxString ALTIUM_CIRCUIT_STUDIO_PLUGIN::PluginName() const +{ + return wxT( "Altium Circuit Studio" ); +} + + +const wxString ALTIUM_CIRCUIT_STUDIO_PLUGIN::GetFileExtension() const +{ + return wxT( "CSPcbDoc" ); +} + + +BOARD* ALTIUM_CIRCUIT_STUDIO_PLUGIN::Load( + const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties ) +{ + 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 ); + + // clang-format off + const std::map mapping = { + { ALTIUM_PCB_DIR::FILE_HEADER, "FileHeader" }, + { ALTIUM_PCB_DIR::ARCS6, "00C595EB90524FFC8C3BD9670020A2\\Data" }, + { ALTIUM_PCB_DIR::BOARD6, "88857D7F1DF64F7BBB61848C965636\\Data" }, + { ALTIUM_PCB_DIR::BOARDREGIONS, "8957CF30F167408D9D263D23FE7C89\\Data" }, + { ALTIUM_PCB_DIR::CLASSES6, "847EFBF87A5149B1AA326A52AD6357\\Data" }, + { ALTIUM_PCB_DIR::COMPONENTS6, "465416896A15486999A39C643935D2\\Data" }, + { ALTIUM_PCB_DIR::DIMENSIONS6, "16C81DBC13C447FF8B42A426677F3C\\Data" }, + { ALTIUM_PCB_DIR::FILLS6, "4E83BDC3253747F08E9006D7F57020\\Data" }, + { ALTIUM_PCB_DIR::NETS6, "D95A0DA2FE9047779A5194C127F30B\\Data" }, + { ALTIUM_PCB_DIR::PADS6, "47D69BC5107A4B8DB8DAA23E39C238\\Data" }, + { ALTIUM_PCB_DIR::POLYGONS6, "D7038392280E4E229B9D9B5426B295\\Data" }, + { ALTIUM_PCB_DIR::REGIONS6, "FFDDC21382BB42FE8A7D0C328D272C\\Data" }, + { ALTIUM_PCB_DIR::RULES6, "48B2FA96DB7546818752B34373D6C6\\Data" }, + { ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6, "D5F54B536E124FB89E2D51B1121508\\Data" }, + { ALTIUM_PCB_DIR::TEXTS6, "349ABBB211DB4F5B8AE41B1B49555A\\Data" }, + { ALTIUM_PCB_DIR::TRACKS6, "530C20C225354B858B2578CAB8C08D\\Data" }, + { ALTIUM_PCB_DIR::VIAS6, "CA5F5989BCDB404DA70A9D1D3D5758\\Data" } + }; + // clang-format on + + ParseAltiumPcb( m_board, aFileName, mapping ); + + return m_board; +} diff --git a/pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.h b/pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.h new file mode 100644 index 0000000000..d88a66e78d --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.h @@ -0,0 +1,63 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 Thomas Pointhuber + * + * 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 + */ + +/** + * @file pcad_plugin.h + * @brief Pcbnew PLUGIN for Altium *.PcbDoc format. + */ + +#ifndef ALTIUM_CIRCUIT_STUDIO_PLUGIN_H_ +#define ALTIUM_CIRCUIT_STUDIO_PLUGIN_H_ + + +#include + +class ALTIUM_CIRCUIT_STUDIO_PLUGIN : public PLUGIN +{ +public: + // ------------------------------------------------------- + + const wxString PluginName() const override; + + BOARD* Load( + const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties ) override; + + const wxString GetFileExtension() const override; + + long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override + { + // TODO? + return 0; + } + + // ------------------------------------------------------ + + ALTIUM_CIRCUIT_STUDIO_PLUGIN(); + ~ALTIUM_CIRCUIT_STUDIO_PLUGIN(); + +private: + const PROPERTIES* m_props; + BOARD* m_board; +}; + +#endif // ALTIUM_CIRCUIT_STUDIO_PLUGIN_H_ \ No newline at end of file diff --git a/pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.cpp b/pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.cpp new file mode 100644 index 0000000000..30a04828bb --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.cpp @@ -0,0 +1,101 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 Thomas Pointhuber + * + * 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 + */ + +/** + * @file altium_plugin.cpp + * @brief Pcbnew PLUGIN for Altium *.PcbDoc format. + */ + +#include + +#include + +#include +#include + +#include + +#include +#include + +ALTIUM_DESIGNER_PLUGIN::ALTIUM_DESIGNER_PLUGIN() +{ + m_board = nullptr; + m_props = nullptr; +} + + +ALTIUM_DESIGNER_PLUGIN::~ALTIUM_DESIGNER_PLUGIN() +{ +} + + +const wxString ALTIUM_DESIGNER_PLUGIN::PluginName() const +{ + return wxT( "Altium Designer" ); +} + + +const wxString ALTIUM_DESIGNER_PLUGIN::GetFileExtension() const +{ + return wxT( "PcbDoc" ); +} + + +BOARD* ALTIUM_DESIGNER_PLUGIN::Load( + const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties ) +{ + 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 ); + + // clang-format off + const std::map mapping = { + { ALTIUM_PCB_DIR::FILE_HEADER, "FileHeader" }, + { ALTIUM_PCB_DIR::ARCS6, "Arcs6\\Data" }, + { ALTIUM_PCB_DIR::BOARD6, "Board6\\Data" }, + { ALTIUM_PCB_DIR::BOARDREGIONS, "BoardRegions\\Data" }, + { ALTIUM_PCB_DIR::CLASSES6, "Classes6\\Data" }, + { ALTIUM_PCB_DIR::COMPONENTS6, "Components6\\Data" }, + { ALTIUM_PCB_DIR::DIMENSIONS6, "Dimensions6\\Data" }, + { ALTIUM_PCB_DIR::FILLS6, "Fills6\\Data" }, + { ALTIUM_PCB_DIR::NETS6, "Nets6\\Data" }, + { ALTIUM_PCB_DIR::PADS6, "Pads6\\Data" }, + { ALTIUM_PCB_DIR::POLYGONS6, "Polygons6\\Data" }, + { ALTIUM_PCB_DIR::REGIONS6, "Regions6\\Data" }, + { ALTIUM_PCB_DIR::RULES6, "Rules6\\Data" }, + { ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6, "ShapeBasedRegions6\\Data" }, + { ALTIUM_PCB_DIR::TEXTS6, "Texts6\\Data" }, + { ALTIUM_PCB_DIR::TRACKS6, "Tracks6\\Data" }, + { ALTIUM_PCB_DIR::VIAS6, "Vias6\\Data" } + }; + // clang-format on + + ParseAltiumPcb( m_board, aFileName, mapping ); + + return m_board; +} diff --git a/pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.h b/pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.h new file mode 100644 index 0000000000..381f30a80b --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.h @@ -0,0 +1,63 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 Thomas Pointhuber + * + * 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 + */ + +/** + * @file pcad_plugin.h + * @brief Pcbnew PLUGIN for Altium *.PcbDoc format. + */ + +#ifndef ALTIUM_DESIGNER_PLUGIN_H_ +#define ALTIUM_DESIGNER_PLUGIN_H_ + + +#include + +class ALTIUM_DESIGNER_PLUGIN : public PLUGIN +{ +public: + // ------------------------------------------------------- + + const wxString PluginName() const override; + + BOARD* Load( + const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties ) override; + + const wxString GetFileExtension() const override; + + long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override + { + // TODO? + return 0; + } + + // ------------------------------------------------------ + + ALTIUM_DESIGNER_PLUGIN(); + ~ALTIUM_DESIGNER_PLUGIN(); + +private: + const PROPERTIES* m_props; + BOARD* m_board; +}; + +#endif // ALTIUM_DESIGNER_PLUGIN_H_ \ No newline at end of file diff --git a/pcbnew/altium2kicadpcb_plugin/altium_parser.cpp b/pcbnew/altium2kicadpcb_plugin/altium_parser.cpp new file mode 100644 index 0000000000..13bc19d8ec --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_parser.cpp @@ -0,0 +1,234 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019-2020 Thomas Pointhuber + * + * 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 "altium_parser.h" + +#include +#include +#include +#include +#include +#include + + +const CFB::COMPOUND_FILE_ENTRY* FindStream( + const CFB::CompoundFileReader& aReader, const char* aStreamName ) +{ + const CFB::COMPOUND_FILE_ENTRY* ret = nullptr; + aReader.EnumFiles( aReader.GetRootEntry(), -1, + [&]( const CFB::COMPOUND_FILE_ENTRY* aEntry, const CFB::utf16string& aU16dir, + int level ) -> void { + if( aReader.IsStream( aEntry ) ) + { + std::string name = UTF16ToUTF8( aEntry->name ); + if( aU16dir.length() > 0 ) + { + std::string dir = UTF16ToUTF8( aU16dir.c_str() ); + if( strncmp( aStreamName, dir.c_str(), dir.length() ) == 0 + && aStreamName[dir.length()] == '\\' + && strcmp( aStreamName + dir.length() + 1, name.c_str() ) == 0 ) + { + ret = aEntry; + } + } + else + { + if( strcmp( aStreamName, name.c_str() ) == 0 ) + { + ret = aEntry; + } + } + } + } ); + return ret; +} + + +ALTIUM_PARSER::ALTIUM_PARSER( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + m_subrecord_end = nullptr; + if( aEntry->size > std::numeric_limits::max() ) + { + THROW_IO_ERROR( _( "stream too large" ) ); + } + + m_size = static_cast( aEntry->size ); + m_error = false; + m_content.reset( new char[m_size] ); + m_pos = m_content.get(); + + // read file into buffer + aReader.ReadFile( aEntry, 0, m_content.get(), m_size ); +} + + +std::map ALTIUM_PARSER::ReadProperties() +{ + std::map kv; + + uint32_t length = Read(); + if( length > GetRemainingBytes() || m_pos[length - 1] != '\0' ) + { + m_error = true; + return kv; + } + + //we use std::string because std::string can handle NULL-bytes + //wxString would end the string at the first NULL-byte + std::string str = std::string( m_pos, length - 1 ); + m_pos += length; + + std::size_t token_end = 0; + while( token_end < str.size() && token_end != std::string::npos ) + { + std::size_t token_start = str.find( '|', token_end ); + std::size_t token_equal = str.find( '=', token_start ); + token_end = str.find( '|', token_equal ); + + std::string keyS = str.substr( token_start + 1, token_equal - token_start - 1 ); + std::string valueS = str.substr( token_equal + 1, token_end - token_equal - 1 ); + + //convert the strings to wxStrings, since we use them everywhere + //value can have non-ASCII characters, so we convert them from LATIN1/ISO8859-1 + wxString key( keyS.c_str(), wxConvISO8859_1 ); + wxString value( valueS.c_str(), wxConvISO8859_1 ); + + kv.insert( { key, value } ); + } + + return kv; +} + +int ALTIUM_PARSER::PropertiesReadInt( + const std::map& aProperties, const wxString& aKey, int aDefault ) +{ + try + { + const wxString& value = aProperties.at( aKey ); + + return wxAtoi( value ); + } + catch( const std::out_of_range& oor ) + { + return aDefault; + } +} + +double ALTIUM_PARSER::PropertiesReadDouble( + const std::map& aProperties, const wxString& aKey, double aDefault ) +{ + try + { + const wxString& value = aProperties.at( aKey ); + + // Locale independent str -> double conversation + std::istringstream istr( (const char*) value.mb_str() ); + istr.imbue( std::locale( "C" ) ); + + double doubleValue; + istr >> doubleValue; + return doubleValue; + } + catch( const std::out_of_range& oor ) + { + return aDefault; + } +} + +bool ALTIUM_PARSER::PropertiesReadBool( + const std::map& aProperties, const wxString& aKey, bool aDefault ) +{ + try + { + const wxString& value = aProperties.at( aKey ); + + return value == "TRUE"; + } + catch( const std::out_of_range& oor ) + { + return aDefault; + } +} + +int32_t ALTIUM_PARSER::PropertiesReadKicadUnit( const std::map& aProperties, + const wxString& aKey, const wxString& aDefault ) +{ + const wxString& value = PropertiesReadString( aProperties, aKey, aDefault ); + + size_t decimal_point = value.find( '.' ); + size_t value_end = value.find_first_not_of( "+-0123456789." ); + + wxString before_decimal_str = value.Left( decimal_point ); + int before_decimal = wxAtoi( before_decimal_str ); + int after_decimal = 0; + size_t after_decimal_digits = 0; + if( decimal_point != wxString::npos ) + { + if( value_end != wxString::npos ) + { + after_decimal_digits = value_end - ( decimal_point + 1 ); + } + else + { + after_decimal_digits = value.size() - ( decimal_point + 1 ); // TODO: correct? + } + wxString after_decimal_str = value.Mid( decimal_point + 1, after_decimal_digits ); + after_decimal = wxAtoi( after_decimal_str ); + } + + if( value.length() > 3 && value.compare( value.length() - 3, 3, "mil" ) == 0 ) + { + // ensure after_decimal is formatted to base 1000 + int after_decimal_1000; + if( after_decimal_digits <= 4 ) + { + after_decimal_1000 = + static_cast( after_decimal * std::pow( 10, 4 - after_decimal_digits ) ); + } + else + { + after_decimal_1000 = + static_cast( after_decimal / std::pow( 10, after_decimal_digits - 4 ) ); + } + + int32_t mils = before_decimal * 10000 + after_decimal_1000; + return ConvertToKicadUnit( mils ); + } + + wxLogError( wxString::Format( _( "Unit '%s' does not end with mils" ) ), value ); + return 0; +} + +wxString ALTIUM_PARSER::PropertiesReadString( const std::map& aProperties, + const wxString& aKey, const wxString& aDefault ) +{ + try + { + return aProperties.at( aKey ); + } + catch( const std::out_of_range& oor ) + { + return aDefault; + } +} diff --git a/pcbnew/altium2kicadpcb_plugin/altium_parser.h b/pcbnew/altium2kicadpcb_plugin/altium_parser.h new file mode 100644 index 0000000000..e8161c9b56 --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_parser.h @@ -0,0 +1,199 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019-2020 Thomas Pointhuber + * + * 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 ALTIUM_PARSER_H +#define ALTIUM_PARSER_H + +#include +#include + +#include +#include + + +namespace CFB +{ +class CompoundFileReader; +struct COMPOUND_FILE_ENTRY; +} // namespace CFB + +// Helper method to find file inside compound file +const CFB::COMPOUND_FILE_ENTRY* FindStream( + const CFB::CompoundFileReader& aReader, const char* aStreamName ); + + +class ALTIUM_PARSER +{ +public: + ALTIUM_PARSER( const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + ~ALTIUM_PARSER() = default; + + template + Type Read() + { + if( GetRemainingBytes() >= sizeof( Type ) ) + { + Type val = *(Type*) ( m_pos ); + m_pos += sizeof( Type ); + return val; + } + else + { + m_error = true; + return 0; + } + } + + wxString ReadWxString() + { + uint8_t len = Read(); + if( GetRemainingBytes() >= len ) + { + + //altium uses LATIN1/ISO 8859-1, convert it + wxString val = wxString( m_pos, wxConvISO8859_1, len ); + m_pos += len; + return val; + } + else + { + m_error = true; + return wxString( "" ); + } + } + + int32_t ReadKicadUnit() + { + return ConvertToKicadUnit( Read() ); + } + + int32_t ReadKicadUnitX() + { + return ReadKicadUnit(); + } + + int32_t ReadKicadUnitY() + { + return -ReadKicadUnit(); + } + + wxPoint ReadWxPoint() + { + int32_t x = ReadKicadUnitX(); + int32_t y = ReadKicadUnitY(); + return { x, y }; + } + + wxSize ReadWxSize() + { + int32_t x = ReadKicadUnit(); + int32_t y = ReadKicadUnit(); + return { x, y }; + } + + size_t ReadAndSetSubrecordLength() + { + uint32_t length = Read(); + m_subrecord_end = m_pos + length; + return length; + } + + std::map ReadProperties(); + + static int32_t ConvertToKicadUnit( const int32_t aValue ) + { + return ( ( (int64_t) aValue ) * 254L ) / 100; + } + + static int32_t ConvertToKicadUnit( const double aValue ) + { + return KiROUND( aValue * 2.54L ); + } + + static int PropertiesReadInt( + const std::map& aProperties, const wxString& aKey, int aDefault ); + + static double PropertiesReadDouble( const std::map& aProperties, + const wxString& aKey, double aDefault ); + + static bool PropertiesReadBool( + const std::map& aProperties, const wxString& aKey, bool aDefault ); + + static int32_t PropertiesReadKicadUnit( const std::map& aProperties, + const wxString& aKey, const wxString& aDefault ); + + static wxString PropertiesReadString( const std::map& aProperties, + const wxString& aKey, const wxString& aDefault ); + + void Skip( size_t aLength ) + { + if( GetRemainingBytes() >= aLength ) + { + m_pos += aLength; + } + else + { + m_error = true; + } + } + + void SkipSubrecord() + { + if( m_subrecord_end == nullptr || m_subrecord_end < m_pos ) + { + m_error = true; + } + else + { + m_pos = m_subrecord_end; + } + }; + + size_t GetRemainingBytes() const + { + return m_pos == nullptr ? 0 : m_size - ( m_pos - m_content.get() ); + } + + size_t GetRemainingSubrecordBytes() const + { + return m_pos == nullptr || m_subrecord_end == nullptr || m_subrecord_end <= m_pos ? + 0 : + m_subrecord_end - m_pos; + }; + + bool HasParsingError() + { + return m_error; + } + +private: + std::unique_ptr m_content; + size_t m_size; + + char* m_pos; // current read pointer + char* m_subrecord_end; // pointer which points to next subrecord start + bool m_error; +}; + + +#endif //ALTIUM_PARSER_H diff --git a/pcbnew/altium2kicadpcb_plugin/altium_parser.ksy b/pcbnew/altium2kicadpcb_plugin/altium_parser.ksy new file mode 100644 index 0000000000..7e2791ee6b --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_parser.ksy @@ -0,0 +1,658 @@ +# Can be viewed in: https://ide.kaitai.io/ +# +# This file is a formal specification of the binary format used in Altium. +# Files need to manually extracted using a program which can read the Microsoft Compound File Format. +# +# While I do not create a parser using this file, it is still very helpful to understand the binary +# format. + +meta: + id: altium_binary + endian: le + encoding: ISO8859-1 + +seq: + - id: record + type: record + repeat: eos + +# https://github.com/thesourcerer8/altium2kicad/blob/master/convertpcb.pl#L1291 +types: + record: + seq: + - id: recordtype + type: u1 + enum: record_id + - id: record + type: + switch-on: recordtype + cases: + record_id::arc6: arc + record_id::pad6: pad + record_id::via6: via + record_id::track6: track + record_id::text6: text + record_id::fill6: fill + record_id::region6: region + + arc: + seq: + - id: sub1_len + type: u4 + - id: data + type: arc_sub1 + size: sub1_len + + arc_sub1: + seq: + - id: layer + type: u1 + - #id: flags_u7 + type: b1 + - #id: flags_u6 + type: b1 + - #id: flags_u5 + type: b1 + - #id: flags_u4 + type: b1 + - #id: flags_u3 + type: b1 + - id: is_not_locked + type: b1 + - id: is_not_polygonoutline + type: b1 + - #id: flags_u0 + type: b1 + - id: is_keepout + type: u1 # KEEPOUT = 2 + - id: net + type: u2 + - id: subpolyindex + type: u2 + - id: component + type: u2 + - size: 4 + - id: center + type: xy + - id: radius + type: u4 + - id: start_angle + type: f8 + - id: end_angle + type: f8 + - id: width + type: u4 + + pad: + seq: + - id: sub1_len + type: u4 + - id: designator + type: pad_sub1 + size: sub1_len + - id: sub2_len + type: u4 + - size: sub2_len + - id: sub3_len + type: u4 + - size: sub3_len + - id: sub4_len + type: u4 + - size: sub4_len + - id: sub5_len + type: u4 + - id: size_and_shape + type: pad_sub5 + size: sub5_len + - id: sub6_len + type: u4 + - id: size_and_shape_by_layer + type: pad_sub6 + size: sub6_len + if: sub6_len > 0 + + pad_sub1: + seq: + - id: name_len # = len-1? + type: u1 + - id: name + type: str + size: name_len + + pad_sub5: + seq: + - id: layer # $pos+23 + type: u1 + enum: layer + - id: test_fab_top + type: b1 + - id: tent_bottom + type: b1 + - id: tent_top + type: b1 + - #id: flags_u4 + type: b1 + - #id: flags_u3 + type: b1 + - id: is_not_locked + type: b1 + - #id: flags_u1 + type: b1 + - #id: flags_u0 + type: b1 + - #id: flags2_u7 + type: b1 + - #id: flags2_u6 + type: b1 + - #id: flags2_u5 + type: b1 + - #id: flags2_u4 + type: b1 + - #id: flags2_u3 + type: b1 + - #id: flags2_u2 + type: b1 + - #id: flags2_u1 + type: b1 + - id: test_fab_bottom + type: b1 + #- id: u + # size: 1 + - id: net # $pos+26 + type: u2 + - size: 2 + - id: component # $pos+30 + type: u2 + - size: 4 + - id: position # $pos+36, $pos+40 + type: xy + - id: topsize # $pos+44, $pos+48 + type: xy + - id: midsize # $pos+52, $pos+56 + type: xy + - id: botsize # $pos+60, $pos+64 + type: xy + - id: holesize # $pos+68 + type: u4 + - id: topshape # $pos+72 + type: u1 + enum: pad_shape + - id: midshape # $pos+73 + type: u1 + enum: pad_shape + - id: botshape # $pos+74 + type: u1 + enum: pad_shape + - id: direction # $pos+75 + type: f8 + - id: plated # $pos+83 + type: u1 + enum: boolean + - size: 1 + - id: pad_mode # $pos+85 + type: u1 + enum: pad_mode + - size: 5 + - id: ccw # $pos+91 + type: u4 + - id: cen # $pos+95 + type: u1 + - size: 1 + - id: cag # $pos+97 + type: u4 + - id: cpr # $pos+101 + type: u4 + - id: cpc # $pos+105 + type: u4 + - id: pastemaskexpanionmanual + type: s4 + - id: soldermaskexpansionmanual # $pos+113 + type: s4 + - id: cpl # $pos+117 + type: u1 + - size: 6 + - id: pastemaskexpansionmode # $pos+124 + type: u1 + enum: pad_mode_rule + - id: soldermaskexpansionmode # $pos+125 + type: u1 + enum: pad_mode_rule + - size: 3 + - id: holerotation # $pos+129 + type: f8 + - size: 4 + - id: testpoint_assembly_top + type: u1 + enum: boolean + - id: testpoint_assembly_bottom + type: u1 + enum: boolean + + pad_sub6: + seq: + - id: x + type: s4 + repeat: expr + repeat-expr: 29 + - id: y + type: s4 + repeat: expr + repeat-expr: 29 + - id: shape + type: u1 + enum: pad_shape + repeat: expr + repeat-expr: 29 + - size: 1 + - id: hole_type + type: u1 + enum: pad_hole_type + - id: slot_length + type: s4 + - id: slot_rotation + type: f8 + - id: holeoffset_x + type: s4 + repeat: expr + repeat-expr: 32 + - id: holeoffset_y + type: s4 + repeat: expr + repeat-expr: 32 + - size: 1 + - id: shape_alt + type: u1 + enum: pad_shape_alt + repeat: expr + repeat-expr: 32 + - id: corner_radius + type: u1 + repeat: expr + repeat-expr: 32 + - size: 32 + + via: + seq: + - id: sub1_len + type: u4 + - id: data + type: via_sub1 + size: sub1_len + + via_sub1: + seq: + - size: 1 + - id: test_fab_top + type: b1 + - id: tent_bottom + type: b1 + - id: tent_top + type: b1 + - #id: flags_u4 + type: b1 + - #id: flags_u3 + type: b1 + - id: is_not_locked + type: b1 + - #id: flags_u1 + type: b1 + - #id: flags_u0 + type: b1 + - #id: flags2_u7 + type: b1 + - #id: flags2_u6 + type: b1 + - #id: flags2_u5 + type: b1 + - #id: flags2_u4 + type: b1 + - #id: flags2_u3 + type: b1 + - #id: flags2_u2 + type: b1 + - #id: flags2_u1 + type: b1 + - id: test_fab_bottom + type: b1 + - id: net + type: u2 + - size: 2 + - id: component + type: u2 + - size: 4 + - id: pos # 13 + type: xy + - id: diameter # 21 + type: s4 + - id: holesize # 29 + type: s4 + - id: start_layer + type: u1 + enum: layer + - id: end_layer + type: u1 + enum: layer + - size: 43 + - id: via_mode + type: u1 + enum: pad_mode + - id: diameter_alt + type: s4 + repeat: expr + repeat-expr: 32 + + track: + seq: + - id: sub1_len + type: u4 + - id: data + type: track_sub1 + size: sub1_len + + track_sub1: + seq: + - id: layer + type: u1 + enum: layer + - #id: flags_u7 + type: b1 + - #id: flags_u6 + type: b1 + - #id: flags_u5 + type: b1 + - #id: flags_u4 + type: b1 + - #id: flags_u3 + type: b1 + - id: is_not_locked + type: b1 + - id: is_not_polygonoutline + type: b1 + - #id: flags_u0 + type: b1 + - id: is_keepout + type: u1 # KEEPOUT = 2 + - id: net + type: u2 + - id: subpolyindex + type: u2 + - id: component + type: u2 + - size: 4 + - id: start # 13 + type: xy + - id: end # 21 + type: xy + - id: width # 29 + type: s4 + + text: + seq: + - id: sub1_len + type: u4 + - id: properties + type: text_sub1 + size: sub1_len + - id: sub2_len + type: u4 + - id: text + type: text_sub2 + size: sub2_len + + text_sub1: + seq: + - id: layer + type: u1 + - #id: flags_u7 + type: b1 + - #id: flags_u6 + type: b1 + - #id: flags_u5 + type: b1 + - #id: flags_u4 + type: b1 + - #id: flags_u3 + type: b1 + - id: is_not_locked + type: b1 + - #id: flags_u1 + type: b1 + - #id: flags_u0 + type: b1 + - size: 1 + - id: net + type: u2 + - size: 2 + - id: component + type: u2 + - size: 4 + - id: pos + type: xy + - id: height + type: u4 + - id: font_name_id + type: u1 + - size: 1 + - id: rotation + type: f8 + - id: mirrored + type: u1 + enum: boolean + - id: strokewidth + type: u4 + - id: is_comment + type: u1 + enum: boolean + - id: is_designator + type: u1 + enum: boolean + - size: 4 + - id: font_name + size: 64 + type: str # TODO: terminates with [0, 0] + encoding: UTF-16 + - size: 22 + - id: position + type: u1 + enum: text_position + - size: 27 + - id: truetype + type: u1 + enum: boolean + - id: barcode_name + size: 64 + type: str # TODO: terminates with [0, 0] + encoding: UTF-16 + + text_sub2: + seq: + - id: len + type: u1 + - id: name + type: str + size: len + + fill: + seq: + - id: sub1_len + type: u4 + - id: data + type: fill_sub1 + size: sub1_len + + fill_sub1: + seq: + - id: layer + type: u1 + - #id: flags_u7 + type: b1 + - #id: flags_u6 + type: b1 + - #id: flags_u5 + type: b1 + - #id: flags_u4 + type: b1 + - #id: flags_u3 + type: b1 + - id: is_not_locked + type: b1 + - #id: flags_u1 + type: b1 + - #id: flags_u0 + type: b1 + - id: is_keepout + type: u1 # KEEPOUT = 2 + - id: net + type: u2 + - size: 2 + - id: component + type: u2 + - size: 4 + - id: pos1 + type: xy + - id: pos2 + type: xy + - id: rotation + type: f8 + + region: + seq: + - id: sub1_len + type: u4 + - id: data + type: region_sub1 + size: sub1_len + + region_sub1: + seq: + - id: layer + type: u1 + - #id: flags_u7 + type: b1 + - #id: flags_u6 + type: b1 + - #id: flags_u5 + type: b1 + - #id: flags_u4 + type: b1 + - #id: flags_u3 + type: b1 + - id: is_not_locked + type: b1 + - #id: flags_u1 + type: b1 + - #id: flags_u0 + type: b1 + - id: is_keepout + type: u1 # KEEPOUT = 2 + - id: net + type: u2 + - size: 2 + - id: component + type: u2 + - size: 5 + - id: holecount # TODO: check + type: u2 + - size: 2 + - id: propterties_len + type: u4 + - id: properties + size: propterties_len + type: str + - id: vertices_num + type: u4 + - id: vertices # region1 type + repeat: expr + repeat-expr: vertices_num + type: xyf + #- id: vertices2 # region2 type + # repeat: expr + # repeat-expr: vertices_num+1 + # type: xyf2 + + xy: + seq: + - id: x + type: s4 + - id: y + type: s4 + + xyf: # no idea why two different formats? + seq: + - id: x + type: f8 + - id: y + type: f8 + + xyf2: # no idea why two different formats? + seq: + - id: is_round + type: u1 + enum: boolean + - id: position + type: xy + - id: center + type: xy + - id: radius + type: u4 + - id: angle1 + type: f8 + - id: angle2 + type: f8 + +enums: + record_id: + 0x01: arc6 + 0x02: pad6 + 0x03: via6 + 0x04: track6 + 0x05: text6 + 0x06: fill6 + 0x0b: region6 + + boolean: + 0: false + 1: true + + pad_shape: + 0: unknown + 1: circle + 2: rect + 3: octagonal + + pad_shape_alt: + 0: unknown + 1: round + 2: rect + 3: octagonal + 9: roundrectangle + + pad_hole_type: + 0: normal + 1: square + 2: slot + + pad_mode: + 0: simple + 1: top_middle_bottom + 2: full_stack + + pad_mode_rule: + 0: unknown + 1: rule + 2: manual + + text_position: + 1: left_top + 2: left_center + 3: left_bottom + 4: center_top + 5: center_center + 6: center_bottom + 7: right_top + 8: right_center + 9: right_bottom + + layer: + 1: f_cu + 32: b_cu diff --git a/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.cpp b/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.cpp new file mode 100644 index 0000000000..194743331f --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.cpp @@ -0,0 +1,941 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 Thomas Pointhuber + * + * 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 +#include + +#include +#include +#include + +#include "altium_parser.h" +#include "altium_parser_pcb.h" + + +ALTIUM_LAYER altium_layer_from_name( const wxString& aName ) +{ + static const std::unordered_map hash_map = { + { "TOP", ALTIUM_LAYER::TOP_LAYER }, + { "MID1", ALTIUM_LAYER::MID_LAYER_1 }, + { "MID2", ALTIUM_LAYER::MID_LAYER_2 }, + { "MID3", ALTIUM_LAYER::MID_LAYER_3 }, + { "MID4", ALTIUM_LAYER::MID_LAYER_4 }, + { "MID5", ALTIUM_LAYER::MID_LAYER_5 }, + { "MID6", ALTIUM_LAYER::MID_LAYER_6 }, + { "MID7", ALTIUM_LAYER::MID_LAYER_7 }, + { "MID8", ALTIUM_LAYER::MID_LAYER_8 }, + { "MID9", ALTIUM_LAYER::MID_LAYER_9 }, + { "MID10", ALTIUM_LAYER::MID_LAYER_10 }, + { "MID11", ALTIUM_LAYER::MID_LAYER_11 }, + { "MID12", ALTIUM_LAYER::MID_LAYER_12 }, + { "MID13", ALTIUM_LAYER::MID_LAYER_13 }, + { "MID14", ALTIUM_LAYER::MID_LAYER_14 }, + { "MID15", ALTIUM_LAYER::MID_LAYER_15 }, + { "MID16", ALTIUM_LAYER::MID_LAYER_16 }, + { "MID17", ALTIUM_LAYER::MID_LAYER_17 }, + { "MID18", ALTIUM_LAYER::MID_LAYER_18 }, + { "MID19", ALTIUM_LAYER::MID_LAYER_19 }, + { "MID20", ALTIUM_LAYER::MID_LAYER_20 }, + { "MID21", ALTIUM_LAYER::MID_LAYER_21 }, + { "MID22", ALTIUM_LAYER::MID_LAYER_22 }, + { "MID23", ALTIUM_LAYER::MID_LAYER_23 }, + { "MID24", ALTIUM_LAYER::MID_LAYER_24 }, + { "MID25", ALTIUM_LAYER::MID_LAYER_25 }, + { "MID26", ALTIUM_LAYER::MID_LAYER_26 }, + { "MID27", ALTIUM_LAYER::MID_LAYER_27 }, + { "MID28", ALTIUM_LAYER::MID_LAYER_28 }, + { "MID29", ALTIUM_LAYER::MID_LAYER_29 }, + { "MID30", ALTIUM_LAYER::MID_LAYER_30 }, + { "BOTTOM", ALTIUM_LAYER::BOTTOM_LAYER }, + + { "PLANE1", ALTIUM_LAYER::INTERNAL_PLANE_1 }, + { "PLANE2", ALTIUM_LAYER::INTERNAL_PLANE_2 }, + { "PLANE3", ALTIUM_LAYER::INTERNAL_PLANE_3 }, + { "PLANE4", ALTIUM_LAYER::INTERNAL_PLANE_4 }, + { "PLANE5", ALTIUM_LAYER::INTERNAL_PLANE_5 }, + { "PLANE6", ALTIUM_LAYER::INTERNAL_PLANE_6 }, + { "PLANE7", ALTIUM_LAYER::INTERNAL_PLANE_7 }, + { "PLANE8", ALTIUM_LAYER::INTERNAL_PLANE_8 }, + { "PLANE9", ALTIUM_LAYER::INTERNAL_PLANE_9 }, + { "PLANE10", ALTIUM_LAYER::INTERNAL_PLANE_10 }, + { "PLANE11", ALTIUM_LAYER::INTERNAL_PLANE_11 }, + { "PLANE12", ALTIUM_LAYER::INTERNAL_PLANE_12 }, + { "PLANE13", ALTIUM_LAYER::INTERNAL_PLANE_13 }, + { "PLANE14", ALTIUM_LAYER::INTERNAL_PLANE_14 }, + { "PLANE15", ALTIUM_LAYER::INTERNAL_PLANE_15 }, + { "PLANE16", ALTIUM_LAYER::INTERNAL_PLANE_16 }, + + { "MECHANICAL1", ALTIUM_LAYER::MECHANICAL_1 }, + { "MECHANICAL2", ALTIUM_LAYER::MECHANICAL_2 }, + { "MECHANICAL3", ALTIUM_LAYER::MECHANICAL_3 }, + { "MECHANICAL4", ALTIUM_LAYER::MECHANICAL_4 }, + { "MECHANICAL5", ALTIUM_LAYER::MECHANICAL_5 }, + { "MECHANICAL6", ALTIUM_LAYER::MECHANICAL_6 }, + { "MECHANICAL7", ALTIUM_LAYER::MECHANICAL_7 }, + { "MECHANICAL8", ALTIUM_LAYER::MECHANICAL_8 }, + { "MECHANICAL9", ALTIUM_LAYER::MECHANICAL_9 }, + { "MECHANICAL10", ALTIUM_LAYER::MECHANICAL_10 }, + { "MECHANICAL11", ALTIUM_LAYER::MECHANICAL_11 }, + { "MECHANICAL12", ALTIUM_LAYER::MECHANICAL_12 }, + { "MECHANICAL13", ALTIUM_LAYER::MECHANICAL_13 }, + { "MECHANICAL14", ALTIUM_LAYER::MECHANICAL_14 }, + { "MECHANICAL15", ALTIUM_LAYER::MECHANICAL_15 }, + { "MECHANICAL16", ALTIUM_LAYER::MECHANICAL_16 }, + }; + + auto it = hash_map.find( std::string( aName.c_str() ) ); + if( it == hash_map.end() ) + { + wxLogError( wxString::Format( + _( "Unknown mapping of the Altium layer '%s'. Please report as issue." ), aName ) ); + return ALTIUM_LAYER::UNKNOWN; + } + else + { + return it->second; + } +} + +void altium_parse_polygons( + std::map& aProperties, std::vector& aVertices ) +{ + for( size_t i = 0; i < std::numeric_limits::max(); i++ ) + { + const wxString si = std::to_string( i ); + + const wxString vxi = "VX" + si; + const wxString vyi = "VY" + si; + + if( aProperties.find( vxi ) == aProperties.end() + || aProperties.find( vyi ) == aProperties.end() ) + { + break; // it doesn't seem like we know beforehand how many vertices are inside a polygon + } + + const bool isRound = ALTIUM_PARSER::PropertiesReadInt( aProperties, "KIND" + si, 0 ) != 0; + const int32_t radius = + ALTIUM_PARSER::PropertiesReadKicadUnit( aProperties, "R" + si, "0mil" ); + const double sa = ALTIUM_PARSER::PropertiesReadDouble( aProperties, "SA" + si, 0. ); + const double ea = ALTIUM_PARSER::PropertiesReadDouble( aProperties, "EA" + si, 0. ); + const wxPoint vp = + wxPoint( ALTIUM_PARSER::PropertiesReadKicadUnit( aProperties, vxi, "0mil" ), + -ALTIUM_PARSER::PropertiesReadKicadUnit( aProperties, vyi, "0mil" ) ); + const wxPoint cp = + wxPoint( ALTIUM_PARSER::PropertiesReadKicadUnit( aProperties, "CX" + si, "0mil" ), + -ALTIUM_PARSER::PropertiesReadKicadUnit( aProperties, "CY" + si, "0mil" ) ); + + aVertices.emplace_back( isRound, radius, sa, ea, vp, cp ); + } +} + +ABOARD6::ABOARD6( ALTIUM_PARSER& aReader ) +{ + std::map properties = aReader.ReadProperties(); + if( properties.empty() ) + { + THROW_IO_ERROR( _( "Board6 stream has no properties!" ) ); + } + + /*for (auto & property : properties) { + std::cout << " * '" << property.first << "' = '" << property.second << "'" << std::endl; + }*/ + + sheetpos = wxPoint( ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "SHEETX", "0mil" ), + -ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "SHEETY", "0mil" ) ); + sheetsize = wxSize( ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "SHEETWIDTH", "0mil" ), + ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "SHEETHEIGHT", "0mil" ) ); + + layercount = ALTIUM_PARSER::PropertiesReadInt( properties, "LAYERSETSCOUNT", 1 ) + 1; + + for( size_t i = 1; i < std::numeric_limits::max(); i++ ) + { + const wxString layeri = "LAYER" + std::to_string( i ); + const wxString layername = layeri + "NAME"; + + auto layernameit = properties.find( layername ); + if( layernameit == properties.end() ) + { + break; // it doesn't seem like we know beforehand how many vertices are inside a polygon + } + + ABOARD6_LAYER_STACKUP curlayer; + + curlayer.name = ALTIUM_PARSER::PropertiesReadString( + properties, layername, "" ); // TODO: trim string + curlayer.nextId = ALTIUM_PARSER::PropertiesReadInt( properties, layeri + "NEXT", 0 ); + curlayer.prevId = ALTIUM_PARSER::PropertiesReadInt( properties, layeri + "PREV", 0 ); + curlayer.copperthick = + ALTIUM_PARSER::PropertiesReadKicadUnit( properties, layeri + "COPTHICK", "1.4mil" ); + + curlayer.dielectricconst = + ALTIUM_PARSER::PropertiesReadDouble( properties, layeri + "DIELCONST", 0. ); + curlayer.dielectricthick = ALTIUM_PARSER::PropertiesReadKicadUnit( + properties, layeri + "DIELHEIGHT", "60mil" ); + curlayer.dielectricmaterial = + ALTIUM_PARSER::PropertiesReadString( properties, layeri + "DIELMATERIAL", "FR-4" ); + + stackup.push_back( curlayer ); + } + + altium_parse_polygons( properties, board_vertices ); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Board6 stream was not parsed correctly!" ) ); + } +} + +ACLASS6::ACLASS6( ALTIUM_PARSER& aReader ) +{ + std::map properties = aReader.ReadProperties(); + if( properties.empty() ) + { + THROW_IO_ERROR( _( "Classes6 stream has no properties!" ) ); + } + + name = ALTIUM_PARSER::PropertiesReadString( properties, "NAME", "" ); + uniqueid = ALTIUM_PARSER::PropertiesReadString( properties, "UNIQUEID", "" ); + kind = static_cast( + ALTIUM_PARSER::PropertiesReadInt( properties, "KIND", -1 ) ); + + for( size_t i = 0; i < std::numeric_limits::max(); i++ ) + { + auto mit = properties.find( "M" + std::to_string( i ) ); + if( mit == properties.end() ) + { + break; // it doesn't seem like we know beforehand how many components are in the netclass + } + names.push_back( mit->second ); + } + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Classes6 stream was not parsed correctly!" ) ); + } +} + +ACOMPONENT6::ACOMPONENT6( ALTIUM_PARSER& aReader ) +{ + std::map properties = aReader.ReadProperties(); + if( properties.empty() ) + { + THROW_IO_ERROR( _( "Components6 stream has no properties!" ) ); + } + + layer = altium_layer_from_name( + ALTIUM_PARSER::PropertiesReadString( properties, "LAYER", "" ) ); + position = wxPoint( ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "X", "0mil" ), + -ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "Y", "0mil" ) ); + rotation = ALTIUM_PARSER::PropertiesReadDouble( properties, "ROTATION", 0. ); + locked = ALTIUM_PARSER::PropertiesReadBool( properties, "LOCKED", false ); + nameon = ALTIUM_PARSER::PropertiesReadBool( properties, "NAMEON", true ); + commenton = ALTIUM_PARSER::PropertiesReadBool( properties, "COMMENTON", false ); + sourcedesignator = ALTIUM_PARSER::PropertiesReadString( properties, "SOURCEDESIGNATOR", "" ); + sourcefootprintlibrary = + ALTIUM_PARSER::PropertiesReadString( properties, "SOURCEFOOTPRINTLIBRARY", "" ); + sourcecomponentlibrary = + ALTIUM_PARSER::PropertiesReadString( properties, "SOURCECOMPONENTLIBRARY", "" ); + sourcelibreference = + ALTIUM_PARSER::PropertiesReadString( properties, "SOURCELIBREFERENCE", "" ); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Components6 stream was not parsed correctly" ) ); + } +} + +ADIMENSION6::ADIMENSION6( ALTIUM_PARSER& aReader ) +{ + aReader.Skip( 2 ); + + std::map properties = aReader.ReadProperties(); + if( properties.empty() ) + { + THROW_IO_ERROR( _( "Dimensions6 stream has no properties" ) ); + } + + layer = altium_layer_from_name( + ALTIUM_PARSER::PropertiesReadString( properties, "LAYER", "" ) ); + kind = static_cast( + ALTIUM_PARSER::PropertiesReadInt( properties, "DIMENSIONKIND", 0 ) ); + + textformat = ALTIUM_PARSER::PropertiesReadString( properties, "TEXTFORMAT", "" ); + + height = ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "HEIGHT", "0mil" ); + angle = ALTIUM_PARSER::PropertiesReadDouble( properties, "ANGLE", 0. ); + + linewidth = ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "LINEWIDTH", "10mil" ); + textheight = ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "TEXTHEIGHT", "10mil" ); + textlinewidth = ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "TEXTLINEWIDTH", "6mil" ); + textprecission = ALTIUM_PARSER::PropertiesReadInt( properties, "TEXTPRECISION", 2 ); + textbold = ALTIUM_PARSER::PropertiesReadBool( properties, "TEXTLINEWIDTH", false ); + textitalic = ALTIUM_PARSER::PropertiesReadBool( properties, "ITALIC", false ); + + arrowsize = ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "ARROWSIZE", "60mil" ); + + xy1 = wxPoint( ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "X1", "0mil" ), + -ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "Y1", "0mil" ) ); + + int refcount = ALTIUM_PARSER::PropertiesReadInt( properties, "REFERENCES_COUNT", 0 ); + for( int i = 0; i < refcount; i++ ) + { + const std::string refi = "REFERENCE" + std::to_string( i ); + referencePoint.emplace_back( + ALTIUM_PARSER::PropertiesReadKicadUnit( properties, refi + "POINTX", "0mil" ), + -ALTIUM_PARSER::PropertiesReadKicadUnit( properties, refi + "POINTY", "0mil" ) ); + } + + for( size_t i = 1; i < std::numeric_limits::max(); i++ ) + { + const std::string texti = "TEXT" + std::to_string( i ); + const std::string textix = texti + "X"; + const std::string textiy = texti + "Y"; + + if( properties.find( textix ) == properties.end() + || properties.find( textiy ) == properties.end() ) + { + break; // it doesn't seem like we know beforehand how many vertices are inside a polygon + } + + textPoint.emplace_back( + ALTIUM_PARSER::PropertiesReadKicadUnit( properties, textix, "0mil" ), + -ALTIUM_PARSER::PropertiesReadKicadUnit( properties, textiy, "0mil" ) ); + } + + wxString dimensionunit = + ALTIUM_PARSER::PropertiesReadString( properties, "TEXTDIMENSIONUNIT", "Millimeters" ); + if( dimensionunit == "Inches" ) + { + textunit = ALTIUM_UNIT::INCHES; + } + else if( dimensionunit == "Mils" ) + { + textunit = ALTIUM_UNIT::MILS; + } + else if( dimensionunit == "Millimeters" ) + { + textunit = ALTIUM_UNIT::MILLIMETERS; + } + else if( dimensionunit == "Centimeters" ) + { + textunit = ALTIUM_UNIT::CENTIMETER; + } + else + { + textunit = ALTIUM_UNIT::UNKNOWN; + } + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Dimensions6 stream was not parsed correctly" ) ); + } +} + +ANET6::ANET6( ALTIUM_PARSER& aReader ) +{ + std::map properties = aReader.ReadProperties(); + if( properties.empty() ) + { + THROW_IO_ERROR( _( "Nets6 stream has no properties" ) ); + } + + name = ALTIUM_PARSER::PropertiesReadString( properties, "NAME", "" ); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Nets6 stream was not parsed correctly" ) ); + } +} + +APOLYGON6::APOLYGON6( ALTIUM_PARSER& aReader ) +{ + std::map properties = aReader.ReadProperties(); + if( properties.empty() ) + { + THROW_IO_ERROR( _( "Polygons6 stream has no properties" ) ); + } + + layer = altium_layer_from_name( + ALTIUM_PARSER::PropertiesReadString( properties, "LAYER", "" ) ); + net = ALTIUM_PARSER::PropertiesReadInt( properties, "NET", ALTIUM_NET_UNCONNECTED ); + locked = ALTIUM_PARSER::PropertiesReadBool( properties, "LOCKED", false ); + + // TODO: kind + + gridsize = ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "GRIDSIZE", "0mil" ); + trackwidth = ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "TRACKWIDTH", "0mil" ); + minprimlength = ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "MINPRIMLENGTH", "0mil" ); + useoctagons = ALTIUM_PARSER::PropertiesReadBool( properties, "USEOCTAGONS", false ); + + wxString hatchstyleraw = ALTIUM_PARSER::PropertiesReadString( properties, "HATCHSTYLE", "" ); + if( hatchstyleraw == "Solid" ) + { + hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::SOLID; + } + else if( hatchstyleraw == "45Degree" ) + { + hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::DEGREE_45; + } + else if( hatchstyleraw == "90Degree" ) + { + hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::DEGREE_90; + } + else if( hatchstyleraw == "Horizontal" ) + { + hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::HORIZONTAL; + } + else if( hatchstyleraw == "Vertical" ) + { + hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::VERTICAL; + } + else if( hatchstyleraw == "None" ) + { + hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::NONE; + } + else + { + hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::UNKNOWN; + } + + altium_parse_polygons( properties, vertices ); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Polygons6 stream was not parsed correctly" ) ); + } +} + +ARULE6::ARULE6( ALTIUM_PARSER& aReader ) +{ + aReader.Skip( 2 ); + + std::map properties = aReader.ReadProperties(); + if( properties.empty() ) + { + THROW_IO_ERROR( _( "Rules6 stream has no properties" ) ); + } + + name = ALTIUM_PARSER::PropertiesReadString( properties, "NAME", "" ); + priority = ALTIUM_PARSER::PropertiesReadInt( properties, "PRIORITY", 1 ); + + scope1expr = ALTIUM_PARSER::PropertiesReadString( properties, "SCOPE1EXPRESSION", "" ); + scope2expr = ALTIUM_PARSER::PropertiesReadString( properties, "SCOPE2EXPRESSION", "" ); + + wxString rulekind = ALTIUM_PARSER::PropertiesReadString( properties, "RULEKIND", "" ); + if( rulekind == "Clearance" ) + { + kind = ALTIUM_RULE_KIND::CLEARANCE; + clearanceGap = ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "GAP", "10mil" ); + } + else if( rulekind == "DiffPairsRouting" ) + { + kind = ALTIUM_RULE_KIND::DIFF_PAIR_ROUTINGS; + } + else if( rulekind == "Height" ) + { + kind = ALTIUM_RULE_KIND::HEIGHT; + } + else if( rulekind == "HoleSize" ) + { + kind = ALTIUM_RULE_KIND::HOLE_SIZE; + } + else if( rulekind == "HoleToHoleClearance" ) + { + kind = ALTIUM_RULE_KIND::HOLE_TO_HOLE_CLEARANCE; + } + else if( rulekind == "Width" ) + { + kind = ALTIUM_RULE_KIND::WIDTH; + } + else if( rulekind == "PasteMaskExpansion" ) + { + kind = ALTIUM_RULE_KIND::PASTE_MASK_EXPANSION; + } + else if( rulekind == "PlaneClearance" ) + { + kind = ALTIUM_RULE_KIND::PLANE_CLEARANCE; + planeclearanceClearance = + ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "CLEARANCE", "10mil" ); + } + else if( rulekind == "PolygonConnect" ) + { + kind = ALTIUM_RULE_KIND::POLYGON_CONNECT; + polygonconnectAirgapwidth = + ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "AIRGAPWIDTH", "10mil" ); + polygonconnectReliefconductorwidth = ALTIUM_PARSER::PropertiesReadKicadUnit( + properties, "RELIEFCONDUCTORWIDTH", "10mil" ); + polygonconnectReliefentries = + ALTIUM_PARSER::PropertiesReadInt( properties, "RELIEFENTRIES", 4 ); + } + else + { + kind = ALTIUM_RULE_KIND::UNKNOWN; + } + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Rules6 stream was not parsed correctly" ) ); + } +} + +AARC6::AARC6( ALTIUM_PARSER& aReader ) +{ + ALTIUM_RECORD recordtype = static_cast( aReader.Read() ); + if( recordtype != ALTIUM_RECORD::ARC ) + { + THROW_IO_ERROR( _( "Arcs6 stream has invalid recordtype" ) ); + } + + // Subrecord 1 + aReader.ReadAndSetSubrecordLength(); + + layer = static_cast( aReader.Read() ); + + uint8_t flags1 = aReader.Read(); + is_locked = ( flags1 & 0x04 ) == 0; + is_polygonoutline = ( flags1 & 0x02 ) != 0; + + uint8_t flags2 = aReader.Read(); + is_keepout = flags2 == 2; + + net = aReader.Read(); + subpolyindex = aReader.Read(); + component = aReader.Read(); + aReader.Skip( 4 ); + center = aReader.ReadWxPoint(); + radius = aReader.ReadKicadUnit(); + startangle = aReader.Read(); + endangle = aReader.Read(); + width = aReader.ReadKicadUnit(); + + aReader.SkipSubrecord(); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Arcs6 stream was not parsed correctly" ) ); + } +} + +APAD6::APAD6( ALTIUM_PARSER& aReader ) +{ + ALTIUM_RECORD recordtype = static_cast( aReader.Read() ); + if( recordtype != ALTIUM_RECORD::PAD ) + { + THROW_IO_ERROR( _( "Pads6 stream has invalid recordtype" ) ); + } + + // Subrecord 1 + size_t subrecord1 = aReader.ReadAndSetSubrecordLength(); + if( subrecord1 == 0 ) + { + THROW_IO_ERROR( _( "Pads6 stream has no subrecord1 data" ) ); + } + name = aReader.ReadWxString(); + if( aReader.GetRemainingSubrecordBytes() != 0 ) + { + THROW_IO_ERROR( _( "Pads6 stream has invalid subrecord1 length" ) ); + } + aReader.SkipSubrecord(); + + // Subrecord 2 + aReader.ReadAndSetSubrecordLength(); + aReader.SkipSubrecord(); + + // Subrecord 3 + aReader.ReadAndSetSubrecordLength(); + aReader.SkipSubrecord(); + + // Subrecord 4 + aReader.ReadAndSetSubrecordLength(); + aReader.SkipSubrecord(); + + // Subrecord 5 + size_t subrecord5 = aReader.ReadAndSetSubrecordLength(); + if( subrecord5 < 120 ) + { + THROW_IO_ERROR( _( + "Pads6 stream subrecord has length < 120, which is unexpected" ) ); // TODO: exact minimum length we know? + } + + layer = static_cast( aReader.Read() ); + + uint8_t flags1 = aReader.Read(); + is_test_fab_top = ( flags1 & 0x80 ) != 0; + is_tent_bottom = ( flags1 & 0x40 ) != 0; + is_tent_top = ( flags1 & 0x20 ) != 0; + is_locked = ( flags1 & 0x04 ) == 0; + + uint8_t flags2 = aReader.Read(); + is_test_fab_bottom = ( flags2 & 0x01 ) != 0; + + net = aReader.Read(); + aReader.Skip( 2 ); + component = aReader.Read(); + aReader.Skip( 4 ); + + position = aReader.ReadWxPoint(); + topsize = aReader.ReadWxSize(); + midsize = aReader.ReadWxSize(); + botsize = aReader.ReadWxSize(); + holesize = aReader.ReadKicadUnit(); + + topshape = static_cast( aReader.Read() ); + midshape = static_cast( aReader.Read() ); + botshape = static_cast( aReader.Read() ); + + direction = aReader.Read(); + plated = aReader.Read() != 0; + aReader.Skip( 1 ); + padmode = static_cast( aReader.Read() ); + aReader.Skip( 23 ); + pastemaskexpansionmanual = aReader.ReadKicadUnit(); + soldermaskexpansionmanual = aReader.ReadKicadUnit(); + aReader.Skip( 7 ); + pastemaskexpansionmode = static_cast( aReader.Read() ); + soldermaskexpansionmode = static_cast( aReader.Read() ); + aReader.Skip( 3 ); + holerotation = aReader.Read(); + if( subrecord5 == 120 ) + { + tolayer = static_cast( aReader.Read() ); + aReader.Skip( 2 ); + fromlayer = static_cast( aReader.Read() ); + //aReader.skip( 2 ); + } + else if( subrecord5 == 171 ) + { + } + aReader.SkipSubrecord(); + + // Subrecord 6 + size_t subrecord6 = aReader.ReadAndSetSubrecordLength(); + if( subrecord6 == 651 + || subrecord6 == 628 ) // TODO: better detection mechanism (Altium 14 = 628) + { // TODO: detect type from something else than the size? + sizeAndShape = std::make_unique(); + + for( int i = 0; i < 29; i++ ) + { + sizeAndShape->inner_size[i].x = aReader.ReadKicadUnitX(); + } + for( int i = 0; i < 29; i++ ) + { + sizeAndShape->inner_size[i].y = aReader.ReadKicadUnitY(); + } + + for( int i = 0; i < 29; i++ ) + { + sizeAndShape->inner_shape[i] = static_cast( aReader.Read() ); + } + + aReader.Skip( 1 ); + + sizeAndShape->holeshape = static_cast( aReader.Read() ); + sizeAndShape->slotsize = aReader.ReadKicadUnit(); + sizeAndShape->slotrotation = aReader.Read(); + + for( int i = 0; i < 32; i++ ) + { + sizeAndShape->holeoffset[i].x = aReader.ReadKicadUnitX(); + } + for( int i = 0; i < 32; i++ ) + { + sizeAndShape->holeoffset[i].y = aReader.ReadKicadUnitY(); + } + + aReader.Skip( 1 ); + + for( int i = 0; i < 32; i++ ) + { + sizeAndShape->alt_shape[i] = + static_cast( aReader.Read() ); + } + + for( int i = 0; i < 32; i++ ) + { + sizeAndShape->cornerradius[i] = aReader.Read(); + } + } + + aReader.SkipSubrecord(); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Pads6 stream was not parsed correctly" ) ); + } +} + +AVIA6::AVIA6( ALTIUM_PARSER& aReader ) +{ + ALTIUM_RECORD recordtype = static_cast( aReader.Read() ); + if( recordtype != ALTIUM_RECORD::VIA ) + { + THROW_IO_ERROR( _( "Vias6 stream has invalid recordtype" ) ); + } + + // Subrecord 1 + aReader.ReadAndSetSubrecordLength(); + + aReader.Skip( 1 ); + + uint8_t flags1 = aReader.Read(); + is_test_fab_top = ( flags1 & 0x80 ) != 0; + is_tent_bottom = ( flags1 & 0x40 ) != 0; + is_tent_top = ( flags1 & 0x20 ) != 0; + is_locked = ( flags1 & 0x04 ) == 0; + + uint8_t flags2 = aReader.Read(); + is_test_fab_bottom = ( flags2 & 0x01 ) != 0; + + net = aReader.Read(); + aReader.Skip( 8 ); + position = aReader.ReadWxPoint(); + diameter = aReader.ReadKicadUnit(); + holesize = aReader.ReadKicadUnit(); + + layer_start = static_cast( aReader.Read() ); + layer_end = static_cast( aReader.Read() ); + aReader.Skip( 43 ); + viamode = static_cast( aReader.Read() ); + + aReader.SkipSubrecord(); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Vias6 stream was not parsed correctly" ) ); + } +} + +ATRACK6::ATRACK6( ALTIUM_PARSER& aReader ) +{ + ALTIUM_RECORD recordtype = static_cast( aReader.Read() ); + if( recordtype != ALTIUM_RECORD::TRACK ) + { + THROW_IO_ERROR( _( "Tracks6 stream has invalid recordtype" ) ); + } + + // Subrecord 1 + aReader.ReadAndSetSubrecordLength(); + + layer = static_cast( aReader.Read() ); + + uint8_t flags1 = aReader.Read(); + is_locked = ( flags1 & 0x04 ) == 0; + is_polygonoutline = ( flags1 & 0x02 ) != 0; + + uint8_t flags2 = aReader.Read(); + is_keepout = flags2 == 2; + + net = aReader.Read(); + subpolyindex = aReader.Read(); + component = aReader.Read(); + aReader.Skip( 4 ); + start = aReader.ReadWxPoint(); + end = aReader.ReadWxPoint(); + width = aReader.ReadKicadUnit(); + + aReader.SkipSubrecord(); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Tracks6 stream was not parsed correctly" ) ); + } +} + +ATEXT6::ATEXT6( ALTIUM_PARSER& aReader ) +{ + ALTIUM_RECORD recordtype = static_cast( aReader.Read() ); + if( recordtype != ALTIUM_RECORD::TEXT ) + { + THROW_IO_ERROR( _( "Texts6 stream has invalid recordtype" ) ); + } + + // Subrecord 1 - Properties + size_t subrecord1 = aReader.ReadAndSetSubrecordLength(); + + layer = static_cast( aReader.Read() ); + aReader.Skip( 6 ); + component = aReader.Read(); + aReader.Skip( 4 ); + position = aReader.ReadWxPoint(); + height = aReader.ReadKicadUnit(); + aReader.Skip( 2 ); + rotation = aReader.Read(); + mirrored = aReader.Read() != 0; + strokewidth = aReader.ReadKicadUnit(); + isComment = aReader.Read() != 0; + isDesignator = aReader.Read() != 0; + aReader.Skip( 90 ); + textposition = static_cast( aReader.Read() ); + /** + * In Altium 14 (subrecord1 == 230) only left bottom is valid? I think there is a bit missing. + * https://gitlab.com/kicad/code/kicad/merge_requests/60#note_274913397 + */ + if( subrecord1 <= 230 ) + { + textposition = ALTIUM_TEXT_POSITION::LEFT_BOTTOM; + } + aReader.Skip( 27 ); + isTruetype = aReader.Read() != 0; + + aReader.SkipSubrecord(); + + // Subrecord 2 - String + aReader.ReadAndSetSubrecordLength(); + + text = aReader.ReadWxString(); // TODO: what about strings with length > 255? + + aReader.SkipSubrecord(); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Texts6 stream was not parsed correctly" ) ); + } +} + +AFILL6::AFILL6( ALTIUM_PARSER& aReader ) +{ + ALTIUM_RECORD recordtype = static_cast( aReader.Read() ); + if( recordtype != ALTIUM_RECORD::FILL ) + { + THROW_IO_ERROR( _( "Fills6 stream has invalid recordtype" ) ); + } + + // Subrecord 1 + aReader.ReadAndSetSubrecordLength(); + + layer = static_cast( aReader.Read() ); + + uint8_t flags1 = aReader.Read(); + is_locked = ( flags1 & 0x04 ) == 0; + + uint8_t flags2 = aReader.Read(); + is_keepout = flags2 == 2; + + net = aReader.Read(); + aReader.Skip( 2 ); + component = aReader.Read(); + aReader.Skip( 4 ); + pos1 = aReader.ReadWxPoint(); + pos2 = aReader.ReadWxPoint(); + rotation = aReader.Read(); + + aReader.SkipSubrecord(); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Fills6 stream was not parsed correctly" ) ); + } +} + +AREGION6::AREGION6( ALTIUM_PARSER& aReader, bool aExtendedVertices ) +{ + ALTIUM_RECORD recordtype = static_cast( aReader.Read() ); + if( recordtype != ALTIUM_RECORD::REGION ) + { + THROW_IO_ERROR( _( "Regions6 stream has invalid recordtype" ) ); + } + + // Subrecord 1 + aReader.ReadAndSetSubrecordLength(); + + layer = static_cast( aReader.Read() ); + + uint8_t flags1 = aReader.Read(); + is_locked = ( flags1 & 0x04 ) == 0; + + uint8_t flags2 = aReader.Read(); + is_keepout = flags2 == 2; + + net = aReader.Read(); + aReader.Skip( 2 ); + component = aReader.Read(); + aReader.Skip( 9 ); + + std::map properties = aReader.ReadProperties(); + if( properties.empty() ) + { + THROW_IO_ERROR( _( "Regions6 stream has empty properties" ) ); + } + + int pkind = ALTIUM_PARSER::PropertiesReadInt( properties, "KIND", 0 ); + bool is_cutout = ALTIUM_PARSER::PropertiesReadBool( properties, "ISBOARDCUTOUT", false ); + + is_shapebased = ALTIUM_PARSER::PropertiesReadBool( properties, "ISSHAPEBASED", false ); + + subpolyindex = static_cast( + ALTIUM_PARSER::PropertiesReadInt( properties, "SUBPOLYINDEX", ALTIUM_POLYGON_NONE ) ); + + switch( pkind ) + { + case 0: + if( is_cutout ) + { + kind = ALTIUM_REGION_KIND::BOARD_CUTOUT; + } + else + { + kind = ALTIUM_REGION_KIND::COPPER; + } + break; + case 1: + kind = ALTIUM_REGION_KIND::POLYGON_CUTOUT; + break; + case 4: + kind = ALTIUM_REGION_KIND::CAVITY_DEFINITION; + break; + default: + kind = ALTIUM_REGION_KIND::UNKNOWN; + break; + } + + uint32_t num_vertices = aReader.Read(); + + for( uint32_t i = 0; i < num_vertices; i++ ) + { + if( aExtendedVertices ) + { + bool isRound = aReader.Read() != 0; + wxPoint position = aReader.ReadWxPoint(); + wxPoint center = aReader.ReadWxPoint(); + int32_t radius = aReader.ReadKicadUnit(); + double angle1 = aReader.Read(); + double angle2 = aReader.Read(); + vertices.emplace_back( isRound, radius, angle1, angle2, position, center ); + } + else + { + // For some regions the coordinates are stored as double and not as int32_t + int32_t x = ALTIUM_PARSER::ConvertToKicadUnit( aReader.Read() ); + int32_t y = ALTIUM_PARSER::ConvertToKicadUnit( -aReader.Read() ); + vertices.emplace_back( wxPoint( x, y ) ); + } + } + + aReader.SkipSubrecord(); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( _( "Regions6 stream was not parsed correctly" ) ); + } +} diff --git a/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.h b/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.h new file mode 100644 index 0000000000..c89ec99804 --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.h @@ -0,0 +1,616 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 Thomas Pointhuber + * + * 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 ALTIUM_PARSER_PCB_H +#define ALTIUM_PARSER_PCB_H + +#include +#include +#include +#include + +#include + +// tthis constant specifies an unconnected net +const uint16_t ALTIUM_NET_UNCONNECTED = std::numeric_limits::max(); + +// this constant specifies a item which is not inside an component +const uint16_t ALTIUM_COMPONENT_NONE = std::numeric_limits::max(); + +// this constant specifies a item which does not define a polygon +const uint16_t ALTIUM_POLYGON_NONE = std::numeric_limits::max(); + + +enum class ALTIUM_UNIT +{ + UNKNOWN = 0, + + INCHES = 1, + MILS = 2, + MILLIMETERS = 3, + CENTIMETER = 4 +}; + +enum class ALTIUM_CLASS_KIND +{ + UNKNOWN = -1, + + NET_CLASS = 0, + SOURCE_SCHEMATIC_CLASS = 1, + FROM_TO = 2, + PAD_CLASS = 3, + LAYER_CLASS = 4, + UNKNOWN_CLASS = 5, + DIFF_PAIR_CLASS = 6, + POLYGON_CLASS = 7 +}; + +enum class ALTIUM_DIMENSION_KIND +{ + UNKNOWN = 0, + + LINEAR = 1, + ANGULAR = 2, + RADIAL = 3, + LEADER = 4, + DATUM = 5, + BASELINE = 6, + CENTER = 7, + LINEAR_DIAMETER = 8, + RADIAL_DIAMETER = 9 +}; + +enum class ALTIUM_REGION_KIND +{ + UNKNOWN = -1, + + COPPER = 0, // KIND=0 + POLYGON_CUTOUT = 1, // KIND=1 + BOARD_CUTOUT = 2, // KIND=0 AND ISBOARDCUTOUT=TRUE + CAVITY_DEFINITION = 3, // KIND=4 +}; + +enum class ALTIUM_RULE_KIND +{ + UNKNOWN = 0, + + CLEARANCE = 1, + DIFF_PAIR_ROUTINGS = 2, + HEIGHT = 3, + HOLE_SIZE = 4, + HOLE_TO_HOLE_CLEARANCE = 5, + WIDTH = 6, + PASTE_MASK_EXPANSION = 7, + PLANE_CLEARANCE = 8, + POLYGON_CONNECT = 9, +}; + +enum class ALTIUM_RECORD +{ + ARC = 1, + PAD = 2, + VIA = 3, + TRACK = 4, + TEXT = 5, + FILL = 6, + REGION = 11, + MODEL = 12 +}; + +enum class ALTIUM_PAD_SHAPE +{ + UNKNOWN = 0, + CIRCLE = 1, + RECT = 2, + OCTAGONAL = 3 +}; + +enum class ALTIUM_PAD_SHAPE_ALT +{ + UNKNOWN = 0, + CIRCLE = 1, + RECT = 2, // TODO: valid? + OCTAGONAL = 3, // TODO: valid? + ROUNDRECT = 9 +}; + +enum class ALTIUM_PAD_HOLE_SHAPE +{ + UNKNOWN = -1, + ROUND = 0, + SQUARE = 1, + SLOT = 2 +}; + +enum class ALTIUM_PAD_MODE +{ + SIMPLE = 0, + TOP_MIDDLE_BOTTOM = 1, + FULL_STACK = 2 +}; + +enum class ALTIUM_PAD_RULE +{ + UNKNOWN = 0, + RULE = 1, + MANUAL = 2 +}; + +enum class ALTIUM_POLYGON_HATCHSTYLE +{ + UNKNOWN = 0, + + SOLID = 1, + DEGREE_45 = 2, + DEGREE_90 = 3, + HORIZONTAL = 4, + VERTICAL = 5, + NONE = 6 +}; + +enum class ALTIUM_TEXT_POSITION +{ + LEFT_TOP = 1, + LEFT_CENTER = 2, + LEFT_BOTTOM = 3, + CENTER_TOP = 4, + CENTER_CENTER = 5, + CENTER_BOTTOM = 6, + RIGHT_TOP = 7, + RIGHT_CENTER = 8, + RIGHT_BOTTOM = 9 +}; + +struct ALTIUM_VERTICE +{ + const bool isRound; + const int32_t radius; + const double startangle; + const double endangle; + const wxPoint position; + const wxPoint center; + + explicit ALTIUM_VERTICE( const wxPoint aPosition ) + : isRound( false ), + radius( 0 ), + startangle( 0. ), + endangle( 0. ), + position( aPosition ), + center( wxPoint( 0, 0 ) ) + { + } + + explicit ALTIUM_VERTICE( bool aIsRound, int32_t aRadius, double aStartAngle, double aEndAngle, + const wxPoint aPosition, const wxPoint aCenter ) + : isRound( aIsRound ), + radius( aRadius ), + startangle( aStartAngle ), + endangle( aEndAngle ), + position( aPosition ), + center( aCenter ) + { + } +}; + +enum class ALTIUM_LAYER +{ + UNKNOWN = 0, + + TOP_LAYER = 1, + MID_LAYER_1 = 2, + MID_LAYER_2 = 3, + MID_LAYER_3 = 4, + MID_LAYER_4 = 5, + MID_LAYER_5 = 6, + MID_LAYER_6 = 7, + MID_LAYER_7 = 8, + MID_LAYER_8 = 9, + MID_LAYER_9 = 10, + MID_LAYER_10 = 11, + MID_LAYER_11 = 12, + MID_LAYER_12 = 13, + MID_LAYER_13 = 14, + MID_LAYER_14 = 15, + MID_LAYER_15 = 16, + MID_LAYER_16 = 17, + MID_LAYER_17 = 18, + MID_LAYER_18 = 19, + MID_LAYER_19 = 20, + MID_LAYER_20 = 21, + MID_LAYER_21 = 22, + MID_LAYER_22 = 23, + MID_LAYER_23 = 24, + MID_LAYER_24 = 25, + MID_LAYER_25 = 26, + MID_LAYER_26 = 27, + MID_LAYER_27 = 28, + MID_LAYER_28 = 29, + MID_LAYER_29 = 30, + MID_LAYER_30 = 31, + BOTTOM_LAYER = 32, + + TOP_OVERLAY = 33, + BOTTOM_OVERLAY = 34, + TOP_PASTE = 35, + BOTTOM_PASTE = 36, + TOP_SOLDER = 37, + BOTTOM_SOLDER = 38, + + INTERNAL_PLANE_1 = 39, + INTERNAL_PLANE_2 = 40, + INTERNAL_PLANE_3 = 41, + INTERNAL_PLANE_4 = 42, + INTERNAL_PLANE_5 = 43, + INTERNAL_PLANE_6 = 44, + INTERNAL_PLANE_7 = 45, + INTERNAL_PLANE_8 = 46, + INTERNAL_PLANE_9 = 47, + INTERNAL_PLANE_10 = 48, + INTERNAL_PLANE_11 = 49, + INTERNAL_PLANE_12 = 50, + INTERNAL_PLANE_13 = 51, + INTERNAL_PLANE_14 = 52, + INTERNAL_PLANE_15 = 53, + INTERNAL_PLANE_16 = 54, + + DRILL_GUIDE = 55, + KEEP_OUT_LAYER = 56, + + MECHANICAL_1 = 57, + MECHANICAL_2 = 58, + MECHANICAL_3 = 59, + MECHANICAL_4 = 60, + MECHANICAL_5 = 61, + MECHANICAL_6 = 62, + MECHANICAL_7 = 63, + MECHANICAL_8 = 64, + MECHANICAL_9 = 65, + MECHANICAL_10 = 66, + MECHANICAL_11 = 67, + MECHANICAL_12 = 68, + MECHANICAL_13 = 69, + MECHANICAL_14 = 70, + MECHANICAL_15 = 71, + MECHANICAL_16 = 72, + + DRILL_DRAWING = 73, + MULTI_LAYER = 74, + CONNECTIONS = 75, + BACKGROUND = 76, + DRC_ERROR_MARKERS = 77, + SELECTIONS = 78, + VISIBLE_GRID_1 = 79, + VISIBLE_GRID_2 = 80, + PAD_HOLES = 81, + VIA_HOLES = 82, +}; + +class ALTIUM_PARSER; + +struct ABOARD6_LAYER_STACKUP +{ + wxString name; + + size_t nextId; + size_t prevId; + + int32_t copperthick; + + double dielectricconst; + int32_t dielectricthick; + wxString dielectricmaterial; +}; + +struct ABOARD6 +{ + wxPoint sheetpos; + wxSize sheetsize; + + int layercount; + std::vector stackup; + + std::vector board_vertices; + + explicit ABOARD6( ALTIUM_PARSER& aReader ); +}; + +struct ACLASS6 +{ + wxString name; + wxString uniqueid; + + ALTIUM_CLASS_KIND kind; + + std::vector names; + + explicit ACLASS6( ALTIUM_PARSER& aReader ); +}; + +struct ACOMPONENT6 +{ + ALTIUM_LAYER layer; + wxPoint position; + double rotation; + bool locked; + bool nameon; + bool commenton; + wxString sourcedesignator; + wxString sourcefootprintlibrary; + wxString sourcecomponentlibrary; + wxString sourcelibreference; + explicit ACOMPONENT6( ALTIUM_PARSER& aReader ); +}; + +struct ADIMENSION6 +{ + ALTIUM_LAYER layer; + ALTIUM_DIMENSION_KIND kind; + + wxString textformat; + + int32_t height; + double angle; + + uint32_t linewidth; + uint32_t textheight; + uint32_t textlinewidth; + int32_t textprecission; + bool textbold; + bool textitalic; + + int32_t arrowsize; + + ALTIUM_UNIT textunit; + + wxPoint xy1; + + std::vector referencePoint; + std::vector textPoint; + + explicit ADIMENSION6( ALTIUM_PARSER& aReader ); +}; + +struct ANET6 +{ + wxString name; + + explicit ANET6( ALTIUM_PARSER& aReader ); +}; + +struct APOLYGON6 +{ + ALTIUM_LAYER layer; + uint16_t net; + bool locked; + + ALTIUM_POLYGON_HATCHSTYLE hatchstyle; + + int32_t gridsize; + int32_t trackwidth; + int32_t minprimlength; + bool useoctagons; + + std::vector vertices; + + explicit APOLYGON6( ALTIUM_PARSER& aReader ); +}; + + +struct ARULE6 +{ + wxString name; + int priority; + + ALTIUM_RULE_KIND kind; + + wxString scope1expr; + wxString scope2expr; + + // ALTIUM_RULE_KIND::CLEARANCE + int clearanceGap; + + // ALTIUM_RULE_KIND::PLANE_CLEARANCE + int planeclearanceClearance; + + // ALTIUM_RULE_KIND::POLYGON_CONNECT + int32_t polygonconnectAirgapwidth; + int32_t polygonconnectReliefconductorwidth; + int polygonconnectReliefentries; + + // TODO: implement different types of rules we need to parse + + explicit ARULE6( ALTIUM_PARSER& aReader ); +}; + +struct AREGION6 +{ + bool is_locked; + bool is_keepout; + + bool is_shapebased; + + ALTIUM_LAYER layer; + uint16_t net; + uint16_t component; + uint16_t subpolyindex; + + ALTIUM_REGION_KIND kind; // I asume this means if normal or keepout? + + std::vector vertices; + + explicit AREGION6( ALTIUM_PARSER& aReader, bool aExtendedVertices ); +}; + +struct AARC6 +{ + bool is_locked; + bool is_keepout; + bool is_polygonoutline; + + ALTIUM_LAYER layer; + uint16_t net; + uint16_t component; + uint16_t subpolyindex; + + wxPoint center; + uint32_t radius; + double startangle; + double endangle; + uint32_t width; + + explicit AARC6( ALTIUM_PARSER& aReader ); +}; + +struct APAD6_SIZE_AND_SHAPE +{ + ALTIUM_PAD_HOLE_SHAPE holeshape; + uint32_t slotsize; + double slotrotation; + + wxSize inner_size[29]; + ALTIUM_PAD_SHAPE inner_shape[29]; + wxPoint holeoffset[32]; + ALTIUM_PAD_SHAPE_ALT alt_shape[32]; + uint8_t cornerradius[32]; +}; + +struct APAD6 +{ + bool is_locked; + bool is_tent_top; + bool is_tent_bottom; + bool is_test_fab_top; + bool is_test_fab_bottom; + + wxString name; + + ALTIUM_LAYER layer; + uint16_t net; + uint16_t component; + + wxPoint position; + wxSize topsize; + wxSize midsize; + wxSize botsize; + uint32_t holesize; + + ALTIUM_PAD_SHAPE topshape; + ALTIUM_PAD_SHAPE midshape; + ALTIUM_PAD_SHAPE botshape; + + ALTIUM_PAD_MODE padmode; + + double direction; + bool plated; + ALTIUM_PAD_RULE pastemaskexpansionmode; + int32_t pastemaskexpansionmanual; + ALTIUM_PAD_RULE soldermaskexpansionmode; + int32_t soldermaskexpansionmanual; + double holerotation; + + ALTIUM_LAYER tolayer; + ALTIUM_LAYER fromlayer; + + std::unique_ptr sizeAndShape; + + explicit APAD6( ALTIUM_PARSER& aReader ); +}; + +struct AVIA6 +{ + bool is_locked; + bool is_tent_top; + bool is_tent_bottom; + bool is_test_fab_top; + bool is_test_fab_bottom; + + uint16_t net; + + wxPoint position; + uint32_t diameter; + uint32_t holesize; + + ALTIUM_LAYER layer_start; + ALTIUM_LAYER layer_end; + ALTIUM_PAD_MODE viamode; + + explicit AVIA6( ALTIUM_PARSER& aReader ); +}; + +struct ATRACK6 +{ + bool is_locked; + bool is_keepout; + bool is_polygonoutline; + + ALTIUM_LAYER layer; + uint16_t net; + uint16_t component; + uint16_t subpolyindex; + + wxPoint start; + wxPoint end; + uint32_t width; + + explicit ATRACK6( ALTIUM_PARSER& aReader ); +}; + +struct ATEXT6 +{ + ALTIUM_LAYER layer; + uint16_t component; + + wxPoint position; + uint32_t height; + double rotation; + uint32_t strokewidth; + ALTIUM_TEXT_POSITION textposition; + bool mirrored; + + bool isComment; + bool isDesignator; + bool isTruetype; + + wxString text; + + explicit ATEXT6( ALTIUM_PARSER& aReader ); +}; + +struct AFILL6 +{ + bool is_locked; + bool is_keepout; + + ALTIUM_LAYER layer; + uint16_t component; + uint16_t net; + + wxPoint pos1; + wxPoint pos2; + double rotation; + + explicit AFILL6( ALTIUM_PARSER& aReader ); +}; + + +#endif //ALTIUM_PARSER_PCB_H \ No newline at end of file diff --git a/pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp b/pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp new file mode 100644 index 0000000000..73eb70e5ad --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp @@ -0,0 +1,2011 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019-2020 Thomas Pointhuber + * + * 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 "altium_pcb.h" +#include "altium_parser.h" +#include "altium_parser_pcb.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + + +void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName, + const std::map& aFileMapping ) +{ + // Open file + FILE* fp = wxFopen( aFileName, "rb" ); + if( fp == nullptr ) + { + wxLogError( wxString::Format( _( "Cannot open file '%s'" ), aFileName ) ); + return; + } + + fseek( fp, 0, SEEK_END ); + size_t len = ftell( fp ); + std::unique_ptr buffer( new unsigned char[len] ); + fseek( fp, 0, SEEK_SET ); + + size_t bytesRead = fread( buffer.get(), sizeof( unsigned char ), len, fp ); + fclose( fp ); + if( len != bytesRead ) + { + THROW_IO_ERROR( "Reading error" ); + } + + try + { + CFB::CompoundFileReader reader( buffer.get(), bytesRead ); + + // Parse File + ALTIUM_PCB pcb( aBoard ); + pcb.Parse( reader, aFileMapping ); + } + catch( CFB::CFBException& exception ) + { + THROW_IO_ERROR( exception.what() ); + } +} + + +bool IsAltiumLayerAPlane( ALTIUM_LAYER aLayer ) +{ + return aLayer >= ALTIUM_LAYER::INTERNAL_PLANE_1 && aLayer <= ALTIUM_LAYER::INTERNAL_PLANE_16; +} + + +PCB_LAYER_ID ALTIUM_PCB::GetKicadLayer( ALTIUM_LAYER aAltiumLayer ) const +{ + auto override = m_layermap.find( aAltiumLayer ); + if( override != m_layermap.end() ) + { + return override->second; + } + + switch( aAltiumLayer ) + { + case ALTIUM_LAYER::UNKNOWN: + return UNDEFINED_LAYER; + + case ALTIUM_LAYER::TOP_LAYER: + return F_Cu; + case ALTIUM_LAYER::MID_LAYER_1: + return In1_Cu; // TODO: stackup same as in KiCad? + case ALTIUM_LAYER::MID_LAYER_2: + return In2_Cu; + case ALTIUM_LAYER::MID_LAYER_3: + return In3_Cu; + case ALTIUM_LAYER::MID_LAYER_4: + return In4_Cu; + case ALTIUM_LAYER::MID_LAYER_5: + return In5_Cu; + case ALTIUM_LAYER::MID_LAYER_6: + return In6_Cu; + case ALTIUM_LAYER::MID_LAYER_7: + return In7_Cu; + case ALTIUM_LAYER::MID_LAYER_8: + return In8_Cu; + case ALTIUM_LAYER::MID_LAYER_9: + return In9_Cu; + case ALTIUM_LAYER::MID_LAYER_10: + return In10_Cu; + case ALTIUM_LAYER::MID_LAYER_11: + return In11_Cu; + case ALTIUM_LAYER::MID_LAYER_12: + return In12_Cu; + case ALTIUM_LAYER::MID_LAYER_13: + return In13_Cu; + case ALTIUM_LAYER::MID_LAYER_14: + return In14_Cu; + case ALTIUM_LAYER::MID_LAYER_15: + return In15_Cu; + case ALTIUM_LAYER::MID_LAYER_16: + return In16_Cu; + case ALTIUM_LAYER::MID_LAYER_17: + return In17_Cu; + case ALTIUM_LAYER::MID_LAYER_18: + return In18_Cu; + case ALTIUM_LAYER::MID_LAYER_19: + return In19_Cu; + case ALTIUM_LAYER::MID_LAYER_20: + return In20_Cu; + case ALTIUM_LAYER::MID_LAYER_21: + return In21_Cu; + case ALTIUM_LAYER::MID_LAYER_22: + return In22_Cu; + case ALTIUM_LAYER::MID_LAYER_23: + return In23_Cu; + case ALTIUM_LAYER::MID_LAYER_24: + return In24_Cu; + case ALTIUM_LAYER::MID_LAYER_25: + return In25_Cu; + case ALTIUM_LAYER::MID_LAYER_26: + return In26_Cu; + case ALTIUM_LAYER::MID_LAYER_27: + return In27_Cu; + case ALTIUM_LAYER::MID_LAYER_28: + return In28_Cu; + case ALTIUM_LAYER::MID_LAYER_29: + return In29_Cu; + case ALTIUM_LAYER::MID_LAYER_30: + return In30_Cu; + case ALTIUM_LAYER::BOTTOM_LAYER: + return B_Cu; + + case ALTIUM_LAYER::TOP_OVERLAY: + return F_SilkS; + case ALTIUM_LAYER::BOTTOM_OVERLAY: + return B_SilkS; + case ALTIUM_LAYER::TOP_PASTE: + return F_Paste; + case ALTIUM_LAYER::BOTTOM_PASTE: + return B_Paste; + case ALTIUM_LAYER::TOP_SOLDER: + return F_Mask; + case ALTIUM_LAYER::BOTTOM_SOLDER: + return B_Mask; + + case ALTIUM_LAYER::INTERNAL_PLANE_1: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_2: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_3: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_4: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_5: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_6: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_7: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_8: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_9: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_10: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_11: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_12: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_13: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_14: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_15: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::INTERNAL_PLANE_16: + return UNDEFINED_LAYER; + + case ALTIUM_LAYER::DRILL_GUIDE: + return Dwgs_User; + case ALTIUM_LAYER::KEEP_OUT_LAYER: + return UNDEFINED_LAYER; + + case ALTIUM_LAYER::MECHANICAL_1: + return Dwgs_User; //Edge_Cuts; + case ALTIUM_LAYER::MECHANICAL_2: + return Dwgs_User; + case ALTIUM_LAYER::MECHANICAL_3: + return Dwgs_User; + case ALTIUM_LAYER::MECHANICAL_4: + return Dwgs_User; + case ALTIUM_LAYER::MECHANICAL_5: + return Dwgs_User; + case ALTIUM_LAYER::MECHANICAL_6: + return Dwgs_User; + case ALTIUM_LAYER::MECHANICAL_7: + return Dwgs_User; + case ALTIUM_LAYER::MECHANICAL_8: + return Dwgs_User; + case ALTIUM_LAYER::MECHANICAL_9: + return Dwgs_User; + case ALTIUM_LAYER::MECHANICAL_10: + return Dwgs_User; + case ALTIUM_LAYER::MECHANICAL_11: + return Dwgs_User; + case ALTIUM_LAYER::MECHANICAL_12: + return Dwgs_User; + case ALTIUM_LAYER::MECHANICAL_13: + return F_Fab; + case ALTIUM_LAYER::MECHANICAL_14: + return B_Fab; + case ALTIUM_LAYER::MECHANICAL_15: + return F_CrtYd; + case ALTIUM_LAYER::MECHANICAL_16: + return B_CrtYd; + + case ALTIUM_LAYER::DRILL_DRAWING: + return Dwgs_User; + case ALTIUM_LAYER::MULTI_LAYER: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::CONNECTIONS: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::BACKGROUND: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::DRC_ERROR_MARKERS: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::SELECTIONS: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::VISIBLE_GRID_1: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::VISIBLE_GRID_2: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::PAD_HOLES: + return UNDEFINED_LAYER; + case ALTIUM_LAYER::VIA_HOLES: + return UNDEFINED_LAYER; + + default: + return UNDEFINED_LAYER; + } +} + + +ALTIUM_PCB::ALTIUM_PCB( BOARD* aBoard ) +{ + m_board = aBoard; + m_num_nets = 0; +} + +ALTIUM_PCB::~ALTIUM_PCB() +{ +} + +void ALTIUM_PCB::Parse( const CFB::CompoundFileReader& aReader, + const std::map& aFileMapping ) +{ + // this vector simply declares in which order which functions to call. + const std::vector> parserOrder = { + { true, ALTIUM_PCB_DIR::FILE_HEADER, + [this]( auto aReader, auto fileHeader ) { + this->ParseFileHeader( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::BOARD6, + [this]( auto aReader, auto fileHeader ) { + this->ParseBoard6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::COMPONENTS6, + [this]( auto aReader, auto fileHeader ) { + this->ParseComponents6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::NETS6, + [this]( auto aReader, auto fileHeader ) { + this->ParseNets6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::CLASSES6, + [this]( auto aReader, auto fileHeader ) { + this->ParseClasses6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::RULES6, + [this]( auto aReader, auto fileHeader ) { + this->ParseRules6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::DIMENSIONS6, + [this]( auto aReader, auto fileHeader ) { + this->ParseDimensions6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::POLYGONS6, + [this]( auto aReader, auto fileHeader ) { + this->ParsePolygons6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::ARCS6, + [this]( auto aReader, auto fileHeader ) { + this->ParseArcs6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::PADS6, + [this]( auto aReader, auto fileHeader ) { + this->ParsePads6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::VIAS6, + [this]( auto aReader, auto fileHeader ) { + this->ParseVias6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::TRACKS6, + [this]( auto aReader, auto fileHeader ) { + this->ParseTracks6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::TEXTS6, + [this]( auto aReader, auto fileHeader ) { + this->ParseTexts6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::FILLS6, + [this]( auto aReader, auto fileHeader ) { + this->ParseFills6Data( aReader, fileHeader ); + } }, + { false, ALTIUM_PCB_DIR::BOARDREGIONS, + [this]( auto aReader, auto fileHeader ) { + this->ParseBoardRegionsData( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6, + [this]( auto aReader, auto fileHeader ) { + this->ParseShapeBasedRegions6Data( aReader, fileHeader ); + } }, + { true, ALTIUM_PCB_DIR::REGIONS6, + [this]( auto aReader, auto fileHeader ) { + this->ParseRegions6Data( aReader, fileHeader ); + } } + }; + + // Parse data in specified order + for( const std::tuple& cur : parserOrder ) + { + bool isRequired; + ALTIUM_PCB_DIR directory; + PARSE_FUNCTION_POINTER_fp fp; + std::tie( isRequired, directory, fp ) = cur; + + const auto& mappedDirectory = aFileMapping.find( directory ); + if( mappedDirectory == aFileMapping.end() ) + { + wxASSERT_MSG( !isRequired, + wxString::Format( + _( "Altium Directory of kind %d was expected, but no mapping is present in the code" ), + directory ) ); + continue; + } + + const CFB::COMPOUND_FILE_ENTRY* file = + FindStream( aReader, mappedDirectory->second.c_str() ); + if( file != nullptr ) + { + fp( aReader, file ); + } + else if( isRequired ) + { + wxLogError( wxString::Format( _( "File not found: '%s'" ), mappedDirectory->second ) ); + } + } + + // change priority of outer zone to zero + for( auto& zone : m_outer_plane ) + { + zone.second->SetPriority( 0 ); + } + + // Finish Board by recalculating module boundingboxes + for( auto& module : m_board->Modules() ) + { + module->CalculateBoundingBox(); + } +} + +int ALTIUM_PCB::GetNetCode( uint16_t aId ) const +{ + if( aId == ALTIUM_NET_UNCONNECTED ) + { + return NETINFO_LIST::UNCONNECTED; + } + else if( m_num_nets < aId ) + { + THROW_IO_ERROR( + wxString::Format( _( "Netcode with id %d does not exist. Only %d nets are known" ), + aId, m_num_nets ) ); + } + else + { + return aId + 1; + } +} + +const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const +{ + const auto rules = m_rules.find( aKind ); + if( rules == m_rules.end() ) + { + return nullptr; + } + for( const ARULE6& rule : rules->second ) + { + if( rule.name == aName ) + { + return &rule; + } + } + return nullptr; +} + +const ARULE6* ALTIUM_PCB::GetRuleDefault( ALTIUM_RULE_KIND aKind ) const +{ + const auto rules = m_rules.find( aKind ); + if( rules == m_rules.end() ) + { + return nullptr; + } + for( const ARULE6& rule : rules->second ) + { + if( rule.scope1expr == "All" && rule.scope2expr == "All" ) + { + return &rule; + } + } + return nullptr; +} + +void ALTIUM_PCB::ParseFileHeader( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + reader.ReadAndSetSubrecordLength(); + wxString header = reader.ReadWxString(); + + //std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File + + //reader.SkipSubrecord(); + + // TODO: does not seem to work all the time at the moment + //if( reader.GetRemainingBytes() != 0 ) + //{ + // THROW_IO_ERROR( _( "FileHeader stream is not fully parsed" ) ); + //} +} + +void ALTIUM_PCB::ParseBoard6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + ABOARD6 elem( reader ); + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Board6 stream is not fully parsed" ) ); + } + + m_board->SetAuxOrigin( elem.sheetpos ); + m_board->SetGridOrigin( elem.sheetpos ); + + // read layercount from stackup, because LAYERSETSCOUNT is not always correct?! + size_t layercount = 0; + for( size_t i = static_cast( ALTIUM_LAYER::TOP_LAYER ); + i < elem.stackup.size() && i != 0; i = elem.stackup[i - 1].nextId, layercount++ ) + ; + m_board->SetCopperLayerCount( layercount ); + + BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings(); + BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor(); + + // create board stackup + stackup.RemoveAll(); // Just to be sure + stackup.BuildDefaultStackupList( &designSettings, layercount ); + + auto it = stackup.GetList().begin(); + // find first copper layer + for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it ) + ; + + auto curLayer = static_cast( F_Cu ); + for( size_t i = static_cast( ALTIUM_LAYER::TOP_LAYER ); + i < elem.stackup.size() && i != 0; i = elem.stackup[i - 1].nextId, layercount++ ) + { + auto layer = elem.stackup.at( i - 1 ); // array starts with 0, but stackup with 1 + m_layermap.insert( + { static_cast( i ), static_cast( curLayer++ ) } ); + + if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER ) + { + THROW_IO_ERROR( _( "Board6 stream, unexpected item while parsing stackup" ) ); + } + ( *it )->SetThickness( layer.copperthick ); + + if( ( *it )->GetBrdLayerId() == B_Cu ) + { + if( layer.nextId != 0 ) + { + THROW_IO_ERROR( + _( "Board6 stream, unexpected id while parsing last stackup layer" ) ); + } + // overwrite entry from internal -> bottom + m_layermap[static_cast( i )] = B_Cu; + break; + } + + ++it; + if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC ) + { + THROW_IO_ERROR( _( "Board6 stream, unexpected item while parsing stackup" ) ); + } + ( *it )->SetThickness( layer.dielectricthick, 0 ); + ( *it )->SetMaterial( layer.dielectricmaterial.empty() ? + NotSpecifiedPrm() : + wxString( layer.dielectricmaterial ) ); + ( *it )->SetEpsilonR( layer.dielectricconst, 0 ); + + ++it; + } + + HelperCreateBoardOutline( elem.board_vertices ); +} + +void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector& aVertices ) +{ + if( !aVertices.empty() ) + { + const ALTIUM_VERTICE* last = &aVertices.at( 0 ); + for( size_t i = 0; i < aVertices.size(); i++ ) + { + const ALTIUM_VERTICE* cur = &aVertices.at( ( i + 1 ) % aVertices.size() ); + + DRAWSEGMENT* ds = new DRAWSEGMENT( m_board ); + m_board->Add( ds, ADD_MODE::APPEND ); + + ds->SetWidth( m_board->GetDesignSettings().GetLineThickness( Edge_Cuts ) ); + ds->SetLayer( Edge_Cuts ); + + if( !last->isRound && !cur->isRound ) + { + ds->SetShape( STROKE_T::S_SEGMENT ); + ds->SetStart( last->position ); + ds->SetEnd( cur->position ); + } + else if( cur->isRound ) + { + ds->SetShape( STROKE_T::S_ARC ); + ds->SetAngle( -NormalizeAngleDegreesPos( cur->endangle - cur->startangle ) * 10. ); + + double startradiant = DEG2RAD( cur->startangle ); + wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * cur->radius ), + -KiROUND( std::sin( startradiant ) * cur->radius ) ); + wxPoint arcStart = cur->center + arcStartOffset; + ds->SetCenter( cur->center ); + ds->SetArcStart( arcStart ); + + if( !last->isRound ) + { + double endradiant = DEG2RAD( cur->endangle ); + wxPoint arcEndOffset = wxPoint( KiROUND( std::cos( endradiant ) * cur->radius ), + -KiROUND( std::sin( endradiant ) * cur->radius ) ); + wxPoint arcEnd = cur->center + arcEndOffset; + + DRAWSEGMENT* ds2 = new DRAWSEGMENT( m_board ); + ds2->SetShape( STROKE_T::S_SEGMENT ); + m_board->Add( ds2, ADD_MODE::APPEND ); + ds2->SetWidth( m_board->GetDesignSettings().GetLineThickness( Edge_Cuts ) ); + ds2->SetLayer( Edge_Cuts ); + ds2->SetStart( last->position ); + + // TODO: this is more of a hack than the real solution + double lineLengthStart = GetLineLength( last->position, arcStart ); + double lineLengthEnd = GetLineLength( last->position, arcEnd ); + if( lineLengthStart > lineLengthEnd ) + { + ds2->SetEnd( cur->center + arcEndOffset ); + } + else + { + ds2->SetEnd( cur->center + arcStartOffset ); + } + } + } + last = cur; + } + } +} + +void ALTIUM_PCB::ParseClasses6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings(); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + ACLASS6 elem( reader ); + + if( elem.kind == ALTIUM_CLASS_KIND::NET_CLASS ) + { + const NETCLASSPTR& netclass = std::make_shared( elem.name ); + designSettings.m_NetClasses.Add( netclass ); + + for( const auto& name : elem.names ) + { + netclass->Add( + name ); // TODO: it seems it can happen that we have names not attached to any net. + } + } + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Classes6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParseComponents6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + uint16_t componentId = 0; + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + ACOMPONENT6 elem( reader ); + + MODULE* module = new MODULE( m_board ); + m_board->Add( module, ADD_MODE::APPEND ); + m_components.emplace_back( module ); + + wxString pack_ref = elem.sourcelibreference; + wxString lib_ref = elem.sourcefootprintlibrary; // TODO: remove ".PcbLib" part + ReplaceIllegalFileNameChars( lib_ref, '_' ); + ReplaceIllegalFileNameChars( pack_ref, '_' ); + + wxString key = !lib_ref.empty() ? lib_ref + ":" + pack_ref : pack_ref; + + LIB_ID fpID; + fpID.Parse( key, LIB_ID::ID_PCB, true ); + module->SetFPID( fpID ); + + module->SetPosition( elem.position ); + module->SetOrientationDegrees( elem.rotation ); + + // KiCad netlisting requires parts to have non-digit + digit annotation. + // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator + wxString reference = elem.sourcedesignator; + if( reference.find_first_not_of( "0123456789" ) == wxString::npos ) + reference.Prepend( "UNK" ); + module->SetReference( reference ); + + module->SetLocked( elem.locked ); + module->Reference().SetVisible( elem.nameon ); + module->Value().SetVisible( elem.commenton ); + module->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu ); + + componentId++; + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Components6 stream is not fully parsed" ) ); + } +} + + +void ALTIUM_PCB::HelperParseDimensions6Linear( const ADIMENSION6& aElem ) +{ + if( aElem.referencePoint.size() != 2 ) + { + THROW_IO_ERROR( _( "Incorrect number of reference points for linear dimension object" ) ); + } + + PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer ); + if( klayer == UNDEFINED_LAYER ) + { + wxLogInfo( wxString::Format( + _( "Dimension on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ), + aElem.layer ) ); + klayer = Eco1_User; + } + + wxPoint referencePoint0 = aElem.referencePoint.at( 0 ); + wxPoint referencePoint1 = aElem.referencePoint.at( 1 ); + + DIMENSION* dimension = new DIMENSION( m_board ); + m_board->Add( dimension, ADD_MODE::APPEND ); + + dimension->SetLayer( klayer ); + dimension->SetOrigin( referencePoint0, aElem.textprecission ); + if( referencePoint0 != aElem.xy1 ) + { + /** + * Basically REFERENCE0POINT and REFERENCE1POINT are the two end points of the dimension. + * XY1 is the position of the arrow above REFERENCE0POINT. those three points are not necesarily + * in 90degree angle, but KiCad requires this to show the correct measurements. + * + * Therefore, we take the vector of REFERENCE0POINT -> XY1, calculate the normal, and intersect it with + * REFERENCE1POINT pointing the same direction as REFERENCE0POINT -> XY1. This should give us a valid + * measurement point where we can place the drawsegment. + */ + wxPoint direction = aElem.xy1 - referencePoint0; + wxPoint directionNormalVector = wxPoint( -direction.y, direction.x ); + SEG segm1( referencePoint0, referencePoint0 + directionNormalVector ); + SEG segm2( referencePoint1, referencePoint1 + direction ); + wxPoint intersection( segm1.Intersect( segm2, true, true ).get() ); + dimension->SetEnd( intersection, aElem.textprecission ); + + int height = static_cast( EuclideanNorm( direction ) ); + if( direction.x <= 0 && direction.y <= 0 ) // TODO: I suspect this is not always correct + { + height = -height; + } + dimension->SetHeight( height, aElem.textprecission ); + } + else + { + dimension->SetEnd( referencePoint1, aElem.textprecission ); + } + + dimension->SetWidth( aElem.linewidth ); + + dimension->Text().SetThickness( aElem.textlinewidth ); + dimension->Text().SetTextSize( wxSize( aElem.textheight, aElem.textheight ) ); + dimension->Text().SetBold( aElem.textbold ); + dimension->Text().SetItalic( aElem.textitalic ); + + switch( aElem.textunit ) + { + case ALTIUM_UNIT::INCHES: + dimension->SetUnits( EDA_UNITS::INCHES, false ); + break; + case ALTIUM_UNIT::MILS: + dimension->SetUnits( EDA_UNITS::INCHES, true ); + break; + case ALTIUM_UNIT::MILLIMETERS: + case ALTIUM_UNIT::CENTIMETER: + dimension->SetUnits( EDA_UNITS::MILLIMETRES, false ); + break; + default: + break; + } + + dimension->AdjustDimensionDetails( aElem.textprecission ); +} + +void ALTIUM_PCB::HelperParseDimensions6Leader( const ADIMENSION6& aElem ) +{ + PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer ); + if( klayer == UNDEFINED_LAYER ) + { + wxLogInfo( wxString::Format( + _( "Dimension on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ), + aElem.layer ) ); + klayer = Eco1_User; + } + + if( !aElem.referencePoint.empty() ) + { + wxPoint referencePoint0 = aElem.referencePoint.at( 0 ); + + // line + wxPoint last = referencePoint0; + for( size_t i = 1; i < aElem.referencePoint.size(); i++ ) + { + DRAWSEGMENT* ds = new DRAWSEGMENT( m_board ); + m_board->Add( ds, ADD_MODE::APPEND ); + ds->SetShape( STROKE_T::S_SEGMENT ); + ds->SetLayer( klayer ); + ds->SetWidth( aElem.linewidth ); + ds->SetStart( last ); + ds->SetEnd( aElem.referencePoint.at( i ) ); + last = aElem.referencePoint.at( i ); + } + + // arrow + if( aElem.referencePoint.size() >= 2 ) + { + wxPoint dirVec = aElem.referencePoint.at( 1 ) - referencePoint0; + if( dirVec.x != 0 || dirVec.y != 0 ) + { + double scaling = EuclideanNorm( dirVec ) / aElem.arrowsize; + wxPoint arrVec = + wxPoint( KiROUND( dirVec.x / scaling ), KiROUND( dirVec.y / scaling ) ); + RotatePoint( &arrVec, 200. ); + + DRAWSEGMENT* ds1 = new DRAWSEGMENT( m_board ); + m_board->Add( ds1, ADD_MODE::APPEND ); + ds1->SetShape( STROKE_T::S_SEGMENT ); + ds1->SetLayer( klayer ); + ds1->SetWidth( aElem.linewidth ); + ds1->SetStart( referencePoint0 ); + ds1->SetEnd( referencePoint0 + arrVec ); + + RotatePoint( &arrVec, -400. ); + + DRAWSEGMENT* ds2 = new DRAWSEGMENT( m_board ); + m_board->Add( ds2, ADD_MODE::APPEND ); + ds2->SetShape( STROKE_T::S_SEGMENT ); + ds2->SetLayer( klayer ); + ds2->SetWidth( aElem.linewidth ); + ds2->SetStart( referencePoint0 ); + ds2->SetEnd( referencePoint0 + arrVec ); + } + } + } + + if( aElem.textPoint.empty() ) + { + wxLogError( + wxString::Format( _( "No text position present for leader dimension object" ) ) ); + return; + } + + TEXTE_PCB* text = new TEXTE_PCB( m_board ); + m_board->Add( text, ADD_MODE::APPEND ); + text->SetText( aElem.textformat ); + text->SetPosition( aElem.textPoint.at( 0 ) ); + text->SetLayer( klayer ); + text->SetTextSize( wxSize( aElem.textheight, aElem.textheight ) ); // TODO: parse text width + text->SetThickness( aElem.textlinewidth ); + text->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_LEFT ); + text->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_BOTTOM ); +} + +void ALTIUM_PCB::HelperParseDimensions6Datum( const ADIMENSION6& aElem ) +{ + PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer ); + if( klayer == UNDEFINED_LAYER ) + { + wxLogInfo( wxString::Format( + _( "Dimension on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ), + aElem.layer ) ); + klayer = Eco1_User; + } + + for( size_t i = 0; i < aElem.referencePoint.size(); i++ ) + { + DRAWSEGMENT* ds1 = new DRAWSEGMENT( m_board ); + m_board->Add( ds1, ADD_MODE::APPEND ); + ds1->SetShape( STROKE_T::S_SEGMENT ); + ds1->SetLayer( klayer ); + ds1->SetWidth( aElem.linewidth ); + ds1->SetStart( aElem.referencePoint.at( i ) ); + // ds1->SetEnd( /* TODO: seems to be based on TEXTY */ ); + } +} + +void ALTIUM_PCB::HelperParseDimensions6Center( const ADIMENSION6& aElem ) +{ + PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer ); + if( klayer == UNDEFINED_LAYER ) + { + wxLogInfo( wxString::Format( + _( "Dimension on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ), + aElem.layer ) ); + klayer = Eco1_User; + } + + DRAWSEGMENT* ds1 = new DRAWSEGMENT( m_board ); + m_board->Add( ds1, ADD_MODE::APPEND ); + ds1->SetShape( STROKE_T::S_SEGMENT ); + ds1->SetLayer( klayer ); + ds1->SetWidth( aElem.linewidth ); + + wxPoint vec1 = wxPoint( 0, aElem.height / 2 ); + RotatePoint( &vec1, aElem.angle * 10. ); + ds1->SetStart( aElem.xy1 + vec1 ); + ds1->SetEnd( aElem.xy1 - vec1 ); + + DRAWSEGMENT* ds2 = new DRAWSEGMENT( m_board ); + m_board->Add( ds2, ADD_MODE::APPEND ); + ds2->SetShape( STROKE_T::S_SEGMENT ); + ds2->SetLayer( klayer ); + ds2->SetWidth( aElem.linewidth ); + + wxPoint vec2 = wxPoint( aElem.height / 2, 0 ); + RotatePoint( &vec2, aElem.angle * 10. ); + ds2->SetStart( aElem.xy1 + vec2 ); + ds2->SetEnd( aElem.xy1 - vec2 ); +} + + +void ALTIUM_PCB::ParseDimensions6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + ADIMENSION6 elem( reader ); + + switch( elem.kind ) + { + case ALTIUM_DIMENSION_KIND::LINEAR: + HelperParseDimensions6Linear( elem ); + break; + case ALTIUM_DIMENSION_KIND::LEADER: + HelperParseDimensions6Leader( elem ); + break; + case ALTIUM_DIMENSION_KIND::DATUM: + wxLogInfo( wxString::Format( _( "Ignore dimension object of kind %d" ), elem.kind ) ); + // HelperParseDimensions6Datum( elem ); + break; + case ALTIUM_DIMENSION_KIND::CENTER: + HelperParseDimensions6Center( elem ); + break; + default: + wxLogInfo( wxString::Format( _( "Ignore dimension object of kind %d" ), elem.kind ) ); + break; + } + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Dimensions6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParseNets6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + wxASSERT( m_num_nets == 0 ); + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + ANET6 elem( reader ); + + m_board->Add( new NETINFO_ITEM( m_board, elem.name, ++m_num_nets ), ADD_MODE::APPEND ); + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Nets6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParsePolygons6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + APOLYGON6 elem( reader ); + + PCB_LAYER_ID klayer = GetKicadLayer( elem.layer ); + if( klayer == UNDEFINED_LAYER ) + { + wxLogWarning( wxString::Format( + _( "Polygon on Altium layer %d has no KiCad equivalent. Ignore it instead" ), + elem.layer ) ); + m_polygons.emplace_back( nullptr ); + continue; + } + + ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board ); + m_board->Add( zone, ADD_MODE::APPEND ); + m_polygons.emplace_back( zone ); + + zone->SetNetCode( GetNetCode( elem.net ) ); + zone->SetLayer( klayer ); + zone->SetPosition( elem.vertices.at( 0 ).position ); + zone->SetLocked( elem.locked ); + + SHAPE_LINE_CHAIN linechain; + for( auto& vertice : elem.vertices ) + { + // TODO: arcs + linechain.Append( vertice.position ); + } + linechain.Append( elem.vertices.at( 0 ).position ); + linechain.SetClosed( true ); + + SHAPE_POLY_SET* outline = new SHAPE_POLY_SET(); + outline->AddOutline( linechain ); + zone->SetOutline( outline ); + + // TODO: more flexible rule parsing + const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::PLANE_CLEARANCE ); + if( clearanceRule != nullptr ) + { + zone->SetZoneClearance( clearanceRule->planeclearanceClearance ); + } + const ARULE6* polygonConnectRule = GetRuleDefault( ALTIUM_RULE_KIND::POLYGON_CONNECT ); + if( clearanceRule != nullptr ) + { + // TODO: correct variables? + zone->SetThermalReliefCopperBridge( + polygonConnectRule->polygonconnectReliefconductorwidth ); + zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth ); + } + + if( IsAltiumLayerAPlane( elem.layer ) ) + { + // outer zone will be set to priority 0 later. + zone->SetPriority( 1 ); + + // check if this is the outer zone by simply comparing the BBOX + const auto& cur_outer_plane = m_outer_plane.find( elem.layer ); + if( cur_outer_plane == m_outer_plane.end() + || zone->GetBoundingBox().Contains( + cur_outer_plane->second->GetBoundingBox() ) ) + { + m_outer_plane[elem.layer] = zone; + } + } + + if( elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::SOLID ) + { + zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN ); + zone->SetHatchFillTypeThickness( elem.trackwidth ); + if( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::NONE ) + { + // use a small hack to get us only an outline (hopefully) + const EDA_RECT& bbox = zone->GetBoundingBox(); + zone->SetHatchFillTypeGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) ); + } + else + { + zone->SetHatchFillTypeGap( elem.gridsize - elem.trackwidth ); + } + zone->SetHatchFillTypeOrientation( + elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::DEGREE_45 ? 45 : 0 ); + } + + zone->SetHatch( ZONE_HATCH_STYLE::DIAGONAL_EDGE, zone->GetDefaultHatchPitch(), true ); + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Polygons6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParseRules6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + ARULE6 elem( reader ); + + m_rules[elem.kind].emplace_back( elem ); + } + + // sort rules by priority + for( auto&& val : m_rules ) + { + std::sort( val.second.begin(), val.second.end(), + []( const auto& lhs, const auto& rhs ) { return lhs.priority < rhs.priority; } ); + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Rules6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParseBoardRegionsData( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + AREGION6 elem( reader, false ); + + // TODO: implement? + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "BoardRegions stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParseShapeBasedRegions6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + AREGION6 elem( reader, true ); + + if( elem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT ) + { + HelperCreateBoardOutline( elem.vertices ); + } + else if( elem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT ) + { + ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board ); + m_board->Add( zone, ADD_MODE::APPEND ); + + if( elem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || elem.is_keepout ) + { + zone->SetIsKeepout( true ); + zone->SetDoNotAllowTracks( false ); + zone->SetDoNotAllowVias( false ); + zone->SetDoNotAllowCopperPour( true ); + } + else + { + zone->SetNetCode( GetNetCode( elem.net ) ); + } + + if( elem.layer == ALTIUM_LAYER::MULTI_LAYER ) + { + zone->SetLayer( F_Cu ); + zone->SetLayerSet( LSET::AllCuMask() ); + } + else + { + PCB_LAYER_ID klayer = GetKicadLayer( elem.layer ); + if( klayer == UNDEFINED_LAYER ) + { + wxLogInfo( wxString::Format( + _( "Zone on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ), + elem.layer ) ); + klayer = Eco1_User; + } + zone->SetLayer( klayer ); + } + + zone->SetPosition( elem.vertices.at( 0 ).position ); + SHAPE_LINE_CHAIN linechain; + for( auto& vertice : elem.vertices ) + { + linechain.Append( vertice.position ); + } + linechain.Append( elem.vertices.at( 0 ).position ); + linechain.SetClosed( true ); + + SHAPE_POLY_SET* outline = new SHAPE_POLY_SET(); + outline->AddOutline( linechain ); + zone->SetOutline( outline ); + + zone->SetHatch( ZONE_HATCH_STYLE::DIAGONAL_EDGE, zone->GetDefaultHatchPitch(), true ); + } + else if( elem.kind == ALTIUM_REGION_KIND::COPPER ) + { + if( elem.subpolyindex == ALTIUM_POLYGON_NONE ) + { + PCB_LAYER_ID klayer = GetKicadLayer( elem.layer ); + if( klayer == UNDEFINED_LAYER ) + { + wxLogInfo( wxString::Format( + _( "Polygon on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ), + elem.layer ) ); + klayer = Eco1_User; + } + + DRAWSEGMENT* ds = new DRAWSEGMENT( m_board ); + m_board->Add( ds, ADD_MODE::APPEND ); + ds->SetShape( STROKE_T::S_POLYGON ); + ds->SetLayer( klayer ); + ds->SetWidth( 0 ); + + SHAPE_LINE_CHAIN linechain; + for( auto& vertice : elem.vertices ) + { + linechain.Append( vertice.position ); + } + linechain.Append( elem.vertices.at( 0 ).position ); + linechain.SetClosed( true ); + + SHAPE_POLY_SET polyset; + polyset.AddOutline( linechain ); + ds->SetPolyShape( polyset ); + } + } + else + { + wxLogError( wxString::Format( + _( "Ignore polygon shape of kind %d on layer %s, because not implemented yet" ), + elem.kind, LSET::Name( GetKicadLayer( elem.layer ) ) ) ); + } + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "ShapeBasedRegions6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParseRegions6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + for( ZONE_CONTAINER* zone : m_polygons ) + { + if( zone != nullptr ) + { + zone->UnFill(); // just to be sure + } + } + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + AREGION6 elem( reader, false ); + +#if 0 // TODO: it seems this code has multiple issues right now, and we can manually fill anyways + if( elem.subpolyindex != ALTIUM_POLYGON_NONE ) + { + if( m_polygons.size() <= elem.subpolyindex ) + { + THROW_IO_ERROR( wxString::Format( + _( "Region stream tries to access polygon id %d of %d existing polygons" ), + elem.subpolyindex, m_polygons.size() ) ); + } + + ZONE_CONTAINER *zone = m_polygons.at(elem.subpolyindex); + + if( zone == nullptr ) + { + continue; // we know the zone id, but because we do not know the layer we did not add it! + } + + SHAPE_LINE_CHAIN linechain; + for( auto& vertice : elem.vertices ) + { + linechain.Append( vertice.position ); + } + linechain.Append( elem.vertices.at( 0 ).position ); + linechain.SetClosed( true ); + + SHAPE_POLY_SET polyset; + polyset.AddOutline( linechain ); + polyset.BooleanAdd( zone->GetFilledPolysList(), SHAPE_POLY_SET::POLYGON_MODE::PM_STRICTLY_SIMPLE ); + + zone->SetFilledPolysList( polyset ); + zone->SetIsFilled( true ); + } +#endif + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Regions6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParseArcs6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + AARC6 elem( reader ); + + if( elem.is_polygonoutline || elem.subpolyindex != ALTIUM_POLYGON_NONE ) + { + continue; + } + + // element in plane is in fact substracted from the plane. Should be already done by Altium? + /*if( IsAltiumLayerAPlane( elem.layer ) ) + { + continue; + }*/ + + PCB_LAYER_ID klayer = GetKicadLayer( elem.layer ); + if( klayer == UNDEFINED_LAYER ) + { + wxLogInfo( wxString::Format( + _( "Arc on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ), + elem.layer ) ); + klayer = Eco1_User; + } + + if( elem.is_keepout || IsAltiumLayerAPlane( elem.layer ) ) + { + DRAWSEGMENT ds( nullptr ); // just a helper to get the graphic + ds.SetWidth( elem.width ); + ds.SetCenter( elem.center ); + if( elem.startangle == 0. && elem.endangle == 360. ) + { // TODO: other variants to define circle? + ds.SetShape( STROKE_T::S_CIRCLE ); + ds.SetArcStart( elem.center - wxPoint( 0, elem.radius ) ); + } + else + { + ds.SetShape( STROKE_T::S_ARC ); + ds.SetAngle( -NormalizeAngleDegreesPos( elem.endangle - elem.startangle ) * 10. ); + + double startradiant = DEG2RAD( elem.startangle ); + wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ), + -KiROUND( std::sin( startradiant ) * elem.radius ) ); + ds.SetArcStart( elem.center + arcStartOffset ); + } + + ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board ); + m_board->Add( zone, ADD_MODE::APPEND ); + + zone->SetLayer( klayer ); + zone->SetIsKeepout( true ); + zone->SetDoNotAllowTracks( false ); + zone->SetDoNotAllowVias( false ); + zone->SetDoNotAllowCopperPour( true ); + + SHAPE_POLY_SET* outline = new SHAPE_POLY_SET(); + ds.TransformShapeWithClearanceToPolygon( *outline, 0, ARC_HIGH_DEF, false ); + outline->Simplify( + SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); // the outline is not a single polygon! + zone->SetOutline( outline ); + + zone->SetHatch( + ZONE_HATCH_STYLE::DIAGONAL_EDGE, ZONE_CONTAINER::GetDefaultHatchPitch(), true ); + continue; + } + + if( klayer >= F_Cu && klayer <= B_Cu ) + { + double angle = -NormalizeAngleDegreesPos( elem.endangle - elem.startangle ); + double startradiant = DEG2RAD( elem.startangle ); + wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ), + -KiROUND( std::sin( startradiant ) * elem.radius ) ); + + SHAPE_ARC shapeArc( elem.center, elem.center + arcStartOffset, angle, elem.width ); + ARC* arc = new ARC( m_board, &shapeArc ); + m_board->Add( arc, ADD_MODE::APPEND ); + + arc->SetWidth( elem.width ); + arc->SetLayer( klayer ); + arc->SetNetCode( GetNetCode( elem.net ) ); + } + else + { + // TODO: better approach to select if item belongs to a MODULE + DRAWSEGMENT* ds = nullptr; + if( elem.component == ALTIUM_COMPONENT_NONE ) + { + ds = new DRAWSEGMENT( m_board ); + m_board->Add( ds, ADD_MODE::APPEND ); + } + else + { + if( m_components.size() <= elem.component ) + { + THROW_IO_ERROR( wxString::Format( + _( "Arcs6 stream tries to access component id %d of %d existing components" ), + elem.component, m_components.size() ) ); + } + MODULE* module = m_components.at( elem.component ); + ds = new EDGE_MODULE( module ); + module->Add( ds, ADD_MODE::APPEND ); + } + + ds->SetCenter( elem.center ); + ds->SetWidth( elem.width ); + ds->SetLayer( klayer ); + + if( elem.startangle == 0. && elem.endangle == 360. ) + { // TODO: other variants to define circle? + ds->SetShape( STROKE_T::S_CIRCLE ); + ds->SetArcStart( elem.center - wxPoint( 0, elem.radius ) ); + } + else + { + ds->SetShape( STROKE_T::S_ARC ); + ds->SetAngle( -NormalizeAngleDegreesPos( elem.endangle - elem.startangle ) * 10. ); + + double startradiant = DEG2RAD( elem.startangle ); + wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ), + -KiROUND( std::sin( startradiant ) * elem.radius ) ); + ds->SetArcStart( elem.center + arcStartOffset ); + } + + if( elem.component != ALTIUM_COMPONENT_NONE ) + { + dynamic_cast( ds )->SetLocalCoord(); + } + } + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Arcs6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParsePads6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + APAD6 elem( reader ); + + // Create Pad + MODULE* module = nullptr; + if( elem.component == ALTIUM_COMPONENT_NONE ) + { + module = new MODULE( m_board ); // We cannot add a pad directly into the PCB + m_board->Add( module, ADD_MODE::APPEND ); + module->SetPosition( elem.position ); + } + else + { + if( m_components.size() <= elem.component ) + { + THROW_IO_ERROR( wxString::Format( + _( "Pads6 stream tries to access component id %d of %d existing components" ), + elem.component, m_components.size() ) ); + } + module = m_components.at( elem.component ); + } + + D_PAD* pad = new D_PAD( module ); + module->Add( pad, ADD_MODE::APPEND ); + + pad->SetName( elem.name ); + pad->SetNetCode( GetNetCode( elem.net ) ); + pad->SetLocked( elem.is_locked ); + + pad->SetPosition( elem.position ); + pad->SetOrientationDegrees( elem.direction ); + pad->SetLocalCoord(); + + pad->SetSize( elem.topsize ); + + if( elem.holesize == 0 ) + { + if( elem.layer == ALTIUM_LAYER::MULTI_LAYER ) + { + wxLogError( wxString::Format( + _( "Pad '%s' of Footprint %s marked as multilayer, but it is an SMT pad" ), + elem.name, module->GetReference() ) ); + } + pad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_SMD ); + } + else + { + if( elem.layer != ALTIUM_LAYER::MULTI_LAYER ) + { + // TODO: I assume other values are possible as well? + wxLogError( wxString::Format( + _( "Pad '%s' of Footprint %s is not marked as multilayer, but it is an THT pad" ), + elem.name, module->GetReference() ) ); + } + pad->SetAttribute( elem.plated ? PAD_ATTR_T::PAD_ATTRIB_STANDARD : + PAD_ATTR_T::PAD_ATTRIB_HOLE_NOT_PLATED ); + if( !elem.sizeAndShape || elem.sizeAndShape->holeshape == ALTIUM_PAD_HOLE_SHAPE::ROUND ) + { + pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE ); + pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); + } + else + { + switch( elem.sizeAndShape->holeshape ) + { + case ALTIUM_PAD_HOLE_SHAPE::ROUND: + wxFAIL_MSG( _( "Round holes are handled before the switch" ) ); + break; + + case ALTIUM_PAD_HOLE_SHAPE::SQUARE: + wxLogWarning( wxString::Format( + _( "Pad '%s' of Footprint %s has a square hole. KiCad does not support this yet" ), + elem.name, module->GetReference() ) ); + pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE ); + pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround + // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in this case or rect holes have a different id + break; + + case ALTIUM_PAD_HOLE_SHAPE::SLOT: + { + pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_OBLONG ); + double normalizedSlotrotation = + NormalizeAngleDegreesPos( elem.sizeAndShape->slotrotation ); + if( normalizedSlotrotation == 0. || normalizedSlotrotation == 180. ) + { + pad->SetDrillSize( wxSize( elem.sizeAndShape->slotsize, elem.holesize ) ); + } + else + { + if( normalizedSlotrotation != 90. && normalizedSlotrotation != 270. ) + { + wxLogWarning( wxString::Format( + _( "Pad '%s' of Footprint %s has a hole-rotation of %f degree. KiCad only supports 90 degree angles" ), + elem.name, module->GetReference(), normalizedSlotrotation ) ); + } + + pad->SetDrillSize( wxSize( elem.holesize, elem.sizeAndShape->slotsize ) ); + } + } + break; + + default: + case ALTIUM_PAD_HOLE_SHAPE::UNKNOWN: + wxLogError( wxString::Format( + _( "Pad '%s' of Footprint %s uses a hole of unknown kind %d" ), + elem.name, module->GetReference(), elem.sizeAndShape->holeshape ) ); + pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE ); + pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround + break; + } + } + } + + if( elem.padmode != ALTIUM_PAD_MODE::SIMPLE ) + { + wxLogWarning( wxString::Format( + _( "Pad '%s' of Footprint %s uses a complex pad stack (kind %d), which is not supported yet" ), + elem.name, module->GetReference(), elem.padmode ) ); + } + + switch( elem.topshape ) + { + case ALTIUM_PAD_SHAPE::RECT: + pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_RECT ); + break; + case ALTIUM_PAD_SHAPE::CIRCLE: + if( elem.sizeAndShape + && elem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT ) + { + pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_ROUNDRECT ); // 100 = round, 0 = rectangular + double ratio = elem.sizeAndShape->cornerradius[0] / 200.; + pad->SetRoundRectRadiusRatio( ratio ); + } + else if( elem.topsize.x == elem.topsize.y ) + { + pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_CIRCLE ); + } + else + { + pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_OVAL ); + } + break; + case ALTIUM_PAD_SHAPE::OCTAGONAL: + pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_CHAMFERED_RECT ); + pad->SetChamferPositions( RECT_CHAMFER_ALL ); + pad->SetChamferRectRatio( 0.25 ); + break; + case ALTIUM_PAD_SHAPE::UNKNOWN: + default: + wxLogError( wxString::Format( _( "Pad '%s' of Footprint %s uses a unknown pad-shape" ), + elem.name, module->GetReference() ) ); + break; + } + + switch( elem.layer ) + { + case ALTIUM_LAYER::TOP_LAYER: + pad->SetLayer( F_Cu ); + pad->SetLayerSet( D_PAD::SMDMask() ); + break; + case ALTIUM_LAYER::BOTTOM_LAYER: + pad->SetLayer( B_Cu ); + pad->SetLayerSet( FlipLayerMask( D_PAD::SMDMask() ) ); + break; + case ALTIUM_LAYER::MULTI_LAYER: + default: + pad->SetLayerSet( elem.plated ? D_PAD::StandardMask() : D_PAD::UnplatedHoleMask() ); + break; + } + + if( elem.pastemaskexpansionmode == ALTIUM_PAD_RULE::MANUAL ) + { + pad->SetLocalSolderPasteMargin( elem.pastemaskexpansionmanual ); + } + + if( elem.soldermaskexpansionmode == ALTIUM_PAD_RULE::MANUAL ) + { + pad->SetLocalSolderMaskMargin( elem.soldermaskexpansionmanual ); + } + + if( elem.is_tent_top ) + { + pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) ); + } + if( elem.is_tent_bottom ) + { + pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) ); + } + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Pads6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParseVias6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + AVIA6 elem( reader ); + + VIA* via = new VIA( m_board ); + m_board->Add( via, ADD_MODE::APPEND ); + + via->SetPosition( elem.position ); + via->SetWidth( elem.diameter ); + via->SetDrill( elem.holesize ); + via->SetNetCode( GetNetCode( elem.net ) ); + via->SetLocked( elem.is_locked ); + + bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER + || elem.layer_start == ALTIUM_LAYER::BOTTOM_LAYER; + bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER + || elem.layer_end == ALTIUM_LAYER::BOTTOM_LAYER; + if( start_layer_outside && end_layer_outside ) + { + via->SetViaType( VIATYPE::THROUGH ); + } + else if( ( !start_layer_outside ) && ( !end_layer_outside ) ) + { + via->SetViaType( VIATYPE::BLIND_BURIED ); + } + else + { + via->SetViaType( VIATYPE::MICROVIA ); // TODO: always a microvia? + } + + PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start ); + PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end ); + if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) ) + { + wxLogError( wxString::Format( + _( "Via from layer %d <-> %d uses non-coppy layer. This should not happen." ), + elem.layer_start, elem.layer_end ) ); + continue; // just assume through-hole instead. + } + + // we need VIATYPE set! + via->SetLayerPair( start_klayer, end_klayer ); + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Vias6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParseTracks6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + ATRACK6 elem( reader ); + + if( elem.is_polygonoutline || elem.subpolyindex != ALTIUM_POLYGON_NONE ) + { + continue; + } + + // element in plane is in fact substracted from the plane. Already done by Altium? + /*if( IsAltiumLayerAPlane( elem.layer ) ) + { + continue; + }*/ + + PCB_LAYER_ID klayer = GetKicadLayer( elem.layer ); + if( klayer == UNDEFINED_LAYER ) + { + wxLogInfo( wxString::Format( + _( "Track on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ), + elem.layer ) ); + klayer = Eco1_User; + } + + if( elem.is_keepout || IsAltiumLayerAPlane( elem.layer ) ) + { + DRAWSEGMENT ds( nullptr ); // just a helper to get the graphic + ds.SetShape( STROKE_T::S_SEGMENT ); + ds.SetStart( elem.start ); + ds.SetEnd( elem.end ); + ds.SetWidth( elem.width ); + + ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board ); + m_board->Add( zone, ADD_MODE::APPEND ); + zone->SetLayer( klayer ); + zone->SetIsKeepout( true ); + zone->SetDoNotAllowTracks( false ); + zone->SetDoNotAllowVias( false ); + zone->SetDoNotAllowCopperPour( true ); + + SHAPE_POLY_SET* outline = new SHAPE_POLY_SET(); + ds.TransformShapeWithClearanceToPolygon( *outline, 0, ARC_HIGH_DEF, false ); + zone->SetOutline( outline ); + + zone->SetHatch( + ZONE_HATCH_STYLE::DIAGONAL_EDGE, ZONE_CONTAINER::GetDefaultHatchPitch(), true ); + continue; + } + + if( klayer >= F_Cu && klayer <= B_Cu ) + { + TRACK* track = new TRACK( m_board ); + m_board->Add( track, ADD_MODE::APPEND ); + + track->SetStart( elem.start ); + track->SetEnd( elem.end ); + track->SetWidth( elem.width ); + track->SetLayer( klayer ); + track->SetNetCode( GetNetCode( elem.net ) ); + } + else + { + DRAWSEGMENT* ds = nullptr; + + if( elem.component == ALTIUM_COMPONENT_NONE ) + { + ds = new DRAWSEGMENT( m_board ); + ds->SetShape( STROKE_T::S_SEGMENT ); + m_board->Add( ds, ADD_MODE::APPEND ); + + ds->SetStart( elem.start ); + ds->SetEnd( elem.end ); + } + else + { + if( m_components.size() <= elem.component ) + { + THROW_IO_ERROR( wxString::Format( + _( "Tracks6 stream tries to access component id %d of %d existing components" ), + elem.component, m_components.size() ) ); + } + MODULE* module = m_components.at( elem.component ); + EDGE_MODULE* em = new EDGE_MODULE( module, STROKE_T::S_SEGMENT ); + module->Add( em, ADD_MODE::APPEND ); + + em->SetStart( elem.start ); + em->SetEnd( elem.end ); + em->SetLocalCoord(); + + ds = em; + } + + ds->SetWidth( elem.width ); + + ds->SetLayer( klayer ); + } + + reader.SkipSubrecord(); + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Tracks6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParseTexts6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + ATEXT6 elem( reader ); + + // TODO: better approach to select if item belongs to a MODULE + EDA_TEXT* tx = nullptr; + BOARD_ITEM* itm = nullptr; + if( elem.component == ALTIUM_COMPONENT_NONE ) + { + TEXTE_PCB* txp = new TEXTE_PCB( m_board ); + tx = txp; + itm = txp; + m_board->Add( txp, ADD_MODE::APPEND ); + } + else + { + if( m_components.size() <= elem.component ) + { + THROW_IO_ERROR( wxString::Format( + _( "Texts6 stream tries to access component id %d of %d existing components" ), + elem.component, m_components.size() ) ); + } + MODULE* module = m_components.at( elem.component ); + TEXTE_MODULE* txm; + if( elem.isDesignator ) + { + txm = &module->Reference(); + } + else if( elem.isComment ) + { + txm = &module->Value(); + } + else + { + txm = new TEXTE_MODULE( module ); + module->Add( txm, ADD_MODE::APPEND ); + } + + tx = txm; + itm = txm; + } + + if( !elem.isDesignator && elem.text == ".Designator" ) + { + tx->SetText( "%R" ); + } + else + { + tx->SetText( elem.text ); + } + + itm->SetPosition( elem.position ); + tx->SetTextAngle( elem.rotation * 10. ); + if( elem.component != ALTIUM_COMPONENT_NONE ) + { + TEXTE_MODULE* txm = dynamic_cast( tx ); + if( elem.isDesignator || elem.isComment ) + { + double orientation = + static_cast( txm->GetParent() )->GetOrientation(); + txm->SetTextAngle( orientation + txm->GetTextAngle() ); + } + txm->SetLocalCoord(); + } + + PCB_LAYER_ID klayer = GetKicadLayer( elem.layer ); + if( klayer == UNDEFINED_LAYER ) + { + wxLogInfo( wxString::Format( + _( "Text on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ), + elem.layer ) ); + klayer = Eco1_User; + } + itm->SetLayer( klayer ); + + tx->SetTextSize( wxSize( elem.height, elem.height ) ); // TODO: parse text width + tx->SetThickness( elem.strokewidth ); + tx->SetMirrored( elem.mirrored ); + if( elem.isDesignator || elem.isComment ) // That's just a bold assumption + { + tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_LEFT ); + tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_BOTTOM ); + } + else + { + switch( elem.textposition ) + { + case ALTIUM_TEXT_POSITION::LEFT_TOP: + case ALTIUM_TEXT_POSITION::LEFT_CENTER: + case ALTIUM_TEXT_POSITION::LEFT_BOTTOM: + tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_LEFT ); + break; + case ALTIUM_TEXT_POSITION::CENTER_TOP: + case ALTIUM_TEXT_POSITION::CENTER_CENTER: + case ALTIUM_TEXT_POSITION::CENTER_BOTTOM: + tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_CENTER ); + break; + case ALTIUM_TEXT_POSITION::RIGHT_TOP: + case ALTIUM_TEXT_POSITION::RIGHT_CENTER: + case ALTIUM_TEXT_POSITION::RIGHT_BOTTOM: + tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_RIGHT ); + break; + default: + wxLogError( _( "Unexpected horizontal Text Position. This should never happen." ) ); + break; + } + + switch( elem.textposition ) + { + case ALTIUM_TEXT_POSITION::LEFT_TOP: + case ALTIUM_TEXT_POSITION::CENTER_TOP: + case ALTIUM_TEXT_POSITION::RIGHT_TOP: + tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_TOP ); + break; + case ALTIUM_TEXT_POSITION::LEFT_CENTER: + case ALTIUM_TEXT_POSITION::CENTER_CENTER: + case ALTIUM_TEXT_POSITION::RIGHT_CENTER: + tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_CENTER ); + break; + case ALTIUM_TEXT_POSITION::LEFT_BOTTOM: + case ALTIUM_TEXT_POSITION::CENTER_BOTTOM: + case ALTIUM_TEXT_POSITION::RIGHT_BOTTOM: + tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_BOTTOM ); + break; + default: + wxLogError( _( "Unexpected vertical text position. This should never happen." ) ); + break; + } + } + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Texts6 stream is not fully parsed" ) ); + } +} + +void ALTIUM_PCB::ParseFills6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + AFILL6 elem( reader ); + + SHAPE_LINE_CHAIN linechain; + + wxPoint p11( elem.pos1.x, elem.pos1.y ); + wxPoint p12( elem.pos1.x, elem.pos2.y ); + wxPoint p22( elem.pos2.x, elem.pos2.y ); + wxPoint p21( elem.pos2.x, elem.pos1.y ); + + // rotate of the linechain behaves different than this? + wxPoint center( ( elem.pos1.x + elem.pos2.x ) / 2, ( elem.pos1.y + elem.pos2.y ) / 2 ); + RotatePoint( &p11, center, elem.rotation * 10. ); + RotatePoint( &p12, center, elem.rotation * 10. ); + RotatePoint( &p22, center, elem.rotation * 10. ); + RotatePoint( &p21, center, elem.rotation * 10. ); + + linechain.Append( p11 ); + linechain.Append( p12 ); + linechain.Append( p22 ); + linechain.Append( p21 ); + linechain.Append( p11 ); + linechain.SetClosed( true ); + + SHAPE_POLY_SET* outline = new SHAPE_POLY_SET(); + outline->AddOutline( linechain ); + + PCB_LAYER_ID klayer = GetKicadLayer( elem.layer ); + if( klayer == UNDEFINED_LAYER ) + { + wxLogInfo( wxString::Format( + _( "Fill on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ), + elem.layer ) ); + klayer = Eco1_User; + } + + if( elem.is_keepout || elem.net != ALTIUM_NET_UNCONNECTED ) + { + ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board ); + m_board->Add( zone, ADD_MODE::APPEND ); + + zone->SetNetCode( GetNetCode( elem.net ) ); + zone->SetLayer( klayer ); + zone->SetPosition( elem.pos1 ); + zone->SetOutline( outline ); + zone->SetPriority( 1000 ); + + // should be correct? + zone->SetZoneClearance( 0 ); + zone->SetPadConnection( ZONE_CONNECTION::FULL ); + + if( elem.is_keepout ) + { + zone->SetIsKeepout( true ); + zone->SetDoNotAllowTracks( false ); + zone->SetDoNotAllowVias( false ); + zone->SetDoNotAllowCopperPour( true ); + } + + zone->SetHatch( ZONE_HATCH_STYLE::DIAGONAL_EDGE, zone->GetDefaultHatchPitch(), true ); + } + else + { + DRAWSEGMENT* ds = new DRAWSEGMENT( m_board ); + m_board->Add( ds, ADD_MODE::APPEND ); + + ds->SetShape( STROKE_T::S_POLYGON ); + ds->SetPolyShape( *outline ); + ds->SetLayer( klayer ); + } + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( _( "Fills6 stream is not fully parsed" ) ); + } +} diff --git a/pcbnew/altium2kicadpcb_plugin/altium_pcb.h b/pcbnew/altium2kicadpcb_plugin/altium_pcb.h new file mode 100644 index 0000000000..a9352eb48d --- /dev/null +++ b/pcbnew/altium2kicadpcb_plugin/altium_pcb.h @@ -0,0 +1,187 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019-2020 Thomas Pointhuber + * + * 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 ALTIUM_PCB_H +#define ALTIUM_PCB_H + +#include +#include +#include +#include + +#include + + +enum class ALTIUM_PCB_DIR +{ + FILE_HEADER, + + ADVANCEDPLACEROPTIONS6, + ARCS6, + BOARD6, + BOARDREGIONS, + CLASSES6, + COMPONENTBODIES6, + COMPONENTS6, + CONNECTIONS6, + COORDINATES6, + DESIGNRULECHECKEROPTIONS6, + DIFFERENTIALPAIRS6, + DIMENSIONS6, + EMBEDDEDBOARDS6, + EMBEDDEDFONTS6, + EMBEDDEDS6, + EXTENDPRIMITIVEINFORMATION, + FILEVERSIONINFO, + FILLS6, + FROMTOS6, + MODELS, + MODELSNOEMBED, + NETS6, + PADS6, + PADVIALIBRARY, + PADVIALIBRARYCACHE, + PADVIALIBRARYLINKS, + PINSWAPOPTIONS6, + PINPAIRSSECTION, + POLYGONS6, + REGIONS6, + RULES6, + SHAPEBASEDCOMPONENTBODIES6, + SHAPEBASEDREGIONS6, + SIGNALCLASSES, + SMARTUNIONS, + TEXTS, + TEXTS6, + TEXTURES, + TRACKS6, + UNIONNAMES, + UNIQUEIDPRIMITIVEINFORMATION, + VIAS6, + WIDESTRINGS6 +}; + + +class BOARD; +class MODULE; +class ZONE_CONTAINER; + + +/** + * Helper method which opens a Altium Board File and parses it. + * + * @param aBoard board the pcb should be appended to + * @param aFileName file name of board file + * @param aFileMapping mapping how altium stream names are mapped + */ +void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName, + const std::map& aFileMapping ); + + +namespace CFB +{ +class CompoundFileReader; +struct COMPOUND_FILE_ENTRY; +} // namespace CFB + + +// type declaration required for a helper method +class ALTIUM_PCB; +typedef std::function + PARSE_FUNCTION_POINTER_fp; + + +class ALTIUM_PCB +{ +public: + explicit ALTIUM_PCB( BOARD* aBoard ); + ~ALTIUM_PCB(); + + void Parse( const CFB::CompoundFileReader& aReader, + const std::map& aFileMapping ); + +private: + PCB_LAYER_ID GetKicadLayer( ALTIUM_LAYER aAltiumLayer ) const; + int GetNetCode( uint16_t aId ) const; + const ARULE6* GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const; + const ARULE6* GetRuleDefault( ALTIUM_RULE_KIND aKind ) const; + + void ParseFileHeader( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + + // Text Format + void ParseBoard6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseClasses6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseComponents6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseDimensions6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseNets6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParsePolygons6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseRules6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + + // Binary Format + void ParseArcs6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParsePads6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseVias6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseTracks6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseTexts6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseFills6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseBoardRegionsData( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseShapeBasedRegions6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseRegions6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + + // Helper Functions + void HelperParseDimensions6Linear( const ADIMENSION6& aElem ); + void HelperParseDimensions6Leader( const ADIMENSION6& aElem ); + void HelperParseDimensions6Datum( const ADIMENSION6& aElem ); + void HelperParseDimensions6Center( const ADIMENSION6& aElem ); + + void HelperCreateBoardOutline( const std::vector& aVertices ); + + BOARD* m_board; + std::vector m_components; + std::vector m_polygons; + size_t m_num_nets; + std::map m_layermap; // used to correctly map copper layers + std::map> m_rules; + + std::map m_outer_plane; +}; + + +#endif //ALTIUM_PCB_H diff --git a/pcbnew/eagle_plugin.h b/pcbnew/eagle_plugin.h index ff7b034429..9af530ce67 100644 --- a/pcbnew/eagle_plugin.h +++ b/pcbnew/eagle_plugin.h @@ -34,6 +34,7 @@ class D_PAD; class TEXTE_MODULE; +class ZONE_CONTAINER; typedef std::map MODULE_MAP; typedef std::vector ZONES; diff --git a/pcbnew/files.cpp b/pcbnew/files.cpp index a375b3423f..67c6d0535b 100644 --- a/pcbnew/files.cpp +++ b/pcbnew/files.cpp @@ -76,17 +76,23 @@ bool AskLoadBoardFileName( wxWindow* aParent, int* aCtl, wxString* aFileName, bo // load a BOARD. User may occasionally use the wrong plugin to load a // *.brd file (since both legacy and eagle use *.brd extension), // but eventually *.kicad_pcb will be more common than legacy *.brd files. + + // clang-format off static const struct { const wxString& filter; IO_MGR::PCB_FILE_T pluginType; } loaders[] = { - { PcbFileWildcard(), IO_MGR::KICAD_SEXP }, // Current Kicad board files - { LegacyPcbFileWildcard(), IO_MGR::LEGACY }, // Old Kicad board files - { EaglePcbFileWildcard(), IO_MGR::EAGLE }, // Import board files - { PCadPcbFileWildcard(), IO_MGR::PCAD }, // Import board files + { PcbFileWildcard(), IO_MGR::KICAD_SEXP }, // Current Kicad board files + { LegacyPcbFileWildcard(), IO_MGR::LEGACY }, // Old Kicad board files + { EaglePcbFileWildcard(), IO_MGR::EAGLE }, // Import Eagle board files + { PCadPcbFileWildcard(), IO_MGR::PCAD }, // Import PCAD board files + { AltiumDesignerPcbFileWildcard(), IO_MGR::ALTIUM_DESIGNER }, // Import Altium Designer board files + { AltiumCircuitStudioPcbFileWildcard(), IO_MGR::ALTIUM_CIRCUIT_STUDIO }, // Import Altium Circuit Studio board files + { AltiumCircuitMakerPcbFileWildcard(), IO_MGR::ALTIUM_CIRCUIT_MAKER }, // Import Altium Circuit Maker board files }; + // clang-format on wxFileName fileName( *aFileName ); wxString fileFilters; @@ -357,7 +363,7 @@ IO_MGR::PCB_FILE_T plugin_type( const wxString& aFileName, int aCtl ) wxFileName fn = aFileName; - // Note: file extensions are expected to be in ower case. + // Note: file extensions are expected to be in lower case. // This is not always true, especially when importing files, so the string // comparisons are case insensitive to try to find the suitable plugin. @@ -366,10 +372,22 @@ IO_MGR::PCB_FILE_T plugin_type( const wxString& aFileName, int aCtl ) // both legacy and eagle share a common file extension. pluginType = ( aCtl & KICTL_EAGLE_BRD ) ? IO_MGR::EAGLE : IO_MGR::LEGACY; } - else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::PCAD ) ) == 0 ) + else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::PCAD ) ) == 0 ) { pluginType = IO_MGR::PCAD; } + else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::ALTIUM_DESIGNER ) ) == 0 ) + { + pluginType = IO_MGR::ALTIUM_DESIGNER; + } + else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::ALTIUM_CIRCUIT_STUDIO ) ) == 0 ) + { + pluginType = IO_MGR::ALTIUM_CIRCUIT_STUDIO; + } + else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::ALTIUM_CIRCUIT_MAKER ) ) == 0 ) + { + pluginType = IO_MGR::ALTIUM_CIRCUIT_MAKER; + } else { pluginType = IO_MGR::KICAD_SEXP; diff --git a/pcbnew/io_mgr.cpp b/pcbnew/io_mgr.cpp index f46dc638f3..69eaea8997 100644 --- a/pcbnew/io_mgr.cpp +++ b/pcbnew/io_mgr.cpp @@ -25,13 +25,16 @@ #include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #if defined(BUILD_GITHUB_PLUGIN) #include @@ -204,6 +207,14 @@ void IO_MGR::Save( PCB_FILE_T aFileType, const wxString& aFileName, BOARD* aBoar static IO_MGR::REGISTER_PLUGIN registerEaglePlugin( IO_MGR::EAGLE, wxT("Eagle"), []() -> PLUGIN* { return new EAGLE_PLUGIN; } ); static IO_MGR::REGISTER_PLUGIN registerKicadPlugin( IO_MGR::KICAD_SEXP, wxT("KiCad"), []() -> PLUGIN* { return new PCB_IO; } ); static IO_MGR::REGISTER_PLUGIN registerPcadPlugin( IO_MGR::PCAD, wxT("P-Cad"), []() -> PLUGIN* { return new PCAD_PLUGIN; } ); +static IO_MGR::REGISTER_PLUGIN registerAltiumDesignerPlugin( IO_MGR::ALTIUM_DESIGNER, + wxT( "Altium Designer" ), []() -> PLUGIN* { return new ALTIUM_DESIGNER_PLUGIN; } ); +static IO_MGR::REGISTER_PLUGIN registerAltiumCircuitStudioPlugin( IO_MGR::ALTIUM_CIRCUIT_STUDIO, + wxT( "Altium Circuit Studio" ), + []() -> PLUGIN* { return new ALTIUM_CIRCUIT_STUDIO_PLUGIN; } ); +static IO_MGR::REGISTER_PLUGIN registerAltiumCircuitMakerPlugin( IO_MGR::ALTIUM_CIRCUIT_MAKER, + wxT( "Altium Circuit Maker" ), + []() -> PLUGIN* { return new ALTIUM_CIRCUIT_MAKER_PLUGIN; } ); #ifdef BUILD_GITHUB_PLUGIN static IO_MGR::REGISTER_PLUGIN registerGithubPlugin( IO_MGR::GITHUB, wxT("Github"), []() -> PLUGIN* { return new GITHUB_PLUGIN; } ); #endif /* BUILD_GITHUB_PLUGIN */ diff --git a/pcbnew/io_mgr.h b/pcbnew/io_mgr.h index 849d87ba19..a2f865636e 100644 --- a/pcbnew/io_mgr.h +++ b/pcbnew/io_mgr.h @@ -53,19 +53,21 @@ public: */ enum PCB_FILE_T { - LEGACY, ///< Legacy Pcbnew file formats prior to s-expression. - KICAD_SEXP, ///< S-expression Pcbnew file format. + LEGACY, ///< Legacy Pcbnew file formats prior to s-expression. + KICAD_SEXP, ///< S-expression Pcbnew file format. EAGLE, PCAD, - GEDA_PCB, ///< Geda PCB file formats. + ALTIUM_DESIGNER, + ALTIUM_CIRCUIT_STUDIO, + ALTIUM_CIRCUIT_MAKER, + GEDA_PCB, ///< Geda PCB file formats. - //N.B. This needs to be commented out to ensure compile-type errors + //N.B. This needs to be commented out to ensure compile-type errors #if defined(BUILD_GITHUB_PLUGIN) - GITHUB, ///< Read only http://github.com repo holding pretty footprints + GITHUB, ///< Read only http://github.com repo holding pretty footprints #endif // add your type here. - // ALTIUM, // etc. FILE_TYPE_NONE diff --git a/qa/gal/gal_pixel_alignment/CMakeLists.txt b/qa/gal/gal_pixel_alignment/CMakeLists.txt index 22c428df83..dabe092c17 100644 --- a/qa/gal/gal_pixel_alignment/CMakeLists.txt +++ b/qa/gal/gal_pixel_alignment/CMakeLists.txt @@ -31,7 +31,7 @@ if( BUILD_GITHUB_PLUGIN ) set( GITHUB_PLUGIN_LIBRARIES github_plugin ) endif() -add_dependencies( pnsrouter pcbcommon pcad2kicadpcb ${GITHUB_PLUGIN_LIBRARIES} ) +add_dependencies( pnsrouter pcbcommon pcad2kicadpcb altium2kicadpcb ${GITHUB_PLUGIN_LIBRARIES} ) add_executable(test_gal_pixel_alignment WIN32 test_gal_pixel_alignment.cpp @@ -72,6 +72,7 @@ target_link_libraries( test_gal_pixel_alignment pnsrouter pcbcommon pcad2kicadpcb + altium2kicadpcb bitmaps 3d-viewer gal diff --git a/qa/pcbnew/CMakeLists.txt b/qa/pcbnew/CMakeLists.txt index d45aaefad6..bea3ca1866 100644 --- a/qa/pcbnew/CMakeLists.txt +++ b/qa/pcbnew/CMakeLists.txt @@ -67,6 +67,7 @@ target_link_libraries( qa_pcbnew pcbcommon pnsrouter pcad2kicadpcb + altium2kicadpcb gal common gal diff --git a/qa/pcbnew_tools/CMakeLists.txt b/qa/pcbnew_tools/CMakeLists.txt index 75ff680187..e2ef6740ea 100644 --- a/qa/pcbnew_tools/CMakeLists.txt +++ b/qa/pcbnew_tools/CMakeLists.txt @@ -54,6 +54,7 @@ target_link_libraries( qa_pcbnew_tools pcbcommon pnsrouter pcad2kicadpcb + altium2kicadpcb gal dxflib_qcad tinyspline_lib diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 28a7ab1940..d1aa51bacb 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -22,6 +22,7 @@ # add_subdirectory( clipper ) +add_subdirectory( compoundfilereader ) add_subdirectory( dxflib_qcad ) add_subdirectory( libcontext ) add_subdirectory( markdown2html ) diff --git a/thirdparty/compoundfilereader/CMakeLists.txt b/thirdparty/compoundfilereader/CMakeLists.txt new file mode 100644 index 0000000000..0fa8173732 --- /dev/null +++ b/thirdparty/compoundfilereader/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library( compoundfilereader INTERFACE ) + +target_include_directories( compoundfilereader INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/thirdparty/compoundfilereader/LICENSE.MIT b/thirdparty/compoundfilereader/LICENSE.MIT new file mode 100644 index 0000000000..dc274272c4 --- /dev/null +++ b/thirdparty/compoundfilereader/LICENSE.MIT @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/thirdparty/compoundfilereader/README.txt b/thirdparty/compoundfilereader/README.txt new file mode 100644 index 0000000000..dcd254145a --- /dev/null +++ b/thirdparty/compoundfilereader/README.txt @@ -0,0 +1,4 @@ +This directory contains the microsoft/compoundfilereader project from https://github.com/microsoft/compoundfilereader. + +They are licensed under MIT, with the license text in this directory. + diff --git a/thirdparty/compoundfilereader/compoundfilereader.h b/thirdparty/compoundfilereader/compoundfilereader.h new file mode 100644 index 0000000000..b647a1fe13 --- /dev/null +++ b/thirdparty/compoundfilereader/compoundfilereader.h @@ -0,0 +1,480 @@ +/** + Microsoft Compound File (and Property Set) Reader + http://en.wikipedia.org/wiki/Compound_File_Binary_Format + + Format specification: + MS-CFB: https://msdn.microsoft.com/en-us/library/dd942138.aspx + MS-OLEPS: https://msdn.microsoft.com/en-us/library/dd942421.aspx + + Note: + 1. For simplification, the code assumes that the target system is little-endian. + + 2. The reader operates the passed buffer in-place. + You must keep the input buffer valid when you are using the reader. + + 3. Single thread usage. + + Example 1: print all streams in a compound file + \code + CFB::CompoundFileReader reader(buffer, len); + reader.EnumFiles(reader.GetRootEntry(), -1, + [&](const CFB::COMPOUND_FILE_ENTRY* entry, const CFB::utf16string& dir, int level)->void + { + bool isDirectory = !reader.IsStream(entry); + std::string name = UTF16ToUTF8(entry->name); + std::string indentstr(level * 4, ' '); + printf("%s%s%s%s\n", indentstr.c_str(), isDirectory ? "[" : "", name.c_str(), isDirectory ? "]" : ""); + }); + \endcode +*/ + +#include +#include +#include +#include +#include +#include + +namespace CFB +{ + struct CFBException : public std::runtime_error + { + CFBException(const char* desc) : std::runtime_error(desc) {} + }; + struct WrongFormat : public CFBException + { + WrongFormat() : CFBException("Wrong file format") {} + }; + struct FileCorrupted : public CFBException + { + FileCorrupted() : CFBException("File corrupted") {} + }; + +#pragma pack(push) +#pragma pack(1) + +struct COMPOUND_FILE_HDR +{ + unsigned char signature[8]; + unsigned char unused_clsid[16]; + uint16_t minorVersion; + uint16_t majorVersion; + uint16_t byteOrder; + uint16_t sectorShift; + uint16_t miniSectorShift; + unsigned char reserved[6]; + uint32_t numDirectorySector; + uint32_t numFATSector; + uint32_t firstDirectorySectorLocation; + uint32_t transactionSignatureNumber; + uint32_t miniStreamCutoffSize; + uint32_t firstMiniFATSectorLocation; + uint32_t numMiniFATSector; + uint32_t firstDIFATSectorLocation; + uint32_t numDIFATSector; + uint32_t headerDIFAT[109]; +}; + +struct COMPOUND_FILE_ENTRY +{ + uint16_t name[32]; + uint16_t nameLen; + uint8_t type; + uint8_t colorFlag; + uint32_t leftSiblingID; // Note that it's actually the left/right child in the RB-tree. + uint32_t rightSiblingID; // So entry.leftSibling.rightSibling does NOT go back to entry. + uint32_t childID; + unsigned char clsid[16]; + uint32_t stateBits; + uint64_t creationTime; + uint64_t modifiedTime; + uint32_t startSectorLocation; + uint64_t size; +}; + +struct PROPERTY_SET_STREAM_HDR +{ + unsigned char byteOrder[2]; + uint16_t version; + uint32_t systemIdentifier; + unsigned char clsid[16]; + uint32_t numPropertySets; + struct + { + char fmtid[16]; + uint32_t offset; + } propertySetInfo[1]; +}; + +struct PROPERTY_SET_HDR +{ + uint32_t size; + uint32_t NumProperties; + struct + { + uint32_t id; + uint32_t offset; + } propertyIdentifierAndOffset[1]; +}; + +#pragma pack(pop) + +const size_t MAXREGSECT = 0xFFFFFFFA; + +struct helper +{ + static uint32_t ParseUint32(const void* buffer) + { + return *static_cast(buffer); + } +}; + +typedef std::basic_string utf16string; +typedef std::function + EnumFilesCallback; + +class CompoundFileReader +{ +public: + CompoundFileReader(const void* buffer, size_t len) + : m_buffer(static_cast(buffer)) + , m_bufferLen(len) + , m_hdr(static_cast(buffer)) + , m_sectorSize(512) + , m_minisectorSize(64) + , m_miniStreamStartSector(0) + { + if (buffer == NULL || len == 0) throw std::invalid_argument(""); + + if (m_bufferLen < sizeof(*m_hdr) || + memcmp(m_hdr->signature, "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1", 8) != 0) + { + throw WrongFormat(); + } + + m_sectorSize = m_hdr->majorVersion == 3 ? 512 : 4096; + + // The file must contains at least 3 sectors + if (m_bufferLen < m_sectorSize * 3) throw FileCorrupted(); + + const COMPOUND_FILE_ENTRY* root = GetEntry(0); + if (root == NULL) throw FileCorrupted(); + + m_miniStreamStartSector = root->startSectorLocation; + } + + /// Get entry (directory or file) by its ID. + /// Pass "0" to get the root directory entry. -- This is the start point to navigate the compound file. + /// Use the returned object to access child entries. + const COMPOUND_FILE_ENTRY* GetEntry(size_t entryID) const + { + if (entryID == 0xFFFFFFFF) + { + return NULL; + } + + if (m_bufferLen / sizeof(COMPOUND_FILE_ENTRY) <= entryID) + { + throw std::invalid_argument(""); + } + + size_t sector = 0; + size_t offset = 0; + LocateFinalSector(m_hdr->firstDirectorySectorLocation, entryID * sizeof(COMPOUND_FILE_ENTRY), §or, &offset); + return reinterpret_cast(SectorOffsetToAddress(sector, offset)); + } + + const COMPOUND_FILE_ENTRY* GetRootEntry() const + { + return GetEntry(0); + } + + const COMPOUND_FILE_HDR* GetFileInfo() const + { + return m_hdr; + } + + /// Get file(stream) data start with "offset". + /// The buffer must have enough space to store "len" bytes. Typically "len" is derived by the steam length. + void ReadFile(const COMPOUND_FILE_ENTRY* entry, size_t offset, char* buffer, size_t len) const + { + if (entry->size < offset || entry->size - offset < len) throw std::invalid_argument(""); + + if (entry->size < m_hdr->miniStreamCutoffSize) + { + ReadMiniStream(entry->startSectorLocation, offset, buffer, len); + } + else + { + ReadStream(entry->startSectorLocation, offset, buffer, len); + } + } + + bool IsPropertyStream(const COMPOUND_FILE_ENTRY* entry) const + { + // defined in [MS-OLEPS] 2.23 "Property Set Stream and Storage Names" + return entry->name[0] == 5; + } + + bool IsStream(const COMPOUND_FILE_ENTRY* entry) const + { + return entry->type == 2; + } + + void EnumFiles(const COMPOUND_FILE_ENTRY* entry, int maxLevel, EnumFilesCallback callback) const + { + utf16string dir; + EnumNodes(GetEntry(entry->childID), 0, maxLevel, dir, callback); + } + +private: + + // Enum entries with same level, including 'entry' itself + void EnumNodes(const COMPOUND_FILE_ENTRY* entry, int currentLevel, int maxLevel, + const utf16string& dir, EnumFilesCallback callback) const + { + if (maxLevel > 0 && currentLevel >= maxLevel) + return; + if (entry == nullptr) + return; + + callback(entry, dir, currentLevel + 1); + + const COMPOUND_FILE_ENTRY* child = GetEntry(entry->childID); + if (child != nullptr) + { + utf16string newDir = dir; + if (dir.length() != 0) + newDir.append(1, '\n'); + newDir.append(entry->name, entry->nameLen / 2); + EnumNodes(GetEntry(entry->childID), currentLevel + 1, maxLevel, newDir, callback); + } + + EnumNodes(GetEntry(entry->leftSiblingID), currentLevel, maxLevel, dir, callback); + EnumNodes(GetEntry(entry->rightSiblingID), currentLevel, maxLevel, dir, callback); + } + + void ReadStream(size_t sector, size_t offset, char* buffer, size_t len) const + { + LocateFinalSector(sector, offset, §or, &offset); + + // copy as many as possible in each step + // copylen typically iterate as: m_sectorSize - offset --> m_sectorSize --> m_sectorSize --> ... --> remaining + while (len > 0) + { + const unsigned char* src = SectorOffsetToAddress(sector, offset); + size_t copylen = std::min(len, m_sectorSize - offset); + if (m_buffer + m_bufferLen < src + copylen) throw FileCorrupted(); + + memcpy(buffer, src, copylen); + buffer += copylen; + len -= copylen; + sector = GetNextSector(sector); + offset = 0; + } + } + + // Same logic as "ReadStream" except that use MiniStream functions instead + void ReadMiniStream(size_t sector, size_t offset, char* buffer, size_t len) const + { + LocateFinalMiniSector(sector, offset, §or, &offset); + + // copy as many as possible in each step + // copylen typically iterate as: m_sectorSize - offset --> m_sectorSize --> m_sectorSize --> ... --> remaining + while (len > 0) + { + const unsigned char* src = MiniSectorOffsetToAddress(sector, offset); + size_t copylen = std::min(len, m_minisectorSize - offset); + if (m_buffer + m_bufferLen < src + copylen) throw FileCorrupted(); + + memcpy(buffer, src, copylen); + buffer += copylen; + len -= copylen; + sector = GetNextMiniSector(sector); + offset = 0; + } + } + + size_t GetNextSector(size_t sector) const + { + // lookup FAT + size_t entriesPerSector = m_sectorSize / 4; + size_t fatSectorNumber = sector / entriesPerSector; + size_t fatSectorLocation = GetFATSectorLocation(fatSectorNumber); + return helper::ParseUint32(SectorOffsetToAddress(fatSectorLocation, sector % entriesPerSector * 4)); + } + + size_t GetNextMiniSector(size_t miniSector) const + { + size_t sector, offset; + LocateFinalSector(m_hdr->firstMiniFATSectorLocation, miniSector * 4, §or, &offset); + return helper::ParseUint32(SectorOffsetToAddress(sector, offset)); + } + + // Get absolute address from sector and offset. + const unsigned char* SectorOffsetToAddress(size_t sector, size_t offset) const + { + if (sector >= MAXREGSECT || + offset >= m_sectorSize || + m_bufferLen <= static_cast(m_sectorSize) * sector + m_sectorSize + offset) + { + throw FileCorrupted(); + } + + return m_buffer + m_sectorSize + m_sectorSize * sector + offset; + } + + const unsigned char* MiniSectorOffsetToAddress(size_t sector, size_t offset) const + { + if (sector >= MAXREGSECT || + offset >= m_minisectorSize || + m_bufferLen <= static_cast(m_minisectorSize) * sector + offset) + { + throw FileCorrupted(); + } + + + LocateFinalSector(m_miniStreamStartSector, sector * m_minisectorSize + offset, §or, &offset); + return SectorOffsetToAddress(sector, offset); + } + + // Locate the final sector/offset when original offset expands multiple sectors + void LocateFinalSector(size_t sector, size_t offset, size_t* finalSector, size_t* finalOffset) const + { + while (offset >= m_sectorSize) + { + offset -= m_sectorSize; + sector = GetNextSector(sector); + } + *finalSector = sector; + *finalOffset = offset; + } + + void LocateFinalMiniSector(size_t sector, size_t offset, size_t* finalSector, size_t* finalOffset) const + { + while (offset >= m_minisectorSize) + { + offset -= m_minisectorSize; + sector = GetNextMiniSector(sector); + } + *finalSector = sector; + *finalOffset = offset; + } + + size_t GetFATSectorLocation(size_t fatSectorNumber) const + { + if (fatSectorNumber < 109) + { + return m_hdr->headerDIFAT[fatSectorNumber]; + } + else + { + fatSectorNumber -= 109; + size_t entriesPerSector = m_sectorSize / 4 - 1; + size_t difatSectorLocation = m_hdr->firstDIFATSectorLocation; + while (fatSectorNumber >= entriesPerSector) + { + fatSectorNumber -= entriesPerSector; + const unsigned char* addr = SectorOffsetToAddress(difatSectorLocation, m_sectorSize - 4); + difatSectorLocation = helper::ParseUint32(addr); + } + return helper::ParseUint32(SectorOffsetToAddress(difatSectorLocation, fatSectorNumber * 4)); + } + } + +private: + const unsigned char * m_buffer; + size_t m_bufferLen; + + const COMPOUND_FILE_HDR* m_hdr; + size_t m_sectorSize; + size_t m_minisectorSize; + size_t m_miniStreamStartSector; +}; + +class PropertySet +{ +public: + PropertySet(const void* buffer, size_t len, const char* fmtid) + : m_buffer(static_cast(buffer)) + , m_bufferLen(len) + , m_hdr(reinterpret_cast(buffer)) + , m_fmtid(fmtid) + { + if (m_bufferLen < sizeof(*m_hdr) || + m_bufferLen < sizeof(*m_hdr) + (m_hdr->NumProperties - 1) * sizeof(m_hdr->propertyIdentifierAndOffset[0])) + { + throw FileCorrupted(); + } + } + + /// return the string property in UTF-16 format + const uint16_t* GetStringProperty(uint32_t propertyID) + { + for (uint32_t i = 0; i < m_hdr->NumProperties; i++) + { + if (m_hdr->propertyIdentifierAndOffset[i].id == propertyID) + { + uint32_t offset = m_hdr->propertyIdentifierAndOffset[i].offset; + if (m_bufferLen < offset + 8) throw FileCorrupted(); + uint32_t stringLengthInChar = helper::ParseUint32(m_buffer + offset + 4); + if (m_bufferLen < offset + 8 + stringLengthInChar*2) throw FileCorrupted(); + return reinterpret_cast(m_buffer + offset + 8); + } + } + + return NULL; + } + + /// Note: Getting property of types other than "string" is not implemented yet. + /// However most other types are simpler than string so can be easily added. see [MS-OLEPS] + + const char* GetFmtID() + { + return m_fmtid; + } + +private: + const unsigned char* m_buffer; + size_t m_bufferLen; + const PROPERTY_SET_HDR* m_hdr; + const char* m_fmtid; // 16 bytes +}; + +class PropertySetStream +{ +public: + PropertySetStream(const void* buffer, size_t len) + : m_buffer(static_cast(buffer)) + , m_bufferLen(len) + , m_hdr(reinterpret_cast(buffer)) + { + if (m_bufferLen < sizeof(*m_hdr) || + m_bufferLen < sizeof(*m_hdr) + (m_hdr->numPropertySets - 1) * sizeof(m_hdr->propertySetInfo[0])) + { + throw FileCorrupted(); + } + } + + size_t GetPropertySetCount() + { + return m_hdr->numPropertySets; + } + + PropertySet GetPropertySet(size_t index) + { + if (index >= GetPropertySetCount()) throw FileCorrupted(); + uint32_t offset = m_hdr->propertySetInfo[index].offset; + if (m_bufferLen < offset + 4) throw FileCorrupted(); + uint32_t size = helper::ParseUint32(m_buffer + offset); + if (m_bufferLen < offset + size) throw FileCorrupted(); + return PropertySet(m_buffer + offset, size, m_hdr->propertySetInfo[index].fmtid); + } + +private: + const unsigned char * m_buffer; + size_t m_bufferLen; + const PROPERTY_SET_STREAM_HDR* m_hdr; +}; + +} diff --git a/thirdparty/compoundfilereader/utf.h b/thirdparty/compoundfilereader/utf.h new file mode 100644 index 0000000000..5b253a80f7 --- /dev/null +++ b/thirdparty/compoundfilereader/utf.h @@ -0,0 +1,137 @@ +#pragma once + +#include +#include + +template +static bool GetNextCodePointFromUTF16z(const T* u16, size_t* pos, uint32_t* cp) +{ + *cp = static_cast(u16[*pos]); + if (*cp == 0) + return false; + + (*pos)++; + if ((*cp & 0xFC00) == 0xD800) + { + uint16_t cp2 = static_cast(u16[*pos]); + if ((cp2 & 0xFC00) == 0xDC00) + { + (*pos)++; + *cp = (*cp << 10) + cp2 - 0x35FDC00; + } + } + return true; +} + +template +static bool GetNextCodePointFromUTF16(const T* u16, size_t len, size_t* pos, uint32_t* cp) +{ + if (len == 0) + return GetNextCodePointFromUTF16z(u16, pos, cp); + + if (*pos >= len) + return false; + + *cp = static_cast(u16[*pos]); + (*pos)++; + if ((*cp & 0xFC00) == 0xD800) + { + if (*pos < len) + { + uint16_t cp2 = static_cast(u16[*pos]); + if ((cp2 & 0xFC00) == 0xDC00) + { + (*pos)++; + *cp = (*cp << 10) + cp2 - 0x35FDC00; + } + } + } + return true; +} + +static int CodePointToUTF8(uint32_t cp, uint32_t* c1, uint32_t* c2, uint32_t* c3, uint32_t* c4) +{ + if (cp < 0x80) + { + *c1 = cp; + return 1; + } + else if (cp <= 0x7FF) + { + *c1 = (cp >> 6) + 0xC0; + *c2 = (cp & 0x3F) + 0x80; + return 2; + } + else if (cp <= 0xFFFF) + { + *c1 = (cp >> 12) + 0xE0; + *c2 = ((cp >> 6) & 0x3F) + 0x80; + *c3 = (cp & 0x3F) + 0x80; + return 3; + } + else if (cp <= 0x10FFFF) + { + *c1 = (cp >> 18) + 0xF0; + *c2 = ((cp >> 12) & 0x3F) + 0x80; + *c3 = ((cp >> 6) & 0x3F) + 0x80; + *c4 = (cp & 0x3F) + 0x80; + return 4; + } + return 0; +} + +template +std::string UTF16ToUTF8(const T* u16, size_t len = 0) +{ + std::string u8; + uint32_t cp; + size_t pos = 0; + while (GetNextCodePointFromUTF16(u16, len, &pos, &cp)) + { + uint32_t c[4]; + int count = CodePointToUTF8(cp, c, c+1, c+2, c+3); + for (int i = 0; i < count; i++) + { + u8 += static_cast(c[i]); + } + } + return u8; +} + +template +std::wstring UTF16ToWstring(const T* u16, size_t len = 0) +{ + std::wstring ret; +#ifdef _MSC_VER + while (*u16) ret += *u16++; +#else + uint32_t cp; + size_t pos = 0; + while (GetNextCodePointFromUTF16(u16, len, &pos, &cp)) + { + ret += cp; + } +#endif + return ret; +} + +template +std::string WstringToUTF8(const T* wstr) +{ +#ifdef _MSC_VER + return UTF16ToUTF8(wstr); +#else + std::string u8; + uint32_t cp; + while ((cp = *wstr++) != 0) + { + uint32_t c[4]; + int count = CodePointToUTF8(cp, c, c+1, c+2, c+3); + for (int i = 0; i < count; i++) + { + u8 += static_cast(c[i]); + } + } + return u8; +#endif +}