summaryrefslogtreecommitdiff
path: root/pcbnew/exporters
diff options
context:
space:
mode:
authorsaurabhb172020-02-26 15:57:49 +0530
committersaurabhb172020-02-26 15:57:49 +0530
commitaa35045840b78d3f48212db45da59a2e5c69b223 (patch)
tree6acee185a4dc19113fcbf0f9a3d6941085dedaf7 /pcbnew/exporters
parent0db48f6533517ecebfd9f0693f89deca28408b76 (diff)
downloadKiCad-eSim-aa35045840b78d3f48212db45da59a2e5c69b223.tar.gz
KiCad-eSim-aa35045840b78d3f48212db45da59a2e5c69b223.tar.bz2
KiCad-eSim-aa35045840b78d3f48212db45da59a2e5c69b223.zip
Added main execs
Diffstat (limited to 'pcbnew/exporters')
-rw-r--r--pcbnew/exporters/export_d356.cpp397
-rw-r--r--pcbnew/exporters/export_gencad.cpp1257
-rw-r--r--pcbnew/exporters/export_idf.cpp609
-rw-r--r--pcbnew/exporters/export_vrml.cpp1408
-rw-r--r--pcbnew/exporters/gen_drill_report_files.cpp455
-rw-r--r--pcbnew/exporters/gen_modules_placefile.cpp690
-rw-r--r--pcbnew/exporters/gendrill_Excellon_writer.cpp780
-rw-r--r--pcbnew/exporters/gendrill_Excellon_writer.h368
8 files changed, 5964 insertions, 0 deletions
diff --git a/pcbnew/exporters/export_d356.cpp b/pcbnew/exporters/export_d356.cpp
new file mode 100644
index 0000000..ed9ed98
--- /dev/null
+++ b/pcbnew/exporters/export_d356.cpp
@@ -0,0 +1,397 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2011-2013 Lorenzo Marcantonio <l.marcantonio@logossrl.com>
+ * Copyright (C) 2004-2011 KiCad Developers, see change_log.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 export_d356.cpp
+ * @brief Export IPC-D-356 test format
+ */
+
+#include <fctsys.h>
+#include <class_drawpanel.h>
+#include <confirm.h>
+#include <gestfich.h>
+#include <kiface_i.h>
+#include <wxPcbStruct.h>
+#include <trigo.h>
+#include <build_version.h>
+#include <macros.h>
+
+#include <pcbnew.h>
+
+#include <class_board.h>
+#include <class_module.h>
+#include <class_track.h>
+#include <class_edge_mod.h>
+#include <vector>
+#include <cctype>
+
+/* Structure for holding the D-356 record fields.
+ * Useful because 356A (when implemented) must be sorted before outputting it */
+struct D356_RECORD
+{
+ bool smd;
+ bool hole;
+ wxString netname;
+ wxString refdes;
+ wxString pin;
+ bool midpoint;
+ int drill;
+ bool mechanical;
+ int access; // Access 0 is 'both sides'
+ int soldermask;
+ // All these in PCB units, will be output in decimils
+ int x_location;
+ int y_location;
+ int x_size;
+ int y_size;
+ int rotation;
+};
+
+// Compute the access code for a pad. Returns -1 if there is no copper
+static int compute_pad_access_code( BOARD *aPcb, LSET aLayerMask )
+{
+ // Non-copper is not interesting here
+ aLayerMask &= LSET::AllCuMask();
+ if( !aLayerMask.any() )
+ return -1;
+
+ // Traditional TH pad
+ if( aLayerMask[F_Cu] && aLayerMask[B_Cu] )
+ return 0;
+
+ // Front SMD pad
+ if( aLayerMask[F_Cu] )
+ return 1;
+
+ // Back SMD pad
+ if( aLayerMask[B_Cu] )
+ return aPcb->GetCopperLayerCount();
+
+ // OK, we have an inner-layer only pad (and I have no idea about
+ // what could be used for); anyway, find the first copper layer
+ // it's on
+ for( LAYER_NUM layer = In1_Cu; layer < B_Cu; ++layer )
+ {
+ if( aLayerMask[layer] )
+ return layer + 1;
+ }
+
+ // This shouldn't happen
+ return -1;
+}
+
+/* Convert and clamp a size from IU to decimils */
+static int iu_to_d356(int iu, int clamp)
+{
+ int val = KiROUND( iu / IU_PER_DECIMILS );
+ if( val > clamp ) return clamp;
+ if( val < -clamp ) return -clamp;
+ return val;
+}
+
+/* Extract the D356 record from the modules (pads) */
+static void build_pad_testpoints( BOARD *aPcb,
+ std::vector <D356_RECORD>& aRecords )
+{
+ wxPoint origin = aPcb->GetAuxOrigin();
+
+ for( MODULE *module = aPcb->m_Modules;
+ module; module = module->Next() )
+ {
+ for( D_PAD *pad = module->Pads(); pad; pad = pad->Next() )
+ {
+ D356_RECORD rk;
+ rk.access = compute_pad_access_code( aPcb, pad->GetLayerSet() );
+
+ // It could be a mask only pad, we only handle pads with copper here
+ if( rk.access != -1 )
+ {
+ rk.netname = pad->GetNetname();
+ rk.refdes = module->GetReference();
+ pad->StringPadName( rk.pin );
+ rk.midpoint = false; // XXX MAYBE need to be computed (how?)
+ const wxSize& drill = pad->GetDrillSize();
+ rk.drill = std::min( drill.x, drill.y );
+ rk.hole = (rk.drill != 0);
+ rk.smd = pad->GetAttribute() == PAD_ATTRIB_SMD;
+ rk.mechanical = (pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED);
+ rk.x_location = pad->GetPosition().x - origin.x;
+ rk.y_location = origin.y - pad->GetPosition().y;
+ rk.x_size = pad->GetSize().x;
+
+ // Rule: round pads have y = 0
+ if( pad->GetShape() == PAD_SHAPE_CIRCLE )
+ rk.y_size = 0;
+ else
+ rk.y_size = pad->GetSize().y;
+
+ rk.rotation = -KiROUND( pad->GetOrientation() ) / 10;
+ if( rk.rotation < 0 ) rk.rotation += 360;
+
+ // the value indicates which sides are *not* accessible
+ rk.soldermask = 3;
+ if( pad->GetLayerSet()[F_Mask] )
+ rk.soldermask &= ~1;
+ if( pad->GetLayerSet()[B_Mask] )
+ rk.soldermask &= ~2;
+
+ aRecords.push_back( rk );
+ }
+ }
+ }
+}
+
+/* Compute the access code for a via. In D-356 layers are numbered from 1 up,
+ where '1' is the 'primary side' (usually the component side);
+ '0' means 'both sides', and other layers follows in an unspecified order */
+static int via_access_code( BOARD *aPcb, int top_layer, int bottom_layer )
+{
+ // Easy case for through vias: top_layer is component, bottom_layer is
+ // solder, access code is 0
+ if( (top_layer == F_Cu) && (bottom_layer == B_Cu) )
+ return 0;
+
+ // Blind via, reachable from front
+ if( top_layer == F_Cu )
+ return 1;
+
+ // Blind via, reachable from bottom
+ if( bottom_layer == B_Cu )
+ return aPcb->GetCopperLayerCount();
+
+ // It's a buried via, accessible from some inner layer
+ // (maybe could be used for testing before laminating? no idea)
+ return bottom_layer + 1; // XXX is this correct?
+}
+
+/* Extract the D356 record from the vias */
+static void build_via_testpoints( BOARD *aPcb,
+ std::vector <D356_RECORD>& aRecords )
+{
+ wxPoint origin = aPcb->GetAuxOrigin();
+
+ // Enumerate all the track segments and keep the vias
+ for( TRACK *track = aPcb->m_Track; track; track = track->Next() )
+ {
+ if( track->Type() == PCB_VIA_T )
+ {
+ VIA *via = (VIA*) track;
+ NETINFO_ITEM *net = track->GetNet();
+
+ D356_RECORD rk;
+ rk.smd = false;
+ rk.hole = true;
+ if( net )
+ rk.netname = net->GetNetname();
+ else
+ rk.netname = wxEmptyString;
+ rk.refdes = wxT("VIA");
+ rk.pin = wxT("");
+ rk.midpoint = true; // Vias are always midpoints
+ rk.drill = via->GetDrillValue();
+ rk.mechanical = false;
+
+ LAYER_ID top_layer, bottom_layer;
+
+ via->LayerPair( &top_layer, &bottom_layer );
+
+ rk.access = via_access_code( aPcb, top_layer, bottom_layer );
+ rk.x_location = via->GetPosition().x - origin.x;
+ rk.y_location = origin.y - via->GetPosition().y;
+ rk.x_size = via->GetWidth();
+ rk.y_size = 0; // Round so height = 0
+ rk.rotation = 0;
+ rk.soldermask = 3; // XXX always tented?
+
+ aRecords.push_back( rk );
+ }
+ }
+}
+
+/* Add a new netname to the d356 canonicalized list */
+static const wxString intern_new_d356_netname( const wxString &aNetname,
+ std::map<wxString, wxString> &aMap, std::set<wxString> &aSet )
+{
+ wxString canon;
+ for (wxString::const_iterator i = aNetname.begin();
+ i != aNetname.end(); ++i)
+ {
+ // Rule: we can only use the standard ASCII, control excluded
+ char ch = *i;
+ if( ch > 126 || !std::isgraph( ch ) )
+ ch = '?';
+ canon += ch;
+ }
+
+ // Rule: only uppercase (unofficial, but known to give problems
+ // otherwise)
+ canon.MakeUpper();
+
+ // Rule: maximum length is 14 characters, otherwise we keep the tail
+ if( canon.size() > 14 )
+ {
+ canon = canon.Right( 14 );
+ }
+
+ // Check if it's still unique
+ if( aSet.count( canon ) )
+ {
+ // Nope, need to uniquify it, trim it more and add a number
+ wxString base( canon );
+ if( base.size() > 10 )
+ {
+ base = base.Right( 10 );
+ }
+
+ int ctr = 0;
+ do
+ {
+ ++ctr;
+ canon = base;
+ canon << '#' << ctr;
+ } while ( aSet.count( canon ) );
+ }
+
+ // Register it
+ aMap[aNetname] = canon;
+ aSet.insert( canon );
+ return canon;
+}
+
+/* Write all the accumuled data to the file in D356 format */
+static void write_D356_records( std::vector <D356_RECORD> &aRecords,
+ FILE *fout )
+{
+ // Sanified and shorted network names and set of short names
+ std::map<wxString, wxString> d356_net_map;
+ std::set<wxString> d356_net_set;
+
+ for (unsigned i = 0; i < aRecords.size(); i++)
+ {
+ D356_RECORD &rk = aRecords[i];
+
+ // Try to sanify the network name (there are limits on this), if
+ // not already done. Also 'empty' net are marked as N/C, as
+ // specified.
+ wxString d356_net( wxT("N/C") );
+ if( !rk.netname.empty() )
+ {
+ d356_net = d356_net_map[rk.netname];
+
+ if( d356_net.empty() )
+ d356_net = intern_new_d356_netname( rk.netname, d356_net_map,
+ d356_net_set );
+ }
+
+ // Choose the best record type
+ int rktype;
+ if( rk.smd )
+ rktype = 327;
+ else
+ {
+ if( rk.mechanical )
+ rktype = 367;
+ else
+ rktype = 317;
+ }
+
+ // Operation code, signal and component
+ fprintf( fout, "%03d%-14.14s %-6.6s%c%-4.4s%c",
+ rktype, TO_UTF8(d356_net),
+ TO_UTF8(rk.refdes),
+ rk.pin.empty()?' ':'-',
+ TO_UTF8(rk.pin),
+ rk.midpoint?'M':' ' );
+
+ // Hole definition
+ if( rk.hole )
+ {
+ fprintf( fout, "D%04d%c",
+ iu_to_d356( rk.drill, 9999 ),
+ rk.mechanical ? 'U':'P' );
+ }
+ else
+ fprintf( fout, " " );
+
+ // Test point access
+ fprintf( fout, "A%02dX%+07dY%+07dX%04dY%04dR%03d",
+ rk.access,
+ iu_to_d356( rk.x_location, 999999 ),
+ iu_to_d356( rk.y_location, 999999 ),
+ iu_to_d356( rk.x_size, 9999 ),
+ iu_to_d356( rk.y_size, 9999 ),
+ rk.rotation );
+
+ // Soldermask
+ fprintf( fout, "S%d\n", rk.soldermask );
+ }
+}
+
+
+void PCB_EDIT_FRAME::GenD356File( wxCommandEvent& aEvent )
+{
+ wxFileName fn = GetBoard()->GetFileName();
+ wxString msg, ext, wildcard;
+ FILE* file;
+
+ ext = wxT( "d356" );
+ wildcard = _( "IPC-D-356 Test Files (.d356)|*.d356" );
+ fn.SetExt( ext );
+
+ wxString pro_dir = wxPathOnly( Prj().GetProjectFullName() );
+
+ wxFileDialog dlg( this, _( "Export D-356 Test File" ), pro_dir,
+ fn.GetFullName(), wildcard,
+ wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
+
+ if( dlg.ShowModal() == wxID_CANCEL )
+ return;
+
+ if( ( file = wxFopen( dlg.GetPath(), wxT( "wt" ) ) ) == NULL )
+ {
+ msg = _( "Unable to create " ) + dlg.GetPath();
+ DisplayError( this, msg ); return;
+ }
+
+ LOCALE_IO toggle; // Switch the locale to standard C
+
+ // This will contain everything needed for the 356 file
+ std::vector <D356_RECORD> d356_records;
+ BOARD* pcb = GetBoard();
+
+ build_via_testpoints( pcb, d356_records );
+
+ build_pad_testpoints( pcb, d356_records );
+
+ // Code 00 AFAIK is ASCII, CUST 0 is decimils/degrees
+ // CUST 1 would be metric but gerbtool simply ignores it!
+ fprintf( file, "P CODE 00\n" );
+ fprintf( file, "P UNITS CUST 0\n" );
+ fprintf( file, "P DIM N\n" );
+ write_D356_records( d356_records, file );
+ fprintf( file, "999\n" );
+
+ fclose( file );
+}
+
diff --git a/pcbnew/exporters/export_gencad.cpp b/pcbnew/exporters/export_gencad.cpp
new file mode 100644
index 0000000..2166021
--- /dev/null
+++ b/pcbnew/exporters/export_gencad.cpp
@@ -0,0 +1,1257 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr
+ * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
+ * Copyright (C) 2012 Wayne Stambaugh <stambaughw@verizon.net>
+ * Copyright (C) 1992-2012 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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 export_gencad.cpp
+ * @brief Export GenCAD 1.4 format.
+ */
+
+#include <fctsys.h>
+#include <class_drawpanel.h>
+#include <confirm.h>
+#include <gestfich.h>
+#include <pgm_base.h>
+#include <wxPcbStruct.h>
+#include <trigo.h>
+#include <build_version.h>
+#include <macros.h>
+
+#include <pcbnew.h>
+
+#include <class_board.h>
+#include <class_module.h>
+#include <class_track.h>
+#include <class_edge_mod.h>
+
+
+static bool CreateHeaderInfoData( FILE* aFile, PCB_EDIT_FRAME* frame );
+static void CreateArtworksSection( FILE* aFile );
+static void CreateTracksInfoData( FILE* aFile, BOARD* aPcb );
+static void CreateBoardSection( FILE* aFile, BOARD* aPcb );
+static void CreateComponentsSection( FILE* aFile, BOARD* aPcb );
+static void CreateDevicesSection( FILE* aFile, BOARD* aPcb );
+static void CreateRoutesSection( FILE* aFile, BOARD* aPcb );
+static void CreateSignalsSection( FILE* aFile, BOARD* aPcb );
+static void CreateShapesSection( FILE* aFile, BOARD* aPcb );
+static void CreatePadsShapesSection( FILE* aFile, BOARD* aPcb );
+static void FootprintWriteShape( FILE* File, MODULE* module );
+
+// layer names for Gencad export
+
+#if 0 // was:
+static const wxString GenCADLayerName[] =
+{
+ wxT( "BOTTOM" ), wxT( "INNER1" ), wxT( "INNER2" ),
+ wxT( "INNER3" ), wxT( "INNER4" ), wxT( "INNER5" ),
+ wxT( "INNER6" ), wxT( "INNER7" ), wxT( "INNER8" ),
+ wxT( "INNER9" ), wxT( "INNER10" ), wxT( "INNER11" ),
+ wxT( "INNER12" ), wxT( "INNER13" ), wxT( "INNER14" ),
+ wxT( "TOP" ), wxT( "LAYER17" ), wxT( "LAYER18" ),
+ wxT( "SOLDERPASTE_BOTTOM" ), wxT( "SOLDERPASTE_TOP" ),
+ wxT( "SILKSCREEN_BOTTOM" ), wxT( "SILKSCREEN_TOP" ),
+ wxT( "SOLDERMASK_BOTTOM" ), wxT( "SOLDERMASK_TOP" ), wxT( "LAYER25" ),
+ wxT( "LAYER26" ), wxT( "LAYER27" ), wxT( "LAYER28" ),
+ wxT( "LAYER29" ), wxT( "LAYER30" ), wxT( "LAYER31" ),
+ wxT( "LAYER32" )
+};
+
+// flipped layer name for Gencad export (to make CAM350 imports correct)
+static const wxString GenCADLayerNameFlipped[32] =
+{
+ wxT( "TOP" ), wxT( "INNER14" ), wxT( "INNER13" ),
+ wxT( "INNER12" ), wxT( "INNER11" ), wxT( "INNER10" ),
+ wxT( "INNER9" ), wxT( "INNER8" ), wxT( "INNER7" ),
+ wxT( "INNER6" ), wxT( "INNER5" ), wxT( "INNER4" ),
+ wxT( "INNER3" ), wxT( "INNER2" ), wxT( "INNER1" ),
+ wxT( "BOTTOM" ), wxT( "LAYER17" ), wxT( "LAYER18" ),
+ wxT( "SOLDERPASTE_TOP" ), wxT( "SOLDERPASTE_BOTTOM" ),
+ wxT( "SILKSCREEN_TOP" ), wxT( "SILKSCREEN_BOTTOM" ),
+ wxT( "SOLDERMASK_TOP" ), wxT( "SOLDERMASK_BOTTOM" ), wxT( "LAYER25" ),
+ wxT( "LAYER26" ), wxT( "LAYER27" ), wxT( "LAYER28" ),
+ wxT( "LAYER29" ), wxT( "LAYER30" ), wxT( "LAYER31" ),
+ wxT( "LAYER32" )
+};
+
+#else
+
+static std::string GenCADLayerName( int aCuCount, LAYER_ID aId )
+{
+ if( IsCopperLayer( aId ) )
+ {
+ if( aId == F_Cu )
+ return "TOP";
+ else if( aId == B_Cu )
+ return "BOTTOM";
+
+ else if( aId <= 14 )
+ {
+ return StrPrintf( "INNER%d", aCuCount - aId - 1 );
+ }
+ else
+ {
+ return StrPrintf( "LAYER%d", aId );
+ }
+ }
+
+ else
+ {
+ const char* txt;
+
+ // using a switch to clearly show mapping & catch out of bounds index.
+ switch( aId )
+ {
+ // Technicals
+ case B_Adhes: txt = "B.Adhes"; break;
+ case F_Adhes: txt = "F.Adhes"; break;
+ case B_Paste: txt = "SOLDERPASTE_BOTTOM"; break;
+ case F_Paste: txt = "SOLDERPASTE_TOP"; break;
+ case B_SilkS: txt = "SILKSCREEN_BOTTOM"; break;
+ case F_SilkS: txt = "SILKSCREEN_TOP"; break;
+ case B_Mask: txt = "SOLDERMASK_BOTTOM"; break;
+ case F_Mask: txt = "SOLDERMASK_TOP"; break;
+
+ // Users
+ case Dwgs_User: txt = "Dwgs.User"; break;
+ case Cmts_User: txt = "Cmts.User"; break;
+ case Eco1_User: txt = "Eco1.User"; break;
+ case Eco2_User: txt = "Eco2.User"; break;
+ case Edge_Cuts: txt = "Edge.Cuts"; break;
+ case Margin: txt = "Margin"; break;
+
+ // Footprint
+ case F_CrtYd: txt = "F_CrtYd"; break;
+ case B_CrtYd: txt = "B_CrtYd"; break;
+ case F_Fab: txt = "F_Fab"; break;
+ case B_Fab: txt = "B_Fab"; break;
+
+ default:
+ wxASSERT_MSG( 0, wxT( "aId UNEXPECTED" ) );
+ txt = "BAD-INDEX!"; break;
+ }
+
+ return txt;
+ }
+};
+
+
+static const LAYER_ID gc_seq[] = {
+ B_Cu,
+ In30_Cu,
+ In29_Cu,
+ In28_Cu,
+ In27_Cu,
+ In26_Cu,
+ In25_Cu,
+ In24_Cu,
+ In23_Cu,
+ In22_Cu,
+ In21_Cu,
+ In20_Cu,
+ In19_Cu,
+ In18_Cu,
+ In17_Cu,
+ In16_Cu,
+ In15_Cu,
+ In14_Cu,
+ In13_Cu,
+ In12_Cu,
+ In11_Cu,
+ In10_Cu,
+ In9_Cu,
+ In8_Cu,
+ In7_Cu,
+ In6_Cu,
+ In5_Cu,
+ In4_Cu,
+ In3_Cu,
+ In2_Cu,
+ In1_Cu,
+ F_Cu,
+};
+
+
+// flipped layer name for Gencad export (to make CAM350 imports correct)
+static std::string GenCADLayerNameFlipped( int aCuCount, LAYER_ID aId )
+{
+ if( 1<= aId && aId <= 14 )
+ {
+ return StrPrintf( "INNER%d", 14 - aId );
+ }
+
+ return GenCADLayerName( aCuCount, aId );
+};
+
+
+#endif
+
+static std::string fmt_mask( LSET aSet )
+{
+#if 0
+ return aSet.FmtHex();
+#else
+ return StrPrintf( "%08x", (unsigned) ( aSet & LSET::AllCuMask() ).to_ulong() );
+#endif
+}
+
+
+// These are the export origin (the auxiliary axis)
+static int GencadOffsetX, GencadOffsetY;
+
+/* GerbTool chokes on units different than INCH so this is the conversion
+ * factor */
+const static double SCALE_FACTOR = 10000.0 * IU_PER_DECIMILS;
+
+
+/* Two helper functions to calculate coordinates of modules in gencad values
+ * (GenCAD Y axis from bottom to top)
+ */
+static double MapXTo( int aX )
+{
+ return (aX - GencadOffsetX) / SCALE_FACTOR;
+}
+
+
+static double MapYTo( int aY )
+{
+ return (GencadOffsetY - aY) / SCALE_FACTOR;
+}
+
+
+/* Driver function: processing starts here */
+void PCB_EDIT_FRAME::ExportToGenCAD( wxCommandEvent& aEvent )
+{
+ wxFileName fn = GetBoard()->GetFileName();
+ FILE* file;
+
+ wxString ext = wxT( "cad" );
+ wxString wildcard = _( "GenCAD 1.4 board files (.cad)|*.cad" );
+
+ fn.SetExt( ext );
+
+ wxString pro_dir = wxPathOnly( Prj().GetProjectFullName() );
+
+ wxFileDialog dlg( this, _( "Save GenCAD Board File" ), pro_dir,
+ fn.GetFullName(), wildcard,
+ wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
+
+ if( dlg.ShowModal() == wxID_CANCEL )
+ return;
+
+ if( ( file = wxFopen( dlg.GetPath(), wxT( "wt" ) ) ) == NULL )
+ {
+ wxString msg;
+
+ msg.Printf( _( "Unable to create <%s>" ), GetChars( dlg.GetPath() ) );
+ DisplayError( this, msg ); return;
+ }
+
+ // Switch the locale to standard C (needed to print floating point numbers)
+ LOCALE_IO toggle;
+
+ // Update some board data, to ensure a reliable gencad export
+ GetBoard()->ComputeBoundingBox();
+
+ // Save the auxiliary origin for the rest of the module
+ GencadOffsetX = GetAuxOrigin().x;
+ GencadOffsetY = GetAuxOrigin().y;
+
+ // No idea on *why* this should be needed... maybe to fix net names?
+ Compile_Ratsnest( NULL, true );
+
+ /* Temporary modification of footprints that are flipped (i.e. on bottom
+ * layer) to convert them to non flipped footprints.
+ * This is necessary to easily export shapes to GenCAD,
+ * that are given as normal orientation (non flipped, rotation = 0))
+ * these changes will be undone later
+ */
+ BOARD* pcb = GetBoard();
+ MODULE* module;
+
+ for( module = pcb->m_Modules; module; module = module->Next() )
+ {
+ module->SetFlag( 0 );
+
+ if( module->GetLayer() == B_Cu )
+ {
+ module->Flip( module->GetPosition() );
+ module->SetFlag( 1 );
+ }
+ }
+
+ /* Gencad has some mandatory and some optional sections: some importer
+ * need the padstack section (which is optional) anyway. Also the
+ * order of the section *is* important */
+
+ CreateHeaderInfoData( file, this ); // Gencad header
+ CreateBoardSection( file, pcb ); // Board perimeter
+
+ CreatePadsShapesSection( file, pcb ); // Pads and padstacks
+ CreateArtworksSection( file ); // Empty but mandatory
+
+ /* Gencad splits a component info in shape, component and device.
+ * We don't do any sharing (it would be difficult since each module is
+ * customizable after placement) */
+ CreateShapesSection( file, pcb );
+ CreateComponentsSection( file, pcb );
+ CreateDevicesSection( file, pcb );
+
+ // In a similar way the netlist is split in net, track and route
+ CreateSignalsSection( file, pcb );
+ CreateTracksInfoData( file, pcb );
+ CreateRoutesSection( file, pcb );
+
+ fclose( file );
+
+ // Undo the footprints modifications (flipped footprints)
+ for( module = pcb->m_Modules; module; module = module->Next() )
+ {
+ if( module->GetFlag() )
+ {
+ module->Flip( module->GetPosition() );
+ module->SetFlag( 0 );
+ }
+ }
+}
+
+
+// Comparator for sorting pads with qsort
+static int PadListSortByShape( const void* aRefptr, const void* aObjptr )
+{
+ const D_PAD* padref = *(D_PAD**) aRefptr;
+ const D_PAD* padcmp = *(D_PAD**) aObjptr;
+
+ return D_PAD::Compare( padref, padcmp );
+}
+
+
+// Sort vias for uniqueness
+static int ViaSort( const void* aRefptr, const void* aObjptr )
+{
+ VIA* padref = *(VIA**) aRefptr;
+ VIA* padcmp = *(VIA**) aObjptr;
+
+ if( padref->GetWidth() != padcmp->GetWidth() )
+ return padref->GetWidth() - padcmp->GetWidth();
+
+ if( padref->GetDrillValue() != padcmp->GetDrillValue() )
+ return padref->GetDrillValue() - padcmp->GetDrillValue();
+
+ if( padref->GetLayerSet() != padcmp->GetLayerSet() )
+ return padref->GetLayerSet().FmtBin().compare( padcmp->GetLayerSet().FmtBin() );
+
+ return 0;
+}
+
+
+// The ARTWORKS section is empty but (officially) mandatory
+static void CreateArtworksSection( FILE* aFile )
+{
+ /* The artworks section is empty */
+ fputs( "$ARTWORKS\n", aFile );
+ fputs( "$ENDARTWORKS\n\n", aFile );
+}
+
+
+// Emit PADS and PADSTACKS. They are sorted and emitted uniquely.
+// Via name is synthesized from their attributes, pads are numbered
+static void CreatePadsShapesSection( FILE* aFile, BOARD* aPcb )
+{
+ std::vector<D_PAD*> pads;
+ std::vector<D_PAD*> padstacks;
+ std::vector<VIA*> vias;
+ std::vector<VIA*> viastacks;
+
+ padstacks.resize( 1 ); // We count pads from 1
+
+ // The master layermask (i.e. the enabled layers) for padstack generation
+ LSET master_layermask = aPcb->GetDesignSettings().GetEnabledLayers();
+ int cu_count = aPcb->GetCopperLayerCount();
+
+ fputs( "$PADS\n", aFile );
+
+ // Enumerate and sort the pads
+ if( aPcb->GetPadCount() > 0 )
+ {
+ pads = aPcb->GetPads();
+ qsort( &pads[0], aPcb->GetPadCount(), sizeof( D_PAD* ),
+ PadListSortByShape );
+ }
+
+ // The same for vias
+ for( VIA* via = GetFirstVia( aPcb->m_Track ); via;
+ via = GetFirstVia( via->Next() ) )
+ {
+ vias.push_back( via );
+ }
+
+ qsort( &vias[0], vias.size(), sizeof(VIA*), ViaSort );
+
+ // Emit vias pads
+ TRACK* old_via = 0;
+
+ for( unsigned i = 0; i < vias.size(); i++ )
+ {
+ VIA* via = vias[i];
+
+ if( old_via && 0 == ViaSort( &old_via, &via ) )
+ continue;
+
+ old_via = via;
+ viastacks.push_back( via );
+ fprintf( aFile, "PAD V%d.%d.%s ROUND %g\nCIRCLE 0 0 %g\n",
+ via->GetWidth(), via->GetDrillValue(),
+ fmt_mask( via->GetLayerSet() ).c_str(),
+ via->GetDrillValue() / SCALE_FACTOR,
+ via->GetWidth() / (SCALE_FACTOR * 2) );
+ }
+
+ // Emit component pads
+ D_PAD* old_pad = 0;
+ int pad_name_number = 0;
+
+ for( unsigned i = 0; i<pads.size(); ++i )
+ {
+ D_PAD* pad = pads[i];
+
+ pad->SetSubRatsnest( pad_name_number );
+
+ if( old_pad && 0==D_PAD::Compare( old_pad, pad ) )
+ continue; // already created
+
+ old_pad = pad;
+
+ pad_name_number++;
+ pad->SetSubRatsnest( pad_name_number );
+
+ fprintf( aFile, "PAD P%d", pad->GetSubRatsnest() );
+
+ padstacks.push_back( pad ); // Will have its own padstack later
+ int dx = pad->GetSize().x / 2;
+ int dy = pad->GetSize().y / 2;
+
+ switch( pad->GetShape() )
+ {
+ default:
+ case PAD_SHAPE_CIRCLE:
+ fprintf( aFile, " ROUND %g\n",
+ pad->GetDrillSize().x / SCALE_FACTOR );
+ /* Circle is center, radius */
+ fprintf( aFile, "CIRCLE %g %g %g\n",
+ pad->GetOffset().x / SCALE_FACTOR,
+ -pad->GetOffset().y / SCALE_FACTOR,
+ pad->GetSize().x / (SCALE_FACTOR * 2) );
+ break;
+
+ case PAD_SHAPE_RECT:
+ fprintf( aFile, " RECTANGULAR %g\n",
+ pad->GetDrillSize().x / SCALE_FACTOR );
+
+ // Rectangle is begin, size *not* begin, end!
+ fprintf( aFile, "RECTANGLE %g %g %g %g\n",
+ (-dx + pad->GetOffset().x ) / SCALE_FACTOR,
+ (-dy - pad->GetOffset().y ) / SCALE_FACTOR,
+ dx / (SCALE_FACTOR / 2), dy / (SCALE_FACTOR / 2) );
+ break;
+
+ case PAD_SHAPE_OVAL: // Create outline by 2 lines and 2 arcs
+ {
+ // OrCAD Layout call them OVAL or OBLONG - GenCAD call them FINGERs
+ fprintf( aFile, " FINGER %g\n",
+ pad->GetDrillSize().x / SCALE_FACTOR );
+ int dr = dx - dy;
+
+ if( dr >= 0 ) // Horizontal oval
+ {
+ int radius = dy;
+ fprintf( aFile, "LINE %g %g %g %g\n",
+ (-dr + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y - radius) / SCALE_FACTOR,
+ (dr + pad->GetOffset().x ) / SCALE_FACTOR,
+ (-pad->GetOffset().y - radius) / SCALE_FACTOR );
+
+ // GenCAD arcs are (start, end, center)
+ fprintf( aFile, "ARC %g %g %g %g %g %g\n",
+ (dr + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y - radius) / SCALE_FACTOR,
+ (dr + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y + radius) / SCALE_FACTOR,
+ (dr + pad->GetOffset().x) / SCALE_FACTOR,
+ -pad->GetOffset().y / SCALE_FACTOR );
+
+ fprintf( aFile, "LINE %g %g %g %g\n",
+ (dr + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y + radius) / SCALE_FACTOR,
+ (-dr + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y + radius) / SCALE_FACTOR );
+ fprintf( aFile, "ARC %g %g %g %g %g %g\n",
+ (-dr + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y + radius) / SCALE_FACTOR,
+ (-dr + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y - radius) / SCALE_FACTOR,
+ (-dr + pad->GetOffset().x) / SCALE_FACTOR,
+ -pad->GetOffset().y / SCALE_FACTOR );
+ }
+ else // Vertical oval
+ {
+ dr = -dr;
+ int radius = dx;
+ fprintf( aFile, "LINE %g %g %g %g\n",
+ (-radius + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y - dr) / SCALE_FACTOR,
+ (-radius + pad->GetOffset().x ) / SCALE_FACTOR,
+ (-pad->GetOffset().y + dr) / SCALE_FACTOR );
+ fprintf( aFile, "ARC %g %g %g %g %g %g\n",
+ (-radius + pad->GetOffset().x ) / SCALE_FACTOR,
+ (-pad->GetOffset().y + dr) / SCALE_FACTOR,
+ (radius + pad->GetOffset().x ) / SCALE_FACTOR,
+ (-pad->GetOffset().y + dr) / SCALE_FACTOR,
+ pad->GetOffset().x / SCALE_FACTOR,
+ (-pad->GetOffset().y + dr) / SCALE_FACTOR );
+
+ fprintf( aFile, "LINE %g %g %g %g\n",
+ (radius + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y + dr) / SCALE_FACTOR,
+ (radius + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y - dr) / SCALE_FACTOR );
+ fprintf( aFile, "ARC %g %g %g %g %g %g\n",
+ (radius + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y - dr) / SCALE_FACTOR,
+ (-radius + pad->GetOffset().x) / SCALE_FACTOR,
+ (-pad->GetOffset().y - dr) / SCALE_FACTOR,
+ pad->GetOffset().x / SCALE_FACTOR,
+ (-pad->GetOffset().y - dr) / SCALE_FACTOR );
+ }
+ }
+ break;
+
+ case PAD_SHAPE_TRAPEZOID:
+ fprintf( aFile, " POLYGON %g\n",
+ pad->GetDrillSize().x / SCALE_FACTOR );
+
+ // XXX TO BE IMPLEMENTED! and I don't know if it could be actually imported by something
+ break;
+ }
+ }
+
+ fputs( "\n$ENDPADS\n\n", aFile );
+
+ // Now emit the padstacks definitions, using the combined layer masks
+ fputs( "$PADSTACKS\n", aFile );
+
+ // Via padstacks
+ for( unsigned i = 0; i < viastacks.size(); i++ )
+ {
+ VIA* via = viastacks[i];
+
+ LSET mask = via->GetLayerSet() & master_layermask;
+
+ fprintf( aFile, "PADSTACK VIA%d.%d.%s %g\n",
+ via->GetWidth(), via->GetDrillValue(),
+ fmt_mask( mask ).c_str(),
+ via->GetDrillValue() / SCALE_FACTOR );
+
+ for( LSEQ seq = mask.Seq( gc_seq, DIM( gc_seq ) ); seq; ++seq )
+ {
+ LAYER_ID layer = *seq;
+
+ fprintf( aFile, "PAD V%d.%d.%s %s 0 0\n",
+ via->GetWidth(), via->GetDrillValue(),
+ fmt_mask( mask ).c_str(),
+ GenCADLayerName( cu_count, layer ).c_str()
+ );
+ }
+ }
+
+ /* Component padstacks
+ * CAM350 don't apply correctly the FLIP semantics for padstacks, i.e. doesn't
+ * swap the top and bottom layers... so I need to define the shape as MIRRORX
+ * and define a separate 'flipped' padstack... until it appears yet another
+ * noncompliant importer */
+ for( unsigned i = 1; i < padstacks.size(); i++ )
+ {
+ D_PAD* pad = padstacks[i];
+
+ // Straight padstack
+ fprintf( aFile, "PADSTACK PAD%u %g\n", i, pad->GetDrillSize().x / SCALE_FACTOR );
+
+ LSET pad_set = pad->GetLayerSet() & master_layermask;
+
+ // the special gc_seq
+ for( LSEQ seq = pad_set.Seq( gc_seq, DIM( gc_seq ) ); seq; ++seq )
+ {
+ LAYER_ID layer = *seq;
+
+ fprintf( aFile, "PAD P%u %s 0 0\n", i, GenCADLayerName( cu_count, layer ).c_str() );
+ }
+
+ // Flipped padstack
+ fprintf( aFile, "PADSTACK PAD%uF %g\n", i, pad->GetDrillSize().x / SCALE_FACTOR );
+
+ // the normal LAYER_ID sequence is inverted from gc_seq[]
+ for( LSEQ seq = pad_set.Seq(); seq; ++seq )
+ {
+ LAYER_ID layer = *seq;
+
+ fprintf( aFile, "PAD P%u %s 0 0\n", i, GenCADLayerNameFlipped( cu_count, layer ).c_str() );
+ }
+ }
+
+ fputs( "$ENDPADSTACKS\n\n", aFile );
+}
+
+
+/* Creates the footprint shape list.
+ * Since module shape is customizable after the placement we cannot share them;
+ * instead we opt for the one-module-one-shape-one-component-one-device approach
+ */
+static void CreateShapesSection( FILE* aFile, BOARD* aPcb )
+{
+ MODULE* module;
+ D_PAD* pad;
+ const char* layer;
+ wxString pinname;
+ const char* mirror = "0";
+
+ fputs( "$SHAPES\n", aFile );
+
+ const LSET all_cu = LSET::AllCuMask();
+
+ for( module = aPcb->m_Modules; module; module = module->Next() )
+ {
+ FootprintWriteShape( aFile, module );
+
+ for( pad = module->Pads(); pad; pad = pad->Next() )
+ {
+ /* Funny thing: GenCAD requires the pad side even if you use
+ * padstacks (which are theorically optional but gerbtools
+ *requires* them). Now the trouble thing is that 'BOTTOM'
+ * is interpreted by someone as a padstack flip even
+ * if the spec explicitly says it's not... */
+ layer = "ALL";
+
+ if( ( pad->GetLayerSet() & all_cu ) == LSET( B_Cu ) )
+ {
+ layer = module->GetFlag() ? "TOP" : "BOTTOM";
+ }
+ else if( ( pad->GetLayerSet() & all_cu ) == LSET( F_Cu ) )
+ {
+ layer = module->GetFlag() ? "BOTTOM" : "TOP";
+ }
+
+ pad->StringPadName( pinname );
+
+ if( pinname.IsEmpty() )
+ pinname = wxT( "none" );
+
+ double orient = pad->GetOrientation() - module->GetOrientation();
+ NORMALIZE_ANGLE_POS( orient );
+
+ // Bottom side modules use the flipped padstack
+ fprintf( aFile, (module->GetFlag()) ?
+ "PIN %s PAD%dF %g %g %s %g %s\n" :
+ "PIN %s PAD%d %g %g %s %g %s\n",
+ TO_UTF8( pinname ), pad->GetSubRatsnest(),
+ pad->GetPos0().x / SCALE_FACTOR,
+ -pad->GetPos0().y / SCALE_FACTOR,
+ layer, orient / 10.0, mirror );
+ }
+ }
+
+ fputs( "$ENDSHAPES\n\n", aFile );
+}
+
+
+/* Creates the section $COMPONENTS (Footprints placement)
+ * Bottom side components are difficult to handle: shapes must be mirrored or
+ * flipped, silk layers need to be handled correctly and so on. Also it seems
+ * that *noone* follows the specs...
+ */
+static void CreateComponentsSection( FILE* aFile, BOARD* aPcb )
+{
+ fputs( "$COMPONENTS\n", aFile );
+
+ int cu_count = aPcb->GetCopperLayerCount();
+
+ for( MODULE* module = aPcb->m_Modules; module; module = module->Next() )
+ {
+ const char* mirror;
+ const char* flip;
+ double orient = module->GetOrientation();
+
+ if( module->GetFlag() )
+ {
+ mirror = "0";
+ flip = "FLIP";
+ NEGATE_AND_NORMALIZE_ANGLE_POS( orient );
+ }
+ else
+ {
+ mirror = "0";
+ flip = "0";
+ }
+
+ fprintf( aFile, "\nCOMPONENT %s\n",
+ TO_UTF8( module->GetReference() ) );
+ fprintf( aFile, "DEVICE %s_%s\n",
+ TO_UTF8( module->GetReference() ),
+ TO_UTF8( module->GetValue() ) );
+ fprintf( aFile, "PLACE %g %g\n",
+ MapXTo( module->GetPosition().x ),
+ MapYTo( module->GetPosition().y ) );
+ fprintf( aFile, "LAYER %s\n",
+ (module->GetFlag()) ? "BOTTOM" : "TOP" );
+ fprintf( aFile, "ROTATION %g\n",
+ orient / 10.0 );
+ fprintf( aFile, "SHAPE %s %s %s\n",
+ TO_UTF8( module->GetReference() ),
+ mirror, flip );
+
+ // Text on silk layer: RefDes and value (are they actually useful?)
+ TEXTE_MODULE *textmod = &module->Reference();
+
+ for( int ii = 0; ii < 2; ii++ )
+ {
+ double orient = textmod->GetOrientation();
+ std::string layer = GenCADLayerName( cu_count, module->GetFlag() ? B_SilkS : F_SilkS );
+
+ fprintf( aFile, "TEXT %g %g %g %g %s %s \"%s\"",
+ textmod->GetPos0().x / SCALE_FACTOR,
+ -textmod->GetPos0().y / SCALE_FACTOR,
+ textmod->GetSize().x / SCALE_FACTOR,
+ orient / 10.0,
+ mirror,
+ layer.c_str(),
+ TO_UTF8( textmod->GetText() ) );
+
+ // Please note, the width is approx
+ fprintf( aFile, " 0 0 %g %g\n",
+ ( textmod->GetSize().x * textmod->GetLength() ) / SCALE_FACTOR,
+ textmod->GetSize().y / SCALE_FACTOR );
+
+ textmod = &module->Value(); // Dirty trick for the second iteration
+ }
+
+ // The SHEET is a 'generic description' for referencing the component
+ fprintf( aFile, "SHEET \"RefDes: %s, Value: %s\"\n",
+ TO_UTF8( module->GetReference() ),
+ TO_UTF8( module->GetValue() ) );
+ }
+
+ fputs( "$ENDCOMPONENTS\n\n", aFile );
+}
+
+
+/* Emit the netlist (which is actually the thing for which GenCAD is used these
+ * days!); tracks are handled later */
+static void CreateSignalsSection( FILE* aFile, BOARD* aPcb )
+{
+ wxString msg;
+ NETINFO_ITEM* net;
+ D_PAD* pad;
+ MODULE* module;
+ int NbNoConn = 1;
+
+ fputs( "$SIGNALS\n", aFile );
+
+ for( unsigned ii = 0; ii < aPcb->GetNetCount(); ii++ )
+ {
+ net = aPcb->FindNet( ii );
+
+ if( net->GetNetname() == wxEmptyString ) // dummy netlist (no connection)
+ {
+ wxString msg; msg << wxT( "NoConnection" ) << NbNoConn++;
+ }
+
+ if( net->GetNet() <= 0 ) // dummy netlist (no connection)
+ continue;
+
+ msg = wxT( "SIGNAL " ) + net->GetNetname();
+
+ fputs( TO_UTF8( msg ), aFile );
+ fputs( "\n", aFile );
+
+ for( module = aPcb->m_Modules; module; module = module->Next() )
+ {
+ for( pad = module->Pads(); pad; pad = pad->Next() )
+ {
+ wxString padname;
+
+ if( pad->GetNetCode() != net->GetNet() )
+ continue;
+
+ pad->StringPadName( padname );
+ msg.Printf( wxT( "NODE %s %s" ),
+ GetChars( module->GetReference() ),
+ GetChars( padname ) );
+
+ fputs( TO_UTF8( msg ), aFile );
+ fputs( "\n", aFile );
+ }
+ }
+ }
+
+ fputs( "$ENDSIGNALS\n\n", aFile );
+}
+
+
+// Creates the header section
+static bool CreateHeaderInfoData( FILE* aFile, PCB_EDIT_FRAME* aFrame )
+{
+ wxString msg;
+ BOARD *board = aFrame->GetBoard();
+
+ fputs( "$HEADER\n", aFile );
+ fputs( "GENCAD 1.4\n", aFile );
+
+ // Please note: GenCAD syntax requires quoted strings if they can contain spaces
+ msg.Printf( wxT( "USER \"%s %s\"\n" ),
+ GetChars( Pgm().App().GetAppName() ),
+ GetChars( GetBuildVersion() ) );
+ fputs( TO_UTF8( msg ), aFile );
+
+ msg = wxT( "DRAWING \"" ) + board->GetFileName() + wxT( "\"\n" );
+ fputs( TO_UTF8( msg ), aFile );
+
+ const TITLE_BLOCK& tb = aFrame->GetTitleBlock();
+
+ msg = wxT( "REVISION \"" ) + tb.GetRevision() + wxT( " " ) + tb.GetDate() + wxT( "\"\n" );
+
+ fputs( TO_UTF8( msg ), aFile );
+ fputs( "UNITS INCH\n", aFile );
+
+ msg.Printf( wxT( "ORIGIN %g %g\n" ),
+ MapXTo( aFrame->GetAuxOrigin().x ),
+ MapYTo( aFrame->GetAuxOrigin().y ) );
+ fputs( TO_UTF8( msg ), aFile );
+
+ fputs( "INTERTRACK 0\n", aFile );
+ fputs( "$ENDHEADER\n\n", aFile );
+
+ return true;
+}
+
+
+/*
+ * Sort function used to sort tracks segments:
+ * items are sorted by netcode, then by width then by layer
+ */
+static int TrackListSortByNetcode( const void* refptr, const void* objptr )
+{
+ const TRACK* ref, * cmp;
+ int diff;
+
+ ref = *( (TRACK**) refptr );
+ cmp = *( (TRACK**) objptr );
+
+ if( ( diff = ref->GetNetCode() - cmp->GetNetCode() ) )
+ return diff;
+
+ if( ( diff = ref->GetWidth() - cmp->GetWidth() ) )
+ return diff;
+
+ if( ( diff = ref->GetLayer() - cmp->GetLayer() ) )
+ return diff;
+
+ return 0;
+}
+
+
+/* Creates the section ROUTES
+ * that handles tracks, vias
+ * TODO: add zones
+ * section:
+ * $ROUTE
+ * ...
+ * $ENROUTE
+ * Track segments must be sorted by nets
+ */
+static void CreateRoutesSection( FILE* aFile, BOARD* aPcb )
+{
+ TRACK* track, ** tracklist;
+ int vianum = 1;
+ int old_netcode, old_width, old_layer;
+ int nbitems, ii;
+ LSET master_layermask = aPcb->GetDesignSettings().GetEnabledLayers();
+
+ int cu_count = aPcb->GetCopperLayerCount();
+
+ // Count items
+ nbitems = 0;
+
+ for( track = aPcb->m_Track; track; track = track->Next() )
+ nbitems++;
+
+ for( track = aPcb->m_Zone; track; track = track->Next() )
+ {
+ if( track->Type() == PCB_ZONE_T )
+ nbitems++;
+ }
+
+ tracklist = (TRACK**) operator new( (nbitems + 1)* sizeof( TRACK* ) );
+
+ nbitems = 0;
+
+ for( track = aPcb->m_Track; track; track = track->Next() )
+ tracklist[nbitems++] = track;
+
+ for( track = aPcb->m_Zone; track; track = track->Next() )
+ {
+ if( track->Type() == PCB_ZONE_T )
+ tracklist[nbitems++] = track;
+ }
+
+ tracklist[nbitems] = NULL;
+
+ qsort( tracklist, nbitems, sizeof(TRACK*), TrackListSortByNetcode );
+
+ fputs( "$ROUTES\n", aFile );
+
+ old_netcode = -1; old_width = -1; old_layer = -1;
+
+ for( ii = 0; ii < nbitems; ii++ )
+ {
+ track = tracklist[ii];
+
+ if( old_netcode != track->GetNetCode() )
+ {
+ old_netcode = track->GetNetCode();
+ NETINFO_ITEM* net = track->GetNet();
+ wxString netname;
+
+ if( net && (net->GetNetname() != wxEmptyString) )
+ netname = net->GetNetname();
+ else
+ netname = wxT( "_noname_" );
+
+ fprintf( aFile, "ROUTE %s\n", TO_UTF8( netname ) );
+ }
+
+ if( old_width != track->GetWidth() )
+ {
+ old_width = track->GetWidth();
+ fprintf( aFile, "TRACK TRACK%d\n", track->GetWidth() );
+ }
+
+ if( (track->Type() == PCB_TRACE_T) || (track->Type() == PCB_ZONE_T) )
+ {
+ if( old_layer != track->GetLayer() )
+ {
+ old_layer = track->GetLayer();
+ fprintf( aFile, "LAYER %s\n",
+ GenCADLayerName( cu_count, track->GetLayer() ).c_str()
+ );
+ }
+
+ fprintf( aFile, "LINE %g %g %g %g\n",
+ MapXTo( track->GetStart().x ), MapYTo( track->GetStart().y ),
+ MapXTo( track->GetEnd().x ), MapYTo( track->GetEnd().y ) );
+ }
+
+ if( track->Type() == PCB_VIA_T )
+ {
+ const VIA* via = static_cast<const VIA*>(track);
+
+ LSET vset = via->GetLayerSet() & master_layermask;
+
+ fprintf( aFile, "VIA VIA%d.%d.%s %g %g ALL %g via%d\n",
+ via->GetWidth(), via->GetDrillValue(),
+ fmt_mask( vset ).c_str(),
+ MapXTo( via->GetStart().x ), MapYTo( via->GetStart().y ),
+ via->GetDrillValue() / SCALE_FACTOR, vianum++ );
+ }
+ }
+
+ fputs( "$ENDROUTES\n\n", aFile );
+
+ delete tracklist;
+}
+
+
+/* Creates the section $DEVICES
+ * This is a list of footprints properties
+ * ( Shapes are in section $SHAPE )
+ */
+static void CreateDevicesSection( FILE* aFile, BOARD* aPcb )
+{
+ MODULE* module;
+
+ fputs( "$DEVICES\n", aFile );
+
+ for( module = aPcb->m_Modules; module; module = module->Next() )
+ {
+ fprintf( aFile, "DEVICE \"%s\"\n", TO_UTF8( module->GetReference() ) );
+ fprintf( aFile, "PART \"%s\"\n", TO_UTF8( module->GetValue() ) );
+ fprintf( aFile, "PACKAGE \"%s\"\n", module->GetFPID().Format().c_str() );
+
+ // The TYPE attribute is almost freeform
+ const char* ty = "TH";
+
+ if( module->GetAttributes() & MOD_CMS )
+ ty = "SMD";
+
+ if( module->GetAttributes() & MOD_VIRTUAL )
+ ty = "VIRTUAL";
+
+ fprintf( aFile, "TYPE %s\n", ty );
+ }
+
+ fputs( "$ENDDEVICES\n\n", aFile );
+}
+
+
+/* Creates the section $BOARD.
+ * We output here only the board perimeter
+ */
+static void CreateBoardSection( FILE* aFile, BOARD* aPcb )
+{
+ fputs( "$BOARD\n", aFile );
+
+ // Extract the board edges
+ for( EDA_ITEM* drawing = aPcb->m_Drawings; drawing != 0;
+ drawing = drawing->Next() )
+ {
+ if( drawing->Type() == PCB_LINE_T )
+ {
+ DRAWSEGMENT* drawseg = static_cast<DRAWSEGMENT*>( drawing );
+ if( drawseg->GetLayer() == Edge_Cuts )
+ {
+ // XXX GenCAD supports arc boundaries but I've seen nothing that reads them
+ fprintf( aFile, "LINE %g %g %g %g\n",
+ MapXTo( drawseg->GetStart().x ), MapYTo( drawseg->GetStart().y ),
+ MapXTo( drawseg->GetEnd().x ), MapYTo( drawseg->GetEnd().y ) );
+ }
+ }
+ }
+
+ fputs( "$ENDBOARD\n\n", aFile );
+}
+
+
+/* Creates the section "$TRACKS"
+ * This sections give the list of widths (tools) used in tracks and vias
+ * format:
+ * $TRACK
+ * TRACK <name> <width>
+ * $ENDTRACK
+ *
+ * Each tool name is build like this: "TRACK" + track width.
+ * For instance for a width = 120 : name = "TRACK120".
+ */
+static void CreateTracksInfoData( FILE* aFile, BOARD* aPcb )
+{
+ TRACK* track;
+ int last_width = -1;
+
+ // Find thickness used for traces
+ // XXX could use the same sorting approach used for pads
+
+ std::vector <int> trackinfo;
+
+ unsigned ii;
+
+ for( track = aPcb->m_Track; track; track = track->Next() )
+ {
+ if( last_width != track->GetWidth() ) // Find a thickness already used.
+ {
+ for( ii = 0; ii < trackinfo.size(); ii++ )
+ {
+ if( trackinfo[ii] == track->GetWidth() )
+ break;
+ }
+
+ if( ii == trackinfo.size() ) // not found
+ trackinfo.push_back( track->GetWidth() );
+
+ last_width = track->GetWidth();
+ }
+ }
+
+ for( track = aPcb->m_Zone; track; track = track->Next() )
+ {
+ if( last_width != track->GetWidth() ) // Find a thickness already used.
+ {
+ for( ii = 0; ii < trackinfo.size(); ii++ )
+ {
+ if( trackinfo[ii] == track->GetWidth() )
+ break;
+ }
+
+ if( ii == trackinfo.size() ) // not found
+ trackinfo.push_back( track->GetWidth() );
+
+ last_width = track->GetWidth();
+ }
+ }
+
+ // Write data
+ fputs( "$TRACKS\n", aFile );
+
+ for( ii = 0; ii < trackinfo.size(); ii++ )
+ {
+ fprintf( aFile, "TRACK TRACK%d %g\n", trackinfo[ii],
+ trackinfo[ii] / SCALE_FACTOR );
+ }
+
+ fputs( "$ENDTRACKS\n\n", aFile );
+}
+
+
+/* Creates the shape of a footprint (section SHAPE)
+ * The shape is always given "normal" (Orient 0, not mirrored)
+ * It's almost guaranteed that the silk layer will be imported wrong but
+ * the shape also contains the pads!
+ */
+static void FootprintWriteShape( FILE* aFile, MODULE* module )
+{
+ EDGE_MODULE* PtEdge;
+ EDA_ITEM* PtStruct;
+
+ // Control Y axis change sign for flipped modules
+ int Yaxis_sign = -1;
+
+ // Flip for bottom side components
+ if( module->GetFlag() )
+ Yaxis_sign = 1;
+
+ /* creates header: */
+ fprintf( aFile, "\nSHAPE %s\n", TO_UTF8( module->GetReference() ) );
+
+ if( module->GetAttributes() & MOD_VIRTUAL )
+ {
+ fprintf( aFile, "INSERT SMD\n" );
+ }
+ else
+ {
+ if( module->GetAttributes() & MOD_CMS )
+ {
+ fprintf( aFile, "INSERT SMD\n" );
+ }
+ else
+ {
+ fprintf( aFile, "INSERT TH\n" );
+ }
+ }
+
+#if 0 /* ATTRIBUTE name and value is unspecified and the original exporter
+ * got the syntax wrong, so CAM350 rejected the whole shape! */
+
+ if( module->m_Attributs != MOD_DEFAULT )
+ {
+ fprintf( aFile, "ATTRIBUTE" );
+
+ if( module->m_Attributs & MOD_CMS )
+ fprintf( aFile, " PAD_SMD" );
+
+ if( module->m_Attributs & MOD_VIRTUAL )
+ fprintf( aFile, " VIRTUAL" );
+
+ fprintf( aFile, "\n" );
+ }
+#endif
+
+ // Silk outline; wildly interpreted by various importers:
+ // CAM350 read it right but only closed shapes
+ // ProntoPlace double-flip it (at least the pads are correct)
+ // GerberTool usually get it right...
+ for( PtStruct = module->GraphicalItems(); PtStruct; PtStruct = PtStruct->Next() )
+ {
+ switch( PtStruct->Type() )
+ {
+ case PCB_MODULE_TEXT_T:
+
+ // If we wanted to export text, this is not the correct section
+ break;
+
+ case PCB_MODULE_EDGE_T:
+ PtEdge = (EDGE_MODULE*) PtStruct;
+ if( PtEdge->GetLayer() == F_SilkS
+ || PtEdge->GetLayer() == B_SilkS )
+ {
+ switch( PtEdge->GetShape() )
+ {
+ case S_SEGMENT:
+ fprintf( aFile, "LINE %g %g %g %g\n",
+ (PtEdge->m_Start0.x) / SCALE_FACTOR,
+ (Yaxis_sign * PtEdge->m_Start0.y) / SCALE_FACTOR,
+ (PtEdge->m_End0.x) / SCALE_FACTOR,
+ (Yaxis_sign * PtEdge->m_End0.y ) / SCALE_FACTOR );
+ break;
+
+ case S_CIRCLE:
+ {
+ int radius = KiROUND( GetLineLength( PtEdge->m_End0,
+ PtEdge->m_Start0 ) );
+ fprintf( aFile, "CIRCLE %g %g %g\n",
+ PtEdge->m_Start0.x / SCALE_FACTOR,
+ Yaxis_sign * PtEdge->m_Start0.y / SCALE_FACTOR,
+ radius / SCALE_FACTOR );
+ break;
+ }
+
+ case S_ARC:
+ {
+ int arcendx, arcendy;
+ arcendx = PtEdge->m_End0.x - PtEdge->m_Start0.x;
+ arcendy = PtEdge->m_End0.y - PtEdge->m_Start0.y;
+ RotatePoint( &arcendx, &arcendy, -PtEdge->GetAngle() );
+ arcendx += PtEdge->GetStart0().x;
+ arcendy += PtEdge->GetStart0().y;
+ if( Yaxis_sign == -1 )
+ {
+ // Flipping Y flips the arc direction too
+ fprintf( aFile, "ARC %g %g %g %g %g %g\n",
+ (arcendx) / SCALE_FACTOR,
+ (Yaxis_sign * arcendy) / SCALE_FACTOR,
+ (PtEdge->m_End0.x) / SCALE_FACTOR,
+ (Yaxis_sign * PtEdge->GetEnd0().y) / SCALE_FACTOR,
+ (PtEdge->GetStart0().x) / SCALE_FACTOR,
+ (Yaxis_sign * PtEdge->GetStart0().y) / SCALE_FACTOR );
+ }
+ else
+ {
+ fprintf( aFile, "ARC %g %g %g %g %g %g\n",
+ (PtEdge->GetEnd0().x) / SCALE_FACTOR,
+ (Yaxis_sign * PtEdge->GetEnd0().y) / SCALE_FACTOR,
+ (arcendx) / SCALE_FACTOR,
+ (Yaxis_sign * arcendy) / SCALE_FACTOR,
+ (PtEdge->GetStart0().x) / SCALE_FACTOR,
+ (Yaxis_sign * PtEdge->GetStart0().y) / SCALE_FACTOR );
+ }
+ break;
+ }
+
+ default:
+ DisplayError( NULL, wxT( "Type Edge Module invalid." ) );
+ break;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
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;
+}
diff --git a/pcbnew/exporters/export_vrml.cpp b/pcbnew/exporters/export_vrml.cpp
new file mode 100644
index 0000000..178e7c8
--- /dev/null
+++ b/pcbnew/exporters/export_vrml.cpp
@@ -0,0 +1,1408 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2009-2013 Lorenzo Mercantonio
+ * Copyright (C) 2014 Cirilo Bernado
+ * Copyright (C) 2013 Jean-Pierre Charras jp.charras at wanadoo.fr
+ * Copyright (C) 2004-2015 KiCad Developers, see change_log.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
+ */
+
+
+#include <fctsys.h>
+#include <kicad_string.h>
+#include <wxPcbStruct.h>
+#include <drawtxt.h>
+#include <trigo.h>
+#include <pgm_base.h>
+#include <3d_struct.h>
+#include <macros.h>
+#include <exception>
+#include <fstream>
+#include <iomanip>
+
+#include <pcbnew.h>
+
+#include <class_board.h>
+#include <class_module.h>
+#include <class_track.h>
+#include <class_zone.h>
+#include <class_edge_mod.h>
+#include <class_pcb_text.h>
+#include <convert_from_iu.h>
+
+#include "../3d-viewer/modelparsers.h"
+
+#include <vector>
+#include <cmath>
+#include <vrml_layer.h>
+
+// minimum width (mm) of a VRML line
+#define MIN_VRML_LINEWIDTH 0.12
+
+// offset for art layers, mm (silk, paste, etc)
+#define ART_OFFSET 0.025
+
+
+struct VRML_COLOR
+{
+ float diffuse_red;
+ float diffuse_grn;
+ float diffuse_blu;
+
+ float spec_red;
+ float spec_grn;
+ float spec_blu;
+
+ float emit_red;
+ float emit_grn;
+ float emit_blu;
+
+ float ambient;
+ float transp;
+ float shiny;
+
+ VRML_COLOR()
+ {
+ // default green
+ diffuse_red = 0.13;
+ diffuse_grn = 0.81;
+ diffuse_blu = 0.22;
+ spec_red = 0.13;
+ spec_grn = 0.81;
+ spec_blu = 0.22;
+ emit_red = 0.0;
+ emit_grn = 0.0;
+ emit_blu = 0.0;
+
+ ambient = 1.0;
+ transp = 0;
+ shiny = 0.2;
+ }
+
+ VRML_COLOR( float dr, float dg, float db,
+ float sr, float sg, float sb,
+ float er, float eg, float eb,
+ float am, float tr, float sh )
+ {
+ diffuse_red = dr;
+ diffuse_grn = dg;
+ diffuse_blu = db;
+ spec_red = sr;
+ spec_grn = sg;
+ spec_blu = sb;
+ emit_red = er;
+ emit_grn = eg;
+ emit_blu = eb;
+
+ ambient = am;
+ transp = tr;
+ shiny = sh;
+ }
+};
+
+
+enum VRML_COLOR_INDEX
+{
+ VRML_COLOR_PCB = 0,
+ VRML_COLOR_TRACK,
+ VRML_COLOR_SILK,
+ VRML_COLOR_TIN,
+ VRML_COLOR_LAST
+};
+
+
+class MODEL_VRML
+{
+private:
+ double layer_z[LAYER_ID_COUNT];
+ VRML_COLOR colors[VRML_COLOR_LAST];
+
+ int iMaxSeg; // max. sides to a small circle
+ double arcMinLen, arcMaxLen; // min and max lengths of an arc chord
+
+public:
+ VRML_LAYER holes;
+ VRML_LAYER board;
+ VRML_LAYER top_copper;
+ VRML_LAYER bot_copper;
+ VRML_LAYER top_silk;
+ VRML_LAYER bot_silk;
+ VRML_LAYER top_tin;
+ VRML_LAYER bot_tin;
+ VRML_LAYER plated_holes;
+
+ bool plainPCB;
+
+ double scale; // board internal units to output scaling
+ double minLineWidth; // minimum width of a VRML line segment
+ int precision; // precision of output units
+
+ double tx; // global translation along X
+ double ty; // global translation along Y
+
+ double board_thickness; // depth of the PCB
+
+ LAYER_NUM s_text_layer;
+ int s_text_width;
+
+ MODEL_VRML()
+ {
+ for( unsigned i = 0; i < DIM( layer_z ); ++i )
+ layer_z[i] = 0;
+
+ holes.GetArcParams( iMaxSeg, arcMinLen, arcMaxLen );
+
+ // this default only makes sense if the output is in mm
+ board_thickness = 1.6;
+
+ // pcb green
+ colors[ VRML_COLOR_PCB ] = VRML_COLOR( .07, .3, .12, .07, .3, .12,
+ 0, 0, 0, 1, 0, 0.2 );
+ // track green
+ colors[ VRML_COLOR_TRACK ] = VRML_COLOR( .08, .5, .1, .08, .5, .1,
+ 0, 0, 0, 1, 0, 0.2 );
+ // silkscreen white
+ colors[ VRML_COLOR_SILK ] = VRML_COLOR( .9, .9, .9, .9, .9, .9,
+ 0, 0, 0, 1, 0, 0.2 );
+ // pad silver
+ colors[ VRML_COLOR_TIN ] = VRML_COLOR( .749, .756, .761, .749, .756, .761,
+ 0, 0, 0, 0.8, 0, 0.8 );
+
+ plainPCB = false;
+ SetScale( 1.0 );
+ SetOffset( 0.0, 0.0 );
+ s_text_layer = F_Cu;
+ s_text_width = 1;
+ }
+
+ VRML_COLOR& GetColor( VRML_COLOR_INDEX aIndex )
+ {
+ return colors[aIndex];
+ }
+
+ void SetOffset( double aXoff, double aYoff )
+ {
+ tx = aXoff;
+ ty = -aYoff;
+
+ holes.SetVertexOffsets( aXoff, aYoff );
+ board.SetVertexOffsets( aXoff, aYoff );
+ top_copper.SetVertexOffsets( aXoff, aYoff );
+ bot_copper.SetVertexOffsets( aXoff, aYoff );
+ top_silk.SetVertexOffsets( aXoff, aYoff );
+ bot_silk.SetVertexOffsets( aXoff, aYoff );
+ top_tin.SetVertexOffsets( aXoff, aYoff );
+ bot_tin.SetVertexOffsets( aXoff, aYoff );
+ plated_holes.SetVertexOffsets( aXoff, aYoff );
+ }
+
+ double GetLayerZ( LAYER_NUM aLayer )
+ {
+ if( unsigned( aLayer ) >= DIM( layer_z ) )
+ return 0;
+
+ return layer_z[ aLayer ];
+ }
+
+ void SetLayerZ( LAYER_NUM aLayer, double aValue )
+ {
+ layer_z[aLayer] = aValue;
+ }
+
+ // set the scaling of the VRML world
+ bool SetScale( double aWorldScale )
+ {
+ if( aWorldScale < 0.001 || aWorldScale > 10.0 )
+ throw( std::runtime_error( "WorldScale out of range (valid range is 0.001 to 10.0)" ) );
+
+ scale = aWorldScale * MM_PER_IU;
+ minLineWidth = aWorldScale * MIN_VRML_LINEWIDTH;
+
+ // set the precision of the VRML coordinates
+ if( aWorldScale < 0.01 )
+ precision = 8;
+ else if( aWorldScale < 0.1 )
+ precision = 7;
+ else if( aWorldScale< 1.0 )
+ precision = 6;
+ else if( aWorldScale < 10.0 )
+ precision = 5;
+ else
+ precision = 4;
+
+ double smin = arcMinLen * aWorldScale;
+ double smax = arcMaxLen * aWorldScale;
+
+ holes.SetArcParams( iMaxSeg, smin, smax );
+ board.SetArcParams( iMaxSeg, smin, smax );
+ top_copper.SetArcParams( iMaxSeg, smin, smax);
+ bot_copper.SetArcParams( iMaxSeg, smin, smax);
+ top_silk.SetArcParams( iMaxSeg, smin, smax );
+ bot_silk.SetArcParams( iMaxSeg, smin, smax );
+ top_tin.SetArcParams( iMaxSeg, smin, smax );
+ bot_tin.SetArcParams( iMaxSeg, smin, smax );
+ plated_holes.SetArcParams( iMaxSeg, smin, smax );
+
+ return true;
+ }
+
+};
+
+
+// static var. for dealing with text
+static MODEL_VRML* model_vrml;
+
+
+// select the VRML layer object to draw on; return true if
+// a layer has been selected.
+static bool GetLayer( MODEL_VRML& aModel, LAYER_NUM layer, VRML_LAYER** vlayer )
+{
+ switch( layer )
+ {
+ case B_Cu:
+ *vlayer = &aModel.bot_copper;
+ break;
+
+ case F_Cu:
+ *vlayer = &aModel.top_copper;
+ break;
+
+ case B_SilkS:
+ *vlayer = &aModel.bot_silk;
+ break;
+
+ case F_SilkS:
+ *vlayer = &aModel.top_silk;
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+
+static void write_triangle_bag( std::ofstream& output_file, VRML_COLOR& color,
+ VRML_LAYER* layer, bool plane, bool top,
+ double top_z, double bottom_z, int aPrecision )
+{
+ /* A lot of nodes are not required, but blender sometimes chokes
+ * without them */
+ static const char* shape_boiler[] =
+ {
+ "Transform {\n",
+ " children [\n",
+ " Group {\n",
+ " children [\n",
+ " Shape {\n",
+ " appearance Appearance {\n",
+ " material Material {\n",
+ 0, // Material marker
+ " }\n",
+ " }\n",
+ " geometry IndexedFaceSet {\n",
+ " solid TRUE\n",
+ " coord Coordinate {\n",
+ " point [\n",
+ 0, // Coordinates marker
+ " ]\n",
+ " }\n",
+ " coordIndex [\n",
+ 0, // Index marker
+ " ]\n",
+ " }\n",
+ " }\n",
+ " ]\n",
+ " }\n",
+ " ]\n",
+ "}\n",
+ 0 // End marker
+ };
+
+ int marker_found = 0, lineno = 0;
+
+ while( marker_found < 4 )
+ {
+ if( shape_boiler[lineno] )
+ output_file << shape_boiler[lineno];
+ else
+ {
+ marker_found++;
+
+ switch( marker_found )
+ {
+ case 1: // Material marker
+ output_file << " diffuseColor " << std::setprecision(3);
+ output_file << color.diffuse_red << " ";
+ output_file << color.diffuse_grn << " ";
+ output_file << color.diffuse_blu << "\n";
+
+ output_file << " specularColor ";
+ output_file << color.spec_red << " ";
+ output_file << color.spec_grn << " ";
+ output_file << color.spec_blu << "\n";
+
+ output_file << " emissiveColor ";
+ output_file << color.emit_red << " ";
+ output_file << color.emit_grn << " ";
+ output_file << color.emit_blu << "\n";
+
+ output_file << " ambientIntensity " << color.ambient << "\n";
+ output_file << " transparency " << color.transp << "\n";
+ output_file << " shininess " << color.shiny << "\n";
+ break;
+
+ case 2:
+
+ if( plane )
+ layer->WriteVertices( top_z, output_file, aPrecision );
+ else
+ layer->Write3DVertices( top_z, bottom_z, output_file, aPrecision );
+
+ output_file << "\n";
+ break;
+
+ case 3:
+
+ if( plane )
+ layer->WriteIndices( top, output_file );
+ else
+ layer->Write3DIndices( output_file );
+
+ output_file << "\n";
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ lineno++;
+ }
+}
+
+
+static void write_layers( MODEL_VRML& aModel, std::ofstream& output_file, BOARD* aPcb )
+{
+ // VRML_LAYER board;
+ aModel.board.Tesselate( &aModel.holes );
+ double brdz = aModel.board_thickness / 2.0
+ - ( Millimeter2iu( ART_OFFSET / 2.0 ) ) * aModel.scale;
+ write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_PCB ),
+ &aModel.board, false, false, brdz, -brdz, aModel.precision );
+
+ if( aModel.plainPCB )
+ return;
+
+ // VRML_LAYER top_copper;
+ aModel.top_copper.Tesselate( &aModel.holes );
+ write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TRACK ),
+ &aModel.top_copper, true, true,
+ aModel.GetLayerZ( F_Cu ), 0, aModel.precision );
+
+ // VRML_LAYER top_tin;
+ aModel.top_tin.Tesselate( &aModel.holes );
+ write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TIN ),
+ &aModel.top_tin, true, true,
+ aModel.GetLayerZ( F_Cu ) + Millimeter2iu( ART_OFFSET / 2.0 ) * aModel.scale,
+ 0, aModel.precision );
+
+ // VRML_LAYER bot_copper;
+ aModel.bot_copper.Tesselate( &aModel.holes );
+ write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TRACK ),
+ &aModel.bot_copper, true, false,
+ aModel.GetLayerZ( B_Cu ), 0, aModel.precision );
+
+ // VRML_LAYER bot_tin;
+ aModel.bot_tin.Tesselate( &aModel.holes );
+ write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TIN ),
+ &aModel.bot_tin, true, false,
+ aModel.GetLayerZ( B_Cu )
+ - Millimeter2iu( ART_OFFSET / 2.0 ) * aModel.scale,
+ 0, aModel.precision );
+
+ // VRML_LAYER PTH;
+ aModel.plated_holes.Tesselate( NULL, true );
+ write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TIN ),
+ &aModel.plated_holes, false, false,
+ aModel.GetLayerZ( F_Cu ) + Millimeter2iu( ART_OFFSET / 2.0 ) * aModel.scale,
+ aModel.GetLayerZ( B_Cu ) - Millimeter2iu( ART_OFFSET / 2.0 ) * aModel.scale,
+ aModel.precision );
+
+ // VRML_LAYER top_silk;
+ aModel.top_silk.Tesselate( &aModel.holes );
+ write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_SILK ), &aModel.top_silk,
+ true, true, aModel.GetLayerZ( F_SilkS ), 0, aModel.precision );
+
+ // VRML_LAYER bot_silk;
+ aModel.bot_silk.Tesselate( &aModel.holes );
+ write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_SILK ), &aModel.bot_silk,
+ true, false, aModel.GetLayerZ( B_SilkS ), 0, aModel.precision );
+}
+
+
+static void compute_layer_Zs( MODEL_VRML& aModel, BOARD* pcb )
+{
+ int copper_layers = pcb->GetCopperLayerCount();
+
+ // We call it 'layer' thickness, but it's the whole board thickness!
+ aModel.board_thickness = pcb->GetDesignSettings().GetBoardThickness() * aModel.scale;
+ double half_thickness = aModel.board_thickness / 2;
+
+ // Compute each layer's Z value, more or less like the 3d view
+ for( LSEQ seq = LSET::AllCuMask().Seq(); seq; ++seq )
+ {
+ LAYER_ID i = *seq;
+
+ if( i < copper_layers )
+ aModel.SetLayerZ( i, half_thickness - aModel.board_thickness * i / (copper_layers - 1) );
+ else
+ aModel.SetLayerZ( i, - half_thickness ); // bottom layer
+ }
+
+ /* To avoid rounding interference, we apply an epsilon to each
+ * successive layer */
+ double epsilon_z = Millimeter2iu( ART_OFFSET ) * aModel.scale;
+ aModel.SetLayerZ( B_Paste, -half_thickness - epsilon_z * 4 );
+ aModel.SetLayerZ( B_Adhes, -half_thickness - epsilon_z * 3 );
+ aModel.SetLayerZ( B_SilkS, -half_thickness - epsilon_z * 2 );
+ aModel.SetLayerZ( B_Mask, -half_thickness - epsilon_z );
+ aModel.SetLayerZ( F_Mask, half_thickness + epsilon_z );
+ aModel.SetLayerZ( F_SilkS, half_thickness + epsilon_z * 2 );
+ aModel.SetLayerZ( F_Adhes, half_thickness + epsilon_z * 3 );
+ aModel.SetLayerZ( F_Paste, half_thickness + epsilon_z * 4 );
+ aModel.SetLayerZ( Dwgs_User, half_thickness + epsilon_z * 5 );
+ aModel.SetLayerZ( Cmts_User, half_thickness + epsilon_z * 6 );
+ aModel.SetLayerZ( Eco1_User, half_thickness + epsilon_z * 7 );
+ aModel.SetLayerZ( Eco2_User, half_thickness + epsilon_z * 8 );
+ aModel.SetLayerZ( Edge_Cuts, 0 );
+}
+
+
+static void export_vrml_line( MODEL_VRML& aModel, LAYER_NUM layer,
+ double startx, double starty,
+ double endx, double endy, double width )
+{
+ VRML_LAYER* vlayer;
+
+ if( !GetLayer( aModel, layer, &vlayer ) )
+ return;
+
+ if( width < aModel.minLineWidth)
+ width = aModel.minLineWidth;
+
+ starty = -starty;
+ endy = -endy;
+
+ double angle = atan2( endy - starty, endx - startx ) * 180.0 / M_PI;
+ double length = Distance( startx, starty, endx, endy ) + width;
+ double cx = ( startx + endx ) / 2.0;
+ double cy = ( starty + endy ) / 2.0;
+
+ if( !vlayer->AddSlot( cx, cy, length, width, angle, false ) )
+ throw( std::runtime_error( vlayer->GetError() ) );
+}
+
+
+static void export_vrml_circle( MODEL_VRML& aModel, LAYER_NUM layer,
+ double startx, double starty,
+ double endx, double endy, double width )
+{
+ VRML_LAYER* vlayer;
+
+ if( !GetLayer( aModel, layer, &vlayer ) )
+ return;
+
+ if( width < aModel.minLineWidth )
+ width = aModel.minLineWidth;
+
+ starty = -starty;
+ endy = -endy;
+
+ double hole, radius;
+
+ radius = Distance( startx, starty, endx, endy ) + ( width / 2);
+ hole = radius - width;
+
+ if( !vlayer->AddCircle( startx, starty, radius, false ) )
+ throw( std::runtime_error( vlayer->GetError() ) );
+
+ if( hole > 0.0001 )
+ {
+ if( !vlayer->AddCircle( startx, starty, hole, true ) )
+ throw( std::runtime_error( vlayer->GetError() ) );
+ }
+}
+
+
+static void export_vrml_arc( MODEL_VRML& aModel, LAYER_NUM layer,
+ double centerx, double centery,
+ double arc_startx, double arc_starty,
+ double width, double arc_angle )
+{
+ VRML_LAYER* vlayer;
+
+ if( !GetLayer( aModel, layer, &vlayer ) )
+ return;
+
+ if( width < aModel.minLineWidth )
+ width = aModel.minLineWidth;
+
+ centery = -centery;
+ arc_starty = -arc_starty;
+
+ if( !vlayer->AddArc( centerx, centery, arc_startx, arc_starty, width, -arc_angle, false ) )
+ throw( std::runtime_error( vlayer->GetError() ) );
+
+}
+
+
+static void export_vrml_drawsegment( MODEL_VRML& aModel, DRAWSEGMENT* drawseg )
+{
+ LAYER_NUM layer = drawseg->GetLayer();
+ double w = drawseg->GetWidth() * aModel.scale;
+ double x = drawseg->GetStart().x * aModel.scale;
+ double y = drawseg->GetStart().y * aModel.scale;
+ double xf = drawseg->GetEnd().x * aModel.scale;
+ double yf = drawseg->GetEnd().y * aModel.scale;
+ double r = sqrt( pow( x - xf, 2 ) + pow( y - yf, 2 ) );
+
+ // Items on the edge layer are handled elsewhere; just return
+ if( layer == Edge_Cuts )
+ return;
+
+ switch( drawseg->GetShape() )
+ {
+ case S_ARC:
+ export_vrml_arc( aModel, layer,
+ (double) drawseg->GetCenter().x * aModel.scale,
+ (double) drawseg->GetCenter().y * aModel.scale,
+ (double) drawseg->GetArcStart().x * aModel.scale,
+ (double) drawseg->GetArcStart().y * aModel.scale,
+ w, drawseg->GetAngle() / 10 );
+ break;
+
+ case S_CIRCLE:
+ // Break circles into two 180 arcs to prevent the vrml hole from obscuring objects
+ // within the hole area of the circle.
+ export_vrml_arc( aModel, layer, x, y, x, y-r, w, 180.0 );
+ export_vrml_arc( aModel, layer, x, y, x, y+r, w, 180.0 );
+ break;
+
+ default:
+ export_vrml_line( aModel, layer, x, y, xf, yf, w );
+ break;
+ }
+}
+
+
+/* C++ doesn't have closures and neither continuation forms... this is
+ * for coupling the vrml_text_callback with the common parameters */
+static void vrml_text_callback( int x0, int y0, int xf, int yf )
+{
+ LAYER_NUM s_text_layer = model_vrml->s_text_layer;
+ int s_text_width = model_vrml->s_text_width;
+ double scale = model_vrml->scale;
+
+ export_vrml_line( *model_vrml, s_text_layer,
+ x0 * scale, y0 * scale,
+ xf * scale, yf * scale,
+ s_text_width * scale );
+}
+
+
+static void export_vrml_pcbtext( MODEL_VRML& aModel, TEXTE_PCB* text )
+{
+ model_vrml->s_text_layer = text->GetLayer();
+ model_vrml->s_text_width = text->GetThickness();
+
+ wxSize size = text->GetSize();
+
+ if( text->IsMirrored() )
+ size.x = -size.x;
+
+ EDA_COLOR_T color = BLACK; // not actually used, but needed by DrawGraphicText
+
+ if( text->IsMultilineAllowed() )
+ {
+ wxArrayString strings_list;
+ wxStringSplit( text->GetShownText(), strings_list, '\n' );
+ std::vector<wxPoint> positions;
+ positions.reserve( strings_list.Count() );
+ text->GetPositionsOfLinesOfMultilineText( positions, strings_list.Count() );
+
+ for( unsigned ii = 0; ii < strings_list.Count(); ii++ )
+ {
+ wxString& txt = strings_list.Item( ii );
+ DrawGraphicText( NULL, NULL, positions[ii], color,
+ txt, text->GetOrientation(), size,
+ text->GetHorizJustify(), text->GetVertJustify(),
+ text->GetThickness(), text->IsItalic(),
+ true,
+ vrml_text_callback );
+ }
+ }
+ else
+ {
+ DrawGraphicText( NULL, NULL, text->GetTextPosition(), color,
+ text->GetShownText(), text->GetOrientation(), size,
+ text->GetHorizJustify(), text->GetVertJustify(),
+ text->GetThickness(), text->IsItalic(),
+ true,
+ vrml_text_callback );
+ }
+}
+
+
+static void export_vrml_drawings( MODEL_VRML& aModel, BOARD* pcb )
+{
+ // draw graphic items
+ for( BOARD_ITEM* drawing = pcb->m_Drawings; drawing != 0; drawing = drawing->Next() )
+ {
+ LAYER_ID layer = drawing->GetLayer();
+
+ if( layer != F_Cu && layer != B_Cu && layer != B_SilkS && layer != F_SilkS )
+ continue;
+
+ switch( drawing->Type() )
+ {
+ case PCB_LINE_T:
+ export_vrml_drawsegment( aModel, (DRAWSEGMENT*) drawing );
+ break;
+
+ case PCB_TEXT_T:
+ export_vrml_pcbtext( aModel, (TEXTE_PCB*) drawing );
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+// board edges and cutouts
+static void export_vrml_board( MODEL_VRML& aModel, BOARD* pcb )
+{
+ SHAPE_POLY_SET bufferPcbOutlines; // stores the board main outlines
+ SHAPE_POLY_SET allLayerHoles; // Contains through holes, calculated only once
+ // Build a polygon from edge cut items
+ wxString msg;
+
+ if( !pcb->GetBoardPolygonOutlines( bufferPcbOutlines, allLayerHoles, &msg ) )
+ {
+ msg << wxT( "\n\n" ) <<
+ _( "Unable to calculate the board outlines;\n"
+ "fall back to using the board boundary box." );
+ wxMessageBox( msg );
+ }
+
+ double scale = aModel.scale;
+ int seg;
+
+ for( int i = 0; i < bufferPcbOutlines.OutlineCount(); i++ )
+ {
+ const SHAPE_LINE_CHAIN& outline = bufferPcbOutlines.COutline( i );
+
+ seg = aModel.board.NewContour();
+
+ for( int j = 0; j < outline.PointCount(); j++ )
+ {
+ aModel.board.AddVertex( seg, (double)outline.CPoint(j).x * scale,
+ -((double)outline.CPoint(j).y * scale ) );
+
+ }
+
+ aModel.board.EnsureWinding( seg, false );
+ }
+
+ for( int i = 0; i < allLayerHoles.OutlineCount(); i++ )
+ {
+ const SHAPE_LINE_CHAIN& outline = allLayerHoles.COutline( i );
+
+ seg = aModel.holes.NewContour();
+
+ if( seg < 0 )
+ {
+ msg << wxT( "\n\n" ) <<
+ _( "VRML Export Failed:\nCould not add holes to contours." );
+ wxMessageBox( msg );
+
+ return;
+ }
+
+ for( int j = 0; j < outline.PointCount(); j++ )
+ {
+ aModel.holes.AddVertex( seg, (double)outline.CPoint(j).x * scale,
+ -((double)outline.CPoint(j).y * scale ) );
+
+ }
+
+ aModel.holes.EnsureWinding( seg, true );
+ }
+}
+
+
+static void export_round_padstack( MODEL_VRML& aModel, BOARD* pcb,
+ double x, double y, double r,
+ LAYER_NUM bottom_layer, LAYER_NUM top_layer,
+ double hole )
+{
+ LAYER_NUM layer = top_layer;
+ bool thru = true;
+
+ // if not a thru hole do not put a hole in the board
+ if( top_layer != F_Cu || bottom_layer != B_Cu )
+ thru = false;
+
+ if( thru && hole > 0 )
+ aModel.holes.AddCircle( x, -y, hole, true );
+
+ if( aModel.plainPCB )
+ return;
+
+ while( 1 )
+ {
+ if( layer == B_Cu )
+ {
+ aModel.bot_copper.AddCircle( x, -y, r );
+
+ if( hole > 0 && !thru )
+ aModel.bot_copper.AddCircle( x, -y, hole, true );
+
+ }
+ else if( layer == F_Cu )
+ {
+ aModel.top_copper.AddCircle( x, -y, r );
+
+ if( hole > 0 && !thru )
+ aModel.top_copper.AddCircle( x, -y, hole, true );
+
+ }
+
+ if( layer == bottom_layer )
+ break;
+
+ layer = bottom_layer;
+ }
+}
+
+
+static void export_vrml_via( MODEL_VRML& aModel, BOARD* pcb, const VIA* via )
+{
+ double x, y, r, hole;
+ LAYER_ID top_layer, bottom_layer;
+
+ hole = via->GetDrillValue() * aModel.scale / 2.0;
+ r = via->GetWidth() * aModel.scale / 2.0;
+ x = via->GetStart().x * aModel.scale;
+ y = via->GetStart().y * aModel.scale;
+ via->LayerPair( &top_layer, &bottom_layer );
+
+ // do not render a buried via
+ if( top_layer != F_Cu && bottom_layer != B_Cu )
+ return;
+
+ // Export the via padstack
+ export_round_padstack( aModel, pcb, x, y, r, bottom_layer, top_layer, hole );
+}
+
+
+static void export_vrml_tracks( MODEL_VRML& aModel, BOARD* pcb )
+{
+ for( TRACK* track = pcb->m_Track; track; track = track->Next() )
+ {
+ if( track->Type() == PCB_VIA_T )
+ {
+ export_vrml_via( aModel, pcb, (const VIA*) track );
+ }
+ else if( ( track->GetLayer() == B_Cu || track->GetLayer() == F_Cu )
+ && !aModel.plainPCB )
+ export_vrml_line( aModel, track->GetLayer(),
+ track->GetStart().x * aModel.scale,
+ track->GetStart().y * aModel.scale,
+ track->GetEnd().x * aModel.scale,
+ track->GetEnd().y * aModel.scale,
+ track->GetWidth() * aModel.scale );
+ }
+}
+
+
+static void export_vrml_zones( MODEL_VRML& aModel, BOARD* aPcb )
+{
+ double scale = aModel.scale;
+
+ for( int ii = 0; ii < aPcb->GetAreaCount(); ii++ )
+ {
+ ZONE_CONTAINER* zone = aPcb->GetArea( ii );
+
+ VRML_LAYER* vl;
+
+ if( !GetLayer( aModel, zone->GetLayer(), &vl ) )
+ continue;
+
+ if( !zone->IsFilled() )
+ {
+ zone->SetFillMode( 0 ); // use filled polygons
+ zone->BuildFilledSolidAreasPolygons( aPcb );
+ }
+
+ const SHAPE_POLY_SET& poly = zone->GetFilledPolysList();
+
+ for( int i = 0; i < poly.OutlineCount(); i++ )
+ {
+ const SHAPE_LINE_CHAIN& outline = poly.COutline( i );
+
+ int seg = vl->NewContour();
+
+ for( int j = 0; j < outline.PointCount(); j++ )
+ {
+ if( !vl->AddVertex( seg, (double)outline.CPoint( j ).x * scale,
+ -((double)outline.CPoint( j ).y * scale ) ) )
+ throw( std::runtime_error( vl->GetError() ) );
+
+ }
+
+ vl->EnsureWinding( seg, false );
+ }
+ }
+}
+
+
+static void export_vrml_text_module( TEXTE_MODULE* module )
+{
+ if( module->IsVisible() )
+ {
+ wxSize size = module->GetSize();
+
+ if( module->IsMirrored() )
+ size.x = -size.x; // Text is mirrored
+
+ model_vrml->s_text_layer = module->GetLayer();
+ model_vrml->s_text_width = module->GetThickness();
+
+ DrawGraphicText( NULL, NULL, module->GetTextPosition(), BLACK,
+ module->GetShownText(), module->GetDrawRotation(), size,
+ module->GetHorizJustify(), module->GetVertJustify(),
+ module->GetThickness(), module->IsItalic(),
+ true,
+ vrml_text_callback );
+ }
+}
+
+
+static void export_vrml_edge_module( MODEL_VRML& aModel, EDGE_MODULE* aOutline,
+ double aOrientation )
+{
+ LAYER_NUM layer = aOutline->GetLayer();
+ double x = aOutline->GetStart().x * aModel.scale;
+ double y = aOutline->GetStart().y * aModel.scale;
+ double xf = aOutline->GetEnd().x * aModel.scale;
+ double yf = aOutline->GetEnd().y * aModel.scale;
+ double w = aOutline->GetWidth() * aModel.scale;
+
+ switch( aOutline->GetShape() )
+ {
+ case S_SEGMENT:
+ export_vrml_line( aModel, layer, x, y, xf, yf, w );
+ break;
+
+ case S_ARC:
+ export_vrml_arc( aModel, layer, x, y, xf, yf, w, aOutline->GetAngle() / 10 );
+ break;
+
+ case S_CIRCLE:
+ export_vrml_circle( aModel, layer, x, y, xf, yf, w );
+ break;
+
+ case S_POLYGON:
+ {
+ VRML_LAYER* vl;
+
+ if( !GetLayer( aModel, layer, &vl ) )
+ break;
+
+ int nvert = aOutline->GetPolyPoints().size() - 1;
+ int i = 0;
+
+ if( nvert < 3 ) break;
+
+ int seg = vl->NewContour();
+
+ if( seg < 0 )
+ break;
+
+ while( i < nvert )
+ {
+ CPolyPt corner( aOutline->GetPolyPoints()[i] );
+ RotatePoint( &corner.x, &corner.y, aOrientation );
+ corner.x += aOutline->GetPosition().x;
+ corner.y += aOutline->GetPosition().y;
+
+ x = corner.x * aModel.scale;
+ y = - ( corner.y * aModel.scale );
+
+ if( !vl->AddVertex( seg, x, y ) )
+ throw( std::runtime_error( vl->GetError() ) );
+
+ ++i;
+ }
+ vl->EnsureWinding( seg, false );
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static void export_vrml_padshape( MODEL_VRML& aModel, VRML_LAYER* aTinLayer, D_PAD* aPad )
+{
+ // The (maybe offset) pad position
+ wxPoint pad_pos = aPad->ShapePos();
+ double pad_x = pad_pos.x * aModel.scale;
+ double pad_y = pad_pos.y * aModel.scale;
+ wxSize pad_delta = aPad->GetDelta();
+
+ double pad_dx = pad_delta.x * aModel.scale / 2.0;
+ double pad_dy = pad_delta.y * aModel.scale / 2.0;
+
+ double pad_w = aPad->GetSize().x * aModel.scale / 2.0;
+ double pad_h = aPad->GetSize().y * aModel.scale / 2.0;
+
+ switch( aPad->GetShape() )
+ {
+ case PAD_SHAPE_CIRCLE:
+
+ if( !aTinLayer->AddCircle( pad_x, -pad_y, pad_w, false ) )
+ throw( std::runtime_error( aTinLayer->GetError() ) );
+
+ break;
+
+ case PAD_SHAPE_OVAL:
+
+ if( !aTinLayer->AddSlot( pad_x, -pad_y, pad_w * 2.0, pad_h * 2.0,
+ aPad->GetOrientation()/10.0, false ) )
+ throw( std::runtime_error( aTinLayer->GetError() ) );
+
+ break;
+
+ case PAD_SHAPE_RECT:
+ // Just to be sure :D
+ pad_dx = 0;
+ pad_dy = 0;
+
+ case PAD_SHAPE_TRAPEZOID:
+ {
+ double coord[8] =
+ {
+ -pad_w + pad_dy, -pad_h - pad_dx,
+ -pad_w - pad_dy, pad_h + pad_dx,
+ +pad_w - pad_dy, -pad_h + pad_dx,
+ +pad_w + pad_dy, pad_h - pad_dx
+ };
+
+ for( int i = 0; i < 4; i++ )
+ {
+ RotatePoint( &coord[i * 2], &coord[i * 2 + 1], aPad->GetOrientation() );
+ coord[i * 2] += pad_x;
+ coord[i * 2 + 1] += pad_y;
+ }
+
+ int lines;
+
+ lines = aTinLayer->NewContour();
+
+ if( lines < 0 )
+ throw( std::runtime_error( aTinLayer->GetError() ) );
+
+ if( !aTinLayer->AddVertex( lines, coord[0], -coord[1] ) )
+ throw( std::runtime_error( aTinLayer->GetError() ) );
+
+ if( !aTinLayer->AddVertex( lines, coord[4], -coord[5] ) )
+ throw( std::runtime_error( aTinLayer->GetError() ) );
+
+ if( !aTinLayer->AddVertex( lines, coord[6], -coord[7] ) )
+ throw( std::runtime_error( aTinLayer->GetError() ) );
+
+ if( !aTinLayer->AddVertex( lines, coord[2], -coord[3] ) )
+ throw( std::runtime_error( aTinLayer->GetError() ) );
+
+ if( !aTinLayer->EnsureWinding( lines, false ) )
+ throw( std::runtime_error( aTinLayer->GetError() ) );
+
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+
+static void export_vrml_pad( MODEL_VRML& aModel, BOARD* pcb, D_PAD* aPad )
+{
+ double hole_drill_w = (double) aPad->GetDrillSize().x * aModel.scale / 2.0;
+ double hole_drill_h = (double) aPad->GetDrillSize().y * aModel.scale / 2.0;
+ double hole_drill = std::min( hole_drill_w, hole_drill_h );
+ double hole_x = aPad->GetPosition().x * aModel.scale;
+ double hole_y = aPad->GetPosition().y * aModel.scale;
+
+ // Export the hole on the edge layer
+ if( hole_drill > 0 )
+ {
+ bool pth = false;
+
+ if( ( aPad->GetAttribute() != PAD_ATTRIB_HOLE_NOT_PLATED )
+ && !aModel.plainPCB )
+ pth = true;
+
+ if( aPad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG )
+ {
+ // Oblong hole (slot)
+ aModel.holes.AddSlot( hole_x, -hole_y, hole_drill_w * 2.0, hole_drill_h * 2.0,
+ aPad->GetOrientation()/10.0, true, pth );
+
+ if( pth )
+ aModel.plated_holes.AddSlot( hole_x, -hole_y,
+ hole_drill_w * 2.0, hole_drill_h * 2.0,
+ aPad->GetOrientation()/10.0, true, false );
+ }
+ else
+ {
+ // Drill a round hole
+ aModel.holes.AddCircle( hole_x, -hole_y, hole_drill, true, pth );
+
+ if( pth )
+ aModel.plated_holes.AddCircle( hole_x, -hole_y, hole_drill, true, false );
+
+ }
+ }
+
+ if( aModel.plainPCB )
+ return;
+
+ // The pad proper, on the selected layers
+ LSET layer_mask = aPad->GetLayerSet();
+
+ if( layer_mask[B_Cu] )
+ {
+ export_vrml_padshape( aModel, &aModel.bot_tin, aPad );
+ }
+
+ if( layer_mask[F_Cu] )
+ {
+ export_vrml_padshape( aModel, &aModel.top_tin, aPad );
+ }
+}
+
+
+// From axis/rot to quaternion
+static void build_quat( double x, double y, double z, double a, double q[4] )
+{
+ double sina = sin( a / 2 );
+
+ q[0] = x * sina;
+ q[1] = y * sina;
+ q[2] = z * sina;
+ q[3] = cos( a / 2 );
+}
+
+
+// From quaternion to axis/rot
+static void from_quat( double q[4], double rot[4] )
+{
+ rot[3] = acos( q[3] ) * 2;
+
+ for( int i = 0; i < 3; i++ )
+ {
+ rot[i] = q[i] / sin( rot[3] / 2 );
+ }
+}
+
+
+// Quaternion composition
+static void compose_quat( double q1[4], double q2[4], double qr[4] )
+{
+ double tmp[4];
+
+ tmp[0] = q2[3] * q1[0] + q2[0] * q1[3] + q2[1] * q1[2] - q2[2] * q1[1];
+ tmp[1] = q2[3] * q1[1] + q2[1] * q1[3] + q2[2] * q1[0] - q2[0] * q1[2];
+ tmp[2] = q2[3] * q1[2] + q2[2] * q1[3] + q2[0] * q1[1] - q2[1] * q1[0];
+ tmp[3] = q2[3] * q1[3] - q2[0] * q1[0] - q2[1] * q1[1] - q2[2] * q1[2];
+
+ qr[0] = tmp[0];
+ qr[1] = tmp[1];
+ qr[2] = tmp[2];
+ qr[3] = tmp[3];
+}
+
+
+static void export_vrml_module( MODEL_VRML& aModel, BOARD* aPcb, MODULE* aModule,
+ std::ofstream& aOutputFile, double aVRMLModelsToBiu,
+ bool aExport3DFiles, bool aUseRelativePaths,
+ const wxString& a3D_Subdir )
+{
+ if( !aModel.plainPCB )
+ {
+ // Reference and value
+ if( aModule->Reference().IsVisible() )
+ export_vrml_text_module( &aModule->Reference() );
+
+ if( aModule->Value().IsVisible() )
+ export_vrml_text_module( &aModule->Value() );
+
+ // Export module edges
+ for( EDA_ITEM* item = aModule->GraphicalItems(); item; item = item->Next() )
+ {
+ switch( item->Type() )
+ {
+ case PCB_MODULE_TEXT_T:
+ export_vrml_text_module( static_cast<TEXTE_MODULE*>( item ) );
+ break;
+
+ case PCB_MODULE_EDGE_T:
+ export_vrml_edge_module( aModel, static_cast<EDGE_MODULE*>( item ),
+ aModule->GetOrientation() );
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ // Export pads
+ for( D_PAD* pad = aModule->Pads(); pad; pad = pad->Next() )
+ export_vrml_pad( aModel, aPcb, pad );
+
+ bool isFlipped = aModule->GetLayer() == B_Cu;
+
+ // Export the object VRML model(s)
+ for( S3D_MASTER* vrmlm = aModule->Models(); vrmlm; vrmlm = vrmlm->Next() )
+ {
+ if( !vrmlm->Is3DType( S3D_MASTER::FILE3D_VRML ) )
+ continue;
+
+ wxFileName modelFileName = vrmlm->GetShape3DFullFilename();
+ wxFileName destFileName( a3D_Subdir, modelFileName.GetName(), modelFileName.GetExt() );
+
+ // Only copy VRML files.
+ if( modelFileName.FileExists() && modelFileName.GetExt() == wxT( "wrl" ) )
+ {
+ if( aExport3DFiles )
+ {
+ wxDateTime srcModTime = modelFileName.GetModificationTime();
+ wxDateTime destModTime = srcModTime;
+
+ destModTime.SetToCurrent();
+
+ if( destFileName.FileExists() )
+ destModTime = destFileName.GetModificationTime();
+
+ // Only copy the file if it doesn't exist or has been modified. This eliminates
+ // the redundant file copies.
+ if( srcModTime != destModTime )
+ {
+ wxLogDebug( wxT( "Copying 3D model %s to %s." ),
+ GetChars( modelFileName.GetFullPath() ),
+ GetChars( destFileName.GetFullPath() ) );
+
+ if( !wxCopyFile( modelFileName.GetFullPath(), destFileName.GetFullPath() ) )
+ continue;
+ }
+ }
+
+ /* Calculate 3D shape rotation:
+ * this is the rotation parameters, with an additional 180 deg rotation
+ * for footprints that are flipped
+ * When flipped, axis rotation is the horizontal axis (X axis)
+ */
+ double rotx = -vrmlm->m_MatRotation.x;
+ double roty = -vrmlm->m_MatRotation.y;
+ double rotz = -vrmlm->m_MatRotation.z;
+
+ if( isFlipped )
+ {
+ rotx += 180.0;
+ roty = -roty;
+ rotz = -rotz;
+ }
+
+ // Do some quaternion munching
+ double q1[4], q2[4], rot[4];
+ build_quat( 1, 0, 0, DEG2RAD( rotx ), q1 );
+ build_quat( 0, 1, 0, DEG2RAD( roty ), q2 );
+ compose_quat( q1, q2, q1 );
+ build_quat( 0, 0, 1, DEG2RAD( rotz ), q2 );
+ compose_quat( q1, q2, q1 );
+
+ // Note here aModule->GetOrientation() is in 0.1 degrees,
+ // so module rotation has to be converted to radians
+ build_quat( 0, 0, 1, DECIDEG2RAD( aModule->GetOrientation() ), q2 );
+ compose_quat( q1, q2, q1 );
+ from_quat( q1, rot );
+
+ aOutputFile << "Transform {\n";
+
+ // A null rotation would fail the acos!
+ if( rot[3] != 0.0 )
+ {
+ aOutputFile << " rotation " << std::setprecision( 3 );
+ aOutputFile << rot[0] << " " << rot[1] << " " << rot[2] << " " << rot[3] << "\n";
+ }
+
+ // adjust 3D shape local offset position
+ // they are given in inch, so they are converted in board IU.
+ double offsetx = vrmlm->m_MatPosition.x * IU_PER_MILS * 1000.0;
+ double offsety = vrmlm->m_MatPosition.y * IU_PER_MILS * 1000.0;
+ double offsetz = vrmlm->m_MatPosition.z * IU_PER_MILS * 1000.0;
+
+ if( isFlipped )
+ offsetz = -offsetz;
+ else // In normal mode, Y axis is reversed in Pcbnew.
+ offsety = -offsety;
+
+ RotatePoint( &offsetx, &offsety, aModule->GetOrientation() );
+
+ aOutputFile << " translation " << std::setprecision( aModel.precision );
+ aOutputFile << ( ( offsetx + aModule->GetPosition().x ) *
+ aModel.scale + aModel.tx ) << " ";
+ aOutputFile << ( -(offsety + aModule->GetPosition().y) *
+ aModel.scale - aModel.ty ) << " ";
+ aOutputFile << ( (offsetz * aModel.scale ) +
+ aModel.GetLayerZ( aModule->GetLayer() ) ) << "\n";
+ aOutputFile << " scale ";
+ aOutputFile << ( vrmlm->m_MatScale.x * aVRMLModelsToBiu ) << " ";
+ aOutputFile << ( vrmlm->m_MatScale.y * aVRMLModelsToBiu ) << " ";
+ aOutputFile << ( vrmlm->m_MatScale.z * aVRMLModelsToBiu ) << "\n";
+ aOutputFile << " children [\n Inline {\n url \"";
+
+ if( aUseRelativePaths )
+ {
+ wxFileName tmp = destFileName;
+ tmp.SetExt( wxT( "" ) );
+ tmp.SetName( wxT( "" ) );
+ tmp.RemoveLastDir();
+ destFileName.MakeRelativeTo( tmp.GetPath() );
+ }
+
+ wxString fn = destFileName.GetFullPath();
+ fn.Replace( wxT( "\\" ), wxT( "/" ) );
+ aOutputFile << TO_UTF8( fn ) << "\"\n } ]\n";
+ aOutputFile << " }\n";
+ }
+ }
+}
+
+
+bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, double aMMtoWRMLunit,
+ bool aExport3DFiles, bool aUseRelativePaths,
+ bool aUsePlainPCB, const wxString& a3D_Subdir,
+ double aXRef, double aYRef )
+{
+ BOARD* pcb = GetBoard();
+ bool ok = true;
+
+ MODEL_VRML model3d;
+ model3d.plainPCB = aUsePlainPCB;
+
+ model_vrml = &model3d;
+ std::ofstream output_file;
+
+ try
+ {
+ output_file.exceptions( std::ofstream::failbit );
+ output_file.open( TO_UTF8( aFullFileName ), std::ios_base::out );
+
+ // Switch the locale to standard C (needed to print floating point numbers)
+ LOCALE_IO toggle;
+
+ // Begin with the usual VRML boilerplate
+ wxString fn = aFullFileName;
+ fn.Replace( wxT( "\\" ), wxT( "/" ) );
+ output_file << "#VRML V2.0 utf8\n";
+ output_file << "WorldInfo {\n";
+ output_file << " title \"" << TO_UTF8( fn ) << " - Generated by Pcbnew\"\n";
+ output_file << "}\n";
+
+ // Set the VRML world scale factor
+ model3d.SetScale( aMMtoWRMLunit );
+
+ output_file << "Transform {\n";
+
+ // board reference point
+ model3d.SetOffset( -aXRef, aYRef );
+
+ output_file << " children [\n";
+
+ // Preliminary computation: the z value for each layer
+ compute_layer_Zs( model3d, pcb );
+
+ // board edges and cutouts
+ export_vrml_board( model3d, pcb );
+
+ // Drawing and text on the board
+ if( !aUsePlainPCB )
+ export_vrml_drawings( model3d, pcb );
+
+ // Export vias and trackage
+ export_vrml_tracks( model3d, pcb );
+
+ // Export zone fills
+ if( !aUsePlainPCB )
+ export_vrml_zones( model3d, pcb);
+
+ /* scaling factor to convert 3D models to board units (decimils)
+ * Usually we use Wings3D to create thems.
+ * One can consider the 3D units is 0.1 inch (2.54 mm)
+ * So the scaling factor from 0.1 inch to board units
+ * is 2.54 * aMMtoWRMLunit
+ */
+ double wrml_3D_models_scaling_factor = 2.54 * aMMtoWRMLunit;
+
+ // Export footprints
+ for( MODULE* module = pcb->m_Modules; module != 0; module = module->Next() )
+ export_vrml_module( model3d, pcb, module, output_file, wrml_3D_models_scaling_factor,
+ aExport3DFiles, aUseRelativePaths, a3D_Subdir );
+
+ // write out the board and all layers
+ write_layers( model3d, output_file, pcb );
+
+ // Close the outer 'transform' node
+ output_file << "]\n}\n";
+ }
+ catch( const std::exception& e )
+ {
+ wxString msg;
+ msg << _( "IDF Export Failed:\n" ) << FROM_UTF8( e.what() );
+ wxMessageBox( msg );
+
+ ok = false;
+ }
+
+ // End of work
+ output_file.exceptions( std::ios_base::goodbit );
+ output_file.close();
+
+ return ok;
+}
diff --git a/pcbnew/exporters/gen_drill_report_files.cpp b/pcbnew/exporters/gen_drill_report_files.cpp
new file mode 100644
index 0000000..94a6c75
--- /dev/null
+++ b/pcbnew/exporters/gen_drill_report_files.cpp
@@ -0,0 +1,455 @@
+/**
+ * @file gen_drill_report_files.cpp
+ * @brief Functions to create report and map files for EXCELLON drill files.
+ */
+
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 1992-2015 Jean_Pierre Charras <jp.charras at wanadoo.fr>
+ * Copyright (C) 1992-2015 KiCad Developers, see change_log.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
+ */
+
+#include <fctsys.h>
+#include <common.h>
+#include <plot_common.h>
+#include <base_struct.h>
+#include <drawtxt.h>
+#include <confirm.h>
+#include <kicad_string.h>
+#include <macros.h>
+
+#include <class_board.h>
+
+#include <pcbnew.h>
+#include <pcbplot.h>
+#include <gendrill_Excellon_writer.h>
+
+/* Conversion utilities - these will be used often in there... */
+inline double diameter_in_inches( double ius )
+{
+ return ius * 0.001 / IU_PER_MILS;
+}
+
+
+inline double diameter_in_mm( double ius )
+{
+ return ius / IU_PER_MM;
+}
+
+
+bool EXCELLON_WRITER::GenDrillMapFile( const wxString& aFullFileName,
+ PlotFormat aFormat )
+{
+ double scale = 1.0;
+ wxPoint offset;
+ PLOTTER* plotter = NULL;
+ PAGE_INFO dummy( PAGE_INFO::A4, false );
+
+ PCB_PLOT_PARAMS plot_opts; // starts plotting with default options
+
+ LOCALE_IO toggle; // use standard C notation for float numbers
+
+ const PAGE_INFO& page_info = m_pageInfo ? *m_pageInfo : dummy;
+
+ // Calculate dimensions and center of PCB
+ EDA_RECT bbbox = m_pcb->ComputeBoundingBox( true );
+
+ // Calculate the scale for the format type, scale 1 in HPGL, drawing on
+ // an A4 sheet in PS, + text description of symbols
+ switch( aFormat )
+ {
+ case PLOT_FORMAT_GERBER:
+ offset = GetOffset();
+ plotter = new GERBER_PLOTTER();
+ plotter->SetViewport( offset, IU_PER_DECIMILS, scale, false );
+ plotter->SetGerberCoordinatesFormat( 5 ); // format x.5 unit = mm
+ break;
+
+ case PLOT_FORMAT_HPGL: // Scale for HPGL format.
+ {
+ HPGL_PLOTTER* hpgl_plotter = new HPGL_PLOTTER;
+ plotter = hpgl_plotter;
+ hpgl_plotter->SetPenNumber( plot_opts.GetHPGLPenNum() );
+ hpgl_plotter->SetPenSpeed( plot_opts.GetHPGLPenSpeed() );
+ hpgl_plotter->SetPenOverlap( 0 );
+ plotter->SetPageSettings( page_info );
+ plotter->SetViewport( offset, IU_PER_DECIMILS, scale, false );
+ }
+ break;
+
+
+ default:
+ wxASSERT( false );
+ // fall through
+ case PLOT_FORMAT_PDF:
+ case PLOT_FORMAT_POST:
+ {
+ PAGE_INFO pageA4( wxT( "A4" ) );
+ wxSize pageSizeIU = pageA4.GetSizeIU();
+
+ // Reserve a margin around the page.
+ int margin = KiROUND( 20 * IU_PER_MM );
+
+ // Calculate a scaling factor to print the board on the sheet
+ double Xscale = double( pageSizeIU.x - ( 2 * margin ) ) / bbbox.GetWidth();
+
+ // We should print the list of drill sizes, so reserve room for it
+ // 60% height for board 40% height for list
+ int ypagesize_for_board = KiROUND( pageSizeIU.y * 0.6 );
+ double Yscale = double( ypagesize_for_board - margin ) / bbbox.GetHeight();
+
+ scale = std::min( Xscale, Yscale );
+
+ // Experience shows the scale should not to large, because texts
+ // create problem (can be to big or too small).
+ // So the scale is clipped at 3.0;
+ scale = std::min( scale, 3.0 );
+
+ offset.x = KiROUND( double( bbbox.Centre().x ) -
+ ( pageSizeIU.x / 2.0 ) / scale );
+ offset.y = KiROUND( double( bbbox.Centre().y ) -
+ ( ypagesize_for_board / 2.0 ) / scale );
+
+ if( aFormat == PLOT_FORMAT_PDF )
+ plotter = new PDF_PLOTTER;
+ else
+ plotter = new PS_PLOTTER;
+
+ plotter->SetPageSettings( pageA4 );
+ plotter->SetViewport( offset, IU_PER_DECIMILS, scale, false );
+ }
+ break;
+
+ case PLOT_FORMAT_DXF:
+ {
+ DXF_PLOTTER* dxf_plotter = new DXF_PLOTTER;
+ plotter = dxf_plotter;
+ plotter->SetPageSettings( page_info );
+ plotter->SetViewport( offset, IU_PER_DECIMILS, scale, false );
+ }
+ break;
+
+ case PLOT_FORMAT_SVG:
+ {
+ SVG_PLOTTER* svg_plotter = new SVG_PLOTTER;
+ plotter = svg_plotter;
+ plotter->SetPageSettings( page_info );
+ plotter->SetViewport( offset, IU_PER_DECIMILS, scale, false );
+ }
+ break;
+ }
+
+ plotter->SetCreator( wxT( "PCBNEW" ) );
+ plotter->SetDefaultLineWidth( 5 * IU_PER_MILS );
+ plotter->SetColorMode( false );
+
+ if( ! plotter->OpenFile( aFullFileName ) )
+ {
+ delete plotter;
+ return false;
+ }
+
+ plotter->StartPlot();
+
+ // Draw items on edge layer (not all, only items useful for drill map
+ BRDITEMS_PLOTTER itemplotter( plotter, m_pcb, plot_opts );
+ itemplotter.SetLayerSet( Edge_Cuts );
+
+ for( EDA_ITEM* PtStruct = m_pcb->m_Drawings; PtStruct != NULL; PtStruct = PtStruct->Next() )
+ {
+ switch( PtStruct->Type() )
+ {
+ case PCB_LINE_T:
+ itemplotter.PlotDrawSegment( (DRAWSEGMENT*) PtStruct );
+ break;
+
+ case PCB_TEXT_T:
+ itemplotter.PlotTextePcb( (TEXTE_PCB*) PtStruct );
+ break;
+
+ case PCB_DIMENSION_T:
+ case PCB_TARGET_T:
+ case PCB_MARKER_T: // do not draw
+ default:
+ break;
+ }
+ }
+
+ int x, y;
+ int plotX, plotY, TextWidth;
+ int intervalle = 0;
+ char line[1024];
+ wxString msg;
+ int textmarginaftersymbol = KiROUND( 2 * IU_PER_MM );
+
+ // Set Drill Symbols width
+ plotter->SetDefaultLineWidth( 0.2 * IU_PER_MM / scale );
+ plotter->SetCurrentLineWidth( -1 );
+
+ // Plot board outlines and drill map
+ PlotDrillMarks( plotter );
+
+ // Print a list of symbols used.
+ int charSize = 3 * IU_PER_MM; // text size in IUs
+ double charScale = 1.0 / scale; // real scale will be 1/scale,
+ // because the global plot scale is scale
+ TextWidth = KiROUND( (charSize * charScale) / 10.0 ); // Set text width (thickness)
+ intervalle = KiROUND( charSize * charScale ) + TextWidth;
+
+ // Trace information.
+ plotX = KiROUND( bbbox.GetX() + textmarginaftersymbol * charScale );
+ plotY = bbbox.GetBottom() + intervalle;
+
+ // Plot title "Info"
+ wxString Text = wxT( "Drill Map:" );
+ plotter->Text( wxPoint( plotX, plotY ), UNSPECIFIED_COLOR, Text, 0,
+ wxSize( KiROUND( charSize * charScale ),
+ KiROUND( charSize * charScale ) ),
+ GR_TEXT_HJUSTIFY_LEFT, GR_TEXT_VJUSTIFY_CENTER,
+ TextWidth, false, false );
+
+ for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
+ {
+ DRILL_TOOL& tool = m_toolListBuffer[ii];
+
+ if( tool.m_TotalCount == 0 )
+ continue;
+
+ plotY += intervalle;
+
+ int plot_diam = KiROUND( tool.m_Diameter );
+ x = KiROUND( plotX - textmarginaftersymbol * charScale - plot_diam / 2.0 );
+ y = KiROUND( plotY + charSize * charScale );
+ plotter->Marker( wxPoint( x, y ), plot_diam, ii );
+
+ // List the diameter of each drill in mm and inches.
+ sprintf( line, "%2.2fmm / %2.3f\" ",
+ diameter_in_mm( tool.m_Diameter ),
+ diameter_in_inches( tool.m_Diameter ) );
+
+ msg = FROM_UTF8( line );
+
+ // Now list how many holes and ovals are associated with each drill.
+ if( ( tool.m_TotalCount == 1 )
+ && ( tool.m_OvalCount == 0 ) )
+ sprintf( line, "(1 hole)" );
+ else if( tool.m_TotalCount == 1 ) // && ( toolm_OvalCount == 1 )
+ sprintf( line, "(1 slot)" );
+ else if( tool.m_OvalCount == 0 )
+ sprintf( line, "(%d holes)", tool.m_TotalCount );
+ else if( tool.m_OvalCount == 1 )
+ sprintf( line, "(%d holes + 1 slot)", tool.m_TotalCount - 1 );
+ else // if ( toolm_OvalCount > 1 )
+ sprintf( line, "(%d holes + %d slots)",
+ tool.m_TotalCount - tool.m_OvalCount,
+ tool.m_OvalCount );
+
+ msg += FROM_UTF8( line );
+
+ if( tool.m_Hole_NotPlated )
+ msg += wxT( " (not plated)" );
+
+ plotter->Text( wxPoint( plotX, y ), UNSPECIFIED_COLOR, msg, 0,
+ wxSize( KiROUND( charSize * charScale ),
+ KiROUND( charSize * charScale ) ),
+ GR_TEXT_HJUSTIFY_LEFT, GR_TEXT_VJUSTIFY_CENTER,
+ TextWidth, false, false );
+
+ intervalle = KiROUND( ( ( charSize * charScale ) + TextWidth ) * 1.2 );
+
+ if( intervalle < ( plot_diam + ( 1 * IU_PER_MM / scale ) + TextWidth ) )
+ intervalle = plot_diam + ( 1 * IU_PER_MM / scale ) + TextWidth;
+ }
+
+ plotter->EndPlot();
+ delete plotter;
+
+ return true;
+}
+
+
+bool EXCELLON_WRITER::GenDrillReportFile( const wxString& aFullFileName )
+{
+ FILE_OUTPUTFORMATTER out( aFullFileName );
+
+ static const char separator[] =
+ " =============================================================\n";
+
+ wxASSERT( m_pcb );
+
+ unsigned totalHoleCount;
+ wxString brdFilename = m_pcb->GetFileName();
+
+ std::vector<LAYER_PAIR> hole_sets = getUniqueLayerPairs();
+
+ out.Print( 0, "Drill report for %s\n", TO_UTF8( brdFilename ) );
+ out.Print( 0, "Created on %s\n\n", TO_UTF8( DateAndTime() ) );
+
+ // Output the cu layer stackup, so layer name references make sense.
+ out.Print( 0, "Copper Layer Stackup:\n" );
+ out.Print( 0, separator );
+
+ LSET cu = m_pcb->GetEnabledLayers() & LSET::AllCuMask();
+
+ int conventional_layer_num = 1;
+ for( LSEQ seq = cu.Seq(); seq; ++seq, ++conventional_layer_num )
+ {
+ out.Print( 0, " L%-2d: %-25s %s\n",
+ conventional_layer_num,
+ TO_UTF8( m_pcb->GetLayerName( *seq ) ),
+ layerName( *seq ).c_str() // generic layer name
+ );
+ }
+
+ out.Print( 0, "\n\n" );
+
+ /* output hole lists:
+ * 1 - through holes
+ * 2 - for partial holes only: by layer starting and ending pair
+ * 3 - Non Plated through holes
+ */
+
+ bool buildNPTHlist = false;
+
+ // in this loop are plated only:
+ for( unsigned pair_ndx = 0; pair_ndx < hole_sets.size(); ++pair_ndx )
+ {
+ LAYER_PAIR pair = hole_sets[pair_ndx];
+
+ BuildHolesList( pair, buildNPTHlist );
+
+ if( pair == LAYER_PAIR( F_Cu, B_Cu ) )
+ {
+ out.Print( 0, "Drill file '%s' contains\n",
+ TO_UTF8( drillFileName( pair, false ) ) );
+
+ out.Print( 0, " plated through holes:\n" );
+ out.Print( 0, separator );
+ totalHoleCount = printToolSummary( out, false );
+ out.Print( 0, " Total plated holes count %u\n", totalHoleCount );
+ }
+ else // blind/buried
+ {
+ out.Print( 0, "Drill file '%s' contains\n",
+ TO_UTF8( drillFileName( pair, false ) ) );
+
+ out.Print( 0, " holes connecting layer pair: '%s and %s' (%s vias):\n",
+ TO_UTF8( m_pcb->GetLayerName( ToLAYER_ID( pair.first ) ) ),
+ TO_UTF8( m_pcb->GetLayerName( ToLAYER_ID( pair.second ) ) ),
+ pair.first == F_Cu || pair.second == B_Cu ? "blind" : "buried"
+ );
+
+ out.Print( 0, separator );
+ totalHoleCount = printToolSummary( out, false );
+ out.Print( 0, " Total plated holes count %u\n", totalHoleCount );
+ }
+
+ out.Print( 0, "\n\n" );
+ }
+
+ // NPTHoles. Generate the full list (pads+vias) if PTH and NPTH are merged,
+ // or only the NPTH list (which never has vias)
+ if( !m_merge_PTH_NPTH )
+ buildNPTHlist = true;
+
+ BuildHolesList( LAYER_PAIR( F_Cu, B_Cu ), buildNPTHlist );
+
+ // nothing wrong with an empty NPTH file in report.
+ if( m_merge_PTH_NPTH )
+ out.Print( 0, "Not plated through holes are merged with plated holes\n" );
+ else
+ out.Print( 0, "Drill file '%s' contains\n",
+ TO_UTF8( drillFileName( LAYER_PAIR( F_Cu, B_Cu ), true ) ) );
+
+ out.Print( 0, " unplated through holes:\n" );
+ out.Print( 0, separator );
+ totalHoleCount = printToolSummary( out, true );
+ out.Print( 0, " Total unplated holes count %u\n", totalHoleCount );
+
+ return true;
+}
+
+
+bool EXCELLON_WRITER::PlotDrillMarks( PLOTTER* aPlotter )
+{
+ // Plot the drill map:
+ wxPoint pos;
+
+ for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
+ {
+ const HOLE_INFO& hole = m_holeListBuffer[ii];
+ pos = hole.m_Hole_Pos;
+
+ // Always plot the drill symbol (for slots identifies the needed cutter!
+ aPlotter->Marker( pos, hole.m_Hole_Diameter, hole.m_Tool_Reference - 1 );
+
+ if( hole.m_Hole_Shape != 0 )
+ {
+ wxSize oblong_size = hole.m_Hole_Size;
+ aPlotter->FlashPadOval( pos, oblong_size, hole.m_Hole_Orient, SKETCH );
+ }
+ }
+
+ return true;
+}
+
+
+unsigned EXCELLON_WRITER::printToolSummary( OUTPUTFORMATTER& out, bool aSummaryNPTH ) const
+{
+ unsigned totalHoleCount = 0;
+
+ for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
+ {
+ const DRILL_TOOL& tool = m_toolListBuffer[ii];
+
+ if( aSummaryNPTH && !tool.m_Hole_NotPlated )
+ continue;
+
+ if( !aSummaryNPTH && tool.m_Hole_NotPlated )
+ continue;
+
+ // List the tool number assigned to each drill,
+ // in mm then in inches.
+ int tool_number = ii+1;
+ out.Print( 0, " T%d %2.2fmm %2.3f\" ", tool_number,
+ diameter_in_mm( tool.m_Diameter ),
+ diameter_in_inches( tool.m_Diameter ) );
+
+ // Now list how many holes and ovals are associated with each drill.
+ if( ( tool.m_TotalCount == 1 ) && ( tool.m_OvalCount == 0 ) )
+ out.Print( 0, "(1 hole)\n" );
+ else if( tool.m_TotalCount == 1 )
+ out.Print( 0, "(1 hole) (with 1 slot)\n" );
+ else if( tool.m_OvalCount == 0 )
+ out.Print( 0, "(%d holes)\n", tool.m_TotalCount );
+ else if( tool.m_OvalCount == 1 )
+ out.Print( 0, "(%d holes) (with 1 slot)\n", tool.m_TotalCount );
+ else // tool.m_OvalCount > 1
+ out.Print( 0, "(%d holes) (with %d slots)\n",
+ tool.m_TotalCount, tool.m_OvalCount );
+
+ totalHoleCount += tool.m_TotalCount;
+ }
+
+ out.Print( 0, "\n" );
+
+ return totalHoleCount;
+}
diff --git a/pcbnew/exporters/gen_modules_placefile.cpp b/pcbnew/exporters/gen_modules_placefile.cpp
new file mode 100644
index 0000000..202ddb1
--- /dev/null
+++ b/pcbnew/exporters/gen_modules_placefile.cpp
@@ -0,0 +1,690 @@
+/**
+ * @file gen_modules_placefile.cpp
+ */
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2015 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
+ */
+
+/*
+ * 1 - create ascii files for automatic placement of smd components
+ * 2 - create a module report (pos and module descr) (ascii file)
+ */
+
+#include <fctsys.h>
+#include <confirm.h>
+#include <kicad_string.h>
+#include <gestfich.h>
+#include <wxPcbStruct.h>
+#include <pgm_base.h>
+#include <build_version.h>
+#include <macros.h>
+#include <reporter.h>
+
+#include <class_board.h>
+#include <class_module.h>
+
+#include <pcbnew.h>
+#include <wildcards_and_files_ext.h>
+#include <kiface_i.h>
+#include <wx_html_report_panel.h>
+
+
+#include <dialog_gen_module_position_file_base.h>
+/*
+ * The format of the kicad place file is:
+ * ### Module positions - created on 04/12/2012 15:24:24 ###
+ * ### Printed by Pcbnew version pcbnew (2012-11-30 BZR 3828)-testing
+ * ## Unit = inches, Angle = deg.
+ * or
+ * ## Unit = mm, Angle = deg.
+ * ## Side : top
+ * or
+ * ## Side : bottom
+ * or
+ * ## Side : all
+ * # Ref Val Package PosX PosY Rot Side
+ * C123 0,1uF/50V SM0603 1.6024 -2.6280 180.0 Front
+ * C124 0,1uF/50V SM0603 1.6063 -2.7579 180.0 Front
+ * C125 0,1uF/50V SM0603 1.6010 -2.8310 180.0 Front
+ * ## End
+ */
+
+#define PLACEFILE_UNITS_KEY wxT( "PlaceFileUnits" )
+#define PLACEFILE_OPT_KEY wxT( "PlaceFileOpts" )
+
+
+#define PCB_BACK_SIDE 0
+#define PCB_FRONT_SIDE 1
+#define PCB_BOTH_SIDES 2
+
+class LIST_MOD // An helper class used to build a list of useful footprints.
+{
+public:
+ MODULE* m_Module; // Link to the actual footprint
+ wxString m_Reference; // Its schematic reference
+ wxString m_Value; // Its schematic value
+ LAYER_NUM m_Layer; // its side (B_Cu, or F_Cu)
+};
+
+
+/**
+ * The dialog to create footprint position files,
+ * and choose options (one or 2 files, units and force all SMD footprints in list)
+ */
+class DIALOG_GEN_MODULE_POSITION : public DIALOG_GEN_MODULE_POSITION_BASE
+{
+public:
+ DIALOG_GEN_MODULE_POSITION( PCB_EDIT_FRAME * aParent ):
+ DIALOG_GEN_MODULE_POSITION_BASE( aParent ),
+ m_parent( aParent ),
+ m_plotOpts( aParent->GetPlotSettings() )
+ {
+ m_reporter = &m_messagesPanel->Reporter();
+ initDialog();
+
+ GetSizer()->SetSizeHints(this);
+ Centre();
+ }
+
+private:
+ PCB_EDIT_FRAME* m_parent;
+ PCB_PLOT_PARAMS m_plotOpts;
+ wxConfigBase* m_config;
+ REPORTER* m_reporter;
+
+ static int m_unitsOpt;
+ static int m_fileOpt;
+
+ void initDialog();
+ void OnOutputDirectoryBrowseClicked( wxCommandEvent& event );
+ void OnOKButton( wxCommandEvent& event );
+
+ bool CreateFiles();
+
+ // accessors to options:
+ wxString GetOutputDirectory()
+ {
+ return m_outputDirectoryName->GetValue();
+ }
+
+ bool UnitsMM()
+ {
+ return m_radioBoxUnits->GetSelection() == 1;
+ }
+
+ bool OneFileOnly()
+ {
+ return m_radioBoxFilesCount->GetSelection() == 1;
+ }
+
+ bool ForceAllSmd()
+ {
+ return m_radioBoxForceSmd->GetSelection() == 1;
+ }
+};
+
+
+// Static members to remember choices
+int DIALOG_GEN_MODULE_POSITION::m_unitsOpt = 0;
+int DIALOG_GEN_MODULE_POSITION::m_fileOpt = 0;
+
+// Use standard board side name. do not translate them,
+// they are keywords in place file
+const wxString frontSideName = wxT( "top" );
+const wxString backSideName = wxT( "bottom" );
+
+void DIALOG_GEN_MODULE_POSITION::initDialog()
+{
+ m_config = Kiface().KifaceSettings();
+ m_config->Read( PLACEFILE_UNITS_KEY, &m_unitsOpt, 1 );
+ m_config->Read( PLACEFILE_OPT_KEY, &m_fileOpt, 0 );
+
+ // Output directory
+ m_outputDirectoryName->SetValue( m_plotOpts.GetOutputDirectory() );
+ m_radioBoxUnits->SetSelection( m_unitsOpt );
+ m_radioBoxFilesCount->SetSelection( m_fileOpt );
+
+ m_sdbSizerButtonsOK->SetDefault();
+}
+
+void DIALOG_GEN_MODULE_POSITION::OnOutputDirectoryBrowseClicked( wxCommandEvent& event )
+{
+ // Build the absolute path of current output plot directory
+ // to preselect it when opening the dialog.
+ wxString path = Prj().AbsolutePath( m_outputDirectoryName->GetValue() );
+
+ wxDirDialog dirDialog( this, _( "Select Output Directory" ), path );
+
+ if( dirDialog.ShowModal() == wxID_CANCEL )
+ return;
+
+ wxFileName dirName = wxFileName::DirName( dirDialog.GetPath() );
+
+ wxMessageDialog dialog( this, _( "Use a relative path? "),
+ _( "Plot Output Directory" ),
+ wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT );
+
+ if( dialog.ShowModal() == wxID_YES )
+ {
+ wxString boardFilePath = ( (wxFileName) m_parent->GetBoard()->GetFileName()).GetPath();
+
+ if( !dirName.MakeRelativeTo( boardFilePath ) )
+ wxMessageBox( _( "Cannot make path relative (target volume different from board file volume)!" ),
+ _( "Plot Output Directory" ), wxOK | wxICON_ERROR );
+ }
+
+ m_outputDirectoryName->SetValue( dirName.GetFullPath() );
+}
+
+void DIALOG_GEN_MODULE_POSITION::OnOKButton( wxCommandEvent& event )
+{
+ m_unitsOpt = m_radioBoxUnits->GetSelection();
+ m_fileOpt = m_radioBoxFilesCount->GetSelection();
+
+ m_config->Write( PLACEFILE_UNITS_KEY, m_unitsOpt );
+ m_config->Write( PLACEFILE_OPT_KEY, m_fileOpt );
+
+ // Set output directory and replace backslashes with forward ones
+ // (Keep unix convention in cfg files)
+ wxString dirStr;
+ dirStr = m_outputDirectoryName->GetValue();
+ dirStr.Replace( wxT( "\\" ), wxT( "/" ) );
+
+ m_plotOpts.SetOutputDirectory( dirStr );
+
+ m_parent->SetPlotSettings( m_plotOpts );
+
+ CreateFiles();
+}
+
+
+bool DIALOG_GEN_MODULE_POSITION::CreateFiles()
+{
+ BOARD * brd = m_parent->GetBoard();
+ wxFileName fn;
+ wxString msg;
+ bool singleFile = OneFileOnly();
+ int fullcount = 0;
+
+ // Count the footprints to place, do not yet create a file
+ int fpcount = m_parent->DoGenFootprintsPositionFile( wxEmptyString, UnitsMM(),
+ ForceAllSmd(), PCB_BOTH_SIDES );
+ if( fpcount == 0)
+ {
+ wxMessageBox( _( "No footprint for automated placement." ) );
+ return false;
+ }
+
+ // Create output directory if it does not exist (also transform it in
+ // absolute form). Bail if it fails
+ wxFileName outputDir = wxFileName::DirName( m_plotOpts.GetOutputDirectory() );
+ wxString boardFilename = m_parent->GetBoard()->GetFileName();
+
+ m_reporter = &m_messagesPanel->Reporter();
+
+ if( !EnsureFileDirectoryExists( &outputDir, boardFilename, m_reporter ) )
+ {
+ msg.Printf( _( "Could not write plot files to folder \"%s\"." ),
+ GetChars( outputDir.GetPath() ) );
+ DisplayError( this, msg );
+ return false;
+ }
+
+ fn = m_parent->GetBoard()->GetFileName();
+ fn.SetPath( outputDir.GetPath() );
+
+ // Create the the Front or Top side placement file,
+ // or the single file
+ int side = PCB_FRONT_SIDE;
+
+ if( singleFile )
+ {
+ side = PCB_BOTH_SIDES;
+ fn.SetName( fn.GetName() + wxT( "-" ) + wxT("all") );
+ }
+ else
+ fn.SetName( fn.GetName() + wxT( "-" ) + frontSideName );
+
+ fn.SetExt( FootprintPlaceFileExtension );
+
+ fpcount = m_parent->DoGenFootprintsPositionFile( fn.GetFullPath(), UnitsMM(),
+ ForceAllSmd(), side );
+ if( fpcount < 0 )
+ {
+ msg.Printf( _( "Unable to create '%s'." ), GetChars( fn.GetFullPath() ) );
+ wxMessageBox( msg );
+ m_reporter->Report( msg, REPORTER::RPT_ERROR );
+ return false;
+ }
+
+ if( singleFile )
+ msg.Printf( _( "Place file: '%s'." ), GetChars( fn.GetFullPath() ) );
+ else
+ msg.Printf( _( "Front side (top side) place file: '%s'." ),
+ GetChars( fn.GetFullPath() ) );
+ m_reporter->Report( msg, REPORTER::RPT_INFO );
+
+ msg.Printf( _( "Component count: %d." ), fpcount );
+ m_reporter->Report( msg, REPORTER::RPT_INFO );
+
+ if( singleFile )
+ {
+ m_reporter->Report( _( "Component Placement File generation OK." ), REPORTER::RPT_ACTION );
+ return true;
+ }
+
+ // Create the Back or Bottom side placement file
+ fullcount = fpcount;
+ side = PCB_BACK_SIDE;
+ fn = brd->GetFileName();
+ fn.SetPath( outputDir.GetPath() );
+ fn.SetName( fn.GetName() + wxT( "-" ) + backSideName );
+ fn.SetExt( wxT( "pos" ) );
+
+ fpcount = m_parent->DoGenFootprintsPositionFile( fn.GetFullPath(), UnitsMM(),
+ ForceAllSmd(), side );
+
+ if( fpcount < 0 )
+ {
+ msg.Printf( _( "Unable to create file '%s'." ), GetChars( fn.GetFullPath() ) );
+ m_reporter->Report( msg, REPORTER::RPT_ERROR );
+ wxMessageBox( msg );
+ return false;
+ }
+
+ // Display results
+ if( !singleFile )
+ {
+ msg.Printf( _( "Back side (bottom side) place file: '%s'." ), GetChars( fn.GetFullPath() ) );
+ m_reporter->Report( msg, REPORTER::RPT_INFO );
+
+ msg.Printf( _( "Component count: %d." ), fpcount );
+
+ m_reporter->Report( msg, REPORTER::RPT_INFO );
+ }
+
+ if( !singleFile )
+ {
+ fullcount += fpcount;
+ msg.Printf( _( "Full component count: %d\n" ), fullcount );
+ m_reporter->Report( msg, REPORTER::RPT_INFO );
+ }
+
+ m_reporter->Report( _( "Component Placement File generation OK." ), REPORTER::RPT_ACTION );
+
+ return true;
+}
+
+// Defined values to write coordinates using inches or mm:
+static const double conv_unit_inch = 0.001 / IU_PER_MILS ; // units = INCHES
+static const char unit_text_inch[] = "## Unit = inches, Angle = deg.\n";
+
+static const double conv_unit_mm = 1.0 / IU_PER_MM; // units = mm
+static const char unit_text_mm[] = "## Unit = mm, Angle = deg.\n";
+
+static wxPoint File_Place_Offset; // Offset coordinates for generated file.
+
+
+// Sort function use by GenereModulesPosition()
+// sort is made by side (layer) top layer first
+// then by reference increasing order
+static bool sortFPlist( const LIST_MOD& ref, const LIST_MOD& tst )
+{
+ if( ref.m_Layer == tst.m_Layer )
+ return StrNumCmp( ref.m_Reference, tst.m_Reference, 16 ) < 0;
+
+ return ref.m_Layer > tst.m_Layer;
+}
+
+
+/**
+ * Helper function HasNonSMDPins
+ * returns true if the given module has any non smd pins, such as through hole
+ * and therefore cannot be placed automatically.
+ */
+static bool HasNonSMDPins( MODULE* aModule )
+{
+ D_PAD* pad;
+
+ for( pad = aModule->Pads(); pad; pad = pad->Next() )
+ {
+ if( pad->GetAttribute() != PAD_ATTRIB_SMD )
+ return true;
+ }
+
+ return false;
+}
+
+void PCB_EDIT_FRAME::GenFootprintsPositionFile( wxCommandEvent& event )
+{
+ DIALOG_GEN_MODULE_POSITION dlg( this );
+ dlg.ShowModal();
+}
+
+/*
+ * Creates a footprint position file
+ * aSide = 0 -> Back (bottom) side)
+ * aSide = 1 -> Front (top) side)
+ * aSide = 2 -> both sides
+ * if aFullFileName is empty, the file is not created, only the
+ * count of footprints to place is returned
+ */
+int PCB_EDIT_FRAME::DoGenFootprintsPositionFile( const wxString& aFullFileName,
+ bool aUnitsMM,
+ bool aForceSmdItems, int aSide )
+{
+ MODULE* footprint;
+
+ // Minimal text lenghts:
+ int lenRefText = 8;
+ int lenValText = 8;
+ int lenPkgText = 16;
+
+ File_Place_Offset = GetAuxOrigin();
+
+ // Calculating the number of useful footprints (CMS attribute, not VIRTUAL)
+ int footprintCount = 0;
+
+ // Select units:
+ double conv_unit = aUnitsMM ? conv_unit_mm : conv_unit_inch;
+ const char *unit_text = aUnitsMM ? unit_text_mm : unit_text_inch;
+
+ // Build and sort the list of footprints alphabetically
+ std::vector<LIST_MOD> list;
+ list.reserve( footprintCount );
+
+ for( footprint = GetBoard()->m_Modules; footprint; footprint = footprint->Next() )
+ {
+ if( aSide != PCB_BOTH_SIDES )
+ {
+ if( footprint->GetLayer() == B_Cu && aSide == PCB_FRONT_SIDE)
+ continue;
+ if( footprint->GetLayer() == F_Cu && aSide == PCB_BACK_SIDE)
+ continue;
+ }
+
+ if( footprint->GetAttributes() & MOD_VIRTUAL )
+ {
+ DBG( printf( "skipping footprint %s because it's virtual\n",
+ TO_UTF8( footprint->GetReference() ) );)
+ continue;
+ }
+
+ if( ( footprint->GetAttributes() & MOD_CMS ) == 0 )
+ {
+ if( aForceSmdItems ) // true to fix a bunch of mis-labeled footprints:
+ {
+ if( !HasNonSMDPins( footprint ) )
+ {
+ // all footprint's pins are SMD, mark the part for pick and place
+ footprint->SetAttributes( footprint->GetAttributes() | MOD_CMS );
+ OnModify();
+ }
+ else
+ {
+ DBG(printf( "skipping %s because its attribute is not CMS and it has non SMD pins\n",
+ TO_UTF8(footprint->GetReference()) ) );
+ continue;
+ }
+ }
+ else
+ continue;
+ }
+
+ footprintCount++;
+
+ LIST_MOD item;
+ item.m_Module = footprint;
+ item.m_Reference = footprint->GetReference();
+ item.m_Value = footprint->GetValue();
+ item.m_Layer = footprint->GetLayer();
+ list.push_back( item );
+
+ lenRefText = std::max( lenRefText, int(item.m_Reference.length()) );
+ lenValText = std::max( lenValText, int(item.m_Value.length()) );
+ lenPkgText = std::max( lenPkgText, int(item.m_Module->GetFPID().GetFootprintName().length()) );
+ }
+
+ if( aFullFileName.IsEmpty() )
+ return footprintCount;
+
+ FILE * file = wxFopen( aFullFileName, wxT( "wt" ) );
+ if( file == NULL )
+ return -1;
+
+ if( list.size() > 1 )
+ sort( list.begin(), list.end(), sortFPlist );
+
+ // Switch the locale to standard C (needed to print floating point numbers)
+ LOCALE_IO toggle;
+
+ // Write file header
+ fprintf( file, "### Module positions - created on %s ###\n", TO_UTF8( DateAndTime() ) );
+
+ wxString Title = Pgm().App().GetAppName() + wxT( " " ) + GetBuildVersion();
+ fprintf( file, "### Printed by Pcbnew version %s\n", TO_UTF8( Title ) );
+
+ fputs( unit_text, file );
+
+ fputs( "## Side : ", file );
+
+ if( aSide == PCB_BACK_SIDE )
+ fputs( TO_UTF8( backSideName ), file );
+ else if( aSide == PCB_FRONT_SIDE )
+ fputs( TO_UTF8( frontSideName ), file );
+ else
+ fputs( "All", file );
+
+ fputs( "\n", file );
+
+ fprintf(file, "%-*s %-*s %-*s %9.9s %9.9s %8.8s %s\n",
+ int(lenRefText), "# Ref",
+ int(lenValText), "Val",
+ int(lenPkgText), "Package",
+ "PosX", "PosY", "Rot", "Side" );
+
+ for( int ii = 0; ii < footprintCount; ii++ )
+ {
+ wxPoint footprint_pos;
+ footprint_pos = list[ii].m_Module->GetPosition();
+ footprint_pos -= File_Place_Offset;
+
+ LAYER_NUM layer = list[ii].m_Module->GetLayer();
+ wxASSERT( layer==F_Cu || layer==B_Cu );
+
+ const wxString& ref = list[ii].m_Reference;
+ const wxString& val = list[ii].m_Value;
+ const wxString& pkg = list[ii].m_Module->GetFPID().GetFootprintName();
+
+ fprintf(file, "%-*s %-*s %-*s %9.4f %9.4f %8.4f %s\n",
+ lenRefText, TO_UTF8( ref ),
+ lenValText, TO_UTF8( val ),
+ lenPkgText, TO_UTF8( pkg ),
+ footprint_pos.x * conv_unit,
+ // Keep the coordinates in the first quadrant,
+ // (i.e. change y sign
+ -footprint_pos.y * conv_unit,
+ list[ii].m_Module->GetOrientation() / 10.0,
+ (layer == F_Cu ) ? TO_UTF8( frontSideName ) : TO_UTF8( backSideName ));
+ }
+
+ // Write EOF
+ fputs( "## End\n", file );
+
+ fclose( file );
+ return footprintCount;
+}
+
+
+void PCB_EDIT_FRAME::GenFootprintsReport( wxCommandEvent& event )
+{
+ wxFileName fn;
+
+ wxString boardFilePath = ( (wxFileName) GetBoard()->GetFileName()).GetPath();
+ wxDirDialog dirDialog( this, _( "Select Output Directory" ), boardFilePath );
+
+ if( dirDialog.ShowModal() == wxID_CANCEL )
+ return;
+
+ fn = GetBoard()->GetFileName();
+ fn.SetPath( dirDialog.GetPath() );
+ fn.SetExt( wxT( "rpt" ) );
+
+ bool unitMM = g_UserUnit != INCHES;
+ bool success = DoGenFootprintsReport( fn.GetFullPath(), unitMM );
+
+ wxString msg;
+ if( success )
+ {
+ msg.Printf( _( "Footprint report file created:\n'%s'" ),
+ GetChars( fn.GetFullPath() ) );
+ wxMessageBox( msg, _( "Footprint Report" ), wxICON_INFORMATION );
+ }
+
+ else
+ {
+ msg.Printf( _( "Unable to create '%s'" ), GetChars( fn.GetFullPath() ) );
+ DisplayError( this, msg );
+ }
+}
+
+/* Print a module report.
+ */
+bool PCB_EDIT_FRAME::DoGenFootprintsReport( const wxString& aFullFilename, bool aUnitsMM )
+{
+ wxString msg;
+ FILE* rptfile;
+ wxPoint module_pos;
+
+ File_Place_Offset = wxPoint( 0, 0 );
+
+ rptfile = wxFopen( aFullFilename, wxT( "wt" ) );
+
+ if( rptfile == NULL )
+ return false;
+
+ // Select units:
+ double conv_unit = aUnitsMM ? conv_unit_mm : conv_unit_inch;
+ const char *unit_text = aUnitsMM ? unit_text_mm : unit_text_inch;
+
+ LOCALE_IO toggle;
+
+ // Generate header file comments.)
+ fprintf( rptfile, "## Footprint report - date %s\n", TO_UTF8( DateAndTime() ) );
+
+ wxString Title = Pgm().App().GetAppName() + wxT( " " ) + GetBuildVersion();
+ fprintf( rptfile, "## Created by Pcbnew version %s\n", TO_UTF8( Title ) );
+ fputs( unit_text, rptfile );
+
+ fputs( "\n$BeginDESCRIPTION\n", rptfile );
+
+ EDA_RECT bbbox = GetBoard()->ComputeBoundingBox();
+
+ fputs( "\n$BOARD\n", rptfile );
+
+ fprintf( rptfile, "upper_left_corner %9.6f %9.6f\n",
+ bbbox.GetX() * conv_unit,
+ bbbox.GetY() * conv_unit );
+
+ fprintf( rptfile, "lower_right_corner %9.6f %9.6f\n",
+ bbbox.GetRight() * conv_unit,
+ bbbox.GetBottom() * conv_unit );
+
+ fputs( "$EndBOARD\n\n", rptfile );
+
+ for( MODULE* Module = GetBoard()->m_Modules; Module; Module = Module->Next() )
+ {
+ fprintf( rptfile, "$MODULE %s\n", EscapedUTF8( Module->GetReference() ).c_str() );
+
+ fprintf( rptfile, "reference %s\n", EscapedUTF8( Module->GetReference() ).c_str() );
+ fprintf( rptfile, "value %s\n", EscapedUTF8( Module->GetValue() ).c_str() );
+ fprintf( rptfile, "footprint %s\n",
+ EscapedUTF8( FROM_UTF8( Module->GetFPID().Format().c_str() ) ).c_str() );
+
+ msg = wxT( "attribut" );
+
+ if( Module->GetAttributes() & MOD_VIRTUAL )
+ msg += wxT( " virtual" );
+
+ if( Module->GetAttributes() & MOD_CMS )
+ msg += wxT( " smd" );
+
+ if( ( Module->GetAttributes() & (MOD_VIRTUAL | MOD_CMS) ) == 0 )
+ msg += wxT( " none" );
+
+ msg += wxT( "\n" );
+ fputs( TO_UTF8( msg ), rptfile );
+
+ module_pos = Module->GetPosition();
+ module_pos.x -= File_Place_Offset.x;
+ module_pos.y -= File_Place_Offset.y;
+
+ fprintf( rptfile, "position %9.6f %9.6f orientation %.2f\n",
+ module_pos.x * conv_unit,
+ module_pos.y * conv_unit,
+ Module->GetOrientation() / 10.0 );
+
+ if( Module->GetLayer() == F_Cu )
+ fputs( "layer front\n", rptfile );
+ else if( Module->GetLayer() == B_Cu )
+ fputs( "layer back\n", rptfile );
+ else
+ fputs( "layer other\n", rptfile );
+
+ for( D_PAD* pad = Module->Pads(); pad != NULL; pad = pad->Next() )
+ {
+ fprintf( rptfile, "$PAD \"%s\"\n", TO_UTF8( pad->GetPadName() ) );
+ int layer = 0;
+
+ if( pad->GetLayerSet()[B_Cu] )
+ layer = 1;
+
+ if( pad->GetLayerSet()[F_Cu] )
+ layer |= 2;
+
+ static const char* layer_name[4] = { "nocopper", "back", "front", "both" };
+ fprintf( rptfile, "Shape %s Layer %s\n", TO_UTF8( pad->ShowPadShape() ), layer_name[layer] );
+
+ fprintf( rptfile, "position %9.6f %9.6f size %9.6f %9.6f orientation %.2f\n",
+ pad->GetPos0().x * conv_unit, pad->GetPos0().y * conv_unit,
+ pad->GetSize().x * conv_unit, pad->GetSize().y * conv_unit,
+ (pad->GetOrientation() - Module->GetOrientation()) / 10.0 );
+
+ fprintf( rptfile, "drill %9.6f\n", pad->GetDrillSize().x * conv_unit );
+
+ fprintf( rptfile, "shape_offset %9.6f %9.6f\n",
+ pad->GetOffset().x * conv_unit,
+ pad->GetOffset().y * conv_unit );
+
+ fprintf( rptfile, "$EndPAD\n" );
+ }
+
+ fprintf( rptfile, "$EndMODULE %s\n\n", TO_UTF8 (Module->GetReference() ) );
+ }
+
+ // Generate EOF.
+ fputs( "$EndDESCRIPTION\n", rptfile );
+ fclose( rptfile );
+
+ return true;
+}
+
diff --git a/pcbnew/exporters/gendrill_Excellon_writer.cpp b/pcbnew/exporters/gendrill_Excellon_writer.cpp
new file mode 100644
index 0000000..2a607f7
--- /dev/null
+++ b/pcbnew/exporters/gendrill_Excellon_writer.cpp
@@ -0,0 +1,780 @@
+/**
+ * @file gendrill_Excellon_writer.cpp
+ * @brief Functions to create EXCELLON drill files and report files.
+ */
+
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 1992-2012 Jean_Pierre Charras <jp.charras at wanadoo.fr>
+ * Copyright (C) 1992-2016 KiCad Developers, see change_log.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
+ */
+
+/**
+ * @see for EXCELLON format, see:
+ * http://www.excellon.com/manuals/program.htm
+ * and the CNC-7 manual.
+ */
+
+#include <fctsys.h>
+
+#include <vector>
+
+#include <plot_common.h>
+#include <trigo.h>
+#include <macros.h>
+#include <kicad_string.h>
+#include <wxPcbStruct.h>
+#include <pgm_base.h>
+#include <build_version.h>
+
+#include <class_board.h>
+#include <class_module.h>
+#include <class_track.h>
+
+#include <pcbplot.h>
+#include <pcbnew.h>
+#include <gendrill_Excellon_writer.h>
+#include <wildcards_and_files_ext.h>
+#include <reporter.h>
+#include <collectors.h>
+
+// Comment/uncomment this to write or not a comment
+// in drill file when PTH and NPTH are merged to flag
+// tools used for PTH and tools used for NPTH
+// #define WRITE_PTH_NPTH_COMMENT
+
+
+EXCELLON_WRITER::EXCELLON_WRITER( BOARD* aPcb )
+{
+ m_file = NULL;
+ m_pcb = aPcb;
+ m_zeroFormat = DECIMAL_FORMAT;
+ m_conversionUnits = 0.0001;
+ m_unitsDecimal = true;
+ m_mirror = false;
+ m_merge_PTH_NPTH = false;
+ m_minimalHeader = false;
+ m_ShortHeader = false;
+ m_mapFileFmt = PLOT_FORMAT_PDF;
+ m_pageInfo = NULL;
+}
+
+
+void EXCELLON_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
+ bool aGenDrill, bool aGenMap,
+ REPORTER * aReporter )
+{
+ wxFileName fn;
+ wxString msg;
+
+ std::vector<LAYER_PAIR> hole_sets = getUniqueLayerPairs();
+
+ // append a pair representing the NPTH set of holes, for separate drill files.
+ if( !m_merge_PTH_NPTH )
+ hole_sets.push_back( LAYER_PAIR( F_Cu, B_Cu ) );
+
+ for( std::vector<LAYER_PAIR>::const_iterator it = hole_sets.begin();
+ it != hole_sets.end(); ++it )
+ {
+ LAYER_PAIR pair = *it;
+ // For separate drill files, the last layer pair is the NPTH dril file.
+ bool doing_npth = m_merge_PTH_NPTH ? false : ( it == hole_sets.end() - 1 );
+
+ BuildHolesList( pair, doing_npth );
+
+ if( GetHolesCount() > 0 ) // has holes?
+ {
+ fn = drillFileName( pair, doing_npth );
+ fn.SetPath( aPlotDirectory );
+
+ if( aGenDrill )
+ {
+ wxString fullFilename = fn.GetFullPath();
+
+ FILE* file = wxFopen( fullFilename, wxT( "w" ) );
+
+ if( file == NULL )
+ {
+ if( aReporter )
+ {
+ msg.Printf( _( "** Unable to create %s **\n" ),
+ GetChars( fullFilename ) );
+ aReporter->Report( msg );
+ }
+ break;
+ }
+ else
+ {
+ if( aReporter )
+ {
+ msg.Printf( _( "Create file %s\n" ), GetChars( fullFilename ) );
+ aReporter->Report( msg );
+ }
+ }
+
+ CreateDrillFile( file );
+ }
+
+ if( aGenMap )
+ {
+ fn.SetExt( wxEmptyString ); // Will be added by GenDrillMap
+ wxString fullfilename = fn.GetFullPath() + wxT( "-drl_map" );
+ fullfilename << wxT(".") << GetDefaultPlotExtension( m_mapFileFmt );
+
+ bool success = GenDrillMapFile( fullfilename, m_mapFileFmt );
+
+ if( ! success )
+ {
+ if( aReporter )
+ {
+ msg.Printf( _( "** Unable to create %s **\n" ), GetChars( fullfilename ) );
+ aReporter->Report( msg );
+ }
+
+ return;
+ }
+ else
+ {
+ if( aReporter )
+ {
+ msg.Printf( _( "Create file %s\n" ), GetChars( fullfilename ) );
+ aReporter->Report( msg );
+ }
+ }
+ }
+ }
+ }
+}
+
+
+
+/*
+ * Creates the drill files in EXCELLON format
+ * Number format:
+ * - Floating point format
+ * - integer format
+ * - integer format: "Trailing Zero" ( TZ ) or "Leading Zero"
+ * Units
+ * - Decimal
+ * - Metric
+ */
+int EXCELLON_WRITER::CreateDrillFile( FILE* aFile )
+{
+ m_file = aFile;
+
+ int diam, holes_count;
+ int x0, y0, xf, yf, xc, yc;
+ double xt, yt;
+ char line[1024];
+
+ LOCALE_IO dummy; // Use the standard notation for double numbers
+
+ WriteEXCELLONHeader();
+
+ holes_count = 0;
+
+#ifdef WRITE_PTH_NPTH_COMMENT
+ // if PTH_ and NPTH are merged write a comment in drill file at the
+ // beginning of NPTH section
+ bool writePTHcomment = m_merge_PTH_NPTH;
+ bool writeNPTHcomment = m_merge_PTH_NPTH;
+#endif
+
+ /* Write the tool list */
+ for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
+ {
+ DRILL_TOOL& tool_descr = m_toolListBuffer[ii];
+
+#ifdef WRITE_PTH_NPTH_COMMENT
+ if( writePTHcomment && !tool_descr.m_Hole_NotPlated )
+ {
+ writePTHcomment = false;
+ fprintf( m_file, ";TYPE=PLATED\n" );
+ }
+
+ if( writeNPTHcomment && tool_descr.m_Hole_NotPlated )
+ {
+ writeNPTHcomment = false;
+ fprintf( m_file, ";TYPE=NON_PLATED\n" );
+ }
+#endif
+
+ fprintf( m_file, "T%dC%.3f\n", ii + 1,
+ tool_descr.m_Diameter * m_conversionUnits );
+ }
+
+ fputs( "%\n", m_file ); // End of header info
+
+ fputs( "G90\n", m_file ); // Absolute mode
+ fputs( "G05\n", m_file ); // Drill mode
+
+ // Units :
+ if( !m_minimalHeader )
+ {
+ if( m_unitsDecimal )
+ fputs( "M71\n", m_file ); /* M71 = metric mode */
+ else
+ fputs( "M72\n", m_file ); /* M72 = inch mode */
+ }
+
+ /* Read the hole file and generate lines for normal holes (oblong
+ * holes will be created later) */
+ int tool_reference = -2;
+
+ for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
+ {
+ HOLE_INFO& hole_descr = m_holeListBuffer[ii];
+
+ if( hole_descr.m_Hole_Shape )
+ continue; // oblong holes will be created later
+
+ if( tool_reference != hole_descr.m_Tool_Reference )
+ {
+ tool_reference = hole_descr.m_Tool_Reference;
+ fprintf( m_file, "T%d\n", tool_reference );
+ }
+
+ x0 = hole_descr.m_Hole_Pos.x - m_offset.x;
+ y0 = hole_descr.m_Hole_Pos.y - m_offset.y;
+
+ if( !m_mirror )
+ y0 *= -1;
+
+ xt = x0 * m_conversionUnits;
+ yt = y0 * m_conversionUnits;
+ WriteCoordinates( line, xt, yt );
+
+ fputs( line, m_file );
+ holes_count++;
+ }
+
+ /* Read the hole file and generate lines for normal holes (oblong holes
+ * will be created later) */
+ tool_reference = -2; // set to a value not used for
+ // m_holeListBuffer[ii].m_Tool_Reference
+ for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
+ {
+ HOLE_INFO& hole_descr = m_holeListBuffer[ii];
+
+ if( hole_descr.m_Hole_Shape == 0 )
+ continue; // wait for oblong holes
+
+ if( tool_reference != hole_descr.m_Tool_Reference )
+ {
+ tool_reference = hole_descr.m_Tool_Reference;
+ fprintf( m_file, "T%d\n", tool_reference );
+ }
+
+ diam = std::min( hole_descr.m_Hole_Size.x, hole_descr.m_Hole_Size.y );
+
+ if( diam == 0 )
+ continue;
+
+ /* Compute the hole coordinates: */
+ xc = x0 = xf = hole_descr.m_Hole_Pos.x - m_offset.x;
+ yc = y0 = yf = hole_descr.m_Hole_Pos.y - m_offset.y;
+
+ /* Compute the start and end coordinates for the shape */
+ if( hole_descr.m_Hole_Size.x < hole_descr.m_Hole_Size.y )
+ {
+ int delta = ( hole_descr.m_Hole_Size.y - hole_descr.m_Hole_Size.x ) / 2;
+ y0 -= delta;
+ yf += delta;
+ }
+ else
+ {
+ int delta = ( hole_descr.m_Hole_Size.x - hole_descr.m_Hole_Size.y ) / 2;
+ x0 -= delta;
+ xf += delta;
+ }
+
+ RotatePoint( &x0, &y0, xc, yc, hole_descr.m_Hole_Orient );
+ RotatePoint( &xf, &yf, xc, yc, hole_descr.m_Hole_Orient );
+
+ if( !m_mirror )
+ {
+ y0 *= -1;
+ yf *= -1;
+ }
+
+ xt = x0 * m_conversionUnits;
+ yt = y0 * m_conversionUnits;
+ WriteCoordinates( line, xt, yt );
+
+ /* remove the '\n' from end of line, because we must add the "G85"
+ * command to the line: */
+ for( int kk = 0; line[kk] != 0; kk++ )
+ {
+ if( line[kk] == '\n' || line[kk] =='\r' )
+ line[kk] = 0;
+ }
+
+ fputs( line, m_file );
+ fputs( "G85", m_file ); // add the "G85" command
+
+ xt = xf * m_conversionUnits;
+ yt = yf * m_conversionUnits;
+ WriteCoordinates( line, xt, yt );
+
+ fputs( line, m_file );
+ fputs( "G05\n", m_file );
+ holes_count++;
+ }
+
+ WriteEXCELLONEndOfFile();
+
+ return holes_count;
+}
+
+
+void EXCELLON_WRITER::SetFormat( bool aMetric,
+ ZEROS_FMT aZerosFmt,
+ int aLeftDigits,
+ int aRightDigits )
+{
+ m_unitsDecimal = aMetric;
+ m_zeroFormat = aZerosFmt;
+
+ /* Set conversion scale depending on drill file units */
+ if( m_unitsDecimal )
+ m_conversionUnits = 1.0 / IU_PER_MM; // EXCELLON units = mm
+ else
+ m_conversionUnits = 0.001 / IU_PER_MILS; // EXCELLON units = INCHES
+
+ // Set the zero counts. if aZerosFmt == DECIMAL_FORMAT, these values
+ // will be set, but not used.
+ if( aLeftDigits <= 0 )
+ aLeftDigits = m_unitsDecimal ? 3 : 2;
+
+ if( aRightDigits <= 0 )
+ aRightDigits = m_unitsDecimal ? 3 : 4;
+
+ m_precision.m_lhs = aLeftDigits;
+ m_precision.m_rhs = aRightDigits;
+}
+
+
+void EXCELLON_WRITER::WriteCoordinates( char* aLine, double aCoordX, double aCoordY )
+{
+ wxString xs, ys;
+ int xpad = m_precision.m_lhs + m_precision.m_rhs;
+ int ypad = xpad;
+
+ switch( m_zeroFormat )
+ {
+ default:
+ case DECIMAL_FORMAT:
+ /* In Excellon files, resolution is 1/1000 mm or 1/10000 inch (0.1 mil)
+ * Although in decimal format, Excellon specifications do not specify
+ * clearly the resolution. However it seems to be 1/1000mm or 0.1 mil
+ * like in non decimal formats, so we trunk coordinates to 3 or 4 digits in mantissa
+ * Decimal format just prohibit useless leading 0:
+ * 0.45 or .45 is right, but 00.54 is incorrect.
+ */
+ if( m_unitsDecimal )
+ {
+ // resolution is 1/1000 mm
+ xs.Printf( wxT( "%.3f" ), aCoordX );
+ ys.Printf( wxT( "%.3f" ), aCoordY );
+ }
+ else
+ {
+ // resolution is 1/10000 inch
+ xs.Printf( wxT( "%.4f" ), aCoordX );
+ ys.Printf( wxT( "%.4f" ), aCoordY );
+ }
+
+ //Remove useless trailing 0
+ while( xs.Last() == '0' )
+ xs.RemoveLast();
+
+ while( ys.Last() == '0' )
+ ys.RemoveLast();
+
+ sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
+ break;
+
+ case SUPPRESS_LEADING:
+ for( int i = 0; i< m_precision.m_rhs; i++ )
+ {
+ aCoordX *= 10; aCoordY *= 10;
+ }
+
+ sprintf( aLine, "X%dY%d\n", KiROUND( aCoordX ), KiROUND( aCoordY ) );
+ break;
+
+ case SUPPRESS_TRAILING:
+ {
+ for( int i = 0; i < m_precision.m_rhs; i++ )
+ {
+ aCoordX *= 10;
+ aCoordY *= 10;
+ }
+
+ if( aCoordX < 0 )
+ xpad++;
+
+ if( aCoordY < 0 )
+ ypad++;
+
+ xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
+ ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
+
+ size_t j = xs.Len() - 1;
+
+ while( xs[j] == '0' && j )
+ xs.Truncate( j-- );
+
+ j = ys.Len() - 1;
+
+ while( ys[j] == '0' && j )
+ ys.Truncate( j-- );
+
+ sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
+ break;
+ }
+
+ case KEEP_ZEROS:
+ for( int i = 0; i< m_precision.m_rhs; i++ )
+ {
+ aCoordX *= 10; aCoordY *= 10;
+ }
+
+ if( aCoordX < 0 )
+ xpad++;
+
+ if( aCoordY < 0 )
+ ypad++;
+
+ xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
+ ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
+ sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
+ break;
+ }
+}
+
+
+void EXCELLON_WRITER::WriteEXCELLONHeader()
+{
+ fputs( "M48\n", m_file ); // The beginning of a header
+
+ if( !m_minimalHeader )
+ {
+ // The next 2 lines in EXCELLON files are comments:
+ wxString msg;
+ msg << wxT("KiCad") << wxT( " " ) << GetBuildVersion();
+
+ fprintf( m_file, ";DRILL file {%s} date %s\n", TO_UTF8( msg ),
+ TO_UTF8( DateAndTime() ) );
+ msg = wxT( ";FORMAT={" );
+
+ // Print precision:
+ if( m_zeroFormat != DECIMAL_FORMAT )
+ msg << m_precision.GetPrecisionString();
+ else
+ msg << wxT( "-:-" ); // in decimal format the precision is irrelevant
+
+ msg << wxT( "/ absolute / " );
+ msg << ( m_unitsDecimal ? wxT( "metric" ) : wxT( "inch" ) );
+
+ /* Adding numbers notation format.
+ * this is same as m_Choice_Zeros_Format strings, but NOT translated
+ * because some EXCELLON parsers do not like non ASCII values
+ * so we use ONLY English (ASCII) strings.
+ * if new options are added in m_Choice_Zeros_Format, they must also
+ * be added here
+ */
+ msg << wxT( " / " );
+
+ const wxString zero_fmt[4] =
+ {
+ wxT( "decimal" ),
+ wxT( "suppress leading zeros" ),
+ wxT( "suppress trailing zeros" ),
+ wxT( "keep zeros" )
+ };
+
+ msg << zero_fmt[m_zeroFormat];
+ msg << wxT( "}\n" );
+ fputs( TO_UTF8( msg ), m_file );
+ fputs( "FMAT,2\n", m_file ); // Use Format 2 commands (version used since 1979)
+ }
+
+ fputs( m_unitsDecimal ? "METRIC" : "INCH", m_file );
+
+ switch( m_zeroFormat )
+ {
+ case SUPPRESS_LEADING:
+ case DECIMAL_FORMAT:
+ fputs( ",TZ\n", m_file );
+ break;
+
+ case SUPPRESS_TRAILING:
+ fputs( ",LZ\n", m_file );
+ break;
+
+ case KEEP_ZEROS:
+ fputs( ",TZ\n", m_file ); // TZ is acceptable when all zeros are kept
+ break;
+ }
+}
+
+
+void EXCELLON_WRITER::WriteEXCELLONEndOfFile()
+{
+ //add if minimal here
+ fputs( "T0\nM30\n", m_file );
+ fclose( m_file );
+}
+
+
+
+/* Helper function for sorting hole list.
+ * Compare function used for sorting holes type type (plated then not plated)
+ * then by increasing diameter value and X value
+ */
+static bool CmpHoleSettings( const HOLE_INFO& a, const HOLE_INFO& b )
+{
+ if( a.m_Hole_NotPlated != b.m_Hole_NotPlated )
+ return b.m_Hole_NotPlated;
+
+ if( a.m_Hole_Diameter != b.m_Hole_Diameter )
+ return a.m_Hole_Diameter < b.m_Hole_Diameter;
+
+ if( a.m_Hole_Pos.x != b.m_Hole_Pos.x )
+ return a.m_Hole_Pos.x < b.m_Hole_Pos.x;
+
+ return a.m_Hole_Pos.y < b.m_Hole_Pos.y;
+}
+
+
+void EXCELLON_WRITER::BuildHolesList( LAYER_PAIR aLayerPair,
+ bool aGenerateNPTH_list )
+{
+ HOLE_INFO new_hole;
+
+ m_holeListBuffer.clear();
+ m_toolListBuffer.clear();
+
+ wxASSERT( aLayerPair.first < aLayerPair.second ); // fix the caller
+
+ // build hole list for vias
+ if( ! aGenerateNPTH_list ) // vias are always plated !
+ {
+ for( VIA* via = GetFirstVia( m_pcb->m_Track ); via; via = GetFirstVia( via->Next() ) )
+ {
+ int hole_sz = via->GetDrillValue();
+
+ if( hole_sz == 0 ) // Should not occur.
+ continue;
+
+ new_hole.m_Tool_Reference = -1; // Flag value for Not initialized
+ new_hole.m_Hole_Orient = 0;
+ new_hole.m_Hole_Diameter = hole_sz;
+ new_hole.m_Hole_NotPlated = false;
+ new_hole.m_Hole_Size.x = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;
+
+ new_hole.m_Hole_Shape = 0; // hole shape: round
+ new_hole.m_Hole_Pos = via->GetStart();
+
+ via->LayerPair( &new_hole.m_Hole_Top_Layer, &new_hole.m_Hole_Bottom_Layer );
+
+ // LayerPair() returns params with m_Hole_Bottom_Layer > m_Hole_Top_Layer
+ // Remember: top layer = 0 and bottom layer = 31 for through hole vias
+ // Any captured via should be from aLayerPair.first to aLayerPair.second exactly.
+ if( new_hole.m_Hole_Top_Layer != aLayerPair.first ||
+ new_hole.m_Hole_Bottom_Layer != aLayerPair.second )
+ continue;
+
+ m_holeListBuffer.push_back( new_hole );
+ }
+ }
+
+ if( aLayerPair == LAYER_PAIR( F_Cu, B_Cu ) )
+ {
+ // add holes for thru hole pads
+ for( MODULE* module = m_pcb->m_Modules; module; module = module->Next() )
+ {
+ for( D_PAD* pad = module->Pads(); pad; pad = pad->Next() )
+ {
+ if( !m_merge_PTH_NPTH )
+ {
+ if( !aGenerateNPTH_list && pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED )
+ continue;
+
+ if( aGenerateNPTH_list && pad->GetAttribute() != PAD_ATTRIB_HOLE_NOT_PLATED )
+ continue;
+ }
+
+ if( pad->GetDrillSize().x == 0 )
+ continue;
+
+ new_hole.m_Hole_NotPlated = (pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED);
+ new_hole.m_Tool_Reference = -1; // Flag is: Not initialized
+ new_hole.m_Hole_Orient = pad->GetOrientation();
+ new_hole.m_Hole_Shape = 0; // hole shape: round
+ new_hole.m_Hole_Diameter = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
+ new_hole.m_Hole_Size.x = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;
+
+ if( pad->GetDrillShape() != PAD_DRILL_SHAPE_CIRCLE )
+ new_hole.m_Hole_Shape = 1; // oval flag set
+
+ new_hole.m_Hole_Size = pad->GetDrillSize();
+ new_hole.m_Hole_Pos = pad->GetPosition(); // hole position
+ new_hole.m_Hole_Bottom_Layer = B_Cu;
+ new_hole.m_Hole_Top_Layer = F_Cu; // pad holes are through holes
+ m_holeListBuffer.push_back( new_hole );
+ }
+ }
+ }
+
+ // Sort holes per increasing diameter value
+ sort( m_holeListBuffer.begin(), m_holeListBuffer.end(), CmpHoleSettings );
+
+ // build the tool list
+ int last_hole = -1; // Set to not initialized (this is a value not used
+ // for m_holeListBuffer[ii].m_Hole_Diameter)
+ bool last_notplated_opt = false;
+
+ DRILL_TOOL new_tool( 0, false );
+ unsigned jj;
+
+ for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
+ {
+ if( m_holeListBuffer[ii].m_Hole_Diameter != last_hole ||
+ m_holeListBuffer[ii].m_Hole_NotPlated != last_notplated_opt )
+ {
+ new_tool.m_Diameter = m_holeListBuffer[ii].m_Hole_Diameter;
+ new_tool.m_Hole_NotPlated = m_holeListBuffer[ii].m_Hole_NotPlated;
+ m_toolListBuffer.push_back( new_tool );
+ last_hole = new_tool.m_Diameter;
+ last_notplated_opt = new_tool.m_Hole_NotPlated;
+ }
+
+ jj = m_toolListBuffer.size();
+
+ if( jj == 0 )
+ continue; // Should not occurs
+
+ m_holeListBuffer[ii].m_Tool_Reference = jj; // Tool value Initialized (value >= 1)
+
+ m_toolListBuffer.back().m_TotalCount++;
+
+ if( m_holeListBuffer[ii].m_Hole_Shape )
+ m_toolListBuffer.back().m_OvalCount++;
+ }
+}
+
+
+std::vector<LAYER_PAIR> EXCELLON_WRITER::getUniqueLayerPairs() const
+{
+ wxASSERT( m_pcb );
+
+ static const KICAD_T interesting_stuff_to_collect[] = {
+ PCB_VIA_T,
+ EOT
+ };
+
+ PCB_TYPE_COLLECTOR vias;
+
+ vias.Collect( m_pcb, interesting_stuff_to_collect );
+
+ std::set< LAYER_PAIR > unique;
+
+ LAYER_PAIR layer_pair;
+
+ for( int i = 0; i < vias.GetCount(); ++i )
+ {
+ VIA* v = (VIA*) vias[i];
+
+ v->LayerPair( &layer_pair.first, &layer_pair.second );
+
+ // only make note of blind buried.
+ // thru hole is placed unconditionally as first in fetched list.
+ if( layer_pair != LAYER_PAIR( F_Cu, B_Cu ) )
+ {
+ unique.insert( layer_pair );
+ }
+ }
+
+ std::vector<LAYER_PAIR> ret;
+
+ ret.push_back( LAYER_PAIR( F_Cu, B_Cu ) ); // always first in returned list
+
+ for( std::set< LAYER_PAIR >::const_iterator it = unique.begin(); it != unique.end(); ++it )
+ ret.push_back( *it );
+
+ return ret;
+}
+
+
+const std::string EXCELLON_WRITER::layerName( LAYER_ID aLayer ) const
+{
+ // Generic names here.
+ switch( aLayer )
+ {
+ case F_Cu:
+ return "front";
+ case B_Cu:
+ return "back";
+ default:
+ return StrPrintf( "inner%d", aLayer );
+ }
+}
+
+
+const std::string EXCELLON_WRITER::layerPairName( LAYER_PAIR aPair ) const
+{
+ std::string ret = layerName( aPair.first );
+ ret += '-';
+ ret += layerName( aPair.second );
+
+ return ret;
+}
+
+
+const wxString EXCELLON_WRITER::drillFileName( LAYER_PAIR aPair, bool aNPTH ) const
+{
+ wxASSERT( m_pcb );
+
+ wxString extend;
+
+ if( aNPTH )
+ extend = "-NPTH";
+ else if( aPair == LAYER_PAIR( F_Cu, B_Cu ) )
+ {
+ // extend with nothing
+ }
+ else
+ {
+ extend += '-';
+ extend += layerPairName( aPair );
+ }
+
+ wxFileName fn = m_pcb->GetFileName();
+
+ fn.SetName( fn.GetName() + extend );
+ fn.SetExt( DrillFileExtension );
+
+ wxString ret = fn.GetFullName(); // show me in debugger
+
+ return ret;
+}
diff --git a/pcbnew/exporters/gendrill_Excellon_writer.h b/pcbnew/exporters/gendrill_Excellon_writer.h
new file mode 100644
index 0000000..9a4d9c1
--- /dev/null
+++ b/pcbnew/exporters/gendrill_Excellon_writer.h
@@ -0,0 +1,368 @@
+/**
+ * @file gendrill_Excellon_writer.h
+ * @brief Classes used in drill files, map files and report files generation.
+ */
+
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 1992-2015 Jean_Pierre Charras <jp.charras at wanadoo.fr>
+ * Copyright (C) 1992-2015 KiCad Developers, see change_log.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
+ */
+
+#ifndef _GENDRILL_EXCELLON_WRITER_
+#define _GENDRILL_EXCELLON_WRITER_
+
+#include <vector>
+
+
+class BOARD;
+class PLOTTER;
+
+
+// the DRILL_TOOL class handles tools used in the excellon drill file:
+class DRILL_TOOL
+{
+public:
+ int m_Diameter; // the diameter of the used tool (for oblong, the smaller size)
+ int m_TotalCount; // how many times it is used (round and oblong)
+ int m_OvalCount; // oblong count
+ bool m_Hole_NotPlated; // Is the hole plated or not plated
+
+public:
+ DRILL_TOOL( int aDiameter, bool a_NotPlated )
+ {
+ m_TotalCount = 0;
+ m_OvalCount = 0;
+ m_Diameter = aDiameter;
+ m_Hole_NotPlated = a_NotPlated;
+ }
+};
+
+
+/* the HOLE_INFO class handle hole which must be drilled (diameter, position and layers)
+ * For buried or micro vias, the hole is not on all layers.
+ * So we must generate a drill file for each layer pair (adjacent layers)
+ * Not plated holes are always through holes, and must be output on a specific drill file
+ * because they are drilled after the Pcb process is finished.
+ */
+class HOLE_INFO
+{
+public:
+ int m_Hole_Diameter; // hole value, and for oblong: min(hole size x, hole size y)
+ int m_Tool_Reference; // Tool reference for this hole = 1 ... n (values <=0 must not be used)
+ wxSize m_Hole_Size; // hole size for oblong holes
+ double m_Hole_Orient; // Hole rotation (= pad rotation) for oblong holes
+ int m_Hole_Shape; // hole shape: round (0) or oval (1)
+ wxPoint m_Hole_Pos; // hole position
+ LAYER_ID m_Hole_Bottom_Layer; // hole ending layer (usually back layer)
+ LAYER_ID m_Hole_Top_Layer; // hole starting layer (usually front layer):
+ // m_Hole_Top_Layer < m_Hole_Bottom_Layer
+ bool m_Hole_NotPlated; // hole not plated. Must be in a specific drill file or section
+
+public:
+ HOLE_INFO()
+ {
+ m_Hole_NotPlated = false;
+ m_Hole_Diameter = 0;
+ m_Tool_Reference = 0;
+ m_Hole_Orient = 0.0;
+ m_Hole_Shape = 0;
+ m_Hole_Bottom_Layer = B_Cu;
+ m_Hole_Top_Layer = F_Cu;
+ }
+};
+
+
+/* the DRILL_PRECISION helper class to handle drill precision format in excellon files
+ */
+class DRILL_PRECISION
+{
+public:
+ int m_lhs; // Left digit number (integer value of coordinates)
+ int m_rhs; // Right digit number (decimal value of coordinates)
+
+public: DRILL_PRECISION( int l = 2, int r = 4 )
+ {
+ m_lhs = l; m_rhs = r;
+ }
+
+
+ wxString GetPrecisionString()
+ {
+ wxString text;
+
+ text << m_lhs << wxT( ":" ) << m_rhs;
+ return text;
+ }
+};
+
+
+typedef std::pair<LAYER_ID, LAYER_ID> LAYER_PAIR;
+class OUTPUTFORMATTER;
+
+/**
+ * EXCELLON_WRITER is a class mainly used to create Excellon drill files
+ * However, this class is also used to create drill maps and drill report
+ */
+class EXCELLON_WRITER
+{
+public:
+ enum ZEROS_FMT { // Zero format in coordinates
+ DECIMAL_FORMAT, // Floating point coordinates
+ SUPPRESS_LEADING, // Suppress leading zeros
+ SUPPRESS_TRAILING, // Suppress trainling zeros
+ KEEP_ZEROS // keep zeros
+ };
+
+ wxPoint m_Offset; // offset coordinates
+ bool m_ShortHeader; // true to generate the smallest header (strip comments)
+
+private:
+ FILE* m_file; // The output file
+ BOARD* m_pcb;
+ bool m_minimalHeader; // True to use minimal header
+ // in excellon file (strip comments)
+ bool m_unitsDecimal; // true = decimal, false = inches
+ ZEROS_FMT m_zeroFormat; // the zero format option for output file
+ DRILL_PRECISION m_precision; // The current coordinate precision (not used in decimal format)
+ double m_conversionUnits; // scaling factor to convert the board unites to Excellon units
+ // (i.e inches or mm)
+ bool m_mirror;
+ wxPoint m_offset; // Drill offset coordinates
+ bool m_merge_PTH_NPTH; // True to generate only one drill file
+ std::vector<HOLE_INFO> m_holeListBuffer; // Buffer containing holes
+ std::vector<DRILL_TOOL> m_toolListBuffer; // Buffer containing tools
+
+ PlotFormat m_mapFileFmt; // the format of the map drill file,
+ // if this map is needed
+ const PAGE_INFO* m_pageInfo; // the page info used to plot drill maps
+ // If NULL, use a A4 page format
+
+public:
+ EXCELLON_WRITER( BOARD* aPcb );
+
+ ~EXCELLON_WRITER()
+ {
+ }
+
+ /**
+ * Return the plot offset (usually the position
+ * of the auxiliary axis
+ */
+ const wxPoint GetOffset() { return m_offset; }
+
+ /**
+ * Function SetFormat
+ * Initialize internal parameters to match the given format
+ * @param aMetric = true for metric coordinates, false for imperial units
+ * @param aZerosFmt = DECIMAL_FORMAT, SUPPRESS_LEADING, SUPPRESS_TRAILING, KEEP_ZEROS
+ * @param aLeftDigits = number of digits for integer part of coordinates
+ * if <= 0 (default), a suitable value will be used, depending on units
+ * @param aRightDigits = number of digits for mantissa part of coordinates
+ * if <= 0 (default), a suitable value will be used, depending on units
+ */
+ void SetFormat( bool aMetric, ZEROS_FMT aZerosFmt = DECIMAL_FORMAT,
+ int aLeftDigits = 0, int aRightDigits = 0 );
+
+ /**
+ * Sets the page info used to plot drill maps
+ * If NULL, a A4 page format will be used
+ * @param aPageInfo = a reference to the page info, usually used to plot/display the board
+ */
+ void SetPageInfo( const PAGE_INFO* aPageInfo ) { m_pageInfo = aPageInfo; }
+
+ /**
+ * Function SetMapFileFormat
+ * Initialize the format for the drill map file
+ * @param SetMapFileFormat = a PlotFormat value (one of
+ * PLOT_FORMAT_HPGL, PLOT_FORMAT_POST, PLOT_FORMAT_GERBER,
+ * PLOT_FORMAT_DXF, PLOT_FORMAT_SVG, PLOT_FORMAT_PDF
+ * the most useful are PLOT_FORMAT_PDF and PLOT_FORMAT_POST
+ */
+ void SetMapFileFormat( PlotFormat aMapFmt ) { m_mapFileFmt = aMapFmt; }
+
+
+ /**
+ * Function SetOptions
+ * Initialize internal parameters to match drill options
+ * @param aMirror = true to create mirrored coordinates (Y coordinates negated)
+ * @param aMinimalHeader = true to use a minimal header (no comments, no info)
+ * @param aOffset = drill coordinates offset
+ */
+ void SetOptions( bool aMirror, bool aMinimalHeader, wxPoint aOffset, bool aMerge_PTH_NPTH )
+ {
+ m_mirror = aMirror;
+ m_offset = aOffset;
+ m_minimalHeader = aMinimalHeader;
+ m_merge_PTH_NPTH = aMerge_PTH_NPTH;
+ }
+
+ /**
+ * Function BuildHolesList
+ * Create the list of holes and tools for a given board
+ * The list is sorted by increasing drill size.
+ * Only holes included within aLayerPair are listed.
+ * If aLayerPair identifies with [F_Cu, B_Cu], then
+ * pad holes are always included also.
+ *
+ * @param aLayerPair is an inclusive range of layers.
+ * @param aGenerateNPTH_list :
+ * true to create NPTH only list (with no plated holes)
+ * false to created plated holes list (with no NPTH )
+ */
+ void BuildHolesList( LAYER_PAIR aLayerPair,
+ bool aGenerateNPTH_list );
+
+ int GetHolesCount() const { return m_holeListBuffer.size(); }
+
+ /**
+ * Function CreateDrillandMapFilesSet
+ * Creates the full set of Excellon drill file for the board
+ * filenames are computed from the board name, and layers id
+ * @param aPlotDirectory = the output folder
+ * @param aGenDrill = true to generate the EXCELLON drill file
+ * @param aGenMap = true to generate a drill map file
+ * @param aReporter = a REPORTER to return activity or any message (can be NULL)
+ */
+ void CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
+ bool aGenDrill, bool aGenMap,
+ REPORTER * aReporter = NULL );
+
+ /**
+ * Function CreateDrillFile
+ * Creates an Excellon drill file
+ * @param aFile = an opened file to write to will be closed by CreateDrillFile
+ * @return hole count
+ */
+ int CreateDrillFile( FILE * aFile );
+
+ /**
+ * Function GenDrillReportFile
+ * Create a plain text report file giving a list of drill values and drill count
+ * for through holes, oblong holes, and for buried vias,
+ * drill values and drill count per layer pair
+ * there is only one report for all drill files even when buried or blinds vias exist
+ *
+ * Here is a sample created by this function:
+ * Drill report for F:/tmp/interf_u/interf_u.brd
+ * Created on 04/10/2012 20:48:38
+ * Selected Drill Unit: Imperial (inches)
+ *
+ * Drill report for plated through holes :
+ * T1 0,025" 0,64mm (88 holes)
+ * T2 0,031" 0,79mm (120 holes)
+ * T3 0,032" 0,81mm (151 holes) (with 1 slot)
+ * T4 0,040" 1,02mm (43 holes)
+ * T5 0,079" 2,00mm (1 hole) (with 1 slot)
+ * T6 0,120" 3,05mm (1 hole) (with 1 slot)
+ *
+ * Total plated holes count 404
+ *
+ *
+ * Drill report for buried and blind vias :
+ *
+ * Drill report for holes from layer Soudure to layer Interne1 :
+ *
+ * Total plated holes count 0
+ *
+ *
+ * Drill report for holes from layer Interne1 to layer Interne2 :
+ * T1 0,025" 0,64mm (3 holes)
+ *
+ * Total plated holes count 3
+ *
+ *
+ * Drill report for holes from layer Interne2 to layer Composant :
+ * T1 0,025" 0,64mm (1 hole)
+ *
+ * Total plated holes count 1
+ *
+ *
+ * Drill report for unplated through holes :
+ * T1 0,120" 3,05mm (1 hole) (with 1 slot)
+ *
+ * Total unplated holes count 1
+ *
+ * @param aFullFileName : the name of the file to create
+ * m_unitsDecimal = false to use inches, true to use mm in report file
+ *
+ * @return success if the file is created
+ */
+ bool GenDrillReportFile( const wxString& aFullFileName );
+
+ /**
+ * Function GenDrillMapFile
+ * Plot a map of drill marks for holes.
+ * the paper sheet to use to plot the map is set in m_pageInfo
+ * ( calls SetPageInfo() to set it )
+ * if NULL, A4 format will be used
+ * @param aFullFileName : the full filename of the map file to create,
+ * @param aFormat : one of the supported plot formats (see enum PlotFormat )
+ */
+ bool GenDrillMapFile( const wxString& aFullFileName, PlotFormat aFormat );
+
+private:
+ /* Print the DRILL file header. The full header is:
+ * M48
+ * ;DRILL file {PCBNEW (2007-11-29-b)} date 17/1/2008-21:02:35
+ * ;FORMAT={ <precision> / absolute / <units> / <numbers format>}
+ * FMAT,2
+ * INCH,TZ
+ */
+ void WriteEXCELLONHeader();
+
+ void WriteEXCELLONEndOfFile();
+
+ /* Created a line like:
+ * X48000Y19500
+ * According to the selected format
+ */
+ void WriteCoordinates( char* aLine, double aCoordX, double aCoordY );
+
+ /** Helper function.
+ * Writes the drill marks in HPGL, POSTSCRIPT or other supported formats
+ * Each hole size has a symbol (circle, cross X, cross + ...) up to
+ * PLOTTER::MARKER_COUNT different values.
+ * If more than PLOTTER::MARKER_COUNT different values,
+ * these other values share the same mark shape
+ * @param aPlotter = a PLOTTER instance (HPGL, POSTSCRIPT ... plotter).
+ */
+ bool PlotDrillMarks( PLOTTER* aPlotter );
+
+ /// Get unique layer pairs by examining the micro and blind_buried vias.
+ std::vector<LAYER_PAIR> getUniqueLayerPairs() const;
+
+ /**
+ * Function printToolSummary
+ * prints m_toolListBuffer[] tools to aOut and returns total hole count.
+ * @param aOut = the current OUTPUTFORMATTER to print summary
+ * @param aSummaryNPTH = true to print summary for NPTH, false for PTH
+ */
+ unsigned printToolSummary( OUTPUTFORMATTER& aOut, bool aSummaryNPTH ) const;
+
+ const std::string layerPairName( LAYER_PAIR aPair ) const;
+
+ const std::string layerName( LAYER_ID aLayer ) const;
+
+ const wxString drillFileName( LAYER_PAIR aPair, bool aNPTH ) const;
+};
+
+#endif // #ifndef _GENDRILL_EXCELLON_WRITER_