From 1cf4284930cbd8a07e3a36ba75b49bacbec2cbf7 Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Fri, 6 Mar 2026 09:13:27 -0800 Subject: [PATCH] IPC-2581: use schematic revision for BOM The BOM revision in IPC-2581 export was hardcoded to "1.0" instead of reflecting the actual design revision. The project file stores two separate fields: bomRev for explicit user overrides set in the PCBnew export dialog or via --bom-rev on the CLI, and schRevision for the value auto-propagated from eeschema. --- common/jobs/job_export_pcb_ipc2581.cpp | 4 +- common/jobs/job_export_pcb_ipc2581.h | 1 + common/project/project_file.cpp | 6 + eeschema/eeschema_config.cpp | 7 ++ include/project/board_project_settings.h | 4 +- kicad/cli/command_pcb_export_ipc2581.cpp | 9 ++ pcbnew/dialogs/dialog_export_2581.cpp | 14 +++ pcbnew/dialogs/dialog_export_2581.fbp | 129 +++++++++++++++++++++ pcbnew/dialogs/dialog_export_2581_base.cpp | 11 ++ pcbnew/dialogs/dialog_export_2581_base.h | 2 + pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp | 13 ++- pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.h | 1 + pcbnew/pcbnew_jobs_handler.cpp | 14 +++ 13 files changed, 212 insertions(+), 3 deletions(-) diff --git a/common/jobs/job_export_pcb_ipc2581.cpp b/common/jobs/job_export_pcb_ipc2581.cpp index 294ba4ae99..ac7c16d99a 100644 --- a/common/jobs/job_export_pcb_ipc2581.cpp +++ b/common/jobs/job_export_pcb_ipc2581.cpp @@ -47,7 +47,8 @@ JOB_EXPORT_PCB_IPC2581::JOB_EXPORT_PCB_IPC2581() : m_colMfgPn(), m_colMfg(), m_colDistPn(), - m_colDist() + m_colDist(), + m_bomRev() { m_params.emplace_back( new JOB_PARAM( "drawing_sheet", &m_drawingSheet, m_drawingSheet ) ); m_params.emplace_back( new JOB_PARAM( "units", &m_units, m_units ) ); @@ -64,6 +65,7 @@ JOB_EXPORT_PCB_IPC2581::JOB_EXPORT_PCB_IPC2581() : &m_colDistPn, m_colDistPn ) ); m_params.emplace_back( new JOB_PARAM( "field_bom_map.dist", &m_colDist, m_colDist ) ); + m_params.emplace_back( new JOB_PARAM( "bom_rev", &m_bomRev, m_bomRev ) ); } diff --git a/common/jobs/job_export_pcb_ipc2581.h b/common/jobs/job_export_pcb_ipc2581.h index ebd9c6fc4f..59176d0d8c 100644 --- a/common/jobs/job_export_pcb_ipc2581.h +++ b/common/jobs/job_export_pcb_ipc2581.h @@ -61,6 +61,7 @@ public: wxString m_colMfg; wxString m_colDistPn; wxString m_colDist; + wxString m_bomRev; }; #endif diff --git a/common/project/project_file.cpp b/common/project/project_file.cpp index 1d27524d15..cb71095213 100644 --- a/common/project/project_file.cpp +++ b/common/project/project_file.cpp @@ -195,6 +195,12 @@ PROJECT_FILE::PROJECT_FILE( const wxString& aFullPath ) : m_params.emplace_back( new PARAM( "board.ipc2581.dist", &m_IP2581Bom.dist, wxEmptyString ) ); + m_params.emplace_back( new PARAM( "board.ipc2581.bom_rev", + &m_IP2581Bom.bomRev, wxEmptyString ) ); + + m_params.emplace_back( new PARAM( "board.ipc2581.sch_revision", + &m_IP2581Bom.schRevision, wxEmptyString ) ); + registerMigration( 1, 2, std::bind( &PROJECT_FILE::migrateSchema1To2, this ) ); registerMigration( 2, 3, std::bind( &PROJECT_FILE::migrateSchema2To3, this ) ); diff --git a/eeschema/eeschema_config.cpp b/eeschema/eeschema_config.cpp index 401864b12b..7ac5a5cd82 100644 --- a/eeschema/eeschema_config.cpp +++ b/eeschema/eeschema_config.cpp @@ -223,6 +223,13 @@ void SCH_EDIT_FRAME::saveProjectSettings() } } + // Propagate the root schematic revision to the project file for IPC-2581 BOM export + if( Schematic().RootScreen() ) + { + Prj().GetProjectFile().m_IP2581Bom.schRevision = + Schematic().RootScreen()->GetTitleBlock().GetRevision(); + } + // Update top-level sheets information in the project file const std::vector& topLevelSheets = Schematic().GetTopLevelSheets(); diff --git a/include/project/board_project_settings.h b/include/project/board_project_settings.h index dcf64072a6..552a7faf46 100644 --- a/include/project/board_project_settings.h +++ b/include/project/board_project_settings.h @@ -143,10 +143,12 @@ enum class RATSNEST_MODE struct KICOMMON_API IP2581_BOM { wxString mfg; ///< Manufacturer name column - wxString MPN; ///< Manufacturer part number column + wxString MPN; ///< Manufacturer part number column wxString dist; ///< Distributor name column wxString distPN; ///< Distributor part number column wxString id; ///< Internal ID column + wxString bomRev; ///< Explicit BOM revision override set by user + wxString schRevision; ///< Auto-propagated schematic title block revision }; /** diff --git a/kicad/cli/command_pcb_export_ipc2581.cpp b/kicad/cli/command_pcb_export_ipc2581.cpp index 885dfe7648..317f7e0484 100644 --- a/kicad/cli/command_pcb_export_ipc2581.cpp +++ b/kicad/cli/command_pcb_export_ipc2581.cpp @@ -37,6 +37,7 @@ #define ARG_BOM_COL_MFG "--bom-col-mfg" #define ARG_BOM_COL_DIST_PN "--bom-col-dist-pn" #define ARG_BOM_COL_DIST "--bom-col-dist" +#define ARG_BOM_REV "--bom-rev" #define ARG_UNITS "--units" CLI::PCB_EXPORT_IPC2581_COMMAND::PCB_EXPORT_IPC2581_COMMAND() : @@ -97,6 +98,12 @@ CLI::PCB_EXPORT_IPC2581_COMMAND::PCB_EXPORT_IPC2581_COMMAND() : "Material Distributor Column" ) ) .metavar( "FIELD_NAME" ); + m_argParser.add_argument( ARG_BOM_REV ) + .default_value( std::string() ) + .help( std::string( "BOM revision to use in the output file. " + "Defaults to schematic revision from the project file" ) ) + .metavar( "REVISION" ); + addVariantsArg(); } @@ -143,6 +150,8 @@ int CLI::PCB_EXPORT_IPC2581_COMMAND::doPerform( KIWAY& aKiway ) From_UTF8( m_argParser.get( ARG_BOM_COL_DIST_PN ).c_str() ); ipc2581Job->m_colDist = From_UTF8( m_argParser.get( ARG_BOM_COL_DIST ).c_str() ); + ipc2581Job->m_bomRev = + From_UTF8( m_argParser.get( ARG_BOM_REV ).c_str() ); LOCALE_IO dummy; return aKiway.ProcessJob( KIWAY::FACE_PCB, ipc2581Job.get() ); diff --git a/pcbnew/dialogs/dialog_export_2581.cpp b/pcbnew/dialogs/dialog_export_2581.cpp index 9d8f966129..308b0f45b6 100644 --- a/pcbnew/dialogs/dialog_export_2581.cpp +++ b/pcbnew/dialogs/dialog_export_2581.cpp @@ -272,6 +272,11 @@ void DIALOG_EXPORT_2581::onOKClick( wxCommandEvent& event ) props[ "dist" ] = TO_UTF8( GetDist() ); props[ "distpn" ] = TO_UTF8( GetDistPN() ); + wxString bomRev = m_textBomRev->GetValue(); + + if( !bomRev.IsEmpty() ) + props[ "bomrev" ] = TO_UTF8( bomRev ); + try { IO_RELEASER pi( PCB_IO_MGR::FindPlugin( PCB_IO_MGR::IPC2581 ) ); @@ -369,6 +374,12 @@ bool DIALOG_EXPORT_2581::TransferDataToWindow() distPnCol = prj.m_IP2581Bom.distPN; mfgCol = prj.m_IP2581Bom.mfg; distCol = prj.m_IP2581Bom.dist; + wxString bomRev = prj.m_IP2581Bom.bomRev; + + if( bomRev.IsEmpty() ) + bomRev = prj.m_IP2581Bom.schRevision; + + m_textBomRev->SetValue( bomRev ); } else { @@ -377,6 +388,7 @@ bool DIALOG_EXPORT_2581::TransferDataToWindow() distPnCol = m_job->m_colDistPn; mfgCol = m_job->m_colMfg; distCol = m_job->m_colDist; + m_textBomRev->SetValue( m_job->m_bomRev ); } if( !m_choiceMPN->SetStringSelection( internalIdCol ) ) @@ -433,6 +445,7 @@ bool DIALOG_EXPORT_2581::TransferDataFromWindow() prj.m_IP2581Bom.MPN = GetMPN(); prj.m_IP2581Bom.distPN = GetDistPN(); prj.m_IP2581Bom.dist = GetDist(); + prj.m_IP2581Bom.bomRev = m_textBomRev->GetValue(); } else { @@ -443,6 +456,7 @@ bool DIALOG_EXPORT_2581::TransferDataFromWindow() m_job->m_colDistPn = GetDistPN(); m_job->m_colMfg = GetMfg(); m_job->m_colMfgPn = GetMPN(); + m_job->m_bomRev = m_textBomRev->GetValue(); m_job->m_version = GetVersion() == 'B' ? JOB_EXPORT_PCB_IPC2581::IPC2581_VERSION::B : JOB_EXPORT_PCB_IPC2581::IPC2581_VERSION::C; diff --git a/pcbnew/dialogs/dialog_export_2581.fbp b/pcbnew/dialogs/dialog_export_2581.fbp index a30e4d488e..8b18188945 100644 --- a/pcbnew/dialogs/dialog_export_2581.fbp +++ b/pcbnew/dialogs/dialog_export_2581.fbp @@ -897,6 +897,135 @@ onCompressCheck + + 5 + 1 + 0 + wxALIGN_CENTER_VERTICAL|wxRIGHT|wxTOP + 4 + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + BOM revision: + 0 + + 0 + + + 0 + + 1 + m_lblBomRev + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + Revision string for the BOM section. Auto-populated from schematic title block revision + + + + -1 + + + + 5 + 1 + 1 + wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT|wxTOP + 4 + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + 0 + + 1 + m_textBomRev + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + Revision string for the BOM section. Auto-populated from schematic title block revision + + + + + + diff --git a/pcbnew/dialogs/dialog_export_2581_base.cpp b/pcbnew/dialogs/dialog_export_2581_base.cpp index 63666807ea..68c3c188e7 100644 --- a/pcbnew/dialogs/dialog_export_2581_base.cpp +++ b/pcbnew/dialogs/dialog_export_2581_base.cpp @@ -91,6 +91,17 @@ DIALOG_EXPORT_2581_BASE::DIALOG_EXPORT_2581_BASE( wxWindow* parent, wxWindowID i gbSizer1->Add( m_cbCompress, wxGBPosition( 3, 0 ), wxGBSpan( 1, 2 ), wxTOP, 5 ); + m_lblBomRev = new wxStaticText( this, wxID_ANY, _("BOM revision:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_lblBomRev->Wrap( -1 ); + m_lblBomRev->SetToolTip( _("Revision string for the BOM section. Auto-populated from schematic title block revision") ); + + gbSizer1->Add( m_lblBomRev, wxGBPosition( 4, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxRIGHT|wxTOP, 5 ); + + m_textBomRev = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + m_textBomRev->SetToolTip( _("Revision string for the BOM section. Auto-populated from schematic title block revision") ); + + gbSizer1->Add( m_textBomRev, wxGBPosition( 4, 1 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT|wxTOP, 5 ); + gbSizer1->AddGrowableCol( 1 ); diff --git a/pcbnew/dialogs/dialog_export_2581_base.h b/pcbnew/dialogs/dialog_export_2581_base.h index 0258870429..22d62dd2e0 100644 --- a/pcbnew/dialogs/dialog_export_2581_base.h +++ b/pcbnew/dialogs/dialog_export_2581_base.h @@ -58,6 +58,8 @@ class DIALOG_EXPORT_2581_BASE : public DIALOG_SHIM wxStaticText* m_lblVersion; wxChoice* m_versionChoice; wxCheckBox* m_cbCompress; + wxStaticText* m_lblBomRev; + wxTextCtrl* m_textBomRev; wxStaticText* m_columnsLabel; wxStaticLine* m_staticline2; wxStaticText* m_lblOEM; diff --git a/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp b/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp index 543c414805..dace87a11a 100644 --- a/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp +++ b/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp @@ -1579,7 +1579,15 @@ wxXmlNode* PCB_IO_IPC2581::generateBOMSection( wxXmlNode* aEcadNode ) addAttribute( bomNode, "name", genString( fn.GetName(), "BOM" ) ); wxXmlNode* bomHeaderNode = appendNode( bomNode, "BomHeader" ); - addAttribute( bomHeaderNode, "revision", "1.0" ); + wxString bomRevision = m_bomRev; + + if( bomRevision.IsEmpty() ) + bomRevision = m_board->GetTitleBlock().GetRevision(); + + if( bomRevision.IsEmpty() ) + bomRevision = wxS( "1.0" ); + + addAttribute( bomHeaderNode, "revision", bomRevision ); addAttribute( bomHeaderNode, "assembly", genString( fn.GetName() ) ); wxXmlNode* stepRefNode = appendNode( bomHeaderNode, "StepRef" ); @@ -4031,6 +4039,9 @@ void PCB_IO_IPC2581::SaveBoard( const wxString& aFileName, BOARD* aBoard, if( auto it = aProperties->find( "distpn" ); it != aProperties->end() ) m_distpn = it->second.wx_str(); + if( auto it = aProperties->find( "bomrev" ); it != aProperties->end() ) + m_bomRev = it->second.wx_str(); + if( m_version == 'B' ) { for( char c = 'a'; c <= 'z'; ++c ) diff --git a/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.h b/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.h index 386ff527f8..9d3c4878c7 100644 --- a/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.h +++ b/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.h @@ -302,6 +302,7 @@ private: wxString m_mfg; //m_colDist; props["distpn"] = job->m_colDistPn; + wxString bomRev = job->m_bomRev; + + if( bomRev.IsEmpty() && brd->GetProject() ) + { + const IP2581_BOM& bomSettings = brd->GetProject()->GetProjectFile().m_IP2581Bom; + bomRev = bomSettings.bomRev; + + if( bomRev.IsEmpty() ) + bomRev = bomSettings.schRevision; + } + + if( !bomRev.IsEmpty() ) + props["bomrev"] = bomRev; + wxString tempFile = wxFileName::CreateTempFileName( wxS( "pcbnew_ipc" ) ); try {