From 1506beecbcd7ae2f47e5807edaaf4d623ffff819 Mon Sep 17 00:00:00 2001 From: Eric <840064358@qq.com> Date: Sat, 14 Sep 2024 15:34:51 +0000 Subject: [PATCH] Implement ODB++ export ADDED: Add support in Pcbnew for exporting ODB++ files under Fabrication Outputs, base on ODB++Design Format Specification (Release v8.1 Update 3 February 2021). Note: There is still a lot of work to do if we will make the feature as complete as the ODB++ spec. However, the current functionality's completeness is already sufficient to cover general production scenarios. I have compared the output results with Gerber files by DFM tool and the accuracy at the graphic level should be able to cover most usage scenarios. Additionally, I am very grateful to the great open-source project Horizon EDA for giving me a lot of inspiration in terms of ideas. The feature can be enabled by adding "EnableODB=1" to the kicad_advanced configuration file. Fixes https://gitlab.com/kicad/code/kicad/-/issues/2019 --- common/advanced_config.cpp | 4 + common/bitmap_info.cpp | 10 + include/advanced_config.h | 9 +- include/bitmaps/bitmaps_list.h | 1 + include/project/project_file.h | 1 + pcbnew/CMakeLists.txt | 5 +- pcbnew/dialogs/dialog_export_odbpp.cpp | 112 ++ pcbnew/dialogs/dialog_export_odbpp.fbp | 671 +++++++++++ pcbnew/dialogs/dialog_export_odbpp.h | 56 + pcbnew/dialogs/dialog_export_odbpp_base.cpp | 117 ++ pcbnew/dialogs/dialog_export_odbpp_base.h | 72 ++ pcbnew/files.cpp | 143 +++ pcbnew/menubar_pcb_editor.cpp | 4 + pcbnew/pcb_edit_frame.h | 5 + pcbnew/pcb_io/odbpp/CMakeLists.txt | 38 + pcbnew/pcb_io/odbpp/odb_attribute.cpp | 101 ++ pcbnew/pcb_io/odbpp/odb_attribute.h | 352 ++++++ pcbnew/pcb_io/odbpp/odb_component.cpp | 94 ++ pcbnew/pcb_io/odbpp/odb_component.h | 99 ++ pcbnew/pcb_io/odbpp/odb_defines.h | 34 + pcbnew/pcb_io/odbpp/odb_eda_data.cpp | 420 +++++++ pcbnew/pcb_io/odbpp/odb_eda_data.h | 340 ++++++ pcbnew/pcb_io/odbpp/odb_entity.cpp | 975 ++++++++++++++++ pcbnew/pcb_io/odbpp/odb_entity.h | 273 +++++ pcbnew/pcb_io/odbpp/odb_feature.cpp | 1002 +++++++++++++++++ pcbnew/pcb_io/odbpp/odb_feature.h | 349 ++++++ pcbnew/pcb_io/odbpp/odb_fonts.cpp | 671 +++++++++++ pcbnew/pcb_io/odbpp/odb_netlist.cpp | 285 +++++ pcbnew/pcb_io/odbpp/odb_netlist.h | 77 ++ pcbnew/pcb_io/odbpp/odb_util.cpp | 352 ++++++ pcbnew/pcb_io/odbpp/odb_util.h | 355 ++++++ pcbnew/pcb_io/odbpp/pcb_io_odbpp.cpp | 170 +++ pcbnew/pcb_io/odbpp/pcb_io_odbpp.h | 182 +++ pcbnew/pcb_io/pcb_io_mgr.cpp | 8 + pcbnew/pcb_io/pcb_io_mgr.h | 1 + pcbnew/pcbnew_settings.h | 9 + pcbnew/tools/board_editor_control.cpp | 3 + pcbnew/tools/pcb_actions.cpp | 7 + pcbnew/tools/pcb_actions.h | 1 + resources/bitmaps_png/CMakeLists.txt | 1 + resources/bitmaps_png/png/post_odb_16.png | Bin 0 -> 592 bytes resources/bitmaps_png/png/post_odb_24.png | Bin 0 -> 781 bytes resources/bitmaps_png/png/post_odb_32.png | Bin 0 -> 1061 bytes resources/bitmaps_png/png/post_odb_48.png | Bin 0 -> 1517 bytes resources/bitmaps_png/png/post_odb_64.png | Bin 0 -> 2010 bytes .../bitmaps_png/png/post_odb_dark_16.png | Bin 0 -> 557 bytes .../bitmaps_png/png/post_odb_dark_24.png | Bin 0 -> 760 bytes .../bitmaps_png/png/post_odb_dark_32.png | Bin 0 -> 1009 bytes .../bitmaps_png/png/post_odb_dark_48.png | Bin 0 -> 1488 bytes .../bitmaps_png/png/post_odb_dark_64.png | Bin 0 -> 1976 bytes .../bitmaps_png/sources/dark/post_odb.svg | 147 +++ .../bitmaps_png/sources/light/post_odb.svg | 145 +++ 52 files changed, 7698 insertions(+), 3 deletions(-) create mode 100644 pcbnew/dialogs/dialog_export_odbpp.cpp create mode 100644 pcbnew/dialogs/dialog_export_odbpp.fbp create mode 100644 pcbnew/dialogs/dialog_export_odbpp.h create mode 100644 pcbnew/dialogs/dialog_export_odbpp_base.cpp create mode 100644 pcbnew/dialogs/dialog_export_odbpp_base.h create mode 100644 pcbnew/pcb_io/odbpp/CMakeLists.txt create mode 100644 pcbnew/pcb_io/odbpp/odb_attribute.cpp create mode 100644 pcbnew/pcb_io/odbpp/odb_attribute.h create mode 100644 pcbnew/pcb_io/odbpp/odb_component.cpp create mode 100644 pcbnew/pcb_io/odbpp/odb_component.h create mode 100644 pcbnew/pcb_io/odbpp/odb_defines.h create mode 100644 pcbnew/pcb_io/odbpp/odb_eda_data.cpp create mode 100644 pcbnew/pcb_io/odbpp/odb_eda_data.h create mode 100644 pcbnew/pcb_io/odbpp/odb_entity.cpp create mode 100644 pcbnew/pcb_io/odbpp/odb_entity.h create mode 100644 pcbnew/pcb_io/odbpp/odb_feature.cpp create mode 100644 pcbnew/pcb_io/odbpp/odb_feature.h create mode 100644 pcbnew/pcb_io/odbpp/odb_fonts.cpp create mode 100644 pcbnew/pcb_io/odbpp/odb_netlist.cpp create mode 100644 pcbnew/pcb_io/odbpp/odb_netlist.h create mode 100644 pcbnew/pcb_io/odbpp/odb_util.cpp create mode 100644 pcbnew/pcb_io/odbpp/odb_util.h create mode 100644 pcbnew/pcb_io/odbpp/pcb_io_odbpp.cpp create mode 100644 pcbnew/pcb_io/odbpp/pcb_io_odbpp.h create mode 100644 resources/bitmaps_png/png/post_odb_16.png create mode 100644 resources/bitmaps_png/png/post_odb_24.png create mode 100644 resources/bitmaps_png/png/post_odb_32.png create mode 100644 resources/bitmaps_png/png/post_odb_48.png create mode 100644 resources/bitmaps_png/png/post_odb_64.png create mode 100644 resources/bitmaps_png/png/post_odb_dark_16.png create mode 100644 resources/bitmaps_png/png/post_odb_dark_24.png create mode 100644 resources/bitmaps_png/png/post_odb_dark_32.png create mode 100644 resources/bitmaps_png/png/post_odb_dark_48.png create mode 100644 resources/bitmaps_png/png/post_odb_dark_64.png create mode 100644 resources/bitmaps_png/sources/dark/post_odb.svg create mode 100644 resources/bitmaps_png/sources/light/post_odb.svg diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp index c8764d2737..d135e64159 100644 --- a/common/advanced_config.cpp +++ b/common/advanced_config.cpp @@ -119,6 +119,7 @@ static const wxChar MinorSchematicGraphSize[] = wxT( "MinorSchematicGraphSize" ) static const wxChar ResolveTextRecursionDepth[] = wxT( "ResolveTextRecursionDepth" ); static const wxChar EnableExtensionSnaps[] = wxT( "EnableExtensionSnaps" ); static const wxChar EnableSnapAnchorsDebug[] = wxT( "EnableSnapAnchorsDebug" ); +static const wxChar EnableODB[] = wxT( "EnableODB" ); } // namespace KEYS @@ -527,6 +528,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg ) configParams.push_back( new PARAM_CFG_INT( true, AC_KEYS::ResolveTextRecursionDepth, &m_ResolveTextRecursionDepth, m_ResolveTextRecursionDepth, 0, 10 ) ); + + configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableODB, + &m_EnableODB, m_EnableODB ) ); configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableExtensionSnaps, &m_EnableExtensionSnaps, diff --git a/common/bitmap_info.cpp b/common/bitmap_info.cpp index 6f0ebbab78..093c9a3eb6 100644 --- a/common/bitmap_info.cpp +++ b/common/bitmap_info.cpp @@ -647,6 +647,7 @@ void BuildBitmapInfo( std::unordered_map>& aBi aBitmapInfoCache[BITMAPS::post_gerber].emplace_back( BITMAPS::post_gerber, wxT( "post_gerber_24.png" ), 24, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::post_rpt].emplace_back( BITMAPS::post_rpt, wxT( "post_rpt_24.png" ), 24, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::post_xml].emplace_back( BITMAPS::post_xml, wxT( "post_xml_24.png" ), 24, wxT( "light" ) ); + aBitmapInfoCache[BITMAPS::post_odb].emplace_back( BITMAPS::post_odb, wxT( "post_odb_24.png" ), 24, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::preference].emplace_back( BITMAPS::preference, wxT( "preference_24.png" ), 24, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::print_button].emplace_back( BITMAPS::print_button, wxT( "print_button_24.png" ), 24, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::project].emplace_back( BITMAPS::project, wxT( "project_24.png" ), 24, wxT( "light" ) ); @@ -1063,6 +1064,7 @@ void BuildBitmapInfo( std::unordered_map>& aBi aBitmapInfoCache[BITMAPS::post_gerber].emplace_back( BITMAPS::post_gerber, wxT( "post_gerber_dark_24.png" ), 24, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::post_rpt].emplace_back( BITMAPS::post_rpt, wxT( "post_rpt_dark_24.png" ), 24, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::post_xml].emplace_back( BITMAPS::post_xml, wxT( "post_xml_dark_24.png" ), 24, wxT( "dark" ) ); + aBitmapInfoCache[BITMAPS::post_odb].emplace_back( BITMAPS::post_odb, wxT( "post_odb_dark_24.png" ), 24, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::preference].emplace_back( BITMAPS::preference, wxT( "preference_dark_24.png" ), 24, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::print_button].emplace_back( BITMAPS::print_button, wxT( "print_button_dark_24.png" ), 24, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::project].emplace_back( BITMAPS::project, wxT( "project_dark_24.png" ), 24, wxT( "dark" ) ); @@ -1479,6 +1481,7 @@ void BuildBitmapInfo( std::unordered_map>& aBi aBitmapInfoCache[BITMAPS::post_gerber].emplace_back( BITMAPS::post_gerber, wxT( "post_gerber_16.png" ), 16, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::post_rpt].emplace_back( BITMAPS::post_rpt, wxT( "post_rpt_16.png" ), 16, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::post_xml].emplace_back( BITMAPS::post_xml, wxT( "post_xml_16.png" ), 16, wxT( "light" ) ); + aBitmapInfoCache[BITMAPS::post_odb].emplace_back( BITMAPS::post_odb, wxT( "post_odb_16.png" ), 16, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::preference].emplace_back( BITMAPS::preference, wxT( "preference_16.png" ), 16, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::print_button].emplace_back( BITMAPS::print_button, wxT( "print_button_16.png" ), 16, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::project].emplace_back( BITMAPS::project, wxT( "project_16.png" ), 16, wxT( "light" ) ); @@ -1895,6 +1898,7 @@ void BuildBitmapInfo( std::unordered_map>& aBi aBitmapInfoCache[BITMAPS::post_gerber].emplace_back( BITMAPS::post_gerber, wxT( "post_gerber_dark_16.png" ), 16, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::post_rpt].emplace_back( BITMAPS::post_rpt, wxT( "post_rpt_dark_16.png" ), 16, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::post_xml].emplace_back( BITMAPS::post_xml, wxT( "post_xml_dark_16.png" ), 16, wxT( "dark" ) ); + aBitmapInfoCache[BITMAPS::post_odb].emplace_back( BITMAPS::post_odb, wxT( "post_odb_dark_16.png" ), 16, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::preference].emplace_back( BITMAPS::preference, wxT( "preference_dark_16.png" ), 16, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::print_button].emplace_back( BITMAPS::print_button, wxT( "print_button_dark_16.png" ), 16, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::project].emplace_back( BITMAPS::project, wxT( "project_dark_16.png" ), 16, wxT( "dark" ) ); @@ -2311,6 +2315,7 @@ void BuildBitmapInfo( std::unordered_map>& aBi aBitmapInfoCache[BITMAPS::post_gerber].emplace_back( BITMAPS::post_gerber, wxT( "post_gerber_32.png" ), 32, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::post_rpt].emplace_back( BITMAPS::post_rpt, wxT( "post_rpt_32.png" ), 32, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::post_xml].emplace_back( BITMAPS::post_xml, wxT( "post_xml_32.png" ), 32, wxT( "light" ) ); + aBitmapInfoCache[BITMAPS::post_odb].emplace_back( BITMAPS::post_odb, wxT( "post_odb_32.png" ), 32, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::preference].emplace_back( BITMAPS::preference, wxT( "preference_32.png" ), 32, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::print_button].emplace_back( BITMAPS::print_button, wxT( "print_button_32.png" ), 32, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::project].emplace_back( BITMAPS::project, wxT( "project_32.png" ), 32, wxT( "light" ) ); @@ -2727,6 +2732,7 @@ void BuildBitmapInfo( std::unordered_map>& aBi aBitmapInfoCache[BITMAPS::post_gerber].emplace_back( BITMAPS::post_gerber, wxT( "post_gerber_dark_32.png" ), 32, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::post_rpt].emplace_back( BITMAPS::post_rpt, wxT( "post_rpt_dark_32.png" ), 32, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::post_xml].emplace_back( BITMAPS::post_xml, wxT( "post_xml_dark_32.png" ), 32, wxT( "dark" ) ); + aBitmapInfoCache[BITMAPS::post_odb].emplace_back( BITMAPS::post_odb, wxT( "post_odb_dark_32.png" ), 32, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::preference].emplace_back( BITMAPS::preference, wxT( "preference_dark_32.png" ), 32, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::print_button].emplace_back( BITMAPS::print_button, wxT( "print_button_dark_32.png" ), 32, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::project].emplace_back( BITMAPS::project, wxT( "project_dark_32.png" ), 32, wxT( "dark" ) ); @@ -3143,6 +3149,7 @@ void BuildBitmapInfo( std::unordered_map>& aBi aBitmapInfoCache[BITMAPS::post_gerber].emplace_back( BITMAPS::post_gerber, wxT( "post_gerber_48.png" ), 48, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::post_rpt].emplace_back( BITMAPS::post_rpt, wxT( "post_rpt_48.png" ), 48, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::post_xml].emplace_back( BITMAPS::post_xml, wxT( "post_xml_48.png" ), 48, wxT( "light" ) ); + aBitmapInfoCache[BITMAPS::post_odb].emplace_back( BITMAPS::post_odb, wxT( "post_odb_48.png" ), 48, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::preference].emplace_back( BITMAPS::preference, wxT( "preference_48.png" ), 48, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::print_button].emplace_back( BITMAPS::print_button, wxT( "print_button_48.png" ), 48, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::project].emplace_back( BITMAPS::project, wxT( "project_48.png" ), 48, wxT( "light" ) ); @@ -3559,6 +3566,7 @@ void BuildBitmapInfo( std::unordered_map>& aBi aBitmapInfoCache[BITMAPS::post_gerber].emplace_back( BITMAPS::post_gerber, wxT( "post_gerber_dark_48.png" ), 48, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::post_rpt].emplace_back( BITMAPS::post_rpt, wxT( "post_rpt_dark_48.png" ), 48, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::post_xml].emplace_back( BITMAPS::post_xml, wxT( "post_xml_dark_48.png" ), 48, wxT( "dark" ) ); + aBitmapInfoCache[BITMAPS::post_odb].emplace_back( BITMAPS::post_odb, wxT( "post_odb_dark_48.png" ), 48, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::preference].emplace_back( BITMAPS::preference, wxT( "preference_dark_48.png" ), 48, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::print_button].emplace_back( BITMAPS::print_button, wxT( "print_button_dark_48.png" ), 48, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::project].emplace_back( BITMAPS::project, wxT( "project_dark_48.png" ), 48, wxT( "dark" ) ); @@ -3975,6 +3983,7 @@ void BuildBitmapInfo( std::unordered_map>& aBi aBitmapInfoCache[BITMAPS::post_gerber].emplace_back( BITMAPS::post_gerber, wxT( "post_gerber_64.png" ), 64, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::post_rpt].emplace_back( BITMAPS::post_rpt, wxT( "post_rpt_64.png" ), 64, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::post_xml].emplace_back( BITMAPS::post_xml, wxT( "post_xml_64.png" ), 64, wxT( "light" ) ); + aBitmapInfoCache[BITMAPS::post_odb].emplace_back( BITMAPS::post_odb, wxT( "post_odb_64.png" ), 64, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::preference].emplace_back( BITMAPS::preference, wxT( "preference_64.png" ), 64, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::print_button].emplace_back( BITMAPS::print_button, wxT( "print_button_64.png" ), 64, wxT( "light" ) ); aBitmapInfoCache[BITMAPS::project].emplace_back( BITMAPS::project, wxT( "project_64.png" ), 64, wxT( "light" ) ); @@ -4391,6 +4400,7 @@ void BuildBitmapInfo( std::unordered_map>& aBi aBitmapInfoCache[BITMAPS::post_gerber].emplace_back( BITMAPS::post_gerber, wxT( "post_gerber_dark_64.png" ), 64, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::post_rpt].emplace_back( BITMAPS::post_rpt, wxT( "post_rpt_dark_64.png" ), 64, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::post_xml].emplace_back( BITMAPS::post_xml, wxT( "post_xml_dark_64.png" ), 64, wxT( "dark" ) ); + aBitmapInfoCache[BITMAPS::post_odb].emplace_back( BITMAPS::post_odb, wxT( "post_odb_dark_64.png" ), 64, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::preference].emplace_back( BITMAPS::preference, wxT( "preference_dark_64.png" ), 64, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::print_button].emplace_back( BITMAPS::print_button, wxT( "print_button_dark_64.png" ), 64, wxT( "dark" ) ); aBitmapInfoCache[BITMAPS::project].emplace_back( BITMAPS::project, wxT( "project_dark_64.png" ), 64, wxT( "dark" ) ); diff --git a/include/advanced_config.h b/include/advanced_config.h index 7e14047825..cc6ac288dd 100644 --- a/include/advanced_config.h +++ b/include/advanced_config.h @@ -71,8 +71,8 @@ public: */ static const ADVANCED_CFG& GetCfg(); -///@{ -/// \ingroup advanced_config + ///@{ + /// \ingroup advanced_config /** * Distance from an arc end point and the estimated end point, when rotating from the @@ -660,6 +660,11 @@ public: */ bool m_EnableSnapAnchorsDebug; + /** + * When true, enable output to odb++ + */ + bool m_EnableODB; + ///@} private: diff --git a/include/bitmaps/bitmaps_list.h b/include/bitmaps/bitmaps_list.h index 70c2928a76..dd8bdd41fd 100644 --- a/include/bitmaps/bitmaps_list.h +++ b/include/bitmaps/bitmaps_list.h @@ -464,6 +464,7 @@ enum class BITMAPS : unsigned int post_module, post_rpt, post_xml, + post_odb, preference, primitives_to_custom_pad, print_button, diff --git a/include/project/project_file.h b/include/project/project_file.h index e4500dbcbe..12f92b0779 100644 --- a/include/project/project_file.h +++ b/include/project/project_file.h @@ -57,6 +57,7 @@ enum LAST_PATH_TYPE : unsigned int LAST_PATH_SVG, LAST_PATH_PLOT, LAST_PATH_2581, + LAST_PATH_ODBPP, LAST_PATH_SIZE }; diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index e00d76765b..5f11b59596 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -68,6 +68,8 @@ set( PCBNEW_DIALOGS dialogs/dialog_export_idf.cpp dialogs/dialog_export_idf_base.cpp dialogs/dialog_export_step.cpp + dialogs/dialog_export_odbpp.cpp + dialogs/dialog_export_odbpp_base.cpp dialogs/dialog_export_step_base.cpp dialogs/dialog_export_step_process.cpp dialogs/dialog_export_step_process_base.cpp @@ -633,8 +635,9 @@ add_subdirectory( pcb_io/easyeda ) add_subdirectory( pcb_io/easyedapro ) add_subdirectory( pcb_io/fabmaster ) add_subdirectory( pcb_io/ipc2581 ) +add_subdirectory( pcb_io/odbpp ) -set( PCBNEW_IO_LIBRARIES pcad2kicadpcb altium2pcbnew cadstar2pcbnew easyeda easyedapro fabmaster ipc2581 CACHE INTERNAL "") +set( PCBNEW_IO_LIBRARIES pcad2kicadpcb altium2pcbnew cadstar2pcbnew easyeda easyedapro fabmaster ipc2581 odbpp CACHE INTERNAL "") # a very small program launcher for pcbnew_kiface add_executable( pcbnew WIN32 MACOSX_BUNDLE diff --git a/pcbnew/dialogs/dialog_export_odbpp.cpp b/pcbnew/dialogs/dialog_export_odbpp.cpp new file mode 100644 index 0000000000..4d1e222f27 --- /dev/null +++ b/pcbnew/dialogs/dialog_export_odbpp.cpp @@ -0,0 +1,112 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 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, see . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static wxString s_oemColumn = wxEmptyString; + +DIALOG_EXPORT_ODBPP::DIALOG_EXPORT_ODBPP( PCB_EDIT_FRAME* aParent ) : + DIALOG_EXPORT_ODBPP_BASE( aParent ), m_parent( aParent ) +{ + m_browseButton->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) ); + + SetupStandardButtons( { { wxID_OK, _( "Export" ) }, { wxID_CANCEL, _( "Close" ) } } ); + + wxString path = m_parent->GetLastPath( LAST_PATH_ODBPP ); + + if( path.IsEmpty() ) + { + wxFileName brdFile( m_parent->GetBoard()->GetFileName() ); + path = brdFile.GetPath(); + } + + m_outputFileName->SetValue( path ); + + // Fill wxChoice (and others) items with data before calling finishDialogSettings() + // to calculate suitable widgets sizes + Init(); + + // Now all widgets have the size fixed, call FinishDialogSettings + finishDialogSettings(); +} + + +void DIALOG_EXPORT_ODBPP::onBrowseClicked( wxCommandEvent& event ) +{ + // Build the absolute path of current output directory to preselect it in the file browser. + wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() ); + wxFileName fn( Prj().AbsolutePath( path ) ); + + wxDirDialog dlg( this, _( "Export ODB++ File" ), fn.GetPath() ); + + if( dlg.ShowModal() == wxID_CANCEL ) + return; + + m_outputFileName->SetValue( dlg.GetPath() ); +} + + +void DIALOG_EXPORT_ODBPP::onOKClick( wxCommandEvent& event ) +{ + m_parent->SetLastPath( LAST_PATH_ODBPP, m_outputFileName->GetValue() ); + + event.Skip(); +} + + +bool DIALOG_EXPORT_ODBPP::Init() +{ + PCBNEW_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings(); + + BOARD* board = m_parent->GetBoard(); + + m_choiceUnits->SetSelection( cfg->m_ExportODBPP.units ); + m_precision->SetValue( cfg->m_ExportODBPP.precision ); + m_cbCompress->SetValue( cfg->m_ExportODBPP.compress ); + + return true; +} + + +bool DIALOG_EXPORT_ODBPP::TransferDataFromWindow() +{ + PCBNEW_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings(); + + cfg->m_ExportODBPP.units = m_choiceUnits->GetSelection(); + cfg->m_ExportODBPP.precision = m_precision->GetValue(); + cfg->m_ExportODBPP.compress = m_cbCompress->GetValue(); + + return true; +} \ No newline at end of file diff --git a/pcbnew/dialogs/dialog_export_odbpp.fbp b/pcbnew/dialogs/dialog_export_odbpp.fbp new file mode 100644 index 0000000000..416751a051 --- /dev/null +++ b/pcbnew/dialogs/dialog_export_odbpp.fbp @@ -0,0 +1,671 @@ + + + + + + C++ + 1 + source_name + 0 + 0 + res + UTF-8 + connect + dialog_export_odbpp_base + 1000 + none + + + 1 + DIALOG_EXPORT_ODBPP_BASE + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 0 + 1 + impl_virtual + + + + 0 + wxID_ANY + + + DIALOG_EXPORT_ODBPP_BASE + + 380,300 + wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER + DIALOG_SHIM; dialog_shim.h + Export ODB++ + + 0 + + + + + + bMainSizer + wxVERTICAL + none + + 15 + wxBOTTOM|wxEXPAND|wxTOP + 0 + + + bSizerTop + wxHORIZONTAL + protected + + 5 + wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + File: + 0 + + 0 + + + 0 + + 1 + m_lblBrdFile + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + 5 + wxALIGN_CENTER_VERTICAL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + 0 + 350,-1 + 1 + m_outputFileName + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + Enter a filename if you do not want to use default file names Can be used only when printing the current sheet + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + 5 + wxALIGN_CENTER_VERTICAL|wxRIGHT + 0 + + 1 + 1 + 1 + 1 + + + + + 0 + + + + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 0 + 1 + + 1 + + + 0 + 0 + wxID_ANY + MyButton + + 0 + + 0 + + + 0 + -1,-1 + 1 + m_browseButton + 1 + + + protected + 1 + + + + Resizable + 1 + -1,-1 + + STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onBrowseClicked + + + + + + 5 + wxEXPAND + 0 + + + bSizer3 + wxHORIZONTAL + none + + 10 + wxEXPAND|wxLEFT|wxRIGHT|wxTOP + 1 + + wxID_ANY + File Format + + sbSizer1 + wxVERTICAL + 1 + none + + 5 + wxEXPAND|wxALL + 3 + + 2 + wxBOTH + 1 + + 0 + + fgSizer + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Units: + 0 + + 0 + + + 0 + + 1 + m_lblUnits + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + 5 + wxALIGN_RIGHT|wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + "Millimeters" "Inches" + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_choiceUnits + 1 + + + protected + 1 + + Resizable + 0 + 1 + 130,30 + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Precision: + 0 + + 0 + + + 0 + + 1 + m_lblPrecision + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + The number of values following the decimal separator + + + + -1 + + + + 5 + wxALIGN_RIGHT|wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + 7 + 16 + + 0 + + 2 + + 0 + + 1 + m_precision + 1 + + + protected + 1 + + Resizable + 1 + 130,30 + wxSP_ARROW_KEYS + + 0 + The number of values following the decimal separator + + + + + + + + 5 + wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Compress output + + 0 + + + 0 + + 1 + m_cbCompress + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + Compress output into 'zip' file + + wxFILTER_NONE + wxDefaultValidator + + + + + onCompressCheck + + + + + + + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + + 5 + wxALL|wxEXPAND + 0 + + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + + m_stdButtons + protected + onOKClick + + + + + + diff --git a/pcbnew/dialogs/dialog_export_odbpp.h b/pcbnew/dialogs/dialog_export_odbpp.h new file mode 100644 index 0000000000..bca2c2d19b --- /dev/null +++ b/pcbnew/dialogs/dialog_export_odbpp.h @@ -0,0 +1,56 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 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, see . + */ + +#ifndef ODBPP_EXPORT_DIALOG_H +#define ODBPP_EXPORT_DIALOG_H +#include "dialog_export_odbpp_base.h" + +class PCB_EDIT_FRAME; + +class DIALOG_EXPORT_ODBPP : public DIALOG_EXPORT_ODBPP_BASE +{ +public: + DIALOG_EXPORT_ODBPP( PCB_EDIT_FRAME* aParent ); + + wxString GetOutputPath() const { return m_outputFileName->GetValue(); } + + wxString GetUnitsString() const + { + if( m_choiceUnits->GetSelection() == 0 ) + return wxT( "mm" ); + else + return wxT( "inch" ); + } + + wxString GetPrecision() const { return wxString::Format( "%d", m_precision->GetValue() ); } + + + bool GetCompress() const { return m_cbCompress->GetValue(); } + +private: + void onBrowseClicked( wxCommandEvent& event ) override; + void onOKClick( wxCommandEvent& event ) override; + + bool Init(); + bool TransferDataFromWindow() override; + + PCB_EDIT_FRAME* m_parent; +}; + +#endif // ODBPP_EXPORT_DIALOG_H \ No newline at end of file diff --git a/pcbnew/dialogs/dialog_export_odbpp_base.cpp b/pcbnew/dialogs/dialog_export_odbpp_base.cpp new file mode 100644 index 0000000000..1e19e2a68d --- /dev/null +++ b/pcbnew/dialogs/dialog_export_odbpp_base.cpp @@ -0,0 +1,117 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version 4.0.0-0-g0efcecf) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#include "widgets/std_bitmap_button.h" + +#include "dialog_export_odbpp_base.h" + +/////////////////////////////////////////////////////////////////////////// + +DIALOG_EXPORT_ODBPP_BASE::DIALOG_EXPORT_ODBPP_BASE( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : DIALOG_SHIM( parent, id, title, pos, size, style ) +{ + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + + wxBoxSizer* bMainSizer; + bMainSizer = new wxBoxSizer( wxVERTICAL ); + + bSizerTop = new wxBoxSizer( wxHORIZONTAL ); + + m_lblBrdFile = new wxStaticText( this, wxID_ANY, _("File:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_lblBrdFile->Wrap( -1 ); + bSizerTop->Add( m_lblBrdFile, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + + m_outputFileName = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + m_outputFileName->SetToolTip( _("Enter a filename if you do not want to use default file names\nCan be used only when printing the current sheet") ); + m_outputFileName->SetMinSize( wxSize( 350,-1 ) ); + + bSizerTop->Add( m_outputFileName, 1, wxALIGN_CENTER_VERTICAL, 5 ); + + m_browseButton = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 ); + bSizerTop->Add( m_browseButton, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + + + bMainSizer->Add( bSizerTop, 0, wxBOTTOM|wxEXPAND|wxTOP, 15 ); + + wxBoxSizer* bSizer3; + bSizer3 = new wxBoxSizer( wxHORIZONTAL ); + + wxStaticBoxSizer* sbSizer1; + sbSizer1 = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, _("File Format") ), wxVERTICAL ); + + wxFlexGridSizer* fgSizer; + fgSizer = new wxFlexGridSizer( 0, 2, 0, 0 ); + fgSizer->AddGrowableCol( 1 ); + fgSizer->SetFlexibleDirection( wxBOTH ); + fgSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + + m_lblUnits = new wxStaticText( sbSizer1->GetStaticBox(), wxID_ANY, _("Units:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_lblUnits->Wrap( -1 ); + fgSizer->Add( m_lblUnits, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + + wxString m_choiceUnitsChoices[] = { _("Millimeters"), _("Inches") }; + int m_choiceUnitsNChoices = sizeof( m_choiceUnitsChoices ) / sizeof( wxString ); + m_choiceUnits = new wxChoice( sbSizer1->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxSize( 130,30 ), m_choiceUnitsNChoices, m_choiceUnitsChoices, 0 ); + m_choiceUnits->SetSelection( 0 ); + fgSizer->Add( m_choiceUnits, 0, wxALIGN_RIGHT|wxALL, 5 ); + + m_lblPrecision = new wxStaticText( sbSizer1->GetStaticBox(), wxID_ANY, _("Precision:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_lblPrecision->Wrap( -1 ); + m_lblPrecision->SetToolTip( _("The number of values following the decimal separator") ); + + fgSizer->Add( m_lblPrecision, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_precision = new wxSpinCtrl( sbSizer1->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 130,30 ), wxSP_ARROW_KEYS, 2, 16, 3 ); + m_precision->SetToolTip( _("The number of values following the decimal separator") ); + + fgSizer->Add( m_precision, 0, wxALIGN_RIGHT|wxALL, 5 ); + + m_cbCompress = new wxCheckBox( sbSizer1->GetStaticBox(), wxID_ANY, _("Compress output"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cbCompress->SetToolTip( _("Compress output into 'zip' file") ); + + fgSizer->Add( m_cbCompress, 0, wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND, 5 ); + + + sbSizer1->Add( fgSizer, 3, wxEXPAND|wxALL, 5 ); + + + bSizer3->Add( sbSizer1, 1, wxEXPAND|wxLEFT|wxRIGHT|wxTOP, 10 ); + + + bMainSizer->Add( bSizer3, 0, wxEXPAND, 5 ); + + + bMainSizer->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_stdButtons = new wxStdDialogButtonSizer(); + m_stdButtonsOK = new wxButton( this, wxID_OK ); + m_stdButtons->AddButton( m_stdButtonsOK ); + m_stdButtonsCancel = new wxButton( this, wxID_CANCEL ); + m_stdButtons->AddButton( m_stdButtonsCancel ); + m_stdButtons->Realize(); + + bMainSizer->Add( m_stdButtons, 0, wxALL|wxEXPAND, 5 ); + + + this->SetSizer( bMainSizer ); + this->Layout(); + + this->Centre( wxBOTH ); + + // Connect Events + m_browseButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_EXPORT_ODBPP_BASE::onBrowseClicked ), NULL, this ); + m_cbCompress->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_EXPORT_ODBPP_BASE::onCompressCheck ), NULL, this ); + m_stdButtonsOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_EXPORT_ODBPP_BASE::onOKClick ), NULL, this ); +} + +DIALOG_EXPORT_ODBPP_BASE::~DIALOG_EXPORT_ODBPP_BASE() +{ + // Disconnect Events + m_browseButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_EXPORT_ODBPP_BASE::onBrowseClicked ), NULL, this ); + m_cbCompress->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_EXPORT_ODBPP_BASE::onCompressCheck ), NULL, this ); + m_stdButtonsOK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_EXPORT_ODBPP_BASE::onOKClick ), NULL, this ); + +} diff --git a/pcbnew/dialogs/dialog_export_odbpp_base.h b/pcbnew/dialogs/dialog_export_odbpp_base.h new file mode 100644 index 0000000000..0a50dbc20a --- /dev/null +++ b/pcbnew/dialogs/dialog_export_odbpp_base.h @@ -0,0 +1,72 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version 4.0.0-0-g0efcecf) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +class STD_BITMAP_BUTTON; + +#include "dialog_shim.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class DIALOG_EXPORT_ODBPP_BASE +/////////////////////////////////////////////////////////////////////////////// +class DIALOG_EXPORT_ODBPP_BASE : public DIALOG_SHIM +{ + private: + + protected: + wxBoxSizer* bSizerTop; + wxStaticText* m_lblBrdFile; + wxTextCtrl* m_outputFileName; + STD_BITMAP_BUTTON* m_browseButton; + wxStaticText* m_lblUnits; + wxChoice* m_choiceUnits; + wxStaticText* m_lblPrecision; + wxSpinCtrl* m_precision; + wxCheckBox* m_cbCompress; + wxStdDialogButtonSizer* m_stdButtons; + wxButton* m_stdButtonsOK; + wxButton* m_stdButtonsCancel; + + // Virtual event handlers, override them in your derived class + virtual void onBrowseClicked( wxCommandEvent& event ) { event.Skip(); } + virtual void onCompressCheck( wxCommandEvent& event ) { event.Skip(); } + virtual void onOKClick( wxCommandEvent& event ) { event.Skip(); } + + + public: + + DIALOG_EXPORT_ODBPP_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Export ODB++"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 380,300 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + + ~DIALOG_EXPORT_ODBPP_BASE(); + +}; + diff --git a/pcbnew/files.cpp b/pcbnew/files.cpp index 19a19dcc66..96da9c6163 100644 --- a/pcbnew/files.cpp +++ b/pcbnew/files.cpp @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include "footprint_info_impl.h" @@ -80,6 +81,7 @@ #include #include #include +#include #include "widgets/filedlg_hook_save_project.h" @@ -1404,3 +1406,144 @@ void PCB_EDIT_FRAME::GenIPC2581File( wxCommandEvent& event ) GetScreen()->SetContentModified( false ); } + + +void PCB_EDIT_FRAME::GenODBPPFiles( wxCommandEvent& event ) +{ + DIALOG_EXPORT_ODBPP dlg( this ); + + if( dlg.ShowModal() != wxID_OK ) + return; + + wxFileName pcbFileName = dlg.GetOutputPath(); + + // Write through symlinks, don't replace them + WX_FILENAME::ResolvePossibleSymlinks( pcbFileName ); + + if( !IsWritable( pcbFileName ) ) + { + wxString msg = wxString::Format( _( "Insufficient permissions to write file '%s'." ), + pcbFileName.GetFullPath() ); + + DisplayErrorMessage( this, msg ); + return; + } + + if( !wxFileName::DirExists( pcbFileName.GetFullPath() ) ) + { + // Make every directory provided when the provided path doesn't exist + if( !wxFileName::Mkdir( pcbFileName.GetFullPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) ) + { + wxString msg; + + msg.Printf( _( "Cannot create output directory '%s'." ), pcbFileName.GetFullPath() ); + + DisplayErrorMessage( this, msg ); + return; + } + } + + wxFileName tempFile( pcbFileName.GetFullPath(), "" ); + tempFile.AppendDir( "odb" ); + + wxString upperTxt; + wxString lowerTxt; + std::map props; + + props["units"] = dlg.GetUnitsString(); + props["sigfig"] = dlg.GetPrecision(); + WX_PROGRESS_REPORTER reporter( this, _( "Generating ODB++ output files" ), 5 ); + + auto saveFile = [&]() -> bool + { + try + { + IO_RELEASER pi( PCB_IO_MGR::PluginFind( PCB_IO_MGR::ODBPP ) ); + pi->SetProgressReporter( &reporter ); + pi->SaveBoard( pcbFileName.GetFullPath(), GetBoard(), &props ); + return true; + } + catch( const IO_ERROR& ioe ) + { + DisplayError( this, wxString::Format( _( "Error generating ODBPP files '%s'.\n%s" ), + tempFile.GetFullPath(), ioe.What() ) ); + + lowerTxt.Printf( _( "Failed to create directory '%s'." ), tempFile.GetFullPath() ); + + SetMsgPanel( upperTxt, lowerTxt ); + + // In case we started a file but didn't fully write it, clean up + wxFileName::Rmdir( tempFile.GetFullPath() ); + return false; + } + }; + + thread_pool& tp = GetKiCadThreadPool(); + auto ret = tp.submit( saveFile ); + + std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) ); + + while( status != std::future_status::ready ) + { + reporter.KeepRefreshing(); + status = ret.wait_for( std::chrono::milliseconds( 250 ) ); + } + + try + { + if( !ret.get() ) + return; + } + catch( const std::exception& e ) + { + wxLogError( "Exception in ODB++ generation: %s", e.what() ); + GetScreen()->SetContentModified( false ); + return; + } + + if( dlg.GetCompress() ) + { + wxFileName zipFileName( pcbFileName.GetFullPath(), "odb.zip" ); + + wxFFileOutputStream fnout( zipFileName.GetFullPath() ); + wxZipOutputStream zipStream( fnout ); + + std::function addDirToZip = + [&]( const wxString& dirPath, const wxString& parentPath ) + { + wxDir dir( dirPath ); + wxString fileName; + + bool cont = dir.GetFirst( &fileName, wxEmptyString, wxDIR_DEFAULT ); + while( cont ) + { + wxFileName fileInZip( dirPath, fileName ); + wxString relativePath = + parentPath.IsEmpty() + ? fileName + : parentPath + wxString( wxFileName::GetPathSeparator() ) + + fileName; + + if( wxFileName::DirExists( fileInZip.GetFullPath() ) ) + { + zipStream.PutNextDirEntry( relativePath ); + addDirToZip( fileInZip.GetFullPath(), relativePath ); + } + else + { + wxFFileInputStream fileStream( fileInZip.GetFullPath() ); + zipStream.PutNextEntry( relativePath ); + fileStream.Read( zipStream ); + } + cont = dir.GetNext( &fileName ); + } + }; + + addDirToZip( tempFile.GetFullPath(), wxEmptyString ); + + zipStream.Close(); + fnout.Close(); + } + + GetScreen()->SetContentModified( false ); +} diff --git a/pcbnew/menubar_pcb_editor.cpp b/pcbnew/menubar_pcb_editor.cpp index 870049afe4..e4fa9e5d4b 100644 --- a/pcbnew/menubar_pcb_editor.cpp +++ b/pcbnew/menubar_pcb_editor.cpp @@ -168,6 +168,10 @@ void PCB_EDIT_FRAME::doReCreateMenuBar() submenuFabOutputs->Add( PCB_ACTIONS::generateGerbers ); submenuFabOutputs->Add( PCB_ACTIONS::generateDrillFiles ); submenuFabOutputs->Add( PCB_ACTIONS::generateIPC2581File ); + + if( ADVANCED_CFG::GetCfg().m_EnableODB ) + submenuFabOutputs->Add( PCB_ACTIONS::generateODBPPFile ); + submenuFabOutputs->Add( PCB_ACTIONS::generatePosFile ); submenuFabOutputs->Add( PCB_ACTIONS::generateReportFile ); submenuFabOutputs->Add( PCB_ACTIONS::generateD356File ); diff --git a/pcbnew/pcb_edit_frame.h b/pcbnew/pcb_edit_frame.h index 8846368100..a90768e508 100644 --- a/pcbnew/pcb_edit_frame.h +++ b/pcbnew/pcb_edit_frame.h @@ -346,6 +346,11 @@ public: */ void GenIPC2581File( wxCommandEvent& event ); + /** + * Create and Generate ODB++ output files + */ + void GenODBPPFiles( wxCommandEvent& event ); + /** * Create an ASCII footprint report file giving some infos on footprints and board outlines. * diff --git a/pcbnew/pcb_io/odbpp/CMakeLists.txt b/pcbnew/pcb_io/odbpp/CMakeLists.txt new file mode 100644 index 0000000000..89265b31b1 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/CMakeLists.txt @@ -0,0 +1,38 @@ +# This program source code file is part of KiCad, a free EDA CAD application. +# +# Copyright (C) 2024 KiCad Developers, see AUTHORS.TXT for contributors. +# Author: SYSUEric . +# +# 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 3 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, see . + +# Sources for the pcbnew pcb_io called ODB++ + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) + + +set( ODBPP_SRCS + odb_util.cpp + odb_attribute.cpp + odb_feature.cpp + odb_component.cpp + odb_netlist.cpp + odb_eda_data.cpp + odb_fonts.cpp + odb_entity.cpp + pcb_io_odbpp.cpp + ) + +add_library( odbpp STATIC ${ODBPP_SRCS} ) + +target_link_libraries( odbpp pcbcommon ) diff --git a/pcbnew/pcb_io/odbpp/odb_attribute.cpp b/pcbnew/pcb_io/odbpp/odb_attribute.cpp new file mode 100644 index 0000000000..c8e2ce4ef2 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_attribute.cpp @@ -0,0 +1,101 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + +#include "odb_attribute.h" +#include +#include + + +size_t ATTR_MANAGER::GetTextIndex( std::unordered_map& aMap, + std::vector>& aVec, + const std::string& aText ) +{ + if( aMap.count( aText ) ) + { + return aMap.at( aText ); + } + else + { + auto index = aMap.size(); + aMap.emplace( aText, index ); + aVec.emplace_back( index, aText ); + + return index; + } +} + + +size_t ATTR_MANAGER::GetAttrNameNumber( const wxString& aName ) +{ + return GetTextIndex( m_attrNames, m_attrNameVec, aName.Lower().ToStdString() ); +} + + +size_t ATTR_MANAGER::GetAttrTextNumber( const wxString& aText ) +{ + return GetTextIndex( m_attrTexts, m_attrTextVec, aText.Upper().ToStdString() ); +} + + +void ATTR_RECORD_WRITER::WriteAttributes( std::ostream& ost ) const +{ + ODB::CHECK_ONCE once; + + for( const auto& attr : attributes ) + { + if( once() ) + ost << ";"; + else + ost << ","; + ost << attr.first; + if( attr.second.size() ) + ost << "=" << attr.second; + } + + ost << ";"; +} + + +void ATTR_MANAGER::WriteAttributesName( std::ostream& ost, const std::string& prefix ) const +{ + for( const auto& [n, name] : m_attrNameVec ) + { + ost << prefix << "@" << n << " " << name << std::endl; + } +} + + +void ATTR_MANAGER::WriteAttributesText( std::ostream& ost, const std::string& prefix ) const +{ + for( const auto& [n, name] : m_attrTextVec ) + { + ost << prefix << "&" << n << " " << name << std::endl; + } +} + + +void ATTR_MANAGER::WriteAttributes( std::ostream& ost, const std::string& prefix ) const +{ + ost << std::endl << "#\n#Feature attribute names\n#" << std::endl; + WriteAttributesName( ost ); + + ost << std::endl << "#\n#Feature attribute text strings\n#" << std::endl; + WriteAttributesText( ost ); +} diff --git a/pcbnew/pcb_io/odbpp/odb_attribute.h b/pcbnew/pcb_io/odbpp/odb_attribute.h new file mode 100644 index 0000000000..391392567f --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_attribute.h @@ -0,0 +1,352 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + +#ifndef _ATTRIBUTE_PROVIDER_H_ +#define _ATTRIBUTE_PROVIDER_H_ + +#include "odb_util.h" +#include "stroke_params.h" +#include +#include +#include + + +namespace ODB_ATTR +{ + +enum class TYPE +{ + FLOAT, + BOOLEAN, + TEXT, + OPTION, + INTEGER +}; + +// Base class template for attributes +template +struct AttributeBase +{ + using ValueType = T; + static constexpr TYPE type = AttrType; + constexpr AttributeBase( T v ) : value( v ) {} + T value; +}; + +// Specialized attribute types +template +struct FloatAttribute : AttributeBase +{ + static constexpr unsigned int digits = N; + using AttributeBase::AttributeBase; +}; + +template +struct BooleanAttribute : AttributeBase +{ + using AttributeBase::AttributeBase; +}; + +template +struct TextAttribute : AttributeBase +{ + constexpr TextAttribute( const std::string& t ) : + AttributeBase( ODB::GenLegalEntityName( t ).ToStdString() ) + { + } +}; + +template +struct OPTION_Attribute : AttributeBase +{ + using AttributeBase::AttributeBase; +}; + + +template +struct AttributeName +{ +}; + +// Attribute name and type definitions +template class Attr, TYPE AttrType, unsigned int N> +struct Attribute +{ + using TYPE = Attr; +}; + +template class Attr, TYPE AttrType> +struct AttributeSimple +{ + using TYPE = Attr; +}; + +// TYPE traits for attributes +template +struct IsFeature : std::false_type +{ +}; +template +struct IsNet : std::false_type +{ +}; +template +struct IsPkg : std::false_type +{ +}; +template +struct IsLayer : std::false_type +{ +}; +template +struct IsStep : std::false_type +{ +}; +template +struct IsComp : std::false_type +{ +}; +template +struct IsProductModel : std::false_type +{ +}; +template +struct IsSymbol : std::false_type +{ +}; + + +#define DEFINE_ATTR( Tag, Attr, AttrType, AttrName, ... ) \ + struct Tag##_t \ + { \ + }; \ + constexpr const char Tag##_name[] = AttrName; \ + using Tag = Attribute::TYPE; \ + template <> \ + struct AttributeName \ + { \ + static constexpr const char* name = Tag##_name; \ + }; + +#define DEFINE_ATTR_SIMPLE( Tag, Attr, AttrType, AttrName ) \ + struct Tag##_t \ + { \ + }; \ + constexpr const char Tag##_name[] = AttrName; \ + using Tag = AttributeSimple::TYPE; \ + template <> \ + struct AttributeName \ + { \ + static constexpr const char* name = Tag##_name; \ + }; + + +#define DEFINE_FLOAT_ATTR( NAME, N ) DEFINE_ATTR( NAME, FloatAttribute, TYPE::FLOAT, #NAME, N ) + +#define DEFINE_BOOLEAN_ATTR( NAME ) \ + DEFINE_ATTR_SIMPLE( NAME, BooleanAttribute, TYPE::BOOLEAN, #NAME ) + +#define DEFINE_TEXT_ATTR( NAME ) DEFINE_ATTR_SIMPLE( NAME, TextAttribute, TYPE::TEXT, #NAME ) + +#define DEFINE_OPTION_ATTR( NAME ) \ + struct NAME##_t \ + { \ + }; \ + template <> \ + struct AttributeSimple; \ + template <> \ + struct AttributeName \ + { \ + static constexpr const char* name = #NAME; \ + }; + + +// used by which entity +#define USED_BY_FEATURE_ENTITY( NAME ) \ + template <> \ + struct IsFeature : std::true_type \ + { \ + }; + +#define USED_BY_NET_ENTITY( NAME ) \ + template <> \ + struct IsNet : std::true_type \ + { \ + }; + +#define USED_BY_PKG_ENTITY( NAME ) \ + template <> \ + struct IsPkg : std::true_type \ + { \ + }; + +// Attribute definitions +// BOOLEAN ATTRIBUTES +DEFINE_BOOLEAN_ATTR( SMD ) +USED_BY_FEATURE_ENTITY( SMD ) + +DEFINE_BOOLEAN_ATTR( NET_POINT ) +USED_BY_FEATURE_ENTITY( NET_POINT ) + +DEFINE_BOOLEAN_ATTR( ROUT_PLATED ) +USED_BY_FEATURE_ENTITY( ROUT_PLATED ) + +DEFINE_BOOLEAN_ATTR( MECHANICAL ) + +DEFINE_BOOLEAN_ATTR( MOUNT_HOLE ) +USED_BY_FEATURE_ENTITY( MOUNT_HOLE ) + +DEFINE_BOOLEAN_ATTR( TEAR_DROP ) +USED_BY_FEATURE_ENTITY( TEAR_DROP ) + +DEFINE_BOOLEAN_ATTR( TEST_POINT ) +USED_BY_FEATURE_ENTITY( TEST_POINT ) + +// TEXT ATTRIBUTES +DEFINE_TEXT_ATTR( STRING ) +USED_BY_FEATURE_ENTITY( STRING ) + +DEFINE_TEXT_ATTR( GEOMETRY ) +USED_BY_FEATURE_ENTITY( GEOMETRY ) + +DEFINE_TEXT_ATTR( NET_NAME ) +USED_BY_FEATURE_ENTITY( NET_NAME ) + + +// FLOAT ATTRIBUTES +DEFINE_FLOAT_ATTR( BOARD_THICKNESS, 1 ) // 0.0~10.0 + +DEFINE_FLOAT_ATTR( STRING_ANGLE, 1 ) // 0.0~360.0 +USED_BY_FEATURE_ENTITY( STRING_ANGLE ) + + +// OPTION ATTRIBUTES +enum class DRILL +{ + PLATED, + NON_PLATED, + VIA +}; +DEFINE_OPTION_ATTR( DRILL ) +USED_BY_FEATURE_ENTITY( DRILL ) + +enum class PAD_USAGE +{ + TOEPRINT, + VIA, + G_FIDUCIAL, + L_FIDUCIAL, + TOOLING_HOLE, + BOND_FINGER +}; +DEFINE_OPTION_ATTR( PAD_USAGE ) +USED_BY_FEATURE_ENTITY( PAD_USAGE ) + +enum class PLATED_TYPE +{ + STANDARD, + PRESS_FIT +}; +DEFINE_OPTION_ATTR( PLATED_TYPE ) +USED_BY_FEATURE_ENTITY( PLATED_TYPE ) + +enum class VIA_TYPE +{ + DRILLED, + LASER, + PHOTO +}; +DEFINE_OPTION_ATTR( VIA_TYPE ) +USED_BY_FEATURE_ENTITY( VIA_TYPE ) + +} // namespace ODB_ATTR + + +class ATTR_MANAGER +{ +public: + ATTR_MANAGER() = default; + virtual ~ATTR_MANAGER() = default; + + template + void AddFeatureAttribute( Tr& r, Ta v ) + { + const auto id = GetAttrNameNumber( ODB_ATTR::AttributeName::name ); + + if constexpr( std::is_enum_v ) + r.attributes.emplace( id, std::to_string( static_cast( v ) ) ); + else + r.attributes.emplace( id, AttrValue2String( v ) ); + } + +protected: + size_t GetAttrNameNumber( const wxString& name ); + + void WriteAttributes( std::ostream& ost, const std::string& prefix = "" ) const; + void WriteAttributesName( std::ostream& ost, const std::string& prefix = "" ) const; + void WriteAttributesText( std::ostream& ost, const std::string& prefix = "" ) const; + + +private: + size_t GetAttrTextNumber( const wxString& aName ); + size_t GetTextIndex( std::unordered_map& aMap, + std::vector>& aVec, + const std::string& aText ); + + template + std::string AttrValue2String( ODB_ATTR::FloatAttribute a ) + { + return ODB::Double2String( a.value, a.digits ); + } + + template + std::string AttrValue2String( ODB_ATTR::BooleanAttribute a ) + { + return ""; + } + + template + std::string AttrValue2String( ODB_ATTR::TextAttribute a ) + { + return std::to_string( GetAttrTextNumber( a.value ) ); + } + + + std::unordered_map m_attrNames; + std::vector> m_attrNameVec; + + std::unordered_map m_attrTexts; + std::vector> m_attrTextVec; +}; + +class ATTR_RECORD_WRITER +{ +public: + ATTR_RECORD_WRITER() = default; + virtual ~ATTR_RECORD_WRITER() = default; + + void WriteAttributes( std::ostream& ost ) const; + +public: + std::map attributes; +}; + + +#endif // ATTRIBUTE_PROVIDER_H_ \ No newline at end of file diff --git a/pcbnew/pcb_io/odbpp/odb_component.cpp b/pcbnew/pcb_io/odbpp/odb_component.cpp new file mode 100644 index 0000000000..8a62cca398 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_component.cpp @@ -0,0 +1,94 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + +#include "odb_component.h" +#include "odb_util.h" +#include "hash_eda.h" +#include "pcb_io_odbpp.h" + + +ODB_COMPONENT& COMPONENTS_MANAGER::AddComponent( const FOOTPRINT* aFp, + const EDA_DATA::PACKAGE& aPkg ) +{ + auto& comp = m_compList.emplace_back( m_compList.size(), aPkg.m_index ); + + comp.m_center = ODB::AddXY( aFp->GetPosition() ); + + if( aFp->GetOrientation() != ANGLE_0 ) + { + // odb Rotation is expressed in degrees and is always clockwise. + // while kicad EDA_ANGLE is anticlockwise. + + comp.m_rot = + ODB::Double2String( ( ANGLE_360 - aFp->GetOrientation() ).Normalize().AsDegrees() ); + } + + if( aFp->GetLayer() != F_Cu ) + { + comp.m_mirror = wxT( "M" ); + } + + comp.m_comp_name = aFp->GetReference().ToAscii(); + comp.m_part_name = + wxString::Format( "%s_%s_%s", aFp->GetFPID().GetFullLibraryName(), + aFp->GetFPID().GetLibItemName().wx_str(), aFp->GetValue() ); + + return comp; +} + + +void COMPONENTS_MANAGER::Write( std::ostream& ost ) const +{ + ost << "UNITS=" << PCB_IO_ODBPP::m_unitsStr << std::endl; + + WriteAttributes( ost ); + + for( const auto& comp : m_compList ) + { + comp.Write( ost ); + } +} + + +void ODB_COMPONENT::Write( std::ostream& ost ) const +{ + ost << "# CMP " << m_index << std::endl; + ost << "CMP " << m_pkg_ref << " " << m_center.first << " " << m_center.second << " " << m_rot + << " " << m_mirror << " " << m_comp_name << " " << m_part_name; + + WriteAttributes( ost ); + + ost << std::endl; + + for( const auto& toep : m_toeprints ) + { + toep.Write( ost ); + } + + ost << "#" << std::endl; +} + + +void ODB_COMPONENT::TOEPRINT::Write( std::ostream& ost ) const +{ + ost << "TOP " << m_pin_num << " " << m_center.first << " " << m_center.second << " " << m_rot + << " " << m_mirror << " " << m_net_num << " " << m_subnet_num << " " << m_toeprint_name + << std::endl; +} diff --git a/pcbnew/pcb_io/odbpp/odb_component.h b/pcbnew/pcb_io/odbpp/odb_component.h new file mode 100644 index 0000000000..b5fc055d60 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_component.h @@ -0,0 +1,99 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + + +#ifndef _ODB_COMPONENT_H_ +#define _ODB_COMPONENT_H_ + +#include "odb_util.h" +#include +#include +#include "odb_attribute.h" +#include "odb_eda_data.h" + +class ODB_COMPONENT; +class COMPONENTS_MANAGER : public ATTR_MANAGER +{ +public: + COMPONENTS_MANAGER() = default; + + virtual ~COMPONENTS_MANAGER() { m_compList.clear(); } + + ODB_COMPONENT& AddComponent( const FOOTPRINT* aFp, const EDA_DATA::PACKAGE& aPkg ); + + void Write( std::ostream& ost ) const; + +private: + std::list m_compList; +}; + + +class ODB_COMPONENT : public ATTR_RECORD_WRITER +{ +public: + ODB_COMPONENT( size_t aIndex, size_t r ) : m_index( aIndex ), m_pkg_ref( r ) {} + + const size_t m_index; /// m_center; + wxString m_rot = wxT( "0" ); + wxString m_mirror = wxT( "N" ); + + wxString m_comp_name; ///> m_prp; // !< Component Property Record + + struct TOEPRINT + { + public: + TOEPRINT( const EDA_DATA::PIN& pin ) : + m_pin_num( pin.m_index ), m_toeprint_name( pin.m_name ) + { + } + + const size_t m_pin_num; /// m_center; /// m_toeprints; + + void Write( std::ostream& ost ) const; +}; + + +#endif // _ODB_COMPONENT_H_ \ No newline at end of file diff --git a/pcbnew/pcb_io/odbpp/odb_defines.h b/pcbnew/pcb_io/odbpp/odb_defines.h new file mode 100644 index 0000000000..4bbcfd77e1 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_defines.h @@ -0,0 +1,34 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + + +#ifndef _ODB_DEFINES_H_ +#define _ODB_DEFINES_H_ + +#include + +#define ODB_JOB_NAME "JOB_NAME" +#define ODB_UNITS "UNITS" +#define ODB_DIM_X "x" +#define ODB_DIM_R "r" +#define ODB_DIM_C "c" +#define ODB_NONE "NONE" + +#endif // _ODB_DEFINES_H_ diff --git a/pcbnew/pcb_io/odbpp/odb_eda_data.cpp b/pcbnew/pcb_io/odbpp/odb_eda_data.cpp new file mode 100644 index 0000000000..611eaa60fe --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_eda_data.cpp @@ -0,0 +1,420 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + +#include "odb_eda_data.h" +#include "hash_eda.h" +#include "netinfo.h" +#include "odb_feature.h" +#include "base_units.h" +#include "pcb_io_odbpp.h" + + +EDA_DATA::EDA_DATA() +{ + auto& x = nets_map.emplace( std::piecewise_construct, std::forward_as_tuple( 0 ), + std::forward_as_tuple( nets.size(), "$NONE$" ) ) + .first->second; + + nets.push_back( &x ); +} + + +void EDA_DATA::NET::Write( std::ostream& ost ) const +{ + ost << "NET " << m_name; + + WriteAttributes( ost ); + + ost << std::endl; + + for( const auto& subnet : subnets ) + { + subnet->Write( ost ); + } +} + + +void EDA_DATA::AddNET( const NETINFO_ITEM* aNet ) +{ + if( nets_map.end() == nets_map.find( aNet->GetNetCode() ) ) + { + auto& net = nets_map.emplace( std::piecewise_construct, + std::forward_as_tuple( aNet->GetNetCode() ), + std::forward_as_tuple( nets.size(), aNet->GetNetname() ) ) + .first->second; + + nets.push_back( &net ); + + //TODO: netname check + } +} + + +void EDA_DATA::SUB_NET::Write( std::ostream& ost ) const +{ + ost << "SNT "; + + WriteSubnet( ost ); + + ost << std::endl; + + for( const auto& fid : feature_ids ) + { + fid.Write( ost ); + } +} + + +void EDA_DATA::FEATURE_ID::Write( std::ostream& ost ) const +{ + static const std::map type_map = { + { TYPE::COPPER, "C" }, + { TYPE::HOLE, "H" }, + }; + + ost << "FID " << type_map.at( type ) << " " << layer << " " << feature_id << std::endl; +} + + +void EDA_DATA::SUB_NET_VIA::WriteSubnet( std::ostream& ost ) const +{ + ost << "VIA"; +} + + +void EDA_DATA::SUB_NET_TRACE::WriteSubnet( std::ostream& ost ) const +{ + ost << "TRC"; +} + + +void EDA_DATA::SUB_NET_PLANE::WriteSubnet( std::ostream& ost ) const +{ + static const std::map fill_type_map = { { FILL_TYPE::SOLID, "S" }, + { FILL_TYPE::OUTLINE, "O" } }; + + static const std::map cutout_type_map = { + { CUTOUT_TYPE::CIRCLE, "C" }, + { CUTOUT_TYPE::RECT, "R" }, + { CUTOUT_TYPE::OCTAGON, "O" }, + { CUTOUT_TYPE::EXACT, "E" } + }; + + ost << "PLN " << fill_type_map.at( fill_type ) << " " << cutout_type_map.at( cutout_type ) + << " " << fill_size; +} + + +void EDA_DATA::SUB_NET_TOEPRINT::WriteSubnet( std::ostream& ost ) const +{ + static const std::map side_map = { + { SIDE::BOTTOM, "B" }, + { SIDE::TOP, "T" }, + }; + ost << "TOP " << side_map.at( side ) << " " << comp_num << " " << toep_num; +} + + +void EDA_DATA::SUB_NET::AddFeatureID( FEATURE_ID::TYPE type, const wxString& layer, + size_t feature_id ) +{ + feature_ids.emplace_back( type, m_edadata->GetLyrIdx( layer ), feature_id ); +} + + +size_t EDA_DATA::GetLyrIdx( const wxString& aLayer ) +{ + if( layers_map.count( aLayer ) ) + { + return layers_map.at( aLayer ); + } + else + { + auto idx = layers_map.size(); + layers_map.emplace( aLayer, idx ); + layers.push_back( aLayer ); + return idx; + } +} + + +void OUTLINE_SQUARE::Write( std::ostream& ost ) const +{ + ost << "SQ " << ODB::Data2String( m_center.x ) << " " << ODB::Data2String( m_center.y ) << " " + << ODB::Data2String( m_halfSide ) << std::endl; +} + + +void OUTLINE_CIRCLE::Write( std::ostream& ost ) const +{ + ost << "CR " << ODB::Data2String( m_center.x ) << " " << ODB::Data2String( m_center.y ) << " " + << ODB::Data2String( m_radius ) << std::endl; +} + + +void OUTLINE_RECT::Write( std::ostream& ost ) const +{ + ost << "RC " << ODB::Data2String( m_lower_left.x ) << " " << ODB::Data2String( m_lower_left.y ) + << " " << ODB::Data2String( m_width ) << " " << ODB::Data2String( m_height ) << std::endl; +} + + +void OUTLINE_CONTOUR::Write( std::ostream& ost ) const +{ + ost << "CT" << std::endl; + m_surfaces->WriteData( ost ); + ost << "CE" << std::endl; +} + + +void EDA_DATA::AddPackage( const FOOTPRINT* aFp ) +{ + // ODBPP only need unique PACKAGE in PKG record in eda/data file. + // the PKG index can repeat to be ref in CMP record in component file. + + std::shared_ptr fp( static_cast( aFp->Clone() ) ); + m_eda_footprints.emplace_back( fp ); + fp->SetParentGroup( nullptr ); + fp->SetPosition( { 0, 0 } ); + + if( fp->GetLayer() != F_Cu ) + fp->Flip( fp->GetPosition(), false ); + + fp->SetOrientation( ANGLE_0 ); + + size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD ); + size_t pkg_index = packages_map.size(); + wxString fp_name = fp->GetFPID().GetLibItemName().wx_str(); + + auto [iter, success] = packages_map.emplace( hash, PACKAGE( pkg_index, fp_name ) ); + + if( !success ) + { + return; + } + + PACKAGE* pkg = &( iter->second ); + + packages.push_back( pkg ); + + BOX2I bbox = fp->GetBoundingBox(); + pkg->m_xmin = bbox.GetPosition().x; + pkg->m_ymin = bbox.GetPosition().y; + pkg->m_xmax = bbox.GetEnd().x; + pkg->m_ymax = bbox.GetEnd().y; + pkg->m_pitch = UINT64_MAX; + + if( fp->Pads().size() < 2 ) + pkg->m_pitch = pcbIUScale.mmToIU( 1.0 ); // placeholder value + + for( size_t i = 0; i < fp->Pads().size(); ++i ) + { + const PAD* pad1 = fp->Pads()[i]; + for( size_t j = i + 1; j < fp->Pads().size(); ++j ) + { + const PAD* pad2 = fp->Pads()[j]; + const uint64_t pin_dist = ( pad1->GetCenter() - pad2->GetCenter() ).EuclideanNorm(); + pkg->m_pitch = std::min( pkg->m_pitch, pin_dist ); + } + } + + const SHAPE_POLY_SET& courtyard = fp->GetCourtyard( F_CrtYd ); + const SHAPE_POLY_SET& courtyard_back = fp->GetCourtyard( B_CrtYd ); + SHAPE_POLY_SET pkg_outline; + + if( courtyard.OutlineCount() > 0 ) + pkg_outline = courtyard; + + if( courtyard_back.OutlineCount() > 0 ) + { + pkg_outline = courtyard_back; + } + + if( !courtyard.OutlineCount() && !courtyard_back.OutlineCount() ) + { + pkg_outline = fp->GetBoundingHull(); + } + + // TODO: Here we put rect, square, and circle, all as polygon + + if( pkg_outline.OutlineCount() > 0 ) + { + for( int ii = 0; ii < pkg_outline.OutlineCount(); ++ii ) + { + pkg->m_pkgOutlines.push_back( + std::make_unique( pkg_outline.Polygon( ii ) ) ); + } + } + + for( size_t i = 0; i < fp->Pads().size(); ++i ) + { + const PAD* pad = fp->Pads()[i]; + pkg->AddPin( pad, i ); + } + + return; +} + + +void EDA_DATA::PACKAGE::AddPin( const PAD* aPad, size_t aPinNum ) +{ + wxString name = aPad->GetNumber(); + + // Pins are required to have names, so if our pad doesn't have a name, we need to + // generate one that is unique + + if( aPad->GetAttribute() == PAD_ATTRIB::NPTH ) + name = wxString::Format( "NPTH%zu", aPinNum ); + else if( name.empty() ) + name = wxString::Format( "PAD%zu", aPinNum ); + + // // for SNT record, pad, net, pin + std::shared_ptr pin = std::make_shared( m_pinsVec.size(), name ); + m_pinsVec.push_back( pin ); + + VECTOR2D relpos = aPad->GetFPRelativePosition(); + + // TODO: is odb pkg pin center means center of pad hole or center of pad shape? + + if( aPad->GetOffset().x != 0 || aPad->GetOffset().y != 0 ) + relpos += aPad->GetOffset(); + + pin->m_center = ODB::AddXY( relpos ); + + if( aPad->HasHole() ) + { + pin->type = PIN::TYPE::THROUGH_HOLE; + } + else + { + pin->type = PIN::TYPE::SURFACE; + } + + if( aPad->GetAttribute() == PAD_ATTRIB::NPTH ) + pin->etype = PIN::ELECTRICAL_TYPE::MECHANICAL; + else if( aPad->IsOnCopperLayer() ) + pin->etype = PIN::ELECTRICAL_TYPE::ELECTRICAL; + else + pin->etype = PIN::ELECTRICAL_TYPE::UNDEFINED; + + + if( ( aPad->HasHole() && aPad->IsOnCopperLayer() ) || aPad->GetAttribute() == PAD_ATTRIB::PTH ) + { + pin->mtype = PIN::MOUNT_TYPE::THROUGH_HOLE; + } + else if( aPad->HasHole() && aPad->GetAttribute() == PAD_ATTRIB::NPTH ) + { + pin->mtype = PIN::MOUNT_TYPE::HOLE; + } + else if( aPad->GetAttribute() == PAD_ATTRIB::SMD ) + { + pin->mtype = PIN::MOUNT_TYPE::SMT; + } + else + { + pin->mtype = PIN::MOUNT_TYPE::UNDEFINED; + } + + const std::shared_ptr& polygons = aPad->GetEffectivePolygon( ERROR_INSIDE ); + + // TODO: Here we put all pad shapes as polygonl, we should switch by pad shape + // Note:pad only use polygons->Polygon(0), + if( polygons->OutlineCount() > 0 ) + { + pin->m_pinOutlines.push_back( std::make_unique( polygons->Polygon( 0 ) ) ); + } +} + + +void EDA_DATA::PIN::Write( std::ostream& ost ) const +{ + static const std::map type_map = { { TYPE::SURFACE, "S" }, + { TYPE::THROUGH_HOLE, "T" }, + { TYPE::BLIND, "B" } }; + + static const std::map etype_map = { + { ELECTRICAL_TYPE::ELECTRICAL, "E" }, + { ELECTRICAL_TYPE::MECHANICAL, "M" }, + { ELECTRICAL_TYPE::UNDEFINED, "U" } + }; + static const std::map mtype_map = { { MOUNT_TYPE::THROUGH_HOLE, "T" }, + { MOUNT_TYPE::HOLE, "H" }, + { MOUNT_TYPE::SMT, "S" }, + { MOUNT_TYPE::UNDEFINED, "U" } }; + + ost << "PIN " << m_name << " " << type_map.at( type ) << " " << m_center.first << " " + << m_center.second << " 0 " << etype_map.at( etype ) << " " << mtype_map.at( mtype ) + << std::endl; + + for( const auto& outline : m_pinOutlines ) + { + outline->Write( ost ); + } +} + + +void EDA_DATA::PACKAGE::Write( std::ostream& ost ) const +{ + ost << "PKG " << m_name << " " << ODB::Data2String( m_pitch ) << " " + << ODB::Data2String( m_xmin ) << " " << ODB::Data2String( m_ymin ) << " " + << ODB::Data2String( m_xmax ) << " " << ODB::Data2String( m_ymax ) << std::endl; + + for( const auto& outline : m_pkgOutlines ) + { + outline->Write( ost ); + } + + for( const auto& pin : m_pinsVec ) + { + pin->Write( ost ); + } +} + + +void EDA_DATA::Write( std::ostream& ost ) const +{ + ost << "# " << wxDateTime::Now().FormatISOCombined() << std::endl; + ost << "UNITS=" << PCB_IO_ODBPP::m_unitsStr << std::endl; + ost << "LYR"; + + for( const auto& layer : layers ) + { + ost << " " << layer; + } + + ost << std::endl; + + WriteAttributes( ost, "#" ); + + for( const auto& net : nets ) + { + ost << "#NET " << net->m_index << std::endl; + net->Write( ost ); + } + + size_t i = 0; + for( const auto* pkg : packages ) + { + ost << "# PKG " << i << std::endl; + i++; + pkg->Write( ost ); + ost << "#" << std::endl; + } +} diff --git a/pcbnew/pcb_io/odbpp/odb_eda_data.h b/pcbnew/pcb_io/odbpp/odb_eda_data.h new file mode 100644 index 0000000000..7dbb2d84f4 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_eda_data.h @@ -0,0 +1,340 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + +#ifndef _ODB_EDA_DATA_H_ +#define _ODB_EDA_DATA_H_ + + +#include +#include + +#include "odb_attribute.h" +#include "odb_feature.h" + + +class PKG_OUTLINE; +class EDA_DATA : public ATTR_MANAGER +{ +public: + EDA_DATA(); + + void Write( std::ostream& ost ) const; + size_t GetLyrIdx( const wxString& aLayerName ); + std::vector> GetEdaFootprints() const { return m_eda_footprints; } + + class FEATURE_ID + { + friend EDA_DATA; + + public: + enum class TYPE + { + COPPER, + LAMINATE, + HOLE + }; + + FEATURE_ID( TYPE t, size_t l, size_t fid ) : type( t ), layer( l ), feature_id( fid ) {} + + TYPE type; + size_t layer; + size_t feature_id; + + void Write( std::ostream& ost ) const; + }; + + class SUB_NET + { + public: + SUB_NET( size_t aIndex, EDA_DATA* aEda ) : m_index( aIndex ), m_edadata( aEda ) {} + const size_t m_index; + void Write( std::ostream& ost ) const; + + std::list feature_ids; + void AddFeatureID( FEATURE_ID::TYPE type, const wxString& layer, size_t feature_id ); + + virtual ~SUB_NET() {} + + protected: + virtual void WriteSubnet( std::ostream& ost ) const = 0; + EDA_DATA* m_edadata; + }; + + class SUB_NET_VIA : public SUB_NET + { + public: + SUB_NET_VIA( size_t aIndex, EDA_DATA* aEda ) : SUB_NET( aIndex, aEda ) {} + void WriteSubnet( std::ostream& ost ) const override; + }; + + class SUB_NET_TRACE : public SUB_NET + { + public: + SUB_NET_TRACE( size_t aIndex, EDA_DATA* aEda ) : SUB_NET( aIndex, aEda ) {} + void WriteSubnet( std::ostream& ost ) const override; + }; + + class SUB_NET_PLANE : public SUB_NET + { + public: + enum class FILL_TYPE + { + SOLID, + OUTLINE + }; + + enum class CUTOUT_TYPE + { + CIRCLE, + RECT, + OCTAGON, + EXACT + }; + + SUB_NET_PLANE( size_t aIndex, EDA_DATA* aEda, FILL_TYPE aFill, CUTOUT_TYPE aCutout, + size_t aFillSize ) : + SUB_NET( aIndex, aEda ), fill_type( aFill ), cutout_type( aCutout ), + fill_size( aFillSize ) + { + } + + FILL_TYPE fill_type; + CUTOUT_TYPE cutout_type; + size_t fill_size; + + void WriteSubnet( std::ostream& ost ) const override; + }; + + class SUB_NET_TOEPRINT : public SUB_NET + { + public: + enum class SIDE + { + TOP, + BOTTOM + }; + + SUB_NET_TOEPRINT( size_t aIndex, EDA_DATA* aEda, SIDE aSide, size_t aCompNum, + size_t aToepNum ) : + SUB_NET( aIndex, aEda ), side( aSide ), comp_num( aCompNum ), toep_num( aToepNum ) + { + } + + ~SUB_NET_TOEPRINT() {} + + SIDE side; + + size_t comp_num; + size_t toep_num; + + void WriteSubnet( std::ostream& ost ) const override; + }; + + class NET : public ATTR_RECORD_WRITER + { + public: + NET( size_t aIndex, const wxString& aName ) : m_index( aIndex ), m_name( aName ) {} + + const size_t m_index; + wxString m_name; + std::list> subnets; + + template + T& AddSubnet( Args&&... args ) + { + auto f = std::make_unique( subnets.size(), std::forward( args )... ); + auto& r = *f; + subnets.push_back( std::move( f ) ); + return r; + } + + void Write( std::ostream& ost ) const; + }; + + void AddNET( const NETINFO_ITEM* aNet ); + NET& GetNet( size_t aNetcode ) { return nets_map.at( aNetcode ); } + + class PIN + { + public: + PIN( const size_t aIndex, const wxString& aName ) : m_index( aIndex ), m_name( aName ) {} + + const size_t m_index; + wxString m_name; + + std::pair m_center; + + enum class TYPE + { + THROUGH_HOLE, + BLIND, + SURFACE + }; + + TYPE type = TYPE::SURFACE; + + enum class ELECTRICAL_TYPE + { + ELECTRICAL, + MECHANICAL, + UNDEFINED + }; + + ELECTRICAL_TYPE etype = ELECTRICAL_TYPE::UNDEFINED; + + enum class MOUNT_TYPE + { + SMT, + SMT_RECOMMENDED, + THROUGH_HOLE, + THROUGH_RECOMMENDED, + PRESSFIT, + NON_BOARD, + HOLE, + UNDEFINED + }; + MOUNT_TYPE mtype = MOUNT_TYPE::UNDEFINED; + + std::list> m_pinOutlines; + + void Write( std::ostream& ost ) const; + }; + + class PACKAGE : public ATTR_RECORD_WRITER + { + public: + PACKAGE( const size_t aIndex, const wxString& afpName ) : + m_index( aIndex ), m_name( afpName ) + { + } + + const size_t m_index; /// > m_pkgOutlines; + + void AddPin( const PAD* aPad, size_t aPinNum ); + const std::shared_ptr GetEdaPkgPin( size_t aPadIndex ) const + { + return m_pinsVec.at( aPadIndex ); + } + + void Write( std::ostream& ost ) const; + + private: + std::vector> m_pinsVec; + }; + + void AddPackage( const FOOTPRINT* aFp ); + const PACKAGE& GetPackage( size_t aHash ) const { return packages_map.at( aHash ); } + +private: + std::map nets_map; + std::list nets; + + std::map packages_map; //hash value, package + std::list packages; + + std::map layers_map; + std::vector layers; + std::vector> m_eda_footprints; +}; + +class PKG_OUTLINE +{ +public: + virtual void Write( std::ostream& ost ) const = 0; + + virtual ~PKG_OUTLINE() = default; +}; + +class OUTLINE_RECT : public PKG_OUTLINE +{ +public: + OUTLINE_RECT( const VECTOR2I& aLowerLeft, size_t aWidth, size_t aHeight ) : + m_lower_left( aLowerLeft ), m_width( aWidth ), m_height( aHeight ) + { + } + + OUTLINE_RECT( const BOX2I& aBox ) : + OUTLINE_RECT( aBox.GetPosition(), aBox.GetWidth(), aBox.GetHeight() ) + { + } + + VECTOR2I m_lower_left; + size_t m_width; + size_t m_height; + + void Write( std::ostream& ost ) const override; +}; + +class ODB_SURFACE_DATA; +class OUTLINE_CONTOUR : public PKG_OUTLINE +{ +public: + OUTLINE_CONTOUR( const SHAPE_POLY_SET::POLYGON& aPolygon, + FILL_T aFillType = FILL_T::FILLED_SHAPE ) + { + if( !aPolygon.empty() && aPolygon[0].PointCount() >= 3 ) + { + m_surfaces = std::make_unique( aPolygon ); + if( aFillType != FILL_T::NO_FILL ) + { + m_surfaces->AddPolygonHoles( aPolygon ); + } + } + } + + std::unique_ptr m_surfaces; + + void Write( std::ostream& ost ) const override; +}; + +class OUTLINE_SQUARE : public PKG_OUTLINE +{ +public: + OUTLINE_SQUARE( const VECTOR2I& aCenter, size_t aHalfSide ) : + m_center( aCenter ), m_halfSide( aHalfSide ) + { + } + VECTOR2I m_center; + size_t m_halfSide; + + void Write( std::ostream& ost ) const override; +}; + +class OUTLINE_CIRCLE : public PKG_OUTLINE +{ +public: + OUTLINE_CIRCLE( const VECTOR2I& aCenter, size_t aRadius ) : + m_center( aCenter ), m_radius( aRadius ) + { + } + VECTOR2I m_center; + size_t m_radius; + + void Write( std::ostream& ost ) const override; +}; + + +#endif // _ODB_EDA_DATA_H_ \ No newline at end of file diff --git a/pcbnew/pcb_io/odbpp/odb_entity.cpp b/pcbnew/pcb_io/odbpp/odb_entity.cpp new file mode 100644 index 0000000000..5db7f082eb --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_entity.cpp @@ -0,0 +1,975 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "odb_attribute.h" +#include "odb_entity.h" +#include "odb_defines.h" +#include "odb_feature.h" +#include "odb_util.h" +#include "pcb_io_odbpp.h" + + +bool ODB_ENTITY_BASE::CreateDirectiryTree( ODB_TREE_WRITER& writer ) +{ + try + { + writer.CreateEntityDirectory( writer.GetRootPath(), GetEntityName() ); + return true; + } + catch( const std::exception& e ) + { + std::cerr << e.what() << std::endl; + return false; + } +} + + +ODB_MISC_ENTITY::ODB_MISC_ENTITY() +{ + m_info = { { wxS( ODB_JOB_NAME ), wxS( "job" ) }, + { wxS( ODB_UNITS ), PCB_IO_ODBPP::m_unitsStr }, + { wxS( "ODB_VERSION_MAJOR" ), wxS( "8" ) }, + { wxS( "ODB_VERSION_MINOR" ), wxS( "1" ) }, + { wxS( "ODB_SOURCE" ), wxS( "KiCad EDA" + GetMajorMinorPatchVersion() ) }, + { wxS( "CREATION_DATE" ), wxDateTime::Now().FormatISOCombined() }, + { wxS( "SAVE_DATE" ), wxDateTime::Now().FormatISOCombined() }, + { wxS( "SAVE_APP" ), wxS( "Pcbnew" ) }, + { wxS( "SAVE_USER" ), wxS( "" ) }, + { wxS( "MAX_UID" ), wxS( "" ) } }; +} + + +void ODB_MISC_ENTITY::GenerateFiles( ODB_TREE_WRITER& writer ) +{ + auto fileproxy = writer.CreateFileProxy( "info" ); + + ODB_TEXT_WRITER twriter( fileproxy.GetStream() ); + + for( auto& info : m_info ) + { + twriter.WriteEquationLine( info.first, info.second ); + } +} + + +void ODB_MATRIX_ENTITY::AddStep( const wxString& aStepName ) +{ + m_matrixSteps.emplace( aStepName.Upper(), m_col++ ); +} + + +void ODB_MATRIX_ENTITY::InitEntityData() +{ + AddStep( "PCB" ); + + InitMatrixLayerData(); +} + + +void ODB_MATRIX_ENTITY::InitMatrixLayerData() +{ + BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings(); + BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor(); + stackup.SynchronizeWithBoard( &dsnSettings ); + + std::vector layers = stackup.GetList(); + std::set added_layers; + + for( int i = 0; i < stackup.GetCount(); i++ ) + { + BOARD_STACKUP_ITEM* stackup_item = layers.at( i ); + + for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ ) + { + wxString ly_name = stackup_item->GetLayerName(); + + if( ly_name.IsEmpty() ) + { + if( IsValidLayer( stackup_item->GetBrdLayerId() ) ) + ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() ); + + if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) + ly_name = wxString::Format( "DIELECTRIC_%d", + stackup_item->GetDielectricLayerId() ); + } + + MATRIX_LAYER matrix( m_row++, ly_name ); + + if( stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) + { + if( stackup_item->GetTypeName() == KEY_CORE ) + matrix.m_diType.emplace( ODB_DIELECTRIC_TYPE::CORE ); + else + matrix.m_diType.emplace( ODB_DIELECTRIC_TYPE::PREPREG ); + + matrix.m_type = ODB_TYPE::DIELECTRIC; + matrix.m_context = ODB_CONTEXT::BOARD; + matrix.m_polarity = ODB_POLARITY::POSITIVE; + m_matrixLayers.push_back( matrix ); + m_plugin->GetLayerNameList().emplace_back( + std::make_pair( PCB_LAYER_ID::UNDEFINED_LAYER, matrix.m_layerName ) ); + + continue; + } + else + { + added_layers.insert( stackup_item->GetBrdLayerId() ); + AddMatrixLayerField( matrix, stackup_item->GetBrdLayerId() ); + } + } + } + + LSEQ layer_seq = m_board->GetEnabledLayers().Seq(); + + for( PCB_LAYER_ID layer : layer_seq ) + { + if( added_layers.find( layer ) != added_layers.end() ) + continue; + + MATRIX_LAYER matrix( m_row++, m_board->GetLayerName( layer ) ); + added_layers.insert( layer ); + AddMatrixLayerField( matrix, layer ); + } + + AddDrillMatrixLayer(); + + AddCOMPMatrixLayer(); +} + + +void ODB_MATRIX_ENTITY::AddMatrixLayerField( MATRIX_LAYER& aMLayer, PCB_LAYER_ID aLayer ) +{ + aMLayer.m_polarity = ODB_POLARITY::POSITIVE; + aMLayer.m_context = ODB_CONTEXT::BOARD; + switch( aLayer ) + { + case F_Paste: + case B_Paste: aMLayer.m_type = ODB_TYPE::SOLDER_PASTE; break; + case F_SilkS: + case B_SilkS: aMLayer.m_type = ODB_TYPE::SILK_SCREEN; break; + case F_Mask: + case B_Mask: aMLayer.m_type = ODB_TYPE::SOLDER_MASK; break; + case B_CrtYd: + case F_CrtYd: + case Edge_Cuts: + case B_Fab: + case F_Fab: + case F_Adhes: + case B_Adhes: + case Dwgs_User: + case Cmts_User: + case Eco1_User: + case Eco2_User: + case Margin: + case User_1: + case User_2: + case User_3: + case User_4: + case User_5: + case User_6: + case User_7: + case User_8: + case User_9: + aMLayer.m_context = ODB_CONTEXT::MISC; + aMLayer.m_type = ODB_TYPE::DOCUMENT; + break; + + default: + if( IsCopperLayer( aLayer ) ) + { + aMLayer.m_type = ODB_TYPE::SIGNAL; + } + else + { + // Do not handle other layers : + aMLayer.m_type = ODB_TYPE::UNDEFINED; + m_row--; + } + + break; + } + + if( aMLayer.m_type != ODB_TYPE::UNDEFINED ) + { + m_matrixLayers.push_back( aMLayer ); + m_plugin->GetLayerNameList().emplace_back( std::make_pair( aLayer, aMLayer.m_layerName ) ); + } +} + + +void ODB_MATRIX_ENTITY::AddDrillMatrixLayer() +{ + std::map, std::vector>& drill_layers = + m_plugin->GetDrillLayerItemsMap(); + + std::map, std::vector>& slot_holes = + m_plugin->GetSlotHolesMap(); + + bool has_pth_layer = false; + bool has_npth_layer = false; + + for( BOARD_ITEM* item : m_board->Tracks() ) + { + if( item->Type() == PCB_VIA_T ) + { + PCB_VIA* via = static_cast( item ); + drill_layers[std::make_pair( via->TopLayer(), via->BottomLayer() )].push_back( via ); + } + } + + for( FOOTPRINT* fp : m_board->Footprints() ) + { + // std::shared_ptr fp( static_cast( it_fp->Clone() ) ); + + if( fp->IsFlipped() ) + { + m_hasBotComp = true; + } + + for( PAD* pad : fp->Pads() ) + { + if( !has_pth_layer && pad->GetAttribute() == PAD_ATTRIB::PTH ) + has_pth_layer = true; + if( !has_npth_layer && pad->GetAttribute() == PAD_ATTRIB::NPTH ) + has_npth_layer = true; + + if( pad->HasHole() && pad->GetDrillSizeX() != pad->GetDrillSizeY() ) + slot_holes[std::make_pair( F_Cu, B_Cu )].push_back( pad ); + else if( pad->HasHole() ) + drill_layers[std::make_pair( F_Cu, B_Cu )].push_back( pad ); + } + + // m_plugin->GetLoadedFootprintList().push_back( std::move( fp ) ); + } + + auto InitDrillMatrix = + [&]( const wxString& aHasPlated, std::pair aLayerPair ) + { + wxString dLayerName = wxString::Format( "drill_%s_%s-%s", aHasPlated, + m_board->GetLayerName( aLayerPair.first ), + m_board->GetLayerName( aLayerPair.second ) ); + MATRIX_LAYER matrix( m_row++, dLayerName ); + + matrix.m_type = ODB_TYPE::DRILL; + matrix.m_context = ODB_CONTEXT::BOARD; + matrix.m_polarity = ODB_POLARITY::POSITIVE; + matrix.m_span.emplace( std::make_pair( + ODB::GenLegalEntityName( m_board->GetLayerName( aLayerPair.first ) ), + ODB::GenLegalEntityName( m_board->GetLayerName( aLayerPair.second ) ) ) ); + m_matrixLayers.push_back( matrix ); + m_plugin->GetLayerNameList().emplace_back( + std::make_pair( PCB_LAYER_ID::UNDEFINED_LAYER, matrix.m_layerName ) ); + }; + + if( drill_layers.find( std::make_pair( F_Cu, B_Cu ) ) != drill_layers.end() + || !slot_holes.empty() ) + { + // for pad has hole + if( has_pth_layer ) + InitDrillMatrix( "plated", std::make_pair( F_Cu, B_Cu ) ); + if( has_npth_layer ) + InitDrillMatrix( "non-plated", std::make_pair( F_Cu, B_Cu ) ); + } + + for( const auto& [layer_pair, vec] : drill_layers ) + { + if( layer_pair != std::make_pair( F_Cu, B_Cu ) ) // pad has initialized above + InitDrillMatrix( "plated", layer_pair ); // for via + } +} + + +void ODB_MATRIX_ENTITY::AddCOMPMatrixLayer() +{ + MATRIX_LAYER matrix( m_row++, "COMP_+_TOP" ); + matrix.m_type = ODB_TYPE::COMPONENT; + matrix.m_context = ODB_CONTEXT::BOARD; + + m_matrixLayers.push_back( matrix ); + m_plugin->GetLayerNameList().emplace_back( + std::make_pair( PCB_LAYER_ID::UNDEFINED_LAYER, matrix.m_layerName ) ); + + if( m_hasBotComp ) + { + matrix.m_layerName = ODB::GenLegalEntityName( "COMP_+_BOT" ); + matrix.m_rowNumber = m_row++; + m_matrixLayers.push_back( matrix ); + m_plugin->GetLayerNameList().emplace_back( + std::make_pair( PCB_LAYER_ID::UNDEFINED_LAYER, matrix.m_layerName ) ); + } +} + + +void ODB_MATRIX_ENTITY::GenerateFiles( ODB_TREE_WRITER& writer ) +{ + auto fileproxy = writer.CreateFileProxy( "matrix" ); + + ODB_TEXT_WRITER twriter( fileproxy.GetStream() ); + + for( const auto& [step_name, column] : m_matrixSteps ) + { + const auto array_proxy = twriter.MakeArrayProxy( "STEP" ); + twriter.WriteEquationLine( "COL", column ); + twriter.WriteEquationLine( "NAME", step_name ); + } + + for( const auto& layer : m_matrixLayers ) + { + const auto array_proxy = twriter.MakeArrayProxy( "LAYER" ); + twriter.WriteEquationLine( "ROW", layer.m_rowNumber ); + twriter.write_line_enum( "CONTEXT", layer.m_context ); + twriter.write_line_enum( "TYPE", layer.m_type ); + + if( layer.m_addType.has_value() ) + { + twriter.write_line_enum( "ADD_TYPE", layer.m_addType.value() ); + } + + twriter.WriteEquationLine( "NAME", layer.m_layerName.Upper() ); + twriter.WriteEquationLine( "OLD_NAME", wxEmptyString ); + twriter.write_line_enum( "POLARITY", layer.m_polarity ); + + if( layer.m_diType.has_value() ) + { + twriter.write_line_enum( "DIELECTRIC_TYPE", layer.m_diType.value() ); + } + + twriter.WriteEquationLine( "DIELECTRIC_NAME", wxEmptyString ); + twriter.WriteEquationLine( "CU_TOP", wxEmptyString ); + twriter.WriteEquationLine( "CU_BOTTOM", wxEmptyString ); + twriter.WriteEquationLine( "REF", wxEmptyString ); + + if( layer.m_span.has_value() ) + { + twriter.WriteEquationLine( "START_NAME", layer.m_span->first.Upper() ); + twriter.WriteEquationLine( "END_NAME", layer.m_span->second.Upper() ); + } + else + { + twriter.WriteEquationLine( "START_NAME", wxEmptyString ); + twriter.WriteEquationLine( "END_NAME", wxEmptyString ); + } + twriter.WriteEquationLine( "COLOR", wxEmptyString ); + } +} + + +ODB_LAYER_ENTITY::ODB_LAYER_ENTITY( BOARD* aBoard, PCB_IO_ODBPP* aPlugin, + std::map>& aMap, + const PCB_LAYER_ID& aLayerID, const wxString& aLayerName ) : + ODB_ENTITY_BASE( aBoard, aPlugin ), m_layerItems( aMap ), m_layerID( aLayerID ), + m_matrixLayerName( aLayerName ) +{ + m_featuresMgr = std::make_unique( aBoard, aPlugin, aLayerName ); +} + + +void ODB_LAYER_ENTITY::InitEntityData() +{ + if( m_matrixLayerName.Contains( "drill" ) ) + { + InitDrillData(); + InitFeatureData(); + return; + } + + if( m_layerID != PCB_LAYER_ID::UNDEFINED_LAYER ) + { + InitFeatureData(); + } +} + +void ODB_LAYER_ENTITY::InitFeatureData() +{ + if( m_layerItems.empty() ) + return; + + const NETINFO_LIST& nets = m_board->GetNetInfo(); + + for( const NETINFO_ITEM* net : nets ) + { + std::vector& vec = m_layerItems[net->GetNetCode()]; + + std::stable_sort( vec.begin(), vec.end(), + []( BOARD_ITEM* a, BOARD_ITEM* b ) + { + if( a->GetParentFootprint() == b->GetParentFootprint() ) + return a->Type() < b->Type(); + + return a->GetParentFootprint() < b->GetParentFootprint(); + } ); + + if( vec.empty() ) + continue; + + m_featuresMgr->InitFeatureList( m_layerID, vec ); + } +} + + +ODB_COMPONENT& ODB_LAYER_ENTITY::InitComponentData( const FOOTPRINT* aFp, + const EDA_DATA::PACKAGE& aPkg ) +{ + if( m_matrixLayerName == "COMP_+_BOT" ) + { + if( !m_compBot.has_value() ) + { + m_compBot.emplace(); + } + return m_compBot.value().AddComponent( aFp, aPkg ); + } + else + { + if( !m_compTop.has_value() ) + { + m_compTop.emplace(); + } + + return m_compTop.value().AddComponent( aFp, aPkg ); + } +} + + +void ODB_LAYER_ENTITY::InitDrillData() +{ + std::map, std::vector>& drill_layers = + m_plugin->GetDrillLayerItemsMap(); + + std::map, std::vector>& slot_holes = + m_plugin->GetSlotHolesMap(); + + if( !m_layerItems.empty() ) + { + m_layerItems.clear(); + } + + m_tools.emplace( PCB_IO_ODBPP::m_unitsStr ); + + bool is_npth_layer = false; + wxString plated_name = "plated"; + + if( m_matrixLayerName.Contains( "non-plated" ) ) + { + is_npth_layer = true; + plated_name = "non-plated"; + } + + + for( const auto& [layer_pair, vec] : slot_holes ) + { + wxString dLayerName = wxString::Format( "drill_%s_%s-%s", plated_name, + m_board->GetLayerName( layer_pair.first ), + m_board->GetLayerName( layer_pair.second ) ); + + if( ODB::GenLegalEntityName( dLayerName ) == m_matrixLayerName ) + { + for( BOARD_ITEM* item : vec ) + { + if( item->Type() == PCB_PAD_T ) + { + PAD* pad = static_cast( item ); + + if( ( is_npth_layer && pad->GetAttribute() == PAD_ATTRIB::PTH ) + || ( !is_npth_layer && pad->GetAttribute() == PAD_ATTRIB::NPTH ) ) + { + continue; + } + + m_tools.value().AddDrillTools( + pad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NON_PLATED", + ODB::SymDouble2String( + std::min( pad->GetDrillSizeX(), pad->GetDrillSizeY() ) ) ); + + // for drill features + m_layerItems[pad->GetNetCode()].push_back( item ); + } + } + + break; + } + } + + for( const auto& [layer_pair, vec] : drill_layers ) + { + wxString dLayerName = wxString::Format( "drill_%s_%s-%s", plated_name, + m_board->GetLayerName( layer_pair.first ), + m_board->GetLayerName( layer_pair.second ) ); + + if( ODB::GenLegalEntityName( dLayerName ) == m_matrixLayerName ) + { + for( BOARD_ITEM* item : vec ) + { + if( item->Type() == PCB_VIA_T && !is_npth_layer ) + { + PCB_VIA* via = static_cast( item ); + + m_tools.value().AddDrillTools( "VIA", + ODB::SymDouble2String( via->GetDrillValue() ) ); + + // for drill features + m_layerItems[via->GetNetCode()].push_back( item ); + } + else if( item->Type() == PCB_PAD_T ) + { + PAD* pad = static_cast( item ); + + if( ( is_npth_layer && pad->GetAttribute() == PAD_ATTRIB::PTH ) + || ( !is_npth_layer && pad->GetAttribute() == PAD_ATTRIB::NPTH ) ) + { + continue; + } + + m_tools.value().AddDrillTools( + pad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NON_PLATED", + ODB::SymDouble2String( pad->GetDrillSizeX() ) ); + + // for drill features + m_layerItems[pad->GetNetCode()].push_back( item ); + } + } + + break; + } + } +} + + +void ODB_STEP_ENTITY::InitEntityData() +{ + MakeLayerEntity(); + + InitEdaData(); + + // Init Layer Entity Data + for( const auto& [layerName, layer_entity_ptr] : m_layerEntityMap ) + { + layer_entity_ptr->InitEntityData(); + } +} + + +void ODB_LAYER_ENTITY::GenerateFiles( ODB_TREE_WRITER& writer ) +{ + GenAttrList( writer ); + + GenFeatures( writer ); + + if( m_compTop.has_value() || m_compBot.has_value() ) + { + GenComponents( writer ); + } + + if( m_tools.has_value() ) + { + GenTools( writer ); + } +} + + +void ODB_LAYER_ENTITY::GenComponents( ODB_TREE_WRITER& writer ) +{ + auto fileproxy = writer.CreateFileProxy( "components" ); + + if( m_compTop.has_value() ) + { + m_compTop->Write( fileproxy.GetStream() ); + } + else if( m_compBot.has_value() ) + { + m_compBot->Write( fileproxy.GetStream() ); + } +} + + +void ODB_LAYER_ENTITY::GenFeatures( ODB_TREE_WRITER& writer ) +{ + auto fileproxy = writer.CreateFileProxy( "features" ); + + m_featuresMgr->GenerateFeatureFile( fileproxy.GetStream() ); +} + + +void ODB_LAYER_ENTITY::GenAttrList( ODB_TREE_WRITER& writer ) +{ + auto fileproxy = writer.CreateFileProxy( "attrlist" ); +} + + +void ODB_LAYER_ENTITY::GenTools( ODB_TREE_WRITER& writer ) +{ + auto fileproxy = writer.CreateFileProxy( "tools" ); + + m_tools.value().GenerateFile( fileproxy.GetStream() ); +} + + +void ODB_STEP_ENTITY::InitEdaData() +{ + //InitPackage + for( const FOOTPRINT* fp : m_board->Footprints() ) + { + m_edaData.AddPackage( fp ); + } + + // for NET + const NETINFO_LIST& nets = m_board->GetNetInfo(); + + for( const NETINFO_ITEM* net : nets ) + { + m_edaData.AddNET( net ); + } + + // for CMP + size_t j = 0; + + for( const FOOTPRINT* fp : m_board->Footprints() ) + { + wxString compName = ODB::GenLegalEntityName( "COMP_+_TOP" ); + if( fp->IsFlipped() ) + compName = ODB::GenLegalEntityName( "COMP_+_BOT" ); + + auto iter = m_layerEntityMap.find( compName ); + + if( iter == m_layerEntityMap.end() ) + { + wxLogError( _( "Failed to add component data" ) ); + return; + } + + // ODBPP only need unique PACKAGE in PKG record in eda/data file. + // the PKG index can repeat to be ref in CMP record in component file. + std::shared_ptr fp_pkg = m_edaData.GetEdaFootprints().at( j ); + ++j; + + const EDA_DATA::PACKAGE& eda_pkg = + m_edaData.GetPackage( hash_fp_item( fp_pkg.get(), HASH_POS | REL_COORD ) ); + + ODB_COMPONENT& comp = iter->second->InitComponentData( fp, eda_pkg ); + + for( int i = 0; i < fp->Pads().size(); ++i ) + { + PAD* pad = fp->Pads()[i]; + auto& eda_net = m_edaData.GetNet( pad->GetNetCode() ); + + auto& subnet = eda_net.AddSubnet( + &m_edaData, + fp->IsFlipped() ? EDA_DATA::SUB_NET_TOEPRINT::SIDE::BOTTOM + : EDA_DATA::SUB_NET_TOEPRINT::SIDE::TOP, + comp.m_index, comp.m_toeprints.size() ); + + m_plugin->GetPadSubnetMap().emplace( pad, &subnet ); + + const std::shared_ptr pin = eda_pkg.GetEdaPkgPin( i ); + const EDA_DATA::PIN& pin_ref = *pin; + auto& toep = comp.m_toeprints.emplace_back( pin_ref ); + + toep.m_net_num = eda_net.m_index; + toep.m_subnet_num = subnet.m_index; + + toep.m_center = ODB::AddXY( pad->GetPosition() ); + + toep.m_rot = ODB::Double2String( + ( ANGLE_360 - pad->GetOrientation() ).Normalize().AsDegrees() ); + + if( pad->IsFlipped() ) + toep.m_mirror = wxT( "M" ); + else + toep.m_mirror = wxT( "N" ); + } + } + + for( PCB_TRACK* track : m_board->Tracks() ) + { + auto& eda_net = m_edaData.GetNet( track->GetNetCode() ); + EDA_DATA::SUB_NET* subnet = nullptr; + + if( track->Type() == PCB_VIA_T ) + subnet = &( eda_net.AddSubnet( &m_edaData ) ); + else + subnet = &( eda_net.AddSubnet( &m_edaData ) ); + + m_plugin->GetViaTraceSubnetMap().emplace( track, subnet ); + } + + for( ZONE* zone : m_board->Zones() ) + { + for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() ) + { + auto& eda_net = m_edaData.GetNet( zone->GetNetCode() ); + auto& subnet = eda_net.AddSubnet( + &m_edaData, EDA_DATA::SUB_NET_PLANE::FILL_TYPE::SOLID, + EDA_DATA::SUB_NET_PLANE::CUTOUT_TYPE::EXACT, 0 ); + m_plugin->GetPlaneSubnetMap().emplace( std::piecewise_construct, + std::forward_as_tuple( layer, zone ), + std::forward_as_tuple( &subnet ) ); + } + } +} + + +void ODB_STEP_ENTITY::GenerateFiles( ODB_TREE_WRITER& writer ) +{ + wxString step_root = writer.GetCurrentPath(); + + writer.CreateEntityDirectory( step_root, "layers" ); + GenerateLayerFiles( writer ); + + writer.CreateEntityDirectory( step_root, "eda" ); + GenerateEdaFiles( writer ); + + writer.CreateEntityDirectory( step_root, "netlists/cadnet" ); + GenerateNetlistsFiles( writer ); + + writer.SetCurrentPath( step_root ); + GenerateProfileFile( writer ); + + GenerateStepHeaderFile( writer ); + + //TODO: system attributes + // GenerateAttrListFile( writer ); +} + + +void ODB_STEP_ENTITY::GenerateProfileFile( ODB_TREE_WRITER& writer ) +{ + auto fileproxy = writer.CreateFileProxy( "profile" ); + + m_profile = std::make_unique( m_board, m_plugin, wxEmptyString ); + + SHAPE_POLY_SET board_outline; + + if( !m_board->GetBoardPolygonOutlines( board_outline ) ) + { + wxLogError( "Failed to get board outline" ); + } + + if( !m_profile->AddContour( board_outline, 0 ) ) + { + wxLogError( "Failed to add polygon to profile" ); + } + + m_profile->GenerateProfileFeatures( fileproxy.GetStream() ); +} + + +void ODB_STEP_ENTITY::GenerateStepHeaderFile( ODB_TREE_WRITER& writer ) +{ + auto fileproxy = writer.CreateFileProxy( "stephdr" ); + + m_stephdr = { + { ODB_UNITS, PCB_IO_ODBPP::m_unitsStr }, + { "X_DATUM", "0" }, + { "Y_DATUM", "0" }, + { "X_ORIGIN", "0" }, + { "Y_ORIGIN", "0" }, + { "TOP_ACTIVE", "0" }, + { "BOTTOM_ACTIVE", "0" }, + { "RIGHT_ACTIVE", "0" }, + { "LEFT_ACTIVE", "0" }, + { "AFFECTING_BOM", "" }, + { "AFFECTING_BOM_CHANGED", "0" }, + }; + + ODB_TEXT_WRITER twriter( fileproxy.GetStream() ); + + for( const auto& [key, value] : m_stephdr ) + { + twriter.WriteEquationLine( key, value ); + } +} + + +void ODB_STEP_ENTITY::GenerateLayerFiles( ODB_TREE_WRITER& writer ) +{ + wxString layers_root = writer.GetCurrentPath(); + + for( auto& [layerName, layerEntity] : m_layerEntityMap ) + { + writer.CreateEntityDirectory( layers_root, layerName ); + + layerEntity->GenerateFiles( writer ); + } +} + + +void ODB_STEP_ENTITY::GenerateEdaFiles( ODB_TREE_WRITER& writer ) +{ + auto fileproxy = writer.CreateFileProxy( "data" ); + + m_edaData.Write( fileproxy.GetStream() ); +} + + +void ODB_STEP_ENTITY::GenerateNetlistsFiles( ODB_TREE_WRITER& writer ) +{ + auto fileproxy = writer.CreateFileProxy( "netlist" ); + + m_netlist.Write( fileproxy.GetStream() ); +} + + +bool ODB_STEP_ENTITY::CreateDirectiryTree( ODB_TREE_WRITER& writer ) +{ + try + { + writer.CreateEntityDirectory( writer.GetRootPath(), "steps" ); + writer.CreateEntityDirectory( writer.GetCurrentPath(), GetEntityName() ); + return true; + } + catch( const std::exception& e ) + { + std::cerr << e.what() << std::endl; + return false; + } +} + + +void ODB_STEP_ENTITY::MakeLayerEntity() +{ + LSEQ layers = m_board->GetEnabledLayers().Seq(); + const NETINFO_LIST& nets = m_board->GetNetInfo(); + + // To avoid the overhead of repeatedly cycling through the layers and nets, + // we pre-sort the board items into a map of layer -> net -> items + std::map>>& elements = + m_plugin->GetLayerElementsMap(); + + std::for_each( m_board->Tracks().begin(), m_board->Tracks().end(), + [&layers, &elements]( PCB_TRACK* aTrack ) + { + if( aTrack->Type() == PCB_VIA_T ) + { + PCB_VIA* via = static_cast( aTrack ); + + for( PCB_LAYER_ID layer : layers ) + { + if( via->FlashLayer( layer ) ) + elements[layer][via->GetNetCode()].push_back( via ); + } + } + else + { + elements[aTrack->GetLayer()][aTrack->GetNetCode()].push_back( aTrack ); + } + } ); + + std::for_each( m_board->Zones().begin(), m_board->Zones().end(), + [&elements]( ZONE* zone ) + { + LSEQ zone_layers = zone->GetLayerSet().Seq(); + + for( PCB_LAYER_ID layer : zone_layers ) + { + elements[layer][zone->GetNetCode()].push_back( zone ); + } + } ); + + for( BOARD_ITEM* item : m_board->Drawings() ) + { + if( BOARD_CONNECTED_ITEM* conn_it = dynamic_cast( item ) ) + elements[conn_it->GetLayer()][conn_it->GetNetCode()].push_back( conn_it ); + else + elements[item->GetLayer()][0].push_back( item ); + } + + for( FOOTPRINT* fp : m_board->Footprints() ) + { + for( PCB_FIELD* field : fp->GetFields() ) + elements[field->GetLayer()][0].push_back( field ); + + for( BOARD_ITEM* item : fp->GraphicalItems() ) + elements[item->GetLayer()][0].push_back( item ); + + for( PAD* pad : fp->Pads() ) + { + LSEQ pad_layers = pad->GetLayerSet().Seq(); + VECTOR2I margin; + + for( PCB_LAYER_ID layer : pad_layers ) + { + bool onCopperLayer = ( LSET::AllCuMask() & LSET( { layer } ) ).any(); + bool onSolderMaskLayer = ( LSET( { F_Mask, B_Mask } ) & LSET( { layer } ) ).any(); + bool onSolderPasteLayer = + ( LSET( { F_Paste, B_Paste } ) & LSET( { layer } ) ).any(); + + if( onSolderMaskLayer ) + margin.x = margin.y = pad->GetSolderMaskExpansion(); + + if( onSolderPasteLayer ) + margin = pad->GetSolderPasteMargin(); + + VECTOR2I padPlotsSize = pad->GetSize() + margin * 2; + + if( onCopperLayer && !pad->IsOnCopperLayer() ) + continue; + + if( onCopperLayer && !pad->FlashLayer( layer ) ) + continue; + + if( pad->GetShape() != PAD_SHAPE::CUSTOM + && ( padPlotsSize.x <= 0 || padPlotsSize.y <= 0 ) ) + continue; + + elements[layer][pad->GetNetCode()].push_back( pad ); + } + } + } + + for( const auto& [layerID, layerName] : m_plugin->GetLayerNameList() ) + { + std::shared_ptr layer_entity_ptr = std::make_shared( + m_board, m_plugin, elements[layerID], layerID, layerName ); + + m_layerEntityMap.emplace( layerName, layer_entity_ptr ); + } +} diff --git a/pcbnew/pcb_io/odbpp/odb_entity.h b/pcbnew/pcb_io/odbpp/odb_entity.h new file mode 100644 index 0000000000..3d67a017b9 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_entity.h @@ -0,0 +1,273 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + +#ifndef _ODB_ENTITY_H_ +#define _ODB_ENTITY_H_ + + +#include +#include +#include +#include +#include +#include +#include "odb_feature.h" +#include "odb_eda_data.h" +#include "odb_netlist.h" +#include "odb_component.h" + + +class BOARD; +class ODB_TREE_WRITER; +class BOARD_ITEM; +class PCB_IO_ODBPP; + +class ODB_ENTITY_BASE +{ +public: + ODB_ENTITY_BASE( BOARD* aBoard, PCB_IO_ODBPP* aPlugin ) : m_board( aBoard ), m_plugin( aPlugin ) + { + } + + ODB_ENTITY_BASE() : m_board( nullptr ), m_plugin( nullptr ) {} + + virtual ~ODB_ENTITY_BASE() = default; + virtual void GenerateFiles( ODB_TREE_WRITER& writer ) {} + virtual bool CreateDirectiryTree( ODB_TREE_WRITER& writer ); + virtual std::string GetEntityName() = 0; + virtual void InitEntityData() {} + + +protected: + BOARD* m_board; + std::vector m_fileName; + PCB_IO_ODBPP* m_plugin; +}; + +enum class ODB_SUBTYPE; +enum class ODB_POLARITY; +enum class ODB_CONTEXT; +enum class ODB_TYPE; + +class ODB_MATRIX_ENTITY : public ODB_ENTITY_BASE +{ +public: + ODB_MATRIX_ENTITY( BOARD* aBoard, PCB_IO_ODBPP* aPlugin ) : ODB_ENTITY_BASE( aBoard, aPlugin ) + { + } + + virtual ~ODB_MATRIX_ENTITY() = default; + + inline virtual std::string GetEntityName() { return "matrix"; } + + struct MATRIX_LAYER + { + std::optional> m_span; // !< start, end + std::optional m_addType; + std::optional m_diType; + + uint32_t m_rowNumber; + wxString m_layerName; + ODB_CONTEXT m_context; + ODB_TYPE m_type; + ODB_POLARITY m_polarity = ODB_POLARITY::POSITIVE; + + MATRIX_LAYER( uint32_t aRow, const wxString& aLayerName ) : + m_rowNumber( aRow ), m_layerName( ODB::GenLegalEntityName( aLayerName ) ) + { + } + }; + + virtual void GenerateFiles( ODB_TREE_WRITER& writer ); + virtual void InitEntityData(); + void InitMatrixLayerData(); + + void AddStep( const wxString& aStepName ); + void AddMatrixLayerField( MATRIX_LAYER& aMLayer, PCB_LAYER_ID aLayer ); + void AddDrillMatrixLayer(); + void AddCOMPMatrixLayer(); + +private: + std::map m_matrixSteps; + std::vector m_matrixLayers; + unsigned int m_row = 1; + unsigned int m_col = 1; + bool m_hasBotComp = false; +}; + +class ODB_MISC_ENTITY : public ODB_ENTITY_BASE +{ +public: + ODB_MISC_ENTITY(); + virtual ~ODB_MISC_ENTITY() = default; + inline virtual std::string GetEntityName() { return "misc"; } + + //TODO + // bool AddAttrList(); + // bool AddSysAttrFiles(); + virtual void GenerateFiles( ODB_TREE_WRITER& writer ); + +private: + std::map m_info; + // ODB_ATTRLIST m_attrlist; +}; + +class FEATURES_MANAGER; +class ODB_LAYER_ENTITY; +class ODB_STEP_ENTITY : public ODB_ENTITY_BASE +{ +public: + ODB_STEP_ENTITY( BOARD* aBoard, PCB_IO_ODBPP* aPlugin ) : + ODB_ENTITY_BASE( aBoard, aPlugin ), m_profile( nullptr ), m_netlist( aBoard ) + { + } + + virtual ~ODB_STEP_ENTITY() = default; + + inline virtual std::string GetEntityName() { return "pcb"; } + + void InitEdaData(); + void InitPackage(); + void InitNetListData(); + void MakeLayerEntity(); + bool AddNetList(); + bool AddProfile(); + bool AddStepHeader(); + + virtual bool CreateDirectiryTree( ODB_TREE_WRITER& writer ); + + virtual void InitEntityData(); + void GenerateLayerFiles( ODB_TREE_WRITER& writer ); + void GenerateEdaFiles( ODB_TREE_WRITER& writer ); + void GenerateNetlistsFiles( ODB_TREE_WRITER& writer ); + void GenerateProfileFile( ODB_TREE_WRITER& writer ); + void GenerateStepHeaderFile( ODB_TREE_WRITER& writer ); + + virtual void GenerateFiles( ODB_TREE_WRITER& writer ); + +private: + // ODB_ATTRLIST m_attrList; + std::map> m_layerEntityMap; + std::unique_ptr m_profile; + + EDA_DATA m_edaData; + std::unordered_map m_stephdr; + ODB_NET_LIST m_netlist; +}; + + +class ODB_LAYER_ENTITY : public ODB_ENTITY_BASE +{ +public: + ODB_LAYER_ENTITY( BOARD* aBoard, PCB_IO_ODBPP* aPlugin, + std::map>& aMap, const PCB_LAYER_ID& aLayerID, + const wxString& aLayerName ); + + virtual ~ODB_LAYER_ENTITY() = default; + + inline virtual std::string GetEntityName() { return "layers"; } + virtual void InitEntityData(); + void InitFeatureData(); + ODB_COMPONENT& InitComponentData( const FOOTPRINT* aFp, const EDA_DATA::PACKAGE& aPkg ); + void InitDrillData(); + + void AddLayerFeatures(); + + + void GenAttrList( ODB_TREE_WRITER& writer ); + void GenComponents( ODB_TREE_WRITER& writer ); + void GenTools( ODB_TREE_WRITER& writer ); + + void GenFeatures( ODB_TREE_WRITER& writer ); + + virtual void GenerateFiles( ODB_TREE_WRITER& writer ); + +private: + std::map> m_layerItems; + PCB_LAYER_ID m_layerID; + wxString m_matrixLayerName; + // ODB_ATTRLIST m_attrList; + std::optional m_tools; + std::optional m_compTop; + std::optional m_compBot; + std::unique_ptr m_featuresMgr; +}; + +class ODB_SYMBOLS_ENTITY : public ODB_ENTITY_BASE +{ +public: + ODB_SYMBOLS_ENTITY() = default; + + virtual ~ODB_SYMBOLS_ENTITY() = default; + + inline virtual std::string GetEntityName() { return "symbols"; } + + //TODO + // virtual void GenerateFiles( ODB_TREE_WRITER& writer ); +}; + +class ODB_FONTS_ENTITY : public ODB_ENTITY_BASE +{ +public: + ODB_FONTS_ENTITY() = default; + virtual ~ODB_FONTS_ENTITY() = default; + + inline virtual std::string GetEntityName() { return "fonts"; } + + virtual void GenerateFiles( ODB_TREE_WRITER& writer ); +}; + +class ODB_WHEELS_ENTITY : public ODB_ENTITY_BASE +{ +public: + ODB_WHEELS_ENTITY() = default; + virtual ~ODB_WHEELS_ENTITY() = default; + + inline virtual std::string GetEntityName() { return "wheels"; } + + // TODO + // virtual void GenerateFiles( ODB_TREE_WRITER& writer ); +}; + +class ODB_INPUT_ENTITY : public ODB_ENTITY_BASE +{ +public: + ODB_INPUT_ENTITY() = default; + virtual ~ODB_INPUT_ENTITY() = default; + + inline virtual std::string GetEntityName() { return "input"; } + + // TODO + // virtual void GenerateFiles( ODB_TREE_WRITER &writer ); +}; + +class ODB_USER_ENTITY : public ODB_ENTITY_BASE +{ +public: + ODB_USER_ENTITY() = default; + virtual ~ODB_USER_ENTITY() = default; + + inline virtual std::string GetEntityName() { return "user"; } + + // TODO + // virtual void GenerateFiles( ODB_TREE_WRITER &writer ); +}; + +#endif // _ODB_ENTITY_H_ diff --git a/pcbnew/pcb_io/odbpp/odb_feature.cpp b/pcbnew/pcb_io/odbpp/odb_feature.cpp new file mode 100644 index 0000000000..fe3ce0b452 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_feature.cpp @@ -0,0 +1,1002 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + +#include "odb_feature.h" +#include +#include "pcb_shape.h" +#include "odb_defines.h" +#include "pcb_track.h" +#include "pcb_textbox.h" +#include "zone.h" +#include "board.h" +#include "board_design_settings.h" +#include "geometry/eda_angle.h" +#include "odb_eda_data.h" +#include "pcb_io_odbpp.h" +#include +#include "wx/log.h" +#include + + +void FEATURES_MANAGER::AddFeatureLine( const VECTOR2I& aStart, const VECTOR2I& aEnd, + uint64_t aWidth ) +{ + AddFeature( ODB::AddXY( aStart ), ODB::AddXY( aEnd ), + AddCircleSymbol( ODB::SymDouble2String( aWidth ) ) ); +} + + +void FEATURES_MANAGER::AddFeatureArc( const VECTOR2I& aStart, const VECTOR2I& aEnd, + const VECTOR2I& aCenter, uint64_t aWidth, + ODB_DIRECTION aDirection ) +{ + AddFeature( ODB::AddXY( aStart ), ODB::AddXY( aEnd ), ODB::AddXY( aCenter ), + AddCircleSymbol( ODB::SymDouble2String( aWidth ) ), aDirection ); +} + + +void FEATURES_MANAGER::AddPadCircle( const VECTOR2I& aCenter, uint64_t aDiameter, + const EDA_ANGLE& aAngle, bool aMirror, + double aResize /*= 1.0 */ ) +{ + AddFeature( ODB::AddXY( aCenter ), + AddCircleSymbol( ODB::SymDouble2String( aDiameter ) ), aAngle, aMirror, + aResize ); +} + + +bool FEATURES_MANAGER::AddContour( const SHAPE_POLY_SET& aPolySet, int aOutline /*= 0*/, + FILL_T aFillType /*= FILL_T::FILLED_SHAPE*/ ) +{ + // todo: args modify aPolySet.Polygon( aOutline ) instead of aPolySet + + if( aPolySet.OutlineCount() < ( aOutline + 1 ) ) + return false; + + AddFeatureSurface( aPolySet.Polygon( aOutline ), aFillType ); + + return true; +} + + +void FEATURES_MANAGER::AddShape( const PCB_SHAPE& aShape, PCB_LAYER_ID aLayer ) +{ + switch( aShape.GetShape() ) + { + case SHAPE_T::CIRCLE: + { + int diameter = aShape.GetRadius() * 2; + int width = aShape.GetWidth(); + VECTOR2I center = ODB::GetShapePosition( aShape ); + wxString innerDim = ODB::SymDouble2String( ( diameter - width / 2 ) ); + wxString outerDim = ODB::SymDouble2String( ( width + diameter ) ); + + if( aShape.GetFillMode() == FILL_T::NO_FILL ) + { + AddFeature( ODB::AddXY( center ), AddRoundDonutSymbol( outerDim, innerDim ) ); + } + else + { + AddFeature( ODB::AddXY( center ), AddCircleSymbol( outerDim ) ); + } + + break; + } + + case SHAPE_T::RECTANGLE: + { + int stroke_width = aShape.GetWidth(); + int width = std::abs( aShape.GetRectangleWidth() ) + stroke_width; + int height = std::abs( aShape.GetRectangleHeight() ) + stroke_width; + wxString rad = ODB::SymDouble2String( ( stroke_width / 2.0 ) ); + VECTOR2I center = ODB::GetShapePosition( aShape ); + + if( aShape.GetFillMode() == FILL_T::NO_FILL ) + { + AddFeature( ODB::AddXY( center ), + AddRoundRectDonutSymbol( ODB::SymDouble2String( width ), + ODB::SymDouble2String( height ), + ODB::SymDouble2String( stroke_width ), + rad ) ); + } + else + { + AddFeature( ODB::AddXY( center ), + AddRoundRectSymbol( ODB::SymDouble2String( width ), + ODB::SymDouble2String( height ), rad ) ); + } + + break; + } + + case SHAPE_T::POLY: + { + int soldermask_min_thickness = 0; + + // TODO: check if soldermask_min_thickness should be Stroke width + + if( aLayer != UNDEFINED_LAYER && LSET( { F_Mask, B_Mask } ).Contains( aLayer ) ) + soldermask_min_thickness = aShape.GetWidth(); + int maxError = m_board->GetDesignSettings().m_MaxError; + SHAPE_POLY_SET poly_set; + + if( soldermask_min_thickness == 0 ) + { + poly_set = aShape.GetPolyShape().CloneDropTriangulation(); + poly_set.Fracture( SHAPE_POLY_SET::PM_FAST ); + } + else + { + SHAPE_POLY_SET initialPolys; + + // add shapes inflated by aMinThickness/2 in areas + aShape.TransformShapeToPolygon( initialPolys, aLayer, 0, maxError, ERROR_OUTSIDE ); + aShape.TransformShapeToPolygon( poly_set, aLayer, soldermask_min_thickness / 2 - 1, + maxError, ERROR_OUTSIDE ); + + poly_set.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + poly_set.Deflate( soldermask_min_thickness / 2 - 1, + CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError ); + poly_set.BooleanAdd( initialPolys, SHAPE_POLY_SET::PM_FAST ); + poly_set.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + } + + for( int ii = 0; ii < poly_set.OutlineCount(); ++ii ) + { + if( aShape.GetFillMode() != FILL_T::NO_FILL ) + { + AddContour( poly_set, ii, FILL_T::FILLED_SHAPE ); + } + + AddContour( poly_set, ii, FILL_T::NO_FILL ); + } + + break; + } + + case SHAPE_T::ARC: + { + ODB_DIRECTION dir = !aShape.IsClockwiseArc() ? ODB_DIRECTION::CW : ODB_DIRECTION::CCW; + + AddFeatureArc( aShape.GetStart(), aShape.GetEnd(), aShape.GetCenter(), + aShape.GetStroke().GetWidth(), dir ); + + break; + } + + case SHAPE_T::BEZIER: + { + const std::vector& points = aShape.GetBezierPoints(); + + for( size_t i = 0; i < points.size(); i++ ) + { + AddFeatureLine( points[i], points[i + 1], aShape.GetStroke().GetWidth() ); + } + + break; + } + + case SHAPE_T::SEGMENT: + { + AddFeatureLine( aShape.GetStart(), aShape.GetEnd(), aShape.GetStroke().GetWidth() ); + + break; + } + + default: + { + wxLogError( _( "Unknown shape when adding ODBPP layer feature" ) ); + break; + } + } +} + + +void FEATURES_MANAGER::AddFeatureSurface( const SHAPE_POLY_SET::POLYGON& aPolygon, + FILL_T aFillType /*= FILL_T::FILLED_SHAPE */ ) +{ + AddFeature( aPolygon, aFillType ); +} + + +void FEATURES_MANAGER::AddPadShape( const PAD& aPad, PCB_LAYER_ID aLayer ) +{ + FOOTPRINT* fp = aPad.GetParentFootprint(); + bool mirror = false; + + if( aPad.GetOrientation() != ANGLE_0 ) + { + if( fp && fp->IsFlipped() ) + mirror = true; + } + + int maxError = m_board->GetDesignSettings().m_MaxError; + + VECTOR2I expansion{ 0, 0 }; + + if( aLayer != UNDEFINED_LAYER && LSET( { F_Mask, B_Mask } ).Contains( aLayer ) ) + expansion.x = expansion.y = aPad.GetSolderMaskExpansion(); + + if( aLayer != UNDEFINED_LAYER && LSET( { F_Paste, B_Paste } ).Contains( aLayer ) ) + expansion = aPad.GetSolderPasteMargin(); + + int mask_clearance = expansion.x; + + VECTOR2I plotSize = aPad.GetSize() + 2 * expansion; + + VECTOR2I center = aPad.ShapePos(); + + wxString width = ODB::SymDouble2String( std::abs( plotSize.x ) ); + wxString height = ODB::SymDouble2String( std::abs( plotSize.y ) ); + + switch( aPad.GetShape() ) + { + case PAD_SHAPE::CIRCLE: + { + wxString diam = ODB::SymDouble2String( plotSize.x ); + + AddFeature( ODB::AddXY( center ), AddCircleSymbol( diam ), aPad.GetOrientation(), + mirror ); + + break; + } + case PAD_SHAPE::RECTANGLE: + { + if( mask_clearance > 0 ) + { + wxString rad = ODB::SymDouble2String( mask_clearance ); + + AddFeature( ODB::AddXY( center ), AddRoundRectSymbol( width, height, rad ), + aPad.GetOrientation(), mirror ); + } + else + { + AddFeature( ODB::AddXY( center ), AddRectSymbol( width, height ), + aPad.GetOrientation(), mirror ); + } + + break; + } + case PAD_SHAPE::OVAL: + { + AddFeature( ODB::AddXY( center ), AddOvalSymbol( width, height ), + aPad.GetOrientation(), mirror ); + break; + } + case PAD_SHAPE::ROUNDRECT: + { + wxString rad = ODB::SymDouble2String( aPad.GetRoundRectCornerRadius() ); + + AddFeature( ODB::AddXY( center ), AddRoundRectSymbol( width, height, rad ), + aPad.GetOrientation(), mirror ); + + break; + } + case PAD_SHAPE::CHAMFERED_RECT: + { + int shorterSide = std::min( plotSize.x, plotSize.y ); + int chamfer = std::max( 0, KiROUND( aPad.GetChamferRectRatio() * shorterSide ) ); + wxString rad = ODB::SymDouble2String( chamfer ); + int positions = aPad.GetChamferPositions(); + + AddFeature( ODB::AddXY( center ), + AddChamferRectSymbol( width, height, rad, positions ), + aPad.GetOrientation(), mirror ); + + break; + } + case PAD_SHAPE::TRAPEZOID: + { + SHAPE_POLY_SET outline; + + aPad.TransformShapeToPolygon( outline, aLayer, 0, maxError, ERROR_INSIDE ); + + // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate() + // which can create bad shapes if margin.x is < 0 + + if( mask_clearance ) + { + outline.InflateWithLinkedHoles( expansion.x, CORNER_STRATEGY::ROUND_ALL_CORNERS, + maxError, SHAPE_POLY_SET::PM_FAST ); + } + + for( int ii = 0; ii < outline.OutlineCount(); ++ii ) + AddContour( outline, ii ); + + break; + } + case PAD_SHAPE::CUSTOM: + { + SHAPE_POLY_SET shape; + aPad.MergePrimitivesAsPolygon( &shape ); + + // as for custome shape, odb++ don't rotate the polygon, + // so we rotate the polygon in kicad anticlockwise + + shape.Rotate( aPad.GetOrientation() ); + shape.Move( center ); + + if( expansion != VECTOR2I( 0, 0 ) ) + { + shape.InflateWithLinkedHoles( std::max( expansion.x, expansion.y ), + CORNER_STRATEGY::ROUND_ALL_CORNERS, maxError, + SHAPE_POLY_SET::PM_FAST ); + } + + for( int ii = 0; ii < shape.OutlineCount(); ++ii ) + AddContour( shape, ii ); + + break; + } + default: wxLogError( "Unknown pad type" ); break; + } +} + + +void FEATURES_MANAGER::InitFeatureList( PCB_LAYER_ID aLayer, std::vector& aItems ) +{ + auto add_track = [&]( PCB_TRACK* track ) + { + auto iter = GetODBPlugin()->GetViaTraceSubnetMap().find( track ); + + if( iter == GetODBPlugin()->GetViaTraceSubnetMap().end() ) + { + wxLogError( _( "Failed to get subnet trace data" ) ); + return; + } + + auto subnet = iter->second; + + if( track->Type() == PCB_TRACE_T ) + { + PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT ); + shape.SetStart( track->GetStart() ); + shape.SetEnd( track->GetEnd() ); + shape.SetWidth( track->GetWidth() ); + + AddShape( shape ); + subnet->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::COPPER, m_layerName, + m_featuresList.size() - 1 ); + } + else if( track->Type() == PCB_ARC_T ) + { + PCB_ARC* arc = static_cast( track ); + PCB_SHAPE shape( nullptr, SHAPE_T::ARC ); + shape.SetArcGeometry( arc->GetStart(), arc->GetMid(), arc->GetEnd() ); + shape.SetWidth( arc->GetWidth() ); + + AddShape( shape ); + + subnet->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::COPPER, m_layerName, + m_featuresList.size() - 1 ); + } + else + { + // add via + PCB_VIA* via = static_cast( track ); + + if( aLayer != PCB_LAYER_ID::UNDEFINED_LAYER ) + { + // to draw via copper shape on copper layer + AddVia( via, aLayer ); + subnet->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::COPPER, m_layerName, + m_featuresList.size() - 1 ); + + if( !m_featuresList.empty() ) + { + AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::PAD_USAGE::VIA ); + AddFeatureAttribute( + *m_featuresList.back(), + ODB_ATTR::GEOMETRY{ "VIA_RoundD" + + std::to_string( via->GetWidth() ) } ); + } + } + else + { + // to draw via drill hole on drill layer + + if( m_layerName.Contains( "drill" ) ) + { + AddViaDrillHole( via, aLayer ); + subnet->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::HOLE, m_layerName, + m_featuresList.size() - 1 ); + + // TODO: confirm TOOLING_HOLE + // AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::PAD_USAGE::TOOLING_HOLE ); + + if( !m_featuresList.empty() ) + { + AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::DRILL::VIA ); + AddFeatureAttribute( + *m_featuresList.back(), + ODB_ATTR::GEOMETRY{ "VIA_RoundD" + + std::to_string( via->GetWidth() ) } ); + } + } + } + } + }; + + auto add_zone = [&]( ZONE* zone ) + { + SHAPE_POLY_SET zone_shape = zone->GetFilledPolysList( aLayer )->CloneDropTriangulation(); + + for( int ii = 0; ii < zone_shape.OutlineCount(); ++ii ) + { + AddContour( zone_shape, ii ); + + auto iter = GetODBPlugin()->GetPlaneSubnetMap().find( std::make_pair( aLayer, zone ) ); + + if( iter == GetODBPlugin()->GetPlaneSubnetMap().end() ) + { + wxLogError( _( "Failed to get subnet plane data" ) ); + return; + } + + iter->second->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::COPPER, m_layerName, + m_featuresList.size() - 1 ); + + if( zone->IsTeardropArea() && !m_featuresList.empty() ) + AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::TEAR_DROP{ true } ); + } + }; + + auto add_text = [&]( BOARD_ITEM* item ) + { + EDA_TEXT* text_item = nullptr; + + if( PCB_TEXT* tmp_text = dynamic_cast( item ) ) + text_item = static_cast( tmp_text ); + else if( PCB_TEXTBOX* tmp_text = dynamic_cast( item ) ) + text_item = static_cast( tmp_text ); + + if( !text_item->IsVisible() || text_item->GetShownText( false ).empty() ) + return; + + auto plot_text = [&]( const VECTOR2I& aPos, const wxString& aTextString, + const TEXT_ATTRIBUTES& aAttributes, KIFONT::FONT* aFont, + const KIFONT::METRICS& aFontMetrics ) + { + KIGFX::GAL_DISPLAY_OPTIONS empty_opts; + + TEXT_ATTRIBUTES attributes = aAttributes; + int penWidth = attributes.m_StrokeWidth; + + if( penWidth == 0 && attributes.m_Bold ) // Use default values if aPenWidth == 0 + penWidth = + GetPenSizeForBold( std::min( attributes.m_Size.x, attributes.m_Size.y ) ); + + if( penWidth < 0 ) + penWidth = -penWidth; + + attributes.m_StrokeWidth = penWidth; + + std::list pts; + + auto push_pts = [&]() + { + if( pts.size() < 2 ) + return; + + // Polylines are only allowed for more than 3 points. + // Otherwise, we have to use a line + + if( pts.size() < 3 ) + { + PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT ); + shape.SetStart( pts.front() ); + shape.SetEnd( pts.back() ); + shape.SetWidth( attributes.m_StrokeWidth ); + + AddShape( shape ); + AddFeatureAttribute( *m_featuresList.back(), + ODB_ATTR::STRING{ aTextString.ToStdString() } ); + } + else + { + for( auto it = pts.begin(); std::next( it ) != pts.end(); ++it ) + { + auto it2 = std::next( it ); + PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT ); + shape.SetStart( *it ); + shape.SetEnd( *it2 ); + shape.SetWidth( attributes.m_StrokeWidth ); + AddShape( shape ); + if( !m_featuresList.empty() ) + AddFeatureAttribute( *m_featuresList.back(), + ODB_ATTR::STRING{ aTextString.ToStdString() } ); + } + } + + pts.clear(); + }; + + CALLBACK_GAL callback_gal( + empty_opts, + // Stroke callback + [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 ) + { + if( !pts.empty() ) + { + if( aPt1 == pts.back() ) + pts.push_back( aPt2 ); + else if( aPt2 == pts.front() ) + pts.push_front( aPt1 ); + else if( aPt1 == pts.front() ) + pts.push_front( aPt2 ); + else if( aPt2 == pts.back() ) + pts.push_back( aPt1 ); + else + { + push_pts(); + pts.push_back( aPt1 ); + pts.push_back( aPt2 ); + } + } + else + { + pts.push_back( aPt1 ); + pts.push_back( aPt2 ); + } + }, + // Polygon callback + [&]( const SHAPE_LINE_CHAIN& aPoly ) + { + if( aPoly.PointCount() < 3 ) + return; + + SHAPE_POLY_SET poly_set; + poly_set.AddOutline( aPoly ); + + for( int ii = 0; ii < poly_set.OutlineCount(); ++ii ) + { + AddContour( poly_set, ii, FILL_T::FILLED_SHAPE ); + + if( !m_featuresList.empty() ) + AddFeatureAttribute( + *m_featuresList.back(), + ODB_ATTR::STRING{ aTextString.ToStdString() } ); + } + } ); + + aFont->Draw( &callback_gal, aTextString, aPos, aAttributes, aFontMetrics ); + + if( !pts.empty() ) + push_pts(); + }; + + bool isKnockout = false; + + if( item->Type() == PCB_TEXT_T || item->Type() == PCB_FIELD_T ) + isKnockout = static_cast( item )->IsKnockout(); + else if( item->Type() == PCB_TEXTBOX_T ) + isKnockout = static_cast( item )->IsKnockout(); + + const KIFONT::METRICS& fontMetrics = item->GetFontMetrics(); + + KIFONT::FONT* font = text_item->GetFont(); + + if( !font ) + { + wxString defaultFontName; // empty string is the KiCad stroke font + + font = KIFONT::FONT::GetFont( defaultFontName, text_item->IsBold(), + text_item->IsItalic() ); + } + + wxString shownText( text_item->GetShownText( true ) ); + + if( shownText.IsEmpty() ) + return; + + VECTOR2I pos = text_item->GetTextPos(); + + TEXT_ATTRIBUTES attrs = text_item->GetAttributes(); + attrs.m_StrokeWidth = text_item->GetEffectiveTextPenWidth(); + attrs.m_Angle = text_item->GetDrawRotation(); + attrs.m_Multiline = false; + + if( isKnockout ) + { + PCB_TEXT* text = static_cast( item ); + SHAPE_POLY_SET finalpolyset; + + text->TransformTextToPolySet( finalpolyset, 0, m_board->GetDesignSettings().m_MaxError, + ERROR_INSIDE ); + finalpolyset.Fracture( SHAPE_POLY_SET::PM_FAST ); + + for( int ii = 0; ii < finalpolyset.OutlineCount(); ++ii ) + { + AddContour( finalpolyset, ii, FILL_T::FILLED_SHAPE ); + + if( !m_featuresList.empty() ) + AddFeatureAttribute( *m_featuresList.back(), + ODB_ATTR::STRING{ shownText.ToStdString() } ); + } + } + else if( text_item->IsMultilineAllowed() ) + { + std::vector positions; + wxArrayString strings_list; + wxStringSplit( shownText, strings_list, '\n' ); + positions.reserve( strings_list.Count() ); + + text_item->GetLinePositions( positions, strings_list.Count() ); + + for( unsigned ii = 0; ii < strings_list.Count(); ii++ ) + { + wxString& txt = strings_list.Item( ii ); + plot_text( positions[ii], txt, attrs, font, fontMetrics ); + } + } + else + { + plot_text( pos, shownText, attrs, font, fontMetrics ); + } + }; + + + auto add_shape = [&]( PCB_SHAPE* shape ) + { + // FOOTPRINT* fp = shape->GetParentFootprint(); + AddShape( *shape, aLayer ); + }; + + auto add_pad = [&]( PAD* pad ) + { + auto iter = GetODBPlugin()->GetPadSubnetMap().find( pad ); + + if( iter == GetODBPlugin()->GetPadSubnetMap().end() ) + { + wxLogError( _( "Failed to get subnet top data" ) ); + return; + } + + if( aLayer != PCB_LAYER_ID::UNDEFINED_LAYER ) + { + // FOOTPRINT* fp = pad->GetParentFootprint(); + + AddPadShape( *pad, aLayer ); + + iter->second->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::COPPER, m_layerName, + m_featuresList.size() - 1 ); + if( !m_featuresList.empty() ) + AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::PAD_USAGE::TOEPRINT ); + + if( !pad->HasHole() && !m_featuresList.empty() ) + AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::SMD{ true } ); + } + else + { + // drill layer round hole or slot hole + if( m_layerName.Contains( "drill" ) ) + { + // here we exchange round hole or slot hole into pad to draw in drill layer + PAD dummy( *pad ); + + if( pad->GetDrillSizeX() == pad->GetDrillSizeY() ) + dummy.SetShape( PAD_SHAPE::CIRCLE ); // round hole shape + else + dummy.SetShape( PAD_SHAPE::OVAL ); // slot hole shape + + dummy.SetOffset( VECTOR2I( 0, 0 ) ); // use hole position not pad position + dummy.SetSize( pad->GetDrillSize() ); + + AddPadShape( dummy, aLayer ); + + if( pad->GetAttribute() == PAD_ATTRIB::PTH ) + { + // only plated holes link to subnet + iter->second->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::HOLE, m_layerName, + m_featuresList.size() - 1 ); + + if( !m_featuresList.empty() ) + AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::DRILL::PLATED ); + } + else + { + if( !m_featuresList.empty() ) + AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::DRILL::NON_PLATED ); + } + } + } + // AddFeatureAttribute( *m_featuresList.back(), + // ODB_ATTR::GEOMETRY{ "PAD_xxxx" } ); + }; + + for( BOARD_ITEM* item : aItems ) + { + switch( item->Type() ) + { + case PCB_TRACE_T: + case PCB_ARC_T: + case PCB_VIA_T: add_track( static_cast( item ) ); break; + + case PCB_ZONE_T: add_zone( static_cast( item ) ); break; + + case PCB_PAD_T: add_pad( static_cast( item ) ); break; + + case PCB_SHAPE_T: add_shape( static_cast( item ) ); break; + + case PCB_TEXT_T: + case PCB_FIELD_T: add_text( item ); break; + case PCB_TEXTBOX_T: + { + PCB_TEXTBOX* textbox = static_cast( item ); + add_text( item ); + + if( textbox->IsBorderEnabled() ) + add_shape( textbox ); + } + break; + + case PCB_DIMENSION_T: + case PCB_TARGET_T: + case PCB_DIM_ALIGNED_T: + case PCB_DIM_LEADER_T: + case PCB_DIM_CENTER_T: + case PCB_DIM_RADIAL_T: + case PCB_DIM_ORTHOGONAL_T: + //TODO: Add support for dimensions + break; + + default: break; + } + } +} + + +void FEATURES_MANAGER::AddVia( const PCB_VIA* aVia, PCB_LAYER_ID aLayer ) +{ + if( !aVia->FlashLayer( aLayer ) ) + return; + + PAD dummy( nullptr ); // default pad shape is circle + int hole = aVia->GetDrillValue(); + dummy.SetDrillSize( VECTOR2I( hole, hole ) ); + dummy.SetPosition( aVia->GetStart() ); + dummy.SetSize( VECTOR2I( aVia->GetWidth(), aVia->GetWidth() ) ); + + AddPadShape( dummy, aLayer ); +} + + +void FEATURES_MANAGER::AddViaDrillHole( const PCB_VIA* aVia, PCB_LAYER_ID aLayer ) +{ + PAD dummy( nullptr ); // default pad shape is circle + int hole = aVia->GetDrillValue(); + dummy.SetPosition( aVia->GetStart() ); + dummy.SetSize( VECTOR2I( hole, hole ) ); + + AddPadShape( dummy, aLayer ); +} + + +void FEATURES_MANAGER::GenerateProfileFeatures( std::ostream& ost ) const +{ + ost << "UNITS=" << PCB_IO_ODBPP::m_unitsStr << std::endl; + ost << "#\n#Num Features\n#" << std::endl; + ost << "F " << m_featuresList.size() << std::endl; + + if( m_featuresList.empty() ) + return; + + ost << "#\n#Layer features\n#" << std::endl; + + for( const auto& feat : m_featuresList ) + { + feat->WriteFeatures( ost ); + } +} + + +void FEATURES_MANAGER::GenerateFeatureFile( std::ostream& ost ) const +{ + ost << "UNITS=" << PCB_IO_ODBPP::m_unitsStr << std::endl; + ost << "#\n#Num Features\n#" << std::endl; + ost << "F " << m_featuresList.size() << std::endl << std::endl; + + if( m_featuresList.empty() ) + return; + + ost << "#\n#Feature symbol names\n#" << std::endl; + + for( const auto& [n, name] : m_allSymMap ) + { + ost << "$" << n << " " << name << std::endl; + } + + WriteAttributes( ost ); + + ost << "#\n#Layer features\n#" << std::endl; + + for( const auto& feat : m_featuresList ) + { + feat->WriteFeatures( ost ); + } +} + + +void ODB_FEATURE::WriteFeatures( std::ostream& ost ) +{ + switch( GetFeatureType() ) + { + case FEATURE_TYPE::LINE: ost << "L "; break; + + case FEATURE_TYPE::ARC: ost << "A "; break; + + case FEATURE_TYPE::PAD: ost << "P "; break; + + case FEATURE_TYPE::SURFACE: ost << "S "; break; + default: return; + } + + WriteRecordContent( ost ); + ost << std::endl; +} + + +void ODB_LINE::WriteRecordContent( std::ostream& ost ) +{ + ost << m_start.first << " " << m_start.second << " " << m_end.first << " " << m_end.second + << " " << m_symIndex << " P 0"; + + WriteAttributes( ost ); +} + + +void ODB_ARC::WriteRecordContent( std::ostream& ost ) +{ + ost << m_start.first << " " << m_start.second << " " << m_end.first << " " << m_end.second + << " " << m_center.first << " " << m_center.second << " " << m_symIndex << " P 0 " + << ( m_direction == ODB_DIRECTION::CW ? "Y" : "N" ); + + WriteAttributes( ost ); +} + + +void ODB_PAD::WriteRecordContent( std::ostream& ost ) +{ + ost << m_center.first << " " << m_center.second << " "; + + // TODO: support resize symbol + // ost << "-1" << " " << m_symIndex << " " + // << m_resize << " P 0 "; + + ost << m_symIndex << " P 0 "; + + if( m_mirror ) + ost << "9 " << ODB::Double2String( m_angle.Normalize().AsDegrees() ); + else + ost << "8 " << ODB::Double2String( ( ANGLE_360 - m_angle ).Normalize().AsDegrees() ); + + WriteAttributes( ost ); +} + + +ODB_SURFACE::ODB_SURFACE( uint32_t aIndex, const SHAPE_POLY_SET::POLYGON& aPolygon, + FILL_T aFillType /*= FILL_T::FILLED_SHAPE*/ ) : ODB_FEATURE( aIndex ) +{ + if( !aPolygon.empty() && aPolygon[0].PointCount() >= 3 ) + { + m_surfaces = std::make_unique( aPolygon ); + if( aFillType != FILL_T::NO_FILL ) + { + m_surfaces->AddPolygonHoles( aPolygon ); + } + } + else + { + delete this; + } +} + + +void ODB_SURFACE::WriteRecordContent( std::ostream& ost ) +{ + ost << "P 0"; + WriteAttributes( ost ); + ost << std::endl; + m_surfaces->WriteData( ost ); + ost << "SE"; +} + + +ODB_SURFACE_DATA::ODB_SURFACE_DATA( const SHAPE_POLY_SET::POLYGON& aPolygon ) +{ + const std::vector& pts = aPolygon[0].CPoints(); + if( !pts.empty() ) + { + if( m_polygons.empty() ) + { + m_polygons.resize( 1 ); + } + + m_polygons.at( 0 ).reserve( pts.size() ); + m_polygons.at( 0 ).emplace_back( pts.back() ); + + for( size_t jj = 0; jj < pts.size(); ++jj ) + { + m_polygons.at( 0 ).emplace_back( pts.at( jj ) ); + } + } +} + + +void ODB_SURFACE_DATA::AddPolygonHoles( const SHAPE_POLY_SET::POLYGON& aPolygon ) +{ + for( size_t ii = 1; ii < aPolygon.size(); ++ii ) + { + wxCHECK2( aPolygon[ii].PointCount() >= 3, continue ); + + const std::vector& hole = aPolygon[ii].CPoints(); + + if( hole.empty() ) + continue; + + if( m_polygons.size() <= ii ) + { + m_polygons.resize( ii + 1 ); + + m_polygons[ii].reserve( hole.size() ); + } + + m_polygons.at( ii ).emplace_back( hole.back() ); + + for( size_t jj = 0; jj < hole.size(); ++jj ) + { + m_polygons.at( ii ).emplace_back( hole[jj] ); + } + } +} + + +void ODB_SURFACE_DATA::WriteData( std::ostream& ost ) const +{ + ODB::CHECK_ONCE is_island; + + for( const auto& contour : m_polygons ) + { + if( contour.empty() ) + continue; + + ost << "OB " << ODB::AddXY( contour.back().m_end ).first << " " + << ODB::AddXY( contour.back().m_end ).second << " "; + + if( is_island() ) + ost << "I"; + else + ost << "H"; + ost << std::endl; + + for( const auto& line : contour ) + { + if( SURFACE_LINE::LINE_TYPE::SEGMENT == line.m_type ) + ost << "OS " << ODB::AddXY( line.m_end ).first << " " + << ODB::AddXY( line.m_end ).second << std::endl; + else + ost << "OC " << ODB::AddXY( line.m_end ).first << " " + << ODB::AddXY( line.m_end ).second << " " << ODB::AddXY( line.m_center ).first + << " " << ODB::AddXY( line.m_center ).second << " " + << ( line.m_direction == ODB_DIRECTION::CW ? "Y" : "N" ) << std::endl; + } + ost << "OE" << std::endl; + } +} diff --git a/pcbnew/pcb_io/odbpp/odb_feature.h b/pcbnew/pcb_io/odbpp/odb_feature.h new file mode 100644 index 0000000000..1ef6b57c0e --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_feature.h @@ -0,0 +1,349 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + + +#ifndef _ODB_FEATURE_H_ +#define _ODB_FEATURE_H_ + +#include "odb_attribute.h" +#include "pad.h" +#include "convert_basic_shapes_to_polygon.h" +#include "footprint.h" +#include +#include "math/vector2d.h" +#include "odb_defines.h" + + +enum class ODB_DIRECTION +{ + CW, + CCW +}; + + +class ODB_LINE; +class ODB_ARC; +class ODB_PAD; +class ODB_SURFACE; +class ODB_FEATURE; +class PCB_IO_ODBPP; +class PCB_VIA; + +class FEATURES_MANAGER : public ATTR_MANAGER +{ +public: + FEATURES_MANAGER( BOARD* aBoard, PCB_IO_ODBPP* aPlugin, const wxString& aLayerName ) : + m_board( aBoard ), m_plugin( aPlugin ), m_layerName( aLayerName ) + { + } + + virtual ~FEATURES_MANAGER() { m_featuresList.clear(); } + + void InitFeatureList( PCB_LAYER_ID aLayer, std::vector& aItems ); + + void AddFeatureLine( const VECTOR2I& aStart, const VECTOR2I& aEnd, uint64_t aWidth ); + + void AddFeatureArc( const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& aCenter, + uint64_t aWidth, ODB_DIRECTION aDirection ); + + void AddPadCircle( const VECTOR2I& aCenter, uint64_t aDiameter, const EDA_ANGLE& aAngle, + bool aMirror, double aResize = 1.0 ); + + void AddPadShape( const PAD& aPad, PCB_LAYER_ID aLayer ); + + void AddFeatureSurface( const SHAPE_POLY_SET::POLYGON& aPolygon, + FILL_T aFillType = FILL_T::FILLED_SHAPE ); + + void AddShape( const PCB_SHAPE& aShape, PCB_LAYER_ID aLayer = UNDEFINED_LAYER ); + + void AddVia( const PCB_VIA* aVia, PCB_LAYER_ID aLayer ); + + void AddViaDrillHole( const PCB_VIA* aVia, PCB_LAYER_ID aLayer ); + + bool AddContour( const SHAPE_POLY_SET& aPolySet, int aOutline = 0, + FILL_T aFillType = FILL_T::FILLED_SHAPE ); + + bool AddPolygon( const SHAPE_POLY_SET::POLYGON& aPolygon, FILL_T aFillType, int aWidth, + LINE_STYLE aDashType ); + + bool AddPolygonCutouts( const SHAPE_POLY_SET::POLYGON& aPolygon ); + + void GenerateFeatureFile( std::ostream& ost ) const; + + void GenerateProfileFeatures( std::ostream& ost ) const; + +private: + inline uint32_t AddCircleSymbol( const wxString& aDiameter ) + { + return GetSymbolIndex( m_circleSymMap, "r" + aDiameter ); + } + + uint32_t AddRoundDonutSymbol( const wxString& aOuterDim, const wxString& aInnerDim ) + { + wxString sym = "donut_r" + aOuterDim + ODB_DIM_X + aInnerDim; + return GetSymbolIndex( m_roundDonutSymMap, sym ); + } + + uint32_t AddRectSymbol( const wxString& aWidth, const wxString& aHeight ) + { + wxString sym = "rect" + aWidth + ODB_DIM_X + aHeight; + return GetSymbolIndex( m_rectSymMap, sym ); + } + + uint32_t AddOvalSymbol( const wxString& aWidth, const wxString& aHeight ) + { + wxString sym = "oval" + aWidth + ODB_DIM_X + aHeight; + return GetSymbolIndex( m_ovalSymMap, sym ); + } + + uint32_t AddRoundRectSymbol( const wxString& aWidth, const wxString& aHeight, + const wxString& aRadius ) + { + wxString sym = "rect" + aWidth + ODB_DIM_X + aHeight + ODB_DIM_X + ODB_DIM_R + aRadius; + return GetSymbolIndex( m_roundRectSymMap, sym ); + } + + uint32_t AddRoundRectDonutSymbol( const wxString& aOuterWidth, const wxString& aOuterHeight, + const wxString& aLineWidth, const wxString& aRadius ) + { + wxString sym = "donut_rc" + aOuterWidth + ODB_DIM_X + aOuterHeight + ODB_DIM_X + aLineWidth + + ODB_DIM_X + ODB_DIM_R + aRadius; + return GetSymbolIndex( m_roundRectDonutSymMap, sym ); + } + + uint32_t AddChamferRectSymbol( const wxString& aWidth, const wxString& aHeight, + const wxString& aRadius, int aPositions ) + { + wxString sym = "rect" + aWidth + ODB_DIM_X + aHeight + ODB_DIM_X + ODB_DIM_C + aRadius; + + if( aPositions != RECT_CHAMFER_ALL ) + { + sym += ODB_DIM_X; + if( aPositions & RECT_CHAMFER_TOP_RIGHT ) + sym += "1"; + if( aPositions & RECT_CHAMFER_TOP_LEFT ) + sym += "2"; + if( aPositions & RECT_CHAMFER_BOTTOM_LEFT ) + sym += "3"; + if( aPositions & RECT_CHAMFER_BOTTOM_RIGHT ) + sym += "4"; + } + + return GetSymbolIndex( m_chamRectSymMap, sym ); + } + + + uint32_t GetSymbolIndex( std::map& aSymMap, const wxString& aKey ) + { + if( aSymMap.count( aKey ) ) + { + return aSymMap.at( aKey ); + } + else + { + uint32_t index = m_symIndex; + m_symIndex++; + aSymMap.emplace( aKey, index ); + m_allSymMap.emplace( index, aKey ); + return index; + } + } + + std::map m_circleSymMap; // diameter -> symbol index + std::map m_roundDonutSymMap; + std::map m_padSymMap; // name -> symbol index + std::map m_rectSymMap; // w,h -> symbol index + std::map m_ovalSymMap; // w,h -> symbol index + std::map m_roundRectSymMap; + std::map m_roundRectDonutSymMap; + std::map m_chamRectSymMap; + + std::map m_allSymMap; + + template + void AddFeature( Args&&... args ) + { + auto feature = std::make_unique( m_featuresList.size(), std::forward( args )... ); + + m_featuresList.emplace_back( std::move( feature ) ); + } + + inline PCB_IO_ODBPP* GetODBPlugin() { return m_plugin; } + + BOARD* m_board; + PCB_IO_ODBPP* m_plugin; + wxString m_layerName; + uint32_t m_symIndex = 0; + + std::list> m_featuresList; + std::map> m_featureIDMap; +}; + + +class ODB_FEATURE : public ATTR_RECORD_WRITER +{ +public: + // template using check_type = attribute::is_feature; + ODB_FEATURE( uint32_t aIndex ) : m_index( aIndex ) {} + virtual void WriteFeatures( std::ostream& ost ); + + virtual ~ODB_FEATURE() = default; + +protected: + enum class FEATURE_TYPE + { + LINE, + ARC, + PAD, + SURFACE + }; + + virtual FEATURE_TYPE GetFeatureType() = 0; + + virtual void WriteRecordContent( std::ostream& ost ) = 0; + + const uint32_t m_index; +}; + +class ODB_LINE : public ODB_FEATURE +{ +public: + ODB_LINE( uint32_t aIndex, const std::pair& aStart, + const std::pair& aEnd, uint32_t aSym ) : + ODB_FEATURE( aIndex ), m_start( aStart ), m_end( aEnd ), m_symIndex( aSym ) + { + } + + inline virtual FEATURE_TYPE GetFeatureType() override { return FEATURE_TYPE::LINE; } + +protected: + virtual void WriteRecordContent( std::ostream& ost ) override; + +private: + std::pair m_start; + std::pair m_end; + uint32_t m_symIndex; +}; + + +class ODB_ARC : public ODB_FEATURE +{ +public: + inline virtual FEATURE_TYPE GetFeatureType() override { return FEATURE_TYPE::ARC; } + + + ODB_ARC( uint32_t aIndex, const std::pair& aStart, + const std::pair& aEnd, + const std::pair& aCenter, uint32_t aSym, + ODB_DIRECTION aDirection ) : + ODB_FEATURE( aIndex ), m_start( aStart ), m_end( aEnd ), m_center( aCenter ), + m_symIndex( aSym ), m_direction( aDirection ) + { + } + +protected: + virtual void WriteRecordContent( std::ostream& ost ) override; + +private: + std::pair m_start; + std::pair m_end; + std::pair m_center; + uint32_t m_symIndex; + ODB_DIRECTION m_direction; +}; + +class ODB_PAD : public ODB_FEATURE +{ +public: + ODB_PAD( uint32_t aIndex, const std::pair& aCenter, uint32_t aSym, + EDA_ANGLE aAngle = ANGLE_0, bool aMirror = false, double aResize = 1.0 ) : + ODB_FEATURE( aIndex ), m_center( aCenter ), m_symIndex( aSym ), m_angle( aAngle ), + m_mirror( aMirror ), m_resize( aResize ) + { + } + + inline virtual FEATURE_TYPE GetFeatureType() override { return FEATURE_TYPE::PAD; } + +protected: + virtual void WriteRecordContent( std::ostream& ost ) override; + +private: + std::pair m_center; + uint32_t m_symIndex; + EDA_ANGLE m_angle; + bool m_mirror; + double m_resize; +}; + +class ODB_SURFACE_DATA; +class ODB_SURFACE : public ODB_FEATURE +{ +public: + ODB_SURFACE( uint32_t aIndex, const SHAPE_POLY_SET::POLYGON& aPolygon, + FILL_T aFillType = FILL_T::FILLED_SHAPE ); + + virtual ~ODB_SURFACE() = default; + + inline virtual FEATURE_TYPE GetFeatureType() override { return FEATURE_TYPE::SURFACE; } + + std::unique_ptr m_surfaces; + +protected: + virtual void WriteRecordContent( std::ostream& ost ) override; +}; + + +class ODB_SURFACE_DATA +{ +public: + ODB_SURFACE_DATA( const SHAPE_POLY_SET::POLYGON& aPolygon ); + + struct SURFACE_LINE + { + enum class LINE_TYPE + { + SEGMENT, + ARC + }; + SURFACE_LINE() = default; + + SURFACE_LINE( const VECTOR2I& aEnd ) : m_end( aEnd ) {} + + SURFACE_LINE( const VECTOR2I& aEnd, const VECTOR2I& aCenter, ODB_DIRECTION aDirection ) : + m_end( aEnd ), m_type( LINE_TYPE::ARC ), m_center( aCenter ), + m_direction( aDirection ) + { + } + + VECTOR2I m_end; + LINE_TYPE m_type = LINE_TYPE::SEGMENT; + + VECTOR2I m_center; + ODB_DIRECTION m_direction; + }; + + void AddPolygonHoles( const SHAPE_POLY_SET::POLYGON& aPolygon ); + void WriteData( std::ostream& ost ) const; + + std::vector> m_polygons; +}; + + +#endif // _ODB_FEATURE_H_ diff --git a/pcbnew/pcb_io/odbpp/odb_fonts.cpp b/pcbnew/pcb_io/odbpp/odb_fonts.cpp new file mode 100644 index 0000000000..323c7e0f75 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_fonts.cpp @@ -0,0 +1,671 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + +#include +#include "odb_entity.h" + +const std::string odb_fonts1 = R"( +XSIZE 0.302000 +YSIZE 0.302000 +OFFSET 0.000000 +CHAR ! +LINE 0.000000 0.000000 0.000000 0.200000 P R 0.012000 +LINE 0.000000 -0.100000 0.000000 -0.100000 P R 0.012000 +ECHAR +CHAR " +LINE -0.050000 0.100000 -0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.100000 0.050000 0.200000 P R 0.012000 +ECHAR +CHAR # +LINE -0.050000 -0.100000 -0.050000 0.200000 P R 0.012000 +LINE 0.050000 -0.100000 0.050000 0.200000 P R 0.012000 +LINE -0.100000 0.000000 0.100000 0.000000 P R 0.012000 +LINE -0.100000 0.100000 0.100000 0.100000 P R 0.012000 +ECHAR +CHAR $ +LINE 0.000000 -0.100000 0.000000 0.200000 P R 0.012000 +LINE -0.100000 -0.050000 0.050000 -0.050000 P R 0.012000 +LINE 0.050000 -0.050000 0.100000 0.000000 P R 0.012000 +LINE 0.100000 0.000000 0.050000 0.050000 P R 0.012000 +LINE 0.050000 0.050000 -0.050000 0.050000 P R 0.012000 +LINE -0.050000 0.050000 -0.100000 0.100000 P R 0.012000 +LINE -0.100000 0.100000 -0.050000 0.150000 P R 0.012000 +LINE -0.050000 0.150000 0.100000 0.150000 P R 0.012000 +ECHAR +CHAR % +LINE -0.100000 -0.100000 0.100000 0.200000 P R 0.012000 +LINE -0.075000 0.175000 -0.075000 0.175000 P R 0.012000 +LINE 0.075000 -0.075000 0.075000 -0.075000 P R 0.012000 +ECHAR +CHAR & +LINE 0.100000 -0.100000 -0.100000 0.100000 P R 0.012000 +LINE -0.100000 0.100000 -0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.150000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 0.000000 0.150000 P R 0.012000 +LINE 0.000000 0.150000 0.000000 0.100000 P R 0.012000 +LINE 0.000000 0.100000 -0.100000 0.000000 P R 0.012000 +LINE -0.100000 0.000000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 0.000000 -0.100000 P R 0.012000 +LINE 0.000000 -0.100000 0.100000 0.000000 P R 0.012000 +ECHAR +CHAR ' +LINE 0.000000 0.050000 0.050000 0.150000 P R 0.012000 +LINE 0.025000 0.175000 0.025000 0.175000 P R 0.012000 +ECHAR +CHAR ( +LINE 0.050000 -0.100000 -0.050000 0.000000 P R 0.012000 +LINE -0.050000 0.000000 -0.050000 0.100000 P R 0.012000 +LINE -0.050000 0.100000 0.050000 0.200000 P R 0.012000 +ECHAR +CHAR ) +LINE -0.050000 -0.100000 0.050000 0.000000 P R 0.012000 +LINE 0.050000 0.000000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 -0.050000 0.200000 P R 0.012000 +ECHAR +CHAR * +LINE -0.100000 -0.050000 0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.150000 0.100000 -0.050000 P R 0.012000 +LINE 0.000000 -0.100000 0.000000 0.200000 P R 0.012000 +ECHAR +CHAR + +LINE -0.100000 0.050000 0.100000 0.050000 P R 0.012000 +LINE 0.000000 -0.050000 0.000000 0.150000 P R 0.012000 +ECHAR +CHAR , +LINE 0.000000 -0.100000 0.050000 0.000000 P R 0.012000 +LINE 0.025000 0.025000 0.025000 0.025000 P R 0.012000 +ECHAR +CHAR - +LINE -0.100000 0.050000 0.100000 0.050000 P R 0.012000 +ECHAR +CHAR . +LINE 0.025000 -0.075000 0.025000 -0.075000 P R 0.012000 +ECHAR +CHAR / +LINE -0.100000 -0.050000 0.100000 0.150000 P R 0.012000 +ECHAR +CHAR 0 +LINE -0.100000 -0.050000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 -0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.150000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 0.100000 0.150000 P R 0.012000 +ECHAR +CHAR 1 +LINE -0.050000 0.150000 0.000000 0.200000 P R 0.012000 +LINE 0.000000 0.200000 0.000000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 0.050000 -0.100000 P R 0.012000 +ECHAR +CHAR 2 +LINE -0.100000 0.150000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.100000 0.100000 P R 0.012000 +LINE 0.100000 0.100000 0.050000 0.050000 P R 0.012000 +LINE 0.050000 0.050000 0.000000 0.050000 P R 0.012000 +LINE 0.000000 0.050000 -0.100000 -0.100000 P R 0.012000 +LINE -0.100000 -0.100000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR 3 +LINE -0.100000 0.200000 0.100000 0.200000 P R 0.012000 +LINE 0.100000 0.200000 0.100000 0.100000 P R 0.012000 +LINE 0.100000 0.100000 0.050000 0.050000 P R 0.012000 +LINE 0.050000 0.050000 0.000000 0.050000 P R 0.012000 +LINE 0.050000 0.050000 0.100000 0.000000 P R 0.012000 +LINE 0.100000 0.000000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +ECHAR +CHAR 4 +LINE 0.050000 -0.100000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 -0.100000 0.000000 P R 0.012000 +LINE -0.100000 0.000000 0.100000 0.000000 P R 0.012000 +ECHAR +CHAR 5 +LINE 0.100000 0.200000 -0.100000 0.200000 P R 0.012000 +LINE -0.100000 0.200000 -0.100000 0.100000 P R 0.012000 +LINE -0.100000 0.100000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +ECHAR +CHAR 6 +LINE 0.100000 0.200000 0.000000 0.200000 P R 0.012000 +LINE 0.000000 0.200000 -0.100000 0.100000 P R 0.012000 +LINE -0.100000 0.100000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.100000 0.000000 P R 0.012000 +LINE 0.100000 0.000000 0.050000 0.050000 P R 0.012000 +LINE 0.050000 0.050000 -0.100000 0.050000 P R 0.012000 +ECHAR +CHAR 7 +LINE -0.100000 0.200000 0.100000 0.200000 P R 0.012000 +LINE 0.100000 0.200000 -0.050000 -0.100000 P R 0.012000 +ECHAR +CHAR 8 +LINE -0.050000 0.200000 0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.050000 0.050000 0.050000 P R 0.012000 +LINE -0.050000 -0.100000 0.050000 -0.100000 P R 0.012000 +LINE -0.100000 0.150000 -0.100000 0.100000 P R 0.012000 +LINE 0.100000 0.150000 0.100000 0.100000 P R 0.012000 +LINE -0.100000 0.000000 -0.100000 -0.050000 P R 0.012000 +LINE 0.100000 0.000000 0.100000 -0.050000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE -0.050000 0.200000 -0.100000 0.150000 P R 0.012000 +LINE 0.050000 0.050000 0.100000 0.100000 P R 0.012000 +LINE -0.050000 0.050000 -0.100000 0.100000 P R 0.012000 +LINE 0.050000 0.050000 0.100000 0.000000 P R 0.012000 +LINE -0.050000 0.050000 -0.100000 0.000000 P R 0.012000 +LINE 0.050000 -0.100000 0.100000 -0.050000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +ECHAR +CHAR 9 +LINE -0.100000 -0.100000 0.000000 -0.100000 P R 0.012000 +LINE 0.000000 -0.100000 0.100000 0.000000 P R 0.012000 +LINE 0.100000 0.000000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 -0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.150000 -0.100000 0.100000 P R 0.012000 +LINE -0.100000 0.100000 -0.050000 0.050000 P R 0.012000 +LINE -0.050000 0.050000 0.100000 0.050000 P R 0.012000 +ECHAR +CHAR : +LINE 0.000000 0.012000 0.000000 0.012000 P R 0.012000 +LINE 0.000000 -0.025000 0.000000 -0.025000 P R 0.012000 +ECHAR +CHAR ; +LINE 0.000000 0.025000 -0.050000 -0.100000 P R 0.012000 +LINE 0.000000 0.025000 0.000000 0.025000 P R 0.012000 +LINE 0.000000 0.175000 0.000000 0.175000 P R 0.012000 +ECHAR +CHAR < +LINE 0.100000 0.200000 -0.050000 0.050000 P R 0.012000 +LINE -0.050000 0.050000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR = +LINE -0.100000 0.100000 0.100000 0.100000 P R 0.012000 +LINE -0.100000 0.000000 0.100000 0.000000 P R 0.012000 +ECHAR +CHAR > +LINE -0.100000 0.200000 0.050000 0.050000 P R 0.012000 +LINE 0.050000 0.050000 -0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR ? +LINE -0.100000 0.150000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.000000 0.050000 P R 0.012000 +LINE 0.000000 0.050000 0.000000 0.000000 P R 0.012000 +LINE 0.000000 -0.100000 0.000000 -0.100000 P R 0.012000 +ECHAR +CHAR @ +LINE 0.050000 -0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 -0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.150000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.100000 0.000000 P R 0.012000 +LINE 0.100000 0.000000 0.000000 0.000000 P R 0.012000 +LINE 0.000000 0.000000 0.000000 0.100000 P R 0.012000 +LINE 0.000000 0.100000 0.100000 0.100000 P R 0.012000 +ECHAR +CHAR A +LINE -0.100000 -0.100000 -0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.150000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.100000 -0.100000 P R 0.012000 +LINE -0.100000 0.050000 0.100000 0.050000 P R 0.012000 +ECHAR +CHAR B +LINE -0.100000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.100000 0.100000 P R 0.012000 +LINE 0.100000 0.100000 0.050000 0.050000 P R 0.012000 +LINE 0.050000 0.050000 0.100000 0.000000 P R 0.012000 +LINE 0.100000 0.000000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 -0.100000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.050000 0.050000 0.050000 P R 0.012000 +ECHAR +CHAR C +LINE 0.100000 -0.050000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 -0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.150000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +ECHAR +CHAR D +LINE -0.100000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 -0.100000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.050000 0.200000 P R 0.012000 +ECHAR +CHAR E +LINE -0.100000 0.200000 0.100000 0.200000 P R 0.012000 +LINE -0.100000 -0.100000 0.100000 -0.100000 P R 0.012000 +LINE -0.100000 0.050000 0.050000 0.050000 P R 0.012000 +LINE -0.100000 0.200000 -0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR F +LINE -0.100000 0.200000 0.100000 0.200000 P R 0.012000 +LINE -0.100000 0.050000 0.050000 0.050000 P R 0.012000 +LINE -0.100000 0.200000 -0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR G +LINE -0.050000 0.200000 0.100000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 -0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.150000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 0.100000 -0.100000 P R 0.012000 +LINE 0.100000 -0.100000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.050000 0.050000 P R 0.012000 +ECHAR +CHAR H +LINE -0.100000 0.200000 -0.100000 -0.100000 P R 0.012000 +LINE 0.100000 0.200000 0.100000 -0.100000 P R 0.012000 +LINE -0.100000 0.050000 0.100000 0.050000 P R 0.012000 +ECHAR +CHAR I +LINE -0.050000 0.200000 0.050000 0.200000 P R 0.012000 +LINE -0.050000 -0.100000 0.050000 -0.100000 P R 0.012000 +LINE 0.000000 0.200000 0.000000 -0.100000 P R 0.012000 +ECHAR +CHAR J +LINE 0.100000 0.200000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +ECHAR +CHAR K +LINE -0.100000 0.200000 -0.100000 -0.100000 P R 0.012000 +LINE 0.100000 0.200000 -0.100000 0.050000 P R 0.012000 +LINE -0.100000 0.050000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR L +LINE -0.100000 0.200000 -0.100000 -0.100000 P R 0.012000 +LINE -0.100000 -0.100000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR M +LINE -0.100000 -0.100000 -0.100000 0.200000 P R 0.012000 +LINE -0.100000 0.200000 0.000000 0.050000 P R 0.012000 +LINE 0.000000 0.050000 0.100000 0.200000 P R 0.012000 +LINE 0.100000 0.200000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR N +LINE -0.100000 -0.100000 -0.100000 0.200000 P R 0.012000 +LINE -0.100000 0.200000 0.100000 -0.100000 P R 0.012000 +LINE 0.100000 -0.100000 0.100000 0.200000 P R 0.012000 +ECHAR +CHAR O +LINE -0.100000 -0.050000 -0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.150000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +ECHAR +CHAR P +LINE -0.100000 -0.100000 -0.100000 0.200000 P R 0.012000 +LINE -0.100000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.100000 0.100000 P R 0.012000 +LINE 0.100000 0.100000 0.050000 0.050000 P R 0.012000 +LINE 0.050000 0.050000 -0.100000 0.050000 P R 0.012000 +ECHAR +CHAR Q +LINE -0.100000 -0.050000 -0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.150000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.100000 0.000000 P R 0.012000 +LINE 0.100000 0.000000 0.000000 -0.100000 P R 0.012000 +LINE 0.000000 -0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +LINE 0.000000 0.000000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR R +LINE -0.100000 -0.100000 -0.100000 0.200000 P R 0.012000 +LINE -0.100000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE 0.100000 0.150000 0.100000 0.100000 P R 0.012000 +LINE 0.100000 0.100000 0.050000 0.050000 P R 0.012000 +LINE 0.050000 0.050000 -0.100000 0.050000 P R 0.012000 +LINE -0.050000 0.050000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR S +LINE -0.100000 -0.050000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.100000 0.000000 P R 0.012000 +LINE 0.100000 0.000000 0.050000 0.050000 P R 0.012000 +LINE 0.050000 0.050000 -0.050000 0.050000 P R 0.012000 +LINE -0.050000 0.050000 -0.100000 0.100000 P R 0.012000 +LINE -0.100000 0.100000 -0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.150000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +ECHAR +CHAR T +LINE -0.100000 0.200000 0.100000 0.200000 P R 0.012000 +LINE 0.000000 0.200000 0.000000 -0.100000 P R 0.012000 +ECHAR +CHAR U +LINE -0.100000 0.200000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.100000 0.200000 P R 0.012000 +ECHAR +CHAR V +LINE -0.100000 0.200000 0.000000 -0.100000 P R 0.012000 +LINE 0.000000 -0.100000 0.100000 0.200000 P R 0.012000 +ECHAR +CHAR W +LINE -0.100000 0.200000 -0.100000 -0.100000 P R 0.012000 +LINE -0.100000 -0.100000 0.000000 0.050000 P R 0.012000 +LINE 0.000000 0.050000 0.100000 -0.100000 P R 0.012000 +LINE 0.100000 -0.100000 0.100000 0.200000 P R 0.012000 +ECHAR +CHAR X +LINE -0.100000 0.200000 0.100000 -0.100000 P R 0.012000 +LINE -0.100000 -0.100000 0.100000 0.200000 P R 0.012000 +ECHAR +CHAR Y +LINE -0.100000 0.200000 0.000000 0.050000 P R 0.012000 +LINE 0.000000 0.050000 0.100000 0.200000 P R 0.012000 +LINE 0.000000 0.050000 0.000000 -0.100000 P R 0.012000 +ECHAR +CHAR Z +LINE -0.100000 0.200000 0.100000 0.200000 P R 0.012000 +LINE 0.100000 0.200000 -0.100000 -0.100000 P R 0.012000 +LINE -0.100000 -0.100000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR [ +LINE 0.050000 0.200000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 0.050000 -0.100000 P R 0.012000 +ECHAR)"; + +const std::string odb_fonts2 = R"( +CHAR \ +LINE -0.100000 0.200000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR ] +LINE -0.050000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 -0.050000 -0.100000 P R 0.012000 +ECHAR +CHAR ^ +LINE -0.100000 0.100000 0.000000 0.200000 P R 0.012000 +LINE 0.000000 0.200000 0.100000 0.100000 P R 0.012000 +ECHAR +CHAR _ +LINE -0.100000 -0.100000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR ` +LINE -0.050000 0.200000 0.000000 0.050000 P R 0.012000 +ECHAR +CHAR a +LINE -0.050000 0.100000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.100000 -0.100000 P R 0.012000 +LINE 0.100000 -0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 -0.050000 0.000000 P R 0.012000 +LINE -0.050000 0.000000 0.100000 0.000000 P R 0.012000 +ECHAR +CHAR b +LINE -0.100000 0.200000 -0.100000 -0.100000 P R 0.012000 +LINE -0.100000 -0.100000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 0.000000 0.100000 P R 0.012000 +LINE 0.000000 0.100000 -0.100000 0.050000 P R 0.012000 +ECHAR +CHAR c +LINE 0.100000 0.100000 -0.050000 0.100000 P R 0.012000 +LINE -0.050000 0.100000 -0.100000 0.050000 P R 0.012000 +LINE -0.100000 0.050000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR d +LINE 0.100000 0.200000 0.100000 -0.100000 P R 0.012000 +LINE 0.100000 -0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 -0.100000 0.050000 P R 0.012000 +LINE -0.100000 0.050000 -0.050000 0.100000 P R 0.012000 +LINE -0.050000 0.100000 0.000000 0.100000 P R 0.012000 +LINE 0.000000 0.100000 0.100000 0.050000 P R 0.012000 +ECHAR +CHAR e +LINE 0.050000 -0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 -0.100000 0.050000 P R 0.012000 +LINE -0.100000 0.050000 -0.050000 0.100000 P R 0.012000 +LINE -0.050000 0.100000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.050000 0.000000 P R 0.012000 +LINE 0.050000 0.000000 -0.100000 0.000000 P R 0.012000 +ECHAR +CHAR f +LINE -0.050000 -0.100000 -0.050000 0.150000 P R 0.012000 +LINE -0.050000 0.150000 0.000000 0.200000 P R 0.012000 +LINE 0.000000 0.200000 0.050000 0.200000 P R 0.012000 +LINE 0.050000 0.200000 0.100000 0.150000 P R 0.012000 +LINE -0.100000 0.050000 0.000000 0.050000 P R 0.012000 +ECHAR +CHAR g +LINE -0.050000 -0.150000 0.050000 -0.150000 P R 0.012000 +LINE 0.050000 -0.150000 0.100000 -0.100000 P R 0.012000 +LINE 0.100000 -0.100000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 -0.050000 0.100000 P R 0.012000 +LINE -0.050000 0.100000 -0.100000 0.050000 P R 0.012000 +LINE -0.100000 0.050000 -0.100000 0.000000 P R 0.012000 +LINE -0.100000 0.000000 -0.050000 -0.050000 P R 0.012000 +LINE -0.050000 -0.050000 0.100000 -0.050000 P R 0.012000 +ECHAR +CHAR h +LINE -0.100000 0.200000 -0.100000 -0.100000 P R 0.012000 +LINE -0.100000 0.050000 0.000000 0.100000 P R 0.012000 +LINE 0.000000 0.100000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR i +LINE 0.000000 0.100000 0.000000 -0.050000 P R 0.012000 +LINE 0.000000 -0.050000 0.050000 -0.100000 P R 0.012000 +LINE 0.000000 0.200000 0.000000 0.200000 P R 0.012000 +ECHAR +CHAR j +LINE 0.050000 0.100000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 0.000000 -0.150000 P R 0.012000 +LINE 0.000000 -0.150000 -0.050000 -0.150000 P R 0.012000 +LINE -0.050000 -0.150000 -0.100000 -0.100000 P R 0.012000 +LINE 0.050000 0.200000 0.050000 0.200000 P R 0.012000 +ECHAR +CHAR k +LINE -0.100000 0.200000 -0.100000 -0.100000 P R 0.012000 +LINE -0.100000 0.000000 0.000000 0.000000 P R 0.012000 +LINE 0.000000 0.000000 0.100000 -0.100000 P R 0.012000 +LINE -0.050000 0.000000 0.050000 0.100000 P R 0.012000 +ECHAR +CHAR l +LINE -0.050000 0.200000 0.000000 0.200000 P R 0.012000 +LINE 0.000000 0.200000 0.000000 -0.050000 P R 0.012000 +LINE 0.000000 -0.050000 0.050000 -0.100000 P R 0.012000 +ECHAR +CHAR m +LINE -0.100000 -0.100000 -0.100000 0.050000 P R 0.012000 +LINE -0.100000 0.050000 -0.050000 0.100000 P R 0.012000 +LINE -0.050000 0.100000 0.000000 0.050000 P R 0.012000 +LINE 0.000000 0.050000 0.000000 0.000000 P R 0.012000 +LINE 0.000000 0.050000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR n +LINE -0.100000 0.100000 -0.100000 -0.100000 P R 0.012000 +LINE -0.100000 0.000000 0.000000 0.100000 P R 0.012000 +LINE 0.000000 0.100000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR o +LINE -0.100000 -0.050000 -0.100000 0.050000 P R 0.012000 +LINE -0.100000 0.050000 -0.050000 0.100000 P R 0.012000 +LINE -0.050000 0.100000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 -0.100000 -0.050000 P R 0.012000 +ECHAR +CHAR p +LINE -0.100000 -0.150000 -0.100000 0.050000 P R 0.012000 +LINE -0.100000 0.050000 -0.050000 0.100000 P R 0.012000 +LINE -0.050000 0.100000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.100000 0.000000 P R 0.012000 +LINE 0.100000 0.000000 0.050000 -0.050000 P R 0.012000 +LINE 0.050000 -0.050000 -0.100000 -0.050000 P R 0.012000 +ECHAR +CHAR q +LINE 0.100000 -0.150000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 -0.050000 0.100000 P R 0.012000 +LINE -0.050000 0.100000 -0.100000 0.050000 P R 0.012000 +LINE -0.100000 0.050000 -0.100000 0.000000 P R 0.012000 +LINE -0.100000 0.000000 -0.050000 -0.050000 P R 0.012000 +LINE -0.050000 -0.050000 0.100000 -0.050000 P R 0.012000 +ECHAR +CHAR r +LINE -0.100000 0.100000 -0.100000 -0.100000 P R 0.012000 +LINE -0.100000 0.000000 0.000000 0.100000 P R 0.012000 +LINE 0.000000 0.100000 0.050000 0.050000 P R 0.012000 +ECHAR +CHAR s +LINE -0.100000 -0.100000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 -0.050000 0.050000 0.000000 P R 0.012000 +LINE 0.050000 0.000000 -0.050000 0.000000 P R 0.012000 +LINE -0.050000 0.000000 -0.100000 0.050000 P R 0.012000 +LINE -0.100000 0.050000 -0.050000 0.100000 P R 0.012000 +LINE -0.050000 0.100000 0.100000 0.100000 P R 0.012000 +ECHAR +CHAR t +LINE -0.050000 0.200000 -0.050000 -0.050000 P R 0.012000 +LINE -0.050000 -0.050000 0.000000 -0.100000 P R 0.012000 +LINE 0.000000 -0.100000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 0.100000 -0.050000 P R 0.012000 +LINE -0.100000 0.100000 0.050000 0.100000 P R 0.012000 +ECHAR +CHAR u +LINE -0.100000 0.100000 -0.100000 -0.050000 P R 0.012000 +LINE -0.100000 -0.050000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 0.000000 -0.100000 P R 0.012000 +LINE 0.000000 -0.100000 0.100000 0.000000 P R 0.012000 +LINE 0.100000 -0.100000 0.100000 0.100000 P R 0.012000 +ECHAR +CHAR v +LINE -0.100000 0.100000 0.000000 -0.100000 P R 0.012000 +LINE 0.000000 -0.100000 0.100000 0.100000 P R 0.012000 +ECHAR +CHAR w +LINE -0.100000 0.100000 -0.050000 -0.100000 P R 0.012000 +LINE -0.050000 -0.100000 0.000000 0.000000 P R 0.012000 +LINE 0.000000 0.000000 0.050000 -0.100000 P R 0.012000 +LINE 0.050000 -0.100000 0.100000 0.100000 P R 0.012000 +ECHAR +CHAR x +LINE -0.100000 0.100000 0.100000 -0.100000 P R 0.012000 +LINE -0.100000 -0.100000 0.100000 0.100000 P R 0.012000 +ECHAR +CHAR y +LINE -0.100000 0.100000 -0.100000 0.000000 P R 0.012000 +LINE -0.100000 0.000000 -0.050000 -0.050000 P R 0.012000 +LINE -0.050000 -0.050000 0.100000 -0.050000 P R 0.012000 +LINE 0.100000 0.100000 0.100000 -0.100000 P R 0.012000 +LINE 0.100000 -0.100000 0.050000 -0.150000 P R 0.012000 +LINE 0.050000 -0.150000 -0.050000 -0.150000 P R 0.012000 +ECHAR +CHAR z +LINE -0.100000 0.100000 0.100000 0.100000 P R 0.012000 +LINE 0.100000 0.100000 -0.100000 -0.100000 P R 0.012000 +LINE -0.100000 -0.100000 0.100000 -0.100000 P R 0.012000 +ECHAR +CHAR { +LINE 0.050000 0.200000 0.000000 0.200000 P R 0.012000 +LINE 0.000000 0.200000 -0.050000 0.150000 P R 0.012000 +LINE -0.050000 0.150000 -0.050000 0.100000 P R 0.012000 +LINE -0.050000 0.100000 -0.100000 0.050000 P R 0.012000 +LINE -0.100000 0.050000 -0.050000 0.000000 P R 0.012000 +LINE -0.050000 0.000000 -0.050000 -0.050000 P R 0.012000 +LINE -0.050000 -0.050000 0.000000 -0.100000 P R 0.012000 +LINE 0.000000 -0.100000 0.050000 -0.100000 P R 0.012000 +ECHAR +CHAR | +LINE 0.000000 0.200000 0.000000 -0.100000 P R 0.012000 +ECHAR +CHAR } +LINE -0.050000 0.200000 0.000000 0.200000 P R 0.012000 +LINE 0.000000 0.200000 0.050000 0.150000 P R 0.012000 +LINE 0.050000 0.150000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 0.100000 0.050000 P R 0.012000 +LINE 0.100000 0.050000 0.050000 0.000000 P R 0.012000 +LINE 0.050000 0.000000 0.050000 -0.050000 P R 0.012000 +LINE 0.050000 -0.050000 0.000000 -0.100000 P R 0.012000 +LINE 0.000000 -0.100000 -0.050000 -0.100000 P R 0.012000 +ECHAR +CHAR ~ +LINE -0.100000 0.150000 -0.050000 0.200000 P R 0.012000 +LINE -0.050000 0.200000 0.050000 0.100000 P R 0.012000 +LINE 0.050000 0.100000 0.100000 0.150000 P R 0.012000 +ECHAR)"; + +// just implementation here +void ODB_FONTS_ENTITY::GenerateFiles( ODB_TREE_WRITER& writer ) +{ + auto fileproxy = writer.CreateFileProxy( "standard" ); + + fileproxy.GetStream() << odb_fonts1 << odb_fonts2 << std::endl; +} diff --git a/pcbnew/pcb_io/odbpp/odb_netlist.cpp b/pcbnew/pcb_io/odbpp/odb_netlist.cpp new file mode 100644 index 0000000000..de5acfc7c7 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_netlist.cpp @@ -0,0 +1,285 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "odb_util.h" + + +// Compute the side code for a pad. Returns "" if there is no copper +std::string ODB_NET_LIST::ComputePadAccessSide( BOARD* aBoard, LSET aLayerMask ) +{ + // Non-copper is not interesting here + aLayerMask &= LSET::AllCuMask(); + if( !aLayerMask.any() ) + return ""; + + // Traditional TH pad + if( aLayerMask[F_Cu] && aLayerMask[B_Cu] ) + return "B"; + + // Front SMD pad + if( aLayerMask[F_Cu] ) + return "T"; + + // Back SMD pad + if( aLayerMask[B_Cu] ) + return "D"; + + // Inner + for( int layer = In1_Cu; layer < B_Cu; ++layer ) + { + if( aLayerMask[layer] ) + return "I"; + } + + // This shouldn't happen + wxLogDebug( "Unhandled layer mask input when compute pad access side of ODB++ netlist file." ); + return ""; +} + + +void ODB_NET_LIST::InitPadNetPoints( BOARD* aBoard, + std::map>& aRecords ) +{ + VECTOR2I origin = aBoard->GetDesignSettings().GetAuxOrigin(); + + for( FOOTPRINT* footprint : aBoard->Footprints() ) + { + for( PAD* pad : footprint->Pads() ) + { + ODB_NET_RECORD net_point; + net_point.side = ComputePadAccessSide( aBoard, pad->GetLayerSet() ); + + // It could be a mask only pad, we only handle pads with copper here + if( !net_point.side.empty() && net_point.side != "I" ) + { + if( pad->GetNetCode() == 0 ) + net_point.netname = "$NONE$"; + else + net_point.netname = pad->GetNetname(); + // net_point.pin = pad->GetNumber(); + net_point.refdes = footprint->GetReference(); + const VECTOR2I& drill = pad->GetDrillSize(); + net_point.hole = pad->HasHole(); + + if( !net_point.hole ) + net_point.drill_radius = 0; + else + net_point.drill_radius = std::min( drill.x, drill.y ); + + net_point.smd = pad->GetAttribute() == PAD_ATTRIB::SMD + || pad->GetAttribute() == PAD_ATTRIB::CONN; + net_point.is_via = false; + net_point.mechanical = ( pad->GetAttribute() == PAD_ATTRIB::NPTH ); + net_point.x_location = pad->GetPosition().x - origin.x; + net_point.y_location = origin.y - pad->GetPosition().y; + net_point.x_size = pad->GetSize().x; + + // Rule: round pads have y = 0 + if( pad->GetShape() == PAD_SHAPE::CIRCLE ) + net_point.y_size = net_point.x_size; + else + net_point.y_size = pad->GetSize().y; + + // net_point.rotation = ( ANGLE_360 - pad->GetOrientation() ).Normalize().AsDegrees(); + + // if( net_point.rotation < 0 ) + // net_point.rotation += 360; + + // always output NET end point as net test point + net_point.epoint = "e"; + + // the value indicates which sides are *not* accessible + net_point.soldermask = 3; + + if( pad->GetLayerSet()[F_Mask] ) + net_point.soldermask &= ~1; + + if( pad->GetLayerSet()[B_Mask] ) + net_point.soldermask &= ~2; + + aRecords[pad->GetNetCode()].push_back( net_point ); + } + } + } +} + +// Compute the side code for a via. +std::string ODB_NET_LIST::ComputeViaAccessSide( BOARD* aBoard, int top_layer, int bottom_layer ) +{ + // Easy case for through vias: top_layer is component, bottom_layer is + // solder, side code is Both + if( ( top_layer == F_Cu ) && ( bottom_layer == B_Cu ) ) + return "B"; + + // Blind via, reachable from front, Top + if( top_layer == F_Cu ) + return "T"; + + // Blind via, reachable from bottom, Down + if( bottom_layer == B_Cu ) + return "D"; + + // It's a buried via, accessible from some inner layer, Inner + return "I"; +} + + +void ODB_NET_LIST::InitViaNetPoints( BOARD* aBoard, + std::map>& aRecords ) +{ + VECTOR2I origin = aBoard->GetDesignSettings().GetAuxOrigin(); + + // Enumerate all the track segments and keep the vias + for( auto track : aBoard->Tracks() ) + { + if( track->Type() == PCB_VIA_T ) + { + PCB_VIA* via = static_cast( track ); + PCB_LAYER_ID top_layer, bottom_layer; + + via->LayerPair( &top_layer, &bottom_layer ); + + ODB_NET_RECORD net_point; + net_point.side = ComputeViaAccessSide( aBoard, top_layer, bottom_layer ); + + if( net_point.side != "I" ) + { + NETINFO_ITEM* net = track->GetNet(); + net_point.smd = false; + net_point.hole = true; + + if( net->GetNetCode() == 0 ) + net_point.netname = "$NONE$"; + else + net_point.netname = net->GetNetname(); + + net_point.refdes = "VIA"; + net_point.is_via = true; + net_point.drill_radius = via->GetDrillValue(); + net_point.mechanical = false; + net_point.x_location = via->GetPosition().x - origin.x; + net_point.y_location = origin.y - via->GetPosition().y; + + // via always has drill radius, Width and Height are 0 + net_point.x_size = 0; + net_point.y_size = 0; // Round so height = 0 + net_point.epoint = "e"; // only buried via is "m" net mid point + + // the value indicates which sides are *not* accessible + net_point.soldermask = 3; + + if( via->GetLayerSet()[F_Mask] ) + net_point.soldermask &= ~1; + + if( via->GetLayerSet()[B_Mask] ) + net_point.soldermask &= ~2; + + aRecords[net->GetNetCode()].push_back( net_point ); + } + } + } +} + + +void ODB_NET_LIST::WriteNetPointRecords( std::map>& aRecords, + std::ostream& aStream ) +{ + aStream << "H optimize n staggered n" << std::endl; + + for( const auto& [key, vec] : aRecords ) + { + aStream << "$" << key << " " << ODB::GenLegalNetName( vec.front().netname ) << std::endl; + } + + aStream << "#" << std::endl << "#Netlist points" << std::endl << "#" << std::endl; + + for( const auto& [key, vec] : aRecords ) + { + for( const auto& net_point : vec ) + { + aStream << key << " "; + + if( net_point.hole ) + aStream << ODB::Data2String( net_point.drill_radius ); + else + aStream << 0; + + aStream << " " << ODB::Data2String( net_point.x_location ) << " " + << ODB::Data2String( net_point.y_location ) << " " << net_point.side << " "; + + if( !net_point.hole ) + aStream << ODB::Data2String( net_point.x_size ) << " " + << ODB::Data2String( net_point.y_size ) << " "; + + std::string exp; + + if( net_point.soldermask == 3 ) + exp = "c"; + else if( net_point.soldermask == 2 ) + exp = "s"; + else if( net_point.soldermask == 1 ) + exp = "p"; + else if( net_point.soldermask == 0 ) + exp = "e"; + + aStream << net_point.epoint << " " << exp; + + if( net_point.hole ) + aStream << " staggered 0 0 0"; + + if( net_point.is_via ) + aStream << " v"; + + aStream << std::endl; + } + } +} + + +void ODB_NET_LIST::Write( std::ostream& aStream ) +{ + std::map> net_point_records; + + InitViaNetPoints( m_board, net_point_records ); + + InitPadNetPoints( m_board, net_point_records ); + + WriteNetPointRecords( net_point_records, aStream ); +} diff --git a/pcbnew/pcb_io/odbpp/odb_netlist.h b/pcbnew/pcb_io/odbpp/odb_netlist.h new file mode 100644 index 0000000000..d4de2d6f37 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_netlist.h @@ -0,0 +1,77 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + +#ifndef _ODB_NETLIST_H_ +#define _ODB_NETLIST_H_ + +#include + +// Structure for holding the ODB net point record. +struct ODB_NET_RECORD +{ + bool smd; + bool hole; + bool is_via; + wxString netname; + std::string refdes; + int drill_radius; + bool mechanical; + std::string side; // B: Both, T: Top, D: Down + + // All these in PCB units, will be output in decimils + int x_location; + int y_location; + // Width and height of non-drilled pads (only when radius = 0). + int x_size; // Width + int y_size; // Height + // int rotation; + + std::string epoint; // e: net end point, m: net mid point + + int soldermask; // !< e — Solder mask exposed point. soldermask = 0 + // !< c — Solder mask covered point. = 3 + // !< p — Solder mask covered point on top side of product model. = 1 + // !< s — Solder mask covered point on bottom side of product model. = 2 +}; + + +class BOARD; +class ODB_NET_LIST +{ +public: + ODB_NET_LIST( BOARD* aBoard ) : m_board( aBoard ) {} + + virtual ~ODB_NET_LIST() {} + + void Write( std::ostream& aStream ); + +private: + BOARD* m_board; + std::string ComputePadAccessSide( BOARD* aBoard, LSET aLayerMask ); + std::string ComputeViaAccessSide( BOARD* aBoard, int top_layer, int bottom_layer ); + + void InitPadNetPoints( BOARD* aBoard, std::map>& aRecords ); + void InitViaNetPoints( BOARD* aBoard, std::map>& aRecords ); + /// Writes a list of records to the given output stream + void WriteNetPointRecords( std::map>& aRecords, + std::ostream& aStream ); +}; + +#endif // _ODB_NETLIST_H_ \ No newline at end of file diff --git a/pcbnew/pcb_io/odbpp/odb_util.cpp b/pcbnew/pcb_io/odbpp/odb_util.cpp new file mode 100644 index 0000000000..b848914cdd --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_util.cpp @@ -0,0 +1,352 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + +#include +#include +#include +#include "odb_util.h" +#include +#include +#include "idf_helpers.h" +#include "odb_defines.h" +#include "pcb_io_odbpp.h" + +namespace ODB +{ + +wxString GenODBString( const wxString& aStr ) +{ + wxString str; + + for( size_t ii = 0; ii < aStr.Len(); ++ii ) + { + // Rule: we can only use the standard ASCII, control excluded + wxUniChar ch = aStr[ii]; + + if( ch > 126 || !std::isgraph( static_cast( ch ) ) ) + ch = '?'; + + str += ch; + } + + // Rule: only uppercase + str.MakeUpper(); + + return str; +} + + +wxString GenLegalNetName( const wxString& aStr ) +{ + std::string str = aStr.ToStdString(); + wxString out; + out.reserve( str.size() ); + + for( auto c : str ) + { + if( ( c >= 33 && c <= 126 ) && c != ';' ) + { + out.append( 1, c ); + } + else + { + out.append( 1, '_' ); // Replace invalid characters with underscore + } + } + + return out; +} + + +// The names of these ODB++ entities must comply with +// the rules for legal entity names: +// product, model, step, layer, symbol, and attribute. +wxString GenLegalEntityName( const wxString& aStr ) +{ + std::string str = aStr.ToStdString(); + wxString out; + out.reserve( str.size() ); + + for( auto c : str ) + { + if( isalpha( c ) ) + c = tolower( c ); + else if( isdigit( c ) || c == '-' || c == '_' || c == '+' || c == '.' ) + ; + else + c = '_'; + + out.append( 1, c ); + } + + if( out.length() > 64 ) + { + out.Truncate( 64 ); + } + + while( !out.IsEmpty() && ( out[0] == '.' || out[0] == '-' || out[0] == '+' ) ) + { + out.erase( 0, 1 ); + } + + while( !out.IsEmpty() && out.Last() == '.' ) + { + out.RemoveLast(); + } + + return out; +} + + +wxString Double2String( double aVal ) +{ + // We don't want to output -0.0 as this value is just 0 for fabs + if( aVal == -0.0 ) + aVal = 0.0; + + wxString str = wxString::FromCDouble( aVal, PCB_IO_ODBPP::m_sigfig ); + + // Remove all but the last trailing zeros from str + while( str.EndsWith( wxT( "00" ) ) ) + str.RemoveLast(); + + return str; +} + + +std::string Double2String( double aVal, int32_t aDigits ) +{ + // We don't want to output -0.0 as this value is just 0 for fabs + if( aVal == -0.0 ) + aVal = 0.0; + + wxString str = wxString::FromCDouble( aVal, aDigits ); + + return str.ToStdString(); +} + + +wxString SymDouble2String( double aVal ) +{ + return Double2String( PCB_IO_ODBPP::m_symbolScale * aVal ); +} + + +wxString Data2String( double aVal ) +{ + return Double2String( PCB_IO_ODBPP::m_scale * aVal ); +} + + +std::pair AddXY( const VECTOR2I& aVec ) +{ + // TODO: to deal with user preference x y increment setting + std::pair xy = + std::pair( Double2String( PCB_IO_ODBPP::m_scale * aVec.x ), + Double2String( -PCB_IO_ODBPP::m_scale * aVec.y ) ); + + return xy; +} + + +VECTOR2I GetShapePosition( const PCB_SHAPE& aShape ) +{ + VECTOR2D pos{}; + + switch( aShape.GetShape() ) + { + // Rectangles in KiCad are mapped by their corner while ODBPP uses the center + case SHAPE_T::RECTANGLE: + pos = aShape.GetPosition() + + VECTOR2I( aShape.GetRectangleWidth() / 2.0, aShape.GetRectangleHeight() / 2.0 ); + break; + // Both KiCad and ODBPP use the center of the circle + case SHAPE_T::CIRCLE: + // KiCad uses the exact points on the board + case SHAPE_T::POLY: + case SHAPE_T::BEZIER: + case SHAPE_T::SEGMENT: + case SHAPE_T::ARC: pos = aShape.GetPosition(); break; + } + + return pos; +} +} // namespace ODB + + +void ODB_TREE_WRITER::CreateEntityDirectory( const wxString& aPareDir, + const wxString& aSubDir /*= wxEmptyString*/ ) +{ + wxFileName path = wxFileName::DirName( aPareDir ); + + wxArrayString subDirs = wxFileName::DirName( aSubDir.Lower() ).GetDirs(); + + for( size_t i = 0; i < subDirs.GetCount(); i++ ) + path.AppendDir( subDirs[i] ); + + if( !path.DirExists() ) + { + if( !path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) ) + { + throw( std::runtime_error( "Could not create directory" + path.GetPath() ) ); + } + } + + m_currentPath = path.GetPath(); +} + + +ODB_FILE_WRITER::ODB_FILE_WRITER( ODB_TREE_WRITER& aTreeWriter, const wxString& aFileName ) : + m_treeWriter( aTreeWriter ) +{ + CreateFile( aFileName ); +} + + +void ODB_FILE_WRITER::CreateFile( const wxString& aFileName ) +{ + if( aFileName.IsEmpty() || m_treeWriter.GetCurrentPath().IsEmpty() ) + return; + + wxFileName fn; + fn.SetPath( m_treeWriter.GetCurrentPath() ); + fn.SetFullName( aFileName ); + + wxString dirPath = fn.GetPath(); + + if( !wxDir::Exists( dirPath ) ) + { + if( !wxDir::Make( dirPath ) ) + throw( std::runtime_error( "Could not create directory" + dirPath ) ); + } + + if( !fn.IsDirWritable() || ( fn.Exists() && !fn.IsFileWritable() ) ) + return; + + if( m_ostream.is_open() ) + m_ostream.close(); + + m_ostream.open( TO_UTF8( fn.GetFullPath() ), + std::ios_base::out | std::ios_base::trunc | std::ios_base::binary ); + + m_ostream.imbue( std::locale::classic() ); + + if( !m_ostream.is_open() || !m_ostream.good() ) + throw std::runtime_error( "Failed to open file: " + fn.GetFullPath() ); +} + + +bool ODB_FILE_WRITER::CloseFile() +{ + if( m_ostream.is_open() ) + { + m_ostream.close(); + + if( !m_ostream.good() ) + { + throw std::runtime_error( "close file failed" ); + return false; + } + } + + return true; +} + + +void ODB_TEXT_WRITER::WriteEquationLine( const std::string& var, int value ) +{ + WriteIndent(); + m_ostream << var << "=" << value << std::endl; +} + + +void ODB_TEXT_WRITER::WriteEquationLine( const wxString& var, const wxString& value ) +{ + WriteIndent(); + m_ostream << var << "=" << value << std::endl; +} + + +void ODB_TEXT_WRITER::WriteIndent() +{ + if( in_array ) + m_ostream << " "; +} + + +void ODB_TEXT_WRITER::BeginArray( const std::string& a ) +{ + if( in_array ) + throw std::runtime_error( "already in array" ); + in_array = true; + m_ostream << a << " {" << std::endl; +} + + +void ODB_TEXT_WRITER::EndArray() +{ + if( !in_array ) + throw std::runtime_error( "not in array" ); + in_array = false; + m_ostream << "}" << std::endl << std::endl; +} + + +ODB_TEXT_WRITER::ARRAY_PROXY::ARRAY_PROXY( ODB_TEXT_WRITER& aWriter, const std::string& aStr ) : + m_writer( aWriter ) +{ + m_writer.BeginArray( aStr ); +} + + +ODB_TEXT_WRITER::ARRAY_PROXY::~ARRAY_PROXY() +{ + m_writer.EndArray(); +} + + +ODB_DRILL_TOOLS::ODB_DRILL_TOOLS( const wxString& aUnits, const wxString& aThickness, + const wxString& aUserParams ) : + m_units( aUnits ), m_thickness( aThickness ), m_userParams( aUserParams ) +{ +} + + +void ODB_DRILL_TOOLS::GenerateFile( std::ostream& aStream ) +{ + ODB_TEXT_WRITER twriter( aStream ); + + twriter.WriteEquationLine( "UNITS", m_units ); + twriter.WriteEquationLine( "THICKNESS", m_thickness ); + twriter.WriteEquationLine( "USER_PARAMS", m_userParams ); + + for( const auto& tool : m_tools ) + { + const auto array_proxy = twriter.MakeArrayProxy( "TOOLS" ); + twriter.WriteEquationLine( "NUM", tool.m_num ); + twriter.WriteEquationLine( "TYPE", tool.m_type ); + twriter.WriteEquationLine( "TYPE2", tool.m_type2 ); + twriter.WriteEquationLine( "MIN_TOL", tool.m_minTol ); + twriter.WriteEquationLine( "MAX_TOL", tool.m_maxTol ); + twriter.WriteEquationLine( "BIT", tool.m_bit ); + twriter.WriteEquationLine( "FINISH_SIZE", tool.m_finishSize ); + twriter.WriteEquationLine( "DRILL_SIZE", tool.m_drillSize ); + } +} diff --git a/pcbnew/pcb_io/odbpp/odb_util.h b/pcbnew/pcb_io/odbpp/odb_util.h new file mode 100644 index 0000000000..9ce0e8387e --- /dev/null +++ b/pcbnew/pcb_io/odbpp/odb_util.h @@ -0,0 +1,355 @@ +#ifndef _ODB_UTIL_H_ +#define _ODB_UTIL_H_ + +#include +#include +#include +#include +#include +#include +#include "pcb_shape.h" +#include + + +enum class ODB_POLARITY +{ + POSITIVE, + NEGATIVE +}; + +enum class ODB_CONTEXT +{ + BOARD, + MISC +}; + +enum class ODB_DIELECTRIC_TYPE +{ + NONE, + PREPREG, + CORE +}; + +enum class ODB_TYPE +{ + UNDEFINED, + + SIGNAL, + POWER_GROUND, + DIELECTRIC, + MIXED, + SOLDER_MASK, + SOLDER_PASTE, + SILK_SCREEN, + DRILL, + ROUT, + DOCUMENT, + COMPONENT, + MASK, + CONDUCTIVE_PASTE, +}; + +enum class ODB_SUBTYPE +{ + COVERLAY, + COVERCOAT, + STIFFENER, + BEND_AREA, + FLEX_AREA, + RIGID_AREA, + PSA, + SILVER_MASK, + CARBON_MASK, +}; + +enum class ODB_FID_TYPE +{ + COPPER, + LAMINATE, + HOLE +}; + + +namespace ODB +{ +wxString GenODBString( const wxString& aStr ); + +wxString GenLegalNetName( const wxString& aStr ); + +wxString GenLegalEntityName( const wxString& aStr ); + +wxString Double2String( double aVal ); + +std::string Double2String( double aVal, int32_t aDigits ); + +wxString Data2String( double aVal ); + +wxString SymDouble2String( double aVal ); + +std::pair AddXY( const VECTOR2I& aVec ); + +VECTOR2I GetShapePosition( const PCB_SHAPE& aShape ); + +template +class EnumStringMap +{ +public: + static std::map& GetMap() + { + static_assert( std::is_enum_v, "Template parameter T must be an enum type" ); + + static std::map map = []() + { + std::map result; + + if constexpr( std::is_same_v ) + { + result[ODB_POLARITY::POSITIVE] = "POSITIVE"; + result[ODB_POLARITY::NEGATIVE] = "NEGATIVE"; + } + + if constexpr( std::is_same_v ) + { + result[ODB_CONTEXT::BOARD] = "BOARD"; + result[ODB_CONTEXT::MISC] = "MISC"; + } + + if constexpr( std::is_same_v ) + { + //just for logical reasons.TYPE field must be defined. + result[ODB_TYPE::UNDEFINED] = ""; + + result[ODB_TYPE::SIGNAL] = "SIGNAL"; + result[ODB_TYPE::POWER_GROUND] = "POWER_GROUND"; + result[ODB_TYPE::DIELECTRIC] = "DIELECTRIC"; + result[ODB_TYPE::MIXED] = "MIXED"; + result[ODB_TYPE::SOLDER_MASK] = "SOLDER_MASK"; + result[ODB_TYPE::SOLDER_PASTE] = "SOLDER_PASTE"; + result[ODB_TYPE::SILK_SCREEN] = "SILK_SCREEN"; + result[ODB_TYPE::DRILL] = "DRILL"; + result[ODB_TYPE::ROUT] = "ROUT"; + result[ODB_TYPE::DOCUMENT] = "DOCUMENT"; + result[ODB_TYPE::COMPONENT] = "COMPONENT"; + result[ODB_TYPE::MASK] = "MASK"; + result[ODB_TYPE::CONDUCTIVE_PASTE] = "CONDUCTIVE_PASTE"; + } + + if constexpr( std::is_same_v ) + { + result[ODB_SUBTYPE::COVERLAY] = "COVERLAY"; + result[ODB_SUBTYPE::COVERCOAT] = "COVERCOAT"; + result[ODB_SUBTYPE::STIFFENER] = "STIFFENER"; + result[ODB_SUBTYPE::BEND_AREA] = "BEND_AREA"; + result[ODB_SUBTYPE::FLEX_AREA] = "FLEX_AREA"; + result[ODB_SUBTYPE::RIGID_AREA] = "RIGID_AREA"; + result[ODB_SUBTYPE::PSA] = "PSA"; + result[ODB_SUBTYPE::SILVER_MASK] = "SILVER_MASK"; + result[ODB_SUBTYPE::CARBON_MASK] = "CARBON_MASK"; + } + + if constexpr( std::is_same_v ) + { + result[ODB_DIELECTRIC_TYPE::NONE] = "NONE"; + result[ODB_DIELECTRIC_TYPE::PREPREG] = "PREPREG"; + result[ODB_DIELECTRIC_TYPE::CORE] = "CORE"; + } + + if constexpr( std::is_same_v ) + { + result[ODB_FID_TYPE::COPPER] = "C"; + result[ODB_FID_TYPE::LAMINATE] = "L"; + result[ODB_FID_TYPE::HOLE] = "H"; + } + + return result; + }(); + + return map; + } +}; + +template +std::string Enum2String( T value ) +{ + const auto& map = EnumStringMap::GetMap(); + auto it = map.find( value ); + if( it != map.end() ) + { + return it->second; + } + else + { + throw std::out_of_range( "Enum value not found in map" ); + } +} + +class CHECK_ONCE +{ +public: + bool operator()() + { + if( first ) + { + first = false; + return true; + } + return false; + } + +private: + bool first = true; +}; + +} // namespace ODB + +class ODB_TREE_WRITER; +class ODB_FILE_WRITER +{ +public: + ODB_FILE_WRITER( ODB_TREE_WRITER& aTreeWriter, const wxString& aFileName ); + + virtual ~ODB_FILE_WRITER() { CloseFile(); } + + ODB_FILE_WRITER( ODB_FILE_WRITER&& ) = delete; + ODB_FILE_WRITER& operator=( ODB_FILE_WRITER&& ) = delete; + + ODB_FILE_WRITER( ODB_FILE_WRITER const& ) = delete; + ODB_FILE_WRITER& operator=( ODB_FILE_WRITER const& ) = delete; + + void CreateFile( const wxString& aFileName ); + bool CloseFile(); + inline std::ostream& GetStream() { return m_ostream; } + +private: + ODB_TREE_WRITER& m_treeWriter; + std::ofstream m_ostream; +}; + + +class ODB_TREE_WRITER +{ +public: + ODB_TREE_WRITER( const wxString& aDir ) : m_currentPath( aDir ) {} + + ODB_TREE_WRITER( const wxString& aPareDir, const wxString& aSubDir ) + { + CreateEntityDirectory( aPareDir, aSubDir ); + } + + virtual ~ODB_TREE_WRITER() {} + + [[nodiscard]] ODB_FILE_WRITER CreateFileProxy( const wxString& aFileName ) + { + return ODB_FILE_WRITER( *this, aFileName ); + } + + void CreateEntityDirectory( const wxString& aPareDir, const wxString& aSubDir = wxEmptyString ); + + inline const wxString GetCurrentPath() const { return m_currentPath; } + + inline void SetCurrentPath( const wxString& aDir ) { m_currentPath = aDir; } + + inline void SetRootPath( const wxString& aDir ) { m_rootPath = aDir; } + + inline const wxString GetRootPath() const { return m_rootPath; } + + +private: + wxString m_currentPath; + wxString m_rootPath; +}; + + +class ODB_TEXT_WRITER +{ +public: + ODB_TEXT_WRITER( std::ostream& aStream ) : m_ostream( aStream ) {} + virtual ~ODB_TEXT_WRITER() {} + + // void WriteEquationLine( const std::string &var, const std::string &value ); + void WriteEquationLine( const std::string& var, int value ); + void WriteEquationLine( const wxString& var, const wxString& value ); + template + void write_line_enum( const std::string& var, const T& value ) + { + WriteEquationLine( var, ODB::Enum2String( value ) ); + } + + class ARRAY_PROXY + { + friend ODB_TEXT_WRITER; + + public: + ~ARRAY_PROXY(); + + private: + ARRAY_PROXY( ODB_TEXT_WRITER& aWriter, const std::string& aStr ); + + ODB_TEXT_WRITER& m_writer; + + ARRAY_PROXY( ARRAY_PROXY&& ) = delete; + + ARRAY_PROXY& operator=( ARRAY_PROXY&& ) = delete; + + ARRAY_PROXY( ARRAY_PROXY const& ) = delete; + + ARRAY_PROXY& operator=( ARRAY_PROXY const& ) = delete; + }; + + [[nodiscard]] ARRAY_PROXY MakeArrayProxy( const std::string& aStr ) + { + return ARRAY_PROXY( *this, aStr ); + } + +private: + void WriteIndent(); + + void BeginArray( const std::string& a ); + + void EndArray(); + + std::ostream& m_ostream; + bool in_array = false; +}; + + +class ODB_DRILL_TOOLS +{ +public: + struct TOOLS + { + uint32_t m_num; + wxString m_type; + wxString m_type2 = wxT( "STANDARD" ); + uint32_t m_minTol; + uint32_t m_maxTol; + wxString m_bit = wxEmptyString; + wxString m_finishSize; + wxString m_drillSize; + + TOOLS() : m_num( 0 ), m_minTol( 0 ), m_maxTol( 0 ) {} + }; + + ODB_DRILL_TOOLS( const wxString& aUnits, const wxString& aThickness = "0", + const wxString& aUserParams = wxEmptyString ); + + void AddDrillTools( const wxString& aType, const wxString& aFinishSize ) + { + TOOLS tool; + tool.m_num = m_tools.size() + 1; + tool.m_type = aType; + tool.m_finishSize = aFinishSize; + tool.m_drillSize = aFinishSize; + + m_tools.push_back( tool ); + } + + void GenerateFile( std::ostream& aStream ); + + wxString m_units; + wxString m_thickness; + wxString m_userParams; + std::vector m_tools; +}; + +#endif // _ODB_UTIL_H_ diff --git a/pcbnew/pcb_io/odbpp/pcb_io_odbpp.cpp b/pcbnew/pcb_io/odbpp/pcb_io_odbpp.cpp new file mode 100644 index 0000000000..e72f514366 --- /dev/null +++ b/pcbnew/pcb_io/odbpp/pcb_io_odbpp.cpp @@ -0,0 +1,170 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + +#include "pcb_io_odbpp.h" +#include "progress_reporter.h" +#include "odb_util.h" +#include "odb_attribute.h" + +#include "odb_defines.h" +#include "odb_feature.h" +#include "odb_entity.h" +#include "wx/log.h" + + +double PCB_IO_ODBPP::m_scale = 1.0 / PCB_IU_PER_MM; +double PCB_IO_ODBPP::m_symbolScale = 1.0 / PL_IU_PER_MM; +int PCB_IO_ODBPP::m_sigfig = 4; +std::string PCB_IO_ODBPP::m_unitsStr = "MM"; + +PCB_IO_ODBPP::~PCB_IO_ODBPP() +{ + ClearLoadedFootprints(); +} + + +void PCB_IO_ODBPP::ClearLoadedFootprints() +{ + m_loaded_footprints.clear(); +} + + +void PCB_IO_ODBPP::CreateEntity() +{ + Make(); + Make(); + Make( m_board, this ); + Make( m_board, this ); + Make(); + Make(); + Make(); + Make(); +} + + +bool PCB_IO_ODBPP::GenerateFiles( ODB_TREE_WRITER& writer ) +{ + for( const auto entity : m_entities ) + { + if( !entity->CreateDirectiryTree( writer ) ) + { + throw std::runtime_error( "Failed in create directiry tree process" ); + return false; + } + + try + { + entity->GenerateFiles( writer ); + } + catch( const std::exception& e ) + { + throw std::runtime_error( "Failed in generate files process.\n" + + std::string( e.what() ) ); + return false; + } + + } + return true; +} + + +bool PCB_IO_ODBPP::ExportODB( const wxString& aFileName ) +{ + try + { + std::shared_ptr writer = + std::make_shared( aFileName, "odb" ); + writer->SetRootPath( writer->GetCurrentPath() ); + + if( m_progressReporter ) + { + m_progressReporter->SetNumPhases( 3 ); + m_progressReporter->BeginPhase( 0 ); + m_progressReporter->Report( _( "Creating Entities" ) ); + } + + CreateEntity(); + + if( m_progressReporter ) + { + m_progressReporter->SetCurrentProgress( 1.0 ); + m_progressReporter->AdvancePhase( _( "Init Entity Data" ) ); + } + + for( auto const& entity : m_entities ) + { + entity->InitEntityData(); + } + + if( m_progressReporter ) + { + m_progressReporter->SetCurrentProgress( 1.0 ); + m_progressReporter->AdvancePhase( _( "Generating Entities Files" ) ); + } + + if( !GenerateFiles( *writer ) ) + return false; + + return true; + } + catch( const std::exception& e ) + { + wxLogError( "Exception in ODB++ ExportODB process: %s", e.what() ); + std::cerr << e.what() << std::endl; + return false; + } +} + + +std::vector PCB_IO_ODBPP::GetImportedCachedLibraryFootprints() +{ + std::vector retval; + retval.reserve( m_loaded_footprints.size() ); + + for( const auto& fp : m_loaded_footprints ) + { + retval.push_back( static_cast( fp->Clone() ) ); + } + + return retval; +} + + +void PCB_IO_ODBPP::SaveBoard( const wxString& aFileName, BOARD* aBoard, + const std::map* aProperties ) +{ + m_board = aBoard; + + if( auto it = aProperties->find( "units" ); it != aProperties->end() ) + { + if( it->second == "inch" ) + { + m_unitsStr = "INCH"; + m_scale = 25.4 / PCB_IU_PER_MM; + m_symbolScale = 25.4 / PL_IU_PER_MM; + } + } + + if( auto it = aProperties->find( "sigfig" ); it != aProperties->end() ) + m_sigfig = std::stoi( it->second ); + + ExportODB( aFileName ); + +} diff --git a/pcbnew/pcb_io/odbpp/pcb_io_odbpp.h b/pcbnew/pcb_io/odbpp/pcb_io_odbpp.h new file mode 100644 index 0000000000..7b362e674c --- /dev/null +++ b/pcbnew/pcb_io/odbpp/pcb_io_odbpp.h @@ -0,0 +1,182 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * Author: SYSUEric . + * + * 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 3 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, see . + */ + + +#ifndef _PCB_IO_ODBPP_H_ +#define _PCB_IO_ODBPP_H_ + +#include +#include +#include + +#include +#include // PCB_LAYER_ID +#include +#include +#include +#include +#include "odb_entity.h" + +class BOARD; +class BOARD_ITEM; +class EDA_TEXT; +class FOOTPRINT; +class PROGRESS_REPORTER; +class NETINFO_ITEM; +class PAD; +class PCB_SHAPE; +class PCB_VIA; +class PROGRESS_REPORTER; +class SHAPE_POLY_SET; +class SHAPE_SEGMENT; +class EDA_DATA::SUB_NET; + + +class PCB_IO_ODBPP : public PCB_IO +{ +public: + PCB_IO_ODBPP() : PCB_IO( wxS( "ODBPlusPlus" ) ) { m_board = nullptr; } + + ~PCB_IO_ODBPP() override; + + void SaveBoard( const wxString& aFileName, BOARD* aBoard, + const std::map* aProperties = nullptr ) override; + const IO_BASE::IO_FILE_DESC GetBoardFileDesc() const override + { + return IO_BASE::IO_FILE_DESC( _HKI( "ODB++ Production File" ), { "ZIP" } ); + } + + const IO_BASE::IO_FILE_DESC GetLibraryDesc() const override + { + // No library description for this plugin + return IO_BASE::IO_FILE_DESC( wxEmptyString, {} ); + } + + + std::vector GetImportedCachedLibraryFootprints() override; + + long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override { return 0; } + + // Reading currently disabled + bool CanReadBoard( const wxString& aFileName ) const override { return false; } + + // Reading currently disabled + bool CanReadFootprint( const wxString& aFileName ) const override { return false; } + + // Reading currently disabled + bool CanReadLibrary( const wxString& aFileName ) const override { return false; } + +public: + inline std::vector>& GetLayerNameList() + { + return m_layer_name_list; + } + + inline std::map>>& GetLayerElementsMap() + { + return m_layer_elements; + } + + inline std::vector>& GetLoadedFootprintList() + { + return m_loaded_footprints; + } + + inline std::map, std::vector>& + GetDrillLayerItemsMap() + { + return m_drill_layers; + } + + inline std::map, std::vector>& + GetSlotHolesMap() + { + return m_slot_holes; + } + + inline std::map& GetPadSubnetMap() + { + return m_topeprint_subnets; + } + + inline std::map, EDA_DATA::SUB_NET_PLANE*>& GetPlaneSubnetMap() + { + return m_plane_subnets; + } + + inline std::map& GetViaTraceSubnetMap() + { + return m_via_trace_subnets; + } + + + std::shared_ptr m_writer; + + bool GenerateFiles( ODB_TREE_WRITER& writer ); + bool ExportODB( const wxString& aFileName ); + void CreateEntity(); + + bool CreateDirectories( ODB_TREE_WRITER& writer ); + + // Frees the memory allocated for the loaded footprints in #m_loaded_footprints. + void ClearLoadedFootprints(); + + static double m_scale; + static double m_symbolScale; + static int m_sigfig; + static std::string m_unitsStr; + +private: + template + void Make( Args&&... args ) + { + std::shared_ptr entity = + std::make_shared( std::forward( args )... ); + + if( entity ) + m_entities.push_back( entity ); + } + + BOARD* m_board; + + std::vector> m_loaded_footprints; + + std::vector> + m_layer_name_list; //, std::vector> + m_drill_layers; //, std::vector> + m_slot_holes; //>> + m_layer_elements; // m_topeprint_subnets; + + std::map, EDA_DATA::SUB_NET_PLANE*> m_plane_subnets; + + std::map m_via_trace_subnets; + + std::vector> m_entities; +}; + +#endif // _PCB_IO_ODBPP_H_ \ No newline at end of file diff --git a/pcbnew/pcb_io/pcb_io_mgr.cpp b/pcbnew/pcb_io/pcb_io_mgr.cpp index 5abafef455..dce1a4db07 100644 --- a/pcbnew/pcb_io/pcb_io_mgr.cpp +++ b/pcbnew/pcb_io/pcb_io_mgr.cpp @@ -45,8 +45,11 @@ #include #include #include +#include #include + + #define FMT_UNIMPLEMENTED _( "Plugin \"%s\" does not implement the \"%s\" function." ) #define FMT_NOTFOUND _( "Plugin type \"%s\" is not found." ) @@ -328,4 +331,9 @@ static PCB_IO_MGR::REGISTER_PLUGIN registerIPC2581Plugin( PCB_IO_MGR::IPC2581, wxT( "IPC-2581" ), []() -> PCB_IO* { return new PCB_IO_IPC2581; } ); + +static PCB_IO_MGR::REGISTER_PLUGIN registerODBPPPlugin( + PCB_IO_MGR::ODBPP, + wxT( "ODB++" ), + []() -> PCB_IO* { return new PCB_IO_ODBPP; } ); // clang-format on diff --git a/pcbnew/pcb_io/pcb_io_mgr.h b/pcbnew/pcb_io/pcb_io_mgr.h index 84a128ad5c..b93fd4109e 100644 --- a/pcbnew/pcb_io/pcb_io_mgr.h +++ b/pcbnew/pcb_io/pcb_io_mgr.h @@ -69,6 +69,7 @@ public: PCAD, SOLIDWORKS_PCB, IPC2581, + ODBPP, // add your type here. // etc. diff --git a/pcbnew/pcbnew_settings.h b/pcbnew/pcbnew_settings.h index 040be65fef..570fb6ae1a 100644 --- a/pcbnew/pcbnew_settings.h +++ b/pcbnew/pcbnew_settings.h @@ -201,6 +201,13 @@ public: bool compress; }; + struct DIALOG_EXPORT_ODBPP + { + int precision; + int units; + bool compress; + }; + struct DIALOG_EXPORT_SVG { bool black_and_white; @@ -379,6 +386,8 @@ public: DIALOG_EXPORT_2581 m_Export2581; + DIALOG_EXPORT_ODBPP m_ExportODBPP; + DIALOG_EXPORT_SVG m_ExportSvg; DIALOG_EXPORT_VRML m_ExportVrml; diff --git a/pcbnew/tools/board_editor_control.cpp b/pcbnew/tools/board_editor_control.cpp index e99ef3224c..8aa4edcb77 100644 --- a/pcbnew/tools/board_editor_control.cpp +++ b/pcbnew/tools/board_editor_control.cpp @@ -512,6 +512,8 @@ int BOARD_EDITOR_CONTROL::GenerateFabFiles( const TOOL_EVENT& aEvent ) m_frame->RecreateBOMFileFromBoard( dummy ); else if( aEvent.IsAction( &PCB_ACTIONS::generateIPC2581File ) ) m_frame->GenIPC2581File( dummy ); + else if( aEvent.IsAction( &PCB_ACTIONS::generateODBPPFile ) ) + m_frame->GenODBPPFiles( dummy ); else wxFAIL_MSG( wxT( "GenerateFabFiles(): unexpected request" ) ); @@ -1695,6 +1697,7 @@ void BOARD_EDITOR_CONTROL::setTransitions() Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles, PCB_ACTIONS::generateD356File.MakeEvent() ); Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles, PCB_ACTIONS::generateBOM.MakeEvent() ); Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles, PCB_ACTIONS::generateIPC2581File.MakeEvent() ); + Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles, PCB_ACTIONS::generateODBPPFile.MakeEvent() ); // Track & via size control Go( &BOARD_EDITOR_CONTROL::TrackWidthInc, PCB_ACTIONS::trackWidthInc.MakeEvent() ); diff --git a/pcbnew/tools/pcb_actions.cpp b/pcbnew/tools/pcb_actions.cpp index 85b61ef434..2019adb236 100644 --- a/pcbnew/tools/pcb_actions.cpp +++ b/pcbnew/tools/pcb_actions.cpp @@ -1029,6 +1029,13 @@ TOOL_ACTION PCB_ACTIONS::generateIPC2581File( TOOL_ACTION_ARGS() .Tooltip( _( "Generate an IPC-2581 file" ) ) .Icon( BITMAPS::post_xml ) ); +TOOL_ACTION PCB_ACTIONS::generateODBPPFile( TOOL_ACTION_ARGS() + .Name( "pcbnew.EditorControl.generateODBPPFile" ) + .Scope( AS_GLOBAL ) + .FriendlyName( _( "ODB++ Output File..." ) ) + .Tooltip( _( "Generate ODB++ output files" ) ) + .Icon( BITMAPS::post_odb ) ); + TOOL_ACTION PCB_ACTIONS::generateD356File( TOOL_ACTION_ARGS() .Name( "pcbnew.EditorControl.generateD356File" ) .Scope( AS_GLOBAL ) diff --git a/pcbnew/tools/pcb_actions.h b/pcbnew/tools/pcb_actions.h index 6b8a0e9089..4f15ceb813 100644 --- a/pcbnew/tools/pcb_actions.h +++ b/pcbnew/tools/pcb_actions.h @@ -435,6 +435,7 @@ public: static TOOL_ACTION generatePosFile; static TOOL_ACTION generateReportFile; static TOOL_ACTION generateIPC2581File; + static TOOL_ACTION generateODBPPFile; static TOOL_ACTION generateD356File; static TOOL_ACTION generateBOM; diff --git a/resources/bitmaps_png/CMakeLists.txt b/resources/bitmaps_png/CMakeLists.txt index 434d95e34d..445b0c018c 100644 --- a/resources/bitmaps_png/CMakeLists.txt +++ b/resources/bitmaps_png/CMakeLists.txt @@ -447,6 +447,7 @@ set( BMAPS_MID post_gerber post_rpt post_xml + post_odb preference print_button project diff --git a/resources/bitmaps_png/png/post_odb_16.png b/resources/bitmaps_png/png/post_odb_16.png new file mode 100644 index 0000000000000000000000000000000000000000..427fb58302efd3e334a75f3cacf2a12277d3b432 GIT binary patch literal 592 zcmV-W0%L@`9ykpl$|m(kftc zi=R9f-Uz_+ZW%RC-k^RUf~~Ei`33z#61BEDwEO4rfRE!g8-tMeP6W?FNes>{Vb7h3 zY=Ja!ORFC*W|y$-`Y3EO3t-=;u<4o~-bjiF8pJQS>WHH9aX4FWxI2h)i-5CY(K4O@ z^Kc5MtRdX<#bM@RFmwgcHWY=`;zh-U9yFSt{{0BKs4#ZGzWXRRic#<$s=`pwK`J9? z;b{r0-Lv}0?tvOx^IdSXo`fdOT*P8<7JSJGS@gwCmlmk+!H+fn+u;CPoSFRUh!HCb zNu<+hEQjBM-&dZI&`hUV5Lt6sI(tde%UxyoHlM)abeITIk&pQ58z;44iutS-=tZKk zz|+>QoLMk6Vfy?PA{aANW6F40uB|G9^>CD~NWHk%=NgE>xA(Yep?V>wAR}RXa`R-R zi3moDk0}CH%b4;CyoLJhVq-$y#Y|n}pXs#jd7tL=hP(oaksSv%a#~%7v@nsMoR$fR e;d&`7)qew?`?!sltel$w0000WdP)e!QxFJf5V|DZQwO`Z+tb?QToS7I^rwvuEafzd19rvl0N3fLNB@ zq}6JRh`tgbMosE;x>B`Ty_xSR;lF4!8s!>{1tK2BQ2}Itb-e&0y8y7Qpc@;Z%*xIH zV0le|%320q(GNytX({(fJwraVL$v8g`)D=0dyGDFD13nS*883zgO;9xy!RMOxi^+IDz< z;Y6(&t{=ar)y#X=bu<0EA63xqm_LWu(QYr`Gd$M zI#%6}8IK5n`=@_Kr|Ji36{9mgHK?k<%H#sWQDr%7kDq~!7sUS>Wb&Mqt_pXwD?s<61?+(gfw|>I8GJq; zm0kTN9OBmQPTwjT(0)x3Y3}s~0h~THHVYv7y-Z;^U@RIypLQ;oICx3nHz#sW&r);G zypujB>Kb_ykq-y}7@i7}_U;lvxx2Ruf6+Tj^Nx0pWcH8Dka=&`TL* ztN^kHD$#)U__Qq~X(8OiIc}qXz>@LgVx_5Zq5=M8N5EWG7 z5^04}EP>hY1Omah z+}zyzd3kvqMDS3ZM32Q{F^A2I&3rN$p!I^Wwe%v9Xu4AX^24-YFlR&n9=HO~G+Y2g zIvarT(NYYjrp6ST1N3_hAT+%Skva8Dd&}Lo*p*%Te*u!D&1lqrXWAL6Runh1dkDs~Pw+phuz0p0QHdF(j}2h*saKeHq0}>g(6lP_^#8yO*=I=V+fnzehY3to z^syaXeS`Qw`abGhe@#|%UhV4yHW$R zovv(k9bk5R3FamYtzBk?1_gC`rcL|$2Y)il5-VsnEsjPOQ3G=#e0%!3P{)69Dt4ylYVJK2eSl= zE~{Jz;9V<6aB_ud2DI|S?p0$=>KjZjTOQ77IkV?O$S#>fvbHw0lIdT1wG4qtWmtEq z22^-4F{=N{08}1Lm50=s9Cp;1>i5hsUay+>H<=TpjQ_z^d>& z5Y7y-YizAT4lh|XfBwk708!F4=8DTK=sJ@rhAX-ebsZ9a+gTURScZ2|7f}|r-yTG| zqtj6D($Sjr2$I<=Y=Z{`LnAGP(P)IRyBk#pW7r}5f_8ZXP_T6`t9kRYydm@7>NXfq zx#t8MguZxI;8^zn9zP|i=Q}lb2Jw=^+ie3tzFHcc0py>Iw+u$AU;QN4WZ0!^V}*Df ze$*P`1zQGEoB@b;AF&7|=Wj6?b*NkLDJ|P{U!Rn2YHMC8t?Gc+WCI`+3MUeiFAf2= zMOalmAL?u&w2E>lR&K+GOQ|MPMQ|8ujwhh)Z7nMIMOkN{*N5#>nV3u>OOZjY8rA~b z8R>AZTLU=wt%eoAnMm;*^bP>G)GYw9FF(K=0OW-f?G^wP383^4V31_M-)#W0419Y4 zb@4Y%05a|-I{-nqSl@*ImW7~rw%{_}X0#T{(Qx~Lz1hFZpSo;}#{ds~){mewarGqp zz3$Z#f9@2!hmm`k;O-!SwNA_jDXh>pWVFNohVDx0w`7!LIyanv2#A@QT>I!gr&~?p f$Bm%{$2RU4*U}LGiEUb>00000NkvXXu0mjf9CYC+ literal 0 HcmV?d00001 diff --git a/resources/bitmaps_png/png/post_odb_48.png b/resources/bitmaps_png/png/post_odb_48.png new file mode 100644 index 0000000000000000000000000000000000000000..59287c510eb96abc9f98703e3cde1bc530a1272e GIT binary patch literal 1517 zcmVd zbAP<|Itc(K`4Y>rPb-zmY@!O4N@XX4Fw03)rB8}qaPsaTnk`n=yJGi-S!b` zi!Z2t0w5Y->fTB?pzDN|ZYSLA8-g z(hM#5`BT+xm=X?v>|!(Rr1EAzyu8QgvX!`OHJx^MJq}Xa-UrK1HbG8FD+T5QFx|4l z>NCx-@j@H)I9#@m&bEXe;M~<7+W4pE+uSxBsjh($zyZY7MD{}+tuuIkl%@_>Z;g)> z!iaIUTmpfGy>1IVfU32ZHlBXG(ZgO#Mi5K#K@+Sk8Xpl9j;NSP{ML6h)b_V+DD(io zHr}C)uOm)AZs*ct);Cw^wO{~nGGoE01`V)?tS+cm_EggETT0t$<2WI?IyiZ;;H1Ux z?;JAGbh>x{AM>oT{~e zeRz~Qj5*GAR{E#ay;2qEJL!(=92w;Wz~y+SvIF)pH=%#%L1-p1PR>r-Fcsz{*AR~C zd6G&v!dP0#$d64a$=6kI+SCmL#2H*>>;ADr=BXy?D9$6U-Zkqzx}R+S{kq$6{M@C! z@&7u^P06bO*`{LHo|zAYZ)ZVKLI$X2ECBWNR49sH28GF4@bj84L4EiHsFcd+0YJyo z*A~Lrgyq0W43WPF>}fK>y}K-xFIZ&kP#?lalFs?ccxnZ31O`q+B zo=TI4{pz}HaJQ+|!*=Vpa=xgx`g%VwS>-Bz0kC9K@;X{GQo(9!bUAOgL(`Vs)IJuV zX2mBYTIpb|Z+6$k6^jSJA@p|UIr3mt<5vXY`8O||kfsked+fU20Ql;9NuhyBLa%V} zMF)ZS;J;|#%GBpeOv;K;f6h3303u};1n^hn!1YH20W={8AYt7RDF8+y|0?7F`nb8% zBM2}n5tumMmAsaz+<5sTL70dP5OjhMSLqu_Bm zSgEC{F{Gp0g2(EQKX;#x@EFZX6+!@aHIn&LMZKvHR@!NbB+}uq5QH%~1FDy<2X>l5 zXyiwjSb2)*zZ!&OS&f~SNIJI!dsB}iKlXk&A^@oJsq zmtIPoY4+!+z{JTjxCuMq0Gx0G!d(>4^Q=VCf~QI%Cpt_Z8Dh)R$IhK2rmcSg+%N}r T`3Vb79jzC9uM`046#o z>kh!O3;EF9`;6*fXU|iRE(ZnohqS?+e$e_@<~#{%=Yot=1;8i04l=7dWZE`JdI^YH z2k@R#GE_Eq6P+dvgzrw(^)XsH}FiWrRs5dBa4%qci4xO zsRU41FCnH1d2FR$0X6mFN?WuG;B-$O9whU?{K-AE0(!1&hKQ|je;9lPi76=4ts=j z8=Tu!0q{m?P`b^#PPYjUBU7ooC$a)y+x5Ft2Tmc{isAq+Q6){VKe7UR(bRa-HxTSf z8@pvXwJY>_q{klO5LE#iQ4Yr$p%LYCRRD0j!6UxZC!ZJ)oDRt%oo%!R;DbL?2*VW> zJldb1ksW0mEPV%KhkU_#y{xf&SOEW`*wL?`EzUIZ8GtnaJdYeWp74Os>WhU6V4iKVD<9sCptnDtr^37ggv*|o6+2R9p?E-=-^&elZ^{cvWu!%(1%);TB6YW&EJH z8HrQoyrmd`W3tb1TIqhPW35L5XhJ(20wC6WfrgDAP=YZpUo?sPhB{A%DmC)l?ouGc zPg}&uh(yaTq54J?v^}f?acVZCFZqrc2oqj{h|7IN_W&XyA%5O^#>O|7ltEWl7bQt+ z8)UB9%5*xo=p2A>rvsx-C(T?2jX8xf#*^x3Xo3uzE%ei*G4ENNF@KiM00^#L1CjZB zMkC*lnWxZr3WB(iktd*dkgPKRLR))QMt%r{M-7ZT0Zj!f?E^roGm*tYnj^nd<;W}a zgq1(g42aw?tpgA`yV4)|>;gu{%Xq>HTAt90ykHz10QqbIh+Ds1BO|ZACvex%0T9j> z0K7gQHaMXTkT@-enZXi?!us}lF{E%+EU!7-b94aMu>v3@RON_dfBmykLu*2!71me! z1}TM5e#TFg2PCQlAQFkjAp_b*oc_H1RRYM{vQMEQ)z>2-Y5HP?$8tCS0>!@_QwX8L zD^S%nAJTeJ61{G=bEE)b&w8nz|7dC4QeHL0_yn_g=$N%q0JyC@1W9{4WGuH;bxjHF zcZJc5$X+Az0Jn3rGC;}~YoRLO2GqqSLW$!^5Sj7^;S3J|E9YB#cz9C?HK8$(z21(_ z_4=5+S&iQpwpaJ9QGti|h<0;y0H~o>Bc`vHqv~ebM0XMYr(2YywCy;hkdq)flT@ zz?nB6b)=tJz~VzkW77}-nX9)k1yC0|+3Izj0w4k+hwsL801D00f8*HFjOwDM$#!#g^n zFGn>c{NNIKLWHIaA8`AjBd<3Ah|hA397m&=@cY1xN>%~XlefS18UPg-&A3xhhvQjz z5ZZ%A{v3Hu$zt}K2`2nC$S`-}$u7#_ sNS%WQw;+|__Nkl-OPA6z&TV(!+t=Jg-hID!-Ep;MK6t_RK0jWc?|trU0BoE{Byuhs4u56fKWvMl zc;4u<83&d~AEVhIV+!hCNT^0Aslq;Az~#BT8M`(%#RZ2I0{;p@V3nYroiEPh(6T7w ztyF@K#bcj}a4=C-ftwYNrYVNji@5bp#)n)Pt?w0lEF5S8o_n=Z!iDMGNQpD&P80{T_zaM?BUXEu2SH7Q_ybkfC>5-ywS6)XjA#K>vaL~l#FN7 zYiJ){fUkez-#e&=XK)_d*SkRMf=Rl$6U5=I)dXjsOQ$(^V#E9XzBsHr4MbU?M{f)a zfPT(^-0U_)-{}?aNt@+I`f2^vJ)(%6tG}CtUeMh?>2Zhf(E1~-TLq5; zA-%}u1N|Yf>6@CMxRpgQv1Z)jn^jQ9_+kjgeL?e`)z}lL;ZaRMI-*eTj~KaqQ}8#$ z={;jz90|sCf%}hzCaMZFNs};V7IzQng66xXK(XI$IZ_}yJ5I5}tg#EV_mOq_BpMq} v94X*XXM2re_w@6HF-nZ2;t<@tndg52!cFRK*39cd00000NkvXXu0mjflGzI$ literal 0 HcmV?d00001 diff --git a/resources/bitmaps_png/png/post_odb_dark_24.png b/resources/bitmaps_png/png/post_odb_dark_24.png new file mode 100644 index 0000000000000000000000000000000000000000..1235654e1fc1bb6795f21203e11972dc0b301d85 GIT binary patch literal 760 zcmVuJ%xG&MB|IT-6i9ybgDG~n}I0i-1z;Ao#0W^{JQkJ8Z~@~ADmmooYb z9!@0zv77+*Ng0#=DDwKfSpO5x4bU>jF#0Qkwt2=gRby7lx_MUNF)hI6kx>6Z!Lfdm zfQktTGr=ed+O>__#Kj4>*YYVdM(1CAL5wbL@D0#RNr2dyFrh|m&FzVM-r z#tf}&>LRJN=l~9Up=Td+puC>ES z*>2qr+irHkNS(FqkcumHhV0&Bu)Yz<`gmL^6!NoO6|&J4(B^Q$U!pfxIvyb$4s$X0 zcsfK@O2tE_0q*jeOmoIk01g=#)j^~p!JZD7Hv%k{*|*JGyAtNAe1U{{D3o|Ub87(} z9iXA1p~48@vuEnQ%NAeiT)u1uCJKO7rrF#}K{ZckLpaJi1R zQ-G@u$YoT$utVdR`H-|0EZNcr)c7)t_-c+P`aO|n{QJ6PiK5+rXVztXJ>F#@V+hcoB$j` qOQ$QNO5H^DD3TPlt;Wu*b89LJZ4mxy;>5Z(~+U$Bq}LC8WPUeK;H5)zhe5xvY^wd>jhnK6vG&V9@&%p1cG`2!+HT#xe$Sb%U3=OdZN1Uto1Euzp6}=RJ-_F9PV)fd#mi_kPP14n z=SUn0S;|*cRdv;DHqVjsN~kcIOf%`FpE)p&m)B~w&Q1$J9Z&rO^N21g0dV|70Cxhw zg1atct0ni{SWJ>@TXd%{7GN-E0B`$;@c2hidbT@5xb-dY9{}W&YCni@2Z!Me4dd)5 z5&2Eg_T8`jxat_dfj541apM6V^aZ8<*Bt?@vwJfGFud|37#=}MJBO_=I0!={Fn01% zo2`onHR0ly0o?2fV3?j`0a)K-?*u&m6~d2!Fcvmt0MPV3h-WlFq|9dZ)Hzyq2w|xl zX^W3Q!=EKs`OpLQ+wz?Idw^y#c;bwj-02OB0r>|_RQ*BfniirSXrNLnsK{(P|A}<_gwf!5s3wefT~*ir3k26yvhOCtUJAf(!h^s zP`1{zq|%^~{k^@7gGiOVVi-NbsA2)$`-Y@8*5_f&QYix1K(rbnkG!`uK>g(nT)bBD5)AA2Tvq}IGyS?^eVa8YK&%hie$`-Dj;;egD8rU`07it5WHGfoE5;U zlD+cgtuM#SCGkA?!{_B7yj*el)QkX*95RDbVx9(qFVt?51NfQ_ph16d{9u#^LQ6-~ zu%DBMu$_s6&^9IjR903VPY2LizHj_Y$}LeN=PwBNTB12{qj~I^jir|gsukvQpKsb6Kei?#!a-Yid2|a_1>PSvG zfM=zt_kBD-ioQ-+fOPT~YPaPGpe-W+e!XE{E&zOm`ihJIq$sF$?O6hZDR<^(3?Qg< z`N;tI`lcvAsX5tkTqAoD{$&(QvH)e35MOtLZ?q?yeYaOrHYIBS6ckKgGf{pMvO`Pv zwtu5xR?^ETqe!TJ;Rz literal 0 HcmV?d00001 diff --git a/resources/bitmaps_png/png/post_odb_dark_48.png b/resources/bitmaps_png/png/post_odb_dark_48.png new file mode 100644 index 0000000000000000000000000000000000000000..fdfcb313f841f258a8ac0af9ca1a661f1bd4dc0e GIT binary patch literal 1488 zcmV;>1uy!EP)z%H-jIm0gOduDfM7n*pJ6EbHm_xykN z+9w{v?)fkP&W;7NJ;%q#|Yr~&e82d3PePM4_R8%~H1GOYTbSUd8 zFE8H|6#&w2!~q)DP`t-(m`tWeBL=`UzP}E{i-ua#BL=`Sx$AGKY%l#0Ehl&p>}8nKK;`!UMJUEpmD+p=SOEB4uGcn6c3>0A3?m{Qg0Ej ztMc*yCnKRf^`3|Kcd3;vCSZMo>%H>Je}t&=X8LUCilf(U%= zzAGd^Ne>6pu0@z%1Za8Sf*n_;lm{s5 z@HXeuk4mPTJ@Auz0*ZP#i#`Q4-CL2Xylln>CpPJ2`UNBeove|%6-4nXB4 z?4#z@)NaG|PHWq>xFlwYd=@uQ-YrK|04MQ2HDj*=8OUgz7LCN69iCaKI&@7-i8da! zOXJR!K=tFxLff$lCA*i~)-Zd3;6L!%1H$1+pUWI7c(12u~_5CK!P!&GF zsLYX(n0GE)1A?XF>`?|tI6+XE)!uZ7!oKNd#Xgily8Z#zdI9>(xcSGi0#R0jjk)GW ziEyU6FbQe3J#eD z4CESsRqJHg_#Qo+V~!BW+5+tQvT7aW*v@Nyq8V! z8YVda;FSxO&w|;WQw@%+ntF)EiptKwbXuOYxa_ZiJ0NRm&2egGv)p&$d$GL7eqUGu z57MQ@bH{3dXIT&g0r=Ykz-Hu2X2N?p#SOrmlP@j4>=XzbFN*gVf3sH(L&HYIDEvfK-}m7HZwc~r_t_=8^B1mK78}`T9oh1wx#?7-CYim&0XXdo%#US;1$EE2s((&sCiPzGu+K&lzh#xcjd!K<11M z^ti!bXov)0G86zAZb+`^gwInqC_sq50jAM8l4N{h+K3Rl-PxeZWD92_S^9-Y&Nv|f zgo~&;85!t;sNkwq5!y5txj(A+UFDp5VN4s{gnWKe#y_Q z(E~7B;|`$FZHpU#2%*+B$^yXSvo`>%EtdmuUGg{`s|hL0CJZz#xe@8f>pmZe$7qe@ zOOVx`!aObOOT7Y1=_L&4l$_E1g}~(>lOx`R?*T8%{#OG@52cJ zAhV@0PvY~TIR^39?dpS1Mf}em%0J|ocd>f(Msif(G`bz04ZG$59-{{kJw@p}k0Sf% qREfruU)||PF?;C#r0000 literal 0 HcmV?d00001 diff --git a/resources/bitmaps_png/png/post_odb_dark_64.png b/resources/bitmaps_png/png/post_odb_dark_64.png new file mode 100644 index 0000000000000000000000000000000000000000..140802925c30a76d102ad9c4de96c67c6f100488 GIT binary patch literal 1976 zcmV;p2S@mcP);NqKQv+XDPibML zwVZy(UDr*D&iUU0D1AVEnmSTi3}X4NI|i{l9wEE{@B+XK0O0{({-r@swQ+E7$P7mF z1PqT&K$pP``|j~}LCjUaqoB6FK?h$|X(R#oyix;OZVW-vEj@gHg$)gW1!(-$eiOJl zTC}6E*h$2u>yF+ZhUW-NI7Y`O;4~V4_Th840QR(VpcxsrYpe3fh)duB15nt>iyf$c zW`gZadPry-g6gLuV)>be!w%P7c>LV1^Zmo)Fl3rC$ZTce5&*IS9#8Dho53(@y?_7) z4FI{X(0H$L3>IFp)$1zp2rRb>!|sm&xe(dYyO;xwFxRfGYrh51Qahi)Yc) zY1560@Rt6i4Gw^xasCNMVW-idKHa9n)On%&qRr?Rbnp(>k;k|Lu&&-d2=2J47wdBG za=`(R(aMR9%A55L^%SUtqo~UY=m9q-mM^b$3|^$)x2?%qpWkkEb&U;xFDf;D z12Ak^QL2IkfP&5Vgmnd&USDoA*W+wDd{<|-8d9(G3xH*pwPM}zp5ed%ph!+j97#w# zYWt^_JDPtK^r9^1(v&b+=#atoOQ%unY~6qfqHLzY5{&5G2D4iLr)-uxYV+J9E9}h} zoC5+NwZ+7Z;hDo}ZVGXSKO=nc)pmr{V*|cCu`1<5b%}TxH?FKRlG+3Zrb%!^*Qgu(6~Cb`~{2Qei!8EpCMP zW38~H>~Uo620axSmbJGAYLbdzAYKWE7`aRJY|MnF1SOQFWI|q~16Y2#8&0R40Ie)} zMx$M+KTZKviRqA^t&S`Ji!Sy-k*W+>FAblrzBmTXF&U&o?{Lk?h%S`9txlO-E( ze9#L@*lq#1AL3Hv;0XVf5L=6Ujh#BF~8Xx^+0%*cD`30!Q_GEY39++P4F zKcBHvG6wkeF5iq7G^hn`Yxa!o>0=c8J@J4I{xE*8O6`91GY`2osP3lIa=zj!739Jxm;KQh?SoY z9sra+M&2Aw0I)wPK?Q)s)!_tyi(x(uE&v(xaToy@0GVt~PyyiHYc@9A5f%WV71@%$ z8S>*ahz$b(qq^wCcS8cevKn-R5&##Y$dmllm_IcXBVT|WnUJ<*DS&5njsTQWZ;HyF zva)Tjg%kjZi1cl!%bX_e2;Ckj|c!ZAU?rqaT*O7k(%DpeuI@U@A&=e z@sQ*T@!Q|P<#MS$TCta&?9v*J;w;9ps4j{hw5y|y&GaX~p6EZC^>K0m_`*j30000< KMNUMnLSTZRgQD*M literal 0 HcmV?d00001 diff --git a/resources/bitmaps_png/sources/dark/post_odb.svg b/resources/bitmaps_png/sources/dark/post_odb.svg new file mode 100644 index 0000000000..4f65fb28c2 --- /dev/null +++ b/resources/bitmaps_png/sources/dark/post_odb.svg @@ -0,0 +1,147 @@ + + + + + + + + + image/svg+xml + + post_odb + + + + + + + + + + + + + + + + post_odb + + + + + + + + + + + + diff --git a/resources/bitmaps_png/sources/light/post_odb.svg b/resources/bitmaps_png/sources/light/post_odb.svg new file mode 100644 index 0000000000..f5e6f6cdab --- /dev/null +++ b/resources/bitmaps_png/sources/light/post_odb.svg @@ -0,0 +1,145 @@ + + + + + + + + + image/svg+xml + + post_odb + + + + + + + + + + + + + + + + post_odb + + + + + + + + + + + +