/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 1992-2016 Jean-Pierre Charras * 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 * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /* * Here is a sample of drill files created by Pcbnew, in decimal format: * (Note: coordinates formats are same as Gerber, and T commands are near Gerber D commands). * M48 * ;DRILL file {PCBnew (2011-03-14 BZR 2894)-testing} date 15/03/2011 14:23:22 * ;FORMAT={-:-/ absolute / inch / decimal} * FMAT,2 * INCH,TZ * T1C0.02 * T2C0.032 * % * G90 * G05 * M72 * T1 * X1.580Y-1.360 * X1.580Y-4.860 * X8.680Y-1.360 * X8.680Y-4.860 * T2 * X2.930Y-3.560 * X5.280Y-2.535 * X5.405Y-2.610 * X5.620Y-2.900 * T0 * M30 */ /* * Note there are some variant of tool definition: * T1F00S00C0.2 or T1C0.02F00S00 ... Feed Rate and Spindle Speed of Tool 1 * Feed Rate and Spindle Speed are just skipped because they are not used in a viewer */ /** * @file excellon_read_drill_file.cpp * Functions to read drill files (EXCELLON format) created by Pcbnew * These files use only a subset of EXCELLON commands. */ #include // for KiROUND #include // for RotatePoint #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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 VECTOR2I computeCenter( VECTOR2I aStart, VECTOR2I aEnd, int& aRadius, bool aRotCCW ) { VECTOR2I 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) */ EDA_ANGLE seg_angle( end ); VECTOR2D h_segm = end; RotatePoint( h_segm, seg_angle ); double cX = h_segm.x/2; double cY1 = sqrt( (double)aRadius*aRadius - cX*cX ); double cY2 = -cY1; VECTOR2D center1( cX, cY1 ); RotatePoint( center1, -seg_angle ); EDA_ANGLE arc_angle1 = EDA_ANGLE( end - center1 ) - EDA_ANGLE( VECTOR2D( 0.0, 0.0 ) - center1 ); VECTOR2D center2( cX, cY2 ); RotatePoint( center2, -seg_angle ); EDA_ANGLE arc_angle2 = EDA_ANGLE( end - center2 ) - EDA_ANGLE( VECTOR2D( 0.0, 0.0 ) - center2 ); if( !aRotCCW ) { if( arc_angle1 < ANGLE_0 ) arc_angle1 += ANGLE_360; if( arc_angle2 < ANGLE_0 ) arc_angle2 += ANGLE_360; } else { if( arc_angle1 > ANGLE_0 ) arc_angle1 -= ANGLE_360; if( arc_angle2 > ANGLE_0 ) arc_angle2 -= ANGLE_360; } // Arc angle must be <= 180.0 degrees. // So choose the center that create a arc angle <= 180.0 if( std::abs( arc_angle1 ) <= ANGLE_180 ) { 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 rs274d.cpp: extern void fillFlashedGBRITEM( GERBER_DRAW_ITEM* aGbrItem, APERTURE_T aAperture, int Dcode_index, const VECTOR2I& aPos, wxSize aSize, bool aLayerNegative ); extern void fillLineGBRITEM( GERBER_DRAW_ITEM* aGbrItem, int Dcode_index, const VECTOR2I& aStart, const VECTOR2I& aEnd, wxSize aPenSize, bool aLayerNegative ); extern void fillArcGBRITEM( GERBER_DRAW_ITEM* aGbrItem, int Dcode_index, const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& 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 // In gerbview, we add this attribute, similar to a Gerber drill file static const char file_attribute[] = ".FileFunction,Other,Drill*"; 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_ENDFILE, -1 }, // End of File (last line of NC drill) { "M47", DRILL_M_MESSAGE, -1 }, // Operator Message { "M45", DRILL_M_LONGMESSAGE, -1 }, // Long Operator message (use more than one line) { "M48", DRILL_M_HEADER, 0 }, // beginning of a header { "M95", DRILL_M_ENDHEADER, 0 }, // End of the header { "METRIC", DRILL_METRIC_HEADER, 1 }, { "INCH", DRILL_IMPERIAL_HEADER, 1 }, { "M71", DRILL_M_METRIC, 1 }, { "M72", DRILL_M_IMPERIAL, 1 }, { "M25", DRILL_M_BEGINPATTERN, 0 }, // Beginning of Pattern { "M01", DRILL_M_ENDPATTERN, 0 }, // End of Pattern { "M97", DRILL_M_CANNEDTEXT, -1 }, { "M98", DRILL_M_CANNEDTEXT, -1 }, { "DETECT", DRILL_DETECT_BROKEN, -1 }, { "ICI", DRILL_INCREMENTALHEADER, 1 }, { "FMAT", DRILL_FMT, 1 }, // Use Format command { ";FILE_FORMAT", DRILL_FORMAT_ALTIUM, 1 }, // Use Format command { ";", DRILL_HEADER_SKIP, 0 }, // Other ; hints that we don't implement { "ATC", DRILL_AUTOMATIC_TOOL_CHANGE, 0 }, { "TCST", DRILL_TOOL_CHANGE_STOP, 0 }, // Tool Change Stop { "AFS", DRILL_AUTOMATIC_SPEED, 0 }, // Automatic Feeds and Speeds { "VER", DRILL_AXIS_VERSION, 1 }, // Selection of X and Y Axis Version { "R", DRILL_RESET_CMD, -1 }, // Reset commands { "%", DRILL_REWIND_STOP, -1 }, // Rewind stop. End of the header { "/", DRILL_SKIP, -1 }, // Clear Tool Linking. End of the header // Keep this item after all commands starting by 'T': { "T", DRILL_TOOL_INFORMATION, 0 }, // Tool Information { "", DRILL_M_UNKNOWN, 0 } // last item in list }; static EXCELLON_CMD excellon_G_CmdList[] = { { "G90", DRILL_G_ABSOLUTE, 0 }, // Absolute Mode { "G91", DRILL_G_INCREMENTAL, 0 }, // Incremental Input Mode { "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 }, // 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 coordinates origin) { "", DRILL_G_UNKNOWN, 0 }, // last item in list }; bool GERBVIEW_FRAME::Read_EXCELLON_File( const wxString& aFullFileName ) { wxString msg; int layerId = GetActiveLayer(); // current layer used in GerbView GERBER_FILE_IMAGE_LIST* images = GetGerberLayout()->GetImagesList(); GERBER_FILE_IMAGE* gerber_layer = images->GetGbrImage( layerId ); // If the active layer contains old gerber or nc drill data, remove it if( gerber_layer ) Erase_Current_DrawLayer( false ); std::unique_ptr drill_layer_uptr = std::make_unique( layerId ); EXCELLON_DEFAULTS nc_defaults; GERBVIEW_SETTINGS* cfg = static_cast( config() ); cfg->GetExcellonDefaults( nc_defaults ); // Read the Excellon drill file: bool success = drill_layer_uptr->LoadFile( aFullFileName, &nc_defaults ); if( !success ) { drill_layer_uptr.reset(); msg.Printf( _( "File %s not found." ), aFullFileName ); ShowInfoBarError( msg ); return false; } EXCELLON_IMAGE* drill_layer = drill_layer_uptr.release(); layerId = images->AddGbrImage( drill_layer, layerId ); if( layerId < 0 ) { delete drill_layer; ShowInfoBarError( _( "No empty layers to load file into." ) ); return false; } // Display errors list if( drill_layer->GetMessages().size() > 0 ) { HTML_MESSAGE_BOX dlg( this, _( "Error reading EXCELLON drill file" ) ); dlg.ListSet( drill_layer->GetMessages() ); dlg.ShowModal(); } if( GetCanvas() ) { for( GERBER_DRAW_ITEM* item : drill_layer->GetItems() ) GetCanvas()->GetView()->Add( (KIGFX::VIEW_ITEM*) item ); } return success; } void EXCELLON_IMAGE::ResetDefaultValues() { GERBER_FILE_IMAGE::ResetDefaultValues(); SelectUnits( false, nullptr ); // Default unit = inch m_hasFormat = false; // will be true if a Altium file containing // the nn:mm file format is read // Files using non decimal can use No Trailing zeros or No leading Zeros // Unfortunately, the identifier (INCH,TZ or INCH,LZ for instance) is not // always set in drill files. // The option leading zeros looks like more frequent, so use this default m_NoTrailingZeros = true; } /* * Original function derived from drill_file_p() of gerbv 2.7.0. * Copyright of the source file drill.cpp included below: */ /* * gEDA - GNU Electronic Design Automation * drill.c * Copyright (C) 2000-2006 Andreas Andersson * * $Id$ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ bool EXCELLON_IMAGE::TestFileIsExcellon( const wxString& aFullFileName ) { char* letter; bool foundM48 = false; bool foundM30 = false; bool foundPercent = false; bool foundT = false; bool foundX = false; bool foundY = false; FILE* file = wxFopen( aFullFileName, "rb" ); if( file == nullptr ) return false; FILE_LINE_READER excellonReader( file, aFullFileName ); try { while( true ) { if( excellonReader.ReadLine() == nullptr ) break; // Remove all whitespace from the beginning and end char* line = StrPurge( excellonReader.Line() ); // Skip empty lines if( *line == 0 ) continue; // Check that file is not binary (non-printing chars) for( size_t i = 0; i < strlen( line ); i++ ) { if( !isascii( line[i] ) ) return false; } // We don't want to look for any commands after a comment so // just end the line early if we find a comment char* buf = strstr( line, ";" ); if( buf != nullptr ) *buf = 0; // Check for M48 = start of drill header if( strstr( line, "M48" ) ) foundM48 = true; // Check for M30 = end of drill program if( strstr( line, "M30" ) ) if( foundPercent ) foundM30 = true; // Found M30 after % = good // Check for % on its own line at end of header if( ( letter = strstr( line, "%" ) ) != nullptr ) if( ( letter[1] == '\r' ) || ( letter[1] == '\n' ) ) foundPercent = true; // Check for T if( ( letter = strstr( line, "T" ) ) != nullptr ) { if( !foundT && ( foundX || foundY ) ) foundT = false; /* Found first T after X or Y */ else { // verify next char is digit if( isdigit( letter[1] ) ) foundT = true; } } // look for X or Y if( ( letter = strstr( line, "X" ) ) != nullptr ) if( isdigit( letter[1] ) ) foundX = true; if( ( letter = strstr( line, "Y" ) ) != nullptr ) if( isdigit( letter[1] ) ) foundY = true; } } catch( IO_ERROR& ) { return false; } /* Now form logical expression determining if this is a drill file */ if( ( ( foundX || foundY ) && foundT ) && ( foundM48 || ( foundPercent && foundM30 ) ) ) return true; else if( foundM48 && foundT && foundPercent && foundM30 ) /* Pathological case of drill file with valid header and EOF but no drill XY locations. */ return true; return false; } /* * Read a EXCELLON file. * Gerber classes are used because there is likeness between Gerber files * and Excellon files * DCode can easily store T code (tool size) as round (or oval) shape * Drill commands are similar to flashed gerber items * Routing commands are similar to Gerber polygons * coordinates have the same format as Gerber, can be given in: * decimal format (i.i. floating notation format) * integer 2.4 format in imperial units, * integer 3.2 or 3.3 format (metric units). */ bool EXCELLON_IMAGE::LoadFile( const wxString & aFullFileName, EXCELLON_DEFAULTS* aDefaults ) { // Set the default parameter values: ResetDefaultValues(); ClearMessageList(); m_Current_File = wxFopen( aFullFileName, wxT( "rt" ) ); if( m_Current_File == nullptr ) return false; // Initial format setting, usualy defined in file, but not always... m_NoTrailingZeros = aDefaults->m_LeadingZero; m_GerbMetric = aDefaults->m_UnitsMM; wxString msg; m_FileName = aFullFileName; LOCALE_IO toggleIo; // FILE_LINE_READER will close the file. FILE_LINE_READER excellonReader( m_Current_File, m_FileName ); while( true ) { if( excellonReader.ReadLine() == nullptr ) break; char* line = excellonReader.Line(); char* text = StrPurge( line ); if( *text == 0 ) // Skip empty lines continue; if( m_State == EXCELLON_IMAGE::READ_HEADER_STATE ) { Execute_HEADER_And_M_Command( text ); // Now units (inch/mm) are known, set the coordinate format SelectUnits( m_GerbMetric, aDefaults ); } else { switch( *text ) { case ';': case 'M': Execute_HEADER_And_M_Command( text ); break; case 'G': // Line type Gxx : command Execute_EXCELLON_G_Command( text ); break; case 'X': case 'Y': // command like X12550Y19250 Execute_Drill_Command(text); break; case 'I': case 'J': /* Auxiliary Move command */ m_IJPos = ReadIJCoord( text ); if( *text == '*' ) // command like X35142Y15945J504 { Execute_Drill_Command( text); } break; case 'T': // Select Tool command (can also create // the tool with an embedded definition) Select_Tool( text ); break; case '%': break; default: msg.Printf( wxT( "Unexpected symbol 0x%2.2X <%c>" ), *text, *text ); AddMessageToList( msg ); break; } // End switch } } // Add our file attribute, to identify the drill file X2_ATTRIBUTE dummy; char* text = (char*)file_attribute; int dummyline = 0; dummy.ParseAttribCmd( nullptr, nullptr, 0, text, dummyline ); delete m_FileFunction; m_FileFunction = new X2_ATTRIBUTE_FILEFUNCTION( dummy ); m_InUse = true; return true; } bool EXCELLON_IMAGE::Execute_HEADER_And_M_Command( char*& text ) { EXCELLON_CMD* cmd = nullptr; wxString msg; // Search command in list for( unsigned ii = 0; ; ii++ ) { EXCELLON_CMD* candidate = &excellonHeaderCmdList[ii]; int len = candidate->m_Name.size(); if( len == 0 ) // End of list reached break; if( candidate->m_Name.compare( 0, len, text, len ) == 0 ) // found. { cmd = candidate; text += len; break; } } if( !cmd ) { msg.Printf( _( "Unknown Excellon command <%s>" ), text ); AddMessageToList( msg ); while( *text ) text++; return false; } // Execute command // some do nothing switch( cmd->m_Code ) { case DRILL_SKIP: case DRILL_M_UNKNOWN: break; case DRILL_M_END: case DRILL_M_ENDFILE: // if a route command is in progress, finish it if( m_RouteModeOn ) FinishRouteCommand(); break; case DRILL_M_MESSAGE: break; case DRILL_M_LONGMESSAGE: break; case DRILL_M_HEADER: m_State = READ_HEADER_STATE; break; case DRILL_M_ENDHEADER: m_State = READ_PROGRAM_STATE; break; case DRILL_REWIND_STOP: // End of header. No action in a viewer m_State = READ_PROGRAM_STATE; break; case DRILL_FORMAT_ALTIUM: readFileFormat( text ); break; case DRILL_HEADER_SKIP: break; case DRILL_M_METRIC: SelectUnits( true, nullptr ); break; case DRILL_IMPERIAL_HEADER: // command like INCH,TZ or INCH,LZ case DRILL_METRIC_HEADER: // command like METRIC,TZ or METRIC,LZ SelectUnits( cmd->m_Code == DRILL_METRIC_HEADER ? true : false, nullptr ); if( *text != ',' ) { // No TZ or LZ specified. Should be a decimal format // but this is not always the case. Use our default setting break; } text++; // skip separator if( *text == 'T' ) m_NoTrailingZeros = false; else m_NoTrailingZeros = true; break; case DRILL_M_BEGINPATTERN: break; case DRILL_M_ENDPATTERN: break; case DRILL_M_CANNEDTEXT: break; case DRILL_M_TIPCHECK: break; case DRILL_DETECT_BROKEN: break; case DRILL_INCREMENTALHEADER: if( *text != ',' ) { AddMessageToList( wxT( "ICI command has no parameter" ) ); break; } text++; // skip separator // Parameter should be ON or OFF if( strncasecmp( text, "OFF", 3 ) == 0 ) m_Relative = false; else if( strncasecmp( text, "ON", 2 ) == 0 ) m_Relative = true; else AddMessageToList( wxT( "ICI command has incorrect parameter" ) ); break; case DRILL_TOOL_CHANGE_STOP: break; case DRILL_AUTOMATIC_SPEED: break; case DRILL_AXIS_VERSION: break; case DRILL_RESET_CMD: break; case DRILL_AUTOMATIC_TOOL_CHANGE: break; case DRILL_FMT: break; 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 useful: 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) FinishRouteCommand(); break; } while( *text ) text++; return true; } void EXCELLON_IMAGE::readFileFormat( char*& aText ) { int mantissaDigits = 0; int characteristicDigits = 0; // Example String: ;FILE_FORMAT=4:4 // The ;FILE_FORMAT potion will already be stripped off. // Parse the rest strictly as single_digit:single_digit like 4:4 or 2:4 // Don't allow anything clever like spaces or multiple digits if( *aText != '=' ) return; aText++; if( !isdigit( *aText ) ) return; characteristicDigits = *aText - '0'; aText++; if( *aText != ':' ) return; aText++; if( !isdigit( *aText ) ) return; mantissaDigits = *aText - '0'; m_hasFormat = true; m_FmtLen.x = m_FmtLen.y = characteristicDigits + mantissaDigits; m_FmtScale.x = m_FmtScale.y = mantissaDigits; } bool EXCELLON_IMAGE::readToolInformation( char*& aText ) { // Read a tool definition like T1C0.02 or T1F00S00C0.02 or T1C0.02F00S00 // and enter the TCODE param in list (using the D_CODE param management, which // is similar to TCODE params. if( *aText == 'T' ) // This is the beginning of the definition aText++; // Read tool number: int iprm = ReadInt( aText, false ); // Skip Feed rate and Spindle speed, if any here while( *aText && ( *aText == 'F' || *aText == 'S' ) ) { aText++; ReadInt( aText, false ); } // Read tool shape if( ! *aText ) AddMessageToList( wxString:: Format( _( "Tool definition shape not found" ) ) ); else if( *aText != 'C' ) AddMessageToList( wxString:: Format( _( "Tool definition '%c' not supported" ), *aText ) ); if( *aText ) aText++; //read tool diameter: double dprm = ReadDouble( aText, false ); m_Has_DCode = true; // Initialize Dcode to handle this Tool // Remember: dcodes are >= FIRST_DCODE D_CODE* dcode = GetDCODEOrCreate( iprm + FIRST_DCODE ); if( dcode == nullptr ) return false; // conv_scale = scaling factor from inch to Internal Unit double conv_scale = gerbIUScale.IU_PER_MILS * 1000; if( m_GerbMetric ) conv_scale /= 25.4; dcode->m_Size.x = dcode->m_Size.y = KiROUND( dprm * conv_scale ); dcode->m_Shape = APT_CIRCLE; dcode->m_Defined = true; return true; } bool EXCELLON_IMAGE::Execute_Drill_Command( char*& text ) { D_CODE* tool; GERBER_DRAW_ITEM * gbritem; while( true ) { switch( *text ) { case 'X': case 'Y': ReadXYCoord( text, true ); if( *text == 'I' || *text == 'J' ) ReadIJCoord( text ); break; case 'G': // G85 is found here for oval holes m_PreviousPos = m_CurrentPos; Execute_EXCELLON_G_Command( text ); break; case 0: // E.O.L: execute command 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; msg.Printf( _( "Tool %d not defined" ), m_Current_Tool ); AddMessageToList( msg ); return false; } gbritem = new GERBER_DRAW_ITEM( this ); AddItemToList( gbritem ); if( m_SlotOn ) // Oblong hole { fillLineGBRITEM( gbritem, tool->m_Num_Dcode, m_PreviousPos, m_CurrentPos, tool->m_Size, false ); // the hole is made: reset the slot on command (G85) // (it is needed for each oblong hole) m_SlotOn = false; } else { fillFlashedGBRITEM( gbritem, tool->m_Shape, tool->m_Num_Dcode, m_CurrentPos, tool->m_Size, false ); } StepAndRepeatItem( *gbritem ); m_PreviousPos = m_CurrentPos; return true; break; default: text++; break; } } return true; } bool EXCELLON_IMAGE::Select_Tool( char*& text ) { // Select the tool from the command line Tn, with n = 1 ... TOOLS_MAX_COUNT - 1 // Because some drill file have an embedded TCODE definition (like T1C.008F0S0) // in tool selection command, if the tool is not defined in list, // and the definition is embedded, it will be entered in list char * startline = text; // the tool id starts here. int tool_id = CodeNumber( text ); // T0 is legal, but is not a selection tool. it is a special command if( tool_id >= 0 ) { int dcode_id = tool_id + FIRST_DCODE; // Remember: dcodes are >= FIRST_DCODE if( dcode_id > (TOOLS_MAX_COUNT - 1) ) dcode_id = TOOLS_MAX_COUNT - 1; m_Current_Tool = dcode_id; D_CODE* currDcode = GetDCODE( dcode_id ); // if the tool does not exist, and the definition is embedded, create this tool if( currDcode == nullptr && tool_id > 0 ) { text = startline; // text starts at the beginning of the command readToolInformation( text ); currDcode = GetDCODE( dcode_id ); } // If the Tool is still not existing, create a dummy tool if( !currDcode ) currDcode = GetDCODEOrCreate( dcode_id, true ); if( currDcode ) currDcode->m_InUse = true; } while( *text ) text++; return tool_id >= 0; } bool EXCELLON_IMAGE::Execute_EXCELLON_G_Command( char*& text ) { EXCELLON_CMD* cmd = nullptr; bool success = false; int id = DRILL_G_UNKNOWN; // Search command in list EXCELLON_CMD* candidate; char * gcmd = text; // gcmd points the G command, for error messages. for( unsigned ii = 0; ; ii++ ) { candidate = &excellon_G_CmdList[ii]; int len = candidate->m_Name.size(); if( len == 0 ) // End of list reached break; if( candidate->m_Name.compare( 0, len, text, len ) == 0 ) // found. { cmd = candidate; text += len; success = true; id = cmd->m_Code; break; } } switch( id ) { case DRILL_G_ZERO_SET: ReadXYCoord( text, true ); m_Offset = m_CurrentPos; break; case DRILL_G_ROUT: m_SlotOn = false; if( m_RouteModeOn ) FinishRouteCommand(); 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.emplace_back( m_CurrentPos ); break; case DRILL_G_DRILL: m_SlotOn = false; if( m_RouteModeOn ) FinishRouteCommand(); m_RouteModeOn = false; m_RoutePositions.clear(); m_LastArcDataType = ARC_INFO_TYPE_NONE; break; case DRILL_G_SLOT: m_SlotOn = true; break; case DRILL_G_LINEARMOVE: m_LastArcDataType = ARC_INFO_TYPE_NONE; m_Iterpolation = GERB_INTERPOL_LINEAR_1X; ReadXYCoord( text, true ); m_RoutePositions.emplace_back( 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.emplace_back( m_CurrentPos, m_IJPos, ROUTE_CW ); else m_RoutePositions.emplace_back( 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.emplace_back( m_CurrentPos, m_IJPos, ROUTE_CCW ); else m_RoutePositions.emplace_back( m_CurrentPos, m_ArcRadius, ROUTE_CCW ); break; case DRILL_G_ABSOLUTE: m_Relative = false; // false = absolute coord break; case DRILL_G_INCREMENTAL: m_Relative = true; // true = relative coord break; case DRILL_G_UNKNOWN: default: AddMessageToList( wxString::Format( _( "Unknown Excellon G Code: <%s>" ), FROM_UTF8(gcmd) ) ); while( *text ) text++; return false; } return success; } void EXCELLON_IMAGE::SelectUnits( bool aMetric, EXCELLON_DEFAULTS* aDefaults ) { /* Coordinates are measured either in inch or metric (millimeters). * Inch coordinates are in six digits (00.0000) with increments * as small as 0.0001 (1/10,000). * Metric coordinates can be measured in microns (thousandths of a millimeter) * in one of the following three ways: * Five digit 10 micron resolution (000.00) * Six digit 10 micron resolution (0000.00) * Six digit micron resolution (000.000) * * Inches: Default fmt = 2.4 for X and Y axis: 6 digits with 0.0001 resolution * metric: Default fmt = 3.3 for X and Y axis: 6 digits, 1 micron resolution * * However some drill files do not use standard values. */ if( aMetric ) { m_GerbMetric = true; if( !m_hasFormat ) { if( aDefaults ) { // number of digits in mantissa m_FmtScale.x = m_FmtScale.y = aDefaults->m_MmMantissaLen; // number of digits (mantissa+integer) m_FmtLen.x = m_FmtLen.y = aDefaults->m_MmIntegerLen + aDefaults->m_MmMantissaLen; } else { m_FmtScale.x = m_FmtScale.y = FMT_MANTISSA_MM; m_FmtLen.x = m_FmtLen.y = FMT_INTEGER_MM + FMT_MANTISSA_MM; } } } else { m_GerbMetric = false; if( !m_hasFormat ) { if( aDefaults ) { m_FmtScale.x = m_FmtScale.y = aDefaults->m_InchMantissaLen; m_FmtLen.x = m_FmtLen.y = aDefaults->m_InchIntegerLen + aDefaults->m_InchMantissaLen; } else { m_FmtScale.x = m_FmtScale.y = FMT_MANTISSA_INCH; m_FmtLen.x = m_FmtLen.y = FMT_INTEGER_INCH + FMT_MANTISSA_INCH; } } } } void EXCELLON_IMAGE::FinishRouteCommand() { // Ends a route command started by M15 ot G01, G02 or G03 command // if a route command is not in progress, do nothing if( !m_RouteModeOn ) return; D_CODE* tool = GetDCODE( m_Current_Tool ); if( !tool ) { AddMessageToList( wxString::Format( wxT( "Unknown tool code %d" ), m_Current_Tool ) ); return; } 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. VECTOR2I center; if( m_RoutePositions[ii].m_arc_type_info == ARC_INFO_TYPE_CENTER ) center = VECTOR2I( 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 ); } AddItemToList( gbritem ); StepAndRepeatItem( *gbritem ); } m_RoutePositions.clear(); m_RouteModeOn = false; }