Add support for .gbrjob new file format (JSON format) in Gerbview

Add experimental code to generate .gbrjob files in the new JSON file format
This commit is contained in:
jean-pierre charras 2018-03-16 19:32:49 +01:00
parent 1a845bc4ad
commit 2ff74cb3fc
7 changed files with 2240 additions and 70 deletions

View File

@ -649,7 +649,6 @@ void GERBER_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, doub
EDA_DRAW_MODE_T trace_mode, void* aData )
{
wxASSERT( outputFile );
int x0, y0, x1, y1, delta;
wxSize size( aSize );
GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
@ -684,18 +683,17 @@ void GERBER_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, doub
if( trace_mode == FILLED )
{
// TODO: use an aperture macro to declare the rotated pad
//
// Flash a pad anchor, if a netlist attribute is set
if( aData )
FlashPadCircle( pos, size.x, trace_mode, aData );
// The pad is reduced to an segment with dy > dx
delta = size.y - size.x;
x0 = 0;
y0 = -delta / 2;
x1 = 0;
y1 = delta / 2;
int delta = size.y - size.x;
int x0 = 0;
int y0 = -delta / 2;
int x1 = 0;
int y1 = delta / 2;
RotatePoint( &x0, &y0, orient );
RotatePoint( &x1, &y1, orient );
GBR_METADATA metadata;
@ -740,8 +738,7 @@ void GERBER_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& aSize,
case 900:
case 2700: // rotation of 90 degrees or 270 swaps sizes
std::swap( size.x, size.y );
// Pass through
// Pass through
case 0:
case 1800:
if( trace_mode == SKETCH )
@ -755,7 +752,7 @@ void GERBER_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& aSize,
pos.y - (size.y - currentPenWidth) / 2 ),
wxPoint( pos.x + (size.x - currentPenWidth) / 2,
pos.y + (size.y - currentPenWidth) / 2 ),
NO_FILL );
NO_FILL, GetCurrentLineWidth() );
}
else
{
@ -800,24 +797,6 @@ void GERBER_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aS
EDA_DRAW_MODE_T aTraceMode, void* aData )
{
// Currently, a Pad RoundRect is plotted as polygon.
// TODO: use Aperture macro and flash it
SHAPE_POLY_SET outline;
const int segmentToCircleCount = 64;
TransformRoundRectToPolygon( outline, aPadPos, aSize, aOrient,
aCornerRadius, segmentToCircleCount );
std::vector< wxPoint > cornerList;
cornerList.reserve( segmentToCircleCount + 5 );
// TransformRoundRectToPolygon creates only one convex polygon
SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
for( int ii = 0; ii < poly.PointCount(); ++ii )
cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) );
// Close polygon
cornerList.push_back( cornerList[0] );
GBR_METADATA gbr_metadata;
if( aData )
@ -832,7 +811,32 @@ void GERBER_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aS
gbr_metadata.m_NetlistMetadata.ClearAttribute( &attrname ); // not allowed on inner layers
}
PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILLED_SHAPE : NO_FILL, USE_DEFAULT_LINE_WIDTH, &gbr_metadata );
if( aTraceMode != FILLED )
SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, &gbr_metadata );
// Currently, a Pad RoundRect is plotted as polygon.
// TODO: use Aperture macro and flash it
SHAPE_POLY_SET outline;
const int segmentToCircleCount = 64;
TransformRoundRectToPolygon( outline, aPadPos, aSize, aOrient,
aCornerRadius, segmentToCircleCount );
if( aTraceMode != FILLED )
outline.Inflate( -GetCurrentLineWidth()/2, 16 );
std::vector< wxPoint > cornerList;
// TransformRoundRectToPolygon creates only one convex polygon
SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
cornerList.reserve( poly.PointCount() + 1 );
for( int ii = 0; ii < poly.PointCount(); ++ii )
cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) );
// Close polygon
cornerList.push_back( cornerList[0] );
PlotPoly( cornerList, aTraceMode == FILLED ? FILLED_SHAPE : NO_FILL,
aTraceMode == FILLED ? 0 : GetCurrentLineWidth(), &gbr_metadata );
// Now, flash a pad anchor, if a netlist attribute is set
// (remove me when a Aperture macro will be used)
@ -854,7 +858,9 @@ void GERBER_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize
// However, because the anchor pad can be circle or rect, we use only
// a circle not bigger than the rect.
// the main purpose is to print a flashed DCode as pad anchor
FlashPadCircle( aPadPos, std::min( aSize.x, aSize.y ), aTraceMode, aData );
if( aTraceMode == FILLED )
FlashPadCircle( aPadPos, std::min( aSize.x, aSize.y ), aTraceMode, aData );
GBR_METADATA gbr_metadata;
if( aData )
@ -869,11 +875,20 @@ void GERBER_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize
gbr_metadata.m_NetlistMetadata.ClearAttribute( &attrname ); // not allowed on inner layers
}
SHAPE_POLY_SET polyshape = *aPolygons;
if( aTraceMode != FILLED )
{
SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, &gbr_metadata );
polyshape.Inflate( -GetCurrentLineWidth()/2, 16 );
}
std::vector< wxPoint > cornerList;
for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt )
for( int cnt = 0; cnt < polyshape.OutlineCount(); ++cnt )
{
SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt );
SHAPE_LINE_CHAIN& poly = polyshape.Outline( cnt );
cornerList.clear();
for( int ii = 0; ii < poly.PointCount(); ++ii )
@ -882,7 +897,9 @@ void GERBER_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize
// Close polygon
cornerList.push_back( cornerList[0] );
PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILLED_SHAPE : NO_FILL, USE_DEFAULT_LINE_WIDTH, &gbr_metadata );
PlotPoly( cornerList,
aTraceMode == FILLED ? FILLED_SHAPE : NO_FILL,
aTraceMode == FILLED ? 0 : GetCurrentLineWidth(), &gbr_metadata );
}
}
@ -902,7 +919,7 @@ void GERBER_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint* aCo
// Now, flash a pad anchor, if a netlist attribute is set
// (remove me when a Aperture macro will be used)
if( aData && (aTrace_Mode==FILLED) )
if( aData && ( aTrace_Mode == FILLED ) )
{
// Calculate the radius of the circle inside the shape
// It is the smaller dist from shape pos to edges
@ -945,7 +962,9 @@ void GERBER_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint* aCo
}
SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, &metadata );
PlotPoly( cornerList, aTrace_Mode==FILLED ? FILLED_SHAPE : NO_FILL, USE_DEFAULT_LINE_WIDTH, &metadata );
PlotPoly( cornerList, aTrace_Mode == FILLED ? FILLED_SHAPE : NO_FILL,
aTrace_Mode == FILLED ? 0 : GetCurrentLineWidth(),
&metadata );
}

