Implement Gerber job file writing using nlohmann/json

This commit is contained in:
Jon Evans 2019-12-25 11:30:46 -05:00
parent 3cb05155be
commit 21d577e638
4 changed files with 207 additions and 318 deletions

View File

@ -63,7 +63,7 @@ wxString GbrMakeCreationDateAttributeString( GBR_NC_STRING_FORMAT aFormat )
break; break;
case GBR_NC_STRING_FORMAT_GBRJOB: case GBR_NC_STRING_FORMAT_GBRJOB:
msg.Printf( "\"CreationDate\": \"%s%s\"", date.FormatISOCombined(), timezone_offset ); msg.Printf( "%s%s", date.FormatISOCombined(), timezone_offset );
break; break;
case GBR_NC_STRING_FORMAT_NCDRILL: case GBR_NC_STRING_FORMAT_NCDRILL:
@ -481,7 +481,7 @@ std::string FormatStringToGerber( const wxString& aString )
* quoted, the conversion is expected to be already made, and the returned string must use * quoted, the conversion is expected to be already made, and the returned string must use
* UTF8 encoding * UTF8 encoding
*/ */
if( aString[0] != '\"' || aString[aString.Len()-1] != '\"' ) if( !aString.IsEmpty() && ( aString[0] != '\"' || aString[aString.Len()-1] != '\"' ) )
converted = ConvertNotAllowedCharsInGerber( aString, false, false ); converted = ConvertNotAllowedCharsInGerber( aString, false, false );
else else
converted = aString; converted = aString;

View File

@ -621,6 +621,7 @@ target_include_directories( pcbnew_kiface_objects PRIVATE
$<TARGET_PROPERTY:dxflib_qcad,INTERFACE_INCLUDE_DIRECTORIES> $<TARGET_PROPERTY:dxflib_qcad,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:nanosvg,INTERFACE_INCLUDE_DIRECTORIES> $<TARGET_PROPERTY:nanosvg,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:tinyspline_lib,INTERFACE_INCLUDE_DIRECTORIES> $<TARGET_PROPERTY:tinyspline_lib,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:nlohmann_json,INTERFACE_INCLUDE_DIRECTORIES>
) )
# Since we're not using target_link_libraries, we need to explicitly # Since we're not using target_link_libraries, we need to explicitly

View File

