KiCad PCB EDA Suite
gendrill_file_writer_base.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) 2017 Jean_Pierre Charras <jp.charras at wanadoo.fr>
5  * Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
6  * Copyright (C) 1992-2017 KiCad Developers, see change_log.txt for contributors.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24  */
25 #include <fctsys.h>
26 
27 #include <class_board.h>
28 #include <class_module.h>
29 #include <class_track.h>
30 #include <collectors.h>
31 #include <reporter.h>
32 
34 
35 
36 /* Helper function for sorting hole list.
37  * Compare function used for sorting holes type type (plated then not plated)
38  * then by increasing diameter value and X value
39  */
40 static bool CmpHoleSorting( const HOLE_INFO& a, const HOLE_INFO& b )
41 {
43  return b.m_Hole_NotPlated;
44 
45  if( a.m_Hole_Diameter != b.m_Hole_Diameter )
46  return a.m_Hole_Diameter < b.m_Hole_Diameter;
47 
48  // group by components when possible
49  const D_PAD* pada = dyn_cast<const D_PAD*>( a.m_ItemParent );
50  const D_PAD* padb = dyn_cast<const D_PAD*>( b.m_ItemParent );
51 
52  if( pada && padb )
53  {
54  // cmp == 0 means the pads have the same parent, therfore the same reference
55  int cmp = pada->GetParent() - padb->GetParent();
56 
57  if( cmp )
58  return cmp < 0;
59  }
60  else if( pada || padb ) // in this case, other item is a via. Sort via first
61  {
62  return padb != nullptr;
63  }
64 
65  // At this point, sort by position, as last sort criteria
66  if( a.m_Hole_Pos.x != b.m_Hole_Pos.x )
67  return a.m_Hole_Pos.x < b.m_Hole_Pos.x;
68 
69  return a.m_Hole_Pos.y < b.m_Hole_Pos.y;
70 }
71 
72 
74  bool aGenerateNPTH_list )
75 {
76  HOLE_INFO new_hole;
77 
78  m_holeListBuffer.clear();
79  m_toolListBuffer.clear();
80 
81  wxASSERT( aLayerPair.first < aLayerPair.second ); // fix the caller
82 
83  // build hole list for vias
84  if( ! aGenerateNPTH_list ) // vias are always plated !
85  {
86  for( auto track : m_pcb->Tracks() )
87  {
88  if( track->Type() != PCB_VIA_T )
89  continue;
90 
91  auto via = static_cast<VIA*>( track );
92  int hole_sz = via->GetDrillValue();
93 
94  if( hole_sz == 0 ) // Should not occur.
95  continue;
96 
97  new_hole.m_ItemParent = via;
98  new_hole.m_Tool_Reference = -1; // Flag value for Not initialized
99  new_hole.m_Hole_Orient = 0;
100  new_hole.m_Hole_Diameter = hole_sz;
101  new_hole.m_Hole_NotPlated = false;
102  new_hole.m_Hole_Size.x = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;
103 
104  new_hole.m_Hole_Shape = 0; // hole shape: round
105  new_hole.m_Hole_Pos = via->GetStart();
106 
107  via->LayerPair( &new_hole.m_Hole_Top_Layer, &new_hole.m_Hole_Bottom_Layer );
108 
109  // LayerPair() returns params with m_Hole_Bottom_Layer > m_Hole_Top_Layer
110  // Remember: top layer = 0 and bottom layer = 31 for through hole vias
111  // Any captured via should be from aLayerPair.first to aLayerPair.second exactly.
112  if( new_hole.m_Hole_Top_Layer != aLayerPair.first ||
113  new_hole.m_Hole_Bottom_Layer != aLayerPair.second )
114  continue;
115 
116  m_holeListBuffer.push_back( new_hole );
117  }
118  }
119 
120  if( aLayerPair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
121  {
122  // add holes for thru hole pads
123  for( auto module : m_pcb->Modules() )
124  {
125  for( auto& pad : module->Pads() )
126  {
127  if( !m_merge_PTH_NPTH )
128  {
129  if( !aGenerateNPTH_list && pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED )
130  continue;
131 
132  if( aGenerateNPTH_list && pad->GetAttribute() != PAD_ATTRIB_HOLE_NOT_PLATED )
133  continue;
134  }
135 
136  if( pad->GetDrillSize().x == 0 )
137  continue;
138 
139  new_hole.m_ItemParent = pad;
140  new_hole.m_Hole_NotPlated = (pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED);
141  new_hole.m_Tool_Reference = -1; // Flag is: Not initialized
142  new_hole.m_Hole_Orient = pad->GetOrientation();
143  new_hole.m_Hole_Shape = 0; // hole shape: round
144  new_hole.m_Hole_Diameter = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
145  new_hole.m_Hole_Size.x = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;
146 
147  if( pad->GetDrillShape() != PAD_DRILL_SHAPE_CIRCLE )
148  new_hole.m_Hole_Shape = 1; // oval flag set
149 
150  new_hole.m_Hole_Size = pad->GetDrillSize();
151  new_hole.m_Hole_Pos = pad->GetPosition(); // hole position
152  new_hole.m_Hole_Bottom_Layer = B_Cu;
153  new_hole.m_Hole_Top_Layer = F_Cu; // pad holes are through holes
154  m_holeListBuffer.push_back( new_hole );
155  }
156  }
157  }
158 
159  // Sort holes per increasing diameter value
160  sort( m_holeListBuffer.begin(), m_holeListBuffer.end(), CmpHoleSorting );
161 
162  // build the tool list
163  int last_hole = -1; // Set to not initialized (this is a value not used
164  // for m_holeListBuffer[ii].m_Hole_Diameter)
165  bool last_notplated_opt = false;
166 
167  DRILL_TOOL new_tool( 0, false );
168  unsigned jj;
169 
170  for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
171  {
172  if( m_holeListBuffer[ii].m_Hole_Diameter != last_hole ||
173  m_holeListBuffer[ii].m_Hole_NotPlated != last_notplated_opt )
174  {
175  new_tool.m_Diameter = m_holeListBuffer[ii].m_Hole_Diameter;
176  new_tool.m_Hole_NotPlated = m_holeListBuffer[ii].m_Hole_NotPlated;
177  m_toolListBuffer.push_back( new_tool );
178  last_hole = new_tool.m_Diameter;
179  last_notplated_opt = new_tool.m_Hole_NotPlated;
180  }
181 
182  jj = m_toolListBuffer.size();
183 
184  if( jj == 0 )
185  continue; // Should not occurs
186 
187  m_holeListBuffer[ii].m_Tool_Reference = jj; // Tool value Initialized (value >= 1)
188 
189  m_toolListBuffer.back().m_TotalCount++;
190 
191  if( m_holeListBuffer[ii].m_Hole_Shape )
192  m_toolListBuffer.back().m_OvalCount++;
193  }
194 }
195 
196 
197 std::vector<DRILL_LAYER_PAIR> GENDRILL_WRITER_BASE::getUniqueLayerPairs() const
198 {
199  wxASSERT( m_pcb );
200 
201  static const KICAD_T interesting_stuff_to_collect[] = {
202  PCB_VIA_T,
203  EOT
204  };
205 
206  PCB_TYPE_COLLECTOR vias;
207 
208  vias.Collect( m_pcb, interesting_stuff_to_collect );
209 
210  std::set< DRILL_LAYER_PAIR > unique;
211 
212  DRILL_LAYER_PAIR layer_pair;
213 
214  for( int i = 0; i < vias.GetCount(); ++i )
215  {
216  VIA* v = (VIA*) vias[i];
217 
218  v->LayerPair( &layer_pair.first, &layer_pair.second );
219 
220  // only make note of blind buried.
221  // thru hole is placed unconditionally as first in fetched list.
222  if( layer_pair != DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
223  {
224  unique.insert( layer_pair );
225  }
226  }
227 
228  std::vector<DRILL_LAYER_PAIR> ret;
229 
230  ret.emplace_back( F_Cu, B_Cu ); // always first in returned list
231 
232  for( std::set< DRILL_LAYER_PAIR >::const_iterator it = unique.begin(); it != unique.end(); ++it )
233  ret.push_back( *it );
234 
235  return ret;
236 }
237 
238 
239 const std::string GENDRILL_WRITER_BASE::layerName( PCB_LAYER_ID aLayer ) const
240 {
241  // Generic names here.
242  switch( aLayer )
243  {
244  case F_Cu:
245  return "front";
246  case B_Cu:
247  return "back";
248  default:
249  return StrPrintf( "in%d", aLayer );
250  }
251 }
252 
253 
255 {
256  std::string ret = layerName( aPair.first );
257  ret += '-';
258  ret += layerName( aPair.second );
259 
260  return ret;
261 }
262 
263 
265  bool aMerge_PTH_NPTH ) const
266 {
267  wxASSERT( m_pcb );
268 
269  wxString extend;
270 
271  if( aNPTH )
272  extend = "-NPTH";
273  else if( aPair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
274  {
275  if( !aMerge_PTH_NPTH )
276  extend = "-PTH";
277  // if merged, extend with nothing
278  }
279  else
280  {
281  extend += '-';
282  extend += layerPairName( aPair );
283  }
284 
285  wxFileName fn = m_pcb->GetFileName();
286 
287  fn.SetName( fn.GetName() + extend );
288  fn.SetExt( m_drillFileExtension );
289 
290  wxString ret = fn.GetFullName();
291 
292  return ret;
293 }
294 
295 void GENDRILL_WRITER_BASE::CreateMapFilesSet( const wxString& aPlotDirectory,
296  REPORTER * aReporter )
297 {
298  wxFileName fn;
299  wxString msg;
300 
301  std::vector<DRILL_LAYER_PAIR> hole_sets = getUniqueLayerPairs();
302 
303  // append a pair representing the NPTH set of holes, for separate drill files.
304  if( !m_merge_PTH_NPTH )
305  hole_sets.emplace_back( F_Cu, B_Cu );
306 
307  for( std::vector<DRILL_LAYER_PAIR>::const_iterator it = hole_sets.begin();
308  it != hole_sets.end(); ++it )
309  {
310  DRILL_LAYER_PAIR pair = *it;
311  // For separate drill files, the last layer pair is the NPTH drill file.
312  bool doing_npth = m_merge_PTH_NPTH ? false : ( it == hole_sets.end() - 1 );
313 
314  buildHolesList( pair, doing_npth );
315 
316  // The file is created if it has holes, or if it is the non plated drill file
317  // to be sure the NPTH file is up to date in separate files mode.
318  if( getHolesCount() > 0 || doing_npth )
319  {
320  fn = getDrillFileName( pair, doing_npth, m_merge_PTH_NPTH );
321  fn.SetPath( aPlotDirectory );
322 
323  fn.SetExt( wxEmptyString ); // Will be added by GenDrillMap
324  wxString fullfilename = fn.GetFullPath() + wxT( "-drl_map" );
325  fullfilename << wxT(".") << GetDefaultPlotExtension( m_mapFileFmt );
326 
327  bool success = genDrillMapFile( fullfilename, m_mapFileFmt );
328 
329  if( ! success )
330  {
331  if( aReporter )
332  {
333  msg.Printf( _( "** Unable to create %s **\n" ), GetChars( fullfilename ) );
334  aReporter->Report( msg );
335  }
336 
337  return;
338  }
339  else
340  {
341  if( aReporter )
342  {
343  msg.Printf( _( "Create file %s\n" ), GetChars( fullfilename ) );
344  aReporter->Report( msg );
345  }
346  }
347  }
348  }
349 }
350 
351 
353  DRILL_LAYER_PAIR aLayerPair, bool aIsNpth, bool aCompatNCdrill ) const
354 {
355 // Build a wxString containing the .FileFunction attribute for drill files.
356 // %TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH][Blind][Buried],Drill[Route][Mixed]*%
357  wxString text;
358 
359  if( aCompatNCdrill )
360  text = "; #@! ";
361  else
362  text = "%";
363 
364  text << "TF.FileFunction,";
365 
366  if( aIsNpth )
367  text << "NonPlated,";
368  else
369  text << "Plated,";
370 
371  int layer1 = aLayerPair.first;
372  int layer2 = aLayerPair.second;
373  // In Gerber files, layers num are 1 to copper layer count instead of F_Cu to B_Cu
374  // (0 to copper layer count-1)
375  // Note also for a n copper layers board, gerber layers num are 1 ... n
376  layer1 += 1;
377 
378  if( layer2 == B_Cu )
379  layer2 = m_pcb->GetCopperLayerCount();
380  else
381  layer2 += 1;
382 
383  text << layer1 << ",";
384  text << layer2 << ",";
385 
386  // Now add PTH or NPTH or Blind or Buried attribute
387  int toplayer = 1;
388  int bottomlayer = m_pcb->GetCopperLayerCount();
389 
390  if( aIsNpth )
391  text << "NPTH";
392  else if( layer1 == toplayer && layer2 == bottomlayer )
393  text << "PTH";
394  else if( layer1 == toplayer || layer2 == bottomlayer )
395  text << "Blind";
396  else
397  text << "Buried";
398 
399  // In NC drill file, these previous parameters should be enough:
400  if( aCompatNCdrill )
401  return text;
402 
403 
404  // Now add Drill or Route or Mixed:
405  // file containing only round holes have Drill attribute
406  // file containing only oblong holes have Routed attribute
407  // file containing both holes have Mixed attribute
408  bool hasOblong = false;
409  bool hasDrill = false;
410 
411  for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
412  {
413  const HOLE_INFO& hole_descr = m_holeListBuffer[ii];
414 
415  if( hole_descr.m_Hole_Shape ) // m_Hole_Shape not 0 is an oblong hole)
416  hasOblong = true;
417  else
418  hasDrill = true;
419  }
420 
421  if( hasOblong && hasDrill )
422  text << ",Mixed";
423  else if( hasDrill )
424  text << ",Drill";
425  else if( hasOblong )
426  text << ",Route";
427 
428  // else: empty file.
429 
430  // End of .FileFunction attribute:
431  text << "*%";
432 
433  return text;
434 }
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...
BOARD_ITEM * m_ItemParent
virtual const wxString getDrillFileName(DRILL_LAYER_PAIR aPair, bool aNPTH, bool aMerge_PTH_NPTH) const
static bool CmpHoleSorting(const HOLE_INFO &a, const HOLE_INFO &b)
int StrPrintf(std::string *aResult, const char *aFormat,...)
Function StrPrintf is like sprintf() but the output is appended to a std::string instead of to a char...
Definition: richio.cpp:74
like PAD_STANDARD, but not plated mechanical use only, no connection allowed
Definition: pad_shapes.h:85
wxString GetDefaultPlotExtension(PLOT_FORMAT aFormat)
Returns the default plot extension for a format.
std::vector< DRILL_LAYER_PAIR > getUniqueLayerPairs() const
Get unique layer pairs by examining the micro and blind_buried vias.
REPORTER is a pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:64
const wxString & GetFileName() const
Definition: class_board.h:255
void CreateMapFilesSet(const wxString &aPlotDirectory, REPORTER *aReporter=NULL)
Function CreateMapFilesSet Creates the full set of map files for the board, in PS,...
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Function Report is a pure virtual function to override in the derived object.
search types array terminator (End Of Types)
Definition: typeinfo.h:82
KICAD_T
Enum KICAD_T is the set of class identification values, stored in EDA_ITEM::m_StructType.
Definition: typeinfo.h:78
A single base class (TRACK) represents both tracks and vias, with subclasses for curved tracks (ARC) ...
std::vector< DRILL_TOOL > m_toolListBuffer
int GetCount() const
Function GetCount returns the number of objects in the list.
Definition: collector.h:101
PCB_LAYER_ID
A quick note on layer IDs:
MODULES & Modules()
Definition: class_board.h:266
const std::string layerPairName(DRILL_LAYER_PAIR aPair) const
minor helper function.
helper classes to handle hole info for drill files generators.
std::pair< PCB_LAYER_ID, PCB_LAYER_ID > DRILL_LAYER_PAIR
const wxString BuildFileFunctionAttributeString(DRILL_LAYER_PAIR aLayerPair, bool aIsNpth, bool aCompatNCdrill=false) const
void Collect(BOARD_ITEM *aBoard, const KICAD_T aScanList[])
Collect BOARD_ITEM objects using this class's Inspector method, which does the collection.
Definition: collectors.cpp:577
void buildHolesList(DRILL_LAYER_PAIR aLayerPair, bool aGenerateNPTH_list)
Function BuildHolesList Create the list of holes and tools for a given board The list is sorted by in...
MODULE * GetParent() const
Definition: class_pad.h:111
PCB_LAYER_ID m_Hole_Bottom_Layer
PCB_LAYER_ID m_Hole_Top_Layer
static const wxChar * GetChars(const wxString &s)
Function GetChars returns a wxChar* to the actual wxChar* data within a wxString, and is helpful for ...
Definition: macros.h:153
const std::string layerName(PCB_LAYER_ID aLayer) const
minor helper function.
#define _(s)
Definition: 3d_actions.cpp:33
int GetCopperLayerCount() const
Function GetCopperLayerCount.
bool genDrillMapFile(const wxString &aFullFileName, PLOT_FORMAT aFormat)
Function GenDrillMapFile Plot a map of drill marks for holes.
class VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:97
Collect all BOARD_ITEM objects of a given set of KICAD_T type(s).
Definition: collectors.h:621
std::vector< HOLE_INFO > m_holeListBuffer
TRACKS & Tracks()
Definition: class_board.h:257