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:
parent
1a845bc4ad
commit
2ff74cb3fc
|
@ -649,7 +649,6 @@ void GERBER_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, doub
|
||||||
EDA_DRAW_MODE_T trace_mode, void* aData )
|
EDA_DRAW_MODE_T trace_mode, void* aData )
|
||||||
{
|
{
|
||||||
wxASSERT( outputFile );
|
wxASSERT( outputFile );
|
||||||
int x0, y0, x1, y1, delta;
|
|
||||||
wxSize size( aSize );
|
wxSize size( aSize );
|
||||||
GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
|
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 )
|
if( trace_mode == FILLED )
|
||||||
{
|
{
|
||||||
// TODO: use an aperture macro to declare the rotated pad
|
// TODO: use an aperture macro to declare the rotated pad
|
||||||
//
|
|
||||||
|
|
||||||
// Flash a pad anchor, if a netlist attribute is set
|
// Flash a pad anchor, if a netlist attribute is set
|
||||||
if( aData )
|
if( aData )
|
||||||
FlashPadCircle( pos, size.x, trace_mode, aData );
|
FlashPadCircle( pos, size.x, trace_mode, aData );
|
||||||
|
|
||||||
// The pad is reduced to an segment with dy > dx
|
// The pad is reduced to an segment with dy > dx
|
||||||
delta = size.y - size.x;
|
int delta = size.y - size.x;
|
||||||
x0 = 0;
|
int x0 = 0;
|
||||||
y0 = -delta / 2;
|
int y0 = -delta / 2;
|
||||||
x1 = 0;
|
int x1 = 0;
|
||||||
y1 = delta / 2;
|
int y1 = delta / 2;
|
||||||
RotatePoint( &x0, &y0, orient );
|
RotatePoint( &x0, &y0, orient );
|
||||||
RotatePoint( &x1, &y1, orient );
|
RotatePoint( &x1, &y1, orient );
|
||||||
GBR_METADATA metadata;
|
GBR_METADATA metadata;
|
||||||
|
@ -740,7 +738,6 @@ void GERBER_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& aSize,
|
||||||
case 900:
|
case 900:
|
||||||
case 2700: // rotation of 90 degrees or 270 swaps sizes
|
case 2700: // rotation of 90 degrees or 270 swaps sizes
|
||||||
std::swap( size.x, size.y );
|
std::swap( size.x, size.y );
|
||||||
|
|
||||||
// Pass through
|
// Pass through
|
||||||
case 0:
|
case 0:
|
||||||
case 1800:
|
case 1800:
|
||||||
|
@ -755,7 +752,7 @@ void GERBER_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& aSize,
|
||||||
pos.y - (size.y - currentPenWidth) / 2 ),
|
pos.y - (size.y - currentPenWidth) / 2 ),
|
||||||
wxPoint( pos.x + (size.x - currentPenWidth) / 2,
|
wxPoint( pos.x + (size.x - currentPenWidth) / 2,
|
||||||
pos.y + (size.y - currentPenWidth) / 2 ),
|
pos.y + (size.y - currentPenWidth) / 2 ),
|
||||||
NO_FILL );
|
NO_FILL, GetCurrentLineWidth() );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -800,24 +797,6 @@ void GERBER_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aS
|
||||||
EDA_DRAW_MODE_T aTraceMode, void* aData )
|
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;
|
GBR_METADATA gbr_metadata;
|
||||||
|
|
||||||
if( aData )
|
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
|
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
|
// Now, flash a pad anchor, if a netlist attribute is set
|
||||||
// (remove me when a Aperture macro will be used)
|
// (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
|
// However, because the anchor pad can be circle or rect, we use only
|
||||||
// a circle not bigger than the rect.
|
// a circle not bigger than the rect.
|
||||||
// the main purpose is to print a flashed DCode as pad anchor
|
// the main purpose is to print a flashed DCode as pad anchor
|
||||||
|
if( aTraceMode == FILLED )
|
||||||
FlashPadCircle( aPadPos, std::min( aSize.x, aSize.y ), aTraceMode, aData );
|
FlashPadCircle( aPadPos, std::min( aSize.x, aSize.y ), aTraceMode, aData );
|
||||||
|
|
||||||
GBR_METADATA gbr_metadata;
|
GBR_METADATA gbr_metadata;
|
||||||
|
|
||||||
if( aData )
|
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
|
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;
|
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();
|
cornerList.clear();
|
||||||
|
|
||||||
for( int ii = 0; ii < poly.PointCount(); ++ii )
|
for( int ii = 0; ii < poly.PointCount(); ++ii )
|
||||||
|
@ -882,7 +897,9 @@ void GERBER_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize
|
||||||
// Close polygon
|
// Close polygon
|
||||||
cornerList.push_back( cornerList[0] );
|
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
|
// Now, flash a pad anchor, if a netlist attribute is set
|
||||||
// (remove me when a Aperture macro will be used)
|
// (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
|
// Calculate the radius of the circle inside the shape
|
||||||
// It is the smaller dist from shape pos to edges
|
// 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 );
|
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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ set( GERBVIEW_SRCS
|
||||||
gerbview_config.cpp
|
gerbview_config.cpp
|
||||||
gerbview_frame.cpp
|
gerbview_frame.cpp
|
||||||
hotkeys.cpp
|
hotkeys.cpp
|
||||||
|
json11.cpp
|
||||||
job_file_reader.cpp
|
job_file_reader.cpp
|
||||||
locate.cpp
|
locate.cpp
|
||||||
menubar.cpp
|
menubar.cpp
|
||||||
|
|
|
@ -41,19 +41,39 @@
|
||||||
#include <html_messagebox.h>
|
#include <html_messagebox.h>
|
||||||
#include <view/view.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
|
* this class read and parse a Gerber job file to extract useful info
|
||||||
* for GerbView
|
* 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)
|
* %TF. (usual Gerber X2 info)
|
||||||
* %TJ.B. (board info)
|
* %TJ.B. (board info)
|
||||||
* %TJ.D. (design info)
|
* %TJ.D. (design info)
|
||||||
* %TJ.L. (layers info)
|
* %TJ.L. (layers info)
|
||||||
* some others are not yet handled by Kicad
|
* some others are not yet handled by Kicad
|
||||||
* M02* is the last line
|
* 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
|
class GERBER_JOBFILE_READER
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -96,9 +116,45 @@ bool GERBER_JOBFILE_READER::ReadGerberJobFile()
|
||||||
wxString msg;
|
wxString msg;
|
||||||
wxString data;
|
wxString data;
|
||||||
|
|
||||||
|
// 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 )
|
||||||
|
{
|
||||||
|
while( ( line = jobfileReader.ReadLine() ) )
|
||||||
|
data << '\n' << line;
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
json11::Json json_parser = json11::Json::parse( TO_UTF8( data ), err );
|
||||||
|
|
||||||
|
if( !err.empty() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for( auto& entry : json_parser["FilesAttributes"].array_items() )
|
||||||
|
{
|
||||||
|
//wxLogMessage( entry.dump().c_str() );
|
||||||
|
std::string name = entry["Path"].string_value();
|
||||||
|
//wxLogMessage( name.c_str() );
|
||||||
|
m_GerberFiles.Add( FormatStringFromGerber( name ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jobfileReader.Rewind();
|
||||||
|
|
||||||
while( true )
|
while( true )
|
||||||
{
|
{
|
||||||
char* line = jobfileReader.ReadLine();
|
line = jobfileReader.ReadLine();
|
||||||
|
|
||||||
if( !line ) // end of file
|
if( !line ) // end of file
|
||||||
break;
|
break;
|
||||||
|
@ -119,6 +175,7 @@ bool GERBER_JOBFILE_READER::ReadGerberJobFile()
|
||||||
if( text.StartsWith( "M02" ) ) // End of file
|
if( text.StartsWith( "M02" ) ) // End of file
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -1,8 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
* 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) 2018 Jean_Pierre Charras <jp.charras at wanadoo.fr>
|
||||||
* Copyright (C) 1992-2017 KiCad Developers, see AUTHORS.txt for contributors.
|
* Copyright (C) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file gendrill_gerber_writer.cpp
|
* @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>
|
#include <fctsys.h>
|
||||||
|
@ -52,6 +52,8 @@ 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_useJSONformat = true;
|
||||||
|
m_indent = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ONSIDE GERBER_JOBFILE_WRITER::hasSilkLayers()
|
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 );
|
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
|
// Note: in Gerber job file, dimensions are in mm, and are floating numbers
|
||||||
FILE* jobFile = wxFopen( aFullFilename, "wt" );
|
FILE* jobFile = wxFopen( aFullFilename, "wt" );
|
||||||
|
@ -124,14 +154,7 @@ bool GERBER_JOBFILE_WRITER::CreateJobFile( const wxString& aFullFilename )
|
||||||
wxString msg;
|
wxString msg;
|
||||||
|
|
||||||
if( jobFile == nullptr )
|
if( jobFile == nullptr )
|
||||||
{
|
|
||||||
if( m_reporter )
|
|
||||||
{
|
|
||||||
msg.Printf( _( "Unable to create job file \"%s\"" ), aFullFilename );
|
|
||||||
m_reporter->Report( msg, REPORTER::RPT_ERROR );
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
LOCALE_IO dummy;
|
LOCALE_IO dummy;
|
||||||
|
|
||||||
|
@ -378,11 +401,581 @@ bool GERBER_JOBFILE_WRITER::CreateJobFile( const wxString& aFullFilename )
|
||||||
|
|
||||||
fclose( jobFile );
|
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_JSONbuffer.RemoveLast();
|
||||||
m_reporter->Report( msg, REPORTER::RPT_ACTION );
|
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;
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/**
|
/**
|
||||||
* @file gerber_jobfile_writer.h
|
* @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.
|
* 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-2018 Jean_Pierre Charras <jp.charras at wanadoo.fr>
|
||||||
* Copyright (C) 1992-2017 KiCad Developers, see change_log.txt for contributors.
|
* Copyright (C) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* 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 aFullFilename = the full filename
|
||||||
* @param aParams = true for a NPTH file, false for a PTH file
|
* @param aParams = true for a NPTH file, false for a PTH file
|
||||||
* @return true, or false if the file cannot be created
|
* @return true, or false if the file cannot be created
|
||||||
*/
|
*/
|
||||||
bool CreateJobFile( const wxString& aFullFilename );
|
bool CreateJSONJobFile( const wxString& aFullFilename );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** @return SIDE_NONE if no silk screen layer is in list
|
/** @return SIDE_NONE if no silk screen layer is in list
|
||||||
|
@ -114,12 +128,94 @@ private:
|
||||||
*/
|
*/
|
||||||
const char* sideKeyValue( enum ONSIDE aValue );
|
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:
|
private:
|
||||||
BOARD* m_pcb; // The board
|
BOARD* m_pcb; // The board
|
||||||
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)
|
||||||
|
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
|
#endif // #ifndef GERBER_JOBFILE_WRITER_H
|
||||||
|
|
Loading…
Reference in New Issue