@ -29,38 +29,38 @@
#include <fctsys.h> #include <fctsys.h>
#include <fstream>
#include <iomanip>
#include <vector> #include <vector>
#include <plotter.h>
#include <pcb_edit_frame.h>
#include <build_version.h> #include <build_version.h>
#include <pcb_edit_frame.h>
#include <plotter.h>
#include <class_board.h> #include <class_board.h>
#include <class_zone.h>
#include <class_module.h> #include <class_module.h>
#include <class_zone.h>
#include <pcbplot.h>
#include <pcbnew.h>
#include <gerber_jobfile_writer.h>
#include <wildcards_and_files_ext.h>
#include <reporter.h>
#include <gbr_metadata.h>
#include <board_stackup_manager/stackup_predefined_prms.h> #include <board_stackup_manager/stackup_predefined_prms.h>
#include <gbr_metadata.h>
#include <gerber_jobfile_writer.h>
#include <pcbnew.h>
#include <pcbplot.h>
#include <reporter.h>
#include <wildcards_and_files_ext.h>
GERBER_JOBFILE_WRITER::GERBER_JOBFILE_WRITER( BOARD* aPcb, REPORTER* aReporter ) GERBER_JOBFILE_WRITER::GERBER_JOBFILE_WRITER( BOARD* aPcb, REPORTER* aReporter )
{ {
m_pcb = aPcb; m_pcb = aPcb;
m_reporter = aReporter; m_reporter = aReporter;
m_conversionUnits = 1.0 / IU_PER_MM; // Gerber units = mm m_conversionUnits = 1.0 / IU_PER_MM; // Gerber units = mm
m_indent = 0;
} }
std::string GERBER_JOBFILE_WRITER::formatStringFromUTF32( const wxString& aText ) std::string GERBER_JOBFILE_WRITER::formatStringFromUTF32( const wxString& aText )
{ {
std::string fmt_text; // the text after UTF32 to UTF8 conversion std::string fmt_text; // the text after UTF32 to UTF8 conversion
for( unsigned long letter: aText ) for( unsigned long letter : aText )
{ {
if( letter >= ' ' && letter <= 0x7F ) if( letter >= ' ' && letter <= 0x7F )
fmt_text += char( letter ); fmt_text += char( letter );
@ -88,7 +88,7 @@ enum ONSIDE GERBER_JOBFILE_WRITER::hasSilkLayers()
flag |= SIDE_TOP; flag |= SIDE_TOP;
} }
return (enum ONSIDE)flag; return (enum ONSIDE) flag;
} }
@ -105,7 +105,7 @@ enum ONSIDE GERBER_JOBFILE_WRITER::hasSolderMasks()
flag |= SIDE_TOP; flag |= SIDE_TOP;
} }
return (enum ONSIDE)flag; return (enum ONSIDE) flag;
} }
const char* GERBER_JOBFILE_WRITER::sideKeyValue( enum ONSIDE aValue ) const char* GERBER_JOBFILE_WRITER::sideKeyValue( enum ONSIDE aValue )
@ -116,18 +116,21 @@ const char* GERBER_JOBFILE_WRITER::sideKeyValue( enum ONSIDE aValue )
switch( aValue ) switch( aValue )
{ {
case SIDE_NONE: case SIDE_NONE:
value = "No"; break; value = "No";
break;
case SIDE_TOP: case SIDE_TOP:
value = "TopOnly"; break; value = "TopOnly";
break;
case SIDE_BOTTOM: case SIDE_BOTTOM:
value = "BotOnly"; break; value = "BotOnly";
break;
case SIDE_BOTH:
value = "Both"; break;
case SIDE_BOTH:
value = "Both";
break;
} }
return value; return value;
@ -136,7 +139,7 @@ const char* GERBER_JOBFILE_WRITER::sideKeyValue( enum ONSIDE aValue )
bool GERBER_JOBFILE_WRITER::CreateJobFile( const wxString& aFullFilename ) bool GERBER_JOBFILE_WRITER::CreateJobFile( const wxString& aFullFilename )
{ {
bool success; bool success;
wxString msg; wxString msg;
success = WriteJSONJobFile( aFullFilename ); success = WriteJSONJobFile( aFullFilename );
@ -162,61 +165,34 @@ bool GERBER_JOBFILE_WRITER::CreateJobFile( const wxString& aFullFilename )
void GERBER_JOBFILE_WRITER::addJSONHeader() void GERBER_JOBFILE_WRITER::addJSONHeader()
{ {
wxString text; wxString text;
openBlock();
addJSONObject( "\"Header\":\n" );
openBlock();
// Creates the GenerationSoftware m_json["Header"] = {
addJSONObject( "\"GenerationSoftware\":\n" ); {
openBlock(); "GenerationSoftware",
addJSONObject( "\"Vendor\": \"KiCad\",\n" ); {
addJSONObject( "\"Application\": \"Pcbnew\",\n" ); { "Vendor", "KiCad" },
text.Printf( "\"Version\": \"%s\"\n", GetBuildVersion() ); { "Application", "Pcbnew" },
addJSONObject( text ); { "Version", GetBuildVersion() }
closeBlockWithSep(); }
},
// creates the CreationDate attribute: {
// The attribute value must conform to the full version of the ISO 8601 // The attribute value must conform to the full version of the ISO 8601
// date and time format, including time and time zone. // date and time format, including time and time zone.
text = GbrMakeCreationDateAttributeString( GBR_NC_STRING_FORMAT_GBRJOB ) + "\n"; "CreationDate", GbrMakeCreationDateAttributeString( GBR_NC_STRING_FORMAT_GBRJOB )
addJSONObject( text ); }
};
closeBlockWithSep();
} }
void GERBER_JOBFILE_WRITER::removeJSONSepararator()
{
if( m_JSONbuffer.Last() == ',' )
{
m_JSONbuffer.RemoveLast();
return;
}
if( m_JSONbuffer.Last() == '\n' )
{
m_JSONbuffer.RemoveLast();
if( m_JSONbuffer.Last() == ',' )
m_JSONbuffer.RemoveLast();
m_JSONbuffer.Append( '\n' );
}
}
bool GERBER_JOBFILE_WRITER::WriteJSONJobFile( const wxString& aFullFilename ) bool GERBER_JOBFILE_WRITER::WriteJSONJobFile( const wxString& aFullFilename )
{ {
// Note: in Gerber job file, dimensions are in mm, and are floating numbers // Note: in Gerber job file, dimensions are in mm, and are floating numbers
FILE* jobFile = wxFopen( aFullFilename, "wt" ); std::ofstream file( aFullFilename.ToUTF8() );
m_JSONbuffer.Empty();
m_indent = 0;
if( jobFile == nullptr )
return false;
LOCALE_IO dummy; LOCALE_IO dummy;
m_json = json( {} );
// output the job file header // output the job file header
addJSONHeader(); addJSONHeader();
@ -232,13 +208,7 @@ bool GERBER_JOBFILE_WRITER::WriteJSONJobFile( const wxString& aFullFilename )
// output the board stackup: // output the board stackup:
addJSONMaterialStackup(); addJSONMaterialStackup();
// Close job file full block data file << std::setw( 2 ) << m_json << std::endl;
removeJSONSepararator(); // remove the last separator
closeBlock();
fputs( TO_UTF8( m_JSONbuffer ), jobFile );
fclose( jobFile );
return true; return true;
} }
@ -246,11 +216,8 @@ bool GERBER_JOBFILE_WRITER::WriteJSONJobFile( const wxString& aFullFilename )
void GERBER_JOBFILE_WRITER::addJSONGeneralSpecs() void GERBER_JOBFILE_WRITER::addJSONGeneralSpecs()
{ {
addJSONObject( "\"GeneralSpecs\":\n" ); m_json["GeneralSpecs"] = json( {} );
openBlock(); m_json["GeneralSpecs"]["ProjectId"] = json( {} );
addJSONObject( "\"ProjectId\":\n" );
openBlock();
// Creates the ProjectId. Format is (from Gerber file format doc): // Creates the ProjectId. Format is (from Gerber file format doc):
// ProjectId,<project id>,<project GUID>,<revision id>*% // ProjectId,<project id>,<project GUID>,<revision id>*%
@ -262,7 +229,7 @@ void GERBER_JOBFILE_WRITER::addJSONGeneralSpecs()
// <project GUID> is a string which is an unique id of a project. // <project GUID> is a string which is an unique id of a project.
// However Kicad does not handle such a project GUID, so it is built from the board name // However Kicad does not handle such a project GUID, so it is built from the board name
wxFileName fn = m_pcb->GetFileName(); wxFileName fn = m_pcb->GetFileName();
wxString msg = fn.GetFullName(); wxString msg = fn.GetFullName();
// Build a <project GUID>, from the board name // Build a <project GUID>, from the board name
wxString guid = GbrMakeProjectGUIDfromString( msg ); wxString guid = GbrMakeProjectGUIDfromString( msg );
@ -277,53 +244,46 @@ void GERBER_JOBFILE_WRITER::addJSONGeneralSpecs()
if( rev.IsEmpty() ) if( rev.IsEmpty() )
rev = wxT( "rev?" ); rev = wxT( "rev?" );
addJSONObject( wxString::Format( "\"Name\": \"%s\",\n", msg.ToAscii() ) ); m_json["GeneralSpecs"]["ProjectId"]["Name"] = msg.ToAscii();
addJSONObject( wxString::Format( "\"GUID\": \"%s\",\n", guid ) ); m_json["GeneralSpecs"]["ProjectId"]["GUID"] = guid;
addJSONObject( wxString::Format( "\"Revision\": \"%s\"\n", rev.ToAscii() ) ); m_json["GeneralSpecs"]["ProjectId"]["Revision"] = rev.ToAscii();
closeBlockWithSep();
// output the bord size in mm: // output the bord size in mm:
EDA_RECT brect = m_pcb->GetBoardEdgesBoundingBox(); EDA_RECT brect = m_pcb->GetBoardEdgesBoundingBox();
addJSONObject( "\"Size\":\n" );
openBlock();
addJSONObject( wxString::Format( "\"X\": %.3f,\n", brect.GetWidth()*m_conversionUnits ) ); m_json["GeneralSpecs"]["Size"]["X"] = brect.GetWidth() * m_conversionUnits;
addJSONObject( wxString::Format( "\"Y\": %.3f\n", brect.GetHeight()*m_conversionUnits ) ); m_json["GeneralSpecs"]["Size"]["Y"] = brect.GetHeight() * m_conversionUnits;
closeBlockWithSep();
// Add some data to the JSON header, GeneralSpecs: // Add some data to the JSON header, GeneralSpecs:
// number of copper layers // number of copper layers
addJSONObject( wxString::Format( "\"LayerNumber\": %d,\n", m_pcb->GetCopperLayerCount() ) ); m_json["GeneralSpecs"]["LayerNumber"] = m_pcb->GetCopperLayerCount();
// Board thickness // Board thickness
addJSONObject( wxString::Format( "\"BoardThickness\": %.3f,\n", m_json["GeneralSpecs"]["BoardThickness"] =
m_pcb->GetDesignSettings().GetBoardThickness()*m_conversionUnits ) ); m_pcb->GetDesignSettings().GetBoardThickness() * m_conversionUnits;
// Copper finish // Copper finish
BOARD_STACKUP brd_stackup = m_pcb->GetDesignSettings().GetStackupDescriptor(); BOARD_STACKUP brd_stackup = m_pcb->GetDesignSettings().GetStackupDescriptor();
if( !brd_stackup.m_FinishType.IsEmpty() ) if( !brd_stackup.m_FinishType.IsEmpty() )
addJSONObject( wxString::Format( "\"Finish\": \"%s\",\n", brd_stackup.m_FinishType ) ); m_json["GeneralSpecs"]["Finish"] = brd_stackup.m_FinishType;
if( brd_stackup.m_CastellatedPads ) if( brd_stackup.m_CastellatedPads )
addJSONObject( "\"Castellated\": \"true\",\n" ); m_json["GeneralSpecs"]["Castellated"] = true;
if( brd_stackup.m_EdgePlating ) if( brd_stackup.m_EdgePlating )
addJSONObject( "\"EdgePlating\": \"true\",\n" ); m_json["GeneralSpecs"]["EdgePlating"] = true;
if( brd_stackup.m_EdgeConnectorConstraints ) if( brd_stackup.m_EdgeConnectorConstraints )
{ {
addJSONObject( "\"EdgeConnector\": \"true\",\n" ); m_json["GeneralSpecs"]["EdgeConnector"] = true;
if( brd_stackup.m_EdgeConnectorConstraints == BS_EDGE_CONNECTOR_BEVELLED ) m_json["GeneralSpecs"]["EdgeConnectorBevelled"] =
addJSONObject( "\"EdgeConnectorBevelled\": \"true\",\n" ); ( brd_stackup.m_EdgeConnectorConstraints == BS_EDGE_CONNECTOR_BEVELLED );
else
addJSONObject( "\"EdgeConnectorBevelled\": \"false\",\n" );
} }
#if 0 // Not yet in use #if 0 // Not yet in use
/* The board type according to IPC-2221. There are six primary board types: /* The board type according to IPC-2221. There are six primary board types:
- Type 1 - Single-sided - Type 1 - Single-sided
- Type 2 - Double-sided - Type 2 - Double-sided
@ -332,7 +292,7 @@ void GERBER_JOBFILE_WRITER::addJSONGeneralSpecs()
- Type 5 - Multilayer metal-core board, TH components only - Type 5 - Multilayer metal-core board, TH components only
- Type 6 - Multilayer metal-core - Type 6 - Multilayer metal-core
*/ */
addJSONObject( wxString::Format( "\"IPC-2221-Type\": \"%d\",\n", 4 ) ); m_json["GeneralSpecs"]["IPC-2221-Type"] = 4;
/* Via protection: key words: /* Via protection: key words:
Ia Tented - Single-sided Ia Tented - Single-sided
@ -348,26 +308,24 @@ void GERBER_JOBFILE_WRITER::addJSONGeneralSpecs()
VIII Filled and Capped VIII Filled and Capped
None...No protection None...No protection
*/ */
addJSONObject( wxString::Format( "\"ViaProtection\": \"%s\",\n", "Ib" ) ); m_json["GeneralSpecs"]["ViaProtection"] = "Ib";
#endif #endif
removeJSONSepararator();
closeBlockWithSep();
} }
void GERBER_JOBFILE_WRITER::addJSONFilesAttributes() void GERBER_JOBFILE_WRITER::addJSONFilesAttributes()
{ {
// Add the Files Attributes section in JSON format to m_JSONbuffer // Add the Files Attributes section in JSON format to m_JSONbuffer
addJSONObject( "\"FilesAttributes\":\n" ); m_json["FilesAttributes"] = json::array();
openArrayBlock();
for( unsigned ii = 0; ii < m_params.m_GerberFileList.GetCount(); ii ++ ) for( unsigned ii = 0; ii < m_params.m_GerberFileList.GetCount(); ii++ )
{ {
wxString& name = m_params.m_GerberFileList[ii]; wxString& name = m_params.m_GerberFileList[ii];
PCB_LAYER_ID layer = m_params.m_LayerId[ii]; PCB_LAYER_ID layer = m_params.m_LayerId[ii];
wxString gbr_layer_id; wxString gbr_layer_id;
bool skip_file = false; // true to skip files which should not be in job file bool skip_file = false; // true to skip files which should not be in job file
const char* polarity = "Positive"; const char* polarity = "Positive";
json file_json;
if( layer <= B_Cu ) if( layer <= B_Cu )
{ {
@ -376,7 +334,7 @@ void GERBER_JOBFILE_WRITER::addJSONFilesAttributes()
if( layer == B_Cu ) if( layer == B_Cu )
gbr_layer_id << m_pcb->GetCopperLayerCount(); gbr_layer_id << m_pcb->GetCopperLayerCount();
else else
gbr_layer_id << layer+1; gbr_layer_id << layer + 1;
gbr_layer_id << ","; gbr_layer_id << ",";
@ -392,48 +350,61 @@ void GERBER_JOBFILE_WRITER::addJSONFilesAttributes()
{ {
switch( layer ) switch( layer )
{ {
case B_Adhes: case B_Adhes:
gbr_layer_id = "Glue,Bot"; break; gbr_layer_id = "Glue,Bot";
case F_Adhes: break;
gbr_layer_id = "Glue,Top"; break; case F_Adhes:
gbr_layer_id = "Glue,Top";
break;
case B_Paste: case B_Paste:
gbr_layer_id = "SolderPaste,Bot"; break; gbr_layer_id = "SolderPaste,Bot";
case F_Paste: break;
gbr_layer_id = "SolderPaste,Top"; break; case F_Paste:
gbr_layer_id = "SolderPaste,Top";
break;
case B_SilkS: case B_SilkS:
gbr_layer_id = "Legend,Bot"; break; gbr_layer_id = "Legend,Bot";
case F_SilkS: break;
gbr_layer_id = "Legend,Top"; break; case F_SilkS:
gbr_layer_id = "Legend,Top";
break;
case B_Mask: case B_Mask:
gbr_layer_id = "SolderMask,Bot"; polarity = "Negative"; break; gbr_layer_id = "SolderMask,Bot";
case F_Mask: polarity = "Negative";
gbr_layer_id = "SolderMask,Top"; polarity = "Negative"; break; break;
case F_Mask:
gbr_layer_id = "SolderMask,Top";
polarity = "Negative";
break;
case Edge_Cuts: case Edge_Cuts:
gbr_layer_id = "Profile"; break; gbr_layer_id = "Profile";
break;
case B_Fab: case B_Fab:
gbr_layer_id = "AssemblyDrawing,Bot"; break; gbr_layer_id = "AssemblyDrawing,Bot";
case F_Fab: break;
gbr_layer_id = "AssemblyDrawing,Top"; break; case F_Fab:
gbr_layer_id = "AssemblyDrawing,Top";
break;
case Dwgs_User: case Dwgs_User:
case Cmts_User: case Cmts_User:
case Eco1_User: case Eco1_User:
case Eco2_User: case Eco2_User:
case Margin: case Margin:
case B_CrtYd: case B_CrtYd:
case F_CrtYd: case F_CrtYd:
skip_file = true; break; skip_file = true;
break;
default: default:
skip_file = true; skip_file = true;
m_reporter->Report( "Unexpected layer id in job file", m_reporter->Report( "Unexpected layer id in job file", REPORTER::RPT_ERROR );
REPORTER::RPT_ERROR ); break;
break;
} }
} }
@ -443,16 +414,13 @@ void GERBER_JOBFILE_WRITER::addJSONFilesAttributes()
// Ensure the name is JSON compatible. // Ensure the name is JSON compatible.
std::string strname = formatStringFromUTF32( name ); std::string strname = formatStringFromUTF32( name );
openBlock(); file_json["Path"] = strname.c_str();
addJSONObject( wxString::Format( "\"Path\": \"%s\",\n", strname.c_str() ) ); file_json["FileFunction"] = gbr_layer_id;
addJSONObject( wxString::Format( "\"FileFunction\": \"%s\",\n", gbr_layer_id ) ), file_json["FilePolarity"] = polarity;
addJSONObject( wxString::Format( "\"FilePolarity\": \"%s\"\n", polarity ) );
closeBlockWithSep(); m_json["FilesAttributes"] += file_json;
} }
} }
// Close the file list:
removeJSONSepararator(); // remove the last separator
closeArrayBlockWithSep();
} }
@ -461,14 +429,13 @@ void GERBER_JOBFILE_WRITER::addJSONDesignRules()
// Add the Design Rules section in JSON format to m_JSONbuffer // Add the Design Rules section in JSON format to m_JSONbuffer
// Job file support a few design rules: // Job file support a few design rules:
const BOARD_DESIGN_SETTINGS& dsnSettings = m_pcb->GetDesignSettings(); const BOARD_DESIGN_SETTINGS& dsnSettings = m_pcb->GetDesignSettings();
NETCLASS defaultNC = *dsnSettings.GetDefault(); NETCLASS defaultNC = *dsnSettings.GetDefault();
int minclearanceOuter = defaultNC.GetClearance(); int minclearanceOuter = defaultNC.GetClearance();
bool hasInnerLayers = m_pcb->GetCopperLayerCount() > 2; bool hasInnerLayers = m_pcb->GetCopperLayerCount() > 2;
// Search a smaller clearance in other net classes, if any. // Search a smaller clearance in other net classes, if any.
for( NETCLASSES::const_iterator it = dsnSettings.m_NetClasses.begin(); for( NETCLASSES::const_iterator it = dsnSettings.m_NetClasses.begin();
it != dsnSettings.m_NetClasses.end(); it != dsnSettings.m_NetClasses.end(); ++it )
++it )
{ {
NETCLASS netclass = *it->second; NETCLASS netclass = *it->second;
minclearanceOuter = std::min( minclearanceOuter, netclass.GetClearance() ); minclearanceOuter = std::min( minclearanceOuter, netclass.GetClearance() );
@ -489,22 +456,19 @@ void GERBER_JOBFILE_WRITER::addJSONDesignRules()
for( auto& pad : module->Pads() ) for( auto& pad : module->Pads() )
{ {
if( ( pad->GetLayerSet() & LSET::InternalCuMask() ).any() ) if( ( pad->GetLayerSet() & LSET::InternalCuMask() ).any() )
minPadClearanceInner = std::min( minPadClearanceInner, pad->GetClearance() ); minPadClearanceInner = std::min( minPadClearanceInner, pad->GetClearance() );
if( ( pad->GetLayerSet() & LSET::ExternalCuMask() ).any() ) if( ( pad->GetLayerSet() & LSET::ExternalCuMask() ).any() )
minPadClearanceOuter = std::min( minPadClearanceOuter, pad->GetClearance() ); minPadClearanceOuter = std::min( minPadClearanceOuter, pad->GetClearance() );
} }
} }
m_json["DesignRules"] = { {
addJSONObject( "\"DesignRules\":\n" ); { "Layers", "Outer" },
openArrayBlock(); { "PadToPad", minPadClearanceOuter * m_conversionUnits },
{ "PadToTrack", minPadClearanceOuter * m_conversionUnits },
openBlock(); { "TrackToTrack", minclearance_track2track * m_conversionUnits }
addJSONObject( "\"Layers\": \"Outer\",\n" ); } };
addJSONObject( wxString::Format( "\"PadToPad\": %.3f,\n", minPadClearanceOuter*m_conversionUnits ) );
addJSONObject( wxString::Format( "\"PadToTrack\": %.3f,\n", minPadClearanceOuter*m_conversionUnits ) );
addJSONObject( wxString::Format( "\"TrackToTrack\": %.3f,\n", minclearance_track2track*m_conversionUnits ) );
// Until this is changed in Kicad, use the same value for internal tracks // Until this is changed in Kicad, use the same value for internal tracks
int minclearanceInner = minclearanceOuter; int minclearanceInner = minclearanceOuter;
@ -525,8 +489,7 @@ void GERBER_JOBFILE_WRITER::addJSONDesignRules()
} }
if( mintrackWidthOuter != INT_MAX ) if( mintrackWidthOuter != INT_MAX )
addJSONObject( wxString::Format( "\"MinLineWidth\": %.3f,\n", m_json["DesignRules"][0]["MinLineWidth"] = mintrackWidthOuter * m_conversionUnits;
mintrackWidthOuter*m_conversionUnits ) );
// Output the minimal zone to xx clearance // Output the minimal zone to xx clearance
// Note: zones can have a zone clearance set to 0 // Note: zones can have a zone clearance set to 0
@ -550,55 +513,39 @@ void GERBER_JOBFILE_WRITER::addJSONDesignRules()
} }
if( minclearanceOuter != INT_MAX ) if( minclearanceOuter != INT_MAX )
addJSONObject( wxString::Format( "\"TrackToRegion\": %.3f,\n", m_json["DesignRules"][0]["TrackToRegion"] = minclearanceOuter * m_conversionUnits;
minclearanceOuter*m_conversionUnits ) );
if( minclearanceOuter != INT_MAX ) if( minclearanceOuter != INT_MAX )
addJSONObject( wxString::Format( "\"RegionToRegion\": %.3f,\n", m_json["DesignRules"][0]["RegionToRegion"] = minclearanceOuter * m_conversionUnits;
minclearanceOuter*m_conversionUnits ) );
removeJSONSepararator(); // remove the last separator
if( !hasInnerLayers )
closeBlock();
else
closeBlockWithSep();
if( hasInnerLayers ) if( hasInnerLayers )
{ {
openBlock(); m_json["DesignRules"] += json( {
addJSONObject( "\"Layers\": \"Inner\",\n" ); { "Layers", "Inner" },
addJSONObject( wxString::Format( "\"PadToPad\": %.3f,\n", minPadClearanceInner*m_conversionUnits ) ); { "PadToPad", minPadClearanceInner * m_conversionUnits },
addJSONObject( wxString::Format( "\"PadToTrack\": %.3f,\n", minPadClearanceInner*m_conversionUnits ) ); { "PadToTrack", minPadClearanceInner * m_conversionUnits },
addJSONObject( wxString::Format( "\"TrackToTrack\": %.3f,\n", minclearance_track2track*m_conversionUnits ) ); { "TrackToTrack", minclearance_track2track * m_conversionUnits }
} );
if( mintrackWidthInner != INT_MAX ) if( mintrackWidthInner != INT_MAX )
addJSONObject( wxString::Format( "\"MinLineWidth\": %.3f,\n", mintrackWidthInner*m_conversionUnits ) ); m_json["DesignRules"][1]["MinLineWidth"] = mintrackWidthInner * m_conversionUnits;
if( minclearanceInner != INT_MAX ) if( minclearanceInner != INT_MAX )
addJSONObject( wxString::Format( "\"TrackToRegion\": %.3f,\n", minclearanceInner*m_conversionUnits ) ); m_json["DesignRules"][1]["TrackToRegion"] = minclearanceInner * m_conversionUnits;
if( minclearanceInner != INT_MAX ) if( minclearanceInner != INT_MAX )
addJSONObject( wxString::Format( "\"RegionToRegion\": %.3f,\n", minclearanceInner*m_conversionUnits ) ); m_json["DesignRules"][1]["RegionToRegion"] = minclearanceInner * m_conversionUnits;
removeJSONSepararator(); // remove the last separator
closeBlock();
} }
// Close DesignRules
closeArrayBlockWithSep();
} }
void GERBER_JOBFILE_WRITER::addJSONMaterialStackup() void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
{ {
// Add the Material Stackup section in JSON format to m_JSONbuffer // Add the Material Stackup section in JSON format to m_JSONbuffer
addJSONObject( "\"MaterialStackup\":\n" ); m_json["MaterialStackup"] = json::array();
openArrayBlock();
// Build the candidates list: // Build the candidates list:
LSET maskLayer; LSET maskLayer;
BOARD_STACKUP brd_stackup = m_pcb->GetDesignSettings().GetStackupDescriptor(); BOARD_STACKUP brd_stackup = m_pcb->GetDesignSettings().GetStackupDescriptor();
// Ensure brd_stackup is up to date (i.e. no change made by SynchronizeWithBoard() ) // Ensure brd_stackup is up to date (i.e. no change made by SynchronizeWithBoard() )
@ -606,7 +553,8 @@ void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
if( !uptodate && m_pcb->GetDesignSettings().m_HasStackup ) if( !uptodate && m_pcb->GetDesignSettings().m_HasStackup )
m_reporter->Report( _( "Board stackup settings not up to date\n" m_reporter->Report( _( "Board stackup settings not up to date\n"
"Please fix the stackup" ), REPORTER::RPT_ERROR ); "Please fix the stackup" ),
REPORTER::RPT_ERROR );
PCB_LAYER_ID last_copper_layer = F_Cu; PCB_LAYER_ID last_copper_layer = F_Cu;
@ -615,15 +563,16 @@ void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
{ {
BOARD_STACKUP_ITEM* item = brd_stackup.GetStackupLayer( ii ); BOARD_STACKUP_ITEM* item = brd_stackup.GetStackupLayer( ii );
int sub_layer_count = item->GetType() == BS_ITEM_TYPE_DIELECTRIC int sub_layer_count =
? item->GetSublayersCount() : 1; item->GetType() == BS_ITEM_TYPE_DIELECTRIC ? item->GetSublayersCount() : 1;
for( int sub_idx = 0; sub_idx < sub_layer_count; sub_idx++ ) for( int sub_idx = 0; sub_idx < sub_layer_count; sub_idx++ )
{ {
// layer thickness is always in mm // layer thickness is always in mm
double thickness = item->GetThickness( sub_idx )*m_conversionUnits; double thickness = item->GetThickness( sub_idx ) * m_conversionUnits;
wxString layer_type; wxString layer_type;
std::string layer_name; // for comment std::string layer_name; // for comment
json layer_json;
switch( item->GetType() ) switch( item->GetType() )
{ {
@ -654,23 +603,20 @@ void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
// in build process, not necessary wanted. // in build process, not necessary wanted.
if( sub_layer_count > 1 ) if( sub_layer_count > 1 )
{ {
layer_name = formatStringFromUTF32( layer_name =
wxString::Format( "dielectric layer %d - %d/%d", formatStringFromUTF32( wxString::Format( "dielectric layer %d - %d/%d",
item->GetDielectricLayerId(), item->GetDielectricLayerId(), sub_idx + 1, sub_layer_count ) );
sub_idx+1, sub_layer_count ) );
} }
else else
layer_name = formatStringFromUTF32( layer_name = formatStringFromUTF32( wxString::Format(
wxString::Format( "dielectric layer %d", "dielectric layer %d", item->GetDielectricLayerId() ) );
item->GetDielectricLayerId() ) );
break; break;
default: default:
break; break;
} }
openBlock(); layer_json["Type"] = layer_type;
addJSONObject( wxString::Format( "\"Type\": \"%s\",\n", layer_type ) );
if( item->IsColorEditable() && uptodate ) if( item->IsColorEditable() && uptodate )
{ {
@ -678,26 +624,25 @@ void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
{ {
wxString colorName = item->GetColor(); wxString colorName = item->GetColor();
if( colorName.StartsWith( "#" ) ) // This is a user defined color. if( colorName.StartsWith( "#" ) ) // This is a user defined color.
{ {
// In job file a color can be given by its RGB values (0...255) // In job file a color can be given by its RGB values (0...255)
wxColor color( colorName ); wxColor color( colorName );
colorName.Printf( "R%dG%dB%d", color.Red(), color.Green(), color.Blue() ); colorName.Printf( "R%dG%dB%d", color.Red(), color.Green(), color.Blue() );
} }
addJSONObject( wxString::Format( "\"Color\": \"%s\",\n", colorName ) ); layer_json["Color"] = colorName;
} }
} }
if( item->IsThicknessEditable() && uptodate ) if( item->IsThicknessEditable() && uptodate )
addJSONObject( wxString::Format( "\"Thickness\": %.3f,\n", thickness ) ); layer_json["Thickness"] = thickness;
if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
{ {
if( item->HasMaterialValue() ) if( item->HasMaterialValue() )
{ {
addJSONObject( wxString::Format( "\"Material\": \"%s\",\n", layer_json["Material"] = item->GetMaterial( sub_idx );
item->GetMaterial( sub_idx ) ) );
// These constrains are only written if the board has impedance controlled tracks. // These constrains are only written if the board has impedance controlled tracks.
// If the board is not impedance controlled, they are useless. // If the board is not impedance controlled, they are useless.
@ -707,50 +652,50 @@ void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
// Generate Epsilon R if > 1.0 (value <= 1.0 means not specified: it is not // Generate Epsilon R if > 1.0 (value <= 1.0 means not specified: it is not
// a possible value // a possible value
if( item->GetEpsilonR() > 1.0 ) if( item->GetEpsilonR() > 1.0 )
addJSONObject( wxString::Format( "\"DielectricConstant\": %s,\n", layer_json["DielectricConstant"] = item->FormatEpsilonR( sub_idx );
item->FormatEpsilonR( sub_idx ) ) );
// Generate LossTangent > 0.0 (value <= 0.0 means not specified: it is not // Generate LossTangent > 0.0 (value <= 0.0 means not specified: it is not
// a possible value // a possible value
if( item->GetLossTangent() > 0.0 ) if( item->GetLossTangent() > 0.0 )
addJSONObject( wxString::Format( "\"LossTangent\": %s,\n", layer_json["LossTangent"] = item->FormatLossTangent( sub_idx );
item->FormatLossTangent( sub_idx ) ) );
} }
} }
PCB_LAYER_ID next_copper_layer = (PCB_LAYER_ID) (last_copper_layer+1); PCB_LAYER_ID next_copper_layer = ( PCB_LAYER_ID )( last_copper_layer + 1 );
// If the next_copper_layer is the last copper layer, the next layer id is B_Cu // If the next_copper_layer is the last copper layer, the next layer id is B_Cu
if( next_copper_layer >= m_pcb->GetCopperLayerCount()-1 ) if( next_copper_layer >= m_pcb->GetCopperLayerCount() - 1 )
next_copper_layer = B_Cu; next_copper_layer = B_Cu;
wxString subLayerName; wxString subLayerName;
if( sub_layer_count > 1 ) if( sub_layer_count > 1 )
subLayerName.Printf( " (%d/%d)",sub_idx+1, sub_layer_count ); subLayerName.Printf( " (%d/%d)", sub_idx + 1, sub_layer_count );
addJSONObject( wxString::Format( "\"Name\": \"%s/%s%s\",\n", wxString name = wxString::Format( "%s/%s%s",
formatStringFromUTF32( m_pcb->GetLayerName( last_copper_layer ) ), formatStringFromUTF32( m_pcb->GetLayerName( last_copper_layer ) ),
formatStringFromUTF32( m_pcb->GetLayerName( next_copper_layer ) ), formatStringFromUTF32( m_pcb->GetLayerName( next_copper_layer ) ),
subLayerName ) subLayerName );
);
layer_json["Name"] = name;
// Add a comment ("Notes"): // Add a comment ("Notes"):
wxString note = "\"Notes\": "; wxString note;
note << wxString::Format( " \"Type: %s", layer_name.c_str() ); note << wxString::Format( "Type: %s", layer_name.c_str() );
note << wxString::Format( " (from %s to %s)\"\n", note << wxString::Format( " (from %s to %s)",
formatStringFromUTF32( m_pcb->GetLayerName( last_copper_layer ) ), formatStringFromUTF32( m_pcb->GetLayerName( last_copper_layer ) ),
formatStringFromUTF32( m_pcb->GetLayerName( next_copper_layer ) ) ); formatStringFromUTF32( m_pcb->GetLayerName( next_copper_layer ) ) );
addJSONObject( note ); layer_json["Notes"] = note;
} }
else if( item->GetType() == BS_ITEM_TYPE_SOLDERMASK || item->GetType() == BS_ITEM_TYPE_SILKSCREEN ) else if( item->GetType() == BS_ITEM_TYPE_SOLDERMASK
|| item->GetType() == BS_ITEM_TYPE_SILKSCREEN )
{ {
if( item->HasMaterialValue() ) if( item->HasMaterialValue() )
{ {
addJSONObject( wxString::Format( "\"Material\": \"%s\",\n", item->GetMaterial() ) ); layer_json["Material"] = item->GetMaterial();
// These constrains are only written if the board has impedance controlled tracks. // These constrains are only written if the board has impedance controlled tracks.
// If the board is not impedance controlled, they are useless. // If the board is not impedance controlled, they are useless.
@ -760,29 +705,23 @@ void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
// Generate Epsilon R if > 1.0 (value <= 1.0 means not specified: it is not // Generate Epsilon R if > 1.0 (value <= 1.0 means not specified: it is not
// a possible value // a possible value
if( item->GetEpsilonR() > 1.0 ) if( item->GetEpsilonR() > 1.0 )
addJSONObject( wxString::Format( "\"DielectricConstant\": %s,\n", layer_json["DielectricConstant"] = item->FormatEpsilonR();
item->FormatEpsilonR() ) );
// Generate LossTangent > 0.0 (value <= 0.0 means not specified: it is not // Generate LossTangent > 0.0 (value <= 0.0 means not specified: it is not
// a possible value // a possible value
if( item->GetLossTangent() > 0.0 ) if( item->GetLossTangent() > 0.0 )
addJSONObject( wxString::Format( "\"LossTangent\": %s,\n", layer_json["LossTangent"] = item->FormatLossTangent();
item->FormatLossTangent() ) );
} }
} }
addJSONObject( wxString::Format( "\"Name\": \"%s\",\n", layer_name.c_str() ) ); layer_json["Name"] = layer_name.c_str();
} }
else else
{ {
addJSONObject( wxString::Format( "\"Name\": \"%s\",\n", layer_name.c_str() ) ); layer_json["Name"] = layer_name.c_str();
} }
removeJSONSepararator(); m_json["MaterialStackup"].insert( m_json["MaterialStackup"].end(), layer_json );
closeBlockWithSep();
} }
} }
removeJSONSepararator();
closeArrayBlockWithSep();
} }