View File

@ -51,6 +51,7 @@ set( GERBVIEW_SRCS
gerbview_config.cpp
gerbview_frame.cpp
hotkeys.cpp
json11.cpp
job_file_reader.cpp
locate.cpp
menubar.cpp

View File

@ -41,19 +41,39 @@
#include <html_messagebox.h>
#include <view/view.h>
#include "json11.hpp" // A light JSON parser
/**
* this class read and parse a Gerber job file to extract useful info
* for GerbView
*
* In a gerber job file, data lines start by
* In a gerber job file, old (deprecated) format, data lines start by
* %TF. (usual Gerber X2 info)
* %TJ.B. (board info)
* %TJ.D. (design info)
* %TJ.L. (layers info)
* some others are not yet handled by Kicad
* M02* is the last line
* In a gerber job file, JSON format, first lines are
* {
* "Header":
* and the block ( a JSON array) containing the filename of files to load is
* "FilesAttributes":
* [
* {
* "Path": "interf_u-Composant.gbr",
* "FileFunction": "Copper,L1,Top",
* "FilePolarity": "Positive"
* },
* {
* "Path": "interf_u-In1.Cu.gbr",
* "FileFunction": "Copper,L2,Inr",
* "FilePolarity": "Positive"
* },
* ],
*/
class GERBER_JOBFILE_READER
{
public:
@ -96,28 +116,65 @@ bool GERBER_JOBFILE_READER::ReadGerberJobFile()
wxString msg;
wxString data;
while( true )
// detect the file format: old gerber format of new JSON format
bool json_format = false;
char* line = jobfileReader.ReadLine();
if( !line ) // end of file
return false;
data = line;
if( data.Contains("{" ) )
json_format = true;
if( json_format )
{
char* line = jobfileReader.ReadLine();
while( ( line = jobfileReader.ReadLine() ) )
data << '\n' << line;
if( !line ) // end of file
break;
std::string err;
json11::Json json_parser = json11::Json::parse( TO_UTF8( data ), err );
wxString text( line );
text.Trim( true );
text.Trim( false );
if( !err.empty() )
return false;
// Search for lines starting by '%', others are not usefull
if( text.StartsWith( "%TJ.L.", &data ) // First job file syntax
|| text.StartsWith( "%TJ.L_", &data ) // current job file syntax
)
for( auto& entry : json_parser["FilesAttributes"].array_items() )
{
parseTJLayerString( data );
continue;
//wxLogMessage( entry.dump().c_str() );
std::string name = entry["Path"].string_value();
//wxLogMessage( name.c_str() );
m_GerberFiles.Add( FormatStringFromGerber( name ) );
}
}
else
{
jobfileReader.Rewind();
if( text.StartsWith( "M02" ) ) // End of file
break;
while( true )
{
line = jobfileReader.ReadLine();
if( !line ) // end of file
break;
wxString text( line );
text.Trim( true );
text.Trim( false );
// Search for lines starting by '%', others are not usefull
if( text.StartsWith( "%TJ.L.", &data ) // First job file syntax
|| text.StartsWith( "%TJ.L_", &data ) // current job file syntax
)
{
parseTJLayerString( data );
continue;
}
if( text.StartsWith( "M02" ) ) // End of file
break;
}
}
return true;

1153
gerbview/json11.cpp Normal file

File diff suppressed because it is too large Load Diff

251
gerbview/json11.hpp Normal file
View File

@ -0,0 +1,251 @@
/* json11
*
* json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
*
* The core object provided by the library is json11::Json. A Json object represents any JSON
* value: null, bool, number (int or double), string (std::string), array (std::vector), or
* object (std::map).
*
* Json objects act like values: they can be assigned, copied, moved, compared for equality or
* order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and
* Json::parse (static) to parse a std::string as a Json object.
*
* Internally, the various types of Json object are represented by the JsonValue class
* hierarchy.
*
* A note on numbers - JSON specifies the syntax of number formatting but not its semantics,
* so some JSON implementations distinguish between integers and floating-point numbers, while
* some don't. In json11, we choose the latter. Because some JSON implementations (namely
* Javascript itself) treat all numbers as the same type, distinguishing the two leads
* to JSON that will be *silently* changed by a round-trip through those implementations.
* Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also
* provides integer helpers.
*
* Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the
* range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64
* or long long to avoid the Y2038K problem; a double storing microseconds since some epoch
* will be exact for +/- 275 years.)
*/
/* Copyright (c) 2013 Dropbox, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <initializer_list>
#ifdef _MSC_VER
#if _MSC_VER <= 1800 // VS 2013
#ifndef noexcept
#define noexcept throw()
#endif
#ifndef snprintf
#define snprintf _snprintf_s
#endif
#endif
#endif
namespace json11 {
enum JsonParse
{
STANDARD, COMMENTS
};
class JsonValue;
class Json final
{
public:
// Types
enum Type
{
NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT
};
// Array and object typedefs
typedef std::vector<Json> array;
typedef std::map<std::string, Json> object;
// Constructors for the various types of JSON value.
Json() noexcept; // NUL
Json( std::nullptr_t ) noexcept; // NUL
Json( double value ); // NUMBER
Json( int value ); // NUMBER
Json( bool value ); // BOOL
Json( const std::string& value ); // STRING
Json( std::string&& value ); // STRING
Json( const char* value ); // STRING
Json( const array& values ); // ARRAY
Json( array&& values ); // ARRAY
Json( const object& values ); // OBJECT
Json( object&& values ); // OBJECT
// Implicit constructor: anything with a to_json() function.
template <class T, class = decltype(& T::to_json)>
Json( const T& t ) : Json( t.to_json() ) {}
// Implicit constructor: map-like objects (std::map, std::unordered_map, etc)
template <class M, typename std::enable_if<
std::is_constructible<std::string,
decltype(std::declval<M>().begin()->first)>::value
&& std::is_constructible<Json,
decltype(std::declval<M>().begin()->second)>::value,
int>::type = 0>
Json( const M& m ) : Json( object( m.begin(), m.end() ) ) {}
// Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc)
template <class V, typename std::enable_if<
std::is_constructible<Json, decltype( * std::declval<V>().begin() )>::value,
int>::type = 0>
Json( const V& v ) : Json( array( v.begin(), v.end() ) ) {}
// This prevents Json(some_pointer) from accidentally producing a bool. Use
// Json(bool(some_pointer)) if that behavior is desired.
Json( void* ) = delete;
// Accessors
Type type() const;
bool is_null() const { return type() == NUL; }
bool is_number() const { return type() == NUMBER; }
bool is_bool() const { return type() == BOOL; }
bool is_string() const { return type() == STRING; }
bool is_array() const { return type() == ARRAY; }
bool is_object() const { return type() == OBJECT; }
// Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not
// distinguish between integer and non-integer numbers - number_value() and int_value()
// can both be applied to a NUMBER-typed object.
double number_value() const;
int int_value() const;
// Return the enclosed value if this is a boolean, false otherwise.
bool bool_value() const;
// Return the enclosed string if this is a string, "" otherwise.
const std::string& string_value() const;
// Return the enclosed std::vector if this is an array, or an empty vector otherwise.
const array& array_items() const;
// Return the enclosed std::map if this is an object, or an empty map otherwise.
const object& object_items() const;
// Return a reference to arr[i] if this is an array, Json() otherwise.
const Json& operator[]( size_t i ) const;
// Return a reference to obj[key] if this is an object, Json() otherwise.
const Json& operator[]( const std::string& key ) const;
// Serialize.
void dump( std::string& out ) const;
std::string dump() const
{
std::string out;
dump( out );
return out;
}
// Parse. If parse fails, return Json() and assign an error message to err.
static Json parse( const std::string& in,
std::string& err,
JsonParse strategy = JsonParse::STANDARD );
static Json parse( const char* in,
std::string& err,
JsonParse strategy = JsonParse::STANDARD )
{
if( in )
{
return parse( std::string( in ), err, strategy );
}
else
{
err = "null input";
return nullptr;
}
}
// Parse multiple objects, concatenated or separated by whitespace
static std::vector<Json> parse_multi( const std::string& in,
std::string::size_type& parser_stop_pos,
std::string& err,
JsonParse strategy = JsonParse::STANDARD );
static inline std::vector<Json> parse_multi( const std::string& in,
std::string& err,
JsonParse strategy = JsonParse::STANDARD )
{
std::string::size_type parser_stop_pos;
return parse_multi( in, parser_stop_pos, err, strategy );
}
bool operator==( const Json& rhs ) const;
bool operator<( const Json& rhs ) const;
bool operator!=( const Json& rhs ) const { return !(*this == rhs); }
bool operator<=( const Json& rhs ) const { return !(rhs < *this); }
bool operator>( const Json& rhs ) const { return rhs < *this; }
bool operator>=( const Json& rhs ) const { return !(*this < rhs); }
/* has_shape(types, err)
*
* Return true if this is a JSON object and, for each item in types, has a field of
* the given type. If not, return false and set err to a descriptive message.
*/
typedef std::initializer_list<std::pair<std::string, Type> > shape;
bool has_shape( const shape& types, std::string& err ) const;
private:
std::shared_ptr<JsonValue> m_ptr;
};
// Internal class hierarchy - JsonValue objects are not exposed to users of this API.
class JsonValue
{
protected:
friend class Json;
friend class JsonInt;
friend class JsonDouble;
virtual Json::Type type() const = 0;
virtual bool equals( const JsonValue* other ) const = 0;
virtual bool less( const JsonValue* other ) const = 0;
virtual void dump( std::string& out ) const = 0;
virtual double number_value() const;
virtual int int_value() const;
virtual bool bool_value() const;
virtual const std::string& string_value() const;
virtual const Json::array& array_items() const;
virtual const Json& operator[]( size_t i ) const;
virtual const Json::object& object_items() const;
virtual const Json& operator[]( const std::string& key ) const;
virtual ~JsonValue() {}
};
} // namespace json11

