plugins 0002: kicad_tools --ipc356 + --fab-components fab exporters
Two generic (manufacturer-agnostic) pcbnew-side subcommands for platform
manufacturing plugins: --ipc356 (IPC-D-356 netlist via IPC356D_WRITER) and
--fab-components (board metrics + per-footprint placement/BOM JSON —
absolute board coords, consumers apply their own conventions). Bumps the
kicad submodule for the diet keep-back of export_d356.cpp.
Also closes a CI cache gap: wasm-cache-hash never hashed wasm/**, so a
pure wasm/cli change would cache-hit stale output — { dir: "wasm" } is
now an input (busts the cache once on landing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mr8PQ34yCfvEtEcwSUvLAV
This commit is contained in:
parent
cb8959331d
commit
f1ea6b3965
4 changed files with 229 additions and 3 deletions
2
kicad
2
kicad
|
|
@ -1 +1 @@
|
||||||
Subproject commit d06f653f0edc18b1f7cc6e52d0bb3be426a5cefa
|
Subproject commit ca1dde0e5ffdd9fa32062d816c59a301d1d0fc6c
|
||||||
|
|
@ -59,6 +59,13 @@ const INPUTS = [
|
||||||
// Per-tool compile recipes (compile flags / emcc link options).
|
// Per-tool compile recipes (compile flags / emcc link options).
|
||||||
{ dir: "scripts/kicad", match: /^build-.*\.sh$/ },
|
{ dir: "scripts/kicad", match: /^build-.*\.sh$/ },
|
||||||
|
|
||||||
|
// The pcbjam-repo C++/CMake/shim sources compiled INTO the tools (bindings,
|
||||||
|
// cli mains, kiplatform, gl1, stubs, editor glue). These live outside the
|
||||||
|
// kicad/wx submodules, so without this entry a pure wasm/** edit (e.g. a
|
||||||
|
// new kicad_tools subcommand) would cache-hit stale output — plugins 0002
|
||||||
|
// shipped exactly that way before this entry existed.
|
||||||
|
{ dir: "wasm" },
|
||||||
|
|
||||||
// Dependency builds (boost/cairo/occ/... — the sysroot the wasm links against).
|
// Dependency builds (boost/cairo/occ/... — the sysroot the wasm links against).
|
||||||
{ dir: "scripts/deps" },
|
{ dir: "scripts/deps" },
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
* kicad_tools --gerbers <file.kicad_pcb> [<outdir>]
|
* kicad_tools --gerbers <file.kicad_pcb> [<outdir>]
|
||||||
* kicad_tools --drill <file.kicad_pcb> [<outdir>]
|
* kicad_tools --drill <file.kicad_pcb> [<outdir>]
|
||||||
* kicad_tools --plot-board [--pdf] [--layers <a,b,...>] <file.kicad_pcb> [<out>]
|
* kicad_tools --plot-board [--pdf] [--layers <a,b,...>] <file.kicad_pcb> [<out>]
|
||||||
|
* kicad_tools --ipc356 <file.kicad_pcb> [<out.ipc>]
|
||||||
|
* kicad_tools --fab-components <file.kicad_pcb> [<out.json>]
|
||||||
*
|
*
|
||||||
* eeschema side (sym_convert_main.cpp) — everything else:
|
* eeschema side (sym_convert_main.cpp) — everything else:
|
||||||
* kicad_tools --convert-lib <input.lib> <output.kicad_sym>
|
* kicad_tools --convert-lib <input.lib> <output.kicad_sym>
|
||||||
|
|
@ -47,7 +49,9 @@ int main( int argc, char** argv )
|
||||||
if( argc >= 2
|
if( argc >= 2
|
||||||
&& ( std::strcmp( argv[1], "--drc" ) == 0 || std::strcmp( argv[1], "--gerbers" ) == 0
|
&& ( std::strcmp( argv[1], "--drc" ) == 0 || std::strcmp( argv[1], "--gerbers" ) == 0
|
||||||
|| std::strcmp( argv[1], "--drill" ) == 0
|
|| std::strcmp( argv[1], "--drill" ) == 0
|
||||||
|| std::strcmp( argv[1], "--plot-board" ) == 0 ) )
|
|| std::strcmp( argv[1], "--plot-board" ) == 0
|
||||||
|
|| std::strcmp( argv[1], "--ipc356" ) == 0
|
||||||
|
|| std::strcmp( argv[1], "--fab-components" ) == 0 ) )
|
||||||
{
|
{
|
||||||
rc = pcbConvertMain( argc, argv );
|
rc = pcbConvertMain( argc, argv );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,20 @@
|
||||||
* default to the board's enabled set, or a comma-separated list
|
* default to the board's enabled set, or a comma-separated list
|
||||||
* of canonical/user layer names. Exit: 0 ok, 2 usage, 4 failure.
|
* of canonical/user layer names. Exit: 0 ok, 2 usage, 4 failure.
|
||||||
*
|
*
|
||||||
|
* ipc356: pcb_convert --ipc356 <file.kicad_pcb> [<out.ipc>]
|
||||||
|
* IPC-D-356 netlist via IPC356D_WRITER, default settings (out
|
||||||
|
* defaults to <file>-netlist.ipc). Exit: 0 ok, 2 usage, 4 load
|
||||||
|
* failure, 5 write failure.
|
||||||
|
*
|
||||||
|
* fab: pcb_convert --fab-components <file.kicad_pcb> [<out.json>]
|
||||||
|
* Manufacturer-agnostic fabrication data JSON (plugins 0002):
|
||||||
|
* board metrics (Edge.Cuts bbox mm, aux origin, copper layer
|
||||||
|
* count, thickness) + one entry per footprint (ref, fpid, side,
|
||||||
|
* absolute position mm in board coords — no Y flip, no
|
||||||
|
* aux-origin subtraction; consumers derive their own — rotation
|
||||||
|
* deg, smd/exclude/dnp attributes, all non-empty fields).
|
||||||
|
* Exit: 0 ok, 2 usage, 4 load failure, 5 write failure.
|
||||||
|
*
|
||||||
* No GUI, no renderer, no embind bindings. Wired into pcbnew/CMakeLists.txt
|
* No GUI, no renderer, no embind bindings. Wired into pcbnew/CMakeLists.txt
|
||||||
* behind the KICAD_PCB_CONVERTER_WASM option (see
|
* behind the KICAD_PCB_CONVERTER_WASM option (see
|
||||||
* scripts/kicad/build-pcb_convert.sh).
|
* scripts/kicad/build-pcb_convert.sh).
|
||||||
|
|
@ -43,6 +57,7 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
@ -54,13 +69,16 @@
|
||||||
#include <wx/string.h>
|
#include <wx/string.h>
|
||||||
|
|
||||||
#include <base_screen.h>
|
#include <base_screen.h>
|
||||||
|
#include <base_units.h>
|
||||||
#include <board.h>
|
#include <board.h>
|
||||||
#include <board_design_settings.h>
|
#include <board_design_settings.h>
|
||||||
#include <drc/drc_engine.h>
|
#include <drc/drc_engine.h>
|
||||||
#include <drc/drc_item.h>
|
#include <drc/drc_item.h>
|
||||||
#include <drc/drc_report.h>
|
#include <drc/drc_report.h>
|
||||||
|
#include <exporters/export_d356.h>
|
||||||
#include <exporters/gendrill_excellon_writer.h>
|
#include <exporters/gendrill_excellon_writer.h>
|
||||||
#include <exporters/gerber_jobfile_writer.h>
|
#include <exporters/gerber_jobfile_writer.h>
|
||||||
|
#include <json_common.h>
|
||||||
#include <jobs/job_export_pcb_drill.h>
|
#include <jobs/job_export_pcb_drill.h>
|
||||||
#include <jobs/job_export_pcb_gerbers.h>
|
#include <jobs/job_export_pcb_gerbers.h>
|
||||||
#include <jobs/job_export_pcb_pdf.h>
|
#include <jobs/job_export_pcb_pdf.h>
|
||||||
|
|
@ -70,6 +88,7 @@
|
||||||
#include <lset.h>
|
#include <lset.h>
|
||||||
#include <libraries/library_manager.h>
|
#include <libraries/library_manager.h>
|
||||||
#include <footprint.h>
|
#include <footprint.h>
|
||||||
|
#include <pcb_field.h>
|
||||||
#include <pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h>
|
#include <pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h>
|
||||||
#include <pcb_io/pcb_io_mgr.h>
|
#include <pcb_io/pcb_io_mgr.h>
|
||||||
#include <pcb_marker.h>
|
#include <pcb_marker.h>
|
||||||
|
|
@ -636,6 +655,147 @@ int runDrill( const char* aInPath, const char* aOutDir )
|
||||||
return ok ? 0 : 4;
|
return ok ? 0 : 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── IPC-D-356 netlist export ──────────────────────────────────────────────────
|
||||||
|
// Same IPC356D_WRITER call the pcbnew GUI export makes, default settings; net
|
||||||
|
// assignments come from the saved board (no connectivity rebuild needed).
|
||||||
|
|
||||||
|
int runIpc356( const char* aInPath, const char* aOutPath )
|
||||||
|
{
|
||||||
|
wxFileName fn( wxString::FromUTF8( aInPath ) );
|
||||||
|
fn.MakeAbsolute();
|
||||||
|
|
||||||
|
BOARD* brd = loadBoardHeadless( aInPath );
|
||||||
|
|
||||||
|
if( !brd )
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
wxString outPath;
|
||||||
|
|
||||||
|
if( aOutPath )
|
||||||
|
{
|
||||||
|
outPath = wxString::FromUTF8( aOutPath );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wxFileName out( fn );
|
||||||
|
out.SetName( out.GetName() + wxS( "-netlist" ) );
|
||||||
|
out.SetExt( wxS( "ipc" ) );
|
||||||
|
outPath = out.GetFullPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
trace( "runIpc356: IPC356D_WRITER::Write" );
|
||||||
|
IPC356D_WRITER writer( brd );
|
||||||
|
const bool ok = writer.Write( outPath );
|
||||||
|
|
||||||
|
std::fprintf( stderr, "%s: %s (ipc-356d) -> %s\n", aInPath, ok ? "OK" : "FAIL",
|
||||||
|
(const char*) outPath.ToUTF8() );
|
||||||
|
|
||||||
|
return ok ? 0 : 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── fabrication components JSON ───────────────────────────────────────────────
|
||||||
|
// Manufacturer-agnostic per-footprint placement/BOM data + board metrics
|
||||||
|
// (plugins 0002). Coordinates are absolute board coords in mm, as-is: no Y
|
||||||
|
// flip, no aux-origin subtraction — manufacturer-specific conventions (e.g.
|
||||||
|
// PCBWay's aux-relative negated-Y positions) are applied by the consumer.
|
||||||
|
|
||||||
|
int runFabComponents( const char* aInPath, const char* aOutPath )
|
||||||
|
{
|
||||||
|
wxFileName fn( wxString::FromUTF8( aInPath ) );
|
||||||
|
fn.MakeAbsolute();
|
||||||
|
|
||||||
|
BOARD* brd = loadBoardHeadless( aInPath );
|
||||||
|
|
||||||
|
if( !brd )
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
wxString outPath;
|
||||||
|
|
||||||
|
if( aOutPath )
|
||||||
|
{
|
||||||
|
outPath = wxString::FromUTF8( aOutPath );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wxFileName out( fn );
|
||||||
|
out.SetName( out.GetName() + wxS( "-fab-components" ) );
|
||||||
|
out.SetExt( wxS( "json" ) );
|
||||||
|
outPath = out.GetFullPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
trace( "runFabComponents: collecting" );
|
||||||
|
|
||||||
|
const BOX2I bbox = brd->GetBoardEdgesBoundingBox();
|
||||||
|
const VECTOR2I aux = brd->GetDesignSettings().GetAuxOrigin();
|
||||||
|
|
||||||
|
nlohmann::json board;
|
||||||
|
board["widthMm"] = pcbIUScale.IUTomm( bbox.GetWidth() );
|
||||||
|
board["heightMm"] = pcbIUScale.IUTomm( bbox.GetHeight() );
|
||||||
|
board["bboxOriginMm"] = { { "x", pcbIUScale.IUTomm( bbox.GetX() ) },
|
||||||
|
{ "y", pcbIUScale.IUTomm( bbox.GetY() ) } };
|
||||||
|
board["auxOriginMm"] = { { "x", pcbIUScale.IUTomm( aux.x ) },
|
||||||
|
{ "y", pcbIUScale.IUTomm( aux.y ) } };
|
||||||
|
board["copperLayers"] = brd->GetCopperLayerCount();
|
||||||
|
board["thicknessMm"] = pcbIUScale.IUTomm( brd->GetDesignSettings().GetBoardThickness() );
|
||||||
|
|
||||||
|
nlohmann::json footprints = nlohmann::json::array();
|
||||||
|
|
||||||
|
for( FOOTPRINT* fp : brd->Footprints() )
|
||||||
|
{
|
||||||
|
const VECTOR2I pos = fp->GetPosition();
|
||||||
|
const int attrs = fp->GetAttributes();
|
||||||
|
|
||||||
|
nlohmann::json f;
|
||||||
|
f["ref"] = std::string( fp->GetReference().ToUTF8() );
|
||||||
|
f["fpid"] = std::string( fp->GetFPID().GetUniStringLibId().ToUTF8() );
|
||||||
|
f["side"] = fp->GetLayer() == B_Cu ? "bottom" : "top";
|
||||||
|
f["posMm"] = { { "x", pcbIUScale.IUTomm( pos.x ) },
|
||||||
|
{ "y", pcbIUScale.IUTomm( pos.y ) } };
|
||||||
|
f["rotationDeg"] = fp->GetOrientationDegrees();
|
||||||
|
f["smd"] = bool( attrs & FP_SMD );
|
||||||
|
f["excludeFromPos"] = fp->IsExcludedFromPosFiles();
|
||||||
|
f["excludeFromBom"] = fp->IsExcludedFromBOM();
|
||||||
|
f["dnp"] = fp->IsDNP();
|
||||||
|
|
||||||
|
nlohmann::json fields = nlohmann::json::object();
|
||||||
|
|
||||||
|
std::vector<PCB_FIELD*> fieldList;
|
||||||
|
fp->GetFields( fieldList, false );
|
||||||
|
|
||||||
|
for( PCB_FIELD* field : fieldList )
|
||||||
|
{
|
||||||
|
const wxString text = field->GetShownText( false );
|
||||||
|
|
||||||
|
if( !text.IsEmpty() )
|
||||||
|
fields[std::string( field->GetName().ToUTF8() )] = std::string( text.ToUTF8() );
|
||||||
|
}
|
||||||
|
|
||||||
|
f["fields"] = fields;
|
||||||
|
footprints.push_back( f );
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json j;
|
||||||
|
j["board"] = board;
|
||||||
|
j["footprints"] = footprints;
|
||||||
|
|
||||||
|
trace( "runFabComponents: writing" );
|
||||||
|
std::ofstream out( outPath.fn_str() );
|
||||||
|
out << j.dump( 2 ) << "\n";
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
if( !out )
|
||||||
|
{
|
||||||
|
std::fprintf( stderr, "%s: error: unable to write %s\n", aInPath,
|
||||||
|
(const char*) outPath.ToUTF8() );
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fprintf( stderr, "%s: OK (%d footprints) -> %s\n", aInPath,
|
||||||
|
(int) brd->Footprints().size(), (const char*) outPath.ToUTF8() );
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1035,10 +1195,65 @@ int pcbConvertMain( int argc, char** argv )
|
||||||
_exit( rc );
|
_exit( rc );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( argc >= 2 && std::strcmp( argv[1], "--ipc356" ) == 0 )
|
||||||
|
{
|
||||||
|
wxDisableAsserts();
|
||||||
|
|
||||||
|
if( argc < 3 )
|
||||||
|
{
|
||||||
|
std::fprintf( stderr, "usage: kicad_tools --ipc356 <file.kicad_pcb> [<out.ipc>]\n" );
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = 4;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rc = runIpc356( 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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( argc >= 2 && std::strcmp( argv[1], "--fab-components" ) == 0 )
|
||||||
|
{
|
||||||
|
wxDisableAsserts();
|
||||||
|
|
||||||
|
if( argc < 3 )
|
||||||
|
{
|
||||||
|
std::fprintf( stderr,
|
||||||
|
"usage: kicad_tools --fab-components <file.kicad_pcb> [<out.json>]\n" );
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = 4;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rc = runFabComponents( 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"
|
std::fprintf( stderr, "usage: kicad_tools --drc [--json] [--strict] <file.kicad_pcb> [<out>]\n"
|
||||||
" kicad_tools --gerbers <file.kicad_pcb> [<outdir>]\n"
|
" kicad_tools --gerbers <file.kicad_pcb> [<outdir>]\n"
|
||||||
" kicad_tools --drill <file.kicad_pcb> [<outdir>]\n"
|
" kicad_tools --drill <file.kicad_pcb> [<outdir>]\n"
|
||||||
" kicad_tools --plot-board [--pdf] [--layers <a,b,...>] <file.kicad_pcb> [<out>]\n" );
|
" kicad_tools --plot-board [--pdf] [--layers <a,b,...>] <file.kicad_pcb> [<out>]\n"
|
||||||
|
" kicad_tools --ipc356 <file.kicad_pcb> [<out.ipc>]\n"
|
||||||
|
" kicad_tools --fab-components <file.kicad_pcb> [<out.json>]\n" );
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue