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 <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 
30 #include <fctsys.h>
31 #include <confirm.h>
32 #include <gestfich.h>
33 #include <kiface_i.h>
34 #include <pcb_edit_frame.h>
35 #include <trigo.h>
36 #include <build_version.h>
37 #include <macros.h>
39 #include <pcbnew.h>
40 #include <class_board.h>
41 #include <class_module.h>
42 #include <class_track.h>
43 #include <class_edge_mod.h>
44 #include <vector>
45 #include <cctype>
46 #include <math/util.h> // for KiROUND
47 #include <export_d356.h>
48 
49 
50 
51 // Compute the access code for a pad. Returns -1 if there is no copper
52 static int compute_pad_access_code( BOARD *aPcb, LSET aLayerMask )
53 {
54  // Non-copper is not interesting here
55  aLayerMask &= LSET::AllCuMask();
56  if( !aLayerMask.any() )
57  return -1;
58 
59  // Traditional TH pad
60  if( aLayerMask[F_Cu] && aLayerMask[B_Cu] )
61  return 0;
62 
63  // Front SMD pad
64  if( aLayerMask[F_Cu] )
65  return 1;
66 
67  // Back SMD pad
68  if( aLayerMask[B_Cu] )
69  return aPcb->GetCopperLayerCount();
70 
71  // OK, we have an inner-layer only pad (and I have no idea about
72  // what could be used for); anyway, find the first copper layer
73  // it's on
74  for( LAYER_NUM layer = In1_Cu; layer < B_Cu; ++layer )
75  {
76  if( aLayerMask[layer] )
77  return layer + 1;
78  }
79 
80  // This shouldn't happen
81  return -1;
82 }
83 
84 /* Convert and clamp a size from IU to decimils */
85 static int iu_to_d356(int iu, int clamp)
86 {
87  int val = KiROUND( iu / ( IU_PER_MILS / 10 ) );
88  if( val > clamp ) return clamp;
89  if( val < -clamp ) return -clamp;
90  return val;
91 }
92 
93 /* Extract the D356 record from the modules (pads) */
94 static void build_pad_testpoints( BOARD *aPcb, std::vector <D356_RECORD>& aRecords )
95 {
96  wxPoint origin = aPcb->GetDesignSettings().m_AuxOrigin;
97 
98  for( auto module : aPcb->Modules() )
99  {
100  for( auto pad : module->Pads() )
101  {
102  D356_RECORD rk;
103  rk.access = compute_pad_access_code( aPcb, pad->GetLayerSet() );
104 
105  // It could be a mask only pad, we only handle pads with copper here
106  if( rk.access != -1 )
107  {
108  rk.netname = pad->GetNetname();
109  rk.pin = pad->GetName();
110  rk.refdes = module->GetReference();
111  rk.midpoint = false; // XXX MAYBE need to be computed (how?)
112  const wxSize& drill = pad->GetDrillSize();
113  rk.drill = std::min( drill.x, drill.y );
114  rk.hole = (rk.drill != 0);
115  rk.smd = pad->GetAttribute() == PAD_ATTRIB_SMD;
116  rk.mechanical = (pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED);
117  rk.x_location = pad->GetPosition().x - origin.x;
118  rk.y_location = origin.y - pad->GetPosition().y;
119  rk.x_size = pad->GetSize().x;
120 
121  // Rule: round pads have y = 0
122  if( pad->GetShape() == PAD_SHAPE_CIRCLE )
123  rk.y_size = 0;
124  else
125  rk.y_size = pad->GetSize().y;
126 
127  rk.rotation = -KiROUND( pad->GetOrientation() ) / 10;
128  if( rk.rotation < 0 ) rk.rotation += 360;
129 
130  // the value indicates which sides are *not* accessible
131  rk.soldermask = 3;
132  if( pad->GetLayerSet()[F_Mask] )
133  rk.soldermask &= ~1;
134  if( pad->GetLayerSet()[B_Mask] )
135  rk.soldermask &= ~2;
136 
137  aRecords.push_back( rk );
138  }
139  }
140  }
141 }
142 
143 /* Compute the access code for a via. In D-356 layers are numbered from 1 up,
144  where '1' is the 'primary side' (usually the component side);
145  '0' means 'both sides', and other layers follows in an unspecified order */
146 static int via_access_code( BOARD *aPcb, int top_layer, int bottom_layer )
147 {
148  // Easy case for through vias: top_layer is component, bottom_layer is
149  // solder, access code is 0
150  if( (top_layer == F_Cu) && (bottom_layer == B_Cu) )
151  return 0;
152 
153  // Blind via, reachable from front
154  if( top_layer == F_Cu )
155  return 1;
156 
157  // Blind via, reachable from bottom
158  if( bottom_layer == B_Cu )
159  return aPcb->GetCopperLayerCount();
160 
161  // It's a buried via, accessible from some inner layer
162  // (maybe could be used for testing before laminating? no idea)
163  return bottom_layer + 1; // XXX is this correct?
164 }
165 
166 /* Extract the D356 record from the vias */
167 static void build_via_testpoints( BOARD *aPcb, std::vector <D356_RECORD>& aRecords )
168 {
169  wxPoint origin = aPcb->GetDesignSettings().m_AuxOrigin;
170 
171  // Enumerate all the track segments and keep the vias
172  for( auto track : aPcb->Tracks() )
173  {
174  if( track->Type() == PCB_VIA_T )
175  {
176  VIA *via = (VIA*) track;
177  NETINFO_ITEM *net = track->GetNet();
178 
179  D356_RECORD rk;
180  rk.smd = false;
181  rk.hole = true;
182  if( net )
183  rk.netname = net->GetNetname();
184  else
185  rk.netname = wxEmptyString;
186  rk.refdes = wxT("VIA");
187  rk.pin = wxT("");
188  rk.midpoint = true; // Vias are always midpoints
189  rk.drill = via->GetDrillValue();
190  rk.mechanical = false;
191 
192  PCB_LAYER_ID top_layer, bottom_layer;
193 
194  via->LayerPair( &top_layer, &bottom_layer );
195 
196  rk.access = via_access_code( aPcb, top_layer, bottom_layer );
197  rk.x_location = via->GetPosition().x - origin.x;
198  rk.y_location = origin.y - via->GetPosition().y;
199  rk.x_size = via->GetWidth();
200  rk.y_size = 0; // Round so height = 0
201  rk.rotation = 0;
202  rk.soldermask = 3; // XXX always tented?
203 
204  aRecords.push_back( rk );
205  }
206  }
207 }
208 
209 /* Add a new netname to the d356 canonicalized list */
210 static const wxString intern_new_d356_netname( const wxString &aNetname,
211  std::map<wxString, wxString> &aMap, std::set<wxString> &aSet )
212 {
213  wxString canon;
214 
215  for( size_t ii = 0; ii < aNetname.Len(); ++ii )
216  {
217  // Rule: we can only use the standard ASCII, control excluded
218  wxUniChar ch = aNetname[ii];
219 
220  if( ch > 126 || !std::isgraph( static_cast<unsigned char>( ch ) ) )
221  ch = '?';
222 
223  canon += ch;
224  }
225 
226  // Rule: only uppercase (unofficial, but known to give problems
227  // otherwise)
228  canon.MakeUpper();
229 
230  // Rule: maximum length is 14 characters, otherwise we keep the tail
231  if( canon.size() > 14 )
232  {
233  canon = canon.Right( 14 );
234  }
235 
236  // Check if it's still unique
237  if( aSet.count( canon ) )
238  {
239  // Nope, need to uniquify it, trim it more and add a number
240  wxString base( canon );
241  if( base.size() > 10 )
242  {
243  base = base.Right( 10 );
244  }
245 
246  int ctr = 0;
247  do
248  {
249  ++ctr;
250  canon = base;
251  canon << '#' << ctr;
252  } while ( aSet.count( canon ) );
253  }
254 
255  // Register it
256  aMap[aNetname] = canon;
257  aSet.insert( canon );
258  return canon;
259 }
260 
261 /* Write all the accumuled data to the file in D356 format */
262 void IPC356D_WRITER::write_D356_records( std::vector <D356_RECORD> &aRecords, FILE* aFile )
263 {
264  // Sanified and shorted network names and set of short names
265  std::map<wxString, wxString> d356_net_map;
266  std::set<wxString> d356_net_set;
267 
268  for( unsigned i = 0; i < aRecords.size(); i++ )
269  {
270  D356_RECORD &rk = aRecords[i];
271 
272  // Try to sanify the network name (there are limits on this), if
273  // not already done. Also 'empty' net are marked as N/C, as
274  // specified.
275  wxString d356_net( wxT( "N/C" ) );
276 
277  if( !rk.netname.empty() )
278  {
279  d356_net = d356_net_map[rk.netname];
280 
281  if( d356_net.empty() )
282  d356_net = intern_new_d356_netname( rk.netname, d356_net_map, d356_net_set );
283  }
284 
285  // Choose the best record type
286  int rktype;
287 
288  if( rk.smd )
289  rktype = 327;
290  else
291  {
292  if( rk.mechanical )
293  rktype = 367;
294  else
295  rktype = 317;
296  }
297 
298  // Operation code, signal and component
299  fprintf( aFile, "%03d%-14.14s %-6.6s%c%-4.4s%c",
300  rktype, TO_UTF8(d356_net),
301  TO_UTF8(rk.refdes),
302  rk.pin.empty()?' ':'-',
303  TO_UTF8(rk.pin),
304  rk.midpoint?'M':' ' );
305 
306  // Hole definition
307  if( rk.hole )
308  {
309  fprintf( aFile, "D%04d%c",
310  iu_to_d356( rk.drill, 9999 ),
311  rk.mechanical ? 'U':'P' );
312  }
313  else
314  fprintf( aFile, " " );
315 
316  // Test point access
317  fprintf( aFile, "A%02dX%+07dY%+07dX%04dY%04dR%03d",
318  rk.access,
319  iu_to_d356( rk.x_location, 999999 ),
320  iu_to_d356( rk.y_location, 999999 ),
321  iu_to_d356( rk.x_size, 9999 ),
322  iu_to_d356( rk.y_size, 9999 ),
323  rk.rotation );
324 
325  // Soldermask
326  fprintf( aFile, "S%d\n", rk.soldermask );
327  }
328 }
329 
330 
331 void IPC356D_WRITER::Write( const wxString& aFilename )
332 {
333  FILE* file = nullptr;
334  LOCALE_IO toggle; // Switch the locale to standard C
335 
336  if( ( file = wxFopen( aFilename, wxT( "wt" ) ) ) == nullptr )
337  {
338  wxString details;
339  details.Printf( "The file %s could not be opened for writing", aFilename );
340  DisplayErrorMessage( m_parent, "Could not write IPC-356D file!", details );
341  return;
342  }
343 
344  // This will contain everything needed for the 356 file
345  std::vector<D356_RECORD> d356_records;
346 
347  build_via_testpoints( m_pcb, d356_records );
348 
349  build_pad_testpoints( m_pcb, d356_records );
350 
351  // Code 00 AFAIK is ASCII, CUST 0 is decimils/degrees
352  // CUST 1 would be metric but gerbtool simply ignores it!
353  fprintf( file, "P CODE 00\n" );
354  fprintf( file, "P UNITS CUST 0\n" );
355  fprintf( file, "P arrayDim N\n" );
356  write_D356_records( d356_records, file );
357  fprintf( file, "999\n" );
358 
359  fclose( file );
360 }
361 
362 
363 void PCB_EDIT_FRAME::GenD356File( wxCommandEvent& aEvent )
364 {
365  wxFileName fn = GetBoard()->GetFileName();
366  wxString msg, ext, wildcard;
367 
368  ext = IpcD356FileExtension;
369  wildcard = IpcD356FileWildcard();
370  fn.SetExt( ext );
371 
372  wxString pro_dir = wxPathOnly( Prj().GetProjectFullName() );
373 
374  wxFileDialog dlg( this, _( "Export D-356 Test File" ), pro_dir,
375  fn.GetFullName(), wildcard,
376  wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
377 
378  if( dlg.ShowModal() == wxID_CANCEL )
379  return;
380 
381  IPC356D_WRITER writer( GetBoard(), this );
382 
383  writer.Write( dlg.GetPath() );
384 }
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Function AllCuMask returns a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:712
void LayerPair(PCB_LAYER_ID *top_layer, PCB_LAYER_ID *bottom_layer) const
Function LayerPair Return the 2 layers used by the via (the via actually uses all layers between thes...
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:252
int x_location
Definition: export_d356.h:42
This file is part of the common library TODO brief description.
like PAD_STANDARD, but not plated mechanical use only, no connection allowed
Definition: pad_shapes.h:85
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: common.h:216
wxWindow * m_parent
Definition: export_d356.h:76
static int iu_to_d356(int iu, int clamp)
Definition: export_d356.cpp:85
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:94
void write_D356_records(std::vector< D356_RECORD > &aRecords, FILE *aFile)
Writes a list of records to the given output stream.
Smd pad, appears on the solder paste layer (default)
Definition: pad_shapes.h:81
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Function GetDesignSettings.
Definition: class_board.h:551
const wxString & GetFileName() const
Definition: class_board.h:255
bool midpoint
Definition: export_d356.h:36
A single base class (TRACK) represents both tracks and vias, with subclasses for curved tracks (ARC) ...
This file contains miscellaneous commonly used macros and functions.
PCB_LAYER_ID
A quick note on layer IDs:
LSET is a set of PCB_LAYER_IDs.
Wrapper to expose an API for writing IPC-D356 files.
Definition: export_d356.h:53
int GetDrillValue() const
Function GetDrillValue "calculates" the drill value for vias (m-Drill if > 0, or default drill value ...
MODULES & Modules()
Definition: class_board.h:266
PROJECT & Prj() const
Function Prj returns a reference to the PROJECT "associated with" this KIWAY.
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
Function GetNetname.
Definition: netinfo.h:231
const std::string IpcD356FileExtension
const wxPoint GetPosition() const override
Definition: class_track.h:406
BOARD * m_pcb
Definition: export_d356.h:74
wxString IpcD356FileWildcard()
int soldermask
Definition: export_d356.h:40
int LAYER_NUM
Type LAYER_NUM can be replaced with int and removed.
wxString netname
Definition: export_d356.h:33
NETINFO_ITEM handles the data for a net.
Definition: netinfo.h:65
static const wxString intern_new_d356_netname(const wxString &aNetname, std::map< wxString, wxString > &aMap, std::set< wxString > &aSet)
int GetWidth() const
Definition: class_track.h:112
int GetNet() const
Function GetNet.
Definition: netinfo.h:223
void GenD356File(wxCommandEvent &event)
BOARD holds information pertinent to a Pcbnew printed circuit board.
Definition: class_board.h:180
#define _(s)
Definition: 3d_actions.cpp:33
static int compute_pad_access_code(BOARD *aPcb, LSET aLayerMask)
Definition: export_d356.cpp:52
int GetCopperLayerCount() const
Function GetCopperLayerCount.
#define IU_PER_MILS
Definition: plotter.cpp:138
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:68
#define TO_UTF8(wxstring)
static int via_access_code(BOARD *aPcb, int top_layer, int bottom_layer)
class VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:97
BOARD * GetBoard() const
EDGE_MODULE class definition.
int y_location
Definition: export_d356.h:43
wxPoint m_AuxOrigin
origin for plot exports
TRACKS & Tracks()
Definition: class_board.h:257
virtual LSET GetLayerSet() const
Function GetLayerSet returns a "layer mask", which is a bitmap of all layers on which the TRACK segme...