Excellon drill files: add structured comments to identify via holes and pad holes.

This is useful for CAM tools because constraints for via holes and pad holes are
not the same
This commit is contained in:
jean-pierre charras 2021-03-07 17:47:37 +01:00
parent fde22da478
commit e0cbcc3330
4 changed files with 137 additions and 47 deletions

View File

@ -3,7 +3,7 @@
*
* Copyright (C) 2018 Jean_Pierre Charras <jp.charras at wanadoo.fr>
* Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -51,11 +51,6 @@
#include <reporter.h>
#include <gbr_metadata.h>
// Comment/uncomment this to write or not a comment
// in drill file when PTH and NPTH are merged to flag
// tools used for PTH and tools used for NPTH
// #define WRITE_PTH_NPTH_COMMENT
// Oblong holes can be drilled by a "canned slot" command (G85) or a routing command
// a linear routing command (G01) is perhaps more usual for drill files
//
@ -141,6 +136,47 @@ void EXCELLON_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
}
void EXCELLON_WRITER::writeHoleAttribute( HOLE_ATTRIBUTE aAttribute, bool aToolAttr )
{
// Hole attributes are comments (lines starting by ';') in the drill files
// For tools (file header), they are similar to X2 apertures attributes.
// for attributes added in coordinate list, they are just comments.
if( !m_minimalHeader )
{
switch( aAttribute )
{
case HOLE_ATTRIBUTE::HOLE_VIA:
if( aToolAttr )
fprintf( m_file, "; #@! TA.AperFunction,ViaDrill\n" );
else
fprintf( m_file, "; via hole\n" );
break;
case HOLE_ATTRIBUTE::HOLE_PAD:
if( aToolAttr )
fprintf( m_file, "; #@! TA.AperFunction,ComponentDrill\n" );
else
fprintf( m_file, "; pad hole\n" );
break;
case HOLE_ATTRIBUTE::HOLE_MECHANICAL:
if( aToolAttr )
fprintf( m_file, "; #@! TA.AperFunction,MechanicalDrill\n" );
else
fprintf( m_file, "; mechanical\n" );
break;
case HOLE_ATTRIBUTE::HOLE_UNKNOWN:
if( aToolAttr )
fprintf( m_file, "; #@! TD\n" );
else
fprintf( m_file, "; unknown\n" );
break;
}
}
}
int EXCELLON_WRITER::createDrillFile( FILE* aFile, DRILL_LAYER_PAIR aLayerPair,
bool aGenerateNPTH_list )
{
@ -157,35 +193,19 @@ int EXCELLON_WRITER::createDrillFile( FILE* aFile, DRILL_LAYER_PAIR aLayerPair,
holes_count = 0;
#ifdef WRITE_PTH_NPTH_COMMENT
// if PTH_ and NPTH are merged write a comment in drill file at the
// beginning of NPTH section
bool writePTHcomment = m_merge_PTH_NPTH;
bool writeNPTHcomment = m_merge_PTH_NPTH;
#endif
/* Write the tool list */
for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
{
DRILL_TOOL& tool_descr = m_toolListBuffer[ii];
#ifdef WRITE_PTH_NPTH_COMMENT
if( writePTHcomment && !tool_descr.m_Hole_NotPlated )
{
writePTHcomment = false;
fprintf( m_file, ";TYPE=PLATED\n" );
}
if( writeNPTHcomment && tool_descr.m_Hole_NotPlated )
{
writeNPTHcomment = false;
fprintf( m_file, ";TYPE=NON_PLATED\n" );
}
#if USE_ATTRIB_FOR_HOLES
writeHoleAttribute( tool_descr.m_HoleAttribute, true );
#endif
if( m_unitsMetric ) // if units are mm, the resolution is 0.001 mm (3 digits in mantissa)
// if units are mm, the resolution is 0.001 mm (3 digits in mantissa)
// if units are inches, the resolution is 0.1 mil (4 digits in mantissa)
if( m_unitsMetric )
fprintf( m_file, "T%dC%.3f\n", ii + 1, tool_descr.m_Diameter * m_conversionUnits );
else // if units are inches, the resolution is 0.1 mil (4 digits in mantissa)
else
fprintf( m_file, "T%dC%.4f\n", ii + 1, tool_descr.m_Diameter * m_conversionUnits );
}
@ -193,10 +213,12 @@ int EXCELLON_WRITER::createDrillFile( FILE* aFile, DRILL_LAYER_PAIR aLayerPair,
fputs( "G90\n", m_file ); // Absolute mode
fputs( "G05\n", m_file ); // Drill mode
/* Read the hole file and generate lines for normal holes (oblong
/* Read the hole list and generate data for normal holes (oblong
* holes will be created later) */
int tool_reference = -2;
HOLE_ATTRIBUTE last_item_type = HOLE_ATTRIBUTE::HOLE_UNKNOWN;
for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
{
HOLE_INFO& hole_descr = m_holeListBuffer[ii];
@ -210,6 +232,13 @@ int EXCELLON_WRITER::createDrillFile( FILE* aFile, DRILL_LAYER_PAIR aLayerPair,
fprintf( m_file, "T%d\n", tool_reference );
}
HOLE_ATTRIBUTE curr_item_type = hole_descr.m_HoleAttribute;
if( curr_item_type != last_item_type )
writeHoleAttribute( curr_item_type, false );
last_item_type = curr_item_type;
x0 = hole_descr.m_Hole_Pos.x - m_offset.x;
y0 = hole_descr.m_Hole_Pos.y - m_offset.y;
@ -224,10 +253,12 @@ int EXCELLON_WRITER::createDrillFile( FILE* aFile, DRILL_LAYER_PAIR aLayerPair,
holes_count++;
}
/* Read the hole file and generate lines for normal holes (oblong holes
* will be created later) */
/* Read the hole list and generate data for oblong holes
*/
tool_reference = -2; // set to a value not used for
// m_holeListBuffer[ii].m_Tool_Reference
last_item_type = HOLE_ATTRIBUTE::HOLE_UNKNOWN;
for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
{
HOLE_INFO& hole_descr = m_holeListBuffer[ii];
@ -241,6 +272,13 @@ int EXCELLON_WRITER::createDrillFile( FILE* aFile, DRILL_LAYER_PAIR aLayerPair,
fprintf( m_file, "T%d\n", tool_reference );
}
HOLE_ATTRIBUTE curr_item_type = hole_descr.m_HoleAttribute;
if( curr_item_type != last_item_type )
writeHoleAttribute( curr_item_type, false );
last_item_type = curr_item_type;
diam = std::min( hole_descr.m_Hole_Size.x, hole_descr.m_Hole_Size.y );
if( diam == 0 )

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-2017 Jean_Pierre Charras <jp.charras at wanadoo.fr>
* Copyright (C) 1992-2017 KiCad Developers, see change_log.txt for contributors.
* Copyright (C) 1992-2021 KiCad Developers, see AUTHOR.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -37,6 +37,7 @@ class BOARD;
class PLOTTER;
class OUTPUTFORMATTER;
/**
* EXCELLON_WRITER is a class mainly used to create Excellon drill files
* However, this class is also used to create drill maps and drill report
@ -127,7 +128,7 @@ private:
int createDrillFile( FILE * aFile, DRILL_LAYER_PAIR aLayerPair, bool aGenerateNPTH_list );
/* Print the DRILL file header. The full header is somethink like:
/** Print the DRILL file header. The full header is somethink like:
* M48
* ;DRILL file {PCBNEW (2007-11-29-b)} date 17/1/2008-21:02:35
* ;FORMAT={ <precision> / absolute / <units> / <numbers format>}
@ -141,11 +142,20 @@ private:
void writeEXCELLONEndOfFile();
/* Created a line like:
/** Created a line like:
* X48000Y19500
* According to the selected format
*/
void writeCoordinates( char* aLine, double aCoordX, double aCoordY );
/**
* write a comment string giving the hole attribute like
* "; #@! TO.P,viatype\n"
* @param aAttribute is the hole attribute
* @param aToolAttr = true if the comment is for a tool,
* false for a hole in coord list
*/
void writeHoleAttribute( HOLE_ATTRIBUTE aAttribute, bool aToolAttr );
};
#endif // #ifndef _GENDRILL_EXCELLON_WRITER_

View File

@ -3,7 +3,7 @@
*
* Copyright (C) 2017 Jean_Pierre Charras <jp.charras at wanadoo.fr>
* Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -33,10 +33,13 @@
/* Helper function for sorting hole list.
* Compare function used for sorting holes type type (plated then not plated)
* then by increasing diameter value and X then Y position
* Compare function used for sorting holes type type:
* plated then not plated
* then by increasing diameter value
* then by attribute type (vias, pad, mechanical)
* then by X then Y position
*/
static bool CmpHoleSorting( const HOLE_INFO& a, const HOLE_INFO& b )
static bool cmpHoleSorting( const HOLE_INFO& a, const HOLE_INFO& b )
{
if( a.m_Hole_NotPlated != b.m_Hole_NotPlated )
return b.m_Hole_NotPlated;
@ -44,7 +47,12 @@ static bool CmpHoleSorting( const HOLE_INFO& a, const HOLE_INFO& b )
if( a.m_Hole_Diameter != b.m_Hole_Diameter )
return a.m_Hole_Diameter < b.m_Hole_Diameter;
// At this point (same diameter), sort by X then Y position.
// At this point (same diameter, same plated type), group by attribute
// type (via, pad, mechanical, although currently only not plated pads are mechanical)
if( a.m_HoleAttribute != b.m_HoleAttribute )
return a.m_HoleAttribute < b.m_HoleAttribute;
// At this point (same diameter, same type), sort by X then Y position.
// This is optimal for drilling and make the file reproducible as long as holes
// have not changed, even if the data order has changed.
if( a.m_Hole_Pos.x != b.m_Hole_Pos.x )
@ -79,6 +87,7 @@ void GENDRILL_WRITER_BASE::buildHolesList( DRILL_LAYER_PAIR aLayerPair,
continue;
new_hole.m_ItemParent = via;
new_hole.m_HoleAttribute = HOLE_ATTRIBUTE::HOLE_VIA;
new_hole.m_Tool_Reference = -1; // Flag value for Not initialized
new_hole.m_Hole_Orient = 0;
new_hole.m_Hole_Diameter = hole_sz;
@ -122,6 +131,9 @@ void GENDRILL_WRITER_BASE::buildHolesList( DRILL_LAYER_PAIR aLayerPair,
new_hole.m_ItemParent = pad;
new_hole.m_Hole_NotPlated = (pad->GetAttribute() == PAD_ATTRIB_NPTH);
new_hole.m_HoleAttribute = new_hole.m_Hole_NotPlated
? HOLE_ATTRIBUTE::HOLE_MECHANICAL
: HOLE_ATTRIBUTE::HOLE_PAD;
new_hole.m_Tool_Reference = -1; // Flag is: Not initialized
new_hole.m_Hole_Orient = pad->GetOrientation();
new_hole.m_Hole_Shape = 0; // hole shape: round
@ -141,26 +153,33 @@ void GENDRILL_WRITER_BASE::buildHolesList( DRILL_LAYER_PAIR aLayerPair,
}
// Sort holes per increasing diameter value (and for each dimater, by position)
sort( m_holeListBuffer.begin(), m_holeListBuffer.end(), CmpHoleSorting );
sort( m_holeListBuffer.begin(), m_holeListBuffer.end(), cmpHoleSorting );
// build the tool list
int last_hole = -1; // Set to not initialized (this is a value not used
// for m_holeListBuffer[ii].m_Hole_Diameter)
bool last_notplated_opt = false;
HOLE_ATTRIBUTE last_attribute = HOLE_ATTRIBUTE::HOLE_UNKNOWN;
DRILL_TOOL new_tool( 0, false );
unsigned jj;
for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
{
if( m_holeListBuffer[ii].m_Hole_Diameter != last_hole ||
m_holeListBuffer[ii].m_Hole_NotPlated != last_notplated_opt )
if( m_holeListBuffer[ii].m_Hole_Diameter != last_hole
|| m_holeListBuffer[ii].m_Hole_NotPlated != last_notplated_opt
#if USE_ATTRIB_FOR_HOLES
|| m_holeListBuffer[ii].m_HoleAttribute != last_attribute
#endif
)
{
new_tool.m_Diameter = m_holeListBuffer[ii].m_Hole_Diameter;
new_tool.m_Hole_NotPlated = m_holeListBuffer[ii].m_Hole_NotPlated;
new_tool.m_HoleAttribute = m_holeListBuffer[ii].m_HoleAttribute;
m_toolListBuffer.push_back( new_tool );
last_hole = new_tool.m_Diameter;
last_notplated_opt = new_tool.m_Hole_NotPlated;
last_attribute = new_tool.m_HoleAttribute;
}
jj = m_toolListBuffer.size();

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-2017 Jean_Pierre Charras <jp.charras at wanadoo.fr>
* Copyright (C) 1992-2017 KiCad Developers, see change_log.txt for contributors.
* Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -29,19 +29,37 @@
#ifndef GENDRILL_FILE_WRITER_BASE_H
#define GENDRILL_FILE_WRITER_BASE_H
// holes can have an attribute in Excellon drill files, similar to attributes
// in Gerber X2 format
// They are only comments for a better identification of holes (vias, pads...)
// Set to 1 to add these comments and 0 to not use these comments
#define USE_ATTRIB_FOR_HOLES 1
#include <vector>
class BOARD_ITEM;
// hole attribute, mainly to identify vias and pads and add this info as comment
// in NC drill files
enum class HOLE_ATTRIBUTE
{
HOLE_UNKNOWN, // uninitialized type
HOLE_VIA, // a via hole
HOLE_PAD, // a plated pad hole
HOLE_MECHANICAL // a mechanical (not plated) pad hole
};
// the DRILL_TOOL class handles tools used in the excellon drill file:
class DRILL_TOOL
{
public:
int m_Diameter; // the diameter of the used tool (for oblong, the smaller size)
int m_TotalCount; // how many times it is used (round and oblong)
int m_OvalCount; // oblong count
bool m_Hole_NotPlated; // Is the hole plated or not plated
int m_Diameter; // the diameter of the used tool
// (for oblong, the smaller size)
int m_TotalCount; // how many times it is used (round and oblong)
int m_OvalCount; // oblong count
bool m_Hole_NotPlated; // Is the hole plated or not plated
HOLE_ATTRIBUTE m_HoleAttribute; // Attribute (used in Excellon drill file)
public:
DRILL_TOOL( int aDiameter, bool a_NotPlated )
@ -50,6 +68,7 @@ public:
m_OvalCount = 0;
m_Diameter = aDiameter;
m_Hole_NotPlated = a_NotPlated;
m_HoleAttribute = HOLE_ATTRIBUTE::HOLE_UNKNOWN;
}
};
@ -60,6 +79,7 @@ public:
* Not plated holes are always through holes, and must be output on a specific drill file
* because they are drilled after the Pcb process is finished.
*/
class HOLE_INFO
{
public:
@ -74,6 +94,8 @@ public:
PCB_LAYER_ID m_Hole_Top_Layer; // hole starting layer (usually front layer):
// m_Hole_Top_Layer < m_Hole_Bottom_Layer
bool m_Hole_NotPlated; // hole not plated. Must be in a specific drill file or section
HOLE_ATTRIBUTE m_HoleAttribute; // Attribute, used in Excellon drill file and to sort holes
// by type.
public:
HOLE_INFO()
@ -86,6 +108,7 @@ public:
m_Hole_Shape = 0;
m_Hole_Bottom_Layer = B_Cu;
m_Hole_Top_Layer = F_Cu;
m_HoleAttribute = HOLE_ATTRIBUTE::HOLE_UNKNOWN;
}
};