From 0b853c5af262923700c010d545000c7ecbc8694f Mon Sep 17 00:00:00 2001 From: Cirilo Bernardo Date: Thu, 2 Jan 2014 10:26:03 +0100 Subject: [PATCH] Adds basic IDF3 export (board and cutouts / holes only) --- include/wxPcbStruct.h | 6 + pcbnew/CMakeLists.txt | 4 + pcbnew/dialogs/dialog_export_idf.cpp | 121 +++ pcbnew/dialogs/dialog_export_idf_base.cpp | 49 ++ pcbnew/dialogs/dialog_export_idf_base.fbp | 383 +++++++++ pcbnew/dialogs/dialog_export_idf_base.h | 52 ++ pcbnew/export_idf.cpp | 357 ++++++++ pcbnew/idf.cpp | 968 ++++++++++++++++++++++ pcbnew/idf.h | 454 ++++++++++ pcbnew/menubar_pcbframe.cpp | 5 + pcbnew/pcbframe.cpp | 1 + pcbnew/pcbnew_id.h | 1 + 12 files changed, 2401 insertions(+) create mode 100644 pcbnew/dialogs/dialog_export_idf.cpp create mode 100644 pcbnew/dialogs/dialog_export_idf_base.cpp create mode 100644 pcbnew/dialogs/dialog_export_idf_base.fbp create mode 100644 pcbnew/dialogs/dialog_export_idf_base.h create mode 100644 pcbnew/export_idf.cpp create mode 100644 pcbnew/idf.cpp create mode 100644 pcbnew/idf.h diff --git a/include/wxPcbStruct.h b/include/wxPcbStruct.h index 05e4e0dbfc..576c4a5038 100644 --- a/include/wxPcbStruct.h +++ b/include/wxPcbStruct.h @@ -977,6 +977,12 @@ public: bool ExportVRML_File( const wxString & aFullFileName, double aMMtoWRMLunit, bool aExport3DFiles, const wxString & a3D_Subdir ); + /** + * Function ExportToIDF3 + * will export the current BOARD to a IDFv3 board and lib files. + */ + void ExportToIDF3( wxCommandEvent& event ); + /** * Function ExporttoSPECCTRA * will export the current BOARD to a specctra dsn file. See diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 06cec9d8e1..1816bf42c9 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -55,6 +55,8 @@ set( PCBNEW_DIALOGS dialogs/dialog_edit_module_text.cpp dialogs/dialog_edit_module_text_base.cpp dialogs/dialog_exchange_modules_base.cpp + dialogs/dialog_export_idf.cpp + dialogs/dialog_export_idf_base.cpp dialogs/dialog_export_vrml_base.cpp dialogs/dialog_export_vrml.cpp dialogs/dialog_find_base.cpp @@ -173,6 +175,7 @@ set( PCBNEW_CLASS_SRCS event_handlers_tracks_vias_sizes.cpp export_d356.cpp export_gencad.cpp + export_idf.cpp export_vrml.cpp files.cpp gen_drill_report_files.cpp @@ -183,6 +186,7 @@ set( PCBNEW_CLASS_SRCS hotkeys.cpp hotkeys_board_editor.cpp hotkeys_module_editor.cpp + idf.cpp initpcb.cpp layer_widget.cpp librairi.cpp diff --git a/pcbnew/dialogs/dialog_export_idf.cpp b/pcbnew/dialogs/dialog_export_idf.cpp new file mode 100644 index 0000000000..a87710a6ef --- /dev/null +++ b/pcbnew/dialogs/dialog_export_idf.cpp @@ -0,0 +1,121 @@ +/** + * @file dialog_export_idf.cpp + */ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include + +// IDF export header generated by wxFormBuilder +#include + +#define OPTKEY_IDF_THOU wxT( "IDFExportThou" ) + + +bool Export_IDF3( BOARD *aPcb, const wxString & aFullFileName, double aUseThou ); + + +class DIALOG_EXPORT_IDF3: public DIALOG_EXPORT_IDF3_BASE +{ +private: + PCB_EDIT_FRAME* m_parent; + wxConfig* m_config; + bool m_idfThouOpt; // remember last preference for units in THOU + + void OnCancelClick( wxCommandEvent& event ) + { + EndModal( wxID_CANCEL ); + } + void OnOkClick( wxCommandEvent& event ) + { + EndModal( wxID_OK ); + } + +public: + DIALOG_EXPORT_IDF3( PCB_EDIT_FRAME* parent ) : + DIALOG_EXPORT_IDF3_BASE( parent ) + { + m_parent = parent; + m_config = wxGetApp().GetSettings(); + SetFocus(); + m_idfThouOpt = false; + m_config->Read( OPTKEY_IDF_THOU, &m_idfThouOpt ); + m_chkThou->SetValue( m_idfThouOpt ); + + GetSizer()->SetSizeHints( this ); + Centre(); + } + + ~DIALOG_EXPORT_IDF3() + { + m_idfThouOpt = m_chkThou->GetValue(); + m_config->Write( OPTKEY_IDF_THOU, m_idfThouOpt ); + } + + bool GetThouOption() + { + return m_chkThou->GetValue(); + } + + wxFilePickerCtrl* FilePicker() + { + return m_filePickerIDF; + } +}; + + +/** + * Function OnExportIDF3 + * will export the current BOARD to IDF board and lib files. + */ +void PCB_EDIT_FRAME::ExportToIDF3( wxCommandEvent& event ) +{ + wxFileName fn; + + // Build default file name + fn = GetBoard()->GetFileName(); + fn.SetExt( wxT( "emn" ) ); + + DIALOG_EXPORT_IDF3 dlg( this ); + dlg.FilePicker()->SetPath( fn.GetFullPath() ); + + if ( dlg.ShowModal() != wxID_OK ) + return; + + bool thou = dlg.GetThouOption(); + + wxBusyCursor dummy; + + wxString fullFilename = dlg.FilePicker()->GetPath(); + + if ( !Export_IDF3( GetBoard(), fullFilename, thou ) ) + { + wxString msg = _("Unable to create ") + fullFilename; + wxMessageBox( msg ); + return; + } +} diff --git a/pcbnew/dialogs/dialog_export_idf_base.cpp b/pcbnew/dialogs/dialog_export_idf_base.cpp new file mode 100644 index 0000000000..8d79b41733 --- /dev/null +++ b/pcbnew/dialogs/dialog_export_idf_base.cpp @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 8 2012) +// http://www.wxformbuilder.org/ +// +// PLEASE DO "NOT" EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#include "dialog_export_idf_base.h" + +/////////////////////////////////////////////////////////////////////////// + +DIALOG_EXPORT_IDF3_BASE::DIALOG_EXPORT_IDF3_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* bSizerIDFFile; + bSizerIDFFile = new wxBoxSizer( wxVERTICAL ); + + m_txtBrdFile = new wxStaticText( this, wxID_ANY, wxT("IDF Board file"), wxDefaultPosition, wxDefaultSize, 0 ); + m_txtBrdFile->Wrap( -1 ); + bSizerIDFFile->Add( m_txtBrdFile, 0, wxALL, 5 ); + + m_filePickerIDF = new wxFilePickerCtrl( this, wxID_ANY, wxEmptyString, wxT("Select a board file"), wxT("*.emn"), wxDefaultPosition, wxDefaultSize, wxFLP_OVERWRITE_PROMPT|wxFLP_SAVE|wxFLP_USE_TEXTCTRL ); + m_filePickerIDF->SetMinSize( wxSize( 420,30 ) ); + + bSizerIDFFile->Add( m_filePickerIDF, 0, wxALL, 5 ); + + m_chkThou = new wxCheckBox( this, wxID_ANY, wxT("unit: THOU"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizerIDFFile->Add( m_chkThou, 0, wxALL, 5 ); + + m_sdbSizer1 = new wxStdDialogButtonSizer(); + m_sdbSizer1OK = new wxButton( this, wxID_OK ); + m_sdbSizer1->AddButton( m_sdbSizer1OK ); + m_sdbSizer1Cancel = new wxButton( this, wxID_CANCEL ); + m_sdbSizer1->AddButton( m_sdbSizer1Cancel ); + m_sdbSizer1->Realize(); + + bSizerIDFFile->Add( m_sdbSizer1, 1, wxEXPAND, 5 ); + + + this->SetSizer( bSizerIDFFile ); + this->Layout(); + + this->Centre( wxBOTH ); +} + +DIALOG_EXPORT_IDF3_BASE::~DIALOG_EXPORT_IDF3_BASE() +{ +} diff --git a/pcbnew/dialogs/dialog_export_idf_base.fbp b/pcbnew/dialogs/dialog_export_idf_base.fbp new file mode 100644 index 0000000000..1199e64b6a --- /dev/null +++ b/pcbnew/dialogs/dialog_export_idf_base.fbp @@ -0,0 +1,383 @@ + + + + + + C++ + 1 + source_name + 0 + 0 + res + UTF-8 + connect + dialog_export_idf_base + 1000 + none + 0 + dialog_export_idf3_base + + . + + 1 + 1 + 1 + 1 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + + DIALOG_EXPORT_IDF3_BASE + + 458,177 + wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER + DIALOG_SHIM; dialog_shim.h + Export IDFv3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bSizerIDFFile + wxVERTICAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + IDF Board file + + 0 + + + 0 + + 1 + m_txtBrdFile + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + Select a board file + + 0 + 420,30 + 1 + m_filePickerIDF + 1 + + + protected + 1 + + Resizable + 1 + + wxFLP_OVERWRITE_PROMPT|wxFLP_SAVE|wxFLP_USE_TEXTCTRL + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + *.emn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + unit: THOU + + 0 + + + 0 + + 1 + m_chkThou + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 1 + + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + + m_sdbSizer1 + protected + + + + + + + + + + + + + + diff --git a/pcbnew/dialogs/dialog_export_idf_base.h b/pcbnew/dialogs/dialog_export_idf_base.h new file mode 100644 index 0000000000..c581798507 --- /dev/null +++ b/pcbnew/dialogs/dialog_export_idf_base.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 8 2012) +// http://www.wxformbuilder.org/ +// +// PLEASE DO "NOT" EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#ifndef __DIALOG_EXPORT_IDF_BASE_H__ +#define __DIALOG_EXPORT_IDF_BASE_H__ + +#include +#include +class DIALOG_SHIM; + +#include "dialog_shim.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +/// Class DIALOG_EXPORT_IDF3_BASE +/////////////////////////////////////////////////////////////////////////////// +class DIALOG_EXPORT_IDF3_BASE : public DIALOG_SHIM +{ + private: + + protected: + wxStaticText* m_txtBrdFile; + wxFilePickerCtrl* m_filePickerIDF; + wxCheckBox* m_chkThou; + wxStdDialogButtonSizer* m_sdbSizer1; + wxButton* m_sdbSizer1OK; + wxButton* m_sdbSizer1Cancel; + + public: + + DIALOG_EXPORT_IDF3_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Export IDFv3"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 458,177 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + ~DIALOG_EXPORT_IDF3_BASE(); + +}; + +#endif //__DIALOG_EXPORT_IDF_BASE_H__ diff --git a/pcbnew/export_idf.cpp b/pcbnew/export_idf.cpp new file mode 100644 index 0000000000..e3a568d4a6 --- /dev/null +++ b/pcbnew/export_idf.cpp @@ -0,0 +1,357 @@ +/** + * @file export_idf.cpp + */ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 Cirilo Bernardo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +// assumed default graphical line thickness: 10000 IU == 0.1mm +#define LINE_WIDTH (100000) + +/** + * Function idf_export_outline + * retrieves line segment information from the edge layer and compiles + * the data into a form which can be output as an IDFv3 compliant + * BOARD_OUTLINE section. + */ +static void idf_export_outline( BOARD* aPcb, IDF_BOARD& aIDFBoard ) +{ + double scale = aIDFBoard.GetScale(); + + DRAWSEGMENT* graphic; // KiCad graphical item + IDF_POINT sp, ep; // start and end points from KiCad item + + std::list< IDF_SEGMENT* > lines; // IDF intermediate form of KiCad graphical item + IDF_OUTLINE outline; // graphical items forming an outline or cutout + + // NOTE: IMPLEMENTATION + // If/when component cutouts are allowed, we must implement them separately. Cutouts + // must be added to the board outline section and not to the Other Outline section. + // The module cutouts should be handled via the idf_export_module() routine. + + double offX, offY; + aIDFBoard.GetOffset( offX, offY ); + + // Retrieve segments and arcs from the board + for( BOARD_ITEM* item = aPcb->m_Drawings; item; item = item->Next() ) + { + if( item->Type() != PCB_LINE_T || item->GetLayer() != EDGE_N ) + continue; + + graphic = (DRAWSEGMENT*) item; + + switch( graphic->GetShape() ) + { + case S_SEGMENT: + { + sp.x = graphic->GetStart().x * scale + offX; + sp.y = -graphic->GetStart().y * scale + offY; + ep.x = graphic->GetEnd().x * scale + offX; + ep.y = -graphic->GetEnd().y * scale + offY; + IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep ); + + if( seg ) + lines.push_back( seg ); + } + break; + + case S_ARC: + { + sp.x = graphic->GetCenter().x * scale + offX; + sp.y = -graphic->GetCenter().y * scale + offY; + ep.x = graphic->GetArcStart().x * scale + offX; + ep.y = -graphic->GetArcStart().y * scale + offY; + IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep, -graphic->GetAngle() / 10.0, true ); + + if( seg ) + lines.push_back( seg ); + } + break; + + case S_CIRCLE: + { + sp.x = graphic->GetCenter().x * scale + offX; + sp.y = -graphic->GetCenter().y * scale + offY; + ep.x = sp.x - graphic->GetRadius() * scale; + ep.y = sp.y; + // Circles must always have an angle of +360 deg. to appease + // quirky MCAD implementations of IDF. + IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep, 360.0, true ); + + if( seg ) + lines.push_back( seg ); + } + break; + + default: + break; + } + } + + // if there is no outline then use the bounding box + if( lines.empty() ) + { + goto UseBoundingBox; + } + + // get the board outline and write it out + // note: we do not use a try/catch block here since we intend + // to simply ignore unclosed loops and continue processing + // until we're out of segments to process + IDF3::GetOutline( lines, outline ); + + if( outline.empty() ) + goto UseBoundingBox; + + aIDFBoard.AddOutline( outline ); + + // get all cutouts and write them out + while( !lines.empty() ) + { + IDF3::GetOutline( lines, outline ); + + if( outline.empty() ) + continue; + + aIDFBoard.AddOutline( outline ); + } + + return; + +UseBoundingBox: + + // clean up if necessary + while( !lines.empty() ) + { + delete lines.front(); + lines.pop_front(); + } + + outline.Clear(); + + // fetch a rectangular bounding box for the board; + // there is always some uncertainty in the board dimensions + // computed via ComputeBoundingBox() since this depends on the + // individual module entities. + EDA_RECT bbbox = aPcb->ComputeBoundingBox( true ); + + // convert to mm and compensate for an assumed LINE_WIDTH line thickness + double x = ( bbbox.GetOrigin().x + LINE_WIDTH / 2 ) * scale + offX; + double y = ( bbbox.GetOrigin().y + LINE_WIDTH / 2 ) * scale + offY; + double dx = ( bbbox.GetSize().x - LINE_WIDTH ) * scale; + double dy = ( bbbox.GetSize().y - LINE_WIDTH ) * scale; + + double px[4], py[4]; + px[0] = x; + py[0] = y; + + px[1] = x; + py[1] = y + dy; + + px[2] = x + dx; + py[2] = y + dy; + + px[3] = x + dx; + py[3] = y; + + IDF_POINT p1, p2; + + p1.x = px[3]; + p1.y = py[3]; + p2.x = px[0]; + p2.y = py[0]; + + outline.push( new IDF_SEGMENT( p1, p2 ) ); + + for( int i = 1; i < 4; ++i ) + { + p1.x = px[i - 1]; + p1.y = py[i - 1]; + p2.x = px[i]; + p2.y = py[i]; + + outline.push( new IDF_SEGMENT( p1, p2 ) ); + } + + aIDFBoard.AddOutline( outline ); +} + + +/** + * Function idf_export_module + * retrieves information from all board modules, adds drill holes to + * the DRILLED_HOLES or BOARD_OUTLINE section as appropriate, + * compiles data for the PLACEMENT section and compiles data for + * the library ELECTRICAL section. + */ +static void idf_export_module( BOARD* aPcb, MODULE* aModule, + IDF_BOARD& aIDFBoard ) +{ + // Reference Designator + std::string crefdes = TO_UTF8( aModule->GetReference() ); + + if( crefdes.empty() || !crefdes.compare( "~" ) ) + { + std::string cvalue = TO_UTF8( aModule->GetValue() ); + + // if both the RefDes and Value are empty or set to '~' the board owns the part, + // otherwise associated parts of the module must be marked NOREFDES. + if( cvalue.empty() || !cvalue.compare( "~" ) ) + crefdes = "BOARD"; + else + crefdes = "NOREFDES"; + } + + // TODO: If module cutouts are supported we must add code here + // for( EDA_ITEM* item = aModule->GraphicalItems(); item != NULL; item = item->Next() ) + // { + // if( ( item->Type() != PCB_MODULE_EDGE_T ) + // || (item->GetLayer() != EDGE_N ) ) continue; + // code to export cutouts + // } + + // Export pads + double drill, x, y; + double scale = aIDFBoard.GetScale(); + IDF3::KEY_PLATING kplate; + std::string pintype; + std::string tstr; + + double dx, dy; + + aIDFBoard.GetOffset( dx, dy ); + + for( D_PAD* pad = aModule->Pads(); pad; pad = pad->Next() ) + { + drill = (double) pad->GetDrillSize().x * scale; + x = pad->GetPosition().x * scale + dx; + y = -pad->GetPosition().y * scale + dy; + + // Export the hole on the edge layer + if( drill > 0.0 ) + { + // plating + if( pad->GetAttribute() == PAD_HOLE_NOT_PLATED ) + kplate = IDF3::NPTH; + else + kplate = IDF3::PTH; + + // hole type + tstr = TO_UTF8( pad->GetPadName() ); + + if( tstr.empty() || !tstr.compare( "0" ) || !tstr.compare( "~" ) + || ( kplate == IDF3::NPTH ) || ( pad->GetDrillShape() == PAD_OVAL ) ) + pintype = "MTG"; + else + pintype = "PIN"; + + // fields: + // 1. hole dia. : float + // 2. X coord : float + // 3. Y coord : float + // 4. plating : PTH | NPTH + // 5. Assoc. part : BOARD | NOREFDES | PANEL | {"refdes"} + // 6. type : PIN | VIA | MTG | TOOL | { "other" } + // 7. owner : MCAD | ECAD | UNOWNED + if( ( pad->GetDrillShape() == PAD_OVAL ) + && ( pad->GetDrillSize().x != pad->GetDrillSize().y ) ) + { + // NOTE: IDF does not have direct support for slots; + // slots are implemented as a board cutout and we + // cannot represent plating or reference designators + + double dlength = pad->GetDrillSize().y * scale; + + // NOTE: The orientation of modules and pads have + // the opposite sense due to KiCad drawing on a + // screen with a LH coordinate system + double angle = pad->GetOrientation() / 10.0; + + if( dlength < drill ) + { + std::swap( drill, dlength ); + angle += M_PI2; + } + + // NOTE: KiCad measures a slot's length from end to end + // rather than between the centers of the arcs + dlength -= drill; + + aIDFBoard.AddSlot( drill, dlength, angle, x, y ); + } + else + { + aIDFBoard.AddDrill( drill, x, y, kplate, crefdes, pintype, IDF3::ECAD ); + } + } + } + + // TODO + // add to the library item list +} + + +/** + * Function Export_IDF3 + * generates IDFv3 compliant board (*.emn) and library (*.emp) + * files representing the user's PCB design. + */ +bool Export_IDF3( BOARD* aPcb, const wxString& aFullFileName, double aUseThou ) +{ + IDF_BOARD idfBoard; + + SetLocaleTo_C_standard(); + + // NOTE: + // XXX We may enclose all this in a TRY .. CATCH block + idfBoard.Setup( aPcb->GetFileName(), aFullFileName, aUseThou, + aPcb->GetDesignSettings().GetBoardThickness() ); + + // set up the global offsets + EDA_RECT bbox = aPcb->ComputeBoundingBox( true ); + idfBoard.SetOffset( bbox.Centre().x * idfBoard.GetScale(), + bbox.Centre().y * idfBoard.GetScale() ); + + // Export the board outline + idf_export_outline( aPcb, idfBoard ); + + // Output the drill holes and module (library) data. + for( MODULE* module = aPcb->m_Modules; module != 0; module = module->Next() ) + idf_export_module( aPcb, module, idfBoard ); + + idfBoard.Finish(); + + SetLocaleTo_Default(); + + return true; +} diff --git a/pcbnew/idf.cpp b/pcbnew/idf.cpp new file mode 100644 index 0000000000..6bbddfdabc --- /dev/null +++ b/pcbnew/idf.cpp @@ -0,0 +1,968 @@ +/** + * file: idf.cpp + * + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 Cirilo Bernardo + * + * 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 + */ + +// TODO: Consider using different precision formats for THOU vs MM output +// Keep in mind that THOU cannot represent MM very well although MM can +// represent 1 THOU with 4 decimal places. For modern manufacturing we +// are interested in a resolution of about 0.1 THOU. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// differences in angle smaller than MIN_ANG are considered equal +#define MIN_ANG (0.01) +// minimum drill diameter (nanometers) - 10000 is a 0.01mm drill +#define IDF_MIN_DIA ( 10000.0 ) + +// minimum board thickness; this is about 0.012mm (0.5 mils) +// which is about the thickness of a single kapton layer typically +// used in a flexible design. +#define IDF_MIN_BRD_THICKNESS (12000) + +bool IDF_POINT::Matches( const IDF_POINT& aPoint, double aRadius ) +{ + double dx = x - aPoint.x; + double dy = y - aPoint.y; + + double d2 = dx * dx + dy * dy; + + if( d2 <= aRadius * aRadius ) + return true; + + return false; +} + + +double IDF_POINT::CalcDistance( const IDF_POINT& aPoint ) const +{ + double dx = aPoint.x - x; + double dy = aPoint.y - y; + double dist = sqrt( dx * dx + dy * dy ); + + return dist; +} + + +double IDF3::CalcAngleRad( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ) +{ + return atan2( aEndPoint.y - aStartPoint.y, aEndPoint.x - aStartPoint.x ); +} + + +double IDF3::CalcAngleDeg( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ) +{ + double ang = CalcAngleRad( aStartPoint, aEndPoint ); + + // round to thousandths of a degree + int iang = int (ang / M_PI * 1800000.0); + + ang = iang / 10000.0; + + return ang; +} + + +IDF_SEGMENT::IDF_SEGMENT() +{ + angle = 0.0; + offsetAngle = 0.0; + radius = 0.0; +} + + +IDF_SEGMENT::IDF_SEGMENT( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ) +{ + angle = 0.0; + offsetAngle = 0.0; + radius = 0.0; + startPoint = aStartPoint; + endPoint = aEndPoint; +} + + +IDF_SEGMENT::IDF_SEGMENT( const IDF_POINT& aStartPoint, + const IDF_POINT& aEndPoint, + double aAngle, + bool aFromKicad ) +{ + double diff = abs( aAngle ) - 360.0; + + if( ( diff < MIN_ANG + && diff > -MIN_ANG ) || ( aAngle < MIN_ANG && aAngle > -MIN_ANG ) || (!aFromKicad) ) + { + angle = 0.0; + startPoint = aStartPoint; + endPoint = aEndPoint; + + if( diff < MIN_ANG && diff > -MIN_ANG ) + { + angle = 360.0; + center = aStartPoint; + offsetAngle = 0.0; + radius = aStartPoint.CalcDistance( aEndPoint ); + } + else if( aAngle < MIN_ANG && aAngle > -MIN_ANG ) + { + CalcCenterAndRadius(); + } + + return; + } + + // we need to convert from the KiCad arc convention + angle = aAngle; + + center = aStartPoint; + + offsetAngle = IDF3::CalcAngleDeg( aStartPoint, aEndPoint ); + + radius = aStartPoint.CalcDistance( aEndPoint ); + + startPoint = aEndPoint; + + double ang = offsetAngle + aAngle; + ang = (ang / 180.0) * M_PI; + + endPoint.x = ( radius * cos( ang ) ) + center.x; + endPoint.y = ( radius * sin( ang ) ) + center.y; +} + + +bool IDF_SEGMENT::MatchesStart( const IDF_POINT& aPoint, double aRadius ) +{ + return startPoint.Matches( aPoint, aRadius ); +} + + +bool IDF_SEGMENT::MatchesEnd( const IDF_POINT& aPoint, double aRadius ) +{ + return endPoint.Matches( aPoint, aRadius ); +} + + +void IDF_SEGMENT::CalcCenterAndRadius( void ) +{ + // NOTE: this routine does not check if the points are the same + // or too close to be sensible in a production setting. + + double offAng = IDF3::CalcAngleRad( startPoint, endPoint ); + double d = startPoint.CalcDistance( endPoint ) / 2.0; + double xm = ( startPoint.x + endPoint.x ) * 0.5; + double ym = ( startPoint.y + endPoint.y ) * 0.5; + + radius = d / sin( angle * M_PI / 180.0 ); + + if( radius < 0.0 ) + { + radius = -radius; + } + + // calculate the height of the triangle with base d and hypotenuse r + double dh2 = radius * radius - d * d; + + if( dh2 < 0 ) + { + // this should only ever happen due to rounding errors when r == d + dh2 = 0; + } + + double h = sqrt( dh2 ); + + if( angle > 0.0 ) + offAng += M_PI2; + else + offAng -= M_PI2; + + if( ( angle > M_PI ) || ( angle < -M_PI ) ) + offAng += M_PI; + + center.x = h * cos( offAng ) + xm; + center.y = h * sin( offAng ) + ym; + + offsetAngle = IDF3::CalcAngleDeg( center, startPoint ); +} + + +bool IDF_SEGMENT::IsCircle( void ) +{ + double diff = abs( angle ) - 360.0; + + if( ( diff < MIN_ANG ) && ( diff > -MIN_ANG ) ) + return true; + + return false; +} + + +double IDF_SEGMENT::GetMinX( void ) +{ + if( angle == 0.0 ) + return std::min( startPoint.x, endPoint.x ); + + // Calculate the leftmost point of the circle or arc + + if( IsCircle() ) + { + // if only everything were this easy + return center.x - radius; + } + + // cases: + // 1. CCW arc: if offset + included angle >= 180 deg then + // MinX = center.x - radius, otherwise MinX is the + // same as for the case of a line. + // 2. CW arc: if offset + included angle <= -180 deg then + // MinX = center.x - radius, otherwise MinX is the + // same as for the case of a line. + + if( angle > 0 ) + { + // CCW case + if( ( offsetAngle + angle ) >= 180.0 ) + { + return center.x - radius; + } + else + { + return std::min( startPoint.x, endPoint.x ); + } + } + + // CW case + if( ( offsetAngle + angle ) <= -180.0 ) + { + return center.x - radius; + } + + return std::min( startPoint.x, endPoint.x ); +} + + +void IDF_SEGMENT::SwapEnds( void ) +{ + if( IsCircle() ) + { + // reverse the direction + angle = -angle; + return; + } + + IDF_POINT tmp = startPoint; + startPoint = endPoint; + endPoint = tmp; + + if( ( angle < MIN_ANG ) && ( angle > -MIN_ANG ) ) + return; // nothing more to do + + // change the direction of the arc + angle = -angle; + // calculate the new offset angle + offsetAngle = IDF3::CalcAngleDeg( center, startPoint ); +} + + +IDF_DRILL_DATA::IDF_DRILL_DATA( double aDrillDia, double aPosX, double aPosY, + IDF3::KEY_PLATING aPlating, + const std::string aRefDes, + const std::string aHoleType, + IDF3::KEY_OWNER aOwner ) +{ + if( aDrillDia < 0.3 ) + dia = 0.3; + else + dia = aDrillDia; + + x = aPosX; + y = aPosY; + plating = aPlating; + + if( !aRefDes.compare( "BOARD" ) ) + { + kref = IDF3::BOARD; + } + else if( aRefDes.empty() || !aRefDes.compare( "NOREFDES" ) ) + { + kref = IDF3::NOREFDES; + } + else if( !aRefDes.compare( "PANEL" ) ) + { + kref = IDF3::PANEL; + } + else + { + kref = IDF3::REFDES; + refdes = aRefDes; + } + + if( !aHoleType.compare( "PIN" ) ) + { + khole = IDF3::PIN; + } + else if( !aHoleType.compare( "VIA" ) ) + { + khole = IDF3::VIA; + } + else if( aHoleType.empty() || !aHoleType.compare( "MTG" ) ) + { + khole = IDF3::MTG; + } + else if( !aHoleType.compare( "TOOL" ) ) + { + khole = IDF3::TOOL; + } + else + { + khole = IDF3::OTHER; + holetype = aHoleType; + } + + owner = aOwner; +} // IDF_DRILL_DATA::IDF_DRILL_DATA( ... ) + + +bool IDF_DRILL_DATA::Write( FILE* aLayoutFile ) +{ + // TODO: check stream integrity and return 'false' as appropriate + + if( !aLayoutFile ) + return false; + + std::string holestr; + std::string refstr; + std::string ownstr; + std::string pltstr; + + switch( khole ) + { + case IDF3::PIN: + holestr = "PIN"; + break; + + case IDF3::VIA: + holestr = "VIA"; + break; + + case IDF3::TOOL: + holestr = "TOOL"; + break; + + case IDF3::OTHER: + holestr = "\"" + holetype + "\""; + break; + + default: + holestr = "MTG"; + break; + } + + switch( kref ) + { + case IDF3::BOARD: + refstr = "BOARD"; + break; + + case IDF3::PANEL: + refstr = "PANEL"; + break; + + case IDF3::REFDES: + refstr = "\"" + refdes + "\""; + break; + + default: + refstr = "NOREFDES"; + break; + } + + if( plating == IDF3::PTH ) + pltstr = "PTH"; + else + pltstr = "NPTH"; + + switch( owner ) + { + case IDF3::MCAD: + ownstr = "MCAD"; + break; + + case IDF3::ECAD: + ownstr = "ECAD"; + break; + + default: + ownstr = "UNOWNED"; + } + + fprintf( aLayoutFile, "%.3f %.5f %.5f %s %s %s %s\n", + dia, x, y, pltstr.c_str(), refstr.c_str(), holestr.c_str(), ownstr.c_str() ); + + return true; +} // IDF_DRILL_DATA::Write( aLayoutFile ) + + +IDF_BOARD::IDF_BOARD() +{ + outlineIndex = 0; + scale = 1e-6; + boardThickness = 1.6; // default to 1.6mm thick boards + + useThou = false; // by default we want mm output + hasBrdOutlineHdr = false; + + layoutFile = NULL; + libFile = NULL; +} + + +IDF_BOARD::~IDF_BOARD() +{ + Finish(); +} + + +bool IDF_BOARD::Setup( wxString aBoardName, + wxString aFullFileName, + bool aUseThou, + int aBoardThickness ) +{ + if( aBoardThickness < IDF_MIN_BRD_THICKNESS ) + return false; + + if( aUseThou ) + { + useThou = true; + scale = 1e-3 / 25.4; + } + else + { + useThou = false; + scale = 1e-6; + } + + boardThickness = aBoardThickness * scale; + + wxFileName brdname( aBoardName ); + wxFileName idfname( aFullFileName ); + + // open the layout file + idfname.SetExt( wxT( "emn" ) ); + layoutFile = wxFopen( aFullFileName, wxT( "wt" ) ); + + if( layoutFile == NULL ) + return false; + + // open the library file + idfname.SetExt( wxT( "emp" ) ); + libFile = wxFopen( idfname.GetFullPath(), wxT( "wt" ) ); + + if( libFile == NULL ) + { + fclose( layoutFile ); + layoutFile = NULL; + return false; + } + + + time_t date; + time( &date ); + struct tm tdate; + + time( &date ); + localtime_r( &date, &tdate ); + + fprintf( layoutFile, ".HEADER\n" + "BOARD_FILE 3.0 \"Created by KiCad %s\"" + " %.4d/%.2d/%.2d.%.2d:%.2d:%.2d 1\n" + "\"%s\" %s\n" + ".END_HEADER\n\n", + TO_UTF8( GetBuildVersion() ), + tdate.tm_year + 1900, tdate.tm_mon + 1, tdate.tm_mday, + tdate.tm_hour, tdate.tm_min, tdate.tm_sec, + TO_UTF8( brdname.GetFullName() ), useThou ? "THOU" : "MM" ); + + fprintf( libFile, ".HEADER\n" + "BOARD_FILE 3.0 \"Created by KiCad %s\" %.4d/%.2d/%.2d.%.2d:%.2d:%.2d 1\n" + ".END_HEADER\n\n", + TO_UTF8( GetBuildVersion() ), + tdate.tm_year + 1900, tdate.tm_mon + 1, tdate.tm_mday, + tdate.tm_hour, tdate.tm_min, tdate.tm_sec ); + + return true; +} + + +bool IDF_BOARD::Finish( void ) +{ + // Steps to finalize the board and library files: + // 1. (emp) finalize the library file + // 2. (emn) close the BOARD_OUTLINE section + // 3. (emn) write out the DRILLED_HOLES section + // 4. (emn) write out the COMPONENT_PLACEMENT section + + // TODO: + // idfLib.Finish(); + if( libFile != NULL ) + { + fclose( libFile ); + libFile = NULL; + } + + if( layoutFile == NULL ) + return false; + + // Finalize the board outline section + fprintf( layoutFile, ".END_BOARD_OUTLINE\n\n" ); + + // Write out the drill section + if( WriteDrills() ) + { + fclose( layoutFile ); + layoutFile = NULL; + return false; + } + + // TODO: Write out the component placement section + // IDF3::export_placement(); + + fclose( layoutFile ); + layoutFile = NULL; + + return true; +} + + +bool IDF_BOARD::AddOutline( IDF_OUTLINE& aOutline ) +{ + if( !layoutFile ) + return false; + + // TODO: check the stream integrity + + std::list::iterator bo; + std::list::iterator eo; + + if( !hasBrdOutlineHdr ) + { + fprintf( layoutFile, ".BOARD_OUTLINE ECAD\n%.5f\n", boardThickness ); + hasBrdOutlineHdr = true; + } + + if( aOutline.size() == 1 ) + { + if( !aOutline.front()->IsCircle() ) + return false; // this is a bad outline + + // NOTE: a circle always has an angle of 360, never -360, + // otherwise SolidWorks chokes on the file. + fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex, + aOutline.front()->startPoint.x, aOutline.front()->startPoint.y ); + fprintf( layoutFile, "%d %.5f %.5f 360\n", outlineIndex, + aOutline.front()->endPoint.x, aOutline.front()->endPoint.y ); + + ++outlineIndex; + return true; + } + + // ensure that the very last point is the same as the very first point + aOutline.back()-> endPoint = aOutline.front()->startPoint; + + // check if we must reverse things + if( ( aOutline.IsCCW() && ( outlineIndex > 0 ) ) + || ( ( !aOutline.IsCCW() ) && ( outlineIndex == 0 ) ) ) + { + eo = aOutline.begin(); + bo = aOutline.end(); + --bo; + + // for the first item we write out both points + if( aOutline.front()->angle < MIN_ANG && aOutline.front()->angle > -MIN_ANG ) + { + fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex, + aOutline.front()->endPoint.x, aOutline.front()->endPoint.y ); + fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex, + aOutline.front()->startPoint.x, aOutline.front()->startPoint.y ); + } + else + { + fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex, + aOutline.front()->endPoint.x, aOutline.front()->endPoint.y ); + fprintf( layoutFile, "%d %.5f %.5f %.5f\n", outlineIndex, + aOutline.front()->startPoint.x, aOutline.front()->startPoint.y, + -aOutline.front()->angle ); + } + + // for all other segments we only write out the start point + while( bo != eo ) + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex, + (*bo)->startPoint.x, (*bo)->startPoint.y ); + } + else + { + fprintf( layoutFile, "%d %.5f %.5f %.5f\n", outlineIndex, + (*bo)->startPoint.x, (*bo)->startPoint.y, -(*bo)->angle ); + } + + --bo; + } + } + else + { + bo = aOutline.begin(); + eo = aOutline.end(); + + // for the first item we write out both points + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex, + (*bo)->startPoint.x, (*bo)->startPoint.y ); + fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex, + (*bo)->endPoint.x, (*bo)->endPoint.y ); + } + else + { + fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex, + (*bo)->startPoint.x, (*bo)->startPoint.y ); + fprintf( layoutFile, "%d %.5f %.5f %.5f\n", outlineIndex, + (*bo)->endPoint.x, (*bo)->endPoint.y, (*bo)->angle ); + } + + ++bo; + + // for all other segments we only write out the last point + while( bo != eo ) + { + if( (*bo)->angle < MIN_ANG && (*bo)->angle > -MIN_ANG ) + { + fprintf( layoutFile, "%d %.5f %.5f 0\n", outlineIndex, + (*bo)->endPoint.x, (*bo)->endPoint.y ); + } + else + { + fprintf( layoutFile, "%d %.5f %.5f %.5f\n", outlineIndex, + (*bo)->endPoint.x, (*bo)->endPoint.y, (*bo)->angle ); + } + + ++bo; + } + } + + ++outlineIndex; + + return true; +} + + +bool IDF_BOARD::AddDrill( double dia, double x, double y, + IDF3::KEY_PLATING plating, + const std::string refdes, + const std::string holeType, + IDF3::KEY_OWNER owner ) +{ + if( dia < IDF_MIN_DIA * scale ) + return false; + + IDF_DRILL_DATA* dp = new IDF_DRILL_DATA( dia, x, y, plating, refdes, holeType, owner ); + drills.push_back( dp ); + + return true; +} + + +bool IDF_BOARD::AddSlot( double aWidth, double aLength, double aOrientation, + double aX, double aY ) +{ + if( aWidth < IDF_MIN_DIA * scale ) + return false; + + if( aLength < IDF_MIN_DIA * scale ) + return false; + + IDF_POINT c[2]; // centers + IDF_POINT pt[4]; + + double a1 = aOrientation / 180.0 * M_PI; + double a2 = a1 + M_PI2; + double d1 = aLength / 2.0; + double d2 = aWidth / 2.0; + double sa1 = sin( a1 ); + double ca1 = cos( a1 ); + double dsa2 = d2 * sin( a2 ); + double dca2 = d2 * cos( a2 ); + + c[0].x = aX + d1 * ca1; + c[0].y = aY + d1 * sa1; + + c[1].x = aX - d1 * ca1; + c[1].y = aY - d1 * sa1; + + pt[0].x = c[0].x - dca2; + pt[0].y = c[0].y - dsa2; + + pt[1].x = c[1].x - dca2; + pt[1].y = c[1].y - dsa2; + + pt[2].x = c[1].x + dca2; + pt[2].y = c[1].y + dsa2; + + pt[3].x = c[0].x + dca2; + pt[3].y = c[0].y + dsa2; + + IDF_OUTLINE outline; + + // first straight run + IDF_SEGMENT* seg = new IDF_SEGMENT( pt[0], pt[1] ); + outline.push( seg ); + // first 180 degree cap + seg = new IDF_SEGMENT( c[1], pt[1], -180.0, true ); + outline.push( seg ); + // final straight run + seg = new IDF_SEGMENT( pt[2], pt[3] ); + outline.push( seg ); + // final 180 degree cap + seg = new IDF_SEGMENT( c[0], pt[3], -180.0, true ); + outline.push( seg ); + + return AddOutline( outline ); +} + + +bool IDF_BOARD::WriteDrills( void ) +{ + if( !layoutFile ) + return false; + + // TODO: check the stream integrity and return false as appropriate + if( drills.empty() ) + return true; + + fprintf( layoutFile, ".DRILLED_HOLES\n" ); + + std::list::iterator ds = drills.begin(); + std::list::iterator de = drills.end(); + + while( ds != de ) + { + if( !(*ds)->Write( layoutFile ) ) + return false; + + ++ds; + } + + fprintf( layoutFile, ".END_DRILLED_HOLES\n" ); + + return true; +} + + +double IDF_BOARD::GetScale( void ) +{ + return scale; +} + + +void IDF_BOARD::SetOffset( double x, double y ) +{ + offsetX = x; + offsetY = y; +} + + +void IDF_BOARD::GetOffset( double& x, double& y ) +{ + x = offsetX; + y = offsetY; +} + + +void IDF3::GetOutline( std::list& aLines, + IDF_OUTLINE& aOutline ) +{ + aOutline.Clear(); + + // NOTE: To tell if the point order is CCW or CW, + // sum all: (endPoint.X[n] - startPoint.X[n])*(endPoint[n] + startPoint.Y[n]) + // If the result is >0, the direction is CW, otherwise + // it is CCW. Note that the result cannot be 0 unless + // we have a bounded area of 0. + + // First we find the segment with the leftmost point + std::list::iterator bl = aLines.begin(); + std::list::iterator el = aLines.end(); + std::list::iterator idx = bl++; // iterator for the object with minX + + double minx = (*idx)->GetMinX(); + double curx; + + while( bl != el ) + { + curx = (*bl)->GetMinX(); + + if( curx < minx ) + { + minx = curx; + idx = bl; + } + + ++bl; + } + + aOutline.push( *idx ); + aLines.erase( idx ); + + // If the item is a circle then we're done + if( aOutline.front()->IsCircle() ) + return; + + // Assemble the loop + bool complete = false; // set if loop is complete + bool matched; // set if a segment's end point was matched + + while( !complete ) + { + matched = false; + bl = aLines.begin(); + el = aLines.end(); + + while( bl != el && !matched ) + { + if( (*bl)->MatchesStart( aOutline.back()->endPoint ) ) + { + if( (*bl)->IsCircle() ) + { + // a circle on the perimeter is pathological but we just ignore it + ++bl; + } + else + { + matched = true; + aOutline.push( *bl ); + aLines.erase( bl ); + } + + continue; + } + + ++bl; + } + + if( !matched ) + { + // attempt to match the end points + bl = aLines.begin(); + el = aLines.end(); + + while( bl != el && !matched ) + { + if( (*bl)->MatchesEnd( aOutline.back()->endPoint ) ) + { + if( (*bl)->IsCircle() ) + { + // a circle on the perimeter is pathological but we just ignore it + ++bl; + } + else + { + matched = true; + (*bl)->SwapEnds(); + aOutline.push( *bl ); + aLines.erase( bl ); + } + + continue; + } + + ++bl; + } + } + + if( !matched ) + { + // still no match - attempt to close the loop + if( (aOutline.size() > 1) || ( aOutline.front()->angle < -MIN_ANG ) + || ( aOutline.front()->angle > MIN_ANG ) ) + { + // close the loop + IDF_SEGMENT* seg = new IDF_SEGMENT( aOutline.back()->endPoint, + aOutline.front()->startPoint ); + + if( seg ) + { + complete = true; + aOutline.push( seg ); + break; + } + } + + // the outline is bad; drop the segments + aOutline.Clear(); + + return; + } + + // check if the loop is complete + if( aOutline.front()->MatchesStart( aOutline.back()->endPoint ) ) + { + complete = true; + break; + } + } +} + + +bool IDF_LIB::WriteLib( FILE* aLibFile ) +{ + if( !aLibFile ) + return false; + + // TODO: check stream integrity and return false as appropriate + + // TODO: export models + + return true; +} + + +bool IDF_LIB::WriteBrd( FILE* aLayoutFile ) +{ + if( !aLayoutFile ) + return false; + + // TODO: check stream integrity and return false as appropriate + + // TODO: write out the board placement information + + return true; +} diff --git a/pcbnew/idf.h b/pcbnew/idf.h new file mode 100644 index 0000000000..926f9dce9d --- /dev/null +++ b/pcbnew/idf.h @@ -0,0 +1,454 @@ +/** + * @file idf.h + */ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 Cirilo Bernardo + * + * 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 IDF_H +#define IDF_H + +#include + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795028841 +#endif + +#ifndef M_PI2 +#define M_PI2 ( M_PI / 2.0 ) +#endif + +#ifndef M_PI4 +#define M_PI4 ( M_PI / 4.0 ) +#endif + +class IDF_POINT; +class IDF_SEGMENT; +class IDF_DRILL_DATA; +class IDF_OUTLINE; + +namespace IDF3 { +enum KEY_OWNER +{ + UNOWNED = 0, // < either MCAD or ECAD may modify a feature + MCAD, // < only MCAD may modify a feature + ECAD // < only ECAD may modify a feature +}; + +enum KEY_HOLETYPE +{ + PIN = 0, // < drill hole is for a pin + VIA, // < drill hole is for a via + MTG, // < drill hole is for mounting + TOOL, // < drill hole is for tooling + OTHER // < user has specified a custom type +}; + +enum KEY_PLATING +{ + PTH = 0, // < Plate-Through Hole + NPTH // < Non-Plate-Through Hole +}; + +enum KEY_REFDES +{ + BOARD = 0, // < feature is associated with the board + NOREFDES, // < feature is associated with a component with no RefDes + PANEL, // < feature is associated with an IDF panel + REFDES // < reference designator as assigned by the CAD software +}; + +// calculate the angle between the horizon and the segment aStartPoint to aEndPoint +double CalcAngleRad( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ); +double CalcAngleDeg( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ); + +// take contiguous elements from 'lines' and stuff them into 'outline' +void GetOutline( std::list& aLines, + IDF_OUTLINE& aOutline ); +} + + +/** + * @Class IDF_POINT + * represents a point + */ +class IDF_POINT +{ +public: + double x; // < X coordinate + double y; // < Y coordinate + + IDF_POINT() + { + x = 0.0; + y = 0.0; + } + + /** + * Function Matches() + * returns true if the given coordinate point is within the given radius + * of the point. + * @param aPoint : coordinates of the point being compared + * @param aRadius : radius within which the points are considered the same + */ + bool Matches( const IDF_POINT& aPoint, double aRadius = 1e-5 ); + double CalcDistance( const IDF_POINT& aPoint ) const; +}; + + +/** + * @Class IDF_SEGMENT + * represents a geometry segment as used in IDFv3 outlines + */ +class IDF_SEGMENT +{ +private: + /** + * Function CalcCenterAndRadius() + * Calculates the center, radius, and angle between center and start point given the + * IDF compliant points and included angle. + * @var startPoint, @var endPoint, and @var angle must be set prior as per IDFv3 + */ + void CalcCenterAndRadius( void ); + +public: + IDF_POINT startPoint; // starting point in IDF coordinates + IDF_POINT endPoint; // end point in IDF coordinates + IDF_POINT center; // center of an arc or circle; used primarily for calculating min X + double angle; // included angle (degrees) according to IDFv3 specification + double offsetAngle; // angle between center and start of arc; used to speed up some calcs. + double radius; // radius of the arc or circle; used to speed up some calcs. + + /** + * Function IDF_SEGMENT() + * initializes the internal variables + */ + IDF_SEGMENT(); + + /** + * Function IDF_SEGMENT( start, end ) + * creates a straight segment + */ + IDF_SEGMENT( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ); + + /** + * Function IDF_SEGMENT( start, end ) + * creates a straight segment, arc, or circle depending on the angle + * @param aStartPoint : start point (center if using KiCad convention, otherwise IDF convention) + * @param aEndPoint : end point (start of arc if using KiCad convention, otherwise IDF convention) + * @param aAngle : included angle; the KiCad convention is equivalent to the IDF convention + * @param fromKicad : set true if we need to convert from KiCad to IDF convention + */ + IDF_SEGMENT( const IDF_POINT& aStartPoint, + const IDF_POINT& aEndPoint, + double aAngle, + bool aFromKicad ); + + /** + * Function MatchesStart() + * returns true if the given coordinate is within a radius 'rad' + * of the start point. + * @param aPoint : coordinates of the point being compared + * @param aRadius : radius within which the points are considered the same + */ + bool MatchesStart( const IDF_POINT& aPoint, double aRadius = 1e-3 ); + + /** + * Function MatchesEnd() + * returns true if the given coordinate is within a radius 'rad' + * of the end point. + * @param aPoint : coordinates of the point being compared + * @param aRadius : radius within which the points are considered the same + */ + bool MatchesEnd( const IDF_POINT& aPoint, double aRadius = 1e-3 ); + + /** + * Function IsCircle() + * returns true if this segment is a circle + */ + bool IsCircle( void ); + + /** + * Function GetMinX() + * returns the minimum X coordinate of this segment + */ + double GetMinX( void ); + + /** + * Function SwapEnds() + * Swaps the start and end points and alters internal + * variables as necessary for arcs + */ + void SwapEnds( void ); +}; + + +/** + * @Class IDF_OUTLINE + * contains segment and winding information for an IDF outline + */ +class IDF_OUTLINE +{ +private: + double dir; + std::list outline; + +public: + IDF_OUTLINE() { dir = 0.0; } + ~IDF_OUTLINE() { Clear(); } + + // returns true if the current list of points represents a counterclockwise winding + bool IsCCW( void ) + { + if( dir > 0.0 ) + return false; + + return true; + } + + // clears the internal list of outline segments + void Clear( void ) + { + dir = 0.0; + + while( !outline.empty() ) + { + delete outline.front(); + outline.pop_front(); + } + } + + // returns the size of the internal segment list + size_t size( void ) + { + return outline.size(); + } + + // returns true if the internal segment list is empty + bool empty( void ) + { + return outline.empty(); + } + + // return the front() of the internal segment list + IDF_SEGMENT*& front( void ) + { + return outline.front(); + } + + // return the back() of the internal segment list + IDF_SEGMENT*& back( void ) + { + return outline.back(); + } + + // return the begin() iterator of the internal segment list + std::list::iterator begin( void ) + { + return outline.begin(); + } + + // return the end() iterator of the internal segment list + std::list::iterator end( void ) + { + return outline.end(); + } + + // push a segment onto the internal list + void push( IDF_SEGMENT* item ) + { + // XXX - check that startPoint[N] == endPoint[N -1], otherwise THROW + // XXX - a Circle must stand alone; if we add to a circle or add a + // circle to an existing list, we should throw an exception. + outline.push_back( item ); + dir += ( outline.back()->endPoint.x - outline.back()->startPoint.x ) + * ( outline.back()->endPoint.y + outline.back()->startPoint.y ); + } +}; + + +/** + * @Class IDF_BOARD + * contains objects necessary for the maintenance of the IDF board and library files. + */ +class IDF_BOARD +{ +private: + std::list drills; ///< IDF drill data + int outlineIndex; ///< next outline index to use + bool useThou; ///< true if output is THOU + double scale; ///< scale from KiCad IU to IDF output units + double boardThickness; ///< total thickness of the PCB + bool hasBrdOutlineHdr; ///< true when a board outline header has been written + + double offsetX; ///< offset to roughly center the board on the world origin + double offsetY; + + FILE* layoutFile; ///< IDF board file (*.emn) + FILE* libFile; ///< IDF library file (*.emp) + + /** + * Function Write + * outputs a .DRILLED_HOLES section compliant with the + * IDFv3 specification. + * @param aLayoutFile : open file (*.emn) for output + */ + bool WriteDrills( void ); + +public: + IDF_BOARD(); + + ~IDF_BOARD(); + + // Set up the output files and scale factor; + // return TRUE if everything is OK + bool Setup( wxString aBoardName, wxString aFullFileName, bool aUseThou, int aBoardThickness ); + + // Finish a board + // Write out all current data and close files. + // Return true for success + bool Finish( void ); + + /** + * Function GetScale + * returns the output scaling factor + */ + double GetScale( void ); + + /** + * Function SetOffset + * sets the global coordinate offsets + */ + void SetOffset( double x, double y ); + + /** + * Function GetOffset + * returns the global coordinate offsets + */ + void GetOffset( double& x, double& y ); + + // Add an outline; the very first outline is the board perimeter; + // all additional outlines are cutouts. + bool AddOutline( IDF_OUTLINE& aOutline ); + + /** + * Function AddDrill + * creates a drill entry and adds it to the list of PCB holes + * @param dia : drill diameter + * @param x : X coordinate of the drill center + * @param y : Y coordinate of the drill center + * @param plating : flag, PTH or NPTH + * @param refdes : component Reference Designator + * @param holetype : purpose of hole + * @param owner : one of MCAD, ECAD, UNOWNED + */ + bool AddDrill( double dia, double x, double y, + IDF3::KEY_PLATING plating, + const std::string refdes, + const std::string holeType, + IDF3::KEY_OWNER owner ); + + /** + * Function AddSlot + * creates a slot cutout within the IDF BOARD section; this is a deficient representation + * of a KiCad 'oval' drill; IDF is unable to represent a plated slot and unable to + * represent the Reference Designator association with a slot. + */ + bool AddSlot( double aWidth, double aLength, double aOrientation, double aX, double aY ); +}; + + +/** + * @Class IDF_DRILL_DATA + * contains information describing a drilled hole and is responsible for + * writing this information to a file in compliance with the IDFv3 specification. + */ +class IDF_DRILL_DATA +{ +private: + double dia; + double x; + double y; + IDF3::KEY_PLATING plating; + IDF3::KEY_REFDES kref; + IDF3::KEY_HOLETYPE khole; + std::string refdes; + std::string holetype; + IDF3::KEY_OWNER owner; + +public: + /** + * Constructor IDF_DRILL_DATA + * creates a drill entry with information compliant with the + * IDFv3 specifications. + * @param aDrillDia : drill diameter + * @param aPosX : X coordinate of the drill center + * @param aPosY : Y coordinate of the drill center + * @param aPlating : flag, PTH or NPTH + * @param aRefDes : component Reference Designator + * @param aHoleType : purpose of hole + * @param aOwner : one of MCAD, ECAD, UNOWNED + */ + IDF_DRILL_DATA( double aDrillDia, double aPosX, double aPosY, + IDF3::KEY_PLATING aPlating, + const std::string aRefDes, + const std::string aHoleType, + IDF3::KEY_OWNER aOwner ); + + /** + * Function Write + * writes a single line representing the hole within a .DRILLED_HOLES section + */ + bool Write( FILE* aLayoutFile ); +}; + + +/** + * @Class IDF_LIB + * stores information on IDF models ( also has an inbuilt NOMODEL model ) + * and is responsible for writing the ELECTRICAL sections of the library file + * (*.emp) and the PLACEMENT section of the board file. + */ +class IDF_LIB +{ + // TODO: IMPLEMENT + +public: + /** + * Function WriteLib + * writes all current library information to the output file + */ + bool WriteLib( FILE* aLibFile ); + + // write placement information to the board file + bool WriteBrd( FILE* aLayoutFile ); + + // bool Finish( void ) + // { + // TODO: Write out the library (*.emp) file + // idf_lib.Write( lib_file ); + // TODO: fclose( lib_file ); + // } +}; + +#endif // IDF_H diff --git a/pcbnew/menubar_pcbframe.cpp b/pcbnew/menubar_pcbframe.cpp index 0070d7d837..386dfe522d 100644 --- a/pcbnew/menubar_pcbframe.cpp +++ b/pcbnew/menubar_pcbframe.cpp @@ -204,6 +204,11 @@ void PCB_EDIT_FRAME::ReCreateMenuBar() _( "Export a VRML board representation" ), KiBitmap( three_d_xpm ) ); + // IDF3 + AddMenuItem( submenuexport, ID_GEN_EXPORT_FILE_IDF3, + _( "I&DFv3 Board Shape Export" ), _( "Basci export of board shape only IDFv3 format" ), + KiBitmap( export_xpm ) ); + AddMenuItem( filesMenu, submenuexport, ID_GEN_EXPORT_FILE, _( "E&xport" ), _( "Export board" ), KiBitmap( export_xpm ) ); diff --git a/pcbnew/pcbframe.cpp b/pcbnew/pcbframe.cpp index 58c1692d31..47d0218b3d 100644 --- a/pcbnew/pcbframe.cpp +++ b/pcbnew/pcbframe.cpp @@ -115,6 +115,7 @@ BEGIN_EVENT_TABLE( PCB_EDIT_FRAME, PCB_BASE_FRAME ) EVT_MENU( ID_GEN_EXPORT_FILE_GENCADFORMAT, PCB_EDIT_FRAME::ExportToGenCAD ) EVT_MENU( ID_GEN_EXPORT_FILE_MODULE_REPORT, PCB_EDIT_FRAME::GenFootprintsReport ) EVT_MENU( ID_GEN_EXPORT_FILE_VRML, PCB_EDIT_FRAME::OnExportVRML ) + EVT_MENU( ID_GEN_EXPORT_FILE_IDF3, PCB_EDIT_FRAME::ExportToIDF3 ) EVT_MENU( ID_GEN_IMPORT_SPECCTRA_SESSION,PCB_EDIT_FRAME::ImportSpecctraSession ) EVT_MENU( ID_GEN_IMPORT_SPECCTRA_DESIGN, PCB_EDIT_FRAME::ImportSpecctraDesign ) diff --git a/pcbnew/pcbnew_id.h b/pcbnew/pcbnew_id.h index 1a03a3764c..d4acbfedf8 100644 --- a/pcbnew/pcbnew_id.h +++ b/pcbnew/pcbnew_id.h @@ -246,6 +246,7 @@ enum pcbnew_ids ID_MENU_PCB_SWAP_LAYERS, ID_MENU_PCB_RESET_TEXTMODULE_FIELDS_SIZES, + ID_GEN_EXPORT_FILE_IDF3, ID_GEN_EXPORT_FILE_VRML, ID_GEN_EXPORT_SPECCTRA, ID_GEN_EXPORT_FILE_GENCADFORMAT,