1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2011-2013 Lorenzo Marcantonio <l.marcantonio@logossrl.com>
5  * Copyright (C) 2004-2017 KiCad Developers, see change_log.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
23  */
24 
25 /**
26  * @file export_d356.cpp
27  * @brief Export IPC-D-356 test format
28  */
29 
30 #include <confirm.h>
31 #include <gestfich.h>
32 #include <kiface_base.h>
33 #include <pcb_edit_frame.h>
34 #include <trigo.h>
35 #include <build_version.h>
36 #include <macros.h>
37 #include <wildcards_and_files_ext.h>
38 #include <locale_io.h>
39 #include <pcbnew.h>
40 #include <board.h>
41 #include <board_design_settings.h>
42 #include <footprint.h>
43 #include <pad.h>
44 #include <pcb_track.h>
45 #include <vector>
46 #include <cctype>
47 #include <math/util.h>      // for KiROUND
48 #include <export_d356.h>
49 #include <wx/filedlg.h>
50 
51 
52 
53 // Compute the access code for a pad. Returns -1 if there is no copper
compute_pad_access_code(BOARD * aPcb,LSET aLayerMask)54 static int compute_pad_access_code( BOARD *aPcb, LSET aLayerMask )
55 {
56     // Non-copper is not interesting here
57     aLayerMask &= LSET::AllCuMask();
58     if( !aLayerMask.any() )
59         return -1;
60 
61     // Traditional TH pad
62     if( aLayerMask[F_Cu] && aLayerMask[B_Cu] )
63         return 0;
64 
65     // Front SMD pad
66     if( aLayerMask[F_Cu] )
67         return 1;
68 
69     // Back SMD pad
70     if( aLayerMask[B_Cu] )
71         return aPcb->GetCopperLayerCount();
72 
73     // OK, we have an inner-layer only pad (and I have no idea about
74     // what could be used for); anyway, find the first copper layer
75     // it's on
76     for( LAYER_NUM layer = In1_Cu; layer < B_Cu; ++layer )
77     {
78         if( aLayerMask[layer] )
79             return layer + 1;
80     }
81 
82     // This shouldn't happen
83     return -1;
84 }
85 
86 /* Convert and clamp a size from IU to decimils */
iu_to_d356(int iu,int clamp)87 static int iu_to_d356(int iu, int clamp)
88 {
89     int val = KiROUND( iu / ( IU_PER_MILS / 10 ) );
90     if( val > clamp ) return clamp;
91     if( val < -clamp ) return -clamp;
92     return val;
93 }
94 
95 /* Extract the D356 record from the footprints (pads) */
build_pad_testpoints(BOARD * aPcb,std::vector<D356_RECORD> & aRecords)96 static void build_pad_testpoints( BOARD *aPcb, std::vector <D356_RECORD>& aRecords )
97 {
98     wxPoint origin = aPcb->GetDesignSettings().GetAuxOrigin();
99 
100     for( FOOTPRINT* footprint : aPcb->Footprints() )
101     {
102         for( PAD* pad : footprint->Pads() )
103         {
104             D356_RECORD rk;
105             rk.access = compute_pad_access_code( aPcb, pad->GetLayerSet() );
106 
107             // It could be a mask only pad, we only handle pads with copper here
108             if( rk.access != -1 )
109             {
110                 rk.netname = pad->GetNetname();
111                 rk.pin = pad->GetNumber();
112                 rk.refdes = footprint->GetReference();
113                 rk.midpoint = false; // XXX MAYBE need to be computed (how?)
114                 const wxSize& drill = pad->GetDrillSize();
115                 rk.drill = std::min( drill.x, drill.y );
116                 rk.hole = (rk.drill != 0);
117                 rk.smd = pad->GetAttribute() == PAD_ATTRIB::SMD;
118                 rk.mechanical = ( pad->GetAttribute() == PAD_ATTRIB::NPTH );
119                 rk.x_location = pad->GetPosition().x - origin.x;
120                 rk.y_location = origin.y - pad->GetPosition().y;
121                 rk.x_size = pad->GetSize().x;
122 
123                 // Rule: round pads have y = 0
124                 if( pad->GetShape() == PAD_SHAPE::CIRCLE )
125                     rk.y_size = 0;
126                 else
127                     rk.y_size = pad->GetSize().y;
128 
129                 rk.rotation = -KiROUND( pad->GetOrientation() ) / 10;
130                 if( rk.rotation < 0 ) rk.rotation += 360;
131 
132                 // the value indicates which sides are *not* accessible
133                 rk.soldermask = 3;
134                 if( pad->GetLayerSet()[F_Mask] )
135                     rk.soldermask &= ~1;
136                 if( pad->GetLayerSet()[B_Mask] )
137                     rk.soldermask &= ~2;
138 
139                 aRecords.push_back( rk );
140             }
141         }
142     }
143 }
144 
145 /* Compute the access code for a via. In D-356 layers are numbered from 1 up,
146    where '1' is the 'primary side' (usually the component side);
147    '0' means 'both sides', and other layers follows in an unspecified order */
via_access_code(BOARD * aPcb,int top_layer,int bottom_layer)148 static int via_access_code( BOARD *aPcb, int top_layer, int bottom_layer )
149 {
150     // Easy case for through vias: top_layer is component, bottom_layer is
151     // solder, access code is 0
152     if( (top_layer == F_Cu) && (bottom_layer == B_Cu) )
153         return 0;
154 
155     // Blind via, reachable from front
156     if( top_layer == F_Cu )
157         return 1;
158 
159     // Blind via, reachable from bottom
160     if( bottom_layer == B_Cu )
161         return aPcb->GetCopperLayerCount();
162 
163     // It's a buried via, accessible from some inner layer
164     // (maybe could be used for testing before laminating? no idea)
165     return bottom_layer + 1; // XXX is this correct?
166 }
167 
168 /* Extract the D356 record from the vias */
build_via_testpoints(BOARD * aPcb,std::vector<D356_RECORD> & aRecords)169 static void build_via_testpoints( BOARD *aPcb, std::vector <D356_RECORD>& aRecords )
170 {
171     wxPoint origin = aPcb->GetDesignSettings().GetAuxOrigin();
172 
173     // Enumerate all the track segments and keep the vias
174     for( auto track : aPcb->Tracks() )
175     {
176         if( track->Type() == PCB_VIA_T )
177         {
178             PCB_VIA *via = static_cast<PCB_VIA*>( track );
179             NETINFO_ITEM *net = track->GetNet();
180 
181             D356_RECORD rk;
182             rk.smd = false;
183             rk.hole = true;
184             if( net )
185                 rk.netname = net->GetNetname();
186             else
187                 rk.netname = wxEmptyString;
188             rk.refdes = wxT("VIA");
189             rk.pin = wxT("");
190             rk.midpoint = true; // Vias are always midpoints
191             rk.drill = via->GetDrillValue();
192             rk.mechanical = false;
193 
194             PCB_LAYER_ID top_layer, bottom_layer;
195 
196             via->LayerPair( &top_layer, &bottom_layer );
197 
198             rk.access = via_access_code( aPcb, top_layer, bottom_layer );
199             rk.x_location = via->GetPosition().x - origin.x;
200             rk.y_location = origin.y - via->GetPosition().y;
201             rk.x_size = via->GetWidth();
202             rk.y_size = 0; // Round so height = 0
203             rk.rotation = 0;
204             rk.soldermask = 3; // XXX always tented?
205 
206             aRecords.push_back( rk );
207         }
208     }
209 }
210 
211 /* Add a new netname to the d356 canonicalized list */
intern_new_d356_netname(const wxString & aNetname,std::map<wxString,wxString> & aMap,std::set<wxString> & aSet)212 static const wxString intern_new_d356_netname( const wxString &aNetname,
213         std::map<wxString, wxString> &aMap, std::set<wxString> &aSet )
214 {
215     wxString canon;
216 
217     for( size_t ii = 0; ii < aNetname.Len(); ++ii )
218     {
219         // Rule: we can only use the standard ASCII, control excluded
220         wxUniChar ch = aNetname[ii];
221 
222         if( ch > 126 || !std::isgraph( static_cast<unsigned char>( ch ) ) )
223             ch = '?';
224 
225         canon += ch;
226     }
227 
228     // Rule: only uppercase (unofficial, but known to give problems
229     // otherwise)
230     canon.MakeUpper();
231 
232     // Rule: maximum length is 14 characters, otherwise we keep the tail
233     if( canon.size() > 14 )
234     {
235         canon = canon.Right( 14 );
236     }
237 
238     // Check if it's still unique
239     if( aSet.count( canon ) )
240     {
241         // Nope, need to uniquify it, trim it more and add a number
242         wxString base( canon );
243         if( base.size() > 10 )
244         {
245             base = base.Right( 10 );
246         }
247 
248         int ctr = 0;
249         do
250         {
251             ++ctr;
252             canon = base;
253             canon << '#' << ctr;
254         } while ( aSet.count( canon ) );
255     }
256 
257     // Register it
258     aMap[aNetname] = canon;
259     aSet.insert( canon );
260     return canon;
261 }
262 
263 /* Write all the accumuled data to the file in D356 format */
write_D356_records(std::vector<D356_RECORD> & aRecords,FILE * aFile)264 void IPC356D_WRITER::write_D356_records( std::vector <D356_RECORD> &aRecords, FILE* aFile )
265 {
266     // Sanified and shorted network names and set of short names
267     std::map<wxString, wxString> d356_net_map;
268     std::set<wxString> d356_net_set;
269 
270     for( unsigned i = 0; i < aRecords.size(); i++ )
271     {
272         D356_RECORD &rk = aRecords[i];
273 
274         // Try to sanify the network name (there are limits on this), if
275         // not already done. Also 'empty' net are marked as N/C, as
276         // specified.
277         wxString d356_net( wxT( "N/C" ) );
278 
279         if( !rk.netname.empty() )
280         {
281             d356_net = d356_net_map[rk.netname];
282 
283             if( d356_net.empty() )
284                 d356_net = intern_new_d356_netname( rk.netname, d356_net_map, d356_net_set );
285         }
286 
287         // Choose the best record type
288         int rktype;
289 
290         if( rk.smd )
291             rktype = 327;
292         else
293         {
294             if( rk.mechanical )
295                 rktype = 367;
296             else
297                 rktype = 317;
298         }
299 
300         // Operation code, signal and component
301         fprintf( aFile, "%03d%-14.14s   %-6.6s%c%-4.4s%c",
302                  rktype, TO_UTF8(d356_net),
303                  TO_UTF8(rk.refdes),
304                  rk.pin.empty()?' ':'-',
305                  TO_UTF8(rk.pin),
306                  rk.midpoint?'M':' ' );
307 
308         // Hole definition
309         if( rk.hole )
310         {
311             fprintf( aFile, "D%04d%c",
312                      iu_to_d356( rk.drill, 9999 ),
313                      rk.mechanical ? 'U':'P' );
314         }
315         else
316             fprintf( aFile, "      " );
317 
318         // Test point access
319         fprintf( aFile, "A%02dX%+07dY%+07dX%04dY%04dR%03d",
320                 rk.access,
321                 iu_to_d356( rk.x_location, 999999 ),
322                 iu_to_d356( rk.y_location, 999999 ),
323                 iu_to_d356( rk.x_size, 9999 ),
324                 iu_to_d356( rk.y_size, 9999 ),
325                 rk.rotation );
326 
327         // Soldermask
328         fprintf( aFile, "S%d\n", rk.soldermask );
329     }
330 }
331 
332 
Write(const wxString & aFilename)333 void IPC356D_WRITER::Write( const wxString& aFilename )
334 {
335     FILE*     file = nullptr;
336     LOCALE_IO toggle; // Switch the locale to standard C
337 
338     if( ( file = wxFopen( aFilename, wxT( "wt" ) ) ) == nullptr )
339     {
340         wxString details;
341         details.Printf( "The file %s could not be opened for writing.", aFilename );
342         DisplayErrorMessage( m_parent, "Could not write IPC-356D file!", details );
343         return;
344     }
345 
346     // This will contain everything needed for the 356 file
347     std::vector<D356_RECORD> d356_records;
348 
349     build_via_testpoints( m_pcb, d356_records );
350 
351     build_pad_testpoints( m_pcb, d356_records );
352 
353     // Code 00 AFAIK is ASCII, CUST 0 is decimils/degrees
354     // CUST 1 would be metric but gerbtool simply ignores it!
355     fprintf( file, "P  CODE 00\n" );
356     fprintf( file, "P  UNITS CUST 0\n" );
357     fprintf( file, "P  arrayDim   N\n" );
358     write_D356_records( d356_records, file );
359     fprintf( file, "999\n" );
360 
361     fclose( file );
362 }
363 
364 
GenD356File(wxCommandEvent & aEvent)365 void PCB_EDIT_FRAME::GenD356File( wxCommandEvent& aEvent )
366 {
367     wxFileName  fn = GetBoard()->GetFileName();
368     wxString    msg, ext, wildcard;
369 
370     ext = IpcD356FileExtension;
371     wildcard = IpcD356FileWildcard();
372     fn.SetExt( ext );
373 
374     wxString pro_dir = wxPathOnly( Prj().GetProjectFullName() );
375 
376     wxFileDialog dlg( this, _( "Export D-356 Test File" ), pro_dir,
377                       fn.GetFullName(), wildcard,
378                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
379 
380     if( dlg.ShowModal() == wxID_CANCEL )
381         return;
382 
383     IPC356D_WRITER writer( GetBoard(), this );
384 
385     writer.Write( dlg.GetPath() );
386 }
387