KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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 The 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#include <richio.h>
33
35
36
37/* Helper function for sorting hole list.
38 * Compare function used for sorting holes type type:
39 * plated then not plated
40 * then by increasing diameter value
41 * then by attribute type (vias, pad, mechanical)
42 * then by X then Y position
43 */
44static bool cmpHoleSorting( const HOLE_INFO& a, const HOLE_INFO& b )
45{
47 return b.m_Hole_NotPlated;
48
51
52 // At this point (same diameter, same plated type), group by attribute
53 // type (via, pad, mechanical, although currently only not plated pads are mechanical)
56
57 // At this point (same diameter, same type), sort by X then Y position.
58 // This is optimal for drilling and make the file reproducible as long as holes
59 // have not changed, even if the data order has changed.
60 if( a.m_Hole_Pos.x != b.m_Hole_Pos.x )
61 return a.m_Hole_Pos.x < b.m_Hole_Pos.x;
62
63 return a.m_Hole_Pos.y < b.m_Hole_Pos.y;
64}
65
66
67void GENDRILL_WRITER_BASE::buildHolesList( DRILL_LAYER_PAIR aLayerPair, 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( PCB_TRACK* 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 ) )
93 new_hole.m_HoleAttribute = HOLE_ATTRIBUTE::HOLE_VIA_THROUGH;
94 else
95 new_hole.m_HoleAttribute = HOLE_ATTRIBUTE::HOLE_VIA_BURIED;
96
97 new_hole.m_Tool_Reference = -1; // Flag value for Not initialized
98 new_hole.m_Hole_Orient = ANGLE_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 new_hole.m_Hole_Filled = via->Padstack().IsFilled().value_or( false );
109 new_hole.m_Hole_Capped = via->Padstack().IsCapped().value_or( false );
110
111 new_hole.m_Hole_Top_Covered = via->Padstack().IsCovered( new_hole.m_Hole_Top_Layer ).value_or( false );
112 new_hole.m_Hole_Bot_Covered = via->Padstack().IsCovered( new_hole.m_Hole_Bottom_Layer ).value_or( false );
113
114 new_hole.m_Hole_Top_Plugged = via->Padstack().IsPlugged( new_hole.m_Hole_Top_Layer ).value_or( false );
115 new_hole.m_Hole_Bot_Plugged = via->Padstack().IsPlugged( new_hole.m_Hole_Bottom_Layer ).value_or( false );
116
117 new_hole.m_Hole_Top_Tented = via->Padstack().IsTented( new_hole.m_Hole_Top_Layer ).value_or( false );
118 new_hole.m_Hole_Bot_Tented = via->Padstack().IsTented( new_hole.m_Hole_Bottom_Layer ).value_or( false );
119
120 // LayerPair() returns params with m_Hole_Bottom_Layer > m_Hole_Top_Layer
121 // Remember: top layer = 0 and bottom layer = 31 for through hole vias
122 // Any captured via should be from aLayerPair.first to aLayerPair.second exactly.
123 if( new_hole.m_Hole_Top_Layer != aLayerPair.first || new_hole.m_Hole_Bottom_Layer != aLayerPair.second )
124 continue;
125
126 m_holeListBuffer.push_back( new_hole );
127 }
128 }
129
130 if( aLayerPair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
131 {
132 // add holes for thru hole pads
133 for( FOOTPRINT* footprint : m_pcb->Footprints() )
134 {
135 for( PAD* pad : footprint->Pads() )
136 {
137 if( !m_merge_PTH_NPTH )
138 {
139 if( !aGenerateNPTH_list && pad->GetAttribute() == PAD_ATTRIB::NPTH )
140 continue;
141
142 if( aGenerateNPTH_list && pad->GetAttribute() != PAD_ATTRIB::NPTH )
143 continue;
144 }
145
146 if( pad->GetDrillSize().x == 0 )
147 continue;
148
149 new_hole.m_ItemParent = pad;
150 new_hole.m_Hole_NotPlated = ( pad->GetAttribute() == PAD_ATTRIB::NPTH );
151
152 if( new_hole.m_Hole_NotPlated )
153 new_hole.m_HoleAttribute = HOLE_ATTRIBUTE::HOLE_MECHANICAL;
154 else
155 {
156 if( pad->GetProperty() == PAD_PROP::CASTELLATED )
157 new_hole.m_HoleAttribute = HOLE_ATTRIBUTE::HOLE_PAD_CASTELLATED;
158 else if( pad->GetProperty() == PAD_PROP::PRESSFIT )
159 new_hole.m_HoleAttribute = HOLE_ATTRIBUTE::HOLE_PAD_PRESSFIT;
160 else
161 new_hole.m_HoleAttribute = HOLE_ATTRIBUTE::HOLE_PAD;
162 }
163
164 new_hole.m_Tool_Reference = -1; // Flag is: Not initialized
165 new_hole.m_Hole_Orient = pad->GetOrientation();
166 new_hole.m_Hole_Shape = 0; // hole shape: round
167 new_hole.m_Hole_Diameter = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
168 new_hole.m_Hole_Size.x = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;
169
170 // Convert oblong holes that are actually circular into drill hits
171 if( pad->GetDrillShape() != PAD_DRILL_SHAPE::CIRCLE && pad->GetDrillSizeX() != pad->GetDrillSizeY() )
172 new_hole.m_Hole_Shape = 1; // oval flag set
173
174 new_hole.m_Hole_Size = pad->GetDrillSize();
175 new_hole.m_Hole_Pos = pad->GetPosition(); // hole position
176 new_hole.m_Hole_Bottom_Layer = B_Cu;
177 new_hole.m_Hole_Top_Layer = F_Cu; // pad holes are through holes
178 m_holeListBuffer.push_back( new_hole );
179 }
180 }
181 }
182
183 // Sort holes per increasing diameter value (and for each dimater, by position)
184 sort( m_holeListBuffer.begin(), m_holeListBuffer.end(), cmpHoleSorting );
185
186 // build the tool list
187 int last_hole = -1; // Set to not initialized (this is a value not used
188 // for m_holeListBuffer[ii].m_Hole_Diameter)
189 bool last_notplated_opt = false;
190 HOLE_ATTRIBUTE last_attribute = HOLE_ATTRIBUTE::HOLE_UNKNOWN;
191
192 DRILL_TOOL new_tool( 0, false );
193 unsigned jj;
194
195 for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
196 {
197 if( m_holeListBuffer[ii].m_Hole_Diameter != last_hole
198 || m_holeListBuffer[ii].m_Hole_NotPlated != last_notplated_opt
200 || m_holeListBuffer[ii].m_HoleAttribute != last_attribute
201#endif
202 )
203 {
204 new_tool.m_Diameter = m_holeListBuffer[ii].m_Hole_Diameter;
205 new_tool.m_Hole_NotPlated = m_holeListBuffer[ii].m_Hole_NotPlated;
206 new_tool.m_HoleAttribute = m_holeListBuffer[ii].m_HoleAttribute;
207 m_toolListBuffer.push_back( new_tool );
208 last_hole = new_tool.m_Diameter;
209 last_notplated_opt = new_tool.m_Hole_NotPlated;
210 last_attribute = new_tool.m_HoleAttribute;
211 }
212
213 jj = m_toolListBuffer.size();
214
215 if( jj == 0 )
216 continue; // Should not occurs
217
218 m_holeListBuffer[ii].m_Tool_Reference = jj; // Tool value Initialized (value >= 1)
219
220 m_toolListBuffer.back().m_TotalCount++;
221
222 if( m_holeListBuffer[ii].m_Hole_Shape )
223 m_toolListBuffer.back().m_OvalCount++;
224 }
225}
226
227
228std::vector<DRILL_LAYER_PAIR> GENDRILL_WRITER_BASE::getUniqueLayerPairs() const
229{
230 wxASSERT( m_pcb );
231
233
234 vias.Collect( m_pcb, { PCB_VIA_T } );
235
236 std::set<DRILL_LAYER_PAIR> unique;
237 DRILL_LAYER_PAIR layer_pair;
238
239 for( int i = 0; i < vias.GetCount(); ++i )
240 {
241 PCB_VIA* v = static_cast<PCB_VIA*>( vias[i] );
242
243 v->LayerPair( &layer_pair.first, &layer_pair.second );
244
245 // only make note of blind buried.
246 // thru hole is placed unconditionally as first in fetched list.
247 if( layer_pair != DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
248 unique.insert( layer_pair );
249 }
250
251 std::vector<DRILL_LAYER_PAIR> ret;
252
253 ret.emplace_back( F_Cu, B_Cu ); // always first in returned list
254
255 for( const DRILL_LAYER_PAIR& pair : unique )
256 ret.push_back( pair );
257
258 return ret;
259}
260
261
262const std::string GENDRILL_WRITER_BASE::layerName( PCB_LAYER_ID aLayer ) const
263{
264 // Generic names here.
265 switch( aLayer )
266 {
267 case F_Cu:
268 return "front";
269 case B_Cu:
270 return "back";
271 default:
272 {
273 // aLayer use even values, and the first internal layer (In1) is B_Cu + 2.
274 int ly_id = ( aLayer - B_Cu ) / 2;
275 return StrPrintf( "in%d", ly_id );
276 }
277 }
278}
279
280
282{
283 std::string ret = layerName( aPair.first );
284 ret += '-';
285 ret += layerName( aPair.second );
286
287 return ret;
288}
289
290
292 bool aMerge_PTH_NPTH ) const
293{
294 wxASSERT( m_pcb );
295
296 wxString extend;
297
298 if( aNPTH )
299 {
300 extend = wxT( "-NPTH" );
301 }
302 else if( aPair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
303 {
304 if( !aMerge_PTH_NPTH )
305 extend = wxT( "-PTH" );
306 // if merged, extend with nothing
307 }
308 else
309 {
310 extend += '-';
311 extend += layerPairName( aPair );
312 }
313
314 wxFileName fn = m_pcb->GetFileName();
315
316 fn.SetName( fn.GetName() + extend );
317 fn.SetExt( m_drillFileExtension );
318
319 wxString ret = fn.GetFullName();
320
321 return ret;
322}
323
324
326 IPC4761_FEATURES aFeature ) const
327{
328 wxASSERT( m_pcb );
329
330 wxString extend;
331
332 switch( aFeature )
333 {
334 case IPC4761_FEATURES::FILLED:
335 extend << wxT( "-filling-" );
336 extend << layerPairName( aPair );
337 break;
338 case IPC4761_FEATURES::CAPPED:
339 extend << wxT( "-capping-" );
340 extend << layerPairName( aPair );
341 break;
342 case IPC4761_FEATURES::COVERED_BACK:
343 extend << wxT( "-covering-" );
344 extend << layerName( aPair.second );
345 break;
346 case IPC4761_FEATURES::COVERED_FRONT:
347 extend << wxT( "-covering-" );
348 extend << layerName( aPair.first );
349 break;
350 case IPC4761_FEATURES::PLUGGED_BACK:
351 extend << wxT( "-plugging-" );
352 extend << layerName( aPair.second );
353 break;
354 case IPC4761_FEATURES::PLUGGED_FRONT:
355 extend << wxT( "-plugging-" );
356 extend << layerName( aPair.first );
357 break;
358 case IPC4761_FEATURES::TENTED_BACK:
359 extend << wxT( "-tenting-" );
360 extend << layerName( aPair.second );
361 break;
362 case IPC4761_FEATURES::TENTED_FRONT:
363 extend << wxT( "-tenting-" );
364 extend << layerName( aPair.first );
365 break;
366 }
367
368 wxFileName fn = m_pcb->GetFileName();
369
370 fn.SetName( fn.GetName() + extend );
371 fn.SetExt( m_drillFileExtension );
372
373 wxString ret = fn.GetFullName();
374
375 return ret;
376}
377
378
379bool GENDRILL_WRITER_BASE::CreateMapFilesSet( const wxString& aPlotDirectory, REPORTER * aReporter )
380{
381 wxFileName fn;
382 wxString msg;
383
384 std::vector<DRILL_LAYER_PAIR> hole_sets = getUniqueLayerPairs();
385
386 // append a pair representing the NPTH set of holes, for separate drill files.
387 if( !m_merge_PTH_NPTH )
388 hole_sets.emplace_back( F_Cu, B_Cu );
389
390 for( std::vector<DRILL_LAYER_PAIR>::const_iterator it = hole_sets.begin(); it != hole_sets.end(); ++it )
391 {
392 DRILL_LAYER_PAIR pair = *it;
393 // For separate drill files, the last layer pair is the NPTH drill file.
394 bool doing_npth = m_merge_PTH_NPTH ? false : ( it == hole_sets.end() - 1 );
395
396 buildHolesList( pair, doing_npth );
397
398 // The file is created if it has holes, or if it is the non plated drill file
399 // to be sure the NPTH file is up to date in separate files mode.
400 // Also a PTH drill file is always created, to be sure at least one plated hole drill file
401 // is created (do not create any PTH drill file can be seen as not working drill generator).
402 if( getHolesCount() > 0 || doing_npth || pair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
403 {
405 fn.SetPath( aPlotDirectory );
406
407 fn.SetExt( wxEmptyString ); // Will be added by GenDrillMap
408 wxString fullfilename = fn.GetFullPath() + wxT( "-drl_map" );
409 fullfilename << wxT(".") << GetDefaultPlotExtension( m_mapFileFmt );
410
411 bool success = genDrillMapFile( fullfilename, m_mapFileFmt );
412
413 if( ! success )
414 {
415 if( aReporter )
416 {
417 msg.Printf( _( "Failed to create file '%s'." ), fullfilename );
418 aReporter->Report( msg, RPT_SEVERITY_ERROR );
419 }
420
421 return false;
422 }
423 else
424 {
425 if( aReporter )
426 {
427 msg.Printf( _( "Created file '%s'." ), fullfilename );
428 aReporter->Report( msg, RPT_SEVERITY_ACTION );
429 }
430 }
431 }
432 }
433
434 return true;
435}
436
437
439 TYPE_FILE aHoleType,
440 bool aCompatNCdrill ) const
441{
442// Build a wxString containing the .FileFunction attribute for drill files.
443// %TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH][Blind][Buried],Drill[Route][Mixed]*%
444 wxString text;
445
446 if( aCompatNCdrill )
447 text = wxT( "; #@! " );
448 else
449 text = wxT( "%" );
450
451 text << wxT( "TF.FileFunction," );
452
453 if( aHoleType == NPTH_FILE )
454 text << wxT( "NonPlated," );
455 else if( aHoleType == MIXED_FILE ) // only for Excellon format
456 text << wxT( "MixedPlating," );
457 else
458 text << wxT( "Plated," );
459
460 int layer1 = aLayerPair.first;
461 int layer2 = aLayerPair.second;
462 // In Gerber files, layers num are 1 to copper layer count instead of F_Cu to B_Cu
463 // (0 to copper layer count-1)
464 // Note also for a n copper layers board, gerber layers num are 1 ... n
465 //
466 // Copper layers use even values, so the layer id in file is
467 // (Copper layer id) /2 + 1 if layer is not B_Cu
468 if( layer1 == F_Cu )
469 layer1 = 1;
470 else
471 layer1 = ( ( layer1 - B_Cu ) / 2 ) + 1;
472
473 if( layer2 == B_Cu )
474 layer2 = m_pcb->GetCopperLayerCount();
475 else
476 layer2 = ( ( layer2 - B_Cu ) / 2) + 1;
477
478 text << layer1 << wxT( "," ) << layer2;
479
480 // Now add PTH or NPTH or Blind or Buried attribute
481 int toplayer = 1;
482 int bottomlayer = m_pcb->GetCopperLayerCount();
483
484 if( aHoleType == NPTH_FILE )
485 text << wxT( ",NPTH" );
486 else if( aHoleType == MIXED_FILE ) // only for Excellon format
487 ; // write nothing
488 else if( layer1 == toplayer && layer2 == bottomlayer )
489 text << wxT( ",PTH" );
490 else if( layer1 == toplayer || layer2 == bottomlayer )
491 text << wxT( ",Blind" );
492 else
493 text << wxT( ",Buried" );
494
495 // In NC drill file, these previous parameters should be enough:
496 if( aCompatNCdrill )
497 return text;
498
499
500 // Now add Drill or Route or Mixed:
501 // file containing only round holes have Drill attribute
502 // file containing only oblong holes have Routed attribute
503 // file containing both holes have Mixed attribute
504 bool hasOblong = false;
505 bool hasDrill = false;
506
507 for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
508 {
509 const HOLE_INFO& hole_descr = m_holeListBuffer[ii];
510
511 if( hole_descr.m_Hole_Shape ) // m_Hole_Shape not 0 is an oblong hole)
512 hasOblong = true;
513 else
514 hasDrill = true;
515 }
516
517 if( hasOblong && hasDrill )
518 text << wxT( ",Mixed" );
519 else if( hasDrill )
520 text << wxT( ",Drill" );
521 else if( hasOblong )
522 text << wxT( ",Rout" );
523
524 // else: empty file.
525
526 // End of .FileFunction attribute:
527 text << wxT( "*%" );
528
529 return text;
530}
int GetCopperLayerCount() const
Definition: board.cpp:859
const FOOTPRINTS & Footprints() const
Definition: board.h:358
const TRACKS & Tracks() const
Definition: board.h:356
const wxString & GetFileName() const
Definition: board.h:354
int GetCount() const
Return the number of objects in the list.
Definition: collector.h:83
HOLE_ATTRIBUTE m_HoleAttribute
std::vector< DRILL_LAYER_PAIR > getUniqueLayerPairs() const
Get unique layer pairs by examining the micro and blind_buried vias.
virtual const wxString getDrillFileName(DRILL_LAYER_PAIR aPair, bool aNPTH, bool aMerge_PTH_NPTH) const
void buildHolesList(DRILL_LAYER_PAIR aLayerPair, bool aGenerateNPTH_list)
Create the list of holes and tools for a given board.
const wxString BuildFileFunctionAttributeString(DRILL_LAYER_PAIR aLayerPair, TYPE_FILE aHoleType, bool aCompatNCdrill=false) const
virtual const wxString getProtectionFileName(DRILL_LAYER_PAIR aPair, IPC4761_FEATURES aFeature) const
std::vector< HOLE_INFO > m_holeListBuffer
bool genDrillMapFile(const wxString &aFullFileName, PLOT_FORMAT aFormat)
Plot a map of drill marks for holes.
std::vector< DRILL_TOOL > m_toolListBuffer
const std::string layerPairName(DRILL_LAYER_PAIR aPair) const
bool CreateMapFilesSet(const wxString &aPlotDirectory, REPORTER *aReporter=nullptr)
Create the full set of map files for the board, in PS, PDF ... format (use SetMapFileFormat() to sele...
const std::string layerName(PCB_LAYER_ID aLayer) const
Handle hole which must be drilled (diameter, position and layers).
PCB_LAYER_ID m_Hole_Bottom_Layer
PCB_LAYER_ID m_Hole_Top_Layer
HOLE_ATTRIBUTE m_HoleAttribute
BOARD_ITEM * m_ItemParent
Definition: pad.h:54
Collect all BOARD_ITEM objects of a given set of KICAD_T type(s).
Definition: collectors.h:521
void Collect(BOARD_ITEM *aBoard, const std::vector< KICAD_T > &aTypes)
Collect BOARD_ITEM objects using this class's Inspector method, which does the collection.
Definition: collectors.cpp:541
void LayerPair(PCB_LAYER_ID *top_layer, PCB_LAYER_ID *bottom_layer) const
Return the 2 layers used by the via (the via actually uses all layers between these 2 layers)
Definition: pcb_track.cpp:1367
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition: reporter.h:102
wxString GetDefaultPlotExtension(PLOT_FORMAT aFormat)
Return the default plot extension for a format.
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition: eda_angle.h:411
static bool cmpHoleSorting(const HOLE_INFO &a, const HOLE_INFO &b)
helper classes to handle hole info for drill files generators.
#define USE_ATTRIB_FOR_HOLES
std::pair< PCB_LAYER_ID, PCB_LAYER_ID > DRILL_LAYER_PAIR
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:60
@ B_Cu
Definition: layer_ids.h:65
@ F_Cu
Definition: layer_ids.h:64
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_ACTION
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:71
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:97