View File

@ -1,8 +1,8 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 Jean_Pierre Charras <jp.charras at wanadoo.fr>
* Copyright (C) 1992-2017 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2018 Jean_Pierre Charras <jp.charras at wanadoo.fr>
* Copyright (C) 1992-2018 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
@ -24,7 +24,7 @@
/**
* @file gendrill_gerber_writer.cpp
* @brief Functions to create drill files in gerber X2 format.
* @brief Functions to create the Gerber job file in JSON format.
*/
#include <fctsys.h>
@ -52,6 +52,8 @@ 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_useJSONformat = true;
m_indent = 0;
}
enum ONSIDE GERBER_JOBFILE_WRITER::hasSilkLayers()
@ -113,10 +115,38 @@ const char* GERBER_JOBFILE_WRITER::sideKeyValue( enum ONSIDE aValue )
}
bool GERBER_JOBFILE_WRITER::CreateJobFile( const wxString& aFullFilename )
{
bool success;
wxString msg;
if( m_useJSONformat )
success = CreateJSONJobFile( aFullFilename );
else
success = CreateGbrJobFile( aFullFilename );
if( !success )
{
if( m_reporter )
{
msg.Printf( _( "Unable to create job file \"%s\"" ), aFullFilename );
m_reporter->Report( msg, REPORTER::RPT_ERROR );
}
}
else if( m_reporter )
{
msg.Printf( _( "Create Gerber job file \"%s\"" ), aFullFilename );
m_reporter->Report( msg, REPORTER::RPT_ACTION );
}
return success;
}
extern void BuildGerberX2Header( const BOARD *aBoard, wxArrayString& aHeader );
bool GERBER_JOBFILE_WRITER::CreateJobFile( const wxString& aFullFilename )
bool GERBER_JOBFILE_WRITER::CreateGbrJobFile( const wxString& aFullFilename )
{
// Note: in Gerber job file, dimensions are in mm, and are floating numbers
FILE* jobFile = wxFopen( aFullFilename, "wt" );
@ -124,14 +154,7 @@ bool GERBER_JOBFILE_WRITER::CreateJobFile( const wxString& aFullFilename )
wxString msg;
if( jobFile == nullptr )
{
if( m_reporter )
{
msg.Printf( _( "Unable to create job file \"%s\"" ), aFullFilename );
m_reporter->Report( msg, REPORTER::RPT_ERROR );
}
return false;
}
LOCALE_IO dummy;
@ -378,11 +401,581 @@ bool GERBER_JOBFILE_WRITER::CreateJobFile( const wxString& aFullFilename )
fclose( jobFile );
if( m_reporter )
return true;
}
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() == ',' )
{
msg.Printf( _( "Create Gerber job file \"%s\"" ), aFullFilename );
m_reporter->Report( msg, REPORTER::RPT_ACTION );
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::CreateJSONJobFile( 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;
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 );
return true;
}
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 Multilayer, TH components only
- Type 4 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 Single-sided
IIb Tented and Covered Double-sided
IIIa Plugged Single-sided
IIIb.Plugged Double-sided
IVa.Plugged and Covered Single-sided
IVb.Plugged and Covered Double-sided
V Filled (fully plugged)
VI Filled and Covered
VIII Filled and Capped
None...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( "\"Values\":\n" );
openBlock();
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
closeBlock();
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();
}

