diff --git a/kicad b/kicad index acaf472..7f123c0 160000 --- a/kicad +++ b/kicad @@ -1 +1 @@ -Subproject commit acaf47234afe2e93400b23a93b6401b28d9932db +Subproject commit 7f123c0fa6cbae79d7ec803667d01d043dd44f14 diff --git a/wasm/cli/kicad_tools_main.cpp b/wasm/cli/kicad_tools_main.cpp index cb5b144..fa65144 100644 --- a/wasm/cli/kicad_tools_main.cpp +++ b/wasm/cli/kicad_tools_main.cpp @@ -7,6 +7,7 @@ * * pcbnew side (pcb_convert_main.cpp): * kicad_tools --drc [--json] [--strict] [] + * kicad_tools --gerbers [] * * eeschema side (sym_convert_main.cpp) — everything else: * kicad_tools --convert-lib @@ -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 diff --git a/wasm/cli/pcb_convert_main.cpp b/wasm/cli/pcb_convert_main.cpp index 1003aa1..08fa8da 100644 --- a/wasm/cli/pcb_convert_main.cpp +++ b/wasm/cli/pcb_convert_main.cpp @@ -16,6 +16,12 @@ * Exit: 0 clean, 1 violations (--strict: also warnings + * unconnected), 2 usage, 4 load/run failure. * + * gerbers: pcb_convert --gerbers [] + * One .gbr per enabled layer (stackup plot order) + the .gbrjob + * file, kicad-cli CLI defaults (no board-plot-params), into + * (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 #include #include +#include +#include #include #include #include #include #include #include +#include +#include #include +#include #include #include #include #include +#include #include #include @@ -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] []\n" ); + if( argc >= 2 && std::strcmp( argv[1], "--gerbers" ) == 0 ) + { + wxDisableAsserts(); + + if( argc < 3 ) + { + std::fprintf( stderr, "usage: kicad_tools --gerbers []\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] []\n" + " kicad_tools --gerbers []\n" ); return 2; } diff --git a/wasm/cli/sym_convert_main.cpp b/wasm/cli/sym_convert_main.cpp index 995e6c0..4e0164a 100644 --- a/wasm/cli/sym_convert_main.cpp +++ b/wasm/cli/sym_convert_main.cpp @@ -1113,7 +1113,8 @@ int symConvertMain( int argc, char** argv ) " kicad_tools --netlist [--xml] []\n" " kicad_tools --bom []\n" " kicad_tools --plot [--pdf] []\n" - " kicad_tools --drc [--json] [--strict] []\n" ); + " kicad_tools --drc [--json] [--strict] []\n" + " kicad_tools --gerbers []\n" ); return 2; }