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-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
33 #include <gendrill_file_writer_base.h>
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 */
cmpHoleSorting(const HOLE_INFO & a,const HOLE_INFO & b)43 static bool cmpHoleSorting( const HOLE_INFO& a, const HOLE_INFO& b )
44 {
45 if( a.m_Hole_NotPlated != b.m_Hole_NotPlated )
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
buildHolesList(DRILL_LAYER_PAIR aLayerPair,bool aGenerateNPTH_list)66 void GENDRILL_WRITER_BASE::buildHolesList( DRILL_LAYER_PAIR aLayerPair,
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 ) )
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 = 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
141 ? HOLE_ATTRIBUTE::HOLE_MECHANICAL
142 : HOLE_ATTRIBUTE::HOLE_PAD;
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;
168 HOLE_ATTRIBUTE last_attribute = HOLE_ATTRIBUTE::HOLE_UNKNOWN;
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
177 #if USE_ATTRIB_FOR_HOLES
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
getUniqueLayerPairs() const206 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
layerName(PCB_LAYER_ID aLayer) const248 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
layerPairName(DRILL_LAYER_PAIR aPair) const263 const std::string GENDRILL_WRITER_BASE::layerPairName( DRILL_LAYER_PAIR aPair ) const
264 {
265 std::string ret = layerName( aPair.first );
266 ret += '-';
267 ret += layerName( aPair.second );
268
269 return ret;
270 }
271
272
getDrillFileName(DRILL_LAYER_PAIR aPair,bool aNPTH,bool aMerge_PTH_NPTH) const273 const wxString GENDRILL_WRITER_BASE::getDrillFileName( DRILL_LAYER_PAIR aPair, bool aNPTH,
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
CreateMapFilesSet(const wxString & aPlotDirectory,REPORTER * aReporter)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
BuildFileFunctionAttributeString(DRILL_LAYER_PAIR aLayerPair,TYPE_FILE aHoleType,bool aCompatNCdrill) const363 const wxString GENDRILL_WRITER_BASE::BuildFileFunctionAttributeString(
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 }
452