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