View File

@ -30,6 +30,10 @@
#ifndef GERBER_JOBFILE_WRITER_H #ifndef GERBER_JOBFILE_WRITER_H
#define GERBER_JOBFILE_WRITER_H #define GERBER_JOBFILE_WRITER_H
#include <nlohmann/json.hpp>
using json = nlohmann::json;
// A helper enum to handle sides of some layers (silk, mask) // A helper enum to handle sides of some layers (silk, mask)
enum ONSIDE enum ONSIDE
@ -148,60 +152,6 @@ private:
*/ */
void addJSONDesignRules(); void addJSONDesignRules();
/**
* Remove the comma if it is the last char in m_JSONbuffer,
* or the previous char if the last char is a \n
*/
void removeJSONSepararator();
/**
* add m_indent spaces in m_JSONbuffer
*/
void addIndent() { m_JSONbuffer.Append( ' ', m_indent ); }
/**
* open a JSON block: add '{' and increment indentation
*/
void openBlock() { addIndent(); m_JSONbuffer << "{\n"; m_indent += 2; }
/**
* open a JSON array block: add '[' and increment indentation
*/
void openArrayBlock() { addIndent(); m_JSONbuffer << "[\n"; m_indent += 2; }
/**
* close a JSON block: decrement indentation and add '}'
*/
void closeBlock() { m_indent -= 2; addIndent(); m_JSONbuffer << "}\n"; }
/**
* close a JSON block: decrement indentation and add '}' and ','
*/
void closeBlockWithSep() { m_indent -= 2; addIndent(); m_JSONbuffer << "},\n"; }
/**
* close a JSON array block: decrement indentation and add ']'
*/
void closeArrayBlock() { m_indent -= 2; addIndent(); m_JSONbuffer << "]\n"; }
/**
* close a JSON array block: decrement indentation and add ']' and ','
*/
void closeArrayBlockWithSep() { m_indent -= 2; addIndent(); m_JSONbuffer << "],\n"; }
/**
* Add aParam to m_JSONbuffer, with suitable indentation
*/
void addJSONObject( const wxString& aParam )
{
addIndent(); m_JSONbuffer << aParam;
}
void addJSONObject( const char* aParam )
{
addIndent(); m_JSONbuffer << aParam;
}
/** A helper function to convert a wxString ( therefore a Unicode text ) to /** A helper function to convert a wxString ( therefore a Unicode text ) to
* a JSON compatible string (a escaped unicode sequence of 4 hexa). * a JSON compatible string (a escaped unicode sequence of 4 hexa).
*/ */
@ -212,8 +162,7 @@ private:
REPORTER* m_reporter; // a reporter for messages (can be null) REPORTER* m_reporter; // a reporter for messages (can be null)
JOBFILE_PARAMS m_params; // the list of various prms and data to write in a job file JOBFILE_PARAMS m_params; // the list of various prms and data to write in a job file
double m_conversionUnits; // scaling factor to convert brd units to gerber units (mm) double m_conversionUnits; // scaling factor to convert brd units to gerber units (mm)
wxString m_JSONbuffer; // a buffer to build the JSON data json m_json; // json document built by this class
int m_indent; // helper for JSON format: the current indentation value
}; };
#endif // #ifndef GERBER_JOBFILE_WRITER_H #endif // #ifndef GERBER_JOBFILE_WRITER_H