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;
case GBR_NC_STRING_FORMAT_GBRJOB:
msg.Printf( "\"CreationDate\": \"%s%s\"", date.FormatISOCombined(), timezone_offset );
msg.Printf( "%s%s", date.FormatISOCombined(), timezone_offset );
break;
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
* UTF8 encoding
*/
if( aString[0] != '\"' || aString[aString.Len()-1] != '\"' )
if( !aString.IsEmpty() && ( aString[0] != '\"' || aString[aString.Len()-1] != '\"' ) )
converted = ConvertNotAllowedCharsInGerber( aString, false, false );
else
converted = aString;

View File

@ -621,6 +621,7 @@ target_include_directories( pcbnew_kiface_objects PRIVATE
$<TARGET_PROPERTY:dxflib_qcad,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:nanosvg,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

View File

@ -29,38 +29,38 @@
#include <fctsys.h>
#include <fstream>
#include <iomanip>
#include <vector>
#include <plotter.h>
#include <pcb_edit_frame.h>
#include <build_version.h>
#include <pcb_edit_frame.h>
#include <plotter.h>
#include <class_board.h>
#include <class_zone.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 <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 )
{
m_pcb = aPcb;
m_reporter = aReporter;
m_conversionUnits = 1.0 / IU_PER_MM; // Gerber units = mm
m_indent = 0;
}
std::string GERBER_JOBFILE_WRITER::formatStringFromUTF32( const wxString& aText )
{
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 )
fmt_text += char( letter );
@ -88,7 +88,7 @@ enum ONSIDE GERBER_JOBFILE_WRITER::hasSilkLayers()
flag |= SIDE_TOP;
}
return (enum ONSIDE)flag;
return (enum ONSIDE) flag;
}
@ -105,7 +105,7 @@ enum ONSIDE GERBER_JOBFILE_WRITER::hasSolderMasks()
flag |= SIDE_TOP;
}
return (enum ONSIDE)flag;
return (enum ONSIDE) flag;
}
const char* GERBER_JOBFILE_WRITER::sideKeyValue( enum ONSIDE aValue )
@ -117,17 +117,20 @@ const char* GERBER_JOBFILE_WRITER::sideKeyValue( enum ONSIDE aValue )
switch( aValue )
{
case SIDE_NONE:
value = "No"; break;
value = "No";
break;
case SIDE_TOP:
value = "TopOnly"; break;
value = "TopOnly";
break;
case SIDE_BOTTOM:
value = "BotOnly"; break;
value = "BotOnly";
break;
case SIDE_BOTH:
value = "Both"; break;
value = "Both";
break;
}
return value;
@ -162,61 +165,34 @@ bool GERBER_JOBFILE_WRITER::CreateJobFile( const wxString& aFullFilename )
void GERBER_JOBFILE_WRITER::addJSONHeader()
{
wxString text;
openBlock();
addJSONObject( "\"Header\":\n" );
openBlock();
// Creates the GenerationSoftware
addJSONObject( "\"GenerationSoftware\":\n" );
openBlock();
addJSONObject( "\"Vendor\": \"KiCad\",\n" );
addJSONObject( "\"Application\": \"Pcbnew\",\n" );
text.Printf( "\"Version\": \"%s\"\n", GetBuildVersion() );
addJSONObject( text );
closeBlockWithSep();
// creates the CreationDate attribute:
m_json["Header"] = {
{
"GenerationSoftware",
{
{ "Vendor", "KiCad" },
{ "Application", "Pcbnew" },
{ "Version", GetBuildVersion() }
}
},
{
// The attribute value must conform to the full version of the ISO 8601
// date and time format, including time and time zone.
text = GbrMakeCreationDateAttributeString( GBR_NC_STRING_FORMAT_GBRJOB ) + "\n";
addJSONObject( text );
closeBlockWithSep();
"CreationDate", GbrMakeCreationDateAttributeString( GBR_NC_STRING_FORMAT_GBRJOB )
}
};
}
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 )
{
// Note: in Gerber job file, dimensions are in mm, and are floating numbers
FILE* jobFile = wxFopen( aFullFilename, "wt" );
m_JSONbuffer.Empty();
m_indent = 0;
if( jobFile == nullptr )
return false;
std::ofstream file( aFullFilename.ToUTF8() );
LOCALE_IO dummy;
m_json = json( {} );
// output the job file header
addJSONHeader();
@ -232,13 +208,7 @@ bool GERBER_JOBFILE_WRITER::WriteJSONJobFile( const wxString& aFullFilename )
// output the board stackup:
addJSONMaterialStackup();
// Close job file full block data
removeJSONSepararator(); // remove the last separator
closeBlock();
fputs( TO_UTF8( m_JSONbuffer ), jobFile );
fclose( jobFile );
file << std::setw( 2 ) << m_json << std::endl;
return true;
}
@ -246,11 +216,8 @@ bool GERBER_JOBFILE_WRITER::WriteJSONJobFile( const wxString& aFullFilename )
void GERBER_JOBFILE_WRITER::addJSONGeneralSpecs()
{
addJSONObject( "\"GeneralSpecs\":\n" );
openBlock();
addJSONObject( "\"ProjectId\":\n" );
openBlock();
m_json["GeneralSpecs"] = json( {} );
m_json["GeneralSpecs"]["ProjectId"] = json( {} );
// Creates the ProjectId. Format is (from Gerber file format doc):
// ProjectId,<project id>,<project GUID>,<revision id>*%
@ -277,50 +244,43 @@ void GERBER_JOBFILE_WRITER::addJSONGeneralSpecs()
if( rev.IsEmpty() )
rev = wxT( "rev?" );
addJSONObject( wxString::Format( "\"Name\": \"%s\",\n", msg.ToAscii() ) );
addJSONObject( wxString::Format( "\"GUID\": \"%s\",\n", guid ) );
addJSONObject( wxString::Format( "\"Revision\": \"%s\"\n", rev.ToAscii() ) );
closeBlockWithSep();
m_json["GeneralSpecs"]["ProjectId"]["Name"] = msg.ToAscii();
m_json["GeneralSpecs"]["ProjectId"]["GUID"] = guid;
m_json["GeneralSpecs"]["ProjectId"]["Revision"] = rev.ToAscii();
// output the bord size in mm:
EDA_RECT brect = m_pcb->GetBoardEdgesBoundingBox();
addJSONObject( "\"Size\":\n" );
openBlock();
addJSONObject( wxString::Format( "\"X\": %.3f,\n", brect.GetWidth()*m_conversionUnits ) );
addJSONObject( wxString::Format( "\"Y\": %.3f\n", brect.GetHeight()*m_conversionUnits ) );
closeBlockWithSep();
m_json["GeneralSpecs"]["Size"]["X"] = brect.GetWidth() * m_conversionUnits;
m_json["GeneralSpecs"]["Size"]["Y"] = brect.GetHeight() * m_conversionUnits;
// Add some data to the JSON header, GeneralSpecs:
// number of copper layers
addJSONObject( wxString::Format( "\"LayerNumber\": %d,\n", m_pcb->GetCopperLayerCount() ) );
m_json["GeneralSpecs"]["LayerNumber"] = m_pcb->GetCopperLayerCount();
// Board thickness
addJSONObject( wxString::Format( "\"BoardThickness\": %.3f,\n",
m_pcb->GetDesignSettings().GetBoardThickness()*m_conversionUnits ) );
m_json["GeneralSpecs"]["BoardThickness"] =
m_pcb->GetDesignSettings().GetBoardThickness() * m_conversionUnits;
// Copper finish
BOARD_STACKUP brd_stackup = m_pcb->GetDesignSettings().GetStackupDescriptor();
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 )
addJSONObject( "\"Castellated\": \"true\",\n" );
m_json["GeneralSpecs"]["Castellated"] = true;
if( brd_stackup.m_EdgePlating )
addJSONObject( "\"EdgePlating\": \"true\",\n" );
m_json["GeneralSpecs"]["EdgePlating"] = true;
if( brd_stackup.m_EdgeConnectorConstraints )
{
addJSONObject( "\"EdgeConnector\": \"true\",\n" );
m_json["GeneralSpecs"]["EdgeConnector"] = true;
if( brd_stackup.m_EdgeConnectorConstraints == BS_EDGE_CONNECTOR_BEVELLED )
addJSONObject( "\"EdgeConnectorBevelled\": \"true\",\n" );
else
addJSONObject( "\"EdgeConnectorBevelled\": \"false\",\n" );
m_json["GeneralSpecs"]["EdgeConnectorBevelled"] =
( brd_stackup.m_EdgeConnectorConstraints == BS_EDGE_CONNECTOR_BEVELLED );
}
#if 0 // Not yet in use
@ -332,7 +292,7 @@ void GERBER_JOBFILE_WRITER::addJSONGeneralSpecs()
- Type 5 - Multilayer metal-core board, TH components only
- 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:
Ia Tented - Single-sided
@ -348,26 +308,24 @@ void GERBER_JOBFILE_WRITER::addJSONGeneralSpecs()
VIII Filled and Capped
None...No protection
*/
addJSONObject( wxString::Format( "\"ViaProtection\": \"%s\",\n", "Ib" ) );
m_json["GeneralSpecs"]["ViaProtection"] = "Ib";
#endif
removeJSONSepararator();
closeBlockWithSep();
}
void GERBER_JOBFILE_WRITER::addJSONFilesAttributes()
{
// Add the Files Attributes section in JSON format to m_JSONbuffer
addJSONObject( "\"FilesAttributes\":\n" );
openArrayBlock();
m_json["FilesAttributes"] = json::array();
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];
PCB_LAYER_ID layer = m_params.m_LayerId[ii];
wxString gbr_layer_id;
bool skip_file = false; // true to skip files which should not be in job file
const char* polarity = "Positive";
json file_json;
if( layer <= B_Cu )
{
@ -376,7 +334,7 @@ void GERBER_JOBFILE_WRITER::addJSONFilesAttributes()
if( layer == B_Cu )
gbr_layer_id << m_pcb->GetCopperLayerCount();
else
gbr_layer_id << layer+1;
gbr_layer_id << layer + 1;
gbr_layer_id << ",";
@ -393,32 +351,45 @@ void GERBER_JOBFILE_WRITER::addJSONFilesAttributes()
switch( layer )
{
case B_Adhes:
gbr_layer_id = "Glue,Bot"; break;
gbr_layer_id = "Glue,Bot";
break;
case F_Adhes:
gbr_layer_id = "Glue,Top"; break;
gbr_layer_id = "Glue,Top";
break;
case B_Paste:
gbr_layer_id = "SolderPaste,Bot"; break;
gbr_layer_id = "SolderPaste,Bot";
break;
case F_Paste:
gbr_layer_id = "SolderPaste,Top"; break;
gbr_layer_id = "SolderPaste,Top";
break;
case B_SilkS:
gbr_layer_id = "Legend,Bot"; break;
gbr_layer_id = "Legend,Bot";
break;
case F_SilkS:
gbr_layer_id = "Legend,Top"; break;
gbr_layer_id = "Legend,Top";
break;
case B_Mask:
gbr_layer_id = "SolderMask,Bot"; polarity = "Negative"; break;
gbr_layer_id = "SolderMask,Bot";
polarity = "Negative";
break;
case F_Mask:
gbr_layer_id = "SolderMask,Top"; polarity = "Negative"; break;
gbr_layer_id = "SolderMask,Top";
polarity = "Negative";
break;
case Edge_Cuts:
gbr_layer_id = "Profile"; break;
gbr_layer_id = "Profile";
break;
case B_Fab:
gbr_layer_id = "AssemblyDrawing,Bot"; break;
gbr_layer_id = "AssemblyDrawing,Bot";
break;
case F_Fab:
gbr_layer_id = "AssemblyDrawing,Top"; break;
gbr_layer_id = "AssemblyDrawing,Top";
break;
case Dwgs_User:
case Cmts_User:
@ -427,12 +398,12 @@ void GERBER_JOBFILE_WRITER::addJSONFilesAttributes()
case Margin:
case B_CrtYd:
case F_CrtYd:
skip_file = true; break;
skip_file = true;
break;
default:
skip_file = true;
m_reporter->Report( "Unexpected layer id in job file",
REPORTER::RPT_ERROR );
m_reporter->Report( "Unexpected layer id in job file", REPORTER::RPT_ERROR );
break;
}
}
@ -443,16 +414,13 @@ void GERBER_JOBFILE_WRITER::addJSONFilesAttributes()
// Ensure the name is JSON compatible.
std::string strname = formatStringFromUTF32( name );
openBlock();
addJSONObject( wxString::Format( "\"Path\": \"%s\",\n", strname.c_str() ) );
addJSONObject( wxString::Format( "\"FileFunction\": \"%s\",\n", gbr_layer_id ) ),
addJSONObject( wxString::Format( "\"FilePolarity\": \"%s\"\n", polarity ) );
closeBlockWithSep();
file_json["Path"] = strname.c_str();
file_json["FileFunction"] = gbr_layer_id;
file_json["FilePolarity"] = polarity;
m_json["FilesAttributes"] += file_json;
}
}
// Close the file list:
removeJSONSepararator(); // remove the last separator
closeArrayBlockWithSep();
}
@ -467,8 +435,7 @@ void GERBER_JOBFILE_WRITER::addJSONDesignRules()
// Search a smaller clearance in other net classes, if any.
for( NETCLASSES::const_iterator it = dsnSettings.m_NetClasses.begin();
it != dsnSettings.m_NetClasses.end();
++it )
it != dsnSettings.m_NetClasses.end(); ++it )
{
NETCLASS netclass = *it->second;
minclearanceOuter = std::min( minclearanceOuter, netclass.GetClearance() );
@ -496,15 +463,12 @@ void GERBER_JOBFILE_WRITER::addJSONDesignRules()
}
}
addJSONObject( "\"DesignRules\":\n" );
openArrayBlock();
openBlock();
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 ) );
m_json["DesignRules"] = { {
{ "Layers", "Outer" },
{ "PadToPad", minPadClearanceOuter * m_conversionUnits },
{ "PadToTrack", minPadClearanceOuter * m_conversionUnits },
{ "TrackToTrack", minclearance_track2track * m_conversionUnits }
} };
// Until this is changed in Kicad, use the same value for internal tracks
int minclearanceInner = minclearanceOuter;
@ -525,8 +489,7 @@ void GERBER_JOBFILE_WRITER::addJSONDesignRules()
}
if( mintrackWidthOuter != INT_MAX )
addJSONObject( wxString::Format( "\"MinLineWidth\": %.3f,\n",
mintrackWidthOuter*m_conversionUnits ) );
m_json["DesignRules"][0]["MinLineWidth"] = mintrackWidthOuter * m_conversionUnits;
// Output the minimal zone to xx clearance
// Note: zones can have a zone clearance set to 0
@ -550,52 +513,36 @@ void GERBER_JOBFILE_WRITER::addJSONDesignRules()
}
if( minclearanceOuter != INT_MAX )
addJSONObject( wxString::Format( "\"TrackToRegion\": %.3f,\n",
minclearanceOuter*m_conversionUnits ) );
m_json["DesignRules"][0]["TrackToRegion"] = minclearanceOuter * m_conversionUnits;
if( minclearanceOuter != INT_MAX )
addJSONObject( wxString::Format( "\"RegionToRegion\": %.3f,\n",
minclearanceOuter*m_conversionUnits ) );
removeJSONSepararator(); // remove the last separator
if( !hasInnerLayers )
closeBlock();
else
closeBlockWithSep();
m_json["DesignRules"][0]["RegionToRegion"] = minclearanceOuter * m_conversionUnits;
if( hasInnerLayers )
{
openBlock();
addJSONObject( "\"Layers\": \"Inner\",\n" );
addJSONObject( wxString::Format( "\"PadToPad\": %.3f,\n", minPadClearanceInner*m_conversionUnits ) );
addJSONObject( wxString::Format( "\"PadToTrack\": %.3f,\n", minPadClearanceInner*m_conversionUnits ) );
addJSONObject( wxString::Format( "\"TrackToTrack\": %.3f,\n", minclearance_track2track*m_conversionUnits ) );
m_json["DesignRules"] += json( {
{ "Layers", "Inner" },
{ "PadToPad", minPadClearanceInner * m_conversionUnits },
{ "PadToTrack", minPadClearanceInner * m_conversionUnits },
{ "TrackToTrack", minclearance_track2track * m_conversionUnits }
} );
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 )
addJSONObject( wxString::Format( "\"TrackToRegion\": %.3f,\n", minclearanceInner*m_conversionUnits ) );
m_json["DesignRules"][1]["TrackToRegion"] = minclearanceInner * m_conversionUnits;
if( minclearanceInner != INT_MAX )
addJSONObject( wxString::Format( "\"RegionToRegion\": %.3f,\n", minclearanceInner*m_conversionUnits ) );
removeJSONSepararator(); // remove the last separator
closeBlock();
m_json["DesignRules"][1]["RegionToRegion"] = minclearanceInner * m_conversionUnits;
}
// Close DesignRules
closeArrayBlockWithSep();
}
void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
{
// Add the Material Stackup section in JSON format to m_JSONbuffer
addJSONObject( "\"MaterialStackup\":\n" );
openArrayBlock();
m_json["MaterialStackup"] = json::array();
// Build the candidates list:
LSET maskLayer;
@ -606,7 +553,8 @@ void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
if( !uptodate && m_pcb->GetDesignSettings().m_HasStackup )
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;
@ -615,15 +563,16 @@ void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
{
BOARD_STACKUP_ITEM* item = brd_stackup.GetStackupLayer( ii );
int sub_layer_count = item->GetType() == BS_ITEM_TYPE_DIELECTRIC
? item->GetSublayersCount() : 1;
int sub_layer_count =
item->GetType() == BS_ITEM_TYPE_DIELECTRIC ? item->GetSublayersCount() : 1;
for( int sub_idx = 0; sub_idx < sub_layer_count; sub_idx++ )
{
// 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;
std::string layer_name; // for comment
json layer_json;
switch( item->GetType() )
{
@ -654,23 +603,20 @@ void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
// in build process, not necessary wanted.
if( sub_layer_count > 1 )
{
layer_name = formatStringFromUTF32(
wxString::Format( "dielectric layer %d - %d/%d",
item->GetDielectricLayerId(),
sub_idx+1, sub_layer_count ) );
layer_name =
formatStringFromUTF32( wxString::Format( "dielectric layer %d - %d/%d",
item->GetDielectricLayerId(), sub_idx + 1, sub_layer_count ) );
}
else
layer_name = formatStringFromUTF32(
wxString::Format( "dielectric layer %d",
item->GetDielectricLayerId() ) );
layer_name = formatStringFromUTF32( wxString::Format(
"dielectric layer %d", item->GetDielectricLayerId() ) );
break;
default:
break;
}
openBlock();
addJSONObject( wxString::Format( "\"Type\": \"%s\",\n", layer_type ) );
layer_json["Type"] = layer_type;
if( item->IsColorEditable() && uptodate )
{
@ -685,19 +631,18 @@ void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
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 )
addJSONObject( wxString::Format( "\"Thickness\": %.3f,\n", thickness ) );
layer_json["Thickness"] = thickness;
if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
{
if( item->HasMaterialValue() )
{
addJSONObject( wxString::Format( "\"Material\": \"%s\",\n",
item->GetMaterial( sub_idx ) ) );
layer_json["Material"] = item->GetMaterial( sub_idx );
// These constrains are only written if the board has impedance controlled tracks.
// 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
// a possible value
if( item->GetEpsilonR() > 1.0 )
addJSONObject( wxString::Format( "\"DielectricConstant\": %s,\n",
item->FormatEpsilonR( sub_idx ) ) );
layer_json["DielectricConstant"] = item->FormatEpsilonR( sub_idx );
// Generate LossTangent > 0.0 (value <= 0.0 means not specified: it is not
// a possible value
if( item->GetLossTangent() > 0.0 )
addJSONObject( wxString::Format( "\"LossTangent\": %s,\n",
item->FormatLossTangent( sub_idx ) ) );
layer_json["LossTangent"] = 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( next_copper_layer >= m_pcb->GetCopperLayerCount()-1 )
if( next_copper_layer >= m_pcb->GetCopperLayerCount() - 1 )
next_copper_layer = B_Cu;
wxString subLayerName;
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( next_copper_layer ) ),
subLayerName )
);
subLayerName );
layer_json["Name"] = name;
// 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( 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() )
{
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.
// 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
// a possible value
if( item->GetEpsilonR() > 1.0 )
addJSONObject( wxString::Format( "\"DielectricConstant\": %s,\n",
item->FormatEpsilonR() ) );
layer_json["DielectricConstant"] = item->FormatEpsilonR();
// Generate LossTangent > 0.0 (value <= 0.0 means not specified: it is not
// a possible value
if( item->GetLossTangent() > 0.0 )
addJSONObject( wxString::Format( "\"LossTangent\": %s,\n",
item->FormatLossTangent() ) );
layer_json["LossTangent"] = item->FormatLossTangent();
}
}
addJSONObject( wxString::Format( "\"Name\": \"%s\",\n", layer_name.c_str() ) );
layer_json["Name"] = layer_name.c_str();
}
else
{
addJSONObject( wxString::Format( "\"Name\": \"%s\",\n", layer_name.c_str() ) );
layer_json["Name"] = layer_name.c_str();
}
removeJSONSepararator();
closeBlockWithSep();
m_json["MaterialStackup"].insert( m_json["MaterialStackup"].end(), layer_json );
}
}
removeJSONSepararator();
closeArrayBlockWithSep();
}

View File

@ -30,6 +30,10 @@
#ifndef 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)
enum ONSIDE
@ -148,60 +152,6 @@ private:
*/
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 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)
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)
wxString m_JSONbuffer; // a buffer to build the JSON data
int m_indent; // helper for JSON format: the current indentation value
json m_json; // json document built by this class
};
#endif // #ifndef GERBER_JOBFILE_WRITER_H