Gerbview: NC drill file reader: add support for routing mode, used by latest Kicad version.

From master branch commit 4136aca221
This commit is contained in:
jean-pierre charras 2018-11-10 10:28:37 +01:00
parent 3f32f0be9e
commit 76adf7dbe8
7 changed files with 287 additions and 39 deletions

View File

@ -29,6 +29,8 @@
enum drill_M_code_t {
DRILL_M_UNKNOWN,
DRILL_M_END,
DRILL_M_TOOL_DOWN, // tool down (starting a routed hole)
DRILL_M_TOOL_UP, // tool up (ending a routed hole)
DRILL_M_ENDREWIND,
DRILL_M_MESSAGE,
DRILL_M_LONGMESSAGE,
@ -52,12 +54,13 @@ enum drill_M_code_t {
DRILL_AUTOMATIC_TOOL_CHANGE,
DRILL_FMT,
DRILL_SKIP,
DRILL_TOOL_INFORMATION
DRILL_TOOL_INFORMATION,
DRILL_M_END_LIST // not used: sentinel
};
enum drill_G_code_t {
DRILL_G_UNKNOWN,
DRILL_G_UNKNOWN = DRILL_M_END_LIST+1, // Use next available value
DRILL_G_ABSOLUTE,
DRILL_G_INCREMENTAL,
DRILL_G_ZEROSET,
@ -78,6 +81,47 @@ struct EXCELLON_CMD
int m_asParams; // 0 = no param, -1 = skip params, 1 = read params
};
// Helper struct to store Excellon points in routing mode
#define ROUTE_CCW 1
#define ROUTE_CW -1
struct EXCELLON_ROUTE_COORD
{
int m_x; // X coordinate
int m_y; // y coordinate
int m_cx; // center X coordinate in circular routing mode
// (when the IJ commad is used)
int m_cy; // center y coordinate in circular routing mode
// (when the IJ commad is used)
int m_radius; // radius in circular routing mode (when the A## command is used)
int m_rmode; // routing mode: 0 = circular, ROUTE_CCW (1) = ccw, ROUTE_CW (-1) = cw
int m_arc_type_info; // arc using radius or center coordinates
EXCELLON_ROUTE_COORD():
m_x( 0 ), m_y( 0 ), m_cx( 0 ), m_cy( 0 ), m_radius( 0 ),
m_rmode( 0 ), m_arc_type_info( 0 )
{}
EXCELLON_ROUTE_COORD( const wxPoint& aPos ):
m_x( aPos.x ), m_y( aPos.y ),
m_cx( 0 ), m_cy( 0 ), m_radius( 0 ), m_rmode( 0 ),
m_arc_type_info( ARC_INFO_TYPE_NONE )
{}
EXCELLON_ROUTE_COORD( const wxPoint& aPos, const wxPoint& aCenter, int aMode ):
m_x( aPos.x ), m_y( aPos.y ),
m_cx( aCenter.x ), m_cy( aCenter.y ), m_radius( 0 ), m_rmode( aMode ),
m_arc_type_info( ARC_INFO_TYPE_CENTER )
{}
EXCELLON_ROUTE_COORD( const wxPoint& aPos, int aRadius, int aMode ):
m_x( aPos.x ), m_y( aPos.y ),
m_cx( 0 ), m_cy( 0 ), m_radius( aRadius ), m_rmode( aMode ),
m_arc_type_info( ARC_INFO_TYPE_RADIUS )
{}
wxPoint GetPos() { return wxPoint( m_x, m_y ); }
};
/* EXCELLON_IMAGE handle a drill image
* It is derived from GERBER_FILE_IMAGE because there is a lot of likeness
@ -96,12 +140,16 @@ private:
excellon_state m_State; // state of excellon file analysis
bool m_SlotOn; // true during an oblong drill definition
// by G85 (canned slot) command
bool m_RouteModeOn; // true during a route mode (for instance a oval hole) or a cutout
std::vector<EXCELLON_ROUTE_COORD> m_RoutePositions; // The list of points in a route mode
public: EXCELLON_IMAGE( int layer ) :
GERBER_FILE_IMAGE( layer )
{
m_State = READ_HEADER_STATE;
m_SlotOn = false;
m_RouteModeOn = false;
}
@ -124,7 +172,7 @@ public: EXCELLON_IMAGE( int layer ) :
bool LoadFile( const wxString& aFullFileName );
private:
bool Execute_HEADER_Command( char*& text );
bool Execute_HEADER_And_M_Command( char*& text );
bool Select_Tool( char*& text );
bool Execute_EXCELLON_G_Command( char*& text );
bool Execute_Drill_Command( char*& text );
@ -233,10 +281,10 @@ private:
* B# Retract Rate
* C# Tool Diameter
* F# Table Feed Rate;Z Axis Infeed Rate
* G00X#Y# Route Mode
* G01 Linear (Straight Line) Mode
* G02 Circular CW Mode
* G03 Circular CCW Mode
* G00X#Y# Route Mode; XY is the starting point
* G01X#Y# Linear (Straight Line) Route Mode YX is the ending point
* G02X#Y#... Circular CW Mode. Radius value (A#) or Center position (I#J#) follows
* G03X#Y#... Circular CCW Mode. Radius value (A#) or Center position (I#J#) follows
* G04 X# Variable Dwell
* G05 Drill Mode
* G07 Override current tool feed or speed

View File

@ -87,23 +87,103 @@ static const int fmtMantissaInch = 4;
static const int fmtIntegerMM = 3;
static const int fmtIntegerInch = 2;
// A helper function to calculate the arc center of an arc
// known by 2 end points, the radius, and the angle direction (CW or CCW)
// Arc angles are <= 180 degrees in circular interpol.
static wxPoint computeCenter(wxPoint aStart, wxPoint aEnd, int& aRadius, bool aRotCCW )
{
wxPoint center;
VECTOR2D end;
end.x = double(aEnd.x - aStart.x);
end.y = double(aEnd.y - aStart.y);
// Be sure aRadius/2 > dist between aStart and aEnd
double min_radius = end.EuclideanNorm() * 2;
if( min_radius <= aRadius )
{
// Adjust the radius and the arc center for a 180 deg arc between end points
aRadius = KiROUND( min_radius );
center.x = ( aStart.x + aEnd.x + 1 ) / 2;
center.y = ( aStart.y + aEnd.y + 1 ) / 2;
return center;
}
/* to compute the centers position easily:
* rotate the segment (0,0 to end.x,end.y) to make it horizontal (end.y = 0).
* the X center position is end.x/2
* the Y center positions are on the vertical line starting at end.x/2, 0
* and solve aRadius^2 = X^2 + Y^2 (2 values)
*/
double seg_angle = end.Angle(); //in radian
VECTOR2D h_segm = end.Rotate( - seg_angle );
double cX = h_segm.x/2;
double cY1 = sqrt( (double)aRadius*aRadius - cX*cX );
double cY2 = -cY1;
VECTOR2D center1( cX, cY1 );
center1 = center1.Rotate( seg_angle );
double arc_angle1 = (end - center1).Angle() - (VECTOR2D(0.0,0.0) - center1).Angle();
VECTOR2D center2( cX, cY2 );
center2 = center2.Rotate( seg_angle );
double arc_angle2 = (end - center2).Angle() - (VECTOR2D(0.0,0.0) - center2).Angle();
if( !aRotCCW )
{
if( arc_angle1 < 0.0 )
arc_angle1 += 2*M_PI;
if( arc_angle2 < 0.0 )
arc_angle2 += 2*M_PI;
}
else
{
if( arc_angle1 > 0.0 )
arc_angle1 -= 2*M_PI;
if( arc_angle2 > 0.0 )
arc_angle2 -= 2*M_PI;
}
// Arc angle must be <= 180.0 degrees.
// So choose the center that create a arc angle <= 180.0
if( std::abs( arc_angle1 ) <= M_PI )
{
center.x = KiROUND( center1.x );
center.y = KiROUND( center1.y );
}
else
{
center.x = KiROUND( center2.x );
center.y = KiROUND( center2.y );
}
return center+aStart;
}
extern int ReadInt( char*& text, bool aSkipSeparator = true );
extern double ReadDouble( char*& text, bool aSkipSeparator = true );
// See ds274d.cpp:
// See rs274d.cpp:
extern void fillFlashedGBRITEM( GERBER_DRAW_ITEM* aGbrItem,
APERTURE_T aAperture,
int Dcode_index,
const wxPoint& aPos,
wxSize aSize,
bool aLayerNegative );
void fillLineGBRITEM( GERBER_DRAW_ITEM* aGbrItem,
extern void fillLineGBRITEM( GERBER_DRAW_ITEM* aGbrItem,
int Dcode_index,
const wxPoint& aStart,
const wxPoint& aEnd,
wxSize aPenSize,
bool aLayerNegative );
extern void fillArcGBRITEM( GERBER_DRAW_ITEM* aGbrItem, int Dcode_index,
const wxPoint& aStart, const wxPoint& aEnd,
const wxPoint& aRelCenter, wxSize aPenSize,
bool aClockwise, bool aMultiquadrant,
bool aLayerNegative );
// Gerber X2 files have a file attribute which specify the type of image
// (copper, solder paste ... and sides tpo, bottom or inner copper layers)
// Excellon drill files do not have attributes, so, just to identify the image
@ -114,6 +194,9 @@ static EXCELLON_CMD excellonHeaderCmdList[] =
{
{ "M0", DRILL_M_END, -1 }, // End of Program - No Rewind
{ "M00", DRILL_M_END, -1 }, // End of Program - No Rewind
{ "M15", DRILL_M_TOOL_DOWN, 0 }, // tool down (starting a routed hole)
{ "M16", DRILL_M_TOOL_UP, 0 }, // tool up (ending a routed hole)
{ "M17", DRILL_M_TOOL_UP, 0 }, // tool up similar to M16 for a viewer
{ "M30", DRILL_M_ENDREWIND, -1 }, // End of Program Rewind
{ "M47", DRILL_M_MESSAGE, -1 }, // Operator Message
{ "M45", DRILL_M_LONGMESSAGE, -1 }, // Long Operator message (use more than one line)
@ -149,10 +232,10 @@ static EXCELLON_CMD excellon_G_CmdList[] =
{ "G90", DRILL_G_ZEROSET, 0 }, // Absolute Mode
{ "G00", DRILL_G_ROUT, 1 }, // Route Mode
{ "G05", DRILL_G_DRILL, 0 }, // Drill Mode
{ "G85", DRILL_G_SLOT, 0 }, // Drill Mode slot (oval holes)
{ "G01", DRILL_G_LINEARMOVE, 0 }, // Linear (Straight Line) Mode
{ "G02", DRILL_G_CWMOVE, 0 }, // Circular CW Mode
{ "G03", DRILL_G_CCWMOVE, 0 }, // Circular CCW Mode
{ "G85", DRILL_G_SLOT, 0 }, // Canned Mode slot (oval holes)
{ "G01", DRILL_G_LINEARMOVE, 1 }, // Linear (Straight Line) routing Mode
{ "G02", DRILL_G_CWMOVE, 1 }, // Circular CW Mode
{ "G03", DRILL_G_CCWMOVE, 1 }, // Circular CCW Mode
{ "G93", DRILL_G_ZERO_SET, 1 }, // Zero Set (XnnYmm and coordintes origin)
{ "", DRILL_G_UNKNOWN, 0 }, // last item in list
};
@ -264,14 +347,14 @@ bool EXCELLON_IMAGE::LoadFile( const wxString & aFullFileName )
if( m_State == EXCELLON_IMAGE::READ_HEADER_STATE )
{
Execute_HEADER_Command( text );
Execute_HEADER_And_M_Command( text );
}
else
{
switch( *text )
{
case 'M':
Execute_HEADER_Command( text );
Execute_HEADER_And_M_Command( text );
break;
case 'G': /* Line type Gxx : command */
@ -324,7 +407,7 @@ bool EXCELLON_IMAGE::LoadFile( const wxString & aFullFileName )
}
bool EXCELLON_IMAGE::Execute_HEADER_Command( char*& text )
bool EXCELLON_IMAGE::Execute_HEADER_And_M_Command( char*& text )
{
EXCELLON_CMD* cmd = NULL;
wxString msg;
@ -465,6 +548,55 @@ bool EXCELLON_IMAGE::Execute_HEADER_Command( char*& text )
case DRILL_TOOL_INFORMATION:
readToolInformation( text );
break;
case DRILL_M_TOOL_DOWN: // tool down (starting a routed hole or polyline)
// Only the last position is usefull:
if( m_RoutePositions.size() > 1 )
m_RoutePositions.erase( m_RoutePositions.begin(), m_RoutePositions.begin() + m_RoutePositions.size() - 1 );
break;
case DRILL_M_TOOL_UP: // tool up (ending a routed polyline)
{
D_CODE* tool = GetDCODE( m_Current_Tool );
for( size_t ii = 1; ii < m_RoutePositions.size(); ii++ )
{
GERBER_DRAW_ITEM* gbritem = new GERBER_DRAW_ITEM( this );
if( m_RoutePositions[ii].m_rmode == 0 ) // linear routing
{
fillLineGBRITEM( gbritem, tool->m_Num_Dcode,
m_RoutePositions[ii-1].GetPos(), m_RoutePositions[ii].GetPos(),
tool->m_Size, false );
}
else // circular (cw or ccw) routing
{
bool rot_ccw = m_RoutePositions[ii].m_rmode == ROUTE_CW;
int radius = m_RoutePositions[ii].m_radius; // Can be adjusted by computeCenter.
wxPoint center;
if( m_RoutePositions[ii].m_arc_type_info == ARC_INFO_TYPE_CENTER )
center = wxPoint( m_RoutePositions[ii].m_cx, m_RoutePositions[ii].m_cy );
else
center = computeCenter( m_RoutePositions[ii-1].GetPos(),
m_RoutePositions[ii].GetPos(), radius, rot_ccw );
fillArcGBRITEM( gbritem, tool->m_Num_Dcode,
m_RoutePositions[ii-1].GetPos(), m_RoutePositions[ii].GetPos(),
center - m_RoutePositions[ii-1].GetPos(),
tool->m_Size, not rot_ccw , true,
false );
}
m_Drawings.Append( gbritem );
StepAndRepeatItem( *gbritem );
}
m_RoutePositions.clear();
}
break;
}
while( *text )
@ -537,11 +669,12 @@ bool EXCELLON_IMAGE::Execute_Drill_Command( char*& text )
switch( *text )
{
case 'X':
ReadXYCoord( text, true );
break;
case 'Y':
ReadXYCoord( text, true );
if( *text == 'I' || *text == 'J' )
ReadIJCoord( text );
break;
case 'G': // G85 is found here for oval holes
@ -550,8 +683,31 @@ bool EXCELLON_IMAGE::Execute_Drill_Command( char*& text )
break;
case 0: // E.O.L: execute command
tool = GetDCODE( m_Current_Tool );
if( m_RouteModeOn )
{
// We are in routing mode, and this is an intermediate point.
// So just store it
int rmode = 0; // linear routing.
if( m_Iterpolation == GERB_INTERPOL_ARC_NEG )
rmode = ROUTE_CW;
else if( m_Iterpolation == GERB_INTERPOL_ARC_POS )
rmode = ROUTE_CCW;
if( m_LastArcDataType == ARC_INFO_TYPE_CENTER )
{
EXCELLON_ROUTE_COORD point( m_CurrentPos, m_IJPos, rmode );
m_RoutePositions.push_back( point );
}
else
{
EXCELLON_ROUTE_COORD point( m_CurrentPos, m_ArcRadius, rmode );
m_RoutePositions.push_back( point );
}
return true;
}
tool = GetDCODE( m_Current_Tool );
if( !tool )
{
wxString msg;
@ -666,12 +822,19 @@ bool EXCELLON_IMAGE::Execute_EXCELLON_G_Command( char*& text )
case DRILL_G_ROUT:
m_SlotOn = false;
m_PolygonFillMode = true;
m_RouteModeOn = true;
m_RoutePositions.clear();
m_LastArcDataType = ARC_INFO_TYPE_NONE;
ReadXYCoord( text, true );
// This is the first point (starting point) of routing
m_RoutePositions.push_back( EXCELLON_ROUTE_COORD( m_CurrentPos ) );
break;
case DRILL_G_DRILL:
m_SlotOn = false;
m_PolygonFillMode = false;
m_RouteModeOn = false;
m_RoutePositions.clear();
m_LastArcDataType = ARC_INFO_TYPE_NONE;
break;
case DRILL_G_SLOT:
@ -679,15 +842,36 @@ bool EXCELLON_IMAGE::Execute_EXCELLON_G_Command( char*& text )
break;
case DRILL_G_LINEARMOVE:
m_LastArcDataType = ARC_INFO_TYPE_NONE;
m_Iterpolation = GERB_INTERPOL_LINEAR_1X;
ReadXYCoord( text, true );
m_RoutePositions.push_back( EXCELLON_ROUTE_COORD( m_CurrentPos ) );
break;
case DRILL_G_CWMOVE:
m_Iterpolation = GERB_INTERPOL_ARC_NEG;
ReadXYCoord( text, true );
if( *text == 'I' || *text == 'J' )
ReadIJCoord( text );
if( m_LastArcDataType == ARC_INFO_TYPE_CENTER )
m_RoutePositions.push_back( EXCELLON_ROUTE_COORD( m_CurrentPos, m_IJPos, ROUTE_CW ) );
else
m_RoutePositions.push_back( EXCELLON_ROUTE_COORD( m_CurrentPos, m_ArcRadius, ROUTE_CW ) );
break;
case DRILL_G_CCWMOVE:
m_Iterpolation = GERB_INTERPOL_ARC_POS;
ReadXYCoord( text, true );
if( *text == 'I' || *text == 'J' )
ReadIJCoord( text );
if( m_LastArcDataType == ARC_INFO_TYPE_CENTER )
m_RoutePositions.push_back( EXCELLON_ROUTE_COORD( m_CurrentPos, m_IJPos, ROUTE_CCW ) );
else
m_RoutePositions.push_back( EXCELLON_ROUTE_COORD( m_CurrentPos, m_ArcRadius, ROUTE_CCW ) );
break;
case DRILL_G_ABSOLUTE:
@ -700,15 +884,12 @@ bool EXCELLON_IMAGE::Execute_EXCELLON_G_Command( char*& text )
case DRILL_G_UNKNOWN:
default:
{
wxString msg;
msg.Printf( _( "Unknown Excellon G Code: &lt;%s&gt;" ), GetChars(FROM_UTF8(gcmd)) );
AddMessageToList( msg );
AddMessageToList( wxString::Format( _( "Unknown Excellon G Code: &lt;%s&gt;" ), FROM_UTF8(gcmd) ) );
while( *text )
text++;
return false;
}
}
return success;
}

View File

@ -219,6 +219,10 @@ void GERBER_FILE_IMAGE::ResetDefaultValues()
m_PreviousPos.x = m_PreviousPos.y = 0; // last specified coord
m_IJPos.x = m_IJPos.y = 0; // current centre coord for
// plot arcs & circles
m_ArcRadius = 0; // radius of arcs in circular interpol (given by A## command).
// in command like X##Y##A##
m_LastArcDataType = ARC_INFO_TYPE_NONE; // Extra coordinate info type for arcs
// (radius or IJ center coord)
m_LineNum = 0; // line number in file being read
m_Current_File = NULL; // Gerber file to read
m_PolygonFillMode = false;

View File

@ -65,6 +65,15 @@ class GERBER_FILE_IMAGE;
class X2_ATTRIBUTE;
class X2_ATTRIBUTE_FILEFUNCTION;
// For arcs, coordinates need 3 info: start point, end point and center or radius
// In Excellon files it can be a A## value (radius) or I#J# center coordinates (like in gerber)
// We need to know the last read type when reading a list of routing coordinates
enum LAST_EXTRA_ARC_DATA_TYPE
{
ARC_INFO_TYPE_NONE,
ARC_INFO_TYPE_CENTER, // last info is a IJ command: arc center is given
ARC_INFO_TYPE_RADIUS, // last info is a A command: arc radius is given
};
class GERBER_LAYER
{
@ -145,7 +154,8 @@ public:
wxPoint m_CurrentPos; // current specified coord for plot
wxPoint m_PreviousPos; // old current specified coord for plot
wxPoint m_IJPos; // IJ coord (for arcs & circles )
int m_ArcRadius; // A value ( = radius in circular routing in Excellon files )
LAST_EXTRA_ARC_DATA_TYPE m_LastArcDataType; // Identifier for arc data type (IJ (center) or A## (radius))
FILE* m_Current_File; // Current file to read
int m_Selected_Tool; // For hightlight: current selected Dcode

View File

@ -89,7 +89,7 @@ wxPoint GERBER_FILE_IMAGE::ReadXYCoord( char*& Text, bool aExcellonMode )
text = line;
while( *Text )
{
if( (*Text == 'X') || (*Text == 'Y') )
if( (*Text == 'X') || (*Text == 'Y') || (*Text == 'A') )
{
type_coord = *Text;
Text++;
@ -111,7 +111,7 @@ wxPoint GERBER_FILE_IMAGE::ReadXYCoord( char*& Text, bool aExcellonMode )
if( is_float )
{
// When X or Y values are float numbers, they are given in mm or inches
// When X or Y (or A) values are float numbers, they are given in mm or inches
if( m_GerbMetric ) // units are mm
current_coord = KiROUND( atof( line ) * IU_PER_MILS / 0.0254 );
else // units are inches
@ -160,6 +160,11 @@ wxPoint GERBER_FILE_IMAGE::ReadXYCoord( char*& Text, bool aExcellonMode )
pos.x = current_coord;
else if( type_coord == 'Y' )
pos.y = current_coord;
else if( type_coord == 'A' )
{
m_ArcRadius = current_coord;
m_LastArcDataType = ARC_INFO_TYPE_RADIUS;
}
continue;
}
@ -263,6 +268,8 @@ wxPoint GERBER_FILE_IMAGE::ReadIJCoord( char*& Text )
}
m_IJPos = pos;
m_LastArcDataType = ARC_INFO_TYPE_CENTER;
return pos;
}

View File

@ -208,11 +208,11 @@ void fillLineGBRITEM( GERBER_DRAW_ITEM* aGbrItem,
* false when arc is inside one quadrant
* @param aLayerNegative = true if the current layer is negative
*/
static void fillArcGBRITEM( GERBER_DRAW_ITEM* aGbrItem, int Dcode_index,
const wxPoint& aStart, const wxPoint& aEnd,
const wxPoint& aRelCenter, wxSize aPenSize,
bool aClockwise, bool aMultiquadrant,
bool aLayerNegative )
void fillArcGBRITEM( GERBER_DRAW_ITEM* aGbrItem, int Dcode_index,
const wxPoint& aStart, const wxPoint& aEnd,
const wxPoint& aRelCenter, wxSize aPenSize,
bool aClockwise, bool aMultiquadrant,
bool aLayerNegative )
{
wxPoint center, delta;

View File

@ -250,11 +250,10 @@ bool GERBER_FILE_IMAGE::ExecuteRS274XCommand( int aCommand, char* aBuff,
// conv_scale = scaling factor from inch to Internal Unit
double conv_scale = IU_PER_MILS * 1000;
if( m_GerbMetric )
conv_scale /= 25.4;
// DBG( printf( "%22s: Command <%c%c>\n", __func__, (command >> 8) & 0xFF, command & 0xFF ); )
switch( aCommand )
{
case FORMAT_STATEMENT:
@ -670,8 +669,7 @@ bool GERBER_FILE_IMAGE::ExecuteRS274XCommand( int aCommand, char* aBuff,
m_ImageNegative = true;
else
m_ImageNegative = false;
DBG( printf( "%22s: IMAGE_POLARITY m_ImageNegative=%s\n", __func__,
m_ImageNegative ? "true" : "false" ); )
break;
case LOAD_POLARITY: