Move IPC2581 output to REPORTER

Adds a wx_reporter_panel to the 2581 output window, shunt messages
through the reporter instance

Fixes https://gitlab.com/kicad/code/kicad/-/issues/16314
This commit is contained in:
Seth Hillbrand 2025-08-20 13:34:36 -07:00
parent c2f503774b
commit 44228bd580
7 changed files with 151 additions and 150 deletions

View File

@ -20,9 +20,11 @@
#include "dialogs/dialog_export_2581.h" #include "dialogs/dialog_export_2581.h"
#include <set> #include <set>
#include <map>
#include <vector> #include <vector>
#include <wx/filedlg.h> #include <wx/filedlg.h>
#include <wx/filefn.h>
#include <board.h> #include <board.h>
#include <footprint.h> #include <footprint.h>
@ -32,10 +34,14 @@
#include <pgm_base.h> #include <pgm_base.h>
#include <project.h> #include <project.h>
#include <project/project_file.h> #include <project/project_file.h>
#include <pcb_io/pcb_io_mgr.h>
#include <widgets/wx_html_report_panel.h>
#include <widgets/wx_progress_reporters.h>
#include <settings/settings_manager.h> #include <settings/settings_manager.h>
#include <string_utils.h> #include <string_utils.h>
#include <widgets/std_bitmap_button.h> #include <widgets/std_bitmap_button.h>
#include <jobs/job_export_pcb_ipc2581.h> #include <jobs/job_export_pcb_ipc2581.h>
#include <wx_filename.h>
@ -213,6 +219,88 @@ void DIALOG_EXPORT_2581::onDistPNChange( wxCommandEvent& event )
} }
void DIALOG_EXPORT_2581::onOKClick( wxCommandEvent& event )
{
if( m_job )
{
if( TransferDataFromWindow() )
EndModal( wxID_OK );
return;
}
if( !TransferDataFromWindow() )
return;
m_messagesPanel->Clear();
REPORTER& reporter = m_messagesPanel->Reporter();
wxFileName pcbFileName = GetOutputPath();
WX_FILENAME::ResolvePossibleSymlinks( pcbFileName );
if( pcbFileName.GetName().empty() )
{
reporter.Report( _( "The board must be saved before generating IPC-2581 file." ),
RPT_SEVERITY_ERROR );
return;
}
if( !m_parent->IsWritable( pcbFileName ) )
{
reporter.Report( wxString::Format( _( "Insufficient permissions to write file '%s'." ),
pcbFileName.GetFullPath() ),
RPT_SEVERITY_ERROR );
return;
}
wxString tempFile = wxFileName::CreateTempFileName( wxS( "pcbnew_ipc" ) );
WX_PROGRESS_REPORTER progress( this, _( "Generate IPC-2581 File" ), 5, PR_CAN_ABORT );
std::map<std::string, UTF8> props;
props[ "units" ] = TO_UTF8( GetUnitsString() );
props[ "sigfig" ] = TO_UTF8( GetPrecision() );
props[ "version" ] = TO_UTF8( wxString( GetVersion() ) );
props[ "OEMRef" ] = TO_UTF8( GetOEM() );
props[ "mpn" ] = TO_UTF8( GetMPN() );
props[ "mfg" ] = TO_UTF8( GetMfg() );
props[ "dist" ] = TO_UTF8( GetDist() );
props[ "distpn" ] = TO_UTF8( GetDistPN() );
try
{
IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( PCB_IO_MGR::IPC2581 ) );
pi->SetProgressReporter( &progress );
pi->SetReporter( &reporter );
pi->SaveBoard( tempFile, m_parent->GetBoard(), &props );
}
catch( const IO_ERROR& ioe )
{
reporter.Report( wxString::Format( _( "Error generating IPC-2581 file '%s'.\n%s" ),
pcbFileName.GetFullPath(), ioe.What() ),
RPT_SEVERITY_ERROR );
wxRemoveFile( tempFile );
return;
}
if( wxFileExists( pcbFileName.GetFullPath() ) )
wxRemoveFile( pcbFileName.GetFullPath() );
if( !wxRenameFile( tempFile, pcbFileName.GetFullPath() ) )
{
reporter.Report( wxString::Format( _( "Failed to create file '%s'." ), pcbFileName.GetFullPath() ),
RPT_SEVERITY_ERROR );
wxRemoveFile( tempFile );
return;
}
reporter.Report( _( "IPC-2581 file generated successfully." ), RPT_SEVERITY_ACTION );
}
void DIALOG_EXPORT_2581::init() void DIALOG_EXPORT_2581::init()
{ {
m_textDistributor->SetSize( m_choiceDistPN->GetSize() ); m_textDistributor->SetSize( m_choiceDistPN->GetSize() );

View File

@ -1700,6 +1700,17 @@
</object> </object>
</object> </object>
</object> </object>
<object class="sizeritem" expanded="true">
<property name="border">5</property>
<property name="flag">wxEXPAND|wxLEFT|wxRIGHT</property>
<property name="proportion">1</property>
<object class="wxPanel" expanded="false">
<property name="minimum_size">-300,150</property>
<property name="name">m_messagesPanel</property>
<property name="permission">protected</property>
<property name="subclass">WX_HTML_REPORT_PANEL; widgets/wx_html_report_panel.h; forward_declare</property>
</object>
</object>
<object class="sizeritem" expanded="true"> <object class="sizeritem" expanded="true">
<property name="border">5</property> <property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property> <property name="flag">wxALL|wxEXPAND</property>

View File

@ -103,6 +103,7 @@ private:
void onCompressCheck( wxCommandEvent& event ) override; void onCompressCheck( wxCommandEvent& event ) override;
void onMfgPNChange( wxCommandEvent& event ) override; void onMfgPNChange( wxCommandEvent& event ) override;
void onDistPNChange( wxCommandEvent& event ) override; void onDistPNChange( wxCommandEvent& event ) override;
void onOKClick( wxCommandEvent& event ) override;
void init(); void init();

View File

@ -1,11 +1,12 @@
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6) // C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6a-dirty)
// http://www.wxformbuilder.org/ // http://www.wxformbuilder.org/
// //
// PLEASE DO *NOT* EDIT THIS FILE! // PLEASE DO *NOT* EDIT THIS FILE!
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
#include "widgets/std_bitmap_button.h" #include "widgets/std_bitmap_button.h"
#include "widgets/wx_html_report_panel.h"
#include "dialog_export_2581_base.h" #include "dialog_export_2581_base.h"
@ -185,6 +186,11 @@ DIALOG_EXPORT_2581_BASE::DIALOG_EXPORT_2581_BASE( wxWindow* parent, wxWindowID i
bMainSizer->Add( bSizerMiddle, 0, wxEXPAND|wxBOTTOM, 5 ); bMainSizer->Add( bSizerMiddle, 0, wxEXPAND|wxBOTTOM, 5 );
m_messagesPanel = new WX_HTML_REPORT_PANEL( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_messagesPanel->SetMinSize( wxSize( -300,150 ) );
bMainSizer->Add( m_messagesPanel, 1, wxEXPAND|wxLEFT|wxRIGHT, 5 );
m_stdButtons = new wxStdDialogButtonSizer(); m_stdButtons = new wxStdDialogButtonSizer();
m_stdButtonsOK = new wxButton( this, wxID_OK ); m_stdButtonsOK = new wxButton( this, wxID_OK );
m_stdButtons->AddButton( m_stdButtonsOK ); m_stdButtons->AddButton( m_stdButtonsOK );

View File

@ -1,5 +1,5 @@
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6) // C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6a-dirty)
// http://www.wxformbuilder.org/ // http://www.wxformbuilder.org/
// //
// PLEASE DO *NOT* EDIT THIS FILE! // PLEASE DO *NOT* EDIT THIS FILE!
@ -11,6 +11,7 @@
#include <wx/xrc/xmlres.h> #include <wx/xrc/xmlres.h>
#include <wx/intl.h> #include <wx/intl.h>
class STD_BITMAP_BUTTON; class STD_BITMAP_BUTTON;
class WX_HTML_REPORT_PANEL;
#include "dialog_shim.h" #include "dialog_shim.h"
#include <wx/string.h> #include <wx/string.h>
@ -31,6 +32,7 @@ class STD_BITMAP_BUTTON;
#include <wx/spinctrl.h> #include <wx/spinctrl.h>
#include <wx/checkbox.h> #include <wx/checkbox.h>
#include <wx/gbsizer.h> #include <wx/gbsizer.h>
#include <wx/panel.h>
#include <wx/dialog.h> #include <wx/dialog.h>
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -68,6 +70,7 @@ class DIALOG_EXPORT_2581_BASE : public DIALOG_SHIM
wxChoice* m_choiceDistPN; wxChoice* m_choiceDistPN;
wxStaticText* m_staticText9; wxStaticText* m_staticText9;
wxTextCtrl* m_textDistributor; wxTextCtrl* m_textDistributor;
WX_HTML_REPORT_PANEL* m_messagesPanel;
wxStdDialogButtonSizer* m_stdButtons; wxStdDialogButtonSizer* m_stdButtons;
wxButton* m_stdButtonsOK; wxButton* m_stdButtonsOK;
wxButton* m_stdButtonsCancel; wxButton* m_stdButtonsCancel;

View File

@ -1284,131 +1284,7 @@ int BOARD_EDITOR_CONTROL::GenIPC2581File( const TOOL_EVENT& aEvent )
{ {
DIALOG_EXPORT_2581 dlg( m_frame ); DIALOG_EXPORT_2581 dlg( m_frame );
if( dlg.ShowModal() != wxID_OK ) dlg.ShowModal();
return 0;
wxFileName pcbFileName = dlg.GetOutputPath();
// Write through symlinks, don't replace them
WX_FILENAME::ResolvePossibleSymlinks( pcbFileName );
if( pcbFileName.GetName().empty() )
{
DisplayError( m_frame, _( "The board must be saved before generating IPC-2581 file." ) );
return 0;
}
if( !m_frame->IsWritable( pcbFileName ) )
{
DisplayError( m_frame, wxString::Format( _( "Insufficient permissions to write file '%s'." ),
pcbFileName.GetFullPath() ) );
return 0;
}
wxString tempFile = wxFileName::CreateTempFileName( wxS( "pcbnew_ipc" ) );
wxString upperTxt;
wxString lowerTxt;
WX_PROGRESS_REPORTER reporter( m_frame, _( "Generate IPC-2581 File" ), 5, PR_CAN_ABORT );
std::map<std::string, UTF8> props;
props["units"] = dlg.GetUnitsString();
props["sigfig"] = dlg.GetPrecision();
props["version"] = dlg.GetVersion();
props["OEMRef"] = dlg.GetOEM();
props["mpn"] = dlg.GetMPN();
props["mfg"] = dlg.GetMfg();
props["dist"] = dlg.GetDist();
props["distpn"] = dlg.GetDistPN();
auto saveFile =
[&]() -> bool
{
try
{
IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( PCB_IO_MGR::IPC2581 ) );
pi->SetProgressReporter( &reporter );
pi->SaveBoard( tempFile, m_frame->GetBoard(), &props );
return true;
}
catch( const IO_ERROR& ioe )
{
DisplayError( m_frame, wxString::Format( _( "Error generating IPC-2581 file '%s'.\n%s" ),
pcbFileName.GetFullPath(),
ioe.What() ) );
lowerTxt.Printf( _( "Failed to create temporary file '%s'." ), tempFile );
m_frame->SetMsgPanel( upperTxt, lowerTxt );
// In case we started a file but didn't fully write it, clean up
wxRemoveFile( tempFile );
return false;
}
};
thread_pool& tp = GetKiCadThreadPool();
auto ret = tp.submit( saveFile );
std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
while( status != std::future_status::ready )
{
reporter.KeepRefreshing();
status = ret.wait_for( std::chrono::milliseconds( 250 ) );
}
try
{
if( !ret.get() )
return 0;
}
catch( const std::exception& e )
{
wxLogError( "Exception in IPC-2581 generation: %s", e.what() );
m_frame->GetScreen()->SetContentModified( false );
return 0;
}
// Preserve the permissions of the current file
KIPLATFORM::IO::DuplicatePermissions( pcbFileName.GetFullPath(), tempFile );
if( dlg.GetCompress() )
{
wxFileName tempfn = pcbFileName;
tempfn.SetExt( FILEEXT::Ipc2581FileExtension );
wxFileName zipfn = tempFile;
zipfn.SetExt( "zip" );
{
wxFFileOutputStream fnout( zipfn.GetFullPath() );
wxZipOutputStream zip( fnout );
wxFFileInputStream fnin( tempFile );
zip.PutNextEntry( tempfn.GetFullName() );
fnin.Read( zip );
}
wxRemoveFile( tempFile );
tempFile = zipfn.GetFullPath();
}
// If save succeeded, replace the original with what we just wrote
if( !wxRenameFile( tempFile, pcbFileName.GetFullPath() ) )
{
DisplayError( m_frame, wxString::Format( _( "Error generating IPC-2581 file '%s'.\n"
"Failed to rename temporary file '%s." ),
pcbFileName.GetFullPath(),
tempFile ) );
lowerTxt.Printf( _( "Failed to rename temporary file '%s'." ),
tempFile );
m_frame->SetMsgPanel( upperTxt, lowerTxt );
}
m_frame->GetScreen()->SetContentModified( false );
return 0; return 0;
} }

View File

@ -923,7 +923,7 @@ void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAY
break; break;
} }
default: default:
wxLogError( "Unknown pad type" ); Report( _( "Pad has unsupported type; it was skipped." ), RPT_SEVERITY_WARNING );
break; break;
} }
@ -1256,8 +1256,9 @@ wxXmlNode* PCB_IO_IPC2581::generateBOMSection( wxXmlNode* aEcadNode )
if( iter == m_footprint_dict.end() ) if( iter == m_footprint_dict.end() )
{ {
wxLogError( "Footprint %s not found in dictionary", Report( wxString::Format( _( "Footprint %s not found in dictionary; BOM data may be incomplete." ),
fp->GetFPID().GetLibItemName().wx_str() ); fp->GetFPID().GetLibItemName().wx_str() ),
RPT_SEVERITY_WARNING );
continue; continue;
} }
@ -1271,8 +1272,9 @@ wxXmlNode* PCB_IO_IPC2581::generateBOMSection( wxXmlNode* aEcadNode )
} }
else else
{ {
wxLogError( "Footprint %s not found in OEMRef dictionary", Report( wxString::Format( _( "Component \"%s\" missing OEM reference; BOM entry will be skipped." ),
fp->GetFPID().GetLibItemName().wx_str() ); fp->GetFPID().GetLibItemName().wx_str() ),
RPT_SEVERITY_WARNING );
} }
entry->m_OEMDesignRef = genString( entry->m_OEMDesignRef, "REF" ); entry->m_OEMDesignRef = genString( entry->m_OEMDesignRef, "REF" );
@ -2253,7 +2255,7 @@ void PCB_IO_IPC2581::generateProfile( wxXmlNode* aStepNode )
if( ! m_board->GetBoardPolygonOutlines( board_outline ) ) if( ! m_board->GetBoardPolygonOutlines( board_outline ) )
{ {
wxLogError( "Failed to get board outline" ); Report( _( "Board outline is invalid or missing. Please run DRC." ), RPT_SEVERITY_ERROR );
return; return;
} }
@ -2614,7 +2616,8 @@ void PCB_IO_IPC2581::generateComponents( wxXmlNode* aStepNode )
} }
if( !m_OEMRef_dict.emplace( fp, name ).second ) if( !m_OEMRef_dict.emplace( fp, name ).second )
wxLogError( "Duplicate footprint pointers. Please report this bug." ); Report( _( "Duplicate footprint pointers encountered; IPC-2581 output may be incorrect." ),
RPT_SEVERITY_ERROR );
addAttribute( componentNode, "part", genString( name, "REF" ) ); addAttribute( componentNode, "part", genString( name, "REF" ) );
addAttribute( componentNode, "layerRef", m_layer_name_map[fp->GetLayer()] ); addAttribute( componentNode, "layerRef", m_layer_name_map[fp->GetLayer()] );
@ -2814,7 +2817,8 @@ void PCB_IO_IPC2581::generateLayerSetDrill( wxXmlNode* aLayerNode )
if( it == m_padstack_dict.end() ) if( it == m_padstack_dict.end() )
{ {
wxLogError( "Failed to find padstack for via" ); Report( _( "Via uses unsupported padstack; omitted from drill data." ),
RPT_SEVERITY_WARNING );
continue; continue;
} }
@ -2839,7 +2843,8 @@ void PCB_IO_IPC2581::generateLayerSetDrill( wxXmlNode* aLayerNode )
if( it == m_padstack_dict.end() ) if( it == m_padstack_dict.end() )
{ {
wxLogError( "Failed to find padstack for pad" ); Report( _( "Pad uses unsupported padstack; hole was omitted from drill data." ),
RPT_SEVERITY_WARNING );
continue; continue;
} }
@ -2899,6 +2904,8 @@ void PCB_IO_IPC2581::generateLayerSetNet( wxXmlNode* aLayerNode, PCB_LAYER_ID aL
wxXmlNode* teardropLayerSetNode = nullptr; wxXmlNode* teardropLayerSetNode = nullptr;
wxXmlNode* teardropFeatureSetNode = nullptr; wxXmlNode* teardropFeatureSetNode = nullptr;
bool teardrop_warning = false;
if( BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( *it ); if( BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( *it );
IsCopperLayer( aLayer ) && item ) IsCopperLayer( aLayer ) && item )
{ {
@ -2954,25 +2961,34 @@ void PCB_IO_IPC2581::generateLayerSetNet( wxXmlNode* aLayerNode, PCB_LAYER_ID aL
{ {
wxXmlNode* zoneFeatureNode = specialNode; wxXmlNode* zoneFeatureNode = specialNode;
if( zone->IsTeardropArea() && m_version > 'B' ) if( zone->IsTeardropArea() )
{ {
if( !teardropFeatureSetNode ) if( m_version > 'B' )
{ {
teardropLayerSetNode = appendNode( aLayerNode, "Set" ); if( !teardropFeatureSetNode )
addAttribute( teardropLayerSetNode, "geometryUsage", "TEARDROP" );
if( zone->GetNetCode() > 0 )
{ {
addAttribute( teardropLayerSetNode, "net", teardropLayerSetNode = appendNode( aLayerNode, "Set" );
genString( zone->GetNetname(), "NET" ) ); addAttribute( teardropLayerSetNode, "geometryUsage", "TEARDROP" );
if( zone->GetNetCode() > 0 )
{
addAttribute( teardropLayerSetNode, "net",
genString( zone->GetNetname(), "NET" ) );
}
wxXmlNode* new_teardrops = appendNode( teardropLayerSetNode, "Features" );
addLocationNode( new_teardrops, 0.0, 0.0 );
teardropFeatureSetNode = appendNode( new_teardrops, "UserSpecial" );
} }
wxXmlNode* new_teardrops = appendNode( teardropLayerSetNode, "Features" ); zoneFeatureNode = teardropFeatureSetNode;
addLocationNode( new_teardrops, 0.0, 0.0 ); }
teardropFeatureSetNode = appendNode( new_teardrops, "UserSpecial" ); else if( !teardrop_warning )
{
Report( _( "Teardrops are not supported in IPC-2581 revision B; they were exported as zones." ),
RPT_SEVERITY_WARNING );
teardrop_warning = true;
} }
zoneFeatureNode = teardropFeatureSetNode;
} }
else else
{ {
@ -3448,7 +3464,7 @@ void PCB_IO_IPC2581::SaveBoard( const wxString& aFileName, BOARD* aBoard,
if( !m_xml_doc->Save( out_stream ) ) if( !m_xml_doc->Save( out_stream ) )
{ {
wxLogError( _( "Failed to save file to buffer" ) ); Report( _( "Failed to save IPC-2581 data to buffer." ), RPT_SEVERITY_ERROR );
return; return;
} }