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