|
|
/*
* This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2019 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 */
/**
* @file gbr_metadata.cpp * @brief helper functions to handle the gerber metadata in files, * related to the netlist info and aperture attribute. */#include <wx/string.h>
#include <wx/datetime.h>
#include <gbr_metadata.h>
#include <utf8.h>
wxString GbrMakeCreationDateAttributeString( GBR_NC_STRING_FORMAT aFormat ){ // creates the CreationDate attribute:
// The attribute value must conform to the full version of the ISO 8601
// date and time format, including time and time zone. Note that this is
// the date the Gerber file was effectively created,
// not the time the project of PCB was started
wxDateTime date( wxDateTime::GetTimeNow() ); // Date format: see http://www.cplusplus.com/reference/ctime/strftime
wxString timezone_offset; // ISO 8601 offset from UTC in timezone
timezone_offset = date.Format( "%z" ); // Extract the time zone offset
// The time zone offset format is +mm or +hhmm (or -mm or -hhmm)
// (mm = number of minutes, hh = number of hours. 1h00mn is returned as +0100)
// we want +(or -) hh:mm
if( timezone_offset.Len() > 3 ) // format +hhmm or -hhmm found
// Add separator between hours and minutes
timezone_offset.insert( 3, ":", 1 );
wxString msg;
switch( aFormat ) { case GBR_NC_STRING_FORMAT_X2: msg.Printf( "%%TF.CreationDate,%s%s*%%", date.FormatISOCombined(), timezone_offset ); break;
case GBR_NC_STRING_FORMAT_X1: msg.Printf( "G04 #@! TF.CreationDate,%s%s*", date.FormatISOCombined(), timezone_offset ); break;
case GBR_NC_STRING_FORMAT_GBRJOB: msg.Printf( "%s%s", date.FormatISOCombined(), timezone_offset ); break;
case GBR_NC_STRING_FORMAT_NCDRILL: msg.Printf( "; #@! TF.CreationDate,%s%s", date.FormatISOCombined(), timezone_offset ); break; } return msg;}
wxString GbrMakeProjectGUIDfromString( const wxString& aText ){ /* Gerber GUID format should be RFC4122 Version 1 or 4.
* See en.wikipedia.org/wiki/Universally_unique_identifier * The format is: * xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx * with * x = hexDigit lower/upper case * and * M = '1' or '4' (UUID version: 1 (basic) or 4 (random)) (we use 4: UUID random) * and * N = '8' or '9' or 'A|a' or 'B|b' : UUID variant 1: 2 MSB bits have meaning) (we use N = 9) * N = 1000 or 1001 or 1010 or 1011 : 10xx means Variant 1 (Variant2: 110x and 111x are reserved) */
wxString guid;
// Build a 32 digits GUID from the board name:
// guid has 32 digits, so add chars in name to be sure we can build a 32 digits guid
// (i.e. from a 16 char string name)
// In fact only 30 digits are used, and 2 UID id
wxString bname = aText; int cnt = 16 - bname.Len();
if( cnt > 0 ) bname.Append( 'X', cnt );
int chr_idx = 0;
// Output the 8 first hex digits:
for( unsigned ii = 0; ii < 4; ii++ ) { int cc = int( bname[chr_idx++] ) & 0xFF; guid << wxString::Format( "%2.2x", cc ); }
// Output the 4 next hex digits:
guid << '-';
for( unsigned ii = 0; ii < 2; ii++ ) { int cc = int( bname[chr_idx++] ) & 0xFF; guid << wxString::Format( "%2.2x", cc ); }
// Output the 4 next hex digits (UUID version and 3 digits):
guid << "-4"; // first digit: UUID version 4 (M = 4)
{ int cc = int( bname[chr_idx++] ) << 4 & 0xFF0; cc += int( bname[chr_idx] ) >> 4 & 0x0F; guid << wxString::Format( "%3.3x", cc ); }
// Output the 4 next hex digits (UUID variant and 3 digits):
guid << "-9"; // first digit: UUID variant 1 (N = 9)
{ int cc = (int( bname[chr_idx++] ) & 0x0F) << 8; cc += int( bname[chr_idx++] ) & 0xFF; guid << wxString::Format( "%3.3x", cc ); }
// Output the 12 last hex digits:
guid << '-';
for( unsigned ii = 0; ii < 6; ii++ ) { int cc = int( bname[chr_idx++] ) & 0xFF; guid << wxString::Format( "%2.2x", cc ); }
return guid;}
std::string GBR_APERTURE_METADATA::FormatAttribute( GBR_APERTURE_ATTRIB aAttribute, bool aUseX1StructuredComment ){ std::string attribute_string; // the specific aperture attribute (TA.xxx)
std::string comment_string; // a optional G04 comment line to write before the TA. line
// generate a string to print a Gerber Aperture attribute
switch( aAttribute ) { case GBR_APERTURE_ATTRIB_END: // Dummy value (aAttribute must be < GBR_APERTURE_ATTRIB_END)
case GBR_APERTURE_ATTRIB_NONE: // idle command: do nothing
break;
case GBR_APERTURE_ATTRIB_ETCHEDCMP: // print info associated to an item
// which connects 2 different nets
// (Net tees, microwave component)
attribute_string = "TA.AperFunction,EtchedComponent"; break;
case GBR_APERTURE_ATTRIB_CONDUCTOR: // print info associated to a track
attribute_string = "TA.AperFunction,Conductor"; break;
case GBR_APERTURE_ATTRIB_EDGECUT: // print info associated to a board outline
attribute_string = "TA.AperFunction,Profile"; break;
case GBR_APERTURE_ATTRIB_VIAPAD: // print info associated to a flashed via
attribute_string = "TA.AperFunction,ViaPad"; break;
case GBR_APERTURE_ATTRIB_NONCONDUCTOR: // print info associated to a item on a copper layer
// which is not a track (for instance a text)
attribute_string = "TA.AperFunction,NonConductor"; break;
case GBR_APERTURE_ATTRIB_COMPONENTPAD: // print info associated to a flashed
// through hole component on outer layer
attribute_string = "TA.AperFunction,ComponentPad"; break;
case GBR_APERTURE_ATTRIB_SMDPAD_SMDEF: // print info associated to a flashed for SMD pad.
// with solder mask defined from the copper shape
// Excluded BGA pads which have their own type
attribute_string = "TA.AperFunction,SMDPad,SMDef"; break;
case GBR_APERTURE_ATTRIB_SMDPAD_CUDEF: // print info associated to a flashed SMD pad with
// a solder mask defined by the solder mask
attribute_string = "TA.AperFunction,SMDPad,CuDef"; break;
case GBR_APERTURE_ATTRIB_BGAPAD_SMDEF: // print info associated to flashed BGA pads with
// a solder mask defined by the copper shape
attribute_string = "TA.AperFunction,BGAPad,SMDef"; break;
case GBR_APERTURE_ATTRIB_BGAPAD_CUDEF: // print info associated to a flashed BGA pad with
// a solder mask defined by the solder mask
attribute_string = "TA.AperFunction,BGAPad,CuDef"; break;
case GBR_APERTURE_ATTRIB_CONNECTORPAD: // print info associated to a flashed edge connector pad (outer layers)
attribute_string = "TA.AperFunction,ConnectorPad"; break;
case GBR_APERTURE_ATTRIB_WASHERPAD: // print info associated to flashed mechanical pads (NPTH)
attribute_string = "TA.AperFunction,WasherPad"; break;
case GBR_APERTURE_ATTRIB_HEATSINKPAD: // print info associated to a flashed heat sink pad
// (typically for SMDs)
attribute_string = "TA.AperFunction,HeatsinkPad"; break;
case GBR_APERTURE_ATTRIB_TESTPOINT: // print info associated to a flashed test point pad
// (typically for SMDs)
attribute_string = "TA.AperFunction,TestPad"; break;
case GBR_APERTURE_ATTRIB_FIDUCIAL_GLBL: // print info associated to a flashed fiducial pad
// (typically for SMDs)
attribute_string = "TA.AperFunction,FiducialPad,Global"; break;
case GBR_APERTURE_ATTRIB_FIDUCIAL_LOCAL: // print info associated to a flashed fiducial pad
// (typically for SMDs)
attribute_string = "TA.AperFunction,FiducialPad,Local"; break;
case GBR_APERTURE_ATTRIB_CASTELLATEDPAD: // print info associated to a flashed castellated pad
// (typically for SMDs)
attribute_string = "TA.AperFunction,CastellatedPad"; break;
case GBR_APERTURE_ATTRIB_CASTELLATEDDRILL: // print info associated to a flashed castellated pad
// in drill files
attribute_string = "TA.AperFunction,CastellatedDrill"; break;
case GBR_APERTURE_ATTRIB_VIADRILL: // print info associated to a via hole in drill files
attribute_string = "TA.AperFunction,ViaDrill"; break;
case GBR_APERTURE_ATTRIB_CMP_DRILL: // print info associated to a component
// round pad hole in drill files
attribute_string = "TA.AperFunction,ComponentDrill"; break;
// print info associated to a component oblong pad hole in drill files
// Same as a round pad hole, but is a specific aperture in drill file and
// a G04 comment is added to the aperture function
case GBR_APERTURE_ATTRIB_CMP_OBLONG_DRILL: comment_string = "aperture for slot hole"; attribute_string = "TA.AperFunction,ComponentDrill"; break;
case GBR_APERTURE_ATTRIB_CMP_POSITION: // print info associated to a component
// flashed shape at the component position
// in placement files
attribute_string = "TA.AperFunction,ComponentMain"; break;
case GBR_APERTURE_ATTRIB_PAD1_POSITION: // print info associated to a component
// flashed shape at pad 1 position
// (pad 1 is also pad A1 or pad AA1)
// in placement files
attribute_string = "TA.AperFunction,ComponentPin"; break;
case GBR_APERTURE_ATTRIB_PADOTHER_POSITION: // print info associated to a component
// flashed shape at pads position (all but pad 1)
// in placement files
// Currently: (could be changed later) same as
// GBR_APERTURE_ATTRIB_PADOTHER_POSITION
attribute_string = "TA.AperFunction,ComponentPin"; break;
case GBR_APERTURE_ATTRIB_CMP_BODY: // print info associated to a component
// print the component physical body
// polygon in placement files
attribute_string = "TA.AperFunction,ComponentOutline,Body"; break;
case GBR_APERTURE_ATTRIB_CMP_LEAD2LEAD: // print info associated to a component
// print the component physical lead to lead
// polygon in placement files
attribute_string = "TA.AperFunction,ComponentOutline,Lead2Lead"; break;
case GBR_APERTURE_ATTRIB_CMP_FOOTPRINT: // print info associated to a component
// print the component footprint bounding box
// polygon in placement files
attribute_string = "TA.AperFunction,ComponentOutline,Footprint"; break;
case GBR_APERTURE_ATTRIB_CMP_COURTYARD: // print info associated to a component
// print the component courtyard
// polygon in placement files
attribute_string = "TA.AperFunction,ComponentOutline,Courtyard"; break;
break; }
std::string full_attribute_string; wxString eol_string;
if( !attribute_string.empty() ) { if( !comment_string.empty() ) { full_attribute_string = "G04 " + comment_string + "*\n"; }
if( aUseX1StructuredComment ) { full_attribute_string += "G04 #@! "; eol_string = "*\n"; } else { full_attribute_string += "%"; eol_string = "*%\n"; } }
full_attribute_string += attribute_string + eol_string;
return full_attribute_string;}
// Helper function to convert a ascii hex char to its integer value
// If the char is not a hexa char, return -1
int char2Hex( unsigned aCode ){ if( aCode >= '0' && aCode <= '9' ) return aCode - '0';
if( aCode >= 'A' && aCode <= 'F' ) return aCode - 'A' + 10;
if( aCode >= 'a' && aCode <= 'f' ) return aCode - 'a' + 10;
return -1;}
wxString FormatStringFromGerber( const wxString& aString ){ // make the inverse conversion of FormatStringToGerber()
// It converts a "normalized" gerber string containing escape sequences
// and convert it to a 16 bits unicode char
// and return a wxString (unicode 16) from the gerber string
// Note the initial gerber string can already contain unicode chars.
wxString txt; // The string converted from Gerber string
unsigned count = aString.Length();
for( unsigned ii = 0; ii < count; ++ii ) { unsigned code = aString[ii];
if( code == '\\' && ii < count-5 && aString[ii+1] == 'u' ) { // Note the latest Gerber X2 spec (2019 06) uses \uXXXX to encode
// the unicode XXXX hexadecimal value
// If 4 chars next to 'u' are hexadecimal chars,
// Convert these 4 hexadecimal digits to a 16 bit unicode
// (Gerber allows only 4 hexadecimal digits)
// If an error occurs, the escape sequence is not translated,
// and used "as this"
long value = 0; bool error = false;
for( int jj = 0; jj < 4; jj++ ) { value <<= 4; code = aString[ii+jj+2];
int hexa = char2Hex( code );
if( hexa >= 0 ) value += hexa; else { error = true; break; } }
if( !error ) { if( value >= ' ' ) // Is a valid wxChar ?
txt.Append( wxChar( value ) );
ii += 5; } else { txt.Append( aString[ii] ); continue; } } else txt.Append( aString[ii] ); }
return txt;}
wxString ConvertNotAllowedCharsInGerber( const wxString& aString, bool aAllowUtf8Chars, bool aQuoteString ){ /* format string means convert any code > 0x7E and unautorized codes to a hexadecimal
* 16 bits sequence unicode * However if aAllowUtf8Chars is true only unautorized codes will be escaped, because some * Gerber files accept UTF8 chars. * unautorized codes are ',' '*' '%' '\' '"' and are used as separators in Gerber files */ wxString txt;
if( aQuoteString ) txt << "\"";
for( unsigned ii = 0; ii < aString.Length(); ++ii ) { wxChar code = aString[ii]; bool convert = false;
switch( code ) { case '\\': case '%': case '*': case ',': convert = true; break;
case '"': if( aQuoteString ) convert = true; break;
default: break; }
if( !aAllowUtf8Chars && code > 0x7F ) convert = true;
if( convert ) { // Convert code to 4 hexadecimal digit
// (Gerber allows only 4 hexadecimal digit) in escape seq:
// "\uXXXX", XXXX is the unicode 16 bits hexa value
char hexa[32]; sprintf( hexa,"\\u%4.4X", code & 0xFFFF); txt += hexa; } else txt += code; }
if( aQuoteString ) txt << "\"";
return txt;}
std::string GBR_DATA_FIELD::GetGerberString() const{ wxString converted;
if( !m_field.IsEmpty() ) converted = ConvertNotAllowedCharsInGerber( m_field, m_useUTF8, m_escapeString );
// Convert the char string to std::string. Be carefull when converting a wxString to
// a std::string: using static_cast<const char*> is mandatory
std::string txt = static_cast<const char*>( converted.utf8_str() );
return txt;}
std::string FormatStringToGerber( const wxString& aString ){ wxString converted; /* format string means convert any code > 0x7E and unautorized codes to a hexadecimal
* 16 bits sequence unicode * unautorized codes are ',' '*' '%' '\' * This conversion is not made for quoted strings, because if the string is * quoted, the conversion is expected to be already made, and the returned string must use * UTF8 encoding */ if( !aString.IsEmpty() && ( aString[0] != '\"' || aString[aString.Len()-1] != '\"' ) ) converted = ConvertNotAllowedCharsInGerber( aString, false, false ); else converted = aString;
// Convert the char string to std::string. Be carefull when converting awxString to
// a std::string: using static_cast<const char*> is mandatory
std::string txt = static_cast<const char*>( converted.utf8_str() );
return txt;}
// Netname and Pan num fields cannot be empty in Gerber files
// Normalized names must be used, if any
#define NO_NET_NAME wxT( "N/C" ) // net name of not connected pads (one pad net) (normalized)
#define NO_PAD_NAME wxT( "" ) // pad name of pads without pad name/number (not normalized)
bool FormatNetAttribute( std::string& aPrintedText, std::string& aLastNetAttributes, const GBR_NETLIST_METADATA* aData, bool& aClearPreviousAttributes, bool aUseX1StructuredComment ){ aClearPreviousAttributes = false; wxString prepend_string; wxString eol_string;
if( aUseX1StructuredComment ) { prepend_string = "G04 #@! "; eol_string = "*\n"; } else { prepend_string = "%"; eol_string = "*%\n"; }
// print a Gerber net attribute record.
// it is added to the object attributes dictionary
// On file, only modified or new attributes are printed.
if( aData == NULL ) return false;
std::string pad_attribute_string; std::string net_attribute_string; std::string cmp_attribute_string;
if( aData->m_NetAttribType == GBR_NETLIST_METADATA::GBR_NETINFO_UNSPECIFIED ) return false; // idle command: do nothing
if( ( aData->m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_PAD ) ) { // print info associated to a flashed pad (cmpref, pad name, and optionally pin function)
// example1: %TO.P,R5,3*%
// example2: %TO.P,R5,3,reset*%
pad_attribute_string = prepend_string + "TO.P,"; pad_attribute_string += FormatStringToGerber( aData->m_Cmpref ) + ",";
if( aData->m_Padname.IsEmpty() ) // Happens for "mechanical" or never connected pads
pad_attribute_string += FormatStringToGerber( NO_PAD_NAME ); else { pad_attribute_string += aData->m_Padname.GetGerberString();
// In Pcbnew, the pin function comes from the schematic.
// so it exists only for named pads
if( !aData->m_PadPinFunction.IsEmpty() ) { pad_attribute_string += ','; pad_attribute_string += aData->m_PadPinFunction.GetGerberString(); } }
pad_attribute_string += eol_string; }
if( ( aData->m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_NET ) ) { // print info associated to a net
// example: %TO.N,Clk3*%
net_attribute_string = prepend_string + "TO.N,";
if( aData->m_Netname.IsEmpty() ) { if( aData->m_NotInNet ) { // Happens for not connectable pads: mechanical pads
// and pads with no padname/num
// In this case the net name must be left empty
} else { // Happens for not connected pads: use a normalized
// dummy name
net_attribute_string += FormatStringToGerber( NO_NET_NAME ); } } else net_attribute_string += FormatStringToGerber( aData->m_Netname );
net_attribute_string += eol_string; }
if( ( aData->m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_CMP ) && !( aData->m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_PAD ) ) { // print info associated to a footprint
// example: %TO.C,R2*%
// Because GBR_NETINFO_PAD option already contains this info, it is not
// created here for a GBR_NETINFO_PAD attribute
cmp_attribute_string = prepend_string + "TO.C,"; cmp_attribute_string += FormatStringToGerber( aData->m_Cmpref ) + eol_string; }
// the full list of requested attributes:
std::string full_attribute_string = pad_attribute_string + net_attribute_string + cmp_attribute_string; // the short list of requested attributes
// (only modified or new attributes are stored here):
std::string short_attribute_string;
// Attributes have changed: update attribute string, and see if the previous attribute
// list (dictionary in Gerber language) must be cleared
if( aLastNetAttributes != full_attribute_string ) { // first, remove no longer existing attributes.
// Because in Kicad the full attribute list is evaluated for each object,
// the entire dictionary is cleared
// If m_TryKeepPreviousAttributes is true, only the no longer existing attribute
// is cleared.
// Note: to avoid interaction beteween clear attributes and set attributes
// the clear attribute is inserted first.
bool clearDict = false;
if( aLastNetAttributes.find( "TO.P," ) != std::string::npos ) { if( pad_attribute_string.empty() ) // No more this attribute
{ if( aData->m_TryKeepPreviousAttributes ) // Clear only this attribute
short_attribute_string.insert( 0, prepend_string + "TO.P" + eol_string ); else clearDict = true; } else if( aLastNetAttributes.find( pad_attribute_string ) == std::string::npos ) // This attribute has changed
short_attribute_string += pad_attribute_string; } else // New attribute
short_attribute_string += pad_attribute_string;
if( aLastNetAttributes.find( "TO.N," ) != std::string::npos ) { if( net_attribute_string.empty() ) // No more this attribute
{ if( aData->m_TryKeepPreviousAttributes ) // Clear only this attribute
short_attribute_string.insert( 0, prepend_string + "TO.N" + eol_string ); else clearDict = true; } else if( aLastNetAttributes.find( net_attribute_string ) == std::string::npos ) // This attribute has changed
short_attribute_string += net_attribute_string; } else // New attribute
short_attribute_string += net_attribute_string;
if( aLastNetAttributes.find( "TO.C," ) != std::string::npos ) { if( cmp_attribute_string.empty() ) // No more this attribute
{ if( aData->m_TryKeepPreviousAttributes ) // Clear only this attribute
{ // Refinement:
// the attribute will be cleared only if there is no pad attribute.
// If a pad attribute exists, the component name exists so the old
// TO.C value will be updated, therefore no need to clear it before updating
if( pad_attribute_string.empty() ) short_attribute_string.insert( 0, prepend_string + "TO.C" + eol_string ); } else clearDict = true; } else if( aLastNetAttributes.find( cmp_attribute_string ) == std::string::npos ) // This attribute has changed
short_attribute_string += cmp_attribute_string; } else // New attribute
short_attribute_string += cmp_attribute_string;
aClearPreviousAttributes = clearDict;
aLastNetAttributes = full_attribute_string;
if( clearDict ) aPrintedText = full_attribute_string; else aPrintedText = short_attribute_string; }
return true;}
/************ class GBR_CMP_PNP_METADATA *************/
void GBR_CMP_PNP_METADATA::ClearData(){ // Clear all strings
m_Orientation = 0.0; m_Manufacturer.Clear(); m_MPN.Clear(); m_Package.Clear(); m_Value.Clear(); m_MountType = MOUNT_TYPE_UNSPECIFIED;}/**
* @return a string containing the formated metadata in X2 syntax. * one line by non empty data * the orientation (.CRot) and mount type (.CMnt) are always generated */wxString GBR_CMP_PNP_METADATA::FormatCmpPnPMetadata(){ wxString text; wxString start_of_line( "%TO."); wxString end_of_line( "*%\n" );
wxString mounType[] = { "Other", "SMD", "TH" };
if( !m_Manufacturer.IsEmpty() ) text << start_of_line << "CMfr," << m_Manufacturer << end_of_line;
if( !m_MPN.IsEmpty() ) text << start_of_line << "CMPN," << m_MPN << end_of_line;
if( !m_Package.IsEmpty() ) text << start_of_line << "Cpkg," << m_Package << end_of_line;
if( !m_Footprint.IsEmpty() ) text << start_of_line << "CFtp," << m_Footprint << end_of_line;
if( !m_Value.IsEmpty() ) text << start_of_line << "CVal," << m_Value << end_of_line;
if( !m_LibraryName.IsEmpty() ) text << start_of_line << "CLbN," << m_LibraryName << end_of_line;
if( !m_LibraryDescr.IsEmpty() ) text << start_of_line << "CLbD," << m_LibraryDescr << end_of_line;
text << start_of_line << "CMnt," << mounType[m_MountType] << end_of_line; text << start_of_line << "CRot," << m_Orientation << end_of_line;
return text;}
|