View File

@ -1,13 +1,13 @@
/**
* @file gerber_jobfile_writer.h
* @brief Classes used in drill files, map files and report files generation.
* @brief Classes used to generate a Gerber job file in JSON
*/
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-2017 Jean_Pierre Charras <jp.charras at wanadoo.fr>
* Copyright (C) 1992-2017 KiCad Developers, see change_log.txt for contributors.
* Copyright (C) 1992-2018 Jean_Pierre Charras <jp.charras at wanadoo.fr>
* Copyright (C) 1992-2018 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
@ -87,12 +87,26 @@ public:
}
/**
* Creates an Excellon drill file
* Creates a Gerber job file
* @param aFullFilename = the full filename
* @return true, or false if the file cannot be created
*/
bool CreateJobFile( const wxString& aFullFilename );
/**
* Creates a Gerber job file in old gbr format
* @param aFullFilename = the full filename
* @return true, or false if the file cannot be created
*/
bool CreateGbrJobFile( const wxString& aFullFilename );
/**
* Creates an Gerber job file in JSON format
* @param aFullFilename = the full filename
* @param aParams = true for a NPTH file, false for a PTH file
* @return true, or false if the file cannot be created
*/
bool CreateJobFile( const wxString& aFullFilename );
bool CreateJSONJobFile( const wxString& aFullFilename );
private:
/** @return SIDE_NONE if no silk screen layer is in list
@ -114,12 +128,94 @@ private:
*/
const char* sideKeyValue( enum ONSIDE aValue );
/**
* Add the job file header in JSON format to m_JSONbuffer
*/
void addJSONHeader();
/**
* Add the General Specs in JSON format to m_JSONbuffer
*/
void addJSONGeneralSpecs();
/**
* Add the Files Attributes section in JSON format to m_JSONbuffer
*/
void addJSONFilesAttributes();
/**
* Add the Material Stackup section in JSON format to m_JSONbuffer
* This is the ordered list of stackup layers (mask, paste, silk, copper, dielectric)
* used to make the physical board. Therefore not all layers are listed here
*/
void addJSONMaterialStackup();
/**
* Add the Design Rules section in JSON format to m_JSONbuffer
*/
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;
}
private:
BOARD* m_pcb; // The board
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)
bool m_useJSONformat; // temporary option
wxString m_JSONbuffer; // a buffer to build the JSON data
int m_indent; // helper for JSON format: the current indentation value
};
#endif // #ifndef GERBER_JOBFILE_WRITER_H