KiCad PCB EDA Suite
Loading...
Searching...
No Matches
gendrill_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, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <board.h>
24#include <footprint.h>
25#include <pad.h>
26#include <pcb_track.h>
27#include <collectors.h>
28#include <macros.h>
29#include <reporter.h>
30#include <string_utils.h>
34#include <pcbplot.h>
35#include <pcb_painter.h>
36#include <pcb_shape.h>
37#include <fmt.h>
38#include <wx/ffile.h>
39#include <reporter.h>
40
41#include <set>
42
45
46
47/* Helper function for sorting hole list.
48 * Compare function used for sorting holes type type:
49 * plated then not plated
50 * then by increasing diameter value
51 * then by attribute type (vias, pad, mechanical)
52 * then by X then Y position
53 */
54static bool cmpHoleSorting( const DRILL_OPERATION& a, const DRILL_OPERATION& b )
55{
56 if( a.m_NotPlated != b.m_NotPlated )
57 return b.m_NotPlated;
58
59 if( a.m_Diameter != b.m_Diameter )
60 return a.m_Diameter < b.m_Diameter;
61
62 // At this point (same diameter, same plated type), group by attribute
63 // type (via, pad, mechanical, although currently only not plated pads are mechanical)
64 if( a.m_Attribute != b.m_Attribute )
65 return a.m_Attribute < b.m_Attribute;
66
67 // At this point (same diameter, same type), sort by X then Y position.
68 // This is optimal for drilling and make the file reproducible as long as holes
69 // have not changed, even if the data order has changed.
70 if( a.m_Position.x != b.m_Position.x )
71 return a.m_Position.x < b.m_Position.x;
72
73 return a.m_Position.y < b.m_Position.y;
74}
75
76
77void GENDRILL_WRITER_BASE::buildHolesList( const DRILL_SPAN& aSpan, bool aGenerateNPTH_list )
78{
79 m_holeListBuffer.clear();
80 m_toolListBuffer.clear();
82
83 DRILL_QUERY query;
84 query.m_Span = aSpan;
85 query.m_NonPlatedOnly = aGenerateNPTH_list;
87
89
90 sort( m_holeListBuffer.begin(), m_holeListBuffer.end(), cmpHoleSorting );
91
92 m_holeToolReferences.resize( m_holeListBuffer.size(), -1 );
93
94 int last_hole = -1;
95 bool last_notplated_opt = false;
97
98 DRILL_TOOL new_tool( 0, false );
99 unsigned jj;
100
101 for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
102 {
103 const DRILL_OPERATION& hole = m_holeListBuffer[ii];
104
105 if( hole.m_Diameter != last_hole
106 || hole.m_NotPlated != last_notplated_opt
108 || hole.m_Attribute != last_attribute
109#endif
110 )
111 {
112 new_tool.m_Diameter = hole.m_Diameter;
113 new_tool.m_Hole_NotPlated = hole.m_NotPlated;
114 new_tool.m_HoleAttribute = hole.m_Attribute;
115 m_toolListBuffer.push_back( new_tool );
116 last_hole = new_tool.m_Diameter;
117 last_notplated_opt = new_tool.m_Hole_NotPlated;
118 last_attribute = new_tool.m_HoleAttribute;
119 }
120
121 jj = m_toolListBuffer.size();
122
123 if( jj == 0 )
124 continue; // Should not occurs
125
126 m_holeToolReferences[ii] = jj;
127
128 m_toolListBuffer.back().m_TotalCount++;
129
130 if( hole.m_IsSlot )
131 m_toolListBuffer.back().m_OvalCount++;
132
133 if( hole.IsBackdrill() )
134 {
135 m_toolListBuffer.back().m_IsBackdrill = true;
136
137 if( hole.m_StubLength.has_value() )
138 {
139 int stub = *hole.m_StubLength;
140
141 if( !m_toolListBuffer.back().m_MinStubLength.has_value()
142 || stub < *m_toolListBuffer.back().m_MinStubLength )
143 {
144 m_toolListBuffer.back().m_MinStubLength = stub;
145 }
146
147 if( !m_toolListBuffer.back().m_MaxStubLength.has_value()
148 || stub > *m_toolListBuffer.back().m_MaxStubLength )
149 {
150 m_toolListBuffer.back().m_MaxStubLength = stub;
151 }
152 }
153 }
154
159 || hole.IsBackdrill() )
160 m_toolListBuffer.back().m_HasPostMachining = true;
161 }
162}
163
164
165const std::string GENDRILL_WRITER_BASE::layerName( PCB_LAYER_ID aLayer ) const
166{
167 // Generic names here.
168 switch( aLayer )
169 {
170 case F_Cu:
171 return "front";
172 case B_Cu:
173 return "back";
174 default:
175 {
176 // aLayer use even values, and the first internal layer (In1) is B_Cu + 2.
177 int ly_id = ( aLayer - B_Cu ) / 2;
178 return fmt::format( "in{}", ly_id );
179 }
180 }
181}
182
183
185{
186 std::string ret = layerName( aPair.first );
187 ret += '-';
188 ret += layerName( aPair.second );
189
190 return ret;
191}
192
193
194const wxString GENDRILL_WRITER_BASE::getDrillFileName( const DRILL_SPAN& aSpan, bool aNPTH,
195 bool aMerge_PTH_NPTH ) const
196{
197 wxASSERT( m_pcb );
198
199 wxString extend;
200
201 auto layerIndex = [&]( PCB_LAYER_ID aLayer )
202 {
203 int conventional_layer_num = 1;
204
205 for( PCB_LAYER_ID layer : LSET::AllCuMask( m_pcb->GetCopperLayerCount() ).UIOrder() )
206 {
207 if( layer == aLayer )
208 return conventional_layer_num;
209
210 conventional_layer_num++;
211 }
212
213 return conventional_layer_num;
214 };
215
216 if( aSpan.m_IsBackdrill )
217 {
218 extend.Printf( wxT( "_Backdrills_Drill_%d_%d" ),
219 layerIndex( aSpan.DrillStartLayer() ),
220 layerIndex( aSpan.DrillEndLayer() ) );
221 }
222 else if( aNPTH )
223 {
224 extend = wxT( "-NPTH" );
225 }
226 else if( aSpan.Pair() == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
227 {
228 if( !aMerge_PTH_NPTH )
229 extend = wxT( "-PTH" );
230 // if merged, extend with nothing
231 }
232 else
233 {
234 extend += '-';
235 extend += layerPairName( aSpan.Pair() );
236 }
237
238 wxFileName fn = m_pcb->GetFileName();
239
240 fn.SetName( fn.GetName() + extend );
241 fn.SetExt( m_drillFileExtension );
242
243 wxString ret = fn.GetFullName();
244
245 return ret;
246}
247
248
250 IPC4761_FEATURES aFeature ) const
251{
252 wxASSERT( m_pcb );
253
254 wxString extend;
255
256 switch( aFeature )
257 {
259 extend << wxT( "-filling-" );
260 extend << layerPairName( aSpan.Pair() );
261 break;
263 extend << wxT( "-capping-" );
264 extend << layerPairName( aSpan.Pair() );
265 break;
267 extend << wxT( "-covering-" );
268 extend << layerName( aSpan.Pair().second );
269 break;
271 extend << wxT( "-covering-" );
272 extend << layerName( aSpan.Pair().first );
273 break;
275 extend << wxT( "-plugging-" );
276 extend << layerName( aSpan.Pair().second );
277 break;
279 extend << wxT( "-plugging-" );
280 extend << layerName( aSpan.Pair().first );
281 break;
283 extend << wxT( "-tenting-" );
284 extend << layerName( aSpan.Pair().second );
285 break;
287 extend << wxT( "-tenting-" );
288 extend << layerName( aSpan.Pair().first );
289 break;
290 }
291
292 wxFileName fn = m_pcb->GetFileName();
293
294 fn.SetName( fn.GetName() + extend );
295 fn.SetExt( m_drillFileExtension );
296
297 wxString ret = fn.GetFullName();
298
299 return ret;
300}
301
302
303bool GENDRILL_WRITER_BASE::CreateMapFilesSet( const wxString& aPlotDirectory, REPORTER * aReporter )
304{
305 wxFileName fn;
306 wxString msg;
307
308 std::vector<DRILL_SPAN> hole_sets = EnumerateDrillSpans( *m_pcb );
309
310 if( !m_merge_PTH_NPTH )
311 hole_sets.emplace_back( F_Cu, B_Cu, false, true );
312
313 for( std::vector<DRILL_SPAN>::const_iterator it = hole_sets.begin(); it != hole_sets.end(); ++it )
314 {
315 const DRILL_SPAN& span = *it;
316 bool doing_npth = span.m_IsNonPlatedFile;
317
318 buildHolesList( span, doing_npth );
319
320 if( getHolesCount() > 0 || doing_npth || span.Pair() == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
321 {
323 fn.SetPath( aPlotDirectory );
324
325 fn.SetExt( wxEmptyString ); // Will be added by GenDrillMap
326 wxString fullfilename = fn.GetFullPath() + wxT( "-drl_map" );
327 fullfilename << wxT(".") << GetDefaultPlotExtension( m_mapFileFmt );
328
329 bool success = genDrillMapFile( fullfilename, m_mapFileFmt );
330
331 if( ! success )
332 {
333 if( aReporter )
334 {
335 msg.Printf( _( "Failed to create file '%s'." ), fullfilename );
336 aReporter->Report( msg, RPT_SEVERITY_ERROR );
337 }
338
339 return false;
340 }
341 else
342 {
343 if( aReporter )
344 {
345 msg.Printf( _( "Created file '%s'." ), fullfilename );
346 aReporter->Report( msg, RPT_SEVERITY_ACTION );
347 }
348
349 AddCreatedFile( fullfilename );
350 }
351 }
352 }
353
354 return true;
355}
356
357
359 TYPE_FILE aHoleType,
360 bool aCompatNCdrill ) const
361{
362// Build a wxString containing the .FileFunction attribute for drill files.
363// %TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH][Blind][Buried],Drill[Route][Mixed]*%
364 wxString text;
365
366 if( aCompatNCdrill )
367 text = wxT( "; #@! " );
368 else
369 text = wxT( "%" );
370
371 text << wxT( "TF.FileFunction," );
372
373 if( aSpan.m_IsBackdrill || aHoleType == NPTH_FILE )
374 text << wxT( "NonPlated," );
375 else if( aHoleType == MIXED_FILE ) // only for Excellon format
376 text << wxT( "MixedPlating," );
377 else
378 text << wxT( "Plated," );
379
380 int layer1 = aSpan.Pair().first;
381 int layer2 = aSpan.Pair().second;
382
383 // In Gerber files, layers num are 1 to copper layer count instead of F_Cu to B_Cu
384 // (0 to copper layer count-1)
385 // Note also for a n copper layers board, gerber layers num are 1 ... n
386 //
387 // Copper layers use even values, so the layer id in file is
388 // (Copper layer id) /2 + 1 if layer is not B_Cu
389 if( layer1 == F_Cu )
390 layer1 = 1;
391 else if( layer1 == B_Cu )
392 layer1 = m_pcb->GetCopperLayerCount();
393 else
394 layer1 = ( ( layer1 - B_Cu ) / 2 ) + 1;
395
396 if( layer2 == F_Cu )
397 layer2 = 1;
398 else if( layer2 == B_Cu )
399 layer2 = m_pcb->GetCopperLayerCount();
400 else
401 layer2 = ( ( layer2 - B_Cu ) / 2) + 1;
402
403 // Ensure layer order is from top (smaller layer number) to bottom (bigger layer number)
404 if( layer1 > layer2 )
405 std::swap( layer1, layer2 );
406
407 text << layer1 << wxT( "," ) << layer2;
408
409 // Now add PTH or NPTH or Blind or Buried attribute
410 int toplayer = 1;
411 int bottomlayer = m_pcb->GetCopperLayerCount();
412
413 if( aSpan.m_IsBackdrill )
414 text << wxT( ",Blind" );
415 else if( aHoleType == NPTH_FILE )
416 text << wxT( ",NPTH" );
417 else if( aHoleType == MIXED_FILE ) // only for Excellon format
418 ; // write nothing
419 else if( layer1 == toplayer && layer2 == bottomlayer )
420 text << wxT( ",PTH" );
421 else if( layer1 == toplayer || layer2 == bottomlayer )
422 text << wxT( ",Blind" );
423 else
424 text << wxT( ",Buried" );
425
426 // In NC drill file, these previous parameters should be enough:
427 if( aCompatNCdrill )
428 return text;
429
430
431 // Now add Drill or Route or Mixed:
432 // file containing only round holes have Drill attribute
433 // file containing only oblong holes have Routed attribute
434 // file containing both holes have Mixed attribute
435 bool hasOblong = false;
436 bool hasDrill = false;
437
438 for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
439 {
440 const DRILL_OPERATION& hole_descr = m_holeListBuffer[ii];
441
442 if( hole_descr.m_IsSlot )
443 hasOblong = true;
444 else
445 hasDrill = true;
446 }
447
448 if( hasOblong && hasDrill )
449 text << wxT( ",Mixed" );
450 else if( hasDrill )
451 text << wxT( ",Drill" );
452 else if( hasOblong )
453 text << wxT( ",Rout" );
454
455 // else: empty file.
456
457 // End of .FileFunction attribute:
458 text << wxT( "*%" );
459
460 return text;
461}
462
463
464/* Conversion utilities - these will be used often in there... */
465inline double diameter_in_inches( double ius )
466{
467 return ius * 0.001 / pcbIUScale.IU_PER_MILS;
468}
469
470
471inline double diameter_in_mm( double ius )
472{
473 return ius / pcbIUScale.IU_PER_MM;
474}
475
476
477// return a pen size to plot markers and having a readable shape
478// clamped to be >= MIN_SIZE_MM to avoid too small line width
479static int getMarkerBestPenSize( int aMarkerDiameter )
480{
481 int bestsize = aMarkerDiameter / 10;
482
483 const double MIN_SIZE_MM = 0.1;
484 bestsize = std::max( bestsize, pcbIUScale.mmToIU( MIN_SIZE_MM ) );
485
486 return bestsize;
487}
488
489
490// return a pen size to plot outlines for oval holes
492{
493 const double SKETCH_LINE_WIDTH_MM = 0.1;
494 return pcbIUScale.mmToIU( SKETCH_LINE_WIDTH_MM );
495}
496
497
498// return a default pen size to plot items with no specific line thickness
500{
501 const double DEFAULT_LINE_WIDTH_MM = 0.2;
502 return pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH_MM );
503}
504
505
506bool GENDRILL_WRITER_BASE::genDrillMapFile( const wxString& aFullFileName, PLOT_FORMAT aFormat )
507{
508 // Remark:
509 // Hole list must be created before calling this function, by buildHolesList(),
510 // for the right holes set (PTH, NPTH, buried/blind vias ...)
511
512 double scale = 1.0;
513 VECTOR2I offset = GetOffset();
514 PLOTTER* plotter = nullptr;
516 int bottom_limit = 0; // Y coord limit of page. 0 mean do not use
517
518 PCB_PLOT_PARAMS plot_opts; // starts plotting with default options
519
520 const PAGE_INFO& page_info = m_pageInfo ? *m_pageInfo : dummy;
521
522 // Calculate dimensions and center of PCB. The Edge_Cuts layer must be visible
523 // to calculate the board edges bounding box
524 LSET visibleLayers = m_pcb->GetVisibleLayers();
525 m_pcb->SetVisibleLayers( visibleLayers | LSET( { Edge_Cuts } ) );
526 BOX2I bbbox = m_pcb->GetBoardEdgesBoundingBox();
527 m_pcb->SetVisibleLayers( visibleLayers );
528
529 // Some formats cannot be used to generate a document like the map files
530 // Currently HPGL (old format not very used)
531
532 if( aFormat == PLOT_FORMAT::HPGL )
533 aFormat = PLOT_FORMAT::PDF;
534
535 // Calculate the scale for the format type, scale 1 in HPGL, drawing on
536 // an A4 sheet in PS, + text description of symbols
537 switch( aFormat )
538 {
540 plotter = new GERBER_PLOTTER();
541 plotter->SetViewport( offset, pcbIUScale.IU_PER_MILS / 10, scale, false );
542 plotter->SetGerberCoordinatesFormat( 5 ); // format x.5 unit = mm
543 break;
544
545 default: wxASSERT( false ); KI_FALLTHROUGH;
546
547 case PLOT_FORMAT::PDF:
549 case PLOT_FORMAT::SVG:
550 {
551 VECTOR2I pageSizeIU = page_info.GetSizeIU( pcbIUScale.IU_PER_MILS );
552
553 // Reserve a 10 mm margin around the page.
554 int margin = pcbIUScale.mmToIU( 10 );
555
556 // Calculate a scaling factor to print the board on the sheet
557 double Xscale = double( pageSizeIU.x - ( 2 * margin ) ) / bbbox.GetWidth();
558
559 // We should print the list of drill sizes, so reserve room for it
560 // 60% height for board 40% height for list
561 int ypagesize_for_board = KiROUND( pageSizeIU.y * 0.6 );
562 double Yscale = double( ypagesize_for_board - margin ) / bbbox.GetHeight();
563
564 scale = std::min( Xscale, Yscale );
565
566 // Experience shows the scale should not to large, because texts
567 // create problem (can be to big or too small).
568 // So the scale is clipped at 3.0;
569 scale = std::min( scale, 3.0 );
570
571 offset.x = KiROUND( double( bbbox.Centre().x ) - ( pageSizeIU.x / 2.0 ) / scale );
572 offset.y = KiROUND( double( bbbox.Centre().y ) - ( ypagesize_for_board / 2.0 ) / scale );
573
574 // bottom_limit is used to plot the legend (drill diameters)
575 // texts are scaled differently for scale > 1.0 and <= 1.0
576 // so the limit is scaled differently.
577 bottom_limit = ( pageSizeIU.y - margin ) / std::min( scale, 1.0 );
578
579 if( aFormat == PLOT_FORMAT::SVG )
580 plotter = new SVG_PLOTTER;
581 else if( aFormat == PLOT_FORMAT::PDF )
582 plotter = new PDF_PLOTTER;
583 else
584 plotter = new PS_PLOTTER;
585
586 plotter->SetPageSettings( page_info );
587 plotter->SetViewport( offset, pcbIUScale.IU_PER_MILS / 10, scale, false );
588 break;
589 }
590
591 case PLOT_FORMAT::DXF:
592 {
593 DXF_PLOTTER* dxf_plotter = new DXF_PLOTTER;
594
596
597 plotter = dxf_plotter;
598 plotter->SetPageSettings( page_info );
599 plotter->SetViewport( offset, pcbIUScale.IU_PER_MILS / 10, scale, false );
600 break;
601 }
602 }
603
604 plotter->SetCreator( wxT( "PCBNEW" ) );
605 plotter->SetColorMode( false );
606
607 KIGFX::PCB_RENDER_SETTINGS renderSettings;
608 renderSettings.SetDefaultPenWidth( getDefaultPenSize() );
609
610 plotter->SetRenderSettings( &renderSettings );
611
612 if( !plotter->OpenFile( aFullFileName ) )
613 {
614 delete plotter;
615 return false;
616 }
617
618 plotter->ClearHeaderLinesList();
619
620 // For the Gerber X2 format we need to set the "FileFunction" to Drillmap
621 // and set a few other options.
622 if( plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
623 {
624 GERBER_PLOTTER* gbrplotter = static_cast<GERBER_PLOTTER*>( plotter );
625 gbrplotter->DisableApertMacros( false );
626 gbrplotter->UseX2format( true ); // Mandatory
627 gbrplotter->UseX2NetAttributes( false ); // net attributes have no meaning here
628
629 // Attributes are added using X2 format
630 AddGerberX2Header( gbrplotter, m_pcb, false );
631
632 wxString text;
633
634 // Add the TF.FileFunction
635 text = "%TF.FileFunction,Drillmap*%";
636 gbrplotter->AddLineToHeader( text );
637
638 // Add the TF.FilePolarity
639 text = wxT( "%TF.FilePolarity,Positive*%" );
640 gbrplotter->AddLineToHeader( text );
641 }
642
643 plotter->StartPlot( wxT( "1" ) );
644
645 // Draw items on edge layer.
646 // Not all, only items useful for drill map, i.e. board outlines.
647 BRDITEMS_PLOTTER itemplotter( plotter, m_pcb, plot_opts );
648
649 // Use attributes of a drawing layer (we are not really draw the Edge.Cuts layer)
650 itemplotter.SetLayerSet( { Dwgs_User } );
651
652 for( BOARD_ITEM* item : m_pcb->Drawings() )
653 {
654 if( item->GetLayer() != Edge_Cuts )
655 continue;
656
657 switch( item->Type() )
658 {
659 case PCB_SHAPE_T:
660 {
661 PCB_SHAPE dummy_shape( *static_cast<PCB_SHAPE*>( item ) );
662 dummy_shape.SetLayer( Dwgs_User );
663 dummy_shape.SetParentGroup( nullptr ); // Remove group association, not needed for plotting
664 itemplotter.PlotShape( &dummy_shape );
665 }
666 break;
667
668 default: break;
669 }
670 }
671
672 // Plot edge cuts in footprints
673 for( const FOOTPRINT* footprint : m_pcb->Footprints() )
674 {
675 for( BOARD_ITEM* item : footprint->GraphicalItems() )
676 {
677 if( item->GetLayer() != Edge_Cuts )
678 continue;
679
680 switch( item->Type() )
681 {
682 case PCB_SHAPE_T:
683 {
684 PCB_SHAPE dummy_shape( *static_cast<PCB_SHAPE*>( item ) );
685 dummy_shape.SetLayer( Dwgs_User );
686 dummy_shape.SetParentGroup( nullptr ); // Remove group association, not needed for plotting
687 itemplotter.PlotShape( &dummy_shape );
688 }
689 break;
690
691 default: break;
692 }
693 }
694 }
695
696 int plotX, plotY, TextWidth;
697 int intervalle = 0;
698 char line[4096];
699 wxString msg;
700 int textmarginaftersymbol = pcbIUScale.mmToIU( 2 );
701
702 // Set Drill Symbols width
703 plotter->SetCurrentLineWidth( -1 );
704
705 // Plot board outlines and drill map
706 plotDrillMarks( plotter );
707
708 // Print a list of symbols used.
709 int charSize = pcbIUScale.mmToIU( 2 ); // text size in IUs
710
711 // real char scale will be 1/scale, because the global plot scale is scale
712 // for scale < 1.0 ( plot bigger actual size)
713 // Therefore charScale = 1.0 / scale keep the initial charSize
714 // (for scale < 1 we use the global scaling factor: the board must be plotted
715 // smaller than the actual size)
716 double charScale = std::min( 1.0, 1.0 / scale );
717
718 TextWidth = KiROUND( ( charSize * charScale ) / 10.0 ); // Set text width (thickness)
719 intervalle = KiROUND( charSize * charScale ) + TextWidth;
720
721 // Trace information.
722 plotX = KiROUND( bbbox.GetX() + textmarginaftersymbol * charScale );
723 plotY = bbbox.GetBottom() + intervalle;
724
725 // Plot title "Info"
726 wxString Text = wxT( "Drill Map:" );
727
728 TEXT_ATTRIBUTES attrs;
729 attrs.m_StrokeWidth = TextWidth;
731 attrs.m_Size = KiROUND( charSize * charScale, charSize * charScale );
734 attrs.m_Multiline = false;
735
736 plotter->PlotText( VECTOR2I( plotX, plotY ), COLOR4D::UNSPECIFIED, Text, attrs, nullptr /* stroke font */,
738
739 // For some formats (PS, PDF SVG) we plot the drill size list on more than one column
740 // because the list must be contained inside the printed page
741 // (others formats do not have a defined page size)
742 int max_line_len = 0; // The max line len in iu of the currently plotted column
743
744 for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
745 {
746 DRILL_TOOL& tool = m_toolListBuffer[ii];
747
748 if( tool.m_TotalCount == 0 )
749 continue;
750
751 plotY += intervalle;
752
753 // Ensure there are room to plot the line
754 if( bottom_limit && ( plotY + intervalle > bottom_limit ) )
755 {
756 plotY = bbbox.GetBottom() + intervalle;
757 plotX += max_line_len + pcbIUScale.mmToIU( 10 ); //column_width;
758 max_line_len = 0;
759 }
760
761 int plot_diam = KiROUND( tool.m_Diameter );
762
763 // For markers plotted with the comment, keep marker size <= text height
764 plot_diam = std::min( plot_diam, KiROUND( charSize * charScale ) );
765 int x = KiROUND( plotX - textmarginaftersymbol * charScale - plot_diam / 2.0 );
766 int y = KiROUND( plotY + charSize * charScale );
767
768 plotter->SetCurrentLineWidth( getMarkerBestPenSize( plot_diam ) );
769 plotter->Marker( VECTOR2I( x, y ), plot_diam, ii );
770 plotter->SetCurrentLineWidth( -1 );
771
772 // List the diameter of each drill in mm and inches.
773 snprintf( line, sizeof( line ), "%3.3fmm / %2.4f\" ", diameter_in_mm( tool.m_Diameter ),
775
776 msg = From_UTF8( line );
777 wxString extraInfo;
778
780 extraInfo += wxT( ", castellated" );
782 extraInfo += wxT( ", press-fit" );
783
784 if( tool.m_IsBackdrill )
785 {
786 if( tool.m_MinStubLength.has_value() )
787 {
788 double minStub = pcbIUScale.IUTomm( *tool.m_MinStubLength );
789
790 if( tool.m_MaxStubLength.has_value() && tool.m_MaxStubLength != tool.m_MinStubLength )
791 {
792 double maxStub = pcbIUScale.IUTomm( *tool.m_MaxStubLength );
793 extraInfo += wxString::Format( wxT( ", backdrill stub %.3f-%.3fmm" ), minStub, maxStub );
794 }
795 else
796 {
797 extraInfo += wxString::Format( wxT( ", backdrill stub %.3fmm" ), minStub );
798 }
799 }
800 else
801 {
802 extraInfo += wxT( ", backdrill" );
803 }
804 }
805
806 if( tool.m_HasPostMachining )
807 extraInfo += wxT( ", post-machined" );
808
809 wxString counts;
810
811 if( ( tool.m_TotalCount == 1 ) && ( tool.m_OvalCount == 0 ) )
812 counts.Printf( wxT( "(1 hole%s)" ), extraInfo );
813 else if( tool.m_TotalCount == 1 )
814 counts.Printf( wxT( "(1 slot%s)" ), extraInfo );
815 else if( tool.m_OvalCount == 0 )
816 counts.Printf( wxT( "(%d holes%s)" ), tool.m_TotalCount, extraInfo );
817 else if( tool.m_OvalCount == 1 )
818 counts.Printf( wxT( "(%d holes + 1 slot%s)" ), tool.m_TotalCount - 1, extraInfo );
819 else
820 counts.Printf( wxT( "(%d holes + %d slots%s)" ), tool.m_TotalCount - tool.m_OvalCount, tool.m_OvalCount,
821 extraInfo );
822
823 msg += counts;
824
825 // Backdrills carry the non-plated flag but are already called out as backdrills above
826 if( tool.m_Hole_NotPlated && !tool.m_IsBackdrill )
827 msg += wxT( " (not plated)" );
828
829 plotter->PlotText( VECTOR2I( plotX, y ), COLOR4D::UNSPECIFIED, msg, attrs, nullptr /* stroke font */,
831
832 intervalle = KiROUND( ( ( charSize * charScale ) + TextWidth ) * 1.2 );
833
834 if( intervalle < ( plot_diam + ( 1 * pcbIUScale.IU_PER_MM / scale ) + TextWidth ) )
835 intervalle = plot_diam + ( 1 * pcbIUScale.IU_PER_MM / scale ) + TextWidth;
836
837 // Evaluate the text horizontal size, to know the maximal column size
838 // This is a rough value, but ok to create a new column to plot next texts
839 int text_len = msg.Len() * ( ( charSize * charScale ) + TextWidth );
840 max_line_len = std::max( max_line_len, text_len + plot_diam );
841 }
842
843 plotter->EndPlot();
844 delete plotter;
845
846 return true;
847}
848
849
850bool GENDRILL_WRITER_BASE::GenDrillReportFile( const wxString& aFullFileName, REPORTER* aReporter )
851{
852 wxFFile out( aFullFileName, "wb" );
853
854 if( !out.IsOpened() )
855 {
856 if( aReporter )
857 {
858 wxString msg = wxString::Format( _( "Error creating drill report file '%s'" ),
859 aFullFileName );
860 aReporter->Report( msg, RPT_SEVERITY_ERROR );
861 }
862
863 return false;
864 }
865
866 FILE* outFp = out.fp();
867
868 static const char separator[] =
869 " =============================================================\n";
870
871 wxASSERT( m_pcb );
872
873 unsigned totalHoleCount;
874 wxFileName brdFilename( m_pcb->GetFileName() );
875
876 std::vector<DRILL_SPAN> hole_sets = EnumerateDrillSpans( *m_pcb );
877
878 bool writeError = false;
879
880 try
881 {
882 fmt::print( outFp, "Drill report for {}\n", TO_UTF8( brdFilename.GetFullName() ) );
883 fmt::print( outFp, "Created on {}\n\n", TO_UTF8( GetISO8601CurrentDateTime() ) );
884
885 // Output the cu layer stackup, so layer name references make sense.
886 fmt::print( outFp, "Copper Layer Stackup:\n" );
887 fmt::print( outFp, "{}", separator );
888
889 int conventional_layer_num = 1;
890
891 for( PCB_LAYER_ID layer : LSET::AllCuMask( m_pcb->GetCopperLayerCount() ).UIOrder() )
892 {
893 fmt::print( outFp, " L{:<2}: {:<25} {}\n", conventional_layer_num++,
894 TO_UTF8( m_pcb->GetLayerName( layer ) ),
895 layerName( layer ).c_str() ); // generic layer name
896 }
897
898 fmt::print( outFp, "\n\n" );
899
900 /* output hole lists:
901 * 1 - through holes
902 * 2 - for partial holes only: by layer starting and ending pair
903 * 3 - Non Plated through holes
904 */
905
906 bool buildNPTHlist = false; // First pass: build PTH list only
907
908 // in this loop are plated only:
909 for( unsigned pair_ndx = 0; pair_ndx < hole_sets.size(); ++pair_ndx )
910 {
911 const DRILL_SPAN& span = hole_sets[pair_ndx];
912
913 buildHolesList( span, buildNPTHlist );
914
915 if( span.Pair() == DRILL_LAYER_PAIR( F_Cu, B_Cu ) && !span.m_IsBackdrill )
916 {
917 fmt::print( outFp, "Drill file '{}' contains\n",
918 TO_UTF8( getDrillFileName( span, false, m_merge_PTH_NPTH ) ) );
919
920 fmt::print( outFp, " plated through holes:\n" );
921 fmt::print( outFp, "{}", separator );
922 totalHoleCount = printToolSummary( outFp, TOOL_SUMMARY::PLATED );
923 fmt::print( outFp, " Total plated holes count {}\n", totalHoleCount );
924 }
925 else if( span.m_IsBackdrill )
926 {
927 fmt::print( outFp, "Drill file '{}' contains\n",
928 TO_UTF8( getDrillFileName( span, false, m_merge_PTH_NPTH ) ) );
929
930 fmt::print( outFp, " backdrill span: '{}' to '{}':\n",
931 TO_UTF8( m_pcb->GetLayerName( ToLAYER_ID( span.DrillStartLayer() ) ) ),
932 TO_UTF8( m_pcb->GetLayerName( ToLAYER_ID( span.DrillEndLayer() ) ) ) );
933
934 fmt::print( outFp, "{}", separator );
935 totalHoleCount = printToolSummary( outFp, TOOL_SUMMARY::BACKDRILL );
936 fmt::print( outFp, " Total backdrilled holes count {}\n", totalHoleCount );
937 }
938 else
939 {
940 fmt::print( outFp, "Drill file '{}' contains\n",
941 TO_UTF8( getDrillFileName( span, false, m_merge_PTH_NPTH ) ) );
942
943 fmt::print( outFp, " holes connecting layer pair: '{} and {}' ({} vias):\n",
944 TO_UTF8( m_pcb->GetLayerName( ToLAYER_ID( span.Pair().first ) ) ),
945 TO_UTF8( m_pcb->GetLayerName( ToLAYER_ID( span.Pair().second ) ) ),
946 span.Pair().first == F_Cu || span.Pair().second == B_Cu ? "blind" : "buried" );
947
948 fmt::print( outFp, "{}", separator );
949 totalHoleCount = printToolSummary( outFp, TOOL_SUMMARY::PLATED );
950 fmt::print( outFp, " Total plated holes count {}\n", totalHoleCount );
951 }
952
953 fmt::print( outFp, "\n\n" );
954 }
955
956 // NPTHoles. Generate the full list (pads+vias) if PTH and NPTH are merged,
957 // or only the NPTH list (which never has vias)
958 if( !m_merge_PTH_NPTH )
959 buildNPTHlist = true;
960
961 DRILL_SPAN npthSpan( F_Cu, B_Cu, false, buildNPTHlist );
962
963 buildHolesList( npthSpan, buildNPTHlist );
964
965 // nothing wrong with an empty NPTH file in report.
966 if( m_merge_PTH_NPTH )
967 fmt::print( outFp, "Not plated through holes are merged with plated holes\n" );
968 else
969 fmt::print( outFp, "Drill file '{}' contains\n",
970 TO_UTF8( getDrillFileName( npthSpan, true, m_merge_PTH_NPTH ) ) );
971
972 fmt::print( outFp, " unplated through holes:\n" );
973 fmt::print( outFp, "{}", separator );
974 totalHoleCount = printToolSummary( outFp, TOOL_SUMMARY::UNPLATED );
975 fmt::print( outFp, " Total unplated holes count {}\n", totalHoleCount );
976 }
977 catch( const std::system_error& )
978 {
979 writeError = true;
980 }
981 catch( const fmt::format_error& )
982 {
983 writeError = true;
984 }
985
986 if( writeError && aReporter )
987 {
988 wxString msg = wxString::Format( _( "Error writing drill report file '%s'" ), aFullFileName );
989 aReporter->Report( msg, RPT_SEVERITY_ERROR );
990 }
991
992 return !writeError;
993}
994
995
997{
998 // Plot the drill map:
999 for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
1000 {
1001 const DRILL_OPERATION& hole = m_holeListBuffer[ii];
1002
1003 // Gives a good line thickness to have a good marker shape:
1005
1006 // Always plot the drill symbol (for slots identifies the needed cutter!
1007 aPlotter->Marker( hole.m_Position, hole.m_Diameter, m_holeToolReferences[ii] - 1 );
1008
1009 if( hole.m_IsSlot )
1010 {
1012 nullptr );
1013 }
1014 }
1015
1017
1018 return true;
1019}
1020
1021
1022unsigned GENDRILL_WRITER_BASE::printToolSummary( FILE* out, TOOL_SUMMARY aSummary ) const
1023{
1024 unsigned totalHoleCount = 0;
1025
1026 for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
1027 {
1028 const DRILL_TOOL& tool = m_toolListBuffer[ii];
1029
1030 // Backdrills set the non-plated flag, so they must be classified before it is read
1034
1035 if( bucket != aSummary )
1036 continue;
1037
1038 // List the tool number assigned to each drill in mm then in inches.
1039 int tool_number = ii+1;
1040 fmt::print( out, " T{} {:2.3f}mm {:2.4f}\" ", tool_number,
1041 diameter_in_mm( tool.m_Diameter ),
1043
1044 // Now list how many holes and ovals are associated with each drill.
1045 if( ( tool.m_TotalCount == 1 ) && ( tool.m_OvalCount == 0 ) )
1046 fmt::print( out, "(1 hole" );
1047 else if( tool.m_TotalCount == 1 )
1048 fmt::print( out, "(1 hole) (with 1 slot" );
1049 else if( tool.m_OvalCount == 0 )
1050 fmt::print( out, "({} holes)", tool.m_TotalCount );
1051 else if( tool.m_OvalCount == 1 )
1052 fmt::print( out, "({} holes) (with 1 slot", tool.m_TotalCount );
1053 else // tool.m_OvalCount > 1
1054 fmt::print( out, "({} holes) (with {} slots", tool.m_TotalCount, tool.m_OvalCount );
1055
1057 fmt::print( out, ", castellated" );
1058
1060 fmt::print( out, ", press-fit" );
1061
1062 fmt::print( out, ")\n" );
1063
1064 totalHoleCount += tool.m_TotalCount;
1065 }
1066
1067 fmt::print( out, "\n" );
1068
1069 return totalHoleCount;
1070}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr Vec Centre() const
Definition box2.h:94
constexpr coord_type GetX() const
Definition box2.h:204
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr coord_type GetBottom() const
Definition box2.h:219
void SetLayerSet(const LSET &aLayerMask)
Definition pcbplot.h:84
void PlotShape(const PCB_SHAPE *aShape)
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:400
std::optional< int > m_MinStubLength
HOLE_ATTRIBUTE m_HoleAttribute
std::optional< int > m_MaxStubLength
void SetUnits(DXF_UNITS aUnit)
Set the units to use for plotting the DXF file.
virtual void SetParentGroup(EDA_GROUP *aGroup)
Definition eda_item.h:115
virtual const wxString getProtectionFileName(const DRILL_SPAN &aSpan, IPC4761_FEATURES aFeature) const
const PAGE_INFO * m_pageInfo
unsigned printToolSummary(FILE *aOut, TOOL_SUMMARY aSummary) const
Print m_toolListBuffer[] tools to aOut and returns total hole count.
void AddCreatedFile(const wxString &aPath)
TOOL_SUMMARY
Selects which subset of m_toolListBuffer a summary covers.
@ UNPLATED
Non-plated holes, excluding backdrills.
@ BACKDRILL
Backdrills, which are non-plated by construction.
void buildHolesList(const DRILL_SPAN &aSpan, bool aGenerateNPTH_list)
Create the list of holes and tools for a given board.
bool genDrillMapFile(const wxString &aFullFileName, PLOT_FORMAT aFormat)
Plot a map of drill marks for holes.
VECTOR2I GetOffset()
Return the plot offset (usually the position of the drill/place origin).
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
std::vector< int > m_holeToolReferences
std::vector< DRILL_OPERATION > m_holeListBuffer
virtual const wxString getDrillFileName(const DRILL_SPAN &aSpan, bool aNPTH, bool aMerge_PTH_NPTH) const
bool plotDrillMarks(PLOTTER *aPlotter)
Write the drill marks in PDF, POSTSCRIPT or other supported formats/.
const wxString BuildFileFunctionAttributeString(const DRILL_SPAN &aSpan, TYPE_FILE aHoleType, bool aCompatNCdrill=false) const
The standard X2 FileFunction for drill files is %TF.FileFunction,Plated[NonPlated],...
bool GenDrillReportFile(const wxString &aFullFileName, REPORTER *aReporter=nullptr)
Create a plain text report file giving a list of drill values and drill count for through holes,...
void UseX2format(bool aEnable)
void UseX2NetAttributes(bool aEnable)
void DisableApertMacros(bool aDisable)
Disable Aperture Macro (AM) command, only for broken Gerber Readers.
static const METRICS & Default()
Definition font.cpp:48
PCB specific render settings.
Definition pcb_painter.h:84
void SetDefaultPenWidth(int aWidth)
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
const VECTOR2D GetSizeIU(double aIUScale) const
Gets the page size in internal units.
Definition page_info.h:172
Parameters and options when plotting/printing a board.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Base plotter engine class.
Definition plotter.h:136
virtual bool OpenFile(const wxString &aFullFilename)
Open or create the plot file aFullFilename.
Definition plotter.cpp:75
void Marker(const VECTOR2I &aPosition, int aDiameter, unsigned aShapeId)
Draw a pattern shape number aShapeId, to coord position.
Definition plotter.cpp:363
virtual void SetPageSettings(const PAGE_INFO &aPageSettings)
Definition plotter.h:169
void SetRenderSettings(RENDER_SETTINGS *aSettings)
Definition plotter.h:166
static const int USE_DEFAULT_LINE_WIDTH
Definition plotter.h:140
virtual bool EndPlot()=0
virtual void ThickOval(const VECTOR2I &aPos, const VECTOR2I &aSize, const EDA_ANGLE &aOrient, int aWidth, void *aData)
Definition plotter.cpp:388
virtual bool StartPlot(const wxString &aPageNumber)=0
virtual void SetGerberCoordinatesFormat(int aResolution, bool aUseInches=false)
Definition plotter.h:577
virtual PLOT_FORMAT GetPlotterType() const =0
Return the effective plot engine in use.
virtual void SetCreator(const wxString &aCreator)
Definition plotter.h:188
void ClearHeaderLinesList()
Remove all lines from the list of free lines to print at the beginning of the file.
Definition plotter.h:206
virtual void SetViewport(const VECTOR2I &aOffset, double aIusPerDecimil, double aScale, bool aMirror)=0
Set the plot offset and scaling for the current plot.
void AddLineToHeader(const wxString &aExtraString)
Add a line to the list of free lines to print at the beginning of the file.
Definition plotter.h:198
virtual void SetColorMode(bool aColorMode)
Plot in B/W or color.
Definition plotter.h:163
virtual void SetCurrentLineWidth(int width, void *aData=nullptr)=0
Set the line width for the next drawing.
virtual void PlotText(const VECTOR2I &aPos, const COLOR4D &aColor, const wxString &aText, const TEXT_ATTRIBUTES &aAttributes, KIFONT::FONT *aFont=nullptr, const KIFONT::METRICS &aFontMetrics=KIFONT::METRICS::Default(), void *aData=nullptr)
Definition plotter.cpp:617
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
EDA_ORIENTATION m_Angle
GR_TEXT_H_ALIGN_T m_Halign
GR_TEXT_V_ALIGN_T m_Valign
wxString GetDefaultPlotExtension(PLOT_FORMAT aFormat)
Return the default plot extension for a format.
std::vector< DRILL_SPAN > EnumerateDrillSpans(const BOARD &aBoard, bool aPadBackdrills)
Every drill span present on the board, through-holes first.
std::vector< DRILL_OPERATION > EnumerateDrillOperations(const BOARD &aBoard, const DRILL_QUERY &aQuery)
The one place that decides what the board's holes are.
HOLE_ATTRIBUTE
Definition drill_span.h:36
std::pair< PCB_LAYER_ID, PCB_LAYER_ID > DRILL_LAYER_PAIR
Definition drill_span.h:49
#define _(s)
static constexpr EDA_ANGLE ANGLE_HORIZONTAL
Definition eda_angle.h:444
double diameter_in_mm(double ius)
double diameter_in_inches(double ius)
int getDefaultPenSize()
static int getMarkerBestPenSize(int aMarkerDiameter)
static bool cmpHoleSorting(const DRILL_OPERATION &a, const DRILL_OPERATION &b)
int getSketchOvalBestPenSize()
helper classes to handle hole info for drill files generators.
#define USE_ATTRIB_FOR_HOLES
static wxString From_UTF8(const char *cstring)
Convert a UTF8 encoded C string to a wxString for all wxWidgets build modes.
Definition idf_helpers.h:44
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Edge_Cuts
Definition layer_ids.h:108
@ Dwgs_User
Definition layer_ids.h:103
@ B_Cu
Definition layer_ids.h:61
@ F_Cu
Definition layer_ids.h:60
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition lset.cpp:750
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:79
void AddGerberX2Header(PLOTTER *aPlotter, const BOARD *aBoard, bool aUseX1CompatibilityMode)
Calculate some X2 attributes as defined in the Gerber file format specification J4 (chapter 5) and ad...
Definition pcbplot.cpp:290
PLOT_FORMAT
The set of supported output plot formats.
Definition plotter.h:63
Plotting engines similar to ps (PostScript, Gerber, svg)
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_ACTION
const int scale
std::vector< FAB_LAYER_COLOR > dummy
wxString GetISO8601CurrentDateTime()
One machining action, which is also one NC hit and one tool assignment.
bool IsBackdrill() const
DRILL_POST_MACHINING m_BackPostMachining
VECTOR2I m_SizeXY
Source axes, never normalized to (width, length).
EDA_ANGLE m_Orientation
DRILL_POST_MACHINING m_FrontPostMachining
std::optional< int > m_StubLength
HOLE_ATTRIBUTE m_Attribute
PAD_DRILL_POST_MACHINING_MODE m_Mode
Selects which operations EnumerateDrillOperations() returns.
bool m_NonPlatedOnly
Emit only non-plated holes.
bool m_MergePTHNPTH
Emit plated and non-plated together, as the merged drill file does.
DRILL_SPAN m_Span
DRILL_LAYER_PAIR Pair() const
Definition drill_span.h:93
PCB_LAYER_ID DrillEndLayer() const
Definition drill_span.h:88
PCB_LAYER_ID DrillStartLayer() const
Definition drill_span.h:83
bool m_IsNonPlatedFile
Definition drill_span.h:131
bool m_IsBackdrill
Definition drill_span.h:130
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_CENTER
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708