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 <[email protected]>
6  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.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 
26 #include <board.h>
27 #include <footprint.h>
28 #include <pad.h>
29 #include <pcb_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:
38  * plated then not plated
39  * then by increasing diameter value
40  * then by attribute type (vias, pad, mechanical)
41  * then by X then Y position
42  */
43 static bool cmpHoleSorting( const HOLE_INFO& a, const HOLE_INFO& b )
44 {
46  return b.m_Hole_NotPlated;
47 
48  if( a.m_Hole_Diameter != b.m_Hole_Diameter )
49  return a.m_Hole_Diameter < b.m_Hole_Diameter;
50 
51  // At this point (same diameter, same plated type), group by attribute
52  // type (via, pad, mechanical, although currently only not plated pads are mechanical)
53  if( a.m_HoleAttribute != b.m_HoleAttribute )
54  return a.m_HoleAttribute < b.m_HoleAttribute;
55 
56  // At this point (same diameter, same type), sort by X then Y position.
57  // This is optimal for drilling and make the file reproducible as long as holes
58  // have not changed, even if the data order has changed.
59  if( a.m_Hole_Pos.x != b.m_Hole_Pos.x )
60  return a.m_Hole_Pos.x < b.m_Hole_Pos.x;
61 
62  return a.m_Hole_Pos.y < b.m_Hole_Pos.y;
63 }
64 
65 
67  bool aGenerateNPTH_list )
68 {
69  HOLE_INFO new_hole;
70 
71  m_holeListBuffer.clear();
72  m_toolListBuffer.clear();
73 
74  wxASSERT( aLayerPair.first < aLayerPair.second ); // fix the caller
75 
76  // build hole list for vias
77  if( ! aGenerateNPTH_list ) // vias are always plated !
78  {
79  for( auto track : m_pcb->Tracks() )
80  {
81  if( track->Type() != PCB_VIA_T )
82  continue;
83 
84  PCB_VIA* via = static_cast<PCB_VIA*>( track );
85  int hole_sz = via->GetDrillValue();
86 
87  if( hole_sz == 0 ) // Should not occur.
88  continue;
89 
90  new_hole.m_ItemParent = via;
91 
92  if( aLayerPair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
94  else
96 
97  new_hole.m_Tool_Reference = -1; // Flag value for Not initialized
98  new_hole.m_Hole_Orient = 0;
99  new_hole.m_Hole_Diameter = hole_sz;
100  new_hole.m_Hole_NotPlated = false;
101  new_hole.m_Hole_Size.x = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;
102 
103  new_hole.m_Hole_Shape = 0; // hole shape: round
104  new_hole.m_Hole_Pos = via->GetStart();
105 
106  via->LayerPair( &new_hole.m_Hole_Top_Layer, &new_hole.m_Hole_Bottom_Layer );
107 
108  // LayerPair() returns params with m_Hole_Bottom_Layer > m_Hole_Top_Layer
109  // Remember: top layer = 0 and bottom layer = 31 for through hole vias
110  // Any captured via should be from aLayerPair.first to aLayerPair.second exactly.
111  if( new_hole.m_Hole_Top_Layer != aLayerPair.first ||
112  new_hole.m_Hole_Bottom_Layer != aLayerPair.second )
113  continue;
114 
115  m_holeListBuffer.push_back( new_hole );
116  }
117  }
118 
119  if( aLayerPair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
120  {
121  // add holes for thru hole pads
122  for( FOOTPRINT* footprint : m_pcb->Footprints() )
123  {
124  for( PAD* pad : footprint->Pads() )
125  {
126  if( !m_merge_PTH_NPTH )
127  {
128  if( !aGenerateNPTH_list && pad->GetAttribute() == PAD_ATTRIB::NPTH )
129  continue;
130 
131  if( aGenerateNPTH_list && pad->GetAttribute() != PAD_ATTRIB::NPTH )
132  continue;
133  }
134 
135  if( pad->GetDrillSize().x == 0 )
136  continue;
137 
138  new_hole.m_ItemParent = pad;
139  new_hole.m_Hole_NotPlated = (pad->GetAttribute() == PAD_ATTRIB::NPTH);
140  new_hole.m_HoleAttribute = new_hole.m_Hole_NotPlated
143  new_hole.m_Tool_Reference = -1; // Flag is: Not initialized
144  new_hole.m_Hole_Orient = pad->GetOrientation();
145  new_hole.m_Hole_Shape = 0; // hole shape: round
146  new_hole.m_Hole_Diameter = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
147  new_hole.m_Hole_Size.x = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;
148 
149  if( pad->GetDrillShape() != PAD_DRILL_SHAPE_CIRCLE )
150  new_hole.m_Hole_Shape = 1; // oval flag set
151 
152  new_hole.m_Hole_Size = pad->GetDrillSize();
153  new_hole.m_Hole_Pos = pad->GetPosition(); // hole position
154  new_hole.m_Hole_Bottom_Layer = B_Cu;
155  new_hole.m_Hole_Top_Layer = F_Cu; // pad holes are through holes
156  m_holeListBuffer.push_back( new_hole );
157  }
158  }
159  }
160 
161  // Sort holes per increasing diameter value (and for each dimater, by position)
162  sort( m_holeListBuffer.begin(), m_holeListBuffer.end(), cmpHoleSorting );
163 
164  // build the tool list
165  int last_hole = -1; // Set to not initialized (this is a value not used
166  // for m_holeListBuffer[ii].m_Hole_Diameter)
167  bool last_notplated_opt = false;
169 
170  DRILL_TOOL new_tool( 0, false );
171  unsigned jj;
172 
173  for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
174  {
175  if( m_holeListBuffer[ii].m_Hole_Diameter != last_hole
176  || m_holeListBuffer[ii].m_Hole_NotPlated != last_notplated_opt
178  || m_holeListBuffer[ii].m_HoleAttribute != last_attribute
179 #endif
180  )
181  {
182  new_tool.m_Diameter = m_holeListBuffer[ii].m_Hole_Diameter;
183  new_tool.m_Hole_NotPlated = m_holeListBuffer[ii].m_Hole_NotPlated;
184  new_tool.m_HoleAttribute = m_holeListBuffer[ii].m_HoleAttribute;
185  m_toolListBuffer.push_back( new_tool );
186  last_hole = new_tool.m_Diameter;
187  last_notplated_opt = new_tool.m_Hole_NotPlated;
188  last_attribute = new_tool.m_HoleAttribute;
189  }
190 
191  jj = m_toolListBuffer.size();
192 
193  if( jj == 0 )
194  continue; // Should not occurs
195 
196  m_holeListBuffer[ii].m_Tool_Reference = jj; // Tool value Initialized (value >= 1)
197 
198  m_toolListBuffer.back().m_TotalCount++;
199 
200  if( m_holeListBuffer[ii].m_Hole_Shape )
201  m_toolListBuffer.back().m_OvalCount++;
202  }
203 }
204 
205 
206 std::vector<DRILL_LAYER_PAIR> GENDRILL_WRITER_BASE::getUniqueLayerPairs() const
207 {
208  wxASSERT( m_pcb );
209 
210  static const KICAD_T interesting_stuff_to_collect[] = {
211  PCB_VIA_T,
212  EOT
213  };
214 
215  PCB_TYPE_COLLECTOR vias;
216 
217  vias.Collect( m_pcb, interesting_stuff_to_collect );
218 
219  std::set< DRILL_LAYER_PAIR > unique;
220 
221  DRILL_LAYER_PAIR layer_pair;
222 
223  for( int i = 0; i < vias.GetCount(); ++i )
224  {
225  PCB_VIA* v = static_cast<PCB_VIA*>( vias[i] );
226 
227  v->LayerPair( &layer_pair.first, &layer_pair.second );
228 
229  // only make note of blind buried.
230  // thru hole is placed unconditionally as first in fetched list.
231  if( layer_pair != DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
232  {
233  unique.insert( layer_pair );
234  }
235  }
236 
237  std::vector<DRILL_LAYER_PAIR> ret;
238 
239  ret.emplace_back( F_Cu, B_Cu ); // always first in returned list
240 
241  for( std::set<DRILL_LAYER_PAIR>::const_iterator it = unique.begin(); it != unique.end(); ++it )
242  ret.push_back( *it );
243 
244  return ret;
245 }
246 
247 
248 const std::string GENDRILL_WRITER_BASE::layerName( PCB_LAYER_ID aLayer ) const
249 {
250  // Generic names here.
251  switch( aLayer )
252  {
253  case F_Cu:
254  return "front";
255  case B_Cu:
256  return "back";
257  default:
258  return StrPrintf( "in%d", aLayer );
259  }
260 }
261 
262 
264 {
265  std::string ret = layerName( aPair.first );
266  ret += '-';
267  ret += layerName( aPair.second );
268 
269  return ret;
270 }
271 
272 
274  bool aMerge_PTH_NPTH ) const
275 {
276  wxASSERT( m_pcb );
277 
278  wxString extend;
279 
280  if( aNPTH )
281  extend = "-NPTH";
282  else if( aPair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
283  {
284  if( !aMerge_PTH_NPTH )
285  extend = "-PTH";
286  // if merged, extend with nothing
287  }
288  else
289  {
290  extend += '-';
291  extend += layerPairName( aPair );
292  }
293 
294  wxFileName fn = m_pcb->GetFileName();
295 
296  fn.SetName( fn.GetName() + extend );
297  fn.SetExt( m_drillFileExtension );
298 
299  wxString ret = fn.GetFullName();
300 
301  return ret;
302 }
303 
304 void GENDRILL_WRITER_BASE::CreateMapFilesSet( const wxString& aPlotDirectory,
305  REPORTER * aReporter )
306 {
307  wxFileName fn;
308  wxString msg;
309 
310  std::vector<DRILL_LAYER_PAIR> hole_sets = getUniqueLayerPairs();
311 
312  // append a pair representing the NPTH set of holes, for separate drill files.
313  if( !m_merge_PTH_NPTH )
314  hole_sets.emplace_back( F_Cu, B_Cu );
315 
316  for( std::vector<DRILL_LAYER_PAIR>::const_iterator it = hole_sets.begin();
317  it != hole_sets.end(); ++it )
318  {
319  DRILL_LAYER_PAIR pair = *it;
320  // For separate drill files, the last layer pair is the NPTH drill file.
321  bool doing_npth = m_merge_PTH_NPTH ? false : ( it == hole_sets.end() - 1 );
322 
323  buildHolesList( pair, doing_npth );
324 
325  // The file is created if it has holes, or if it is the non plated drill file
326  // to be sure the NPTH file is up to date in separate files mode.
327  // Also a PTH drill file is always created, to be sure at least one plated hole drill file
328  // is created (do not create any PTH drill file can be seen as not working drill generator).
329  if( getHolesCount() > 0 || doing_npth || pair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
330  {
331  fn = getDrillFileName( pair, doing_npth, m_merge_PTH_NPTH );
332  fn.SetPath( aPlotDirectory );
333 
334  fn.SetExt( wxEmptyString ); // Will be added by GenDrillMap
335  wxString fullfilename = fn.GetFullPath() + wxT( "-drl_map" );
336  fullfilename << wxT(".") << GetDefaultPlotExtension( m_mapFileFmt );
337 
338  bool success = genDrillMapFile( fullfilename, m_mapFileFmt );
339 
340  if( ! success )
341  {
342  if( aReporter )
343  {
344  msg.Printf( _( "Failed to create file '%s'." ), fullfilename );
345  aReporter->Report( msg, RPT_SEVERITY_ERROR );
346  }
347 
348  return;
349  }
350  else
351  {
352  if( aReporter )
353  {
354  msg.Printf( _( "Created file '%s'." ), fullfilename );
355  aReporter->Report( msg, RPT_SEVERITY_ACTION );
356  }
357  }
358  }
359  }
360 }
361 
362 
364  DRILL_LAYER_PAIR aLayerPair, TYPE_FILE aHoleType,
365  bool aCompatNCdrill ) const
366 {
367 // Build a wxString containing the .FileFunction attribute for drill files.
368 // %TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH][Blind][Buried],Drill[Route][Mixed]*%
369  wxString text;
370 
371  if( aCompatNCdrill )
372  text = "; #@! ";
373  else
374  text = "%";
375 
376  text << "TF.FileFunction,";
377 
378  if( aHoleType == NPTH_FILE )
379  text << "NonPlated,";
380  else if( aHoleType == MIXED_FILE ) // only for Excellon format
381  text << "MixedPlating,";
382  else
383  text << "Plated,";
384 
385  int layer1 = aLayerPair.first;
386  int layer2 = aLayerPair.second;
387  // In Gerber files, layers num are 1 to copper layer count instead of F_Cu to B_Cu
388  // (0 to copper layer count-1)
389  // Note also for a n copper layers board, gerber layers num are 1 ... n
390  layer1 += 1;
391 
392  if( layer2 == B_Cu )
393  layer2 = m_pcb->GetCopperLayerCount();
394  else
395  layer2 += 1;
396 
397  text << layer1 << "," << layer2;
398 
399  // Now add PTH or NPTH or Blind or Buried attribute
400  int toplayer = 1;
401  int bottomlayer = m_pcb->GetCopperLayerCount();
402 
403  if( aHoleType == NPTH_FILE )
404  text << ",NPTH";
405  else if( aHoleType == MIXED_FILE ) // only for Excellon format
406  {
407  // write nothing
408  }
409  else if( layer1 == toplayer && layer2 == bottomlayer )
410  text << ",PTH";
411  else if( layer1 == toplayer || layer2 == bottomlayer )
412  text << ",Blind";
413  else
414  text << ",Buried";
415 
416  // In NC drill file, these previous parameters should be enough:
417  if( aCompatNCdrill )
418  return text;
419 
420 
421  // Now add Drill or Route or Mixed:
422  // file containing only round holes have Drill attribute
423  // file containing only oblong holes have Routed attribute
424  // file containing both holes have Mixed attribute
425  bool hasOblong = false;
426  bool hasDrill = false;
427 
428  for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
429  {
430  const HOLE_INFO& hole_descr = m_holeListBuffer[ii];
431 
432  if( hole_descr.m_Hole_Shape ) // m_Hole_Shape not 0 is an oblong hole)
433  hasOblong = true;
434  else
435  hasDrill = true;
436  }
437 
438  if( hasOblong && hasDrill )
439  text << ",Mixed";
440  else if( hasDrill )
441  text << ",Drill";
442  else if( hasOblong )
443  text << ",Route";
444 
445  // else: empty file.
446 
447  // End of .FileFunction attribute:
448  text << "*%";
449 
450  return text;
451 }
BOARD_ITEM * m_ItemParent
virtual const wxString getDrillFileName(DRILL_LAYER_PAIR aPair, bool aNPTH, bool aMerge_PTH_NPTH) const
wxString GetDefaultPlotExtension(PLOT_FORMAT aFormat)
Returns the default plot extension for a format.
void CreateMapFilesSet(const wxString &aPlotDirectory, REPORTER *aReporter=nullptr)
Create the full set of map files for the board, in PS, PDF ...
#define USE_ATTRIB_FOR_HOLES
std::vector< DRILL_LAYER_PAIR > getUniqueLayerPairs() const
Get unique layer pairs by examining the micro and blind_buried vias.
HOLE_ATTRIBUTE m_HoleAttribute
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:70
const wxString & GetFileName() const
Definition: board.h:229
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
search types array terminator (End Of Types)
Definition: typeinfo.h:81
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition: typeinfo.h:77
int StrPrintf(std::string *result, const char *format,...)
This is like sprintf() but the output is appended to a std::string instead of to a character array.
Definition: richio.cpp:79
std::vector< DRILL_TOOL > m_toolListBuffer
int GetCount() const
Return the number of objects in the list.
Definition: collector.h:82
like PAD_PTH, but not plated
const std::string layerPairName(DRILL_LAYER_PAIR aPair) const
FOOTPRINTS & Footprints()
Definition: board.h:234
helper classes to handle hole info for drill files generators.
const wxString BuildFileFunctionAttributeString(DRILL_LAYER_PAIR aLayerPair, TYPE_FILE aHoleType, bool aCompatNCdrill=false) const
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...
Definition: pcb_track.cpp:434
#define _(s)
Handle hole which must be drilled (diameter, position and layers).
std::pair< PCB_LAYER_ID, PCB_LAYER_ID > DRILL_LAYER_PAIR
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:609
void buildHolesList(DRILL_LAYER_PAIR aLayerPair, bool aGenerateNPTH_list)
Create the list of holes and tools for a given board.
PCB_LAYER_ID m_Hole_Bottom_Layer
HOLE_ATTRIBUTE m_HoleAttribute
PCB_LAYER_ID m_Hole_Top_Layer
const std::string layerName(PCB_LAYER_ID aLayer) const
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:65
Definition: layer_ids.h:71
int GetCopperLayerCount() const
Definition: board.cpp:455
bool genDrillMapFile(const wxString &aFullFileName, PLOT_FORMAT aFormat)
Plot a map of drill marks for holes.
static bool cmpHoleSorting(const HOLE_INFO &a, const HOLE_INFO &b)
class PCB_VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:96
Collect all BOARD_ITEM objects of a given set of KICAD_T type(s).
Definition: collectors.h:614
Definition: pad.h:57
std::vector< HOLE_INFO > m_holeListBuffer
TRACKS & Tracks()
Definition: board.h:231