feat: kicad_tools --gerbers

--gerbers <pcb> [outdir]: one .gbr per enabled layer (stackup plot order,
kicad-cli defaults via default JOB_EXPORT_PCB_GERBERS + PlotJobToPlotOpts)
plus the .gbrjob file — mirrors JobExportGerbers minus the zone re-check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADzCSeN3Q9DJL3YW2FZTXB
This commit is contained in:
Gergő Törcsvári 2026-07-10 17:45:19 +02:00
commit 19014ae113
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
4 changed files with 132 additions and 4 deletions

2
kicad

@ -1 +1 @@
Subproject commit acaf47234afe2e93400b23a93b6401b28d9932db
Subproject commit 7f123c0fa6cbae79d7ec803667d01d043dd44f14

View file

@ -7,6 +7,7 @@
*
* pcbnew side (pcb_convert_main.cpp):
* kicad_tools --drc [--json] [--strict] <file.kicad_pcb> [<out>]
* kicad_tools --gerbers <file.kicad_pcb> [<outdir>]
*
* eeschema side (sym_convert_main.cpp) everything else:
* kicad_tools --convert-lib <input.lib> <output.kicad_sym>
@ -35,10 +36,15 @@ int main( int argc, char** argv )
{
int rc;
if( argc >= 2 && std::strcmp( argv[1], "--drc" ) == 0 )
if( argc >= 2
&& ( std::strcmp( argv[1], "--drc" ) == 0 || std::strcmp( argv[1], "--gerbers" ) == 0 ) )
{
rc = pcbConvertMain( argc, argv );
}
else
{
rc = symConvertMain( argc, argv );
}
// Skip the EXIT_RUNTIME static-dtor pass (same rationale as
// pcb_convert_main.cpp, which already does this on its --drc path): with

View file

@ -16,6 +16,12 @@
* Exit: 0 clean, 1 violations (--strict: also warnings +
* unconnected), 2 usage, 4 load/run failure.
*
* gerbers: pcb_convert --gerbers <file.kicad_pcb> [<outdir>]
* One .gbr per enabled layer (stackup plot order) + the .gbrjob
* file, kicad-cli CLI defaults (no board-plot-params), into
* <outdir> (default: the input's directory). Zone fills plot as
* saved (no re-check). Exit: 0 ok, 2 usage, 4 failure.
*
* No GUI, no renderer, no embind bindings. Wired into pcbnew/CMakeLists.txt
* behind the KICAD_PCB_CONVERTER_WASM option (see
* scripts/kicad/build-pcb_convert.sh).
@ -43,17 +49,23 @@
#include <drc/drc_engine.h>
#include <drc/drc_item.h>
#include <drc/drc_report.h>
#include <exporters/gerber_jobfile_writer.h>
#include <jobs/job_export_pcb_gerbers.h>
#include <ki_exception.h>
#include <layer_ids.h>
#include <lset.h>
#include <libraries/library_manager.h>
#include <pcb_io/pcb_io_mgr.h>
#include <pcb_marker.h>
#include <pcb_plotter.h>
#include <pcbplot.h>
#include <pgm_base.h>
#include <plotters/plotter_gerber.h>
#include <project.h>
#include <project/project_file.h>
#include <properties/property.h>
#include <properties/property_mgr.h>
#include <reporter.h>
#include <settings/settings_manager.h>
#include <widgets/report_severity.h>
@ -348,6 +360,88 @@ int runDrc( const char* aInPath, bool aJson, bool aStrict, const char* aOutPath
return failed ? 1 : 0;
}
// ── headless gerber export ────────────────────────────────────────────────────
// Mirrors PCBNEW_JOBS_HANDLER::JobExportGerbers with the headless deltas: no
// zone re-check (tool framework pruned; fills plot as saved), no progress
// reporter, kicad-cli CLI defaults via a default-constructed
// JOB_EXPORT_PCB_GERBERS (board's enabled layers, .gbrjob file on, no protel
// extensions unless the board plot params say so — we DON'T use board plot
// params, matching kicad-cli's default).
int runGerbers( const char* aInPath, const char* aOutDir )
{
wxFileName fn( wxString::FromUTF8( aInPath ) );
fn.MakeAbsolute();
BOARD* brd = loadBoardHeadless( aInPath );
if( !brd )
return 4;
wxString outPath = aOutDir ? wxString::FromUTF8( aOutDir ) : fn.GetPath();
if( !outPath.EndsWith( wxS( "/" ) ) )
outPath += wxS( "/" );
REPORTER& reporter = CLI_REPORTER::GetInstance();
// kicad-cli defaults (m_useBoardPlotParams=false, m_createJobsFile=true);
// layer selection = the board's enabled layers, stackup plot order.
JOB_EXPORT_PCB_GERBERS gerberJob;
PCB_PLOT_PARAMS plotOpts;
PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, &gerberJob, reporter );
GERBER_JOBFILE_WRITER jobfileWriter( brd );
LSEQ layersToPlot = brd->GetEnabledLayers().SeqStackupForPlotting();
int plotted = 0;
bool failed = false;
for( PCB_LAYER_ID layer : layersToPlot )
{
LSEQ plotSequence;
plotSequence.push_back( layer );
wxFileName gbrFn( brd->GetFileName() );
wxString layerName = brd->GetLayerName( layer );
wxString fileExt = plotOpts.GetUseGerberProtelExtensions()
? GetGerberProtelExtension( layer )
: wxString( wxS( "gbr" ) );
BuildPlotFileName( &gbrFn, outPath, layerName, fileExt );
wxString gbrName = gbrFn.GetFullName(); // AddGbrFile wants a non-const ref
jobfileWriter.AddGbrFile( layer, gbrName );
trace( "runGerbers: StartPlotBoard" );
GERBER_PLOTTER* plotter = (GERBER_PLOTTER*) StartPlotBoard(
brd, &plotOpts, layer, layerName, gbrFn.GetFullPath(), wxEmptyString,
wxEmptyString );
if( plotter )
{
PlotBoardLayers( brd, plotter, plotSequence, plotOpts );
plotter->EndPlot();
plotted++;
}
else
{
std::fprintf( stderr, "%s: error: failed to plot %s\n", aInPath,
(const char*) gbrFn.GetFullPath().ToUTF8() );
failed = true;
}
delete plotter;
}
wxFileName jobFn( brd->GetFileName() );
BuildPlotFileName( &jobFn, outPath, wxS( "job" ), wxS( "gbrjob" ) );
jobfileWriter.CreateJobFile( jobFn.GetFullPath() );
std::fprintf( stderr, "%s: %s (%d layers + job file) -> %s\n", aInPath,
failed ? "FAIL" : "OK", plotted, (const char*) outPath.ToUTF8() );
return failed ? 4 : 0;
}
} // namespace
@ -468,7 +562,34 @@ int pcbConvertMain( int argc, char** argv )
_exit( rc );
}
std::fprintf( stderr, "usage: pcb_convert --drc [--json] [--strict] <file.kicad_pcb> [<out>]\n" );
if( argc >= 2 && std::strcmp( argv[1], "--gerbers" ) == 0 )
{
wxDisableAsserts();
if( argc < 3 )
{
std::fprintf( stderr, "usage: kicad_tools --gerbers <file.kicad_pcb> [<outdir>]\n" );
return 2;
}
int rc = 4;
try
{
rc = runGerbers( argv[2], argc >= 4 ? argv[3] : nullptr );
}
catch( const std::exception& e )
{
std::fprintf( stderr, "%s: error: %s\n", argv[2], e.what() );
}
// Same static-dtor rationale as --drc above.
std::fflush( nullptr );
_exit( rc );
}
std::fprintf( stderr, "usage: kicad_tools --drc [--json] [--strict] <file.kicad_pcb> [<out>]\n"
" kicad_tools --gerbers <file.kicad_pcb> [<outdir>]\n" );
return 2;
}

View file

@ -1113,7 +1113,8 @@ int symConvertMain( int argc, char** argv )
" kicad_tools --netlist [--xml] <file.kicad_sch> [<out>]\n"
" kicad_tools --bom <file.kicad_sch> [<out>]\n"
" kicad_tools --plot [--pdf] <file.kicad_sch> [<out>]\n"
" kicad_tools --drc [--json] [--strict] <file.kicad_pcb> [<out>]\n" );
" kicad_tools --drc [--json] [--strict] <file.kicad_pcb> [<out>]\n"
" kicad_tools --gerbers <file.kicad_pcb> [<outdir>]\n" );
return 2;
}