KiCad PCB EDA Suite
export_d356.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2011-2013 Lorenzo Marcantonio <[email protected]>
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 
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>
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
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 */
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) */
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  || pad->GetAttribute() == PAD_ATTRIB::CONN;
119  rk.mechanical = ( pad->GetAttribute() == PAD_ATTRIB::NPTH );
120  rk.x_location = pad->GetPosition().x - origin.x;
121  rk.y_location = origin.y - pad->GetPosition().y;
122  rk.x_size = pad->GetSize().x;
123 
124  // Rule: round pads have y = 0
125  if( pad->GetShape() == PAD_SHAPE::CIRCLE )
126  rk.y_size = 0;
127  else
128  rk.y_size = pad->GetSize().y;
129 
130  rk.rotation = -KiROUND( pad->GetOrientation() ) / 10;
131  if( rk.rotation < 0 ) rk.rotation += 360;
132 
133  // the value indicates which sides are *not* accessible
134  rk.soldermask = 3;
135  if( pad->GetLayerSet()[F_Mask] )
136  rk.soldermask &= ~1;
137  if( pad->GetLayerSet()[B_Mask] )
138  rk.soldermask &= ~2;
139 
140  aRecords.push_back( rk );
141  }
142  }
143  }
144 }
145 
146 /* Compute the access code for a via. In D-356 layers are numbered from 1 up,
147  where '1' is the 'primary side' (usually the component side);
148  '0' means 'both sides', and other layers follows in an unspecified order */
149 static int via_access_code( BOARD *aPcb, int top_layer, int bottom_layer )
150 {
151  // Easy case for through vias: top_layer is component, bottom_layer is
152  // solder, access code is 0
153  if( (top_layer == F_Cu) && (bottom_layer == B_Cu) )
154  return 0;
155 
156  // Blind via, reachable from front
157  if( top_layer == F_Cu )
158  return 1;
159 
160  // Blind via, reachable from bottom
161  if( bottom_layer == B_Cu )
162  return aPcb->GetCopperLayerCount();
163 
164  // It's a buried via, accessible from some inner layer
165  // (maybe could be used for testing before laminating? no idea)
166  return bottom_layer + 1; // XXX is this correct?
167 }
168 
169 /* Extract the D356 record from the vias */
170 static void build_via_testpoints( BOARD *aPcb, std::vector <D356_RECORD>& aRecords )
171 {
172  wxPoint origin = aPcb->GetDesignSettings().GetAuxOrigin();
173 
174  // Enumerate all the track segments and keep the vias
175  for( auto track : aPcb->Tracks() )
176  {
177  if( track->Type() == PCB_VIA_T )
178  {
179  PCB_VIA *via = static_cast<PCB_VIA*>( track );
180  NETINFO_ITEM *net = track->GetNet();
181 
182  D356_RECORD rk;
183  rk.smd = false;
184  rk.hole = true;
185  if( net )
186  rk.netname = net->GetNetname();
187  else
188  rk.netname = wxEmptyString;
189  rk.refdes = wxT("VIA");
190  rk.pin = wxT("");
191  rk.midpoint = true; // Vias are always midpoints
192  rk.drill = via->GetDrillValue();
193  rk.mechanical = false;
194 
195  PCB_LAYER_ID top_layer, bottom_layer;
196 
197  via->LayerPair( &top_layer, &bottom_layer );
198 
199  rk.access = via_access_code( aPcb, top_layer, bottom_layer );
200  rk.x_location = via->GetPosition().x - origin.x;
201  rk.y_location = origin.y - via->GetPosition().y;
202  rk.x_size = via->GetWidth();
203  rk.y_size = 0; // Round so height = 0
204  rk.rotation = 0;
205  rk.soldermask = 3; // XXX always tented?
206 
207  aRecords.push_back( rk );
208  }
209  }
210 }
211 
212 /* Add a new netname to the d356 canonicalized list */
213 static const wxString intern_new_d356_netname( const wxString &aNetname,
214  std::map<wxString, wxString> &aMap, std::set<wxString> &aSet )
215 {
216  wxString canon;
217 
218  for( size_t ii = 0; ii < aNetname.Len(); ++ii )
219  {
220  // Rule: we can only use the standard ASCII, control excluded
221  wxUniChar ch = aNetname[ii];
222 
223  if( ch > 126 || !std::isgraph( static_cast<unsigned char>( ch ) ) )
224  ch = '?';
225 
226  canon += ch;
227  }
228 
229  // Rule: only uppercase (unofficial, but known to give problems
230  // otherwise)
231  canon.MakeUpper();
232 
233  // Rule: maximum length is 14 characters, otherwise we keep the tail
234  if( canon.size() > 14 )
235  {
236  canon = canon.Right( 14 );
237  }
238 
239  // Check if it's still unique
240  if( aSet.count( canon ) )
241  {
242  // Nope, need to uniquify it, trim it more and add a number
243  wxString base( canon );
244  if( base.size() > 10 )
245  {
246  base = base.Right( 10 );
247  }
248 
249  int ctr = 0;
250  do
251  {
252  ++ctr;
253  canon = base;
254  canon << '#' << ctr;
255  } while ( aSet.count( canon ) );
256  }
257 
258  // Register it
259  aMap[aNetname] = canon;
260  aSet.insert( canon );
261  return canon;
262 }
263 
264 /* Write all the accumuled data to the file in D356 format */
265 void IPC356D_WRITER::write_D356_records( std::vector <D356_RECORD> &aRecords, FILE* aFile )
266 {
267  // Sanified and shorted network names and set of short names
268  std::map<wxString, wxString> d356_net_map;
269  std::set<wxString> d356_net_set;
270 
271  for( unsigned i = 0; i < aRecords.size(); i++ )
272  {
273  D356_RECORD &rk = aRecords[i];
274 
275  // Try to sanify the network name (there are limits on this), if
276  // not already done. Also 'empty' net are marked as N/C, as
277  // specified.
278  wxString d356_net( wxT( "N/C" ) );
279 
280  if( !rk.netname.empty() )
281  {
282  d356_net = d356_net_map[rk.netname];
283 
284  if( d356_net.empty() )
285  d356_net = intern_new_d356_netname( rk.netname, d356_net_map, d356_net_set );
286  }
287 
288  // Choose the best record type
289  int rktype;
290 
291  if( rk.smd )
292  rktype = 327;
293  else
294  {
295  if( rk.mechanical )
296  rktype = 367;
297  else
298  rktype = 317;
299  }
300 
301  // Operation code, signal and component
302  fprintf( aFile, "%03d%-14.14s %-6.6s%c%-4.4s%c",
303  rktype, TO_UTF8(d356_net),
304  TO_UTF8(rk.refdes),
305  rk.pin.empty()?' ':'-',
306  TO_UTF8(rk.pin),
307  rk.midpoint?'M':' ' );
308 
309  // Hole definition
310  if( rk.hole )
311  {
312  fprintf( aFile, "D%04d%c",
313  iu_to_d356( rk.drill, 9999 ),
314  rk.mechanical ? 'U':'P' );
315  }
316  else
317  fprintf( aFile, " " );
318 
319  // Test point access
320  fprintf( aFile, "A%02dX%+07dY%+07dX%04dY%04dR%03d",
321  rk.access,
322  iu_to_d356( rk.x_location, 999999 ),
323  iu_to_d356( rk.y_location, 999999 ),
324  iu_to_d356( rk.x_size, 9999 ),
325  iu_to_d356( rk.y_size, 9999 ),
326  rk.rotation );
327 
328  // Soldermask
329  fprintf( aFile, "S%d\n", rk.soldermask );
330  }
331 }
332 
333 
334 void IPC356D_WRITER::Write( const wxString& aFilename )
335 {
336  FILE* file = nullptr;
337  LOCALE_IO toggle; // Switch the locale to standard C
338 
339  if( ( file = wxFopen( aFilename, wxT( "wt" ) ) ) == nullptr )
340  {
341  wxString details;
342  details.Printf( wxT( "The file %s could not be opened for writing." ), aFilename );
343  DisplayErrorMessage( m_parent, wxT( "Could not write IPC-356D file!" ), details );
344  return;
345  }
346 
347  // This will contain everything needed for the 356 file
348  std::vector<D356_RECORD> d356_records;
349 
350  build_via_testpoints( m_pcb, d356_records );
351 
352  build_pad_testpoints( m_pcb, d356_records );
353 
354  // Code 00 AFAIK is ASCII, CUST 0 is decimils/degrees
355  // CUST 1 would be metric but gerbtool simply ignores it!
356  fprintf( file, "P CODE 00\n" );
357  fprintf( file, "P UNITS CUST 0\n" );
358  fprintf( file, "P arrayDim N\n" );
359  write_D356_records( d356_records, file );
360  fprintf( file, "999\n" );
361 
362  fclose( file );
363 }
364 
365 
366 void PCB_EDIT_FRAME::GenD356File( wxCommandEvent& aEvent )
367 {
368  wxFileName fn = GetBoard()->GetFileName();
369  wxString msg, ext, wildcard;
370 
371  ext = IpcD356FileExtension;
372  wildcard = IpcD356FileWildcard();
373  fn.SetExt( ext );
374 
375  wxString pro_dir = wxPathOnly( Prj().GetProjectFullName() );
376 
377  wxFileDialog dlg( this, _( "Export D-356 Test File" ), pro_dir,
378  fn.GetFullName(), wildcard,
379  wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
380 
381  if( dlg.ShowModal() == wxID_CANCEL )
382  return;
383 
384  IPC356D_WRITER writer( GetBoard(), this );
385 
386  writer.Write( dlg.GetPath() );
387 }
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:759
wxString pin
Definition: export_d356.h:35
void Write(const wxString &aFilename)
Generates and writes the netlist to a given path.
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:292
int x_location
Definition: export_d356.h:42
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:40
wxWindow * m_parent
Definition: export_d356.h:75
static int iu_to_d356(int iu, int clamp)
Definition: export_d356.cpp:87
This file is part of the common library.
bool mechanical
Definition: export_d356.h:38
static void build_pad_testpoints(BOARD *aPcb, std::vector< D356_RECORD > &aRecords)
Definition: export_d356.cpp:96
void write_D356_records(std::vector< D356_RECORD > &aRecords, FILE *aFile)
Writes a list of records to the given output stream.
Like smd, does not appear on the solder paste layer (default)
Smd pad, appears on the solder paste layer (default)
int LAYER_NUM
This can be replaced with int and removed.
Definition: layer_ids.h:41
const wxString & GetFileName() const
Definition: board.h:229
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:590
bool midpoint
Definition: export_d356.h:36
This file contains miscellaneous commonly used macros and functions.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: macros.h:96
like PAD_PTH, but not plated
LSET is a set of PCB_LAYER_IDs.
Definition: layer_ids.h:516
Wrapper to expose an API for writing IPC-D356 files.
Definition: export_d356.h:53
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
FOOTPRINTS & Footprints()
Definition: board.h:234
wxString refdes
Definition: export_d356.h:34
static void build_via_testpoints(BOARD *aPcb, std::vector< D356_RECORD > &aRecords)
Definition of file extensions used in Kicad.
const wxString & GetNetname() const
Definition: netinfo.h:126
#define _(s)
const std::string IpcD356FileExtension
BOARD * m_pcb
Definition: export_d356.h:74
wxString IpcD356FileWildcard()
int soldermask
Definition: export_d356.h:40
wxString netname
Definition: export_d356.h:33
Handle the data for a net.
Definition: netinfo.h:66
static const wxString intern_new_d356_netname(const wxString &aNetname, std::map< wxString, wxString > &aMap, std::set< wxString > &aSet)
const wxPoint & GetAuxOrigin()
void GenD356File(wxCommandEvent &event)
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:191
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:65
Definition: layer_ids.h:71
static int compute_pad_access_code(BOARD *aPcb, LSET aLayerMask)
Definition: export_d356.cpp:54
int GetCopperLayerCount() const
Definition: board.cpp:455
#define IU_PER_MILS
Definition: plotter.cpp:130
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:73
static int via_access_code(BOARD *aPcb, int top_layer, int bottom_layer)
class PCB_VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:96
BOARD * GetBoard() const
Definition: pad.h:57
int y_location
Definition: export_d356.h:43
TRACKS & Tracks()
Definition: board.h:231