summaryrefslogtreecommitdiff
path: root/pcbnew/exporters/export_d356.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'pcbnew/exporters/export_d356.cpp')
-rw-r--r--pcbnew/exporters/export_d356.cpp397
1 files changed, 397 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 );
+}
+