summaryrefslogtreecommitdiff
path: root/pcbnew/exporters/export_idf.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'pcbnew/exporters/export_idf.cpp')
-rw-r--r--pcbnew/exporters/export_idf.cpp609
1 files changed, 609 insertions, 0 deletions
diff --git a/pcbnew/exporters/export_idf.cpp b/pcbnew/exporters/export_idf.cpp
new file mode 100644
index 0000000..51a61cc
--- /dev/null
+++ b/pcbnew/exporters/export_idf.cpp
@@ -0,0 +1,609 @@
+/**
+ * @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 <list>
+#include <wxPcbStruct.h>
+#include <macros.h>
+#include <pcbnew.h>
+#include <class_board.h>
+#include <class_module.h>
+#include <class_edge_mod.h>
+#include <idf_parser.h>
+#include <3d_struct.h>
+#include <build_version.h>
+#include <convert_from_iu.h>
+
+#ifndef PCBNEW
+#define PCBNEW // needed to define the right value of Millimeter2iu(x)
+#endif
+#include <convert_to_biu.h> // to define Millimeter2iu(x)
+
+// assumed default graphical line thickness: == 0.1mm
+#define LINE_WIDTH (Millimeter2iu( 0.1 ))
+
+/**
+ * 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, IDF3_BOARD& aIDFBoard )
+{
+ double scale = aIDFBoard.GetUserScale();
+
+ 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 = NULL; // 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.GetUserOffset( 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_Cuts )
+ continue;
+
+ graphic = (DRAWSEGMENT*) item;
+
+ switch( graphic->GetShape() )
+ {
+ case S_SEGMENT:
+ {
+ if( ( graphic->GetStart().x == graphic->GetEnd().x )
+ && ( graphic->GetStart().y == graphic->GetEnd().y ) )
+ break;
+
+ 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:
+ {
+ if( ( graphic->GetCenter().x == graphic->GetArcStart().x )
+ && ( graphic->GetCenter().y == graphic->GetArcStart().y ) )
+ break;
+
+ 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:
+ {
+ if( graphic->GetRadius() == 0 )
+ break;
+
+ 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
+ outline = new IDF_OUTLINE;
+ IDF3::GetOutline( lines, *outline );
+
+ if( outline->empty() )
+ goto UseBoundingBox;
+
+ aIDFBoard.AddBoardOutline( outline );
+ outline = NULL;
+
+ // get all cutouts and write them out
+ while( !lines.empty() )
+ {
+ if( !outline )
+ outline = new IDF_OUTLINE;
+
+ IDF3::GetOutline( lines, *outline );
+
+ if( outline->empty() )
+ {
+ outline->Clear();
+ continue;
+ }
+
+ aIDFBoard.AddBoardOutline( outline );
+ outline = NULL;
+ }
+
+ return;
+
+UseBoundingBox:
+
+ // clean up if necessary
+ while( !lines.empty() )
+ {
+ delete lines.front();
+ lines.pop_front();
+ }
+
+ if( outline )
+ outline->Clear();
+ else
+ outline = new IDF_OUTLINE;
+
+ // 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.AddBoardOutline( 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,
+ IDF3_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_Cuts ) ) continue;
+ // code to export cutouts
+ // }
+
+ // Export pads
+ double drill, x, y;
+ double scale = aIDFBoard.GetUserScale();
+ IDF3::KEY_PLATING kplate;
+ std::string pintype;
+ std::string tstr;
+
+ double dx, dy;
+
+ aIDFBoard.GetUserOffset( 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_ATTRIB_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_DRILL_SHAPE_OBLONG ) )
+ 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_DRILL_SHAPE_OBLONG )
+ && ( 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;
+
+ // NOTE: Since this code assumes the scenario where
+ // GetDrillSize().y is the length but idf_parser.cpp
+ // assumes a length along the X axis, the orientation
+ // must be shifted +90 deg when GetDrillSize().y is
+ // the major axis.
+
+ if( dlength < drill )
+ {
+ std::swap( drill, dlength );
+ }
+ else
+ {
+ angle += 90.0;
+ }
+
+ // 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
+ {
+ IDF_DRILL_DATA *dp = new IDF_DRILL_DATA( drill, x, y, kplate, crefdes,
+ pintype, IDF3::ECAD );
+
+ if( !aIDFBoard.AddDrill( dp ) )
+ {
+ delete dp;
+
+ std::ostringstream ostr;
+ ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__;
+ ostr << "(): could not add drill";
+
+ throw std::runtime_error( ostr.str() );
+ }
+ }
+ }
+ }
+
+ // add any valid models to the library item list
+ std::string refdes;
+
+ IDF3_COMPONENT* comp = NULL;
+
+ for( S3D_MASTER* modfile = aModule->Models(); modfile != 0; modfile = modfile->Next() )
+ {
+ if( !modfile->Is3DType( S3D_MASTER::FILE3D_IDF ) )
+ continue;
+
+ if( refdes.empty() )
+ {
+ refdes = TO_UTF8( aModule->GetReference() );
+
+ // NOREFDES cannot be used or else the software gets confused
+ // when writing out the placement data due to conflicting
+ // placement and layer specifications; to work around this we
+ // create a (hopefully) unique refdes for our exported part.
+ if( refdes.empty() || !refdes.compare( "~" ) )
+ refdes = aIDFBoard.GetNewRefDes();
+ }
+
+ IDF3_COMP_OUTLINE* outline;
+
+ outline = aIDFBoard.GetComponentOutline( modfile->GetShape3DFullFilename() );
+
+ if( !outline )
+ throw( std::runtime_error( aIDFBoard.GetError() ) );
+
+ double rotz = aModule->GetOrientation()/10.0;
+ double locx = modfile->m_MatPosition.x * 25.4; // part offsets are in inches
+ double locy = modfile->m_MatPosition.y * 25.4;
+ double locz = modfile->m_MatPosition.z * 25.4;
+ double lrot = modfile->m_MatRotation.z;
+
+ bool top = ( aModule->GetLayer() == B_Cu ) ? false : true;
+
+ if( top )
+ {
+ rotz += modfile->m_MatRotation.z;
+ locy = -locy;
+ RotatePoint( &locx, &locy, aModule->GetOrientation() );
+ locy = -locy;
+ }
+
+ if( !top )
+ {
+ RotatePoint( &locx, &locy, aModule->GetOrientation() );
+ locy = -locy;
+
+ rotz = 180.0 - rotz;
+
+ if( rotz >= 360.0 )
+ while( rotz >= 360.0 ) rotz -= 360.0;
+
+ if( rotz <= -360.0 )
+ while( rotz <= -360.0 ) rotz += 360.0;
+ }
+
+ if( comp == NULL )
+ comp = aIDFBoard.FindComponent( refdes );
+
+ if( comp == NULL )
+ {
+ comp = new IDF3_COMPONENT( &aIDFBoard );
+
+ if( comp == NULL )
+ throw( std::runtime_error( aIDFBoard.GetError() ) );
+
+ comp->SetRefDes( refdes );
+
+ if( top )
+ comp->SetPosition( aModule->GetPosition().x * scale + dx,
+ -aModule->GetPosition().y * scale + dy,
+ rotz, IDF3::LYR_TOP );
+ else
+ comp->SetPosition( aModule->GetPosition().x * scale + dx,
+ -aModule->GetPosition().y * scale + dy,
+ rotz, IDF3::LYR_BOTTOM );
+
+ comp->SetPlacement( IDF3::PS_ECAD );
+
+ aIDFBoard.AddComponent( comp );
+ }
+ else
+ {
+ double refX, refY, refA;
+ IDF3::IDF_LAYER side;
+
+ if( ! comp->GetPosition( refX, refY, refA, side ) )
+ {
+ // place the item
+ if( top )
+ comp->SetPosition( aModule->GetPosition().x * scale + dx,
+ -aModule->GetPosition().y * scale + dy,
+ rotz, IDF3::LYR_TOP );
+ else
+ comp->SetPosition( aModule->GetPosition().x * scale + dx,
+ -aModule->GetPosition().y * scale + dy,
+ rotz, IDF3::LYR_BOTTOM );
+
+ comp->SetPlacement( IDF3::PS_ECAD );
+
+ }
+ else
+ {
+ // check that the retrieved component matches this one
+ refX = refX - ( aModule->GetPosition().x * scale + dx );
+ refY = refY - ( -aModule->GetPosition().y * scale + dy );
+ refA = refA - rotz;
+ refA *= refA;
+ refX *= refX;
+ refY *= refY;
+ refX += refY;
+
+ // conditions: same side, X,Y coordinates within 10 microns,
+ // angle within 0.01 degree
+ if( ( top && side == IDF3::LYR_BOTTOM ) || ( !top && side == IDF3::LYR_TOP )
+ || ( refA > 0.0001 ) || ( refX > 0.0001 ) )
+ {
+ comp->GetPosition( refX, refY, refA, side );
+
+ std::ostringstream ostr;
+ ostr << "* " << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
+ ostr << "* conflicting Reference Designator '" << refdes << "'\n";
+ ostr << "* X loc: " << (aModule->GetPosition().x * scale + dx);
+ ostr << " vs. " << refX << "\n";
+ ostr << "* Y loc: " << (-aModule->GetPosition().y * scale + dy);
+ ostr << " vs. " << refY << "\n";
+ ostr << "* angle: " << rotz;
+ ostr << " vs. " << refA << "\n";
+
+ if( top )
+ ostr << "* TOP vs. ";
+ else
+ ostr << "* BOTTOM vs. ";
+
+ if( side == IDF3::LYR_TOP )
+ ostr << "TOP";
+ else
+ ostr << "BOTTOM";
+
+ throw( std::runtime_error( ostr.str() ) );
+ }
+ }
+ }
+
+
+ // create the local data ...
+ IDF3_COMP_OUTLINE_DATA* data = new IDF3_COMP_OUTLINE_DATA( comp, outline );
+
+ data->SetOffsets( locx, locy, locz, lrot );
+ comp->AddOutlineData( data );
+ }
+
+ return;
+}
+
+
+/**
+ * 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, bool aUseThou,
+ double aXRef, double aYRef )
+{
+ IDF3_BOARD idfBoard( IDF3::CAD_ELEC );
+
+ // Switch the locale to standard C (needed to print floating point numbers)
+ LOCALE_IO toggle;
+
+ bool ok = true;
+ double scale = MM_PER_IU; // we must scale internal units to mm for IDF
+ IDF3::IDF_UNIT idfUnit;
+
+ if( aUseThou )
+ {
+ idfUnit = IDF3::UNIT_THOU;
+ idfBoard.SetUserPrecision( 1 );
+ }
+ else
+ {
+ idfUnit = IDF3::UNIT_MM;
+ idfBoard.SetUserPrecision( 5 );
+ }
+
+ wxFileName brdName = aPcb->GetFileName();
+
+ idfBoard.SetUserScale( scale );
+ idfBoard.SetBoardThickness( aPcb->GetDesignSettings().GetBoardThickness() * scale );
+ idfBoard.SetBoardName( TO_UTF8( brdName.GetFullName() ) );
+ idfBoard.SetBoardVersion( 0 );
+ idfBoard.SetLibraryVersion( 0 );
+
+ std::ostringstream ostr;
+ ostr << "KiCad " << TO_UTF8( GetBuildVersion() );
+ idfBoard.SetIDFSource( ostr.str() );
+
+ try
+ {
+ // set up the board reference point
+ idfBoard.SetUserOffset( -aXRef, aYRef );
+
+ // 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 );
+
+ if( !idfBoard.WriteFile( aFullFileName, idfUnit, false ) )
+ {
+ wxString msg;
+ msg << _( "IDF Export Failed:\n" ) << FROM_UTF8( idfBoard.GetError().c_str() );
+ wxMessageBox( msg );
+
+ ok = false;
+ }
+ }
+ catch( const IO_ERROR& ioe )
+ {
+ wxString msg;
+ msg << _( "IDF Export Failed:\n" ) << ioe.errorText;
+ wxMessageBox( msg );
+
+ ok = false;
+ }
+ catch( const std::exception& e )
+ {
+ wxString msg;
+ msg << _( "IDF Export Failed:\n" ) << FROM_UTF8( e.what() );
+ wxMessageBox( msg );
+ ok = false;
+ }
+
+ return ok;
+}