2017-08-19 16:28:11 +00:00
|
|
|
|
/*
|
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
|
*
|
2018-03-16 18:32:49 +00:00
|
|
|
|
* Copyright (C) 2018 Jean_Pierre Charras <jp.charras at wanadoo.fr>
|
|
|
|
|
* Copyright (C) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors.
|
2017-08-19 16:28:11 +00:00
|
|
|
|
*
|
|
|
|
|
* 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 gendrill_gerber_writer.cpp
|
2018-03-16 18:32:49 +00:00
|
|
|
|
* @brief Functions to create the Gerber job file in JSON format.
|
2017-08-19 16:28:11 +00:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <fctsys.h>
|
|
|
|
|
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
2018-01-28 18:12:26 +00:00
|
|
|
|
#include <plotter.h>
|
2018-01-29 20:58:58 +00:00
|
|
|
|
#include <pcb_edit_frame.h>
|
2017-08-19 16:28:11 +00:00
|
|
|
|
#include <build_version.h>
|
|
|
|
|
|
|
|
|
|
#include <class_board.h>
|
|
|
|
|
#include <class_zone.h>
|
|
|
|
|
#include <class_module.h>
|
|
|
|
|
|
|
|
|
|
#include <pcbplot.h>
|
|
|
|
|
#include <pcbnew.h>
|
|
|
|
|
#include <gerber_jobfile_writer.h>
|
|
|
|
|
#include <wildcards_and_files_ext.h>
|
|
|
|
|
#include <reporter.h>
|
|
|
|
|
#include <plot_auxiliary_data.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
|
2018-03-16 18:32:49 +00:00
|
|
|
|
m_indent = 0;
|
2017-08-19 16:28:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum ONSIDE GERBER_JOBFILE_WRITER::hasSilkLayers()
|
|
|
|
|
{
|
|
|
|
|
int flag = SIDE_NONE;
|
|
|
|
|
|
|
|
|
|
for( unsigned ii = 0; ii < m_params.m_LayerId.size(); ii++ )
|
|
|
|
|
{
|
|
|
|
|
if( m_params.m_LayerId[ii] == B_SilkS )
|
|
|
|
|
flag |= SIDE_BOTTOM;
|
|
|
|
|
|
|
|
|
|
if( m_params.m_LayerId[ii] == F_SilkS )
|
|
|
|
|
flag |= SIDE_TOP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (enum ONSIDE)flag;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum ONSIDE GERBER_JOBFILE_WRITER::hasSolderMasks()
|
|
|
|
|
{
|
|
|
|
|
int flag = SIDE_NONE;
|
|
|
|
|
|
|
|
|
|
for( unsigned ii = 0; ii < m_params.m_LayerId.size(); ii++ )
|
|
|
|
|
{
|
|
|
|
|
if( m_params.m_LayerId[ii] == B_Mask )
|
|
|
|
|
flag |= SIDE_BOTTOM;
|
|
|
|
|
|
|
|
|
|
if( m_params.m_LayerId[ii] == F_Mask )
|
|
|
|
|
flag |= SIDE_TOP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (enum ONSIDE)flag;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* GERBER_JOBFILE_WRITER::sideKeyValue( enum ONSIDE aValue )
|
|
|
|
|
{
|
|
|
|
|
// return the key associated to sides used for some layers
|
|
|
|
|
// "No, TopOnly, BotOnly or Both"
|
|
|
|
|
const char* value = nullptr;
|
|
|
|
|
|
|
|
|
|
switch( aValue )
|
|
|
|
|
{
|
|
|
|
|
case SIDE_NONE:
|
|
|
|
|
value = "No"; break;
|
|
|
|
|
|
|
|
|
|
case SIDE_TOP:
|
|
|
|
|
value = "TopOnly"; break;
|
|
|
|
|
|
|
|
|
|
case SIDE_BOTTOM:
|
|
|
|
|
value = "BotOnly"; break;
|
|
|
|
|
|
|
|
|
|
case SIDE_BOTH:
|
|
|
|
|
value = "Both"; break;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool GERBER_JOBFILE_WRITER::CreateJobFile( const wxString& aFullFilename )
|
|
|
|
|
{
|
2018-03-16 18:32:49 +00:00
|
|
|
|
bool success;
|
2017-08-19 16:28:11 +00:00
|
|
|
|
wxString msg;
|
|
|
|
|
|
2018-04-08 17:49:02 +00:00
|
|
|
|
success = WriteJSONJobFile( aFullFilename );
|
2018-03-16 18:32:49 +00:00
|
|
|
|
|
|
|
|
|
if( !success )
|
2017-08-19 16:28:11 +00:00
|
|
|
|
{
|
|
|
|
|
if( m_reporter )
|
|
|
|
|
{
|
2017-12-15 11:37:46 +00:00
|
|
|
|
msg.Printf( _( "Unable to create job file \"%s\"" ), aFullFilename );
|
2017-08-19 16:28:11 +00:00
|
|
|
|
m_reporter->Report( msg, REPORTER::RPT_ERROR );
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-16 18:32:49 +00:00
|
|
|
|
else if( m_reporter )
|
|
|
|
|
{
|
|
|
|
|
msg.Printf( _( "Create Gerber job file \"%s\"" ), aFullFilename );
|
|
|
|
|
m_reporter->Report( msg, REPORTER::RPT_ACTION );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 TF.CreationDate ext:
|
|
|
|
|
// 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 msg = date.Format( wxT( "%z" ) ); // Extract the time zone offset
|
|
|
|
|
// The time zone offset format is + (or -) mm or hhmm (mm = number of minutes, hh = number of hours)
|
|
|
|
|
// we want +(or -) hh:mm
|
|
|
|
|
if( msg.Len() > 3 )
|
|
|
|
|
msg.insert( 3, ":", 1 ),
|
|
|
|
|
text.Printf( wxT( "\"CreationDate\": \"%s%s\"\n" ),date.FormatISOCombined(), msg );
|
|
|
|
|
addJSONObject( text );
|
|
|
|
|
|
|
|
|
|
closeBlockWithSep();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void GERBER_JOBFILE_WRITER::removeJSONSepararator()
|
|
|
|
|
{
|
|
|
|
|
if( m_JSONbuffer.Last() == ',' )
|
2017-08-19 16:28:11 +00:00
|
|
|
|
{
|
2018-03-16 18:32:49 +00:00
|
|
|
|
m_JSONbuffer.RemoveLast();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( m_JSONbuffer.Last() == '\n' )
|
|
|
|
|
{
|
|
|
|
|
m_JSONbuffer.RemoveLast();
|
|
|
|
|
|
|
|
|
|
if( m_JSONbuffer.Last() == ',' )
|
|
|
|
|
m_JSONbuffer.RemoveLast();
|
|
|
|
|
|
|
|
|
|
m_JSONbuffer.Append( '\n' );
|
2017-08-19 16:28:11 +00:00
|
|
|
|
}
|
2018-03-16 18:32:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-08 17:49:02 +00:00
|
|
|
|
bool GERBER_JOBFILE_WRITER::WriteJSONJobFile( const wxString& aFullFilename )
|
2018-03-16 18:32:49 +00:00
|
|
|
|
{
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
LOCALE_IO dummy;
|
|
|
|
|
|
|
|
|
|
// output the job file header
|
|
|
|
|
addJSONHeader();
|
|
|
|
|
|
|
|
|
|
// Add the General Specs
|
|
|
|
|
addJSONGeneralSpecs();
|
|
|
|
|
|
|
|
|
|
// Job file support a few design rules:
|
|
|
|
|
addJSONDesignRules();
|
|
|
|
|
|
|
|
|
|
// output the gerber file list:
|
|
|
|
|
addJSONFilesAttributes();
|
|
|
|
|
|
|
|
|
|
// 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 );
|
2017-08-19 16:28:11 +00:00
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-03-16 18:32:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void GERBER_JOBFILE_WRITER::addJSONGeneralSpecs()
|
|
|
|
|
{
|
|
|
|
|
addJSONObject( "\"GeneralSpecs\":\n" );
|
|
|
|
|
openBlock();
|
|
|
|
|
|
|
|
|
|
addJSONObject( "\"ProjectId\":\n" );
|
|
|
|
|
openBlock();
|
|
|
|
|
|
|
|
|
|
// Creates the ProjectId. Format is (from Gerber file format doc):
|
|
|
|
|
// ProjectId,<project id>,<project GUID>,<revision id>*%
|
|
|
|
|
// <project id> is the name of the project, restricted to basic ASCII symbols only,
|
|
|
|
|
// and comma not accepted
|
|
|
|
|
// All illegal chars will be replaced by underscore
|
|
|
|
|
// <project GUID> is a 32 hexadecimal digits string which is an unique id of a project.
|
|
|
|
|
// This is a random 128-bit number expressed in 32 hexadecimal digits.
|
|
|
|
|
// See en.wikipedia.org/wiki/GUID for more information
|
|
|
|
|
// However Kicad does not handle such a project GUID, so it is built from the board name
|
|
|
|
|
// Rem: <project id> accepts only ASCII 7 code (only basic ASCII codes are allowed in gerber files).
|
|
|
|
|
wxFileName fn = m_pcb->GetFileName();
|
|
|
|
|
wxString msg = fn.GetFullName();
|
|
|
|
|
wxString guid;
|
|
|
|
|
|
|
|
|
|
// Build a 32 digits GUID from the board name:
|
|
|
|
|
for( unsigned ii = 0; ii < msg.Len(); ii++ )
|
|
|
|
|
{
|
|
|
|
|
int cc1 = int( msg[ii] ) & 0x0F;
|
|
|
|
|
int cc2 = ( int( msg[ii] ) >> 4) & 0x0F;
|
|
|
|
|
guid << wxString::Format( wxT( "%X%X" ), cc2, cc1 );
|
|
|
|
|
|
|
|
|
|
if( guid.Len() >= 32 )
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// guid has 32 digits, so add missing digits
|
|
|
|
|
int cnt = 32 - guid.Len();
|
|
|
|
|
|
|
|
|
|
if( cnt > 0 )
|
|
|
|
|
guid.Append( '0', cnt );
|
|
|
|
|
|
|
|
|
|
// build the <project id> string: this is the board short filename (without ext)
|
|
|
|
|
// and all non ASCII chars are replaced by '_'
|
|
|
|
|
msg = fn.GetName();
|
|
|
|
|
|
|
|
|
|
// build the <rec> string. All non ASCII chars and comma are replaced by '_'
|
|
|
|
|
wxString rev = m_pcb->GetTitleBlock().GetRevision();
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
|
|
|
|
|
// Add some data to the JSON header, GeneralSpecs:
|
|
|
|
|
// number of copper layers
|
|
|
|
|
addJSONObject( wxString::Format( "\"LayerNumber\": %d,\n", m_pcb->GetCopperLayerCount() ) );
|
|
|
|
|
|
|
|
|
|
// Board thickness
|
|
|
|
|
addJSONObject( wxString::Format( "\"BoardThickness\": %.3f,\n",
|
|
|
|
|
m_pcb->GetDesignSettings().GetBoardThickness()*m_conversionUnits ) );
|
|
|
|
|
|
|
|
|
|
#if 0 // Not yet in use
|
|
|
|
|
/* The board type according to IPC-2221. There are six primary board types:
|
|
|
|
|
- Type 1 - Single-sided
|
|
|
|
|
- Type 2 - Double-sided
|
|
|
|
|
- Type 3 <EFBFBD> Multilayer, TH components only
|
|
|
|
|
- Type 4 <EFBFBD> Multilayer, with TH, blind and/or buried vias.
|
|
|
|
|
- Type 5 - Multilayer metal-core board, TH components only
|
|
|
|
|
- Type 6 - Multilayer metal-core
|
|
|
|
|
*/
|
|
|
|
|
addJSONObject( wxString::Format( "\"IPC-2221-Type\": \"%d\",\n", 4 ) );
|
|
|
|
|
|
|
|
|
|
/* Via protection: key words:
|
|
|
|
|
Ia Tented - Single-sided
|
|
|
|
|
Ib Tented - Double-sided
|
|
|
|
|
IIa Tented and Covered <EFBFBD> Single-sided
|
|
|
|
|
IIb Tented and Covered <EFBFBD> Double-sided
|
|
|
|
|
IIIa Plugged <EFBFBD> Single-sided
|
|
|
|
|
IIIb<EFBFBD><EFBFBD>.Plugged <EFBFBD> Double-sided
|
|
|
|
|
IVa<EFBFBD><EFBFBD>.Plugged and Covered <EFBFBD> Single-sided
|
|
|
|
|
IVb<EFBFBD><EFBFBD>.Plugged and Covered <EFBFBD> Double-sided
|
|
|
|
|
V Filled (fully plugged)
|
|
|
|
|
VI Filled and Covered
|
|
|
|
|
VIII Filled and Capped
|
|
|
|
|
None<EFBFBD>...No protection
|
|
|
|
|
*/
|
|
|
|
|
addJSONObject( wxString::Format( "\"ViaProtection\": \"%s\",\n", "Ib" ) );
|
|
|
|
|
#endif
|
|
|
|
|
removeJSONSepararator();
|
|
|
|
|
closeBlockWithSep();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void GERBER_JOBFILE_WRITER::addJSONFilesAttributes()
|
|
|
|
|
{
|
|
|
|
|
// Add the Files Attributes section in JSON format to m_JSONbuffer
|
|
|
|
|
addJSONObject( "\"FilesAttributes\":\n" );
|
|
|
|
|
openArrayBlock();
|
|
|
|
|
|
|
|
|
|
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";
|
|
|
|
|
|
|
|
|
|
if( layer <= B_Cu )
|
|
|
|
|
{
|
|
|
|
|
gbr_layer_id = "Copper,L";
|
|
|
|
|
|
|
|
|
|
if( layer == B_Cu )
|
|
|
|
|
gbr_layer_id << m_pcb->GetCopperLayerCount();
|
|
|
|
|
else
|
|
|
|
|
gbr_layer_id << layer+1;
|
|
|
|
|
|
|
|
|
|
gbr_layer_id << ",";
|
|
|
|
|
|
|
|
|
|
if( layer == B_Cu )
|
|
|
|
|
gbr_layer_id << "Bot";
|
|
|
|
|
else if( layer == F_Cu )
|
|
|
|
|
gbr_layer_id << "Top";
|
|
|
|
|
else
|
|
|
|
|
gbr_layer_id << "Inr";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
switch( layer )
|
|
|
|
|
{
|
|
|
|
|
case B_Adhes:
|
|
|
|
|
gbr_layer_id = "Glue,Bot"; break;
|
|
|
|
|
case F_Adhes:
|
|
|
|
|
gbr_layer_id = "Glue,Top"; break;
|
|
|
|
|
|
|
|
|
|
case B_Paste:
|
|
|
|
|
gbr_layer_id = "SolderPaste,Bot"; break;
|
|
|
|
|
case F_Paste:
|
|
|
|
|
gbr_layer_id = "SolderPaste,Top"; break;
|
|
|
|
|
|
|
|
|
|
case B_SilkS:
|
|
|
|
|
gbr_layer_id = "Legend,Bot"; break;
|
|
|
|
|
case F_SilkS:
|
|
|
|
|
gbr_layer_id = "Legend,Top"; break;
|
|
|
|
|
|
|
|
|
|
case B_Mask:
|
|
|
|
|
gbr_layer_id = "SolderMask,Bot"; polarity = "Negative"; break;
|
|
|
|
|
case F_Mask:
|
|
|
|
|
gbr_layer_id = "SolderMask,Top"; polarity = "Negative"; break;
|
|
|
|
|
|
|
|
|
|
case Edge_Cuts:
|
|
|
|
|
gbr_layer_id = "Profile"; break;
|
|
|
|
|
|
|
|
|
|
case B_Fab:
|
|
|
|
|
gbr_layer_id = "AssemblyDrawing,Bot"; break;
|
|
|
|
|
case F_Fab:
|
|
|
|
|
gbr_layer_id = "AssemblyDrawing,Top"; break;
|
|
|
|
|
|
|
|
|
|
case Dwgs_User:
|
|
|
|
|
case Cmts_User:
|
|
|
|
|
case Eco1_User:
|
|
|
|
|
case Eco2_User:
|
|
|
|
|
case Margin:
|
|
|
|
|
case B_CrtYd:
|
|
|
|
|
case F_CrtYd:
|
|
|
|
|
skip_file = true; break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
skip_file = true;
|
|
|
|
|
m_reporter->Report( "Unexpected layer id in job file",
|
|
|
|
|
REPORTER::RPT_ERROR );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( !skip_file )
|
|
|
|
|
{
|
|
|
|
|
// name can contain non ASCII7 chars.
|
|
|
|
|
// Only ASCII7 chars are accepted in gerber files. others must be converted to
|
|
|
|
|
// a gerber hexa sequence.
|
|
|
|
|
std::string strname = formatStringToGerber( 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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Close the file list:
|
|
|
|
|
removeJSONSepararator(); // remove the last separator
|
|
|
|
|
closeArrayBlockWithSep();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void GERBER_JOBFILE_WRITER::addJSONDesignRules()
|
|
|
|
|
{
|
|
|
|
|
// Add the Design Rules section in JSON format to m_JSONbuffer
|
|
|
|
|
// Job file support a few design rules:
|
|
|
|
|
const BOARD_DESIGN_SETTINGS& dsnSettings = m_pcb->GetDesignSettings();
|
|
|
|
|
NETCLASS defaultNC = *dsnSettings.GetDefault();
|
|
|
|
|
int minclearanceOuter = defaultNC.GetClearance();
|
|
|
|
|
bool hasInnerLayers = m_pcb->GetCopperLayerCount() > 2;
|
|
|
|
|
|
|
|
|
|
// 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 )
|
|
|
|
|
{
|
|
|
|
|
NETCLASS netclass = *it->second;
|
|
|
|
|
minclearanceOuter = std::min( minclearanceOuter, netclass.GetClearance() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// job file knows different clearance types.
|
|
|
|
|
// Kicad knows only one clearance for pads and tracks
|
|
|
|
|
int minclearance_track2track = minclearanceOuter;
|
|
|
|
|
|
|
|
|
|
// However, pads can have a specific clearance defined for a pad or a footprint,
|
|
|
|
|
// and min clearance can be dependent on layers.
|
|
|
|
|
// Search for a minimal pad clearance:
|
|
|
|
|
int minPadClearanceOuter = defaultNC.GetClearance();
|
|
|
|
|
int minPadClearanceInner = defaultNC.GetClearance();
|
|
|
|
|
|
|
|
|
|
for( MODULE* module : m_pcb->Modules() )
|
|
|
|
|
{
|
|
|
|
|
for( auto& pad : module->Pads() )
|
|
|
|
|
{
|
|
|
|
|
if( ( pad->GetLayerSet() & LSET::InternalCuMask() ).any() )
|
|
|
|
|
minPadClearanceInner = std::min( minPadClearanceInner, pad->GetClearance() );
|
|
|
|
|
|
|
|
|
|
if( ( pad->GetLayerSet() & LSET::ExternalCuMask() ).any() )
|
|
|
|
|
minPadClearanceOuter = std::min( minPadClearanceOuter, pad->GetClearance() );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 ) );
|
|
|
|
|
|
|
|
|
|
// Until this is changed in Kicad, use the same value for internal tracks
|
|
|
|
|
int minclearanceInner = minclearanceOuter;
|
|
|
|
|
|
|
|
|
|
// Output the minimal track width
|
|
|
|
|
int mintrackWidthOuter = INT_MAX;
|
|
|
|
|
int mintrackWidthInner = INT_MAX;
|
|
|
|
|
|
|
|
|
|
for( TRACK* track : m_pcb->Tracks() )
|
|
|
|
|
{
|
|
|
|
|
if( track->Type() == PCB_VIA_T )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if( track->GetLayer() == B_Cu || track->GetLayer() == F_Cu )
|
|
|
|
|
mintrackWidthOuter = std::min( mintrackWidthOuter, track->GetWidth() );
|
|
|
|
|
else
|
|
|
|
|
mintrackWidthInner = std::min( mintrackWidthInner, track->GetWidth() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( mintrackWidthOuter != INT_MAX )
|
|
|
|
|
addJSONObject( wxString::Format( "\"MinLineWidth\": %.3f,\n",
|
|
|
|
|
mintrackWidthOuter*m_conversionUnits ) );
|
|
|
|
|
|
|
|
|
|
// Output the minimal zone to xx clearance
|
|
|
|
|
// Note: zones can have a zone clearance set to 0
|
|
|
|
|
// if happens, the actual zone clearance is the clearance of its class
|
|
|
|
|
minclearanceOuter = INT_MAX;
|
|
|
|
|
minclearanceInner = INT_MAX;
|
|
|
|
|
|
|
|
|
|
for( int ii = 0; ii < m_pcb->GetAreaCount(); ii++ )
|
|
|
|
|
{
|
|
|
|
|
ZONE_CONTAINER* zone = m_pcb->GetArea( ii );
|
|
|
|
|
|
|
|
|
|
if( zone->GetIsKeepout() || !zone->IsOnCopperLayer() )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
int zclerance = zone->GetClearance();
|
|
|
|
|
|
|
|
|
|
if( zone->GetLayer() == B_Cu || zone->GetLayer() == F_Cu )
|
|
|
|
|
minclearanceOuter = std::min( minclearanceOuter, zclerance );
|
|
|
|
|
else
|
|
|
|
|
minclearanceInner = std::min( minclearanceInner, zclerance );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( minclearanceOuter != INT_MAX )
|
|
|
|
|
addJSONObject( wxString::Format( "\"TrackToRegion\": %.3f,\n",
|
|
|
|
|
minclearanceOuter*m_conversionUnits ) );
|
|
|
|
|
|
|
|
|
|
if( minclearanceOuter != INT_MAX )
|
|
|
|
|
addJSONObject( wxString::Format( "\"RegionToRegion\": %.3f,\n",
|
|
|
|
|
minclearanceOuter*m_conversionUnits ) );
|
|
|
|
|
|
|
|
|
|
removeJSONSepararator(); // remove the last separator
|
|
|
|
|
closeBlock();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if( hasInnerLayers )
|
|
|
|
|
{
|
|
|
|
|
closeBlockWithSep();
|
|
|
|
|
|
|
|
|
|
openBlock();
|
|
|
|
|
addJSONObject( "\"Layers\": \"Inner\",\n" );
|
|
|
|
|
|
|
|
|
|
addJSONObject( "\"Values\":\n" );
|
|
|
|
|
openBlock();
|
|
|
|
|
|
|
|
|
|
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 ) );
|
|
|
|
|
|
|
|
|
|
if( mintrackWidthInner != INT_MAX )
|
|
|
|
|
addJSONObject( wxString::Format( "\"MinLineWidth\": %.3f,\n", mintrackWidthInner*m_conversionUnits ) );
|
|
|
|
|
|
|
|
|
|
if( minclearanceInner != INT_MAX )
|
|
|
|
|
addJSONObject( wxString::Format( "\"TrackToRegion\": %.3f,\n", minclearanceInner*m_conversionUnits ) );
|
|
|
|
|
|
|
|
|
|
if( minclearanceInner != INT_MAX )
|
|
|
|
|
addJSONObject( wxString::Format( "\"RegionToRegion\": %.3f,\n", minclearanceInner*m_conversionUnits ) );
|
|
|
|
|
|
|
|
|
|
removeJSONSepararator(); // remove the last separator
|
|
|
|
|
closeBlock();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close DesignRules
|
|
|
|
|
closeArrayBlockWithSep();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void GERBER_JOBFILE_WRITER::addJSONMaterialStackup()
|
|
|
|
|
{
|
|
|
|
|
// Add the Material Stackup section in JSON format to m_JSONbuffer
|
|
|
|
|
addJSONObject( "\"MaterialStackup\":\n" );
|
|
|
|
|
openArrayBlock();
|
|
|
|
|
|
|
|
|
|
// Build the candidates: only layers on a board are candidates:
|
|
|
|
|
LSET maskLayer;
|
|
|
|
|
|
|
|
|
|
for( unsigned ii = 0; ii < m_params.m_GerberFileList.GetCount(); ii ++ )
|
|
|
|
|
{
|
|
|
|
|
PCB_LAYER_ID layer = m_params.m_LayerId[ii];
|
|
|
|
|
|
|
|
|
|
if( layer <= B_Cu )
|
|
|
|
|
maskLayer.set( layer );
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
switch( layer )
|
|
|
|
|
{
|
|
|
|
|
case B_Paste:
|
|
|
|
|
case F_Paste:
|
|
|
|
|
case B_SilkS:
|
|
|
|
|
case F_SilkS:
|
|
|
|
|
case B_Mask:
|
|
|
|
|
case F_Mask:
|
|
|
|
|
maskLayer.set( layer );
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Edge_Cuts:
|
|
|
|
|
case B_Adhes:
|
|
|
|
|
case F_Adhes:
|
|
|
|
|
case B_Fab:
|
|
|
|
|
case F_Fab:
|
|
|
|
|
case Dwgs_User:
|
|
|
|
|
case Cmts_User:
|
|
|
|
|
case Eco1_User:
|
|
|
|
|
case Eco2_User:
|
|
|
|
|
case Margin:
|
|
|
|
|
case B_CrtYd:
|
|
|
|
|
case F_CrtYd:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
m_reporter->Report(
|
|
|
|
|
wxString::Format( "Unexpected layer id %d in job file", layer ),
|
|
|
|
|
REPORTER::RPT_ERROR );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// build a candidate list (in reverse order: bottom to top):
|
|
|
|
|
LSEQ list = maskLayer.SeqStackupBottom2Top();
|
|
|
|
|
// Generate the list (top to bottom):
|
|
|
|
|
for( int ii = list.size()-1; ii >= 0; --ii )
|
|
|
|
|
{
|
|
|
|
|
PCB_LAYER_ID layer = list[ii];
|
|
|
|
|
wxString layer_type;
|
|
|
|
|
wxString color;
|
|
|
|
|
wxString dielectric;
|
|
|
|
|
double thickness = 0.0; // layer thickness in mm
|
|
|
|
|
|
|
|
|
|
if( layer <= B_Cu )
|
|
|
|
|
{
|
|
|
|
|
layer_type = "Copper";
|
|
|
|
|
//thickness = 0.035;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
switch( layer )
|
|
|
|
|
{
|
|
|
|
|
case B_Paste:
|
|
|
|
|
case F_Paste:
|
|
|
|
|
layer_type = "SolderPaste";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case B_SilkS:
|
|
|
|
|
case F_SilkS:
|
|
|
|
|
//color = "White";
|
|
|
|
|
layer_type = "Legend";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case B_Mask:
|
|
|
|
|
case F_Mask:
|
|
|
|
|
//color = "Green";
|
|
|
|
|
//thickness = 0.025;
|
|
|
|
|
layer_type = "SolderMask";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
openBlock();
|
|
|
|
|
addJSONObject( wxString::Format( "\"Type\": \"%s\",\n", layer_type ) );
|
|
|
|
|
|
|
|
|
|
if( !color.IsEmpty() )
|
|
|
|
|
addJSONObject( wxString::Format( "\"Color\": \"%s\",\n", color ) );
|
|
|
|
|
|
|
|
|
|
if( thickness > 0.0 )
|
|
|
|
|
addJSONObject( wxString::Format( "\"Thickness\": %f,\n", thickness ) );
|
|
|
|
|
|
|
|
|
|
std::string strname = formatStringToGerber( m_pcb->GetLayerName( layer ) );
|
|
|
|
|
addJSONObject( wxString::Format( "\"S_Notes\": \"Layer %s\",\n", strname.c_str() ) );
|
|
|
|
|
removeJSONSepararator();
|
|
|
|
|
closeBlockWithSep();
|
|
|
|
|
|
|
|
|
|
if( layer < B_Cu ) // Add dielectric between copper layers
|
|
|
|
|
{
|
|
|
|
|
dielectric = "FR4"; // Temporary
|
|
|
|
|
|
|
|
|
|
openBlock();
|
|
|
|
|
addJSONObject( wxString::Format( "\"Type\": \"%s\",\n", "Dielectric" ) );
|
|
|
|
|
|
|
|
|
|
if( thickness > 0.0 )
|
|
|
|
|
addJSONObject( wxString::Format( "\"Thickness\": %f,\n", color ) );
|
|
|
|
|
|
|
|
|
|
if( !dielectric.IsEmpty() )
|
|
|
|
|
addJSONObject( wxString::Format( "\"Material\": \"%s\",\n", dielectric ) );
|
|
|
|
|
|
|
|
|
|
addJSONObject( wxString::Format( "\"S_Notes\": \"Layers L%d/L%d\",\n",
|
|
|
|
|
layer+1, layer+2 ) );
|
|
|
|
|
removeJSONSepararator();
|
|
|
|
|
closeBlockWithSep();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
removeJSONSepararator();
|
|
|
|
|
closeArrayBlockWithSep();
|
|
|
|
|
}
|