From 950db6ff9c41f44dfd62097956ab9a02590ac45e Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Wed, 1 Jul 2020 15:30:40 -0700 Subject: [PATCH] pcbnew: Add Fabmaster import Allows importing ASCII design output from OrCAD designs --- include/kicad_string.h | 1 - pcbnew/CMakeLists.txt | 4 +- pcbnew/files.cpp | 7 +- pcbnew/io_mgr.cpp | 3 + pcbnew/io_mgr.h | 1 + pcbnew/plugins/fabmaster/CMakeLists.txt | 14 + pcbnew/plugins/fabmaster/fabmaster_plugin.cpp | 83 + pcbnew/plugins/fabmaster/fabmaster_plugin.h | 68 + pcbnew/plugins/fabmaster/import_fabmaster.cpp | 2662 +++++++++++++++++ pcbnew/plugins/fabmaster/import_fabmaster.h | 562 ++++ 10 files changed, 3402 insertions(+), 3 deletions(-) create mode 100644 pcbnew/plugins/fabmaster/CMakeLists.txt create mode 100644 pcbnew/plugins/fabmaster/fabmaster_plugin.cpp create mode 100644 pcbnew/plugins/fabmaster/fabmaster_plugin.h create mode 100644 pcbnew/plugins/fabmaster/import_fabmaster.cpp create mode 100644 pcbnew/plugins/fabmaster/import_fabmaster.h diff --git a/include/kicad_string.h b/include/kicad_string.h index ab9962300b..8f4a52218c 100644 --- a/include/kicad_string.h +++ b/include/kicad_string.h @@ -295,7 +295,6 @@ static inline std::vector split( const std::string& aStr, const std return tokens; } - /// Utility to build comma separated lists in messages inline void AccumulateDescription( wxString& aDesc, const wxString& aItem ) { diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 6360375d8d..01b1113356 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -35,6 +35,7 @@ include_directories( ${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/common/dialogs ./exporters + ./importers ${CMAKE_SOURCE_DIR}/utils/idftools ./specctra_import_export ${INC_AFTER} @@ -606,8 +607,9 @@ endif() add_subdirectory( plugins/pcad ) add_subdirectory( plugins/altium ) add_subdirectory( plugins/cadstar ) +add_subdirectory( plugins/fabmaster ) -set( PCBNEW_IO_LIBRARIES pcad2kicadpcb altium2pcbnew cadstar2pcbnew CACHE INTERNAL "") +set( PCBNEW_IO_LIBRARIES pcad2kicadpcb altium2pcbnew cadstar2pcbnew fabmaster CACHE INTERNAL "") # a very small program launcher for pcbnew_kiface add_executable( pcbnew WIN32 MACOSX_BUNDLE diff --git a/pcbnew/files.cpp b/pcbnew/files.cpp index a5f0825e2a..e3b0e5c166 100644 --- a/pcbnew/files.cpp +++ b/pcbnew/files.cpp @@ -96,7 +96,8 @@ bool AskLoadBoardFileName( wxWindow* aParent, int* aCtl, wxString* aFileName, bo { AltiumDesignerPcbFileWildcard(), IO_MGR::ALTIUM_DESIGNER }, // Import Altium Designer board files { CadstarPcbArchiveFileWildcard(), IO_MGR::CADSTAR_PCB_ARCHIVE }, // Import Cadstar PCB Archive board files { EaglePcbFileWildcard(), IO_MGR::EAGLE }, // Import Eagle board files - { PCadPcbFileWildcard(), IO_MGR::PCAD } // Import PCAD board files + { PCadPcbFileWildcard(), IO_MGR::PCAD }, // Import PCAD board files + { FabmasterPcbFileWildcard(), IO_MGR::FABMASTER }, // Import Fabmaster board files }; // clang-format on @@ -470,6 +471,10 @@ IO_MGR::PCB_FILE_T plugin_type( const wxString& aFileName, int aCtl ) { pluginType = IO_MGR::CADSTAR_PCB_ARCHIVE; } + else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::FABMASTER ) ) == 0 ) + { + pluginType = IO_MGR::FABMASTER; + } else { pluginType = IO_MGR::KICAD_SEXP; diff --git a/pcbnew/io_mgr.cpp b/pcbnew/io_mgr.cpp index 4e380dd77e..611730b557 100644 --- a/pcbnew/io_mgr.cpp +++ b/pcbnew/io_mgr.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #define FMT_UNIMPLEMENTED _( "Plugin \"%s\" does not implement the \"%s\" function." ) @@ -192,6 +193,8 @@ static IO_MGR::REGISTER_PLUGIN registerKicadPlugin( IO_MGR::KICAD_SEXP, wxT("KiCad"), []() -> PLUGIN* { return new PCB_IO; } ); static IO_MGR::REGISTER_PLUGIN registerPcadPlugin( IO_MGR::PCAD, wxT("P-Cad"), []() -> PLUGIN* { return new PCAD_PLUGIN; } ); +static IO_MGR::REGISTER_PLUGIN registerFabmasterPlugin( IO_MGR::FABMASTER, wxT( "Fabmaster" ), + []() -> PLUGIN* { return new FABMASTER_PLUGIN; } ); static IO_MGR::REGISTER_PLUGIN registerAltiumDesignerPlugin( IO_MGR::ALTIUM_DESIGNER, wxT( "Altium Designer" ), []() -> PLUGIN* { return new ALTIUM_DESIGNER_PLUGIN; } ); static IO_MGR::REGISTER_PLUGIN registerAltiumCircuitStudioPlugin( IO_MGR::ALTIUM_CIRCUIT_STUDIO, diff --git a/pcbnew/io_mgr.h b/pcbnew/io_mgr.h index e1e720e2a2..d2418f3a4e 100644 --- a/pcbnew/io_mgr.h +++ b/pcbnew/io_mgr.h @@ -55,6 +55,7 @@ public: KICAD_SEXP, ///< S-expression Pcbnew file format. EAGLE, PCAD, + FABMASTER, ALTIUM_DESIGNER, ALTIUM_CIRCUIT_STUDIO, ALTIUM_CIRCUIT_MAKER, diff --git a/pcbnew/plugins/fabmaster/CMakeLists.txt b/pcbnew/plugins/fabmaster/CMakeLists.txt new file mode 100644 index 0000000000..86efec3764 --- /dev/null +++ b/pcbnew/plugins/fabmaster/CMakeLists.txt @@ -0,0 +1,14 @@ + +# Sources for the pcbnew PLUGIN called FABMASTER_PLUGIN + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) + + +set( FABMASTER_SRCS + fabmaster_plugin.cpp + import_fabmaster.cpp + ) + +add_library( fabmaster STATIC ${FABMASTER_SRCS} ) + +target_link_libraries( fabmaster pcbcommon ) diff --git a/pcbnew/plugins/fabmaster/fabmaster_plugin.cpp b/pcbnew/plugins/fabmaster/fabmaster_plugin.cpp new file mode 100644 index 0000000000..32cdabcd6d --- /dev/null +++ b/pcbnew/plugins/fabmaster/fabmaster_plugin.cpp @@ -0,0 +1,83 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 BeagleBoard Foundation + * Copyright (C) 2020 KiCad Developers, see CHANGELOG.TXT for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-3.0.html + * or you may search the http://www.gnu.org website for the version 3 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file pcad_plugin.cpp + * @brief Pcbnew PLUGIN for FABMASTER ASCII *.txt/*.fab format. + */ + +#include "fabmaster_plugin.h" +#include + +#include +#include + + +FABMASTER_PLUGIN::FABMASTER_PLUGIN() +{ + m_board = NULL; + m_props = NULL; +} + + +FABMASTER_PLUGIN::~FABMASTER_PLUGIN() +{ +} + + +const wxString FABMASTER_PLUGIN::PluginName() const +{ + return wxT( "Fabmaster" ); +} + + +const wxString FABMASTER_PLUGIN::GetFileExtension() const +{ + return wxT( "txt" ); +} + + +BOARD* FABMASTER_PLUGIN::Load( const wxString &aFileName, BOARD *aAppendToMe, + const PROPERTIES *aProperties, PROJECT *aProject ) +{ + m_props = aProperties; + + m_board = aAppendToMe ? aAppendToMe : new BOARD(); + + // Give the filename to the board if it's new + if( !aAppendToMe ) + m_board->SetFileName( aFileName ); + + if( !m_fabmaster.Read( aFileName.ToStdString() ) ) + { + std::string readerr = + + readerr = _( "Could not read file " ) + aFileName.ToStdString(); + THROW_IO_ERROR( readerr ); + } + + m_fabmaster.Process(); + m_fabmaster.LoadBoard( m_board ); + return m_board; +} diff --git a/pcbnew/plugins/fabmaster/fabmaster_plugin.h b/pcbnew/plugins/fabmaster/fabmaster_plugin.h new file mode 100644 index 0000000000..4b875ddd87 --- /dev/null +++ b/pcbnew/plugins/fabmaster/fabmaster_plugin.h @@ -0,0 +1,68 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see CHANGELOG.TXT for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file fabmaster_plugin.h + * @brief Pcbnew PLUGIN for Fabmaster (Allegro) ASCII format. + */ + +#ifndef FABMASTER_PLUGIN_H_ +#define FABMASTER_PLUGIN_H_ + + +#include "import_fabmaster.h" +#include + +class FABMASTER_PLUGIN : public PLUGIN +{ +public: + + // ------------------------------------------------------- + + const wxString PluginName() const override; + + BOARD* Load( const wxString& aFileName, + BOARD* aAppendToMe, + const PROPERTIES* aProperties = NULL, PROJECT* aProject = nullptr ) override; + + const wxString GetFileExtension() const override; + + long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override + { + // No support for libraries.... + return 0; + } + + // ------------------------------------------------------ + + FABMASTER_PLUGIN(); + ~FABMASTER_PLUGIN(); + +private: + const PROPERTIES* m_props; + BOARD* m_board; + + FABMASTER m_fabmaster; +}; + +#endif // PCAD_PLUGIN_H_ diff --git a/pcbnew/plugins/fabmaster/import_fabmaster.cpp b/pcbnew/plugins/fabmaster/import_fabmaster.cpp new file mode 100644 index 0000000000..e79623b606 --- /dev/null +++ b/pcbnew/plugins/fabmaster/import_fabmaster.cpp @@ -0,0 +1,2662 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 BeagleBoard Foundation + * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors. + * Author: Seth Hillbrand + * + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "import_fabmaster.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +bool FABMASTER::Read( const std::string& aFile ) +{ + + std::ifstream ifs( aFile, std::ios::in | std::ios::binary ); + + if( !ifs.is_open() ) + return false; + + m_filename = aFile; + + std::string buffer( std::istreambuf_iterator{ ifs }, {} ); + std::vector < std::string > row; + std::string cell; + bool quoted = false; + + for( auto ch : buffer ) + { + switch( ch ) + { + case '"': + + if( cell.empty() || cell[0] == '"' ) + quoted = !quoted; + + cell += ch; + break; + + case '!': + if( !quoted ) + { + row.push_back( cell ); + cell.clear(); + } + else + cell += ch; + + break; + + case '\n': + + /// Rows end with "!" and we don't want to keep the empty cell + if( !cell.empty() ) + row.push_back( cell ); + + cell.clear(); + rows.push_back( row ); + row.clear(); + quoted = false; + break; + + case '\r': + break; + + default: + cell += std::toupper( ch ); + } + } + + // Handle last line without linebreak + if( !cell.empty() || !row.empty() ) + { + row.push_back( cell ); + cell.clear(); + rows.push_back( row ); + row.clear(); + } + + return true; +} + +FABMASTER::section_type FABMASTER::detectType( size_t aOffset ) +{ + single_row row; + try + { + row = rows.at( aOffset ); + } + catch( std::out_of_range& e ) + { + return UNKNOWN_EXTRACT; + } + + if( row.size() < 3 ) + return UNKNOWN_EXTRACT; + + std::string row1 = row[1]; + std::string row2 = row[2]; + std::string row3{}; + + /// We strip the underscores from all column names as some export variants use them and some do not + row1.erase( std::remove_if( row1.begin(), row1.end(), []( char c ){ return c == '_'; } ), + row1.end() ); + row2.erase( std::remove_if( row2.begin(), row2.end(), []( char c ){ return c == '_'; } ), + row2.end() ); + + if( row.size() > 3 ) + { + row3 = row[3]; + row3.erase( std::remove_if( row3.begin(), row3.end(), []( char c ){ return c == '_'; } ), + row3.end() ); + } + + if( row1 == "REFDES" && row2 == "COMPCLASS" ) + return EXTRACT_REFDES; + + if( row1 == "NETNAME" && row2 == "REFDES" ) + return EXTRACT_NETS; + + if( row1 == "CLASS" && row2 == "SUBCLASS" && row3.empty() ) + return EXTRACT_BASIC_LAYERS; + + if( row1 == "GRAPHICDATANAME" && row2 == "GRAPHICDATANUMBER" ) + return EXTRACT_GRAPHICS; + + if( row1 == "CLASS" && row2 == "SUBCLASS" && row3 == "GRAPHICDATANAME" ) + return EXTRACT_TRACES; + + if( row1 == "SYMNAME" && row2 == "PINNAME" ) + return FABMASTER_EXTRACT_PINS; + + if( row1 == "SYMNAME" && row2 == "SYMMIRROR" && row3 == "PINNAME" ) + return EXTRACT_PINS; + + if( row1 == "VIAX" && row2 == "VIAY" ) + return EXTRACT_VIAS; + + if( row1 == "SUBCLASS" && row2 == "PADSHAPENAME" ) + return EXTRACT_PAD_SHAPES; + + if( row1 == "PADNAME" ) + return EXTRACT_PADSTACKS; + + if( row1 == "LAYERSORT" ) + return EXTRACT_FULL_LAYERS; + + std::cout << "UNKNOWN Columns " << row1 << " : " << row2 << std::endl; + return UNKNOWN_EXTRACT; + +} + +double FABMASTER::processScaleFactor( size_t aRow ) +{ + double retval = 0.0; + + if( rows.size() < aRow ) + return -1.0; + + if( rows[aRow].size() < 11 ) + { + wxLogError( wxString::Format( _( "Invalid row size in J row %zu. " + "Expecting 11 elements but found %zu" ), aRow, rows[aRow].size() ) ); + return -1.0; + } + + for( int i = 7; i < 10 && retval < 1.0; ++i ) + { + auto units = rows[aRow][i]; + std::transform(units.begin(), units.end(),units.begin(), ::toupper); + + if( units == "MILS" ) + retval = IU_PER_MILS; + else if( units == "MILLIMETERS" ) + retval = IU_PER_MM; + else if( units == "MICRONS" ) + retval = IU_PER_MM * 10.0; + else if( units == "INCHES" ) + retval = IU_PER_MILS * 1000.0; + } + + if( retval < 1.0 ) + { + wxLogError( _( "Could not find units value, defaulting to Mils" ) ); + retval = IU_PER_MILS; + } + + return retval; +} + +int FABMASTER::getColFromName( size_t aRow, const std::string& aStr ) +{ + if( aRow >= rows.size() ) + return -1; + + auto header = rows[aRow]; + + for( size_t i = 0; i < header.size(); i++ ) + { + /// Some Fabmaster headers include the underscores while others do not + /// so we strip them uniformly before comparing + header[i].erase( std::remove_if( header[i].begin(), header[i].end(), + []( const char c ){ return c == '_'; } ), header[i].end() ); + + if( header[i] == aStr ) + return i; + } + + THROW_IO_ERROR( wxString::Format( _( "Could not find column label %s" ), aStr.c_str() ) ); + return -1; +} + + +PCB_LAYER_ID FABMASTER::getLayer( const std::string& aLayerName ) +{ + const auto& kicad_layer = layers.find( aLayerName); + + if( kicad_layer == layers.end() ) + return UNDEFINED_LAYER; + else + return static_cast( kicad_layer->second.layerid ); +} + + +size_t FABMASTER::processPadStackLayers( size_t aRow ) +{ + size_t rownum = aRow + 2; + + if( rows.size() < rownum ) + return -1; + + const auto header = rows[aRow]; + + int pad_name_col = getColFromName( aRow, "PADNAME" ); + int pad_num_col = getColFromName( aRow, "RECNUMBER" ); + int pad_lay_col = getColFromName( aRow, "LAYER" ); + int pad_fix_col = getColFromName( aRow, "FIXFLAG" ); + int pad_via_col = getColFromName( aRow, "VIAFLAG" ); + int pad_shape_col = getColFromName( aRow, "PADSHAPE1" ); + int pad_width_col = getColFromName( aRow, "PADWIDTH" ); + int pad_height_col = getColFromName( aRow, "PADHGHT" ); + int pad_xoff_col = getColFromName( aRow, "PADXOFF" ); + int pad_yoff_col = getColFromName( aRow, "PADYOFF" ); + int pad_flash_col = getColFromName( aRow, "PADFLASH" ); + int pad_shape_name_col = getColFromName( aRow, "PADSHAPENAME" ); + + for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum ) + { + auto row = rows[rownum]; + + if( row.size() != header.size() ) + { + wxLogError( wxString::Format( _( "Invalid row size in row %zu. " + "Expecting %zu elements but found %zu" ), rownum, header.size(), row.size() ) ); + continue; + } + + auto pad_name = row[pad_name_col]; + auto pad_num = row[pad_num_col]; + auto pad_layer = row[pad_lay_col]; + auto pad_is_fixed = row[pad_fix_col]; + auto pad_is_via = row[pad_via_col]; + auto pad_shape = row[pad_shape_col]; + auto pad_width = row[pad_width_col]; + auto pad_height = row[pad_height_col]; + auto pad_xoff = row[pad_xoff_col]; + auto pad_yoff = row[pad_yoff_col]; + auto pad_flash = row[pad_flash_col]; + auto pad_shapename = row[pad_shape_name_col]; + + // This layer setting seems to be unused + if( pad_layer == "INTERNAL_PAD_DEF" || pad_layer == "internal_pad_def" ) + continue; + + // Skip the technical layers + if( pad_layer[0] == '~' ) + break; + + auto result = layers.emplace( pad_layer, FABMASTER_LAYER{} ); + FABMASTER_LAYER& layer = result.first->second; + + /// If the layer ids have not yet been assigned + if( layer.id == 0 ) + { + layer.name = pad_layer; + layer.id = std::stoi( pad_num ); + layer.conductive = true; + } + } + + return 0; +} + + +/** + * A!PADNAME!RECNUMBER!LAYER!FIXFLAG!VIAFLAG!PADSHAPE1!PADWIDTH!PADHGHT! + * PADXOFF!PADYOFF!PADFLASH!PADSHAPENAME!TRELSHAPE1!TRELWIDTH!TRELHGHT! + * TRELXOFF!TRELYOFF!TRELFLASH!TRELSHAPENAME!APADSHAPE1!APADWIDTH!APADHGHT! + * APADXOFF!APADYOFF!APADFLASH!APADSHAPENAME! + */ +size_t FABMASTER::processPadStacks( size_t aRow ) +{ + size_t rownum = aRow + 2; + + if( rows.size() < rownum ) + return -1; + + const auto header = rows[aRow]; + double scale_factor = processScaleFactor( aRow + 1 ); + + if( scale_factor <= 0.0 ) + return -1; + + int pad_name_col = getColFromName( aRow, "PADNAME" ); + int pad_num_col = getColFromName( aRow, "RECNUMBER" ); + int pad_lay_col = getColFromName( aRow, "LAYER" ); + int pad_fix_col = getColFromName( aRow, "FIXFLAG" ); + int pad_via_col = getColFromName( aRow, "VIAFLAG" ); + int pad_shape_col = getColFromName( aRow, "PADSHAPE1" ); + int pad_width_col = getColFromName( aRow, "PADWIDTH" ); + int pad_height_col = getColFromName( aRow, "PADHGHT" ); + int pad_xoff_col = getColFromName( aRow, "PADXOFF" ); + int pad_yoff_col = getColFromName( aRow, "PADYOFF" ); + int pad_flash_col = getColFromName( aRow, "PADFLASH" ); + int pad_shape_name_col = getColFromName( aRow, "PADSHAPENAME" ); + + for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum ) + { + auto row = rows[rownum]; + FM_PAD* pad; + + if( row.size() != header.size() ) + { + wxLogError( wxString::Format( _( "Invalid row size in row %zu. " + "Expecting %zu elements but found %zu" ), rownum, header.size(), row.size() ) ); + continue; + } + + auto pad_name = row[pad_name_col]; + auto pad_num = row[pad_num_col]; + auto pad_layer = row[pad_lay_col]; + auto pad_is_fixed = row[pad_fix_col]; + auto pad_is_via = row[pad_via_col]; + auto pad_shape = row[pad_shape_col]; + auto pad_width = row[pad_width_col]; + auto pad_height = row[pad_height_col]; + auto pad_xoff = row[pad_xoff_col]; + auto pad_yoff = row[pad_yoff_col]; + auto pad_flash = row[pad_flash_col]; + auto pad_shapename = row[pad_shape_name_col]; + + // This layer setting seems to be unused + if( pad_layer == "INTERNAL_PAD_DEF" || pad_layer == "internal_pad_def" ) + continue; + + int recnum = KiROUND( stod( pad_num ) ); + + auto new_pad = pads.find( pad_name ); + + if( new_pad != pads.end() ) + pad = &new_pad->second; + else + { + pads[pad_name] = FM_PAD(); + pad = &pads[pad_name]; + pad->name = pad_name; + } + + /// Handle the drill layer + if( pad_layer == "~DRILL" ) + { + int drill_hit; + int drill_x; + int drill_y; + + try + { + drill_hit = KiROUND( std::fabs( stod( pad_shape ) * scale_factor ) ); + drill_x = KiROUND( std::fabs( stod( pad_width ) * scale_factor ) ); + drill_y = KiROUND( std::fabs( stod( pad_height ) * scale_factor ) ); + } + catch( ... ) + { + wxLogError( wxString::Format( _( "Expecting drill size value " + "but found %s!%s!%s at line %zu" ), + pad_shape.c_str(), pad_width.c_str(), pad_height.c_str(), rownum ) ); + continue; + } + + if( drill_hit == 0 ) + { + pad->drill = false; + continue; + } + + pad->drill = true; + + /// This is to account for broken fabmaster outputs where circle drill hits don't actually get the + /// drill hit value. + if( drill_x == drill_y ) + { + pad->drill_size_x = drill_hit; + pad->drill_size_y = drill_hit; + } + else + { + pad->drill_size_x = drill_x; + pad->drill_size_y = drill_y; + } + + if( !pad_shapename.empty() && pad_shapename[0] == 'P' ) + pad->plated = true; + + continue; + } + + if( pad_shape.empty() ) + continue; + + double w; + double h; + + try + { + w = std::stod( pad_width ) * scale_factor; + h = std::stod( pad_height ) * scale_factor; + } + catch( ... ) + { + wxLogError( wxString::Format( _( "Expecting pad size values " + "but found %s : %s at line %zu" ), pad_width.c_str(), pad_height.c_str(), rownum ) ); + continue; + } + + if( w <= 0.0 ) + continue; + + auto layer = layers.find( pad_layer ); + + if( layer != layers.end() ) + { + if( layer->second.layerid == F_Cu ) + pad->top = true; + else if( layer->second.layerid == B_Cu ) + pad->bottom = true; + } + + if( w > std::numeric_limits::max() || h > std::numeric_limits::max() ) + { + wxLogError( wxString::Format( _( "Invalid pad size on line %zu" ), rownum ) ); + continue; + } + + if( pad_layer == "~TSM" || pad_layer == "~BSM" ) + { + if( w > 0.0 && h > 0.0 ) + { + pad->mask_width = KiROUND( w ); + pad->mask_height = KiROUND( h ); + } + continue; + } + + if( pad_layer == "~TSP" || pad_layer == "~BSP" ) + { + if( w > 0.0 && h > 0.0 ) + { + pad->paste_width = KiROUND( w ); + pad->paste_height = KiROUND( h ); + } + continue; + } + + /// All remaining technical layers are not handled + if( pad_layer[0] == '~' ) + continue; + + try + { + pad->x_offset = KiROUND( std::stod( pad_xoff ) * scale_factor ); + pad->y_offset = -KiROUND( std::stod( pad_yoff ) * scale_factor ); + } + catch( ... ) + { + wxLogError( wxString::Format( _( "Expecting pad offset values " + "but found %s : %s at line %zu" ), pad_xoff.c_str(), pad_yoff.c_str(), rownum ) ); + continue; + } + + if( w > 0.0 && h > 0.0 && recnum == 1 ) + { + pad->width = KiROUND( w ); + pad->height = KiROUND( h ); + pad->via = ( std::toupper( pad_is_via[0] ) != 'V' ); + + if( pad_shape == "CIRCLE" ) + { + pad->height = pad->width; + pad->shape = PAD_SHAPE_CIRCLE; + } + else if( pad_shape == "RECTANGLE" ) + { + pad->shape = PAD_SHAPE_RECT; + } + else if( pad_shape == "SQUARE" ) + { + pad->shape = PAD_SHAPE_RECT; + pad->height = pad->width; + } + else if( pad_shape == "OBLONG" || pad_shape == "OBLONG_X" || pad_shape == "OBLONG_Y" ) + pad->shape = PAD_SHAPE_OVAL; + else if( pad_shape == "OCTAGON" ) + { + pad->shape = PAD_SHAPE_RECT; + pad->is_octogon = true; + } + else if( pad_shape == "SHAPE" ) + { + pad->shape = PAD_SHAPE_CUSTOM; + pad->custom_name = pad_shapename; + } + else + { + wxLogError( wxString::Format( _( "Unknown pad shape name %s at line %zu" ), + pad_shapename.c_str(), rownum ) ); + continue; + } + } + } + + return rownum - aRow; +} + + +size_t FABMASTER::processSimpleLayers( size_t aRow ) +{ + size_t rownum = aRow + 2; + + if( rows.size() < rownum ) + return 0; + + auto header = rows[aRow]; + double scale_factor = processScaleFactor( aRow + 1 ); + + if( scale_factor <= 0.0 ) + return 0; + + int layer_class_col = getColFromName( aRow, "CLASS" ); + int layer_subclass_col = getColFromName( aRow, "SUBCLASS" ); + + for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum ) + { + auto row = rows[rownum]; + + if( row.size() != header.size() ) + { + wxLogError( wxString::Format( _( "Invalid row size in row %zu. " + "Expecting %zu elements but found %zu" ), rownum, header.size(), row.size() ) ); + continue; + } + + auto result = layers.emplace( row[layer_subclass_col], FABMASTER_LAYER{} ); + FABMASTER_LAYER& layer = result.first->second; + + layer.name = row[layer_subclass_col]; + layer.positive = true; + layer.conductive = false; + + if( row[layer_class_col] == "ANTI ETCH" ) + { + layer.positive = false; + layer.conductive = true; + } + else if( row[layer_class_col] == "ETCH" ) + { + layer.conductive = true; + } + } + + return rownum - aRow; +} + + +bool FABMASTER::assignLayers() +{ + bool has_l1 = false; + int max_layer = 0; + std::string max_layer_name; + + std::vector> extra_layers + { + { "ASSEMBLY_TOP", F_Fab }, + { "ASSEMBLY_BOTTOM", B_Fab }, + { "PLACE_BOUND_TOP", F_CrtYd }, + { "PLACE_BOUND_BOTTOM", B_CrtYd }, + }; + + std::vector layer_order; + + for( auto& el : layers ) + { + FABMASTER_LAYER& layer = el.second; + layer.layerid = UNSELECTED_LAYER; + + if( layer.conductive ) + { + layer_order.push_back( &layer ); + } + else if( layer.name.find( "SILK" ) != std::string::npos && + layer.name.find( "AUTOSILK" ) == std::string::npos ) // Skip the autosilk layer + { + if( layer.name.find( "B" ) != std::string::npos ) + layer.layerid = B_SilkS; + else + layer.layerid = F_SilkS; + } + else if( layer.name.find( "MASK" ) != std::string::npos || + layer.name.find( "MSK" ) != std::string::npos ) + { + if( layer.name.find( "B" ) != std::string::npos ) + layer.layerid = B_Mask; + else + layer.layerid = F_Mask; + } + else if( layer.name.find( "PAST" ) != std::string::npos ) + { + if( layer.name.find( "B" ) != std::string::npos ) + layer.layerid = B_Paste; + else + layer.layerid = F_Paste; + } + else if( layer.name.find( "NCLEGEND" ) != std::string::npos ) + layer.layerid = Dwgs_User; + else + layer.disable = true; + } + + std::sort( layer_order.begin(), layer_order.end(), FABMASTER_LAYER::BY_ID() ); + int layernum = 0; + + for( auto layer : layer_order ) + layer->layerid = layernum++; + + /// Back copper has a special id number, so assign that to the last copper layer + /// in the stackup + layer_order.back()->layerid = B_Cu; + + for( auto& new_pair : extra_layers ) + { + FABMASTER_LAYER new_layer; + + new_layer.name = new_pair.first; + new_layer.layerid = new_pair.second; + new_layer.conductive = false; + + auto result = layers.emplace( new_pair.first, new_layer ); + + if( !result.second ) + { + result.first->second.layerid = new_pair.second; + result.first->second.disable = false; + } + } + + return true; +} + + +/** + * A!LAYER_SORT!LAYER_SUBCLASS!LAYER_ARTWORK!LAYER_USE!LAYER_CONDUCTOR!LAYER_DIELECTRIC_CONSTANT! + * LAYER_ELECTRICAL_CONDUCTIVITY!LAYER_MATERIAL!LAYER_SHIELD_LAYER!LAYER_THERMAL_CONDUCTIVITY! + * LAYER_THICKNESS! + */ +size_t FABMASTER::processLayers( size_t aRow ) +{ + size_t rownum = aRow + 2; + + if( rows.size() < rownum ) + return 0; + + auto header = rows[aRow]; + double scale_factor = processScaleFactor( aRow + 1 ); + + if( scale_factor <= 0.0 ) + return 0; + + int layer_sort_col = getColFromName( aRow, "LAYERSORT" ); + int layer_subclass_col = getColFromName( aRow, "LAYERSUBCLASS" ); + int layer_art_col = getColFromName( aRow, "LAYERARTWORK" ); + int layer_use_col = getColFromName( aRow, "LAYERUSE" ); + int layer_cond_col = getColFromName( aRow, "LAYERCONDUCTOR" ); + int layer_er_col = getColFromName( aRow, "LAYERDIELECTRICCONSTANT" ); + int layer_rho_col = getColFromName( aRow, "LAYERELECTRICALCONDUCTIVITY" ); + int layer_mat_col = getColFromName( aRow, "LAYERMATERIAL" ); + + for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum ) + { + auto row = rows[rownum]; + + if( row.size() != header.size() ) + { + wxLogError( wxString::Format( _( "Invalid row size in row %zu. " + "Expecting %zu elements but found %zu" ), rownum, header.size(), row.size() ) ); + continue; + } + + auto layer_sort = row[layer_sort_col]; + auto layer_subclass = row[layer_subclass_col]; + auto layer_art = row[layer_art_col]; + auto layer_use = row[layer_use_col]; + auto layer_cond = row[layer_cond_col]; + auto layer_er = row[layer_er_col]; + auto layer_rho = row[layer_rho_col]; + auto layer_mat = row[layer_mat_col]; + + if( layer_mat == "AIR" ) + continue; + + FABMASTER_LAYER layer; + + if( layer_subclass.empty() ) + { + if( layer_cond != "NO" ) + layer.name = "In.Cu" + layer_sort; + else + layer.name = "Dielectric" + layer_sort; + } + + layer.positive = ( layer_art != "NEGATIVE" ); + + layers.emplace( layer.name, layer ); + } + + return rownum - aRow; +} + + +/** + * A!SUBCLASS!PAD_SHAPE_NAME!GRAPHIC_DATA_NAME!GRAPHIC_DATA_NUMBER!RECORD_TAG!GRAPHIC_DATA_1! + * GRAPHIC_DATA_2!GRAPHIC_DATA_3!GRAPHIC_DATA_4!GRAPHIC_DATA_5!GRAPHIC_DATA_6!GRAPHIC_DATA_7! + * GRAPHIC_DATA_8!GRAPHIC_DATA_9!PAD_STACK_NAME!REFDES!PIN_NUMBER! + */ +size_t FABMASTER::processCustomPads( size_t aRow ) +{ + size_t rownum = aRow + 2; + size_t offset = 2; + + if( rows.size() < aRow + offset) + return 0; + + auto header = rows[aRow]; + double scale_factor = processScaleFactor( aRow + 1 ); + + if( scale_factor <= 0.0 ) + return 0; + + int pad_subclass_col = getColFromName( aRow, "SUBCLASS" ); + int pad_shape_name_col = getColFromName( aRow, "PADSHAPENAME" ); + int pad_grdata_name_col = getColFromName( aRow, "GRAPHICDATANAME" ); + int pad_grdata_num_col = getColFromName( aRow, "GRAPHICDATANUMBER" ); + int pad_record_tag_col = getColFromName( aRow, "RECORDTAG" ); + int pad_grdata1_col = getColFromName( aRow, "GRAPHICDATA1" ); + int pad_grdata2_col = getColFromName( aRow, "GRAPHICDATA2" ); + int pad_grdata3_col = getColFromName( aRow, "GRAPHICDATA3" ); + int pad_grdata4_col = getColFromName( aRow, "GRAPHICDATA4" ); + int pad_grdata5_col = getColFromName( aRow, "GRAPHICDATA5" ); + int pad_grdata6_col = getColFromName( aRow, "GRAPHICDATA6" ); + int pad_grdata7_col = getColFromName( aRow, "GRAPHICDATA7" ); + int pad_grdata8_col = getColFromName( aRow, "GRAPHICDATA8" ); + int pad_grdata9_col = getColFromName( aRow, "GRAPHICDATA9" ); + int pad_stack_name_col = getColFromName( aRow, "PADSTACKNAME" ); + int pad_refdes_col = getColFromName( aRow, "REFDES" ); + int pad_pin_num_col = getColFromName( aRow, "PINNUMBER" ); + + for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum ) + { + size_t rownum = aRow + offset; + auto row = rows[rownum]; + + if( row.size() != header.size() ) + { + wxLogError( wxString::Format( _( "Invalid row size in row %zu. " + "Expecting %zu elements but found %zu" ), rownum, header.size(), row.size() ) ); + + ++offset; + continue; + } + + auto pad_layer = row[pad_subclass_col]; + auto pad_shape_name = row[pad_shape_name_col]; + auto pad_record_tag = row[pad_record_tag_col]; + + GRAPHIC_DATA gr_data; + gr_data.graphic_dataname = row[pad_grdata_name_col]; + gr_data.graphic_datanum = row[pad_grdata_num_col]; + gr_data.graphic_data1 = row[pad_grdata1_col]; + gr_data.graphic_data2 = row[pad_grdata2_col]; + gr_data.graphic_data3 = row[pad_grdata3_col]; + gr_data.graphic_data4 = row[pad_grdata4_col]; + gr_data.graphic_data5 = row[pad_grdata5_col]; + gr_data.graphic_data6 = row[pad_grdata6_col]; + gr_data.graphic_data7 = row[pad_grdata7_col]; + gr_data.graphic_data8 = row[pad_grdata8_col]; + gr_data.graphic_data9 = row[pad_grdata9_col]; + + auto pad_stack_name = row[pad_stack_name_col]; + auto pad_refdes = row[pad_refdes_col]; + auto pad_pin_num = row[pad_pin_num_col]; + + // N.B. We get the FIGSHAPE records as "FIG_SHAPE name". We only want "name" + // and we don't process other pad shape records + std::string prefix( "FIG_SHAPE " ); + + if( !std::equal( prefix.begin(), prefix.end(), pad_shape_name.begin() ) ) + { + ++offset; + continue; + } + + // Custom pads are a series of records with the same record ID but incrementing + // Sequence numbers. + int id = -1; + int seq = -1; + + if( std::sscanf( pad_record_tag.c_str(), "%d %d", &id, &seq ) != 2 ) + { + wxLogError( wxString::Format( _( "Invalid format for id string \"%s\" " + "in custom pad row %zu" ), + pad_record_tag.c_str(), rownum ) ); + continue; + } + + auto name = pad_shape_name.substr( prefix.length() ); + auto ret = pad_shapes.emplace( name, PAD_SHAPE{} ); + + auto& custom_pad = ret.first->second; + + if( !ret.second ) + { + custom_pad.name = name; + custom_pad.padstack = pad_stack_name; + custom_pad.pinnum = pad_pin_num; + custom_pad.refdes = pad_refdes; + } + + // At this point we extract the individual graphical elements for processing the complex pad. The + // coordinates are in board origin format, so we'll need to fix the offset later when we assign them + // to the modules. + + auto gr_item = std::unique_ptr( processGraphic( gr_data, scale_factor ) ); + + if( gr_item ) + { + gr_item->layer = pad_layer; + gr_item->refdes = pad_refdes; + gr_item->seq = seq; + + /// emplace may fail here, in which case, it returns the correct position to use for the existing map + auto pad_it = custom_pad.elements.emplace( id, graphic_element{} ); + pad_it.first->second.emplace( std::move(gr_item ) ); + } + else + { + wxLogError( wxString::Format( _( "Unrecognized pad shape primitive \"%s\" in line %zu." ), + gr_data.graphic_dataname, rownum ) ); + } + } + + return rownum - aRow; +} + + +FABMASTER::GRAPHIC_LINE* FABMASTER::processLine( const FABMASTER::GRAPHIC_DATA& aData, double aScale ) +{ + GRAPHIC_LINE* new_line = new GRAPHIC_LINE ; + + new_line->shape = GR_SHAPE_LINE; + new_line->start_x = KiROUND( std::atof( aData.graphic_data1.c_str() ) * aScale ); + new_line->start_y = -KiROUND( std::atof( aData.graphic_data2.c_str() ) * aScale ); + new_line->end_x = KiROUND( std::atof( aData.graphic_data3.c_str() ) * aScale ); + new_line->end_y = -KiROUND( std::atof( aData.graphic_data4.c_str() ) * aScale ); + new_line->width = KiROUND( std::atof( aData.graphic_data5.c_str() ) * aScale ); + + return new_line; +} + +FABMASTER::GRAPHIC_ARC* FABMASTER::processArc( const FABMASTER::GRAPHIC_DATA& aData, double aScale ) +{ + GRAPHIC_ARC* new_arc = new GRAPHIC_ARC ; + + new_arc->shape = GR_SHAPE_ARC; + new_arc->start_x = KiROUND( std::atof( aData.graphic_data1.c_str() ) * aScale ); + new_arc->start_y = -KiROUND( std::atof( aData.graphic_data2.c_str() ) * aScale ); + new_arc->end_x = KiROUND( std::atof( aData.graphic_data3.c_str() ) * aScale ); + new_arc->end_y = -KiROUND( std::atof( aData.graphic_data4.c_str() ) * aScale ); + new_arc->center_x = KiROUND( std::atof( aData.graphic_data5.c_str() ) * aScale ); + new_arc->center_y = -KiROUND( std::atof( aData.graphic_data6.c_str() ) * aScale ); + new_arc->radius = KiROUND( std::atof( aData.graphic_data7.c_str() ) * aScale ); + new_arc->width = KiROUND( std::atof( aData.graphic_data8.c_str() ) * aScale ); + + new_arc->clockwise = ( aData.graphic_data9 != "COUNTERCLOCKWISE" ); + + double startangle = NormalizeAnglePos( RAD2DECIDEG( + atan2( new_arc->start_y - new_arc->center_y, + new_arc->start_x - new_arc->center_x ) ) ); + double endangle = NormalizeAnglePos( RAD2DECIDEG( + atan2( new_arc->end_y - new_arc->center_y, + new_arc->end_x - new_arc->center_x ) ) ); + double angle; + + VECTOR2I center( new_arc->center_x, new_arc->center_y ); + VECTOR2I start( new_arc->start_x, new_arc->start_y ); + VECTOR2I mid( new_arc->start_x, new_arc->start_y ); + VECTOR2I end( new_arc->end_x, new_arc->end_y ); + + angle = endangle - startangle; + + if( new_arc->clockwise && angle < 0.0 ) + angle += 3600.0; + if( !new_arc->clockwise && angle > 0.0 ) + angle -= 3600.0; + + if( start == end ) + angle = -3600.0; + + RotatePoint( mid, center, -angle / 2.0 ); + + new_arc->result = SHAPE_ARC( start, mid, end, 0 ); + + return new_arc; +} + +FABMASTER::GRAPHIC_RECTANGLE* FABMASTER::processRectangle( const FABMASTER::GRAPHIC_DATA& aData, double aScale ) +{ + GRAPHIC_RECTANGLE* new_rect = new GRAPHIC_RECTANGLE; + + new_rect->shape = GR_SHAPE_RECTANGLE; + new_rect->start_x = KiROUND( std::atof( aData.graphic_data1.c_str() ) * aScale ); + new_rect->start_y = -KiROUND( std::atof( aData.graphic_data2.c_str() ) * aScale ); + new_rect->end_x = KiROUND( std::atof( aData.graphic_data3.c_str() ) * aScale ); + new_rect->end_y = -KiROUND( std::atof( aData.graphic_data4.c_str() ) * aScale ); + new_rect->fill = aData.graphic_data5 == "1"; + new_rect->width = 0; + + return new_rect; +} + +FABMASTER::GRAPHIC_TEXT* FABMASTER::processText( const FABMASTER::GRAPHIC_DATA& aData, double aScale ) +{ + GRAPHIC_TEXT* new_text = new GRAPHIC_TEXT; + + new_text->shape = GR_SHAPE_TEXT; + new_text->start_x = KiROUND( std::atof( aData.graphic_data1.c_str() ) * aScale ); + new_text->start_y = -KiROUND( std::atof( aData.graphic_data2.c_str() ) * aScale ); + new_text->rotation = KiROUND( std::atof( aData.graphic_data3.c_str() ) ); + new_text->mirror = ( aData.graphic_data4 == "YES" ); + + if( aData.graphic_data5 == "RIGHT" ) + new_text->orient = GR_TEXT_HJUSTIFY_RIGHT; + else if( aData.graphic_data5 == "CENTER" ) + new_text->orient = GR_TEXT_HJUSTIFY_CENTER; + else + new_text->orient = GR_TEXT_HJUSTIFY_LEFT; + + std::vector toks = split( aData.graphic_data6, " \t" ); + + if( toks.size() < 8 ) + { + // We log the error here but continue in the case of too few tokens + wxLogError( wxString::Format( _( "Invalid token count." + " Expected 8 but found %zu" ), toks.size() ) ); + new_text->height = 0; + new_text->width = 0; + new_text->ital = false; + new_text->thickness = 0; + } + else + { + // 0 = size + // 1 = font + new_text->height = KiROUND( std::atof( toks[2].c_str() ) * aScale ); + new_text->width = KiROUND( std::atof( toks[3].c_str() ) * aScale ); + new_text->ital = std::atof( toks[4].c_str() ) != 0.0; + // 5 = character spacing + // 6 = line spacing + new_text->thickness = KiROUND( std::atof( toks[7].c_str() ) * aScale ); + } + + new_text->text = aData.graphic_data7; + return new_text; +} + + +FABMASTER::GRAPHIC_ITEM* FABMASTER::processGraphic( const GRAPHIC_DATA& aData, double aScale ) +{ + GRAPHIC_ITEM* retval = nullptr; + + if( aData.graphic_dataname == "LINE" ) + retval = processLine( aData, aScale ); + else if( aData.graphic_dataname == "ARC" ) + retval = processArc( aData, aScale ); + else if( aData.graphic_dataname == "RECTANGLE" ) + retval = processRectangle( aData, aScale ); + else if( aData.graphic_dataname == "TEXT" ) + retval = processText( aData, aScale ); + + if( retval && !aData.graphic_data10.empty() ) + { + if( aData.graphic_data10 == "CONNECT" ) + retval->type = GR_TYPE_CONNECT; + else if( aData.graphic_data10 == "NOTCONNECT" ) + retval->type = GR_TYPE_NOTCONNECT; + else if( aData.graphic_data10 == "SHAPE" ) + retval->type = GR_TYPE_NOTCONNECT; + else if( aData.graphic_data10 == "VOID" ) + retval->type = GR_TYPE_NOTCONNECT; + else if( aData.graphic_data10 == "POLYGON" ) + retval->type = GR_TYPE_NOTCONNECT; + else + retval->type = GR_TYPE_NONE; + } + + return retval; +} + + +/** + * A!GRAPHIC_DATA_NAME!GRAPHIC_DATA_NUMBER!RECORD_TAG!GRAPHIC_DATA_1!GRAPHIC_DATA_2!GRAPHIC_DATA_3! + * GRAPHIC_DATA_4!GRAPHIC_DATA_5!GRAPHIC_DATA_6!GRAPHIC_DATA_7!GRAPHIC_DATA_8!GRAPHIC_DATA_9! + * SUBCLASS!SYM_NAME!REFDES! + */ +size_t FABMASTER::processGeometry( size_t aRow ) +{ + size_t rownum = aRow + 2; + + if( rows.size() < rownum ) + return -1; + + const auto header = rows[aRow]; + double scale_factor = processScaleFactor( aRow + 1 ); + + if( scale_factor <= 0.0 ) + return -1; + + int geo_name_col = getColFromName( aRow, "GRAPHICDATANAME" ); + int geo_num_col = getColFromName( aRow, "GRAPHICDATANUMBER" ); + int geo_tag_col = getColFromName( aRow, "RECORDTAG" ); + int geo_grdata1_col = getColFromName( aRow, "GRAPHICDATA1" ); + int geo_grdata2_col = getColFromName( aRow, "GRAPHICDATA2" ); + int geo_grdata3_col = getColFromName( aRow, "GRAPHICDATA3" ); + int geo_grdata4_col = getColFromName( aRow, "GRAPHICDATA4" ); + int geo_grdata5_col = getColFromName( aRow, "GRAPHICDATA5" ); + int geo_grdata6_col = getColFromName( aRow, "GRAPHICDATA6" ); + int geo_grdata7_col = getColFromName( aRow, "GRAPHICDATA7" ); + int geo_grdata8_col = getColFromName( aRow, "GRAPHICDATA8" ); + int geo_grdata9_col = getColFromName( aRow, "GRAPHICDATA9" ); + int geo_subclass_col = getColFromName( aRow, "SUBCLASS" ); + int geo_sym_name_col = getColFromName( aRow, "SYMNAME" ); + int geo_refdes_col = getColFromName( aRow, "REFDES" ); + + for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum ) + { + auto row = rows[rownum]; + + if( row.size() != header.size() ) + { + wxLogError( wxString::Format( _( "Invalid row size in row %zu. " + "Expecting %zu elements but found %zu" ), rownum, header.size(), row.size() ) ); + continue; + } + + auto geo_tag = row[geo_tag_col]; + + GRAPHIC_DATA gr_data; + gr_data.graphic_dataname = row[geo_name_col]; + gr_data.graphic_datanum = row[geo_num_col]; + gr_data.graphic_data1 = row[geo_grdata1_col]; + gr_data.graphic_data2 = row[geo_grdata2_col]; + gr_data.graphic_data3 = row[geo_grdata3_col]; + gr_data.graphic_data4 = row[geo_grdata4_col]; + gr_data.graphic_data5 = row[geo_grdata5_col]; + gr_data.graphic_data6 = row[geo_grdata6_col]; + gr_data.graphic_data7 = row[geo_grdata7_col]; + gr_data.graphic_data8 = row[geo_grdata8_col]; + gr_data.graphic_data9 = row[geo_grdata9_col]; + + auto geo_refdes = row[geo_refdes_col]; + + // Grouped graphics are a series of records with the same record ID but incrementing + // Sequence numbers. + int id = -1; + int seq = -1; + int subseq = 0; + + if( std::sscanf( geo_tag.c_str(), "%d %d %d", &id, &seq, &subseq ) < 2 ) + { + wxLogError( wxString::Format( _( "Invalid format for record_tag string \"%s\" " + "in Geometric definition row %zu" ), + geo_tag.c_str(), rownum ) ); + continue; + } + + auto gr_item = std::unique_ptr( processGraphic( gr_data, scale_factor ) ); + + if( !gr_item ) + { + wxLogError( wxString::Format( _( "Invalid graphic item " + "in Geometric definition row %zu" ), + geo_tag.c_str(), rownum ) ); + continue; + } + + gr_item->layer = row[geo_subclass_col]; + gr_item->seq = seq; + gr_item->subseq = subseq; + + if( geo_refdes.empty() ) + { + if( board_graphics.empty() || board_graphics.back().id != id ) + { + GEOM_GRAPHIC new_gr; + new_gr.subclass = row[geo_subclass_col]; + new_gr.refdes = row[geo_refdes_col]; + new_gr.name = row[geo_sym_name_col]; + new_gr.id = id; + board_graphics.push_back( std::move( new_gr ) ); + } + + GEOM_GRAPHIC& graphic = board_graphics.back(); + graphic.elements.emplace( std::move( gr_item ) ); + } + else + { + auto sym_gr_it = comp_graphics.emplace( geo_refdes, + std::map{} ); + auto map_it = sym_gr_it.first->second.emplace( id, GEOM_GRAPHIC{} ); + auto& gr = map_it.first; + + if( map_it.second ) + { + gr->second.subclass = row[geo_subclass_col]; + gr->second.refdes = row[geo_refdes_col]; + gr->second.name = row[geo_sym_name_col]; + gr->second.id = id; + } + + auto result = gr->second.elements.emplace( std::move( gr_item ) ); + } + } + + return rownum - aRow; +} + + +/** + * A!VIA_X!VIA_Y!PAD_STACK_NAME!NET_NAME!TEST_POINT! + */ +size_t FABMASTER::processVias( size_t aRow ) +{ + size_t rownum = aRow + 2; + + if( rows.size() < rownum ) + return -1; + + const auto header = rows[aRow]; + double scale_factor = processScaleFactor( aRow + 1 ); + + if( scale_factor <= 0.0 ) + return -1; + + int viax_col = getColFromName( aRow, "VIAX" ); + int viay_col = getColFromName( aRow, "VIAY" ); + int padstack_name_col = getColFromName( aRow, "PADSTACKNAME" ); + int net_name_col = getColFromName( aRow, "NETNAME" ); + int test_point_col = getColFromName( aRow, "TESTPOINT" ); + + for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum ) + { + auto row = rows[rownum]; + + if( row.size() != header.size() ) + { + wxLogError( wxString::Format( _( "Invalid row size in row %zu. " + "Expecting %zu elements but found %zu" ), rownum, header.size(), row.size() ) ); + continue; + } + + vias.emplace_back( std::make_unique() ); + auto& via = vias.back(); + + via->x = KiROUND( std::stod( row[viax_col] ) * scale_factor ); + via->y = -KiROUND( std::stod( row[viay_col] ) * scale_factor ); + via->padstack = row[padstack_name_col]; + via->net = row[net_name_col]; + via->test_point = ( row[test_point_col] == "YES" ); + } + + return rownum - aRow; +} + + +/** + * A!CLASS!SUBCLASS!GRAPHIC_DATA_NAME!GRAPHIC_DATA_NUMBER!RECORD_TAG!GRAPHIC_DATA_1!GRAPHIC_DATA_2! + * GRAPHIC_DATA_3!GRAPHIC_DATA_4!GRAPHIC_DATA_5!GRAPHIC_DATA_6!GRAPHIC_DATA_7!GRAPHIC_DATA_8! + * GRAPHIC_DATA_9!NET_NAME! + */ +size_t FABMASTER::processTraces( size_t aRow ) +{ + size_t rownum = aRow + 2; + + if( rows.size() < rownum ) + return -1; + + const auto header = rows[aRow]; + double scale_factor = processScaleFactor( aRow + 1 ); + + if( scale_factor <= 0.0 ) + return -1; + + int class_col = getColFromName( aRow, "CLASS" ); + int layer_col = getColFromName( aRow, "SUBCLASS" ); + int grdata_name_col = getColFromName( aRow, "GRAPHICDATANAME" ); + int grdata_num_col = getColFromName( aRow, "GRAPHICDATANUMBER" ); + int tag_col = getColFromName( aRow, "RECORDTAG" ); + int grdata1_col = getColFromName( aRow, "GRAPHICDATA1" ); + int grdata2_col = getColFromName( aRow, "GRAPHICDATA2" ); + int grdata3_col = getColFromName( aRow, "GRAPHICDATA3" ); + int grdata4_col = getColFromName( aRow, "GRAPHICDATA4" ); + int grdata5_col = getColFromName( aRow, "GRAPHICDATA5" ); + int grdata6_col = getColFromName( aRow, "GRAPHICDATA6" ); + int grdata7_col = getColFromName( aRow, "GRAPHICDATA7" ); + int grdata8_col = getColFromName( aRow, "GRAPHICDATA8" ); + int grdata9_col = getColFromName( aRow, "GRAPHICDATA9" ); + int netname_col = getColFromName( aRow, "NETNAME" ); + + for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum ) + { + auto row = rows[rownum]; + + if( row.size() != header.size() ) + { + wxLogError( wxString::Format( _( "Invalid row size in row %zu. " + "Expecting %zu elements but found %zu" ), rownum, header.size(), row.size() ) ); + continue; + } + + GRAPHIC_DATA gr_data; + gr_data.graphic_dataname = row[grdata_name_col]; + gr_data.graphic_datanum = row[grdata_num_col]; + gr_data.graphic_data1 = row[grdata1_col]; + gr_data.graphic_data2 = row[grdata2_col]; + gr_data.graphic_data3 = row[grdata3_col]; + gr_data.graphic_data4 = row[grdata4_col]; + gr_data.graphic_data5 = row[grdata5_col]; + gr_data.graphic_data6 = row[grdata6_col]; + gr_data.graphic_data7 = row[grdata7_col]; + gr_data.graphic_data8 = row[grdata8_col]; + gr_data.graphic_data9 = row[grdata9_col]; + + auto geo_tag = row[tag_col]; + // Grouped graphics are a series of records with the same record ID but incrementing + // Sequence numbers. + int id = -1; + int seq = -1; + int subseq = 0; + + if( std::sscanf( geo_tag.c_str(), "%d %d %d", &id, &seq, &subseq ) < 2 ) + { + wxLogError( wxString::Format( _( "Invalid format for record_tag string \"%s\" " + "in Traces definition row %zu" ), + geo_tag.c_str(), rownum ) ); + continue; + } + + auto gr_item = std::unique_ptr( processGraphic( gr_data, scale_factor ) ); + + if( !gr_item ) + { + wxLogError( wxString::Format( _( "Invalid graphic item " + "in Traces definition row %zu" ), rownum ) ); + continue; + } + + auto new_trace = std::make_unique(); + new_trace->id = id; + new_trace->layer = row[layer_col]; + new_trace->netname = row[netname_col]; + new_trace->lclass = row[class_col]; + + gr_item->layer = row[layer_col]; + gr_item->seq = seq; + gr_item->subseq = subseq; + + if( gr_item->width == 0 ) + { + auto result = zones.emplace( std::move( new_trace ) ); + auto& zone = *result.first; + auto gr_result = zone->segment.emplace( std::move( gr_item ) ); + + if( !gr_result.second ) + { + wxLogError( wxString::Format( _( "Duplicate item for ID %d and sequence %d " + "in Traces definition row %zu \n" ), id, seq, rownum ) ); + } + + } + else + { + auto result = traces.emplace( std::move( new_trace ) ); + auto& trace = *result.first; + auto gr_result = trace->segment.emplace( std::move( gr_item ) ); + + if( !gr_result.second ) + { + wxLogError( wxString::Format( _( "Duplicate item for ID %d and sequence %d " + "in Traces definition row %zu \n" ), id, seq, rownum ) ); + } + } + } + + return rownum - aRow; +} + + +FABMASTER::SYMTYPE FABMASTER::parseSymType( const std::string& aSymType ) +{ + if( aSymType == "PACKAGE" ) + return SYMTYPE_PACKAGE; + else if( aSymType == "DRAFTING") + return SYMTYPE_DRAFTING; + else if( aSymType == "MECHANICAL" ) + return SYMTYPE_MECH; + else if( aSymType == "FORMAT" ) + return SYMTYPE_FORMAT; + + return SYMTYPE_NONE; +} + + +FABMASTER::COMPCLASS FABMASTER::parseCompClass( const std::string& aCmpClass ) +{ + if( aCmpClass == "IO" ) + return COMPCLASS_IO; + else if( aCmpClass == "IC" ) + return COMPCLASS_IC; + else if( aCmpClass == "DISCRETE" ) + return COMPCLASS_DISCRETE; + + return COMPCLASS_NONE; +} + +/** + * A!REFDES!COMP_CLASS!COMP_PART_NUMBER!COMP_HEIGHT!COMP_DEVICE_LABEL!COMP_INSERTION_CODE!SYM_TYPE! + * SYM_NAME!SYM_MIRROR!SYM_ROTATE!SYM_X!SYM_Y!COMP_VALUE!COMP_TOL!COMP_VOLTAGE! + */ +size_t FABMASTER::processFootprints( size_t aRow ) +{ + size_t rownum = aRow + 2; + + if( rows.size() < rownum ) + return -1; + + const auto header = rows[aRow]; + double scale_factor = processScaleFactor( aRow + 1 ); + + if( scale_factor <= 0.0 ) + return -1; + + int refdes_col = getColFromName( aRow, "REFDES" ); + int compclass_col = getColFromName( aRow, "COMPCLASS" ); + int comppartnum_col = getColFromName( aRow, "COMPPARTNUMBER" ); + int compheight_col = getColFromName( aRow, "COMPHEIGHT" ); + int compdevlabelcol = getColFromName( aRow, "COMPDEVICELABEL" ); + int compinscode_col = getColFromName( aRow, "COMPINSERTIONCODE" ); + int symtype_col = getColFromName( aRow, "SYMTYPE" ); + int symname_col = getColFromName( aRow, "SYMNAME" ); + int symmirror_col = getColFromName( aRow, "SYMMIRROR" ); + int symrotate_col = getColFromName( aRow, "SYMROTATE" ); + int symx_col = getColFromName( aRow, "SYMX" ); + int symy_col = getColFromName( aRow, "SYMY" ); + int compvalue_col = getColFromName( aRow, "COMPVALUE" ); + int comptol_col = getColFromName( aRow, "COMPTOL" ); + int compvolt_col = getColFromName( aRow, "COMPVOLTAGE" ); + + for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum ) + { + auto row = rows[rownum]; + + if( row.size() != header.size() ) + { + wxLogError( wxString::Format( _( "Invalid row size in row %zu. " + "Expecting %zu elements but found %zu" ), rownum, header.size(), row.size() ) ); + continue; + } + + auto cmp = std::make_unique(); + + cmp->refdes = row[refdes_col]; + cmp->cclass = parseCompClass( row[compclass_col] ); + cmp->pn = row[comppartnum_col]; + cmp->height = row[compheight_col]; + cmp->dev_label = row[compdevlabelcol]; + cmp->insert_code = row[compinscode_col]; + cmp->type = parseSymType( row[symtype_col] ); + cmp->name = row[symname_col]; + cmp->mirror = ( row[symmirror_col] == "YES" ); + cmp->rotate = std::stod( row[symrotate_col] ); + cmp->x = KiROUND( std::stod( row[symx_col] ) * scale_factor ); + cmp->y = -KiROUND( std::stod( row[symy_col] ) * scale_factor ); + cmp->value = row[compvalue_col]; + cmp->tol = row[comptol_col]; + cmp->voltage = row[compvolt_col]; + + auto vec = components.find( cmp->refdes ); + + if( vec == components.end() ) + { + auto retval = components.insert( std::make_pair( cmp->refdes, std::vector>{} ) ); + + vec = retval.first; + } + + vec->second.push_back( std::move( cmp ) ); + } + + return rownum - aRow; +} + + +/** + * A!SYM_NAME!SYM_MIRROR!PIN_NAME!PIN_NUMBER!PIN_X!PIN_Y!PAD_STACK_NAME!REFDES!PIN_ROTATION!TEST_POINT! + */ +size_t FABMASTER::processPins( size_t aRow ) +{ + size_t rownum = aRow + 2; + + if( rows.size() < rownum ) + return -1; + + const auto header = rows[aRow]; + double scale_factor = processScaleFactor( aRow + 1 ); + + if( scale_factor <= 0.0 ) + return -1; + + int symname_col = getColFromName( aRow, "SYMNAME" ); + int symmirror_col = getColFromName( aRow, "SYMMIRROR" ); + int pinname_col = getColFromName( aRow, "PINNAME" ); + int pinnum_col = getColFromName( aRow, "PINNUMBER" ); + int pinx_col = getColFromName( aRow, "PINX" ); + int piny_col = getColFromName( aRow, "PINY" ); + int padstack_col = getColFromName( aRow, "PADSTACKNAME" ); + int refdes_col = getColFromName( aRow, "REFDES" ); + int pinrot_col = getColFromName( aRow, "PINROTATION" ); + int testpoint_col = getColFromName( aRow, "TESTPOINT" ); + + for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum ) + { + auto row = rows[rownum]; + + if( row.size() != header.size() ) + { + wxLogError( wxString::Format( _( "Invalid row size in row %zu. " + "Expecting %zu elements but found %zu" ), rownum, header.size(), row.size() ) ); + continue; + } + + auto pin = std::make_unique(); + + pin->name = row[symname_col]; + pin->mirror = ( row[symmirror_col] == "YES" ); + pin->pin_name = row[pinname_col]; + pin->pin_number = row[pinnum_col]; + pin->pin_x = KiROUND( std::stod( row[pinx_col] ) * scale_factor ); + pin->pin_y = -KiROUND( std::stod( row[piny_col] ) * scale_factor ); + pin->padstack = row[padstack_col]; + pin->refdes = row[refdes_col]; + pin->rotation = std::stod( row[pinrot_col] ); + + auto map_it = pins.find( pin->refdes ); + + if( map_it == pins.end() ) + { + auto retval = pins.insert( std::make_pair( pin->refdes, std::set, PIN::BY_NUM>{} ) ); + map_it = retval.first; + } + + map_it->second.insert( std::move( pin ) ); + } + + return rownum - aRow; +} + + +/** + * A!NET_NAME!REFDES!PIN_NUMBER!PIN_NAME!PIN_GROUND!PIN_POWER! + */ +size_t FABMASTER::processNets( size_t aRow ) +{ + size_t rownum = aRow + 2; + + if( rows.size() < rownum ) + return -1; + + const auto header = rows[aRow]; + double scale_factor = processScaleFactor( aRow + 1 ); + + if( scale_factor <= 0.0 ) + return -1; + + int netname_col = getColFromName( aRow, "NETNAME" ); + int refdes_col = getColFromName( aRow, "REFDES" ); + int pinnum_col = getColFromName( aRow, "PINNUMBER" ); + int pinname_col = getColFromName( aRow, "PINNAME" ); + int pingnd_col = getColFromName( aRow, "PINGROUND" ); + int pinpwr_col = getColFromName( aRow, "PINPOWER" ); + + for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum ) + { + auto row = rows[rownum]; + + if( row.size() != header.size() ) + { + wxLogError( wxString::Format( _( "Invalid row size in row %zu. " + "Expecting %zu elements but found %zu" ), rownum, header.size(), row.size() ) ); + continue; + } + + NETNAME new_net; + new_net.name = row[netname_col]; + new_net.refdes = row[refdes_col]; + new_net.pin_num = row[pinnum_col]; + new_net.pin_name = row[pinname_col]; + new_net.pin_gnd = ( row[pingnd_col] == "YES" ); + new_net.pin_pwr = ( row[pinpwr_col] == "YES" ); + + pin_nets.emplace( std::make_pair( new_net.refdes, new_net.pin_num ), new_net ); + netnames.insert( row[netname_col] ); + } + + return rownum - aRow; +} + + +bool FABMASTER::Process() +{ + + for( size_t i = 0; i < rows.size(); ) + { + auto type = detectType( i ); + + switch( type ) + { + case EXTRACT_PADSTACKS: + { + /// We extract the basic layers from the padstacks first as this is the only place + /// the stackup is kept in the basic fabmaster export + processPadStackLayers( i ); + assignLayers(); + int retval = processPadStacks( i ); + + i += std::max( retval, 1 ); + break; + } + + case EXTRACT_FULL_LAYERS: + { + int retval = processLayers( i ); + + i += std::max( retval, 1 ); + break; + } + + case EXTRACT_BASIC_LAYERS: + { + int retval = processSimpleLayers( i ); + + i += std::max( retval, 1 ); + break; + } + + case EXTRACT_VIAS: + { + int retval = processVias( i ); + + i += std::max( retval, 1 ); + break; + } + + case EXTRACT_TRACES: + { + int retval = processTraces( i ); + + i += std::max( retval, 1 ); + break; + } + + case EXTRACT_REFDES: + { + int retval = processFootprints( i ); + + i += std::max( retval, 1 ); + break; + } + + case EXTRACT_NETS: + { + int retval = processNets( i ); + + i += std::max( retval, 1 ); + break; + } + + case EXTRACT_GRAPHICS: + { + int retval = processGeometry( i ); + + i += std::max( retval, 1 ); + break; + } + + case EXTRACT_PINS: + { + int retval = processPins( i ); + + i += std::max( retval, 1 ); + break; + } + + case EXTRACT_PAD_SHAPES: + { + int retval = processCustomPads( i ); + + i += std::max( retval, 1 ); + break; + } + + default: + ++i; + break; + } + + } + + return true; +} + + +bool FABMASTER::loadZones( BOARD* aBoard ) +{ + + for( auto& zone : zones ) + { + if( IsCopperLayer( getLayer( zone->layer ) ) ) + loadZone( aBoard, zone ); + else + { + if( zone->layer == "OUTLINE" ) + { + loadOutline( aBoard, zone ); + } + else + { + loadPolygon( aBoard, zone ); + } + } + } + + /** + * Zones in FABMASTER come in two varieties: + * - Outlines with no net code attached + * - Filled areas with net code attached + * + * In pcbnew, we want the outline with net code attached. To determine which + * outline should have which netcode, we look for overlapping areas. Each unnetted zone + * outline will be assigned the netcode that with the most hits on the edge of their + * outline. + */ + std::set zones_to_delete; + + for( auto zone : aBoard->Zones() ) + { + /// Remove the filled areas in favor of the outlines + if( zone->GetNetCode() > 0 ) + { + zones_to_delete.insert( zone ); + } + } + + for( auto zone1 : aBoard->Zones() ) + { + /// Zone1 will be the destination zone for the new net + if( zone1->GetNetCode() > 0 ) + continue; + + SHAPE_LINE_CHAIN& outline1 = zone1->Outline()->Outline( 0 ); + std::vector overlaps( aBoard->GetNetInfo().GetNetCount() + 1, 0 ); + std::vector> possible_deletions( overlaps.size() ); + + for( auto zone2 : aBoard->Zones() ) + { + if( zone2->GetNetCode() <= 0 ) + continue; + + SHAPE_LINE_CHAIN& outline2 = zone2->Outline()->Outline( 0 ); + + if( zone1->GetLayer() != zone2->GetLayer() ) + continue; + + if( !outline1.BBox().Intersects( outline2.BBox() ) ) + continue; + + for( auto& pt1 : outline1.CPoints() ) + { + if( !outline2.PointOnEdge( pt1, 1 ) ) + continue; + + /// We're looking for the netcode with the most overlaps to the un-netted zone + overlaps[ zone2->GetNetCode() ]++; + break; + } + } + + size_t max_net = 0; + size_t max_net_id = 0; + + for( size_t el = 1; el < overlaps.size(); ++el ) + { + if( overlaps[el] > max_net ) + { + max_net = overlaps[el]; + max_net_id = el; + } + } + + if( max_net > 0 ) + zone1->SetNetCode( max_net_id ); + } + + for( auto zone : zones_to_delete ) + { + aBoard->Remove( zone ); + free(zone); + } + + return true; +} + + +bool FABMASTER::loadFootprints( BOARD* aBoard ) +{ + const NETNAMES_MAP& netinfo = aBoard->GetNetInfo().NetsByName(); + const auto& ds = aBoard->GetDesignSettings(); + + for( auto& mod : components ) + { + bool has_multiple = mod.second.size() > 1; + + for( int i = 0; i < mod.second.size(); ++i ) + { + auto& src = mod.second[i]; + + FOOTPRINT* fp = new FOOTPRINT( aBoard ); + + wxString mod_ref = src->name; + wxString lib_ref = m_filename.GetName(); + + if( has_multiple ) + mod_ref.Append( wxString::Format( "_%d", i ) ); + + ReplaceIllegalFileNameChars( lib_ref, '_' ); + ReplaceIllegalFileNameChars( mod_ref, '_' ); + + wxString key = !lib_ref.empty() ? lib_ref + ":" + mod_ref : mod_ref; + + LIB_ID fpID; + fpID.Parse( key, true ); + fp->SetFPID( fpID ); + + fp->SetPosition( wxPoint( src->x, src->y ) ); + fp->SetOrientationDegrees( -src->rotate ); + + // KiCad netlisting requires parts to have non-digit + digit annotation. + // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator + wxString reference = src->refdes; + + if( !std::isalpha( src->refdes[0] ) ) + reference.Prepend( "UNK" ); + + fp->SetReference( reference ); + fp->SetValue( src->value ); + + /// Always set the module to the top and flip later if needed + /// When flipping later, we get the full coordinate transform for free + fp->SetLayer( F_Cu ); + + auto gr_it = comp_graphics.find( src->refdes ); + + if( gr_it == comp_graphics.end() ) + { + continue; + //TODO: Error + } + + for( auto& gr_ref : gr_it->second ) + { + auto& graphic = gr_ref.second; + + for( auto& seg : graphic.elements ) + { + PCB_LAYER_ID layer = Dwgs_User; + + if( IsValidLayer( getLayer( seg->layer ) ) ) + layer = getLayer( seg->layer ); + + switch( seg->shape ) + { + + case GR_SHAPE_LINE: + { + const GRAPHIC_LINE* lsrc = static_cast( seg.get() ); + + FP_SHAPE* line = new FP_SHAPE( fp, S_SEGMENT ); + + if( src->mirror ) + { + line->SetLayer( FlipLayer( layer ) ); + line->SetStart( wxPoint( lsrc->start_x, 2 * src->y - lsrc->start_y ) ); + line->SetEnd( wxPoint( lsrc->end_x, 2 * src->y - lsrc->end_y ) ); + } + else + { + line->SetLayer( layer ); + line->SetStart( wxPoint( lsrc->start_x, lsrc->start_y ) ); + line->SetEnd( wxPoint( lsrc->end_x, lsrc->end_y ) ); + } + + line->SetWidth( lsrc->width ); + line->SetLocalCoord(); + + if( lsrc->width == 0 ) + ds.GetLineThickness( line->GetLayer() ); + + fp->Add( line, ADD_MODE::APPEND ); + break; + } + case GR_SHAPE_ARC: + { + const GRAPHIC_ARC* lsrc = static_cast( seg.get() ); + + FP_SHAPE* arc = new FP_SHAPE( fp, S_ARC ); + + if( src->mirror ) + { + arc->SetLayer( FlipLayer( layer ) ); + arc->SetCenter( wxPoint( lsrc->center_x, 2 * src->y - lsrc->center_y ) ); + arc->SetArcStart( wxPoint( lsrc->end_x, 2 * src->y - lsrc->end_y ) ); + arc->SetAngle( lsrc->result.GetCentralAngle() * 10.0 ); + } + else + { + arc->SetLayer( layer ); + arc->SetCenter( wxPoint( lsrc->center_x, lsrc->center_y ) ); + arc->SetArcStart( wxPoint( lsrc->end_x, lsrc->end_y ) ); + arc->SetAngle( -lsrc->result.GetCentralAngle() * 10.0 ); + } + + arc->SetWidth( lsrc->width ); + arc->SetLocalCoord(); + + if( lsrc->width == 0 ) + ds.GetLineThickness( arc->GetLayer() ); + + fp->Add( arc, ADD_MODE::APPEND ); + break; + } + case GR_SHAPE_RECTANGLE: + { + const GRAPHIC_RECTANGLE *lsrc = + static_cast( seg.get() ); + + FP_SHAPE* rect = new FP_SHAPE( fp, S_RECT ); + + if( src->mirror ) + { + rect->SetLayer( FlipLayer( layer ) ); + rect->SetStart( wxPoint( lsrc->start_x, 2 * src->y - lsrc->start_y ) ); + rect->SetEnd( wxPoint( lsrc->end_x, 2 * src->y - lsrc->end_y ) ); + } + else + { + rect->SetLayer( layer ); + rect->SetStart( wxPoint( lsrc->start_x, lsrc->start_y ) ); + rect->SetEnd( wxPoint( lsrc->end_x, lsrc->end_y ) ); + } + + rect->SetWidth( 0 ); + rect->SetLocalCoord(); + + fp->Add( rect, ADD_MODE::APPEND ); + break; + } + case GR_SHAPE_TEXT: + { + const GRAPHIC_TEXT *lsrc = + static_cast( seg.get() ); + + FP_TEXT* txt = new FP_TEXT( fp ); + + if( src->mirror ) + { + txt->SetLayer( FlipLayer( layer ) ); + txt->SetTextPos( wxPoint( lsrc->start_x, 2 * src->y - ( lsrc->start_y - lsrc->height / 2 ) ) ); + } + else + { + txt->SetLayer( layer ); + txt->SetTextPos( wxPoint( lsrc->start_x, lsrc->start_y - lsrc->height / 2 ) ); + } + + txt->SetText( lsrc->text ); + txt->SetItalic( lsrc->ital ); + txt->SetTextThickness( lsrc->thickness ); + txt->SetTextHeight( lsrc->height ); + txt->SetTextWidth( lsrc->width ); + txt->SetHorizJustify( lsrc->orient ); + txt->SetLocalCoord(); + + fp->Add( txt, ADD_MODE::APPEND ); + break; + } + default: + continue; + } + } + } + + auto pin_it = pins.find( src->refdes ); + + if( pin_it != pins.end() ) + { + for( auto& pin : pin_it->second ) + { + auto pin_net_it = pin_nets.find( std::make_pair( pin->refdes, pin->pin_number ) ); + auto padstack = pads.find( pin->padstack ); + std::string netname = ""; + + if( pin_net_it != pin_nets.end() ) + netname = pin_net_it->second.name; + + auto net_it = netinfo.find( netname ); + + PAD* newpad = new PAD( fp ); + + if( net_it != netinfo.end() ) + newpad->SetNet( net_it->second ); + else + newpad->SetNetCode( 0 ); + + newpad->SetX( pin->pin_x ); + + if( src->mirror ) + newpad->SetY( 2 * src->y - pin->pin_y ); + else + newpad->SetY( pin->pin_y ); + + newpad->SetName( pin->pin_number ); + + if( padstack == pads.end() ) + { + ///TODO:Warning + free( newpad ); + continue; + } + else + { + auto& pad = padstack->second; + + newpad->SetShape( pad.shape ); + + if( pad.shape == PAD_SHAPE_CUSTOM ) + { + newpad->SetSize( wxSize( pad.width / 2, pad.height / 2 ) ); + + auto custom_it = pad_shapes.find( pad.custom_name ); + + if( custom_it != pad_shapes.end() ) + { + + SHAPE_POLY_SET poly_outline; + int last_subseq = 0; + int hole_idx = 0; + + poly_outline.NewOutline(); + + // Custom pad shapes have a group of elements + // that are a list of graphical polygons + for( const auto& el : (*custom_it).second.elements ) + { + for( const auto& seg : el.second ) + { + if( seg->subseq > 0 || seg->subseq != last_subseq ) + { + poly_outline.Polygon(0).back().SetClosed( true ); + hole_idx = poly_outline.AddHole( SHAPE_LINE_CHAIN{} ); + } + + if( seg->shape == GR_SHAPE_LINE ) + { + const GRAPHIC_LINE* src = static_cast( seg.get() ); + + if( poly_outline.VertexCount( 0, hole_idx ) == 0 ) + poly_outline.Append( src->start_x, src->start_y, 0, hole_idx ); + + poly_outline.Append( src->end_x, src->end_y, 0, hole_idx ); + } + else if( seg->shape == GR_SHAPE_ARC ) + { + const GRAPHIC_ARC* src = static_cast( seg.get() ); + SHAPE_LINE_CHAIN& chain = poly_outline.Hole( 0, hole_idx ); + + chain.Append( src->result ); + } + } + } + poly_outline.Fracture( SHAPE_POLY_SET::POLYGON_MODE::PM_FAST ); + + newpad->AddPrimitivePoly( poly_outline, 0, true ); + + } + } + else + newpad->SetSize( wxSize( pad.width, pad.height ) ); + + if( pad.drill ) + { + if( pad.plated ) + { + newpad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_PTH ); + newpad->SetLayerSet( PAD::PTHMask() ); + } + else + { + newpad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_NPTH ); + newpad->SetLayerSet( PAD::UnplatedHoleMask() ); + } + + if( pad.drill_size_x == pad.drill_size_y ) + newpad->SetDrillShape( PAD_DRILL_SHAPE_CIRCLE ); + else + newpad->SetDrillShape( PAD_DRILL_SHAPE_OBLONG ); + + newpad->SetDrillSize( wxSize( pad.drill_size_x, pad.drill_size_y ) ); + } + else + { + newpad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_SMD ); + + if( pad.top ) + newpad->SetLayerSet( PAD::SMDMask() ); + else if( pad.bottom ) + newpad->SetLayerSet( FlipLayerMask( PAD::SMDMask() ) ); + } + } + + newpad->SetLocalCoord(); + + if( src->mirror ) + newpad->SetOrientation( ( -src->rotate + pin->rotation ) * 10.0 ); + else + newpad->SetOrientation( ( src->rotate - pin->rotation ) * 10.0 ); + + fp->Add( newpad, ADD_MODE::APPEND ); + } + } + + if( src->mirror ) + { + fp->SetOrientationDegrees( 180.0 - src->rotate ); + fp->Flip( fp->GetPosition(), true ); + } + + fp->BuildPolyCourtyards(); + + aBoard->Add( fp, ADD_MODE::APPEND ); + } + } + + return true; +} + + +bool FABMASTER::loadLayers( BOARD* aBoard ) +{ + LSET layer_set; + + /// The basic layers that get enabled for normal boards + layer_set |= LSET::AllTechMask() | LSET::UserMask(); + + for( auto& layer : layers ) + { + if( layer.second.layerid >= PCBNEW_LAYER_ID_START ) + layer_set.set( layer.second.layerid ); + } + + aBoard->SetEnabledLayers( layer_set ); + + for( auto& layer : layers ) + { + if( layer.second.conductive ) + { + aBoard->SetLayerName( static_cast( layer.second.layerid ), + layer.second.name ); + } + } + + return true; +} + + +bool FABMASTER::loadVias( BOARD* aBoard ) +{ + const NETNAMES_MAP& netinfo = aBoard->GetNetInfo().NetsByName(); + const auto& ds = aBoard->GetDesignSettings(); + + for( auto& via : vias ) + { + auto net_it = netinfo.find( via->net ); + auto padstack = pads.find( via->padstack ); + + VIA* new_via = new VIA( aBoard ); + + new_via->SetPosition( wxPoint( via->x, via->y ) ); + + if( net_it != netinfo.end() ) + new_via->SetNet( net_it->second ); + + if( padstack == pads.end() ) + { + new_via->SetDrillDefault(); + + if( !ds.m_ViasDimensionsList.empty() ) + { + new_via->SetWidth( ds.m_ViasDimensionsList[0].m_Diameter ); + new_via->SetDrill( ds.m_ViasDimensionsList[0].m_Drill ); + } + else + { + new_via->SetDrillDefault(); + new_via->SetWidth( ds.m_ViasMinSize ); + } + } + else + { + new_via->SetDrill( padstack->second.drill_size_x ); + new_via->SetWidth( padstack->second.width ); + } + + aBoard->Add( new_via, ADD_MODE::APPEND ); + } + + return true; +} + + +bool FABMASTER::loadNets( BOARD* aBoard ) +{ + for( auto& net : netnames ) + { + NETINFO_ITEM *newnet = new NETINFO_ITEM( aBoard, net ); + aBoard->Add( newnet, ADD_MODE::APPEND ); + } + + return true; +} + + +bool FABMASTER::loadEtch( BOARD* aBoard, const std::unique_ptr& aLine) +{ + const NETNAMES_MAP& netinfo = aBoard->GetNetInfo().NetsByName(); + auto net_it = netinfo.find( aLine->netname ); + + int last_subseq = 0; + ZONE* new_zone = nullptr; + + for( const auto& seg : aLine->segment ) + { + PCB_LAYER_ID layer = getLayer( seg->layer ); + + if( IsCopperLayer( layer ) ) + { + if( seg->shape == GR_SHAPE_LINE ) + { + const GRAPHIC_LINE* src = static_cast( seg.get() ); + + TRACK* trk = new TRACK( aBoard ); + + trk->SetLayer( layer ); + trk->SetStart( wxPoint( src->start_x, src->start_y ) ); + trk->SetEnd( wxPoint( src->end_x, src->end_y ) ); + trk->SetWidth( src->width ); + + if( net_it != netinfo.end() ) + trk->SetNet( net_it->second ); + + aBoard->Add( trk, ADD_MODE::APPEND ); + } + else if( seg->shape == GR_SHAPE_ARC ) + { + const GRAPHIC_ARC* src = static_cast( seg.get() ); + + ARC* trk = new ARC( aBoard, &src->result ); + trk->SetLayer( layer ); + trk->SetWidth( src->width ); + + if( net_it != netinfo.end() ) + trk->SetNet( net_it->second ); + + aBoard->Add( trk, ADD_MODE::APPEND ); + } + } + else + { + wxLogError( wxString::Format( _( "Expecting etch data to be on copper layer. " + "Row found on layer '%s'" ), seg->layer.c_str() ) ); + } + } + + return true; +} + + +bool FABMASTER::loadPolygon( BOARD* aBoard, const std::unique_ptr& aLine) +{ + if( aLine->segment.size() < 3 ) + return false; + + PCB_LAYER_ID layer = Cmts_User; + int last_subseq = 0; + int hole_idx = -1; + SHAPE_POLY_SET poly_outline; + PCB_SHAPE* new_poly = new PCB_SHAPE( aBoard ); + + new_poly->SetShape( S_POLYGON ); + poly_outline.NewOutline(); + + auto new_layer = getLayer( aLine->layer ); + + if( IsValidLayer( new_layer ) ) + layer = new_layer; + + for( const auto& seg : aLine->segment ) + { + if( seg->subseq > 0 || seg->subseq != last_subseq ) + hole_idx = poly_outline.AddHole( SHAPE_LINE_CHAIN{} ); + + if( seg->shape == GR_SHAPE_LINE ) + { + const GRAPHIC_LINE* src = static_cast( seg.get() ); + + if( poly_outline.VertexCount( 0, hole_idx ) == 0 ) + poly_outline.Append( src->start_x, src->start_y, 0, hole_idx ); + + poly_outline.Append( src->end_x, src->end_y, 0, hole_idx ); + } + else if( seg->shape == GR_SHAPE_ARC ) + { + const GRAPHIC_ARC* src = static_cast( seg.get() ); + SHAPE_LINE_CHAIN& chain = poly_outline.Hole( 0, hole_idx ); + + chain.Append( src->result ); + } + } + + poly_outline.Fracture( SHAPE_POLY_SET::POLYGON_MODE::PM_FAST ); + + new_poly->SetLayer( layer ); + new_poly->SetPolyShape( poly_outline ); + aBoard->Add( new_poly, ADD_MODE::APPEND ); + + return true; + +} + + +bool FABMASTER::loadZone( BOARD* aBoard, const std::unique_ptr& aLine) +{ + if( aLine->segment.size() < 3 ) + return false; + + int last_subseq = 0; + int hole_idx = -1; + SHAPE_POLY_SET* zone_outline = nullptr; + ZONE* zone = nullptr; + + const NETNAMES_MAP& netinfo = aBoard->GetNetInfo().NetsByName(); + auto net_it = netinfo.find( aLine->netname ); + PCB_LAYER_ID layer = Cmts_User; + auto new_layer = getLayer( aLine->layer ); + + if( IsValidLayer( new_layer ) ) + layer = new_layer; + + auto store_zone = [&]() + { + if( zone && zone_outline ) + { + if( zone_outline->Outline( 0 ).PointCount() >= 3 ) + { + zone->SetOutline( zone_outline ); + aBoard->Add( zone, ADD_MODE::APPEND ); + } + else + { + delete( zone_outline ); + delete( zone ); + } + } + }; + + auto new_zone = [&]() + { + + zone = new ZONE( aBoard ); + zone_outline = new SHAPE_POLY_SET; + + if( net_it != netinfo.end() ) + zone->SetNet( net_it->second ); + + zone->SetLayer( layer ); + + zone->SetIsRuleArea( false ); + zone->SetDoNotAllowTracks( false ); + zone->SetDoNotAllowVias( false ); + zone->SetDoNotAllowPads( false ); + zone->SetDoNotAllowFootprints( false ); + zone->SetDoNotAllowCopperPour( false ); + + zone->SetPriority( 50 ); + zone->SetLocalClearance( 0 ); + zone->SetPadConnection( ZONE_CONNECTION::FULL ); + + zone_outline->NewOutline(); + }; + + new_zone(); + + for( const auto& seg : aLine->segment ) + { + if( seg->subseq > 0 && seg->subseq != last_subseq ) + { + /// Don't knock holes in the BOUNDARY systems. These are the outer layers for zone fills. + if( aLine->lclass == "BOUNDARY" ) + break; + + hole_idx = zone_outline->AddHole( SHAPE_LINE_CHAIN{} ); + last_subseq = seg->subseq; + last_subseq = seg->subseq; + } + + if( seg->shape == GR_SHAPE_LINE ) + { + const GRAPHIC_LINE* src = static_cast( seg.get() ); + + if( zone_outline->VertexCount( 0, hole_idx ) == 0 ) + zone_outline->Append( src->start_x, src->start_y, 0, hole_idx ); + + zone_outline->Append( src->end_x, src->end_y, 0, hole_idx ); + } + else if( seg->shape == GR_SHAPE_ARC ) + { + const GRAPHIC_ARC* src = static_cast( seg.get() ); + zone_outline->Hole( 0, hole_idx ).Append( src->result ); + } + } + + store_zone(); + + return true; +} + + +bool FABMASTER::loadOutline( BOARD* aBoard, const std::unique_ptr& aLine) +{ + PCB_LAYER_ID layer; + + if( aLine->lclass == "BOARD GEOMETRY" ) + layer = Edge_Cuts; + else if( aLine->lclass == "DRAWING FORMAT" ) + layer = Dwgs_User; + else + layer = Cmts_User; + + for( auto& seg : aLine->segment ) + { + switch( seg->shape ) + { + + case GR_SHAPE_LINE: + { + const GRAPHIC_LINE* src = static_cast( seg.get() ); + + PCB_SHAPE* line = new PCB_SHAPE( aBoard ); + line->SetShape( S_SEGMENT ); + line->SetLayer( layer ); + line->SetStart( wxPoint( src->start_x, src->start_y ) ); + line->SetEnd( wxPoint( src->end_x, src->end_y ) ); + line->SetWidth( src->width ); + + aBoard->Add( line, ADD_MODE::APPEND ); + break; + } + case GR_SHAPE_ARC: + { + const GRAPHIC_ARC* src = static_cast( seg.get() ); + + PCB_SHAPE* arc = new PCB_SHAPE( aBoard ); + arc->SetShape( S_ARC ); + arc->SetLayer( layer ); + arc->SetCenter( wxPoint( src->center_x, src->center_y ) ); + arc->SetArcStart( wxPoint( src->start_x, src->start_y ) ); + arc->SetAngle( src->result.GetCentralAngle() * 10.0 ); + arc->SetWidth( src->width ); + + aBoard->Add( arc, ADD_MODE::APPEND ); + break; + } + case GR_SHAPE_RECTANGLE: + { + const GRAPHIC_RECTANGLE *src = + static_cast( seg.get() ); + + PCB_SHAPE* rect = new PCB_SHAPE( aBoard ); + rect->SetShape( S_RECT ); + rect->SetLayer( layer ); + rect->SetStart( wxPoint( src->start_x, src->start_y ) ); + rect->SetEnd( wxPoint( src->end_x, src->end_y ) ); + rect->SetWidth( 0 ); + aBoard->Add( rect, ADD_MODE::APPEND ); + break; + } + case GR_SHAPE_TEXT: + { + const GRAPHIC_TEXT *src = + static_cast( seg.get() ); + + PCB_TEXT* txt = new PCB_TEXT( aBoard ); + txt->SetLayer( layer ); + txt->SetTextPos( wxPoint( src->start_x, src->start_y - src->height / 2 ) ); + txt->SetText( src->text ); + txt->SetItalic( src->ital ); + txt->SetTextThickness( src->thickness ); + txt->SetTextHeight( src->height ); + txt->SetTextWidth( src->width ); + txt->SetHorizJustify( src->orient ); + aBoard->Add( txt, ADD_MODE::APPEND ); + break; + } + default: + return false; + } + } + + return true; +} + + +bool FABMASTER::loadGraphics( BOARD* aBoard ) +{ + + for( auto& geom : board_graphics ) + { + PCB_LAYER_ID layer; + + if( geom.subclass == "SILKSCREEN_TOP" ) + layer = F_SilkS; + else if( geom.subclass == "SILKSCREEN_BOTTOM" ) + layer = B_SilkS; + // The pin numbers are not useful for us outside of the footprints + else if( geom.subclass == "PIN_NUMBER" ) + continue; + else + layer = Cmts_User; + + for( auto& seg : geom.elements ) + { + switch( seg->shape ) + { + + case GR_SHAPE_LINE: + { + const GRAPHIC_LINE* src = static_cast( seg.get() ); + + PCB_SHAPE* line = new PCB_SHAPE( aBoard ); + line->SetShape( S_SEGMENT ); + line->SetLayer( layer ); + line->SetStart( wxPoint( src->start_x, src->start_y ) ); + line->SetEnd( wxPoint( src->end_x, src->end_y ) ); + line->SetWidth( src->width ); + + aBoard->Add( line, ADD_MODE::APPEND ); + break; + } + case GR_SHAPE_ARC: + { + const GRAPHIC_ARC* src = static_cast( seg.get() ); + + PCB_SHAPE* arc = new PCB_SHAPE( aBoard ); + arc->SetShape( S_ARC ); + arc->SetLayer( layer ); + arc->SetCenter( wxPoint( src->center_x, src->center_y ) ); + arc->SetArcStart( wxPoint( src->start_x, src->start_y ) ); + arc->SetAngle( src->result.GetCentralAngle() * 10.0 ); + arc->SetWidth( src->width ); + + aBoard->Add( arc, ADD_MODE::APPEND ); + break; + } + case GR_SHAPE_RECTANGLE: + { + const GRAPHIC_RECTANGLE *src = + static_cast( seg.get() ); + + PCB_SHAPE* rect = new PCB_SHAPE( aBoard ); + rect->SetShape( S_RECT ); + rect->SetLayer( layer ); + rect->SetStart( wxPoint( src->start_x, src->start_y ) ); + rect->SetEnd( wxPoint( src->end_x, src->end_y ) ); + rect->SetWidth( 0 ); + aBoard->Add( rect, ADD_MODE::APPEND ); + break; + } + case GR_SHAPE_TEXT: + { + const GRAPHIC_TEXT *src = + static_cast( seg.get() ); + + PCB_TEXT* txt = new PCB_TEXT( aBoard ); + txt->SetLayer( layer ); + txt->SetTextPos( wxPoint( src->start_x, src->start_y - src->height / 2 ) ); + txt->SetText( src->text ); + txt->SetItalic( src->ital ); + txt->SetTextThickness( src->thickness ); + txt->SetTextHeight( src->height ); + txt->SetTextWidth( src->width ); + txt->SetHorizJustify( src->orient ); + aBoard->Add( txt, ADD_MODE::APPEND ); + break; + } + default: + return false; + } + } + } + + return true; + +} + + +bool FABMASTER::orderZones( BOARD* aBoard ) +{ + std::vector zones = aBoard->Zones(); + + std::sort( zones.begin(), zones.end(), + [&]( const ZONE* a, const ZONE* b ) { + if( a->GetLayer() == b->GetLayer() ) + return a->GetBoundingBox().GetArea() > b->GetBoundingBox().GetArea(); + + return a->GetLayer() < b->GetLayer(); + } ); + + PCB_LAYER_ID layer = UNDEFINED_LAYER; + unsigned int priority = 0; + + for( ZONE* zone : zones ) + { + if( zone->GetLayer() != layer ) + { + layer = zone->GetLayer(); + priority = 0; + } + + zone->SetPriority( priority ); + priority += 10; + } + + return true; +} + + +bool FABMASTER::LoadBoard( BOARD* aBoard ) +{ + aBoard->SetFileName( m_filename.GetFullPath() ); + + loadNets( aBoard ); + loadLayers( aBoard ); + loadVias( aBoard ); + loadFootprints( aBoard ); + loadZones( aBoard ); + loadGraphics( aBoard ); + + for( auto& track : traces ) + { + if( track->lclass == "ETCH" ) + { + loadEtch( aBoard, track); + } + else if( track->layer == "OUTLINE" ) + { + loadOutline( aBoard, track ); + } + } + + orderZones( aBoard ); + + return true; +} diff --git a/pcbnew/plugins/fabmaster/import_fabmaster.h b/pcbnew/plugins/fabmaster/import_fabmaster.h new file mode 100644 index 0000000000..6f8f57afbd --- /dev/null +++ b/pcbnew/plugins/fabmaster/import_fabmaster.h @@ -0,0 +1,562 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * Author: Seth Hillbrand + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef PCBNEW_IMPORTERS_IMPORT_FABMASTER_H_ +#define PCBNEW_IMPORTERS_IMPORT_FABMASTER_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +enum PCB_LAYER_ID : int; +class BOARD; + +class FABMASTER +{ +public: + + using single_row = std::vector; + FABMASTER() : + has_pads( false ), has_comps( false ), has_graphic( false ), + has_nets( false ), has_pins( false ) + {} + + bool Read( const std::string& aFile ); + + bool Process(); + + bool LoadBoard( BOARD* aBoard ); + +private: + + wxFileName m_filename; + + enum section_type : int + { + UNKNOWN_EXTRACT, + EXTRACT_PADSTACKS, + EXTRACT_PAD_SHAPES, + EXTRACT_FULL_LAYERS, + EXTRACT_VIAS, + FABMASTER_EXTRACT_PINS, + EXTRACT_PINS, + EXTRACT_TRACES, + EXTRACT_GRAPHICS, + EXTRACT_BASIC_LAYERS, + EXTRACT_NETS, + EXTRACT_REFDES + }; + + std::deque rows; + + bool has_pads; + bool has_comps; + bool has_graphic; + bool has_nets; + bool has_pins; + + + struct FM_PAD + { + std::string name; + bool fixed; + bool via; + PAD_SHAPE_T shape; + std::string custom_name; + bool top; + bool bottom; + bool paste; + bool mask; + bool drill; + bool plated; + bool is_octogon; + int drill_size_x; + int drill_size_y; + int width; + int height; + int mask_width; + int mask_height; + int paste_width; + int paste_height; + int x_offset; + int y_offset; + int antipad_size; + + struct HASH + { + std::size_t operator()( const FM_PAD& aPad ) const + { + return std::hash{}( aPad.name ); + } + }; + }; + + std::unordered_map pads; + + enum COMPCLASS + { + COMPCLASS_NONE, + COMPCLASS_IO, + COMPCLASS_IC, + COMPCLASS_DISCRETE + }; + + enum SYMTYPE + { + SYMTYPE_NONE, + SYMTYPE_PACKAGE, + SYMTYPE_MECH, + SYMTYPE_FORMAT, + SYMTYPE_DRAFTING + }; + + // A!NET_NAME!REFDES!PIN_NUMBER!PIN_NAME!PIN_GROUND!PIN_POWER! + struct NETNAME + { + std::string name; ///!< NET_NAME + std::string refdes; ///!< REFDES + std::string pin_num; ///!< PIN_NUMBER + std::string pin_name; ///!< PIN_NAME + bool pin_gnd; ///!< PIN_GND + bool pin_pwr; ///!< PIN_PWR + + struct LESS + { + bool operator()(const NETNAME& lhs, const NETNAME& rhs) const + { + if( lhs.refdes == rhs.refdes ) + return lhs.pin_num < rhs.pin_num; + + return lhs.refdes < rhs.refdes; + } + }; + }; + + std::map, NETNAME> pin_nets; + std::set netnames; + + struct CLASS + { + std::string name; ///!< CLASS + std::string subclass; ///!< SUBCLASS + }; + + + enum GRAPHIC_SHAPE + { + GR_SHAPE_LINE, + GR_SHAPE_TEXT, + GR_SHAPE_RECTANGLE, + GR_SHAPE_ARC + }; + + enum GRAPHIC_TYPE + { + GR_TYPE_NONE, + GR_TYPE_CONNECT, + GR_TYPE_NOTCONNECT, + GR_TYPE_SHAPE, + GR_TYPE_POLYGON, + GR_TYPE_VOID + }; + + struct GRAPHIC_ITEM + { + int start_x; ///& lhs, + const std::unique_ptr& rhs) const + { + return lhs->seq < rhs->seq; + } + }; + }; + + struct GRAPHIC_LINE : public GRAPHIC_ITEM + { + int end_x; ///, GRAPHIC_ITEM::SEQ_CMP>; + + /// A!LAYER_SORT!LAYER_SUBCLASS!LAYER_ARTWORK!LAYER_USE!LAYER_CONDUCTOR!LAYER_DIELECTRIC_CONSTANT + /// !LAYER_ELECTRICAL_CONDUCTIVITY!LAYER_MATERIAL!LAYER_SHIELD_LAYER!LAYER_THERMAL_CONDUCTIVITY!LAYER_THICKNESS! + struct FABMASTER_LAYER + { + int id; ///id < rhs->id; + } + }; + }; + + std::map layers; + + /** + * A!SUBCLASS!PAD_SHAPE_NAME!GRAPHIC_DATA_NAME!GRAPHIC_DATA_NUMBER!RECORD_TAG!GRAPHIC_DATA_1! + * GRAPHIC_DATA_2!GRAPHIC_DATA_3!GRAPHIC_DATA_4!GRAPHIC_DATA_5!GRAPHIC_DATA_6!GRAPHIC_DATA_7! + * GRAPHIC_DATA_8!GRAPHIC_DATA_9!PAD_STACK_NAME!REFDES!PIN_NUMBER! + */ + struct PAD_SHAPE + { + std::string name; /// elements; + + struct HASH + { + std::size_t operator()( const std::unique_ptr& aPad ) const + { + return std::hash{}( aPad->name ); + } + }; + }; + + std::unordered_map pad_shapes; + + // * A!SYM_TYPE!SYM_NAME!REFDES!SYM_X!SYM_Y!SYM_ROTATE!SYM_MIRROR!NET_NAME!CLASS!SUBCLASS!RECORD_TAG! + // * GRAPHIC_DATA_NAME!GRAPHIC_DATA_NUMBER!GRAPHIC_DATA_1!GRAPHIC_DATA_2!GRAPHIC_DATA_3!GRAPHIC_DATA_4! + // * GRAPHIC_DATA_5!GRAPHIC_DATA_6!GRAPHIC_DATA_7!GRAPHIC_DATA_8!GRAPHIC_DATA_9!GRAPHIC_DATA_10!COMP_DEVICE_TYPE! + // * COMP_PACKAGE!COMP_PART_NUMBER!COMP_VALUE!CLIP_DRAWING! + struct SYMBOL + { + int sym_id; /// elements; + + struct HASH + { + std::size_t operator()( const FABMASTER::SYMBOL& aSym ) const + { + return std::hash{}( aSym.name ); + } + }; + }; + + // Temporary data structure to pass graphic data from file for processing + struct GRAPHIC_DATA + { + std::string graphic_dataname; + std::string graphic_datanum; + std::string graphic_data1; + std::string graphic_data2; + std::string graphic_data3; + std::string graphic_data4; + std::string graphic_data5; + std::string graphic_data6; + std::string graphic_data7; + std::string graphic_data8; + std::string graphic_data9; + std::string graphic_data10; + }; + + std::unordered_map symbols; + + + // * A!GRAPHIC_DATA_NAME!GRAPHIC_DATA_NUMBER!RECORD_TAG!GRAPHIC_DATA_1!GRAPHIC_DATA_2!GRAPHIC_DATA_3! + // * GRAPHIC_DATA_4!GRAPHIC_DATA_5!GRAPHIC_DATA_6!GRAPHIC_DATA_7!GRAPHIC_DATA_8!GRAPHIC_DATA_9! + // * SUBCLASS!SYM_NAME!REFDES! + + struct GEOM_GRAPHIC + { + std::string subclass; /// board_graphics; + std::map> comp_graphics; + + + // A!VIA_X!VIA_Y!PAD_STACK_NAME!NET_NAME!TEST_POINT! + struct FM_VIA + { + int x; ///> vias; + + // A!CLASS!SUBCLASS!GRAPHIC_DATA_NAME!GRAPHIC_DATA_NUMBER!RECORD_TAG!GRAPHIC_DATA_1! + // GRAPHIC_DATA_2!GRAPHIC_DATA_3!GRAPHIC_DATA_4!GRAPHIC_DATA_5!GRAPHIC_DATA_6!GRAPHIC_DATA_7! + // GRAPHIC_DATA_8!GRAPHIC_DATA_9!NET_NAME! + struct TRACE + { + std::string lclass; ///& lhs, const std::unique_ptr& rhs) const + { + return lhs->id < rhs->id; + } + }; + }; + + std::set, TRACE::BY_ID> traces; + + std::set, TRACE::BY_ID> zones; + + std::set, TRACE::BY_ID> polygons; + + + // A!REFDES!COMP_CLASS!COMP_PART_NUMBER!COMP_HEIGHT!COMP_DEVICE_LABEL!COMP_INSERTION_CODE!SYM_TYPE! + // SYM_NAME!SYM_MIRROR!SYM_ROTATE!SYM_X!SYM_Y!COMP_VALUE!COMP_TOL!COMP_VOLTAGE! + struct COMPONENT + { + std::string refdes; ///{}( aCmp.refdes ); + } + }; + + }; + + std::map>> components; + + + // A!SYM_NAME!SYM_MIRROR!PIN_NAME!PIN_NUMBER!PIN_X!PIN_Y!PAD_STACK_NAME!REFDES!PIN_ROTATION!TEST_POINT! + + struct PIN + { + std::string name; ///& lhs, + const std::unique_ptr& rhs) const + { + return lhs->pin_number < rhs->pin_number; + } + }; + }; + + std::map, PIN::BY_NUM>> pins; + + std::map layer_map; + + section_type detectType( size_t aOffset ); + + int execute_recordbuffer( int filetype ); + int getColFromName( size_t aRow, const std::string& aStr ); + SYMTYPE parseSymType( const std::string& aSymType ); + COMPCLASS parseCompClass( const std::string& aCompClass ); + + /** + * Processes data from text vectors into internal database + * for further ordering + * @param aRow vector offset being processed + * @return Count of the number of rows processed + */ + double processScaleFactor( size_t aRow ); + size_t processPadStacks( size_t aRow ); + size_t processCustomPads( size_t aRow ); + size_t processGeometry( size_t aRow ); + size_t processVias( size_t aRow ); + size_t processTraces( size_t aRow ); + size_t processFootprints( size_t aRow ); + size_t processNets( size_t aRow ); + size_t processLayers( size_t aRow ); + size_t processSimpleLayers( size_t aRow ); + size_t processPadStackLayers( size_t aRow ); + size_t processSymbols( size_t aRow ); + size_t processPins( size_t aRow ); + + /** + * Specialty functions for processing graphical data rows into the internal + * database + * @param aData Loaded data vector + * @param aScale Prior loaded scale factor + * @return Pointer to newly allocated graphical item or nullptr on failure + */ + GRAPHIC_ITEM* processGraphic( const GRAPHIC_DATA& aData, double aScale ); + GRAPHIC_ARC* processArc( const GRAPHIC_DATA& aData, double aScale ); + GRAPHIC_LINE* processLine( const GRAPHIC_DATA& aData, double aScale ); + GRAPHIC_TEXT* processText( const GRAPHIC_DATA& aData, double aScale ); + GRAPHIC_RECTANGLE* processRectangle( const GRAPHIC_DATA& aData, double aScale ); + + PCB_LAYER_ID getLayer( const std::string& aLayerName ); + bool assignLayers(); + + /** + * Sets zone priorities based on zone BB size. Larger bounding boxes get smaller priorities + * so smaller zones can knock out areas where they overlap. + * @param aBoard + * @return True if successful + */ + bool orderZones( BOARD* aBoard ); + + /** + * Loads sections of the database into the board + * @param aBoard + * @return True if successful + */ + bool loadZones( BOARD* aBoard ); + bool loadOutline( BOARD* aBoard, const std::unique_ptr& aLine); + bool loadNets( BOARD* aBoard ); + bool loadLayers( BOARD* aBoard ); + bool loadGraphics( BOARD* aBoard ); + bool loadVias( BOARD* aBoard ); + bool loadEtch( BOARD* aBoard, const std::unique_ptr& aLine); + bool loadZone( BOARD* aBoard, const std::unique_ptr& aLine); + bool loadPolygon( BOARD* aBoard, const std::unique_ptr& aLine); + bool loadFootprints( BOARD* aBoard ); + +}; + + + + +#endif /* PCBNEW_IMPORTERS_IMPORT_FABMASTER_H_ */