diff options
author | saurabhb17 | 2020-02-26 16:00:53 +0530 |
---|---|---|
committer | GitHub | 2020-02-26 16:00:53 +0530 |
commit | 886d9cb772e81d2e5262284bc3082664f084337f (patch) | |
tree | 6acee185a4dc19113fcbf0f9a3d6941085dedaf7 /pcbnew/exporters | |
parent | 0db48f6533517ecebfd9f0693f89deca28408b76 (diff) | |
parent | aa35045840b78d3f48212db45da59a2e5c69b223 (diff) | |
download | KiCad-eSim-886d9cb772e81d2e5262284bc3082664f084337f.tar.gz KiCad-eSim-886d9cb772e81d2e5262284bc3082664f084337f.tar.bz2 KiCad-eSim-886d9cb772e81d2e5262284bc3082664f084337f.zip |
Merge pull request #1 from saurabhb17/develop
Added main functions
Diffstat (limited to 'pcbnew/exporters')
-rw-r--r-- | pcbnew/exporters/export_d356.cpp | 397 | ||||
-rw-r--r-- | pcbnew/exporters/export_gencad.cpp | 1257 | ||||
-rw-r--r-- | pcbnew/exporters/export_idf.cpp | 609 | ||||
-rw-r--r-- | pcbnew/exporters/export_vrml.cpp | 1408 | ||||
-rw-r--r-- | pcbnew/exporters/gen_drill_report_files.cpp | 455 | ||||
-rw-r--r-- | pcbnew/exporters/gen_modules_placefile.cpp | 690 | ||||
-rw-r--r-- | pcbnew/exporters/gendrill_Excellon_writer.cpp | 780 | ||||
-rw-r--r-- | pcbnew/exporters/gendrill_Excellon_writer.h | 368 |
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_ |